feat: Implement player locomotion constants and update movement logic in engine
feat: Add key icons to HUD modules and implement key rendering in HUD test: Add player movement and rotation parity tests to ensure consistency with classic Wolf3D test: Enhance HUD rendering tests for gold and silver key icons Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
import 'dart:math' as math;
|
||||
|
||||
/// Canonical player movement and rotation constants derived directly from the
|
||||
/// original Wolfenstein 3D source code (WL_PLAY.C / WL_AGENT.C).
|
||||
///
|
||||
/// Source constants (keyboard, walking state):
|
||||
/// BASEMOVE = 35, RUNMOVE = 70
|
||||
/// MOVESCALE = 150, BACKMOVESCALE = 100
|
||||
/// BASETURN = 35, RUNTURN = 70, ANGLESCALE = 20
|
||||
/// TILEGLOBAL = 65536 (fixed-point units per tile), TICRATE = 70
|
||||
abstract final class PlayerLocomotionConstants {
|
||||
static const double basemove = 35.0;
|
||||
static const double runmove = 70.0;
|
||||
static const double movescale = 150.0;
|
||||
static const double backmovescale = 100.0;
|
||||
static const double baseturn = 35.0;
|
||||
static const double runturn = 70.0;
|
||||
static const double anglescale = 20.0;
|
||||
static const double tileglobal = 65536.0;
|
||||
static const double ticrate = 70.0;
|
||||
|
||||
/// Walking forward speed in tiles per second.
|
||||
///
|
||||
/// Derived from: `(BASEMOVE * MOVESCALE * TICRATE) / TILEGLOBAL`
|
||||
static const double forwardTilesPerSecond =
|
||||
(basemove * movescale * ticrate) / tileglobal;
|
||||
|
||||
/// Walking backward speed in tiles per second (intentionally slower than
|
||||
/// forward in the original game).
|
||||
///
|
||||
/// Derived from: `(BASEMOVE * BACKMOVESCALE * TICRATE) / TILEGLOBAL`
|
||||
static const double backwardTilesPerSecond =
|
||||
(basemove * backmovescale * ticrate) / tileglobal;
|
||||
|
||||
/// Walking turn rate in radians per second.
|
||||
///
|
||||
/// Derived from: `(BASETURN / ANGLESCALE) * TICRATE` in degrees/sec,
|
||||
/// then converted to radians.
|
||||
static const double turnRadiansPerSecond =
|
||||
(baseturn / anglescale) * ticrate * (math.pi / 180.0);
|
||||
}
|
||||
@@ -877,9 +877,15 @@ class WolfEngine {
|
||||
// Standardize movement to 60 FPS (16.66ms per frame)
|
||||
final double timeScale = delta.inMilliseconds / 16.666;
|
||||
|
||||
// Apply the timeScale multiplier to ensure consistent speed at any framerate
|
||||
final double moveSpeed = 0.10 * timeScale;
|
||||
final double turnSpeed = 0.08 * timeScale;
|
||||
// Apply the timeScale multiplier to ensure consistent speed at any framerate.
|
||||
// Rates are derived from the canonical Wolf3D source constants; see
|
||||
// PlayerLocomotionConstants for the full derivation.
|
||||
final double forwardMoveSpeed =
|
||||
PlayerLocomotionConstants.forwardTilesPerSecond / 60.0 * timeScale;
|
||||
final double backwardMoveSpeed =
|
||||
PlayerLocomotionConstants.backwardTilesPerSecond / 60.0 * timeScale;
|
||||
final double turnSpeed =
|
||||
PlayerLocomotionConstants.turnRadiansPerSecond / 60.0 * timeScale;
|
||||
|
||||
Coordinate2D movement = const Coordinate2D(0, 0);
|
||||
double dAngle = 0.0;
|
||||
@@ -910,8 +916,8 @@ class WolfEngine {
|
||||
math.cos(player.angle),
|
||||
math.sin(player.angle),
|
||||
);
|
||||
if (input.isMovingForward) movement += forwardVec * moveSpeed;
|
||||
if (input.isMovingBackward) movement -= forwardVec * moveSpeed;
|
||||
if (input.isMovingForward) movement += forwardVec * forwardMoveSpeed;
|
||||
if (input.isMovingBackward) movement -= forwardVec * backwardMoveSpeed;
|
||||
|
||||
if (input.isInteracting) {
|
||||
int targetX = (player.x + math.cos(player.angle)).toInt();
|
||||
|
||||
@@ -48,6 +48,9 @@ class RetailHudModule extends HudModule {
|
||||
HudKey.pistolIcon: 89,
|
||||
HudKey.machineGunIcon: 90,
|
||||
HudKey.chainGunIcon: 91,
|
||||
HudKey.noKeyIcon: 92,
|
||||
HudKey.goldKeyIcon: 93,
|
||||
HudKey.silverKeyIcon: 94,
|
||||
};
|
||||
|
||||
@override
|
||||
|
||||
@@ -47,6 +47,9 @@ class SharewareHudModule extends HudModule {
|
||||
HudKey.pistolIcon: 89,
|
||||
HudKey.machineGunIcon: 90,
|
||||
HudKey.chainGunIcon: 91,
|
||||
HudKey.noKeyIcon: 92,
|
||||
HudKey.goldKeyIcon: 93,
|
||||
HudKey.silverKeyIcon: 94,
|
||||
};
|
||||
|
||||
int? _offset;
|
||||
@@ -65,7 +68,8 @@ class SharewareHudModule extends HudModule {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int get _effectiveOffset => useOriginalWl1Map ? _originalWl1Offset : (_offset ?? 0);
|
||||
int get _effectiveOffset =>
|
||||
useOriginalWl1Map ? _originalWl1Offset : (_offset ?? 0);
|
||||
|
||||
@override
|
||||
HudAssetRef? resolve(HudKey key) {
|
||||
|
||||
@@ -40,6 +40,9 @@ class SpearDemoHudModule extends HudModule {
|
||||
HudKey.pistolIcon: 77, // GUNPIC
|
||||
HudKey.machineGunIcon: 78, // MACHINEGUNPIC
|
||||
HudKey.chainGunIcon: 79, // GATLINGGUNPIC
|
||||
HudKey.noKeyIcon: 80,
|
||||
HudKey.goldKeyIcon: 81,
|
||||
HudKey.silverKeyIcon: 82,
|
||||
};
|
||||
|
||||
@override
|
||||
|
||||
@@ -28,7 +28,12 @@ enum HudKey {
|
||||
// --- Weapon icons ---
|
||||
pistolIcon('pistolIcon'),
|
||||
machineGunIcon('machineGunIcon'),
|
||||
chainGunIcon('chainGunIcon')
|
||||
chainGunIcon('chainGunIcon'),
|
||||
|
||||
// --- Key icons ---
|
||||
noKeyIcon('noKeyIcon'),
|
||||
goldKeyIcon('goldKeyIcon'),
|
||||
silverKeyIcon('silverKeyIcon')
|
||||
;
|
||||
|
||||
const HudKey(this.id);
|
||||
|
||||
@@ -416,11 +416,15 @@ class AsciiRenderer extends CliRendererBackend<dynamic> {
|
||||
final int mapBgColor = ColorPalette.vga32Bit[0];
|
||||
final int floorColor = ColorPalette.vga32Bit[8];
|
||||
final int wallColor = ColorPalette.vga32Bit[7];
|
||||
final int doorColor = ColorPalette.vga32Bit[14];
|
||||
final int doorColor = ColorPalette.vga32Bit[9];
|
||||
final int pushwallColor = ColorPalette.vga32Bit[6];
|
||||
final int normalExitColor = ColorPalette.vga32Bit[2];
|
||||
final int secretExitColor = ColorPalette.vga32Bit[10];
|
||||
final int goldKeyColor = ColorPalette.vga32Bit[11];
|
||||
final int silverKeyColor = ColorPalette.vga32Bit[16];
|
||||
final int enemyColor = ColorPalette.vga32Bit[12];
|
||||
final int playerColor = ColorPalette.vga32Bit[10];
|
||||
final int facingColor = ColorPalette.vga32Bit[15];
|
||||
final int playerColor = ColorPalette.vga32Bit[15];
|
||||
final int facingColor = ColorPalette.vga32Bit[14];
|
||||
|
||||
final int viewportY = 0;
|
||||
final int viewportX = _usesTerminalLayout ? projectionOffsetX : 0;
|
||||
@@ -471,15 +475,24 @@ class AsciiRenderer extends CliRendererBackend<dynamic> {
|
||||
|
||||
for (int y = 0; y < 64; y++) {
|
||||
for (int x = 0; x < 64; x++) {
|
||||
final int tileId = engine.currentLevel[y][x];
|
||||
final int wallTile = engine.currentLevel[y][x];
|
||||
final int objTile = engine.activeLevel.objectGrid[y][x];
|
||||
final bool isPushwall = engine.pushwallManager.pushwalls.containsKey(
|
||||
'$x,$y',
|
||||
);
|
||||
final int color = isPushwall
|
||||
? pushwallColor
|
||||
: (tileId == 0
|
||||
: objTile == MapObject.normalExitTrigger
|
||||
? normalExitColor
|
||||
: objTile == MapObject.secretExitTrigger
|
||||
? secretExitColor
|
||||
: objTile == MapObject.goldKey
|
||||
? goldKeyColor
|
||||
: objTile == MapObject.silverKey
|
||||
? silverKeyColor
|
||||
: (wallTile == 0
|
||||
? floorColor
|
||||
: (tileId >= 90 ? doorColor : wallColor));
|
||||
: (wallTile >= 90 ? doorColor : wallColor));
|
||||
_fillMapRect(
|
||||
mapStartX + (x * tileSize),
|
||||
mapStartY + (y * tileSize),
|
||||
|
||||
@@ -184,6 +184,7 @@ abstract class RendererBackend<T>
|
||||
_drawHudNumber(engine, vgaImages, engine.player.ammo, 232, 176);
|
||||
_drawHudFace(engine, vgaImages);
|
||||
_drawHudWeaponIcon(engine, vgaImages);
|
||||
_drawHudKeys(engine, vgaImages);
|
||||
}
|
||||
|
||||
void _drawHudNumber(
|
||||
@@ -243,6 +244,41 @@ abstract class RendererBackend<T>
|
||||
}
|
||||
}
|
||||
|
||||
void _drawHudKeys(WolfEngine engine, List<VgaImage> vgaImages) {
|
||||
_drawHudKeySlot(
|
||||
engine,
|
||||
vgaImages,
|
||||
startX: 30,
|
||||
startY: 164,
|
||||
hasKey: engine.player.hasGoldKey,
|
||||
presentKey: HudKey.goldKeyIcon,
|
||||
);
|
||||
_drawHudKeySlot(
|
||||
engine,
|
||||
vgaImages,
|
||||
startX: 30,
|
||||
startY: 180,
|
||||
hasKey: engine.player.hasSilverKey,
|
||||
presentKey: HudKey.silverKeyIcon,
|
||||
);
|
||||
}
|
||||
|
||||
void _drawHudKeySlot(
|
||||
WolfEngine engine,
|
||||
List<VgaImage> vgaImages, {
|
||||
required int startX,
|
||||
required int startY,
|
||||
required bool hasKey,
|
||||
required HudKey presentKey,
|
||||
}) {
|
||||
final HudKey keyIcon = hasKey ? presentKey : HudKey.noKeyIcon;
|
||||
final keyRef = engine.data.registry.hud.resolve(keyIcon);
|
||||
final int keyIndex = keyRef?.vgaIndex ?? -1;
|
||||
if (keyIndex >= 0 && keyIndex < vgaImages.length) {
|
||||
blitHudVgaImage(vgaImages[keyIndex], startX, startY);
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculates depth-based lighting falloff (0.0 to 1.0).
|
||||
/// While the original Wolf3D didn't use depth fog, this provides a great
|
||||
/// atmospheric effect for custom backends (like ASCII dithering).
|
||||
|
||||
@@ -370,11 +370,15 @@ class SixelRenderer extends CliRendererBackend<String> {
|
||||
const int mapBgColor = 0;
|
||||
const int floorColor = 8;
|
||||
const int wallColor = 7;
|
||||
const int doorColor = 14;
|
||||
const int doorColor = 9;
|
||||
const int pushwallColor = 6;
|
||||
const int normalExitColor = 2;
|
||||
const int secretExitColor = 10;
|
||||
const int goldKeyColor = 11;
|
||||
const int silverKeyColor = 16;
|
||||
const int enemyColor = 12;
|
||||
const int playerColor = 10;
|
||||
const int facingColor = 15;
|
||||
const int playerColor = 15;
|
||||
const int facingColor = 14;
|
||||
|
||||
_fillMapRect(0, 0, width, viewHeight, mapBgColor);
|
||||
|
||||
@@ -395,15 +399,24 @@ class SixelRenderer extends CliRendererBackend<String> {
|
||||
|
||||
for (int y = 0; y < 64; y++) {
|
||||
for (int x = 0; x < 64; x++) {
|
||||
final int tileId = engine.currentLevel[y][x];
|
||||
final int wallTile = engine.currentLevel[y][x];
|
||||
final int objTile = engine.activeLevel.objectGrid[y][x];
|
||||
final bool isPushwall = engine.pushwallManager.pushwalls.containsKey(
|
||||
'$x,$y',
|
||||
);
|
||||
final int color = isPushwall
|
||||
? pushwallColor
|
||||
: (tileId == 0
|
||||
: objTile == MapObject.normalExitTrigger
|
||||
? normalExitColor
|
||||
: objTile == MapObject.secretExitTrigger
|
||||
? secretExitColor
|
||||
: objTile == MapObject.goldKey
|
||||
? goldKeyColor
|
||||
: objTile == MapObject.silverKey
|
||||
? silverKeyColor
|
||||
: (wallTile == 0
|
||||
? floorColor
|
||||
: (tileId >= 90 ? doorColor : wallColor));
|
||||
: (wallTile >= 90 ? doorColor : wallColor));
|
||||
_fillMapRect(
|
||||
mapStartX + (x * tileSize),
|
||||
mapStartY + (y * tileSize),
|
||||
|
||||
@@ -150,11 +150,15 @@ class SoftwareRenderer extends RendererBackend<FrameBuffer> {
|
||||
final int mapBgColor = ColorPalette.vga32Bit[0];
|
||||
final int floorColor = ColorPalette.vga32Bit[8];
|
||||
final int wallColor = ColorPalette.vga32Bit[7];
|
||||
final int doorColor = ColorPalette.vga32Bit[14];
|
||||
final int doorColor = ColorPalette.vga32Bit[9];
|
||||
final int pushwallColor = ColorPalette.vga32Bit[6];
|
||||
final int normalExitColor = ColorPalette.vga32Bit[2];
|
||||
final int secretExitColor = ColorPalette.vga32Bit[10];
|
||||
final int goldKeyColor = ColorPalette.vga32Bit[11];
|
||||
final int silverKeyColor = ColorPalette.vga32Bit[16];
|
||||
final int enemyColor = ColorPalette.vga32Bit[12];
|
||||
final int playerColor = ColorPalette.vga32Bit[10];
|
||||
final int facingColor = ColorPalette.vga32Bit[15];
|
||||
final int playerColor = ColorPalette.vga32Bit[15];
|
||||
final int facingColor = ColorPalette.vga32Bit[14];
|
||||
|
||||
_fillMenuPanel(0, 0, width, viewHeight, mapBgColor);
|
||||
|
||||
@@ -175,15 +179,20 @@ class SoftwareRenderer extends RendererBackend<FrameBuffer> {
|
||||
|
||||
for (int y = 0; y < 64; y++) {
|
||||
for (int x = 0; x < 64; x++) {
|
||||
final int tileId = engine.currentLevel[y][x];
|
||||
final int wallTile = engine.currentLevel[y][x];
|
||||
final int objTile = engine.activeLevel.objectGrid[y][x];
|
||||
final bool isPushwall = engine.pushwallManager.pushwalls.containsKey(
|
||||
'$x,$y',
|
||||
);
|
||||
final int color = isPushwall
|
||||
? pushwallColor
|
||||
: (tileId == 0
|
||||
: objTile == MapObject.normalExitTrigger
|
||||
? normalExitColor
|
||||
: objTile == MapObject.secretExitTrigger
|
||||
? secretExitColor
|
||||
: (wallTile == 0
|
||||
? floorColor
|
||||
: (tileId >= 90 ? doorColor : wallColor));
|
||||
: (wallTile >= 90 ? doorColor : wallColor));
|
||||
_fillMenuPanel(
|
||||
mapStartX + (x * tileSize),
|
||||
mapStartY + (y * tileSize),
|
||||
|
||||
@@ -11,6 +11,7 @@ export 'src/engine/input/engine_input.dart';
|
||||
export 'src/engine/managers/door_manager.dart';
|
||||
export 'src/engine/managers/pushwall_manager.dart';
|
||||
export 'src/engine/player/player.dart';
|
||||
export 'src/engine/player_locomotion_constants.dart';
|
||||
export 'src/engine/rendering/renderer_settings.dart';
|
||||
export 'src/engine/rendering/renderer_settings_persistence.dart';
|
||||
export 'src/engine/wolf_3d_engine_base.dart';
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
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_engine.dart';
|
||||
import 'package:wolf_3d_dart/wolf_3d_input.dart';
|
||||
|
||||
void main() {
|
||||
group('Player movement parity', () {
|
||||
test('matches classic Wolf3D forward walking speed', () {
|
||||
final input = _HeldMoveInput(forward: true, backward: false);
|
||||
final engine = _buildEngine(input: input);
|
||||
|
||||
engine.init();
|
||||
final startX = engine.player.x;
|
||||
|
||||
engine.tick(const Duration(milliseconds: 1000));
|
||||
|
||||
final movedForward = engine.player.x - startX;
|
||||
|
||||
expect(
|
||||
movedForward,
|
||||
closeTo(PlayerLocomotionConstants.forwardTilesPerSecond, 0.01),
|
||||
);
|
||||
});
|
||||
|
||||
test('matches classic Wolf3D backward walking speed', () {
|
||||
final input = _HeldMoveInput(forward: false, backward: true);
|
||||
final engine = _buildEngine(input: input);
|
||||
|
||||
engine.init();
|
||||
final startX = engine.player.x;
|
||||
|
||||
engine.tick(const Duration(milliseconds: 1000));
|
||||
|
||||
final movedBackward = startX - engine.player.x;
|
||||
|
||||
expect(
|
||||
movedBackward,
|
||||
closeTo(PlayerLocomotionConstants.backwardTilesPerSecond, 0.01),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
class _HeldMoveInput extends Wolf3dInput {
|
||||
_HeldMoveInput({required this.forward, required this.backward});
|
||||
|
||||
final bool forward;
|
||||
final bool backward;
|
||||
|
||||
@override
|
||||
void update() {
|
||||
isMovingForward = forward;
|
||||
isMovingBackward = backward;
|
||||
|
||||
isTurningLeft = false;
|
||||
isTurningRight = false;
|
||||
isMapToggle = false;
|
||||
isInteracting = false;
|
||||
isFiring = false;
|
||||
isBack = false;
|
||||
requestedWeapon = null;
|
||||
menuTapX = null;
|
||||
menuTapY = null;
|
||||
}
|
||||
}
|
||||
|
||||
WolfEngine _buildEngine({
|
||||
required Wolf3dInput input,
|
||||
}) {
|
||||
final wallGrid = _buildGrid();
|
||||
final objectGrid = _buildGrid();
|
||||
_fillBoundaries(wallGrid, 2);
|
||||
objectGrid[20][20] = MapObject.playerEast;
|
||||
|
||||
return WolfEngine(
|
||||
data: WolfensteinData(
|
||||
version: GameVersion.shareware,
|
||||
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: const [],
|
||||
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(64, 64),
|
||||
input: input,
|
||||
onGameWon: () {},
|
||||
);
|
||||
}
|
||||
|
||||
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)));
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
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_engine.dart';
|
||||
import 'package:wolf_3d_dart/wolf_3d_input.dart';
|
||||
|
||||
void main() {
|
||||
group('Player rotation parity', () {
|
||||
test('matches classic Wolf3D keyboard walking turn rate', () {
|
||||
final input = _HeldTurnInput(turnRight: true);
|
||||
final engine = _buildEngine(input: input);
|
||||
|
||||
engine.init();
|
||||
final startingAngle = engine.player.angle;
|
||||
|
||||
engine.tick(const Duration(milliseconds: 1000));
|
||||
|
||||
final turned = engine.player.angle - startingAngle;
|
||||
|
||||
expect(
|
||||
turned,
|
||||
closeTo(PlayerLocomotionConstants.turnRadiansPerSecond, 0.01),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
class _HeldTurnInput extends Wolf3dInput {
|
||||
_HeldTurnInput({required this.turnRight});
|
||||
|
||||
final bool turnRight;
|
||||
|
||||
@override
|
||||
void update() {
|
||||
isTurningRight = turnRight;
|
||||
isTurningLeft = !turnRight;
|
||||
|
||||
isMovingForward = false;
|
||||
isMovingBackward = false;
|
||||
isMapToggle = false;
|
||||
isInteracting = false;
|
||||
isFiring = false;
|
||||
isBack = false;
|
||||
requestedWeapon = null;
|
||||
menuTapX = null;
|
||||
menuTapY = null;
|
||||
}
|
||||
}
|
||||
|
||||
WolfEngine _buildEngine({
|
||||
required Wolf3dInput input,
|
||||
}) {
|
||||
final wallGrid = _buildGrid();
|
||||
final objectGrid = _buildGrid();
|
||||
_fillBoundaries(wallGrid, 2);
|
||||
objectGrid[2][2] = MapObject.playerEast;
|
||||
|
||||
return WolfEngine(
|
||||
data: WolfensteinData(
|
||||
version: GameVersion.shareware,
|
||||
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: const [],
|
||||
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(64, 64),
|
||||
input: input,
|
||||
onGameWon: () {},
|
||||
);
|
||||
}
|
||||
|
||||
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)));
|
||||
}
|
||||
@@ -27,6 +27,36 @@ void main() {
|
||||
|
||||
expect(livesDigitCall.imageIndex, expectedDigitIndex);
|
||||
});
|
||||
|
||||
test('standard VGA HUD renders gold key icon when collected', () {
|
||||
final engine = _buildEngine();
|
||||
engine.init();
|
||||
engine.player.hasGoldKey = true;
|
||||
|
||||
final renderer = _HudProbeRenderer(vgaImages: engine.data.vgaImages);
|
||||
renderer.drawHudForTest(engine);
|
||||
|
||||
final expectedGoldKeyIndex = engine.data.registry.hud
|
||||
.resolve(HudKey.goldKeyIcon)
|
||||
?.vgaIndex;
|
||||
final expectedNoKeyIndex = engine.data.registry.hud
|
||||
.resolve(HudKey.noKeyIcon)
|
||||
?.vgaIndex;
|
||||
expect(expectedGoldKeyIndex, isNotNull);
|
||||
expect(expectedNoKeyIndex, isNotNull);
|
||||
|
||||
final goldKeyCall = renderer.drawCalls.firstWhere(
|
||||
(call) => call.startY == 164 && call.startX == 30,
|
||||
orElse: () => throw StateError('Gold key slot was not rendered.'),
|
||||
);
|
||||
final silverKeyCall = renderer.drawCalls.firstWhere(
|
||||
(call) => call.startY == 180 && call.startX == 30,
|
||||
orElse: () => throw StateError('Silver key slot was not rendered.'),
|
||||
);
|
||||
|
||||
expect(goldKeyCall.imageIndex, expectedGoldKeyIndex);
|
||||
expect(silverKeyCall.imageIndex, expectedNoKeyIndex);
|
||||
});
|
||||
}
|
||||
|
||||
class _HudProbeRenderer extends RendererBackend<int> {
|
||||
|
||||
@@ -45,7 +45,8 @@ void main() {
|
||||
expect(changedPixels, greaterThan(mapPixels.length ~/ 5));
|
||||
expect(mapPixels.contains(ColorPalette.vga32Bit[6]), isTrue);
|
||||
expect(mapPixels.contains(ColorPalette.vga32Bit[12]), isTrue);
|
||||
expect(mapPixels.contains(ColorPalette.vga32Bit[10]), isTrue);
|
||||
expect(mapPixels.contains(ColorPalette.vga32Bit[15]), isTrue);
|
||||
expect(mapPixels.contains(ColorPalette.vga32Bit[2]), isTrue);
|
||||
expect(mapPixels[hudProbeIndex], equals(normalPixels[hudProbeIndex]));
|
||||
});
|
||||
});
|
||||
@@ -78,6 +79,7 @@ WolfEngine _buildEngine() {
|
||||
wallGrid[5][5] = 1;
|
||||
objectGrid[2][2] = MapObject.playerEast;
|
||||
objectGrid[5][5] = MapObject.pushwallTrigger;
|
||||
objectGrid[10][10] = MapObject.normalExitTrigger;
|
||||
|
||||
return WolfEngine(
|
||||
data: WolfensteinData(
|
||||
|
||||
Reference in New Issue
Block a user