From cad0c129c8c125a5dbef00d11c310ecbacd8bfd8 Mon Sep 17 00:00:00 2001 From: Aikar 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 --- .../net/minecraft/util/SpawnUtil.java.patch | 22 ++++++- .../world/entity/EntityType.java.patch | 22 +++++-- .../world/entity/npc/Villager.java.patch | 2 +- .../world/level/BaseSpawner.java.patch | 27 ++++++++- .../world/level/NaturalSpawner.java.patch | 59 +++++++++++++++++-- 5 files changed, 115 insertions(+), 17 deletions(-) diff --git a/paper-server/patches/sources/net/minecraft/util/SpawnUtil.java.patch b/paper-server/patches/sources/net/minecraft/util/SpawnUtil.java.patch index c10588bdee..803fd149f1 100644 --- a/paper-server/patches/sources/net/minecraft/util/SpawnUtil.java.patch +++ b/paper-server/patches/sources/net/minecraft/util/SpawnUtil.java.patch @@ -1,18 +1,18 @@ --- a/net/minecraft/util/SpawnUtil.java +++ b/net/minecraft/util/SpawnUtil.java -@@ -21,24 +21,31 @@ +@@ -21,24 +21,47 @@ public SpawnUtil() {} public static Optional trySpawnMob(EntityType entityType, EntitySpawnReason reason, ServerLevel world, BlockPos pos, int tries, int horizontalRange, int verticalRange, SpawnUtil.Strategy requirements, boolean requireEmptySpace) { - BlockPos.MutableBlockPos blockposition_mutableblockposition = pos.mutable(); + // CraftBukkit start -+ return SpawnUtil.trySpawnMob(entityType, reason, world, pos, tries, horizontalRange, verticalRange, requirements, requireEmptySpace, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT); ++ return SpawnUtil.trySpawnMob(entityType, reason, world, pos, tries, horizontalRange, verticalRange, requirements, requireEmptySpace, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT, null); // Paper - pre creature spawn event + } - for (int l = 0; l < tries; ++l) { - int i1 = Mth.randomBetweenInclusive(world.random, -horizontalRange, horizontalRange); - int j1 = Mth.randomBetweenInclusive(world.random, -horizontalRange, horizontalRange); -+ public static Optional trySpawnMob(EntityType entitytypes, EntitySpawnReason entityspawnreason, ServerLevel worldserver, BlockPos blockposition, int i, int j, int k, SpawnUtil.Strategy spawnutil_a, boolean flag, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason reason) { ++ public static Optional trySpawnMob(EntityType entitytypes, EntitySpawnReason entityspawnreason, ServerLevel worldserver, BlockPos blockposition, int i, int j, int k, SpawnUtil.Strategy spawnutil_a, boolean flag, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason reason, @javax.annotation.Nullable Runnable onAbort) { // Paper - pre creature spawn event + // CraftBukkit end + BlockPos.MutableBlockPos blockposition_mutableblockposition = blockposition.mutable(); @@ -25,6 +25,22 @@ + blockposition_mutableblockposition.setWithOffset(blockposition, i1, k, j1); + if (worldserver.getWorldBorder().isWithinBounds((BlockPos) blockposition_mutableblockposition) && SpawnUtil.moveToPossibleSpawnPosition(worldserver, k, blockposition_mutableblockposition, spawnutil_a) && (!flag || worldserver.noCollision(entitytypes.getSpawnAABB((double) blockposition_mutableblockposition.getX() + 0.5D, (double) blockposition_mutableblockposition.getY(), (double) blockposition_mutableblockposition.getZ() + 0.5D)))) { ++ // Paper start - PreCreatureSpawnEvent ++ final com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent event = new com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent( ++ io.papermc.paper.util.MCUtil.toLocation(worldserver, blockposition), ++ org.bukkit.craftbukkit.entity.CraftEntityType.minecraftToBukkit(entitytypes), ++ reason ++ ); ++ if (!event.callEvent()) { ++ if (event.shouldAbortSpawn()) { ++ if (onAbort != null) { ++ onAbort.run(); ++ } ++ return Optional.empty(); ++ } ++ break; ++ } ++ // Paper end - PreCreatureSpawnEvent + T t0 = entitytypes.create(worldserver, (Consumer) null, blockposition_mutableblockposition, entityspawnreason, false, false); // CraftBukkit - decompile error + if (t0 != null) { diff --git a/paper-server/patches/sources/net/minecraft/world/entity/EntityType.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/EntityType.java.patch index 3045be366e..96cfe9b1a7 100644 --- a/paper-server/patches/sources/net/minecraft/world/entity/EntityType.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/entity/EntityType.java.patch @@ -54,7 +54,7 @@ } public static Consumer createDefaultStackConfig(Level world, ItemStack stack, @Nullable Player player) { -@@ -464,21 +472,40 @@ +@@ -464,21 +472,50 @@ CustomData customdata = (CustomData) stack.getOrDefault(DataComponents.ENTITY_DATA, CustomData.EMPTY); return !customdata.isEmpty() ? chained.andThen((entity) -> { @@ -82,12 +82,22 @@ + // CraftBukkit start + return this.spawn(world, afterConsumer, pos, reason, alignPosition, invertY, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT); + } -+ + + @Nullable + public T spawn(ServerLevel worldserver, @Nullable Consumer consumer, BlockPos blockposition, EntitySpawnReason entityspawnreason, boolean flag, boolean flag1, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason) { + // CraftBukkit end ++ // Paper start - PreCreatureSpawnEvent ++ com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent event = new com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent( ++ io.papermc.paper.util.MCUtil.toLocation(worldserver, blockposition), ++ org.bukkit.craftbukkit.entity.CraftEntityType.minecraftToBukkit(this), ++ spawnReason ++ ); ++ if (!event.callEvent()) { ++ return null; ++ } ++ // Paper end - PreCreatureSpawnEvent + T t0 = this.create(worldserver, consumer, blockposition, entityspawnreason, flag, flag1); - ++ if (t0 != null) { - world.addFreshEntityWithPassengers(t0); + // CraftBukkit start @@ -99,7 +109,7 @@ if (t0 instanceof Mob) { Mob entityinsentient = (Mob) t0; -@@ -657,7 +684,7 @@ +@@ -657,7 +694,7 @@ } return entity; @@ -108,7 +118,7 @@ } public static Stream loadEntitiesRecursive(final List entityNbtList, final Level world, final EntitySpawnReason reason) { -@@ -718,7 +745,7 @@ +@@ -718,7 +755,7 @@ @Nullable public T tryCast(Entity obj) { @@ -117,7 +127,7 @@ } @Override -@@ -791,7 +818,7 @@ +@@ -791,7 +828,7 @@ this.canSpawnFarFromPlayer = spawnGroup == MobCategory.CREATURE || spawnGroup == MobCategory.MISC; } diff --git a/paper-server/patches/sources/net/minecraft/world/entity/npc/Villager.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/npc/Villager.java.patch index d28a09255d..da2b2d1d8d 100644 --- a/paper-server/patches/sources/net/minecraft/world/entity/npc/Villager.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/entity/npc/Villager.java.patch @@ -145,7 +145,7 @@ if (list1.size() >= requiredCount) { - if (!SpawnUtil.trySpawnMob(EntityType.IRON_GOLEM, EntitySpawnReason.MOB_SUMMONED, world, this.blockPosition(), 10, 8, 6, SpawnUtil.Strategy.LEGACY_IRON_GOLEM, false).isEmpty()) { -+ if (!SpawnUtil.trySpawnMob(EntityType.IRON_GOLEM, EntitySpawnReason.MOB_SUMMONED, world, this.blockPosition(), 10, 8, 6, SpawnUtil.Strategy.LEGACY_IRON_GOLEM, false, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.VILLAGE_DEFENSE).isEmpty()) { // CraftBukkit ++ if (SpawnUtil.trySpawnMob(EntityType.IRON_GOLEM, EntitySpawnReason.MOB_SUMMONED, world, this.blockPosition(), 10, 8, 6, SpawnUtil.Strategy.LEGACY_IRON_GOLEM, false, 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/paper-server/patches/sources/net/minecraft/world/level/BaseSpawner.java.patch b/paper-server/patches/sources/net/minecraft/world/level/BaseSpawner.java.patch index 9193b7da39..ce9e5d276f 100644 --- a/paper-server/patches/sources/net/minecraft/world/level/BaseSpawner.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/level/BaseSpawner.java.patch @@ -40,7 +40,30 @@ } else { boolean flag = false; RandomSource randomsource = world.getRandom(); -@@ -157,13 +164,26 @@ +@@ -123,8 +130,22 @@ + continue; + } + } else if (!SpawnPlacements.checkSpawnRules((EntityType) optional.get(), world, EntitySpawnReason.SPAWNER, blockposition1, world.getRandom())) { ++ continue; ++ } ++ // Paper start - PreCreatureSpawnEvent ++ com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent event = new com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent( ++ io.papermc.paper.util.MCUtil.toLocation(world, d0, d1, d2), ++ org.bukkit.craftbukkit.entity.CraftEntityType.minecraftToBukkit(optional.get()), ++ org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPAWNER ++ ); ++ if (!event.callEvent()) { ++ flag = true; ++ if (event.shouldAbortSpawn()) { ++ break; ++ } + continue; + } ++ // Paper end - PreCreatureSpawnEvent + + Entity entity = EntityType.loadEntityRecursive(nbttagcompound, world, EntitySpawnReason.SPAWNER, (entity1) -> { + entity1.moveTo(d0, d1, d2, entity1.getYRot(), entity1.getXRot()); +@@ -157,13 +178,26 @@ ((Mob) entity).finalizeSpawn(world, world.getCurrentDifficultyAt(entity.blockPosition()), EntitySpawnReason.SPAWNER, (SpawnGroupData) null); } @@ -69,7 +92,7 @@ this.delay(world, pos); return; } -@@ -174,7 +194,7 @@ +@@ -174,7 +208,7 @@ ((Mob) entity).spawnAnim(); } diff --git a/paper-server/patches/sources/net/minecraft/world/level/NaturalSpawner.java.patch b/paper-server/patches/sources/net/minecraft/world/level/NaturalSpawner.java.patch index 18d4ed97cf..7c479fd1c8 100644 --- a/paper-server/patches/sources/net/minecraft/world/level/NaturalSpawner.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/level/NaturalSpawner.java.patch @@ -48,7 +48,22 @@ list.add(enumcreaturetype); } } -@@ -217,10 +238,15 @@ +@@ -207,7 +228,13 @@ + 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 - PreCreatureSpawnEvent ++ PreSpawnStatus doSpawning = isValidSpawnPostitionForType(world, group, structuremanager, chunkgenerator, biomesettingsmobs_c, blockposition_mutableblockposition, d2); ++ if (doSpawning == PreSpawnStatus.ABORT) { ++ return; ++ } ++ if (doSpawning == PreSpawnStatus.SUCCESS && checker.test(biomesettingsmobs_c.type, blockposition_mutableblockposition, chunk)) { ++ // Paper end - PreCreatureSpawnEvent + Mob entityinsentient = NaturalSpawner.getMobForSpawn(world, biomesettingsmobs_c.type); + + if (entityinsentient == null) { +@@ -217,10 +244,15 @@ entityinsentient.moveTo(d0, (double) i, d1, world.random.nextFloat() * 360.0F, 0.0F); if (NaturalSpawner.isValidPositionForMob(world, entityinsentient, d2)) { groupdataentity = entityinsentient.finalizeSpawn(world, world.getCurrentDifficultyAt(entityinsentient.blockPosition()), EntitySpawnReason.NATURAL, groupdataentity); @@ -68,7 +83,41 @@ if (j >= entityinsentient.getMaxSpawnClusterSize()) { return; } -@@ -268,6 +294,7 @@ +@@ -250,10 +282,31 @@ + 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) { ++ // Paper start - PreCreatureSpawnEvent ++ private enum PreSpawnStatus { ++ FAIL, ++ SUCCESS, ++ CANCELLED, ++ ABORT ++ } ++ private static PreSpawnStatus isValidSpawnPostitionForType(ServerLevel world, MobCategory group, StructureManager structureAccessor, ChunkGenerator chunkGenerator, MobSpawnSettings.SpawnerData spawnEntry, BlockPos.MutableBlockPos pos, double squaredDistance) { ++ // Paper end - PreCreatureSpawnEvent + EntityType entitytypes = spawnEntry.type; + +- return entitytypes.getCategory() == MobCategory.MISC ? false : (!entitytypes.canSpawnFarFromPlayer() && squaredDistance > (double) (entitytypes.getCategory().getDespawnDistance() * entitytypes.getCategory().getDespawnDistance()) ? false : (entitytypes.canSummon() && NaturalSpawner.canSpawnMobAt(world, structureAccessor, chunkGenerator, group, spawnEntry, pos) ? (!SpawnPlacements.isSpawnPositionOk(entitytypes, world, pos) ? false : (!SpawnPlacements.checkSpawnRules(entitytypes, world, EntitySpawnReason.NATURAL, pos, world.random) ? false : world.noCollision(entitytypes.getSpawnAABB((double) pos.getX() + 0.5D, (double) pos.getY(), (double) pos.getZ() + 0.5D)))) : false)); ++ // Paper start - PreCreatureSpawnEvent ++ com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent event = new com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent( ++ io.papermc.paper.util.MCUtil.toLocation(world, pos), ++ org.bukkit.craftbukkit.entity.CraftEntityType.minecraftToBukkit(entitytypes), SpawnReason.NATURAL ++ ); ++ if (!event.callEvent()) { ++ if (event.shouldAbortSpawn()) { ++ return PreSpawnStatus.ABORT; ++ } ++ return PreSpawnStatus.CANCELLED; ++ } ++ // Paper end - PreCreatureSpawnEvent ++ ++ return entitytypes.getCategory() == MobCategory.MISC ? PreSpawnStatus.FAIL : (!entitytypes.canSpawnFarFromPlayer() && squaredDistance > (double) (entitytypes.getCategory().getDespawnDistance() * entitytypes.getCategory().getDespawnDistance()) ? PreSpawnStatus.FAIL : (entitytypes.canSummon() && NaturalSpawner.canSpawnMobAt(world, structureAccessor, chunkGenerator, group, spawnEntry, pos) ? (!SpawnPlacements.isSpawnPositionOk(entitytypes, world, pos) ? PreSpawnStatus.FAIL : (!SpawnPlacements.checkSpawnRules(entitytypes, world, EntitySpawnReason.NATURAL, pos, world.random) ? PreSpawnStatus.FAIL : world.noCollision(entitytypes.getSpawnAABB((double) pos.getX() + 0.5D, (double) pos.getY(), (double) pos.getZ() + 0.5D)) ? PreSpawnStatus.SUCCESS : PreSpawnStatus.FAIL)) : PreSpawnStatus.FAIL)); // Paper - PreCreatureSpawnEvent + } + + @Nullable +@@ -268,6 +321,7 @@ NaturalSpawner.LOGGER.warn("Can't spawn entity of type: {}", BuiltInRegistries.ENTITY_TYPE.getKey(type)); } catch (Exception exception) { NaturalSpawner.LOGGER.warn("Failed to create mob", exception); @@ -76,7 +125,7 @@ } return null; -@@ -356,6 +383,7 @@ +@@ -356,6 +410,7 @@ entity = biomesettingsmobs_c.type.create(world.getLevel(), EntitySpawnReason.NATURAL); } catch (Exception exception) { NaturalSpawner.LOGGER.warn("Failed to create mob", exception); @@ -84,7 +133,7 @@ continue; } -@@ -369,7 +397,7 @@ +@@ -369,7 +424,7 @@ if (entityinsentient.checkSpawnRules(world, EntitySpawnReason.CHUNK_GENERATION) && entityinsentient.checkSpawnObstruction(world)) { groupdataentity = entityinsentient.finalizeSpawn(world, world.getCurrentDifficultyAt(entityinsentient.blockPosition()), EntitySpawnReason.CHUNK_GENERATION, groupdataentity); @@ -93,7 +142,7 @@ flag = true; } } -@@ -482,10 +510,12 @@ +@@ -482,10 +537,12 @@ return this.unmodifiableMobCategoryCounts; }