Added some additional sound effects
Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
@@ -19,6 +19,12 @@ class Pushwall {
|
|||||||
///
|
///
|
||||||
/// Only one pushwall can be active (moving) at any given time.
|
/// Only one pushwall can be active (moving) at any given time.
|
||||||
class PushwallManager {
|
class PushwallManager {
|
||||||
|
/// Creates a pushwall manager with an optional sound dispatch callback.
|
||||||
|
PushwallManager({this.onPlaySound});
|
||||||
|
|
||||||
|
/// Optional callback used to emit audio cues when pushwalls activate.
|
||||||
|
final void Function(int sfxId)? onPlaySound;
|
||||||
|
|
||||||
final Map<String, Pushwall> pushwalls = {};
|
final Map<String, Pushwall> pushwalls = {};
|
||||||
Pushwall? activePushwall;
|
Pushwall? activePushwall;
|
||||||
|
|
||||||
@@ -121,6 +127,7 @@ class PushwallManager {
|
|||||||
int checkY = targetY + pw.dirY;
|
int checkY = targetY + pw.dirY;
|
||||||
if (wallGrid[checkY][checkX] == 0) {
|
if (wallGrid[checkY][checkX] == 0) {
|
||||||
activePushwall = pw;
|
activePushwall = pw;
|
||||||
|
onPlaySound?.call(WolfSound.pushWall);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -135,34 +135,55 @@ class Player {
|
|||||||
ammo = newAmmo;
|
ammo = newAmmo;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool tryPickup(Collectible item) {
|
/// Attempts to collect [item] and returns the SFX to play.
|
||||||
|
///
|
||||||
|
/// Returns `null` when the item was not collected (for example: full health).
|
||||||
|
int? tryPickup(Collectible item) {
|
||||||
bool pickedUp = false;
|
bool pickedUp = false;
|
||||||
|
int? pickupSfxId;
|
||||||
|
|
||||||
switch (item.type) {
|
switch (item.type) {
|
||||||
case CollectibleType.health:
|
case CollectibleType.health:
|
||||||
if (health >= 100) return false;
|
if (health >= 100) return null;
|
||||||
heal(item.mapId == MapObject.dogFoodDecoration ? 4 : 25);
|
heal(item.mapId == MapObject.food ? 4 : 25);
|
||||||
|
pickupSfxId = item.mapId == MapObject.food
|
||||||
|
? WolfSound.healthSmall
|
||||||
|
: WolfSound.healthLarge;
|
||||||
pickedUp = true;
|
pickedUp = true;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case CollectibleType.ammo:
|
case CollectibleType.ammo:
|
||||||
if (ammo >= 99) return false;
|
if (ammo >= 99) return null;
|
||||||
int previousAmmo = ammo;
|
int previousAmmo = ammo;
|
||||||
addAmmo(8);
|
addAmmo(8);
|
||||||
if (currentWeapon is Knife && previousAmmo <= 0) {
|
if (currentWeapon is Knife && previousAmmo <= 0) {
|
||||||
requestWeaponSwitch(WeaponType.pistol);
|
requestWeaponSwitch(WeaponType.pistol);
|
||||||
}
|
}
|
||||||
|
pickupSfxId = WolfSound.getAmmo;
|
||||||
pickedUp = true;
|
pickedUp = true;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case CollectibleType.treasure:
|
case CollectibleType.treasure:
|
||||||
if (item.mapId == MapObject.cross) score += 100;
|
if (item.mapId == MapObject.cross) {
|
||||||
if (item.mapId == MapObject.chalice) score += 500;
|
score += 100;
|
||||||
if (item.mapId == MapObject.chest) score += 1000;
|
pickupSfxId = WolfSound.treasure1;
|
||||||
if (item.mapId == MapObject.crown) score += 5000;
|
}
|
||||||
|
if (item.mapId == MapObject.chalice) {
|
||||||
|
score += 500;
|
||||||
|
pickupSfxId = WolfSound.treasure2;
|
||||||
|
}
|
||||||
|
if (item.mapId == MapObject.chest) {
|
||||||
|
score += 1000;
|
||||||
|
pickupSfxId = WolfSound.treasure3;
|
||||||
|
}
|
||||||
|
if (item.mapId == MapObject.crown) {
|
||||||
|
score += 5000;
|
||||||
|
pickupSfxId = WolfSound.treasure4;
|
||||||
|
}
|
||||||
if (item.mapId == MapObject.extraLife) {
|
if (item.mapId == MapObject.extraLife) {
|
||||||
heal(100);
|
heal(100);
|
||||||
addAmmo(25);
|
addAmmo(25);
|
||||||
|
pickupSfxId = WolfSound.extraLife;
|
||||||
}
|
}
|
||||||
pickedUp = true;
|
pickedUp = true;
|
||||||
break;
|
break;
|
||||||
@@ -175,6 +196,7 @@ class Player {
|
|||||||
}
|
}
|
||||||
addAmmo(8);
|
addAmmo(8);
|
||||||
requestWeaponSwitch(WeaponType.machineGun);
|
requestWeaponSwitch(WeaponType.machineGun);
|
||||||
|
pickupSfxId = WolfSound.getMachineGun;
|
||||||
pickedUp = true;
|
pickedUp = true;
|
||||||
}
|
}
|
||||||
if (item.mapId == MapObject.chainGun) {
|
if (item.mapId == MapObject.chainGun) {
|
||||||
@@ -184,6 +206,7 @@ class Player {
|
|||||||
}
|
}
|
||||||
addAmmo(8);
|
addAmmo(8);
|
||||||
requestWeaponSwitch(WeaponType.chainGun);
|
requestWeaponSwitch(WeaponType.chainGun);
|
||||||
|
pickupSfxId = WolfSound.getGatling;
|
||||||
pickedUp = true;
|
pickedUp = true;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -191,10 +214,11 @@ class Player {
|
|||||||
case CollectibleType.key:
|
case CollectibleType.key:
|
||||||
if (item.mapId == MapObject.goldKey) hasGoldKey = true;
|
if (item.mapId == MapObject.goldKey) hasGoldKey = true;
|
||||||
if (item.mapId == MapObject.silverKey) hasSilverKey = true;
|
if (item.mapId == MapObject.silverKey) hasSilverKey = true;
|
||||||
|
pickupSfxId = WolfSound.getAmmo;
|
||||||
pickedUp = true;
|
pickedUp = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return pickedUp;
|
return pickedUp ? pickupSfxId : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool fire(int currentTime) {
|
bool fire(int currentTime) {
|
||||||
|
|||||||
@@ -22,6 +22,9 @@ class WolfEngine {
|
|||||||
}) : audio = audio ?? CliSilentAudio(),
|
}) : audio = audio ?? CliSilentAudio(),
|
||||||
doorManager = DoorManager(
|
doorManager = DoorManager(
|
||||||
onPlaySound: (sfxId) => audio?.playSoundEffect(sfxId),
|
onPlaySound: (sfxId) => audio?.playSoundEffect(sfxId),
|
||||||
|
),
|
||||||
|
pushwallManager = PushwallManager(
|
||||||
|
onPlaySound: (sfxId) => audio?.playSoundEffect(sfxId),
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Total milliseconds elapsed since the engine was initialized.
|
/// Total milliseconds elapsed since the engine was initialized.
|
||||||
@@ -54,7 +57,7 @@ class WolfEngine {
|
|||||||
FrameBuffer frameBuffer;
|
FrameBuffer frameBuffer;
|
||||||
|
|
||||||
/// Handles the detection and movement of secret "Pushwalls".
|
/// Handles the detection and movement of secret "Pushwalls".
|
||||||
final PushwallManager pushwallManager = PushwallManager();
|
final PushwallManager pushwallManager;
|
||||||
|
|
||||||
// --- World State ---
|
// --- World State ---
|
||||||
|
|
||||||
@@ -203,6 +206,7 @@ class WolfEngine {
|
|||||||
|
|
||||||
/// Handles floor transitions, including the "Level 10" secret floor logic.
|
/// Handles floor transitions, including the "Level 10" secret floor logic.
|
||||||
void _onLevelCompleted({bool isSecretExit = false}) {
|
void _onLevelCompleted({bool isSecretExit = false}) {
|
||||||
|
audio.playSoundEffect(WolfSound.levelDone);
|
||||||
audio.stopMusic();
|
audio.stopMusic();
|
||||||
final currentEpisode = data.episodes[_currentEpisodeIndex];
|
final currentEpisode = data.episodes[_currentEpisodeIndex];
|
||||||
|
|
||||||
@@ -335,6 +339,13 @@ class WolfEngine {
|
|||||||
|
|
||||||
for (Entity entity in entities) {
|
for (Entity entity in entities) {
|
||||||
if (entity is Enemy) {
|
if (entity is Enemy) {
|
||||||
|
if (entity.state == EntityState.dead &&
|
||||||
|
entity.isDying &&
|
||||||
|
!entity.hasPlayedDeathSound) {
|
||||||
|
audio.playSoundEffect(entity.deathSoundId);
|
||||||
|
entity.hasPlayedDeathSound = true;
|
||||||
|
}
|
||||||
|
|
||||||
// --- ANIMATION TRANSITION FIX (SAFE VERSION) ---
|
// --- ANIMATION TRANSITION FIX (SAFE VERSION) ---
|
||||||
// We check if the enemy is in the 'dead' state and currently 'isDying'.
|
// We check if the enemy is in the 'dead' state and currently 'isDying'.
|
||||||
// Inside WolfEngine._updateEntities
|
// Inside WolfEngine._updateEntities
|
||||||
@@ -397,7 +408,9 @@ class WolfEngine {
|
|||||||
}
|
}
|
||||||
} else if (entity is Collectible) {
|
} else if (entity is Collectible) {
|
||||||
if (player.position.distanceTo(entity.position) < 0.5) {
|
if (player.position.distanceTo(entity.position) < 0.5) {
|
||||||
if (player.tryPickup(entity)) {
|
final pickupSfxId = player.tryPickup(entity);
|
||||||
|
if (pickupSfxId != null) {
|
||||||
|
audio.playSoundEffect(pickupSfxId);
|
||||||
itemsToRemove.add(entity);
|
itemsToRemove.add(entity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,9 @@ class HansGrosse extends Enemy {
|
|||||||
@override
|
@override
|
||||||
int get attackSoundId => WolfSound.naziFire;
|
int get attackSoundId => WolfSound.naziFire;
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get deathSoundId => WolfSound.mutti;
|
||||||
|
|
||||||
HansGrosse({
|
HansGrosse({
|
||||||
required super.x,
|
required super.x,
|
||||||
required super.y,
|
required super.y,
|
||||||
|
|||||||
@@ -54,9 +54,15 @@ abstract class Enemy extends Entity {
|
|||||||
/// The sound played when this enemy performs its attack animation.
|
/// The sound played when this enemy performs its attack animation.
|
||||||
int get attackSoundId => type.attackSoundId;
|
int get attackSoundId => type.attackSoundId;
|
||||||
|
|
||||||
|
/// The sound played once when this enemy starts dying.
|
||||||
|
int get deathSoundId => type.deathSoundId;
|
||||||
|
|
||||||
/// Ensures enemies drop only one item (like ammo or a key) upon death.
|
/// Ensures enemies drop only one item (like ammo or a key) upon death.
|
||||||
bool hasDroppedItem = false;
|
bool hasDroppedItem = false;
|
||||||
|
|
||||||
|
/// Prevents duplicate death screams while death frames continue animating.
|
||||||
|
bool hasPlayedDeathSound = false;
|
||||||
|
|
||||||
/// When true, the enemy has spotted the player and is actively pursuing or attacking.
|
/// When true, the enemy has spotted the player and is actively pursuing or attacking.
|
||||||
bool isAlerted = false;
|
bool isAlerted = false;
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ enum EnemyType {
|
|||||||
mapData: EnemyMapData(MapObject.guardStart),
|
mapData: EnemyMapData(MapObject.guardStart),
|
||||||
alertSoundId: WolfSound.guardHalt,
|
alertSoundId: WolfSound.guardHalt,
|
||||||
attackSoundId: WolfSound.naziFire,
|
attackSoundId: WolfSound.naziFire,
|
||||||
|
deathSoundId: WolfSound.deathScream1,
|
||||||
animations: EnemyAnimationMap(
|
animations: EnemyAnimationMap(
|
||||||
idle: SpriteFrameRange(50, 57),
|
idle: SpriteFrameRange(50, 57),
|
||||||
walking: SpriteFrameRange(58, 89),
|
walking: SpriteFrameRange(58, 89),
|
||||||
@@ -28,6 +29,7 @@ enum EnemyType {
|
|||||||
mapData: EnemyMapData(MapObject.dogStart),
|
mapData: EnemyMapData(MapObject.dogStart),
|
||||||
alertSoundId: WolfSound.dogBark,
|
alertSoundId: WolfSound.dogBark,
|
||||||
attackSoundId: WolfSound.dogAttack,
|
attackSoundId: WolfSound.dogAttack,
|
||||||
|
deathSoundId: WolfSound.dogDeath,
|
||||||
animations: EnemyAnimationMap(
|
animations: EnemyAnimationMap(
|
||||||
// Dogs don't have true idle sprites, so map idle to the first walk frame safely
|
// Dogs don't have true idle sprites, so map idle to the first walk frame safely
|
||||||
idle: SpriteFrameRange(99, 106),
|
idle: SpriteFrameRange(99, 106),
|
||||||
@@ -49,6 +51,7 @@ enum EnemyType {
|
|||||||
mapData: EnemyMapData(MapObject.ssStart),
|
mapData: EnemyMapData(MapObject.ssStart),
|
||||||
alertSoundId: WolfSound.ssSchutzstaffel,
|
alertSoundId: WolfSound.ssSchutzstaffel,
|
||||||
attackSoundId: WolfSound.naziFire,
|
attackSoundId: WolfSound.naziFire,
|
||||||
|
deathSoundId: WolfSound.ssMeinGott,
|
||||||
animations: EnemyAnimationMap(
|
animations: EnemyAnimationMap(
|
||||||
idle: SpriteFrameRange(138, 145),
|
idle: SpriteFrameRange(138, 145),
|
||||||
walking: SpriteFrameRange(146, 177),
|
walking: SpriteFrameRange(146, 177),
|
||||||
@@ -64,6 +67,7 @@ enum EnemyType {
|
|||||||
mapData: EnemyMapData(MapObject.mutantStart, tierOffset: 18),
|
mapData: EnemyMapData(MapObject.mutantStart, tierOffset: 18),
|
||||||
alertSoundId: WolfSound.guardHalt,
|
alertSoundId: WolfSound.guardHalt,
|
||||||
attackSoundId: WolfSound.naziFire,
|
attackSoundId: WolfSound.naziFire,
|
||||||
|
deathSoundId: WolfSound.deathScream2,
|
||||||
animations: EnemyAnimationMap(
|
animations: EnemyAnimationMap(
|
||||||
idle: SpriteFrameRange(187, 194),
|
idle: SpriteFrameRange(187, 194),
|
||||||
walking: SpriteFrameRange(195, 226),
|
walking: SpriteFrameRange(195, 226),
|
||||||
@@ -80,6 +84,7 @@ enum EnemyType {
|
|||||||
mapData: EnemyMapData(MapObject.officerStart),
|
mapData: EnemyMapData(MapObject.officerStart),
|
||||||
alertSoundId: WolfSound.guardHalt,
|
alertSoundId: WolfSound.guardHalt,
|
||||||
attackSoundId: WolfSound.naziFire,
|
attackSoundId: WolfSound.naziFire,
|
||||||
|
deathSoundId: WolfSound.deathScream3,
|
||||||
animations: EnemyAnimationMap(
|
animations: EnemyAnimationMap(
|
||||||
idle: SpriteFrameRange(238, 245),
|
idle: SpriteFrameRange(238, 245),
|
||||||
walking: SpriteFrameRange(246, 277),
|
walking: SpriteFrameRange(246, 277),
|
||||||
@@ -104,6 +109,9 @@ enum EnemyType {
|
|||||||
/// The sound played when this enemy attacks.
|
/// The sound played when this enemy attacks.
|
||||||
final int attackSoundId;
|
final int attackSoundId;
|
||||||
|
|
||||||
|
/// The sound played when this enemy enters its death animation.
|
||||||
|
final int deathSoundId;
|
||||||
|
|
||||||
/// If false, this enemy type will be ignored when loading shareware data.
|
/// If false, this enemy type will be ignored when loading shareware data.
|
||||||
final bool existsInShareware;
|
final bool existsInShareware;
|
||||||
|
|
||||||
@@ -112,6 +120,7 @@ enum EnemyType {
|
|||||||
required this.animations,
|
required this.animations,
|
||||||
required this.alertSoundId,
|
required this.alertSoundId,
|
||||||
required this.attackSoundId,
|
required this.attackSoundId,
|
||||||
|
required this.deathSoundId,
|
||||||
this.existsInShareware = true,
|
this.existsInShareware = true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
206
packages/wolf_3d_dart/test/engine/audio_events_test.dart
Normal file
206
packages/wolf_3d_dart/test/engine/audio_events_test.dart
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
import 'package:wolf_3d_dart/wolf_3d_data_types.dart';
|
||||||
|
import 'package:wolf_3d_dart/wolf_3d_engine.dart';
|
||||||
|
import 'package:wolf_3d_dart/wolf_3d_entities.dart';
|
||||||
|
import 'package:wolf_3d_dart/wolf_3d_input.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('Engine audio events', () {
|
||||||
|
test('plays pickup sound effect when player collects ammo', () {
|
||||||
|
final input = _TestInput();
|
||||||
|
final audio = _CapturingAudio();
|
||||||
|
final engine = _buildEngine(
|
||||||
|
input: input,
|
||||||
|
audio: audio,
|
||||||
|
objectGridSetup: (grid) {
|
||||||
|
grid[2][2] = MapObject.playerEast;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
engine.init();
|
||||||
|
engine.entities.add(
|
||||||
|
Collectible(
|
||||||
|
x: engine.player.x,
|
||||||
|
y: engine.player.y,
|
||||||
|
spriteIndex: 0,
|
||||||
|
mapId: MapObject.ammoClip,
|
||||||
|
type: CollectibleType.ammo,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
engine.tick(const Duration(milliseconds: 16));
|
||||||
|
|
||||||
|
expect(audio.sfxIds, contains(WolfSound.getAmmo));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('plays guard alert when guard notices player', () {
|
||||||
|
final input = _TestInput();
|
||||||
|
final audio = _CapturingAudio();
|
||||||
|
final engine = _buildEngine(
|
||||||
|
input: input,
|
||||||
|
audio: audio,
|
||||||
|
objectGridSetup: (grid) {
|
||||||
|
grid[2][2] = MapObject.playerEast;
|
||||||
|
grid[2][5] = MapObject.guardStart + 2;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
engine.init();
|
||||||
|
|
||||||
|
// Wake-up delay is randomized, so tick long enough to always exceed it.
|
||||||
|
for (int i = 0; i < 80; i++) {
|
||||||
|
engine.tick(const Duration(milliseconds: 16));
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(audio.sfxIds, contains(WolfSound.guardHalt));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('plays dog death sound when dog dies', () {
|
||||||
|
final input = _TestInput();
|
||||||
|
final audio = _CapturingAudio();
|
||||||
|
final engine = _buildEngine(
|
||||||
|
input: input,
|
||||||
|
audio: audio,
|
||||||
|
objectGridSetup: (grid) {
|
||||||
|
grid[2][2] = MapObject.playerEast;
|
||||||
|
grid[2][4] = MapObject.dogStart;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
engine.init();
|
||||||
|
|
||||||
|
final dog = engine.entities.whereType<Dog>().single;
|
||||||
|
dog.takeDamage(999, 0);
|
||||||
|
|
||||||
|
engine.tick(const Duration(milliseconds: 16));
|
||||||
|
|
||||||
|
expect(audio.sfxIds, contains(WolfSound.dogDeath));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('plays pushwall sound when triggering a secret wall', () {
|
||||||
|
final input = _TestInput()..isInteracting = true;
|
||||||
|
final audio = _CapturingAudio();
|
||||||
|
final engine = _buildEngine(
|
||||||
|
input: input,
|
||||||
|
audio: audio,
|
||||||
|
wallGridSetup: (grid) {
|
||||||
|
grid[2][3] = 1;
|
||||||
|
},
|
||||||
|
objectGridSetup: (grid) {
|
||||||
|
grid[2][2] = MapObject.playerEast;
|
||||||
|
grid[2][3] = MapObject.pushwallTrigger;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
engine.init();
|
||||||
|
engine.tick(const Duration(milliseconds: 16));
|
||||||
|
|
||||||
|
expect(audio.sfxIds, contains(WolfSound.pushWall));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Builds a minimal deterministic engine world for audio event tests.
|
||||||
|
WolfEngine _buildEngine({
|
||||||
|
required _TestInput input,
|
||||||
|
required _CapturingAudio audio,
|
||||||
|
void Function(SpriteMap grid)? wallGridSetup,
|
||||||
|
void Function(SpriteMap grid)? objectGridSetup,
|
||||||
|
}) {
|
||||||
|
final wallGrid = _buildGrid();
|
||||||
|
final objectGrid = _buildGrid();
|
||||||
|
|
||||||
|
_fillBoundaries(wallGrid, 2);
|
||||||
|
wallGridSetup?.call(wallGrid);
|
||||||
|
objectGridSetup?.call(objectGrid);
|
||||||
|
|
||||||
|
return WolfEngine(
|
||||||
|
data: WolfensteinData(
|
||||||
|
version: GameVersion.retail,
|
||||||
|
walls: [
|
||||||
|
_solidSprite(1),
|
||||||
|
_solidSprite(1),
|
||||||
|
_solidSprite(2),
|
||||||
|
_solidSprite(2),
|
||||||
|
],
|
||||||
|
sprites: List.generate(436, (_) => _solidSprite(255)),
|
||||||
|
sounds: List.generate(200, (_) => PcmSound(Uint8List(1))),
|
||||||
|
adLibSounds: const [],
|
||||||
|
music: const [],
|
||||||
|
vgaImages: const [],
|
||||||
|
episodes: [
|
||||||
|
Episode(
|
||||||
|
name: 'Episode 1',
|
||||||
|
levels: [
|
||||||
|
WolfLevel(
|
||||||
|
name: 'Test Level',
|
||||||
|
wallGrid: wallGrid,
|
||||||
|
objectGrid: objectGrid,
|
||||||
|
musicIndex: 0,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
difficulty: Difficulty.hard,
|
||||||
|
startingEpisode: 0,
|
||||||
|
frameBuffer: FrameBuffer(64, 64),
|
||||||
|
input: input,
|
||||||
|
audio: audio,
|
||||||
|
onGameWon: () {},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class _TestInput extends Wolf3dInput {
|
||||||
|
@override
|
||||||
|
void update() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Captures engine SFX IDs so tests can assert dispatch behavior.
|
||||||
|
class _CapturingAudio implements EngineAudio {
|
||||||
|
@override
|
||||||
|
WolfensteinData? activeGame;
|
||||||
|
|
||||||
|
final List<int> sfxIds = [];
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> debugSoundTest() async {}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> init() async {}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void playLevelMusic(WolfLevel level) {}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void playMenuMusic() {}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void playSoundEffect(int sfxId) {
|
||||||
|
sfxIds.add(sfxId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void stopMusic() {}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
SpriteMap _buildGrid() => List.generate(64, (_) => List.filled(64, 0));
|
||||||
|
|
||||||
|
/// Fills the map borders so entities never walk off the test map.
|
||||||
|
void _fillBoundaries(SpriteMap grid, int wallId) {
|
||||||
|
for (int i = 0; i < 64; i++) {
|
||||||
|
grid[0][i] = wallId;
|
||||||
|
grid[63][i] = wallId;
|
||||||
|
grid[i][0] = wallId;
|
||||||
|
grid[i][63] = wallId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Produces a simple solid-color sprite frame for synthetic test assets.
|
||||||
|
Sprite _solidSprite(int colorIndex) {
|
||||||
|
return Sprite(Uint8List.fromList(List.filled(64 * 64, colorIndex)));
|
||||||
|
}
|
||||||
@@ -11,7 +11,10 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('uses the original mutant 18-ID difficulty offset', () {
|
test('uses the original mutant 18-ID difficulty offset', () {
|
||||||
expect(EnemyType.mutant.mapData.getNormalizedId(216, Difficulty.easy), 216);
|
expect(
|
||||||
|
EnemyType.mutant.mapData.getNormalizedId(216, Difficulty.easy),
|
||||||
|
216,
|
||||||
|
);
|
||||||
expect(
|
expect(
|
||||||
EnemyType.mutant.mapData.getNormalizedId(234, Difficulty.medium),
|
EnemyType.mutant.mapData.getNormalizedId(234, Difficulty.medium),
|
||||||
216,
|
216,
|
||||||
@@ -44,4 +47,4 @@ Enemy _spawnEnemy(int mapId) {
|
|||||||
final enemy = Enemy.spawn(mapId, 8.5, 8.5, Difficulty.hard);
|
final enemy = Enemy.spawn(mapId, 8.5, 8.5, Difficulty.hard);
|
||||||
expect(enemy, isNotNull);
|
expect(enemy, isNotNull);
|
||||||
return enemy!;
|
return enemy!;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user