feat: Enhance weapon switching logic and add tests for animation pacing and menu behavior

Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
2026-03-23 12:35:31 +01:00
parent 827b8c779e
commit a66ccf52c5
6 changed files with 66 additions and 15 deletions
@@ -61,8 +61,11 @@ class Player {
// 0.0 is resting, 500.0 is fully off-screen
double weaponAnimOffset = 0.0;
// How fast the weapon drops/raises per tick
final double switchSpeed = 30.0;
static const double _weaponSwitchTravel = 500.0;
static const double _weaponSwitchPhaseTics = 6.0;
static const double _ticRate = 70.0;
static const double _weaponSwitchUnitsPerSecond =
(_weaponSwitchTravel * _ticRate) / _weaponSwitchPhaseTics;
Player({required this.x, required this.y, required this.angle}) {
currentWeapon = weapons[WeaponType.pistol]!;
@@ -121,12 +124,18 @@ class Player {
}
}
updateWeaponSwitch();
updateWeaponSwitch(elapsed);
}
// --- Weapon Switching & Animation Logic ---
void updateWeaponSwitch() {
void updateWeaponSwitch(Duration elapsed) {
final double delta =
_weaponSwitchUnitsPerSecond * (elapsed.inMicroseconds / 1000000.0);
if (delta <= 0.0) {
return;
}
if (switchState == WeaponSwitchState.lowering) {
// If the map doesn't contain the pending weapon, stop immediately
if (weapons[pendingWeaponType] == null) {
@@ -134,9 +143,9 @@ class Player {
return;
}
weaponAnimOffset += switchSpeed;
if (weaponAnimOffset >= 500.0) {
weaponAnimOffset = 500.0;
weaponAnimOffset += delta;
if (weaponAnimOffset >= _weaponSwitchTravel) {
weaponAnimOffset = _weaponSwitchTravel;
// We already know it's not null now, but we can keep the
// fallback to pistol just to be extra safe.
@@ -145,7 +154,7 @@ class Player {
switchState = WeaponSwitchState.raising;
}
} else if (switchState == WeaponSwitchState.raising) {
weaponAnimOffset -= switchSpeed;
weaponAnimOffset -= delta;
if (weaponAnimOffset <= 0) {
weaponAnimOffset = 0.0;
switchState = WeaponSwitchState.idle;
@@ -175,8 +184,7 @@ class Player {
// A 10 damage hit gives a 0.5 flash, a 20 damage hit maxes it out at 1.0
damageFlash = math.min(1.0, damageFlash + (damage * 0.05));
_chaingunPickupFaceMsRemaining = 0;
_mutantDeathFaceActive =
health <= 0 && attackerType == EnemyType.mutant;
_mutantDeathFaceActive = health <= 0 && attackerType == EnemyType.mutant;
if (health <= 0) {
log("[PLAYER] Died! Final Score: $score");
@@ -2094,7 +2094,7 @@ class AsciiRenderer extends CliRendererBackend<dynamic> {
@override
dynamic finalizeFrame() {
if (engine.difficulty != null) {
if (engine.difficulty != null && !engine.isMenuOpen) {
if (engine.player.damageFlash > 0.0) {
if (_usesTerminalLayout) {
_applyDamageFlashToScene();
@@ -1336,7 +1336,7 @@ class SixelRenderer extends CliRendererBackend<String> {
sb.write('\x1bPq');
sb.write('"1;1;$_outputWidth;$_outputHeight');
final bool gameplayActive = engine.difficulty != null;
final bool gameplayActive = engine.difficulty != null && !engine.isMenuOpen;
final double damageIntensity = gameplayActive
? engine.player.damageFlash
: 0.0;
@@ -1401,7 +1401,7 @@ class SoftwareRenderer extends RendererBackend<FrameBuffer> {
@override
FrameBuffer finalizeFrame() {
if (engine.difficulty != null) {
if (engine.difficulty != null && !engine.isMenuOpen) {
if (engine.player.damageFlash > 0) {
_applyDamageFlash();
} else if (engine.player.bonusFlash > 0) {