shadow-over-bishkek001/src/MenuManager.h
2026-07-07 13:46:28 +03:00

308 lines
10 KiB
C++

#pragma once
#include "render/Renderer.h"
#include "Environment.h"
#include "render/TextureManager.h"
#include "UiManager.h"
#include "items/Item.h"
#include "GameState.h"
#include <vector>
#include <deque>
#include <string>
#include <memory>
#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<std::string,
std::unordered_map<Language, std::string>> 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<void()> startGameFunc;
std::function<void(const std::string&)> startDialogueFunc;
std::function<void()> startDarklandsTransitionFunc;
std::function<void()> startNightTransitionFunc;
std::function<void(int)> chatOpenCallback;
std::function<void()> skipCutsceneFunc;
std::function<void()> callTaxiFunc;
std::function<void()> tutorialUnlockInteractiveObjectsFunc;
std::function<void(int)> onSaveGame;
std::function<void(int)> onLoadGame;
std::function<SaveSlotInfo(int)> getSlotInfoFunc;
std::function<void()> 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<UiNode> 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<UiNode> mapRoot);
void refreshChatUnreadIndicators();
void resetPhoneChatNodes();
void recomputePhoneChatPositions();
void recomputePhoneChatPositionsPortrait();
void openPhoneChatFromList(int chatIndex, std::shared_ptr<UiNode> 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<ToastEntry> activeToasts_;
std::deque<ToastMessage> 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<std::string> visibleQuestIds;
std::shared_ptr<UiNode> hudRoot;
std::shared_ptr<UiNode> hudStep1Root;
std::shared_ptr<UiNode> hudStep2Root;
std::shared_ptr<UiNode> hudStep3Root;
std::shared_ptr<UiNode> hudStep4Root;
std::shared_ptr<UiNode> hudStep5aRoot;
std::shared_ptr<UiNode> hudStep5bRoot;
std::shared_ptr<UiNode> hudStep5abRoot;
std::shared_ptr<UiNode> hudUniExtRoot;
std::shared_ptr<UiNode> hudUniIntStep10Root;
std::shared_ptr<UiNode> hudUniIntStep11Root;
std::shared_ptr<UiNode> hudUniIntStep12Root;
std::shared_ptr<UiNode> hudUniIntFullRoot;
std::shared_ptr<UiNode> hudUniIntStep13Root;
std::shared_ptr<UiNode> hudUniIntDarkFullRoot;
std::shared_ptr<UiNode> hudUniExtDarkRoot;
std::shared_ptr<UiNode> hudCutsceneRoot_;
std::shared_ptr<UiNode> hudTopHintRoot_;
std::shared_ptr<UiNode> phoneMainRoot;
std::shared_ptr<UiNode> phoneMainHintARoot;
std::shared_ptr<UiNode> phoneMainHintBRoot;
std::shared_ptr<UiNode> phoneMainHintABRoot;
std::shared_ptr<UiNode> phoneBankRoot;
std::shared_ptr<UiNode> phoneVideoRoot;
std::shared_ptr<UiNode> phoneMapDormRoot;
std::shared_ptr<UiNode> phoneMapUniRoot;
// Portrait-mode (vertical) equivalents for screens that have a portrait layout
std::shared_ptr<UiNode> verticalPhoneMainRoot;
std::shared_ptr<UiNode> verticalPhoneMainHintARoot;
std::shared_ptr<UiNode> verticalPhoneMainHintBRoot;
std::shared_ptr<UiNode> verticalPhoneMainHintABRoot;
std::shared_ptr<UiNode> verticalPhoneBankRoot;
std::shared_ptr<UiNode> verticalPhoneVideoRoot;
std::shared_ptr<UiNode> verticalPhoneMapDormRoot;
std::shared_ptr<UiNode> verticalPhoneMapUniRoot;
std::shared_ptr<UiNode> phoneChatListRoot;
std::shared_ptr<UiNode> phoneChatListHintRoot;
std::shared_ptr<UiNode> verticalPhoneChatListRoot;
std::shared_ptr<UiNode> verticalPhoneChatListHintRoot;
std::shared_ptr<UiNode> phoneChat1Root;
std::shared_ptr<UiNode> phoneChat2Root;
std::shared_ptr<UiNode> phoneChat3Root;
std::shared_ptr<UiNode> verticalPhoneChat1Root;
std::shared_ptr<UiNode> verticalPhoneChat2Root;
std::shared_ptr<UiNode> verticalPhoneChat3Root;
std::shared_ptr<UiNode> modalMenuRoot;
std::shared_ptr<UiNode> mainMenuRoot;
std::shared_ptr<UiNode> aboutScreenRoot;
std::shared_ptr<UiNode> settingsScreenRoot;
std::shared_ptr<UiNode> languageScreenRoot;
std::shared_ptr<UiNode> loadSavedGameScreenRoot;
std::shared_ptr<UiNode> saveGameScreenRoot;
std::shared_ptr<UiNode> newInventoryRoot;
std::shared_ptr<UiNode> portraitInventoryListRoot;
std::shared_ptr<UiNode> portraitInventoryItemRoot;
std::shared_ptr<UiNode> questJournalRoot;
std::shared_ptr<UiNode> portraitQuestJournalListRoot;
std::shared_ptr<UiNode> portraitQuestJournalItemRoot;
std::shared_ptr<Texture> texObjectiveCompleted_;
std::shared_ptr<Texture> texObjectiveBlank_;
std::shared_ptr<Texture> texItemSelected_;
std::shared_ptr<Texture> 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<PhoneChatBubbleInfo> phoneChatVisibleBubbles_;
int activeChatIndex_ = -1;
// Preloaded bubble textures
std::shared_ptr<Texture> texBubbleInCenter_, texBubbleInLT_, texBubbleInLB_, texBubbleInRT_, texBubbleInRB_;
std::shared_ptr<Texture> 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<FrameBuffer> chatFrameBuffer_;
VertexRenderStruct chatFboQuad_;
float chatFboQuadLastProjW_ = -1.0f;
float chatFboQuadLastProjH_ = -1.0f;
bool chatFboQuadLastPortrait_ = false;
};
} // namespace ZL