finished the game (i will probably not be touching this again)

This commit is contained in:
2026-11-08 12:51:19 -05:00
parent 36e093f84c
commit 5d7aa3b93b
37 changed files with 141 additions and 11 deletions
+105 -8
View File
@@ -226,7 +226,7 @@ void Engine::Initiate()
shoot.knockback = 1.5f;
shoot.stun = 0.8f;
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
shoot.killAnimation = charactermanager->LoadAnimation("rifleexecutionhit", "assets/rifleexecutionhit.glb", "humanoid");
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.
Ability 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.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
@@ -246,6 +246,7 @@ void Engine::Initiate()
stab.hitExitFrame = 50.0f; // landing it, it's over 10 frames early
stab.cooldown = 3.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.lungeStartFrame = 0.0f;
stab.lungeEndFrame = 15.0f;
@@ -481,7 +482,14 @@ void Engine::Initiate()
// 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,
float stun) {
// Hyper armor takes the damage and nothing else
const bool armored = target.HasHyperArmor();
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.Stun(stun);
if (target.ragdoll) {
@@ -968,7 +976,8 @@ void Engine::Initiate()
float life = 0.0f; // real seconds left, 0 when it's free
};
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) {
Tracer* tracer = nullptr;
@@ -979,12 +988,12 @@ void Engine::Initiate()
tracer = &tracers.back();
tracer->mesh = CreateCubeMesh();
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->castshadow = false;
}
// 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.TranslateTo(from + direction * (length * 0.5f));
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->facecamera = true;
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) {
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) {
if (tracer.life <= 0.0f)
continue;
@@ -1015,7 +1080,11 @@ void Engine::Initiate()
if (tracer.life <= 0.0f) {
tracer.life = 0.0f;
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.killAnimation = ability.killAnimation;
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);
ShowTracer(origin, direction, ability.range);
@@ -1052,6 +1128,16 @@ void Engine::Initiate()
muzzleSparks->t.RotateToQuaternion(LookAt(direction));
for (int i = 0; i < sparksPerShot; i++)
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
if (!ability.sound.empty())
@@ -1179,7 +1265,7 @@ void Engine::Initiate()
auto ApplyAbilityStatus = [&](Character& user, const Ability& ability) {
const unsigned int use = user.body->combat.GetAbilityUse();
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);
};
@@ -1490,6 +1576,7 @@ void Engine::Initiate()
// ------------------------------------------------------------------------
struct Execution {
Character* victim = nullptr;
Character* killer = nullptr; // has iframes until it's over, so the cutscene can't be cut short
Animation* animation = nullptr;
float timeLeft = 0.0f; // real seconds
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;
execution.timeLeft = animation->GetDuration() / ticksPerSecond;
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)
execution.sounds = ability->killSounds;
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
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) {
action->holdsAtEnd = false;
action->StopAnimation();
@@ -1865,7 +1962,7 @@ void Engine::Initiate()
StartExecution(target, attacker, hitbox.killAnimation, hitbox.killAbility);
return;
}
if (hitbox.knockdown > 0.0f)
if (hitbox.knockdown > 0.0f && !target.HasHyperArmor())
target.Knockdown(hitbox.knockdown);
if (hitbox.onHit)
hitbox.onHit(target);