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

+59 -20
View File
@@ -2,7 +2,9 @@
#include <string>
#include <vector>
#include <unordered_map>
#include <functional>
#include <array>
#include <cstdint>
#include <memory>
#include <Eigen/Core>
#include <Eigen/Geometry>
#include "external/nlohmann/json.hpp"
@@ -12,6 +14,10 @@ namespace FRG {
constexpr float VISIBLE_RANGE = 6.f;
struct CharacterUpdateContext;
struct CharacterUpdateEvents;
struct LocationState;
enum class AnimationState {
STAND = 0,
WALK = 1,
@@ -24,6 +30,8 @@ enum class AnimationState {
DEATH_IDLE = 8,
};
// Paths and fixed parameters needed to recreate a Character's visual assets
// from scratch (e.g. after loading a saved game).
struct CharacterCreationInfo {
@@ -45,19 +53,14 @@ struct CharacterCreationInfo {
float weaponMeshScale = 1.0f;
};
// All serializable game state for a character.
// This is a member of Character; rendering-only data lives in Character itself.
// All serializable authoritative state for a character. GameState or
// LocationGameState owns it; rendering-only data lives in Character.
class CharacterState : public ISaveable {
public:
// Special index values for attackTargetIndex / faceTargetIndex
static constexpr int kNoTarget = -1; // no target (was nullptr)
static constexpr int kPlayerIndex = -2; // refers to Location::player
// Distance below which the character is considered to have reached its target.
static constexpr float kWalkThreshold = 0.05f;
// --- Identity ---
std::string npcId;
std::string npcName;
bool isPlayer = false;
bool enabled = true;
@@ -84,9 +87,11 @@ public:
// Weapon visibility toggle (changes during animation)
bool showWeapon = true;
// --- Index-based character references (replace raw pointers) ---
int attackTargetIndex = kNoTarget;
int faceTargetIndex = kNoTarget;
// --- Stable character references ---
// Empty means no target. "player" is the campaign player; NPC IDs are
// location-local and validated while loading location content.
std::string attackTargetId;
std::string faceTargetId;
// --- Home position for drift recovery ---
Eigen::Vector3f homePosition = Eigen::Vector3f::Zero();
@@ -97,6 +102,13 @@ public:
float slowMoActiveTime = 6.0f;
float slowMoTransitionTime = 0.5f;
// --- Authoritative logical animation clock ---
// Frame counts are immutable metadata copied from loaded clips during
// character construction. Simulation advances animationFrame itself.
float animationFrame = 0.0f;
std::array<int, 9> animationStartFrames = {{0, 0, 0, 0, 0, 0, 0, 0, 0}};
std::array<int, 9> animationFrameCounts = {{1, 1, 1, 1, 1, 1, 1, 1, 1}};
// --- Movement / visual parameters ---
float walkSpeed = 3.0f;
float rotationSpeed = 4.0f;
@@ -115,14 +127,8 @@ public:
// Internal drift-check timer (serializable so it survives save/load).
float homeDriftCheckTimer = 5.0f;
// --- Non-serializable callbacks (re-wired after each load) ---
std::function<void()> onArrivedCallback;
std::function<void()> onDeathAnimComplete;
std::function<void(float, float, int)> onHpChanged;
// Global Lua function name backing onArrivedCallback, if it was sourced from a
// script (see ScriptEngine::rehydrateNamedCallback). Serialized so the callback
// can be re-looked-up by name after a save/load; empty if none or non-Lua.
// Global Lua function called when the character reaches the final waypoint.
// The name is serialized, so no runtime callback needs to be rehydrated.
std::string onArrivedCallbackName;
// --- Asset / creation info (used to reload visuals from a saved state) ---
@@ -133,12 +139,45 @@ public:
const Eigen::Vector3f& getRequestedWalkTarget() const { return requestedWalkTarget; }
Eigen::Vector3f getCurrentNavigationTarget() const;
void stopInPlace();
void setTarget(const Eigen::Vector3f& target,
std::string callbackName,
const LocationState& location,
const std::string& entityId);
void forceReplan(const LocationState& location,
const std::string& entityId);
void stopAndRotateToTarget(std::string characterId);
void resetPlayerAfterDeath();
void tryAttackCombo();
float getHp() const { return hp; }
void setHp(float newHp);
CharacterUpdateEvents update(std::int64_t deltaMs, const CharacterUpdateContext& context);
void setAnimationMetadata(AnimationState state, int startFrame, int frameCount);
// --- Serialisation (runtime mutable fields only; creation info is reloaded from config) ---
void save(nlohmann::json& out) const override;
void load(const nlohmann::json& in) override;
};
using PlayerStateMap =
std::unordered_map<std::string, std::shared_ptr<CharacterState>>;
struct CharacterObservation {
Eigen::Vector3f position = Eigen::Vector3f::Zero();
float hp = 0.f;
};
struct CharacterUpdateContext {
const CharacterObservation* attackTarget = nullptr;
const CharacterObservation* faceTarget = nullptr;
};
struct CharacterUpdateEvents {
bool attackImpact = false;
bool deathAnimationComplete = false;
bool navigationRequested = false;
Eigen::Vector3f navigationTarget = Eigen::Vector3f::Zero();
std::string arrivedCallbackName;
std::string attackTargetId;
Eigen::Vector3f attackDirection = Eigen::Vector3f::Zero();
};
} // namespace FRG