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 /reload keeps 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 as kills["#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, a Global, a score or a Storage field. It is not part of a larger expression, so write let n = /data get ... and then use n.
  • A result is a whole number. To read text, such as a block's id, copy it into a Storage field 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 (after subtitle, when it is given), and actionbar(target, message) writes /title ... actionbar.
  • The message is a string of markup, a const or Template of type Text, or TextFile("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 as PlacedBlock, PlayerKilledEntity, InventoryChanged or Tick, 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_join makes it run again.
  • OnRespawn { ... } runs right after a player respawns. A deathCount objective, 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 consumable component that takes about eleven days to eat, with no animation, sound or particles, so right click "uses" it without using it up. It also gets use_effects, so the player is not slowed down while holding the button. Set consumable or use_effects in the item's components to use your own instead.
  • A using_item advancement, .../press, fires on every tick the button is held. on_hold_right_click runs 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_block and on_right_click_entity step a ray from the player's eyes, 0.1 blocks at a time, out to their block_interaction_range and entity_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 has on_right_click_entity, that hook runs. Otherwise on_right_click runs, so in the ember_wand above, clicking a block lights it and clicking the air casts an ember. An item with only on_right_click runs 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 = n takes n of the item from the stack.
  • damage_on_use = n costs n points of durability. When the durability runs out, the item breaks with the break sound, and on_break runs. The item needs a max_damage component. 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 gets max_stack_size = 1 unless 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_arrow or spectral_arrow: the item itself, once it is shot from any bow, crossbow or dispenser. Prefer tipped_arrow: Infinity makes plain arrow items free, and a tipped arrow can be coloured with potion_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.