feat: Add CompatibleSaveGameCodec for legacy W3DS support and enhance SaveGamePersistence with existence check
Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
@@ -402,6 +402,48 @@ void main() {
|
||||
expect(quitCalls, 0);
|
||||
expect(exitCalls, 1);
|
||||
});
|
||||
|
||||
test('load availability is scoped to active game version', () async {
|
||||
final persistence = _InMemorySaveGamePersistence(
|
||||
saves: {
|
||||
_InMemorySaveGamePersistence.key(
|
||||
slot: 0,
|
||||
version: GameVersion.shareware,
|
||||
): Uint8List.fromList(const <int>[
|
||||
1,
|
||||
]),
|
||||
},
|
||||
);
|
||||
|
||||
final retailEngine = WolfEngine(
|
||||
data: _buildTestData(gameVersion: GameVersion.retail),
|
||||
difficulty: null,
|
||||
startingEpisode: 0,
|
||||
frameBuffer: FrameBuffer(64, 64),
|
||||
input: _TestInput(),
|
||||
engineAudio: _SilentAudio(),
|
||||
saveGamePersistence: persistence,
|
||||
onGameWon: () {},
|
||||
);
|
||||
retailEngine.init();
|
||||
await Future<void>.delayed(Duration.zero);
|
||||
|
||||
final sharewareEngine = WolfEngine(
|
||||
data: _buildTestData(gameVersion: GameVersion.shareware),
|
||||
difficulty: null,
|
||||
startingEpisode: 0,
|
||||
frameBuffer: FrameBuffer(64, 64),
|
||||
input: _TestInput(),
|
||||
engineAudio: _SilentAudio(),
|
||||
saveGamePersistence: persistence,
|
||||
onGameWon: () {},
|
||||
);
|
||||
sharewareEngine.init();
|
||||
await Future<void>.delayed(Duration.zero);
|
||||
|
||||
expect(retailEngine.hasLoadableSave, isFalse);
|
||||
expect(sharewareEngine.hasLoadableSave, isTrue);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -410,6 +452,7 @@ WolfEngine _buildMultiGameEngine({
|
||||
required Difficulty? difficulty,
|
||||
void Function()? onMenuExit,
|
||||
void Function()? onQuit,
|
||||
SaveGamePersistence? saveGamePersistence,
|
||||
}) {
|
||||
final WolfensteinData retail = _buildTestData(
|
||||
gameVersion: GameVersion.retail,
|
||||
@@ -425,6 +468,7 @@ WolfEngine _buildMultiGameEngine({
|
||||
frameBuffer: FrameBuffer(64, 64),
|
||||
input: input,
|
||||
engineAudio: _SilentAudio(),
|
||||
saveGamePersistence: saveGamePersistence,
|
||||
onGameWon: () {},
|
||||
onMenuExit: onMenuExit,
|
||||
onQuit: onQuit,
|
||||
@@ -585,10 +629,43 @@ class _SilentAudio implements EngineAudio {
|
||||
void dispose() {}
|
||||
}
|
||||
|
||||
class _InMemorySaveGamePersistence implements SaveGamePersistence {
|
||||
_InMemorySaveGamePersistence({Map<String, Uint8List>? saves})
|
||||
: _saves = saves ?? <String, Uint8List>{};
|
||||
|
||||
final Map<String, Uint8List> _saves;
|
||||
|
||||
static String key({required int slot, required GameVersion version}) =>
|
||||
'${version.name}:$slot';
|
||||
|
||||
@override
|
||||
Future<Uint8List?> load({
|
||||
required int slot,
|
||||
required GameVersion version,
|
||||
}) async {
|
||||
return _saves[key(slot: slot, version: version)];
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> exists({required int slot, required GameVersion version}) async {
|
||||
final Uint8List? bytes = _saves[key(slot: slot, version: version)];
|
||||
return bytes != null && bytes.isNotEmpty;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> save({
|
||||
required int slot,
|
||||
required GameVersion version,
|
||||
required Uint8List bytes,
|
||||
}) async {
|
||||
_saves[key(slot: slot, version: version)] = Uint8List.fromList(bytes);
|
||||
}
|
||||
}
|
||||
|
||||
void _dismissIntroSplash(WolfEngine engine, _TestInput input) {
|
||||
int safety = 0;
|
||||
while (engine.menuManager.activeMenu == WolfMenuScreen.introSplash &&
|
||||
safety < 160) {
|
||||
safety < 600) {
|
||||
input.isInteracting = safety.isEven;
|
||||
engine.tick(const Duration(milliseconds: 16));
|
||||
safety++;
|
||||
|
||||
@@ -109,10 +109,10 @@ void main() {
|
||||
expect(decoded.createdAtMs, 999);
|
||||
expect(decoded.gameVersion, engine.data.version);
|
||||
expect(
|
||||
decoded.snapshot.activeEpisodeIndex,
|
||||
file.snapshot.activeEpisodeIndex,
|
||||
decoded.snapshot.currentEpisodeIndex,
|
||||
file.snapshot.currentEpisodeIndex,
|
||||
);
|
||||
expect(decoded.snapshot.activeLevelIndex, file.snapshot.activeLevelIndex);
|
||||
expect(decoded.snapshot.currentLevelIndex, file.snapshot.currentLevelIndex);
|
||||
});
|
||||
|
||||
test('OriginalLayoutEnvelopeSaveGameCodec rejects invalid checksum', () {
|
||||
@@ -140,6 +140,30 @@ void main() {
|
||||
throwsA(isA<FormatException>()),
|
||||
);
|
||||
});
|
||||
|
||||
test('CompatibleSaveGameCodec decodes legacy W3DS saves', () {
|
||||
final WolfEngine engine = _buildEngine();
|
||||
engine.init();
|
||||
|
||||
final SaveGameCodec legacyCodec = SaveGameCodec();
|
||||
final SaveGameFile file = SaveGameFile(
|
||||
slot: 1,
|
||||
gameVersion: engine.data.version,
|
||||
dataVersionName: engine.data.dataVersion.name,
|
||||
description: 'Legacy Save',
|
||||
createdAtMs: 777,
|
||||
snapshot: engine.captureSaveState(),
|
||||
checksum: 0,
|
||||
);
|
||||
final Uint8List legacyBytes = legacyCodec.encode(file);
|
||||
|
||||
final CompatibleSaveGameCodec compatibleCodec = CompatibleSaveGameCodec();
|
||||
final SaveGameFile decoded = compatibleCodec.decode(legacyBytes);
|
||||
|
||||
expect(decoded.slot, 1);
|
||||
expect(decoded.description, 'Legacy Save');
|
||||
expect(decoded.createdAtMs, 777);
|
||||
});
|
||||
}
|
||||
|
||||
class _TestInput extends Wolf3dInput {
|
||||
|
||||
Reference in New Issue
Block a user