Blast damage
From DoomWiki.org

Blast damage attacks, also referred to as splash damage, inflict radius damage in the area surrounding their point of explosion, in addition to any damage inflicted by the direct hit from the source actor (such as a rocket). The original Doom engine uses the same code for all blasts, and even the same sound effect. In Doom II, the blast radius is either 70 or 128 map units, and any damageable object that is in that radius and exposed to the explosion will lose hit points proportional to the object's distance to the explosion. As special cases, the cyberdemon and spiderdemon do not take blast damage. Other games based on the Doom engine introduce more variability and additional mechanics to explosions.
The distance calculation is not euclidean; the calculation considers the greater of the x and y distances from between the center points of the explosion and the object, less the object's radius (such that the distance is to the edge of the object rather than the centre). As a result of this, both an object standing e.g. some distance to the north of an explosion, and another object standing with the same distance north and to the east, unintuitively take the exact same amount of damage, even though the object with additional distance to the east is further away considering euclidean distance.
To be considered exposed to the explosion, an object's centre must have a line of sight to the centre of the explosion; if the view of the object from the explosion is obstructed by a wall, no damage is inflicted. Otherwise, the damage inflicted in hit points is equal to the value of the blast radius minus the distance. If an explosion's centre is within the bounding box of the object and there is no obstruction, the object takes the maximum amount of blast damage, which is equal to the value of the blast radius.
Rockets (such as those launched from a rocket launcher or a cyberdemon) move, collide, and explode in the following order: In a tic, the rocket attempts to move from position A to position B, and if position B is obstructed, the obstruction in B is considered directly hit while the rocket explodes at position A. As a result, a direct hit from a rocket typically deals less than full blast damage due to the distance between position A and the obstruction at position B. To deal full blast damage, a rocket can be detonated on the ceiling above the object, as no vertical distance is considered.
Contents
Properties of radius attacks in Doom[edit]
Counting both the hit and the blast, a successful rocket launcher attack averages about the same damage as the super shotgun at close range (about three times the regular shotgun), but is equally effective at long range.
For arch-vile attacks, the radius is 70 units; for barrels and player or cyberdemon rockets, it is 128 units.
Cyberdemons and spiderdemons are immune to all blast damage (which does not include the tracer damage from BFG blasts). Therefore, a greater number of rockets is required to kill these bosses than might be expected, and they cannot be damaged at all by barrels. They receive only the direct damage from arch-vile attacks. The arch-vile itself is not immune to blast radius damage, and may take splash-back from its own attacks if it is too close when the detonation of its flame cloud is initiated. It is possible for arch-viles to eventually kill themselves when engaging a target with sufficient health due to this effect. Note that this does not cause arch-viles to infight with themselves, or with other arch-viles, due to the fact that arch-viles are specially excluded from being the target of monster infighting.
Extra effects in other games[edit]
Blast radii induce certain other effects in other games based on the Doom engine. In Heretic and Hexen, blast radius attacks will trigger terrain hit effects if they occur at a distance from the floor that is equal to or less than the explosion's radius. In Strife, blast radius attacks fire four invisible non-damaging tracers in the cardinal directions. These tracers enable nearby blasts to break glass screens and windows. Most code that creates blast radius attacks in Strife will additionally make a call to the function P_NoiseAlert to awaken monsters and set off the alarm, though this is done separately outside the actual blast damage code itself.
Extensions were also made in Hexen that allow explosions to be clipped to a certain z height around the projectile, have differing damage and radius measurements, and to optionally not inflict damage to the actor which originated the explosion (such as is used for Baratus's Hammer of Retribution).
In Heretic, all monsters flagged as being bosses are immune to splash damage attacks in the same manner as Doom's boss creatures. This includes the maulotaur and both forms of D'Sparil, but does not include iron liches.
In Strife, only the inquisitor is specifically immune to blast damage, though the spectres and the Entity are also immune to most blasts due to the fact that they are only allowed to take damage from the Sigil.
Bugs[edit]
The blast damage of a single explosion can be applied onto a given thing any number of times, by means of damaging idle monsters nearby to wake them up and reorder memory—this can be observed in casual play, e.g. by killing a previously unharmed revenant with a single rocket.
The location of an explosion will shift mid-blast if it causes another explosion on the same tic. The start of blast damage code sets up global state that includes the point of the explosion and its damage, and then it iterates things. If the game has been modified with DeHackEd to make an object explode on the same tic it was damaged by another explosion, it is possible on a given thing iteration for the blast damage code to be called recursively, overwriting this global state with that of a different explosion. A result is that an initial explosion deals damage in location A and destroys something that explodes in location B, which causes the initial explosion to resume trying to deal damage in location B instead, sparing remaining things at location A.
Technical[edit]
Implementation[edit]
Explosions are triggered via the function P_RadiusAttack. It finds things in the vicinity by checking blockmap tiles that the explosion overlaps, and it runs the explosion logic on those things.
P_RadiusAttack, p_map.c:
... bombspot = spot; bombsource = source; bombdamage = damage; for (y = yl; y <= yh; y++) { for (x = xl; x <= xh; x++) { P_BlockThingsIterator(x, y, PIT_RadiusAttack); } }
The explosion logic inside PIT_RadiusAttack contains the distance calculation and other mechanics related to how an explosion interacts with things.
PIT_RadiusAttack (slightly edited for clarity), p_map.c:
... // Boss spider and cyborg take no damage from concussion. if (thing->type == MT_CYBORG || thing->type == MT_SPIDER) return true; dx = abs(thing->x - bombspot->x); dy = abs(thing->y - bombspot->y); farthest = dx > dy ? dx : dy; // The farther of the two distances. dist = (farthest - thing->radius) >> FRACBITS; if (dist < 0) dist = 0; if (dist >= bombdamage) return true; // out of range if (P_CheckSight(thing, bombspot)) { // must be in direct path P_DamageMobj(thing, bombspot, bombsource, bombdamage - dist); } return true;
This code is the site of several oddities:
- The blast damage immunity for cyberdemons and spiderdemons is hardcoded directly into the logic. For this reason, the blast damage component of rockets is ineffective so it requires more rockets than would be expected to kill them, and they are fully immune to exploding barrels.
- The distance calculation not euclidean, and instead uses the biggest of the differences in the x and y coordinates, adjusted for the thing's radius. These differences have a fractional component, and downshifting using FRACBITS removes that fractional component, essentially rounding the value to a whole number. This is used as the distance that subtracts the damage, so damage inflicted does not follow the real world intuition that euclidean distance would provide.
- To determine whether or not there is an obstruction between the explosion and the object, a line of sight check is performed (using P_CheckSight), which carries the same oddities that monsters' line of sight does; this check draws a line directly between two points to determine if there is an obstruction between them, not considering that a thing is wider than a single point. As a result, a 1 map unit thick column standing between an explosion and an object intuitively provides inadequate cover because parts of the object are exposed to the explosion, but the explosion may miss the object entirely because there was no direct line of sight to the object's center.
One thing hit multiple times from the same blast[edit]
When iterating blockmap tiles as seen in the implementation section, the linked list of things in a tile is iterated by P_BlockThingsIterator. A problem arises from the fact that the list may be reordered during iteration, placing already iterated (and hit) things in the path of iteration again.
P_BlockThingsIterator, p_maputl.c:
... for (mobj = blocklinks[y*bmapwidth+x]; mobj; mobj = mobj->bnext) { if (!func(mobj)) return false; } return true;
The first thing in the linked list is fetched from the given blockmap tile, and the list is traversed until there are no more things (which is marked by a null pointer). The code pointer (PIT_RadiusAttack in this case) always returns true, so the linked list is iterated to completion.
Inside PIT_RadiusAttack, as also shown in the implementation section, there is a call to P_DamageMobj which will deal blast damage to things caught in the explosion radius. When a monster is damaged, it may wake up in order to chase its attacker, which happens by setting its state from its idle state to its chasing state, which also instantly runs the associated chasing code in A_Chase.
When A_Chase is executed, the monster may walk in some direction. When this happens, the engine updates its position in the blockmap by removing it from its linked list and placing it at the start of the linked list in whichever blockmap tile it stepped on—even if it stepped on the same tile as it was on before. In short, as a worst case scenario, hitting a sleeping monster places it instantly at the start of the list.
As a result of this, suppose there are two sleeping monsters standing close together on the same blockmap tile. If a rocket is fired on the monster ordered first in the list, it takes blast damage. Then the blast iterates for the next monster, which then walks and is put at the start of the list—now, the next thing in the list is the monster that already took a beating. That monster is iterated for again, and it takes another round of blast damage.
Global state bug[edit]
As shown in the code for the implementation section, the global variables bombspot, bombsource, bombdamage are set before entering the loop that walks through things in the blast zone. If a recursive call into this code happens and sets these global variables to a different explosion, this eventually resumes back into the original loop, which continues with the overwritten global variables belonging to the other explosion.
This is a case of Doom's source code functions often using global variables and thereby being non-reentrant, meaning that the functions are vulnerable to being called again during execution such that when the original call is resumed it has an unintended state.
There is nothing in vanilla Doom or Doom II which leads to this particular bug happening, but applying the following DeHackEd patch will change barrels to explode instantly when destroyed, which makes it possible:
BARREL.DEH
Patch File for DeHackEd v2.3 Doom version = 19 Patch format = 5 Thing 31 (Barrel) Death frame = 811
Thus, when a barrel takes enough damage to be destroyed, its death frame is applied and the associated code is immediately executed, which is the explosion code, leading to the recursive call.
As an example of effects this can have, suppose there is a wall, and in front of which is an explosive barrel to the left and another to the right. They are far enough apart to not damage each other, though a rocket shot between them catches both in the blast radius, dealing enough damage to destroy both. With the patch however, shooting the same spot between them leads to only one barrel taking damage, leaving the other intact; one barrel is iterated first by the rocket's explosion, causing another explosion and overwriting the global variables to effectively moves the rocket's explosion onto the destroyed barrel, which is too far away to do harm to the other barrel.
See also[edit]
- Arch-vile
- Barrel
- Barrel suicide
- BFG9000
- Cyberdemon
- Fléchette
- Incinerator
- Phoenix rod
- Rocket launcher
- Time Bomb of the Ancients
External links[edit]
- More Fun With Buggy Explosions YouTube video by decino that touches on the blast hitting something multiple times bug.