diff --git a/paper-api/src/main/java/org/bukkit/OfflinePlayer.java b/paper-api/src/main/java/org/bukkit/OfflinePlayer.java
index 5622fe3165ba..3da609059c4a 100644
--- a/paper-api/src/main/java/org/bukkit/OfflinePlayer.java
+++ b/paper-api/src/main/java/org/bukkit/OfflinePlayer.java
@@ -264,6 +264,7 @@ public default BanEntry banPlayer(@Nullable String reason, @Nullable java.util.D
@Nullable
@Deprecated(since = "1.20.4")
public Location getBedSpawnLocation();
+
// Paper start
/**
* Gets the last date and time that this player logged into the server.
@@ -290,13 +291,28 @@ public default BanEntry banPlayer(@Nullable String reason, @Nullable java.util.D
// Paper end
/**
- * Gets the Location where the player will spawn at, null if they
+ * Gets the Location where the player will spawn at, {@code null} if they
+ * don't have a valid respawn point.
+ *
+ * Unlike online players, the location if found will not be loaded by default.
+ *
+ * @return respawn location if exists, otherwise {@code null}.
+ */
+ @Nullable
+ default Location getRespawnLocation() {
+ return this.getRespawnLocation(false); // keep old behavior for offline players
+ }
+
+ /**
+ * Gets the Location where the player will spawn at, {@code null} if they
* don't have a valid respawn point.
*
- * @return respawn location if exists, otherwise null.
+ * @param load load the current location to retrieve the exact position of the spawn block
+ * and check if this position is still valid or not
+ * @return respawn location if exists, otherwise {@code null}.
*/
@Nullable
- public Location getRespawnLocation();
+ Location getRespawnLocation(boolean load);
/**
* Increments the given statistic for this player.
diff --git a/paper-api/src/main/java/org/bukkit/entity/HumanEntity.java b/paper-api/src/main/java/org/bukkit/entity/HumanEntity.java
index 34538999ef88..8a4259d2bec0 100644
--- a/paper-api/src/main/java/org/bukkit/entity/HumanEntity.java
+++ b/paper-api/src/main/java/org/bukkit/entity/HumanEntity.java
@@ -469,9 +469,11 @@ default Location getPotentialBedLocation() {
* to validate if the current respawn location is still valid.
*
* @return respawn location if exists, otherwise null.
+ * @deprecated this method doesn't take in account the respawn angle, use
+ * {@link Player#getRespawnLocation(boolean)} with load = false instead
*/
- @Nullable
- Location getPotentialRespawnLocation();
+ @Deprecated(since = "1.21.4")
+ @Nullable Location getPotentialRespawnLocation();
// Paper end
// Paper start
/**
diff --git a/paper-api/src/main/java/org/bukkit/entity/Player.java b/paper-api/src/main/java/org/bukkit/entity/Player.java
index 7d21ee64c9b9..fe802ce5266d 100644
--- a/paper-api/src/main/java/org/bukkit/entity/Player.java
+++ b/paper-api/src/main/java/org/bukkit/entity/Player.java
@@ -560,15 +560,11 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM
@Deprecated(since = "1.20.4")
public Location getBedSpawnLocation();
- /**
- * Gets the Location where the player will spawn at, null if they
- * don't have a valid respawn point.
- *
- * @return respawn location if exists, otherwise null.
- */
@Nullable
@Override
- public Location getRespawnLocation();
+ default Location getRespawnLocation() {
+ return this.getRespawnLocation(true);
+ }
/**
* Sets the Location where the player will spawn at their bed.
diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java
index 94ca0407303c..5db986b1cfb1 100644
--- a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java
+++ b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java
@@ -13,6 +13,8 @@
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.NbtOps;
+import net.minecraft.nbt.Tag;
+import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.players.UserWhiteListEntry;
import net.minecraft.stats.ServerStatsCounter;
import net.minecraft.world.level.storage.PlayerDataStorage;
@@ -28,12 +30,11 @@
import org.bukkit.configuration.serialization.ConfigurationSerializable;
import org.bukkit.configuration.serialization.SerializableAs;
import org.bukkit.craftbukkit.entity.memory.CraftMemoryMapper;
-import org.bukkit.craftbukkit.profile.CraftPlayerProfile;
+import org.bukkit.craftbukkit.util.CraftLocation;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.metadata.MetadataValue;
import org.bukkit.plugin.Plugin;
-import org.bukkit.profile.PlayerProfile;
@SerializableAs("Player")
public class CraftOfflinePlayer implements OfflinePlayer, ConfigurationSerializable {
@@ -392,25 +393,35 @@ public Location getBedSpawnLocation() {
}
@Override
- public Location getRespawnLocation() {
+ public Location getRespawnLocation(boolean load) {
CompoundTag data = this.getData();
if (data == null) return null;
- if (data.contains("SpawnX") && data.contains("SpawnY") && data.contains("SpawnZ")) {
- // Paper start - fix wrong world
- final float respawnAngle = data.getFloat("SpawnAngle");
- org.bukkit.World spawnWorld = this.server.getWorld(data.getString("SpawnWorld")); // legacy
+ if (data.contains("SpawnX", Tag.TAG_ANY_NUMERIC) && data.contains("SpawnY", Tag.TAG_ANY_NUMERIC) && data.contains("SpawnZ", Tag.TAG_ANY_NUMERIC)) {
+ org.bukkit.World spawnWorld = null;
if (data.contains("SpawnDimension")) {
com.mojang.serialization.DataResult> result = net.minecraft.world.level.Level.RESOURCE_KEY_CODEC.parse(net.minecraft.nbt.NbtOps.INSTANCE, data.get("SpawnDimension"));
net.minecraft.resources.ResourceKey levelKey = result.resultOrPartial(LOGGER::error).orElse(net.minecraft.world.level.Level.OVERWORLD);
net.minecraft.server.level.ServerLevel level = this.server.console.getLevel(levelKey);
- spawnWorld = level != null ? level.getWorld() : spawnWorld;
+ spawnWorld = level != null ? level.getWorld() : null;
+ }
+ if (spawnWorld == null && data.contains("SpawnWorld")) { // legacy
+ spawnWorld = this.server.getWorld(data.getString("SpawnWorld"));
}
if (spawnWorld == null) {
return null;
}
- return new Location(spawnWorld, data.getInt("SpawnX"), data.getInt("SpawnY"), data.getInt("SpawnZ"), respawnAngle, 0);
- // Paper end
+
+ final float respawnAngle = data.getFloat("SpawnAngle");
+ net.minecraft.core.BlockPos respawnPos = new net.minecraft.core.BlockPos(data.getInt("SpawnX"), data.getInt("SpawnY"), data.getInt("SpawnZ"));
+ if (!load) {
+ return CraftLocation.toBukkit(respawnPos, spawnWorld, respawnAngle, 0);
+ }
+
+ final org.bukkit.World finalSpawnWorld = spawnWorld;
+ return ServerPlayer.findRespawnAndUseSpawnBlock(((CraftWorld) spawnWorld).getHandle(), respawnPos, respawnAngle, data.getBoolean("SpawnForced"), false)
+ .map(resolvedPos -> CraftLocation.toBukkit(resolvedPos.position(), finalSpawnWorld, resolvedPos.yaw(), 0))
+ .orElse(null);
}
return null;
}
diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
index 98fc89cc7a71..5de2cfbcdff8 100644
--- a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
+++ b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
@@ -167,7 +167,6 @@
import org.bukkit.craftbukkit.map.RenderData;
import org.bukkit.craftbukkit.potion.CraftPotionEffectType;
import org.bukkit.craftbukkit.potion.CraftPotionUtil;
-import org.bukkit.craftbukkit.profile.CraftPlayerProfile;
import org.bukkit.craftbukkit.scoreboard.CraftScoreboard;
import org.bukkit.craftbukkit.util.CraftChatMessage;
import org.bukkit.craftbukkit.util.CraftLocation;
@@ -181,7 +180,6 @@
import org.bukkit.event.player.PlayerHideEntityEvent;
import org.bukkit.event.player.PlayerRegisterChannelEvent;
import org.bukkit.event.player.PlayerShowEntityEvent;
-import org.bukkit.event.player.PlayerSpawnChangeEvent;
import org.bukkit.event.player.PlayerTeleportEvent;
import org.bukkit.event.player.PlayerUnregisterChannelEvent;
import org.bukkit.inventory.EquipmentSlot;
@@ -194,7 +192,6 @@
import org.bukkit.plugin.messaging.StandardMessenger;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
-import org.bukkit.profile.PlayerProfile;
import org.bukkit.scoreboard.Scoreboard;
import org.jetbrains.annotations.NotNull;
@@ -1556,16 +1553,16 @@ public Location getBedSpawnLocation() {
}
@Override
- public Location getRespawnLocation() {
+ public Location getRespawnLocation(boolean load) {
ServerLevel world = this.getHandle().server.getLevel(this.getHandle().getRespawnDimension());
- BlockPos bed = this.getHandle().getRespawnPosition();
+ BlockPos respawnPos = this.getHandle().getRespawnPosition();
- if (world != null && bed != null) {
- Optional spawnLoc = ServerPlayer.findRespawnAndUseSpawnBlock(world, bed, this.getHandle().getRespawnAngle(), this.getHandle().isRespawnForced(), true);
- if (spawnLoc.isPresent()) {
- ServerPlayer.RespawnPosAngle vec = spawnLoc.get();
- return CraftLocation.toBukkit(vec.position(), world.getWorld(), vec.yaw(), 0);
+ if (world != null && respawnPos != null) {
+ if (!load) {
+ return CraftLocation.toBukkit(respawnPos, world.getWorld(), this.getHandle().getRespawnAngle(), 0);
}
+ return ServerPlayer.findRespawnAndUseSpawnBlock(world, respawnPos, this.getHandle().getRespawnAngle(), this.getHandle().isRespawnForced(), false)
+ .map(resolvedPos -> CraftLocation.toBukkit(resolvedPos.position(), world.getWorld(), resolvedPos.yaw(), 0)).orElse(null);
}
return null;
}
diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/util/CraftLocation.java b/paper-server/src/main/java/org/bukkit/craftbukkit/util/CraftLocation.java
index a8b46ea5e4b6..cc2b34f41a0c 100644
--- a/paper-server/src/main/java/org/bukkit/craftbukkit/util/CraftLocation.java
+++ b/paper-server/src/main/java/org/bukkit/craftbukkit/util/CraftLocation.java
@@ -28,6 +28,7 @@ public static Location toBukkit(BlockPos blockPosition) {
public static Location toBukkit(BlockPos blockPosition, net.minecraft.world.level.Level world) {
return CraftLocation.toBukkit(blockPosition, world.getWorld(), 0.0F, 0.0F);
}
+
public static Location toBukkit(BlockPos blockPosition, World world) {
return CraftLocation.toBukkit(blockPosition, world, 0.0F, 0.0F);
}
@@ -40,16 +41,14 @@ public static BlockPos toBlockPosition(Location location) {
return new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ());
}
- // Paper start
public static net.minecraft.core.GlobalPos toGlobalPos(Location location) {
return net.minecraft.core.GlobalPos.of(((org.bukkit.craftbukkit.CraftWorld) location.getWorld()).getHandle().dimension(), toBlockPosition(location));
}
public static Location fromGlobalPos(net.minecraft.core.GlobalPos globalPos) {
BlockPos pos = globalPos.pos();
- return new org.bukkit.Location(net.minecraft.server.MinecraftServer.getServer().getLevel(globalPos.dimension()).getWorld(), pos.getX(), pos.getY(), pos.getZ());
+ return new Location(net.minecraft.server.MinecraftServer.getServer().getLevel(globalPos.dimension()).getWorld(), pos.getX(), pos.getY(), pos.getZ());
}
- // Paper end
public static Vec3 toVec3D(Location location) {
return new Vec3(location.getX(), location.getY(), location.getZ());