Chapter 25 — Function Macros and Return Values
What You’ll Build
Every function you’ve written so far does exactly the same thing every time you run it. Your
house-building commands from Chapter 2 build the same house in the same spot. Your
zombie-summoning /execute from Chapter 4 summons the same zombies. That’s fine for a fixed job, but
what if you wanted one function that could summon any mob, at any size, just by telling it
which mob and which size when you call it? Writing a separate function for every mob would be madness.
This chapter teaches the two features that turn a function from a fixed script into a real tool. The first is macros: a way to pass values into a function, so a single file can behave a hundred different ways. The second is return values: a way for a function to hand an answer back to whoever called it, so a function can act like a question (“is this player inside the arena?”) and the rest of your pack can react to the answer.
By the end you’ll have two new functions in the mypack pack you started in Chapter 9:
mypack:summon_scaled, a macro that summons any mob at any scale, and mypack:in_arena, a function
that reports whether you’re standing inside a region. We’ll test both in the test world you set up in
Chapter 1.
One function, many behaviors
Think back to how a function runs. Put plainly:
“Functions are data pack files, allowing players to run lists of commands.”
Each line is one command, no leading slash, exactly the convention you’ve used since Chapter 9. Up to now those lines have been fixed: whatever you typed in the file is exactly what runs. A macro changes that. A macro lets a line contain a blank that gets filled in with a value you supply at the moment you call the function. Same file, different value, different result.
These special lines are called macro lines:
“Functions can include macro lines, lines preceded by
$. Macro lines act similar to normal commands but can reference the compound NBT tag provided when invoking the function with the function command.”
So a macro line is just a normal command line with one difference: it starts with a dollar sign
$, and somewhere inside it there’s a placeholder waiting to be filled.
The $ prefix and $(key) substitution
Here’s how the blank works. Inside a macro line, you write $(key) wherever you want a value to
appear:
“Values from this compound tag can be referenced with their associated key by using
$(<key>)anywhere in the macro line.”
The word key is just the name of the value, like a label on a box. If you pass in a value
labelled speed, you write $(speed) in the line, and the game swaps in whatever speed was set to,
right before the line runs. The timing is precise:
“Macro lines are evaluated each time before the function executes, substituting the variable specifications with the associated values and parsing the resulting command.”
That last part matters: the substitution happens first, and then Minecraft reads the finished line
as a command. So $(speed) doesn’t stay in the command. By the time the command actually runs, the
$(speed) has already been replaced by, say, 5.
There’s an exact set of characters a key is allowed to use:
Valid characters for a key are:
a-z,A-Z,0-9,_
In other words, keys follow the same letters-numbers-underscore style you already use for names. Pick
clear ones like mob, scale, or target_x.
Here is a worked example. Suppose the function foo:bar contains these three lines:
say This is a normal non-macro command where $(key_1) does not work
$say This is a macro line, using $(key_1)!
$teleport @s ~ ~$(key_2) ~
Look carefully at the difference. The first line has no $ at the start, so it is an ordinary
command, and $(key_1) “does not work” there; it would be sent to chat
literally. The second and third lines start with $, marking them as macro lines, so their
$(key_1) and $(key_2) get filled in.
What Can Go Wrong? Forgetting the
$at the start of the line is the number-one macro mistake. Without it, the line is a plain command and$(key)is left untouched: yoursayprints the literal text$(key_1)and yourteleportfails to parse. The$(...)placeholder only has meaning on a line that begins with$.
Supplying values inline: function mypack:foo {key:value}
A placeholder is no use unless you actually provide the value. The simplest way is to type the values
right after the function name, as a compound (the same {key:value} shape you learned for command
storage in Chapter 12). This form of the /function command works like so:
function <name> <arguments>— “Runs a function or functions in a tag, with arguments for macros.” Thearguments“Specifies arguments for macro functions in a compound NBT tag.”
Here’s an example you can read directly:
“To run a function macro with arguments a=42, b="example":
/function custom:example/test {a: 42, b: \"example\"}”
So to run the foo:bar from above, supplying both keys, you’d type in chat:
/function foo:bar {key_1:"Example String", key_2:10}
Here’s exactly what that produces. The macro say line prints This is a macro line, using Example String!, and notice the quotes around "Example String" are gone in the output. That’s a
rule worth knowing:
“For strings, the value of the string is inserted directly. (That is, without the quotes.)”
And the $teleport @s ~ ~$(key_2) ~ line, with key_2:10, “would teleport you 10 blocks up.” The
number 10 is dropped straight into the command. Here’s how each kind of value is inserted:
“For all numeric types, the value is converted to plain text. The type suffix is not included. (For example,
10bis converted to10)…trueandfalseare equivalent to1band0b, so they are converted to1and0respectively. For lists, compound tags, and the array types, the canonical SNBT representation is used.”
So a string loses its quotes, a number like 10b loses its b suffix and becomes plain 10, and a
whole compound or list is inserted in SNBT form (the NBT text you met in Chapter 12).
Supplying values from the world: the with clause
Typing the compound by hand is great for testing, but often the values you want are already in the
world, stored on an entity, in a block, or in command storage. Instead of copying them out by hand,
you can point the function straight at that source with the with clause:
“Macro functions can also harness stored NBT data using the
withinstruction that may follow the function name. The argument succeedingwithmust specify a NBT source (a block, entity, or command storage) followed by the NBT path of a compound tag.”
The full shape, from the /function command page, is:
function <name> with (block <sourcePos>|entity <source>|storage <source>) [<path>]
Read that as: after with, name where the data lives (a block at some position, an entity, or
a storage) and then, optionally, a <path> pointing at the exact compound tag to use. These are
the same three NBT sources and the same path idea you used with /data in Chapter 12.
This example reads a value off the player and uses it:
execute as @p run function foo:bar2 with entity @s SelectedItem
where foo:bar2 is the single macro line:
$say The player running this function is holding $(count) items with ID $(id)!
Here with entity @s SelectedItem says “take the argument compound from the SelectedItem data on
the entity running this, the item in the player’s hand.” Because that item’s NBT contains count and
id keys, the macro’s $(count) and $(id) get filled in with the held item’s stack size and ID.
One macro line, and it reports whatever the player happens to be holding.
There’s a storage form too: “To run a function macro with arguments from
storage custom:storage: /function custom:example/test with custom:storage.” That points the
function at a whole storage compound you’ve filled in earlier, which is exactly how you’ll feed
mypack:summon_scaled from your mypack:config storage later in this chapter.
Under the Hood (skippable). Why are macro lines parsed each time before the function executes, while ordinary lines are parsed once when the pack loads? Because a macro line isn’t a finished command until its values are filled in, and those values can be different on every call. So Minecraft waits, builds the real command at the last second, and only then reads it. The cost is small but real: a macro line does a little extra work every single time it runs.
The rules of macros (read these once, save yourself an hour)
There are three rules that catch everyone eventually. Learn them now.
1. Every $(key) you use must be provided.
“The compound tag provided must contain one entry for each variable used in the macro function, but may contain entries not referenced by the macro function. If any variables are not provided, or any commands evaluated from macro lines are unparseable, the entire function is not invoked and no commands in it run.”
Two things to take from that. First, you may pass extra keys the function ignores (harmless). Second, if you forget a key the function needs (or your filled-in line turns out to be a broken command), the function doesn’t run at all, not even the non-macro lines before the broken one. It’s all-or-nothing.
2. A function with any macro line cannot be called bare in a tag. The /function command fails if
“There’s any macro line in the function(s)” and no arguments were given. So a macro function can’t just
sit in your tick.json and run on its own: it needs its values supplied at call time. (We’ll come
back to scheduling and ticking macros in Chapter 26.)
3. Substitution is text, not magic. $(key) is replaced by the value’s text and then parsed. If
a value contains something that breaks the command’s grammar, you get an unparseable command and rule
1 kicks in. Keep macro values simple and the substituted line valid.
Functions as questions: return values
Now the second half of the chapter. So far a function just does things. But sometimes you want a function to answer something (“is the player in the arena?”, “did the setup succeed?”) and let the caller decide what to do next. That’s what return values are for.
Here’s the idea:
“After execution, the function can return a return value and a successfulness. The return value is an integer, and the successfulness is failure or success.”
So a function can hand back two things: a return value (a whole number) and a successfulness
(success or failure). It does this with the /return command. And if a function never runs a
return? There’s a name for that:
“If no return command is executed in the function, the function is a void function that does not return any return value or successfulness.”
A void function is the kind you’ve written all along: it just runs its commands and ends, handing nothing back. That’s still perfectly normal; most functions are void. Returning is something you add only when you want an answer.
/return <value> and return fail
The return command lives inside a function:
“A command that can be embedded inside a function to control its execution. It ends function execution and sets the successfulness and the return value of the function.”
There are three forms. The first two are simple. The return value is an integer and the successfulness is either success or failure (you met both in the void-function note above), so:
return <value>hands back an integer. Writingreturn 1stops the function on the spot and makes1its return value;return 5does the same with5.return failstops the function too, but marks the result a failure.
The key thing both share: the function stops right there. Whichever return runs first wins, and any
lines below it don’t run at all.
That “stops right there” is useful on its own, even when you don’t care about the value. Like other
commands, a return placed after an execute if/unless can be made
conditional, so under different conditions a function can end at different lines, “thus achieving more
complex behaviors”: for example, a function that “simulates an if-else statement.” In
plain terms: you can guard the rest of a function behind a check and bail out early when the check
fails, the same shape as an early exit in real programming.
/return run <command>
The third form is the most flexible. Instead of giving a fixed number, you can have return run a
command and hand back that command’s outcome, so return run execute if entity @s[...] returns
success-or-failure depending on whether that entity test passed. This is how a function becomes a real
question: ask the question with a command, and return run forwards the verdict to whoever called the
function. There’s one more wrinkle when the run command branches: a return “can also end
a forking execute command that has multiple branches at the first branch.” So if the command is a
forking execute, only the first branch runs before the function stops.
Under the Hood (skippable). Every command in Minecraft quietly produces two outputs: a success (did it work?) and a result (usually a count of things it affected).
return runis what lets a function adopt a command’s success and result as its own answer; that’s whyexecute store(below) can capture them. The dependable rules are the ones to lean on:returnends the function, returns an integer, ends a forking execute at the first branch, and the function’s answer can be stored withexecute store. If you ever need the exact per-command success/result wording, the wiki’s Return command page lays it out form by form.
Using what a function returns
A returned value is only useful if the caller can read it. There are two ways.
Checking it with execute if function.
“If the function is called by a execute if function command, its return value is checked whether it is not
0.”
So execute if function mypack:in_arena run ... runs the ... only when mypack:in_arena returned a
non-zero value. Zero counts as “no”; anything else counts as “yes.” This lets one function ask
another a yes/no question and branch on the answer, the function version of the if score and
if block checks you learned in Chapters 4 and 11.
Saving it with execute store.
“If the function is called by a function command, the return value and successfulness are returned to the function command as its output values, and then can be stored using execute store.”
So execute store result score @s arena_count run function mypack:count_players captures the function’s
return value into a scoreboard score (or into storage). store result saves the return value;
store success saves the successfulness (1 or 0). This is how you turn a function’s answer into a
number your pack can keep and reuse.
Modern Minecraft. Older tutorials, written before functions could return anything, fake this by having a function
scoreboard players setsome flag and then checking that flag afterward. You can still do that, butreturnplusexecute if function/execute storeis the clean, modern way. One function asks; the caller reads the answer directly. No leftover flag to remember to reset.
Walkthrough: summon any mob at any scale
Let’s build the macro promised at the start. We want one function that summons whatever mob we name, at whatever scale we ask for. Both pieces (the mob’s ID and the scale number) will be macro values.
Create this file:
mypack/data/mypack/function/summon_scaled.mcfunction
$summon $(mob) ~ ~ ~ {attributes:[{id:"minecraft:scale",base:$(scale)}]}
That’s a single macro line (note the leading $). It summons a mob of type $(mob) at your position,
and sets its scale attribute’s base value to $(scale). Both placeholders get filled in when you
call it. Now run it from chat, inline:
/function mypack:summon_scaled {mob:"minecraft:zombie", scale:2.0}
Substitution turns that macro line into summon minecraft:zombie ~ ~ ~ {attributes:[{id:"minecraft:scale",base:2.0}]},
and a double-size zombie appears. Change the call to {mob:"minecraft:chicken", scale:0.5} and you get
a tiny chicken from the very same file. One function, every mob, every size.
Figure (to be captured). a giant zombie and a tiny chicken side by side, both spawned from mypack:summon_scaled
Now feed it from storage instead, to see the with clause work. First stash an argument compound in
your mypack:config storage (the storage you created in Chapter 12). Add a small helper:
mypack/data/mypack/function/set_spawn_args.mcfunction
data modify storage mypack:config SpawnArgs set value {mob:"minecraft:cow", scale:3.0}
Run /function mypack:set_spawn_args once, then call the macro pointed at that compound:
/function mypack:summon_scaled with storage mypack:config SpawnArgs
The with storage mypack:config SpawnArgs says “take the {mob,scale} compound from the SpawnArgs
path of mypack:config storage.” A giant cow appears, and you never retyped the values. That’s the
pattern you’ll reuse whenever the data already lives somewhere in your pack.
Try It! Add a third value,
name, and a second macro line:$data modify entity @e[...] CustomName set value ..., or simpler, append,CustomName:'"$(name)"'thinking carefully about quotes. Pass{mob:"minecraft:zombie", scale:2.0, name:"Brute"}. Remember rule 1: every key you reference must be in the compound, or nothing runs.
Walkthrough: a function that answers a question
Now a returning function. We want mypack:in_arena to answer “is the player standing inside the
arena?”, returning 1 for yes and ending void (no value) for no. We’ll define the arena as a fixed
box and use return run to forward an entity test.
mypack/data/mypack/function/in_arena.mcfunction
return run execute if entity @s[x=0,y=64,z=0,dx=20,dy=10,dz=20]
One line. execute if entity @s[...] tests whether the running player (@s) falls inside the box
that starts at 0 64 0 and stretches 20 blocks along X and Z and 10 up (the dx/dy/dz volume
selector from Chapter 3). return run forwards that test’s success straight out as the function’s
answer: if you’re inside, the function returns a success; if you’re not, the execute test matches
nothing, so the function comes back as a failure rather than a non-zero value, which, to
execute if function, counts as “no.”
Now use the answer. Anywhere in your pack you can write:
execute if function mypack:in_arena run say You are in the arena!
Because execute if function checks whether the return value “is not 0,” the say runs only when
you’re inside the box. You’ve turned a function into a reusable yes/no question.
Figure (to be captured). chat showing “You are in the arena!” appearing only while the player stands inside the marked region
You can also count with it. To record the verdict as a score:
execute store result score @s in_arena run function mypack:in_arena
store result saves the function’s return value into the in_arena score for @s (1 when inside,
0 when not) so later commands can read the score instead of re-asking.
Under the Hood (skippable). Word order matters when you nest these.
return run execute ...puts thereturnon the outside, so the function always ends here and always produces an answer (a success when the test matches, a failure when it doesn’t), which is what you want for a simple yes/no. Writing it the other way around,execute ... run return run ..., makes thereturnrun only when theexecutematches, so a non-match leaves the function running on to whatever comes next. Formypack:in_arenawe want a guaranteed answer, so thereturn run executeform above is the right one.
Practice
-
Greeting macro. Write
mypack:greet_playeras a single macro line:$say Welcome, $(name)!. Call it with/function mypack:greet_player {name:"Steve"}. Then call it with thenamekey missing and confirm the function refuses to run at all (rule 1). Add a second, non-macro line above it (a plainsay) and confirm that line also doesn’t run when the macro key is missing — proof that a failed macro aborts the whole function. -
Read the held item. Recreate the held-item example: a one-line macro
$say You are holding $(count) of $(id)!called with/execute as @p run function mypack:held with entity @s SelectedItem. Hold different items and see the report change. (If you’re holding nothing,SelectedItemis absent and the function won’t run; that’s rule 1 again.) -
Early exit with
return. Writemypack:height_flagwith two lines: firstexecute if entity @s[y=100,dy=320] run return 1(return1if you’re high up), then a finalreturn 0for everyone else. Call it withexecute store result score @s height_flag run function mypack:height_flagand read the score. Confirm that whicheverreturnfires first wins, and the line below it never runs — proof thatreturnstops the function on the spot. -
Combine both ideas. Make
mypack:summon_in_arena: firstexecute unless function mypack:in_arena run return fail(bail out if you’re not in the arena), then a macro$summon $(mob) ~ ~ ~line. Now it only summons when you’re standing inside the box: a returning function gating a macro function.
What Can Go Wrong
-
You forgot the
$at the start of a macro line. Then$(key)is never substituted: asayprints the literal$(key)text, and most other commands fail to parse. Every line that contains$(...)must begin with$. -
A key you reference wasn’t supplied. The rule is blunt: “If any variables are not provided… the entire function is not invoked and no commands in it run.” Nothing happens, not even the lines before the macro. Double-check that your
{...}compound (orwithsource) contains every key the function uses. Extra keys are fine; missing ones are fatal. -
You expected a void function to return something. If your function never reaches a
return, it hands back nothing, andexecute if functiontreats it as… a failed check (no non-zero value). If you want a yes/no answer, make sure every path through the function ends in areturn, or accept that “no return” reads as “no.” -
You put the quotes in twice (or zero times). When a string value is substituted, its quotes are removed:
"Example String"becomesExample String. If the spot where you wrote$(name)needs quotes (like inside a JSON text component), you must add them around the placeholder yourself. If it doesn’t (like a bare mob ID), don’t.
What You Know Now
You can write a macro line (starts with $, contains $(key) placeholders), and supply its
values two ways: inline as a {key:value} compound after the function name, or from a live block,
entity, or command storage with the with clause. You know substitution happens just before the
line runs, that strings lose their quotes and numbers lose their suffixes, and that a single missing
key aborts the whole function. You can make a function answer a question with /return <value>,
return fail, or /return run <command>, you know a function with no return is a void
function, and you can read a function’s answer with execute if function (non-zero = yes) and
save it with execute store result. One function can now behave a hundred ways and report back,
the foundation for the timing, randomness, and minigame work coming next.