From a1b2df74cc56e93f3f35e09b22f1530c67794740 Mon Sep 17 00:00:00 2001 From: Jikoo Date: Sun, 19 Apr 2020 17:52:44 -0400 Subject: [PATCH] Save existing vehicles if we had to load player (#22) --- api/pom.xml | 2 +- .../internal/v1_14_R1/PlayerDataManager.java | 86 +++++++++++++++++- .../internal/v1_15_R1/PlayerDataManager.java | 89 ++++++++++++++++++- .../internal/v1_8_R3/PlayerDataManager.java | 87 +++++++++++++++++- .../main/java/com/lishid/openinv/OpenInv.java | 3 + .../openinv/internal/IPlayerDataManager.java | 8 ++ 6 files changed, 269 insertions(+), 6 deletions(-) diff --git a/api/pom.xml b/api/pom.xml index 22aa3ce..5149d68 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -21,7 +21,7 @@ com.lishid openinvparent - 4.1.0 + 4.1.1-SNAPSHOT openinvapi diff --git a/internal/v1_14_R1/src/main/java/com/lishid/openinv/internal/v1_14_R1/PlayerDataManager.java b/internal/v1_14_R1/src/main/java/com/lishid/openinv/internal/v1_14_R1/PlayerDataManager.java index 5d6c9d7..96a5d33 100644 --- a/internal/v1_14_R1/src/main/java/com/lishid/openinv/internal/v1_14_R1/PlayerDataManager.java +++ b/internal/v1_14_R1/src/main/java/com/lishid/openinv/internal/v1_14_R1/PlayerDataManager.java @@ -20,18 +20,26 @@ import com.lishid.openinv.OpenInv; import com.lishid.openinv.internal.IPlayerDataManager; import com.lishid.openinv.internal.ISpecialInventory; import com.mojang.authlib.GameProfile; +import java.io.File; +import java.io.FileOutputStream; +import java.lang.reflect.Field; import net.minecraft.server.v1_14_R1.ChatComponentText; import net.minecraft.server.v1_14_R1.ChatMessageType; import net.minecraft.server.v1_14_R1.Container; import net.minecraft.server.v1_14_R1.Containers; import net.minecraft.server.v1_14_R1.DimensionManager; +import net.minecraft.server.v1_14_R1.Entity; import net.minecraft.server.v1_14_R1.EntityHuman; import net.minecraft.server.v1_14_R1.EntityPlayer; import net.minecraft.server.v1_14_R1.MinecraftServer; +import net.minecraft.server.v1_14_R1.NBTCompressedStreamTools; +import net.minecraft.server.v1_14_R1.NBTTagCompound; import net.minecraft.server.v1_14_R1.PacketPlayOutChat; import net.minecraft.server.v1_14_R1.PacketPlayOutOpenWindow; import net.minecraft.server.v1_14_R1.PlayerInteractManager; import net.minecraft.server.v1_14_R1.PlayerInventory; +import net.minecraft.server.v1_14_R1.WorldNBTStorage; +import org.apache.logging.log4j.LogManager; import org.bukkit.Bukkit; import org.bukkit.OfflinePlayer; import org.bukkit.Server; @@ -49,6 +57,18 @@ import org.jetbrains.annotations.Nullable; public class PlayerDataManager implements IPlayerDataManager { + private Field bukkitEntity; + + public PlayerDataManager() { + try { + bukkitEntity = Entity.class.getDeclaredField("bukkitEntity"); + } catch (NoSuchFieldException e) { + System.out.println("Unable to obtain field to inject custom save process - players' mounts may be deleted when loaded."); + e.printStackTrace(); + bukkitEntity = null; + } + } + @NotNull public static EntityPlayer getHandle(final Player player) { if (player instanceof CraftPlayer) { @@ -78,11 +98,18 @@ public class PlayerDataManager implements IPlayerDataManager { } // Create a profile and entity to load the player data - GameProfile profile = new GameProfile(offline.getUniqueId(), offline.getName()); + GameProfile profile = new GameProfile(offline.getUniqueId(), + offline.getName() != null ? offline.getName() : offline.getUniqueId().toString()); MinecraftServer server = ((CraftServer) Bukkit.getServer()).getServer(); EntityPlayer entity = new EntityPlayer(server, server.getWorldServer(DimensionManager.OVERWORLD), profile, new PlayerInteractManager(server.getWorldServer(DimensionManager.OVERWORLD))); + try { + injectPlayer(entity); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + // Get the bukkit entity Player target = entity.getBukkitEntity(); if (target != null) { @@ -93,6 +120,63 @@ public class PlayerDataManager implements IPlayerDataManager { return target; } + void injectPlayer(EntityPlayer player) throws IllegalAccessException { + if (bukkitEntity == null) { + return; + } + + bukkitEntity.setAccessible(true); + + bukkitEntity.set(player, new CraftPlayer(player.server.server, player) { + @Override + public void saveData() { + super.saveData(); + // See net.minecraft.server.WorldNBTStorage#save(EntityPlayer) + try { + WorldNBTStorage worldNBTStorage = (WorldNBTStorage) player.server.getPlayerList().playerFileData; + + NBTTagCompound playerData = player.save(new NBTTagCompound()); + + if (!isOnline()) { + // Special case: save old vehicle data + NBTTagCompound oldData = worldNBTStorage.load(player); + + if (oldData != null && oldData.hasKeyOfType("RootVehicle", 10)) { + // See net.minecraft.server.PlayerList#a(NetworkManager, EntityPlayer) and net.minecraft.server.EntityPlayer#b(NBTTagCompound) + playerData.set("RootVehicle", oldData.getCompound("RootVehicle")); + } + } + + File file = new File(worldNBTStorage.getPlayerDir(), player.getUniqueIDString() + ".dat.tmp"); + File file1 = new File(worldNBTStorage.getPlayerDir(), player.getUniqueIDString() + ".dat"); + + NBTCompressedStreamTools.a(playerData, new FileOutputStream(file)); + + if (file1.exists()) { + file1.delete(); + } + + file.renameTo(file1); + } catch (Exception e) { + LogManager.getLogger().warn("Failed to save player data for {}", player.getDisplayName().getString()); + } + } + }); + } + + @NotNull + @Override + public Player inject(@NotNull Player player) { + try { + EntityPlayer nmsPlayer = getHandle(player); + injectPlayer(nmsPlayer); + return nmsPlayer.getBukkitEntity(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + return player; + } + } + @Nullable @Override public InventoryView openInventory(@NotNull Player player, @NotNull ISpecialInventory inventory) { diff --git a/internal/v1_15_R1/src/main/java/com/lishid/openinv/internal/v1_15_R1/PlayerDataManager.java b/internal/v1_15_R1/src/main/java/com/lishid/openinv/internal/v1_15_R1/PlayerDataManager.java index 25e6deb..854a7b7 100644 --- a/internal/v1_15_R1/src/main/java/com/lishid/openinv/internal/v1_15_R1/PlayerDataManager.java +++ b/internal/v1_15_R1/src/main/java/com/lishid/openinv/internal/v1_15_R1/PlayerDataManager.java @@ -20,18 +20,26 @@ import com.lishid.openinv.OpenInv; import com.lishid.openinv.internal.IPlayerDataManager; import com.lishid.openinv.internal.ISpecialInventory; import com.mojang.authlib.GameProfile; +import java.io.File; +import java.io.FileOutputStream; +import java.lang.reflect.Field; import net.minecraft.server.v1_15_R1.ChatComponentText; import net.minecraft.server.v1_15_R1.ChatMessageType; import net.minecraft.server.v1_15_R1.Container; import net.minecraft.server.v1_15_R1.Containers; import net.minecraft.server.v1_15_R1.DimensionManager; +import net.minecraft.server.v1_15_R1.Entity; import net.minecraft.server.v1_15_R1.EntityHuman; import net.minecraft.server.v1_15_R1.EntityPlayer; import net.minecraft.server.v1_15_R1.MinecraftServer; +import net.minecraft.server.v1_15_R1.NBTCompressedStreamTools; +import net.minecraft.server.v1_15_R1.NBTTagCompound; import net.minecraft.server.v1_15_R1.PacketPlayOutChat; import net.minecraft.server.v1_15_R1.PacketPlayOutOpenWindow; import net.minecraft.server.v1_15_R1.PlayerInteractManager; import net.minecraft.server.v1_15_R1.PlayerInventory; +import net.minecraft.server.v1_15_R1.WorldNBTStorage; +import org.apache.logging.log4j.LogManager; import org.bukkit.Bukkit; import org.bukkit.OfflinePlayer; import org.bukkit.Server; @@ -49,6 +57,18 @@ import org.jetbrains.annotations.Nullable; public class PlayerDataManager implements IPlayerDataManager { + private Field bukkitEntity; + + public PlayerDataManager() { + try { + bukkitEntity = Entity.class.getDeclaredField("bukkitEntity"); + } catch (NoSuchFieldException e) { + System.out.println("Unable to obtain field to inject custom save process - players' mounts may be deleted when loaded."); + e.printStackTrace(); + bukkitEntity = null; + } + } + @NotNull public static EntityPlayer getHandle(final Player player) { if (player instanceof CraftPlayer) { @@ -79,11 +99,19 @@ public class PlayerDataManager implements IPlayerDataManager { } // Create a profile and entity to load the player data - GameProfile profile = new GameProfile(offline.getUniqueId(), offline.getName()); + // See net.minecraft.server.PlayerList#attemptLogin + GameProfile profile = new GameProfile(offline.getUniqueId(), + offline.getName() != null ? offline.getName() : offline.getUniqueId().toString()); MinecraftServer server = ((CraftServer) Bukkit.getServer()).getServer(); EntityPlayer entity = new EntityPlayer(server, server.getWorldServer(DimensionManager.OVERWORLD), profile, new PlayerInteractManager(server.getWorldServer(DimensionManager.OVERWORLD))); + try { + injectPlayer(entity); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + // Get the bukkit entity Player target = entity.getBukkitEntity(); if (target != null) { @@ -94,6 +122,63 @@ public class PlayerDataManager implements IPlayerDataManager { return target; } + void injectPlayer(EntityPlayer player) throws IllegalAccessException { + if (bukkitEntity == null) { + return; + } + + bukkitEntity.setAccessible(true); + + bukkitEntity.set(player, new CraftPlayer(player.server.server, player) { + @Override + public void saveData() { + super.saveData(); + // See net.minecraft.server.WorldNBTStorage#save(EntityPlayer) + try { + WorldNBTStorage worldNBTStorage = (WorldNBTStorage) player.server.getPlayerList().playerFileData; + + NBTTagCompound playerData = player.save(new NBTTagCompound()); + + if (!isOnline()) { + // Special case: save old vehicle data + NBTTagCompound oldData = worldNBTStorage.load(player); + + if (oldData != null && oldData.hasKeyOfType("RootVehicle", 10)) { + // See net.minecraft.server.PlayerList#a(NetworkManager, EntityPlayer) and net.minecraft.server.EntityPlayer#b(NBTTagCompound) + playerData.set("RootVehicle", oldData.getCompound("RootVehicle")); + } + } + + File file = new File(worldNBTStorage.getPlayerDir(), player.getUniqueIDString() + ".dat.tmp"); + File file1 = new File(worldNBTStorage.getPlayerDir(), player.getUniqueIDString() + ".dat"); + + NBTCompressedStreamTools.a(playerData, new FileOutputStream(file)); + + if (file1.exists()) { + file1.delete(); + } + + file.renameTo(file1); + } catch (Exception e) { + LogManager.getLogger().warn("Failed to save player data for {}", player.getDisplayName().getString()); + } + } + }); + } + + @NotNull + @Override + public Player inject(@NotNull Player player) { + try { + EntityPlayer nmsPlayer = getHandle(player); + injectPlayer(nmsPlayer); + return nmsPlayer.getBukkitEntity(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + return player; + } + } + @Nullable @Override public InventoryView openInventory(@NotNull Player player, @NotNull ISpecialInventory inventory) { @@ -111,8 +196,6 @@ public class PlayerDataManager implements IPlayerDataManager { if (title == null) { title = "%player%'s Ender Chest"; } - //noinspection ConstantConditions - owner name can be null if loaded by UUID - title = title.replace("%player%", owner.getName() != null ? owner.getName() : owner.getUniqueId().toString()); } else if (inventory instanceof SpecialPlayerInventory) { EntityHuman owner = ((PlayerInventory) inventory).player; title = OpenInv.getPlugin(OpenInv.class).getLocalizedMessage(player, "container.player"); diff --git a/internal/v1_8_R3/src/main/java/com/lishid/openinv/internal/v1_8_R3/PlayerDataManager.java b/internal/v1_8_R3/src/main/java/com/lishid/openinv/internal/v1_8_R3/PlayerDataManager.java index 7153515..f38240d 100644 --- a/internal/v1_8_R3/src/main/java/com/lishid/openinv/internal/v1_8_R3/PlayerDataManager.java +++ b/internal/v1_8_R3/src/main/java/com/lishid/openinv/internal/v1_8_R3/PlayerDataManager.java @@ -19,11 +19,19 @@ package com.lishid.openinv.internal.v1_8_R3; import com.lishid.openinv.internal.IPlayerDataManager; import com.lishid.openinv.internal.ISpecialInventory; import com.mojang.authlib.GameProfile; +import java.io.File; +import java.io.FileOutputStream; +import java.lang.reflect.Field; import net.minecraft.server.v1_8_R3.ChatComponentText; +import net.minecraft.server.v1_8_R3.Entity; import net.minecraft.server.v1_8_R3.EntityPlayer; import net.minecraft.server.v1_8_R3.MinecraftServer; +import net.minecraft.server.v1_8_R3.NBTCompressedStreamTools; +import net.minecraft.server.v1_8_R3.NBTTagCompound; import net.minecraft.server.v1_8_R3.PacketPlayOutChat; import net.minecraft.server.v1_8_R3.PlayerInteractManager; +import net.minecraft.server.v1_8_R3.WorldNBTStorage; +import org.apache.logging.log4j.LogManager; import org.bukkit.Bukkit; import org.bukkit.OfflinePlayer; import org.bukkit.Server; @@ -36,6 +44,18 @@ import org.jetbrains.annotations.Nullable; public class PlayerDataManager implements IPlayerDataManager { + private Field bukkitEntity; + + public PlayerDataManager() { + try { + bukkitEntity = Entity.class.getDeclaredField("bukkitEntity"); + } catch (NoSuchFieldException e) { + System.out.println("Unable to obtain field to inject custom save process - players' mounts may be deleted when loaded."); + e.printStackTrace(); + bukkitEntity = null; + } + } + @NotNull public static EntityPlayer getHandle(Player player) { if (player instanceof CraftPlayer) { @@ -66,11 +86,18 @@ public class PlayerDataManager implements IPlayerDataManager { } // Create a profile and entity to load the player data - GameProfile profile = new GameProfile(offline.getUniqueId(), offline.getName()); + GameProfile profile = new GameProfile(offline.getUniqueId(), + offline.getName() != null ? offline.getName() : offline.getUniqueId().toString()); MinecraftServer server = ((CraftServer) Bukkit.getServer()).getServer(); EntityPlayer entity = new EntityPlayer(server, server.getWorldServer(0), profile, new PlayerInteractManager(server.getWorldServer(0))); + try { + injectPlayer(entity); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + // Get the bukkit entity Player target = entity.getBukkitEntity(); if (target != null) { @@ -81,6 +108,64 @@ public class PlayerDataManager implements IPlayerDataManager { return target; } + void injectPlayer(EntityPlayer player) throws IllegalAccessException { + if (bukkitEntity == null) { + return; + } + + bukkitEntity.setAccessible(true); + + bukkitEntity.set(player, new CraftPlayer(player.server.server, player) { + @Override + public void saveData() { + super.saveData(); + // See net.minecraft.server.WorldNBTStorage#save(EntityHuman) + try { + WorldNBTStorage worldNBTStorage = (WorldNBTStorage) player.server.getPlayerList().playerFileData; + + NBTTagCompound playerData = new NBTTagCompound(); + player.e(playerData); + + if (!isOnline()) { + // Special case: save old vehicle data + NBTTagCompound oldData = worldNBTStorage.load(player); + + if (oldData != null && oldData.hasKeyOfType("Riding", 10)) { + // See net.minecraft.server.PlayerList#a(NetworkManager, EntityPlayer) and net.minecraft.server.EntityPlayer#b(NBTTagCompound) + playerData.set("Riding", oldData.getCompound("Riding")); + } + } + + File file = new File(worldNBTStorage.getPlayerDir(), player.getUniqueID().toString() + ".dat.tmp"); + File file1 = new File(worldNBTStorage.getPlayerDir(), player.getUniqueID().toString() + ".dat"); + + NBTCompressedStreamTools.a(playerData, new FileOutputStream(file)); + + if (file1.exists()) { + file1.delete(); + } + + file.renameTo(file1); + } catch (Exception e) { + LogManager.getLogger().warn("Failed to save player data for {}", player.getName()); + } + } + }); + } + + @NotNull + @Override + public Player inject(@NotNull Player player) { + try { + EntityPlayer nmsPlayer = getHandle(player); + injectPlayer(nmsPlayer); + return nmsPlayer.getBukkitEntity(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + return player; + } + } + @Nullable @Override public InventoryView openInventory(@NotNull Player player, @NotNull ISpecialInventory inventory) { diff --git a/plugin/src/main/java/com/lishid/openinv/OpenInv.java b/plugin/src/main/java/com/lishid/openinv/OpenInv.java index 151ad67..4f95b7b 100644 --- a/plugin/src/main/java/com/lishid/openinv/OpenInv.java +++ b/plugin/src/main/java/com/lishid/openinv/OpenInv.java @@ -465,6 +465,9 @@ public class OpenInv extends JavaPlugin implements IOpenInv { return; } + // Replace stored player with our own version + this.playerCache.put(key, this.accessor.getPlayerDataManager().inject(player)); + if (this.inventories.containsKey(key)) { this.inventories.get(key).setPlayerOffline(); } diff --git a/plugin/src/main/java/com/lishid/openinv/internal/IPlayerDataManager.java b/plugin/src/main/java/com/lishid/openinv/internal/IPlayerDataManager.java index a801490..92c86af 100644 --- a/plugin/src/main/java/com/lishid/openinv/internal/IPlayerDataManager.java +++ b/plugin/src/main/java/com/lishid/openinv/internal/IPlayerDataManager.java @@ -35,6 +35,14 @@ public interface IPlayerDataManager { @Nullable Player loadPlayer(@NotNull OfflinePlayer offline); + /** + * Creates a new Player from an existing one that will function slightly better offline. + * + * @return the Player + */ + @NotNull + Player inject(@NotNull Player player); + /** * Opens an ISpecialInventory for a Player. *