Expressions
Every numeric and string parameter is an expression rather than a value, resolved at execution time, per target.
- damage{amount=<caster.level> * 2.5 + <random.1to4>} @target
- message{msg="<target.name> is at <target.hp.percent>%"} @nearestPlayer{r=10}
Constants are just expressions that ignore their input, so a mechanic never branches on whether its parameter happened to be literal.
Resolution order
The order is pinned, and implemented literally:
- Every
<...>placeholder is substituted to its string value. - In numeric contexts only, the result is parsed as an infix expression.
Substituting first is what lets <mob.var.formula> contain arithmetic. Parsing first would not, because the variable's contents would arrive after the parser had already run.
String parameters get substitution only, never arithmetic.
Arithmetic
+ - * / %, unary minus, parentheses, doubles throughout, whitespace insignificant.
Deliberately small and deliberately not configurable: one pinned behaviour means one thing to implement and one thing to document. Adding an operator later would be a breaking change to every config that used that character for something else.
Namespaces
| Namespace | Holds |
|---|---|
caster | the entity casting |
target | the current target |
trigger | whatever tripped the trigger |
origin | the origin location |
skill.var.* | variables scoped to one execution |
mob.var.* | variables on the casting mob |
caster.var.*, target.var.* | variables on that specific entity |
global.var.* | server-wide variables |
random.* | randomness |
math.* | constants and functions |
papi.* | PlaceholderAPI, when it is installed |
On an entity
name, display, uuid, type, id, level, phase, faction, hp, maxhp, hp.percent, hp.missing, armor, air, food, exp, distance, threat.
health is an alias of hp throughout, so <target.health.percent> and <target.hp.percent> are the same.
On a location
x, y, z for exact coordinates and bx, by, bz for block coordinates, plus yaw, pitch, world and biome.
random
| Written | Gives |
|---|---|
<random.4to9> | an integer from 4 to 9 inclusive |
<random.10> | an integer from 0 to 10 inclusive |
<random.float> | a double from 0 to 1; double is an alias |
<random.bool> | true or false |
<random.sign> | 1 or -1 |
<random.angle> | a double from 0 to 360 |
<random.4to9> is the unambiguous spelling and the only one accepted outside drop amounts, because 4-9 is also a perfectly good subtraction. Treating it as a range everywhere would silently change the meaning of every expression containing a minus sign.
math
<math.pi>, <math.e>, <math.tau> and <math.random>.
Variables
Four scopes, written by set_variable and variable_math and read back through expressions.
- set_variable{name=charges;value=3;scope=mob}
- damage{amount=<mob.var.charges> * 4} @target
scope | Lives for | Read as |
|---|---|---|
skill (default) | one execution | <skill.var.name> |
mob | the life of the mob | <mob.var.name> |
target | the life of that entity | <target.var.name> |
global | server-wide | <global.var.name> |
name takes n, var and key as aliases, and value takes v and amount.
MiniMessage survives
A bracketed token whose first path segment is not a registered namespace is left exactly as written:
display: "<gradient:#e8d9a0:#c9a227>Example Champion</gradient>"
Nobody registered gradient, so Bestiary does not touch it and MiniMessage renders it later. This is why a display name can use the full tag set without escaping anything, and why adding a namespace is a considered decision rather than a free one.
Where they work
Everywhere a parameter is read: mechanic, targeter and condition arguments, bossbar titles, messages. A bossbar title resolves live:
bossbar:
title: "<gold>Example Champion <gray>- <white><caster.hp.percent>%"
Testing one
/bestiary cast <skill> traces every step, including what each expression resolved to for each target. When a number is wrong, that trace is the fastest way to see whether the expression or the targeter is at fault.
Test with the same caster and target kind the real trigger supplies. A player command can resolve
attributes that a location-only trigger cannot. <papi.*> values only resolve when PlaceholderAPI
is installed; /bestiary platform confirms whether that optional hook was detected.