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/src/main/java/io/papermc/paper/configuration/WorldConfiguration.java b/src/main/java/io/papermc/paper/configuration/WorldConfiguration.java index 95e96fe8aa93efbbb2e0a7dd98377fdc4fe0e6dd..b6728bb0c1974eb236a7e8429b936492ff490bdd 100644 --- a/src/main/java/io/papermc/paper/configuration/WorldConfiguration.java +++ b/src/main/java/io/papermc/paper/configuration/WorldConfiguration.java @@ -188,6 +188,15 @@ public class WorldConfiguration extends ConfigurationPart { @MergeMap public Reference2IntMap ticksPerSpawn = Util.make(new Reference2IntOpenHashMap<>(NaturalSpawner.SPAWNING_CATEGORIES.length), map -> Arrays.stream(NaturalSpawner.SPAWNING_CATEGORIES).forEach(mobCategory -> map.put(mobCategory, -1))); + public SpawningThrottle spawningThrottle; + + public class SpawningThrottle extends ConfigurationPart { + public IntOr.Disabled failedAttemptsThreshold = IntOr.Disabled.DISABLED; + + @MergeMap + public Reference2IntMap throttledTicksPerSpawn = Util.make(new Reference2IntOpenHashMap<>(NaturalSpawner.SPAWNING_CATEGORIES.length), map -> Arrays.stream(NaturalSpawner.SPAWNING_CATEGORIES).forEach(mobCategory -> map.put(mobCategory, -1))); + } + @ConfigSerializable public record DespawnRangePair(@Required DespawnRange hard, @Required DespawnRange soft) { public static DespawnRangePair createDefault() { diff --git a/src/main/java/net/minecraft/world/level/NaturalSpawner.java b/src/main/java/net/minecraft/world/level/NaturalSpawner.java index 7e1a007ed2f2c625f01fea210c9935cb02d119e0..8e9ada03e5134d2f776bbbf872087121f649c19d 100644 --- a/src/main/java/net/minecraft/world/level/NaturalSpawner.java +++ b/src/main/java/net/minecraft/world/level/NaturalSpawner.java @@ -138,8 +138,14 @@ public final class NaturalSpawner { boolean spawnThisTick = true; int limit = enumcreaturetype.getMaxInstancesPerChunk(); SpawnCategory spawnCategory = CraftSpawnCategory.toBukkit(enumcreaturetype); + // Paper start - throttle failed spawn attempts + long ticksPerSpawn = world.ticksPerSpawnCategory.getLong(spawnCategory); + if (world.paperConfig().entities.spawning.spawningThrottle.failedAttemptsThreshold.test(threshold -> chunk.failedSpawnAttempts[enumcreaturetype.ordinal()] > threshold)) { + ticksPerSpawn = Math.max(ticksPerSpawn, world.paperConfig().entities.spawning.spawningThrottle.throttledTicksPerSpawn.getOrDefault(enumcreaturetype, -1)); + } + // Paper end - throttle failed spawn attempts if (CraftSpawnCategory.isValidForLimits(spawnCategory)) { - spawnThisTick = world.ticksPerSpawnCategory.getLong(spawnCategory) != 0 && worlddata.getGameTime() % world.ticksPerSpawnCategory.getLong(spawnCategory) == 0; + spawnThisTick = world.ticksPerSpawnCategory.getLong(spawnCategory) != 0 && worlddata.getGameTime() % ticksPerSpawn == 0; // Paper - throttle failed spawn attempts limit = world.getWorld().getSpawnLimit(spawnCategory); } @@ -176,6 +182,13 @@ public final class NaturalSpawner { difference, world.paperConfig().entities.spawning.perPlayerMobSpawns ? world.getChunkSource().chunkMap::updatePlayerMobTypeMap : null); info.mobCategoryCounts.mergeInt(enumcreaturetype, spawnCount, Integer::sum); // Paper end - Optional per player mob spawns + // Paper start - throttle failed spawn attempts + if (spawnCount == 0) { + chunk.failedSpawnAttempts[enumcreaturetype.ordinal()]++; + } else { + chunk.failedSpawnAttempts[enumcreaturetype.ordinal()] = 0; + } + // Paper end - throttle failed spawn attempts } } } diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java b/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java index 5bb8fe022f580a626a99324f53515890a99b798d..5cee82d3d17739858e47f24ec5340233f09d0713 100644 --- a/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java +++ b/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java @@ -84,6 +84,7 @@ public abstract class ChunkAccess implements BlockGetter, BiomeManager.NoiseBiom public final Map blockEntities = new Object2ObjectOpenHashMap(); protected final LevelHeightAccessor levelHeightAccessor; protected final LevelChunkSection[] sections; + public final long[] failedSpawnAttempts = new long[net.minecraft.world.entity.MobCategory.values().length]; // Paper - throttle failed spawn attempts // Leaf start - Matter - Feature Secure Seed private boolean slimeChunk; diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java index 4bd048387651250135f963303c78c17f8473cfee..8257177404b23e131143017f3a7d835ad447f9fb 100644 --- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java +++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java @@ -302,6 +302,16 @@ public class ChunkSerializer { ((ChunkAccess) object1).addPackedPostProcess(nbttaglist2.getShort(i1), j1); } } + // Paper start - throttle failed spawn attempts + if (nbt.contains("Paper.FailedSpawnAttempts", Tag.TAG_COMPOUND)) { + CompoundTag failedSpawnAttemptsTag = nbt.getCompound("Paper.FailedSpawnAttempts"); + for (net.minecraft.world.entity.MobCategory mobCategory : net.minecraft.world.level.NaturalSpawner.SPAWNING_CATEGORIES) { + if (failedSpawnAttemptsTag.contains(mobCategory.getSerializedName(), Tag.TAG_LONG)) { + ((ChunkAccess) object1).failedSpawnAttempts[mobCategory.ordinal()] = failedSpawnAttemptsTag.getLong(mobCategory.getSerializedName()); + } + } + } + // Paper end - throttle failed spawn attempts ca.spottedleaf.moonrise.patches.starlight.util.SaveUtil.loadLightHook(world, chunkPos, nbt, (ChunkAccess)object1); // Paper - rewrite chunk system - note: it's ok to pass the raw value instead of wrapped @@ -545,6 +555,18 @@ public class ChunkSerializer { nbttagcompound.put("ChunkBukkitValues", chunk.persistentDataContainer.toTagCompound()); } // CraftBukkit end + // Paper start - throttle failed spawn attempts + CompoundTag failedSpawnAttemptsTag = new CompoundTag(); + for (net.minecraft.world.entity.MobCategory mobCategory : net.minecraft.world.entity.MobCategory.values()) { + long failedSpawnAttempts = chunk.failedSpawnAttempts[mobCategory.ordinal()]; + if (failedSpawnAttempts > 0) { + failedSpawnAttemptsTag.putLong(mobCategory.getSerializedName(), failedSpawnAttempts); + } + } + if (!failedSpawnAttemptsTag.isEmpty()) { + nbttagcompound.put("Paper.FailedSpawnAttempts", failedSpawnAttemptsTag); + } + // Paper end - throttle failed spawn attempts ca.spottedleaf.moonrise.patches.starlight.util.SaveUtil.saveLightHook(world, chunk, nbttagcompound); // Paper - rewrite chunk system return nbttagcompound; }