diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/blocks/BaseItem.java b/worldedit-core/src/main/java/com/sk89q/worldedit/blocks/BaseItem.java index 16b979725b..6b5dbcfbbb 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/blocks/BaseItem.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/blocks/BaseItem.java @@ -22,7 +22,9 @@ import com.sk89q.jnbt.CompoundTag; import com.sk89q.worldedit.WorldEdit; import com.sk89q.worldedit.util.concurrency.LazyReference; +import com.sk89q.worldedit.util.nbt.BinaryTag; import com.sk89q.worldedit.util.nbt.CompoundBinaryTag; +import com.sk89q.worldedit.util.nbt.NbtUtils; import com.sk89q.worldedit.util.nbt.TagStringIO; import com.sk89q.worldedit.world.NbtValued; import com.sk89q.worldedit.world.item.ItemType; @@ -106,6 +108,26 @@ public void setNbtReference(@Nullable LazyReference nbtData) this.nbtData = nbtData; } + /** + * Gets whether the current item matches the given mask. + * + *

+ * See {@link NbtUtils#matches(BinaryTag, BinaryTag)} for specifics of NBT behavior. + *

+ * + * @param mask The mask to test against + * @return if it matches the mask + */ + public boolean matches(BaseItem mask) { + checkNotNull(mask); + + if (!mask.getType().equals(getType())) { + return false; + } + + return NbtUtils.matches(mask.getNbt(), getNbt()); + } + @Override public String toString() { String nbtString = ""; diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/nbt/NbtUtils.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/nbt/NbtUtils.java index 2c032e8bce..a0fd40a0e7 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/util/nbt/NbtUtils.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/nbt/NbtUtils.java @@ -47,4 +47,96 @@ public static T getChildTag(CompoundBinaryTag tag, String return childTagCast; } + /** + * Tests a {@link BinaryTag} against a "mask" {@link BinaryTag}. + * + *

+ * This method true if "test" contains all values from "mask". It does not + * matter if it contains more, as long as the mask values are present. + * + * For list tags, compound lists are treated as unordered sets, whereas + * non-compound lists are treated as ordered. This matches the way Minecraft + * treats NBT in items. + *

+ * + * @param mask The mask tag + * @param test The tested tag + * @return If the test tag contains the values from the mask + */ + public static boolean matches(BinaryTag mask, BinaryTag test) { + if (mask == null) { + // If our mask is null, all match + return true; + } + if (test == null) { + // If our mask is not null but our test is, never match + return false; + } + + if (mask.type() != test.type()) { + // If the types differ, they do not match + return false; + } + + if (mask.type() == BinaryTagTypes.COMPOUND) { + // For compounds, we ensure that all the keys are available and values match + CompoundBinaryTag maskCompound = (CompoundBinaryTag) mask; + CompoundBinaryTag testCompound = (CompoundBinaryTag) test; + + for (String binaryKey : maskCompound.keySet()) { + if (!matches(maskCompound.get(binaryKey), testCompound.get(binaryKey))) { + return false; + } + } + + return true; + } else if (mask.type() == BinaryTagTypes.LIST) { + // For lists, we ensure that all the values match + ListBinaryTag maskList = (ListBinaryTag) mask; + ListBinaryTag testList = (ListBinaryTag) test; + + if (!maskList.elementType().equals(testList.elementType())) { + // These lists are of different types + return false; + } + + if (maskList.elementType() == BinaryTagTypes.COMPOUND) { + // Treat compound lists like a set, due to how MC handle them + for (BinaryTag binaryTag : maskList) { + boolean found = false; + for (BinaryTag testTag : testList) { + if (matches(binaryTag, testTag)) { + found = true; + break; + } + } + if (!found) { + return false; + } + } + } else { + int startIndex = 0; + for (BinaryTag binaryTag : maskList) { + boolean found = false; + for (int i = startIndex; i < testList.size(); i++) { + BinaryTag testTag = testList.get(i); + if (matches(binaryTag, testTag)) { + found = true; + startIndex = i + 1; + break; + } + } + if (!found) { + return false; + } + } + } + + return true; + } else { + // For types that are just a value, we can do direct equality. + return mask.equals(test); + } + } + }