Major refactoring - internal server state machine implemented

This commit is contained in:
Vladislav Khorev committed 2026-10-29 00:09:16 +06:00
1 parent 71c654e5b6
commit 66e93d46f4
48 files changed
+3948 -3696

No files matched your search

+308 -6
View File
@@ -1,7 +1,17 @@
#include "CharacterState.h"
#include "LocationState.h"
#include <algorithm>
#include <cmath>
namespace FRG {
namespace {
constexpr float ATTACK_COOLDOWN_TIME = 1.6f;
constexpr float HOME_DRIFT_CHECK_INTERVAL = 5.0f;
constexpr float TARGET_REPLAN_THRESHOLD = 0.25f;
constexpr float PI = 3.14159265358979323846f;
}
bool CharacterState::isMoving() const {
Eigen::Vector3f toTarget = walkTarget - position;
toTarget.y() = 0.f;
@@ -20,14 +30,304 @@ void CharacterState::stopInPlace() {
requestedWalkTarget = walkTarget;
pathWaypoints.clear();
currentWaypointIndex = 0;
onArrivedCallback = nullptr;
onArrivedCallbackName.clear();
}
void CharacterState::setTarget(
const Eigen::Vector3f& target,
std::string callbackName,
const LocationState& location,
const std::string& entityId)
{
const Eigen::Vector3f normalizedTarget(target.x(), 0.f, target.z());
const bool sameRequestedTarget =
(normalizedTarget - requestedWalkTarget).norm() <= TARGET_REPLAN_THRESHOLD;
const bool alreadyMovingToTarget =
!pathWaypoints.empty() ||
(walkTarget - normalizedTarget).norm() <= TARGET_REPLAN_THRESHOLD;
const bool stoppedAfterFailedPath =
pathWaypoints.empty() &&
(walkTarget - position).norm() <= TARGET_REPLAN_THRESHOLD;
if (callbackName.empty() && sameRequestedTarget &&
(alreadyMovingToTarget || stoppedAfterFailedPath)) {
return;
}
requestedWalkTarget = normalizedTarget;
onArrivedCallbackName = std::move(callbackName);
pathWaypoints = location.planPathExceptMyself(position, normalizedTarget, entityId);
currentWaypointIndex = 0;
if (!pathWaypoints.empty()) {
for (Eigen::Vector3f& waypoint : pathWaypoints) waypoint.y() = 0.f;
walkTarget = pathWaypoints.back();
return;
}
walkTarget = Eigen::Vector3f(position.x(), 0.f, position.z());
onArrivedCallbackName.clear();
}
void CharacterState::forceReplan(
const LocationState& location, const std::string& entityId)
{
const Eigen::Vector3f normalizedTarget(
requestedWalkTarget.x(), 0.f, requestedWalkTarget.z());
pathWaypoints = location.planPathExceptMyself(position, normalizedTarget, entityId);
currentWaypointIndex = 0;
if (!pathWaypoints.empty()) {
for (Eigen::Vector3f& waypoint : pathWaypoints) waypoint.y() = 0.f;
walkTarget = pathWaypoints.back();
return;
}
walkTarget = Eigen::Vector3f(position.x(), 0.f, position.z());
onArrivedCallbackName.clear();
}
void CharacterState::stopAndRotateToTarget(std::string characterId)
{
stopInPlace();
faceTargetId = std::move(characterId);
}
void CharacterState::resetPlayerAfterDeath()
{
if (!isPlayer) return;
currentState = AnimationState::STAND;
resetAnim = true;
showWeapon = false;
battle_state = 0;
attack = 0;
}
void CharacterState::tryAttackCombo()
{
if (static_cast<int>(animationFrame) < 20 ||
(currentState != AnimationState::ACTION_ATTACK &&
currentState != AnimationState::ACTION_ATTACK_2)) {
return;
}
const auto index = static_cast<std::size_t>(currentState);
animationFrame = index < animationStartFrames.size()
? static_cast<float>(animationStartFrames[index]) : 0.f;
}
void CharacterState::setHp(float newHp) {
hp = newHp;
if (hp < 0.f) hp = 0.f;
if (onHpChanged) onHpChanged(hp, initialHp, CharacterState::kNoTarget);
}
void CharacterState::setAnimationMetadata(
AnimationState state, int startFrame, int frameCount)
{
const auto index = static_cast<std::size_t>(state);
if (index < animationFrameCounts.size()) {
animationStartFrames[index] = (std::max)(0, startFrame);
animationFrameCounts[index] = (std::max)(1, frameCount);
}
}
CharacterUpdateEvents CharacterState::update(
std::int64_t deltaMs, const CharacterUpdateContext& context)
{
CharacterUpdateEvents events;
if (!enabled) return events;
if (slowMoTimeRemaining > 0.0f) {
slowMoTimeRemaining = (std::max)(
0.0f, slowMoTimeRemaining - static_cast<float>(deltaMs) / 1000.0f);
float transitionProgress = 1.0f;
if (slowMoTimeRemaining > slowMoActiveTime - slowMoTransitionTime) {
const float timeInTransition = slowMoActiveTime - slowMoTimeRemaining;
transitionProgress = (std::min)(1.0f, timeInTransition / slowMoTransitionTime);
} else if (slowMoTimeRemaining < slowMoTransitionTime) {
transitionProgress = (std::max)(0.0f, slowMoTimeRemaining / slowMoTransitionTime);
}
const float speedMultiplier = isPlayer
? 1.0f + 0.5f * transitionProgress
: 1.0f - 0.9f * transitionProgress;
deltaMs = static_cast<std::int64_t>(
static_cast<float>(deltaMs) * speedMultiplier);
}
const CharacterObservation* attackTarget = context.attackTarget;
const CharacterObservation* faceTarget = context.faceTarget;
if (!attackTarget) {
battle_state = 0;
attack = 0;
attackTargetId.clear();
} else {
const float distance = (attackTarget->position - position).norm();
if (distance >= visibleRange) {
if (isPlayer &&
(attackTarget->position - requestedWalkTarget).norm() > TARGET_REPLAN_THRESHOLD) {
events.navigationRequested = true;
events.navigationTarget = attackTarget->position;
}
battle_state = 0;
} else if (distance >= 1.25f) {
if ((attackTarget->position - requestedWalkTarget).norm() > TARGET_REPLAN_THRESHOLD) {
events.navigationRequested = true;
events.navigationTarget = attackTarget->position;
}
battle_state = 0;
} else {
battle_state = 1;
}
}
const Eigen::Vector3f activeTarget = getCurrentNavigationTarget();
const Eigen::Vector3f 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<float>(deltaMs) / 1000.f;
position = moveAmount >= distance ? activeTarget : position + direction * moveAmount;
position.y() = 0.f;
targetFacingAngle = std::atan2(direction.x(), -direction.z());
currentState = AnimationState::WALK;
} else {
currentState = AnimationState::STAND;
const bool hasNextWaypoint =
!pathWaypoints.empty() && currentWaypointIndex + 1 < pathWaypoints.size();
if (hasNextWaypoint) {
++currentWaypointIndex;
} else {
pathWaypoints.clear();
currentWaypointIndex = 0;
if (!onArrivedCallbackName.empty()) {
events.arrivedCallbackName = onArrivedCallbackName;
onArrivedCallbackName.clear();
}
}
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)
: facingAngle;
} else if (!faceTargetId.empty()) {
faceTargetId.clear();
}
}
}
if (hp <= 0.f) {
if (currentState != AnimationState::ACTION_TO_DEATH &&
currentState != AnimationState::DEATH_IDLE) {
currentState = AnimationState::ACTION_TO_DEATH;
resetAnim = true;
attackTargetId.clear();
}
} else if (battle_state == 1 && attackTarget) {
targetFacingAngle = std::atan2(
lookTarget.x() - position.x(), -(lookTarget.z() - position.z()));
if (currentState == AnimationState::STAND || currentState == AnimationState::WALK) {
currentState = AnimationState::STAND_TO_ACTION;
resetAnim = true;
}
if (canAttack && attack == 0 && attack_cooldown < 0.f &&
currentState == AnimationState::ACTION_IDLE) {
attack = 1;
attack_cooldown = ATTACK_COOLDOWN_TIME;
}
if (canAttack && attack_cooldown >= 0.f)
attack_cooldown -= static_cast<float>(deltaMs) / 1000.f;
if (attack == 1 && currentState == AnimationState::ACTION_IDLE &&
attackTarget->hp > 0.f) {
currentState = AnimationState::ACTION_ATTACK;
resetAnim = true;
}
} else if (currentState == AnimationState::STAND_TO_ACTION ||
currentState == AnimationState::ACTION_IDLE ||
currentState == AnimationState::ACTION_ATTACK) {
currentState = AnimationState::ACTION_TO_STAND;
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<float>(deltaMs) / 1000.f;
if (std::fabs(angleDiff) <= rotationStep) {
facingAngle = targetFacingAngle;
faceTargetId.clear();
} else {
facingAngle += angleDiff > 0.f ? rotationStep : -rotationStep;
}
if (!isPlayer && hp > 0.f && battle_state == 0 && maxHomeDrift > 0.f) {
homeDriftCheckTimer -= static_cast<float>(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) {
events.navigationRequested = true;
events.navigationTarget = homePosition;
}
}
}
if (resetAnim) {
resetAnim = false;
const auto index = static_cast<std::size_t>(currentState);
animationFrame = index < animationStartFrames.size()
? static_cast<float>(animationStartFrames[index]) : 0.f;
}
const int previousFrame = static_cast<int>(animationFrame);
animationFrame += static_cast<float>(deltaMs) / 24.f;
if (previousFrame < 19 && static_cast<int>(animationFrame) >= 19 &&
(currentState == AnimationState::ACTION_ATTACK ||
currentState == AnimationState::ACTION_ATTACK_2) &&
attackTarget) {
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();
}
showWeapon =
(static_cast<int>(animationFrame) >= 20 && currentState == AnimationState::STAND_TO_ACTION) ||
(static_cast<int>(animationFrame) <= 32 && currentState == AnimationState::ACTION_TO_STAND) ||
currentState == AnimationState::ACTION_ATTACK_2 ||
currentState == AnimationState::ACTION_ATTACK ||
currentState == AnimationState::ACTION_IDLE;
const auto animationIndex = static_cast<std::size_t>(currentState);
const int frameCount = animationIndex < animationFrameCounts.size()
? animationFrameCounts[animationIndex] : 1;
if (frameCount > 1 && static_cast<int>(animationFrame) >= frameCount - 1) {
if (currentState == AnimationState::STAND_TO_ACTION)
currentState = AnimationState::ACTION_IDLE;
else if (currentState == AnimationState::ACTION_TO_STAND)
currentState = AnimationState::STAND;
else if (currentState == AnimationState::ACTION_ATTACK ||
currentState == AnimationState::ACTION_ATTACK_2) {
currentState = AnimationState::ACTION_IDLE;
attack = 0;
} else if (currentState == AnimationState::ACTION_TO_DEATH) {
currentState = AnimationState::DEATH_IDLE;
events.deathAnimationComplete = true;
}
const auto nextIndex = static_cast<std::size_t>(currentState);
animationFrame = nextIndex < animationStartFrames.size()
? static_cast<float>(animationStartFrames[nextIndex]) : 0.f;
}
return events;
}
void CharacterState::save(nlohmann::json& out) const
@@ -41,8 +341,8 @@ void CharacterState::save(nlohmann::json& out) const
out["currentState"] = static_cast<int>(currentState);
out["attack_cooldown"] = attack_cooldown;
out["showWeapon"] = showWeapon;
out["attackTargetIndex"] = attackTargetIndex;
out["faceTargetIndex"] = faceTargetIndex;
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() };
@@ -53,6 +353,7 @@ void CharacterState::save(nlohmann::json& out) const
out["pathWaypoints"] = std::move(wpArr);
out["currentWaypointIndex"] = currentWaypointIndex;
out["homeDriftCheckTimer"] = homeDriftCheckTimer;
out["animationFrame"] = animationFrame;
out["visibleRange"] = visibleRange;
if (!onArrivedCallbackName.empty())
out["onArrivedCallbackName"] = onArrivedCallbackName;
@@ -76,8 +377,8 @@ void CharacterState::load(const nlohmann::json& in)
currentState = static_cast<AnimationState>(in.value("currentState", static_cast<int>(currentState)));
attack_cooldown = in.value("attack_cooldown", attack_cooldown);
showWeapon = in.value("showWeapon", showWeapon);
attackTargetIndex = in.value("attackTargetIndex", attackTargetIndex);
faceTargetIndex = in.value("faceTargetIndex", faceTargetIndex);
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);
@@ -89,6 +390,7 @@ void CharacterState::load(const nlohmann::json& in)
}
currentWaypointIndex = in.value("currentWaypointIndex", currentWaypointIndex);
homeDriftCheckTimer = in.value("homeDriftCheckTimer", homeDriftCheckTimer);
animationFrame = in.value("animationFrame", animationFrame);
onArrivedCallbackName = in.value("onArrivedCallbackName", std::string());
}