Bounding box of a corpse
Every Half-Life player has experienced the following: shoot an NPC, the NPC dies without gibbing, and the body blocks you until after dying animation finishes.
Although mostly harmless in a casual playthrough, the fact that a dying body continues to exhibit collision can be annoying in speedrunning. For example, killing the headcrabs in the vent linked to the flooded room in Office Complex without gibbing them is a guaranteed way to temporarily hinder a speedrunner’s progress while the he/she attempts to crawl through the vent as fast as possible.
When an NPC dies without gibbing, CBaseMonster::Killed
is called.
m_IdealMonsterState = MONSTERSTATE_DEAD;
When CBaseMonster::MaintainSchedule
is called, it reconciles the mismatch between m_IdealMonsterState
and m_MonsterState
, and calls CBaseMonster::ChangeSchedule
in the base class:
SetState( m_IdealMonsterState );
if ( m_MonsterState == MONSTERSTATE_SCRIPT || m_MonsterState == MONSTERSTATE_DEAD )
pNewSchedule = CBaseMonster::GetSchedule();
else
pNewSchedule = GetSchedule();
ChangeSchedule( pNewSchedule );
We see that a schedule corresponding to SCHED_DIE
is returned given this monster state:
case MONSTERSTATE_DEAD:
{
return GetScheduleOfType( SCHED_DIE );
break;
}
For most NPCs, the actual scheduled returned is slDie
defined as follows:
//=========================================================
// Die!
//=========================================================
Task_t tlDie1[] =
{
{ TASK_STOP_MOVING, 0 },
{ TASK_SOUND_DIE, (float)0 },
{ TASK_DIE, (float)0 },
};
Schedule_t slDie[] =
{
{
tlDie1,
ARRAYSIZE( tlDie1 ),
0,
0,
"Die"
},
};
We see that nothing can interrupt this schedule except death by gibbing. The most important task in the schedule is, as you might expect, TASK_DIE
.
Different monsters have different ways of running TASK_DIE
. Most of them execute the task the same way as defined in schedule.cpp
. In CBaseMonster::StartTask
, a death animation is chosen and set:
case TASK_DIE:
{
RouteClear();
m_IdealActivity = GetDeathActivity();
pev->deadflag = DEAD_DYING;
break;
}
In CBaseMonster::RunTask
, it is clear why a corpse behaves the way it does. Here is the case in full:
case TASK_DIE:
{
if ( m_fSequenceFinished && pev->frame >= 255 )
{
pev->deadflag = DEAD_DEAD;
SetThink ( NULL );
StopAnimation();
if ( !BBoxFlat() )
{
// a bit of a hack. If a corpses' bbox is positioned such that being left solid so that it can be attacked will
// block the player on a slope or stairs, the corpse is made nonsolid.
// pev->solid = SOLID_NOT;
UTIL_SetSize ( pev, Vector ( -4, -4, 0 ), Vector ( 4, 4, 1 ) );
}
else // !!!HACKHACK - put monster in a thin, wide bounding box until we fix the solid type/bounding volume problem
UTIL_SetSize ( pev, Vector ( pev->mins.x, pev->mins.y, pev->mins.z ), Vector ( pev->maxs.x, pev->maxs.y, pev->mins.z + 1 ) );
if ( ShouldFadeOnDeath() )
{
// this monster was created by a monstermaker... fade the corpse out.
SUB_StartFadeOut();
}
else
{
// body is gonna be around for a while, so have it stink for a bit.
CSoundEnt::InsertSound ( bits_SOUND_CARCASS, pev->origin, 384, 30 );
}
}
break;
}
First, notice that this entire block does not run until the animation finishes as indicated by m_fSequenceFinished
and the mostly equivalent condition of pev->frame >= 255
. Second, assuming BBoxFlat
returns true, which is most of the cases if the NPC is lying flat on a surface, the bounding box is set to have a height of only one unit. For reasons not clear to me for now, this causes the body to effectively have zero height to the player, creating no collision with the player. The body does still collide with other entities such as boxes and doors. Finally, if the corpse is not meant to be faded, a smell will be created. Before this piece of code is run, the bounding box of the NPC is the same as its bounding box before dying.