From bf59617e196fa26cddcd82c45716ab2b982551b8 Mon Sep 17 00:00:00 2001 From: Marc Baloup Date: Sat, 18 Feb 2023 21:32:12 +0100 Subject: [PATCH] Refactor offline player data access - Class that handle all Bukkit/NBT conversion of player data - Ability to read and save the player inventory (more to come later) --- .../lib/paper/players/PaperOffPlayer.java | 78 ++-- .../lib/paper/players/PaperOnlinePlayer.java | 19 +- .../wrapper/craftbukkit/CraftItemStack.java | 6 + .../wrapper/minecraft/nbt/CollectionTag.java | 17 +- .../wrapper/minecraft/nbt/ListTag.java | 6 + .../wrapper/minecraft/world/ItemStack.java | 8 +- .../lib/paper/util/InventoryWrapper.java | 199 ++++++++++ .../lib/paper/util/PlayerDataWrapper.java | 350 ++++++++++++++++++ 8 files changed, 613 insertions(+), 70 deletions(-) create mode 100644 pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/InventoryWrapper.java create mode 100644 pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/PlayerDataWrapper.java diff --git a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/players/PaperOffPlayer.java b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/players/PaperOffPlayer.java index 4e02f0f..114c0e1 100644 --- a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/players/PaperOffPlayer.java +++ b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/players/PaperOffPlayer.java @@ -2,19 +2,18 @@ package fr.pandacube.lib.paper.players; import com.google.common.io.Files; import fr.pandacube.lib.paper.reflect.util.PrimaryWorlds; -import fr.pandacube.lib.paper.reflect.wrapper.craftbukkit.CraftItemStack; import fr.pandacube.lib.paper.reflect.wrapper.craftbukkit.CraftServer; import fr.pandacube.lib.paper.reflect.wrapper.minecraft.nbt.CompoundTag; -import fr.pandacube.lib.paper.reflect.wrapper.minecraft.nbt.ListTag; import fr.pandacube.lib.paper.reflect.wrapper.minecraft.nbt.NbtIo; +import fr.pandacube.lib.paper.util.PlayerDataWrapper; import fr.pandacube.lib.paper.util.WorldUtil; import fr.pandacube.lib.players.standalone.AbstractOffPlayer; import fr.pandacube.lib.reflect.wrapper.ReflectWrapper; import org.bukkit.Bukkit; import org.bukkit.OfflinePlayer; import org.bukkit.entity.Player; -import org.bukkit.event.inventory.InventoryType; -import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.PlayerInventory; import org.bukkit.scoreboard.Team; import java.io.File; @@ -164,6 +163,16 @@ public interface PaperOffPlayer extends AbstractOffPlayer { .getPlayerData(getUniqueId().toString()); } + /** + * Gets a wrapper for the NBT data from the playerdata file. + * It will not work if the player is online, because the data on the file are not synchronized with real-time values. + * @return the NBT data from the playerdata file. + * @throws IllegalStateException if the player is online. + */ + default PlayerDataWrapper getPlayerDataWrapper() { + return new PlayerDataWrapper(getPlayerData()); + } + /** * Saves the provided NBT data to the playerdata file. * It will not work if the player is online, because the provided data will be lost when the player disconnects. @@ -171,14 +180,14 @@ public interface PaperOffPlayer extends AbstractOffPlayer { * @throws IllegalStateException if the player is online. * @throws IOException if an IO error occurs. */ - default void savePlayerData(CompoundTag data) throws IOException { + default void savePlayerData(PlayerDataWrapper data) throws IOException { if (isOnline()) throw new IllegalStateException("Cannot write data file of " + getName() + " because they’re online."); File file = getPlayerDataFile(false); File old = getPlayerDataFile(true); old.delete(); Files.move(file, old); - NbtIo.writeCompressed(data, file); + NbtIo.writeCompressed(data.data, file); } /** @@ -192,60 +201,19 @@ public interface PaperOffPlayer extends AbstractOffPlayer { } /** - * Gets the content of the player’s inventory. - * @return the content of the player’s inventory. + * Gets the player’s inventory. + * @return the player’s inventory. */ - default ItemStack[] getInventoryContent() { - ItemStack[] content = new ItemStack[InventoryType.PLAYER.getDefaultSize()]; - CompoundTag playerData = getPlayerData(); - if (playerData == null) - return content; - ListTag nbttaglist = playerData.getList("Inventory", 10); // type of list element 10 is CompoundTag - if (nbttaglist == null) - return content; - // cat nbEl NBTslot bukkitSlot NBT->Bukkit - // items 36el 0-35 == - // armor 4el start 100 36-39 -100 + 36 - // offhnd 1el start 150 40 -150 + 40 - for (int i = 0; i < nbttaglist.size(); i++) { - CompoundTag itemTag = nbttaglist.getCompound(i); - ItemStack is = CraftItemStack.asCraftMirror(itemTag); - if (is != null && !is.getType().isAir()) { - int nbtSlot = itemTag.getByte("Slot") & 255; - int bukkitSlot = nbtSlot < 36 ? nbtSlot - : (nbtSlot >= 100 && nbtSlot < 104) ? nbtSlot - 100 + 36 - : nbtSlot == 150 ? 40 - : -1; - if (bukkitSlot >= 0) - content[bukkitSlot] = is; - } - } - - return content; + default PlayerInventory getInventory() { + return getPlayerDataWrapper().getInventory(); } /** - * Gets the content of the player’s enderchest. - * @return the content of the player’s enderchest. + * Gets the player’s enderchest. + * @return the player’s enderchest. */ - default ItemStack[] getEnderchestContent() { - ItemStack[] content = new ItemStack[InventoryType.ENDER_CHEST.getDefaultSize()]; - CompoundTag playerData = getPlayerData(); - if (playerData == null || !playerData.contains("EnderItems", 9)) // type 9 is list - return content; - ListTag nbtList = playerData.getList("EnderItems", 10); // type of list element 10 is CompoundTag - if (nbtList == null) - return content; - for (int i = 0; i < nbtList.size(); i++) { - CompoundTag itemTag = nbtList.getCompound(i); - int nbtSlot = itemTag.getByte("Slot") & 255; - ItemStack is = CraftItemStack.asCraftMirror(itemTag); - if (nbtSlot < content.length && is != null && !is.getType().isAir()) { - content[nbtSlot] = CraftItemStack.asCraftMirror(itemTag); - } - } - - return content; + default Inventory getEnderChest() { + return getPlayerDataWrapper().getEnderChest(); } } diff --git a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/players/PaperOnlinePlayer.java b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/players/PaperOnlinePlayer.java index c69fb86..54e3700 100644 --- a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/players/PaperOnlinePlayer.java +++ b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/players/PaperOnlinePlayer.java @@ -5,7 +5,6 @@ import com.destroystokyo.paper.ClientOption.ChatVisibility; import com.destroystokyo.paper.SkinParts; import fr.pandacube.lib.paper.players.PlayerNonPersistentConfig.Expiration; import fr.pandacube.lib.paper.reflect.wrapper.craftbukkit.CraftPlayer; -import fr.pandacube.lib.paper.reflect.wrapper.minecraft.nbt.CompoundTag; import fr.pandacube.lib.players.standalone.AbstractOnlinePlayer; import fr.pandacube.lib.reflect.wrapper.ReflectWrapper; import net.kyori.adventure.audience.MessageType; @@ -20,8 +19,9 @@ import org.bukkit.Location; import org.bukkit.OfflinePlayer; import org.bukkit.Sound; import org.bukkit.entity.LivingEntity; -import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.Inventory; import org.bukkit.inventory.MainHand; +import org.bukkit.inventory.PlayerInventory; import java.util.Locale; import java.util.UUID; @@ -324,20 +324,13 @@ public interface PaperOnlinePlayer extends PaperOffPlayer, AbstractOnlinePlayer */ @Override - default CompoundTag getPlayerData() { - CompoundTag tag = new CompoundTag(); - getWrappedCraftPlayer().getHandle().serializeEntity(tag); - return tag; + default PlayerInventory getInventory() { + return getBukkitPlayer().getInventory(); } @Override - default ItemStack[] getInventoryContent() { - return getBukkitPlayer().getInventory().getContents(); - } - - @Override - default ItemStack[] getEnderchestContent() { - return getBukkitPlayer().getEnderChest().getContents(); + default Inventory getEnderChest() { + return getBukkitPlayer().getEnderChest(); } diff --git a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/reflect/wrapper/craftbukkit/CraftItemStack.java b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/reflect/wrapper/craftbukkit/CraftItemStack.java index 60b04b7..9496688 100644 --- a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/reflect/wrapper/craftbukkit/CraftItemStack.java +++ b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/reflect/wrapper/craftbukkit/CraftItemStack.java @@ -17,6 +17,7 @@ import static fr.pandacube.lib.util.ThrowableUtil.wrapReflectEx; public class CraftItemStack extends ReflectWrapperTyped { public static final ReflectClass REFLECT = wrapEx(() -> OBCReflect.ofClass("inventory.CraftItemStack")); public static final ReflectMethod asCraftMirror = wrapEx(() -> REFLECT.method("asCraftMirror", fr.pandacube.lib.paper.reflect.wrapper.minecraft.world.ItemStack.MAPPING.runtimeClass())); + public static final ReflectMethod asNMSCopy = wrapEx(() -> REFLECT.method("asNMSCopy", ItemStack.class)); public static ItemStack asCraftMirror(fr.pandacube.lib.paper.reflect.wrapper.minecraft.world.ItemStack original) { return (ItemStack) wrapReflectEx(() -> asCraftMirror.invokeStatic(unwrap(original))); @@ -28,6 +29,11 @@ public class CraftItemStack extends ReflectWrapperTyped { } + public static fr.pandacube.lib.paper.reflect.wrapper.minecraft.world.ItemStack asNMSCopy(ItemStack original) { + return wrap(wrapReflectEx(() -> asNMSCopy.invokeStatic(original)), fr.pandacube.lib.paper.reflect.wrapper.minecraft.world.ItemStack.class); + } + + protected CraftItemStack(Object obj) { diff --git a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/reflect/wrapper/minecraft/nbt/CollectionTag.java b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/reflect/wrapper/minecraft/nbt/CollectionTag.java index 8c5d307..6c27f7e 100644 --- a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/reflect/wrapper/minecraft/nbt/CollectionTag.java +++ b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/reflect/wrapper/minecraft/nbt/CollectionTag.java @@ -2,7 +2,6 @@ package fr.pandacube.lib.paper.reflect.wrapper.minecraft.nbt; import fr.pandacube.lib.paper.reflect.NMSReflect; import fr.pandacube.lib.paper.reflect.NMSReflect.ClassMapping; -import fr.pandacube.lib.reflect.wrapper.ReflectWrapper; import fr.pandacube.lib.reflect.wrapper.ReflectWrapperTyped; import java.util.AbstractList; @@ -17,6 +16,22 @@ public class CollectionTag extends ReflectWrapperTyped> implemen return __getRuntimeInstance().size(); } + public Tag get(int i) { + return wrap(__getRuntimeInstance().get(i), Tag.class); + } + + public Tag set(int i, Tag t) { + return wrap(((AbstractList)__getRuntimeInstance()).set(i, unwrap(t)), Tag.class); + } + + public void add(int i, Tag t) { + ((AbstractList)__getRuntimeInstance()).add(i, unwrap(t)); + } + + public Tag remove(int i) { + return wrap(__getRuntimeInstance().remove(i), Tag.class); + } + protected CollectionTag(Object nms) { super(nms); } diff --git a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/reflect/wrapper/minecraft/nbt/ListTag.java b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/reflect/wrapper/minecraft/nbt/ListTag.java index 876a618..df67d0d 100644 --- a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/reflect/wrapper/minecraft/nbt/ListTag.java +++ b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/reflect/wrapper/minecraft/nbt/ListTag.java @@ -2,6 +2,7 @@ package fr.pandacube.lib.paper.reflect.wrapper.minecraft.nbt; import fr.pandacube.lib.paper.reflect.NMSReflect; import fr.pandacube.lib.paper.reflect.NMSReflect.ClassMapping; +import fr.pandacube.lib.reflect.ReflectConstructor; import fr.pandacube.lib.reflect.ReflectMethod; import fr.pandacube.lib.reflect.wrapper.ReflectWrapper; @@ -10,12 +11,17 @@ import static fr.pandacube.lib.util.ThrowableUtil.wrapReflectEx; public class ListTag extends CollectionTag { public static final ClassMapping MAPPING = wrapEx(() -> NMSReflect.mojClass("net.minecraft.nbt.ListTag")); + public static final ReflectConstructor CONSTRUCTOR = wrapEx(() -> MAPPING.runtimeReflect().constructor()); private static final ReflectMethod getCompound = wrapEx(() -> MAPPING.mojMethod("getCompound", int.class)); public CompoundTag getCompound(int index) { return wrap(wrapReflectEx(() -> getCompound.invoke(__getRuntimeInstance(), index)), CompoundTag.class); } + public ListTag() { + this(wrapReflectEx(() -> CONSTRUCTOR.instanciate())); + } + protected ListTag(Object nms) { super(nms); } diff --git a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/reflect/wrapper/minecraft/world/ItemStack.java b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/reflect/wrapper/minecraft/world/ItemStack.java index 8354f01..b95279b 100644 --- a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/reflect/wrapper/minecraft/world/ItemStack.java +++ b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/reflect/wrapper/minecraft/world/ItemStack.java @@ -2,7 +2,6 @@ package fr.pandacube.lib.paper.reflect.wrapper.minecraft.world; import fr.pandacube.lib.paper.reflect.NMSReflect; import fr.pandacube.lib.paper.reflect.wrapper.minecraft.nbt.CompoundTag; -import fr.pandacube.lib.reflect.ReflectConstructor; import fr.pandacube.lib.reflect.ReflectMethod; import fr.pandacube.lib.reflect.wrapper.ReflectWrapper; @@ -12,12 +11,19 @@ import static fr.pandacube.lib.util.ThrowableUtil.wrapReflectEx; public class ItemStack extends ReflectWrapper { public static final NMSReflect.ClassMapping MAPPING = wrapEx(() -> NMSReflect.mojClass("net.minecraft.world.item.ItemStack")); private static final ReflectMethod of = wrapEx(() -> MAPPING.mojMethod("of", CompoundTag.MAPPING)); + private static final ReflectMethod save = wrapEx(() -> MAPPING.mojMethod("save", CompoundTag.MAPPING)); public static ItemStack of(CompoundTag nbt) { return wrap(wrapReflectEx(() -> of.invokeStatic(unwrap(nbt))), ItemStack.class); } + protected ItemStack(Object obj) { super(obj); } + + + public CompoundTag save(CompoundTag nbt) { + return wrap(wrapReflectEx(() -> save.invoke(__getRuntimeInstance(), unwrap(nbt))), CompoundTag.class); + } } diff --git a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/InventoryWrapper.java b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/InventoryWrapper.java new file mode 100644 index 0000000..e7085c4 --- /dev/null +++ b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/InventoryWrapper.java @@ -0,0 +1,199 @@ +package fr.pandacube.lib.paper.util; + +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryHolder; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.HashMap; +import java.util.List; +import java.util.ListIterator; + +public class InventoryWrapper implements Inventory { + private final Inventory base; + + public InventoryWrapper(Inventory base) { + this.base = base; + } + + + @Override + public int getSize() { + return base.getSize(); + } + + @Override + public int getMaxStackSize() { + return base.getMaxStackSize(); + } + + @Override + public void setMaxStackSize(int size) { + base.setMaxStackSize(size); + } + + @Override + public @Nullable ItemStack getItem(int index) { + return base.getItem(index); + } + + @Override + public void setItem(int index, @Nullable ItemStack item) { + base.setItem(index, item); + } + + @Override + public @NotNull HashMap addItem(@NotNull ItemStack... items) throws IllegalArgumentException { + return base.addItem(items); + } + + @Override + public @NotNull HashMap removeItem(@NotNull ItemStack... items) throws IllegalArgumentException { + return base.removeItem(items); + } + + @Override + public @NotNull HashMap removeItemAnySlot(@NotNull ItemStack... items) throws IllegalArgumentException { + return base.removeItemAnySlot(items); + } + + @Override + public @Nullable ItemStack @NotNull [] getContents() { + return base.getContents(); + } + + @Override + public void setContents(@Nullable ItemStack @NotNull [] items) throws IllegalArgumentException { + base.setContents(items); + } + + @Override + public @Nullable ItemStack @NotNull [] getStorageContents() { + return base.getStorageContents(); + } + + @Override + public void setStorageContents(@Nullable ItemStack @NotNull [] items) throws IllegalArgumentException { + base.setStorageContents(items); + } + + @Override + public boolean contains(@NotNull Material material) throws IllegalArgumentException { + return base.contains(material); + } + + @Override + public boolean contains(@Nullable ItemStack item) { + return base.contains(item); + } + + @Override + public boolean contains(@NotNull Material material, int amount) throws IllegalArgumentException { + return base.contains(material, amount); + } + + @Override + public boolean contains(@Nullable ItemStack item, int amount) { + return base.contains(item, amount); + } + + @Override + public boolean containsAtLeast(@Nullable ItemStack item, int amount) { + return base.containsAtLeast(item, amount); + } + + @Override + public @NotNull HashMap all(@NotNull Material material) throws IllegalArgumentException { + return base.all(material); + } + + @Override + public @NotNull HashMap all(@Nullable ItemStack item) { + return base.all(item); + } + + @Override + public int first(@NotNull Material material) throws IllegalArgumentException { + return base.first(material); + } + + @Override + public int first(@NotNull ItemStack item) { + return base.first(item); + } + + @Override + public int firstEmpty() { + return base.firstEmpty(); + } + + @Override + public boolean isEmpty() { + return base.isEmpty(); + } + + @Override + public void remove(@NotNull Material material) throws IllegalArgumentException { + base.remove(material); + } + + @Override + public void remove(@NotNull ItemStack item) { + base.remove(item); + } + + @Override + public void clear(int index) { + base.clear(index); + } + + @Override + public void clear() { + base.clear(); + } + + @Override + public int close() { + return base.close(); + } + + @Override + public @NotNull List getViewers() { + return base.getViewers(); + } + + @Override + public @NotNull InventoryType getType() { + return base.getType(); + } + + @Override + public @Nullable InventoryHolder getHolder() { + return base.getHolder(); + } + + @Override + public @Nullable InventoryHolder getHolder(boolean useSnapshot) { + return base.getHolder(useSnapshot); + } + + @Override + public @NotNull ListIterator iterator() { + return base.iterator(); + } + + @Override + public @NotNull ListIterator iterator(int index) { + return base.iterator(index); + } + + @Override + public @Nullable Location getLocation() { + return base.getLocation(); + } +} diff --git a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/PlayerDataWrapper.java b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/PlayerDataWrapper.java new file mode 100644 index 0000000..6ac9d5a --- /dev/null +++ b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/PlayerDataWrapper.java @@ -0,0 +1,350 @@ +package fr.pandacube.lib.paper.util; + +import com.google.common.base.Preconditions; +import fr.pandacube.lib.paper.reflect.wrapper.craftbukkit.CraftItemStack; +import fr.pandacube.lib.paper.reflect.wrapper.minecraft.nbt.CompoundTag; +import fr.pandacube.lib.paper.reflect.wrapper.minecraft.nbt.ListTag; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.PlayerInventory; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Arrays; +import java.util.Map; +import java.util.Objects; +import java.util.TreeMap; +import java.util.function.IntUnaryOperator; + +/** + * A wrapper to easily manipulate vanilla player data. + */ +public class PlayerDataWrapper { + + public final CompoundTag data; + + public PlayerDataWrapper(CompoundTag data) { + this.data = data == null ? new CompoundTag() : data; + } + + /** + * Gets a snapshot of the inventory of this player. + * If modified, call the {@link #setInventory(PlayerInventory)} to update the data. + * @return the player inventory + */ + public PlayerInventory getInventory() { + return new DummyPlayerInventory( + getBukkitInventory("Inventory", InventoryType.PLAYER, this::fromNBTtoBukkitInventorySlot), + getHeldItemSlot()); + } + + private int fromNBTtoBukkitInventorySlot(int nbtSlot) { + // cat nbEl NBTslot bukkitSlot NBT->Bukkit + // items 36el 0-35 == + // armor 4el start 100 36-39 -100 + 36 + // offhnd 1el start 150 40 -150 + 40 + if (nbtSlot >= 0 && nbtSlot < 36) { // regular inventory slots + return nbtSlot; + } + if (nbtSlot >= 100 && nbtSlot < 104) { // armor slots + return nbtSlot - 100 + 36; + } + if (nbtSlot == 150) { // second hand + return 40; + } + throw new IllegalArgumentException("Unrecognized NBT player inventory slot " + nbtSlot); + } + + public void setInventory(PlayerInventory inv) { + setBukkitInventory("Inventory", inv, this::fromBukkitToNBTInventorySlot); + setHeldItemSlot(inv.getHeldItemSlot()); + } + + private int fromBukkitToNBTInventorySlot(int bukkitSlot) { + if (bukkitSlot >= 0 && bukkitSlot < 36) { // regular inventory slots + return bukkitSlot; + } + if (bukkitSlot >= 36 && bukkitSlot < 40) { // armor slots + return bukkitSlot + 100 - 36; + } + if (bukkitSlot == 40) { // second hand + return 150; + } + throw new IllegalArgumentException("Unrecognized Bukkit player inventory slot " + bukkitSlot); + } + + + + + public Inventory getEnderChest() { + return getBukkitInventory("EnderItems", InventoryType.ENDER_CHEST, IntUnaryOperator.identity()); + } + + public void setEnderChest(Inventory inv) { + setBukkitInventory("EnderItems", inv, IntUnaryOperator.identity()); + } + + + + + + private int getHeldItemSlot() { + if (!data.contains("SelectedItemSlot")) + return 0; + return data.getInt("SelectedItemSlot"); + } + + private void setHeldItemSlot(int slot) { + data.putInt("SelectedItemSlot", slot); + } + + + + private Inventory getBukkitInventory(String nbtKey, InventoryType bukkitType, IntUnaryOperator nbtToBukkitSlotConverter) { + Map stacks = getRawInvontoryContent(nbtKey); + Inventory inv = Bukkit.createInventory(null, bukkitType); + if (stacks.isEmpty()) + return inv; + for (Map.Entry is : stacks.entrySet()) { + inv.setItem(nbtToBukkitSlotConverter.applyAsInt(is.getKey()), is.getValue()); + } + return inv; + } + + private Map getRawInvontoryContent(String key) { + if (!data.contains(key, 9)) // type 9 is list + return Map.of(); + ListTag list = data.getList(key, 10); // type of list element 10 is CompoundTag + if (list == null) + return Map.of(); + + Map stacks = new TreeMap<>(); + for (int i = 0; i < list.size(); i++) { + CompoundTag itemTag = list.getCompound(i); + int nbtSlot = itemTag.getByte("Slot") & 255; + ItemStack is = CraftItemStack.asCraftMirror(itemTag); + if (is != null && !is.getType().isAir()) { + stacks.put(nbtSlot, CraftItemStack.asCraftMirror(itemTag)); + } + } + return stacks; + } + + + + + + private void setBukkitInventory(String nbtKey, Inventory inv, IntUnaryOperator bukkitToNBTSlotconverter) { + Map stacks = new TreeMap<>(); + if (inv == null) { + setRawInventoryContent(nbtKey, stacks); + } + for (int bukkitSlot = 0; bukkitSlot < inv.getSize(); bukkitSlot++) { + ItemStack is = inv.getItem(bukkitSlot); + int nbtSlot = bukkitToNBTSlotconverter.applyAsInt(bukkitSlot); + stacks.put(nbtSlot, is); + } + setRawInventoryContent(nbtKey, stacks); + } + + private void setRawInventoryContent(String key, Map stacks) { + ListTag list = new ListTag(); + for (Map.Entry is : stacks.entrySet()) { + CompoundTag itemTag = new CompoundTag(); + CraftItemStack.asNMSCopy(is.getValue()).save(itemTag); + itemTag.putByte("Slot", is.getKey().byteValue()); + list.add(list.size(), itemTag); + } + data.put(key, list); + } + + + + + + + + private static class DummyPlayerInventory extends InventoryWrapper implements PlayerInventory { + + private int heldItemSlot; + + public DummyPlayerInventory(Inventory base, int heldItemSlot) { + super(base); + this.heldItemSlot = heldItemSlot; + } + + @Override + public @Nullable ItemStack @NotNull [] getStorageContents() { + return Arrays.copyOfRange(getContents(), 0, 36); + } + + @Override + public @Nullable ItemStack @NotNull [] getArmorContents() { + return Arrays.copyOfRange(getContents(), 36, 40); + } + + @Override + public void setArmorContents(@Nullable ItemStack[] items) { + this.setSlots(items, 36, 4); + } + + @Override + public @Nullable ItemStack @NotNull [] getExtraContents() { + return Arrays.copyOfRange(getContents(), 40, getSize()); + } + + @Override + public void setExtraContents(@Nullable ItemStack[] items) { + this.setSlots(items, 40, getSize() - 40); + } + + private void setSlots(ItemStack[] items, int baseSlot, int length) { + if (items == null) { + items = new ItemStack[length]; + } + Preconditions.checkArgument(items.length <= length, "items.length must be < %s", length); + + for (int i = 0; i < length; i++) { + if (i >= items.length) { + this.setItem(baseSlot + i, null); + } else { + this.setItem(baseSlot + i, items[i]); + } + } + } + + @Override + public ItemStack getHelmet() { + return getItem(39); + } + + @Override + public void setHelmet(@Nullable ItemStack helmet) { + setItem(39, helmet); + } + + @Override + public ItemStack getChestplate() { + return getItem(38); + } + + @Override + public void setChestplate(@Nullable ItemStack chestplate) { + setItem(38, chestplate); + } + + @Override + public ItemStack getLeggings() { + return getItem(37); + } + + @Override + public void setLeggings(@Nullable ItemStack leggings) { + setItem(37, leggings); + } + + @Override + public ItemStack getBoots() { + return getItem(36); + } + + @Override + public void setBoots(@Nullable ItemStack boots) { + setItem(36, boots); + } + + @Override + public void setItem(EquipmentSlot slot, ItemStack item) { + Preconditions.checkArgument(slot != null, "slot must not be null"); + + switch (slot) { + case HAND: + this.setItemInMainHand(item); + break; + case OFF_HAND: + this.setItemInOffHand(item); + break; + case FEET: + this.setBoots(item); + break; + case LEGS: + this.setLeggings(item); + break; + case CHEST: + this.setChestplate(item); + break; + case HEAD: + this.setHelmet(item); + break; + default: + throw new IllegalArgumentException("Not implemented. This is a bug"); + } + } + + @Override + public ItemStack getItem(EquipmentSlot slot) { + Preconditions.checkArgument(slot != null, "slot must not be null"); + + return switch (slot) { + case HAND -> this.getItemInMainHand(); + case OFF_HAND -> this.getItemInOffHand(); + case FEET -> Objects.requireNonNullElseGet(this.getBoots(), () -> new ItemStack(Material.AIR)); + case LEGS -> Objects.requireNonNullElseGet(this.getLeggings(), () -> new ItemStack(Material.AIR)); + case CHEST -> Objects.requireNonNullElseGet(this.getChestplate(), () -> new ItemStack(Material.AIR)); + case HEAD -> Objects.requireNonNullElseGet(this.getHelmet(), () -> new ItemStack(Material.AIR)); + }; + } + + @Override + public @NotNull ItemStack getItemInMainHand() { + return getItem(heldItemSlot); + } + + @Override + public void setItemInMainHand(@Nullable ItemStack item) { + setItem(heldItemSlot, item); + } + + @Override + public @NotNull ItemStack getItemInOffHand() { + return getItem(40); + } + + @Override + public void setItemInOffHand(@Nullable ItemStack item) { + setItem(40, item); + } + + @Override + public @NotNull ItemStack getItemInHand() { + return getItemInMainHand(); + } + + @Override + public void setItemInHand(@Nullable ItemStack stack) { + setItemInMainHand(stack); + } + + @Override + public int getHeldItemSlot() { + return heldItemSlot; + } + + @Override + public void setHeldItemSlot(int slot) { + if (slot < 0 || slot > 8) + throw new IllegalArgumentException("Slot is not between 0 and 8 inclusive"); + heldItemSlot = slot; + } + + @Override + public @Nullable HumanEntity getHolder() { + return null; + } + } +}