Skip to content

Commit

Permalink
Fix world not loading
Browse files Browse the repository at this point in the history
  • Loading branch information
Jikoo committed Nov 12, 2023
1 parent c9a2ae4 commit f9c7ed7
Show file tree
Hide file tree
Showing 2 changed files with 82 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,7 @@ public OpenPlayer(CraftServer server, ServerPlayer entity) {

@Override
public void loadData() {
// See CraftPlayer#loadData
CompoundTag loaded = this.server.getHandle().playerIo.load(this.getHandle());
if (loaded != null) {
getHandle().readAdditionalSaveData(loaded);
}
PlayerDataManager.loadData(getHandle());
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.Server;
import org.bukkit.World;
import org.bukkit.craftbukkit.v1_20_R2.CraftServer;
import org.bukkit.craftbukkit.v1_20_R2.CraftWorld;
import org.bukkit.craftbukkit.v1_20_R2.entity.CraftPlayer;
import org.bukkit.craftbukkit.v1_20_R2.event.CraftEventFactory;
import org.bukkit.craftbukkit.v1_20_R2.inventory.CraftContainer;
Expand All @@ -50,18 +52,30 @@
import org.jetbrains.annotations.Nullable;

import java.lang.reflect.Field;
import java.util.UUID;
import java.util.logging.Logger;

public class PlayerDataManager implements IPlayerDataManager {

private static boolean paper;

static {
try {
Class.forName("io.papermc.paper.configuration.Configuration");
paper = true;
} catch (ClassNotFoundException ignored) {
paper = false;
}
}

private @Nullable Field bukkitEntity;

public PlayerDataManager() {
try {
bukkitEntity = Entity.class.getDeclaredField("bukkitEntity");
} catch (NoSuchFieldException e) {
Logger logger = JavaPlugin.getPlugin(OpenInv.class).getLogger();
logger.warning("Unable to obtain field to inject custom save process - players' mounts may be deleted when loaded.");
logger.warning("Unable to obtain field to inject custom save process - certain player data may be lost when saving!");

This comment has been minimized.

Copy link
@rautamiekka

rautamiekka Nov 12, 2023

I could be wrong, but can't you add the declared field to inject the custom save at this point ?

This comment has been minimized.

Copy link
@Jikoo

Jikoo Nov 12, 2023

Author Owner

Not sure what you're trying to say, could you reword? This is the field that must be replaced by OI's Player implementation with a customized save process. If it cannot be accessed, OI cannot replace the field's content.

This comment has been minimized.

Copy link
@rautamiekka

rautamiekka Nov 12, 2023

Exactly what I meant. Doesn't make much sense to me, but that's just me.

This comment has been minimized.

Copy link
@Jikoo

Jikoo Nov 12, 2023

Author Owner

The specific field is the one returned by the method for getting the Bukkit entity. If it doesn't exist and we were to instrument or otherwise create it, it wouldn't do us any good because the existing method would need to be modified (if the field doesn't exist, the method definitely isn't trying to return its value), at which point this field wouldn't matter anyways because we could just rewrite the method.

The reason we inject our copy rather than make our own ServerPlayer implementation is that existing loaded ServerPlayers also must be supported. Unless we replace large swathes of the PlayerList or instrument ServerPlayer, that just isn't possible. There are a lot of reasons not to do that, too - maintainability, instrumenting on Spigot being a pain, and no reason to be invasive than OI needs to be to function.

This comment has been minimized.

Copy link
@rautamiekka

rautamiekka Nov 12, 2023

Fair enough.

logger.log(java.util.logging.Level.WARNING, e.getMessage(), e);
bukkitEntity = null;
}
Expand All @@ -81,7 +95,7 @@ public PlayerDataManager() {

if (nmsPlayer == null) {
// Could use reflection to examine fields, but it's honestly not worth the bother.
throw new RuntimeException("Unable to fetch EntityPlayer from provided Player implementation");
throw new RuntimeException("Unable to fetch EntityPlayer from Player implementation " + player.getClass().getName());
}

return nmsPlayer;
Expand All @@ -94,18 +108,37 @@ public PlayerDataManager() {
return null;
}

// Create a profile and entity to load the player data
// See net.minecraft.server.players.PlayerList#canPlayerLogin
// and net.minecraft.server.network.ServerLoginPacketListenerImpl#handleHello
GameProfile profile = new GameProfile(offline.getUniqueId(),
offline.getName() != null ? offline.getName() : offline.getUniqueId().toString());
MinecraftServer server = ((CraftServer) Bukkit.getServer()).getServer();
ServerLevel worldServer = server.getLevel(Level.OVERWORLD);

if (worldServer == null) {
return null;
}

// Create a new ServerPlayer.
ServerPlayer entity = createNewPlayer(server, worldServer, offline);

// Stop listening for advancement progression - if this is not cleaned up, loading causes a memory leak.
entity.getAdvancements().stopListening();

// Try to load the player's data.
if (loadData(entity)) {
// If data is loaded successfully, return the Bukkit entity.
return entity.getBukkitEntity();
}

return null;
}

private @NotNull ServerPlayer createNewPlayer(
@NotNull MinecraftServer server,
@NotNull ServerLevel worldServer,
@NotNull final OfflinePlayer offline) {
// See net.minecraft.server.players.PlayerList#canPlayerLogin(ServerLoginPacketListenerImpl, GameProfile)
// See net.minecraft.server.network.ServerLoginPacketListenerImpl#handleHello(ServerboundHelloPacket)
GameProfile profile = new GameProfile(offline.getUniqueId(),
offline.getName() != null ? offline.getName() : offline.getUniqueId().toString());

ClientInformation dummyInfo = new ClientInformation(
"en_us",
1, // Reduce distance just in case.
Expand All @@ -119,46 +152,66 @@ public PlayerDataManager() {

ServerPlayer entity = new ServerPlayer(server, worldServer, profile, dummyInfo);

// Stop listening for advancement progression - if this is not cleaned up, loading causes a memory leak.
entity.getAdvancements().stopListening();

try {
injectPlayer(entity);
} catch (IllegalAccessException e) {
JavaPlugin.getPlugin(OpenInv.class).getLogger().log(
java.util.logging.Level.WARNING,
e,
() -> "Unable to inject ServerPlayer, players' mounts may be lost!");
() -> "Unable to inject ServerPlayer, certain player data may be lost when saving!");
}

// Load data. This also reads basic data into the player.
return entity;
}

static boolean loadData(@NotNull ServerPlayer player) {
// See CraftPlayer#loadData
CompoundTag loadedData = server.getPlayerList().playerIo.load(entity);
CompoundTag loadedData = player.server.getPlayerList().playerIo.load(player);

if (loadedData == null) {
// Exceptions with loading are logged by Mojang.
return null;
return false;
}

// Read basic data into the player.
player.load(loadedData);
// Also read "extra" data.
entity.readAdditionalSaveData(loadedData);
player.readAdditionalSaveData(loadedData);

if (paper) {
// Paper: world is not loaded by ServerPlayer#load(CompoundTag).
parseWorld(player, loadedData);
}

if (entity.level() == null) {
// Paper: Also read world.
// See PlayerList#placeNewPlayer
// Using legacy parse supports ancient vanilla player data.
return true;
}

private static void parseWorld(@NotNull ServerPlayer player, @NotNull CompoundTag loadedData) {
// See PlayerList#placeNewPlayer
World bukkitWorld;
if (loadedData.contains("WorldUUIDMost") && loadedData.contains("WorldUUIDLeast")) {
// Modern Bukkit world.
bukkitWorld = org.bukkit.Bukkit.getServer().getWorld(new UUID(loadedData.getLong("WorldUUIDMost"), loadedData.getLong("WorldUUIDLeast")));
} else if (loadedData.contains("world", net.minecraft.nbt.Tag.TAG_STRING)) {
// Legacy Bukkit world.
bukkitWorld = org.bukkit.Bukkit.getServer().getWorld(loadedData.getString("world"));
} else {
// Vanilla player data.
DimensionType.parseLegacy(new Dynamic<>(NbtOps.INSTANCE, loadedData.get("Dimension")))
.resultOrPartial(JavaPlugin.getPlugin(OpenInv.class).getLogger()::warning)
.map(server::getLevel)
.map(player.server::getLevel)
// If ServerLevel exists, set, otherwise move to spawn.
.ifPresentOrElse(entity::setServerLevel, () -> entity.spawnIn(null));
.ifPresentOrElse(player::setServerLevel, () -> player.spawnIn(null));
return;
}

// Return the Bukkit entity.
return entity.getBukkitEntity();
if (bukkitWorld == null) {
player.spawnIn(null);
return;
}
player.setServerLevel(((CraftWorld) bukkitWorld).getHandle());
}

void injectPlayer(ServerPlayer player) throws IllegalAccessException {
private void injectPlayer(ServerPlayer player) throws IllegalAccessException {
if (bukkitEntity == null) {
return;
}
Expand All @@ -168,9 +221,8 @@ void injectPlayer(ServerPlayer player) throws IllegalAccessException {
bukkitEntity.set(player, new OpenPlayer(player.server.server, player));
}

@NotNull
@Override
public Player inject(@NotNull Player player) {
public @NotNull Player inject(@NotNull Player player) {
try {
ServerPlayer nmsPlayer = getHandle(player);
if (nmsPlayer.getBukkitEntity() instanceof OpenPlayer openPlayer) {
Expand All @@ -182,14 +234,13 @@ public Player inject(@NotNull Player player) {
JavaPlugin.getPlugin(OpenInv.class).getLogger().log(
java.util.logging.Level.WARNING,
e,
() -> "Unable to inject ServerPlayer, players' mounts may be lost!");
() -> "Unable to inject ServerPlayer, certain player data may be lost when saving!");
return player;
}
}

@Nullable
@Override
public InventoryView openInventory(@NotNull Player player, @NotNull ISpecialInventory inventory) {
public @Nullable InventoryView openInventory(@NotNull Player player, @NotNull ISpecialInventory inventory) {

ServerPlayer nmsPlayer = getHandle(player);

Expand Down

0 comments on commit f9c7ed7

Please sign in to comment.