5c309c2240
- 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>
286 lines
8.6 KiB
Markdown
286 lines
8.6 KiB
Markdown
# 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-backed `WolfMenuPresentation` helpers for hosts.
|
|
- `wolf_3d_host.dart` — host-level glue contracts.
|
|
|
|
Implementation details live under `lib/src/`.
|
|
|
|
## Setup
|
|
|
|
From this directory:
|
|
|
|
```bash
|
|
dart pub get
|
|
```
|
|
|
|
## Development Commands
|
|
|
|
From this directory:
|
|
|
|
```bash
|
|
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 prefer `lib/wolf_3d_menu.dart` or the internal barrel at `lib/src/menu/menu_manager.dart` instead 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:
|
|
|
|
- `MenuPicModule` maps symbolic menu keys such as `MenuPicKey.title` or an episode selection entry to concrete VGA picture indices in `WolfensteinData.vgaImages`.
|
|
- `MenuPresentationModule` defines the palette indices and higher-level menu art lookups that renderers and hosts consume.
|
|
|
|
That split is intentional:
|
|
|
|
- `MenuPicModule` answers "which image index represents this menu asset for this game/mod?"
|
|
- `MenuPresentationModule` answers "which colors and optional art should the UI use?"
|
|
|
|
In practice, most custom variants will either:
|
|
|
|
- reuse an existing `MenuPicModule` and only change colors/presentation, or
|
|
- provide both a custom `MenuPicModule` and a matching `MenuPresentationModule` when 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:
|
|
|
|
```dart
|
|
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:
|
|
|
|
```dart
|
|
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:
|
|
|
|
```dart
|
|
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
|
|
|
|
```dart
|
|
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 `VgaImage` when that surface has variant-specific art,
|
|
- return `null` when 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:
|
|
|
|
```dart
|
|
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()` or `WolfMenuPresentation.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/` and `test/entities/`.
|
|
- **Host integration issues**: verify host packages/apps are using exported surfaces from `lib/` rather than private `src/` paths.
|
|
|
|
## Related Modules
|
|
|
|
- Flutter integration layer: [`../wolf_3d_flutter/README.md`](../wolf_3d_flutter/README.md)
|
|
- Shared packaged assets: [`../wolf_3d_assets/README.md`](../wolf_3d_assets/README.md)
|
|
- CLI host app: [`../../apps/wolf_3d_cli/README.md`](../../apps/wolf_3d_cli/README.md)
|
|
- GUI host app: [`../../apps/wolf_3d_gui/README.md`](../../apps/wolf_3d_gui/README.md)
|
|
- Workspace overview: [`../../README.md`](../../README.md)
|