M7.3: Save/load round-trip — F5 quicksave, Continue → slot picker
SavePaths ported verbatim from Theriapolis.Game/Platform/. Same OS directories as MonoGame (%LOCALAPPDATA%\Theriapolis\Saves on Windows, ~/Library/Application Support/Theriapolis/Saves on macOS, $XDG_DATA_HOME/Theriapolis/saves on Linux) so saves round-trip across the two builds without migration. PlayScreen save layer. Wired PlayerReputation + Flags + QuestEngine + QuestContext + _killedByChunk + _pendingEncounterRestore in _Ready, even though M7.3 doesn't actively drive any of those — they're round-trip-required, so a save written by the MonoGame build with non-empty rep/flags/quest state loads here and re-saves without data loss. SaveTo/BuildHeader/CaptureBody/ApplyRestoredBody are field-for-field ports of the MonoGame methods (Phase 5 M3 + M5, Phase 6 M2 + M4); CaptureBody flushes the streamer first so chunk deltas land in the store before serialisation. HandleChunkLoaded now honours _killedByChunk so a killed spawn stays dead across chunk reload + save round-trip. F5 quicksaves to the autosave slot. Save-flash toast (bottom-center Label, fade-out via Modulate.A) confirms each write. _Ready branches on session.PendingRestore: when set (load path), calls ApplyRestoredBody and skips the new-game spawn; otherwise spawns at the Tier-1 anchor with the M6 character. The mid-combat encounter snapshot is captured on save but the push to CombatHUDScreen is the M8 stub (logs a console diagnostic). SaveLoadScreen — load-only slot picker. Header-only deserialise per row (SaveCodec.DeserializeHeaderOnly reads just the JSON prefix, body untouched), so opening the picker is cheap even with many large saves. Slot label matches MonoGame's SlotLabel() format exactly. Incompatible / unreadable rows render disabled with the reason inline. TitleScreen Continue. Enable-gate replaced — was "user://character.json exists" (M7.1 placeholder), now scans SavesDir for *.trps + checks SaveCodec.IsCompatible. OnContinue swaps to SaveLoadScreen instead of the print stub. Manual play-test loop confirmed: F5 in run #1, quit, relaunch, Continue → Autosave row → progress bar → PlayScreen with character restored at saved tile. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -18,7 +18,7 @@ namespace Theriapolis.GodotHost.Scenes;
|
||||
/// </summary>
|
||||
public partial class TitleScreen : Control
|
||||
{
|
||||
private const string VersionLabel = "PORT / GODOT · M7.2";
|
||||
private const string VersionLabel = "PORT / GODOT · M7.3";
|
||||
private const string WizardScenePath = "res://Scenes/Wizard.tscn";
|
||||
|
||||
public override void _Ready()
|
||||
@@ -70,7 +70,7 @@ public partial class TitleScreen : Control
|
||||
buttonStack.AddChild(newBtn);
|
||||
|
||||
var continueBtn = MakeMenuButton("Continue", primary: false);
|
||||
continueBtn.Disabled = !FileAccess.FileExists(CharacterAssembler.PersistedStatePath);
|
||||
continueBtn.Disabled = !AnyCompatibleSaveExists();
|
||||
continueBtn.Pressed += OnContinue;
|
||||
buttonStack.AddChild(continueBtn);
|
||||
|
||||
@@ -162,12 +162,38 @@ public partial class TitleScreen : Control
|
||||
|
||||
private void OnContinue()
|
||||
{
|
||||
// M7 territory — the play-loop screens that consume the persisted
|
||||
// character don't exist yet. For now, surface a print so the click
|
||||
// does something visible and the button isn't dead UI.
|
||||
GD.Print($"[title] Continue: {CharacterAssembler.PersistedStatePath} exists. "
|
||||
+ "Play-loop pickup lands with M7.");
|
||||
var parent = GetParent();
|
||||
if (parent is null) return;
|
||||
foreach (Node sibling in parent.GetChildren())
|
||||
if (sibling != this) sibling.QueueFree();
|
||||
parent.AddChild(new SaveLoadScreen());
|
||||
QueueFree();
|
||||
}
|
||||
|
||||
private void OnQuit() => GetTree().Quit();
|
||||
|
||||
/// <summary>True iff at least one slot under <see cref="Platform.SavePaths.SavesDir"/>
|
||||
/// has a header that <see cref="Theriapolis.Core.Persistence.SaveCodec.IsCompatible"/>
|
||||
/// accepts. Cheap: <see cref="Theriapolis.Core.Persistence.SaveCodec.DeserializeHeaderOnly"/>
|
||||
/// reads only the JSON prefix, not the binary body.</summary>
|
||||
private static bool AnyCompatibleSaveExists()
|
||||
{
|
||||
try
|
||||
{
|
||||
string dir = Platform.SavePaths.SavesDir;
|
||||
if (!System.IO.Directory.Exists(dir)) return false;
|
||||
foreach (var path in System.IO.Directory.EnumerateFiles(dir, "*.trps"))
|
||||
{
|
||||
try
|
||||
{
|
||||
var bytes = System.IO.File.ReadAllBytes(path);
|
||||
var header = Theriapolis.Core.Persistence.SaveCodec.DeserializeHeaderOnly(bytes);
|
||||
if (Theriapolis.Core.Persistence.SaveCodec.IsCompatible(header)) return true;
|
||||
}
|
||||
catch { /* skip broken slot */ }
|
||||
}
|
||||
}
|
||||
catch { /* defensive */ }
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user