2020import com .janboerman .invsee .spigot .internal .NamesAndUUIDs ;
2121import com .janboerman .invsee .spigot .internal .OpenSpectatorsCache ;
2222import com .mojang .authlib .GameProfile ;
23+ import com .mojang .serialization .DataResult ;
24+ import com .mojang .serialization .Dynamic ;
25+
2326import net .minecraft .core .BlockPos ;
2427import net .minecraft .nbt .CompoundTag ;
28+ import net .minecraft .nbt .NbtOps ;
2529import net .minecraft .network .protocol .game .ClientboundContainerSetSlotPacket ;
2630import net .minecraft .network .protocol .game .ClientboundOpenScreenPacket ;
2731import net .minecraft .network .protocol .game .ServerboundContainerClosePacket ;
32+ import net .minecraft .resources .ResourceKey ;
2833import net .minecraft .server .dedicated .DedicatedPlayerList ;
2934import net .minecraft .server .level .ClientInformation ;
35+ import net .minecraft .server .level .ServerLevel ;
3036import net .minecraft .server .level .ServerPlayer ;
3137import net .minecraft .world .entity .player .Inventory ;
3238import net .minecraft .world .inventory .AbstractContainerMenu ;
3339import net .minecraft .world .inventory .Slot ;
3440import net .minecraft .world .item .ItemStack ;
41+ import net .minecraft .world .level .Level ;
42+ import net .minecraft .world .level .dimension .DimensionType ;
3543import net .minecraft .world .level .storage .PlayerDataStorage ;
3644
3745import 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 ) {
0 commit comments