TUTORIAL 30 - Beheading (headshot!)
by Kilderean
To make this tutorial work you'll need to implement headshot detection in your code, I'll provide a very
quick headshot detection and I won't go into much detail about how to make it work because it is not the
point of this tutorial. For an in-depth tutorial on locational damage I recommend
Tutorial 14 here on Code3arena.
Now what we're going to do today is pretty cool, when we get a head shot we're gonna kill the player and
blow his head off. ;)
1. Adding some new variables in the client and the server
First let's add some new vars to the server, in g_local.h go to line ~278 in the gclient_s struct
and add this variable:
int timeResidual;
qboolean noHead;
We'll be needing that so that the server knows if a client has his head on or not.
Now open up cg_local.h and go to line ~120 in the playerEntity_t struct and add this variable:
qboolean barrelSpinning;
qboolean noHead;
We'll also need that one to determine, client side, if a player has his head on or not.
Also at around line ~1019 add this new function we'll be using later:
void CG_GibPlayer( vec3_t playerOrigin );
void CG_GibPlayerHeadshot( vec3_t playerOrigin );
void CG_Bleed( vec3_t origin, int entityNum );
Now lets open up bg_public.h and define 2 new events that will be used by the server to
tell the client that someone lost his head. One of those needed to tell the client that
a body has no head, you'll understand why later.
Go at around line ~320-380 in the entity_event_t enumeration
and add these events:
EV_POWERUP_REGEN,
EV_GIB_PLAYER, // gib a previously living player
EV_GIB_PLAYER_HEADSHOT,
EV_BODY_NOHEAD,
EV_DEBUG_LINE,
Also add this to the meansOfDeath_t enumeration:
MOD_TRIGGER_HURT,
MOD_HEADSHOT,
MOD_GRAPPLE
That's it, now let's do some real work!
2. Modifying server code for head shots
Now quickly add these lines to the G_Damage function in g_combat.c at around line ~448 to make headshots a reality:
gclient_t *client;
int take;
int save;
int asave;
int knockback;
float z_ratio;
float z_rel;
int height;
float targ_maxs2;
if (!targ->takedamage) {
return;
}
And at around line ~618:
if (targ->client) {
// set the last client who damaged the target
targ->client->lasthurt_client = attacker->s.number;
targ->client->lasthurt_mod = mod;
}
if (targ->client && attacker->client && targ->health > 0)
{
// let's say only railgun can do head shots
if(inflictor->s.weapon==WP_RAILGUN){
targ_maxs2 = targ->r.maxs[2];
// handling crouching
if(targ->client->ps.pm_flags & PMF_DUCKED){
height = (abs(targ->r.mins[2]) + targ_maxs2)*(0.75);
}
else
height = abs(targ->r.mins[2]) + targ_maxs2;
// project the z component of point
// onto the z component of the model's origin
// this results in the z component from the origin at 0
z_rel = point[2] - targ->r.currentOrigin[2] + abs(targ->r.mins[2]);
z_ratio = z_rel / height;
if (z_ratio > 0.90){
take=9999; // head shot is a sure kill
mod=MOD_HEADSHOT;
}
}
}
// do the damage
Also add this line in the modNames[] array, for logging, at around line ~171:
"MOD_TRIGGER_HURT",
"MOD_HEADSHOT",
"MOD_GRAPPLE"
This needs to match the position of the MOD_HEADSHOT flag in the meansOfDeath_t enumeration.
Now this is not the best head shot detection code but it works for what I'm tutoring here.
3. Server head removal
Still in g_combat.c go in the player_die function at around line ~336 and make
the following changes:
memset( self->client->ps.powerups, 0, sizeof(self->client->ps.powerups) );
// never gib in a nodrop
if ( self->health <= GIB_HEALTH
&& !(contents & CONTENTS_NODROP)
&& g_blood.integer
&& meansOfDeath != MOD_HEADSHOT ) {
// gib death
GibEntity( self, killer );
} else {
// normal death
static int i;
switch ( i ) {
case 0:
anim = BOTH_DEATH1;
break;
case 1:
anim = BOTH_DEATH2;
break;
case 2:
default:
anim = BOTH_DEATH3;
break;
}
// for the no-blood option, we need to prevent the health
// from going to gib level
if ( self->health <= GIB_HEALTH ) {
self->health = GIB_HEALTH+1;
}
self->client->ps.legsAnim =
( ( self->client->ps.legsAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim;
self->client->ps.torsoAnim =
( ( self->client->ps.torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim;
G_AddEvent( self, EV_DEATH1 + 1, killer );
if(meansOfDeath == MOD_HEADSHOT)
GibEntity_Headshot( self, killer );
else
self->client->noHead = qfalse;
// the body can still be gibbed
self->die = body_die;
// globally cycle through the different death animations
i = ( i + 1 ) % 3;
}
Now what we did here is check if we have a head shot death, if so, we don't want the player to gib
all over the place like a regular violent death, we want the regular death animation to play and only
the head to blow up.
Find the GibEntity function at around line ~120 and code what we need to do just that.
The function should look like this:
void GibEntity( gentity_t *self, int killer ) {
G_AddEvent( self, EV_GIB_PLAYER, killer );
self->takedamage = qfalse;
self->s.eType = ET_INVISIBLE;
self->r.contents = 0;
}
Now add this new function right under the GibEntity function:
//c3a
void GibEntity_Headshot( gentity_t *self, int killer ) {
G_AddEvent( self, EV_GIB_PLAYER_HEADSHOT, 0 );
self->client->noHead = qtrue;
}
Here we send the head removal event to the clients and we also set the noHead boolean of the gclient
to true.
Now there's something we have to fix on the server before we do the client side of the beheading,
we need to remove the head on the cadavre of the client who lost his head. That is because Q3 uses a
"body queue" to draw the cadavres on the ground and if we don't tell the body in the body queue to
leave his head behind it will pop back on when the player respawns.
Ok so open up g_client.c and go in the CopyToBodyQue function at around
line ~279 and make the following changes:
// don't take more damage if already gibbed
if ( ent->health <= GIB_HEALTH ) {
body->takedamage = qfalse;
} else {
body->takedamage = qtrue;
}
if(ent->client->noHead)
G_AddEvent( body,EV_BODY_NOHEAD,0 );
VectorCopy ( body->s.pos.trBase, body->r.currentOrigin );
trap_LinkEntity (body);
And let's not forget to reset the head when the player respawns, at around line ~1011 of G_ClientSpawn:
ClientEndFrame( ent );
ent->client->noHead=qfalse;
// clear entity state values
BG_PlayerStateToEntityState( &client->ps, &ent->s, qtrue );
Done with the server!
4. Modifying client code for head shots
Open up cg_event.c and add this code at around line ~203 to handle head shot MOD with the
other weapons MOD.
case MOD_HEADSHOT:
gender = ci->gender;
if(gender==GENDER_FEMALE)
message = "got her head blown off by";
else{
if(gender==GENDER_NEUTER)
message = "got its head blown off by";
else
message = "got his head blown off by";
}
break;
And at around line ~174 add this to display to the player that he made a head shot:
if ( attacker == cg.snap->ps.clientNum ) {
char *s;
if(mod!=MOD_HEADSHOT){
if ( cgs.gametype < GT_TEAM ) {
s = va("You fragged %s\n%s place with %i", targetName,
CG_PlaceString( cg.snap->ps.persistant[PERS_RANK] + 1 ),
cg.snap->ps.persistant[PERS_SCORE] );
} else {
s = va("You fragged %s", targetName );
}
}
else{
if ( cgs.gametype < GT_TEAM ) {
s = va("Headshot!\n\nYou fragged %s\n%s place with %i", targetName,
CG_PlaceString( cg.snap->ps.persistant[PERS_RANK] + 1 ),
cg.snap->ps.persistant[PERS_SCORE] );
} else {
s = va("Headshot!\n\nYou fragged %s", targetName );
}
}
CG_CenterPrint( s, SCREEN_HEIGHT * 0.25, BIGCHAR_WIDTH );
// print the text message as well
}
5. Client head removal
All right, open cg_event.c and at around line ~852 add the two event handlers
after the regular gib event:
case EV_GIB_PLAYER:
DEBUGNAME("EV_GIB_PLAYER");
trap_S_StartSound( NULL, es->number, CHAN_BODY, cgs.media.gibSound );
CG_GibPlayer( cent->lerpOrigin );
break;
case EV_GIB_PLAYER_HEADSHOT:
DEBUGNAME("EV_GIB_PLAYER_HEADSHOT");
trap_S_StartSound( NULL, es->number, CHAN_BODY, cgs.media.gibSound );
cent->pe.noHead = qtrue;
CG_GibPlayerHeadshot( cent->lerpOrigin );
break;
case EV_BODY_NOHEAD:
DEBUGNAME("EV_BODY_NOHEAD");
cent->pe.noHead = qtrue;
break;
The first one is to gib the head and remove the head initially, the second
is to remove the head of the cadavre.
Now lets go make our CG_GibPlayerHeadshot function in cg_effects.c
at the end of the file. Make it look like this:
void CG_GibPlayerHeadshot( vec3_t playerOrigin ) {
vec3_t origin, velocity;
if ( !cg_blood.integer ) {
return;
}
VectorCopy( playerOrigin, origin );
origin[2]+=25;
velocity[0] = crandom()*GIB_VELOCITY;
velocity[1] = crandom()*GIB_VELOCITY;
velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY;
if ( rand() & 1 ) {
CG_LaunchGib( origin, velocity, cgs.media.gibSkull );
} else {
CG_LaunchGib( origin, velocity, cgs.media.gibBrain );
}
//delete from here
VectorCopy( playerOrigin, origin );
velocity[0] = crandom()*GIB_VELOCITY;
velocity[1] = crandom()*GIB_VELOCITY;
velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY;
CG_LaunchGib( origin, velocity, cgs.media.gibFist );
//to here to remove the gibFist, same apply for the others
VectorCopy( playerOrigin, origin );
velocity[0] = crandom()*GIB_VELOCITY;
velocity[1] = crandom()*GIB_VELOCITY;
velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY;
CG_LaunchGib( origin, velocity, cgs.media.gibFoot );
VectorCopy( playerOrigin, origin );
velocity[0] = crandom()*GIB_VELOCITY;
velocity[1] = crandom()*GIB_VELOCITY;
velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY;
CG_LaunchGib( origin, velocity, cgs.media.gibForearm );
}
Now in this CG_GibPlayerHeadshot function I add the gibFist, gibFoot and gibForearm elements
to the blowing up of the head(brain or skull). Now this is my choice and you can remove them if you
want to by deleting from the VectorCopy(...) to the CG_LaunchGib(...) for each one you wish to remove.
And finally, lets not draw the player's head if it has been blown off in
cg_players.c at around line ~1482 in function CG_Player, replace
the existing code with:
CG_AddRefEntityWithPowerups( &torso, cent->currentState.powerups, ci->team );
//
// add the head
//
if(!cent->pe.noHead){
head.hModel = ci->headModel;
if (!head.hModel) {
return;
}
head.customSkin = ci->headSkin;
VectorCopy( cent->lerpOrigin, head.lightingOrigin );
CG_PositionRotatedEntityOnTag( &head, &torso, ci->torsoModel, "tag_head");
head.shadowPlane = shadowPlane;
head.renderfx = renderfx;
CG_AddRefEntityWithPowerups( &head, cent->currentState.powerups, ci->team );
}
//
// add the gun / barrel / flash
//
CG_AddPlayerWeapon( &torso, NULL, cent );
And also let's not forget to reset the head when the player respawns, at around line ~1622 in
CG_ResetPlayerEntity:
cent->pe.torso.pitching = qfalse;
cent->pe.noHead = qfalse;
if ( cg_debugPosition.integer ) {
CG_Printf("%i ResetPlayerEntity yaw=%i\n", cent->currentState.number, cent->pe.torso.yawAngle );
}
Done!
6. Final thoughts
I just noticed a small bug, when you get your head blown off and you see your player fall on the ground
he still has his head on. If someone finds a fix, just email me so I can update the tutorial.
Et voilà!
Have fun!
|