/// 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 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; } } }