namespace Aeshnidae.AwakenedRewards;
///
/// Entry point ACE looks for.
///
/// ModContainer resolves the type purely by convention:
/// assembly "Aeshnidae.AwakenedRewards.dll" -> type "Aeshnidae.AwakenedRewards.Mod"
/// so this class must be named Mod and live in a namespace matching the
/// assembly name. Renaming one without the other is the most common reason a mod
/// loads its assembly and then logs "Missing IHarmonyMod Type".
///
public class Mod : IHarmonyMod
{
///
/// "Aeshnidae.AwakenedRewards". Taken from the assembly rather than written out,
/// so a rename cannot leave a stale literal behind.
///
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. C:\ACEPublic\Mods\Aeshnidae.AwakenedRewards
public static readonly string ModPath = Path.Combine(ModManager.ModPath, Name);
public static ModContainer? Container => ModManager.GetModContainerByPath(ModPath);
/// Live settings, re-read every time the mod is enabled.
public static Settings Settings { get; private set; } = new();
private static Harmony? _harmony;
private bool _disposed;
///
/// Called by ModContainer.Enable() once the assembly is loaded.
/// The world is not open yet at this point, so keep this cheap and defensive -
/// anything that throws leaves the mod stuck Inactive.
///
public void Initialize()
{
Settings = Settings.Load(ModPath);
_harmony = new Harmony(HarmonyId);
// Pass the assembly explicitly. The parameterless overload finds its target
// by walking the stack, and the JIT inlines a small Initialize(), so the walk
// lands on ACE.Server instead of this mod and silently patches nothing.
_harmony.PatchAllUncategorized(typeof(Mod).Assembly);
// InstancesNoDat is looked up lazily, at the first corpse, and again whenever
// its assembly changes (a /mod find reloads every mod) - so nothing here can
// fail because of the order the mods came up in. This first look is only for
// the log line.
Instances.Refresh();
// Whoever is online is told their tier now: what an earlier load sent is not known here.
TierFeed.CheckAllOnline("load");
var s = Settings;
ModManager.Log($"[{Name}] ready - copy loot {(s.ScaleCopyLoot ? $"{(s.LootQualityPercentPerTier > 0 ? $"+{s.LootQualityPercentPerTier:0.##}% quality/tier (cap {s.MaxLootQuality:0.##})" : "retail quality")}, " +
$"{(s.LootTierStepEveryTiers > 0 ? $"+1 loot tier per {s.LootTierStepEveryTiers} tiers" : "retail loot tier")}, " +
$"item stats +{s.PercentPerAwakening:0.##}%/tier{(s.MaxPercent > 0 ? $" to +{s.MaxPercent:0.##}%" : " uncapped")}, " +
$"wield gate {(s.MinimumAwakeningsToWieldLoot ? "on" : "off")}" : "off")}; " +
$"attuned items {(s.ScaleAttunedItems ? $"+{s.PercentPerAwakening:0.##}%/awakening of the first owner" : "off")}; " +
$"names {(s.StampNames ? "stamped" : "untouched")}; " +
$"tier feed {(s.SendTierToClient ? $"on (PropertyInt {TierFeed.PropertyId})" : "off")}; InstancesNoDat {Instances.Status}");
}
///
/// Called by ModContainer.Disable() on shutdown, hot-reload, and /mod disable.
/// Every patch added above must come off here, or the old assembly cannot be
/// collected and the next load will double-patch.
///
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (_disposed)
return;
if (disposing)
{
_harmony?.UnpatchAll(HarmonyId);
_harmony = null;
Instances.Unbind();
TierFeed.Reset();
ModManager.Log($"[{Name}] shut down");
}
_disposed = true;
}
}