Damage to Enemies

Dam age is exactly what it sounds like: it's how old the building in South Hyrule is.

It is not to be confused with damage, which is how much health is taken away from Link or an enemy when sustaining a hit. There are a handful of routines that do some sort of damage handling. Who's hitting whom determines which calculation will be used. This explication covers what happens when you, the player, are the aggressor.

The Basics

A few pieces of information are needed to know exactly how much damage is taken or what happens to an enemy when it is hit with something.

Damage class is derived from whatever is dealing the hit. It is used with the sprite's ID to check a lookup table for that sprite's damage subclass within the damage class. Finally, the damage class and subclass together are fed to another table for the final value.

That's all there is to understand the basics, but if the basics are all you're after, then you are clearly out of sync with my style of deep-dive learning. The rest of you should strap on your brain snorkels and continue reading, as we will submerge into the inner workings of each of these calculations to understand how they differ and how they all converge into a single routine.

Sprites don't just take damage out of nowhere. Specific routines need to be called. There are 3 main ways that sprites can take damage:

There are other, minor ways to deal damage to sprites, but we'll cover those after the main trilogy.

Damage classes

Subclass damages
Damage class Subclass damage
0 1 2 3 4 5 6 7
$0 Boomerang $00 $01 $20 $FF $FC $FB $00 $00
$1 Sword 1 $00 $02 $40 $04 $00 $00 $00 $00
$2 Sword 2 $00 $04 $40 $02 $03 $00 $00 $00
$3 Sword 3 $00 $08 $40 $04 $00 $00 $00 $00
$4 Sword 4 $00 $10 $40 $08 $00 $00 $00 $00
$5 Sword 5 $00 $10 $40 $08 $00 $00 $00 $00
$6 Arrow $00 $04 $40 $10 $00 $00 $00 $00
$7 Hookshot $00 $FF $40 $FF $FC $FB $00 $00
$8 Bomb $00 $04 $40 $FF $FC $FB $20 $00
$9 Silver arrow $00 $64 $18 $64 $00 $00 $00 $00
$A Powder $00 $F9 $FA $FF $64 $00 $00 $00
$B Fire rod $00 $08 $40 $FD $04 $10 $00 $00
$C Ice rod $00 $08 $40 $FE $04 $00 $00 $00
$D Bombos $00 $10 $40 $FD $00 $00 $00 $00
$E Ether $00 $FE $40 $10 $00 $00 $00 $00
$F Quake $00 $20 $40 $FF $00 $00 $00 $FA

There are 16 damage classes, each with 8 subclasses. These subclasses determine the damage or effect inflicted on a sprite. Subclass data is stored as a table in ROM at $0D:B8F1. A value of $00 not only results in 0 damage, but will also prevent hits from even registering for the sword by default (though other properties of a sprite may let it clink anyways). Values of $F9 or higher result in specific code being run rather than damage being applied. All other values are subtracted directly from a sprite's health.

Special damage values
ID Function
$F9 Target becomes a faerie
$FA Target becomes a blob
$FB Target stunned for 32 frames
$FC Target stunned for 128 frames
$FD Target incinerated
$FE Target becomes frozen
$FF Target stunned for 255 frames

The subclass that each sprite should look for in each damage class is in a table in WRAM at $7F:6000. The data in ROM is actually compressed two-fold. When the game first boots up, it runs the normal decompression routine to obtain 8 bytes for each sprite. Why 8 when there are 16 damage classes? Well, each damage class only has 8 subclasses, which only needs 3 bits to reference. Each byte holds the subclass for 2 damage classes. Bits 3 and 7 hold no data. The high and low nibble of each byte are split apart and then written to WRAM separately, where the entire table can be read uncompressed.

The full table for each sprite, including how much damage that subclass results in, is documented here.

Honestly, I'm not sure why damage subclass is even a gameplay routine. All this data is static, so they could have easily precalculated every damage value as they were decompressing the subclass data and stuck that into WRAM.

Contrary to some's belief, there is no "insta-kill" damage. Anything that dies to a single hit just doesn't have enough health to survive the damage inflicted. Technically, the inceration damage just sets sprites to AI mode $7, which burns them to death. Slowly. The damage routine itself never interacts with the sprite's health when doing this; it's the conflagratory AI module that deals the killing blow.

I don't know about you, but when I hear "instant", I expect things to be done without delay. I don't see how sadistically roasting your victims alive until their 3rd degree burns kill them is "instant" by any stretch of the word.

You sicken me.

Sword and hammer

Sword damage classes
Attack
type
Sword level
1 2 3 4
Slash/Dash 1 2 3 4
Spin attack 2 3 4 5
Poke attack 1 1 2 3

Most people understand the sword's damage mechanics, but it's still worth looking at parts of this routine more indepth, just to understand how exactly the game knows what damage to do.

Sword and hammer damage require that the sprite in question calls CheckDamageFromLink as part of its daily regimen. The routine begins by performing various checks to see if damage should even be considered, including how to handle recoil and electrocution. This is also where Ganon's special hammer immunity and the frozen sprite smash prize pack are handled.

There are several types of sword damages that can be dealt, and most people are familiar with them, but here's exactly how the game sees it:

The damage application routine actually begins right after this. It's literally the next instruction. But before we look at it, we have other things to cover.

Ancillæ

Ancilla damage classes
Ancilla Class Ancilla Class
$00 Nothing $6 $22 Item get $1
$01 Somaria missile $1 $23 Poof $1
$02 Fire rod shot $B $24 Overworld gravestone $1
$03 Unused $0 $25 Unused $1
$04 Beam wall hit $0 $26 Sword swing sparkle $1
$05 Boomerang $0 $27 Duck $1
$06 Wall hit $0 $28 Fairy pond toss item $1
$07 Bomb $8 $29 Pendant/crystal/medallion item get $1
$08 Door debris $0 $2A Spin attack sparkle $1
$09 Arrow $6 $2B Spin attack sparkle $1
$0A Wall arrow $0 $2C Somaria block $1
$0B Ice shot $C $2D Somaria block dying $1
$0C Sword beam $1 $2E Somaria block exploding $1
$0D Max sword charge sparkle $0 $2F Lamp flame $B
$0E Unused $0 $30 Byrna charge up spark $0
$0F Unused $0 $31 Byrna spark $1
$10 Unused $0 $32 Blast wall explosion $1
$11 Ice rod shot wall hit $1 $33 Blast wall explosion $1
$12 Unused $0 $34 Skull Woods fire $1
$13 Ice shot sparkle $0 $35 Master Sword cutscene $1
$14 Unused $0 $36 Flute from ground $1
$15 Splash $0 $37 Flute spot debris $1
$16 Stars $0 $38 Flute cutscene bird $1
$17 Dirt $0 $39 Somaria platform poof $BC
$18 Ether $E $3A Super Bomb explosion $F0
$19 Bombos $D $3B Sword up sparkle $0E
$1A Powder $0 $3C Sword charge sparkle $10
$1B Wall poke spark $0 $3D Item splash $01
$1C Quake $F $3E Crystal rising $6B
$1D Bonk screen shake $0 $3F Bush powder poof $DA
$1E Dash dust $0 $40 Smithy poof $AA
$1F Hookshot $7 $41 Waterfall splash $BF
$20 Link's blanky $1 $42 Upgrade rupees $84
$21 Link snoring $1 $43 Ganon's Tower cutscene $EC

Ancillae begin by using their ID to index a table of damage classes. This table has a couple of oddities. First off, there are a total of 68 ancillae, but the table only has 57 entries; the last 11 bleed into code. While it might make sense to only include data up to the highest ID that can do damage, that's not what this table does. The last ID that needs a damage class is entry 49, the cane of byrna spark.

Half of the default values here are $0 and the other half are $1. That I can't really come up with a reason for. Personally, I would have made every unneeded value $FF, to distinguish empty values from the boomerang's damage class. It's also interesting that the null ID of $00 has the arrow damage class ($6). That's not reading code as data. That's actually the value they gave it.

Lamp flames don't contain any routine for checking damage, but if they did, they would do the same damage as fire rod.

But I digress…

Once the damage class is determined, it is used to perform a couple more checks that determine the flow of the routine.

Thrown sprites

You'd think these would be simple, right? In a way, they are, but there's some curious logic I'd like to point out. As mentioned, a small handful of sprites explicitly call the routine ThrownSprite_CheckDamageToSinglePeer, but every sprite has access to it when in the frozen state, via the frozen sprite AI handler.

Every sprite thrown, regardless of what it is, is given an 8x8 hitbox, and that is matched against the hitbox of the target.

Then, for some reason, there's an explicit check for the target's sprite ID to see if it's $3F, a tutorial guard from the beginning of the game. If it is one of those guards, then the damage application is completely skipped. These guards do have non-zero damage class data, but that could have just been removed, since they are immune through sprite properties already. Anyways, I fixed those sprite properties by editing them in RAM and then killed the guards.

Throw subtypes ($0DB0,X)
Subtype Thrown object
Outdoors Indoors
0 Sign Glitchy placeholder
1 Light rock Skull pot
2 Light bush World-based pot
3 Link's head Link's head
4 Dark bush Miscolored ceramic pot
5 Dark rock Miscolored skull pot
6 Big light rock Big light block
7 Dark light rock Big miscolored block
8+ Invalid (glitchy) Invalid (glitchy)

The thrown sprite's ID is checked, specifically for $EC, to see if it's a bush or pot. And if it is, it checks the subtype. Subtype 2 indicates a normal bush. And then, only if we're outside, the damage class used will be 1. In all other cases, non-bushes, or bushes indoors, damage class 3 is used.

And what's with the subtypes for throwable objects? Isn't it kind of odd that everything is class 3 damage, except for one type of bush, and only when outdoors? And when I say that subtype 3 is Link's head, I mean it. With an unused ID, it would make a bit more sense to use that for bushes, so that indoors/outdoors isn't relevant. And why do dark bushes deal more damage? I have a feeling that these were written sometime after the thrown sprite damage routine, resulting in someone not fixing their damage class either. I think this thrown-object-specific damage class stuff would have worked better as a table.

BALLZ

The first minor class of damage to discuss is reflected energy balls. Unlike thrown sprites, these are super simple. They just load a value of 16 for the damage and 0 into the sprite index before entering at a point in the middle of the damage routine. Though, this entry point is sort of wasteful, and it could have been calling a point farther along in the checks. Perhaps they wanted to generalize the entry point for any hardcoded damage? In the end, only Agahnim-ballz interactions use it.

Ballz also makes the hefty assumption that Agahnim is always in slot 0. That's true, but it has the potential for wacky consequences. Imagine spawning Agahnim into slot 2 then hitting him with a ballz. Whatever is in slot 0 will take the hit. Unfortunately, I couldn't find a way to make this damage a beamos.

Spikes

Spike damage requires a specific routine to be called as well, but, in this case, it's Sprite_CheckTileCollision. As part of this routine, if a sprite interacts with a spike block and is currently recoiling, then the ancilla damage routine is hijacked with a damage class of 4.

The Main Event

Now that we've covered damage class calculation, we can finally examine the ApplyDamage routine.

This routine has a single parameter, which is a value used later for an iframe timer. Everything else in memory should already be set up for damage to occur.

First, 2 checks are performed: bit 6 of $0E60,X is checked for total immunity. Then the sprite ID is checked. IDs of $D8 and higher belong to items and a couple other undamageable sprites. So if the ID is in this range, the routine just exits immediately.

Next, the sprite's ID and the calculated damage class are used to index the WRAM sprite damage table for the subclass. The subclass is combined with the damage class to index the table in ROM at $0D:B8F1 to get the actual damage inflicted.

Now we begin individual checks to see if we're doing using a special damage class or direct damage. This is the point in the routine that Agahnim's energy shots enter from. The fairy ($F9) and blob ($FA) classes are checked for first. These simply transmute the target sprite into the new sprite; a new sprite is not actually spawned, per se.

Following that is a check to make sure that better damage is not being overwritten. If the damage we entered with is less than the damage already being dealt, then the sprite's current "damage taken" property is left alone. But that's all that's skipped. The rest of the routine still continues as normal. This allows for damage application to be extended, such as with doubling fire rod shots on Kholdstare's shell.

If the inflicted damage is 0, it continues, but otherwise, there's this weird segment of code. As far as I can tell, it's essentially useless, but this code appears to "shield" non-boss sprites from damage, and even revive them. If the damage class was powder and powder doesn't damage the sprite normally, then its iframe timer and damage taken property will be zeroed.

For some reason, powder is a bit special when it deals damage to sprites. It does use the same routine, but only part of it. When the routine is entered normally, the sprite's death is checked, and bit 7 is a flag that tells the ancilla to stop. This check is not performed at the point where magic powder enters the routine. This seems pretty useless; you don't really have time to use powder in most cases. The best place to see this is to get an enemy that takes damage from the boomerang (debirandos are good because they don't move) and throw behind it. Then right before the boomerang hits it on its return, sprinkle the target.

But let's continue with the routine when it's nonzero damage. More special damage classes are checked. First, freeze and long stun are checked together. Then there's the code that handles water bubble respawning. I'm not sure why it's here, but it is. Basically, it just sets the bubble back to alive and resets its AI. Enemy arrows also have their deflection code here.

The value we initially entered with is used for the iframe timer, the same one that controls how long damage flashing occurs for. Helmasaur King has a special check here to determine which sound to play, based on what phase he's on. Otherwise, the boss flag sprite property is checked to determine which OOF is heard.

And finally, the damage class is checked again to apply recoil. Forcing 0 recoil against the sprite if it's one of the medallion classes. This is how recoilless Agahnim hits happen. The damage class address is a single byte, and it's only updated when it needs to be. So if the last damage class used was a medallion, it will be checked when energy balls deal damage, because they jump in halfway through the routine, and don't need to use the address; it gets left alone, but checked later for recoil.

For non-medallion damage classes, 3 sprites have a special recoil timer:

For everything else, the recoil timer is set to 15 frames.