2626import com .janboerman .invsee .spigot .api .template .EnderChestSlot ;
2727import com .janboerman .invsee .spigot .api .template .Mirror ;
2828import com .janboerman .invsee .spigot .api .template .PlayerInventorySlot ;
29- import com .janboerman .invsee .spigot .impl_1_21_4_R3 .FakeCraftHumanEntity ;
30- import com .janboerman .invsee .spigot .impl_1_21_4_R3 .MainNmsContainer ;
31- import com .janboerman .invsee .spigot .impl_1_21_4_R3 .UUIDSearchSaveFilesStrategy ;
3229import com .janboerman .invsee .spigot .internal .EventHelper ;
3330import com .janboerman .invsee .spigot .internal .InvseePlatform ;
3431import com .janboerman .invsee .spigot .internal .NamesAndUUIDs ;
3532import com .janboerman .invsee .spigot .internal .OpenSpectatorsCache ;
3633import com .mojang .authlib .GameProfile ;
34+ import com .mojang .serialization .DataResult ;
35+ import com .mojang .serialization .Dynamic ;
3736
3837import org .bukkit .Bukkit ;
3938import org .bukkit .Location ;
5352
5453import net .minecraft .core .BlockPos ;
5554import net .minecraft .nbt .CompoundTag ;
55+ import net .minecraft .nbt .NbtOps ;
5656import net .minecraft .network .protocol .game .ClientboundContainerSetSlotPacket ;
5757import net .minecraft .network .protocol .game .ClientboundOpenScreenPacket ;
5858import net .minecraft .network .protocol .game .ServerboundContainerClosePacket ;
59+ import net .minecraft .resources .ResourceKey ;
5960import net .minecraft .server .dedicated .DedicatedPlayerList ;
6061import net .minecraft .server .level .ClientInformation ;
62+ import net .minecraft .server .level .ServerLevel ;
6163import net .minecraft .server .level .ServerPlayer ;
64+ import net .minecraft .server .players .PlayerList ;
6265import net .minecraft .world .entity .player .Inventory ;
6366import net .minecraft .world .inventory .AbstractContainerMenu ;
6467import net .minecraft .world .inventory .Slot ;
6568import net .minecraft .world .item .ItemStack ;
69+ import net .minecraft .world .level .Level ;
70+ import net .minecraft .world .level .dimension .DimensionType ;
6671import net .minecraft .world .level .storage .PlayerDataStorage ;
6772
6873public class InvseeImpl implements InvseePlatform {
@@ -247,15 +252,6 @@ private <Slot, SI extends SpectatorInventory<Slot>> CompletableFuture<SaveRespon
247252 SpectatorInventorySaveEvent event = EventHelper .callSpectatorInventorySaveEvent (server , newInventory );
248253 if (event .isCancelled ()) return CompletableFuture .completedFuture (SaveResponse .notSaved (newInventory ));
249254
250- // TODO Ticket #105: Target player's world switches to overworld when they log off in newly generated chunks in Nether.
251- /* What could cause this?
252- - We use the first world (usually overworld) to create the fake player entity.
253- - Why would this world value be written to the player save file?
254- We use fakeCraftPlayer.loadData(). Apparently this does not load the correct player location when the player
255- - other reasons?
256- Fix approach can be inspired by PlayerList#canPlayerLogin()
257- */
258-
259255 CraftWorld world = (CraftWorld ) server .getWorlds ().get (0 );
260256 UUID playerId = newInventory .getSpectatedPlayerId ();
261257 GameProfile gameProfile = new GameProfile (playerId , newInventory .getSpectatedPlayerName ());
@@ -270,6 +266,7 @@ private <Slot, SI extends SpectatorInventory<Slot>> CompletableFuture<SaveRespon
270266 return CompletableFuture .supplyAsync (() -> {
271267 FakeCraftPlayer fakeCraftPlayer = fakeEntityPlayer .getBukkitEntity ();
272268 fakeCraftPlayer .loadData ();
269+ loadWorldData (server , fakeEntityPlayer ); //workaround for https://github.com/PaperMC/Paper/issues/11572
273270
274271 CreationOptions <Slot > creationOptions = newInventory .getCreationOptions ();
275272 SI currentInv = currentInvProvider .apply (fakeCraftPlayer , creationOptions );
@@ -280,6 +277,45 @@ private <Slot, SI extends SpectatorInventory<Slot>> CompletableFuture<SaveRespon
280277 }, runnable -> scheduler .executeSyncPlayer (playerId , runnable , null ));
281278 }
282279
280+ private void loadWorldData (CraftServer server , FakeEntityPlayer fakeEntityPlayer ) {
281+ // In Paper, Entity#load(CompoundTag) does not load the world info.
282+ // Thus, in order to not upset our users, we do it ourselves manually in order to work around this Paper bug.
283+ // See https://github.com/Jannyboy11/InvSee-plus-plus/issues/105.
284+ // See PaperMC/PlayerList#placeNewPlayer.
285+
286+ if (fakeEntityPlayer .level () != null ) return ; //player already has level (should happen on CraftBukkit & Spigot).
287+
288+ PlayerDataStorage playerDataStorage = server .getHandle ().playerIo ;
289+ Optional <CompoundTag > optional = playerDataStorage .load (fakeEntityPlayer );
290+
291+ ServerLevel level = null ;
292+
293+ if (optional .isPresent ()) {
294+ CompoundTag nbttagcompound = optional .get ();
295+
296+ org .bukkit .World bWorld = null ;
297+ if (nbttagcompound .contains ("WorldUUIDMost" ) && nbttagcompound .contains ("WorldUUIDLeast" )) {
298+ // The main way for bukkit worlds to store the world is the world UUID despite mojang adding custom worlds
299+ bWorld = server .getWorld (new UUID (nbttagcompound .getLong ("WorldUUIDMost" ), nbttagcompound .getLong ("WorldUUIDLeast" )));
300+ } else if (nbttagcompound .contains ("world" , net .minecraft .nbt .Tag .TAG_STRING )) { // legacy bukkit world name
301+ bWorld = server .getWorld (nbttagcompound .getString ("world" ));
302+ }
303+
304+ if (bWorld != null ) {
305+ level = ((CraftWorld ) bWorld ).getHandle ();
306+ fakeEntityPlayer .setServerLevel (level );
307+ } else {
308+ DataResult <ResourceKey <Level >> dataresult = DimensionType .parseLegacy (new Dynamic (NbtOps .INSTANCE , nbttagcompound .get ("Dimension" )));
309+ Optional <ResourceKey <Level >> optionalLevelKey = dataresult .resultOrPartial (message -> plugin .getLogger ().severe (message ));
310+ ResourceKey <Level > levelResourceKey = optionalLevelKey .orElse (Level .OVERWORLD );
311+ level = server .getHandle ().getServer ().getLevel (levelResourceKey );
312+ if (level != null ) {
313+ fakeEntityPlayer .spawnIn (level ); //note: not only sets the ServerLevel, also sets x/y/z coordinates and gamemode.
314+ }
315+ }
316+ }
317+ }
318+
283319 private static Optional <InventoryOpenEvent > callInventoryOpenEvent (ServerPlayer nmsPlayer , AbstractContainerMenu nmsView ) {
284320 //copy-pasta from CraftEventFactory, but returns the cancelled event in case it was cancelled.
285321 if (nmsPlayer .containerMenu != nmsPlayer .inventoryMenu ) {
0 commit comments