diff --git a/lib/features/difficulty/difficulty_screen.dart b/lib/features/difficulty/difficulty_screen.dart index b8957a0..ecd4956 100644 --- a/lib/features/difficulty/difficulty_screen.dart +++ b/lib/features/difficulty/difficulty_screen.dart @@ -4,15 +4,13 @@ import 'package:wolf_3d_data_types/wolf_3d_data_types.dart'; import 'package:wolf_3d_synth/wolf_3d_synth.dart'; import 'package:wolf_dart/features/difficulty/difficulty.dart'; import 'package:wolf_dart/features/renderer/renderer.dart'; +import 'package:wolf_dart/wolf_3d.dart'; class DifficultyScreen extends StatefulWidget { - const DifficultyScreen( - this.data, { + const DifficultyScreen({ super.key, }); - final WolfensteinData data; - @override State createState() => _DifficultyScreenState(); } @@ -21,7 +19,7 @@ class _DifficultyScreenState extends State { AudioSource? _menuMusicSource; SoundHandle? _menuMusicHandle; - bool get isShareware => widget.data.version == GameVersion.shareware; + bool get isShareware => Wolf3d.I.activeGame.version == GameVersion.shareware; @override void initState() { @@ -37,9 +35,9 @@ class _DifficultyScreenState extends State { } // 2. We only want to play music if the IMF data actually exists - if (widget.data.music.isNotEmpty) { + if (Wolf3d.I.music.isNotEmpty) { // Get the first track (usually the menu theme "Wondering About My Loved Ones") - final music = widget.data.music.first; + final music = Wolf3d.I.music.first; // Render the hardware instructions into PCM and wrap in a WAV header final pcmSamples = ImfRenderer.render(music); @@ -85,7 +83,7 @@ class _DifficultyScreenState extends State { Navigator.of(context).pushReplacement( MaterialPageRoute( builder: (_) => WolfRenderer( - widget.data, + Wolf3d.I.activeGame, difficulty: difficulty, isShareware: isShareware, showSpriteGallery: showGallery, diff --git a/lib/game_select_screen.dart b/lib/game_select_screen.dart index 68abc42..a325926 100644 --- a/lib/game_select_screen.dart +++ b/lib/game_select_screen.dart @@ -1,9 +1,7 @@ -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:wolf_3d_data/wolf_3d_data.dart'; import 'package:wolf_3d_data_types/wolf_3d_data_types.dart'; import 'package:wolf_dart/features/difficulty/difficulty_screen.dart'; +import 'package:wolf_dart/wolf_3d.dart'; class GameSelectScreen extends StatelessWidget { const GameSelectScreen({super.key}); @@ -11,115 +9,27 @@ class GameSelectScreen extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( - body: FutureBuilder( - future: loadData(), - builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.waiting) { - return CircularProgressIndicator(); - } + body: ListView.builder( + itemCount: Wolf3d.I.availableGames.length, + itemBuilder: (context, i) { + final WolfensteinData data = Wolf3d.I.availableGames[i]; + final GameVersion version = data.version; - if (!snapshot.hasData) { - return Text("Unable to load data"); - } - - final List loadedGames = snapshot.data!; - - if (loadedGames.length == 1) { - WidgetsBinding.instance.addPostFrameCallback((_) { - Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => DifficultyScreen(loadedGames.first), - ), - ); - }); - } - - return ListView.builder( - itemCount: loadedGames.length, - itemBuilder: (context, i) { - final WolfensteinData data = loadedGames[i]; - final GameVersion version = data.version; - - return Card( - child: ListTile( - title: Text(version.name), - onTap: () { - Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => DifficultyScreen(data), - ), - ); - }, - ), - ); - }, + return Card( + child: ListTile( + title: Text(version.name), + onTap: () { + Wolf3d.I.setActiveGame(data); + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => DifficultyScreen(), + ), + ); + }, + ), ); }, ), ); } - - Future tryLoad(String path) async { - try { - return await rootBundle.load(path); - } catch (e) { - debugPrint("Asset not found: $path"); - return null; - } - } - - Future> loadData({String? directory}) async { - final List loadedGames = []; - - // 1. Always attempt to load bundled assets first (works on ALL platforms) - final versionsToTry = [ - (version: GameVersion.retail, path: 'retail'), - (version: GameVersion.shareware, path: 'shareware'), - ]; - - for (final version in versionsToTry) { - try { - final ext = version.version.fileExtension; - final folder = 'assets/${version.path}'; - - final data = WolfensteinLoader.loadFromBytes( - version: version.version, - vswap: await tryLoad('$folder/VSWAP.$ext'), - mapHead: await tryLoad('$folder/MAPHEAD.$ext'), - gameMaps: await tryLoad('$folder/GAMEMAPS.$ext'), - vgaDict: await tryLoad('$folder/VGADICT.$ext'), - vgaHead: await tryLoad('$folder/VGAHEAD.$ext'), - vgaGraph: await tryLoad('$folder/VGAGRAPH.$ext'), - audioHed: await tryLoad('$folder/AUDIOHED.$ext'), - audioT: await tryLoad('$folder/AUDIOT.$ext'), - ); - - loadedGames.add(data); - } catch (e) { - // The loader now provides the specific error: - // "ArgumentError: Cannot load retail: Missing files: VSWAP.WL6, ..." - debugPrint(e.toString()); - } - } - - // 2. On non-web, also check for external files in a specific "games" folder - // if you want to support side-loading. - if (!kIsWeb) { - try { - final externalGames = await WolfensteinLoader.discover( - directoryPath: directory, - recursive: true, - ); - for (var entry in externalGames.entries) { - if (!loadedGames.any((g) => g.version == entry.key)) { - loadedGames.add(entry.value); - } - } - } catch (e) { - debugPrint("External discovery failed: $e"); - } - } - - return loadedGames; - } } diff --git a/lib/main.dart b/lib/main.dart index 7ed25e3..39f529b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_soloud/flutter_soloud.dart'; import 'package:wolf_dart/game_select_screen.dart'; +import 'package:wolf_dart/wolf_3d.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); @@ -11,6 +12,8 @@ void main() async { channels: Channels.stereo, ); + await Wolf3d.I.init(); + runApp( const MaterialApp( home: GameSelectScreen(), diff --git a/lib/wolf_3d.dart b/lib/wolf_3d.dart new file mode 100644 index 0000000..130962f --- /dev/null +++ b/lib/wolf_3d.dart @@ -0,0 +1,96 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; +import 'package:wolf_3d_data/wolf_3d_data.dart'; +import 'package:wolf_3d_data_types/wolf_3d_data_types.dart'; + +class Wolf3d { + Wolf3d._(); + static final Wolf3d _instance = Wolf3d._(); + static Wolf3d get I => _instance; + + // --- State --- + final List availableGames = []; + WolfensteinData? _activeGame; + + // --- Getters --- + WolfensteinData get activeGame { + if (_activeGame == null) { + throw StateError("No active game selected. Call setActiveGame() first."); + } + return _activeGame!; + } + + // Convenience getters for the active game's assets + List get levels => activeGame.levels; + List get walls => activeGame.walls; + List get sprites => activeGame.sprites; + List get sounds => activeGame.sounds; + List get adLibSounds => activeGame.adLibSounds; + List get music => activeGame.music; + List get vgaImages => activeGame.vgaImages; + + // --- Actions --- + void setActiveGame(WolfensteinData game) { + _activeGame = game; + } + + /// Initializes the engine by loading available game data. + Future init({String? directory}) async { + availableGames.clear(); + + // 1. Bundle asset loading (migrated from GameSelectScreen) + final versionsToTry = [ + (version: GameVersion.retail, path: 'retail'), + (version: GameVersion.shareware, path: 'shareware'), + ]; + + for (final version in versionsToTry) { + try { + final ext = version.version.fileExtension; + final folder = 'assets/${version.path}'; + + final data = WolfensteinLoader.loadFromBytes( + version: version.version, + vswap: await _tryLoad('$folder/VSWAP.$ext'), + mapHead: await _tryLoad('$folder/MAPHEAD.$ext'), + gameMaps: await _tryLoad('$folder/GAMEMAPS.$ext'), + vgaDict: await _tryLoad('$folder/VGADICT.$ext'), + vgaHead: await _tryLoad('$folder/VGAHEAD.$ext'), + vgaGraph: await _tryLoad('$folder/VGAGRAPH.$ext'), + audioHed: await _tryLoad('$folder/AUDIOHED.$ext'), + audioT: await _tryLoad('$folder/AUDIOT.$ext'), + ); + + availableGames.add(data); + } catch (e) { + debugPrint(e.toString()); + } + } + + // 2. External side-loading (non-web) + if (!kIsWeb) { + try { + final externalGames = await WolfensteinLoader.discover( + directoryPath: directory, + recursive: true, + ); + for (var entry in externalGames.entries) { + if (!availableGames.any((g) => g.version == entry.key)) { + availableGames.add(entry.value); + } + } + } catch (e) { + debugPrint("External discovery failed: $e"); + } + } + } + + Future _tryLoad(String path) async { + try { + return await rootBundle.load(path); + } catch (e) { + debugPrint("Asset not found: $path"); + return null; + } + } +} diff --git a/packages/wolf_3d_synth/lib/wolf_3d_synth.dart b/packages/wolf_3d_synth/lib/wolf_3d_synth.dart index 33645b0..5c15b0c 100644 --- a/packages/wolf_3d_synth/lib/wolf_3d_synth.dart +++ b/packages/wolf_3d_synth/lib/wolf_3d_synth.dart @@ -3,4 +3,4 @@ /// More dartdocs go here. library; -export 'src/imf_renderer.dart' show ImfRenderer, NumChannels; +export 'src/imf_renderer.dart' show ImfRenderer;