finished the game (i will probably not be touching this again)
This commit is contained in:
@@ -434,6 +434,17 @@ bool Character::CanAct() const {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Character::IsInvulnerable() const {
|
||||||
|
for (const StatusEffect& status : statuses)
|
||||||
|
if (GetStatusEffectInfo(status.type).invulnerable)
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Character::HasHyperArmor() const {
|
||||||
|
return body && body->combat.HasHyperArmor();
|
||||||
|
}
|
||||||
|
|
||||||
bool Character::DodgesHits() const {
|
bool Character::DodgesHits() const {
|
||||||
for (const StatusEffect& status : statuses)
|
for (const StatusEffect& status : statuses)
|
||||||
if (GetStatusEffectInfo(status.type).dodgesHits)
|
if (GetStatusEffectInfo(status.type).dodgesHits)
|
||||||
|
|||||||
@@ -220,6 +220,8 @@ public:
|
|||||||
bool IsKnockedDown() const { return HasStatus(StatusEffect::Type::Knockdown); }
|
bool IsKnockedDown() const { return HasStatus(StatusEffect::Type::Knockdown); }
|
||||||
bool CanAct() const; // not stunned or held by a status effect
|
bool CanAct() const; // not stunned or held by a status effect
|
||||||
bool DodgesHits() const; // a status effect keeps ordinary hits off it
|
bool DodgesHits() const; // a status effect keeps ordinary hits off it
|
||||||
|
bool IsInvulnerable() const; // has iframes: a status effect keeps every hit off it
|
||||||
|
bool HasHyperArmor() const; // in a move that takes hits without flinching, being stunned or pushed about
|
||||||
const std::vector<StatusEffect>& GetStatuses() const { return statuses; }
|
const std::vector<StatusEffect>& GetStatuses() const { return statuses; }
|
||||||
|
|
||||||
// Recoils from a hit: whatever swing it was in is cut off and its flinch plays over everything, if it has one.
|
// Recoils from a hit: whatever swing it was in is cut off and its flinch plays over everything, if it has one.
|
||||||
|
|||||||
@@ -96,6 +96,14 @@ bool CharacterCombat::IsAbilityPivoting() const {
|
|||||||
return move.pivots && (move.pivotEndFrame <= 0.0f || abilityTime < AbilityFrameTime(move, move.pivotEndFrame));
|
return move.pivots && (move.pivotEndFrame <= 0.0f || abilityTime < AbilityFrameTime(move, move.pivotEndFrame));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool CharacterCombat::HasHyperArmor() const {
|
||||||
|
if (ability < 0)
|
||||||
|
return false;
|
||||||
|
const Ability& move = abilities[ability];
|
||||||
|
return move.hyperArmor && abilityTime >= AbilityFrameTime(move, move.hyperArmorStartFrame)
|
||||||
|
&& (move.hyperArmorEndFrame <= 0.0f || abilityTime < AbilityFrameTime(move, move.hyperArmorEndFrame));
|
||||||
|
}
|
||||||
|
|
||||||
void CharacterCombat::StartAbility(CharacterBody& body, int index) {
|
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
|
// It takes over the action layer, so a swing in its recovery is over and the combo starts again after
|
||||||
current = -1;
|
current = -1;
|
||||||
|
|||||||
@@ -53,6 +53,10 @@ struct Ability {
|
|||||||
float moveSpeedMultiplier = 0.5f; // of walking speed while it's going
|
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
|
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
|
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.
|
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 screenshake = 0.0f; // how hard the camera shakes as it goes off, when it's the player's
|
||||||
|
|
||||||
@@ -142,6 +146,9 @@ public:
|
|||||||
// Whether the ability it's in turns it to face where it's aimed right now
|
// Whether the ability it's in turns it to face where it's aimed right now
|
||||||
bool IsAbilityPivoting() const;
|
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
|
// 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;
|
bool IsAbilityReady(int index) const;
|
||||||
float GetCooldownFraction(int index) const;
|
float GetCooldownFraction(int index) const;
|
||||||
|
|||||||
+105
-8
@@ -226,7 +226,7 @@ void Engine::Initiate()
|
|||||||
shoot.knockback = 1.5f;
|
shoot.knockback = 1.5f;
|
||||||
shoot.stun = 0.8f;
|
shoot.stun = 0.8f;
|
||||||
shoot.sound = "assets/gunshot.mp3";
|
shoot.sound = "assets/gunshot.mp3";
|
||||||
shoot.screenshake = 0.45f; // a punch landing is 0.3, the combo finisher 0.6
|
shoot.screenshake = 0.65f; // a punch landing is 0.3, the combo finisher 0.6
|
||||||
if (std::filesystem::exists("assets/rifleexecutionhit.glb")) // whoever it kills goes down like this, filmed by its Camera bone
|
if (std::filesystem::exists("assets/rifleexecutionhit.glb")) // whoever it kills goes down like this, filmed by its Camera bone
|
||||||
shoot.killAnimation = charactermanager->LoadAnimation("rifleexecutionhit", "assets/rifleexecutionhit.glb", "humanoid");
|
shoot.killAnimation = charactermanager->LoadAnimation("rifleexecutionhit", "assets/rifleexecutionhit.glb", "humanoid");
|
||||||
shoot.killSounds = { { 40.0f, "assets/dramaticwoosh.mp3" }, { 60.0f, "assets/glassbreak.mp3" } };
|
shoot.killSounds = { { 40.0f, "assets/dramaticwoosh.mp3" }, { 60.0f, "assets/glassbreak.mp3" } };
|
||||||
@@ -238,7 +238,7 @@ void Engine::Initiate()
|
|||||||
// 2: a switchblade out of nowhere into whoever's in front. It stays in them a while, bleeding them.
|
// 2: a switchblade out of nowhere into whoever's in front. It stays in them a while, bleeding them.
|
||||||
Ability stab;
|
Ability stab;
|
||||||
stab.name = "Stab";
|
stab.name = "Stab";
|
||||||
stab.description = "Pull a switchblade and stab whoever is right in front of you. The knife stays in them, making them bleed. Stuns what it hits.";
|
stab.description = "Pull a switchblade and stab whoever is right in front of you. The knife stays in them, making them bleed. Stuns what it hits. Hyper armor: hits don't stop it.";
|
||||||
stab.effect = Ability::Effect::Stab;
|
stab.effect = Ability::Effect::Stab;
|
||||||
stab.animation = charactermanager->LoadAnimation("riflestab", "assets/riflestab.glb", "humanoid"); // brings the Knife bone
|
stab.animation = charactermanager->LoadAnimation("riflestab", "assets/riflestab.glb", "humanoid"); // brings the Knife bone
|
||||||
stab.lastFrame = 60.0f; // 1 s at 60 fps, played at its own speed
|
stab.lastFrame = 60.0f; // 1 s at 60 fps, played at its own speed
|
||||||
@@ -246,6 +246,7 @@ void Engine::Initiate()
|
|||||||
stab.hitExitFrame = 50.0f; // landing it, it's over 10 frames early
|
stab.hitExitFrame = 50.0f; // landing it, it's over 10 frames early
|
||||||
stab.cooldown = 3.0f;
|
stab.cooldown = 3.0f;
|
||||||
stab.moveSpeedMultiplier = 0.0f;
|
stab.moveSpeedMultiplier = 0.0f;
|
||||||
|
stab.hyperArmor = true; // trades hits rather than being knocked out of it
|
||||||
stab.lungeDistance = 1.5f; // steps in over the first 15 frames
|
stab.lungeDistance = 1.5f; // steps in over the first 15 frames
|
||||||
stab.lungeStartFrame = 0.0f;
|
stab.lungeStartFrame = 0.0f;
|
||||||
stab.lungeEndFrame = 15.0f;
|
stab.lungeEndFrame = 15.0f;
|
||||||
@@ -481,7 +482,14 @@ void Engine::Initiate()
|
|||||||
// The camera shakes by shake when it was the player's.
|
// The camera shakes by shake when it was the player's.
|
||||||
auto LandHit = [&](const Character* attacker, Character& target, float damage, const glm::vec3& shove, float shake,
|
auto LandHit = [&](const Character* attacker, Character& target, float damage, const glm::vec3& shove, float shake,
|
||||||
float stun) {
|
float stun) {
|
||||||
|
// Hyper armor takes the damage and nothing else
|
||||||
|
const bool armored = target.HasHyperArmor();
|
||||||
Hurt(target, damage);
|
Hurt(target, damage);
|
||||||
|
if (armored && !target.IsDead()) {
|
||||||
|
if (attacker == controlled)
|
||||||
|
camera->AddScreenshake(shake);
|
||||||
|
return;
|
||||||
|
}
|
||||||
target.Flinch(); // nothing if that was the hit that put them down
|
target.Flinch(); // nothing if that was the hit that put them down
|
||||||
target.Stun(stun);
|
target.Stun(stun);
|
||||||
if (target.ragdoll) {
|
if (target.ragdoll) {
|
||||||
@@ -968,7 +976,8 @@ void Engine::Initiate()
|
|||||||
float life = 0.0f; // real seconds left, 0 when it's free
|
float life = 0.0f; // real seconds left, 0 when it's free
|
||||||
};
|
};
|
||||||
std::vector<Tracer> tracers;
|
std::vector<Tracer> tracers;
|
||||||
const float tracerTime = 0.06f;
|
const float tracerTime = 0.16f; // real seconds, thinning out to nothing over it
|
||||||
|
const float tracerThickness = 0.07f;
|
||||||
|
|
||||||
auto ShowTracer = [&](const glm::vec3& from, const glm::vec3& direction, float length) {
|
auto ShowTracer = [&](const glm::vec3& from, const glm::vec3& direction, float length) {
|
||||||
Tracer* tracer = nullptr;
|
Tracer* tracer = nullptr;
|
||||||
@@ -979,12 +988,12 @@ void Engine::Initiate()
|
|||||||
tracer = &tracers.back();
|
tracer = &tracers.back();
|
||||||
tracer->mesh = CreateCubeMesh();
|
tracer->mesh = CreateCubeMesh();
|
||||||
tracer->mesh->name = "tracer";
|
tracer->mesh->name = "tracer";
|
||||||
tracer->mesh->colorOverride = glm::vec4(1.0f, 0.86f, 0.55f, 1.0f);
|
tracer->mesh->colorOverride = glm::vec4(1.0f, 0.82f, 0.45f, 1.0f);
|
||||||
tracer->mesh->emissive = 1.0f;
|
tracer->mesh->emissive = 1.0f;
|
||||||
tracer->mesh->castshadow = false;
|
tracer->mesh->castshadow = false;
|
||||||
}
|
}
|
||||||
// A thin box from end to end, its length along its own z
|
// A thin box from end to end, its length along its own z
|
||||||
tracer->mesh->t.ScaleTo({ 0.025f, 0.025f, length });
|
tracer->mesh->t.ScaleTo({ tracerThickness, tracerThickness, length });
|
||||||
tracer->mesh->t.RotateToQuaternion(LookAt(direction));
|
tracer->mesh->t.RotateToQuaternion(LookAt(direction));
|
||||||
tracer->mesh->t.TranslateTo(from + direction * (length * 0.5f));
|
tracer->mesh->t.TranslateTo(from + direction * (length * 0.5f));
|
||||||
if (tracer->life <= 0.0f)
|
if (tracer->life <= 0.0f)
|
||||||
@@ -1004,10 +1013,66 @@ void Engine::Initiate()
|
|||||||
muzzleSparks->emitangle = glm::vec3(0.7f, 0.7f, 0.0f); // radians of spread
|
muzzleSparks->emitangle = glm::vec3(0.7f, 0.7f, 0.0f); // radians of spread
|
||||||
muzzleSparks->facecamera = true;
|
muzzleSparks->facecamera = true;
|
||||||
muzzleSparks->BindToRenderSystem();
|
muzzleSparks->BindToRenderSystem();
|
||||||
const int sparksPerShot = 14;
|
const int sparksPerShot = 32;
|
||||||
|
|
||||||
|
// The flash itself: a few big bright blobs right on the muzzle for a frame or two
|
||||||
|
ParticleEmitter* muzzleFlash = new ParticleEmitter();
|
||||||
|
muzzleFlash->name = "muzzle flash";
|
||||||
|
muzzleFlash->surface = SurfaceTexture::Stretched(blanktex);
|
||||||
|
muzzleFlash->opaque = false;
|
||||||
|
muzzleFlash->color = glm::vec4(3.0f, 2.2f, 1.0f, 1.0f);
|
||||||
|
muzzleFlash->size = glm::vec3(0.32f);
|
||||||
|
muzzleFlash->speed = 2.5f;
|
||||||
|
muzzleFlash->lifespan = 0.05f;
|
||||||
|
muzzleFlash->emitangle = glm::vec3(0.5f, 0.5f, 0.0f);
|
||||||
|
muzzleFlash->facecamera = true;
|
||||||
|
muzzleFlash->BindToRenderSystem();
|
||||||
|
const int flashesPerShot = 6;
|
||||||
|
|
||||||
|
// Smoke hanging about the barrel afterwards, drifting up
|
||||||
|
ParticleEmitter* muzzleSmoke = new ParticleEmitter();
|
||||||
|
muzzleSmoke->name = "muzzle smoke";
|
||||||
|
muzzleSmoke->surface = SurfaceTexture::Stretched(blanktex);
|
||||||
|
muzzleSmoke->opaque = false;
|
||||||
|
muzzleSmoke->color = glm::vec4(0.55f, 0.55f, 0.55f, 0.35f);
|
||||||
|
muzzleSmoke->size = glm::vec3(0.22f);
|
||||||
|
muzzleSmoke->speed = 1.2f;
|
||||||
|
muzzleSmoke->lifespan = 0.7f;
|
||||||
|
muzzleSmoke->emitangle = glm::vec3(1.2f, 1.2f, 0.0f);
|
||||||
|
muzzleSmoke->gravity = glm::vec3(0.0f, 1.5f, 0.0f);
|
||||||
|
muzzleSmoke->facecamera = true;
|
||||||
|
muzzleSmoke->BindToRenderSystem();
|
||||||
|
const int smokePerShot = 8;
|
||||||
|
|
||||||
|
// Sparks off whoever it hits, back toward the shooter
|
||||||
|
ParticleEmitter* impactSparks = new ParticleEmitter();
|
||||||
|
impactSparks->name = "impact sparks";
|
||||||
|
impactSparks->surface = SurfaceTexture::Stretched(blanktex);
|
||||||
|
impactSparks->opaque = false;
|
||||||
|
impactSparks->color = glm::vec4(2.0f, 1.4f, 0.6f, 1.0f);
|
||||||
|
impactSparks->size = glm::vec3(0.05f);
|
||||||
|
impactSparks->speed = 6.0f;
|
||||||
|
impactSparks->lifespan = 0.2f;
|
||||||
|
impactSparks->emitangle = glm::vec3(1.6f, 1.6f, 0.0f);
|
||||||
|
impactSparks->gravity = glm::vec3(0.0f, -12.0f, 0.0f);
|
||||||
|
impactSparks->facecamera = true;
|
||||||
|
impactSparks->BindToRenderSystem();
|
||||||
|
const int impactSparksPerHit = 24;
|
||||||
|
|
||||||
|
// Lights up everything around the gun for a moment
|
||||||
|
PointLight* muzzleLight = rendersystem->lighting->AddPointLight(glm::vec3(0.0f), glm::vec3(1.0f, 0.7f, 0.35f), 0.0f, 8.0f);
|
||||||
|
muzzleLight->castShadow = false; // too quick to be worth six shadow renders a frame
|
||||||
|
const float muzzleLightIntensity = 160.0f;
|
||||||
|
const float muzzleLightTime = 0.08f; // real seconds
|
||||||
|
float muzzleLightLeft = 0.0f;
|
||||||
|
|
||||||
auto UpdateTracers = [&](float dt) {
|
auto UpdateTracers = [&](float dt) {
|
||||||
muzzleSparks->Step(dt);
|
muzzleSparks->Step(dt);
|
||||||
|
muzzleFlash->Step(dt);
|
||||||
|
muzzleSmoke->Step(dt);
|
||||||
|
impactSparks->Step(dt);
|
||||||
|
muzzleLightLeft = std::max(muzzleLightLeft - dt, 0.0f);
|
||||||
|
muzzleLight->intensity = muzzleLightIntensity * (muzzleLightLeft / muzzleLightTime);
|
||||||
for (Tracer& tracer : tracers) {
|
for (Tracer& tracer : tracers) {
|
||||||
if (tracer.life <= 0.0f)
|
if (tracer.life <= 0.0f)
|
||||||
continue;
|
continue;
|
||||||
@@ -1015,7 +1080,11 @@ void Engine::Initiate()
|
|||||||
if (tracer.life <= 0.0f) {
|
if (tracer.life <= 0.0f) {
|
||||||
tracer.life = 0.0f;
|
tracer.life = 0.0f;
|
||||||
rendersystem->RemoveRenderable(tracer.mesh);
|
rendersystem->RemoveRenderable(tracer.mesh);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
const float thickness = tracerThickness * (tracer.life / tracerTime);
|
||||||
|
const glm::vec3 scale = tracer.mesh->t.GetScale();
|
||||||
|
tracer.mesh->t.ScaleTo({ thickness, thickness, scale.z });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1042,6 +1111,13 @@ void Engine::Initiate()
|
|||||||
shot.stun = ability.stun;
|
shot.stun = ability.stun;
|
||||||
shot.killAnimation = ability.killAnimation;
|
shot.killAnimation = ability.killAnimation;
|
||||||
shot.killAbility = &ability;
|
shot.killAbility = &ability;
|
||||||
|
shot.onHit = [&, direction](Character& target) {
|
||||||
|
const glm::vec3 at = target.body->t.GetTranslation() + glm::vec3(0.0f, target.body->t.GetScale().y * 0.2f, 0.0f);
|
||||||
|
impactSparks->t.TranslateTo(at);
|
||||||
|
impactSparks->t.RotateToQuaternion(LookAt(-direction));
|
||||||
|
for (int i = 0; i < impactSparksPerHit; i++)
|
||||||
|
impactSparks->Emit();
|
||||||
|
};
|
||||||
hitboxsystem->Add(shot);
|
hitboxsystem->Add(shot);
|
||||||
ShowTracer(origin, direction, ability.range);
|
ShowTracer(origin, direction, ability.range);
|
||||||
|
|
||||||
@@ -1052,6 +1128,16 @@ void Engine::Initiate()
|
|||||||
muzzleSparks->t.RotateToQuaternion(LookAt(direction));
|
muzzleSparks->t.RotateToQuaternion(LookAt(direction));
|
||||||
for (int i = 0; i < sparksPerShot; i++)
|
for (int i = 0; i < sparksPerShot; i++)
|
||||||
muzzleSparks->Emit();
|
muzzleSparks->Emit();
|
||||||
|
muzzleFlash->t.TranslateTo(origin + direction * 0.15f);
|
||||||
|
muzzleFlash->t.RotateToQuaternion(LookAt(direction));
|
||||||
|
for (int i = 0; i < flashesPerShot; i++)
|
||||||
|
muzzleFlash->Emit();
|
||||||
|
muzzleSmoke->t.TranslateTo(origin);
|
||||||
|
muzzleSmoke->t.RotateToQuaternion(LookAt(direction));
|
||||||
|
for (int i = 0; i < smokePerShot; i++)
|
||||||
|
muzzleSmoke->Emit();
|
||||||
|
muzzleLight->position = origin + direction * 0.3f;
|
||||||
|
muzzleLightLeft = muzzleLightTime;
|
||||||
|
|
||||||
// Its own channel every time, so quick shots overlap rather than cutting each other off
|
// Its own channel every time, so quick shots overlap rather than cutting each other off
|
||||||
if (!ability.sound.empty())
|
if (!ability.sound.empty())
|
||||||
@@ -1179,7 +1265,7 @@ void Engine::Initiate()
|
|||||||
auto ApplyAbilityStatus = [&](Character& user, const Ability& ability) {
|
auto ApplyAbilityStatus = [&](Character& user, const Ability& ability) {
|
||||||
const unsigned int use = user.body->combat.GetAbilityUse();
|
const unsigned int use = user.body->combat.GetAbilityUse();
|
||||||
for (const AbilityHit& hit : abilityHits)
|
for (const AbilityHit& hit : abilityHits)
|
||||||
if (hit.user == &user && hit.use == use && !hit.target->IsDead())
|
if (hit.user == &user && hit.use == use && !hit.target->IsDead() && !hit.target->IsInvulnerable())
|
||||||
hit.target->AddStatus(ability.status, ability.statusTime);
|
hit.target->AddStatus(ability.status, ability.statusTime);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1490,6 +1576,7 @@ void Engine::Initiate()
|
|||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
struct Execution {
|
struct Execution {
|
||||||
Character* victim = nullptr;
|
Character* victim = nullptr;
|
||||||
|
Character* killer = nullptr; // has iframes until it's over, so the cutscene can't be cut short
|
||||||
Animation* animation = nullptr;
|
Animation* animation = nullptr;
|
||||||
float timeLeft = 0.0f; // real seconds
|
float timeLeft = 0.0f; // real seconds
|
||||||
float time = 0.0f; // real seconds since it started
|
float time = 0.0f; // real seconds since it started
|
||||||
@@ -1526,6 +1613,9 @@ void Engine::Initiate()
|
|||||||
const float ticksPerSecond = animation->GetTicksPerSecond() > 0.0f ? animation->GetTicksPerSecond() : 25.0f;
|
const float ticksPerSecond = animation->GetTicksPerSecond() > 0.0f ? animation->GetTicksPerSecond() : 25.0f;
|
||||||
execution.timeLeft = animation->GetDuration() / ticksPerSecond;
|
execution.timeLeft = animation->GetDuration() / ticksPerSecond;
|
||||||
execution.filmed = (&victim == controlled || killer == controlled) && !IsFilming();
|
execution.filmed = (&victim == controlled || killer == controlled) && !IsFilming();
|
||||||
|
execution.killer = const_cast<Character*>(killer);
|
||||||
|
if (execution.killer && execution.killer != &victim)
|
||||||
|
execution.killer->AddStatus(StatusEffect::Type::Invulnerable);
|
||||||
if (ability && execution.filmed)
|
if (ability && execution.filmed)
|
||||||
execution.sounds = ability->killSounds;
|
execution.sounds = ability->killSounds;
|
||||||
executions.push_back(execution);
|
executions.push_back(execution);
|
||||||
@@ -1560,6 +1650,13 @@ void Engine::Initiate()
|
|||||||
|
|
||||||
// Over: up again somewhere else, or for those that heal on death, right where they are
|
// Over: up again somewhere else, or for those that heal on death, right where they are
|
||||||
victim.RemoveStatus(StatusEffect::Type::Executed);
|
victim.RemoveStatus(StatusEffect::Type::Executed);
|
||||||
|
if (Character* killer = execution.killer) {
|
||||||
|
// Unless it's in another one of its own
|
||||||
|
const bool stillExecuting = std::any_of(executions.begin(), executions.end(), [&](const Execution& other) {
|
||||||
|
return &other != &execution && other.killer == killer; });
|
||||||
|
if (!stillExecuting)
|
||||||
|
killer->RemoveStatus(StatusEffect::Type::Invulnerable);
|
||||||
|
}
|
||||||
if (Animator* action = victim.body->actionAnimator; action && action->m_CurrentAnimation == execution.animation) {
|
if (Animator* action = victim.body->actionAnimator; action && action->m_CurrentAnimation == execution.animation) {
|
||||||
action->holdsAtEnd = false;
|
action->holdsAtEnd = false;
|
||||||
action->StopAnimation();
|
action->StopAnimation();
|
||||||
@@ -1865,7 +1962,7 @@ void Engine::Initiate()
|
|||||||
StartExecution(target, attacker, hitbox.killAnimation, hitbox.killAbility);
|
StartExecution(target, attacker, hitbox.killAnimation, hitbox.killAbility);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (hitbox.knockdown > 0.0f)
|
if (hitbox.knockdown > 0.0f && !target.HasHyperArmor())
|
||||||
target.Knockdown(hitbox.knockdown);
|
target.Knockdown(hitbox.knockdown);
|
||||||
if (hitbox.onHit)
|
if (hitbox.onHit)
|
||||||
hitbox.onHit(target);
|
hitbox.onHit(target);
|
||||||
|
|||||||
+1
-1
@@ -36,7 +36,7 @@ void HitboxSystem::Resolve(const std::vector<Character*>& characters, const HitH
|
|||||||
for (Character* target : characters) {
|
for (Character* target : characters) {
|
||||||
if (target == hitbox.owner || target->IsDead() || target->ragdoll || !target->body)
|
if (target == hitbox.owner || target->IsDead() || target->ragdoll || !target->body)
|
||||||
continue;
|
continue;
|
||||||
if (target->DodgesHits() && !hitbox.hitsKnockedDown)
|
if (target->IsInvulnerable() || (target->DodgesHits() && !hitbox.hitsKnockedDown))
|
||||||
continue;
|
continue;
|
||||||
if (hitbox.swing != 0) {
|
if (hitbox.swing != 0) {
|
||||||
const std::vector<const Character*>& already = hitBySwing[hitbox.swing];
|
const std::vector<const Character*>& already = hitBySwing[hitbox.swing];
|
||||||
|
|||||||
+7
-2
@@ -8,7 +8,8 @@ struct StatusEffect {
|
|||||||
enum class Type {
|
enum class Type {
|
||||||
HeavyGravity, // pulled down harder: slower, can't jump, and falls faster
|
HeavyGravity, // pulled down harder: slower, can't jump, and falls faster
|
||||||
Knockdown, // on the floor: can't do anything, and only attacks that hit the knocked down can hit it
|
Knockdown, // on the floor: can't do anything, and only attacks that hit the knocked down can hit it
|
||||||
Executed // playing out how it was killed: can't do anything or be hit until it's over
|
Executed, // playing out how it was killed: can't do anything or be hit until it's over
|
||||||
|
Invulnerable // iframes: nothing at all can hit it, not even what hits the knocked down
|
||||||
};
|
};
|
||||||
|
|
||||||
Type type = Type::HeavyGravity;
|
Type type = Type::HeavyGravity;
|
||||||
@@ -23,13 +24,17 @@ struct StatusEffectInfo {
|
|||||||
bool preventsJumping = false;
|
bool preventsJumping = false;
|
||||||
bool preventsActing = false; // can't move, turn, attack or anything else, like a stun
|
bool preventsActing = false; // can't move, turn, attack or anything else, like a stun
|
||||||
bool dodgesHits = false; // only hitboxes that hit the knocked down can touch it
|
bool dodgesHits = false; // only hitboxes that hit the knocked down can touch it
|
||||||
|
bool invulnerable = false; // no hitbox can touch it
|
||||||
};
|
};
|
||||||
|
|
||||||
inline const StatusEffectInfo& GetStatusEffectInfo(StatusEffect::Type type) {
|
inline const StatusEffectInfo& GetStatusEffectInfo(StatusEffect::Type type) {
|
||||||
static const StatusEffectInfo heavyGravity = { "HEAVY", 0.5f, 3.0f, true };
|
static const StatusEffectInfo heavyGravity = { "HEAVY", 0.5f, 3.0f, true };
|
||||||
static const StatusEffectInfo knockdown = { "KNOCKED DOWN", 0.0f, 1.0f, true, true, true };
|
static const StatusEffectInfo knockdown = { "KNOCKED DOWN", 0.0f, 1.0f, true, true, true };
|
||||||
static const StatusEffectInfo executed = { "", 0.0f, 1.0f, true, true, true };
|
static const StatusEffectInfo executed = { "", 0.0f, 1.0f, true, true, true, true };
|
||||||
|
static const StatusEffectInfo invulnerable = { "", 1.0f, 1.0f, false, false, false, true };
|
||||||
switch (type) {
|
switch (type) {
|
||||||
|
case StatusEffect::Type::Invulnerable:
|
||||||
|
return invulnerable;
|
||||||
case StatusEffect::Type::Executed:
|
case StatusEffect::Type::Executed:
|
||||||
return executed;
|
return executed;
|
||||||
case StatusEffect::Type::Knockdown:
|
case StatusEffect::Type::Knockdown:
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user