From 9712ee72dea1861259271499816871709b69c33e Mon Sep 17 00:00:00 2001 From: Sam Russell Date: Wed, 5 Aug 2026 22:41:40 +0200 Subject: [PATCH 1/2] Add quota for player arrows that don't auto-despawn Addresses #14142 Workaround for #13452 that breaks warden switches. This allows admins to have a quota of arrows for players to use for fair use while still protecting them from mass arrow spam. --- .../0035-fixup-paper-File-Patches.patch | 124 ++++++++++++++++++ .../configuration/WorldConfiguration.java | 1 + 2 files changed, 125 insertions(+) create mode 100644 paper-server/patches/features/0035-fixup-paper-File-Patches.patch diff --git a/paper-server/patches/features/0035-fixup-paper-File-Patches.patch b/paper-server/patches/features/0035-fixup-paper-File-Patches.patch new file mode 100644 index 000000000000..c23f8c291957 --- /dev/null +++ b/paper-server/patches/features/0035-fixup-paper-File-Patches.patch @@ -0,0 +1,124 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Sam Russell +Date: Wed, 5 Aug 2026 22:35:03 +0200 +Subject: [PATCH] fixup! paper File Patches + + +diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java +index 59598bfb95c2f8a38efd92685c46a954dc76739d..14ae3ed13c8a028b715ba62653d49822a6ff7984 100644 +--- a/net/minecraft/server/level/ServerLevel.java ++++ b/net/minecraft/server/level/ServerLevel.java +@@ -882,6 +882,12 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet + } + + io.papermc.paper.entity.activation.ActivationRange.activateEntities(this); // Paper - EAR ++ // Paper start - tick life after X seconds if too many arrows from player ++ // reset numArrows, then each arrow increments this in the same order ++ for (ServerPlayer player:this.players) { ++ player.numArrows = 0; ++ } ++ // Paper end - tick life after X seconds if too many arrows from player + this.entityTickList + .forEach( + entity -> { +diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java +index 3cd4e850f223100b61ac6831a87dd38f3a3bb58f..c0ed4bd56450e3b92ac1d801dd88246a467c11b0 100644 +--- a/net/minecraft/server/level/ServerPlayer.java ++++ b/net/minecraft/server/level/ServerPlayer.java +@@ -421,6 +421,9 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc + public static final int MOBCATEGORY_TOTAL_ENUMS = net.minecraft.world.entity.MobCategory.values().length; + public final int[] mobCounts = new int[MOBCATEGORY_TOTAL_ENUMS]; + // Paper end - Optional per player mob spawns ++ // Paper start - tick life after X seconds if too many arrows from player ++ public int numArrows = 0; ++ // Paper end - tick life after X seconds if too many arrows from player + public final int[] mobBackoffCounts = new int[MOBCATEGORY_TOTAL_ENUMS]; // Paper - per player mob count backoff + // CraftBukkit start + public @Nullable String lastKnownName; // Better rename detection +@@ -728,7 +731,6 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc + if (this.invulnerableTime > 0) { + this.invulnerableTime--; + } +- + // Paper start - Configurable container update tick rate + if (--this.containerUpdateDelay <= 0) { + this.containerMenu.broadcastChanges(); +diff --git a/net/minecraft/world/entity/projectile/arrow/AbstractArrow.java b/net/minecraft/world/entity/projectile/arrow/AbstractArrow.java +index d25d525bad6b8096d80da19f3f969c1be514b115..0ae13b0cf4cce0a2154f171ad329cf4859bccf1b 100644 +--- a/net/minecraft/world/entity/projectile/arrow/AbstractArrow.java ++++ b/net/minecraft/world/entity/projectile/arrow/AbstractArrow.java +@@ -165,7 +165,7 @@ public abstract class AbstractArrow extends Projectile { + public void lerpMotion(final Vec3 movement) { + super.lerpMotion(movement); + // Paper start - Fix loophole for arrow immunity +- if (!this.level().paperConfig().entities.spawning.maxArrowDespawnInvulnerability.enabled()) { ++ if (!this.shouldForceArrowDespawn()) { + this.life = 0; + } + // Paper end - Fix loophole for arrow immunity +@@ -181,6 +181,34 @@ public abstract class AbstractArrow extends Projectile { + this.shakeTime = 7; + } + } ++ // Paper start - tick life after X seconds if too many arrows from player ++ private void increaseServerPlayerArrowCount() { ++ if (this.getOwner() instanceof ServerPlayer serverPlayerOwner && serverPlayerOwner.level() == this.level()) { ++ // this gets called in the tick phase ++ // because they get lerped out of order by the pistons and that breaks things ++ // so this way they are in a consistent order and it's always the same arrows that are within the quota ++ serverPlayerOwner.numArrows++; ++ } ++ } ++ ++ private boolean isWithinPlayerArrowQuota() { ++ // give players a quota of arrows before despawn kicks in ++ // we can only do this when the player is in the same dimension because dimensions tick independently ++ if (this.getOwner() instanceof ServerPlayer serverPlayerOwner && serverPlayerOwner.level() == this.level()) ++ { ++ return this.level().paperConfig().entities.spawning.playerArrowQuota.enabled() && serverPlayerOwner.numArrows <= this.level().paperConfig().entities.spawning.playerArrowQuota.intValue(); ++ } ++ ++ return false; ++ } ++ ++ private boolean shouldForceArrowDespawn() { ++ final io.papermc.paper.configuration.type.number.IntOr.Disabled maxArrowDespawnInvulnerability = this.level().paperConfig().entities.spawning.maxArrowDespawnInvulnerability; ++ boolean isTimeToForceArrowDespawn = maxArrowDespawnInvulnerability.enabled() && this.tickCount > maxArrowDespawnInvulnerability.intValue(); ++ ++ return isTimeToForceArrowDespawn && !this.isWithinPlayerArrowQuota(); ++ } ++ // Paper end - tick life after X seconds if too many arrows from player + + @Override + public void tick() { +@@ -211,6 +239,11 @@ public abstract class AbstractArrow extends Projectile { + this.clearFire(); + } + ++ // Paper start - tick life after X seconds if too many arrows from player ++ // need to do this in the tick phase ++ increaseServerPlayerArrowCount(); ++ // Paper start - tick life after X seconds if too many arrows from player ++ + if (this.isInGround() && physicsEnabled) { + if (!this.level().isClientSide()) { + if (this.lastState != blockState && this.shouldFall()) { +@@ -230,8 +263,7 @@ public abstract class AbstractArrow extends Projectile { + } + } else { + // Paper start - tick life regardless after X seconds +- final io.papermc.paper.configuration.type.number.IntOr.Disabled maxArrowDespawnInvulnerability = this.level().paperConfig().entities.spawning.maxArrowDespawnInvulnerability; +- if (maxArrowDespawnInvulnerability.enabled() && this.tickCount > maxArrowDespawnInvulnerability.intValue()) this.tickDespawn(); ++ if (this.shouldForceArrowDespawn()) this.tickDespawn(); + // Paper end - tick life regardless after X seconds + this.inGroundTime = 0; + Vec3 originalPosition = this.position(); +@@ -385,7 +417,7 @@ public abstract class AbstractArrow extends Projectile { + Vec3 deltaMovement = this.getDeltaMovement(); + this.setDeltaMovement(deltaMovement.multiply(this.random.nextFloat() * 0.2F, this.random.nextFloat() * 0.2F, this.random.nextFloat() * 0.2F)); + // Paper start - Fix loophole for arrow immunity +- if (!this.level().paperConfig().entities.spawning.maxArrowDespawnInvulnerability.enabled()) { ++ if (!this.shouldForceArrowDespawn()) { + this.life = 0; + } + // Paper end - Fix loophole for arrow immunity diff --git a/paper-server/src/main/java/io/papermc/paper/configuration/WorldConfiguration.java b/paper-server/src/main/java/io/papermc/paper/configuration/WorldConfiguration.java index 86ae93a9cad3..27670409ad58 100644 --- a/paper-server/src/main/java/io/papermc/paper/configuration/WorldConfiguration.java +++ b/paper-server/src/main/java/io/papermc/paper/configuration/WorldConfiguration.java @@ -173,6 +173,7 @@ public class Spawning extends ConfigurationPart { public ArrowDespawnRate nonPlayerArrowDespawnRate = ArrowDespawnRate.def(WorldConfiguration.this.spigotConfig); public ArrowDespawnRate creativeArrowDespawnRate = ArrowDespawnRate.def(WorldConfiguration.this.spigotConfig); public IntOr.Disabled maxArrowDespawnInvulnerability = new IntOr.Disabled(OptionalInt.of(200)); + public IntOr.Disabled playerArrowQuota = new IntOr.Disabled(OptionalInt.of(5)); public boolean filterBadTileEntityNbtFromFallingBlocks = true; public List filteredEntityTagNbtPaths = NbtPathSerializer.fromString(List.of("Pos", "Motion", "sleeping_pos")); public boolean disableMobSpawnerSpawnEggTransformation = false; From d3315696db2d3532f316acd9228aad90a397019c Mon Sep 17 00:00:00 2001 From: Sam Russell Date: Thu, 6 Aug 2026 09:21:07 +0200 Subject: [PATCH 2/2] Move arrow reset to inside AbstractArrow, keep ServerLevel clean --- .../0036-fixup-paper-File-Patches.patch | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 paper-server/patches/features/0036-fixup-paper-File-Patches.patch diff --git a/paper-server/patches/features/0036-fixup-paper-File-Patches.patch b/paper-server/patches/features/0036-fixup-paper-File-Patches.patch new file mode 100644 index 000000000000..73381cc79be3 --- /dev/null +++ b/paper-server/patches/features/0036-fixup-paper-File-Patches.patch @@ -0,0 +1,52 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Sam Russell +Date: Thu, 6 Aug 2026 09:20:08 +0200 +Subject: [PATCH] fixup! paper File Patches + + +diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java +index 14ae3ed13c8a028b715ba62653d49822a6ff7984..59598bfb95c2f8a38efd92685c46a954dc76739d 100644 +--- a/net/minecraft/server/level/ServerLevel.java ++++ b/net/minecraft/server/level/ServerLevel.java +@@ -882,12 +882,6 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet + } + + io.papermc.paper.entity.activation.ActivationRange.activateEntities(this); // Paper - EAR +- // Paper start - tick life after X seconds if too many arrows from player +- // reset numArrows, then each arrow increments this in the same order +- for (ServerPlayer player:this.players) { +- player.numArrows = 0; +- } +- // Paper end - tick life after X seconds if too many arrows from player + this.entityTickList + .forEach( + entity -> { +diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java +index c0ed4bd56450e3b92ac1d801dd88246a467c11b0..04a75a40e514d5e9e94c37412e2cd626383d2c12 100644 +--- a/net/minecraft/server/level/ServerPlayer.java ++++ b/net/minecraft/server/level/ServerPlayer.java +@@ -423,6 +423,7 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc + // Paper end - Optional per player mob spawns + // Paper start - tick life after X seconds if too many arrows from player + public int numArrows = 0; ++ public long timeNumArrowsUpdated = 0; + // Paper end - tick life after X seconds if too many arrows from player + public final int[] mobBackoffCounts = new int[MOBCATEGORY_TOTAL_ENUMS]; // Paper - per player mob count backoff + // CraftBukkit start +diff --git a/net/minecraft/world/entity/projectile/arrow/AbstractArrow.java b/net/minecraft/world/entity/projectile/arrow/AbstractArrow.java +index 0ae13b0cf4cce0a2154f171ad329cf4859bccf1b..c9154e5bfc9cc468f56fbff02a2ebb2f4cb21532 100644 +--- a/net/minecraft/world/entity/projectile/arrow/AbstractArrow.java ++++ b/net/minecraft/world/entity/projectile/arrow/AbstractArrow.java +@@ -187,6 +187,12 @@ public abstract class AbstractArrow extends Projectile { + // this gets called in the tick phase + // because they get lerped out of order by the pistons and that breaks things + // so this way they are in a consistent order and it's always the same arrows that are within the quota ++ ++ // reset in new tick ++ if(serverPlayerOwner.timeNumArrowsUpdated != this.level().getGameTime()) { ++ serverPlayerOwner.timeNumArrowsUpdated = this.level().getGameTime(); ++ serverPlayerOwner.numArrows = 0; ++ } + serverPlayerOwner.numArrows++; + } + }