Initial commit: Theriapolis baseline at port/godot branch point
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>
This commit is contained in:
@@ -0,0 +1,143 @@
|
||||
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() { }
|
||||
}
|
||||
Reference in New Issue
Block a user