#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 "ScriptEngine.h" #include "dialogue/DialogueSystem.h" #include "SparkEmitter.h" #include "TeleportZone.h" #include #include #include namespace ZL { 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 }; struct LocationSetup { std::string gameObjectsJsonPath; std::string interactiveObjectsJsonPath; std::string npcsJsonPath; std::string dialoguesJsonPath; std::vector navigationJsonPaths; std::string scriptPath; std::string teleportsJsonPath; std::string triggerZonesJsonPath; Eigen::Vector3f playerPosition = Eigen::Vector3f::Zero(); }; class Location { public: Location(Renderer& iRenderer, Inventory& iInventory); std::unordered_map gameObjects; std::vector interactiveObjects; std::unique_ptr player; std::vector> npcs; float cameraAzimuth = 0.0f; float cameraInclination = M_PI * 30.f / 180.f; std::vector navigationMaps; PathFinder* navigation = nullptr; int activeNavigationIndex = 0; std::unique_ptr shadowMap; Eigen::Matrix4f cameraViewMatrix = Eigen::Matrix4f::Identity(); InteractiveObject* targetInteractiveObject = nullptr; // "Walk to NPC, then fire on_npc_interact" — mirrors targetInteractiveObject // so a click from outside interactionRadius still leads to a Lua callback // once the player gets close enough. Character* targetInteractNpc = nullptr; int targetInteractNpcIndex = -1; std::unique_ptr npcNameText; ScriptEngine scriptEngine; Dialogue::DialogueSystem dialogueSystem; std::vector teleportZones; TeleportZone* targetTeleportZone = nullptr; std::function onTeleport; std::vector triggerZones; // Set by Game and kept in sync across location transitions. // Read by draw functions and raycast — do not write from Location code. bool isDarklands = false; // Set by Game after setup(). Lua and onDeathAnimComplete call this to // request the transition without coupling Location to Game directly. std::function requestDarklandsTransition; // Navigation editor — toggle with 'N', save with 'B', right-click to finalize polygon bool navigationEditorMode = false; std::vector navigationEditorPoints; VertexRenderStruct navigationEditorPointsMesh; std::vector navigationEditorNavMeshes; std::vector navigationMapPaths; int navigationEditorObstacleCounter = 0; void navigationEditorBuildNavMeshes(); void navigationEditorDrawNavigation(); void navigationEditorRebuildPointsMesh(); void navigationEditorDrawPoints(); void navigationEditorHandleLeftClick(const Eigen::Vector3f& hit, bool ctrlHeld); void navigationEditorHandleRightClick(); void navigationEditorSave(); void navigationEditorReload(); void gameObjectEditorPlaceTree(); void gameObjectEditorSave(); std::vector editorPlacedObjects; int editorPlacedObjectCounter = 0; // 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; void setup(const LocationSetup& params); void setupNavigation(const std::vector& paths); bool switchNavigation(int index); 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(); bool setNavigationAreaAvailable(const std::string& areaName, bool available); void update(int64_t deltaMs); 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: Renderer& renderer; Inventory& inventory; private: void resolveCharacterCollisions(); void updateDynamicReplans(int64_t deltaMs); void loadTeleportZones(const std::string& jsonPath, const char* zipFile); void loadTriggerZones(const std::string& jsonPath, const char* zipFile); void updateTriggerZones(const Eigen::Vector3f& playerPos); void nudgeCharacterAside(Character* standing, const Eigen::Vector3f& awayFrom); int findNpcIndex(const Character* npc) const; void fireBumpCallbacks(Character* mover, Character* standing); std::unordered_map lastCharacterPositions; std::unordered_map replanCooldownRemainingMs; static constexpr float NPC_BUMP_CALLBACK_COOLDOWN = 5.0f; std::unordered_map npcBumpedByPlayerCooldown; std::unordered_map npcBumpsPlayerCooldown; }; } // namespace ZL