Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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 /schedule is 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 (20t for one second, 100t for 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, type schedule function <function> in-game and watch the command suggestions that pop up. For this book we’ll stick with t.

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.
  • append allows 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?”).
  • /schedule when you want something on a slower or one-off timer: once a second, or once after a 5-second delay. Self-rescheduling at 20t is 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 value is 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 roll is 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:

  1. If the count is 1 or more (matches 1..), show that number big on screen with title. The {"score":...} text component prints the live value of #timer in the countdown objective, the same score-in-text idea from Chapter 11’s tellraw.
  2. If the count is exactly 0 (matches 0), show “GO!” instead of a number.
  3. Count down by one with scoreboard players remove (Chapter 11).
  4. 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, this if fails, 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 value for random roll in loot_box_open (and drop the execute store, just running random 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. Use value (silent) for the real, behind-the-scenes roll.

Practice

  1. Auto-restarting countdown. Make countdown_start re-arm itself: after “GO!”, have countdown_tick wait 200t (ten seconds) and then call mypack:countdown_start again, so the countdown loops forever on a ten-second cycle. (Hint: add one more execute if score #timer countdown matches -1 run ... line.)

  2. Cancel button. Write mypack:countdown_cancel that runs schedule clear mypack:countdown_tick so you can stop a running countdown early. Remember the name must be fully namespaced.

  3. 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 exactly 1..100 with no gaps and no overlaps.

  4. Seeded daily prize. Use a named sequence, random value 1..100 mypack:daily, so the roll comes from a reproducible stream. Then experiment with random reset mypack:daily 12345 and 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 function and schedule clear both want the full namespaced IDmypack:countdown_tick, not countdown_tick. A bare name silently fails to match. Double-check the namespace, and confirm the function file actually exists at data/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 write matches .. 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 matches ranges overlap or leave a gap. If 1..60 and 60..90 both include 60, a roll of 60 triggers both prizes; if you write 1..59 and 61..90, a roll of 60 gives nothing. Lay the ranges out so each number from your min to your max is covered by exactly one range, back-to-back, like 1..60, 61..90, 91..100.