namespace Aeshnidae.InstancesNoDat;
///
/// The two places a variant takes effect. See Variants for the design; this file is
/// only the plumbing. Both are keyed on Context.Ambient being one of our copies, so
/// ACE's own landblocks never pass through anything here.
///
[HarmonyPatch]
public static class VariantPatches
{
///
/// Landblock.Init spawns on a Task, so the ambient set around the tick is not
/// there when the placed rows are read. Set it for the duration of
/// CreateWorldObjects when the landblock is a copy; the rows are read, and the
/// direct objects built, before it returns.
///
[HarmonyPrefix]
[HarmonyPatch(typeof(Landblock), "CreateWorldObjects")]
public static void PreCreateWorldObjects(Landblock __instance, out Landblock? __state)
{
__state = null;
try
{
if (!InstanceWorld.IsOurs(__instance))
return;
__state = Context.Ambient;
Context.Ambient = __instance;
}
catch (Exception ex)
{
ModManager.Log($"[{Mod.Name}] variant ambient failed: {ex.Message}", ModManager.LogLevel.Warn);
}
}
[HarmonyPostfix]
[HarmonyPatch(typeof(Landblock), "CreateWorldObjects")]
public static void PostCreateWorldObjects(Landblock __instance, Landblock? __state)
{
if (InstanceWorld.IsOurs(__instance))
Context.Ambient = __state;
}
/// The placed rows, as the copy's variant wants them. A new list; the cache is untouched.
[HarmonyPostfix]
[HarmonyPatch(typeof(WorldDatabaseWithEntityCache), nameof(WorldDatabaseWithEntityCache.GetCachedInstancesByLandblock), new[] { typeof(ushort) })]
public static void PostGetCachedInstances(ref List __result)
{
try
{
__result = Variants.ApplyToRows(__result);
}
catch (Exception ex)
{
ModManager.Log($"[{Mod.Name}] variant rows failed: {ex.Message}", ModManager.LogLevel.Error);
}
}
/// A generator spawning inside a copy asks for the variant's creature instead.
[HarmonyPrefix]
[HarmonyPatch(typeof(ACE.Server.Factories.WorldObjectFactory), nameof(ACE.Server.Factories.WorldObjectFactory.CreateNewWorldObject), new[] { typeof(uint) })]
public static void PreCreateNewWorldObject(ref uint weenieClassId)
{
try
{
weenieClassId = Variants.SwapWcid(weenieClassId);
}
catch (Exception ex)
{
ModManager.Log($"[{Mod.Name}] variant swap failed: {ex.Message}", ModManager.LogLevel.Error);
}
}
///
/// The same swap one level down, where it cannot be skipped. CreateNewWorldObject(uint)
/// is four lines, and the JIT's tiered recompile inlines it into GeneratorProfile.Spawn
/// - after which the patch above is never reached, and the hive's swarm generators
/// spawned retail Olthoi into a tier-0 copy (2026-09-19). CreateWorldObject(Weenie,
/// ObjectGuid) is the big factory switch every path funnels through and is never
/// inlined. Swapping a wcid that was already swapped is a no-op, so both may run.
///
[HarmonyPrefix]
[HarmonyPatch(typeof(ACE.Server.Factories.WorldObjectFactory), nameof(ACE.Server.Factories.WorldObjectFactory.CreateWorldObject), new[] { typeof(ACE.Entity.Models.Weenie), typeof(ObjectGuid) })]
public static void PreCreateWorldObject(ref ACE.Entity.Models.Weenie weenie)
{
try
{
if (weenie is null)
return;
var swapped = Variants.SwapWcid(weenie.WeenieClassId);
if (swapped == weenie.WeenieClassId)
return;
var replacement = DatabaseManager.World.GetCachedWeenie(swapped);
if (replacement is null)
{
ModManager.Log($"[{Mod.Name}] variant swap {weenie.WeenieClassId} -> {swapped}: no such weenie; spawning the original", ModManager.LogLevel.Warn);
return;
}
Variants.TraceSwap(weenie.WeenieClassId, swapped);
weenie = replacement;
}
catch (Exception ex)
{
ModManager.Log($"[{Mod.Name}] variant swap failed: {ex.Message}", ModManager.LogLevel.Error);
}
}
}