M7.4: Pause menu + save-from-pause, slot rows show wall-clock time

PauseMenuScreen — CanvasLayer overlay on PlayScreen, Layer=50 with
ProcessMode=WhenPaused so it stays responsive while the tree is
paused. _Ready sets GetTree().Paused=true; Resume/Close/QuitToTitle
unpause first. Two sub-states share one VBoxContainer-and-status-label
panel: main menu (Resume / ★ Level Up / Save Game / Quicksave / Quit
to Title) and slot picker. Save Game flips to the picker, click a
slot to write, back returns to main. Esc backs out of picker to main
on first press, closes the overlay on a second. CodexTheme applied
at the panel root since overlays mount outside PlayScreen's Control
tree and theme cascade doesn't cross CanvasLayer boundaries.

Level-up button surfaces only when LevelUpFlow.CanLevelUp(pc)
returns true (matches MonoGame), and is rendered disabled with a
"ships with M8" tooltip — fresh L1 characters won't see it in M7
play-tests.

Quit to Title autosaves first (matches MonoGame). A failed autosave
doesn't block the quit; better to let the user leave than trap them.

Save-from-pause writes to an internal status label inside the panel
rather than PlayScreen's save-flash toast — the toast lives on the
paused tree branch and would freeze mid-fade.

PlayScreen Esc now AddChild(new PauseMenuScreen(this)) instead of
BackToTitle. Added paused-guard + Echo filter in _Input. New public
PlayerCharacter() accessor lets the pause panel call CanLevelUp.
HUD hint updated to "F5 quicksaves · Esc opens pause".

SaveSlotFormat — shared helper between SaveLoadScreen (load picker)
and PauseMenuScreen (save picker) so both surfaces render rows with
the same prefix + in-game time + wall-clock time. Parses
SavedAtUtc, converts to local time, renders relative
(today/yesterday), short (MMM d, HH:mm within year), or full
(yyyy-MM-dd HH:mm), with "<unknown>" fallback for empty headers.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Christopher Wiebe
2026-05-10 19:14:24 -07:00
parent 8e2efdd878
commit 116193c1e3
5 changed files with 368 additions and 4 deletions
+14 -2
View File
@@ -269,6 +269,12 @@ public partial class PlayScreen : Control
public override void _Input(InputEvent @event)
{
if (@event is not InputEventKey { Pressed: true } key) return;
if (key.Echo) return;
// Skip key events while the game is paused — the pause overlay
// owns input handling for itself; PlayScreen shouldn't see Esc/F5
// again until the overlay closes.
if (GetTree().Paused) return;
switch (key.Keycode)
{
case Key.F5:
@@ -277,11 +283,17 @@ public partial class PlayScreen : Control
break;
case Key.Escape:
GetViewport().SetInputAsHandled();
BackToTitle();
AddChild(new PauseMenuScreen(this));
break;
}
}
/// <summary>Read-only accessor for the live player Character — used
/// by <see cref="PauseMenuScreen"/> to surface the level-up affordance
/// when eligible.</summary>
public Theriapolis.Core.Rules.Character.Character? PlayerCharacter()
=> _actors?.Player?.Character;
private Vector2 ScreenToWorld(Vector2 screenPos)
=> _render.Camera.GetCanvasTransform().AffineInverse() * screenPos;
@@ -594,7 +606,7 @@ public partial class PlayScreen : Control
$"{viewBlock}\n" +
$"Time: Day {_clock.Day}, {_clock.Hour:D2}:{_clock.Minute:D2}\n" +
$"{status}\n" +
"F5 quicksaves · Esc → title (pause menu lands M7.4)";
"F5 quicksaves · Esc opens pause";
}
// ──────────────────────────────────────────────────────────────────────