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>
This commit is contained in:
2026-03-19 14:25:14 +01:00
parent 6e53da7095
commit 57dde0f31c
15 changed files with 584 additions and 223 deletions

View File

@@ -98,103 +98,45 @@ class WolfClassicMenuArt {
WolfClassicMenuArt(this.data);
int? _resolvedIndexOffset;
VgaImage? get controlBackground {
final preferred = mappedPic(WolfMenuPic.cControl);
if (_looksLikeMenuBackdrop(preferred)) {
return preferred;
}
// Older data layouts may shift/control-panel art around nearby indices.
for (int delta = -4; delta <= 4; delta++) {
final candidate = mappedPic(WolfMenuPic.cControl + delta);
if (_looksLikeMenuBackdrop(candidate)) {
return candidate;
}
}
return preferred;
return _imageForKey(MenuPicKey.controlBackground);
}
VgaImage? get title => mappedPic(WolfMenuPic.title);
VgaImage? get title => _imageForKey(MenuPicKey.title);
VgaImage? get heading => mappedPic(WolfMenuPic.hTopWindow);
VgaImage? get heading => _imageForKey(MenuPicKey.heading);
VgaImage? get selectedMarker => mappedPic(WolfMenuPic.cSelected);
VgaImage? get selectedMarker => _imageForKey(MenuPicKey.markerSelected);
VgaImage? get unselectedMarker => mappedPic(WolfMenuPic.cNotSelected);
VgaImage? get unselectedMarker => _imageForKey(MenuPicKey.markerUnselected);
VgaImage? get optionsLabel => mappedPic(WolfMenuPic.cOptions);
VgaImage? get optionsLabel => _imageForKey(MenuPicKey.optionsLabel);
VgaImage? get credits => mappedPic(WolfMenuPic.credits);
VgaImage? get credits => _imageForKey(MenuPicKey.credits);
VgaImage? episodeOption(int episodeIndex) {
if (episodeIndex < 0 || episodeIndex >= WolfMenuPic.episodePics.length) {
if (episodeIndex < 0) {
return null;
}
return mappedPic(WolfMenuPic.episodePics[episodeIndex]);
final key = data.registry.menu.episodeKey(episodeIndex);
return _imageForKey(key);
}
VgaImage? difficultyOption(Difficulty difficulty) {
switch (difficulty) {
case Difficulty.baby:
return mappedPic(WolfMenuPic.cBabyMode);
case Difficulty.easy:
return mappedPic(WolfMenuPic.cEasy);
case Difficulty.medium:
return mappedPic(WolfMenuPic.cNormal);
case Difficulty.hard:
return mappedPic(WolfMenuPic.cHard);
}
final key = data.registry.menu.difficultyKey(difficulty);
return _imageForKey(key);
}
/// Returns [index] after applying a detected version/layout offset.
/// 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) {
return pic(index + _indexOffset);
}
int get _indexOffset {
if (_resolvedIndexOffset != null) {
return _resolvedIndexOffset!;
final key = _legacyKeyForIndex(index);
if (key != null) {
return _imageForKey(key);
}
// Retail and shareware generally place STATUSBAR/TITLE/PG13/CREDITS as a
// contiguous block. If files are from a different release, infer a shift.
for (int i = 0; i < data.vgaImages.length - 3; i++) {
final status = data.vgaImages[i];
if (!_looksLikeStatusBar(status)) {
continue;
}
final title = data.vgaImages[i + 1];
final pg13 = data.vgaImages[i + 2];
final credits = data.vgaImages[i + 3];
if (_looksLikeFullScreen(title) &&
_looksLikeFullScreen(pg13) &&
_looksLikeFullScreen(credits)) {
_resolvedIndexOffset = i - WolfMenuPic.statusBar;
return _resolvedIndexOffset!;
}
}
_resolvedIndexOffset = 0;
return 0;
}
bool _looksLikeStatusBar(VgaImage image) {
return image.width >= 280 && image.height >= 24 && image.height <= 64;
}
bool _looksLikeFullScreen(VgaImage image) {
return image.width >= 280 && image.height >= 140;
}
bool _looksLikeMenuBackdrop(VgaImage? image) {
if (image == null) {
return false;
}
return image.width >= 180 && image.height >= 100;
return pic(index);
}
VgaImage? pic(int index) {
@@ -203,14 +145,67 @@ class WolfClassicMenuArt {
}
final image = data.vgaImages[index];
// Ignore known gameplay HUD art in menu composition.
if (index == WolfMenuPic.statusBar + _indexOffset) {
return null;
}
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;
}
}
}