namespace Aeshnidae.Enlightenment; /// /// Aeshnidae's terms for enlightenment. /// /// Retail's bargain was all cost: reset to level 1 and lose society, luminance, /// auras, aetheria and your unspent xp, for +1 to all skills, +2 vitality and a /// title. Aeshnidae keeps the reset - which is the part that makes it mean /// something - and stops charging for the grinds you have already finished. The /// full terms are in Settings.cs, one field per clause. /// /// Entry is through the Font of Enlightenment and Rebirth (wcid 53412) and nothing /// else. See Commands.cs for why this mod adds no way to grant one, and Guard.cs /// for what stops the Font granting two. /// 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 ModContainer? Container => ModManager.GetModContainerByPath(ModPath); 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); // 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); WarnOnCouplings(); ModManager.Log($"[{Name}] active - level {Settings.BaseLevelRequirement} " + $"+{Settings.LevelRequirementPerEnlightenment} per enlightenment, " + $"{(Settings.MaxEnlightenments > 0 ? $"max {Settings.MaxEnlightenments}" : "uncapped")}"); } /// /// Shouts about the two settings combinations that break something in a mod /// other than this one. Both are legitimate choices; neither is one you want to /// discover from a player. /// public static void WarnOnCouplings() { if (!Settings.Enabled) return; // Aeshnidae.XpCurrency lets a player move unspent xp to another character. // While enlightenment zeroed that pool, the transfer was a way to carry it // across the reset - park it with a friend, enlighten, take it back. // KeepUnassignedExperience closes that by making the dodge pointless. // Turning it off reopens it, and XpCurrency has no guard of its own. if (!Settings.KeepUnassignedExperience && IsLoaded("Aeshnidae.XpCurrency")) { ModManager.Log( $"[{Name}] KeepUnassignedExperience is off while Aeshnidae.XpCurrency is loaded. " + "A player can transfer their unspent xp to another character, enlighten, and have it " + "sent back - the reset is avoidable. Either keep the pool, or add a transfer lockout " + "to XpCurrency.", ModManager.LogLevel.Warn); } // Aeshnidae.SkillMastery ranks survive enlightenment by design, because they // live outside the skill. That is only balanced while mastery costs more // than the retail rank it competes with - otherwise the optimal play is to // buy nothing but mastery and keep the entire build through the reset. if (Settings.ResetSkills && IsLoaded("Aeshnidae.SkillMastery")) { ModManager.Log( $"[{Name}] Aeshnidae.SkillMastery is loaded: mastery ranks survive enlightenment and " + "retail ranks do not. Keep its cost bands above the retail rank they compete with, or " + "enlightenment stops costing anything."); } } private static bool IsLoaded(string modName) { try { return ModManager.GetModContainerByPath(Path.Combine(ModManager.ModPath, modName))?.Status == ModStatus.Active; } catch { return false; } } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (_disposed) return; if (disposing) { Guard.Clear(); _harmony?.UnpatchAll(HarmonyId); _harmony = null; ModManager.Log($"[{Name}] shut down, retail enlightenment rules back in force"); } _disposed = true; } }