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:
@@ -21,13 +21,7 @@ void main() {
|
||||
|
||||
engine.init();
|
||||
engine.entities.add(
|
||||
Collectible(
|
||||
x: engine.player.x,
|
||||
y: engine.player.y,
|
||||
spriteIndex: 0,
|
||||
mapId: MapObject.ammoClip,
|
||||
type: CollectibleType.ammo,
|
||||
),
|
||||
AmmoCollectible(x: engine.player.x, y: engine.player.y),
|
||||
);
|
||||
engine.tick(const Duration(milliseconds: 16));
|
||||
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import 'package:test/test.dart';
|
||||
import 'package:wolf_3d_dart/wolf_3d_data_types.dart';
|
||||
import 'package:wolf_3d_dart/wolf_3d_entities.dart';
|
||||
|
||||
void main() {
|
||||
group('Scoring ownership', () {
|
||||
test('treasure collectibles expose score values', () {
|
||||
final cross = TreasureCollectible(x: 1, y: 1, mapId: MapObject.cross);
|
||||
final chalice = TreasureCollectible(
|
||||
x: 1,
|
||||
y: 1,
|
||||
mapId: MapObject.chalice,
|
||||
);
|
||||
final chest = TreasureCollectible(x: 1, y: 1, mapId: MapObject.chest);
|
||||
final crown = TreasureCollectible(x: 1, y: 1, mapId: MapObject.crown);
|
||||
final extraLife = TreasureCollectible(
|
||||
x: 1,
|
||||
y: 1,
|
||||
mapId: MapObject.extraLife,
|
||||
);
|
||||
|
||||
expect(cross.scoreValue, 100);
|
||||
expect(chalice.scoreValue, 500);
|
||||
expect(chest.scoreValue, 1000);
|
||||
expect(crown.scoreValue, 5000);
|
||||
expect(extraLife.scoreValue, 0);
|
||||
});
|
||||
|
||||
test('enemy instances expose score values from enemy type metadata', () {
|
||||
final guard = Guard(x: 1, y: 1, angle: 0, mapId: MapObject.guardStart);
|
||||
final dog = Dog(x: 1, y: 1, angle: 0, mapId: MapObject.dogStart);
|
||||
final ss = SS(x: 1, y: 1, angle: 0, mapId: MapObject.ssStart);
|
||||
|
||||
expect(guard.scoreValue, 100);
|
||||
expect(dog.scoreValue, 200);
|
||||
expect(ss.scoreValue, 100);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
import 'package:test/test.dart';
|
||||
import 'package:wolf_3d_dart/src/registry/built_in/shareware_menu_module.dart';
|
||||
import 'package:wolf_3d_dart/wolf_3d_data_types.dart';
|
||||
|
||||
void main() {
|
||||
group('SharewareMenuPicModule', () {
|
||||
test('uses fixed canonical WL1 mapping when strict mode is enabled', () {
|
||||
final module = SharewareMenuPicModule(useOriginalWl1Map: true);
|
||||
|
||||
expect(module.resolve(MenuPicKey.title)?.pictureIndex, 96);
|
||||
expect(module.resolve(MenuPicKey.pg13)?.pictureIndex, 97);
|
||||
expect(module.resolve(MenuPicKey.credits)?.pictureIndex, 98);
|
||||
expect(module.resolve(MenuPicKey.cursorActive)?.pictureIndex, 20);
|
||||
expect(module.resolve(MenuPicKey.footer)?.pictureIndex, 27);
|
||||
expect(module.resolve(MenuPicKey.difficultyEasy)?.pictureIndex, 29);
|
||||
expect(module.resolve(MenuPicKey.episode1)?.pictureIndex, 39);
|
||||
});
|
||||
|
||||
test('returns null for unavailable WL1 episode art in strict mode', () {
|
||||
final module = SharewareMenuPicModule(useOriginalWl1Map: true);
|
||||
|
||||
expect(module.resolve(MenuPicKey.episode2), isNull);
|
||||
expect(module.resolve(MenuPicKey.episode3), isNull);
|
||||
expect(module.resolve(MenuPicKey.episode4), isNull);
|
||||
expect(module.resolve(MenuPicKey.episode5), isNull);
|
||||
expect(module.resolve(MenuPicKey.episode6), isNull);
|
||||
});
|
||||
|
||||
test('heuristic mode only shifts statusbar-era indices', () {
|
||||
final module = SharewareMenuPicModule();
|
||||
final imageSizes = List.generate(
|
||||
90,
|
||||
(_) => (width: 64, height: 64),
|
||||
);
|
||||
|
||||
imageSizes[35] = (width: 320, height: 40); // STATUSBARPIC-like
|
||||
imageSizes[36] = (width: 320, height: 200); // TITLEPIC-like
|
||||
imageSizes[37] = (width: 320, height: 200); // PG13PIC-like
|
||||
imageSizes[38] = (width: 320, height: 200); // CREDITSPIC-like
|
||||
|
||||
module.initWithImageSizes(imageSizes);
|
||||
|
||||
// 84 shifted by -48.
|
||||
expect(module.resolve(MenuPicKey.title)?.pictureIndex, 36);
|
||||
// Control-panel assets should remain unshifted.
|
||||
expect(module.resolve(MenuPicKey.difficultyEasy)?.pictureIndex, 17);
|
||||
expect(module.resolve(MenuPicKey.episode1)?.pictureIndex, 27);
|
||||
});
|
||||
});
|
||||
|
||||
group('BuiltInAssetRegistryResolver shareware selection', () {
|
||||
test(
|
||||
'uses strict canonical WL1 map for exact known shareware identity',
|
||||
() {
|
||||
const resolver = BuiltInAssetRegistryResolver();
|
||||
|
||||
final registry = resolver.resolve(
|
||||
const RegistrySelectionContext(
|
||||
gameVersion: GameVersion.shareware,
|
||||
dataVersion: DataVersion.version14Shareware,
|
||||
),
|
||||
);
|
||||
|
||||
expect(registry.menu.resolve(MenuPicKey.title)?.pictureIndex, 96);
|
||||
expect(
|
||||
registry.menu.resolve(MenuPicKey.cursorActive)?.pictureIndex,
|
||||
20,
|
||||
);
|
||||
expect(registry.menu.resolve(MenuPicKey.episode2), isNull);
|
||||
},
|
||||
);
|
||||
|
||||
test('uses flexible shareware fallback for unknown identities', () {
|
||||
const resolver = BuiltInAssetRegistryResolver();
|
||||
|
||||
final registry = resolver.resolve(
|
||||
const RegistrySelectionContext(
|
||||
gameVersion: GameVersion.shareware,
|
||||
dataVersion: DataVersion.unknown,
|
||||
),
|
||||
);
|
||||
|
||||
// Unknown shareware falls back to baseline until image-size heuristic init.
|
||||
expect(registry.menu.resolve(MenuPicKey.title)?.pictureIndex, 84);
|
||||
expect(registry.menu.resolve(MenuPicKey.episode2)?.pictureIndex, 28);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:test/test.dart';
|
||||
import 'package:wolf_3d_dart/wolf_3d_data_types.dart';
|
||||
import 'package:wolf_3d_dart/wolf_3d_menu.dart';
|
||||
|
||||
void main() {
|
||||
test(
|
||||
'legacy numeric menu IDs are remapped through registry in shareware',
|
||||
() {
|
||||
final data = WolfensteinData(
|
||||
version: GameVersion.shareware,
|
||||
dataVersion: DataVersion.version14Shareware,
|
||||
registry: SharewareAssetRegistry(strictOriginalShareware: true),
|
||||
walls: const [],
|
||||
sprites: const [],
|
||||
sounds: const [],
|
||||
adLibSounds: const [],
|
||||
music: const [],
|
||||
episodes: const [],
|
||||
vgaImages: List.generate(120, (i) => _img(height: i + 1)),
|
||||
);
|
||||
|
||||
final art = WolfClassicMenuArt(data);
|
||||
|
||||
// Retail cursor constants 8/9 must resolve to shareware cursor IDs 20/21.
|
||||
expect(art.mappedPic(8)?.height, 21);
|
||||
expect(art.mappedPic(9)?.height, 22);
|
||||
|
||||
// Retail footer constant 15 must resolve to shareware footer ID 27.
|
||||
expect(art.mappedPic(15)?.height, 28);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
VgaImage _img({required int height}) {
|
||||
const width = 4;
|
||||
return VgaImage(
|
||||
width: width,
|
||||
height: height,
|
||||
pixels: Uint8List(width * height),
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user