Added some additional sound effects

Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
2026-03-18 02:40:53 +01:00
parent 806c9b6966
commit 7fe9a8bc40
8 changed files with 284 additions and 13 deletions

View File

@@ -19,6 +19,12 @@ class Pushwall {
///
/// Only one pushwall can be active (moving) at any given time.
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 = {};
Pushwall? activePushwall;
@@ -121,6 +127,7 @@ class PushwallManager {
int checkY = targetY + pw.dirY;
if (wallGrid[checkY][checkX] == 0) {
activePushwall = pw;
onPlaySound?.call(WolfSound.pushWall);
}
}
}

View File

@@ -135,34 +135,55 @@ class Player {
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;
int? pickupSfxId;
switch (item.type) {
case CollectibleType.health:
if (health >= 100) return false;
heal(item.mapId == MapObject.dogFoodDecoration ? 4 : 25);
if (health >= 100) return null;
heal(item.mapId == MapObject.food ? 4 : 25);
pickupSfxId = item.mapId == MapObject.food
? WolfSound.healthSmall
: WolfSound.healthLarge;
pickedUp = true;
break;
case CollectibleType.ammo:
if (ammo >= 99) return false;
if (ammo >= 99) return null;
int previousAmmo = ammo;
addAmmo(8);
if (currentWeapon is Knife && previousAmmo <= 0) {
requestWeaponSwitch(WeaponType.pistol);
}
pickupSfxId = WolfSound.getAmmo;
pickedUp = true;
break;
case CollectibleType.treasure:
if (item.mapId == MapObject.cross) score += 100;
if (item.mapId == MapObject.chalice) score += 500;
if (item.mapId == MapObject.chest) score += 1000;
if (item.mapId == MapObject.crown) score += 5000;
if (item.mapId == MapObject.cross) {
score += 100;
pickupSfxId = WolfSound.treasure1;
}
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) {
heal(100);
addAmmo(25);
pickupSfxId = WolfSound.extraLife;
}
pickedUp = true;
break;
@@ -175,6 +196,7 @@ class Player {
}
addAmmo(8);
requestWeaponSwitch(WeaponType.machineGun);
pickupSfxId = WolfSound.getMachineGun;
pickedUp = true;
}
if (item.mapId == MapObject.chainGun) {
@@ -184,6 +206,7 @@ class Player {
}
addAmmo(8);
requestWeaponSwitch(WeaponType.chainGun);
pickupSfxId = WolfSound.getGatling;
pickedUp = true;
}
break;
@@ -191,10 +214,11 @@ class Player {
case CollectibleType.key:
if (item.mapId == MapObject.goldKey) hasGoldKey = true;
if (item.mapId == MapObject.silverKey) hasSilverKey = true;
pickupSfxId = WolfSound.getAmmo;
pickedUp = true;
break;
}
return pickedUp;
return pickedUp ? pickupSfxId : null;
}
bool fire(int currentTime) {

View File

@@ -22,6 +22,9 @@ class WolfEngine {
}) : audio = audio ?? CliSilentAudio(),
doorManager = DoorManager(
onPlaySound: (sfxId) => audio?.playSoundEffect(sfxId),
),
pushwallManager = PushwallManager(
onPlaySound: (sfxId) => audio?.playSoundEffect(sfxId),
);
/// Total milliseconds elapsed since the engine was initialized.
@@ -54,7 +57,7 @@ class WolfEngine {
FrameBuffer frameBuffer;
/// Handles the detection and movement of secret "Pushwalls".
final PushwallManager pushwallManager = PushwallManager();
final PushwallManager pushwallManager;
// --- World State ---
@@ -203,6 +206,7 @@ class WolfEngine {
/// Handles floor transitions, including the "Level 10" secret floor logic.
void _onLevelCompleted({bool isSecretExit = false}) {
audio.playSoundEffect(WolfSound.levelDone);
audio.stopMusic();
final currentEpisode = data.episodes[_currentEpisodeIndex];
@@ -335,6 +339,13 @@ class WolfEngine {
for (Entity entity in entities) {
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) ---
// We check if the enemy is in the 'dead' state and currently 'isDying'.
// Inside WolfEngine._updateEntities
@@ -397,7 +408,9 @@ class WolfEngine {
}
} else if (entity is Collectible) {
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);
}
}

View File

@@ -17,6 +17,9 @@ class HansGrosse extends Enemy {
@override
int get attackSoundId => WolfSound.naziFire;
@override
int get deathSoundId => WolfSound.mutti;
HansGrosse({
required super.x,
required super.y,

View File

@@ -54,9 +54,15 @@ abstract class Enemy extends Entity {
/// The sound played when this enemy performs its attack animation.
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.
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.
bool isAlerted = false;

View File

@@ -13,6 +13,7 @@ enum EnemyType {
mapData: EnemyMapData(MapObject.guardStart),
alertSoundId: WolfSound.guardHalt,
attackSoundId: WolfSound.naziFire,
deathSoundId: WolfSound.deathScream1,
animations: EnemyAnimationMap(
idle: SpriteFrameRange(50, 57),
walking: SpriteFrameRange(58, 89),
@@ -28,6 +29,7 @@ enum EnemyType {
mapData: EnemyMapData(MapObject.dogStart),
alertSoundId: WolfSound.dogBark,
attackSoundId: WolfSound.dogAttack,
deathSoundId: WolfSound.dogDeath,
animations: EnemyAnimationMap(
// Dogs don't have true idle sprites, so map idle to the first walk frame safely
idle: SpriteFrameRange(99, 106),
@@ -49,6 +51,7 @@ enum EnemyType {
mapData: EnemyMapData(MapObject.ssStart),
alertSoundId: WolfSound.ssSchutzstaffel,
attackSoundId: WolfSound.naziFire,
deathSoundId: WolfSound.ssMeinGott,
animations: EnemyAnimationMap(
idle: SpriteFrameRange(138, 145),
walking: SpriteFrameRange(146, 177),
@@ -64,6 +67,7 @@ enum EnemyType {
mapData: EnemyMapData(MapObject.mutantStart, tierOffset: 18),
alertSoundId: WolfSound.guardHalt,
attackSoundId: WolfSound.naziFire,
deathSoundId: WolfSound.deathScream2,
animations: EnemyAnimationMap(
idle: SpriteFrameRange(187, 194),
walking: SpriteFrameRange(195, 226),
@@ -80,6 +84,7 @@ enum EnemyType {
mapData: EnemyMapData(MapObject.officerStart),
alertSoundId: WolfSound.guardHalt,
attackSoundId: WolfSound.naziFire,
deathSoundId: WolfSound.deathScream3,
animations: EnemyAnimationMap(
idle: SpriteFrameRange(238, 245),
walking: SpriteFrameRange(246, 277),
@@ -104,6 +109,9 @@ enum EnemyType {
/// The sound played when this enemy attacks.
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.
final bool existsInShareware;
@@ -112,6 +120,7 @@ enum EnemyType {
required this.animations,
required this.alertSoundId,
required this.attackSoundId,
required this.deathSoundId,
this.existsInShareware = true,
});