Files
neo.yt-tremulous/src/game/g_combat.c
T
Paradox460 49e3c15ae0 * Damage done to self does not raise bleed counter. (Issue 157)
* BleedingSpree goes down when damage is done to other team, but at 1/3 the damage done (Issue 157)
 * Admitdefeat votes cannot be spammed anymore by players, can't be called before `g_admitDefeatTime`, or by admin (Issue 159)
 * Fixed bug in listplayers and lockname (Issue 161)
 * Cleans up build near doors code (Issue 163)
 * Adds `/devolve` command for aliens. Does not give credits back, player must have full health, and is cvar controlled by `g_allowDevolve` (Issue 166)
 * Added silent specme, when `!specme s` is done by an admin (anyone with kick flag k), they are put to spec team, but it is not announced. (Issue 168)
 * Increase max registered players. (Issue 177)
2008-09-04 02:46:40 +00:00

1898 lines
53 KiB
C

/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.
Copyright (C) 2000-2006 Tim Angus
This file is part of Tremulous.
Tremulous is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
Tremulous is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Tremulous; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
===========================================================================
*/
#include "g_local.h"
damageRegion_t g_damageRegions[ PCL_NUM_CLASSES ][ MAX_LOCDAMAGE_REGIONS ];
int g_numDamageRegions[ PCL_NUM_CLASSES ];
armourRegion_t g_armourRegions[ UP_NUM_UPGRADES ][ MAX_ARMOUR_REGIONS ];
int g_numArmourRegions[ UP_NUM_UPGRADES ];
/*
============
AddScore
Adds score to both the client and his team
============
*/
void AddScore( gentity_t *ent, int score )
{
if( !ent->client )
return;
ent->client->ps.persistant[ PERS_SCORE ] += score;
CalculateRanks( );
}
/*
==================
LookAtKiller
==================
*/
void LookAtKiller( gentity_t *self, gentity_t *inflictor, gentity_t *attacker )
{
vec3_t dir;
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_VIEWLOCK ] = self->s.angles[ YAW ];
return;
}
self->client->ps.stats[ STAT_VIEWLOCK ] = vectoyaw( dir );
}
// these are just for logging, the client prints its own messages
char *modNames[ ] =
{
"MOD_UNKNOWN",
"MOD_SHOTGUN",
"MOD_BLASTER",
"MOD_PAINSAW",
"MOD_MACHINEGUN",
"MOD_CHAINGUN",
"MOD_PRIFLE",
"MOD_MDRIVER",
"MOD_LASGUN",
"MOD_LCANNON",
"MOD_LCANNON_SPLASH",
"MOD_FLAMER",
"MOD_FLAMER_SPLASH",
"MOD_GRENADE",
"MOD_WATER",
"MOD_SLIME",
"MOD_LAVA",
"MOD_CRUSH",
"MOD_TELEFRAG",
"MOD_FALLING",
"MOD_SUICIDE",
"MOD_TARGET_LASER",
"MOD_TRIGGER_HURT",
"MOD_ABUILDER_CLAW",
"MOD_LEVEL0_BITE",
"MOD_LEVEL1_CLAW",
"MOD_LEVEL1_PCLOUD",
"MOD_LEVEL3_CLAW",
"MOD_LEVEL3_POUNCE",
"MOD_LEVEL3_BOUNCEBALL",
"MOD_LEVEL2_CLAW",
"MOD_LEVEL2_ZAP",
"MOD_LEVEL4_CLAW",
"MOD_LEVEL4_CHARGE",
"MOD_SLOWBLOB",
"MOD_POISON",
"MOD_SWARM",
"MOD_HSPAWN",
"MOD_TESLAGEN",
"MOD_MGTURRET",
"MOD_REACTOR",
"MOD_ASPAWN",
"MOD_ATUBE",
"MOD_OVERMIND",
"MOD_RADIATION",
"MOD_SLAP",
"MOD_EXPLODE"
};
/*
==================
player_die
==================
*/
void player_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath )
{
gentity_t *ent;
int anim;
int killer;
int i, j;
char *killerName, *obit;
float totalTK = 0;
float totalDamage = 0.0f;
float percentDamage = 0.0f;
gentity_t *player;
qboolean tk = qfalse;
int spreeRate = 0;
if( self->client->ps.pm_type == PM_DEAD )
return;
if( attacker != self && attacker->client->ps.stats[ STAT_PTEAM ] == self->client->ps.stats[ STAT_PTEAM ] )
{
attacker->client->pers.statscounters.teamkills++;
if( attacker->client->pers.teamSelection == PTE_ALIENS )
{
level.alienStatsCounters.teamkills++;
}
else if( attacker->client->pers.teamSelection == PTE_HUMANS )
{
level.humanStatsCounters.teamkills++;
}
}
if( level.intermissiontime )
return;
// stop any following clients
// r1: removed, annoying.
self->client->ps.pm_type = PM_DEAD;
self->suicideTime = 0;
if( attacker )
{
killer = attacker->s.number;
if( attacker->client )
{
killerName = attacker->client->pers.netname;
tk = ( attacker != self && attacker->client->ps.stats[ STAT_PTEAM ]
== self->client->ps.stats[ STAT_PTEAM ] );
}
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^7 killed %s^7 by %s\n",
killer, self->s.number, meansOfDeath, killerName,
self->client->pers.netname, obit );
//TA: close any menus the client has open
G_CloseMenus( self->client->ps.clientNum );
//TA: deactivate all upgrades
for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ )
BG_DeactivateUpgrade( i, self->client->ps.stats );
// killing spree over
if( self->client->pers.statscounters.spreekills == -1 )
{
spreeRate = 2;
trap_SendServerCommand( -1,
va( "print \"%s^7's killing spree has come to an end\n\"",
self->client->pers.netname ) );
}
self->client->pers.statscounters.spreekills = 0;
if( meansOfDeath == MOD_SLAP )
{
if( self == attacker || !attacker )
{
if( g_slapKnockback.integer ){
trap_SendServerCommand( -1, va( "print \"%s^7 slapped themself silly\n\"",
self->client->pers.netname ) );
}
else {
trap_SendServerCommand( -1, va( "print \"%s^7 suffered a heart attck\n\"",
self->client->pers.netname ));
}
}
else
trap_SendServerCommand( -1,
va( "print \"%s^7 felt %s^7's authoritay\n\"",
self->client->pers.netname, killerName ) );
// do not send obituary or credit any kills by slapping, skip straight to
// setting the animations etc
goto finish_dying;
}
else if( meansOfDeath == MOD_EXPLODE )
{
if( self == attacker || !attacker )
trap_SendServerCommand( -1, va( "print \"%s^7 blew up in a firey death.\n\"",
self->client->pers.netname ) );
else
trap_SendServerCommand( -1,
va( "print \"^7%s^7 shouldn't have stood so close to ^7%s^7\n\"",
self->client->pers.netname, killerName ) );
// do not send obituary or credit any kills by slapping, skip straight to
// setting the animations etc
goto finish_dying;
}
// broadcast the death event to everyone
else if( !tk )
{
// this works as a temporary hack until the client supports it
if ( meansOfDeath == MOD_RADIATION )
trap_SendServerCommand( -1, va( "print \"%s^7 was irradiated by his base\n\"", self->client->pers.netname ) );
else if ( meansOfDeath == MOD_LEVEL2_CLAW && g_maraObituary.integer )
trap_SendServerCommand( -1, va( "print \"%s^7 bit off %s^7's face\n\"", attacker->client->pers.netname, self->client->pers.netname ) );
else
{
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
}
}
else
{
// tjw: obviously this is a hack and belongs in the client, but
// this works as a temporary fix.
trap_SendServerCommand( -1,
va( "print \"%s^7 was killed by ^1TEAMMATE^7 %s^7 (Did %d damage to %d max)\n\"",
self->client->pers.netname, attacker->client->pers.netname, self->client->tkcredits[ attacker->s.number ], self->client->ps.stats[ STAT_MAX_HEALTH ] ) );
trap_SendServerCommand( attacker - g_entities,
va( "cp \"You killed ^1TEAMMATE^7 %s\"", self->client->pers.netname ) );
G_admin_tklog_log( attacker, self, meansOfDeath );
}
self->enemy = attacker;
self->client->ps.persistant[ PERS_KILLED ]++;
self->client->pers.statscounters.deaths++;
if( self->client->pers.teamSelection == PTE_ALIENS )
{
level.alienStatsCounters.deaths++;
}
else if( self->client->pers.teamSelection == PTE_HUMANS )
{
level.humanStatsCounters.deaths++;
}
if( attacker && attacker->client )
{
attacker->client->lastkilled_client = self->s.number;
if( g_KillerHP.integer )
{
trap_SendServerCommand( self-g_entities, va( "print \"Your killer, %s^7, had %3i HP.\n\"", killerName, attacker->health ) );
}
if( attacker == self || OnSameTeam( self, attacker ) )
{
AddScore( attacker, -1 );
// Normal teamkill penalty
if( !g_retribution.integer ) {
if( attacker->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
G_AddCreditToClient( attacker->client, -FREEKILL_ALIEN, qtrue );
else if( attacker->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
G_AddCreditToClient( attacker->client, -FREEKILL_HUMAN, qtrue );
}
}
else
{
AddScore( attacker, 1 );
attacker->client->lastKillTime = level.time;
attacker->client->pers.statscounters.kills++;
if( attacker->client->pers.teamSelection == PTE_ALIENS )
{
level.alienStatsCounters.kills++;
}
else if( attacker->client->pers.teamSelection == PTE_HUMANS )
{
level.humanStatsCounters.kills++;
}
if( g_killingSpree.integer > 2 )
{
if( attacker->client->pers.statscounters.spreekills >= 0 )
attacker->client->pers.statscounters.spreekills += 60;
if( attacker->client->pers.statscounters.spreekills > ( g_killingSpree.integer - 1 ) * 60 )
{
attacker->client->pers.statscounters.spreekills = -1;
trap_SendServerCommand( -1,
va( "print \"%s^3 is on a killing spree! killer gets a double reward bonus\n\"",
attacker->client->pers.netname ) );
}
}
}
if( attacker == self )
{
attacker->client->pers.statscounters.suicides++;
if( attacker->client->pers.teamSelection == PTE_ALIENS )
{
level.alienStatsCounters.suicides++;
}
else if( attacker->client->pers.teamSelection == PTE_HUMANS )
{
level.humanStatsCounters.suicides++;
}
}
}
else if( attacker->s.eType != ET_BUILDABLE )
AddScore( self, -1 );
//total up all the damage done by every client
for( i = 0; i < MAX_CLIENTS; i++ )
{
totalDamage += (float)self->credits[ i ];
totalTK += (float)self->client->tkcredits[ i ];
}
// punish players for damaging teammates
if ( g_retribution.integer && totalTK )
{
int totalPrice;
int max = HUMAN_MAX_CREDITS;
if ( self->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
{
totalPrice = BG_ClassCanEvolveFromTo( PCL_ALIEN_LEVEL0, self->client->ps.stats[ STAT_PCLASS ], ALIEN_MAX_KILLS, 0 );
max = ALIEN_MAX_KILLS;
}
else
{
totalPrice = BG_GetValueOfEquipment( &self->client->ps );
}
if ( self->client->ps.persistant[ PERS_CREDIT ] + totalPrice > max )
totalPrice = max - self->client->ps.persistant[ PERS_CREDIT ];
if ( totalPrice > 0 )
{
totalTK += totalDamage;
if( totalTK < self->client->ps.stats[ STAT_MAX_HEALTH ] )
totalTK = self->client->ps.stats[ STAT_MAX_HEALTH ];
if ( self->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
{
for ( i = 0; i < MAX_CLIENTS; i++ )
{
int price;
// no retribution if self damage or enemmy damage or building damage or no damage from this client
if ( i == self - g_entities || !g_entities[ i ].client || !OnSameTeam( &g_entities[ i ], self ) || !self->client->tkcredits[ i ] )
continue;
// calculate retribution price (rounded up)
price = ( totalPrice * self->client->tkcredits[ i ] ) / totalTK + 0.5f;
self->client->tkcredits[ i ] = 0;
// check for enough credits
if ( g_entities[ i ].client->ps.persistant[ PERS_CREDIT ] < price )
price = g_entities[ i ].client->ps.persistant[ PERS_CREDIT ];
if ( price )
{
G_AddCreditToClient( self->client, price, qtrue );
G_AddCreditToClient( g_entities[ i ].client, -price, qtrue );
trap_SendServerCommand( self->client->ps.clientNum, va( "print \"Received ^3%d credits ^7from %s ^7in retribution.\n\"", price, g_entities[ i ].client->pers.netname ) );
trap_SendServerCommand( g_entities[ i ].client->ps.clientNum, va( "print \"Transfered ^3%d credits ^7to %s ^7in retribution.\n\"", price, self->client->pers.netname ) );
}
}
}
else
{
int toPay[ MAX_CLIENTS ] = { 0 };
int frags = totalPrice;
int damageForEvo = totalTK / totalPrice;
for ( i = 0; i < MAX_CLIENTS; i++ )
{
// no retribution if self damage or enemmy damage or building damage or no damage from this client
if ( i == self - g_entities || !g_entities[ i ].client || !OnSameTeam( &g_entities[ i ], self ) || !self->client->tkcredits[ i ] )
continue;
// find out how many full evos this client needs to pay
toPay[ i ] = ( totalPrice * self->client->tkcredits[ i ] ) / totalTK;
if ( toPay[ i ] > g_entities[ i ].client->ps.persistant[ PERS_CREDIT ] )
toPay[ i ] = g_entities[ i ].client->ps.persistant[ PERS_CREDIT ];
frags -= toPay[ i ];
self->client->tkcredits[ i ] -= damageForEvo * toPay[ i ];
}
// if we have not met the evo count, continue stealing evos
while ( 1 )
{
int maximum = 0;
int topClient = 0;
for ( i = 0; i < MAX_CLIENTS; i++ )
{
if ( self->client->tkcredits[ i ] > maximum && g_entities[ i ].client->ps.persistant[ PERS_CREDIT ] )
{
maximum = self->client->tkcredits[ i ];
topClient = i;
}
}
if ( !maximum )
break;
toPay[ topClient ]++;
self->client->tkcredits[ topClient ] = 0;
frags--;
if ( !frags )
break;
}
// now move the evos around
for ( i = 0; i < MAX_CLIENTS; i++ )
{
if ( !toPay[ i ] )
continue;
G_AddCreditToClient( self->client, toPay[ i ], qtrue );
G_AddCreditToClient( g_entities[ i ].client, -toPay[ i ], qtrue );
trap_SendServerCommand( self->client->ps.clientNum, va( "print \"Received ^3%d ^7evos from %s ^7in retribution.\n\"", toPay[ i ], g_entities[ i ].client->pers.netname ) );
trap_SendServerCommand( g_entities[ i ].client->ps.clientNum, va( "print \"Transfered ^3%d ^7evos to %s ^7in retribution.\n\"", toPay[ i ], self->client->pers.netname ) );
}
}
}
}
// if players did more than DAMAGE_FRACTION_FOR_KILL increment the stage counters
if( !OnSameTeam( self, attacker ) && totalDamage >= ( self->client->ps.stats[ STAT_MAX_HEALTH ] * DAMAGE_FRACTION_FOR_KILL ) )
{
if( self->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
{
trap_Cvar_Set( "g_alienKills", va( "%d", g_alienKills.integer + 1 ) );
if( g_alienStage.integer < 2 )
{
self->client->pers.statscounters.feeds++;
level.humanStatsCounters.feeds++;
if( g_feedingSpree.integer &&
level.reactorPresent &&
!G_BuildableRange( self->client->ps.origin, 600, BA_H_REACTOR ) &&
!G_BuildableRange( self->client->ps.origin, 200, BA_H_SPAWN ) )
{
self->client->pers.statscounters.spreefeeds += SPREE_FEED_VALUE;
}
}
}
else if( self->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
{
trap_Cvar_Set( "g_humanKills", va( "%d", g_humanKills.integer + 1 ) );
if( g_humanStage.integer < 2 )
{
self->client->pers.statscounters.feeds++;
level.alienStatsCounters.feeds++;
if( g_feedingSpree.integer &&
level.overmindPresent &&
!G_BuildableRange( self->client->ps.origin, 600, BA_A_OVERMIND ) &&
!G_BuildableRange( self->client->ps.origin, 200, BA_A_SPAWN ) )
{
self->client->pers.statscounters.spreefeeds += SPREE_FEED_VALUE;
}
}
}
}
if( totalDamage > 0.0f )
{
if( self->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
{
//nice simple happy bouncy human land
float classValue = BG_FindValueOfClass( self->client->ps.stats[ STAT_PCLASS ] );
for( i = 0; i < MAX_CLIENTS; i++ )
{
player = g_entities + i;
if( !player->client )
continue;
if( player->client->ps.stats[ STAT_PTEAM ] != PTE_HUMANS )
continue;
if( !self->credits[ i ] )
continue;
percentDamage = (float)self->credits[ i ] / totalDamage;
if( percentDamage > 0 && percentDamage < 1)
{
player->client->pers.statscounters.assists++;
level.humanStatsCounters.assists++;
}
if( spreeRate && player == attacker )
percentDamage *= (float)spreeRate;
//add credit
G_AddCreditToClient( player->client,
(int)( classValue * percentDamage ), qtrue );
}
}
else if( self->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
{
//horribly complex nasty alien land
float humanValue = BG_GetValueOfHuman( &self->client->ps );
int frags;
int unclaimedFrags = (int)humanValue;
for( i = 0; i < MAX_CLIENTS; i++ )
{
player = g_entities + i;
if( !player->client )
continue;
if( player->client->ps.stats[ STAT_PTEAM ] != PTE_ALIENS )
continue;
//this client did no damage
if( !self->credits[ i ] )
continue;
//nothing left to claim
if( !unclaimedFrags )
break;
percentDamage = (float)self->credits[ i ] / totalDamage;
if( percentDamage > 0 && percentDamage < 1)
{
player->client->pers.statscounters.assists++;
level.alienStatsCounters.assists++;
}
frags = (int)floor( humanValue * percentDamage);
if( frags > 0 )
{
//add kills
if( spreeRate && player == attacker )
G_AddCreditToClient( player->client, frags * spreeRate, qtrue );
else
G_AddCreditToClient( player->client, frags, qtrue );
//can't revist this account later
self->credits[ i ] = 0;
//reduce frags left to be claimed
unclaimedFrags -= frags;
}
}
//there are frags still to be claimed
if( unclaimedFrags )
{
//the clients remaining at this point do not
//have enough credit to claim even one frag
//so simply give the top <unclaimedFrags> clients
//a frag each
for( i = 0; i < unclaimedFrags; i++ )
{
int maximum = 0;
int topClient = 0;
for( j = 0; j < MAX_CLIENTS; j++ )
{
//this client did no damage
if( !self->credits[ j ] )
continue;
if( self->credits[ j ] > maximum )
{
maximum = self->credits[ j ];
topClient = j;
}
}
if( maximum > 0 )
{
player = g_entities + topClient;
//add kills
if( spreeRate && player == attacker )
G_AddCreditToClient( player->client, spreeRate, qtrue );
else
G_AddCreditToClient( player->client, 1, qtrue );
//can't revist this account again
self->credits[ topClient ] = 0;
}
}
}
}
}
ScoreboardMessage( 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 )
ScoreboardMessage( g_entities + i );
}
finish_dying:
self->client->pers.classSelection = PCL_NONE; //TA: reset the classtype
VectorCopy( self->s.origin, self->client->pers.lastDeathLocation );
self->takedamage = qfalse; // can still be gibbed
self->s.weapon = WP_NONE;
self->r.contents = CONTENTS_CORPSE;
self->s.angles[ PITCH ] = 0;
self->s.angles[ ROLL ] = 0;
self->s.angles[ YAW ] = self->s.apos.trBase[ YAW ];
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;
if( g_feedingSpree.integer )
{
int maxfeed;
maxfeed = g_feedingSpree.integer * SPREE_FEED_VALUE;
if( self->client->pers.statscounters.spreefeeds > maxfeed )
{
self->client->respawnTime += SPREE_FEED_DELAY * (self->client->pers.statscounters.spreefeeds - maxfeed );
trap_SendServerCommand( self->client->ps.clientNum,
va( "print \"You are on a feeding spree! respawn delayed %d seconds\n\"",
(self->client->respawnTime - level.time) / 1000 ) );
}
}
if( self->client->pers.bleeder )
{
int spreeLength;
spreeLength = self->client->pers.statscounters.spreebleeds / 10;
self->client->respawnTime += 9 * 1000;
trap_SendServerCommand( self->client->ps.clientNum,
va( "print \"^3Bleeding has made you an enemy of your own base for ^7%d:%02d\n^1Respawn delayed ^7%d^1 seconds\n",
spreeLength / 60, spreeLength % 60,
( self->client->respawnTime - level.time) / 1000 ) );
trap_SendServerCommand( self->client->ps.clientNum,
va( "cp \"^3Bleeding made you an enemy of your own base!\n\n^7for %d:%02d\n\n^1Respawn delayed %d seconds\"",
spreeLength / 60, spreeLength % 60,
( self->client->respawnTime - level.time) / 1000 ) );
}
// remove powerups
memset( self->client->ps.powerups, 0, sizeof( self->client->ps.powerups ) );
{
// normal death
static int i;
if( !( self->client->ps.persistant[ PERS_STATE ] & PS_NONSEGMODEL ) )
{
switch( i )
{
case 0:
anim = BOTH_DEATH1;
break;
case 1:
anim = BOTH_DEATH2;
break;
case 2:
default:
anim = BOTH_DEATH3;
break;
}
}
else
{
switch( i )
{
case 0:
anim = NSPA_DEATH1;
break;
case 1:
anim = NSPA_DEATH2;
break;
case 2:
default:
anim = NSPA_DEATH3;
break;
}
}
self->client->ps.legsAnim =
( ( self->client->ps.legsAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim;
if( !( self->client->ps.persistant[ PERS_STATE ] & PS_NONSEGMODEL ) )
{
self->client->ps.torsoAnim =
( ( self->client->ps.torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim;
}
// use own entityid if killed by non-client to prevent uint8_t overflow
G_AddEvent( self, EV_DEATH1 + i,
( killer < MAX_CLIENTS ) ? killer : self - g_entities );
// globally cycle through the different death animations
i = ( i + 1 ) % 3;
}
trap_LinkEntity( self );
}
////////TA: locdamage
/*
===============
G_ParseArmourScript
===============
*/
void G_ParseArmourScript( char *buf, int upgrade )
{
char *token;
int count;
count = 0;
while( 1 )
{
token = COM_Parse( &buf );
if( !token[0] )
break;
if( strcmp( token, "{" ) )
{
G_Printf( "Missing { in armour file\n" );
break;
}
if( count == MAX_ARMOUR_REGIONS )
{
G_Printf( "Max armour regions exceeded in locdamage file\n" );
break;
}
//default
g_armourRegions[ upgrade ][ count ].minHeight = 0.0;
g_armourRegions[ upgrade ][ count ].maxHeight = 1.0;
g_armourRegions[ upgrade ][ count ].minAngle = 0;
g_armourRegions[ upgrade ][ count ].maxAngle = 360;
g_armourRegions[ upgrade ][ count ].modifier = 1.0;
g_armourRegions[ upgrade ][ count ].crouch = qfalse;
while( 1 )
{
token = COM_ParseExt( &buf, qtrue );
if( !token[0] )
{
G_Printf( "Unexpected end of armour file\n" );
break;
}
if( !Q_stricmp( token, "}" ) )
{
break;
}
else if( !strcmp( token, "minHeight" ) )
{
token = COM_ParseExt( &buf, qfalse );
if ( !token[0] )
strcpy( token, "0" );
g_armourRegions[ upgrade ][ count ].minHeight = atof( token );
}
else if( !strcmp( token, "maxHeight" ) )
{
token = COM_ParseExt( &buf, qfalse );
if ( !token[0] )
strcpy( token, "100" );
g_armourRegions[ upgrade ][ count ].maxHeight = atof( token );
}
else if( !strcmp( token, "minAngle" ) )
{
token = COM_ParseExt( &buf, qfalse );
if ( !token[0] )
strcpy( token, "0" );
g_armourRegions[ upgrade ][ count ].minAngle = atoi( token );
}
else if( !strcmp( token, "maxAngle" ) )
{
token = COM_ParseExt( &buf, qfalse );
if ( !token[0] )
strcpy( token, "360" );
g_armourRegions[ upgrade ][ count ].maxAngle = atoi( token );
}
else if( !strcmp( token, "modifier" ) )
{
token = COM_ParseExt( &buf, qfalse );
if ( !token[0] )
strcpy( token, "1.0" );
g_armourRegions[ upgrade ][ count ].modifier = atof( token );
}
else if( !strcmp( token, "crouch" ) )
{
g_armourRegions[ upgrade ][ count ].crouch = qtrue;
}
}
g_numArmourRegions[ upgrade ]++;
count++;
}
}
/*
===============
G_ParseDmgScript
===============
*/
void G_ParseDmgScript( char *buf, int class )
{
char *token;
int count;
count = 0;
while( 1 )
{
token = COM_Parse( &buf );
if( !token[0] )
break;
if( strcmp( token, "{" ) )
{
G_Printf( "Missing { in locdamage file\n" );
break;
}
if( count == MAX_LOCDAMAGE_REGIONS )
{
G_Printf( "Max damage regions exceeded in locdamage file\n" );
break;
}
//default
g_damageRegions[ class ][ count ].minHeight = 0.0;
g_damageRegions[ class ][ count ].maxHeight = 1.0;
g_damageRegions[ class ][ count ].minAngle = 0;
g_damageRegions[ class ][ count ].maxAngle = 360;
g_damageRegions[ class ][ count ].modifier = 1.0;
g_damageRegions[ class ][ count ].crouch = qfalse;
while( 1 )
{
token = COM_ParseExt( &buf, qtrue );
if( !token[0] )
{
G_Printf( "Unexpected end of locdamage file\n" );
break;
}
if( !Q_stricmp( token, "}" ) )
{
break;
}
else if( !strcmp( token, "minHeight" ) )
{
token = COM_ParseExt( &buf, qfalse );
if ( !token[0] )
strcpy( token, "0" );
g_damageRegions[ class ][ count ].minHeight = atof( token );
}
else if( !strcmp( token, "maxHeight" ) )
{
token = COM_ParseExt( &buf, qfalse );
if ( !token[0] )
strcpy( token, "100" );
g_damageRegions[ class ][ count ].maxHeight = atof( token );
}
else if( !strcmp( token, "minAngle" ) )
{
token = COM_ParseExt( &buf, qfalse );
if ( !token[0] )
strcpy( token, "0" );
g_damageRegions[ class ][ count ].minAngle = atoi( token );
}
else if( !strcmp( token, "maxAngle" ) )
{
token = COM_ParseExt( &buf, qfalse );
if ( !token[0] )
strcpy( token, "360" );
g_damageRegions[ class ][ count ].maxAngle = atoi( token );
}
else if( !strcmp( token, "modifier" ) )
{
token = COM_ParseExt( &buf, qfalse );
if ( !token[0] )
strcpy( token, "1.0" );
g_damageRegions[ class ][ count ].modifier = atof( token );
}
else if( !strcmp( token, "crouch" ) )
{
g_damageRegions[ class ][ count ].crouch = qtrue;
}
}
g_numDamageRegions[ class ]++;
count++;
}
}
/*
============
G_CalcDamageModifier
============
*/
static float G_CalcDamageModifier( vec3_t point, gentity_t *targ, gentity_t *attacker, int class, int dflags )
{
vec3_t targOrigin;
vec3_t bulletPath;
vec3_t bulletAngle;
vec3_t pMINUSfloor, floor, normal;
float clientHeight, hitRelative, hitRatio;
int bulletRotation, clientRotation, hitRotation;
float modifier = 1.0f;
int i, j;
if( point == NULL )
return 1.0f;
if( g_unlagged.integer && targ->client && targ->client->unlaggedCalc.used )
VectorCopy( targ->client->unlaggedCalc.origin, targOrigin );
else
VectorCopy( targ->r.currentOrigin, targOrigin );
clientHeight = targ->r.maxs[ 2 ] - targ->r.mins[ 2 ];
if( targ->client->ps.stats[ STAT_STATE ] & SS_WALLCLIMBING )
VectorCopy( targ->client->ps.grapplePoint, normal );
else
VectorSet( normal, 0, 0, 1 );
VectorMA( targOrigin, targ->r.mins[ 2 ], normal, floor );
VectorSubtract( point, floor, pMINUSfloor );
hitRelative = DotProduct( normal, pMINUSfloor ) / VectorLength( normal );
if( hitRelative < 0.0f )
hitRelative = 0.0f;
if( hitRelative > clientHeight )
hitRelative = clientHeight;
hitRatio = hitRelative / clientHeight;
VectorSubtract( targOrigin, point, bulletPath );
vectoangles( bulletPath, bulletAngle );
clientRotation = targ->client->ps.viewangles[ YAW ];
bulletRotation = bulletAngle[ YAW ];
hitRotation = abs( clientRotation - bulletRotation );
hitRotation = hitRotation % 360; // Keep it in the 0-359 range
if( dflags & DAMAGE_NO_LOCDAMAGE )
{
for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ )
{
float totalModifier = 0.0f;
float averageModifier = 1.0f;
//average all of this upgrade's armour regions together
if( BG_InventoryContainsUpgrade( i, targ->client->ps.stats ) )
{
for( j = 0; j < g_numArmourRegions[ i ]; j++ )
totalModifier += g_armourRegions[ i ][ j ].modifier;
if( g_numArmourRegions[ i ] )
averageModifier = totalModifier / g_numArmourRegions[ i ];
else
averageModifier = 1.0f;
}
modifier *= averageModifier;
}
}
else
{
attacker->client->pers.statscounters.hitslocational++;
level.alienStatsCounters.hitslocational++;
for( i = 0; i < g_numDamageRegions[ class ]; i++ )
{
qboolean rotationBound;
if( g_damageRegions[ class ][ i ].minAngle >
g_damageRegions[ class ][ i ].maxAngle )
{
rotationBound = ( hitRotation >= g_damageRegions[ class ][ i ].minAngle &&
hitRotation <= 360 ) || ( hitRotation >= 0 &&
hitRotation <= g_damageRegions[ class ][ i ].maxAngle );
}
else
{
rotationBound = ( hitRotation >= g_damageRegions[ class ][ i ].minAngle &&
hitRotation <= g_damageRegions[ class ][ i ].maxAngle );
}
if( rotationBound &&
hitRatio >= g_damageRegions[ class ][ i ].minHeight &&
hitRatio <= g_damageRegions[ class ][ i ].maxHeight &&
( g_damageRegions[ class ][ i ].crouch ==
( targ->client->ps.pm_flags & PMF_DUCKED ) ) )
modifier *= g_damageRegions[ class ][ i ].modifier;
}
if(modifier == 2)
{
attacker->client->pers.statscounters.headshots++;
level.alienStatsCounters.headshots++;
}
for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ )
{
if( BG_InventoryContainsUpgrade( i, targ->client->ps.stats ) )
{
for( j = 0; j < g_numArmourRegions[ i ]; j++ )
{
qboolean rotationBound;
if( g_armourRegions[ i ][ j ].minAngle >
g_armourRegions[ i ][ j ].maxAngle )
{
rotationBound = ( hitRotation >= g_armourRegions[ i ][ j ].minAngle &&
hitRotation <= 360 ) || ( hitRotation >= 0 &&
hitRotation <= g_armourRegions[ i ][ j ].maxAngle );
}
else
{
rotationBound = ( hitRotation >= g_armourRegions[ i ][ j ].minAngle &&
hitRotation <= g_armourRegions[ i ][ j ].maxAngle );
}
if( rotationBound &&
hitRatio >= g_armourRegions[ i ][ j ].minHeight &&
hitRatio <= g_armourRegions[ i ][ j ].maxHeight &&
( g_armourRegions[ i ][ j ].crouch ==
( targ->client->ps.pm_flags & PMF_DUCKED ) ) )
modifier *= g_armourRegions[ i ][ j ].modifier;
}
}
}
}
return modifier;
}
/*
============
G_InitDamageLocations
============
*/
void G_InitDamageLocations( void )
{
char *modelName;
char filename[ MAX_QPATH ];
int i;
int len;
fileHandle_t fileHandle;
char buffer[ MAX_LOCDAMAGE_TEXT ];
for( i = PCL_NONE + 1; i < PCL_NUM_CLASSES; i++ )
{
modelName = BG_FindModelNameForClass( i );
Com_sprintf( filename, sizeof( filename ), "models/players/%s/locdamage.cfg", modelName );
len = trap_FS_FOpenFile( filename, &fileHandle, FS_READ );
if ( !fileHandle )
{
G_Printf( va( S_COLOR_RED "file not found: %s\n", filename ) );
continue;
}
if( len >= MAX_LOCDAMAGE_TEXT )
{
G_Printf( va( S_COLOR_RED "file too large: %s is %i, max allowed is %i", filename, len, MAX_LOCDAMAGE_TEXT ) );
trap_FS_FCloseFile( fileHandle );
continue;
}
trap_FS_Read( buffer, len, fileHandle );
buffer[len] = 0;
trap_FS_FCloseFile( fileHandle );
G_ParseDmgScript( buffer, i );
}
for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ )
{
modelName = BG_FindNameForUpgrade( i );
Com_sprintf( filename, sizeof( filename ), "armour/%s.armour", modelName );
len = trap_FS_FOpenFile( filename, &fileHandle, FS_READ );
//no file - no parsage
if ( !fileHandle )
continue;
if( len >= MAX_LOCDAMAGE_TEXT )
{
G_Printf( va( S_COLOR_RED "file too large: %s is %i, max allowed is %i", filename, len, MAX_LOCDAMAGE_TEXT ) );
trap_FS_FCloseFile( fileHandle );
continue;
}
trap_FS_Read( buffer, len, fileHandle );
buffer[len] = 0;
trap_FS_FCloseFile( fileHandle );
G_ParseArmourScript( buffer, i );
}
}
////////TA: locdamage
/*
============
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
============
*/
//TA: team is the team that is immune to this damage
void G_SelectiveDamage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker,
vec3_t dir, vec3_t point, int damage, int dflags, int mod, int team )
{
if( targ->client && ( team != targ->client->ps.stats[ STAT_PTEAM ] || targ->client->pers.bleeder ) )
G_Damage( targ, inflictor, attacker, dir, point, damage, dflags, mod );
}
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 = 0;
int knockback;
int takeNoOverkill;
float damagemodifier=0.0;
if( !targ->takedamage )
return;
// 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 ];
else if( attacker->client && attacker->client->pers.paused )
return;
// shootable doors / buttons don't actually have any health
if( targ->s.eType == ET_MOVER )
{
if( targ->use && ( targ->moverState == MOVER_POS1 ||
targ->moverState == ROTATOR_POS1 ) )
targ->use( targ, inflictor, attacker );
return;
}
client = targ->client;
if( client )
{
if( client->noclip && !g_devmapNoGod.integer)
return;
if( client->pers.paused )
return;
}
if( !dir )
dflags |= DAMAGE_NO_KNOCKBACK;
else
VectorNormalize( dir );
knockback = damage;
if( inflictor->s.weapon != WP_NONE )
{
knockback = (int)( (float)knockback *
BG_FindKnockbackScaleForWeapon( inflictor->s.weapon ) );
}
if( targ->client )
{
knockback = (int)( (float)knockback *
BG_FindKnockbackScaleForClass( targ->client->ps.stats[ STAT_PCLASS ] ) );
}
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;
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_dretchPunt.integer &&
targ->client->ps.stats[ STAT_PCLASS ] == PCL_ALIEN_LEVEL0 )
{
vec3_t dir, push;
VectorSubtract( targ->r.currentOrigin, attacker->r.currentOrigin, dir );
VectorNormalizeFast( dir );
VectorScale( dir, ( damage * 10.0f ), push );
push[2] = 64.0f;
VectorAdd( targ->client->ps.velocity, push, targ->client->ps.velocity );
return;
} else if( !g_friendlyFireMovementAttacks.integer && (mod == MOD_LEVEL4_CHARGE || mod == MOD_LEVEL3_POUNCE )){
// don't do friendly fire on movement attacks
return;
}
else if( !g_friendlyFire.integer )
{
if( !g_friendlyFireHumans.integer
&& targ->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
{
return;
}
if( !g_friendlyFireAliens.integer
&& targ->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
{
return;
}
}
}
// If target is buildable on the same team as the attacking client
if( targ->s.eType == ET_BUILDABLE && attacker->client &&
targ->biteam == attacker->client->pers.teamSelection )
{
if( !g_friendlyBuildableFire.integer )
return;
// don't do friendly fire on movement attacks
if( !g_friendlyFireMovementAttacks.integer && (mod == MOD_LEVEL4_CHARGE || mod == MOD_LEVEL3_POUNCE ))
return;
}
// check for godmode
if ( targ->flags & FL_GODMODE && !g_devmapNoGod.integer)
return;
// paused
if ( level.paused )
return;
if(targ->s.eType == ET_BUILDABLE && g_cheats.integer && g_devmapNoStructDmg.integer)
return;
}
// add to the attacker's hit counter
if( attacker->client && targ != attacker && targ->health > 0
&& targ->s.eType != ET_MISSILE
&& targ->s.eType != ET_GENERAL )
{
if( OnSameTeam( targ, attacker ) )
attacker->client->ps.persistant[ PERS_HITS ]--;
else
attacker->client->ps.persistant[ PERS_HITS ]++;
}
take = damage;
save = 0;
// 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;
}
// set the last client who damaged the target
targ->client->lasthurt_client = attacker->s.number;
targ->client->lasthurt_mod = mod;
damagemodifier = G_CalcDamageModifier( point, targ, attacker, client->ps.stats[ STAT_PCLASS ], dflags );
take = (int)( (float)take * damagemodifier );
//if boosted poison every attack
if( attacker->client && attacker->client->ps.stats[ STAT_STATE ] & SS_BOOSTED )
{
if( targ->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS &&
!( targ->client->ps.stats[ STAT_STATE ] & SS_POISONED ) &&
mod != MOD_LEVEL2_ZAP &&
targ->client->poisonImmunityTime < level.time )
{
targ->client->ps.stats[ STAT_STATE ] |= SS_POISONED;
targ->client->lastPoisonTime = level.time;
targ->client->lastPoisonClient = attacker;
attacker->client->pers.statscounters.repairspoisons++;
level.alienStatsCounters.repairspoisons++;
}
}
}
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 );
}
if( take )
{
if( take > targ->health )
{
take = targ->health;
}
//Increment some stats counters
if( attacker->client )
{
if( targ->biteam == attacker->client->pers.teamSelection || OnSameTeam( targ, attacker ) )
{
attacker->client->pers.statscounters.ffdmgdone += take;
if( attacker->client->pers.teamSelection == PTE_ALIENS )
{
level.alienStatsCounters.ffdmgdone+=take;
}
else if( attacker->client->pers.teamSelection == PTE_HUMANS )
{
level.humanStatsCounters.ffdmgdone+=take;
}
// bleeding spree
if( g_bleedingSpree.integer > 1 && attacker != targ )
{
attacker->client->pers.statscounters.spreebleeds += take;
if( attacker->client->pers.statscounters.spreebleeds > g_bleedingSpree.integer * 100 &&
!attacker->client->pers.bleeder )
{
attacker->client->pers.bleeder = qtrue;
level.bleeders++;
trap_SendServerCommand( -1,
va( "print \"%s^3 has become an enemy of their own base\n\"",
attacker->client->pers.netname ) );
trap_SendServerCommand( attacker - g_entities,
"cp \"^1Your team bleeding has irritated your base!\"" );
G_admin_tklog_log( attacker, NULL, mod );
}
else if( attacker->client->pers.statscounters.spreebleeds > g_bleedingSpree.integer * 66 &&
!attacker->client->pers.bleeder &&
attacker->client->pers.bleederLastWarn + 20 * 1000 < level.time )
{
attacker->client->pers.bleederLastWarn = level.time;
trap_SendServerCommand( attacker - g_entities,
"print \"^3Your bleeding is close to aggravating your base!\n\"" );
trap_SendServerCommand( attacker - g_entities,
"cp \"^1Your bleeding is close to aggravating your base!\"" );
}
}
}
else if( targ->s.eType == ET_BUILDABLE )
{
attacker->client->pers.statscounters.structdmgdone += take;
if( attacker->client->pers.teamSelection == PTE_ALIENS )
{
level.alienStatsCounters.structdmgdone+=take;
}
else if( attacker->client->pers.teamSelection == PTE_HUMANS )
{
level.humanStatsCounters.structdmgdone+=take;
}
if( targ->health > 0 && ( targ->health - take ) <=0 )
{
attacker->client->pers.statscounters.structskilled++;
if( attacker->client->pers.teamSelection == PTE_ALIENS )
{
level.alienStatsCounters.structskilled++;
}
else if( attacker->client->pers.teamSelection == PTE_HUMANS )
{
level.humanStatsCounters.structskilled++;
}
}
if( attacker->client->pers.statscounters.spreefeeds )
{
attacker->client->pers.statscounters.spreefeeds -= take;
if( attacker->client->pers.statscounters.spreefeeds < 0 )
attacker->client->pers.statscounters.spreefeeds = 0;
}
if( attacker->client->pers.statscounters.spreebleeds > 10 )
{
attacker->client->pers.statscounters.spreebleeds -= take / 3;
if( attacker->client->pers.statscounters.spreebleeds < 10 )
attacker->client->pers.statscounters.spreebleeds = 10;
}
}
else if( targ->client )
{
attacker->client->pers.statscounters.dmgdone +=take;
attacker->client->pers.statscounters.hits++;
if( attacker->client->pers.teamSelection == PTE_ALIENS )
{
level.alienStatsCounters.dmgdone+=take;
}
else if( attacker->client->pers.teamSelection == PTE_HUMANS )
{
level.humanStatsCounters.dmgdone+=take;
}
if( attacker->client->pers.statscounters.spreefeeds )
{
attacker->client->pers.statscounters.spreefeeds -= take;
if( attacker->client->pers.statscounters.spreefeeds < 0 )
attacker->client->pers.statscounters.spreefeeds = 0;
}
if( attacker->client->pers.statscounters.spreebleeds > 10 )
{
attacker->client->pers.statscounters.spreebleeds -= take / 3;
if( attacker->client->pers.statscounters.spreebleeds < 10 )
attacker->client->pers.statscounters.spreebleeds = 10;
}
}
}
takeNoOverkill = take;
if( takeNoOverkill > targ->health )
{
takeNoOverkill = targ->health;
}
targ->health = targ->health - take; // do the damage
if( targ->client )
targ->client->ps.stats[ STAT_HEALTH ] = targ->health;
targ->lastDamageTime = level.time;
//TA: add to the attackers "account" on the target
if( targ->client && attacker->client )
{
if( attacker != targ && !OnSameTeam( targ, attacker ) )
targ->credits[ attacker->client->ps.clientNum ] += take;
else if( attacker != targ && OnSameTeam( targ, attacker ) )
targ->client->tkcredits[ attacker->client->ps.clientNum ] += takeNoOverkill;
}
if( targ->health <= 0 )
{
if( client )
targ->flags |= FL_NO_KNOCKBACK;
if( targ->health < -999 )
targ->health = -999;
targ->enemy = attacker;
targ->die( targ, inflictor, attacker, take, mod );
return;
}
else if( targ->pain )
targ->pain( targ, attacker, 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;
}
//TA:
/*
============
G_SelectiveRadiusDamage
============
*/
qboolean G_SelectiveRadiusDamage( vec3_t origin, gentity_t *attacker, float damage,
float radius, gentity_t *ignore, int mod, int team )
{
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 ) )
{
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_SelectiveDamage( ent, NULL, attacker, dir, origin,
(int)points, DAMAGE_RADIUS|DAMAGE_NO_LOCDAMAGE, mod, team );
}
}
return hitClient;
}
/*
============
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 ) )
{
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|DAMAGE_NO_LOCDAMAGE, mod );
}
}
return hitClient;
}
void G_Knockback( gentity_t *targ, vec3_t dir, int knockback )
{
if( knockback && targ->client )
{
vec3_t kvel;
float mass;
mass = 200;
// Halve knockback for bsuits
if( targ->client &&
targ->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS &&
BG_InventoryContainsUpgrade( UP_BATTLESUIT, targ->client->ps.stats ) )
mass += 400;
// Halve knockback for crouching players
if(targ->client->ps.pm_flags&PMF_DUCKED) knockback /= 2;
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;
}
}
}