Skip to content

Commit ed37122

Browse files
committed
Workaround for PaperMC/Paper#11572: extra logic for loading world for offline player.
1 parent cfca826 commit ed37122

File tree

1 file changed

+48
-12
lines changed
  • InvSee++_Platforms/Impl_1_21_4_R3/src/main/java/com/janboerman/invsee/spigot/impl_1_21_4_R3

1 file changed

+48
-12
lines changed

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

Lines changed: 48 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,13 @@
2626
import com.janboerman.invsee.spigot.api.template.EnderChestSlot;
2727
import com.janboerman.invsee.spigot.api.template.Mirror;
2828
import 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;
3229
import com.janboerman.invsee.spigot.internal.EventHelper;
3330
import com.janboerman.invsee.spigot.internal.InvseePlatform;
3431
import com.janboerman.invsee.spigot.internal.NamesAndUUIDs;
3532
import com.janboerman.invsee.spigot.internal.OpenSpectatorsCache;
3633
import com.mojang.authlib.GameProfile;
34+
import com.mojang.serialization.DataResult;
35+
import com.mojang.serialization.Dynamic;
3736

3837
import org.bukkit.Bukkit;
3938
import org.bukkit.Location;
@@ -53,16 +52,22 @@
5352

5453
import net.minecraft.core.BlockPos;
5554
import net.minecraft.nbt.CompoundTag;
55+
import net.minecraft.nbt.NbtOps;
5656
import net.minecraft.network.protocol.game.ClientboundContainerSetSlotPacket;
5757
import net.minecraft.network.protocol.game.ClientboundOpenScreenPacket;
5858
import net.minecraft.network.protocol.game.ServerboundContainerClosePacket;
59+
import net.minecraft.resources.ResourceKey;
5960
import net.minecraft.server.dedicated.DedicatedPlayerList;
6061
import net.minecraft.server.level.ClientInformation;
62+
import net.minecraft.server.level.ServerLevel;
6163
import net.minecraft.server.level.ServerPlayer;
64+
import net.minecraft.server.players.PlayerList;
6265
import net.minecraft.world.entity.player.Inventory;
6366
import net.minecraft.world.inventory.AbstractContainerMenu;
6467
import net.minecraft.world.inventory.Slot;
6568
import net.minecraft.world.item.ItemStack;
69+
import net.minecraft.world.level.Level;
70+
import net.minecraft.world.level.dimension.DimensionType;
6671
import net.minecraft.world.level.storage.PlayerDataStorage;
6772

6873
public 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

Comments
 (0)