From f90324e2b781f6ff9181bcf8defaae4f8fe7333e Mon Sep 17 00:00:00 2001 From: Vladislav Khorev Date: Sat, 14 Nov 2026 18:11:41 +0600 Subject: [PATCH] Restore back dialogs and interaction with objects --- src/Location.cpp | 8 ++--- src/LocationEditor.cpp | 15 ++++---- src/LocationGameState.cpp | 39 +++++++++++++++++++++ src/content/LocationDefinition.cpp | 56 ++++++++++++++---------------- src/content/LocationDefinition.h | 6 ++-- src/items/GameObjectLoader.cpp | 8 ++--- src/items/InteractiveObjectState.h | 11 +++--- 7 files changed, 86 insertions(+), 57 deletions(-) diff --git a/src/Location.cpp b/src/Location.cpp index 5d30d24..8b9e6e1 100644 --- a/src/Location.cpp +++ b/src/Location.cpp @@ -2067,11 +2067,11 @@ namespace FRG gameServer_.submit(SetLocationTargetsCommand{ locationId_, clickedObject->entityId(), std::string(), std::string()}); - const Eigen::Vector3f interactionTarget = clickedObject->state.hasInteractionPosition + const Vec3fp interactionTarget = clickedObject->state.hasInteractionPosition ? clickedObject->state.interactionPosition - : clickedObject->state.position; - setCharacterTarget(*player, Vec3fp::fromEigen(interactionTarget)); - pathVisualization.start(*player, interactionTarget); + : Vec3fp::fromEigen(clickedObject->state.position); + setCharacterTarget(*player, interactionTarget); + pathVisualization.start(*player, interactionTarget.toEigen()); clearCharacterAttackTarget(*player); //logger() << "[CLICK] Player moving to object..." << std::endl; } diff --git a/src/LocationEditor.cpp b/src/LocationEditor.cpp index f4bb29f..7a88879 100644 --- a/src/LocationEditor.cpp +++ b/src/LocationEditor.cpp @@ -278,7 +278,7 @@ namespace FRG // --- interaction position pole --- if (obj.state.hasInteractionPosition) { - const Eigen::Vector3f& p = obj.state.interactionPosition; + Eigen::Vector3f p = obj.state.interactionPosition.toEigen(); static constexpr float hw = 0.05f; // half width/depth static constexpr float hh = 1.5f; // half height (pole = 3.0 tall) @@ -340,7 +340,7 @@ namespace FRG if (ctrlHeld) { // Set the interaction position for the selected object (world XZ, Y=0). auto& target = loc.interactiveObjects[selectedInteractiveObjectIndex]; - target.state.interactionPosition = Eigen::Vector3f(worldHit.x(), 0.f, worldHit.z()); + target.state.interactionPosition = Vec3fp(static_cast(worldHit.x()), static_cast(0.f), static_cast(worldHit.z())); target.state.hasInteractionPosition = true; boundsClickCount = 0; // cancel any in-progress bounds placement buildInteractiveObjectBoundsMeshes(); @@ -417,12 +417,9 @@ namespace FRG item["rotationX"] = obj.loadedObject.creationInfo.meshRotationX; item["rotationY"] = obj.loadedObject.creationInfo.meshRotationY; item["rotationZ"] = obj.loadedObject.creationInfo.meshRotationZ; - item["positionX"] = obj.state.jsonPositionX; - item["positionY"] = obj.state.jsonPositionY; - item["positionZ"] = obj.state.jsonPositionZ; item["scale"] = obj.loadedObject.creationInfo.meshScale; item["interactionRadius"] = obj.state.interactionRadius; - item["approachRadius"] = obj.state.approachRadius; + item["approachRadius"] = static_cast(obj.state.approachRadius); if (!obj.state.activateFunctionName.empty()) item["activateFunction"] = obj.state.activateFunctionName; item["pivotX"] = obj.state.pivot.x(); @@ -435,9 +432,9 @@ namespace FRG item["boundsMaxY"] = obj.state.boundsMax.y(); item["boundsMaxZ"] = obj.state.boundsMax.z(); if (obj.state.hasInteractionPosition) { - item["interactionPositionX"] = obj.state.interactionPosition.x(); - item["interactionPositionY"] = obj.state.interactionPosition.y(); - item["interactionPositionZ"] = obj.state.interactionPosition.z(); + item["interactionPositionX"] = static_cast(obj.state.interactionPosition.x); + item["interactionPositionY"] = static_cast(obj.state.interactionPosition.y); + item["interactionPositionZ"] = static_cast(obj.state.interactionPosition.z); } j["objects"].push_back(item); } diff --git a/src/LocationGameState.cpp b/src/LocationGameState.cpp index 2f0edde..3c520e4 100644 --- a/src/LocationGameState.cpp +++ b/src/LocationGameState.cpp @@ -9,6 +9,7 @@ namespace FRG { namespace { constexpr float PI = 3.14159265358979323846f; constexpr float NPC_TALK_DISTANCE = 1.35f; +constexpr fp NPC_TALK_DISTANCE_FP_SQUARED() { return fp(NPC_TALK_DISTANCE * NPC_TALK_DISTANCE); }; } bool LocationGameState::setup( @@ -496,6 +497,44 @@ LocationSimulationEvents LocationGameState::update( source.events.attackDirection }); } + + //Main player can interact with interactive objects + auto player = players.find("player1"); + if (!interactiveObjectsLocked && + !location->targetInteractiveObjectId.empty() && + player != players.end() && player->second && player->second->enabled) { + auto target = interactiveObjects.find(location->targetInteractiveObjectId); + const auto object = + target == interactiveObjects.end() ? nullptr : target->second; + if (object && !object->isAnimating) { + Vec3fp approachTarget = object->hasInteractionPosition + ? object->interactionPosition : Vec3fp::fromEigen(object->position); + + if ((player->second->position - approachTarget).normSquared() <= object->approachRadius* object->approachRadius) { + result.interactiveObjects.push_back( + { location->targetInteractiveObjectId, {}, true, + object->activateFunctionName }); + location->targetInteractiveObjectId.clear(); + } + } + } + + //Main player can also talk to other NPCs + if (!location->targetInteractNpcId.empty() && + player != players.end() && player->second && player->second->enabled && + npcs.count(location->targetInteractNpcId) != 0 && + npcs.at(location->targetInteractNpcId)) { + const auto& npc = npcs.at(location->targetInteractNpcId); + const fp distanceSquared = + (player->second->position - npc->position).normSquared(); + if (distanceSquared <= NPC_TALK_DISTANCE_FP_SQUARED()) { + player->second->stopInPlace(); + npc->faceTargetId = "player1"; + result.npcInteractionId = location->targetInteractNpcId; + location->targetInteractNpcId.clear(); + } + } + return result; } diff --git a/src/content/LocationDefinition.cpp b/src/content/LocationDefinition.cpp index fb01cc0..062c337 100644 --- a/src/content/LocationDefinition.cpp +++ b/src/content/LocationDefinition.cpp @@ -51,7 +51,7 @@ GameObjectData parseGameObject(const json& item) return data; } -Eigen::Vector3f vector( +Eigen::Vector3f toVectorJson( const json& item, const char* x, const char* y, const char* z, const Eigen::Vector3f& fallback = Eigen::Vector3f::Zero()) { @@ -59,6 +59,17 @@ Eigen::Vector3f vector( item.value(z, fallback.z())}; } +Vec3fp toVec3fp( + const json& item, const char* x, const char* y, const char* z) +{ + return { + static_cast(item.value(x, 0.0)), + static_cast(item.value(y, 0.0)), + static_cast(item.value(z, 0.0)) + }; +} + + std::pair readBinaryAnimationMetadata( const std::string& path, const std::string& zipFile) { @@ -160,9 +171,7 @@ InteractiveObjectState InteractiveObjectData::toInteractiveObjectState() const state.boundsMax = boundsMax; state.hasInteractionPosition = hasInteractionPosition; state.interactionPosition = interactionPosition; - state.jsonPositionX = base.positionX; - state.jsonPositionY = base.positionY; - state.jsonPositionZ = base.positionZ; + state.position = Eigen::Vector3f(base.positionX, base.positionY, base.positionZ); return state; @@ -264,20 +273,20 @@ LocationDefinition LocationContentLoader::load( "Missing or duplicate interactive-object id in " + config.interactiveObjectsJsonPath); data.base = parseGameObject(item); - data.interactionRadius = item.value("interactionRadius", 2.f); + data.interactionRadius = item.value("interactionRadius", 2.0); data.approachRadius = - item.value("approachRadius", data.interactionRadius); + static_cast(item.value("approachRadius", data.interactionRadius)); data.activateFunctionName = item.value("activateFunction", ""); - data.pivot = vector(item, "pivotX", "pivotY", "pivotZ"); + data.pivot = toVectorJson(item, "pivotX", "pivotY", "pivotZ"); data.boundsMin = - vector(item, "boundsMinX", "boundsMinY", "boundsMinZ"); + toVectorJson(item, "boundsMinX", "boundsMinY", "boundsMinZ"); data.boundsMax = - vector(item, "boundsMaxX", "boundsMaxY", "boundsMaxZ"); + toVectorJson(item, "boundsMaxX", "boundsMaxY", "boundsMaxZ"); data.hasInteractionPosition = item.contains("interactionPositionX"); if (data.hasInteractionPosition) { - data.interactionPosition = vector( + data.interactionPosition = toVec3fp( item, "interactionPositionX", "interactionPositionY", "interactionPositionZ"); } @@ -348,20 +357,7 @@ LocationDefinition LocationContentLoader::load( static_cast(item.value("visibleRange", static_cast(VISIBLE_RANGE))); data.canAttack = item.value("canAttack", false); data.enabled = item.value("enabled", true); - /* - data.navigationAvoidanceRadius = item.value( - "navigationAvoidanceRadius", - item.value("collisionRadius", 0.45f)); - data.navigationAvoidanceRadius = - (std::max)(0.05f, data.navigationAvoidanceRadius); - data.movementContactRadius = item.value( - "movementContactRadius", - data.navigationAvoidanceRadius * 0.72f); - data.movementContactRadius = (std::max)( - 0.05f, (std::min)( - data.movementContactRadius, - data.navigationAvoidanceRadius)); - */ + const char* cooldownKey = item.contains("navigationRetryCooldownTicks") ? "navigationRetryCooldownTicks" @@ -394,14 +390,14 @@ LocationDefinition LocationContentLoader::load( config.teleportsJsonPath); data.active = item.value("active", false); data.position = - vector(item, "positionX", "positionY", "positionZ"); + toVectorJson(item, "positionX", "positionY", "positionZ"); data.radius = item.value("radius", 0.f); data.automatic = item.value("automatic", false); data.automaticRadius = item.value("automaticRadius", data.radius); data.destinationLocation = item.value("destinationLocation", ""); - data.destinationPosition = vector( + data.destinationPosition = toVectorJson( item, "destinationPositionX", "destinationPositionY", "destinationPositionZ"); data.destinationRotationY = @@ -425,7 +421,7 @@ LocationDefinition LocationContentLoader::load( data.radius = item.value("radius", 1.5f); data.hysteresis = item.value("hysteresis", 0.3f); data.position = - vector(item, "positionX", "positionY", "positionZ"); + toVectorJson(item, "positionX", "positionY", "positionZ"); result.triggerZones.push_back(std::move(data)); } } @@ -436,11 +432,11 @@ LocationDefinition LocationContentLoader::load( PointLightDefinition data; data.id = item.value("id", ""); data.position = - vector(item, "positionX", "positionY", "positionZ"); - data.direction = vector( + toVectorJson(item, "positionX", "positionY", "positionZ"); + data.direction = toVectorJson( item, "directionX", "directionY", "directionZ", Eigen::Vector3f(0.f, -1.f, 0.f)); - data.color = vector( + data.color = toVectorJson( item, "colorR", "colorG", "colorB", Eigen::Vector3f::Ones()); data.autoLight = item.value("autoLight", false); diff --git a/src/content/LocationDefinition.h b/src/content/LocationDefinition.h index dd0ed04..56ac023 100644 --- a/src/content/LocationDefinition.h +++ b/src/content/LocationDefinition.h @@ -30,14 +30,14 @@ struct GameObjectData { struct InteractiveObjectData { std::string id; GameObjectData base; - float interactionRadius = 2.f; - float approachRadius = 2.f; + float interactionRadius = 2.f; //This is only for detecting mouse/touch event + fp approachRadius = static_cast(2.f); std::string activateFunctionName; Eigen::Vector3f pivot = Eigen::Vector3f::Zero(); Eigen::Vector3f boundsMin = Eigen::Vector3f::Zero(); Eigen::Vector3f boundsMax = Eigen::Vector3f::Zero(); bool hasInteractionPosition = false; - Eigen::Vector3f interactionPosition = Eigen::Vector3f::Zero(); + Vec3fp interactionPosition; bool castShadow = true; bool castShadowNight = true; bool isActive = true; diff --git a/src/items/GameObjectLoader.cpp b/src/items/GameObjectLoader.cpp index fe4a26a..772f730 100644 --- a/src/items/GameObjectLoader.cpp +++ b/src/items/GameObjectLoader.cpp @@ -100,7 +100,7 @@ namespace FRG { throw std::runtime_error("Interactive object id must not be empty in " + jsonPath); data.base = parseGameObjectData(item); data.interactionRadius = item.value("interactionRadius", 2.0f); - data.approachRadius = item.value("approachRadius", data.interactionRadius); + data.approachRadius = static_cast(item.value("approachRadius", data.interactionRadius)); data.castShadow = item.value("castShadow", true); data.castShadowNight = item.value("castShadowNight", true); data.isActive = item.value("isActive", true); @@ -117,9 +117,9 @@ namespace FRG { if (item.contains("interactionPositionX")) { data.hasInteractionPosition = true; data.interactionPosition = { - item.value("interactionPositionX", 0.0f), - item.value("interactionPositionY", 0.0f), - item.value("interactionPositionZ", 0.0f)}; + static_cast(item.value("interactionPositionX", 0.0)), + static_cast(item.value("interactionPositionY", 0.0)), + static_cast(item.value("interactionPositionZ", 0.0))}; } if (!data.base.meshPath.empty()) objects.push_back(std::move(data)); diff --git a/src/items/InteractiveObjectState.h b/src/items/InteractiveObjectState.h index 858ca54..7af13e5 100644 --- a/src/items/InteractiveObjectState.h +++ b/src/items/InteractiveObjectState.h @@ -6,6 +6,7 @@ #include #include "external/nlohmann/json.hpp" #include "ISaveable.h" +#include "../navigation/PathFinder.h" namespace FRG { @@ -47,20 +48,16 @@ class InteractiveObjectState : public ISaveable { public: // --- Creation info (fixed at load time) --- LoadedGameObjectState objectInfo; - float interactionRadius = 2.f; - float approachRadius = 2.f; + float interactionRadius = 2.f; //This is only for detecting mouse/touch event + fp approachRadius = static_cast(2.f); Eigen::Vector3f pivot = Eigen::Vector3f::Zero(); Eigen::Vector3f boundsMin = Eigen::Vector3f::Zero(); Eigen::Vector3f boundsMax = Eigen::Vector3f::Zero(); bool hasInteractionPosition = false; - Eigen::Vector3f interactionPosition = Eigen::Vector3f::Zero(); + Vec3fp interactionPosition; bool castShadow = true; bool castShadowNight = true; std::string activateFunctionName; - // Original JSON position before mesh-centering offset — needed for re-serialization. - float jsonPositionX = 0.f; - float jsonPositionY = 0.f; - float jsonPositionZ = 0.f; // --- Runtime mutable state --- Eigen::Vector3f position = Eigen::Vector3f::Zero();