diff --git a/paper-api/src/main/java/io/papermc/paper/event/player/AsyncPlayerSpawnLocationEvent.java b/paper-api/src/main/java/io/papermc/paper/event/player/AsyncPlayerSpawnLocationEvent.java index 7945e9e3388d..00736f9fa6d8 100644 --- a/paper-api/src/main/java/io/papermc/paper/event/player/AsyncPlayerSpawnLocationEvent.java +++ b/paper-api/src/main/java/io/papermc/paper/event/player/AsyncPlayerSpawnLocationEvent.java @@ -23,14 +23,32 @@ public class AsyncPlayerSpawnLocationEvent extends Event { private final PlayerConfigurationConnection connection; private final boolean newPlayer; + private final boolean invalidWorld; private Location spawnLocation; @ApiStatus.Internal public AsyncPlayerSpawnLocationEvent(final PlayerConfigurationConnection connection, final Location spawnLocation, final boolean newPlayer) { + this(connection, spawnLocation, newPlayer, false); + } + + @ApiStatus.Internal + public AsyncPlayerSpawnLocationEvent(final PlayerConfigurationConnection connection, final Location spawnLocation, final boolean newPlayer, final boolean invalidWorld) { super(true); this.connection = connection; this.spawnLocation = spawnLocation; this.newPlayer = newPlayer; + this.invalidWorld = invalidWorld; + } + + /** + * Returns true if the player's saved data referenced a Bukkit world that is no longer loaded or available. + * + *

When this is true, the server falls back to spawning them in a different world at the same coordinates.

+ * + * @return whether the player's saved world was invalid + */ + public boolean isInvalidWorld() { + return this.invalidWorld; } /** diff --git a/paper-server/patches/features/0033-add-isInvalidPlayerWorld-to-Asyncplayerspawnlocationevent.patch b/paper-server/patches/features/0033-add-isInvalidPlayerWorld-to-Asyncplayerspawnlocationevent.patch new file mode 100644 index 000000000000..ff5d6fa23608 --- /dev/null +++ b/paper-server/patches/features/0033-add-isInvalidPlayerWorld-to-Asyncplayerspawnlocationevent.patch @@ -0,0 +1,57 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Newwind +Date: Sat, 6 Jun 2026 18:00:37 +0200 +Subject: [PATCH] Add isInvalidWorld to AsyncPlayerSpawnLocationEvent + +If a world that players are in gets unloaded/reset, they end up spawning in the overworld at the same coordinates they were at, this can cause players die suffocate in walls or fall to their death +There is no easy way to detect if a player spawn location has had its world changed, this patch fixes that by tracking if the world was determined to be invalid. + +diff --git a/net/minecraft/server/network/config/PrepareSpawnTask.java b/net/minecraft/server/network/config/PrepareSpawnTask.java +index f087b290..ac47509a 100644 +--- a/net/minecraft/server/network/config/PrepareSpawnTask.java ++++ b/net/minecraft/server/network/config/PrepareSpawnTask.java +@@ -40,6 +40,7 @@ public class PrepareSpawnTask implements ConfigurationTask { + private final com.mojang.authlib.GameProfile profile; + private final net.minecraft.server.network.ServerConfigurationPacketListenerImpl listener; + private boolean newPlayer; ++ private boolean invalidPlayerWorld; + + public PrepareSpawnTask(final MinecraftServer server, final com.mojang.authlib.GameProfile profile, final net.minecraft.server.network.ServerConfigurationPacketListenerImpl listener) { + this.profile = profile; +@@ -59,8 +60,8 @@ public class PrepareSpawnTask implements ConfigurationTask { + .map(tag -> TagValueInput.create(reporter, this.server.registryAccess(), tag)); + // Paper start - move logic in Entity to here, to use bukkit supplied world UUID & reset to main world spawn if no valid world is found + this.newPlayer = loadedData.isEmpty(); // New players don't have saved data! ++ this.invalidPlayerWorld = false; + net.minecraft.resources.ResourceKey resourceKey = null; // Paper +- boolean[] invalidPlayerWorld = {false}; + bukkitData: if (loadedData.isPresent()) { + // The main way for bukkit worlds to store the world is the world UUID despite mojang adding custom worlds + final org.bukkit.World bWorld; +@@ -80,7 +81,7 @@ public class PrepareSpawnTask implements ConfigurationTask { + resourceKey = ((org.bukkit.craftbukkit.CraftWorld) bWorld).getHandle().dimension(); + } else { + resourceKey = net.minecraft.world.level.Level.OVERWORLD; +- invalidPlayerWorld[0] = true; ++ this.invalidPlayerWorld = true; + } + } + ServerPlayer.SavedPosition loadedPosition = loadedData.flatMap(tag -> tag.read(ServerPlayer.SavedPosition.MAP_CODEC)) +@@ -102,6 +103,7 @@ public class PrepareSpawnTask implements ConfigurationTask { + if (spawnLevel == null) { + LOGGER.warn("Unknown respawn dimension {}, defaulting to overworld", resourceKey); + spawnLevel = spawnDataLevel; ++ this.invalidPlayerWorld = true; + } + } + final ServerLevel finalSpawnLevel = spawnLevel; +@@ -215,7 +217,8 @@ public class PrepareSpawnTask implements ConfigurationTask { + io.papermc.paper.event.player.AsyncPlayerSpawnLocationEvent ev = new io.papermc.paper.event.player.AsyncPlayerSpawnLocationEvent( + PrepareSpawnTask.this.listener.paperConnection, + org.bukkit.craftbukkit.util.CraftLocation.toBukkit(spawnPositionFinal, this.spawnLevel, this.spawnAngle.x, this.spawnAngle.y), +- PrepareSpawnTask.this.newPlayer ++ PrepareSpawnTask.this.newPlayer, ++ PrepareSpawnTask.this.invalidPlayerWorld + ); + ev.callEvent(); + return ev.getSpawnLocation();