namespace Aeshnidae.Leaderboard; /// /// Leaderboards. Who is highest, richest, deadliest and most enlightened, from the /// numbers the server already keeps plus the few it did not (what each character /// has earned and spent). /top in game, a page in the Codex, leaderboard.json for /// the website, and a daily post to Discord. /// public class Mod : IHarmonyMod { public static readonly string Name = typeof(Mod).Assembly.GetName().Name!; public static readonly string HarmonyId = $"mod.{Name.ToLowerInvariant()}"; public static readonly string ModPath = Path.Combine(ModManager.ModPath, Name); public static ModContainer? Container => ModManager.GetModContainerByPath(ModPath); public static Settings Settings { get; private set; } = new(); private static Harmony? _harmony; private static Timer? _refresh; private static Timer? _flush; private bool _disposed; public void Initialize() { Settings = Settings.Load(ModPath); Db.Initialize(); _harmony = new Harmony(HarmonyId); _harmony.PatchAllUncategorized(typeof(Mod).Assembly); Hooks.BindSiblings(_harmony); // The first refresh runs off the timer thread too, so enabling never blocks // on the database. var every = TimeSpan.FromMinutes(Math.Max(1, Settings.RefreshMinutes)); _refresh = new Timer(_ => Tick(), null, TimeSpan.FromSeconds(2), every); _flush = new Timer(_ => Db.Flush(), null, TimeSpan.FromSeconds(Settings.FlushSeconds), TimeSpan.FromSeconds(Math.Max(5, Settings.FlushSeconds))); var unbound = Hooks.Status.Where(s => !s.Bound).Select(s => $"{s.Target} ({s.Note})").ToList(); ModManager.Log($"[{Name}] ready - {Boards.All.Count} boards, refresh every {every.TotalMinutes:N0} min" + $"{(Db.Ready ? "" : " (storage unavailable, boards off)")}" + $"{(unbound.Count == 0 ? "" : $"; not hooked: {string.Join(", ", unbound)}")}"); } /// One refresh: rebind any hook that failed (its mod may be up now), rebuild, maybe post. private static void Tick() { try { if (_harmony is not null && !Hooks.AllBound) Hooks.BindSiblings(_harmony); Db.Flush(); Boards.Refresh(); Discord.MaybePostDaily(); } catch (Exception ex) { ModManager.Log($"[{Name}] tick failed: {ex.Message}", ModManager.LogLevel.Error); } } public static void RefreshNow() => Tick(); public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (_disposed) return; if (disposing) { _refresh?.Dispose(); _refresh = null; _flush?.Dispose(); _flush = null; try { Db.Flush(); } catch { /* logged inside */ } _harmony?.UnpatchAll(HarmonyId); _harmony = null; ModManager.Log($"[{Name}] shut down"); } _disposed = true; } }