diff --git a/apps/wolf_3d_gui/lib/screens/audio_gallery.dart b/apps/wolf_3d_gui/lib/screens/audio_gallery.dart index 43ca7a6..a1925d9 100644 --- a/apps/wolf_3d_gui/lib/screens/audio_gallery.dart +++ b/apps/wolf_3d_gui/lib/screens/audio_gallery.dart @@ -46,74 +46,6 @@ class _AudioGalleryState extends State { return 2; } - static const List _knownSfxKeys = [ - SfxKey.openDoor, - SfxKey.closeDoor, - SfxKey.pushWall, - SfxKey.knifeAttack, - SfxKey.pistolFire, - SfxKey.machineGunFire, - SfxKey.chainGunFire, - SfxKey.enemyFire, - SfxKey.getMachineGun, - SfxKey.getAmmo, - SfxKey.getChainGun, - SfxKey.healthSmall, - SfxKey.healthLarge, - SfxKey.treasure1, - SfxKey.treasure2, - SfxKey.treasure3, - SfxKey.treasure4, - SfxKey.extraLife, - SfxKey.guardHalt, - SfxKey.dogBark, - SfxKey.dogDeath, - SfxKey.dogAttack, - SfxKey.deathScream1, - SfxKey.deathScream2, - SfxKey.deathScream3, - SfxKey.ssAlert, - SfxKey.ssDeath, - SfxKey.bossActive, - SfxKey.hansGrosseDeath, - SfxKey.schabbs, - SfxKey.schabbsDeath, - SfxKey.hitlerGreeting, - SfxKey.hitlerDeath, - SfxKey.mechaSteps, - SfxKey.ottoAlert, - SfxKey.gretelDeath, - SfxKey.levelComplete, - SfxKey.endBonus1, - SfxKey.endBonus2, - SfxKey.noBonus, - SfxKey.percent100, - ]; - - static const List _knownMusicKeys = [ - MusicKey.menuTheme, - MusicKey.level01, - MusicKey.level02, - MusicKey.level03, - MusicKey.level04, - MusicKey.level05, - MusicKey.level06, - MusicKey.level07, - MusicKey.level08, - MusicKey.level09, - MusicKey.level10, - MusicKey.level11, - MusicKey.level12, - MusicKey.level13, - MusicKey.level14, - MusicKey.level15, - MusicKey.level16, - MusicKey.level17, - MusicKey.level18, - MusicKey.level19, - MusicKey.level20, - ]; - @override void initState() { super.initState(); @@ -130,14 +62,14 @@ class _AudioGalleryState extends State { Map> _buildSfxAliases() { final Map> aliasesById = {}; - for (final key in _knownSfxKeys) { + for (final key in SoundEffect.values) { final ref = _selectedGame.registry.sfx.resolve(key); if (ref == null) { continue; } aliasesById .putIfAbsent(ref.slotIndex, () => {}) - .add(_readableKeyName(key.toString(), 'SfxKey(')); + .add(_readableKeyName(key.name)); } return aliasesById.map( @@ -147,14 +79,14 @@ class _AudioGalleryState extends State { Map> _buildMusicAliases() { final Map> aliasesById = {}; - for (final key in _knownMusicKeys) { + for (final key in Music.values) { final route = _selectedGame.registry.music.resolve(key); if (route == null) { continue; } aliasesById .putIfAbsent(route.trackIndex, () => {}) - .add(_readableKeyName(key.toString(), 'MusicKey(')); + .add(_readableKeyName(key.name)); } return aliasesById.map( @@ -162,15 +94,12 @@ class _AudioGalleryState extends State { ); } - String _readableKeyName(String raw, String prefix) { - final String trimmed = raw.startsWith(prefix) && raw.endsWith(')') - ? raw.substring(prefix.length, raw.length - 1) - : raw; - if (trimmed.isEmpty) { + String _readableKeyName(String raw) { + if (raw.isEmpty) { return raw; } - return trimmed.replaceAllMapped( + return raw.replaceAllMapped( RegExp(r'([a-z0-9])([A-Z])'), (match) => '${match.group(1)} ${match.group(2)}', ); @@ -203,7 +132,7 @@ class _AudioGalleryState extends State { } void _playSfx(int id) { - widget.wolf3d.audio.playSoundEffect(id); + widget.wolf3d.audio.playSoundEffectId(id); } Future _toggleMusic(int trackIndex) async { diff --git a/packages/wolf_3d_dart/lib/src/data/wl_parser.dart b/packages/wolf_3d_dart/lib/src/data/wl_parser.dart index 7bc11b4..52f1a19 100644 --- a/packages/wolf_3d_dart/lib/src/data/wl_parser.dart +++ b/packages/wolf_3d_dart/lib/src/data/wl_parser.dart @@ -13,20 +13,6 @@ import 'package:wolf_3d_dart/wolf_3d_data_types.dart'; abstract class WLParser { static const int _areaTileBase = 107; - // --- Original Song Lookup Tables --- - static const List _sharewareMusicMap = [ - 2, 3, 4, 5, 2, 3, 4, 5, 6, 7, // Episode 1 - ]; - - static const List _retailMusicMap = [ - 2, 3, 4, 5, 2, 3, 4, 5, 6, 7, // Ep 1 - 8, 9, 10, 11, 8, 9, 11, 10, 6, 12, // Ep 2 - 13, 14, 15, 16, 13, 14, 15, 16, 17, 18, // Ep 3 - 2, 3, 4, 5, 2, 3, 4, 5, 6, 7, // Ep 4 - 8, 9, 10, 11, 8, 9, 11, 10, 6, 12, // Ep 5 - 13, 14, 15, 16, 13, 14, 15, 16, 17, 19, // Ep 6 - ]; - /// Asynchronously discovers the game version and loads all necessary files. /// /// Provide a [fileFetcher] callback (e.g., Flutter's `rootBundle.load` or @@ -182,8 +168,6 @@ abstract class WLParser { required DataVersion dataIdentity, AssetRegistry? registryOverride, }) { - final isShareware = version == GameVersion.shareware; - final audio = parseAudio(audioHed, audioT, version); final vgaImages = parseVgaImages(vgaDict, vgaHead, vgaGraph); @@ -204,7 +188,7 @@ abstract class WLParser { walls: parseWalls(vswap), sprites: parseSprites(vswap), sounds: parseSounds(vswap).map((bytes) => PcmSound(bytes)).toList(), - episodes: parseEpisodes(mapHead, gameMaps, isShareware: isShareware), + episodes: parseEpisodes(mapHead, gameMaps, version: version), vgaImages: vgaImages, adLibSounds: audio.adLib, music: audio.music, @@ -438,13 +422,12 @@ abstract class WLParser { static List parseEpisodes( ByteData mapHead, ByteData gameMaps, { - bool isShareware = true, + required GameVersion version, }) { List allLevels = []; int rlewTag = mapHead.getUint16(0, Endian.little); - // Select the correct music map based on the version - final activeMusicMap = isShareware ? _sharewareMusicMap : _retailMusicMap; + final isShareware = version == GameVersion.shareware; final episodeNames = isShareware ? _sharewareEpisodeNames : _retailEpisodeNames; @@ -510,9 +493,9 @@ abstract class WLParser { } // --- ASSIGN MUSIC --- - int trackIndex = (i < activeMusicMap.length) - ? activeMusicMap[i] - : activeMusicMap[i % activeMusicMap.length]; + final episodeIndex = i ~/ 10; + final levelIndex = i % 10; + final levelMusic = Music.levelFor(version, episodeIndex, levelIndex); allLevels.add( WolfLevel( @@ -520,7 +503,7 @@ abstract class WLParser { wallGrid: wallGrid, objectGrid: objectGrid, areaGrid: areaGrid, - musicIndex: trackIndex, + music: levelMusic, ), ); } diff --git a/packages/wolf_3d_dart/lib/src/data_types/sound.dart b/packages/wolf_3d_dart/lib/src/data_types/sound.dart index fcdec1a..1bb3189 100644 --- a/packages/wolf_3d_dart/lib/src/data_types/sound.dart +++ b/packages/wolf_3d_dart/lib/src/data_types/sound.dart @@ -57,64 +57,3 @@ class ImfMusic { return ImfMusic(instructions); } } - -typedef WolfMusicMap = List; - -/// Map indices to original sound effects as defined in the Wolfenstein 3D source. -abstract class WolfSound { - // --- Doors & Environment --- - static const int openDoor = 8; - static const int closeDoor = 9; - static const int pushWall = 46; // Secret sliding walls - - // --- Weapons & Combat --- - static const int knifeAttack = 23; - static const int pistolFire = 24; - static const int machineGunFire = 26; - static const int gatlingFire = 32; // Historically SHOOTSND in the source - static const int naziFire = 58; // Enemy gunshots - - // --- Pickups & Items --- - static const int getMachineGun = 30; - static const int getAmmo = 31; - static const int getGatling = 38; - static const int healthSmall = 33; // Dog food / Meals - static const int healthLarge = 34; // First Aid - static const int treasure1 = 35; // Cross - static const int treasure2 = 36; // Chalice - static const int treasure3 = 37; // Chest - static const int treasure4 = 45; // Crown - static const int extraLife = 44; // 1-Up - - // --- Enemies: Standard --- - static const int guardHalt = 21; // "Halt!" - static const int dogBark = 41; - static const int dogDeath = 62; - static const int dogAttack = 68; - static const int deathScream1 = 29; - static const int deathScream2 = 22; - static const int deathScream3 = 25; - static const int ssSchutzstaffel = 51; // "Schutzstaffel!" - static const int ssMeinGott = 63; // SS Death - - // --- Enemies: Bosses (Retail Episodes 1-6) --- - static const int bossActive = 49; - static const int mutti = 50; // Hans Grosse Death - static const int ahhhg = 52; - static const int eva = 54; // Dr. Schabbs Death - static const int gutenTag = 55; // Hitler Greeting - static const int leben = 56; - static const int scheist = 57; // Hitler Death - static const int schabbsHas = 64; // Dr. Schabbs - static const int hitlerHas = 65; - static const int spion = 66; // Otto Giftmacher - static const int neinSoVass = 67; // Gretel Grosse Death - static const int mechSteps = 70; // Mecha-Hitler walking - - // --- UI & Progression --- - static const int levelDone = 40; - static const int endBonus1 = 42; - static const int endBonus2 = 43; - static const int noBonus = 47; - static const int percent100 = 48; -} diff --git a/packages/wolf_3d_dart/lib/src/data_types/wolf_level.dart b/packages/wolf_3d_dart/lib/src/data_types/wolf_level.dart index defa4ac..d95e917 100644 --- a/packages/wolf_3d_dart/lib/src/data_types/wolf_level.dart +++ b/packages/wolf_3d_dart/lib/src/data_types/wolf_level.dart @@ -22,14 +22,14 @@ class WolfLevel { /// zero-based and correspond to original AREATILE-derived sectors. final SpriteMap areaGrid; - /// The index of the [ImfMusic] track to play while this level is active. - final int musicIndex; + /// The [Music] track to play while this level is active. + final Music music; const WolfLevel({ required this.name, required this.wallGrid, required this.objectGrid, required this.areaGrid, - required this.musicIndex, + required this.music, }); } diff --git a/packages/wolf_3d_dart/lib/src/data_types/wolfenstein_data.dart b/packages/wolf_3d_dart/lib/src/data_types/wolfenstein_data.dart index 13b6f40..9af34d1 100644 --- a/packages/wolf_3d_dart/lib/src/data_types/wolfenstein_data.dart +++ b/packages/wolf_3d_dart/lib/src/data_types/wolfenstein_data.dart @@ -23,7 +23,7 @@ class WolfensteinData { /// /// Access the five sub-modules via: /// ```dart - /// data.registry.sfx.resolve(SfxKey.pistolFire) + /// data.registry.sfx.resolve(SoundEffect.pistolFire) /// data.registry.music.musicForLevel(episode, level) /// data.registry.entities.resolve(EntityKey.guard) /// data.registry.hud.faceForHealth(player.health) diff --git a/packages/wolf_3d_dart/lib/src/engine/audio/engine_audio.dart b/packages/wolf_3d_dart/lib/src/engine/audio/engine_audio.dart index ddd8cd8..de5b99e 100644 --- a/packages/wolf_3d_dart/lib/src/engine/audio/engine_audio.dart +++ b/packages/wolf_3d_dart/lib/src/engine/audio/engine_audio.dart @@ -4,10 +4,11 @@ abstract class EngineAudio { WolfensteinData? activeGame; Future debugSoundTest(); void playMenuMusic(); - void playLevelMusic(WolfLevel level); + void playLevelMusic(Music music); void stopMusic(); Future stopAllAudio(); - void playSoundEffect(int sfxId); + void playSoundEffect(SoundEffect effect); + void playSoundEffectId(int sfxId); Future init(); void dispose(); } diff --git a/packages/wolf_3d_dart/lib/src/engine/audio/silent_renderer.dart b/packages/wolf_3d_dart/lib/src/engine/audio/silent_renderer.dart index 0fa90d5..b48d6a5 100644 --- a/packages/wolf_3d_dart/lib/src/engine/audio/silent_renderer.dart +++ b/packages/wolf_3d_dart/lib/src/engine/audio/silent_renderer.dart @@ -14,10 +14,7 @@ class CliSilentAudio implements EngineAudio { void playMenuMusic() {} @override - void playLevelMusic(WolfLevel level) { - // Optional: Print a log so you know it's working! - // debugPrint("🎵 Playing music for: ${level.name} 🎵"); - } + void playLevelMusic(Music music) {} @override void stopMusic() {} @@ -26,7 +23,14 @@ class CliSilentAudio implements EngineAudio { Future stopAllAudio() async {} @override - void playSoundEffect(int sfxId) { + void playSoundEffect(SoundEffect effect) { + // Optional: You could use the terminal 'bell' character here + // to actually make a system beep when a sound plays! + // stdout.write('\x07'); + } + + @override + void playSoundEffectId(int sfxId) { // Optional: You could use the terminal 'bell' character here // to actually make a system beep when a sound plays! // stdout.write('\x07'); diff --git a/packages/wolf_3d_dart/lib/src/engine/managers/door_manager.dart b/packages/wolf_3d_dart/lib/src/engine/managers/door_manager.dart index ab6fd6d..68f3206 100644 --- a/packages/wolf_3d_dart/lib/src/engine/managers/door_manager.dart +++ b/packages/wolf_3d_dart/lib/src/engine/managers/door_manager.dart @@ -15,7 +15,7 @@ class DoorManager { /// Callback used to trigger sound effects without tight coupling /// to a specific audio engine implementation. - final void Function(int sfxId) onPlaySound; + final void Function(SoundEffect effect) onPlaySound; static int _key(int x, int y) => ((y & 0xFFFF) << 16) | (x & 0xFFFF); @@ -41,7 +41,7 @@ class DoorManager { final newState = door.update(elapsed.inMilliseconds); if (newState == DoorState.closing) { - onPlaySound(WolfSound.closeDoor); + onPlaySound(SoundEffect.closeDoor); } } } @@ -60,7 +60,7 @@ class DoorManager { final int key = _key(targetX, targetY); if (doors.containsKey(key) && doors[key]!.interact()) { - onPlaySound(WolfSound.openDoor); + onPlaySound(SoundEffect.openDoor); log('[DEBUG] Player opened door at ($targetX, $targetY)'); return (x: targetX, y: targetY); } @@ -74,7 +74,7 @@ class DoorManager { // AI only interacts if the door is currently fully closed (offset == 0). if (doors.containsKey(key) && doors[key]!.offset == 0.0) { if (doors[key]!.interact()) { - onPlaySound(WolfSound.openDoor); + onPlaySound(SoundEffect.openDoor); } } } diff --git a/packages/wolf_3d_dart/lib/src/engine/managers/pushwall_manager.dart b/packages/wolf_3d_dart/lib/src/engine/managers/pushwall_manager.dart index 591cd70..49c11fd 100644 --- a/packages/wolf_3d_dart/lib/src/engine/managers/pushwall_manager.dart +++ b/packages/wolf_3d_dart/lib/src/engine/managers/pushwall_manager.dart @@ -23,7 +23,7 @@ class PushwallManager { PushwallManager({this.onPlaySound}); /// Optional callback used to emit audio cues when pushwalls activate. - final void Function(int sfxId)? onPlaySound; + final void Function(SoundEffect effect)? onPlaySound; final Map pushwalls = {}; Pushwall? activePushwall; @@ -127,7 +127,7 @@ class PushwallManager { int checkY = targetY + pw.dirY; if (wallGrid[checkY][checkX] == 0) { activePushwall = pw; - onPlaySound?.call(WolfSound.pushWall); + onPlaySound?.call(SoundEffect.pushWall); } } } diff --git a/packages/wolf_3d_dart/lib/src/engine/player/player.dart b/packages/wolf_3d_dart/lib/src/engine/player/player.dart index af0be1c..43a2570 100644 --- a/packages/wolf_3d_dart/lib/src/engine/player/player.dart +++ b/packages/wolf_3d_dart/lib/src/engine/player/player.dart @@ -147,7 +147,7 @@ class Player { /// 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) { + SoundEffect? tryPickup(Collectible item) { final effect = item.tryCollect( CollectiblePickupContext( health: health, @@ -211,7 +211,7 @@ class Player { requestWeaponSwitch(weaponType); } - return effect.pickupSfxId; + return effect.pickupSoundEffect; } bool fire(int currentTime) { diff --git a/packages/wolf_3d_dart/lib/src/engine/wolf_3d_engine_base.dart b/packages/wolf_3d_dart/lib/src/engine/wolf_3d_engine_base.dart index fdf4c0b..63edd0b 100644 --- a/packages/wolf_3d_dart/lib/src/engine/wolf_3d_engine_base.dart +++ b/packages/wolf_3d_dart/lib/src/engine/wolf_3d_engine_base.dart @@ -35,10 +35,10 @@ class WolfEngine { _availableGames = availableGames ?? [data!], audio = engineAudio ?? CliSilentAudio(), doorManager = DoorManager( - onPlaySound: (sfxId) => engineAudio?.playSoundEffect(sfxId), + onPlaySound: (effect) => engineAudio?.playSoundEffect(effect), ), pushwallManager = PushwallManager( - onPlaySound: (sfxId) => engineAudio?.playSoundEffect(sfxId), + onPlaySound: (effect) => engineAudio?.playSoundEffect(effect), ) { if (_availableGames.isEmpty) { throw StateError('WolfEngine requires at least one game data set.'); @@ -481,7 +481,7 @@ class WolfEngine { doorManager.initDoors(currentLevel); pushwallManager.initPushwalls(currentLevel, _objectLevel); - audio.playLevelMusic(activeLevel); + audio.playLevelMusic(activeLevel.music); // Spawn Player and Entities from the Object Grid bool playerSpawned = false; @@ -540,7 +540,7 @@ class WolfEngine { /// Handles floor transitions, including the "Level 10" secret floor logic. void _onLevelCompleted({bool isSecretExit = false}) { - audio.playSoundEffect(WolfSound.levelDone); + audio.playSoundEffect(SoundEffect.levelComplete); audio.stopMusic(); player ..hasGoldKey = false @@ -679,7 +679,7 @@ class WolfEngine { if (entity.state == EntityState.dead && entity.isDying && !entity.hasPlayedDeathSound) { - audio.playSoundEffect(entity.deathSoundId); + audio.playSoundEffect(entity.deathSound); entity.hasPlayedDeathSound = true; } @@ -787,9 +787,9 @@ class WolfEngine { } } else if (entity is Collectible) { if (player.position.distanceTo(entity.position) < 0.5) { - final pickupSfxId = player.tryPickup(entity); - if (pickupSfxId != null) { - audio.playSoundEffect(pickupSfxId); + final pickupSoundEffect = player.tryPickup(entity); + if (pickupSoundEffect != null) { + audio.playSoundEffect(pickupSoundEffect); itemsToRemove.add(entity); } } @@ -826,7 +826,7 @@ class WolfEngine { } entity.isAlerted = true; - audio.playSoundEffect(entity.alertSoundId); + audio.playSoundEffect(entity.alertSound); log( '[DEBUG] Enemy #${entity.debugId} (${entity.type.name}) ' 'alerted by gunfire in area $area', @@ -1007,10 +1007,10 @@ class WolfEngine { void _playPlayerWeaponSound() { final sfxId = switch (player.currentWeapon.type) { - WeaponType.knife => WolfSound.knifeAttack, - WeaponType.pistol => WolfSound.pistolFire, - WeaponType.machineGun => WolfSound.machineGunFire, - WeaponType.chainGun => WolfSound.gatlingFire, + WeaponType.knife => SoundEffect.knifeAttack, + WeaponType.pistol => SoundEffect.pistolFire, + WeaponType.machineGun => SoundEffect.machineGunFire, + WeaponType.chainGun => SoundEffect.chainGunFire, }; audio.playSoundEffect(sfxId); diff --git a/packages/wolf_3d_dart/lib/src/entities/entities/collectible.dart b/packages/wolf_3d_dart/lib/src/entities/entities/collectible.dart index 13af19c..d04af03 100644 --- a/packages/wolf_3d_dart/lib/src/entities/entities/collectible.dart +++ b/packages/wolf_3d_dart/lib/src/entities/entities/collectible.dart @@ -29,7 +29,7 @@ class CollectiblePickupEffect { final int ammoToAdd; final int scoreToAdd; final int extraLivesToAdd; - final int pickupSfxId; + final SoundEffect pickupSoundEffect; final bool grantGoldKey; final bool grantSilverKey; final WeaponType? grantWeapon; @@ -40,7 +40,7 @@ class CollectiblePickupEffect { this.ammoToAdd = 0, this.scoreToAdd = 0, this.extraLivesToAdd = 0, - required this.pickupSfxId, + required this.pickupSoundEffect, this.grantGoldKey = false, this.grantSilverKey = false, this.grantWeapon, @@ -49,7 +49,7 @@ class CollectiblePickupEffect { @override String toString() { - return 'CollectiblePickupEffect(healthToRestore: $healthToRestore, ammoToAdd: $ammoToAdd, scoreToAdd: $scoreToAdd, extraLivesToAdd: $extraLivesToAdd, pickupSfxId: $pickupSfxId, grantGoldKey: $grantGoldKey, grantSilverKey: $grantSilverKey, grantWeapon: $grantWeapon, requestWeaponSwitch: $requestWeaponSwitch)'; + return 'CollectiblePickupEffect(healthToRestore: $healthToRestore, ammoToAdd: $ammoToAdd, scoreToAdd: $scoreToAdd, extraLivesToAdd: $extraLivesToAdd, pickupSoundEffect: $pickupSoundEffect, grantGoldKey: $grantGoldKey, grantSilverKey: $grantSilverKey, grantWeapon: $grantWeapon, requestWeaponSwitch: $requestWeaponSwitch)'; } } @@ -123,7 +123,9 @@ class HealthCollectible extends Collectible { final bool isFood = mapId == MapObject.food; return CollectiblePickupEffect( healthToRestore: isFood ? 10 : 25, - pickupSfxId: isFood ? WolfSound.healthSmall : WolfSound.healthLarge, + pickupSoundEffect: isFood + ? SoundEffect.healthSmall + : SoundEffect.healthLarge, ); } } @@ -149,7 +151,7 @@ class AmmoCollectible extends Collectible { return CollectiblePickupEffect( ammoToAdd: ammoAmount, - pickupSfxId: WolfSound.getAmmo, + pickupSoundEffect: SoundEffect.getAmmo, requestWeaponSwitch: shouldAutoswitchToPistol ? WeaponType.pistol : null, ); } @@ -172,7 +174,7 @@ class WeaponCollectible extends Collectible { if (mapId == MapObject.machineGun) { return const CollectiblePickupEffect( ammoToAdd: 6, - pickupSfxId: WolfSound.getMachineGun, + pickupSoundEffect: SoundEffect.getMachineGun, grantWeapon: WeaponType.machineGun, requestWeaponSwitch: WeaponType.machineGun, ); @@ -181,7 +183,7 @@ class WeaponCollectible extends Collectible { if (mapId == MapObject.chainGun) { return const CollectiblePickupEffect( ammoToAdd: 6, - pickupSfxId: WolfSound.getGatling, + pickupSoundEffect: SoundEffect.getChainGun, grantWeapon: WeaponType.chainGun, requestWeaponSwitch: WeaponType.chainGun, ); @@ -203,7 +205,7 @@ class KeyCollectible extends Collectible { if (mapId == MapObject.goldKey) { if (context.hasGoldKey) return null; return const CollectiblePickupEffect( - pickupSfxId: WolfSound.getAmmo, + pickupSoundEffect: SoundEffect.getAmmo, grantGoldKey: true, ); } @@ -211,7 +213,7 @@ class KeyCollectible extends Collectible { if (mapId == MapObject.silverKey) { if (context.hasSilverKey) return null; return const CollectiblePickupEffect( - pickupSfxId: WolfSound.getAmmo, + pickupSoundEffect: SoundEffect.getAmmo, grantSilverKey: true, ); } @@ -243,18 +245,18 @@ class TreasureCollectible extends Collectible { healthToRestore: 99, ammoToAdd: 25, extraLivesToAdd: 1, - pickupSfxId: WolfSound.extraLife, + pickupSoundEffect: SoundEffect.extraLife, ); } return CollectiblePickupEffect( scoreToAdd: scoreValue, - pickupSfxId: switch (mapId) { - MapObject.cross => WolfSound.treasure1, - MapObject.chalice => WolfSound.treasure2, - MapObject.chest => WolfSound.treasure3, - MapObject.crown => WolfSound.treasure4, - _ => WolfSound.getAmmo, + pickupSoundEffect: switch (mapId) { + MapObject.cross => SoundEffect.treasure1, + MapObject.chalice => SoundEffect.treasure2, + MapObject.chest => SoundEffect.treasure3, + MapObject.crown => SoundEffect.treasure4, + _ => SoundEffect.getAmmo, }, ); } diff --git a/packages/wolf_3d_dart/lib/src/entities/entities/enemies/bosses/hans_grosse.dart b/packages/wolf_3d_dart/lib/src/entities/entities/enemies/bosses/hans_grosse.dart index 7d0f21c..d18873b 100644 --- a/packages/wolf_3d_dart/lib/src/entities/entities/enemies/bosses/hans_grosse.dart +++ b/packages/wolf_3d_dart/lib/src/entities/entities/enemies/bosses/hans_grosse.dart @@ -12,13 +12,13 @@ class HansGrosse extends Enemy { throw UnimplementedError("Hans Grosse uses manual animation logic."); @override - int get alertSoundId => WolfSound.bossActive; + SoundEffect get alertSound => SoundEffect.bossActive; @override - int get attackSoundId => WolfSound.naziFire; + SoundEffect get attackSound => SoundEffect.enemyFire; @override - int get deathSoundId => WolfSound.mutti; + SoundEffect get deathSound => SoundEffect.hansGrosseDeath; @override int get scoreValue => 5000; @@ -85,7 +85,7 @@ class HansGrosse extends Enemy { required bool Function(int area) isAreaConnectedToPlayer, required void Function(int damage) onDamagePlayer, required void Function(int x, int y) tryOpenDoor, - required void Function(int sfxId) onPlaySound, + required void Function(SoundEffect effect) onPlaySound, }) { Coordinate2D movement = const Coordinate2D(0, 0); double newAngle = angle; @@ -102,7 +102,7 @@ class HansGrosse extends Enemy { isAreaConnectedToPlayer: isAreaConnectedToPlayer, baseReactionMs: 50, )) { - onPlaySound(alertSoundId); + onPlaySound(alertSound); } double distance = position.distanceTo(playerPosition); @@ -151,7 +151,7 @@ class HansGrosse extends Enemy { setTics(10); } else if (currentFrame == 2) { spriteIndex = _baseSprite + 6; // Fire - onPlaySound(attackSoundId); + onPlaySound(attackSound); onDamagePlayer(damage); setTics(4); } else if (currentFrame == 3) { diff --git a/packages/wolf_3d_dart/lib/src/entities/entities/enemies/dog.dart b/packages/wolf_3d_dart/lib/src/entities/entities/enemies/dog.dart index e48bd61..4e3a1a4 100644 --- a/packages/wolf_3d_dart/lib/src/entities/entities/enemies/dog.dart +++ b/packages/wolf_3d_dart/lib/src/entities/entities/enemies/dog.dart @@ -45,7 +45,7 @@ class Dog extends Enemy { required bool Function(int area) isAreaConnectedToPlayer, required void Function(int damage) onDamagePlayer, required void Function(int x, int y) tryOpenDoor, - required void Function(int sfxId) onPlaySound, + required void Function(SoundEffect effect) onPlaySound, }) { final previousState = state; Coordinate2D movement = const Coordinate2D(0, 0); @@ -60,7 +60,7 @@ class Dog extends Enemy { areaAt: areaAt, isAreaConnectedToPlayer: isAreaConnectedToPlayer, )) { - onPlaySound(alertSoundId); + onPlaySound(alertSound); } } @@ -72,7 +72,7 @@ class Dog extends Enemy { currentFrame++; // Phase 2: The actual bite if (currentFrame == 1) { - onPlaySound(attackSoundId); + onPlaySound(attackSound); final bool attackSuccessful = math.Random().nextDouble() < (180 / 256); @@ -176,7 +176,9 @@ class Dog extends Enemy { // A movement magnitude threshold prevents accepting near-zero floating- // point residuals (e.g. cos(π/2) ≈ 6e-17) as valid movement. final double minEffective = currentMoveSpeed * 0.5; - final double currentDistanceToPlayer = position.distanceTo(playerPosition); + final double currentDistanceToPlayer = position.distanceTo( + playerPosition, + ); int selectedCandidateIndex = -1; for (int i = 0; i < candidateAngles.length; i++) { @@ -192,7 +194,8 @@ class Dog extends Enemy { tryOpenDoor: (x, y) {}, ); - if (candidateMovement.x.abs() + candidateMovement.y.abs() < minEffective) { + if (candidateMovement.x.abs() + candidateMovement.y.abs() < + minEffective) { continue; } diff --git a/packages/wolf_3d_dart/lib/src/entities/entities/enemies/enemy.dart b/packages/wolf_3d_dart/lib/src/entities/entities/enemies/enemy.dart index 28eeeda..aa4ca14 100644 --- a/packages/wolf_3d_dart/lib/src/entities/entities/enemies/enemy.dart +++ b/packages/wolf_3d_dart/lib/src/entities/entities/enemies/enemy.dart @@ -55,16 +55,16 @@ abstract class Enemy extends Entity { EnemyType get type; /// The sound played when this enemy notices the player or hears combat. - int get alertSoundId => type.alertSoundId; + SoundEffect get alertSound => type.alertSound; /// The sound played when this enemy performs its attack animation. - int get attackSoundId => type.attackSoundId; + SoundEffect get attackSound => type.attackSound; /// The score awarded when this enemy is killed. int get scoreValue => type.scoreValue; /// The sound played once when this enemy starts dying. - int get deathSoundId => type.deathSoundId; + SoundEffect get deathSound => type.deathSound; /// Ensures enemies drop only one item (like ammo or a key) upon death. bool hasDroppedItem = false; @@ -412,11 +412,19 @@ abstract class Enemy extends Entity { // is moving mostly north and clips a wall tile beside an open door, the // Y (northward) slide is preferred over the X (sideways) slide. if (intendedMovement.y.abs() >= intendedMovement.x.abs()) { - if (canMoveY) return normalizeTiny(Coordinate2D(0, intendedMovement.y)); - if (canMoveX) return normalizeTiny(Coordinate2D(intendedMovement.x, 0)); + if (canMoveY) { + return normalizeTiny(Coordinate2D(0, intendedMovement.y)); + } + if (canMoveX) { + return normalizeTiny(Coordinate2D(intendedMovement.x, 0)); + } } else { - if (canMoveX) return normalizeTiny(Coordinate2D(intendedMovement.x, 0)); - if (canMoveY) return normalizeTiny(Coordinate2D(0, intendedMovement.y)); + if (canMoveX) { + return normalizeTiny(Coordinate2D(intendedMovement.x, 0)); + } + if (canMoveY) { + return normalizeTiny(Coordinate2D(0, intendedMovement.y)); + } } return const Coordinate2D(0, 0); } @@ -534,7 +542,7 @@ abstract class Enemy extends Entity { required bool Function(int area) isAreaConnectedToPlayer, required void Function(int x, int y) tryOpenDoor, required void Function(int damage) onDamagePlayer, - required void Function(int sfxId) onPlaySound, + required void Function(SoundEffect effect) onPlaySound, }); /// Factory method to spawn the correct [Enemy] subclass based on a Map ID. diff --git a/packages/wolf_3d_dart/lib/src/entities/entities/enemies/enemy_type.dart b/packages/wolf_3d_dart/lib/src/entities/entities/enemies/enemy_type.dart index d37d694..a7a8dc9 100644 --- a/packages/wolf_3d_dart/lib/src/entities/entities/enemies/enemy_type.dart +++ b/packages/wolf_3d_dart/lib/src/entities/entities/enemies/enemy_type.dart @@ -11,9 +11,9 @@ enum EnemyType { guard( mapData: EnemyMapData(MapObject.guardStart), scoreValue: 100, - alertSoundId: WolfSound.guardHalt, - attackSoundId: WolfSound.naziFire, - deathSoundId: WolfSound.deathScream1, + alertSound: SoundEffect.guardHalt, + attackSound: SoundEffect.enemyFire, + deathSound: SoundEffect.deathScream1, animations: EnemyAnimationMap( idle: SpriteFrameRange(50, 57), walking: SpriteFrameRange(58, 89), @@ -28,9 +28,9 @@ enum EnemyType { dog( mapData: EnemyMapData(MapObject.dogStart), scoreValue: 200, - alertSoundId: WolfSound.dogBark, - attackSoundId: WolfSound.dogAttack, - deathSoundId: WolfSound.dogDeath, + alertSound: SoundEffect.dogBark, + attackSound: SoundEffect.dogAttack, + deathSound: SoundEffect.dogDeath, animations: EnemyAnimationMap( // Dogs don't have true idle sprites, so map idle to the first walk frame safely idle: SpriteFrameRange(99, 106), @@ -51,9 +51,9 @@ enum EnemyType { ss( mapData: EnemyMapData(MapObject.ssStart), scoreValue: 500, - alertSoundId: WolfSound.ssSchutzstaffel, - attackSoundId: WolfSound.naziFire, - deathSoundId: WolfSound.ssMeinGott, + alertSound: SoundEffect.ssAlert, + attackSound: SoundEffect.enemyFire, + deathSound: SoundEffect.ssDeath, animations: EnemyAnimationMap( idle: SpriteFrameRange(138, 145), walking: SpriteFrameRange(146, 177), @@ -68,9 +68,9 @@ enum EnemyType { mutant( mapData: EnemyMapData(MapObject.mutantStart, tierOffset: 18), scoreValue: 700, - alertSoundId: WolfSound.guardHalt, - attackSoundId: WolfSound.naziFire, - deathSoundId: WolfSound.deathScream2, + alertSound: SoundEffect.guardHalt, + attackSound: SoundEffect.enemyFire, + deathSound: SoundEffect.deathScream2, animations: EnemyAnimationMap( idle: SpriteFrameRange(187, 194), walking: SpriteFrameRange(195, 226), @@ -86,9 +86,9 @@ enum EnemyType { officer( mapData: EnemyMapData(MapObject.officerStart), scoreValue: 400, - alertSoundId: WolfSound.guardHalt, - attackSoundId: WolfSound.naziFire, - deathSoundId: WolfSound.deathScream3, + alertSound: SoundEffect.guardHalt, + attackSound: SoundEffect.enemyFire, + deathSound: SoundEffect.deathScream3, animations: EnemyAnimationMap( idle: SpriteFrameRange(238, 245), walking: SpriteFrameRange(246, 277), @@ -111,13 +111,13 @@ enum EnemyType { final int scoreValue; /// The sound played when this enemy first becomes alerted. - final int alertSoundId; + final SoundEffect alertSound; /// The sound played when this enemy attacks. - final int attackSoundId; + final SoundEffect attackSound; /// The sound played when this enemy enters its death animation. - final int deathSoundId; + final SoundEffect deathSound; /// If false, this enemy type will be ignored when loading shareware data. final bool existsInShareware; @@ -126,9 +126,9 @@ enum EnemyType { required this.mapData, required this.animations, required this.scoreValue, - required this.alertSoundId, - required this.attackSoundId, - required this.deathSoundId, + required this.alertSound, + required this.attackSound, + required this.deathSound, this.existsInShareware = true, }); diff --git a/packages/wolf_3d_dart/lib/src/entities/entities/enemies/guard.dart b/packages/wolf_3d_dart/lib/src/entities/entities/enemies/guard.dart index a80db1f..74b8da3 100644 --- a/packages/wolf_3d_dart/lib/src/entities/entities/enemies/guard.dart +++ b/packages/wolf_3d_dart/lib/src/entities/entities/enemies/guard.dart @@ -38,7 +38,7 @@ class Guard extends Enemy { required bool Function(int area) isAreaConnectedToPlayer, required void Function(int damage) onDamagePlayer, required void Function(int x, int y) tryOpenDoor, - required void Function(int sfxId) onPlaySound, + required void Function(SoundEffect effect) onPlaySound, }) { Coordinate2D movement = const Coordinate2D(0, 0); double newAngle = angle; @@ -52,7 +52,7 @@ class Guard extends Enemy { areaAt: areaAt, isAreaConnectedToPlayer: isAreaConnectedToPlayer, )) { - onPlaySound(alertSoundId); + onPlaySound(alertSound); } // 2. Discrete AI Logic (Decisions happen every 10 tics) @@ -62,7 +62,7 @@ class Guard extends Enemy { if (processTics(elapsedDeltaMs, moveSpeed: 0)) { currentFrame++; if (currentFrame == 1) { - onPlaySound(attackSoundId); + onPlaySound(attackSound); if (tryRollRangedHit( distance: distance, playerPosition: playerPosition, diff --git a/packages/wolf_3d_dart/lib/src/entities/entities/enemies/mutant.dart b/packages/wolf_3d_dart/lib/src/entities/entities/enemies/mutant.dart index 42d0893..88197e6 100644 --- a/packages/wolf_3d_dart/lib/src/entities/entities/enemies/mutant.dart +++ b/packages/wolf_3d_dart/lib/src/entities/entities/enemies/mutant.dart @@ -36,7 +36,7 @@ class Mutant extends Enemy { required bool Function(int area) isAreaConnectedToPlayer, required void Function(int damage) onDamagePlayer, required void Function(int x, int y) tryOpenDoor, - required void Function(int sfxId) onPlaySound, + required void Function(SoundEffect effect) onPlaySound, }) { Coordinate2D movement = const Coordinate2D(0, 0); double newAngle = angle; @@ -48,7 +48,7 @@ class Mutant extends Enemy { areaAt: areaAt, isAreaConnectedToPlayer: isAreaConnectedToPlayer, )) { - onPlaySound(alertSoundId); + onPlaySound(alertSound); } double distance = position.distanceTo(playerPosition); @@ -122,7 +122,7 @@ class Mutant extends Enemy { if (processTics(elapsedDeltaMs, moveSpeed: 0)) { currentFrame++; if (currentFrame == 1) { - onPlaySound(attackSoundId); + onPlaySound(attackSound); if (tryRollRangedHit( distance: distance, playerPosition: playerPosition, diff --git a/packages/wolf_3d_dart/lib/src/entities/entities/enemies/officer.dart b/packages/wolf_3d_dart/lib/src/entities/entities/enemies/officer.dart index e18c463..8663d42 100644 --- a/packages/wolf_3d_dart/lib/src/entities/entities/enemies/officer.dart +++ b/packages/wolf_3d_dart/lib/src/entities/entities/enemies/officer.dart @@ -36,7 +36,7 @@ class Officer extends Enemy { required bool Function(int area) isAreaConnectedToPlayer, required void Function(int damage) onDamagePlayer, required void Function(int x, int y) tryOpenDoor, - required void Function(int sfxId) onPlaySound, + required void Function(SoundEffect effect) onPlaySound, }) { Coordinate2D movement = const Coordinate2D(0, 0); double newAngle = angle; @@ -48,7 +48,7 @@ class Officer extends Enemy { areaAt: areaAt, isAreaConnectedToPlayer: isAreaConnectedToPlayer, )) { - onPlaySound(alertSoundId); + onPlaySound(alertSound); } double distance = position.distanceTo(playerPosition); @@ -122,7 +122,7 @@ class Officer extends Enemy { if (processTics(elapsedDeltaMs, moveSpeed: 0)) { currentFrame++; if (currentFrame == 1) { - onPlaySound(attackSoundId); + onPlaySound(attackSound); if (tryRollRangedHit( distance: distance, playerPosition: playerPosition, diff --git a/packages/wolf_3d_dart/lib/src/entities/entities/enemies/ss.dart b/packages/wolf_3d_dart/lib/src/entities/entities/enemies/ss.dart index ef1485f..c498248 100644 --- a/packages/wolf_3d_dart/lib/src/entities/entities/enemies/ss.dart +++ b/packages/wolf_3d_dart/lib/src/entities/entities/enemies/ss.dart @@ -35,7 +35,7 @@ class SS extends Enemy { required bool Function(int area) isAreaConnectedToPlayer, required void Function(int damage) onDamagePlayer, required void Function(int x, int y) tryOpenDoor, - required void Function(int sfxId) onPlaySound, + required void Function(SoundEffect effect) onPlaySound, }) { Coordinate2D movement = const Coordinate2D(0, 0); double newAngle = angle; @@ -47,7 +47,7 @@ class SS extends Enemy { areaAt: areaAt, isAreaConnectedToPlayer: isAreaConnectedToPlayer, )) { - onPlaySound(alertSoundId); + onPlaySound(alertSound); } double distance = position.distanceTo(playerPosition); @@ -121,7 +121,7 @@ class SS extends Enemy { if (processTics(elapsedDeltaMs, moveSpeed: 0)) { currentFrame++; if (currentFrame == 1) { - onPlaySound(attackSoundId); + onPlaySound(attackSound); if (tryRollRangedHit( distance: distance, playerPosition: playerPosition, @@ -140,7 +140,7 @@ class SS extends Enemy { if (math.Random().nextDouble() > 0.5) { // 50% chance to burst currentFrame = 1; - onPlaySound(attackSoundId); + onPlaySound(attackSound); if (tryRollRangedHit( distance: distance, playerPosition: playerPosition, diff --git a/packages/wolf_3d_dart/lib/src/registry/asset_registry.dart b/packages/wolf_3d_dart/lib/src/registry/asset_registry.dart index f1bd5bd..3c8fd01 100644 --- a/packages/wolf_3d_dart/lib/src/registry/asset_registry.dart +++ b/packages/wolf_3d_dart/lib/src/registry/asset_registry.dart @@ -9,7 +9,7 @@ import 'package:wolf_3d_dart/src/registry/modules/sfx_module.dart'; /// Access version-specific asset IDs through the five typed modules: /// /// ```dart -/// data.registry.sfx.resolve(SfxKey.pistolFire) +/// data.registry.sfx.resolve(SoundEffect.pistolFire) /// data.registry.music.musicForLevel(episode, level) /// data.registry.entities.resolve(EntityKey.guard) /// data.registry.hud.faceForHealth(player.health) diff --git a/packages/wolf_3d_dart/lib/src/registry/built_in/built_in_music_module.dart b/packages/wolf_3d_dart/lib/src/registry/built_in/built_in_music_module.dart new file mode 100644 index 0000000..9fa15ea --- /dev/null +++ b/packages/wolf_3d_dart/lib/src/registry/built_in/built_in_music_module.dart @@ -0,0 +1,27 @@ +import 'package:wolf_3d_dart/src/data_types/game_version.dart'; +import 'package:wolf_3d_dart/src/registry/keys/music.dart'; +import 'package:wolf_3d_dart/src/registry/modules/music_module.dart'; + +/// Built-in music router backed directly by [Music] metadata. +class BuiltInMusicModule extends MusicModule { + const BuiltInMusicModule(this.version); + + final GameVersion version; + + @override + MusicRoute get menuMusic => + MusicRoute(Music.menuTheme.trackIndexFor(version)!); + + @override + MusicRoute musicForLevel(int episodeIndex, int levelIndex) { + return MusicRoute( + Music.trackIndexForLevel(version, episodeIndex, levelIndex)!, + ); + } + + @override + MusicRoute? resolve(Music key) { + final index = key.trackIndexFor(version); + return index != null ? MusicRoute(index) : null; + } +} diff --git a/packages/wolf_3d_dart/lib/src/registry/built_in/built_in_sfx_module.dart b/packages/wolf_3d_dart/lib/src/registry/built_in/built_in_sfx_module.dart new file mode 100644 index 0000000..ee4ef49 --- /dev/null +++ b/packages/wolf_3d_dart/lib/src/registry/built_in/built_in_sfx_module.dart @@ -0,0 +1,13 @@ +import 'package:wolf_3d_dart/src/data_types/game_version.dart'; +import 'package:wolf_3d_dart/src/registry/keys/sound_effect.dart'; +import 'package:wolf_3d_dart/src/registry/modules/sfx_module.dart'; + +/// Built-in sound-slot resolver backed directly by [SoundEffect] metadata. +class BuiltInSfxModule extends SfxModule { + const BuiltInSfxModule(this.version); + + final GameVersion version; + + @override + SoundAssetRef? resolve(SoundEffect key) => SoundAssetRef(key.idFor(version)); +} diff --git a/packages/wolf_3d_dart/lib/src/registry/built_in/retail_asset_registry.dart b/packages/wolf_3d_dart/lib/src/registry/built_in/retail_asset_registry.dart index c7524f8..5496dae 100644 --- a/packages/wolf_3d_dart/lib/src/registry/built_in/retail_asset_registry.dart +++ b/packages/wolf_3d_dart/lib/src/registry/built_in/retail_asset_registry.dart @@ -1,20 +1,21 @@ +import 'package:wolf_3d_dart/src/data_types/game_version.dart'; import 'package:wolf_3d_dart/src/registry/asset_registry.dart'; +import 'package:wolf_3d_dart/src/registry/built_in/built_in_music_module.dart'; +import 'package:wolf_3d_dart/src/registry/built_in/built_in_sfx_module.dart'; import 'package:wolf_3d_dart/src/registry/built_in/retail_entity_module.dart'; import 'package:wolf_3d_dart/src/registry/built_in/retail_hud_module.dart'; import 'package:wolf_3d_dart/src/registry/built_in/retail_menu_module.dart'; -import 'package:wolf_3d_dart/src/registry/built_in/retail_music_module.dart'; -import 'package:wolf_3d_dart/src/registry/built_in/retail_sfx_module.dart'; /// The canonical [AssetRegistry] for all retail Wolf3D releases. /// /// Composes the five retail built-in modules and preserves the original -/// id Software asset layout exactly. All other registries and tests that +/// id Software asset layout exactly. All other registries and tests that /// need a stable reference should start from this factory. class RetailAssetRegistry extends AssetRegistry { RetailAssetRegistry() : super( - sfx: const RetailSfxModule(), - music: const RetailMusicModule(), + sfx: const BuiltInSfxModule(GameVersion.retail), + music: const BuiltInMusicModule(GameVersion.retail), entities: const RetailEntityModule(), hud: const RetailHudModule(), menu: const RetailMenuPicModule(), diff --git a/packages/wolf_3d_dart/lib/src/registry/built_in/retail_music_module.dart b/packages/wolf_3d_dart/lib/src/registry/built_in/retail_music_module.dart deleted file mode 100644 index d0908f5..0000000 --- a/packages/wolf_3d_dart/lib/src/registry/built_in/retail_music_module.dart +++ /dev/null @@ -1,62 +0,0 @@ -import 'package:wolf_3d_dart/src/registry/keys/music_key.dart'; -import 'package:wolf_3d_dart/src/registry/modules/music_module.dart'; - -/// Built-in music module for all retail Wolf3D releases (v1.0, v1.1, v1.4). -/// -/// Encodes the original id Software level-to-track routing table and sets -/// track index 1 as the menu music, exactly matching the original engine. -class RetailMusicModule extends MusicModule { - const RetailMusicModule(); - - // Original WL_INTER.C music table — 60 entries (6 episodes × 10 levels). - static const List _levelMap = [ - 2, 3, 4, 5, 2, 3, 4, 5, 6, 7, // Episode 1 - 8, 9, 10, 11, 8, 9, 11, 10, 6, 12, // Episode 2 - 13, 14, 15, 16, 13, 14, 15, 16, 17, 18, // Episode 3 - 2, 3, 4, 5, 2, 3, 4, 5, 6, 7, // Episode 4 - 8, 9, 10, 11, 8, 9, 11, 10, 6, 12, // Episode 5 - 13, 14, 15, 16, 13, 14, 15, 16, 17, 19, // Episode 6 - ]; - - // Named MusicKey → track index for keyed lookups. - static final Map _named = { - MusicKey.menuTheme: 1, - MusicKey.level01: 2, - MusicKey.level02: 3, - MusicKey.level03: 4, - MusicKey.level04: 5, - MusicKey.level05: 6, - MusicKey.level06: 7, - MusicKey.level07: 8, - MusicKey.level08: 9, - MusicKey.level09: 10, - MusicKey.level10: 11, - MusicKey.level11: 12, - MusicKey.level12: 13, - MusicKey.level13: 14, - MusicKey.level14: 15, - MusicKey.level15: 16, - MusicKey.level16: 17, - MusicKey.level17: 18, - MusicKey.level18: 19, - }; - - @override - MusicRoute get menuMusic => const MusicRoute(1); - - @override - MusicRoute musicForLevel(int episodeIndex, int levelIndex) { - final flat = episodeIndex * 10 + levelIndex; - if (flat >= 0 && flat < _levelMap.length) { - return MusicRoute(_levelMap[flat]); - } - // Wrap for custom episode counts beyond the built-in 6. - return MusicRoute(_levelMap[flat % _levelMap.length]); - } - - @override - MusicRoute? resolve(MusicKey key) { - final index = _named[key]; - return index != null ? MusicRoute(index) : null; - } -} diff --git a/packages/wolf_3d_dart/lib/src/registry/built_in/retail_sfx_module.dart b/packages/wolf_3d_dart/lib/src/registry/built_in/retail_sfx_module.dart deleted file mode 100644 index 8e12cce..0000000 --- a/packages/wolf_3d_dart/lib/src/registry/built_in/retail_sfx_module.dart +++ /dev/null @@ -1,67 +0,0 @@ -import 'package:wolf_3d_dart/src/data_types/sound.dart'; -import 'package:wolf_3d_dart/src/registry/keys/sfx_key.dart'; -import 'package:wolf_3d_dart/src/registry/modules/sfx_module.dart'; - -/// Built-in SFX module for all retail Wolf3D releases (v1.0, v1.1, v1.4). -/// -/// Maps every [SfxKey] to the corresponding numeric slot from [WolfSound], -/// preserving the original id Software audio index layout exactly. -class RetailSfxModule extends SfxModule { - const RetailSfxModule(); - - static final Map _slots = { - // --- Doors & Environment --- - SfxKey.openDoor: WolfSound.openDoor, - SfxKey.closeDoor: WolfSound.closeDoor, - SfxKey.pushWall: WolfSound.pushWall, - // --- Weapons --- - SfxKey.knifeAttack: WolfSound.knifeAttack, - SfxKey.pistolFire: WolfSound.pistolFire, - SfxKey.machineGunFire: WolfSound.machineGunFire, - SfxKey.chainGunFire: WolfSound.gatlingFire, - SfxKey.enemyFire: WolfSound.naziFire, - // --- Pickups --- - SfxKey.getMachineGun: WolfSound.getMachineGun, - SfxKey.getAmmo: WolfSound.getAmmo, - SfxKey.getChainGun: WolfSound.getGatling, - SfxKey.healthSmall: WolfSound.healthSmall, - SfxKey.healthLarge: WolfSound.healthLarge, - SfxKey.treasure1: WolfSound.treasure1, - SfxKey.treasure2: WolfSound.treasure2, - SfxKey.treasure3: WolfSound.treasure3, - SfxKey.treasure4: WolfSound.treasure4, - SfxKey.extraLife: WolfSound.extraLife, - // --- Standard Enemies --- - SfxKey.guardHalt: WolfSound.guardHalt, - SfxKey.dogBark: WolfSound.dogBark, - SfxKey.dogDeath: WolfSound.dogDeath, - SfxKey.dogAttack: WolfSound.dogAttack, - SfxKey.deathScream1: WolfSound.deathScream1, - SfxKey.deathScream2: WolfSound.deathScream2, - SfxKey.deathScream3: WolfSound.deathScream3, - SfxKey.ssAlert: WolfSound.ssSchutzstaffel, - SfxKey.ssDeath: WolfSound.ssMeinGott, - // --- Bosses --- - SfxKey.bossActive: WolfSound.bossActive, - SfxKey.hansGrosseDeath: WolfSound.mutti, - SfxKey.schabbs: WolfSound.schabbsHas, - SfxKey.schabbsDeath: WolfSound.eva, - SfxKey.hitlerGreeting: WolfSound.gutenTag, - SfxKey.hitlerDeath: WolfSound.scheist, - SfxKey.mechaSteps: WolfSound.mechSteps, - SfxKey.ottoAlert: WolfSound.spion, - SfxKey.gretelDeath: WolfSound.neinSoVass, - // --- UI & Progression --- - SfxKey.levelComplete: WolfSound.levelDone, - SfxKey.endBonus1: WolfSound.endBonus1, - SfxKey.endBonus2: WolfSound.endBonus2, - SfxKey.noBonus: WolfSound.noBonus, - SfxKey.percent100: WolfSound.percent100, - }; - - @override - SoundAssetRef? resolve(SfxKey key) { - final slot = _slots[key]; - return slot != null ? SoundAssetRef(slot) : null; - } -} diff --git a/packages/wolf_3d_dart/lib/src/registry/built_in/shareware_asset_registry.dart b/packages/wolf_3d_dart/lib/src/registry/built_in/shareware_asset_registry.dart index 61def58..8a5cc00 100644 --- a/packages/wolf_3d_dart/lib/src/registry/built_in/shareware_asset_registry.dart +++ b/packages/wolf_3d_dart/lib/src/registry/built_in/shareware_asset_registry.dart @@ -1,14 +1,15 @@ +import 'package:wolf_3d_dart/src/data_types/game_version.dart'; import 'package:wolf_3d_dart/src/registry/asset_registry.dart'; -import 'package:wolf_3d_dart/src/registry/built_in/retail_sfx_module.dart'; +import 'package:wolf_3d_dart/src/registry/built_in/built_in_music_module.dart'; +import 'package:wolf_3d_dart/src/registry/built_in/built_in_sfx_module.dart'; import 'package:wolf_3d_dart/src/registry/built_in/shareware_entity_module.dart'; import 'package:wolf_3d_dart/src/registry/built_in/shareware_hud_module.dart'; import 'package:wolf_3d_dart/src/registry/built_in/shareware_menu_module.dart'; -import 'package:wolf_3d_dart/src/registry/built_in/shareware_music_module.dart'; /// The [AssetRegistry] for the Wolfenstein 3D v1.4 Shareware release. /// -/// - SFX slots are identical to retail (same AUDIOT layout). -/// - Music routing uses the 10-level shareware table. +/// - SFX slots are resolved through version-aware [SoundEffect.idFor]. +/// - Music routing uses [Music.levelFor] for the 10-level shareware table. /// - Entity definitions are limited to the three shareware enemies. /// - HUD indices are shareware-aware and offset from retail layout. /// - Menu picture indices are resolved via runtime heuristic offset; call @@ -17,8 +18,8 @@ import 'package:wolf_3d_dart/src/registry/built_in/shareware_music_module.dart'; class SharewareAssetRegistry extends AssetRegistry { SharewareAssetRegistry({bool strictOriginalShareware = false}) : super( - sfx: const RetailSfxModule(), - music: const SharewareMusicModule(), + sfx: const BuiltInSfxModule(GameVersion.shareware), + music: const BuiltInMusicModule(GameVersion.shareware), entities: const SharewareEntityModule(), hud: SharewareHudModule( useOriginalWl1Map: strictOriginalShareware, diff --git a/packages/wolf_3d_dart/lib/src/registry/built_in/shareware_music_module.dart b/packages/wolf_3d_dart/lib/src/registry/built_in/shareware_music_module.dart deleted file mode 100644 index 47a79ae..0000000 --- a/packages/wolf_3d_dart/lib/src/registry/built_in/shareware_music_module.dart +++ /dev/null @@ -1,49 +0,0 @@ -import 'package:wolf_3d_dart/src/registry/keys/music_key.dart'; -import 'package:wolf_3d_dart/src/registry/modules/music_module.dart'; - -/// Built-in music module for the Wolfenstein 3D v1.4 Shareware release. -/// -/// Uses the original id Software shareware level-to-track routing table -/// (10 levels, 1 episode) as opposed to the 60-entry retail table. -class SharewareMusicModule extends MusicModule { - const SharewareMusicModule(); - - // Original WL_INTER.C shareware music table (Episode 1 only). - static const List _levelMap = [ - 2, - 3, - 4, - 5, - 2, - 3, - 4, - 5, - 6, - 7, - ]; - - static final Map _named = { - MusicKey.menuTheme: 1, - MusicKey.level01: 2, - MusicKey.level02: 3, - MusicKey.level03: 4, - MusicKey.level04: 5, - MusicKey.level05: 6, - MusicKey.level06: 7, - }; - - @override - MusicRoute get menuMusic => const MusicRoute(1); - - @override - MusicRoute musicForLevel(int episodeIndex, int levelIndex) { - final flat = levelIndex % _levelMap.length; - return MusicRoute(_levelMap[flat]); - } - - @override - MusicRoute? resolve(MusicKey key) { - final index = _named[key]; - return index != null ? MusicRoute(index) : null; - } -} diff --git a/packages/wolf_3d_dart/lib/src/registry/keys/music.dart b/packages/wolf_3d_dart/lib/src/registry/keys/music.dart new file mode 100644 index 0000000..8e3c030 --- /dev/null +++ b/packages/wolf_3d_dart/lib/src/registry/keys/music.dart @@ -0,0 +1,162 @@ +import 'package:wolf_3d_dart/src/data_types/game_version.dart'; + +/// Canonical music identifiers used by built-in registries. +enum Music { + // --- Menus & UI --- + menuTheme(retailTrackIndex: 1, sharewareTrackIndex: 1), + + // --- Gameplay --- + // Generic level-slot keys used when routing by episode+floor index. + level01(retailTrackIndex: 2, sharewareTrackIndex: 2), + level02(retailTrackIndex: 3, sharewareTrackIndex: 3), + level03(retailTrackIndex: 4, sharewareTrackIndex: 4), + level04(retailTrackIndex: 5, sharewareTrackIndex: 5), + level05(retailTrackIndex: 6, sharewareTrackIndex: 6), + level06(retailTrackIndex: 7, sharewareTrackIndex: 7), + level07(retailTrackIndex: 8), + level08(retailTrackIndex: 9), + level09(retailTrackIndex: 10), + level10(retailTrackIndex: 11), + level11(retailTrackIndex: 12), + level12(retailTrackIndex: 13), + level13(retailTrackIndex: 14), + level14(retailTrackIndex: 15), + level15(retailTrackIndex: 16), + level16(retailTrackIndex: 17), + level17(retailTrackIndex: 18), + level18(retailTrackIndex: 19), + level19(), + level20(), + ; + + const Music({this.retailTrackIndex, this.sharewareTrackIndex}); + + final int? retailTrackIndex; + final int? sharewareTrackIndex; + + int? trackIndexFor(GameVersion version) { + switch (version) { + case GameVersion.shareware: + return sharewareTrackIndex; + case GameVersion.retail: + case GameVersion.spearOfDestiny: + case GameVersion.spearOfDestinyDemo: + return retailTrackIndex; + } + } + + static Music levelFor(GameVersion version, int episodeIndex, int levelIndex) { + final route = switch (version) { + GameVersion.shareware => _sharewareLevelRoute, + GameVersion.retail || + GameVersion.spearOfDestiny || + GameVersion.spearOfDestinyDemo => _retailLevelRoute, + }; + + final flatIndex = switch (version) { + GameVersion.shareware => levelIndex, + GameVersion.retail || + GameVersion.spearOfDestiny || + GameVersion.spearOfDestinyDemo => episodeIndex * 10 + levelIndex, + }; + + final normalizedIndex = flatIndex >= 0 + ? flatIndex % route.length + : (flatIndex % route.length + route.length) % route.length; + + return route[normalizedIndex]; + } + + static int? trackIndexForLevel( + GameVersion version, + int episodeIndex, + int levelIndex, + ) { + return levelFor(version, episodeIndex, levelIndex).trackIndexFor(version); + } + + static Music? fromTrackIndex(GameVersion version, int trackIndex) { + for (final music in values) { + if (music.trackIndexFor(version) == trackIndex) { + return music; + } + } + return null; + } + + static const List _sharewareLevelRoute = [ + level01, + level02, + level03, + level04, + level01, + level02, + level03, + level04, + level05, + level06, + ]; + + static const List _retailLevelRoute = [ + level01, + level02, + level03, + level04, + level01, + level02, + level03, + level04, + level05, + level06, + level07, + level08, + level09, + level10, + level07, + level08, + level10, + level09, + level05, + level11, + level12, + level13, + level14, + level15, + level12, + level13, + level14, + level15, + level16, + level17, + level01, + level02, + level03, + level04, + level01, + level02, + level03, + level04, + level05, + level06, + level07, + level08, + level09, + level10, + level07, + level08, + level10, + level09, + level05, + level11, + level12, + level13, + level14, + level15, + level12, + level13, + level14, + level15, + level16, + level18, + ]; +} diff --git a/packages/wolf_3d_dart/lib/src/registry/keys/music_key.dart b/packages/wolf_3d_dart/lib/src/registry/keys/music_key.dart deleted file mode 100644 index 729d937..0000000 --- a/packages/wolf_3d_dart/lib/src/registry/keys/music_key.dart +++ /dev/null @@ -1,51 +0,0 @@ -/// Extensible typed key for music tracks. -/// -/// Built-in Wolf3D music contexts are exposed as named static constants. -/// Custom modules can define new keys with `const MusicKey('myTrack')` -/// without modifying this class. -/// -/// Example: -/// ```dart -/// registry.music.resolve(MusicKey.menuTheme) -/// ``` -final class MusicKey { - const MusicKey(this._id); - - final String _id; - - // --- Menus & UI --- - static const menuTheme = MusicKey('menuTheme'); - - // --- Gameplay --- - // Generic level-slot keys used when routing by episode+floor index. - // The MusicModule maps these to actual IMF track indices. - static const level01 = MusicKey('level01'); - static const level02 = MusicKey('level02'); - static const level03 = MusicKey('level03'); - static const level04 = MusicKey('level04'); - static const level05 = MusicKey('level05'); - static const level06 = MusicKey('level06'); - static const level07 = MusicKey('level07'); - static const level08 = MusicKey('level08'); - static const level09 = MusicKey('level09'); - static const level10 = MusicKey('level10'); - static const level11 = MusicKey('level11'); - static const level12 = MusicKey('level12'); - static const level13 = MusicKey('level13'); - static const level14 = MusicKey('level14'); - static const level15 = MusicKey('level15'); - static const level16 = MusicKey('level16'); - static const level17 = MusicKey('level17'); - static const level18 = MusicKey('level18'); - static const level19 = MusicKey('level19'); - static const level20 = MusicKey('level20'); - - @override - bool operator ==(Object other) => other is MusicKey && other._id == _id; - - @override - int get hashCode => _id.hashCode; - - @override - String toString() => 'MusicKey($_id)'; -} diff --git a/packages/wolf_3d_dart/lib/src/registry/keys/sfx_key.dart b/packages/wolf_3d_dart/lib/src/registry/keys/sfx_key.dart deleted file mode 100644 index 2f4c4ba..0000000 --- a/packages/wolf_3d_dart/lib/src/registry/keys/sfx_key.dart +++ /dev/null @@ -1,77 +0,0 @@ -/// Extensible typed key for sound effects. -/// -/// Built-in Wolf3D sound effects are exposed as named static constants. -/// Custom modules can define new keys with `const SfxKey('myCustomId')` -/// without modifying this class. -/// -/// Example: -/// ```dart -/// registry.sfx.resolve(SfxKey.pistolFire) -/// ``` -final class SfxKey { - const SfxKey(this._id); - - final String _id; - - // --- Doors & Environment --- - static const openDoor = SfxKey('openDoor'); - static const closeDoor = SfxKey('closeDoor'); - static const pushWall = SfxKey('pushWall'); - - // --- Weapons --- - static const knifeAttack = SfxKey('knifeAttack'); - static const pistolFire = SfxKey('pistolFire'); - static const machineGunFire = SfxKey('machineGunFire'); - static const chainGunFire = SfxKey('chainGunFire'); - static const enemyFire = SfxKey('enemyFire'); - - // --- Pickups --- - static const getMachineGun = SfxKey('getMachineGun'); - static const getAmmo = SfxKey('getAmmo'); - static const getChainGun = SfxKey('getChainGun'); - static const healthSmall = SfxKey('healthSmall'); - static const healthLarge = SfxKey('healthLarge'); - static const treasure1 = SfxKey('treasure1'); - static const treasure2 = SfxKey('treasure2'); - static const treasure3 = SfxKey('treasure3'); - static const treasure4 = SfxKey('treasure4'); - static const extraLife = SfxKey('extraLife'); - - // --- Standard Enemies --- - static const guardHalt = SfxKey('guardHalt'); - static const dogBark = SfxKey('dogBark'); - static const dogDeath = SfxKey('dogDeath'); - static const dogAttack = SfxKey('dogAttack'); - static const deathScream1 = SfxKey('deathScream1'); - static const deathScream2 = SfxKey('deathScream2'); - static const deathScream3 = SfxKey('deathScream3'); - static const ssAlert = SfxKey('ssAlert'); - static const ssDeath = SfxKey('ssDeath'); - - // --- Bosses --- - static const bossActive = SfxKey('bossActive'); - static const hansGrosseDeath = SfxKey('hansGrosseDeath'); - static const schabbs = SfxKey('schabbs'); - static const schabbsDeath = SfxKey('schabbsDeath'); - static const hitlerGreeting = SfxKey('hitlerGreeting'); - static const hitlerDeath = SfxKey('hitlerDeath'); - static const mechaSteps = SfxKey('mechaSteps'); - static const ottoAlert = SfxKey('ottoAlert'); - static const gretelDeath = SfxKey('gretelDeath'); - - // --- UI & Progression --- - static const levelComplete = SfxKey('levelComplete'); - static const endBonus1 = SfxKey('endBonus1'); - static const endBonus2 = SfxKey('endBonus2'); - static const noBonus = SfxKey('noBonus'); - static const percent100 = SfxKey('percent100'); - - @override - bool operator ==(Object other) => other is SfxKey && other._id == _id; - - @override - int get hashCode => _id.hashCode; - - @override - String toString() => 'SfxKey($_id)'; -} diff --git a/packages/wolf_3d_dart/lib/src/registry/keys/sound_effect.dart b/packages/wolf_3d_dart/lib/src/registry/keys/sound_effect.dart new file mode 100644 index 0000000..e6c20d5 --- /dev/null +++ b/packages/wolf_3d_dart/lib/src/registry/keys/sound_effect.dart @@ -0,0 +1,75 @@ +import 'package:wolf_3d_dart/src/data_types/game_version.dart'; + +/// Canonical sound-effect identifiers used by built-in registries. +enum SoundEffect { + // --- Doors & Environment --- + openDoor(retailId: 8), + closeDoor(retailId: 9), + pushWall(retailId: 46), + + // --- Weapons --- + knifeAttack(retailId: 23), + pistolFire(retailId: 24), + machineGunFire(retailId: 26), + chainGunFire(retailId: 32), + enemyFire(retailId: 58), + + // --- Pickups --- + getMachineGun(retailId: 30), + getAmmo(retailId: 31), + getChainGun(retailId: 38), + healthSmall(retailId: 33), + healthLarge(retailId: 34), + treasure1(retailId: 35), + treasure2(retailId: 36), + treasure3(retailId: 37), + treasure4(retailId: 45), + extraLife(retailId: 44), + + // --- Standard Enemies --- + guardHalt(retailId: 21), + dogBark(retailId: 41), + dogDeath(retailId: 62), + dogAttack(retailId: 68), + deathScream1(retailId: 29), + deathScream2(retailId: 22), + deathScream3(retailId: 25), + ssAlert(retailId: 51), + ssDeath(retailId: 63), + + // --- Bosses --- + bossActive(retailId: 49), + hansGrosseDeath(retailId: 50), + schabbs(retailId: 64), + schabbsDeath(retailId: 54), + hitlerGreeting(retailId: 55), + hitlerDeath(retailId: 57), + mechaSteps(retailId: 70), + ottoAlert(retailId: 66), + gretelDeath(retailId: 67), + + // --- UI & Progression --- + levelComplete(retailId: 40), + endBonus1(retailId: 42), + endBonus2(retailId: 43), + noBonus(retailId: 47), + percent100(retailId: 48), + ; + + const SoundEffect({required this.retailId, int? sharewareId}) + : sharewareId = sharewareId ?? retailId; + + final int retailId; + final int sharewareId; + + int idFor(GameVersion version) { + switch (version) { + case GameVersion.shareware: + return sharewareId; + case GameVersion.retail: + case GameVersion.spearOfDestiny: + case GameVersion.spearOfDestinyDemo: + return retailId; + } + } +} diff --git a/packages/wolf_3d_dart/lib/src/registry/modules/music_module.dart b/packages/wolf_3d_dart/lib/src/registry/modules/music_module.dart index e228991..b959df0 100644 --- a/packages/wolf_3d_dart/lib/src/registry/modules/music_module.dart +++ b/packages/wolf_3d_dart/lib/src/registry/modules/music_module.dart @@ -1,4 +1,4 @@ -import 'package:wolf_3d_dart/src/registry/keys/music_key.dart'; +import 'package:wolf_3d_dart/src/registry/keys/music.dart'; /// The resolved reference for a music track: a numeric index into /// [WolfensteinData.music]. @@ -19,10 +19,10 @@ class MusicRoute { abstract class MusicModule { const MusicModule(); - /// Resolves a named [MusicKey] to a [MusicRoute]. + /// Resolves a named [Music] to a [MusicRoute]. /// /// Returns `null` if the key is not supported by this module. - MusicRoute? resolve(MusicKey key); + MusicRoute? resolve(Music key); /// Resolves the level music track for a given [episodeIndex] and /// zero-based [levelIndex]. diff --git a/packages/wolf_3d_dart/lib/src/registry/modules/sfx_module.dart b/packages/wolf_3d_dart/lib/src/registry/modules/sfx_module.dart index 2eee8f9..0d1df1a 100644 --- a/packages/wolf_3d_dart/lib/src/registry/modules/sfx_module.dart +++ b/packages/wolf_3d_dart/lib/src/registry/modules/sfx_module.dart @@ -1,4 +1,4 @@ -import 'package:wolf_3d_dart/src/registry/keys/sfx_key.dart'; +import 'package:wolf_3d_dart/src/registry/keys/sound_effect.dart'; /// The resolved reference for a sound effect: a numeric slot index into /// [WolfensteinData.sounds]. @@ -11,7 +11,7 @@ class SoundAssetRef { String toString() => 'SoundAssetRef($slotIndex)'; } -/// Owns the mapping from symbolic [SfxKey] identifiers to numeric sound slots. +/// Owns the mapping from symbolic [SoundEffect] identifiers to numeric sound slots. /// /// Implement this class to provide a custom sound layout for a modded or /// alternate game version. Pass the implementation inside a custom @@ -22,5 +22,5 @@ abstract class SfxModule { /// Resolves [key] to a [SoundAssetRef] containing the numeric slot index. /// /// Returns `null` if the key is not supported by this module. - SoundAssetRef? resolve(SfxKey key); + SoundAssetRef? resolve(SoundEffect key); } diff --git a/packages/wolf_3d_dart/lib/src/synth/wolf_3d_audio.dart b/packages/wolf_3d_dart/lib/src/synth/wolf_3d_audio.dart index a9ba049..b43e140 100644 --- a/packages/wolf_3d_dart/lib/src/synth/wolf_3d_audio.dart +++ b/packages/wolf_3d_dart/lib/src/synth/wolf_3d_audio.dart @@ -13,7 +13,7 @@ class WolfAudio implements EngineAudio { for (int i = 0; i < 50; i++) { Future.delayed(Duration(seconds: i * 2), () { log("[AUDIO] Testing Sound ID: $i"); - playSoundEffect(i); + playSoundEffectId(i); }); } } @@ -122,16 +122,21 @@ class WolfAudio implements EngineAudio { @override Future playMenuMusic() async { final data = activeGame; - if (data == null || data.music.length <= 1) return; - await playMusic(data.music[1]); + final trackIndex = data == null + ? null + : Music.menuTheme.trackIndexFor(data.version); + if (data == null || trackIndex == null || trackIndex >= data.music.length) { + return; + } + await playMusic(data.music[trackIndex]); } @override - Future playLevelMusic(WolfLevel level) async { + Future playLevelMusic(Music music) async { final data = activeGame; if (data == null || data.music.isEmpty) return; - final index = level.musicIndex; + final index = music.trackIndexFor(data.version) ?? 0; if (index < data.music.length) { await playMusic(data.music[index]); } else { @@ -144,14 +149,24 @@ class WolfAudio implements EngineAudio { // ========================================== @override - Future playSoundEffect(int sfxId) async { + Future playSoundEffect(SoundEffect effect) async { + final data = activeGame; + if (data == null) return; + await playSoundEffectId(effect.idFor(data.version)); + } + + @override + Future playSoundEffectId(int sfxId) async { log("[AUDIO] Playing sfx id $sfxId"); // The original engine uses a specific starting chunk for digitized sounds. // In many loaders, the 'sounds' list is already just the digitized ones. // If your list contains EVERYTHING, you need to add the offset (174). // If it's JUST digitized sounds, sfxId should work directly. - final soundsList = activeGame!.sounds; + final data = activeGame; + if (data == null) return; + + final soundsList = data.sounds; if (sfxId < 0 || sfxId >= soundsList.length) return; final raw8bitBytes = soundsList[sfxId].bytes; diff --git a/packages/wolf_3d_dart/lib/wolf_3d_data_types.dart b/packages/wolf_3d_dart/lib/wolf_3d_data_types.dart index 0e1b4e2..d4c14f2 100644 --- a/packages/wolf_3d_dart/lib/wolf_3d_data_types.dart +++ b/packages/wolf_3d_dart/lib/wolf_3d_data_types.dart @@ -19,8 +19,7 @@ export 'src/data_types/game_file.dart' show GameFile; export 'src/data_types/game_version.dart' show GameVersion; export 'src/data_types/image.dart' show VgaImage; export 'src/data_types/map_objects.dart' show MapObject; -export 'src/data_types/sound.dart' - show PcmSound, ImfMusic, ImfInstruction, WolfMusicMap, WolfSound; +export 'src/data_types/sound.dart' show PcmSound, ImfMusic, ImfInstruction; export 'src/data_types/sprite.dart' hide Matrix; export 'src/data_types/sprite_frame_range.dart' show SpriteFrameRange; export 'src/data_types/wolf_level.dart' show WolfLevel; @@ -34,8 +33,8 @@ export 'src/registry/built_in/shareware_asset_registry.dart' export 'src/registry/keys/entity_key.dart' show EntityKey; export 'src/registry/keys/hud_key.dart' show HudKey; export 'src/registry/keys/menu_pic_key.dart' show MenuPicKey; -export 'src/registry/keys/music_key.dart' show MusicKey; -export 'src/registry/keys/sfx_key.dart' show SfxKey; +export 'src/registry/keys/music.dart' show Music; +export 'src/registry/keys/sound_effect.dart' show SoundEffect; export 'src/registry/modules/entity_asset_module.dart' show EntityAssetModule, EntityAssetDefinition; export 'src/registry/modules/hud_module.dart' show HudModule, HudAssetRef; diff --git a/packages/wolf_3d_dart/test/engine/audio_events_test.dart b/packages/wolf_3d_dart/test/engine/audio_events_test.dart index 4c32243..a173296 100644 --- a/packages/wolf_3d_dart/test/engine/audio_events_test.dart +++ b/packages/wolf_3d_dart/test/engine/audio_events_test.dart @@ -25,7 +25,7 @@ void main() { ); engine.tick(const Duration(milliseconds: 16)); - expect(audio.sfxIds, contains(WolfSound.getAmmo)); + expect(audio.sfxIds, contains(SoundEffect.getAmmo)); }); test('plays guard alert when guard notices player', () { @@ -47,7 +47,7 @@ void main() { engine.tick(const Duration(milliseconds: 16)); } - expect(audio.sfxIds, contains(WolfSound.guardHalt)); + expect(audio.sfxIds, contains(SoundEffect.guardHalt)); }); test('plays dog death sound when dog dies', () { @@ -69,7 +69,7 @@ void main() { engine.tick(const Duration(milliseconds: 16)); - expect(audio.sfxIds, contains(WolfSound.dogDeath)); + expect(audio.sfxIds, contains(SoundEffect.dogDeath)); }); test('plays pushwall sound when triggering a secret wall', () { @@ -90,7 +90,7 @@ void main() { engine.init(); engine.tick(const Duration(milliseconds: 16)); - expect(audio.sfxIds, contains(WolfSound.pushWall)); + expect(audio.sfxIds, contains(SoundEffect.pushWall)); }); }); } @@ -134,7 +134,7 @@ WolfEngine _buildEngine({ wallGrid: wallGrid, areaGrid: List.generate(64, (_) => List.filled(64, -1)), objectGrid: objectGrid, - musicIndex: 0, + music: Music.level01, ), ], ), @@ -159,7 +159,7 @@ class _CapturingAudio implements EngineAudio { @override WolfensteinData? activeGame; - final List sfxIds = []; + final List sfxIds = []; @override Future debugSoundTest() async {} @@ -168,14 +168,24 @@ class _CapturingAudio implements EngineAudio { Future init() async {} @override - void playLevelMusic(WolfLevel level) {} + void playLevelMusic(Music music) {} @override void playMenuMusic() {} @override - void playSoundEffect(int sfxId) { - sfxIds.add(sfxId); + void playSoundEffect(SoundEffect effect) { + sfxIds.add(effect); + } + + @override + void playSoundEffectId(int sfxId) { + final effect = SoundEffect.values + .where((entry) => entry.retailId == sfxId) + .firstOrNull; + if (effect != null) { + sfxIds.add(effect); + } } @override diff --git a/packages/wolf_3d_dart/test/engine/enemy_drop_parity_test.dart b/packages/wolf_3d_dart/test/engine/enemy_drop_parity_test.dart index e7bc871..e256f27 100644 --- a/packages/wolf_3d_dart/test/engine/enemy_drop_parity_test.dart +++ b/packages/wolf_3d_dart/test/engine/enemy_drop_parity_test.dart @@ -88,7 +88,7 @@ WolfEngine _buildEngine() { wallGrid: wallGrid, areaGrid: List.generate(64, (_) => List.filled(64, -1)), objectGrid: objectGrid, - musicIndex: 0, + music: Music.level01, ), ], ), @@ -119,13 +119,16 @@ class _SilentAudio implements EngineAudio { Future init() async {} @override - void playLevelMusic(WolfLevel level) {} + void playLevelMusic(Music music) {} @override void playMenuMusic() {} @override - void playSoundEffect(int sfxId) {} + void playSoundEffect(SoundEffect effect) {} + + @override + void playSoundEffectId(int sfxId) {} @override void stopMusic() {} diff --git a/packages/wolf_3d_dart/test/engine/level_state_and_pause_menu_test.dart b/packages/wolf_3d_dart/test/engine/level_state_and_pause_menu_test.dart index d369dc2..2ee618f 100644 --- a/packages/wolf_3d_dart/test/engine/level_state_and_pause_menu_test.dart +++ b/packages/wolf_3d_dart/test/engine/level_state_and_pause_menu_test.dart @@ -365,14 +365,14 @@ WolfensteinData _buildTestData({required GameVersion gameVersion}) { wallGrid: levelOneWalls, areaGrid: List.generate(64, (_) => List.filled(64, -1)), objectGrid: levelOneObjects, - musicIndex: 0, + music: Music.level01, ), WolfLevel( name: 'Level 2', wallGrid: levelTwoWalls, areaGrid: List.generate(64, (_) => List.filled(64, -1)), objectGrid: levelTwoObjects, - musicIndex: 1, + music: Music.level02, ), ], ), @@ -396,13 +396,16 @@ class _SilentAudio implements EngineAudio { Future init() async {} @override - void playLevelMusic(WolfLevel level) {} + void playLevelMusic(Music music) {} @override void playMenuMusic() {} @override - void playSoundEffect(int sfxId) {} + void playSoundEffect(SoundEffect effect) {} + + @override + void playSoundEffectId(int sfxId) {} @override void stopMusic() {} diff --git a/packages/wolf_3d_dart/test/rasterizer/pushwall_rasterizer_test.dart b/packages/wolf_3d_dart/test/rasterizer/pushwall_rasterizer_test.dart index 51bde6c..f4d7226 100644 --- a/packages/wolf_3d_dart/test/rasterizer/pushwall_rasterizer_test.dart +++ b/packages/wolf_3d_dart/test/rasterizer/pushwall_rasterizer_test.dart @@ -45,7 +45,7 @@ void main() { wallGrid: wallGrid, areaGrid: List.generate(64, (_) => List.filled(64, -1)), objectGrid: objectGrid, - musicIndex: 0, + music: Music.level01, ), ], ), diff --git a/packages/wolf_3d_dart/test/rendering/pushwall_renderer_test.dart b/packages/wolf_3d_dart/test/rendering/pushwall_renderer_test.dart index cb96998..cf2d08e 100644 --- a/packages/wolf_3d_dart/test/rendering/pushwall_renderer_test.dart +++ b/packages/wolf_3d_dart/test/rendering/pushwall_renderer_test.dart @@ -45,7 +45,7 @@ void main() { wallGrid: wallGrid, areaGrid: List.generate(64, (_) => List.filled(64, -1)), objectGrid: objectGrid, - musicIndex: 0, + music: Music.level01, ), ], ), diff --git a/packages/wolf_3d_flutter/lib/audio/audio_adaptor.dart b/packages/wolf_3d_flutter/lib/audio/audio_adaptor.dart index 3c3b49a..9f3db2b 100644 --- a/packages/wolf_3d_flutter/lib/audio/audio_adaptor.dart +++ b/packages/wolf_3d_flutter/lib/audio/audio_adaptor.dart @@ -8,8 +8,8 @@ class FlutterAudioAdapter implements EngineAudio { FlutterAudioAdapter(this.wolf3d); @override - void playLevelMusic(WolfLevel level) { - wolf3d.audio.playLevelMusic(level); + void playLevelMusic(Music music) { + wolf3d.audio.playLevelMusic(music); } @override @@ -23,8 +23,13 @@ class FlutterAudioAdapter implements EngineAudio { } @override - void playSoundEffect(int sfxId) { - wolf3d.audio.playSoundEffect(sfxId); + void playSoundEffect(SoundEffect effect) { + wolf3d.audio.playSoundEffect(effect); + } + + @override + void playSoundEffectId(int sfxId) { + wolf3d.audio.playSoundEffectId(sfxId); } @override