From 74ab001fc129cf29d5d100d1cecc8ee280237c64 Mon Sep 17 00:00:00 2001 From: UnlimitedBytes Date: Sat, 10 Jan 2026 01:57:43 +0100 Subject: [PATCH 1/2] Fix cross-region teleportation in SpreadPlayersCommand Use teleportAsync to properly handle teleporting entities to positions that may be in different regions. This prevents "Cannot move entity off-main" errors when using /spreadplayers command. --- .../commands/SpreadPlayersCommand.java.patch | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/commands/SpreadPlayersCommand.java.patch diff --git a/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/commands/SpreadPlayersCommand.java.patch b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/commands/SpreadPlayersCommand.java.patch new file mode 100644 index 0000000..3e22bda --- /dev/null +++ b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/commands/SpreadPlayersCommand.java.patch @@ -0,0 +1,25 @@ +--- a/net/minecraft/server/commands/SpreadPlayersCommand.java ++++ b/net/minecraft/server/commands/SpreadPlayersCommand.java +@@ -246,17 +_,12 @@ + position = positions[i++]; + } + +- entity.teleportTo( +- level, +- Mth.floor(position.x) + 0.5, +- position.getSpawnY(level, maxHeight), +- Mth.floor(position.z) + 0.5, +- Set.of(), +- entity.getYRot(), +- entity.getXRot(), +- true +- , org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.COMMAND // CraftBukkit - handle teleport reason ++ // ShreddedPaper start - use teleportAsync to handle cross-region teleportation ++ entity.getBukkitEntity().teleportAsync( ++ new org.bukkit.Location(level.getWorld(), Mth.floor(position.x) + 0.5, position.getSpawnY(level, maxHeight), Mth.floor(position.z) + 0.5, entity.getYRot(), entity.getXRot()), ++ org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.COMMAND + ); ++ // ShreddedPaper end - use teleportAsync to handle cross-region teleportation + double d1 = Double.MAX_VALUE; + + for (SpreadPlayersCommand.Position position1 : positions) { From cd1df24caed54255db001732e83e9c03e6f52d0a Mon Sep 17 00:00:00 2001 From: UnlimitedBytes Date: Sat, 10 Jan 2026 15:12:21 +0100 Subject: [PATCH 2/2] Run getSpawnY on target region thread in SpreadPlayersCommand --- .../commands/SpreadPlayersCommand.java.patch | 39 +++++++++++++++---- 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/commands/SpreadPlayersCommand.java.patch b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/commands/SpreadPlayersCommand.java.patch index 3e22bda..7d0fec1 100644 --- a/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/commands/SpreadPlayersCommand.java.patch +++ b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/commands/SpreadPlayersCommand.java.patch @@ -1,6 +1,23 @@ --- a/net/minecraft/server/commands/SpreadPlayersCommand.java +++ b/net/minecraft/server/commands/SpreadPlayersCommand.java -@@ -246,17 +_,12 @@ +@@ -30,6 +_,7 @@ + import net.minecraft.world.level.block.state.BlockState; + import net.minecraft.world.phys.Vec2; + import net.minecraft.world.scores.Team; ++import io.multipaper.shreddedpaper.ShreddedPaper; + + public class SpreadPlayersCommand { + private static final int MAX_ITERATION_COUNT = 10000; +@@ -205,6 +_,8 @@ + + if (!flag) { + for (SpreadPlayersCommand.Position position1 : positions) { ++ // ShreddedPaper - isSafe does read-only block access; blocking to sync would cause deadlock ++ // when command runs on a region thread, so we accept the minor race condition here + if (!position1.isSafe(level, maxHeight)) { + position1.randomize(random, minX, minZ, maxX, maxZ); + flag = true; +@@ -246,17 +_,19 @@ position = positions[i++]; } @@ -14,12 +31,20 @@ - entity.getXRot(), - true - , org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.COMMAND // CraftBukkit - handle teleport reason -+ // ShreddedPaper start - use teleportAsync to handle cross-region teleportation -+ entity.getBukkitEntity().teleportAsync( -+ new org.bukkit.Location(level.getWorld(), Mth.floor(position.x) + 0.5, position.getSpawnY(level, maxHeight), Mth.floor(position.z) + 0.5, entity.getYRot(), entity.getXRot()), -+ org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.COMMAND - ); -+ // ShreddedPaper end - use teleportAsync to handle cross-region teleportation +- ); ++ // ShreddedPaper start - compute spawn Y on correct region thread, then teleport ++ final float entityYRot = entity.getYRot(); ++ final float entityXRot = entity.getXRot(); ++ final SpreadPlayersCommand.Position finalPosition = position; ++ BlockPos targetPos = BlockPos.containing(position.x, maxHeight, position.z); ++ ShreddedPaper.runSync(level, targetPos, () -> { ++ int spawnY = finalPosition.getSpawnY(level, maxHeight); ++ entity.getBukkitEntity().teleportAsync( ++ new org.bukkit.Location(level.getWorld(), Mth.floor(finalPosition.x) + 0.5, spawnY, Mth.floor(finalPosition.z) + 0.5, entityYRot, entityXRot), ++ org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.COMMAND ++ ); ++ }); ++ // ShreddedPaper end - compute spawn Y on correct region thread, then teleport double d1 = Double.MAX_VALUE; for (SpreadPlayersCommand.Position position1 : positions) {