VERC: Hitboxes and Code Last edited 1 year ago2022-09-29 07:54:55 UTC

In Half-Life you may have noticed sparks appear when you shoot barney's helmet or an alien grunt's armor. Well, I'll show you how this was done along with a few examples that can be applied to some old monsters.

NOTE: This article assumes that you know how to compile and de-compile models.

The Hitbox

Hitboxes determine where on the model it can be hit (hence the name). If you see barney's .QC file (barney_shared.qc), you'll notice these:
// muzzleflash
$attachment 0 "bip01 R hand" 15 3.5 5 X 1

$hbox 3 "Bip01 Pelvis" -9.89 -5.66 -8.60 2.94 6.52 6.79

$hbox 6 "Bip01 L Leg" 0.00 -5.92 -4.00 19.37 3.78 3.73
$hbox 6 "Bip01 L Leg1" 0.00 -5.69 -3.62 18.83 3.07 2.94
$hbox 6 "Bip01 L Foot" 0.00 -2.99 -4.04 6.42 8.68 1.61

$hbox 7 "Bip01 R Leg" 0.00 -5.93 -3.87 19.27 3.75 3.79
$hbox 7 "Bip01 R Leg1" -0.33 -5.73 -2.98 18.95 2.96 3.19
$hbox 7 "Bip01 R Foot" 0.00 -3.24 -2.32 6.21 8.44 3.28

$hbox 3 "Bip01 Spine" 0.00 -4.89 -6.97 8.37 8.00 6.73
$hbox 2 "Bip01 Spine2" 0.00 -3.46 -8.32 6.11 8.00 7.53
$hbox 2 "Bip01 Spine3" -1.00 -5.99 -8.22 5.96 8.00 8.48

$hbox 4 "Bip01 L Arm" -0.40 -4.18 0.00 7.20 4.75 4.20
$hbox 4 "Bip01 L Arm1" -2.52 -4.57 -3.92 9.86 2.28 3.43
$hbox 4 "Bip01 L Arm2" 0.00 -2.85 -2.08 11.90 2.85 2.75
$hbox 4 "Bip01 L Hand" 0.00 -1.32 -2.29 4.66 2.60 1.65

$hbox 5 "Bip01 R Arm" 0.00 -4.15 -4.19 7.53 4.12 4.24
$hbox 5 "Bip01 R Arm1" -2.69 -4.65 -3.42 9.47 2.28 3.76
$hbox 5 "Bip01 R Arm2" 0.00 -2.73 -2.56 11.00 3.14 2.19
$hbox 5 "Bip01 R Hand" 0.00 -1.32 -2.91 10.38 3.23 4.00

//$hbox 1 "Bip01 Head" -0.66 -4.25 -5.24 11.19 7.97 4.63

$hbox 1 "Bip01 Head" -0.66 2.25 -4.24 6.00 7.97 3.63
$hbox 10 "Bip01 Head" -0.66 -4.25 -5.24 6.00 2.25 4.63
$hbox 10 "Bip01 Head" 6.00 -4.25 -5.24 11.19 7.97 4.63

//$hbox 0 "Bone05" 0.00 -2.47 -2.29 3.73 0.00 2.12
//$hbox 0 "Bone03" -2.69 -14.48 -6.05 1.60 4.30 2.29
Those are all the hitboxes, don't worry about the last bunch of numbers, as they are not relevant to this article.
$hbox 1 "Bip01 Head"
$hbox 10 "Bip01 Head"
$hbox 10 "Bip01 Head"
Here are the three hitboxes for barney's head, you'll notice two different numbers here, 1 and 10. These two numbers are important when refering to the code.

The TraceAttack Function

Hitboxes are usually refered in code in a function called TraceAttack. So open up barney.cpp and scroll down to that function, it looks like this:
void CBarney::TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType)
{
    switch( ptr->iHitgroup)
    {
        case HITGROUP_CHEST:
        case HITGROUP_STOMACH:
            if (bitsDamageType & (DMG_BULLET | DMG_SLASH | DMG_BLAST))
            {
                flDamage = flDamage / 2;
            }
            break;
        case 10:
            if (bitsDamageType & (DMG_BULLET | DMG_SLASH | DMG_CLUB))
            {
                flDamage -= 20;
                if (flDamage <= 0)
                {
                    UTIL_Ricochet( ptr->vecEndPos, 1.0 );
                    flDamage = 0.01;
                }
            }
            // always a head shot
            ptr->iHitgroup = HITGROUP_HEAD;
            break;
    }

    CTalkMonster::TraceAttack( pevAttacker, flDamage, vecDir, ptr, bitsDamageType );
}
You may notice the case 10, this is one way of refering to hitboxes in the code, the HITGROUP_CHEST and HITGROUP_HEAD are other ways of refering to hitboxes, and here's why (this is located in monsters.h):
// Hit Group standards
#define HITGROUP_GENERIC  0
#define HITGROUP_HEAD     1
#define HITGROUP_CHEST    2
#define HITGROUP_STOMACH  3
#define HITGROUP_LEFTARM  4
#define HITGROUP_RIGHTARM 5
#define HITGROUP_LEFTLEG  6
#define HITGROUP_RIGHTLEG 7
As you can see, HITGROUP_HEAD is equal to 1, HITGROUP_CHEST is 2, and so on... If you look throughout the SDK (particulary combat.cpp), you'll see these popup again and again.

Back to the TraceAttack function for some in-depth explaining:
case HITGROUP_CHEST:
case HITGROUP_STOMACH:
    if (bitsDamageType & (DMG_BULLET | DMG_SLASH | DMG_BLAST))
    {
        flDamage = flDamage / 2;
    }
    break;
The two cases are stating that if hitbox 2 or 3 are hit by any damage, then the if statement will be executed; which is dividing the damage by 2.
case 10:
    if (bitsDamageType & (DMG_BULLET | DMG_SLASH | DMG_CLUB))
    {
        flDamage -= 20;
        if (flDamage <= 0)
        {
            UTIL_Ricochet( ptr->vecEndPos, 1.0 );
            flDamage = 0.01;
        }
    }
    // always a head shot
    ptr->iHitgroup = HITGROUP_HEAD;
    break;
This case is executed if hitbox 10 is hit. If it is hit by any of the damage types defined, it will take away 20 damage. If the damage turns out to be zero (after taking away damage), then it will make a ricochet and give out a very little bit of damage. That last bit basically forces it to count as a headshot.

Here's another way to refer to hitboxes (easier in my opinion):
// check for helmet shot
if (ptr->iHitgroup == 11)
{
    // make sure we're wearing one
    if (GetBodygroup( 1 ) == HEAD_GRUNT && (bitsDamageType & (DMG_BULLET | DMG_SLASH | DMG_BLAST | DMG_CLUB)))
    {
        // absorb damage
        flDamage -= 20;
        if (flDamage <= 0)
        {
            UTIL_Ricochet( ptr->vecEndPos, 1.0 );
            flDamage = 0.01;
        }
    }
    // it's head shot anyways
    ptr->iHitgroup = HITGROUP_HEAD;
}
This is in the TraceAttack function in hgrunt.cpp. Here you can see that it is set up differently, instead of a switch statement, it uses an if statement.

The first line checks to see if hitbox 11 was hit (which is the grunts helmet). Then it checks to see if it is the helmet wearing grunt (HEAD_GRUNT), the rest should look familiar.

Well, now you know how hitboxes are refered to in code, the next part of this article shows two examples of how this can be applied to the zombie and alien slave.

Examples

Zombie

The first example is simply adding some new code to the zombie. What we'll do is make the zombie bleed red unless it is shot in the head (which is the headcrab). So first of all, you'll need to define the TraceAttack function (right under int IgnoreConditions(void);):
// Teh_Freak: TraceAttack checks to see if it was shot in the head
void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType);
Now you'll need to make the actual function, add this at the end of the file:
//=========================================================
// TraceAttack - checks to see if the zombie was shot in the head
//=========================================================
void CZombie :: TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType)
{
    // Teh_Freak: check to see if shot in the head
    if (ptr->iHitgroup == HITGROUP_HEAD)
    {
        m_bloodColor = BLOOD_COLOR_YELLOW; // Teh_Freak: is shot in the head, emit yellow blood
    }
    else
    {
        m_bloodColor = BLOOD_COLOR_RED; // Teh_Freak: if not, emit red blood
    }

    CBaseMonster::TraceAttack( pevAttacker, flDamage, vecDir, ptr, bitsDamageType );
}
OK, what the if statement does is check to see if hitbox 1 was hit (remeber that little list I showed you?), if it was, then the monsters blood color will change to yellow; if it wasn't, however, then the blood color will change to red.

Alien Slave

This next example is somewhat harder, you'll need to edit the alien slave's QC file (so you'll need to de-compile it first). If you don't have a program that can de-compile models, I've included the model in this article (it is non-HD).

First, open up the QC file, you'll see these hitboxes (or something similar):
$hbox 6 "Bip01 L Leg" -3.96 -6.55 -3.51  18.51 5.56 4.46
$hbox 6 "Bip01 L Leg1" -0.64 -2.27 -2.09  13.41 3.03 1.95
$hbox 6 "Bip01 L Foot" -0.55 -2.82 -6.41  8.7 8.88 4.51
$hbox 7 "Bip01 R Leg" -4.07 -6.65 -4.52  18.51 5.49 3.46
$hbox 7 "Bip01 R Leg1" -0.54 -2.34 -2.00  13.41 1.72 2.03
$hbox 7 "Bip01 R Foot" -0.54 -3.17 -4.46  8.66 9.01 6.46
$hbox 3 "Bip01 Pelvis" -6.31 -8.74 -7.26  6.33 5.23 7.64
$hbox 3 "Bip01 Spine" 0.00 -6.02 -4.51  6.15 5.10 5.35
$hbox 2 "Bip01 Spine1" 0.00 -1.49 -5.48  6.18 0.00 6.13
$hbox 2 "Bip01 Spine2" 0.00 -10.04 -13.03  8.82 8.40 13.29
$hbox 2 "Bip01 Spine3" 0.00 -13.07 -13.18  10.64 11.36 13.76 // <------
$hbox 4 "Bip01 L Arm" 0.00 0.00 -6.89  8.36 1.88 0.00
$hbox 4 "Bip01 L Arm1" 0.00 -5.12 -4.23  22.309999 2.61 2.65
$hbox 4 "Bip01 L Arm2" 0.00 -4.05 -4.42  14.97 2.97 4.59 // <------
$hbox 4 "Bip01 L Hand" 0.00 -1.87 -2.40  7.80 2.52 2.61
$hbox 5 "Bip01 R Arm" 0.00 0.00 0.00  8.42 1.87 7.10
$hbox 5 "Bip01 R Arm1" 0.00 -4.98 -2.42  22.309999 2.82 4.49
$hbox 5 "Bip01 R Arm2" -0.04 -3.65 -4.19  15.23 3.34 4.69 // <------
$hbox 5 "Bip01 R Hand" 0.00 -1.84 -2.38  7.54 2.58 2.63
$hbox 1 "Bip01 Head" 0.00 -5.54 -7.74  11.38 9.60 8.16
The three that I highlighted are the three we are going to edit (I found them by looking through Milkshape3D at the bone view, and a little bit of guesswork)

Simply change the number after $hbox from 2, 4, or 5 to 11 (or anyother number higher than 7).

Now, compile the model and place it in your mod's folder (sub-folder, models) and open up islave.cpp. You'll notice that it already has a TraceAttack function, so just add this after return :
// Teh_Freak: if damaged in the collar/arm brace
if ( ptr->iHitgroup == 11 )
{
    if (bitsDamageType & (DMG_BULLET | DMG_SLASH | DMG_CLUB))
    {
        // Teh_Freak: make a ricochet effect and take little damage
        UTIL_Ricochet( ptr->vecEndPos, 1.0 );
        flDamage = 0.01;
    }
}
Well, now all that explaining at the top may make sense now. Hitgroup 11 gets hit, so a ricochet effect will be made.

Well, now I hope you know the relationship between hitboxes and code. This should make some questions answered (like, "How do I make only one part of a monster take damage"). Happy coding!
This article was originally published on Valve Editing Resource Collective (VERC).
The archived page is available here.
TWHL only publishes archived articles from defunct websites, or with permission. For more information on TWHL's archiving efforts, please visit the TWHL Archiving Project page.

Comments

You must log in to post a comment. You can login or register a new account.