#pragma once #include "render/Renderer.h" #include "Environment.h" #include "render/TextureManager.h" #include "render/TextRenderer.h" #include "items/GameObjectLoader.h" #include "items/InteractiveObject.h" #include "navigation/PathFinder.h" #include "render/ShadowMap.h" #include "render/PathVisualization.h" #include "ScriptEngine.h" #include "dialogue/DialogueSystem.h" #include "SparkEmitter.h" #include "TeleportZone.h" #include "LocationEditor.h" #include "LocationState.h" #include "LocationGameState.h" #include #include #include #include #include "Character.h" namespace FRG { class IGameServer; struct PointLight { std::string id; Eigen::Vector3f position; // world space Eigen::Vector3f direction; // world space, unit vector the cone points toward Eigen::Vector3f color; // RGB, values > 1.0 increase intensity bool autoLight = false; // if true, only activates when nearest light to player float autoLightDistance = -1.0f; // max distance for autoLight activation; <=0 means unlimited // Rectangular limits — keep light confined to a room-sized AABB centred on the light float limitX = 99999.f; // shader fragment limit: half-width in world X float limitY = 99999.f; // shader fragment limit: half-width in world Z float autoLightLimitX = -1.0f; // activation rect half-width X; <0 = use nearest-light logic float autoLightLimitY = -1.0f; // activation rect half-width Z }; struct TriggerZone { std::string id; Eigen::Vector3f position = Eigen::Vector3f::Zero(); float radius = 1.5f; float hysteresis = 0.3f; // exit fires at radius + hysteresis to prevent flickering bool enabled = true; bool playerInside = false; // runtime tracking state, not serialized (mirrored in LocationNetworkState) }; struct LocationSetup { std::shared_ptr definition; std::string gameObjectsJsonPath; std::string interactiveObjectsJsonPath; std::string npcsJsonPath; std::string dialoguesJsonPath; //std::vector navigationJsonPaths; std::string scriptPath; std::string teleportsJsonPath; std::string triggerZonesJsonPath; std::string lightsJsonPath; Vec3fp playerPosition; // Player shared by all locations of a campaign (see Location::createPlayer); // setup() creates a private one when this is null. std::shared_ptr player; LocationGameState* gameState = nullptr; std::shared_ptr localState; }; class Location { public: Location(Renderer& iRenderer, IGameServer& server, std::string locationId); // Detaches the (shared, longer-lived) player from this location. ~Location(); const std::string& locationId() const { return locationId_; } IGameServer& gameServer() { return gameServer_; } std::shared_ptr stateSnapshot() const; LocationLocalState& localState() { return *localState_; } const LocationLocalState& localState() const { return *localState_; } bool isDarklands() const; bool isNight() const; bool isDawn() const; int globalInt(const std::string& key) const; float globalFloat(const std::string& key) const; void setGlobalInt(const std::string& key, int value); void setGlobalFloat(const std::string& key, float value); std::unordered_map gameObjects; // Repeated static objects (same mesh + textures) drawn with one instanced call each. std::vector instancedBatches; std::vector interactiveObjects; // Shared: the same player moves from location to location and outlives them. std::shared_ptr player; std::vector> npcs; // Builds the player character (models, animations, weapon, textures). static std::shared_ptr createPlayer(Renderer& renderer, AudioPlayerAsync* audioPlayer, IGameServer& server, const std::string& playerId); std::shared_ptr shadowMap; Eigen::Matrix4f cameraViewMatrix = Eigen::Matrix4f::Identity(); std::unique_ptr npcNameText; ScriptEngine scriptEngine; Dialogue::DialogueSystem dialogueSystem; std::vector teleportZones; std::function onTeleport; std::vector triggerZones; std::vector pointLights; // Called when the player successfully taps the ground and a floor walk target is set. // Used by the tutorial system to detect the "tap to walk" gesture. std::function onPlayerFloorWalk; std::function onPlayerTaxiRequired; // Set by Game after setup(). Lua and onDeathAnimComplete call this to // request the transition without coupling Location to Game directly. std::function requestDarklandsTransition; std::function requestNightDayTransition; // Set by Game after setup(). Lua calls this to advance the uni_interior darklands // tutorial to the attack hint (trigger-zone encounter). std::function requestAdvanceDarklandsHud; std::function requestClosePhone; std::function requestReturnToMainMenu; std::function requestSetChatUnread; std::function requestDarklandsPlayBattleMusic; std::function requestDarklandsPlayNormalMusic; // Navigation editor — toggle with 'N', save with 'B', right-click to finalize polygon EditorMode editorMode = EditorMode::None; LocationEditor editor; //std::vector navigationMapPaths; // Set by Game when the user's primary pointer (left mouse / single touch) // has crossed the tap-vs-drag threshold and is now rotating the camera. bool cameraDragging = false; int lastMouseX = 0; int lastMouseY = 0; bool playBattleMusic = false; std::shared_ptr carTexture; VertexRenderStruct carMesh; std::shared_ptr carWheelTexture; VertexRenderStruct carWheelMesh; void setup(const LocationSetup& params, AudioPlayerAsync* audioPlayer); //void setupNavigation(const std::vector& paths); bool switchNavigation(int index); void setCharacterTarget(Character& character, const Vec3fp& target, std::string callbackName = ""); void clearCharacterAttackTarget(Character& character); void stopCharacter(Character& character); void faceCharacter(Character& character, std::string targetId); void replanCharacter(Character& character); void resetCharacterAfterDeath(Character& character); void tryCharacterAttackCombo(Character& character); void setCharacterHp(Character& character, float hp); void refillCharacterHp(Character& character); bool hasInventoryItem(const std::string& itemId) const; std::size_t inventoryItemCount() const; void addInventoryItem(std::string itemId); void removeInventoryItem(std::string itemId); bool unlockQuest(const std::string& questId); bool completeQuest(const std::string& questId); bool failQuest(const std::string& questId); bool setQuestObjectiveCompleted( const std::string& questId, const std::string& objectiveId, bool completed); bool setQuestObjectiveVisible( const std::string& questId, const std::string& objectiveId, bool visible); bool setQuestActiveObjective(const std::string& questId, int objectiveIndex); InteractiveObject* raycastInteractiveObjects(const Eigen::Vector3f& rayOrigin, const Eigen::Vector3f& rayDir); Character* raycastNpcs(const Eigen::Vector3f& rayOrigin, const Eigen::Vector3f& rayDir, float maxDistance = 100.0f); void drawGame(); void drawShadowDepthPass(); void drawGameWithShadows(); void drawGameDarklands(); void drawGameNight(); void drawNightShadowDepthPass(const PointLight& light); void drawNpcCar(); bool setNavigationAreaAvailable(const std::string& areaName, bool available); void update(int64_t deltaMs, const LocationSimulationEvents& events); void updatePresentation(int64_t deltaMs); // Purely visual feedback for click-triggered player routes. Disabling it // also cancels an animation that is currently playing. void setPathVisualizationEnabled(bool enabled); bool isPathVisualizationEnabled() const { return pathVisualization.isEnabled(); } void handleDown(int64_t fingerId, int eventX, int eventY, int mx, int my); void handleUp(int64_t fingerId, int mx, int my); void handleMotion(int64_t fingerId, int eventX, int eventY, int mx, int my); bool requestDialogueStart(const std::string& dialogueId); bool requestCutsceneStart(const std::string& cutsceneId); void setOnCutsceneFinished(std::function cb); void setDialogueFlag(const std::string& flag, int value); int getDialogueFlag(const std::string& flag) const; protected: friend class LocationEditor; friend class Game; friend class ScriptEngine; friend class LocalGameServerHost; friend class RemoteGameServerHost; Renderer& renderer; AudioPlayerAsync* audioPlayer; private: IGameServer& gameServer_; std::string locationId_; std::shared_ptr localState_; std::unordered_map> remotePlayers_; void synchronizeRemotePlayers(); void drawRemotePlayerNames( const Eigen::Matrix4f& cameraViewMatrix, const Eigen::Matrix4f& projectionMatrix); // ---- Pointer-to-index helpers ---- TeleportZone* getTargetTeleportZone(const LocationNetworkState& state); enum class InstancedTexture { None, Color, Darklands }; // Draws instancedBatches with the instancing variant of the current pass shader // (e.g. "fog_inst"). The variant is a separate program, so setUniforms must re-send // the pass uniforms; the matrix stack tops are uploaded here. void drawInstancedStatics(const std::string& instancedShaderName, InstancedTexture textureKind, const std::function& setUniforms = {}); void loadTeleportZones(const std::string& jsonPath, const char* zipFile); void loadTriggerZones(const std::string& jsonPath, const char* zipFile); void loadPointLights(const std::string& jsonPath, const char* zipFile); void updateTriggerZones(const Eigen::Vector3f& playerPos); void captureAuxiliaryState(LocationGameState& out) const; void applyAuxiliaryState(const LocationGameState& in); PathVisualization pathVisualization; struct NpcCarPresentationSample { Eigen::Vector3f position = Eigen::Vector3f::Zero(); float rotation = 0.f; float wheelAngle = 0.f; float steeringAngle = 0.f; }; NpcCarPresentationSample previousNpcCarPresentation_; NpcCarPresentationSample currentNpcCarPresentation_; std::chrono::steady_clock::time_point lastNpcCarPresentationUpdate_; bool hasNpcCarPresentationSample_ = false; void captureNpcCarPresentation(const NpcCar& car); NpcCarPresentationSample interpolatedNpcCarPresentation(const NpcCar& car) const; }; } // namespace FRG