Can anyone explain what happened at 0:04?
If I am not mistaken, the maximum damage a rocket can do is 288!
Plays back with Perdition's Gate.
I just saw this and had to look into it.
Interesting demo, your first rocket did 603 damage (total).
380 (160 impact + 110 radius + 110 radius) to one revenant, 200 (100 radius + 100 radius) to the other and 23 (radius) to the imp.
The explosion damage from the rocket was applied a second time to both revenants. It's basically a quirk with the blockmap links that you have successfully exploited in your demo.
There are two revenants, an imp, and two boxes of shells in the same blockmap square. The player opens the door, the two revenants wake up, but importantly, the imp does not.
The player fires the rocket. When it explodes P_BlockThingsIterator runs PIT_RadiusAttack over these things in order, first damaging the two revenants, then the imp.
In P_DamageMobj, because the imp is still asleep (in spawnstate), it wakes it up.¹ This calls A_Chase and a long chain of functions that ends up finally in P_TryMove. P_TryMove moves the imp.
When a thing moves, it is unlinked from its old position in the blockmap and linked into the new one. In this case, it remains within the same block, so it is re-inserted into the same thing list. However it is not inserted back into the same position in the list, it is re-inserted back at the start of the list. Therefore P_BlockThingsIterator continues to loop, processing the revenants a second time and causing them to receive two doses of splash damage.²
¹ from P_DamageMobj:
It's worth noting that a sleeping monster is not always in state spawnstate; most sleeping monsters cycle through the well-known two-step dance, which means they're only in spawnstate half the time. This would make getting this code to trigger somewhat unreliable. (I certainly haven't managed to replicate the double splash damage in my own tests.)
if (target->state == &states[target->info->spawnstate]
&& target->info->seestate != S_NULL)
P_SetMobjState (target, target->info->seestate);
² from P_BlockThingsIterator loop, with added comments. Note that 'func' is PIT_RadiusAttack and 'mobj' is the imp.
Essentially, the list being iterated over is modified in place. This is something they tell you you should never do ☺
for (mobj = blocklinks[y*bmapwidth+x]; mobj; mobj = mobj->bnext)
// before PIT_RadiusAttack: imp is last in list, mobj->bnext is NULL
// (i.e. the loop should end after this iteration when mobj is set to mobj->bnext)
// after PIT_RadiusAttack: list has been re-ordered;
// imp is now first, mobj->bnext is one of the revenants, the loop continues