#pragma once #include #include #include #include #include #include #include #include #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> 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; // --- 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 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 --- 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 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>; 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