Functions
Function bodies and event hooks (OnLoad, OnTick, OnFirstJoin, OnRespawn, OnAdvancement) compile to .mcfunction files.
Raw commands
A line that starts with / is a command. It is written to the output as it is, without the slash:
Function greet {
/say hello
/execute as @a at @s run particle minecraft:heart ~ ~2 ~
}
Every command is checked against the 26.3 command tree, the same one the game uses. The checker reports:
- unknown commands and options, with the alternatives
- missing and extra arguments
- argument syntax: numbers and their bounds, selectors and their options, coordinates, ids, ranges, and balanced NBT and text
- unknown vanilla ids, such as
minecraft:diamnd - ids in your own namespaces that nothing declares, such as
function demo:missing
Completion inside a command offers the next literal and values for the argument being typed. These include selectors, game modes, registry ids, and your project's functions, tags and objectives.
The checker checks syntax, not meaning. Whether an entity exists, or whether an NBT path fits the entity, is left to the game.
Values and variables
Function code computes with whole numbers (int, 32-bit, wrapping like the game's scoreboards) and bools.
Global deaths: int = 0 // one value for the whole pack
Objective kills: deathCount // a score per player
Function on_death {
deaths += 1
kills[@s] = 0 // a score: objective[holder]
let streak = kills[@s] * 2 // a local; its type is inferred
let lucky: bool = streak > 10
}
- Globals are set once, when the pack first loads. A
/reloadkeeps their values. - Locals (
let) belong to their function. A name in an inner block can shadow an outer one. - Objective scores are written
objective[holder]. The holder is a selector, or a fake player name in quotes, such askills["#total"].
Storage records
A Storage keeps a record, a Type with fields, in command storage. Functions read and write its int, bool and string fields by name:
/** The state of a siege. */
Type SiegeState {
wave: int = 0
boss_alive: bool
/** Mobs still to spawn this wave. */
queue: [Id<entity_type>] = []
}
/** Kept in storage siege:mms game. */
Storage game: SiegeState
Function next_wave {
game.wave += 1
game.boss_alive = game.wave % 5 == 0
tell(@a, "Wave ${game.wave} begins")
}
A field that holds a record, written in place or as another Type, is reached by its path, such as game.spawn.x. A field can also hold one of a few strings, such as mode: "siege" | "raid". Functions read it as a string, and setting it to any other string literal is an error. The load function sets each missing field to its default. A field without one starts at 0, false, "", or the first of its strings. A /reload keeps the values, and a field added later starts at its default. The record is at <namespace>:mms <name>, so /data get storage siege:mms game shows it in the game. Fields of other types, such as queue, are stored and given their defaults, but functions do not read them yet. Completion after game. lists the fields, and hovering one shows its type and doc comment.
A field costs a storage read and write each time a function uses it. A Global is a score, so it is cheaper in a tight loop.
Selectors
exists(target) tells whether a selector finds any entity, and count(target) how many it finds:
const boss = @e[tag=siege.boss, limit=1]
Function check {
if (!exists(boss)) {
/summon wither_skeleton ~ ~ ~ {Tags:["siege.boss"]}
}
let zombies = count(@e[type=zombie, distance=..16])
}
Operators
| Operators | Meaning |
|---|---|
+ - * / % |
Arithmetic. Division rounds down and % takes the divisor's sign, as the scoreboard does. |
< <= > >= == != |
Comparisons. |
&& || ! |
Logic. The right side of && and || only runs when it is needed. |
Constant parts are folded at compile time.
Command results
A command after = is a value: what the command returns, as execute store result would store it. Assigned to a bool, it is whether the command succeeded, as execute store success would store it.
Objective checks
Function inspect {
let items = /data get block ~ ~ ~ Items
let is_chest: bool = /execute if block ~ ~ ~ chest
if (is_chest && items > 20) {
/say this chest is nearly full
}
checks[@s] = /time query gametime
}
- The command runs to the end of the line, as a command on a line of its own does, and
${...}works in it the same way. - It works wherever a value is assigned:
let,=to a local, aGlobal, a score or aStoragefield. It is not part of a larger expression, so writelet n = /data get ...and then usen. - A result is a whole number. To read text, such as a block's id, copy it into a
Storagefield with/data modify storage ... set from ...and read the field.
Strings
string values live in command storage (<namespace>:mms). They can be joined with +, appended with +=, and compared with == and !=. Ints and bools join as their text.
Global motd: string = "Welcome"
Function welcome(name: string) {
let line = motd + ", " + name + "!"
if (name == "Notch") {
line += " Nice to meet you."
}
tell(@a, "${line}")
}
Lists of constants
A const list of ints or strings can be read in function code. len(list) is its length, and list[i] is an item. When i is known only as the function runs, such as a Storage field, each item gets its own branch. An index outside the list gives 0 or "". Shift-right-clicking an item to cycle through modes looks like this:
const modes: [string] = ["Measure", "Replace"]
Type ToolState {
mode: int
}
Storage tool: ToolState
Function next_mode {
tool.mode = (tool.mode + 1) % len(modes)
actionbar(@s, "Mode: ${modes[tool.mode]}")
}
Interpolation
${expr} puts a value into a command:
Function report(points: int) {
/say ${points} points, ${points * 2} doubled
/scoreboard players set @s kills ${points}
}
- Constant values (literals, constant arithmetic) are written into the command at compile time.
- Run-time values turn the line into a macro: the values are copied into storage, and a one-line macro function runs the command.
- Quotes: a string with a
"in it will break a command that wraps it in quotes, just as it would in a hand-written macro.
Text commands
tell, title and actionbar show text markup to players:
Global wave = 1
Objective kills
Function start_wave {
tell(@a, "{red}Wave ${wave}{/} begins. [Rules](run:/function siege:rules)")
title(@a, "{gold}Wave ${wave}", subtitle = "{gray}*hold the banner*")
actionbar(@s, "**${kills[@s]}** kills")
}
tell(target, message)writes/tellraw,title(target, message, subtitle = ...)writes/title ... title(aftersubtitle, when it is given), andactionbar(target, message)writes/title ... actionbar.- The message is a string of markup, a
constorTemplateof typeText, orTextFile("text/rules.mmstext"). ${...}in the message is part of the text component, filled in by the game when the message is shown: a score, a Global, a variable, a selector such as${@s}(the names it finds), or a constant. A computed value such as${n * 2}is worked out just before the command. No macro is needed.
In the web editor, the Preview tab shows the message at the cursor as the game shows it: in chat, as a title with its subtitle, or above the hotbar, with values to try for the scores and selectors in it. The cursor in a .mmstext file that a text command reads shows that message.
Control flow
Function classify(x: int): int {
if (x > 100) {
return 2
} else if (x > 0) {
return 1
}
return 0
}
Function count {
for i in 1..10 { // both ends included, like 1..10 in a selector
if (i % 2 == 0) {
continue
}
/say odd
}
let n = 0
while (n < 5) {
n += 1
if (n == 3) {
break
}
}
}
A function with a return type must end with return on every path.
Calls
A call looks like add(1, 2), or other_ns:add(1, 2) for a function in another namespace. Arguments are passed by position. A call can stand on its own, or be used as a value when the function has a return type.
Parameters are int, bool or string. Functions return int or bool. To hand a string back, assign it to a Global.
Command templates
A Template that returns Commands writes its body where it is called, at compile time. Its loops unroll and its ${...} pieces are filled in, so it costs no function call in the game:
/** A ring of particles around the executor. */
Template particle_ring(p: Id<particle>, radius: int, points: int = 8): Commands {
for i in 0..points - 1 {
/particle ${p} ^${radius * cos(i * 360 / points)} ^1 ^${radius * sin(i * 360 / points)}
}
}
Function celebrate {
As(@a).At(@s) {
particle_ring(flame, 2)
}
}
Its call looks like a function call, so the editor marks it with an inlined hint. Hovering the hint or the name shows the commands it writes. Arguments are compile-time values. A variable, a Global or a Storage field is passed as its value when the function runs, so the template can use it in commands and code but not in a compile-time loop.
Execute blocks
A chain of execute helpers followed by a block runs the block under execute:
Objective hits
Function zap {
As(@e[tag=tower]).At(@s) {
/particle minecraft:flame ~ ~1 ~
hits[@s] += 1
}
Positioned(~ ~1 ~) {
/say above
}
Execute("if entity @e[tag=boss]").In(the_nether) {
/say the boss is here
}
}
| Helper | Subcommand |
|---|---|
As(targets) |
as |
At(targets) |
at |
Positioned(pos), Positioned(targets) |
positioned, positioned as |
Rotated(rot), Rotated(targets) |
rotated, rotated as |
Facing(pos), Facing(targets, anchor) |
facing, facing entity (the anchor defaults to eyes) |
In(dimension) |
in |
Anchored(eyes) |
anchored |
Align(xyz) |
align |
On(relation) |
on |
Summon(entity) |
summon |
Execute("...") |
any subcommands, written as text |
Arguments are written into the command as they appear: selectors as @e[tag=x], coordinates as ~ ~1 ~ or 0, 64, 0, ids as the_nether. The chain is checked against the command tree like a raw command, and Execute("...") covers conditions such as if block and anything without a helper.
A body with one command is written inline, as execute as @a at @s run particle .... A longer body becomes a sub-function, such as demo:zap/_exec0.
The body can use the function's variables. They are not per entity: a body that runs for five entities updates one n five times. Keep per-entity values in an objective, such as hits[@s].
break, continue and return cannot leave an execute block, because the block may run once per entity. Set a variable inside it and act on it afterwards.
Event hooks
These declarations run their body when something happens, as the player it happens to, at their position:
Objective banners
/** Counts every banner a player places. */
OnAdvancement placed_banner = PlacedBlock(location = LocationCheck(predicate = { block = { blocks = #banners } })) {
banners[@s] += 1
}
OnFirstJoin {
/give @s minecraft:bread 4
}
OnRespawn {
/effect give @s minecraft:regeneration 5 1
}
OnAdvancement name = Trigger(...) { ... }runs each time a player meets a trigger. The trigger is one of the criterion helpers, such asPlacedBlock,PlayerKilledEntity,InventoryChangedorTick, and its named arguments are the conditions. It writes the advancement<namespace>:<name>and a reward function,<namespace>:mms_gen/advancement/<name>, which revokes the advancement and then runs the body, so it fires again next time. Commands can refer to it, as in/advancement grant @s only demo:placed_banner.OnFirstJoin { ... }runs once per player, the first time they are in the world with the pack. It uses a tick advancement,<namespace>:mms_gen/first_join, that is never revoked;/advancement revoke @s only <namespace>:mms_gen/first_joinmakes it run again.OnRespawn { ... }runs right after a player respawns. AdeathCountobjective,mms_respawn.<namespace>, notes the death, and the tick function runs the body once the player is alive again.
Item hooks
A custom item's on_* fields each name a function to run when something happens with the item. The functions take no parameters and can be in any file. A field can also hold the commands themselves in a run { ... } block, written like a function body (each command on its own line). That block is compiled as a function of its own, under mms_gen/<item>/. They run as the player, at the player unless the table says otherwise:
CustomItem ember_wand {
base = stick
name = "Ember Wand"
on_right_click = cast_ember
on_use_on_block = light_block
}
Function cast_ember {
/particle flame ^ ^1.5 ^1 0.2 0.2 0.2 0.05 20
}
Function light_block {
/setblock ~ ~1 ~ fire keep
}
| Field | Runs when the player... |
|---|---|
on_right_click |
right-clicks with the item, once per press, unless on_use_on_block or on_right_click_entity runs for that press |
on_hold_right_click |
holds right click with the item: every tick, from the first |
on_use_on_block |
right-clicks a block with the item, once per press. It runs positioned at the centre of the block |
on_right_click_entity |
right-clicks an entity with the item, once per press. It runs at the entity, which has the tag mms_target while the function runs, so @e[tag=mms_target,limit=1] is the entity |
on_hit |
hurts an entity while holding the item in their main hand |
on_kill |
kills an entity while holding the item in their main hand |
on_consume |
finishes eating or drinking the item. It needs a consumable component, or a base item that is food or a potion |
on_pickup |
picks the item up from the ground |
on_break |
uses up the item's durability, so it breaks. It needs a base with durability, or a max_damage component |
on_projectile_hit_block |
shoots an arrow that hits a block (see Projectile hooks). It runs as the arrow, at the point it hit |
on_projectile_hit_entity |
shoots an arrow that hurts an entity. It runs as and at the entity hit |
on_projectile_land |
throws the item, a splash or lingering potion, and it lands. It runs at the spot it landed |
Every hook marks the item with mms_item: "<namespace>:<name>" in its custom_data, wherever it is used: in /give, recipes and loot. A custom_data of your own keeps its keys and gains the marker. Each hook is an advancement, <namespace>:mms_gen/item_hook/<name>/<hook>, whose trigger matches the base item with that marker, and a reward function of the same name that revokes it and calls your function.
Right click. The four right-click hooks use the consumable method from the datapack.wiki guide:
- The item gets a
consumablecomponent that takes about eleven days to eat, with no animation, sound or particles, so right click "uses" it without using it up. It also getsuse_effects, so the player is not slowed down while holding the button. Setconsumableoruse_effectsin the item'scomponentsto use your own instead. - A
using_itemadvancement,.../press, fires on every tick the button is held.on_hold_right_clickruns on each of those ticks. For the others, a score,mms_click.<namespace>, notes that the player is still holding the button. The tick function counts it down, and they run only once it has reached 0. on_use_on_blockandon_right_click_entitystep a ray from the player's eyes, 0.1 blocks at a time, out to theirblock_interaction_rangeandentity_interaction_range. It stops at the first block or entity it meets. The ray counts a whole block as solid, so it can stop short of a slab or a flower's actual shape, and it skips items, arrows, markers and display entities.- A press runs one hook. When the ray reaches a block and the item has
on_use_on_block, or an entity and it hason_right_click_entity, that hook runs. Otherwiseon_right_clickruns, so in theember_wandabove, clicking a block lights it and clicking the air casts an ember. An item with onlyon_right_clickruns it on every press. - The press score also notes which item was pressed, so holding the button while switching to another custom item counts as a new press of that item.
Use cost. Two more fields make each right-click use cost something:
CustomItem ember_wand {
base = blaze_rod
on_use_on_block = kindle
damage_on_use = 1
components = { max_damage = 32 }
}
consume_on_use = ntakesnof the item from the stack.damage_on_use = ncostsnpoints of durability. When the durability runs out, the item breaks with the break sound, andon_breakruns. The item needs amax_damagecomponent. MMS doesn't know vanilla items' durability, so a base item's own durability is not enough. A damageable item cannot stack, so the item getsmax_stack_size = 1unless it sets one, and any other stack size is an error.
The cost is paid once per press, from the hand that holds the item, by whichever hook the press runs. With on_right_click every press runs a hook, so every press pays. Without it, a click at the air runs nothing and costs nothing. on_hold_right_click alone pays no cost.
A base item that already does something on right click keeps doing it, and then none of the right-click hooks runs. A block is still placed and a bow still draws, and a chest still opens when clicked. Pick a base such as stick, paper or amethyst_shard that does nothing on its own. A use_cooldown component has no effect, because the item is never finished being used. Keep a cooldown in a score instead. on_consume cannot be combined with the right-click hooks, because they stop the item from ever being finished.
Projectile hooks
on_projectile_hit_block and on_projectile_hit_entity follow arrows. What they follow depends on the item's base:
- A bow or crossbow: every arrow shot from the item, whatever the arrow is.
- An
arrow,tipped_arroworspectral_arrow: the item itself, once it is shot from any bow, crossbow or dispenser. Prefertipped_arrow: Infinity makes plainarrowitems free, and a tipped arrow can be coloured withpotion_contents.custom_color.
CustomItem explosive_arrow {
base = tipped_arrow
name = "Explosive Arrow"
on_projectile_hit_block = boom
on_projectile_hit_entity = boom
}
Function boom {
/summon tnt ~ ~ ~ {fuse:0s,explosion_power:2f}
}
On a block hit the function runs as the arrow, at the point it hit, so execute on origin is whoever shot it, and kill @s keeps the arrow from being picked up. On an entity hit it runs as and at the entity hurt, and execute on attacker is the shooter.
How it works. An arrow keeps the bow or crossbow it was shot from as its weapon, and when it hits, the game runs the weapon's enchantment effects at the exact spot: hit_block as the arrow, post_attack as the entity it hurt. Each item with projectile hooks gets an enchantment, <namespace>:mms_gen/item_hook/<name>/projectile, whose effects run the functions. The tick function looks once at each new arrow (it tags them mms_shot.<namespace>), and adds the enchantment to the weapon of an arrow that is the item, or that was shot from it. An arrow from a dispenser has no weapon, so it gets a plain bow. The enchantment is never on the item, so it shows in no tooltip and gives no glint. Several items' hooks combine: an Explosive Arrow shot from a bow with hooks runs both.
Thrown potions. on_projectile_land works on a splash_potion or lingering_potion base, and runs where the thrown potion lands, whatever it hits:
CustomItem blast_potion {
base = splash_potion
name = "Blast Potion"
on_projectile_land = boom
}
A thrown potion keeps no weapon, so no enchantment runs for it. Instead the tick function puts a marker on each new one, riding it, with the function's id in its data. The game drops a projectile's passengers where it removes it, so a marker that is no longer riding anything (<namespace>:mms_gen/projectile/riding) marks the spot. The function runs as that marker, at it, and the marker is removed afterwards. The spot is where the potion was at the end of its last tick of flight, so up to about half a block short, and the function can't tell what the potion hit or who threw it. The potion still splashes as usual.
Tests
A Test checks functions without starting the game. It has a setup { } block, then run and expect lines:
Type SiegeState { wave: int = 0 }
Storage game: SiegeState
Function next_wave {
game.wave += 1
for i in 1..game.wave * 2 {
As(@e[tag=siege.spawn]).At(@s) {
/summon zombie ~ ~ ~ {Tags:["siege.enemy"]}
}
}
}
Test second_wave_spawns_four {
setup {
game.wave = 1
/summon marker 0 64 0 {Tags:["siege.spawn"]}
}
run next_wave()
expect game.wave == 2
expect count(@e[tag=siege.enemy]) == 4
}
Each test runs in a fresh world after the pack's load functions. setup holds function code and commands, and its let variables stay in scope for the expect lines. run calls a function, and a test of a function that returns a value can skip it: expect fib(10) == 55. expect checks a condition, and the test stops at the first one that fails. A failing comparison of ints shows both sides: expected game.wave == start, but game.wave is 6 and start is 5. A test can have several run and expect lines, in any order after setup.
mms test runs every test and exits with 1 when one fails. -run <regexp> runs the tests whose names match, and -v lists the passing tests too and prints what each test's commands said. The web editor shows a Tests tab once the project has a test, and runs the tests after every edit. VS Code lists them in its Testing view. All three run the same interpreter, so they give the same results.
Tests compile to functions under <namespace>:mms_test/ that the pack never includes. The interpreter runs the commands that functions compile to (scoreboard, storage, execute, function, return, say and tellraw) and keeps entities that summon creates, with their type, tags and position, for kill, tag, execute as, execute at and if entity. Selectors are @e and @s, with type, tag, limit and sort=arbitrary. There are no players, blocks or ticks, so @a selects no one. A command or selector argument the interpreter does not know fails the test and names it, so a test never passes by skipping a command.
To see what functions become in the data pack, read How functions compile.