- Moved menu-related classes to a new structure under `src/menu/`. - Introduced `WolfMenuPresentation` to handle menu art and mappings. - Added `MenuManager` tests to ensure menu state reflects game status. - Implemented `FlutterRendererSettingsPersistence` and `FlutterSaveGamePersistence` for managing settings and save files on desktop platforms. - Created `Wolf3dFlutterInput` to handle keyboard and mouse input in a Flutter environment. - Updated README to reflect new package structure and usage instructions. Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
8.6 KiB
wolf_3d_dart
Core non-Flutter Wolfenstein 3D runtime package used by both CLI and Flutter hosts.
What This Package Provides
wolf_3d_dart contains the platform-neutral simulation/runtime surface:
- engine and session lifecycle,
- game-data loading and data types,
- renderer backends and frame-buffer abstractions,
- menu state/navigation models,
- input/audio host abstractions,
- entity and gameplay logic.
Public Library Surfaces
Primary entry libraries in lib/:
wolf_3d_engine.dart— engine exports and runtime contracts.wolf_3d_data.dart/wolf_3d_data_types.dart— game-data discovery and DTOs.wolf_3d_renderer.dart— rendering/backends integration points.wolf_3d_audio.dart— audio interfaces and host backends.wolf_3d_input.dart— input abstractions.wolf_3d_menu.dart— menu models/managers and the registry-backedWolfMenuPresentationhelpers for hosts.wolf_3d_host.dart— host-level glue contracts.
Implementation details live under lib/src/.
Setup
From this directory:
dart pub get
Development Commands
From this directory:
dart analyze
dart test
Architecture Notes
- Hosts own platform concerns (windowing, lifecycle, platform input wiring).
- This package owns deterministic engine/frame progression and shared game logic.
- Frame-buffer sizing is controlled by hosts through engine APIs.
- Rendering code is maintained under
lib/src/rendering/. - Menu coordination is split under
lib/src/menu/manager/; public consumers should preferlib/wolf_3d_menu.dartor the internal barrel atlib/src/menu/menu_manager.dartinstead of reaching into individual implementation files. - Menu presentation is selected through
AssetRegistry.menuPresentation, which keeps retail/shareware/Spear variants and user-defined menu overrides aligned with the rest of the registry system.
Custom Menus
Custom menu support is split across two registry modules:
MenuPicModulemaps symbolic menu keys such asMenuPicKey.titleor an episode selection entry to concrete VGA picture indices inWolfensteinData.vgaImages.MenuPresentationModuledefines the palette indices and higher-level menu art lookups that renderers and hosts consume.
That split is intentional:
MenuPicModuleanswers "which image index represents this menu asset for this game/mod?"MenuPresentationModuleanswers "which colors and optional art should the UI use?"
In practice, most custom variants will either:
- reuse an existing
MenuPicModuleand only change colors/presentation, or - provide both a custom
MenuPicModuleand a matchingMenuPresentationModulewhen the menu art layout itself changes.
Using Menu Presentation From Loaded Data
Once game data has been loaded, bind menu presentation through the active registry:
final WolfMenuPresentation menu = WolfMenuPresentation(data);
final int panelColor = menu.panelColor;
final VgaImage? title = menu.title;
final VgaImage? episode1 = menu.episodeOption(0);
This is the normal path for renderers and any UI that should track the active game variant automatically.
Fallback Presentation Before Data Loads
Host-owned screens that appear before game data discovery can still use menu-consistent colors:
const WolfMenuPresentation classicMenu = WolfMenuPresentation.classic();
const WolfMenuPresentation spearMenu = WolfMenuPresentation.spear();
Those fallback constructors expose colors without requiring a loaded WolfensteinData instance. Art getters return null until real data is attached.
Implementing A Custom MenuPicModule
Use MenuPicModule when your mod changes which VGA pictures back the classic menu keys:
class ModMenuPics extends MenuPicModule {
const ModMenuPics();
@override
MenuPicRef? resolve(MenuPicKey key) {
switch (key) {
case MenuPicKey.title:
return const MenuPicRef(140);
case MenuPicKey.optionTitle:
return const MenuPicRef(141);
case MenuPicKey.customizeTitle:
return const MenuPicRef(142);
default:
return null;
}
}
@override
MenuPicKey episodeKey(int episodeIndex) {
switch (episodeIndex) {
case 0:
return MenuPicKey.episode1;
case 1:
return MenuPicKey.episode2;
case 2:
return MenuPicKey.episode3;
default:
return MenuPicKey.episode1;
}
}
@override
MenuPicKey difficultyKey(Difficulty difficulty) {
switch (difficulty) {
case Difficulty.easy:
return MenuPicKey.skill1;
case Difficulty.medium:
return MenuPicKey.skill2;
case Difficulty.hard:
return MenuPicKey.skill3;
case Difficulty.expert:
return MenuPicKey.skill4;
}
}
}
Returning null from resolve means that the key is not provided by that module.
Implementing A Custom MenuPresentationModule
Use MenuPresentationModule when you want to change menu colors, point existing menu concepts at different art, or selectively omit optional art:
Custom Menu Presentation Example
class ModMenuPresentation extends MenuPresentationModule {
const ModMenuPresentation();
@override
int get backgroundIndex => 111;
@override
int get panelIndex => 97;
@override
int get borderIndex => 87;
@override
int get emphasisIndex => 10;
@override
int get warningIndex => 14;
@override
int get mutedIndex => 8;
@override
int get selectedTextIndex => 19;
@override
int get unselectedTextIndex => 23;
@override
int get disabledTextIndex => 4;
@override
int get headerTextIndex => 15;
@override
VgaImage? controlBackground(WolfensteinData data) => null;
@override
VgaImage? title(WolfensteinData data) => null;
@override
VgaImage? heading(WolfensteinData data) => null;
@override
VgaImage? selectedMarker(WolfensteinData data) => null;
@override
VgaImage? unselectedMarker(WolfensteinData data) => null;
@override
VgaImage? optionsLabel(WolfensteinData data) => null;
@override
VgaImage? customizeLabel(WolfensteinData data) => null;
@override
VgaImage? credits(WolfensteinData data) => null;
@override
VgaImage? episodeOption(WolfensteinData data, int episodeIndex) => null;
@override
VgaImage? difficultyOption(WolfensteinData data, Difficulty difficulty) =>
null;
@override
VgaImage? mappedPic(WolfensteinData data, int index) => null;
}
final registry = AssetRegistry(
sfx: mySfxModule,
music: myMusicModule,
entities: myEntityModule,
hud: myHudModule,
menu: myMenuPicModule,
menuPresentation: const ModMenuPresentation(),
);
The presentation module should treat its image-returning methods as optional hooks:
- return a
VgaImagewhen that surface has variant-specific art, - return
nullwhen the presentation intentionally has no image for that concept, - use
mappedPic(...)only for legacy numeric menu art lookups that still matter for a renderer path.
Wiring A Fully Custom Registry
To ship a complete custom menu variant, provide both modules through AssetRegistry when loading data:
final registry = AssetRegistry(
sfx: mySfxModule,
music: myMusicModule,
entities: myEntityModule,
hud: myHudModule,
menu: const ModMenuPics(),
menuPresentation: const ModMenuPresentation(),
);
If your menu art still follows the built-in retail/shareware layout, you may not need a custom MenuPicModule. In that case, keep the built-in module and only swap menuPresentation.
Choosing The Right Extension Point
- Change colors only: implement
MenuPresentationModule. - Change symbolic menu art mapping: implement
MenuPicModule. - Change both colors and art layout: implement both modules.
- Build a host setup screen before data loads: use
WolfMenuPresentation.classic()orWolfMenuPresentation.spear().
For most host code, prefer the public wolf_3d_menu.dart surface instead of importing internal files directly.
Non-Goals
- Flutter widgets/screens are not part of this package.
- Bundled app assets are handled by
wolf_3d_assets.
Troubleshooting
- Parity regressions: run targeted tests under
test/engine/andtest/entities/. - Host integration issues: verify host packages/apps are using exported surfaces from
lib/rather than privatesrc/paths.
Related Modules
- Flutter integration layer:
../wolf_3d_flutter/README.md - Shared packaged assets:
../wolf_3d_assets/README.md - CLI host app:
../../apps/wolf_3d_cli/README.md - GUI host app:
../../apps/wolf_3d_gui/README.md - Workspace overview:
../../README.md