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:
@@ -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) {
|
||||
|
||||
@@ -115,5 +115,22 @@ void main() {
|
||||
expect(player.bonusFlash, lessThan(1.0));
|
||||
expect(player.bonusFlash, greaterThan(0.0));
|
||||
});
|
||||
|
||||
test('weapon switch animates at canonical tic pacing', () {
|
||||
final player = Player(x: 1.5, y: 1.5, angle: 0);
|
||||
player.weapons[WeaponType.machineGun] = MachineGun();
|
||||
|
||||
player.requestWeaponSwitch(WeaponType.machineGun);
|
||||
expect(player.switchState, WeaponSwitchState.lowering);
|
||||
|
||||
player.tick(const Duration(milliseconds: 86));
|
||||
expect(player.switchState, WeaponSwitchState.raising);
|
||||
expect(player.weaponAnimOffset, closeTo(500.0, 0.001));
|
||||
expect(player.currentWeapon.type, WeaponType.machineGun);
|
||||
|
||||
player.tick(const Duration(milliseconds: 86));
|
||||
expect(player.switchState, WeaponSwitchState.idle);
|
||||
expect(player.weaponAnimOffset, closeTo(0.0, 0.001));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -49,6 +49,27 @@ void main() {
|
||||
expect(mapPixels.contains(ColorPalette.vga32Bit[2]), isTrue);
|
||||
expect(mapPixels[hudProbeIndex], equals(normalPixels[hudProbeIndex]));
|
||||
});
|
||||
|
||||
test('software renderer does not apply bonus flash while menu is open', () {
|
||||
final input = _MutableInput();
|
||||
final engine = _buildEngine(input: input);
|
||||
engine.init();
|
||||
input.isBack = true;
|
||||
engine.tick(const Duration(milliseconds: 16));
|
||||
input.isBack = false;
|
||||
|
||||
expect(engine.isMenuOpen, isTrue);
|
||||
|
||||
final renderer = SoftwareRenderer();
|
||||
|
||||
engine.player.bonusFlash = 0.0;
|
||||
final List<int> menuBaseline = List<int>.from(renderer.render(engine).pixels);
|
||||
|
||||
engine.player.bonusFlash = 1.0;
|
||||
final List<int> menuWithFlash = List<int>.from(renderer.render(engine).pixels);
|
||||
|
||||
expect(menuWithFlash, equals(menuBaseline));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -69,7 +90,12 @@ class _StaticInput extends Wolf3dInput {
|
||||
}
|
||||
}
|
||||
|
||||
WolfEngine _buildEngine() {
|
||||
class _MutableInput extends Wolf3dInput {
|
||||
@override
|
||||
void update() {}
|
||||
}
|
||||
|
||||
WolfEngine _buildEngine({Wolf3dInput? input}) {
|
||||
final wallGrid = _buildGrid();
|
||||
final objectGrid = _buildGrid();
|
||||
|
||||
@@ -115,7 +141,7 @@ WolfEngine _buildEngine() {
|
||||
difficulty: Difficulty.medium,
|
||||
startingEpisode: 0,
|
||||
frameBuffer: FrameBuffer(96, 96),
|
||||
input: _StaticInput(),
|
||||
input: input ?? _StaticInput(),
|
||||
onGameWon: () {},
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user