diff --git a/paper-api/src/main/java/org/bukkit/inventory/ItemStack.java b/paper-api/src/main/java/org/bukkit/inventory/ItemStack.java index 55457c7539c0..c84cddac865a 100644 --- a/paper-api/src/main/java/org/bukkit/inventory/ItemStack.java +++ b/paper-api/src/main/java/org/bukkit/inventory/ItemStack.java @@ -1,16 +1,15 @@ package org.bukkit.inventory; import com.google.common.base.Preconditions; -import com.google.common.collect.ImmutableMap; import io.papermc.paper.registry.RegistryKey; import java.util.LinkedHashMap; import java.util.Locale; import java.util.Map; +import java.util.function.Consumer; import net.kyori.adventure.text.Component; import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.NamespacedKey; -import org.bukkit.Registry; import org.bukkit.Translatable; import org.bukkit.UndefinedNullability; import org.bukkit.Utility; @@ -19,6 +18,7 @@ import org.bukkit.inventory.meta.Damageable; import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.material.MaterialData; +import org.bukkit.persistence.PersistentDataContainer; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -64,10 +64,26 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat // Paper end // Paper start - pdc + /** + * @see #editPersistentDataContainer(Consumer) + */ @Override public io.papermc.paper.persistence.@NotNull PersistentDataContainerView getPersistentDataContainer() { return this.craftDelegate.getPersistentDataContainer(); } + + /** + * Edits the {@link PersistentDataContainer} of this stack. The + * {@link PersistentDataContainer} instance is only valid inside the + * consumer. + * + * @param consumer the persistent data container consumer + * @return {@code true} if the edit was successful, {@code false} otherwise. Failure to edit the persistent data + * container may be caused by empty or invalid itemstacks. + */ + public boolean editPersistentDataContainer(@NotNull Consumer consumer) { + return this.craftDelegate.editPersistentDataContainer(consumer); + } // Paper end - pdc @Utility diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java b/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java index 3799973696ea..a6668ae29373 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java @@ -2,10 +2,11 @@ import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableMap; +import io.papermc.paper.adventure.PaperAdventure; import java.util.Collections; import java.util.Map; import java.util.Optional; -import io.papermc.paper.adventure.PaperAdventure; +import java.util.function.Consumer; import net.kyori.adventure.text.Component; import net.minecraft.advancements.critereon.ItemPredicate; import net.minecraft.advancements.critereon.MinMaxBounds; @@ -16,17 +17,21 @@ import net.minecraft.core.component.DataComponentPredicate; import net.minecraft.core.component.DataComponents; import net.minecraft.core.component.PatchedDataComponentMap; +import net.minecraft.nbt.CompoundTag; import net.minecraft.world.item.Item; +import net.minecraft.world.item.component.CustomData; import net.minecraft.world.item.enchantment.EnchantmentHelper; import net.minecraft.world.item.enchantment.ItemEnchantments; import org.bukkit.Material; import org.bukkit.configuration.serialization.DelegateDeserialization; import org.bukkit.craftbukkit.enchantments.CraftEnchantment; +import org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer; import org.bukkit.craftbukkit.util.CraftMagicNumbers; import org.bukkit.enchantments.Enchantment; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.material.MaterialData; +import org.bukkit.persistence.PersistentDataContainer; import org.jetbrains.annotations.NotNull; @DelegateDeserialization(ItemStack.class) @@ -159,7 +164,6 @@ public static ItemPredicate asCriterionConditionItem(ItemStack original) { } public net.minecraft.world.item.ItemStack handle; - private boolean isForInventoryDrop; /** * Mirror @@ -522,7 +526,7 @@ public ItemStack withType(final Material type) { } // Paper end - // Paper start - pdc + public static final String PDC_CUSTOM_DATA_KEY = "PublicBukkitValues"; private net.minecraft.nbt.CompoundTag getPdcTag() { if (this.handle == null) { return new net.minecraft.nbt.CompoundTag(); @@ -530,7 +534,7 @@ private net.minecraft.nbt.CompoundTag getPdcTag() { 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"); + return customData.getUnsafe().getCompound(PDC_CUSTOM_DATA_KEY); } private static final org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry REGISTRY = new org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry(); @@ -550,7 +554,30 @@ public net.minecraft.nbt.Tag getTag(final String key) { public io.papermc.paper.persistence.PersistentDataContainerView getPersistentDataContainer() { return this.pdcView; } - // Paper end - pdc + + @Override + public boolean editPersistentDataContainer(final Consumer consumer) { + if (this.handle == null || this.handle.isEmpty()) return false; + + final CraftPersistentDataContainer container = new CraftPersistentDataContainer(REGISTRY); + CustomData customData = this.handle.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY); + //noinspection deprecation // we copy only the pdc tag + final CompoundTag pdcTag = customData.getUnsafe().getCompound(PDC_CUSTOM_DATA_KEY).copy(); + container.putAll(pdcTag); + consumer.accept(container); + + final CompoundTag newPdcTag = container.toTagCompound(); + if (!newPdcTag.isEmpty()) { + customData = customData.update(tag -> tag.put(PDC_CUSTOM_DATA_KEY, newPdcTag)); + } else if (newPdcTag.isEmpty() && customData.contains(PDC_CUSTOM_DATA_KEY)) { + customData = customData.update(tag -> tag.remove(PDC_CUSTOM_DATA_KEY)); + } + + // mirror CraftMetaItem behavior of clearing component if it's empty. + this.handle.set(DataComponents.CUSTOM_DATA, customData.isEmpty() ? null : customData); + return true; + } + // Paper start - data component API @Override public T getData(final io.papermc.paper.datacomponent.DataComponentType.Valued type) { diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java b/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java index eb7d90cfa99f..595117a60e04 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java @@ -273,7 +273,7 @@ DataComponentPatch build() { static final ItemMetaKeyType MAX_DAMAGE = new ItemMetaKeyType<>(DataComponents.MAX_DAMAGE, "max-damage"); @Specific(Specific.To.NBT) static final ItemMetaKeyType BLOCK_DATA = new ItemMetaKeyType<>(DataComponents.BLOCK_STATE, "BlockStateTag"); - static final ItemMetaKey BUKKIT_CUSTOM_TAG = new ItemMetaKey("PublicBukkitValues"); + static final ItemMetaKey BUKKIT_CUSTOM_TAG = new ItemMetaKey(CraftItemStack.PDC_CUSTOM_DATA_KEY); @Specific(Specific.To.NBT) static final ItemMetaKeyType HIDE_ADDITIONAL_TOOLTIP = new ItemMetaKeyType(DataComponents.HIDE_ADDITIONAL_TOOLTIP); @Specific(Specific.To.NBT)