namespace Aeshnidae.Invasions;
///
/// Entry point ACE looks for (assembly "Aeshnidae.Invasions.dll" -> type
/// "Aeshnidae.Invasions.Mod").
///
/// Town invasions: on a timer, a town with players in it is warned, then attacked in
/// waves of retail monsters that march from the edge of town to its centre, ending with a
/// boss. Kill the boss inside the time limit and every player who damaged an invader earns
/// a bonus scaled to their level and a token; otherwise the invaders withdraw. /invasion.
///
public class Mod : IHarmonyMod
{
public static readonly string Name = typeof(Mod).Assembly.GetName().Name!;
/// Harmony owner id. Unique per mod - it is what UnpatchAll targets.
public static readonly string HarmonyId = $"mod.{Name.ToLowerInvariant()}";
/// Folder this mod was loaded from, e.g. ~/ace/Mods/Aeshnidae.Invasions
public static readonly string ModPath = Path.Combine(ModManager.ModPath, Name);
public static ModContainer? Container => ModManager.GetModContainerByPath(ModPath);
/// Live settings, re-read on enable and by /invasion-reload.
public static Settings Settings { get; private set; } = new();
private static Harmony? _harmony;
private bool _disposed;
public void Initialize()
{
Settings = Settings.Load(ModPath);
Invasions.Initialize();
_harmony = new Harmony(HarmonyId);
// Pass the assembly explicitly: the parameterless overload walks the stack, and an
// inlined Initialize() makes it land on ACE.Server and patch nothing.
_harmony.PatchAllUncategorized(typeof(Mod).Assembly);
ModManager.Log($"[{Name}] ready - {Settings.Towns.Count} town(s): {string.Join(", ", Settings.Towns.Select(t => $"{t.Name} [{t.WaveSet}]"))}; " +
$"enabled {Settings.Enabled}, timer {(Settings.AutoSchedule ? $"on, first at {Invasions.NextScheduled:HH:mm} UTC" : "off")}; " +
$"bonus {Settings.BonusPercentOfLevel:P0} of a level, token {(Settings.TokenWcid == 0 ? "none" : Settings.TokenWcid.ToString())}");
}
/// /invasion-reload: the settings, not the patches. A running invasion keeps its town and waves.
public static void ReloadSettings()
{
var wasScheduled = Settings.AutoSchedule;
Settings = Settings.Load(ModPath);
// Switching the timer on should not fire at once on a stale time.
if (Settings.AutoSchedule && !wasScheduled && Invasions.Current is null)
Invasions.SetNext(DateTime.UtcNow.AddMinutes(Settings.FirstDelayMinutes));
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (_disposed)
return;
if (disposing)
{
Invasions.Shutdown();
_harmony?.UnpatchAll(HarmonyId);
_harmony = null;
ModManager.Log($"[{Name}] shut down");
}
_disposed = true;
}
}