Files
wolf_dart/packages/wolf_3d_dart
hans 5c309c2240 Refactor menu structure and add Flutter-specific input and persistence layers
- 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>
2026-03-24 18:45:34 +01:00
..

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:

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 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:

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 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:

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.