Minecraft Data Packs
Minecraft Data Packs
The data packs built in this series can be found in the unicorn-
utterances/mc-datapacks-tutorial repository. Feel free to use it for reference
as you read through these articles!
One thing to note: While data packs are simple to use and enable a huge
amount of functionality, they do have a couple drawbacks. One is that,
while data packs allow most game features to be changed, they do not allow
players to add new features into the game (although some can convincingly
create that illusion with a few tricks).
If you want to add new controls to the game, integrate with external
services, or provide a complex user interface, a Minecraft modding
framework such as Fabric or Spigot might be better for you.
While data packs can add things like custom mobs or items through a
couple workarounds, there are always some limitations. Mods can add
any code to the game with no restrictions on their behavior.
Summary
I usually prefer to write data packs for most things I work on, as I find them
to be more useful to a wider audience because of their easier installation
process. Some players simply don't want the trouble of setting up another
installation folder or using a different Minecraft loader to play with a
specific mod, and data packs can work with almost any combination of
other mods and server technology.
With that said, data packs can certainly be tedious to write at times — while
they are easier to build for simple functionality that can be directly invoked
through commands, more complex behavior might be better off as a mod if
those advantages are more appealing. Nothing is without its drawbacks, and
any choice here is a valid one.
Let's try making a new Minecraft world; I'll name mine "testing" so I can
find it easily. Make sure that the "Allow Cheats" option is set to "ON", then
press "Create World".
If you press "t" to bring up the text chat, then type "/s", a list of commands
should appear! This list can be navigated with the "up" and "down" arrow
keys, and includes every command in the game. If you start typing one out,
it should prompt you for any additional syntax it requires. If the command
turns red, that means the syntax is invalid.
Let's try making a list of commands that can spawn some animals. The
below commands should all work when typed into the text chat, and will
summon the entity at the same location as the player.
shell
1 /summon cow
2 /summon sheep
3 /summon pig
4 /summon goat
5 /summon llama
json
1 {
2 "pack": {
3 "pack_format": 10,
4 "description": "Spawns a bunch of animals around the
player"
5 }
6 }
We then need to create a series of folders next to this file, which should be
nested inside each other as follows:
1 data/fennifith/functions/animals/
The namespace and the animals/ folder can be renamed as you like, but the
data/ and functions/ folders must stay the same for the data pack to work.
Additionally, it is important that the "functions" folder is exactly one level
below the "data" folder. For example, data/functions/ or
data/a/b/functions/ would not be valid structures.
Finally, we should make our .mcfunction file in this folder. I'm going to
name mine spawn.mcfunction:
shell
1 summon cow
2 summon sheep
3 summon pig
4 summon goat
5 summon llama
Note that, while a preceding / is needed to type these commands into the
text chat, it should not be included in the .mcfunction file.
shell
1 1-introduction/
2 pack.mcmeta
3 data/
4 fennifith/
5 functions/
6 animals/
7 spawn.mcfunction
Windows
This can be done by holding down the Shift key and selecting both the
pack.mcmeta and data/ files in the file explorer. Then, right click and
choose "Send to > Compressed (zipped) folder".
This should create a zip file in the same location — you might want to
rename this to the name of your data pack. Right click & copy it so we can
move it to the Minecraft world!
To find the location of your world save, open Minecraft and find the
"testing" world that we created earlier. Click on it, then choose the "Edit"
option, and "Open World Folder".
In the Explorer window that opens, enter the "datapacks" folder. Right click
and paste the zip file here.
MacOS
This can be done by opening your data pack in Finder and selecting both
the pack.mcmeta and data/ files. Control-click or tap the selected files
using two fingers, then choose "Compress" from the options menu.
You should now have a file named "Archive.zip" — you might want to
rename this to the name of your data pack. Then, copy this file so we can
move it to the Minecraft world!
To find the location of your world save, open Minecraft and find the
"testing" world that we created earlier. Click on it, then choose the "Edit"
option, and "Open World Folder".
In the Finder window that opens, enter the "datapacks" folder, then paste
the zip file inside it.
Linux
This can be done using the zip command in your terminal. After cd-ing into
the data pack folder, run the command below to create a zip file.
shell
1 cd 1-introduction/
2 zip -r 1-introduction.zip ./*
That's because, while our function exists, it isn't connected to any game
events — we still need to type a command to actually run it. Here's what the
command should look like for my function:
shell
1 /function fennifith:animals/spawn
If you didn't use the same folder names, autocomplete should help you
figure out what your function is named. After running this command, if you
see all your animals spawn, you have a working data pack!
shell
1 1-introduction/
2 pack.mcmeta
3 data/
4 minecraft/
5 tags/
6 functions/
7 load.json
8 fennifith/
9 functions/
10 animals/
11 load.mcfunction
12 spawn.mcfunction
Note that, while I'm using the fennifith/ namespace for my functions, the
tag file lives under the minecraft/ namespace. This helps to keep some
data isolated from the rest of the game — any files in the minecraft/ folder
are modifying Minecraft's functionality, while anything in a different
namespace is creating something that belongs to my data pack.
Inside load.json, we can add a JSON array that contains the name of our
load function as follows:
json
1 {
2 "values": ["fennifith:animals/load"]
3 }
shell
1 say Hello, world!
To invoke the "load" tag manually, you can either use the /reload
command, or type /function #minecraft:load (note the # symbol used to
specify the tag).
Now, what if we try adding a file for the tick event with the same contents?
We could add a tick.json file pointing to a fennifith:animals/tick
function — and write a tick.mcfunction file for it to run.
The chat window fills up with "Hello, world" messages! Every time the
tick function tag is invoked (the game typically runs 20 ticks per second) it
adds a new message! This is probably not something we want to do.
Could there be a way to check some kind of condition before running our
commands? For example, if we wanted to run our say command when the
player stands on a specific block...
Try experimenting! See if you can find a command that does this — and
check out the next post in this series for the solution!
Conclusion
If your data pack hasn't worked first try — don't worry! There are a lot of
steps here, and the slightest typo or misplacement will cause Minecraft to
completely ignore your code altogether. If you're ever stuck and can't find
the issue, the Unicorn Utterances discord is a great place to ask for help!
So far, we've covered the basics of data packs and how to write them — but
there's a lot more to get into. Next, we'll start writing conditional behavior
using block positions and entity selectors!
Minecraft Data Pack
Programming: Command Syntax
Please note: this guide specifically covers the Java Edition version of
Minecraft. Bedrock Edition does not use data packs, but provides
customization through add-ons.
The data packs built in this series can be found in the unicorn-
utterances/mc-datapacks-tutorial repository. Feel free to use it for reference
as you read through these articles!
A note on tooling
At this point, we're starting to write more complex behavior in our data
packs, and it might be useful to have some tools to check that our
commands are valid while we're writing them.
I use the Visual Studio Code editor with the language-mcfunction extension
by Arcensoth, which provides syntax highlighting and autocompletion for
my commands directly in the text editor. However, there are many similar
extensions with different features, and other text editors likely have their
own plugins for providing this behavior as well.
For example:
shell
1 execute if block ~ ~ ~ air run say "You're standing in
air!"
shell
1 execute unless block ~ ~ ~ air run say "You aren't
standing in air!"
You could also change the block identifier to look for a different type of
block. For example, if block ~ ~ ~ water would make sure that the
player is standing in water.
Position syntax
So what do the tildes (~ ~ ~) mean in the previous command? This is
referring to the current position (in the X, Y, and Z axes) of the player that
is executing the command. There are a few different ways to write positions
like these in Minecraft, which I'll explain here:
Absolute coordinates
Using the tilde symbols (~ ~ ~) will reference the current position that
the command is executed at. This can also be mixed with static values,
such as 32 ~ -94, which will reference the block at (x: 32, z: -94)
using the player's current y-axis.
Relative coordinates
These positions can also be offset by a certain number of blocks in any
direction by adding a number after the tilde. For example, ~2 ~-4 ~3
will move 2 blocks horizontally from the player's x-axis, 4 blocks
down in the y-axis, and 3 blocks horizontally in the z-axis.
To experiment with the position syntax and see where certain positions end
up in the world, we can add coordinates to the /summon command to spawn
entities at a specific location. /summon pig ~ ~ ~ would use the current
position of the player (its default behavior), while /summon pig ~ ~-4 ~
would probably spawn the pig underground. If you spawn too many pigs,
you can use /kill @e[type=pig] to remove them.
An important note when using these positions: for players (and most other
entities), any positions will actually start at the player's feet. If we want to
start at the player's head, we can use the anchored eyes subcommand to
correct this — using directional coordinates, /execute anchored eyes run
summon pig ^ ^ ^4 should summon a pig 4 blocks forward in the exact
center of wherever the player is looking.
The /execute command also has a subcommand that can change its
location in the world: positioned ~ ~ ~. Using this, we can rewrite our
previous command:
shell
1 execute anchored eyes run summon pig ^ ^ ^4
2 execute anchored eyes positioned ^ ^ ^4 run summon pig ~
~ ~
If we use /execute to set the position of this function before it runs, this
will also affect the location of every command in that function.
shell
1 execute anchored eyes positioned ^ ^ ^4 run function
fennifith:animals/spawn
Since our spawn function summons all of the animals at its current
coordinates, we can use the /execute command to change that position!
This command should now spawn all the animals in front of the player,
rather than directly on top of them.
Coordinate grid alignment
In order to align a position with the edge of a block, we can use another
subcommand: /execute align xyz. This will align the command's
position on the X, Y, and Z axes. You can also omit any axes that don't need
alignment, so align x or align xz would also work as expected.
shell
1 execute align xz run summon pig ~ ~ ~
(0.5, 0.5)
shell
1 execute align xz run summon pig ~0.5 ~ ~0.5
Entity selectors
So we've figured out how to use the position of the player, but how can we
refer to other entities in the world? If you've paid attention to the /kill
@e[type=pig] command from earlier, this is actually using an entity
selector to reference all of the pigs in the world. We're using the @e variable
(all entities in the world), and filtering it by type=pig to only select the
entities that are pigs.
And here are some of the ways that we can apply the filter attributes:
shell
1 kill @e[type=pig]
2 execute as @e[type=pig] run kill @s
An important note about how this feature works is that, after the as
@a[type=pig] subcommand, it will actually run any following
subcommands once for every entity it selects. This means that it is
individually running kill @s once for every entity of type=pig.
shell
1 execute as @e[type=pig] if block ~ ~ ~ air run kill @s
You'll notice that this is actually affecting all pigs in the world... unless you
stand underwater or in a block of foliage, in which case it won't do
anything. This is because, while the as <entity> command changes the
executing entity, it doesn't affect the position of the command's execution
— it's still running at your location.
shell
1 execute as @e[type=pig] at @s if block ~ ~ ~ air run
kill @s
This command first selects all @e[type=pig] entities, then - for each pig -
changes the position of the command to the position of @s (the selected
entity). As a result, the position at ~ ~ ~ now refers to the position of @s.
This can also be used with functions, same as before! However, I'm going
to add a limit=5 onto our entity selector here — otherwise it might spawn
an increasing number of entities each time it runs, which could cause lag in
your game if executed repeatedly.
shell
1 execute as @e[type=pig,limit=5] at @s run function
fennifith:animals/spawn
Radius selection
With the [distance=<range>] attribute, entities will be selected if they are
within a specific radius of a position. However, for this to work as expected,
the value needs to be a range, not a number. For example, [distance=6]
will only select entities at a distance of exactly 6 blocks away.
Ranges can be specified by placing two dots (..) as the range between two
numbers. If either side is left out, the range is interpreted as open, and will
accept any number in that direction. By itself, .. is a range that includes all
numbers, 5.. will accept any number above 5, ..5 accepts any number
below 5, and 1..5 accepts any number between 1 and 5.
Area selection
The [x=], [y=], and [z=] attributes will filter entities by their exact
position. However, since entities can move to positions in-between blocks,
their coordinates usually aren't in whole numbers — so it is unlikely that
these filters by themselves will select any entities.
However, these attributes can be paired with [dx=], [dy=], and [dz=] to
select a range of values on the X, Y, and Z axes. For example,
[y=10,dy=20] will filter any entity with a position between Y=10 and Y=30.
Using all of these attributes togther can create a box area to search for
entities within. For example, @e[x=1,y=2,z=3,dx=10,dy=20,dz=30] is
effectively creating a box that is 10 blocks wide, 20 blocks high, 30 blocks
deep, starting at the position (1, 2, 3).
(15, 6)
(5, 1)
x=5 x=15
(5, 1)
Need a hint?
Solution
Note: If you use the as and at subcommands together, be aware that both
will run any consecutive subcommands for every entity they select. So as
@a at @a, on a multiplayer server, will first select every player entity, then
(for every player entity) will run at the position of every player entity. If n =
the number of players, this will result in the command running n*n times
in total.
You can try this with @e[type=pig] to see how many times it prints:
shell
1 # This command will print far more messages than the
number of pigs in your world.
2 execute as @e[type=pig] at @e[type=pig] run say hi
Conclusion
So far, we've started using conditional logic and covered most of the syntax
you'll see in Minecraft commands.
The data packs built in this series can be found in the unicorn-
utterances/mc-datapacks-tutorial repository. Feel free to use it for reference
as you read through these articles!
Previously, this series has covered the structure of a data pack, conditional
statements, and other command syntax. This article will build on top of that
to cover scoreboards, which allows us to keep track of player information
and store variables in our programs.
Storing scores
In many data packs, you might find a need to store information that can't be
directly accessed through an entity or another command. A common way to
do this is through the use of scoreboards, which can store a table of
numbers for each entity or player. These can be used to reference player
statistics, such as the number of blocks mined, or keep track of arbitrary
values in your code.
Creating a scoreboard
We can use the subcommands of /scoreboard objectives to create and
modify scoreboards in a world. Let's try making a scoreboard to track the
number of animals that each player has spawned through our data pack.
shell
1 scoreboard objectives add fennifith.animals_spawned
dummy
What is an objective?
Scoreboard conventions
Namespaced objective names
Players often want to have multiple data packs installed in their world at
once. Since all scoreboards operate globally in the world, we need to make
sure that our scoreboard names will not conflict with any scoreboards used
by other data packs.
For example, what might happen if two data packs want to track different
blocks that the player has mined? The first might create a scoreboard for
blocksMined that tracks stone, while the second might use blocksMined to
track dirt. However, both data packs will be referencing the same
blocksMined scoreboard in the world, which could end up tracking both
stone and dirt, mixing up the behavior of both. We need a way to separate
the scores of these data packs and prevent them from conflicting with each
other.
To accomplish this, it is common to "namespace" the scoreboard names
within your data pack by adding a certain prefix. Here, I've started my
scoreboard names with fennifith.animals to indicate that they belong to
my data pack.
Setting values
We can set values of a scoreboard using the /scoreboard players
subcommands. Most of these subcommands accept two arguments for the
<selector> and <objective> of the score to change. For example, the
following command will set our entry in the fennifith.animals_spawned
table to 1.
shell
1 # set our scoreboard entry
2 # | use the entry of the current player ("fennifith")
3 # | | modify the scoreboard named
"fennifith.animals_spawned"
4 # | | | set "1" as the value of this entry
5 # | | | |
6 scoreboard players set @s fennifith.animals_spawned 1
Entry fennifith.animals_spawned
fennifith 1
If we want to add to this value, we can use the scoreboard players add
subcommand instead. Likewise, scoreboard players remove will subtract
a value from our scoreboard.
shell
1 # add a number to the current scoreboard value
2 # | use the entry of the current player
3 # | | use "2" as the number to add
4 # | | |
5 scoreboard players add @s fennifith.animals_spawned 2
Entry fennifith.animals_spawned
fennifith 3
In certain cases, we want to store values that aren't player specific, but
instead affect our entire data pack. For example, we might want to track the
total number of animals spawned in our world in addition to the number of
animals for each player.
shell
1 scoreboard players set $global fennifith.animals_spawned
4
Entry fennifith.animals_spawned
fennifith 3
$global 4
This trick works because $ is not a character that Minecraft players can
register in their username. As such, we can ensure that the $global entry
will never be used by any actual player or entity in the world.
If we didn't include the $ before our variable name in this snippet, our code
would still work! However, what would happen if a player registered the
username global and tried to use our data pack? Their score would use the
same entry as our global variable, and both would attempt to store their
values in the same place — causing any logic we write to appear broken.
Since the $ is an invalid username character, we can safely use it for global
values without that possibility.
For example, this command will copy the value of our $global variable
into $global_2...
shell
1 # store the result of the command
2 # | place it in "$global_2"
3 # | | run a command that returns the value of "$global"
4 # | | |
5 execute store result score $global_2
fennifith.animals_spawned run scoreboard players get $global
fennifith.animals_spawned
Entry fennifith.animals_spawned
fennifith 3
$global 4
$global_2 4
Scoreboard operations
If we want to set a scoreboard value based on another entry, we can use the
scoreboard players operation subcommand to specify a conceptual
state of existence between the two values.
For example, to make our $global entry in the previous examples equal to
the fennifith entry, we can use the following command:
shell
1 # write the result *to* the $global entry
2 # | set the scoreboards equal to each other
3 # | | get the value *from* @s
4 # | | |
5 scoreboard players operation $global
fennifith.animals_spawned = @s fennifith.animals_spawned
Entry fennifith.animals_spawned
fennifith 3
$global 3
$global_2 4
Math operations
We can also replace the = operation with other math operations that can be
performed on the scoreboard entry.
shell
1 # write the result *to* the $global entry
2 # | add to the existing value
3 # | | get the value *from* @s
4 # | | |
5 scoreboard players operation $global
fennifith.animals_spawned += @s fennifith.animals_spawned
Entry fennifith.animals_spawned
fennifith 3
$global 6
$global_2 4
If we want to divide our $global entry by two, we need to write the divisor
value to another temporary scoreboard first.
shell
1 # set the "$divisor" variable to "2"
2 scoreboard players set $divisor
fennifith.animals_spawned 2
3 # divide the "$global" entry by "$divisor" (2)
4 scoreboard players operation $global
fennifith.animals_spawned /= $divisor
fennifith.animals_spawned
Entry fennifith.animals_spawned
fennifith 3
$divisor 2
$global 3
$global_2 4
Here is a list of all the other operations that can be performed with this
command. lhs denotes the left hand side of the operation (the scoreboard
entry being written to), while rhs denotes the right hand side.
While both sides of these operations accept entity selectors, only lhs
can refer to multiple entities. For example, @e[type=pig] could be
used to set the scoreboards of every pig entity in the game.
In rhs, you may need to add a limit=1 attribute to limit the number of
entities that it can select.
Displaying scores
In order to see what scores are applied to any entity, there are a few
methods of displaying the scoreboard values to players.
In-game display
The /scoreboard objectives setdisplay subcommand can be used to set
a particular scoreboard to display in part of the UI. For example,
/scoreboard objectives setdisplay sidebar
fennifith.animals_spawned will show every player and the number of
animals they have spawned in a sidebar on the right of the screen.
list,which shows the scores next to player names in the tab menu (in
Multiplayer only)
belowName, which displays a player's score underneath their name tag
/tellraw command
The /tellraw command can be used to send a formatted message in the
game chat. It has a wide variety of uses and formatting options, one of
which can embed a scoreboard value into the printed message.
shell
1 tellraw @s ["You have summoned ",{"score":
{"name":"@s","objective":"fennifith.animals_spawned"}},"
animals!"]
In the previous article, you may have noticed that /execute has an
additional if score subcommand. We can use this to check our scoreboard
values as a condition!
Comparing values between scoreboards
With the <, <=, =, >=, or > symbols, we can use this command to compare
values between different scoreboard entries. For example, the following
command compares a player's score between the
fennifith.animals_spawned and fennifith.berries_eaten
scoreboards...
shell
1 # check a score condition...
2 # | if the player's "fennifith.animals_spawned" score...
3 # | | is greater than...
4 # | | | the player's "fennifith.berries_eaten" score...
5 # | | | | send the player a message!
6 # | | | | |
7 execute if score @s fennifith.animals_spawned > @s
fennifith.berries_eaten run tellraw @s "You've spawned more
animals than berries!"
shell
1 # if this score condition is valid...
2 # | for the current player's entry in
"fennifith.animals_spawned"...
3 # | | if its value matches "0"...
4 # | | | send the player a message!
5 # | | | |
6 execute if score @s fennifith.animals_spawned matches 0
run tellraw @s "You haven't summoned any animals yet!"
This command checks if the player's score in "fennifith.animals_spawned"
is exactly equal to 0. However, we could also use ..0 for "less than or
equal", 0.. for "greater than or equal", and so on.
Number ranges can also be bound on both sides — such as 10..50 for
"between 10 and 50" — and are inclusively bound, meaning that a range of
10..50 will also include both 10 and 50 in addition to any numbers in-
between.
shell
1 # check that $nonexistent <= 0
2 # | check that $nonexistent >= 0
3 # | | if neither are true, the score cannot exist
4 # | | |
5 execute unless score $nonexistent
fennifith.animals_spawned matches ..0 unless score
$nonexistent fennifith.animals_spawned matches 0.. run
tellraw @s "The score for $nonexistent in
fennifith.animals_spawned doesn't exist!"
There's another slightly simpler way to check this, which takes advantage of
the maximum value that the game can store in a scoreboard. Minecraft's
scoreboards are limited by Java's minimum/maximum integer size of 32
bits, or a range from -2147483648 to 2147483647. We can write this in a
single condition to check if the score is anywhere within that range.
shell
1 # check if the score is anywhere within Java's integer
bounds
2 # | if not, the score cannot exist
3 # | |
4 execute unless score $nonexistent
fennifith.animals_spawned matches -2147483648..2147483647 run
tellraw @s "The score for $nonexistent in
fennifith.animals_spawned doesn't exist!"
Tracking statistics
Scoreboards can also be created to track game statistics, such as the number
of blocks mined or number of times an item has been used. These can be
found in the game by opening the pause menu in any world or server and
clicking the "Statistics" button — and the names used to reference them can
be found on the Minecraft wiki.
shell
1 scoreboard objectives add fennifith.animals_carrot_stick
minecraft.used:minecraft.carrot_on_a_stick
Note: These statistics are only tracked for players! While we can still
manipulate scoreboard values for other entities using commands, non-
player entities do not have statistics, and their objectives will not be
updated when an action is performed.
While this scoreboard will be updated when its statistic changes, its entries
can also be individually changed by the data pack, so it might not
necessarily reflect the same value as the statistic at all times.
For example, we can create the scoreboard above to track the number of
times a "Carrot on a Stick" has been used. If we then set our entry to 0 in
that scoreboard, its value will stay at 0, regardless of the player's statistic for
that item. If the player then uses the "Carrot on a Stick" again, the statistic
and the scoreboard will both increase by 1.
To check each player's value in our scoreboard, we can use the /execute
if score subcommand along with a number range to conditionally execute
our function if the scoreboard has a value >= 1.
1. We first need to create our scoreboard when our data pack is loaded by
the game — so we'll place the following line in our load.mcfunction:
shell
1 # data/fennifith/functions/animals/load.mcfunction
2
3 # create a new scoreboard tracking the
"carrot_on_a_stick" statistic
4 scoreboard objectives add
fennifith.animals_carrot_stick
minecraft.used:minecraft.carrot_on_a_stick
2. Then, we can place a command in tick.mcfunction to run our
fennifith:animals/spawn function if the scoreboard has a value >=
1.
shell
1 # data/fennifith/functions/animals/tick.mcfunction
2
3 # for every player in the game...
4 # | if their score for "carrot_stick" is >= 1
5 # | | spawn some animals
6 # | | |
7 execute as @a if score @s
fennifith.animals_carrot_stick matches 1.. run function
fennifith:animals/spawn
shell
1 # set the "carrot_stick" score for all players to 0
2 scoreboard players set @a
fennifith.animals_carrot_stick 0
Examples of scoreboard
functionality
Applying unique values to each entity in a selector
If we have an entity selector, such as @e[type=pig], we might want to
assign a different scoreboard value to each entity. This can be done
somewhat concisely using the execute store result subcommand...
shell
1 # create a dummy objective to store unique pig entity
ids
2 scoreboard objectives add fennifith.animals_id dummy
3
4 # set a $counter variable to 0
5 scoreboard players set $counter fennifith.animals_id 0
6
7 # for every entity in @e[type=pig]...
8 # | store the result as the entity's
"fennifith.animals_id" score
9 # | | add "1" to the $counter variable
10 # | | |
11 execute as @e[type=pig] store result score @s
fennifith.animals_id run scoreboard players add $counter
fennifith.animals_id 1
For each pig entity, the scoreboard add command increments our
$counter variable by 1. Conveniently, the add command also returns the
total value of its scoreboard as its result, so we can use that to store the
incremented value as the pig entity's score.
Hint
Solution
Conclusion
This article has covered most of the scoreboard commands we can use, but
there is a lot more that can be done with them. These can be used
throughout functions to write almost any numerical logic; try experimenting
to see what you can accomplish!
In the next post, we'll cover advancements, which provide some alternative
ways to detect specific player actions and other conditions.