379 lines
13 KiB
C++
379 lines
13 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 FRG { struct SaveSlotInfo; }
|
|
|
|
namespace FRG {
|
|
|
|
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();
|
|
void showMenuAfterGameCompleted();
|
|
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(bool)> shadowMapSettingsChangedFunc;
|
|
|
|
|
|
// Set by Game: runs the given closures one per frame with the loading
|
|
// screen shown (same mechanism as the initial resource load), so a heavy
|
|
// operation like reloadLocalizedGameContent() doesn't block the window
|
|
// for its whole duration. Game re-enables normal rendering once all the
|
|
// closures have run.
|
|
std::function<void(std::vector<std::function<void()>>)> runAsLoadingSequence;
|
|
|
|
// 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(std::shared_ptr<Texture> toastTexture, const std::string& text);
|
|
void update(float deltaMs);
|
|
|
|
std::shared_ptr<Texture> toastTextureQuestNew;
|
|
std::shared_ptr<Texture> toastTextureQuestCompleted;
|
|
std::shared_ptr<Texture> toastTextureQuestFailed;
|
|
std::shared_ptr<Texture> toastTextureItemAdded;
|
|
std::shared_ptr<Texture> toastTextureItemRemoved;
|
|
|
|
std::shared_ptr<Texture> languageLoadingRu;
|
|
std::shared_ptr<Texture> languageLoadingEn;
|
|
|
|
|
|
void refreshFullScreenToggleButton();
|
|
|
|
#ifdef __EMSCRIPTEN__
|
|
bool shadowsEnabled = false;
|
|
#else
|
|
bool shadowsEnabled = true;
|
|
#endif
|
|
std::string lastMusicTrack;
|
|
|
|
#ifdef __EMSCRIPTEN__
|
|
static void onMusicZipLoaded(unsigned /*handle*/, void* /*userData*/, const char* /*filename*/);
|
|
static void onMusicZipError(unsigned /*handle*/, void* /*userData*/, int httpStatus);
|
|
static void onMusicZipProgress(unsigned /*handle*/, void* /*userData*/, int percentComplete);
|
|
|
|
float musicDownloadProgress = 0.0f;
|
|
|
|
bool musicDownloadStarted = false;
|
|
bool musicDownloadError = false;
|
|
bool playerInSettingsNoMusic = false;
|
|
bool playerInSettingsNoMusicGraphics = false;
|
|
#endif
|
|
|
|
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);
|
|
std::shared_ptr<Texture> loadLocalizedTexture(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 closePortraitJournalItemCompletely();
|
|
|
|
void showAboutScreen();
|
|
void showCreditsScreen();
|
|
void showSettingsScreen();
|
|
void showSettingsGraphicsScreen();
|
|
|
|
|
|
#ifdef __EMSCRIPTEN__
|
|
void showSettingsNoMusicWebScreen();
|
|
#endif
|
|
void showLanguageScreen();
|
|
void showLoadGameScreen();
|
|
void showSaveGameScreen();
|
|
|
|
// Toast internals
|
|
struct ToastEntry {
|
|
std::shared_ptr<Texture> toastTexture;
|
|
std::string text;
|
|
float timer = 0.0f;
|
|
enum class State { FadeIn, Visible, FadeOut } state = State::FadeIn;
|
|
float currentAlpha() const;
|
|
};
|
|
struct ToastMessage { std::shared_ptr<Texture> toastTexture; 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;
|
|
bool isPlayerInCreditsMenu = false;
|
|
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> creditsScreenRoot;
|
|
std::shared_ptr<UiNode> portraitCreditsScreenRoot;
|
|
|
|
#ifdef __EMSCRIPTEN__
|
|
std::shared_ptr<UiNode> settingsScreenNoMusicWebRoot;
|
|
#endif
|
|
std::shared_ptr<UiNode> settingsScreenRoot;
|
|
std::shared_ptr<UiNode> settingsGraphicsScreenRoot;
|
|
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 = 240.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 = 364.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;
|
|
|
|
std::string musicVolumePrefix;
|
|
std::string soundVolumePrefix;
|
|
std::string musicToggleOn;
|
|
std::string musicToggleOff;
|
|
std::string soundToggleOn;
|
|
std::string soundToggleOff;
|
|
std::string fullScreenToggleOn;
|
|
std::string fullScreenToggleOff;
|
|
std::string shadowToggleOn;
|
|
std::string shadowToggleOff;
|
|
std::string musicDownloadProgressPrefix;
|
|
|
|
};
|
|
|
|
} // namespace FRG
|