diff --git a/src/Campaign.cpp b/src/Campaign.cpp index ed7336c..68555e7 100644 --- a/src/Campaign.cpp +++ b/src/Campaign.cpp @@ -28,6 +28,12 @@ namespace FRG { return Eigen::Vector3f(j[0].get(), j[1].get(), j[2].get()); } + + static Vec3fp readVec3fp(const json& j) { + if (!j.is_array() || j.size() != 3) return Vec3fp(); + return Vec3fp(static_cast(j[0].get()), static_cast(j[1].get()), static_cast(j[2].get())); + } + bool LocationConfig::fromFile(const std::string& path, const std::string& zipFile, LocationConfig& out) { json j; if (!parseJsonFile(path, zipFile, j)) return false; @@ -43,7 +49,7 @@ namespace FRG { out.teleportsJsonPath = j.value("teleports", ""); out.triggerZonesJsonPath = j.value("triggerZones", ""); out.lightsJsonPath = j.value("lights", ""); - if (j.contains("playerPosition")) out.playerPosition = readVec3(j["playerPosition"]); + if (j.contains("playerPosition")) out.playerPosition = readVec3fp(j["playerPosition"]); out.lockInteractiveObjectsDuringTutorial = j.value("lockInteractiveObjectsDuringTutorial", false); out.loadingScreenPath = j.value("loadingScreen", ""); diff --git a/src/Campaign.h b/src/Campaign.h index 62371ba..797b77a 100644 --- a/src/Campaign.h +++ b/src/Campaign.h @@ -5,6 +5,7 @@ #include #include #include +#include "navigation/PathFinder.h" namespace FRG { @@ -21,7 +22,7 @@ namespace FRG { std::string teleportsJsonPath; std::string triggerZonesJsonPath; std::string lightsJsonPath; - Eigen::Vector3f playerPosition = Eigen::Vector3f::Zero(); + Vec3fp playerPosition; // Where the taxi drives in when the player calls it. No taxi when absent. struct Taxi { diff --git a/src/Character.cpp b/src/Character.cpp index 4dba51e..6d65e18 100644 --- a/src/Character.cpp +++ b/src/Character.cpp @@ -42,19 +42,9 @@ bool Character::isMoving() const { return state && state->isMoving(); } -Eigen::Vector3f Character::getRequestedWalkTarget() const { - auto state = stateSnapshot(); - return state ? state->getRequestedWalkTarget() : Eigen::Vector3f::Zero(); -} - -Eigen::Vector3f Character::getCurrentNavigationTarget() const { - auto state = stateSnapshot(); - return state ? state->getCurrentNavigationTarget() : Eigen::Vector3f::Zero(); -} - Eigen::Vector3f Character::renderPosition() const { auto state = stateSnapshot(); - return state ? interpolatedState(*state).position : Eigen::Vector3f::Zero(); + return state ? interpolatedState(*state).position.toEigen() : Eigen::Vector3f::Zero(); } float Character::getHp() const { @@ -181,8 +171,8 @@ CharacterState Character::interpolatedState(const CharacterState& state) const { const float alpha = (std::max)(0.0f, (std::min)(1.0f, elapsedMs / kSimulationTickMs)); CharacterState result = state; - result.position = previousPresentationSample_.position + - alpha * (currentPresentationSample_.position - previousPresentationSample_.position); + result.position = Vec3fp::fromEigen(previousPresentationSample_.position + + alpha * (currentPresentationSample_.position - previousPresentationSample_.position)); constexpr float kPi = 3.14159265358979323846f; constexpr float kTwoPi = 2.0f * kPi; @@ -190,7 +180,7 @@ CharacterState Character::interpolatedState(const CharacterState& state) const { previousPresentationSample_.facingAngle; while (angleDelta > kPi) angleDelta -= kTwoPi; while (angleDelta < -kPi) angleDelta += kTwoPi; - result.facingAngle = previousPresentationSample_.facingAngle + alpha * angleDelta; + result.facingAngle = static_cast(previousPresentationSample_.facingAngle + alpha * angleDelta); if (previousPresentationSample_.currentState == state.currentState && currentPresentationSample_.currentState == state.currentState) { float previousFrame = previousPresentationSample_.animationFrame; @@ -235,8 +225,8 @@ void Character::update(int64_t deltaMs, const CharacterUpdateEvents* events) { const auto now = std::chrono::steady_clock::now(); PresentationSample sample; - sample.position = state.position; - sample.facingAngle = state.facingAngle; + sample.position = state.position.toEigen(); + sample.facingAngle = static_cast(state.facingAngle); sample.animationFrame = state.animationFrame; sample.currentState = state.currentState; sample.enabled = state.enabled; @@ -299,8 +289,8 @@ void Character::draw(Renderer& renderer) { Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR); renderer.PushMatrix(); - renderer.TranslateMatrix({ state.position.x(), state.position.y(), state.position.z() }); - renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-state.facingAngle, Eigen::Vector3f::UnitY())).toRotationMatrix()); + renderer.TranslateMatrix({ static_cast(state.position.x), static_cast(state.position.y), static_cast(state.position.z) }); + renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-static_cast(state.facingAngle), Eigen::Vector3f::UnitY())).toRotationMatrix()); renderer.ScaleMatrix(state.modelScale); renderer.RotateMatrix(state.modelCorrectionRotation.toRotationMatrix()); @@ -353,8 +343,8 @@ void Character::drawGpuSkinning(Renderer& renderer, const CharacterState& state) Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR); renderer.PushMatrix(); - renderer.TranslateMatrix({ state.position.x(), state.position.y(), state.position.z() }); - renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-state.facingAngle, Eigen::Vector3f::UnitY())).toRotationMatrix()); + renderer.TranslateMatrix({ static_cast(state.position.x), static_cast(state.position.y), static_cast(state.position.z) }); + renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-static_cast(state.facingAngle), Eigen::Vector3f::UnitY())).toRotationMatrix()); renderer.ScaleMatrix(state.modelScale); renderer.RotateMatrix(state.modelCorrectionRotation.toRotationMatrix()); @@ -417,8 +407,8 @@ void Character::drawAttachedWeapon(Renderer& renderer, const CharacterState& sta Eigen::Matrix4f currentBone = anim.gpuSkinningShaderData.skinningMatrices[boneIdx] * bindBone; renderer.PushMatrix(); - renderer.TranslateMatrix({ state.position.x(), state.position.y(), state.position.z() }); - renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-state.facingAngle, Eigen::Vector3f::UnitY())).toRotationMatrix()); + renderer.TranslateMatrix({ static_cast(state.position.x), static_cast(state.position.y), static_cast(state.position.z) }); + renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-static_cast(state.facingAngle), Eigen::Vector3f::UnitY())).toRotationMatrix()); renderer.ScaleMatrix(state.modelScale); renderer.RotateMatrix(state.modelCorrectionRotation.toRotationMatrix()); renderer.TranslateMatrix(Eigen::Vector3f(currentBone.block<3, 1>(0, 3))); @@ -452,8 +442,8 @@ void Character::drawShadowDepthCpu(Renderer& renderer, const CharacterState& sta if (it == animations.end()) return; renderer.PushMatrix(); - renderer.TranslateMatrix({ state.position.x(), state.position.y(), state.position.z() }); - renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-state.facingAngle, Eigen::Vector3f::UnitY())).toRotationMatrix()); + renderer.TranslateMatrix({ static_cast(state.position.x), static_cast(state.position.y), static_cast(state.position.z) }); + renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-static_cast(state.facingAngle), Eigen::Vector3f::UnitY())).toRotationMatrix()); renderer.ScaleMatrix(state.modelScale); renderer.RotateMatrix(state.modelCorrectionRotation.toRotationMatrix()); @@ -488,8 +478,8 @@ void Character::drawShadowDepthGpuSkinning(Renderer& renderer, const CharacterSt renderer.shaderManager.PushShader(shadowSkinningShader); renderer.PushMatrix(); - renderer.TranslateMatrix({ state.position.x(), state.position.y(), state.position.z() }); - renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-state.facingAngle, Eigen::Vector3f::UnitY())).toRotationMatrix()); + renderer.TranslateMatrix({ static_cast(state.position.x), static_cast(state.position.y), static_cast(state.position.z) }); + renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-static_cast(state.facingAngle), Eigen::Vector3f::UnitY())).toRotationMatrix()); renderer.ScaleMatrix(state.modelScale); renderer.RotateMatrix(state.modelCorrectionRotation.toRotationMatrix()); @@ -561,8 +551,8 @@ void Character::drawCpuWithShadow(Renderer& renderer, const CharacterState& stat Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR); renderer.PushMatrix(); - renderer.TranslateMatrix({ state.position.x(), state.position.y(), state.position.z() }); - renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-state.facingAngle, Eigen::Vector3f::UnitY())).toRotationMatrix()); + renderer.TranslateMatrix({ static_cast(state.position.x), static_cast(state.position.y), static_cast(state.position.z) }); + renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-static_cast(state.facingAngle), Eigen::Vector3f::UnitY())).toRotationMatrix()); renderer.ScaleMatrix(state.modelScale); renderer.RotateMatrix(state.modelCorrectionRotation.toRotationMatrix()); @@ -623,8 +613,8 @@ void Character::drawGpuSkinningWithShadow(Renderer& renderer, const CharacterSta Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR); renderer.PushMatrix(); - renderer.TranslateMatrix({ state.position.x(), state.position.y(), state.position.z() }); - renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-state.facingAngle, Eigen::Vector3f::UnitY())).toRotationMatrix()); + renderer.TranslateMatrix({ static_cast(state.position.x), static_cast(state.position.y), static_cast(state.position.z) }); + renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-static_cast(state.facingAngle), Eigen::Vector3f::UnitY())).toRotationMatrix()); renderer.ScaleMatrix(state.modelScale); renderer.RotateMatrix(state.modelCorrectionRotation.toRotationMatrix()); @@ -726,8 +716,8 @@ void Character::drawNightGpuSkinning(Renderer& renderer, Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR); renderer.PushMatrix(); - renderer.TranslateMatrix({ state.position.x(), state.position.y(), state.position.z() }); - renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-state.facingAngle, Eigen::Vector3f::UnitY())).toRotationMatrix()); + renderer.TranslateMatrix({ static_cast(state.position.x), static_cast(state.position.y), static_cast(state.position.z) }); + renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-static_cast(state.facingAngle), Eigen::Vector3f::UnitY())).toRotationMatrix()); renderer.ScaleMatrix(state.modelScale); renderer.RotateMatrix(state.modelCorrectionRotation.toRotationMatrix()); @@ -798,8 +788,8 @@ void Character::drawNightCpu(Renderer& renderer, Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR); renderer.PushMatrix(); - renderer.TranslateMatrix({ state.position.x(), state.position.y(), state.position.z() }); - renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-state.facingAngle, Eigen::Vector3f::UnitY())).toRotationMatrix()); + renderer.TranslateMatrix({ static_cast(state.position.x), static_cast(state.position.y), static_cast(state.position.z) }); + renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-static_cast(state.facingAngle), Eigen::Vector3f::UnitY())).toRotationMatrix()); renderer.ScaleMatrix(state.modelScale); renderer.RotateMatrix(state.modelCorrectionRotation.toRotationMatrix()); @@ -900,8 +890,8 @@ void Character::drawNightGpuSkinningWithShadow(Renderer& renderer, Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR); renderer.PushMatrix(); - renderer.TranslateMatrix({ state.position.x(), state.position.y(), state.position.z() }); - renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-state.facingAngle, Eigen::Vector3f::UnitY())).toRotationMatrix()); + renderer.TranslateMatrix({ static_cast(state.position.x), static_cast(state.position.y), static_cast(state.position.z) }); + renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-static_cast(state.facingAngle), Eigen::Vector3f::UnitY())).toRotationMatrix()); renderer.ScaleMatrix(state.modelScale); renderer.RotateMatrix(state.modelCorrectionRotation.toRotationMatrix()); @@ -985,8 +975,8 @@ void Character::drawNightCpuWithShadow(Renderer& renderer, Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR); renderer.PushMatrix(); - renderer.TranslateMatrix({ state.position.x(), state.position.y(), state.position.z() }); - renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-state.facingAngle, Eigen::Vector3f::UnitY())).toRotationMatrix()); + renderer.TranslateMatrix({ static_cast(state.position.x), static_cast(state.position.y), static_cast(state.position.z) }); + renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-static_cast(state.facingAngle), Eigen::Vector3f::UnitY())).toRotationMatrix()); renderer.ScaleMatrix(state.modelScale); renderer.RotateMatrix(state.modelCorrectionRotation.toRotationMatrix()); @@ -1027,7 +1017,7 @@ void Character::setupHitSparks(std::shared_ptr sparkTexture) { } void Character::presentDamage(float hp, float maxHp, - const Eigen::Vector3f& attackDirection, + const Vec3fp& attackDirection, const std::string& attackerId) { if (onHpChanged) onHpChanged(hp, maxHp, attackerId); @@ -1037,9 +1027,9 @@ void Character::presentDamage(float hp, float maxHp, if (!stateSnapshot) return; const CharacterState& state = *stateSnapshot; - Eigen::Vector3f emitPos = state.position + Eigen::Vector3f(0.f, 1.f, 0.f); + Eigen::Vector3f emitPos = state.position.toEigen() + Eigen::Vector3f(0.f, 1.f, 0.f); hitSparkEmitter.resetEmissionPoints({ emitPos }); - hitSparkEmitter.setEmissionDirection(attackDirection); + hitSparkEmitter.setEmissionDirection(attackDirection.toEigen()); static std::mt19937 gen(std::random_device{}()); std::uniform_int_distribution countDist(10, 15); @@ -1065,7 +1055,7 @@ static void drawCharacterNameLabel( if (!state.enabled || state.hp <= 0.f || displayName.empty()) return; Eigen::Vector4f worldPos( - state.position.x(), state.position.y() + 1.83f, state.position.z(), 1.f); + static_cast(state.position.x), static_cast(state.position.y) + 1.83f, static_cast(state.position.z), 1.f); Eigen::Vector4f clip = projectionMatrix * cameraViewMatrix * worldPos; if (clip.w() <= 0.f) return; @@ -1126,7 +1116,7 @@ void Character::drawHealthBar(Renderer& renderer, if (state.hp <= 0.f) return; if (state.initialHp <= 0.f) return; - Eigen::Vector4f worldPos(state.position.x(), state.position.y() + 1.75f, state.position.z(), 1.f); + Eigen::Vector4f worldPos(static_cast(state.position.x), static_cast(state.position.y) + 1.75f, static_cast(state.position.z), 1.f); Eigen::Vector4f clip = projectionMatrix * cameraViewMatrix * worldPos; if (clip.w() <= 0.f) return; float ndcX = clip.x() / clip.w(); diff --git a/src/Character.h b/src/Character.h index e71371f..82ab82c 100644 --- a/src/Character.h +++ b/src/Character.h @@ -55,7 +55,7 @@ public: void update(int64_t deltaMs, const CharacterUpdateEvents* events = nullptr); // ---- Damage ---- - void presentDamage(float hp, float maxHp, const Eigen::Vector3f& attackDirection, + void presentDamage(float hp, float maxHp, const Vec3fp& attackDirection, const std::string& attackerId); // ---- Spark setup ---- @@ -93,8 +93,6 @@ public: // ---- Query helpers (read immutable snapshots) ---- bool isMoving() const; - Eigen::Vector3f getRequestedWalkTarget() const; - Eigen::Vector3f getCurrentNavigationTarget() const; Eigen::Vector3f renderPosition() const; float getHp() const; diff --git a/src/CharacterState.cpp b/src/CharacterState.cpp index fb2daff..011210d 100644 --- a/src/CharacterState.cpp +++ b/src/CharacterState.cpp @@ -9,7 +9,13 @@ namespace { // Tick counts are authoritative and therefore independent of render rate. constexpr float ATTACK_COOLDOWN_TIME = 1.6f; constexpr float HOME_DRIFT_CHECK_INTERVAL = 5.0f; +fp HOME_DRIFT_CHECK_INTERVAL_FP = static_cast(HOME_DRIFT_CHECK_INTERVAL); constexpr float TARGET_REPLAN_THRESHOLD = 0.25f; + +fp TARGET_REPLAN_THRESHOLD_SQUARED = static_cast(TARGET_REPLAN_THRESHOLD * TARGET_REPLAN_THRESHOLD); + + + constexpr float PI = 3.14159265358979323846f; constexpr int BLOCKED_REPLAN_TICKS = 3; @@ -25,8 +31,8 @@ std::uint32_t stableIdHash(const std::string& value) } void CharacterState::configureAsPlayer() { - walkSpeed = 3.0f; - rotationSpeed = 8.0f; + walkSpeed = static_cast(3.0f); + rotationSpeed = static_cast < fp>(8.0f); modelScale = 1.f; modelCorrectionRotation = Eigen::Quaternionf( Eigen::AngleAxisf(PI, Eigen::Vector3f::UnitY()) * @@ -66,12 +72,12 @@ void CharacterState::scheduleNextNavigationRetry() } bool CharacterState::isMoving() const { - Eigen::Vector3f toTarget = walkTarget - position; - toTarget.y() = 0.f; - return !pathWaypoints.empty() || toTarget.norm() > kWalkThreshold; + Vec3fp toTarget = walkTarget - position; + toTarget.y = static_cast(0.f); + return !pathWaypoints.empty() || toTarget.normSquared() > kWalkThreshold_SQUARED(); } -Eigen::Vector3f CharacterState::getCurrentNavigationTarget() const { +Vec3fp CharacterState::getCurrentNavigationTarget() const { if (!pathWaypoints.empty() && currentWaypointIndex < pathWaypoints.size()) { return pathWaypoints[currentWaypointIndex]; } @@ -79,7 +85,7 @@ Eigen::Vector3f CharacterState::getCurrentNavigationTarget() const { } void CharacterState::stopInPlace() { - walkTarget = Eigen::Vector3f(position.x(), 0.f, position.z()); + walkTarget = Vec3fp(position.x, static_cast(0.f), position.z); requestedWalkTarget = walkTarget; pathWaypoints.clear(); currentWaypointIndex = 0; @@ -90,20 +96,20 @@ void CharacterState::stopInPlace() { } void CharacterState::setTarget( - const Eigen::Vector3f& target, + const Vec3fp& target, std::string callbackName, const LocationNetworkState& location, const std::string& entityId) { - const Eigen::Vector3f normalizedTarget(target.x(), 0.f, target.z()); + const Vec3fp normalizedTarget(target.x, static_cast(0.f), target.z); const bool sameRequestedTarget = - (normalizedTarget - requestedWalkTarget).norm() <= TARGET_REPLAN_THRESHOLD; + (normalizedTarget - requestedWalkTarget).normSquared() <= TARGET_REPLAN_THRESHOLD_SQUARED; const bool alreadyMovingToTarget = !pathWaypoints.empty() || - (walkTarget - normalizedTarget).norm() <= TARGET_REPLAN_THRESHOLD; + (walkTarget - normalizedTarget).normSquared() <= TARGET_REPLAN_THRESHOLD_SQUARED; const bool stoppedAfterFailedPath = pathWaypoints.empty() && - (walkTarget - position).norm() <= TARGET_REPLAN_THRESHOLD; + (walkTarget - position).normSquared() <= TARGET_REPLAN_THRESHOLD_SQUARED; if (callbackName.empty() && sameRequestedTarget && (alreadyMovingToTarget || stoppedAfterFailedPath)) { return; @@ -119,12 +125,15 @@ void CharacterState::setTarget( currentWaypointIndex = 0; if (!pathWaypoints.empty()) { - for (Eigen::Vector3f& waypoint : pathWaypoints) waypoint.y() = 0.f; + for (Vec3fp& waypoint : pathWaypoints) + { + waypoint.y = static_cast(0.f); + } walkTarget = pathWaypoints.back(); return; } - walkTarget = Eigen::Vector3f(position.x(), 0.f, position.z()); + walkTarget = Vec3fp(position.x, static_cast(0.f), position.z); if (!isPlayer) scheduleNextNavigationRetry(); } @@ -132,8 +141,8 @@ void CharacterState::forceReplan( const LocationNetworkState& location, const std::string& entityId) { - const Eigen::Vector3f normalizedTarget( - requestedWalkTarget.x(), 0.f, requestedWalkTarget.z()); + const Vec3fp normalizedTarget( + requestedWalkTarget.x, static_cast(0.f), requestedWalkTarget.z); pathWaypoints = location.navigationMaps[location.activeNavigationIndex].findPathToNearest(this->position, normalizedTarget); @@ -141,13 +150,16 @@ void CharacterState::forceReplan( movementBlockedTicks = 0; navigationRetryTicks = 0; if (!pathWaypoints.empty()) { - for (Eigen::Vector3f& waypoint : pathWaypoints) waypoint.y() = 0.f; + for (Vec3fp& waypoint : pathWaypoints) + { + waypoint.y = static_cast(0.f); + } walkTarget = pathWaypoints.back(); blockingCharacterId.clear(); return; } - walkTarget = Eigen::Vector3f(position.x(), 0.f, position.z()); + walkTarget = Vec3fp(position.x, static_cast(0.f), position.z); if (!isPlayer) scheduleNextNavigationRetry(); } @@ -209,16 +221,16 @@ CharacterUpdateEvents CharacterState::update( attack = 0; attackTargetId.clear(); } else { - const float distance = (attackTarget->position - position).norm(); - if (distance >= visibleRange) { - if ((attackTarget->position - requestedWalkTarget).norm() > - TARGET_REPLAN_THRESHOLD) { + const fp distanceSquared = (attackTarget->position - position).normSquared(); + if (distanceSquared >= visibleRange* visibleRange) { + if ((attackTarget->position - requestedWalkTarget).normSquared() > + TARGET_REPLAN_THRESHOLD_SQUARED) { events.navigationRequested = true; events.navigationTarget = attackTarget->position; } battle_state = 0; - } else if (distance >= 1.25f) { - if ((attackTarget->position - requestedWalkTarget).norm() > TARGET_REPLAN_THRESHOLD) { + } else if (distanceSquared >= static_cast(1.25f*1.25f)) { + if ((attackTarget->position - requestedWalkTarget).normSquared() > TARGET_REPLAN_THRESHOLD_SQUARED) { events.navigationRequested = true; events.navigationTarget = attackTarget->position; } @@ -228,21 +240,22 @@ CharacterUpdateEvents CharacterState::update( } } - const Eigen::Vector3f activeTarget = getCurrentNavigationTarget(); - const Eigen::Vector3f lookTarget = + const Vec3fp activeTarget = getCurrentNavigationTarget(); + const Vec3fp lookTarget = battle_state == 1 && attackTarget ? attackTarget->position : activeTarget; if (currentState == AnimationState::WALK || currentState == AnimationState::STAND) { - Eigen::Vector3f toTarget = activeTarget - position; - toTarget.y() = 0.f; - const float distance = toTarget.norm(); - if (distance > kWalkThreshold) { - const Eigen::Vector3f direction = toTarget / distance; - const float moveAmount = walkSpeed * static_cast(deltaMs) / 1000.f; - Eigen::Vector3f candidate = + Vec3fp toTarget = activeTarget - position; + toTarget.y = static_cast(0.f); + const fp distanceSquared = toTarget.normSquared(); + if (distanceSquared > kWalkThreshold_SQUARED()) { + fp distance = sqrt(distanceSquared); //FP SQRT + const Vec3fp direction = toTarget / distance; + const fp moveAmount = walkSpeed * static_cast(deltaMs) / static_cast < fp>(1000.f); + Vec3fp candidate = moveAmount >= distance ? activeTarget : position + direction * moveAmount; - candidate.y() = 0.f; - const CharacterMovementObstacle* blocker = nullptr; + candidate.y = static_cast(0.f); + /*const CharacterMovementObstacle* blocker = nullptr; if (context.movementObstacles) { for (const CharacterMovementObstacle& obstacle : *context.movementObstacles) { @@ -296,15 +309,15 @@ CharacterUpdateEvents CharacterState::update( navigationPausedThisTick = true; } } - } else { + } else*/{ movedLastTick = - (candidate - position).squaredNorm() > 1e-8f; + (candidate - position).normSquared() > static_cast(1e-8f); position = candidate; movementBlockedTicks = 0; blockingCharacterId.clear(); } - targetFacingAngle = std::atan2(direction.x(), -direction.z()); - if (!blocker) currentState = AnimationState::WALK; + targetFacingAngle = fpm::atan2(direction.x, -direction.z); + currentState = AnimationState::WALK; } else { currentState = AnimationState::STAND; const bool completedPath = !pathWaypoints.empty(); @@ -316,8 +329,8 @@ CharacterUpdateEvents CharacterState::update( pathWaypoints.clear(); currentWaypointIndex = 0; if (completedPath && - (requestedWalkTarget - position).norm() > - TARGET_REPLAN_THRESHOLD && + (requestedWalkTarget - position).normSquared() > + TARGET_REPLAN_THRESHOLD_SQUARED && !isPlayer) scheduleNextNavigationRetry(); if (completedPath && !onArrivedCallbackName.empty()) { @@ -327,11 +340,11 @@ CharacterUpdateEvents CharacterState::update( } if (faceTarget) { - Eigen::Vector3f toFace = faceTarget->position - position; - toFace.y() = 0.f; - const float distanceToFace = toFace.norm(); - targetFacingAngle = distanceToFace > 1e-3f - ? std::atan2(toFace.x() / distanceToFace, -toFace.z() / distanceToFace) + Vec3fp toFace = faceTarget->position - position; + toFace.y = static_cast(0.f); + const fp distanceToFace = sqrt(toFace.normSquared()); //FP SQRT + targetFacingAngle = distanceToFace > static_cast(1e-3f) + ? fpm::atan2(toFace.x / distanceToFace, -toFace.z / distanceToFace) : facingAngle; } else if (!faceTargetId.empty()) { faceTargetId.clear(); @@ -340,7 +353,7 @@ CharacterUpdateEvents CharacterState::update( } if (!isPlayer && !navigationPausedThisTick && pathWaypoints.empty() && - (requestedWalkTarget - position).norm() > TARGET_REPLAN_THRESHOLD && + (requestedWalkTarget - position).normSquared() > TARGET_REPLAN_THRESHOLD_SQUARED && !events.navigationRequested) { if (navigationRetryTicks > 0) --navigationRetryTicks; if (navigationRetryTicks == 0) { @@ -359,8 +372,8 @@ CharacterUpdateEvents CharacterState::update( attackTargetId.clear(); } } else if (battle_state == 1 && attackTarget) { - targetFacingAngle = std::atan2( - lookTarget.x() - position.x(), -(lookTarget.z() - position.z())); + targetFacingAngle = fpm::atan2( + lookTarget.x - position.x, -(lookTarget.z - position.z)); if (currentState == AnimationState::STAND || currentState == AnimationState::WALK) { currentState = AnimationState::STAND_TO_ACTION; resetAnim = true; @@ -384,24 +397,25 @@ CharacterUpdateEvents CharacterState::update( resetAnim = true; } - float angleDiff = targetFacingAngle - facingAngle; - while (angleDiff > PI) angleDiff -= 2.f * PI; - while (angleDiff < -PI) angleDiff += 2.f * PI; - const float rotationStep = rotationSpeed * static_cast(deltaMs) / 1000.f; - if (std::fabs(angleDiff) <= rotationStep) { + fp angleDiff = targetFacingAngle - facingAngle; + const fp PIfp = static_cast(PI); + while (angleDiff > PIfp) angleDiff -= static_cast(2.f) * PIfp; + while (angleDiff < -PIfp) angleDiff += static_cast(2.f) * PIfp; + const fp rotationStep = rotationSpeed * static_cast(deltaMs) / static_cast(1000.f); + if (fpm::abs(angleDiff) <= rotationStep) { facingAngle = targetFacingAngle; faceTargetId.clear(); } else { - facingAngle += angleDiff > 0.f ? rotationStep : -rotationStep; + facingAngle += angleDiff > static_cast(0.f) ? rotationStep : -rotationStep; } - if (!isPlayer && hp > 0.f && battle_state == 0 && maxHomeDrift > 0.f) { - homeDriftCheckTimer -= static_cast(deltaMs) / 1000.f; - if (homeDriftCheckTimer <= 0.f) { - homeDriftCheckTimer = HOME_DRIFT_CHECK_INTERVAL; - Eigen::Vector3f toHome = homePosition - position; - toHome.y() = 0.f; - if (!isMoving() && toHome.norm() > maxHomeDrift) { + if (!isPlayer && hp > 0.f && battle_state == 0 && maxHomeDriftSquared > static_cast(0.f)) { + homeDriftCheckTimer -= static_cast(deltaMs) / static_cast(1000.f); + if (homeDriftCheckTimer <= static_cast (0.f)) { + homeDriftCheckTimer = HOME_DRIFT_CHECK_INTERVAL_FP; + Vec3fp toHome = homePosition - position; + toHome.y = static_cast(0.f); + if (!isMoving() && toHome.normSquared() > maxHomeDriftSquared) { events.navigationRequested = true; events.navigationTarget = homePosition; } @@ -424,10 +438,18 @@ CharacterUpdateEvents CharacterState::update( events.attackImpact = true; events.attackTargetId = attackTargetId; events.attackDirection = attackTarget->position - position; - events.attackDirection.y() = 0.f; - const float length = events.attackDirection.norm(); - if (length > 1e-3f) events.attackDirection /= length; - else events.attackDirection = Eigen::Vector3f::Zero(); + events.attackDirection.y = static_cast(0.f); + const fp lengthSquared = fpm::sqrt(events.attackDirection.normSquared()); + if (lengthSquared > static_cast(1e-3f * 1e-3f)) + { + const fp length = fpm::sqrt(lengthSquared); //FP SQRT + + events.attackDirection = events.attackDirection / length; + } + else + { + events.attackDirection = Vec3fp{}; + } } showWeapon = @@ -463,9 +485,9 @@ CharacterUpdateEvents CharacterState::update( void CharacterState::save(nlohmann::json& out) const { - out["position"] = { position.x(), position.y(), position.z() }; - out["facingAngle"] = facingAngle; - out["targetFacingAngle"] = targetFacingAngle; + out["position"] = { static_cast(position.x), static_cast(position.y), static_cast(position.z) }; + out["facingAngle"] = static_cast(facingAngle); + out["targetFacingAngle"] = static_cast(targetFacingAngle); out["hp"] = hp; out["enabled"] = enabled; out["battle_state"] = battle_state; @@ -474,16 +496,16 @@ void CharacterState::save(nlohmann::json& out) const out["showWeapon"] = showWeapon; out["attackTargetId"] = attackTargetId; out["faceTargetId"] = faceTargetId; - out["homePosition"] = { homePosition.x(), homePosition.y(), homePosition.z() }; - out["walkTarget"] = { walkTarget.x(), walkTarget.y(), walkTarget.z() }; - out["requestedWalkTarget"] = { requestedWalkTarget.x(), requestedWalkTarget.y(), requestedWalkTarget.z() }; + out["homePosition"] = { static_cast(homePosition.x), static_cast(homePosition.y), static_cast(homePosition.z) }; + out["walkTarget"] = { static_cast(walkTarget.x), static_cast(walkTarget.y), static_cast(walkTarget.z) }; + out["requestedWalkTarget"] = { static_cast(requestedWalkTarget.x), static_cast(requestedWalkTarget.y), static_cast(requestedWalkTarget.z) }; nlohmann::json wpArr = nlohmann::json::array(); for (const auto& wp : pathWaypoints) { - wpArr.push_back({ wp.x(), wp.y(), wp.z() }); + wpArr.push_back({ static_cast(wp.x), static_cast(wp.y), static_cast(wp.z) }); } out["pathWaypoints"] = std::move(wpArr); out["currentWaypointIndex"] = currentWaypointIndex; - out["homeDriftCheckTimer"] = homeDriftCheckTimer; + out["homeDriftCheckTimer"] = static_cast(homeDriftCheckTimer); out["movementBlockedTicks"] = movementBlockedTicks; out["navigationRetryTicks"] = navigationRetryTicks; out["movedLastTick"] = movedLastTick; @@ -492,7 +514,7 @@ void CharacterState::save(nlohmann::json& out) const if (!blockingCharacterId.empty()) out["blockingCharacterId"] = blockingCharacterId; out["animationFrame"] = animationFrame; - out["visibleRange"] = visibleRange; + out["visibleRange"] = static_cast(visibleRange); if (!onArrivedCallbackName.empty()) out["onArrivedCallbackName"] = onArrivedCallbackName; } @@ -505,11 +527,19 @@ void CharacterState::load(const nlohmann::json& in) return def; }; - if (in.contains("position")) position = readVec3(in["position"], position); - facingAngle = in.value("facingAngle", facingAngle); - targetFacingAngle = in.value("targetFacingAngle", targetFacingAngle); + auto readVec3fp = [](const nlohmann::json& j, Vec3fp def) -> Vec3fp { + if (j.is_array() && j.size() == 3) + return Vec3fp{ static_cast(j[0].get()), static_cast(j[1].get()), static_cast(j[2].get()) }; + return def; + }; + + if (in.contains("position")) { + position = readVec3fp(in["position"], position); + } + facingAngle = static_cast(in.value("facingAngle", static_cast(facingAngle))); + targetFacingAngle = static_cast(in.value("targetFacingAngle", static_cast(targetFacingAngle))); hp = in.value("hp", hp); - visibleRange = in.value("visibleRange", visibleRange); + visibleRange = static_cast(in.value("visibleRange", static_cast(visibleRange))); enabled = in.value("enabled", enabled); battle_state = in.value("battle_state", battle_state); currentState = static_cast(in.value("currentState", static_cast(currentState))); @@ -517,17 +547,17 @@ void CharacterState::load(const nlohmann::json& in) showWeapon = in.value("showWeapon", showWeapon); attackTargetId = in.value("attackTargetId", attackTargetId); faceTargetId = in.value("faceTargetId", faceTargetId); - if (in.contains("homePosition")) homePosition = readVec3(in["homePosition"], homePosition); - if (in.contains("walkTarget")) walkTarget = readVec3(in["walkTarget"], walkTarget); - if (in.contains("requestedWalkTarget")) requestedWalkTarget = readVec3(in["requestedWalkTarget"], requestedWalkTarget); + if (in.contains("homePosition")) homePosition = readVec3fp(in["homePosition"], homePosition); + if (in.contains("walkTarget")) walkTarget = readVec3fp(in["walkTarget"], walkTarget); + if (in.contains("requestedWalkTarget")) requestedWalkTarget = readVec3fp(in["requestedWalkTarget"], requestedWalkTarget); pathWaypoints.clear(); if (in.contains("pathWaypoints") && in["pathWaypoints"].is_array()) { for (const auto& wp : in["pathWaypoints"]) { - pathWaypoints.push_back(readVec3(wp, Eigen::Vector3f::Zero())); + pathWaypoints.push_back(readVec3fp(wp, Vec3fp())); } } currentWaypointIndex = in.value("currentWaypointIndex", currentWaypointIndex); - homeDriftCheckTimer = in.value("homeDriftCheckTimer", homeDriftCheckTimer); + homeDriftCheckTimer = static_cast(in.value("homeDriftCheckTimer", static_cast(homeDriftCheckTimer))); movementBlockedTicks = in.value("movementBlockedTicks", movementBlockedTicks); navigationRetryTicks = in.value("navigationRetryTicks", navigationRetryTicks); movedLastTick = in.value("movedLastTick", movedLastTick); diff --git a/src/CharacterState.h b/src/CharacterState.h index fffb505..384cd2e 100644 --- a/src/CharacterState.h +++ b/src/CharacterState.h @@ -61,6 +61,9 @@ public: // Distance below which the character is considered to have reached its target. static constexpr float kWalkThreshold = 0.05f; + static fp kWalkThreshold_SQUARED() { return static_cast(kWalkThreshold * kWalkThreshold); }; + + // --- Identity --- std::string npcName; bool isPlayer = false; @@ -68,15 +71,15 @@ public: bool canAttack = false; // --- Position / orientation --- - Eigen::Vector3f position = Eigen::Vector3f::Zero(); - float facingAngle = 0.0f; - float targetFacingAngle = 0.0f; + Vec3fp position; + fp facingAngle; + fp targetFacingAngle; // --- Health --- float hp = 200.f; float initialHp = 200.f; - float visibleRange = VISIBLE_RANGE; + fp visibleRange = static_cast(VISIBLE_RANGE); // --- Combat flags --- int battle_state = 0; @@ -95,8 +98,8 @@ public: std::string faceTargetId; // --- Home position for drift recovery --- - Eigen::Vector3f homePosition = Eigen::Vector3f::Zero(); - float maxHomeDrift = 3.0f; + Vec3fp homePosition; + fp maxHomeDriftSquared = static_cast(3.f*3.f); // --- Authoritative logical animation clock --- // Frame counts are immutable metadata copied from loaded clips during @@ -106,26 +109,27 @@ public: std::array animationFrameCounts = {{1, 1, 1, 1, 1, 1, 1, 1, 1}}; // --- Movement / visual parameters --- - float walkSpeed = 3.0f; - float rotationSpeed = 4.0f; + fp walkSpeed = static_cast(3.0f); + fp rotationSpeed = static_cast(4.0f); + float modelScale = 0.12f; Eigen::Quaternionf modelCorrectionRotation = Eigen::Quaternionf::Identity(); bool useGpuSkinning = true; // Full clearance used while planning around stationary characters. - float collisionRadius = 0.45f; + //fp collisionRadius = static_cast(0.45f); // Smaller physical contact threshold used by the movement guard. - float movementContactRadius = 0.32f; - float interactionRadius = 0.0f; + //fp movementContactRadius = static_cast(0.32f); + fp interactionRadius = static_cast(0.0f); bool movedLastTick = false; // --- Pathfinding data (serializable parts) --- - Eigen::Vector3f walkTarget = Eigen::Vector3f::Zero(); - Eigen::Vector3f requestedWalkTarget = Eigen::Vector3f::Zero(); - std::vector pathWaypoints; + Vec3fp walkTarget; + Vec3fp requestedWalkTarget; + std::vector pathWaypoints; size_t currentWaypointIndex = 0; // Internal drift-check timer (serializable so it survives save/load). - float homeDriftCheckTimer = 5.0f; + fp homeDriftCheckTimer = static_cast(5.0f); int movementBlockedTicks = 0; int navigationRetryTicks = 0; std::string blockingCharacterId; @@ -145,10 +149,10 @@ public: const std::string& entityId, std::vector cooldownTicks = {}); bool isMoving() const; - const Eigen::Vector3f& getRequestedWalkTarget() const { return requestedWalkTarget; } - Eigen::Vector3f getCurrentNavigationTarget() const; + const Vec3fp& getRequestedWalkTarget() const { return requestedWalkTarget; } + Vec3fp getCurrentNavigationTarget() const; void stopInPlace(); - void setTarget(const Eigen::Vector3f& target, + void setTarget(const Vec3fp& target, std::string callbackName, const LocationNetworkState& location, const std::string& entityId); @@ -173,23 +177,14 @@ using PlayerStateMap = std::unordered_map>; struct CharacterObservation { - Eigen::Vector3f position = Eigen::Vector3f::Zero(); + Vec3fp position; float hp = 0.f; }; -struct CharacterMovementObstacle { - std::string id; - Eigen::Vector3f position = Eigen::Vector3f::Zero(); - float radius = 0.f; - bool moving = false; - Eigen::Vector3f movementDirection = Eigen::Vector3f::Zero(); -}; - struct CharacterUpdateContext { std::string characterId; const CharacterObservation* attackTarget = nullptr; const CharacterObservation* faceTarget = nullptr; - const std::vector* movementObstacles = nullptr; }; struct CharacterUpdateEvents { @@ -197,11 +192,11 @@ struct CharacterUpdateEvents { bool deathAnimationComplete = false; bool navigationRequested = false; bool forceNavigationReplan = false; - Eigen::Vector3f navigationTarget = Eigen::Vector3f::Zero(); + Vec3fp navigationTarget; std::string blockingCharacterId; std::string arrivedCallbackName; std::string attackTargetId; - Eigen::Vector3f attackDirection = Eigen::Vector3f::Zero(); + Vec3fp attackDirection; }; } // namespace FRG diff --git a/src/Game.cpp b/src/Game.cpp index 7bb1e9c..21058a3 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -664,9 +664,9 @@ namespace FRG if (!loc) return; if (loc->player) { server().submit(SetCharacterPositionCommand{ - destName, loc->player->entityId(), destPos}); + destName, loc->player->entityId(), Vec3fp::fromEigen(destPos)}); server().submit(SetCharacterFacingAngleCommand{ - destName, loc->player->entityId(), destRotY, true}); + destName, loc->player->entityId(), static_cast(destRotY), true}); loc->stopCharacter(*loc->player); } loc->localState().cameraAzimuth = destRotY; diff --git a/src/Location.cpp b/src/Location.cpp index 9a8875a..5d30d24 100644 --- a/src/Location.cpp +++ b/src/Location.cpp @@ -126,7 +126,7 @@ namespace FRG } void Location::setCharacterTarget( - Character& character, const Eigen::Vector3f& target, std::string callbackName) + Character& character, const Vec3fp& target, std::string callbackName) { gameServer_.submit(SetCharacterDestinationCommand{ locationId_, character.entityId(), target, std::move(callbackName)}); @@ -1597,7 +1597,7 @@ namespace FRG if (playerState && !pointLights.empty()) { float minDist = FLT_MAX; for (int i = 0; i < static_cast(pointLights.size()); ++i) { - const float d = (pointLights[i].position - playerState->position).norm(); + const float d = (pointLights[i].position - playerState->position.toEigen()).norm(); if (d < minDist) { minDist = d; nearestLightIdx = i; } } } @@ -1611,12 +1611,12 @@ namespace FRG if (!active && playerState) { if (pl.autoLightLimitX > 0.0f && pl.autoLightLimitY > 0.0f) { // Rectangular activation zone — independent of nearest-light heuristic - const float dx = std::abs(playerState->position.x() - pl.position.x()); - const float dz = std::abs(playerState->position.z() - pl.position.z()); + const float dx = std::abs(static_cast(playerState->position.x) - pl.position.x()); + const float dz = std::abs(static_cast(playerState->position.z) - pl.position.z()); active = (dx <= pl.autoLightLimitX && dz <= pl.autoLightLimitY); } else { // only the nearest autoLight activates, with optional distance cap - const float dist = (pl.position - playerState->position).norm(); + const float dist = (pl.position - playerState->position.toEigen()).norm(); active = (i == nearestLightIdx) && (pl.autoLightDistance <= 0.0f || dist <= pl.autoLightDistance); } @@ -1632,7 +1632,7 @@ namespace FRG if (playerState && !activeLightIndices.empty()) { float minDist = FLT_MAX; for (int idx : activeLightIndices) { - const float d = (pointLights[idx].position - playerState->position).norm(); + const float d = (pointLights[idx].position - playerState->position.toEigen()).norm(); if (d < minDist) { minDist = d; shadowLight = &pointLights[idx]; } } } @@ -1850,37 +1850,6 @@ namespace FRG return true; } - void Location::nudgeCharacterAside(Character* standing, const Eigen::Vector3f& awayFrom) - { - auto standingState = standing ? standing->stateSnapshot() : nullptr; - if (!standingState || standingState->isPlayer) return; - auto locationState = stateSnapshot(); - if (!locationState || locationState->activeNavigationIndex < 0 || - locationState->activeNavigationIndex >= - static_cast(locationState->navigationMaps.size())) return; - const PathFinder& navigation = - locationState->navigationMaps[locationState->activeNavigationIndex]; - if (navigation.isReady()) return; - - static constexpr float kNudgeDist = 1.2f; - - Eigen::Vector3f dir = standingState->position - awayFrom; - dir.y() = 0.f; - if (dir.norm() < 1e-3f) dir = Eigen::Vector3f(1.f, 0.f, 0.f); - else dir.normalize(); - - const float angles[] = { 0.f, static_cast(M_PI * 0.5), static_cast(-M_PI * 0.5), static_cast(M_PI) }; - for (float angle : angles) { - Eigen::Vector3f rotated = Eigen::AngleAxisf(angle, Eigen::Vector3f::UnitY()) * dir; - Eigen::Vector3f candidate = standingState->position + rotated * kNudgeDist; - candidate.y() = 0.f; - if (navigation.isWalkable(candidate)) { - setCharacterTarget(*standing, candidate); - return; - } - } - } - void Location::update(int64_t delta, const LocationSimulationEvents& events) { synchronizeRemotePlayers(); @@ -1951,7 +1920,7 @@ namespace FRG player->update(delta, eventFor(gameServer_.localPlayerId())); auto playerState = player->stateSnapshot(); if (playerState && playerState->enabled) - updateTriggerZones(playerState->position); + updateTriggerZones(playerState->position.toEigen()); } for (auto& [id, remote] : remotePlayers_) if (remote) remote->update(delta, eventFor(id)); @@ -1988,7 +1957,7 @@ namespace FRG auto playerState = player ? player->stateSnapshot() : nullptr; if (auto* tz = getTargetTeleportZone(*locationState); tz && playerState && playerState->enabled) { - float dist = (playerState->position - tz->position).norm(); + float dist = (playerState->position.toEigen() - tz->position).norm(); if (dist <= tz->radius) { logger() << "[TELEPORT] Player reached teleport zone '" << tz->id << "'" << std::endl; gameServer_.submit(SetLocationTargetsCommand{ @@ -2005,7 +1974,7 @@ namespace FRG for (auto& tz : teleportZones) { if (!tz.active || !tz.automatic) continue; const float autoR = tz.automaticRadius > 0.0f ? tz.automaticRadius : tz.radius; - const float dist = (playerState->position - tz.position).norm(); + const float dist = (playerState->position.toEigen() - tz.position).norm(); if (!tz.automaticInitialized) { // If the player starts inside the zone (e.g. just arrived here via teleport), @@ -2101,7 +2070,7 @@ namespace FRG const Eigen::Vector3f interactionTarget = clickedObject->state.hasInteractionPosition ? clickedObject->state.interactionPosition : clickedObject->state.position; - setCharacterTarget(*player, interactionTarget); + setCharacterTarget(*player, Vec3fp::fromEigen(interactionTarget)); pathVisualization.start(*player, interactionTarget); clearCharacterAttackTarget(*player); //logger() << "[CLICK] Player moving to object..." << std::endl; @@ -2114,7 +2083,7 @@ namespace FRG if (clickedNpc && player) { auto clickedNpcState = clickedNpc->stateSnapshot(); if (!clickedNpcState) return; - float distance = (playerState->position - clickedNpcState->position).norm(); + float distance = (playerState->position - clickedNpcState->position).toEigen().norm(); int npcIndex = -1; for (size_t i = 0; i < npcs.size(); ++i) { if (npcs[i].get() == clickedNpc) { @@ -2144,11 +2113,11 @@ namespace FRG setCharacterTarget(*player, clickedNpcState->position); pathVisualization.start( *player, - clickedNpcState->position, + clickedNpcState->position.toEigen(), PathVisualization::Style::Hostile); } - if (distance <= clickedNpcState->interactionRadius) { + if (distance <= static_cast(clickedNpcState->interactionRadius)) { //logger() << "[CLICK] Hostile NPC " << npcIndex << " in range; firing on_npc_interact" << std::endl; scriptEngine.callNpcInteractCallback(clickedNpc->entityId()); } @@ -2172,7 +2141,7 @@ namespace FRG //logger() << "[CLICK] NPC " << npcIndex << " out of talk range (distance " << distance // << " > " << NPC_TALK_DISTANCE << "); walking to NPC..." << std::endl; setCharacterTarget(*player, clickedNpcState->position); - pathVisualization.start(*player, clickedNpcState->position); + pathVisualization.start(*player, clickedNpcState->position.toEigen()); gameServer_.submit(SetLocationTargetsCommand{ locationId_, std::string(), clickedNpc->entityId(), std::string()}); @@ -2199,7 +2168,7 @@ namespace FRG gameServer_.submit(SetLocationTargetsCommand{ locationId_, std::string(), std::string(), clickedTeleport->id}); - setCharacterTarget(*player, clickedTeleport->position); + setCharacterTarget(*player, Vec3fp::fromEigen(clickedTeleport->position)); pathVisualization.start( *player, clickedTeleport->position, @@ -2207,7 +2176,7 @@ namespace FRG clearCharacterAttackTarget(*player); } else { const Eigen::Vector3f clickedDestination(hit.x(), 0.f, hit.z()); - setCharacterTarget(*player, clickedDestination); + setCharacterTarget(*player, Vec3fp::fromEigen(clickedDestination)); pathVisualization.start(*player, clickedDestination); clearCharacterAttackTarget(*player); gameServer_.submit(SetLocationTargetsCommand{ diff --git a/src/Location.h b/src/Location.h index d27cc30..c60e93f 100644 --- a/src/Location.h +++ b/src/Location.h @@ -64,7 +64,7 @@ namespace FRG std::string teleportsJsonPath; std::string triggerZonesJsonPath; std::string lightsJsonPath; - Eigen::Vector3f playerPosition = Eigen::Vector3f::Zero(); + Vec3fp playerPosition; // Player shared by all locations of a campaign (see Location::createPlayer); // setup() creates a private one when this is null. std::shared_ptr player; @@ -174,7 +174,7 @@ namespace FRG void setup(const LocationSetup& params, AudioPlayerAsync* audioPlayer); //void setupNavigation(const std::vector& paths); bool switchNavigation(int index); - void setCharacterTarget(Character& character, const Eigen::Vector3f& target, + void setCharacterTarget(Character& character, const Vec3fp& target, std::string callbackName = ""); void clearCharacterAttackTarget(Character& character); void stopCharacter(Character& character); @@ -259,7 +259,6 @@ namespace FRG void loadTriggerZones(const std::string& jsonPath, const char* zipFile); void loadPointLights(const std::string& jsonPath, const char* zipFile); void updateTriggerZones(const Eigen::Vector3f& playerPos); - void nudgeCharacterAside(Character* standing, const Eigen::Vector3f& awayFrom); void captureAuxiliaryState(LocationGameState& out) const; void applyAuxiliaryState(const LocationGameState& in); diff --git a/src/LocationEditor.cpp b/src/LocationEditor.cpp index b0022ee..f4bb29f 100644 --- a/src/LocationEditor.cpp +++ b/src/LocationEditor.cpp @@ -484,9 +484,9 @@ namespace FRG data.rotationZ = 0.0f; auto playerState = loc.player ? loc.player->stateSnapshot() : nullptr; if (!playerState) return; - data.positionX = playerState->position.x(); - data.positionY = playerState->position.y();//-0.1; - data.positionZ = playerState->position.z(); + data.positionX = static_cast(playerState->position.x); + data.positionY = static_cast(playerState->position.y); + data.positionZ = static_cast(playerState->position.z); data.scale = scaleDist(rng); GameObjectData data2; diff --git a/src/LocationGameState.cpp b/src/LocationGameState.cpp index fc9d5a7..9d4dd38 100644 --- a/src/LocationGameState.cpp +++ b/src/LocationGameState.cpp @@ -159,7 +159,7 @@ std::vector LocationGameState::collectPathObstacles bool LocationGameState::setCharacterDestination( const std::string& id, - const Eigen::Vector3f& target, + const Vec3fp& target, std::string callbackName, PlayerStateMap& players, const std::string& forcedObstacleId) @@ -201,10 +201,10 @@ bool LocationGameState::replanCharacter( character->attackTargetId.clear(); character->battle_state = 0; character->attack = 0; - const Eigen::Vector3f homeTarget( - character->homePosition.x(), 0.f, character->homePosition.z()); - if ((homeTarget - character->position).norm() > - CharacterState::kWalkThreshold) { + const Vec3fp homeTarget( + character->homePosition.x, static_cast(0.f), character->homePosition.z); + if ((homeTarget - character->position).normSquared() > + CharacterState::kWalkThreshold_SQUARED()) { character->setTarget( homeTarget, {}, *location, id); } @@ -234,14 +234,14 @@ std::string LocationGameState::selectNpcTarget( playerIds.push_back(id); std::sort(playerIds.begin(), playerIds.end()); - const float visibleRangeSquared = npc.visibleRange * npc.visibleRange; + const fp visibleRangeSquared = npc.visibleRange * npc.visibleRange; std::string selectedTarget; - float nearestDistanceSquared = visibleRangeSquared; + fp nearestDistanceSquared = visibleRangeSquared; for (const std::string& playerId : playerIds) { - const Eigen::Vector3f delta = + const Vec3fp delta = players.at(playerId)->position - npc.position; - const float distanceSquared = - delta.x() * delta.x() + delta.z() * delta.z(); + const fp distanceSquared = + delta.x * delta.x + delta.z * delta.z; if (distanceSquared <= nearestDistanceSquared && (selectedTarget.empty() || distanceSquared < nearestDistanceSquared || @@ -423,90 +423,17 @@ LocationSimulationEvents LocationGameState::update( if (selectedTarget.empty()) { npc.battle_state = 0; npc.attack = 0; - const Eigen::Vector3f homeTarget( - npc.homePosition.x(), 0.f, npc.homePosition.z()); - const Eigen::Vector3f toHome = homeTarget - npc.position; - if (toHome.norm() > CharacterState::kWalkThreshold && - (npc.requestedWalkTarget - homeTarget).norm() > - CharacterState::kWalkThreshold) { + const Vec3fp homeTarget( + npc.homePosition.x, static_cast(0.f), npc.homePosition.z); + const Vec3fp toHome = homeTarget - npc.position; + if (toHome.normSquared() > CharacterState::kWalkThreshold_SQUARED() && + (npc.requestedWalkTarget - homeTarget).normSquared() > + CharacterState::kWalkThreshold_SQUARED()) { setCharacterDestination(id, homeTarget, {}, players); } } } - /* - std::vector movementObstacles; - movementObstacles.reserve(playerIds.size() + npcIds.size()); - auto addMovementObstacle = - [&movementObstacles](const std::string& id, - const CharacterState& character) { - Eigen::Vector3f direction = - character.getCurrentNavigationTarget() - character.position; - direction.y() = 0.f; - const float length = direction.norm(); - if (length > CharacterState::kWalkThreshold) direction /= length; - else direction = Eigen::Vector3f::Zero(); - movementObstacles.push_back({ - id, - character.position, - character.movementContactRadius, - character.movedLastTick, - direction}); - }; - for (const std::string& id : playerIds) { - const CharacterState& character = *players.at(id); - if (character.enabled && character.hp > 0.f) - addMovementObstacle(id, character); - } - for (const std::string& id : npcIds) { - const CharacterState& character = *npcs.at(id); - if (character.enabled && character.hp > 0.f) - addMovementObstacle(id, character); - } - // One global ID order also makes blocker selection deterministic. - std::sort( - movementObstacles.begin(), movementObstacles.end(), - [](const CharacterMovementObstacle& lhs, - const CharacterMovementObstacle& rhs) { return lhs.id < rhs.id; });*/ - - /* - auto advance = [&](const std::string& id, CharacterState& state) { - CharacterUpdateContext context; - context.characterId = id; - context.movementObstacles = &movementObstacles; - auto attack = observations.find(state.attackTargetId); - if (attack != observations.end()) context.attackTarget = &attack->second; - auto face = observations.find(state.faceTargetId); - if (face != observations.end()) context.faceTarget = &face->second; - CharacterUpdateEvents events = state.update(deltaMs, context); - auto reservation = std::lower_bound( - movementObstacles.begin(), movementObstacles.end(), id, - [](const CharacterMovementObstacle& obstacle, - const std::string& value) { return obstacle.id < value; }); - if (reservation != movementObstacles.end() && reservation->id == id) { - reservation->position = state.position; - reservation->moving = state.movedLastTick; - Eigen::Vector3f direction = - state.getCurrentNavigationTarget() - state.position; - direction.y() = 0.f; - const float length = direction.norm(); - if (length > CharacterState::kWalkThreshold) - reservation->movementDirection = direction / length; - else - reservation->movementDirection = Eigen::Vector3f::Zero(); - } - if (!events.arrivedCallbackName.empty() || events.attackImpact || - events.deathAnimationComplete || events.navigationRequested) { - result.characters.push_back({id, std::move(events)}); - } - };*/ - /* - for (const std::string& id : playerIds) - if (players.at(id)->enabled) - advance(id, *players.at(id)); - for (const std::string& id : npcIds) - if (npcs.at(id)->enablesd) - advance(id, *npcs.at(id));*/ - + for (const std::string& id : playerIds) { if (players.at(id)->enabled) @@ -544,41 +471,6 @@ LocationSimulationEvents LocationGameState::update( source.events.attackDirection}); } - /* - 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) { - const Eigen::Vector3f& approachTarget = object->hasInteractionPosition - ? object->interactionPosition : object->position; - if ((player->second->position - approachTarget).norm() <= object->approachRadius) { - result.interactiveObjects.push_back( - {location->targetInteractiveObjectId, {}, true, - object->activateFunctionName}); - location->targetInteractiveObjectId.clear(); - } - } - } - - 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 float distance = - (player->second->position - npc->position).norm(); - if (distance <= NPC_TALK_DISTANCE) { - player->second->stopInPlace(); - npc->faceTargetId = "player1"; - result.npcInteractionId = location->targetInteractNpcId; - location->targetInteractNpcId.clear(); - } - }*/ - return result; } diff --git a/src/LocationGameState.h b/src/LocationGameState.h index 589138d..0eb372b 100644 --- a/src/LocationGameState.h +++ b/src/LocationGameState.h @@ -27,7 +27,7 @@ struct CharacterDamageEvent { float amount = 0.f; float hp = 0.f; float maxHp = 0.f; - Eigen::Vector3f direction = Eigen::Vector3f::Zero(); + Vec3fp direction; }; struct InteractiveObjectSimulationEvent { @@ -70,7 +70,7 @@ struct LocationGameState { const std::string& forcedObstacleId = {}) const;*/ bool setCharacterDestination( const std::string& id, - const Eigen::Vector3f& target, + const Vec3fp& target, std::string callbackName, PlayerStateMap& players, const std::string& forcedObstacleId = {}); diff --git a/src/ScriptEngine.cpp b/src/ScriptEngine.cpp index 8650637..035fcad 100644 --- a/src/ScriptEngine.cpp +++ b/src/ScriptEngine.cpp @@ -13,6 +13,9 @@ namespace FRG { + constexpr float kPi = 3.14159265358979323846f; + + namespace { // Reverse-looks-up the global name a Lua function is registered under, by @@ -126,9 +129,9 @@ namespace FRG { std::cerr << "[script] npc_walk_to: on_arrived must be a global Lua function\n"; } loc->gameServer().submit(SetCharacterHomePositionCommand{ - loc->locationId(), npc->entityId(), Eigen::Vector3f(x, 0.f, z)}); + loc->locationId(), npc->entityId(), Vec3fp(static_cast(x), static_cast(0.f), static_cast(z))}); loc->setCharacterTarget( - *npc, Eigen::Vector3f(x, y, z), std::move(cbName)); + *npc, Vec3fp(static_cast(x), static_cast(y), static_cast(z)), std::move(cbName)); }); @@ -140,16 +143,16 @@ namespace FRG { return; } - const float rad = angle * static_cast(M_PI) / 180.f; - logger() << "[script] npc_rotate_to: " << npcId << " angle " << angle << " degrees (" << rad << " radians)" << std::endl; + const fp rad = static_cast(angle) * static_cast(kPi) / static_cast < fp>(180.f); + logger() << "[script] npc_rotate_to: " << npcId << " angle " << angle << " degrees (" << static_cast(rad) << " radians)" << std::endl; loc->gameServer().submit(SetCharacterFacingAngleCommand{ loc->locationId(), npc->entityId(), rad, false}); }); api.set_function("player_rotate_to", [loc](float angle) { - const float rad = angle * static_cast(M_PI) / 180.f; - logger() << "[script] player_rotate_to: angle " << angle << " degrees (" << rad << " radians)" << std::endl; + const fp rad = static_cast(angle) * static_cast(kPi) / static_cast (180.f); + logger() << "[script] player_rotate_to: angle " << angle << " degrees (" << static_cast(rad) << " radians)" << std::endl; loc->gameServer().submit(SetCharacterFacingAngleCommand{ loc->locationId(), loc->player->entityId(), rad, false}); }); @@ -188,9 +191,9 @@ namespace FRG { } loc->gameServer().submit(SetCharacterHomePositionCommand{ loc->locationId(), loc->player->entityId(), - Eigen::Vector3f(x, 0.f, z)}); + Vec3fp(static_cast(x), static_cast(0.f), static_cast(z))}); loc->setCharacterTarget( - *loc->player, Eigen::Vector3f(x, y, z), std::move(cbName)); + *loc->player, Vec3fp(static_cast(x), static_cast(y), static_cast(z)), std::move(cbName)); }); @@ -424,12 +427,12 @@ namespace FRG { api.set_function("get_player_x", [loc]() -> float { auto state = loc->player ? loc->player->stateSnapshot() : nullptr; - return state ? state->position.x() : 0.0f; + return state ? static_cast(state->position.x) : 0.0f; }); api.set_function("get_player_z", [loc]() -> float { auto state = loc->player ? loc->player->stateSnapshot() : nullptr; - return state ? state->position.z() : 0.0f; + return state ? static_cast(state->position.z) : 0.0f; }); api.set_function("refill_current_player_hp", @@ -593,7 +596,7 @@ namespace FRG { std::cerr << "[script] npc_set_position: unknown NPC id '" << npcId << "'\n"; return; } - Eigen::Vector3f pos(x, y, z); + Vec3fp pos(static_cast(x), static_cast(y), static_cast(z)); loc->gameServer().submit(SetCharacterPositionCommand{ loc->locationId(), npc->entityId(), pos, true}); loc->setCharacterTarget(*npc, pos); @@ -607,7 +610,8 @@ namespace FRG { std::cerr << "[script] npc_set_rotation: unknown NPC id '" << npcId << "'\n"; return; } - const float rad = angle * static_cast(M_PI) / 180.f; + const fp rad = static_cast(angle) * static_cast(kPi) / static_cast (180.f); + loc->gameServer().submit(SetCharacterFacingAngleCommand{ loc->locationId(), npc->entityId(), rad, true}); }); @@ -626,7 +630,7 @@ namespace FRG { api.set_function("player_set_position", [loc](float x, float y, float z) { - Eigen::Vector3f pos(x, y, z); + Vec3fp pos(static_cast(x), static_cast(y), static_cast(z)); loc->gameServer().submit(SetCharacterPositionCommand{ loc->locationId(), loc->player->entityId(), pos, true}); loc->setCharacterTarget(*loc->player, pos); @@ -634,7 +638,8 @@ namespace FRG { api.set_function("player_set_rotation", [loc](float angle) { - const float rad = angle * static_cast(M_PI) / 180.f; + const fp rad = static_cast(angle) * static_cast(kPi) / static_cast (180.f); + loc->gameServer().submit(SetCharacterFacingAngleCommand{ loc->locationId(), loc->player->entityId(), rad, true}); }); diff --git a/src/content/LocationDefinition.cpp b/src/content/LocationDefinition.cpp index ca64438..fb01cc0 100644 --- a/src/content/LocationDefinition.cpp +++ b/src/content/LocationDefinition.cpp @@ -6,6 +6,7 @@ #include #include #include +#include namespace FRG { namespace { @@ -172,7 +173,7 @@ CharacterState NpcData::toCharacterState() const CharacterState state; state.npcName = name; state.position = {positionX, positionY, positionZ}; - state.homePosition = {positionX, 0.f, positionZ}; + state.homePosition = {positionX, static_cast(0.f), positionZ}; state.stopInPlace(); state.facingAngle = facingAngle; state.walkSpeed = walkSpeed; @@ -183,9 +184,9 @@ CharacterState NpcData::toCharacterState() const state.visibleRange = visibleRange; state.canAttack = canAttack; state.enabled = enabled; - state.collisionRadius = navigationAvoidanceRadius; - state.movementContactRadius = - (std::min)(movementContactRadius, navigationAvoidanceRadius); + //state.collisionRadius = navigationAvoidanceRadius; + //state.movementContactRadius = + // (std::min)(movementContactRadius, navigationAvoidanceRadius); state.interactionRadius = interactionRadius; state.configureNavigationRetryCooldowns( id, navigationRetryCooldownTicks); @@ -309,8 +310,8 @@ LocationDefinition LocationContentLoader::load( data.meshTextures[name] = path.get(); } } - data.interactionRadius = - item.value("interactionRadius", 0.f); + data.interactionRadius = static_cast( + item.value("interactionRadius", 0.0)); data.animationIdlePath = item.value("animationIdlePath", ""); data.animationWalkPath = @@ -327,11 +328,11 @@ LocationDefinition LocationContentLoader::load( item.value("animationActionToDeathPath", ""); data.animationDeathIdlePath = item.value("animationDeathIdlePath", ""); - data.positionX = item.value("positionX", 0.f); - data.positionY = item.value("positionY", 0.f); - data.positionZ = item.value("positionZ", 0.f); - data.walkSpeed = item.value("walkSpeed", 1.5f); - data.rotationSpeed = item.value("rotationSpeed", 8.f); + data.positionX = static_cast(item.value("positionX", 0.0)); + data.positionY = static_cast(item.value("positionY", 0.0)); + data.positionZ = static_cast(item.value("positionZ", 0.0)); + data.walkSpeed = static_cast(item.value("walkSpeed", 1.5)); + data.rotationSpeed = static_cast(item.value("rotationSpeed", 8.0)); data.modelScale = item.value("modelScale", 0.01f); const float degrees = kPi / 180.f; data.modelCorrectionRotX = @@ -341,12 +342,13 @@ LocationDefinition LocationContentLoader::load( data.modelCorrectionRotZ = item.value("modelCorrectionRotZ", 0.f) * degrees; data.facingAngle = - item.value("facingAngle", 0.f) * degrees; + static_cast(item.value("facingAngle", 0.0)) * static_cast(kPi) / static_cast(180.0); data.hp = item.value("hp", 100.f); data.visibleRange = - item.value("visibleRange", VISIBLE_RANGE); + 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)); @@ -359,6 +361,7 @@ LocationDefinition LocationContentLoader::load( 0.05f, (std::min)( data.movementContactRadius, data.navigationAvoidanceRadius)); + */ const char* cooldownKey = item.contains("navigationRetryCooldownTicks") ? "navigationRetryCooldownTicks" diff --git a/src/content/LocationDefinition.h b/src/content/LocationDefinition.h index dc76f8f..dd0ed04 100644 --- a/src/content/LocationDefinition.h +++ b/src/content/LocationDefinition.h @@ -58,23 +58,23 @@ struct NpcData { std::string animationActionToStandPath; std::string animationActionToDeathPath; std::string animationDeathIdlePath; - float positionX = 0.f; - float positionY = 0.f; - float positionZ = 0.f; - float walkSpeed = 1.5f; - float rotationSpeed = 8.f; + fp positionX = static_cast(0.f); + fp positionY = static_cast(0.f); + fp positionZ = static_cast(0.f); + fp walkSpeed = static_cast(1.5f); + fp rotationSpeed = static_cast(8.f); float modelScale = 0.01f; float modelCorrectionRotX = 0.f; float modelCorrectionRotY = 0.f; float modelCorrectionRotZ = 0.f; - float facingAngle = 0.f; + fp facingAngle = static_cast(0.f); float hp = 100.f; - float visibleRange = VISIBLE_RANGE; + fp visibleRange = static_cast(VISIBLE_RANGE); bool canAttack = false; bool enabled = true; - float navigationAvoidanceRadius = 0.45f; - float movementContactRadius = 0.32f; - float interactionRadius = 0.f; + //fp navigationAvoidanceRadius = static_cast(0.45f); + //fp movementContactRadius = static_cast(0.32f); + fp interactionRadius = static_cast(0.f); std::vector navigationRetryCooldownTicks; CharacterState toCharacterState() const; diff --git a/src/game_server/IGameServer.h b/src/game_server/IGameServer.h index 761abb4..190ccd5 100644 --- a/src/game_server/IGameServer.h +++ b/src/game_server/IGameServer.h @@ -137,19 +137,19 @@ struct SetGlobalFloatCommand { std::string key; float value; }; struct SetCharacterPositionCommand { std::string locationId; std::string characterId; - Eigen::Vector3f position; + Vec3fp position; bool updateHomePosition = false; }; struct SetCharacterFacingAngleCommand { std::string locationId; std::string characterId; - float angle; + fp angle; bool setCurrentAngle = false; }; struct SetCharacterHomePositionCommand { std::string locationId; std::string characterId; - Eigen::Vector3f position; + Vec3fp position; }; struct SetCharacterTargetCommand { std::string locationId; @@ -173,7 +173,7 @@ struct ClearCharacterAttackTargetCommand { struct SetCharacterDestinationCommand { std::string locationId; std::string characterId; - Eigen::Vector3f target; + Vec3fp target; std::string callbackName; }; struct StopCharacterCommand { diff --git a/src/game_server/InternalGameServer.cpp b/src/game_server/InternalGameServer.cpp index c1bab91..2780cfd 100644 --- a/src/game_server/InternalGameServer.cpp +++ b/src/game_server/InternalGameServer.cpp @@ -70,11 +70,11 @@ void InternalGameServer::updateBattleZoneTestBot(std::int64_t deltaMs) battleZoneTestBotElapsedMs_ %= kBattleZoneTestBotIntervalMs; static std::mt19937 randomEngine(std::random_device{}()); - static std::uniform_real_distribution coordinate(-10.0f, 10.0f); + static std::uniform_int_distribution coordinate(-100, 100); //Divide by 10 then. submit(SetCharacterDestinationCommand{ state_.currentLocationName, kBattleZonePlayer2Id, - Eigen::Vector3f(coordinate(randomEngine), 0.f, coordinate(randomEngine)), + Vec3fp(static_cast(coordinate(randomEngine)) / static_cast(10), static_cast(0.f), coordinate(randomEngine) / static_cast(10)), std::string()}); } diff --git a/src/game_server/NetworkProtocol.cpp b/src/game_server/NetworkProtocol.cpp index c558fbf..c09a720 100644 --- a/src/game_server/NetworkProtocol.cpp +++ b/src/game_server/NetworkProtocol.cpp @@ -91,6 +91,44 @@ bool readTarget( return true; } +bool readTargetFp( + const nlohmann::json& value, FRG::Vec3fp& target, std::string& error) +{ + if (!value.is_array() || value.size() != 3) { + error = "target must contain three coordinates"; + return false; + } + + FRG::fp coords[3]; + for (std::size_t index = 0; index < 3; ++index) { + if (!value[index].is_number()) { + error = "target coordinates must be numbers"; + return false; + } + const double coordinate = value[index].get(); + if (!std::isfinite(coordinate) || std::abs(coordinate) > 100000.0) { + error = "target coordinate is not finite or is out of range"; + return false; + } + // Округляем до сотых в double и конвертируем в Q20.12 (fp) + coords[index] = FRG::fp(coordinate); + } + + target.x = coords[0]; + target.y = coords[1]; + target.z = coords[2]; + return true; +} + +nlohmann::json targetJsonFp(const FRG::Vec3fp& target) +{ + return nlohmann::json::array({ + static_cast(target.x), + static_cast(target.y), + static_cast(target.z) + }); +} + nlohmann::json targetJson(const Eigen::Vector3f& target) { return nlohmann::json::array({target.x(), target.y(), target.z()}); @@ -161,7 +199,7 @@ bool parseInitialStateMessage( } std::uint64_t LockstepTimeline::schedule( - const std::string& playerId, const Eigen::Vector3f& target) + const std::string& playerId, const Vec3fp& target) { const std::uint64_t sequence = nextCommandSequence_++; pending_[currentTick_ + inputDelayTicks_].push_back( @@ -206,7 +244,7 @@ std::string makeDestinationInputMessage(const DestinationInputMessage& message) { return envelope("destination_input", { {"clientSequence", message.clientSequence}, - {"target", targetJson(message.target)} + {"target", targetJsonFp(message.target)} }); } @@ -219,7 +257,7 @@ bool parseDestinationInputMessage( return false; if (!payload.contains("target")) return error = "target is required", false; - return readTarget(payload["target"], message.target, error); + return readTargetFp(payload["target"], message.target, error); } std::string makeCharacterActionInputMessage( @@ -265,7 +303,7 @@ std::string makeTickMessage(const TickMessage& message) {"type", "destination"}, {"commandSequence", command.commandSequence}, {"playerId", command.playerId}, - {"target", targetJson(command.target)} + {"target", targetJsonFp(command.target)} }); } else { if constexpr (std::is_same_v) { @@ -320,7 +358,7 @@ bool parseTickMessage( destination.commandSequence = previousSequence; if (!value.contains("playerId") || !value["playerId"].is_string() || !value.contains("target") || - !readTarget(value["target"], destination.target, error)) { + !readTargetFp(value["target"], destination.target, error)) { if (error.empty()) error = "invalid destination command"; return false; } diff --git a/src/game_server/NetworkProtocol.h b/src/game_server/NetworkProtocol.h index 521a907..403fa1d 100644 --- a/src/game_server/NetworkProtocol.h +++ b/src/game_server/NetworkProtocol.h @@ -7,6 +7,7 @@ #include #include #include +#include "../navigation/PathFinder.h" namespace FRG { @@ -25,7 +26,7 @@ struct InitialStateMessage { struct DestinationInputMessage { std::uint64_t clientSequence = 0; - Eigen::Vector3f target = Eigen::Vector3f::Zero(); + Vec3fp target; }; struct CharacterActionInputMessage { @@ -37,7 +38,7 @@ struct CharacterActionInputMessage { struct ScheduledDestinationCommand { std::uint64_t commandSequence = 0; std::string playerId; - Eigen::Vector3f target = Eigen::Vector3f::Zero(); + Vec3fp target; }; struct ScheduledCharacterEnabledCommand { @@ -73,7 +74,7 @@ public: std::uint64_t currentTick() const { return currentTick_; } std::uint64_t schedule( - const std::string& playerId, const Eigen::Vector3f& target); + const std::string& playerId, const Vec3fp& target); std::uint64_t scheduleCharacterEnabled( const std::string& locationId, const std::string& characterId, bool enabled); diff --git a/src/items/GameObjectLoader.cpp b/src/items/GameObjectLoader.cpp index 5624c28..fe4a26a 100644 --- a/src/items/GameObjectLoader.cpp +++ b/src/items/GameObjectLoader.cpp @@ -13,6 +13,9 @@ namespace FRG { + constexpr float kPi = 3.14159265358979323846f; + + using json = nlohmann::json; // ----------------------------------------------------------------------- @@ -341,7 +344,7 @@ namespace FRG { data.meshTextures[it.key()] = it.value().get(); } } - data.interactionRadius = item.value("interactionRadius", 0.0f); + data.interactionRadius = static_cast(item.value("interactionRadius", 0.0)); data.animationIdlePath = item.value("animationIdlePath", ""); data.animationWalkPath = item.value("animationWalkPath", ""); data.animationActionIdlePath = item.value("animationActionIdlePath", ""); @@ -350,18 +353,18 @@ namespace FRG { data.animationActionToStandPath = item.value("animationActionToStandPath", ""); data.animationActionToDeathPath = item.value("animationActionToDeathPath", ""); data.animationDeathIdlePath = item.value("animationDeathIdlePath", ""); - data.positionX = item.value("positionX", 0.0f); - data.positionY = item.value("positionY", 0.0f); - data.positionZ = item.value("positionZ", 0.0f); - data.walkSpeed = item.value("walkSpeed", 1.5f); - data.rotationSpeed = item.value("rotationSpeed", 8.0f); + data.positionX = static_cast(item.value("positionX", 0.0)); + data.positionY = static_cast(item.value("positionY", 0.0)); + data.positionZ = static_cast(item.value("positionZ", 0.0)); + data.walkSpeed = static_cast(item.value("walkSpeed", 1.5)); + data.rotationSpeed = static_cast(item.value("rotationSpeed", 8.0)); data.modelScale = item.value("modelScale", 0.01f); data.modelCorrectionRotX = item.value("modelCorrectionRotX", 0.0f) * static_cast(M_PI) / 180.0f; data.modelCorrectionRotY = item.value("modelCorrectionRotY", 0.0f) * static_cast(M_PI) / 180.0f; data.modelCorrectionRotZ = item.value("modelCorrectionRotZ", 0.0f) * static_cast(M_PI) / 180.0f; - data.facingAngle = item.value("facingAngle", 0.0f) * static_cast(M_PI) / 180.0f; + data.facingAngle = static_cast(item.value("facingAngle", 0.0f)) * static_cast(kPi) / static_cast(180.0f); data.hp = item.value("hp", 100.0f); - data.visibleRange = item.value("visibleRange", VISIBLE_RANGE); + data.visibleRange = static_cast(item.value("visibleRange", static_cast(VISIBLE_RANGE))); data.canAttack = item.value("canAttack", false); data.enabled = item.value("enabled", true); @@ -401,7 +404,7 @@ namespace FRG { renderer, zipPath.c_str()); if (npc) { logger() << "Successfully loaded NPC: " << npcData.name << " at (" - << npcData.positionX << ", " << npcData.positionY << ", " << npcData.positionZ << ")" << std::endl; + << static_cast(npcData.positionX) << ", " << static_cast(npcData.positionY) << ", " << static_cast(npcData.positionZ) << ")" << std::endl; npcs.push_back(std::move(npc)); } } catch (const std::exception& e) { diff --git a/src/navigation/PathFinder.cpp b/src/navigation/PathFinder.cpp index 76f9df2..53d9c60 100644 --- a/src/navigation/PathFinder.cpp +++ b/src/navigation/PathFinder.cpp @@ -12,8 +12,27 @@ namespace FRG { using json = nlohmann::json; +Vec3fp operator-(Vec3fp a, Vec3fp b) +{ + return Vec3fp{ + a.x - b.x, + a.y - b.y, + a.z - b.z + }; +} +Vec3fp operator+(Vec3fp a, Vec3fp b) +{ + return Vec3fp{ + a.x + b.x, + a.y + b.y, + a.z + b.z + }; +} namespace { + + + std::vector readPolygon(const json& item) { std::vector polygon; diff --git a/src/navigation/PathFinder.h b/src/navigation/PathFinder.h index 49978d3..cc6ec6e 100644 --- a/src/navigation/PathFinder.h +++ b/src/navigation/PathFinder.h @@ -80,8 +80,37 @@ namespace FRG { Eigen::Vector3f toEigen() const { return Eigen::Vector3f(static_cast(x), static_cast(y), static_cast(z)); } + + fp normSquared() const + { + return x * x + y * y + z * z; + } + Vec3fp operator/(fp value) const + { + if (value == static_cast(0.0)) + { + throw std::runtime_error("Vec3fp error: division by zero"); + return Vec3fp(); + } + Vec3fp r; + r.x = x / value; + r.y = y / value; + r.z = z / value; + return r; + } + Vec3fp operator*(const fp value) const + { + Vec3fp r; + r.x = x * value; + r.y = y * value; + r.z = z * value; + return r; + } }; + Vec3fp operator-(Vec3fp a, Vec3fp b); + Vec3fp operator+(Vec3fp a, Vec3fp b); + inline std::vector toEigenVector(const std::vector& src) { std::vector dst; dst.reserve(src.size()); diff --git a/src/render/PathVisualization.cpp b/src/render/PathVisualization.cpp index 098b002..f3c05e2 100644 --- a/src/render/PathVisualization.cpp +++ b/src/render/PathVisualization.cpp @@ -109,7 +109,7 @@ namespace FRG { const size_t firstWaypoint = (std::min)( state->currentWaypointIndex, state->pathWaypoints.size()); for (size_t i = firstWaypoint; i < state->pathWaypoints.size(); ++i) { - const Eigen::Vector3f& waypoint = state->pathWaypoints[i]; + const Eigen::Vector3f& waypoint = state->pathWaypoints[i].toEigen(); route.push_back(Eigen::Vector3f(waypoint.x(), 0.0f, waypoint.z())); } const Eigen::Vector3f clickedPosition( @@ -140,7 +140,7 @@ namespace FRG { constexpr float kPlayerClearanceMs = 20.0f; constexpr float kPlayerMaximumSpeedMultiplier = 1.5f; const float playerSpeed = (std::max)( - state->walkSpeed * kPlayerMaximumSpeedMultiplier, 0.01f); + static_cast(state->walkSpeed) * kPlayerMaximumSpeedMultiplier, 0.01f); const float playerRouteTimeMs = totalLength / playerSpeed * 1000.0f; const float revealDurationMs = (std::min)(kMaximumRevealMs, playerRouteTimeMs / 3.0f); const float waveSpeed = totalLength / (revealDurationMs / 1000.0f);