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