space-game001/src/Character.h
2026-04-25 14:39:27 +03:00

175 lines
6.9 KiB
C++

#pragma once
#include "BoneAnimatedModelNew.h"
#include "render/Renderer.h"
#include "render/TextureManager.h"
#include "items/Item.h"
#include "SparkEmitter.h"
#include <functional>
#include <memory>
#include <map>
#include <string>
#include <cstdint>
#include <vector>
#include <unordered_map>
namespace ZL {
class TextRenderer;
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,
};
class Character {
public:
using PathPlanner = std::function<std::vector<Eigen::Vector3f>(
const Eigen::Vector3f& start,
const Eigen::Vector3f& end)>;
void loadAnimation(AnimationState state, const std::string& filename, const std::string& zipFile = "");
void loadBinaryAnimation(AnimationState state, const std::string& filename, const std::string& zipFile = "");
// Assigns the given texture to a specific mesh (by name, as defined in the animation file).
void setTexture(const std::string& meshName, std::shared_ptr<Texture> texture);
// Assigns the given texture to every mesh in every loaded animation.
// Call AFTER loading animations so the mesh names are known.
void setTexture(std::shared_ptr<ZL::Texture> texture);
void update(int64_t deltaMs);
// onArrived is called once when the character reaches this target.
// From Python you can chain further commands (walk, wait, trigger others).
void setTarget(const Eigen::Vector3f& target,
std::function<void()> onArrived = nullptr);
void setPathPlanner(PathPlanner planner);
void draw(Renderer& renderer);
void drawShadowDepth(Renderer& renderer);
void drawWithShadow(Renderer& renderer, const Eigen::Matrix4f& lightFromCamera, GLuint shadowMapTex, const Eigen::Vector3f& lightDirCamera);
// attackDirection is a world-space horizontal vector pointing from the
// attacker toward this character — i.e. the direction the hit pushes
// them. A zero vector disables directional sparks (isotropic burst).
void applyDamage(float damageAmount, const Eigen::Vector3f& attackDirection = Eigen::Vector3f::Zero());
// Configures the per-character hit-spark emitter with the shared spark
// texture. Safe to call once per character after construction.
void setupHitSparks(std::shared_ptr<Texture> sparkTexture);
// Call per-frame between update() and draw(), with the active camera
// view matrix (world → eye). The emitter billboards and sorts its
// particles against that camera before its VBO is uploaded.
void prepareHitSparksForDraw(const Eigen::Matrix4f& cameraViewMatrix);
// Draws the NPC name floating above this character in 2D screen space.
// No-op for the player, for dead characters, or if npcName is empty.
// The text is green for peaceful (canAttack == false) NPCs, red otherwise.
void drawName(TextRenderer& textRenderer,
const Eigen::Matrix4f& cameraViewMatrix,
const Eigen::Matrix4f& projectionMatrix);
// Draws a horizontal health bar above this character in 2D screen space.
// Visible only for the player and hostile NPCs (canAttack == true), and
// only while hp > 0. The left portion is green (hp / initialHp) and the
// remainder is red.
void drawHealthBar(Renderer& renderer,
const Eigen::Matrix4f& cameraViewMatrix,
const Eigen::Matrix4f& projectionMatrix);
// Public: read by Game for camera tracking and ray-cast origin
Eigen::Vector3f position = Eigen::Vector3f(0.f, 0.f, 0.f);
float facingAngle = 0.0f;
// Per-character tuning — set after construction, before first update
float walkSpeed = 3.0f;
float rotationSpeed = 4.0f;
float modelScale = 0.12f;
// Applied after scale, fixes model-space orientation (e.g. Blender Z-up exports)
Eigen::Quaternionf modelCorrectionRotation = Eigen::Quaternionf::Identity();
// NPC Gift
Item giftItem;
std::string npcId;
std::string npcName;
bool giftReceived = false;
float hp = 100.f;
// Captured lazily from `hp` on the first update() tick if left at zero;
// set explicitly if you need a different reference for the health bar.
float initialHp = 0.f;
int battle_state = 0;
bool resetAnim = false;
int attack = 0;
AnimationState currentState = AnimationState::STAND;
float attack_cooldown = 0.f;
bool canAttack = false;
Character* attackTarget = nullptr;
// While standing still, smoothly rotate once to face this character.
// Auto-cleared by update() the moment this character finishes rotating,
// so it acts as a one-shot orient (e.g. an NPC turning to the player at
// the start of a conversation, then holding that pose).
Character* faceTarget = nullptr;
bool isPlayer = false;
bool useGpuSkinning = true;
//bool useGpuSkinning = false;
float interactionRadius = 0.0f;
VertexRenderStruct weaponMesh;
std::shared_ptr<Texture> weaponTexture;
Eigen::Matrix3f weaponInitialRotation = Eigen::Matrix3f::Identity();
Eigen::Vector3f weaponInitialPosition = Eigen::Vector3f::Zero();
std::string weaponAttachBoneName = "RightHand";
bool showWeapon = true;
SparkEmitter hitSparkEmitter;
private:
std::map<AnimationState, BoneAnimationDataNew> animations;
VertexRenderStruct modelMutable;
VertexRenderStruct healthBarMesh;
std::unordered_map<std::string, std::shared_ptr<Texture>> meshTextures;
Eigen::Vector3f walkTarget = Eigen::Vector3f(0.f, 0.f, 0.f);
Eigen::Vector3f requestedWalkTarget = Eigen::Vector3f(0.f, 0.f, 0.f);
std::vector<Eigen::Vector3f> pathWaypoints;
size_t currentWaypointIndex = 0;
PathPlanner pathPlanner;
float targetFacingAngle = 0.0f;
std::function<void()> onArrivedCallback;
static constexpr float WALK_THRESHOLD = 0.05f;
static constexpr float TARGET_REPLAN_THRESHOLD = 0.25f;
// Returns the animation state to actually play/draw, falling back to IDLE
// if the requested state has no loaded animation.
AnimationState resolveActiveState() const;
bool prepareGpuSkinning();
// Draws the weapon mesh attached to weaponAttachBoneName.
// Caller must ensure the right shader is active with matching uniforms.
void drawAttachedWeapon(Renderer& renderer);
// GPU skinning: draw using shader-based skinning
void drawGpuSkinning(Renderer& renderer);
// Shadow: draw into depth map (no texture, no projection push)
void drawShadowDepthGpuSkinning(Renderer& renderer);
void drawShadowDepthCpu(Renderer& renderer);
// Shadow: draw with shadow-aware shaders
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);
};
} // namespace ZL