namespace Aeshnidae.Leaderboard; public class DiscordSettings { /// /// Discord webhook to post the daily summary to. Empty is off. Set it with /// C:\Aeshnidae\ops\set-webhook.sh on the deployed Settings.json - never in source. /// public string WebhookUrl { get; set; } = ""; /// Name the post appears under. public string Username { get; set; } = "Aelrynth Leaderboards"; /// Hour of the day, UTC, for the daily post. -1 never posts on its own; /top post still works. public int PostHourUtc { get; set; } = 12; /// How many rows per board in the daily post. Discord messages are 2,000 characters; five keeps it to two or three. public int Rows { get; set; } = 5; /// Which boards go in the daily post, in order. Empty means all of them. public List Boards { get; set; } = new() { "level", "xp", "kills", "radiance", "resonance", "mastery", "auras", "allegiance-size" }; /// Which boards' movers of the last 24 hours go in the daily post. Empty means none. public List MoverBoards { get; set; } = new() { "level", "xp", "kills" }; } /// A gain on one board over one window that the staff should hear about. public class AlertSetting { /// The board's key: level, xp, kills... public string Board { get; set; } = ""; /// The window, in hours: 24 or 168 (the movers windows). public int Hours { get; set; } = 24; /// A gain of this much or more in the window raises the alert. public long Delta { get; set; } } public class HistorySettings { /// Keep hourly snapshots of every board, and work out the movers from them. public bool Enabled { get; set; } = true; /// How long snapshots are kept; hourly ones are thinned to daily after a week regardless. 0 keeps them for ever. public int KeepDays { get; set; } = 0; /// /// Write the day's snapshot to history/YYYY-MM-DD.json beside the dll (the /// first snapshot of each UTC day), and history.json - every name's value /// on every board, day by day, for the last PublishDays - for the website. /// public bool WriteDailyFiles { get; set; } = true; /// How many days history.json carries; the daily files and the table carry the rest. public int PublishDays { get; set; } = 120; /// /// Discord webhook for the alerts - the staff channel, not the daily post's. /// Empty leaves the alerts in the server log only. Set it with set-webhook.sh. /// public string AlertWebhookUrl { get; set; } = ""; /// /// Gains that are more than the game gives in the time. The defaults are /// generous; tighten them as the server's pace becomes clear. /// public List Alerts { get; set; } = new() { new() { Board = "level", Hours = 24, Delta = 100 }, new() { Board = "xp", Hours = 24, Delta = 500_000_000_000 }, new() { Board = "kills", Hours = 24, Delta = 25_000 }, new() { Board = "radiance-earned", Hours = 24, Delta = 5_000_000_000_000 }, new() { Board = "mastery", Hours = 24, Delta = 400 }, new() { Board = "auras", Hours = 24, Delta = 400 }, }; } public class Settings { public const string FileName = "Settings.json"; public bool Enabled { get; set; } = true; /// /// How often the boards are rebuilt from the database. Online players are read live /// on top of that, so the boards are exact for anyone in the world and at most this /// stale for anyone who has logged out. /// public int RefreshMinutes { get; set; } = 5; /// Rows /top shows by default; /top can ask for more, up to MaxRows. public int Rows { get; set; } = 10; public int MaxRows { get; set; } = 50; /// /// Leave staff off the boards. Anyone whose account access level is above Player /// (Advocate and up) - a level-999 admin character is not a leaderboard entry. /// public bool ExcludeStaff { get; set; } = true; /// Character names to leave off every board, whatever their account is. public List ExcludeNames { get; set; } = new(); /// /// Write leaderboard.json beside the dll on every refresh - the feed for the /// website's Leaderboards page and anything else that wants the numbers. /// public bool WriteJson { get; set; } = true; /// Seconds between flushes of the lifetime counters (kills of Radiance earned, and so on) to the database. public int FlushSeconds { get; set; } = 15; public DiscordSettings Discord { get; set; } = new(); public HistorySettings History { get; set; } = new(); private static readonly JsonSerializerOptions JsonOptions = new() { WriteIndented = true, ReadCommentHandling = JsonCommentHandling.Skip, AllowTrailingCommas = true, }; public static Settings Load(string modPath) { var path = Path.Combine(modPath, FileName); try { if (File.Exists(path)) return JsonSerializer.Deserialize(File.ReadAllText(path), JsonOptions) ?? new Settings(); var defaults = new Settings(); defaults.Save(modPath); ModManager.Log($"[{Mod.Name}] wrote default settings to {path}"); return defaults; } catch (Exception ex) { ModManager.Log($"[{Mod.Name}] could not read {path}, using defaults: {ex.Message}", ModManager.LogLevel.Warn); return new Settings(); } } public void Save(string modPath) { try { File.WriteAllText(Path.Combine(modPath, FileName), JsonSerializer.Serialize(this, JsonOptions)); } catch (Exception ex) { ModManager.Log($"[{Mod.Name}] could not save settings: {ex.Message}", ModManager.LogLevel.Error); } } }