Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Chapter 32 — Project: A Custom Achievement Tree

What You’ll Build

Back in Chapter 19 you learned the secret of advancements: each one is really an event listener that happens to show a toast. You built two of them on their own. In this chapter you put a whole group of them together into something the player actually sees as a feature: a custom advancement tab, your own page in the advancement screen, with its own background, its own root, and a tree of linked achievements branching across it.

By the end you’ll have a themed tab called Explorer’s Path living in the mypack pack you’ve been growing since Chapter 9. It has a root advancement that creates the tab, three visible achievements that get progressively harder (easy → medium → hard), each linked to the one before it, and one hidden advancement tucked inside that works as a silent re-armable detector (the exact pattern from Chapter 19, now used in a real project). Several of the advancements hand out custom items you build from data components (Chapters 21–24) through small reward functions. At the end you’ll learn to use /advancement grant and /advancement revoke to cheat-test the tree and, beyond that, as a way to read and set a player’s progress like a switch.

Nothing in this chapter is a brand-new Minecraft idea. Every file uses pieces you already know. The skill this chapter teaches is assembly: taking systems you’ve learned one at a time and wiring them into one coherent thing. That is what real data packs are.

Figure (to be captured). the advancement screen with a new “Explorer’s Path” tab selected, showing the root icon on the left and arrows branching to three child advancements

First, design the tree — on paper

Before writing a single file, decide what the tab is. A good advancement tree has three things:

  1. A theme. Ours is exploration — venturing out, going underground, facing the dragon. The theme decides the icons, titles, and the background art.
  2. A root. Every tab starts with one root advancement: an advancement with no parent. From Chapter 19 you know a root with valid display data automatically creates a new tab in the advancement menu. The root is the leftmost icon; everything else hangs off it.
  3. A progression. Children branch rightward from the root, each one linked to a parent, getting harder as you go. We’ll do three tiers:
AdvancementDifficultyHow you earn itFrame
Explorer’s Path (root)given at the start (a tick trigger)task
First Stepseasypick up a maptask
Cave Delvermediumbe deep undergroundgoal
Dragon Slayerhardkill the Ender Dragonchallenge
(secret snack)hiddeneat a golden carrot (re-arms)(no display)

That last row is the Chapter 19 trick: a hidden, display-less detector that lives inside the same tab’s files but never shows on the screen. Designing it in now, on paper, is how professionals work: the structure exists before the JSON does.

A note on folders. All of this still lives in the singular advancement/ folder from Chapter 19. To keep a project tidy, we’ll put every file for this tree in a subfolder named explorer/. A file at data/mypack/advancement/explorer/root.json has the id mypack:explorer/root: the subfolder becomes part of the id, exactly like it did for functions. Grouping a project’s files in a subfolder named for the project is a convention worth keeping for every project from here on.

The root: creating the tab

The root is the most important file because it builds the tab. It has no parent (that’s what makes it a root), full display data so the tab appears, and the one field we’ve mentioned but never used: background.

From the advancement reference, background is “the directory for the background to use in this advancement tab (used only for the root advancement)”, and the reference adds that “each tab has a different background with a repeating texture.” So background points at a texture path that tiles behind your tab. Any valid texture path works; the listing below uses minecraft:textures/block/stone.png only as an example so we don’t have to make art yet. Treat the exact path as a placeholder and confirm a real texture path when you reach resource packs and textures in Chapter 29. (If the path is wrong, the game just shows the missing-texture pattern behind the tab; the tab still works.)

We also need the root to actually grant itself, or the tab will sit there grayed-out forever. The simplest way is a tick trigger with no conditions. It completes the instant the player exists, which makes the root a “you’ve started” marker. From Chapter 19, minecraft:tick fires every tick; with nothing to test, it completes immediately.

mypack/data/mypack/advancement/explorer/root.json

{
  "display": {
    "icon": {
      "id": "minecraft:compass"
    },
    "title": {
      "text": "Explorer's Path"
    },
    "description": {
      "text": "Your journey begins"
    },
    "frame": "task",
    "background": "minecraft:textures/block/stone.png",
    "show_toast": false,
    "announce_to_chat": false,
    "hidden": false
  },
  "criteria": {
    "started": {
      "trigger": "minecraft:tick"
    }
  }
}

Every field here is one you met in Chapter 19; the only new one is background, and it does exactly what the reference says: sets the art behind this tab. We turned show_toast and announce_to_chat off so the player isn’t spammed with a “you exist!” popup at world join; the tab still appears once they open the advancement screen.

Save it, run /reload in your test world, and press L. A new Explorer’s Path tab should be there, with a lonely compass icon. Now let’s give it some branches.

What Went Wrong? No new tab appears. A root only makes a tab if it has valid display data, and display is only valid if it includes icon, title, and description (the reference marks all three required once display is present). Leave one out and the whole display is ignored, so the advancement becomes a silent one and no tab is drawn. If your tab is missing, check those three fields first, then check the file is under advancement/ (singular).

The branches: progressive children linked by parent

Now the tiers. Each child sets parent to the id of the advancement before it, which draws it one column to the right with an arrow pointing in. The frames climb with the difficulty: task for easy, goal for medium, challenge for the showstopper.

Tier 1 — easy. “First Steps”: earned by picking up a map. We use inventory_changed (fires when the inventory changes) narrowed with an items condition, the same item-condition object from Chapter 19, whose items field “tests if the type of item in the item stack matches any of the listed values.” Its parent is the root.

mypack/data/mypack/advancement/explorer/first_steps.json

{
  "parent": "mypack:explorer/root",
  "display": {
    "icon": {
      "id": "minecraft:map"
    },
    "title": {
      "text": "First Steps"
    },
    "description": {
      "text": "Hold a map and head out"
    },
    "frame": "task"
  },
  "criteria": {
    "got_map": {
      "trigger": "minecraft:inventory_changed",
      "conditions": {
        "items": [
          {
            "items": ["minecraft:map"]
          }
        ]
      }
    }
  },
  "rewards": {
    "function": "mypack:explorer/give_compass"
  }
}

Notice the rewards.function: when the player earns “First Steps”, it runs a function that hands them a custom item. We’ll write that function in the next section. Notice too that this child has no background: the reference says background is root-only, so children just inherit the tab’s.

Tier 2 — medium. “Cave Delver”: earned by being deep underground. This one uses the polling location trigger from Chapter 19 (it fires once a second and checks the player’s situation), with a location condition testing the player’s Y position. From the location-condition object, position takes x/y/z ranges; we test that y is low. Its parent is “First Steps”, so it draws to the right of it. Frame is goal.

mypack/data/mypack/advancement/explorer/cave_delver.json

{
  "parent": "mypack:explorer/first_steps",
  "display": {
    "icon": {
      "id": "minecraft:torch"
    },
    "title": {
      "text": "Cave Delver"
    },
    "description": {
      "text": "Descend deep below the surface"
    },
    "frame": "goal"
  },
  "criteria": {
    "went_deep": {
      "trigger": "minecraft:location",
      "conditions": {
        "player": [
          {
            "condition": "minecraft:location_check",
            "predicate": {
              "position": {
                "y": {
                  "max": 0
                }
              }
            }
          }
        ]
      }
    }
  },
  "rewards": {
    "function": "mypack:explorer/give_lantern"
  }
}

Here the criterion’s player is written in its list form. The reference notes that player can be “a list of predicates that must pass.” Each entry is a predicate object exactly like the Chapter 18 ones: a condition of minecraft:location_check whose predicate carries the location fields. So “is the player at Y 0 or below?” reuses the predicate vocabulary you already have.

Tier 3 — hard. “Dragon Slayer”: kill the Ender Dragon. This is player_killed_entity (fires when the player kills something) with an entity condition whose type is the dragon (the entity- condition object from Chapter 19). Parent is “Cave Delver”; frame is challenge, the one that shows the pink “Challenge Complete!” header and plays the big sound.

mypack/data/mypack/advancement/explorer/dragon_slayer.json

{
  "parent": "mypack:explorer/cave_delver",
  "display": {
    "icon": {
      "id": "minecraft:dragon_head"
    },
    "title": {
      "text": "Dragon Slayer"
    },
    "description": {
      "text": "Defeat the Ender Dragon"
    },
    "frame": "challenge",
    "show_toast": true,
    "announce_to_chat": true
  },
  "criteria": {
    "slew_dragon": {
      "trigger": "minecraft:player_killed_entity",
      "conditions": {
        "entity": {
          "type": "minecraft:ender_dragon"
        }
      }
    }
  },
  "rewards": {
    "experience": 500,
    "function": "mypack:explorer/dragon_reward"
  }
}

Two rewards stack here: a flat experience of 500 (an integer reward straight from the reference) and a function that grants the trophy item. You can combine reward types freely; they all fire when the advancement completes.

/reload, open the L screen, and you’ll see the tab now has the compass root with First Steps → Cave Delver → Dragon Slayer marching to the right, each arrow pointing at the next. The tree exists. Now let’s make its rewards real.

Figure (to be captured). the Explorer’s Path tab fully populated — compass root, then map / torch / dragon-head icons connected by arrows, the dragon-head one wearing the fancy challenge frame

Under the Hood (skippable). The game arranges the rows for you. Each advancement draws an arrow from its closest visible ancestor, so if you ever insert a display-less advancement in the middle of a chain, the arrow simply skips it and links to its grandparent. That’s why a hidden detector (next section) can sit inside the tree’s files without disturbing the picture the player sees.

Reward functions that grant custom items

The whole reason rewards.function matters (Chapter 19) is that a function can do anything, and the most satisfying thing a project can do is hand the player a custom item they can’t get any other way. We build those items out of data components (Chapters 21–24) and give them with /give.

Remember the rule from Chapter 19: a reward function runs as and at the player who earned the advancement, so @s is them. Each function below gives one themed item, named and described with the custom_name and lore components from Chapter 21, using the [component=value] bracket form you learned there.

The “First Steps” reward, an explorer’s compass with a name and a lore line:

mypack/data/mypack/function/explorer/give_compass.mcfunction

give @s minecraft:compass[custom_name={text:"Pathfinder's Compass",color:"aqua",italic:false},lore=[{text:"Points the way onward.",color:"gray"}]]
title @s actionbar {"text":"Reward: Pathfinder's Compass","color":"aqua"}

Both components trace to the item-component reference: custom_name is “the player-assigned name of this item… See Text component format,” so its value is a text component (Chapter 5); lore is a “list of additional lines… Text component representing a line of text,” so its value is a list of text components. One renamed, lore-bearing compass, given the moment the player earns the achievement.

The “Cave Delver” reward is a lantern that doubles as a snack, using the food component from Chapter 23 so a deep-cave explorer never starves:

mypack/data/mypack/function/explorer/give_lantern.mcfunction

give @s minecraft:lantern[custom_name={text:"Everlight Lantern",color:"gold",italic:false},food={nutrition:4,saturation:2,can_always_eat:true}]
title @s actionbar {"text":"Reward: Everlight Lantern","color":"gold"}

From the food reference, nutrition is the food points restored, saturation the saturation, and can_always_eat:true means “this item can be eaten even if the player is not hungry.” (In a finished pack you’d also add the consumable component from Chapter 23 to control the eating animation; we keep this listing to the one component the reward needs.)

The “Dragon Slayer” trophy, a named, lore-stamped dragon head:

mypack/data/mypack/function/explorer/dragon_reward.mcfunction

give @s minecraft:dragon_head[custom_name={text:"Dragonslayer's Trophy",color:"light_purple",italic:false},lore=[{text:"Slayer of the Ender Dragon.",color:"dark_purple"},{text:"Explorer's Path complete.",color:"gray"}]]
title @s actionbar {"text":"TROPHY EARNED","color":"light_purple","bold":true}

That lore value is a list with two entries, drawing two tooltip lines, exactly what the reference allows. /reload and test by granting yourself an advancement (the next section shows how), and you’ll get the item in hand, fully named and described, with no model or texture work at all. The components do everything.

Modern Minecraft. Older tutorials built “custom” reward items with long /give ... {NBT} blobs or by reading raw NBT. The modern way is exactly what you see here: pick a base item, override a few components in brackets, done. The reward item is just an ordinary /give with components: the same skill from Chapter 21, now paying off inside a real project.

A hidden detector living inside the tree

A project tab can hold more than the achievements the player sees. We’ll add the Chapter 19 hidden-detector pattern inside the explorer/ folder: an advancement with no display at all, whose only job is to notice an event and run a function, every time, by revoking itself to re-arm. Here it watches for the player eating a golden carrot, a little secret snack that heals.

mypack/data/mypack/advancement/explorer/secret_snack.json

{
  "criteria": {
    "ate_carrot": {
      "trigger": "minecraft:consume_item",
      "conditions": {
        "item": {
          "items": ["minecraft:golden_carrot"]
        }
      }
    }
  },
  "rewards": {
    "function": "mypack:explorer/secret_snack"
  }
}

No parent, no display, so it is not a second tab and not a visible node. The reference is explicit that advancements which “lack a display… should not have the display field defined in order to hide from users.” It simply sits in the files as plumbing. And the reward function re-arms it, the way Chapter 19 taught: do something, then revoke the advancement from @s so it can fire again.

mypack/data/mypack/function/explorer/secret_snack.mcfunction

effect give @s minecraft:regeneration 5 0
title @s actionbar {"text":"A warm glow spreads through you...","color":"green"}
advancement revoke @s only mypack:explorer/secret_snack

That last line is the whole trick, copied from the /advancement command: advancement (grant|revoke) <targets> only <advancement>, which “adds or removes a single advancement.” Revoking it un-completes it, so the next golden carrot fires consume_item again. A perfect reusable hook, hidden inside the same project as the showy achievements. /reload, eat a golden carrot (/give @s golden_carrot 5 first), and you should get Regeneration every time.

Using grant and revoke as advancement-based state

The /advancement command isn’t only for re-arming detectors. Because an advancement is either completed or not for each player, it doubles as a simple on/off state you can set and check: “has this player finished the Explorer’s Path?” is just “do they have mypack:explorer/dragon_slayer?”

Granting, to test. While building, you don’t want to actually kill the dragon every time. Grant yourself an advancement straight from chat to fire its rewards and check the whole chain:

/advancement grant @s only mypack:explorer/first_steps

From the command reference, grant ... only <advancement> adds that single advancement, which fires its reward function, so this is how you test give_compass without finding a map. There’s also a sweeping form: advancement grant <targets> from <advancement> “adds… an advancement and all its child advancements.” So to unlock the whole tree for a test run:

/advancement grant @s from mypack:explorer/root

That grants the root and everything branching off it in order: instant full tree.

Revoking, to reset. To wipe your progress and start the tab fresh (handy when testing the visible-vs-hidden behavior), revoke from the root:

/advancement revoke @s from mypack:explorer/root

Now the tab is back to its starting state for you.

Reading state in a function. Because completion is per-player, you can gate later content on an advancement the same way you gate on a scoreboard or a tag. You already have the tools: a reward function on the final advancement can set a marker the rest of your pack reads. For example, the dragon reward could tag the player as a finisher so other systems can react:

mypack/data/mypack/function/explorer/dragon_reward.mcfunction (extended)

give @s minecraft:dragon_head[custom_name={text:"Dragonslayer's Trophy",color:"light_purple",italic:false},lore=[{text:"Slayer of the Ender Dragon.",color:"dark_purple"},{text:"Explorer's Path complete.",color:"gray"}]]
tag @s add explorer_complete
title @s actionbar {"text":"TROPHY EARNED","color":"light_purple","bold":true}

Now any function in your pack can check @s[tag=explorer_complete] (Chapter 13) to know whether a player has finished the tree. The advancement drove a piece of game state. That is the real power of grant/revoke: the achievement tree is a working progress system the rest of your pack can build on.

Practice

  1. Add a fourth tier. Branch a new advancement off “Cave Delver” (its parent is mypack:explorer/cave_delver) for a different exploration goal (say bred_animals for “Trail Companion”, tame the wild) with its own goal frame and a reward function granting a named lead or saddle. Confirm the new arrow appears in the tab.

  2. Two paths from one parent. Give the root two easy children instead of one: “First Steps” (the map) and a sibling earned a different way (e.g. placed_block for setting a campfire). Both set parent to mypack:explorer/root. Watch the tab fork into two branches.

  3. A second hidden detector. Copy secret_snack.json to a new display-less file inside explorer/ that watches a different event (for instance player_killed_entity on a minecraft: bat) and whose reward function does something fun and then advancement revoke @s only <its own id>. Confirm it re-fires every time and never shows on the tab.

  4. Gate a reward on completion. Write a function that uses execute if entity @s[tag= explorer_complete] run ... (from your extended dragon reward) to give a bonus only to players who finished the tree, proving an advancement can act as a gate for later content.

What Can Go Wrong

  • “My tab is grayed-out / empty.” A tab only shows advancements whose display is valid, and the root must grant itself or nothing lights up. Make sure the root has icon + title + description and a criterion that actually completes (the tick trigger completes immediately). If the whole tab is missing, the root’s display is probably invalid (a missing required field).

  • “A child floats off on its own / isn’t linked.” Its parent must be the exact id of another advancement in your tree, including the explorer/ subfolder, e.g. mypack:explorer/first_steps, not mypack:first_steps. A wrong or misspelled parent either errors on load or makes the child a stray root. Check the id matches the file path.

  • “The reward item gives but looks plain.” The components are the item; if the name or lore didn’t apply, the bracket syntax is the suspect: = (not :) between a component and its value, commas between components, and lore must be a list [ ... ] of text components even for one line. Re-read Chapter 21’s bracket rules if /give complained.

  • “The hidden detector only worked once.” Same as Chapter 19: a completed advancement won’t re-fire until you take it back. The reward function must end with advancement revoke @s only <its own id>. Forgetting that line is the classic detector bug.

What You Know Now

You can build a complete, themed advancement tab from parts you already had: a root that creates the tab and sets its background, a chain of parent-linked children climbing from task to goal to challenge frames, reward functions that grant component-built custom items the moment each is earned, and a hidden, re-armable detector living quietly inside the same project folder. You learned to drive the whole thing with /advancement grant and /advancement revoke: granting from the root to unlock the tree for testing, revoking to reset it, and using a completed advancement as a piece of game state the rest of your pack can read. That is a real, shippable feature, several systems assembled into something a player experiences as a single progression. The next project chapter builds a minigame the same way: many small systems, one coherent whole.