TUTORIAL 14 - Locational
Damage! by Calrathan
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.
// 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 && 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.
|