namespace Aeshnidae.Mule;
///
/// Two patches. One warms a vault so the first /mule is instant; the other stops ACE's own
/// maintenance from deleting it.
///
[HarmonyPatch]
public static class Patches
{
/// Purges this mod has refused, for mule-admin and the log.
public static int PurgesRefused;
///
/// Read a vault off disk as its owner logs in, so the first /mule finds it there
/// rather than being told to try again. The load runs on its own task; this returns
/// immediately.
///
[HarmonyPostfix]
[HarmonyPatch(typeof(Player), nameof(Player.PlayerEnterWorld))]
public static void AfterEnterWorld(Player __instance)
{
try
{
var accountId = __instance?.Account?.AccountId ?? 0;
if (accountId != 0)
Vault.Warm(accountId);
}
catch (Exception ex)
{
// Never throw out of a hot-path patch.
ModManager.Log($"[{Mod.Name}] warming a vault at login failed: {ex.Message}", ModManager.LogLevel.Warn);
}
}
///
/// ACE WOULD DELETE THE VAULT. This stops it.
///
/// `ShardDatabaseOfflineTools.PurgeOrphanedBiotasInParallel` deletes every biota that
/// has no parent Container, no Wielder and no Location, with one exemption
/// (WeenieType.Allegiance). A vault container is exactly that: it is in nobody's
/// inventory, nobody wields it, and it has no position because it is in no landblock.
/// It would be purged - and on the NEXT run of the same tool every item that had been
/// in it would be purged too, by the "Parent container not found" pass.
///
/// The tool only runs when `Offline.PurgeOrphanedBiotas` is set in Config.js, it runs
/// at startup, and its own documentation calls it a once-every-few-months job. That is
/// precisely the kind of switch that gets flipped a year from now by someone who has
/// never heard of the vault, so the protection is in code and not in a note:
/// ModManager.Initialize runs at Program.cs:158 and the purge at :173, so this patch is
/// in place before the tool can run, and MuleDb has already read every vault container
/// id out of `aeshnidae_mule` using its own connection (the mod does not need
/// DatabaseManager, which is not started until :259).
///
/// Giving the containers a dummy Location row would also satisfy the tool, and was
/// rejected: a biota with a Location in a landblock's range is exactly what
/// GetDynamicObjectsByLandblock loads when that landblock wakes up, and a vault
/// appearing on the ground somewhere is worse than the problem.
///
[HarmonyPrefix]
[HarmonyPatch(typeof(ACE.Database.ShardDatabaseOfflineTools), nameof(ACE.Database.ShardDatabaseOfflineTools.PurgeBiota),
new[] { typeof(ACE.Database.Models.Shard.ShardDbContext), typeof(uint), typeof(string) })]
public static bool BeforePurgeBiota(uint id, string reason, ref bool __result)
{
try
{
if (!MuleDb.IsVaultContainer(id))
return true;
Interlocked.Increment(ref PurgesRefused);
ModManager.Log($"[{Mod.Name}] REFUSED a purge of 0x{id:X8} - it is an account's vault container (reason given: {reason ?? "none"}). ACE's orphaned-biota pass cannot see that a vault has an owner; aeshnidae_mule can.",
ModManager.LogLevel.Warn);
__result = false;
return false;
}
catch (Exception ex)
{
// If the guard itself cannot answer, let ACE through rather than wedging a
// maintenance tool - but say so loudly, because this is the one patch whose
// failure can cost an account its gear.
ModManager.Log($"[{Mod.Name}] the vault purge guard threw on 0x{id:X8} and ACE's purge was NOT blocked: {ex.Message}",
ModManager.LogLevel.Error);
return true;
}
}
}