shadow-over-bishkek001/src/Game.h
2026-07-19 13:07:39 +03:00

214 lines
7.2 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 "GameState.h"
namespace FRG {
struct SaveSlotInfo {
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;
GameState gameState;
EditorMode editorMode = EditorMode::None;
// Returns false if a transition is already in progress.
bool startDarklandsTransition();
bool startNightTransition();
std::unique_ptr<AudioPlayerAsync> audioPlayer;
MenuManager menuManager;
void activateSlowMoEffect();
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;
void saveGame(int slot);
void loadGame(int slot);
SaveSlotInfo readSlotInfo(int slot) const;
void createLocationsStep1();
void createLocationsStep2();
void createLocationsStep3();
void createLocationsStep4();
void createLocationsStep5();
void createShadowMapAndProparateToLocations();
void destroyShadowMapAndProparateToLocations();
// Reset entry point used by "Start New Game": loads resources/config/start_state.json
// into the existing Location objects if present, 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();
void performFullReset();
void saveInitialState();
// (Re-)arms one-shot gameplay callbacks (tutorial unlock, taxi request) that
// createLocations() wires up but that get consumed (set to nullptr) once used
// during play. Must be called after gameState.load() on the fast reset path,
// since that path keeps the existing Location objects instead of recreating them.
void rearmDormOneShotCallbacks();
int64_t getSyncTimeMs();
void processTickCount();
void drawScene();
void drawUI();
void drawLoading();
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 Game* s_instance;
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 newTickCount;
int64_t lastTickCount;
int lastDrawableW = -1;
int lastDrawableH = -1;
static const size_t CONST_TIMER_INTERVAL = 10;
static const size_t CONST_MAX_TIME_INTERVAL = 1000;
};
} // namespace FRG