Working on teleports
This commit is contained in:
parent
7e94009217
commit
8214342248
@ -83,6 +83,8 @@ set(SOURCES
|
|||||||
../src/utils/Utils.h
|
../src/utils/Utils.h
|
||||||
../src/SparkEmitter.cpp
|
../src/SparkEmitter.cpp
|
||||||
../src/SparkEmitter.h
|
../src/SparkEmitter.h
|
||||||
|
../src/TeleportZone.h
|
||||||
|
../src/TeleportZone.cpp
|
||||||
../src/utils/TaskManager.cpp
|
../src/utils/TaskManager.cpp
|
||||||
../src/utils/TaskManager.h
|
../src/utils/TaskManager.h
|
||||||
../src/render/FrameBuffer.cpp
|
../src/render/FrameBuffer.cpp
|
||||||
|
|||||||
@ -40,6 +40,8 @@ add_executable(witcher001
|
|||||||
../src/utils/Utils.h
|
../src/utils/Utils.h
|
||||||
../src/SparkEmitter.cpp
|
../src/SparkEmitter.cpp
|
||||||
../src/SparkEmitter.h
|
../src/SparkEmitter.h
|
||||||
|
../src/TeleportZone.h
|
||||||
|
../src/TeleportZone.cpp
|
||||||
../src/utils/TaskManager.cpp
|
../src/utils/TaskManager.cpp
|
||||||
../src/utils/TaskManager.h
|
../src/utils/TaskManager.h
|
||||||
../src/render/FrameBuffer.cpp
|
../src/render/FrameBuffer.cpp
|
||||||
|
|||||||
17
resources/config2/teleports.json
Normal file
17
resources/config2/teleports.json
Normal file
@ -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
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
30
resources/config2/teleports2.json
Normal file
30
resources/config2/teleports2.json
Normal file
@ -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
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
30
resources/config2/teleports_dorm.json
Normal file
30
resources/config2/teleports_dorm.json
Normal file
@ -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
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -189,7 +189,7 @@
|
|||||||
"id": "line_goods",
|
"id": "line_goods",
|
||||||
"type": "Line",
|
"type": "Line",
|
||||||
"speaker": "Беспокойный Призрак",
|
"speaker": "Беспокойный Призрак",
|
||||||
"portrait": "resources/avatar_ghost.png",
|
"portrait": "resources/w/avatar_ghost.png",
|
||||||
"text": "Это моя месть студентам за то что они призвали меня.",
|
"text": "Это моя месть студентам за то что они призвали меня.",
|
||||||
"next": "end_1"
|
"next": "end_1"
|
||||||
},
|
},
|
||||||
@ -197,7 +197,7 @@
|
|||||||
"id": "line_who",
|
"id": "line_who",
|
||||||
"type": "Line",
|
"type": "Line",
|
||||||
"speaker": "Беспокойный Призрак",
|
"speaker": "Беспокойный Призрак",
|
||||||
"portrait": "resources/avatar_ghost.png",
|
"portrait": "resources/w/avatar_ghost.png",
|
||||||
"text": "Группа студентов совершила ритуал и призвала меня сюда. Пока проклятие не спадет, я всегда буду здесь обитать.",
|
"text": "Группа студентов совершила ритуал и призвала меня сюда. Пока проклятие не спадет, я всегда буду здесь обитать.",
|
||||||
"next": "choice_1"
|
"next": "choice_1"
|
||||||
},
|
},
|
||||||
@ -375,7 +375,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"speaker": "Ghost",
|
"speaker": "Ghost",
|
||||||
"portrait": "resources/ghost_avatar.png",
|
"portrait": "resources/w/avatar_ghost.png",
|
||||||
"text": "Some memories never fade.",
|
"text": "Some memories never fade.",
|
||||||
"durationMs": 2600,
|
"durationMs": 2600,
|
||||||
"background": "resources/loading.png"
|
"background": "resources/loading.png"
|
||||||
|
|||||||
BIN
resources/ghost_avatar.png
(Stored with Git LFS)
BIN
resources/ghost_avatar.png
(Stored with Git LFS)
Binary file not shown.
BIN
resources/w/star_red.png
(Stored with Git LFS)
Normal file
BIN
resources/w/star_red.png
(Stored with Git LFS)
Normal file
Binary file not shown.
@ -96,6 +96,8 @@ public:
|
|||||||
// Public: read by Game for camera tracking and ray-cast origin
|
// Public: read by Game for camera tracking and ray-cast origin
|
||||||
Eigen::Vector3f position = Eigen::Vector3f(0.f, 0.f, 0.f);
|
Eigen::Vector3f position = Eigen::Vector3f(0.f, 0.f, 0.f);
|
||||||
float facingAngle = 0.0f;
|
float facingAngle = 0.0f;
|
||||||
|
float targetFacingAngle = 0.0f;
|
||||||
|
|
||||||
|
|
||||||
// Per-character tuning — set after construction, before first update
|
// Per-character tuning — set after construction, before first update
|
||||||
float walkSpeed = 3.0f;
|
float walkSpeed = 3.0f;
|
||||||
@ -159,7 +161,6 @@ private:
|
|||||||
std::vector<Eigen::Vector3f> pathWaypoints;
|
std::vector<Eigen::Vector3f> pathWaypoints;
|
||||||
size_t currentWaypointIndex = 0;
|
size_t currentWaypointIndex = 0;
|
||||||
PathPlanner pathPlanner;
|
PathPlanner pathPlanner;
|
||||||
float targetFacingAngle = 0.0f;
|
|
||||||
std::function<void()> onArrivedCallback;
|
std::function<void()> onArrivedCallback;
|
||||||
|
|
||||||
static constexpr float WALK_THRESHOLD = 0.05f;
|
static constexpr float WALK_THRESHOLD = 0.05f;
|
||||||
|
|||||||
76
src/Game.cpp
76
src/Game.cpp
@ -165,27 +165,25 @@ namespace ZL
|
|||||||
params1.npcsJsonPath = "resources/config2/npcs.json";
|
params1.npcsJsonPath = "resources/config2/npcs.json";
|
||||||
params1.dialoguesJsonPath = "resources/dialogue/sample_dialogues.json";
|
params1.dialoguesJsonPath = "resources/dialogue/sample_dialogues.json";
|
||||||
params1.navigationJsonPath = "resources/config2/navigation.json";
|
params1.navigationJsonPath = "resources/config2/navigation.json";
|
||||||
|
params1.teleportsJsonPath = "resources/config2/teleports.json";
|
||||||
params1.scriptPath = "resources/start.lua";
|
params1.scriptPath = "resources/start.lua";
|
||||||
params1.playerPosition = Eigen::Vector3f(0.942694, 0, -9.63104);
|
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<Location>(renderer, inventory);
|
locations["location1"] = std::make_shared<Location>(renderer, inventory);
|
||||||
location1->setup(params1);
|
locations["location1"]->setup(params1);
|
||||||
|
|
||||||
LocationSetup params2 = params1;
|
LocationSetup params2 = params1;
|
||||||
params2.roomMeshPath = "resources/w/exterior/Segmented_Plane002.txt";
|
params2.roomMeshPath = "resources/w/exterior/Segmented_Plane002.txt";
|
||||||
params2.roomTexturePath = "resources/w/exterior/Segmented_Plane002.png";
|
params2.roomTexturePath = "resources/w/exterior/Segmented_Plane002.png";
|
||||||
params2.gameObjectsJsonPath = "resources/config2/gameobjects2.json";
|
params2.gameObjectsJsonPath = "resources/config2/gameobjects2.json";
|
||||||
params2.navigationJsonPath = "resources/config2/navigation2.json";
|
params2.navigationJsonPath = "resources/config2/navigation2.json";
|
||||||
|
params2.teleportsJsonPath = "resources/config2/teleports2.json";
|
||||||
params2.scriptPath = "resources/start2.lua";
|
params2.scriptPath = "resources/start2.lua";
|
||||||
params2.playerPosition = Eigen::Vector3f(5, 0, -18.4);
|
params2.playerPosition = Eigen::Vector3f(5, 0, -18.4);
|
||||||
|
|
||||||
params2.npcsJsonPath = "resources/config2/npcs2.json";
|
params2.npcsJsonPath = "resources/config2/npcs2.json";
|
||||||
params2.teleportPosition = Eigen::Vector3f(8.2, 0, -9.9);
|
|
||||||
params2.teleportRadius = 1.5f;
|
locations["location2"] = std::make_shared<Location>(renderer, inventory);
|
||||||
location2 = std::make_shared<Location>(renderer, inventory);
|
locations["location2"]->setup(params2);
|
||||||
location2->setup(params2);
|
|
||||||
|
|
||||||
LocationSetup params_dorm;
|
LocationSetup params_dorm;
|
||||||
params_dorm.roomMeshPath = "";
|
params_dorm.roomMeshPath = "";
|
||||||
@ -194,48 +192,38 @@ namespace ZL
|
|||||||
params_dorm.npcsJsonPath = "resources/config2/npcs_dorm.json";
|
params_dorm.npcsJsonPath = "resources/config2/npcs_dorm.json";
|
||||||
params_dorm.dialoguesJsonPath = "resources/dialogue/sample_dialogues.json";
|
params_dorm.dialoguesJsonPath = "resources/dialogue/sample_dialogues.json";
|
||||||
params_dorm.navigationJsonPath = "resources/config2/navigation_dorm.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.scriptPath = "resources/start_dorm.lua";
|
||||||
params_dorm.playerPosition = Eigen::Vector3f(6.0357, 0, -16.0581);
|
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<Location>(renderer, inventory);
|
locations["location_dorm"] = std::make_shared<Location>(renderer, inventory);
|
||||||
location_dorm->setup(params_dorm);
|
locations["location_dorm"]->setup(params_dorm);
|
||||||
|
|
||||||
// Teleport callbacks: stepping into a location's zone hands the player
|
// Teleport callbacks: destination name and position come from the teleport zone data.
|
||||||
// to the other location at *its* teleport position. We pre-arm the
|
auto makeTeleportCallback = [this](const std::string& sourceName) {
|
||||||
// destination's playerInTeleportZone so the player must walk out and
|
return [this, sourceName](const std::string& destName, const Eigen::Vector3f& destPos, float destRotY) {
|
||||||
// back in before they can teleport again.
|
std::cout << "[TELEPORT] " << sourceName << " -> " << destName << std::endl;
|
||||||
location1->onTeleport = [this]() {
|
auto it = locations.find(destName);
|
||||||
std::cout << "[TELEPORT] location 1 -> location 2" << std::endl;
|
if (it == locations.end()) {
|
||||||
currentLocation = location2;
|
std::cerr << "[TELEPORT] Unknown destination location: " << destName << std::endl;
|
||||||
if (currentLocation->player) {
|
return;
|
||||||
currentLocation->player->position = currentLocation->teleportPosition;
|
|
||||||
currentLocation->player->setTarget(currentLocation->teleportPosition);
|
|
||||||
}
|
}
|
||||||
currentLocation->playerInTeleportZone = true;
|
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;
|
||||||
};
|
};
|
||||||
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;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
location_dorm->onTeleport = [this]() {
|
locations["location1"]->onTeleport = makeTeleportCallback("location1");
|
||||||
std::cout << "[TELEPORT] location 2 -> location 1" << std::endl;
|
locations["location2"]->onTeleport = makeTeleportCallback("location2");
|
||||||
currentLocation = location2;
|
locations["location_dorm"]->onTeleport = makeTeleportCallback("location_dorm");
|
||||||
if (currentLocation->player) {
|
|
||||||
currentLocation->player->position = currentLocation->teleportPosition;
|
|
||||||
currentLocation->player->setTarget(currentLocation->teleportPosition);
|
|
||||||
}
|
|
||||||
currentLocation->playerInTeleportZone = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
currentLocation = location_dorm;
|
currentLocation = locations["location_dorm"];
|
||||||
|
|
||||||
std::cout << "Load resurces step 5" << std::endl;
|
std::cout << "Load resurces step 5" << std::endl;
|
||||||
|
|
||||||
@ -540,8 +528,8 @@ namespace ZL
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case SDLK_p:
|
case SDLK_p:
|
||||||
currentLocation = (currentLocation == location1) ? location2 : location1;
|
currentLocation = (currentLocation == locations["location1"]) ? locations["location2"] : locations["location1"];
|
||||||
std::cout << "Switched to location " << ((currentLocation == location1) ? "1" : "2") << std::endl;
|
std::cout << "Switched to location " << ((currentLocation == locations["location1"]) ? "1" : "2") << std::endl;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SDLK_l:
|
case SDLK_l:
|
||||||
|
|||||||
@ -42,10 +42,7 @@ namespace ZL {
|
|||||||
VertexRenderStruct loadingMesh;
|
VertexRenderStruct loadingMesh;
|
||||||
bool loadingCompleted = false;
|
bool loadingCompleted = false;
|
||||||
|
|
||||||
std::shared_ptr<Location> location1;
|
std::unordered_map<std::string, std::shared_ptr<Location>> locations;
|
||||||
std::shared_ptr<Location> location2;
|
|
||||||
std::shared_ptr<Location> location_dorm;
|
|
||||||
|
|
||||||
std::shared_ptr<Location> currentLocation;
|
std::shared_ptr<Location> currentLocation;
|
||||||
|
|
||||||
Inventory inventory;
|
Inventory inventory;
|
||||||
|
|||||||
154
src/Location.cpp
154
src/Location.cpp
@ -12,6 +12,7 @@
|
|||||||
#include <cfloat>
|
#include <cfloat>
|
||||||
#include "GameConstants.h"
|
#include "GameConstants.h"
|
||||||
#include "Character.h"
|
#include "Character.h"
|
||||||
|
#include "external/nlohmann/json.hpp"
|
||||||
|
|
||||||
|
|
||||||
namespace ZL
|
namespace ZL
|
||||||
@ -133,38 +134,7 @@ namespace ZL
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto sparkTexture2 = renderer.textureManager.LoadFromPng("resources/w/star.png", CONST_ZIP_FILE);
|
loadTeleportZones(params.teleportsJsonPath, 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<SparkEmitter>();
|
|
||||||
std::vector<Vector3f> 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifndef EMSCRIPTEN
|
#ifndef EMSCRIPTEN
|
||||||
// Create shadow map (2048x2048, ortho size 40, near 0.1, far 100)
|
// 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)
|
void Location::setupNavigation(const std::string& navigationJsonPath)
|
||||||
{
|
{
|
||||||
// Static navigation blockers are defined in the navigation JSON as polygons.
|
// Static navigation blockers are defined in the navigation JSON as polygons.
|
||||||
@ -447,14 +476,12 @@ namespace ZL
|
|||||||
const Eigen::Matrix4f currentView = renderer.GetCurrentModelViewMatrix();
|
const Eigen::Matrix4f currentView = renderer.GetCurrentModelViewMatrix();
|
||||||
if (player) player->prepareHitSparksForDraw(currentView);
|
if (player) player->prepareHitSparksForDraw(currentView);
|
||||||
for (auto& npc : npcs) npc->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);
|
if (player) player->draw(renderer);
|
||||||
for (auto& npc : npcs) npc->draw(renderer);
|
for (auto& npc : npcs) npc->draw(renderer);
|
||||||
|
|
||||||
if (teleportSparks) {
|
for (auto& tz : teleportZones) tz.draw(renderer, Environment::zoom, Environment::width, Environment::height);
|
||||||
teleportSparks->draw(renderer, Environment::zoom, Environment::width, Environment::height);
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef SHOW_PATH
|
#ifdef SHOW_PATH
|
||||||
drawDebugNavigation();
|
drawDebugNavigation();
|
||||||
@ -641,16 +668,14 @@ namespace ZL
|
|||||||
|
|
||||||
if (player) player->prepareHitSparksForDraw(cameraViewMatrix);
|
if (player) player->prepareHitSparksForDraw(cameraViewMatrix);
|
||||||
for (auto& npc : npcs) npc->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);
|
if (player) player->drawWithShadow(renderer, lightFromCamera, shadowMap->getDepthTexture(), lightDirCamera);
|
||||||
CheckGlError(__FILE__, __LINE__);
|
CheckGlError(__FILE__, __LINE__);
|
||||||
|
|
||||||
for (auto& npc : npcs) npc->drawWithShadow(renderer, lightFromCamera, shadowMap->getDepthTexture(), lightDirCamera);
|
for (auto& npc : npcs) npc->drawWithShadow(renderer, lightFromCamera, shadowMap->getDepthTexture(), lightDirCamera);
|
||||||
|
|
||||||
if (teleportSparks) {
|
for (auto& tz : teleportZones) tz.draw(renderer, Environment::zoom, Environment::width, Environment::height);
|
||||||
teleportSparks->draw(renderer, Environment::zoom, Environment::width, Environment::height);
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
CheckGlError(__FILE__, __LINE__);
|
CheckGlError(__FILE__, __LINE__);
|
||||||
|
|
||||||
@ -889,26 +914,15 @@ namespace ZL
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Drive teleport spark animation regardless of whether the zone is armed.
|
for (auto& tz : teleportZones) tz.update(static_cast<float>(delta));
|
||||||
if (teleportSparks) {
|
|
||||||
teleportSparks->update(static_cast<float>(delta));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Teleport zone: rising-edge trigger only. The destination location's
|
if (targetTeleportZone && player) {
|
||||||
// callback is responsible for placing the player back inside its own
|
float dist = (player->position - targetTeleportZone->position).norm();
|
||||||
// zone and setting playerInTeleportZone = true so we don't bounce.
|
if (dist <= targetTeleportZone->radius) {
|
||||||
if (player && onTeleport && teleportRadius > 0.0f) {
|
std::cout << "[TELEPORT] Player reached teleport zone '" << targetTeleportZone->id << "'" << std::endl;
|
||||||
float distToZone = (player->position - teleportPosition).norm();
|
if (onTeleport) onTeleport(targetTeleportZone->destinationLocation, targetTeleportZone->destinationPosition, targetTeleportZone->destinationRotationY);
|
||||||
if (distToZone <= teleportRadius) {
|
targetTeleportZone = nullptr;
|
||||||
if (!playerInTeleportZone) {
|
return;
|
||||||
playerInTeleportZone = true;
|
|
||||||
std::cout << "[TELEPORT] Player entered teleport zone" << std::endl;
|
|
||||||
onTeleport();
|
|
||||||
return; // active location may have changed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
playerInTeleportZone = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -955,6 +969,7 @@ namespace ZL
|
|||||||
targetInteractiveObject = clickedObject;
|
targetInteractiveObject = clickedObject;
|
||||||
targetInteractNpc = nullptr;
|
targetInteractNpc = nullptr;
|
||||||
targetInteractNpcIndex = -1;
|
targetInteractNpcIndex = -1;
|
||||||
|
targetTeleportZone = nullptr;
|
||||||
player->setTarget(clickedObject->position);
|
player->setTarget(clickedObject->position);
|
||||||
player->attackTarget = nullptr;
|
player->attackTarget = nullptr;
|
||||||
std::cout << "[CLICK] Player moving to object..." << std::endl;
|
std::cout << "[CLICK] Player moving to object..." << std::endl;
|
||||||
@ -974,6 +989,7 @@ namespace ZL
|
|||||||
}
|
}
|
||||||
if (npcIndex != -1) {
|
if (npcIndex != -1) {
|
||||||
targetInteractiveObject = nullptr;
|
targetInteractiveObject = nullptr;
|
||||||
|
targetTeleportZone = nullptr;
|
||||||
|
|
||||||
if (clickedNpc->canAttack) {
|
if (clickedNpc->canAttack) {
|
||||||
// Hostile NPC: combat logic walks the player in via attackTarget;
|
// Hostile NPC: combat logic walks the player in via attackTarget;
|
||||||
@ -1012,15 +1028,33 @@ namespace ZL
|
|||||||
|
|
||||||
}
|
}
|
||||||
else if (rayDir.y() < -0.001f && player) {
|
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();
|
float t = -camPos.y() / rayDir.y();
|
||||||
Eigen::Vector3f hit = camPos + rayDir * t;
|
Eigen::Vector3f hit = camPos + rayDir * t;
|
||||||
std::cout << "[CLICK] Clicked on ground at: (" << hit.x() << ", " << hit.z() << ")" << std::endl;
|
std::cout << "[CLICK] Clicked on ground at: (" << hit.x() << ", " << hit.z() << ")" << std::endl;
|
||||||
|
|
||||||
|
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->setTarget(Eigen::Vector3f(hit.x(), 0.f, hit.z()));
|
||||||
player->attackTarget = nullptr;
|
player->attackTarget = nullptr;
|
||||||
targetInteractNpc = nullptr;
|
targetInteractNpc = nullptr;
|
||||||
targetInteractNpcIndex = -1;
|
targetInteractNpcIndex = -1;
|
||||||
|
targetTeleportZone = nullptr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
std::cout << "[CLICK] No valid target found" << std::endl;
|
std::cout << "[CLICK] No valid target found" << std::endl;
|
||||||
|
|||||||
@ -12,6 +12,7 @@
|
|||||||
#include "ScriptEngine.h"
|
#include "ScriptEngine.h"
|
||||||
#include "dialogue/DialogueSystem.h"
|
#include "dialogue/DialogueSystem.h"
|
||||||
#include "SparkEmitter.h"
|
#include "SparkEmitter.h"
|
||||||
|
#include "TeleportZone.h"
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
@ -27,9 +28,8 @@ namespace ZL
|
|||||||
std::string dialoguesJsonPath;
|
std::string dialoguesJsonPath;
|
||||||
std::string navigationJsonPath;
|
std::string navigationJsonPath;
|
||||||
std::string scriptPath;
|
std::string scriptPath;
|
||||||
|
std::string teleportsJsonPath;
|
||||||
Eigen::Vector3f playerPosition = Eigen::Vector3f::Zero();
|
Eigen::Vector3f playerPosition = Eigen::Vector3f::Zero();
|
||||||
Eigen::Vector3f teleportPosition = Eigen::Vector3f::Zero();
|
|
||||||
float teleportRadius = 0.0f;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class Location
|
class Location
|
||||||
@ -69,14 +69,9 @@ namespace ZL
|
|||||||
ScriptEngine scriptEngine;
|
ScriptEngine scriptEngine;
|
||||||
Dialogue::DialogueSystem dialogueSystem;
|
Dialogue::DialogueSystem dialogueSystem;
|
||||||
|
|
||||||
// Teleport zone: when the player crosses into the radius of teleportPosition,
|
std::vector<TeleportZone> teleportZones;
|
||||||
// onTeleport() is invoked once. Re-triggering requires leaving the zone first
|
TeleportZone* targetTeleportZone = nullptr;
|
||||||
// (rising-edge detection via playerInTeleportZone).
|
std::function<void(const std::string&, const Eigen::Vector3f&, float)> onTeleport;
|
||||||
Eigen::Vector3f teleportPosition = Eigen::Vector3f::Zero();
|
|
||||||
float teleportRadius = 0.0f;
|
|
||||||
bool playerInTeleportZone = false;
|
|
||||||
std::function<void()> onTeleport;
|
|
||||||
std::unique_ptr<SparkEmitter> teleportSparks;
|
|
||||||
|
|
||||||
#ifdef SHOW_PATH
|
#ifdef SHOW_PATH
|
||||||
std::vector<VertexRenderStruct> debugNavMeshes;
|
std::vector<VertexRenderStruct> debugNavMeshes;
|
||||||
@ -118,6 +113,7 @@ namespace ZL
|
|||||||
private:
|
private:
|
||||||
void resolveCharacterCollisions();
|
void resolveCharacterCollisions();
|
||||||
void updateDynamicReplans(int64_t deltaMs);
|
void updateDynamicReplans(int64_t deltaMs);
|
||||||
|
void loadTeleportZones(const std::string& jsonPath, const char* zipFile);
|
||||||
|
|
||||||
std::unordered_map<Character*, Eigen::Vector3f> lastCharacterPositions;
|
std::unordered_map<Character*, Eigen::Vector3f> lastCharacterPositions;
|
||||||
std::unordered_map<Character*, int64_t> replanCooldownRemainingMs;
|
std::unordered_map<Character*, int64_t> replanCooldownRemainingMs;
|
||||||
|
|||||||
43
src/TeleportZone.cpp
Normal file
43
src/TeleportZone.cpp
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
#include "TeleportZone.h"
|
||||||
|
#include "render/Renderer.h"
|
||||||
|
#include "render/TextureManager.h"
|
||||||
|
|
||||||
|
namespace ZL {
|
||||||
|
|
||||||
|
void TeleportZone::initSparks(std::shared_ptr<Texture> activeTex, std::shared_ptr<Texture> inactiveTex)
|
||||||
|
{
|
||||||
|
sparks = std::make_unique<SparkEmitter>();
|
||||||
|
std::vector<Vector3f> 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
|
||||||
27
src/TeleportZone.h
Normal file
27
src/TeleportZone.h
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <string>
|
||||||
|
#include <memory>
|
||||||
|
#include <Eigen/Core>
|
||||||
|
#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<SparkEmitter> sparks;
|
||||||
|
|
||||||
|
void initSparks(std::shared_ptr<Texture> activeTex, std::shared_ptr<Texture> inactiveTex);
|
||||||
|
void update(float deltaMs);
|
||||||
|
void prepareForDraw(const Eigen::Matrix4f& viewMatrix);
|
||||||
|
void draw(Renderer& renderer, float zoom, int width, int height);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace ZL
|
||||||
Loading…
Reference in New Issue
Block a user