#pragma once #include "render/Renderer.h" #include "Environment.h" #include "render/TextureManager.h" #include "UiManager.h" #include "items/Item.h" #include "GameState.h" #include #include #include #include #include "AudioPlayerAsync.h" #include "render/FrameBuffer.h" // Forward-declared here to avoid pulling Game.h into MenuManager.h. namespace ZL { struct SaveSlotInfo; } namespace ZL { extern const char* CONST_ZIP_FILE; enum class GameUiState { MainMenu, About, Gameplay, Inventory, QuestJournal, PhoneScreen }; class MenuManager { public: UiManager uiManager; UiManager topUiManager; UiManager chatUiManager; std::unordered_map> localizedLocationName; MenuManager(Renderer& iRenderer, GameState& gameState, AudioPlayerAsync& audioPlayer); void setup(Inventory& inv, const std::string& zipFile); void openInventory(); void selectInventoryItem(int index); void closeInventory(); void openQuestJournal(); void closeQuestJournal(); void toggleQuestJournal(); bool isInventoryOpen() const { return uiState_ == GameUiState::Inventory; } bool isQuestJournalOpen() const { return uiState_ == GameUiState::QuestJournal; } void showMainMenu(); bool isMainMenuOpen() const { return uiState_ == GameUiState::MainMenu; } void openPhoneScreen(); void closePhoneScreen(); bool isPhoneScreenOpen() const { return uiState_ == GameUiState::PhoneScreen; } void closePhoneEntirely(); void tutorialShowTaxiHint(); void setChatUnread(int chatIndex, bool unread, const std::string& previewMsg = ""); void spendMoney(int amount); int getMoney() const { return gameState_.money; } std::function startGameFunc; std::function startDialogueFunc; std::function startDarklandsTransitionFunc; std::function startNightTransitionFunc; std::function chatOpenCallback; std::function skipCutsceneFunc; std::function callTaxiFunc; std::function tutorialUnlockInteractiveObjectsFunc; std::function onSaveGame; std::function onLoadGame; std::function getSlotInfoFunc; std::function onResetGame; // Called when a chat message bubble should be shown (text + direction) void onChatBubbleReady(const std::string& text, bool incoming); void setDarklandsMode(bool enabled); void advanceTutorialStep(); void onItemPickedUp(const std::string& itemId); void onLocationChanged(const std::string& locationName); void onEnemyKilledInUniInterior(); void advanceUniIntDarklandsHud(); void onPlayerStartedWalking(); // Called by Game when the player's HP changes. Stores values and updates HUD. void updateHealthBar(float hp, float maxHp); void onCutsceneStarted(); void onCutsceneFinished(); bool cutsceneHudActive_ = false; // Called by Game when the window is resized and orientation has changed. void onOrientationChanged(); // Called from Game::drawUI() to render chat bubbles through an FBO. void drawChatFbo(Renderer& renderer); void saveSettings(); void loadSettings(); // Toast notification system void showToast(const std::string& iconPath, const std::string& text); void update(float deltaMs); protected: Renderer& renderer; private: GameState& gameState_; AudioPlayerAsync& audioPlayer_; void enterGameplay(bool isFromLoad = false); void applyHudForCurrentState(); void refreshQuestJournalUi(); void reloadLocalizedGameContent(); std::shared_ptr loadLocalizedUi(const std::string& basePath, const std::string& zipFile); void loadUiRoots(const std::string& zipFile); void selectQuestByIndex(int index); void refreshItemPickupHud(); void setupStep5Callbacks(); void setupGameplayHudCallbacks(); void openPhoneMessenger(); void openPhoneBank(); void openPhoneVideo(); void openPhoneTaxi(); void openPhoneMapScreen(std::shared_ptr mapRoot); void refreshChatUnreadIndicators(); void resetPhoneChatNodes(); void recomputePhoneChatPositions(); void recomputePhoneChatPositionsPortrait(); void openPhoneChatFromList(int chatIndex, std::shared_ptr chatRoot); void returnToPhoneChatList(); void closePhoneScreenFromChat(); void applyUniIntHud(); void rebuildChatBubblesFromHistory(int chatIndex, bool isPortrait); void showModalMenuScreen(); void openPortraitInventoryItem(int index); void closePortraitInventoryItem(); void openPortraitQuestJournalItem(int index); void closePortraitJournalItem(); void showAboutScreen(); void showSettingsScreen(); void showLanguageScreen(); void showLoadGameScreen(); void showSaveGameScreen(); // Toast internals struct ToastEntry { std::string iconPath; std::string text; float timer = 0.0f; enum class State { FadeIn, Visible, FadeOut } state = State::FadeIn; float currentAlpha() const; }; struct ToastMessage { std::string iconPath; std::string text; }; void updateToasts(float deltaMs); void applyToastsToUi(); void hideAllToastWidgets(); std::vector activeToasts_; std::deque toastQueue_; static constexpr float TOAST_FADE_MS = 1000.0f; static constexpr float TOAST_VISIBLE_MS = 2000.0f; GameUiState uiState_ = GameUiState::Gameplay; Inventory* inventory = nullptr; int inventorySelectedIndex_ = -1; int inventoryMenuStackDepth_ = 0; int selectedQuestIndex = -1; int journalMenuStackDepth_ = 0; std::vector visibleQuestIds; std::shared_ptr hudRoot; std::shared_ptr hudStep1Root; std::shared_ptr hudStep2Root; std::shared_ptr hudStep3Root; std::shared_ptr hudStep4Root; std::shared_ptr hudStep5aRoot; std::shared_ptr hudStep5bRoot; std::shared_ptr hudStep5abRoot; std::shared_ptr hudUniExtRoot; std::shared_ptr hudUniIntStep10Root; std::shared_ptr hudUniIntStep11Root; std::shared_ptr hudUniIntStep12Root; std::shared_ptr hudUniIntFullRoot; std::shared_ptr hudUniIntStep13Root; 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; std::shared_ptr phoneMainHintABRoot; std::shared_ptr phoneBankRoot; std::shared_ptr phoneVideoRoot; std::shared_ptr phoneMapDormRoot; std::shared_ptr phoneMapUniRoot; // Portrait-mode (vertical) equivalents for screens that have a portrait layout std::shared_ptr verticalPhoneMainRoot; std::shared_ptr verticalPhoneMainHintARoot; std::shared_ptr verticalPhoneMainHintBRoot; std::shared_ptr verticalPhoneMainHintABRoot; std::shared_ptr verticalPhoneBankRoot; std::shared_ptr verticalPhoneVideoRoot; std::shared_ptr verticalPhoneMapDormRoot; std::shared_ptr verticalPhoneMapUniRoot; std::shared_ptr phoneChatListRoot; std::shared_ptr phoneChatListHintRoot; std::shared_ptr verticalPhoneChatListRoot; std::shared_ptr verticalPhoneChatListHintRoot; std::shared_ptr phoneChat1Root; std::shared_ptr phoneChat2Root; std::shared_ptr phoneChat3Root; std::shared_ptr verticalPhoneChat1Root; std::shared_ptr verticalPhoneChat2Root; std::shared_ptr verticalPhoneChat3Root; std::shared_ptr modalMenuRoot; std::shared_ptr mainMenuRoot; std::shared_ptr aboutScreenRoot; std::shared_ptr settingsScreenRoot; std::shared_ptr languageScreenRoot; std::shared_ptr loadSavedGameScreenRoot; std::shared_ptr saveGameScreenRoot; std::shared_ptr newInventoryRoot; std::shared_ptr portraitInventoryListRoot; std::shared_ptr portraitInventoryItemRoot; std::shared_ptr questJournalRoot; std::shared_ptr portraitQuestJournalListRoot; std::shared_ptr portraitQuestJournalItemRoot; std::shared_ptr texObjectiveCompleted_; std::shared_ptr texObjectiveBlank_; std::shared_ptr texItemSelected_; std::shared_ptr texItemTransparent_; void applyCurrentHealthBar(); bool isPortraitMode() const; // Tracks which phone sub-screen is currently shown so onOrientationChanged() // can reopen the correct screen after swapping roots. enum class PhoneSubScreen { None, Main, Bank, Video, MapDorm, MapUni, ChatList, Chat }; PhoneSubScreen currentPhoneSubScreen_ = PhoneSubScreen::None; // Phone chat UI state (transient — rebuilt from gameState_.chatHistory on open) struct PhoneChatBubbleInfo { std::string nodeName; float height; }; std::vector phoneChatVisibleBubbles_; int activeChatIndex_ = -1; // Preloaded bubble textures std::shared_ptr texBubbleInCenter_, texBubbleInLT_, texBubbleInLB_, texBubbleInRT_, texBubbleInRB_; std::shared_ptr texBubbleOutCenter_, texBubbleOutLT_, texBubbleOutLB_, texBubbleOutRT_, texBubbleOutRB_; static constexpr float CHAT_TOP_Y = 610.0f; static constexpr float CHAT_BOTTOM_Y = 290.0f; static constexpr float CHAT_SPACING = 10.0f; static constexpr float P_CHAT_TOP_Y = 610.0f + 246; static constexpr float P_CHAT_BOTTOM_Y = 290.0f + 126; // FBO dimensions for chat clipping. // OFFSET_UP / OFFSET_DOWN = how far the FBO extends above / below screen center. // Height = UP + DOWN. Quad: y0 = projH/2 - DOWN, y1 = projH/2 + UP. static constexpr float CHAT_FBO_WIDTH_L = 469.0f; static constexpr float CHAT_FBO_OFFSET_UP_L = 260.0f; static constexpr float CHAT_FBO_OFFSET_DOWN_L = 70.0f; static constexpr float CHAT_FBO_WIDTH_P = 670.0f; static constexpr float CHAT_FBO_OFFSET_UP_P = 360.0f; static constexpr float CHAT_FBO_OFFSET_DOWN_P = 70.0f; std::unique_ptr chatFrameBuffer_; VertexRenderStruct chatFboQuad_; float chatFboQuadLastProjW_ = -1.0f; float chatFboQuadLastProjH_ = -1.0f; bool chatFboQuadLastPortrait_ = false; }; } // namespace ZL