-
Notifications
You must be signed in to change notification settings - Fork 0
NBT Predicate
TLDR: Just read the examples in each section
NBT predicates describe restrictions on NBT data. They are used by special recipes to define complex ingredients with NBT requirements.
There are different types of NBT predicates, for each different NBT type.
All predicates have a compound predicate as their root, which is simply a set of NBT Paths mapped to other predicates within braces.
Multiple predicates within the compound must be separated by commas (,). A colon (:) is tolerated between paths and predicates, but not required.
{number: 0, text: ""} matches any compound that contains a number node equal to 0 and a text node equal to the empty string.
NBT predicates always allow extra nodes to exist, for example, the compound {number: 0, text: "", extra: 1} would also match the previous predicate.
If you're a mod developer, you may be interested in the following section
There are three types of numeric predicates.
A literal number value. Only matches that exact number.
-
{n: 2}annnumber equal to 2
A number preceded by a comparison operator, between the following:
-
=equal to -
>greater than -
<smaller than -
>=greater than or equals -
<=smaller than or equals -
!=not equals
It's recommended to omit the colon between paths and predicates when the predicate starts with an operator symbol.
-
{n >= 2}annnumber greater than or equal to 2 -
{n != 2}annnumber distinct from 2 -
{n > -0.5}annnumber greater than -0.5
A range consists of two numbers between parentheses or brackets, separated by a tilde symbol ~.
Brackets denote a closed range, while parentheses an open range (does not include the limits).
It's possible to mix brackets with parentheses to include one side and not the other. Spaces are tolerated between elements (except between a sign and its number), but not required.
-
{n: [0 ~ 10]}annnumber between 0 and 10, including 0 and 10 -
{n: [0 ~ 1)}annnumber between 0 and 1, including 0 but not 1 -
{n: [-1 ~ 1]}annnumber between -1 and 1, including -1 and 1
In addition to the above predicate types, all predicate types accept an additional single letter suffix among BSILFD (case-insensitive)
to require the value to have a specific numeric type:
-
BByte -
SShort -
IInt -
LLong -
FFloat -
DDouble
The letter must be specified after another predicate:
-
{n >= 2i}annInt number greater than or equal to 2 -
{n: 0b}annByte number equal to 0 -
{n: (0 ~ 1)f}annFloat number between 0 and 1 (not including 0 nor 1)
There are two types of string predicates
A literal string value between double quotes.
-
{str: "Steve"}matches astrnode with the exact textSteve(case-sensitive)
A tilde (~) preceding a regular expression between double quotes. Uses Java regex.
As with numbers, it's recommended to omit the colon when the predicate starts with an operator symbol.
-
{str ~ "Steve|Alex"}matches astrwith eitherSteveorAlex -
{str ~ "Alex.*"}matches astrwith any text that starts withAlex -
{str ~ ".*Steve.*"}matches astrwith any text that containsSteve -
{str ~ "Alex #\d+"}matches astrwith any text of the formAlex #dwhere d is any number (one or more digits)
Keep in mind that, if you're writing these expressions within a JSON string literal, you'll need to escape quotes as \" and backslashes as \\.
For example, the last example would be
{
"predicate": "{str ~ \"Alex #\\d+\"}"
}While Java regular expressions do not support recursive subpatterns like PCRE regex, they can be used to match many different types of text. You may learn about the syntax of these expressions at their official documentation, but whether you're new to them or not, I recommend using regex101.com to design, test or learn about these expressions (make sure you set the Regex dialect to "Java 8")
If your regex predicate is going to be executed very frequently (maybe a mod evaluates it every tick), you may want to learn about how to use possessive and reluctant quantifiers as well as atomic capture groups to improve matching efficiency, since Java regex doesn't apply automatic expression optimizations as some other regex engines do.
List predicates compare a list to another one, using a given comparison mode.
The default comparison mode is equality, but a different mode can be specified with an operator preceding the list.
-
=Exact match: All elements must match in order (you may omit the=) -
~Contain any: The list must contain at least one of the elements from the predicate list -
>Contain all: The list must contain all of the elements from the predicate list -
<Subset: The list must only contain elements from the predicate list -
<<Starts with: The list must start with the predicate list -
>>Ends with: The list must end with the predicate list -
><Contains: The list must contain the predicate list at some position
In addition, the elements from the predicate list need not be exact values. They can be arbitrary NBT predicates.
-
{l: ["a", "b", "c"]}matches a string listlwith exactly 3 elements,a,bandcin that order -
{l: ["a", ~"b+", "c"]}matches a string listlwith exactly 3 elements, the first and the last beingaandc, and the second any amount ofbs -
{l ~ ["a", "b"]}matches a string listlthat contains at least oneaelement or onebelement -
{l > ["a", "b"]}matches a string listlthat contains oneaelement and onebelement -
{l > ["a", "a"]}matches a string listlthat contains twoaelements -
{l < ["a", "a", "b", "c"]}matches a string listlif it contains only at most twoaelements, onebelement and onecelement, and no other element -
{l << ["a", "b"]}matches a string listlif its first element isaand its second element isb -
{l[0]: "a", l[1]: "b"}equivalent to the one above using indexing paths and multiple predicates -
{l >> ["y", "z"]}matches a string listlif its last elements areyand thenz -
{l[-2]: "y", l[-1]: "z"}equivalent to the one above using indexing paths and multiple predicates -
{l >< ["b", "d"]}matches a string listlif it contains abelement followed by adelement -
{l > [>= 2, >= 4]}matches a number listlif it contains one element that is greater than or equal to 2 and another element greater than or equal to 4. In particular, it would not match[4]alone, even though it matches both predicates, because each predicate must match a different element
Note that some comparison modes may be extremely inefficient if their subpredicates are expensive to compute.
For example, the > (contains all) comparison mode may need to backtrack to check if predicates match in a different order.
Also, keep in mind that NBT lists only support a single type for all its elements. This restriction also applies to list predicates.
-
{l: [0l, [1~3], >=4]}the first predicate enforces numbers of Long type, so the list must either be a Long Array or a List of Long nodes. Matches a Long number listlwith exactly three elements, the first being 0, the second between 1 and 3 (including 1 or 3), and the third greater than or equal to 4
If you've read the above sections, you've already seen some compound predicates, as they must be the root of any NBT predicate. However, compound predicates support a few more complex uses.
Compound predicates are not limited to simple name paths. You may use any NBT Path as key for a predicate.
-
{Fireworks.Flight >= 3}only matches if the NBT contains a compound namedFireworksthat contains a number namedFlightwhich is greater than or equal to 3
You may specify a compound predicate as a subpredicate. This can be useful to shorten common paths.
-
{Fireworks: {Flight >= 3}}equivalent to the predicate above -
{Very.Long.Path: {Shared: "Between", Multiple: "Paths"}}group common predicates in a compound predicate -
{Very.Long.Path.Shared: "Between", Very.Long.Path.Multiple: "Paths"}equivalent to the predicate above
You may repeat a path to specify multiple predicates for it, which must all match.
-
{number >= 0, number <= 10}a number between 0 and 10, including 0 and 10 -
{number: [0 ~ 10]}equivalent to the predicate above
You may precede an NBT path with an exclamation symbol ! to negate its predicate.
This can be combined with repeated paths to define complex predicates.
-
{number: [0 ~ 3], !number: (1 ~ 2)}matches a number within[0 ~ 1]or within[2 ~ 3]
Note that negated predicates do not require the path to exist. If a path doesn't exist then it can't fail the predicate, and thus it will pass the negated predicate. For example, the following two predicates are different:
-
{!number = 0}matches any NBT unless it contains a number namednumberthat is equal to 0 -
{number != 0}matches any NBT that contains a number namednumber, only if it is not equal to 0
Both would match {number: 1}, but the first would match {not: "a number"}, and the second would not.
The following section is only relevant for developers.
NBT predicate objects can be created with the static NBTPredicate.parse(string) method.
NBT predicate objects support the following operations:
-
generateValid()attempts to generate a valid tag that would match this predicate. Note that this can be impossible, as you may easily define contradictory predicates such as{n = 0, n != 0}. This method cannot be used as a satisfiability test, it only does a best effort search for a valid pattern, without any backtracking. -
test(nbt)tests this pattern on an NBT value -
test(itemStack)tests this pattern on an item stack's NBT tag, if it has one -
test(entity)tests this pattern on an entity's persistent data NBT tag -
isUnique()whether this predicate only allows one matching value. Unique patterns are handled specially when comparing lists to avoid unnecessary backtracking. -
getDisplay()pretty print -
getDisplay(predicateStyle)pretty print with a givenNBTPredicate.Style -
write(byteBuffer)serialize to a packet buffer, to send over the network
You may also use the static method NBTPredicate.read(byteBuffer) to deserialize a predicate from a packet.
If you have any doubts, feel free to drop by the official Discord Server.
- Recipes
- Math
- Network
- NBT
- Text