#pragma once #include "Character.h" #include "render/Renderer.h" #include "Environment.h" #include "render/TextureManager.h" #include "SparkEmitter.h" #include "UiManager.h" #include "utils/TaskManager.h" #include "items/GameObjectLoader.h" #include "items/Item.h" #include "items/InteractiveObject.h" #include #include #include #include #include #include #include #include "MenuManager.h" #include #include #include "Location.h" #include "AudioPlayerAsync.h" #include "game_server/InternalGameServer.h" #include "game_server/LocalGameServerHost.h" #include "game_server/GameServerRouter.h" #include "game_server/GameServerHostRouter.h" #if defined(NETWORK) #include "game_server/WebsocketGameClient.h" #include "game_server/RemoteGameServerHost.h" #endif namespace FRG { struct LocationConfig; struct CampaignConfig; struct SaveSlotInfo { std::string campaignId; std::string locationName; std::string savedAt; bool empty = true; }; class Game { public: Game(); ~Game(); void setup(); void setupPart2(); void update(); void render(); void onWindowResized(); bool shouldExit() const { return Environment::exitGameLoop; } Renderer renderer; TaskManager taskManager; MainThreadHandler mainThreadHandler; std::shared_ptr loadingTexture; VertexRenderStruct loadingMesh; bool loadingCompleted = false; std::shared_ptr shadowMap; // setupPart2() populates this queue with one closure per loading step instead of // running the work itself; Game::update() drains one step per frame so the window // keeps pumping events / repainting (and, on Emscripten, the browser keeps the tab // responsive) instead of freezing for the whole duration of loading. std::queue> loadSteps; size_t loadStepsTotal = 0; size_t loadStepsDone = 0; float currentLoadingProgress() const { return loadStepsTotal ? (float)loadStepsDone / (float)loadStepsTotal : resourcesDownloadProgress; } // The progress bar only reflects the initial resource load (its steps are // pre-counted upfront). Later loadSteps runs, like MenuManager's localized // content reload, don't have a meaningful denominator, so the bar is hidden. bool showLoadingProgressBar = true; //EditorMode editorMode = EditorMode::None; // Returns false if a transition is already in progress. bool startDarklandsTransition(); bool startNightTransition(); std::unique_ptr audioPlayer; private: InternalGameServer internalGameServer; LocalGameServerHost localGameServerHost; #if defined(NETWORK) std::unique_ptr websocketGameClient; std::unique_ptr remoteGameServerHost; #endif GameServerRouter gameServer; GameServerHostRouter gameServerHost; bool remoteCampaignActive = false; bool remoteInitialStateBound = false; public: IGameServer& server() { return gameServer; } const IGameServer& server() const { return gameServer; } MenuManager menuManager; #ifdef EMSCRIPTEN static Game* s_instance; #endif private: // Unified pointer handling: mouse-left and a single touch share one path. // A press becomes a tap (interact / walk-to) on release if it never crossed // CAMERA_DRAG_PIXEL_THRESHOLD; otherwise it becomes a camera-rotation drag. // Two simultaneous touches enter pinch-zoom instead. static constexpr int CAMERA_DRAG_PIXEL_THRESHOLD = 12; struct PointerState { int eventX = 0, eventY = 0; // current raw window-pixel coords int mx = 0, my = 0; // current projection-space coords int downEventX = 0, downEventY = 0;// where the press started (raw px) int downMx = 0, downMy = 0; // where the press started (proj) bool capturedByUi = false; }; std::unordered_map activePointers; bool hasPrimaryPointer = false; int64_t primaryPointerId = 0; bool cameraDragging = false; bool pinchActive = false; int64_t pinchFingerA = 0; int64_t pinchFingerB = 0; float pinchStartDistance = 0.0f; float pinchStartZoom = 0.0f; // Tutorial: azimuth and inclination captured at the start of each camera drag, // used to measure how far the user has rotated before advancing the tutorial step. float dragStartAzimuth = 0.0f; float dragStartInclination = 0.0f; Location* currentLocation() const; std::shared_ptr stateSnapshot() const { return gameServer.snapshot(); } void saveGame(int slot); void loadGame(int slot); void playMusicForLoadedGame(); SaveSlotInfo readSlotInfo(int slot) const; // Loading model: one campaign at a time. InternalGameServer privately owns the // one live Location while GameState retains pure value state for every location. std::string loadedCampaignId; std::shared_ptr player; bool isLocationLoaded(const std::string& campaignId, const std::string& locationId) const; // Runs `then` once `locationId` of `campaignId` is the loaded location. If it isn't, // it is loaded first behind its loading screen, releasing the previous location // (or, for another campaign, the whole previous campaign). void enterLocationThen(const std::string& campaignId, const std::string& locationId, std::function then); // Starts the campaign: its quests and the player. No location is loaded yet. void loadCampaign(const std::string& campaignId); void unloadCampaign(); // Builds the campaign's location and restores its stored state, if any. void loadLocation(const std::string& locationId); // Releases the loaded runtime location, retaining its value state when requested. void unloadLocation(bool storeState); // Teleport zones: moves the player to another location of the campaign. void teleportPlayer(const std::string& destName, const Eigen::Vector3f& destPos, float destRotY); // Builds one location from its config and wires the callbacks every location gets. std::shared_ptr createLocation(const LocationConfig& config); // Wires the location's callbacks into the game (teleports, dialogue, HUD...). void wireLocation(Location* loc); const CampaignConfig& activeCampaign() const; void createShadowMapAndProparateToLocations(); void destroyShadowMapAndProparateToLocations(); // Reset entry point used by "Start New Game": loads the active campaign's start-state // JSON (into the loaded location, storing the rest), else falls back to performFullReset(). // Must only ever run from a deferred task (see mainThreadHandler.EnqueueMainThreadTask), // never synchronously from a Lua/cutscene call stack. void performResetToInitialState(); // Rebuilds the loaded location from scratch and forgets the other locations' states. void performFullReset(); // Applies the campaign's tutorial flag and MenuManager::tutorialEnabledSetting to // the freshly reset state (new games only). void applyTutorialSetting(); void saveInitialState(); int64_t getSyncTimeMs(); void processTickCount(); void processServerEvents(); void drawScene(); void drawUI(); void drawLoading(); void drawLoadingScreenUi(); void onPointerDown(int64_t fingerId, int eventX, int eventY, int mx, int my); void onPointerUp(int64_t fingerId, int eventX, int eventY, int mx, int my); void onPointerMotion(int64_t fingerId, int eventX, int eventY, int mx, int my); void enterCameraDragMode(int eventX, int eventY); void exitCameraDragMode(); void startPinch(); void updatePinchZoom(); void endPinch(); int countNonUiPointers() const; #ifdef EMSCRIPTEN static void onResourcesZipLoaded(unsigned handle, void* userData, const char* filename); static void onResourcesZipError(unsigned handle, void* userData, int httpStatus); static void onResourcesZipProgress(unsigned handle, void* userData, int percentComplete); #endif // Only meaningful during the Emscripten zip-download phase, before loadSteps // exists (see currentLoadingProgress() above); harmless/unused on other platforms. float resourcesDownloadProgress = 0.0f; // Progress bar assets/meshes, loaded and drawn on every platform so setupPart2's // step-by-step progress is visible during desktop/Android loading too. std::shared_ptr loadingProgressBarTexture; std::shared_ptr loadingProgressBarFrameTexture; VertexRenderStruct loadingProgressBarFrameMesh; VertexRenderStruct loadingProgressBarFillMesh; float loadingBarLastRenderedProgress = -1.0f; float loadingBarLastW = -1.0f; float loadingBarLastH = -1.0f; // Screen-flash transition state (shared by darklands and night transitions) float darklandsFlashAlpha = 0.0f; bool darklandsFlashActive = false; bool darklandsFlashFadingIn = true; bool isNightTransition = false; // true → black flash toggling isNight; false → white flash toggling isDarklands VertexRenderStruct darklandsFlashQuad; float darklandsFlashQuadW = -1.0f; float darklandsFlashQuadH = -1.0f; void updateDarklandsFlash(int64_t deltaMs); void drawDarklandsFlash(); // Portrait-mode overlay (mobile / portrait browsers) /*std::shared_ptr mobileRotateTexture; VertexRenderStruct mobileRotateMesh; float mobileRotateMeshLastW = -1.0f; float mobileRotateMeshLastH = -1.0f; */ //void drawMobilePortraitOverlay(); int64_t lastTickCount; int64_t simulationAccumulatorMs = 0; int lastDrawableW = -1; int lastDrawableH = -1; static const size_t CONST_MAX_TIME_INTERVAL = 1000; }; } // namespace FRG