From 78354fdf68ed8a798906ad65bcc7f3c664f9b98e Mon Sep 17 00:00:00 2001 From: Adam Date: Fri, 29 Dec 2023 10:01:54 -0500 Subject: [PATCH] Add support for 1.20.4 (#173) --- internal/{v1_20_R1 => v1_20_R3}/pom.xml | 6 +- .../v1_20_R3}/AnySilentContainer.java | 9 +- .../internal/v1_20_R3}/OpenPlayer.java | 24 ++- .../internal/v1_20_R3}/PlayerDataManager.java | 145 ++++++++++++++---- .../internal/v1_20_R3}/SpecialEnderChest.java | 13 +- .../v1_20_R3}/SpecialPlayerInventory.java | 19 +-- .../com/lishid/openinv/InternalAccessor.java | 4 +- pom.xml | 2 +- 8 files changed, 153 insertions(+), 69 deletions(-) rename internal/{v1_20_R1 => v1_20_R3}/pom.xml (94%) rename internal/{v1_20_R1/src/main/java/com/lishid/openinv/internal/v1_20_R1 => v1_20_R3/src/main/java/com/lishid/openinv/internal/v1_20_R3}/AnySilentContainer.java (99%) rename internal/{v1_20_R1/src/main/java/com/lishid/openinv/internal/v1_20_R1 => v1_20_R3/src/main/java/com/lishid/openinv/internal/v1_20_R3}/OpenPlayer.java (85%) rename internal/{v1_20_R1/src/main/java/com/lishid/openinv/internal/v1_20_R1 => v1_20_R3/src/main/java/com/lishid/openinv/internal/v1_20_R3}/PlayerDataManager.java (59%) rename internal/{v1_20_R1/src/main/java/com/lishid/openinv/internal/v1_20_R1 => v1_20_R3/src/main/java/com/lishid/openinv/internal/v1_20_R3}/SpecialEnderChest.java (98%) rename internal/{v1_20_R1/src/main/java/com/lishid/openinv/internal/v1_20_R1 => v1_20_R3/src/main/java/com/lishid/openinv/internal/v1_20_R3}/SpecialPlayerInventory.java (99%) diff --git a/internal/v1_20_R1/pom.xml b/internal/v1_20_R3/pom.xml similarity index 94% rename from internal/v1_20_R1/pom.xml rename to internal/v1_20_R3/pom.xml index 0ac9f36b..45af58dc 100644 --- a/internal/v1_20_R1/pom.xml +++ b/internal/v1_20_R3/pom.xml @@ -26,13 +26,13 @@ 4.4.2-SNAPSHOT - openinvadapter1_20_R1 - OpenInvAdapter1_20_R1 + openinvadapter1_20_R3 + OpenInvAdapter1_20_R3 17 17 - 1.20.1-R0.1-SNAPSHOT + 1.20.4-R0.1-SNAPSHOT diff --git a/internal/v1_20_R1/src/main/java/com/lishid/openinv/internal/v1_20_R1/AnySilentContainer.java b/internal/v1_20_R3/src/main/java/com/lishid/openinv/internal/v1_20_R3/AnySilentContainer.java similarity index 99% rename from internal/v1_20_R1/src/main/java/com/lishid/openinv/internal/v1_20_R1/AnySilentContainer.java rename to internal/v1_20_R3/src/main/java/com/lishid/openinv/internal/v1_20_R3/AnySilentContainer.java index 459f9693..ea492c70 100644 --- a/internal/v1_20_R1/src/main/java/com/lishid/openinv/internal/v1_20_R1/AnySilentContainer.java +++ b/internal/v1_20_R3/src/main/java/com/lishid/openinv/internal/v1_20_R3/AnySilentContainer.java @@ -14,13 +14,11 @@ * along with this program. If not, see . */ -package com.lishid.openinv.internal.v1_20_R1; +package com.lishid.openinv.internal.v1_20_R3; import com.lishid.openinv.OpenInv; import com.lishid.openinv.internal.IAnySilentContainer; import com.lishid.openinv.util.ReflectionHelper; -import java.lang.reflect.Field; -import java.util.logging.Logger; import net.minecraft.core.BlockPos; import net.minecraft.network.chat.Component; import net.minecraft.server.level.ServerLevel; @@ -49,11 +47,14 @@ import org.bukkit.Material; import org.bukkit.Statistic; import org.bukkit.block.ShulkerBox; -import org.bukkit.craftbukkit.v1_20_R1.CraftWorld; +import org.bukkit.craftbukkit.v1_20_R3.CraftWorld; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.lang.reflect.Field; +import java.util.logging.Logger; + public class AnySilentContainer implements IAnySilentContainer { private @Nullable Field serverPlayerGameModeGameType; diff --git a/internal/v1_20_R1/src/main/java/com/lishid/openinv/internal/v1_20_R1/OpenPlayer.java b/internal/v1_20_R3/src/main/java/com/lishid/openinv/internal/v1_20_R3/OpenPlayer.java similarity index 85% rename from internal/v1_20_R1/src/main/java/com/lishid/openinv/internal/v1_20_R1/OpenPlayer.java rename to internal/v1_20_R3/src/main/java/com/lishid/openinv/internal/v1_20_R3/OpenPlayer.java index 6d70e3db..23ed6bb9 100644 --- a/internal/v1_20_R1/src/main/java/com/lishid/openinv/internal/v1_20_R1/OpenPlayer.java +++ b/internal/v1_20_R3/src/main/java/com/lishid/openinv/internal/v1_20_R3/OpenPlayer.java @@ -14,7 +14,7 @@ * along with this program. If not, see . */ -package com.lishid.openinv.internal.v1_20_R1; +package com.lishid.openinv.internal.v1_20_R3; import com.mojang.logging.LogUtils; import net.minecraft.Util; @@ -25,12 +25,13 @@ import net.minecraft.nbt.Tag; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.level.storage.PlayerDataStorage; -import org.bukkit.craftbukkit.v1_20_R1.CraftServer; -import org.bukkit.craftbukkit.v1_20_R1.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_20_R3.CraftServer; +import org.bukkit.craftbukkit.v1_20_R3.entity.CraftPlayer; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; public class OpenPlayer extends CraftPlayer { @@ -40,11 +41,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 @@ -63,11 +60,12 @@ public void saveData() { revertSpecialValues(playerData, oldData); } - File file = File.createTempFile(player.getStringUUID() + "-", ".dat", worldNBTStorage.getPlayerDir()); + Path playerDataDir = worldNBTStorage.getPlayerDir().toPath(); + Path file = Files.createTempFile(playerDataDir, player.getStringUUID() + "-", ".dat"); NbtIo.writeCompressed(playerData, file); - File file1 = new File(worldNBTStorage.getPlayerDir(), player.getStringUUID() + ".dat"); - File file2 = new File(worldNBTStorage.getPlayerDir(), player.getStringUUID() + ".dat_old"); - Util.safeReplaceFile(file1, file, file2); + Path dataFile = playerDataDir.resolve(player.getStringUUID() + ".dat"); + Path backupFile = playerDataDir.resolve(player.getStringUUID() + ".dat_old"); + Util.safeReplaceFile(dataFile, file, backupFile); } catch (Exception e) { LogUtils.getLogger().warn("Failed to save player data for {}: {}", player.getScoreboardName(), e); } diff --git a/internal/v1_20_R1/src/main/java/com/lishid/openinv/internal/v1_20_R1/PlayerDataManager.java b/internal/v1_20_R3/src/main/java/com/lishid/openinv/internal/v1_20_R3/PlayerDataManager.java similarity index 59% rename from internal/v1_20_R1/src/main/java/com/lishid/openinv/internal/v1_20_R1/PlayerDataManager.java rename to internal/v1_20_R3/src/main/java/com/lishid/openinv/internal/v1_20_R3/PlayerDataManager.java index 710e1987..240fafa5 100644 --- a/internal/v1_20_R1/src/main/java/com/lishid/openinv/internal/v1_20_R1/PlayerDataManager.java +++ b/internal/v1_20_R3/src/main/java/com/lishid/openinv/internal/v1_20_R3/PlayerDataManager.java @@ -14,47 +14,68 @@ * along with this program. If not, see . */ -package com.lishid.openinv.internal.v1_20_R1; +package com.lishid.openinv.internal.v1_20_R3; import com.lishid.openinv.OpenInv; import com.lishid.openinv.internal.IPlayerDataManager; import com.lishid.openinv.internal.ISpecialInventory; import com.lishid.openinv.internal.OpenInventoryView; import com.mojang.authlib.GameProfile; -import java.lang.reflect.Field; -import java.util.logging.Logger; +import com.mojang.serialization.Dynamic; import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtOps; import net.minecraft.network.chat.Component; import net.minecraft.network.protocol.game.ClientboundOpenScreenPacket; import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ClientInformation; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.player.ChatVisiblity; import net.minecraft.world.inventory.AbstractContainerMenu; import net.minecraft.world.inventory.MenuType; import net.minecraft.world.level.Level; +import net.minecraft.world.level.dimension.DimensionType; import org.bukkit.Bukkit; import org.bukkit.OfflinePlayer; import org.bukkit.Server; -import org.bukkit.craftbukkit.v1_20_R1.CraftServer; -import org.bukkit.craftbukkit.v1_20_R1.entity.CraftPlayer; -import org.bukkit.craftbukkit.v1_20_R1.event.CraftEventFactory; -import org.bukkit.craftbukkit.v1_20_R1.inventory.CraftContainer; +import org.bukkit.World; +import org.bukkit.craftbukkit.v1_20_R3.CraftServer; +import org.bukkit.craftbukkit.v1_20_R3.CraftWorld; +import org.bukkit.craftbukkit.v1_20_R3.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_20_R3.event.CraftEventFactory; +import org.bukkit.craftbukkit.v1_20_R3.inventory.CraftContainer; import org.bukkit.entity.Player; import org.bukkit.inventory.InventoryView; +import org.bukkit.plugin.java.JavaPlugin; import org.jetbrains.annotations.NotNull; 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 = OpenInv.getPlugin(OpenInv.class).getLogger(); - logger.warning("Unable to obtain field to inject custom save process - players' mounts may be deleted when loaded."); + Logger logger = JavaPlugin.getPlugin(OpenInv.class).getLogger(); + logger.warning("Unable to obtain field to inject custom save process - certain player data may be lost when saving!"); logger.log(java.util.logging.Level.WARNING, e.getMessage(), e); bukkitEntity = null; } @@ -74,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; @@ -87,11 +108,6 @@ 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); @@ -99,39 +115,103 @@ public PlayerDataManager() { return null; } - ServerPlayer entity = new ServerPlayer(server, worldServer, profile); + // 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. + ChatVisiblity.HIDDEN, // Don't accept chat. + false, + ServerPlayer.DEFAULT_MODEL_CUSTOMIZATION, + ServerPlayer.DEFAULT_MAIN_HAND, + true, + false // Don't list in player list (not that this player is in the list anyway). + ); + + ServerPlayer entity = new ServerPlayer(server, worldServer, profile, dummyInfo); + try { injectPlayer(entity); } catch (IllegalAccessException e) { - e.printStackTrace(); + JavaPlugin.getPlugin(OpenInv.class).getLogger().log( + java.util.logging.Level.WARNING, + e, + () -> "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 (entity.level() == null) { - // Paper: Move player to spawn - entity.spawnIn(null); + if (paper) { + // Paper: world is not loaded by ServerPlayer#load(CompoundTag). + parseWorld(player, loadedData); } - // Return the Bukkit entity. - return entity.getBukkitEntity(); + 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 = 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 = 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(player.server::getLevel) + // If ServerLevel exists, set, otherwise move to spawn. + .ifPresentOrElse(player::setServerLevel, () -> player.spawnIn(null)); + return; + } + 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; } @@ -141,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) { @@ -152,14 +231,16 @@ public Player inject(@NotNull Player player) { injectPlayer(nmsPlayer); return nmsPlayer.getBukkitEntity(); } catch (IllegalAccessException e) { - e.printStackTrace(); + JavaPlugin.getPlugin(OpenInv.class).getLogger().log( + java.util.logging.Level.WARNING, + e, + () -> "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); diff --git a/internal/v1_20_R1/src/main/java/com/lishid/openinv/internal/v1_20_R1/SpecialEnderChest.java b/internal/v1_20_R3/src/main/java/com/lishid/openinv/internal/v1_20_R3/SpecialEnderChest.java similarity index 98% rename from internal/v1_20_R1/src/main/java/com/lishid/openinv/internal/v1_20_R1/SpecialEnderChest.java rename to internal/v1_20_R3/src/main/java/com/lishid/openinv/internal/v1_20_R3/SpecialEnderChest.java index 3c66f9cd..941e7979 100644 --- a/internal/v1_20_R1/src/main/java/com/lishid/openinv/internal/v1_20_R1/SpecialEnderChest.java +++ b/internal/v1_20_R3/src/main/java/com/lishid/openinv/internal/v1_20_R3/SpecialEnderChest.java @@ -14,12 +14,9 @@ * along with this program. If not, see . */ -package com.lishid.openinv.internal.v1_20_R1; +package com.lishid.openinv.internal.v1_20_R3; import com.lishid.openinv.internal.ISpecialEnderChest; -import java.util.List; -import java.util.function.Predicate; -import java.util.stream.Collectors; import net.minecraft.core.NonNullList; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; @@ -33,13 +30,17 @@ import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.block.entity.EnderChestBlockEntity; import org.bukkit.Location; -import org.bukkit.craftbukkit.v1_20_R1.entity.CraftHumanEntity; -import org.bukkit.craftbukkit.v1_20_R1.inventory.CraftInventory; +import org.bukkit.craftbukkit.v1_20_R3.entity.CraftHumanEntity; +import org.bukkit.craftbukkit.v1_20_R3.inventory.CraftInventory; import org.bukkit.entity.HumanEntity; import org.bukkit.inventory.InventoryHolder; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.List; +import java.util.function.Predicate; +import java.util.stream.Collectors; + public class SpecialEnderChest extends PlayerEnderChestContainer implements ISpecialEnderChest { private final CraftInventory inventory; diff --git a/internal/v1_20_R1/src/main/java/com/lishid/openinv/internal/v1_20_R1/SpecialPlayerInventory.java b/internal/v1_20_R3/src/main/java/com/lishid/openinv/internal/v1_20_R3/SpecialPlayerInventory.java similarity index 99% rename from internal/v1_20_R1/src/main/java/com/lishid/openinv/internal/v1_20_R1/SpecialPlayerInventory.java rename to internal/v1_20_R3/src/main/java/com/lishid/openinv/internal/v1_20_R3/SpecialPlayerInventory.java index aef0914a..2ce13a58 100644 --- a/internal/v1_20_R1/src/main/java/com/lishid/openinv/internal/v1_20_R1/SpecialPlayerInventory.java +++ b/internal/v1_20_R3/src/main/java/com/lishid/openinv/internal/v1_20_R3/SpecialPlayerInventory.java @@ -14,16 +14,10 @@ * along with this program. If not, see . */ -package com.lishid.openinv.internal.v1_20_R1; +package com.lishid.openinv.internal.v1_20_R3; import com.google.common.collect.ImmutableList; import com.lishid.openinv.internal.ISpecialPlayerInventory; -import java.util.Collection; -import java.util.Iterator; -import java.util.List; -import java.util.function.Function; -import java.util.function.Predicate; -import java.util.stream.Collectors; import net.minecraft.CrashReport; import net.minecraft.CrashReportCategory; import net.minecraft.ReportedException; @@ -47,13 +41,20 @@ import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.block.state.BlockState; import org.bukkit.Location; -import org.bukkit.craftbukkit.v1_20_R1.entity.CraftHumanEntity; -import org.bukkit.craftbukkit.v1_20_R1.inventory.CraftInventory; +import org.bukkit.craftbukkit.v1_20_R3.entity.CraftHumanEntity; +import org.bukkit.craftbukkit.v1_20_R3.inventory.CraftInventory; import org.bukkit.entity.HumanEntity; import org.bukkit.inventory.InventoryHolder; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Collectors; + public class SpecialPlayerInventory extends Inventory implements ISpecialPlayerInventory { private final CraftInventory inventory; diff --git a/plugin/src/main/java/com/lishid/openinv/InternalAccessor.java b/plugin/src/main/java/com/lishid/openinv/InternalAccessor.java index 2bf529a3..ff2c716a 100644 --- a/plugin/src/main/java/com/lishid/openinv/InternalAccessor.java +++ b/plugin/src/main/java/com/lishid/openinv/InternalAccessor.java @@ -22,11 +22,12 @@ import com.lishid.openinv.internal.ISpecialInventory; import com.lishid.openinv.internal.ISpecialPlayerInventory; import com.lishid.openinv.util.InventoryAccess; -import java.lang.reflect.Constructor; import org.bukkit.entity.Player; import org.bukkit.plugin.Plugin; import org.jetbrains.annotations.NotNull; +import java.lang.reflect.Constructor; + class InternalAccessor { private final @NotNull Plugin plugin; @@ -66,6 +67,7 @@ public String getReleasesLink() { case "v1_17_R1", "v1_18_R1" -> "https://github.com/Jikoo/OpenInv/releases/tag/4.1.10"; case "v1_19_R1" -> "https://github.com/Jikoo/OpenInv/releases/tag/4.2.2"; case "v1_18_R2", "v1_19_R2" -> "https://github.com/Jikoo/OpenInv/releases/tag/4.3.0"; + case "v1_20_R1" -> "https://github.com/Jikoo/OpenInv/releases/tag/4.4.1"; default -> "https://github.com/Jikoo/OpenInv/releases"; }; } diff --git a/pom.xml b/pom.xml index d55b7657..6126bc82 100644 --- a/pom.xml +++ b/pom.xml @@ -41,8 +41,8 @@ api plugin internal/v1_19_R3 - internal/v1_20_R1 internal/v1_20_R2 + internal/v1_20_R3 assembly