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 attacking. 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 will cover what happens when you, the player, are the aggressor.
A few pieces of data are needed to know exactly how much damage is taken or what happens to an enemy when it is hit with something. Our 4 components are: the object of infliction, the sprite's ID, the damage class, and the damage subclass. The damage class is derived from whatever is dealing the hit. It is then used with the sprite's ID to index a table for the damage subclass. 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 opposed to learning. The rest of you should continue reading, as we will dive 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:
Sprite_CheckDamageFromPlayerroutine. Any sprite that doesn't call this routine is more than immune to direct attacks; it completely ignores them.
Ancilla_CheckSpriteDamageroutine. Just like sprites and direct damage, any ancilla that doesn't call this routine will just not do damage, no matter how hard you try.
ThrownSprite_CheckDamageToPeersroutine. A couple sprites call this routine as part of their normal behavior, but every sprite has access to it when frozen, via the frozen state handler.
There are other, minor ways to deal damage to sprites, but we'll cover those after the main trilogy.
|Damage class||Subclass damage|
Before looking at how damage is applied, we need to understand how it is calculated.
There are 16 damage classes, each having 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, in lieu of damage being applied. All other values are subtracted directly from a sprite's health.
|$F9||Target becomes a faerie|
|$FA||Target becomes a blob|
|$FB||Target stunned for 32 frames|
|$FC||Target stunned for 128 frames|
|$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.
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
Sprite_CheckDamageFromPlayer as part of its daily regimen. The routine begins by performing various checks to see if damage should even be considered, including:
Along with these checks is a nested series of checks to handle the ice smash prize pack:
This is where Ganon's immunity to the hammer is coded. If the rest of the checks pass, the sprite is put into a poofing state and its prize pack is set to the magic pack. After this, the routine is satisfied.
When any of these other checks fail, the routine continues just checkin' more things:
And the final check:
It might be time to recoil.
If the sprite made it that far and is not immune, then it will continue to a subroutine that checks for the sprite's ability to discharge electrons through Link's nerves. If the sprite is properly grounded and Link is unable to steal the negative charge, then we'll finally reach the routine that handles sword damage classes. Otherwise, Link will end up like Mehdi Sadaghdar.
We're finally here. Sword damage classes. 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:
$0E60,Xis a bitfield sprite property. Bit 6 of this property is a flag for total immunity to damage.
$0372is one of a couple addresses used to indicate that Link is dashing. This address is checked to determine if Link is performing a dashpoke.
$3Cis a timer/bitfield used for handling spin attacks. Bit 7 indicates a spin attack is in progress, and the timer segment is used to determine if we're doing a normal poke.
$0301is a bitfield for items. Bit 1 indicates the hammer is being used.
$9Dis used as a parameter to the damage routine for the sprite's iframe timer.
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.
|$02||Fire rod shot||$B||$24||Overworld gravestone||$1|
|$04||Beam wall hit||$0||$26||Sword swing sparkle||$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|
|$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|
|$1D||Bonk screen shake||$0||$3F||Bush powder poof||$DA|
|$1E||Dash dust||$0||$40||Smithy poof||$AA|
|$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.
$6(arrows), check for silver arrows. If we do have silver arrows, change the damage class to
$9. This is also where an explicit check for silver-vulnerable Ganon is performed; if it's him, his damage timer is set to 32 frames.
$F(quake), check the target and make sure it isn't airborne. If it is, the routine exits and no damage is done.
$7(hookshot), then a repulse spark ancilla is added after the damage routine.
53will be used as the iframe timer parameter for the damage routine. Otherwise,
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 then killed them.
|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 1 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.
By the way, I set up some nifty CSS for tables for this article.
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.
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
Now that we've covered damage class calculation, we can finally examine the
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.
The reason this seems pretty useless is that 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.
After this is 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 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.