Move initialization and access to a singleton
Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
@@ -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_3d_synth/wolf_3d_synth.dart';
|
||||||
import 'package:wolf_dart/features/difficulty/difficulty.dart';
|
import 'package:wolf_dart/features/difficulty/difficulty.dart';
|
||||||
import 'package:wolf_dart/features/renderer/renderer.dart';
|
import 'package:wolf_dart/features/renderer/renderer.dart';
|
||||||
|
import 'package:wolf_dart/wolf_3d.dart';
|
||||||
|
|
||||||
class DifficultyScreen extends StatefulWidget {
|
class DifficultyScreen extends StatefulWidget {
|
||||||
const DifficultyScreen(
|
const DifficultyScreen({
|
||||||
this.data, {
|
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
final WolfensteinData data;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<DifficultyScreen> createState() => _DifficultyScreenState();
|
State<DifficultyScreen> createState() => _DifficultyScreenState();
|
||||||
}
|
}
|
||||||
@@ -21,7 +19,7 @@ class _DifficultyScreenState extends State<DifficultyScreen> {
|
|||||||
AudioSource? _menuMusicSource;
|
AudioSource? _menuMusicSource;
|
||||||
SoundHandle? _menuMusicHandle;
|
SoundHandle? _menuMusicHandle;
|
||||||
|
|
||||||
bool get isShareware => widget.data.version == GameVersion.shareware;
|
bool get isShareware => Wolf3d.I.activeGame.version == GameVersion.shareware;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@@ -37,9 +35,9 @@ class _DifficultyScreenState extends State<DifficultyScreen> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 2. We only want to play music if the IMF data actually exists
|
// 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")
|
// 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
|
// Render the hardware instructions into PCM and wrap in a WAV header
|
||||||
final pcmSamples = ImfRenderer.render(music);
|
final pcmSamples = ImfRenderer.render(music);
|
||||||
@@ -85,7 +83,7 @@ class _DifficultyScreenState extends State<DifficultyScreen> {
|
|||||||
Navigator.of(context).pushReplacement(
|
Navigator.of(context).pushReplacement(
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (_) => WolfRenderer(
|
builder: (_) => WolfRenderer(
|
||||||
widget.data,
|
Wolf3d.I.activeGame,
|
||||||
difficulty: difficulty,
|
difficulty: difficulty,
|
||||||
isShareware: isShareware,
|
isShareware: isShareware,
|
||||||
showSpriteGallery: showGallery,
|
showSpriteGallery: showGallery,
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:flutter/material.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_3d_data_types/wolf_3d_data_types.dart';
|
||||||
import 'package:wolf_dart/features/difficulty/difficulty_screen.dart';
|
import 'package:wolf_dart/features/difficulty/difficulty_screen.dart';
|
||||||
|
import 'package:wolf_dart/wolf_3d.dart';
|
||||||
|
|
||||||
class GameSelectScreen extends StatelessWidget {
|
class GameSelectScreen extends StatelessWidget {
|
||||||
const GameSelectScreen({super.key});
|
const GameSelectScreen({super.key});
|
||||||
@@ -11,115 +9,27 @@ class GameSelectScreen extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: FutureBuilder(
|
body: ListView.builder(
|
||||||
future: loadData(),
|
itemCount: Wolf3d.I.availableGames.length,
|
||||||
builder: (context, snapshot) {
|
itemBuilder: (context, i) {
|
||||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
final WolfensteinData data = Wolf3d.I.availableGames[i];
|
||||||
return CircularProgressIndicator();
|
final GameVersion version = data.version;
|
||||||
}
|
|
||||||
|
|
||||||
if (!snapshot.hasData) {
|
return Card(
|
||||||
return Text("Unable to load data");
|
child: ListTile(
|
||||||
}
|
title: Text(version.name),
|
||||||
|
onTap: () {
|
||||||
final List<WolfensteinData> loadedGames = snapshot.data!;
|
Wolf3d.I.setActiveGame(data);
|
||||||
|
Navigator.of(context).push(
|
||||||
if (loadedGames.length == 1) {
|
MaterialPageRoute(
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
builder: (context) => DifficultyScreen(),
|
||||||
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),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<ByteData?> tryLoad(String path) async {
|
|
||||||
try {
|
|
||||||
return await rootBundle.load(path);
|
|
||||||
} catch (e) {
|
|
||||||
debugPrint("Asset not found: $path");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<WolfensteinData>> loadData({String? directory}) async {
|
|
||||||
final List<WolfensteinData> 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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_soloud/flutter_soloud.dart';
|
import 'package:flutter_soloud/flutter_soloud.dart';
|
||||||
import 'package:wolf_dart/game_select_screen.dart';
|
import 'package:wolf_dart/game_select_screen.dart';
|
||||||
|
import 'package:wolf_dart/wolf_3d.dart';
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
@@ -11,6 +12,8 @@ void main() async {
|
|||||||
channels: Channels.stereo,
|
channels: Channels.stereo,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await Wolf3d.I.init();
|
||||||
|
|
||||||
runApp(
|
runApp(
|
||||||
const MaterialApp(
|
const MaterialApp(
|
||||||
home: GameSelectScreen(),
|
home: GameSelectScreen(),
|
||||||
|
|||||||
96
lib/wolf_3d.dart
Normal file
96
lib/wolf_3d.dart
Normal file
@@ -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<WolfensteinData> 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<WolfLevel> get levels => activeGame.levels;
|
||||||
|
List<Sprite> get walls => activeGame.walls;
|
||||||
|
List<Sprite> get sprites => activeGame.sprites;
|
||||||
|
List<PcmSound> get sounds => activeGame.sounds;
|
||||||
|
List<AdLibSound> get adLibSounds => activeGame.adLibSounds;
|
||||||
|
List<ImfMusic> get music => activeGame.music;
|
||||||
|
List<VgaImage> get vgaImages => activeGame.vgaImages;
|
||||||
|
|
||||||
|
// --- Actions ---
|
||||||
|
void setActiveGame(WolfensteinData game) {
|
||||||
|
_activeGame = game;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initializes the engine by loading available game data.
|
||||||
|
Future<void> 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<ByteData?> _tryLoad(String path) async {
|
||||||
|
try {
|
||||||
|
return await rootBundle.load(path);
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint("Asset not found: $path");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,4 +3,4 @@
|
|||||||
/// More dartdocs go here.
|
/// More dartdocs go here.
|
||||||
library;
|
library;
|
||||||
|
|
||||||
export 'src/imf_renderer.dart' show ImfRenderer, NumChannels;
|
export 'src/imf_renderer.dart' show ImfRenderer;
|
||||||
|
|||||||
Reference in New Issue
Block a user