Skip to content

Commit 75c8a85

Browse files
committed
chore(git): merge respawn data fixes
Signed-off-by: Gabriel Harris-Rouquette <gabizou@me.com>
2 parents 3d339b5 + e1dfd0d commit 75c8a85

8 files changed

Lines changed: 247 additions & 10 deletions

File tree

src/main/java/org/spongepowered/common/bridge/world/level/storage/ServerLevelDataBridge.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import org.spongepowered.api.Sponge;
4040
import org.spongepowered.api.world.SerializationBehavior;
4141
import org.spongepowered.common.SpongeCommon;
42+
import org.spongepowered.common.bridge.world.level.border.WorldBorderBridge;
4243
import org.spongepowered.common.world.server.SpongeServerLevelData;
4344
import org.spongepowered.common.world.server.SpongeWorldManager;
4445

@@ -129,6 +130,15 @@ public interface ServerLevelDataBridge {
129130
return Optional.empty();
130131
}
131132

133+
default @Nullable WorldBorderBridge bridge$worldBorder() {
134+
final ServerLevel level = this.bridge$level();
135+
if (level != null) {
136+
return (WorldBorderBridge) level.getWorldBorder();
137+
}
138+
139+
return null;
140+
}
141+
132142
default void bridge$triggerViewDistanceLogic() {
133143
final ServerLevel level = this.bridge$level();
134144
if (level != null) {

src/main/java/org/spongepowered/common/data/provider/world/WorldPropertiesData.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@
3636
import org.spongepowered.api.entity.living.player.gamemode.GameMode;
3737
import org.spongepowered.api.world.SerializationBehavior;
3838
import org.spongepowered.api.world.WorldType;
39-
import org.spongepowered.api.world.border.WorldBorder;
4039
import org.spongepowered.api.world.difficulty.Difficulty;
4140
import org.spongepowered.api.world.generation.config.WorldGenerationConfig;
4241
import org.spongepowered.api.world.server.WorldArchetype;
@@ -79,9 +78,6 @@ public static void register(final DataProviderRegistrator registrator) {
7978
.get(ServerLevelData::isAllowCommands)
8079
.create(Keys.INITIALIZED)
8180
.get(ServerLevelData::isInitialized)
82-
.create(Keys.WORLD_BORDER)
83-
// TODO - 26.1 changes how borders are decided
84-
.get(h -> (WorldBorder) (Object) ((ServerLevelDataBridge)h).bridge$level().getWorldBorder())
8581
.asMutable(ServerLevelDataBridge.class)
8682
.create(Keys.WORLD_TYPE)
8783
.get(h -> (WorldType) (Object) h.bridge$dimensionType())
@@ -99,6 +95,8 @@ public static void register(final DataProviderRegistrator registrator) {
9995
.get(ServerLevelDataBridge::bridge$loadOnStartup)
10096
.create(Keys.WORLD_ARCHETYPE_TYPE)
10197
.get(h -> (WorldArchetypeType) (Object) h.bridge$levelStem())
98+
.create(Keys.WORLD_BORDER)
99+
.get(h -> h.bridge$worldBorder().bridge$asImmutable())
102100
.asMutable(PrimaryLevelData.class)
103101
.create(Keys.WORLD_DIFFICULTY)
104102
.set((h, v) -> h.setDifficulty((net.minecraft.world.Difficulty) (Object) v))

src/main/java/org/spongepowered/common/entity/player/SpongeUserData.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,12 @@ public void setItemInHand(final HandType handType, final @Nullable ItemStackLike
404404

405405
@Override
406406
public boolean bridge$setBedLocations(final Map<ResourceKey, RespawnLocation> value) {
407+
for (final Map.Entry<ResourceKey, RespawnLocation> entry : value.entrySet()) {
408+
if (!Objects.equals(entry.getKey(), entry.getValue().worldKey())) {
409+
throw new IllegalArgumentException("RespawnLocation world key " + entry.getValue().worldKey()
410+
+ " does not match map key " + entry.getKey());
411+
}
412+
}
407413
final Optional<ServerPlayer> player = this.player();
408414
if (player.isPresent()) {
409415
return ((BedLocationHolderBridge) player.get()).bridge$setBedLocations(value);

src/main/java/org/spongepowered/common/world/teleport/SpongeTeleportHelper.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ public Optional<ServerLocation> findSafeLocation(ServerLocation location, int he
7373

7474
private Stream<Vector3i> getBlockLocations(ServerLocation worldLocation, int height, int width) {
7575
// We don't want to warp outside of the world border, so we want to check that we're within it.
76-
final WorldBorder.Settings worldBorder = (WorldBorder.Settings) (Object) worldLocation.world().properties().worldBorder();
76+
final WorldBorder.Settings worldBorder = (WorldBorder.Settings) (Object) worldLocation.world().border();
7777
final double radius = worldBorder.size() / 2.0D;
7878
int worldBorderMinX = GenericMath.floor(worldBorder.centerX() - radius);
7979
int worldBorderMinZ = GenericMath.floor(worldBorder.centerZ() - radius);

src/mixins/java/org/spongepowered/common/mixin/api/minecraft/server/players/GameProfileCacheMixin_API.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import com.google.common.collect.ImmutableMap;
2828
import com.google.common.collect.ImmutableSet;
2929
import com.google.common.collect.Maps;
30+
import net.minecraft.server.players.NameAndId;
3031
import org.checkerframework.checker.nullness.qual.Nullable;
3132
import org.spongepowered.api.profile.GameProfile;
3233
import org.spongepowered.api.profile.GameProfileCache;
@@ -57,7 +58,7 @@ public abstract class GameProfileCacheMixin_API implements GameProfileCache {
5758
@Shadow @Final @Mutable private final Map<String, GameProfileCache_GameProfileInfoAccessor> profilesByName = new ConcurrentHashMap<>();
5859
@Shadow @Final @Mutable private final Map<UUID, GameProfileCache_GameProfileInfoAccessor> profilesByUUID = new ConcurrentHashMap<>();
5960

60-
@Shadow public abstract Optional<com.mojang.authlib.GameProfile> shadow$get(UUID uniqueId);
61+
@Shadow public abstract Optional<NameAndId> shadow$get(UUID uniqueId);
6162
@Shadow protected abstract long shadow$getNextOperation();
6263
// @formatter:on
6364

src/mixins/java/org/spongepowered/common/mixin/core/server/level/ServerLevelMixin.java

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -475,9 +475,6 @@ public abstract class ServerLevelMixin extends LevelMixin implements ServerLevel
475475

476476
if (behavior != SerializationBehavior.NONE) {
477477
original.call(self, flush);
478-
// In 26.1, per-world WorldBorder, BossEvents, Weather, and GameRules are all
479-
// SavedData instances in per-dimension SavedDataStorage. They are automatically
480-
// saved by vanilla's saveLevelData() → SavedDataStorage.saveAndJoin().
481478
}
482479
}
483480

src/mixins/java/org/spongepowered/common/mixin/core/server/level/ServerPlayerMixin.java

Lines changed: 152 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
*/
2525
package org.spongepowered.common.mixin.core.server.level;
2626

27+
import com.google.common.collect.ImmutableMap;
2728
import com.google.common.collect.ImmutableSet;
2829
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
2930
import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod;
@@ -38,12 +39,15 @@
3839
import net.kyori.adventure.text.Component;
3940
import net.minecraft.advancements.CriteriaTriggers;
4041
import net.minecraft.core.BlockPos;
42+
import net.minecraft.core.GlobalPos;
43+
import net.minecraft.core.registries.Registries;
4144
import net.minecraft.nbt.CompoundTag;
4245
import net.minecraft.network.chat.ChatType;
4346
import net.minecraft.network.chat.OutgoingChatMessage;
4447
import net.minecraft.network.protocol.Packet;
4548
import net.minecraft.network.protocol.game.ClientGamePacketListener;
4649
import net.minecraft.network.protocol.game.ClientboundSetEntityLinkPacket;
50+
import net.minecraft.resources.Identifier;
4751
import net.minecraft.resources.ResourceKey;
4852
import net.minecraft.server.MinecraftServer;
4953
import net.minecraft.server.ServerScoreboard;
@@ -70,6 +74,8 @@
7074
import net.minecraft.world.level.border.WorldBorder;
7175
import net.minecraft.world.level.gamerules.GameRules;
7276
import net.minecraft.world.level.portal.TeleportTransition;
77+
import net.minecraft.world.level.storage.LevelData;
78+
import net.minecraft.world.level.storage.ValueInput;
7379
import net.minecraft.world.phys.Vec3;
7480
import net.minecraft.world.scores.PlayerTeam;
7581
import org.checkerframework.checker.nullness.qual.Nullable;
@@ -100,13 +106,15 @@
100106
import org.spongepowered.api.registry.RegistryTypes;
101107
import org.spongepowered.api.scoreboard.Scoreboard;
102108
import org.spongepowered.api.service.permission.PermissionService;
109+
import org.spongepowered.api.util.RespawnLocation;
103110
import org.spongepowered.api.util.Tristate;
104111
import org.spongepowered.api.util.locale.Locales;
105112
import org.spongepowered.api.world.server.ServerWorld;
106113
import org.spongepowered.asm.mixin.Final;
107114
import org.spongepowered.asm.mixin.Mixin;
108115
import org.spongepowered.asm.mixin.Overwrite;
109116
import org.spongepowered.asm.mixin.Shadow;
117+
import org.spongepowered.asm.mixin.Unique;
110118
import org.spongepowered.asm.mixin.injection.At;
111119
import org.spongepowered.asm.mixin.injection.Inject;
112120
import org.spongepowered.asm.mixin.injection.ModifyVariable;
@@ -123,11 +131,13 @@
123131
import org.spongepowered.common.accessor.world.level.portal.TeleportTransitionAccessor;
124132
import org.spongepowered.common.adventure.SpongeAdventure;
125133
import org.spongepowered.common.bridge.data.DataCompoundHolder;
134+
import org.spongepowered.common.bridge.data.SpongeDataHolderBridge;
126135
import org.spongepowered.common.bridge.data.TransientBridge;
127136
import org.spongepowered.common.bridge.permissions.SubjectBridge;
128137
import org.spongepowered.common.bridge.server.ServerScoreboardBridge;
129138
import org.spongepowered.common.bridge.server.level.ServerPlayerBridge;
130139
import org.spongepowered.common.bridge.world.BossEventBridge;
140+
import org.spongepowered.common.bridge.world.entity.player.BedLocationHolderBridge;
131141
import org.spongepowered.common.bridge.world.entity.player.PlayerBridge;
132142
import org.spongepowered.common.data.DataUtil;
133143
import org.spongepowered.common.data.type.SpongeSkinPart;
@@ -139,26 +149,31 @@
139149
import org.spongepowered.common.network.packet.SpongePacketHandler;
140150
import org.spongepowered.common.util.LocaleCache;
141151
import org.spongepowered.common.world.border.PlayerOwnBorderListener;
152+
import org.spongepowered.math.vector.Vector3d;
142153

154+
import java.util.HashMap;
143155
import java.util.Locale;
156+
import java.util.Map;
144157
import java.util.Objects;
145158
import java.util.Set;
146159

147160
// See also: SubjectMixin_API and SubjectMixin
148161
@SuppressWarnings("ConstantConditions")
149162
@Mixin(net.minecraft.server.level.ServerPlayer.class)
150-
public abstract class ServerPlayerMixin extends PlayerMixin implements SubjectBridge, ServerPlayerBridge {
163+
public abstract class ServerPlayerMixin extends PlayerMixin implements SubjectBridge, ServerPlayerBridge, BedLocationHolderBridge {
151164

152165
// @formatter:off
153166
@Shadow public ServerGamePacketListenerImpl connection;
154167
@Shadow @Final public ServerPlayerGameMode gameMode;
155168
@Shadow @Final private MinecraftServer server;
156169
@Shadow private int lastRecordedExperience;
170+
@Shadow @javax.annotation.Nullable private net.minecraft.server.level.ServerPlayer.RespawnConfig respawnConfig;
157171

158172
@Shadow public abstract ServerLevel shadow$level();
159173
@Shadow public abstract void shadow$doCloseContainer();
160174
@Shadow public abstract boolean shadow$setGameMode(GameType param0);
161175
@Shadow public abstract void shadow$setCamera(@org.jetbrains.annotations.Nullable final Entity $$0);
176+
@Shadow public abstract void shadow$setRespawnPosition(@javax.annotation.Nullable net.minecraft.server.level.ServerPlayer.RespawnConfig config, boolean sendMessage);
162177
// @formatter:on
163178

164179
private net.minecraft.network.chat.@Nullable Component impl$connectionMessage;
@@ -179,6 +194,8 @@ public abstract class ServerPlayerMixin extends PlayerMixin implements SubjectBr
179194
@Nullable
180195
private WorldBorder impl$worldBorder;
181196
private ServerLevel impl$respawnLevel;
197+
private final Map<org.spongepowered.api.ResourceKey, RespawnLocation> impl$bedLocations = new HashMap<>();
198+
private boolean impl$syncingRespawn;
182199

183200
@Override
184201
public net.minecraft.network.chat.@Nullable Component bridge$getConnectionMessageToSend() {
@@ -763,6 +780,20 @@ private boolean isPvpAllowed() {
763780
final Operation<net.minecraft.server.level.ServerPlayer.RespawnConfig> original,
764781
@Share("sponge:overridden-respawn") final LocalRef<ResourceKey<Level>> dimension
765782
) {
783+
// Prefer a forced Keys.RESPAWN_LOCATIONS entry for the world the player died in, even if vanilla's single
784+
// slot points elsewhere. Mirror it into vanilla's respawnConfig up front so findRespawnPositionAndUseSpawnBlock
785+
// reads the correct position when locating the bed/anchor.
786+
final org.spongepowered.api.ResourceKey dyingWorldKey = (org.spongepowered.api.ResourceKey) (Object) player.level().dimension().identifier();
787+
final RespawnLocation forcedInDyingWorld = this.impl$bedLocations.get(dyingWorldKey);
788+
if (forcedInDyingWorld != null && forcedInDyingWorld.isForced()) {
789+
this.impl$syncingRespawn = true;
790+
try {
791+
this.shadow$setRespawnPosition(this.impl$toRespawnConfig(forcedInDyingWorld), false);
792+
} finally {
793+
this.impl$syncingRespawn = false;
794+
}
795+
}
796+
766797
final var config = original.call(player);
767798
final var defaulted = config == null ? Level.OVERWORLD : config.respawnData().dimension();
768799

@@ -818,4 +849,124 @@ private boolean isPvpAllowed() {
818849
}
819850
original.call(instance, packet);
820851
}
852+
853+
@Override
854+
public Map<org.spongepowered.api.ResourceKey, RespawnLocation> bridge$getBedlocations() {
855+
return new HashMap<>(this.impl$bedLocations);
856+
}
857+
858+
@Override
859+
public boolean bridge$setBedLocations(final Map<org.spongepowered.api.ResourceKey, RespawnLocation> value) {
860+
for (final Map.Entry<org.spongepowered.api.ResourceKey, RespawnLocation> entry : value.entrySet()) {
861+
if (!Objects.equals(entry.getKey(), entry.getValue().worldKey())) {
862+
throw new IllegalArgumentException("RespawnLocation world key " + entry.getValue().worldKey()
863+
+ " does not match map key " + entry.getKey());
864+
}
865+
}
866+
this.impl$bedLocations.clear();
867+
this.impl$bedLocations.putAll(value);
868+
this.impl$syncingRespawn = true;
869+
try {
870+
final RespawnLocation active = this.impl$pickActiveRespawn(value);
871+
this.shadow$setRespawnPosition(active == null ? null : this.impl$toRespawnConfig(active), false);
872+
} finally {
873+
this.impl$syncingRespawn = false;
874+
}
875+
if (!((SpongeDataHolderBridge) this).brigde$isDeserializing()) {
876+
if (value.isEmpty()) {
877+
((SpongeDataHolderBridge) this).bridge$remove(Keys.RESPAWN_LOCATIONS);
878+
} else {
879+
((SpongeDataHolderBridge) this).bridge$offer(Keys.RESPAWN_LOCATIONS, new HashMap<>(value));
880+
}
881+
}
882+
return true;
883+
}
884+
885+
@Override
886+
public ImmutableMap<org.spongepowered.api.ResourceKey, RespawnLocation> bridge$removeAllBeds() {
887+
final ImmutableMap<org.spongepowered.api.ResourceKey, RespawnLocation> snapshot = ImmutableMap.copyOf(this.impl$bedLocations);
888+
this.impl$bedLocations.clear();
889+
this.impl$syncingRespawn = true;
890+
try {
891+
this.shadow$setRespawnPosition(null, false);
892+
} finally {
893+
this.impl$syncingRespawn = false;
894+
}
895+
((SpongeDataHolderBridge) this).bridge$remove(Keys.RESPAWN_LOCATIONS);
896+
return snapshot;
897+
}
898+
899+
@Unique
900+
private @Nullable RespawnLocation impl$pickActiveRespawn(final Map<org.spongepowered.api.ResourceKey, RespawnLocation> map) {
901+
if (map.isEmpty()) {
902+
return null;
903+
}
904+
if (this.respawnConfig != null) {
905+
final org.spongepowered.api.ResourceKey vanillaKey = (org.spongepowered.api.ResourceKey) (Object) this.respawnConfig.respawnData().dimension().identifier();
906+
final RespawnLocation forVanilla = map.get(vanillaKey);
907+
if (forVanilla != null) {
908+
return forVanilla;
909+
}
910+
}
911+
final org.spongepowered.api.ResourceKey currentLevelKey = (org.spongepowered.api.ResourceKey) (Object) this.shadow$level().dimension().identifier();
912+
final RespawnLocation forCurrent = map.get(currentLevelKey);
913+
if (forCurrent != null) {
914+
return forCurrent;
915+
}
916+
return map.values().iterator().next();
917+
}
918+
919+
@Unique
920+
private net.minecraft.server.level.ServerPlayer.RespawnConfig impl$toRespawnConfig(final RespawnLocation location) {
921+
final ResourceKey<Level> dim = ResourceKey.create(Registries.DIMENSION,
922+
(Identifier) (Object) location.worldKey());
923+
final Vector3d pos = location.position();
924+
// Vector3d -> BlockPos via floor (BlockPos.containing uses floor semantics).
925+
// RespawnLocation has no yaw/pitch — keep the player's current orientation so sleep angle isn't clobbered.
926+
final LevelData.RespawnData data = LevelData.RespawnData.of(
927+
dim, BlockPos.containing(pos.x(), pos.y(), pos.z()), this.shadow$getYRot(), this.shadow$getXRot());
928+
return new net.minecraft.server.level.ServerPlayer.RespawnConfig(data, location.isForced());
929+
}
930+
931+
@Inject(method = "setRespawnPosition(Lnet/minecraft/server/level/ServerPlayer$RespawnConfig;Z)V", at = @At("RETURN"))
932+
private void impl$mirrorVanillaRespawnToBedLocations(
933+
final @javax.annotation.Nullable net.minecraft.server.level.ServerPlayer.RespawnConfig config,
934+
final boolean sendMessage, final CallbackInfo ci) {
935+
if (this.impl$syncingRespawn) {
936+
return;
937+
}
938+
if (config == null) {
939+
// Vanilla cleared respawn — drop every entry (vanilla only tracks one slot anyway)
940+
this.impl$bedLocations.clear();
941+
} else {
942+
final GlobalPos globalPos = config.respawnData().globalPos();
943+
final BlockPos pos = globalPos.pos();
944+
final org.spongepowered.api.ResourceKey apiKey = (org.spongepowered.api.ResourceKey) (Object) globalPos.dimension().identifier();
945+
this.impl$bedLocations.put(apiKey, RespawnLocation.builder()
946+
.world(apiKey)
947+
.position(new Vector3d(pos.getX() + 0.5D, pos.getY(), pos.getZ() + 0.5D))
948+
.forceSpawn(config.forced())
949+
.build());
950+
}
951+
if (this.impl$bedLocations.isEmpty()) {
952+
((SpongeDataHolderBridge) this).bridge$remove(Keys.RESPAWN_LOCATIONS);
953+
} else {
954+
((SpongeDataHolderBridge) this).bridge$offer(Keys.RESPAWN_LOCATIONS, new HashMap<>(this.impl$bedLocations));
955+
}
956+
}
957+
958+
@Inject(method = "readAdditionalSaveData", at = @At("RETURN"))
959+
private void impl$seedBedLocationsFromVanilla(final ValueInput input, final CallbackInfo ci) {
960+
if (this.respawnConfig == null || !this.impl$bedLocations.isEmpty()) {
961+
return;
962+
}
963+
final GlobalPos globalPos = this.respawnConfig.respawnData().globalPos();
964+
final BlockPos pos = globalPos.pos();
965+
final org.spongepowered.api.ResourceKey apiKey = (org.spongepowered.api.ResourceKey) (Object) globalPos.dimension().identifier();
966+
this.impl$bedLocations.put(apiKey, RespawnLocation.builder()
967+
.world(apiKey)
968+
.position(new Vector3d(pos.getX() + 0.5D, pos.getY(), pos.getZ() + 0.5D))
969+
.forceSpawn(this.respawnConfig.forced())
970+
.build());
971+
}
821972
}

0 commit comments

Comments
 (0)