Handle exit elevators and secret levels

Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
2026-03-15 15:25:22 +01:00
parent 45ab8e4aed
commit 5f3e3bb823
8 changed files with 187 additions and 105 deletions

View File

@@ -83,7 +83,7 @@ abstract class WLParser {
walls: parseWalls(vswap),
sprites: parseSprites(vswap),
sounds: parseSounds(vswap).map((bytes) => PcmSound(bytes)).toList(),
levels: parseMaps(mapHead, gameMaps, isShareware: isShareware),
episodes: parseEpisodes(mapHead, gameMaps, isShareware: isShareware),
vgaImages: parseVgaImages(vgaDict, vgaHead, vgaGraph),
adLibSounds: audio.adLib,
music: audio.music,
@@ -236,22 +236,39 @@ abstract class WLParser {
return images;
}
// --- Episode Names (From the original C Executable) ---
static const List<String> _sharewareEpisodeNames = [
"Episode 1\nEscape from Wolfenstein",
];
static const List<String> _retailEpisodeNames = [
"Episode 1\nEscape from Wolfenstein",
"Episode 2\nOperation: Eisenfaust",
"Episode 3\nDie, Fuhrer, Die!",
"Episode 4\nA Dark Secret",
"Episode 5\nTrail of the Madman",
"Episode 6\nConfrontation",
];
/// Parses MAPHEAD and GAMEMAPS to extract the raw level data.
static List<WolfLevel> parseMaps(
static List<Episode> parseEpisodes(
ByteData mapHead,
ByteData gameMaps, {
bool isShareware = true,
}) {
List<WolfLevel> levels = [];
// 1. Select the correct map based on the version
final activeMusicMap = isShareware ? _sharewareMusicMap : _retailMusicMap;
List<WolfLevel> allLevels = [];
int rlewTag = mapHead.getUint16(0, Endian.little);
// Select the correct music map based on the version
final activeMusicMap = isShareware ? _sharewareMusicMap : _retailMusicMap;
final episodeNames = isShareware
? _sharewareEpisodeNames
: _retailEpisodeNames;
// The game allows for up to 100 maps per file
for (int i = 0; i < 100; i++) {
int mapOffset = mapHead.getUint32(2 + (i * 4), Endian.little);
if (mapOffset == 0) continue;
if (mapOffset == 0) continue; // Empty map slot
int plane0Offset = gameMaps.getUint32(mapOffset + 0, Endian.little);
int plane1Offset = gameMaps.getUint32(mapOffset + 4, Endian.little);
@@ -259,13 +276,15 @@ abstract class WLParser {
int plane0Length = gameMaps.getUint16(mapOffset + 12, Endian.little);
int plane1Length = gameMaps.getUint16(mapOffset + 14, Endian.little);
// --- EXTRACT ACTUAL GAME DATA NAME ---
// The name is exactly 16 bytes long, starting at offset 22
List<int> nameBytes = [];
for (int n = 0; n < 16; n++) {
int charCode = gameMaps.getUint8(mapOffset + 22 + n);
if (charCode == 0) break;
if (charCode == 0) break; // Stop at the null-terminator
nameBytes.add(charCode);
}
String name = ascii.decode(nameBytes);
String parsedName = ascii.decode(nameBytes);
// --- DECOMPRESS PLANES ---
final compressedWallData = gameMaps.buffer.asUint8List(
@@ -282,7 +301,7 @@ abstract class WLParser {
Uint16List carmackExpandedObjects = _expandCarmack(compressedObjectData);
List<int> flatObjectGrid = _expandRlew(carmackExpandedObjects, rlewTag);
// --- BUILD GRIDS ---
// --- BUILD 64x64 GRIDS ---
List<List<int>> wallGrid = [];
List<List<int>> objectGrid = [];
@@ -297,17 +316,14 @@ 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.
// --- ASSIGN MUSIC ---
int trackIndex = (i < activeMusicMap.length)
? activeMusicMap[i]
: activeMusicMap[i % activeMusicMap.length];
levels.add(
allLevels.add(
WolfLevel(
name: name,
name: parsedName,
wallGrid: wallGrid,
objectGrid: objectGrid,
musicIndex: trackIndex,
@@ -315,7 +331,35 @@ abstract class WLParser {
);
}
return levels;
// 2. Group the parsed levels into Episodes!
List<Episode> episodes = [];
// Calculate how many episodes we need (10 levels per episode)
int totalEpisodes = (allLevels.length / 10).ceil();
for (int i = 0; i < totalEpisodes; i++) {
int startIndex = i * 10;
int endIndex = startIndex + 10;
// Safety clamp for incomplete episodes at the end of the file
if (endIndex > allLevels.length) {
endIndex = allLevels.length;
}
// If we run out of hardcoded id Software names, generate a custom one!
String epName = (i < episodeNames.length)
? episodeNames[i]
: "Episode ${i + 1}\nCustom Maps";
episodes.add(
Episode(
name: epName,
levels: allLevels.sublist(startIndex, endIndex),
),
);
}
return episodes;
}
/// Extracts AdLib sounds and IMF music tracks from the audio files.