Add built-in asset modules for Wolfenstein 3D v1.4 Shareware release
- Implement RetailSfxModule to map sound effects to numeric slots. - Create SharewareAssetRegistry to manage assets for the Shareware version. - Introduce SharewareEntityModule to define available enemies in Shareware. - Add SharewareMenuPicModule to handle menu pictures with runtime offset computation. - Implement SharewareMusicModule for music routing in Shareware. - Define keys for entities, HUD elements, menu pictures, and music tracks. - Create abstract modules for entity, HUD, menu picture, and music assets. - Add registry resolver to select appropriate asset registry based on game version and data version. - Update WolfensteinData to include new asset registry exports. - Modify tests to utilize the new asset registry structure for Shareware and Retail versions. Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
@@ -2,7 +2,6 @@ import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:crypto/crypto.dart';
|
||||
import 'package:wolf_3d_dart/src/data/data_version.dart';
|
||||
import 'package:wolf_3d_dart/src/data/wl_parser.dart';
|
||||
import 'package:wolf_3d_dart/wolf_3d_data_types.dart';
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:crypto/crypto.dart' show md5;
|
||||
import 'package:wolf_3d_dart/src/data/data_version.dart';
|
||||
import 'package:wolf_3d_dart/wolf_3d_data_types.dart';
|
||||
|
||||
/// The primary parser for Wolfenstein 3D data formats.
|
||||
@@ -99,6 +98,70 @@ abstract class WLParser {
|
||||
);
|
||||
}
|
||||
|
||||
/// Async file-by-file equivalent of [loadAsync]; exposed for use from
|
||||
/// external callers that need a [registryOverride] in async contexts.
|
||||
static Future<WolfensteinData> loadAsyncWithOverride(
|
||||
Future<ByteData> Function(String filename) fileFetcher, {
|
||||
AssetRegistry? registryOverride,
|
||||
}) async {
|
||||
GameVersion? detectedVersion;
|
||||
ByteData? vswap;
|
||||
|
||||
for (final version in GameVersion.values) {
|
||||
try {
|
||||
vswap = await fileFetcher('VSWAP.${version.fileExtension}');
|
||||
detectedVersion = version;
|
||||
break;
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
if (detectedVersion == null || vswap == null) {
|
||||
throw Exception('Could not locate a valid VSWAP file.');
|
||||
}
|
||||
|
||||
final ext = detectedVersion.fileExtension;
|
||||
final vswapBytes = vswap.buffer.asUint8List(
|
||||
vswap.offsetInBytes,
|
||||
vswap.lengthInBytes,
|
||||
);
|
||||
final vswapHash = md5.convert(vswapBytes).toString();
|
||||
final dataIdentity = DataVersion.fromChecksum(vswapHash);
|
||||
|
||||
ByteData gameMapsData;
|
||||
if (dataIdentity == DataVersion.version10Retail) {
|
||||
try {
|
||||
gameMapsData = await fileFetcher('MAPTEMP.$ext');
|
||||
} catch (_) {
|
||||
gameMapsData = await fileFetcher('GAMEMAPS.$ext');
|
||||
}
|
||||
} else {
|
||||
gameMapsData = await fileFetcher('GAMEMAPS.$ext');
|
||||
}
|
||||
|
||||
final rawFiles = {
|
||||
'MAPHEAD.$ext': await fileFetcher('MAPHEAD.$ext'),
|
||||
'VGADICT.$ext': await fileFetcher('VGADICT.$ext'),
|
||||
'VGAHEAD.$ext': await fileFetcher('VGAHEAD.$ext'),
|
||||
'VGAGRAPH.$ext': await fileFetcher('VGAGRAPH.$ext'),
|
||||
'AUDIOHED.$ext': await fileFetcher('AUDIOHED.$ext'),
|
||||
'AUDIOT.$ext': await fileFetcher('AUDIOT.$ext'),
|
||||
};
|
||||
|
||||
return load(
|
||||
version: detectedVersion,
|
||||
dataIdentity: dataIdentity,
|
||||
vswap: vswap,
|
||||
mapHead: rawFiles['MAPHEAD.$ext']!,
|
||||
gameMaps: gameMapsData,
|
||||
vgaDict: rawFiles['VGADICT.$ext']!,
|
||||
vgaHead: rawFiles['VGAHEAD.$ext']!,
|
||||
vgaGraph: rawFiles['VGAGRAPH.$ext']!,
|
||||
audioHed: rawFiles['AUDIOHED.$ext']!,
|
||||
audioT: rawFiles['AUDIOT.$ext']!,
|
||||
registryOverride: registryOverride,
|
||||
);
|
||||
}
|
||||
|
||||
/// Parses all raw ByteData upfront and returns a fully populated [WolfensteinData] object.
|
||||
///
|
||||
/// By using named parameters, the compiler guarantees no files are missing or misnamed.
|
||||
@@ -115,28 +178,61 @@ abstract class WLParser {
|
||||
required ByteData audioHed,
|
||||
required ByteData audioT,
|
||||
required DataVersion dataIdentity,
|
||||
AssetRegistry? registryOverride,
|
||||
}) {
|
||||
final isShareware = version == GameVersion.shareware;
|
||||
|
||||
// v1.0/1.1 used different HUD strings and had different secret wall bugs
|
||||
final isLegacy =
|
||||
dataIdentity == DataVersion.version10Retail ||
|
||||
dataIdentity == DataVersion.version11Retail;
|
||||
|
||||
final audio = parseAudio(audioHed, audioT, version);
|
||||
final vgaImages = parseVgaImages(vgaDict, vgaHead, vgaGraph);
|
||||
|
||||
// Resolve the appropriate registry for this data identity, unless the
|
||||
// caller has supplied an explicit override (e.g. a modded asset pack).
|
||||
final registry =
|
||||
registryOverride ??
|
||||
_resolveRegistry(
|
||||
version: version,
|
||||
dataVersion: dataIdentity,
|
||||
vgaImages: vgaImages,
|
||||
);
|
||||
|
||||
return WolfensteinData(
|
||||
version: version,
|
||||
dataVersion: dataIdentity,
|
||||
registry: registry,
|
||||
walls: parseWalls(vswap),
|
||||
sprites: parseSprites(vswap),
|
||||
sounds: parseSounds(vswap).map((bytes) => PcmSound(bytes)).toList(),
|
||||
episodes: parseEpisodes(mapHead, gameMaps, isShareware: isShareware),
|
||||
vgaImages: parseVgaImages(vgaDict, vgaHead, vgaGraph),
|
||||
vgaImages: vgaImages,
|
||||
adLibSounds: audio.adLib,
|
||||
music: audio.music,
|
||||
);
|
||||
}
|
||||
|
||||
/// Selects the registry for [version]/[dataVersion] and, for shareware,
|
||||
/// initialises the menu module's runtime image-offset heuristic.
|
||||
static AssetRegistry _resolveRegistry({
|
||||
required GameVersion version,
|
||||
required DataVersion dataVersion,
|
||||
required List<VgaImage> vgaImages,
|
||||
}) {
|
||||
final context = RegistrySelectionContext(
|
||||
gameVersion: version,
|
||||
dataVersion: dataVersion,
|
||||
);
|
||||
final registry = const BuiltInAssetRegistryResolver().resolve(context);
|
||||
|
||||
// Initialise the shareware menu heuristic now that images are available.
|
||||
if (registry is SharewareAssetRegistry) {
|
||||
final sizes = vgaImages
|
||||
.map((img) => (width: img.width, height: img.height))
|
||||
.toList();
|
||||
registry.sharewareMenu.initWithImageSizes(sizes);
|
||||
}
|
||||
|
||||
return registry;
|
||||
}
|
||||
|
||||
/// Extracts the 64x64 wall textures from the VSWAP file.
|
||||
///
|
||||
/// Wall textures are stored sequentially at the beginning of the chunks array.
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:crypto/crypto.dart'; // Import for MD5
|
||||
import 'package:wolf_3d_dart/src/data/data_version.dart'; // Import your enum
|
||||
import 'package:crypto/crypto.dart';
|
||||
import 'package:wolf_3d_dart/wolf_3d_data_types.dart';
|
||||
|
||||
import 'io/discovery_stub.dart'
|
||||
@@ -33,6 +32,10 @@ class WolfensteinLoader {
|
||||
/// Use this for Web or Flutter assets where automated directory scanning
|
||||
/// is not possible. It automatically detects the [DataVersion] (v1.0, v1.4, etc.)
|
||||
/// by hashing the [vswap] buffer.
|
||||
///
|
||||
/// Supply [registryOverride] to use a fully custom [AssetRegistry] instead
|
||||
/// of the built-in version-detected registry. This is the primary extension
|
||||
/// point for modded or custom asset packs.
|
||||
static WolfensteinData loadFromBytes({
|
||||
required GameVersion version,
|
||||
required ByteData? vswap,
|
||||
@@ -43,6 +46,7 @@ class WolfensteinLoader {
|
||||
required ByteData? vgaGraph,
|
||||
required ByteData? audioHed,
|
||||
required ByteData? audioT,
|
||||
AssetRegistry? registryOverride,
|
||||
}) {
|
||||
// 1. Validation Check
|
||||
final Map<String, ByteData?> files = {
|
||||
@@ -67,7 +71,7 @@ class WolfensteinLoader {
|
||||
);
|
||||
}
|
||||
|
||||
// 2. Identify identity for specialized parsing (e.g., MAPTEMP vs GAMEMAPS)
|
||||
// 2. Identify identity for specialised parsing (e.g., MAPTEMP vs GAMEMAPS).
|
||||
final vswapBytes = vswap!.buffer.asUint8List(
|
||||
vswap.offsetInBytes,
|
||||
vswap.lengthInBytes,
|
||||
@@ -75,10 +79,9 @@ class WolfensteinLoader {
|
||||
final hash = md5.convert(vswapBytes).toString();
|
||||
final dataIdentity = DataVersion.fromChecksum(hash);
|
||||
|
||||
// 3. Pass-through to parser with the detected identity
|
||||
// 3. Pass-through to parser with the detected identity and optional override.
|
||||
return WLParser.load(
|
||||
version: version,
|
||||
// Correctly identifies v1.0/1.1/1.4
|
||||
dataIdentity: dataIdentity,
|
||||
vswap: vswap,
|
||||
mapHead: mapHead!,
|
||||
@@ -88,6 +91,7 @@ class WolfensteinLoader {
|
||||
vgaGraph: vgaGraph!,
|
||||
audioHed: audioHed!,
|
||||
audioT: audioT!,
|
||||
registryOverride: registryOverride,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user