Check out Half-Life Re-imagined competition results!
Check out Skewing textures in Hammer, our newest tutorial!
Welcome, Shadowx93ca, our newest member!

logo

Site Stuff

Reference

Maps

Community

ShoutBOX

Poll

Feeling Blue

What's your favourite shade of blue?

Azure

12

Cobalt

26

Turquoise

7

Cyan

9

Royal

5

Teal

3

Onliners

48 secs

JeffMOD

6 mins

Windawz

10 mins

Solokiller

16 mins

Admer456

28 mins

Instant Mix

34 mins

Striker

35 mins

MistaX88

Affiliates

A gaming and technology blog by TWHL admins Penguinboy and Ant. A music blog by TWHL users Ant and Hugh.

Hitboxes and Code

By Jonathan 'Teh_Freak' Smith

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!