#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 "LocationEditor.h" #include #include #include namespace ZL { 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 }; 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; std::string lightsJsonPath; 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 = -2.35; float cameraInclination = 1.1036;//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; std::vector pointLights; // 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; bool isNight = false; bool isDawn = false; bool tutorialInteractiveObjectsLocked = false; // 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 HUD // from step12 to step13 (trigger-zone encounter hint). std::function requestAdvanceDarklandsHud; std::function requestClosePhone; std::function requestReturnToMainMenu; std::function requestSetChatUnread; // 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; void setup(const LocationSetup& params, Quest::QuestJournal* journal); 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(); void drawGameNight(); void drawNightShadowDepthPass(const PointLight& light); 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: friend class LocationEditor; 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 loadPointLights(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; std::unordered_map stuckFrameCounts; std::unordered_map stuckPauseRemainingS; static constexpr float NPC_BUMP_CALLBACK_COOLDOWN = 5.0f; std::unordered_map npcBumpedByPlayerCooldown; std::unordered_map npcBumpsPlayerCooldown; }; } // namespace ZL