From c82766d43601495e9d251dad0e210efc8d4b503e Mon Sep 17 00:00:00 2001 From: Jake Potrebic Date: Sat, 27 Apr 2024 12:17:58 -0700 Subject: [PATCH] fix item flags --- ...0792-Add-paper-dumplisteners-command.patch | 147 +++++++++++++++ ...d-experimental-improved-give-command.patch | 12 +- ...emFlag-HIDE_DESTROYES-HIDE_PLACED_ON.patch | 178 ++++++++++++++++++ 3 files changed, 331 insertions(+), 6 deletions(-) create mode 100644 patches/server/1044-Fix-ItemFlag-HIDE_DESTROYES-HIDE_PLACED_ON.patch diff --git a/patches/server/0792-Add-paper-dumplisteners-command.patch b/patches/server/0792-Add-paper-dumplisteners-command.patch index ba293d1dad..4170206f59 100644 --- a/patches/server/0792-Add-paper-dumplisteners-command.patch +++ b/patches/server/0792-Add-paper-dumplisteners-command.patch @@ -17,6 +17,153 @@ index c9bb2df0d884227576ed8d2e72219bbbd7ba827e..534d9c380f26d6cce3c99fa88ad2e154 return commands.entrySet().stream() .flatMap(entry -> entry.getKey().stream().map(s -> Map.entry(s, entry.getValue()))) +diff --git a/src/main/java/io/papermc/paper/command/subcommands/DumpItemCommand.java b/src/main/java/io/papermc/paper/command/subcommands/DumpItemCommand.java +index efc4ec58ab4b3ceefd66b714d286a6187babc724..fae73bac86fea722be4c9178eda0779ea88d1884 100644 +--- a/src/main/java/io/papermc/paper/command/subcommands/DumpItemCommand.java ++++ b/src/main/java/io/papermc/paper/command/subcommands/DumpItemCommand.java +@@ -1,19 +1,26 @@ + package io.papermc.paper.command.subcommands; + + import io.papermc.paper.adventure.PaperAdventure; ++import io.papermc.paper.command.CommandUtil; + import io.papermc.paper.command.PaperSubcommand; + import java.util.ArrayList; ++import java.util.Collections; ++import java.util.IdentityHashMap; + import java.util.List; + import java.util.Map; + import java.util.Optional; ++import java.util.Set; ++import java.util.function.Consumer; + import net.kyori.adventure.text.Component; + import net.kyori.adventure.text.ComponentLike; + import net.kyori.adventure.text.JoinConfiguration; + import net.kyori.adventure.text.TextComponent; + import net.minecraft.core.Registry; + import net.minecraft.core.RegistryAccess; ++import net.minecraft.core.component.DataComponentMap; + import net.minecraft.core.component.DataComponentPatch; + import net.minecraft.core.component.DataComponentType; ++import net.minecraft.core.component.TypedDataComponent; + import net.minecraft.core.registries.Registries; + import net.minecraft.nbt.NbtOps; + import net.minecraft.nbt.NbtUtils; +@@ -25,6 +32,7 @@ import org.bukkit.craftbukkit.CraftServer; + import org.bukkit.craftbukkit.inventory.CraftItemStack; + import org.bukkit.entity.Player; + import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.checker.nullness.qual.Nullable; + import org.checkerframework.framework.qual.DefaultQualifier; + + import static net.kyori.adventure.text.Component.join; +@@ -36,17 +44,19 @@ import static net.kyori.adventure.text.format.NamedTextColor.GRAY; + import static net.kyori.adventure.text.format.NamedTextColor.RED; + import static net.kyori.adventure.text.format.NamedTextColor.WHITE; + import static net.kyori.adventure.text.format.NamedTextColor.YELLOW; ++import static net.kyori.adventure.text.format.TextColor.color; + import static net.kyori.adventure.text.format.TextDecoration.ITALIC; + + @DefaultQualifier(NonNull.class) + public final class DumpItemCommand implements PaperSubcommand { + @Override + public boolean execute(final CommandSender sender, final String subCommand, final String[] args) { +- this.doDumpItem(sender); ++ this.doDumpItem(sender, args.length > 0 && "all".equals(args[0])); + return true; + } + +- private void doDumpItem(final CommandSender sender) { ++ @SuppressWarnings({"unchecked", "OptionalAssignedToNull", "rawtypes"}) ++ private void doDumpItem(final CommandSender sender, final boolean includeAllComponents) { + if (!(sender instanceof final Player player)) { + sender.sendMessage("Only players can use this command"); + return; +@@ -57,38 +67,65 @@ public final class DumpItemCommand implements PaperSubcommand { + final String itemName = itemStack.getItemHolder().unwrapKey().orElseThrow().location().toString(); + itemCommandBuilder.append(itemName); + visualOutput.append(text(itemName, YELLOW)); // item type ++ final Set> referencedComponentTypes = Collections.newSetFromMap(new IdentityHashMap<>()); + final DataComponentPatch patch = itemStack.getComponentsPatch(); ++ referencedComponentTypes.addAll(patch.entrySet().stream().map(Map.Entry::getKey).toList()); ++ final DataComponentMap prototype = itemStack.getItem().components(); ++ if (includeAllComponents) { ++ referencedComponentTypes.addAll(prototype.keySet()); ++ } + + final RegistryAccess.Frozen access = ((CraftServer) sender.getServer()).getServer().registryAccess(); + final RegistryOps ops = access.createSerializationContext(NbtOps.INSTANCE); + final Registry> registry = access.registryOrThrow(Registries.DATA_COMPONENT_TYPE); +- if (!patch.isEmpty()) { +- visualOutput.append(text("[", WHITE)); +- itemCommandBuilder.append("["); +- final List componentComponents = new ArrayList<>(); +- final List commandComponents = new ArrayList<>(); +- for (final Map.Entry, Optional> entry : patch.entrySet()) { +- final String path = registry.getResourceKey(entry.getKey()).orElseThrow().location().getPath(); +- if (entry.getValue().isEmpty()) { ++ final List componentComponents = new ArrayList<>(); ++ final List commandComponents = new ArrayList<>(); ++ for (final DataComponentType type : referencedComponentTypes) { ++ final String path = registry.getResourceKey(type).orElseThrow().location().getPath(); ++ final @Nullable Optional patchedValue = patch.get(type); ++ final @Nullable TypedDataComponent prototypeValue = prototype.getTyped(type); ++ if (patchedValue != null) { ++ if (patchedValue.isEmpty()) { + componentComponents.add(text().append(text('!', RED), text(path, AQUA))); + commandComponents.add("!" + path); + } else { +- final Tag serialized = (Tag) ((DataComponentType) entry.getKey()).codecOrThrow().encodeStart(ops, entry.getValue().get()).getOrThrow(); +- componentComponents.add(textOfChildren( +- text(path, AQUA), +- text("=", WHITE), +- PaperAdventure.asAdventure(NbtUtils.toPrettyComponent(serialized)) +- )); +- commandComponents.add(path + "=" + serialized.getAsString()); ++ final Tag serialized = (Tag) ((DataComponentType) type).codecOrThrow().encodeStart(ops, patchedValue.get()).getOrThrow(); ++ writeComponentValue(componentComponents::add, commandComponents::add, path, serialized); + } +- ++ } else if (includeAllComponents && prototypeValue != null) { ++ final Tag serialized = prototypeValue.encodeValue(ops).getOrThrow(); ++ writeComponentValue(componentComponents::add, commandComponents::add, path, serialized); + } +- visualOutput +- .append(join(JoinConfiguration.separator(text(",", WHITE)), componentComponents)) +- .append(text("]", WHITE)); +- itemCommandBuilder.append(String.join(",", commandComponents)).append("]"); ++ } ++ if (!componentComponents.isEmpty()) { ++ visualOutput.append( ++ text("[", color(0x8910CE)), ++ join(JoinConfiguration.separator(text(",", GRAY)), componentComponents), ++ text("]", color(0x8910CE)) ++ ); ++ itemCommandBuilder ++ .append("[") ++ .append(String.join(",", commandComponents)) ++ .append("]"); + } + final Component hoverMsg = text("Click to copy item definition to clipboard for use with /pgive", GRAY, ITALIC); + player.sendMessage(visualOutput.build().compact().hoverEvent(hoverMsg).clickEvent(copyToClipboard(itemCommandBuilder.toString()))); + } ++ ++ private static void writeComponentValue(final Consumer visualOutput, final Consumer commandOutput, final String path, final Tag serialized) { ++ visualOutput.accept(textOfChildren( ++ text(path, color(0xFF7FD7)), ++ text("=", WHITE), ++ PaperAdventure.asAdventure(NbtUtils.toPrettyComponent(serialized)) ++ )); ++ commandOutput.accept(path + "=" + serialized.getAsString()); ++ } ++ ++ @Override ++ public List tabComplete(final CommandSender sender, final String subCommand, final String[] args) { ++ if (args.length == 1) { ++ return CommandUtil.getListMatchingLast(sender, args, "all"); ++ } ++ return Collections.emptyList(); ++ } + } diff --git a/src/main/java/io/papermc/paper/command/subcommands/DumpListenersCommand.java b/src/main/java/io/papermc/paper/command/subcommands/DumpListenersCommand.java new file mode 100644 index 0000000000000000000000000000000000000000..aa44d4685de3caee4131449bead7a084868ff976 diff --git a/patches/server/1042-Add-experimental-improved-give-command.patch b/patches/server/1042-Add-experimental-improved-give-command.patch index b7a7ef41c0..aaadb56694 100644 --- a/patches/server/1042-Add-experimental-improved-give-command.patch +++ b/patches/server/1042-Add-experimental-improved-give-command.patch @@ -183,7 +183,7 @@ index 5347a96be3bfbbd2963747ba4b5f222215d80371..fa431de18de902c580855e9c44191255 default void visitSuggestions(Function> suggestor) { } diff --git a/src/main/java/net/minecraft/server/commands/GiveCommand.java b/src/main/java/net/minecraft/server/commands/GiveCommand.java -index 0d9de4c61c7b26a6ff37c12fde629161fd0c3d5a..c738bbfa73888a0caed507e02f5c113f681e5cc2 100644 +index 0d9de4c61c7b26a6ff37c12fde629161fd0c3d5a..47355158e5e762540a10dc67b23092a0fc53bce3 100644 --- a/src/main/java/net/minecraft/server/commands/GiveCommand.java +++ b/src/main/java/net/minecraft/server/commands/GiveCommand.java @@ -34,6 +34,38 @@ public class GiveCommand { @@ -192,13 +192,13 @@ index 0d9de4c61c7b26a6ff37c12fde629161fd0c3d5a..c738bbfa73888a0caed507e02f5c113f }))))); + // Paper start - support component removals with a custom pgive command + final com.mojang.brigadier.tree.CommandNode node = net.minecraft.commands.Commands -+ .literal("pgive").requires((commandlistenerwrapper) -> commandlistenerwrapper.hasPermission(2)) ++ .literal("pgive").requires((css) -> css.hasPermission(2)) + .then(net.minecraft.commands.Commands.argument("targets", EntityArgument.players()) -+ .then(net.minecraft.commands.Commands.argument("item", new ItemArgument(commandRegistryAccess, true)).executes((commandcontext) -> { -+ return GiveCommand.giveItem((CommandSourceStack) commandcontext.getSource(), ItemArgument.getItem(commandcontext, "item"), EntityArgument.getPlayers(commandcontext, "targets"), 1); ++ .then(net.minecraft.commands.Commands.argument("item", new ItemArgument(commandRegistryAccess, true)).executes((ctx) -> { ++ return GiveCommand.giveItem(ctx.getSource(), ItemArgument.getItem(ctx, "item"), EntityArgument.getPlayers(ctx, "targets"), 1); + }) -+ .then(net.minecraft.commands.Commands.argument("count", IntegerArgumentType.integer(1)).executes((commandcontext) -> { -+ return GiveCommand.giveItem((CommandSourceStack) commandcontext.getSource(), ItemArgument.getItem(commandcontext, "item"), EntityArgument.getPlayers(commandcontext, "targets"), IntegerArgumentType.getInteger(commandcontext, "count")); ++ .then(net.minecraft.commands.Commands.argument("count", IntegerArgumentType.integer(1)).executes((ctx) -> { ++ return GiveCommand.giveItem(ctx.getSource(), ItemArgument.getItem(ctx, "item"), EntityArgument.getPlayers(ctx, "targets"), IntegerArgumentType.getInteger(ctx, "count")); + })) + ) + ).build(); diff --git a/patches/server/1044-Fix-ItemFlag-HIDE_DESTROYES-HIDE_PLACED_ON.patch b/patches/server/1044-Fix-ItemFlag-HIDE_DESTROYES-HIDE_PLACED_ON.patch new file mode 100644 index 0000000000..dd2167c3e8 --- /dev/null +++ b/patches/server/1044-Fix-ItemFlag-HIDE_DESTROYES-HIDE_PLACED_ON.patch @@ -0,0 +1,178 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sat, 27 Apr 2024 12:16:38 -0700 +Subject: [PATCH] Fix ItemFlag HIDE_DESTROYES & HIDE_PLACED_ON + +== AT == +public net.minecraft.world.item.AdventureModePredicate predicates + +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java +index 94e9213414ec08794e875c23c300bfae5dcc877e..940c2c9663657369eda6962728bd37a869209199 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java +@@ -224,6 +224,12 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + static final ItemMetaKeyType HIDE_ADDITIONAL_TOOLTIP = new ItemMetaKeyType(DataComponents.HIDE_ADDITIONAL_TOOLTIP); + @Specific(Specific.To.NBT) + static final ItemMetaKeyType CUSTOM_DATA = new ItemMetaKeyType<>(DataComponents.CUSTOM_DATA); ++ // Paper start - fix ItemFlags ++ static final ItemMetaKeyType CAN_PLACE_ON = new ItemMetaKeyType<>(DataComponents.CAN_PLACE_ON); ++ static final ItemMetaKeyType CAN_BREAK = new ItemMetaKeyType<>(DataComponents.CAN_BREAK); ++ private List canPlaceOnPredicates; ++ private List canBreakPredicates; ++ // Paper end - fix ItemFlags + + // We store the raw original JSON representation of all text data. See SPIGOT-5063, SPIGOT-5656, SPIGOT-5304 + private Component displayName; +@@ -296,6 +302,10 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + this.customTag = meta.customTag; + + this.version = meta.version; ++ // Paper start ++ this.canPlaceOnPredicates = meta.canPlaceOnPredicates; ++ this.canBreakPredicates = meta.canBreakPredicates; ++ // Paper end + } + + CraftMetaItem(DataComponentPatch tag) { +@@ -384,6 +394,20 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + } + } + }); ++ // Paper start - fix ItemFlags ++ CraftMetaItem.getOrEmpty(tag, CraftMetaItem.CAN_PLACE_ON).ifPresent(data -> { ++ this.canPlaceOnPredicates = List.copyOf(data.predicates); ++ if (!data.showInTooltip()) { ++ this.addItemFlags(ItemFlag.HIDE_PLACED_ON); ++ } ++ }); ++ CraftMetaItem.getOrEmpty(tag, CraftMetaItem.CAN_BREAK).ifPresent(data -> { ++ this.canBreakPredicates = List.copyOf(data.predicates); ++ if (!data.showInTooltip()) { ++ this.addItemFlags(ItemFlag.HIDE_DESTROYS); ++ } ++ }); ++ // Paper end - fix ItemFlags + + Set, Optional>> keys = tag.entrySet(); + for (Map.Entry, Optional> key : keys) { +@@ -565,10 +589,19 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + + String unhandled = SerializableMeta.getString(map, "unhandled", true); + if (unhandled != null) { +- ByteArrayInputStream buf = new ByteArrayInputStream(Base64.getDecoder().decode(internal)); ++ ByteArrayInputStream buf = new ByteArrayInputStream(Base64.getDecoder().decode(unhandled)); // Paper - fix deserializing unhandled tags + try { + CompoundTag unhandledTag = NbtIo.readCompressed(buf, NbtAccounter.unlimitedHeap()); +- this.unhandledTags.copy(DataComponentPatch.CODEC.parse(MinecraftServer.getDefaultRegistryAccess().createSerializationContext(NbtOps.INSTANCE), unhandledTag).result().get()); ++ // Paper start ++ final net.minecraft.core.component.DataComponentPatch patch = net.minecraft.core.component.DataComponentPatch.CODEC.parse(net.minecraft.server.MinecraftServer.getDefaultRegistryAccess().createSerializationContext(net.minecraft.nbt.NbtOps.INSTANCE), unhandledTag).result().get(); ++ CraftMetaItem.getOrEmpty(patch, CraftMetaItem.CAN_PLACE_ON).ifPresent(data -> { ++ this.canPlaceOnPredicates = List.copyOf(data.predicates); ++ }); ++ CraftMetaItem.getOrEmpty(patch, CraftMetaItem.CAN_BREAK).ifPresent(data -> { ++ this.canBreakPredicates = List.copyOf(data.predicates); ++ }); ++ this.unhandledTags.copy(patch.forget(type -> type == CraftMetaItem.CAN_PLACE_ON.TYPE || type == CraftMetaItem.CAN_BREAK.TYPE)); ++ // Paper end + } catch (IOException ex) { + Logger.getLogger(CraftMetaItem.class.getName()).log(Level.SEVERE, null, ex); + } +@@ -786,6 +819,15 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + itemTag.put(CraftMetaItem.MAX_DAMAGE, this.maxDamage); + } + ++ // Paper start ++ if (this.canPlaceOnPredicates != null || this.hasItemFlag(ItemFlag.HIDE_PLACED_ON)) { ++ itemTag.put(CraftMetaItem.CAN_PLACE_ON, new net.minecraft.world.item.AdventureModePredicate(this.canPlaceOnPredicates != null ? this.canPlaceOnPredicates : Collections.emptyList(), !this.hasItemFlag(ItemFlag.HIDE_PLACED_ON))); ++ } ++ if (this.canBreakPredicates != null || this.hasItemFlag(ItemFlag.HIDE_DESTROYS)) { ++ itemTag.put(CraftMetaItem.CAN_BREAK, new net.minecraft.world.item.AdventureModePredicate(this.canBreakPredicates != null ? this.canBreakPredicates : Collections.emptyList(), !this.hasItemFlag(ItemFlag.HIDE_DESTROYS))); ++ } ++ // Paper end ++ + for (Map.Entry, Optional> e : this.unhandledTags.build().entrySet()) { + e.getValue().ifPresentOrElse((value) -> { + itemTag.builder.set((DataComponentType) e.getKey(), value); +@@ -858,7 +900,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.persistentDataContainer.isEmpty() || this.hideFlag != 0 || this.isHideTooltip() || this.isUnbreakable() || this.hasEnchantmentGlintOverride() || this.isFireResistant() || this.hasMaxStackSize() || this.hasRarity() || this.hasFood() || this.hasDamage() || this.hasMaxDamage() || this.hasAttributeModifiers() || this.customTag != null); ++ return !(this.hasDisplayName() || this.hasItemName() || this.hasLocalizedName() || this.hasEnchants() || (this.lore != null) || this.hasCustomModelData() || this.hasBlockData() || this.hasRepairCost() || !this.unhandledTags.build().isEmpty() || !this.persistentDataContainer.isEmpty() || this.hideFlag != 0 || this.isHideTooltip() || this.isUnbreakable() || this.hasEnchantmentGlintOverride() || this.isFireResistant() || this.hasMaxStackSize() || this.hasRarity() || this.hasFood() || this.hasDamage() || this.hasMaxDamage() || this.hasAttributeModifiers() || this.customTag != null || this.canPlaceOnPredicates != null || this.canBreakPredicates != null); // Paper + } + + // Paper start +@@ -1453,6 +1495,8 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + && (this.hasFood() ? that.hasFood() && this.food.equals(that.food) : !that.hasFood()) + && (this.hasDamage() ? that.hasDamage() && this.damage == that.damage : !that.hasDamage()) + && (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 + && (this.version == that.version); + } + +@@ -1495,6 +1539,8 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + 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.canPlaceOnPredicates != null ? this.canPlaceOnPredicates.hashCode() : 0); // Paper ++ hash = 61 * hash + (this.canBreakPredicates != null ? this.canBreakPredicates.hashCode() : 0); // Paper + hash = 61 * hash + this.version; + return hash; + } +@@ -1532,6 +1578,14 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + clone.damage = this.damage; + clone.maxDamage = this.maxDamage; + clone.version = this.version; ++ // Paper start ++ if (this.canPlaceOnPredicates != null) { ++ clone.canPlaceOnPredicates = List.copyOf(this.canPlaceOnPredicates); ++ } ++ if (this.canBreakPredicates != null) { ++ clone.canBreakPredicates = List.copyOf(this.canBreakPredicates); ++ } ++ // Paper end + return clone; + } catch (CloneNotSupportedException e) { + throw new Error(e); +@@ -1641,6 +1695,16 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + } + } + ++ // Paper start ++ final boolean canBreakAddToUnhandled = this.canBreakPredicates != null || this.hasItemFlag(ItemFlag.HIDE_DESTROYS); ++ if (canBreakAddToUnhandled) { ++ this.unhandledTags.set(DataComponents.CAN_BREAK, new net.minecraft.world.item.AdventureModePredicate(this.canBreakPredicates != null ? this.canBreakPredicates : Collections.emptyList(), !this.hasItemFlag(ItemFlag.HIDE_DESTROYS))); ++ } ++ final boolean canPlaceOnAddToUnhandled = this.canPlaceOnPredicates != null || this.hasItemFlag(ItemFlag.HIDE_PLACED_ON); ++ if (canPlaceOnAddToUnhandled) { ++ this.unhandledTags.set(DataComponents.CAN_PLACE_ON, new net.minecraft.world.item.AdventureModePredicate(this.canPlaceOnPredicates != null ? this.canPlaceOnPredicates : Collections.emptyList(), !this.hasItemFlag(ItemFlag.HIDE_PLACED_ON))); ++ } ++ // Paper end + if (!this.unhandledTags.isEmpty()) { + Tag unhandled = DataComponentPatch.CODEC.encodeStart(MinecraftServer.getDefaultRegistryAccess().createSerializationContext(NbtOps.INSTANCE), this.unhandledTags.build()).getOrThrow(IllegalStateException::new); + try { +@@ -1651,6 +1715,14 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + Logger.getLogger(CraftMetaItem.class.getName()).log(Level.SEVERE, null, ex); + } + } ++ // Paper start ++ if (canBreakAddToUnhandled) { ++ this.unhandledTags.clear(DataComponents.CAN_BREAK); ++ } ++ if (canPlaceOnAddToUnhandled) { ++ this.unhandledTags.clear(DataComponents.CAN_PLACE_ON); ++ } ++ // Paper end + + if (!this.persistentDataContainer.isEmpty()) { // Store custom tags, wrapped in their compound + builder.put(CraftMetaItem.BUKKIT_CUSTOM_TAG.BUKKIT, this.persistentDataContainer.serialize()); +@@ -1792,6 +1864,8 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + CraftMetaItem.MAX_DAMAGE.TYPE, + CraftMetaItem.CUSTOM_DATA.TYPE, + CraftMetaItem.ATTRIBUTES.TYPE, ++ CraftMetaItem.CAN_PLACE_ON.TYPE, // Paper ++ CraftMetaItem.CAN_BREAK.TYPE, // Paper + CraftMetaArmor.TRIM.TYPE, + CraftMetaArmorStand.ENTITY_TAG.TYPE, + CraftMetaBanner.PATTERNS.TYPE,