Chapter 26 — Timing and Randomness
What You’ll Build
So far every function you’ve written runs right now: the moment it’s called, it does its work and finishes. But two of the most fun things in Minecraft happen on their own schedule. A countdown says “3… 2… 1… GO!” with a pause between each number. A loot box gives you a different prize every time you open it. Neither of those is “do it all this instant”: one needs to happen later, and the other needs to be unpredictable.
This chapter teaches the two commands that make those possible. /schedule runs a function after
a delay you choose, and a scheduled function can even schedule itself again, which gives you a
repeating loop without touching the tick tag. /random rolls random numbers, either quietly (just
for you) or out loud (for everyone), and execute store lets a command branch on whatever number
came up. By the end you’ll have added two systems to your mypack pack: a delayed-start countdown
timer that announces each second by re-scheduling itself, and a loot box that rolls a weighted
random prize. This chapter builds on functions (Chapter 9), scoreboards (Chapter 11), and the
execute store and execute if score skills from Chapter 4.
Running a function later: /schedule
A schedule is a request you hand to the server: “run this function, but not yet, wait a bit
first.” The /schedule command delays the execution of a function: the function is executed by the
server after a specified amount of time passes.
Here’s the full Java Edition syntax:
schedule function <function> <time> [append|replace]
schedule clear <function>
The first form adds a schedule; the second removes one. The <function> is the namespaced name
of a function you’ve written, exactly the kind of name you’ve been using with /function since
Chapter 9, like mypack:countdown_tick. The <time> is how long to wait.
Modern Minecraft. Older tutorials sometimes fake delays by counting ticks in a tick-tag function (“add 1 every tick, and when the count hits 100, do the thing”). That still works, but
/scheduleis the purpose-built tool: you say when once, and the server remembers for you. Reach for the counting trick only when you genuinely need to check something every tick.
Time units and the 1t surprise
The <time> is a number with a unit letter on the end. The t suffix (for example
schedule function <function> 1t) means ticks. A tick is one step
of the game loop, and as Chapter 7 recorded, Minecraft runs 20 ticks per second, so an in-game day
is 24,000 ticks (about 20 minutes). That gives you a handy conversion: one second is 20t, five
seconds is 100t, and so on.
Tip. Ticks are all you need for everything in this chapter (
20tfor one second,100tfor five), and counting in ticks keeps the timing precise. The game also accepts other unit letters on the<time>argument; if you’re curious which ones, typeschedule function <function>in-game and watch the command suggestions that pop up. For this book we’ll stick witht.
There’s one genuinely surprising detail about ticks and scheduling worth a warning: the delay time of
1t does NOT always mean one tick of delay. Instead, it schedules the function for the upcoming phase
for scheduled functions. Each game tick has phases, and scheduled functions run in their own phase
after the #minecraft:tick functions. So specifying 1t in #minecraft:tick functions makes the
function run within the same tick, while specifying 1t in scheduled functions makes the function run
in the next tick. You almost never need
to think about this, but if a 1t schedule ever fires sooner or later than you expected by a single
tick, this is why. For any delay of two ticks or more it behaves exactly as you’d guess.
Canceling a schedule, and append vs replace
What happens if you schedule the same function twice before the first one fires? That’s what the last argument controls. There are two modes:
replace(default) simply replaces the current function’s schedule time.appendallows multiple schedules to exist at different times.
So replace is the default: if mypack:countdown_tick is already scheduled and you schedule it again,
the new time replaces the old one: there’s still only one pending run. Here’s a concrete reason this
is useful: if a function is scheduled to be executed in 30 seconds, and before it is executed you want
to modify the execution time, you can use replace mode to set a new schedule to replace the original.
Use append only when you deliberately want the same function queued to fire at
several different times at once.
To cancel a pending schedule before it fires, use schedule clear:
schedule clear <function>
One catch to watch for: the function name here should be a namespaced ID (minecraft: cannot be
omitted). In other words, always write the full namespace:path: mypack:countdown_tick, never just
countdown_tick.
A loop that schedules itself
Here’s the trick that makes /schedule powerful: a scheduled function can schedule itself. When
mypack:countdown_tick runs, its last line can be schedule function mypack:countdown_tick 20t, and
now it’ll run again in one second, where it will schedule itself again, forever (or until you tell it
to stop). That’s a repeating loop that lives entirely inside one function, with no entry in the tick
tag.
So when should you use /schedule versus adding a function to the minecraft:tick tag (the way Chapters
4, 11, and 12 did)?
- Tick tag when you need to check something constantly, every single tick, 20 times a second (like “is anyone standing on gold?”).
/schedulewhen you want something on a slower or one-off timer: once a second, or once after a 5-second delay. Self-rescheduling at20tis far gentler on the game than a tick function that runs 20× as often and counts to 20 each time.
Random numbers: /random
The other half of this chapter is unpredictability. The /random command generates a random integer,
and it comes in two flavors, which differ by who gets told the result:
random (value|roll) <range>
Here’s the difference: if it is value, the result is displayed in chat only to the player executing
the command; if roll, the result is broadcast to all players. So:
random valueis a quiet roll. You usually want this inside a data pack, because you don’t want spammy numbers in everyone’s chat; you’re going to use the number, not show it.random rollis a loud roll, announced to the whole server. Great for a visible “everybody sees the dice” moment, like a party game.
The <range> is written as two numbers joined by two dots (min..max), and the roll picks one
integer from that range, ends included. For example, random roll 1..5 rolls a random number between
1 and 5 and writes it in chat. One rule to know: the size of the range (calculated by max - min + 1)
should be between 2 and 2147483646, so you always need at least two possible values.
Branching on a roll with execute store
A number you can’t react to isn’t much use. To make decisions, you capture the roll into a scoreboard and then test it, both skills you already have from Chapters 4 and 11. Here’s exactly that pattern:
execute store result score @p random_number run random value 1..10
This generates a random number between 1 and 10 and stores it in a scoreboard
called random_number for the nearest player. execute store result score <target> <objective> run <command> takes whatever number the command produces and writes it into a scoreboard, here the silent
random value 1..10. Once the number is in a scoreboard, you branch on it with execute if score ... matches <range>, the range test from Chapter 4: execute if score @p random_number matches 1..3 run ...
fires only when the roll landed between 1 and 3. That single idea (roll into a score, then test the
score against ranges) is the engine behind the weighted loot box you’ll build below.
Named random sequences: reproducible randomness
Plain random value 1..10 is freshly random every time. But sometimes you want randomness that’s
reproducible: the same “random” results every run, like a puzzle map where the layout should be
surprising but identical for every player. That’s what a named random sequence is for. Here’s the
longer form:
random (value|roll) <range> <sequence>
The <sequence> is the resource location of a random sequence, a namespaced name like
mypack:loot, just like a function name. If the sequence does not exist, it is created with the random
sequence settings of the world. Each named sequence has its own seed (this seed is called the salt
value), so two different sequence names give two independent streams
of numbers, and a given sequence with a given seed always produces the same stream.
You control a sequence’s state with random reset:
random reset *
random reset <sequence> [<seed>] [<includeWorldSeed>] [<includeSequenceId>]
random reset * removes all the random sequences in the world. Resetting a single sequence removes
and re-creates a random sequence, optionally with a <seed> you choose, which is how you pin a
sequence to a known starting point so it replays identically. The two extra flags,
<includeWorldSeed> and <includeSequenceId>, both default to true and decide whether the world
seed and the sequence’s own name get mixed into the seeding; for now the default behavior is fine.
Under the Hood (skippable). Named sequences are the same machinery loot tables use for their randomness: a sequence is created with the settings of the world when called by a loot table. That’s why two players opening the same loot table can be given reproducible results. You don’t need this for the loot box below (we’ll keep it plainly random), but it’s the door into seeded, shareable randomness when you want it.
Walkthrough: a delayed-start countdown timer
Let’s build a countdown that, when started, waits a moment and then announces “3… 2… 1… GO!”, one number per second, using nothing but self-rescheduling. We’ll track the current number in a scoreboard so each tick knows where it is.
First, an objective to count on. Chapter 11 taught scoreboard objectives add; we’ll create one named
countdown. We’ll store the count on a fake player (a score holder that isn’t a real entity, here
#timer), the same fake-player trick from Chapter 11 for keeping a single shared number. Here’s the starter
function:
mypack/data/mypack/function/countdown_start.mcfunction
scoreboard objectives add countdown dummy
scoreboard players set #timer countdown 3
tellraw @a {"text":"Get ready..."}
schedule function mypack:countdown_tick 20t replace
The last line schedules the tick function to run in 20t (one second) from now. That one-second
gap is the “delayed start”: nothing is announced instantly; the first number appears after the pause.
We pass replace (the default, written out here so it’s obvious) so that starting the countdown twice
doesn’t stack up two overlapping countdowns.
Now the tick function. Each time it runs it announces the current number, counts down by one, and then either schedules itself again or stops:
mypack/data/mypack/function/countdown_tick.mcfunction
execute if score #timer countdown matches 1.. run title @a title {"text":"","extra":[{"score":{"name":"#timer","objective":"countdown"}}]}
execute if score #timer countdown matches 0 run title @a title {"text":"GO!","color":"green"}
scoreboard players remove #timer countdown 1
execute if score #timer countdown matches 0.. run schedule function mypack:countdown_tick 20t replace
Reading it line by line:
- If the count is 1 or more (
matches 1..), show that number big on screen withtitle. The{"score":...}text component prints the live value of#timerin thecountdownobjective, the same score-in-text idea from Chapter 11’s tellraw. - If the count is exactly 0 (
matches 0), show “GO!” instead of a number. - Count down by one with
scoreboard players remove(Chapter 11). - If the count is still 0 or more (
matches 0..) after decrementing, re-schedule this same function for one more second. Once the count drops to -1, thisiffails, nothing gets re-scheduled, and the loop quietly stops.
To run it, call function mypack:countdown_start (you’ll wire a trigger for it in the projects of Part
VIII; for now, calling it by hand is fine). You’ll see “Get ready…”, a one-second pause, then 3, 2, 1,
GO!, each a second apart, driven entirely by /schedule.
Figure (to be captured). the title “3” filling the screen mid-countdown, with “Get ready…” in chat above
Notice there is no new entry in mypack/data/minecraft/tags/function/tick.json. That’s the whole
point. The loop runs itself.
Walkthrough: a weighted random loot box
Now the unpredictable system. We want a loot box that, when opened, rolls a number and gives a prize, but not all prizes equally likely. We’ll make common prizes cover a wide range of numbers and rare prizes a narrow one. This is the same “weights” idea you met with loot tables in Chapter 16, done here by hand with ranges.
We’ll roll 1..100 so the math reads like percentages. Say we want: 60% dirt (common), 30% iron
(uncommon), 10% a diamond (rare). That maps to ranges 1..60, 61..90, and 91..100. First, roll
into a scoreboard:
mypack/data/mypack/function/loot_box_open.mcfunction
scoreboard objectives add loot_roll dummy
execute store result score #roll loot_roll run random value 1..100
function mypack:loot_box_give
The middle line is the execute store result score ... run random value ... pattern from earlier: it
rolls a silent number from 1 to 100 and stores it on the fake player #roll in the loot_roll
objective. Then it calls the giver function to act on that roll. Now the branching:
mypack/data/mypack/function/loot_box_give.mcfunction
execute if score #roll loot_roll matches 1..60 run give @p minecraft:dirt 16
execute if score #roll loot_roll matches 1..60 run tellraw @p {"text":"Common: a stack of dirt.","color":"gray"}
execute if score #roll loot_roll matches 61..90 run give @p minecraft:iron_ingot 4
execute if score #roll loot_roll matches 61..90 run tellraw @p {"text":"Uncommon: 4 iron!","color":"white"}
execute if score #roll loot_roll matches 91..100 run give @p minecraft:diamond 1
execute if score #roll loot_roll matches 91..100 run tellraw @p {"text":"RARE: a diamond!","color":"aqua"}
Each pair of lines covers one prize: an execute if score ... matches <range> that gives the item, and
a matching one that announces it. Because the three ranges (1..60, 61..90, 91..100) don’t overlap
and together cover every number from 1 to 100, exactly one prize fires on every open. To change the
odds, just resize the ranges: make diamond 96..100 and it drops to a 5% chance. To open the box, call
function mypack:loot_box_open.
Try It! Swap
random valueforrandom rollinloot_box_open(and drop theexecute store, just runningrandom roll 1..100) when you want the number shouted to the whole server before the prize appears, a fun “watch the dice” moment for a party. Usevalue(silent) for the real, behind-the-scenes roll.
Practice
-
Auto-restarting countdown. Make
countdown_startre-arm itself: after “GO!”, havecountdown_tickwait200t(ten seconds) and then callmypack:countdown_startagain, so the countdown loops forever on a ten-second cycle. (Hint: add one moreexecute if score #timer countdown matches -1 run ...line.) -
Cancel button. Write
mypack:countdown_cancelthat runsschedule clear mypack:countdown_tickso you can stop a running countdown early. Remember the name must be fully namespaced. -
Four-tier loot box. Add a fourth prize tier to the loot box (say a 1% “jackpot” of an enchanted golden apple at
100..100) and shrink the other ranges so they still add up to exactly1..100with no gaps and no overlaps. -
Seeded daily prize. Use a named sequence,
random value 1..100 mypack:daily, so the roll comes from a reproducible stream. Then experiment withrandom reset mypack:daily 12345and watch the same sequence of rolls repeat: reproducible randomness in action.
What Can Go Wrong
What Went Wrong? “My scheduled function never runs.” The most common cause is the function name.
schedule functionandschedule clearboth want the full namespaced ID —mypack:countdown_tick, notcountdown_tick. A bare name silently fails to match. Double-check the namespace, and confirm the function file actually exists atdata/mypack/function/countdown_tick.mcfunction.
What Went Wrong? “It re-schedules forever and won’t stop.” A self-rescheduling loop only stops if some run skips the re-schedule line. In the countdown, the final
execute if score #timer countdown matches 0..is what stops it: once the count goes below 0, the condition is false and nothing new is queued. If you forget that guard (or writematches ..with no bound), the function re-schedules unconditionally and runs forever. If you’re stuck in a loop,schedule clear <function>cancels the pending run.
What Went Wrong? “The loot box gives two prizes, or none.” This happens when your
matchesranges overlap or leave a gap. If1..60and60..90both include 60, a roll of 60 triggers both prizes; if you write1..59and61..90, a roll of 60 gives nothing. Lay the ranges out so each number from yourminto yourmaxis covered by exactly one range, back-to-back, like1..60,61..90,91..100.