readme
This commit is contained in:
+229
@@ -0,0 +1,229 @@
|
||||
#include "Combat.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "Animator.h"
|
||||
#include "Character.h"
|
||||
#include "Engine.h"
|
||||
#include "Physics.h"
|
||||
|
||||
// How long an animation is at its own speed, in real seconds, which is what animators play in
|
||||
static float ClipSeconds(const Animation* animation) {
|
||||
if (!animation)
|
||||
return 0.0f;
|
||||
const float ticksPerSecond = animation->GetTicksPerSecond() > 0.0f ? animation->GetTicksPerSecond() : 25.0f;
|
||||
return animation->GetDuration() / ticksPerSecond;
|
||||
}
|
||||
|
||||
static float ClipSeconds(const AttackMove& move) {
|
||||
return ClipSeconds(move.animation);
|
||||
}
|
||||
|
||||
// Game seconds a whole ability takes
|
||||
static float AbilityTime(const Ability& ability) {
|
||||
return (ability.duration > 0.0f ? ability.duration : ClipSeconds(ability.animation)) * GetGameTimeScale();
|
||||
}
|
||||
|
||||
// Game seconds into an ability one of its frames is
|
||||
static float AbilityFrameTime(const Ability& ability, float frame) {
|
||||
if (ability.lastFrame > 0.0f)
|
||||
return frame / ability.lastFrame * AbilityTime(ability);
|
||||
return frame / 60.0f * GetGameTimeScale();
|
||||
}
|
||||
|
||||
unsigned int CharacterCombat::NewSwingId() {
|
||||
static unsigned int nextSwingId = 1;
|
||||
return nextSwingId++;
|
||||
}
|
||||
|
||||
void CharacterCombat::Reset() {
|
||||
current = -1;
|
||||
next = 0;
|
||||
time = 0.0f;
|
||||
queued = false;
|
||||
ability = -1;
|
||||
queuedAbility = -1;
|
||||
}
|
||||
|
||||
void CharacterCombat::UseAbility(int index, const glm::vec3& aimPoint) {
|
||||
if (!IsAbilityReady(index))
|
||||
return;
|
||||
queuedAbility = index;
|
||||
queuedAim = aimPoint;
|
||||
}
|
||||
|
||||
const Ability* CharacterCombat::GetCurrentAbility() const {
|
||||
return ability >= 0 && ability < (int)abilities.size() ? &abilities[ability] : nullptr;
|
||||
}
|
||||
|
||||
bool CharacterCombat::IsAbilityReady(int index) const {
|
||||
if (index < 0 || index >= (int)abilities.size())
|
||||
return false;
|
||||
return index >= (int)cooldowns.size() || cooldowns[index] <= 0.0f;
|
||||
}
|
||||
|
||||
float CharacterCombat::GetCooldownFraction(int index) const {
|
||||
if (index < 0 || index >= (int)abilities.size() || index >= (int)cooldowns.size())
|
||||
return 0.0f;
|
||||
const float total = abilities[index].cooldown * GetGameTimeScale();
|
||||
return total > 0.0f ? glm::clamp(cooldowns[index] / total, 0.0f, 1.0f) : 0.0f;
|
||||
}
|
||||
|
||||
std::vector<AbilityFired> CharacterCombat::TakeFired() {
|
||||
std::vector<AbilityFired> result;
|
||||
result.swap(fired);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool CharacterCombat::GetAbilityLunge(float& speed) const {
|
||||
if (ability < 0)
|
||||
return false;
|
||||
const Ability& move = abilities[ability];
|
||||
if (move.lungeDistance <= 0.0f || move.lungeEndFrame <= move.lungeStartFrame)
|
||||
return false;
|
||||
const float start = AbilityFrameTime(move, move.lungeStartFrame);
|
||||
const float end = AbilityFrameTime(move, move.lungeEndFrame);
|
||||
if (abilityTime < start || abilityTime >= end)
|
||||
return false;
|
||||
speed = move.lungeDistance / (end - start);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CharacterCombat::IsAbilityPivoting() const {
|
||||
if (ability < 0)
|
||||
return false;
|
||||
const Ability& move = abilities[ability];
|
||||
return move.pivots && (move.pivotEndFrame <= 0.0f || abilityTime < AbilityFrameTime(move, move.pivotEndFrame));
|
||||
}
|
||||
|
||||
void CharacterCombat::StartAbility(CharacterBody& body, int index) {
|
||||
// It takes over the action layer, so a swing in its recovery is over and the combo starts again after
|
||||
current = -1;
|
||||
next = 0;
|
||||
queued = false;
|
||||
|
||||
ability = index;
|
||||
abilityTime = 0.0f;
|
||||
abilityFired = false;
|
||||
abilityStatusFired = false;
|
||||
abilityLanded = false;
|
||||
abilityUse = NewSwingId();
|
||||
abilityAim = queuedAim;
|
||||
queuedAbility = -1;
|
||||
|
||||
const Ability& move = abilities[index];
|
||||
cooldowns.resize(abilities.size(), 0.0f);
|
||||
cooldowns[index] = move.cooldown * GetGameTimeScale();
|
||||
const float length = AbilityTime(move);
|
||||
if (move.animation)
|
||||
body.PlayAction(move.animation, length > 0.0f ? ClipSeconds(move.animation) * GetGameTimeScale() / length : 1.0f);
|
||||
}
|
||||
|
||||
void CharacterCombat::StepAbility(CharacterBody& body, float dt) {
|
||||
// An ability's cooldown only starts running once it's finished, so it can't be chained straight back into itself
|
||||
for (int i = 0; i < (int)cooldowns.size(); i++)
|
||||
if (i != ability)
|
||||
cooldowns[i] = std::max(cooldowns[i] - dt, 0.0f);
|
||||
|
||||
if (ability >= 0) {
|
||||
abilityTime += dt;
|
||||
const Ability& move = abilities[ability];
|
||||
const float length = AbilityTime(move);
|
||||
if (!abilityFired && abilityTime >= AbilityFrameTime(move, move.fireFrame)) {
|
||||
abilityFired = true;
|
||||
fired.push_back({ ability, abilityAim });
|
||||
}
|
||||
if (abilityFired && !abilityStatusFired && move.appliesStatus && move.statusFrame > 0.0f
|
||||
&& abilityTime >= AbilityFrameTime(move, move.statusFrame)) {
|
||||
abilityStatusFired = true;
|
||||
fired.push_back({ ability, abilityAim, true });
|
||||
}
|
||||
// Landing cuts it short at its hitExitFrame, if it has one, and missing at its missExitFrame
|
||||
const bool exitEarly = abilityLanded
|
||||
? move.hitExitFrame > 0.0f && abilityTime >= AbilityFrameTime(move, move.hitExitFrame)
|
||||
: move.missExitFrame > 0.0f && abilityTime >= AbilityFrameTime(move, move.missExitFrame);
|
||||
if (abilityFired && (abilityTime >= length || exitEarly)) {
|
||||
if (exitEarly && body.actionAnimator && body.actionAnimator->m_CurrentAnimation == move.animation)
|
||||
body.actionAnimator->StopAnimation();
|
||||
ability = -1;
|
||||
abilityLanded = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Waits for a swing to reach its recovery, like the next swing in a combo would
|
||||
if (queuedAbility < 0 || ability >= 0)
|
||||
return;
|
||||
if (current >= 0 && time < FrameTime(combo[current], combo[current].recoveryFrame))
|
||||
return;
|
||||
StartAbility(body, queuedAbility);
|
||||
}
|
||||
|
||||
// Combat counts in game time like everything else in a character, so real seconds get scaled up to it
|
||||
float CharacterCombat::MoveTime(const AttackMove& move) const {
|
||||
return (move.duration > 0.0f ? move.duration : ClipSeconds(move)) * GetGameTimeScale();
|
||||
}
|
||||
|
||||
float CharacterCombat::FrameTime(const AttackMove& move, float frame) const {
|
||||
if (move.lastFrame <= 0.0f)
|
||||
return 0.0f;
|
||||
return frame / move.lastFrame * MoveTime(move);
|
||||
}
|
||||
|
||||
void CharacterCombat::Start(CharacterBody& body, int index) {
|
||||
current = index;
|
||||
next = (index + 1) % (int)combo.size();
|
||||
time = 0.0f;
|
||||
queued = false;
|
||||
swingId = NewSwingId();
|
||||
|
||||
// Sped up or slowed down to last as long as the move does
|
||||
const AttackMove& move = combo[index];
|
||||
const float length = MoveTime(move);
|
||||
body.PlayAction(move.animation, length > 0.0f ? ClipSeconds(move) * GetGameTimeScale() / length : 1.0f);
|
||||
|
||||
// A little step into it
|
||||
if (body.physics && move.forwardVelocity != 0.0f) {
|
||||
body.physics->linearvelocity += body.GetForwardDirection() * move.forwardVelocity;
|
||||
body.physics->WakeUp();
|
||||
}
|
||||
}
|
||||
|
||||
void CharacterCombat::Step(CharacterBody& body, float dt) {
|
||||
StepAbility(body, dt);
|
||||
if (combo.empty() || ability >= 0) {
|
||||
queued = false; // no swinging in the middle of an ability
|
||||
return;
|
||||
}
|
||||
|
||||
if (current >= 0) {
|
||||
time += dt;
|
||||
const AttackMove& move = combo[current];
|
||||
if (time >= FrameTime(move, move.lastFrame)) {
|
||||
// Let it play all the way out, so the combo starts over
|
||||
current = -1;
|
||||
next = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (!queued)
|
||||
return;
|
||||
if (current < 0) {
|
||||
Start(body, next);
|
||||
return;
|
||||
}
|
||||
|
||||
// Cutting into the recovery carries the combo on. The last swing has to be waited out.
|
||||
const AttackMove& move = combo[current];
|
||||
const bool last = current == (int)combo.size() - 1;
|
||||
if (!last && time >= FrameTime(move, move.recoveryFrame))
|
||||
Start(body, next);
|
||||
}
|
||||
|
||||
const AttackMove* CharacterCombat::GetCurrentMove() const {
|
||||
return current >= 0 && current < (int)combo.size() ? &combo[current] : nullptr;
|
||||
}
|
||||
|
||||
bool CharacterCombat::IsHitting() const {
|
||||
const AttackMove* move = GetCurrentMove();
|
||||
return move && time >= FrameTime(*move, move->hitStartFrame) && time <= FrameTime(*move, move->hitEndFrame);
|
||||
}
|
||||
Reference in New Issue
Block a user