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