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 17 — Loot Tables: Conditions and Functions

What You’ll Build

In the last chapter you built a loot table out of pools, entries, and weights: the machinery that decides which items can drop and how rare each one is. That’s half the story. A loot table that can only say “drop a diamond 5% of the time” is useful, but it can’t yet say “…but only if a player did the killing,” or “drop three diamonds,” or “drop a diamond sword that’s already enchanted and has a custom name.” Those two missing powers are what this chapter adds.

The first is conditions, tests that decide when a pool or an entry is allowed to drop at all. The second is functions, steps that modify the item on its way out: bumping its stack size, enchanting it, renaming it, giving it lore. Together they turn a plain list of possible drops into something that reacts to how the loot was earned and hands back exactly the item you designed.

By the end you’ll have made zombies rarely drop a custom-named, enchanted diamond sword, but only when a player lands the kill, by writing one loot table file in the mypack pack you started in Chapter 9. Along the way you’ll learn five condition types and six functions, with the names exactly right.

This chapter extends your mypack pack and uses the test world you’ve used since Chapter 1. It assumes the loot-table structure from Chapter 16 (pools, entries, weights, number providers).

Where conditions and functions live in a loot table

Before meeting any specific condition or function, you need to know where they go. Recall the shape of a loot table from Chapter 16: a root object with a pools list, and each pool with an entries list. Conditions and functions slot into that shape at three levels, and the wiki spells out each one.

At the pool level, a pool may have a conditions field (“a list of predicates, that must all pass for this pool to be used”) and a functions field that “applies item modifiers in order, onto all item stacks dropped by this pool.” At the entry level, a single (singleton) entry may also have its own conditions (“a list of predicates that must all pass for this singleton entry to be included into the pool”) and its own functions, applied “onto all item stacks dropped by this singleton entry.” And at the very top of the file, the loot table’s root object may carry a functions list that applies “onto all item stacks dropped by this table,” covering every pool at once.

So the rule of thumb is: put a condition or function where you want its reach. Gate a whole pool? Use the pool’s conditions. Modify just one item among several? Use that entry’s functions. The syntax is the same everywhere; only the placement changes.

A note on the word “predicate” The wiki calls the things in a conditions list “predicates.” A predicate is just a test that comes out true or false. In this chapter you’ll write these tests inline, right inside the loot table. In Chapter 18 you’ll learn to save a test as its own reusable file (also called a predicate) and point many loot tables, commands, and advancements at it. Same idea, two homes: here it lives inside the loot table; there it gets its own file.

Conditions: deciding when an entry drops

A condition is one entry in a conditions list: a small object that names a test and gives it whatever values it needs. Every condition has a condition field holding the resource location (the namespace:path name you met in Chapter 8) of the test type. If you list several conditions, all of them must pass: the pool or entry drops only when every test comes out true.

Here are the five condition types this chapter uses. The names and fields are copied exactly from the wiki’s predicate page.

random_chance: “Generates a random number between 0.0 and 1.0, and checks if it is less than a specified value.” It has one field, chance, a number from 0.0 to 1.0. So chance: 0.05 means “pass about 5% of the time.” This is how you make a drop rare.

{ "condition": "minecraft:random_chance", "chance": 0.05 }

killed_by_player: “Checks if there is a attacking_player entity.” In plain terms, it passes only when a player dealt the killing blow. If a cactus, a fall, or another mob killed the zombie, this test fails and the gated drop is skipped. It needs no fields at all:

{ "condition": "minecraft:killed_by_player" }

match_tool: “Checks tool used to mine the block.” More broadly, it checks the item that was used (for a mob kill, the weapon in the killer’s hand). It takes a predicate field describing the item to match (the same item-matching format advancements use). You’ll see its shape in the “Try It!” box later; for now, know that this is how a loot table can react to what the player was holding.

entity_properties: “Checks properties of an entity.” It takes an entity field naming which entity from the situation to look at (such as "this", the entity that died) and a predicate field describing what to check about it. This is the general-purpose “look at the mob/player and test something about it” condition.

location_check: “Checks the current location against location criteria.” It takes a predicate field describing the place (biome, dimension, and so on), plus optional offsetX, offsetY, and offsetZ numbers to shift the spot being checked. This is how a drop can depend on where it happened: only in the Nether, only in a particular biome.

Why do killed_by_player and the tool check only work on mob drops? Some conditions need information the situation has to provide. The wiki calls that information loot context: “a set of parameters available to loot tables, predicates, item modifiers, and number providers.” When a living entity dies, its loot context supplies an attacking_player entity and the Tool that was used, which is exactly what killed_by_player and match_tool read. A chest being opened has no killer and no weapon, so those same conditions would always fail there. That’s why this chapter’s project lives on a mob loot table: it’s the context that hands these conditions what they need.

Functions: modifying the item that drops

A function (the wiki also calls it a loot function or item modifier) is one step that changes the item being dropped. Where a condition is a yes/no gate, a function is an action: “set the count to 3,” “enchant it,” “give it this name.” Functions live in a functions list, and each one names its type in a function field, the same resource-location naming as conditions, just a different key. The wiki notes that functions in a list are “applied in order,” so later functions act on the result of earlier ones.

A function can also carry its own conditions list: “A list of predicates, of which all must pass, for this function to be applied.” That lets you, say, only rename the sword when a player got the kill, while still dropping a plain sword otherwise. Handy, but optional.

Here are the six functions this chapter uses, with their exact fields from the wiki.

set_count: “Sets the stack size.” Its main field, count, is a number provider (the exact / uniform / binomial pickers from Chapter 16), so the count can be fixed or randomised. An optional add boolean, when true, makes the change relative to the current count instead of replacing it.

enchant_with_levels: “Enchants the item, with the specified enchantment level (roughly equivalent to using an enchanting table at that level).” Its levels field is a number provider giving the enchantment level to spend; a higher level means stronger, more numerous enchantments, exactly like a real enchanting table. An optional options field is a list limiting which enchantments may be chosen; leave it out and “all enchantments are possible.”

set_name: “Adds or changes the item’s custom name.” Its name field is a text component, the rich-text format from Chapter 5, so you get colour and styling for free. An optional target field chooses which name to set: the wiki’s allowed values are "custom_name" (the default) or "item_name" (the item’s built-in display name).

set_lore: “Adds or changes the item’s lore.” Lore is the small grey description text under an item’s name. Its lore field is a list of text-component lines. It also requires a mode field saying how to combine with any existing lore: one of "append", "insert", "replace_all", or "replace_section". For a fresh item, "replace_all" is the simplest: it sets the lore to exactly your lines.

enchanted_count_increase: “Adjusts the stack size based on the level of the specified enchantment on the killer entity.” This is the Looting bonus: more loot when the killer’s weapon has the Looting enchantment. Its count field (a number provider) is how many extra items to add per level of the enchantment; an optional limit caps the final stack size (0 means no cap); and enchantment names which enchantment to read, normally minecraft:looting.

Modern Minecraft If you follow an older tutorial, you’ll see this function called looting_enchant. That name is gone. In current Java Edition the wiki lists it as enchanted_count_increase: same job (a Looting-style bonus), new name, and it now works for any enchantment you name, not just Looting. If a looting_enchant from an old guide silently does nothing, this rename is why.

set_components: “Sets components of an item.” This is the modern, all-purpose way to attach custom data to an item: its single field, components, is “a map of components ID to component value.” Components are a deep topic. They’re how every piece of modern item data (damage, custom model, container contents, and much more) is stored, so this book gives them their own home in Chapter 21. Here you only need to know set_components exists and is the function you reach for when a simpler one like set_name doesn’t cover what you want.

Modern Minecraft set_components replaces the old set_nbt function you’ll see in pre-1.20.5 tutorials. The mental shift (from one big blob of “NBT” to a tidy map of named components) is the same one running through all of modern item handling, and Chapter 21 is where you’ll learn it properly. For this chapter, the friendly functions (set_name, set_lore, enchant_with_levels) do everything our sword needs, so you won’t have to write a single component yet.

Walkthrough: the rare zombie sword

Time to build the project. The plan: edit the zombie’s loot table so that, on top of its normal drops, a zombie killed by a player has a small chance to drop a diamond sword that is enchanted, named “Cursed Blade,” and carries a line of lore, and gets a Looting bonus to boot.

Step 1 — find the right file

A mob’s drops come from a loot table named after the mob, in the singular. To change what zombies drop, you create a file at this path inside your pack:

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

Notice the namespace is minecraft, not mypack. The wiki explains why: “Editing this file in a datapack would make every zombie in a Minecraft world use the modified datapack’s loot table rather than the default zombie loot table.” Because you’re overriding vanilla’s own minecraft:entities/zombie table, your file has to sit at the same address. (This is the same override trick you’d use for any vanilla file: your version wins.)

What Can Go Wrong? Overriding the whole file means you replace vanilla’s zombie drops, not add to them. If you only write your sword pool, zombies stop dropping rotten flesh entirely. The listing below keeps rotten flesh in a first pool so the vanilla drop survives. When you override a vanilla table, you’re responsible for everything it used to do.

Step 2 — write the loot table

Here is the complete file. Read it once top to bottom, then we’ll walk the new parts.

mypack/data/minecraft/loot_table/entities/zombie.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 }
      ],
      "entries": [
        {
          "type": "minecraft:item",
          "name": "minecraft:diamond_sword",
          "functions": [
            {
              "function": "minecraft:enchant_with_levels",
              "levels": { "min": 20, "max": 30 }
            },
            {
              "function": "minecraft:set_name",
              "name": { "text": "Cursed Blade", "color": "dark_purple", "italic": false }
            },
            {
              "function": "minecraft:set_lore",
              "mode": "replace_all",
              "lore": [
                { "text": "Dropped by the restless dead", "color": "gray", "italic": true }
              ]
            },
            {
              "function": "minecraft:enchanted_count_increase",
              "enchantment": "minecraft:looting",
              "count": { "min": 0, "max": 1 }
            }
          ]
        }
      ]
    }
  ]
}

Step 3 — read it back

Start at the top. "type": "minecraft:entity" declares the loot context: this table runs on a mob’s death, which (from the box earlier) is what makes killed_by_player and the Looting bonus work. The first pool is plain Chapter-16 material: one roll, one entry, rotten flesh, with a set_count of 0–2 so the vanilla drop is preserved.

The second pool is where this chapter lives. Its conditions list holds two tests, and both must pass for the pool to run: killed_by_player (a player got the kill) and random_chance with chance: 0.05 (the 1-in-20 rarity). Gate the pool, and the whole sword is skipped most of the time. Clean and cheap.

Inside, the single entry drops a minecraft:diamond_sword, then its functions list reshapes that plain sword in order:

  1. enchant_with_levels with levels 20–30 enchants it as if at an enchanting table set to a random level in that range: strong, varied enchantments every time.
  2. set_name gives it the custom name “Cursed Blade.” The name value is a Chapter-5 text component, so "color": "dark_purple" tints it; the "italic": false is just ordinary text-component styling (Chapter 5) turning the slant off.
  3. set_lore with mode: "replace_all" sets a single grey, italic line of lore beneath the name.
  4. enchanted_count_increase reads minecraft:looting on the killer’s weapon and adds 0–1 extra swords per Looting level, a small bonus for a well-enchanted player.

Step 4 — load and test

Save the file, then in your test world’s chat run /reload so the game re-reads your pack. Now go hit some zombies. Punching them won’t always work. Remember, killed_by_player needs a player kill, and even then the 5% chance means most zombies drop only rotten flesh. Kill a dozen or two and a glowing, purple-named “Cursed Blade” should eventually clatter to the ground.

Figure (to be captured). the “Cursed Blade” diamond sword item dropped on the ground, showing its dark-purple custom name and grey lore line in the floating tooltip

Try It! Want the sword only when the player kills with a particular weapon? Add a match_tool condition to the pool. The wiki says match_tool takes a predicate field that describes the held item, “using same structure as advancements” — the same item-matching format an advancement uses to check what you’re holding:

{
  "condition": "minecraft:match_tool",
  "predicate": { }
}

The exact contents of that item predicate (matching by item id, by item tag, by enchantment, and so on) are the advancement item-condition format. You’ll meet it properly in Chapter 18 alongside predicate files. For now, an empty predicate: {} matches any tool and lets you see the wiring; fill it in once Chapter 18 has shown you the item-predicate fields.

Practice

These extend the zombie-sword table you just built. Make a copy of the file first if you want to keep the original.

  1. Make it rarer, then richer. Lower the random_chance to 0.02. Then add a second line of lore using set_lore. Remember lore is a list, so add another text-component object to the list. Reload and confirm both lines show up.

  2. A location-gated drop. Add a new pool whose conditions include a location_check, and have it drop a small bonus (say, a few minecraft:bone) only somewhere specific. Use the entity_properties or location_check field shapes from this chapter as your guide; you’ll fill in the exact predicate contents properly in Chapter 18, so for now a bare location_check with an empty predicate: {} is enough to see the wiring.

  3. A table-wide function. Move a set_count out of an entry and up to the loot table’s root functions list. Watch how it now applies to every drop (rotten flesh included) because a root function runs “onto all item stacks dropped by this table.” This shows you the difference between gating at the entry, the pool, and the whole table.

What Can Go Wrong

The sword never drops, no matter how many zombies you kill. Two usual causes. First, check you’re killing them yourself: killed_by_player fails for fall damage, cacti, or another mob finishing the job. Second, 5% is genuinely rare; kill more, or temporarily raise chance to 1.0 to confirm the table works, then lower it back.

You changed the file but nothing changed in game. Did you run /reload? Loot-table files hot-reload with /reload (unlike dynamic registries, which need a world reboot, Chapter 7). Also re-check the path: it must be data/minecraft/loot_table/entities/zombie.json exactly, with the singular loot_table folder: the same singular-folder rule that bites people on function and recipe.

Zombies stopped dropping rotten flesh. You overrode vanilla’s whole table and forgot to keep its drops. Add a plain pool for rotten flesh back, as in the listing: overriding means you own all of the mob’s loot, not just your additions.

What You Know Now

You can now make a loot table react. Conditions (random_chance, killed_by_player, match_tool, entity_properties, location_check) decide when a pool or entry is allowed to drop, and you know they read from the situation’s loot context, which is why kill-based and tool-based conditions belong on mob tables. Functions (set_count, enchant_with_levels, set_name, set_lore, enchanted_count_increase, and the all-purpose set_components) decide how the dropped item is modified, applied in order, optionally gated by their own conditions. You override a vanilla mob’s drops by writing a file at the same minecraft: address, and you keep the vanilla loot you still want.

You can now build: rare custom mob drops, enchanted and named reward items, Looting-scaled bonuses, and drops that only happen under conditions you choose. Next, in Chapter 18, you’ll lift those inline conditions out into reusable predicate files, so the same test can guard a loot table, a command, and an advancement without being rewritten each time. And the one function we only previewed here, set_components, opens up in Chapter 21, where data components get the full treatment.