265 lines
9.4 KiB
C++
265 lines
9.4 KiB
C++
#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 <queue>
|
|
#include <functional>
|
|
#include <vector>
|
|
#include <string>
|
|
#include <memory>
|
|
#include <cstdint>
|
|
#include <render/TextRenderer.h>
|
|
#include "MenuManager.h"
|
|
#include <unordered_map>
|
|
#include <unordered_set>
|
|
#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<Texture> loadingTexture;
|
|
VertexRenderStruct loadingMesh;
|
|
bool loadingCompleted = false;
|
|
|
|
std::shared_ptr<ShadowMap> 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<std::function<void()>> 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<AudioPlayerAsync> audioPlayer;
|
|
|
|
private:
|
|
InternalGameServer internalGameServer;
|
|
LocalGameServerHost localGameServerHost;
|
|
#if defined(NETWORK)
|
|
std::unique_ptr<WebsocketGameClient> websocketGameClient;
|
|
std::unique_ptr<RemoteGameServerHost> 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<int64_t, PointerState> 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<const GameSnapshot> 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<Character> 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<void()> 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<Location> 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<Texture> loadingProgressBarTexture;
|
|
std::shared_ptr<Texture> 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<Texture> 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
|