#ifndef COMBAT_CLASS #define COMBAT_CLASS #include #include "StatusEffect.h" #include #include class Animation; class CharacterBody; // One swing in a combo. Timings are frames of its animation, which has lastFrame of them. struct AttackMove { Animation* animation = nullptr; float duration = 0.5f; // real seconds for the whole move, recovery and all. 0 plays the animation at its own speed. float lastFrame = 120.0f; float hitStartFrame = 40.0f; // the fist can connect from here float hitEndFrame = 60.0f; // to here float recoveryFrame = 60.0f; // from here it's just getting back to normal, and the next swing can cut in float damage = 8.0f; glm::vec3 hitboxSize = glm::vec3(0.9f, 1.2f, 1.0f); // meters across, tall and deep glm::vec3 hitboxOffset = glm::vec3(0.0f, 0.3f, 0.85f); // its middle from the attacker's: right, up and forward. Starts at its front. float knockback = 2.0f; // m/s away from the attacker float knockbackUp = 0.0f; // m/s upward, enough of it and they leave the ground float stun = 0.0f; // real seconds whoever it hits can't do anything bool hitsKnockedDown = false; // can hit characters that are knocked down float moveSpeedMultiplier = 0.0f; // of walking speed while it's going, recovery and all. 0 stands still. float forwardVelocity = 4.0f; // m/s added along where it faces as the swing starts }; // A move on a number key, like shooting. It plays on the action layer like a swing and goes off partway through, // then can't be used again until its cooldown is up. Timings are frames of its animation, which has lastFrame of them, // or with lastFrame 0, frames at 60 a second. struct Ability { enum class Effect { Shot, // a long thin hitbox from the gun toward where it was aimed, out for the frame it fires Stab, // a short hitbox in front of it the frame it fires. A hit leaves its prop stuck in them, bleeding them. Sweep // a wide hitbox in front of it the frame it fires, catching everyone in it }; std::string name; std::string description; // what it does, shown hovering over it in the moves list Effect effect = Effect::Shot; Animation* animation = nullptr; float duration = 0.0f; // real seconds for the whole move. 0 plays the animation at its own speed. float lastFrame = 40.0f; float fireFrame = 4.0f; // when it goes off float hitExitFrame = 0.0f; // when it lands, it's over this far in rather than at lastFrame. 0 plays it all. float missExitFrame = 0.0f; // when it doesn't land, it's over this far in. 0 plays it all. float cooldown = 0.5f; // real seconds from it finishing until it can be used again float moveSpeedMultiplier = 0.5f; // of walking speed while it's going bool pivots = false; // keeps turning to face where the player looks while it's going, so it can be aimed float pivotEndFrame = 0.0f; // stops pivoting here, 0 pivots all the way through // Hyper armor: hits still hurt, but don't flinch, stun, knock back or knock down the user, so the move carries on bool hyperArmor = false; float hyperArmorStartFrame = 0.0f; float hyperArmorEndFrame = 0.0f; // 0 keeps it to the end std::string sound; // played where it goes off, as it does. Empty plays nothing. float screenshake = 0.0f; // how hard the camera shakes as it goes off, when it's the player's float damage = 12.0f; float range = 100.0f; // meters, how long the shot's hitbox reaches glm::vec2 hitboxSize = glm::vec2(0.3f, 0.3f); // meters across and tall, around the line of the shot float knockback = 1.0f; // m/s away from the shooter float stun = 0.0f; // real seconds whoever it hits can't do anything float knockdownTime = 0.0f; // real seconds whoever it hits is knocked down for, 0 doesn't // Played on whoever it kills, instead of them just dropping. If it has a bone called Camera and the player is // either of them, the camera follows that bone until it's over. Animation* killAnimation = nullptr; struct KillSound { float frame = 0.0f; // into killAnimation, at 60 a second std::string file; }; std::vector killSounds; // played over the kill animation, heard when the camera's following it bool hitsKnockedDown = false; // can hit characters that are knocked down // A model shown on one of the animation's own bones while it's going, like a knife. Empty shows nothing. std::string prop; std::string propBone; glm::vec3 propTurn = glm::vec3(0.0f); // degrees around x, y and z on top of the bone's rotation // Stab only: how long the prop stays stuck in whoever it hits, hurting them bleedDamage a second meanwhile float bleedTime = 0.0f; // real seconds float bleedDamage = 0.0f; // Carries it forward lungeDistance meters, at a steady pace from lungeStartFrame to lungeEndFrame. 0 stays put. float lungeDistance = 0.0f; float lungeStartFrame = 0.0f; float lungeEndFrame = 0.0f; // Whoever it hits gets status for statusTime real seconds, at statusFrame if it's still going then, // or as they're hit with statusFrame 0 bool appliesStatus = false; StatusEffect::Type status = StatusEffect::Type::HeavyGravity; float statusTime = 0.0f; float statusFrame = 0.0f; }; // An ability that's just gone off, for the game to carry out struct AbilityFired { int index = -1; // into abilities glm::vec3 aimPoint = glm::vec3(0.0f); // world space, what it was aimed at bool status = false; // its statusFrame came rather than its fireFrame: whoever it hit gets its status now }; // A character's attacks: a combo that goes through its moves in order, each click starting the next one // while the last is recovering. Waiting out a recovery starts the combo over, so does finishing it. // Abilities sit beside it, one per number key. class CharacterCombat { public: std::vector combo; std::string comboName; // what the moves list calls the combo std::vector abilities; // abilities[0] is on 1, abilities[1] on 2 and so on // Asks for the next swing, which starts as soon as the one going lets it void QueueAttack() { queued = true; } // Uses abilities[index] aimed at aimPoint, as soon as whatever's going lets it. Nothing while it's cooling down. void UseAbility(int index, const glm::vec3& aimPoint); // Stops whatever swing or ability is going, and starts the combo over. Cooldowns carry on. void Reset(); // Once a frame, dt in game time. Starts queued swings on the body's action layer. void Step(CharacterBody& body, float dt); // The swing going now, nullptr between swings const AttackMove* GetCurrentMove() const; int GetCurrentIndex() const { return current; } // The ability going now, nullptr if none is const Ability* GetCurrentAbility() const; // Different every time an ability starts, for telling one use from the next unsigned int GetAbilityUse() const { return abilityUse; } // The ability going now landed, so it ends early if it has a hitExitFrame void AbilityLanded() { abilityLanded = ability >= 0; } bool HasAbilityLanded() const { return abilityLanded; } // Whether the ability it's in is lunging it forward right now, and if so how fast, in m/s of game time bool GetAbilityLunge(float& speed) const; // Whether the ability it's in turns it to face where it's aimed right now bool IsAbilityPivoting() const; // Whether the ability it's in has hyper armor right now bool HasHyperArmor() const; // Whether abilities[index] can be used now, and how much of its cooldown is left, 1 just used to 0 ready bool IsAbilityReady(int index) const; float GetCooldownFraction(int index) const; // Abilities that went off since the last call, emptied by it std::vector TakeFired(); // Whether the current swing can connect right now, so its hitbox is out bool IsHitting() const; // Different for every swing anyone throws, so its hitbox only hits each character once unsigned int GetSwingId() const { return swingId; } // A swing id nothing else has, for anything else that puts out hitboxes static unsigned int NewSwingId(); private: float MoveTime(const AttackMove& move) const; // game seconds the whole move takes float FrameTime(const AttackMove& move, float frame) const; // game seconds into the move void Start(CharacterBody& body, int index); void StartAbility(CharacterBody& body, int index); void StepAbility(CharacterBody& body, float dt); int current = -1; // index into combo, -1 between swings int next = 0; float time = 0.0f; // seconds into the current swing bool queued = false; unsigned int swingId = 0; int ability = -1; // index into abilities going now, -1 if none float abilityTime = 0.0f; // game seconds into it bool abilityFired = false; bool abilityStatusFired = false; bool abilityLanded = false; unsigned int abilityUse = 0; int queuedAbility = -1; glm::vec3 queuedAim = glm::vec3(0.0f); glm::vec3 abilityAim = glm::vec3(0.0f); std::vector cooldowns; // game seconds left, by ability std::vector fired; }; #endif