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.
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.
|