116193c1e3
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>
47 lines
2.0 KiB
C#
47 lines
2.0 KiB
C#
using System;
|
|
using System.Globalization;
|
|
using Theriapolis.Core.Persistence;
|
|
|
|
namespace Theriapolis.GodotHost.Platform;
|
|
|
|
/// <summary>
|
|
/// Slot-picker label formatting. Pulls the in-game time from
|
|
/// <see cref="SaveHeader.SlotLabel"/> (e.g. "Howlwind — Y0 Spring D5
|
|
/// (Tier 1)") and appends the wall-clock saved-at time parsed from
|
|
/// <see cref="SaveHeader.SavedAtUtc"/>, rendered in the player's local
|
|
/// timezone with a relative label when recent.
|
|
///
|
|
/// Shared between <see cref="Scenes.SaveLoadScreen"/> (load picker
|
|
/// from Title) and <see cref="Scenes.PauseMenuScreen"/>'s save picker
|
|
/// so both surfaces present the same row format.
|
|
/// </summary>
|
|
public static class SaveSlotFormat
|
|
{
|
|
/// <summary>Composed row label: "{slot} — {in-game} · saved {when}".</summary>
|
|
public static string FormatRow(string slotPrefix, SaveHeader header)
|
|
=> $"{slotPrefix} — {header.SlotLabel()} · saved {FormatSavedAt(header.SavedAtUtc)}";
|
|
|
|
/// <summary>Parses the SaveHeader's UTC saved-at timestamp and
|
|
/// renders it relative to now, in local time. Returns "<unknown>"
|
|
/// for empty / unparseable inputs so the row still shows something.</summary>
|
|
public static string FormatSavedAt(string savedAtUtc)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(savedAtUtc)) return "<unknown>";
|
|
if (!DateTime.TryParse(
|
|
savedAtUtc, CultureInfo.InvariantCulture,
|
|
DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal,
|
|
out var utc))
|
|
return savedAtUtc;
|
|
|
|
DateTime local = utc.ToLocalTime();
|
|
DateTime now = DateTime.Now;
|
|
if (local.Date == now.Date)
|
|
return $"today, {local:HH:mm}";
|
|
if (local.Date == now.Date.AddDays(-1))
|
|
return $"yesterday, {local:HH:mm}";
|
|
if (local.Year == now.Year)
|
|
return local.ToString("MMM d, HH:mm", CultureInfo.InvariantCulture);
|
|
return local.ToString("yyyy-MM-dd HH:mm", CultureInfo.InvariantCulture);
|
|
}
|
|
}
|