b451f83174
Captures the pre-Godot-port state of the codebase. This is the rollback anchor for the Godot port (M0 of theriapolis-rpg-implementation-plan-godot-port.md). All Phase 0 through Phase 6.5 work is included; Phase 7 is in flight. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
144 lines
4.4 KiB
C#
144 lines
4.4 KiB
C#
using Microsoft.Xna.Framework;
|
|
using Microsoft.Xna.Framework.Graphics;
|
|
using Microsoft.Xna.Framework.Input;
|
|
using Myra.Graphics2D.UI;
|
|
using Theriapolis.Core;
|
|
using Theriapolis.Core.Persistence;
|
|
using Theriapolis.Game.Platform;
|
|
|
|
namespace Theriapolis.Game.Screens;
|
|
|
|
/// <summary>
|
|
/// Slot picker. Lists C.SAVE_SLOT_COUNT slots plus the autosave slot. Reading
|
|
/// each slot only deserializes the JSON header (cheap), so opening the picker
|
|
/// is fast even if there are many large saves.
|
|
///
|
|
/// Phase 4 mode: load only (called from TitleScreen). Save-from-game uses the
|
|
/// F5 quicksave; a save-as-slot UI can be added later by extending this screen
|
|
/// with an Action.
|
|
/// </summary>
|
|
public sealed class SaveLoadScreen : IScreen
|
|
{
|
|
private Game1 _game = null!;
|
|
private Desktop _desktop = null!;
|
|
|
|
public void Initialize(Game1 game)
|
|
{
|
|
_game = game;
|
|
BuildUI();
|
|
}
|
|
|
|
private void BuildUI()
|
|
{
|
|
var root = new VerticalStackPanel
|
|
{
|
|
Spacing = 8,
|
|
HorizontalAlignment = HorizontalAlignment.Center,
|
|
VerticalAlignment = VerticalAlignment.Center,
|
|
};
|
|
|
|
root.Widgets.Add(new Label
|
|
{
|
|
Text = "LOAD GAME",
|
|
HorizontalAlignment = HorizontalAlignment.Center,
|
|
});
|
|
root.Widgets.Add(new Label { Text = " " });
|
|
|
|
// Autosave row first.
|
|
AddSlotRow(root, "Autosave", SavePaths.AutosavePath());
|
|
for (int i = 1; i <= C.SAVE_SLOT_COUNT; i++)
|
|
AddSlotRow(root, $"Slot {i:D2}", SavePaths.SlotPath(i));
|
|
|
|
root.Widgets.Add(new Label { Text = " " });
|
|
var back = new TextButton { Text = "Back", Width = 200, HorizontalAlignment = HorizontalAlignment.Center };
|
|
back.Click += (_, _) => _game.Screens.Pop();
|
|
root.Widgets.Add(back);
|
|
|
|
_desktop = new Desktop { Root = root };
|
|
}
|
|
|
|
private void AddSlotRow(VerticalStackPanel parent, string label, string path)
|
|
{
|
|
string text = label;
|
|
bool exists = File.Exists(path);
|
|
bool compatible = false;
|
|
if (exists)
|
|
{
|
|
try
|
|
{
|
|
var bytes = File.ReadAllBytes(path);
|
|
var header = SaveCodec.DeserializeHeaderOnly(bytes);
|
|
if (SaveCodec.IsCompatible(header))
|
|
{
|
|
text = $"{label}: {header.SlotLabel()}";
|
|
compatible = true;
|
|
}
|
|
else
|
|
{
|
|
text = $"{label}: <v{header.Version} — incompatible (Phase 5+ only)>";
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
text = $"{label}: <unreadable>";
|
|
}
|
|
}
|
|
else
|
|
{
|
|
text = $"{label}: <empty>";
|
|
}
|
|
|
|
var btn = new TextButton
|
|
{
|
|
Text = text,
|
|
Width = 480,
|
|
HorizontalAlignment = HorizontalAlignment.Center,
|
|
};
|
|
if (exists && compatible) btn.Click += (_, _) => LoadSlot(path);
|
|
else btn.Enabled = false;
|
|
parent.Widgets.Add(btn);
|
|
}
|
|
|
|
private void LoadSlot(string path)
|
|
{
|
|
try
|
|
{
|
|
var bytes = File.ReadAllBytes(path);
|
|
var headerOnly = SaveCodec.DeserializeHeaderOnly(bytes);
|
|
if (!SaveCodec.IsCompatible(headerOnly))
|
|
{
|
|
var err = new Label
|
|
{
|
|
Text = SaveCodec.IncompatibilityReason(headerOnly),
|
|
HorizontalAlignment = HorizontalAlignment.Center,
|
|
};
|
|
_desktop.Root = err;
|
|
return;
|
|
}
|
|
var (header, body) = SaveCodec.Deserialize(bytes);
|
|
_game.Screens.Pop(); // back to title
|
|
_game.Screens.Push(new WorldGenProgressScreen(header.ParseSeed(), restoreFromSave: body, savedHeader: header));
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
// Crude error display: replace the screen content with the error.
|
|
var err = new Label { Text = $"Load failed:\n{ex.Message}", HorizontalAlignment = HorizontalAlignment.Center };
|
|
_desktop.Root = err;
|
|
}
|
|
}
|
|
|
|
public void Update(GameTime gt)
|
|
{
|
|
if (Keyboard.GetState().IsKeyDown(Keys.Escape)) _game.Screens.Pop();
|
|
}
|
|
|
|
public void Draw(GameTime gt, SpriteBatch sb)
|
|
{
|
|
_game.GraphicsDevice.Clear(new Color(20, 20, 30));
|
|
_desktop.Render();
|
|
}
|
|
|
|
public void Deactivate() { }
|
|
public void Reactivate() { }
|
|
}
|