Handle exit elevators and secret levels
Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
@@ -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.
|
||||
|
||||
8
packages/wolf_3d_data_types/lib/src/episode.dart
Normal file
8
packages/wolf_3d_data_types/lib/src/episode.dart
Normal file
@@ -0,0 +1,8 @@
|
||||
import 'package:wolf_3d_data_types/wolf_3d_data_types.dart';
|
||||
|
||||
class Episode {
|
||||
final String name;
|
||||
final List<WolfLevel> levels;
|
||||
|
||||
const Episode({required this.name, required this.levels});
|
||||
}
|
||||
@@ -7,13 +7,8 @@ class WolfensteinData {
|
||||
final List<PcmSound> sounds;
|
||||
final List<AdLibSound> adLibSounds;
|
||||
final List<ImfMusic> music;
|
||||
final List<WolfLevel> levels;
|
||||
final List<VgaImage> vgaImages;
|
||||
|
||||
// --- Derived Properties ---
|
||||
/// Calculates the number of available episodes based on the loaded levels.
|
||||
/// (Each episode consists of exactly 10 levels).
|
||||
int get numberOfEpisodes => (levels.length / 10).floor().clamp(1, 6);
|
||||
final List<Episode> episodes;
|
||||
|
||||
const WolfensteinData({
|
||||
required this.version,
|
||||
@@ -22,7 +17,7 @@ class WolfensteinData {
|
||||
required this.sounds,
|
||||
required this.adLibSounds,
|
||||
required this.music,
|
||||
required this.levels,
|
||||
required this.vgaImages,
|
||||
required this.episodes,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
/// More dartdocs go here.
|
||||
library;
|
||||
|
||||
export 'src/episode.dart' show Episode;
|
||||
export 'src/game_file.dart' show GameFile;
|
||||
export 'src/game_version.dart' show GameVersion;
|
||||
export 'src/image.dart' show VgaImage;
|
||||
|
||||
Reference in New Issue
Block a user