Major refactoring - internal server state machine implemented
This commit is contained in:
1 parent
71c654e5b6
commit
66e93d46f4
48 files changed
+3948
-3696
No files matched your search
+131
-114
@@ -201,9 +201,9 @@ namespace FRG {
|
||||
}
|
||||
#endif
|
||||
|
||||
MenuManager::MenuManager(Renderer& iRenderer, GameState& gameState, AudioPlayerAsync& audioPlayer) :
|
||||
MenuManager::MenuManager(Renderer& iRenderer, IGameServer& server, AudioPlayerAsync& audioPlayer) :
|
||||
renderer(iRenderer),
|
||||
gameState_(gameState),
|
||||
server_(server),
|
||||
audioPlayer_(audioPlayer)
|
||||
{
|
||||
//Localization
|
||||
@@ -338,9 +338,7 @@ namespace FRG {
|
||||
saveGameScreenRoot = loadLocalizedUi("resources/w/ui/screen_save.json", zipFile);
|
||||
}
|
||||
|
||||
void MenuManager::setup(Inventory& inv, const std::string& zipFile) {
|
||||
inventory = &inv;
|
||||
|
||||
void MenuManager::setup(const std::string& zipFile) {
|
||||
loadUiRoots(zipFile);
|
||||
|
||||
texObjectiveCompleted_ = renderer.textureManager.LoadFromPng("resources/w/ui/img/journal/quest_objective_completed.png", zipFile, true);
|
||||
@@ -360,19 +358,6 @@ namespace FRG {
|
||||
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);
|
||||
@@ -419,9 +404,10 @@ namespace FRG {
|
||||
}
|
||||
|
||||
std::shared_ptr<UiNode> MenuManager::gameplayHudRoot() const {
|
||||
const std::string& loc = gameState_.currentLocationName;
|
||||
if (loc == "uni_exterior") return gameState_.isDarklands ? hudUniExtDarkRoot : hudUniExtRoot;
|
||||
if (loc == "uni_interior") return gameState_.isDarklands ? hudUniIntDarkRoot : hudUniIntRoot;
|
||||
const auto snapshot = server_.snapshot();
|
||||
const std::string& loc = snapshot->currentLocationId;
|
||||
if (loc == "uni_exterior") return snapshot->isDarklands ? hudUniExtDarkRoot : hudUniExtRoot;
|
||||
if (loc == "uni_interior") return snapshot->isDarklands ? hudUniIntDarkRoot : hudUniIntRoot;
|
||||
return hudDormRoot; // dorm, or a new game before the location is set
|
||||
}
|
||||
|
||||
@@ -434,21 +420,22 @@ namespace FRG {
|
||||
}
|
||||
|
||||
void MenuManager::refreshHudVisibility() {
|
||||
if (!inventory) return; // update() already ticks during loading, before setup()
|
||||
const auto itemIds = server_.snapshot()->inventoryItemIds;
|
||||
|
||||
const TutorialStep step = gameState_.tutorialStep;
|
||||
const bool tutorial = gameState_.isTutorialActive();
|
||||
const bool hasPhone = inventory->hasItem("phone");
|
||||
const bool hasJournal = inventory->hasItem("journal");
|
||||
const bool hasNote = inventory->hasItem("note_spell");
|
||||
const bool dark = gameState_.isDarklands;
|
||||
const auto snapshot = server_.snapshot();
|
||||
const TutorialStep step = snapshot->tutorialStep;
|
||||
const bool tutorial = snapshot->isTutorialActive();
|
||||
const bool hasPhone = std::find(itemIds.begin(), itemIds.end(), "phone") != itemIds.end();
|
||||
const bool hasJournal = std::find(itemIds.begin(), itemIds.end(), "journal") != itemIds.end();
|
||||
const bool hasNote = std::find(itemIds.begin(), itemIds.end(), "note_spell") != itemIds.end();
|
||||
const bool dark = snapshot->isDarklands;
|
||||
|
||||
auto show = [this](std::initializer_list<const char*> names, bool visible) {
|
||||
for (const char* name : names) uiManager.setNodeVisible(name, visible);
|
||||
};
|
||||
|
||||
// --- HUD buttons: present while the matching item is in the inventory ---
|
||||
show({ "inventoryButton" }, inventory->getCount() > 0);
|
||||
show({ "inventoryButton" }, !itemIds.empty());
|
||||
show({ "phoneButton" }, hasPhone && !dark);
|
||||
show({ "journalButton" }, hasJournal);
|
||||
show({ "darklandsButton" }, hasNote && !dark);
|
||||
@@ -466,9 +453,9 @@ namespace FRG {
|
||||
show({ "hint4" }, step == TutorialStep::Zoom);
|
||||
show({ "hint5" }, step == TutorialStep::PickUpItem);
|
||||
// ...then the hints for the picked-up items, which can be done in any order.
|
||||
const bool chatHint = tutorial && !gameState_.tutorialPhoneChatScreenOpened;
|
||||
const bool journalHint = tutorial && !gameState_.tutorialJournalScreenOpened;
|
||||
const bool taxiHint = tutorial && gameState_.tutorialNeedOpenTaxiScreen;
|
||||
const bool chatHint = tutorial && !snapshot->tutorialPhoneChatScreenOpened;
|
||||
const bool journalHint = tutorial && !snapshot->tutorialJournalScreenOpened;
|
||||
const bool taxiHint = tutorial && snapshot->tutorialNeedOpenTaxiScreen;
|
||||
show({ "hint6a", "hint6aarrow" }, hasPhone && chatHint && !taxiHint);
|
||||
show({ "hint6b", "hint6barrow" }, hasJournal && journalHint);
|
||||
show({ "hint7", "hint7arrow" }, taxiHint);
|
||||
@@ -485,7 +472,7 @@ namespace FRG {
|
||||
}
|
||||
|
||||
void MenuManager::showMainMenu() {
|
||||
gameState_.currentLocationName.clear();
|
||||
server_.submit(SetCurrentLocationCommand{""});
|
||||
|
||||
audioPlayer_.crossFadeMusicAsync("music/main menu final.ogg");
|
||||
lastMusicTrack = "music/main menu final.ogg";
|
||||
@@ -546,7 +533,6 @@ namespace FRG {
|
||||
}
|
||||
|
||||
void MenuManager::showMenuAfterGameCompleted() {
|
||||
//gameState_.currentLocationName.clear();
|
||||
|
||||
audioPlayer_.crossFadeMusicAsync("music/main menu final.ogg");
|
||||
lastMusicTrack = "music/main menu final.ogg";
|
||||
@@ -1060,8 +1046,8 @@ namespace FRG {
|
||||
|
||||
steps.push_back([this]() {
|
||||
ItemRegistry::instance().loadFromJson("resources/config/items.json", CONST_ZIP_FILE);
|
||||
if (const CampaignConfig* campaign = CampaignRegistry::instance().find(gameState_.campaignId))
|
||||
gameState_.questJournal.reloadDefinitionsPreservingState(campaign->questsPath, CONST_ZIP_FILE);
|
||||
if (const CampaignConfig* campaign = CampaignRegistry::instance().find(server_.snapshot()->campaignId))
|
||||
server_.submit(ReloadQuestDefinitionsCommand{campaign->questsPath, CONST_ZIP_FILE});
|
||||
});
|
||||
|
||||
steps.push_back([this]() {
|
||||
@@ -1197,7 +1183,7 @@ namespace FRG {
|
||||
|
||||
uiManager.setButtonCallback("inventoryMain", [this](const std::string&) {});
|
||||
|
||||
const auto& items = inventory->getItems();
|
||||
const auto items = inventoryItems();
|
||||
/*logger() << "Item count: " << items.size() << std::endl;
|
||||
for (int x = 0; x < items.size(); x++)
|
||||
{
|
||||
@@ -1236,7 +1222,7 @@ namespace FRG {
|
||||
|
||||
void MenuManager::selectInventoryItem(int index) {
|
||||
logger() << "MenuManager::selectInventoryItem: " << index << std::endl;
|
||||
const auto& items = inventory->getItems();
|
||||
const auto items = inventoryItems();
|
||||
if (index < 0 || index >= static_cast<int>(items.size())) return;
|
||||
|
||||
if (isPortraitMode() && inventoryMenuStackDepth_ == 1) {
|
||||
@@ -1288,7 +1274,10 @@ namespace FRG {
|
||||
|
||||
void MenuManager::openQuestJournal() {
|
||||
uiState_ = GameUiState::QuestJournal;
|
||||
gameState_.tutorialJournalScreenOpened = true;
|
||||
auto tutorial = server_.snapshot();
|
||||
server_.submit(SetTutorialFlagsCommand{
|
||||
tutorial->tutorialPhoneChatScreenOpened, true,
|
||||
tutorial->tutorialNeedOpenTaxiScreen, tutorial->tutorialTaxiHintDone});
|
||||
journalMenuStackDepth_ = 1;
|
||||
|
||||
const bool portrait = isPortraitMode();
|
||||
@@ -1380,9 +1369,10 @@ namespace FRG {
|
||||
audioPlayer_.playSoundAsync("resources/audio/537061__imafoley__message-pop-sound.ogg");
|
||||
});
|
||||
|
||||
if (gameState_.isNight)
|
||||
const auto timeState = server_.snapshot();
|
||||
if (timeState->isNight)
|
||||
{
|
||||
if (gameState_.isDawn)
|
||||
if (timeState->isDawn)
|
||||
{
|
||||
uiManager.setNodeVisible("phoneTimeDay", false);
|
||||
uiManager.setNodeVisible("phoneTimeNight", false);
|
||||
@@ -1418,51 +1408,67 @@ namespace FRG {
|
||||
});
|
||||
uiManager.setButtonCallback("phoneMain", [this](const std::string&) {});
|
||||
uiManager.setTextButtonCallback("chat1button", [this](const std::string&) {
|
||||
gameState_.chatUnread[0] = false;
|
||||
setChatUnread(0, false);
|
||||
openPhoneChatFromList(0, isPortraitMode() ? verticalPhoneChat1Root : phoneChat1Root);
|
||||
audioPlayer_.playSoundAsync("resources/audio/537061__imafoley__message-pop-sound.ogg");
|
||||
});
|
||||
uiManager.setTextButtonCallback("chat2button", [this](const std::string&) {
|
||||
gameState_.chatUnread[1] = false;
|
||||
setChatUnread(1, false);
|
||||
openPhoneChatFromList(1, isPortraitMode() ? verticalPhoneChat2Root : phoneChat2Root);
|
||||
audioPlayer_.playSoundAsync("resources/audio/537061__imafoley__message-pop-sound.ogg");
|
||||
});
|
||||
uiManager.setTextButtonCallback("chat3button", [this](const std::string&) {
|
||||
gameState_.chatUnread[2] = false;
|
||||
setChatUnread(2, false);
|
||||
openPhoneChatFromList(2, isPortraitMode() ? verticalPhoneChat3Root : phoneChat3Root);
|
||||
audioPlayer_.playSoundAsync("resources/audio/537061__imafoley__message-pop-sound.ogg");
|
||||
});
|
||||
}
|
||||
|
||||
void MenuManager::refreshChatUnreadIndicators() {
|
||||
const auto state = server_.snapshot();
|
||||
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]);
|
||||
uiManager.setNodeVisible(kUnreadNodes[i], state->chatUnread[i]);
|
||||
if (!state->chatPreview[i].empty())
|
||||
uiManager.setText(kMsgNodes[i], state->chatPreview[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void MenuManager::setChatUnread(int chatIndex, bool unread, const std::string& previewMsg) {
|
||||
if (chatIndex < 0 || chatIndex > 2) return;
|
||||
gameState_.chatUnread[chatIndex] = unread;
|
||||
const auto state = server_.snapshot();
|
||||
std::string preview = state->chatPreview[chatIndex];
|
||||
if (!previewMsg.empty()) {
|
||||
gameState_.chatPreviewMsg[chatIndex] = trimChatPreview(previewMsg);
|
||||
} else if (!unread && !gameState_.chatHistory[chatIndex].empty()) {
|
||||
gameState_.chatPreviewMsg[chatIndex] = trimChatPreview(gameState_.chatHistory[chatIndex].back().text);
|
||||
preview = trimChatPreview(previewMsg);
|
||||
} else if (!unread && !state->chatHistory[chatIndex].empty()) {
|
||||
preview = trimChatPreview(state->chatHistory[chatIndex].back().text);
|
||||
}
|
||||
server_.submit(SetChatCommand{chatIndex, unread, std::move(preview)});
|
||||
}
|
||||
|
||||
void MenuManager::spendMoney(int amount) {
|
||||
gameState_.money -= amount;
|
||||
server_.submit(SpendMoneyCommand{amount});
|
||||
}
|
||||
|
||||
std::vector<Item> MenuManager::inventoryItems() const {
|
||||
std::vector<Item> items;
|
||||
for (const std::string& id : server_.snapshot()->inventoryItemIds) {
|
||||
if (const Item* item = ItemRegistry::instance().findById(id)) items.push_back(*item);
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
bool MenuManager::hasInventoryItem(const std::string& id) const {
|
||||
const auto& ids = server_.snapshot()->inventoryItemIds;
|
||||
return std::find(ids.begin(), ids.end(), id) != ids.end();
|
||||
}
|
||||
|
||||
|
||||
void MenuManager::openPhoneBank() {
|
||||
currentPhoneSubScreen_ = PhoneSubScreen::Bank;
|
||||
uiManager.pushMenuFromSavedRoot(isPortraitMode() ? verticalPhoneBankRoot : phoneBankRoot);
|
||||
uiManager.setText("balanceText", formatMoney(gameState_.money));
|
||||
uiManager.setText("balanceText", formatMoney(server_.snapshot()->money));
|
||||
|
||||
uiManager.setButtonCallback("phoneExitButton", [this](const std::string&) {
|
||||
closePhoneEntirely();
|
||||
@@ -1492,13 +1498,15 @@ namespace FRG {
|
||||
});
|
||||
uiManager.setButtonCallback("videoSkip", [this](const std::string&) {
|
||||
closePhoneEntirely();
|
||||
if (gameState_.isNight)
|
||||
const auto state = server_.snapshot();
|
||||
if (state->isNight)
|
||||
{
|
||||
startDialogueFunc("dialog_video002");
|
||||
}
|
||||
else
|
||||
{
|
||||
auto day = gameState_.globalInts["day"];
|
||||
auto dayIt = state->globalInts.find("day");
|
||||
const int day = dayIt == state->globalInts.end() ? 0 : dayIt->second;
|
||||
if (day == 0)
|
||||
{
|
||||
startDialogueFunc("dialog_video003");
|
||||
@@ -1513,10 +1521,11 @@ namespace FRG {
|
||||
}
|
||||
|
||||
void MenuManager::openPhoneTaxi() {
|
||||
if (gameState_.currentLocationName == "uni_interior") {
|
||||
const std::string location = server_.snapshot()->currentLocationId;
|
||||
if (location == "uni_interior") {
|
||||
closePhoneEntirely();
|
||||
if (startDialogueFunc) startDialogueFunc("dialog_taxi003");
|
||||
} else if (gameState_.currentLocationName == "uni_exterior") {
|
||||
} else if (location == "uni_exterior") {
|
||||
currentPhoneSubScreen_ = PhoneSubScreen::MapUni;
|
||||
openPhoneMapScreen(isPortraitMode() ? verticalPhoneMapUniRoot : phoneMapUniRoot);
|
||||
} else {
|
||||
@@ -1539,17 +1548,18 @@ namespace FRG {
|
||||
audioPlayer_.playSoundAsync("resources/audio/537061__imafoley__message-pop-sound.ogg");
|
||||
});
|
||||
uiManager.setButtonCallback("mapGo", [this](const std::string&) {
|
||||
gameState_.tutorialNeedOpenTaxiScreen = false;
|
||||
gameState_.tutorialTaxiHintDone = true;
|
||||
auto state = server_.snapshot();
|
||||
server_.submit(SetTutorialFlagsCommand{
|
||||
state->tutorialPhoneChatScreenOpened, state->tutorialJournalScreenOpened,
|
||||
false, true});
|
||||
|
||||
if (gameState_.taxiIsCalled == false)
|
||||
if (!state->taxiIsCalled)
|
||||
{
|
||||
gameState_.taxiIsCalled = true;
|
||||
if (callTaxiFunc)
|
||||
{
|
||||
callTaxiFunc();
|
||||
}
|
||||
gameState_.money -= 500;
|
||||
server_.submit(SetTaxiCalledCommand{true});
|
||||
server_.submit(StartTaxiCommand{state->currentLocationId});
|
||||
server_.submit(SetTriggerZoneEnabledCommand{
|
||||
state->currentLocationId, "taxi_zone001", false});
|
||||
server_.submit(SpendMoneyCommand{500});
|
||||
closePhoneEntirely();
|
||||
if (startDialogueFunc) startDialogueFunc("dialog_taxi002");
|
||||
audioPlayer_.playSoundAsync("resources/audio/537061__imafoley__message-pop-sound.ogg");
|
||||
@@ -1569,7 +1579,10 @@ namespace FRG {
|
||||
activeChatIndex_ = chatIndex;
|
||||
currentPhoneSubScreen_ = PhoneSubScreen::Chat;
|
||||
phoneChatVisibleBubbles_.clear();
|
||||
gameState_.tutorialPhoneChatScreenOpened = true;
|
||||
auto tutorial = server_.snapshot();
|
||||
server_.submit(SetTutorialFlagsCommand{
|
||||
true, tutorial->tutorialJournalScreenOpened,
|
||||
tutorial->tutorialNeedOpenTaxiScreen, tutorial->tutorialTaxiHintDone});
|
||||
uiManager.pushMenuFromSavedRoot(chatRoot);
|
||||
|
||||
// Build a dedicated chatUiManager root sized to the FBO chat area
|
||||
@@ -1616,11 +1629,7 @@ namespace FRG {
|
||||
}
|
||||
|
||||
void MenuManager::returnToPhoneChatList() {
|
||||
if (activeChatIndex_ >= 0 && activeChatIndex_ <= 2
|
||||
&& !gameState_.chatHistory[activeChatIndex_].empty()) {
|
||||
gameState_.chatPreviewMsg[activeChatIndex_] =
|
||||
trimChatPreview(gameState_.chatHistory[activeChatIndex_].back().text);
|
||||
}
|
||||
if (activeChatIndex_ >= 0 && activeChatIndex_ <= 2) setChatUnread(activeChatIndex_, false);
|
||||
activeChatIndex_ = -1;
|
||||
currentPhoneSubScreen_ = PhoneSubScreen::ChatList;
|
||||
phoneChatVisibleBubbles_.clear();
|
||||
@@ -1634,11 +1643,7 @@ namespace FRG {
|
||||
|
||||
|
||||
void MenuManager::closePhoneEntirely() {
|
||||
if (activeChatIndex_ >= 0 && activeChatIndex_ <= 2
|
||||
&& !gameState_.chatHistory[activeChatIndex_].empty()) {
|
||||
gameState_.chatPreviewMsg[activeChatIndex_] =
|
||||
trimChatPreview(gameState_.chatHistory[activeChatIndex_].back().text);
|
||||
}
|
||||
if (activeChatIndex_ >= 0 && activeChatIndex_ <= 2) setChatUnread(activeChatIndex_, false);
|
||||
activeChatIndex_ = -1;
|
||||
currentPhoneSubScreen_ = PhoneSubScreen::None;
|
||||
uiState_ = GameUiState::Gameplay;
|
||||
@@ -1699,8 +1704,11 @@ namespace FRG {
|
||||
void MenuManager::tutorialShowTaxiHint()
|
||||
{
|
||||
logger() << "tutorialShowTaxiHint" << std::endl;
|
||||
if (!gameState_.isTutorialActive() || gameState_.tutorialTaxiHintDone) return;
|
||||
gameState_.tutorialNeedOpenTaxiScreen = true; // cleared when the taxi is ordered
|
||||
auto state = server_.snapshot();
|
||||
if (!state->isTutorialActive() || state->tutorialTaxiHintDone) return;
|
||||
server_.submit(SetTutorialFlagsCommand{
|
||||
state->tutorialPhoneChatScreenOpened, state->tutorialJournalScreenOpened,
|
||||
true, state->tutorialTaxiHintDone});
|
||||
refreshHudVisibility();
|
||||
}
|
||||
|
||||
@@ -1723,16 +1731,17 @@ namespace FRG {
|
||||
|
||||
void MenuManager::onLocationChanged(const std::string& locationName) {
|
||||
if (uiState_ != GameUiState::Gameplay) return;
|
||||
gameState_.currentLocationName = locationName;
|
||||
server_.submit(SetCurrentLocationCommand{locationName});
|
||||
const auto state = server_.snapshot();
|
||||
showGameplayHud();
|
||||
|
||||
if (locationName == "uni_exterior" || locationName == "uni_interior") {
|
||||
if (gameState_.isDarklands)
|
||||
if (state->isDarklands)
|
||||
{
|
||||
audioPlayer_.crossFadeMusicAsync("music/bishkek fight calm.ogg");
|
||||
lastMusicTrack = "music/bishkek fight calm.ogg";
|
||||
}
|
||||
else if (gameState_.isNight)
|
||||
else if (state->isNight)
|
||||
{
|
||||
audioPlayer_.crossFadeMusicAsync("music/bishkek night.ogg");
|
||||
lastMusicTrack = "music/bishkek night.ogg";
|
||||
@@ -1743,7 +1752,7 @@ namespace FRG {
|
||||
lastMusicTrack = "music/bishkek univer day.ogg";
|
||||
}
|
||||
} else {
|
||||
if (gameState_.isNight)
|
||||
if (state->isNight)
|
||||
{
|
||||
audioPlayer_.crossFadeMusicAsync("music/bishkek night.ogg");
|
||||
lastMusicTrack = "music/bishkek night.ogg";
|
||||
@@ -1759,13 +1768,16 @@ namespace FRG {
|
||||
void MenuManager::onItemPickedUp(const std::string& itemId) {
|
||||
// The new item's HUD button appears through refreshHudVisibility().
|
||||
if (itemId == "phone" || itemId == "journal")
|
||||
gameState_.completeTutorialStep(TutorialStep::PickUpItem);
|
||||
server_.submit(CompleteTutorialStepCommand{TutorialStep::PickUpItem});
|
||||
refreshHudVisibility();
|
||||
}
|
||||
|
||||
void MenuManager::refreshQuestJournalUi() {
|
||||
visibleQuestIds.clear();
|
||||
auto quests = gameState_.questJournal.getVisibleQuests();
|
||||
const auto state = server_.snapshot();
|
||||
std::vector<const Quest::QuestState*> quests;
|
||||
quests.reserve(state->quests.size());
|
||||
for (const auto& quest : state->quests) quests.push_back(&quest);
|
||||
|
||||
std::sort(quests.begin(), quests.end(), [](const Quest::QuestState* a, const Quest::QuestState* b) {
|
||||
const int pa = questStatusPriority(a->status);
|
||||
@@ -1840,7 +1852,10 @@ namespace FRG {
|
||||
}
|
||||
|
||||
selectedQuestIndex = index;
|
||||
Quest::QuestState* quest = gameState_.questJournal.findQuest(visibleQuestIds[index]);
|
||||
const auto state = server_.snapshot();
|
||||
const Quest::QuestState* quest = nullptr;
|
||||
for (const auto& candidate : state->quests)
|
||||
if (candidate.definition.id == visibleQuestIds[index]) quest = &candidate;
|
||||
if (!quest) {
|
||||
return;
|
||||
}
|
||||
@@ -1888,7 +1903,7 @@ namespace FRG {
|
||||
}
|
||||
|
||||
void MenuManager::openPortraitInventoryItem(int index) {
|
||||
const auto& items = inventory->getItems();
|
||||
const auto items = inventoryItems();
|
||||
inventorySelectedIndex_ = index;
|
||||
inventoryMenuStackDepth_ = 2;
|
||||
uiManager.pushMenuFromSavedRoot(portraitInventoryItemRoot);
|
||||
@@ -1943,7 +1958,10 @@ namespace FRG {
|
||||
uiManager.setButtonCallback("journalMain", [this](const std::string&) {});
|
||||
|
||||
|
||||
Quest::QuestState* quest = gameState_.questJournal.findQuest(visibleQuestIds[index]);
|
||||
const auto state = server_.snapshot();
|
||||
const Quest::QuestState* quest = nullptr;
|
||||
for (const auto& candidate : state->quests)
|
||||
if (candidate.definition.id == visibleQuestIds[index]) quest = &candidate;
|
||||
if (!quest) return;
|
||||
const auto& def = quest->definition;
|
||||
|
||||
@@ -2070,7 +2088,8 @@ namespace FRG {
|
||||
phoneChatVisibleBubbles_.clear();
|
||||
|
||||
if (chatIndex < 0 || chatIndex > 2) return;
|
||||
for (const auto& msg : gameState_.chatHistory[chatIndex]) {
|
||||
const auto state = server_.snapshot();
|
||||
for (const auto& msg : state->chatHistory[chatIndex]) {
|
||||
const bool inc = msg.incoming;
|
||||
const std::string nodeName = chatUiManager.addChatBubble(
|
||||
"chatMessagesContainer", msg.text, inc,
|
||||
@@ -2100,11 +2119,7 @@ namespace FRG {
|
||||
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 });
|
||||
server_.submit(AppendChatMessageCommand{activeChatIndex_, text, incoming});
|
||||
|
||||
if (uiState_ != GameUiState::PhoneScreen) return;
|
||||
|
||||
@@ -2143,8 +2158,8 @@ namespace FRG {
|
||||
{
|
||||
logger() << "MenuManager::setDarklandsMode called" << std::endl;
|
||||
|
||||
if (enabled && gameState_.currentLocationName == "uni_interior" && inventory->hasItem("note_spell"))
|
||||
gameState_.completeTutorialStep(TutorialStep::EnterDarklands);
|
||||
if (enabled && server_.snapshot()->currentLocationId == "uni_interior" && hasInventoryItem("note_spell"))
|
||||
server_.submit(CompleteTutorialStepCommand{TutorialStep::EnterDarklands});
|
||||
|
||||
if (uiState_ == GameUiState::Gameplay)
|
||||
showGameplayHud(); // darklands has its own HUD layouts
|
||||
@@ -2152,30 +2167,31 @@ namespace FRG {
|
||||
|
||||
void MenuManager::onPlayerStartedWalking()
|
||||
{
|
||||
if (gameState_.currentLocationName == "uni_interior" && gameState_.isDarklands)
|
||||
gameState_.completeTutorialStep(TutorialStep::DarklandsHealth);
|
||||
const auto state = server_.snapshot();
|
||||
if (state->currentLocationId == "uni_interior" && state->isDarklands)
|
||||
server_.submit(CompleteTutorialStepCommand{TutorialStep::DarklandsHealth});
|
||||
}
|
||||
|
||||
void MenuManager::advanceUniIntDarklandsHud()
|
||||
{
|
||||
// The ghost zone can be reached without a floor tap (DarklandsHealth never completed).
|
||||
if (gameState_.tutorialStep == TutorialStep::DarklandsHealth
|
||||
|| gameState_.tutorialStep == TutorialStep::DarklandsExplore)
|
||||
gameState_.tutorialStep = TutorialStep::DarklandsAttack;
|
||||
const TutorialStep step = server_.snapshot()->tutorialStep;
|
||||
if (step == TutorialStep::DarklandsHealth || step == TutorialStep::DarklandsExplore)
|
||||
server_.submit(SetTutorialStepCommand{TutorialStep::DarklandsAttack});
|
||||
}
|
||||
|
||||
void MenuManager::onEnemyKilledInUniInterior()
|
||||
{
|
||||
// Every location's enemies report here; only the university interior drives the tutorial.
|
||||
if (gameState_.currentLocationName != "uni_interior") return;
|
||||
const auto state = server_.snapshot();
|
||||
if (state->currentLocationId != "uni_interior") return;
|
||||
// The last tutorial step; like the attack hint, also ends the earlier darklands hints.
|
||||
if (gameState_.tutorialStep >= TutorialStep::DarklandsHealth)
|
||||
gameState_.tutorialStep = TutorialStep::Completed;
|
||||
if (state->tutorialStep >= TutorialStep::DarklandsHealth)
|
||||
server_.submit(SetTutorialStepCommand{TutorialStep::Completed});
|
||||
}
|
||||
|
||||
void MenuManager::updateHealthBar(float hp, float maxHp) {
|
||||
gameState_.playerHp = hp;
|
||||
gameState_.playerMaxHp = maxHp;
|
||||
server_.submit(SetPlayerHealthCommand{hp, maxHp});
|
||||
applyCurrentHealthBar();
|
||||
}
|
||||
|
||||
@@ -2191,21 +2207,22 @@ namespace FRG {
|
||||
cutsceneHudActive_ = false;
|
||||
topUiManager.replaceRoot(nullptr);
|
||||
if (uiState_ == GameUiState::Gameplay)
|
||||
onLocationChanged(gameState_.currentLocationName);
|
||||
onLocationChanged(server_.snapshot()->currentLocationId);
|
||||
}
|
||||
|
||||
void MenuManager::applyCurrentHealthBar() {
|
||||
logger() << "MenuManager::applyCurrentHealthBar called step 1" << std::endl;
|
||||
logger() << "currentPlayerMaxHp_ is " << gameState_.playerMaxHp << std::endl;
|
||||
if (gameState_.playerMaxHp <= 0.f) return;
|
||||
const auto state = server_.snapshot();
|
||||
logger() << "currentPlayerMaxHp_ is " << state->playerMaxHp << std::endl;
|
||||
if (state->playerMaxHp <= 0.f) return;
|
||||
logger() << "MenuManager::applyCurrentHealthBar called step 2" << std::endl;
|
||||
logger() << "currentPlayerHp_ is " << gameState_.playerHp << std::endl;
|
||||
logger() << "currentPlayerHp_ is " << state->playerHp << std::endl;
|
||||
|
||||
const float fraction = std::clamp(gameState_.playerHp / gameState_.playerMaxHp, 0.f, 1.f);
|
||||
const float fraction = std::clamp(state->playerHp / state->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));
|
||||
std::string hpText = std::to_string(static_cast<int>(state->playerHp)) + "/" +
|
||||
std::to_string(static_cast<int>(state->playerMaxHp));
|
||||
if (hpText.size() < 7) hpText.insert(0, 7 - hpText.size(), ' ');
|
||||
uiManager.setText("healthValue", hpText);
|
||||
}
|
||||
@@ -2447,7 +2464,7 @@ namespace FRG {
|
||||
|
||||
openInventory();
|
||||
|
||||
const int itemCount = static_cast<int>(inventory->getItems().size());
|
||||
const int itemCount = static_cast<int>(inventoryItems().size());
|
||||
if (isPortraitMode() && wasOnItemDetail &&
|
||||
savedIndex >= 0 && savedIndex < itemCount) {
|
||||
selectInventoryItem(savedIndex);
|
||||
|
||||
Reference in new issue
Block a user