diff --git a/patches/minecraft/net/minecraft/world/level/NaturalSpawner.java.patch b/patches/minecraft/net/minecraft/world/level/NaturalSpawner.java.patch
index 4e2be8dbdf..1e2750ded4 100644
--- a/patches/minecraft/net/minecraft/world/level/NaturalSpawner.java.patch
+++ b/patches/minecraft/net/minecraft/world/level/NaturalSpawner.java.patch
@@ -4,7 +4,7 @@
import net.minecraft.world.phys.Vec3;
import org.slf4j.Logger;
-+// TODO: ForgeEventFactory.getPotentialSpawns, ForgeHooks.canEntitySpawn
++// TODO: ForgeHooks.canEntitySpawn
public final class NaturalSpawner {
private static final Logger f_46977_ = LogUtils.getLogger();
private static final int f_151589_ = 24;
@@ -35,12 +35,13 @@
return;
}
-@@ -266,7 +_,7 @@
+@@ -266,7 +_,8 @@
}
private static WeightedRandomList m_220443_(ServerLevel p_220444_, StructureManager p_220445_, ChunkGenerator p_220446_, MobCategory p_220447_, BlockPos p_220448_, @Nullable Holder p_220449_) {
- return m_220455_(p_220448_, p_220444_, p_220447_, p_220445_) ? NetherFortressStructure.f_228517_ : p_220446_.m_223133_(p_220449_ != null ? p_220449_ : p_220444_.m_204166_(p_220448_), p_220445_, p_220447_, p_220448_);
-+ return m_220455_(p_220448_, p_220444_, p_220447_, p_220445_) ? p_220445_.m_220521_().m_175515_(Registry.f_235725_).m_123013_(BuiltinStructures.f_209859_).m_226612_().get(MobCategory.MONSTER).f_210044_() : p_220446_.m_223133_(p_220449_ != null ? p_220449_ : p_220444_.m_204166_(p_220448_), p_220445_, p_220447_, p_220448_);
++ // Forge: Add in potential spawns, and replace hardcoded nether fortress mob list
++ return net.minecraftforge.event.ForgeEventFactory.getPotentialSpawns(p_220444_, p_220447_, p_220448_, m_220455_(p_220448_, p_220444_, p_220447_, p_220445_) ? p_220445_.m_220521_().m_175515_(Registry.f_235725_).m_123013_(BuiltinStructures.f_209859_).m_226612_().get(MobCategory.MONSTER).f_210044_() : p_220446_.m_223133_(p_220449_ != null ? p_220449_ : p_220444_.m_204166_(p_220448_), p_220445_, p_220447_, p_220448_));
}
public static boolean m_220455_(BlockPos p_220456_, ServerLevel p_220457_, MobCategory p_220458_, StructureManager p_220459_) {
diff --git a/src/main/java/net/minecraftforge/event/ForgeEventFactory.java b/src/main/java/net/minecraftforge/event/ForgeEventFactory.java
index be69edf12c..4c5af112b0 100644
--- a/src/main/java/net/minecraftforge/event/ForgeEventFactory.java
+++ b/src/main/java/net/minecraftforge/event/ForgeEventFactory.java
@@ -18,9 +18,12 @@ import net.minecraft.server.MinecraftServer;
import net.minecraft.server.ReloadableServerResources;
import net.minecraft.server.players.PlayerList;
import net.minecraft.util.RandomSource;
+import net.minecraft.util.random.WeightedRandomList;
import net.minecraft.world.Container;
+import net.minecraft.world.entity.MobCategory;
import net.minecraft.world.entity.projectile.Projectile;
import net.minecraft.world.entity.projectile.ThrownEnderpearl;
+import net.minecraft.world.level.biome.MobSpawnSettings;
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.level.portal.PortalShape;
import net.minecraft.world.level.block.state.BlockState;
@@ -801,4 +804,12 @@ public class ForgeEventFactory
{
MinecraftForge.EVENT_BUS.post(new TickEvent.ServerTickEvent(TickEvent.Phase.END, haveTime, server));
}
+
+ public static WeightedRandomList getPotentialSpawns(LevelAccessor level, MobCategory category, BlockPos pos, WeightedRandomList oldList)
+ {
+ LevelEvent.PotentialSpawns event = new LevelEvent.PotentialSpawns(level, category, pos, oldList);
+ if (MinecraftForge.EVENT_BUS.post(event))
+ return WeightedRandomList.create();
+ return WeightedRandomList.create(event.getSpawnerDataList());
+ }
}
diff --git a/src/main/java/net/minecraftforge/event/level/LevelEvent.java b/src/main/java/net/minecraftforge/event/level/LevelEvent.java
index addc76d506..8749e36656 100644
--- a/src/main/java/net/minecraftforge/event/level/LevelEvent.java
+++ b/src/main/java/net/minecraftforge/event/level/LevelEvent.java
@@ -5,13 +5,21 @@
package net.minecraftforge.event.level;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.client.multiplayer.ClientLevel;
+import net.minecraft.core.BlockPos;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.util.ProgressListener;
+import net.minecraft.util.random.WeightedRandomList;
+import net.minecraft.world.entity.MobCategory;
import net.minecraft.world.level.LevelAccessor;
+import net.minecraft.world.level.biome.MobSpawnSettings;
import net.minecraft.world.level.storage.ServerLevelData;
import net.minecraftforge.common.ForgeInternalHandler;
import net.minecraftforge.common.MinecraftForge;
@@ -117,4 +125,83 @@ public class LevelEvent extends Event
return settings;
}
}
+
+ /**
+ * Fired when building a list of all possible entities that can spawn at the specified location.
+ *
+ * If an entry is added to the list, it needs to be a globally unique instance.
+ *
+ * The event is called in {@link net.minecraft.world.level.NaturalSpawner#getRandomSpawnMobAt(ServerLevel,
+ * StructureManager, ChunkGenerator, MobCategory, RandomSource, BlockPos)}.
+ *
+ * This event is {@linkplain Cancelable cancellable}, and does not {@linkplain HasResult have a result}.
+ * Canceling the event will result in an empty list, meaning no entity will be spawned.
+ */
+ @Cancelable
+ public static class PotentialSpawns extends LevelEvent
+ {
+ private final MobCategory mobcategory;
+ private final BlockPos pos;
+ private final List list;
+ private final List view;
+
+ public PotentialSpawns(LevelAccessor level, MobCategory category, BlockPos pos, WeightedRandomList oldList)
+ {
+ super(level);
+ this.pos = pos;
+ this.mobcategory = category;
+ if (!oldList.isEmpty())
+ this.list = new ArrayList<>(oldList.unwrap());
+ else
+ this.list = new ArrayList<>();
+
+ this.view = Collections.unmodifiableList(list);
+ }
+
+ /**
+ * {@return the category of the mobs in the spawn list.}
+ */
+ public MobCategory getMobCategory()
+ {
+ return mobcategory;
+ }
+
+ /**
+ * {@return the block position where the chosen mob will be spawned.}
+ */
+ public BlockPos getPos()
+ {
+ return pos;
+ }
+
+ /**
+ * {@return the list of mobs that can potentially be spawned.}
+ */
+ public List getSpawnerDataList()
+ {
+ return view;
+ }
+
+ /**
+ * Appends a SpawnerData entry to the spawn list.
+ *
+ * @param data SpawnerData entry to be appended to the spawn list.
+ */
+ public void addSpawnerData(MobSpawnSettings.SpawnerData data)
+ {
+ list.add(data);
+ }
+
+ /**
+ * Removes a SpawnerData entry from the spawn list.
+ *
+ * @param data SpawnerData entry to be removed from the spawn list.
+ *
+ * {@return {@code true} if the spawn list contained the specified element.}
+ */
+ public boolean removeSpawnerData(MobSpawnSettings.SpawnerData data)
+ {
+ return list.remove(data);
+ }
+ }
}
diff --git a/src/test/java/net/minecraftforge/debug/world/PotentialSpawnsEventTest.java b/src/test/java/net/minecraftforge/debug/world/PotentialSpawnsEventTest.java
new file mode 100644
index 0000000000..2881f509ef
--- /dev/null
+++ b/src/test/java/net/minecraftforge/debug/world/PotentialSpawnsEventTest.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) Forge Development LLC and contributors
+ * SPDX-License-Identifier: LGPL-2.1-only
+ */
+
+package net.minecraftforge.debug.world;
+
+import net.minecraft.core.BlockPos;
+import net.minecraft.world.Difficulty;
+import net.minecraft.world.entity.MobCategory;
+import net.minecraft.world.level.LevelAccessor;
+import net.minecraftforge.common.MinecraftForge;
+import net.minecraftforge.event.level.LevelEvent;
+import net.minecraftforge.fml.common.Mod;
+
+/**
+ * This tests net.minecraftforge.event.world.World.WorldEvent.PotentialSpawns. If ENABLED is set to true,
+ * this test mod uses the PotentialSpawns event to prevent mobs in the MONSTER mob category from spawning if the
+ * game difficulty is set to anything other than hard.
+ */
+@Mod("potentialspawnsevent_test")
+public class PotentialSpawnsEventTest
+{
+ public static final boolean ENABLED = false;
+
+ public PotentialSpawnsEventTest()
+ {
+ if (ENABLED)
+ {
+ MinecraftForge.EVENT_BUS.addListener(PotentialSpawnsEventTest::onlySpawnHostileMobs);
+ }
+ }
+
+ public static void onlySpawnHostileMobs(LevelEvent.PotentialSpawns event)
+ {
+ LevelAccessor level = event.getLevel();
+ BlockPos pos = event.getPos();
+ Difficulty difficulty = level.getCurrentDifficultyAt(pos).getDifficulty();
+ MobCategory category = event.getMobCategory();
+
+ if (category == MobCategory.MONSTER && difficulty != Difficulty.HARD)
+ {
+ event.setCanceled(true);
+ }
+ }
+}
diff --git a/src/test/resources/META-INF/mods.toml b/src/test/resources/META-INF/mods.toml
index fb214dd703..5d93f73e4c 100644
--- a/src/test/resources/META-INF/mods.toml
+++ b/src/test/resources/META-INF/mods.toml
@@ -12,6 +12,8 @@ license="LGPL v2.1"
modId="structure_modifiers_test"
[[mods]]
modId="fluid_type_test"
+[[mods]]
+ modId="potentialspawnsevent_test"
# LEGACY TEST CASES