feat: Update HUD rendering to display current player lives dynamically
Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
@@ -38,12 +38,75 @@ void main() {
|
||||
expect(engine.player.lives, 5);
|
||||
expect(engine.player.hasMachineGun, isTrue);
|
||||
expect(engine.player.hasChainGun, isTrue);
|
||||
expect(engine.player.weapons[WeaponType.machineGun], isNotNull);
|
||||
expect(engine.player.weapons[WeaponType.chainGun], isNotNull);
|
||||
expect(engine.player.currentWeapon.type, WeaponType.chainGun);
|
||||
expect(engine.player.hasGoldKey, isFalse);
|
||||
expect(engine.player.hasSilverKey, isFalse);
|
||||
expect(engine.player.x, closeTo(4.5, 0.001));
|
||||
expect(engine.player.y, closeTo(4.5, 0.001));
|
||||
});
|
||||
|
||||
test('secret-exit transitions clear keys but preserve other player state', () {
|
||||
final input = _TestInput();
|
||||
final engine = WolfEngine(
|
||||
data: _buildSecretExitTestData(),
|
||||
difficulty: Difficulty.hard,
|
||||
startingEpisode: 0,
|
||||
frameBuffer: FrameBuffer(64, 64),
|
||||
input: input,
|
||||
engineAudio: _SilentAudio(),
|
||||
onGameWon: () {},
|
||||
);
|
||||
|
||||
engine.init();
|
||||
engine.player
|
||||
..health = 61
|
||||
..ammo = 42
|
||||
..score = 777
|
||||
..lives = 6
|
||||
..hasMachineGun = true
|
||||
..hasChainGun = true
|
||||
..hasGoldKey = true
|
||||
..hasSilverKey = true;
|
||||
engine.player.weapons[WeaponType.machineGun] = MachineGun();
|
||||
engine.player.weapons[WeaponType.chainGun] = ChainGun();
|
||||
engine.player.currentWeapon = engine.player.weapons[WeaponType.chainGun]!;
|
||||
|
||||
input.isInteracting = true;
|
||||
engine.tick(const Duration(milliseconds: 16));
|
||||
input.isInteracting = false;
|
||||
|
||||
expect(engine.activeLevel.name, 'Level 10');
|
||||
expect(engine.player.health, 61);
|
||||
expect(engine.player.ammo, 42);
|
||||
expect(engine.player.score, 777);
|
||||
expect(engine.player.lives, 6);
|
||||
expect(engine.player.hasMachineGun, isTrue);
|
||||
expect(engine.player.hasChainGun, isTrue);
|
||||
expect(engine.player.currentWeapon.type, WeaponType.chainGun);
|
||||
expect(engine.player.hasGoldKey, isFalse);
|
||||
expect(engine.player.hasSilverKey, isFalse);
|
||||
|
||||
engine.player
|
||||
..hasGoldKey = true
|
||||
..hasSilverKey = true;
|
||||
|
||||
input.isInteracting = true;
|
||||
engine.tick(const Duration(milliseconds: 16));
|
||||
input.isInteracting = false;
|
||||
|
||||
expect(engine.activeLevel.name, 'Level 2');
|
||||
expect(engine.player.health, 61);
|
||||
expect(engine.player.ammo, 42);
|
||||
expect(engine.player.score, 777);
|
||||
expect(engine.player.lives, 6);
|
||||
expect(engine.player.hasMachineGun, isTrue);
|
||||
expect(engine.player.hasChainGun, isTrue);
|
||||
expect(engine.player.currentWeapon.type, WeaponType.chainGun);
|
||||
expect(engine.player.hasGoldKey, isFalse);
|
||||
expect(engine.player.hasSilverKey, isFalse);
|
||||
});
|
||||
});
|
||||
|
||||
group('Pause and main menu', () {
|
||||
@@ -434,6 +497,49 @@ WolfensteinData _buildTestData({required GameVersion gameVersion}) {
|
||||
);
|
||||
}
|
||||
|
||||
WolfensteinData _buildSecretExitTestData() {
|
||||
final List<WolfLevel> levels = [];
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
final walls = _buildGrid();
|
||||
final objects = _buildGrid();
|
||||
_fillBoundaries(walls, 2);
|
||||
|
||||
objects[2][2] = MapObject.playerEast;
|
||||
|
||||
if (i == 0) {
|
||||
walls[2][3] = MapObject.secretElevatorSwitch;
|
||||
} else if (i == 9) {
|
||||
walls[2][3] = MapObject.normalElevatorSwitch;
|
||||
}
|
||||
|
||||
levels.add(
|
||||
WolfLevel(
|
||||
name: 'Level ${i + 1}',
|
||||
wallGrid: walls,
|
||||
areaGrid: List.generate(64, (_) => List.filled(64, -1)),
|
||||
objectGrid: objects,
|
||||
music: Music.level01,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return WolfensteinData(
|
||||
version: GameVersion.retail,
|
||||
dataVersion: DataVersion.unknown,
|
||||
registry: RetailAssetRegistry(),
|
||||
walls: [_solidSprite(1), _solidSprite(1), _solidSprite(2), _solidSprite(2)],
|
||||
sprites: List.generate(436, (_) => _solidSprite(255)),
|
||||
sounds: List.generate(200, (_) => PcmSound(Uint8List(1))),
|
||||
adLibSounds: const [],
|
||||
music: const [],
|
||||
vgaImages: const [],
|
||||
episodes: [
|
||||
Episode(name: 'Episode 1', levels: levels),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
class _TestInput extends Wolf3dInput {
|
||||
@override
|
||||
void update() {}
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:test/test.dart';
|
||||
import 'package:wolf_3d_dart/src/rendering/renderer_backend.dart';
|
||||
import 'package:wolf_3d_dart/wolf_3d_data_types.dart';
|
||||
import 'package:wolf_3d_dart/wolf_3d_engine.dart';
|
||||
import 'package:wolf_3d_dart/wolf_3d_input.dart';
|
||||
|
||||
void main() {
|
||||
test('standard VGA HUD renders current player lives value', () {
|
||||
final engine = _buildEngine();
|
||||
engine.init();
|
||||
engine.player.lives = 7;
|
||||
|
||||
final renderer = _HudProbeRenderer(vgaImages: engine.data.vgaImages);
|
||||
renderer.drawHudForTest(engine);
|
||||
|
||||
final expectedDigitIndex = engine.data.registry.hud
|
||||
.resolve(HudKey.digit7)
|
||||
?.vgaIndex;
|
||||
expect(expectedDigitIndex, isNotNull);
|
||||
|
||||
final livesDigitCall = renderer.drawCalls.firstWhere(
|
||||
(call) => call.startY == 176 && call.startX == 112,
|
||||
orElse: () => throw StateError('Lives digit was not rendered.'),
|
||||
);
|
||||
|
||||
expect(livesDigitCall.imageIndex, expectedDigitIndex);
|
||||
});
|
||||
}
|
||||
|
||||
class _HudProbeRenderer extends RendererBackend<int> {
|
||||
final List<VgaImage> vgaImages;
|
||||
final List<_HudDrawCall> drawCalls = <_HudDrawCall>[];
|
||||
|
||||
_HudProbeRenderer({required this.vgaImages});
|
||||
|
||||
void drawHudForTest(WolfEngine engine) {
|
||||
drawStandardVgaHud(engine);
|
||||
}
|
||||
|
||||
@override
|
||||
void blitHudVgaImage(VgaImage image, int startX320, int startY200) {
|
||||
drawCalls.add(
|
||||
_HudDrawCall(
|
||||
imageIndex: vgaImages.indexOf(image),
|
||||
startX: startX320,
|
||||
startY: startY200,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
int finalizeFrame() => 0;
|
||||
|
||||
@override
|
||||
void drawHud(WolfEngine engine) {}
|
||||
|
||||
@override
|
||||
void drawSpriteStripe(
|
||||
int stripeX,
|
||||
int drawStartY,
|
||||
int drawEndY,
|
||||
int spriteHeight,
|
||||
Sprite texture,
|
||||
int texX,
|
||||
double transformY,
|
||||
) {}
|
||||
|
||||
@override
|
||||
void drawWallColumn(
|
||||
int x,
|
||||
int drawStart,
|
||||
int drawEnd,
|
||||
int columnHeight,
|
||||
Sprite texture,
|
||||
int texX,
|
||||
double perpWallDist,
|
||||
int side,
|
||||
) {}
|
||||
|
||||
@override
|
||||
void drawWeapon(WolfEngine engine) {}
|
||||
|
||||
@override
|
||||
void prepareFrame(WolfEngine engine) {}
|
||||
}
|
||||
|
||||
class _HudDrawCall {
|
||||
final int imageIndex;
|
||||
final int startX;
|
||||
final int startY;
|
||||
|
||||
const _HudDrawCall({
|
||||
required this.imageIndex,
|
||||
required this.startX,
|
||||
required this.startY,
|
||||
});
|
||||
}
|
||||
|
||||
WolfEngine _buildEngine() {
|
||||
final wallGrid = _buildGrid();
|
||||
final objectGrid = _buildGrid();
|
||||
|
||||
_fillBoundaries(wallGrid, 2);
|
||||
objectGrid[2][2] = MapObject.playerEast;
|
||||
|
||||
return WolfEngine(
|
||||
data: WolfensteinData(
|
||||
version: GameVersion.retail,
|
||||
dataVersion: DataVersion.unknown,
|
||||
registry: RetailAssetRegistry(),
|
||||
walls: [
|
||||
_solidSprite(1),
|
||||
_solidSprite(1),
|
||||
_solidSprite(2),
|
||||
_solidSprite(2),
|
||||
],
|
||||
sprites: List.generate(436, (_) => _solidSprite(255)),
|
||||
sounds: const [],
|
||||
adLibSounds: const [],
|
||||
music: const [],
|
||||
vgaImages: List.generate(220, (_) => _vgaStub()),
|
||||
episodes: [
|
||||
Episode(
|
||||
name: 'Episode 1',
|
||||
levels: [
|
||||
WolfLevel(
|
||||
name: 'Level 1',
|
||||
wallGrid: wallGrid,
|
||||
areaGrid: List.generate(64, (_) => List.filled(64, -1)),
|
||||
objectGrid: objectGrid,
|
||||
music: Music.level01,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
difficulty: Difficulty.medium,
|
||||
startingEpisode: 0,
|
||||
frameBuffer: FrameBuffer(96, 96),
|
||||
input: _StaticInput(),
|
||||
onGameWon: () {},
|
||||
);
|
||||
}
|
||||
|
||||
class _StaticInput extends Wolf3dInput {
|
||||
@override
|
||||
void update() {}
|
||||
}
|
||||
|
||||
VgaImage _vgaStub() {
|
||||
return VgaImage(
|
||||
width: 4,
|
||||
height: 1,
|
||||
pixels: Uint8List.fromList([0, 0, 0, 0]),
|
||||
);
|
||||
}
|
||||
|
||||
SpriteMap _buildGrid() => List.generate(64, (_) => List.filled(64, 0));
|
||||
|
||||
void _fillBoundaries(SpriteMap grid, int wallId) {
|
||||
for (int i = 0; i < 64; i++) {
|
||||
grid[0][i] = wallId;
|
||||
grid[63][i] = wallId;
|
||||
grid[i][0] = wallId;
|
||||
grid[i][63] = wallId;
|
||||
}
|
||||
}
|
||||
|
||||
Sprite _solidSprite(int colorIndex) {
|
||||
return Sprite(Uint8List.fromList(List.filled(64 * 64, colorIndex)));
|
||||
}
|
||||
Reference in New Issue
Block a user