#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 "dialogue/TranslationDatabase.h" #include "AudioPlayerAsync.h" namespace ZL { class TextRenderer; class Character { public: // Resolves a character index to a live Character pointer. // kNoTarget (-1) → nullptr, kPlayerIndex (-2) → Location::player, 0..n → npcs[n]. using CharacterResolver = std::function; using PathPlanner = std::function( const Eigen::Vector3f& start, const Eigen::Vector3f& end)>; // All serializable game state (position, hp, flags, pathfinding, etc.) CharacterState state; int index = CharacterState::kNoTarget; // ---- 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(CharacterState st, Renderer& renderer, const char* zipFile); // ---- Per-frame update ---- // resolver maps character indices to live pointers so attackTargetIndex / faceTargetIndex // can be dereferenced inside update(). Pass nullptr only if no targets are set. void update(int64_t deltaMs, const CharacterResolver& resolver = nullptr); void setPathPlanner(PathPlanner planner); // ---- Navigation (path data lives in state; pathPlanner lives here) ---- // callbackName, if given, is the global Lua function name backing onArrived — // stored in state.onArrivedCallbackName so it survives save/load (see ScriptEngine::rehydrateNamedCallback). void setTarget(const Eigen::Vector3f& target, std::function onArrived = nullptr, std::string callbackName = ""); void stopAndRotateToTarget(int npcId); void forceReplan(); void stopInPlace() { state.stopInPlace(); } // ---- Reset ---- void resetPlayerAfterDeath(); void tryAttackCombo(); // ---- Damage ---- void applyDamage(float damageAmount, const Eigen::Vector3f& attackDirection = Eigen::Vector3f::Zero(), int attackerIndex = CharacterState::kNoTarget); // ---- 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 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 (delegate to state) ---- bool isMoving() const { return state.isMoving(); } const Eigen::Vector3f& getRequestedWalkTarget() const { return state.getRequestedWalkTarget(); } Eigen::Vector3f getCurrentNavigationTarget() const { return state.getCurrentNavigationTarget(); } float getHp() const { return state.getHp(); } void setHp(float newHp) { state.setHp(newHp); } float getSpeedMultiplier(float transitionProgress) 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; static Dialogue::TranslationDatabase nameTranslations; private: std::map animations; VertexRenderStruct modelMutable; VertexRenderStruct healthBarMesh; std::unordered_map> meshTextures; PathPlanner pathPlanner; static constexpr float TARGET_REPLAN_THRESHOLD = 0.25f; static constexpr float HOME_DRIFT_CHECK_INTERVAL = 5.0f; AnimationState resolveActiveState() const; bool prepareGpuSkinning(); void drawAttachedWeapon(Renderer& renderer); void drawGpuSkinning(Renderer& renderer); void drawShadowDepthGpuSkinning(Renderer& renderer); void drawShadowDepthCpu(Renderer& renderer); void drawGpuSkinningWithShadow(Renderer& renderer, const Eigen::Matrix4f& lightFromCamera, GLuint shadowMapTex, const Eigen::Vector3f& lightDirCamera); void drawCpuWithShadow(Renderer& renderer, const Eigen::Matrix4f& lightFromCamera, GLuint shadowMapTex, const Eigen::Vector3f& lightDirCamera); void drawNightGpuSkinning(Renderer& renderer, 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 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 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 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 ZL