Code3Arena

PlanetQuake | Code3Arena | Tutorials | << Prev | Tutorial 30 | Next >>

menu

  • Home/News
  • ModSource
  • Compiling
  • Help!!!
  • Submission
  • Contributors
  • Staff
  • Downloads

    Tutorials
    < Index >
    1. Mod making 101
    2. Up 'n running
    3. Hello, QWorld!
    4. Infinite Haste
    5. Armor Piercing Rails
    6. Bouncing Rockets
    7. Cloaking
    8. Ladders
    9. Favourite Server
    10. Flame Thrower
    11. Vortex Grenades
    12. Grapple
    13. Lightning Discharge
    14. Locational Damage
    15. Leg Shots
    16. Weapon Switching
    17. Scoreboard frag-rate
    18. Vortex Grenades II
    19. Vulnerable Missiles
    20. Creating Classes
    21. Scrolling Credits
    22. Weapon Dropping
    23. Anti-Gravity Boots
    24. HUD scoreboard
    25. Flashlight and laser
    26. Weapon Positioning
    27. Weapon Reloading
    28. Progressive Zooming
    29. Rotating Doors
    30. Beheading (headshot!)
    31. Alt Weapon Fire
    32. Popup Menus I
    33. Popup Menus II
    34. Cluster Grenades
    35. Homing Rockets
    36. Spreadfire Powerup
    37. Instagib gameplay
    38. Accelerating rockets
    39. Server only Instagib
    40. Advanced Grapple Hook
    41. Unlagging your mod


    Articles
    < Index >
    1. Entities
    2. Vectors
    3. Good Coding
    4. Compilers I
    5. Compilers II
    6. UI Menu Primer I
    7. UI Menu Primer II
    8. UI Menu Primer III
    9. QVM Communication, Cvars, commands
    10. Metrowerks CodeWarrior
    11. 1.27g code, bugs, batch


    Links

  • Quake3 Files
  • Quake3 Forums
  • Q3A Editing Message Board
  • Quake3 Editing


    Feedback

  • SumFuka
  • Calrathan
  • HypoThermia
  • WarZone





    Site Design by:
    ICEmosis Design


  •  
    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!


    PlanetQuake | Code3Arena | Tutorials | << Prev | Tutorial 30 | Next >>