From 35dce37a959e9f4b9b98f03437efbc13ac47a19c 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 005ebec26..97d85f845 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, Comparable> k = Maps.newLinkedHashMap(); // CraftBukkit - stable 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 @@ -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 1eede4bcc..8a9a48bb8 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 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(); @@ -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 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 items = this.placeableKeys.stream() + .map(this::serializeNamespaced) + .collect(java.util.stream.Collectors.toList()); + + itemTag.set(CAN_PLACE_ON.NBT, createNonComponentStringList(items)); + } + + if (hasDestroyableKeys()) { + List 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 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 list) { + if (list == null || list.isEmpty()) { + return null; + } + + NBTTagList tagList = new NBTTagList(); + for (String value : list) { + tagList.add(new NBTTagString(value)); + } + + return tagList; + } + // Paper end + NBTTagList createStringList(List 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; } @@ -1215,6 +1331,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); @@ -1272,6 +1396,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 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()) { @@ -1435,7 +1577,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 )); } @@ -1460,4 +1604,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 getCanDestroy() { + return !hasDestroyableKeys() ? Collections.emptySet() : legacyGetMatsFromKeys(this.destroyableKeys); + } + + @Override + @SuppressWarnings("deprecation") + public void setCanDestroy(Set canDestroy) { + Validate.notNull(canDestroy, "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) { + Validate.notNull(canPlaceOn, "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) { + 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 !hasPlaceableKeys() ? 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!"); + } + + 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) == '#'; + 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 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.17.1