Working on teleports
This commit is contained in:
parent
7e94009217
commit
8214342248
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
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",
|
||||
"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"
|
||||
|
||||
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
|
||||
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<Eigen::Vector3f> pathWaypoints;
|
||||
size_t currentWaypointIndex = 0;
|
||||
PathPlanner pathPlanner;
|
||||
float targetFacingAngle = 0.0f;
|
||||
std::function<void()> onArrivedCallback;
|
||||
|
||||
static constexpr float WALK_THRESHOLD = 0.05f;
|
||||
|
||||
80
src/Game.cpp
80
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<Location>(renderer, inventory);
|
||||
location1->setup(params1);
|
||||
locations["location1"] = std::make_shared<Location>(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<Location>(renderer, inventory);
|
||||
location2->setup(params2);
|
||||
|
||||
locations["location2"] = std::make_shared<Location>(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<Location>(renderer, inventory);
|
||||
location_dorm->setup(params_dorm);
|
||||
locations["location_dorm"] = std::make_shared<Location>(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:
|
||||
|
||||
@ -42,10 +42,7 @@ namespace ZL {
|
||||
VertexRenderStruct loadingMesh;
|
||||
bool loadingCompleted = false;
|
||||
|
||||
std::shared_ptr<Location> location1;
|
||||
std::shared_ptr<Location> location2;
|
||||
std::shared_ptr<Location> location_dorm;
|
||||
|
||||
std::unordered_map<std::string, std::shared_ptr<Location>> locations;
|
||||
std::shared_ptr<Location> currentLocation;
|
||||
|
||||
Inventory inventory;
|
||||
|
||||
162
src/Location.cpp
162
src/Location.cpp
@ -12,6 +12,7 @@
|
||||
#include <cfloat>
|
||||
#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<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;
|
||||
}
|
||||
}
|
||||
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<float>(delta));
|
||||
}
|
||||
for (auto& tz : teleportZones) tz.update(static_cast<float>(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;
|
||||
|
||||
@ -12,6 +12,7 @@
|
||||
#include "ScriptEngine.h"
|
||||
#include "dialogue/DialogueSystem.h"
|
||||
#include "SparkEmitter.h"
|
||||
#include "TeleportZone.h"
|
||||
#include <functional>
|
||||
#include <cstdint>
|
||||
#include <unordered_map>
|
||||
@ -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<void()> onTeleport;
|
||||
std::unique_ptr<SparkEmitter> teleportSparks;
|
||||
std::vector<TeleportZone> teleportZones;
|
||||
TeleportZone* targetTeleportZone = nullptr;
|
||||
std::function<void(const std::string&, const Eigen::Vector3f&, float)> onTeleport;
|
||||
|
||||
#ifdef SHOW_PATH
|
||||
std::vector<VertexRenderStruct> 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<Character*, Eigen::Vector3f> lastCharacterPositions;
|
||||
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