9
0
mirror of https://github.com/Winds-Studio/Leaf.git synced 2025-12-27 02:49:19 +00:00

Merge remote-tracking branch 'origin/ver/1.21.4' into ver/1.21.4

This commit is contained in:
Taiyou06
2025-03-19 19:41:12 +01:00
8 changed files with 432 additions and 194 deletions

View File

@@ -1,140 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: froobynooby <froobynooby@froobworld.com>
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 c867796f625813797f167610ad443c4be5a7561e..04d13abd8d98f4e72732afbdbb9719d835389793 100644
--- a/src/main/java/io/papermc/paper/configuration/WorldConfiguration.java
+++ b/src/main/java/io/papermc/paper/configuration/WorldConfiguration.java
@@ -190,6 +190,15 @@ public class WorldConfiguration extends ConfigurationPart {
@MergeMap
public Reference2IntMap<MobCategory> 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<MobCategory> 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 95a000dbf1a05cfd8182f15c0e0bbf7023578974..455c7e66fc1ff28c4f720a01ff11aa408446b42e 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);
}
@@ -172,8 +178,13 @@ public final class NaturalSpawner {
Objects.requireNonNull(info);
// Paper start - Optional per player mob spawns
- NaturalSpawner.spawnCategoryForChunk(enumcreaturetype, world, chunk, spawnercreature_c, info::afterSpawn,
- difference, world.paperConfig().entities.spawning.perPlayerMobSpawns ? world.getChunkSource().chunkMap::updatePlayerMobTypeMap : null);
+ // Paper start - throttle failed spawn attempts
+ if (NaturalSpawner.spawnCategoryForChunk(enumcreaturetype, world, chunk, spawnercreature_c, info::afterSpawn, difference, world.paperConfig().entities.spawning.perPlayerMobSpawns ? world.getChunkSource().chunkMap::updatePlayerMobTypeMap : null) == 0) {
+ chunk.failedSpawnAttempts[enumcreaturetype.ordinal()]++;
+ } else {
+ chunk.failedSpawnAttempts[enumcreaturetype.ordinal()] = 0;
+ }
+ // Paper end - throttle failed spawn attempts
// Paper end - Optional per player mob spawns
}
}
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<BlockPos, BlockEntity> 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;
}

View File

@@ -12,18 +12,10 @@ In non-strict test, this can give ~60-110% improvement (524ms on Paper, 204ms on
under 625 villagers situation.
diff --git a/net/minecraft/world/entity/ai/sensing/NearestLivingEntitySensor.java b/net/minecraft/world/entity/ai/sensing/NearestLivingEntitySensor.java
index b0c5e41fefc7c9adf1a61bd5b52861736657d37e..5ba8bc485d1c06f16060789dd42a82cc66b5bcc0 100644
index b0c5e41fefc7c9adf1a61bd5b52861736657d37e..9ef531551bb135d4b6a1ca9c3320e35fa62d7c41 100644
--- a/net/minecraft/world/entity/ai/sensing/NearestLivingEntitySensor.java
+++ b/net/minecraft/world/entity/ai/sensing/NearestLivingEntitySensor.java
@@ -1,7 +1,6 @@
package net.minecraft.world.entity.ai.sensing;
import com.google.common.collect.ImmutableSet;
-import java.util.Comparator;
import java.util.List;
import java.util.Set;
import net.minecraft.server.level.ServerLevel;
@@ -13,18 +12,92 @@ import net.minecraft.world.entity.ai.memory.NearestVisibleLivingEntities;
@@ -13,18 +13,92 @@ import net.minecraft.world.entity.ai.memory.NearestVisibleLivingEntities;
import net.minecraft.world.phys.AABB;
public class NearestLivingEntitySensor<T extends LivingEntity> extends Sensor<T> {
@@ -63,7 +55,7 @@ index b0c5e41fefc7c9adf1a61bd5b52861736657d37e..5ba8bc485d1c06f16060789dd42a82cc
+ bits[i] = Double.doubleToRawLongBits(reference.distanceToSqr(entities[i]));
+ }
+
+ fastRadixSort(entities, bits, 0, entities.length-1, 62);
+ fastRadixSort(entities, bits, 0, entities.length - 1, 62);
+ return entities;
+ }
+
@@ -87,19 +79,19 @@ index b0c5e41fefc7c9adf1a61bd5b52861736657d37e..5ba8bc485d1c06f16060789dd42a82cc
+ }
+ }
+
+ if (low < j) fastRadixSort(ents, bits, low, j, bit-1);
+ if (i < high) fastRadixSort(ents, bits, i, high, bit-1);
+ if (low < j) fastRadixSort(ents, bits, low, j, bit - 1);
+ if (i < high) fastRadixSort(ents, bits, i, high, bit - 1);
+ }
+
+ private void insertionSort(LivingEntity[] ents, long[] bits, int low, int high) {
+ for (int i = low+1; i <= high; i++) {
+ for (int i = low + 1; i <= high; i++) {
+ int j = i;
+ LivingEntity e = ents[j];
+ long b = bits[j];
+
+ while (j > low && bits[j-1] > b) {
+ ents[j] = ents[j-1];
+ bits[j] = bits[j-1];
+ while (j > low && bits[j - 1] > b) {
+ ents[j] = ents[j - 1];
+ bits[j] = bits[j - 1];
+ j--;
+ }
+

View File

@@ -9,7 +9,7 @@ Paper: 3859ms
Leaf : 1363ms (-64% reduction)
diff --git a/net/minecraft/world/entity/ai/behavior/SetLookAndInteract.java b/net/minecraft/world/entity/ai/behavior/SetLookAndInteract.java
index 13359e484486a5280f408955fe2a365cd3c34a43..d326bf267123ff26d85e63aeb273baf4b59613d6 100644
index 13359e484486a5280f408955fe2a365cd3c34a43..48c30c8b686fdc82481bd72d6d10f541e9eb0217 100644
--- a/net/minecraft/world/entity/ai/behavior/SetLookAndInteract.java
+++ b/net/minecraft/world/entity/ai/behavior/SetLookAndInteract.java
@@ -9,7 +9,7 @@ import net.minecraft.world.entity.ai.memory.NearestVisibleLivingEntities;
@@ -17,16 +17,17 @@ index 13359e484486a5280f408955fe2a365cd3c34a43..d326bf267123ff26d85e63aeb273baf4
public class SetLookAndInteract {
public static BehaviorControl<LivingEntity> create(EntityType<?> entityType, int maxDist) {
- int i = maxDist * maxDist;
+ final int maxDistSq = maxDist * maxDist;
+ final int maxDistSq = maxDist * maxDist; // Leaf - Optimize SetLookAndInteract and NearestVisibleLivingEntities
return BehaviorBuilder.create(
instance -> instance.group(
instance.registered(MemoryModuleType.LOOK_TARGET),
@@ -19,16 +19,20 @@ public class SetLookAndInteract {
@@ -19,16 +19,22 @@ public class SetLookAndInteract {
.apply(
instance,
(lookTarget, interactionTarget, nearestVisibleLivingEntities) -> (level, entity, gameTime) -> {
- Optional<LivingEntity> optional = instance.<NearestVisibleLivingEntities>get(nearestVisibleLivingEntities)
- .findClosest(nearEntity -> nearEntity.distanceToSqr(entity) <= i && entityType.equals(nearEntity.getType()));
+ // Leaf start - Optimize SetLookAndInteract and NearestVisibleLivingEntities
+ // Check entity type first as it's likely cheaper than distance calculation
+ NearestVisibleLivingEntities entities = instance.get(nearestVisibleLivingEntities);
+ Optional<LivingEntity> optional = entities.findClosest(
@@ -46,33 +47,22 @@ index 13359e484486a5280f408955fe2a365cd3c34a43..d326bf267123ff26d85e63aeb273baf4
+ interactionTarget.set(livingEntity);
+ lookTarget.set(new EntityTracker(livingEntity, true));
+ return true;
+ // Leaf end - Optimize SetLookAndInteract and NearestVisibleLivingEntities
}
)
);
diff --git a/net/minecraft/world/entity/ai/memory/NearestVisibleLivingEntities.java b/net/minecraft/world/entity/ai/memory/NearestVisibleLivingEntities.java
index 2b973a3ba7d65330fa4690e71e5321c28457ec61..99a3c44b95ed0a14d272c5eb07b7db90aaa6e6af 100644
index 2b973a3ba7d65330fa4690e71e5321c28457ec61..82de8e3bc462638bffbb43456076de2670d76540 100644
--- a/net/minecraft/world/entity/ai/memory/NearestVisibleLivingEntities.java
+++ b/net/minecraft/world/entity/ai/memory/NearestVisibleLivingEntities.java
@@ -2,10 +2,11 @@ package net.minecraft.world.entity.ai.memory;
import com.google.common.collect.Iterables;
import it.unimi.dsi.fastutil.objects.Object2BooleanOpenHashMap;
+import it.unimi.dsi.fastutil.objects.ObjectArrayList;
+import it.unimi.dsi.fastutil.objects.ObjectList;
import java.util.List;
import java.util.Optional;
import java.util.function.Predicate;
-import java.util.stream.Stream;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.ai.sensing.Sensor;
@@ -32,9 +33,26 @@ public class NearestVisibleLivingEntities {
@@ -32,11 +32,30 @@ public class NearestVisibleLivingEntities {
}
public Optional<LivingEntity> findClosest(Predicate<LivingEntity> predicate) {
- for (LivingEntity livingEntity : this.nearbyEntities) {
- if (predicate.test(livingEntity) && this.lineOfSightTest.test(livingEntity)) {
- return Optional.of(livingEntity);
+ // Leaf start - Optimize SetLookAndInteract and NearestVisibleLivingEntities
+ // Early return if no entities
+ if (this.nearbyEntities.isEmpty()) {
+ return Optional.empty();
@@ -95,15 +85,21 @@ index 2b973a3ba7d65330fa4690e71e5321c28457ec61..99a3c44b95ed0a14d272c5eb07b7db90
+ }
}
}
+ // Leaf end - Optimize SetLookAndInteract and NearestVisibleLivingEntities
@@ -45,8 +63,14 @@ public class NearestVisibleLivingEntities {
return Iterables.filter(this.nearbyEntities, target -> predicate.test(target) && this.lineOfSightTest.test(target)); // Leaf - Optimize baby villager sensor - diff on change
return Optional.empty();
}
@@ -46,8 +65,20 @@ public class NearestVisibleLivingEntities {
}
- public Stream<LivingEntity> find(Predicate<LivingEntity> predicate) {
public Stream<LivingEntity> find(Predicate<LivingEntity> predicate) {
- return this.nearbyEntities.stream().filter(target -> predicate.test(target) && this.lineOfSightTest.test(target));
+ public List<LivingEntity> find(Predicate<LivingEntity> predicate) {
+ ObjectList<LivingEntity> result = new ObjectArrayList<>();
+ return this.nearbyEntities.stream().filter(target -> predicate.test(target) && this.lineOfSightTest.test(target)); // Leaf - Optimize SetLookAndInteract and NearestVisibleLivingEntities - diff on change
+ }
+
+ // Leaf start - Optimize SetLookAndInteract and NearestVisibleLivingEntities
+ public List<LivingEntity> findWithList(Predicate<LivingEntity> predicate) {
+ it.unimi.dsi.fastutil.objects.ObjectList<LivingEntity> result = new it.unimi.dsi.fastutil.objects.ObjectArrayList<>();
+ for (LivingEntity entity : this.nearbyEntities) {
+ if (predicate.test(entity) && this.lineOfSightTest.test(entity)) {
+ result.add(entity);
@@ -111,5 +107,7 @@ index 2b973a3ba7d65330fa4690e71e5321c28457ec61..99a3c44b95ed0a14d272c5eb07b7db90
+ }
+ return result;
}
+ // Leaf end - Optimize SetLookAndInteract and NearestVisibleLivingEntities
public boolean contains(LivingEntity entity) {
return this.nearbyEntities.contains(entity) && this.lineOfSightTest.test(entity);

View File

@@ -7,27 +7,20 @@ This method is maninly visible when ton of villagers suddenly wants to sleep
Safe optimization, no need to provide any numbers.
diff --git a/net/minecraft/world/entity/ai/behavior/InsideBrownianWalk.java b/net/minecraft/world/entity/ai/behavior/InsideBrownianWalk.java
index cbde74f4b6d586a5f80cdd675573441636bf682d..3ac3059ce23d6291aa4e96ed5441159133d3e965 100644
index cbde74f4b6d586a5f80cdd675573441636bf682d..2324b941c7513692608b5146bc90c8e6124d2ee6 100644
--- a/net/minecraft/world/entity/ai/behavior/InsideBrownianWalk.java
+++ b/net/minecraft/world/entity/ai/behavior/InsideBrownianWalk.java
@@ -2,6 +2,7 @@ package net.minecraft.world.entity.ai.behavior;
import java.util.Collections;
import java.util.List;
+import java.util.ArrayList;
import net.minecraft.Util;
import net.minecraft.core.BlockPos;
import net.minecraft.world.entity.PathfinderMob;
@@ -20,16 +21,29 @@ public class InsideBrownianWalk {
@@ -20,16 +20,31 @@ public class InsideBrownianWalk {
return false;
} else {
BlockPos blockPos = mob.blockPosition();
- List<BlockPos> list = BlockPos.betweenClosedStream(blockPos.offset(-1, -1, -1), blockPos.offset(1, 1, 1))
- .map(BlockPos::immutable)
- .collect(Util.toMutableList());
+ // Leaf start - Remove streams on InsideBrownianWalk
+ BlockPos minPos = blockPos.offset(-1, -1, -1);
+ BlockPos maxPos = blockPos.offset(1, 1, 1);
+ List<BlockPos> list = new ArrayList<>();
+ List<BlockPos> list = new java.util.ArrayList<>();
+
+ for (int x = minPos.getX(); x <= maxPos.getX(); x++) {
+ for (int y = minPos.getY(); y <= maxPos.getY(); y++) {
@@ -53,6 +46,7 @@ index cbde74f4b6d586a5f80cdd675573441636bf682d..3ac3059ce23d6291aa4e96ed54411591
+ break;
+ }
+ }
+ // Leaf end - Remove streams on InsideBrownianWalk
+
return true;
}

View File

@@ -0,0 +1,23 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com>
Date: Wed, 19 Mar 2025 13:32:39 -0400
Subject: [PATCH] Validate recipe display index before retrieving it
diff --git a/net/minecraft/world/item/crafting/RecipeManager.java b/net/minecraft/world/item/crafting/RecipeManager.java
index 4bd1b514f91c0a2c9261b41211a4a341f784a995..5b69c4927174611d62f0f4698215ab97c827c7f5 100644
--- a/net/minecraft/world/item/crafting/RecipeManager.java
+++ b/net/minecraft/world/item/crafting/RecipeManager.java
@@ -206,7 +206,11 @@ public class RecipeManager extends SimplePreparableReloadListener<RecipeMap> imp
@Nullable
public RecipeManager.ServerDisplayInfo getRecipeFromDisplay(RecipeDisplayId display) {
- return this.allDisplays.get(display.index());
+ // Leaf start - Validate recipe display index before retrieving it
+ final int index = display.index();
+
+ return this.allDisplays.size() > index ? this.allDisplays.get(index) : null;
+ // Leaf end - Validate recipe display index before retrieving it
}
public void listDisplaysForRecipe(ResourceKey<Recipe<?>> recipe, Consumer<RecipeDisplayEntry> output) {

View File

@@ -0,0 +1,320 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: froobynooby <froobynooby@froobworld.com>
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 d3f5242fc66529bf3137da4d505a6cf55e749e43..ce2621a87dec1befb016b3437ceb2d02ed6d0b75 100644
--- a/net/minecraft/world/level/NaturalSpawner.java
+++ b/net/minecraft/world/level/NaturalSpawner.java
@@ -164,10 +164,21 @@ public final class NaturalSpawner {
// Copied from getFilteredSpawningCategories
int limit = mobCategory.getMaxInstancesPerChunk();
SpawnCategory spawnCategory = CraftSpawnCategory.toBukkit(mobCategory);
+ // Paper 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 end - throttle failed spawn attempts
if (CraftSpawnCategory.isValidForLimits(spawnCategory)) {
+ spawnThisTick = ticksPerSpawnTmp != 0 && level.getGameTime() % ticksPerSpawn == 0; // Paper - throttle failed spawn attempts
limit = level.getWorld().getSpawnLimit(spawnCategory);
}
+ if (!spawningThrottle.failedAttemptsThreshold.enabled() || spawnThisTick) { // Paper - throttle failed spawn attempts
// Apply per-player limit
int minDiff = Integer.MAX_VALUE;
final ca.spottedleaf.moonrise.common.list.ReferenceList<net.minecraft.server.level.ServerPlayer> inRange =
@@ -181,12 +192,20 @@ public final class NaturalSpawner {
maxSpawns = (minDiff == Integer.MAX_VALUE) ? 0 : minDiff;
canSpawn = maxSpawns > 0;
+ } else { canSpawn = false; } // Paper - 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 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 - throttle failed spawn attempts
// Paper end - Optional per player mob spawns
}
}
@@ -210,12 +229,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<Entity> trackEntity
+ // Paper 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<Entity> trackEntity, final boolean nothing
+ // Paper 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 - throttle failed spawn attempts
}
+
+ return 0; // Paper - throttle failed spawn attempts
}
@VisibleForDebug
@@ -235,15 +263,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<Entity> trackEntity
+ // Paper 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<Entity> trackEntity, final boolean nothing
+ // Paper 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 - 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 - throttle failed spawn attempts - move up
for (int i1 = 0; i1 < 3; i1++) {
int x = pos.getX();
@@ -284,13 +320,13 @@ public final class NaturalSpawner {
}
// Paper end - per player mob count backoff
if (doSpawning == PreSpawnStatus.ABORT) {
- return;
+ return i; // Paper - throttle failed spawn attempts
}
if (doSpawning == PreSpawnStatus.SUCCESS && filter.test(spawnerData.type, mutableBlockPos, chunk)) {
// Paper end - PreCreatureSpawnEvent
Mob mobForSpawn = getMobForSpawn(level, spawnerData.type);
if (mobForSpawn == null) {
- return;
+ return i; // Paper - throttle failed spawn attempts
}
mobForSpawn.moveTo(d, y, d1, level.random.nextFloat() * 360.0F, 0.0F);
@@ -313,7 +349,7 @@ public final class NaturalSpawner {
}
// CraftBukkit end
if (i >= mobForSpawn.getMaxSpawnClusterSize() || i >= maxSpawns) { // Paper - Optional per player mob spawns
- return;
+ return i; // Paper - throttle failed spawn attempts
}
if (mobForSpawn.isMaxGroupSizeReached(i3)) {
@@ -326,6 +362,8 @@ public final class NaturalSpawner {
}
}
}
+
+ return i; // Paper - 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 3a6db5bc0c8be7d68e15317a621c1965fdc3a9bd..50a9903367f49ece2a267d10944b1515c7b93859 100644
--- a/net/minecraft/world/level/chunk/ChunkAccess.java
+++ b/net/minecraft/world/level/chunk/ChunkAccess.java
@@ -91,6 +91,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 - 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 c0939c311c554a4660b80725294663bab7915733..e2df93b2500a74c4cecac1515f3991967a07a052 100644
--- a/net/minecraft/world/level/chunk/storage/SerializableChunkData.java
+++ b/net/minecraft/world/level/chunk/storage/SerializableChunkData.java
@@ -92,6 +92,7 @@ public record SerializableChunkData(
List<CompoundTag> blockEntities,
CompoundTag structureData
, @Nullable net.minecraft.nbt.Tag persistentDataContainer // CraftBukkit - persistentDataContainer
+ , @Nullable long[] failedSpawnAttempts // Paper - throttle failed spawn attempts
) {
public static final Codec<PalettedContainer<BlockState>> BLOCK_STATE_CODEC = PalettedContainer.codecRW(
Block.BLOCK_STATE_REGISTRY, BlockState.CODEC, PalettedContainer.Strategy.SECTION_STATES, Blocks.AIR.defaultBlockState(), null // Paper - Anti-Xray
@@ -216,6 +217,19 @@ public record SerializableChunkData(
lists[i] = list4;
}
+ // Paper start - throttle failed spawn attempts
+ long[] failedSpawnAttemptsData = null;
+ if (tag.contains("Paper.FailedSpawnAttempts", net.minecraft.nbt.Tag.TAG_COMPOUND)) {
+ failedSpawnAttemptsData = new long[net.minecraft.world.entity.MobCategory.values().length];
+ CompoundTag failedSpawnAttemptsTag = tag.getCompound("Paper.FailedSpawnAttempts");
+ for (net.minecraft.world.entity.MobCategory mobCategory : net.minecraft.world.level.NaturalSpawner.SPAWNING_CATEGORIES) {
+ if (failedSpawnAttemptsTag.contains(mobCategory.getSerializedName(), net.minecraft.nbt.Tag.TAG_LONG)) {
+ failedSpawnAttemptsData[mobCategory.ordinal()] = failedSpawnAttemptsTag.getLong(mobCategory.getSerializedName());
+ }
+ }
+ }
+ // Paper end - throttle failed spawn attempts
+
List<CompoundTag> list5 = Lists.transform(tag.getList("entities", 10), tag1 -> (CompoundTag)tag1);
List<CompoundTag> list6 = Lists.transform(tag.getList("block_entities", 10), tag1 -> (CompoundTag)tag1);
CompoundTag compound1 = tag.getCompound("structures");
@@ -294,6 +308,7 @@ public record SerializableChunkData(
list6,
compound1
, tag.get("ChunkBukkitValues") // CraftBukkit - ChunkBukkitValues
+ , failedSpawnAttemptsData // Paper - throttle failed spawn attempts
);
}
}
@@ -450,6 +465,15 @@ public record SerializableChunkData(
chunkAccess.addPackedPostProcess(this.postProcessingSections[i], i);
}
+ // Paper start - throttle failed spawn attempts
+ long[] failedSpawnAttemptsData = this.failedSpawnAttempts;
+ if (failedSpawnAttemptsData != null) {
+ for (net.minecraft.world.entity.MobCategory mobCategory : net.minecraft.world.entity.MobCategory.values()) {
+ System.arraycopy(failedSpawnAttemptsData, 0, chunkAccess.failedSpawnAttempts, 0, failedSpawnAttemptsData.length);
+ }
+ }
+ // Paper end - throttle failed spawn attempts
+
if (chunkType == ChunkType.LEVELCHUNK) {
return this.loadStarlightLightData(level, new ImposterProtoChunk((LevelChunk)chunkAccess, false)); // Paper - starlight
} else {
@@ -603,6 +627,7 @@ public record SerializableChunkData(
persistentDataContainer = chunk.persistentDataContainer.toTagCompound();
}
// CraftBukkit end
+ final long[] failedSpawnAttemptsData = chunk.failedSpawnAttempts; // Paper - throttle failed spawn attempts
return new SerializableChunkData(
level.registryAccess().lookupOrThrow(Registries.BIOME),
pos,
@@ -623,6 +648,7 @@ public record SerializableChunkData(
list1,
compoundTag
, persistentDataContainer // CraftBukkit - persistentDataContainer
+ , failedSpawnAttemptsData // Paper - throttle failed spawn attempts
);
}
}
@@ -719,6 +745,21 @@ public record SerializableChunkData(
compoundTag.put("ChunkBukkitValues", this.persistentDataContainer);
}
// CraftBukkit end
+ // Paper 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 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
@@ -947,4 +988,50 @@ public record SerializableChunkData(
}
// Paper end - starlight - convert from record
}
+
+ // Paper start - throttle failed spawn attempts
+ // For plugin compatibility
+ public SerializableChunkData(
+ Registry<Biome> biomeRegistry,
+ ChunkPos chunkPos,
+ int minSectionY,
+ long lastUpdateTime,
+ long inhabitedTime,
+ ChunkStatus chunkStatus,
+ @Nullable BlendingData.Packed blendingData,
+ @Nullable BelowZeroRetrogen belowZeroRetrogen,
+ UpgradeData upgradeData,
+ @Nullable long[] carvingMask,
+ Map<Heightmap.Types, long[]> heightmaps,
+ ChunkAccess.PackedTicks packedTicks,
+ ShortList[] postProcessingSections,
+ boolean lightCorrect,
+ List<net.minecraft.world.level.chunk.storage.SerializableChunkData.SectionData> sectionData,
+ List<CompoundTag> entities,
+ List<CompoundTag> 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 end - throttle failed spawn attempts
}

View File

@@ -0,0 +1,54 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: froobynooby <froobynooby@froobworld.com>
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 7bfa7aa30c1181587c7632f920f48348d2493ea4..d838c90f98c6593404c77d0aab8655c0d15905c4 100644
--- a/src/main/java/io/papermc/paper/configuration/WorldConfiguration.java
+++ b/src/main/java/io/papermc/paper/configuration/WorldConfiguration.java
@@ -181,6 +181,17 @@ public class WorldConfiguration extends ConfigurationPart {
@MergeMap
public Reference2IntMap<MobCategory> ticksPerSpawn = Util.make(new Reference2IntOpenHashMap<>(NaturalSpawner.SPAWNING_CATEGORIES.length), map -> Arrays.stream(NaturalSpawner.SPAWNING_CATEGORIES).forEach(mobCategory -> map.put(mobCategory, -1)));
+ // Paper start - throttle failed spawn attempts
+ public SpawningThrottle spawningThrottle;
+
+ public class SpawningThrottle extends ConfigurationPart {
+ public IntOr.Disabled failedAttemptsThreshold = IntOr.Disabled.DISABLED;
+
+ @MergeMap
+ public Reference2IntMap<MobCategory> throttledTicksPerSpawn = Util.make(new Reference2IntOpenHashMap<>(NaturalSpawner.SPAWNING_CATEGORIES.length), map -> Arrays.stream(NaturalSpawner.SPAWNING_CATEGORIES).forEach(mobCategory -> map.put(mobCategory, -1)));
+ }
+ // Paper end - throttle failed spawn attempts
+
@ConfigSerializable
public record DespawnRangePair(@Required DespawnRange hard, @Required DespawnRange soft) {
public static DespawnRangePair createDefault() {

View File

@@ -6,11 +6,8 @@
# Leaf TODOs
- [ ] refactor leaves protocol manager opt and pr it.
- [ ] Check and apply work patches
- [ ] Transfer patch notes to file for Gale and Leaf
- [ ] Add spigot config unknown message to leaf docs
- [ ] Add server full join config explanation to docs
- [ ] Add purpur config changes to docs moved config
- [ ] Merge changes from Tramontane
- [ ] check Dont send useless entity packets
- [ ] Check lithium fastutil and equipment tracking missing stuff?