From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: froobynooby Date: Wed, 17 Jul 2024 18:46:11 +0930 Subject: [PATCH] Paper PR: Throttle failed spawn attempts Original license: GPLv3 Original project: https://github.com/PaperMC/Paper Paper pull request: https://github.com/PaperMC/Paper/pull/11099 For example config in paper-world-defaults.yml ``` spawning-throttle: failed-attempts-threshold: 1200 throttled-ticks-per-spawn: ambient: 10 # default value in bukkit.yml tickers-per * 10 axolotls: 10 creature: 4000 monster: 10 underground_water_creature: 10 water_ambient: 10 water_creature: 10 ``` This patch adds the option to use longer ticks-per-spawn for a given mob type in chunks where spawn attempts are consecutively failing. This behaviour is particularly useful on servers where players build mob farms. Mob farm designs often require making surrounding chunks spawnproof, which causes the server to waste CPU cycles trying to spawn mobs in vain. Throttling spawn attempts in suspected spawnproof chunks improves performance without noticeably advantaging or disadvantaging the mob farm. diff --git a/net/minecraft/world/level/NaturalSpawner.java b/net/minecraft/world/level/NaturalSpawner.java index 102d88fc2989f80a39826e50ee706d853bfb2c5e..838146e997a2033c3d2a96602a252178093d263e 100644 --- a/net/minecraft/world/level/NaturalSpawner.java +++ b/net/minecraft/world/level/NaturalSpawner.java @@ -158,10 +158,21 @@ public final class NaturalSpawner { // Copied from getFilteredSpawningCategories int limit = mobCategory.getMaxInstancesPerChunk(); org.bukkit.entity.SpawnCategory spawnCategory = org.bukkit.craftbukkit.util.CraftSpawnCategory.toBukkit(mobCategory); + // Paper PR start - throttle failed spawn attempts + boolean spawnThisTick = true; + long ticksPerSpawn = level.ticksPerSpawnCategory.getLong(spawnCategory); + long ticksPerSpawnTmp = ticksPerSpawn; + io.papermc.paper.configuration.WorldConfiguration.Entities.Spawning.SpawningThrottle spawningThrottle = level.paperConfig().entities.spawning.spawningThrottle; + if (spawningThrottle.failedAttemptsThreshold.test(threshold -> chunk.failedSpawnAttempts[mobCategory.ordinal()] > threshold)) { + ticksPerSpawn = Math.max(ticksPerSpawn, spawningThrottle.throttledTicksPerSpawn.getOrDefault(mobCategory, -1)); + } + // Paper PR end - throttle failed spawn attempts if (org.bukkit.craftbukkit.util.CraftSpawnCategory.isValidForLimits(spawnCategory)) { + spawnThisTick = ticksPerSpawnTmp != 0 && level.getGameTime() % ticksPerSpawn == 0; // Paper PR - throttle failed spawn attempts limit = level.getWorld().getSpawnLimit(spawnCategory); } + if (!spawningThrottle.failedAttemptsThreshold.enabled() || spawnThisTick) { // Paper PR - throttle failed spawn attempts // Apply per-player limit int minDiff = Integer.MAX_VALUE; final ca.spottedleaf.moonrise.common.list.ReferenceList inRange = @@ -175,12 +186,20 @@ public final class NaturalSpawner { maxSpawns = (minDiff == Integer.MAX_VALUE) ? 0 : minDiff; canSpawn = maxSpawns > 0; + } else { canSpawn = false; } // Paper PR - throttle failed spawn attempts } else { canSpawn = spawnState.canSpawnForCategoryLocal(mobCategory, chunk.getPos()); } if (canSpawn) { - spawnCategoryForChunk(mobCategory, level, chunk, spawnState::canSpawn, spawnState::afterSpawn, - maxSpawns, level.paperConfig().entities.spawning.perPlayerMobSpawns ? level.getChunkSource().chunkMap::updatePlayerMobTypeMap : null); + // Paper PR start - throttle failed spawn attempts + int spawnCount = spawnCategoryForChunk(mobCategory, level, chunk, spawnState::canSpawn, spawnState::afterSpawn, + maxSpawns, level.paperConfig().entities.spawning.perPlayerMobSpawns ? level.getChunkSource().chunkMap::updatePlayerMobTypeMap : null, false); + if (spawnCount == 0) { + chunk.failedSpawnAttempts[mobCategory.ordinal()]++; + } else { + chunk.failedSpawnAttempts[mobCategory.ordinal()] = 0; + } + // Paper end PR - throttle failed spawn attempts // Paper end - Optional per player mob spawns } } @@ -204,12 +223,21 @@ public final class NaturalSpawner { } public static void spawnCategoryForChunk( MobCategory category, ServerLevel level, LevelChunk chunk, NaturalSpawner.SpawnPredicate filter, NaturalSpawner.AfterSpawnCallback callback, final int maxSpawns, final Consumer trackEntity + // Paper PR start - throttle failed spawn attempts + ) { + spawnCategoryForChunk(category, level, chunk, filter, callback, maxSpawns, trackEntity, false); + } + public static int spawnCategoryForChunk( + MobCategory category, ServerLevel level, LevelChunk chunk, NaturalSpawner.SpawnPredicate filter, NaturalSpawner.AfterSpawnCallback callback, final int maxSpawns, final Consumer trackEntity, final boolean nothing + // Paper PR end - throttle failed spawn attempts ) { // Paper end - Optional per player mob spawns BlockPos randomPosWithin = getRandomPosWithin(level, chunk); if (randomPosWithin.getY() >= level.getMinY() + 1) { - spawnCategoryForPosition(category, level, chunk, randomPosWithin, filter, callback, maxSpawns, trackEntity); // Paper - Optional per player mob spawns + return spawnCategoryForPosition(category, level, chunk, randomPosWithin, filter, callback, maxSpawns, trackEntity, false); // Paper - Optional per player mob spawns // Paper PR - throttle failed spawn attempts } + + return 0; // Paper PR - throttle failed spawn attempts } @VisibleForDebug @@ -229,15 +257,23 @@ public final class NaturalSpawner { } public static void spawnCategoryForPosition( MobCategory category, ServerLevel level, ChunkAccess chunk, BlockPos pos, NaturalSpawner.SpawnPredicate filter, NaturalSpawner.AfterSpawnCallback callback, final int maxSpawns, final @Nullable Consumer trackEntity + // Paper PR start - throttle failed spawn attempts + ) { + spawnCategoryForPosition(category, level, chunk, pos, filter, callback, maxSpawns, trackEntity, false); + } + public static int spawnCategoryForPosition( + MobCategory category, ServerLevel level, ChunkAccess chunk, BlockPos pos, NaturalSpawner.SpawnPredicate filter, NaturalSpawner.AfterSpawnCallback callback, final int maxSpawns, final @Nullable Consumer trackEntity, final boolean nothing + // Paper PR end - throttle failed spawn attempts ) { // Paper end - Optional per player mob spawns StructureManager structureManager = level.structureManager(); ChunkGenerator generator = level.getChunkSource().getGenerator(); int y = pos.getY(); + int i = 0; // Paper PR - throttle failed spawn attempts BlockState blockState = level.getBlockStateIfLoadedAndInBounds(pos); // Paper - don't load chunks for mob spawn if (blockState != null && !blockState.isRedstoneConductor(chunk, pos)) { // Paper - don't load chunks for mob spawn BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos(); - int i = 0; + //int i = 0; // Paper PR - throttle failed spawn attempts - move up for (int i1 = 0; i1 < 3; i1++) { int x = pos.getX(); @@ -278,14 +314,14 @@ public final class NaturalSpawner { } // Paper end - per player mob count backoff if (doSpawning == PreSpawnStatus.ABORT) { - return; + return i; // Paper PR - throttle failed spawn attempts } if (doSpawning == PreSpawnStatus.SUCCESS // Paper end - PreCreatureSpawnEvent && filter.test(spawnerData.type(), mutableBlockPos, chunk)) { Mob mobForSpawn = getMobForSpawn(level, spawnerData.type()); if (mobForSpawn == null) { - return; + return i; // Paper PR - throttle failed spawn attempts } mobForSpawn.snapTo(d, y, d1, level.random.nextFloat() * 360.0F, 0.0F); @@ -308,7 +344,7 @@ public final class NaturalSpawner { } // CraftBukkit end if (i >= mobForSpawn.getMaxSpawnClusterSize() || i >= maxSpawns) { // Paper - Optional per player mob spawns - return; + return i; // Paper PR - throttle failed spawn attempts } if (mobForSpawn.isMaxGroupSizeReached(i3)) { @@ -321,6 +357,8 @@ public final class NaturalSpawner { } } } + + return i; // Paper PR - throttle failed spawn attempts } private static boolean isRightDistanceToPlayerAndSpawnPoint(ServerLevel level, ChunkAccess chunk, BlockPos.MutableBlockPos pos, double distance) { diff --git a/net/minecraft/world/level/chunk/ChunkAccess.java b/net/minecraft/world/level/chunk/ChunkAccess.java index 686c031ec73acc80683aaa39a78fe9221f0215a6..7fc5e56586f2fe8ea1a7437b42da893383878880 100644 --- a/net/minecraft/world/level/chunk/ChunkAccess.java +++ b/net/minecraft/world/level/chunk/ChunkAccess.java @@ -92,6 +92,7 @@ public abstract class ChunkAccess implements BiomeManager.NoiseBiomeSource, Ligh private boolean slimeChunk; private boolean hasComputedSlimeChunk; // Leaf end - Matter - Secure Seed + public final long[] failedSpawnAttempts = new long[net.minecraft.world.entity.MobCategory.values().length]; // Paper PR - throttle failed spawn attempts // Paper start - rewrite chunk system private volatile ca.spottedleaf.moonrise.patches.starlight.light.SWMRNibbleArray[] blockNibbles; diff --git a/net/minecraft/world/level/chunk/storage/SerializableChunkData.java b/net/minecraft/world/level/chunk/storage/SerializableChunkData.java index e04d3479383cd480cf35ed7ac3c82e7f6fb69e28..08b98e60518027a6d0aa99d03ba02982355ffa76 100644 --- a/net/minecraft/world/level/chunk/storage/SerializableChunkData.java +++ b/net/minecraft/world/level/chunk/storage/SerializableChunkData.java @@ -94,6 +94,7 @@ public record SerializableChunkData( List blockEntities, CompoundTag structureData , @Nullable net.minecraft.nbt.Tag persistentDataContainer // CraftBukkit - persistentDataContainer + , @Nullable long[] failedSpawnAttempts // Paper PR - throttle failed spawn attempts ) { public static final Codec> BLOCK_STATE_CODEC = PalettedContainer.codecRW( Block.BLOCK_STATE_REGISTRY, BlockState.CODEC, PalettedContainer.Strategy.SECTION_STATES, Blocks.AIR.defaultBlockState(), null // Paper - Anti-Xray @@ -190,6 +191,19 @@ public record SerializableChunkData( lists[i] = list2; } + // Paper PR start - throttle failed spawn attempts + long[] failedSpawnAttemptsData = null; + if (tag.contains("Paper.FailedSpawnAttempts")) { + failedSpawnAttemptsData = new long[net.minecraft.world.entity.MobCategory.values().length]; + CompoundTag failedSpawnAttemptsTag = tag.getCompoundOrEmpty("Paper.FailedSpawnAttempts"); + for (net.minecraft.world.entity.MobCategory mobCategory : net.minecraft.world.level.NaturalSpawner.SPAWNING_CATEGORIES) { + if (failedSpawnAttemptsTag.contains(mobCategory.getSerializedName())) { + failedSpawnAttemptsData[mobCategory.ordinal()] = failedSpawnAttemptsTag.getLongOr(mobCategory.getSerializedName(), 0); + } + } + } + // Paper PR end - throttle failed spawn attempts + List list3 = tag.getList("entities").stream().flatMap(ListTag::compoundStream).toList(); List list4 = tag.getList("block_entities").stream().flatMap(ListTag::compoundStream).toList(); CompoundTag compoundOrEmpty = tag.getCompoundOrEmpty("structures"); @@ -270,6 +284,7 @@ public record SerializableChunkData( list4, compoundOrEmpty , tag.get("ChunkBukkitValues") // CraftBukkit - ChunkBukkitValues + , failedSpawnAttemptsData // Paper PR - throttle failed spawn attempts ); } } @@ -426,6 +441,13 @@ public record SerializableChunkData( chunkAccess.addPackedPostProcess(this.postProcessingSections[i], i); } + // Paper PR start - throttle failed spawn attempts + long[] failedSpawnAttemptsData = this.failedSpawnAttempts; + if (failedSpawnAttemptsData != null) { + System.arraycopy(failedSpawnAttemptsData, 0, chunkAccess.failedSpawnAttempts, 0, failedSpawnAttemptsData.length); + } + // Paper PR end - throttle failed spawn attempts + if (chunkType == ChunkType.LEVELCHUNK) { return this.loadStarlightLightData(level, new ImposterProtoChunk((LevelChunk)chunkAccess, false)); // Paper - starlight } else { @@ -556,6 +578,7 @@ public record SerializableChunkData( persistentDataContainer = chunk.persistentDataContainer.toTagCompound(); } // CraftBukkit end + final long[] failedSpawnAttemptsData = chunk.failedSpawnAttempts; // Paper PR - throttle failed spawn attempts return new SerializableChunkData( level.registryAccess().lookupOrThrow(Registries.BIOME), pos, @@ -576,6 +599,7 @@ public record SerializableChunkData( list1, compoundTag , persistentDataContainer // CraftBukkit - persistentDataContainer + , failedSpawnAttemptsData // Paper PR - throttle failed spawn attempts ); } } @@ -660,6 +684,21 @@ public record SerializableChunkData( compoundTag.put("ChunkBukkitValues", this.persistentDataContainer); } // CraftBukkit end + // Paper PR start - throttle failed spawn attempts + CompoundTag failedSpawnAttemptsTag = new CompoundTag(); + long[] failedSpawnAttemptsData = this.failedSpawnAttempts; + if (failedSpawnAttemptsData != null) { + for (net.minecraft.world.entity.MobCategory mobCategory : net.minecraft.world.entity.MobCategory.values()) { + long failedAttempts = failedSpawnAttemptsData[mobCategory.ordinal()]; + if (failedAttempts > 0) { + failedSpawnAttemptsTag.putLong(mobCategory.getSerializedName(), failedAttempts); + } + } + } + if (!failedSpawnAttemptsTag.isEmpty()) { + compoundTag.put("Paper.FailedSpawnAttempts", failedSpawnAttemptsTag); + } + // Paper PR end - throttle failed spawn attempts // Paper start - starlight if (this.lightCorrect && !this.chunkStatus.isBefore(net.minecraft.world.level.chunk.status.ChunkStatus.LIGHT)) { // clobber vanilla value to force vanilla to relight @@ -875,4 +914,50 @@ public record SerializableChunkData( } // Paper end - starlight - convert from record } + + // Paper PR start - throttle failed spawn attempts + // For plugin compatibility + public SerializableChunkData( + Registry biomeRegistry, + ChunkPos chunkPos, + int minSectionY, + long lastUpdateTime, + long inhabitedTime, + ChunkStatus chunkStatus, + @Nullable BlendingData.Packed blendingData, + @Nullable BelowZeroRetrogen belowZeroRetrogen, + UpgradeData upgradeData, + @Nullable long[] carvingMask, + Map heightmaps, + ChunkAccess.PackedTicks packedTicks, + ShortList[] postProcessingSections, + boolean lightCorrect, + List sectionData, + List entities, + List blockEntities, + CompoundTag structureData, + @Nullable net.minecraft.nbt.Tag persistentDataContainer // CraftBukkit - persistentDataContainer + ) { + this(biomeRegistry, + chunkPos, + minSectionY, + lastUpdateTime, + inhabitedTime, + chunkStatus, + blendingData, + belowZeroRetrogen, + upgradeData, + carvingMask, + heightmaps, + packedTicks, + postProcessingSections, + lightCorrect, + sectionData, + entities, + blockEntities, + structureData, + persistentDataContainer, + null); + } + // Paper PR end - throttle failed spawn attempts }