Restore back dialogs and interaction with objects

This commit is contained in:
Vladislav Khorev committed 2026-11-14 18:11:41 +06:00
1 parent 0fcf8c4fa5
commit f90324e2b7
7 files changed
+86 -57

No files matched your search

+4 -4
View File
@@ -2067,11 +2067,11 @@ namespace FRG
gameServer_.submit(SetLocationTargetsCommand{ gameServer_.submit(SetLocationTargetsCommand{
locationId_, clickedObject->entityId(), std::string(), std::string()}); 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.interactionPosition
: clickedObject->state.position; : Vec3fp::fromEigen(clickedObject->state.position);
setCharacterTarget(*player, Vec3fp::fromEigen(interactionTarget)); setCharacterTarget(*player, interactionTarget);
pathVisualization.start(*player, interactionTarget); pathVisualization.start(*player, interactionTarget.toEigen());
clearCharacterAttackTarget(*player); clearCharacterAttackTarget(*player);
//logger() << "[CLICK] Player moving to object..." << std::endl; //logger() << "[CLICK] Player moving to object..." << std::endl;
} }
+6 -9
View File
@@ -278,7 +278,7 @@ namespace FRG
// --- interaction position pole --- // --- interaction position pole ---
if (obj.state.hasInteractionPosition) { 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 hw = 0.05f; // half width/depth
static constexpr float hh = 1.5f; // half height (pole = 3.0 tall) static constexpr float hh = 1.5f; // half height (pole = 3.0 tall)
@@ -340,7 +340,7 @@ namespace FRG
if (ctrlHeld) { if (ctrlHeld) {
// Set the interaction position for the selected object (world XZ, Y=0). // Set the interaction position for the selected object (world XZ, Y=0).
auto& target = loc.interactiveObjects[selectedInteractiveObjectIndex]; auto& target = loc.interactiveObjects[selectedInteractiveObjectIndex];
target.state.interactionPosition = Eigen::Vector3f(worldHit.x(), 0.f, worldHit.z()); target.state.interactionPosition = Vec3fp(static_cast<fp>(worldHit.x()), static_cast<fp>(0.f), static_cast<fp>(worldHit.z()));
target.state.hasInteractionPosition = true; target.state.hasInteractionPosition = true;
boundsClickCount = 0; // cancel any in-progress bounds placement boundsClickCount = 0; // cancel any in-progress bounds placement
buildInteractiveObjectBoundsMeshes(); buildInteractiveObjectBoundsMeshes();
@@ -417,12 +417,9 @@ namespace FRG
item["rotationX"] = obj.loadedObject.creationInfo.meshRotationX; item["rotationX"] = obj.loadedObject.creationInfo.meshRotationX;
item["rotationY"] = obj.loadedObject.creationInfo.meshRotationY; item["rotationY"] = obj.loadedObject.creationInfo.meshRotationY;
item["rotationZ"] = obj.loadedObject.creationInfo.meshRotationZ; 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["scale"] = obj.loadedObject.creationInfo.meshScale;
item["interactionRadius"] = obj.state.interactionRadius; item["interactionRadius"] = obj.state.interactionRadius;
item["approachRadius"] = obj.state.approachRadius; item["approachRadius"] = static_cast<double>(obj.state.approachRadius);
if (!obj.state.activateFunctionName.empty()) if (!obj.state.activateFunctionName.empty())
item["activateFunction"] = obj.state.activateFunctionName; item["activateFunction"] = obj.state.activateFunctionName;
item["pivotX"] = obj.state.pivot.x(); item["pivotX"] = obj.state.pivot.x();
@@ -435,9 +432,9 @@ namespace FRG
item["boundsMaxY"] = obj.state.boundsMax.y(); item["boundsMaxY"] = obj.state.boundsMax.y();
item["boundsMaxZ"] = obj.state.boundsMax.z(); item["boundsMaxZ"] = obj.state.boundsMax.z();
if (obj.state.hasInteractionPosition) { if (obj.state.hasInteractionPosition) {
item["interactionPositionX"] = obj.state.interactionPosition.x(); item["interactionPositionX"] = static_cast<double>(obj.state.interactionPosition.x);
item["interactionPositionY"] = obj.state.interactionPosition.y(); item["interactionPositionY"] = static_cast<double>(obj.state.interactionPosition.y);
item["interactionPositionZ"] = obj.state.interactionPosition.z(); item["interactionPositionZ"] = static_cast<double>(obj.state.interactionPosition.z);
} }
j["objects"].push_back(item); j["objects"].push_back(item);
} }
+39
View File
@@ -9,6 +9,7 @@ namespace FRG {
namespace { namespace {
constexpr float PI = 3.14159265358979323846f; constexpr float PI = 3.14159265358979323846f;
constexpr float NPC_TALK_DISTANCE = 1.35f; 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( bool LocationGameState::setup(
@@ -496,6 +497,44 @@ LocationSimulationEvents LocationGameState::update(
source.events.attackDirection }); 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; return result;
} }
+26 -30
View File
@@ -51,7 +51,7 @@ GameObjectData parseGameObject(const json& item)
return data; return data;
} }
Eigen::Vector3f vector( Eigen::Vector3f toVectorJson(
const json& item, const char* x, const char* y, const char* z, const json& item, const char* x, const char* y, const char* z,
const Eigen::Vector3f& fallback = Eigen::Vector3f::Zero()) const Eigen::Vector3f& fallback = Eigen::Vector3f::Zero())
{ {
@@ -59,6 +59,17 @@ Eigen::Vector3f vector(
item.value(z, fallback.z())}; item.value(z, fallback.z())};
} }
Vec3fp toVec3fp(
const json& item, const char* x, const char* y, const char* z)
{
return {
static_cast<fp>(item.value<double>(x, 0.0)),
static_cast<fp>(item.value<double>(y, 0.0)),
static_cast<fp>(item.value<double>(z, 0.0))
};
}
std::pair<int, int> readBinaryAnimationMetadata( std::pair<int, int> readBinaryAnimationMetadata(
const std::string& path, const std::string& zipFile) const std::string& path, const std::string& zipFile)
{ {
@@ -160,9 +171,7 @@ InteractiveObjectState InteractiveObjectData::toInteractiveObjectState() const
state.boundsMax = boundsMax; state.boundsMax = boundsMax;
state.hasInteractionPosition = hasInteractionPosition; state.hasInteractionPosition = hasInteractionPosition;
state.interactionPosition = interactionPosition; state.interactionPosition = interactionPosition;
state.jsonPositionX = base.positionX;
state.jsonPositionY = base.positionY;
state.jsonPositionZ = base.positionZ;
state.position = state.position =
Eigen::Vector3f(base.positionX, base.positionY, base.positionZ); Eigen::Vector3f(base.positionX, base.positionY, base.positionZ);
return state; return state;
@@ -264,20 +273,20 @@ LocationDefinition LocationContentLoader::load(
"Missing or duplicate interactive-object id in " + "Missing or duplicate interactive-object id in " +
config.interactiveObjectsJsonPath); config.interactiveObjectsJsonPath);
data.base = parseGameObject(item); data.base = parseGameObject(item);
data.interactionRadius = item.value("interactionRadius", 2.f); data.interactionRadius = item.value<double>("interactionRadius", 2.0);
data.approachRadius = data.approachRadius =
item.value("approachRadius", data.interactionRadius); static_cast<fp>(item.value<double>("approachRadius", data.interactionRadius));
data.activateFunctionName = data.activateFunctionName =
item.value("activateFunction", ""); item.value("activateFunction", "");
data.pivot = vector(item, "pivotX", "pivotY", "pivotZ"); data.pivot = toVectorJson(item, "pivotX", "pivotY", "pivotZ");
data.boundsMin = data.boundsMin =
vector(item, "boundsMinX", "boundsMinY", "boundsMinZ"); toVectorJson(item, "boundsMinX", "boundsMinY", "boundsMinZ");
data.boundsMax = data.boundsMax =
vector(item, "boundsMaxX", "boundsMaxY", "boundsMaxZ"); toVectorJson(item, "boundsMaxX", "boundsMaxY", "boundsMaxZ");
data.hasInteractionPosition = data.hasInteractionPosition =
item.contains("interactionPositionX"); item.contains("interactionPositionX");
if (data.hasInteractionPosition) { if (data.hasInteractionPosition) {
data.interactionPosition = vector( data.interactionPosition = toVec3fp(
item, "interactionPositionX", "interactionPositionY", item, "interactionPositionX", "interactionPositionY",
"interactionPositionZ"); "interactionPositionZ");
} }
@@ -348,20 +357,7 @@ LocationDefinition LocationContentLoader::load(
static_cast<fp>(item.value<double>("visibleRange", static_cast<double>(VISIBLE_RANGE))); static_cast<fp>(item.value<double>("visibleRange", static_cast<double>(VISIBLE_RANGE)));
data.canAttack = item.value("canAttack", false); data.canAttack = item.value("canAttack", false);
data.enabled = item.value("enabled", true); 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 = const char* cooldownKey =
item.contains("navigationRetryCooldownTicks") item.contains("navigationRetryCooldownTicks")
? "navigationRetryCooldownTicks" ? "navigationRetryCooldownTicks"
@@ -394,14 +390,14 @@ LocationDefinition LocationContentLoader::load(
config.teleportsJsonPath); config.teleportsJsonPath);
data.active = item.value("active", false); data.active = item.value("active", false);
data.position = data.position =
vector(item, "positionX", "positionY", "positionZ"); toVectorJson(item, "positionX", "positionY", "positionZ");
data.radius = item.value("radius", 0.f); data.radius = item.value("radius", 0.f);
data.automatic = item.value("automatic", false); data.automatic = item.value("automatic", false);
data.automaticRadius = data.automaticRadius =
item.value("automaticRadius", data.radius); item.value("automaticRadius", data.radius);
data.destinationLocation = data.destinationLocation =
item.value("destinationLocation", ""); item.value("destinationLocation", "");
data.destinationPosition = vector( data.destinationPosition = toVectorJson(
item, "destinationPositionX", "destinationPositionY", item, "destinationPositionX", "destinationPositionY",
"destinationPositionZ"); "destinationPositionZ");
data.destinationRotationY = data.destinationRotationY =
@@ -425,7 +421,7 @@ LocationDefinition LocationContentLoader::load(
data.radius = item.value("radius", 1.5f); data.radius = item.value("radius", 1.5f);
data.hysteresis = item.value("hysteresis", 0.3f); data.hysteresis = item.value("hysteresis", 0.3f);
data.position = data.position =
vector(item, "positionX", "positionY", "positionZ"); toVectorJson(item, "positionX", "positionY", "positionZ");
result.triggerZones.push_back(std::move(data)); result.triggerZones.push_back(std::move(data));
} }
} }
@@ -436,11 +432,11 @@ LocationDefinition LocationContentLoader::load(
PointLightDefinition data; PointLightDefinition data;
data.id = item.value("id", ""); data.id = item.value("id", "");
data.position = data.position =
vector(item, "positionX", "positionY", "positionZ"); toVectorJson(item, "positionX", "positionY", "positionZ");
data.direction = vector( data.direction = toVectorJson(
item, "directionX", "directionY", "directionZ", item, "directionX", "directionY", "directionZ",
Eigen::Vector3f(0.f, -1.f, 0.f)); Eigen::Vector3f(0.f, -1.f, 0.f));
data.color = vector( data.color = toVectorJson(
item, "colorR", "colorG", "colorB", item, "colorR", "colorG", "colorB",
Eigen::Vector3f::Ones()); Eigen::Vector3f::Ones());
data.autoLight = item.value("autoLight", false); data.autoLight = item.value("autoLight", false);
+3 -3
View File
@@ -30,14 +30,14 @@ struct GameObjectData {
struct InteractiveObjectData { struct InteractiveObjectData {
std::string id; std::string id;
GameObjectData base; GameObjectData base;
float interactionRadius = 2.f; float interactionRadius = 2.f; //This is only for detecting mouse/touch event
float approachRadius = 2.f; fp approachRadius = static_cast<fp>(2.f);
std::string activateFunctionName; std::string activateFunctionName;
Eigen::Vector3f pivot = Eigen::Vector3f::Zero(); Eigen::Vector3f pivot = Eigen::Vector3f::Zero();
Eigen::Vector3f boundsMin = Eigen::Vector3f::Zero(); Eigen::Vector3f boundsMin = Eigen::Vector3f::Zero();
Eigen::Vector3f boundsMax = Eigen::Vector3f::Zero(); Eigen::Vector3f boundsMax = Eigen::Vector3f::Zero();
bool hasInteractionPosition = false; bool hasInteractionPosition = false;
Eigen::Vector3f interactionPosition = Eigen::Vector3f::Zero(); Vec3fp interactionPosition;
bool castShadow = true; bool castShadow = true;
bool castShadowNight = true; bool castShadowNight = true;
bool isActive = true; bool isActive = true;
+4 -4
View File
@@ -100,7 +100,7 @@ namespace FRG {
throw std::runtime_error("Interactive object id must not be empty in " + jsonPath); throw std::runtime_error("Interactive object id must not be empty in " + jsonPath);
data.base = parseGameObjectData(item); data.base = parseGameObjectData(item);
data.interactionRadius = item.value("interactionRadius", 2.0f); data.interactionRadius = item.value("interactionRadius", 2.0f);
data.approachRadius = item.value("approachRadius", data.interactionRadius); data.approachRadius = static_cast<fp>(item.value<double>("approachRadius", data.interactionRadius));
data.castShadow = item.value("castShadow", true); data.castShadow = item.value("castShadow", true);
data.castShadowNight = item.value("castShadowNight", true); data.castShadowNight = item.value("castShadowNight", true);
data.isActive = item.value("isActive", true); data.isActive = item.value("isActive", true);
@@ -117,9 +117,9 @@ namespace FRG {
if (item.contains("interactionPositionX")) { if (item.contains("interactionPositionX")) {
data.hasInteractionPosition = true; data.hasInteractionPosition = true;
data.interactionPosition = { data.interactionPosition = {
item.value("interactionPositionX", 0.0f), static_cast<fp>(item.value<double>("interactionPositionX", 0.0)),
item.value("interactionPositionY", 0.0f), static_cast<fp>(item.value<double>("interactionPositionY", 0.0)),
item.value("interactionPositionZ", 0.0f)}; static_cast<fp>(item.value<double>("interactionPositionZ", 0.0))};
} }
if (!data.base.meshPath.empty()) if (!data.base.meshPath.empty())
objects.push_back(std::move(data)); objects.push_back(std::move(data));
+4 -7
View File
@@ -6,6 +6,7 @@
#include <Eigen/Geometry> #include <Eigen/Geometry>
#include "external/nlohmann/json.hpp" #include "external/nlohmann/json.hpp"
#include "ISaveable.h" #include "ISaveable.h"
#include "../navigation/PathFinder.h"
namespace FRG { namespace FRG {
@@ -47,20 +48,16 @@ class InteractiveObjectState : public ISaveable {
public: public:
// --- Creation info (fixed at load time) --- // --- Creation info (fixed at load time) ---
LoadedGameObjectState objectInfo; LoadedGameObjectState objectInfo;
float interactionRadius = 2.f; float interactionRadius = 2.f; //This is only for detecting mouse/touch event
float approachRadius = 2.f; fp approachRadius = static_cast<fp>(2.f);
Eigen::Vector3f pivot = Eigen::Vector3f::Zero(); Eigen::Vector3f pivot = Eigen::Vector3f::Zero();
Eigen::Vector3f boundsMin = Eigen::Vector3f::Zero(); Eigen::Vector3f boundsMin = Eigen::Vector3f::Zero();
Eigen::Vector3f boundsMax = Eigen::Vector3f::Zero(); Eigen::Vector3f boundsMax = Eigen::Vector3f::Zero();
bool hasInteractionPosition = false; bool hasInteractionPosition = false;
Eigen::Vector3f interactionPosition = Eigen::Vector3f::Zero(); Vec3fp interactionPosition;
bool castShadow = true; bool castShadow = true;
bool castShadowNight = true; bool castShadowNight = true;
std::string activateFunctionName; 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 --- // --- Runtime mutable state ---
Eigen::Vector3f position = Eigen::Vector3f::Zero(); Eigen::Vector3f position = Eigen::Vector3f::Zero();