namespace Aeshnidae.OverworldCopies; /// /// Private copies of one OUTDOOR landblock - the bubble - built on /// Aeshnidae.InstancesNoDat's copy machinery. See Bubbles for what a bubble is and /// Patches for the edge. /// /// Test server only (2026-09-24): never deployed to live. The switch is /overworld on /// and /overworld off, kept in Settings.json; off closes every bubble (everyone inside /// back in the real world where they stand) and leaves every hook passing straight /// through, so the dungeon copies behave exactly as they do without this mod. /// 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 Settings Settings { get; private set; } = new(); private static Harmony? _harmony; private bool _disposed; public void Initialize() { Settings = Settings.Load(ModPath); _harmony = new Harmony(HarmonyId); // Explicit assembly: the parameterless overload walks the stack (see InstancesNoDat). _harmony.PatchAllUncategorized(typeof(Mod).Assembly); Bridge.Refresh(); ModManager.Log($"[{Name}] loaded - {(Settings.Enabled ? "switched ON" : "switched off")}; InstancesNoDat {Bridge.Status}"); } public static void Trace(string message) { if (Settings.Trace) ModManager.Log($"[{Name}] {message}"); } /// The switch. Off closes every bubble first. public static string Switch(bool on, string who) { if (on == Settings.Enabled) return $"Overworld copies are already {(on ? "on" : "off")}."; var closed = 0; if (!on) closed = Bubbles.CloseAll("overworld copies were switched off"); Settings.Enabled = on; Settings.Save(ModPath); var line = on ? "Overworld copies are ON. /overworld enter opens a private copy of the outdoor landblock you stand on." : $"Overworld copies are OFF. {closed} bubble(s) closed; everyone inside is back in the real world where they stood."; ModManager.Log($"[{Name}] switched {(on ? "on" : "off")} by {who}"); return line; } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (_disposed) return; if (disposing) { try { // Without this mod's hooks a bubble has no edge: its monsters could walk // into the real world. So the bubbles go with the mod, everyone inside put // back where they stand. The switch stays as it was - a reload is not a // decision to turn it off. var closed = Bubbles.CloseAll("the overworld mod was reloaded"); if (closed > 0) ModManager.Log($"[{Name}] closed {closed} bubble(s) on unload"); } catch (Exception ex) { ModManager.Log($"[{Name}] could not close bubbles on unload: {ex}", ModManager.LogLevel.Error); } _harmony?.UnpatchAll(HarmonyId); _harmony = null; ModManager.Log($"[{Name}] shut down"); } _disposed = true; } }