Locational Damage!

Locational damage isn’t really a big thing if you’re just trying to make a DM mod variant, but concidering the large volume of realism mods on the way, I’ve made some basic locational damage code which has a lot of room for expansion. There are flaws with this method, but unfortunately nothing is perfect. The main problem I’m speaking of is the ORBB model, and how it’s structured differently from the human forms. So, as I said before: This tutorial is mainly for those people who are working on a realism mod, not a DM variant. I don’t know about you, but I haven’t seen a huge walking eyeball in real life lately. Anyway, down to business.

1. SETTING UP

You can’t get down to the nitty gritty without first getting your variables declared and definitions made. In this case, we’re going to define a list of binary flags, matching different body locations. The way I set this up is to break the body into multiple vertical layers. Then I broke it up into four quadrants designating which side the attack came from: Front, Left, Right, Back. For those who don’t know what binary flags are, they’re values which when added to an integer, will turn on only a single bit. 1 turns on 00000001, 2 turns on 00000010, 4 turns on 00000100, and so on. But back to the task at hand. Let’s define our flags in bg_public.h around line 435. We’re putting it in this file so, just like the MOD [means of death], any file can determine where a client was hit.

// How many players on the overlay
#define TEAM_MAXOVERLAY		8

#define LOCATION_NONE		0x00000000

// Height layers
#define LOCATION_HEAD		0x00000001 // [F,B,L,R] Top of head
#define LOCATION_FACE		0x00000002 // [F] Face [B,L,R] Head
#define LOCATION_SHOULDER	0x00000004 // [L,R] Shoulder [F] Throat, [B] Neck
#define LOCATION_CHEST		0x00000008 // [F] Chest [B] Back [L,R] Arm
#define LOCATION_STOMACH	0x00000010 // [L,R] Sides [F] Stomach [B] Lower Back
#define LOCATION_GROIN		0x00000020 // [F] Groin [B] Butt [L,R] Hip
#define LOCATION_LEG		0x00000040 // [F,B,L,R] Legs
#define LOCATION_FOOT		0x00000080 // [F,B,L,R] Bottom of Feet

// Relative direction strike came from
#define LOCATION_LEFT		0x00000100
#define LOCATION_RIGHT		0x00000200
#define LOCATION_FRONT		0x00000400
#define LOCATION_BACK		0x00000800

// means of death
typedef enum {
	MOD_UNKNOWN,
	MOD_SHOTGUN,
	MOD_GAUNTLET,

ADDENDUM!

Sorry, very sorry. When I first uploaded the tutorial I forgot an important definition. You need to define a variable we’ll be using which belongs in the client struct. Open up g_local.h and go to somewhere around line 261.

	
int		lasthurt_client;	// last client that damaged this client
int		lasthurt_mod;		// type of damage the client did
int		lasthurt_location;	// Where the client was hit.

// timers
int		respawnTime;		// can respawn when time > this, force after g_forcerespwan
int		inactivityTime;		// kick players when time > this
qboolean	inactivityWarning;	// qtrue if the five seoond warning has been given
int		rewardTime;		// clear the EF_AWARD_IMPRESSIVE, etc when time > this

2. CHECKING THE LOCATION

There we go. All defined up. Now let’s make our function to actually do the checking for the location the damage came from. I’d suggest putting it right before void G_Damage() [mislabed in the comment as T_Damage()] which is around line 415 in g_combat.c. Yes, this is a server file, so make sure you’re in the GAME module. From here its a matter of copy/paste.


/* 
============
G_LocationDamage
============
*/
int G_LocationDamage(vec3_t point, gentity_t* targ, gentity_t* attacker, int take) {
	vec3_t bulletPath;
	vec3_t bulletAngle;

	int clientHeight;
	int clientFeetZ;
	int clientRotation;
	int bulletHeight;
	int bulletRotation;	// Degrees rotation around client.
				// used to check Back of head vs. Face
	int impactRotation;


	// First things first.  If we're not damaging them, why are we here? 
	if (!take) 
		return 0;

	// Point[2] is the REAL world Z. We want Z relative to the clients feet
	
	// Where the feet are at [real Z]
	clientFeetZ  = targ->r.currentOrigin[2] + targ->r.mins[2];	
	// How tall the client is [Relative Z]
	clientHeight = targ->r.maxs[2] - targ->r.mins[2];
	// Where the bullet struck [Relative Z]
	bulletHeight = point[2] - clientFeetZ;

	// Get a vector aiming from the client to the bullet hit 
	VectorSubtract(targ->r.currentOrigin, point, bulletPath); 
	// Convert it into PITCH, ROLL, YAW
	vectoangles(bulletPath, bulletAngle);

	clientRotation = targ->client->ps.viewangles[YAW];
	bulletRotation = bulletAngle[YAW];

	impactRotation = abs(clientRotation-bulletRotation);
	
	impactRotation += 45; // just to make it easier to work with
	impactRotation = impactRotation % 360; // Keep it in the 0-359 range

	if (impactRotation < 90)
		targ->client->lasthurt_location = LOCATION_BACK;
	else if (impactRotation < 180)
		targ->client->lasthurt_location = LOCATION_RIGHT;
	else if (impactRotation < 270)
		targ->client->lasthurt_location = LOCATION_FRONT;
	else if (impactRotation < 360)
		targ->client->lasthurt_location = LOCATION_LEFT;
	else
		targ->client->lasthurt_location = LOCATION_NONE;

	// The upper body never changes height, just distance from the feet
		if (bulletHeight > clientHeight - 2)
			targ->client->lasthurt_location |= LOCATION_HEAD;
		else if (bulletHeight > clientHeight - 8)
			targ->client->lasthurt_location |= LOCATION_FACE;
		else if (bulletHeight > clientHeight - 10)
			targ->client->lasthurt_location |= LOCATION_SHOULDER;
		else if (bulletHeight > clientHeight - 16)
			targ->client->lasthurt_location |= LOCATION_CHEST;
		else if (bulletHeight > clientHeight - 26)
			targ->client->lasthurt_location |= LOCATION_STOMACH;
		else if (bulletHeight > clientHeight - 29)
			targ->client->lasthurt_location |= LOCATION_GROIN;
		else if (bulletHeight < 4)
			targ->client->lasthurt_location |= LOCATION_FOOT;
		else
			// The leg is the only thing that changes size when you duck,
			// so we check for every other parts RELATIVE location, and
			// whats left over must be the leg. 
			targ->client->lasthurt_location |= LOCATION_LEG; 


		
		// Check the location ignoring the rotation info
		switch ( targ->client->lasthurt_location & 
				~(LOCATION_BACK | LOCATION_LEFT | LOCATION_RIGHT | LOCATION_FRONT) )
		{
		case LOCATION_HEAD:
			take *= 1.8;
			break;
		case LOCATION_FACE:
			if (targ->client->lasthurt_location & LOCATION_FRONT)
				take *= 5.0; // Faceshots REALLY suck
			else
				take *= 1.8;
			break;
		case LOCATION_SHOULDER:
			if (targ->client->lasthurt_location & (LOCATION_FRONT | LOCATION_BACK))
				take *= 1.4; // Throat or nape of neck
			else
				take *= 1.1; // Shoulders
			break;
		case LOCATION_CHEST:
			if (targ->client->lasthurt_location & (LOCATION_FRONT | LOCATION_BACK))
				take *= 1.3; // Belly or back
			else
				take *= 0.8; // Arms
			break;
		case LOCATION_STOMACH:
			take *= 1.2;
			break;
		case LOCATION_GROIN:
			if (targ->client->lasthurt_location & LOCATION_FRONT)
				take *= 1.3; // Groin shot
			break;
		case LOCATION_LEG:
			take *= 0.7;
			break;
		case LOCATION_FOOT:
			take *= 0.5;
			break;

		}
	return take;

}

If you want to look deeper, you’ll see that all I’ve done is use the location and height of the player to determine the location the damage was inflicted, relative to the player’s feet. Ducking is compensated for because only the legs change size when you duck. If you don’t believe me, go into 3rd person view and check for yourself. And don’t spend too much time staring at mynx’s butt while you’re at it. Anyway, after we split the body up into layers, we split it up into the four quadrants. We did this by drawing a vector in the direction of the bullet’s entrance point, from the center of our player. We convert the vector to angles, which gives us PITCH, YAW, and ROLL. We simply take the YAW of the player compared to the YAW of the bullet, and we can determine the angle it struck from. Easy, no?

2. MAKING THE CALL

We’ve made our function, but it still does nothing. Why? Because we haven’t called it of course! Well, that’s an easy fix. Stay in g_combat.c, and go to the middle of G_Damage(), more specifically, somewhere around line 730. Just add the red lines to make it work right.

ADDENDUM!

The code below was modified since the original tutorial was uploaded. If you have already used this tutorial before this addendum was added, it is HIGHLY suggested you make the change in your code. The error within can cause crashes if a dead body is shot. The first IF statement originally was „ if (point && targ && attacker && take) „. The corrected format adds a check for a client with no health [dead], and is as follows: „ if (point && targ && targ->health > 0 && attacker && take) „. Good Luck, and thanks for the bug report Leon.

	// See if it's the player hurting the emeny flag carrier
	Team_CheckHurtCarrier(targ, attacker);

	if (targ->client) {
		// set the last client who damaged the target
		targ->client->lasthurt_client = attacker->s.number;
		targ->client->lasthurt_mod = mod;

		// Modify the damage for location damage
		if (point && targ && targ->health > 0 && attacker && take)
			take = G_LocationDamage(point, targ, attacker, take);
		else
			targ->client->lasthurt_location = LOCATION_NONE; 

	}

	
	// do the damage
	if (take) {
		targ->health = targ->health - take;
		if ( targ->client ) {
			targ->client->ps.stats[STAT_HEALTH] = targ->health;


That’s it for just modifying the damage for locations, and the end of this tutorial. Unless there’s much protest, I’ll just leave the obituary messages and things such as locational armor to you mod authors. Good luck, and good coding.