Skip to content

Commit 254250c

Browse files
committed
Load world data again to work around a bug on Paper-based servers.
1 parent 3d0d479 commit 254250c

File tree

4 files changed

+140
-12
lines changed
  • InvSee++_Platforms
    • Impl_1_20_6_R4/src/main/java/com/janboerman/invsee/spigot/impl_1_20_6_R4
    • Impl_1_21_1_R1/src/main/java/com/janboerman/invsee/spigot/impl_1_21_1_R1
    • Impl_1_21_3_R2/src/main/java/com/janboerman/invsee/spigot/impl_1_21_3_R2
    • Impl_1_21_4_R3/src/main/java/com/janboerman/invsee/spigot/impl_1_21_4_R3

4 files changed

+140
-12
lines changed

InvSee++_Platforms/Impl_1_20_6_R4/src/main/java/com/janboerman/invsee/spigot/impl_1_20_6_R4/InvseeImpl.java

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,18 +20,26 @@
2020
import com.janboerman.invsee.spigot.internal.NamesAndUUIDs;
2121
import com.janboerman.invsee.spigot.internal.OpenSpectatorsCache;
2222
import com.mojang.authlib.GameProfile;
23+
import com.mojang.serialization.DataResult;
24+
import com.mojang.serialization.Dynamic;
25+
2326
import net.minecraft.core.BlockPos;
2427
import net.minecraft.nbt.CompoundTag;
28+
import net.minecraft.nbt.NbtOps;
2529
import net.minecraft.network.protocol.game.ClientboundContainerSetSlotPacket;
2630
import net.minecraft.network.protocol.game.ClientboundOpenScreenPacket;
2731
import net.minecraft.network.protocol.game.ServerboundContainerClosePacket;
32+
import net.minecraft.resources.ResourceKey;
2833
import net.minecraft.server.dedicated.DedicatedPlayerList;
2934
import net.minecraft.server.level.ClientInformation;
35+
import net.minecraft.server.level.ServerLevel;
3036
import net.minecraft.server.level.ServerPlayer;
3137
import net.minecraft.world.entity.player.Inventory;
3238
import net.minecraft.world.inventory.AbstractContainerMenu;
3339
import net.minecraft.world.inventory.Slot;
3440
import net.minecraft.world.item.ItemStack;
41+
import net.minecraft.world.level.Level;
42+
import net.minecraft.world.level.dimension.DimensionType;
3543
import net.minecraft.world.level.storage.PlayerDataStorage;
3644
import org.bukkit.Location;
3745
import org.bukkit.craftbukkit.v1_20_R4.CraftServer;
@@ -248,6 +256,7 @@ private <Slot, SI extends SpectatorInventory<Slot>> CompletableFuture<SaveRespon
248256
return CompletableFuture.supplyAsync(() -> {
249257
FakeCraftPlayer fakeCraftPlayer = fakeEntityPlayer.getBukkitEntity();
250258
fakeCraftPlayer.loadData();
259+
loadWorldData(server, fakeEntityPlayer);
251260

252261
CreationOptions<Slot> creationOptions = newInventory.getCreationOptions();
253262
SI currentInv = currentInvProvider.apply(fakeCraftPlayer, creationOptions);
@@ -258,6 +267,44 @@ private <Slot, SI extends SpectatorInventory<Slot>> CompletableFuture<SaveRespon
258267
}, runnable -> scheduler.executeSyncPlayer(playerId, runnable, null));
259268
}
260269

270+
271+
private void loadWorldData(CraftServer server, FakeEntityPlayer fakeEntityPlayer) {
272+
// In Paper, Entity#load(CompoundTag) does not load the world info.
273+
// Thus, in order to not upset our users, we do it ourselves manually in order to work around this Paper bug.
274+
// See https://github.com/Jannyboy11/InvSee-plus-plus/issues/105.
275+
// See PaperMC/PlayerList#placeNewPlayer.
276+
277+
PlayerDataStorage playerDataStorage = server.getHandle().playerIo;
278+
Optional<CompoundTag> optional = playerDataStorage.load(fakeEntityPlayer);
279+
280+
if (optional.isPresent()) {
281+
ServerLevel level;
282+
CompoundTag nbttagcompound = optional.get();
283+
284+
org.bukkit.World bWorld = null;
285+
if (nbttagcompound.contains("WorldUUIDMost") && nbttagcompound.contains("WorldUUIDLeast")) {
286+
// The main way for bukkit worlds to store the world is the world UUID despite mojang adding custom worlds
287+
bWorld = server.getWorld(new UUID(nbttagcompound.getLong("WorldUUIDMost"), nbttagcompound.getLong("WorldUUIDLeast")));
288+
} else if (nbttagcompound.contains("world", net.minecraft.nbt.Tag.TAG_STRING)) { // legacy bukkit world name
289+
bWorld = server.getWorld(nbttagcompound.getString("world"));
290+
}
291+
292+
if (bWorld != null) {
293+
level = ((CraftWorld) bWorld).getHandle();
294+
fakeEntityPlayer.setServerLevel(level);
295+
} else {
296+
DataResult<ResourceKey<Level>> dataresult = DimensionType.parseLegacy(new Dynamic(NbtOps.INSTANCE, nbttagcompound.get("Dimension")));
297+
Optional<ResourceKey<Level>> optionalLevelKey = dataresult.resultOrPartial(message -> plugin.getLogger().severe(message));
298+
ResourceKey<Level> levelResourceKey = optionalLevelKey.orElse(Level.OVERWORLD);
299+
level = server.getHandle().getServer().getLevel(levelResourceKey);
300+
301+
if (level != null) {
302+
fakeEntityPlayer.spawnIn(level); //note: not only sets the ServerLevel, also sets x/y/z coordinates and gamemode.
303+
}
304+
}
305+
}
306+
}
307+
261308
private static Optional<InventoryOpenEvent> callInventoryOpenEvent(ServerPlayer nmsPlayer, AbstractContainerMenu nmsView) {
262309
//copy-pasta from CraftEventFactory, but returns the cancelled event in case it was cancelled.
263310
if (nmsPlayer.containerMenu != nmsPlayer.inventoryMenu) {

InvSee++_Platforms/Impl_1_21_1_R1/src/main/java/com/janboerman/invsee/spigot/impl_1_21_1_R1/InvseeImpl.java

Lines changed: 46 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,18 +20,26 @@
2020
import com.janboerman.invsee.spigot.internal.NamesAndUUIDs;
2121
import com.janboerman.invsee.spigot.internal.OpenSpectatorsCache;
2222
import com.mojang.authlib.GameProfile;
23+
import com.mojang.serialization.DataResult;
24+
import com.mojang.serialization.Dynamic;
25+
2326
import net.minecraft.core.BlockPos;
2427
import net.minecraft.nbt.CompoundTag;
28+
import net.minecraft.nbt.NbtOps;
2529
import net.minecraft.network.protocol.game.ClientboundContainerSetSlotPacket;
2630
import net.minecraft.network.protocol.game.ClientboundOpenScreenPacket;
2731
import net.minecraft.network.protocol.game.ServerboundContainerClosePacket;
32+
import net.minecraft.resources.ResourceKey;
2833
import net.minecraft.server.dedicated.DedicatedPlayerList;
2934
import net.minecraft.server.level.ClientInformation;
35+
import net.minecraft.server.level.ServerLevel;
3036
import net.minecraft.server.level.ServerPlayer;
3137
import net.minecraft.world.entity.player.Inventory;
3238
import net.minecraft.world.inventory.AbstractContainerMenu;
3339
import net.minecraft.world.inventory.Slot;
3440
import net.minecraft.world.item.ItemStack;
41+
import net.minecraft.world.level.Level;
42+
import net.minecraft.world.level.dimension.DimensionType;
3543
import net.minecraft.world.level.storage.PlayerDataStorage;
3644

3745
import org.bukkit.Bukkit;
@@ -240,15 +248,6 @@ private <Slot, SI extends SpectatorInventory<Slot>> CompletableFuture<SaveRespon
240248
SpectatorInventorySaveEvent event = EventHelper.callSpectatorInventorySaveEvent(server, newInventory);
241249
if (event.isCancelled()) return CompletableFuture.completedFuture(SaveResponse.notSaved(newInventory));
242250

243-
// TODO Ticket #105: Target player's world switches to overworld when they log off in newly generated chunks in Nether.
244-
/* What could cause this?
245-
- We use the first world (usually overworld) to create the fake player entity.
246-
- Why would this world value be written to the player save file?
247-
We use fakeCraftPlayer.loadData(). Apparently this does not load the correct player location when the player
248-
- other reasons?
249-
Fix approach can be inspired by PlayerList#canPlayerLogin()
250-
*/
251-
252251
CraftWorld world = (CraftWorld) server.getWorlds().get(0);
253252
UUID playerId = newInventory.getSpectatedPlayerId();
254253
GameProfile gameProfile = new GameProfile(playerId, newInventory.getSpectatedPlayerName());
@@ -263,6 +262,7 @@ private <Slot, SI extends SpectatorInventory<Slot>> CompletableFuture<SaveRespon
263262
return CompletableFuture.supplyAsync(() -> {
264263
FakeCraftPlayer fakeCraftPlayer = fakeEntityPlayer.getBukkitEntity();
265264
fakeCraftPlayer.loadData();
265+
loadWorldData(server, fakeEntityPlayer);
266266

267267
CreationOptions<Slot> creationOptions = newInventory.getCreationOptions();
268268
SI currentInv = currentInvProvider.apply(fakeCraftPlayer, creationOptions);
@@ -273,6 +273,43 @@ private <Slot, SI extends SpectatorInventory<Slot>> CompletableFuture<SaveRespon
273273
}, runnable -> scheduler.executeSyncPlayer(playerId, runnable, null));
274274
}
275275

276+
private void loadWorldData(CraftServer server, FakeEntityPlayer fakeEntityPlayer) {
277+
// In Paper, Entity#load(CompoundTag) does not load the world info.
278+
// Thus, in order to not upset our users, we do it ourselves manually in order to work around this Paper bug.
279+
// See https://github.com/Jannyboy11/InvSee-plus-plus/issues/105.
280+
// See PaperMC/PlayerList#placeNewPlayer.
281+
282+
PlayerDataStorage playerDataStorage = server.getHandle().playerIo;
283+
Optional<CompoundTag> optional = playerDataStorage.load(fakeEntityPlayer);
284+
285+
if (optional.isPresent()) {
286+
ServerLevel level;
287+
CompoundTag nbttagcompound = optional.get();
288+
289+
org.bukkit.World bWorld = null;
290+
if (nbttagcompound.contains("WorldUUIDMost") && nbttagcompound.contains("WorldUUIDLeast")) {
291+
// The main way for bukkit worlds to store the world is the world UUID despite mojang adding custom worlds
292+
bWorld = server.getWorld(new UUID(nbttagcompound.getLong("WorldUUIDMost"), nbttagcompound.getLong("WorldUUIDLeast")));
293+
} else if (nbttagcompound.contains("world", net.minecraft.nbt.Tag.TAG_STRING)) { // legacy bukkit world name
294+
bWorld = server.getWorld(nbttagcompound.getString("world"));
295+
}
296+
297+
if (bWorld != null) {
298+
level = ((CraftWorld) bWorld).getHandle();
299+
fakeEntityPlayer.setServerLevel(level);
300+
} else {
301+
DataResult<ResourceKey<Level>> dataresult = DimensionType.parseLegacy(new Dynamic(NbtOps.INSTANCE, nbttagcompound.get("Dimension")));
302+
Optional<ResourceKey<Level>> optionalLevelKey = dataresult.resultOrPartial(message -> plugin.getLogger().severe(message));
303+
ResourceKey<Level> levelResourceKey = optionalLevelKey.orElse(Level.OVERWORLD);
304+
level = server.getHandle().getServer().getLevel(levelResourceKey);
305+
306+
if (level != null) {
307+
fakeEntityPlayer.spawnIn(level); //note: not only sets the ServerLevel, also sets x/y/z coordinates and gamemode.
308+
}
309+
}
310+
}
311+
}
312+
276313
private static Optional<InventoryOpenEvent> callInventoryOpenEvent(ServerPlayer nmsPlayer, AbstractContainerMenu nmsView) {
277314
//copy-pasta from CraftEventFactory, but returns the cancelled event in case it was cancelled.
278315
if (nmsPlayer.containerMenu != nmsPlayer.inventoryMenu) {

InvSee++_Platforms/Impl_1_21_3_R2/src/main/java/com/janboerman/invsee/spigot/impl_1_21_3_R2/InvseeImpl.java

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,18 +20,26 @@
2020
import com.janboerman.invsee.spigot.internal.NamesAndUUIDs;
2121
import com.janboerman.invsee.spigot.internal.OpenSpectatorsCache;
2222
import com.mojang.authlib.GameProfile;
23+
import com.mojang.serialization.DataResult;
24+
import com.mojang.serialization.Dynamic;
25+
2326
import net.minecraft.core.BlockPos;
2427
import net.minecraft.nbt.CompoundTag;
28+
import net.minecraft.nbt.NbtOps;
2529
import net.minecraft.network.protocol.game.ClientboundContainerSetSlotPacket;
2630
import net.minecraft.network.protocol.game.ClientboundOpenScreenPacket;
2731
import net.minecraft.network.protocol.game.ServerboundContainerClosePacket;
32+
import net.minecraft.resources.ResourceKey;
2833
import net.minecraft.server.dedicated.DedicatedPlayerList;
2934
import net.minecraft.server.level.ClientInformation;
35+
import net.minecraft.server.level.ServerLevel;
3036
import net.minecraft.server.level.ServerPlayer;
3137
import net.minecraft.world.entity.player.Inventory;
3238
import net.minecraft.world.inventory.AbstractContainerMenu;
3339
import net.minecraft.world.inventory.Slot;
3440
import net.minecraft.world.item.ItemStack;
41+
import net.minecraft.world.level.Level;
42+
import net.minecraft.world.level.dimension.DimensionType;
3543
import net.minecraft.world.level.storage.PlayerDataStorage;
3644

3745
import org.bukkit.Bukkit;
@@ -262,6 +270,7 @@ private <Slot, SI extends SpectatorInventory<Slot>> CompletableFuture<SaveRespon
262270
return CompletableFuture.supplyAsync(() -> {
263271
FakeCraftPlayer fakeCraftPlayer = fakeEntityPlayer.getBukkitEntity();
264272
fakeCraftPlayer.loadData();
273+
loadWorldData(server, fakeEntityPlayer);
265274

266275
CreationOptions<Slot> creationOptions = newInventory.getCreationOptions();
267276
SI currentInv = currentInvProvider.apply(fakeCraftPlayer, creationOptions);
@@ -272,6 +281,43 @@ private <Slot, SI extends SpectatorInventory<Slot>> CompletableFuture<SaveRespon
272281
}, runnable -> scheduler.executeSyncPlayer(playerId, runnable, null));
273282
}
274283

284+
private void loadWorldData(CraftServer server, FakeEntityPlayer fakeEntityPlayer) {
285+
// In Paper, Entity#load(CompoundTag) does not load the world info.
286+
// Thus, in order to not upset our users, we do it ourselves manually in order to work around this Paper bug.
287+
// See https://github.com/Jannyboy11/InvSee-plus-plus/issues/105.
288+
// See PaperMC/PlayerList#placeNewPlayer.
289+
290+
PlayerDataStorage playerDataStorage = server.getHandle().playerIo;
291+
Optional<CompoundTag> optional = playerDataStorage.load(fakeEntityPlayer);
292+
293+
if (optional.isPresent()) {
294+
ServerLevel level;
295+
CompoundTag nbttagcompound = optional.get();
296+
297+
org.bukkit.World bWorld = null;
298+
if (nbttagcompound.contains("WorldUUIDMost") && nbttagcompound.contains("WorldUUIDLeast")) {
299+
// The main way for bukkit worlds to store the world is the world UUID despite mojang adding custom worlds
300+
bWorld = server.getWorld(new UUID(nbttagcompound.getLong("WorldUUIDMost"), nbttagcompound.getLong("WorldUUIDLeast")));
301+
} else if (nbttagcompound.contains("world", net.minecraft.nbt.Tag.TAG_STRING)) { // legacy bukkit world name
302+
bWorld = server.getWorld(nbttagcompound.getString("world"));
303+
}
304+
305+
if (bWorld != null) {
306+
level = ((CraftWorld) bWorld).getHandle();
307+
fakeEntityPlayer.setServerLevel(level);
308+
} else {
309+
DataResult<ResourceKey<Level>> dataresult = DimensionType.parseLegacy(new Dynamic(NbtOps.INSTANCE, nbttagcompound.get("Dimension")));
310+
Optional<ResourceKey<Level>> optionalLevelKey = dataresult.resultOrPartial(message -> plugin.getLogger().severe(message));
311+
ResourceKey<Level> levelResourceKey = optionalLevelKey.orElse(Level.OVERWORLD);
312+
level = server.getHandle().getServer().getLevel(levelResourceKey);
313+
314+
if (level != null) {
315+
fakeEntityPlayer.spawnIn(level); //note: not only sets the ServerLevel, also sets x/y/z coordinates and gamemode.
316+
}
317+
}
318+
}
319+
}
320+
275321
private static Optional<InventoryOpenEvent> callInventoryOpenEvent(ServerPlayer nmsPlayer, AbstractContainerMenu nmsView) {
276322
//copy-pasta from CraftEventFactory, but returns the cancelled event in case it was cancelled.
277323
if (nmsPlayer.containerMenu != nmsPlayer.inventoryMenu) {

InvSee++_Platforms/Impl_1_21_4_R3/src/main/java/com/janboerman/invsee/spigot/impl_1_21_4_R3/InvseeImpl.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -283,12 +283,9 @@ private void loadWorldData(CraftServer server, FakeEntityPlayer fakeEntityPlayer
283283
// See https://github.com/Jannyboy11/InvSee-plus-plus/issues/105.
284284
// See PaperMC/PlayerList#placeNewPlayer.
285285

286-
if (fakeEntityPlayer.level() != null) return; //player already has level (should happen on CraftBukkit & Spigot).
287-
288286
PlayerDataStorage playerDataStorage = server.getHandle().playerIo;
289287
Optional<CompoundTag> optional = playerDataStorage.load(fakeEntityPlayer);
290288

291-
292289
if (optional.isPresent()) {
293290
ServerLevel level;
294291
CompoundTag nbttagcompound = optional.get();
@@ -309,6 +306,7 @@ private void loadWorldData(CraftServer server, FakeEntityPlayer fakeEntityPlayer
309306
Optional<ResourceKey<Level>> optionalLevelKey = dataresult.resultOrPartial(message -> plugin.getLogger().severe(message));
310307
ResourceKey<Level> levelResourceKey = optionalLevelKey.orElse(Level.OVERWORLD);
311308
level = server.getHandle().getServer().getLevel(levelResourceKey);
309+
312310
if (level != null) {
313311
fakeEntityPlayer.spawnIn(level); //note: not only sets the ServerLevel, also sets x/y/z coordinates and gamemode.
314312
}

0 commit comments

Comments
 (0)