#pragma once #include "CharacterState.h" #include "BoneAnimatedModelNew.h" #include "render/Renderer.h" #include "render/TextureManager.h" #include "SparkEmitter.h" #include #include #include #include #include #include #include #include #include #include "dialogue/TranslationDatabase.h" #include "AudioPlayerAsync.h" namespace FRG { class TextRenderer; class IGameServer; class Character { public: Character(IGameServer& server, std::string locationId, std::string entityId) : gameServer_(server), locationId_(std::move(locationId)), entityId_(std::move(entityId)) {} const std::string& entityId() const { return entityId_; } void bindToLocation(std::string locationId) { locationId_ = std::move(locationId); } // The aliasing shared_ptr keeps the owning GameSnapshot alive for as long // as the returned CharacterState is being read. std::shared_ptr stateSnapshot() const; static constexpr float kAttackRange = 1.25f; // ---- Asset loading ---- void loadAnimation(AnimationState animState, const std::string& filename, const std::string& zipFile = ""); void loadBinaryAnimation(AnimationState animState, const std::string& filename, const std::string& zipFile = ""); // Assigns a texture to a specific mesh by name. void setTexture(const std::string& meshName, std::shared_ptr texture); // Assigns one texture to every mesh in every loaded animation. Call AFTER loading animations. void setTexture(std::shared_ptr texture); // Creates a fully-loaded Character from a CharacterState (including its creationInfo). // Loads all animations, textures, and weapon mesh/texture from the paths stored in creationInfo. static std::unique_ptr createFromState( const CharacterState& initialState, IGameServer& server, const std::string& locationId, const std::string& entityId, Renderer& renderer, const char* zipFile); // ---- Per-frame update ---- // Applies authoritative animation state to runtime resources and consumes // presentation-only events emitted by CharacterState::update. void update(int64_t deltaMs, const CharacterUpdateEvents* events = nullptr); // ---- Damage ---- void presentDamage(float hp, float maxHp, const Vec3fp& attackDirection, const std::string& attackerId); // ---- Spark setup ---- void setupHitSparks(std::shared_ptr sparkTexture); void prepareHitSparksForDraw(const Eigen::Matrix4f& cameraViewMatrix); // ---- HUD overlays ---- void drawName(TextRenderer& textRenderer, const Eigen::Matrix4f& cameraViewMatrix, const Eigen::Matrix4f& projectionMatrix); void drawPlayerName(TextRenderer& textRenderer, const Eigen::Matrix4f& cameraViewMatrix, const Eigen::Matrix4f& projectionMatrix, const std::string& displayName); void drawHealthBar(Renderer& renderer, const Eigen::Matrix4f& cameraViewMatrix, const Eigen::Matrix4f& projectionMatrix); // ---- Draw passes ---- void draw(Renderer& renderer); void drawShadowDepth(Renderer& renderer); void drawWithShadow(Renderer& renderer, const Eigen::Matrix4f& lightFromCamera, GLuint shadowMapTex, const Eigen::Vector3f& lightDirCamera); void drawNight(Renderer& renderer, const float* pointLightPosEye, const float* pointLightDirEye, const float* pointLightColors, int pointLightCount, const float* pointLightWorldXZ, const float* pointLightLimitXZ, const Eigen::Matrix4f& cameraViewInverse, const float* ambientColor, const float* fogColor); void drawNightWithShadow(Renderer& renderer, const float* plPosEye, const float* plDirEye, const float* plColors, int plCount, const float* plWorldXZ, const float* plLimitXZ, const Eigen::Matrix4f& cameraViewInverse, const Eigen::Matrix4f& lightFromCamera, GLuint shadowMapTex, const float* ambientColor, const float* fogColor); // ---- Query helpers (read immutable snapshots) ---- bool isMoving() const; Eigen::Vector3f renderPosition() const; float getHp() const; // ---- Weapon rendering data (initialized from state.creationInfo by createFromState) ---- VertexRenderStruct weaponMesh; std::shared_ptr weaponTexture; Eigen::Matrix3f weaponInitialRotation = Eigen::Matrix3f::Identity(); Eigen::Vector3f weaponInitialPosition = Eigen::Vector3f::Zero(); std::string weaponAttachBoneName = "RightHand"; SparkEmitter hitSparkEmitter; AudioPlayerAsync* audioPlayer = nullptr; static Dialogue::TranslationDatabase nameTranslations; private: friend class Game; friend class Location; friend class ScriptEngine; friend class LocationEditor; friend class PathVisualization; IGameServer& gameServer_; std::string locationId_; std::string entityId_; std::function onDeathAnimComplete; std::function onHpChanged; std::map animations; VertexRenderStruct modelMutable; VertexRenderStruct healthBarMesh; std::unordered_map> meshTextures; struct PresentationSample { Eigen::Vector3f position = Eigen::Vector3f::Zero(); float facingAngle = 0.0f; float animationFrame = 0.0f; AnimationState currentState = AnimationState::STAND; bool enabled = true; }; PresentationSample previousPresentationSample_; PresentationSample currentPresentationSample_; std::chrono::steady_clock::time_point lastPresentationUpdate_; bool hasPresentationSample_ = false; AnimationState resolveActiveState(const CharacterState& state) const; CharacterState interpolatedState(const CharacterState& state) const; void updateAnimationForRender(const CharacterState& state); bool prepareGpuSkinning(const CharacterState& state); void drawAttachedWeapon(Renderer& renderer, const CharacterState& state); void drawGpuSkinning(Renderer& renderer, const CharacterState& state); void drawShadowDepthGpuSkinning(Renderer& renderer, const CharacterState& state); void drawShadowDepthCpu(Renderer& renderer, const CharacterState& state); void drawGpuSkinningWithShadow(Renderer& renderer, const CharacterState& state, const Eigen::Matrix4f& lightFromCamera, GLuint shadowMapTex, const Eigen::Vector3f& lightDirCamera); void drawCpuWithShadow(Renderer& renderer, const CharacterState& state, const Eigen::Matrix4f& lightFromCamera, GLuint shadowMapTex, const Eigen::Vector3f& lightDirCamera); void drawNightGpuSkinning(Renderer& renderer, const CharacterState& state, const float* plPosEye, const float* plDirEye, const float* plColors, int plCount, const float* plWorldXZ, const float* plLimitXZ, const Eigen::Matrix4f& cameraViewInverse, const float* ambientColor, const float* fogColor); void drawNightCpu(Renderer& renderer, const CharacterState& state, const float* plPosEye, const float* plDirEye, const float* plColors, int plCount, const float* plWorldXZ, const float* plLimitXZ, const Eigen::Matrix4f& cameraViewInverse, const float* ambientColor, const float* fogColor); void drawNightGpuSkinningWithShadow(Renderer& renderer, const CharacterState& state, const float* plPosEye, const float* plDirEye, const float* plColors, int plCount, const float* plWorldXZ, const float* plLimitXZ, const Eigen::Matrix4f& cameraViewInverse, const Eigen::Matrix4f& lightFromCamera, GLuint shadowMapTex, const float* ambientColor, const float* fogColor); void drawNightCpuWithShadow(Renderer& renderer, const CharacterState& state, const float* plPosEye, const float* plDirEye, const float* plColors, int plCount, const float* plWorldXZ, const float* plLimitXZ, const Eigen::Matrix4f& cameraViewInverse, const Eigen::Matrix4f& lightFromCamera, GLuint shadowMapTex, const float* ambientColor, const float* fogColor); }; } // namespace FRG