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 31 — Project: Custom Mob Drops and Behavior

What You’ll Build

This is the first project chapter, and it works differently from everything before it. Up to now each chapter taught you one new system (loot tables, predicates, advancements, components) one at a time, each with its own little demo. A project doesn’t teach a new system. It hands you the ones you already have and asks the real question: how do they fit together into one finished thing?

By the end of this chapter your mypack pack (the one you started in Chapter 9) will do all of this, working as a single feature:

  • A husk (the dried-out, desert version of a zombie) that is killed by a player, with a diamond sword, at night has a small chance to drop a one-of-a-kind sword called the Sunforged Blade: enchanted, custom-named, lore-bearing, and secretly tagged so your pack can recognise it later.
  • The moment that special kill happens, a hidden advancement notices it and fires a celebration function (a burst of particles, a sound, and a short status effect on the player) so the rare drop feels rare.
  • And you’ll learn a testing workflow: a repeatable way to force the drop, isolate each condition, and find which piece is broken when something doesn’t fire. In a multi-file project, something always doesn’t fire the first time.

Here is the whole project laid out as files, so you can see the shape before we build it:

mypack/
  data/
    mypack/
      predicate/
        with_diamond_sword.json     (Piece 1 — "killed with a diamond sword?")
        is_night.json               (Piece 1 — "is it night?")
      advancement/
        slew_a_husk.json            (Piece 4 — silent detector for the special kill)
      function/
        husk_reward.mcfunction      (Piece 5 — the particle/sound/effect celebration)
        give_test_blade.mcfunction  (testing helper)
    minecraft/
      loot_table/
        entities/
          husk.json                 (Piece 3 — the gated drop + the custom item)

Six files, each small, each pointing at the others. Every condition, every function, every component here is one you met in Chapters 16 through 23. The skill this chapter builds is assembly: wiring separate files into one feature, and testing it methodically, like a builder.

This chapter extends the mypack pack from Chapter 9 and uses the test world from Chapter 1.

Designing the project before you build it

Good data-pack projects start on paper (or in your head), not in a JSON file. Before writing anything, it helps to name the pieces and the connections between them, because the connections are where projects break.

Here’s the design for our husk drop, written as plain sentences:

  1. When a husk dies, if a player landed the kill, and if that player held a diamond sword, and if it’s night, then (rarely) drop a special sword.
  2. That special sword is a diamond sword that’s been enchanted, renamed “Sunforged Blade,” given a line of lore, and stamped with a hidden tag so the pack knows it’s ours.
  3. Separately, whenever a player kills a husk, run a short celebration (particles, a sound, a buff) so the event has some flair.

Now map each sentence to a system you already know:

Design sentenceThe system that does itChapter
“if a player landed the kill”loot condition killed_by_player17
“rarely”loot condition random_chance17
“if held a diamond sword”a predicate (match_tool), referenced from the loot table17, 18
“if it’s night”a predicate (time_check), referenced from the loot table18
“drop a special sword”a loot entry with loot functions16, 17
“enchanted / renamed / lore / hidden tag”components, set by loot functions / set_components17, 21–24
“drop only happens on the husk”override the husk’s vanilla loot table17
“notice the kill”an advancement with player_killed_entity, no display19
“run a celebration”a function of particle/sound/effect commands9

Every row points back to something you’ve done. The project is just connecting the rows. Let’s build them in dependency order: the small reusable pieces first, then the things that reference them.

Why the husk? A husk is a mob, and that matters. Back in Chapter 17 you learned about loot context (the bundle of facts a loot situation provides). A living entity’s death supplies an attacking_player entity (and a damage source), which is exactly what killed_by_player and the Looting bonus read. A chest opening has no killer, so those conditions would always fail there. Building this on a mob’s death table is what gives our player-kill and weapon checks the context they need.

Piece 1 — the reusable predicates

Two of our conditions, “with a diamond sword” and “at night,” are exactly the kind of test Chapter 18 taught you to save as a predicate file so it can be reused by name. We’ll write them first, because the loot table will reference both.

“Killed with a diamond sword” — match_tool

Chapter 18 introduced match_tool: it checks the tool used to mine the block and, for a kill, that means the weapon the killer was holding. Its single field is predicate, an item test that uses the same structure as advancements, and the advancement item condition (Chapter 19) has an items field that is a list of item IDs the held item must match. So:

mypack/data/mypack/predicate/with_diamond_sword.json

{
  "condition": "minecraft:match_tool",
  "predicate": {
    "items": ["minecraft:diamond_sword"]
  }
}

This is a complete, working predicate, the same file you’d have written in Chapter 18. It passes when the tool involved is a diamond sword and fails otherwise. Saved as a file, the loot table can pull it in by name instead of spelling the check out inline.

Under the Hood (skippable). Remember from Chapter 18 that match_tool “requires tool provided by loot context, and always fails if not provided.” On a mob death table the killer’s weapon is in the context, so the check works. If you ever reused this predicate somewhere with no tool (a bare execute if predicate standing in open air) it would simply fail. That’s the context rule from Chapter 17 doing its job.

“Is it night” — time_check

Chapter 18 also covered time_check: it compares the current day time against given values, takes a value (a number or a min/max range) and an optional period, and is invokable from any context. A Minecraft day is 24,000 ticks (Chapter 7); night runs roughly from 13,000 to 23,000. Setting period to 24000 causes the checked time to be equal to the current daytime, so the comparison resets each day instead of climbing forever:

mypack/data/mypack/predicate/is_night.json

{
  "condition": "minecraft:time_check",
  "value": {
    "min": 13000,
    "max": 23000
  },
  "period": 24000
}

This passes during the night portion of each day. Same shape as the is_daytime predicate from Chapter 18; only the numbers changed.

Try It! Want the blade to drop only during a thunderstorm instead of at night? Swap this predicate’s reference (coming up in Piece 3) for a weather_check predicate with "thundering": true, exactly like the is_thundering file you wrote in Chapter 18’s practice. The loot table doesn’t care which predicate it references, only that it returns pass or fail.

Piece 2 — the rare custom item

Before we make the husk drop the Sunforged Blade, let’s build the blade by hand with a /give command, the way Chapter 21 taught. Building it as a command first means you can hold the finished item in your hand and confirm it looks right before you bury it inside a loot table: a debugging habit worth keeping.

From Chapters 21–24, an item is its ID plus a bag of components written in square brackets: item_id[component=value, component2=value]. Our blade uses five components you already know:

  • custom_name — a text component (Chapter 5) for the item’s name. This component has highest priority to display as the item’s name, and appears italic unless overridden by the text component format, so we set italic:false to keep it upright.
  • lore — “List of additional lines to display in this item’s tooltip,” each line a text component.
  • enchantments — “a map of each of this item’s enchantments to its enchantment level.”
  • rarity — sets the rarity of this item, which affects the default color of its name. It can be common, uncommon, rare, or epic. Note the word default: epic would tint the name light purple on its own, but our explicit custom_name color (gold) wins, since custom_name has highest priority to display as the item’s name. So rarity here mostly sets the tooltip’s rarity tint, not the visible name color. (The light-purple/aqua name color actually shows up through the enchantments component, since an enchanted item’s name is colored by its rarity, so rarity and enchantments work together on the tint; the custom_name color overrides whatever they’d pick.)
  • custom_data — “key-value pairs of any custom data not used by the game.” This is the hidden stamp from Chapter 24: the game ignores it, but your pack can test for it later.

Open your test world’s chat bar and run this as one line:

/give @s diamond_sword[custom_name={"text":"Sunforged Blade","color":"gold","italic":false},lore=[{"text":"Forged in desert light","color":"gray","italic":true}],enchantments={"minecraft:sharpness":4,"minecraft:fire_aspect":2},rarity="epic",custom_data={mypack_sunforged:true}]

You should get a gold-named diamond sword (the custom_name color wins) with a grey lore line, Sharpness IV and Fire Aspect II already on it, the “epic” rarity showing in its tooltip, and, invisibly, the tag {mypack_sunforged:true} riding along in custom_data. Hold it, hover it, swing it. This is the exact item the husk will drop.

Figure (to be captured). the “Sunforged Blade” diamond sword held in hand, tooltip showing the gold name, grey lore line, and the Sharpness IV / Fire Aspect II enchantment lines

Modern Minecraft. That custom_data stamp is how modern packs mark “this is our special item.” Old tutorials detected custom items by matching their name, which is fragile, because a player could rename anything. The supported way (Chapter 24) is a custom_data tag the game never touches and only your pack reads. We won’t read it back in this chapter, but stamping it now means a later project can ask “is the player holding a Sunforged Blade?” with a predicate, and get a reliable yes/no.

Translating the item into loot functions

A loot table can’t drop a finished [...] item directly; it drops a plain item and then modifies it with loot functions (Chapter 17). Each component above maps to a function:

  • custom_name → the set_name function (“Adds or changes the item’s custom name”; field name).
  • lore → the set_lore function (“Adds or changes the item’s lore”; field lore, plus a mode).
  • enchantments → the enchant_with_levels function from Chapter 17 or, for an exact set of enchantments, the set_components function. We’ll use set_components so the blade always comes out with exactly Sharpness IV and Fire Aspect II, not a random enchant.
  • rarity and custom_data → also set_components, the function whose field components is a map of component ID to component value: the loot-table doorway to any component, including the two that have no friendly dedicated function.

We’ll put enchantments, rarity, and custom_data together into a single set_components, and keep set_name and set_lore as their own friendly functions. That’s the same toolkit from Chapter 17; we’re just using set_components for the components that need it.

Piece 3 — the loot table that drops it

Now the centrepiece: override the husk’s loot table so that, on top of its normal drops, a player-killed husk has a small chance to drop the Sunforged Blade, but only with a diamond sword, only at night.

As in Chapter 17, you override a vanilla mob’s drops by writing a file at the same address in the minecraft namespace. The husk’s table lives at entities/husk, so your file is:

mypack/data/minecraft/loot_table/entities/husk.json

Remember the warning from Chapter 17: overriding replaces the whole table, so you’re now responsible for the husk’s normal drops too. We keep a rotten-flesh pool first, then add the gated sword pool. Here is the complete file:

mypack/data/minecraft/loot_table/entities/husk.json

{
  "type": "minecraft:entity",
  "pools": [
    {
      "rolls": 1,
      "entries": [
        {
          "type": "minecraft:item",
          "name": "minecraft:rotten_flesh",
          "functions": [
            { "function": "minecraft:set_count", "count": { "min": 0, "max": 2 } }
          ]
        }
      ]
    },
    {
      "rolls": 1,
      "conditions": [
        { "condition": "minecraft:killed_by_player" },
        { "condition": "minecraft:random_chance", "chance": 0.05 },
        { "condition": "minecraft:reference", "name": "mypack:with_diamond_sword" },
        { "condition": "minecraft:reference", "name": "mypack:is_night" }
      ],
      "entries": [
        {
          "type": "minecraft:item",
          "name": "minecraft:diamond_sword",
          "functions": [
            {
              "function": "minecraft:set_name",
              "name": { "text": "Sunforged Blade", "color": "gold", "italic": false }
            },
            {
              "function": "minecraft:set_lore",
              "mode": "replace_all",
              "lore": [
                { "text": "Forged in desert light", "color": "gray", "italic": true }
              ]
            },
            {
              "function": "minecraft:set_components",
              "components": {
                "minecraft:enchantments": { "minecraft:sharpness": 4, "minecraft:fire_aspect": 2 },
                "minecraft:rarity": "epic",
                "minecraft:custom_data": { "mypack_sunforged": true }
              }
            },
            {
              "function": "minecraft:enchanted_count_increase",
              "enchantment": "minecraft:looting",
              "count": { "min": 0, "max": 1 }
            }
          ]
        }
      ]
    }
  ]
}

Read it the way Chapter 17 taught. "type": "minecraft:entity" declares the mob-death loot context. The first pool is plain Chapter-16 material (rotten flesh, 0–2 of it) so the husk’s normal drop survives the override.

The second pool is the project. Its conditions list holds four tests, and all must pass for the pool to run:

  1. killed_by_player — a player landed the kill (Chapter 17).
  2. random_chance with chance: 0.05 — the 1-in-20 rarity (Chapter 17).
  3. reference to mypack:with_diamond_sword — our Piece 1 predicate. Chapter 18 taught the reference condition: it “invokes a predicate file and returns its result,” with the predicate’s name in the name field. This is the same vocabulary the loot table’s conditions list already speaks: a condition in that list is a predicate, so referencing a predicate file is natural.
  4. reference to mypack:is_night — our second Piece 1 predicate.

That’s the heart of the assembly: two of the four conditions live in their own files and are pulled in by name. If you later decide “actually, a netherite sword should count too,” you edit with_diamond_sword.json once and this table updates automatically. That’s the payoff Chapter 18 promised.

Inside the pool, the single entry drops a plain minecraft:diamond_sword, then its functions reshape it in order (Chapter 17): set_name gives the gold “Sunforged Blade”; set_lore with mode: "replace_all" sets the grey lore line; set_components stamps on the exact enchantments, the epic rarity, and the hidden custom_data tag in one go; and enchanted_count_increase adds the small Looting bonus. The finished drop is exactly the item you /give-tested in Piece 2.

What Can Go Wrong? The path must be exactly data/minecraft/loot_table/entities/husk.json: the minecraft namespace (you’re overriding vanilla), and the singular loot_table folder, the same singular-folder rule that bites people on function and recipe. A typo in the path means your file just sits there, ignored, and husks drop vanilla loot as if nothing changed.

Piece 4 — the silent kill detector

The loot table handles the drop. The celebration needs to know the moment a player kills a husk, and that’s exactly the job of a hidden advancement from Chapter 19.

Recall the pattern: an advancement is really an event listener; strip its display and it becomes a silent detector that just watches for an event and fires a reward function. The event here is player_killed_entity, narrowed to husks. And because we want it to fire on every husk kill, the reward function will revoke the advancement to re-arm it: the loop from Chapter 19.

mypack/data/mypack/advancement/slew_a_husk.json

{
  "criteria": {
    "killed_husk": {
      "trigger": "minecraft:player_killed_entity",
      "conditions": {
        "entity": {
          "type": "minecraft:husk"
        }
      }
    }
  },
  "rewards": {
    "function": "mypack:husk_reward"
  }
}

Notice there is no display field at all: no toast, no screen entry, nothing visible. This is the pure-detector form from Chapter 19. The criteria block has one criterion, killed_husk, whose trigger is player_killed_entity and whose conditions narrow it with an entity whose type is minecraft:husk, copied from the same advancement-entity-condition shape you used in Chapter 19. When a player kills a husk, the criterion completes, the advancement completes, and its rewards.function runs mypack:husk_reward as and at that player.

That as/at detail is what makes the next piece easy. Inside the reward function, @s is the killing player and ~ ~ ~ is where they are.

Piece 5 — the celebration function

Now the payoff: a function that plays a little burst of feedback. It runs as the player who got the kill, so @s and ~ ~ ~ already point at the right person and place. We’ll use particle and playsound from Chapter 2, plus a status effect, now with their full syntax confirmed from the command pages, and then the revoke line from Chapter 19 that re-arms the detector.

mypack/data/mypack/function/husk_reward.mcfunction

particle minecraft:flame ~ ~1 ~ 0.3 0.5 0.3 0.02 30 force
playsound minecraft:entity.blaze.shoot player @s ~ ~ ~ 1 1
effect give @s minecraft:fire_resistance 10 0
advancement revoke @s only mypack:slew_a_husk

Walk each line, all grounded in the command pages:

  1. particle minecraft:flame ~ ~1 ~ 0.3 0.5 0.3 0.02 30 force — the particle command’s full form is particle <name> <pos> <delta> <speed> <count> [force|normal]. So this makes 30 (<count>) flame particles one block above the player (~ ~1 ~), spread within the <delta> box 0.3 0.5 0.3 around that point, with a tiny <speed> of 0.02. When <count> is not 0, the particles are created at random positions scattered around <pos> by <delta>, so you get a little cloud, not a single dot. force makes them show even for players with reduced particle settings.
  2. playsound minecraft:entity.blaze.shoot player @s ~ ~ ~ 1 1 — the playsound command’s form is playsound <sound> <source> <targets> <pos> <volume> <pitch>. The sound must be a sound event defined in sounds.json (for example, entity.pig.ambient); player is one of the eleven source categories from Chapter 2; @s is the killer; 1 1 is full volume and normal pitch.
  3. effect give @s minecraft:fire_resistance 10 0 — the effect command’s form is effect give <targets> <effect> [<seconds>] [<amplifier>]. So this grants Fire Resistance for 10 seconds at amplifier 0. The amplifier rule is explicit: the first tier of a status effect (e.g. Regeneration I) is 0, so 0 means level I.
  4. advancement revoke @s only mypack:slew_a_husk — the re-arm line. From the advancement command page, advancement revoke <targets> only <advancement> “removes a single advancement.” It takes the detector back from this one player so it’s armed again and fires on their next husk kill, the exact loop from Chapter 19.

Figure (to be captured). the moment after a husk dies — a small cloud of flame particles above the player, with the Fire Resistance effect icon just appearing in the HUD

Try It! The chapter wires the celebration to every husk kill. Want it only on the rare drop? That’s harder: the advancement fires on the kill, before knowing whether the loot rolled the sword. One clean approach you already have the tools for: have the loot table’s sword entry also run a small function (you’ll combine loot and functions like this in the projects ahead), or detect the player picking up the Sunforged Blade with an inventory_changed advancement that tests the custom_data stamp. Sketch it; you don’t have to build it yet.

The testing workflow

Here’s the part that separates “I wrote six files” from “I have a working feature.” A multi-file project almost never works on the first /reload, and the worst way to fix it is to stare at all six files at once. Instead, test like a builder: make each piece fail loudly or pass obviously, one at a time.

Step 0 — reload after every edit. Loot tables, predicates, advancements, and functions all hot-reload with /reload (Chapter 7). Get in the habit: edit, save, /reload, test. If /reload prints a red error in chat, a file has a JSON typo; fix that before anything else, because a file that won’t load does nothing.

Step 1 — confirm the item, alone. Before testing drops at all, give yourself the finished blade with a tiny helper function so you’re sure the item is right:

mypack/data/mypack/function/give_test_blade.mcfunction

give @s diamond_sword[custom_name={"text":"Sunforged Blade","color":"gold","italic":false},lore=[{"text":"Forged in desert light","color":"gray","italic":true}],enchantments={"minecraft:sharpness":4,"minecraft:fire_aspect":2},rarity="epic",custom_data={mypack_sunforged:true}]

Run function mypack:give_test_blade (as a chat command, /function mypack:give_test_blade). If the blade looks right here, you know the components are correct; if the loot version later looks different, the bug is in the loot functions, not the item design.

Step 2 — force the drop to confirm the wiring. A 5% chance gated by three conditions is miserable to test by luck. Temporarily make the pool always drop: in husk.json, raise random_chance to 1.0, and comment out (actually, JSON has no comments, so temporarily delete) the two reference conditions and killed_by_player. Now every husk you kill drops the blade. /reload, kill a husk, confirm the sword appears with all its components. This proves the loot entry and functions work, separate from the conditions.

Step 3 — add the conditions back one at a time. Restore killed_by_player first; confirm a player kill still drops it but (say) lava does not. Then restore with_diamond_sword; confirm it drops only when you swing a diamond sword, not your fist. Then is_night; test once at night, once after /time set day. Adding conditions one at a time means that when the drop suddenly stops, you know exactly which condition you just added is the culprit: almost always a typo in a predicate name or a predicate file that itself won’t load. (Test a predicate in isolation the Chapter 18 way: execute if predicate mypack:is_night run say it is night in a function.)

Step 4 — lower the chance and test the detector. Put random_chance back to 0.05. Now test the advancement side independently: kill any husk (the detector ignores the drop and the sword entirely) and confirm the flame burst, the sound, and the Fire Resistance fire every time. If they don’t, the bug is in the advancement or the function, not the loot table.

That four-step loop (item alone → force the drop → conditions one at a time → detector on its own) is the whole testing discipline. Each step isolates one system so a failure points at one file. When all four pass, the project works; restore the real values and play.

What Can Go Wrong? When the whole thing “doesn’t work,” resist editing all six files. Ask which step fails. Item wrong → it’s the components (Step 1). Nothing drops even forced → loot path or JSON error (Step 2). Drops when forced but not normally → a condition/predicate (Step 3). Drop fine but no flair → advancement or function (Step 4). The file map at the top of the chapter is your checklist.

Practice

These extend the project you just built; keep the same files.

  1. A second special drop. Give the husk a second gated pool that drops a different custom item under different conditions: say, a named, custom_data-stamped bone (“Sun-bleached Bone”) when killed during the day instead of at night. Reuse the set_name + set_components pattern, and write a new is_day predicate (or invert is_night with inverted + reference, the Chapter 18 way).

  2. Pick a different mob. Copy husk.json to entities/zombie.json and adapt it so zombies have their own rare drop. Notice how little changes: the loot context is the same for any mob death, so all your conditions and functions carry over. Update the advancement’s entity.type to minecraft:zombie if you want the celebration there too.

  3. Tune the feel. Change the celebration: a different particle (try minecraft:soul or minecraft:crit), a different playsound event, a different effect (a brief minecraft:strength?). Adjust the particle <count> and <delta> and watch how the cloud’s size and density change. This is pure iteration (reload, watch, adjust) and it’s most of what polishing a project actually is.

What Can Go Wrong

The husk drops vanilla loot, like nothing changed. Your override file isn’t being read. Check the path letter for letter: data/minecraft/loot_table/entities/husk.json, minecraft namespace, singular loot_table. Run /reload and watch for a red error: a JSON mistake (a missing comma, a stray bracket) stops the file loading silently as far as gameplay is concerned.

The blade drops, but plain — no name, no enchantments. The entry’s functions aren’t applying. Most often the set_components map has a misspelled component ID, or a function object is missing its "function" key. Use Step 1’s give_test_blade to confirm the target item, then compare it field by field with what actually drops.

It drops when I force it, but never in normal play. A condition is failing. The usual culprit is a reference pointing at a predicate name that doesn’t exist (a typo in with_diamond_sword or is_night), or a predicate file that itself won’t load. Test each predicate alone with execute if predicate (Chapter 18). Remember too that killed_by_player needs a player kill; fall damage, cacti, or another mob won’t count.

The celebration fires but the sword never drops (or vice versa). Good: that’s the design working. The advancement detector and the loot table are independent. The advancement fires on every husk kill; the drop needs all four conditions plus the 5% roll. They’re separate systems wired to the same event, and testing them separately (Steps 2 and 4) is exactly why they don’t get tangled.

What You Know Now

You’ve built your first complete project: six small files (two predicates, a loot table, an advancement, and two functions) wired into a single feature. You saw how a loot table references predicate files by name (reference), how a vanilla mob’s drops are overridden at the minecraft: address while keeping its normal loot, how set_components carries the components (enchantments, rarity, custom_data) that have no friendly loot function, and how a display-less advancement detects an event and fires a celebration function of particle, sound, and effect commands, re-arming itself with advancement revoke so it works on every kill.

Most of all, you learned to test like a builder: isolate the item, force the drop, add conditions one at a time, and check the detector on its own, so when a multi-file project misbehaves, a failure points at one file instead of six. That testing discipline is worth more than any single command in this book; you’ll use it on every project ahead.

You can now build: custom mob drops gated by who/what/when, rare reward items assembled from components inside a loot table, silent event detectors that fire feedback the instant something happens, and (the real skill) a multi-file feature you can actually debug.

Next, in Chapter 32, you’ll turn the advancement from a hidden detector into the star of the show: a whole custom achievement tree with its own themed tab, progressive goals, and polished display, the visible cousin of the silent detector you just wired up.