From bf8552f0f074e6e3a5151c0c1407041d64399dee Mon Sep 17 00:00:00 2001 From: Jake Potrebic Date: Mon, 17 Jun 2024 12:12:42 -0700 Subject: [PATCH] Delegate ItemStack (#10852) --- patches/api/0473-General-ItemMeta-fixes.patch | 52 ++ ...78-Proxy-ItemStack-to-CraftItemStack.patch | 472 ++++++++++++++++++ ...w-accessible-directly-from-ItemStack.patch | 434 ++++++++++++++++ .../0537-ItemStack-repair-check-API.patch | 11 +- .../server/0970-General-ItemMeta-fixes.patch | 94 +++- ...977-Fix-equipment-slot-and-group-API.patch | 2 +- ...24-Proxy-ItemStack-to-CraftItemStack.patch | 300 +++++++++++ ...w-accessible-directly-from-ItemStack.patch | 266 ++++++++++ 8 files changed, 1611 insertions(+), 20 deletions(-) create mode 100644 patches/api/0478-Proxy-ItemStack-to-CraftItemStack.patch create mode 100644 patches/api/0479-Make-a-PDC-view-accessible-directly-from-ItemStack.patch create mode 100644 patches/server/1024-Proxy-ItemStack-to-CraftItemStack.patch create mode 100644 patches/server/1025-Make-a-PDC-view-accessible-directly-from-ItemStack.patch diff --git a/patches/api/0473-General-ItemMeta-fixes.patch b/patches/api/0473-General-ItemMeta-fixes.patch index 22f1db839c..a8eb5a99e1 100644 --- a/patches/api/0473-General-ItemMeta-fixes.patch +++ b/patches/api/0473-General-ItemMeta-fixes.patch @@ -19,3 +19,55 @@ index 637fa73d4366c2d88e2716e5c8d3465706d788a7..0f03c546ecdd6383fb36a362d18d6fb5 this.flicker = flicker; this.trail = trail; this.colors = colors; +diff --git a/src/main/java/org/bukkit/inventory/meta/Damageable.java b/src/main/java/org/bukkit/inventory/meta/Damageable.java +index ff6818b6d9e0207eafdd749928f33aeac3f27191..992f39da07bafe9769effaa7dc6adc018c89329d 100644 +--- a/src/main/java/org/bukkit/inventory/meta/Damageable.java ++++ b/src/main/java/org/bukkit/inventory/meta/Damageable.java +@@ -9,14 +9,17 @@ import org.jetbrains.annotations.Nullable; + public interface Damageable extends ItemMeta { + + /** +- * Checks to see if this item has damage ++ * Checks to see if this item has damage greater than 0. + * +- * @return true if this has damage ++ * @return true if this has damage > 0 + */ + boolean hasDamage(); + + /** + * Gets the damage ++ *

++ * Call {@link #hasDamageValue()} to be sure ++ * a damage value is set. + * + * @return the damage + */ +@@ -26,9 +29,27 @@ public interface Damageable extends ItemMeta { + * Sets the damage + * + * @param damage item damage ++ * @see #resetDamage() to reset and clear the damage data component + */ + void setDamage(int damage); + ++ // Paper start ++ /** ++ * Checks if any damage value, including 0, ++ * is set on this meta. ++ * ++ * @return true if any value is set ++ */ ++ boolean hasDamageValue(); ++ ++ /** ++ * Clears the damage component from the meta. Differs ++ * from {@code setDamage(0)} in that it removes the component ++ * instead of adding the component with a value of 0. ++ */ ++ void resetDamage(); ++ // Paper end ++ + /** + * Checks to see if this item has a maximum amount of damage. + * diff --git a/patches/api/0478-Proxy-ItemStack-to-CraftItemStack.patch b/patches/api/0478-Proxy-ItemStack-to-CraftItemStack.patch new file mode 100644 index 0000000000..cb34e5b4af --- /dev/null +++ b/patches/api/0478-Proxy-ItemStack-to-CraftItemStack.patch @@ -0,0 +1,472 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Tue, 14 May 2024 11:57:51 -0700 +Subject: [PATCH] Proxy ItemStack to CraftItemStack + + +diff --git a/src/main/java/org/bukkit/UnsafeValues.java b/src/main/java/org/bukkit/UnsafeValues.java +index 7332034bb1753f48f7904dafab1ef4b3ee117ea3..11bccf6c27b8b2a2ed558a1375a3b3adc7952076 100644 +--- a/src/main/java/org/bukkit/UnsafeValues.java ++++ b/src/main/java/org/bukkit/UnsafeValues.java +@@ -277,4 +277,6 @@ public interface UnsafeValues { + @NotNull java.util.List computeTooltipLines(@NotNull ItemStack itemStack, @NotNull io.papermc.paper.inventory.tooltip.TooltipContext tooltipContext, @Nullable org.bukkit.entity.Player player); // Paper - expose itemstack tooltip lines + + io.papermc.paper.registry.tag.@Nullable Tag getTag(io.papermc.paper.registry.tag.@NotNull TagKey tagKey); // Paper - hack to get tags for non-server backed registries ++ ++ ItemStack createEmptyStack(); // Paper - proxy ItemStack + } +diff --git a/src/main/java/org/bukkit/inventory/ItemStack.java b/src/main/java/org/bukkit/inventory/ItemStack.java +index 7f5633e7689b82b937d5b985c3e6ae15dc94a20f..da91938ff941bb5903dcbd430c68c5980c01b57b 100644 +--- a/src/main/java/org/bukkit/inventory/ItemStack.java ++++ b/src/main/java/org/bukkit/inventory/ItemStack.java +@@ -28,10 +28,38 @@ import org.jetbrains.annotations.Nullable; + * returns false. + */ + public class ItemStack implements Cloneable, ConfigurationSerializable, Translatable, net.kyori.adventure.text.event.HoverEventSource, net.kyori.adventure.translation.Translatable { // Paper +- private Material type = Material.AIR; +- private int amount = 0; ++ private ItemStack craftDelegate; // Paper - always delegate to server-backed stack + private MaterialData data = null; +- private ItemMeta meta; ++ ++ // Paper start - add static factory methods ++ /** ++ * Creates an itemstack with the specified item type and a count of 1. ++ * ++ * @param type the item type to use ++ * @return a new itemstack ++ * @throws IllegalArgumentException if the Material provided is not an item ({@link Material#isItem()}) ++ */ ++ @org.jetbrains.annotations.Contract(value = "_ -> new", pure = true) ++ public static @NotNull ItemStack of(final @NotNull Material type) { ++ return of(type, 1); ++ } ++ ++ /** ++ * Creates an itemstack with the specified item type and count. ++ * ++ * @param type the item type to use ++ * @param amount the count of items in the stack ++ * @return a new itemstack ++ * @throws IllegalArgumentException if the Material provided is not an item ({@link Material#isItem()}) ++ * @throws IllegalArgumentException if the amount is less than 1 ++ */ ++ @org.jetbrains.annotations.Contract(value = "_, _ -> new", pure = true) ++ public static @NotNull ItemStack of(final @NotNull Material type, final int amount) { ++ Preconditions.checkArgument(type.asItemType() != null, type + " isn't an item"); ++ Preconditions.checkArgument(amount > 0, "amount must be greater than 0"); ++ return java.util.Objects.requireNonNull(type.asItemType(), type + " is not an item").createItemStack(amount); // Paper - delegate ++ } ++ // Paper end + + @Utility + protected ItemStack() {} +@@ -44,7 +72,10 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat + * {@link Material#isItem()} returns false. + * + * @param type item material ++ * @apiNote use {@link #of(Material)} ++ * @see #of(Material) + */ ++ @org.jetbrains.annotations.ApiStatus.Obsolete(since = "1.21") // Paper + public ItemStack(@NotNull final Material type) { + this(type, 1); + } +@@ -58,7 +89,10 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat + * + * @param type item material + * @param amount stack size ++ * @apiNote Use {@link #of(Material, int)} ++ * @see #of(Material, int) + */ ++ @org.jetbrains.annotations.ApiStatus.Obsolete(since = "1.21") // Paper + public ItemStack(@NotNull final Material type, final int amount) { + this(type, amount, (short) 0); + } +@@ -93,8 +127,7 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat + type = Bukkit.getUnsafe().fromLegacy(new MaterialData(type, data == null ? (byte) damage : data), true); + } + } +- this.type = type; +- this.amount = amount; ++ this.craftDelegate = ItemStack.of(type, amount); // Paper - create delegate + if (damage != 0) { + setDurability(damage); + } +@@ -109,17 +142,16 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat + * @param stack the stack to copy + * @throws IllegalArgumentException if the specified stack is null or + * returns an item meta not created by the item factory ++ * @apiNote Use {@link #clone()} ++ * @see #clone() + */ ++ @org.jetbrains.annotations.ApiStatus.Obsolete(since = "1.21") // Paper + public ItemStack(@NotNull final ItemStack stack) throws IllegalArgumentException { + Preconditions.checkArgument(stack != null, "Cannot copy null stack"); +- this.type = stack.getType(); +- this.amount = stack.getAmount(); +- if (this.type.isLegacy()) { ++ this.craftDelegate = stack.clone(); // Paper - delegate ++ if (stack.getType().isLegacy()) { + this.data = stack.getData(); + } +- if (stack.hasItemMeta()) { +- setItemMeta0(stack.getItemMeta(), type); +- } + } + + /** +@@ -127,10 +159,9 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat + * + * @return Type of the items in this stack + */ +- @Utility + @NotNull + public Material getType() { +- return type; ++ return this.craftDelegate.getType(); // Paper - delegate + } + + /** +@@ -153,19 +184,10 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat + * Using this method in ItemStacks passed in events will result in undefined behavior. + * @see ItemStack#withType(Material) + */ +- @Utility + @Deprecated // Paper + public void setType(@NotNull Material type) { + Preconditions.checkArgument(type != null, "Material cannot be null"); +- this.type = type; +- if (this.meta != null) { +- this.meta = Bukkit.getItemFactory().asMetaFor(meta, type); +- } +- if (type.isLegacy()) { +- createData((byte) 0); +- } else { +- this.data = null; +- } ++ this.craftDelegate.setType(type); // Paper - delegate + } + // Paper start + /** +@@ -177,12 +199,7 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat + @NotNull + @org.jetbrains.annotations.Contract(value = "_ -> new", pure = true) + public ItemStack withType(@NotNull Material type) { +- ItemStack itemStack = new ItemStack(type, this.amount); +- if (this.hasItemMeta()) { +- itemStack.setItemMeta(this.getItemMeta()); +- } +- +- return itemStack; ++ return this.craftDelegate.withType(type); // Paper - delegate + } + // Paper end + +@@ -192,7 +209,7 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat + * @return Amount of items in this stack + */ + public int getAmount() { +- return amount; ++ return this.craftDelegate.getAmount(); // Paper - delegate + } + + /** +@@ -201,7 +218,7 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat + * @param amount New amount of items in this stack + */ + public void setAmount(int amount) { +- this.amount = amount; ++ this.craftDelegate.setAmount(amount); // Paper - delegate + } + + /** +@@ -254,11 +271,7 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat + */ + @Deprecated + public void setDurability(final short durability) { +- ItemMeta meta = getItemMeta(); +- if (meta != null) { +- ((Damageable) meta).setDamage(durability); +- setItemMeta(meta); +- } ++ this.craftDelegate.setDurability(durability); // Paper - delegate + } + + /** +@@ -269,8 +282,7 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat + */ + @Deprecated + public short getDurability() { +- ItemMeta meta = getItemMeta(); +- return (meta == null) ? 0 : (short) ((Damageable) meta).getDamage(); ++ return this.craftDelegate.getDurability(); // Paper - delegate + } + + /** +@@ -282,17 +294,12 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat + * + * @return The maximum you can stack this item to. + */ +- @Utility + public int getMaxStackSize() { +- if (meta != null && meta.hasMaxStackSize()) { +- return meta.getMaxStackSize(); +- } +- +- return getType().getMaxStackSize(); ++ return this.craftDelegate.getMaxStackSize(); // Paper - delegate + } + + private void createData(final byte data) { +- this.data = type.getNewData(data); ++ this.data = this.craftDelegate.getType().getNewData(data); // Paper + } + + @Override +@@ -306,17 +313,8 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat + } + + @Override +- @Utility + public boolean equals(Object obj) { +- if (this == obj) { +- return true; +- } +- if (!(obj instanceof ItemStack)) { +- return false; +- } +- +- ItemStack stack = (ItemStack) obj; +- return getAmount() == stack.getAmount() && isSimilar(stack); ++ return this.craftDelegate.equals(obj); // Paper - delegate + } + + /** +@@ -326,49 +324,19 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat + * @param stack the item stack to compare to + * @return true if the two stacks are equal, ignoring the amount + */ +- @Utility + public boolean isSimilar(@Nullable ItemStack stack) { +- if (stack == null) { +- return false; +- } +- if (stack == this) { +- return true; +- } +- Material comparisonType = (this.type.isLegacy()) ? Bukkit.getUnsafe().fromLegacy(this.getData(), true) : this.type; // This may be called from legacy item stacks, try to get the right material +- return comparisonType == stack.getType() && /* getDurability() == stack.getDurability() && */hasItemMeta() == stack.hasItemMeta() && (hasItemMeta() ? Bukkit.getItemFactory().equals(getItemMeta(), stack.getItemMeta()) : true); // Paper - remove redundant item durability check ++ return this.craftDelegate.isSimilar(stack); // Paper - delegate + } + + @NotNull + @Override + public ItemStack clone() { +- try { +- ItemStack itemStack = (ItemStack) super.clone(); +- +- if (this.meta != null) { +- itemStack.meta = this.meta.clone(); +- } +- +- if (this.data != null) { +- itemStack.data = this.data.clone(); +- } +- +- return itemStack; +- } catch (CloneNotSupportedException e) { +- throw new Error(e); +- } ++ return this.craftDelegate.clone(); // Paper - delegate + } + + @Override +- @Utility + public int hashCode() { +- int hash = 1; +- +- hash = hash * 31 + getType().hashCode(); +- hash = hash * 31 + getAmount(); +- hash = hash * 31 + (getDurability() & 0xffff); +- hash = hash * 31 + (hasItemMeta() ? (meta == null ? getItemMeta().hashCode() : meta.hashCode()) : 0); +- +- return hash; ++ return this.craftDelegate.hashCode(); // Paper - delegate + } + + /** +@@ -378,7 +346,7 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat + * @return True if this has the given enchantment + */ + public boolean containsEnchantment(@NotNull Enchantment ench) { +- return meta == null ? false : meta.hasEnchant(ench); ++ return this.craftDelegate.containsEnchantment(ench); // Paper - delegate + } + + /** +@@ -388,7 +356,7 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat + * @return Level of the enchantment, or 0 + */ + public int getEnchantmentLevel(@NotNull Enchantment ench) { +- return meta == null ? 0 : meta.getEnchantLevel(ench); ++ return this.craftDelegate.getEnchantmentLevel(ench); // Paper - delegate + } + + /** +@@ -398,7 +366,7 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat + */ + @NotNull + public Map getEnchantments() { +- return meta == null ? ImmutableMap.of() : meta.getEnchants(); ++ return this.craftDelegate.getEnchantments(); // Paper - delegate + } + + /** +@@ -474,10 +442,7 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat + * @param level Level of the enchantment + */ + public void addUnsafeEnchantment(@NotNull Enchantment ench, int level) { +- ItemMeta itemMeta = (meta == null ? meta = Bukkit.getItemFactory().getItemMeta(type) : meta); +- if (itemMeta != null) { +- itemMeta.addEnchant(ench, level, true); +- } ++ this.craftDelegate.addUnsafeEnchantment(ench, level); // Paper - delegate + } + + /** +@@ -488,23 +453,14 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat + * @return Previous level, or 0 + */ + public int removeEnchantment(@NotNull Enchantment ench) { +- int level = getEnchantmentLevel(ench); +- if (level == 0 || meta == null) { +- return level; +- } +- meta.removeEnchant(ench); +- return level; ++ return this.craftDelegate.removeEnchantment(ench); // Paper - delegate + } + + /** + * Removes all enchantments on this ItemStack. + */ + public void removeEnchantments() { +- if (meta == null) { +- return; +- } +- +- meta.removeEnchantments(); ++ this.craftDelegate.removeEnchantments(); // Paper - delegate + } + + @Override +@@ -660,7 +616,7 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat + */ + @UndefinedNullability // Paper + public ItemMeta getItemMeta() { +- return this.meta == null ? Bukkit.getItemFactory().getItemMeta(this.type) : this.meta.clone(); ++ return this.craftDelegate.getItemMeta(); // Paper - delegate + } + + /** +@@ -669,7 +625,7 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat + * @return Returns true if some meta data has been set for this item + */ + public boolean hasItemMeta() { +- return !Bukkit.getItemFactory().equals(meta, null); ++ return this.craftDelegate.hasItemMeta(); // Paper - delegate + } + + /** +@@ -682,28 +638,10 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat + * the {@link ItemFactory} + */ + public boolean setItemMeta(@Nullable ItemMeta itemMeta) { +- return setItemMeta0(itemMeta, type); ++ return this.craftDelegate.setItemMeta(itemMeta); // Paper - delegate + } + +- /* +- * Cannot be overridden, so it's safe for constructor call +- */ +- private boolean setItemMeta0(@Nullable ItemMeta itemMeta, @NotNull Material material) { +- if (itemMeta == null) { +- this.meta = null; +- return true; +- } +- if (!Bukkit.getItemFactory().isApplicable(itemMeta, material)) { +- return false; +- } +- this.meta = Bukkit.getItemFactory().asMetaFor(itemMeta, material); +- +- if (this.meta == itemMeta) { +- this.meta = itemMeta.clone(); +- } +- +- return true; +- } ++ // Paper - delegate + + @Override + @NotNull +@@ -807,11 +745,7 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat + } + + public int getMaxItemUseDuration(@NotNull final org.bukkit.entity.LivingEntity entity) { +- if (type == null || type == Material.AIR || !type.isItem()) { +- return 0; +- } +- // Requires access to NMS +- return ensureServerConversions().getMaxItemUseDuration(entity); ++ return this.craftDelegate.getMaxItemUseDuration(entity); // Paper - delegate + } + + /** +@@ -1061,7 +995,8 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat + */ + @NotNull + public static ItemStack empty() { +- return new ItemStack(); ++ //noinspection deprecation ++ return Bukkit.getUnsafe().createEmptyStack(); // Paper - proxy ItemStack + } + + /** +@@ -1069,7 +1004,7 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat + * it is either air or the stack has a size of 0. + */ + public boolean isEmpty() { +- return type.isAir() || amount <= 0; ++ return this.craftDelegate.isEmpty(); // Paper - delegate + } + // Paper end + // Paper start - expose itemstack tooltip lines +diff --git a/src/test/java/org/bukkit/configuration/ConfigurationSectionTest.java b/src/test/java/org/bukkit/configuration/ConfigurationSectionTest.java +index ee5bc86f47cf5599e4c5c34e3a9084f86d74bdb7..c6b49c7e0a3357566fc859d4f0c76e77fefbd371 100644 +--- a/src/test/java/org/bukkit/configuration/ConfigurationSectionTest.java ++++ b/src/test/java/org/bukkit/configuration/ConfigurationSectionTest.java +@@ -547,6 +547,7 @@ public abstract class ConfigurationSectionTest { + } + + @Test ++ @org.junit.jupiter.api.Disabled("ItemStack can't exist without the Server, test moved to server") + public void testGetItemStack_String() { + ConfigurationSection section = getConfigurationSection(); + String key = "exists"; +@@ -559,6 +560,7 @@ public abstract class ConfigurationSectionTest { + } + + @Test ++ @org.junit.jupiter.api.Disabled("ItemStack can't exist without the Server, test moved to server") + public void testGetItemStack_String_ItemStack() { + ConfigurationSection section = getConfigurationSection(); + String key = "exists"; +@@ -572,6 +574,7 @@ public abstract class ConfigurationSectionTest { + } + + @Test ++ @org.junit.jupiter.api.Disabled("ItemStack can't exist without the Server, test moved to server") + public void testIsItemStack() { + ConfigurationSection section = getConfigurationSection(); + String key = "exists"; diff --git a/patches/api/0479-Make-a-PDC-view-accessible-directly-from-ItemStack.patch b/patches/api/0479-Make-a-PDC-view-accessible-directly-from-ItemStack.patch new file mode 100644 index 0000000000..a37a724014 --- /dev/null +++ b/patches/api/0479-Make-a-PDC-view-accessible-directly-from-ItemStack.patch @@ -0,0 +1,434 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Wed, 12 Jun 2024 10:29:30 -0700 +Subject: [PATCH] Make a PDC view accessible directly from ItemStack + + +diff --git a/src/main/java/io/papermc/paper/persistence/PersistentDataContainerView.java b/src/main/java/io/papermc/paper/persistence/PersistentDataContainerView.java +new file mode 100644 +index 0000000000000000000000000000000000000000..4a4949fde3f03e2ea9d2dde245f7dea119e202bf +--- /dev/null ++++ b/src/main/java/io/papermc/paper/persistence/PersistentDataContainerView.java +@@ -0,0 +1,167 @@ ++package io.papermc.paper.persistence; ++ ++import java.util.Set; ++import org.bukkit.NamespacedKey; ++import org.bukkit.persistence.PersistentDataAdapterContext; ++import org.bukkit.persistence.PersistentDataContainer; ++import org.bukkit.persistence.PersistentDataHolder; ++import org.bukkit.persistence.PersistentDataType; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.checker.nullness.qual.Nullable; ++import org.jetbrains.annotations.ApiStatus; ++ ++/** ++ * This represents a view of a persistent data container. No ++ * methods on this interface mutate the container. ++ * @see PersistentDataContainer ++ */ ++@ApiStatus.NonExtendable ++public interface PersistentDataContainerView { ++ ++ /** ++ * Returns if the persistent metadata provider has metadata registered ++ * matching the provided parameters. ++ *

++ * This method will only return true if the found value has the same primitive ++ * data type as the provided key. ++ *

++ * Storing a value using a custom {@link PersistentDataType} implementation ++ * will not store the complex data type. Therefore storing a UUID (by ++ * storing a byte[]) will match has("key" , ++ * {@link PersistentDataType#BYTE_ARRAY}). Likewise a stored byte[] will ++ * always match your UUID {@link PersistentDataType} even if it is not 16 ++ * bytes long. ++ *

++ * This method is only usable for custom object keys. Overwriting existing ++ * tags, like the display name, will not work as the values are stored ++ * using your namespace. ++ * ++ * @param key the key the value is stored under ++ * @param type the type the primative stored value has to match ++ * @param

the generic type of the stored primitive ++ * @param the generic type of the eventually created complex object ++ * ++ * @return if a value with the provided key and type exists ++ * ++ * @throws IllegalArgumentException if the key to look up is null ++ * @throws IllegalArgumentException if the type to cast the found object to is ++ * null ++ */ ++ boolean has(@NonNull NamespacedKey key, @NonNull PersistentDataType type); ++ ++ /** ++ * Returns if the persistent metadata provider has metadata registered matching ++ * the provided parameters. ++ *

++ * This method will return true as long as a value with the given key exists, ++ * regardless of its type. ++ *

++ * This method is only usable for custom object keys. Overwriting existing tags, ++ * like the display name, will not work as the values are stored using your ++ * namespace. ++ * ++ * @param key the key the value is stored under ++ * ++ * @return if a value with the provided key exists ++ * ++ * @throws IllegalArgumentException if the key to look up is null ++ */ ++ boolean has(@NonNull NamespacedKey key); ++ ++ /** ++ * Returns the metadata value that is stored on the ++ * {@link PersistentDataHolder} instance. ++ * ++ * @param key the key to look up in the custom tag map ++ * @param type the type the value must have and will be casted to ++ * @param

the generic type of the stored primitive ++ * @param the generic type of the eventually created complex object ++ * ++ * @return the value or {@code null} if no value was mapped under the given ++ * value ++ * ++ * @throws IllegalArgumentException if the key to look up is null ++ * @throws IllegalArgumentException if the type to cast the found object to is ++ * null ++ * @throws IllegalArgumentException if a value exists under the given key, ++ * but cannot be accessed using the given type ++ * @throws IllegalArgumentException if no suitable adapter was found for ++ * the {@link ++ * PersistentDataType#getPrimitiveType()} ++ */ ++ @Nullable C get(@NonNull NamespacedKey key, @NonNull PersistentDataType type); ++ ++ /** ++ * Returns the metadata value that is stored on the ++ * {@link PersistentDataHolder} instance. If the value does not exist in the ++ * container, the default value provided is returned. ++ * ++ * @param key the key to look up in the custom tag map ++ * @param type the type the value must have and will be casted to ++ * @param defaultValue the default value to return if no value was found for ++ * the provided key ++ * @param

the generic type of the stored primitive ++ * @param the generic type of the eventually created complex object ++ * ++ * @return the value or the default value if no value was mapped under the ++ * given key ++ * ++ * @throws IllegalArgumentException if the key to look up is null ++ * @throws IllegalArgumentException if the type to cast the found object to is ++ * null ++ * @throws IllegalArgumentException if a value exists under the given key, ++ * but cannot be accessed using the given type ++ * @throws IllegalArgumentException if no suitable adapter was found for ++ * the {@link PersistentDataType#getPrimitiveType()} ++ */ ++ @NonNull C getOrDefault(@NonNull NamespacedKey key, @NonNull PersistentDataType type, @NonNull C defaultValue); ++ ++ /** ++ * Get the set of keys present on this {@link PersistentDataContainer} ++ * instance. ++ * ++ * Any changes made to the returned set will not be reflected on the ++ * instance. ++ * ++ * @return the key set ++ */ ++ @NonNull Set getKeys(); ++ ++ /** ++ * Returns if the container instance is empty, therefore has no entries ++ * inside it. ++ * ++ * @return the boolean ++ */ ++ boolean isEmpty(); ++ ++ /** ++ * Copies all values from this {@link PersistentDataContainer} to the provided ++ * container. ++ *

++ * This method only copies custom object keys. Existing tags, like the display ++ * name, will not be copied as the values are stored using your namespace. ++ * ++ * @param other the container to copy to ++ * @param replace whether to replace any matching values in the target container ++ * ++ * @throws IllegalArgumentException if the other container is null ++ */ ++ void copyTo(@NonNull PersistentDataContainer other, boolean replace); ++ ++ /** ++ * Returns the adapter context this tag container uses. ++ * ++ * @return the tag context ++ */ ++ @NonNull PersistentDataAdapterContext getAdapterContext(); ++ ++ /** ++ * Serialize this {@link PersistentDataContainer} instance to a ++ * byte array. ++ * ++ * @return a binary representation of this container ++ * @throws java.io.IOException if we fail to write this container to a byte array ++ */ ++ byte @NonNull [] serializeToBytes() throws java.io.IOException; ++} +diff --git a/src/main/java/io/papermc/paper/persistence/PersistentDataViewHolder.java b/src/main/java/io/papermc/paper/persistence/PersistentDataViewHolder.java +new file mode 100644 +index 0000000000000000000000000000000000000000..9789916f949374bfb50da535b076180700f7ae73 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/persistence/PersistentDataViewHolder.java +@@ -0,0 +1,23 @@ ++package io.papermc.paper.persistence; ++ ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.jetbrains.annotations.ApiStatus; ++ ++/** ++ * The {@link PersistentDataViewHolder} interface defines an object that can view ++ * custom persistent data on it. ++ */ ++@ApiStatus.NonExtendable ++public interface PersistentDataViewHolder { ++ ++ /** ++ * Returns a custom tag container view capable of viewing tags on the object. ++ *

++ * Note that the tags stored on this container are all stored under their ++ * own custom namespace therefore modifying default tags using this ++ * {@link PersistentDataViewHolder} is impossible. ++ * ++ * @return the persistent data container view ++ */ ++ @NonNull PersistentDataContainerView getPersistentDataContainer(); ++} +diff --git a/src/main/java/org/bukkit/inventory/ItemStack.java b/src/main/java/org/bukkit/inventory/ItemStack.java +index da91938ff941bb5903dcbd430c68c5980c01b57b..f603b5b6ba80af919f415322583a8345a5b1358a 100644 +--- a/src/main/java/org/bukkit/inventory/ItemStack.java ++++ b/src/main/java/org/bukkit/inventory/ItemStack.java +@@ -27,7 +27,7 @@ import org.jetbrains.annotations.Nullable; + * use this class to encapsulate Materials for which {@link Material#isItem()} + * returns false. + */ +-public class ItemStack implements Cloneable, ConfigurationSerializable, Translatable, net.kyori.adventure.text.event.HoverEventSource, net.kyori.adventure.translation.Translatable { // Paper ++public class ItemStack implements Cloneable, ConfigurationSerializable, Translatable, net.kyori.adventure.text.event.HoverEventSource, net.kyori.adventure.translation.Translatable, io.papermc.paper.persistence.PersistentDataViewHolder { // Paper + private ItemStack craftDelegate; // Paper - always delegate to server-backed stack + private MaterialData data = null; + +@@ -61,6 +61,13 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat + } + // Paper end + ++ // Paper start - pdc ++ @Override ++ public io.papermc.paper.persistence.@NotNull PersistentDataContainerView getPersistentDataContainer() { ++ return this.craftDelegate.getPersistentDataContainer(); ++ } ++ // Paper end - pdc ++ + @Utility + protected ItemStack() {} + +diff --git a/src/main/java/org/bukkit/persistence/PersistentDataContainer.java b/src/main/java/org/bukkit/persistence/PersistentDataContainer.java +index decf3b1949d4653a9fb01684b93ff91048137076..0f40eb52f6e2a5b6932cf9f223c1bbe9ab9eab1b 100644 +--- a/src/main/java/org/bukkit/persistence/PersistentDataContainer.java ++++ b/src/main/java/org/bukkit/persistence/PersistentDataContainer.java +@@ -9,7 +9,7 @@ import org.jetbrains.annotations.Nullable; + * This interface represents a map like object, capable of storing custom tags + * in it. + */ +-public interface PersistentDataContainer { ++public interface PersistentDataContainer extends io.papermc.paper.persistence.PersistentDataContainerView { // Paper - split up view and mutable + + /** + * Stores a metadata value on the {@link PersistentDataHolder} instance. +@@ -33,118 +33,7 @@ public interface PersistentDataContainer { + * the {@link PersistentDataType#getPrimitiveType()} + */ + void set(@NotNull NamespacedKey key, @NotNull PersistentDataType type, @NotNull C value); +- +- /** +- * Returns if the persistent metadata provider has metadata registered +- * matching the provided parameters. +- *

+- * This method will only return true if the found value has the same primitive +- * data type as the provided key. +- *

+- * Storing a value using a custom {@link PersistentDataType} implementation +- * will not store the complex data type. Therefore storing a UUID (by +- * storing a byte[]) will match has("key" , +- * {@link PersistentDataType#BYTE_ARRAY}). Likewise a stored byte[] will +- * always match your UUID {@link PersistentDataType} even if it is not 16 +- * bytes long. +- *

+- * This method is only usable for custom object keys. Overwriting existing +- * tags, like the display name, will not work as the values are stored +- * using your namespace. +- * +- * @param key the key the value is stored under +- * @param type the type the primative stored value has to match +- * @param

the generic type of the stored primitive +- * @param the generic type of the eventually created complex object +- * +- * @return if a value with the provided key and type exists +- * +- * @throws IllegalArgumentException if the key to look up is null +- * @throws IllegalArgumentException if the type to cast the found object to is +- * null +- */ +- boolean has(@NotNull NamespacedKey key, @NotNull PersistentDataType type); +- +- /** +- * Returns if the persistent metadata provider has metadata registered matching +- * the provided parameters. +- *

+- * This method will return true as long as a value with the given key exists, +- * regardless of its type. +- *

+- * This method is only usable for custom object keys. Overwriting existing tags, +- * like the display name, will not work as the values are stored using your +- * namespace. +- * +- * @param key the key the value is stored under +- * +- * @return if a value with the provided key exists +- * +- * @throws IllegalArgumentException if the key to look up is null +- */ +- boolean has(@NotNull NamespacedKey key); +- +- /** +- * Returns the metadata value that is stored on the +- * {@link PersistentDataHolder} instance. +- * +- * @param key the key to look up in the custom tag map +- * @param type the type the value must have and will be casted to +- * @param

the generic type of the stored primitive +- * @param the generic type of the eventually created complex object +- * +- * @return the value or {@code null} if no value was mapped under the given +- * value +- * +- * @throws IllegalArgumentException if the key to look up is null +- * @throws IllegalArgumentException if the type to cast the found object to is +- * null +- * @throws IllegalArgumentException if a value exists under the given key, +- * but cannot be accessed using the given type +- * @throws IllegalArgumentException if no suitable adapter was found for +- * the {@link +- * PersistentDataType#getPrimitiveType()} +- */ +- @Nullable +- C get(@NotNull NamespacedKey key, @NotNull PersistentDataType type); +- +- /** +- * Returns the metadata value that is stored on the +- * {@link PersistentDataHolder} instance. If the value does not exist in the +- * container, the default value provided is returned. +- * +- * @param key the key to look up in the custom tag map +- * @param type the type the value must have and will be casted to +- * @param defaultValue the default value to return if no value was found for +- * the provided key +- * @param

the generic type of the stored primitive +- * @param the generic type of the eventually created complex object +- * +- * @return the value or the default value if no value was mapped under the +- * given key +- * +- * @throws IllegalArgumentException if the key to look up is null +- * @throws IllegalArgumentException if the type to cast the found object to is +- * null +- * @throws IllegalArgumentException if a value exists under the given key, +- * but cannot be accessed using the given type +- * @throws IllegalArgumentException if no suitable adapter was found for +- * the {@link PersistentDataType#getPrimitiveType()} +- */ +- @NotNull +- C getOrDefault(@NotNull NamespacedKey key, @NotNull PersistentDataType type, @NotNull C defaultValue); +- +- /** +- * Get the set of keys present on this {@link PersistentDataContainer} +- * instance. +- * +- * Any changes made to the returned set will not be reflected on the +- * instance. +- * +- * @return the key set +- */ +- @NotNull +- Set getKeys(); ++ // Paper - move to PersistentDataContainerView + + /** + * Removes a custom key from the {@link PersistentDataHolder} instance. +@@ -154,47 +43,10 @@ public interface PersistentDataContainer { + * @throws IllegalArgumentException if the provided key is null + */ + void remove(@NotNull NamespacedKey key); +- +- /** +- * Returns if the container instance is empty, therefore has no entries +- * inside it. +- * +- * @return the boolean +- */ +- boolean isEmpty(); +- +- /** +- * Copies all values from this {@link PersistentDataContainer} to the provided +- * container. +- *

+- * This method only copies custom object keys. Existing tags, like the display +- * name, will not be copied as the values are stored using your namespace. +- * +- * @param other the container to copy to +- * @param replace whether to replace any matching values in the target container +- * +- * @throws IllegalArgumentException if the other container is null +- */ +- void copyTo(@NotNull PersistentDataContainer other, boolean replace); +- +- /** +- * Returns the adapter context this tag container uses. +- * +- * @return the tag context +- */ +- @NotNull +- PersistentDataAdapterContext getAdapterContext(); ++ // Paper - move to PersistentDataContainerView + + // Paper start - byte array serialization +- /** +- * Serialize this {@link PersistentDataContainer} instance to a +- * byte array. +- * +- * @return a binary representation of this container +- * @throws java.io.IOException if we fail to write this container to a byte array +- */ +- byte @NotNull [] serializeToBytes() throws java.io.IOException; +- ++ // Paper - move to PersistentDataContainerView + /** + * Read values from a serialised byte array into this + * {@link PersistentDataContainer} instance. +diff --git a/src/main/java/org/bukkit/persistence/PersistentDataHolder.java b/src/main/java/org/bukkit/persistence/PersistentDataHolder.java +index 80b277cc57f092f04fbf7810ac78d250b207b775..71f33c1265bc753ef40108ffce0d4bd8656d2903 100644 +--- a/src/main/java/org/bukkit/persistence/PersistentDataHolder.java ++++ b/src/main/java/org/bukkit/persistence/PersistentDataHolder.java +@@ -5,8 +5,10 @@ import org.jetbrains.annotations.NotNull; + /** + * The {@link PersistentDataHolder} interface defines an object that can store + * custom persistent meta data on it. ++ *

Prefer using {@link io.papermc.paper.persistence.PersistentDataViewHolder} for read-only operations ++ * as it covers more types.

+ */ +-public interface PersistentDataHolder { ++public interface PersistentDataHolder extends io.papermc.paper.persistence.PersistentDataViewHolder { // Paper + + /** + * Returns a custom tag container capable of storing tags on the object. diff --git a/patches/server/0537-ItemStack-repair-check-API.patch b/patches/server/0537-ItemStack-repair-check-API.patch index 1a746225f9..b2c00e80ef 100644 --- a/patches/server/0537-ItemStack-repair-check-API.patch +++ b/patches/server/0537-ItemStack-repair-check-API.patch @@ -25,10 +25,10 @@ index a71717fe99e78c480747cc61ab30b53b6667fde7..080ab25d3585552c1abd62a9992d48bf @Override diff --git a/src/test/java/io/papermc/paper/util/ItemStackRepairCheckTest.java b/src/test/java/io/papermc/paper/util/ItemStackRepairCheckTest.java new file mode 100644 -index 0000000000000000000000000000000000000000..6b8d360ef86e181a680ad77f28b7dd7368dddfe7 +index 0000000000000000000000000000000000000000..9f8abe2376f16aeffe4e9f90a2da04b7e3a55429 --- /dev/null +++ b/src/test/java/io/papermc/paper/util/ItemStackRepairCheckTest.java -@@ -0,0 +1,48 @@ +@@ -0,0 +1,41 @@ +package io.papermc.paper.util; + +import org.bukkit.Material; @@ -69,11 +69,4 @@ index 0000000000000000000000000000000000000000..6b8d360ef86e181a680ad77f28b7dd73 + + assertFalse(diamond.canRepair(new ItemStack(Material.OAK_BUTTON)), "diamond can repair oak button"); + } -+ -+ @Test -+ public void testInvalidItem() { -+ ItemStack badItemStack = new ItemStack(Material.ACACIA_WALL_SIGN); -+ -+ assertFalse(badItemStack.isRepairableBy(new ItemStack(Material.DIAMOND)), "acacia wall sign is repairable by diamond"); -+ } +} diff --git a/patches/server/0970-General-ItemMeta-fixes.patch b/patches/server/0970-General-ItemMeta-fixes.patch index 776e90997b..5fad25eee7 100644 --- a/patches/server/0970-General-ItemMeta-fixes.patch +++ b/patches/server/0970-General-ItemMeta-fixes.patch @@ -830,7 +830,7 @@ index 97b7085250d749c5e46352b372068b51de89bc7f..7277e7ee566aabf6e01937072d949ed6 } } diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java -index 8cfce9c557c4411914adffd10872bf3129435423..d9d4b983672b4340df926d98a7fa6708acad8a05 100644 +index 8cfce9c557c4411914adffd10872bf3129435423..62d615a4c7acfbd970278531e1bbceb71591f7b1 100644 --- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java @@ -182,9 +182,10 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { @@ -846,6 +846,15 @@ index 8cfce9c557c4411914adffd10872bf3129435423..d9d4b983672b4340df926d98a7fa6708 Applicator put(ItemMetaKeyType key, T value) { this.builder.set(key.TYPE, value); +@@ -278,7 +279,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + private CraftFoodComponent food; + private CraftToolComponent tool; + private CraftJukeboxComponent jukebox; +- private int damage; ++ private Integer damage; // Paper - may not be set + private Integer maxDamage; + + private static final Set HANDLED_TAGS = Sets.newHashSet(); @@ -310,7 +311,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { this.enchantments = new EnchantmentMap(meta.enchantments); // Paper } @@ -876,6 +885,15 @@ index 8cfce9c557c4411914adffd10872bf3129435423..d9d4b983672b4340df926d98a7fa6708 } for (Object obj : mods.keySet()) { +@@ -905,7 +911,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + itemTag.put(CraftMetaItem.JUKEBOX_PLAYABLE, this.jukebox.getHandle()); + } + +- if (this.hasDamage()) { ++ if (this.hasDamageValue()) { // Paper - preserve empty/0 damage + itemTag.put(CraftMetaItem.DAMAGE, this.damage); + } + @@ -970,10 +976,8 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { } @@ -889,6 +907,15 @@ index 8cfce9c557c4411914adffd10872bf3129435423..d9d4b983672b4340df926d98a7fa6708 return; } +@@ -1010,7 +1014,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + + @Overridden + boolean isEmpty() { +- return !(this.hasDisplayName() || this.hasItemName() || this.hasLocalizedName() || this.hasEnchants() || (this.lore != null) || this.hasCustomModelData() || this.hasBlockData() || this.hasRepairCost() || !this.unhandledTags.build().isEmpty() || !this.removedTags.isEmpty() || !this.persistentDataContainer.isEmpty() || this.hideFlag != 0 || this.isHideTooltip() || this.isUnbreakable() || this.hasEnchantmentGlintOverride() || this.isFireResistant() || this.hasMaxStackSize() || this.hasRarity() || this.hasFood() || this.hasTool() || this.hasDamage() || this.hasMaxDamage() || this.hasAttributeModifiers() || this.customTag != null || this.canPlaceOnPredicates != null || this.canBreakPredicates != null); // Paper ++ return !(this.hasDisplayName() || this.hasItemName() || this.hasLocalizedName() || this.hasEnchants() || (this.lore != null) || this.hasCustomModelData() || this.hasBlockData() || this.hasRepairCost() || !this.unhandledTags.build().isEmpty() || !this.removedTags.isEmpty() || !this.persistentDataContainer.isEmpty() || this.hideFlag != 0 || this.isHideTooltip() || this.isUnbreakable() || this.hasEnchantmentGlintOverride() || this.isFireResistant() || this.hasMaxStackSize() || this.hasRarity() || this.hasFood() || this.hasTool() || this.hasDamageValue() || this.hasMaxDamage() || this.hasAttributeModifiers() || this.customTag != null || this.canPlaceOnPredicates != null || this.canBreakPredicates != null); // Paper + } + + // Paper start @@ -1106,6 +1110,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { @Override @@ -1035,7 +1062,19 @@ index 8cfce9c557c4411914adffd10872bf3129435423..d9d4b983672b4340df926d98a7fa6708 for (Map.Entry entry : first.entries()) { if (!second.containsEntry(entry.getKey(), entry.getValue())) { return false; -@@ -1616,6 +1631,8 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { +@@ -1606,19 +1621,33 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + + @Override + public boolean hasDamage() { +- return this.damage > 0; ++ return this.damage != null && this.damage > 0; // Paper - null check + } + + @Override + public int getDamage() { +- return this.damage; ++ return this.damage == null ? 0 : this.damage; // Paper - null check + } @Override public void setDamage(int damage) { @@ -1044,7 +1083,22 @@ index 8cfce9c557c4411914adffd10872bf3129435423..d9d4b983672b4340df926d98a7fa6708 this.damage = damage; } -@@ -1632,6 +1649,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { ++ // Paper start - preserve empty/0 damage ++ @Override ++ public boolean hasDamageValue() { ++ return this.damage != null; ++ } ++ ++ @Override ++ public void resetDamage() { ++ this.damage = null; ++ } ++ // Paper end - preserve empty/0 damage ++ + @Override + public boolean hasMaxDamage() { + return this.maxDamage != null; +@@ -1632,6 +1661,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { @Override public void setMaxDamage(Integer maxDamage) { @@ -1052,7 +1106,7 @@ index 8cfce9c557c4411914adffd10872bf3129435423..d9d4b983672b4340df926d98a7fa6708 this.maxDamage = maxDamage; } -@@ -1663,7 +1681,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { +@@ -1663,7 +1693,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { && (this.hasCustomModelData() ? that.hasCustomModelData() && this.customModelData.equals(that.customModelData) : !that.hasCustomModelData()) && (this.hasBlockData() ? that.hasBlockData() && this.blockData.equals(that.blockData) : !that.hasBlockData()) && (this.hasRepairCost() ? that.hasRepairCost() && this.repairCost == that.repairCost : !that.hasRepairCost()) @@ -1061,18 +1115,29 @@ index 8cfce9c557c4411914adffd10872bf3129435423..d9d4b983672b4340df926d98a7fa6708 && (this.unhandledTags.equals(that.unhandledTags)) && (this.removedTags.equals(that.removedTags)) && (Objects.equals(this.customTag, that.customTag)) -@@ -1725,8 +1743,8 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { +@@ -1678,7 +1708,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + && (this.hasFood() ? that.hasFood() && this.food.equals(that.food) : !that.hasFood()) + && (this.hasTool() ? that.hasTool() && this.tool.equals(that.tool) : !that.hasTool()) + && (this.hasJukeboxPlayable() ? that.hasJukeboxPlayable() && this.jukebox.equals(that.jukebox) : !that.hasJukeboxPlayable()) +- && (this.hasDamage() ? that.hasDamage() && this.damage == that.damage : !that.hasDamage()) ++ && (Objects.equals(this.damage, that.damage)) // Paper - preserve empty/0 damage + && (this.hasMaxDamage() ? that.hasMaxDamage() && this.maxDamage.equals(that.maxDamage) : !that.hasMaxDamage()) + && (this.canPlaceOnPredicates != null ? that.canPlaceOnPredicates != null && this.canPlaceOnPredicates.equals(that.canPlaceOnPredicates) : that.canPlaceOnPredicates == null) // Paper + && (this.canBreakPredicates != null ? that.canBreakPredicates != null && this.canBreakPredicates.equals(that.canBreakPredicates) : that.canBreakPredicates == null) // Paper +@@ -1724,9 +1754,9 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + hash = 61 * hash + (this.hasFood() ? this.food.hashCode() : 0); hash = 61 * hash + (this.hasTool() ? this.tool.hashCode() : 0); hash = 61 * hash + (this.hasJukeboxPlayable() ? this.jukebox.hashCode() : 0); - hash = 61 * hash + (this.hasDamage() ? this.damage : 0); +- hash = 61 * hash + (this.hasDamage() ? this.damage : 0); - hash = 61 * hash + (this.hasMaxDamage() ? 1231 : 1237); - hash = 61 * hash + (this.hasAttributeModifiers() ? this.attributeModifiers.hashCode() : 0); ++ hash = 61 * hash + (this.hasDamageValue() ? this.damage : -1); // Paper - preserve empty/0 damage + hash = 61 * hash + (this.hasMaxDamage() ? this.maxDamage.hashCode() : 0); // Paper - max damage is not a boolean + hash = 61 * hash + (this.attributeModifiers != null ? this.attributeModifiers.hashCode() : 0); // Paper - track only null attributes hash = 61 * hash + (this.canPlaceOnPredicates != null ? this.canPlaceOnPredicates.hashCode() : 0); // Paper hash = 61 * hash + (this.canBreakPredicates != null ? this.canBreakPredicates.hashCode() : 0); // Paper hash = 61 * hash + this.version; -@@ -1746,7 +1764,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { +@@ -1746,7 +1776,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { if (this.enchantments != null) { clone.enchantments = new EnchantmentMap(this.enchantments); // Paper } @@ -1081,7 +1146,16 @@ index 8cfce9c557c4411914adffd10872bf3129435423..d9d4b983672b4340df926d98a7fa6708 clone.attributeModifiers = LinkedHashMultimap.create(this.attributeModifiers); } if (this.customTag != null) { -@@ -1975,7 +1993,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { +@@ -1874,7 +1904,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + builder.put(CraftMetaItem.JUKEBOX_PLAYABLE.BUKKIT, this.jukebox); + } + +- if (this.hasDamage()) { ++ if (this.hasDamageValue()) { // Paper - preserve empty/0 damage + builder.put(CraftMetaItem.DAMAGE.BUKKIT, this.damage); + } + +@@ -1975,7 +2005,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { } static void serializeModifiers(Multimap modifiers, ImmutableMap.Builder builder, ItemMetaKey key) { @@ -1090,7 +1164,7 @@ index 8cfce9c557c4411914adffd10872bf3129435423..d9d4b983672b4340df926d98a7fa6708 return; } -@@ -2057,7 +2075,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { +@@ -2057,7 +2087,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { // Paper start - improve checking handled tags @org.jetbrains.annotations.VisibleForTesting public static final Map, Set>> HANDLED_DCTS_PER_TYPE = new HashMap<>(); @@ -1099,7 +1173,7 @@ index 8cfce9c557c4411914adffd10872bf3129435423..d9d4b983672b4340df926d98a7fa6708 CraftMetaItem.NAME.TYPE, CraftMetaItem.ITEM_NAME.TYPE, CraftMetaItem.LORE.TYPE, -@@ -2125,7 +2143,12 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { +@@ -2125,7 +2155,12 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { // Paper end - improve checking handled data component types protected static Optional getOrEmpty(DataComponentPatch tag, ItemMetaKeyType type) { diff --git a/patches/server/0977-Fix-equipment-slot-and-group-API.patch b/patches/server/0977-Fix-equipment-slot-and-group-API.patch index 0369dbaa73..1bc1cdaf85 100644 --- a/patches/server/0977-Fix-equipment-slot-and-group-API.patch +++ b/patches/server/0977-Fix-equipment-slot-and-group-API.patch @@ -32,7 +32,7 @@ index 9d74577af071954e1e37201a96368c1360076209..eafa54c870c3e2aef30c3f9f96f51660 throw new IllegalArgumentException("Not implemented. This is a bug"); } diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java -index d9d4b983672b4340df926d98a7fa6708acad8a05..7c6d4c08cf07da18b414773dfed5704926938bea 100644 +index 62d615a4c7acfbd970278531e1bbceb71591f7b1..4d97024bb05ab815409fc25c5924903868cc3945 100644 --- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java @@ -1452,7 +1452,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { diff --git a/patches/server/1024-Proxy-ItemStack-to-CraftItemStack.patch b/patches/server/1024-Proxy-ItemStack-to-CraftItemStack.patch new file mode 100644 index 0000000000..aba1d9b341 --- /dev/null +++ b/patches/server/1024-Proxy-ItemStack-to-CraftItemStack.patch @@ -0,0 +1,300 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Tue, 14 May 2024 11:57:43 -0700 +Subject: [PATCH] Proxy ItemStack to CraftItemStack + + +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java +index be36336a3c7d1ae88277f4ee1be70075001de7a7..814e8ece6821e359b504e8c4d140cc38700f2abe 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java +@@ -25,15 +25,57 @@ import org.bukkit.material.MaterialData; + @DelegateDeserialization(ItemStack.class) + public final class CraftItemStack extends ItemStack { + +- // Paper start - MC Utils +- public static net.minecraft.world.item.ItemStack unwrap(ItemStack bukkit) { +- if (bukkit instanceof CraftItemStack craftItemStack) { +- return craftItemStack.handle != null ? craftItemStack.handle : net.minecraft.world.item.ItemStack.EMPTY; ++ // Paper start - delegate api-ItemStack to CraftItemStack ++ private static final java.lang.invoke.VarHandle API_ITEM_STACK_CRAFT_DELEGATE_FIELD; ++ static { ++ try { ++ API_ITEM_STACK_CRAFT_DELEGATE_FIELD = java.lang.invoke.MethodHandles.privateLookupIn( ++ ItemStack.class, ++ java.lang.invoke.MethodHandles.lookup() ++ ).findVarHandle(ItemStack.class, "craftDelegate", ItemStack.class); ++ } catch (final IllegalAccessException | NoSuchFieldException exception) { ++ throw new RuntimeException(exception); ++ } ++ } ++ ++ private static CraftItemStack getCraftStack(final ItemStack bukkit) { ++ if (bukkit instanceof final CraftItemStack craftItemStack) { ++ return craftItemStack; + } else { +- return asNMSCopy(bukkit); ++ return (CraftItemStack) API_ITEM_STACK_CRAFT_DELEGATE_FIELD.get(bukkit); + } + } + ++ @Override ++ public int hashCode() { ++ if (this.handle == null || this.handle.isEmpty()) { ++ return net.minecraft.world.item.ItemStack.EMPTY.hashCode(); ++ } else { ++ int hash = net.minecraft.world.item.ItemStack.hashItemAndComponents(this.handle); ++ hash = hash * 31 + this.handle.getCount(); ++ return hash; ++ } ++ } ++ ++ @Override ++ public boolean equals(final Object obj) { ++ if (!(obj instanceof final org.bukkit.inventory.ItemStack bukkit)) return false; ++ final CraftItemStack craftStack = getCraftStack(bukkit); ++ if (this.handle == craftStack.handle) return true; ++ else if (this.handle == null || craftStack.handle == null) return false; ++ else if (this.handle.isEmpty() && craftStack.handle.isEmpty()) return true; ++ else return net.minecraft.world.item.ItemStack.matches(this.handle, craftStack.handle); ++ } ++ // Paper end ++ ++ // Paper start - MC Utils ++ public static net.minecraft.world.item.ItemStack unwrap(ItemStack bukkit) { ++ // Paper start - re-implement after delegating all api ItemStack calls to CraftItemStack ++ final CraftItemStack craftItemStack = getCraftStack(bukkit); ++ return craftItemStack.handle == null ? net.minecraft.world.item.ItemStack.EMPTY : craftItemStack.handle; ++ // Paper end - re-implement after delegating all api ItemStack calls to CraftItemStack ++ } ++ + public static net.minecraft.world.item.ItemStack getOrCloneOnMutation(ItemStack old, ItemStack newInstance) { + return old == newInstance ? unwrap(old) : asNMSCopy(newInstance); + } +@@ -47,25 +89,13 @@ public final class CraftItemStack extends ItemStack { + // Paper end - override isEmpty to use vanilla's impl + + public static net.minecraft.world.item.ItemStack asNMSCopy(ItemStack original) { +- if (original instanceof CraftItemStack) { +- CraftItemStack stack = (CraftItemStack) original; +- return stack.handle == null ? net.minecraft.world.item.ItemStack.EMPTY : stack.handle.copy(); +- } +- if (original == null || original.isEmpty()) { // Paper - override isEmpty to use vanilla's impl; use isEmpty ++ // Paper start - re-implement after delegating all api ItemStack calls to CraftItemStack ++ if (original == null || original.isEmpty()) { + return net.minecraft.world.item.ItemStack.EMPTY; + } +- +- Item item = CraftItemType.bukkitToMinecraft(original.getType()); +- +- if (item == null) { +- return net.minecraft.world.item.ItemStack.EMPTY; +- } +- +- net.minecraft.world.item.ItemStack stack = new net.minecraft.world.item.ItemStack(item, original.getAmount()); +- if (original.hasItemMeta()) { +- CraftItemStack.setItemMeta(stack, original.getItemMeta()); +- } +- return stack; ++ final CraftItemStack stack = getCraftStack(original); ++ return stack.handle == null ? net.minecraft.world.item.ItemStack.EMPTY : stack.handle.copy(); ++ // Paper end - re-implement after delegating all api ItemStack calls to CraftItemStack + } + + // Paper start +@@ -88,14 +118,10 @@ public final class CraftItemStack extends ItemStack { + * Copies the NMS stack to return as a strictly-Bukkit stack + */ + public static ItemStack asBukkitCopy(net.minecraft.world.item.ItemStack original) { +- if (original.isEmpty()) { +- return new ItemStack(Material.AIR); +- } +- ItemStack stack = new ItemStack(CraftItemType.minecraftToBukkit(original.getItem()), original.getCount()); +- if (CraftItemStack.hasItemMeta(original)) { +- stack.setItemMeta(CraftItemStack.getItemMeta(original)); +- } +- return stack; ++ // Paper start - no such thing as a "strictly-Bukkit stack" anymore ++ // we copy the stack since it should be a complete copy not a mirror ++ return asCraftMirror(original.copy()); ++ // Paper end + } + + public static CraftItemStack asCraftMirror(net.minecraft.world.item.ItemStack original) { +@@ -313,11 +339,7 @@ public final class CraftItemStack extends ItemStack { + + @Override + public CraftItemStack clone() { +- CraftItemStack itemStack = (CraftItemStack) super.clone(); +- if (this.handle != null) { +- itemStack.handle = this.handle.copy(); +- } +- return itemStack; ++ return new org.bukkit.craftbukkit.inventory.CraftItemStack(this.handle != null ? this.handle.copy() : null); // Paper + } + + @Override +@@ -420,22 +442,14 @@ public final class CraftItemStack extends ItemStack { + if (stack == this) { + return true; + } +- if (!(stack instanceof CraftItemStack)) { +- return stack.getClass() == ItemStack.class && stack.isSimilar(this); +- } +- +- CraftItemStack that = (CraftItemStack) stack; ++ final CraftItemStack that = getCraftStack(stack); // Paper - re-implement after delegating all api ItemStack calls to CraftItemStack + if (this.handle == that.handle) { + return true; + } + if (this.handle == null || that.handle == null) { + return false; + } +- Material comparisonType = CraftLegacy.fromLegacy(that.getType()); // This may be called from legacy item stacks, try to get the right material +- if (!(comparisonType == this.getType() && this.getDurability() == that.getDurability())) { +- return false; +- } +- return this.hasItemMeta() ? that.hasItemMeta() && this.handle.getComponents().equals(that.handle.getComponents()) : !that.hasItemMeta(); ++ return net.minecraft.world.item.ItemStack.isSameItemSameComponents(this.handle, that.handle); // Paper - re-implement after delegating all api ItemStack calls to CraftItemStack + } + + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemType.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemType.java +index 9d2feff3df87cfeefe7105d954854af05cef6f69..07539ebfefa3352de5ee7a17f2724cf2c979f399 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemType.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemType.java +@@ -100,13 +100,14 @@ public class CraftItemType implements ItemType.Typed, Han + @NotNull + @Override + public ItemStack createItemStack(final int amount, @Nullable final Consumer metaConfigurator) { +- final ItemStack itemStack = new ItemStack(this.asMaterial(), amount); ++ // Paper start - re-implement to return CraftItemStack ++ final net.minecraft.world.item.ItemStack stack = new net.minecraft.world.item.ItemStack(this.item, amount); ++ final CraftItemStack mirror = CraftItemStack.asCraftMirror(stack); + if (metaConfigurator != null) { +- final ItemMeta itemMeta = itemStack.getItemMeta(); +- metaConfigurator.accept((M) itemMeta); +- itemStack.setItemMeta(itemMeta); ++ mirror.editMeta(this.getItemMetaClass(), metaConfigurator); + } +- return itemStack; ++ return mirror; ++ // Paper start - reimplement to return CraftItemStack + } + + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/legacy/MaterialRerouting.java b/src/main/java/org/bukkit/craftbukkit/legacy/MaterialRerouting.java +index 9c004e7cb46841d874ab997bf2e3b63ae763aec7..d7c8f26b21276d9ff1d5c7c9738cc1126ce7d4b9 100644 +--- a/src/main/java/org/bukkit/craftbukkit/legacy/MaterialRerouting.java ++++ b/src/main/java/org/bukkit/craftbukkit/legacy/MaterialRerouting.java +@@ -678,4 +678,16 @@ public class MaterialRerouting { + return itemStack.withType(material); + } + // Paper end - register paper API specific material consumers in rerouting ++ ++ // Paper start - methods added post 1.13, no-op ++ @RerouteStatic("org/bukkit/inventory/ItemStack") ++ public static ItemStack of(final Material material) { ++ return ItemStack.of(material); ++ } ++ ++ @RerouteStatic("org/bukkit/inventory/ItemStack") ++ public static ItemStack of(final Material material, final int amount) { ++ return ItemStack.of(material, amount); ++ } ++ // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +index 8dba6c4a2e1f305cf576e8bfdca5d0c07ab871ae..d70c5546c8bd6f364fad9b24880b6867efdab644 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +@@ -700,6 +700,13 @@ public final class CraftMagicNumbers implements UnsafeValues { + } + // Paper end - hack to get tags for non server-backed registries + ++ // Paper start - proxy ItemStack ++ @Override ++ public org.bukkit.inventory.ItemStack createEmptyStack() { ++ return CraftItemStack.asCraftMirror(null); ++ } ++ // Paper end - proxy ItemStack ++ + /** + * This helper class represents the different NBT Tags. + *

+diff --git a/src/test/java/io/papermc/paper/configuration/ConfigurationSectionTest.java b/src/test/java/io/papermc/paper/configuration/ConfigurationSectionTest.java +new file mode 100644 +index 0000000000000000000000000000000000000000..e5c2fb160e9d390cdfa0259a3feb9f488b2dc14d +--- /dev/null ++++ b/src/test/java/io/papermc/paper/configuration/ConfigurationSectionTest.java +@@ -0,0 +1,53 @@ ++package io.papermc.paper.configuration; ++ ++import org.bukkit.Material; ++import org.bukkit.configuration.ConfigurationSection; ++import org.bukkit.inventory.ItemStack; ++import org.bukkit.support.AbstractTestingBase; ++import org.junit.jupiter.api.Test; ++ ++import static org.junit.jupiter.api.Assertions.assertEquals; ++import static org.junit.jupiter.api.Assertions.assertFalse; ++import static org.junit.jupiter.api.Assertions.assertNull; ++import static org.junit.jupiter.api.Assertions.assertTrue; ++ ++public abstract class ConfigurationSectionTest extends AbstractTestingBase { ++ public abstract ConfigurationSection getConfigurationSection(); ++ ++ @Test ++ public void testGetItemStack_String() { ++ ConfigurationSection section = getConfigurationSection(); ++ String key = "exists"; ++ ItemStack value = new ItemStack(Material.ACACIA_WOOD, 50); ++ ++ section.set(key, value); ++ ++ assertEquals(value, section.getItemStack(key)); ++ assertNull(section.getString("doesntExist")); ++ } ++ ++ @Test ++ public void testGetItemStack_String_ItemStack() { ++ ConfigurationSection section = getConfigurationSection(); ++ String key = "exists"; ++ ItemStack value = new ItemStack(Material.ACACIA_WOOD, 50); ++ ItemStack def = new ItemStack(Material.STONE, 1); ++ ++ section.set(key, value); ++ ++ assertEquals(value, section.getItemStack(key, def)); ++ assertEquals(def, section.getItemStack("doesntExist", def)); ++ } ++ ++ @Test ++ public void testIsItemStack() { ++ ConfigurationSection section = getConfigurationSection(); ++ String key = "exists"; ++ ItemStack value = new ItemStack(Material.ACACIA_WOOD, 50); ++ ++ section.set(key, value); ++ ++ assertTrue(section.isItemStack(key)); ++ assertFalse(section.isItemStack("doesntExist")); ++ } ++} +diff --git a/src/test/java/io/papermc/paper/configuration/MemorySectionTest.java b/src/test/java/io/papermc/paper/configuration/MemorySectionTest.java +new file mode 100644 +index 0000000000000000000000000000000000000000..def33c36f207a4c5306b5a895336aa70335c1678 +--- /dev/null ++++ b/src/test/java/io/papermc/paper/configuration/MemorySectionTest.java +@@ -0,0 +1,11 @@ ++package io.papermc.paper.configuration; ++ ++import org.bukkit.configuration.ConfigurationSection; ++import org.bukkit.configuration.MemoryConfiguration; ++ ++public class MemorySectionTest extends ConfigurationSectionTest { ++ @Override ++ public ConfigurationSection getConfigurationSection() { ++ return new MemoryConfiguration().createSection("section"); ++ } ++} diff --git a/patches/server/1025-Make-a-PDC-view-accessible-directly-from-ItemStack.patch b/patches/server/1025-Make-a-PDC-view-accessible-directly-from-ItemStack.patch new file mode 100644 index 0000000000..95338b76bf --- /dev/null +++ b/patches/server/1025-Make-a-PDC-view-accessible-directly-from-ItemStack.patch @@ -0,0 +1,266 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Wed, 12 Jun 2024 10:29:40 -0700 +Subject: [PATCH] Make a PDC view accessible directly from ItemStack + + +diff --git a/src/main/java/io/papermc/paper/persistence/PaperPersistentDataContainerView.java b/src/main/java/io/papermc/paper/persistence/PaperPersistentDataContainerView.java +new file mode 100644 +index 0000000000000000000000000000000000000000..fac401280d3f3689b00e16c19155ca753faa06e0 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/persistence/PaperPersistentDataContainerView.java +@@ -0,0 +1,86 @@ ++package io.papermc.paper.persistence; ++ ++import com.google.common.base.Preconditions; ++import java.io.ByteArrayOutputStream; ++import java.io.DataOutputStream; ++import java.io.IOException; ++import net.minecraft.nbt.CompoundTag; ++import net.minecraft.nbt.NbtIo; ++import net.minecraft.nbt.Tag; ++import org.bukkit.NamespacedKey; ++import org.bukkit.craftbukkit.persistence.CraftPersistentDataAdapterContext; ++import org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry; ++import org.bukkit.persistence.PersistentDataAdapterContext; ++import org.bukkit.persistence.PersistentDataType; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.checker.nullness.qual.Nullable; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++@DefaultQualifier(NonNull.class) ++public abstract class PaperPersistentDataContainerView implements PersistentDataContainerView { ++ ++ protected final CraftPersistentDataTypeRegistry registry; ++ protected final CraftPersistentDataAdapterContext adapterContext; ++ ++ public PaperPersistentDataContainerView(final CraftPersistentDataTypeRegistry registry) { ++ this.registry = registry; ++ this.adapterContext = new CraftPersistentDataAdapterContext(this.registry); ++ } ++ ++ public abstract @Nullable Tag getTag(final String key); ++ ++ public abstract CompoundTag toTagCompound(); ++ ++ @Override ++ public boolean has(final NamespacedKey key, final PersistentDataType type) { ++ Preconditions.checkArgument(key != null, "The NamespacedKey key cannot be null"); ++ Preconditions.checkArgument(type != null, "The provided type cannot be null"); ++ ++ final @Nullable Tag value = this.getTag(key.toString()); ++ if (value == null) { ++ return false; ++ } ++ ++ return this.registry.isInstanceOf(type, value); ++ } ++ ++ @Override ++ public boolean has(final NamespacedKey key) { ++ Preconditions.checkArgument(key != null, "The provided key for the custom value was null"); // Paper ++ return this.getTag(key.toString()) != null; ++ } ++ ++ @Override ++ public @Nullable C get(final NamespacedKey key, final PersistentDataType type) { ++ Preconditions.checkArgument(key != null, "The NamespacedKey key cannot be null"); ++ Preconditions.checkArgument(type != null, "The provided type cannot be null"); ++ ++ final @Nullable Tag value = this.getTag(key.toString()); ++ if (value == null) { ++ return null; ++ } ++ ++ return type.fromPrimitive(this.registry.extract(type, value), this.adapterContext); ++ } ++ ++ @Override ++ public C getOrDefault(final NamespacedKey key, final PersistentDataType type, final C defaultValue) { ++ final C c = this.get(key, type); ++ return c != null ? c : defaultValue; ++ } ++ ++ @Override ++ public PersistentDataAdapterContext getAdapterContext() { ++ return this.adapterContext; ++ } ++ ++ @Override ++ public byte[] serializeToBytes() throws IOException { ++ final net.minecraft.nbt.CompoundTag root = this.toTagCompound(); ++ final ByteArrayOutputStream byteArrayOutput = new ByteArrayOutputStream(); ++ try (final DataOutputStream dataOutput = new DataOutputStream(byteArrayOutput)) { ++ NbtIo.write(root, dataOutput); ++ return byteArrayOutput.toByteArray(); ++ } ++ } ++} +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java +index 814e8ece6821e359b504e8c4d140cc38700f2abe..32a41c8b324aad67b9dcf74387aef299e6478a64 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java +@@ -480,4 +480,63 @@ public final class CraftItemStack extends ItemStack { + return mirrored; + } + // Paper end ++ ++ // Paper start - pdc ++ private net.minecraft.nbt.CompoundTag getPdcTag() { ++ if (this.handle == null) { ++ return new net.minecraft.nbt.CompoundTag(); ++ } ++ final net.minecraft.world.item.component.CustomData customData = this.handle.getOrDefault(DataComponents.CUSTOM_DATA, net.minecraft.world.item.component.CustomData.EMPTY); ++ // getUnsafe is OK here because we are only ever *reading* the data so immutability is preserved ++ //noinspection deprecation ++ return customData.getUnsafe().getCompound("PublicBukkitValues"); ++ } ++ ++ private static final org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry REGISTRY = new org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry(); ++ private final io.papermc.paper.persistence.PaperPersistentDataContainerView pdcView = new io.papermc.paper.persistence.PaperPersistentDataContainerView(REGISTRY) { ++ ++ @Override ++ public net.minecraft.nbt.CompoundTag toTagCompound() { ++ return CraftItemStack.this.getPdcTag(); ++ } ++ ++ @Override ++ public net.minecraft.nbt.Tag getTag(final String key) { ++ return CraftItemStack.this.getPdcTag().get(key); ++ } ++ ++ @Override ++ public java.util.Set getKeys() { ++ java.util.Set keys = new java.util.HashSet<>(); ++ CraftItemStack.this.getPdcTag().getAllKeys().forEach(key -> { ++ final String[] keyData = key.split(":", 2); ++ if (keyData.length == 2) { ++ keys.add(new org.bukkit.NamespacedKey(keyData[0], keyData[1])); ++ } ++ }); ++ return java.util.Collections.unmodifiableSet(keys); ++ }; ++ ++ @Override ++ public boolean isEmpty() { ++ return CraftItemStack.this.getPdcTag().isEmpty(); ++ } ++ ++ @Override ++ public void copyTo(final org.bukkit.persistence.PersistentDataContainer other, final boolean replace) { ++ Preconditions.checkArgument(other != null, "The target container cannot be null"); ++ final org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer target = (org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer) other; ++ final net.minecraft.nbt.CompoundTag pdcTag = org.bukkit.craftbukkit.inventory.CraftItemStack.this.getPdcTag(); ++ for (final String key : pdcTag.getAllKeys()) { ++ if (replace || !target.getRaw().containsKey(key)) { ++ target.getRaw().put(key, pdcTag.get(key).copy()); ++ } ++ } ++ } ++ }; ++ @Override ++ public io.papermc.paper.persistence.PersistentDataContainerView getPersistentDataContainer() { ++ return this.pdcView; ++ } ++ // Paper end - pdc + } +diff --git a/src/main/java/org/bukkit/craftbukkit/persistence/CraftPersistentDataContainer.java b/src/main/java/org/bukkit/craftbukkit/persistence/CraftPersistentDataContainer.java +index f55fdd57ced259ad5a95878840e98ffaa3db2e05..9d867f1659433ea15f281c8b441db7e339013100 100644 +--- a/src/main/java/org/bukkit/craftbukkit/persistence/CraftPersistentDataContainer.java ++++ b/src/main/java/org/bukkit/craftbukkit/persistence/CraftPersistentDataContainer.java +@@ -16,11 +16,10 @@ import org.bukkit.persistence.PersistentDataContainer; + import org.bukkit.persistence.PersistentDataType; + import org.jetbrains.annotations.NotNull; + +-public class CraftPersistentDataContainer implements PersistentDataContainer { ++public class CraftPersistentDataContainer extends io.papermc.paper.persistence.PaperPersistentDataContainerView implements PersistentDataContainer { // Paper - split up view and mutable + + private final Map customDataTags = new HashMap<>(); +- private final CraftPersistentDataTypeRegistry registry; +- private final CraftPersistentDataAdapterContext adapterContext; ++ // Paper - move to PersistentDataContainerView + + public CraftPersistentDataContainer(Map customTags, CraftPersistentDataTypeRegistry registry) { + this(registry); +@@ -28,10 +27,15 @@ public class CraftPersistentDataContainer implements PersistentDataContainer { + } + + public CraftPersistentDataContainer(CraftPersistentDataTypeRegistry registry) { +- this.registry = registry; +- this.adapterContext = new CraftPersistentDataAdapterContext(this.registry); ++ super(registry); // Paper - move to PersistentDataContainerView + } + ++ // Paper start ++ @Override ++ public Tag getTag(final String key) { ++ return this.customDataTags.get(key); ++ } ++ // Paper end + + @Override + public void set(@NotNull NamespacedKey key, @NotNull PersistentDataType type, @NotNull Z value) { +@@ -42,44 +46,7 @@ public class CraftPersistentDataContainer implements PersistentDataContainer { + this.customDataTags.put(key.toString(), this.registry.wrap(type, type.toPrimitive(value, this.adapterContext))); + } + +- @Override +- public boolean has(@NotNull NamespacedKey key, @NotNull PersistentDataType type) { +- Preconditions.checkArgument(key != null, "The NamespacedKey key cannot be null"); +- Preconditions.checkArgument(type != null, "The provided type cannot be null"); +- +- Tag value = this.customDataTags.get(key.toString()); +- if (value == null) { +- return false; +- } +- +- return this.registry.isInstanceOf(type, value); +- } +- +- @Override +- public boolean has(NamespacedKey key) { +- Preconditions.checkArgument(key != null, "The provided key for the custom value was null"); // Paper +- return this.customDataTags.get(key.toString()) != null; +- } +- +- @Override +- public Z get(@NotNull NamespacedKey key, @NotNull PersistentDataType type) { +- Preconditions.checkArgument(key != null, "The NamespacedKey key cannot be null"); +- Preconditions.checkArgument(type != null, "The provided type cannot be null"); +- +- Tag value = this.customDataTags.get(key.toString()); +- if (value == null) { +- return null; +- } +- +- return type.fromPrimitive(this.registry.extract(type, value), this.adapterContext); +- } +- +- @NotNull +- @Override +- public Z getOrDefault(@NotNull NamespacedKey key, @NotNull PersistentDataType type, @NotNull Z defaultValue) { +- Z z = this.get(key, type); +- return z != null ? z : defaultValue; +- } ++ // Paper - move to PersistentDataContainerView + + @NotNull + @Override +@@ -186,16 +153,7 @@ public class CraftPersistentDataContainer implements PersistentDataContainer { + // Paper end + + // Paper start - byte array serialization +- @Override +- public byte[] serializeToBytes() throws java.io.IOException { +- final net.minecraft.nbt.CompoundTag root = this.toTagCompound(); +- final java.io.ByteArrayOutputStream byteArrayOutput = new java.io.ByteArrayOutputStream(); +- try (final java.io.DataOutputStream dataOutput = new java.io.DataOutputStream(byteArrayOutput)) { +- net.minecraft.nbt.NbtIo.write(root, dataOutput); +- return byteArrayOutput.toByteArray(); +- } +- } +- ++ // Paper - move to PersistentDataContainerView + @Override + public void readFromBytes(final byte[] bytes, final boolean clear) throws java.io.IOException { + if (clear) {