Working on state for location

This commit is contained in:
Vladislav Khorev committed 2026-06-18 21:48:27 +03:00
1 parent e0968b5522
commit b01e970554
13 files changed
+494 -101

No files matched your search

+193 -58
View File
@@ -1,4 +1,4 @@
#include "Location.h"
#include "Location.h"
#include "utils/Utils.h"
#include "render/OpenGlExtensions.h"
#include <iostream>
@@ -49,6 +49,40 @@ namespace ZL
{
}
// ---- Pointer-to-index helpers ----
InteractiveObject* Location::getTargetInteractiveObject()
{
const int idx = state.targetInteractiveObjectIndex;
if (idx < 0 || idx >= static_cast<int>(interactiveObjects.size())) return nullptr;
return &interactiveObjects[idx];
}
TeleportZone* Location::getTargetTeleportZone()
{
const int idx = state.targetTeleportZoneIndex;
if (idx < 0 || idx >= static_cast<int>(teleportZones.size())) return nullptr;
return &teleportZones[idx];
}
int Location::findInteractiveObjectIndex(const InteractiveObject* obj) const
{
for (int i = 0; i < static_cast<int>(interactiveObjects.size()); ++i) {
if (&interactiveObjects[i] == obj) return i;
}
return -1;
}
int Location::findTeleportZoneIndex(const TeleportZone* tz) const
{
for (int i = 0; i < static_cast<int>(teleportZones.size()); ++i) {
if (&teleportZones[i] == tz) return i;
}
return -1;
}
// ---- Setup ----
void Location::setup(const LocationSetup& params, Quest::QuestJournal* journal)
{
@@ -68,7 +102,7 @@ namespace ZL
player->loadBinaryAnimation(AnimationState::STAND, "resources/w/gg/new2/gg_stand_idle001_small.anim", CONST_ZIP_FILE);
player->loadBinaryAnimation(AnimationState::WALK, "resources/w/gg/new2/gg_walk001_small.anim", CONST_ZIP_FILE);
player->loadBinaryAnimation(AnimationState::STAND_TO_ACTION, "resources/w/gg/new2/gg_stand_to_action001_small.anim", CONST_ZIP_FILE);
player->loadBinaryAnimation(AnimationState::ACTION_ATTACK, "resources/w/gg/new2/gg_action_chop001_small.anim", CONST_ZIP_FILE);
player->loadBinaryAnimation(AnimationState::ACTION_ATTACK_2, "resources/w/gg/new2/gg_action_stab001_small.anim", CONST_ZIP_FILE);
player->loadBinaryAnimation(AnimationState::ACTION_IDLE, "resources/w/gg/new2/gg_action_idle002_small.anim", CONST_ZIP_FILE);
@@ -278,6 +312,9 @@ namespace ZL
triggerZones.push_back(std::move(tz));
}
// Resize the parallel playerInside vector in state to match
state.triggerZonePlayerInside.assign(triggerZones.size(), false);
std::cout << "[TRIGGER] Loaded " << triggerZones.size() << " trigger zone(s) from " << jsonPath << std::endl;
}
@@ -345,14 +382,17 @@ namespace ZL
void Location::updateTriggerZones(const Eigen::Vector3f& playerPos)
{
for (auto& tz : triggerZones) {
for (int i = 0; i < static_cast<int>(triggerZones.size()); ++i) {
TriggerZone& tz = triggerZones[i];
if (!tz.enabled) continue;
const float dist = (playerPos - tz.position).norm();
if (!tz.playerInside && dist <= tz.radius) {
tz.playerInside = true;
state.triggerZonePlayerInside[i] = true;
scriptEngine.callTriggerEnterCallback(tz.id);
} else if (tz.playerInside && dist > tz.radius + tz.hysteresis) {
tz.playerInside = false;
state.triggerZonePlayerInside[i] = false;
scriptEngine.callTriggerExitCallback(tz.id);
}
}
@@ -368,7 +408,7 @@ namespace ZL
navigationMaps[i].build(paths[i], CONST_ZIP_FILE);
}
activeNavigationIndex = 0;
state.activeNavigationIndex = 0;
navigation = navigationMaps.empty() ? nullptr : &navigationMaps[0];
if (editorMode == EditorMode::Navigation && navigation) {
@@ -424,7 +464,7 @@ namespace ZL
std::cerr << "[NAV] switchNavigation: index " << index << " out of range\n";
return false;
}
activeNavigationIndex = index;
state.activeNavigationIndex = index;
navigation = &navigationMaps[index];
// Force all characters to replan their paths against the new nav map.
@@ -461,7 +501,7 @@ namespace ZL
continue;
}
if (isDarklands) {
if (state.isDarklands) {
if (!intObj.loadedObject.textureDarklands) continue;
} else {
if (!intObj.loadedObject.texture) continue;
@@ -615,8 +655,8 @@ namespace ZL
renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom });
//renderer.TranslateMatrix({ 0, -6.f, 0 });
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(cameraInclination, Eigen::Vector3f::UnitX())).toRotationMatrix());
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(cameraAzimuth, Eigen::Vector3f::UnitY())).toRotationMatrix());
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(state.cameraInclination, Eigen::Vector3f::UnitX())).toRotationMatrix());
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(state.cameraAzimuth, Eigen::Vector3f::UnitY())).toRotationMatrix());
const Eigen::Vector3f& camTarget = player ? player->state.position : Eigen::Vector3f::Zero();
renderer.TranslateMatrix({ -camTarget.x(), -camTarget.y(), -camTarget.z() });
renderer.TranslateMatrix({ 0, -1.f, 0 });
@@ -785,9 +825,9 @@ namespace ZL
renderer.LoadIdentity();
renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom });
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(cameraInclination, Eigen::Vector3f::UnitX())).toRotationMatrix());
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(cameraAzimuth, Eigen::Vector3f::UnitY())).toRotationMatrix());
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(state.cameraInclination, Eigen::Vector3f::UnitX())).toRotationMatrix());
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(state.cameraAzimuth, Eigen::Vector3f::UnitY())).toRotationMatrix());
const Eigen::Vector3f& camTarget = player ? player->state.position : Eigen::Vector3f::Zero();
renderer.TranslateMatrix({ -camTarget.x(), -camTarget.y(), -camTarget.z() });
renderer.TranslateMatrix({ 0, -1.f, 0 });
@@ -897,8 +937,8 @@ namespace ZL
renderer.LoadIdentity();
renderer.TranslateMatrix({ 0, 0, -1.0f * Environment::zoom });
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(cameraInclination, Eigen::Vector3f::UnitX())).toRotationMatrix());
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(cameraAzimuth, Eigen::Vector3f::UnitY())).toRotationMatrix());
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(state.cameraInclination, Eigen::Vector3f::UnitX())).toRotationMatrix());
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(state.cameraAzimuth, Eigen::Vector3f::UnitY())).toRotationMatrix());
const Eigen::Vector3f& camTarget = player ? player->state.position : Eigen::Vector3f::Zero();
renderer.TranslateMatrix({ -camTarget.x(), -camTarget.y(), -camTarget.z() });
renderer.TranslateMatrix({ 0, -1.f, 0 });
@@ -1044,8 +1084,8 @@ namespace ZL
static const float kDawnAmbient[3] = { 0.72f, 0.52f, 0.62f }; // brighter warm pink
static const float kNightFogColor[3] = { 0.01f, 0.01f, 0.05f };
static const float kDawnFogColor[3] = { 0.50f, 0.44f, 0.47f }; // grey with slight pink
const float* ambientColor = isDawn ? kDawnAmbient : kNightAmbient;
const float* fogColor = isDawn ? kDawnFogColor : kNightFogColor;
const float* ambientColor = state.isDawn ? kDawnAmbient : kNightAmbient;
const float* fogColor = state.isDawn ? kDawnFogColor : kNightFogColor;
if (hasShadows) {
drawNightShadowDepthPass(*shadowLight);
@@ -1079,8 +1119,8 @@ namespace ZL
renderer.LoadIdentity();
renderer.TranslateMatrix({ 0, 0, -1.0f * Environment::zoom });
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(cameraInclination, Eigen::Vector3f::UnitX())).toRotationMatrix());
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(cameraAzimuth, Eigen::Vector3f::UnitY())).toRotationMatrix());
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(state.cameraInclination, Eigen::Vector3f::UnitX())).toRotationMatrix());
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(state.cameraAzimuth, Eigen::Vector3f::UnitY())).toRotationMatrix());
const Eigen::Vector3f& camTarget = player ? player->state.position : Eigen::Vector3f::Zero();
renderer.TranslateMatrix({ -camTarget.x(), -camTarget.y(), -camTarget.z() });
renderer.TranslateMatrix({ 0, -1.f, 0 });
@@ -1492,63 +1532,65 @@ namespace ZL
for (auto& [idx, t] : npcBumpsPlayerCooldown) t -= deltaS;
// Check if player reached target interactive object
if (!tutorialInteractiveObjectsLocked && targetInteractiveObject && player && !targetInteractiveObject->state.isAnimating) {
const Eigen::Vector3f& approachTarget = targetInteractiveObject->state.hasInteractionPosition
? targetInteractiveObject->state.interactionPosition
: targetInteractiveObject->state.position;
if (auto* obj = getTargetInteractiveObject();
!state.tutorialInteractiveObjectsLocked && obj && player && !obj->state.isAnimating)
{
const Eigen::Vector3f& approachTarget = obj->state.hasInteractionPosition
? obj->state.interactionPosition
: obj->state.position;
float distToObject = (player->state.position - approachTarget).norm();
// If player is close enough to pick up the item
if (distToObject <= targetInteractiveObject->state.approachRadius) {
if (distToObject <= obj->state.approachRadius) {
std::cout << "[PICKUP] Player reached object! Distance: " << distToObject << std::endl;
std::cout << "[PICKUP] Calling Lua callback for: " << targetInteractiveObject->loadedObject.name << std::endl;
std::cout << "[PICKUP] Calling Lua callback for: " << obj->loadedObject.name << std::endl;
// Call custom activate function if specified, otherwise use fallback
try {
if (!targetInteractiveObject->state.activateFunctionName.empty()) {
std::cout << "[PICKUP] Using custom function: " << targetInteractiveObject->state.activateFunctionName << std::endl;
scriptEngine.callActivateFunction(targetInteractiveObject->state.activateFunctionName);
if (!obj->state.activateFunctionName.empty()) {
std::cout << "[PICKUP] Using custom function: " << obj->state.activateFunctionName << std::endl;
scriptEngine.callActivateFunction(obj->state.activateFunctionName);
}
else {
std::cout << "[PICKUP] Using fallback callback" << std::endl;
scriptEngine.callItemPickupCallback(targetInteractiveObject->loadedObject.name);
scriptEngine.callItemPickupCallback(obj->loadedObject.name);
}
}
catch (const std::exception& e) {
std::cerr << "[PICKUP] Error calling function: " << e.what() << std::endl;
}
targetInteractiveObject = nullptr;
state.targetInteractiveObjectIndex = -1;
}
}
// Check if player reached target NPC for interaction.
if (targetInteractNpcIndex >= 0 && player) {
float distToNpc = (player->state.position - npcs[targetInteractNpcIndex]->state.position).norm();
if (state.targetInteractNpcIndex >= 0 && player) {
float distToNpc = (player->state.position - npcs[state.targetInteractNpcIndex]->state.position).norm();
if (distToNpc <= NPC_TALK_DISTANCE) {
std::cout << "[NPC] Player reached NPC index " << targetInteractNpcIndex
std::cout << "[NPC] Player reached NPC index " << state.targetInteractNpcIndex
<< " (distance " << distToNpc << "); firing on_npc_interact" << std::endl;
// Stop the player at the talk distance and have the NPC turn to face them.
player->setTarget(player->state.position);
npcs[targetInteractNpcIndex]->state.faceTargetIndex = CharacterState::kPlayerIndex;
npcs[state.targetInteractNpcIndex]->state.faceTargetIndex = CharacterState::kPlayerIndex;
try {
scriptEngine.callNpcInteractCallback(targetInteractNpcIndex);
scriptEngine.callNpcInteractCallback(state.targetInteractNpcIndex);
}
catch (const std::exception& e) {
std::cerr << "[NPC] callback error: " << e.what() << std::endl;
}
targetInteractNpcIndex = -1;
state.targetInteractNpcIndex = -1;
}
}
for (auto& tz : teleportZones) tz.update(static_cast<float>(delta));
if (targetTeleportZone && player) {
float dist = (player->state.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;
if (auto* tz = getTargetTeleportZone(); tz && player) {
float dist = (player->state.position - tz->position).norm();
if (dist <= tz->radius) {
std::cout << "[TELEPORT] Player reached teleport zone '" << tz->id << "'" << std::endl;
if (onTeleport) onTeleport(tz->destinationLocation, tz->destinationPosition, tz->destinationRotationY);
state.targetTeleportZoneIndex = -1;
return;
}
}
@@ -1596,8 +1638,8 @@ namespace ZL
float aspect = (float)Environment::width / (float)Environment::height;
float tanHalfFov = tan(CAMERA_FOV_Y * 0.5f);
float cosAzim = cos(cameraAzimuth), sinAzim = sin(cameraAzimuth);
float cosIncl = cos(cameraInclination), sinIncl = sin(cameraInclination);
float cosAzim = cos(state.cameraAzimuth), sinAzim = sin(state.cameraAzimuth);
float cosIncl = cos(state.cameraInclination), sinIncl = sin(state.cameraInclination);
Eigen::Vector3f camRight(cosAzim, 0.f, sinAzim);
Eigen::Vector3f camForward(sinAzim * cosIncl, -sinIncl, -cosAzim * cosIncl);
@@ -1639,9 +1681,9 @@ namespace ZL
std::cout << "[CLICK] Player position: (" << player->state.position.x() << ", "
<< player->state.position.y() << ", " << player->state.position.z() << ")" << std::endl;
targetInteractiveObject = clickedObject;
targetInteractNpcIndex = -1;
targetTeleportZone = nullptr;
state.targetInteractiveObjectIndex = findInteractiveObjectIndex(clickedObject);
state.targetInteractNpcIndex = -1;
state.targetTeleportZoneIndex = -1;
player->setTarget(clickedObject->state.hasInteractionPosition
? clickedObject->state.interactionPosition
: clickedObject->state.position);
@@ -1662,13 +1704,13 @@ namespace ZL
}
}
if (npcIndex != -1) {
targetInteractiveObject = nullptr;
targetTeleportZone = nullptr;
state.targetInteractiveObjectIndex = -1;
state.targetTeleportZoneIndex = -1;
if (clickedNpc->state.canAttack) {
// Hostile NPC: combat logic walks the player in via attackTargetIndex.
player->state.attackTargetIndex = npcIndex;
targetInteractNpcIndex = -1;
state.targetInteractNpcIndex = -1;
if (distance <= clickedNpc->state.interactionRadius) {
std::cout << "[CLICK] Hostile NPC " << npcIndex << " in range; firing on_npc_interact" << std::endl;
@@ -1685,13 +1727,13 @@ namespace ZL
player->setTarget(player->state.position);
npcs[npcIndex]->state.faceTargetIndex = CharacterState::kPlayerIndex;
scriptEngine.callNpcInteractCallback(npcIndex);
targetInteractNpcIndex = -1;
state.targetInteractNpcIndex = -1;
}
else {
std::cout << "[CLICK] NPC " << npcIndex << " out of talk range (distance " << distance
<< " > " << NPC_TALK_DISTANCE << "); walking to NPC..." << std::endl;
player->setTarget(clickedNpc->state.position);
targetInteractNpcIndex = npcIndex;
state.targetInteractNpcIndex = npcIndex;
}
}
}
@@ -1712,16 +1754,16 @@ namespace ZL
if (clickedTeleport) {
std::cout << "[CLICK] Clicked teleport zone '" << clickedTeleport->id << "'" << std::endl;
targetTeleportZone = clickedTeleport;
targetInteractiveObject = nullptr;
targetInteractNpcIndex = -1;
state.targetTeleportZoneIndex = findTeleportZoneIndex(clickedTeleport);
state.targetInteractiveObjectIndex = -1;
state.targetInteractNpcIndex = -1;
player->setTarget(clickedTeleport->position);
player->state.attackTargetIndex = CharacterState::kNoTarget;
} else {
player->setTarget(Eigen::Vector3f(hit.x(), 0.f, hit.z()));
player->state.attackTargetIndex = CharacterState::kNoTarget;
targetInteractNpcIndex = -1;
targetTeleportZone = nullptr;
state.targetInteractNpcIndex = -1;
state.targetTeleportZoneIndex = -1;
if (onPlayerFloorWalk) onPlayerFloorWalk();
}
}
@@ -1755,12 +1797,12 @@ namespace ZL
lastMouseY = eventY;
const float sensitivity = 0.005f;
cameraAzimuth += dx * sensitivity;
cameraInclination += dy * sensitivity;
state.cameraAzimuth += dx * sensitivity;
state.cameraInclination += dy * sensitivity;
const float minInclination = M_PI * 30.f / 180.f;
const float maxInclination = M_PI * 0.5f;
cameraInclination = max(minInclination, min(maxInclination, cameraInclination));
state.cameraInclination = max(minInclination, min(maxInclination, state.cameraInclination));
}
}
@@ -1789,5 +1831,98 @@ namespace ZL
return dialogueSystem.getFlag(flag);
}
// ---- Save / Load ----
void Location::saveFullState(nlohmann::json& out) const
{
// Location-level state
nlohmann::json locState;
state.save(locState);
out["locationState"] = std::move(locState);
// Player character state
if (player) {
nlohmann::json ps;
player->state.save(ps);
out["playerState"] = std::move(ps);
}
// NPC states
nlohmann::json npcArr = nlohmann::json::array();
for (const auto& npc : npcs) {
if (npc) {
nlohmann::json ns;
npc->state.save(ns);
npcArr.push_back(std::move(ns));
} else {
npcArr.push_back(nullptr);
}
}
out["npcStates"] = std::move(npcArr);
// Interactive object states
nlohmann::json ioArr = nlohmann::json::array();
for (const auto& intObj : interactiveObjects) {
nlohmann::json ios;
intObj.state.save(ios);
ioArr.push_back(std::move(ios));
}
out["interactiveObjectStates"] = std::move(ioArr);
// Lua global variable state
nlohmann::json scriptState;
scriptEngine.saveScriptGlobals(scriptState);
out["scriptGlobals"] = std::move(scriptState);
}
void Location::loadFullState(const nlohmann::json& in)
{
// Location-level state
if (in.contains("locationState")) {
state.load(in["locationState"]);
}
// Re-sync navigation pointer from restored index
if (state.activeNavigationIndex >= 0 &&
state.activeNavigationIndex < static_cast<int>(navigationMaps.size()))
{
navigation = &navigationMaps[state.activeNavigationIndex];
}
// Re-sync triggerZones[i].playerInside from state
for (int i = 0; i < static_cast<int>(triggerZones.size()); ++i) {
if (i < static_cast<int>(state.triggerZonePlayerInside.size())) {
triggerZones[i].playerInside = state.triggerZonePlayerInside[i];
}
}
// Player character state
if (player && in.contains("playerState")) {
player->state.load(in["playerState"]);
}
// NPC states
if (in.contains("npcStates") && in["npcStates"].is_array()) {
const auto& arr = in["npcStates"];
for (int i = 0; i < static_cast<int>(arr.size()) && i < static_cast<int>(npcs.size()); ++i) {
if (npcs[i] && !arr[i].is_null()) {
npcs[i]->state.load(arr[i]);
}
}
}
// Interactive object states
if (in.contains("interactiveObjectStates") && in["interactiveObjectStates"].is_array()) {
const auto& arr = in["interactiveObjectStates"];
for (int i = 0; i < static_cast<int>(arr.size()) && i < static_cast<int>(interactiveObjects.size()); ++i) {
interactiveObjects[i].state.load(arr[i]);
}
}
// Lua globals
if (in.contains("scriptGlobals")) {
scriptEngine.loadScriptGlobals(in["scriptGlobals"]);
}
}
} // namespace ZL