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

Damage and death

Verified against Minecraft 26.3 · Part VI · An arrow hits a player in full iron with Protection II: six damage becomes two, and if it kills, a message, a loot drop and a death screen.

The arrow lands, the screen goes red, the hearts drop by one, and somewhere behind you the archer’s client plays its hit ding. Six damage left the bow and about two reached your health, which is the part everybody knows. The part nobody sees is what happens when a second arrow arrives four ticks later, inside the flash. If more than ten ticks of invulnerability remain, that hit is worth only its excess over LivingEntity.lastHurt: a weaker one returns without dealing any damage, and a stronger one takes a partial branch that clears an internal took full damage flag. That one flag gates the damage-event broadcast, the knockback, the hurt sound and the red flash alike. Health goes down and none of those happens — which means neither ClientboundDamageEventPacket nor the red flash is a reliable “was hit” signal. It is all in LivingEntity.hurtServer.

The cast

classwhat it decidesthread
DamageSourcewhat hit and who: a direct entity, a causing entity, and — rarely — a position insteadbuilt wherever the hit starts, on the Server thread
DamageTypethe message id, the difficulty scaling, the food cost, a player’s hurt sound, and the kind of death messagea dynamic registry entry, loaded from a data pack and synced to clients
DamageTypeTagsalmost every behavioural branch on the path belowread on the Server thread
LivingEntitythe whole reduction pipeline, the i-frames, the flash, the two attribution references and deaththe Server thread
CombatRulesthe two pieces of arithmetic — armour and enchantment protectionstateless statics
CombatTrackerwhat killed you, and which entry gets the creditthe Server thread, cleared on its own timer
ServerPlayerPvP, the death screen packet, the death message, the inventory dropthe Server thread
Entitythat Entity.hurtServer is abstract — there is no default behaviour to inherit—

Everything above is server-side, and LivingEntity declares no Entity.hurtClient at all: no damage is ever calculated on a client. A client does set health — from LivingEntity.DATA_HEALTH_ID, from ClientboundSetHealthPacket, and to zero on the death event — but it never decides a number, and the signature says so: Entity.hurtServer takes a ServerLevel, so the side is enforced by the compiler rather than by a convention. Which side is allowed to decide anything is authority.

The number the arrow decides

AbstractArrow.onHitEntity builds the source first, because the number needs it: EnchantmentHelper.modifyDamage takes the DamageSource as an argument. It starts from AbstractArrow.baseDamage, 2.0 for an ordinary arrow, runs EnchantmentHelper.modifyDamage over the bow that fired it so Power raises the base, then multiplies by the arrow’s current speed and rounds up with Mth.ceil — so the damage that leaves the bow is an integer, and a slowing arrow does less. AbstractArrow.isCritArrow adds a random bonus of up to half the damage plus one. Six is a fresh arrow at full draw before that bonus, which a full draw always earns, so six is the one draw in five that adds nothing.

DamageSources.arrow then names two entities: the arrow as the direct entity, the shooter as the causing one — or, when the arrow has no owner left, the arrow itself in both roles. DamageSource.getWeaponItem reaches through the direct entity, which is how Breach on the attacker’s weapon reaches the victim’s armour calculation. A source can carry a position instead, though far less often than you would guess: ordinary explosions do not, and fall back to the direct entity in DamageSource.getSourcePosition. Exactly three sources are genuinely positional, and only they put a position on the wire through DamageSource.sourcePositionRaw — DamageSources.badRespawnPointExplosion, /damage … at (DamageCommand), and ExplodeEffect unattributed.

The DamageType behind the source is five fields — message id, DamageScaling, food exhaustion, DamageEffects (which picks the hurt sound, and only for a Player: Player.getHurtSound is its one caller) and DeathMessageType — and it lives in a data pack. There are 51 keys in DamageTypes and 36 tags in DamageTypeTags, and almost every behavioural branch below is tag-driven rather than type-driven. The exceptions are worth listing because they are the whole list: thorns picks its own secondary sound in LivingEntity.playSecondaryHurtSound, wind charge is excluded from mob-anger attribution against an EntityTypeTags.NO_ANGER_FROM_WIND_CHARGE entity, and a ServerPlayer mid-dimension-change is invulnerable to all but DamageTypes.ENDER_PEARL.

Three gates before anything is computed

LivingEntity.hurtServer opens with three early returns: already invulnerable, already dying, or a DamageTypeTags.IS_FIRE source against MobEffects.FIRE_RESISTANCE — a mob-effect immunity sitting outside the reduction pipeline entirely. Invulnerability itself is Entity.isInvulnerableToBase (removed, the invulnerable flag or its timed twin Entity.invulnerableTime, fire immunity, fall immunity) or an enchantment-granted one through EnchantmentHelper.isImmuneToDamage, which is how Frost Walker makes its wearer immune to magma blocks.

Two subclasses get there first. ServerPlayer.hurtServer adds the PvP and team check — ServerPlayer.canHarmPlayer refuses when the level forbids PvP or the two share a team that disallows friendly fire — testing both a Player causing entity directly and an AbstractArrow causing entity by asking it for its owner. ServerPlayer.isInvulnerableTo adds the two conditions that have no tag: mid-dimension-change, and a client that has not finished loading. Then Player.hurtServer refuses a creative player unless the source is DamageTypeTags.BYPASSES_INVULNERABILITY, and applies difficulty scaling — halved and offset on easy, one and a half times on hard, zero on peaceful — but only when DamageSource.scalesWithDifficulty says so. That reads DamageScaling off the type, and DamageTypes.ARROW is when_caused_by_living_non_player: a skeleton’s arrow scales and a player’s does not. Player.isInvulnerableTo is also where GameRules.DROWNING_DAMAGE, GameRules.FALL_DAMAGE, GameRules.FIRE_DAMAGE and GameRules.FREEZE_DAMAGE live — switching one off makes a player immune rather than making the damage smaller.

One number, eight steps, and no step that knows another

Past the gates the number goes down a chain of eight arithmetic steps — five multiplications and three subtractions — each owned by a different piece of the game, and none of them aware of the others.

flowchart TD
    N0["the bow"]
    N1["blocking"]
    N2["freezing"]
    N3["the helmet"]
    N4["i-frames"]
    N5["armour"]
    N6["Resistance"]
    N7["protection"]
    N8["absorption, then health"]

    N0 -- "6.0" --> N1
    N1 -- "6.0" --> N2
    N2 -- "6.0" --> N3
    N3 -- "6.0" --> N4
    N4 -- "6.0" --> N5
    N5 -- "3.12" --> N6
    N6 -- "3.12" --> N7
    N7 -- "2.12" --> N8

The number rides the arrows: four steps in a row take nothing at all, and the whole reduction on a fully armoured victim happens in two.

Which piece of the game owns each of the eight, and what it does to the number:

#stepowned bywhat it does to the numberleaves
—the arrow leaves the bowAbstractArrowMth.ceil of speed times base damage, Power already folded into the base6.0
1blockingDataComponents.BLOCKS_ATTACKS, through LivingEntity.applyItemBlockingsubtracts what the component resolves for this angle and this damage type6.0
2freezingEntityTypeTags.FREEZE_HURTS_EXTRA_TYPESmultiplies by five6.0
3the helmetDamageTypeTags.DAMAGES_HELMETmultiplies by 0.756.0
4the i-frame windowLivingEntity.lastHurtover ten ticks left, only the excess survives6.0
5armourCombatRules.getDamageAfterAbsorbeffective armour over 25, floored at a fifth and capped at 20 — here 48 per cent off3.12
6ResistanceLivingEntity.getDamageAfterMagicAbsorbfive of twenty-five per level, and nothing at all at amplifier four3.12
7protectionCombatRules.getDamageAfterMagicAbsorbthe summed points over 25, capped at 20 — here 8 points, 32 per cent off2.12
8absorptionLivingEntityspent before health is, then CombatTracker.recordDamage and LivingEntity.setHealth2.12

Before the first row there is one gate the chain never sees: Player.hurtServer scales incoming damage by difficulty, and a player’s own arrow is not scaled.

The first link is blocking, and shields are no longer a mechanism, only a vocabulary. LivingEntity.applyItemBlocking asks the item being used — through LivingEntity.getItemBlockingWith, which enforces BlocksAttacks.blockDelayTicks — for a DataComponents.BLOCKS_ATTACKS component, checks the damage type against BlocksAttacks.bypassedBy, computes the angle between the source position and the victim’s head rotation, and lets BlocksAttacks.resolveBlockedDamage sum the reductions in the component’s own list that apply. BlocksAttacks.hurtBlockingItem then charges durability — for a blocking Player only; a mob’s item never wears — and a non-projectile block sends the blocker reeling through LivingEntity.blockedByItem, which is how a Hoglin throws whoever blocked it and a Ravager, unless it is roaring, either stuns itself while shoving them or shoves them harder on a coin flip. An arrow with any piercing level skips all of it before the angle is computed. ShieldItem still exists, the statistic is still Stats.DAMAGE_BLOCKED_BY_SHIELD, and the axe-disables-shield rule survives as LivingEntity.getSecondsToDisableBlocking — which Warden overrides, and which reads the attacker’s Weapon component only while that weapon is also their active item (the sword swing) — feeding Player.blockUsingItem and BlocksAttacks.disable. None of it is shield-specific code any more.

The two multipliers after it almost never fire and are both pure tag lookups: DamageTypeTags.IS_FREEZING against an entity in EntityTypeTags.FREEZE_HURTS_EXTRA_TYPES multiplies by five, and DamageTypeTags.DAMAGES_HELMET against a helmeted victim calls LivingEntity.hurtHelmet and multiplies by 0.75.

Ten ticks in which nothing shows, and ten that protect nothing

LivingEntity.damageCooldownTime is set to 20 by a hit that lands in full and counted down once a tick — from LivingEntity.baseTick for everything except a ServerPlayer, and from ServerPlayer.tick for players, earlier in the tick. LivingEntity.hurtDuration and LivingEntity.hurtTime are set to 10, so the red flash covers half of it.

It is the flashing half that is invulnerable. The test is LivingEntity.damageCooldownTime still being above ten, which is exactly the ten ticks the flash is showing; once the counter falls to ten the next hit takes the ordinary branch, lands in full, and resets both numbers. The second half of what everyone calls the invulnerability window protects nothing at all.

Inside the first half, then, unless the type carries DamageTypeTags.BYPASSES_COOLDOWN, the incoming damage is compared against LivingEntity.lastHurt — what the last hit was worth — and only the excess is applied. A hit that is not bigger returns before any hurt sound, damage packet, knockback of the victim or combat entry. A hit that is bigger applies the difference and clears a local took full damage flag, and everything downstream sits inside a test of that flag: no ServerLevel.broadcastDamageEvent, no Entity.markHurt, no LivingEntity.dealDefaultKnockback, no hurt sound, no reset of the flash. So the first hit in each window is the one everyone sees; a later, stronger one lands its excess with no flash for anyone watching, though its lower health still goes out, and a victim’s own client, which deduces a hit from the drop, flashes anyway.

LivingEntity.resolveMobResponsibleForDamage and LivingEntity.resolvePlayerResponsibleForDamage sit outside that test and run on the silent stronger partial hit as well as the full one — only the weaker hit, which returns before them, leaves no damage and no attribution. They write LivingEntity.lastHurtByMob and LivingEntity.lastHurtByPlayer (with its hundred-tick LivingEntity.lastHurtByPlayerMemoryTime countdown), crediting a tamed Wolf’s work to its owner. LivingEntity.lastDamageSource and LivingEntity.lastDamageStamp are set beside them, but only when the hit counted for something.

Armour, and why big hits punch through it

LivingEntity.actuallyHurt is where the number meets the victim’s gear. LivingEntity.getDamageAfterArmorAbsorb first calls LivingEntity.hurtArmor — which is empty on LivingEntity and overridden only by Player, Horse and Wolf. A skeleton in full iron never wears its armour out. Where it is implemented it routes to LivingEntity.doHurtEquipment: one durability point per four damage, minimum one, per piece, and each piece must separately be Equippable.damageOnHurt, damageable, and pass ItemStack.canBeHurtBy.

The armour formula, in the order it is applied

Then CombatRules.getDamageAfterAbsorb does the arithmetic. Effective armour is the armour points, reduced by the incoming damage divided by (two plus a quarter of toughness), then clamped between CombatRules.MIN_ARMOR_RATIO (0.2) of the nominal points and CombatRules.MAX_ARMOR (20); the fraction taken off is that effective armour over CombatRules.ARMOR_PROTECTION_DIVIDER, which is 25. Big hits punch through armour by design: the subtraction is what makes a 40-damage hit see less armour than a 6-damage one, and Attributes.ARMOR_TOUGHNESS is exactly the term that slows it down. Full iron is 15 points and no toughness, so a 6-damage hit sees 12 effective armour, 48 per cent off, 3.12 left. Breach moves the resulting fraction through EnchantmentHelper.modifyArmorEffectiveness first.

LivingEntity.getDamageAfterMagicAbsorb then applies Resistance and sums EnchantmentHelper.getDamageProtection across every equipment slot. Protection is not a class: it is EnchantmentEffectComponents.DAMAGE_PROTECTION holding a conditional value effect, confined to armour by its own slots declaration in the JSON rather than by the helper, worth one point per level per piece — so Protection II on four pieces is 8. CombatRules.getDamageAfterMagicAbsorb caps that sum at 20 and takes sum over 25 off. 3.12 becomes about 2.12.

Armour is capped twice and floored once. Effective armour never drops below a fifth of nominal and never counts above 20, and protection points cap at 20 as well — so armour alone tops out at 80 per cent reduction, protection at 80 per cent of what is left, 96 per cent combined — so armour alone can never take a hit to nothing. An effect can: Resistance at amplifier four multiplies by zero. What survives comes off absorption first; then, unless absorption took it all, CombatTracker.recordDamage files a CombatEntry, and health takes the rest. Player.actuallyHurt overrides the whole method to add food exhaustion (DamageType.exhaustion, 0.1 for an arrow) and the damage statistics.

Telling everyone, and what a block replaces

The arithmetic is over and the number has landed. What follows is an order, and two of its steps are the ones players notice going missing:

sequenceDiagram
    participant AA as AbstractArrow
    participant SP as ServerPlayer
    participant CT as CombatTracker
    participant SL as ServerLevel
    participant CPL as ClientPacketListener

    AA->>SP: hurtOrSimulate, the arrow's source and 6.0
    SP->>SP: ServerPlayer.hurtServer, canHarmPlayer and teams
    SP->>SP: Player.hurtServer, the creative gate and difficulty
    SP->>SP: LivingEntity.hurtServer, the gates and the multipliers
    SP->>SP: i-frames, where a partial hit clears the flag
    SP->>SP: actuallyHurt, absorption first
    SP->>CT: recordDamage, one CombatEntry
    SP->>SP: then setHealth
    SP->>SP: resolveMobResponsible<br/>ForDamage
    SP->>SP: resolvePlayer<br/>ResponsibleForDamage
    SP->>SL: broadcastDamageEvent, full hits and nothing blocked
    SL-->>CPL: ClientboundDamageEventPacket, every tracker and the victim
    SP->>SP: markHurt, then dealDefaultKnockback
    SP->>SP: indicateDamage, skipped if anything was blocked
    SP-->>CPL: ClientboundHurtAnimationPacket, that one player
    SP->>SP: dead? then checkTotemDeath<br/>Protection

One lane for the victim, because ServerPlayer and LivingEntity are one object: the three hurtServer messages at the top are one virtual call going down the override chain. ServerLevel.broadcastDamageEvent comes before the knockback, not after — and the last message is where the next section takes over.

ServerLevel.broadcastDamageEvent sends the type, three entity ids — the victim, the causing entity and the direct one — and an optional position to every tracking player and the victim, and it runs before the knockback, not after. A successful block replaces it entirely: if the blocking component absorbed anything, BlocksAttacks.onBlocked plays the block sound and no damage event is broadcast at all, so a blocked hit puts no flash on anyone else’s screen. Only then does Entity.markHurt queue the velocity packet, and LivingEntity.dealDefaultKnockback compute a direction (from the projectile for a projectile, from the source position otherwise) for LivingEntity.knockback, which scales it by one minus Attributes.KNOCKBACK_RESISTANCE. ServerPlayer.indicateDamage ends that call, and is skipped when anything was blocked. The hurt sound comes after all of it — after the death check, not with the flash.

Six packets carry the hit itself, beside the sound packets. ClientboundDamageEventPacket (type holder, causing and direct entity ids, optional position) and ClientboundSetEntityMotionPacket go to every tracker, and to the victim when it is a player, ClientboundHurtAnimationPacket and ClientboundSetHealthPacket only ever to one player about themselves, ClientboundSetEntityDataPacket carries LivingEntity.DATA_HEALTH_ID for a mob, and ClientboundEntityEventPacket carries one byte — 3 for death, 60 for the poof, 35 for a totem. Inbound, only the respawn command.

The damage amount never crosses the wire. The client picks a flash, and for your own player a sound, from the type and infers magnitude from health — and only for your own player, in LocalPlayer.hurtTo, the one place a hit is deduced from a health drop. LivingEntity.handleDamageEvent sets LivingEntity.damageCooldownTime to 20 and the flash to 10 and plays the sound (which only your own player’s hurt is heard from; everyone else’s comes from the server), touching health not at all.

Death, or not

If health has reached zero, LivingEntity.checkTotemDeathProtection looks for DataComponents.DEATH_PROTECTION in either hand — unless the source is DamageTypeTags.BYPASSES_INVULNERABILITY, which is why /kill cannot be totemed — and on a hit consumes one, sets health to one, applies DeathProtection’s effects and broadcasts byte 35.

Otherwise LivingEntity.die runs, and its order matters:

flowchart TD
    Z["health has reached zero"]
    T{"DEATH_PROTECTION in a hand, and the source does not bypass?"}
    TOT["consume one, health to one, byte 35"]
    D["LivingEntity.die: kill credit, then LivingEntity.handleKillingBlow"]
    V{"no causing entity, or Entity.killedEntity agrees?"}
    L["the death game event, LivingEntity.dropAllDeathLoot, the wither rose"]
    B["the entity-event byte, to every watcher"]
    P["Pose.DYING"]
    Z --> T
    T -- "yes" --> TOT
    T -- "no" --> D
    D --> V
    V -- "yes" --> L
    V -- "no" --> B
    L --> B
    B --> P

The diamond in the middle is a veto one other entity holds over this one’s loot. The two boxes below it are what the veto cannot reach: the byte goes to every watcher either way, and the pose is set after the byte, not with it.

Kill credit is read from the attribution references written a few lines earlier, and LivingEntity.handleKillingBlow sets LivingEntity.dead. Then — with a causing entity, only if Entity.killedEntity on it agrees; with none, unconditionally — the death game event fires, LivingEntity.dropAllDeathLoot runs and the wither rose is planted. The entity-event byte sits outside that veto and goes out to every watcher before Pose.DYING is set — the byte first, the pose after.

Loot needs a recent player. LivingEntity.dropAllDeathLoot reads killed by a player off LivingEntity.lastHurtByPlayerMemoryTime still being above zero, so an attribution older than a hundred ticks costs the loot context its LootContextParams.LAST_DAMAGE_PLAYER and its luck, and costs the experience drop entirely unless the mob is an always-dropper. LivingEntity.shouldDropLoot gates loot on GameRules.MOB_DROPS and on not being a baby — but Monster.shouldDropLoot drops the baby condition, which is why baby zombies and piglins do drop. LivingEntity.shouldDropExperience mirrors it. Only LivingEntity.dropEquipment is outside both.

The death screen, and what the client does alone

ServerPlayer.die never calls up. It reimplements the sequence, so LivingEntity.handleKillingBlow never runs and LivingEntity.dead stays false on a dead player for the whole death screen. It reads the message from the combat log at the top and calls CombatTracker.recheckStatus at the bottom, so the log survives long enough to be read. GameRules.SHOW_DEATH_MESSAGES gates more than the chat line: with the rule off the message is never assembled (text components owns the assembling) and ClientboundPlayerCombatKillPacket carries an empty component, so the death screen still opens and says nothing. ServerPlayer.die also forgives neutral mobs under GameRules.FORGIVE_DEAD_PLAYERS, drops the inventory through Player.dropEquipment unless GameRules.KEEP_INVENTORY, and calls ServerGamePacketListenerImpl.markClientUnloadedAfterDeath. DeathScreen opens unless GameRules.IMMEDIATE_RESPAWN, and the object that comes back — a new ServerPlayer wearing the old one’s id and connection — belongs to players and sessions.

Two things then happen without a packet of their own. The client kills mobs on its own, from one byte: LivingEntity.handleEntityEvent for byte 3 sets a non-player entity’s health to zero and runs LivingEntity.die locally, so the twenty tick animation in LivingEntity.tickDeath is client-driven, not a consequence of a health update. And CombatTracker clears itself — after CombatTracker.RESET_DAMAGE_STATUS_TIME out of combat or CombatTracker.RESET_COMBAT_STATUS_TIME in it. Four places call the check: a twenty-tick timer in LivingEntity.tick, the top of CombatTracker.recordDamage, LivingEntity.remove, and ServerPlayer.die — so a hit after a long lull discards the old log before filing its entry.

Who gets the credit for a fall

The log is not read in order, and the rule that reads it is the reason for was doomed to fall by. CombatTracker.getMostSignificantFall walks the entries looking for the biggest fall, and when it finds one it credits the entry before it — the hit logged just before the fall’s own entry — taking the fall entry itself only when the fall is the first thing in the log. It keeps a second candidate beside that, the biggest-damage entry that carries a FallLocation. Then the threshold: the fall counts only if it was more than five blocks, and the alternative only if its damage was more than five; below both, the tracker returns nothing and the death message falls back to the ordinary one for the killing blow. So a skeleton’s arrow and then a four-block fall that kills you is hit the ground too hard while trying to escape the skeleton (the source’s own message, crediting the last mob that hurt you), and the same arrow before a ten-block fall is doomed to fall by the skeleton — same two entries, one number apart. The FallLocation is what turns the credited entry into the wording (fell off some vines, fell off a ladder, fell while climbing), and building the sentence out of it belongs to text components.

Everything that calls it

Entity.hurt and Entity.hurtOrSimulate are deprecated final wrappers over the abstract method: the second picks a side and returns Entity.hurtClient off a client level, the first does nothing there. Entity.hurtClient has ten declarations counting the base, and every one only ever answers did this connect — including RemotePlayer, which returns true unconditionally so a client-side arrow can play its own effects without knowing any numbers.

Environmental damage is ticked from more places than base tick suggests. Fire (once per twenty fire ticks) is in Entity.baseTick, lava (four at a time) in Entity.lavaHurt, and suffocation, world-border and drowning damage in LivingEntity.baseTick. Freezing and cramming are not: freezing is in LivingEntity.aiStep every forty ticks, and cramming inside LivingEntity.pushEntities, called near the end of the same method under GameRules.MAX_ENTITY_CRAMMING, just before the drowning a water-sensitive mob takes in water or rain.

Twenty-two classes with no pipeline at all

Everything above this line is LivingEntity’s, and none of it is inherited. Entity.hurtServer is abstract: there is no default behaviour anywhere in the tree, so every branch answers for itself. Fifty-five files override it — a fifty-sixth, Entity itself, only declares it — thirty-three of them LivingEntity and its descendants, ArmorStand among them, a LivingEntity despite having no AI (entity anatomy). The other twenty-two are not living entities at all, and none of them runs armour, i-frames, absorption, the combat tracker or the death sequence itself.

Six patterns cover all twenty-two, and the sharpest are the ones that read the damage number — only four classes do. ItemEntity and ExperienceOrb keep a plain integer of health and subtract from it. VehicleEntity adds damage × 10 to an accumulator that loses one a tick and breaks past 40, which is why any hit over four damage breaks a minecart at once and smaller ones must land close together, and MinecartTNT inherits that accumulator after its own override has looked at the arrow rather than the number. For the other eighteen the answer is a yes or a no: ten do nothing whatever, two resend their motion and nothing else, five are destroyed by one hit of any size (an unfixed frame holding something loses only its item to the first) — an EndCrystal among them, and it is immune to the EnderDragon that eats it — and EnderDragonPart reads nothing, forwarding the whole call, number included, to its parent. Which class does which is the non-living damage table.

For a 1.21-era reader. The hurt cooldown is LivingEntity.damageCooldownTime. invulnerableTime on Entity is now a separate timer of full invulnerability that no hit sets: a skeleton trap gives the horses it adds and every skeleton rider sixty ticks of it, a cushion broken by lightning gives the item it drops twenty, a conversion carries it over, and Entity.isInvulnerable is true while it runs.

Where to look

LivingEntity.hurtServer is the whole first half of this page in one method, and the two overrides that reach it first — ServerPlayer.hurtServer and Player.hurtServer, over the abstract Entity.hurtServer — are the gates. Then LivingEntity.actuallyHurt and the two CombatRules statics it reaches, which are short and settle every argument about armour. DamageSource and DamageType are the what hit you, and DamageTypeTags is where nearly every branch on the page lives, so read the tag file before the type file. For death: LivingEntity.die and then ServerPlayer.die, which does not call it, with CombatTracker and FallLocation for the message. Two doors: BlocksAttacks, the component that replaced shield code, and VehicleEntity, the clearest of the twenty-two branches that inherit none of this. For the numbers as attributes — armour, toughness, knockback resistance — see attributes.


Rules: names, never code · how the system works, not how the code reads · newest version only · every backticked name passes tools/verify_names.py.