using System.Text.Json.Serialization; namespace Theriapolis.Core.Persistence; /// /// JSON-serializable metadata stored at the front of a save file. Designed so /// the slot picker can deserialize just the header (with a 4-byte length /// prefix preceding it) without touching the binary body. /// public sealed class SaveHeader { /// Schema version. Bump on breaking changes — see SaveMigrations. [JsonPropertyName("version")] public int Version { get; set; } = C.SAVE_SCHEMA_VERSION; [JsonPropertyName("worldSeed")] public string WorldSeedHex { get; set; } = "0x0"; [JsonPropertyName("stageHashes")] public Dictionary StageHashes { get; set; } = new(); [JsonPropertyName("playerName")] public string PlayerName { get; set; } = "Wanderer"; [JsonPropertyName("playerTier")] public int PlayerTier { get; set; } [JsonPropertyName("inGameSeconds")] public long InGameSeconds { get; set; } [JsonPropertyName("savedAt")] public string SavedAtUtc { get; set; } = ""; [JsonPropertyName("appVersion")] public string AppVersion { get; set; } = "0.4.0"; /// Convenience: a one-line label for the slot picker. public string SlotLabel() { // "Wanderer — Y0 Spring D5 (Tier 1)" style long sec = InGameSeconds; long days = sec / Time.WorldClock.SecondsPerDay; int year = (int)(days / Time.WorldClock.DaysPerYear); var season = (Time.Season)((days / Time.WorldClock.DaysPerSeason) % 4); long dayOfSeason = days % Time.WorldClock.DaysPerSeason; return $"{PlayerName} — Y{year} {season} D{dayOfSeason} (Tier {PlayerTier})"; } public ulong ParseSeed() { string s = WorldSeedHex.Trim(); if (s.StartsWith("0x", StringComparison.OrdinalIgnoreCase)) return Convert.ToUInt64(s[2..], 16); return ulong.Parse(s); } }