Minecraft Blogs / Tutorial

Function Data Packs for Dummies #9 | Check a lot of things, but only once! (+ /execute if/unless)

  • 5,159 views, 7 today
  • 22
  • 8
  • 1
Bertiecrafter's Avatar Bertiecrafter
Retired Moderator
Level 70 : Legendary Engineer
772
This series of tutorials will teach you how to create your very own data packs, assuming you know absolutely nothing about commands. Although data packs could just contain files that replace built-in assets like resource packs, these posts will focus on adding new features and mechanics to the game. In every part I assume you've read the previous parts.

Data packs mostly consist of a bunch of checks that run 20 times a second. Constantly checking values is called "polling" and is a horribly inefficient way to make sure certain conditions are met. The other option is "event-driven", which consists of registering code to be executed as soon as the calling code knows the conditions are met. In minecraft, examples of "event-driven" techniques are advancements that run a function once achieved, letting the player decide when to trigger a function (for uninstall/admin functions) and registering functions in the minecraft:tick/minecraft:load tags. Unfortunately, advancements are outside the scope of this tutorial series, so for now we are stuck to running a billion checks 20 times a second :D

Since checks are a big part of creating data packs, it is important to know how to turn any conditional requirement into a command. Furthermore, since each check is run 20 times a second, it's important to know how to check efficiently.


Types of checks

Let's first look at different types of checks, with examples of turning human language into command syntax. You might remember the covered /execute instructions, but they are not checks. They set variables in the command environment, but unless used with target selectors, they are not running any checks or filters.

Target Selectors
In part 6 we have met target selectors. The syntax allows targeting multiple entities dynamically, based on target selector variables and target selector arguments. The concept was further expanded in Part 7.2 with scoreboard arguments. Have a look at this page for all possible target selectors.

All players within 5 blocks in gamemode survival (for achievable targets in a map, like RPGs with talking villagers)
@a[gamemode=survival,distance=..5]
All players within 3 blocks with at least 5 xp levels, full health and food (for doors in dungeon type maps that only open if the player has enough experience)
@a[distance=..3,level=5..,scores={foodObjective=20..,healthObjective=20..}]
All players who have died in the past, but are alive now (for spawn invincibility)
@a[scores={deathsObjective=1..,healthObjective=1..}]
5 Randomly selected villager zombies within 10 blocks (to convert them back into villagers after the player has performed some kind of ritual)
@e[type=zombie_villager,distance=..10,limit=5,sort=random]
The user clicked a button in chat. Using tellraw, a clickable text can be printed that executes the /trigger command to update the value of a "trigger" type objective. See part 7.2 for more information. It sounds "event-driven", but we still have to check 20 times a second for the value to update.
@a[scores={triggerObjective=1..}]
Execute If/Unless
I have already covered plenty of /execute instructions like "as", "at", "in" and a couple others. However, /execute also has a couple conditional instructions. They can start with "if" which cancels execution if the condition is not met, or "unless" which cancels execution if the condition is met. You can chain these instructions, just like any other instruction:

Only run if A and B and C and D
execute if <A> if <B> if <C> if <D> run <command>
Only run if A or B. Unfortunately, we can't combine instructions with "or", you'll have to split up the command and probably move the final command instruction to a function for easy reuse.
execute if <A> run function .....
exeucte if <B> run function .....

Now let's look at all the possible conditional instructions, using examples. Once again, remember that you can replace "if" by "unless" in any of these examples.

If a player stands on tnt (to spawn an invisible armor stand that spawns lit tnt if someone gets close, like an invisible mine)
Note the "as @a at @s" instructions that set the location of execution to a single player. Without them, the function would only trigger as soon as someone places a tnt block 1 block below the execution location, which is world spawn inside the tick function.
execute as @a at @s if block ~ ~-1 ~ minecraft:tnt run function ....
If a certain area filled with blocks matches another area (often used in puzzle maps where you need to place blocks in the right order).
execute if blocks 325 68 -234 330 68 -234 325 68 -200 all run function ....
execute if blocks <start> <end> <destination> (all|masked) run <command>
The syntax consists of 3 pairs of coordinates and a mode. The first two pairs indicate opposite corners of the source area. The second pair must be in the positive x, y and z direction relative to the first pair. The third pair indicates one of the corners of the destination area, with the area expanding in the positive x, y and z directions using the size of the source area. If the mode is "all", the source area must exactly match the destination area, including air blocks. If the mode is "masked", only the non-air blocks in the source area must match the blocks in the destination area.

If creepers are nearby (for a survival detection system)
Keep in mind that the "if entity" instruction is unnecessary when using the same target selector that's used elsewhere in the command, since target selectors that select 0 players will always cancel the command no matter where they are.
execute as @a at @s if entity @e[type=creeper,distance=..10] run ......
#Bad usage of "if entity":
execute if entity @e[type=item] as @e[type=item] at @s run ......
execute if entity @a[distance=..5] run tp @a[distance=..5] x y z

If a score matches a range. This one should only be used after you used fake players on the scoreboard to do some complex calculations, since player scores can easily be checked with the scores target argument.
#Checking fake players on the scoreboard,
#which you could have used for variables in calculations:
execute if score #outputCalc myObjective matches 20..50 run .....
#Checking scores of online players:
execute if entity @a[scores={myObjective=20..50}] run .....
#It's not wrong to use "if score" for online players though, just preference.

If a score of one player is higher than the other (for automated tournaments using 1v1 groups)
Comparison operators: <, <=, >, >=, =
"Not equal to" does not exist as operator, use "unless score" with "=" instead.
execute if score @p[scores={player=1}] points > @p[scores={player=2}] points run say Player 1 Wins!
execute if score @p[scores={player=1}] points < @p[scores={player=2}] points run say Player 2 Wins!
execute if score @p[scores={player=1}] points = @p[scores={player=2}] points run say It's a tie!


Checking Efficiently

After covering even more ways to check certain conditions, let's have a look at how you can avoid repeating checks. One of the most common cases of repetition is when you want to execute something every time the player reaches a certain score and want to reset the score after, so it can be triggered again. It also happens when you need to repeat commands to cover all cases, like each slot of the hotbar, multiple teams or different times of the day.

Grouping in functions
This one is really easy. If you find yourself repeating a lot of target selectors or /execute if instructions, just combine all the commands for that condition into a single function and call that function.
# Inefficient:
give @a[.....] diamond 5
execute as @a[.....] at @s summon fireworks (...)
tellraw @a[.....] {"text":"Good job!"}
tellraw @a {"selector":"@a[.....]","extra":[{"text":" just achieved xxxxxxxx !"}]}

# Efficient (note that the last command gets repeated for every player):
# tick.mcfunction:
execute as @a[.....] at @s run function bertiecrafter:reward
# reward.mcfunction:
give @s diamond 5
summon fireworks (...)
tellraw @s {"text":"Good job!"}
tellraw @a {"selector":"@s","extra":[{"text":" just achieved xxxxxxxx !"}]}

Tags and scoreboard values
Sometimes you can't split things off in functions. For example, when you're deep into a function chain and want the next tick to remember that a certain entity matched certain conditions. In this case, you can use tags or scoreboard objectives to remember states. Syntax for /tag:
#Modifying tags:
tag [selector] (add|remove|list) <name>
#Checking tags:
@e[tag=<name>]
If you need more flexibility than just "you have a tag or not" you can use a scoreboard objective with any number value.
#Inefficient:
#achieved-something.mcfunction:
tellraw @s {"text":"Good job!"}
# tick.mcfunction:
execute as @a[...............................................] run <special particle trail command>

#Efficient:
#achieved-something.mcfunction:
tellraw @s {"text":"Good job!"}
tag @s add special-rank
# tick.mcfunction:
execute as @a[tag=special-rank] run <special particle trail command>

Data Tags
Instead of repeating commands for several blocks, items, entities or functions, consider creating a tag that contains all the possible options.
#Inefficient:
#tick.mcfunction:
effect give @e[type=zombie] fire_resistance 10000 0 true
effect give @e[type=skeleton] fire_resistance 10000 0 true
effect give @e[type=phantom] fire_resistance 10000 0 true

#Efficient:
#tick.mcfunction:
effect give @e[type=#bertiecrafter:burned_by_daylight] fire_resistance 10000 0 true
#burned_by_daylight.json:
{"values":["minecraft:zombie","minecraft:skeleton","minecraft:phantom"]}

Splitting checks

If you have partially duplicate checks, you can most likely split the checks and then apply one of the techniques above to reduce them.
#Inefficient:
#tick.mcfunction:
tp @a[arg1=A,arg2=B,arg3=C] x1 y1 z1
tp @a[arg1=A,arg2=B,arg3=D] x2 y2 z2

#Efficient:
#tick.mcfunction:
execute as @a[arg1=A,arg2=B] at @s run function bertiecrafter:tp
#tp.mcfunction:
tp @s[arg3=C] x1 y1 z1
tp @s[arg3=D] x2 y2 z2

Challenge Yourself

After every tutorial, I'll include a section where you are challenged to apply what you've learned. I recommend you playing with what you've learned, it helps you getting familiar with new concepts and be able to find solutions to problems. You don't have to do exactly what's written below, you can always challenge yourself in a different way.

Let's make your inventory grow depending on how much experience you collected since your last death. Use the "xp" objective type to track this. Now for the inventory slots, you could create a new function for every 20 points with a different amount of commands each for the slots, but this is horribly inefficient. It would be an improvement to create overlapping groups that only clear 1 out of 27 slots at once. The target selectors would be:
  • Players with a score between 0 and 20: Clear slot 0
  • Players with a score between 0 and 40: Clear slot 1
  • Players with a score between 0 and 60: Clear slot 2
  • ...........
  • Players with a score between 0 and 500: Clear slot 24
  • Players with a score between 0 and 520: Clear slot 25
  • Players with a score between 0 and 540 (about level 20): Clear slot 26

We can do better though. Instead of putting this list of checks into the tick function, we can put each of these checks in the function of the bigger group (higher maximum). That way, if a check fails, we don't bother checking any of the others. For example, if a player doesn't have a score between 0 and 400, we already know that they don't have a score between 0 and 380 either. Therefore, we can put the check for 0-380 inside the 0-400 function.

Although it takes effort, try to at least get this chain of functions working for the first row in the inventory. You can clear slots with /replaceitem (just fill them with glass panes or something).

Extra/Alternative Challenge
Make it so that standing on towers of blocks of diamonds gives you status effects (potion effects). The function checks go from the top (1 block below the player) to as far down as you want to implement. Any tower should get all the status effects the shorter towers would have. In a similar fashion as above, make sure that you don't check for a longer tower if you already know it doesn't match a shorter tower.

What's next?

Next up we're going to have a look at NBT. It's invented by Notch, used in almost all minecraft files and based on JSON. There are a couple powerful commands that allow us to interact with this hidden NBT universe behind the graphics. It's important to understand NBT, but this tutorial series will need several parts to cover all of it.

Subscribe if you want to get notified of new posts.

Function Data Packs for Dummies #9 | Check a lot of things, but only once! (+ /execute if/unless)
Function Data Packs for Dummies #9 | Check a lot of things, but only once! (+ /execute if/unless)
Tags

1 Update Logs

Update #1 : by Bertiecrafter 04/08/2020 11:16:35 amApr 8th, 2020

Added data tags as a way of avoiding too many checks.
Thank you GParcade for bringing this to light in your forum post.

Create an account or sign in to comment.

3
04/03/2020 2:21 amhistory
Level 70 : Legendary Engineer
Bertiecrafter
Bertiecrafter's Avatar
Thank you Luracasmus, PMC, Amrith Erilaz, AstroVulpix, Chimerabot, TofuChild36, JONITOKING, TheBigPug, SanctuaryThief, MineFriggs, Tofuzinn, SUPERIONtheKnight, Zamanbre, MCAlexisYT, hogbits and Kefaku for the diamonds!
Planet Minecraft

Website

© 2010 - 2024
www.planetminecraft.com

Welcome