5943 lines
154 KiB
C
5943 lines
154 KiB
C
// Copyright (C) 1999-2000 Id Software, Inc.
|
|
//
|
|
// g_combat.c
|
|
|
|
//#include "g_local.h"
|
|
#include "b_local.h"
|
|
#include "bg_saga.h"
|
|
|
|
extern int G_ShipSurfaceForSurfName( const char *surfaceName );
|
|
extern qboolean G_FlyVehicleDestroySurface( gentity_t *veh, int surface );
|
|
extern void G_VehicleSetDamageLocFlags( gentity_t *veh, int impactDir, int deathPoint );
|
|
extern void G_VehUpdateShields( gentity_t *targ );
|
|
extern void G_LetGoOfWall( gentity_t *ent );
|
|
extern void BG_ClearRocketLock( playerState_t *ps );
|
|
extern void G_CheckTKAutoKickBan( gentity_t *ent );
|
|
//rww - pd
|
|
void BotDamageNotification(gclient_t *bot, gentity_t *attacker);
|
|
//end rww
|
|
|
|
void ThrowSaberToAttacker(gentity_t *self, gentity_t *attacker);
|
|
|
|
void ObjectDie (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath )
|
|
{
|
|
if(self->target)
|
|
{
|
|
G_UseTargets(self, attacker);
|
|
}
|
|
|
|
//remove my script_targetname
|
|
G_FreeEntity( self );
|
|
}
|
|
|
|
qboolean G_HeavyMelee( gentity_t *attacker )
|
|
{
|
|
if (g_gametype.integer == GT_SIEGE
|
|
&& attacker
|
|
&& attacker->client
|
|
&& attacker->client->siegeClass != -1
|
|
&& (bgSiegeClasses[attacker->client->siegeClass].classflags & (1<<CFL_HEAVYMELEE)) )
|
|
{
|
|
return qtrue;
|
|
}
|
|
return qfalse;
|
|
}
|
|
|
|
int G_GetHitLocation(gentity_t *target, vec3_t ppoint)
|
|
{
|
|
vec3_t point, point_dir;
|
|
vec3_t forward, right, up;
|
|
vec3_t tangles, tcenter;
|
|
float tradius;
|
|
float udot, fdot, rdot;
|
|
int Vertical, Forward, Lateral;
|
|
int HitLoc;
|
|
|
|
// Get target forward, right and up.
|
|
if(target->client)
|
|
{
|
|
// Ignore player's pitch and roll.
|
|
VectorSet(tangles, 0, target->r.currentAngles[YAW], 0);
|
|
}
|
|
|
|
AngleVectors(tangles, forward, right, up);
|
|
|
|
// Get center of target.
|
|
VectorAdd(target->r.absmin, target->r.absmax, tcenter);
|
|
VectorScale(tcenter, 0.5, tcenter);
|
|
|
|
// Get radius width of target.
|
|
tradius = (fabs(target->r.maxs[0]) + fabs(target->r.maxs[1]) + fabs(target->r.mins[0]) + fabs(target->r.mins[1]))/4;
|
|
|
|
// Get impact point.
|
|
if(ppoint && !VectorCompare(ppoint, vec3_origin))
|
|
{
|
|
VectorCopy(ppoint, point);
|
|
}
|
|
else
|
|
{
|
|
return HL_NONE;
|
|
}
|
|
|
|
/*
|
|
//get impact dir
|
|
if(pdir && !VectorCompare(pdir, vec3_origin))
|
|
{
|
|
VectorCopy(pdir, dir);
|
|
}
|
|
else
|
|
{
|
|
return;
|
|
}
|
|
|
|
//put point at controlled distance from center
|
|
VectorSubtract(point, tcenter, tempvec);
|
|
tempvec[2] = 0;
|
|
hdist = VectorLength(tempvec);
|
|
|
|
VectorMA(point, hdist - tradius, dir, point);
|
|
//now a point on the surface of a cylinder with a radius of tradius
|
|
*/
|
|
VectorSubtract(point, tcenter, point_dir);
|
|
VectorNormalize(point_dir);
|
|
|
|
// Get bottom to top (vertical) position index
|
|
udot = DotProduct(up, point_dir);
|
|
if(udot>.800)
|
|
{
|
|
Vertical = 4;
|
|
}
|
|
else if(udot>.400)
|
|
{
|
|
Vertical = 3;
|
|
}
|
|
else if(udot>-.333)
|
|
{
|
|
Vertical = 2;
|
|
}
|
|
else if(udot>-.666)
|
|
{
|
|
Vertical = 1;
|
|
}
|
|
else
|
|
{
|
|
Vertical = 0;
|
|
}
|
|
|
|
// Get back to front (forward) position index.
|
|
fdot = DotProduct(forward, point_dir);
|
|
if(fdot>.666)
|
|
{
|
|
Forward = 4;
|
|
}
|
|
else if(fdot>.333)
|
|
{
|
|
Forward = 3;
|
|
}
|
|
else if(fdot>-.333)
|
|
{
|
|
Forward = 2;
|
|
}
|
|
else if(fdot>-.666)
|
|
{
|
|
Forward = 1;
|
|
}
|
|
else
|
|
{
|
|
Forward = 0;
|
|
}
|
|
|
|
// Get left to right (lateral) position index.
|
|
rdot = DotProduct(right, point_dir);
|
|
if(rdot>.666)
|
|
{
|
|
Lateral = 4;
|
|
}
|
|
else if(rdot>.333)
|
|
{
|
|
Lateral = 3;
|
|
}
|
|
else if(rdot>-.333)
|
|
{
|
|
Lateral = 2;
|
|
}
|
|
else if(rdot>-.666)
|
|
{
|
|
Lateral = 1;
|
|
}
|
|
else
|
|
{
|
|
Lateral = 0;
|
|
}
|
|
|
|
HitLoc = Vertical * 25 + Forward * 5 + Lateral;
|
|
|
|
if(HitLoc <= 10)
|
|
{
|
|
// Feet.
|
|
if ( rdot > 0 )
|
|
{
|
|
return HL_FOOT_RT;
|
|
}
|
|
else
|
|
{
|
|
return HL_FOOT_LT;
|
|
}
|
|
}
|
|
else if(HitLoc <= 50)
|
|
{
|
|
// Legs.
|
|
if ( rdot > 0 )
|
|
{
|
|
return HL_LEG_RT;
|
|
}
|
|
else
|
|
{
|
|
return HL_LEG_LT;
|
|
}
|
|
}
|
|
else if(HitLoc == 56||HitLoc == 60||HitLoc == 61||HitLoc == 65||HitLoc == 66||HitLoc == 70)
|
|
{
|
|
// Hands.
|
|
if ( rdot > 0 )
|
|
{
|
|
return HL_HAND_RT;
|
|
}
|
|
else
|
|
{
|
|
return HL_HAND_LT;
|
|
}
|
|
}
|
|
else if(HitLoc == 83||HitLoc == 87||HitLoc == 88||HitLoc == 92||HitLoc == 93||HitLoc == 97)
|
|
{
|
|
// Arms.
|
|
if ( rdot > 0 )
|
|
{
|
|
return HL_ARM_RT;
|
|
}
|
|
else
|
|
{
|
|
return HL_ARM_LT;
|
|
}
|
|
}
|
|
else if((HitLoc >= 107 && HitLoc <= 109)||(HitLoc >= 112 && HitLoc <= 114)||(HitLoc >= 117 && HitLoc <= 119))
|
|
{
|
|
// Head.
|
|
return HL_HEAD;
|
|
}
|
|
else
|
|
{
|
|
if(udot < 0.3)
|
|
{
|
|
return HL_WAIST;
|
|
}
|
|
else if(fdot < 0)
|
|
{
|
|
if(rdot > 0.4)
|
|
{
|
|
return HL_BACK_RT;
|
|
}
|
|
else if(rdot < -0.4)
|
|
{
|
|
return HL_BACK_LT;
|
|
}
|
|
else if(fdot < 0)
|
|
{
|
|
return HL_BACK;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(rdot > 0.3)
|
|
{
|
|
return HL_CHEST_RT;
|
|
}
|
|
else if(rdot < -0.3)
|
|
{
|
|
return HL_CHEST_LT;
|
|
}
|
|
else if(fdot < 0)
|
|
{
|
|
return HL_CHEST;
|
|
}
|
|
}
|
|
}
|
|
return HL_NONE;
|
|
}
|
|
|
|
/*
|
|
int G_PickPainAnim( gentity_t *self, vec3_t point, int damage )
|
|
{
|
|
switch( G_GetHitLocation( self, point ) )
|
|
{
|
|
case HL_FOOT_RT:
|
|
return BOTH_PAIN12;
|
|
//PAIN12 = right foot
|
|
break;
|
|
case HL_FOOT_LT:
|
|
return -1;
|
|
break;
|
|
case HL_LEG_RT:
|
|
if ( !Q_irand( 0, 1 ) )
|
|
{
|
|
return BOTH_PAIN11;
|
|
}
|
|
else
|
|
{
|
|
return BOTH_PAIN13;
|
|
}
|
|
//PAIN11 = twitch right leg
|
|
//PAIN13 = right knee
|
|
break;
|
|
case HL_LEG_LT:
|
|
return BOTH_PAIN14;
|
|
//PAIN14 = twitch left leg
|
|
break;
|
|
case HL_BACK_RT:
|
|
return BOTH_PAIN7;
|
|
//PAIN7 = med left shoulder
|
|
break;
|
|
case HL_BACK_LT:
|
|
return Q_irand( BOTH_PAIN15, BOTH_PAIN16 );
|
|
//PAIN15 = med right shoulder
|
|
//PAIN16 = twitch right shoulder
|
|
break;
|
|
case HL_BACK:
|
|
if ( !Q_irand( 0, 1 ) )
|
|
{
|
|
return BOTH_PAIN1;
|
|
}
|
|
else
|
|
{
|
|
return BOTH_PAIN5;
|
|
}
|
|
//PAIN1 = back
|
|
//PAIN5 = same as 1
|
|
break;
|
|
case HL_CHEST_RT:
|
|
return BOTH_PAIN3;
|
|
//PAIN3 = long, right shoulder
|
|
break;
|
|
case HL_CHEST_LT:
|
|
return BOTH_PAIN2;
|
|
//PAIN2 = long, left shoulder
|
|
break;
|
|
case HL_WAIST:
|
|
case HL_CHEST:
|
|
if ( !Q_irand( 0, 3 ) )
|
|
{
|
|
return BOTH_PAIN6;
|
|
}
|
|
else if ( !Q_irand( 0, 2 ) )
|
|
{
|
|
return BOTH_PAIN8;
|
|
}
|
|
else if ( !Q_irand( 0, 1 ) )
|
|
{
|
|
return BOTH_PAIN17;
|
|
}
|
|
else
|
|
{
|
|
return BOTH_PAIN19;
|
|
}
|
|
//PAIN6 = gut
|
|
//PAIN8 = chest
|
|
//PAIN17 = twitch crotch
|
|
//PAIN19 = med crotch
|
|
break;
|
|
case HL_ARM_RT:
|
|
case HL_HAND_RT:
|
|
return BOTH_PAIN9;
|
|
//PAIN9 = twitch right arm
|
|
break;
|
|
case HL_ARM_LT:
|
|
case HL_HAND_LT:
|
|
return BOTH_PAIN10;
|
|
//PAIN10 = twitch left arm
|
|
break;
|
|
case HL_HEAD:
|
|
return BOTH_PAIN4;
|
|
//PAIN4 = head
|
|
break;
|
|
default:
|
|
return -1;
|
|
break;
|
|
}
|
|
}
|
|
*/
|
|
|
|
void ExplodeDeath( gentity_t *self )
|
|
{
|
|
// gentity_t *tent;
|
|
vec3_t forward;
|
|
|
|
self->takedamage = qfalse;//stop chain reaction runaway loops
|
|
|
|
self->s.loopSound = 0;
|
|
self->s.loopIsSoundset = qfalse;
|
|
|
|
VectorCopy( self->r.currentOrigin, self->s.pos.trBase );
|
|
|
|
// tent = G_TempEntity( self->s.origin, EV_FX_EXPLOSION );
|
|
AngleVectors(self->s.angles, forward, NULL, NULL);
|
|
|
|
/*
|
|
if ( self->fxID > 0 )
|
|
{
|
|
G_PlayEffect( self->fxID, self->r.currentOrigin, forward );
|
|
}
|
|
else
|
|
*/
|
|
|
|
{
|
|
// CG_SurfaceExplosion( self->r.currentOrigin, forward, 20.0f, 12.0f, ((self->spawnflags&4)==qfalse) ); //FIXME: This needs to be consistent to all exploders!
|
|
// G_Sound(self, self->sounds );
|
|
}
|
|
|
|
if(self->splashDamage > 0 && self->splashRadius > 0)
|
|
{
|
|
gentity_t *attacker = self;
|
|
if ( self->parent )
|
|
{
|
|
attacker = self->parent;
|
|
}
|
|
G_RadiusDamage( self->r.currentOrigin, attacker, self->splashDamage, self->splashRadius,
|
|
attacker, NULL, MOD_UNKNOWN );
|
|
}
|
|
|
|
ObjectDie( self, self, self, 20, 0 );
|
|
}
|
|
|
|
|
|
/*
|
|
============
|
|
ScorePlum
|
|
============
|
|
*/
|
|
void ScorePlum( gentity_t *ent, vec3_t origin, int score ) {
|
|
gentity_t *plum;
|
|
|
|
plum = G_TempEntity( origin, EV_SCOREPLUM );
|
|
// only send this temp entity to a single client
|
|
plum->r.svFlags |= SVF_SINGLECLIENT;
|
|
plum->r.singleClient = ent->s.number;
|
|
//
|
|
plum->s.otherEntityNum = ent->s.number;
|
|
plum->s.time = score;
|
|
}
|
|
|
|
/*
|
|
============
|
|
AddScore
|
|
|
|
Adds score to both the client and his team
|
|
============
|
|
*/
|
|
extern qboolean g_dontPenalizeTeam; //g_cmds.c
|
|
void AddScore( gentity_t *ent, vec3_t origin, int score )
|
|
{
|
|
/*
|
|
if (g_gametype.integer == GT_SIEGE)
|
|
{ //no scoring in this gametype at all.
|
|
return;
|
|
}
|
|
*/
|
|
|
|
if ( !ent->client ) {
|
|
return;
|
|
}
|
|
// no scoring during pre-match warmup
|
|
if ( level.warmupTime ) {
|
|
return;
|
|
}
|
|
// show score plum
|
|
//ScorePlum(ent, origin, score);
|
|
//
|
|
ent->client->ps.persistant[PERS_SCORE] += score;
|
|
if ( g_gametype.integer == GT_TEAM && !g_dontPenalizeTeam )
|
|
level.teamScores[ ent->client->ps.persistant[PERS_TEAM] ] += score;
|
|
CalculateRanks();
|
|
}
|
|
|
|
/*
|
|
=================
|
|
TossClientItems
|
|
|
|
rww - Toss the weapon away from the player in the specified direction
|
|
=================
|
|
*/
|
|
void TossClientWeapon(gentity_t *self, vec3_t direction, float speed)
|
|
{
|
|
vec3_t vel;
|
|
gitem_t *item;
|
|
gentity_t *launched;
|
|
int weapon = self->s.weapon;
|
|
int ammoSub;
|
|
|
|
if (g_gametype.integer == GT_SIEGE)
|
|
{ //no dropping weaps
|
|
return;
|
|
}
|
|
|
|
if (weapon <= WP_BRYAR_PISTOL)
|
|
{ //can't have this
|
|
return;
|
|
}
|
|
|
|
if (weapon == WP_EMPLACED_GUN ||
|
|
weapon == WP_TURRET)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// find the item type for this weapon
|
|
item = BG_FindItemForWeapon( weapon );
|
|
|
|
ammoSub = (self->client->ps.ammo[weaponData[weapon].ammoIndex] - bg_itemlist[BG_GetItemIndexByTag(weapon, IT_WEAPON)].quantity);
|
|
|
|
if (ammoSub < 0)
|
|
{
|
|
int ammoQuan = item->quantity;
|
|
ammoQuan -= (-ammoSub);
|
|
|
|
if (ammoQuan <= 0)
|
|
{ //no ammo
|
|
return;
|
|
}
|
|
}
|
|
|
|
vel[0] = direction[0]*speed;
|
|
vel[1] = direction[1]*speed;
|
|
vel[2] = direction[2]*speed;
|
|
|
|
launched = LaunchItem(item, self->client->ps.origin, vel);
|
|
|
|
launched->s.generic1 = self->s.number;
|
|
launched->s.powerups = level.time + 1500;
|
|
|
|
launched->count = bg_itemlist[BG_GetItemIndexByTag(weapon, IT_WEAPON)].quantity;
|
|
|
|
self->client->ps.ammo[weaponData[weapon].ammoIndex] -= bg_itemlist[BG_GetItemIndexByTag(weapon, IT_WEAPON)].quantity;
|
|
|
|
if (self->client->ps.ammo[weaponData[weapon].ammoIndex] < 0)
|
|
{
|
|
launched->count -= (-self->client->ps.ammo[weaponData[weapon].ammoIndex]);
|
|
self->client->ps.ammo[weaponData[weapon].ammoIndex] = 0;
|
|
}
|
|
|
|
if ((self->client->ps.ammo[weaponData[weapon].ammoIndex] < 1 && weapon != WP_DET_PACK) ||
|
|
(weapon != WP_THERMAL && weapon != WP_DET_PACK && weapon != WP_TRIP_MINE))
|
|
{
|
|
int i = 0;
|
|
int weap = -1;
|
|
|
|
self->client->ps.stats[STAT_WEAPONS] &= ~(1 << weapon);
|
|
|
|
while (i < WP_NUM_WEAPONS)
|
|
{
|
|
if ((self->client->ps.stats[STAT_WEAPONS] & (1 << i)) && i != WP_NONE)
|
|
{ //this one's good
|
|
weap = i;
|
|
break;
|
|
}
|
|
i++;
|
|
}
|
|
|
|
if (weap != -1)
|
|
{
|
|
self->s.weapon = weap;
|
|
self->client->ps.weapon = weap;
|
|
}
|
|
else
|
|
{
|
|
self->s.weapon = 0;
|
|
self->client->ps.weapon = 0;
|
|
}
|
|
|
|
G_AddEvent(self, EV_NOAMMO, weapon);
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
TossClientItems
|
|
|
|
Toss the weapon and powerups for the killed player
|
|
=================
|
|
*/
|
|
void TossClientItems( gentity_t *self ) {
|
|
gitem_t *item;
|
|
int weapon;
|
|
float angle;
|
|
int i;
|
|
gentity_t *drop;
|
|
|
|
if (g_gametype.integer == GT_SIEGE)
|
|
{ //just don't drop anything then
|
|
return;
|
|
}
|
|
|
|
// 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 ( weapon == WP_BRYAR_PISTOL) {
|
|
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;
|
|
}
|
|
}
|
|
|
|
self->s.bolt2 = weapon;
|
|
|
|
if ( weapon > WP_BRYAR_PISTOL &&
|
|
weapon != WP_EMPLACED_GUN &&
|
|
weapon != WP_TURRET &&
|
|
self->client->ps.ammo[ weaponData[weapon].ammoIndex ] ) {
|
|
gentity_t *te;
|
|
|
|
// find the item type for this weapon
|
|
item = BG_FindItemForWeapon( weapon );
|
|
|
|
// tell all clients to remove the weapon model on this guy until he respawns
|
|
te = G_TempEntity( vec3_origin, EV_DESTROY_WEAPON_MODEL );
|
|
te->r.svFlags |= SVF_BROADCAST;
|
|
te->s.eventParm = self->s.number;
|
|
|
|
// spawn the item
|
|
Drop_Item( self, item, 0 );
|
|
}
|
|
|
|
// drop all the powerups if not in teamplay
|
|
if ( g_gametype.integer != GT_TEAM && g_gametype.integer != GT_SIEGE ) {
|
|
angle = 45;
|
|
for ( i = 1 ; i < PW_NUM_POWERUPS ; i++ ) {
|
|
if ( self->client->ps.powerups[ i ] > level.time ) {
|
|
item = BG_FindItemForPowerup( i );
|
|
if ( !item ) {
|
|
continue;
|
|
}
|
|
drop = Drop_Item( self, item, angle );
|
|
// decide how many seconds it has left
|
|
drop->count = ( self->client->ps.powerups[ i ] - level.time ) / 1000;
|
|
if ( drop->count < 1 ) {
|
|
drop->count = 1;
|
|
}
|
|
angle += 45;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
==================
|
|
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;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
GibEntity
|
|
==================
|
|
*/
|
|
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;
|
|
}
|
|
|
|
void BodyRid(gentity_t *ent)
|
|
{
|
|
trap_UnlinkEntity( ent );
|
|
ent->physicsObject = qfalse;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
body_die
|
|
==================
|
|
*/
|
|
void body_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath ) {
|
|
// NOTENOTE No gibbing right now, this is star wars.
|
|
qboolean doDisint = qfalse;
|
|
|
|
if (self->s.eType == ET_NPC)
|
|
{ //well, just rem it then, so long as it's done with its death anim and it's not a standard weapon.
|
|
if ( self->client && self->client->ps.torsoTimer <= 0 &&
|
|
(meansOfDeath == MOD_UNKNOWN ||
|
|
meansOfDeath == MOD_WATER ||
|
|
meansOfDeath == MOD_SLIME ||
|
|
meansOfDeath == MOD_LAVA ||
|
|
meansOfDeath == MOD_CRUSH ||
|
|
meansOfDeath == MOD_TELEFRAG ||
|
|
meansOfDeath == MOD_FALLING ||
|
|
meansOfDeath == MOD_SUICIDE ||
|
|
meansOfDeath == MOD_TARGET_LASER ||
|
|
meansOfDeath == MOD_TRIGGER_HURT) )
|
|
{
|
|
self->think = G_FreeEntity;
|
|
self->nextthink = level.time;
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (self->health < (GIB_HEALTH+1))
|
|
{
|
|
self->health = GIB_HEALTH+1;
|
|
|
|
if (self->client && (level.time - self->client->respawnTime) < 2000)
|
|
{
|
|
doDisint = qfalse;
|
|
}
|
|
else
|
|
{
|
|
doDisint = qtrue;
|
|
}
|
|
}
|
|
|
|
if (self->client && (self->client->ps.eFlags & EF_DISINTEGRATION))
|
|
{
|
|
return;
|
|
}
|
|
else if (self->s.eFlags & EF_DISINTEGRATION)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (doDisint)
|
|
{
|
|
if (self->client)
|
|
{
|
|
self->client->ps.eFlags |= EF_DISINTEGRATION;
|
|
VectorCopy(self->client->ps.origin, self->client->ps.lastHitLoc);
|
|
}
|
|
else
|
|
{
|
|
self->s.eFlags |= EF_DISINTEGRATION;
|
|
VectorCopy(self->r.currentOrigin, self->s.origin2);
|
|
|
|
//since it's the corpse entity, tell it to "remove" itself
|
|
self->think = BodyRid;
|
|
self->nextthink = level.time + 1000;
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
// these are just for logging, the client prints its own messages
|
|
char *modNames[MOD_MAX] = {
|
|
"MOD_UNKNOWN",
|
|
"MOD_STUN_BATON",
|
|
"MOD_MELEE",
|
|
"MOD_SABER",
|
|
"MOD_BRYAR_PISTOL",
|
|
"MOD_BRYAR_PISTOL_ALT",
|
|
"MOD_BLASTER",
|
|
"MOD_TURBLAST",
|
|
"MOD_DISRUPTOR",
|
|
"MOD_DISRUPTOR_SPLASH",
|
|
"MOD_DISRUPTOR_SNIPER",
|
|
"MOD_BOWCASTER",
|
|
"MOD_REPEATER",
|
|
"MOD_REPEATER_ALT",
|
|
"MOD_REPEATER_ALT_SPLASH",
|
|
"MOD_DEMP2",
|
|
"MOD_DEMP2_ALT",
|
|
"MOD_FLECHETTE",
|
|
"MOD_FLECHETTE_ALT_SPLASH",
|
|
"MOD_ROCKET",
|
|
"MOD_ROCKET_SPLASH",
|
|
"MOD_ROCKET_HOMING",
|
|
"MOD_ROCKET_HOMING_SPLASH",
|
|
"MOD_THERMAL",
|
|
"MOD_THERMAL_SPLASH",
|
|
"MOD_TRIP_MINE_SPLASH",
|
|
"MOD_TIMED_MINE_SPLASH",
|
|
"MOD_DET_PACK_SPLASH",
|
|
"MOD_VEHICLE",
|
|
"MOD_CONC",
|
|
"MOD_CONC_ALT",
|
|
"MOD_FORCE_DARK",
|
|
"MOD_SENTRY",
|
|
"MOD_WATER",
|
|
"MOD_SLIME",
|
|
"MOD_LAVA",
|
|
"MOD_CRUSH",
|
|
"MOD_TELEFRAG",
|
|
"MOD_FALLING",
|
|
"MOD_SUICIDE",
|
|
"MOD_TARGET_LASER",
|
|
"MOD_TRIGGER_HURT"
|
|
};
|
|
|
|
|
|
/*
|
|
==================
|
|
CheckAlmostCapture
|
|
==================
|
|
*/
|
|
void CheckAlmostCapture( gentity_t *self, gentity_t *attacker ) {
|
|
#if 0
|
|
gentity_t *ent;
|
|
vec3_t dir;
|
|
char *classname;
|
|
|
|
// if this player was carrying a flag
|
|
if ( self->client->ps.powerups[PW_REDFLAG] ||
|
|
self->client->ps.powerups[PW_BLUEFLAG] ||
|
|
self->client->ps.powerups[PW_NEUTRALFLAG] ) {
|
|
// get the goal flag this player should have been going for
|
|
if ( g_gametype.integer == GT_CTF || g_gametype.integer == GT_CTY ) {
|
|
if ( self->client->sess.sessionTeam == TEAM_BLUE ) {
|
|
classname = "team_CTF_blueflag";
|
|
}
|
|
else {
|
|
classname = "team_CTF_redflag";
|
|
}
|
|
}
|
|
else {
|
|
if ( self->client->sess.sessionTeam == TEAM_BLUE ) {
|
|
classname = "team_CTF_redflag";
|
|
}
|
|
else {
|
|
classname = "team_CTF_blueflag";
|
|
}
|
|
}
|
|
ent = NULL;
|
|
do
|
|
{
|
|
ent = G_Find(ent, FOFS(classname), classname);
|
|
} while (ent && (ent->flags & FL_DROPPED_ITEM));
|
|
// if we found the destination flag and it's not picked up
|
|
if (ent && !(ent->r.svFlags & SVF_NOCLIENT) ) {
|
|
// if the player was *very* close
|
|
VectorSubtract( self->client->ps.origin, ent->s.origin, dir );
|
|
if ( VectorLength(dir) < 200 ) {
|
|
self->client->ps.persistant[PERS_PLAYEREVENTS] ^= PLAYEREVENT_HOLYSHIT;
|
|
if ( attacker->client ) {
|
|
attacker->client->ps.persistant[PERS_PLAYEREVENTS] ^= PLAYEREVENT_HOLYSHIT;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
qboolean G_InKnockDown( playerState_t *ps )
|
|
{
|
|
switch ( (ps->legsAnim) )
|
|
{
|
|
case BOTH_KNOCKDOWN1:
|
|
case BOTH_KNOCKDOWN2:
|
|
case BOTH_KNOCKDOWN3:
|
|
case BOTH_KNOCKDOWN4:
|
|
case BOTH_KNOCKDOWN5:
|
|
return qtrue;
|
|
break;
|
|
case BOTH_GETUP1:
|
|
case BOTH_GETUP2:
|
|
case BOTH_GETUP3:
|
|
case BOTH_GETUP4:
|
|
case BOTH_GETUP5:
|
|
case BOTH_FORCE_GETUP_F1:
|
|
case BOTH_FORCE_GETUP_F2:
|
|
case BOTH_FORCE_GETUP_B1:
|
|
case BOTH_FORCE_GETUP_B2:
|
|
case BOTH_FORCE_GETUP_B3:
|
|
case BOTH_FORCE_GETUP_B4:
|
|
case BOTH_FORCE_GETUP_B5:
|
|
return qtrue;
|
|
break;
|
|
}
|
|
return qfalse;
|
|
}
|
|
|
|
static int G_CheckSpecialDeathAnim( gentity_t *self, vec3_t point, int damage, int mod, int hitLoc )
|
|
{
|
|
int deathAnim = -1;
|
|
|
|
if ( BG_InRoll( &self->client->ps, self->client->ps.legsAnim ) )
|
|
{
|
|
deathAnim = BOTH_DEATH_ROLL; //# Death anim from a roll
|
|
}
|
|
else if ( BG_FlippingAnim( self->client->ps.legsAnim ) )
|
|
{
|
|
deathAnim = BOTH_DEATH_FLIP; //# Death anim from a flip
|
|
}
|
|
else if ( G_InKnockDown( &self->client->ps ) )
|
|
{//since these happen a lot, let's handle them case by case
|
|
int animLength = bgAllAnims[self->localAnimIndex].anims[self->client->ps.legsAnim].numFrames * fabs((float)(bgHumanoidAnimations[self->client->ps.legsAnim].frameLerp));
|
|
switch ( self->client->ps.legsAnim )
|
|
{
|
|
case BOTH_KNOCKDOWN1:
|
|
if ( animLength - self->client->ps.legsTimer > 100 )
|
|
{//on our way down
|
|
if ( self->client->ps.legsTimer > 600 )
|
|
{//still partially up
|
|
deathAnim = BOTH_DEATH_FALLING_UP;
|
|
}
|
|
else
|
|
{//down
|
|
deathAnim = BOTH_DEATH_LYING_UP;
|
|
}
|
|
}
|
|
break;
|
|
case BOTH_KNOCKDOWN2:
|
|
if ( animLength - self->client->ps.legsTimer > 700 )
|
|
{//on our way down
|
|
if ( self->client->ps.legsTimer > 600 )
|
|
{//still partially up
|
|
deathAnim = BOTH_DEATH_FALLING_UP;
|
|
}
|
|
else
|
|
{//down
|
|
deathAnim = BOTH_DEATH_LYING_UP;
|
|
}
|
|
}
|
|
break;
|
|
case BOTH_KNOCKDOWN3:
|
|
if ( animLength - self->client->ps.legsTimer > 100 )
|
|
{//on our way down
|
|
if ( self->client->ps.legsTimer > 1300 )
|
|
{//still partially up
|
|
deathAnim = BOTH_DEATH_FALLING_DN;
|
|
}
|
|
else
|
|
{//down
|
|
deathAnim = BOTH_DEATH_LYING_DN;
|
|
}
|
|
}
|
|
break;
|
|
case BOTH_KNOCKDOWN4:
|
|
if ( animLength - self->client->ps.legsTimer > 300 )
|
|
{//on our way down
|
|
if ( self->client->ps.legsTimer > 350 )
|
|
{//still partially up
|
|
deathAnim = BOTH_DEATH_FALLING_UP;
|
|
}
|
|
else
|
|
{//down
|
|
deathAnim = BOTH_DEATH_LYING_UP;
|
|
}
|
|
}
|
|
else
|
|
{//crouch death
|
|
vec3_t fwd;
|
|
float thrown = 0;
|
|
|
|
AngleVectors( self->client->ps.viewangles, fwd, NULL, NULL );
|
|
thrown = DotProduct( fwd, self->client->ps.velocity );
|
|
|
|
if ( thrown < -150 )
|
|
{
|
|
deathAnim = BOTH_DEATHBACKWARD1; //# Death anim when crouched and thrown back
|
|
}
|
|
else
|
|
{
|
|
deathAnim = BOTH_DEATH_CROUCHED; //# Death anim when crouched
|
|
}
|
|
}
|
|
break;
|
|
case BOTH_KNOCKDOWN5:
|
|
if ( self->client->ps.legsTimer < 750 )
|
|
{//flat
|
|
deathAnim = BOTH_DEATH_LYING_DN;
|
|
}
|
|
break;
|
|
case BOTH_GETUP1:
|
|
if ( self->client->ps.legsTimer < 350 )
|
|
{//standing up
|
|
}
|
|
else if ( self->client->ps.legsTimer < 800 )
|
|
{//crouching
|
|
vec3_t fwd;
|
|
float thrown = 0;
|
|
|
|
AngleVectors( self->client->ps.viewangles, fwd, NULL, NULL );
|
|
thrown = DotProduct( fwd, self->client->ps.velocity );
|
|
if ( thrown < -150 )
|
|
{
|
|
deathAnim = BOTH_DEATHBACKWARD1; //# Death anim when crouched and thrown back
|
|
}
|
|
else
|
|
{
|
|
deathAnim = BOTH_DEATH_CROUCHED; //# Death anim when crouched
|
|
}
|
|
}
|
|
else
|
|
{//lying down
|
|
if ( animLength - self->client->ps.legsTimer > 450 )
|
|
{//partially up
|
|
deathAnim = BOTH_DEATH_FALLING_UP;
|
|
}
|
|
else
|
|
{//down
|
|
deathAnim = BOTH_DEATH_LYING_UP;
|
|
}
|
|
}
|
|
break;
|
|
case BOTH_GETUP2:
|
|
if ( self->client->ps.legsTimer < 150 )
|
|
{//standing up
|
|
}
|
|
else if ( self->client->ps.legsTimer < 850 )
|
|
{//crouching
|
|
vec3_t fwd;
|
|
float thrown = 0;
|
|
|
|
AngleVectors( self->client->ps.viewangles, fwd, NULL, NULL );
|
|
thrown = DotProduct( fwd, self->client->ps.velocity );
|
|
|
|
if ( thrown < -150 )
|
|
{
|
|
deathAnim = BOTH_DEATHBACKWARD1; //# Death anim when crouched and thrown back
|
|
}
|
|
else
|
|
{
|
|
deathAnim = BOTH_DEATH_CROUCHED; //# Death anim when crouched
|
|
}
|
|
}
|
|
else
|
|
{//lying down
|
|
if ( animLength - self->client->ps.legsTimer > 500 )
|
|
{//partially up
|
|
deathAnim = BOTH_DEATH_FALLING_UP;
|
|
}
|
|
else
|
|
{//down
|
|
deathAnim = BOTH_DEATH_LYING_UP;
|
|
}
|
|
}
|
|
break;
|
|
case BOTH_GETUP3:
|
|
if ( self->client->ps.legsTimer < 250 )
|
|
{//standing up
|
|
}
|
|
else if ( self->client->ps.legsTimer < 600 )
|
|
{//crouching
|
|
vec3_t fwd;
|
|
float thrown = 0;
|
|
AngleVectors( self->client->ps.viewangles, fwd, NULL, NULL );
|
|
thrown = DotProduct( fwd, self->client->ps.velocity );
|
|
|
|
if ( thrown < -150 )
|
|
{
|
|
deathAnim = BOTH_DEATHBACKWARD1; //# Death anim when crouched and thrown back
|
|
}
|
|
else
|
|
{
|
|
deathAnim = BOTH_DEATH_CROUCHED; //# Death anim when crouched
|
|
}
|
|
}
|
|
else
|
|
{//lying down
|
|
if ( animLength - self->client->ps.legsTimer > 150 )
|
|
{//partially up
|
|
deathAnim = BOTH_DEATH_FALLING_DN;
|
|
}
|
|
else
|
|
{//down
|
|
deathAnim = BOTH_DEATH_LYING_DN;
|
|
}
|
|
}
|
|
break;
|
|
case BOTH_GETUP4:
|
|
if ( self->client->ps.legsTimer < 250 )
|
|
{//standing up
|
|
}
|
|
else if ( self->client->ps.legsTimer < 600 )
|
|
{//crouching
|
|
vec3_t fwd;
|
|
float thrown = 0;
|
|
|
|
AngleVectors( self->client->ps.viewangles, fwd, NULL, NULL );
|
|
thrown = DotProduct( fwd, self->client->ps.velocity );
|
|
|
|
if ( thrown < -150 )
|
|
{
|
|
deathAnim = BOTH_DEATHBACKWARD1; //# Death anim when crouched and thrown back
|
|
}
|
|
else
|
|
{
|
|
deathAnim = BOTH_DEATH_CROUCHED; //# Death anim when crouched
|
|
}
|
|
}
|
|
else
|
|
{//lying down
|
|
if ( animLength - self->client->ps.legsTimer > 850 )
|
|
{//partially up
|
|
deathAnim = BOTH_DEATH_FALLING_DN;
|
|
}
|
|
else
|
|
{//down
|
|
deathAnim = BOTH_DEATH_LYING_UP;
|
|
}
|
|
}
|
|
break;
|
|
case BOTH_GETUP5:
|
|
if ( self->client->ps.legsTimer > 850 )
|
|
{//lying down
|
|
if ( animLength - self->client->ps.legsTimer > 1500 )
|
|
{//partially up
|
|
deathAnim = BOTH_DEATH_FALLING_DN;
|
|
}
|
|
else
|
|
{//down
|
|
deathAnim = BOTH_DEATH_LYING_DN;
|
|
}
|
|
}
|
|
break;
|
|
case BOTH_GETUP_CROUCH_B1:
|
|
if ( self->client->ps.legsTimer < 800 )
|
|
{//crouching
|
|
vec3_t fwd;
|
|
float thrown = 0;
|
|
|
|
AngleVectors( self->client->ps.viewangles, fwd, NULL, NULL );
|
|
thrown = DotProduct( fwd, self->client->ps.velocity );
|
|
|
|
if ( thrown < -150 )
|
|
{
|
|
deathAnim = BOTH_DEATHBACKWARD1; //# Death anim when crouched and thrown back
|
|
}
|
|
else
|
|
{
|
|
deathAnim = BOTH_DEATH_CROUCHED; //# Death anim when crouched
|
|
}
|
|
}
|
|
else
|
|
{//lying down
|
|
if ( animLength - self->client->ps.legsTimer > 400 )
|
|
{//partially up
|
|
deathAnim = BOTH_DEATH_FALLING_UP;
|
|
}
|
|
else
|
|
{//down
|
|
deathAnim = BOTH_DEATH_LYING_UP;
|
|
}
|
|
}
|
|
break;
|
|
case BOTH_GETUP_CROUCH_F1:
|
|
if ( self->client->ps.legsTimer < 800 )
|
|
{//crouching
|
|
vec3_t fwd;
|
|
float thrown = 0;
|
|
|
|
AngleVectors( self->client->ps.viewangles, fwd, NULL, NULL );
|
|
thrown = DotProduct( fwd, self->client->ps.velocity );
|
|
|
|
if ( thrown < -150 )
|
|
{
|
|
deathAnim = BOTH_DEATHBACKWARD1; //# Death anim when crouched and thrown back
|
|
}
|
|
else
|
|
{
|
|
deathAnim = BOTH_DEATH_CROUCHED; //# Death anim when crouched
|
|
}
|
|
}
|
|
else
|
|
{//lying down
|
|
if ( animLength - self->client->ps.legsTimer > 150 )
|
|
{//partially up
|
|
deathAnim = BOTH_DEATH_FALLING_DN;
|
|
}
|
|
else
|
|
{//down
|
|
deathAnim = BOTH_DEATH_LYING_DN;
|
|
}
|
|
}
|
|
break;
|
|
case BOTH_FORCE_GETUP_B1:
|
|
if ( self->client->ps.legsTimer < 325 )
|
|
{//standing up
|
|
}
|
|
else if ( self->client->ps.legsTimer < 725 )
|
|
{//spinning up
|
|
deathAnim = BOTH_DEATH_SPIN_180; //# Death anim when facing backwards
|
|
}
|
|
else if ( self->client->ps.legsTimer < 900 )
|
|
{//crouching
|
|
vec3_t fwd;
|
|
float thrown = 0;
|
|
|
|
AngleVectors( self->client->ps.viewangles, fwd, NULL, NULL );
|
|
thrown = DotProduct( fwd, self->client->ps.velocity );
|
|
|
|
if ( thrown < -150 )
|
|
{
|
|
deathAnim = BOTH_DEATHBACKWARD1; //# Death anim when crouched and thrown back
|
|
}
|
|
else
|
|
{
|
|
deathAnim = BOTH_DEATH_CROUCHED; //# Death anim when crouched
|
|
}
|
|
}
|
|
else
|
|
{//lying down
|
|
if ( animLength - self->client->ps.legsTimer > 50 )
|
|
{//partially up
|
|
deathAnim = BOTH_DEATH_FALLING_UP;
|
|
}
|
|
else
|
|
{//down
|
|
deathAnim = BOTH_DEATH_LYING_UP;
|
|
}
|
|
}
|
|
break;
|
|
case BOTH_FORCE_GETUP_B2:
|
|
if ( self->client->ps.legsTimer < 575 )
|
|
{//standing up
|
|
}
|
|
else if ( self->client->ps.legsTimer < 875 )
|
|
{//spinning up
|
|
deathAnim = BOTH_DEATH_SPIN_180; //# Death anim when facing backwards
|
|
}
|
|
else if ( self->client->ps.legsTimer < 900 )
|
|
{//crouching
|
|
vec3_t fwd;
|
|
float thrown = 0;
|
|
|
|
AngleVectors( self->client->ps.viewangles, fwd, NULL, NULL );
|
|
thrown = DotProduct( fwd, self->client->ps.velocity );
|
|
|
|
if ( thrown < -150 )
|
|
{
|
|
deathAnim = BOTH_DEATHBACKWARD1; //# Death anim when crouched and thrown back
|
|
}
|
|
else
|
|
{
|
|
deathAnim = BOTH_DEATH_CROUCHED; //# Death anim when crouched
|
|
}
|
|
}
|
|
else
|
|
{//lying down
|
|
//partially up
|
|
deathAnim = BOTH_DEATH_FALLING_UP;
|
|
}
|
|
break;
|
|
case BOTH_FORCE_GETUP_B3:
|
|
if ( self->client->ps.legsTimer < 150 )
|
|
{//standing up
|
|
}
|
|
else if ( self->client->ps.legsTimer < 775 )
|
|
{//flipping
|
|
deathAnim = BOTH_DEATHBACKWARD2; //backflip
|
|
}
|
|
else
|
|
{//lying down
|
|
//partially up
|
|
deathAnim = BOTH_DEATH_FALLING_UP;
|
|
}
|
|
break;
|
|
case BOTH_FORCE_GETUP_B4:
|
|
if ( self->client->ps.legsTimer < 325 )
|
|
{//standing up
|
|
}
|
|
else
|
|
{//lying down
|
|
if ( animLength - self->client->ps.legsTimer > 150 )
|
|
{//partially up
|
|
deathAnim = BOTH_DEATH_FALLING_UP;
|
|
}
|
|
else
|
|
{//down
|
|
deathAnim = BOTH_DEATH_LYING_UP;
|
|
}
|
|
}
|
|
break;
|
|
case BOTH_FORCE_GETUP_B5:
|
|
if ( self->client->ps.legsTimer < 550 )
|
|
{//standing up
|
|
}
|
|
else if ( self->client->ps.legsTimer < 1025 )
|
|
{//kicking up
|
|
deathAnim = BOTH_DEATHBACKWARD2; //backflip
|
|
}
|
|
else
|
|
{//lying down
|
|
if ( animLength - self->client->ps.legsTimer > 50 )
|
|
{//partially up
|
|
deathAnim = BOTH_DEATH_FALLING_UP;
|
|
}
|
|
else
|
|
{//down
|
|
deathAnim = BOTH_DEATH_LYING_UP;
|
|
}
|
|
}
|
|
break;
|
|
case BOTH_FORCE_GETUP_B6:
|
|
if ( self->client->ps.legsTimer < 225 )
|
|
{//standing up
|
|
}
|
|
else if ( self->client->ps.legsTimer < 425 )
|
|
{//crouching up
|
|
vec3_t fwd;
|
|
float thrown = 0;
|
|
|
|
AngleVectors( self->client->ps.viewangles, fwd, NULL, NULL );
|
|
thrown = DotProduct( fwd, self->client->ps.velocity );
|
|
|
|
if ( thrown < -150 )
|
|
{
|
|
deathAnim = BOTH_DEATHBACKWARD1; //# Death anim when crouched and thrown back
|
|
}
|
|
else
|
|
{
|
|
deathAnim = BOTH_DEATH_CROUCHED; //# Death anim when crouched
|
|
}
|
|
}
|
|
else if ( self->client->ps.legsTimer < 825 )
|
|
{//flipping up
|
|
deathAnim = BOTH_DEATHFORWARD3; //backflip
|
|
}
|
|
else
|
|
{//lying down
|
|
if ( animLength - self->client->ps.legsTimer > 225 )
|
|
{//partially up
|
|
deathAnim = BOTH_DEATH_FALLING_UP;
|
|
}
|
|
else
|
|
{//down
|
|
deathAnim = BOTH_DEATH_LYING_UP;
|
|
}
|
|
}
|
|
break;
|
|
case BOTH_FORCE_GETUP_F1:
|
|
if ( self->client->ps.legsTimer < 275 )
|
|
{//standing up
|
|
}
|
|
else if ( self->client->ps.legsTimer < 750 )
|
|
{//flipping
|
|
deathAnim = BOTH_DEATH14;
|
|
}
|
|
else
|
|
{//lying down
|
|
if ( animLength - self->client->ps.legsTimer > 100 )
|
|
{//partially up
|
|
deathAnim = BOTH_DEATH_FALLING_DN;
|
|
}
|
|
else
|
|
{//down
|
|
deathAnim = BOTH_DEATH_LYING_DN;
|
|
}
|
|
}
|
|
break;
|
|
case BOTH_FORCE_GETUP_F2:
|
|
if ( self->client->ps.legsTimer < 1200 )
|
|
{//standing
|
|
}
|
|
else
|
|
{//lying down
|
|
if ( animLength - self->client->ps.legsTimer > 225 )
|
|
{//partially up
|
|
deathAnim = BOTH_DEATH_FALLING_DN;
|
|
}
|
|
else
|
|
{//down
|
|
deathAnim = BOTH_DEATH_LYING_DN;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
return deathAnim;
|
|
}
|
|
|
|
int G_PickDeathAnim( gentity_t *self, vec3_t point, int damage, int mod, int hitLoc )
|
|
{//FIXME: play dead flop anims on body if in an appropriate _DEAD anim when this func is called
|
|
int deathAnim = -1;
|
|
int max_health;
|
|
int legAnim = 0;
|
|
vec3_t objVelocity;
|
|
|
|
if (!self || !self->client)
|
|
{
|
|
if (!self || self->s.eType != ET_NPC)
|
|
{ //g2animent
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (self->client)
|
|
{
|
|
max_health = self->client->ps.stats[STAT_MAX_HEALTH];
|
|
|
|
if (self->client->inSpaceIndex && self->client->inSpaceIndex != ENTITYNUM_NONE)
|
|
{
|
|
return BOTH_CHOKE3;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
max_health = 60;
|
|
}
|
|
|
|
if (self->client)
|
|
{
|
|
VectorCopy(self->client->ps.velocity, objVelocity);
|
|
}
|
|
else
|
|
{
|
|
VectorCopy(self->s.pos.trDelta, objVelocity);
|
|
}
|
|
|
|
if ( hitLoc == HL_NONE )
|
|
{
|
|
hitLoc = G_GetHitLocation( self, point );//self->hitLoc
|
|
}
|
|
|
|
if (self->client)
|
|
{
|
|
legAnim = self->client->ps.legsAnim;
|
|
}
|
|
else
|
|
{
|
|
legAnim = self->s.legsAnim;
|
|
}
|
|
|
|
if (gGAvoidDismember)
|
|
{
|
|
return BOTH_RIGHTHANDCHOPPEDOFF;
|
|
}
|
|
|
|
//dead flops
|
|
switch( legAnim )
|
|
{
|
|
case BOTH_DEATH1: //# First Death anim
|
|
case BOTH_DEAD1:
|
|
case BOTH_DEATH2: //# Second Death anim
|
|
case BOTH_DEAD2:
|
|
case BOTH_DEATH8: //#
|
|
case BOTH_DEAD8:
|
|
case BOTH_DEATH13: //#
|
|
case BOTH_DEAD13:
|
|
case BOTH_DEATH14: //#
|
|
case BOTH_DEAD14:
|
|
case BOTH_DEATH16: //#
|
|
case BOTH_DEAD16:
|
|
case BOTH_DEADBACKWARD1: //# First thrown backward death finished pose
|
|
case BOTH_DEADBACKWARD2: //# Second thrown backward death finished pose
|
|
deathAnim = -2;
|
|
/*
|
|
if ( PM_FinishedCurrentLegsAnim( self ) )
|
|
{//done with the anim
|
|
deathAnim = BOTH_DEADFLOP2;
|
|
}
|
|
else
|
|
{
|
|
deathAnim = -2;
|
|
}
|
|
break;
|
|
case BOTH_DEADFLOP2:
|
|
deathAnim = BOTH_DEADFLOP2;
|
|
break;
|
|
*/
|
|
case BOTH_DEATH10: //#
|
|
case BOTH_DEAD10:
|
|
case BOTH_DEATH15: //#
|
|
case BOTH_DEAD15:
|
|
case BOTH_DEADFORWARD1: //# First thrown forward death finished pose
|
|
case BOTH_DEADFORWARD2: //# Second thrown forward death finished pose
|
|
deathAnim = -2;
|
|
/*
|
|
if ( PM_FinishedCurrentLegsAnim( self ) )
|
|
{//done with the anim
|
|
deathAnim = BOTH_DEADFLOP1;
|
|
}
|
|
else
|
|
{
|
|
deathAnim = -2;
|
|
}
|
|
break;
|
|
*/
|
|
case BOTH_DEADFLOP1:
|
|
deathAnim = -2;
|
|
//deathAnim = BOTH_DEADFLOP1;
|
|
break;
|
|
case BOTH_DEAD3: //# Third Death finished pose
|
|
case BOTH_DEAD4: //# Fourth Death finished pose
|
|
case BOTH_DEAD5: //# Fifth Death finished pose
|
|
case BOTH_DEAD6: //# Sixth Death finished pose
|
|
case BOTH_DEAD7: //# Seventh Death finished pose
|
|
case BOTH_DEAD9: //#
|
|
case BOTH_DEAD11: //#
|
|
case BOTH_DEAD12: //#
|
|
case BOTH_DEAD17: //#
|
|
case BOTH_DEAD18: //#
|
|
case BOTH_DEAD19: //#
|
|
case BOTH_LYINGDEAD1: //# Killed lying down death finished pose
|
|
case BOTH_STUMBLEDEAD1: //# Stumble forward death finished pose
|
|
case BOTH_FALLDEAD1LAND: //# Fall forward and splat death finished pose
|
|
case BOTH_DEATH3: //# Third Death anim
|
|
case BOTH_DEATH4: //# Fourth Death anim
|
|
case BOTH_DEATH5: //# Fifth Death anim
|
|
case BOTH_DEATH6: //# Sixth Death anim
|
|
case BOTH_DEATH7: //# Seventh Death anim
|
|
case BOTH_DEATH9: //#
|
|
case BOTH_DEATH11: //#
|
|
case BOTH_DEATH12: //#
|
|
case BOTH_DEATH17: //#
|
|
case BOTH_DEATH18: //#
|
|
case BOTH_DEATH19: //#
|
|
case BOTH_DEATHFORWARD1: //# First Death in which they get thrown forward
|
|
case BOTH_DEATHFORWARD2: //# Second Death in which they get thrown forward
|
|
case BOTH_DEATHBACKWARD1: //# First Death in which they get thrown backward
|
|
case BOTH_DEATHBACKWARD2: //# Second Death in which they get thrown backward
|
|
case BOTH_DEATH1IDLE: //# Idle while close to death
|
|
case BOTH_LYINGDEATH1: //# Death to play when killed lying down
|
|
case BOTH_STUMBLEDEATH1: //# Stumble forward and fall face first death
|
|
case BOTH_FALLDEATH1: //# Fall forward off a high cliff and splat death - start
|
|
case BOTH_FALLDEATH1INAIR: //# Fall forward off a high cliff and splat death - loop
|
|
case BOTH_FALLDEATH1LAND: //# Fall forward off a high cliff and splat death - hit bottom
|
|
deathAnim = -2;
|
|
break;
|
|
}
|
|
if ( deathAnim == -1 )
|
|
{
|
|
if (self->client)
|
|
{
|
|
deathAnim = G_CheckSpecialDeathAnim( self, point, damage, mod, hitLoc );
|
|
}
|
|
|
|
if (deathAnim == -1)
|
|
{
|
|
//death anims
|
|
switch( hitLoc )
|
|
{
|
|
case HL_FOOT_RT:
|
|
case HL_FOOT_LT:
|
|
if ( mod == MOD_SABER && !Q_irand( 0, 2 ) )
|
|
{
|
|
return BOTH_DEATH10;//chest: back flip
|
|
}
|
|
else if ( !Q_irand( 0, 2 ) )
|
|
{
|
|
deathAnim = BOTH_DEATH4;//back: forward
|
|
}
|
|
else if ( !Q_irand( 0, 1 ) )
|
|
{
|
|
deathAnim = BOTH_DEATH5;//same as 4
|
|
}
|
|
else
|
|
{
|
|
deathAnim = BOTH_DEATH15;//back: forward
|
|
}
|
|
break;
|
|
case HL_LEG_RT:
|
|
if ( !Q_irand( 0, 2 ) )
|
|
{
|
|
deathAnim = BOTH_DEATH4;//back: forward
|
|
}
|
|
else if ( !Q_irand( 0, 1 ) )
|
|
{
|
|
deathAnim = BOTH_DEATH5;//same as 4
|
|
}
|
|
else
|
|
{
|
|
deathAnim = BOTH_DEATH15;//back: forward
|
|
}
|
|
break;
|
|
case HL_LEG_LT:
|
|
if ( !Q_irand( 0, 2 ) )
|
|
{
|
|
deathAnim = BOTH_DEATH4;//back: forward
|
|
}
|
|
else if ( !Q_irand( 0, 1 ) )
|
|
{
|
|
deathAnim = BOTH_DEATH5;//same as 4
|
|
}
|
|
else
|
|
{
|
|
deathAnim = BOTH_DEATH15;//back: forward
|
|
}
|
|
break;
|
|
case HL_BACK:
|
|
if ( !VectorLengthSquared( objVelocity ) )
|
|
{
|
|
deathAnim = BOTH_DEATH17;//head/back: croak
|
|
}
|
|
else
|
|
{
|
|
if ( !Q_irand( 0, 2 ) )
|
|
{
|
|
deathAnim = BOTH_DEATH4;//back: forward
|
|
}
|
|
else if ( !Q_irand( 0, 1 ) )
|
|
{
|
|
deathAnim = BOTH_DEATH5;//same as 4
|
|
}
|
|
else
|
|
{
|
|
deathAnim = BOTH_DEATH15;//back: forward
|
|
}
|
|
}
|
|
break;
|
|
case HL_CHEST_RT:
|
|
case HL_ARM_RT:
|
|
case HL_HAND_RT:
|
|
case HL_BACK_RT:
|
|
if ( damage <= max_health*0.25 )
|
|
{
|
|
deathAnim = BOTH_DEATH9;//chest right: snap, fall forward
|
|
}
|
|
else if ( damage <= max_health*0.5 )
|
|
{
|
|
deathAnim = BOTH_DEATH3;//chest right: back
|
|
}
|
|
else if ( damage <= max_health*0.75 )
|
|
{
|
|
deathAnim = BOTH_DEATH6;//chest right: spin
|
|
}
|
|
else
|
|
{
|
|
//TEMP HACK: play spinny deaths less often
|
|
if ( Q_irand( 0, 1 ) )
|
|
{
|
|
deathAnim = BOTH_DEATH8;//chest right: spin high
|
|
}
|
|
else
|
|
{
|
|
switch ( Q_irand( 0, 2 ) )
|
|
{
|
|
default:
|
|
case 0:
|
|
deathAnim = BOTH_DEATH9;//chest right: snap, fall forward
|
|
break;
|
|
case 1:
|
|
deathAnim = BOTH_DEATH3;//chest right: back
|
|
break;
|
|
case 2:
|
|
deathAnim = BOTH_DEATH6;//chest right: spin
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case HL_CHEST_LT:
|
|
case HL_ARM_LT:
|
|
case HL_HAND_LT:
|
|
case HL_BACK_LT:
|
|
if ( damage <= max_health*0.25 )
|
|
{
|
|
deathAnim = BOTH_DEATH11;//chest left: snap, fall forward
|
|
}
|
|
else if ( damage <= max_health*0.5 )
|
|
{
|
|
deathAnim = BOTH_DEATH7;//chest left: back
|
|
}
|
|
else if ( damage <= max_health*0.75 )
|
|
{
|
|
deathAnim = BOTH_DEATH12;//chest left: spin
|
|
}
|
|
else
|
|
{
|
|
//TEMP HACK: play spinny deaths less often
|
|
if ( Q_irand( 0, 1 ) )
|
|
{
|
|
deathAnim = BOTH_DEATH14;//chest left: spin high
|
|
}
|
|
else
|
|
{
|
|
switch ( Q_irand( 0, 2 ) )
|
|
{
|
|
default:
|
|
case 0:
|
|
deathAnim = BOTH_DEATH11;//chest left: snap, fall forward
|
|
break;
|
|
case 1:
|
|
deathAnim = BOTH_DEATH7;//chest left: back
|
|
break;
|
|
case 2:
|
|
deathAnim = BOTH_DEATH12;//chest left: spin
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case HL_CHEST:
|
|
case HL_WAIST:
|
|
if ( damage <= max_health*0.25 || !VectorLengthSquared( objVelocity ) )
|
|
{
|
|
if ( !Q_irand( 0, 1 ) )
|
|
{
|
|
deathAnim = BOTH_DEATH18;//gut: fall right
|
|
}
|
|
else
|
|
{
|
|
deathAnim = BOTH_DEATH19;//gut: fall left
|
|
}
|
|
}
|
|
else if ( damage <= max_health*0.5 )
|
|
{
|
|
deathAnim = BOTH_DEATH2;//chest: backward short
|
|
}
|
|
else if ( damage <= max_health*0.75 )
|
|
{
|
|
if ( !Q_irand( 0, 1 ) )
|
|
{
|
|
deathAnim = BOTH_DEATH1;//chest: backward med
|
|
}
|
|
else
|
|
{
|
|
deathAnim = BOTH_DEATH16;//same as 1
|
|
}
|
|
}
|
|
else
|
|
{
|
|
deathAnim = BOTH_DEATH10;//chest: back flip
|
|
}
|
|
break;
|
|
case HL_HEAD:
|
|
if ( damage <= max_health*0.5 )
|
|
{
|
|
deathAnim = BOTH_DEATH17;//head/back: croak
|
|
}
|
|
else
|
|
{
|
|
deathAnim = BOTH_DEATH13;//head: stumble, fall back
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Validate.....
|
|
if ( deathAnim == -1 || !BG_HasAnimation( self->localAnimIndex, deathAnim ))
|
|
{
|
|
// I guess we'll take what we can get.....
|
|
deathAnim = BG_PickAnim( self->localAnimIndex, BOTH_DEATH1, BOTH_DEATH25 );
|
|
}
|
|
|
|
return deathAnim;
|
|
}
|
|
|
|
gentity_t *G_GetJediMaster(void)
|
|
{
|
|
int i = 0;
|
|
gentity_t *ent;
|
|
|
|
while (i < MAX_CLIENTS)
|
|
{
|
|
ent = &g_entities[i];
|
|
|
|
if (ent && ent->inuse && ent->client && ent->client->ps.isJediMaster)
|
|
{
|
|
return ent;
|
|
}
|
|
|
|
i++;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
-------------------------
|
|
G_AlertTeam
|
|
-------------------------
|
|
*/
|
|
|
|
void G_AlertTeam( gentity_t *victim, gentity_t *attacker, float radius, float soundDist )
|
|
{
|
|
int radiusEnts[ 128 ];
|
|
gentity_t *check;
|
|
vec3_t mins, maxs;
|
|
int numEnts;
|
|
int i;
|
|
float distSq, sndDistSq = (soundDist*soundDist);
|
|
|
|
if ( attacker == NULL || attacker->client == NULL )
|
|
return;
|
|
|
|
//Setup the bbox to search in
|
|
for ( i = 0; i < 3; i++ )
|
|
{
|
|
mins[i] = victim->r.currentOrigin[i] - radius;
|
|
maxs[i] = victim->r.currentOrigin[i] + radius;
|
|
}
|
|
|
|
//Get the number of entities in a given space
|
|
numEnts = trap_EntitiesInBox( mins, maxs, radiusEnts, 128 );
|
|
|
|
//Cull this list
|
|
for ( i = 0; i < numEnts; i++ )
|
|
{
|
|
check = &g_entities[radiusEnts[i]];
|
|
|
|
//Validate clients
|
|
if ( check->client == NULL )
|
|
continue;
|
|
|
|
//only want NPCs
|
|
if ( check->NPC == NULL )
|
|
continue;
|
|
|
|
//Don't bother if they're ignoring enemies
|
|
// if ( check->svFlags & SVF_IGNORE_ENEMIES )
|
|
// continue;
|
|
|
|
//This NPC specifically flagged to ignore alerts
|
|
if ( check->NPC->scriptFlags & SCF_IGNORE_ALERTS )
|
|
continue;
|
|
|
|
//This NPC specifically flagged to ignore alerts
|
|
if ( !(check->NPC->scriptFlags&SCF_LOOK_FOR_ENEMIES) )
|
|
continue;
|
|
|
|
//this ent does not participate in group AI
|
|
if ( (check->NPC->scriptFlags&SCF_NO_GROUPS) )
|
|
continue;
|
|
|
|
//Skip the requested avoid check if present
|
|
if ( check == victim )
|
|
continue;
|
|
|
|
//Skip the attacker
|
|
if ( check == attacker )
|
|
continue;
|
|
|
|
//Must be on the same team
|
|
if ( check->client->playerTeam != victim->client->playerTeam )
|
|
continue;
|
|
|
|
//Must be alive
|
|
if ( check->health <= 0 )
|
|
continue;
|
|
|
|
if ( check->enemy == NULL )
|
|
{//only do this if they're not already mad at someone
|
|
distSq = DistanceSquared( check->r.currentOrigin, victim->r.currentOrigin );
|
|
if ( distSq > 16384 /*128 squared*/ && !trap_InPVS( victim->r.currentOrigin, check->r.currentOrigin ) )
|
|
{//not even potentially visible/hearable
|
|
continue;
|
|
}
|
|
//NOTE: this allows sound alerts to still go through doors/PVS if the teammate is within 128 of the victim...
|
|
if ( soundDist <= 0 || distSq > sndDistSq )
|
|
{//out of sound range
|
|
if ( !InFOV( victim, check, check->NPC->stats.hfov, check->NPC->stats.vfov )
|
|
|| !NPC_ClearLOS2( check, victim->r.currentOrigin ) )
|
|
{//out of FOV or no LOS
|
|
continue;
|
|
}
|
|
}
|
|
|
|
//FIXME: This can have a nasty cascading effect if setup wrong...
|
|
G_SetEnemy( check, attacker );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
-------------------------
|
|
G_DeathAlert
|
|
-------------------------
|
|
*/
|
|
|
|
#define DEATH_ALERT_RADIUS 512
|
|
#define DEATH_ALERT_SOUND_RADIUS 512
|
|
|
|
void G_DeathAlert( gentity_t *victim, gentity_t *attacker )
|
|
{//FIXME: with all the other alert stuff, do we really need this?
|
|
G_AlertTeam( victim, attacker, DEATH_ALERT_RADIUS, DEATH_ALERT_SOUND_RADIUS );
|
|
}
|
|
|
|
/*
|
|
----------------------------------------
|
|
DeathFX
|
|
|
|
Applies appropriate special effects that occur while the entity is dying
|
|
Not to be confused with NPC_RemoveBodyEffects (NPC.cpp), which only applies effect when removing the body
|
|
----------------------------------------
|
|
*/
|
|
|
|
void DeathFX( gentity_t *ent )
|
|
{
|
|
vec3_t effectPos, right;
|
|
vec3_t defaultDir;
|
|
|
|
if ( !ent || !ent->client )
|
|
return;
|
|
|
|
VectorSet(defaultDir, 0, 0, 1);
|
|
|
|
// team no longer indicates species/race. NPC_class should be used to identify certain npc types
|
|
switch(ent->client->NPC_class)
|
|
{
|
|
case CLASS_MOUSE:
|
|
VectorCopy( ent->r.currentOrigin, effectPos );
|
|
effectPos[2] -= 20;
|
|
G_PlayEffectID( G_EffectIndex("env/small_explode"), effectPos, defaultDir );
|
|
G_Sound( ent, CHAN_AUTO, G_SoundIndex("sound/chars/mouse/misc/death1") );
|
|
break;
|
|
|
|
case CLASS_PROBE:
|
|
VectorCopy( ent->r.currentOrigin, effectPos );
|
|
effectPos[2] += 50;
|
|
G_PlayEffectID( G_EffectIndex("explosions/probeexplosion1"), effectPos, defaultDir );
|
|
break;
|
|
|
|
case CLASS_ATST:
|
|
AngleVectors( ent->r.currentAngles, NULL, right, NULL );
|
|
VectorMA( ent->r.currentOrigin, 20, right, effectPos );
|
|
effectPos[2] += 180;
|
|
G_PlayEffectID( G_EffectIndex("explosions/droidexplosion1"), effectPos, defaultDir );
|
|
VectorMA( effectPos, -40, right, effectPos );
|
|
G_PlayEffectID( G_EffectIndex("explosions/droidexplosion1"), effectPos, defaultDir );
|
|
break;
|
|
|
|
case CLASS_SEEKER:
|
|
case CLASS_REMOTE:
|
|
G_PlayEffectID( G_EffectIndex("env/small_explode"), ent->r.currentOrigin, defaultDir );
|
|
break;
|
|
|
|
case CLASS_GONK:
|
|
VectorCopy( ent->r.currentOrigin, effectPos );
|
|
effectPos[2] -= 5;
|
|
// statusTextIndex = Q_irand( IGT_RESISTANCEISFUTILE, IGT_NAMEIS8OF12 );
|
|
G_Sound( ent, CHAN_AUTO, G_SoundIndex(va("sound/chars/gonk/misc/death%d.wav",Q_irand( 1, 3 ))) );
|
|
G_PlayEffectID( G_EffectIndex("env/med_explode"), effectPos, defaultDir );
|
|
break;
|
|
|
|
// should list all remaining droids here, hope I didn't miss any
|
|
case CLASS_R2D2:
|
|
VectorCopy( ent->r.currentOrigin, effectPos );
|
|
effectPos[2] -= 10;
|
|
G_PlayEffectID( G_EffectIndex("env/med_explode"), effectPos, defaultDir );
|
|
G_Sound( ent, CHAN_AUTO, G_SoundIndex("sound/chars/mark2/misc/mark2_explo") );
|
|
break;
|
|
|
|
case CLASS_PROTOCOL: //c3p0
|
|
case CLASS_R5D2:
|
|
VectorCopy( ent->r.currentOrigin, effectPos );
|
|
effectPos[2] -= 10;
|
|
G_PlayEffectID( G_EffectIndex("env/med_explode"), effectPos, defaultDir );
|
|
G_Sound( ent, CHAN_AUTO, G_SoundIndex("sound/chars/mark2/misc/mark2_explo") );
|
|
break;
|
|
|
|
case CLASS_MARK2:
|
|
VectorCopy( ent->r.currentOrigin, effectPos );
|
|
effectPos[2] -= 15;
|
|
G_PlayEffectID( G_EffectIndex("explosions/droidexplosion1"), effectPos, defaultDir );
|
|
G_Sound( ent, CHAN_AUTO, G_SoundIndex("sound/chars/mark2/misc/mark2_explo") );
|
|
break;
|
|
|
|
case CLASS_INTERROGATOR:
|
|
VectorCopy( ent->r.currentOrigin, effectPos );
|
|
effectPos[2] -= 15;
|
|
G_PlayEffectID( G_EffectIndex("explosions/droidexplosion1"), effectPos, defaultDir );
|
|
G_Sound( ent, CHAN_AUTO, G_SoundIndex("sound/chars/interrogator/misc/int_droid_explo") );
|
|
break;
|
|
|
|
case CLASS_MARK1:
|
|
AngleVectors( ent->r.currentAngles, NULL, right, NULL );
|
|
VectorMA( ent->r.currentOrigin, 10, right, effectPos );
|
|
effectPos[2] -= 15;
|
|
G_PlayEffectID( G_EffectIndex("explosions/droidexplosion1"), effectPos, defaultDir );
|
|
VectorMA( effectPos, -20, right, effectPos );
|
|
G_PlayEffectID( G_EffectIndex("explosions/droidexplosion1"), effectPos, defaultDir );
|
|
VectorMA( effectPos, -20, right, effectPos );
|
|
G_PlayEffectID( G_EffectIndex("explosions/droidexplosion1"), effectPos, defaultDir );
|
|
G_Sound( ent, CHAN_AUTO, G_SoundIndex("sound/chars/mark1/misc/mark1_explo") );
|
|
break;
|
|
|
|
case CLASS_SENTRY:
|
|
G_Sound( ent, CHAN_AUTO, G_SoundIndex("sound/chars/sentry/misc/sentry_explo") );
|
|
VectorCopy( ent->r.currentOrigin, effectPos );
|
|
G_PlayEffectID( G_EffectIndex("env/med_explode"), effectPos, defaultDir );
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
void G_CheckVictoryScript(gentity_t *self)
|
|
{
|
|
if ( !G_ActivateBehavior( self, BSET_VICTORY ) )
|
|
{
|
|
if ( self->NPC && self->s.weapon == WP_SABER )
|
|
{//Jedi taunt from within their AI
|
|
self->NPC->blockedSpeechDebounceTime = 0;//get them ready to taunt
|
|
return;
|
|
}
|
|
if ( self->client && self->client->NPC_class == CLASS_GALAKMECH )
|
|
{
|
|
self->wait = 1;
|
|
TIMER_Set( self, "gloatTime", Q_irand( 5000, 8000 ) );
|
|
self->NPC->blockedSpeechDebounceTime = 0;//get him ready to taunt
|
|
return;
|
|
}
|
|
//FIXME: any way to not say this *right away*? Wait for victim's death anim/scream to finish?
|
|
if ( self->NPC && self->NPC->group && self->NPC->group->commander && self->NPC->group->commander->NPC && self->NPC->group->commander->NPC->rank > self->NPC->rank && !Q_irand( 0, 2 ) )
|
|
{//sometimes have the group commander speak instead
|
|
self->NPC->group->commander->NPC->greetingDebounceTime = level.time + Q_irand( 2000, 5000 );
|
|
//G_AddVoiceEvent( self->NPC->group->commander, Q_irand(EV_VICTORY1, EV_VICTORY3), 2000 );
|
|
}
|
|
else if ( self->NPC )
|
|
{
|
|
self->NPC->greetingDebounceTime = level.time + Q_irand( 2000, 5000 );
|
|
//G_AddVoiceEvent( self, Q_irand(EV_VICTORY1, EV_VICTORY3), 2000 );
|
|
}
|
|
}
|
|
}
|
|
|
|
void G_AddPowerDuelScore(int team, int score)
|
|
{
|
|
int i = 0;
|
|
gentity_t *check;
|
|
|
|
while (i < MAX_CLIENTS)
|
|
{
|
|
check = &g_entities[i];
|
|
if (check->inuse && check->client &&
|
|
check->client->pers.connected == CON_CONNECTED && !check->client->iAmALoser &&
|
|
check->client->ps.stats[STAT_HEALTH] > 0 &&
|
|
check->client->sess.sessionTeam != TEAM_SPECTATOR &&
|
|
check->client->sess.duelTeam == team)
|
|
{ //found a living client on the specified team
|
|
check->client->sess.wins += score;
|
|
ClientUserinfoChanged(check->s.number);
|
|
}
|
|
i++;
|
|
}
|
|
}
|
|
|
|
void G_AddPowerDuelLoserScore(int team, int score)
|
|
{
|
|
int i = 0;
|
|
gentity_t *check;
|
|
|
|
while (i < MAX_CLIENTS)
|
|
{
|
|
check = &g_entities[i];
|
|
if (check->inuse && check->client &&
|
|
check->client->pers.connected == CON_CONNECTED &&
|
|
(check->client->iAmALoser || (check->client->ps.stats[STAT_HEALTH] <= 0 && check->client->sess.sessionTeam != TEAM_SPECTATOR)) &&
|
|
check->client->sess.duelTeam == team)
|
|
{ //found a living client on the specified team
|
|
check->client->sess.losses += score;
|
|
ClientUserinfoChanged(check->s.number);
|
|
}
|
|
i++;
|
|
}
|
|
}
|
|
|
|
|
|
extern qboolean g_noPDuelCheck;
|
|
void G_BroadcastObit( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int killer, int meansOfDeath, int wasInVehicle, qboolean wasJediMaster )
|
|
{
|
|
// broadcast the death event to everyone
|
|
if (self->s.eType != ET_NPC && !g_noPDuelCheck)
|
|
{
|
|
gentity_t *ent;
|
|
|
|
ent = G_TempEntity( self->r.currentOrigin, EV_OBITUARY );
|
|
ent->s.eventParm = meansOfDeath;
|
|
ent->s.otherEntityNum = self->s.number;
|
|
if ( attacker )
|
|
{
|
|
ent->s.otherEntityNum2 = attacker->s.number;
|
|
}
|
|
else
|
|
{//???
|
|
ent->s.otherEntityNum2 = killer;
|
|
}
|
|
if ( inflictor
|
|
&& !Q_stricmp( "vehicle_proj", inflictor->classname ) )
|
|
{//a vehicle missile
|
|
ent->s.eventParm = MOD_VEHICLE;
|
|
//store index into g_vehWeaponInfo
|
|
ent->s.weapon = inflictor->s.otherEntityNum2+1;
|
|
//store generic rocket or blaster type of missile
|
|
ent->s.generic1 = inflictor->s.weapon;
|
|
}
|
|
if ( wasInVehicle && self->s.number < MAX_CLIENTS )
|
|
{//target is in a vehicle, store the entnum
|
|
ent->s.lookTarget = wasInVehicle;
|
|
}
|
|
if ( attacker )
|
|
{
|
|
if ( attacker->s.m_iVehicleNum && attacker->s.number < MAX_CLIENTS )
|
|
{//target is in a vehicle, store the entnum
|
|
ent->s.brokenLimbs = attacker->s.m_iVehicleNum;
|
|
}
|
|
else if ( ent->s.lookTarget
|
|
&& !Q_stricmp( "func_rotating", attacker->classname ) )
|
|
{//my vehicle was killed by a func_rotating, probably an asteroid, so...
|
|
ent->s.saberInFlight = qtrue;
|
|
}
|
|
}
|
|
ent->r.svFlags = SVF_BROADCAST; // send to everyone
|
|
ent->s.isJediMaster = wasJediMaster;
|
|
}
|
|
}
|
|
|
|
/*
|
|
==================
|
|
player_die
|
|
==================
|
|
*/
|
|
extern stringID_table_t animTable[MAX_ANIMATIONS+1];
|
|
|
|
extern void AI_DeleteSelfFromGroup( gentity_t *self );
|
|
extern void AI_GroupMemberKilled( gentity_t *self );
|
|
extern void Boba_FlyStop( gentity_t *self );
|
|
extern qboolean Jedi_WaitingAmbush( gentity_t *self );
|
|
void CheckExitRules( void );
|
|
extern void Rancor_DropVictim( gentity_t *self );
|
|
|
|
extern qboolean g_dontFrickinCheck;
|
|
extern qboolean g_endPDuel;
|
|
void player_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath ) {
|
|
int anim;
|
|
int contents;
|
|
int killer;
|
|
int i;
|
|
char *killerName, *obit;
|
|
qboolean wasJediMaster = qfalse;
|
|
int sPMType = 0;
|
|
int wasInVehicle = 0;
|
|
gentity_t *tempInflictorEnt = inflictor;
|
|
qboolean tempInflictor = qfalse;
|
|
int actualMOD = meansOfDeath;
|
|
|
|
if ( self->client->ps.pm_type == PM_DEAD ) {
|
|
return;
|
|
}
|
|
|
|
if ( level.intermissiontime ) {
|
|
return;
|
|
}
|
|
|
|
//check player stuff
|
|
g_dontFrickinCheck = qfalse;
|
|
|
|
if (g_gametype.integer == GT_POWERDUEL)
|
|
{ //don't want to wait til later in the frame if this is the case
|
|
CheckExitRules();
|
|
|
|
if ( level.intermissiontime )
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (self->s.eType == ET_NPC &&
|
|
self->s.NPC_class == CLASS_VEHICLE &&
|
|
self->m_pVehicle &&
|
|
!self->m_pVehicle->m_pVehicleInfo->explosionDelay &&
|
|
(self->m_pVehicle->m_pPilot || self->m_pVehicle->m_iNumPassengers > 0 || self->m_pVehicle->m_pDroidUnit))
|
|
{ //kill everyone on board in the name of the attacker... if the vehicle has no death delay
|
|
gentity_t *murderer = NULL;
|
|
gentity_t *killEnt;
|
|
int i = 0;
|
|
|
|
if (self->client->ps.otherKillerTime >= level.time)
|
|
{ //use the last attacker
|
|
murderer = &g_entities[self->client->ps.otherKiller];
|
|
if (!murderer->inuse || !murderer->client)
|
|
{
|
|
murderer = NULL;
|
|
}
|
|
else
|
|
{
|
|
if (murderer->s.number >= MAX_CLIENTS &&
|
|
murderer->s.eType == ET_NPC &&
|
|
murderer->s.NPC_class == CLASS_VEHICLE &&
|
|
murderer->m_pVehicle &&
|
|
murderer->m_pVehicle->m_pPilot)
|
|
{
|
|
gentity_t *murderPilot = &g_entities[murderer->m_pVehicle->m_pPilot->s.number];
|
|
if (murderPilot->inuse && murderPilot->client)
|
|
{ //give the pilot of the offending vehicle credit for the kill
|
|
murderer = murderPilot;
|
|
actualMOD = self->client->otherKillerMOD;
|
|
if ( self->client->otherKillerVehWeapon > 0 )
|
|
{
|
|
tempInflictorEnt = G_Spawn();
|
|
if ( tempInflictorEnt )
|
|
{//fake up the inflictor
|
|
tempInflictor = qtrue;
|
|
tempInflictorEnt->classname = "vehicle_proj";
|
|
tempInflictorEnt->s.otherEntityNum2 = self->client->otherKillerVehWeapon-1;
|
|
tempInflictorEnt->s.weapon = self->client->otherKillerWeaponType;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (attacker && attacker->inuse && attacker->client)
|
|
{
|
|
if (attacker->s.number >= MAX_CLIENTS &&
|
|
attacker->s.eType == ET_NPC &&
|
|
attacker->s.NPC_class == CLASS_VEHICLE &&
|
|
attacker->m_pVehicle &&
|
|
attacker->m_pVehicle->m_pPilot)
|
|
{ //set vehicles pilot's killer as murderer
|
|
murderer = &g_entities[attacker->m_pVehicle->m_pPilot->s.number];
|
|
if (murderer->inuse && murderer->client &&murderer->client->ps.otherKillerTime >= level.time)
|
|
{
|
|
murderer = &g_entities[murderer->client->ps.otherKiller];
|
|
if (!murderer->inuse || !murderer->client)
|
|
{
|
|
murderer = NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
murderer = NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
murderer = &g_entities[attacker->s.number];
|
|
}
|
|
}
|
|
else if (self->m_pVehicle->m_pPilot)
|
|
{
|
|
murderer = (gentity_t *)self->m_pVehicle->m_pPilot;
|
|
if (!murderer->inuse || !murderer->client)
|
|
{
|
|
murderer = NULL;
|
|
}
|
|
}
|
|
|
|
//no valid murderer.. just use self I guess
|
|
if (!murderer)
|
|
{
|
|
if ( !attacker )
|
|
{
|
|
murderer = self;
|
|
}
|
|
else
|
|
{
|
|
murderer = attacker;
|
|
}
|
|
}
|
|
|
|
if ( self->m_pVehicle->m_pVehicleInfo->hideRider )
|
|
{//pilot is *inside* me, so kill him, too
|
|
killEnt = (gentity_t *)self->m_pVehicle->m_pPilot;
|
|
if (killEnt && killEnt->inuse && killEnt->client)
|
|
{
|
|
G_Damage(killEnt, tempInflictorEnt, murderer, NULL, killEnt->client->ps.origin, 99999, DAMAGE_NO_PROTECTION, actualMOD);
|
|
}
|
|
if ( self->m_pVehicle->m_pVehicleInfo )
|
|
{
|
|
int numPass = self->m_pVehicle->m_iNumPassengers;
|
|
for ( i = 0; i < numPass && self->m_pVehicle->m_iNumPassengers; i++ )
|
|
{//go through and eject the last passenger
|
|
killEnt = (gentity_t *)self->m_pVehicle->m_ppPassengers[self->m_pVehicle->m_iNumPassengers-1];
|
|
if ( killEnt )
|
|
{
|
|
self->m_pVehicle->m_pVehicleInfo->Eject(self->m_pVehicle, (bgEntity_t *)killEnt, qtrue);
|
|
if ( killEnt->inuse && killEnt->client )
|
|
{
|
|
G_Damage(killEnt, tempInflictorEnt, murderer, NULL, killEnt->client->ps.origin, 99999, DAMAGE_NO_PROTECTION, actualMOD);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
killEnt = (gentity_t *)self->m_pVehicle->m_pDroidUnit;
|
|
if (killEnt && killEnt->inuse && killEnt->client)
|
|
{
|
|
killEnt->flags &= ~FL_UNDYING;
|
|
G_Damage(killEnt, tempInflictorEnt, murderer, NULL, killEnt->client->ps.origin, 99999, DAMAGE_NO_PROTECTION, actualMOD);
|
|
}
|
|
if ( tempInflictor )
|
|
{
|
|
G_FreeEntity( tempInflictorEnt );
|
|
}
|
|
tempInflictorEnt = inflictor;
|
|
tempInflictor = qfalse;
|
|
actualMOD = meansOfDeath;
|
|
}
|
|
|
|
self->client->ps.emplacedIndex = 0;
|
|
|
|
G_BreakArm(self, 0); //unbreak anything we have broken
|
|
self->client->ps.saberEntityNum = self->client->saberStoredIndex; //in case we died while our saber was knocked away.
|
|
|
|
self->client->bodyGrabIndex = ENTITYNUM_NONE;
|
|
self->client->bodyGrabTime = 0;
|
|
|
|
if (self->client->holdingObjectiveItem > 0)
|
|
{ //carrying a siege objective item - make sure it updates and removes itself from us now in case this is an instant death-respawn situation
|
|
gentity_t *objectiveItem = &g_entities[self->client->holdingObjectiveItem];
|
|
|
|
if (objectiveItem->inuse && objectiveItem->think)
|
|
{
|
|
objectiveItem->think(objectiveItem);
|
|
}
|
|
}
|
|
|
|
if ( (self->client->inSpaceIndex && self->client->inSpaceIndex != ENTITYNUM_NONE) ||
|
|
(self->client->ps.eFlags2 & EF2_SHIP_DEATH) )
|
|
{
|
|
self->client->noCorpse = qtrue;
|
|
}
|
|
|
|
if ( self->client->NPC_class != CLASS_VEHICLE
|
|
&& self->client->ps.m_iVehicleNum )
|
|
{ //I'm riding a vehicle
|
|
//tell it I'm getting off
|
|
gentity_t *veh = &g_entities[self->client->ps.m_iVehicleNum];
|
|
|
|
if (veh->inuse && veh->client && veh->m_pVehicle)
|
|
{
|
|
//remember it for obit
|
|
wasInVehicle = (veh->m_pVehicle->m_pVehicleInfo-&g_vehicleInfo[0]);
|
|
veh->m_pVehicle->m_pVehicleInfo->Eject(veh->m_pVehicle, (bgEntity_t *)self, qtrue);
|
|
|
|
if (veh->m_pVehicle->m_pVehicleInfo->type == VH_FIGHTER)
|
|
{ //go into "die in ship" mode with flag
|
|
self->client->ps.eFlags2 |= EF2_SHIP_DEATH;
|
|
|
|
//put me over where my vehicle exploded
|
|
G_SetOrigin(self, veh->client->ps.origin);
|
|
VectorCopy(veh->client->ps.origin, self->client->ps.origin);
|
|
}
|
|
}
|
|
//droids throw heads if they haven't yet
|
|
switch(self->client->NPC_class)
|
|
{
|
|
case CLASS_R2D2:
|
|
if ( !trap_G2API_GetSurfaceRenderStatus( self->ghoul2, 0, "head" ) )
|
|
{
|
|
vec3_t up;
|
|
AngleVectors( self->r.currentAngles, NULL, NULL, up );
|
|
G_PlayEffectID( G_EffectIndex("chunks/r2d2head_veh"), self->r.currentOrigin, up );
|
|
}
|
|
break;
|
|
|
|
case CLASS_R5D2:
|
|
if ( !trap_G2API_GetSurfaceRenderStatus( self->ghoul2, 0, "head" ) )
|
|
{
|
|
vec3_t up;
|
|
AngleVectors( self->r.currentAngles, NULL, NULL, up );
|
|
G_PlayEffectID( G_EffectIndex("chunks/r5d2head_veh"), self->r.currentOrigin, up );
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( self->NPC )
|
|
{
|
|
if ( self->client && Jedi_WaitingAmbush( self ) )
|
|
{//ambushing trooper
|
|
self->client->noclip = qfalse;
|
|
}
|
|
NPC_FreeCombatPoint( self->NPC->combatPoint, qfalse );
|
|
if ( self->NPC->group )
|
|
{
|
|
//lastInGroup = (self->NPC->group->numGroup < 2);
|
|
AI_GroupMemberKilled( self );
|
|
AI_DeleteSelfFromGroup( self );
|
|
}
|
|
|
|
if ( self->NPC->tempGoal )
|
|
{
|
|
G_FreeEntity( self->NPC->tempGoal );
|
|
self->NPC->tempGoal = NULL;
|
|
}
|
|
/*
|
|
if ( self->s.eFlags & EF_LOCKED_TO_WEAPON )
|
|
{
|
|
// dumb, just get the NPC out of the chair
|
|
extern void RunEmplacedWeapon( gentity_t *ent, usercmd_t **ucmd );
|
|
|
|
usercmd_t cmd, *ad_cmd;
|
|
|
|
memset( &cmd, 0, sizeof( usercmd_t ));
|
|
|
|
//gentity_t *old = self->owner;
|
|
|
|
if ( self->owner )
|
|
{
|
|
self->owner->s.frame = self->owner->startFrame = self->owner->endFrame = 0;
|
|
self->owner->svFlags &= ~SVF_ANIMATING;
|
|
}
|
|
|
|
cmd.buttons |= BUTTON_USE;
|
|
ad_cmd = &cmd;
|
|
RunEmplacedWeapon( self, &ad_cmd );
|
|
//self->owner = old;
|
|
}
|
|
*/
|
|
//if ( self->client->NPC_class == CLASS_BOBAFETT && self->client->moveType == MT_FLYSWIM )
|
|
if (0)
|
|
{
|
|
Boba_FlyStop( self );
|
|
}
|
|
if ( self->s.NPC_class == CLASS_RANCOR )
|
|
{
|
|
Rancor_DropVictim( self );
|
|
}
|
|
}
|
|
if ( attacker && attacker->NPC && attacker->NPC->group && attacker->NPC->group->enemy == self )
|
|
{
|
|
attacker->NPC->group->enemy = NULL;
|
|
}
|
|
|
|
//Cheap method until/if I decide to put fancier stuff in (e.g. sabers falling out of hand and slowly
|
|
//holstering on death like sp)
|
|
if (self->client->ps.weapon == WP_SABER &&
|
|
!self->client->ps.saberHolstered &&
|
|
self->client->ps.saberEntityNum)
|
|
{
|
|
if (!self->client->ps.saberInFlight &&
|
|
self->client->saber[0].soundOff)
|
|
{
|
|
G_Sound(self, CHAN_AUTO, self->client->saber[0].soundOff);
|
|
}
|
|
if (self->client->saber[1].soundOff &&
|
|
self->client->saber[1].model[0])
|
|
{
|
|
G_Sound(self, CHAN_AUTO, self->client->saber[1].soundOff);
|
|
}
|
|
}
|
|
|
|
//Use any target we had
|
|
G_UseTargets( self, self );
|
|
|
|
if (g_slowmoDuelEnd.integer && (g_gametype.integer == GT_DUEL || g_gametype.integer == GT_POWERDUEL) && attacker && attacker->inuse && attacker->client)
|
|
{
|
|
if (!gDoSlowMoDuel)
|
|
{
|
|
gDoSlowMoDuel = qtrue;
|
|
gSlowMoDuelTime = level.time;
|
|
}
|
|
}
|
|
/*
|
|
else if (self->NPC && attacker && attacker->client && attacker->s.number < MAX_CLIENTS && !gDoSlowMoDuel)
|
|
{
|
|
gDoSlowMoDuel = qtrue;
|
|
gSlowMoDuelTime = level.time;
|
|
}
|
|
*/
|
|
|
|
//Make sure the jetpack is turned off.
|
|
Jetpack_Off(self);
|
|
|
|
self->client->ps.heldByClient = 0;
|
|
self->client->beingThrown = 0;
|
|
self->client->doingThrow = 0;
|
|
BG_ClearRocketLock( &self->client->ps );
|
|
self->client->isHacking = 0;
|
|
self->client->ps.hackingTime = 0;
|
|
|
|
if (inflictor && inflictor->activator && !inflictor->client && !attacker->client &&
|
|
inflictor->activator->client && inflictor->activator->inuse &&
|
|
inflictor->s.weapon == WP_TURRET)
|
|
{
|
|
attacker = inflictor->activator;
|
|
}
|
|
|
|
if (self->client && self->client->ps.isJediMaster)
|
|
{
|
|
wasJediMaster = qtrue;
|
|
}
|
|
|
|
//if he was charging or anything else, kill the sound
|
|
G_MuteSound(self->s.number, CHAN_WEAPON);
|
|
|
|
BlowDetpacks(self); //blow detpacks if they're planted
|
|
|
|
self->client->ps.fd.forceDeactivateAll = 1;
|
|
|
|
if ((self == attacker || !attacker->client) &&
|
|
(meansOfDeath == MOD_CRUSH || meansOfDeath == MOD_FALLING || meansOfDeath == MOD_TRIGGER_HURT || meansOfDeath == MOD_UNKNOWN || meansOfDeath == MOD_SUICIDE) &&
|
|
self->client->ps.otherKillerTime > level.time)
|
|
{//remember who last attacked us
|
|
attacker = &g_entities[self->client->ps.otherKiller];
|
|
if ( self->client->otherKillerMOD != MOD_UNKNOWN )
|
|
{
|
|
actualMOD = self->client->otherKillerMOD;
|
|
}
|
|
if ( self->client->otherKillerVehWeapon > 0 )
|
|
{
|
|
tempInflictorEnt = G_Spawn();
|
|
if ( tempInflictorEnt )
|
|
{//fake up the inflictor
|
|
tempInflictor = qtrue;
|
|
tempInflictorEnt->classname = "vehicle_proj";
|
|
tempInflictorEnt->s.otherEntityNum2 = self->client->otherKillerVehWeapon-1;
|
|
tempInflictorEnt->s.weapon = self->client->otherKillerWeaponType;
|
|
}
|
|
}
|
|
}
|
|
|
|
// check for an almost capture
|
|
CheckAlmostCapture( self, attacker );
|
|
|
|
self->client->ps.pm_type = PM_DEAD;
|
|
self->client->ps.pm_flags &= ~PMF_STUCK_TO_WALL;
|
|
|
|
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 {
|
|
obit = modNames[ meansOfDeath ];
|
|
}
|
|
|
|
G_LogPrintf("Kill: %i %i %i: %s killed %s by %s\n",
|
|
killer, self->s.number, meansOfDeath, killerName,
|
|
self->client->pers.netname, obit );
|
|
|
|
if ( g_austrian.integer
|
|
&& (g_gametype.integer == GT_DUEL)
|
|
&& level.numPlayingClients >= 2 )
|
|
{
|
|
int spawnTime = (level.clients[level.sortedClients[0]].respawnTime > level.clients[level.sortedClients[1]].respawnTime) ? level.clients[level.sortedClients[0]].respawnTime : level.clients[level.sortedClients[1]].respawnTime;
|
|
G_LogPrintf("Duel Kill Details:\n");
|
|
G_LogPrintf("Kill Time: %d\n", level.time-spawnTime );
|
|
G_LogPrintf("victim: %s, hits on enemy %d\n", self->client->pers.netname, self->client->ps.persistant[PERS_HITS] );
|
|
if ( attacker && attacker->client )
|
|
{
|
|
G_LogPrintf("killer: %s, hits on enemy %d, health: %d\n", attacker->client->pers.netname, attacker->client->ps.persistant[PERS_HITS], attacker->health );
|
|
//also - if MOD_SABER, list the animation and saber style
|
|
if ( meansOfDeath == MOD_SABER )
|
|
{
|
|
G_LogPrintf("killer saber style: %d, killer saber anim %s\n", attacker->client->ps.fd.saberAnimLevel, animTable[(attacker->client->ps.torsoAnim)].name );
|
|
}
|
|
}
|
|
}
|
|
|
|
G_LogWeaponKill(killer, meansOfDeath);
|
|
G_LogWeaponDeath(self->s.number, self->s.weapon);
|
|
if (attacker && attacker->client && attacker->inuse)
|
|
{
|
|
G_LogWeaponFrag(killer, self->s.number);
|
|
}
|
|
|
|
G_BroadcastObit( self, inflictor, attacker, killer, actualMOD, wasInVehicle, wasJediMaster );
|
|
if ( tempInflictor )
|
|
{
|
|
G_FreeEntity( tempInflictorEnt );
|
|
}
|
|
|
|
self->enemy = attacker;
|
|
|
|
self->client->ps.persistant[PERS_KILLED]++;
|
|
|
|
if (self == attacker)
|
|
{
|
|
self->client->ps.fd.suicides++;
|
|
}
|
|
|
|
if (attacker && attacker->client)
|
|
{//killed by a client of some kind (player, NPC or vehicle)
|
|
if ( self->s.number < MAX_CLIENTS )
|
|
{//only remember real clients
|
|
attacker->client->lastkilled_client = self->s.number;
|
|
}
|
|
|
|
G_CheckVictoryScript(attacker);
|
|
|
|
if ( self->s.number >= MAX_CLIENTS//not a player client
|
|
&& self->client //an NPC client
|
|
&& self->client->NPC_class != CLASS_VEHICLE //not a vehicle
|
|
&& self->s.m_iVehicleNum )//a droid in a vehicle
|
|
{//no credit for droid, you do get credit for the vehicle kill and the pilot (2 points)
|
|
}
|
|
else if ( meansOfDeath == MOD_COLLISION
|
|
|| meansOfDeath == MOD_VEH_EXPLOSION )
|
|
{//no credit for veh-veh collisions?
|
|
}
|
|
else if ( attacker == self || OnSameTeam (self, attacker ) )
|
|
{//killed self or teammate
|
|
if ( meansOfDeath == MOD_FALLING
|
|
&& attacker != self
|
|
&& attacker->s.number < MAX_CLIENTS
|
|
&& attacker->s.m_iVehicleNum )
|
|
{//crushed by a teammate in a vehicle, no penalty
|
|
}
|
|
else if (g_gametype.integer == GT_DUEL)
|
|
{ //in duel, if you kill yourself, the person you are dueling against gets a kill for it
|
|
int otherClNum = -1;
|
|
if (level.sortedClients[0] == self->s.number)
|
|
{
|
|
otherClNum = level.sortedClients[1];
|
|
}
|
|
else if (level.sortedClients[1] == self->s.number)
|
|
{
|
|
otherClNum = level.sortedClients[0];
|
|
}
|
|
|
|
if (otherClNum >= 0 && otherClNum < MAX_CLIENTS &&
|
|
g_entities[otherClNum].inuse && g_entities[otherClNum].client &&
|
|
otherClNum != attacker->s.number)
|
|
{
|
|
AddScore( &g_entities[otherClNum], self->r.currentOrigin, 1 );
|
|
}
|
|
else
|
|
{
|
|
AddScore( attacker, self->r.currentOrigin, -1 );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
AddScore( attacker, self->r.currentOrigin, -1 );
|
|
if ( attacker != self
|
|
&& attacker->s.number < MAX_CLIENTS
|
|
&& self->s.number < MAX_CLIENTS )
|
|
{
|
|
G_CheckTKAutoKickBan( attacker );
|
|
}
|
|
}
|
|
if (g_gametype.integer == GT_JEDIMASTER)
|
|
{
|
|
if (self->client && self->client->ps.isJediMaster)
|
|
{ //killed ourself so return the saber to the original position
|
|
//(to avoid people jumping off ledges and making the saber
|
|
//unreachable for 60 seconds)
|
|
ThrowSaberToAttacker(self, NULL);
|
|
self->client->ps.isJediMaster = qfalse;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (g_gametype.integer == GT_JEDIMASTER)
|
|
{
|
|
if ((attacker->client && attacker->client->ps.isJediMaster) ||
|
|
(self->client && self->client->ps.isJediMaster))
|
|
{
|
|
AddScore( attacker, self->r.currentOrigin, 1 );
|
|
|
|
if (self->client && self->client->ps.isJediMaster)
|
|
{
|
|
ThrowSaberToAttacker(self, attacker);
|
|
self->client->ps.isJediMaster = qfalse;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
gentity_t *jmEnt = G_GetJediMaster();
|
|
|
|
if (jmEnt && jmEnt->client)
|
|
{
|
|
AddScore( jmEnt, self->r.currentOrigin, 1 );
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
AddScore( attacker, self->r.currentOrigin, 1 );
|
|
}
|
|
|
|
if( meansOfDeath == MOD_STUN_BATON ) {
|
|
|
|
// play humiliation on player
|
|
attacker->client->ps.persistant[PERS_GAUNTLET_FRAG_COUNT]++;
|
|
|
|
attacker->client->rewardTime = level.time + REWARD_SPRITE_TIME;
|
|
|
|
// also play humiliation on target
|
|
self->client->ps.persistant[PERS_PLAYEREVENTS] ^= PLAYEREVENT_GAUNTLETREWARD;
|
|
}
|
|
|
|
// check for two kills in a short amount of time
|
|
// if this is close enough to the last kill, give a reward sound
|
|
if ( level.time - attacker->client->lastKillTime < CARNAGE_REWARD_TIME ) {
|
|
// play excellent on player
|
|
attacker->client->ps.persistant[PERS_EXCELLENT_COUNT]++;
|
|
|
|
attacker->client->rewardTime = level.time + REWARD_SPRITE_TIME;
|
|
}
|
|
attacker->client->lastKillTime = level.time;
|
|
|
|
}
|
|
}
|
|
else if ( meansOfDeath == MOD_COLLISION
|
|
|| meansOfDeath == MOD_VEH_EXPLOSION )
|
|
{//no credit for veh-veh collisions?
|
|
}
|
|
else
|
|
{
|
|
if (self->client && self->client->ps.isJediMaster)
|
|
{ //killed ourself so return the saber to the original position
|
|
//(to avoid people jumping off ledges and making the saber
|
|
//unreachable for 60 seconds)
|
|
ThrowSaberToAttacker(self, NULL);
|
|
self->client->ps.isJediMaster = qfalse;
|
|
}
|
|
|
|
if (g_gametype.integer == GT_DUEL)
|
|
{ //in duel, if you kill yourself, the person you are dueling against gets a kill for it
|
|
int otherClNum = -1;
|
|
if (level.sortedClients[0] == self->s.number)
|
|
{
|
|
otherClNum = level.sortedClients[1];
|
|
}
|
|
else if (level.sortedClients[1] == self->s.number)
|
|
{
|
|
otherClNum = level.sortedClients[0];
|
|
}
|
|
|
|
if (otherClNum >= 0 && otherClNum < MAX_CLIENTS &&
|
|
g_entities[otherClNum].inuse && g_entities[otherClNum].client &&
|
|
otherClNum != self->s.number)
|
|
{
|
|
AddScore( &g_entities[otherClNum], self->r.currentOrigin, 1 );
|
|
}
|
|
else
|
|
{
|
|
AddScore( self, self->r.currentOrigin, -1 );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
AddScore( self, self->r.currentOrigin, -1 );
|
|
}
|
|
}
|
|
|
|
// Add team bonuses
|
|
Team_FragBonuses(self, inflictor, attacker);
|
|
|
|
// if I committed suicide, the flag does not fall, it returns.
|
|
if (meansOfDeath == MOD_SUICIDE) {
|
|
if ( self->client->ps.powerups[PW_NEUTRALFLAG] ) { // only happens in One Flag CTF
|
|
Team_ReturnFlag( TEAM_FREE );
|
|
self->client->ps.powerups[PW_NEUTRALFLAG] = 0;
|
|
}
|
|
else if ( self->client->ps.powerups[PW_REDFLAG] ) { // only happens in standard CTF
|
|
Team_ReturnFlag( TEAM_RED );
|
|
self->client->ps.powerups[PW_REDFLAG] = 0;
|
|
}
|
|
else if ( self->client->ps.powerups[PW_BLUEFLAG] ) { // only happens in standard CTF
|
|
Team_ReturnFlag( TEAM_BLUE );
|
|
self->client->ps.powerups[PW_BLUEFLAG] = 0;
|
|
}
|
|
}
|
|
|
|
// if client is in a nodrop area, don't drop anything (but return CTF flags!)
|
|
contents = trap_PointContents( self->r.currentOrigin, -1 );
|
|
if ( !( contents & CONTENTS_NODROP ) && !self->client->ps.fallingToDeath) {
|
|
if (self->s.eType != ET_NPC)
|
|
{
|
|
TossClientItems( self );
|
|
}
|
|
}
|
|
else {
|
|
if ( self->client->ps.powerups[PW_NEUTRALFLAG] ) { // only happens in One Flag CTF
|
|
Team_ReturnFlag( TEAM_FREE );
|
|
}
|
|
else if ( self->client->ps.powerups[PW_REDFLAG] ) { // only happens in standard CTF
|
|
Team_ReturnFlag( TEAM_RED );
|
|
}
|
|
else if ( self->client->ps.powerups[PW_BLUEFLAG] ) { // only happens in standard CTF
|
|
Team_ReturnFlag( TEAM_BLUE );
|
|
}
|
|
}
|
|
|
|
if ( MOD_TEAM_CHANGE == meansOfDeath )
|
|
{
|
|
// Give them back a point since they didn't really die.
|
|
AddScore( self, self->r.currentOrigin, 1 );
|
|
}
|
|
else
|
|
{
|
|
Cmd_Score_f( self ); // show scores
|
|
}
|
|
|
|
// send updated scores to any clients that are following this one,
|
|
// or they would get stale scoreboards
|
|
for ( i = 0 ; i < level.maxclients ; i++ ) {
|
|
gclient_t *client;
|
|
|
|
client = &level.clients[i];
|
|
if ( client->pers.connected != CON_CONNECTED ) {
|
|
continue;
|
|
}
|
|
if ( client->sess.sessionTeam != TEAM_SPECTATOR ) {
|
|
continue;
|
|
}
|
|
if ( client->sess.spectatorClient == self->s.number ) {
|
|
Cmd_Score_f( g_entities + i );
|
|
}
|
|
}
|
|
|
|
self->takedamage = qtrue; // can still be gibbed
|
|
|
|
self->s.weapon = WP_NONE;
|
|
self->s.powerups = 0;
|
|
if (self->s.eType != ET_NPC)
|
|
{ //handled differently for NPCs
|
|
self->r.contents = CONTENTS_CORPSE;
|
|
}
|
|
self->client->ps.zoomMode = 0; // Turn off zooming when we die
|
|
|
|
//rww - 07/19/02 - I removed this because it isn't working and it's ugly (for people on the outside)
|
|
/*
|
|
self->s.angles[0] = 0;
|
|
self->s.angles[2] = 0;
|
|
LookAtKiller (self, inflictor, attacker);
|
|
|
|
VectorCopy( self->s.angles, self->client->ps.viewangles );
|
|
*/
|
|
|
|
self->s.loopSound = 0;
|
|
self->s.loopIsSoundset = qfalse;
|
|
|
|
if (self->s.eType != ET_NPC)
|
|
{ //handled differently for NPCs
|
|
self->r.maxs[2] = -8;
|
|
}
|
|
|
|
// 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;
|
|
|
|
// remove powerups
|
|
memset( self->client->ps.powerups, 0, sizeof(self->client->ps.powerups) );
|
|
|
|
// NOTENOTE No gib deaths right now, this is star wars.
|
|
/*
|
|
// never gib in a nodrop
|
|
if ( (self->health <= GIB_HEALTH && !(contents & CONTENTS_NODROP) && g_blood.integer) || meansOfDeath == MOD_SUICIDE)
|
|
{
|
|
// gib death
|
|
GibEntity( self, killer );
|
|
}
|
|
else
|
|
*/
|
|
{
|
|
// normal death
|
|
|
|
static int i;
|
|
|
|
anim = G_PickDeathAnim(self, self->pos1, damage, meansOfDeath, HL_NONE);
|
|
|
|
if (anim >= 1)
|
|
{ //Some droids don't have death anims
|
|
// 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->respawnTime = level.time + 1000;//((self->client->animations[anim].numFrames*40)/(50.0f / self->client->animations[anim].frameLerp))+300;
|
|
|
|
sPMType = self->client->ps.pm_type;
|
|
self->client->ps.pm_type = PM_NORMAL; //don't want pm type interfering with our setanim calls.
|
|
|
|
if (self->inuse)
|
|
{ //not disconnecting
|
|
G_SetAnim(self, NULL, SETANIM_BOTH, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD|SETANIM_FLAG_RESTART, 0);
|
|
}
|
|
|
|
self->client->ps.pm_type = sPMType;
|
|
|
|
if (meansOfDeath == MOD_SABER || (meansOfDeath == MOD_MELEE && G_HeavyMelee( attacker )) )//saber or heavy melee (claws)
|
|
{ //update the anim on the actual skeleton (so bolt point will reflect the correct position) and then check for dismem
|
|
G_UpdateClientAnims(self, 1.0f);
|
|
G_CheckForDismemberment(self, attacker, self->pos1, damage, anim, qfalse);
|
|
}
|
|
}
|
|
else if (self->NPC && self->client && self->client->NPC_class != CLASS_MARK1 &&
|
|
self->client->NPC_class != CLASS_VEHICLE)
|
|
{ //in this case if we're an NPC it's my guess that we want to get removed straight away.
|
|
self->think = G_FreeEntity;
|
|
self->nextthink = level.time;
|
|
}
|
|
|
|
//self->client->ps.legsAnim = anim;
|
|
//self->client->ps.torsoAnim = anim;
|
|
// self->client->ps.pm_flags |= PMF_UPDATE_ANIM; // Make sure the pmove sets up the GHOUL2 anims.
|
|
|
|
//rww - do this on respawn, not death
|
|
//CopyToBodyQue (self);
|
|
|
|
//G_AddEvent( self, EV_DEATH1 + i, killer );
|
|
if (wasJediMaster)
|
|
{
|
|
G_AddEvent( self, EV_DEATH1 + i, 1 );
|
|
}
|
|
else
|
|
{
|
|
G_AddEvent( self, EV_DEATH1 + i, 0 );
|
|
}
|
|
|
|
if (self != attacker)
|
|
{ //don't make NPCs want to murder you on respawn for killing yourself!
|
|
G_DeathAlert( self, attacker );
|
|
}
|
|
|
|
// the body can still be gibbed
|
|
if (!self->NPC)
|
|
{ //don't remove NPCs like this!
|
|
self->die = body_die;
|
|
}
|
|
|
|
//It won't gib, it will disintegrate (because this is Star Wars).
|
|
self->takedamage = qtrue;
|
|
|
|
// globally cycle through the different death animations
|
|
i = ( i + 1 ) % 3;
|
|
}
|
|
|
|
if ( self->NPC )
|
|
{//If an NPC, make sure we start running our scripts again- this gets set to infinite while we fall to our deaths
|
|
self->NPC->nextBStateThink = level.time;
|
|
}
|
|
|
|
if ( G_ActivateBehavior( self, BSET_DEATH ) )
|
|
{
|
|
//deathScript = qtrue;
|
|
}
|
|
|
|
if ( self->NPC && (self->NPC->scriptFlags&SCF_FFDEATH) )
|
|
{
|
|
if ( G_ActivateBehavior( self, BSET_FFDEATH ) )
|
|
{//FIXME: should running this preclude running the normal deathscript?
|
|
//deathScript = qtrue;
|
|
}
|
|
G_UseTargets2( self, self, self->target4 );
|
|
}
|
|
|
|
/*
|
|
if ( !deathScript && !(self->svFlags&SVF_KILLED_SELF) )
|
|
{
|
|
//Should no longer run scripts
|
|
//WARNING!!! DO NOT DO THIS WHILE RUNNING A SCRIPT, ICARUS WILL CRASH!!!
|
|
//FIXME: shouldn't ICARUS handle this internally?
|
|
ICARUS_FreeEnt(self);
|
|
}
|
|
*/
|
|
//rwwFIXMEFIXME: Do this too?
|
|
|
|
// Free up any timers we may have on us.
|
|
TIMER_Clear2( self );
|
|
|
|
trap_LinkEntity (self);
|
|
|
|
if ( self->NPC )
|
|
{
|
|
self->NPC->timeOfDeath = level.time;//this will change - used for debouncing post-death events
|
|
}
|
|
|
|
// Start any necessary death fx for this entity
|
|
DeathFX( self );
|
|
|
|
|
|
if (g_gametype.integer == GT_POWERDUEL && !g_noPDuelCheck)
|
|
{ //powerduel checks
|
|
if (self->client->sess.duelTeam == DUELTEAM_LONE)
|
|
{ //automatically means a win as there is only one
|
|
G_AddPowerDuelScore(DUELTEAM_DOUBLE, 1);
|
|
G_AddPowerDuelLoserScore(DUELTEAM_LONE, 1);
|
|
g_endPDuel = qtrue;
|
|
}
|
|
else if (self->client->sess.duelTeam == DUELTEAM_DOUBLE)
|
|
{
|
|
int i = 0;
|
|
gentity_t *check;
|
|
qboolean heLives = qfalse;
|
|
|
|
while (i < MAX_CLIENTS)
|
|
{
|
|
check = &g_entities[i];
|
|
if (check->inuse && check->client && check->s.number != self->s.number &&
|
|
check->client->pers.connected == CON_CONNECTED && !check->client->iAmALoser &&
|
|
check->client->ps.stats[STAT_HEALTH] > 0 &&
|
|
check->client->sess.sessionTeam != TEAM_SPECTATOR &&
|
|
check->client->sess.duelTeam == DUELTEAM_DOUBLE)
|
|
{ //still an active living paired duelist so it's not over yet.
|
|
heLives = qtrue;
|
|
break;
|
|
}
|
|
i++;
|
|
}
|
|
|
|
if (!heLives)
|
|
{ //they're all dead, give the lone duelist the win.
|
|
G_AddPowerDuelScore(DUELTEAM_LONE, 1);
|
|
G_AddPowerDuelLoserScore(DUELTEAM_DOUBLE, 1);
|
|
g_endPDuel = qtrue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
================
|
|
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;
|
|
|
|
if ( client->NPC_class == CLASS_VEHICLE
|
|
&& ent->m_pVehicle
|
|
&& ent->client->ps.electrifyTime > level.time )
|
|
{//ion-cannon has disabled this ship's shields, take damage on hull!
|
|
return 0;
|
|
}
|
|
// armor
|
|
count = client->ps.stats[STAT_ARMOR];
|
|
|
|
if (dflags & DAMAGE_HALF_ABSORB)
|
|
{ // Half the damage gets absorbed by the shields, rather than 100%
|
|
save = ceil( damage * ARMOR_PROTECTION );
|
|
}
|
|
else
|
|
{ // All the damage gets absorbed by the shields.
|
|
save = damage;
|
|
}
|
|
|
|
// save is the most damage that the armor is elibigle to protect, of course, but it's limited by the total armor.
|
|
if (save >= count)
|
|
save = count;
|
|
|
|
if (!save)
|
|
return 0;
|
|
|
|
if (dflags & DAMAGE_HALF_ARMOR_REDUCTION) // Armor isn't whittled so easily by sniper shots.
|
|
{
|
|
client->ps.stats[STAT_ARMOR] -= (int)(save*ARMOR_REDUCTION_FACTOR);
|
|
}
|
|
else
|
|
{
|
|
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 && targ->s.pos.trType != TR_NONLINEAR_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;
|
|
}
|
|
|
|
/*
|
|
===================================
|
|
rww - beginning of the majority of the dismemberment and location based damage code.
|
|
===================================
|
|
*/
|
|
char *hitLocName[HL_MAX] =
|
|
{
|
|
"none", //HL_NONE = 0,
|
|
"right foot", //HL_FOOT_RT,
|
|
"left foot", //HL_FOOT_LT,
|
|
"right leg", //HL_LEG_RT,
|
|
"left leg", //HL_LEG_LT,
|
|
"waist", //HL_WAIST,
|
|
"back right shoulder", //HL_BACK_RT,
|
|
"back left shoulder", //HL_BACK_LT,
|
|
"back", //HL_BACK,
|
|
"front right shouler", //HL_CHEST_RT,
|
|
"front left shoulder", //HL_CHEST_LT,
|
|
"chest", //HL_CHEST,
|
|
"right arm", //HL_ARM_RT,
|
|
"left arm", //HL_ARM_LT,
|
|
"right hand", //HL_HAND_RT,
|
|
"left hand", //HL_HAND_LT,
|
|
"head", //HL_HEAD
|
|
"generic1", //HL_GENERIC1,
|
|
"generic2", //HL_GENERIC2,
|
|
"generic3", //HL_GENERIC3,
|
|
"generic4", //HL_GENERIC4,
|
|
"generic5", //HL_GENERIC5,
|
|
"generic6" //HL_GENERIC6
|
|
};
|
|
|
|
void G_GetDismemberLoc(gentity_t *self, vec3_t boltPoint, int limbType)
|
|
{ //Just get the general area without using server-side ghoul2
|
|
vec3_t fwd, right, up;
|
|
|
|
AngleVectors(self->r.currentAngles, fwd, right, up);
|
|
|
|
VectorCopy(self->r.currentOrigin, boltPoint);
|
|
|
|
switch (limbType)
|
|
{
|
|
case G2_MODELPART_HEAD:
|
|
boltPoint[0] += up[0]*24;
|
|
boltPoint[1] += up[1]*24;
|
|
boltPoint[2] += up[2]*24;
|
|
break;
|
|
case G2_MODELPART_WAIST:
|
|
boltPoint[0] += up[0]*4;
|
|
boltPoint[1] += up[1]*4;
|
|
boltPoint[2] += up[2]*4;
|
|
break;
|
|
case G2_MODELPART_LARM:
|
|
boltPoint[0] += up[0]*18;
|
|
boltPoint[1] += up[1]*18;
|
|
boltPoint[2] += up[2]*18;
|
|
|
|
boltPoint[0] -= right[0]*10;
|
|
boltPoint[1] -= right[1]*10;
|
|
boltPoint[2] -= right[2]*10;
|
|
break;
|
|
case G2_MODELPART_RARM:
|
|
boltPoint[0] += up[0]*18;
|
|
boltPoint[1] += up[1]*18;
|
|
boltPoint[2] += up[2]*18;
|
|
|
|
boltPoint[0] += right[0]*10;
|
|
boltPoint[1] += right[1]*10;
|
|
boltPoint[2] += right[2]*10;
|
|
break;
|
|
case G2_MODELPART_RHAND:
|
|
boltPoint[0] += up[0]*8;
|
|
boltPoint[1] += up[1]*8;
|
|
boltPoint[2] += up[2]*8;
|
|
|
|
boltPoint[0] += right[0]*10;
|
|
boltPoint[1] += right[1]*10;
|
|
boltPoint[2] += right[2]*10;
|
|
break;
|
|
case G2_MODELPART_LLEG:
|
|
boltPoint[0] -= up[0]*4;
|
|
boltPoint[1] -= up[1]*4;
|
|
boltPoint[2] -= up[2]*4;
|
|
|
|
boltPoint[0] -= right[0]*10;
|
|
boltPoint[1] -= right[1]*10;
|
|
boltPoint[2] -= right[2]*10;
|
|
break;
|
|
case G2_MODELPART_RLEG:
|
|
boltPoint[0] -= up[0]*4;
|
|
boltPoint[1] -= up[1]*4;
|
|
boltPoint[2] -= up[2]*4;
|
|
|
|
boltPoint[0] += right[0]*10;
|
|
boltPoint[1] += right[1]*10;
|
|
boltPoint[2] += right[2]*10;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
void G_GetDismemberBolt(gentity_t *self, vec3_t boltPoint, int limbType)
|
|
{
|
|
int useBolt = self->genericValue5;
|
|
vec3_t properOrigin, properAngles, addVel;
|
|
//vec3_t legAxis[3];
|
|
mdxaBone_t boltMatrix;
|
|
float fVSpeed = 0;
|
|
char *rotateBone = NULL;
|
|
|
|
switch (limbType)
|
|
{
|
|
case G2_MODELPART_HEAD:
|
|
rotateBone = "cranium";
|
|
break;
|
|
case G2_MODELPART_WAIST:
|
|
if (self->localAnimIndex <= 1)
|
|
{ //humanoid
|
|
rotateBone = "thoracic";
|
|
}
|
|
else
|
|
{
|
|
rotateBone = "pelvis";
|
|
}
|
|
break;
|
|
case G2_MODELPART_LARM:
|
|
rotateBone = "lradius";
|
|
break;
|
|
case G2_MODELPART_RARM:
|
|
rotateBone = "rradius";
|
|
break;
|
|
case G2_MODELPART_RHAND:
|
|
rotateBone = "rhand";
|
|
break;
|
|
case G2_MODELPART_LLEG:
|
|
rotateBone = "ltibia";
|
|
break;
|
|
case G2_MODELPART_RLEG:
|
|
rotateBone = "rtibia";
|
|
break;
|
|
default:
|
|
rotateBone = "rtibia";
|
|
break;
|
|
}
|
|
|
|
useBolt = trap_G2API_AddBolt(self->ghoul2, 0, rotateBone);
|
|
|
|
VectorCopy(self->client->ps.origin, properOrigin);
|
|
VectorCopy(self->client->ps.viewangles, properAngles);
|
|
|
|
//try to predict the origin based on velocity so it's more like what the client is seeing
|
|
VectorCopy(self->client->ps.velocity, addVel);
|
|
VectorNormalize(addVel);
|
|
|
|
if (self->client->ps.velocity[0] < 0)
|
|
{
|
|
fVSpeed += (-self->client->ps.velocity[0]);
|
|
}
|
|
else
|
|
{
|
|
fVSpeed += self->client->ps.velocity[0];
|
|
}
|
|
if (self->client->ps.velocity[1] < 0)
|
|
{
|
|
fVSpeed += (-self->client->ps.velocity[1]);
|
|
}
|
|
else
|
|
{
|
|
fVSpeed += self->client->ps.velocity[1];
|
|
}
|
|
if (self->client->ps.velocity[2] < 0)
|
|
{
|
|
fVSpeed += (-self->client->ps.velocity[2]);
|
|
}
|
|
else
|
|
{
|
|
fVSpeed += self->client->ps.velocity[2];
|
|
}
|
|
|
|
fVSpeed *= 0.08;
|
|
|
|
properOrigin[0] += addVel[0]*fVSpeed;
|
|
properOrigin[1] += addVel[1]*fVSpeed;
|
|
properOrigin[2] += addVel[2]*fVSpeed;
|
|
|
|
properAngles[0] = 0;
|
|
properAngles[1] = self->client->ps.viewangles[YAW];
|
|
properAngles[2] = 0;
|
|
|
|
trap_G2API_GetBoltMatrix(self->ghoul2, 0, useBolt, &boltMatrix, properAngles, properOrigin, level.time, NULL, self->modelScale);
|
|
|
|
boltPoint[0] = boltMatrix.matrix[0][3];
|
|
boltPoint[1] = boltMatrix.matrix[1][3];
|
|
boltPoint[2] = boltMatrix.matrix[2][3];
|
|
|
|
trap_G2API_GetBoltMatrix(self->ghoul2, 1, 0, &boltMatrix, properAngles, properOrigin, level.time, NULL, self->modelScale);
|
|
|
|
if (self->client && limbType == G2_MODELPART_RHAND)
|
|
{ //Make some saber hit sparks over the severed wrist area
|
|
vec3_t boltAngles;
|
|
gentity_t *te;
|
|
|
|
boltAngles[0] = -boltMatrix.matrix[0][1];
|
|
boltAngles[1] = -boltMatrix.matrix[1][1];
|
|
boltAngles[2] = -boltMatrix.matrix[2][1];
|
|
|
|
te = G_TempEntity( boltPoint, EV_SABER_HIT );
|
|
te->s.otherEntityNum = self->s.number;
|
|
te->s.otherEntityNum2 = ENTITYNUM_NONE;
|
|
te->s.weapon = 0;//saberNum
|
|
te->s.legsAnim = 0;//bladeNum
|
|
|
|
VectorCopy(boltPoint, te->s.origin);
|
|
VectorCopy(boltAngles, te->s.angles);
|
|
|
|
if (!te->s.angles[0] && !te->s.angles[1] && !te->s.angles[2])
|
|
{ //don't let it play with no direction
|
|
te->s.angles[1] = 1;
|
|
}
|
|
|
|
te->s.eventParm = 16; //lots of sparks
|
|
}
|
|
}
|
|
|
|
void LimbTouch( gentity_t *self, gentity_t *other, trace_t *trace )
|
|
{
|
|
}
|
|
|
|
void LimbThink( gentity_t *ent )
|
|
{
|
|
float gravity = 3.0f;
|
|
float mass = 0.09f;
|
|
float bounce = 1.3f;
|
|
|
|
switch (ent->s.modelGhoul2)
|
|
{
|
|
case G2_MODELPART_HEAD:
|
|
mass = 0.08f;
|
|
bounce = 1.4f;
|
|
break;
|
|
case G2_MODELPART_WAIST:
|
|
mass = 0.1f;
|
|
bounce = 1.2f;
|
|
break;
|
|
case G2_MODELPART_LARM:
|
|
case G2_MODELPART_RARM:
|
|
case G2_MODELPART_RHAND:
|
|
case G2_MODELPART_LLEG:
|
|
case G2_MODELPART_RLEG:
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (ent->speed < level.time)
|
|
{
|
|
ent->think = G_FreeEntity;
|
|
ent->nextthink = level.time;
|
|
return;
|
|
}
|
|
|
|
if (ent->genericValue5 <= level.time)
|
|
{ //this will be every frame by standard, but we want to compensate in case sv_fps is not 20.
|
|
G_RunExPhys(ent, gravity, mass, bounce, qtrue, NULL, 0);
|
|
ent->genericValue5 = level.time + 50;
|
|
}
|
|
|
|
ent->nextthink = level.time;
|
|
}
|
|
|
|
#include "../namespace_begin.h"
|
|
extern qboolean BG_GetRootSurfNameWithVariant( void *ghoul2, const char *rootSurfName, char *returnSurfName, int returnSize );
|
|
#include "../namespace_end.h"
|
|
|
|
void G_Dismember( gentity_t *ent, gentity_t *enemy, vec3_t point, int limbType, float limbRollBase, float limbPitchBase, int deathAnim, qboolean postDeath )
|
|
{
|
|
vec3_t newPoint, dir, vel;
|
|
gentity_t *limb;
|
|
char limbName[MAX_QPATH];
|
|
char stubName[MAX_QPATH];
|
|
char stubCapName[MAX_QPATH];
|
|
|
|
if (limbType == G2_MODELPART_HEAD)
|
|
{
|
|
Q_strncpyz( limbName , "head", sizeof( limbName ) );
|
|
Q_strncpyz( stubCapName, "torso_cap_head", sizeof( stubCapName ) );
|
|
}
|
|
else if (limbType == G2_MODELPART_WAIST)
|
|
{
|
|
Q_strncpyz( limbName, "torso", sizeof( limbName ) );
|
|
Q_strncpyz( stubCapName, "hips_cap_torso", sizeof( stubCapName ) );
|
|
}
|
|
else if (limbType == G2_MODELPART_LARM)
|
|
{
|
|
BG_GetRootSurfNameWithVariant( ent->ghoul2, "l_arm", limbName, sizeof(limbName) );
|
|
BG_GetRootSurfNameWithVariant( ent->ghoul2, "torso", stubName, sizeof(stubName) );
|
|
Com_sprintf( stubCapName, sizeof( stubCapName), "%s_cap_l_arm", stubName );
|
|
}
|
|
else if (limbType == G2_MODELPART_RARM)
|
|
{
|
|
BG_GetRootSurfNameWithVariant( ent->ghoul2, "r_arm", limbName, sizeof(limbName) );
|
|
BG_GetRootSurfNameWithVariant( ent->ghoul2, "torso", stubName, sizeof(stubName) );
|
|
Com_sprintf( stubCapName, sizeof( stubCapName), "%s_cap_r_arm", stubName );
|
|
}
|
|
else if (limbType == G2_MODELPART_RHAND)
|
|
{
|
|
BG_GetRootSurfNameWithVariant( ent->ghoul2, "r_hand", limbName, sizeof(limbName) );
|
|
BG_GetRootSurfNameWithVariant( ent->ghoul2, "r_arm", stubName, sizeof(stubName) );
|
|
Com_sprintf( stubCapName, sizeof( stubCapName), "%s_cap_r_hand", stubName );
|
|
}
|
|
else if (limbType == G2_MODELPART_LLEG)
|
|
{
|
|
BG_GetRootSurfNameWithVariant( ent->ghoul2, "l_leg", limbName, sizeof(limbName) );
|
|
BG_GetRootSurfNameWithVariant( ent->ghoul2, "hips", stubName, sizeof(stubName) );
|
|
Com_sprintf( stubCapName, sizeof( stubCapName), "%s_cap_l_leg", stubName );
|
|
}
|
|
else if (limbType == G2_MODELPART_RLEG)
|
|
{
|
|
BG_GetRootSurfNameWithVariant( ent->ghoul2, "r_leg", limbName, sizeof(limbName) );
|
|
BG_GetRootSurfNameWithVariant( ent->ghoul2, "hips", stubName, sizeof(stubName) );
|
|
Com_sprintf( stubCapName, sizeof( stubCapName), "%s_cap_r_leg", stubName );
|
|
}
|
|
else
|
|
{//umm... just default to the right leg, I guess (same as on client)
|
|
BG_GetRootSurfNameWithVariant( ent->ghoul2, "r_leg", limbName, sizeof(limbName) );
|
|
BG_GetRootSurfNameWithVariant( ent->ghoul2, "hips", stubName, sizeof(stubName) );
|
|
Com_sprintf( stubCapName, sizeof( stubCapName), "%s_cap_r_leg", stubName );
|
|
}
|
|
|
|
if (ent->ghoul2 && limbName && trap_G2API_GetSurfaceRenderStatus(ent->ghoul2, 0, limbName))
|
|
{ //is it already off? If so there's no reason to be doing it again, so get out of here.
|
|
return;
|
|
}
|
|
|
|
VectorCopy( point, newPoint );
|
|
limb = G_Spawn();
|
|
limb->classname = "playerlimb";
|
|
|
|
/*
|
|
if (limbType == G2_MODELPART_WAIST)
|
|
{ //slight hack
|
|
newPoint[2] += 1;
|
|
}
|
|
*/
|
|
|
|
G_SetOrigin( limb, newPoint );
|
|
VectorCopy( newPoint, limb->s.pos.trBase );
|
|
limb->think = LimbThink;
|
|
limb->touch = LimbTouch;
|
|
limb->speed = level.time + Q_irand(8000, 16000);
|
|
limb->nextthink = level.time + FRAMETIME;
|
|
|
|
limb->r.svFlags = SVF_USE_CURRENT_ORIGIN;
|
|
limb->clipmask = MASK_SOLID;
|
|
limb->r.contents = CONTENTS_TRIGGER;
|
|
limb->physicsObject = qtrue;
|
|
VectorSet( limb->r.mins, -6.0f, -6.0f, -3.0f );
|
|
VectorSet( limb->r.maxs, 6.0f, 6.0f, 6.0f );
|
|
|
|
limb->s.g2radius = 200;
|
|
|
|
limb->s.eType = ET_GENERAL;
|
|
limb->s.weapon = G2_MODEL_PART;
|
|
limb->s.modelGhoul2 = limbType;
|
|
limb->s.modelindex = ent->s.number;
|
|
if (!ent->client)
|
|
{
|
|
limb->s.modelindex = -1;
|
|
limb->s.otherEntityNum2 = ent->s.number;
|
|
}
|
|
|
|
VectorClear(limb->s.apos.trDelta);
|
|
|
|
if (ent->client)
|
|
{
|
|
VectorCopy(ent->client->ps.viewangles, limb->r.currentAngles);
|
|
VectorCopy(ent->client->ps.viewangles, limb->s.apos.trBase);
|
|
}
|
|
else
|
|
{
|
|
VectorCopy(ent->r.currentAngles, limb->r.currentAngles);
|
|
VectorCopy(ent->r.currentAngles, limb->s.apos.trBase);
|
|
}
|
|
|
|
//Set up the ExPhys values for the entity.
|
|
limb->epGravFactor = 0;
|
|
VectorClear(limb->epVelocity);
|
|
VectorSubtract( point, ent->r.currentOrigin, dir );
|
|
VectorNormalize( dir );
|
|
if (ent->client)
|
|
{
|
|
VectorCopy(ent->client->ps.velocity, vel);
|
|
}
|
|
else
|
|
{
|
|
VectorCopy(ent->s.pos.trDelta, vel);
|
|
}
|
|
VectorMA( vel, 80, dir, limb->epVelocity );
|
|
|
|
//add some vertical velocity
|
|
if (limbType == G2_MODELPART_HEAD ||
|
|
limbType == G2_MODELPART_WAIST)
|
|
{
|
|
limb->epVelocity[2] += 10;
|
|
}
|
|
|
|
if (enemy && enemy->client && ent && ent != enemy && ent->s.number != enemy->s.number &&
|
|
enemy->client->ps.weapon == WP_SABER && enemy->client->olderIsValid &&
|
|
(level.time - enemy->client->lastSaberStorageTime) < 200)
|
|
{ //The enemy has valid saber positions between this and last frame. Use them to factor in direction of the limb.
|
|
vec3_t dif;
|
|
float totalDistance;
|
|
const float distScale = 1.2f;
|
|
|
|
//scale down the initial velocity first, which is based on the speed of the limb owner.
|
|
//ExPhys object velocity operates on a slightly different scale than Q3-based physics velocity.
|
|
VectorScale(limb->epVelocity, 0.4f, limb->epVelocity);
|
|
|
|
VectorSubtract(enemy->client->lastSaberBase_Always, enemy->client->olderSaberBase, dif);
|
|
totalDistance = VectorNormalize(dif);
|
|
|
|
VectorScale(dif, totalDistance*distScale, dif);
|
|
VectorAdd(limb->epVelocity, dif, limb->epVelocity);
|
|
|
|
if (ent->client && (ent->client->ps.torsoTimer > 0 || !BG_InDeathAnim(ent->client->ps.torsoAnim)))
|
|
{ //if he's done with his death anim we don't actually want the limbs going far
|
|
vec3_t preVel;
|
|
|
|
VectorCopy(limb->epVelocity, preVel);
|
|
preVel[2] = 0;
|
|
totalDistance = VectorNormalize(preVel);
|
|
|
|
if (totalDistance < 40.0f)
|
|
{
|
|
float mAmt = 40.0f;//60.0f/totalDistance;
|
|
|
|
limb->epVelocity[0] = preVel[0]*mAmt;
|
|
limb->epVelocity[1] = preVel[1]*mAmt;
|
|
}
|
|
}
|
|
else if (ent->client)
|
|
{
|
|
VectorScale(limb->epVelocity, 0.3f, limb->epVelocity);
|
|
}
|
|
}
|
|
|
|
if (ent->s.eType == ET_NPC && ent->ghoul2 && limbName && stubCapName)
|
|
{ //if it's an npc remove these surfs on the server too. For players we don't even care cause there's no further dismemberment after death.
|
|
trap_G2API_SetSurfaceOnOff(ent->ghoul2, limbName, 0x00000100);
|
|
trap_G2API_SetSurfaceOnOff(ent->ghoul2, stubCapName, 0);
|
|
}
|
|
|
|
limb->s.customRGBA[0] = ent->s.customRGBA[0];
|
|
limb->s.customRGBA[1] = ent->s.customRGBA[1];
|
|
limb->s.customRGBA[2] = ent->s.customRGBA[2];
|
|
limb->s.customRGBA[3] = ent->s.customRGBA[3];
|
|
|
|
trap_LinkEntity( limb );
|
|
}
|
|
|
|
void DismembermentTest(gentity_t *self)
|
|
{
|
|
int sect = G2_MODELPART_HEAD;
|
|
vec3_t boltPoint;
|
|
|
|
while (sect <= G2_MODELPART_RLEG)
|
|
{
|
|
G_GetDismemberBolt(self, boltPoint, sect);
|
|
G_Dismember( self, self, boltPoint, sect, 90, 0, BOTH_DEATH1, qfalse );
|
|
sect++;
|
|
}
|
|
}
|
|
|
|
void DismembermentByNum(gentity_t *self, int num)
|
|
{
|
|
int sect = G2_MODELPART_HEAD;
|
|
vec3_t boltPoint;
|
|
|
|
switch (num)
|
|
{
|
|
case 0:
|
|
sect = G2_MODELPART_HEAD;
|
|
break;
|
|
case 1:
|
|
sect = G2_MODELPART_WAIST;
|
|
break;
|
|
case 2:
|
|
sect = G2_MODELPART_LARM;
|
|
break;
|
|
case 3:
|
|
sect = G2_MODELPART_RARM;
|
|
break;
|
|
case 4:
|
|
sect = G2_MODELPART_RHAND;
|
|
break;
|
|
case 5:
|
|
sect = G2_MODELPART_LLEG;
|
|
break;
|
|
case 6:
|
|
sect = G2_MODELPART_RLEG;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
G_GetDismemberBolt(self, boltPoint, sect);
|
|
G_Dismember( self, self, boltPoint, sect, 90, 0, BOTH_DEATH1, qfalse );
|
|
}
|
|
|
|
int G_GetHitQuad( gentity_t *self, vec3_t hitloc )
|
|
{
|
|
vec3_t diff, fwdangles={0,0,0}, right;
|
|
vec3_t clEye;
|
|
float rightdot;
|
|
float zdiff;
|
|
int hitLoc = gPainHitLoc;
|
|
|
|
if (self->client)
|
|
{
|
|
VectorCopy(self->client->ps.origin, clEye);
|
|
clEye[2] += self->client->ps.viewheight;
|
|
}
|
|
else
|
|
{
|
|
VectorCopy(self->s.pos.trBase, clEye);
|
|
clEye[2] += 16;
|
|
}
|
|
|
|
VectorSubtract( hitloc, clEye, diff );
|
|
diff[2] = 0;
|
|
VectorNormalize( diff );
|
|
|
|
if (self->client)
|
|
{
|
|
fwdangles[1] = self->client->ps.viewangles[1];
|
|
}
|
|
else
|
|
{
|
|
fwdangles[1] = self->s.apos.trBase[1];
|
|
}
|
|
// Ultimately we might care if the shot was ahead or behind, but for now, just quadrant is fine.
|
|
AngleVectors( fwdangles, NULL, right, NULL );
|
|
|
|
rightdot = DotProduct(right, diff);
|
|
zdiff = hitloc[2] - clEye[2];
|
|
|
|
if ( zdiff > 0 )
|
|
{
|
|
if ( rightdot > 0.3 )
|
|
{
|
|
hitLoc = G2_MODELPART_RARM;
|
|
}
|
|
else if ( rightdot < -0.3 )
|
|
{
|
|
hitLoc = G2_MODELPART_LARM;
|
|
}
|
|
else
|
|
{
|
|
hitLoc = G2_MODELPART_HEAD;
|
|
}
|
|
}
|
|
else if ( zdiff > -20 )
|
|
{
|
|
if ( rightdot > 0.1 )
|
|
{
|
|
hitLoc = G2_MODELPART_RARM;
|
|
}
|
|
else if ( rightdot < -0.1 )
|
|
{
|
|
hitLoc = G2_MODELPART_LARM;
|
|
}
|
|
else
|
|
{
|
|
hitLoc = G2_MODELPART_HEAD;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( rightdot >= 0 )
|
|
{
|
|
hitLoc = G2_MODELPART_RLEG;
|
|
}
|
|
else
|
|
{
|
|
hitLoc = G2_MODELPART_LLEG;
|
|
}
|
|
}
|
|
|
|
return hitLoc;
|
|
}
|
|
|
|
int gGAvoidDismember = 0;
|
|
|
|
void UpdateClientRenderBolts(gentity_t *self, vec3_t renderOrigin, vec3_t renderAngles);
|
|
|
|
qboolean G_GetHitLocFromSurfName( gentity_t *ent, const char *surfName, int *hitLoc, vec3_t point, vec3_t dir, vec3_t bladeDir, int mod )
|
|
{
|
|
qboolean dismember = qfalse;
|
|
int actualTime;
|
|
int kneeLBolt = -1;
|
|
int kneeRBolt = -1;
|
|
int handRBolt = -1;
|
|
int handLBolt = -1;
|
|
int footRBolt = -1;
|
|
int footLBolt = -1;
|
|
|
|
*hitLoc = HL_NONE;
|
|
|
|
if ( !surfName || !surfName[0] )
|
|
{
|
|
return qfalse;
|
|
}
|
|
|
|
if( !ent->client )
|
|
{
|
|
return qfalse;
|
|
}
|
|
|
|
if (!point)
|
|
{
|
|
return qfalse;
|
|
}
|
|
|
|
if ( ent->client
|
|
&& ( ent->client->NPC_class == CLASS_R2D2
|
|
|| ent->client->NPC_class == CLASS_R2D2
|
|
|| ent->client->NPC_class == CLASS_GONK
|
|
|| ent->client->NPC_class == CLASS_MOUSE
|
|
|| ent->client->NPC_class == CLASS_SENTRY
|
|
|| ent->client->NPC_class == CLASS_INTERROGATOR
|
|
|| ent->client->NPC_class == CLASS_SENTRY
|
|
|| ent->client->NPC_class == CLASS_PROBE ) )
|
|
{//we don't care about per-surface hit-locations or dismemberment for these guys
|
|
return qfalse;
|
|
}
|
|
|
|
if (ent->localAnimIndex <= 1)
|
|
{ //humanoid
|
|
handLBolt = trap_G2API_AddBolt(ent->ghoul2, 0, "*l_hand");
|
|
handRBolt = trap_G2API_AddBolt(ent->ghoul2, 0, "*r_hand");
|
|
kneeLBolt = trap_G2API_AddBolt(ent->ghoul2, 0, "*hips_l_knee");
|
|
kneeRBolt = trap_G2API_AddBolt(ent->ghoul2, 0, "*hips_r_knee");
|
|
footLBolt = trap_G2API_AddBolt(ent->ghoul2, 0, "*l_leg_foot");
|
|
footRBolt = trap_G2API_AddBolt(ent->ghoul2, 0, "*r_leg_foot");
|
|
}
|
|
|
|
if ( ent->client && (ent->client->NPC_class == CLASS_ATST) )
|
|
{
|
|
//FIXME: almost impossible to hit these... perhaps we should
|
|
// check for splashDamage and do radius damage to these parts?
|
|
// Or, if we ever get bbox G2 traces, that may fix it, too
|
|
if (!Q_stricmp("head_light_blaster_cann",surfName))
|
|
{
|
|
*hitLoc = HL_ARM_LT;
|
|
}
|
|
else if (!Q_stricmp("head_concussion_charger",surfName))
|
|
{
|
|
*hitLoc = HL_ARM_RT;
|
|
}
|
|
return(qfalse);
|
|
}
|
|
else if ( ent->client && (ent->client->NPC_class == CLASS_MARK1) )
|
|
{
|
|
if (!Q_stricmp("l_arm",surfName))
|
|
{
|
|
*hitLoc = HL_ARM_LT;
|
|
}
|
|
else if (!Q_stricmp("r_arm",surfName))
|
|
{
|
|
*hitLoc = HL_ARM_RT;
|
|
}
|
|
else if (!Q_stricmp("torso_front",surfName))
|
|
{
|
|
*hitLoc = HL_CHEST;
|
|
}
|
|
else if (!Q_stricmp("torso_tube1",surfName))
|
|
{
|
|
*hitLoc = HL_GENERIC1;
|
|
}
|
|
else if (!Q_stricmp("torso_tube2",surfName))
|
|
{
|
|
*hitLoc = HL_GENERIC2;
|
|
}
|
|
else if (!Q_stricmp("torso_tube3",surfName))
|
|
{
|
|
*hitLoc = HL_GENERIC3;
|
|
}
|
|
else if (!Q_stricmp("torso_tube4",surfName))
|
|
{
|
|
*hitLoc = HL_GENERIC4;
|
|
}
|
|
else if (!Q_stricmp("torso_tube5",surfName))
|
|
{
|
|
*hitLoc = HL_GENERIC5;
|
|
}
|
|
else if (!Q_stricmp("torso_tube6",surfName))
|
|
{
|
|
*hitLoc = HL_GENERIC6;
|
|
}
|
|
return(qfalse);
|
|
}
|
|
else if ( ent->client && (ent->client->NPC_class == CLASS_MARK2) )
|
|
{
|
|
if (!Q_stricmp("torso_canister1",surfName))
|
|
{
|
|
*hitLoc = HL_GENERIC1;
|
|
}
|
|
else if (!Q_stricmp("torso_canister2",surfName))
|
|
{
|
|
*hitLoc = HL_GENERIC2;
|
|
}
|
|
else if (!Q_stricmp("torso_canister3",surfName))
|
|
{
|
|
*hitLoc = HL_GENERIC3;
|
|
}
|
|
return(qfalse);
|
|
}
|
|
else if ( ent->client && (ent->client->NPC_class == CLASS_GALAKMECH) )
|
|
{
|
|
if (!Q_stricmp("torso_antenna",surfName)||!Q_stricmp("torso_antenna_base",surfName))
|
|
{
|
|
*hitLoc = HL_GENERIC1;
|
|
}
|
|
else if (!Q_stricmp("torso_shield",surfName))
|
|
{
|
|
*hitLoc = HL_GENERIC2;
|
|
}
|
|
else
|
|
{
|
|
*hitLoc = HL_CHEST;
|
|
}
|
|
return(qfalse);
|
|
}
|
|
|
|
//FIXME: check the hitLoc and hitDir against the cap tag for the place
|
|
//where the split will be- if the hit dir is roughly perpendicular to
|
|
//the direction of the cap, then the split is allowed, otherwise we
|
|
//hit it at the wrong angle and should not dismember...
|
|
actualTime = level.time;
|
|
if ( !Q_strncmp( "hips", surfName, 4 ) )
|
|
{//FIXME: test properly for legs
|
|
*hitLoc = HL_WAIST;
|
|
if ( ent->client != NULL && ent->ghoul2 )
|
|
{
|
|
mdxaBone_t boltMatrix;
|
|
vec3_t tagOrg, angles;
|
|
|
|
VectorSet( angles, 0, ent->r.currentAngles[YAW], 0 );
|
|
if (kneeLBolt>=0)
|
|
{
|
|
trap_G2API_GetBoltMatrix( ent->ghoul2, 0, kneeLBolt,
|
|
&boltMatrix, angles, ent->r.currentOrigin,
|
|
actualTime, NULL, ent->modelScale );
|
|
BG_GiveMeVectorFromMatrix( &boltMatrix, ORIGIN, tagOrg );
|
|
if ( DistanceSquared( point, tagOrg ) < 100 )
|
|
{//actually hit the knee
|
|
*hitLoc = HL_LEG_LT;
|
|
}
|
|
}
|
|
if (*hitLoc == HL_WAIST)
|
|
{
|
|
if (kneeRBolt>=0)
|
|
{
|
|
trap_G2API_GetBoltMatrix( ent->ghoul2, 0, kneeRBolt,
|
|
&boltMatrix, angles, ent->r.currentOrigin,
|
|
actualTime, NULL, ent->modelScale );
|
|
BG_GiveMeVectorFromMatrix( &boltMatrix, ORIGIN, tagOrg );
|
|
if ( DistanceSquared( point, tagOrg ) < 100 )
|
|
{//actually hit the knee
|
|
*hitLoc = HL_LEG_RT;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if ( !Q_strncmp( "torso", surfName, 5 ) )
|
|
{
|
|
if ( !ent->client )
|
|
{
|
|
*hitLoc = HL_CHEST;
|
|
}
|
|
else
|
|
{
|
|
vec3_t t_fwd, t_rt, t_up, dirToImpact;
|
|
float frontSide, rightSide, upSide;
|
|
AngleVectors( ent->client->renderInfo.torsoAngles, t_fwd, t_rt, t_up );
|
|
|
|
if (ent->client->renderInfo.boltValidityTime != level.time)
|
|
{
|
|
vec3_t renderAng;
|
|
|
|
renderAng[0] = 0;
|
|
renderAng[1] = ent->client->ps.viewangles[YAW];
|
|
renderAng[2] = 0;
|
|
|
|
UpdateClientRenderBolts(ent, ent->client->ps.origin, renderAng);
|
|
}
|
|
|
|
VectorSubtract( point, ent->client->renderInfo.torsoPoint, dirToImpact );
|
|
frontSide = DotProduct( t_fwd, dirToImpact );
|
|
rightSide = DotProduct( t_rt, dirToImpact );
|
|
upSide = DotProduct( t_up, dirToImpact );
|
|
if ( upSide < -10 )
|
|
{//hit at waist
|
|
*hitLoc = HL_WAIST;
|
|
}
|
|
else
|
|
{//hit on upper torso
|
|
if ( rightSide > 4 )
|
|
{
|
|
*hitLoc = HL_ARM_RT;
|
|
}
|
|
else if ( rightSide < -4 )
|
|
{
|
|
*hitLoc = HL_ARM_LT;
|
|
}
|
|
else if ( rightSide > 2 )
|
|
{
|
|
if ( frontSide > 0 )
|
|
{
|
|
*hitLoc = HL_CHEST_RT;
|
|
}
|
|
else
|
|
{
|
|
*hitLoc = HL_BACK_RT;
|
|
}
|
|
}
|
|
else if ( rightSide < -2 )
|
|
{
|
|
if ( frontSide > 0 )
|
|
{
|
|
*hitLoc = HL_CHEST_LT;
|
|
}
|
|
else
|
|
{
|
|
*hitLoc = HL_BACK_LT;
|
|
}
|
|
}
|
|
else if ( upSide > -3 && mod == MOD_SABER )
|
|
{
|
|
*hitLoc = HL_HEAD;
|
|
}
|
|
else if ( frontSide > 0 )
|
|
{
|
|
*hitLoc = HL_CHEST;
|
|
}
|
|
else
|
|
{
|
|
*hitLoc = HL_BACK;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if ( !Q_strncmp( "head", surfName, 4 ) )
|
|
{
|
|
*hitLoc = HL_HEAD;
|
|
}
|
|
else if ( !Q_strncmp( "r_arm", surfName, 5 ) )
|
|
{
|
|
*hitLoc = HL_ARM_RT;
|
|
if ( ent->client != NULL && ent->ghoul2 )
|
|
{
|
|
mdxaBone_t boltMatrix;
|
|
vec3_t tagOrg, angles;
|
|
|
|
VectorSet( angles, 0, ent->r.currentAngles[YAW], 0 );
|
|
if (handRBolt>=0)
|
|
{
|
|
trap_G2API_GetBoltMatrix( ent->ghoul2, 0, handRBolt,
|
|
&boltMatrix, angles, ent->r.currentOrigin,
|
|
actualTime, NULL, ent->modelScale );
|
|
BG_GiveMeVectorFromMatrix( &boltMatrix, ORIGIN, tagOrg );
|
|
if ( DistanceSquared( point, tagOrg ) < 256 )
|
|
{//actually hit the hand
|
|
*hitLoc = HL_HAND_RT;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if ( !Q_strncmp( "l_arm", surfName, 5 ) )
|
|
{
|
|
*hitLoc = HL_ARM_LT;
|
|
if ( ent->client != NULL && ent->ghoul2 )
|
|
{
|
|
mdxaBone_t boltMatrix;
|
|
vec3_t tagOrg, angles;
|
|
|
|
VectorSet( angles, 0, ent->r.currentAngles[YAW], 0 );
|
|
if (handLBolt>=0)
|
|
{
|
|
trap_G2API_GetBoltMatrix( ent->ghoul2, 0, handLBolt,
|
|
&boltMatrix, angles, ent->r.currentOrigin,
|
|
actualTime, NULL, ent->modelScale );
|
|
BG_GiveMeVectorFromMatrix( &boltMatrix, ORIGIN, tagOrg );
|
|
if ( DistanceSquared( point, tagOrg ) < 256 )
|
|
{//actually hit the hand
|
|
*hitLoc = HL_HAND_LT;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if ( !Q_strncmp( "r_leg", surfName, 5 ) )
|
|
{
|
|
*hitLoc = HL_LEG_RT;
|
|
if ( ent->client != NULL && ent->ghoul2 )
|
|
{
|
|
mdxaBone_t boltMatrix;
|
|
vec3_t tagOrg, angles;
|
|
|
|
VectorSet( angles, 0, ent->r.currentAngles[YAW], 0 );
|
|
if (footRBolt>=0)
|
|
{
|
|
trap_G2API_GetBoltMatrix( ent->ghoul2, 0, footRBolt,
|
|
&boltMatrix, angles, ent->r.currentOrigin,
|
|
actualTime, NULL, ent->modelScale );
|
|
BG_GiveMeVectorFromMatrix( &boltMatrix, ORIGIN, tagOrg );
|
|
if ( DistanceSquared( point, tagOrg ) < 100 )
|
|
{//actually hit the foot
|
|
*hitLoc = HL_FOOT_RT;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if ( !Q_strncmp( "l_leg", surfName, 5 ) )
|
|
{
|
|
*hitLoc = HL_LEG_LT;
|
|
if ( ent->client != NULL && ent->ghoul2 )
|
|
{
|
|
mdxaBone_t boltMatrix;
|
|
vec3_t tagOrg, angles;
|
|
|
|
VectorSet( angles, 0, ent->r.currentAngles[YAW], 0 );
|
|
if (footLBolt>=0)
|
|
{
|
|
trap_G2API_GetBoltMatrix( ent->ghoul2, 0, footLBolt,
|
|
&boltMatrix, angles, ent->r.currentOrigin,
|
|
actualTime, NULL, ent->modelScale );
|
|
BG_GiveMeVectorFromMatrix( &boltMatrix, ORIGIN, tagOrg );
|
|
if ( DistanceSquared( point, tagOrg ) < 100 )
|
|
{//actually hit the foot
|
|
*hitLoc = HL_FOOT_LT;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if ( !Q_strncmp( "r_hand", surfName, 6 ) || !Q_strncmp( "w_", surfName, 2 ) )
|
|
{//right hand or weapon
|
|
*hitLoc = HL_HAND_RT;
|
|
}
|
|
else if ( !Q_strncmp( "l_hand", surfName, 6 ) )
|
|
{
|
|
*hitLoc = HL_HAND_LT;
|
|
}
|
|
/*
|
|
#ifdef _DEBUG
|
|
else
|
|
{
|
|
Com_Printf( "ERROR: surface %s does not belong to any hitLocation!!!\n", surfName );
|
|
}
|
|
#endif //_DEBUG
|
|
*/
|
|
|
|
//if ( g_dismemberment->integer >= 11381138 || !ent->client->dismembered )
|
|
if (g_dismember.integer == 100)
|
|
{ //full probability...
|
|
if ( ent->client && ent->client->NPC_class == CLASS_PROTOCOL )
|
|
{
|
|
dismember = qtrue;
|
|
}
|
|
else if ( dir && (dir[0] || dir[1] || dir[2]) &&
|
|
bladeDir && (bladeDir[0] || bladeDir[1] || bladeDir[2]) )
|
|
{//we care about direction (presumably for dismemberment)
|
|
//if ( g_dismemberProbabilities->value<=0.0f||G_Dismemberable( ent, *hitLoc ) )
|
|
if (1) //Fix me?
|
|
{//either we don't care about probabilties or the probability let us continue
|
|
char *tagName = NULL;
|
|
float aoa = 0.5f;
|
|
//dir must be roughly perpendicular to the hitLoc's cap bolt
|
|
switch ( *hitLoc )
|
|
{
|
|
case HL_LEG_RT:
|
|
tagName = "*hips_cap_r_leg";
|
|
break;
|
|
case HL_LEG_LT:
|
|
tagName = "*hips_cap_l_leg";
|
|
break;
|
|
case HL_WAIST:
|
|
tagName = "*hips_cap_torso";
|
|
aoa = 0.25f;
|
|
break;
|
|
case HL_CHEST_RT:
|
|
case HL_ARM_RT:
|
|
case HL_BACK_LT:
|
|
tagName = "*torso_cap_r_arm";
|
|
break;
|
|
case HL_CHEST_LT:
|
|
case HL_ARM_LT:
|
|
case HL_BACK_RT:
|
|
tagName = "*torso_cap_l_arm";
|
|
break;
|
|
case HL_HAND_RT:
|
|
tagName = "*r_arm_cap_r_hand";
|
|
break;
|
|
case HL_HAND_LT:
|
|
tagName = "*l_arm_cap_l_hand";
|
|
break;
|
|
case HL_HEAD:
|
|
tagName = "*torso_cap_head";
|
|
aoa = 0.25f;
|
|
break;
|
|
case HL_CHEST:
|
|
case HL_BACK:
|
|
case HL_FOOT_RT:
|
|
case HL_FOOT_LT:
|
|
default:
|
|
//no dismemberment possible with these, so no checks needed
|
|
break;
|
|
}
|
|
if ( tagName )
|
|
{
|
|
int tagBolt = trap_G2API_AddBolt( ent->ghoul2, 0, tagName );
|
|
if ( tagBolt != -1 )
|
|
{
|
|
mdxaBone_t boltMatrix;
|
|
vec3_t tagOrg, tagDir, angles;
|
|
|
|
VectorSet( angles, 0, ent->r.currentAngles[YAW], 0 );
|
|
trap_G2API_GetBoltMatrix( ent->ghoul2, 0, tagBolt,
|
|
&boltMatrix, angles, ent->r.currentOrigin,
|
|
actualTime, NULL, ent->modelScale );
|
|
BG_GiveMeVectorFromMatrix( &boltMatrix, ORIGIN, tagOrg );
|
|
BG_GiveMeVectorFromMatrix( &boltMatrix, NEGATIVE_Y, tagDir );
|
|
if ( DistanceSquared( point, tagOrg ) < 256 )
|
|
{//hit close
|
|
float dot = DotProduct( dir, tagDir );
|
|
if ( dot < aoa && dot > -aoa )
|
|
{//hit roughly perpendicular
|
|
dot = DotProduct( bladeDir, tagDir );
|
|
if ( dot < aoa && dot > -aoa )
|
|
{//blade was roughly perpendicular
|
|
dismember = qtrue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{ //hmm, no direction supplied.
|
|
dismember = qtrue;
|
|
}
|
|
}
|
|
return dismember;
|
|
}
|
|
|
|
void G_CheckForDismemberment(gentity_t *ent, gentity_t *enemy, vec3_t point, int damage, int deathAnim, qboolean postDeath)
|
|
{
|
|
int hitLoc = -1, hitLocUse = -1;
|
|
vec3_t boltPoint;
|
|
int dismember = g_dismember.integer;
|
|
|
|
if (ent->localAnimIndex > 1)
|
|
{
|
|
if (!ent->NPC)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (ent->client->NPC_class != CLASS_PROTOCOL)
|
|
{ //this is the only non-humanoid allowed to do dismemberment.
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (!dismember)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (gGAvoidDismember == 1)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (gGAvoidDismember != 2)
|
|
{ //this means do the dismemberment regardless of randomness and damage
|
|
if (Q_irand(0, 100) > dismember)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (damage < 5)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (gGAvoidDismember == 2)
|
|
{
|
|
hitLoc = HL_HAND_RT;
|
|
}
|
|
else
|
|
{
|
|
if (d_saberGhoul2Collision.integer && ent->client && ent->client->g2LastSurfaceTime == level.time)
|
|
{
|
|
char hitSurface[MAX_QPATH];
|
|
|
|
trap_G2API_GetSurfaceName(ent->ghoul2, ent->client->g2LastSurfaceHit, 0, hitSurface);
|
|
|
|
if (hitSurface[0])
|
|
{
|
|
G_GetHitLocFromSurfName(ent, hitSurface, &hitLoc, point, vec3_origin, vec3_origin, MOD_UNKNOWN);
|
|
}
|
|
}
|
|
|
|
if (hitLoc == -1)
|
|
{
|
|
hitLoc = G_GetHitLocation( ent, point );
|
|
}
|
|
}
|
|
|
|
switch(hitLoc)
|
|
{
|
|
case HL_FOOT_RT:
|
|
case HL_LEG_RT:
|
|
hitLocUse = G2_MODELPART_RLEG;
|
|
break;
|
|
case HL_FOOT_LT:
|
|
case HL_LEG_LT:
|
|
hitLocUse = G2_MODELPART_LLEG;
|
|
break;
|
|
|
|
case HL_WAIST:
|
|
hitLocUse = G2_MODELPART_WAIST;
|
|
break;
|
|
/*
|
|
case HL_BACK_RT:
|
|
case HL_BACK_LT:
|
|
case HL_BACK:
|
|
case HL_CHEST_RT:
|
|
case HL_CHEST_LT:
|
|
case HL_CHEST:
|
|
break;
|
|
*/
|
|
case HL_ARM_RT:
|
|
hitLocUse = G2_MODELPART_RARM;
|
|
break;
|
|
case HL_HAND_RT:
|
|
hitLocUse = G2_MODELPART_RHAND;
|
|
break;
|
|
case HL_ARM_LT:
|
|
case HL_HAND_LT:
|
|
hitLocUse = G2_MODELPART_LARM;
|
|
break;
|
|
case HL_HEAD:
|
|
hitLocUse = G2_MODELPART_HEAD;
|
|
break;
|
|
default:
|
|
hitLocUse = G_GetHitQuad(ent, point);
|
|
break;
|
|
}
|
|
|
|
if (hitLocUse == -1)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (ent->client)
|
|
{
|
|
G_GetDismemberBolt(ent, boltPoint, hitLocUse);
|
|
if ( g_austrian.integer
|
|
&& (g_gametype.integer == GT_DUEL || g_gametype.integer == GT_POWERDUEL) )
|
|
{
|
|
G_LogPrintf( "Duel Dismemberment: %s dismembered at %s\n", ent->client->pers.netname, hitLocName[hitLoc] );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
G_GetDismemberLoc(ent, boltPoint, hitLocUse);
|
|
}
|
|
G_Dismember(ent, enemy, boltPoint, hitLocUse, 90, 0, deathAnim, postDeath);
|
|
}
|
|
|
|
void G_LocationBasedDamageModifier(gentity_t *ent, vec3_t point, int mod, int dflags, int *damage)
|
|
{
|
|
int hitLoc = -1;
|
|
|
|
if (!g_locationBasedDamage.integer)
|
|
{ //then leave it alone
|
|
return;
|
|
}
|
|
|
|
if ( (dflags&DAMAGE_NO_HIT_LOC) )
|
|
{ //then leave it alone
|
|
return;
|
|
}
|
|
|
|
if (mod == MOD_SABER && *damage <= 1)
|
|
{ //don't bother for idle damage
|
|
return;
|
|
}
|
|
|
|
if (!point)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( ent->client && ent->client->NPC_class == CLASS_VEHICLE )
|
|
{//no location-based damage on vehicles
|
|
return;
|
|
}
|
|
|
|
if ((d_saberGhoul2Collision.integer && ent->client && ent->client->g2LastSurfaceTime == level.time && mod == MOD_SABER) || //using ghoul2 collision? Then if the mod is a saber we should have surface data from the last hit (unless thrown).
|
|
(d_projectileGhoul2Collision.integer && ent->client && ent->client->g2LastSurfaceTime == level.time)) //It's safe to assume we died from the projectile that just set our surface index. So, go ahead and use that as the surf I guess.
|
|
{
|
|
char hitSurface[MAX_QPATH];
|
|
|
|
trap_G2API_GetSurfaceName(ent->ghoul2, ent->client->g2LastSurfaceHit, 0, hitSurface);
|
|
|
|
if (hitSurface[0])
|
|
{
|
|
G_GetHitLocFromSurfName(ent, hitSurface, &hitLoc, point, vec3_origin, vec3_origin, MOD_UNKNOWN);
|
|
}
|
|
}
|
|
|
|
if (hitLoc == -1)
|
|
{
|
|
hitLoc = G_GetHitLocation( ent, point );
|
|
}
|
|
|
|
switch (hitLoc)
|
|
{
|
|
case HL_FOOT_RT:
|
|
case HL_FOOT_LT:
|
|
*damage *= 0.5;
|
|
break;
|
|
case HL_LEG_RT:
|
|
case HL_LEG_LT:
|
|
*damage *= 0.7;
|
|
break;
|
|
case HL_WAIST:
|
|
case HL_BACK_RT:
|
|
case HL_BACK_LT:
|
|
case HL_BACK:
|
|
case HL_CHEST_RT:
|
|
case HL_CHEST_LT:
|
|
case HL_CHEST:
|
|
break; //normal damage
|
|
case HL_ARM_RT:
|
|
case HL_ARM_LT:
|
|
*damage *= 0.85;
|
|
break;
|
|
case HL_HAND_RT:
|
|
case HL_HAND_LT:
|
|
*damage *= 0.6;
|
|
break;
|
|
case HL_HEAD:
|
|
*damage *= 1.3;
|
|
break;
|
|
default:
|
|
break; //do nothing then
|
|
}
|
|
}
|
|
/*
|
|
===================================
|
|
rww - end dismemberment/lbd
|
|
===================================
|
|
*/
|
|
|
|
qboolean G_ThereIsAMaster(void)
|
|
{
|
|
int i = 0;
|
|
gentity_t *ent;
|
|
|
|
while (i < MAX_CLIENTS)
|
|
{
|
|
ent = &g_entities[i];
|
|
|
|
if (ent && ent->client && ent->client->ps.isJediMaster)
|
|
{
|
|
return qtrue;
|
|
}
|
|
|
|
i++;
|
|
}
|
|
|
|
return qfalse;
|
|
}
|
|
|
|
void G_Knockdown( gentity_t *victim )
|
|
{
|
|
if ( victim && victim->client && BG_KnockDownable(&victim->client->ps) )
|
|
{
|
|
victim->client->ps.forceHandExtend = HANDEXTEND_KNOCKDOWN;
|
|
victim->client->ps.forceDodgeAnim = 0;
|
|
victim->client->ps.forceHandExtendTime = level.time + 1100;
|
|
victim->client->ps.quickerGetup = qfalse;
|
|
}
|
|
}
|
|
|
|
void G_ApplyVehicleOtherKiller( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker, int mod, qboolean vehicleDying )
|
|
{
|
|
if ( targ && targ->client && attacker )
|
|
{
|
|
if ( targ->client->ps.otherKillerDebounceTime > level.time )
|
|
{//wait a minute, I already have a last damager
|
|
if ( targ->health < 0
|
|
|| (targ->m_pVehicle && targ->m_pVehicle->m_iRemovedSurfaces) )
|
|
{//already dying? don't let credit transfer to anyone else
|
|
return;
|
|
}
|
|
//otherwise, still alive, so, fine, use this damager...
|
|
}
|
|
|
|
targ->client->ps.otherKiller = attacker->s.number;
|
|
targ->client->ps.otherKillerTime = level.time + 25000;
|
|
targ->client->ps.otherKillerDebounceTime = level.time + 25000;
|
|
targ->client->otherKillerMOD = mod;
|
|
if ( inflictor && !Q_stricmp( "vehicle_proj", inflictor->classname ) )
|
|
{
|
|
targ->client->otherKillerVehWeapon = inflictor->s.otherEntityNum2+1;
|
|
targ->client->otherKillerWeaponType = inflictor->s.weapon;
|
|
}
|
|
else
|
|
{
|
|
targ->client->otherKillerVehWeapon = 0;
|
|
targ->client->otherKillerWeaponType = WP_NONE;
|
|
}
|
|
if ( vehicleDying )
|
|
{
|
|
//propogate otherkiller down to pilot and passengers so that proper credit is given if they suicide or eject...
|
|
if ( targ->m_pVehicle )
|
|
{
|
|
int passNum;
|
|
if ( targ->m_pVehicle->m_pPilot )
|
|
{
|
|
gentity_t *pilot = &g_entities[targ->m_pVehicle->m_pPilot->s.number];
|
|
if ( pilot->client )
|
|
{
|
|
pilot->client->ps.otherKiller = targ->client->ps.otherKiller;
|
|
pilot->client->ps.otherKillerTime = targ->client->ps.otherKillerTime;
|
|
pilot->client->ps.otherKillerDebounceTime = targ->client->ps.otherKillerDebounceTime;
|
|
pilot->client->otherKillerMOD = targ->client->otherKillerMOD;
|
|
pilot->client->otherKillerVehWeapon = targ->client->otherKillerVehWeapon;
|
|
pilot->client->otherKillerWeaponType = targ->client->otherKillerWeaponType;
|
|
}
|
|
}
|
|
for ( passNum = 0; passNum < targ->m_pVehicle->m_iNumPassengers; passNum++ )
|
|
{
|
|
gentity_t *pass = &g_entities[targ->m_pVehicle->m_ppPassengers[passNum]->s.number];
|
|
if ( pass->client )
|
|
{
|
|
pass->client->ps.otherKiller = targ->client->ps.otherKiller;
|
|
pass->client->ps.otherKillerTime = targ->client->ps.otherKillerTime;
|
|
pass->client->ps.otherKillerDebounceTime = targ->client->ps.otherKillerDebounceTime;
|
|
pass->client->otherKillerMOD = targ->client->otherKillerMOD;
|
|
pass->client->otherKillerVehWeapon = targ->client->otherKillerVehWeapon;
|
|
pass->client->otherKillerWeaponType = targ->client->otherKillerWeaponType;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
qboolean G_CheckVehicleNPCTeamDamage( gentity_t *ent )
|
|
{
|
|
//NOTE: this covers both the vehicle and NPCs riding vehicles (droids)
|
|
if ( !ent || ent->s.number < MAX_CLIENTS || ent->s.eType != ET_NPC )
|
|
{//not valid or a real client or not an NPC
|
|
return qfalse;
|
|
}
|
|
|
|
if ( ent->s.NPC_class != CLASS_VEHICLE )
|
|
{//regualar NPC
|
|
if ( ent->s.m_iVehicleNum )
|
|
{//an NPC in a vehicle, check for team damage
|
|
return qtrue;
|
|
}
|
|
}
|
|
return qfalse;
|
|
}
|
|
|
|
/*
|
|
============
|
|
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
|
|
DAMAGE_HALF_ABSORB half shields, half health
|
|
DAMAGE_HALF_ARMOR_REDUCTION Any damage that shields incur is halved
|
|
============
|
|
*/
|
|
extern qboolean gSiegeRoundBegun;
|
|
|
|
int gPainMOD = 0;
|
|
int gPainHitLoc = -1;
|
|
vec3_t gPainPoint;
|
|
|
|
void G_Damage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker,
|
|
vec3_t dir, vec3_t point, int damage, int dflags, int mod ) {
|
|
gclient_t *client;
|
|
int take;
|
|
int save;
|
|
int asave;
|
|
int knockback;
|
|
int max;
|
|
int subamt = 0;
|
|
float famt = 0;
|
|
float hamt = 0;
|
|
float shieldAbsorbed = 0;
|
|
|
|
if (targ && targ->damageRedirect)
|
|
{
|
|
G_Damage(&g_entities[targ->damageRedirectTo], inflictor, attacker, dir, point, damage, dflags, mod);
|
|
return;
|
|
}
|
|
|
|
if (mod == MOD_DEMP2 && targ && targ->inuse && targ->client)
|
|
{
|
|
if ( targ->client->ps.electrifyTime < level.time )
|
|
{//electrocution effect
|
|
if (targ->s.eType == ET_NPC && targ->s.NPC_class == CLASS_VEHICLE &&
|
|
targ->m_pVehicle && (targ->m_pVehicle->m_pVehicleInfo->type == VH_SPEEDER || targ->m_pVehicle->m_pVehicleInfo->type == VH_WALKER))
|
|
{ //do some extra stuff to speeders/walkers
|
|
targ->client->ps.electrifyTime = level.time + Q_irand( 3000, 4000 );
|
|
}
|
|
else if ( targ->s.NPC_class != CLASS_VEHICLE
|
|
|| (targ->m_pVehicle && targ->m_pVehicle->m_pVehicleInfo->type != VH_FIGHTER) )
|
|
{//don't do this to fighters
|
|
targ->client->ps.electrifyTime = level.time + Q_irand( 300, 800 );
|
|
}
|
|
}
|
|
}
|
|
|
|
if (g_gametype.integer == GT_SIEGE &&
|
|
!gSiegeRoundBegun)
|
|
{ //nothing can be damaged til the round starts.
|
|
return;
|
|
}
|
|
|
|
if (!targ->takedamage) {
|
|
return;
|
|
}
|
|
|
|
if ( (targ->flags&FL_SHIELDED) && mod != MOD_SABER && !targ->client)
|
|
{//magnetically protected, this thing can only be damaged by lightsabers
|
|
return;
|
|
}
|
|
|
|
if ((targ->flags & FL_DMG_BY_SABER_ONLY) && mod != MOD_SABER)
|
|
{ //saber-only damage
|
|
return;
|
|
}
|
|
|
|
if ( targ->client )
|
|
{//don't take damage when in a walker, or fighter
|
|
//unless the walker/fighter is dead!!! -rww
|
|
if ( targ->client->ps.clientNum < MAX_CLIENTS && targ->client->ps.m_iVehicleNum )
|
|
{
|
|
gentity_t *veh = &g_entities[targ->client->ps.m_iVehicleNum];
|
|
if ( veh->m_pVehicle && veh->health > 0 )
|
|
{
|
|
if ( veh->m_pVehicle->m_pVehicleInfo->type == VH_WALKER ||
|
|
veh->m_pVehicle->m_pVehicleInfo->type == VH_FIGHTER)
|
|
{
|
|
if (!(dflags & DAMAGE_NO_PROTECTION))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ((targ->flags & FL_DMG_BY_HEAVY_WEAP_ONLY))
|
|
{ //only take damage from explosives and such
|
|
if (mod != MOD_REPEATER_ALT &&
|
|
mod != MOD_ROCKET &&
|
|
mod != MOD_FLECHETTE_ALT_SPLASH &&
|
|
mod != MOD_ROCKET_HOMING &&
|
|
mod != MOD_THERMAL &&
|
|
mod != MOD_THERMAL_SPLASH &&
|
|
mod != MOD_TRIP_MINE_SPLASH &&
|
|
mod != MOD_TIMED_MINE_SPLASH &&
|
|
mod != MOD_DET_PACK_SPLASH &&
|
|
mod != MOD_VEHICLE &&
|
|
mod != MOD_CONC &&
|
|
mod != MOD_CONC_ALT &&
|
|
mod != MOD_SABER &&
|
|
mod != MOD_TURBLAST &&
|
|
mod != MOD_TARGET_LASER &&
|
|
mod != MOD_SUICIDE &&
|
|
mod != MOD_FALLING &&
|
|
mod != MOD_CRUSH &&
|
|
mod != MOD_TELEFRAG &&
|
|
mod != MOD_COLLISION &&
|
|
mod != MOD_VEH_EXPLOSION &&
|
|
mod != MOD_TRIGGER_HURT)
|
|
{
|
|
if ( mod != MOD_MELEE || !G_HeavyMelee( attacker ) )
|
|
{ //let classes with heavy melee ability damage heavy wpn dmg doors with fists
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (targ->flags & FL_BBRUSH)
|
|
{
|
|
if (mod == MOD_DEMP2 ||
|
|
mod == MOD_DEMP2_ALT ||
|
|
mod == MOD_BRYAR_PISTOL ||
|
|
mod == MOD_BRYAR_PISTOL_ALT ||
|
|
mod == MOD_MELEE)
|
|
{ //these don't damage bbrushes.. ever
|
|
if ( mod != MOD_MELEE || !G_HeavyMelee( attacker ) )
|
|
{ //let classes with heavy melee ability damage breakable brushes with fists
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (targ && targ->client && targ->client->ps.duelInProgress)
|
|
{
|
|
if (attacker && attacker->client && attacker->s.number != targ->client->ps.duelIndex)
|
|
{
|
|
return;
|
|
}
|
|
else if (attacker && attacker->client && mod != MOD_SABER)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
if (attacker && attacker->client && attacker->client->ps.duelInProgress)
|
|
{
|
|
if (targ && targ->client && targ->s.number != attacker->client->ps.duelIndex)
|
|
{
|
|
return;
|
|
}
|
|
else if (targ && targ->client && mod != MOD_SABER)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
if ( !(dflags & DAMAGE_NO_PROTECTION) )
|
|
{//rage overridden by no_protection
|
|
if (targ && targ->client && (targ->client->ps.fd.forcePowersActive & (1 << FP_RAGE)))
|
|
{
|
|
damage *= 0.5;
|
|
}
|
|
}
|
|
|
|
// the intermission has allready been qualified for, so don't
|
|
// allow any extra scoring
|
|
if ( level.intermissionQueued ) {
|
|
return;
|
|
}
|
|
if ( !inflictor ) {
|
|
inflictor = &g_entities[ENTITYNUM_WORLD];
|
|
}
|
|
if ( !attacker ) {
|
|
attacker = &g_entities[ENTITYNUM_WORLD];
|
|
}
|
|
|
|
// shootable doors / buttons don't actually have any health
|
|
|
|
//if genericValue4 == 1 then it's glass or a breakable and those do have health
|
|
if ( targ->s.eType == ET_MOVER && targ->genericValue4 != 1 ) {
|
|
if ( targ->use && targ->moverState == MOVER_POS1 ) {
|
|
GlobalUse( targ, inflictor, attacker );
|
|
}
|
|
return;
|
|
}
|
|
// reduce damage by the attacker's handicap value
|
|
// unless they are rocket jumping
|
|
if ( attacker->client
|
|
&& attacker != targ
|
|
&& attacker->s.eType == ET_PLAYER
|
|
&& g_gametype.integer != GT_SIEGE )
|
|
{
|
|
max = attacker->client->ps.stats[STAT_MAX_HEALTH];
|
|
damage = damage * max / 100;
|
|
}
|
|
|
|
if ( !(dflags&DAMAGE_NO_HIT_LOC) )
|
|
{//see if we should modify it by damage location
|
|
if (targ->inuse && (targ->client || targ->s.eType == ET_NPC) &&
|
|
attacker->inuse && (attacker->client || attacker->s.eType == ET_NPC))
|
|
{ //check for location based damage stuff.
|
|
G_LocationBasedDamageModifier(targ, point, mod, dflags, &damage);
|
|
}
|
|
}
|
|
|
|
if ( targ->client
|
|
&& targ->client->NPC_class == CLASS_RANCOR
|
|
&& (!attacker||!attacker->client||attacker->client->NPC_class!=CLASS_RANCOR) )
|
|
{
|
|
// I guess always do 10 points of damage...feel free to tweak as needed
|
|
if ( damage < 10 )
|
|
{//ignore piddly little damage
|
|
damage = 0;
|
|
}
|
|
else if ( damage >= 10 )
|
|
{
|
|
damage = 10;
|
|
}
|
|
}
|
|
|
|
client = targ->client;
|
|
|
|
if ( client ) {
|
|
if ( client->noclip ) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
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 ) {
|
|
vec3_t kvel;
|
|
float mass;
|
|
|
|
mass = 200;
|
|
|
|
if (mod == MOD_SABER)
|
|
{
|
|
float saberKnockbackScale = g_saberDmgVelocityScale.value;
|
|
if ( (dflags&DAMAGE_SABER_KNOCKBACK1)
|
|
|| (dflags&DAMAGE_SABER_KNOCKBACK2) )
|
|
{//saber does knockback, scale it by the right number
|
|
if ( !saberKnockbackScale )
|
|
{
|
|
saberKnockbackScale = 1.0f;
|
|
}
|
|
if ( attacker
|
|
&& attacker->client )
|
|
{
|
|
if ( (dflags&DAMAGE_SABER_KNOCKBACK1) )
|
|
{
|
|
if ( attacker && attacker->client )
|
|
{
|
|
saberKnockbackScale *= attacker->client->saber[0].knockbackScale;
|
|
}
|
|
}
|
|
if ( (dflags&DAMAGE_SABER_KNOCKBACK1_B2) )
|
|
{
|
|
if ( attacker && attacker->client )
|
|
{
|
|
saberKnockbackScale *= attacker->client->saber[0].knockbackScale2;
|
|
}
|
|
}
|
|
if ( (dflags&DAMAGE_SABER_KNOCKBACK2) )
|
|
{
|
|
if ( attacker && attacker->client )
|
|
{
|
|
saberKnockbackScale *= attacker->client->saber[1].knockbackScale;
|
|
}
|
|
}
|
|
if ( (dflags&DAMAGE_SABER_KNOCKBACK2_B2) )
|
|
{
|
|
if ( attacker && attacker->client )
|
|
{
|
|
saberKnockbackScale *= attacker->client->saber[1].knockbackScale2;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
VectorScale (dir, (g_knockback.value * (float)knockback / mass)*saberKnockbackScale, kvel);
|
|
}
|
|
else
|
|
{
|
|
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 && (g_saberDmgVelocityScale.integer || mod != MOD_SABER || (dflags&DAMAGE_SABER_KNOCKBACK1) || (dflags&DAMAGE_SABER_KNOCKBACK2) || (dflags&DAMAGE_SABER_KNOCKBACK1_B2) || (dflags&DAMAGE_SABER_KNOCKBACK2_B2) ) ) {
|
|
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;
|
|
}
|
|
}
|
|
|
|
if ( (g_trueJedi.integer || g_gametype.integer == GT_SIEGE)
|
|
&& client )
|
|
{//less explosive damage for jedi, more saber damage for non-jedi
|
|
if ( client->ps.trueJedi
|
|
|| (g_gametype.integer == GT_SIEGE&&client->ps.weapon == WP_SABER))
|
|
{//if the target is a trueJedi, reduce splash and explosive damage to 1/2
|
|
switch ( mod )
|
|
{
|
|
case MOD_REPEATER_ALT:
|
|
case MOD_REPEATER_ALT_SPLASH:
|
|
case MOD_DEMP2_ALT:
|
|
case MOD_FLECHETTE_ALT_SPLASH:
|
|
case MOD_ROCKET:
|
|
case MOD_ROCKET_SPLASH:
|
|
case MOD_ROCKET_HOMING:
|
|
case MOD_ROCKET_HOMING_SPLASH:
|
|
case MOD_THERMAL:
|
|
case MOD_THERMAL_SPLASH:
|
|
case MOD_TRIP_MINE_SPLASH:
|
|
case MOD_TIMED_MINE_SPLASH:
|
|
case MOD_DET_PACK_SPLASH:
|
|
damage *= 0.75;
|
|
break;
|
|
}
|
|
}
|
|
else if ( (client->ps.trueNonJedi || (g_gametype.integer == GT_SIEGE&&client->ps.weapon != WP_SABER))
|
|
&& mod == MOD_SABER )
|
|
{//if the target is a trueNonJedi, take more saber damage... combined with the 1.5 in the w_saber stuff, this is 6 times damage!
|
|
if ( damage < 100 )
|
|
{
|
|
damage *= 4;
|
|
if ( damage > 100 )
|
|
{
|
|
damage = 100;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (attacker->client && targ->client && g_gametype.integer == GT_SIEGE &&
|
|
targ->client->siegeClass != -1 && (bgSiegeClasses[targ->client->siegeClass].classflags & (1<<CFL_STRONGAGAINSTPHYSICAL)))
|
|
{ //this class is flagged to take less damage from physical attacks.
|
|
//For now I'm just decreasing against any client-based attack, this can be changed later I guess.
|
|
damage *= 0.5;
|
|
}
|
|
|
|
// 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)
|
|
{
|
|
if (OnSameTeam (targ, attacker))
|
|
{
|
|
if ( !g_friendlyFire.integer )
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
else if (attacker && attacker->inuse &&
|
|
!attacker->client && attacker->activator &&
|
|
targ != attacker->activator &&
|
|
attacker->activator->inuse && attacker->activator->client)
|
|
{ //emplaced guns don't hurt teammates of user
|
|
if (OnSameTeam (targ, attacker->activator))
|
|
{
|
|
if ( !g_friendlyFire.integer )
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
else if (targ->inuse && targ->client &&
|
|
g_gametype.integer >= GT_TEAM &&
|
|
attacker->s.number >= MAX_CLIENTS &&
|
|
attacker->alliedTeam &&
|
|
targ->client->sess.sessionTeam == attacker->alliedTeam &&
|
|
!g_friendlyFire.integer)
|
|
{ //things allied with my team should't hurt me.. I guess
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (g_gametype.integer == GT_JEDIMASTER && !g_friendlyFire.integer &&
|
|
targ && targ->client && attacker && attacker->client &&
|
|
targ != attacker && !targ->client->ps.isJediMaster && !attacker->client->ps.isJediMaster &&
|
|
G_ThereIsAMaster())
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (targ->s.number >= MAX_CLIENTS && targ->client
|
|
&& targ->s.shouldtarget && targ->s.teamowner &&
|
|
attacker && attacker->inuse && attacker->client && targ->s.owner >= 0 && targ->s.owner < MAX_CLIENTS)
|
|
{
|
|
gentity_t *targown = &g_entities[targ->s.owner];
|
|
|
|
if (targown && targown->inuse && targown->client && OnSameTeam(targown, attacker))
|
|
{
|
|
if (!g_friendlyFire.integer)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// check for godmode
|
|
if ( (targ->flags & FL_GODMODE) && targ->s.eType != ET_NPC ) {
|
|
return;
|
|
}
|
|
|
|
if (targ && targ->client && (targ->client->ps.eFlags & EF_INVULNERABLE) &&
|
|
attacker && attacker->client && targ != attacker)
|
|
{
|
|
if (targ->client->invulnerableTimer <= level.time)
|
|
{
|
|
targ->client->ps.eFlags &= ~EF_INVULNERABLE;
|
|
}
|
|
else
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
//check for teamnodmg
|
|
//NOTE: non-client objects hitting clients (and clients hitting clients) purposely doesn't obey this teamnodmg (for emplaced guns)
|
|
if ( attacker && !targ->client )
|
|
{//attacker hit a non-client
|
|
if ( g_gametype.integer == GT_SIEGE &&
|
|
!g_ff_objectives.integer )
|
|
{//in siege mode (and...?)
|
|
if ( targ->teamnodmg )
|
|
{//targ shouldn't take damage from a certain team
|
|
if ( attacker->client )
|
|
{//a client hit a non-client object
|
|
if ( targ->teamnodmg == attacker->client->sess.sessionTeam )
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
else if ( attacker->teamnodmg )
|
|
{//a non-client hit a non-client object
|
|
//FIXME: maybe check alliedTeam instead?
|
|
if ( targ->teamnodmg == attacker->teamnodmg )
|
|
{
|
|
if (attacker->activator &&
|
|
attacker->activator->inuse &&
|
|
attacker->activator->s.number < MAX_CLIENTS &&
|
|
attacker->activator->client &&
|
|
attacker->activator->client->sess.sessionTeam != targ->teamnodmg)
|
|
{ //uh, let them damage it I guess.
|
|
}
|
|
else
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// battlesuit protects from all radius damage (but takes knockback)
|
|
// and protects 50% against all damage
|
|
if ( client && client->ps.powerups[PW_BATTLESUIT] ) {
|
|
G_AddEvent( targ, EV_POWERUP_BATTLESUIT, 0 );
|
|
if ( ( dflags & DAMAGE_RADIUS ) || ( mod == MOD_FALLING ) ) {
|
|
return;
|
|
}
|
|
damage *= 0.5;
|
|
}
|
|
|
|
// add to the attacker's hit counter (if the target isn't a general entity like a prox mine)
|
|
if ( attacker->client && targ != attacker && targ->health > 0
|
|
&& targ->s.eType != ET_MISSILE
|
|
&& targ->s.eType != ET_GENERAL
|
|
&& client) {
|
|
if ( OnSameTeam( targ, attacker ) ) {
|
|
attacker->client->ps.persistant[PERS_HITS]--;
|
|
} else {
|
|
attacker->client->ps.persistant[PERS_HITS]++;
|
|
}
|
|
attacker->client->ps.persistant[PERS_ATTACKEE_ARMOR] = (targ->health<<8)|(client->ps.stats[STAT_ARMOR]);
|
|
}
|
|
|
|
// always give half damage if hurting self... but not in siege. Heavy weapons need a counter.
|
|
// calculated after knockback, so rocket jumping works
|
|
if ( targ == attacker && !(dflags & DAMAGE_NO_SELF_PROTECTION)) {
|
|
if ( g_gametype.integer == GT_SIEGE )
|
|
{
|
|
damage *= 1.5;
|
|
}
|
|
else
|
|
{
|
|
damage *= 0.5;
|
|
}
|
|
}
|
|
|
|
if ( damage < 1 ) {
|
|
damage = 1;
|
|
}
|
|
|
|
//store the attacker as the potential killer in case we die by other means
|
|
if ( targ->client && attacker && attacker != targ )
|
|
{
|
|
if ( targ->s.eType == ET_NPC && targ->s.NPC_class == CLASS_VEHICLE)
|
|
{//vehicle
|
|
G_ApplyVehicleOtherKiller( targ, inflictor, attacker, mod, qfalse );
|
|
}
|
|
else
|
|
{//other client
|
|
targ->client->ps.otherKiller = attacker->s.number;
|
|
targ->client->ps.otherKillerTime = level.time + 5000;
|
|
targ->client->ps.otherKillerDebounceTime = level.time + 100;
|
|
targ->client->otherKillerMOD = mod;
|
|
targ->client->otherKillerVehWeapon = 0;
|
|
targ->client->otherKillerWeaponType = WP_NONE;
|
|
}
|
|
}
|
|
|
|
take = damage;
|
|
save = 0;
|
|
|
|
// save some from armor
|
|
asave = CheckArmor (targ, take, dflags);
|
|
|
|
if (asave)
|
|
{
|
|
shieldAbsorbed = asave;
|
|
}
|
|
|
|
take -= asave;
|
|
if ( targ->client )
|
|
{//update vehicle shields and armor, check for explode
|
|
if ( targ->client->NPC_class == CLASS_VEHICLE &&
|
|
targ->m_pVehicle )
|
|
{//FIXME: should be in its own function in g_vehicles.c now, too big to be here
|
|
int surface = -1;
|
|
if ( attacker )
|
|
{//so we know the last guy who shot at us
|
|
targ->enemy = attacker;
|
|
}
|
|
|
|
if ( ( targ->m_pVehicle->m_pVehicleInfo->type == VH_ANIMAL ) )
|
|
{
|
|
//((CVehicleNPC *)targ->NPC)->m_ulFlags |= CVehicleNPC::VEH_BUCKING;
|
|
}
|
|
|
|
targ->m_pVehicle->m_iShields = targ->client->ps.stats[STAT_ARMOR];
|
|
G_VehUpdateShields( targ );
|
|
targ->m_pVehicle->m_iArmor -= take;
|
|
if ( targ->m_pVehicle->m_iArmor <= 0 )
|
|
{
|
|
targ->s.eFlags |= EF_DEAD;
|
|
targ->client->ps.eFlags |= EF_DEAD;
|
|
targ->m_pVehicle->m_iArmor = 0;
|
|
}
|
|
if ( targ->m_pVehicle->m_pVehicleInfo->type == VH_FIGHTER )
|
|
{//get the last surf that was hit
|
|
if ( targ->client && targ->client->g2LastSurfaceTime == level.time)
|
|
{
|
|
char hitSurface[MAX_QPATH];
|
|
|
|
trap_G2API_GetSurfaceName(targ->ghoul2, targ->client->g2LastSurfaceHit, 0, hitSurface);
|
|
|
|
if (hitSurface[0])
|
|
{
|
|
surface = G_ShipSurfaceForSurfName( &hitSurface[0] );
|
|
|
|
if ( take && surface > 0 )
|
|
{//hit a certain part of the ship
|
|
int deathPoint = 0;
|
|
|
|
targ->locationDamage[surface] += take;
|
|
|
|
switch(surface)
|
|
{
|
|
case SHIPSURF_FRONT:
|
|
deathPoint = targ->m_pVehicle->m_pVehicleInfo->health_front;
|
|
break;
|
|
case SHIPSURF_BACK:
|
|
deathPoint = targ->m_pVehicle->m_pVehicleInfo->health_back;
|
|
break;
|
|
case SHIPSURF_RIGHT:
|
|
deathPoint = targ->m_pVehicle->m_pVehicleInfo->health_right;
|
|
break;
|
|
case SHIPSURF_LEFT:
|
|
deathPoint = targ->m_pVehicle->m_pVehicleInfo->health_left;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
//presume 0 means it wasn't set and so it should never die.
|
|
if ( deathPoint )
|
|
{
|
|
if ( targ->locationDamage[surface] >= deathPoint)
|
|
{ //this area of the ship is now dead
|
|
qboolean wasDying = (targ->m_pVehicle->m_iRemovedSurfaces!=0);
|
|
if ( G_FlyVehicleDestroySurface( targ, surface ) )
|
|
{//actually took off a surface
|
|
G_VehicleSetDamageLocFlags( targ, surface, deathPoint );
|
|
if ( !wasDying )
|
|
{//always take credit for kill if they were healthy before
|
|
targ->client->ps.otherKillerDebounceTime = 0;
|
|
}
|
|
G_ApplyVehicleOtherKiller( targ, inflictor, attacker, mod, qtrue );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
G_VehicleSetDamageLocFlags( targ, surface, deathPoint );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if ( targ->m_pVehicle->m_pVehicleInfo->type != VH_ANIMAL )
|
|
{
|
|
/*
|
|
if ( targ->m_pVehicle->m_iArmor <= 0 )
|
|
{//vehicle all out of armor
|
|
Vehicle_t *pVeh = targ->m_pVehicle;
|
|
if ( pVeh->m_iDieTime == 0 )
|
|
{//just start the flaming effect and explosion delay, if it's not going already...
|
|
pVeh->m_pVehicleInfo->StartDeathDelay( pVeh, 0 );
|
|
}
|
|
}
|
|
else*/
|
|
if ( attacker
|
|
//&& attacker->client
|
|
&& targ != attacker
|
|
&& point
|
|
&& !VectorCompare( targ->client->ps.origin, point )
|
|
&& targ->m_pVehicle->m_LandTrace.fraction >= 1.0f)
|
|
{//just took a hit, knock us around
|
|
vec3_t vUp, impactDir;
|
|
float impactStrength = (damage/200.0f)*10.0f;
|
|
float dot = 0.0f;
|
|
if ( impactStrength > 10.0f )
|
|
{
|
|
impactStrength = 10.0f;
|
|
}
|
|
//pitch or roll us based on where we were hit
|
|
AngleVectors( targ->m_pVehicle->m_vOrientation, NULL, NULL, vUp );
|
|
VectorSubtract( point, targ->r.currentOrigin, impactDir );
|
|
VectorNormalize( impactDir );
|
|
if ( surface <= 0 )
|
|
{//no surf guess where we were hit, then
|
|
vec3_t vFwd, vRight;
|
|
AngleVectors( targ->m_pVehicle->m_vOrientation, vFwd, vRight, vUp );
|
|
dot = DotProduct( vRight, impactDir );
|
|
if ( dot > 0.4f )
|
|
{
|
|
surface = SHIPSURF_RIGHT;
|
|
}
|
|
else if ( dot < -0.4f )
|
|
{
|
|
surface = SHIPSURF_LEFT;
|
|
}
|
|
else
|
|
{
|
|
dot = DotProduct( vFwd, impactDir );
|
|
if ( dot > 0.0f )
|
|
{
|
|
surface = SHIPSURF_FRONT;
|
|
}
|
|
else
|
|
{
|
|
surface = SHIPSURF_BACK;
|
|
}
|
|
}
|
|
}
|
|
switch ( surface )
|
|
{
|
|
case SHIPSURF_FRONT:
|
|
dot = DotProduct( vUp, impactDir );
|
|
if ( dot > 0 )
|
|
{
|
|
targ->m_pVehicle->m_vOrientation[PITCH] += impactStrength;
|
|
}
|
|
else
|
|
{
|
|
targ->m_pVehicle->m_vOrientation[PITCH] -= impactStrength;
|
|
}
|
|
break;
|
|
case SHIPSURF_BACK:
|
|
dot = DotProduct( vUp, impactDir );
|
|
if ( dot > 0 )
|
|
{
|
|
targ->m_pVehicle->m_vOrientation[PITCH] -= impactStrength;
|
|
}
|
|
else
|
|
{
|
|
targ->m_pVehicle->m_vOrientation[PITCH] += impactStrength;
|
|
}
|
|
break;
|
|
case SHIPSURF_RIGHT:
|
|
dot = DotProduct( vUp, impactDir );
|
|
if ( dot > 0 )
|
|
{
|
|
targ->m_pVehicle->m_vOrientation[ROLL] -= impactStrength;
|
|
}
|
|
else
|
|
{
|
|
targ->m_pVehicle->m_vOrientation[ROLL] += impactStrength;
|
|
}
|
|
break;
|
|
case SHIPSURF_LEFT:
|
|
dot = DotProduct( vUp, impactDir );
|
|
if ( dot > 0 )
|
|
{
|
|
targ->m_pVehicle->m_vOrientation[ROLL] += impactStrength;
|
|
}
|
|
else
|
|
{
|
|
targ->m_pVehicle->m_vOrientation[ROLL] -= impactStrength;
|
|
}
|
|
break;
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT )
|
|
{//FIXME: screw with non-animal vehicles, too?
|
|
if ( client )
|
|
{
|
|
if ( client->NPC_class == CLASS_VEHICLE
|
|
&& targ->m_pVehicle
|
|
&& targ->m_pVehicle->m_pVehicleInfo
|
|
&& targ->m_pVehicle->m_pVehicleInfo->type == VH_FIGHTER )
|
|
{//all damage goes into the disruption of shields and systems
|
|
take = 0;
|
|
}
|
|
else
|
|
{
|
|
|
|
if (client->jetPackOn)
|
|
{ //disable jetpack temporarily
|
|
Jetpack_Off(targ);
|
|
client->jetPackToggleTime = level.time + Q_irand(3000, 10000);
|
|
}
|
|
|
|
if ( client->NPC_class == CLASS_PROTOCOL || client->NPC_class == CLASS_SEEKER ||
|
|
client->NPC_class == CLASS_R2D2 || client->NPC_class == CLASS_R5D2 ||
|
|
client->NPC_class == CLASS_MOUSE || client->NPC_class == CLASS_GONK )
|
|
{
|
|
// DEMP2 does more damage to these guys.
|
|
take *= 2;
|
|
}
|
|
else if ( client->NPC_class == CLASS_PROBE || client->NPC_class == CLASS_INTERROGATOR ||
|
|
client->NPC_class == CLASS_MARK1 || client->NPC_class == CLASS_MARK2 || client->NPC_class == CLASS_SENTRY ||
|
|
client->NPC_class == CLASS_ATST )
|
|
{
|
|
// DEMP2 does way more damage to these guys.
|
|
take *= 5;
|
|
}
|
|
else
|
|
{
|
|
if (take > 0)
|
|
{
|
|
take /= 3;
|
|
if (take < 1)
|
|
{
|
|
take = 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifndef FINAL_BUILD
|
|
if ( g_debugDamage.integer ) {
|
|
G_Printf( "%i: client:%i health:%i damage:%i armor:%i\n", level.time, targ->s.number,
|
|
targ->health, take, asave );
|
|
}
|
|
#endif
|
|
|
|
// 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;
|
|
}
|
|
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);
|
|
}
|
|
}
|
|
|
|
// See if it's the player hurting the emeny flag carrier
|
|
if( g_gametype.integer == GT_CTF || g_gametype.integer == GT_CTY) {
|
|
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;
|
|
}
|
|
|
|
if ( !(dflags & DAMAGE_NO_PROTECTION) )
|
|
{//protect overridden by no_protection
|
|
if (take && targ->client && (targ->client->ps.fd.forcePowersActive & (1 << FP_PROTECT)))
|
|
{
|
|
if (targ->client->ps.fd.forcePower)
|
|
{
|
|
int maxtake = take;
|
|
|
|
//G_Sound(targ, CHAN_AUTO, protectHitSound);
|
|
if (targ->client->forcePowerSoundDebounce < level.time)
|
|
{
|
|
G_PreDefSound(targ->client->ps.origin, PDSOUND_PROTECTHIT);
|
|
targ->client->forcePowerSoundDebounce = level.time + 400;
|
|
}
|
|
|
|
if (targ->client->ps.fd.forcePowerLevel[FP_PROTECT] == FORCE_LEVEL_1)
|
|
{
|
|
famt = 1;
|
|
hamt = 0.40;
|
|
|
|
if (maxtake > 100)
|
|
{
|
|
maxtake = 100;
|
|
}
|
|
}
|
|
else if (targ->client->ps.fd.forcePowerLevel[FP_PROTECT] == FORCE_LEVEL_2)
|
|
{
|
|
famt = 0.5;
|
|
hamt = 0.60;
|
|
|
|
if (maxtake > 200)
|
|
{
|
|
maxtake = 200;
|
|
}
|
|
}
|
|
else if (targ->client->ps.fd.forcePowerLevel[FP_PROTECT] == FORCE_LEVEL_3)
|
|
{
|
|
famt = 0.25;
|
|
hamt = 0.80;
|
|
|
|
if (maxtake > 400)
|
|
{
|
|
maxtake = 400;
|
|
}
|
|
}
|
|
|
|
if (!targ->client->ps.powerups[PW_FORCE_BOON])
|
|
{
|
|
targ->client->ps.fd.forcePower -= maxtake*famt;
|
|
}
|
|
else
|
|
{
|
|
targ->client->ps.fd.forcePower -= (maxtake*famt)/2;
|
|
}
|
|
subamt = (maxtake*hamt)+(take-maxtake);
|
|
if (targ->client->ps.fd.forcePower < 0)
|
|
{
|
|
subamt += targ->client->ps.fd.forcePower;
|
|
targ->client->ps.fd.forcePower = 0;
|
|
}
|
|
if (subamt)
|
|
{
|
|
take -= subamt;
|
|
|
|
if (take < 0)
|
|
{
|
|
take = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (shieldAbsorbed)
|
|
{
|
|
/*
|
|
if ( targ->client->NPC_class == CLASS_VEHICLE )
|
|
{
|
|
targ->client->ps.electrifyTime = level.time + Q_irand( 500, 1000 );
|
|
}
|
|
else
|
|
*/
|
|
{
|
|
gentity_t *evEnt;
|
|
|
|
// Send off an event to show a shield shell on the player, pointing in the right direction.
|
|
//evEnt = G_TempEntity(vec3_origin, EV_SHIELD_HIT);
|
|
//rww - er.. what the? This isn't broadcast, why is it being set on vec3_origin?!
|
|
evEnt = G_TempEntity(targ->r.currentOrigin, EV_SHIELD_HIT);
|
|
evEnt->s.otherEntityNum = targ->s.number;
|
|
evEnt->s.eventParm = DirToByte(dir);
|
|
evEnt->s.time2=shieldAbsorbed;
|
|
/*
|
|
shieldAbsorbed *= 20;
|
|
|
|
if (shieldAbsorbed > 1500)
|
|
{
|
|
shieldAbsorbed = 1500;
|
|
}
|
|
if (shieldAbsorbed < 200)
|
|
{
|
|
shieldAbsorbed = 200;
|
|
}
|
|
|
|
if (targ->client->ps.powerups[PW_SHIELDHIT] < (level.time + shieldAbsorbed))
|
|
{
|
|
targ->client->ps.powerups[PW_SHIELDHIT] = level.time + shieldAbsorbed;
|
|
}
|
|
//flicker for as many ms as damage was absorbed (*20)
|
|
//therefore 10 damage causes 1/5 of a seond of flickering, whereas
|
|
//a full 100 causes 2 seconds (but is reduced to 1.5 seconds due to the max)
|
|
|
|
*/
|
|
}
|
|
}
|
|
|
|
// do the damage
|
|
if (take)
|
|
{
|
|
if (targ->client && targ->s.number < MAX_CLIENTS &&
|
|
(mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT))
|
|
{ //uh.. shock them or something. what the hell, I don't know.
|
|
if (targ->client->ps.weaponTime <= 0)
|
|
{ //yeah, we were supposed to be beta a week ago, I don't feel like
|
|
//breaking the game so I'm gonna be safe and only do this only
|
|
//if your weapon is not busy
|
|
targ->client->ps.weaponTime = 2000;
|
|
targ->client->ps.electrifyTime = level.time + 2000;
|
|
if (targ->client->ps.weaponstate == WEAPON_CHARGING ||
|
|
targ->client->ps.weaponstate == WEAPON_CHARGING_ALT)
|
|
{
|
|
targ->client->ps.weaponstate = WEAPON_READY;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( !(dflags & DAMAGE_NO_PROTECTION) )
|
|
{//rage overridden by no_protection
|
|
if (targ->client && (targ->client->ps.fd.forcePowersActive & (1 << FP_RAGE)) && (inflictor->client || attacker->client))
|
|
{
|
|
take /= (targ->client->ps.fd.forcePowerLevel[FP_RAGE]+1);
|
|
}
|
|
}
|
|
targ->health = targ->health - take;
|
|
|
|
if ( (targ->flags&FL_UNDYING) )
|
|
{//take damage down to 1, but never die
|
|
if ( targ->health < 1 )
|
|
{
|
|
targ->health = 1;
|
|
}
|
|
}
|
|
|
|
if ( targ->client ) {
|
|
targ->client->ps.stats[STAT_HEALTH] = targ->health;
|
|
}
|
|
|
|
if ( !(dflags & DAMAGE_NO_PROTECTION) )
|
|
{//rage overridden by no_protection
|
|
if (targ->client && (targ->client->ps.fd.forcePowersActive & (1 << FP_RAGE)) && (inflictor->client || attacker->client))
|
|
{
|
|
if (targ->health <= 0)
|
|
{
|
|
targ->health = 1;
|
|
}
|
|
if (targ->client->ps.stats[STAT_HEALTH] <= 0)
|
|
{
|
|
targ->client->ps.stats[STAT_HEALTH] = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
//We want to go ahead and set gPainHitLoc regardless of if we have a pain func,
|
|
//so we can adjust the location damage too.
|
|
if (targ->client && targ->ghoul2 && targ->client->g2LastSurfaceTime == level.time)
|
|
{ //We updated the hit surface this frame, so it's valid.
|
|
char hitSurface[MAX_QPATH];
|
|
|
|
trap_G2API_GetSurfaceName(targ->ghoul2, targ->client->g2LastSurfaceHit, 0, hitSurface);
|
|
|
|
if (hitSurface[0])
|
|
{
|
|
G_GetHitLocFromSurfName(targ, hitSurface, &gPainHitLoc, point, dir, vec3_origin, mod);
|
|
}
|
|
else
|
|
{
|
|
gPainHitLoc = -1;
|
|
}
|
|
|
|
if (gPainHitLoc < HL_MAX && gPainHitLoc >= 0 && targ->locationDamage[gPainHitLoc] < Q3_INFINITE &&
|
|
(targ->s.eType == ET_PLAYER || targ->s.NPC_class != CLASS_VEHICLE))
|
|
{
|
|
targ->locationDamage[gPainHitLoc] += take;
|
|
|
|
if (g_armBreakage.integer && !targ->client->ps.brokenLimbs &&
|
|
targ->client->ps.stats[STAT_HEALTH] > 0 && targ->health > 0 &&
|
|
!(targ->s.eFlags & EF_DEAD))
|
|
{ //check for breakage
|
|
if (targ->locationDamage[HL_ARM_RT]+targ->locationDamage[HL_HAND_RT] >= 80)
|
|
{
|
|
G_BreakArm(targ, BROKENLIMB_RARM);
|
|
}
|
|
else if (targ->locationDamage[HL_ARM_LT]+targ->locationDamage[HL_HAND_LT] >= 80)
|
|
{
|
|
G_BreakArm(targ, BROKENLIMB_LARM);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
gPainHitLoc = -1;
|
|
}
|
|
|
|
if (targ->maxHealth)
|
|
{ //if this is non-zero this guy should be updated his s.health to send to the client
|
|
G_ScaleNetHealth(targ);
|
|
}
|
|
|
|
if ( targ->health <= 0 ) {
|
|
if ( client )
|
|
{
|
|
targ->flags |= FL_NO_KNOCKBACK;
|
|
|
|
if (point)
|
|
{
|
|
VectorCopy( point, targ->pos1 );
|
|
}
|
|
else
|
|
{
|
|
VectorCopy(targ->client->ps.origin, targ->pos1);
|
|
}
|
|
}
|
|
else if (targ->s.eType == ET_NPC)
|
|
{ //g2animent
|
|
VectorCopy(point, targ->pos1);
|
|
}
|
|
|
|
if (targ->health < -999)
|
|
targ->health = -999;
|
|
|
|
// If we are a breaking glass brush, store the damage point so we can do cool things with it.
|
|
if ( targ->r.svFlags & SVF_GLASS_BRUSH )
|
|
{
|
|
VectorCopy( point, targ->pos1 );
|
|
if (dir)
|
|
{
|
|
VectorCopy( dir, targ->pos2 );
|
|
}
|
|
else
|
|
{
|
|
VectorClear(targ->pos2);
|
|
}
|
|
}
|
|
|
|
if (targ->s.eType == ET_NPC &&
|
|
targ->client &&
|
|
(targ->s.eFlags & EF_DEAD))
|
|
{ //an NPC that's already dead. Maybe we can cut some more limbs off!
|
|
if ( (mod == MOD_SABER || (mod == MOD_MELEE && G_HeavyMelee( attacker )) )//saber or heavy melee (claws)
|
|
&& take > 2
|
|
&& !(dflags&DAMAGE_NO_DISMEMBER) )
|
|
{
|
|
G_CheckForDismemberment(targ, attacker, targ->pos1, take, targ->client->ps.torsoAnim, qtrue);
|
|
}
|
|
}
|
|
|
|
targ->enemy = attacker;
|
|
targ->die (targ, inflictor, attacker, take, mod);
|
|
G_ActivateBehavior( targ, BSET_DEATH );
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
if ( g_debugMelee.integer )
|
|
{//getting hurt makes you let go of the wall
|
|
if ( targ->client && (targ->client->ps.pm_flags&PMF_STUCK_TO_WALL) )
|
|
{
|
|
G_LetGoOfWall( targ );
|
|
}
|
|
}
|
|
if ( targ->pain )
|
|
{
|
|
if (targ->s.eType != ET_NPC || mod != MOD_SABER || take > 1)
|
|
{ //don't even notify NPCs of pain if it's just idle saber damage
|
|
gPainMOD = mod;
|
|
if (point)
|
|
{
|
|
VectorCopy(point, gPainPoint);
|
|
}
|
|
else
|
|
{
|
|
VectorCopy(targ->r.currentOrigin, gPainPoint);
|
|
}
|
|
targ->pain (targ, attacker, take);
|
|
}
|
|
}
|
|
}
|
|
|
|
G_LogWeaponDamage(attacker->s.number, mod, take);
|
|
}
|
|
|
|
}
|
|
|
|
void G_DamageFromKiller( gentity_t *pEnt, gentity_t *pVehEnt, gentity_t *attacker, vec3_t org, int damage, int dflags, int mod )
|
|
{
|
|
gentity_t *killer = attacker, *inflictor = attacker;
|
|
qboolean tempInflictor = qfalse;
|
|
if ( !pEnt || !pVehEnt || !pVehEnt->client )
|
|
{
|
|
return;
|
|
}
|
|
if (pVehEnt->client->ps.otherKiller < ENTITYNUM_WORLD &&
|
|
pVehEnt->client->ps.otherKillerTime > level.time)
|
|
{
|
|
gentity_t *potentialKiller = &g_entities[pVehEnt->client->ps.otherKiller];
|
|
|
|
if ( potentialKiller->inuse )//&& potentialKiller->client)
|
|
{ //he's valid I guess
|
|
killer = potentialKiller;
|
|
mod = pVehEnt->client->otherKillerMOD;
|
|
inflictor = killer;
|
|
if ( pVehEnt->client->otherKillerVehWeapon > 0 )
|
|
{
|
|
inflictor = G_Spawn();
|
|
if ( inflictor )
|
|
{//fake up the inflictor
|
|
tempInflictor = qtrue;
|
|
inflictor->classname = "vehicle_proj";
|
|
inflictor->s.otherEntityNum2 = pVehEnt->client->otherKillerVehWeapon-1;
|
|
inflictor->s.weapon = pVehEnt->client->otherKillerWeaponType;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//FIXME: damage hitEnt, some, too? Our explosion should hurt them some, but...
|
|
if ( killer && killer->s.eType == ET_NPC && killer->s.NPC_class == CLASS_VEHICLE && killer->m_pVehicle && killer->m_pVehicle->m_pPilot )
|
|
{
|
|
killer = (gentity_t *)killer->m_pVehicle->m_pPilot;
|
|
}
|
|
G_Damage( pEnt, inflictor, killer, NULL, org, damage, dflags, mod );
|
|
if ( tempInflictor )
|
|
{
|
|
G_FreeEntity( inflictor );
|
|
}
|
|
}
|
|
|
|
/*
|
|
============
|
|
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[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_RadiusDamage
|
|
============
|
|
*/
|
|
qboolean G_RadiusDamage ( vec3_t origin, gentity_t *attacker, float damage, float radius,
|
|
gentity_t *ignore, gentity_t *missile, int mod) {
|
|
float points, dist;
|
|
gentity_t *ent;
|
|
int entityList[MAX_GENTITIES];
|
|
int numListedEntities;
|
|
vec3_t mins, maxs;
|
|
vec3_t v;
|
|
vec3_t dir;
|
|
int i, e;
|
|
qboolean hitClient = qfalse;
|
|
qboolean roastPeople = qfalse;
|
|
|
|
/*
|
|
if (missile && !missile->client && missile->s.weapon > WP_NONE &&
|
|
missile->s.weapon < WP_NUM_WEAPONS && missile->r.ownerNum >= 0 &&
|
|
(missile->r.ownerNum < MAX_CLIENTS || g_entities[missile->r.ownerNum].s.eType == ET_NPC))
|
|
{ //sounds like it's a valid weapon projectile.. is it a valid explosive to create marks from?
|
|
switch(missile->s.weapon)
|
|
{
|
|
case WP_FLECHETTE: //flechette issuing this will be alt-fire
|
|
case WP_ROCKET_LAUNCHER:
|
|
case WP_THERMAL:
|
|
case WP_TRIP_MINE:
|
|
case WP_DET_PACK:
|
|
roastPeople = qtrue; //Then create explosive marks
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
*/
|
|
//oh well.. maybe sometime? I am trying to cut down on tempent use.
|
|
|
|
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 - dist / radius );
|
|
|
|
if( CanDamage (ent, origin) ) {
|
|
if( LogAccuracyHit( ent, attacker ) ) {
|
|
hitClient = qtrue;
|
|
}
|
|
VectorSubtract (ent->r.currentOrigin, origin, dir);
|
|
// push the center of mass higher than the origin so players
|
|
// get knocked into the air more
|
|
dir[2] += 24;
|
|
if (attacker && attacker->inuse && attacker->client &&
|
|
attacker->s.eType == ET_NPC && attacker->s.NPC_class == CLASS_VEHICLE &&
|
|
attacker->m_pVehicle && attacker->m_pVehicle->m_pPilot)
|
|
{ //say my pilot did it.
|
|
G_Damage (ent, missile, (gentity_t *)attacker->m_pVehicle->m_pPilot, dir, origin, (int)points, DAMAGE_RADIUS, mod);
|
|
}
|
|
else
|
|
{
|
|
G_Damage (ent, missile, attacker, dir, origin, (int)points, DAMAGE_RADIUS, mod);
|
|
}
|
|
|
|
if (ent && ent->client && roastPeople && missile &&
|
|
!VectorCompare(ent->r.currentOrigin, missile->r.currentOrigin))
|
|
{ //the thing calling this function can create burn marks on people, so create an event to do so
|
|
gentity_t *evEnt = G_TempEntity(ent->r.currentOrigin, EV_GHOUL2_MARK);
|
|
|
|
evEnt->s.otherEntityNum = ent->s.number; //the entity the mark should be placed on
|
|
evEnt->s.weapon = WP_ROCKET_LAUNCHER; //always say it's rocket so we make the right mark
|
|
|
|
//Try to place the decal by going from the missile location to the location of the person that was hit
|
|
VectorCopy(missile->r.currentOrigin, evEnt->s.origin);
|
|
VectorCopy(ent->r.currentOrigin, evEnt->s.origin2);
|
|
|
|
//it's hacky, but we want to move it up so it's more likely to hit
|
|
//the torso.
|
|
if (missile->r.currentOrigin[2] < ent->r.currentOrigin[2])
|
|
{ //move it up less so the decal is placed lower on the model then
|
|
evEnt->s.origin2[2] += 8;
|
|
}
|
|
else
|
|
{
|
|
evEnt->s.origin2[2] += 24;
|
|
}
|
|
|
|
//Special col check
|
|
evEnt->s.eventParm = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
return hitClient;
|
|
}
|