Files
wolf_dart/packages/wolf_3d_dart/lib/wolf_3d_menu.dart
Hans Kokx 57dde0f31c Refactor collectible handling and scoring system
- Introduced `CollectiblePickupContext` and `CollectiblePickupEffect` to streamline the collectible pickup logic in the Player class.
- Updated the `tryPickup` method in the Player class to utilize the new effect system for health, ammo, score, keys, and weapons.
- Refactored collectible classes to implement the new `tryCollect` method, returning a `CollectiblePickupEffect`.
- Enhanced enemy classes to expose score values based on their type.
- Added unit tests for scoring ownership and shareware menu module functionality.
- Updated rendering logic in various renderer classes to use the new mapped picture retrieval system.
- Improved the `WolfClassicMenuArt` class to utilize a more structured approach for image retrieval based on registry keys.

Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
2026-03-19 14:25:14 +01:00

212 lines
6.4 KiB
Dart

/// Shared menu helpers for Wolf3D hosts.
library;
import 'package:wolf_3d_dart/wolf_3d_data_types.dart';
/// Known VGA picture indexes used by the original Wolf3D control-panel menus.
///
/// Values below are picture-table indexes (not raw chunk ids).
/// For example, `C_CONTROLPIC` is chunk 26 in `GFXV_WL6.H`, so its picture
/// index is `26 - STARTPICS(3) = 23`.
abstract class WolfMenuPic {
static const int hBj = 0; // H_BJPIC
static const int hTopWindow = 3; // H_TOPWINDOWPIC
static const int cOptions = 7; // C_OPTIONSPIC
static const int cCursor1 = 8; // C_CURSOR1PIC
static const int cCursor2 = 9; // C_CURSOR2PIC
static const int cNotSelected = 10; // C_NOTSELECTEDPIC
static const int cSelected = 11; // C_SELECTEDPIC
static const int cBabyMode = 16; // C_BABYMODEPIC
static const int cEasy = 17; // C_EASYPIC
static const int cNormal = 18; // C_NORMALPIC
static const int cHard = 19; // C_HARDPIC
static const int cControl = 23; // C_CONTROLPIC
static const int cEpisode1 = 27; // C_EPISODE1PIC
static const int cEpisode2 = 28; // C_EPISODE2PIC
static const int cEpisode3 = 29; // C_EPISODE3PIC
static const int cEpisode4 = 30; // C_EPISODE4PIC
static const int cEpisode5 = 31; // C_EPISODE5PIC
static const int cEpisode6 = 32; // C_EPISODE6PIC
static const int statusBar = 83; // STATUSBARPIC
static const int title = 84; // TITLEPIC
static const int pg13 = 85; // PG13PIC
static const int credits = 86; // CREDITSPIC
static const int highScores = 87; // HIGHSCORESPIC
static const List<int> episodePics = [
cEpisode1,
cEpisode2,
cEpisode3,
cEpisode4,
cEpisode5,
cEpisode6,
];
}
/// Shared menu text colors resolved from the VGA palette.
///
/// Keep menu color choices centralized so renderers don't duplicate
/// hard-coded palette slots or RGB conversion logic.
abstract class WolfMenuPalette {
static const int selectedTextIndex = 19;
static const int unselectedTextIndex = 23;
static const int _headerTargetRgb = 0xFFF700;
static int? _cachedHeaderTextIndex;
static int get headerTextIndex =>
_cachedHeaderTextIndex ??= _nearestPaletteIndex(_headerTargetRgb);
static int get selectedTextColor => ColorPalette.vga32Bit[selectedTextIndex];
static int get unselectedTextColor =>
ColorPalette.vga32Bit[unselectedTextIndex];
static int get headerTextColor => ColorPalette.vga32Bit[headerTextIndex];
static int _nearestPaletteIndex(int rgb) {
final int targetR = (rgb >> 16) & 0xFF;
final int targetG = (rgb >> 8) & 0xFF;
final int targetB = rgb & 0xFF;
int bestIndex = 0;
int bestDistance = 1 << 30;
for (int i = 0; i < 256; i++) {
final int color = ColorPalette.vga32Bit[i];
final int r = color & 0xFF;
final int g = (color >> 8) & 0xFF;
final int b = (color >> 16) & 0xFF;
final int dr = targetR - r;
final int dg = targetG - g;
final int db = targetB - b;
final int distance = (dr * dr) + (dg * dg) + (db * db);
if (distance < bestDistance) {
bestDistance = distance;
bestIndex = i;
}
}
return bestIndex;
}
}
/// Structured accessors for classic Wolf3D menu art.
class WolfClassicMenuArt {
final WolfensteinData data;
WolfClassicMenuArt(this.data);
VgaImage? get controlBackground {
return _imageForKey(MenuPicKey.controlBackground);
}
VgaImage? get title => _imageForKey(MenuPicKey.title);
VgaImage? get heading => _imageForKey(MenuPicKey.heading);
VgaImage? get selectedMarker => _imageForKey(MenuPicKey.markerSelected);
VgaImage? get unselectedMarker => _imageForKey(MenuPicKey.markerUnselected);
VgaImage? get optionsLabel => _imageForKey(MenuPicKey.optionsLabel);
VgaImage? get credits => _imageForKey(MenuPicKey.credits);
VgaImage? episodeOption(int episodeIndex) {
if (episodeIndex < 0) {
return null;
}
final key = data.registry.menu.episodeKey(episodeIndex);
return _imageForKey(key);
}
VgaImage? difficultyOption(Difficulty difficulty) {
final key = data.registry.menu.difficultyKey(difficulty);
return _imageForKey(key);
}
/// Legacy numeric lookup API retained for existing renderer call sites.
///
/// Known legacy indices are mapped through symbolic registry keys first.
/// Unknown indices fall back to direct picture-table indexing.
VgaImage? mappedPic(int index) {
final key = _legacyKeyForIndex(index);
if (key != null) {
return _imageForKey(key);
}
return pic(index);
}
VgaImage? pic(int index) {
if (index < 0 || index >= data.vgaImages.length) {
return null;
}
final image = data.vgaImages[index];
if (image.width <= 0 || image.height <= 0) {
return null;
}
return image;
}
VgaImage? _imageForKey(MenuPicKey key) {
final ref = data.registry.menu.resolve(key);
if (ref == null) {
return null;
}
return pic(ref.pictureIndex);
}
MenuPicKey? _legacyKeyForIndex(int index) {
switch (index) {
case WolfMenuPic.hTopWindow:
return MenuPicKey.heading;
case WolfMenuPic.cOptions:
return MenuPicKey.optionsLabel;
case WolfMenuPic.cCursor1:
return MenuPicKey.cursorActive;
case WolfMenuPic.cCursor2:
return MenuPicKey.cursorInactive;
case WolfMenuPic.cNotSelected:
return MenuPicKey.markerUnselected;
case WolfMenuPic.cSelected:
return MenuPicKey.markerSelected;
case 15:
return MenuPicKey.footer;
case WolfMenuPic.cBabyMode:
return MenuPicKey.difficultyBaby;
case WolfMenuPic.cEasy:
return MenuPicKey.difficultyEasy;
case WolfMenuPic.cNormal:
return MenuPicKey.difficultyNormal;
case WolfMenuPic.cHard:
return MenuPicKey.difficultyHard;
case WolfMenuPic.cControl:
return MenuPicKey.controlBackground;
case WolfMenuPic.cEpisode1:
return MenuPicKey.episode1;
case WolfMenuPic.cEpisode2:
return MenuPicKey.episode2;
case WolfMenuPic.cEpisode3:
return MenuPicKey.episode3;
case WolfMenuPic.cEpisode4:
return MenuPicKey.episode4;
case WolfMenuPic.cEpisode5:
return MenuPicKey.episode5;
case WolfMenuPic.cEpisode6:
return MenuPicKey.episode6;
case WolfMenuPic.title:
return MenuPicKey.title;
case WolfMenuPic.pg13:
return MenuPicKey.pg13;
case WolfMenuPic.credits:
return MenuPicKey.credits;
default:
return null;
}
}
}