namespace Aeshnidae.Expedition; /// /// The hooks on ACE. InstancesNoDat is reached through ; everything here is /// a stock ACE method patched by attribute, and every body is wrapped - a hot path must never /// throw into ACE. /// /// Creature.Die a kill on the island copy: meters and contribution /// Player.OnTeleportComplete / PlayerEnterWorld the arrival line on reaching the island /// GeneratorProfile.ProcessQueue a reclaimed zone's spawns held to their share (Thinning) /// WorldObject.IsDynamicThatShouldPersistToShard camp objects are never saved (Camps) /// LandblockManager.Tick the camps' reconcile, and the periodic database write /// Creature.GenerateTreasure (private) Act 2: an island fire elemental's corpse carries Ember Pitch /// Player.GiveObjectToNPC (private) Act 2: pitch given to a Lightkeeper goes in its brazier /// WorldObject.OnActivate Act 2: using a Lightkeeper (status, the lighting) or a settler (pitch) /// and LandblockManager.Tick again Act 2: the siege's step and the beacons' light /// [HarmonyPatch] public static class Patches { [HarmonyPostfix] [HarmonyPatch(typeof(Creature), "Die", new[] { typeof(DamageHistoryInfo), typeof(DamageHistoryInfo) })] public static void PostCreatureDie(Creature __instance, DamageHistoryInfo topDamager) { try { Expedition.OnKill(__instance, topDamager); Siege.OnCreatureDie(__instance); } catch (Exception ex) { ModManager.Log($"[{Mod.Name}] kill count failed: {ex.Message}", ModManager.LogLevel.Warn); } } /// Arriving on the island by teleport (a portal, a recall, a summon): the arrival line. [HarmonyPostfix] [HarmonyPatch(typeof(Player), nameof(Player.OnTeleportComplete))] public static void PostTeleportComplete(Player __instance) { try { if (!__instance.Teleporting) Expedition.OnArrival(__instance); } catch (Exception ex) { ModManager.Log($"[{Mod.Name}] arrival line failed: {ex.Message}", ModManager.LogLevel.Warn); } } /// Logging in on the island: the arrival line, a few seconds on, once the client is drawing. [HarmonyPostfix] [HarmonyPatch(typeof(Player), nameof(Player.PlayerEnterWorld))] public static void PostEnterWorld(Player __instance) { try { var chain = new ActionChain(); chain.AddDelaySeconds(6); chain.AddAction(__instance, () => Expedition.OnArrival(__instance)); chain.EnqueueChain(); } catch (Exception ex) { ModManager.Log($"[{Mod.Name}] login line failed: {ex.Message}", ModManager.LogLevel.Warn); } } /// A generator added to a landblock of the island copy gets the island's density (Density). [HarmonyPostfix] [HarmonyPatch(typeof(Landblock), "AddWorldObjectInternal")] public static void PostAddWorldObject(Landblock __instance, WorldObject wo) { try { Density.OnAdded(__instance, wo); } catch (Exception ex) { ModManager.Log($"[{Mod.Name}] density failed for {wo?.Name}: {ex.Message}", ModManager.LogLevel.Warn); } } [HarmonyPostfix] [HarmonyPatch(typeof(Creature), "GenerateTreasure")] public static void PostGenerateTreasure(Creature __instance, Corpse corpse) { try { Beacons.OnTreasure(__instance, corpse); } catch (Exception ex) { ModManager.Log($"[{Mod.Name}] pitch drop failed: {ex.Message}", ModManager.LogLevel.Warn); } } [HarmonyPrefix] [HarmonyPatch(typeof(Player), "GiveObjectToNPC")] public static bool PreGiveObjectToNPC(Player __instance, WorldObject target, WorldObject item, Container itemFoundInContainer, Container itemRootOwner, bool itemWasEquipped, int amount) { try { return !Beacons.OnGive(__instance, target, item, itemFoundInContainer, itemRootOwner, itemWasEquipped, amount); } catch (Exception ex) { ModManager.Log($"[{Mod.Name}] give to a Lightkeeper failed: {ex.Message}", ModManager.LogLevel.Warn); return true; } } [HarmonyPostfix] [HarmonyPatch(typeof(WorldObject), nameof(WorldObject.OnActivate))] public static void PostActivate(WorldObject __instance, WorldObject activator) { try { if (activator is Player player && __instance is Creature) Beacons.OnUse(player, __instance); } catch (Exception ex) { ModManager.Log($"[{Mod.Name}] use failed: {ex.Message}", ModManager.LogLevel.Warn); } } [HarmonyPrefix] [HarmonyPatch(typeof(GeneratorProfile), nameof(GeneratorProfile.ProcessQueue))] public static void PreProcessQueue(GeneratorProfile __instance) { try { if (Expedition.AnyReclaimed && Mod.Settings.Enabled) Thinning.Trim(__instance); } catch (Exception ex) { ModManager.Log($"[{Mod.Name}] thinning check failed: {ex.Message}", ModManager.LogLevel.Warn); } } [HarmonyPostfix] [HarmonyPatch(typeof(WorldObject), nameof(WorldObject.IsDynamicThatShouldPersistToShard))] public static void PostShouldPersist(WorldObject __instance, ref bool __result) { if (__result && __instance.GetProperty(Camps.Marker) is not null) __result = false; } private static DateTime _lastFlush = DateTime.UtcNow; private static int _flushing; /// World ticks seen since load - proof the hook is live, for the admin status. public static long Ticks; [HarmonyPostfix] [HarmonyPatch(typeof(LandblockManager), nameof(LandblockManager.Tick))] public static void PostTick() { try { Ticks++; if (!Mod.Settings.Enabled) return; Camps.MaybeCheck(); Siege.Tick(); Beacons.Tick(); if (DateTime.UtcNow - _lastFlush >= TimeSpan.FromSeconds(Math.Max(5, Mod.Settings.FlushSeconds)) && Interlocked.CompareExchange(ref _flushing, 1, 0) == 0) { _lastFlush = DateTime.UtcNow; _ = Task.Run(() => { try { Expedition.Flush(); } finally { Interlocked.Exchange(ref _flushing, 0); } }); } } catch (Exception ex) { ModManager.Log($"[{Mod.Name}] tick failed: {ex.Message}", ModManager.LogLevel.Error); } } }