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