Major refactoring - internal server state machine implemented
This commit is contained in:
1 parent
71c654e5b6
commit
66e93d46f4
48 files changed
+3948
-3696
No files matched your search
+308
-6
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user