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 36 — Damage Types

Part X — Advanced: Data-Driven Systems. Like the rest of Part X, this chapter is à la carte: read it when you want it. It assumes you’re comfortable with registry tags (Chapter 14) and with the two item components that have been quietly waiting on it: blocks_attacks (Chapter 23) and damage_resistant (Chapter 24).

What You’ll Build

Back in Chapter 23 you turned items into shields with the blocks_attacks component, and two of its fields (bypassed_by and the type inside damage_reductions) pointed at something called a “damage type tag” that we promised to explain in Chapter 36. In Chapter 24 the damage_resistant component did the same thing with its types field. This is Chapter 36. By the end of it, you’ll know exactly what those fields were pointing at, and you’ll be able to make your own.

A damage type is a named kind of damage with its own properties: how much hunger it drains, whether it gets stronger on Hard difficulty, which hurt animation it shows, and what death message it writes in chat. The game’s own “arrow damage,” “lava damage,” and “fall damage” are all damage types, each defined in a small JSON file. In this chapter you’ll write your own damage type called void touch, learn the /damage command that applies it, group damage types with damage type tags, and use a tag to make your void-touch damage ignore armor entirely. Everything goes into the running mypack data pack from Chapter 9, and you’ll test it in the Chapter 8 test world.

What a damage type actually is

Here’s the plain definition: damage types are JSON files located in data packs that define different kinds of damage that entities can take. They control which attributes the damage has as well as which death message is used when an entity dies due to that type of damage.

So a damage type is data, just like a recipe or a loot table. It lives at a predictable path:

data/<namespace>/damage_type/<name>.json

That means it follows the same friendly rule as the other data-pack files you’ve written since Chapter 9: when you change one and run /reload, the game picks it up right away. (Contrast that with enchantments back in Chapter 35, which are a dynamic registry and need a full world reload to change. Damage types are the easy kind: edit, /reload, done.)

One important limit: custom damage types can be applied only by using the /damage command. A custom damage type doesn’t automatically attach itself to anything: no block, no mob, no item starts dealing it on its own. The only way to make your damage type actually hurt something is the /damage command, which you’ll meet in a moment. That’s by design: it means you decide exactly when your custom damage happens.

The damage type file, field by field

The damage type file has a small, friendly set of fields. Here is every one of them.

The file is a single JSON object (a { } with key-value pairs inside, like every data-pack file since Chapter 8). Its fields:

  • message_id — a string. It’s used as part of the death message translation key when an entity dies to this damage type and death_message_type is set to default (the default, see the field below). (A death message is the line like “Steve was slain by a zombie” that appears in chat when something dies.) More on this just below.
  • exhaustion — a number (it can have a decimal). The amount of hunger exhaustion this damage causes. Exhaustion is the hidden meter that, when it fills up, eats into your saturation and then your hunger bar, so a high exhaustion value makes this damage also make you hungry.
  • scaling — a string controlling whether the damage gets bigger on harder difficulties. It must be one of exactly three values:
    • never — the damage is always the same, whatever the difficulty.
    • always — the damage always scales with difficulty.
    • when_caused_by_living_non_player — it scales with difficulty only if the attacker was a living entity that wasn’t a player (so a zombie’s hit scales, but your own hit doesn’t).
  • effectsoptional. A string controlling how the hit is shown to the player: the little hurt animation and sound. One of: hurt (the default), thorns, drowning, burning, poking, or freezing. If you leave it off, you get hurt.
  • death_message_typeoptional. A string choosing which style of death message to use. One of: default (the default), fall_variants (uses the fall-damage messages), or intentional_game_design (the joke “intentional game design” message you’ve seen from beds in the Nether). If you leave it off, you get default.

That’s the whole format. Notice that only three fields really must be thought about (message_id, exhaustion, and scaling), and the game’s built-in minecraft:arrow damage type uses exactly those three:

{
"exhaustion": 0.1,
"message_id": "arrow",
"scaling": "when_caused_by_living_non_player"
}

That single object is the entire vanilla arrow-damage definition: a tenth of a point of exhaustion, the arrow message id, and difficulty scaling only when a non-player living thing fired it. Your own damage types will look just as short.

Under the Hood (skippable). How does message_id become a death message? When death_message_type is default (the standard message logic), the game builds a translation key from it. In the normal case it’s death.attack.<message_id>, so a message_id of arrow looks up death.attack.arrow. If the killer was holding a named item, it instead uses death.attack.<message_id>.item, and for an “assisted” death (the dying entity was recently hurt by something living) it uses death.attack.<message_id>.player. You don’t have to supply those translation strings; if you don’t, the game just shows the raw key. Writing your own death-message text means adding a language file, which is a resource-pack job covered in Chapter 30; here we only need the message_id field itself.

Applying your damage type: the /damage command

A damage type sitting in a file does nothing until you fire it. The tool for that is /damage. Here is its Java Edition syntax:

damage <target> <amount> [<damageType>] [at <location>]
damage <target> <amount> [<damageType>] [by <entity>] [from <cause>]

The square brackets mean “optional,” so the simplest form is just damage <target> <amount>. The arguments:

  • target — which entity (or entities, via a selector) to damage.
  • amount — how much damage to inflict.
  • damageTypeoptional. Which damage type to use. If not specified, it defaults to minecraft:generic. This is where your custom damage type’s id goes.
  • at <location>optional. Where the damage came from, for damage that wasn’t caused by an entity (like the position of an exploding bed).
  • by <entity>optional. The entity that dealt the damage.
  • from <cause>optional. The cause of the damage, for example the skeleton that shot the arrow, as opposed to the arrow itself.

There’s one caveat worth understanding before you’re confused in-game: the amount you type is not guaranteed to be the exact damage applied. The damage specified by the <damageType> argument is not the exact amount of damage that will be applied to the entity. The resulting damage is affected by statistics that would otherwise modify or nullify it. In plain terms: your /damage runs through all the normal damage math. If the target is wearing armor, armor still reduces it. If the type counts as fire and the target has Fire Resistance, it’s nullified. If a creative-mode player is the target, most damage does nothing. So /damage @s 6 minecraft:generic might land for less than 6 once armor and effects have their say, which is exactly the behavior we’re about to exploit with a tag.

Here’s a worked example. It makes a villager named villager_1 deal 1 point of damage to the nearest iron golem:

damage @e[type=iron_golem, sort=nearest, limit=1] 1 generic by @e[type=villager, limit=1, name="villager_1"]

Read it left to right: damage the nearest single iron golem, for 1, with the generic damage type, dealt by the named villager. Because the villager is named the by argument, a death message could even credit it.

Modern Minecraft. Older tutorials had no clean way to “just hurt that entity for N.” People faked it with instant-damage potion effects, or by briefly summoning harmful mobs. The /damage command (and data-driven damage types behind it) is the modern, direct way: one command, an exact source and type, full control.

Damage type tags: grouping kinds of damage

You already know registry tags from Chapter 14: a JSON file under data/<namespace>/tags/<registry>/ whose values array lists members, referenced elsewhere with a #. A damage type tag is simply that idea applied to the damage_type registry. A damage type tag is a group of damage types. It can be used when testing for damage type arguments with #<resource location>, which succeeds if the damage type matches any of the damage types specified in the tag.

So the file lives at:

data/<namespace>/tags/damage_type/<name>.json

…and you reference the whole group with a #, exactly as you referenced block and item tags in Chapter 14. The difference is what the members are: here, each member is a damage type.

Why do these matter? Because the game uses a long list of built-in damage type tags to decide how damage behaves. There are many; here are the ones you’re most likely to care about:

  • bypasses_armor — “Damage from these types ignores armor reduction.” (Our void-touch trick.)
  • bypasses_shield — “Damage from these types does not get blocked by shields.”
  • bypasses_effects — “bypasses any damage reduction (Resistance effect and enchantments).”
  • bypasses_resistance — bypasses the Resistance potion effect specifically.
  • bypasses_enchantments — bypasses enchantment-based damage reduction (like Protection).
  • is_fire — fire-type damage: ignored if fireDamage is off or the target has Fire Resistance, and reduced by Fire Protection.
  • is_explosion — reduced by the Blast Protection enchantment.
  • is_projectile — reduced by Projectile Protection; also used to decide if endermen teleport.
  • is_fall — fall damage: ignored if fallDamage is off or the target has Slow Falling, and reduced by Feather Falling.
  • witch_resistant_to — “reduce the amount of damage dealt to witches by 85%.”
  • wither_immune_to — “Prevents the Wither from taking these damage types.”

You don’t define these tags; the game ships them. What you do is add your own damage types to them. Want your custom damage to skip armor? Add it to bypasses_armor. Want a mob immune to it? That’s what wither_immune_to does for the wither. Tags are the bridge between “I made a new kind of damage” and “the game’s existing rules treat it correctly.”

Closing the loop: this is what Chapters 23 and 24 pointed at

Now we can finally settle the two promises from Part VI.

Back in Chapter 23 you built shield items with the blocks_attacks component, and two of its fields were left waiting on this chapter. The type inside each damage_reductions rule, and the bypassed_by field, were both described there as taking “a damage type tag.” Now you know exactly what that means and what to write: a #-prefixed damage type tag, the kind of file you just learned to reference. Put #minecraft:is_explosion in a damage_reductions rule’s type and that rule blocks explosions; put a tag in bypassed_by and any incoming hit whose type is in that tag ignores the shield entirely. Those fields were always pointing at the damage type tags this chapter defines.

Chapter 24 did the same with the damage_resistant component. Recall the fireproof cake you made there:

/give @s cake[damage_resistant={types:"#minecraft:is_fire"}]

Its types field was described as “a damage type tag prefixed with #,” and now you can see what that value really is: #minecraft:is_fire is the built-in is_fire damage type tag, the group of all fire-related damage types. The cake shrugs off every member of that group at once. That’s the whole point of tags, and the reason both components take a tag rather than a single type: one #name stands in for a whole family of damage types, so a single field can cover “all fire damage” or “all explosions” without listing each one.

So every “damage type tag” the earlier chapters waved at is the same thing: a data/<namespace>/tags/ damage_type/<name>.json file (or one of the built-in ones), referenced with #.

Walkthrough: the “void touch” damage type that bypasses armor

Let’s build it. The goal: a damage type called mypack:void_touch that hurts no matter what armor the target is wearing.

Step 1 — Write the damage type

Create the file. We’ll give it a little exhaustion, no difficulty scaling, and the freezing hurt effect so it feels eerie:

mypack/data/mypack/damage_type/void_touch.json

{
  "message_id": "void_touch",
  "exhaustion": 0.1,
  "scaling": "never",
  "effects": "freezing",
  "death_message_type": "default"
}

Every field here is one we listed earlier: message_id names it (and feeds the death.attack.void_touch translation key), exhaustion of 0.1 matches vanilla arrow damage, scaling: "never" keeps it constant on every difficulty, effects: "freezing" borrows the icy hurt shake, and death_message_type: "default" uses the normal message logic.

Step 2 — Make it bypass armor with a tag

On its own, void_touch is just ordinary damage that armor would soften. To make it ignore armor, we add it to the game’s built-in bypasses_armor damage type tag. Because we’re extending a vanilla tag, the file goes in the minecraft namespace (this is the Chapter 14 trick for adding to a vanilla tag), and we leave replace out (or set it false) so we add to the vanilla tag instead of wiping it:

mypack/data/minecraft/tags/damage_type/bypasses_armor.json

{
  "values": [
    "mypack:void_touch"
  ]
}

This is the same values-array tag format from Chapter 14. The only new thing is the registry folder name, damage_type. Now mypack:void_touch is a member of bypasses_armor, and damage from anything in that tag ignores armor reduction. Full plate or nothing, the void touch lands the same.

Under the Hood (skippable). Why a separate tag file instead of a field on the damage type that says “ignore armor”? Because “ignore armor” isn’t a property of the damage type itself; it’s a rule the game applies to a group. Lots of different damage types might want to bypass armor; rather than repeat a flag on each, the game keeps one bypasses_armor list and checks membership. This is the same reason damage_resistant and blocks_attacks take tags, not single types: one tag, many members, one rule.

Step 3 — A function to test it

Let’s summon a target and zap it. We’ll spawn a zombie (which can wear armor, making the armor-bypass visible) a few blocks in front of us, then hit it. Following our rule from Chapter 9, there’s no leading slash inside a function file:

mypack/data/mypack/function/void_touch.mcfunction

# spawn a test zombie 3 blocks in front of where the function runs
summon zombie ^ ^ ^3 {CustomName:'"Void Test"'}
# hit the nearest zombie for 6 with our custom damage type
damage @e[type=zombie, sort=nearest, limit=1] 6 mypack:void_touch
say Void touch applied!

Save everything, then in-game:

/reload
/function mypack:void_touch

A zombie named “Void Test” appears ahead of you and immediately takes 6 points of void-touch damage with the icy freezing flash. Because void_touch is in the bypasses_armor tag, that 6 lands in full even if the zombie spawns wearing armor. Try it next to a normal hit and you’ll see armored mobs take the void touch just as hard as bare ones.

Modern Minecraft. This whole flow (define a kind of damage in a file, group it with a tag, apply it with one command) didn’t exist in older versions, where damage types were hardcoded. Being able to ship a brand-new damage type in a data pack, and slot it into the game’s existing armor/shield/resistance rules just by adding it to the right tag, is a genuinely modern capability.

Figure (to be captured). a player running /function mypack:void_touch; a zombie named “Void Test” ahead taking the freezing hurt flash, with the “Void touch applied!” chat line

Practice

  1. A gentler touch. Make a second damage type, mypack:soft_touch, that’s identical but does not go in bypasses_armor. Find or spawn an armored mob (zombies sometimes spawn wearing armor; or test on yourself wearing armor in Survival), hit it with both, and watch the armored target take less from soft_touch than from void_touch. This shows the tag (not the damage type file) is what bypasses armor.

  2. Borrow a vanilla feeling. Change your void_touch file’s effects field to burning instead of freezing, /reload, and run the function again. Same damage, different on-screen reaction. Try each of the legal values (hurt, thorns, drowning, burning, poking, freezing) and notice that effects only changes the look and sound, not the numbers.

  3. Scaling test. Make a mypack:hard_hit damage type with "scaling": "always". Apply it with /damage on Easy, then switch your world to Hard and apply it again with the same amount. Because it scales with difficulty, the harder setting should hurt more. (Set difficulty from the game menu or with the difficulty command.)

  4. Make a shield ignore it. Revisit your Chapter 23 shield item. Build a blocks_attacks item whose bypassed_by field is a #-prefixed damage type tag that contains mypack:void_touch (you can make your own tag file, e.g. mypack:voidish, with void_touch in its values). Block with the item while a function hits you with void_touch, and confirm the block does nothing, because the tag is in bypassed_by. You’ve now wired your Chapter 23 component to your Chapter 36 tag with no missing pieces.

What Can Go Wrong

What Went Wrong? My /damage “worked” but the target barely lost health. Remember the caveat: the amount you type is not the final damage; it runs through normal reductions. Armor, Resistance, Protection enchantments, and Fire Resistance can all soften or cancel it. If you want it to ignore one of those, add your damage type to the matching built-in tag (bypasses_armor, bypasses_resistance, bypasses_enchantments, and so on). And note that a creative-mode target is immune to most damage entirely. Test in Survival.

What Went Wrong? The game says my damage type is an unknown damage type. Two usual causes. First, the file path must be exactly data/<namespace>/damage_type/<name>.json (singular damage_type, just like the singular function, recipe, and loot_table folders from earlier chapters). Second, you must reference it by its full id in /damage: mypack:void_touch, not just void_touch. After fixing either, run /reload and try again.

What Went Wrong? I added my type to bypasses_armor but it wiped out all the vanilla armor-bypassing damage. You almost certainly put "replace": true in the tag file, or you put the tag in your own namespace instead of minecraft. To extend the vanilla bypasses_armor tag, the file must be at data/minecraft/tags/damage_type/bypasses_armor.json and must leave replace out (it defaults to false). That merges your type into the existing list, the extend-vs-replace rule you learned in Chapter 14.