1391 lines
31 KiB
C
1391 lines
31 KiB
C
// Copyright (C) 2001-2002 Raven Software.
|
|
//
|
|
// g_combat.c
|
|
|
|
#include "g_local.h"
|
|
|
|
void BotDamageNotification ( gclient_t *bot, gentity_t *attacker );
|
|
|
|
/*
|
|
============
|
|
G_AddScore
|
|
|
|
Adds score to both the client and his team
|
|
============
|
|
*/
|
|
void G_AddScore( gentity_t *ent, int score )
|
|
{
|
|
if ( !ent->client )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// no scoring during pre-match warmup
|
|
if ( level.warmupTime )
|
|
{
|
|
return;
|
|
}
|
|
|
|
ent->client->sess.score += score;
|
|
ent->client->ps.persistant[PERS_SCORE] = ent->client->sess.score;
|
|
|
|
CalculateRanks();
|
|
}
|
|
|
|
/*
|
|
=================
|
|
TossClientItems
|
|
|
|
Toss the weapon and custom gametype items for the killed player
|
|
=================
|
|
*/
|
|
void TossClientItems( gentity_t *self )
|
|
{
|
|
int weapon;
|
|
|
|
// drop the weapon if not a gauntlet or machinegun
|
|
weapon = self->s.weapon;
|
|
|
|
// make a special check to see if they are changing to a new
|
|
// weapon that isn't the mg or gauntlet. Without this, a client
|
|
// can pick up a weapon, be killed, and not drop the weapon because
|
|
// their weapon change hasn't completed yet and they are still holding the MG.
|
|
if ( self->client->ps.weaponstate == WEAPON_DROPPING )
|
|
{
|
|
weapon = self->client->pers.cmd.weapon;
|
|
}
|
|
|
|
if ( !( self->client->ps.stats[STAT_WEAPONS] & ( 1 << weapon ) ) )
|
|
{
|
|
weapon = WP_NONE;
|
|
}
|
|
|
|
// If we have a valid weapon to drop and it has ammo then drop it
|
|
if ( weapon > WP_KNIFE && weapon < WP_NUM_WEAPONS &&
|
|
(self->client->ps.ammo[ weaponData[weapon].attack[ATTACK_NORMAL].ammoIndex ] + self->client->ps.clip[weapon]) )
|
|
{
|
|
G_DropWeapon ( self, weapon, 0 );
|
|
}
|
|
|
|
G_DropGametypeItems ( self, 0 );
|
|
}
|
|
|
|
/*
|
|
==================
|
|
LookAtKiller
|
|
==================
|
|
*/
|
|
void LookAtKiller( gentity_t *self, gentity_t *inflictor, gentity_t *attacker ) {
|
|
vec3_t dir;
|
|
vec3_t angles;
|
|
|
|
if ( attacker && attacker != self ) {
|
|
VectorSubtract (attacker->s.pos.trBase, self->s.pos.trBase, dir);
|
|
} else if ( inflictor && inflictor != self ) {
|
|
VectorSubtract (inflictor->s.pos.trBase, self->s.pos.trBase, dir);
|
|
} else {
|
|
self->client->ps.stats[STAT_DEAD_YAW] = self->s.angles[YAW];
|
|
return;
|
|
}
|
|
|
|
self->client->ps.stats[STAT_DEAD_YAW] = vectoyaw ( dir );
|
|
|
|
angles[YAW] = vectoyaw ( dir );
|
|
angles[PITCH] = 0;
|
|
angles[ROLL] = 0;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
body_die
|
|
==================
|
|
*/
|
|
void body_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath, int hitLocation, vec3_t hitDir )
|
|
{
|
|
return;
|
|
}
|
|
|
|
|
|
// these are just for logging, the client prints its own messages
|
|
char *modNames[] =
|
|
{
|
|
"MOD_UNKNOWN",
|
|
|
|
"MOD_KNIFE",
|
|
|
|
"MOD_M1911A1_PISTOL",
|
|
"MOD_US_SOCOM_PISTOL",
|
|
"MOD_SILVER_TALON",
|
|
|
|
"MOD_M590_SHOTGUN",
|
|
"MOD_MICRO_UZI_SUBMACHINEGUN",
|
|
"MOD_M3A1_SUBMACHINEGUN",
|
|
"MOD_MP5",
|
|
|
|
"MOD_USAS_12_SHOTGUN",
|
|
"MOD_M4_ASSAULT_RIFLE",
|
|
"MOD_AK74_ASSAULT_RIFLE",
|
|
"MOD_SIG551",
|
|
"MOD_MSG90A1_SNIPER_RIFLE",
|
|
"MOD_M60_MACHINEGUN",
|
|
"MOD_MM1_GRENADE_LAUNCHER",
|
|
"MOD_RPG7_LAUNCHER",
|
|
|
|
"MOD_M84_GRENADE",
|
|
"MOD_SMOHG92_GRENADE",
|
|
"MOD_ANM14_GRENADE",
|
|
"MOD_M15_GRENADE",
|
|
|
|
"MOD_WATER",
|
|
"MOD_CRUSH",
|
|
"MOD_TELEFRAG",
|
|
"MOD_FALLING",
|
|
"MOD_SUICIDE",
|
|
"MOD_TEAMCHANGE",
|
|
"MOD_TARGET_LASER",
|
|
"MOD_TRIGGER_HURT",
|
|
"MOD_TRIGGER_HURT_NOSUICIDE"
|
|
};
|
|
|
|
/*
|
|
==================
|
|
player_die
|
|
==================
|
|
*/
|
|
void player_die(
|
|
gentity_t *self,
|
|
gentity_t *inflictor,
|
|
gentity_t *attacker,
|
|
int damage,
|
|
int mod,
|
|
int hitLocation,
|
|
vec3_t hitDir
|
|
)
|
|
{
|
|
gentity_t *ent;
|
|
int anim;
|
|
int contents;
|
|
int killer;
|
|
int i;
|
|
char *killerName, *obit;
|
|
attackType_t attack;
|
|
int meansOfDeath;
|
|
|
|
attack = (mod >> 8) & 0xFF;
|
|
meansOfDeath = mod & 0xFF;
|
|
|
|
if ( self->client->ps.pm_type == PM_DEAD )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( level.intermissiontime )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Let the gametype know about the player death so it can adjust anything
|
|
// it needs to adjust
|
|
if ( attacker && attacker->client )
|
|
{
|
|
trap_GT_SendEvent ( GTEV_CLIENT_DEATH, level.time, self->s.number, self->client->sess.team, attacker->s.number, attacker->client->sess.team, 0 );
|
|
}
|
|
else
|
|
{
|
|
trap_GT_SendEvent ( GTEV_CLIENT_DEATH, level.time, self->s.number, self->client->sess.team, -1, -1, 0 );
|
|
}
|
|
|
|
// Add to the number of deaths for this player
|
|
self->client->sess.deaths++;
|
|
|
|
// This is just to ensure that the player wont render for even a single frame
|
|
self->s.eFlags |= EF_DEAD;
|
|
|
|
self->client->ps.pm_type = PM_DEAD;
|
|
|
|
if ( attacker )
|
|
{
|
|
killer = attacker->s.number;
|
|
if ( attacker->client )
|
|
{
|
|
killerName = attacker->client->pers.netname;
|
|
}
|
|
else
|
|
{
|
|
killerName = "<non-client>";
|
|
}
|
|
}
|
|
else
|
|
{
|
|
killer = ENTITYNUM_WORLD;
|
|
killerName = "<world>";
|
|
}
|
|
|
|
if ( killer < 0 || killer >= MAX_CLIENTS )
|
|
{
|
|
killer = ENTITYNUM_WORLD;
|
|
killerName = "<world>";
|
|
}
|
|
|
|
if ( meansOfDeath < 0 || meansOfDeath >= sizeof( modNames ) / sizeof( modNames[0] ) )
|
|
{
|
|
obit = "<bad obituary>";
|
|
}
|
|
else
|
|
{
|
|
if ( attack == ATTACK_ALTERNATE )
|
|
{
|
|
obit = va ( "%s_ALT", modNames[ meansOfDeath ] );
|
|
}
|
|
else
|
|
{
|
|
obit = modNames[ meansOfDeath ];
|
|
}
|
|
}
|
|
|
|
// If the weapon was charging then drop it with no forward velocity
|
|
if ( self->client->ps.grenadeTimer )
|
|
{
|
|
gentity_t* missile;
|
|
missile = G_FireWeapon( self, ATTACK_NORMAL );
|
|
|
|
if ( attacker && attacker->client && attacker->client->sess.team != self->client->sess.team )
|
|
{
|
|
missile->dflags |= DAMAGE_NO_TEAMKILL;
|
|
}
|
|
|
|
if ( missile )
|
|
{
|
|
VectorClear ( missile->s.pos.trDelta );
|
|
}
|
|
}
|
|
|
|
G_LogPrintf("Kill: %i %i %i: %s killed %s by %s\n",
|
|
killer, self->s.number, meansOfDeath, killerName,
|
|
self->client->pers.netname, obit );
|
|
|
|
// broadcast the death event to everyone
|
|
ent = G_TempEntity( self->r.currentOrigin, EV_OBITUARY );
|
|
ent->s.eventParm = mod;
|
|
ent->s.otherEntityNum = self->s.number;
|
|
ent->s.otherEntityNum2 = killer;
|
|
ent->r.svFlags = SVF_BROADCAST; // send to everyone
|
|
|
|
self->enemy = attacker;
|
|
|
|
if (attacker && attacker->client)
|
|
{
|
|
attacker->client->lastkilled_client = self->s.number;
|
|
|
|
if ( attacker == self )
|
|
{
|
|
if ( mod != MOD_TEAMCHANGE && mod != MOD_TRIGGER_HURT_NOSUICIDE )
|
|
{
|
|
G_AddScore( attacker, g_suicidePenalty.integer );
|
|
}
|
|
}
|
|
else if ( OnSameTeam ( self, attacker ) )
|
|
{
|
|
if ( mod != MOD_TELEFRAG && mod != MOD_TRIGGER_HURT_NOSUICIDE )
|
|
{
|
|
G_AddScore( attacker, g_teamkillPenalty.integer );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
G_AddScore( attacker, 1 );
|
|
attacker->client->sess.kills++;
|
|
|
|
attacker->client->lastKillTime = level.time;
|
|
}
|
|
}
|
|
else if ( mod != MOD_TEAMCHANGE && mod != MOD_TRIGGER_HURT_NOSUICIDE )
|
|
{
|
|
G_AddScore( self, g_suicidePenalty.integer );
|
|
}
|
|
|
|
// If client is in a nodrop area, don't drop anything
|
|
contents = trap_PointContents( self->r.currentOrigin, -1 );
|
|
if ( !( contents & CONTENTS_NODROP ) )
|
|
{
|
|
// People who kill themselves dont drop guns
|
|
if ( attacker == self )
|
|
{
|
|
self->client->ps.stats[STAT_WEAPONS] = 0;
|
|
}
|
|
|
|
TossClientItems( self );
|
|
}
|
|
else
|
|
{
|
|
// Any gametype items that are dropped into a no drop area need to be reported
|
|
// to the gametype so it can handle it accordingly
|
|
for ( i = 0 ; i < MAX_GAMETYPE_ITEMS ; i++ )
|
|
{
|
|
gitem_t* item;
|
|
|
|
// skip this gametype item if the client doenst have it
|
|
if ( !(self->client->ps.stats[STAT_GAMETYPE_ITEMS] & (1<<i)) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
item = BG_FindGametypeItem ( i );
|
|
|
|
// Let the gametype handle the problem, if it doenst handle it and return 1 then
|
|
// just reset the gametype item
|
|
if ( !trap_GT_SendEvent ( GTEV_ITEM_STUCK, level.time, level.gametypeItems[item->giTag].id, 0, 0, 0, 0 ) )
|
|
{
|
|
G_ResetGametypeItem ( item );
|
|
}
|
|
}
|
|
}
|
|
|
|
Cmd_Score_f( self );
|
|
|
|
// send updated scores to any clients that are following this one,
|
|
// or they would get stale scoreboards
|
|
for ( i = 0 ; i < level.numConnectedClients; i++ )
|
|
{
|
|
gclient_t *client;
|
|
|
|
client = g_entities[level.sortedClients[i]].client;
|
|
|
|
if ( client->pers.connected != CON_CONNECTED )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ( !G_IsClientSpectating ( client ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ( client->sess.spectatorClient == self->s.number )
|
|
{
|
|
Cmd_Score_f( g_entities + i );
|
|
}
|
|
}
|
|
|
|
self->s.weapon = WP_NONE;
|
|
self->s.gametypeitems = 0;
|
|
|
|
// no gibbing right now
|
|
// self->r.contents = CONTENTS_CORPSE;
|
|
// self->takedamage = qtrue; // can still be gibbed
|
|
self->r.contents = CONTENTS_CORPSE;
|
|
self->takedamage = qfalse;
|
|
|
|
self->client->ps.zoomFov = 0; // Turn off zooming when we die
|
|
self->client->ps.stats[STAT_GAMETYPE_ITEMS] = 0;
|
|
self->client->ps.pm_flags &= ~(PMF_GOGGLES_ON|PMF_ZOOM_FLAGS);
|
|
self->client->ps.loopSound = 0;
|
|
|
|
self->s.angles[0] = 0;
|
|
self->s.angles[2] = 0;
|
|
self->s.loopSound = 0;
|
|
self->r.maxs[2] = -8;
|
|
|
|
LookAtKiller (self, inflictor, attacker);
|
|
|
|
VectorCopy( self->s.angles, self->client->ps.viewangles );
|
|
|
|
// don't allow respawn until the death anim is done
|
|
// g_forcerespawn may force spawning at some later time
|
|
self->client->respawnTime = level.time + 1700;
|
|
|
|
switch ( hitLocation & (~HL_DISMEMBERBIT) )
|
|
{
|
|
case HL_WAIST:
|
|
if ( rand() %2 )
|
|
{
|
|
anim = BOTH_DEATH_GROIN_1 + (rand()%2);
|
|
}
|
|
else
|
|
{
|
|
anim = BOTH_DEATH_GUT_1 + (rand()%2); // GUT2 is being shot from the back.
|
|
}
|
|
break;
|
|
|
|
default:
|
|
case HL_CHEST:
|
|
anim = BOTH_DEATH_CHEST_1 + (rand()%2);
|
|
break;
|
|
|
|
case HL_CHEST_RT:
|
|
if ( irand(1,10) < 8 )
|
|
{
|
|
anim = BOTH_DEATH_SHOULDER_RIGHT_1 + (rand()%2);
|
|
}
|
|
else
|
|
{
|
|
anim = BOTH_DEATH_CHEST_1 + (rand()%2);
|
|
}
|
|
break;
|
|
|
|
case HL_CHEST_LT:
|
|
|
|
if ( irand(1,10) < 8 )
|
|
{
|
|
anim = BOTH_DEATH_SHOULDER_LEFT_1 + (rand()%2);
|
|
}
|
|
else
|
|
{
|
|
anim = BOTH_DEATH_CHEST_1 + (rand()%2);
|
|
}
|
|
|
|
break;
|
|
|
|
case HL_NECK:
|
|
anim = BOTH_DEATH_NECK;
|
|
break;
|
|
|
|
case HL_HEAD:
|
|
anim = BOTH_DEATH_HEAD_1 + (rand()%2);
|
|
break;
|
|
|
|
case HL_LEG_UPPER_LT:
|
|
anim = BOTH_DEATH_THIGH_LEFT_1 + (rand()%2);
|
|
break;
|
|
|
|
case HL_LEG_LOWER_LT:
|
|
case HL_FOOT_LT:
|
|
anim = BOTH_DEATH_LEGS_LEFT_1 + (rand()%3);
|
|
break;
|
|
|
|
case HL_ARM_LT:
|
|
|
|
if ( rand()%2 )
|
|
anim = BOTH_DEATH_ARMS_LEFT_1 + (rand()%2);
|
|
else
|
|
anim = BOTH_DEATH_SHOULDER_LEFT_1 + (rand()%2);
|
|
|
|
break;
|
|
|
|
case HL_ARM_RT:
|
|
|
|
if ( rand()%2 )
|
|
anim = BOTH_DEATH_ARMS_RIGHT_1 + (rand()%2);
|
|
else
|
|
anim = BOTH_DEATH_SHOULDER_RIGHT_1 + (rand()%2);
|
|
|
|
break;
|
|
|
|
case HL_LEG_UPPER_RT:
|
|
anim = BOTH_DEATH_THIGH_RIGHT_1 + (rand()%2);
|
|
break;
|
|
|
|
case HL_LEG_LOWER_RT:
|
|
case HL_FOOT_RT:
|
|
anim = BOTH_DEATH_LEGS_RIGHT_1 + (rand()%3);
|
|
break;
|
|
}
|
|
|
|
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;
|
|
|
|
// If the dismember bit is set then make sure the body queue dismembers
|
|
// the location that was hit
|
|
if ( hitLocation & HL_DISMEMBERBIT )
|
|
{
|
|
CopyToBodyQue (self, hitLocation & (~HL_DISMEMBERBIT), hitDir );
|
|
}
|
|
else
|
|
{
|
|
CopyToBodyQue (self, HL_NONE, hitDir );
|
|
}
|
|
|
|
// the body can still be gibbed
|
|
self->die = body_die;
|
|
|
|
trap_LinkEntity (self);
|
|
}
|
|
|
|
/*
|
|
================
|
|
CheckArmor
|
|
================
|
|
*/
|
|
int CheckArmor (gentity_t *ent, int damage, int dflags)
|
|
{
|
|
gclient_t *client;
|
|
int save;
|
|
int count;
|
|
|
|
if (!damage)
|
|
return 0;
|
|
|
|
client = ent->client;
|
|
|
|
if (!client)
|
|
return 0;
|
|
|
|
if (dflags & DAMAGE_NO_ARMOR)
|
|
return 0;
|
|
|
|
// armor
|
|
count = client->ps.stats[STAT_ARMOR];
|
|
save = ceil( damage * ARMOR_PROTECTION );
|
|
if (save >= count)
|
|
save = count;
|
|
|
|
if (!save)
|
|
return 0;
|
|
|
|
client->ps.stats[STAT_ARMOR] -= save;
|
|
|
|
return save;
|
|
}
|
|
|
|
|
|
void G_ApplyKnockback( gentity_t *targ, vec3_t newDir, float knockback )
|
|
{
|
|
vec3_t kvel;
|
|
float mass;
|
|
|
|
if ( targ->physicsBounce > 0 ) //overide the mass
|
|
mass = targ->physicsBounce;
|
|
else
|
|
mass = 200;
|
|
|
|
if ( g_gravity.value > 0 )
|
|
{
|
|
VectorScale( newDir, g_knockback.value * (float)knockback / mass * 0.8, kvel );
|
|
// kvel[2] = newDir[2] * g_knockback.value * (float)knockback / mass * 1.5;
|
|
}
|
|
else
|
|
{
|
|
VectorScale( newDir, g_knockback.value * (float)knockback / mass, kvel );
|
|
}
|
|
|
|
if ( targ->client )
|
|
{
|
|
VectorAdd( targ->client->ps.velocity, kvel, targ->client->ps.velocity );
|
|
}
|
|
else if ( targ->s.pos.trType != TR_STATIONARY && targ->s.pos.trType != TR_LINEAR_STOP )
|
|
{
|
|
VectorAdd( targ->s.pos.trDelta, kvel, targ->s.pos.trDelta );
|
|
VectorCopy( targ->r.currentOrigin, targ->s.pos.trBase );
|
|
targ->s.pos.trTime = level.time;
|
|
}
|
|
|
|
// set the timer so that the other client can't cancel
|
|
// out the movement immediately
|
|
if ( targ->client && !targ->client->ps.pm_time )
|
|
{
|
|
int t;
|
|
|
|
t = knockback * 2;
|
|
if ( t < 50 ) {
|
|
t = 50;
|
|
}
|
|
if ( t > 200 ) {
|
|
t = 200;
|
|
}
|
|
targ->client->ps.pm_time = t;
|
|
targ->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
RaySphereIntersections
|
|
================
|
|
*/
|
|
int RaySphereIntersections( vec3_t origin, float radius, vec3_t point, vec3_t dir, vec3_t intersections[2] ) {
|
|
float b, c, d, t;
|
|
|
|
// | origin - (point + t * dir) | = radius
|
|
// a = dir[0]^2 + dir[1]^2 + dir[2]^2;
|
|
// b = 2 * (dir[0] * (point[0] - origin[0]) + dir[1] * (point[1] - origin[1]) + dir[2] * (point[2] - origin[2]));
|
|
// c = (point[0] - origin[0])^2 + (point[1] - origin[1])^2 + (point[2] - origin[2])^2 - radius^2;
|
|
|
|
// normalize dir so a = 1
|
|
VectorNormalize(dir);
|
|
b = 2 * (dir[0] * (point[0] - origin[0]) + dir[1] * (point[1] - origin[1]) + dir[2] * (point[2] - origin[2]));
|
|
c = (point[0] - origin[0]) * (point[0] - origin[0]) +
|
|
(point[1] - origin[1]) * (point[1] - origin[1]) +
|
|
(point[2] - origin[2]) * (point[2] - origin[2]) -
|
|
radius * radius;
|
|
|
|
d = b * b - 4 * c;
|
|
if (d > 0) {
|
|
t = (- b + sqrt(d)) / 2;
|
|
VectorMA(point, t, dir, intersections[0]);
|
|
t = (- b - sqrt(d)) / 2;
|
|
VectorMA(point, t, dir, intersections[1]);
|
|
return 2;
|
|
}
|
|
else if (d == 0) {
|
|
t = (- b ) / 2;
|
|
VectorMA(point, t, dir, intersections[0]);
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int G_GetHitLocation(gentity_t *target, vec3_t ppoint, vec3_t dir )
|
|
{
|
|
float fdot;
|
|
float rdot;
|
|
vec3_t tangles;
|
|
vec3_t forward;
|
|
vec3_t up;
|
|
vec3_t right;
|
|
vec3_t distance;
|
|
vec3_t tcenter;
|
|
vec3_t temp;
|
|
vec3_t hit;
|
|
|
|
// We are only interested in the YAW angle of the target
|
|
VectorSet( tangles, 0, target->client->ps.viewangles[YAW], 0);
|
|
|
|
// Extract the forward, right, and up vectors
|
|
AngleVectors ( tangles, forward, right, up );
|
|
|
|
// Determine the center of the target entity
|
|
VectorAdd(target->r.absmin, target->r.absmax, tcenter);
|
|
VectorScale(tcenter, 0.5, tcenter);
|
|
|
|
|
|
/* NOTE: This would work to figure out shots that go across the front of someone and
|
|
hit the opposite side, but had an error in it when a shot came from either
|
|
the immediate left or right of the player.
|
|
*/
|
|
|
|
// Calculate the distnace from the shooter to the target
|
|
VectorCopy ( dir, temp );
|
|
VectorSubtract ( tcenter, ppoint, distance );
|
|
|
|
// Use that distnace to determine the point of tangent in relation to
|
|
// the center of the player entity
|
|
VectorMA ( ppoint, DotProduct ( temp, distance ), temp, hit );
|
|
|
|
// Create a vector from the tangent point to the center. This will
|
|
// be used to determine which side was hit
|
|
VectorSubtract ( tcenter, hit, temp );
|
|
VectorCopy ( temp, distance );
|
|
|
|
VectorSubtract ( tcenter, ppoint, temp );
|
|
VectorNormalize ( temp );
|
|
|
|
// Determine the shot in relation to the forward vector
|
|
fdot = DotProduct ( forward, temp );
|
|
|
|
// Determine the shot in relation to the right vector
|
|
rdot = DotProduct ( right, temp );
|
|
|
|
if ( distance[2] < -35 )
|
|
{
|
|
return HL_HEAD;
|
|
}
|
|
else if ( distance[2] < -32 )
|
|
{
|
|
return HL_NECK;
|
|
}
|
|
else if ( distance[2] < -27 )
|
|
{
|
|
if ( rdot > 0 )
|
|
return HL_ARM_LT;
|
|
|
|
return HL_ARM_RT;
|
|
}
|
|
else if ( distance[2] < -3 )
|
|
{
|
|
if ( fdot > 0 )
|
|
{
|
|
if ( rdot > 0 )
|
|
{
|
|
return HL_CHEST_LT;
|
|
}
|
|
|
|
return HL_CHEST_RT;
|
|
}
|
|
|
|
if ( rdot > 0 )
|
|
{
|
|
return HL_BACK_LT;
|
|
}
|
|
|
|
return HL_BACK_RT;
|
|
}
|
|
else if ( distance[2] < 4 )
|
|
{
|
|
return HL_WAIST;
|
|
}
|
|
else if ( distance[2] < 18 )
|
|
{
|
|
if ( rdot > 0 )
|
|
return HL_LEG_UPPER_LT;
|
|
|
|
return HL_LEG_UPPER_RT;
|
|
}
|
|
else if ( distance[2] < 33 )
|
|
{
|
|
if ( rdot > 0 )
|
|
return HL_LEG_LOWER_LT;
|
|
|
|
return HL_LEG_LOWER_RT;
|
|
}
|
|
|
|
if ( rdot > 0 )
|
|
return HL_FOOT_LT;
|
|
|
|
return HL_FOOT_RT;
|
|
}
|
|
|
|
/*
|
|
============
|
|
T_Damage
|
|
|
|
targ entity that is being damaged
|
|
inflictor entity that is causing the damage
|
|
attacker entity that caused the inflictor to damage targ
|
|
example: targ=monster, inflictor=rocket, attacker=player
|
|
|
|
dir direction of the attack for knockback
|
|
point point at which the damage is being inflicted, used for headshots
|
|
damage amount of damage being inflicted
|
|
knockback force to be applied against targ as a result of the damage
|
|
|
|
inflictor, attacker, dir, and point can be NULL for environmental effects
|
|
|
|
dflags these flags are used to control how T_Damage works
|
|
DAMAGE_RADIUS damage was indirect (from a nearby explosion)
|
|
DAMAGE_NO_ARMOR armor does not protect from this damage
|
|
DAMAGE_NO_KNOCKBACK do not affect velocity, just view angles
|
|
DAMAGE_NO_PROTECTION kills godmode, armor, everything
|
|
============
|
|
*/
|
|
|
|
int G_Damage (
|
|
gentity_t *targ,
|
|
gentity_t *inflictor,
|
|
gentity_t *attacker,
|
|
vec3_t dir,
|
|
vec3_t point,
|
|
int damage,
|
|
int dflags,
|
|
int mod,
|
|
int location
|
|
)
|
|
{
|
|
gclient_t *client;
|
|
int take;
|
|
int save;
|
|
int asave;
|
|
int knockback;
|
|
|
|
if (!targ->takedamage)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
// See if they are invulnerable
|
|
if ( (mod&0xFF) < MOD_WATER )
|
|
{
|
|
if ( targ->client && (level.time - targ->client->invulnerableTime < g_respawnInvulnerability.integer * 1000) )
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// Cant change outfitting after being shot
|
|
if ( targ->client )
|
|
{
|
|
targ->client->noOutfittingChange = qtrue;
|
|
}
|
|
|
|
if ( !inflictor )
|
|
{
|
|
inflictor = &g_entities[ENTITYNUM_WORLD];
|
|
}
|
|
if ( !attacker )
|
|
{
|
|
attacker = &g_entities[ENTITYNUM_WORLD];
|
|
}
|
|
|
|
// shootable doors / buttons don't actually have any health
|
|
if ( targ->s.eType == ET_MOVER )
|
|
{
|
|
if ( targ->use && targ->moverState == MOVER_POS1 )
|
|
{
|
|
targ->use( targ, inflictor, attacker );
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
client = targ->client;
|
|
|
|
if ( client )
|
|
{
|
|
if ( client->noclip )
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if ( !dir )
|
|
{
|
|
dflags |= DAMAGE_NO_KNOCKBACK;
|
|
}
|
|
else
|
|
{
|
|
VectorNormalize(dir);
|
|
}
|
|
|
|
knockback = damage;
|
|
if ( knockback > 200 )
|
|
{
|
|
knockback = 200;
|
|
}
|
|
if ( targ->flags & FL_NO_KNOCKBACK ) {
|
|
knockback = 0;
|
|
}
|
|
if ( dflags & DAMAGE_NO_KNOCKBACK ) {
|
|
knockback = 0;
|
|
}
|
|
|
|
/*
|
|
// figure momentum add, even if the damage won't be taken
|
|
if ( knockback && targ->client )
|
|
{
|
|
G_ApplyKnockback ( targ, dir, knockback );
|
|
vec3_t kvel;
|
|
float mass;
|
|
|
|
mass = 200;
|
|
|
|
VectorScale (dir, g_knockback.value * (float)knockback / mass, kvel);
|
|
VectorAdd (targ->client->ps.velocity, kvel, targ->client->ps.velocity);
|
|
|
|
// set the timer so that the other client can't cancel
|
|
// out the movement immediately
|
|
if ( !targ->client->ps.pm_time ) {
|
|
int t;
|
|
|
|
t = knockback * 2;
|
|
if ( t < 50 ) {
|
|
t = 50;
|
|
}
|
|
if ( t > 200 ) {
|
|
t = 200;
|
|
}
|
|
targ->client->ps.pm_time = t;
|
|
targ->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
|
|
}
|
|
}
|
|
*/
|
|
|
|
// check for completely getting out of the damage
|
|
if ( !(dflags & DAMAGE_NO_PROTECTION) ) {
|
|
|
|
// if TF_NO_FRIENDLY_FIRE is set, don't do damage to the target
|
|
// if the attacker was on the same team
|
|
if ( targ != attacker && OnSameTeam (targ, attacker) )
|
|
{
|
|
if ( !g_friendlyFire.integer || level.warmupTime )
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// check for godmode
|
|
if ( targ->flags & FL_GODMODE )
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if ( damage < 1 )
|
|
{
|
|
damage = 1;
|
|
}
|
|
|
|
take = damage;
|
|
save = 0;
|
|
|
|
// Be careful with grenades
|
|
if ( attacker == targ )
|
|
{
|
|
take *= 2;
|
|
}
|
|
|
|
// save some from armor
|
|
asave = CheckArmor (targ, take, dflags);
|
|
take -= asave;
|
|
|
|
// Teamkill dmage thats not caused by a telefrag?
|
|
if ( g_teamkillDamageMax.integer && mod != MOD_TELEFRAG && !(dflags&DAMAGE_NO_TEAMKILL) )
|
|
{
|
|
if ( level.gametypeData->teams && targ && attacker && targ != attacker )
|
|
{
|
|
// Hurt your own team?
|
|
if ( OnSameTeam ( targ, attacker ) )
|
|
{
|
|
int actualtake = Com_Clamp ( 0, targ->health, take );
|
|
|
|
if ( targ->client->ps.stats[STAT_GAMETYPE_ITEMS] )
|
|
{
|
|
actualtake *= 2;
|
|
}
|
|
|
|
// See if this damage falls into the no excuse damage
|
|
if ( level.gametypeData->respawnType == RT_NONE && level.time - level.gametypeDelayTime < g_teamkillNoExcuseTime.integer * 1000 )
|
|
{
|
|
actualtake *= g_teamkillNoExcuseMultiplier.integer;
|
|
}
|
|
|
|
attacker->client->sess.teamkillDamage += actualtake;
|
|
attacker->client->sess.teamkillForgiveTime = level.time;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Output hits
|
|
if ( g_logHits.integer && attacker && targ && attacker->client && targ->client )
|
|
{
|
|
G_LogPrintf ( "hit: %i %i %i %i %i: %s hit %s at location %i for %i\n",
|
|
attacker->s.number,
|
|
targ->s.number,
|
|
location,
|
|
take,
|
|
asave,
|
|
attacker->client->pers.netname,
|
|
targ->client->pers.netname,
|
|
location,
|
|
(int)((float)take) );
|
|
}
|
|
|
|
if ( g_debugDamage.integer )
|
|
{
|
|
Com_Printf( "%i: client:%i health:%i damage:%i armor:%i\n", level.time, targ->s.number, targ->health, take, asave );
|
|
}
|
|
|
|
// add to the damage inflicted on a player this frame
|
|
// the total will be turned into screen blends and view angle kicks
|
|
// at the end of the frame
|
|
if ( client )
|
|
{
|
|
if ( attacker )
|
|
{
|
|
client->ps.persistant[PERS_ATTACKER] = attacker->s.number;
|
|
}
|
|
else
|
|
{
|
|
client->ps.persistant[PERS_ATTACKER] = ENTITYNUM_WORLD;
|
|
}
|
|
|
|
if ( mod != MOD_WATER )
|
|
{
|
|
client->damage_armor += asave;
|
|
client->damage_blood += take;
|
|
}
|
|
|
|
client->damage_knockback += knockback;
|
|
|
|
if ( dir )
|
|
{
|
|
VectorCopy ( dir, client->damage_from );
|
|
client->damage_fromWorld = qfalse;
|
|
}
|
|
else
|
|
{
|
|
VectorCopy ( targ->r.currentOrigin, client->damage_from );
|
|
client->damage_fromWorld = qtrue;
|
|
}
|
|
|
|
if (attacker && attacker->client)
|
|
{
|
|
BotDamageNotification(client, attacker);
|
|
}
|
|
else if (inflictor && inflictor->client)
|
|
{
|
|
BotDamageNotification(client, inflictor);
|
|
}
|
|
}
|
|
|
|
if (targ->client)
|
|
{
|
|
// set the last client who damaged the target
|
|
targ->client->lasthurt_client = attacker->s.number;
|
|
targ->client->lasthurt_time = level.time;
|
|
targ->client->lasthurt_mod = mod;
|
|
}
|
|
|
|
// do the damage
|
|
if (take)
|
|
{
|
|
targ->health = targ->health - take;
|
|
|
|
if ( targ->client )
|
|
{
|
|
targ->client->ps.stats[STAT_HEALTH] = targ->health;
|
|
|
|
if ( targ->health > 0 )
|
|
{
|
|
// 45 damage is full slowdown, so..
|
|
float slowdown;
|
|
|
|
slowdown = (float)damage / 20.0f;
|
|
slowdown = Com_Clampf ( 0.0f, 1.0f, slowdown );
|
|
slowdown *= 0.75f;
|
|
slowdown = 1.0f - slowdown;
|
|
|
|
// Slow down the client at bit when they get hit
|
|
targ->client->ps.velocity[0] *= slowdown;
|
|
targ->client->ps.velocity[1] *= slowdown;
|
|
|
|
// figure momentum add, even if the damage won't be taken
|
|
if ( knockback )
|
|
{
|
|
G_ApplyKnockback ( targ, dir, knockback );
|
|
}
|
|
|
|
// Friendly fire
|
|
if ( !level.warmupTime && g_friendlyFire.integer && targ != attacker && OnSameTeam ( targ, attacker ) )
|
|
{
|
|
vec3_t diff;
|
|
|
|
// Make sure the attacker is close enough to hear the guy whining
|
|
VectorSubtract ( targ->r.currentOrigin, attacker->r.currentOrigin, diff );
|
|
if ( VectorLengthSquared ( diff ) < 800 * 800 )
|
|
{
|
|
G_VoiceGlobal ( targ, "check_fire", qfalse );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( targ->health <= 0 )
|
|
{
|
|
// Something dismembered?
|
|
if ( (targ->health < DISMEMBER_HEALTH && !(dflags&DAMAGE_NO_GORE)) || (dflags&DAMAGE_FORCE_GORE) )
|
|
{
|
|
location |= HL_DISMEMBERBIT;
|
|
}
|
|
|
|
if ( client )
|
|
targ->flags |= FL_NO_KNOCKBACK;
|
|
|
|
if (targ->health < -999)
|
|
targ->health = -999;
|
|
|
|
targ->enemy = attacker;
|
|
targ->die (targ, inflictor, attacker, take, mod, location, dir );
|
|
}
|
|
else if ( targ->pain )
|
|
{
|
|
targ->pain (targ, attacker, take);
|
|
}
|
|
}
|
|
|
|
return take;
|
|
}
|
|
|
|
/*
|
|
============
|
|
CanDamage
|
|
|
|
Returns qtrue if the inflictor can directly damage the target. Used for
|
|
explosions and melee attacks.
|
|
============
|
|
*/
|
|
qboolean CanDamage (gentity_t *targ, vec3_t origin) {
|
|
vec3_t dest;
|
|
trace_t tr;
|
|
vec3_t midpoint;
|
|
|
|
// use the midpoint of the bounds instead of the origin, because
|
|
// bmodels may have their origin is 0,0,0
|
|
VectorAdd (targ->r.absmin, targ->r.absmax, midpoint);
|
|
VectorScale (midpoint, 0.5, midpoint);
|
|
|
|
VectorCopy (midpoint, dest);
|
|
trap_Trace ( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID);
|
|
if (tr.fraction == 1.0 || tr.entityNum == targ->s.number)
|
|
return qtrue;
|
|
|
|
// this should probably check in the plane of projection,
|
|
// rather than in world coordinate, and also include Z
|
|
VectorCopy (midpoint, dest);
|
|
dest[0] += 15.0;
|
|
dest[1] += 15.0;
|
|
trap_Trace ( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID);
|
|
if (tr.fraction == 1.0)
|
|
return qtrue;
|
|
|
|
VectorCopy (midpoint, dest);
|
|
dest[2] = targ->r.absmax[2];
|
|
trap_Trace ( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID);
|
|
if (tr.fraction == 1.0)
|
|
return qtrue;
|
|
|
|
VectorCopy (midpoint, dest);
|
|
dest[2] = targ->r.absmin[2];
|
|
trap_Trace ( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID);
|
|
if (tr.fraction == 1.0)
|
|
return qtrue;
|
|
|
|
VectorCopy (midpoint, dest);
|
|
dest[0] += 15.0;
|
|
dest[1] -= 15.0;
|
|
trap_Trace ( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID);
|
|
if (tr.fraction == 1.0)
|
|
return qtrue;
|
|
|
|
VectorCopy (midpoint, dest);
|
|
dest[0] -= 15.0;
|
|
dest[1] += 15.0;
|
|
trap_Trace ( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID);
|
|
if (tr.fraction == 1.0)
|
|
return qtrue;
|
|
|
|
VectorCopy (midpoint, dest);
|
|
dest[0] -= 15.0;
|
|
dest[1] -= 15.0;
|
|
trap_Trace ( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID);
|
|
if (tr.fraction == 1.0)
|
|
return qtrue;
|
|
|
|
|
|
return qfalse;
|
|
}
|
|
/*
|
|
============
|
|
G_MultipleDamageLocations
|
|
============
|
|
*/
|
|
int G_MultipleDamageLocations(int hitLocation)
|
|
{
|
|
|
|
switch ( hitLocation & (~HL_DISMEMBERBIT) )
|
|
{
|
|
case HL_FOOT_RT:
|
|
case HL_FOOT_LT:
|
|
hitLocation |= (HL_FOOT_RT | HL_FOOT_LT);
|
|
break;
|
|
case HL_LEG_UPPER_RT:
|
|
hitLocation |= (HL_LEG_UPPER_RT | HL_LEG_LOWER_LT);
|
|
if ( rand() %2 )
|
|
{
|
|
hitLocation |= HL_HAND_RT;
|
|
}
|
|
break;
|
|
case HL_LEG_UPPER_LT:
|
|
hitLocation |= (HL_LEG_UPPER_LT | HL_LEG_LOWER_RT);
|
|
if ( rand() %2 )
|
|
{
|
|
hitLocation |= HL_HAND_LT;
|
|
}
|
|
break;
|
|
case HL_LEG_LOWER_RT:
|
|
hitLocation |= (HL_LEG_LOWER_RT | HL_FOOT_LT);
|
|
break;
|
|
case HL_LEG_LOWER_LT:
|
|
hitLocation |= (HL_LEG_LOWER_LT | HL_FOOT_RT);
|
|
break;
|
|
case HL_HAND_RT:
|
|
hitLocation |= HL_HAND_RT;
|
|
break;
|
|
case HL_HAND_LT:
|
|
hitLocation |= HL_HAND_LT;
|
|
break;
|
|
case HL_ARM_RT:
|
|
hitLocation |= (HL_ARM_RT | HL_LEG_UPPER_RT) ;
|
|
break;
|
|
case HL_ARM_LT:
|
|
hitLocation |= (HL_ARM_LT | HL_LEG_UPPER_LT) ;
|
|
break;
|
|
case HL_HEAD:
|
|
hitLocation |= HL_HEAD ;
|
|
if ( rand() %2 )
|
|
{
|
|
hitLocation |= HL_ARM_RT;
|
|
}
|
|
else
|
|
{
|
|
hitLocation |= HL_ARM_LT;
|
|
}
|
|
break;
|
|
case HL_WAIST:
|
|
hitLocation |= (HL_LEG_UPPER_RT | HL_LEG_UPPER_LT) ;
|
|
|
|
if ( rand() %2 )
|
|
{
|
|
if ( rand() %2 )
|
|
{
|
|
hitLocation |= HL_HAND_RT;
|
|
}
|
|
else
|
|
{
|
|
hitLocation |= HL_HAND_LT;
|
|
}
|
|
}
|
|
break;
|
|
case HL_BACK_RT:
|
|
case HL_CHEST_RT:
|
|
hitLocation |= HL_ARM_RT;
|
|
hitLocation |= HL_HEAD;
|
|
break;
|
|
case HL_BACK_LT:
|
|
case HL_CHEST_LT:
|
|
hitLocation |= HL_ARM_LT;
|
|
hitLocation |= HL_HEAD;
|
|
break;
|
|
case HL_BACK:
|
|
case HL_CHEST:
|
|
hitLocation |= (HL_ARM_RT | HL_ARM_LT);
|
|
hitLocation |= HL_HEAD;
|
|
break;
|
|
|
|
}
|
|
|
|
return (hitLocation);
|
|
}
|
|
|
|
|
|
/*
|
|
============
|
|
G_RadiusDamage
|
|
============
|
|
*/
|
|
qboolean G_RadiusDamage (
|
|
vec3_t origin,
|
|
gentity_t* attacker,
|
|
float damage,
|
|
float radius,
|
|
gentity_t* ignore,
|
|
int power,
|
|
int dflags,
|
|
int mod
|
|
)
|
|
{
|
|
float points, dist;
|
|
gentity_t *ent, *tent;
|
|
int entityList[MAX_GENTITIES];
|
|
int numListedEntities;
|
|
vec3_t mins, maxs;
|
|
vec3_t v;
|
|
vec3_t dir;
|
|
int i, e;
|
|
qboolean hitClient = qfalse;
|
|
|
|
if ( radius < 1 )
|
|
{
|
|
radius = 1;
|
|
}
|
|
|
|
for ( i = 0 ; i < 3 ; i++ )
|
|
{
|
|
mins[i] = origin[i] - radius;
|
|
maxs[i] = origin[i] + radius;
|
|
}
|
|
|
|
numListedEntities = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
|
|
|
|
for ( e = 0 ; e < numListedEntities ; e++ )
|
|
{
|
|
ent = &g_entities[entityList[ e ]];
|
|
|
|
if (ent == ignore)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (!ent->takedamage)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// find the distance from the edge of the bounding box
|
|
for ( i = 0 ; i < 3 ; i++ )
|
|
{
|
|
if ( origin[i] < ent->r.absmin[i] )
|
|
{
|
|
v[i] = ent->r.absmin[i] - origin[i];
|
|
}
|
|
else if ( origin[i] > ent->r.absmax[i] )
|
|
{
|
|
v[i] = origin[i] - ent->r.absmax[i];
|
|
}
|
|
else
|
|
{
|
|
v[i] = 0;
|
|
}
|
|
}
|
|
|
|
dist = VectorLength( v );
|
|
if ( dist >= radius )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
points = damage * ( 1.0 - powf((dist / radius), power));
|
|
|
|
if( CanDamage (ent, origin) )
|
|
{
|
|
int location;
|
|
int weapon;
|
|
vec3_t hitdir;
|
|
int d;
|
|
|
|
VectorSubtract (ent->r.currentOrigin, origin, dir);
|
|
// push the center of mass higher than the origin so players
|
|
// get knocked into the air more
|
|
|
|
location = HL_NONE;
|
|
if ( ent->client )
|
|
{
|
|
VectorNormalize ( dir );
|
|
VectorCopy(dir, hitdir);
|
|
dir[2] = 0;
|
|
location = G_GetHitLocation ( ent, origin, dir );
|
|
location = G_MultipleDamageLocations ( location );
|
|
}
|
|
|
|
d = G_Damage (ent, NULL, attacker, dir, origin, (int)points, DAMAGE_RADIUS|DAMAGE_NO_ARMOR|dflags, mod, location );
|
|
|
|
if ( d && ent->client )
|
|
{
|
|
// Only one of the grenade hits will count for tk damage
|
|
if ( ent != attacker )
|
|
{
|
|
dflags |= DAMAGE_NO_TEAMKILL;
|
|
}
|
|
|
|
// Put some procedural gore on the target.
|
|
tent = G_TempEntity( origin, EV_EXPLOSION_HIT_FLESH );
|
|
|
|
// send entity and direction
|
|
tent->s.eventParm = DirToByte( hitdir );
|
|
if (ignore && ignore->s.weapon)
|
|
{
|
|
weapon = ignore->s.weapon; // Weapon type number
|
|
}
|
|
else if (points >= 10)
|
|
{ // dangerous weapon
|
|
weapon = WP_SMOHG92_GRENADE;
|
|
}
|
|
else
|
|
{ // Just a flesh wound
|
|
weapon = WP_M84_GRENADE;
|
|
}
|
|
tent->s.otherEntityNum2 = ent->s.number; // Victim entity number
|
|
|
|
// Pack the shot info into the temp end for gore
|
|
tent->s.time = weapon + ((((int)ent->s.apos.trBase[YAW]&0x7FFF) % 360) << 16);
|
|
if ( attacker->s.eFlags & EF_ALT_FIRING )
|
|
{
|
|
tent->s.time += (ATTACK_ALTERNATE<<8);
|
|
}
|
|
VectorCopy ( ent->r.currentOrigin, tent->s.angles );
|
|
SnapVector ( tent->s.angles );
|
|
}
|
|
}
|
|
}
|
|
|
|
return hitClient;
|
|
}
|