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:
@@ -1951,7 +1951,13 @@ class AsciiRenderer extends CliRendererBackend<dynamic> {
|
|||||||
|
|
||||||
drawBorderedPanel(cursorX, baseY + 1, livesW, 4);
|
drawBorderedPanel(cursorX, baseY + 1, livesW, 4);
|
||||||
_writeString(cursorX + 2, baseY + 2, "LIV", white, vgaPanelDark);
|
_writeString(cursorX + 2, baseY + 2, "LIV", white, vgaPanelDark);
|
||||||
_writeString(cursorX + 3, baseY + 3, "3", white, vgaPanelDark);
|
_writeString(
|
||||||
|
cursorX + 3,
|
||||||
|
baseY + 3,
|
||||||
|
engine.player.lives.toString(),
|
||||||
|
white,
|
||||||
|
vgaPanelDark,
|
||||||
|
);
|
||||||
cursorX += livesW + gap;
|
cursorX += livesW + gap;
|
||||||
|
|
||||||
drawBorderedPanel(cursorX, baseY, faceW, 5);
|
drawBorderedPanel(cursorX, baseY, faceW, 5);
|
||||||
|
|||||||
@@ -179,7 +179,7 @@ abstract class RendererBackend<T>
|
|||||||
|
|
||||||
_drawHudNumber(engine, vgaImages, 1, 32, 176);
|
_drawHudNumber(engine, vgaImages, 1, 32, 176);
|
||||||
_drawHudNumber(engine, vgaImages, engine.player.score, 96, 176);
|
_drawHudNumber(engine, vgaImages, engine.player.score, 96, 176);
|
||||||
_drawHudNumber(engine, vgaImages, 3, 120, 176);
|
_drawHudNumber(engine, vgaImages, engine.player.lives, 120, 176);
|
||||||
_drawHudNumber(engine, vgaImages, engine.player.health, 192, 176);
|
_drawHudNumber(engine, vgaImages, engine.player.health, 192, 176);
|
||||||
_drawHudNumber(engine, vgaImages, engine.player.ammo, 232, 176);
|
_drawHudNumber(engine, vgaImages, engine.player.ammo, 232, 176);
|
||||||
_drawHudFace(engine, vgaImages);
|
_drawHudFace(engine, vgaImages);
|
||||||
|
|||||||
@@ -38,12 +38,75 @@ void main() {
|
|||||||
expect(engine.player.lives, 5);
|
expect(engine.player.lives, 5);
|
||||||
expect(engine.player.hasMachineGun, isTrue);
|
expect(engine.player.hasMachineGun, isTrue);
|
||||||
expect(engine.player.hasChainGun, 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.currentWeapon.type, WeaponType.chainGun);
|
||||||
expect(engine.player.hasGoldKey, isFalse);
|
expect(engine.player.hasGoldKey, isFalse);
|
||||||
expect(engine.player.hasSilverKey, isFalse);
|
expect(engine.player.hasSilverKey, isFalse);
|
||||||
expect(engine.player.x, closeTo(4.5, 0.001));
|
expect(engine.player.x, closeTo(4.5, 0.001));
|
||||||
expect(engine.player.y, 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', () {
|
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 {
|
class _TestInput extends Wolf3dInput {
|
||||||
@override
|
@override
|
||||||
void update() {}
|
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