Files
shadow-over-bishkek001/src/CharacterState.h
T
2026-11-15 17:50:57 +06:00

207 lines
7.9 KiB
C++

#pragma once
#include <string>
#include <vector>
#include <unordered_map>
#include <array>
#include <cstdint>
#include <memory>
#include <Eigen/Core>
#include <Eigen/Geometry>
#include "external/nlohmann/json.hpp"
#include "ISaveable.h"
#include "navigation/PathFinder.h"
namespace FRG {
constexpr float VISIBLE_RANGE = 6.f;
struct CharacterUpdateContext;
struct CharacterUpdateEvents;
struct LocationNetworkState;
enum class AnimationState {
STAND = 0,
WALK = 1,
STAND_TO_ACTION = 2,
ACTION_ATTACK = 3,
ACTION_ATTACK_2 = 4,
ACTION_IDLE = 5,
ACTION_TO_STAND = 6,
ACTION_TO_DEATH = 7,
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 {
// Each entry is (AnimationState, filepath). Binary .anim format is auto-detected.
std::vector<std::pair<AnimationState, std::string>> animationPaths;
// Per-mesh texture paths (mesh name → path); takes priority over singleTexturePath.
std::unordered_map<std::string, std::string> meshTexturePaths;
// Single shared texture path (used when setTexture(shared_ptr) was called on all meshes).
std::string singleTexturePath;
// Weapon asset paths and attachment setup
std::string weaponMeshPath;
std::string weaponTexturePath;
std::string weaponAttachBoneName = "RightHand";
Eigen::Vector3f weaponInitialPosition = Eigen::Vector3f::Zero();
// Weapon orientation stored as a quaternion (serializable as 4 floats).
Eigen::Quaternionf weaponInitialRotation = Eigen::Quaternionf::Identity();
// Optional mesh-space transformations applied after loading the weapon mesh.
Eigen::Vector3f weaponMeshMove = Eigen::Vector3f::Zero();
float weaponMeshScale = 1.0f;
};
// All serializable authoritative state for a character. GameState or
// LocationGameState owns it; rendering-only data lives in Character.
class CharacterState : public ISaveable {
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<fp>(kWalkThreshold * kWalkThreshold); };
// --- Identity ---
std::string npcName;
bool isPlayer = false;
bool enabled = false;
bool canAttack = false;
// --- Position / orientation ---
Vec3fp position;
fp facingAngle;
fp targetFacingAngle;
// --- Health ---
float hp = 200.f;
float initialHp = 200.f;
fp visibleRange = static_cast<fp>(VISIBLE_RANGE);
// --- Combat flags ---
int battle_state = 0;
bool resetAnim = false;
int attack = 0;
AnimationState currentState = AnimationState::STAND;
float attack_cooldown = 0.f;
// Weapon visibility toggle (changes during animation)
bool showWeapon = true;
// --- Stable character references ---
// Empty means no target. "player1" 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 ---
Vec3fp homePosition;
fp maxHomeDriftSquared = static_cast<fp>(3.f*3.f);
// --- 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 ---
fp walkSpeed = static_cast<fp>(3.0f);
fp rotationSpeed = static_cast<fp>(4.0f);
float modelScale = 0.12f;
Eigen::Quaternionf modelCorrectionRotation = Eigen::Quaternionf::Identity();
bool useGpuSkinning = true;
//fp collisionRadius = static_cast<fp>(0.4f); // Радиус капсулы персонажа
fp collisionRadius = static_cast<fp>(kCharacterAgentRadius); // Радиус капсулы персонажа
// Векторы скорости в плоскости XZ (сериализовать в JSON не обязательно, если считаются каждый тик,
// но для строгого lockstep-снапшота velocity лучше сохранять)
Vec2fp velocity{ fp(0), fp(0) }; // Реальная скорость с прошлого тика (нужна соседям в ORCA)
Vec2fp preferredVelocity{ fp(0), fp(0) }; // Желаемая скорость к waypoint'у на текущем тике
fp interactionRadius = static_cast<fp>(0.0f);
bool movedLastTick = false;
// --- Pathfinding data (serializable parts) ---
Vec3fp walkTarget;
Vec3fp requestedWalkTarget;
std::vector<Vec3fp> pathWaypoints;
size_t currentWaypointIndex = 0;
// Internal drift-check timer (serializable so it survives save/load).
fp homeDriftCheckTimer = static_cast<fp>(5.0f);
int movementBlockedTicks = 0;
int navigationRetryTicks = 0;
std::vector<int> navigationRetryCooldownTicks;
std::size_t navigationRetryCooldownIndex = 0;
// 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) ---
CharacterCreationInfo creationInfo;
// --- Pure-state methods ---
void configureAsPlayer();
void configureNavigationRetryCooldowns(
const std::string& entityId,
std::vector<int> cooldownTicks = {});
bool isMoving() const;
const Vec3fp& getRequestedWalkTarget() const { return requestedWalkTarget; }
Vec3fp getCurrentNavigationTarget() const;
void stopInPlace();
void setTarget(const Vec3fp& target,
std::string callbackName,
const LocationNetworkState& location,
const std::string& entityId);
void forceReplan(const LocationNetworkState& 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;
private:
void scheduleNextNavigationRetry();
};
using PlayerStateMap =
std::unordered_map<std::string, std::shared_ptr<CharacterState>>;
struct CharacterObservation {
Vec3fp position;
float hp = 0.f;
};
struct CharacterUpdateContext {
std::string characterId;
const CharacterObservation* attackTarget = nullptr;
const CharacterObservation* faceTarget = nullptr;
};
struct CharacterUpdateEvents {
bool attackImpact = false;
bool deathAnimationComplete = false;
bool navigationRequested = false;
bool forceNavigationReplan = false;
Vec3fp navigationTarget;
std::string arrivedCallbackName;
std::string attackTargetId;
Vec3fp attackDirection;
};
} // namespace FRG