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.
*