#ifndef CHARACTER_CLASS #define CHARACTER_CLASS #include #include #include #include #include #include "t.h" #include "Rig.h" #include "Object.h" #include "Animator.h" class Character; class CharacterManager; class CharacterBody; class BodyComponent; struct Hitbox { t transform; int damage = 0; float knockback = 0.0f; bool active = false; std::set hitTargets; bool CanHit(Character* target) const { return target && hitTargets.find(target) == hitTargets.end(); } void RegisterHit(Character* target) { if (target) hitTargets.insert(target); } }; class Action { public: std::string Name; std::function FunctionPointer; }; class InputAction { public: std::string BodyComponentName; std::string ActionName; }; class BodyComponent { public: std::string name; BodyComponent* ParentComponent = nullptr; std::vector Actions; }; class CharacterBody : public t_package { public: int Health = 100; Rig* rig = nullptr; std::vector animators; Animator* animator = nullptr; // back-compat: base animator at weight 0 std::vector components; BodyComponent* RootComponent = nullptr; CharacterBody() { animator = AddAnimator(0); animator->blendWeight = 1.0f; // base always fully visible } Animator* AddAnimator(int weight) { Animator* a = new Animator(); a->weight = weight; if (rig) a->SetRig(rig); animators.push_back(a); std::sort(animators.begin(), animators.end(), [](Animator* a, Animator* b) { return a->weight < b->weight; }); return a; } // Returns weighted blend of all playing animators. // Base (weight 0) is always the foundation; higher-weight animators // blend in on top based on their blendWeight (0?1). std::vector GetBlendedBoneMatrices() const { // Start from the base animator std::vector result = animator->GetFinalBoneMatrices(); const int count = (int)result.size(); // Blend each higher-priority playing animator on top for (int i = 1; i < (int)animators.size(); i++) { Animator* a = animators[i]; if (!a->isPlaying || !a->m_CurrentAnimation || a->blendWeight <= 0.0f) continue; const auto& matrices = a->GetFinalBoneMatrices(); for (int j = 0; j < count && j < (int)matrices.size(); j++) result[j] = glm::mix(result[j], matrices[j], a->blendWeight); } return result; } // Returns the highest-weight playing animator (for back-compat) Animator* GetActiveAnimator() const { for (int i = (int)animators.size() - 1; i >= 0; i--) if (animators[i]->isPlaying && animators[i]->m_CurrentAnimation) return animators[i]; return animator; } void SetRig(Rig* r) { rig = r; for (Animator* a : animators) a->SetRig(r); } void SetAnimation(Animation* anim) { animator->SetAnimation(anim); animator->PlayAnimation(); animator->blendWeight = 1.0f; // base never blends � always instant } void LoadAnimation(Animation* anim) { SetAnimation(anim); } void Step(float dt) { for (Animator* a : animators) a->Step(dt); } BodyComponent* GetComponentOfName(std::string name); void ExecuteAction(InputAction* action); }; class CharacterMind { public: InputAction* ProduceAction(); }; class Character : public Object, public Renderable { public: CharacterMind* mind = nullptr; CharacterBody* body = nullptr; glm::vec4 colorOverride = glm::vec4(0.0f); // rgb + strength (0 = keep the rig's own texture) float visualScale = 1.4f; // rig is ~1.8 tall natively; hitbox is 2.5 float metallic = 0.0f; float roughness = 0.7f; float ao = 1.0f; float turnSpeed = 12.0f; float punchDamage = 12.0f; float punchReach = 1.15f; float punchAnimationSpeed = 0.6f; glm::vec3 punchHitboxSize = glm::vec3(1.1f, 1.5f, 1.4f); void PressInput(char letter); void ReleaseInput(char letter); void ClearInput(); glm::vec3 GetMovementDirection(const glm::vec3& forward, const glm::vec3& right) const; void SetTurnDirection(const glm::vec3& direction); const glm::vec3& GetTurnDirection() const { return turnDirection; } void SetPunchAnimation(Animation* animation); void SetPunchLooping(bool looping); void Punch(const glm::vec3& lookDirection = glm::vec3(0.0f)); void SetWalking(bool walking); glm::vec3 GetForwardDirection() const; const Hitbox& GetPunchHitbox() const { return punchHitbox; } Character(); void Render(Shader& shader) override; void Step(float dt) { if (punchLookLocked && punchAnimator && punchAnimator->isPlaying) turnDirection = punchLookDirection; TurnToDirection(dt); punchHitbox.active = false; body->Step(dt); } private: friend class CharacterManager; void TurnToDirection(float dt); void OnPunchAnimationEnd(); std::set inputLetters; glm::vec3 turnDirection = glm::vec3(0.0f); Animation* punchAnimation = nullptr; Animator* punchAnimator = nullptr; Hitbox punchHitbox; glm::vec3 punchLookDirection = glm::vec3(0.0f); bool punchLookLocked = false; }; #endif