/// High-level Flutter facade for discovering game data and sharing runtime services. 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'; import 'package:wolf_3d_flutter/audio/wolf3d_platform_audio.dart'; 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'; import 'package:wolf_3d_flutter/wolf_3d_input_flutter.dart'; export 'package:wolf_3d_dart/wolf_3d_audio.dart' show DebugMusicPlayer; export 'audio/wolf3d_platform_audio.dart' show Wolf3dPlatformAudio; export 'managers/game_app_lifecycle_manager.dart' show GameAppLifecycleManager; export 'managers/game_data_directory_persistence.dart' show DefaultGameDataDirectoryPersistence; export 'managers/game_display_manager.dart' show GameDisplayManager; export 'managers/game_persistence_manager.dart' show GamePersistenceManager; export 'managers/game_renderer_mode_manager.dart' show GameRendererMode, gameRendererModeFromSettings, handleGlslUnavailable; export 'managers/game_screen_input_manager.dart' show HostShortcutBinding, HostShortcutHandler, HostShortcutIntent, HostShortcutRegistry, GameScreenInputManager, isAltEnterShortcut; export 'managers/wolf3d_app_manager.dart' show Wolf3dAppManager; export 'screens/audio_gallery.dart' show AudioGallery; export 'screens/debug_tools_screen.dart' show DebugToolsScreen; export 'screens/game_screen.dart' show GameScreen; export 'screens/no_game_data_screen.dart' show NoGameDataScreen; export 'screens/sprite_gallery.dart' show SpriteGallery; export 'screens/vga_gallery.dart' show VgaGallery; export 'widgets/gallery_game_selector.dart' show GalleryGameSelector, formatGalleryGameTitle; export 'widgets/wolf3d_app.dart' show Wolf3dApp; export 'widgets/wolf_menu_shell.dart' show WolfMenuShell; /// Flutter-specific host facade built on top of [Wolf3dEngine]. /// /// This type keeps platform-neutral session/engine state in the Dart package /// while owning Flutter-only concerns such as bundle loading and discovery. class Wolf3dFlutterEngine extends Wolf3dEngine { /// Creates an empty facade that must be initialized with [init]. Wolf3dFlutterEngine({ bool debug = false, EngineAudio? audioBackend, Wolf3dFlutterInput? inputBackend, DefaultGameDataDirectoryPersistence? dataDirectoryPersistence, }) : dataDirectoryPersistence = dataDirectoryPersistence ?? DefaultGameDataDirectoryPersistence(), super( audio: audioBackend ?? Wolf3dPlatformAudio(), input: inputBackend ?? Wolf3dFlutterInput(), ) { if (debug) { enableDebug(); } } /// Persists and restores the preferred external game-data directory. final DefaultGameDataDirectoryPersistence dataDirectoryPersistence; /// Last configured/loaded external game-data directory path. String? configuredDataDirectory; /// Shared Flutter input adapter reused by gameplay screens. @override Wolf3dFlutterInput get input => super.input as Wolf3dFlutterInput; /// Enables host-level debug affordances such as debug navigation UI. @override Wolf3dFlutterEngine enableDebug() { super.enableDebug(); return this; } /// Initializes the engine by loading available game data. Future init({ String? directory, Iterable? additionalDirectories, }) async { await desktop_windowing_support.ensureDesktopWindowingInitialized(); await audio.init(); availableGames.clear(); final String? requestedDirectory = directory?.trim(); final String? resolvedDirectory = requestedDirectory != null && requestedDirectory.isNotEmpty ? requestedDirectory : await dataDirectoryPersistence.loadDataDirectory(); configuredDataDirectory = resolvedDirectory; if (requestedDirectory != null && requestedDirectory.isNotEmpty) { await dataDirectoryPersistence.saveDataDirectory(requestedDirectory); } // Bundled assets let the GUI work out of the box on supported 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 = 'packages/wolf_3d_assets/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()); } } // On non-web platforms, also scan local filesystem locations for // user-supplied data folders so the host can pick up extra versions. final Set directoriesToScan = {}; if (resolvedDirectory != null && resolvedDirectory.isNotEmpty) { directoriesToScan.add(resolvedDirectory); } if (additionalDirectories != null) { for (final String directoryPath in additionalDirectories) { final String trimmedPath = directoryPath.trim(); if (trimmedPath.isNotEmpty) { directoriesToScan.add(trimmedPath); } } } if (!kIsWeb && directoriesToScan.isNotEmpty) { for (final String directoryPath in directoriesToScan) { try { final externalGames = await WolfensteinLoader.discover( directoryPath: directoryPath, 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"); } } } return this; } /// Loads an asset from the Flutter bundle, returning `null` when absent. Future _tryLoad(String path) async { try { return await rootBundle.load(path); } catch (e) { debugPrint("Asset not found: $path"); return null; } } }