namespace Aeshnidae.Trials; /// /// Radiance for a clear, minted the way Aeshnidae.Bank mints it: Earning.Award on the /// account (buffered and flushed by Bank on its own clock), History.Record for the /// character's /earned line, AnnounceRadiance for the chat line. Bound by name at /// runtime like ; Bank's CurrencyKind is its own enum, so the /// Radiance member is parsed out of the loaded assembly rather than named here. /// /// Not GrantXP: a Quest grant of the same size would mint the Radiance through Bank's /// postfix too, but it would also be experience, and the clear's reward is Radiance. /// When Bank is off or its table is not open, nothing is paid and the caller says so. /// public static class Bank { private const string ModName = "Aeshnidae.Bank"; private static readonly object _lock = new(); private static Assembly? _boundTo; private static string _status = "not looked up yet"; private static MethodInfo? _award; // Earning.Award(uint, CurrencyKind, long) private static MethodInfo? _record; // History.Record(Player, CurrencyKind, long) private static MethodInfo? _announce; // Earning.AnnounceRadiance(Player, long) private static PropertyInfo? _ready; // BankDb.Ready private static PropertyInfo? _settings; // Mod.Settings private static PropertyInfo? _enabled; // Settings.RadianceEnabled private static object? _radiance; // CurrencyKind.Radiance, boxed public static string Status { get { lock (_lock) return _status; } } private static bool Refresh() { try { var container = ModManager.GetModContainerByName(ModName, allowPartial: false); if (container is null || container.Status != ModStatus.Active || container.ModAssembly is null) { lock (_lock) { _boundTo = null; _status = container is null ? "not installed" : $"mod is {container.Status}"; } return false; } lock (_lock) { if (ReferenceEquals(_boundTo, container.ModAssembly)) return true; } var asm = container.ModAssembly; var earning = asm.GetType($"{ModName}.Earning", throwOnError: false); var history = asm.GetType($"{ModName}.History", throwOnError: false); var db = asm.GetType($"{ModName}.BankDb", throwOnError: false); var mod = asm.GetType($"{ModName}.Mod", throwOnError: false); var kind = asm.GetType($"{ModName}.CurrencyKind", throwOnError: false); if (earning is null || history is null || db is null || mod is null || kind is null) { lock (_lock) { _boundTo = null; _status = "a type is missing - renamed upstream?"; } return false; } var award = AccessTools.Method(earning, "Award", new[] { typeof(uint), kind, typeof(long) }); var record = AccessTools.Method(history, "Record", new[] { typeof(Player), kind, typeof(long) }); var announce = AccessTools.Method(earning, "AnnounceRadiance", new[] { typeof(Player), typeof(long) }); var ready = AccessTools.Property(db, "Ready"); var settings = AccessTools.Property(mod, "Settings"); var enabled = settings is null ? null : AccessTools.Property(settings.PropertyType, "RadianceEnabled"); if (award is null || ready is null) { lock (_lock) { _boundTo = null; _status = "Earning.Award or BankDb.Ready is missing - renamed upstream?"; } return false; } lock (_lock) { _award = award; _record = record; _announce = announce; _ready = ready; _settings = settings; _enabled = enabled; _radiance = Enum.Parse(kind, "Radiance"); _boundTo = asm; _status = "bound"; } ModManager.Log($"[{Mod.Name}] bound to {ModName} ({asm.GetName().Version})"); return true; } catch (Exception ex) { lock (_lock) { _boundTo = null; _status = $"{ex.GetType().Name}: {ex.Message}"; } return false; } } /// Whether a Radiance award would land right now: Bank on, Radiance on, table open. public static bool Available { get { if (!Refresh()) return false; try { PropertyInfo? ready, settings, enabled; lock (_lock) { ready = _ready; settings = _settings; enabled = _enabled; } if (ready?.GetValue(null) is not true) return false; if (settings is not null && enabled is not null) { var s = settings.GetValue(null); if (s is not null && enabled.GetValue(s) is bool on && !on) return false; } return true; } catch { return false; } } } /// Mints Radiance for a player through Bank. False when Bank could not take it. public static bool AwardRadiance(Player player, long amount) { if (amount <= 0 || player?.Account is null || !Available) return false; MethodInfo? award, record, announce; object? kind; lock (_lock) { award = _award; record = _record; announce = _announce; kind = _radiance; } if (award is null || kind is null) return false; try { award.Invoke(null, new object[] { player.Account.AccountId, kind, amount }); record?.Invoke(null, new object[] { player, kind, amount }); announce?.Invoke(null, new object[] { player, amount }); return true; } catch (Exception ex) { ModManager.Log($"[{Mod.Name}] Radiance award through Bank failed: {(ex.InnerException ?? ex).Message}", ModManager.LogLevel.Warn); return false; } } }