From 9471146c065d29d7d11f1538d6dcffdcf985d32b Mon Sep 17 00:00:00 2001
From: Mark Vainomaa <mikroskeem@mikroskeem.eu>
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 005ebec266..97d85f8451 100644
--- a/src/main/java/net/minecraft/server/ArgumentBlock.java
+++ b/src/main/java/net/minecraft/server/ArgumentBlock.java
@@ -43,7 +43,7 @@ public class ArgumentBlock {
     private final boolean j;
     private final Map<IBlockState<?>, Comparable<?>> k = Maps.newLinkedHashMap(); // CraftBukkit - stable
     private final Map<String, String> l = Maps.newHashMap();
-    private MinecraftKey m = new MinecraftKey("");
+    private MinecraftKey m = new MinecraftKey(""); public MinecraftKey getBlockKey() { return this.m; } // Paper - OBFHELPER
     private BlockStateList<Block, IBlockData> n;
     private IBlockData o;
     @Nullable
@@ -72,11 +72,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() == '#') {
diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java
index 3325111f8a..0606e69a61 100644
--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java
+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java
@@ -87,6 +87,12 @@ import org.bukkit.persistence.PersistentDataContainer;
 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:
  *
@@ -268,6 +274,10 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta {
     @Specific(Specific.To.NBT)
     static final ItemMetaKey BLOCK_DATA = new ItemMetaKey("BlockStateTag");
     static final ItemMetaKey BUKKIT_CUSTOM_TAG = new ItemMetaKey("PublicBukkitValues");
+    // Paper start - Implement an API for CanPlaceOn and CanDestroy NBT values
+    static final ItemMetaKey CAN_DESTROY = new ItemMetaKey("CanDestroy");
+    static final ItemMetaKey CAN_PLACE_ON = new ItemMetaKey("CanPlaceOn");
+    // Paper end
 
     private IChatBaseComponent displayName;
     private IChatBaseComponent locName;
@@ -280,6 +290,10 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta {
     private int hideFlag;
     private boolean unbreakable;
     private int damage;
+    // Paper start - Implement an API for CanPlaceOn and CanDestroy NBT values
+    private Set<Namespaced> placeableKeys = Sets.newHashSet();
+    private Set<Namespaced> destroyableKeys = Sets.newHashSet();
+    // Paper end
 
     private static final Set<String> HANDLED_TAGS = Sets.newHashSet();
     private static final CraftPersistentDataTypeRegistry DATA_TYPE_REGISTRY = new CraftPersistentDataTypeRegistry();
@@ -317,6 +331,15 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta {
         this.hideFlag = meta.hideFlag;
         this.unbreakable = meta.unbreakable;
         this.damage = meta.damage;
+        // Paper start - Implement an API for CanPlaceOn and CanDestroy NBT values
+        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.persistentDataContainer.putAll(meta.persistentDataContainer.getRaw());
 
@@ -393,6 +416,31 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta {
                 persistentDataContainer.put(key, compound.get(key));
             }
         }
+        // Paper start - Implement an API for CanPlaceOn and CanDestroy NBT values
+        if (tag.hasKey(CAN_DESTROY.NBT)) {
+            NBTTagList list = tag.getList(CAN_DESTROY.NBT, CraftMagicNumbers.NBT.TAG_STRING);
+            for (int i = 0; i < list.size(); i++) {
+                Namespaced namespaced = this.deserializeNamespaced(list.getString(i));
+                if (namespaced == null) {
+                    continue;
+                }
+
+                this.destroyableKeys.add(namespaced);
+            }
+        }
+
+        if (tag.hasKey(CAN_PLACE_ON.NBT)) {
+            NBTTagList list = tag.getList(CAN_PLACE_ON.NBT, CraftMagicNumbers.NBT.TAG_STRING);
+            for (int i = 0; i < list.size(); i++) {
+                Namespaced namespaced = this.deserializeNamespaced(list.getString(i));
+                if (namespaced == null) {
+                    continue;
+                }
+
+                this.placeableKeys.add(namespaced);
+            }
+        }
+        // Paper end
 
         Set<String> keys = tag.getKeys();
         for (String key : keys) {
@@ -530,6 +578,34 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta {
             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) {
+            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) {
+            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));
@@ -658,6 +734,23 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta {
         if (hasDamage()) {
             itemTag.setInt(DAMAGE.NBT, damage);
         }
+        // Paper start - Implement an API for CanPlaceOn and CanDestroy NBT values
+        if (hasPlaceableKeys()) {
+            List<String> items = this.placeableKeys.stream()
+                .map(this::serializeNamespaced)
+                .collect(java.util.stream.Collectors.toList());
+
+            itemTag.set(CAN_PLACE_ON.NBT, createNonComponentStringList(items));
+        }
+
+        if (hasDestroyableKeys()) {
+            List<String> items = this.destroyableKeys.stream()
+                .map(this::serializeNamespaced)
+                .collect(java.util.stream.Collectors.toList());
+
+            itemTag.set(CAN_DESTROY.NBT, createNonComponentStringList(items));
+        }
+        // Paper end
 
         for (Map.Entry<String, NBTBase> e : unhandledTags.entrySet()) {
             itemTag.set(e.getKey(), e.getValue());
@@ -674,6 +767,21 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta {
         }
     }
 
+    // Paper start
+    static NBTTagList createNonComponentStringList(List<String> list) {
+        if (list == null || list.isEmpty()) {
+            return null;
+        }
+
+        NBTTagList tagList = new NBTTagList();
+        for (String value : list) {
+            tagList.add(NBTTagString.a(value)); // Paper - NBTTagString.of(String str)
+        }
+
+        return tagList;
+    }
+    // Paper end
+
     NBTTagList createStringList(List<IChatBaseComponent> list) {
         if (list == null || list.isEmpty()) {
             return null;
@@ -757,7 +865,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta {
 
     @Overridden
     boolean isEmpty() {
-        return !(hasDisplayName() || hasLocalizedName() || hasEnchants() || hasLore() || hasCustomModelData() || hasBlockData() || hasRepairCost() || !unhandledTags.isEmpty() || !persistentDataContainer.isEmpty() || hideFlag != 0 || isUnbreakable() || hasDamage() || hasAttributeModifiers());
+        return !(hasDisplayName() || hasLocalizedName() || hasEnchants() || hasLore() || hasCustomModelData() || hasBlockData() || hasRepairCost() || !unhandledTags.isEmpty() || !persistentDataContainer.isEmpty() || hideFlag != 0 || isUnbreakable() || hasDamage() || hasAttributeModifiers() || hasPlaceableKeys() || hasDestroyableKeys()); // Paper - Implement an API for CanPlaceOn and CanDestroy NBT values
     }
 
     @Override
@@ -1157,7 +1265,11 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta {
                 && (this.hideFlag == that.hideFlag)
                 && (this.isUnbreakable() == that.isUnbreakable())
                 && (this.hasDamage() ? that.hasDamage() && this.damage == that.damage : !that.hasDamage())
-                && (this.version == that.version);
+                && (this.version == that.version)
+                // 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
     }
 
     /**
@@ -1192,6 +1304,10 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta {
         hash = 61 * hash + (hasDamage() ? this.damage : 0);
         hash = 61 * hash + (hasAttributeModifiers() ? this.attributeModifiers.hashCode() : 0);
         hash = 61 * hash + version;
+        // 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;
     }
 
@@ -1216,6 +1332,14 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta {
             clone.unbreakable = this.unbreakable;
             clone.damage = this.damage;
             clone.version = this.version;
+            // 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);
@@ -1273,6 +1397,24 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta {
             builder.put(DAMAGE.BUKKIT, damage);
         }
 
+        // Paper start - Implement an API for CanPlaceOn and CanDestroy NBT values
+        if (hasPlaceableKeys()) {
+            List<String> cerealPlaceable = this.placeableKeys.stream()
+                .map(this::serializeNamespaced)
+                .collect(java.util.stream.Collectors.toList());
+
+            builder.put(CAN_PLACE_ON.BUKKIT, cerealPlaceable);
+        }
+
+        if (hasDestroyableKeys()) {
+            List<String> cerealDestroyable = this.destroyableKeys.stream()
+                .map(this::serializeNamespaced)
+                .collect(java.util.stream.Collectors.toList());
+
+            builder.put(CAN_DESTROY.BUKKIT, cerealDestroyable);
+        }
+        // Paper end
+
         final Map<String, NBTBase> internalTags = new HashMap<String, NBTBase>(unhandledTags);
         serializeInternal(internalTags);
         if (!internalTags.isEmpty()) {
@@ -1436,7 +1578,9 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta {
                         CraftMetaArmorStand.NO_BASE_PLATE.NBT,
                         CraftMetaArmorStand.SHOW_ARMS.NBT,
                         CraftMetaArmorStand.SMALL.NBT,
-                        CraftMetaArmorStand.MARKER.NBT
+                        CraftMetaArmorStand.MARKER.NBT,
+                        CAN_DESTROY.NBT,
+                        CAN_PLACE_ON.NBT
                         // Paper end
                 ));
             }
@@ -1461,4 +1605,147 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta {
     }
     // Paper end
 
+    // Paper start - Implement an API for CanPlaceOn and CanDestroy NBT values
+    @Override
+    @SuppressWarnings("deprecation")
+    public Set<Material> getCanDestroy() {
+        return !hasDestroyableKeys() ? Collections.emptySet() : legacyGetMatsFromKeys(this.destroyableKeys);
+    }
+
+    @Override
+    @SuppressWarnings("deprecation")
+    public void setCanDestroy(Set<Material> canDestroy) {
+        Validate.notNull(canDestroy, "Cannot replace with null set!");
+        legacyClearAndReplaceKeys(this.destroyableKeys, canDestroy);
+    }
+
+    @Override
+    @SuppressWarnings("deprecation")
+    public Set<Material> getCanPlaceOn() {
+        return !hasPlaceableKeys() ? Collections.emptySet() : legacyGetMatsFromKeys(this.placeableKeys);
+    }
+
+    @Override
+    @SuppressWarnings("deprecation")
+    public void setCanPlaceOn(Set<Material> canPlaceOn) {
+        Validate.notNull(canPlaceOn, "Cannot replace with null set!");
+        legacyClearAndReplaceKeys(this.placeableKeys, canPlaceOn);
+    }
+
+    @Override
+    public Set<Namespaced> getDestroyableKeys() {
+        return !hasDestroyableKeys() ? Collections.emptySet() : Sets.newHashSet(this.destroyableKeys);
+    }
+
+    @Override
+    public void setDestroyableKeys(Collection<Namespaced> 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<Namespaced> getPlaceableKeys() {
+        return !hasPlaceableKeys() ? Collections.emptySet() : Sets.newHashSet(this.placeableKeys);
+    }
+
+    @Override
+    public void setPlaceableKeys(Collection<Namespaced> 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<Namespaced> toUpdate, Collection<Material> beingSet) {
+        if (beingSet.stream().anyMatch(Material::isLegacy)) {
+            throw new IllegalArgumentException("Set must not contain any legacy materials!");
+        }
+
+        toUpdate.clear();
+        toUpdate.addAll(beingSet.stream().map(Material::getKey).collect(java.util.stream.Collectors.toSet()));
+    }
+
+    @Deprecated
+    private Set<Material> legacyGetMatsFromKeys(Collection<Namespaced> names) {
+        Set<Material> 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.length() > 0 && 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.getNamespace(), 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<Namespaced> 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
 }
-- 
2.25.0