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