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 37 — Advanced Loot Tables

What You’ll Build

Back in Chapters 16 and 17 you learned the everyday half of loot tables: pools that get rolled, entries with weights that decide which item wins a roll, conditions that gate a pool or entry, and functions that modify the dropped item. That’s enough to make a zombie drop a custom sword or a chest fill with random treasure.

This chapter adds the advanced half: the parts that let a single loot table make real decisions. By the end you’ll have a custom block in mypack that drops different things depending on the enchantments on the tool that mined it: bare-handed or with a plain pickaxe you get a chunk of raw material, with Fortune you get more of it, and with a special enchantment you get a rare shard that copies data straight off the block. Along the way you’ll learn entry types that choose between children, functions that copy data off the thing being looted, a function that calls a saved item modifier by name, the full list of loot context types (what data each situation hands your loot table), a condition that reads a scoreboard, and the tools that scale drops by enchantment level.

This chapter extends your mypack pack from Chapter 9 and the test world from Chapter 1. It assumes the loot-table structure from Chapter 16 (pools, entries, weight, number providers), the conditions and functions from Chapter 17 (match_tool, set_name, set_components, enchanted_count_increase), predicates from Chapter 18 (all_of/any_of/inverted and the reference condition), data components from Chapter 21, and scoreboards from Chapter 11. Every new keyword below is copied straight from the wiki so the spelling is exactly right.

Composite entries: choosing between children

In Chapter 16 every entry you wrote was a singleton entry: one entry, one weighted chance to drop one kind of item (item, tag, loot_table, empty). The other kind is a composite entry: an entry that doesn’t get rolled itself, but instead expands into child entries before the pool is rolled. Composite entries first expand their children, and then singleton entries that do not meet their conditions are removed from the pool, and the entries are rolled afterward.

A composite entry has a children field (the list of entries it expands into) instead of a name. There are three composite types:

  • group — “All entries in the children are extracted.” A plain bundle. Handy when one condition should apply to several entries at once: put the condition on the group and it gates the whole batch.
  • alternatives — “Extracts only the first successful (conditions are met) entry. Conditions are checked in order.” This is an if / else-if / else for loot: the first child whose conditions pass wins, and the rest are skipped.
  • sequence — “Extracts the child entries in sequential order, continuing until an entry’s conditions fail, then no more children are extracted.” This drops a run of children: every one from the top until the first failure.

Here’s the shape of an alternatives entry. Read it as “drop a diamond if a player’s tool has Silk Touch; otherwise drop coal”:

{
  "type": "minecraft:alternatives",
  "children": [
    {
      "type": "minecraft:item",
      "name": "minecraft:diamond",
      "conditions": [
        {
          "condition": "minecraft:match_tool",
          "predicate": { "enchantments": [ { "enchantments": "minecraft:silk_touch" } ] }
        }
      ]
    },
    {
      "type": "minecraft:item",
      "name": "minecraft:coal"
    }
  ]
}

The first child has a condition; the second has none, so it always passes. alternatives checks them top to bottom and keeps the first that passes, exactly the “else” behaviour you’d want.

Under the Hood (skippable). Why is a composite entry not just rolled like a normal entry? Because weights only make sense between things competing in the same pool. A composite entry flattens into the pool first (its surviving children become ordinary weighted singletons), and then the pool rolls. So alternatives decides which children exist, and weight still decides which of the survivors wins the roll.

dynamic and tag, revisited

Two more entry types round out the advanced set. You met tag briefly in Chapter 16; the other, dynamic, is new here.

The dynamic entry “Drops block-specific loot.” Its name field can be contents (which drops the items in a shulker box) or sherds (which drops the sherds of a decorated pot). It “Does not work for other blocks,” so it’s a narrow tool: it exists so a shulker box drops what’s inside it and a decorated pot drops its sherds.

{
  "type": "minecraft:dynamic",
  "name": "contents"
}

The tag entry drops items from an item tag. Its behaviour flips on a boolean expand field: if expand is false it’s a single entry that “drops all items in the tag”; if expand is true it becomes a composite entry that “provides one singleton entry per item in the tag with the same weight and conditions.” It spreads the tag’s items across the pool as separate weighted options. (One caveat worth knowing: when expand is true, item modifiers attached to it “do not work due to a bug.”)

Reusing whole loot tables and saved modifiers

You already have two ways to reuse loot you built elsewhere; this section just lines them up.

The loot_table entry (Chapter 16) drops the loot from another loot table. Its field is value, the loot table to be used. The wiki warns: it “Cannot be the ID of the current loot table file. Recursive calling is not allowed.” So a chest table can pull in a shared “common junk” table without copy-pasting it.

{
  "type": "minecraft:loot_table",
  "value": "mypack:common_junk"
}

The matching tool on the function side is the reference function. From the Item modifier page: reference — “Call sub-functions,” with a name field that is the “Location of function to call.” In plain terms, you save an item modifier as its own file (you learned standalone item modifiers in Chapter 20) and then call it by name from inside a loot table’s functions list, instead of pasting the whole modifier in. One saved modifier, reused everywhere.

{
  "function": "minecraft:reference",
  "name": "mypack:name_mystic_shard"
}

Modern Minecraft. Don’t confuse this reference function with the reference condition from Chapter 18. They share a name and a name field but do different jobs: the condition (in a conditions list) invokes a saved predicate and returns pass/fail; the function (in a functions list) invokes a saved item modifier and applies it to the item. Same idea (“call something I saved elsewhere”) applied to two different kinds of saved file.

Copy functions: pulling data off the source

Sometimes the item you drop should carry data from the thing it came from: the block that broke, the mob that died. Two functions do this.

copy_components — “Copies components from a specified source onto an item.” Its fields are:

  • source — “Source type to pull from. Specifies an entity or block entity from loot context.” The wiki lists the allowed values: block_entity, this, attacker, direct_attacker, attacking_player, target_entity, interacting_entity, tool.
  • include — optional, “A list of components to include. If omitted, all components are copied.”
  • exclude — optional, “A list of components to exclude.”

So copy_components with "source": "block_entity" copies data components off the broken block entity onto the dropped item. That’s how, for example, a broken block can drop an item that remembers a stored component.

{
  "function": "minecraft:copy_components",
  "source": "block_entity",
  "include": [ "minecraft:custom_name" ]
}

The second is copy_custom_data. The wiki describes it as: “Copies NBT values from an entity, block entity, or storage to the item’s minecraft:custom_data component.” This is the modern, correctly-named function for copying raw NBT. If you’ve seen old tutorials call it copy_nbt, this is its current name. Its source (shorthand form) names the thing to copy from, and an ops list gives the copy operations, each with a source NBT path, a target path (relative to the item’s minecraft:custom_data), and an op set to replace, append, or merge.

{
  "function": "minecraft:copy_custom_data",
  "source": "block_entity",
  "ops": [
    { "source": "Owner", "target": "miner", "op": "replace" }
  ]
}

Modern Minecraft. In Chapter 21 you learned that data components replaced most raw NBT. That’s why there are two copy functions. copy_components is the modern, component-aware one you’ll reach for most; copy_custom_data is for the cases where you genuinely need to move loose NBT into the catch-all minecraft:custom_data component. Prefer copy_components unless you specifically need raw NBT.

Loot context types: what data each situation gives you

In Chapter 17 you met loot context as the reason killed_by_player and match_tool only work on mob and block tables: a loot table only has the parameters its situation supplies. This section deepens that. Loot context is “a set of parameters available to loot tables, predicates, item modifiers, and number providers,” and the check is done “when the data pack is loaded, rather than at runtime.”

That check is driven by the loot table’s type field (Chapter 16’s root field). The wiki: type “Specifies the loot context in which the loot table should be invoked. All item modifiers, predicates and number providers are then validated to ensure the parameters of the context type specified here cover all requirements, and prints a warning message in the output log if any modifier or predicate requires a context parameter that is not covered.” In other words, declaring the right type lets the game catch your mistakes at load time. If you ask for the killer in a chest table, you get a warning, not a silent failure later.

Different situations provide different parameters. Three you’ll care about most:

  • A chest / container being opened provides an Origin (the centre of the chest) and a this entity (the entity that opened it). No tool, no killer.
  • A living entity’s death (a mob dying) provides the this entity (the one that died), an Origin, a Damage source, and three attacker entities: attacker (“the source of the final damage”), direct_attacker (“the entity that directly contacted the victim”), and attacking_player (“the player that most recently damaged the victim”). This is why killed_by_player, which checks for that attacking_player, works on mob tables.
  • Mining a block provides a Block state (“the block that was broken”), an Origin, a Tool (“the tool used to mine the block”), a this entity (the player who mined it), and a Block entity (“any block entity data of the block that was broken, if it was a block entity”). This is the context your custom-block table runs in, and the reason match_tool and Fortune scaling are available to it.

Two special type values are worth naming. "type": "empty" “means no context parameters can be used in this loot table”; it’s the do-nothing table. "type": "generic" (the default if you omit type) “means no checking for context parameters in this loot table when loading the data pack,” so it skips the load-time check entirely. Declaring a specific type is better when you can, precisely because it turns on that helpful warning.

Try It! Make a deliberate mistake to see the safety net work. In a block-mining table, add a killed_by_player condition (which needs the attacking_player the mob-death context provides, not the block context). With the table’s type set correctly for block mining, the game should warn you in the output log when the pack loads, before you ever break the block.

entity_scores: reading a scoreboard in a loot condition

Chapter 17’s conditions tested the world (was it a player kill? what tool?). The entity_scores condition reaches into the scoreboard system from Chapter 11. The wiki: it “Checks the scoreboard scores of an entity.” Its fields:

  • entity — “The entity to check. Specifies an entity from loot context” (so this, attacker, and so on — only the entities the current context provides).
  • scores — “Scores to check. All specified scores must pass for the condition to pass.” Each key is a scoreboard objective; the value is either a { "min": ..., "max": ... } range or, in the shorthand form, “a single number” the score must equal.

So you can make a drop happen only when a player’s score is in range. For example, only reward a block’s special loot once the player has reached a quest milestone you track on a scoreboard objective.

{
  "condition": "minecraft:entity_scores",
  "entity": "this",
  "scores": {
    "mypack_quest": { "min": 5 }
  }
}

This reads: pass only if the this entity’s mypack_quest objective is at least 5. Because it needs the entity from loot context, it “always fails if not provided,” so the context’s type has to actually supply that entity.

Scaling drops by enchantment: table_bonus and apply_bonus

The last pieces are the tools that make drops respond to Fortune and similar enchantments: the way mining gravel with Fortune drops flint more often, or an ore drops more ingots. There are two, and they do different jobs.

table_bonus is a condition. The wiki: it “Passes with probability picked from a list, indexed by enchantment power. Requires tool provided by loot context. If not provided, the enchantment level is regarded as 0.” Fields:

  • enchantment — the resource location of the enchantment.
  • chances — “List of probabilities for enchantment power, indexed from 0.”

So chances: [0.1, 0.5, 1.0] means: with the enchantment at level 0 the entry passes 10% of the time, at level 1 it’s 50%, at level 2 it’s guaranteed. It’s a chance gate that improves with the enchantment, perfect for a rare extra drop.

{
  "condition": "minecraft:table_bonus",
  "enchantment": "minecraft:fortune",
  "chances": [ 0.1, 0.14, 0.25, 1.0 ]
}

apply_bonus is a function: it scales the item count. The wiki: it “Applies a predefined bonus formula to the count of the item stack.” Fields:

  • enchantment — the enchantment “used for level calculation.”
  • formula — a resource location. The wiki lists three: ore_drops (“a special function used for ore drops in the vanilla game”), uniform_bonus_count (uniform distribution from 0 to level * bonusMultiplier), and binomial_with_bonus_count.
  • parameters — “Values required for the formula” (e.g. bonusMultiplier for uniform_bonus_count; extra and probability for the binomial one).
{
  "function": "minecraft:apply_bonus",
  "enchantment": "minecraft:fortune",
  "formula": "minecraft:ore_drops"
}

That single function is exactly how vanilla ores multiply their drops with Fortune. Drop it into your own block’s loot table and your block behaves like an ore.

Modern Minecraft. Chapter 17 taught enchanted_count_increase for the Looting bonus on mob drops. apply_bonus is its cousin for mining: same idea (more drops per enchantment level), but it runs in the block-mining context and reads the tool’s enchantment instead of the killer’s. Use enchanted_count_increase for mob loot, and apply_bonus for block loot.

Walkthrough: a tool-aware custom block drop

Time to put it together. You’ll write a loot table for a custom “mystic ore” block. The plan:

  1. If the tool has Silk Touch, drop the block itself (one mystic ore).
  2. Otherwise, drop a raw material, and if the tool has Fortune, drop more of it (via apply_bonus), plus a chance at a rare bonus shard (via table_bonus).
  3. The rare shard gets a custom name applied through a saved item modifier called with reference.

First, the saved modifier the table will call. Create this file:

mypack/data/mypack/item_modifier/name_mystic_shard.json

{
  "function": "minecraft:set_name",
  "name": { "text": "Mystic Shard", "italic": false, "color": "aqua" },
  "target": "custom_name"
}

This is a standalone item modifier (Chapter 20): a single set_name function that gives the item the custom name “Mystic Shard” (Chapter 17 taught set_name with its target of custom_name).

Now the loot table itself. Notice the type at the top: minecraft:block declares the block-mining context, which turns on load-time checking and gives us the tool parameter that match_tool, table_bonus, and apply_bonus all need.

mypack/data/mypack/loot_table/blocks/mystic_ore.json

{
  "type": "minecraft:block",
  "pools": [
    {
      "rolls": 1,
      "entries": [
        {
          "type": "minecraft:alternatives",
          "children": [
            {
              "type": "minecraft:item",
              "name": "minecraft:diamond_ore",
              "conditions": [
                {
                  "condition": "minecraft:match_tool",
                  "predicate": {
                    "enchantments": [ { "enchantments": "minecraft:silk_touch" } ]
                  }
                }
              ]
            },
            {
              "type": "minecraft:item",
              "name": "minecraft:diamond",
              "functions": [
                {
                  "function": "minecraft:apply_bonus",
                  "enchantment": "minecraft:fortune",
                  "formula": "minecraft:ore_drops"
                }
              ]
            }
          ]
        }
      ]
    },
    {
      "rolls": 1,
      "conditions": [
        {
          "condition": "minecraft:table_bonus",
          "enchantment": "minecraft:fortune",
          "chances": [ 0.0, 0.25, 0.5, 1.0 ]
        }
      ],
      "entries": [
        {
          "type": "minecraft:item",
          "name": "minecraft:amethyst_shard",
          "functions": [
            {
              "function": "minecraft:reference",
              "name": "mypack:name_mystic_shard"
            }
          ]
        }
      ]
    }
  ]
}

Walk through what happens when the block breaks:

  1. First pool, alternatives entry. Its first child drops diamond_ore only if match_tool sees Silk Touch. If Silk Touch is present, that child wins and the second is skipped, so you get the ore block back. If not, the second child (no condition, always passes) wins and drops a diamond, whose count is scaled by apply_bonus reading Fortune, so plain tools give one and Fortune gives more.
  2. Second pool only runs at all when its table_bonus condition passes, and that chance climbs with Fortune (0.0 at level 0 means never without Fortune; 1.0 at level 3 means always). When it passes, it drops an amethyst_shard and calls your saved name_mystic_shard modifier through reference, so the shard arrives already named “Mystic Shard.”

To use this table on an actual block in-game you’d assign it the way Chapter 17 assigned a table to zombies, by overriding the block’s loot table file (here we’ve put it at a mypack: path so you can test it with the loot command first). Try it from a function:

mypack/data/mypack/function/test_mystic_ore.mcfunction

loot give @s loot mypack:blocks/mystic_ore

Figure (to be captured). running test_mystic_ore with a Fortune III pickaxe in hand and seeing extra diamonds plus a named Mystic Shard appear

Because loot give ... loot uses the chest/command context rather than a real block break, the surest test is to bind the table to a real block and mine it. But the command is a quick first check that the file parses and the named shard appears.

Practice

These extend the block you just built. Keep working in the same file unless told otherwise.

  1. A coal consolation prize. Add a third child to the alternatives entry (after the Silk Touch and Fortune children) so there’s always something. Since alternatives keeps the first passing child, where in the children list must a no-condition fallback go for it to act as the “else”?

  2. Score-gated jackpot. Add a third pool that drops a minecraft:nether_star, gated by an entity_scores condition on the this entity requiring an objective mypack_mining_level of at least 10. (You set up scoreboard objectives in Chapter 11.) Remember entity_scores needs the entity from loot context, which the block-mining type provides as this.

  3. Copy the block’s name onto the drop. If your mystic ore is a block entity that can hold a custom name, add a copy_components function ("source": "block_entity", "include": ["minecraft:custom_name"]) to the diamond entry so a renamed block hands its name to its drop.

  4. Try It! (a sequence). Replace one pool’s single entry with a sequence composite entry whose children each have a table_bonus condition at increasing levels. Watch how sequence drops a run (every child from the top until one fails) versus alternatives, which keeps only the first that passes.

What Can Go Wrong

The wrong context type, or a missing parameter. If you set "type": "minecraft:chest" on this table, the chest context provides no Tool, so match_tool, table_bonus, and apply_bonus have nothing to read. Conditions that require a missing parameter “always fail if not provided,” and the load-time check warns you in the output log. Fix: use "type": "minecraft:block" for a block’s loot, so the Tool and Block state parameters exist.

Confusing table_bonus with apply_bonus. They’re easy to swap because both involve an enchantment. Remember: table_bonus is a condition (it goes in a conditions list and decides whether an entry/pool runs, by chance); apply_bonus is a function (it goes in a functions list and changes the count). Putting one where the other belongs makes the file fail to load.

Expecting copy_nbt to exist. Old tutorials reference a function named copy_nbt. In current Java Edition the function is copy_custom_data (it copies NBT into the minecraft:custom_data component), and for components you usually want copy_components instead. Use the current names or the loot table won’t parse.

A reference that points at the wrong kind of file. The reference function must name an item modifier file; the reference condition must name a predicate file. Point a function at a predicate (or vice-versa) and it won’t resolve. Double-check which list (functions or conditions) your reference sits in.