Load menu and level audio dynamically

Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
2026-03-15 14:54:57 +01:00
parent 6eb903cbaa
commit a75ade8b33
8 changed files with 67 additions and 6 deletions

View File

@@ -69,11 +69,15 @@ class _WolfRendererState extends State<WolfRenderer>
// Grab the specific level from the singleton
_currentLevel = Wolf3d.I.levels[mapIndex];
// Play the exact track id Software intended for this level!
Wolf3d.I.audio.playLevelMusic(_currentLevel);
// TODO: Initialize player position, spawn enemies based on difficulty, etc.
debugPrint("Loaded Level: ${_currentLevel.name}");
}
void _onLevelCompleted() {
Wolf3d.I.audio.stopMusic();
// When the player hits the elevator switch, advance the map
setState(() {
_currentMapIndex++;

View File

@@ -22,16 +22,13 @@ class _EpisodeScreenState extends State<EpisodeScreen> {
@override
void initState() {
super.initState();
if (Wolf3d.I.music.isNotEmpty) {
Wolf3d.I.audio.playMusic(Wolf3d.I.music.first);
}
Wolf3d.I.audio.playMenuMusic();
}
void _selectEpisode(int index) {
Wolf3d.I.setActiveEpisode(index);
Navigator.of(context).push(
MaterialPageRoute(
// We pass the audio handles so the next screen can stop them when the game starts
builder: (context) => DifficultyScreen(),
),
);
@@ -39,7 +36,6 @@ class _EpisodeScreenState extends State<EpisodeScreen> {
@override
Widget build(BuildContext context) {
// Determine how many episodes are available (10 levels per episode)
final int numberOfEpisodes = (Wolf3d.I.levels.length / 10).floor().clamp(
1,
6,

View File

@@ -45,6 +45,7 @@ class Wolf3d {
// --- Actions ---
void setActiveGame(WolfensteinData game) {
_activeGame = game;
audio.activeGame = game;
}
/// Initializes the engine by loading available game data.

View File

@@ -4,6 +4,20 @@ import 'dart:typed_data';
import 'package:wolf_3d_data_types/wolf_3d_data_types.dart';
abstract class WLParser {
// --- Original Song Lookup Tables ---
static const List<int> _sharewareMusicMap = [
2, 3, 4, 5, 2, 3, 4, 5, 6, 7, // Episode 1
];
static const List<int> _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) that
/// takes a filename and returns its ByteData.
@@ -229,6 +243,10 @@ abstract class WLParser {
bool isShareware = true,
}) {
List<WolfLevel> levels = [];
// 1. Select the correct map based on the version
final activeMusicMap = isShareware ? _sharewareMusicMap : _retailMusicMap;
int rlewTag = mapHead.getUint16(0, Endian.little);
for (int i = 0; i < 100; i++) {
@@ -279,11 +297,20 @@ abstract class WLParser {
objectGrid.add(objectRow);
}
// Determine music track index.
// We use 'i' because it represents the absolute map slot (e.g. Map 12).
// If a map exists past the bounds of our lookup table (custom maps),
// we wrap around safely.
int trackIndex = (i < activeMusicMap.length)
? activeMusicMap[i]
: activeMusicMap[i % activeMusicMap.length];
levels.add(
WolfLevel(
name: name,
wallGrid: wallGrid,
objectGrid: objectGrid,
musicIndex: trackIndex,
),
);
}

View File

@@ -49,3 +49,5 @@ class ImfMusic {
return ImfMusic(instructions);
}
}
typedef WolfMusicMap = List<int>;

View File

@@ -4,10 +4,12 @@ class WolfLevel {
final String name;
final Sprite wallGrid;
final Sprite objectGrid;
final int musicIndex;
const WolfLevel({
required this.name,
required this.wallGrid,
required this.objectGrid,
required this.musicIndex,
});
}

View File

@@ -6,7 +6,8 @@ library;
export 'src/game_file.dart' show GameFile;
export 'src/game_version.dart' show GameVersion;
export 'src/image.dart' show VgaImage;
export 'src/sound.dart' show PcmSound, AdLibSound, ImfMusic, ImfInstruction;
export 'src/sound.dart'
show PcmSound, AdLibSound, ImfMusic, ImfInstruction, WolfMusicMap;
export 'src/sprite.dart' hide Matrix;
export 'src/wolf_level.dart' show WolfLevel;
export 'src/wolfenstein_data.dart' show WolfensteinData;

View File

@@ -9,6 +9,8 @@ class WolfAudio {
AudioSource? _currentMusicSource;
SoundHandle? _currentMusicHandle;
WolfensteinData? activeGame;
/// Initializes the SoLoud audio engine.
Future<void> init() async {
if (_isInitialized) return;
@@ -89,4 +91,30 @@ class WolfAudio {
SoLoud.instance.setPause(_currentMusicHandle!, false);
}
}
Future<void> playMenuMusic() async {
final data = activeGame;
// We can't play if data isn't set or doesn't have enough tracks
if (data == null || data.music.length <= 1) return;
// Track 1 is the menu theme in both Shareware and Retail
await playMusic(data.music[1]);
}
/// Plays the specific track assigned to a WolfLevel.
Future<void> playLevelMusic(WolfLevel level) async {
final data = activeGame;
if (data == null || data.music.isEmpty) return;
final index = level.musicIndex;
if (index < data.music.length) {
// 3. FIXED THIS: Call your playMusic method
await playMusic(data.music[index]);
} else {
print(
"WolfAudio: Warning - Track index $index out of bounds for level ${level.name}.",
);
}
}
}