feat: Implement save and restore functionality for game session state, including player and entity states
Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
@@ -0,0 +1,227 @@
|
||||
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_entities.dart';
|
||||
import 'package:wolf_3d_dart/wolf_3d_input.dart';
|
||||
|
||||
void main() {
|
||||
test(
|
||||
'captureSaveState and restoreSaveState round-trip live session state',
|
||||
() {
|
||||
final engine = _buildEngine();
|
||||
engine.init();
|
||||
|
||||
engine.player
|
||||
..health = 47
|
||||
..ammo = 33
|
||||
..score = 1200
|
||||
..lives = 5
|
||||
..x = 10.5
|
||||
..y = 11.5
|
||||
..angle = 1.25
|
||||
..hasGoldKey = true
|
||||
..hasMachineGun = true
|
||||
..weapons[WeaponType.machineGun] = MachineGun()
|
||||
..currentWeapon = MachineGun();
|
||||
|
||||
engine.currentLevel[8][8] = 0;
|
||||
engine.currentLevel[5][6] = 98;
|
||||
|
||||
final door = engine.doorManager.doors.values.first
|
||||
..state = DoorState.opening
|
||||
..offset = 0.42
|
||||
..openTime = 1337;
|
||||
|
||||
final pushwall = engine.pushwallManager.pushwalls.values.first
|
||||
..dirX = 1
|
||||
..dirY = 0
|
||||
..offset = 0.5
|
||||
..tilesMoved = 1;
|
||||
engine.pushwallManager.activePushwall = pushwall;
|
||||
|
||||
final guard =
|
||||
EntityRegistry.spawn(
|
||||
MapObject.guardStart,
|
||||
12.5,
|
||||
13.5,
|
||||
Difficulty.medium,
|
||||
engine.data.sprites.length,
|
||||
registry: engine.data.registry,
|
||||
)!
|
||||
as Guard
|
||||
..health = 17
|
||||
..isAlerted = true
|
||||
..state = EntityState.attacking
|
||||
..currentFrame = 2
|
||||
..lastActionTime = 222;
|
||||
|
||||
final droppedAmmo = SmallAmmoCollectible(x: 7.5, y: 9.5)
|
||||
..spriteIndex = 999 % engine.data.sprites.length;
|
||||
|
||||
engine.entities = <Entity>[guard, droppedAmmo];
|
||||
|
||||
final snapshot = engine.captureSaveState();
|
||||
|
||||
engine.player
|
||||
..health = 1
|
||||
..ammo = 0
|
||||
..score = 0
|
||||
..x = 2.5
|
||||
..y = 2.5
|
||||
..hasGoldKey = false;
|
||||
engine.currentLevel[8][8] = 55;
|
||||
door
|
||||
..state = DoorState.closed
|
||||
..offset = 0.0
|
||||
..openTime = 0;
|
||||
engine.pushwallManager.activePushwall = null;
|
||||
engine.entities = <Entity>[];
|
||||
|
||||
engine.restoreSaveState(snapshot);
|
||||
|
||||
expect(engine.currentGameIndex, 0);
|
||||
expect(engine.currentEpisodeIndex, 0);
|
||||
expect(engine.currentLevelIndex, 0);
|
||||
|
||||
expect(engine.player.health, 47);
|
||||
expect(engine.player.ammo, 33);
|
||||
expect(engine.player.score, 1200);
|
||||
expect(engine.player.lives, 5);
|
||||
expect(engine.player.x, closeTo(10.5, 0.001));
|
||||
expect(engine.player.y, closeTo(11.5, 0.001));
|
||||
expect(engine.player.angle, closeTo(1.25, 0.001));
|
||||
expect(engine.player.hasGoldKey, isTrue);
|
||||
expect(engine.player.hasMachineGun, isTrue);
|
||||
expect(engine.player.currentWeapon.type, WeaponType.machineGun);
|
||||
|
||||
expect(engine.currentLevel[8][8], 0);
|
||||
expect(engine.currentLevel[5][6], 98);
|
||||
|
||||
final restoredDoor = engine.doorManager.doors.values.first;
|
||||
expect(restoredDoor.state, DoorState.opening);
|
||||
expect(restoredDoor.offset, closeTo(0.42, 0.001));
|
||||
expect(restoredDoor.openTime, 1337);
|
||||
|
||||
expect(engine.pushwallManager.activePushwall, isNotNull);
|
||||
expect(
|
||||
engine.pushwallManager.activePushwall!.offset,
|
||||
closeTo(0.5, 0.001),
|
||||
);
|
||||
expect(engine.pushwallManager.activePushwall!.tilesMoved, 1);
|
||||
expect(engine.pushwallManager.activePushwall!.dirX, 1);
|
||||
|
||||
expect(engine.entities, hasLength(2));
|
||||
expect(engine.entities.first, isA<Guard>());
|
||||
final restoredGuard = engine.entities.first as Guard;
|
||||
expect(restoredGuard.health, 17);
|
||||
expect(restoredGuard.isAlerted, isTrue);
|
||||
expect(restoredGuard.state, EntityState.attacking);
|
||||
expect(restoredGuard.currentFrame, 2);
|
||||
expect(engine.entities.last, isA<SmallAmmoCollectible>());
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
class _TestInput extends Wolf3dInput {
|
||||
@override
|
||||
void update() {}
|
||||
}
|
||||
|
||||
class _SilentAudio implements EngineAudio {
|
||||
@override
|
||||
WolfensteinData? activeGame;
|
||||
|
||||
@override
|
||||
Future<void> debugSoundTest() async {}
|
||||
|
||||
@override
|
||||
Future<void> init() async {}
|
||||
|
||||
@override
|
||||
void dispose() {}
|
||||
|
||||
@override
|
||||
void playLevelMusic(Music music) {}
|
||||
|
||||
@override
|
||||
void playMenuMusic() {}
|
||||
|
||||
@override
|
||||
void playSoundEffect(SoundEffect effect) {}
|
||||
|
||||
@override
|
||||
void playSoundEffectId(int sfxId) {}
|
||||
|
||||
@override
|
||||
void stopMusic() {}
|
||||
|
||||
@override
|
||||
Future<void> stopAllAudio() async {}
|
||||
}
|
||||
|
||||
WolfEngine _buildEngine() {
|
||||
final wallGrid = _buildGrid();
|
||||
final objectGrid = _buildGrid();
|
||||
_fillBoundaries(wallGrid, 2);
|
||||
|
||||
objectGrid[2][2] = MapObject.playerEast;
|
||||
objectGrid[4][4] = MapObject.pushwallTrigger;
|
||||
wallGrid[2][3] = 90;
|
||||
wallGrid[4][4] = 5;
|
||||
|
||||
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: List.generate(200, (_) => PcmSound(Uint8List(1))),
|
||||
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: _TestInput(),
|
||||
onGameWon: () {},
|
||||
engineAudio: _SilentAudio(),
|
||||
);
|
||||
}
|
||||
|
||||
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 paletteIndex) {
|
||||
return Sprite(Uint8List.fromList(<int>[paletteIndex]));
|
||||
}
|
||||
Reference in New Issue
Block a user