diff --git a/proj-web/CMakeLists.txt b/proj-web/CMakeLists.txt index fbe8cb7..eb7c92d 100644 --- a/proj-web/CMakeLists.txt +++ b/proj-web/CMakeLists.txt @@ -109,6 +109,8 @@ set(SOURCES ../src/LocationEditor.cpp ../src/GameConstants.h ../src/GameConstants.cpp + ../src/GameState.h + ../src/GameState.cpp ../src/ScriptEngine.h ../src/ScriptEngine.cpp ../src/navigation/PathFinder.h diff --git a/proj-windows/CMakeLists.txt b/proj-windows/CMakeLists.txt index 51ee49d..3f20311 100644 --- a/proj-windows/CMakeLists.txt +++ b/proj-windows/CMakeLists.txt @@ -64,6 +64,8 @@ add_executable(witcher001 ../src/LocationEditor.cpp ../src/GameConstants.h ../src/GameConstants.cpp + ../src/GameState.h + ../src/GameState.cpp ../src/ScriptEngine.h ../src/ScriptEngine.cpp ../src/navigation/PathFinder.h diff --git a/src/Game.cpp b/src/Game.cpp index 178b8b0..5795b71 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -64,10 +64,16 @@ namespace ZL } #endif + Location* Game::currentLocation() const { + if (gameState.currentLocationName.empty()) return nullptr; + auto it = gameState.locations.find(gameState.currentLocationName); + return (it != gameState.locations.end()) ? it->second.get() : nullptr; + } + Game::Game() : newTickCount(0) , lastTickCount(0) - , menuManager(renderer, globalInts) + , menuManager(renderer, gameState) , audioPlayer(std::make_unique()) { } @@ -236,7 +242,7 @@ namespace ZL ItemRegistry::instance().loadFromJson("resources/config2/items.json", CONST_ZIP_FILE); - globalFloats["player_hp"] = 200; + gameState.globalFloats["player_hp"] = 200; LocationSetup uniInteriorParams; uniInteriorParams.gameObjectsJsonPath = "resources/config2/gameobjects_uni_interior_x.json"; @@ -311,24 +317,24 @@ namespace ZL uniInteriorParams.interactiveObjectsJsonPath = "resources/config2/interactive_objects_uni_interior_x.json"; uniInteriorParams.playerPosition = Eigen::Vector3f(0.942694, 0, -9.63104); - locations["uni_interior"] = std::make_shared(renderer, inventory); - locations["uni_interior"]->setup(uniInteriorParams, &menuManager.questJournal); - locations["uni_interior"]->scriptEngine.setGlobalStore(&globalInts); - locations["uni_interior"]->scriptEngine.setGlobalFloatStore(&globalFloats); - locations["uni_interior"]->requestNightDayTransition = [this](bool isNight, bool isDawn) { this->menuManager.isNight = isNight; this->menuManager.isDawn = isDawn; }; - locations["uni_interior"]->requestDarklandsTransition = [this]() { return startDarklandsTransition(); }; - locations["uni_interior"]->requestAdvanceDarklandsHud = [this]() { menuManager.advanceUniIntDarklandsHud(); }; - locations["uni_interior"]->requestClosePhone = [this]() { menuManager.closePhoneEntirely(); }; - locations["uni_interior"]->requestReturnToMainMenu = [this]() { - this->currentLocation = nullptr; + gameState.locations["uni_interior"] = std::make_shared(renderer, gameState.inventory); + gameState.locations["uni_interior"]->setup(uniInteriorParams, &gameState.questJournal); + gameState.locations["uni_interior"]->scriptEngine.setGlobalStore(&gameState.globalInts); + gameState.locations["uni_interior"]->scriptEngine.setGlobalFloatStore(&gameState.globalFloats); + gameState.locations["uni_interior"]->requestNightDayTransition = [this](bool isNight, bool isDawn) { gameState.isNight = isNight; gameState.isDawn = isDawn; }; + gameState.locations["uni_interior"]->requestDarklandsTransition = [this]() { return startDarklandsTransition(); }; + gameState.locations["uni_interior"]->requestAdvanceDarklandsHud = [this]() { menuManager.advanceUniIntDarklandsHud(); }; + gameState.locations["uni_interior"]->requestClosePhone = [this]() { menuManager.closePhoneEntirely(); }; + gameState.locations["uni_interior"]->requestReturnToMainMenu = [this]() { + gameState.currentLocationName.clear(); menuManager.showMainMenu(); }; - locations["uni_interior"]->requestSetChatUnread = [this](int idx, bool unread, const std::string& msg) { + gameState.locations["uni_interior"]->requestSetChatUnread = [this](int idx, bool unread, const std::string& msg) { menuManager.setChatUnread(idx, unread, msg); }; - if (locations["uni_interior"]->player) - locations["uni_interior"]->player->state.onDeathAnimComplete = [this]() { startDarklandsTransition(); }; - for (auto& npc : locations["uni_interior"]->npcs) { + if (gameState.locations["uni_interior"]->player) + gameState.locations["uni_interior"]->player->state.onDeathAnimComplete = [this]() { startDarklandsTransition(); }; + for (auto& npc : gameState.locations["uni_interior"]->npcs) { if (npc && npc->state.canAttack) { npc->state.onDeathAnimComplete = [this]() { menuManager.onEnemyKilledInUniInterior(); }; } @@ -348,28 +354,22 @@ namespace ZL uniExteriorParams.npcsJsonPath = "resources/config2/npcs_uni_exterior.json"; uniExteriorParams.dialoguesJsonPath = "resources/dialogue/uni_exterior_dialogues.json"; - locations["uni_exterior"] = std::make_shared(renderer, inventory); - locations["uni_exterior"]->setup(uniExteriorParams, &menuManager.questJournal); - locations["uni_exterior"]->scriptEngine.setGlobalStore(&globalInts); - locations["uni_exterior"]->scriptEngine.setGlobalFloatStore(&globalFloats); - locations["uni_exterior"]->requestNightDayTransition = [this](bool isNight, bool isDawn) { - this->menuManager.isNight = isNight; - this->menuManager.isDawn = isDawn; - /*for (auto& locPair : locations) { - if (locPair.second) { - locPair.second->isNight = isNight; - locPair.second->isDawn = isDawn; - } - }*/ + gameState.locations["uni_exterior"] = std::make_shared(renderer, gameState.inventory); + gameState.locations["uni_exterior"]->setup(uniExteriorParams, &gameState.questJournal); + gameState.locations["uni_exterior"]->scriptEngine.setGlobalStore(&gameState.globalInts); + gameState.locations["uni_exterior"]->scriptEngine.setGlobalFloatStore(&gameState.globalFloats); + gameState.locations["uni_exterior"]->requestNightDayTransition = [this](bool isNight, bool isDawn) { + gameState.isNight = isNight; + gameState.isDawn = isDawn; }; - locations["uni_exterior"]->requestDarklandsTransition = [this]() { return startDarklandsTransition(); }; - locations["uni_exterior"]->requestClosePhone = [this]() { menuManager.closePhoneEntirely(); }; - locations["uni_exterior"]->requestReturnToMainMenu = [this]() { menuManager.showMainMenu(); }; - locations["uni_exterior"]->requestSetChatUnread = [this](int idx, bool unread, const std::string& msg) { + gameState.locations["uni_exterior"]->requestDarklandsTransition = [this]() { return startDarklandsTransition(); }; + gameState.locations["uni_exterior"]->requestClosePhone = [this]() { menuManager.closePhoneEntirely(); }; + gameState.locations["uni_exterior"]->requestReturnToMainMenu = [this]() { menuManager.showMainMenu(); }; + gameState.locations["uni_exterior"]->requestSetChatUnread = [this](int idx, bool unread, const std::string& msg) { menuManager.setChatUnread(idx, unread, msg); }; - if (locations["uni_exterior"]->player) - locations["uni_exterior"]->player->state.onDeathAnimComplete = [this]() { startDarklandsTransition(); }; + if (gameState.locations["uni_exterior"]->player) + gameState.locations["uni_exterior"]->player->state.onDeathAnimComplete = [this]() { startDarklandsTransition(); }; LocationSetup params_dorm; //params_dorm.gameObjectsJsonPath = "resources/config2/gameobjects_dorm.json"; @@ -404,96 +404,96 @@ namespace ZL params_dorm.playerPosition = Eigen::Vector3f(6.76345, 0, -14.6022); - locations["location_dorm"] = std::make_shared(renderer, inventory); - locations["location_dorm"]->setup(params_dorm, &menuManager.questJournal); - locations["location_dorm"]->scriptEngine.setGlobalStore(&globalInts); - locations["location_dorm"]->scriptEngine.setGlobalFloatStore(&globalFloats); - locations["location_dorm"]->requestNightDayTransition = [this](bool isNight, bool isDawn) { this->menuManager.isNight = isNight; this->menuManager.isDawn = isDawn; }; - locations["location_dorm"]->requestDarklandsTransition = [this]() { return startDarklandsTransition(); }; - locations["location_dorm"]->requestClosePhone = [this]() { menuManager.closePhoneEntirely(); }; - locations["location_dorm"]->requestReturnToMainMenu = [this]() { menuManager.showMainMenu(); }; - locations["location_dorm"]->requestSetChatUnread = [this](int idx, bool unread, const std::string& msg) { + gameState.locations["location_dorm"] = std::make_shared(renderer, gameState.inventory); + gameState.locations["location_dorm"]->setup(params_dorm, &gameState.questJournal); + gameState.locations["location_dorm"]->scriptEngine.setGlobalStore(&gameState.globalInts); + gameState.locations["location_dorm"]->scriptEngine.setGlobalFloatStore(&gameState.globalFloats); + gameState.locations["location_dorm"]->requestNightDayTransition = [this](bool isNight, bool isDawn) { gameState.isNight = isNight; gameState.isDawn = isDawn; }; + gameState.locations["location_dorm"]->requestDarklandsTransition = [this]() { return startDarklandsTransition(); }; + gameState.locations["location_dorm"]->requestClosePhone = [this]() { menuManager.closePhoneEntirely(); }; + gameState.locations["location_dorm"]->requestReturnToMainMenu = [this]() { menuManager.showMainMenu(); }; + gameState.locations["location_dorm"]->requestSetChatUnread = [this](int idx, bool unread, const std::string& msg) { menuManager.setChatUnread(idx, unread, msg); }; - if (locations["location_dorm"]->player) - locations["location_dorm"]->player->state.onDeathAnimComplete = [this]() { startDarklandsTransition(); }; + if (gameState.locations["location_dorm"]->player) + gameState.locations["location_dorm"]->player->state.onDeathAnimComplete = [this]() { startDarklandsTransition(); }; - locations["location_dorm"]->onPlayerTaxiRequired = [this]() { + gameState.locations["location_dorm"]->onPlayerTaxiRequired = [this]() { menuManager.tutorialShowTaxiHint(); }; - locations["location_dorm"]->state.tutorialInteractiveObjectsLocked = true; + gameState.locations["location_dorm"]->state.tutorialInteractiveObjectsLocked = true; menuManager.tutorialUnlockInteractiveObjectsFunc = [this]() { - if (locations["location_dorm"]) + if (gameState.locations["location_dorm"]) { - locations["location_dorm"]->state.tutorialInteractiveObjectsLocked = false; + gameState.locations["location_dorm"]->state.tutorialInteractiveObjectsLocked = false; } }; menuManager.callTaxiFunc = [this]() { - if (locations["location_dorm"]) + if (gameState.locations["location_dorm"]) { - locations["location_dorm"]->onPlayerTaxiRequired = nullptr; + gameState.locations["location_dorm"]->onPlayerTaxiRequired = nullptr; } - if (currentLocation) + if (currentLocation()) { - currentLocation->scriptEngine.callCallTaxiCallback(); + currentLocation()->scriptEngine.callCallTaxiCallback(); } }; // Teleport callbacks: destination name and position come from the teleport zone data. auto teleportCallback = [this](const std::string& destName, const Eigen::Vector3f& destPos, float destRotY) { std::cout << "[TELEPORT] " << " -> " << destName << std::endl; - auto it = locations.find(destName); - if (it == locations.end()) { + auto it = gameState.locations.find(destName); + if (it == gameState.locations.end()) { std::cerr << "[TELEPORT] Unknown destination location: " << destName << std::endl; return; } - if (currentLocation) + if (currentLocation()) { - currentLocation->scriptEngine.callLocationExitCallback(); - currentLocation->dialogueSystem.stopDialogue(); + currentLocation()->scriptEngine.callLocationExitCallback(); + currentLocation()->dialogueSystem.stopDialogue(); } - currentLocation = it->second; - if (currentLocation->player) { - currentLocation->player->state.position = destPos; - currentLocation->player->setTarget(destPos); - currentLocation->player->stopInPlace(); - currentLocation->player->state.facingAngle = destRotY; - currentLocation->player->state.targetFacingAngle = destRotY; + gameState.currentLocationName = destName; + Location* loc = currentLocation(); + if (loc->player) { + loc->player->state.position = destPos; + loc->player->setTarget(destPos); + loc->player->stopInPlace(); + loc->player->state.facingAngle = destRotY; + loc->player->state.targetFacingAngle = destRotY; } - currentLocation->state.cameraAzimuth = destRotY; - currentLocation->state.isDarklands = isDarklands; - currentLocation->state.isNight = menuManager.isNight; - currentLocation->state.isDawn = menuManager.isDawn; - currentLocation->scriptEngine.callLocationEnterCallback(); - + loc->state.cameraAzimuth = destRotY; + loc->state.isDarklands = gameState.isDarklands; + loc->state.isNight = gameState.isNight; + loc->state.isDawn = gameState.isDawn; + loc->scriptEngine.callLocationEnterCallback(); menuManager.onLocationChanged(destName); }; - locations["uni_exterior"]->onTeleport = teleportCallback; - locations["uni_interior"]->onTeleport = teleportCallback; - locations["location_dorm"]->onTeleport = teleportCallback; + gameState.locations["uni_exterior"]->onTeleport = teleportCallback; + gameState.locations["uni_interior"]->onTeleport = teleportCallback; + gameState.locations["location_dorm"]->onTeleport = teleportCallback; // Share the global int store with all dialogue runtimes so flags set via // dialogue JSON and via Lua set_dialogue_flag all see the same state. - for (auto& [name, loc] : locations) { - loc->dialogueSystem.setGlobalFlagStore(&globalInts); + for (auto& [name, loc] : gameState.locations) { + loc->dialogueSystem.setGlobalFlagStore(&gameState.globalInts); } // Wire tutorial advance callbacks for all locations. // advanceTutorialStep() guards against double-advancing, so sharing is safe. - for (auto& [name, loc] : locations) { + for (auto& [name, loc] : gameState.locations) { loc->dialogueSystem.setOnDialogueAdvanced([this]() { menuManager.advanceTutorialStep(); }); loc->onPlayerFloorWalk = [this]() { - if (menuManager.tutorialStep == TutorialStep::Step2) { + if (gameState.tutorialStep == TutorialStep::Step2) { menuManager.advanceTutorialStep(); } menuManager.onPlayerStartedWalking(); @@ -509,19 +509,19 @@ namespace ZL // Wire inventory callbacks: tutorial tracking + toast notifications. - inventory.onItemAdded = [this](const std::string& itemId) { + gameState.inventory.onItemAdded = [this](const std::string& itemId) { menuManager.onItemPickedUp(itemId); const Item* item = ItemRegistry::instance().findById(itemId); if (item) menuManager.showToast("resources/w/ui/img/toast/item_received001.png", item->name); }; - inventory.onItemRemoved = [this](const std::string& itemId) { + gameState.inventory.onItemRemoved = [this](const std::string& itemId) { const Item* item = ItemRegistry::instance().findById(itemId); if (item) menuManager.showToast("resources/w/ui/img/toast/item_removed001.png", item->name); }; // Wire phone dialogue start function so MenuManager can trigger dialogues. menuManager.startDialogueFunc = [this](const std::string& id) { - if (currentLocation) currentLocation->dialogueSystem.startDialogue(id); + if (currentLocation()) currentLocation()->dialogueSystem.startDialogue(id); }; menuManager.startDarklandsTransitionFunc = [this]() { @@ -533,19 +533,19 @@ namespace ZL }; menuManager.chatOpenCallback = [this](int chatIndex) { - if (currentLocation) - currentLocation->scriptEngine.callChatOpenCallback(chatIndex); + if (currentLocation()) + currentLocation()->scriptEngine.callChatOpenCallback(chatIndex); }; // Wire chat-bubble callback so dynamic bubbles appear as dialogue lines are shown. - for (auto& [name, loc] : locations) { + for (auto& [name, loc] : gameState.locations) { loc->dialogueSystem.setOnChatBubbleReady([this](const std::string& text, bool incoming) { menuManager.onChatBubbleReady(text, incoming); }); } // Wire cutscene HUD: show skip button when cutscene starts, restore HUD when it ends. - for (auto& [name, loc] : locations) { + for (auto& [name, loc] : gameState.locations) { loc->dialogueSystem.setOnCutsceneStarted([this]() { menuManager.onCutsceneStarted(); }); @@ -554,13 +554,13 @@ namespace ZL }); } menuManager.skipCutsceneFunc = [this]() { - if (currentLocation) currentLocation->dialogueSystem.skipCutscene(); + if (currentLocation()) currentLocation()->dialogueSystem.skipCutscene(); }; std::cout << "Load resurces step 13" << std::endl; try { - menuManager.setup(inventory, CONST_ZIP_FILE); + menuManager.setup(gameState.inventory, CONST_ZIP_FILE); std::cout << "UI loaded successfully" << std::endl; } catch (const std::exception& e) { @@ -569,12 +569,12 @@ namespace ZL } menuManager.startGameFunc = [this]() { - currentLocation = locations["location_dorm"]; - currentLocation->scriptEngine.callLocationEnterCallback(); + gameState.currentLocationName = "location_dorm"; + currentLocation()->scriptEngine.callLocationEnterCallback(); }; // Wire HP-change callbacks so all player instances update the health bar HUD. - for (auto& [name, loc] : locations) { + for (auto& [name, loc] : gameState.locations) { if (loc->player) { loc->player->state.onHpChanged = [this](float hp, float maxHp) { menuManager.updateHealthBar(hp, maxHp); @@ -608,9 +608,9 @@ namespace ZL if (!menuManager.cutsceneHudActive_) menuManager.uiManager.draw(renderer); - if (currentLocation) + if (currentLocation()) { - currentLocation->dialogueSystem.draw(renderer); + currentLocation()->dialogueSystem.draw(renderer); } glEnable(GL_BLEND); menuManager.topUiManager.draw(renderer); @@ -635,30 +635,31 @@ namespace ZL } else { - if (currentLocation) + Location* loc = currentLocation(); + if (loc) { // Sync global flags so Location's draw functions see them. - currentLocation->state.isDarklands = isDarklands; - currentLocation->state.isNight = menuManager.isNight; - currentLocation->state.isDawn = menuManager.isDawn; + loc->state.isDarklands = gameState.isDarklands; + loc->state.isNight = gameState.isNight; + loc->state.isDawn = gameState.isDawn; - if (isDarklands) { - currentLocation->drawGameDarklands(); + if (gameState.isDarklands) { + loc->drawGameDarklands(); CheckGlError(__FILE__, __LINE__); } - else if (menuManager.isNight) { - currentLocation->drawGameNight(); + else if (gameState.isNight) { + loc->drawGameNight(); CheckGlError(__FILE__, __LINE__); } - else if (currentLocation->shadowMap) { + else if (loc->shadowMap) { CheckGlError(__FILE__, __LINE__); - currentLocation->drawShadowDepthPass(); + loc->drawShadowDepthPass(); CheckGlError(__FILE__, __LINE__); - currentLocation->drawGameWithShadows(); + loc->drawGameWithShadows(); CheckGlError(__FILE__, __LINE__); } else { - currentLocation->drawGame(); + loc->drawGame(); CheckGlError(__FILE__, __LINE__); } } @@ -838,9 +839,9 @@ namespace ZL if (!menuManager.isMainMenuOpen()) menuManager.update(static_cast(delta)); - if (currentLocation) + if (currentLocation()) { - currentLocation->update(delta); + currentLocation()->update(delta); } } @@ -928,8 +929,8 @@ namespace ZL else if (event.button.button == SDL_BUTTON_RIGHT && event.type == SDL_MOUSEBUTTONUP && editorMode == EditorMode::Navigation - && currentLocation) { - currentLocation->editor.handleRightClick(); + && currentLocation()) { + currentLocation()->editor.handleRightClick(); } } else if (event.type == SDL_MOUSEMOTION) { @@ -956,7 +957,7 @@ namespace ZL } std::cout << "Current zoom: " << Environment::zoom << std::endl; // Tutorial step3 → step4: any mouse-wheel scroll counts as "zoom gesture". - if (menuManager.tutorialStep == TutorialStep::Step3) { + if (gameState.tutorialStep == TutorialStep::Step3) { menuManager.advanceTutorialStep(); } } @@ -964,32 +965,27 @@ namespace ZL if (event.type == SDL_KEYDOWN && event.key.repeat == 0) { switch (event.key.keysym.sym) { case SDLK_8: - if (editorMode == EditorMode::InteractiveObjects && currentLocation) { - currentLocation->editor.selectInteractiveObject(8); + if (editorMode == EditorMode::InteractiveObjects && currentLocation()) { + currentLocation()->editor.selectInteractiveObject(8); } else { - menuManager.isDawn = !menuManager.isDawn; - if (menuManager.isDawn) menuManager.isNight = true; + gameState.isDawn = !gameState.isDawn; + if (gameState.isDawn) gameState.isNight = true; } break; case SDLK_9: - if (editorMode == EditorMode::InteractiveObjects && currentLocation) { - currentLocation->editor.selectInteractiveObject(9); + if (editorMode == EditorMode::InteractiveObjects && currentLocation()) { + currentLocation()->editor.selectInteractiveObject(9); } else { startNightTransition(); - /*if (menuManager.isDawn) { - menuManager.isDawn = false; // step back: dawn → plain night - } else { - menuManager.isNight = !menuManager.isNight; - }*/ } break; case SDLK_0: - - if (editorMode == EditorMode::InteractiveObjects && currentLocation) { - currentLocation->editor.selectInteractiveObject(event.key.keysym.sym - SDLK_0); + + if (editorMode == EditorMode::InteractiveObjects && currentLocation()) { + currentLocation()->editor.selectInteractiveObject(event.key.keysym.sym - SDLK_0); } else { - currentLocation->requestDarklandsTransition(); + currentLocation()->requestDarklandsTransition(); } break; case SDLK_1: @@ -999,21 +995,21 @@ namespace ZL case SDLK_5: case SDLK_6: case SDLK_7: - - if (editorMode == EditorMode::InteractiveObjects && currentLocation) { - currentLocation->editor.selectInteractiveObject(event.key.keysym.sym - SDLK_0); + + if (editorMode == EditorMode::InteractiveObjects && currentLocation()) { + currentLocation()->editor.selectInteractiveObject(event.key.keysym.sym - SDLK_0); } else { - currentLocation->switchNavigation(event.key.keysym.sym - SDLK_0); + currentLocation()->switchNavigation(event.key.keysym.sym - SDLK_0); std::cout << "Switched to nav mesh " << (event.key.keysym.sym - SDLK_0) << std::endl; } break; case SDLK_f: - currentLocation->dialogueSystem.startDialogue("phone_night_aiperi001"); + currentLocation()->dialogueSystem.startDialogue("phone_night_aiperi001"); break; case SDLK_e: - currentLocation->dialogueSystem.startCutscene("computer_cutscene001"); //.startDialogue("test_cutscene_pan_dialogue"); + currentLocation()->dialogueSystem.startCutscene("computer_cutscene001"); //.startDialogue("test_cutscene_pan_dialogue"); break; @@ -1024,12 +1020,12 @@ namespace ZL editorMode = EditorMode::InteractiveObjects; else editorMode = EditorMode::None; - if (currentLocation) { - currentLocation->editorMode = editorMode; + if (currentLocation()) { + currentLocation()->editorMode = editorMode; if (editorMode == EditorMode::Navigation) - currentLocation->editor.buildNavMeshes(); + currentLocation()->editor.buildNavMeshes(); else if (editorMode == EditorMode::InteractiveObjects) - currentLocation->editor.buildInteractiveObjectBoundsMeshes(); + currentLocation()->editor.buildInteractiveObjectBoundsMeshes(); } { const char* modeName = (editorMode == EditorMode::Navigation) ? "Navigation" @@ -1055,15 +1051,15 @@ namespace ZL //std::cout << "current y: " << y << std::endl; //y = y - 0.002; - std::cout << "Player pos: " << currentLocation->player->state.position.transpose() << std::endl; + std::cout << "Player pos: " << currentLocation()->player->state.position.transpose() << std::endl; //currentLocation->npcs[0]->walkSpeed -= 0.01f; //std::cout << "Walk speed: " << currentLocation->npcs[0]->walkSpeed << std::endl; break; case SDLK_p: - currentLocation = locations["uni_interior"]; - currentLocation->player->state.position = Eigen::Vector3f(-0.0189243, 0, -13.4314); - currentLocation->player->setTarget(currentLocation->player->state.position); + gameState.currentLocationName = "uni_interior"; + currentLocation()->player->state.position = Eigen::Vector3f(-0.0189243, 0, -13.4314); + currentLocation()->player->setTarget(currentLocation()->player->state.position); //std::cout << "Switched to location " << ((currentLocation == locations["location1"]) ? "1" : "2") << std::endl; break; @@ -1071,8 +1067,8 @@ namespace ZL //x = x - 1; //std::cout << "current x: " << x << std::endl; - std::cout << "Azimuth: " << currentLocation->state.cameraAzimuth << std::endl; - std::cout << "Inclination: " << currentLocation->state.cameraInclination << std::endl; + std::cout << "Azimuth: " << currentLocation()->state.cameraAzimuth << std::endl; + std::cout << "Inclination: " << currentLocation()->state.cameraInclination << std::endl; break; case SDLK_c: @@ -1086,22 +1082,22 @@ namespace ZL break; case SDLK_b: - if (editorMode != EditorMode::None && currentLocation) { - currentLocation->editor.saveAll(); + if (editorMode != EditorMode::None && currentLocation()) { + currentLocation()->editor.saveAll(); } break; case SDLK_j: - if (editorMode != EditorMode::None && currentLocation) { - currentLocation->editor.placeTree(); + if (editorMode != EditorMode::None && currentLocation()) { + currentLocation()->editor.placeTree(); } else { menuManager.toggleQuestJournal(); } break; case SDLK_v: - if (editorMode != EditorMode::None && currentLocation) { - currentLocation->editor.saveObjects(); + if (editorMode != EditorMode::None && currentLocation()) { + currentLocation()->editor.saveObjects(); } break; @@ -1152,13 +1148,13 @@ namespace ZL } void Game::activateSlowMoEffect() { - if (!currentLocation) return; + if (!currentLocation()) return; - if (currentLocation->player) { - currentLocation->player->state.slowMoTimeRemaining = currentLocation->player->state.slowMoActiveTime; + if (currentLocation()->player) { + currentLocation()->player->state.slowMoTimeRemaining = currentLocation()->player->state.slowMoActiveTime; } - for (auto& npc : currentLocation->npcs) { + for (auto& npc : currentLocation()->npcs) { if (npc) { npc->state.slowMoTimeRemaining = npc->state.slowMoActiveTime; } @@ -1168,25 +1164,25 @@ namespace ZL void Game::enterCameraDragMode(int eventX, int eventY) { cameraDragging = true; - if (currentLocation) { - currentLocation->cameraDragging = true; + if (currentLocation()) { + currentLocation()->cameraDragging = true; // Anchor on the *current* position, not the original press, so the // camera doesn't snap by however far the finger drifted before the // movement threshold was crossed. - currentLocation->lastMouseX = eventX; - currentLocation->lastMouseY = eventY; + currentLocation()->lastMouseX = eventX; + currentLocation()->lastMouseY = eventY; // Snapshot current angles so we can measure how far the user rotates. - dragStartAzimuth = currentLocation->state.cameraAzimuth; - dragStartInclination = currentLocation->state.cameraInclination; + dragStartAzimuth = currentLocation()->state.cameraAzimuth; + dragStartInclination = currentLocation()->state.cameraInclination; } } void Game::exitCameraDragMode() { cameraDragging = false; - if (currentLocation) { - currentLocation->cameraDragging = false; + if (currentLocation()) { + currentLocation()->cameraDragging = false; } } @@ -1237,7 +1233,7 @@ namespace ZL Environment::zoom = newZoom; // Tutorial step3 → step4: detect a significant pinch-zoom (≥ 2 zoom units). - if (menuManager.tutorialStep == TutorialStep::Step3) { + if (gameState.tutorialStep == TutorialStep::Step3) { if (std::abs(Environment::zoom - pinchStartZoom) >= 2.0f) { menuManager.advanceTutorialStep(); } @@ -1263,7 +1259,7 @@ namespace ZL const int uiY = Environment::projectionHeight - my; menuManager.topUiManager.onTouchDown(fingerId, uiX, uiY); const bool capturedByTopUi = menuManager.topUiManager.isUiInteractionForFinger(fingerId); - const bool dialogueActive = currentLocation && currentLocation->dialogueSystem.isActive(); + const bool dialogueActive = currentLocation() && currentLocation()->dialogueSystem.isActive(); if (!dialogueActive && !capturedByTopUi) { menuManager.uiManager.onTouchDown(fingerId, uiX, uiY); } @@ -1299,7 +1295,7 @@ namespace ZL const int uiX = mx; const int uiY = Environment::projectionHeight - my; menuManager.topUiManager.onTouchUp(fingerId, uiX, uiY); - const bool dialogueActive = currentLocation && currentLocation->dialogueSystem.isActive(); + const bool dialogueActive = currentLocation() && currentLocation()->dialogueSystem.isActive(); if (!dialogueActive) { menuManager.uiManager.onTouchUp(fingerId, uiX, uiY); } @@ -1334,8 +1330,8 @@ namespace ZL // Tap → walk-to / interact, using the original press coords so the target // isn't shifted by tiny finger drift before release. - if (currentLocation) { - currentLocation->handleDown(fingerId, st.downEventX, st.downEventY, st.downMx, st.downMy); + if (currentLocation()) { + currentLocation()->handleDown(fingerId, st.downEventX, st.downEventY, st.downMx, st.downMy); } } @@ -1344,7 +1340,7 @@ namespace ZL const int uiX = mx; const int uiY = Environment::projectionHeight - my; menuManager.topUiManager.onTouchMove(fingerId, uiX, uiY); - const bool dialogueActive = currentLocation && currentLocation->dialogueSystem.isActive(); + const bool dialogueActive = currentLocation() && currentLocation()->dialogueSystem.isActive(); if (!dialogueActive) { menuManager.uiManager.onTouchMove(fingerId, uiX, uiY); } @@ -1373,18 +1369,18 @@ namespace ZL } } - if (currentLocation) { + if (currentLocation()) { // Forwarded for dialogue hover and (when cameraDragging) camera rotation. - currentLocation->handleMotion(fingerId, eventX, eventY, mx, my); + currentLocation()->handleMotion(fingerId, eventX, eventY, mx, my); // Tutorial step1 → step2: detect a significant camera rotation on BOTH axes. // ~0.15 rad (≈8.6°) per axis ensures the user intentionally panned in 2D, // not just nudged a single axis by accident. static constexpr float TUTORIAL_ROTATION_THRESHOLD = 0.15f; if (cameraDragging - && menuManager.tutorialStep == TutorialStep::Step1) { - float deltaAz = std::abs(currentLocation->state.cameraAzimuth - dragStartAzimuth); - float deltaInc = std::abs(currentLocation->state.cameraInclination - dragStartInclination); + && gameState.tutorialStep == TutorialStep::Step1) { + float deltaAz = std::abs(currentLocation()->state.cameraAzimuth - dragStartAzimuth); + float deltaInc = std::abs(currentLocation()->state.cameraInclination - dragStartInclination); if (deltaAz >= TUTORIAL_ROTATION_THRESHOLD && deltaInc >= TUTORIAL_ROTATION_THRESHOLD) { menuManager.advanceTutorialStep(); } @@ -1394,15 +1390,15 @@ namespace ZL bool Game::startDarklandsTransition() { - if (!menuManager.isNight) + if (!gameState.isNight) { - currentLocation->dialogueSystem.startDialogue("darklands_day_dialog001"); + currentLocation()->dialogueSystem.startDialogue("darklands_day_dialog001"); return false; } - if (menuManager.isDawn) + if (gameState.isDawn) { - currentLocation->dialogueSystem.startDialogue("darklands_morning_dialog001"); + currentLocation()->dialogueSystem.startDialogue("darklands_morning_dialog001"); return false; } @@ -1434,25 +1430,24 @@ namespace ZL darklandsFlashAlpha = min(darklandsFlashAlpha + step, 1.0f); if (darklandsFlashAlpha >= 1.0f) { if (isNightTransition) { - menuManager.isNight = !menuManager.isNight; - if (currentLocation) + gameState.isNight = !gameState.isNight; + if (currentLocation()) { - //currentLocation->dialogueSystem.startDialogue("dialog_video001"); - currentLocation->state.isNight = menuManager.isNight; - currentLocation->scriptEngine.callTriggerNightEnterCallback(); + currentLocation()->state.isNight = gameState.isNight; + currentLocation()->scriptEngine.callTriggerNightEnterCallback(); } } else { - isDarklands = !isDarklands; + gameState.isDarklands = !gameState.isDarklands; // Change HUD - menuManager.setDarklandsMode(isDarklands); + menuManager.setDarklandsMode(gameState.isDarklands); - if (currentLocation) { - currentLocation->state.isDarklands = isDarklands; - if (isDarklands) - currentLocation->scriptEngine.callDarklandsEnterCallback(); + if (currentLocation()) { + currentLocation()->state.isDarklands = gameState.isDarklands; + if (gameState.isDarklands) + currentLocation()->scriptEngine.callDarklandsEnterCallback(); else - currentLocation->scriptEngine.callDarklandsExitCallback(); + currentLocation()->scriptEngine.callDarklandsExitCallback(); } } darklandsFlashFadingIn = false; diff --git a/src/Game.h b/src/Game.h index f478215..87c805e 100644 --- a/src/Game.h +++ b/src/Game.h @@ -20,6 +20,7 @@ #include #include "Location.h" #include "AudioPlayerAsync.h" +#include "GameState.h" namespace ZL { class Game { @@ -42,23 +43,14 @@ namespace ZL { VertexRenderStruct loadingMesh; bool loadingCompleted = false; - std::unordered_map> locations; - std::shared_ptr currentLocation; + GameState gameState; EditorMode editorMode = EditorMode::None; - // Global darklands state — persists across location transitions. - bool isDarklands = false; // Returns false if a transition is already in progress. bool startDarklandsTransition(); bool startNightTransition(); - Inventory inventory; - InteractiveObject* pickedUpObject = nullptr; - - std::unordered_map globalInts; - std::unordered_map globalFloats; - MenuManager menuManager; void activateSlowMoEffect(); @@ -95,6 +87,8 @@ namespace ZL { std::unique_ptr audioPlayer; + Location* currentLocation() const; + int64_t getSyncTimeMs(); void processTickCount(); void drawScene(); diff --git a/src/GameState.cpp b/src/GameState.cpp new file mode 100644 index 0000000..caf3e7a --- /dev/null +++ b/src/GameState.cpp @@ -0,0 +1 @@ +#include "GameState.h" diff --git a/src/GameState.h b/src/GameState.h new file mode 100644 index 0000000..c8d7407 --- /dev/null +++ b/src/GameState.h @@ -0,0 +1,71 @@ +#pragma once +#include "items/Item.h" +#include "quest/QuestJournal.h" +#include "Location.h" +#include +#include +#include +#include + +namespace ZL { + +enum class TutorialStep { + Step0, // Dialogue hint: "click to advance" + Step1, // Camera rotation hint + Step2, // Floor tap / walk hint + Step3, // Pinch-zoom hint + Step4, // Pick-up item hint + Step5, // Post-pickup reaction + Step6, // Tutorial complete +}; + +enum class UniIntTutorialState { + Step10, + Step11, + DarklandsActive, + DarklandsStep13, + DarklandsFull +}; + +struct StoredChatMessage { + std::string text; + bool incoming; +}; + +struct GameState { + // --- World --- + std::unordered_map> locations; + std::string currentLocationName; + bool isDarklands = false; + bool isNight = false; + bool isDawn = false; + + // --- Player resources --- + Inventory inventory; + float playerHp = 200.f; + float playerMaxHp = 200.f; + int money = 5500; + + // --- Script globals --- + std::unordered_map globalInts; + std::unordered_map globalFloats; + + // --- Quest system --- + Quest::QuestJournal questJournal; + + // --- Tutorial --- + TutorialStep tutorialStep = TutorialStep::Step0; + UniIntTutorialState uniIntTutorialState = UniIntTutorialState::Step10; + bool tutorialPhonePickedUp = false; + bool tutorialJournalPickedUp = false; + bool tutorialPhoneChatScreenOpened = false; + bool tutorialJournalScreenOpened = false; + bool tutorialNeedOpenTaxiScreen = false; + + // --- Phone / chat --- + bool chatUnread[3] = { true, true, true }; + std::string chatPreviewMsg[3]; + std::vector chatHistory[3]; +}; + +} // namespace ZL diff --git a/src/Location.cpp b/src/Location.cpp index e89fa36..6e7d9e4 100644 --- a/src/Location.cpp +++ b/src/Location.cpp @@ -1869,6 +1869,11 @@ namespace ZL } out["interactiveObjectStates"] = std::move(ioArr); + // Dialogue runtime state + nlohmann::json dialogueState; + dialogueSystem.saveDialogueState(dialogueState); + out["dialogueState"] = std::move(dialogueState); + // Lua global variable state nlohmann::json scriptState; scriptEngine.saveScriptGlobals(scriptState); @@ -1919,6 +1924,11 @@ namespace ZL } } + // Dialogue runtime state + if (in.contains("dialogueState")) { + dialogueSystem.loadDialogueState(in["dialogueState"]); + } + // Lua globals if (in.contains("scriptGlobals")) { scriptEngine.loadScriptGlobals(in["scriptGlobals"]); diff --git a/src/MenuManager.cpp b/src/MenuManager.cpp index c76bd65..c3fd479 100644 --- a/src/MenuManager.cpp +++ b/src/MenuManager.cpp @@ -91,9 +91,9 @@ namespace ZL { } } - MenuManager::MenuManager(Renderer& iRenderer, std::unordered_map& globalInts) : + MenuManager::MenuManager(Renderer& iRenderer, GameState& gameState) : renderer(iRenderer), - globalInts(globalInts) + gameState_(gameState) { } @@ -118,7 +118,7 @@ namespace ZL { hudUniIntFullRoot = loadUiFromFile("resources/w/ui/hud_uni_int_full.json", renderer, zipFile); hudUniIntDarkFullRoot = loadUiFromFile("resources/w/ui/hud_uni_int_dark_full.json", renderer, zipFile); hudUniExtDarkRoot = loadUiFromFile("resources/w/ui/hud_uni_ext_dark.json", renderer, zipFile); - + hudCutsceneRoot_ = loadUiFromFile("resources/w/ui/hud_cutscene.json", renderer, zipFile); hudTopHintRoot_ = loadUiFromFile("resources/w/ui/hud_top_hint01.json", renderer, zipFile); @@ -147,30 +147,25 @@ namespace ZL { 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); - questJournal.loadFromFile("resources/quests/quests.json", zipFile); + gameState_.questJournal.loadFromFile("resources/quests/quests.json", zipFile); renderer.textureManager.LoadFromPng("resources/w/ui/img/toast/quest_new001.png", zipFile, true); renderer.textureManager.LoadFromPng("resources/w/ui/img/toast/quest_completed001.png", zipFile, true); renderer.textureManager.LoadFromPng("resources/w/ui/img/toast/quest_failed001.png", zipFile, true); - questJournal.onQuestUnlocked = [this](const std::string& id) { - const Quest::QuestState* q = questJournal.findQuest(id); + gameState_.questJournal.onQuestUnlocked = [this](const std::string& id) { + const Quest::QuestState* q = gameState_.questJournal.findQuest(id); if (q) showToast("resources/w/ui/img/toast/quest_new001.png", q->definition.title); }; - questJournal.onQuestCompleted = [this](const std::string& id) { - const Quest::QuestState* q = questJournal.findQuest(id); + gameState_.questJournal.onQuestCompleted = [this](const std::string& id) { + const Quest::QuestState* q = gameState_.questJournal.findQuest(id); if (q) showToast("resources/w/ui/img/toast/quest_completed001.png", q->definition.title); }; - questJournal.onQuestFailed = [this](const std::string& id) { - const Quest::QuestState* q = questJournal.findQuest(id); + gameState_.questJournal.onQuestFailed = [this](const std::string& id) { + const Quest::QuestState* q = gameState_.questJournal.findQuest(id); if (q) showToast("resources/w/ui/img/toast/quest_failed001.png", q->definition.title); }; - /* - questJournal.onObjectiveCompleted = [this](const std::string& id, const std::string&) { - const Quest::QuestState* q = questJournal.findQuest(id); - if (q) showToast("resources/w/ui/img/toast/objective_completed001.png", q->definition.title); - };*/ const std::string imgDir = "resources/w/ui/img/phone/"; texBubbleInCenter_ = renderer.textureManager.LoadFromPng(imgDir + "bubble_in_center.png", zipFile, true); @@ -188,30 +183,20 @@ namespace ZL { } void MenuManager::enterGameplay() { - if (state == GameState::MainMenu && startGameFunc) startGameFunc(); - state = GameState::Gameplay; + if (uiState_ == GameUiState::MainMenu && startGameFunc) startGameFunc(); + uiState_ = GameUiState::Gameplay; uiManager.replaceRoot(hudRoot); topUiManager.replaceRoot(hudTopHintRoot_); - applyCurrentHealthBar(); - /* - uiManager.setTextButtonCallback("inventory_button", [this](const std::string&) { - openInventory(); - }); - - uiManager.setTextButtonCallback("quest_journal_button", [this](const std::string&) { - openQuestJournal(); - });*/ uiManager.setButtonCallback("inventoryButton", [this](const std::string&) { openInventory(); }); - //openInventory() } void MenuManager::showMainMenu() { - state = GameState::MainMenu; + uiState_ = GameUiState::MainMenu; uiManager.replaceRoot(mainMenuRoot); uiManager.setTextButtonCallback("menuStartButton", [this](const std::string&) { @@ -239,7 +224,7 @@ namespace ZL { } void MenuManager::openInventory() { - state = GameState::Inventory; + uiState_ = GameUiState::Inventory; uiManager.pushMenuFromSavedRoot(newInventoryRoot); uiManager.setButtonCallback("inventoryExitButton", [this](const std::string&) { closeInventory(); @@ -284,54 +269,54 @@ namespace ZL { } } - void MenuManager::selectInventoryItem(int index) { - const auto& items = inventory->getItems(); - if (index < 0 || index >= static_cast(items.size())) return; + void MenuManager::selectInventoryItem(int index) { + const auto& items = inventory->getItems(); + if (index < 0 || index >= static_cast(items.size())) return; - // Revert previously selected button to its regular icon - if (inventorySelectedIndex_ >= 0 && inventorySelectedIndex_ < static_cast(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); + // Revert previously selected button to its regular icon + if (inventorySelectedIndex_ >= 0 && inventorySelectedIndex_ < static_cast(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() { - state = GameState::Gameplay; + uiState_ = GameUiState::Gameplay; uiManager.popMenu(); uiManager.updateAllLayouts(); } void MenuManager::openQuestJournal() { - state = GameState::QuestJournal; - tutorialJournalScreenOpened = true; - + uiState_ = GameUiState::QuestJournal; + gameState_.tutorialJournalScreenOpened = true; + uiManager.setNodeVisible("hint6b", false); uiManager.setNodeVisible("hint6barrow", false); uiManager.pushMenuFromSavedRoot(questJournalRoot); @@ -358,7 +343,7 @@ namespace ZL { } void MenuManager::closeQuestJournal() { - state = GameState::Gameplay; + uiState_ = GameUiState::Gameplay; selectedQuestIndex = -1; visibleQuestIds.clear(); uiManager.popMenu(); @@ -367,11 +352,11 @@ namespace ZL { void MenuManager::toggleQuestJournal() { std::cout << "[quest] toggleQuestJournal: " << (isQuestJournalOpen() ? "closing" : "opening") << std::endl; - if (state == GameState::QuestJournal) { + if (uiState_ == GameUiState::QuestJournal) { closeQuestJournal(); } else { - if (state == GameState::Inventory) { + if (uiState_ == GameUiState::Inventory) { closeInventory(); } openQuestJournal(); @@ -379,23 +364,17 @@ namespace ZL { } void MenuManager::openPhoneScreen() { - state = GameState::PhoneScreen; + uiState_ = GameUiState::PhoneScreen; - //uiManager.setNodeVisible("hint6a", false); - //uiManager.setNodeVisible("hint6aarrow", false); - - //tutorialPhoneScreenOpened = true; - - - if (tutorialNeedOpenTaxiScreen && (tutorialPhoneChatScreenOpened == false)) + if (gameState_.tutorialNeedOpenTaxiScreen && (gameState_.tutorialPhoneChatScreenOpened == false)) { uiManager.pushMenuFromSavedRoot(phoneMainHintABRoot); } - else if (tutorialNeedOpenTaxiScreen) + else if (gameState_.tutorialNeedOpenTaxiScreen) { uiManager.pushMenuFromSavedRoot(phoneMainHintBRoot); } - else if (tutorialPhoneChatScreenOpened == false) + else if (gameState_.tutorialPhoneChatScreenOpened == false) { uiManager.pushMenuFromSavedRoot(phoneMainHintARoot); } @@ -403,7 +382,7 @@ namespace ZL { { uiManager.pushMenuFromSavedRoot(phoneMainRoot); } - + uiManager.setButtonCallback("phoneExitButton", [this](const std::string&) { closePhoneEntirely(); }); @@ -420,9 +399,9 @@ namespace ZL { openPhoneTaxi(); }); - if (isNight) + if (gameState_.isNight) { - if (isDawn) + if (gameState_.isDawn) { uiManager.setNodeVisible("phoneTimeDay", false); uiManager.setNodeVisible("phoneTimeNight", false); @@ -445,7 +424,7 @@ namespace ZL { void MenuManager::openPhoneMessenger() { - if (tutorialPhoneChatScreenOpened) + if (gameState_.tutorialPhoneChatScreenOpened) { uiManager.pushMenuFromSavedRoot(phoneChatListRoot); } @@ -453,7 +432,6 @@ namespace ZL { { uiManager.pushMenuFromSavedRoot(phoneChatListHintRoot); } - //uiManager.pushMenuFromSavedRoot(phoneChatListRoot); refreshChatUnreadIndicators(); uiManager.setButtonCallback("phoneExitButton", [this](const std::string&) { @@ -461,15 +439,15 @@ namespace ZL { }); uiManager.setButtonCallback("phoneMain", [this](const std::string&) {}); uiManager.setTextButtonCallback("chat1button", [this](const std::string&) { - chatUnread_[0] = false; + gameState_.chatUnread[0] = false; openPhoneChatFromList(0, phoneChat1Root); }); uiManager.setTextButtonCallback("chat2button", [this](const std::string&) { - chatUnread_[1] = false; + gameState_.chatUnread[1] = false; openPhoneChatFromList(1, phoneChat2Root); }); uiManager.setTextButtonCallback("chat3button", [this](const std::string&) { - chatUnread_[2] = false; + gameState_.chatUnread[2] = false; openPhoneChatFromList(2, phoneChat3Root); }); } @@ -478,30 +456,30 @@ namespace ZL { 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], chatUnread_[i]); - if (!chatPreviewMsg_[i].empty()) - uiManager.setText(kMsgNodes[i], chatPreviewMsg_[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; - chatUnread_[chatIndex] = unread; + gameState_.chatUnread[chatIndex] = unread; if (!previewMsg.empty()) { - chatPreviewMsg_[chatIndex] = trimChatPreview(previewMsg); - } else if (!unread && !chatHistory_[chatIndex].empty()) { - chatPreviewMsg_[chatIndex] = trimChatPreview(chatHistory_[chatIndex].back().text); + 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) { - money_ -= amount; + gameState_.money -= amount; } void MenuManager::openPhoneBank() { uiManager.pushMenuFromSavedRoot(phoneBankRoot); - uiManager.setText("balanceText", formatMoney(money_)); + uiManager.setText("balanceText", formatMoney(gameState_.money)); uiManager.setButtonCallback("phoneExitButton", [this](const std::string&) { closePhoneEntirely(); @@ -525,13 +503,13 @@ namespace ZL { uiManager.setButtonCallback("videoSkip", [this](const std::string&) { closePhoneEntirely(); - if (isNight) + if (gameState_.isNight) { startDialogueFunc("dialog_video002"); } else { - auto day = globalInts["day"]; + auto day = gameState_.globalInts["day"]; if (day == 0) { startDialogueFunc("dialog_video003"); @@ -545,10 +523,10 @@ namespace ZL { } void MenuManager::openPhoneTaxi() { - if (currentLocationName_ == "uni_interior") { + if (gameState_.currentLocationName == "uni_interior") { closePhoneEntirely(); if (startDialogueFunc) startDialogueFunc("dialog_taxi003"); - } else if (currentLocationName_ == "uni_exterior") { + } else if (gameState_.currentLocationName == "uni_exterior") { openPhoneMapScreen(phoneMapUniRoot); } else { openPhoneMapScreen(phoneMapDormRoot); @@ -566,13 +544,13 @@ namespace ZL { uiManager.updateAllLayouts(); }); uiManager.setButtonCallback("mapGo", [this](const std::string&) { - tutorialNeedOpenTaxiScreen = false; - + gameState_.tutorialNeedOpenTaxiScreen = false; + if (callTaxiFunc) { callTaxiFunc(); } - money_ -= 500; + gameState_.money -= 500; closePhoneEntirely(); if (startDialogueFunc) startDialogueFunc("dialog_taxi002"); }); @@ -581,7 +559,7 @@ namespace ZL { void MenuManager::openPhoneChatFromList(int chatIndex, std::shared_ptr chatRoot) { activeChatIndex_ = chatIndex; phoneChatVisibleBubbles_.clear(); - tutorialPhoneChatScreenOpened = true; + gameState_.tutorialPhoneChatScreenOpened = true; uiManager.pushMenuFromSavedRoot(chatRoot); rebuildChatBubblesFromHistory(chatIndex); @@ -601,15 +579,15 @@ namespace ZL { void MenuManager::returnToPhoneChatList() { if (activeChatIndex_ >= 0 && activeChatIndex_ <= 2 - && !chatHistory_[activeChatIndex_].empty()) { - chatPreviewMsg_[activeChatIndex_] = - trimChatPreview(chatHistory_[activeChatIndex_].back().text); + && !gameState_.chatHistory[activeChatIndex_].empty()) { + gameState_.chatPreviewMsg[activeChatIndex_] = + trimChatPreview(gameState_.chatHistory[activeChatIndex_].back().text); } activeChatIndex_ = -1; phoneChatVisibleBubbles_.clear(); uiManager.popMenu(); uiManager.updateAllLayouts(); - if (tutorialPhoneChatScreenOpened) + if (gameState_.tutorialPhoneChatScreenOpened) { uiManager.setNodeVisible("hint_m002", false); } @@ -623,18 +601,18 @@ namespace ZL { void MenuManager::closePhoneEntirely() { if (activeChatIndex_ >= 0 && activeChatIndex_ <= 2 - && !chatHistory_[activeChatIndex_].empty()) { - chatPreviewMsg_[activeChatIndex_] = - trimChatPreview(chatHistory_[activeChatIndex_].back().text); + && !gameState_.chatHistory[activeChatIndex_].empty()) { + gameState_.chatPreviewMsg[activeChatIndex_] = + trimChatPreview(gameState_.chatHistory[activeChatIndex_].back().text); } activeChatIndex_ = -1; - state = GameState::Gameplay; + uiState_ = GameUiState::Gameplay; phoneChatVisibleBubbles_.clear(); const int depth = uiManager.menuStackSize(); for (int i = 0; i < depth; ++i) uiManager.popMenu(); uiManager.updateAllLayouts(); - if (tutorialNeedOpenTaxiScreen) + if (gameState_.tutorialNeedOpenTaxiScreen) { uiManager.setNodeVisible("hint7", true); uiManager.setNodeVisible("hint7arrow", true); @@ -647,7 +625,7 @@ namespace ZL { uiManager.setNodeVisible("hint7", false); uiManager.setNodeVisible("hint7arrow", false); - if (tutorialPhoneChatScreenOpened) + if (gameState_.tutorialPhoneChatScreenOpened) { uiManager.setNodeVisible("hint6a", false); uiManager.setNodeVisible("hint6aarrow", false); @@ -658,10 +636,6 @@ namespace ZL { uiManager.setNodeVisible("hint6aarrow", true); } } - - - - } void MenuManager::closePhoneScreenFromChat() { @@ -675,15 +649,14 @@ namespace ZL { void MenuManager::tutorialShowTaxiHint() { std::cout << "tutorialShowTaxiHint" << std::endl; - tutorialNeedOpenTaxiScreen = true; - if (state == GameState::Gameplay) + gameState_.tutorialNeedOpenTaxiScreen = true; + if (uiState_ == GameUiState::Gameplay) { uiManager.setNodeVisible("hint6a", false); uiManager.setNodeVisible("hint6aarrow", false); uiManager.setNodeVisible("hint7", true); uiManager.setNodeVisible("hint7arrow", true); - } } @@ -706,7 +679,7 @@ namespace ZL { openQuestJournal(); }); } - if (tutorialNeedOpenTaxiScreen) + if (gameState_.tutorialNeedOpenTaxiScreen) { uiManager.setNodeVisible("hint6a", false); uiManager.setNodeVisible("hint6aarrow", false); @@ -720,17 +693,17 @@ namespace ZL { uiManager.setNodeVisible("hint7arrow", false); } - if (tutorialPhoneChatScreenOpened) { + if (gameState_.tutorialPhoneChatScreenOpened) { uiManager.setNodeVisible("hint6a", false); uiManager.setNodeVisible("hint6aarrow", false); } - if (tutorialJournalScreenOpened) + if (gameState_.tutorialJournalScreenOpened) { uiManager.setNodeVisible("hint6b", false); uiManager.setNodeVisible("hint6barrow", false); } - if (tutorialPhoneChatScreenOpened && tutorialJournalScreenOpened) { - tutorialStep = TutorialStep::Step6; + if (gameState_.tutorialPhoneChatScreenOpened && gameState_.tutorialJournalScreenOpened) { + gameState_.tutorialStep = TutorialStep::Step6; } } @@ -748,11 +721,11 @@ namespace ZL { } void MenuManager::onLocationChanged(const std::string& locationName) { - if (state != GameState::Gameplay) return; - currentLocationName_ = locationName; + if (uiState_ != GameUiState::Gameplay) return; + gameState_.currentLocationName = locationName; if (locationName == "uni_exterior") { - uiManager.replaceRoot(currentIsDarklands_ ? hudUniExtDarkRoot : hudUniExtRoot); + uiManager.replaceRoot(gameState_.isDarklands ? hudUniExtDarkRoot : hudUniExtRoot); applyCurrentHealthBar(); setupGameplayHudCallbacks(); } else if (locationName == "uni_interior") { @@ -768,22 +741,22 @@ namespace ZL { void MenuManager::advanceTutorialStep() { std::shared_ptr nextRoot; - switch (tutorialStep) { + switch (gameState_.tutorialStep) { case TutorialStep::Step0: - tutorialStep = TutorialStep::Step1; + gameState_.tutorialStep = TutorialStep::Step1; nextRoot = hudStep1Root; topUiManager.replaceRoot(nullptr); break; case TutorialStep::Step1: - tutorialStep = TutorialStep::Step2; + gameState_.tutorialStep = TutorialStep::Step2; nextRoot = hudStep2Root; break; case TutorialStep::Step2: - tutorialStep = TutorialStep::Step3; + gameState_.tutorialStep = TutorialStep::Step3; nextRoot = hudStep3Root; break; case TutorialStep::Step3: - tutorialStep = TutorialStep::Step4; + gameState_.tutorialStep = TutorialStep::Step4; nextRoot = hudStep4Root; if (tutorialUnlockInteractiveObjectsFunc) { @@ -795,7 +768,7 @@ namespace ZL { return; // Step4/Step5 transitions are driven by onItemPickedUp } - if (state == GameState::Gameplay && nextRoot) { + if (uiState_ == GameUiState::Gameplay && nextRoot) { uiManager.replaceRoot(nextRoot); applyCurrentHealthBar(); @@ -806,41 +779,41 @@ namespace ZL { } void MenuManager::onItemPickedUp(const std::string& itemId) { - if (itemId == "note_spell" && uniIntTutorialState_ == UniIntTutorialState::Step10) { - uniIntTutorialState_ = UniIntTutorialState::Step11; - if (currentLocationName_ == "uni_interior" && state == GameState::Gameplay) + if (itemId == "note_spell" && gameState_.uniIntTutorialState == UniIntTutorialState::Step10) { + gameState_.uniIntTutorialState = UniIntTutorialState::Step11; + if (gameState_.currentLocationName == "uni_interior" && uiState_ == GameUiState::Gameplay) applyUniIntHud(); } - if (tutorialStep != TutorialStep::Step4 && tutorialStep != TutorialStep::Step5) { + 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 (!currentLocationName_.empty() && currentLocationName_ != "location_dorm") { + // currentLocationName is empty only before the first teleport (still in dorm). + if (!gameState_.currentLocationName.empty() && gameState_.currentLocationName != "location_dorm") { return; } - if (itemId == "phone") tutorialPhonePickedUp = true; - if (itemId == "journal") tutorialJournalPickedUp = true; + if (itemId == "phone") gameState_.tutorialPhonePickedUp = true; + if (itemId == "journal") gameState_.tutorialJournalPickedUp = true; - if (tutorialStep == TutorialStep::Step4) { - tutorialStep = TutorialStep::Step5; + if (gameState_.tutorialStep == TutorialStep::Step4) { + gameState_.tutorialStep = TutorialStep::Step5; } refreshItemPickupHud(); } void MenuManager::refreshItemPickupHud() { - if (state != GameState::Gameplay) return; + if (uiState_ != GameUiState::Gameplay) return; std::shared_ptr nextRoot; - if (tutorialPhonePickedUp && tutorialJournalPickedUp) { + if (gameState_.tutorialPhonePickedUp && gameState_.tutorialJournalPickedUp) { nextRoot = hudStep5abRoot; - } else if (tutorialPhonePickedUp) { + } else if (gameState_.tutorialPhonePickedUp) { nextRoot = hudStep5aRoot; - } else if (tutorialJournalPickedUp) { + } else if (gameState_.tutorialJournalPickedUp) { nextRoot = hudStep5bRoot; } @@ -854,7 +827,7 @@ namespace ZL { void MenuManager::refreshQuestJournalUi() { visibleQuestIds.clear(); - auto quests = questJournal.getVisibleQuests(); + 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); @@ -920,7 +893,7 @@ namespace ZL { } selectedQuestIndex = index; - Quest::QuestState* quest = questJournal.findQuest(visibleQuestIds[index]); + Quest::QuestState* quest = gameState_.questJournal.findQuest(visibleQuestIds[index]); if (!quest) { return; } @@ -1007,7 +980,7 @@ namespace ZL { phoneChatVisibleBubbles_.clear(); if (chatIndex < 0 || chatIndex > 2) return; - for (const auto& msg : chatHistory_[chatIndex]) { + for (const auto& msg : gameState_.chatHistory[chatIndex]) { const bool inc = msg.incoming; const std::string nodeName = uiManager.addChatBubble( "chatMessagesContainer", msg.text, inc, @@ -1028,13 +1001,13 @@ namespace ZL { void MenuManager::onChatBubbleReady(const std::string& text, bool incoming) { if (activeChatIndex_ < 0) return; - auto& history = chatHistory_[activeChatIndex_]; + auto& history = gameState_.chatHistory[activeChatIndex_]; if (static_cast(history.size()) >= 5) { history.erase(history.begin()); } history.push_back({ text, incoming }); - if (state != GameState::PhoneScreen) return; + if (uiState_ != GameUiState::PhoneScreen) return; const std::string nodeName = uiManager.addChatBubble( "chatMessagesContainer", text, incoming, @@ -1060,15 +1033,14 @@ namespace ZL { void MenuManager::setDarklandsMode(bool enabled) { std::cout << "MenuManager::setDarklandsMode called" << std::endl; - currentIsDarklands_ = enabled; - if (currentLocationName_ == "uni_interior") { - if (enabled && uniIntTutorialState_ == UniIntTutorialState::Step11) { - uniIntTutorialState_ = UniIntTutorialState::DarklandsActive; + if (gameState_.currentLocationName == "uni_interior") { + if (enabled && gameState_.uniIntTutorialState == UniIntTutorialState::Step11) { + gameState_.uniIntTutorialState = UniIntTutorialState::DarklandsActive; } applyUniIntHud(); - } else if (currentLocationName_ == "uni_exterior") { - if (state == GameState::Gameplay) { + } else if (gameState_.currentLocationName == "uni_exterior") { + if (uiState_ == GameUiState::Gameplay) { uiManager.replaceRoot(enabled ? hudUniExtDarkRoot : hudUniExtRoot); applyCurrentHealthBar(); setupGameplayHudCallbacks(); @@ -1081,31 +1053,31 @@ namespace ZL { void MenuManager::applyUniIntHud() { - if (state != GameState::Gameplay) return; + if (uiState_ != GameUiState::Gameplay) return; std::shared_ptr root; - if (currentIsDarklands_) { - switch (uniIntTutorialState_) { + if (gameState_.isDarklands) { + switch (gameState_.uniIntTutorialState) { case UniIntTutorialState::DarklandsStep13: root = hudUniIntStep13Root; break; case UniIntTutorialState::DarklandsFull: root = hudUniIntDarkFullRoot; break; default: root = hudUniIntStep12Root; break; } } else { - switch (uniIntTutorialState_) { - case UniIntTutorialState::Step10: root = hudUniIntStep10Root; break; - case UniIntTutorialState::Step11: root = hudUniIntStep11Root; break; - default: root = hudUniIntFullRoot; break; + switch (gameState_.uniIntTutorialState) { + case UniIntTutorialState::Step10: root = hudUniIntStep10Root; break; + case UniIntTutorialState::Step11: root = hudUniIntStep11Root; break; + default: root = hudUniIntFullRoot; break; } } uiManager.replaceRoot(root); applyCurrentHealthBar(); - setupGameplayHudCallbacks(); // already calls hideAllToastWidgets + applyToastsToUi + setupGameplayHudCallbacks(); } void MenuManager::onPlayerStartedWalking() { - if (currentLocationName_ == "uni_interior" - && currentIsDarklands_ - && uniIntTutorialState_ == UniIntTutorialState::DarklandsActive) { + if (gameState_.currentLocationName == "uni_interior" + && gameState_.isDarklands + && gameState_.uniIntTutorialState == UniIntTutorialState::DarklandsActive) { uiManager.setNodeVisible("hint_darklands003", false); uiManager.setNodeVisible("hint_darklands003_arrow", false); } @@ -1113,24 +1085,24 @@ namespace ZL { void MenuManager::advanceUniIntDarklandsHud() { - if (uniIntTutorialState_ != UniIntTutorialState::DarklandsActive) return; - uniIntTutorialState_ = UniIntTutorialState::DarklandsStep13; - if (currentLocationName_ == "uni_interior" && currentIsDarklands_ && state == GameState::Gameplay) + 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 (uniIntTutorialState_ != UniIntTutorialState::DarklandsActive - && uniIntTutorialState_ != UniIntTutorialState::DarklandsStep13) return; - uniIntTutorialState_ = UniIntTutorialState::DarklandsFull; - if (currentLocationName_ == "uni_interior" && currentIsDarklands_ && state == GameState::Gameplay) + 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) { - currentPlayerHp_ = hp; - currentPlayerMaxHp_ = maxHp; + gameState_.playerHp = hp; + gameState_.playerMaxHp = maxHp; applyCurrentHealthBar(); } @@ -1145,22 +1117,22 @@ namespace ZL { void MenuManager::onCutsceneFinished() { cutsceneHudActive_ = false; topUiManager.replaceRoot(nullptr); - if (state == GameState::Gameplay) - onLocationChanged(currentLocationName_); + if (uiState_ == GameUiState::Gameplay) + onLocationChanged(gameState_.currentLocationName); } void MenuManager::applyCurrentHealthBar() { std::cout << "MenuManager::applyCurrentHealthBar called step 1" << std::endl; - std::cout << "currentPlayerMaxHp_ is " << currentPlayerMaxHp_ << std::endl; - if (currentPlayerMaxHp_ <= 0.f) return; + std::cout << "currentPlayerMaxHp_ is " << gameState_.playerMaxHp << std::endl; + if (gameState_.playerMaxHp <= 0.f) return; std::cout << "MenuManager::applyCurrentHealthBar called step 2" << std::endl; - std::cout << "currentPlayerHp_ is " << currentPlayerHp_ << std::endl; + std::cout << "currentPlayerHp_ is " << gameState_.playerHp << std::endl; - const float fraction = std::clamp(currentPlayerHp_ / currentPlayerMaxHp_, 0.f, 1.f); + 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(currentPlayerHp_)) + "/" + - std::to_string(static_cast(currentPlayerMaxHp_)); + std::string hpText = std::to_string(static_cast(gameState_.playerHp)) + "/" + + std::to_string(static_cast(gameState_.playerMaxHp)); if (hpText.size() < 7) hpText.insert(0, 7 - hpText.size(), ' '); uiManager.setText("healthValue", hpText); } diff --git a/src/MenuManager.h b/src/MenuManager.h index 8ebc12c..58a6794 100644 --- a/src/MenuManager.h +++ b/src/MenuManager.h @@ -4,7 +4,7 @@ #include "render/TextureManager.h" #include "UiManager.h" #include "items/Item.h" -#include "quest/QuestJournal.h" +#include "GameState.h" #include #include #include @@ -14,7 +14,7 @@ namespace ZL { extern const char* CONST_ZIP_FILE; - enum class GameState { + enum class GameUiState { MainMenu, About, Gameplay, @@ -23,29 +23,12 @@ namespace ZL { PhoneScreen }; - enum class TutorialStep { - Step0, // Dialogue hint: "click to advance" - Step1, // Camera rotation hint - Step2, // Floor tap / walk hint - Step3, // Pinch-zoom hint - Step4, // Pick-up item hint - Step5, // Post-pickup reaction (sub-state: which items were collected) - Step6, // Tutorial complete — both phone and journal opened - }; - class MenuManager { public: UiManager uiManager; UiManager topUiManager; - ZL::Quest::QuestJournal questJournal; - std::unordered_map& globalInts; - // Global night mode state — persists across location transitions. - bool isNight = false; - bool isDawn = false; // sub-variant of night: brighter pink ambient, same lighting - - - MenuManager(Renderer& iRenderer, std::unordered_map& globalInts); + MenuManager(Renderer& iRenderer, GameState& gameState); void setup(Inventory& inv, const std::string& zipFile); @@ -57,15 +40,15 @@ namespace ZL { void closeQuestJournal(); void toggleQuestJournal(); - bool isInventoryOpen() const { return state == GameState::Inventory; } - bool isQuestJournalOpen() const { return state == GameState::QuestJournal; } + bool isInventoryOpen() const { return uiState_ == GameUiState::Inventory; } + bool isQuestJournalOpen() const { return uiState_ == GameUiState::QuestJournal; } void showMainMenu(); - bool isMainMenuOpen() const { return state == GameState::MainMenu; } + bool isMainMenuOpen() const { return uiState_ == GameUiState::MainMenu; } void openPhoneScreen(); void closePhoneScreen(); - bool isPhoneScreenOpen() const { return state == GameState::PhoneScreen; } + bool isPhoneScreenOpen() const { return uiState_ == GameUiState::PhoneScreen; } void closePhoneEntirely(); void tutorialShowTaxiHint(); @@ -73,7 +56,7 @@ namespace ZL { void setChatUnread(int chatIndex, bool unread, const std::string& previewMsg = ""); void spendMoney(int amount); - int getMoney() const { return money_; } + int getMoney() const { return gameState_.money; } std::function startGameFunc; std::function startDialogueFunc; @@ -106,12 +89,12 @@ namespace ZL { void showToast(const std::string& iconPath, const std::string& text); void update(float deltaMs); - TutorialStep tutorialStep = TutorialStep::Step0; - protected: Renderer& renderer; private: + GameState& gameState_; + void enterGameplay(); void refreshQuestJournalUi(); void selectQuestByIndex(int index); @@ -153,25 +136,12 @@ namespace ZL { static constexpr float TOAST_FADE_MS = 1000.0f; static constexpr float TOAST_VISIBLE_MS = 2000.0f; - GameState state = GameState::Gameplay; + GameUiState uiState_ = GameUiState::Gameplay; Inventory* inventory = nullptr; - //std::string zipFile_; int inventorySelectedIndex_ = -1; - enum class UniIntTutorialState { Step10, Step11, DarklandsActive, DarklandsStep13, DarklandsFull }; - UniIntTutorialState uniIntTutorialState_ = UniIntTutorialState::Step10; - std::string currentLocationName_; - bool currentIsDarklands_ = false; - - bool tutorialPhonePickedUp = false; - bool tutorialJournalPickedUp = false; - //bool tutorialPhoneScreenOpened = false; - bool tutorialPhoneChatScreenOpened = false; - bool tutorialJournalScreenOpened = false; - bool tutorialMessengerScreenOpened = false; - //bool tutorialTaxiScreenOpened = false; - - bool tutorialNeedOpenTaxiScreen = false; + int selectedQuestIndex = -1; + std::vector visibleQuestIds; std::shared_ptr hudRoot; std::shared_ptr hudStep1Root; @@ -190,11 +160,9 @@ namespace ZL { std::shared_ptr hudUniIntDarkFullRoot; std::shared_ptr hudUniExtDarkRoot; - std::shared_ptr hudCutsceneRoot_; std::shared_ptr hudTopHintRoot_; - std::shared_ptr phoneMainRoot; std::shared_ptr phoneMainHintARoot; std::shared_ptr phoneMainHintBRoot; @@ -221,27 +189,14 @@ namespace ZL { std::shared_ptr texItemSelected_; std::shared_ptr texItemTransparent_; - float currentPlayerHp_ = 200.f; - float currentPlayerMaxHp_ = 200.f; void applyCurrentHealthBar(); - int selectedQuestIndex = -1; - std::vector visibleQuestIds; - - bool chatUnread_[3] = { true, true, true }; - std::string chatPreviewMsg_[3]; // empty = keep JSON hardcoded text - int money_ = 5500; - - // Phone chat state + // Phone chat UI state (transient — rebuilt from gameState_.chatHistory on open) struct PhoneChatBubbleInfo { std::string nodeName; float height; }; std::vector phoneChatVisibleBubbles_; - - // Per-chat message history (max 5 messages each) - struct StoredChatMessage { std::string text; bool incoming; }; - std::vector chatHistory_[3]; int activeChatIndex_ = -1; // Preloaded bubble textures diff --git a/src/dialogue/DialogueRuntime.cpp b/src/dialogue/DialogueRuntime.cpp index eecf261..1c20a7f 100644 --- a/src/dialogue/DialogueRuntime.cpp +++ b/src/dialogue/DialogueRuntime.cpp @@ -364,4 +364,46 @@ void DialogueRuntime::presentChoices(const Node& node) { presentation.cutsceneBlackAlpha = 0.0f; } +void DialogueRuntime::save(nlohmann::json& out) const +{ + if (!isActive() || !activeDialogue) { + out["active"] = false; + return; + } + out["active"] = true; + out["dialogueId"] = activeDialogue->id; + out["currentNodeId"] = currentNodeId; +} + +void DialogueRuntime::load(const nlohmann::json& in) +{ + stop(); + if (!in.value("active", false)) return; + + const std::string dialogueId = in.value("dialogueId", ""); + const std::string nodeId = in.value("currentNodeId", ""); + if (dialogueId.empty() || nodeId.empty() || !database) return; + + const DialogueDefinition* dialogue = database->findDialogue(dialogueId); + if (!dialogue) { + std::cerr << "[dialogue] load: dialogue '" << dialogueId << "' not found\n"; + return; + } + + activeDialogue = dialogue; + currentNodeId.clear(); + visibleChoices.clear(); + selectedChoice = -1; + revealCharacters = 0.0f; + presentation = {}; + presentation.dialogueId = dialogue->id; + + enterNode(nodeId); + + // Snap text to fully revealed so the typing animation doesn't replay. + presentation.visibleText = presentation.fullText; + presentation.revealCompleted = true; + revealCharacters = static_cast(presentation.fullText.size()); +} + } // namespace ZL::Dialogue diff --git a/src/dialogue/DialogueRuntime.h b/src/dialogue/DialogueRuntime.h index 00dcdb7..775a64b 100644 --- a/src/dialogue/DialogueRuntime.h +++ b/src/dialogue/DialogueRuntime.h @@ -2,6 +2,7 @@ #include "dialogue/DialogueDatabase.h" #include "quest/QuestJournal.h" +#include "external/nlohmann/json.hpp" #include #include #include @@ -40,6 +41,13 @@ public: void setGlobalFlagStore(std::unordered_map* store); void setQuestJournal(Quest::QuestJournal* journal); + // Saves whether a dialogue is active, its id, and current node. + // Cutscene state is not saved. + void save(nlohmann::json& out) const; + // Restores saved dialogue state. Text reveal snaps to completion so the + // typing animation doesn't replay. No callbacks are fired on restore. + void load(const nlohmann::json& in); + private: enum class Mode { Inactive, diff --git a/src/dialogue/DialogueSystem.h b/src/dialogue/DialogueSystem.h index 3e9892d..bf5d712 100644 --- a/src/dialogue/DialogueSystem.h +++ b/src/dialogue/DialogueSystem.h @@ -1,5 +1,6 @@ #pragma once +#include "external/nlohmann/json.hpp" #include "dialogue/DialogueOverlay.h" #include "dialogue/DialogueRuntime.h" #include "cutscene/CutsceneDatabase.h" @@ -41,6 +42,9 @@ public: void setOnDialogueAdvanced(std::function cb); void stopDialogue(); + void saveDialogueState(nlohmann::json& out) const { dialogueRuntime.save(out); } + void loadDialogueState(const nlohmann::json& in) { dialogueRuntime.load(in); } + bool isActive() const { return dialogueRuntime.isActive() || cutsceneRuntime.isActive(); } bool blocksGameplayInput() const { return isActive(); }