From 0000000000000000000000000000000000000000 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/org/bukkit/craftbukkit/inventory/CraftMetaItem.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java index 3d749e6d6193878f1b4f288946afcec9461dc8df..e29134181d5b032311fe163b48fbe53f1298c6fd 100644 --- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java @@ -84,6 +84,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: * @@ -272,6 +278,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 // We store the raw original JSON representation of all text data. See SPIGOT-5063, SPIGOT-5656, SPIGOT-5304 private String displayName; @@ -285,6 +295,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 placeableKeys = Sets.newHashSet(); + private Set destroyableKeys = Sets.newHashSet(); + // Paper end private static final Set HANDLED_TAGS = Sets.newHashSet(); private static final CraftPersistentDataTypeRegistry DATA_TYPE_REGISTRY = new CraftPersistentDataTypeRegistry(); @@ -322,6 +336,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()); @@ -385,6 +408,31 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { this.persistentDataContainer.put(key, compound.get(key).copy()); } } + // Paper start - Implement an API for CanPlaceOn and CanDestroy NBT values + if (tag.contains(CAN_DESTROY.NBT)) { + ListTag 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.contains(CAN_PLACE_ON.NBT)) { + ListTag 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 keys = tag.getAllKeys(); for (String key : keys) { @@ -523,6 +571,34 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { this.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.getDecoder().decode(internal)); @@ -651,6 +727,23 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { if (this.hasDamage()) { itemTag.putInt(CraftMetaItem.DAMAGE.NBT, this.damage); } + // Paper start - Implement an API for CanPlaceOn and CanDestroy NBT values + if (hasPlaceableKeys()) { + List items = this.placeableKeys.stream() + .map(this::serializeNamespaced) + .collect(java.util.stream.Collectors.toList()); + + itemTag.put(CAN_PLACE_ON.NBT, createNonComponentStringList(items)); + } + + if (hasDestroyableKeys()) { + List items = this.destroyableKeys.stream() + .map(this::serializeNamespaced) + .collect(java.util.stream.Collectors.toList()); + + itemTag.put(CAN_DESTROY.NBT, createNonComponentStringList(items)); + } + // Paper end for (Map.Entry e : this.unhandledTags.entrySet()) { itemTag.put(e.getKey(), e.getValue()); @@ -667,6 +760,21 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { } } + // Paper start + static ListTag createNonComponentStringList(List list) { + if (list == null || list.isEmpty()) { + return null; + } + + ListTag tagList = new ListTag(); + for (String value : list) { + tagList.add(StringTag.valueOf(value)); // Paper - NBTTagString.of(String str) + } + + return tagList; + } + // Paper end + ListTag createStringList(List list) { if (list == null) { return null; @@ -750,7 +858,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { @Overridden boolean isEmpty() { - return !(this.hasDisplayName() || this.hasLocalizedName() || this.hasEnchants() || (this.lore != null) || this.hasCustomModelData() || this.hasBlockData() || this.hasRepairCost() || !this.unhandledTags.isEmpty() || !this.persistentDataContainer.isEmpty() || this.hideFlag != 0 || this.isUnbreakable() || this.hasDamage() || this.hasAttributeModifiers()); + return !(this.hasDisplayName() || this.hasLocalizedName() || this.hasEnchants() || (this.lore != null) || this.hasCustomModelData() || this.hasBlockData() || this.hasRepairCost() || !this.unhandledTags.isEmpty() || !this.persistentDataContainer.isEmpty() || this.hideFlag != 0 || this.isUnbreakable() || this.hasDamage() || this.hasAttributeModifiers() || this.hasPlaceableKeys() || this.hasDestroyableKeys()); // Paper - Implement an API for CanPlaceOn and CanDestroy NBT values } // Paper start @@ -1182,7 +1290,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 } /** @@ -1217,6 +1329,10 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { hash = 61 * hash + (this.hasDamage() ? this.damage : 0); hash = 61 * hash + (this.hasAttributeModifiers() ? this.attributeModifiers.hashCode() : 0); hash = 61 * hash + this.version; + // Paper start - Implement an API for CanPlaceOn and CanDestroy NBT values + hash = 61 * hash + (this.hasPlaceableKeys() ? this.placeableKeys.hashCode() : 0); + hash = 61 * hash + (this.hasDestroyableKeys() ? this.destroyableKeys.hashCode() : 0); + // Paper end return hash; } @@ -1241,6 +1357,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); @@ -1298,6 +1422,23 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { builder.put(CraftMetaItem.DAMAGE.BUKKIT, this.damage); } + // Paper start - Implement an API for CanPlaceOn and CanDestroy NBT values + if (this.hasPlaceableKeys()) { + List cerealPlaceable = this.placeableKeys.stream() + .map(this::serializeNamespaced) + .collect(java.util.stream.Collectors.toList()); + + builder.put(CAN_PLACE_ON.BUKKIT, cerealPlaceable); + } + + if (this.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(this.unhandledTags); this.serializeInternal(internalTags); if (!internalTags.isEmpty()) { @@ -1470,6 +1611,8 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { CraftMetaArmorStand.SHOW_ARMS.NBT, CraftMetaArmorStand.SMALL.NBT, CraftMetaArmorStand.MARKER.NBT, + CAN_DESTROY.NBT, + CAN_PLACE_ON.NBT, // Paper end CraftMetaCompass.LODESTONE_DIMENSION.NBT, CraftMetaCompass.LODESTONE_POS.NBT, @@ -1499,4 +1642,146 @@ 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 getCanDestroy() { + return !hasDestroyableKeys() ? Collections.emptySet() : legacyGetMatsFromKeys(this.destroyableKeys); + } + + @Override + @SuppressWarnings("deprecation") + public void setCanDestroy(Set canDestroy) { + Preconditions.checkArgument(canDestroy != null, "Cannot replace with null set!"); + legacyClearAndReplaceKeys(this.destroyableKeys, canDestroy); + } + + @Override + @SuppressWarnings("deprecation") + public Set getCanPlaceOn() { + return !hasPlaceableKeys() ? Collections.emptySet() : legacyGetMatsFromKeys(this.placeableKeys); + } + + @Override + @SuppressWarnings("deprecation") + public void setCanPlaceOn(Set canPlaceOn) { + Preconditions.checkArgument(canPlaceOn != null, "Cannot replace with null set!"); + legacyClearAndReplaceKeys(this.placeableKeys, canPlaceOn); + } + + @Override + public Set getDestroyableKeys() { + return !hasDestroyableKeys() ? Collections.emptySet() : Sets.newHashSet(this.destroyableKeys); + } + + @Override + public void setDestroyableKeys(Collection canDestroy) { + Preconditions.checkArgument(canDestroy != null, "Cannot replace with null collection!"); + Preconditions.checkArgument(ofAcceptableType(canDestroy), "Can only use NamespacedKey or NamespacedTag objects!"); + this.destroyableKeys.clear(); + this.destroyableKeys.addAll(canDestroy); + } + + @Override + public Set getPlaceableKeys() { + return !hasPlaceableKeys() ? Collections.emptySet() : Sets.newHashSet(this.placeableKeys); + } + + @Override + public void setPlaceableKeys(Collection canPlaceOn) { + Preconditions.checkArgument(canPlaceOn != null, "Cannot replace with null collection!"); + Preconditions.checkArgument(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!"); + } + + 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.length() > 0 && raw.codePointAt(0) == '#'; + com.mojang.datafixers.util.Either result; + try { + result = net.minecraft.commands.arguments.blocks.BlockStateParser.parseForTesting(net.minecraft.core.registries.BuiltInRegistries.BLOCK.asLookup(), raw, false); + } catch (com.mojang.brigadier.exceptions.CommandSyntaxException e) { + return null; + } + + net.minecraft.resources.ResourceLocation key = null; + if (isTag && result.right().isPresent() && result.right().get().tag() instanceof net.minecraft.core.HolderSet.Named namedSet) { + key = namedSet.key().location(); + } else if (result.left().isPresent()) { + key = net.minecraft.core.registries.BuiltInRegistries.BLOCK.getKey(result.left().get().blockState().getBlock()); + } + + 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.getPath()); + } 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) { + + for (Namespaced resource : namespacedResources) { + if (!(resource instanceof org.bukkit.NamespacedKey || resource instanceof com.destroystokyo.paper.NamespacedTag)) { + return false; + } + } + + return true; + } + // Paper end }