feat: Implement packaged games loading and update engine initialization to support seeded games
Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
library;
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:wolf_3d_dart/wolf_3d_data.dart';
|
||||
import 'package:wolf_3d_dart/wolf_3d_data_types.dart';
|
||||
import 'package:wolf_3d_dart/wolf_3d_engine.dart';
|
||||
@@ -11,6 +12,8 @@ import 'package:wolf_3d_flutter/managers/desktop_windowing_support.dart'
|
||||
as desktop_windowing_support;
|
||||
import 'package:wolf_3d_flutter/managers/game_data_directory_persistence.dart';
|
||||
|
||||
typedef Wolf3dAssetByteLoader = Future<ByteData> Function(String assetKey);
|
||||
|
||||
/// Flutter-specific host facade built on top of [Wolf3dEngine].
|
||||
///
|
||||
/// This type keeps platform-neutral session and engine state in
|
||||
@@ -51,12 +54,105 @@ class Wolf3dFlutterEngine extends Wolf3dEngine {
|
||||
return this;
|
||||
}
|
||||
|
||||
/// Routes shared menu header band diagnostics to [logger].
|
||||
///
|
||||
/// Pass `null` to disable menu header band diagnostics.
|
||||
@override
|
||||
Wolf3dFlutterEngine setMenuHeaderBandDebugLogger(
|
||||
void Function(String message)? logger,
|
||||
) {
|
||||
super.setMenuHeaderBandDebugLogger(logger);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// Enables menu header band diagnostics with an optional [prefix].
|
||||
@override
|
||||
Wolf3dFlutterEngine enableMenuHeaderBandDebugLogging({
|
||||
String prefix = '[MENU_HEADER_BAND]',
|
||||
}) {
|
||||
super.enableMenuHeaderBandDebugLogging(prefix: prefix);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// Disables menu header band diagnostics.
|
||||
@override
|
||||
Wolf3dFlutterEngine disableMenuHeaderBandDebugLogging() {
|
||||
super.disableMenuHeaderBandDebugLogging();
|
||||
return this;
|
||||
}
|
||||
|
||||
/// Loads a game data set from Flutter packaged assets.
|
||||
///
|
||||
/// Intended for loading from dependency asset packages such as
|
||||
/// `wolf_3d_assets` by passing [packageName] and [assetDirectory], then
|
||||
/// supplying the result to [init] via [seededGames].
|
||||
static Future<WolfensteinData> loadGameDataFromAssets({
|
||||
required GameVersion version,
|
||||
required String assetDirectory,
|
||||
String? packageName = 'wolf_3d_assets',
|
||||
AssetRegistry? registryOverride,
|
||||
Wolf3dAssetByteLoader? assetLoader,
|
||||
}) async {
|
||||
final String ext = version.fileExtension;
|
||||
final Wolf3dAssetByteLoader loader = assetLoader ?? rootBundle.load;
|
||||
|
||||
final String normalizedDirectory = assetDirectory.trim().replaceAll(
|
||||
RegExp(r'^/+|/+$'),
|
||||
'',
|
||||
);
|
||||
if (normalizedDirectory.isEmpty) {
|
||||
throw ArgumentError.value(
|
||||
assetDirectory,
|
||||
'assetDirectory',
|
||||
'Must not be empty.',
|
||||
);
|
||||
}
|
||||
|
||||
String keyFor(String fileName) {
|
||||
final path = '$normalizedDirectory/$fileName';
|
||||
final String? normalizedPackage = packageName?.trim();
|
||||
if (normalizedPackage == null || normalizedPackage.isEmpty) {
|
||||
return path;
|
||||
}
|
||||
return 'packages/$normalizedPackage/$path';
|
||||
}
|
||||
|
||||
Future<ByteData> loadRequired(String fileName) {
|
||||
return loader(keyFor(fileName));
|
||||
}
|
||||
|
||||
Future<ByteData> loadWithFallback(
|
||||
String primaryName,
|
||||
String fallbackName,
|
||||
) async {
|
||||
try {
|
||||
return await loadRequired(primaryName);
|
||||
} catch (_) {
|
||||
return loadRequired(fallbackName);
|
||||
}
|
||||
}
|
||||
|
||||
return WolfensteinLoader.loadFromBytes(
|
||||
version: version,
|
||||
vswap: await loadRequired('VSWAP.$ext'),
|
||||
mapHead: await loadRequired('MAPHEAD.$ext'),
|
||||
gameMaps: await loadWithFallback('GAMEMAPS.$ext', 'MAPTEMP.$ext'),
|
||||
vgaDict: await loadRequired('VGADICT.$ext'),
|
||||
vgaHead: await loadRequired('VGAHEAD.$ext'),
|
||||
vgaGraph: await loadRequired('VGAGRAPH.$ext'),
|
||||
audioHed: await loadRequired('AUDIOHED.$ext'),
|
||||
audioT: await loadRequired('AUDIOT.$ext'),
|
||||
registryOverride: registryOverride,
|
||||
);
|
||||
}
|
||||
|
||||
/// Initializes the engine by loading available game data.
|
||||
///
|
||||
/// If [directory] is provided, it is persisted and treated as the primary
|
||||
/// external search root. If omitted, a previously persisted directory is
|
||||
/// used when available. [additionalDirectories] are scanned after the
|
||||
/// primary directory and are not persisted.
|
||||
/// primary directory and are not persisted. [seededGames] are merged first,
|
||||
/// enabling hosts to inject data from packaged assets.
|
||||
///
|
||||
/// This method scans only configured external directories, deduplicating
|
||||
/// discovered versions by [GameVersion]. Shared package code does not bundle
|
||||
@@ -64,10 +160,12 @@ class Wolf3dFlutterEngine extends Wolf3dEngine {
|
||||
Future<Wolf3dFlutterEngine> init({
|
||||
String? directory,
|
||||
Iterable<String>? additionalDirectories,
|
||||
Iterable<WolfensteinData>? seededGames,
|
||||
}) async {
|
||||
await desktop_windowing_support.ensureDesktopWindowingInitialized();
|
||||
await audio.init();
|
||||
availableGames.clear();
|
||||
_addUniqueGames(seededGames);
|
||||
|
||||
final String? requestedDirectory = directory?.trim();
|
||||
final String? resolvedDirectory =
|
||||
@@ -103,14 +201,7 @@ class Wolf3dFlutterEngine extends Wolf3dEngine {
|
||||
directoryPath: directoryPath,
|
||||
recursive: true,
|
||||
);
|
||||
for (final MapEntry<GameVersion, WolfensteinData> entry
|
||||
in externalGames.entries) {
|
||||
if (!availableGames.any(
|
||||
(WolfensteinData g) => g.version == entry.key,
|
||||
)) {
|
||||
availableGames.add(entry.value);
|
||||
}
|
||||
}
|
||||
_addUniqueGames(externalGames.values);
|
||||
} catch (e) {
|
||||
debugPrint('External discovery failed: $e');
|
||||
}
|
||||
@@ -119,4 +210,15 @@ class Wolf3dFlutterEngine extends Wolf3dEngine {
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
void _addUniqueGames(Iterable<WolfensteinData>? games) {
|
||||
if (games == null) {
|
||||
return;
|
||||
}
|
||||
for (final WolfensteinData game in games) {
|
||||
if (!availableGames.any((g) => g.version == game.version)) {
|
||||
availableGames.add(game);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user