175 lines
6.9 KiB
C++
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
|