From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Aikar <aikar@aikar.co>
Date: Sun, 14 Jan 2018 17:01:31 -0500
Subject: [PATCH] PreCreatureSpawnEvent

Adds an event to fire before an Entity is created, so that plugins that need to cancel
CreatureSpawnEvent can do so from this event instead.

Cancelling CreatureSpawnEvent rapidly causes a lot of garbage collection and CPU waste
as it's done after the Entity object has been fully created.

Mob Limiting plugins and blanket "ban this type of monster" plugins should use this event
instead and save a lot of server resources.

See: https://github.com/PaperMC/Paper/issues/917

diff --git a/src/main/java/net/minecraft/util/SpawnUtil.java b/src/main/java/net/minecraft/util/SpawnUtil.java
index 1b3c7b3fb869215badacdb604199d5b96f74e384..83ef8cb27db685cceb5c2b7c9674e17b93ba081c 100644
--- a/src/main/java/net/minecraft/util/SpawnUtil.java
+++ b/src/main/java/net/minecraft/util/SpawnUtil.java
@@ -18,10 +18,10 @@ public class SpawnUtil {
 
     public static <T extends Mob> Optional<T> trySpawnMob(EntityType<T> entityType, MobSpawnType reason, ServerLevel world, BlockPos pos, int tries, int horizontalRange, int verticalRange, SpawnUtil.Strategy requirements) {
         // CraftBukkit start
-        return SpawnUtil.trySpawnMob(entityType, reason, world, pos, tries, horizontalRange, verticalRange, requirements, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT);
+        return SpawnUtil.trySpawnMob(entityType, reason, world, pos, tries, horizontalRange, verticalRange, requirements, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT, null); // Paper
     }
 
-    public static <T extends Mob> Optional<T> trySpawnMob(EntityType<T> entitytypes, MobSpawnType enummobspawn, ServerLevel worldserver, BlockPos blockposition, int i, int j, int k, SpawnUtil.Strategy spawnutil_a, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason reason) {
+    public static <T extends Mob> Optional<T> trySpawnMob(EntityType<T> entitytypes, MobSpawnType enummobspawn, ServerLevel worldserver, BlockPos blockposition, int i, int j, int k, SpawnUtil.Strategy spawnutil_a, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason reason, @javax.annotation.Nullable Runnable onAbort) { // Paper
         // CraftBukkit end
         BlockPos.MutableBlockPos blockposition_mutableblockposition = blockposition.mutable();
 
@@ -31,6 +31,26 @@ public class SpawnUtil {
 
             blockposition_mutableblockposition.setWithOffset(blockposition, i1, k, j1);
             if (worldserver.getWorldBorder().isWithinBounds((BlockPos) blockposition_mutableblockposition) && SpawnUtil.moveToPossibleSpawnPosition(worldserver, k, blockposition_mutableblockposition, spawnutil_a)) {
+                // Paper start
+                String key = EntityType.getKey(entitytypes).getPath();
+                org.bukkit.entity.EntityType type = org.bukkit.entity.EntityType.fromName(key);
+
+                com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent event;
+                event = new com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent(
+                    io.papermc.paper.util.MCUtil.toLocation(worldserver, blockposition),
+                    type,
+                    reason
+                );
+                if (!event.callEvent()) {
+                    if (event.shouldAbortSpawn()) {
+                        if (onAbort != null) {
+                            onAbort.run();
+                        }
+                        return Optional.empty();
+                    }
+                    break;
+                }
+                // Paper end
                 T t0 = entitytypes.create(worldserver, (CompoundTag) null, null, blockposition_mutableblockposition, enummobspawn, false, false); // CraftBukkit - decompile error
 
                 if (t0 != null) {
diff --git a/src/main/java/net/minecraft/world/entity/EntityType.java b/src/main/java/net/minecraft/world/entity/EntityType.java
index 2e392630d3b4b08ff8ab82b7f8b32b7fbf21570d..cb1fe497477d6850260b09dcfbf290119ff6ab49 100644
--- a/src/main/java/net/minecraft/world/entity/EntityType.java
+++ b/src/main/java/net/minecraft/world/entity/EntityType.java
@@ -405,6 +405,20 @@ public class EntityType<T extends Entity> implements FeatureElement, EntityTypeT
     @Nullable
     public T spawn(ServerLevel worldserver, @Nullable CompoundTag nbttagcompound, @Nullable Consumer<T> consumer, BlockPos blockposition, MobSpawnType enummobspawn, boolean flag, boolean flag1, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason) {
         // CraftBukkit end
+        // Paper start - Call PreCreatureSpawnEvent
+        org.bukkit.entity.EntityType type = org.bukkit.entity.EntityType.fromName(EntityType.getKey(this).getPath());
+        if (type != null) {
+            com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent event;
+            event = new com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent(
+                io.papermc.paper.util.MCUtil.toLocation(worldserver, blockposition),
+                type,
+                spawnReason
+            );
+            if (!event.callEvent()) {
+                return null;
+            }
+        }
+        // Paper end
         T t0 = this.create(worldserver, nbttagcompound, consumer, blockposition, enummobspawn, flag, flag1);
 
         if (t0 != null) {
diff --git a/src/main/java/net/minecraft/world/entity/npc/Villager.java b/src/main/java/net/minecraft/world/entity/npc/Villager.java
index bbbb26550b0c5b36d9c7204f075aa90b79b85ff0..bc778aadb2604756b783d4d024e3154576ab3236 100644
--- a/src/main/java/net/minecraft/world/entity/npc/Villager.java
+++ b/src/main/java/net/minecraft/world/entity/npc/Villager.java
@@ -951,7 +951,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler
             }).limit(5L).collect(Collectors.toList());
 
             if (list1.size() >= requiredCount) {
-                if (SpawnUtil.trySpawnMob(EntityType.IRON_GOLEM, MobSpawnType.MOB_SUMMONED, world, this.blockPosition(), 10, 8, 6, SpawnUtil.Strategy.LEGACY_IRON_GOLEM, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.VILLAGE_DEFENSE).isPresent()) { // CraftBukkit
+                if (SpawnUtil.trySpawnMob(EntityType.IRON_GOLEM, MobSpawnType.MOB_SUMMONED, world, this.blockPosition(), 10, 8, 6, SpawnUtil.Strategy.LEGACY_IRON_GOLEM, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.VILLAGE_DEFENSE, () -> {GolemSensor.golemDetected(this);}).isPresent()) { // CraftBukkit // Paper - Set Golem Last Seen to stop it from spawning another one
                     list.forEach(GolemSensor::golemDetected);
                 }
             }
diff --git a/src/main/java/net/minecraft/world/level/BaseSpawner.java b/src/main/java/net/minecraft/world/level/BaseSpawner.java
index e9e0286083337ccd027e01ec80125fc69550ad07..23cecd12f064f70d314393971312d881cace043a 100644
--- a/src/main/java/net/minecraft/world/level/BaseSpawner.java
+++ b/src/main/java/net/minecraft/world/level/BaseSpawner.java
@@ -128,6 +128,27 @@ public abstract class BaseSpawner {
                         } else if (!SpawnPlacements.checkSpawnRules((EntityType) optional.get(), world, MobSpawnType.SPAWNER, blockposition1, world.getRandom())) {
                             continue;
                         }
+                        // Paper start
+                        EntityType<?> entityType = optional.get();
+                        String key = EntityType.getKey(entityType).getPath();
+
+                        org.bukkit.entity.EntityType type = org.bukkit.entity.EntityType.fromName(key);
+                        if (type != null) {
+                            com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent event;
+                            event = new com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent(
+                                io.papermc.paper.util.MCUtil.toLocation(world, d0, d1, d2),
+                                type,
+                                org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPAWNER
+                            );
+                            if (!event.callEvent()) {
+                                flag = true;
+                                if (event.shouldAbortSpawn()) {
+                                    break;
+                                }
+                                continue;
+                            }
+                        }
+                        // Paper end
 
                         Entity entity = EntityType.loadEntityRecursive(nbttagcompound, world, (entity1) -> {
                             entity1.moveTo(d0, d1, d2, entity1.getYRot(), entity1.getXRot());
diff --git a/src/main/java/net/minecraft/world/level/NaturalSpawner.java b/src/main/java/net/minecraft/world/level/NaturalSpawner.java
index 5c58031279c0b74d2061c802e60b7bed50f27156..608be2b8bfff1b89855fc0bd181430d3a29a4cbb 100644
--- a/src/main/java/net/minecraft/world/level/NaturalSpawner.java
+++ b/src/main/java/net/minecraft/world/level/NaturalSpawner.java
@@ -213,7 +213,13 @@ public final class NaturalSpawner {
                                         j1 = biomesettingsmobs_c.minCount + world.random.nextInt(1 + biomesettingsmobs_c.maxCount - biomesettingsmobs_c.minCount);
                                     }
 
-                                    if (NaturalSpawner.isValidSpawnPostitionForType(world, group, structuremanager, chunkgenerator, biomesettingsmobs_c, blockposition_mutableblockposition, d2) && checker.test(biomesettingsmobs_c.type, blockposition_mutableblockposition, chunk)) {
+                                    // Paper start
+                                    Boolean doSpawning = isValidSpawnPostitionForType(world, group, structuremanager, chunkgenerator, biomesettingsmobs_c, blockposition_mutableblockposition, d2);
+                                    if (doSpawning == null) {
+                                        return;
+                                    }
+                                    if (doSpawning && checker.test(biomesettingsmobs_c.type, blockposition_mutableblockposition, chunk)) {
+                                        // Paper end
                                         Mob entityinsentient = NaturalSpawner.getMobForSpawn(world, biomesettingsmobs_c.type);
 
                                         if (entityinsentient == null) {
@@ -261,9 +267,25 @@ public final class NaturalSpawner {
         return squaredDistance <= 576.0D ? false : (world.getSharedSpawnPos().closerToCenterThan(new Vec3((double) pos.getX() + 0.5D, (double) pos.getY(), (double) pos.getZ() + 0.5D), 24.0D) ? false : Objects.equals(new ChunkPos(pos), chunk.getPos()) || world.isNaturalSpawningAllowed((BlockPos) pos));
     }
 
-    private static boolean isValidSpawnPostitionForType(ServerLevel world, MobCategory group, StructureManager structureAccessor, ChunkGenerator chunkGenerator, MobSpawnSettings.SpawnerData spawnEntry, BlockPos.MutableBlockPos pos, double squaredDistance) {
+    private static Boolean isValidSpawnPostitionForType(ServerLevel world, MobCategory group, StructureManager structureAccessor, ChunkGenerator chunkGenerator, MobSpawnSettings.SpawnerData spawnEntry, BlockPos.MutableBlockPos pos, double squaredDistance) { // Paper
         EntityType<?> entitytypes = spawnEntry.type;
 
+        // Paper start
+        com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent event;
+        org.bukkit.entity.EntityType type = org.bukkit.entity.EntityType.fromName(EntityType.getKey(entitytypes).getPath());
+        if (type != null) {
+            event = new com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent(
+                io.papermc.paper.util.MCUtil.toLocation(world, pos),
+                type, SpawnReason.NATURAL
+            );
+            if (!event.callEvent()) {
+                if (event.shouldAbortSpawn()) {
+                    return null;
+                }
+                return false;
+            }
+        }
+        // Paper end
         if (entitytypes.getCategory() == MobCategory.MISC) {
             return false;
         } else if (!entitytypes.canSpawnFarFromPlayer() && squaredDistance > (double) (entitytypes.getCategory().getDespawnDistance() * entitytypes.getCategory().getDespawnDistance())) {
