#pragma once #include #include #include #include #include #include #include #include #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> animationPaths; // Per-mesh texture paths (mesh name → path); takes priority over singleTexturePath. std::unordered_map 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(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(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(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 animationStartFrames = {{0, 0, 0, 0, 0, 0, 0, 0, 0}}; std::array animationFrameCounts = {{1, 1, 1, 1, 1, 1, 1, 1, 1}}; // --- Movement / visual parameters --- fp walkSpeed = static_cast(3.0f); fp rotationSpeed = static_cast(4.0f); float modelScale = 0.12f; Eigen::Quaternionf modelCorrectionRotation = Eigen::Quaternionf::Identity(); bool useGpuSkinning = true; //fp collisionRadius = static_cast(0.4f); // Радиус капсулы персонажа fp collisionRadius = static_cast(kCharacterAgentRadius); // Радиус капсулы персонажа // Векторы скорости в плоскости XZ (сериализовать в JSON не обязательно, если считаются каждый тик, // но для строгого lockstep-снапшота velocity лучше сохранять) Vec2fp velocity{ fp(0), fp(0) }; // Реальная скорость с прошлого тика (нужна соседям в ORCA) Vec2fp preferredVelocity{ fp(0), fp(0) }; // Желаемая скорость к waypoint'у на текущем тике fp interactionRadius = static_cast(0.0f); bool movedLastTick = false; // --- Pathfinding data (serializable parts) --- Vec3fp walkTarget; Vec3fp requestedWalkTarget; std::vector pathWaypoints; size_t currentWaypointIndex = 0; // Internal drift-check timer (serializable so it survives save/load). fp homeDriftCheckTimer = static_cast(5.0f); int movementBlockedTicks = 0; int navigationRetryTicks = 0; std::vector 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 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>; 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