0% found this document useful (0 votes)
169 views41 pages

Minecraft Data Packs

This document provides an introduction to Minecraft data pack programming. It explains what data packs are, their advantages over mods, and how to structure a basic data pack folder. It also demonstrates how to write a simple data pack that spawns animals using command functions, and how to install and test the data pack in a new Minecraft world.

Uploaded by

will punk
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
169 views41 pages

Minecraft Data Packs

This document provides an introduction to Minecraft data pack programming. It explains what data packs are, their advantages over mods, and how to structure a basic data pack folder. It also demonstrates how to write a simple data pack that spawns animals using command functions, and how to install and test the data pack in a new Minecraft world.

Uploaded by

will punk
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 41

Table Of Contents

1. Minecraft Data Pack Programming: Introduction


2. Minecraft Data Pack Programming: Command Syntax
3. Minecraft Data Pack Programming: Scoreboard Usage
Minecraft Data Pack
Programming: Introduction
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!

What is a data pack?


Minecraft's data pack system allows players to fundamentally modify
existing behavior of the game by "replacing" or adding to its data files. Data
packs typically use .mcfunction files to specify their functionality as a list
of commands for the game to run, and .json files for writing advancements
or loot tables.

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.

Advantages of Minecraft mods


Can communicate with external services
Mods can perform HTTP requests, talk to other applications, or use
any library that is compatible with Minecraft's Java runtime.

Able to modify the user interface and settings menus

Some data packs have used innovative (and highly complex)


workarounds to this using modified item textures, but in general,
Minecraft's controls and user interface cannot be fundamentally
changed without the use of a mod.

Can add entirely new functionality to the game

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.

More performant than data packs when running large operations

This obviously depends on how well their functionality is written, but


mods can provide much better performance with multithreading,
asynchronous code, and generally faster access to the data they need.
In comparison, data packs are limited by the performance of the
commands available to them.

Advantages of data packs


Easy to install on any Minecraft (Java Edition) version

Data packs are widely supported in almost any Minecraft launcher,


mod loader, and hosting provider. In comparison, mods will require
players to set up a specific Minecraft installation (such as Fabric or
Forge) before they can be used.

Generally simpler to test and write


While some modding tools can provide fairly seamless testing &
debugging, they all require programming knowledge in Java and/or
Kotlin, and it can be tedious to set up a development environment for
that if you don't have one already. Most data pack behavior can be
written in any text editor and tested right in the text chat of your game!

Safer to make mistakes with

Since data packs are restricted to interacting with the commands


Minecraft provides, it typically isn't possible to do anything that will
entirely break your game. Mods can run any arbitrary code on your
system, however — which means there's a higher chance that things
can go wrong.

Typically better update compatibility

While some commands do change in new Minecraft updates, I have


(anecdotally) found the changes to be less impactful than the work
required to bring mods up to date with new versions. Since mods often
use mixins and directly interact with Minecraft's internal code, they
can be affected by under-the-hood changes that wouldn't make any
difference to a data pack.

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.

Writing our first Minecraft


function
Data packs make frequent use of .mcfunction files, which are text files that
contain a list of commands for Minecraft to run. But how do we know
which commands to write? We can actually test them in Minecraft first!

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

Now let's see if we can put these into a function!

Building a data pack folder structure


We'll need to make a new folder to build our data pack in — I'll name mine
"1-introduction" to reflect the name of this article. We then need to place a
"pack.mcmeta" file inside this folder to describe our pack.

json
1 {
2 "pack": {
3 "pack_format": 10,
4 "description": "Spawns a bunch of animals around the
player"
5 }
6 }

The "pack_format": 10 in this file corresponds to Minecraft 1.19;


typically, the format changes with each major update, so for newer versions
you might need to increase this number...

Minecraft Version "pack_format" value


1.19 "pack_format": 10
1.18.2 "pack_format": 9
1.18-1.18.1 "pack_format": 8
1.17-1.17.1 "pack_format": 7

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/

In this path, the fennifith/ folder can be called a namespace — this


should be unique to avoid potential clashes if someone tries to use multiple
data packs at once; if two data packs use exactly the same function name, at
least one of them won't work as expected.

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.

We should now have our data pack organized as follows:

shell
1 1-introduction/
2 pack.mcmeta
3 data/
4 fennifith/
5 functions/
6 animals/
7 spawn.mcfunction

Installing & testing the data pack


To turn this folder into a data pack, we simply need to convert the "1-
introduction" folder into a zip file.

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 ./*

Then, assuming you named your world "testing", the command ls


~/.minecraft/saves/testing should list that world's save files. Run mv
./1-introduction.zip ~/.minecraft/saves/testing/datapacks/ to
move the zip file into the world's datapacks folder.
Now that we've installed the data pack, you should be able to enter the
world save again (or use the /reload command if you still have it open).
But nothing happens!

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!

Specifying a function tag


In order to run a function automatically, Minecraft provides two built-in
function tags that run during specific events: load (when the world is
opened) and tick (every game tick).

Using the "load" event


We'll start with load — for which we'll need to create two new files in our
folder structure! Below, I'm creating a new load.mcfunction next to our
previous function, and a minecraft/tags/functions/load.json file for
the load tag.

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 }

In load.mcfunction, I'll just write one command for testing:

shell
1 say Hello, world!

Testing the "load" event


If you repeat the steps to install the data pack now, you should see a "Hello,
world" message appear in the chat window! You could modify this message
to display information about your data pack or explain how to use it.

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).

And the "tick" event...


Be aware that when using the tick event, it is very easy to do things
that cause humongous amounts of lag in your game. For example,
connecting this to our spawn.mcfunction from earlier might have
some adverse consequences when summoning approximately 100
animals per second.

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.

Conditional logic with the


"/execute" command
In the previous post, we ended on an interesting question — how do we
write a command that only executes if the player is standing on a particular
block?
Well, Minecraft actually has a specific command for checking preconditions
and other attributes of a command before running it - the /execute
command!

This command can be used with an indefinite number of arguments, which


might make it confusing to understand by reading its documentation — but
this effectively means that you can add any number of preconditions to this
command.

For example:

shell
1 execute if block ~ ~ ~ air run say "You're standing in
air!"

This uses two subcommands of the execute command: if block ~ ~ ~


air checks if the block identifier at the player's location is minecraft:air,
and run say "You're standing in air!" will invoke the say command if
the previous conditions have passed.

Try running this command in Minecraft! As long as you're standing in an


air block, you should see its message appear in the chat. If you stand
underwater or in any block that isn't air (such as bushes/foliage), it should
stop executing.

Standing in air Standing in water


If we want to negate this condition, we can replace the if subcommand
with unless — this will print its message as long as the player isn't
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

Coordinates can be written as a fixed position in the world - say, 32 60


-94 (these coordinates can be obtained by opening the F3 debug screen
and finding the "Targeted block" position.

Current coordinates (tilde notation)

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.

Directional coordinates (caret notation)

Similar to relative coordinates, directional coordinates (^ ^ ^) will


start from wherever the command is executed from. However, any
offsets will be applied relative to wherever the current player or entity
is looking. For example, ^2 ^-4 ^3 will move 2 blocks to the left of
the player, 4 blocks downward, and 3 blocks in front of the direction
the player faces.

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.

Positions in an "/execute" subcommand


In the following sections, it might help to keep in mind that every
command has a specific context that it executes in. This context
consists of a position in the world and a selected entity that runs the
command. When you type a command in Minecraft's text chat, the
position is your current location in the world, and the selected entity
is your player.
This context affects what blocks, locations, and entities certain
commands and syntax will be referring to. The /execute command
can change this context for any commands that it runs, which is what
you'll see in the following example...

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 ~
~ ~

These two commands do the same thing! When we use positioned ^ ^


^4, we're moving the location of our command to those coordinates. Our
summon pig command then uses its current position at ~ ~ ~, which is in
the location we've moved it to.

Using "/execute" with functions

If you recall the function we created in the previous chapter, we ended up


making a single command (/function fennifith:animals/spawn) that
spawns a bunch of animals at once.

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.

We can use this to ensure that a summoned entity is always spawned in


alignment with the block grid, and not partway in-between block
coordinates:

shell
1 execute align xz run summon pig ~ ~ ~

However, an important thing to note about Minecraft's coordinate system is


that whole numbers do not refer to the center of a block. Instead, they
are aligned with the bottom corner in the negative direction of each axis.

This means that, if you summon an entity at 0 ~ 0, it will actually end up


on the corner of the block at (0, 0). To fix this, you'll need to correct for the
offset by moving it 0.5 on each axis; i.e. 0.5 ~ 0.5.
(0, 0)

(0.5, 0.5)

Thus, to summon an entity in the center of a block, we can use this


command:

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.

Here's a list of some other selector variables we can use:

@p targets only the nearest player to the command's execution


@a targets every player in the world (useful for multiplayer servers /
realms)
@e targets every player, animal, and entity in the world
@s targets only the entity that executed the command

And here are some of the ways that we can apply the filter attributes:

[type=player] selects the entity type (pig, cow, item_frame, etc.)


[gamemode=survival] can select players in a specific game mode
(creative, spectator, etc.)
[limit=1] will restrict the total number of entities that can be picked
by the selector
[sort=nearest] will affect the order of entities selected (furthest,
random, arbitrary)

Using these selectors, we can use @e[type=pig,sort=nearest,limit=3] to


reference the three nearest pigs to player that executes the command.

What if we use /kill @a[type=pig]? This won't select anything —


because @a only selects player entities. Similarly, @s[type=pig] won't
select anything either, because @s refers to the entity that runs the command
— which is you, an entity of type=player.

Entities in an "/execute" subcommand


Just like how /execute positioned <x> <y> <z> can be used to set the
position of the command it runs, the /execute as <entity> subcommand
can be used to set the entity that runs the command. This will effectively
change the entity that @s refers to in anything it executes. Let's use this with
our /kill @e[type=pig] command!

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.

Entity positions in an "/execute" subcommand


So, we could use this with our if block ~ ~ ~ air command from earlier
to select only the pig entities that are standing in a block of air... but that
might not work quite as we expect.

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.

While we can use relative positions with the positioned ~ ~ ~


subcommand, you'll notice that there isn't any way to refer to a selected
entity in this syntax... that's why we'll need to use the at <entity>
subcommand instead!

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

Filtering entities by position


In addition to the filter attributes we discussed earlier, the [distance=
<range>] and [x=<number>,dx=<number>] attributes can be used to select
entities based on their location in the world.

Here are a few examples of this in use:

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.

@e[distance=..5] @e[distance=5..] @e[distance=2..5]


@e[distance=..5] @e[distance=5..] @e[distance=2..5]

r=5 r=5 r=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).

@e[x=5,z=1] @e[x=5,dx=10] @e[x=5,z=1,dx=10,dz=5]


@e[x=5,z=1] @e[x=5,dx=10] @e[x=5,z=1,dx=10,dz=5]

(15, 6)

(5, 1)
x=5 x=15

(5, 1)

Challenge: Using "/execute" in our


tick.mcfunction
In the previous post, we got our data pack to print a message on every game
tick. Let's try to change that — see if you can write a command that will
check the block below the player to see if it is air. If the block underneath
the player is air, they are probably falling, so let's print
"aaaaaaaaaaaaaaaaaaaa" in the text chat.

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.

Between articles, feel free to experiment with other commands, such as


/setblock or /playsound. Most of these won't be directly mentioned in the
rest of this series, so it'll be useful to read through this list to figure out what
each command can do.

In the next post, we'll cover an entirely different feature of Minecraft:


player scoreboards! These will allow us to keep count of different
variables, detect certain in-game actions, and store a player-specific or
global state in our data packs.
Minecraft Data Pack
Programming: Scoreboard Usage
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!

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

This creates an objective named fennifith.animals_spawned that is


connected to the dummy game statistic. We'll talk about other statistics later
on, but the dummy statistic effectively means the scoreboard will only be
modified if you set its values yourself.

What is an objective?

The naming of "objective" and "scoreboard" can be a point of confusion. In


this article, for simplicity's sake, they can be considered as two names for
the same thing — even though they might have slightly different meanings.

Generally speaking, an "objective" is a relation between a set of scores and


a statistic. Here, the objective name is fennifith.animals_spawned and
the statistic is dummy. The objective contains its scores for each player in the
form of a scoreboard.

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.

Creating & removing scoreboards

Typically, you'll want to create any scoreboards you need in a


load.mcfunction function, connected to the #minecraft:load function
tag.

Some data packs additionally create an uninstall.mcfunction file, not


connected to any function tag, that can be executed to remove all of the data
pack's scoreboard objectives. This is useful for when a player wants to
remove your data pack from their world without leaving any of its behavior
behind.

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

Note: Be wary of the difference between /scoreboard objectives


add and /scoreboard players add, as they are easy to confuse — I
even mixed them up a few times while writing this article! The
objectives subcommands are used exclusively for creating or
removing entire scoreboards, while the players subcommands can
modify specific entries in existing scoreboards to change their values.

objectives add is saying to "add a new scoreboard", while players


add is increasing the value of a scoreboard entry by a given number.

Using global entries

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.

We can do this by referencing a nonexistent player. The scoreboard will


include an entry for any entity or name, regardless of whether it actually
exists in our world — so by using a name that will never exist, we can
reference it globally from anywhere in our code.

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.

Using the "/execute store" subcommand

Each Minecraft command provides a "success" and a "result" value which


specify if the command was successful — and if so, what value it returned.

The execute store subcommand can be used to designate a place to store


these values, such as a scoreboard entry.

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

While this example will successfully copy our $global variable to


$global_2, there is somewhat shorter way to achieve that using
scoreboard operations...

It might not always be obvious what value a command returns as its


"result", as this is sometimes different from what it prints in the game chat.
However, all commands can be looked up on the Minecraft wiki to see what
values and behavior they should provide.

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.

For example, to add the @s entry to $global:

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

The operation subcommand only runs on scoreboard entries, so we cannot


pass constant values to it. scoreboard players operation $global
fennifith.animals_spawned /= 2 is, unfortunately, not a command that
the game will run.

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

This results in $global, which was previously 6, being divided by 2 — as


such, its value is now 3.

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.

= sets lhs to the value of rhs


+= adds rhs to lhs
-= subtracts rhs from lhs
*= multiplies lhs by rhs
/= divides lhs by rhs
%= sets lhs to the remainder of lhs / rhs
< sets lhs to rhs only if rhs is smaller
> sets lhs to rhs only if rhs is larger
>< swaps the values of lhs and rhs

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.

More areas other than sidebar include:

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

Sidebar List Below Name


Sidebar List Below Name

/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.

/tellraw accepts an array of arguments which it concatenates together to


form its message. To reference a score in this array, we can write an element
with the structure {"score":{"name":"<selector>","objective":"
<objective>"}}. For example, here is a command that prints the number of
animals that the player (@s) has spawned:

shell
1 tellraw @s ["You have summoned ",{"score":
{"name":"@s","objective":"fennifith.animals_spawned"}},"
animals!"]

Conditions with scoreboard values


What if we have a command that we only want to run if the player has a
certain score?

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!"

In this example, if the player's score for fennifith.animals_spawned is


greater than fennifith.berries_eaten, the condition will be valid — and
it will run the tellraw command that follows it.

Comparing number ranges with "matches"


Using the matches option, it is also possible to directly compare a
scoreboard with a number range.

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.

Checking nonexistent scores


What happens if we access a scoreboard entry that doesn't exist? Normally,
the game treats nonexistent scoreboard entries as "0" — the /scoreboard
players add command, for example, will increase any nonexistent score to
"1".

However, this works a bit differently for if score conditions. If we check


the condition $nonexistent fennifith.animals_spawned matches 0..,
it won't run the command — because $nonexistent doesn't have a value.
Both the range 0.. and ..0 will fail — if the score has a value, we would
expect at least one of those conditions to be true.

Normally, this behavior is not a concern — if you are checking a scoreboard


in a condition, it is generally expected that the condition will not work for
any unset scores. However, if you want to directly check if a score exists,
the following command is one way to do that...

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.

We can use any statistic as the second argument of /scoreboard


objectives add when we create a new objective — for example:

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.

Detecting events with statistics


We can use this behavior in our tick.mcfunction (which runs on every
game tick) to detect when a player has used the carrot on a stick. We'll first
set the value for all players to 0, then check the scoreboard on every tick to
see if it has increased. If it has, we know that the item has been used, and
can reset it to 0 to detect it again.

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.

If it does, we'll run the fennifith:animals/spawn function — which was


created in the previous article — to spawn a group of animals.

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

3. Finally, after we run our function, we need to reset the scoreboard


value so that it won't run until the item is used again:

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.

Challenge: Maximum value of a scoreboard


Now that the fennifith.animals_id scoreboard has a few entries in it,
how can we find the highest score it contains? (without using the $counter
variable...)

To accomplish this, we can use the @e[type=pig] selector to target every


pig entity in the game, and store the result in $max fennifith.animals_id.

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.

You might also like