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);
}
}
}