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