jedioutcast/CODE-mp/game/g_combat.c
2013-04-04 13:01:17 -05:00

2597 lines
60 KiB
C

// Copyright (C) 1999-2000 Id Software, Inc.
//
// g_combat.c
#include "g_local.h"
//rww - pd
void BotDamageNotification(gclient_t *bot, gentity_t *attacker);
//end rww
extern int protectHitSound;
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 );
}
/*
int G_GetHitLocFromSurfName( gentity_t *ent, const char *surfName, vec3_t point )
{
if ( !surfName || !surfName[0] )
{
return HL_NONE;
}
if ( !Q_strncmp( "hips", surfName, 4 ) )
{//FIXME: test properly for legs
if ( Q_irand( 0, 1 ) )
{
return HL_WAIST;
}
else if ( Q_irand( 0, 1 ) )
{
return HL_LEG_RT;
}
else
{
return HL_LEG_LT;
}
}
else if ( !Q_strncmp( "torso", surfName, 5 ) )
{
if ( !ent->client )
{
return 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 );
VectorSubtract( point, ent->client->renderInfo.torsoPoint, dirToImpact );
frontSide = DotProduct( t_fwd, dirToImpact );
rightSide = DotProduct( t_rt, dirToImpact );
upSide = DotProduct( t_up, dirToImpact );
if ( upSide < 0 )
{//hit at waist
return HL_WAIST;
}
else
{//hit on upper torso
if ( rightSide > 10 )
{
return HL_ARM_RT;
}
else if ( rightSide < -10 )
{
return HL_ARM_LT;
}
else if ( rightSide > 4 )
{
if ( frontSide > 0 )
{
return HL_CHEST_RT;
}
else
{
return HL_BACK_RT;
}
}
else if ( rightSide < -4 )
{
if ( frontSide > 0 )
{
return HL_CHEST_LT;
}
else
{
return HL_BACK_LT;
}
}
else if ( upSide > 6 )
{
return HL_HEAD;
}
else if ( frontSide > 0 )
{
return HL_CHEST;
}
else
{
return HL_BACK;
}
}
}
}
else if ( !Q_strncmp( "head", surfName, 4 ) )
{
return HL_HEAD;
}
else if ( !Q_strncmp( "r_arm", surfName, 5 ) )
{
return HL_ARM_RT;
}
else if ( !Q_strncmp( "l_arm", surfName, 5 ) )
{
return HL_ARM_LT;
}
else if ( !Q_strncmp( "r_leg", surfName, 5 ) )
{
if ( ent->client && DistanceSquared( ent->client->renderInfo.crotchPoint, point ) > 144 )
{//not close enough to the pelvis to be a dismemberment hit and we don't dismember at the ankle or knee
return HL_FOOT_RT;
}
return HL_LEG_RT;
}
else if ( !Q_strncmp( "l_leg", surfName, 5 ) )
{
if ( ent->client && DistanceSquared( ent->client->renderInfo.crotchPoint, point ) > 144 )
{//not close enough to the pelvis to be a dismemberment hit and we don't dismember at the ankle or knee
return HL_FOOT_LT;
}
return HL_LEG_LT;
}
else if ( !Q_strncmp( "r_hand", surfName, 6 ) )
{
return HL_HAND_RT;
}
else if ( !Q_strncmp( "l_hand", surfName, 6 ) )
{
return HL_HAND_LT;
}
return HL_NONE;
}
*/
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;
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, 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
============
*/
void AddScore( gentity_t *ent, vec3_t origin, int score ) {
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 )
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 (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))
{
self->client->ps.stats[STAT_WEAPONS] &= ~(1 << weapon);
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;
// 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 ) {
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;
}
/*
==================
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.
if(1) // if ( self->health > GIB_HEALTH )
{
return;
}
if ( !g_blood.integer ) {
self->health = GIB_HEALTH+1;
return;
}
GibEntity( self, 0 );
}
// these are just for logging, the client prints its own messages
char *modNames[] = {
"MOD_UNKNOWN",
"MOD_STUN_BATON",
"MOD_MELEE",
"MOD_SABER",
"MOD_BRYAR_PISTOL",
"MOD_BRYAR_PISTOL_ALT",
"MOD_BLASTER",
"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_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
}
static 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;
if (!self || !self->client)
{
return 0;
}
max_health = self->client->ps.stats[STAT_MAX_HEALTH];
if ( hitLoc == HL_NONE )
{
hitLoc = G_GetHitLocation( self, point );//self->hitLoc
}
//dead flops
switch( self->client->ps.legsAnim&~ANIM_TOGGLEBIT )
{
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 )
{
//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( self->client->ps.velocity ) )
{
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( self->client->ps.velocity ) )
{
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;
}
}
return deathAnim;
}
void G_CheckForDismemberment(gentity_t *ent, vec3_t point, int damage, int deathAnim);
/*
==================
player_die
==================
*/
void player_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath ) {
gentity_t *ent;
int anim;
int contents;
int killer;
int i;
char *killerName, *obit;
qboolean wasJediMaster = qfalse;
if ( self->client->ps.pm_type == PM_DEAD ) {
return;
}
if ( level.intermissiontime ) {
return;
}
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) &&
self->client->ps.otherKillerTime > level.time)
{
attacker = &g_entities[self->client->ps.otherKiller];
}
// check for an almost capture
CheckAlmostCapture( self, attacker );
self->client->ps.pm_type = PM_DEAD;
if ( attacker ) {
killer = attacker->s.number;
if ( attacker->client ) {
killerName = attacker->client->pers.netname;
} else {
killerName = "<non-client>";
}
} else {
killer = ENTITYNUM_WORLD;
killerName = "<world>";
}
if ( killer < 0 || killer >= MAX_CLIENTS ) {
killer = ENTITYNUM_WORLD;
killerName = "<world>";
}
if ( meansOfDeath < 0 || meansOfDeath >= sizeof( modNames ) / sizeof( modNames[0] ) ) {
obit = "<bad obituary>";
} else {
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 );
G_LogWeaponKill(killer, meansOfDeath);
G_LogWeaponDeath(self->s.number, self->s.weapon);
if (attacker && attacker->client && attacker->inuse)
{
G_LogWeaponFrag(killer, self->s.number);
}
// broadcast the death event to everyone
ent = G_TempEntity( self->r.currentOrigin, EV_OBITUARY );
ent->s.eventParm = meansOfDeath;
ent->s.otherEntityNum = self->s.number;
ent->s.otherEntityNum2 = killer;
ent->r.svFlags = SVF_BROADCAST; // send to everyone
ent->s.isJediMaster = wasJediMaster;
self->enemy = attacker;
self->client->ps.persistant[PERS_KILLED]++;
if (self == attacker)
{
self->client->ps.fd.suicides++;
}
if (attacker && attacker->client) {
attacker->client->lastkilled_client = self->s.number;
if ( attacker == self || OnSameTeam (self, attacker ) ) {
AddScore( attacker, self->r.currentOrigin, -1 );
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
{
AddScore( attacker, self->r.currentOrigin, 1 );
}
if( meansOfDeath == MOD_STUN_BATON ) {
// play humiliation on player
attacker->client->ps.persistant[PERS_GAUNTLET_FRAG_COUNT]++;
// add the sprite over the player's head
attacker->client->ps.eFlags &= ~(EF_AWARD_IMPRESSIVE | EF_AWARD_EXCELLENT | EF_AWARD_GAUNTLET | EF_AWARD_ASSIST | EF_AWARD_DEFEND | EF_AWARD_CAP );
attacker->client->ps.eFlags |= EF_AWARD_GAUNTLET;
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]++;
// add the sprite over the player's head
attacker->client->ps.eFlags &= ~(EF_AWARD_IMPRESSIVE | EF_AWARD_EXCELLENT | EF_AWARD_GAUNTLET | EF_AWARD_ASSIST | EF_AWARD_DEFEND | EF_AWARD_CAP );
attacker->client->ps.eFlags |= EF_AWARD_EXCELLENT;
attacker->client->rewardTime = level.time + REWARD_SPRITE_TIME;
}
attacker->client->lastKillTime = level.time;
}
} 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;
}
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) {
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 );
}
}
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;
self->r.contents = CONTENTS_CORPSE;
self->client->ps.zoomMode = 0; // Turn off zooming when we die
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->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;
switch ( i ) {
case 0:
anim = BOTH_DEATH1;
break;
case 1:
anim = BOTH_DEATH2;
break;
case 2:
default:
anim = BOTH_DEATH3;
break;
}
anim = G_PickDeathAnim(self, self->pos1, damage, meansOfDeath, HL_NONE);
if (anim < 1)
{
anim = BOTH_DEATH1;
}
if (meansOfDeath == MOD_SABER)
{
G_CheckForDismemberment(self, self->pos1, damage, anim);
}
// 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;
self->client->ps.legsAnim =
( ( self->client->ps.legsAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim;
self->client->ps.torsoAnim =
( ( self->client->ps.torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim;
// 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 );
}
// the body can still be gibbed
self->die = body_die;
//rww - even though we have no gibbing, if you shoot the body it will disappear unless it doesn't take damage
self->takedamage = qfalse;
// globally cycle through the different death animations
i = ( i + 1 ) % 3;
}
trap_LinkEntity (self);
}
/*
================
CheckArmor
================
*/
int CheckArmor (gentity_t *ent, int damage, int dflags)
{
gclient_t *client;
int save;
int count;
if (!damage)
return 0;
client = ent->client;
if (!client)
return 0;
if (dflags & DAMAGE_NO_ARMOR)
return 0;
// armor
count = client->ps.stats[STAT_ARMOR];
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 && targ->client && targ->client->ps.usingATST)
{
return;
}
if ( targ->physicsBounce > 0 ) //overide the mass
mass = targ->physicsBounce;
else
mass = 200;
if ( g_gravity.value > 0 )
{
VectorScale( newDir, g_knockback.value * (float)knockback / mass * 0.8, kvel );
kvel[2] = newDir[2] * g_knockback.value * (float)knockback / mass * 1.5;
}
else
{
VectorScale( newDir, g_knockback.value * (float)knockback / mass, kvel );
}
if ( targ->client )
{
VectorAdd( targ->client->ps.velocity, kvel, targ->client->ps.velocity );
}
else if ( targ->s.pos.trType != TR_STATIONARY && targ->s.pos.trType != TR_LINEAR_STOP )
{
VectorAdd( targ->s.pos.trDelta, kvel, targ->s.pos.trDelta );
VectorCopy( targ->r.currentOrigin, targ->s.pos.trBase );
targ->s.pos.trTime = level.time;
}
// set the timer so that the other client can't cancel
// out the movement immediately
if ( targ->client && !targ->client->ps.pm_time )
{
int t;
t = knockback * 2;
if ( t < 50 ) {
t = 50;
}
if ( t > 200 ) {
t = 200;
}
targ->client->ps.pm_time = t;
targ->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
}
}
/*
================
RaySphereIntersections
================
*/
int RaySphereIntersections( vec3_t origin, float radius, vec3_t point, vec3_t dir, vec3_t intersections[2] ) {
float b, c, d, t;
// | origin - (point + t * dir) | = radius
// a = dir[0]^2 + dir[1]^2 + dir[2]^2;
// b = 2 * (dir[0] * (point[0] - origin[0]) + dir[1] * (point[1] - origin[1]) + dir[2] * (point[2] - origin[2]));
// c = (point[0] - origin[0])^2 + (point[1] - origin[1])^2 + (point[2] - origin[2])^2 - radius^2;
// normalize dir so a = 1
VectorNormalize(dir);
b = 2 * (dir[0] * (point[0] - origin[0]) + dir[1] * (point[1] - origin[1]) + dir[2] * (point[2] - origin[2]));
c = (point[0] - origin[0]) * (point[0] - origin[0]) +
(point[1] - origin[1]) * (point[1] - origin[1]) +
(point[2] - origin[2]) * (point[2] - origin[2]) -
radius * radius;
d = b * b - 4 * c;
if (d > 0) {
t = (- b + sqrt(d)) / 2;
VectorMA(point, t, dir, intersections[0]);
t = (- b - sqrt(d)) / 2;
VectorMA(point, t, dir, intersections[1]);
return 2;
}
else if (d == 0) {
t = (- b ) / 2;
VectorMA(point, t, dir, intersections[0]);
return 1;
}
return 0;
}
void LimbTouch( gentity_t *self, gentity_t *other, trace_t *trace )
{
}
void LimbThink( gentity_t *ent )
{
if (ent->speed < level.time)
{
ent->think = G_FreeEntity;
ent->nextthink = level.time;
return;
}
if (ent->s.pos.trType != TR_GRAVITY)
{
int addamt = (ent->speed - level.time);
if (addamt > 5000)
{
addamt = 5000;
}
if (addamt < 0)
{
addamt = 0;
}
VectorClear(ent->s.pos.trDelta);
ent->think = G_FreeEntity;
ent->nextthink = level.time + addamt;
return;
}
G_RunMissile(ent);
G_RunObject(ent);
}
void G_G2PlayerAngles( gentity_t *ent, vec3_t legs[3], vec3_t legsAngles);
void G_GetDismemberBolt(gentity_t *self, vec3_t boltPoint, int limbType)
{
int useBolt = self->bolt_Head;
vec3_t properOrigin, properAngles, addVel;
vec3_t legAxis[3];
mdxaBone_t boltMatrix;
float fVSpeed = 0;
switch (limbType)
{
case G2_MODELPART_HEAD:
useBolt = self->bolt_Head;
break;
case G2_MODELPART_WAIST:
useBolt = self->bolt_Waist;
break;
case G2_MODELPART_LARM:
useBolt = self->bolt_LArm;
break;
case G2_MODELPART_RARM:
useBolt = self->bolt_RArm;
break;
case G2_MODELPART_LLEG:
useBolt = self->bolt_LLeg;
break;
case G2_MODELPART_RLEG:
useBolt = self->bolt_RLeg;
break;
default:
useBolt = self->bolt_Head;
break;
}
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;
AnglesToAxis( properAngles, legAxis );
G_G2PlayerAngles( self, legAxis, properAngles );
trap_G2API_GetBoltMatrix(self->client->ghoul2, 0, useBolt, &boltMatrix, properAngles, properOrigin, level.time, NULL, vec3_origin);
boltPoint[0] = boltMatrix.matrix[0][3];
boltPoint[1] = boltMatrix.matrix[1][3];
boltPoint[2] = boltMatrix.matrix[2][3];
}
void G_Dismember( gentity_t *ent, vec3_t point, int limbType, float limbRollBase, float limbPitchBase, int deathAnim )
{
vec3_t dir, newPoint, vel;
gentity_t *limb;
VectorCopy( point, newPoint );
limb = G_Spawn();
limb->classname = "playerlimb";
G_SetOrigin( limb, newPoint );
VectorCopy( newPoint, limb->s.pos.trBase );
limb->think = LimbThink;
limb->touch = LimbTouch;
limb->speed = level.time + Q_irand(4000, 8000);
limb->nextthink = level.time + FRAMETIME;
//need size, contents, clipmask
limb->r.svFlags = SVF_USE_CURRENT_ORIGIN;
limb->clipmask = MASK_SOLID;
limb->r.contents = CONTENTS_TRIGGER;
VectorSet( limb->r.mins, -3.0f, -3.0f, -3.0f );
VectorSet( limb->r.maxs, 3.0f, 3.0f, 3.0f );
// VectorClear(limb->r.mins);
// VectorClear(limb->r.maxs);
//move it
limb->s.eType = ET_GENERAL;
limb->s.weapon = G2_MODEL_PART;
if (limbType == G2_MODELPART_HEAD)
{
limb->bounceCount = 2;
}
else
{
limb->bounceCount = 1;
}
limb->s.pos.trType = TR_GRAVITY;
limb->s.pos.trTime = level.time; // move a bit on the very first frame
VectorSubtract( point, ent->r.currentOrigin, dir );
VectorNormalize( dir );
VectorCopy(ent->client->ps.velocity, vel);
VectorMA( vel, 100, dir, limb->s.pos.trDelta );
//make it bounce some
limb->s.eFlags |= EF_BOUNCE_HALF;
//no trDuration?
//spin it
VectorClear( limb->s.apos.trBase );
limb->s.apos.trBase[0] = limbPitchBase;
limb->s.apos.trBase[1] = ent->client->ps.viewangles[1];
limb->s.apos.trBase[2] = limbRollBase;
VectorClear( limb->s.apos.trDelta );
limb->s.apos.trDelta[0] = Q_irand( -300, 300 );
limb->s.apos.trDelta[2] = Q_irand( -300, 300 );
limb->s.apos.trDelta[1] = Q_irand( -300, 300 );
if (limbType == G2_MODELPART_WAIST)
{
limb->s.apos.trDelta[0] = Q_irand( -60, 60 );
limb->s.apos.trDelta[2] = Q_irand( -60, 60 );
limb->s.apos.trDelta[1] = Q_irand( -60, 60 );
}
limb->s.apos.trTime = level.time;
limb->s.apos.trType = TR_LINEAR;
limb->s.modelGhoul2 = limbType;
limb->s.g2radius = 200;
limb->s.modelindex = ent->s.number;
limb->s.modelindex2 = deathAnim;
trap_LinkEntity( limb );
}
/*
void DismembermentTest(gentity_t *self)
{
int sect = G2_MODELPART_HEAD;
vec3_t boltPoint;
char *sectc = ConcatArgs( 1 );
if (sectc && sectc[0])
{
sect = atoi(sectc)+G2_MODELPART_HEAD;
}
G_GetDismemberBolt(self, boltPoint, sect);
G_Dismember( self, boltPoint, sect, 90, 0 );
}
*/
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 = -1;
VectorCopy(self->client->ps.origin, clEye);
clEye[2] += self->client->ps.viewheight;
VectorSubtract( hitloc, clEye, diff );
diff[2] = 0;
VectorNormalize( diff );
fwdangles[1] = self->client->ps.viewangles[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;
}
void G_CheckForDismemberment(gentity_t *ent, vec3_t point, int damage, int deathAnim)
{
int hitLoc, hitLocUse = -1;
vec3_t boltPoint;
int dismember = g_dismember.integer;
if (!dismember)
{
return;
}
if (Q_irand(0, 100) > dismember)
{
return;
}
if (damage < 20)
{
return;
}
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;
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:
case HL_HAND_RT:
hitLocUse = G2_MODELPART_RARM;
break;
case HL_ARM_LT:
case HL_HAND_LT:
hitLocUse = G2_MODELPART_LARM;
break;
case HL_HEAD:
hitLocUse = G2_MODELPART_HEAD;
default:
hitLocUse = G_GetHitQuad(ent, point);
break;
}
if (hitLocUse == -1)
{
return;
}
G_GetDismemberBolt(ent, boltPoint, hitLocUse);
G_Dismember(ent, boltPoint, hitLocUse, 90, 0, deathAnim);
}
/*
============
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
============
*/
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 (!targ->takedamage) {
return;
}
if (targ && targ->client && targ->client->ps.duelInProgress)
{
if (attacker && attacker->client && attacker->s.number != targ->client->ps.duelIndex)
{
return;
}
}
if (attacker && attacker->client && attacker->client->ps.duelInProgress)
{
if (targ && targ->client && targ->s.number != attacker->client->ps.duelIndex)
{
return;
}
}
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 boltpoint4 == 1 then it's glass or a breakable and those do have health
if ( targ->s.eType == ET_MOVER && targ->boltpoint4 != 1 ) {
if ( targ->use && targ->moverState == MOVER_POS1 ) {
targ->use( targ, inflictor, attacker );
}
return;
}
// reduce damage by the attacker's handicap value
// unless they are rocket jumping
if ( attacker->client && attacker != targ ) {
max = attacker->client->ps.stats[STAT_MAX_HEALTH];
damage = damage * max / 100;
}
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;
}
if (targ && targ->client && targ->client->ps.usingATST)
{
knockback = 0;
}
// figure momentum add, even if the damage won't be taken
if ( knockback && targ->client ) {
vec3_t kvel;
float mass;
mass = 200;
VectorScale (dir, g_knockback.value * (float)knockback / mass, kvel);
VectorAdd (targ->client->ps.velocity, kvel, targ->client->ps.velocity);
// set the timer so that the other client can't cancel
// out the movement immediately
if ( !targ->client->ps.pm_time ) {
int t;
t = knockback * 2;
if ( t < 50 ) {
t = 50;
}
if ( t > 200 ) {
t = 200;
}
targ->client->ps.pm_time = t;
targ->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
}
}
// check for completely getting out of the damage
if ( !(dflags & DAMAGE_NO_PROTECTION) ) {
// if TF_NO_FRIENDLY_FIRE is set, don't do damage to the target
// if the attacker was on the same team
if ( targ != attacker && OnSameTeam (targ, attacker) ) {
if ( !g_friendlyFire.integer ) {
return;
}
}
if (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 ) {
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;
}
}
}
if (attacker && attacker->client)
{
if (targ->teamnodmg &&
targ->teamnodmg == attacker->client->sess.sessionTeam &&
!g_ff_objectives.integer)
{
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
// calculated after knockback, so rocket jumping works
if ( targ == attacker) {
damage *= 0.5;
}
if ( damage < 1 ) {
damage = 1;
}
take = damage;
save = 0;
// save some from armor
asave = CheckArmor (targ, take, dflags);
if (asave)
{
shieldAbsorbed = asave;
}
take -= asave;
if (mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT)
{ //demp2 does full damage to shields, but only 1/3 normal damage to health
if (take > 0)
{
take /= 3;
if (take < 1)
{
take = 1;
}
}
}
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 );
}
// 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 (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->ps.fd.forcePowerLevel[FP_PROTECT] == FORCE_LEVEL_1)
{
famt = 1;
hamt = 0.5;
if (maxtake > 100)
{
maxtake = 100;
}
}
else if (targ->client->ps.fd.forcePowerLevel[FP_PROTECT] == FORCE_LEVEL_2)
{
famt = 0.5;
hamt = 0.75;
if (maxtake > 200)
{
maxtake = 200;
}
}
else if (targ->client->ps.fd.forcePowerLevel[FP_PROTECT] == FORCE_LEVEL_3)
{
famt = 0.25;
hamt = 1;
if (maxtake > 400)
{
maxtake = 400;
}
}
if (!targ->client->ps.powerups[PW_FORCE_BOON])
{
targ->client->ps.fd.forcePower -= maxtake*famt;
}
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)
{
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);
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->client->ps.fd.forcePowersActive & (1 << FP_RAGE)) && (inflictor->client || attacker->client))
{
take /= (targ->client->ps.fd.forcePowerLevel[FP_RAGE]);
}
targ->health = targ->health - take;
if ( targ->client ) {
targ->client->ps.stats[STAT_HEALTH] = targ->health;
}
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;
}
}
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);
}
}
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 );
VectorCopy( dir, targ->pos2 );
}
targ->enemy = attacker;
targ->die (targ, inflictor, attacker, take, mod);
return;
} else if ( targ->pain ) {
targ->pain (targ, attacker, take);
}
G_LogWeaponDamage(attacker->s.number, mod, take);
}
}
/*
============
CanDamage
Returns qtrue if the inflictor can directly damage the target. Used for
explosions and melee attacks.
============
*/
qboolean CanDamage (gentity_t *targ, vec3_t origin) {
vec3_t dest;
trace_t tr;
vec3_t midpoint;
// use the midpoint of the bounds instead of the origin, because
// bmodels may have their origin is 0,0,0
VectorAdd (targ->r.absmin, targ->r.absmax, midpoint);
VectorScale (midpoint, 0.5, midpoint);
VectorCopy (midpoint, dest);
trap_Trace ( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID);
if (tr.fraction == 1.0 || tr.entityNum == targ->s.number)
return qtrue;
// this should probably check in the plane of projection,
// rather than in world coordinate, and also include Z
VectorCopy (midpoint, dest);
dest[0] += 15.0;
dest[1] += 15.0;
trap_Trace ( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID);
if (tr.fraction == 1.0)
return qtrue;
VectorCopy (midpoint, dest);
dest[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, 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;
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;
G_Damage (ent, NULL, attacker, dir, origin, (int)points, DAMAGE_RADIUS, mod);
}
}
return hitClient;
}