namespace Supercell.Laser.Logic.Home { using Newtonsoft.Json; public static class GameTimerConfig { private const string SourceTimerPath = "D:/shiny-v27/src/Supercell.Laser.Server/Json/timers.json"; private static readonly object SyncRoot = new object(); private static TimerConfig CachedConfig; private static DateTime CachedLastWriteTimeUtc = DateTime.MinValue; public static TimerSnapshot GetSnapshot(DateTime utcNow) { TimerConfig config = LoadConfig(); return new TimerSnapshot { SeasonTimeRemaining = GetSeasonTimeRemaining(config.SeasonEnd, utcNow), PowerPlayTimeRemaining = GetRemainingSeconds(config.PowerPlay, utcNow), BrawlPassTimeRemaining = GetRemainingSeconds(config.BrawlPass, utcNow) }; } private static TimerConfig LoadConfig() { lock (SyncRoot) { string path = ResolveConfigPath(); if (!File.Exists(path)) { return CreateDefaultConfig(); } DateTime lastWriteTimeUtc = File.GetLastWriteTimeUtc(path); if (CachedConfig != null && lastWriteTimeUtc == CachedLastWriteTimeUtc) { return CachedConfig; } TimerConfig loadedConfig = JsonConvert.DeserializeObject(File.ReadAllText(path)) ?? CreateDefaultConfig(); CachedConfig = loadedConfig.WithDefaults(CreateDefaultConfig()); CachedLastWriteTimeUtc = lastWriteTimeUtc; return CachedConfig; } } private static string ResolveConfigPath() { string runtimePath = Path.Combine(AppContext.BaseDirectory, "Json", "timers.json"); if (File.Exists(runtimePath)) { return runtimePath; } return SourceTimerPath; } private static int GetRemainingSeconds(DateTime targetUtc, DateTime utcNow) { return Math.Max(0, (int)(targetUtc - utcNow).TotalSeconds); } private static int GetSeasonTimeRemaining(DateTime fallbackSeasonEndUtc, DateTime utcNow) { DateTime? nextResetUtc = TryGetNextSeasonResetUtc(); if (nextResetUtc.HasValue) { return GetRemainingSeconds(nextResetUtc.Value, utcNow); } return GetRemainingSeconds(fallbackSeasonEndUtc, utcNow); } private static DateTime? TryGetNextSeasonResetUtc() { string path = ResolveConfigPath(); if (!File.Exists(path)) { return null; } try { TimerConfig config = JsonConvert.DeserializeObject(File.ReadAllText(path)); if (config?.NextReset == null || config.NextReset == default) { return null; } return DateTime.SpecifyKind(config.NextReset.Value, DateTimeKind.Utc); } catch { return null; } } private static TimerConfig CreateDefaultConfig() { return new TimerConfig { SeasonEnd = new DateTime(2026, 12, 31, 12, 0, 0, DateTimeKind.Utc), BrawlPass = new DateTime(2026, 12, 31, 12, 0, 0, DateTimeKind.Utc), PowerPlay = new DateTime(2026, 5, 31, 12, 0, 0, DateTimeKind.Utc) }; } private sealed class TimerConfig { [JsonProperty("seasonEnd")] public DateTime SeasonEnd { get; set; } [JsonProperty("brawlPass")] public DateTime BrawlPass { get; set; } [JsonProperty("powerPlay")] public DateTime PowerPlay { get; set; } [JsonProperty("lastReset")] public DateTime? LastReset { get; set; } [JsonProperty("nextReset")] public DateTime? NextReset { get; set; } [JsonProperty("seasonInterval")] public string SeasonInterval { get; set; } public TimerConfig WithDefaults(TimerConfig defaults) { if (SeasonEnd == default) { SeasonEnd = defaults.SeasonEnd; } if (BrawlPass == default) { BrawlPass = defaults.BrawlPass; } if (PowerPlay == default) { PowerPlay = defaults.PowerPlay; } return this; } } } public sealed class TimerSnapshot { public int SeasonTimeRemaining { get; init; } public int PowerPlayTimeRemaining { get; init; } public int BrawlPassTimeRemaining { get; init; } } }