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