Fix inconsistencies between offline/online spawn position getter (#11960)

This commit is contained in:
Lulu13022002 2025-04-29 14:57:36 +02:00 committed by GitHub
parent 02d20ff7eb
commit 9e873f50d3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 60 additions and 26 deletions

View File

@ -287,13 +287,28 @@ public interface OfflinePlayer extends ServerOperator, AnimalTamer, Configuratio
// Paper end // 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.
* <br>
* Unlike online players, the location if found will not be loaded by default.
*
* @return respawn location if exists, otherwise {@code null}.
* @see #getRespawnLocation(boolean) for more fine-grained control over chunk loading and validation behaviour.
*/
default @Nullable 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. * don't have a valid respawn point.
* *
* @return respawn location if exists, otherwise null. * @param loadLocationAndValidate load the expected respawn location to retrieve the exact position of the spawn
* block and check if this position is still valid or not. Loading the location
* will induce a sync chunk load and must hence be used with caution.
* @return respawn location if exists, otherwise {@code null}.
*/ */
@Nullable @Nullable Location getRespawnLocation(boolean loadLocationAndValidate);
public Location getRespawnLocation();
/** /**
* Increments the given statistic for this player. * Increments the given statistic for this player.

View File

@ -485,9 +485,11 @@ public interface HumanEntity extends LivingEntity, AnimalTamer, InventoryHolder
* to validate if the current respawn location is still valid. * to validate if the current respawn location is still valid.
* *
* @return respawn location if exists, otherwise null. * @return respawn location if exists, otherwise null.
* @deprecated this method doesn't take the respawn angle into account, use
* {@link Player#getRespawnLocation(boolean)} with loadLocationAndValidate = false instead
*/ */
@Nullable @Deprecated(since = "1.21.5")
Location getPotentialRespawnLocation(); @Nullable Location getPotentialRespawnLocation();
/** /**
* @return the player's fishing hook if they are fishing * @return the player's fishing hook if they are fishing

View File

@ -545,6 +545,20 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM
*/ */
public boolean isSleepingIgnored(); public boolean isSleepingIgnored();
/**
* Gets the Location where the player will spawn at, {@code null} if they
* don't have a valid respawn point.
* <br>
* Unlike offline players, the location if found will be loaded to validate by default.
*
* @return respawn location if exists, otherwise {@code null}.
* @see #getRespawnLocation(boolean) for more fine-grained control over chunk loading and validation behaviour.
*/
@Override
default @Nullable Location getRespawnLocation() {
return this.getRespawnLocation(true);
}
/** /**
* Sets the Location where the player will spawn at their bed. * Sets the Location where the player will spawn at their bed.
* *

View File

@ -6,7 +6,6 @@ import java.time.Duration;
import java.time.Instant; import java.time.Instant;
import java.util.Date; import java.util.Date;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
import net.minecraft.core.GlobalPos; import net.minecraft.core.GlobalPos;
@ -33,8 +32,6 @@ import org.bukkit.configuration.serialization.SerializableAs;
import org.bukkit.craftbukkit.util.CraftLocation; import org.bukkit.craftbukkit.util.CraftLocation;
import org.bukkit.entity.EntityType; import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.metadata.MetadataValue;
import org.bukkit.plugin.Plugin;
@SerializableAs("Player") @SerializableAs("Player")
public class CraftOfflinePlayer implements OfflinePlayer, ConfigurationSerializable { public class CraftOfflinePlayer implements OfflinePlayer, ConfigurationSerializable {
@ -362,18 +359,23 @@ public class CraftOfflinePlayer implements OfflinePlayer, ConfigurationSerializa
} }
@Override @Override
public Location getRespawnLocation() { public Location getRespawnLocation(final boolean loadLocationAndValidate) {
CompoundTag data = this.getData(); final CompoundTag data = this.getData();
if (data == null) return null; if (data == null) return null;
final ServerPlayer.RespawnConfig respawnConfig = data.read("respawn", ServerPlayer.RespawnConfig.CODEC).orElse(null); final ServerPlayer.RespawnConfig respawnConfig = data.read("respawn", ServerPlayer.RespawnConfig.CODEC).orElse(null);
if (respawnConfig != null) { if (respawnConfig == null) return null;
final ServerLevel level = this.server.console.getLevel(respawnConfig.dimension());
if (level != null) { final ServerLevel level = this.server.console.getLevel(respawnConfig.dimension());
return CraftLocation.toBukkit(respawnConfig.pos(), level.getWorld(), respawnConfig.angle(), 0); if (level == null) return null;
}
if (!loadLocationAndValidate) {
return CraftLocation.toBukkit(respawnConfig.pos(), level.getWorld(), respawnConfig.angle(), 0);
} }
return null;
return ServerPlayer.findRespawnAndUseSpawnBlock(level, respawnConfig, false)
.map(resolvedPos -> CraftLocation.toBukkit(resolvedPos.position(), level.getWorld(), resolvedPos.yaw(), 0))
.orElse(null);
} }
private ServerStatsCounter getStatisticManager() { private ServerStatsCounter getStatisticManager() {

View File

@ -1541,19 +1541,20 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
} }
@Override @Override
public Location getRespawnLocation() { public Location getRespawnLocation(final boolean loadLocationAndValidate) {
final ServerPlayer.RespawnConfig respawnConfig = this.getHandle().getRespawnConfig(); final ServerPlayer.RespawnConfig respawnConfig = this.getHandle().getRespawnConfig();
if (respawnConfig == null) return null; if (respawnConfig == null) return null;
ServerLevel world = this.getHandle().server.getLevel(respawnConfig.dimension()); final ServerLevel world = this.getHandle().server.getLevel(respawnConfig.dimension());
if (world != null) { if (world == null) return null;
Optional<ServerPlayer.RespawnPosAngle> spawnLoc = ServerPlayer.findRespawnAndUseSpawnBlock(world, respawnConfig, true);
if (spawnLoc.isPresent()) { if (!loadLocationAndValidate) {
ServerPlayer.RespawnPosAngle vec = spawnLoc.get(); return CraftLocation.toBukkit(respawnConfig.pos(), world.getWorld(), respawnConfig.angle(), 0);
return CraftLocation.toBukkit(vec.position(), world.getWorld(), vec.yaw(), 0);
}
} }
return null;
return ServerPlayer.findRespawnAndUseSpawnBlock(world, respawnConfig, false)
.map(pos -> CraftLocation.toBukkit(pos.position(), world.getWorld(), pos.yaw(), 0))
.orElse(null);
} }
@Override @Override