From 965d062298d6e42389037c339ce948c0038c079d Mon Sep 17 00:00:00 2001 From: Acuadragon100 <30689683+Acuadragon100@users.noreply.github.com> Date: Tue, 26 May 2026 12:53:07 +0200 Subject: [PATCH 1/5] ForgeEventFactory compat patch --- .../net/minecraftforge/event/ForgeEventFactory.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/main/java/net/minecraftforge/event/ForgeEventFactory.java b/src/main/java/net/minecraftforge/event/ForgeEventFactory.java index 2d462f86cb3..a673fc5b8e0 100644 --- a/src/main/java/net/minecraftforge/event/ForgeEventFactory.java +++ b/src/main/java/net/minecraftforge/event/ForgeEventFactory.java @@ -184,6 +184,19 @@ public static boolean checkSpawnPositionSpawner(Mob mob, ServerLevelAccessor lev return event.getResult() == Result.ALLOW; } + // We insert this into onFinalizeSpawn with a mixin for best compatibility. + private static final ThreadLocal> kilt$fabricOriginal = ThreadLocal.withInitial(() -> null); + + // Ideally, I would have wanted to turn onFinalizeSpawn into a stub calling this. + // Unfortunately: https://github.com/The-Aether-Team/The-Aether/blob/1.20.1-develop/src/main/java/com/aetherteam/aether/mixin/mixins/common/ForgeEventFactoryMixin.java#L24-L29 + public static SpawnGroupData kilt$onFinalizeSpawn( + Mob mob, ServerLevelAccessor level, DifficultyInstance difficulty, MobSpawnType spawnType, + @Nullable SpawnGroupData spawnData, @Nullable CompoundTag spawnTag, Operation original + ) { + kilt$fabricOriginal.set(original); + return onFinalizeSpawn(mob, level, difficulty, spawnType, spawnData, spawnTag); + } // Removing would likely just cause unnecessary lag. + /** * Vanilla calls to {@link Mob#finalizeSpawn} are replaced with calls to this method via coremod.
* Mods should call this method in place of calling {@link Mob#finalizeSpawn}. Super calls (from within overrides) should not be wrapped. From bef5b497e1d6602db2f8c51ae7b633ca057f09ca Mon Sep 17 00:00:00 2001 From: Acuadragon100 <30689683+Acuadragon100@users.noreply.github.com> Date: Tue, 26 May 2026 22:34:33 +0200 Subject: [PATCH 2/5] Inject the finalizeSpawn call. --- .../event/ForgeEventFactory.java | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/main/java/net/minecraftforge/event/ForgeEventFactory.java b/src/main/java/net/minecraftforge/event/ForgeEventFactory.java index a673fc5b8e0..1b25f2a3ad2 100644 --- a/src/main/java/net/minecraftforge/event/ForgeEventFactory.java +++ b/src/main/java/net/minecraftforge/event/ForgeEventFactory.java @@ -28,6 +28,7 @@ import net.minecraft.sounds.SoundEvent; import net.minecraft.sounds.SoundSource; import net.minecraft.util.RandomSource; +import net.minecraft.util.Unit; import net.minecraft.util.random.WeightedRandomList; import net.minecraft.world.*; import net.minecraft.world.entity.*; @@ -187,6 +188,8 @@ public static boolean checkSpawnPositionSpawner(Mob mob, ServerLevelAccessor lev // We insert this into onFinalizeSpawn with a mixin for best compatibility. private static final ThreadLocal> kilt$fabricOriginal = ThreadLocal.withInitial(() -> null); + public static final ThreadLocal kilt$didRunEvent = ThreadLocal.withInitial(() -> null); + // Ideally, I would have wanted to turn onFinalizeSpawn into a stub calling this. // Unfortunately: https://github.com/The-Aether-Team/The-Aether/blob/1.20.1-develop/src/main/java/com/aetherteam/aether/mixin/mixins/common/ForgeEventFactoryMixin.java#L24-L29 public static SpawnGroupData kilt$onFinalizeSpawn( @@ -231,15 +234,20 @@ public static boolean checkSpawnPositionSpawner(Mob mob, ServerLevelAccessor lev @SuppressWarnings("deprecation") // Call to deprecated Mob#finalizeSpawn is expected. public static SpawnGroupData onFinalizeSpawn(Mob mob, ServerLevelAccessor level, DifficultyInstance difficulty, MobSpawnType spawnType, @Nullable SpawnGroupData spawnData, @Nullable CompoundTag spawnTag) { - var event = new MobSpawnEvent.FinalizeSpawn(mob, level, mob.getX(), mob.getY(), mob.getZ(), difficulty, spawnType, spawnData, spawnTag, null); - boolean cancel = MinecraftForge.EVENT_BUS.post(event); + try { + kilt$didRunEvent.set(Unit.INSTANCE); + var event = new MobSpawnEvent.FinalizeSpawn(mob, level, mob.getX(), mob.getY(), mob.getZ(), difficulty, spawnType, spawnData, spawnTag, null); + boolean cancel = MinecraftForge.EVENT_BUS.post(event); - if (!cancel) - { - return mob.finalizeSpawn(level, event.getDifficulty(), event.getSpawnType(), event.getSpawnData(), event.getSpawnTag()); - } + if (!cancel) + { + return mob.finalizeSpawn(level, event.getDifficulty(), event.getSpawnType(), event.getSpawnData(), event.getSpawnTag()); + } - return null; + return null; + } finally { + kilt$didRunEvent.set(null); // Do we want to run remove here? Might reduce performance. + } } /** From adb1ce85eee1d3bbcf8b7b4c1fcbaebd524fa42a Mon Sep 17 00:00:00 2001 From: Acuadragon100 <30689683+Acuadragon100@users.noreply.github.com> Date: Wed, 27 May 2026 01:08:58 +0200 Subject: [PATCH 3/5] Clear the Fabric original. --- .../java/net/minecraftforge/event/ForgeEventFactory.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/java/net/minecraftforge/event/ForgeEventFactory.java b/src/main/java/net/minecraftforge/event/ForgeEventFactory.java index 1b25f2a3ad2..119d1f6bcb0 100644 --- a/src/main/java/net/minecraftforge/event/ForgeEventFactory.java +++ b/src/main/java/net/minecraftforge/event/ForgeEventFactory.java @@ -196,8 +196,12 @@ public static boolean checkSpawnPositionSpawner(Mob mob, ServerLevelAccessor lev Mob mob, ServerLevelAccessor level, DifficultyInstance difficulty, MobSpawnType spawnType, @Nullable SpawnGroupData spawnData, @Nullable CompoundTag spawnTag, Operation original ) { - kilt$fabricOriginal.set(original); - return onFinalizeSpawn(mob, level, difficulty, spawnType, spawnData, spawnTag); + try { + kilt$fabricOriginal.set(original); + return onFinalizeSpawn(mob, level, difficulty, spawnType, spawnData, spawnTag); + } finally { + kilt$fabricOriginal.set(null); + } } // Removing would likely just cause unnecessary lag. /** From 7f7402109953fbdec43bfc0a31470b563548eb56 Mon Sep 17 00:00:00 2001 From: Acuadragon100 <30689683+Acuadragon100@users.noreply.github.com> Date: Wed, 27 May 2026 12:19:33 +0200 Subject: [PATCH 4/5] Use KiltHelper::hasMethodOverride instead. This might fire the event again if some forge mod uses `@ModifyReceiver` in `ForgeEventFactory::onFinalizeSpawn`, but will make sure that running `finalizeSpawn` on a new mob from `finalizeSpawn` will work as expected. --- .../event/ForgeEventFactory.java | 21 +++++++------------ 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/src/main/java/net/minecraftforge/event/ForgeEventFactory.java b/src/main/java/net/minecraftforge/event/ForgeEventFactory.java index 119d1f6bcb0..d0779c7e100 100644 --- a/src/main/java/net/minecraftforge/event/ForgeEventFactory.java +++ b/src/main/java/net/minecraftforge/event/ForgeEventFactory.java @@ -188,8 +188,6 @@ public static boolean checkSpawnPositionSpawner(Mob mob, ServerLevelAccessor lev // We insert this into onFinalizeSpawn with a mixin for best compatibility. private static final ThreadLocal> kilt$fabricOriginal = ThreadLocal.withInitial(() -> null); - public static final ThreadLocal kilt$didRunEvent = ThreadLocal.withInitial(() -> null); - // Ideally, I would have wanted to turn onFinalizeSpawn into a stub calling this. // Unfortunately: https://github.com/The-Aether-Team/The-Aether/blob/1.20.1-develop/src/main/java/com/aetherteam/aether/mixin/mixins/common/ForgeEventFactoryMixin.java#L24-L29 public static SpawnGroupData kilt$onFinalizeSpawn( @@ -238,20 +236,15 @@ public static boolean checkSpawnPositionSpawner(Mob mob, ServerLevelAccessor lev @SuppressWarnings("deprecation") // Call to deprecated Mob#finalizeSpawn is expected. public static SpawnGroupData onFinalizeSpawn(Mob mob, ServerLevelAccessor level, DifficultyInstance difficulty, MobSpawnType spawnType, @Nullable SpawnGroupData spawnData, @Nullable CompoundTag spawnTag) { - try { - kilt$didRunEvent.set(Unit.INSTANCE); - var event = new MobSpawnEvent.FinalizeSpawn(mob, level, mob.getX(), mob.getY(), mob.getZ(), difficulty, spawnType, spawnData, spawnTag, null); - boolean cancel = MinecraftForge.EVENT_BUS.post(event); - - if (!cancel) - { - return mob.finalizeSpawn(level, event.getDifficulty(), event.getSpawnType(), event.getSpawnData(), event.getSpawnTag()); - } + var event = new MobSpawnEvent.FinalizeSpawn(mob, level, mob.getX(), mob.getY(), mob.getZ(), difficulty, spawnType, spawnData, spawnTag, null); + boolean cancel = MinecraftForge.EVENT_BUS.post(event); - return null; - } finally { - kilt$didRunEvent.set(null); // Do we want to run remove here? Might reduce performance. + if (!cancel) + { + return mob.finalizeSpawn(level, event.getDifficulty(), event.getSpawnType(), event.getSpawnData(), event.getSpawnTag()); } + + return null; } /** From 187bc43d132b3b17be41fbe71c27052d774cb098 Mon Sep 17 00:00:00 2001 From: Acuadragon100 <30689683+Acuadragon100@users.noreply.github.com> Date: Wed, 27 May 2026 14:07:42 +0200 Subject: [PATCH 5/5] Use a ThreadLocal entity set instead. This should make sure the finalize event is only fired once per entity. --- .../event/ForgeEventFactory.java | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/main/java/net/minecraftforge/event/ForgeEventFactory.java b/src/main/java/net/minecraftforge/event/ForgeEventFactory.java index d0779c7e100..88a8a3ca9c1 100644 --- a/src/main/java/net/minecraftforge/event/ForgeEventFactory.java +++ b/src/main/java/net/minecraftforge/event/ForgeEventFactory.java @@ -28,7 +28,6 @@ import net.minecraft.sounds.SoundEvent; import net.minecraft.sounds.SoundSource; import net.minecraft.util.RandomSource; -import net.minecraft.util.Unit; import net.minecraft.util.random.WeightedRandomList; import net.minecraft.world.*; import net.minecraft.world.entity.*; @@ -83,9 +82,7 @@ import org.jetbrains.annotations.Nullable; import java.io.File; -import java.util.EnumSet; -import java.util.List; -import java.util.Optional; +import java.util.*; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.BooleanSupplier; import java.util.function.Consumer; @@ -188,6 +185,8 @@ public static boolean checkSpawnPositionSpawner(Mob mob, ServerLevelAccessor lev // We insert this into onFinalizeSpawn with a mixin for best compatibility. private static final ThreadLocal> kilt$fabricOriginal = ThreadLocal.withInitial(() -> null); + public static final ThreadLocal> kilt$hasFiredInitializeEvent = ThreadLocal.withInitial(HashSet::new); + // Ideally, I would have wanted to turn onFinalizeSpawn into a stub calling this. // Unfortunately: https://github.com/The-Aether-Team/The-Aether/blob/1.20.1-develop/src/main/java/com/aetherteam/aether/mixin/mixins/common/ForgeEventFactoryMixin.java#L24-L29 public static SpawnGroupData kilt$onFinalizeSpawn( @@ -236,15 +235,20 @@ public static boolean checkSpawnPositionSpawner(Mob mob, ServerLevelAccessor lev @SuppressWarnings("deprecation") // Call to deprecated Mob#finalizeSpawn is expected. public static SpawnGroupData onFinalizeSpawn(Mob mob, ServerLevelAccessor level, DifficultyInstance difficulty, MobSpawnType spawnType, @Nullable SpawnGroupData spawnData, @Nullable CompoundTag spawnTag) { - var event = new MobSpawnEvent.FinalizeSpawn(mob, level, mob.getX(), mob.getY(), mob.getZ(), difficulty, spawnType, spawnData, spawnTag, null); - boolean cancel = MinecraftForge.EVENT_BUS.post(event); + try { + var event = new MobSpawnEvent.FinalizeSpawn(mob, level, mob.getX(), mob.getY(), mob.getZ(), difficulty, spawnType, spawnData, spawnTag, null); + boolean cancel = MinecraftForge.EVENT_BUS.post(event); + kilt$hasFiredInitializeEvent.get().add(mob); - if (!cancel) - { - return mob.finalizeSpawn(level, event.getDifficulty(), event.getSpawnType(), event.getSpawnData(), event.getSpawnTag()); - } + if (!cancel) + { + return mob.finalizeSpawn(level, event.getDifficulty(), event.getSpawnType(), event.getSpawnData(), event.getSpawnTag()); + } - return null; + return null; + } finally { + kilt$hasFiredInitializeEvent.get().remove(mob); + } } /**