diff --git a/Spigot-API-Patches/0097-Expand-World.spawnParticle-API-and-add-Builder.patch b/Spigot-API-Patches/0097-Expand-World.spawnParticle-API-and-add-Builder.patch index e2c20c0739..69dfbafeb2 100644 --- a/Spigot-API-Patches/0097-Expand-World.spawnParticle-API-and-add-Builder.patch +++ b/Spigot-API-Patches/0097-Expand-World.spawnParticle-API-and-add-Builder.patch @@ -1,4 +1,4 @@ -From 43beb0d7397bc0067f08e4ec41ddfb4d46145f7d Mon Sep 17 00:00:00 2001 +From 9688d6dab34b7bac4f43b9c390fb582772b04b06 Mon Sep 17 00:00:00 2001 From: Aikar Date: Tue, 29 Aug 2017 23:58:48 -0400 Subject: [PATCH] Expand World.spawnParticle API and add Builder diff --git a/Spigot-API-Patches/0151-Add-an-API-for-CanPlaceOn-and-CanDestroy-NBT-values.patch b/Spigot-API-Patches/0151-Add-an-API-for-CanPlaceOn-and-CanDestroy-NBT-values.patch index 535addd603..20dcab3aae 100644 --- a/Spigot-API-Patches/0151-Add-an-API-for-CanPlaceOn-and-CanDestroy-NBT-values.patch +++ b/Spigot-API-Patches/0151-Add-an-API-for-CanPlaceOn-and-CanDestroy-NBT-values.patch @@ -1,14 +1,226 @@ -From 7ac07ac07ac07ac07ac07ac07ac07ac07ac07ac0 Mon Sep 17 00:00:00 2001 +From 89e9f2e1093c104637f4bfd282c774b88b1bccc8 Mon Sep 17 00:00:00 2001 From: Mark Vainomaa Date: Wed, 12 Sep 2018 18:53:35 +0300 Subject: [PATCH] Add an API for CanPlaceOn and CanDestroy NBT values +diff --git a/src/main/java/com/destroystokyo/paper/Namespaced.java b/src/main/java/com/destroystokyo/paper/Namespaced.java +new file mode 100644 +index 00000000..2baf58b7 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/Namespaced.java +@@ -0,0 +1,36 @@ ++package com.destroystokyo.paper; ++ ++/** ++ * Represents a namespaced resource, see {@link org.bukkit.NamespacedKey} for single elements ++ * or {@link com.destroystokyo.paper.NamespacedTag} for a collection of elements ++ * ++ * Namespaces may only contain lowercase alphanumeric characters, periods, ++ * underscores, and hyphens. ++ *

++ * Keys may only contain lowercase alphanumeric characters, periods, ++ * underscores, hyphens, and forward slashes. ++ *

++ * You should not be implementing this interface yourself, use {@link org.bukkit.NamespacedKey} ++ * or {@link com.destroystokyo.paper.NamespacedTag} as needed instead. ++ */ ++public interface Namespaced { ++ /** ++ * Gets the namespace this resource is a part of ++ *

++ * This is contractually obligated to only contain lowercase alphanumeric characters, ++ * periods, underscores, and hyphens. ++ * ++ * @return resource namespace ++ */ ++ String getNamespace(); ++ ++ /** ++ * Gets the key corresponding to this resource ++ *

++ * This is contractually obligated to only contain lowercase alphanumeric characters, ++ * periods, underscores, hyphens, and forward slashes. ++ * ++ * @return resource key ++ */ ++ String getKey(); ++} +diff --git a/src/main/java/com/destroystokyo/paper/NamespacedTag.java b/src/main/java/com/destroystokyo/paper/NamespacedTag.java +new file mode 100644 +index 00000000..89949827 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/NamespacedTag.java +@@ -0,0 +1,138 @@ ++package com.destroystokyo.paper; ++ ++import com.google.common.base.Preconditions; ++import java.util.Locale; ++import java.util.UUID; ++import java.util.regex.Pattern; ++import org.bukkit.plugin.Plugin; ++ ++/** ++ * Represents a String based key pertaining to a tagged entry. Consists of two components - a namespace ++ * and a key. ++ *

++ * Namespaces may only contain lowercase alphanumeric characters, periods, ++ * underscores, and hyphens. ++ *

++ * Keys may only contain lowercase alphanumeric characters, periods, ++ * underscores, hyphens, and forward slashes. ++ * ++ */ ++// Paper - entire class, based on org.bukkit.NamespacedKey ++public final class NamespacedTag implements com.destroystokyo.paper.Namespaced { ++ ++ /** ++ * The namespace representing all inbuilt keys. ++ */ ++ public static final String MINECRAFT = "minecraft"; ++ /** ++ * The namespace representing all keys generated by Bukkit for backwards ++ * compatibility measures. ++ */ ++ public static final String BUKKIT = "bukkit"; ++ // ++ private static final Pattern VALID_NAMESPACE = Pattern.compile("[a-z0-9._-]+"); ++ private static final Pattern VALID_KEY = Pattern.compile("[a-z0-9/._-]+"); ++ // ++ private final String namespace; ++ private final String key; ++ ++ /** ++ * Create a key in a specific namespace. ++ * ++ * @param namespace String representing a grouping of keys ++ * @param key Name for this specific key ++ * @deprecated should never be used by plugins, for internal use only!! ++ */ ++ @Deprecated ++ public NamespacedTag(String namespace, String key) { ++ Preconditions.checkArgument(namespace != null && VALID_NAMESPACE.matcher(namespace).matches(), "Invalid namespace. Must be [a-z0-9._-]: %s", namespace); ++ Preconditions.checkArgument(key != null && VALID_KEY.matcher(key).matches(), "Invalid key. Must be [a-z0-9/._-]: %s", key); ++ ++ this.namespace = namespace; ++ this.key = key; ++ ++ String string = toString(); ++ Preconditions.checkArgument(string.length() < 256, "NamespacedTag must be less than 256 characters", string); ++ } ++ ++ /** ++ * Create a key in the plugin's namespace. ++ *

++ * Namespaces may only contain lowercase alphanumeric characters, periods, ++ * underscores, and hyphens. ++ *

++ * Keys may only contain lowercase alphanumeric characters, periods, ++ * underscores, hyphens, and forward slashes. ++ * ++ * @param plugin the plugin to use for the namespace ++ * @param key the key to create ++ */ ++ public NamespacedTag(Plugin plugin, String key) { ++ Preconditions.checkArgument(plugin != null, "Plugin cannot be null"); ++ Preconditions.checkArgument(key != null, "Key cannot be null"); ++ ++ this.namespace = plugin.getName().toLowerCase(Locale.ROOT); ++ this.key = key.toLowerCase().toLowerCase(Locale.ROOT); ++ ++ // Check validity after normalization ++ Preconditions.checkArgument(VALID_NAMESPACE.matcher(this.namespace).matches(), "Invalid namespace. Must be [a-z0-9._-]: %s", this.namespace); ++ Preconditions.checkArgument(VALID_KEY.matcher(this.key).matches(), "Invalid key. Must be [a-z0-9/._-]: %s", this.key); ++ ++ String string = toString(); ++ Preconditions.checkArgument(string.length() < 256, "NamespacedTag must be less than 256 characters (%s)", string); ++ } ++ ++ public String getNamespace() { ++ return namespace; ++ } ++ ++ public String getKey() { ++ return key; ++ } ++ ++ @Override ++ public int hashCode() { ++ int hash = 7; ++ hash = 47 * hash + this.namespace.hashCode(); ++ hash = 47 * hash + this.key.hashCode(); ++ return hash; ++ } ++ ++ @Override ++ public boolean equals(Object obj) { ++ if (obj == null) { ++ return false; ++ } ++ if (getClass() != obj.getClass()) { ++ return false; ++ } ++ final NamespacedTag other = (NamespacedTag) obj; ++ return this.namespace.equals(other.namespace) && this.key.equals(other.key); ++ } ++ ++ @Override ++ public String toString() { ++ return "#" + this.namespace + ":" + this.key; ++ } ++ ++ /** ++ * Return a new random key in the {@link #BUKKIT} namespace. ++ * ++ * @return new key ++ * @deprecated should never be used by plugins, for internal use only!! ++ */ ++ @Deprecated ++ public static NamespacedTag randomKey() { ++ return new NamespacedTag(BUKKIT, UUID.randomUUID().toString()); ++ } ++ ++ /** ++ * Get a key in the Minecraft namespace. ++ * ++ * @param key the key to use ++ * @return new key in the Minecraft namespace ++ */ ++ public static NamespacedTag minecraft(String key) { ++ return new NamespacedTag(MINECRAFT, key); ++ } ++} +diff --git a/src/main/java/org/bukkit/NamespacedKey.java b/src/main/java/org/bukkit/NamespacedKey.java +index fe8d3468..074769c1 100644 +--- a/src/main/java/org/bukkit/NamespacedKey.java ++++ b/src/main/java/org/bukkit/NamespacedKey.java +@@ -17,7 +17,7 @@ import org.bukkit.plugin.Plugin; + * underscores, hyphens, and forward slashes. + * + */ +-public final class NamespacedKey { ++public final class NamespacedKey implements com.destroystokyo.paper.Namespaced { // Paper - implement namespaced + + /** + * The namespace representing all inbuilt keys. +@@ -81,10 +81,12 @@ public final class NamespacedKey { + Preconditions.checkArgument(string.length() < 256, "NamespacedKey must be less than 256 characters (%s)", string); + } + ++ @Override // Paper + public String getNamespace() { + return namespace; + } + ++ @Override // Paper + public String getKey() { + return key; + } diff --git a/src/main/java/org/bukkit/inventory/meta/ItemMeta.java b/src/main/java/org/bukkit/inventory/meta/ItemMeta.java -index 7ac07ac07ac0..7ac07ac07ac0 100644 +index 2278d470..13a153c8 100644 --- a/src/main/java/org/bukkit/inventory/meta/ItemMeta.java +++ b/src/main/java/org/bukkit/inventory/meta/ItemMeta.java -@@ -348,4 +348,33 @@ public interface ItemMeta extends Cloneable, ConfigurationSerializable { +@@ -348,4 +348,83 @@ public interface ItemMeta extends Cloneable, ConfigurationSerializable { Spigot spigot(); // Spigot end @@ -17,29 +229,79 @@ index 7ac07ac07ac0..7ac07ac07ac0 100644 + * Gets set of materials what given item can destroy in {@link org.bukkit.GameMode#ADVENTURE} + * + * @return Set of materials ++ * @deprecated Minecraft does not limit this to the material enum, Use {@link #getDestroyableKeys()} as a replacement + */ ++ @Deprecated + Set getCanDestroy(); + + /** + * Sets set of materials what given item can destroy in {@link org.bukkit.GameMode#ADVENTURE} + * + * @param canDestroy Set of materials ++ * @deprecated Minecraft does not limit this to the material enum, Use {@link #setDestroyableKeys(Collection)} as a replacement + */ ++ @Deprecated + void setCanDestroy(Set canDestroy); + + /** + * Gets set of materials where given item can be placed on in {@link org.bukkit.GameMode#ADVENTURE} + * + * @return Set of materials ++ * @deprecated Minecraft does not limit this to the material enum, Use {@link #getPlaceableKeys()} as a replacement + */ ++ @Deprecated + Set getCanPlaceOn(); + + /** + * Sets set of materials where given item can be placed on in {@link org.bukkit.GameMode#ADVENTURE} + * + * @param canPlaceOn Set of materials ++ * @deprecated Minecraft does not limit this to the material enum, Use {@link #setPlaceableKeys(Collection)} as a replacement + */ ++ @Deprecated + void setCanPlaceOn(Set canPlaceOn); ++ ++ /** ++ * Gets the collection of namespaced keys that the item can destroy in {@link org.bukkit.GameMode#ADVENTURE} ++ * ++ * @return Set of {@link com.destroystokyo.paper.Namespaced} ++ */ ++ Set getDestroyableKeys(); ++ ++ /** ++ * Sets the collection of namespaced keys that the item can destroy in {@link org.bukkit.GameMode#ADVENTURE} ++ * ++ * @param canDestroy Set of {@link com.destroystokyo.paper.Namespaced} ++ */ ++ void setDestroyableKeys(Collection canDestroy); ++ ++ /** ++ * Gets the collection of namespaced keys that the item can be placed on in {@link org.bukkit.GameMode#ADVENTURE} ++ * ++ * @return Set of {@link com.destroystokyo.paper.Namespaced} ++ */ ++ Set getPlaceableKeys(); ++ ++ /** ++ * Sets the set of namespaced keys that the item can be placed on in {@link org.bukkit.GameMode#ADVENTURE} ++ * ++ * @param canPlaceOn Set of {@link Collection} ++ */ ++ void setPlaceableKeys(Collection canPlaceOn); ++ ++ /** ++ * Checks for the existence of any keys that the item can be placed on ++ * ++ * @return true if this item has placeable keys ++ */ ++ boolean hasPlaceableKeys(); ++ ++ /** ++ * Checks for the existence of any keys that the item can destroy ++ * ++ * @return true if this item has destroyable keys ++ */ ++ boolean hasDestroyableKeys(); + // Paper end } -- diff --git a/Spigot-Server-Patches/0231-Add-ArmorStand-Item-Meta.patch b/Spigot-Server-Patches/0231-Add-ArmorStand-Item-Meta.patch index 50ac8bd910..9c12894b8f 100644 --- a/Spigot-Server-Patches/0231-Add-ArmorStand-Item-Meta.patch +++ b/Spigot-Server-Patches/0231-Add-ArmorStand-Item-Meta.patch @@ -1,4 +1,4 @@ -From 0ef5b7e0531f5bf10aa78276ce5bc886c820f81c Mon Sep 17 00:00:00 2001 +From 94d320edecd9da9e43c9720be5cd42d71a89bdad Mon Sep 17 00:00:00 2001 From: Zach Brown Date: Sat, 27 Jan 2018 17:04:14 -0500 Subject: [PATCH] Add ArmorStand Item Meta @@ -354,7 +354,7 @@ index 000000000..0e8acf12e + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java -index 081904dad..6a95f5fa3 100644 +index 081904dad..dacca4bc4 100644 --- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java @@ -152,6 +152,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable { @@ -370,8 +370,8 @@ index 081904dad..6a95f5fa3 100644 CraftMetaBlockState.BLOCK_ENTITY_TAG.NBT, CraftMetaKnowledgeBook.BOOK_RECIPES.NBT, - CraftMetaTropicalFishBucket.VARIANT.NBT -+ // Paper start + CraftMetaTropicalFishBucket.VARIANT.NBT, ++ // Paper start + CraftMetaArmorStand.ENTITY_TAG.NBT, + CraftMetaArmorStand.INVISIBLE.NBT, + CraftMetaArmorStand.NO_BASE_PLATE.NBT, diff --git a/Spigot-Server-Patches/0359-Implement-an-API-for-CanPlaceOn-and-CanDestroy-NBT-v.patch b/Spigot-Server-Patches/0359-Implement-an-API-for-CanPlaceOn-and-CanDestroy-NBT-v.patch index 800174c569..1a33d7313b 100644 --- a/Spigot-Server-Patches/0359-Implement-an-API-for-CanPlaceOn-and-CanDestroy-NBT-v.patch +++ b/Spigot-Server-Patches/0359-Implement-an-API-for-CanPlaceOn-and-CanDestroy-NBT-v.patch @@ -1,98 +1,246 @@ -From 7ac07ac07ac07ac07ac07ac07ac07ac07ac07ac0 Mon Sep 17 00:00:00 2001 +From 18054d7c3c094b5f2977a5affe5d96376dad120b Mon Sep 17 00:00:00 2001 From: Mark Vainomaa Date: Wed, 12 Sep 2018 18:53:55 +0300 Subject: [PATCH] Implement an API for CanPlaceOn and CanDestroy NBT values +diff --git a/src/main/java/net/minecraft/server/ArgumentBlock.java b/src/main/java/net/minecraft/server/ArgumentBlock.java +index 35c436d19..fcfb17e4e 100644 +--- a/src/main/java/net/minecraft/server/ArgumentBlock.java ++++ b/src/main/java/net/minecraft/server/ArgumentBlock.java +@@ -42,7 +42,7 @@ public class ArgumentBlock { + private final boolean j; + private final Map, Comparable> k = Maps.newHashMap(); + private final Map l = Maps.newHashMap(); +- private MinecraftKey m = new MinecraftKey(""); ++ private MinecraftKey m = new MinecraftKey(""); public MinecraftKey getBlockKey() { return this.m; } // Paper - OBFHELPER + private BlockStateList n; + private IBlockData o; + @Nullable +@@ -71,11 +71,13 @@ public class ArgumentBlock { + return this.p; + } + ++ public @Nullable MinecraftKey getTagKey() { return d(); } // Paper - OBFHELPER + @Nullable + public MinecraftKey d() { + return this.q; + } + ++ public ArgumentBlock parse(boolean parseTile) throws CommandSyntaxException { return this.a(parseTile); } // Paper - OBFHELPER + public ArgumentBlock a(boolean flag) throws CommandSyntaxException { + this.s = this::l; + if (this.i.canRead() && this.i.peek() == '#') { +@@ -135,7 +137,7 @@ public class ArgumentBlock { + if (this.q != null && !this.q.getKey().isEmpty()) { + Tag tag = TagsBlock.a().a(this.q); + if (tag != null) { +- for(Block block : tag.a()) { ++ for(Block block : (java.util.Collection) tag.a()) { // Paper - decompiler fix + for(IBlockState iblockstate : block.getStates().d()) { + if (!this.l.containsKey(iblockstate.a()) && iblockstate.a().startsWith(sx)) { + suggestionsbuilder.suggest(iblockstate.a() + '='); +@@ -163,7 +165,7 @@ public class ArgumentBlock { + if (this.q != null) { + Tag tag = TagsBlock.a().a(this.q); + if (tag != null) { +- for(Block block : tag.a()) { ++ for(Block block : (java.util.Collection) tag.a()) { // Paper - decompiler fix + if (block.isTileEntity()) { + return true; + } +@@ -198,9 +200,9 @@ public class ArgumentBlock { + private static > SuggestionsBuilder a(SuggestionsBuilder suggestionsbuilder, IBlockState iblockstate) { + for(Comparable comparable : iblockstate.d()) { + if (comparable instanceof Integer) { +- suggestionsbuilder.suggest(comparable); ++ suggestionsbuilder.suggest((Integer) comparable); // Paper - decompiler fix + } else { +- suggestionsbuilder.suggest(iblockstate.a(comparable)); ++ suggestionsbuilder.suggest(iblockstate.a((T) comparable)); // Paper - decompiler fix + } + } + +@@ -213,7 +215,7 @@ public class ArgumentBlock { + Tag tag = TagsBlock.a().a(this.q); + if (tag != null) { + label40: +- for(Block block : tag.a()) { ++ for(Block block : (java.util.Collection) tag.a()) { // Paper - decompiler fix + IBlockState iblockstate = block.getStates().a(sx); + if (iblockstate != null) { + a(suggestionsbuilder, iblockstate); +@@ -254,7 +256,7 @@ public class ArgumentBlock { + boolean flag = false; + boolean flag1 = false; + +- for(Block block : tag.a()) { ++ for(Block block : (java.util.Collection) tag.a()) { // Paper - decompiler fix + flag |= !block.getStates().d().isEmpty(); + flag1 |= block.isTileEntity(); + if (flag && flag1) { +@@ -453,8 +455,8 @@ public class ArgumentBlock { + private > void a(IBlockState iblockstate, String sx, int ix) throws CommandSyntaxException { + Optional optional = iblockstate.b(sx); + if (optional.isPresent()) { +- this.o = (IBlockData)this.o.set(iblockstate, (Comparable)optional.get()); +- this.k.put(iblockstate, optional.get()); ++ this.o = (IBlockData)this.o.set(iblockstate, (T)optional.get()); // Paper - decompiler fix ++ this.k.put(iblockstate, (Comparable) optional.get()); // Paper - decompiler fix + } else { + this.i.setCursor(ix); + throw e.createWithContext(this.i, this.m.toString(), iblockstate.a(), sx); +@@ -489,7 +491,7 @@ public class ArgumentBlock { + private static > void a(StringBuilder stringbuilder, IBlockState iblockstate, Comparable comparable) { + stringbuilder.append(iblockstate.a()); + stringbuilder.append('='); +- stringbuilder.append(iblockstate.a(comparable)); ++ stringbuilder.append(iblockstate.a((T) comparable)); // Paper - decompile fix + } + + public CompletableFuture a(SuggestionsBuilder suggestionsbuilder) { diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java -index 7ac07ac07ac0..7ac07ac07ac0 100644 +index dacca4bc4..0b040527f 100644 --- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java -@@ -252,6 +252,12 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable { +@@ -78,6 +78,12 @@ import javax.annotation.Nullable; + import static org.spigotmc.ValidateUtils.*; + // Spigot end + ++// Paper start ++import com.destroystokyo.paper.Namespaced; ++import com.destroystokyo.paper.NamespacedTag; ++import java.util.Collections; ++// Paper end ++ + /** + * Children must include the following: + * +@@ -252,6 +258,10 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable { static final ItemMetaKey UNBREAKABLE = new ItemMetaKey("Unbreakable"); @Specific(Specific.To.NBT) static final ItemMetaKey DAMAGE = new ItemMetaKey("Damage"); + // Paper start - Implement an API for CanPlaceOn and CanDestroy NBT values -+ @Specific(Specific.To.NBT) + static final ItemMetaKey CAN_DESTROY = new ItemMetaKey("CanDestroy"); -+ @Specific(Specific.To.NBT) + static final ItemMetaKey CAN_PLACE_ON = new ItemMetaKey("CanPlaceOn"); + // Paper end private IChatBaseComponent displayName; private IChatBaseComponent locName; -@@ -262,6 +268,10 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable { +@@ -262,6 +272,10 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable { private int hideFlag; private boolean unbreakable; private int damage; + // Paper start - Implement an API for CanPlaceOn and CanDestroy NBT values -+ private Set canPlaceOn = Sets.newHashSet(); -+ private Set canDestroy = Sets.newHashSet(); ++ private Set placeableKeys; ++ private Set destroyableKeys; + // Paper end private static final Set HANDLED_TAGS = Sets.newHashSet(); -@@ -292,6 +302,10 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable { +@@ -292,6 +306,15 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable { this.hideFlag = meta.hideFlag; this.unbreakable = meta.unbreakable; this.damage = meta.damage; + // Paper start - Implement an API for CanPlaceOn and CanDestroy NBT values -+ this.canDestroy = new java.util.HashSet<>(meta.canDestroy); -+ this.canPlaceOn = new java.util.HashSet<>(meta.canPlaceOn); ++ if (meta.hasPlaceableKeys()) { ++ this.placeableKeys = new java.util.HashSet<>(meta.placeableKeys); ++ } ++ ++ if (meta.hasDestroyableKeys()) { ++ this.destroyableKeys = new java.util.HashSet<>(meta.destroyableKeys); ++ } + // Paper end this.unhandledTags.putAll(meta.unhandledTags); this.internalTag = meta.internalTag; -@@ -347,6 +361,31 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable { +@@ -347,6 +370,33 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable { if (tag.hasKey(DAMAGE.NBT)) { damage = tag.getInt(DAMAGE.NBT); } + // Paper start - Implement an API for CanPlaceOn and CanDestroy NBT values + if (tag.hasKey(CAN_DESTROY.NBT)) { ++ this.destroyableKeys = Sets.newHashSet(); + NBTTagList list = tag.getList(CAN_DESTROY.NBT, CraftMagicNumbers.NBT.TAG_STRING); + for (int i = 0; i < list.size(); i++) { -+ Material material = Material.matchMaterial(list.getString(i), false); -+ if (material == null) { ++ Namespaced namespaced = this.deserializeNamespaced(list.getString(i)); ++ if (namespaced == null) { + continue; + } + -+ this.canDestroy.add(material); ++ this.destroyableKeys.add(namespaced); + } + } + + if (tag.hasKey(CAN_PLACE_ON.NBT)) { ++ this.placeableKeys = Sets.newHashSet(); + NBTTagList list = tag.getList(CAN_PLACE_ON.NBT, CraftMagicNumbers.NBT.TAG_STRING); + for (int i = 0; i < list.size(); i++) { -+ Material material = Material.matchMaterial(list.getString(i), false); -+ if (material == null) { ++ Namespaced namespaced = this.deserializeNamespaced(list.getString(i)); ++ if (namespaced == null) { + continue; + } + -+ this.canPlaceOn.add(material); ++ this.placeableKeys.add(namespaced); + } + } + // Paper end Set keys = tag.getKeys(); for (String key : keys) { -@@ -579,6 +618,25 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable { +@@ -468,6 +518,36 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable { + setDamage(damage); + } + ++ // Paper start - Implement an API for CanPlaceOn and CanDestroy NBT values ++ Iterable canPlaceOnSerialized = SerializableMeta.getObject(Iterable.class, map, CAN_PLACE_ON.BUKKIT, true); ++ if (canPlaceOnSerialized != null) { ++ this.placeableKeys = Sets.newHashSet(); ++ for (Object canPlaceOnElement : canPlaceOnSerialized) { ++ String canPlaceOnRaw = (String) canPlaceOnElement; ++ Namespaced value = this.deserializeNamespaced(canPlaceOnRaw); ++ if (value == null) { ++ continue; ++ } ++ ++ this.placeableKeys.add(value); ++ } ++ } ++ ++ Iterable canDestroySerialized = SerializableMeta.getObject(Iterable.class, map, CAN_DESTROY.BUKKIT, true); ++ if (canDestroySerialized != null) { ++ this.destroyableKeys = Sets.newHashSet(); ++ for (Object canDestroyElement : canDestroySerialized) { ++ String canDestroyRaw = (String) canDestroyElement; ++ Namespaced value = this.deserializeNamespaced(canDestroyRaw); ++ if (value == null) { ++ continue; ++ } ++ ++ this.destroyableKeys.add(value); ++ } ++ } ++ // Paper end ++ + String internal = SerializableMeta.getString(map, "internal", true); + if (internal != null) { + ByteArrayInputStream buf = new ByteArrayInputStream(Base64.decodeBase64(internal)); +@@ -579,6 +659,23 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable { if (hasDamage()) { itemTag.setInt(DAMAGE.NBT, damage); } + // Paper start - Implement an API for CanPlaceOn and CanDestroy NBT values -+ if (!this.canPlaceOn.isEmpty()) { -+ List items = this.canPlaceOn.stream() -+ .map(Material::getKey) -+ .map(org.bukkit.NamespacedKey::toString) ++ if (hasPlaceableKeys()) { ++ List items = this.placeableKeys.stream() ++ .map(this::serializeNamespaced) + .collect(java.util.stream.Collectors.toList()); + + itemTag.set(CAN_PLACE_ON.NBT, createStringList(items)); + } + -+ if (!this.canDestroy.isEmpty()) { -+ List items = this.canDestroy.stream() -+ .map(Material::getKey) -+ .map(org.bukkit.NamespacedKey::toString) ++ if (hasDestroyableKeys()) { ++ List items = this.destroyableKeys.stream() ++ .map(this::serializeNamespaced) + .collect(java.util.stream.Collectors.toList()); + + itemTag.set(CAN_DESTROY.NBT, createStringList(items)); @@ -101,7 +249,82 @@ index 7ac07ac07ac0..7ac07ac07ac0 100644 for (Map.Entry e : unhandledTags.entrySet()) { itemTag.set(e.getKey(), e.getValue()); -@@ -1247,7 +1305,9 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable { +@@ -667,7 +764,8 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable { + + @Overridden + boolean isEmpty() { +- return !(hasDisplayName() || hasLocalizedName() || hasEnchants() || hasLore() || hasRepairCost() || !unhandledTags.isEmpty() || hideFlag != 0 || isUnbreakable() || hasDamage() || hasAttributeModifiers()); ++ return !(hasDisplayName() || hasLocalizedName() || hasEnchants() || hasLore() || hasRepairCost() || !unhandledTags.isEmpty() || hideFlag != 0 || isUnbreakable() || hasDamage() || hasAttributeModifiers() ++ || hasPlaceableKeys() || hasDestroyableKeys()); // Paper - Implement an API for CanPlaceOn and CanDestroy NBT values + } + + public String getDisplayName() { +@@ -1003,7 +1101,11 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable { + && (this.unhandledTags.equals(that.unhandledTags)) + && (this.hideFlag == that.hideFlag) + && (this.isUnbreakable() == that.isUnbreakable()) +- && (this.hasDamage() ? that.hasDamage() && this.damage == that.damage : !that.hasDamage()); ++ && (this.hasDamage() ? that.hasDamage() && this.damage == that.damage : !that.hasDamage()) ++ // Paper start - Implement an API for CanPlaceOn and CanDestroy NBT values ++ && (this.hasPlaceableKeys() ? that.hasPlaceableKeys() && this.placeableKeys.equals(that.placeableKeys) : !that.hasPlaceableKeys()) ++ && (this.hasDestroyableKeys() ? that.hasDestroyableKeys() && this.destroyableKeys.equals(that.destroyableKeys) : !that.hasDestroyableKeys()); ++ // Paper end + } + + /** +@@ -1034,6 +1136,10 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable { + hash = 61 * hash + (isUnbreakable() ? 1231 : 1237); + hash = 61 * hash + (hasDamage() ? this.damage : 0); + hash = 61 * hash + (hasAttributeModifiers() ? this.attributeModifiers.hashCode() : 0); ++ // Paper start - Implement an API for CanPlaceOn and CanDestroy NBT values ++ hash = 61 * hash + (hasPlaceableKeys() ? this.placeableKeys.hashCode() : 0); ++ hash = 61 * hash + (hasDestroyableKeys() ? this.destroyableKeys.hashCode() : 0); ++ // Paper end + return hash; + } + +@@ -1054,6 +1160,15 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable { + clone.hideFlag = this.hideFlag; + clone.unbreakable = this.unbreakable; + clone.damage = this.damage; ++ // Paper start - Implement an API for CanPlaceOn and CanDestroy NBT values ++ if (this.placeableKeys != null) { ++ clone.placeableKeys = Sets.newHashSet(this.placeableKeys); ++ } ++ ++ if (this.destroyableKeys != null) { ++ clone.destroyableKeys = Sets.newHashSet(this.destroyableKeys); ++ } ++ // Paper end + return clone; + } catch (CloneNotSupportedException e) { + throw new Error(e); +@@ -1103,6 +1218,24 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable { + builder.put(DAMAGE.BUKKIT, damage); + } + ++ // Paper start - Implement an API for CanPlaceOn and CanDestroy NBT values ++ if (hasPlaceableKeys()) { ++ List cerealPlaceable = this.placeableKeys.stream() ++ .map(this::serializeNamespaced) ++ .collect(java.util.stream.Collectors.toList()); ++ ++ builder.put(CAN_PLACE_ON.BUKKIT, cerealPlaceable); ++ } ++ ++ if (hasDestroyableKeys()) { ++ List cerealDestroyable = this.destroyableKeys.stream() ++ .map(this::serializeNamespaced) ++ .collect(java.util.stream.Collectors.toList()); ++ ++ builder.put(CAN_DESTROY.BUKKIT, cerealDestroyable); ++ } ++ // Paper end ++ + final Map internalTags = new HashMap(unhandledTags); + serializeInternal(internalTags); + if (!internalTags.isEmpty()) { +@@ -1247,7 +1380,9 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable { CraftMetaArmorStand.NO_BASE_PLATE.NBT, CraftMetaArmorStand.SHOW_ARMS.NBT, CraftMetaArmorStand.SMALL.NBT, @@ -112,39 +335,151 @@ index 7ac07ac07ac0..7ac07ac07ac0 100644 // Paper end )); } -@@ -1294,4 +1354,35 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable { +@@ -1294,4 +1429,147 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable { return spigot; } // Spigot end + // Paper start - Implement an API for CanPlaceOn and CanDestroy NBT values + @Override ++ @SuppressWarnings("deprecation") + public Set getCanDestroy() { -+ return new java.util.HashSet<>(canDestroy); ++ return this.destroyableKeys == null ? Collections.emptySet() : legacyGetMatsFromKeys(this.destroyableKeys); + } + + @Override + @SuppressWarnings("deprecation") + public void setCanDestroy(Set canDestroy) { -+ if (canDestroy.stream().anyMatch(Material::isLegacy)) { -+ throw new IllegalArgumentException("canDestroy set must not contain any legacy materials!"); -+ } -+ this.canDestroy.clear(); -+ this.canDestroy.addAll(canDestroy); ++ Validate.notNull(canDestroy, "Cannot replace with null set!"); ++ legacyClearAndReplaceKeys(this.destroyableKeys, canDestroy); + } + + @Override ++ @SuppressWarnings("deprecation") + public Set getCanPlaceOn() { -+ return new java.util.HashSet<>(canPlaceOn); ++ return this.placeableKeys == null ? Collections.emptySet() : legacyGetMatsFromKeys(this.placeableKeys); + } + + @Override + @SuppressWarnings("deprecation") + public void setCanPlaceOn(Set canPlaceOn) { -+ if (canPlaceOn.stream().anyMatch(Material::isLegacy)) { -+ throw new IllegalArgumentException("canPlaceOn set must not contain any legacy materials!"); ++ Validate.notNull(canPlaceOn, "Cannot replace with null set!"); ++ legacyClearAndReplaceKeys(this.placeableKeys, canPlaceOn); ++ } ++ ++ @Override ++ public Set getDestroyableKeys() { ++ return this.destroyableKeys == null ? Collections.emptySet() : Sets.newHashSet(this.destroyableKeys); ++ } ++ ++ @Override ++ public void setDestroyableKeys(Collection canDestroy) { ++ Validate.notNull(canDestroy, "Cannot replace with null collection!"); ++ Validate.isTrue(ofAcceptableType(canDestroy), "Can only use NamespacedKey or NamespacedTag objects!"); ++ this.destroyableKeys.clear(); ++ this.destroyableKeys.addAll(canDestroy); ++ } ++ ++ @Override ++ public Set getPlaceableKeys() { ++ return this.placeableKeys == null ? Collections.emptySet() : Sets.newHashSet(this.placeableKeys); ++ } ++ ++ @Override ++ public void setPlaceableKeys(Collection canPlaceOn) { ++ Validate.notNull(canPlaceOn, "Cannot replace with null collection!"); ++ Validate.isTrue(ofAcceptableType(canPlaceOn), "Can only use NamespacedKey or NamespacedTag objects!"); ++ this.placeableKeys.clear(); ++ this.placeableKeys.addAll(canPlaceOn); ++ } ++ ++ @Override ++ public boolean hasPlaceableKeys() { ++ return this.placeableKeys != null && !this.placeableKeys.isEmpty(); ++ } ++ ++ @Override ++ public boolean hasDestroyableKeys() { ++ return this.destroyableKeys != null && !this.destroyableKeys.isEmpty(); ++ } ++ ++ @Deprecated ++ private void legacyClearAndReplaceKeys(Collection toUpdate, Collection beingSet) { ++ if (beingSet.stream().anyMatch(Material::isLegacy)) { ++ throw new IllegalArgumentException("Set must not contain any legacy materials!"); + } -+ this.canPlaceOn.clear(); -+ this.canPlaceOn.addAll(canPlaceOn); ++ ++ toUpdate.clear(); ++ toUpdate.addAll(beingSet.stream().map(Material::getKey).collect(java.util.stream.Collectors.toSet())); ++ } ++ ++ @Deprecated ++ private Set legacyGetMatsFromKeys(Collection names) { ++ Set mats = Sets.newHashSet(); ++ for (Namespaced key : names) { ++ if (!(key instanceof org.bukkit.NamespacedKey)) { ++ continue; ++ } ++ ++ Material material = Material.matchMaterial(key.toString(), false); ++ if (material != null) { ++ mats.add(material); ++ } ++ } ++ ++ return mats; ++ } ++ ++ private @Nullable Namespaced deserializeNamespaced(String raw) { ++ boolean isTag = raw.codePointAt(0) == '#'; ++ net.minecraft.server.ArgumentBlock blockParser = new net.minecraft.server.ArgumentBlock(new com.mojang.brigadier.StringReader(raw), true); ++ try { ++ blockParser = blockParser.parse(false); ++ } catch (com.mojang.brigadier.exceptions.CommandSyntaxException e) { ++ e.printStackTrace(); ++ return null; ++ } ++ ++ net.minecraft.server.MinecraftKey key; ++ if (isTag) { ++ key = blockParser.getTagKey(); ++ } else { ++ key = blockParser.getBlockKey(); ++ } ++ ++ if (key == null) { ++ return null; ++ } ++ ++ // don't DC the player if something slips through somehow ++ Namespaced resource = null; ++ try { ++ if (isTag) { ++ resource = new NamespacedTag(key.b(), key.getKey()); ++ } else { ++ resource = CraftNamespacedKey.fromMinecraft(key); ++ } ++ } catch (IllegalArgumentException ex) { ++ org.bukkit.Bukkit.getLogger().warning("Namespaced resource does not validate: " + key.toString()); ++ ex.printStackTrace(); ++ } ++ ++ return resource; ++ } ++ ++ private @Nonnull String serializeNamespaced(Namespaced resource) { ++ return resource.toString(); ++ } ++ ++ // not a fan of this ++ private boolean ofAcceptableType(Collection namespacedResources) { ++ boolean valid = true; ++ for (Namespaced resource : namespacedResources) { ++ if (valid && !(resource instanceof org.bukkit.NamespacedKey || resource instanceof com.destroystokyo.paper.NamespacedTag)) { ++ valid = false; ++ } ++ } ++ ++ return valid; + } + // Paper end }