Working on teleports

This commit is contained in:
Vladislav Khorev committed 2026-05-07 20:53:20 +03:00
1 parent 7e94009217
commit 8214342248
15 files changed
+298 -131

No files matched your search

+98 -64
View File
@@ -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;