From 8214342248a42463e486e22703d54831e34ae4d8 Mon Sep 17 00:00:00 2001 From: Vladislav Khorev Date: Thu, 7 May 2026 20:53:20 +0300 Subject: [PATCH] Working on teleports --- proj-web/CMakeLists.txt | 2 + proj-windows/CMakeLists.txt | 2 + resources/config2/teleports.json | 17 +++ resources/config2/teleports2.json | 30 +++++ resources/config2/teleports_dorm.json | 30 +++++ resources/dialogue/sample_dialogues.json | 6 +- resources/ghost_avatar.png | 3 - resources/w/star_red.png | 3 + src/Character.h | 3 +- src/Game.cpp | 80 +++++------ src/Game.h | 5 +- src/Location.cpp | 162 ++++++++++++++--------- src/Location.h | 16 +-- src/TeleportZone.cpp | 43 ++++++ src/TeleportZone.h | 27 ++++ 15 files changed, 298 insertions(+), 131 deletions(-) create mode 100644 resources/config2/teleports.json create mode 100644 resources/config2/teleports2.json create mode 100644 resources/config2/teleports_dorm.json delete mode 100644 resources/ghost_avatar.png create mode 100644 resources/w/star_red.png create mode 100644 src/TeleportZone.cpp create mode 100644 src/TeleportZone.h diff --git a/proj-web/CMakeLists.txt b/proj-web/CMakeLists.txt index 1911fbd..4f0959a 100644 --- a/proj-web/CMakeLists.txt +++ b/proj-web/CMakeLists.txt @@ -83,6 +83,8 @@ set(SOURCES ../src/utils/Utils.h ../src/SparkEmitter.cpp ../src/SparkEmitter.h + ../src/TeleportZone.h + ../src/TeleportZone.cpp ../src/utils/TaskManager.cpp ../src/utils/TaskManager.h ../src/render/FrameBuffer.cpp diff --git a/proj-windows/CMakeLists.txt b/proj-windows/CMakeLists.txt index 0668b01..4093255 100644 --- a/proj-windows/CMakeLists.txt +++ b/proj-windows/CMakeLists.txt @@ -40,6 +40,8 @@ add_executable(witcher001 ../src/utils/Utils.h ../src/SparkEmitter.cpp ../src/SparkEmitter.h + ../src/TeleportZone.h + ../src/TeleportZone.cpp ../src/utils/TaskManager.cpp ../src/utils/TaskManager.h ../src/render/FrameBuffer.cpp diff --git a/resources/config2/teleports.json b/resources/config2/teleports.json new file mode 100644 index 0000000..de61362 --- /dev/null +++ b/resources/config2/teleports.json @@ -0,0 +1,17 @@ +{ + "teleports": [ + { + "id": "tp_loc1_to_loc2", + "positionX": 2.64621, + "positionY": 0.0, + "positionZ": -9.37259, + "radius": 1.5, + "active": true, + "destinationLocation": "location2", + "destinationPositionX": 8.2, + "destinationPositionY": 0.0, + "destinationPositionZ": -9.9, + "destinationRotationY": 0.0 + } + ] +} diff --git a/resources/config2/teleports2.json b/resources/config2/teleports2.json new file mode 100644 index 0000000..73cd6e9 --- /dev/null +++ b/resources/config2/teleports2.json @@ -0,0 +1,30 @@ +{ + "teleports": [ + { + "id": "tp_loc2_to_loc1", + "positionX": 8.2, + "positionY": 0.0, + "positionZ": -9.9, + "radius": 1.5, + "active": true, + "destinationLocation": "location1", + "destinationPositionX": 2.64621, + "destinationPositionY": 0.0, + "destinationPositionZ": -9.37259, + "destinationRotationY": -1.5708 + }, + { + "id": "tp_loc2_to_dorm", + "positionX": -21.7327, + "positionY": 0.0, + "positionZ": -34.1036, + "radius": 2.5, + "active": true, + "destinationLocation": "location_dorm", + "destinationPositionX": -8.32343, + "destinationPositionY": 0.0, + "destinationPositionZ": -0.152264, + "destinationRotationY": 1.5708 + } + ] +} diff --git a/resources/config2/teleports_dorm.json b/resources/config2/teleports_dorm.json new file mode 100644 index 0000000..b4f78e0 --- /dev/null +++ b/resources/config2/teleports_dorm.json @@ -0,0 +1,30 @@ +{ + "teleports": [ + { + "id": "tp_dorm_to_loc2", + "positionX": -8.32343, + "positionY": 0.0, + "positionZ": -0.152264, + "radius": 1.5, + "active": true, + "destinationLocation": "location2", + "destinationPositionX": -21.7327, + "destinationPositionY": 0.0, + "destinationPositionZ": -34.1036, + "destinationRotationY": 3.14159 + }, + { + "id": "tp_dorm_to_2floor", + "positionX": 3.95908, + "positionY": 0.0, + "positionZ": -3.06444, + "radius": 1.5, + "active": false, + "destinationLocation": "NONE", + "destinationPositionX": 0.0, + "destinationPositionY": 0.0, + "destinationPositionZ": 0.0, + "destinationRotationY": 0.0 + } + ] +} diff --git a/resources/dialogue/sample_dialogues.json b/resources/dialogue/sample_dialogues.json index 2f875b0..d31ce4d 100644 --- a/resources/dialogue/sample_dialogues.json +++ b/resources/dialogue/sample_dialogues.json @@ -189,7 +189,7 @@ "id": "line_goods", "type": "Line", "speaker": "Беспокойный Призрак", - "portrait": "resources/avatar_ghost.png", + "portrait": "resources/w/avatar_ghost.png", "text": "Это моя месть студентам за то что они призвали меня.", "next": "end_1" }, @@ -197,7 +197,7 @@ "id": "line_who", "type": "Line", "speaker": "Беспокойный Призрак", - "portrait": "resources/avatar_ghost.png", + "portrait": "resources/w/avatar_ghost.png", "text": "Группа студентов совершила ритуал и призвала меня сюда. Пока проклятие не спадет, я всегда буду здесь обитать.", "next": "choice_1" }, @@ -375,7 +375,7 @@ }, { "speaker": "Ghost", - "portrait": "resources/ghost_avatar.png", + "portrait": "resources/w/avatar_ghost.png", "text": "Some memories never fade.", "durationMs": 2600, "background": "resources/loading.png" diff --git a/resources/ghost_avatar.png b/resources/ghost_avatar.png deleted file mode 100644 index b8f3200..0000000 --- a/resources/ghost_avatar.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1839586121d9d513ed0aea18f5b8899d5f79ee7ada455ec3e47aa6c1a4d9986b -size 736341 diff --git a/resources/w/star_red.png b/resources/w/star_red.png new file mode 100644 index 0000000..4793ca3 --- /dev/null +++ b/resources/w/star_red.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:36ae862e62877103c564169224582f5c4f868ce070620513f75782bcbe1d5c1e +size 1081 diff --git a/src/Character.h b/src/Character.h index 1fbd6ab..31fd6fe 100644 --- a/src/Character.h +++ b/src/Character.h @@ -96,6 +96,8 @@ public: // Public: read by Game for camera tracking and ray-cast origin Eigen::Vector3f position = Eigen::Vector3f(0.f, 0.f, 0.f); float facingAngle = 0.0f; + float targetFacingAngle = 0.0f; + // Per-character tuning — set after construction, before first update float walkSpeed = 3.0f; @@ -159,7 +161,6 @@ private: std::vector pathWaypoints; size_t currentWaypointIndex = 0; PathPlanner pathPlanner; - float targetFacingAngle = 0.0f; std::function onArrivedCallback; static constexpr float WALK_THRESHOLD = 0.05f; diff --git a/src/Game.cpp b/src/Game.cpp index f6a9c76..f912964 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -165,27 +165,25 @@ namespace ZL params1.npcsJsonPath = "resources/config2/npcs.json"; params1.dialoguesJsonPath = "resources/dialogue/sample_dialogues.json"; params1.navigationJsonPath = "resources/config2/navigation.json"; + params1.teleportsJsonPath = "resources/config2/teleports.json"; params1.scriptPath = "resources/start.lua"; params1.playerPosition = Eigen::Vector3f(0.942694, 0, -9.63104); - params1.teleportPosition = Eigen::Vector3f(2.64621, 0, -9.37259); - params1.teleportRadius = 1.5f; - location1 = std::make_shared(renderer, inventory); - location1->setup(params1); + locations["location1"] = std::make_shared(renderer, inventory); + locations["location1"]->setup(params1); LocationSetup params2 = params1; params2.roomMeshPath = "resources/w/exterior/Segmented_Plane002.txt"; params2.roomTexturePath = "resources/w/exterior/Segmented_Plane002.png"; params2.gameObjectsJsonPath = "resources/config2/gameobjects2.json"; params2.navigationJsonPath = "resources/config2/navigation2.json"; + params2.teleportsJsonPath = "resources/config2/teleports2.json"; params2.scriptPath = "resources/start2.lua"; params2.playerPosition = Eigen::Vector3f(5, 0, -18.4); - params2.npcsJsonPath = "resources/config2/npcs2.json"; - params2.teleportPosition = Eigen::Vector3f(8.2, 0, -9.9); - params2.teleportRadius = 1.5f; - location2 = std::make_shared(renderer, inventory); - location2->setup(params2); + + locations["location2"] = std::make_shared(renderer, inventory); + locations["location2"]->setup(params2); LocationSetup params_dorm; params_dorm.roomMeshPath = ""; @@ -194,48 +192,38 @@ namespace ZL params_dorm.npcsJsonPath = "resources/config2/npcs_dorm.json"; params_dorm.dialoguesJsonPath = "resources/dialogue/sample_dialogues.json"; params_dorm.navigationJsonPath = "resources/config2/navigation_dorm.json"; + params_dorm.teleportsJsonPath = "resources/config2/teleports_dorm.json"; params_dorm.scriptPath = "resources/start_dorm.lua"; params_dorm.playerPosition = Eigen::Vector3f(6.0357, 0, -16.0581); - params_dorm.teleportPosition = Eigen::Vector3f(-8.32343, 0, -0.152264); - params_dorm.teleportRadius = 1.5f; - location_dorm = std::make_shared(renderer, inventory); - location_dorm->setup(params_dorm); + locations["location_dorm"] = std::make_shared(renderer, inventory); + locations["location_dorm"]->setup(params_dorm); - // Teleport callbacks: stepping into a location's zone hands the player - // to the other location at *its* teleport position. We pre-arm the - // destination's playerInTeleportZone so the player must walk out and - // back in before they can teleport again. - location1->onTeleport = [this]() { - std::cout << "[TELEPORT] location 1 -> location 2" << std::endl; - currentLocation = location2; - if (currentLocation->player) { - currentLocation->player->position = currentLocation->teleportPosition; - currentLocation->player->setTarget(currentLocation->teleportPosition); - } - currentLocation->playerInTeleportZone = true; - }; - location2->onTeleport = [this]() { - std::cout << "[TELEPORT] location 2 -> location 1" << std::endl; - currentLocation = location1; - if (currentLocation->player) { - currentLocation->player->position = currentLocation->teleportPosition; - currentLocation->player->setTarget(currentLocation->teleportPosition); - } - currentLocation->playerInTeleportZone = true; + // Teleport callbacks: destination name and position come from the teleport zone data. + auto makeTeleportCallback = [this](const std::string& sourceName) { + return [this, sourceName](const std::string& destName, const Eigen::Vector3f& destPos, float destRotY) { + std::cout << "[TELEPORT] " << sourceName << " -> " << destName << std::endl; + auto it = locations.find(destName); + if (it == locations.end()) { + std::cerr << "[TELEPORT] Unknown destination location: " << destName << std::endl; + return; + } + currentLocation = it->second; + if (currentLocation->player) { + currentLocation->player->position = destPos; + currentLocation->player->setTarget(destPos); + currentLocation->player->facingAngle = destRotY; + currentLocation->player->targetFacingAngle = destRotY; + } + currentLocation->cameraAzimuth = destRotY; }; + }; - location_dorm->onTeleport = [this]() { - std::cout << "[TELEPORT] location 2 -> location 1" << std::endl; - currentLocation = location2; - if (currentLocation->player) { - currentLocation->player->position = currentLocation->teleportPosition; - currentLocation->player->setTarget(currentLocation->teleportPosition); - } - currentLocation->playerInTeleportZone = true; - }; + locations["location1"]->onTeleport = makeTeleportCallback("location1"); + locations["location2"]->onTeleport = makeTeleportCallback("location2"); + locations["location_dorm"]->onTeleport = makeTeleportCallback("location_dorm"); - currentLocation = location_dorm; + currentLocation = locations["location_dorm"]; std::cout << "Load resurces step 5" << std::endl; @@ -540,8 +528,8 @@ namespace ZL break; case SDLK_p: - currentLocation = (currentLocation == location1) ? location2 : location1; - std::cout << "Switched to location " << ((currentLocation == location1) ? "1" : "2") << std::endl; + currentLocation = (currentLocation == locations["location1"]) ? locations["location2"] : locations["location1"]; + std::cout << "Switched to location " << ((currentLocation == locations["location1"]) ? "1" : "2") << std::endl; break; case SDLK_l: diff --git a/src/Game.h b/src/Game.h index 6eb0d55..320e8be 100644 --- a/src/Game.h +++ b/src/Game.h @@ -42,10 +42,7 @@ namespace ZL { VertexRenderStruct loadingMesh; bool loadingCompleted = false; - std::shared_ptr location1; - std::shared_ptr location2; - std::shared_ptr location_dorm; - + std::unordered_map> locations; std::shared_ptr currentLocation; Inventory inventory; diff --git a/src/Location.cpp b/src/Location.cpp index 516162d..0536f44 100644 --- a/src/Location.cpp +++ b/src/Location.cpp @@ -12,6 +12,7 @@ #include #include "GameConstants.h" #include "Character.h" +#include "external/nlohmann/json.hpp" namespace ZL @@ -133,38 +134,7 @@ namespace ZL } } - auto sparkTexture2 = renderer.textureManager.LoadFromPng("resources/w/star.png", CONST_ZIP_FILE); - - // Teleport zone visuals: an upward fountain of sparks parked at the - // teleport position, so the player can spot the zone from a distance. - teleportPosition = params.teleportPosition; - teleportRadius = params.teleportRadius; - if (teleportRadius > 0.0f) { - teleportSparks = std::make_unique(); - std::vector teleportEmitPoints; - teleportEmitPoints.push_back(Vector3f{ teleportPosition.x(), teleportPosition.y(), teleportPosition.z() }); - teleportSparks->setEmissionPoints(teleportEmitPoints); - teleportSparks->setTexture(sparkTexture2); - teleportSparks->setEmissionRate(50.0f); - teleportSparks->setMaxParticles(80); - teleportSparks->setParticleSize(0.05f); - teleportSparks->setBiasX(0.0f); - teleportSparks->setEmissionDirection(Vector3f{ 0.0f, 1.0f, 0.0f }); - teleportSparks->setEmissionRadius(teleportRadius); - teleportSparks->setSpeedRange(0.5f, 1.0f); - teleportSparks->setZSpeedRange(0.5f, 1.5f); - teleportSparks->setScaleRange(0.5f, 1.0f); - teleportSparks->setLifeTimeRange(1500.0f, 2500.0f); - teleportSparks->setUseWorldSpace(true); - teleportSparks->markConfigured(); - - - // If the player happens to spawn already inside the zone, treat them - // as in-zone so they don't immediately teleport on the first update. - if (player && (player->position - teleportPosition).norm() <= teleportRadius) { - playerInTeleportZone = true; - } - } + loadTeleportZones(params.teleportsJsonPath, CONST_ZIP_FILE); #ifndef EMSCRIPTEN // Create shadow map (2048x2048, ortho size 40, near 0.1, far 100) @@ -198,6 +168,65 @@ namespace ZL } + void Location::loadTeleportZones(const std::string& jsonPath, const char* zipFile) + { + if (jsonPath.empty()) return; + + std::string content; + try { + if (!zipFile || zipFile[0] == '\0') { + content = readTextFile(jsonPath); + } else { + auto buf = readFileFromZIP(jsonPath, zipFile); + if (buf.empty()) { + std::cerr << "[TELEPORT] Failed to read " << jsonPath << " from zip" << std::endl; + return; + } + content.assign(buf.begin(), buf.end()); + } + } catch (const std::exception& e) { + std::cerr << "[TELEPORT] Failed to open " << jsonPath << ": " << e.what() << std::endl; + return; + } + + using json = nlohmann::json; + json j; + try { + j = json::parse(content); + } catch (const std::exception& e) { + std::cerr << "[TELEPORT] JSON parse error in " << jsonPath << ": " << e.what() << std::endl; + return; + } + + if (!j.contains("teleports") || !j["teleports"].is_array()) return; + + auto activeTex = renderer.textureManager.LoadFromPng("resources/w/star.png", zipFile ? zipFile : ""); + auto inactiveTex = renderer.textureManager.LoadFromPng("resources/w/star_red.png", zipFile ? zipFile : ""); + + for (const auto& item : j["teleports"]) { + TeleportZone tz; + tz.id = item.value("id", ""); + tz.active = item.value("active", false); + tz.position = Eigen::Vector3f( + item.value("positionX", 0.0f), + item.value("positionY", 0.0f), + item.value("positionZ", 0.0f) + ); + tz.radius = item.value("radius", 0.0f); + tz.destinationLocation = item.value("destinationLocation", ""); + tz.destinationPosition = Eigen::Vector3f( + item.value("destinationPositionX", 0.0f), + item.value("destinationPositionY", 0.0f), + item.value("destinationPositionZ", 0.0f) + ); + tz.destinationRotationY = item.value("destinationRotationY", 0.0f); + tz.initSparks(activeTex, inactiveTex); + teleportZones.push_back(std::move(tz)); + } + + std::cout << "[TELEPORT] Loaded " << teleportZones.size() << " teleport(s) from " << jsonPath << std::endl; + } + void Location::setupNavigation(const std::string& navigationJsonPath) { // Static navigation blockers are defined in the navigation JSON as polygons. @@ -447,14 +476,12 @@ namespace ZL const Eigen::Matrix4f currentView = renderer.GetCurrentModelViewMatrix(); if (player) player->prepareHitSparksForDraw(currentView); for (auto& npc : npcs) npc->prepareHitSparksForDraw(currentView); - if (teleportSparks) teleportSparks->prepareForDraw(currentView); + for (auto& tz : teleportZones) tz.prepareForDraw(currentView); if (player) player->draw(renderer); for (auto& npc : npcs) npc->draw(renderer); - if (teleportSparks) { - teleportSparks->draw(renderer, Environment::zoom, Environment::width, Environment::height); - } + for (auto& tz : teleportZones) tz.draw(renderer, Environment::zoom, Environment::width, Environment::height); #ifdef SHOW_PATH drawDebugNavigation(); @@ -641,16 +668,14 @@ namespace ZL if (player) player->prepareHitSparksForDraw(cameraViewMatrix); for (auto& npc : npcs) npc->prepareHitSparksForDraw(cameraViewMatrix); - if (teleportSparks) teleportSparks->prepareForDraw(cameraViewMatrix); + for (auto& tz : teleportZones) tz.prepareForDraw(cameraViewMatrix); if (player) player->drawWithShadow(renderer, lightFromCamera, shadowMap->getDepthTexture(), lightDirCamera); CheckGlError(__FILE__, __LINE__); for (auto& npc : npcs) npc->drawWithShadow(renderer, lightFromCamera, shadowMap->getDepthTexture(), lightDirCamera); - if (teleportSparks) { - teleportSparks->draw(renderer, Environment::zoom, Environment::width, Environment::height); - } + for (auto& tz : teleportZones) tz.draw(renderer, Environment::zoom, Environment::width, Environment::height); #endif CheckGlError(__FILE__, __LINE__); @@ -889,26 +914,15 @@ namespace ZL } } - // Drive teleport spark animation regardless of whether the zone is armed. - if (teleportSparks) { - teleportSparks->update(static_cast(delta)); - } + for (auto& tz : teleportZones) tz.update(static_cast(delta)); - // Teleport zone: rising-edge trigger only. The destination location's - // callback is responsible for placing the player back inside its own - // zone and setting playerInTeleportZone = true so we don't bounce. - if (player && onTeleport && teleportRadius > 0.0f) { - float distToZone = (player->position - teleportPosition).norm(); - if (distToZone <= teleportRadius) { - if (!playerInTeleportZone) { - playerInTeleportZone = true; - std::cout << "[TELEPORT] Player entered teleport zone" << std::endl; - onTeleport(); - return; // active location may have changed - } - } - else { - playerInTeleportZone = false; + if (targetTeleportZone && player) { + float dist = (player->position - targetTeleportZone->position).norm(); + if (dist <= targetTeleportZone->radius) { + std::cout << "[TELEPORT] Player reached teleport zone '" << targetTeleportZone->id << "'" << std::endl; + if (onTeleport) onTeleport(targetTeleportZone->destinationLocation, targetTeleportZone->destinationPosition, targetTeleportZone->destinationRotationY); + targetTeleportZone = nullptr; + return; } } } @@ -955,6 +969,7 @@ namespace ZL targetInteractiveObject = clickedObject; targetInteractNpc = nullptr; targetInteractNpcIndex = -1; + targetTeleportZone = nullptr; player->setTarget(clickedObject->position); player->attackTarget = nullptr; std::cout << "[CLICK] Player moving to object..." << std::endl; @@ -974,6 +989,7 @@ namespace ZL } if (npcIndex != -1) { targetInteractiveObject = nullptr; + targetTeleportZone = nullptr; if (clickedNpc->canAttack) { // Hostile NPC: combat logic walks the player in via attackTarget; @@ -1012,15 +1028,33 @@ namespace ZL } else if (rayDir.y() < -0.001f && player) { - // Otherwise, unproject click to ground plane for Viola's walk target + // Unproject click to ground plane for walk target / teleport detection float t = -camPos.y() / rayDir.y(); Eigen::Vector3f hit = camPos + rayDir * t; std::cout << "[CLICK] Clicked on ground at: (" << hit.x() << ", " << hit.z() << ")" << std::endl; - player->setTarget(Eigen::Vector3f(hit.x(), 0.f, hit.z())); - player->attackTarget = nullptr; - targetInteractNpc = nullptr; - targetInteractNpcIndex = -1; + TeleportZone* clickedTeleport = nullptr; + for (auto& tz : teleportZones) { + if (!tz.active) continue; + Eigen::Vector2f d(hit.x() - tz.position.x(), hit.z() - tz.position.z()); + if (d.norm() <= tz.radius) { clickedTeleport = &tz; break; } + } + + if (clickedTeleport) { + std::cout << "[CLICK] Clicked teleport zone '" << clickedTeleport->id << "'" << std::endl; + targetTeleportZone = clickedTeleport; + targetInteractiveObject = nullptr; + targetInteractNpc = nullptr; + targetInteractNpcIndex = -1; + player->setTarget(clickedTeleport->position); + player->attackTarget = nullptr; + } else { + player->setTarget(Eigen::Vector3f(hit.x(), 0.f, hit.z())); + player->attackTarget = nullptr; + targetInteractNpc = nullptr; + targetInteractNpcIndex = -1; + targetTeleportZone = nullptr; + } } else { std::cout << "[CLICK] No valid target found" << std::endl; diff --git a/src/Location.h b/src/Location.h index 7b8649e..9f6ce5a 100644 --- a/src/Location.h +++ b/src/Location.h @@ -12,6 +12,7 @@ #include "ScriptEngine.h" #include "dialogue/DialogueSystem.h" #include "SparkEmitter.h" +#include "TeleportZone.h" #include #include #include @@ -27,9 +28,8 @@ namespace ZL std::string dialoguesJsonPath; std::string navigationJsonPath; std::string scriptPath; + std::string teleportsJsonPath; Eigen::Vector3f playerPosition = Eigen::Vector3f::Zero(); - Eigen::Vector3f teleportPosition = Eigen::Vector3f::Zero(); - float teleportRadius = 0.0f; }; class Location @@ -69,14 +69,9 @@ namespace ZL ScriptEngine scriptEngine; Dialogue::DialogueSystem dialogueSystem; - // Teleport zone: when the player crosses into the radius of teleportPosition, - // onTeleport() is invoked once. Re-triggering requires leaving the zone first - // (rising-edge detection via playerInTeleportZone). - Eigen::Vector3f teleportPosition = Eigen::Vector3f::Zero(); - float teleportRadius = 0.0f; - bool playerInTeleportZone = false; - std::function onTeleport; - std::unique_ptr teleportSparks; + std::vector teleportZones; + TeleportZone* targetTeleportZone = nullptr; + std::function onTeleport; #ifdef SHOW_PATH std::vector debugNavMeshes; @@ -118,6 +113,7 @@ namespace ZL private: void resolveCharacterCollisions(); void updateDynamicReplans(int64_t deltaMs); + void loadTeleportZones(const std::string& jsonPath, const char* zipFile); std::unordered_map lastCharacterPositions; std::unordered_map replanCooldownRemainingMs; diff --git a/src/TeleportZone.cpp b/src/TeleportZone.cpp new file mode 100644 index 0000000..21db1a1 --- /dev/null +++ b/src/TeleportZone.cpp @@ -0,0 +1,43 @@ +#include "TeleportZone.h" +#include "render/Renderer.h" +#include "render/TextureManager.h" + +namespace ZL { + +void TeleportZone::initSparks(std::shared_ptr activeTex, std::shared_ptr inactiveTex) +{ + sparks = std::make_unique(); + std::vector emitPoints; + emitPoints.push_back(Vector3f{ position.x(), position.y(), position.z() }); + sparks->setEmissionPoints(emitPoints); + sparks->setTexture(active ? activeTex : inactiveTex); + sparks->setEmissionRate(50.0f); + sparks->setMaxParticles(80); + sparks->setParticleSize(0.05f); + sparks->setBiasX(0.0f); + sparks->setEmissionDirection(Vector3f{ 0.0f, 1.0f, 0.0f }); + sparks->setEmissionRadius(radius); + sparks->setSpeedRange(0.5f, 1.0f); + sparks->setZSpeedRange(0.5f, 1.5f); + sparks->setScaleRange(0.5f, 1.0f); + sparks->setLifeTimeRange(1500.0f, 2500.0f); + sparks->setUseWorldSpace(true); + sparks->markConfigured(); +} + +void TeleportZone::update(float deltaMs) +{ + if (sparks) sparks->update(deltaMs); +} + +void TeleportZone::prepareForDraw(const Eigen::Matrix4f& viewMatrix) +{ + if (sparks) sparks->prepareForDraw(viewMatrix); +} + +void TeleportZone::draw(Renderer& renderer, float zoom, int width, int height) +{ + if (sparks) sparks->draw(renderer, zoom, width, height); +} + +} // namespace ZL diff --git a/src/TeleportZone.h b/src/TeleportZone.h new file mode 100644 index 0000000..23bf882 --- /dev/null +++ b/src/TeleportZone.h @@ -0,0 +1,27 @@ +#pragma once +#include +#include +#include +#include "SparkEmitter.h" + +namespace ZL { +class Renderer; +class Texture; + +struct TeleportZone { + std::string id; + Eigen::Vector3f position = Eigen::Vector3f::Zero(); + float radius = 0.0f; + bool active = false; + std::string destinationLocation; + Eigen::Vector3f destinationPosition = Eigen::Vector3f::Zero(); + float destinationRotationY = 0.0f; + std::unique_ptr sparks; + + void initSparks(std::shared_ptr activeTex, std::shared_ptr inactiveTex); + void update(float deltaMs); + void prepareForDraw(const Eigen::Matrix4f& viewMatrix); + void draw(Renderer& renderer, float zoom, int width, int height); +}; + +} // namespace ZL