aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJake Potrebic <[email protected]>2024-04-27 12:17:58 -0700
committerJake Potrebic <[email protected]>2024-04-27 12:18:01 -0700
commitc82766d43601495e9d251dad0e210efc8d4b503e (patch)
tree1960ff8a754cccc5d1a2d466e5ce39fc50ce7be2
parentf037f0803530109d156ac462c4b42f05f1708cfc (diff)
downloadPaper-c82766d43601495e9d251dad0e210efc8d4b503e.tar.gz
Paper-c82766d43601495e9d251dad0e210efc8d4b503e.zip
fix item flags
-rw-r--r--patches/server/0792-Add-paper-dumplisteners-command.patch147
-rw-r--r--patches/server/1042-Add-experimental-improved-give-command.patch12
-rw-r--r--patches/server/1044-Fix-ItemFlag-HIDE_DESTROYES-HIDE_PLACED_ON.patch178
3 files changed, 331 insertions, 6 deletions
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<DataComponentType<?>> 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<Tag> ops = access.createSerializationContext(NbtOps.INSTANCE);
+ final Registry<DataComponentType<?>> registry = access.registryOrThrow(Registries.DATA_COMPONENT_TYPE);
+- if (!patch.isEmpty()) {
+- visualOutput.append(text("[", WHITE));
+- itemCommandBuilder.append("[");
+- final List<ComponentLike> componentComponents = new ArrayList<>();
+- final List<String> commandComponents = new ArrayList<>();
+- for (final Map.Entry<DataComponentType<?>, Optional<?>> entry : patch.entrySet()) {
+- final String path = registry.getResourceKey(entry.getKey()).orElseThrow().location().getPath();
+- if (entry.getValue().isEmpty()) {
++ final List<ComponentLike> componentComponents = new ArrayList<>();
++ final List<String> 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<Component> visualOutput, final Consumer<String> 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<String> 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<SuggestionsBuilder, CompletableFuture<Suggestions>> 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<net.minecraft.commands.CommandSourceStack> 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 <[email protected]>
+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<Unit> HIDE_ADDITIONAL_TOOLTIP = new ItemMetaKeyType(DataComponents.HIDE_ADDITIONAL_TOOLTIP);
+ @Specific(Specific.To.NBT)
+ static final ItemMetaKeyType<CustomData> CUSTOM_DATA = new ItemMetaKeyType<>(DataComponents.CUSTOM_DATA);
++ // Paper start - fix ItemFlags
++ static final ItemMetaKeyType<net.minecraft.world.item.AdventureModePredicate> CAN_PLACE_ON = new ItemMetaKeyType<>(DataComponents.CAN_PLACE_ON);
++ static final ItemMetaKeyType<net.minecraft.world.item.AdventureModePredicate> CAN_BREAK = new ItemMetaKeyType<>(DataComponents.CAN_BREAK);
++ private List<net.minecraft.advancements.critereon.BlockPredicate> canPlaceOnPredicates;
++ private List<net.minecraft.advancements.critereon.BlockPredicate> 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<Map.Entry<DataComponentType<?>, Optional<?>>> keys = tag.entrySet();
+ for (Map.Entry<DataComponentType<?>, 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<DataComponentType<?>, 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,