2431 lines
89 KiB
C++
2431 lines
89 KiB
C++
#include "MenuManager.h"
|
|
#include "Game.h"
|
|
#include "Localization.h"
|
|
#include "items/ItemRegistry.h"
|
|
#include "render/TextRenderer.h"
|
|
#include "utils/Utils.h"
|
|
#include <iostream>
|
|
#include <fstream>
|
|
#include <algorithm>
|
|
#include <string>
|
|
#include <SDL.h>
|
|
|
|
namespace FRG {
|
|
|
|
// Localization
|
|
static const std::string EMPTY_LANGUAGE_RU = u8"(пусто)";
|
|
static const std::string EMPTY_LANGUAGE_EN = "(empty)";
|
|
|
|
static const std::string BACK_TO_MAIN_MENU_RU = u8"Вернуться в меню";
|
|
static const std::string BACK_TO_MAIN_MENU_EN = "Back to Main Menu";
|
|
|
|
|
|
static int questStatusPriority(Quest::QuestStatus status) {
|
|
switch (status) {
|
|
case Quest::QuestStatus::Available: return 0;
|
|
case Quest::QuestStatus::Completed: return 1;
|
|
case Quest::QuestStatus::Failed: return 2;
|
|
default: return 3;
|
|
}
|
|
}
|
|
|
|
static int countWrappedLines(const std::string& text, const TextRenderer& tr, float maxWidth) {
|
|
if (text.empty()) return 1;
|
|
int lines = 0;
|
|
std::string currentLine;
|
|
|
|
auto flushLine = [&]() { ++lines; currentLine.clear(); };
|
|
|
|
auto pushWord = [&](const std::string& word) {
|
|
if (word.empty()) return;
|
|
if (currentLine.empty()) {
|
|
currentLine = word;
|
|
} else {
|
|
const std::string candidate = currentLine + " " + word;
|
|
if (tr.measureTextWidth(candidate) <= maxWidth) {
|
|
currentLine = candidate;
|
|
} else {
|
|
flushLine();
|
|
currentLine = word;
|
|
}
|
|
}
|
|
};
|
|
|
|
std::string currentWord;
|
|
for (char ch : text) {
|
|
if (ch == '\n') {
|
|
pushWord(currentWord); currentWord.clear(); flushLine();
|
|
} else if (ch == ' ' || ch == '\t' || ch == '\r') {
|
|
pushWord(currentWord); currentWord.clear();
|
|
} else {
|
|
currentWord.push_back(ch);
|
|
}
|
|
}
|
|
pushWord(currentWord);
|
|
if (!currentLine.empty()) flushLine();
|
|
|
|
return max(1, lines);
|
|
}
|
|
|
|
static std::string trimChatPreview(const std::string& msg, int maxChars = 22) {
|
|
int charCount = 0;
|
|
int bytePos = 0;
|
|
const int len = static_cast<int>(msg.size());
|
|
while (bytePos < len && charCount < maxChars) {
|
|
const unsigned char c = static_cast<unsigned char>(msg[bytePos]);
|
|
if (c < 0x80) bytePos += 1;
|
|
else if (c < 0xE0) bytePos += 2;
|
|
else if (c < 0xF0) bytePos += 3;
|
|
else bytePos += 4;
|
|
++charCount;
|
|
}
|
|
if (bytePos >= len) return msg;
|
|
return msg.substr(0, bytePos) + "...";
|
|
}
|
|
|
|
static std::string formatMoney(int amount) {
|
|
bool negative = amount < 0;
|
|
std::string digits = std::to_string(negative ? -amount : amount);
|
|
std::string result;
|
|
const int len = static_cast<int>(digits.size());
|
|
for (int i = 0; i < len; ++i) {
|
|
if (i > 0 && (len - i) % 3 == 0) result += ' ';
|
|
result += digits[i];
|
|
}
|
|
if (negative) result = "-" + result;
|
|
|
|
|
|
//Localization
|
|
if (g_currentLanguage == Language::English)
|
|
{
|
|
return result + " som";
|
|
}
|
|
return result + u8" сом";
|
|
}
|
|
|
|
static std::array<float, 4> questStatusColor(Quest::QuestStatus status) {
|
|
switch (status) {
|
|
case Quest::QuestStatus::Completed: return { 0.25f, 0.95f, 0.35f, 1.0f };
|
|
case Quest::QuestStatus::Failed: return { 1.0f, 0.25f, 0.25f, 1.0f };
|
|
case Quest::QuestStatus::Available: return { 1.0f, 1.0f, 1.0f, 1.0f };
|
|
default: return { 0.45f, 0.45f, 0.45f, 1.0f };
|
|
}
|
|
}
|
|
|
|
MenuManager::MenuManager(Renderer& iRenderer, GameState& gameState, AudioPlayerAsync& audioPlayer) :
|
|
renderer(iRenderer),
|
|
gameState_(gameState),
|
|
audioPlayer_(audioPlayer)
|
|
{
|
|
//Localization
|
|
localizedLocationName["uni_interior"][Language::Russian] = u8"Университет, 3 этаж";
|
|
localizedLocationName["uni_interior"][Language::English] = "University, 3rd floor";
|
|
|
|
localizedLocationName["uni_exterior"][Language::Russian] = u8"Университет, двор";
|
|
localizedLocationName["uni_exterior"][Language::English] = "University, exterior";
|
|
|
|
localizedLocationName["location_dorm"][Language::Russian] = u8"Общежитие, 1 этаж";
|
|
localizedLocationName["location_dorm"][Language::English] = "Dormitory, 1st floor";
|
|
}
|
|
|
|
std::shared_ptr<UiNode> MenuManager::loadLocalizedUi(const std::string& basePath, const std::string& zipFile) {
|
|
// Service the OS message queue between UI screens so the window doesn't
|
|
// get flagged as "not responding" while loading the ~50 UI layout files.
|
|
SDL_PumpEvents();
|
|
static const std::string marker = "resources/w/ui/";
|
|
if (basePath.rfind(marker, 0) == 0) {
|
|
const std::string localizedPath = "resources/w/ui_" + languageToCode(g_currentLanguage) + "/" + basePath.substr(marker.size());
|
|
try {
|
|
return loadUiFromFile(localizedPath, renderer, zipFile);
|
|
} catch (const std::exception&) {
|
|
// Localized screen not available — fall back to the base path below.
|
|
}
|
|
}
|
|
return loadUiFromFile(basePath, renderer, zipFile);
|
|
}
|
|
|
|
std::shared_ptr<Texture> MenuManager::loadLocalizedTexture(const std::string& basePath, const std::string& zipFile) {
|
|
static const std::string marker = "resources/w/ui/";
|
|
if (basePath.rfind(marker, 0) == 0) {
|
|
const std::string localizedPath = "resources/w/ui_" + languageToCode(g_currentLanguage) + "/" + basePath.substr(marker.size());
|
|
try {
|
|
return renderer.textureManager.LoadFromPng(localizedPath, zipFile, true);
|
|
}
|
|
catch (const std::exception&) {
|
|
// Localized screen not available — fall back to the base path below.
|
|
}
|
|
}
|
|
return renderer.textureManager.LoadFromPng(basePath, zipFile, true);
|
|
}
|
|
|
|
void MenuManager::loadUiRoots(const std::string& zipFile) {
|
|
hudRoot = loadLocalizedUi("resources/w/ui/hud_step0.json", zipFile);
|
|
hudStep1Root = loadLocalizedUi("resources/w/ui/hud_step1.json", zipFile);
|
|
hudStep2Root = loadLocalizedUi("resources/w/ui/hud_step2.json", zipFile);
|
|
hudStep3Root = loadLocalizedUi("resources/w/ui/hud_step3.json", zipFile);
|
|
hudStep4Root = loadLocalizedUi("resources/w/ui/hud_step4.json", zipFile);
|
|
hudStep5aRoot = loadLocalizedUi("resources/w/ui/hud_step5a.json", zipFile);
|
|
hudStep5bRoot = loadLocalizedUi("resources/w/ui/hud_step5b.json", zipFile);
|
|
hudStep5abRoot = loadLocalizedUi("resources/w/ui/hud_step5ab.json", zipFile);
|
|
hudUniExtRoot = loadLocalizedUi("resources/w/ui/hud_uni_ext.json", zipFile);
|
|
hudUniIntStep10Root = loadLocalizedUi("resources/w/ui/hud_uni_int_step10.json", zipFile);
|
|
hudUniIntStep11Root = loadLocalizedUi("resources/w/ui/hud_uni_int_step11.json", zipFile);
|
|
hudUniIntStep12Root = loadLocalizedUi("resources/w/ui/hud_uni_int_step12.json", zipFile);
|
|
hudUniIntStep13Root = loadLocalizedUi("resources/w/ui/hud_uni_int_step13.json", zipFile);
|
|
hudUniIntFullRoot = loadLocalizedUi("resources/w/ui/hud_uni_int_full.json", zipFile);
|
|
hudUniIntDarkFullRoot = loadLocalizedUi("resources/w/ui/hud_uni_int_dark_full.json", zipFile);
|
|
hudUniExtDarkRoot = loadLocalizedUi("resources/w/ui/hud_uni_ext_dark.json", zipFile);
|
|
|
|
hudCutsceneRoot_ = loadLocalizedUi("resources/w/ui/hud_cutscene.json", zipFile);
|
|
hudTopHintRoot_ = loadLocalizedUi("resources/w/ui/hud_top_hint01.json", zipFile);
|
|
|
|
phoneMainRoot = loadLocalizedUi("resources/w/ui/screen_phone.json", zipFile);
|
|
phoneMainHintARoot = loadLocalizedUi("resources/w/ui/screen_phone_hint001.json", zipFile);
|
|
phoneMainHintBRoot = loadLocalizedUi("resources/w/ui/screen_phone_hint002.json", zipFile);
|
|
phoneMainHintABRoot = loadLocalizedUi("resources/w/ui/screen_phone_hint001_002.json", zipFile);
|
|
|
|
phoneBankRoot = loadLocalizedUi("resources/w/ui/screen_phone_bank.json", zipFile);
|
|
phoneVideoRoot = loadLocalizedUi("resources/w/ui/screen_phone_video.json", zipFile);
|
|
phoneMapDormRoot = loadLocalizedUi("resources/w/ui/screen_phone_map_dorm.json", zipFile);
|
|
phoneMapUniRoot = loadLocalizedUi("resources/w/ui/screen_phone_map_uni.json", zipFile);
|
|
|
|
verticalPhoneMainRoot = loadLocalizedUi("resources/w/ui/portrait_screen_phone.json", zipFile);
|
|
verticalPhoneMainHintARoot = loadLocalizedUi("resources/w/ui/portrait_screen_phone_hint001.json", zipFile);
|
|
verticalPhoneMainHintBRoot = loadLocalizedUi("resources/w/ui/portrait_screen_phone_hint002.json", zipFile);
|
|
verticalPhoneMainHintABRoot= loadLocalizedUi("resources/w/ui/portrait_screen_phone_hint001_002.json", zipFile);
|
|
verticalPhoneBankRoot = loadLocalizedUi("resources/w/ui/portrait_screen_phone_bank.json", zipFile);
|
|
verticalPhoneVideoRoot = loadLocalizedUi("resources/w/ui/portrait_screen_phone_video.json", zipFile);
|
|
verticalPhoneMapDormRoot = loadLocalizedUi("resources/w/ui/portrait_screen_phone_map_dorm.json", zipFile);
|
|
verticalPhoneMapUniRoot = loadLocalizedUi("resources/w/ui/portrait_screen_phone_map_uni.json", zipFile);
|
|
|
|
phoneChatListRoot = loadLocalizedUi("resources/w/ui/screen_phone_chat_list.json", zipFile);
|
|
phoneChatListHintRoot = loadLocalizedUi("resources/w/ui/screen_phone_chat_list_hint001.json", zipFile);
|
|
verticalPhoneChatListRoot = loadLocalizedUi("resources/w/ui/portrait_screen_phone_chat_list.json", zipFile);
|
|
verticalPhoneChatListHintRoot = loadLocalizedUi("resources/w/ui/portrait_screen_phone_chat_list_hint001.json", zipFile);
|
|
phoneChat1Root = loadLocalizedUi("resources/w/ui/screen_phone_chat1.json", zipFile);
|
|
phoneChat2Root = loadLocalizedUi("resources/w/ui/screen_phone_chat2.json", zipFile);
|
|
phoneChat3Root = loadLocalizedUi("resources/w/ui/screen_phone_chat3.json", zipFile);
|
|
verticalPhoneChat1Root = loadLocalizedUi("resources/w/ui/portrait_screen_phone_chat1.json", zipFile);
|
|
verticalPhoneChat2Root = loadLocalizedUi("resources/w/ui/portrait_screen_phone_chat2.json", zipFile);
|
|
verticalPhoneChat3Root = loadLocalizedUi("resources/w/ui/portrait_screen_phone_chat3.json", zipFile);
|
|
|
|
modalMenuRoot = loadLocalizedUi("resources/w/ui/screen_modal_menu.json", zipFile);
|
|
newInventoryRoot = loadLocalizedUi("resources/w/ui/screen_inventory.json", zipFile);
|
|
portraitInventoryListRoot = loadLocalizedUi("resources/w/ui/portrait_screen_inventory.json", zipFile);
|
|
portraitInventoryItemRoot = loadLocalizedUi("resources/w/ui/portrait_screen_inventory_item.json", zipFile);
|
|
questJournalRoot = loadLocalizedUi("resources/w/ui/screen_journal.json", zipFile);
|
|
portraitQuestJournalListRoot = loadLocalizedUi("resources/w/ui/portrait_screen_journal.json", zipFile);
|
|
portraitQuestJournalItemRoot = loadLocalizedUi("resources/w/ui/portrait_screen_journal_item.json", zipFile);
|
|
mainMenuRoot = loadLocalizedUi("resources/w/ui/screen_main_menu.json", zipFile);
|
|
aboutScreenRoot = loadLocalizedUi("resources/w/ui/screen_about.json", zipFile);
|
|
creditsScreenRoot = loadLocalizedUi("resources/w/ui/screen_credits.json", zipFile);
|
|
settingsScreenRoot = loadLocalizedUi("resources/w/ui/screen_settings.json", zipFile);
|
|
settingsGraphicsScreenRoot = loadLocalizedUi("resources/w/ui/screen_settings_graphics.json", zipFile);
|
|
languageScreenRoot = loadLocalizedUi("resources/w/ui/screen_language.json", zipFile);
|
|
loadSavedGameScreenRoot = loadLocalizedUi("resources/w/ui/screen_load.json", zipFile);
|
|
saveGameScreenRoot = loadLocalizedUi("resources/w/ui/screen_save.json", zipFile);
|
|
}
|
|
|
|
void MenuManager::setup(Inventory& inv, const std::string& zipFile) {
|
|
inventory = &inv;
|
|
|
|
loadUiRoots(zipFile);
|
|
|
|
texObjectiveCompleted_ = renderer.textureManager.LoadFromPng("resources/w/ui/img/journal/quest_objective_completed.png", zipFile, true);
|
|
texObjectiveBlank_ = renderer.textureManager.LoadFromPng("resources/w/ui/img/journal/quest_objective_blank.png", zipFile, true);
|
|
|
|
texItemSelected_ = renderer.textureManager.LoadFromPng("resources/w/ui/img/journal/ButtonBkg001.png", zipFile, true);
|
|
texItemTransparent_ = renderer.textureManager.LoadFromPng("resources/w/ui/img/journal/ButtonBkgTransparent001.png", zipFile, true);
|
|
|
|
gameState_.questJournal.loadFromFile("resources/config/quests.json", zipFile);
|
|
|
|
toastTextureQuestNew = loadLocalizedTexture("resources/w/ui/img/toast/quest_new001.png", zipFile);
|
|
toastTextureQuestCompleted = loadLocalizedTexture("resources/w/ui/img/toast/quest_completed001.png", zipFile);
|
|
toastTextureQuestFailed = loadLocalizedTexture("resources/w/ui/img/toast/quest_failed001.png", zipFile);
|
|
toastTextureItemAdded = loadLocalizedTexture("resources/w/ui/img/toast/item_received001.png", zipFile);
|
|
toastTextureItemRemoved = loadLocalizedTexture("resources/w/ui/img/toast/item_removed001.png", zipFile);
|
|
|
|
languageLoadingRu = renderer.textureManager.LoadFromPng("resources/loading_land003_ru.png", zipFile);
|
|
languageLoadingEn = renderer.textureManager.LoadFromPng("resources/loading_land003_en.png", zipFile);
|
|
|
|
gameState_.questJournal.onQuestUnlocked = [this](const std::string& id) {
|
|
const Quest::QuestState* q = gameState_.questJournal.findQuest(id);
|
|
if (q) showToast(toastTextureQuestNew, q->definition.title);
|
|
};
|
|
gameState_.questJournal.onQuestCompleted = [this](const std::string& id) {
|
|
const Quest::QuestState* q = gameState_.questJournal.findQuest(id);
|
|
if (q) showToast(toastTextureQuestCompleted, q->definition.title);
|
|
};
|
|
gameState_.questJournal.onQuestFailed = [this](const std::string& id) {
|
|
const Quest::QuestState* q = gameState_.questJournal.findQuest(id);
|
|
if (q) showToast(toastTextureQuestFailed, q->definition.title);
|
|
};
|
|
|
|
const std::string imgDir = "resources/w/ui/img/phone/";
|
|
texBubbleInCenter_ = renderer.textureManager.LoadFromPng(imgDir + "bubble_in_center.png", zipFile, true);
|
|
texBubbleInLT_ = renderer.textureManager.LoadFromPng(imgDir + "bubble_in_corner_left_top.png", zipFile, true);
|
|
texBubbleInLB_ = renderer.textureManager.LoadFromPng(imgDir + "bubble_in_corner_left_bottom.png", zipFile, true);
|
|
texBubbleInRT_ = renderer.textureManager.LoadFromPng(imgDir + "bubble_in_corner_right_top.png", zipFile, true);
|
|
texBubbleInRB_ = renderer.textureManager.LoadFromPng(imgDir + "bubble_in_corner_right_bottom.png",zipFile, true);
|
|
texBubbleOutCenter_ = renderer.textureManager.LoadFromPng(imgDir + "bubble_out_center.png", zipFile, true);
|
|
texBubbleOutLT_ = renderer.textureManager.LoadFromPng(imgDir + "bubble_out_corner_left_top.png", zipFile, true);
|
|
texBubbleOutLB_ = renderer.textureManager.LoadFromPng(imgDir + "bubble_out_corner_left_bottom.png",zipFile, true);
|
|
texBubbleOutRT_ = renderer.textureManager.LoadFromPng(imgDir + "bubble_out_corner_right_top.png", zipFile, true);
|
|
texBubbleOutRB_ = renderer.textureManager.LoadFromPng(imgDir + "bubble_out_corner_right_bottom.png",zipFile, true);
|
|
|
|
showMainMenu();
|
|
}
|
|
|
|
void MenuManager::enterGameplay(bool isFromLoad) {
|
|
if (!isFromLoad && uiState_ == GameUiState::MainMenu && startGameFunc) startGameFunc();
|
|
uiState_ = GameUiState::Gameplay;
|
|
uiManager.replaceRoot(hudRoot);
|
|
topUiManager.replaceRoot(hudTopHintRoot_);
|
|
if (isFromLoad) {
|
|
uiManager.clearMenuStack();
|
|
topUiManager.clearMenuStack();
|
|
}
|
|
|
|
applyCurrentHealthBar();
|
|
|
|
uiManager.setButtonCallback("inventoryButton", [this](const std::string&) {
|
|
openInventory();
|
|
});
|
|
if (uiManager.findButton("settingsButton")) {
|
|
uiManager.setButtonCallback("settingsButton", [this](const std::string&) {
|
|
showModalMenuScreen();
|
|
});
|
|
}
|
|
|
|
if (isFromLoad)
|
|
applyHudForCurrentState();
|
|
}
|
|
|
|
void MenuManager::applyHudForCurrentState() {
|
|
// Clear the step-0 dialogue hint unless we're actually at step 0.
|
|
if (gameState_.tutorialStep != TutorialStep::Step0)
|
|
topUiManager.replaceRoot(nullptr);
|
|
|
|
const std::string& loc = gameState_.currentLocationName;
|
|
|
|
// Non-dorm locations: reuse the existing location-change logic.
|
|
if (loc == "uni_exterior" || loc == "uni_interior") {
|
|
onLocationChanged(loc);
|
|
return;
|
|
}
|
|
|
|
// Dorm (or pre-first-teleport): select HUD root by tutorial step.
|
|
std::shared_ptr<UiNode> nextRoot;
|
|
switch (gameState_.tutorialStep) {
|
|
case TutorialStep::Step0: nextRoot = hudRoot; break;
|
|
case TutorialStep::Step1: nextRoot = hudStep1Root; break;
|
|
case TutorialStep::Step2: nextRoot = hudStep2Root; break;
|
|
case TutorialStep::Step3: nextRoot = hudStep3Root; break;
|
|
case TutorialStep::Step4: nextRoot = hudStep4Root; break;
|
|
default: break; // Step5/6 handled below
|
|
}
|
|
|
|
if (nextRoot) {
|
|
uiManager.replaceRoot(nextRoot);
|
|
applyCurrentHealthBar();
|
|
uiManager.setButtonCallback("inventoryButton", [this](const std::string&) { openInventory(); });
|
|
if (uiManager.findButton("settingsButton"))
|
|
uiManager.setButtonCallback("settingsButton", [this](const std::string&) { showModalMenuScreen(); });
|
|
} else {
|
|
// Step5 or Step6: pick the right variant and apply hint visibility.
|
|
refreshItemPickupHud();
|
|
}
|
|
}
|
|
|
|
void MenuManager::showMainMenu() {
|
|
gameState_.currentLocationName.clear();
|
|
|
|
audioPlayer_.crossFadeMusicAsync("audio/main menu final.ogg");
|
|
|
|
uiState_ = GameUiState::MainMenu;
|
|
uiManager.clearMenuStack();
|
|
topUiManager.replaceRoot(nullptr);
|
|
topUiManager.clearMenuStack();
|
|
uiManager.replaceRoot(mainMenuRoot);
|
|
|
|
|
|
|
|
uiManager.setTextButtonCallback("menuStartButton", [this](const std::string&) {
|
|
audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg");
|
|
enterGameplay();
|
|
|
|
});
|
|
uiManager.setTextButtonCallback("menuAboutButton", [this](const std::string&) {
|
|
showAboutScreen();
|
|
audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg");
|
|
});
|
|
uiManager.setTextButtonCallback("menuCreditsButton", [this](const std::string&) {
|
|
showCreditsScreen();
|
|
audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg");
|
|
});
|
|
uiManager.setTextButtonCallback("menuSettingsButton", [this](const std::string&) {
|
|
showSettingsScreen();
|
|
audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg");
|
|
});
|
|
uiManager.setTextButtonCallback("menuLanguageButton", [this](const std::string&) {
|
|
showLanguageScreen();
|
|
audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg");
|
|
});
|
|
uiManager.setTextButtonCallback("menuLoadButton", [this](const std::string&) {
|
|
showLoadGameScreen();
|
|
audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg");
|
|
});
|
|
|
|
|
|
|
|
#if !defined(EMSCRIPTEN) && !defined(__ANDROID__)
|
|
uiManager.setTextButtonCallback("menuExitButton", [this](const std::string&) {
|
|
audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg");
|
|
Environment::exitGameLoop = true;
|
|
});
|
|
#else
|
|
uiManager.setNodeVisible("menuExitButton", false);
|
|
#endif
|
|
}
|
|
|
|
void MenuManager::showMenuAfterGameCompleted() {
|
|
//gameState_.currentLocationName.clear();
|
|
|
|
audioPlayer_.crossFadeMusicAsync("audio/main menu final.ogg");
|
|
|
|
uiState_ = GameUiState::MainMenu;
|
|
uiManager.clearMenuStack();
|
|
topUiManager.replaceRoot(nullptr);
|
|
topUiManager.clearMenuStack();
|
|
uiManager.replaceRoot(mainMenuRoot);
|
|
|
|
|
|
|
|
uiManager.setTextButtonCallback("menuStartButton", [this](const std::string&) {
|
|
audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg");
|
|
enterGameplay();
|
|
|
|
});
|
|
uiManager.setTextButtonCallback("menuAboutButton", [this](const std::string&) {
|
|
showAboutScreen();
|
|
audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg");
|
|
});
|
|
uiManager.setTextButtonCallback("menuCreditsButton", [this](const std::string&) {
|
|
showCreditsScreen();
|
|
audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg");
|
|
});
|
|
uiManager.setTextButtonCallback("menuSettingsButton", [this](const std::string&) {
|
|
showSettingsScreen();
|
|
audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg");
|
|
});
|
|
uiManager.setTextButtonCallback("menuLanguageButton", [this](const std::string&) {
|
|
showLanguageScreen();
|
|
audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg");
|
|
});
|
|
uiManager.setTextButtonCallback("menuLoadButton", [this](const std::string&) {
|
|
showLoadGameScreen();
|
|
audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg");
|
|
});
|
|
|
|
|
|
|
|
#ifndef EMSCRIPTEN
|
|
uiManager.setTextButtonCallback("menuExitButton", [this](const std::string&) {
|
|
audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg");
|
|
Environment::exitGameLoop = true;
|
|
});
|
|
#else
|
|
uiManager.setNodeVisible("menuExitButton", false);
|
|
#endif
|
|
|
|
//Now open the about screen:
|
|
uiManager.pushMenuFromSavedRoot(aboutScreenRoot);
|
|
uiManager.findTextButton("aboutBackButton")->text = (g_currentLanguage == Language::Russian) ? BACK_TO_MAIN_MENU_RU : BACK_TO_MAIN_MENU_EN;
|
|
uiManager.setTextButtonCallback("aboutBackButton", [this](const std::string&) {
|
|
uiManager.popMenu();
|
|
uiManager.updateAllLayouts();
|
|
audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg");
|
|
});
|
|
|
|
uiManager.setTextButtonCallback("steamButton", [this](const std::string&) {
|
|
audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg");
|
|
SDL_OpenURL("https://store.steampowered.com/app/4801010/");
|
|
});
|
|
|
|
uiManager.setTextButtonCallback("telegramButton", [this](const std::string&) {
|
|
audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg");
|
|
SDL_OpenURL("https://telegram.me/fishrungames");
|
|
});
|
|
|
|
uiManager.setTextButtonCallback("discordButton", [this](const std::string&) {
|
|
audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg");
|
|
SDL_OpenURL("https://discord.gg/S8jgGHXjw");
|
|
});
|
|
}
|
|
|
|
void MenuManager::showAboutScreen() {
|
|
uiManager.pushMenuFromSavedRoot(aboutScreenRoot);
|
|
uiManager.setTextButtonCallback("aboutBackButton", [this](const std::string&) {
|
|
uiManager.popMenu();
|
|
uiManager.updateAllLayouts();
|
|
audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg");
|
|
});
|
|
|
|
uiManager.setTextButtonCallback("steamButton", [this](const std::string&) {
|
|
audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg");
|
|
SDL_OpenURL("https://store.steampowered.com/app/4801010/");
|
|
});
|
|
|
|
uiManager.setTextButtonCallback("telegramButton", [this](const std::string&) {
|
|
audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg");
|
|
SDL_OpenURL("https://telegram.me/fishrungames");
|
|
});
|
|
|
|
uiManager.setTextButtonCallback("discordButton", [this](const std::string&) {
|
|
audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg");
|
|
SDL_OpenURL("https://discord.gg/S8jgGHXjw");
|
|
});
|
|
}
|
|
|
|
void MenuManager::showCreditsScreen() {
|
|
uiManager.pushMenuFromSavedRoot(creditsScreenRoot);
|
|
uiManager.setButtonCallback("creditsBackButton", [this](const std::string&) {
|
|
uiManager.popMenu();
|
|
uiManager.updateAllLayouts();
|
|
audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg");
|
|
});
|
|
|
|
|
|
uiManager.stopAnimationOnNode("creditsLinearLayout", "slowScroll");
|
|
uiManager.resetAnimationOnNode("creditsLinearLayout");
|
|
uiManager.startAnimationOnNode("creditsLinearLayout", "slowScroll");
|
|
}
|
|
|
|
void MenuManager::showSettingsScreen() {
|
|
uiManager.pushMenuFromSavedRoot(settingsScreenRoot);
|
|
uiManager.setTextButtonCallback("settingsBackButton", [this](const std::string&) {
|
|
saveSettings();
|
|
uiManager.popMenu();
|
|
uiManager.updateAllLayouts();
|
|
audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg");
|
|
});
|
|
|
|
uiManager.setTextButtonCallback("graphicsButton", [this](const std::string&) {
|
|
showSettingsGraphicsScreen();
|
|
audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg");
|
|
});
|
|
|
|
// Set initial slider positions before registering callbacks so
|
|
// setSliderValue does not fire an unregistered callback.
|
|
const float musicFrac = audioPlayer_.getMusicVolume() / 128.0f;
|
|
const float soundFrac = audioPlayer_.getSoundVolume() / 128.0f;
|
|
uiManager.setSliderValue("musicVolumeSlider", musicFrac);
|
|
uiManager.setSliderValue("soundVolumeSlider", soundFrac);
|
|
|
|
// Localization
|
|
if (g_currentLanguage == Language::English)
|
|
{
|
|
musicVolumePrefix = "Music volume: ";
|
|
soundVolumePrefix = "Sound volume: ";
|
|
musicToggleOn = "Music: ON";
|
|
musicToggleOff = "Music: OFF";
|
|
soundToggleOn = "Sound: ON";
|
|
soundToggleOff = "Sound: OFF";
|
|
}
|
|
else if (g_currentLanguage == Language::Russian)
|
|
{
|
|
musicVolumePrefix = u8"Громкость музыки: ";
|
|
soundVolumePrefix = u8"Громкость звука: ";
|
|
musicToggleOn = u8"Музыка: ВКЛ";
|
|
musicToggleOff = u8"Музыка: ВЫКЛ";
|
|
soundToggleOn = u8"Звук: ВКЛ";
|
|
soundToggleOff = u8"Звук: ВЫКЛ";
|
|
}
|
|
|
|
uiManager.setText("musicVolumeText",
|
|
musicVolumePrefix + std::to_string(audioPlayer_.getMusicVolume()));
|
|
uiManager.setText("soundVolumeText",
|
|
soundVolumePrefix + std::to_string(audioPlayer_.getSoundVolume()));
|
|
|
|
uiManager.setSliderCallback("musicVolumeSlider",
|
|
[this](const std::string&, float value) {
|
|
const int vol = static_cast<int>(value * 128.0f + 0.5f);
|
|
audioPlayer_.setMusicVolume(vol);
|
|
uiManager.setText("musicVolumeText",
|
|
musicVolumePrefix + std::to_string(vol));
|
|
});
|
|
uiManager.setSliderCallback("soundVolumeSlider",
|
|
[this](const std::string&, float value) {
|
|
const int vol = static_cast<int>(value * 128.0f + 0.5f);
|
|
audioPlayer_.setSoundVolume(vol);
|
|
uiManager.setText("soundVolumeText",
|
|
soundVolumePrefix + std::to_string(vol));
|
|
});
|
|
|
|
// Music on/off toggle — helper to sync button text and color from current state
|
|
auto refreshMusicToggle = [this]() {
|
|
const bool on = audioPlayer_.isMusicEnabled();
|
|
uiManager.setTextButtonText("musicToggleButton", on ? musicToggleOn : musicToggleOff);
|
|
/*uiManager.setTextButtonColor("musicToggleButton", on
|
|
? std::array<float, 4>{ 0.2f, 0.9f, 0.2f, 1.0f }
|
|
: std::array<float, 4>{ 0.9f, 0.2f, 0.2f, 1.0f });*/
|
|
};
|
|
auto refreshSoundToggle = [this]() {
|
|
const bool on = audioPlayer_.isSoundEnabled();
|
|
uiManager.setTextButtonText("soundToggleButton", on ? soundToggleOn : soundToggleOff);
|
|
/*uiManager.setTextButtonColor("soundToggleButton", on
|
|
? std::array<float, 4>{ 0.2f, 0.9f, 0.2f, 1.0f }
|
|
: std::array<float, 4>{ 0.9f, 0.2f, 0.2f, 1.0f });*/
|
|
};
|
|
|
|
refreshMusicToggle();
|
|
refreshSoundToggle();
|
|
|
|
uiManager.setTextButtonCallback("musicToggleButton", [this](const std::string&) {
|
|
audioPlayer_.setMusicEnabled(!audioPlayer_.isMusicEnabled());
|
|
const bool on = audioPlayer_.isMusicEnabled();
|
|
uiManager.setTextButtonText("musicToggleButton", on ? musicToggleOn : musicToggleOff);
|
|
/*uiManager.setTextButtonColor("musicToggleButton", on
|
|
? std::array<float, 4>{ 0.2f, 0.9f, 0.2f, 1.0f }
|
|
: std::array<float, 4>{ 0.9f, 0.2f, 0.2f, 1.0f });*/
|
|
audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg");
|
|
});
|
|
uiManager.setTextButtonCallback("soundToggleButton", [this](const std::string&) {
|
|
audioPlayer_.setSoundEnabled(!audioPlayer_.isSoundEnabled());
|
|
const bool on = audioPlayer_.isSoundEnabled();
|
|
uiManager.setTextButtonText("soundToggleButton", on ? soundToggleOn : soundToggleOff);
|
|
/*uiManager.setTextButtonColor("soundToggleButton", on
|
|
? std::array<float, 4>{ 0.2f, 0.9f, 0.2f, 1.0f }
|
|
: std::array<float, 4>{ 0.9f, 0.2f, 0.2f, 1.0f });*/
|
|
audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg");
|
|
});
|
|
}
|
|
|
|
void MenuManager::showSettingsGraphicsScreen() {
|
|
uiManager.pushMenuFromSavedRoot(settingsGraphicsScreenRoot);
|
|
uiManager.setTextButtonCallback("settingsBackButton", [this](const std::string&) {
|
|
saveSettings();
|
|
uiManager.popMenu();
|
|
audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg");
|
|
uiManager.updateAllLayouts();
|
|
topUiManager.updateAllLayouts();
|
|
});
|
|
|
|
|
|
// Localization
|
|
if (g_currentLanguage == Language::English)
|
|
{
|
|
fullScreenToggleOn = "Full Screen: ON";
|
|
fullScreenToggleOff = "Full Screen: OFF";
|
|
shadowToggleOn = "Shadows: ON";
|
|
shadowToggleOff = "Shadows: OFF";
|
|
}
|
|
else if (g_currentLanguage == Language::Russian)
|
|
{
|
|
fullScreenToggleOn = u8"Полноэкранный режим: ВКЛ";
|
|
fullScreenToggleOff = u8"Полноэкранный режим: ВЫКЛ";
|
|
shadowToggleOn = u8"Тени: ВКЛ";
|
|
shadowToggleOff = u8"Тени: ВЫКЛ";
|
|
}
|
|
|
|
auto refreshFullscreenToggle = [this]() {
|
|
const bool on = Environment::isFullscreen;
|
|
uiManager.setTextButtonText("fullScreenToggleButton", on ? fullScreenToggleOn : fullScreenToggleOff);
|
|
};
|
|
|
|
auto refreshShadowToggle = [this]() {
|
|
const bool on = this->shadowsEnabled;
|
|
uiManager.setTextButtonText("shadowToggleButton", on ? shadowToggleOn : shadowToggleOff);
|
|
};
|
|
|
|
|
|
refreshFullscreenToggle();
|
|
refreshShadowToggle();
|
|
|
|
uiManager.setTextButtonCallback("fullScreenToggleButton", [this, refreshFullscreenToggle](const std::string&) {
|
|
Environment::setFullscreen(!Environment::isFullscreen);
|
|
refreshFullscreenToggle();
|
|
audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg");
|
|
uiManager.updateAllLayouts();
|
|
topUiManager.updateAllLayouts();
|
|
});
|
|
|
|
uiManager.setTextButtonCallback("shadowToggleButton", [this, refreshShadowToggle](const std::string&) {
|
|
bool newValue = !this->shadowsEnabled;
|
|
shadowMapSettingsChangedFunc(newValue);
|
|
refreshShadowToggle();
|
|
audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg");
|
|
});
|
|
}
|
|
|
|
void MenuManager::showLanguageScreen() {
|
|
uiManager.pushMenuFromSavedRoot(languageScreenRoot);
|
|
uiManager.setTextButtonCallback("languageBackButton", [this](const std::string&) {
|
|
saveSettings();
|
|
uiManager.popMenu();
|
|
uiManager.updateAllLayouts();
|
|
audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg");
|
|
});
|
|
|
|
uiManager.setTextButtonCallback("languageRussianButton", [this](const std::string&) {
|
|
audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg");
|
|
FRG::g_currentLanguage = FRG::Language::Russian;
|
|
reloadLocalizedGameContent();
|
|
saveSettings();
|
|
});
|
|
uiManager.setTextButtonCallback("languageEnglishButton", [this](const std::string&) {
|
|
audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg");
|
|
FRG::g_currentLanguage = FRG::Language::English;
|
|
reloadLocalizedGameContent();
|
|
saveSettings();
|
|
});
|
|
}
|
|
|
|
void MenuManager::reloadLocalizedGameContent() {
|
|
// Screens below the Language screen on the menu stack hold onto their old
|
|
// (pre-reload) UiNode instances, so simply reopening Language on top of
|
|
// them would leave Main Menu / Settings / the pause menu showing stale
|
|
// text once the player steps back. Instead, unwind the whole chain and
|
|
// replay it with freshly (re)loaded, localized roots.
|
|
const bool wasInMainMenu = (uiState_ == GameUiState::MainMenu);
|
|
|
|
if (!wasInMainMenu) {
|
|
// Reached via the in-game pause menu (Modal -> Settings -> Language).
|
|
// Fully unwind back to the untouched gameplay HUD underneath — it
|
|
// isn't rebuilt here, only the pause/settings/language chain above it.
|
|
const int depth = uiManager.menuStackSize();
|
|
for (int i = 0; i < depth; ++i) uiManager.popMenu();
|
|
}
|
|
|
|
// Unloading/reloading ~50 UI screens and their textures is heavy enough to
|
|
// freeze the window for a noticeable moment, so run it the same way the
|
|
// initial resource load runs: a queue of closures drained one per frame
|
|
// with the loading screen shown, instead of one big blocking call here.
|
|
std::vector<std::function<void()>> steps;
|
|
|
|
steps.push_back([this]() {
|
|
ItemRegistry::instance().loadFromJson("resources/config/items.json", CONST_ZIP_FILE);
|
|
gameState_.questJournal.reloadDefinitionsPreservingState("resources/config/quests.json", CONST_ZIP_FILE);
|
|
});
|
|
|
|
steps.push_back([this]() {
|
|
renderer.textureManager.UnloadByPrefix("resources/w/ui");
|
|
loadUiRoots(CONST_ZIP_FILE);
|
|
|
|
toastTextureQuestNew = loadLocalizedTexture("resources/w/ui/img/toast/quest_new001.png", CONST_ZIP_FILE);
|
|
toastTextureQuestCompleted = loadLocalizedTexture("resources/w/ui/img/toast/quest_completed001.png", CONST_ZIP_FILE);
|
|
toastTextureQuestFailed = loadLocalizedTexture("resources/w/ui/img/toast/quest_failed001.png", CONST_ZIP_FILE);
|
|
toastTextureItemAdded = loadLocalizedTexture("resources/w/ui/img/toast/item_received001.png", CONST_ZIP_FILE);
|
|
toastTextureItemRemoved = loadLocalizedTexture("resources/w/ui/img/toast/item_removed001.png", CONST_ZIP_FILE);
|
|
});
|
|
|
|
steps.push_back([this]() {
|
|
showMainMenu();
|
|
});
|
|
|
|
if (runAsLoadingSequence) {
|
|
runAsLoadingSequence(std::move(steps));
|
|
} else {
|
|
for (auto& step : steps) step();
|
|
}
|
|
}
|
|
|
|
void MenuManager::showLoadGameScreen()
|
|
{
|
|
uiManager.pushMenuFromSavedRoot(loadSavedGameScreenRoot);
|
|
uiManager.setTextButtonCallback("loadBackButton", [this](const std::string&) {
|
|
uiManager.popMenu();
|
|
uiManager.updateAllLayouts();
|
|
audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg");
|
|
});
|
|
|
|
static const char* kSlotButtons[4] = {
|
|
"loadSlot1Button", "loadSlot2Button", "loadSlot3Button", "loadSlot4Button"
|
|
};
|
|
for (int i = 0; i < 4; ++i) {
|
|
int slot = i + 1;
|
|
if (getSlotInfoFunc) {
|
|
SaveSlotInfo info = getSlotInfoFunc(slot);
|
|
|
|
std::string emptyText;
|
|
if (FRG::g_currentLanguage == FRG::Language::Russian)
|
|
{
|
|
emptyText = EMPTY_LANGUAGE_RU;
|
|
}
|
|
else if (FRG::g_currentLanguage == FRG::Language::English)
|
|
{
|
|
emptyText = EMPTY_LANGUAGE_EN;
|
|
}
|
|
|
|
std::string label = info.empty
|
|
? emptyText
|
|
: localizedLocationName[info.locationName][FRG::g_currentLanguage] + " " + info.savedAt;
|
|
uiManager.setTextButtonText(kSlotButtons[i], label);
|
|
}
|
|
uiManager.setTextButtonCallback(kSlotButtons[i], [this, slot](const std::string&) {
|
|
audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg");
|
|
if (onLoadGame) onLoadGame(slot);
|
|
enterGameplay(true);
|
|
});
|
|
}
|
|
}
|
|
|
|
void MenuManager::showSaveGameScreen()
|
|
{
|
|
uiManager.pushMenuFromSavedRoot(saveGameScreenRoot);
|
|
uiManager.setTextButtonCallback("saveBackButton", [this](const std::string&) {
|
|
uiManager.popMenu();
|
|
uiManager.updateAllLayouts();
|
|
audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg");
|
|
});
|
|
|
|
static const char* kSlotButtons[4] = {
|
|
"saveSlot1Button", "saveSlot2Button", "saveSlot3Button", "saveSlot4Button"
|
|
};
|
|
for (int i = 0; i < 4; ++i) {
|
|
int slot = i + 1;
|
|
const char* buttonName = kSlotButtons[i];
|
|
if (getSlotInfoFunc) {
|
|
SaveSlotInfo info = getSlotInfoFunc(slot);
|
|
|
|
std::string emptyText;
|
|
if (FRG::g_currentLanguage == FRG::Language::Russian)
|
|
{
|
|
emptyText = EMPTY_LANGUAGE_RU;
|
|
}
|
|
else if (FRG::g_currentLanguage == FRG::Language::English)
|
|
{
|
|
emptyText = EMPTY_LANGUAGE_EN;
|
|
}
|
|
|
|
std::string label = info.empty
|
|
? emptyText
|
|
: localizedLocationName[info.locationName][FRG::g_currentLanguage] + " " + info.savedAt;
|
|
uiManager.setTextButtonText(buttonName, label);
|
|
}
|
|
uiManager.setTextButtonCallback(buttonName, [this, slot, buttonName](const std::string&) {
|
|
if (onSaveGame) onSaveGame(slot);
|
|
if (getSlotInfoFunc) {
|
|
SaveSlotInfo info = getSlotInfoFunc(slot);
|
|
|
|
std::string emptyText;
|
|
if (FRG::g_currentLanguage == FRG::Language::Russian)
|
|
{
|
|
emptyText = EMPTY_LANGUAGE_RU;
|
|
}
|
|
else if (FRG::g_currentLanguage == FRG::Language::English)
|
|
{
|
|
emptyText = EMPTY_LANGUAGE_EN;
|
|
}
|
|
|
|
std::string label = info.empty
|
|
? emptyText
|
|
: localizedLocationName[info.locationName][FRG::g_currentLanguage] + " " + info.savedAt;
|
|
uiManager.setTextButtonText(buttonName, label);
|
|
audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg");
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
void MenuManager::openInventory() {
|
|
uiState_ = GameUiState::Inventory;
|
|
inventoryMenuStackDepth_ = 1;
|
|
|
|
const bool portrait = isPortraitMode();
|
|
uiManager.pushMenuFromSavedRoot(portrait ? portraitInventoryListRoot : newInventoryRoot);
|
|
|
|
uiManager.setButtonCallback("inventoryExitButton2", [this](const std::string&) {
|
|
closeInventory();
|
|
});
|
|
|
|
uiManager.setButtonCallback("inventoryExitButton", [this](const std::string&) {
|
|
closeInventory();
|
|
});
|
|
|
|
uiManager.setButtonCallback("inventoryMain", [this](const std::string&) {});
|
|
|
|
const auto& items = inventory->getItems();
|
|
const int maxSlots = 9;
|
|
for (int i = 0; i < maxSlots; ++i) {
|
|
const std::string btnName = "item" + std::to_string(i + 1) + "Button";
|
|
if (i < static_cast<int>(items.size())) {
|
|
uiManager.setNodeVisible(btnName, true);
|
|
auto btn = uiManager.findButton(btnName);
|
|
if (btn) {
|
|
auto tex = renderer.textureManager.LoadFromPng(items[i].icon, CONST_ZIP_FILE, true);
|
|
btn->texNormal = btn->texHover = btn->texPressed = tex;
|
|
}
|
|
uiManager.setButtonCallback(btnName, [this, i](const std::string&) {
|
|
selectInventoryItem(i);
|
|
audioPlayer_.playSoundAsync("audio/sound_bag001.ogg");
|
|
});
|
|
} else {
|
|
uiManager.setNodeVisible(btnName, false);
|
|
}
|
|
}
|
|
|
|
inventorySelectedIndex_ = -1;
|
|
if (!portrait && !items.empty()) {
|
|
selectInventoryItem(0);
|
|
}
|
|
|
|
audioPlayer_.playSoundAsync("audio/sound_bag_open.ogg");
|
|
}
|
|
|
|
void MenuManager::selectInventoryItem(int index) {
|
|
logger() << "MenuManager::selectInventoryItem: " << index << std::endl;
|
|
const auto& items = inventory->getItems();
|
|
if (index < 0 || index >= static_cast<int>(items.size())) return;
|
|
|
|
if (isPortraitMode() && inventoryMenuStackDepth_ == 1) {
|
|
openPortraitInventoryItem(index);
|
|
return;
|
|
}
|
|
|
|
// Revert previously selected button to its regular icon
|
|
if (inventorySelectedIndex_ >= 0 && inventorySelectedIndex_ < static_cast<int>(items.size())) {
|
|
const std::string prevBtnName = "item" + std::to_string(inventorySelectedIndex_ + 1) + "Button";
|
|
auto prevBtn = uiManager.findButton(prevBtnName);
|
|
if (prevBtn) {
|
|
auto tex = renderer.textureManager.LoadFromPng(items[inventorySelectedIndex_].icon, CONST_ZIP_FILE, true);
|
|
prevBtn->texNormal = prevBtn->texHover = prevBtn->texPressed = tex;
|
|
}
|
|
}
|
|
|
|
inventorySelectedIndex_ = index;
|
|
const auto& item = items[index];
|
|
|
|
// Highlight newly selected button with its selected icon
|
|
const std::string btnName = "item" + std::to_string(index + 1) + "Button";
|
|
auto btn = uiManager.findButton(btnName);
|
|
if (btn) {
|
|
const std::string& selPath = item.selectedIcon.empty() ? item.icon : item.selectedIcon;
|
|
auto tex = renderer.textureManager.LoadFromPng(selPath, CONST_ZIP_FILE, true);
|
|
btn->texNormal = btn->texHover = btn->texPressed = tex;
|
|
}
|
|
|
|
// Update the large selected picture on the right panel
|
|
auto img = uiManager.findStaticImage("selectedItemPic");
|
|
if (img) {
|
|
const std::string& path = item.selectedIcon.empty() ? item.icon : item.selectedIcon;
|
|
img->texture = renderer.textureManager.LoadFromPng(path, CONST_ZIP_FILE, true);
|
|
}
|
|
|
|
uiManager.setText("selectedText", item.name);
|
|
uiManager.setText("selectedDescription", item.description);
|
|
}
|
|
|
|
|
|
void MenuManager::closeInventory() {
|
|
uiState_ = GameUiState::Gameplay;
|
|
for (int i = 0; i < inventoryMenuStackDepth_; ++i) uiManager.popMenu();
|
|
inventoryMenuStackDepth_ = 0;
|
|
uiManager.updateAllLayouts();
|
|
audioPlayer_.playSoundAsync("audio/sound_bag_close.ogg");
|
|
}
|
|
|
|
void MenuManager::openQuestJournal() {
|
|
uiState_ = GameUiState::QuestJournal;
|
|
gameState_.tutorialJournalScreenOpened = true;
|
|
journalMenuStackDepth_ = 1;
|
|
|
|
uiManager.setNodeVisible("hint6b", false);
|
|
uiManager.setNodeVisible("hint6barrow", false);
|
|
|
|
const bool portrait = isPortraitMode();
|
|
uiManager.pushMenuFromSavedRoot(portrait ? portraitQuestJournalListRoot : questJournalRoot);
|
|
|
|
uiManager.setButtonCallback("journalExitButton", [this](const std::string&) {
|
|
closeQuestJournal();
|
|
audioPlayer_.playSoundAsync("audio/656126__itsthegoodstuff__paging-through-book.ogg");
|
|
});
|
|
|
|
uiManager.setButtonCallback("journalExitButton2", [this](const std::string&) {
|
|
closeQuestJournal();
|
|
audioPlayer_.playSoundAsync("audio/656126__itsthegoodstuff__paging-through-book.ogg");
|
|
});
|
|
|
|
|
|
uiManager.setButtonCallback("journalMain", [this](const std::string&) {});
|
|
|
|
|
|
static const char* kItemNames[9] = {
|
|
"item1name","item2name","item3name",
|
|
"item4name","item5name","item6name",
|
|
"item7name","item8name","item9name"
|
|
};
|
|
for (int i = 0; i < 9; ++i) {
|
|
uiManager.setTextButtonCallback(kItemNames[i], [this, i](const std::string&) {
|
|
selectQuestByIndex(i);
|
|
audioPlayer_.playSoundAsync("audio/255571__stradie__flipping-paper.ogg");
|
|
});
|
|
}
|
|
|
|
refreshQuestJournalUi();
|
|
if (!portrait && !visibleQuestIds.empty()) {
|
|
selectQuestByIndex(0);
|
|
}
|
|
|
|
audioPlayer_.playSoundAsync("audio/255571__stradie__flipping-paper.ogg");
|
|
}
|
|
|
|
void MenuManager::closeQuestJournal() {
|
|
uiState_ = GameUiState::Gameplay;
|
|
selectedQuestIndex = -1;
|
|
visibleQuestIds.clear();
|
|
for (int i = 0; i < journalMenuStackDepth_; ++i) uiManager.popMenu();
|
|
journalMenuStackDepth_ = 0;
|
|
uiManager.updateAllLayouts();
|
|
}
|
|
|
|
void MenuManager::toggleQuestJournal() {
|
|
logger() << "[quest] toggleQuestJournal: " << (isQuestJournalOpen() ? "closing" : "opening") << std::endl;
|
|
if (uiState_ == GameUiState::QuestJournal) {
|
|
closeQuestJournal();
|
|
}
|
|
else {
|
|
if (uiState_ == GameUiState::Inventory) {
|
|
closeInventory();
|
|
}
|
|
openQuestJournal();
|
|
}
|
|
}
|
|
|
|
void MenuManager::openPhoneScreen() {
|
|
uiState_ = GameUiState::PhoneScreen;
|
|
currentPhoneSubScreen_ = PhoneSubScreen::Main;
|
|
|
|
if (isPortraitMode()) {
|
|
if (gameState_.tutorialNeedOpenTaxiScreen && (gameState_.tutorialPhoneChatScreenOpened == false))
|
|
uiManager.pushMenuFromSavedRoot(verticalPhoneMainHintABRoot);
|
|
else if (gameState_.tutorialNeedOpenTaxiScreen)
|
|
uiManager.pushMenuFromSavedRoot(verticalPhoneMainHintBRoot);
|
|
else if (gameState_.tutorialPhoneChatScreenOpened == false)
|
|
uiManager.pushMenuFromSavedRoot(verticalPhoneMainHintARoot);
|
|
else
|
|
uiManager.pushMenuFromSavedRoot(verticalPhoneMainRoot);
|
|
} else if (gameState_.tutorialNeedOpenTaxiScreen && (gameState_.tutorialPhoneChatScreenOpened == false)) {
|
|
uiManager.pushMenuFromSavedRoot(phoneMainHintABRoot);
|
|
} else if (gameState_.tutorialNeedOpenTaxiScreen) {
|
|
uiManager.pushMenuFromSavedRoot(phoneMainHintBRoot);
|
|
} else if (gameState_.tutorialPhoneChatScreenOpened == false) {
|
|
uiManager.pushMenuFromSavedRoot(phoneMainHintARoot);
|
|
} else {
|
|
uiManager.pushMenuFromSavedRoot(phoneMainRoot);
|
|
}
|
|
|
|
|
|
uiManager.setButtonCallback("phoneExitButton", [this](const std::string&) {
|
|
closePhoneEntirely();
|
|
audioPlayer_.playSoundAsync("audio/537061__imafoley__message-pop-sound.ogg");
|
|
});
|
|
uiManager.setButtonCallback("phoneMain", [this](const std::string&) {});
|
|
uiManager.setButtonCallback("phoneMessenger", [this](const std::string&) {
|
|
openPhoneMessenger();
|
|
audioPlayer_.playSoundAsync("audio/537061__imafoley__message-pop-sound.ogg");
|
|
});
|
|
uiManager.setButtonCallback("phoneBank", [this](const std::string&) {
|
|
openPhoneBank();
|
|
audioPlayer_.playSoundAsync("audio/537061__imafoley__message-pop-sound.ogg");
|
|
});
|
|
uiManager.setButtonCallback("phoneVideo", [this](const std::string&) {
|
|
openPhoneVideo();
|
|
audioPlayer_.playSoundAsync("audio/537061__imafoley__message-pop-sound.ogg");
|
|
});
|
|
uiManager.setButtonCallback("phoneTaxi", [this](const std::string&) {
|
|
openPhoneTaxi();
|
|
audioPlayer_.playSoundAsync("audio/537061__imafoley__message-pop-sound.ogg");
|
|
});
|
|
|
|
if (gameState_.isNight)
|
|
{
|
|
if (gameState_.isDawn)
|
|
{
|
|
uiManager.setNodeVisible("phoneTimeDay", false);
|
|
uiManager.setNodeVisible("phoneTimeNight", false);
|
|
uiManager.setNodeVisible("phoneTimeDawn", true);
|
|
}
|
|
else
|
|
{
|
|
uiManager.setNodeVisible("phoneTimeDay", false);
|
|
uiManager.setNodeVisible("phoneTimeNight", true);
|
|
uiManager.setNodeVisible("phoneTimeDawn", false);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
uiManager.setNodeVisible("phoneTimeDay", true);
|
|
uiManager.setNodeVisible("phoneTimeNight", false);
|
|
uiManager.setNodeVisible("phoneTimeDawn", false);
|
|
}
|
|
|
|
audioPlayer_.playSoundAsync("audio/537061__imafoley__message-pop-sound.ogg");
|
|
}
|
|
|
|
void MenuManager::openPhoneMessenger() {
|
|
currentPhoneSubScreen_ = PhoneSubScreen::ChatList;
|
|
|
|
if (gameState_.tutorialPhoneChatScreenOpened)
|
|
{
|
|
uiManager.pushMenuFromSavedRoot(isPortraitMode() ? verticalPhoneChatListRoot : phoneChatListRoot);
|
|
}
|
|
else
|
|
{
|
|
uiManager.pushMenuFromSavedRoot(isPortraitMode() ? verticalPhoneChatListHintRoot : phoneChatListHintRoot);
|
|
}
|
|
refreshChatUnreadIndicators();
|
|
|
|
uiManager.setButtonCallback("phoneExitButton", [this](const std::string&) {
|
|
closePhoneEntirely();
|
|
audioPlayer_.playSoundAsync("audio/537061__imafoley__message-pop-sound.ogg");
|
|
});
|
|
uiManager.setButtonCallback("phoneMain", [this](const std::string&) {});
|
|
uiManager.setTextButtonCallback("chat1button", [this](const std::string&) {
|
|
gameState_.chatUnread[0] = false;
|
|
openPhoneChatFromList(0, isPortraitMode() ? verticalPhoneChat1Root : phoneChat1Root);
|
|
audioPlayer_.playSoundAsync("audio/537061__imafoley__message-pop-sound.ogg");
|
|
});
|
|
uiManager.setTextButtonCallback("chat2button", [this](const std::string&) {
|
|
gameState_.chatUnread[1] = false;
|
|
openPhoneChatFromList(1, isPortraitMode() ? verticalPhoneChat2Root : phoneChat2Root);
|
|
audioPlayer_.playSoundAsync("audio/537061__imafoley__message-pop-sound.ogg");
|
|
});
|
|
uiManager.setTextButtonCallback("chat3button", [this](const std::string&) {
|
|
gameState_.chatUnread[2] = false;
|
|
openPhoneChatFromList(2, isPortraitMode() ? verticalPhoneChat3Root : phoneChat3Root);
|
|
audioPlayer_.playSoundAsync("audio/537061__imafoley__message-pop-sound.ogg");
|
|
});
|
|
}
|
|
|
|
void MenuManager::refreshChatUnreadIndicators() {
|
|
static const char* kUnreadNodes[3] = { "chat1Unread", "chat2Unread", "chat3Unread" };
|
|
static const char* kMsgNodes[3] = { "chat1msg", "chat2msg", "chat3msg" };
|
|
for (int i = 0; i < 3; ++i) {
|
|
uiManager.setNodeVisible(kUnreadNodes[i], gameState_.chatUnread[i]);
|
|
if (!gameState_.chatPreviewMsg[i].empty())
|
|
uiManager.setText(kMsgNodes[i], gameState_.chatPreviewMsg[i]);
|
|
}
|
|
}
|
|
|
|
void MenuManager::setChatUnread(int chatIndex, bool unread, const std::string& previewMsg) {
|
|
if (chatIndex < 0 || chatIndex > 2) return;
|
|
gameState_.chatUnread[chatIndex] = unread;
|
|
if (!previewMsg.empty()) {
|
|
gameState_.chatPreviewMsg[chatIndex] = trimChatPreview(previewMsg);
|
|
} else if (!unread && !gameState_.chatHistory[chatIndex].empty()) {
|
|
gameState_.chatPreviewMsg[chatIndex] = trimChatPreview(gameState_.chatHistory[chatIndex].back().text);
|
|
}
|
|
}
|
|
|
|
void MenuManager::spendMoney(int amount) {
|
|
gameState_.money -= amount;
|
|
}
|
|
|
|
|
|
void MenuManager::openPhoneBank() {
|
|
currentPhoneSubScreen_ = PhoneSubScreen::Bank;
|
|
uiManager.pushMenuFromSavedRoot(isPortraitMode() ? verticalPhoneBankRoot : phoneBankRoot);
|
|
uiManager.setText("balanceText", formatMoney(gameState_.money));
|
|
|
|
uiManager.setButtonCallback("phoneExitButton", [this](const std::string&) {
|
|
closePhoneEntirely();
|
|
audioPlayer_.playSoundAsync("audio/537061__imafoley__message-pop-sound.ogg");
|
|
});
|
|
uiManager.setButtonCallback("phoneMain", [this](const std::string&) {});
|
|
uiManager.setButtonCallback("buttonBack", [this](const std::string&) {
|
|
uiManager.popMenu();
|
|
uiManager.updateAllLayouts();
|
|
audioPlayer_.playSoundAsync("audio/537061__imafoley__message-pop-sound.ogg");
|
|
});
|
|
}
|
|
|
|
void MenuManager::openPhoneVideo() {
|
|
currentPhoneSubScreen_ = PhoneSubScreen::Video;
|
|
uiManager.pushMenuFromSavedRoot(isPortraitMode() ? verticalPhoneVideoRoot : phoneVideoRoot);
|
|
|
|
uiManager.setButtonCallback("phoneExitButton", [this](const std::string&) {
|
|
closePhoneEntirely();
|
|
audioPlayer_.playSoundAsync("audio/537061__imafoley__message-pop-sound.ogg");
|
|
});
|
|
uiManager.setButtonCallback("phoneMain", [this](const std::string&) {});
|
|
uiManager.setButtonCallback("buttonBack", [this](const std::string&) {
|
|
uiManager.popMenu();
|
|
uiManager.updateAllLayouts();
|
|
audioPlayer_.playSoundAsync("audio/537061__imafoley__message-pop-sound.ogg");
|
|
});
|
|
uiManager.setButtonCallback("videoSkip", [this](const std::string&) {
|
|
closePhoneEntirely();
|
|
if (gameState_.isNight)
|
|
{
|
|
startDialogueFunc("dialog_video002");
|
|
}
|
|
else
|
|
{
|
|
auto day = gameState_.globalInts["day"];
|
|
if (day == 0)
|
|
{
|
|
startDialogueFunc("dialog_video003");
|
|
}
|
|
else
|
|
{
|
|
audioPlayer_.playSoundAsync("audio/78564__joedeshon__alarm_clock_ticking_02.ogg");
|
|
startNightTransitionFunc();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
void MenuManager::openPhoneTaxi() {
|
|
if (gameState_.currentLocationName == "uni_interior") {
|
|
closePhoneEntirely();
|
|
if (startDialogueFunc) startDialogueFunc("dialog_taxi003");
|
|
} else if (gameState_.currentLocationName == "uni_exterior") {
|
|
currentPhoneSubScreen_ = PhoneSubScreen::MapUni;
|
|
openPhoneMapScreen(isPortraitMode() ? verticalPhoneMapUniRoot : phoneMapUniRoot);
|
|
} else {
|
|
currentPhoneSubScreen_ = PhoneSubScreen::MapDorm;
|
|
openPhoneMapScreen(isPortraitMode() ? verticalPhoneMapDormRoot : phoneMapDormRoot);
|
|
}
|
|
}
|
|
|
|
void MenuManager::openPhoneMapScreen(std::shared_ptr<UiNode> mapRoot) {
|
|
uiManager.pushMenuFromSavedRoot(mapRoot);
|
|
|
|
uiManager.setButtonCallback("phoneExitButton", [this](const std::string&) {
|
|
closePhoneEntirely();
|
|
audioPlayer_.playSoundAsync("audio/537061__imafoley__message-pop-sound.ogg");
|
|
});
|
|
uiManager.setButtonCallback("phoneMain", [this](const std::string&) {});
|
|
uiManager.setButtonCallback("buttonBack", [this](const std::string&) {
|
|
uiManager.popMenu();
|
|
uiManager.updateAllLayouts();
|
|
audioPlayer_.playSoundAsync("audio/537061__imafoley__message-pop-sound.ogg");
|
|
});
|
|
uiManager.setButtonCallback("mapGo", [this](const std::string&) {
|
|
gameState_.tutorialNeedOpenTaxiScreen = false;
|
|
|
|
if (gameState_.taxiIsCalled == false)
|
|
{
|
|
gameState_.taxiIsCalled = true;
|
|
if (callTaxiFunc)
|
|
{
|
|
callTaxiFunc();
|
|
}
|
|
gameState_.money -= 500;
|
|
closePhoneEntirely();
|
|
if (startDialogueFunc) startDialogueFunc("dialog_taxi002");
|
|
audioPlayer_.playSoundAsync("audio/537061__imafoley__message-pop-sound.ogg");
|
|
}
|
|
else
|
|
{
|
|
closePhoneEntirely();
|
|
if (startDialogueFunc) startDialogueFunc("dialog_taxi004");
|
|
audioPlayer_.playSoundAsync("audio/537061__imafoley__message-pop-sound.ogg");
|
|
}
|
|
});
|
|
|
|
audioPlayer_.playSoundAsync("audio/537061__imafoley__message-pop-sound.ogg");
|
|
}
|
|
|
|
void MenuManager::openPhoneChatFromList(int chatIndex, std::shared_ptr<UiNode> chatRoot) {
|
|
activeChatIndex_ = chatIndex;
|
|
currentPhoneSubScreen_ = PhoneSubScreen::Chat;
|
|
phoneChatVisibleBubbles_.clear();
|
|
gameState_.tutorialPhoneChatScreenOpened = true;
|
|
uiManager.pushMenuFromSavedRoot(chatRoot);
|
|
|
|
// Build a dedicated chatUiManager root sized to the FBO chat area
|
|
{
|
|
const bool portrait = isPortraitMode();
|
|
const float fboW = portrait ? CHAT_FBO_WIDTH_P : CHAT_FBO_WIDTH_L;
|
|
const float fboH = portrait ? (CHAT_FBO_OFFSET_UP_P + CHAT_FBO_OFFSET_DOWN_P)
|
|
: (CHAT_FBO_OFFSET_UP_L + CHAT_FBO_OFFSET_DOWN_L);
|
|
|
|
auto fboRoot = std::make_shared<UiNode>();
|
|
fboRoot->name = "chatFboRoot";
|
|
fboRoot->width = fboW;
|
|
fboRoot->height = fboH;
|
|
fboRoot->screenRect = { 0.0f, 0.0f, fboW, fboH };
|
|
|
|
auto fboContainer = std::make_shared<UiNode>();
|
|
fboContainer->name = "chatMessagesContainer";
|
|
fboContainer->width = fboW;
|
|
fboContainer->height = fboH;
|
|
fboContainer->screenRect = { 0.0f, 0.0f, fboW, fboH };
|
|
fboRoot->children.push_back(fboContainer);
|
|
|
|
chatUiManager.replaceRoot(fboRoot);
|
|
chatFrameBuffer_ = std::make_unique<FrameBuffer>(
|
|
static_cast<int>(fboW), static_cast<int>(fboH), false);
|
|
chatFboQuadLastProjW_ = -1.0f;
|
|
}
|
|
|
|
rebuildChatBubblesFromHistory(chatIndex, isPortraitMode());
|
|
|
|
uiManager.setButtonCallback("phoneExitButton", [this](const std::string&) {
|
|
closePhoneScreenFromChat();
|
|
audioPlayer_.playSoundAsync("audio/537061__imafoley__message-pop-sound.ogg");
|
|
});
|
|
uiManager.setButtonCallback("phoneMain", [this](const std::string&) {});
|
|
uiManager.setTextButtonCallback("chatTitleButton", [this](const std::string&) {
|
|
returnToPhoneChatList();
|
|
audioPlayer_.playSoundAsync("audio/537061__imafoley__message-pop-sound.ogg");
|
|
});
|
|
|
|
if (chatOpenCallback) {
|
|
chatOpenCallback(chatIndex);
|
|
}
|
|
}
|
|
|
|
void MenuManager::returnToPhoneChatList() {
|
|
if (activeChatIndex_ >= 0 && activeChatIndex_ <= 2
|
|
&& !gameState_.chatHistory[activeChatIndex_].empty()) {
|
|
gameState_.chatPreviewMsg[activeChatIndex_] =
|
|
trimChatPreview(gameState_.chatHistory[activeChatIndex_].back().text);
|
|
}
|
|
activeChatIndex_ = -1;
|
|
currentPhoneSubScreen_ = PhoneSubScreen::ChatList;
|
|
phoneChatVisibleBubbles_.clear();
|
|
chatUiManager.replaceRoot(nullptr);
|
|
chatFrameBuffer_.reset();
|
|
uiManager.popMenu();
|
|
uiManager.updateAllLayouts();
|
|
if (gameState_.tutorialPhoneChatScreenOpened)
|
|
{
|
|
uiManager.setNodeVisible("hint_m002", false);
|
|
}
|
|
else
|
|
{
|
|
uiManager.setNodeVisible("hint_m002", true);
|
|
}
|
|
refreshChatUnreadIndicators();
|
|
}
|
|
|
|
|
|
void MenuManager::closePhoneEntirely() {
|
|
if (activeChatIndex_ >= 0 && activeChatIndex_ <= 2
|
|
&& !gameState_.chatHistory[activeChatIndex_].empty()) {
|
|
gameState_.chatPreviewMsg[activeChatIndex_] =
|
|
trimChatPreview(gameState_.chatHistory[activeChatIndex_].back().text);
|
|
}
|
|
activeChatIndex_ = -1;
|
|
currentPhoneSubScreen_ = PhoneSubScreen::None;
|
|
uiState_ = GameUiState::Gameplay;
|
|
phoneChatVisibleBubbles_.clear();
|
|
chatUiManager.replaceRoot(nullptr);
|
|
chatFrameBuffer_.reset();
|
|
const int depth = uiManager.menuStackSize();
|
|
for (int i = 0; i < depth; ++i) uiManager.popMenu();
|
|
uiManager.updateAllLayouts();
|
|
|
|
if (gameState_.tutorialNeedOpenTaxiScreen)
|
|
{
|
|
uiManager.setNodeVisible("hint7", true);
|
|
uiManager.setNodeVisible("hint7arrow", true);
|
|
|
|
uiManager.setNodeVisible("hint6a", false);
|
|
uiManager.setNodeVisible("hint6aarrow", false);
|
|
}
|
|
else
|
|
{
|
|
uiManager.setNodeVisible("hint7", false);
|
|
uiManager.setNodeVisible("hint7arrow", false);
|
|
|
|
if (gameState_.tutorialPhoneChatScreenOpened)
|
|
{
|
|
uiManager.setNodeVisible("hint6a", false);
|
|
uiManager.setNodeVisible("hint6aarrow", false);
|
|
}
|
|
else
|
|
{
|
|
uiManager.setNodeVisible("hint6a", true);
|
|
uiManager.setNodeVisible("hint6aarrow", true);
|
|
}
|
|
}
|
|
}
|
|
|
|
void MenuManager::closePhoneScreenFromChat() {
|
|
closePhoneEntirely();
|
|
}
|
|
|
|
void MenuManager::closePhoneScreen() {
|
|
closePhoneEntirely();
|
|
}
|
|
|
|
void MenuManager::showModalMenuScreen() {
|
|
audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg");
|
|
uiManager.pushMenuFromSavedRoot(modalMenuRoot);
|
|
uiManager.setButtonCallback("modalExitButton", [this](const std::string&) {
|
|
audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg");
|
|
uiManager.popMenu();
|
|
uiManager.updateAllLayouts();
|
|
});
|
|
|
|
uiManager.setButtonCallback("menuCloseButton", [this](const std::string&) {
|
|
audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg");
|
|
uiManager.popMenu();
|
|
uiManager.updateAllLayouts();
|
|
});
|
|
|
|
uiManager.setButtonCallback("menuSaveButton", [this](const std::string&) {
|
|
showSaveGameScreen();
|
|
audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg");
|
|
});
|
|
|
|
uiManager.setButtonCallback("menuLoadButton", [this](const std::string&) {
|
|
showLoadGameScreen();
|
|
audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg");
|
|
});
|
|
|
|
uiManager.setButtonCallback("menuSettingsButton", [this](const std::string&) {
|
|
showSettingsScreen();
|
|
audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg");
|
|
});
|
|
|
|
uiManager.setButtonCallback("menuExitButton", [this](const std::string&) {
|
|
audioPlayer_.playSoundAsync("audio/751089__smallconfusion__mechanical-plastic-click-11.ogg");
|
|
uiManager.popMenu();
|
|
showMainMenu();
|
|
});
|
|
}
|
|
|
|
void MenuManager::tutorialShowTaxiHint()
|
|
{
|
|
logger() << "tutorialShowTaxiHint" << std::endl;
|
|
gameState_.tutorialNeedOpenTaxiScreen = true;
|
|
if (uiState_ == GameUiState::Gameplay)
|
|
{
|
|
uiManager.setNodeVisible("hint6a", false);
|
|
uiManager.setNodeVisible("hint6aarrow", false);
|
|
|
|
uiManager.setNodeVisible("hint7", true);
|
|
uiManager.setNodeVisible("hint7arrow", true);
|
|
}
|
|
}
|
|
|
|
// Registers phoneButton / journalButton callbacks on the current HUD root
|
|
// and hides any hints that have already been completed.
|
|
// Called after every replaceRoot during step 5.
|
|
void MenuManager::setupStep5Callbacks() {
|
|
if (uiManager.findButton("phoneButton")) {
|
|
uiManager.setButtonCallback("phoneButton", [this](const std::string&) {
|
|
openPhoneScreen();
|
|
});
|
|
}
|
|
if (uiManager.findButton("inventoryButton")) {
|
|
uiManager.setButtonCallback("inventoryButton", [this](const std::string&) {
|
|
openInventory();
|
|
});
|
|
}
|
|
if (uiManager.findButton("journalButton")) {
|
|
uiManager.setButtonCallback("journalButton", [this](const std::string&) {
|
|
openQuestJournal();
|
|
});
|
|
}
|
|
if (uiManager.findButton("settingsButton")) {
|
|
uiManager.setButtonCallback("settingsButton", [this](const std::string&) {
|
|
showModalMenuScreen();
|
|
});
|
|
}
|
|
if (gameState_.tutorialNeedOpenTaxiScreen)
|
|
{
|
|
uiManager.setNodeVisible("hint6a", false);
|
|
uiManager.setNodeVisible("hint6aarrow", false);
|
|
|
|
uiManager.setNodeVisible("hint7", true);
|
|
uiManager.setNodeVisible("hint7arrow", true);
|
|
}
|
|
else
|
|
{
|
|
uiManager.setNodeVisible("hint7", false);
|
|
uiManager.setNodeVisible("hint7arrow", false);
|
|
}
|
|
|
|
if (gameState_.tutorialPhoneChatScreenOpened) {
|
|
uiManager.setNodeVisible("hint6a", false);
|
|
uiManager.setNodeVisible("hint6aarrow", false);
|
|
}
|
|
else
|
|
{
|
|
if (!gameState_.tutorialNeedOpenTaxiScreen)
|
|
{
|
|
uiManager.setNodeVisible("hint6a", true);
|
|
uiManager.setNodeVisible("hint6aarrow", true);
|
|
}
|
|
}
|
|
if (gameState_.tutorialJournalScreenOpened)
|
|
{
|
|
uiManager.setNodeVisible("hint6b", false);
|
|
uiManager.setNodeVisible("hint6barrow", false);
|
|
}
|
|
else
|
|
{
|
|
uiManager.setNodeVisible("hint6b", true);
|
|
uiManager.setNodeVisible("hint6barrow", true);
|
|
}
|
|
if (gameState_.tutorialPhoneChatScreenOpened && gameState_.tutorialJournalScreenOpened) {
|
|
gameState_.tutorialStep = TutorialStep::Step6;
|
|
}
|
|
}
|
|
|
|
void MenuManager::setupGameplayHudCallbacks() {
|
|
if (uiManager.findButton("phoneButton"))
|
|
uiManager.setButtonCallback("phoneButton", [this](const std::string&) { openPhoneScreen(); });
|
|
if (uiManager.findButton("inventoryButton"))
|
|
uiManager.setButtonCallback("inventoryButton", [this](const std::string&) { openInventory(); });
|
|
if (uiManager.findButton("journalButton"))
|
|
uiManager.setButtonCallback("journalButton", [this](const std::string&) { openQuestJournal(); });
|
|
if (uiManager.findButton("darklandsButton"))
|
|
uiManager.setButtonCallback("darklandsButton", [this](const std::string&) { startDarklandsTransitionFunc(); });
|
|
if (uiManager.findButton("settingsButton"))
|
|
uiManager.setButtonCallback("settingsButton", [this](const std::string&) { showModalMenuScreen(); });
|
|
|
|
|
|
hideAllToastWidgets();
|
|
applyToastsToUi();
|
|
}
|
|
|
|
void MenuManager::onLocationChanged(const std::string& locationName) {
|
|
if (uiState_ != GameUiState::Gameplay) return;
|
|
gameState_.currentLocationName = locationName;
|
|
|
|
if (locationName == "uni_exterior") {
|
|
uiManager.replaceRoot(gameState_.isDarklands ? hudUniExtDarkRoot : hudUniExtRoot);
|
|
applyCurrentHealthBar();
|
|
setupGameplayHudCallbacks();
|
|
if (gameState_.isDarklands)
|
|
{
|
|
audioPlayer_.crossFadeMusicAsync("audio/bishkek fight calm.ogg");
|
|
}
|
|
else if (gameState_.isNight)
|
|
{
|
|
audioPlayer_.crossFadeMusicAsync("audio/bishkek night.ogg");
|
|
}
|
|
else
|
|
{
|
|
audioPlayer_.crossFadeMusicAsync("audio/bishkek univer day.ogg");
|
|
}
|
|
} else if (locationName == "uni_interior") {
|
|
applyUniIntHud();
|
|
if (gameState_.isDarklands)
|
|
{
|
|
audioPlayer_.crossFadeMusicAsync("audio/bishkek fight calm.ogg");
|
|
}
|
|
else if (gameState_.isNight)
|
|
{
|
|
audioPlayer_.crossFadeMusicAsync("audio/bishkek night.ogg");
|
|
}
|
|
else
|
|
{
|
|
audioPlayer_.crossFadeMusicAsync("audio/bishkek univer day.ogg");
|
|
}
|
|
} else {
|
|
// Returning to dorm: reuse step5ab, suppress already-completed hints
|
|
uiManager.replaceRoot(hudStep5abRoot);
|
|
applyCurrentHealthBar();
|
|
setupStep5Callbacks();
|
|
|
|
if (gameState_.isNight)
|
|
{
|
|
audioPlayer_.crossFadeMusicAsync("audio/bishkek night.ogg");
|
|
}
|
|
else
|
|
{
|
|
audioPlayer_.crossFadeMusicAsync("audio/obshaga.ogg");
|
|
}
|
|
}
|
|
}
|
|
|
|
void MenuManager::advanceTutorialStep() {
|
|
std::shared_ptr<UiNode> nextRoot;
|
|
|
|
switch (gameState_.tutorialStep) {
|
|
case TutorialStep::Step0:
|
|
gameState_.tutorialStep = TutorialStep::Step1;
|
|
nextRoot = hudStep1Root;
|
|
topUiManager.replaceRoot(nullptr);
|
|
break;
|
|
case TutorialStep::Step1:
|
|
gameState_.tutorialStep = TutorialStep::Step2;
|
|
nextRoot = hudStep2Root;
|
|
break;
|
|
case TutorialStep::Step2:
|
|
gameState_.tutorialStep = TutorialStep::Step3;
|
|
nextRoot = hudStep3Root;
|
|
break;
|
|
case TutorialStep::Step3:
|
|
gameState_.tutorialStep = TutorialStep::Step4;
|
|
nextRoot = hudStep4Root;
|
|
if (tutorialUnlockInteractiveObjectsFunc)
|
|
{
|
|
tutorialUnlockInteractiveObjectsFunc();
|
|
tutorialUnlockInteractiveObjectsFunc = nullptr;
|
|
}
|
|
break;
|
|
default:
|
|
return; // Step4/Step5 transitions are driven by onItemPickedUp
|
|
}
|
|
|
|
if (uiState_ == GameUiState::Gameplay && nextRoot) {
|
|
uiManager.replaceRoot(nextRoot);
|
|
applyCurrentHealthBar();
|
|
|
|
uiManager.setButtonCallback("inventoryButton", [this](const std::string&) {
|
|
openInventory();
|
|
});
|
|
if (uiManager.findButton("settingsButton")) {
|
|
uiManager.setButtonCallback("settingsButton", [this](const std::string&) {
|
|
showModalMenuScreen();
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
void MenuManager::onItemPickedUp(const std::string& itemId) {
|
|
if (itemId == "note_spell" && gameState_.uniIntTutorialState == UniIntTutorialState::Step10) {
|
|
gameState_.uniIntTutorialState = UniIntTutorialState::Step11;
|
|
if (gameState_.currentLocationName == "uni_interior" && uiState_ == GameUiState::Gameplay)
|
|
applyUniIntHud();
|
|
}
|
|
|
|
if (gameState_.tutorialStep != TutorialStep::Step4 && gameState_.tutorialStep != TutorialStep::Step5) {
|
|
return;
|
|
}
|
|
|
|
// Dorm tutorial HUD logic must not run in other locations.
|
|
// currentLocationName is empty only before the first teleport (still in dorm).
|
|
if (!gameState_.currentLocationName.empty() && gameState_.currentLocationName != "location_dorm") {
|
|
return;
|
|
}
|
|
|
|
if (itemId == "phone") gameState_.tutorialPhonePickedUp = true;
|
|
if (itemId == "journal") gameState_.tutorialJournalPickedUp = true;
|
|
|
|
if (gameState_.tutorialStep == TutorialStep::Step4) {
|
|
gameState_.tutorialStep = TutorialStep::Step5;
|
|
}
|
|
|
|
refreshItemPickupHud();
|
|
}
|
|
|
|
void MenuManager::refreshItemPickupHud() {
|
|
if (uiState_ != GameUiState::Gameplay) return;
|
|
|
|
std::shared_ptr<UiNode> nextRoot;
|
|
if (gameState_.tutorialPhonePickedUp && gameState_.tutorialJournalPickedUp) {
|
|
nextRoot = hudStep5abRoot;
|
|
} else if (gameState_.tutorialPhonePickedUp) {
|
|
nextRoot = hudStep5aRoot;
|
|
} else if (gameState_.tutorialJournalPickedUp) {
|
|
nextRoot = hudStep5bRoot;
|
|
}
|
|
|
|
if (nextRoot) {
|
|
uiManager.replaceRoot(nextRoot);
|
|
applyCurrentHealthBar();
|
|
// Register item-screen buttons and re-apply any already-completed hint visibility.
|
|
setupStep5Callbacks();
|
|
}
|
|
}
|
|
|
|
void MenuManager::refreshQuestJournalUi() {
|
|
visibleQuestIds.clear();
|
|
auto quests = gameState_.questJournal.getVisibleQuests();
|
|
|
|
std::sort(quests.begin(), quests.end(), [](const Quest::QuestState* a, const Quest::QuestState* b) {
|
|
const int pa = questStatusPriority(a->status);
|
|
const int pb = questStatusPriority(b->status);
|
|
if (pa != pb) return pa < pb;
|
|
return a->orderIndex < b->orderIndex;
|
|
});
|
|
|
|
static const char* kItemNames[9] = {
|
|
"item1name","item2name","item3name",
|
|
"item4name","item5name","item6name",
|
|
"item7name","item8name","item9name"
|
|
};
|
|
|
|
for (int i = 0; i < 9; ++i) {
|
|
if (i < static_cast<int>(quests.size())) {
|
|
const auto* quest = quests[i];
|
|
visibleQuestIds.push_back(quest->definition.id);
|
|
|
|
const bool selected = (i == selectedQuestIndex);
|
|
|
|
std::array<float, 4> color;
|
|
if (selected) {
|
|
color = { 0.996f, 0.977f, 0.761f, 1.0f };
|
|
} else if (quest->status == Quest::QuestStatus::Completed) {
|
|
color = { 0.02f, 0.875f, 0.447f, 0.6f };
|
|
} else if (quest->status == Quest::QuestStatus::Failed) {
|
|
color = { 1.0f, 0.25f, 0.25f, 0.6f };
|
|
} else {
|
|
color = { 0.996f, 0.977f, 0.761f, 0.7f };
|
|
}
|
|
|
|
auto tb = uiManager.findTextButton(kItemNames[i]);
|
|
if (tb) {
|
|
auto tex = selected ? texItemSelected_ : texItemTransparent_;
|
|
tb->texNormal = tb->texHover = tb->texPressed = tex;
|
|
}
|
|
uiManager.setTextButtonText(kItemNames[i], quest->definition.title);
|
|
uiManager.setTextButtonColor(kItemNames[i], color);
|
|
uiManager.setNodeVisible(kItemNames[i], true);
|
|
|
|
auto node = uiManager.findNode(kItemNames[i]);
|
|
if (node && tb && tb->textRenderer) {
|
|
const float availW = node->width - 2.0f * tb->textPaddingX;
|
|
const int numLines = countWrappedLines(quest->definition.title, *tb->textRenderer, availW);
|
|
if (isPortraitMode()) {
|
|
node->height = numLines * 48.0f + 48.0f;
|
|
} else {
|
|
node->height = numLines * 30.0f + 30.0f;
|
|
}
|
|
}
|
|
} else {
|
|
uiManager.setTextButtonText(kItemNames[i], "");
|
|
uiManager.setNodeVisible(kItemNames[i], false);
|
|
|
|
auto node = uiManager.findNode(kItemNames[i]);
|
|
if (node) node->height = 60.0f;
|
|
}
|
|
}
|
|
|
|
uiManager.updateAllLayouts();
|
|
}
|
|
|
|
void MenuManager::selectQuestByIndex(int index) {
|
|
if (index < 0 || index >= static_cast<int>(visibleQuestIds.size())) {
|
|
return;
|
|
}
|
|
|
|
if (isPortraitMode() && journalMenuStackDepth_ == 1) {
|
|
openPortraitQuestJournalItem(index);
|
|
return;
|
|
}
|
|
|
|
selectedQuestIndex = index;
|
|
Quest::QuestState* quest = gameState_.questJournal.findQuest(visibleQuestIds[index]);
|
|
if (!quest) {
|
|
return;
|
|
}
|
|
|
|
const auto& def = quest->definition;
|
|
|
|
uiManager.setText("quest_title", def.title);
|
|
|
|
static const char* kCheckboxes[3] = { "objective1checkbox", "objective2checkbox", "objective3checkbox" };
|
|
static const char* kObjNames[3] = { "objective1name", "objective2name", "objective3name" };
|
|
|
|
std::vector<const Quest::QuestObjective*> visibleObjs;
|
|
for (const auto& obj : def.objectives)
|
|
if (obj.visible) visibleObjs.push_back(&obj);
|
|
|
|
for (int i = 0; i < 3; ++i) {
|
|
if (i < static_cast<int>(visibleObjs.size())) {
|
|
const auto& obj = *visibleObjs[i];
|
|
const bool isActive = (&obj - def.objectives.data() == quest->activeObjectiveIndex);
|
|
|
|
auto img = uiManager.findStaticImage(kCheckboxes[i]);
|
|
if (img) img->texture = obj.completed ? texObjectiveCompleted_ : texObjectiveBlank_;
|
|
|
|
std::array<float, 4> color;
|
|
if (obj.completed) {
|
|
color = { 0.02f, 0.875f, 0.447f, 0.6f };
|
|
} else if (isActive) {
|
|
color = { 0.996f, 0.977f, 0.761f, 1.0f };
|
|
} else {
|
|
color = { 0.996f, 0.977f, 0.761f, 0.9f };
|
|
}
|
|
uiManager.setText(kObjNames[i], obj.text);
|
|
uiManager.setTextColor(kObjNames[i], color);
|
|
uiManager.setNodeVisible(kCheckboxes[i], true);
|
|
uiManager.setNodeVisible(kObjNames[i], true);
|
|
} else {
|
|
uiManager.setNodeVisible(kCheckboxes[i], false);
|
|
uiManager.setNodeVisible(kObjNames[i], false);
|
|
}
|
|
}
|
|
|
|
uiManager.setText("quest_description", def.description);
|
|
|
|
refreshQuestJournalUi();
|
|
}
|
|
|
|
void MenuManager::openPortraitInventoryItem(int index) {
|
|
const auto& items = inventory->getItems();
|
|
inventorySelectedIndex_ = index;
|
|
inventoryMenuStackDepth_ = 2;
|
|
uiManager.pushMenuFromSavedRoot(portraitInventoryItemRoot);
|
|
|
|
uiManager.setButtonCallback("inventoryExitButton", [this](const std::string&) {
|
|
closePortraitInventoryItem();
|
|
audioPlayer_.playSoundAsync("audio/sound_bag001.ogg");
|
|
});
|
|
|
|
uiManager.setButtonCallback("inventoryExitButton2", [this](const std::string&) {
|
|
closePortraitInventoryItem();
|
|
audioPlayer_.playSoundAsync("audio/sound_bag001.ogg");
|
|
});
|
|
|
|
uiManager.setButtonCallback("inventoryMain", [this](const std::string&) {});
|
|
|
|
|
|
const auto& item = items[index];
|
|
|
|
auto img = uiManager.findStaticImage("selectedItemPic");
|
|
if (img) {
|
|
const std::string& path = item.selectedIcon.empty() ? item.icon : item.selectedIcon;
|
|
img->texture = renderer.textureManager.LoadFromPng(path, CONST_ZIP_FILE, true);
|
|
}
|
|
uiManager.setText("selectedText", item.name);
|
|
uiManager.setText("selectedDescription", item.description);
|
|
uiManager.updateAllLayouts();
|
|
}
|
|
|
|
void MenuManager::closePortraitInventoryItem() {
|
|
inventoryMenuStackDepth_ = 1;
|
|
uiManager.popMenu();
|
|
uiManager.updateAllLayouts();
|
|
}
|
|
|
|
void MenuManager::openPortraitQuestJournalItem(int index) {
|
|
selectedQuestIndex = index;
|
|
journalMenuStackDepth_ = 2;
|
|
uiManager.pushMenuFromSavedRoot(portraitQuestJournalItemRoot);
|
|
|
|
uiManager.setButtonCallback("journalItemExitButton", [this](const std::string&) {
|
|
closePortraitJournalItem();
|
|
audioPlayer_.playSoundAsync("audio/255571__stradie__flipping-paper.ogg");
|
|
});
|
|
|
|
uiManager.setButtonCallback("journalItemExitButton2", [this](const std::string&) {
|
|
closeQuestJournal();
|
|
audioPlayer_.playSoundAsync("audio/656126__itsthegoodstuff__paging-through-book.ogg");
|
|
});
|
|
|
|
|
|
uiManager.setButtonCallback("journalMain", [this](const std::string&) {});
|
|
|
|
|
|
Quest::QuestState* quest = gameState_.questJournal.findQuest(visibleQuestIds[index]);
|
|
if (!quest) return;
|
|
const auto& def = quest->definition;
|
|
|
|
uiManager.setText("quest_title", def.title);
|
|
|
|
static const char* kCheckboxes[3] = { "objective1checkbox", "objective2checkbox", "objective3checkbox" };
|
|
static const char* kObjNames[3] = { "objective1name", "objective2name", "objective3name" };
|
|
static const char* kObjNameLayouts[3] = { "objective1nameLayout", "objective2nameLayout", "objective3nameLayout" };
|
|
|
|
std::vector<const Quest::QuestObjective*> visibleObjs;
|
|
for (const auto& obj : def.objectives)
|
|
if (obj.visible) visibleObjs.push_back(&obj);
|
|
|
|
for (int i = 0; i < 3; ++i) {
|
|
if (i < static_cast<int>(visibleObjs.size())) {
|
|
const auto& obj = *visibleObjs[i];
|
|
const bool isActive = (&obj - def.objectives.data() == quest->activeObjectiveIndex);
|
|
auto img = uiManager.findStaticImage(kCheckboxes[i]);
|
|
auto textView = uiManager.findTextView(kObjNames[i]);
|
|
if (img) img->texture = obj.completed ? texObjectiveCompleted_ : texObjectiveBlank_;
|
|
std::array<float, 4> color;
|
|
if (obj.completed) {
|
|
color = { 0.02f, 0.875f, 0.447f, 0.6f };
|
|
} else if (isActive) {
|
|
color = { 0.996f, 0.977f, 0.761f, 1.0f };
|
|
} else {
|
|
color = { 0.996f, 0.977f, 0.761f, 0.9f };
|
|
}
|
|
|
|
const int numLines = countWrappedLines(obj.text, *textView->textRenderer, 540);
|
|
|
|
if (numLines == 1)
|
|
{
|
|
uiManager.findNode(kObjNameLayouts[i])->height = 80.0f;
|
|
}
|
|
else
|
|
{
|
|
uiManager.findNode(kObjNameLayouts[i])->height = 60.0f;
|
|
}
|
|
|
|
uiManager.setText(kObjNames[i], obj.text);
|
|
uiManager.setTextColor(kObjNames[i], color);
|
|
uiManager.setNodeVisible(kCheckboxes[i], true);
|
|
uiManager.setNodeVisible(kObjNames[i], true);
|
|
} else {
|
|
uiManager.setNodeVisible(kCheckboxes[i], false);
|
|
uiManager.setNodeVisible(kObjNames[i], false);
|
|
}
|
|
}
|
|
|
|
uiManager.setText("quest_description", def.description);
|
|
uiManager.updateAllLayouts();
|
|
}
|
|
|
|
void MenuManager::closePortraitJournalItem() {
|
|
journalMenuStackDepth_ = 1;
|
|
uiManager.popMenu();
|
|
uiManager.updateAllLayouts();
|
|
refreshQuestJournalUi();
|
|
}
|
|
/*
|
|
void MenuManager::closePortraitJournalItemCompletely() {
|
|
journalMenuStackDepth_ = 1;
|
|
uiManager.popMenu();
|
|
uiManager.updateAllLayouts();
|
|
refreshQuestJournalUi();
|
|
}*/
|
|
|
|
void MenuManager::resetPhoneChatNodes() {
|
|
static const char* kChatNodes[] = {
|
|
"message01in", "message02out", "message03in", "message04out",
|
|
"message05in", "message06in", "message07in", "message08out",
|
|
"message09in", "message10in", "message11in", nullptr
|
|
};
|
|
for (int i = 0; kChatNodes[i]; ++i) {
|
|
uiManager.setNodeVisible(kChatNodes[i], false);
|
|
auto n = uiManager.findNode(kChatNodes[i]);
|
|
if (n) { n->scaleX = 1.0f; n->scaleY = 1.0f; }
|
|
}
|
|
}
|
|
|
|
void MenuManager::recomputePhoneChatPositions() {
|
|
float totalHeight = 0.0f;
|
|
for (size_t i = 0; i < phoneChatVisibleBubbles_.size(); ++i) {
|
|
totalHeight += phoneChatVisibleBubbles_[i].height;
|
|
if (i > 0) totalHeight += CHAT_SPACING;
|
|
}
|
|
|
|
const float fboH = CHAT_FBO_OFFSET_UP_L + CHAT_FBO_OFFSET_DOWN_L;
|
|
const float topY = (totalHeight <= fboH) ? fboH : totalHeight;
|
|
|
|
float cursor = topY;
|
|
for (auto& bubble : phoneChatVisibleBubbles_) {
|
|
auto node = chatUiManager.findNode(bubble.nodeName);
|
|
if (!node) continue;
|
|
node->localY = cursor - bubble.height;
|
|
cursor -= bubble.height + CHAT_SPACING;
|
|
}
|
|
chatUiManager.updateAllLayouts();
|
|
}
|
|
|
|
void MenuManager::recomputePhoneChatPositionsPortrait() {
|
|
float totalHeight = 0.0f;
|
|
for (size_t i = 0; i < phoneChatVisibleBubbles_.size(); ++i) {
|
|
totalHeight += phoneChatVisibleBubbles_[i].height;
|
|
if (i > 0) totalHeight += CHAT_SPACING;
|
|
}
|
|
|
|
const float fboH = CHAT_FBO_OFFSET_UP_P + CHAT_FBO_OFFSET_DOWN_P;
|
|
const float topY = (totalHeight <= fboH) ? fboH : totalHeight;
|
|
|
|
float cursor = topY;
|
|
for (auto& bubble : phoneChatVisibleBubbles_) {
|
|
auto node = chatUiManager.findNode(bubble.nodeName);
|
|
if (!node) continue;
|
|
node->localY = cursor - bubble.height;
|
|
cursor -= bubble.height + CHAT_SPACING;
|
|
}
|
|
chatUiManager.updateAllLayouts();
|
|
}
|
|
|
|
void MenuManager::rebuildChatBubblesFromHistory(int chatIndex, bool isPortrait) {
|
|
chatUiManager.clearChatBubbles("chatMessagesContainer");
|
|
phoneChatVisibleBubbles_.clear();
|
|
|
|
if (chatIndex < 0 || chatIndex > 2) return;
|
|
for (const auto& msg : gameState_.chatHistory[chatIndex]) {
|
|
const bool inc = msg.incoming;
|
|
const std::string nodeName = chatUiManager.addChatBubble(
|
|
"chatMessagesContainer", msg.text, inc,
|
|
inc ? texBubbleInCenter_ : texBubbleOutCenter_,
|
|
inc ? texBubbleInLT_ : texBubbleOutLT_,
|
|
inc ? texBubbleInLB_ : texBubbleOutLB_,
|
|
inc ? texBubbleInRT_ : texBubbleOutRT_,
|
|
inc ? texBubbleInRB_ : texBubbleOutRB_,
|
|
renderer, "resources/fonts/DroidSans.ttf", isPortrait ? 32 : 20, isPortrait, CONST_ZIP_FILE);
|
|
if (!nodeName.empty()) {
|
|
auto n = chatUiManager.findNode(nodeName);
|
|
phoneChatVisibleBubbles_.push_back({ nodeName, n ? n->height : 60.0f });
|
|
}
|
|
}
|
|
|
|
if (isPortrait)
|
|
{
|
|
recomputePhoneChatPositionsPortrait();
|
|
}
|
|
else
|
|
{
|
|
recomputePhoneChatPositions();
|
|
}
|
|
|
|
}
|
|
|
|
void MenuManager::onChatBubbleReady(const std::string& text, bool incoming) {
|
|
if (activeChatIndex_ < 0) return;
|
|
|
|
auto& history = gameState_.chatHistory[activeChatIndex_];
|
|
if (static_cast<int>(history.size()) >= 5) {
|
|
history.erase(history.begin());
|
|
}
|
|
history.push_back({ text, incoming });
|
|
|
|
if (uiState_ != GameUiState::PhoneScreen) return;
|
|
|
|
bool isPortrait = isPortraitMode();
|
|
|
|
const std::string nodeName = chatUiManager.addChatBubble(
|
|
"chatMessagesContainer", text, incoming,
|
|
incoming ? texBubbleInCenter_ : texBubbleOutCenter_,
|
|
incoming ? texBubbleInLT_ : texBubbleOutLT_,
|
|
incoming ? texBubbleInLB_ : texBubbleOutLB_,
|
|
incoming ? texBubbleInRT_ : texBubbleOutRT_,
|
|
incoming ? texBubbleInRB_ : texBubbleOutRB_,
|
|
renderer, "resources/fonts/DroidSans.ttf", isPortrait ? 32 : 20, isPortrait, CONST_ZIP_FILE);
|
|
|
|
if (nodeName.empty()) return;
|
|
|
|
auto node = chatUiManager.findNode(nodeName);
|
|
if (node) {
|
|
node->scaleX = 0.0f;
|
|
node->scaleY = 0.0f;
|
|
phoneChatVisibleBubbles_.push_back({ nodeName, node->height });
|
|
}
|
|
if (isPortrait)
|
|
{
|
|
recomputePhoneChatPositionsPortrait();
|
|
}
|
|
else
|
|
{
|
|
recomputePhoneChatPositions();
|
|
}
|
|
chatUiManager.startPopIn(nodeName, 300.0f);
|
|
audioPlayer_.playSoundAsync("audio/537061__imafoley__message-pop-sound.ogg");
|
|
}
|
|
|
|
void MenuManager::setDarklandsMode(bool enabled)
|
|
{
|
|
logger() << "MenuManager::setDarklandsMode called" << std::endl;
|
|
|
|
if (gameState_.currentLocationName == "uni_interior") {
|
|
if (enabled && gameState_.uniIntTutorialState == UniIntTutorialState::Step11) {
|
|
gameState_.uniIntTutorialState = UniIntTutorialState::DarklandsActive;
|
|
}
|
|
applyUniIntHud();
|
|
} else if (gameState_.currentLocationName == "uni_exterior") {
|
|
if (uiState_ == GameUiState::Gameplay) {
|
|
uiManager.replaceRoot(enabled ? hudUniExtDarkRoot : hudUniExtRoot);
|
|
applyCurrentHealthBar();
|
|
setupGameplayHudCallbacks();
|
|
}
|
|
} else {
|
|
uiManager.setNodeVisible("darklandsButton", !enabled);
|
|
uiManager.setNodeVisible("phoneButton", !enabled);
|
|
}
|
|
}
|
|
|
|
void MenuManager::applyUniIntHud()
|
|
{
|
|
if (uiState_ != GameUiState::Gameplay) return;
|
|
std::shared_ptr<UiNode> root;
|
|
if (gameState_.isDarklands) {
|
|
switch (gameState_.uniIntTutorialState) {
|
|
case UniIntTutorialState::DarklandsStep13: root = hudUniIntStep13Root; break;
|
|
case UniIntTutorialState::DarklandsFull: root = hudUniIntDarkFullRoot; break;
|
|
default: root = hudUniIntStep12Root; break;
|
|
}
|
|
} else {
|
|
switch (gameState_.uniIntTutorialState) {
|
|
case UniIntTutorialState::Step10: root = hudUniIntStep10Root; break;
|
|
case UniIntTutorialState::Step11: root = hudUniIntStep11Root; break;
|
|
default: root = hudUniIntFullRoot; break;
|
|
}
|
|
}
|
|
uiManager.replaceRoot(root);
|
|
applyCurrentHealthBar();
|
|
setupGameplayHudCallbacks();
|
|
}
|
|
|
|
void MenuManager::onPlayerStartedWalking()
|
|
{
|
|
if (gameState_.currentLocationName == "uni_interior"
|
|
&& gameState_.isDarklands
|
|
&& gameState_.uniIntTutorialState == UniIntTutorialState::DarklandsActive) {
|
|
uiManager.setNodeVisible("hint_darklands003", false);
|
|
uiManager.setNodeVisible("hint_darklands003_arrow", false);
|
|
}
|
|
}
|
|
|
|
void MenuManager::advanceUniIntDarklandsHud()
|
|
{
|
|
if (gameState_.uniIntTutorialState != UniIntTutorialState::DarklandsActive) return;
|
|
gameState_.uniIntTutorialState = UniIntTutorialState::DarklandsStep13;
|
|
if (gameState_.currentLocationName == "uni_interior" && gameState_.isDarklands && uiState_ == GameUiState::Gameplay)
|
|
applyUniIntHud();
|
|
}
|
|
|
|
void MenuManager::onEnemyKilledInUniInterior()
|
|
{
|
|
if (gameState_.uniIntTutorialState != UniIntTutorialState::DarklandsActive
|
|
&& gameState_.uniIntTutorialState != UniIntTutorialState::DarklandsStep13) return;
|
|
gameState_.uniIntTutorialState = UniIntTutorialState::DarklandsFull;
|
|
if (gameState_.currentLocationName == "uni_interior" && gameState_.isDarklands && uiState_ == GameUiState::Gameplay)
|
|
applyUniIntHud();
|
|
}
|
|
|
|
void MenuManager::updateHealthBar(float hp, float maxHp) {
|
|
gameState_.playerHp = hp;
|
|
gameState_.playerMaxHp = maxHp;
|
|
applyCurrentHealthBar();
|
|
}
|
|
|
|
void MenuManager::onCutsceneStarted() {
|
|
cutsceneHudActive_ = true;
|
|
topUiManager.replaceRoot(hudCutsceneRoot_);
|
|
topUiManager.setButtonCallback("skipButton", [this](const std::string&) {
|
|
if (skipCutsceneFunc) skipCutsceneFunc();
|
|
});
|
|
}
|
|
|
|
void MenuManager::onCutsceneFinished() {
|
|
cutsceneHudActive_ = false;
|
|
topUiManager.replaceRoot(nullptr);
|
|
if (uiState_ == GameUiState::Gameplay)
|
|
onLocationChanged(gameState_.currentLocationName);
|
|
}
|
|
|
|
void MenuManager::applyCurrentHealthBar() {
|
|
logger() << "MenuManager::applyCurrentHealthBar called step 1" << std::endl;
|
|
logger() << "currentPlayerMaxHp_ is " << gameState_.playerMaxHp << std::endl;
|
|
if (gameState_.playerMaxHp <= 0.f) return;
|
|
logger() << "MenuManager::applyCurrentHealthBar called step 2" << std::endl;
|
|
logger() << "currentPlayerHp_ is " << gameState_.playerHp << std::endl;
|
|
|
|
const float fraction = std::clamp(gameState_.playerHp / gameState_.playerMaxHp, 0.f, 1.f);
|
|
|
|
uiManager.setSliderValue("healthBarFill", fraction);
|
|
std::string hpText = std::to_string(static_cast<int>(gameState_.playerHp)) + "/" +
|
|
std::to_string(static_cast<int>(gameState_.playerMaxHp));
|
|
if (hpText.size() < 7) hpText.insert(0, 7 - hpText.size(), ' ');
|
|
uiManager.setText("healthValue", hpText);
|
|
}
|
|
|
|
// ---- Toast notification system ----
|
|
|
|
float MenuManager::ToastEntry::currentAlpha() const {
|
|
switch (state) {
|
|
case State::FadeIn: return min(timer / TOAST_FADE_MS, 1.0f);
|
|
case State::Visible: return 1.0f;
|
|
case State::FadeOut: return max(1.0f - timer / TOAST_FADE_MS, 0.0f);
|
|
}
|
|
return 0.0f;
|
|
}
|
|
|
|
void MenuManager::showToast(std::shared_ptr<Texture> toastTexture, const std::string& text) {
|
|
if (activeToasts_.size() < 3) {
|
|
ToastEntry e;
|
|
e.toastTexture = toastTexture;
|
|
e.text = text;
|
|
activeToasts_.push_back(std::move(e));
|
|
} else {
|
|
toastQueue_.push_back({ toastTexture, text });
|
|
}
|
|
applyToastsToUi();
|
|
}
|
|
|
|
void MenuManager::updateToasts(float deltaMs) {
|
|
// Advance timers and state transitions
|
|
for (auto& e : activeToasts_) {
|
|
e.timer += deltaMs;
|
|
if (e.state == ToastEntry::State::FadeIn && e.timer >= TOAST_FADE_MS) {
|
|
e.state = ToastEntry::State::Visible;
|
|
e.timer = 0.0f;
|
|
} else if (e.state == ToastEntry::State::Visible && e.timer >= TOAST_VISIBLE_MS) {
|
|
e.state = ToastEntry::State::FadeOut;
|
|
e.timer = 0.0f;
|
|
}
|
|
}
|
|
|
|
// Remove completed fade-outs
|
|
activeToasts_.erase(
|
|
std::remove_if(activeToasts_.begin(), activeToasts_.end(),
|
|
[](const ToastEntry& e) {
|
|
return e.state == ToastEntry::State::FadeOut && e.timer >= TOAST_FADE_MS;
|
|
}),
|
|
activeToasts_.end());
|
|
|
|
// Promote queued messages into free slots
|
|
while (!toastQueue_.empty() && activeToasts_.size() < 3) {
|
|
ToastEntry e;
|
|
e.toastTexture = toastQueue_.front().toastTexture;
|
|
e.text = toastQueue_.front().text;
|
|
toastQueue_.pop_front();
|
|
activeToasts_.push_back(std::move(e));
|
|
}
|
|
|
|
// Always sync UI — even with no active toasts, this hides stale widgets
|
|
// that couldn't be hidden while a pushed menu (inventory/journal/phone) was active.
|
|
applyToastsToUi();
|
|
}
|
|
|
|
void MenuManager::applyToastsToUi() {
|
|
static const char* imgNames[3] = { "toast001", "toast002", "toast003" };
|
|
static const char* txtNames[3] = { "toast001text", "toast002text", "toast003text" };
|
|
|
|
for (int i = 0; i < 3; ++i) {
|
|
if (i < static_cast<int>(activeToasts_.size())) {
|
|
const ToastEntry& e = activeToasts_[i];
|
|
const float a = e.currentAlpha();
|
|
|
|
auto img = uiManager.findStaticImage(imgNames[i]);
|
|
auto tv = uiManager.findTextView(txtNames[i]);
|
|
|
|
if (img) {
|
|
img->texture = e.toastTexture;
|
|
img->alpha = a;
|
|
}
|
|
if (tv) {
|
|
tv->color[3] = a;
|
|
}
|
|
uiManager.setText(txtNames[i], e.text);
|
|
uiManager.setNodeVisible(imgNames[i], true);
|
|
uiManager.setNodeVisible(txtNames[i], true);
|
|
} else {
|
|
uiManager.setNodeVisible(imgNames[i], false);
|
|
uiManager.setNodeVisible(txtNames[i], false);
|
|
}
|
|
}
|
|
}
|
|
|
|
void MenuManager::hideAllToastWidgets() {
|
|
static const char* names[6] = {
|
|
"toast001", "toast001text",
|
|
"toast002", "toast002text",
|
|
"toast003", "toast003text"
|
|
};
|
|
for (const char* name : names)
|
|
uiManager.setNodeVisible(name, false);
|
|
}
|
|
|
|
void MenuManager::update(float deltaMs) {
|
|
updateToasts(deltaMs);
|
|
chatUiManager.update(deltaMs);
|
|
}
|
|
|
|
void MenuManager::drawChatFbo(Renderer& renderer) {
|
|
if (!chatFrameBuffer_) return;
|
|
|
|
const bool portrait = isPortraitMode();
|
|
const float fboW = portrait ? CHAT_FBO_WIDTH_P : CHAT_FBO_WIDTH_L;
|
|
const float offsetUp = portrait ? CHAT_FBO_OFFSET_UP_P : CHAT_FBO_OFFSET_UP_L;
|
|
const float offsetDown= portrait ? CHAT_FBO_OFFSET_DOWN_P : CHAT_FBO_OFFSET_DOWN_L;
|
|
const float fboH = offsetUp + offsetDown;
|
|
const float projW = Environment::projectionWidth;
|
|
const float projH = Environment::projectionHeight;
|
|
|
|
// Pass 1: render chatUiManager into FBO
|
|
chatFrameBuffer_->Bind();
|
|
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
|
|
glClear(GL_COLOR_BUFFER_BIT);
|
|
glEnable(GL_BLEND);
|
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
chatUiManager.draw(renderer, fboW, fboH);
|
|
chatFrameBuffer_->Unbind();
|
|
|
|
// Pass 2: blit FBO texture onto screen, centered with up/down offsets from screen center
|
|
if (chatFboQuadLastProjW_ != projW || chatFboQuadLastPortrait_ != portrait ||
|
|
chatFboQuadLastProjH_ != projH) {
|
|
const float x0 = (projW - fboW) * 0.5f;
|
|
const float y0 = projH * 0.5f - offsetDown;
|
|
const float x1 = x0 + fboW;
|
|
const float y1 = y0 + fboH;
|
|
VertexDataStruct vd;
|
|
vd.PositionData = { {x0,y0,0},{x0,y1,0},{x1,y1,0},
|
|
{x0,y0,0},{x1,y1,0},{x1,y0,0} };
|
|
vd.TexCoordData = { {0,0},{0,1},{1,1},
|
|
{0,0},{1,1},{1,0} };
|
|
chatFboQuad_.AssignFrom(vd);
|
|
chatFboQuadLastProjW_ = projW;
|
|
chatFboQuadLastProjH_ = projH;
|
|
chatFboQuadLastPortrait_ = portrait;
|
|
}
|
|
|
|
glEnable(GL_BLEND);
|
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
renderer.PushProjectionMatrix(projW, projH, -1, 1);
|
|
renderer.PushMatrix();
|
|
renderer.LoadIdentity();
|
|
renderer.RenderUniform1f("uAlpha", 1.0f);
|
|
glBindTexture(GL_TEXTURE_2D, chatFrameBuffer_->getTextureID());
|
|
renderer.DrawVertexRenderStruct(chatFboQuad_);
|
|
renderer.PopMatrix();
|
|
renderer.PopProjectionMatrix();
|
|
}
|
|
|
|
void MenuManager::saveSettings() {
|
|
nlohmann::json root;
|
|
root["musicVolume"] = audioPlayer_.getMusicVolume();
|
|
root["soundVolume"] = audioPlayer_.getSoundVolume();
|
|
root["musicEnabled"] = audioPlayer_.isMusicEnabled();
|
|
root["soundEnabled"] = audioPlayer_.isSoundEnabled();
|
|
root["fullscreen"] = Environment::isFullscreen;
|
|
root["language"] = FRG::languageToCode(FRG::g_currentLanguage);
|
|
root["shadow"] = shadowsEnabled;
|
|
|
|
saveJsonToFile(root, "settings.json");
|
|
}
|
|
|
|
void MenuManager::loadSettings() {
|
|
// Note: on desktop, main() already reads settings.json and creates the window
|
|
// directly in the right fullscreen state (see WIN32 main() in main.cpp) to avoid
|
|
// a windowed -> fullscreen flash. The block below just keeps Environment::isFullscreen
|
|
// in sync in case this is ever called from a path that didn't pre-apply it.
|
|
// Language is loaded separately, even earlier (see FRG::loadLanguageSetting()
|
|
// callers), because some location scripts start a dialogue as soon as they're
|
|
// loaded, before this function ever runs.
|
|
FRG::loadLanguageSetting();
|
|
|
|
const std::string content = FRG::readSavedTextFile("settings.json");
|
|
if (content.empty()) return;
|
|
try {
|
|
const nlohmann::json root = nlohmann::json::parse(content);
|
|
if (root.contains("musicVolume"))
|
|
audioPlayer_.setMusicVolume(root["musicVolume"].get<int>());
|
|
if (root.contains("soundVolume"))
|
|
audioPlayer_.setSoundVolume(root["soundVolume"].get<int>());
|
|
if (root.contains("musicEnabled"))
|
|
audioPlayer_.setMusicEnabled(root["musicEnabled"].get<bool>());
|
|
if (root.contains("soundEnabled"))
|
|
audioPlayer_.setSoundEnabled(root["soundEnabled"].get<bool>());
|
|
if (root.contains("fullscreen")) {
|
|
#ifndef EMSCRIPTEN
|
|
Environment::setFullscreen(root["fullscreen"].get<bool>());
|
|
#endif
|
|
}
|
|
if (root.contains("shadow")) {
|
|
if (shadowMapSettingsChangedFunc)
|
|
{
|
|
shadowMapSettingsChangedFunc(root["shadow"].get<bool>());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (shadowMapSettingsChangedFunc)
|
|
{
|
|
shadowMapSettingsChangedFunc(shadowsEnabled);
|
|
}
|
|
}
|
|
} catch (const std::exception& e) {
|
|
std::cerr << "[settings] Failed to parse settings.json: " << e.what() << std::endl;
|
|
}
|
|
}
|
|
|
|
bool MenuManager::isPortraitMode() const {
|
|
return Environment::projectionWidth < Environment::projectionHeight;
|
|
}
|
|
|
|
void MenuManager::onOrientationChanged() {
|
|
if (uiState_ == GameUiState::Inventory) {
|
|
const int savedIndex = inventorySelectedIndex_;
|
|
const bool wasOnItemDetail = (inventoryMenuStackDepth_ == 2);
|
|
|
|
for (int i = 0; i < inventoryMenuStackDepth_; ++i) uiManager.popMenu();
|
|
inventoryMenuStackDepth_ = 0;
|
|
uiState_ = GameUiState::Gameplay;
|
|
inventorySelectedIndex_ = -1;
|
|
|
|
openInventory();
|
|
|
|
const int itemCount = static_cast<int>(inventory->getItems().size());
|
|
if (isPortraitMode() && wasOnItemDetail &&
|
|
savedIndex >= 0 && savedIndex < itemCount) {
|
|
selectInventoryItem(savedIndex);
|
|
} else if (!isPortraitMode() && savedIndex > 0 && savedIndex < itemCount) {
|
|
selectInventoryItem(savedIndex);
|
|
}
|
|
|
|
uiManager.updateAllLayouts();
|
|
return;
|
|
}
|
|
|
|
if (uiState_ == GameUiState::QuestJournal) {
|
|
const int savedQuestIndex = selectedQuestIndex;
|
|
const bool wasOnItemDetail = (journalMenuStackDepth_ == 2);
|
|
|
|
for (int i = 0; i < journalMenuStackDepth_; ++i) uiManager.popMenu();
|
|
journalMenuStackDepth_ = 0;
|
|
uiState_ = GameUiState::Gameplay;
|
|
selectedQuestIndex = -1;
|
|
visibleQuestIds.clear();
|
|
|
|
openQuestJournal();
|
|
|
|
if (isPortraitMode() && wasOnItemDetail &&
|
|
savedQuestIndex >= 0 && savedQuestIndex < static_cast<int>(visibleQuestIds.size())) {
|
|
selectQuestByIndex(savedQuestIndex);
|
|
} else if (!isPortraitMode() && savedQuestIndex > 0 &&
|
|
savedQuestIndex < static_cast<int>(visibleQuestIds.size())) {
|
|
selectQuestByIndex(savedQuestIndex);
|
|
}
|
|
|
|
uiManager.updateAllLayouts();
|
|
return;
|
|
}
|
|
|
|
if (uiState_ != GameUiState::PhoneScreen) return;
|
|
|
|
const PhoneSubScreen savedSubScreen = currentPhoneSubScreen_;
|
|
const int savedChatIndex = activeChatIndex_;
|
|
|
|
// Collapse all stacked phone menus and reopen in the new orientation.
|
|
activeChatIndex_ = -1;
|
|
phoneChatVisibleBubbles_.clear();
|
|
const int depth = uiManager.menuStackSize();
|
|
for (int i = 0; i < depth; ++i) uiManager.popMenu();
|
|
uiState_ = GameUiState::Gameplay;
|
|
|
|
switch (savedSubScreen) {
|
|
case PhoneSubScreen::Bank:
|
|
openPhoneScreen();
|
|
openPhoneBank();
|
|
break;
|
|
case PhoneSubScreen::Video:
|
|
openPhoneScreen();
|
|
openPhoneVideo();
|
|
break;
|
|
case PhoneSubScreen::MapDorm:
|
|
openPhoneScreen();
|
|
openPhoneMapScreen(isPortraitMode() ? verticalPhoneMapDormRoot : phoneMapDormRoot);
|
|
currentPhoneSubScreen_ = PhoneSubScreen::MapDorm;
|
|
break;
|
|
case PhoneSubScreen::MapUni:
|
|
openPhoneScreen();
|
|
openPhoneMapScreen(isPortraitMode() ? verticalPhoneMapUniRoot : phoneMapUniRoot);
|
|
currentPhoneSubScreen_ = PhoneSubScreen::MapUni;
|
|
break;
|
|
case PhoneSubScreen::ChatList:
|
|
openPhoneScreen();
|
|
openPhoneMessenger();
|
|
break;
|
|
case PhoneSubScreen::Chat: {
|
|
openPhoneScreen();
|
|
openPhoneMessenger();
|
|
std::shared_ptr<UiNode> chatRoot;
|
|
if (savedChatIndex == 0) chatRoot = isPortraitMode() ? verticalPhoneChat1Root : phoneChat1Root;
|
|
else if (savedChatIndex == 1) chatRoot = isPortraitMode() ? verticalPhoneChat2Root : phoneChat2Root;
|
|
else if (savedChatIndex == 2) chatRoot = isPortraitMode() ? verticalPhoneChat3Root : phoneChat3Root;
|
|
if (chatRoot) openPhoneChatFromList(savedChatIndex, chatRoot);
|
|
break;
|
|
}
|
|
default:
|
|
openPhoneScreen();
|
|
break;
|
|
}
|
|
|
|
uiManager.updateAllLayouts();
|
|
}
|
|
|
|
} // namespace FRG
|