#pragma once #include "render/Renderer.h" #include "Environment.h" #include "render/TextureManager.h" #include "UiManager.h" #include "items/Item.h" #include "quest/QuestJournal.h" #include #include #include #include namespace ZL { extern const char* CONST_ZIP_FILE; enum class GameState { Gameplay, Inventory, QuestJournal, 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; ZL::Quest::QuestJournal questJournal; MenuManager(Renderer& iRenderer); 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 state == GameState::Inventory; } bool isQuestJournalOpen() const { return state == GameState::QuestJournal; } void openPhoneScreen(); void closePhoneScreen(); void revealPhoneChatBubble(const std::string& slotName); bool isPhoneScreenOpen() const { return state == GameState::PhoneScreen; } std::function startDialogueFunc; std::function startDarklandsTransitionFunc; void setDarklandsMode(bool enabled); void advanceTutorialStep(); void onItemPickedUp(const std::string& itemId); void onLocationChanged(const std::string& locationName); TutorialStep tutorialStep = TutorialStep::Step0; protected: Renderer& renderer; private: void enterGameplay(); void refreshQuestJournalUi(); void selectQuestByIndex(int index); void refreshItemPickupHud(); void setupStep5Callbacks(); void setupGameplayHudCallbacks(); void resetPhoneChatNodes(); void recomputePhoneChatPositions(); void openPhoneChatFromList(std::shared_ptr chatRoot, const std::string& dialogueId); void returnToPhoneChatList(); void closePhoneScreenFromChat(); GameState state = GameState::Gameplay; Inventory* inventory = nullptr; std::string zipFile_; int inventorySelectedIndex_ = -1; bool tutorialPhonePickedUp = false; bool tutorialJournalPickedUp = false; bool tutorialPhoneScreenOpened = false; bool tutorialJournalScreenOpened = false; 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 hudUniIntRoot; std::shared_ptr phoneChatListRoot; std::shared_ptr phoneChat1Root; std::shared_ptr phoneChat2Root; std::shared_ptr phoneChat3Root; std::shared_ptr newInventoryRoot; std::shared_ptr questJournalRoot; std::shared_ptr texObjectiveCompleted_; std::shared_ptr texObjectiveBlank_; std::shared_ptr texItemSelected_; std::shared_ptr texItemTransparent_; int selectedQuestIndex = -1; std::vector visibleQuestIds; // Dialogues that have been started at least once; re-opening a chat won't restart them std::unordered_set startedDialogues_; // Phone chat state struct PhoneChatBubbleInfo { std::string nodeName; float height; }; std::vector phoneChatVisibleBubbles_; static constexpr float CHAT_TOP_Y = 610.0f; static constexpr float CHAT_BOTTOM_Y = 290.0f; static constexpr float CHAT_SPACING = 10.0f; }; } // namespace ZL