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; /// /// 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. /// 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}: "; } } catch { text = $"{label}: "; } } else { text = $"{label}: "; } 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() { } }