using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using Myra.Graphics2D; using Myra.Graphics2D.Brushes; using Myra.Graphics2D.UI; using Theriapolis.Core; using Theriapolis.Game.Platform; namespace Theriapolis.Game.Screens; /// /// Phase 5 M2 pause menu. Pushed by when the player /// presses ESC during play. Provides Save Game (any slot, any time — /// "save-anywhere" per the Phase 5 plan), Resume, and Quit to Title. /// /// Cut-scene exclusion is forward-compat: Phase 5 has no cut scenes, so /// save-anywhere is unconditional. When cut scenes arrive (Phase 6+), the /// caller can suppress this push or grey out the Save Game button. /// public sealed class PauseMenuScreen : IScreen { private readonly PlayScreen _playScreen; private Game1 _game = null!; private Desktop _desktop = null!; private Label? _statusLabel; private bool _showingSlots; // ESC was already down when PauseMenu was pushed (the same press that // opened it). Wait for release before treating ESC as "close the menu". private bool _escWasDown = true; public PauseMenuScreen(PlayScreen playScreen) { _playScreen = playScreen ?? throw new ArgumentNullException(nameof(playScreen)); } public void Initialize(Game1 game) { _game = game; BuildMain(); } private void BuildMain() { _showingSlots = false; var root = new VerticalStackPanel { Spacing = 12, HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Center, Background = new SolidBrush(new Color(0, 0, 0, 200)), Padding = new Thickness(40, 30, 40, 30), }; root.Widgets.Add(new Label { Text = "PAUSED", HorizontalAlignment = HorizontalAlignment.Center }); root.Widgets.Add(new Label { Text = " " }); var resume = new TextButton { Text = "Resume", Width = 220, HorizontalAlignment = HorizontalAlignment.Center }; resume.Click += (_, _) => _game.Screens.Pop(); root.Widgets.Add(resume); // Phase 6.5 M0 — surface the level-up affordance when eligible. var pcChar = _playScreen.PlayerCharacter(); if (pcChar is not null && Theriapolis.Core.Rules.Character.LevelUpFlow.CanLevelUp(pcChar)) { var lvlBtn = new TextButton { Text = $"★ Level Up ({pcChar.Level} → {pcChar.Level + 1})", Width = 220, HorizontalAlignment = HorizontalAlignment.Center, }; lvlBtn.Click += (_, _) => { _game.Screens.Pop(); _game.Screens.Push(new LevelUpScreen( pcChar, _playScreen.WorldSeed(), subclasses: _playScreen.ContentResolver?.Subclasses)); }; root.Widgets.Add(lvlBtn); } var saveBtn = new TextButton { Text = "Save Game", Width = 220, HorizontalAlignment = HorizontalAlignment.Center }; saveBtn.Click += (_, _) => BuildSlotPicker(); root.Widgets.Add(saveBtn); var quickSave = new TextButton { Text = "Quicksave (autosave slot)", Width = 220, HorizontalAlignment = HorizontalAlignment.Center }; quickSave.Click += (_, _) => { bool ok = _playScreen.SaveTo(SavePaths.AutosavePath()); ShowStatus(ok ? "Quicksaved." : "Quicksave failed."); }; root.Widgets.Add(quickSave); var quit = new TextButton { Text = "Quit to Title", Width = 220, HorizontalAlignment = HorizontalAlignment.Center }; quit.Click += (_, _) => { // Autosave on quit-to-title (matches existing Phase 4 behaviour). _playScreen.SaveTo(SavePaths.AutosavePath()); // Pop the pause menu, then pop the play screen. _game.Screens.Pop(); _game.Screens.Pop(); }; root.Widgets.Add(quit); _statusLabel = new Label { Text = " ", HorizontalAlignment = HorizontalAlignment.Center }; root.Widgets.Add(_statusLabel); _desktop = new Desktop { Root = root }; } private void BuildSlotPicker() { _showingSlots = true; var root = new VerticalStackPanel { Spacing = 6, HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Center, Background = new SolidBrush(new Color(0, 0, 0, 200)), Padding = new Thickness(40, 20, 40, 20), }; root.Widgets.Add(new Label { Text = "Save to slot:", HorizontalAlignment = HorizontalAlignment.Center }); root.Widgets.Add(new Label { Text = " " }); for (int i = 1; i <= C.SAVE_SLOT_COUNT; i++) { int slotNum = i; // capture string path = SavePaths.SlotPath(slotNum); string label = $"Slot {slotNum:D2}"; if (File.Exists(path)) { try { var bytes = File.ReadAllBytes(path); var header = Theriapolis.Core.Persistence.SaveCodec.DeserializeHeaderOnly(bytes); label += " — " + header.SlotLabel(); } catch { label += " — "; } } else { label += " — "; } var btn = new TextButton { Text = label, Width = 480, HorizontalAlignment = HorizontalAlignment.Center }; btn.Click += (_, _) => { bool ok = _playScreen.SaveTo(path); if (ok) { ShowStatus($"Saved to slot {slotNum:D2}."); BuildMain(); } else ShowStatus("Save failed."); }; root.Widgets.Add(btn); } root.Widgets.Add(new Label { Text = " " }); var back = new TextButton { Text = "Back", Width = 220, HorizontalAlignment = HorizontalAlignment.Center }; back.Click += (_, _) => BuildMain(); root.Widgets.Add(back); _statusLabel = new Label { Text = " ", HorizontalAlignment = HorizontalAlignment.Center }; root.Widgets.Add(_statusLabel); _desktop.Root = root; } private void ShowStatus(string text) { if (_statusLabel is not null) _statusLabel.Text = text; } public void Update(GameTime gt) { // ESC dismisses the pause menu (resume), or backs out of the slot picker. // Edge-detect so the press that opened the menu doesn't immediately close it. bool down = Keyboard.GetState().IsKeyDown(Keys.Escape); bool justPressed = down && !_escWasDown; _escWasDown = down; if (justPressed) { if (_showingSlots) BuildMain(); else _game.Screens.Pop(); } } public void Draw(GameTime gt, SpriteBatch sb) { // Don't clear — let the play screen's last frame remain visible underneath. _desktop.Render(); } public void Deactivate() { } public void Reactivate() { } }