Files
shadow-over-bishkek001/src/CharacterState.h
T

185 lines
6.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"
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;
// --- Identity ---
std::string npcName;
bool isPlayer = false;
bool enabled = true;
bool canAttack = false;
// --- Position / orientation ---
Eigen::Vector3f position = Eigen::Vector3f::Zero();
float facingAngle = 0.0f;
float targetFacingAngle = 0.0f;
// --- Health ---
float hp = 200.f;
float initialHp = 200.f;
float visibleRange = 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. "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();
float maxHomeDrift = 3.0f;
// --- Slow-motion effect ---
float slowMoTimeRemaining = 0.0f;
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;
float modelScale = 0.12f;
Eigen::Quaternionf modelCorrectionRotation = Eigen::Quaternionf::Identity();
bool useGpuSkinning = true;
float collisionRadius = 0.45f;
float interactionRadius = 0.0f;
// --- Pathfinding data (serializable parts) ---
Eigen::Vector3f walkTarget = Eigen::Vector3f::Zero();
Eigen::Vector3f requestedWalkTarget = Eigen::Vector3f::Zero();
std::vector<Eigen::Vector3f> pathWaypoints;
size_t currentWaypointIndex = 0;
// Internal drift-check timer (serializable so it survives save/load).
float homeDriftCheckTimer = 5.0f;
// 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();
bool isMoving() const;
const Eigen::Vector3f& getRequestedWalkTarget() const { return requestedWalkTarget; }
Eigen::Vector3f getCurrentNavigationTarget() const;
void stopInPlace();
void setTarget(const Eigen::Vector3f& 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;
};
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