diff --git a/patches/server/Don-t-allow-vehicle-movement-from-players-while-tele.patch b/patches/server/Don-t-allow-vehicle-movement-from-players-while-tele.patch index 2ff188fa0a..3f4ecbc0eb 100644 --- a/patches/server/Don-t-allow-vehicle-movement-from-players-while-tele.patch +++ b/patches/server/Don-t-allow-vehicle-movement-from-players-while-tele.patch @@ -15,7 +15,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 Entity entity = this.player.getRootVehicle(); + // Paper start -+ if (this.awaitingPositionFromClient != null) { ++ if (this.awaitingPositionFromClient != null || this.player.isImmobile() || entity.isRemoved()) { + return; + } + // Paper end diff --git a/patches/server/Ensure-entity-passenger-world-matches-ridden-entity.patch b/patches/server/Ensure-entity-passenger-world-matches-ridden-entity.patch new file mode 100644 index 0000000000..64e58a0a7a --- /dev/null +++ b/patches/server/Ensure-entity-passenger-world-matches-ridden-entity.patch @@ -0,0 +1,23 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Thu, 31 Mar 2022 05:11:37 -0700 +Subject: [PATCH] Ensure entity passenger world matches ridden entity + +Bad plugins doing this would cause some obvious problems... + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -0,0 +0,0 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + } + + protected boolean addPassenger(Entity entity) { // CraftBukkit ++ // Paper start ++ if (entity.level != this.level) { ++ throw new IllegalArgumentException("Entity passenger world must match"); ++ } ++ // Paper end + if (entity == this) throw new IllegalArgumentException("Entities cannot become a passenger of themselves"); // Paper - issue 572 + if (entity.getVehicle() != this) { + throw new IllegalStateException("Use x.startRiding(y), not y.addPassenger(x)"); diff --git a/patches/server/Guard-against-invalid-entity-positions.patch b/patches/server/Guard-against-invalid-entity-positions.patch new file mode 100644 index 0000000000..b4c9556cbb --- /dev/null +++ b/patches/server/Guard-against-invalid-entity-positions.patch @@ -0,0 +1,45 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Thu, 31 Mar 2022 05:18:28 -0700 +Subject: [PATCH] Guard against invalid entity positions + +Anything not finite should be blocked and logged + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -0,0 +0,0 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + return this.getZ((2.0D * this.random.nextDouble() - 1.0D) * widthScale); + } + ++ // Paper start - block invalid positions ++ public static boolean checkPosition(Entity entity, double newX, double newY, double newZ) { ++ if (Double.isFinite(newX) && Double.isFinite(newY) && Double.isFinite(newZ)) { ++ return true; ++ } ++ ++ String entityInfo = null; ++ try { ++ entityInfo = entity.toString(); ++ } catch (Exception ex) { ++ entityInfo = "[Entity info unavailable] "; ++ } ++ LOGGER.error("New entity position is invalid! Tried to set invalid position (" + newX + "," + newY + "," + newZ + ") for entity " + entity.getClass().getName() + " located at " + entity.position + ", entity info: " + entityInfo, new Throwable()); ++ return false; ++ } ++ // Paper end - block invalid positions ++ + public final void setPosRaw(double x, double y, double z) { + // Paper start + this.setPosRaw(x, y, z, false); + } + public final void setPosRaw(double x, double y, double z, boolean forceBoundingBoxUpdate) { ++ // Paper start - block invalid positions ++ if (!checkPosition(this, x, y, z)) { ++ return; ++ } ++ // Paper end - block invalid positions + // Paper end + // Paper start - fix MC-4 + if (this instanceof ItemEntity) { diff --git a/patches/server/MC-Utils.patch b/patches/server/MC-Utils.patch index ca92ec1be3..8235d1b0e6 100644 --- a/patches/server/MC-Utils.patch +++ b/patches/server/MC-Utils.patch @@ -2899,14 +2899,16 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 +public final class IntervalledCounter { + + protected long[] times; ++ protected long[] counts; + protected final long interval; + protected long minTime; -+ protected int sum; ++ protected long sum; + protected int head; // inclusive + protected int tail; // exclusive + + public IntervalledCounter(final long interval) { + this.times = new long[8]; ++ this.counts = new long[8]; + this.interval = interval; + } + @@ -2915,7 +2917,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + + public void updateCurrentTime(final long currentTime) { -+ int sum = this.sum; ++ long sum = this.sum; + int head = this.head; + final int tail = this.tail; + final long minTime = currentTime - this.interval; @@ -2924,8 +2926,15 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + + // guard against overflow by using subtraction + while (head != tail && this.times[head] - minTime < 0) { -+ head = (head + 1) % arrayLen; -+ --sum; ++ sum -= this.counts[head]; ++ // there are two ways we can do this: ++ // 1. free the count when adding ++ // 2. free it now ++ // option #2 ++ this.counts[head] = 0; ++ if (++head >= arrayLen) { ++ head = 0; ++ } + } + + this.sum = sum; @@ -2934,6 +2943,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + + public void addTime(final long currTime) { ++ this.addTime(currTime, 1L); ++ } ++ ++ public void addTime(final long currTime, final long count) { + // guard against overflow by using subtraction + if (currTime - this.minTime < 0) { + return; @@ -2945,28 +2958,29 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + + this.times[this.tail] = currTime; ++ this.counts[this.tail] += count; ++ this.sum += count; + this.tail = nextTail; + } + + public void updateAndAdd(final int count) { + final long currTime = System.nanoTime(); + this.updateCurrentTime(currTime); -+ for (int i = 0; i < count; ++i) { -+ this.addTime(currTime); -+ } ++ this.addTime(currTime, count); + } + + public void updateAndAdd(final int count, final long currTime) { + this.updateCurrentTime(currTime); -+ for (int i = 0; i < count; ++i) { -+ this.addTime(currTime); -+ } ++ this.addTime(currTime, count); + } + + private void resize() { + final long[] oldElements = this.times; ++ final long[] oldCounts = this.counts; + final long[] newElements = new long[this.times.length * 2]; ++ final long[] newCounts = new long[this.times.length * 2]; + this.times = newElements; ++ this.counts = newCounts; + + final int head = this.head; + final int tail = this.tail; @@ -2976,9 +2990,13 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + + if (tail >= head) { + System.arraycopy(oldElements, head, newElements, 0, size); ++ System.arraycopy(oldCounts, head, newCounts, 0, size); + } else { + System.arraycopy(oldElements, head, newElements, 0, oldElements.length - head); + System.arraycopy(oldElements, 0, newElements, oldElements.length - head, tail); ++ ++ System.arraycopy(oldCounts, head, newCounts, 0, oldCounts.length - head); ++ System.arraycopy(oldCounts, 0, newCounts, oldCounts.length - head, tail); + } + } + @@ -2987,11 +3005,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + return this.size() / (this.interval * 1.0e-9); + } + -+ public int size() { -+ final int head = this.head; -+ final int tail = this.tail; -+ -+ return tail >= head ? (tail - head) : (tail + (this.times.length - head)); ++ public long size() { ++ return this.sum; + } +} diff --git a/src/main/java/io/papermc/paper/util/WorldUtil.java b/src/main/java/io/papermc/paper/util/WorldUtil.java diff --git a/patches/server/Option-to-have-default-CustomSpawners-in-custom-worl.patch b/patches/server/Option-to-have-default-CustomSpawners-in-custom-worl.patch index a4fd7ab9ba..3f607945ff 100644 --- a/patches/server/Option-to-have-default-CustomSpawners-in-custom-worl.patch +++ b/patches/server/Option-to-have-default-CustomSpawners-in-custom-worl.patch @@ -14,8 +14,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 --- a/src/main/java/com/destroystokyo/paper/PaperConfig.java +++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java @@ -0,0 +0,0 @@ public class PaperConfig { - } globalMaxConcurrentChunkLoads = getDouble("settings.chunk-loading.global-max-concurrent-loads", 500.0); + playerMaxChunkLoadRate = getDouble("settings.chunk-loading.player-max-chunk-load-rate", -1.0); } + + public static boolean useDimensionTypeForCustomSpawners; diff --git a/patches/server/Replace-player-chunk-loader-system.patch b/patches/server/Replace-player-chunk-loader-system.patch index 98edb38cc5..e6504a2e54 100644 --- a/patches/server/Replace-player-chunk-loader-system.patch +++ b/patches/server/Replace-player-chunk-loader-system.patch @@ -102,6 +102,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + public static double globalMaxChunkLoadRate; + public static double playerMaxConcurrentChunkLoads; + public static double globalMaxConcurrentChunkLoads; ++ public static double playerMaxChunkLoadRate; + + private static void newPlayerChunkManagement() { + playerMinChunkLoadRadius = getInt("settings.chunk-loading.min-load-radius", 2); @@ -119,6 +120,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + set("settings.chunk-loading.player-max-concurrent-loads", playerMaxConcurrentChunkLoads = 20.0); + } + globalMaxConcurrentChunkLoads = getDouble("settings.chunk-loading.global-max-concurrent-loads", 500.0); ++ playerMaxChunkLoadRate = getDouble("settings.chunk-loading.player-max-chunk-load-rate", -1.0); + } } diff --git a/src/main/java/io/papermc/paper/chunk/PlayerChunkLoader.java b/src/main/java/io/papermc/paper/chunk/PlayerChunkLoader.java @@ -218,10 +220,16 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + + final int priorityCompare = Double.compare(holder1 == null ? Double.MAX_VALUE : holder1.priority, holder2 == null ? Double.MAX_VALUE : holder2.priority); + -+ if (priorityCompare != 0) { ++ final int lastLoadTimeCompare = Long.compare(p1.lastChunkLoad, p2.lastChunkLoad); ++ ++ if ((holder1 == null || holder2 == null || lastLoadTimeCompare == 0 || holder1.priority < 0.0 || holder2.priority < 0.0) && priorityCompare != 0) { + return priorityCompare; + } + ++ if (lastLoadTimeCompare != 0) { ++ return lastLoadTimeCompare; ++ } ++ + final int idCompare = Integer.compare(p1.player.getId(), p2.player.getId()); + + if (idCompare != 0) { @@ -744,6 +752,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + for (;;) { + final PlayerLoaderData data = this.chunkLoadQueue.pollFirst(); + ++ data.lastChunkLoad = time; ++ + final ChunkPriorityHolder queuedLoad = data.loadQueue.peekFirst(); + if (queuedLoad == null) { + if (this.chunkLoadQueue.isEmpty()) { @@ -756,6 +766,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + updatedCounters = true; + TICKET_ADDITION_COUNTER_SHORT.updateCurrentTime(time); + TICKET_ADDITION_COUNTER_LONG.updateCurrentTime(time); ++ data.ticketAdditionCounterShort.updateCurrentTime(time); ++ data.ticketAdditionCounterLong.updateCurrentTime(time); + } + + if (this.isChunkPlayerLoaded(queuedLoad.chunkX, queuedLoad.chunkZ)) { @@ -791,7 +803,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + // priority >= 0.0 implies rate limited chunks + + final int currentChunkLoads = this.concurrentChunkLoads; -+ if (currentChunkLoads >= maxLoads || (PaperConfig.globalMaxChunkLoadRate > 0 && (TICKET_ADDITION_COUNTER_SHORT.getRate() >= PaperConfig.globalMaxChunkLoadRate || TICKET_ADDITION_COUNTER_LONG.getRate() >= PaperConfig.globalMaxChunkLoadRate))) { ++ if (currentChunkLoads >= maxLoads || (PaperConfig.globalMaxChunkLoadRate > 0 && (TICKET_ADDITION_COUNTER_SHORT.getRate() >= PaperConfig.globalMaxChunkLoadRate || TICKET_ADDITION_COUNTER_LONG.getRate() >= PaperConfig.globalMaxChunkLoadRate)) ++ || (PaperConfig.playerMaxChunkLoadRate > 0.0 && (data.ticketAdditionCounterShort.getRate() >= PaperConfig.playerMaxChunkLoadRate || data.ticketAdditionCounterLong.getRate() >= PaperConfig.playerMaxChunkLoadRate))) { + // don't poll, we didn't load it + this.chunkLoadQueue.add(data); + break; @@ -821,6 +834,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + ++this.concurrentChunkLoads; + TICKET_ADDITION_COUNTER_SHORT.addTime(time); + TICKET_ADDITION_COUNTER_LONG.addTime(time); ++ data.ticketAdditionCounterShort.addTime(time); ++ data.ticketAdditionCounterLong.addTime(time); + } + } + } @@ -926,6 +941,13 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + + protected long nextChunkSendTarget; + ++ // this interval prevents bursting a lot of chunk loads ++ protected final IntervalledCounter ticketAdditionCounterShort = new IntervalledCounter((long)(1.0e6 * 50.0)); // 50ms ++ // this ensures the rate is kept between ticks correctly ++ protected final IntervalledCounter ticketAdditionCounterLong = new IntervalledCounter((long)(1.0e6 * 1000.0)); // 1000ms ++ ++ public long lastChunkLoad; ++ + public PlayerLoaderData(final ServerPlayer player, final PlayerChunkLoader loader) { + this.player = player; + this.loader = loader;