1828 lines
51 KiB
C++
1828 lines
51 KiB
C++
// g_combat.c
|
|
|
|
#include "g_local.h"
|
|
#include "b_local.h"
|
|
#include "g_functions.h"
|
|
#include "anims.h"
|
|
#include "objectives.h"
|
|
#include "..\cgame\cg_text.h"
|
|
#include "..\cgame\cg_local.h"
|
|
|
|
extern cvar_t *g_debugDamage;
|
|
extern qboolean stop_icarus;
|
|
|
|
gentity_t *g_lastClientDamaged;
|
|
|
|
int ffireLevel = 0; //Current level of "anger" for friendly fire
|
|
int ffireForgivenessTimer = 0;
|
|
int killPlayerTimer = 0;
|
|
int loadBrigTimer = 0;
|
|
extern int teamCount[];
|
|
extern int teamLastEnemyTime[];
|
|
extern const int FFIRE_LEVEL_RETALIATION = 10;
|
|
|
|
extern void NPC_TempLookTarget ( gentity_t *self, int lookEntNum, int minLookTime, int maxLookTime );
|
|
extern void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime );
|
|
extern qboolean PM_HasAnimation( gentity_t *ent, int animation );
|
|
extern qboolean G_TeamEnemy( gentity_t *self );
|
|
extern void ChangeWeapon( gentity_t *ent, int newWeapon );
|
|
|
|
extern void AddSightEvent( gentity_t *owner, vec3_t position, float radius, alertEventLevel_e alertLevel );
|
|
extern void AddSoundEvent( gentity_t *owner, vec3_t position, float radius, alertEventLevel_e alertLevel );
|
|
extern void G_SetEnemy( gentity_t *self, gentity_t *enemy );
|
|
extern void PM_SetLegsAnimTimer( gentity_t *ent, int *legsAnimTimer, int time );
|
|
extern void PM_SetTorsoAnimTimer( gentity_t *ent, int *torsoAnimTimer, int time );
|
|
extern int PM_PickAnim( gentity_t *self, int minAnim, int maxAnim );
|
|
extern qboolean PM_InOnGroundAnim (gentity_t *self);
|
|
|
|
/*
|
|
============
|
|
AddScore
|
|
|
|
Adds score to both the client and his team
|
|
============
|
|
*/
|
|
void AddScore( gentity_t *ent, int score ) {
|
|
if ( !ent->client ) {
|
|
return;
|
|
}
|
|
// no scoring during pre-match warmup
|
|
ent->client->ps.persistant[PERS_SCORE] += score;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
TossClientItems
|
|
|
|
Toss the weapon and powerups for the killed player
|
|
=================
|
|
*/
|
|
void TossClientItems( gentity_t *self ) {
|
|
gitem_t *item;
|
|
int weapon;
|
|
|
|
// drop the weapon if not a gauntlet or machinegun
|
|
weapon = self->s.weapon;
|
|
// if ( weapon > WP_PHASER && self->client->ps.ammo[ weapon ] )
|
|
if ( weapon > WP_PHASER && self->client->ps.ammo[ weaponData[weapon].ammoIndex ] ) // checkme
|
|
{
|
|
|
|
// find the item type for this weapon
|
|
item = FindItemForWeapon( (weapon_t) weapon );
|
|
|
|
// spawn the item
|
|
Drop_Item( self, item, 0, qtrue );
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
==================
|
|
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->currentAngles[YAW];
|
|
return;
|
|
}
|
|
|
|
self->client->ps.stats[STAT_DEAD_YAW] = vectoyaw ( dir );
|
|
|
|
angles[YAW] = vectoyaw ( dir );
|
|
angles[PITCH] = 0;
|
|
angles[ROLL] = 0;
|
|
}
|
|
|
|
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 );
|
|
}
|
|
/*
|
|
==================
|
|
ExplodeDeath
|
|
==================
|
|
*/
|
|
|
|
//FIXME: all hacked up...
|
|
|
|
void CG_SurfaceExplosion( vec3_t origin, vec3_t normal, float radius, float shake_speed, qboolean smoke );
|
|
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->currentOrigin, self->s.pos.trBase );
|
|
|
|
// tent = G_TempEntity( self->s.origin, EV_FX_EXPLOSION );
|
|
AngleVectors(self->s.angles, forward, NULL, NULL);
|
|
CG_SurfaceExplosion( self->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->owner )
|
|
{
|
|
attacker = self->owner;
|
|
}
|
|
G_RadiusDamage( self->currentOrigin, attacker, self->splashDamage, self->splashRadius,
|
|
attacker, MOD_UNKNOWN );
|
|
}
|
|
|
|
ObjectDie( self, self, self, 20, 0 );
|
|
}
|
|
|
|
void ExplodeDeath_Wait( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath )
|
|
{
|
|
self->e_DieFunc = dieF_NULL;
|
|
self->nextthink = level.time + Q_irand(100, 500);
|
|
self->e_ThinkFunc = thinkF_ExplodeDeath;
|
|
}
|
|
|
|
void GoExplodeDeath( gentity_t *self, gentity_t *other, gentity_t *activator)
|
|
{
|
|
if (self->behaviorSet[BSET_USE])
|
|
{
|
|
G_ActivateBehavior(self,BSET_USE);
|
|
}
|
|
|
|
self->targetname = ""; //Make sure this entity cannot be told to explode again (recursive death fix)
|
|
|
|
ExplodeDeath( self );
|
|
}
|
|
|
|
void G_ActivateBehavior (gentity_t *self, int bset );
|
|
void G_CheckVictoryScript(gentity_t *self)
|
|
{
|
|
if ( self->behaviorSet[BSET_VICTORY] )
|
|
{
|
|
G_ActivateBehavior( self, BSET_VICTORY );
|
|
}
|
|
else
|
|
{
|
|
G_AddVoiceEvent( self, Q_irand(EV_VICTORY1, EV_VICTORY3), 2000 );
|
|
}
|
|
}
|
|
|
|
qboolean OnSameTeam( gentity_t *ent1, gentity_t *ent2 )
|
|
{
|
|
if ( !ent1->client || !ent2->client )
|
|
{
|
|
if ( ent1->noDamageTeam )
|
|
{
|
|
if ( ent2->client && ent2->client->playerTeam == ent1->noDamageTeam )
|
|
{
|
|
return qtrue;
|
|
}
|
|
else if ( ent2->noDamageTeam == ent1->noDamageTeam )
|
|
{
|
|
if ( ent1->splashDamage && ent2->splashDamage && Q_stricmp("ambient_etherian_fliers", ent1->classname) != 0 )
|
|
{//Barrels, exploding breakables and mines will blow each other up
|
|
return qfalse;
|
|
}
|
|
else
|
|
{
|
|
return qtrue;
|
|
}
|
|
}
|
|
}
|
|
return qfalse;
|
|
}
|
|
|
|
return ( ent1->client->playerTeam == ent2->client->playerTeam );
|
|
}
|
|
|
|
/*
|
|
-------------------------
|
|
G_AlertTeam
|
|
-------------------------
|
|
*/
|
|
|
|
void G_AlertTeam( gentity_t *victim, gentity_t *attacker, float radius, float soundDist )
|
|
{
|
|
gentity_t *radiusEnts[ 128 ];
|
|
vec3_t mins, maxs;
|
|
int numEnts;
|
|
|
|
if ( attacker == NULL || attacker->client == NULL )
|
|
return;
|
|
|
|
//Setup the bbox to search in
|
|
for ( int i = 0; i < 3; i++ )
|
|
{
|
|
mins[i] = victim->currentOrigin[i] - radius;
|
|
maxs[i] = victim->currentOrigin[i] + radius;
|
|
}
|
|
|
|
//Get the number of entities in a given space
|
|
numEnts = gi.EntitiesInBox( mins, maxs, radiusEnts, 128 );
|
|
|
|
//Cull this list
|
|
for ( i = 0; i < numEnts; i++ )
|
|
{
|
|
//Validate clients
|
|
if ( radiusEnts[i]->client == NULL )
|
|
continue;
|
|
|
|
//only want NPCs
|
|
if ( radiusEnts[i]->NPC == NULL )
|
|
continue;
|
|
|
|
//Don't bother if they're ignoring enemies
|
|
if ( radiusEnts[i]->svFlags & SVF_IGNORE_ENEMIES )
|
|
continue;
|
|
|
|
//This NPC specifically flagged to ignore alerts
|
|
if ( radiusEnts[i]->NPC->aiFlags & NPCAI_IGNORE_ALERTS )
|
|
continue;
|
|
|
|
//Skip the requested avoid radiusEnts[i] if present
|
|
if ( radiusEnts[i] == victim )
|
|
continue;
|
|
|
|
//Must be on the same team
|
|
if ( radiusEnts[i]->client->playerTeam != victim->client->playerTeam )
|
|
continue;
|
|
|
|
//Must be alive
|
|
if ( radiusEnts[i]->health <= 0 )
|
|
continue;
|
|
|
|
if ( radiusEnts[i]->enemy == NULL )
|
|
{
|
|
if ( DistanceSquared( radiusEnts[i]->currentOrigin, victim->currentOrigin ) > (soundDist*soundDist) )
|
|
{
|
|
if ( InFOV( victim, radiusEnts[i], radiusEnts[i]->NPC->stats.hfov, radiusEnts[i]->NPC->stats.vfov ) && NPC_ClearLOS( radiusEnts[i], victim->currentOrigin ) )
|
|
{
|
|
G_SetEnemy( radiusEnts[i], attacker );
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
//FIXME: This can have a nasty cascading effect if setup wrong...
|
|
G_SetEnemy( radiusEnts[i], attacker );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
-------------------------
|
|
G_DeathAlert
|
|
-------------------------
|
|
*/
|
|
|
|
#define DEATH_ALERT_RADIUS 512
|
|
#define DEATH_ALERT_SOUND_RADIUS 256
|
|
|
|
void G_DeathAlert( gentity_t *victim, gentity_t *attacker )
|
|
{
|
|
G_AlertTeam( victim, attacker, DEATH_ALERT_RADIUS, DEATH_ALERT_SOUND_RADIUS );
|
|
}
|
|
|
|
/*
|
|
----------------------------------------
|
|
DeathFX
|
|
|
|
Applies appropriate special effects that occur while the entity is dying
|
|
Not to be confused with NPC_RemoveBodyEffects (NPC.cpp), which only applies effect when removing the body
|
|
----------------------------------------
|
|
*/
|
|
|
|
static void DeathFX( gentity_t *ent )
|
|
{
|
|
if ( !ent || !ent->client )
|
|
return;
|
|
|
|
switch( ent->client->playerTeam )
|
|
{
|
|
case TEAM_STASIS:
|
|
// Scale them down while they die, plus play the teleport out effect for them
|
|
ent->s.eFlags |= EF_SCALE_DOWN;
|
|
ent->client->ps.eFlags |= EF_SCALE_DOWN;
|
|
ent->fx_time = level.time;
|
|
G_AddEvent( ent, EV_STASIS_TELEPORT_OUT, 0 );
|
|
break;
|
|
|
|
case TEAM_BORG:
|
|
ent->s.eFlags |= EF_BORG_SPARKIES;
|
|
ent->client->ps.eFlags |= EF_BORG_SPARKIES;
|
|
break;
|
|
|
|
case TEAM_FORGE:
|
|
G_AddEvent( ent, EV_FORGE_FADE, 0 );
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void G_SetMissionStatusText( gentity_t *attacker, int mod )
|
|
{
|
|
if ( statusTextIndex >= 0 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( mod == MOD_FALLING )
|
|
{//fell to your death
|
|
statusTextIndex = IGT_WATCHYOURSTEP;
|
|
}
|
|
else if ( mod == MOD_SURGICAL_LASER )
|
|
{//fell to your death
|
|
statusTextIndex = IGT_JUDGEMENTMUCHDESIRED;
|
|
}
|
|
else if ( mod == MOD_CRUSH )
|
|
{//crushed
|
|
statusTextIndex = IGT_JUDGEMENTMUCHDESIRED;
|
|
}
|
|
else if ( attacker && attacker->client && attacker->client->playerTeam == TEAM_BORG )
|
|
{//assimilated
|
|
statusTextIndex = Q_irand( IGT_RESISTANCEISFUTILE, IGT_NAMEIS8OF12 );
|
|
}
|
|
else if ( attacker && Q_stricmp( "trigger_hurt", attacker->classname ) == 0 )
|
|
{//Killed by something that should have been clearly dangerous
|
|
// statusTextIndex = Q_irand( IGT_JUDGEMENTDESIRED, IGT_JUDGEMENTMUCHDESIRED );
|
|
statusTextIndex = IGT_JUDGEMENTMUCHDESIRED;
|
|
}
|
|
else if ( attacker && attacker->s.number != 0 && attacker->client && attacker->client->playerTeam == TEAM_STARFLEET )
|
|
{//killed by a teammate
|
|
statusTextIndex = IGT_INSUBORDINATION;
|
|
}
|
|
/*
|
|
else if ()
|
|
{//killed a teammate- note: handled above
|
|
if ( Q_irand( 0, 1 ) )
|
|
{
|
|
statusTextIndex = IGT_YOUCAUSEDDEATHOFTEAMMATE;
|
|
}
|
|
else
|
|
{
|
|
statusTextIndex = IGT_KILLEDANINNOCENTCREWMAN;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//This next block is not contiguous
|
|
IGT_INADEQUATE,
|
|
IGT_RESPONSETIME,
|
|
IGT_SHOOTINRANGE,
|
|
IGT_TRYAGAIN,
|
|
IGT_TRAINONHOLODECK,
|
|
IGT_WHATCOLORSHIRT,
|
|
IGT_NOTIMPRESS7OF9,
|
|
IGT_NEELIXFAREDBETTER,
|
|
IGT_THATMUSTHURT,
|
|
IGT_TUVOKDISAPPOINTED,
|
|
IGT_STARFLEETNOTIFYFAMILY,
|
|
IGT_TEAMMATESWILLMISSYOU,
|
|
IGT_LESSTHANEXEMPLARY,
|
|
IGT_SACRIFICEDFORTHEWHOLE,
|
|
IGT_NOTLIVELONGANDPROSPER,
|
|
IGT_BETTERUSEOFSIMULATIONS,
|
|
}
|
|
*/
|
|
|
|
/*
|
|
//These can be set by designers
|
|
IGT_INSUBORDINATION,
|
|
IGT_YOUCAUSEDDEATHOFTEAMMATE,
|
|
IGT_DIDNTPROTECTTECH,
|
|
IGT_DIDNTPROTECT7OF9,
|
|
IGT_NOTSTEALTHYENOUGH,
|
|
IGT_STEALTHTACTICSNECESSARY,
|
|
*/
|
|
}
|
|
|
|
void G_MakeTeamVulnerable( void )
|
|
{
|
|
int i, newhealth;
|
|
gentity_t *ent = &g_entities[0];
|
|
gentity_t *self = &g_entities[0];
|
|
if ( !self->client )
|
|
{
|
|
return;
|
|
}
|
|
|
|
for ( i = 0; i < globals.num_entities ; i++, ent++)
|
|
{
|
|
if ( !ent )
|
|
{
|
|
continue;
|
|
}
|
|
if ( !ent->inuse )
|
|
{
|
|
continue;
|
|
}
|
|
if ( !ent->client )
|
|
{
|
|
continue;
|
|
}
|
|
if ( ent->client->playerTeam != TEAM_STARFLEET )
|
|
{
|
|
continue;
|
|
}
|
|
if ( !(ent->flags&FL_UNDYING) )
|
|
{
|
|
continue;
|
|
}
|
|
if ( !ent->client->squadname )
|
|
{
|
|
continue;
|
|
}
|
|
if ( !ent->client->squadname[0] )
|
|
{
|
|
continue;
|
|
}
|
|
if ( Q_stricmp(self->client->squadname, ent->client->squadname) )
|
|
{
|
|
continue;
|
|
}
|
|
ent->flags &= ~FL_UNDYING;
|
|
newhealth = Q_irand( 5, 40 );
|
|
if ( ent->health > newhealth )
|
|
{
|
|
ent->health = newhealth;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
==================
|
|
player_die
|
|
==================
|
|
*/
|
|
void NPC_SetAnim(gentity_t *ent,int type,int anim,int priority);
|
|
void player_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath )
|
|
{
|
|
int anim;
|
|
int contents;
|
|
qboolean deathScript = qfalse;
|
|
|
|
if ( self->client->ps.pm_type == PM_DEAD )
|
|
return;
|
|
|
|
//Use any target we had
|
|
if ( meansOfDeath != MOD_KNOCKOUT )
|
|
{
|
|
G_UseTargets( self, self );
|
|
}
|
|
//HACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACK
|
|
if ( self->s.number == 0 && attacker && attacker->client && attacker->client->playerTeam == TEAM_BORG )
|
|
{//when Munro is killed by a Borg, try to use the scriptrunner that plays the assimilated mini-cinematic
|
|
G_UseTargets2( self, self, "assimilatemunro" );
|
|
}
|
|
//HACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACK
|
|
|
|
if ( attacker )
|
|
{
|
|
G_CheckVictoryScript(attacker);
|
|
}
|
|
|
|
self->enemy = attacker;
|
|
self->client->renderInfo.lookTarget = ENTITYNUM_WORLD;
|
|
|
|
self->client->ps.persistant[PERS_KILLED]++;
|
|
if ( self->client->playerTeam == TEAM_STARFLEET )
|
|
{//FIXME: just HazTeam members in formation on away missions?
|
|
//or more controlled- via deathscripts?
|
|
// Don't count player
|
|
if (( &g_entities[0] != NULL && g_entities[0].client ) && (self->s.number != 0))
|
|
{//add to the number of teammates lost
|
|
g_entities[0].client->ps.persistant[PERS_TEAMMATES_KILLED]++;
|
|
}
|
|
else // Player died, fire off scoreboard soon
|
|
{
|
|
cg.missionStatusDeadTime = level.time + 1000; // Too long?? Too short??
|
|
}
|
|
}
|
|
|
|
if ( self->s.number == 0 && attacker )
|
|
{
|
|
G_SetMissionStatusText( attacker, meansOfDeath );
|
|
//TEST: If player killed, unmark all teammates from being undying so they can buy it too
|
|
//NOTE: we want this to happen ONLY on our squad ONLY on missions... in the tutorial or on voyager levels this could be really weird.
|
|
G_MakeTeamVulnerable();
|
|
}
|
|
|
|
if ( attacker && attacker->client)
|
|
{
|
|
if ( attacker == self || OnSameTeam (self, attacker ) )
|
|
{
|
|
AddScore( attacker, -1 );
|
|
}
|
|
else
|
|
{
|
|
AddScore( attacker, 1 );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
AddScore( self, -1 );
|
|
}
|
|
|
|
// if client is in a nodrop area, don't drop anything
|
|
contents = gi.pointcontents( self->currentOrigin, -1 );
|
|
if ( self->s.number != 0 && !( contents & CONTENTS_NODROP ) && (self->s.weapon == WP_COMPRESSION_RIFLE || self->s.weapon == WP_SCAVENGER_RIFLE))
|
|
{
|
|
TossClientItems( self );
|
|
}
|
|
|
|
self->takedamage = qfalse; // no gibbing
|
|
|
|
// Sigh...borg shouldn't drop their weapon attachments when they die..
|
|
if ( self->client->playerTeam != TEAM_BORG )
|
|
{
|
|
self->s.weapon = WP_NONE;
|
|
}
|
|
|
|
self->s.powerups = 0;
|
|
//FIXME: do this on a callback? So people can't walk through long death anims?
|
|
//Maybe set on last frame? Would be cool for big blocking corpses if the never got set?
|
|
if ( self->client->playerTeam == TEAM_PARASITE )
|
|
{
|
|
self->contents = CONTENTS_NONE; // FIXME: temp fix
|
|
}
|
|
else
|
|
{
|
|
self->contents = CONTENTS_CORPSE;
|
|
self->maxs[2] = -8;
|
|
}
|
|
self->clipmask&=~CONTENTS_MONSTERCLIP;//so dead NPC can fly off ledges
|
|
self->currentAngles[0] = 0;
|
|
self->currentAngles[2] = 0;
|
|
if ( attacker )
|
|
{
|
|
LookAtKiller (self, inflictor, attacker);
|
|
}
|
|
|
|
VectorCopy( self->currentAngles, self->client->ps.viewangles );
|
|
|
|
self->s.loopSound = 0;
|
|
|
|
// remove powerups
|
|
memset( self->client->ps.powerups, 0, sizeof(self->client->ps.powerups) );
|
|
|
|
if(meansOfDeath == MOD_FALLING && self->client->playerTeam == TEAM_STARFLEET)
|
|
{
|
|
if ( self->client->ps.groundEntityNum == ENTITYNUM_NONE )
|
|
{
|
|
NPC_SetAnim(self, SETANIM_BOTH, BOTH_FALLDEATH1INAIR, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD);
|
|
//FIXME: make this an event
|
|
// G_Sound(self, G_SoundIndex("*falling1.wav")); //we never got this sound...
|
|
G_Sound(self, G_SoundIndex("*fall1.wav"));
|
|
self->client->ps.gravity *= 0.5;//Fall a bit slower
|
|
}
|
|
else
|
|
{
|
|
NPC_SetAnim(self, SETANIM_BOTH, BOTH_FALLDEATH1LAND, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD);
|
|
//FIXME: make this an event
|
|
//PM_AddEvent( EV_FALL_FAR );
|
|
G_Sound(self, G_SoundIndex("*fall1.wav"));//FIXME: CRUNCH sounding hard land?
|
|
}
|
|
}
|
|
else
|
|
{// normal death
|
|
anim = PM_PickAnim( self, BOTH_DEATH1, BOTH_DEATH7 ); //initialize to good data
|
|
|
|
if ( (self->s.legsAnim&~ANIM_TOGGLEBIT) == BOTH_RESTRAINED1 || (self->s.legsAnim&~ANIM_TOGGLEBIT) == BOTH_RESTRAINED1POINT )
|
|
{//super special case, floating in air lying down
|
|
anim = BOTH_RESTRAINED1;
|
|
}
|
|
else if ( PM_InOnGroundAnim( self ) && PM_HasAnimation( self, BOTH_LYINGDEATH1 ) )
|
|
{//on ground, need different death anim
|
|
anim = BOTH_LYINGDEATH1;
|
|
}
|
|
else if(self->client->ps.pm_time > 0 && self->client->ps.pm_flags & PMF_TIME_KNOCKBACK && self->client->ps.velocity[2] > 0)
|
|
{
|
|
float thrown, dot;
|
|
vec3_t throwdir, forward;
|
|
|
|
AngleVectors(self->currentAngles, forward, NULL, NULL);
|
|
thrown = VectorNormalize2(self->client->ps.velocity, throwdir);
|
|
dot = DotProduct(forward, throwdir);
|
|
if(thrown > 100) {
|
|
if(dot > 0.3 && PM_HasAnimation( self, BOTH_DEATHFORWARD1 )) {
|
|
self->client->ps.gravity *= 0.8;
|
|
self->client->ps.friction = 0;
|
|
if ( PM_HasAnimation( self, BOTH_DEATHFORWARD2) ) {
|
|
anim = Q_irand(BOTH_DEATHFORWARD1, BOTH_DEATHFORWARD2);//okay, i assume he has 2 since he has 1
|
|
} else {
|
|
anim = BOTH_DEATHFORWARD1;
|
|
}
|
|
}
|
|
else if(dot < -0.3 && PM_HasAnimation( self, BOTH_DEATHBACKWARD1 )) {
|
|
self->client->ps.gravity *= 0.8;
|
|
self->client->ps.friction = 0;
|
|
if ( PM_HasAnimation( self, BOTH_DEATHBACKWARD2) ) {
|
|
anim = Q_irand(BOTH_DEATHBACKWARD1, BOTH_DEATHBACKWARD2);
|
|
} else {
|
|
anim = BOTH_DEATHBACKWARD1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( meansOfDeath == MOD_KNOCKOUT )
|
|
{
|
|
//FIXME: knock-out sound, and don't remove me
|
|
G_AddEvent( self, EV_JUMP, 0 );
|
|
G_UseTargets2( self, self, self->target2 );
|
|
G_AlertTeam( self, attacker, 512, 32 );
|
|
if ( self->NPC )
|
|
{//stick around for a while
|
|
self->NPC->timeOfDeath = level.time + 10000;
|
|
}
|
|
}
|
|
else if ( meansOfDeath == MOD_SNIPER )
|
|
{
|
|
gentity_t *tent;
|
|
vec3_t spot;
|
|
|
|
if ( self->client->playerTeam != TEAM_BOTS && self->client->playerTeam != TEAM_STASIS )
|
|
{
|
|
VectorCopy( self->currentOrigin, spot );
|
|
|
|
if ( self->client->playerTeam != TEAM_PARASITE )
|
|
spot[2] += 24;
|
|
|
|
tent = G_TempEntity( spot, EV_DISINTEGRATION );
|
|
tent->s.eventParm = PW_REGEN;
|
|
tent->owner = self;
|
|
|
|
G_AlertTeam( self, attacker, 512, 88 );
|
|
|
|
if ( self->NPC )
|
|
{//need to pad deathtime some to stick around long enough for death effect to play
|
|
|
|
self->NPC->timeOfDeath = level.time + 2000;
|
|
}
|
|
}
|
|
}
|
|
else if ( meansOfDeath == MOD_PHASER_ALT )
|
|
{
|
|
gentity_t *tent;
|
|
|
|
if ( self->client->playerTeam != TEAM_BOTS && self->client->playerTeam != TEAM_STASIS )
|
|
{
|
|
tent = G_TempEntity( self->currentOrigin, EV_DISINTEGRATION );
|
|
tent->s.eventParm = PW_DISINT_3;
|
|
tent->owner = self;
|
|
|
|
G_AlertTeam( self, attacker, 512, 88 );
|
|
|
|
if ( self->NPC )
|
|
{//need to pad deathtime some to stick around long enough for death effect to play
|
|
|
|
self->NPC->timeOfDeath = level.time + 2000;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( meansOfDeath == MOD_DREADNOUGHT )
|
|
{
|
|
gentity_t *tent;
|
|
|
|
tent = G_TempEntity( self->currentOrigin, EV_DISINTEGRATION );
|
|
tent->s.eventParm = PW_DISINT_1;
|
|
tent->owner = self;
|
|
}
|
|
else if ( meansOfDeath == MOD_QUANTUM )
|
|
{
|
|
gentity_t *tent;
|
|
|
|
tent = G_TempEntity( self->currentOrigin, EV_DISINTEGRATION );
|
|
tent->s.eventParm = PW_DISINT_2;
|
|
tent->owner = self;
|
|
}
|
|
|
|
G_AddEvent( self, Q_irand(EV_DEATH1, EV_DEATH3), self->health );
|
|
G_DeathAlert( self, attacker );
|
|
}
|
|
|
|
NPC_SetAnim(self, SETANIM_BOTH, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD);
|
|
}
|
|
|
|
// don't allow player to respawn for a few seconds
|
|
self->client->respawnTime = level.time + 3000;//self->client->ps.legsAnimTimer;
|
|
|
|
//lock it to this anim forever
|
|
if ( self->client )
|
|
{
|
|
PM_SetLegsAnimTimer( self, &self->client->ps.legsAnimTimer, -1 );
|
|
PM_SetTorsoAnimTimer( self, &self->client->ps.torsoAnimTimer, -1 );
|
|
}
|
|
|
|
//Flying creatures should drop when killed
|
|
//FIXME: This may screw up certain things that expect to float even while dead <?>
|
|
self->svFlags &= ~SVF_CUSTOM_GRAVITY;
|
|
|
|
self->client->ps.pm_type = PM_DEAD;
|
|
//need to update STAT_HEALTH here because ClientThink_real for self may happen before STAT_HEALTH is updated from self->health and pmove will stomp death anim with a move anim
|
|
self->client->ps.stats[STAT_HEALTH] = self->health;
|
|
|
|
if ( VALIDSTRING( self->behaviorSet[BSET_DEATH] ) )
|
|
{
|
|
G_ActivateBehavior( self, BSET_DEATH );
|
|
deathScript = qtrue;
|
|
}
|
|
|
|
if ( self->NPC && (self->NPC->scriptFlags&SCF_FFDEATH) && VALIDSTRING( self->behaviorSet[BSET_FFDEATH] ) )
|
|
{//FIXME: should running this preclude running the normal deathscript?
|
|
G_ActivateBehavior( self, BSET_FFDEATH );
|
|
deathScript = qtrue;
|
|
}
|
|
|
|
//WARNING!!! DO NOT DO THIS WHILE RUNNING A SCRIPT, ICARUS WILL CRASH!!!
|
|
if ( !deathScript )
|
|
{
|
|
//Should no longer run scripts
|
|
ICARUS_FreeEnt(self);
|
|
}
|
|
|
|
// Set pending objectives to failed
|
|
OBJ_SetPendingObjectives(self);
|
|
|
|
gi.linkentity (self);
|
|
|
|
// Start any necessary death fx for this entity
|
|
DeathFX( self );
|
|
}
|
|
|
|
|
|
/*
|
|
================
|
|
CheckArmor
|
|
================
|
|
*/
|
|
int CheckArmor (gentity_t *ent, int damage, int dflags)
|
|
{
|
|
gclient_t *client;
|
|
int save;
|
|
int count;
|
|
|
|
if (!damage)
|
|
return 0;
|
|
|
|
client = ent->client;
|
|
|
|
if (!client)
|
|
return 0;
|
|
|
|
if (dflags & DAMAGE_NO_ARMOR)
|
|
return 0;
|
|
|
|
// armor
|
|
count = client->ps.stats[STAT_ARMOR];
|
|
save = ceil( (float) damage * ARMOR_PROTECTION );
|
|
|
|
//Always round up
|
|
if (damage == 1)
|
|
{
|
|
if ( client->ps.stats[STAT_ARMOR] > 0 )
|
|
client->ps.stats[STAT_ARMOR] -= save;
|
|
|
|
return 0;
|
|
}
|
|
|
|
if (save >= count)
|
|
save = count;
|
|
|
|
if (!save)
|
|
return 0;
|
|
|
|
client->ps.stats[STAT_ARMOR] -= save;
|
|
|
|
return save;
|
|
}
|
|
|
|
void G_ApplyKnockback( gentity_t *targ, vec3_t newDir, float knockback )
|
|
{
|
|
vec3_t kvel;
|
|
float mass;
|
|
|
|
if (targ->physicsBounce > 0) //overide the mass
|
|
mass = targ->physicsBounce;
|
|
else
|
|
mass = 200;
|
|
|
|
VectorScale (newDir, g_knockback->value * (float)knockback / mass * 0.8, kvel);
|
|
kvel[2] = newDir[2] * g_knockback->value * (float)knockback / mass * 1.5;
|
|
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;
|
|
}
|
|
}
|
|
|
|
void ReCalcDamageForLocation (gentity_t *target, vec3_t pdir, vec3_t ppoint, int *damage)
|
|
{
|
|
/*
|
|
vec3_t dir, point, point_dir, tempvec;
|
|
vec3_t forward, right, up;
|
|
vec3_t tangles, tcenter;
|
|
float tradius, hdist;
|
|
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->currentAngles[YAW], 0);
|
|
}
|
|
|
|
AngleVectors(tangles, forward, right, up);
|
|
|
|
//get center of target
|
|
VectorAdd(target->absmin, target->absmax, tcenter);
|
|
VectorScale(tcenter, 0.5, tcenter);
|
|
|
|
//get radius width of target
|
|
tradius = (fabs(target->maxs[0]) + fabs(target->maxs[1]) + fabs(target->mins[0]) + fabs(target->mins[1]))/4;
|
|
|
|
//get impact point
|
|
if(ppoint && !VectorCompare(ppoint, vec3_origin))
|
|
{
|
|
VectorCopy(ppoint, point);
|
|
}
|
|
else
|
|
{
|
|
return;
|
|
}
|
|
|
|
//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>.666)
|
|
Vertical = 4;
|
|
else if(udot>.333)
|
|
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
|
|
*damage = floor(*damage * 0.10);
|
|
}
|
|
else if(HitLoc <= 50)
|
|
{//legs
|
|
*damage = floor(*damage * 0.30);
|
|
}
|
|
else if(HitLoc == 56||HitLoc == 60||HitLoc == 61||HitLoc == 65||HitLoc == 66||HitLoc == 70||
|
|
HitLoc == 83||HitLoc == 87||HitLoc == 88||HitLoc == 92||HitLoc == 93||HitLoc == 97)
|
|
{//arms
|
|
*damage = floor(*damage * 0.20);
|
|
}
|
|
else if((HitLoc >= 107 && HitLoc <= 109)||
|
|
(HitLoc >= 112 && HitLoc <= 114)||
|
|
(HitLoc >= 117 && HitLoc <= 119))
|
|
{//head
|
|
*damage *= 2;
|
|
}
|
|
*/
|
|
}
|
|
|
|
/*
|
|
void G_TeamRetaliation ( gentity_t *targ, gentity_t *attacker, qboolean stopIcarus )
|
|
|
|
1: Get entire team mad at player
|
|
2: Stop team from running scripts
|
|
3: If on a Voyager map (?!), load the brig after a certain delay- Else, just bring up "mission failed"
|
|
FIXME: need to do this because you could potentially kill everyone.
|
|
can't rely on everyone being undying
|
|
*/
|
|
void G_TeamRetaliation ( gentity_t *targ, gentity_t *attacker, qboolean stopIcarus )
|
|
{
|
|
gentity_t *teammate;
|
|
int teamcount = 0;
|
|
|
|
if ( !attacker->client )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( Q_strncmp( "_holo", level.mapname, 5 ) == 0 )
|
|
{//only do this if not on a holodeck
|
|
return;
|
|
}
|
|
|
|
//attacker->client->playerTeam = TEAM_FREE;
|
|
attacker->flags &= ~FL_GODMODE;
|
|
attacker->flags &= ~FL_UNDYING;
|
|
if ( !stop_icarus )
|
|
{//don't restart ICARUS if it's already been stopped...?
|
|
stop_icarus = stopIcarus;
|
|
}
|
|
//find each member of the team and get them angry at you
|
|
for ( int i = 1; i < globals.num_entities; i++ )
|
|
{
|
|
teammate = &g_entities[i];
|
|
if ( !teammate )
|
|
{
|
|
continue;
|
|
}
|
|
if ( !teammate->inuse )
|
|
{
|
|
continue;
|
|
}
|
|
if ( !teammate->NPC )
|
|
{
|
|
continue;
|
|
}
|
|
if ( !teammate->client )
|
|
{
|
|
continue;
|
|
}
|
|
if ( teammate->client->ps.stats[STAT_HEALTH] <= 0 )
|
|
{
|
|
continue;
|
|
}
|
|
if ( teammate->s.eFlags & EF_NODRAW )
|
|
{
|
|
continue;
|
|
}
|
|
if ( teammate->client->playerTeam != TEAM_STARFLEET )
|
|
{
|
|
continue;
|
|
}
|
|
if ( teammate == targ || !Q_irand(0, 1) )
|
|
{
|
|
G_AddVoiceEvent( teammate, Q_irand( EV_FF_3A, EV_FF_3C ), 2000 );
|
|
}
|
|
teamcount++;//count them up
|
|
//mad at me forever
|
|
G_ClearEnemy( teammate );
|
|
G_SetEnemy( teammate, attacker );
|
|
teammate->svFlags |= SVF_LOCKEDENEMY;
|
|
teammate->NPC->behaviorState = BS_RUN_AND_SHOOT;
|
|
teammate->client->renderInfo.lookTarget = 0;
|
|
if ( teammate->client->race != RACE_HOLOGRAM )
|
|
{//doctor unkillable
|
|
if ( !teammate->contents )
|
|
{
|
|
teammate->contents = CONTENTS_BODY;
|
|
}
|
|
teammate->NPC->ignorePain = qfalse;
|
|
teammate->flags &= ~FL_UNDYING;
|
|
teammate->flags &= ~FL_GODMODE;
|
|
}
|
|
|
|
teammate->svFlags &= ~SVF_IGNORE_ENEMIES;
|
|
teammate->flags &= ~FL_DONT_SHOOT;
|
|
teammate->behaviorSet[BSET_DEATH] = NULL;
|
|
teammate->behaviorSet[BSET_FFDEATH] = NULL;
|
|
teammate->behaviorSet[BSET_FFIRE] = NULL;
|
|
teammate->behaviorSet[BSET_PAIN] = NULL;
|
|
|
|
//give 'em a weapon
|
|
if ( teammate->client->ps.weapon == WP_NONE || teammate->client->ps.weapon == WP_TRICORDER )
|
|
{
|
|
int wp;
|
|
|
|
if ( !Q_irand(0, 1) )
|
|
{
|
|
wp = WP_COMPRESSION_RIFLE;
|
|
}
|
|
else
|
|
{
|
|
wp = WP_PHASER;
|
|
}
|
|
teammate->client->ps.stats[STAT_WEAPONS] = ( 1 << wp );
|
|
teammate->client->ps.ammo[weaponData[wp].ammoIndex] = 999;
|
|
RegisterItem( FindItemForWeapon( (weapon_t) wp) ); //make sure the weapon is cached in case this runs at startup
|
|
ChangeWeapon( teammate, wp );
|
|
teammate->client->ps.weapon = wp;
|
|
teammate->client->ps.weaponstate = WEAPON_READY;
|
|
}
|
|
}
|
|
|
|
if ( !teamcount )
|
|
{//killed all teammates
|
|
//end map now
|
|
ffireLevel = FFIRE_LEVEL_RETALIATION;
|
|
killPlayerTimer = level.time + 500;
|
|
if ( Q_irand( 0, 1 ) )
|
|
{
|
|
statusTextIndex = IGT_YOUCAUSEDDEATHOFTEAMMATE;
|
|
}
|
|
else
|
|
{
|
|
statusTextIndex = IGT_KILLEDANINNOCENTCREWMAN;
|
|
}
|
|
}
|
|
}
|
|
|
|
void G_FriendlyFireReaction( gentity_t *targ, gentity_t *attacker, qboolean inCombat )
|
|
{//NOTE: you only get in here if a starfleet teammember shot another starfleet teammember
|
|
int ffireEvent = 0;
|
|
|
|
if ( attacker != &g_entities[0] || targ == &g_entities[0] )
|
|
{//attacker isn't player, or target is the player ignore it
|
|
return;
|
|
}
|
|
|
|
if ( !targ->NPC )
|
|
{//Only bother with friendly fire checks if the player shot an NPC
|
|
return;
|
|
}
|
|
|
|
if ( targ->health <= 0 )
|
|
{//player killed an NPC teammate, they should turn on player
|
|
if ( VALIDSTRING( targ->behaviorSet[BSET_FFDEATH] ) )
|
|
{//they're going to run a special friendly fire death script
|
|
//Will make them run the ffdeath script
|
|
targ->NPC->scriptFlags |= SCF_FFDEATH;
|
|
}
|
|
else if ( targ->enemy != attacker )
|
|
{//they were killed by the player without provocation - this way
|
|
if ( Q_strncmp( "_holo", level.mapname, 5 ) != 0 )
|
|
{//only do this if not on a holodeck
|
|
//after a certain amount of time, end map anyway
|
|
ffireLevel = FFIRE_LEVEL_RETALIATION;
|
|
if ( !killPlayerTimer )
|
|
{//start the kill player timer if we haven't already
|
|
killPlayerTimer = level.time + 10 * 1000;
|
|
if ( Q_irand( 0, 1 ) )
|
|
{
|
|
statusTextIndex = IGT_YOUCAUSEDDEATHOFTEAMMATE;
|
|
}
|
|
else
|
|
{
|
|
statusTextIndex = IGT_KILLEDANINNOCENTCREWMAN;
|
|
}
|
|
}
|
|
//those who are killed after the team turns on the player
|
|
//do not repeat this behavior... and those that may have
|
|
//been spawned by ffdeathscripts do not do this either (like security)
|
|
G_TeamRetaliation( targ, attacker, qtrue );
|
|
}
|
|
}
|
|
else if ( !teamCount[TEAM_STARFLEET] )
|
|
{//killed everyone on your team!
|
|
if ( Q_strncmp( "_holo", level.mapname, 5 ) != 0 )
|
|
{//only do this if not on a holodeck
|
|
//end map now
|
|
ffireLevel = FFIRE_LEVEL_RETALIATION;
|
|
killPlayerTimer = level.time + 500;
|
|
if ( Q_irand( 0, 1 ) )
|
|
{
|
|
statusTextIndex = IGT_YOUCAUSEDDEATHOFTEAMMATE;
|
|
}
|
|
else
|
|
{
|
|
statusTextIndex = IGT_KILLEDANINNOCENTCREWMAN;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{//Killed a crewmate who was attacking you - start a 30 second timer to end the level one way or another
|
|
gentity_t *brigent = NULL;
|
|
while( (brigent = G_Find(brigent, FOFS(classname), "target_level_change" )) != NULL )
|
|
{
|
|
if(!brigent->message)
|
|
G_Error( "target_level_change without level map name");
|
|
if ( Q_stricmp("_brig", brigent->message) == 0 )
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( brigent )
|
|
{//there is a brig changelevel entity on the map, use it in 30 seconds.
|
|
if ( !loadBrigTimer )
|
|
{
|
|
loadBrigTimer = level.time + 30000;
|
|
}
|
|
}
|
|
else if ( !killPlayerTimer )
|
|
{//start the kill player timer if we haven't already
|
|
if ( Q_strncmp( "_holo", level.mapname, 5 ) != 0 )
|
|
{//only do this if not on a holodeck
|
|
killPlayerTimer = level.time + 30000;
|
|
if ( Q_irand( 0, 1 ) )
|
|
{
|
|
statusTextIndex = IGT_YOUCAUSEDDEATHOFTEAMMATE;
|
|
}
|
|
else
|
|
{
|
|
statusTextIndex = IGT_KILLEDANINNOCENTCREWMAN;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if ( inCombat )
|
|
{//we're in combat
|
|
//okay, say something, but doesn't count toward ffire counter
|
|
if ( TIMER_Done( targ, "ffireDebounce" ) )
|
|
{
|
|
TIMER_Set( targ, "ffireDebounce", 1000 );
|
|
G_AddVoiceEvent( targ, Q_irand( EV_FF_1A, EV_FF_2C ), 2000 );
|
|
}
|
|
|
|
return;
|
|
}
|
|
//else we are not in combat...
|
|
|
|
//shot a teammate, but he's still alive
|
|
if ( targ->enemy == attacker )
|
|
{//Target is already mad at player, just yell at him for continuing to shoot
|
|
if ( TIMER_Done( targ, "ffireDebounce" ) )
|
|
{
|
|
TIMER_Set( targ, "ffireDebounce", 1000 );
|
|
G_AddVoiceEvent( targ, Q_irand( EV_FF_3A, EV_FF_3C ), 2000 );
|
|
}
|
|
return;
|
|
}
|
|
|
|
//okay, he's not alreayd mad at player, inc the ffire counter
|
|
|
|
//2) Only inc the global ffire counter every 1 sec max
|
|
if ( level.time - ffireForgivenessTimer >= 1000 )
|
|
{
|
|
//NOTE: counter is reset evey 2 minutes that passes without ffire and during combat
|
|
ffireLevel++;
|
|
ffireForgivenessTimer = level.time;
|
|
//3) If the counter >= 10, get team mad
|
|
if ( ffireLevel >= FFIRE_LEVEL_RETALIATION )
|
|
{//we are angry enough to attack
|
|
//if have a ffire script, run that instead
|
|
if ( VALIDSTRING( targ->behaviorSet[BSET_FFIRE] ) )
|
|
{//Got two fair warnings, third strike you're out
|
|
G_ActivateBehavior( targ, BSET_FFIRE );
|
|
}
|
|
else if ( attacker->client->playerTeam == TEAM_STARFLEET )//not in disguise, but this is guaranteed
|
|
{//Okay, we can't just stand here and do nothing...
|
|
//NOTE: this will also make the targ yell at the attacker again
|
|
G_TeamRetaliation( targ, attacker, qtrue );
|
|
}
|
|
}
|
|
else
|
|
{//they're getting pissed
|
|
//1) Only reply and every 1 sec max
|
|
if ( TIMER_Done( targ, "ffireDebounce" ) )
|
|
{
|
|
TIMER_Set( targ, "ffireDebounce", 1000 );
|
|
//reply
|
|
if ( ffireLevel < 3 )
|
|
{
|
|
ffireEvent = Q_irand( EV_FF_1A, EV_FF_1C );
|
|
}
|
|
else if ( ffireLevel < 6 )
|
|
{
|
|
ffireEvent = Q_irand( EV_FF_2A, EV_FF_2C );
|
|
}
|
|
else //must be 9 or higher
|
|
{
|
|
ffireEvent = Q_irand( EV_FF_3A, EV_FF_3C );
|
|
}
|
|
if ( (targ->client->ps.legsAnim&~ANIM_TOGGLEBIT) != BOTH_RESTRAINED1 && (targ->client->ps.legsAnim&~ANIM_TOGGLEBIT) != BOTH_RESTRAINED1POINT )
|
|
{
|
|
NPC_TempLookTarget( targ, attacker->s.number, 2000, 4000 );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( ffireEvent )
|
|
{//say whatever we were going to say
|
|
G_AddVoiceEvent( targ, ffireEvent, 2000 );
|
|
}
|
|
}
|
|
/*
|
|
============
|
|
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_NO_HIT_LOC Damage not based on hit location
|
|
============
|
|
*/
|
|
|
|
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;
|
|
vec3_t newDir;
|
|
|
|
if (!targ->takedamage) {
|
|
return;
|
|
}
|
|
|
|
if ( targ->health <= 0 ) {
|
|
return;
|
|
}
|
|
|
|
if ( !inflictor ) {
|
|
inflictor = &g_entities[ENTITYNUM_WORLD];
|
|
}
|
|
if ( !attacker ) {
|
|
attacker = &g_entities[ENTITYNUM_WORLD];
|
|
}
|
|
|
|
if ( attacker->s.number != 0 && damage >= 2 && targ->s.number != 0 && attacker->client && attacker->client->playerTeam == TEAM_STARFLEET )
|
|
{//player-helpers do only half damage to enemies
|
|
damage = ceil((float)damage/2.0f);
|
|
}
|
|
|
|
if ( targ->client )
|
|
{
|
|
if ( ( targ->client->race == RACE_HIROGEN ) && ( targ->s.powerups & ( 1 << PW_HIROGEN_SHIELD ) ) )
|
|
{
|
|
//Shield is up, take no damage
|
|
if ( targ->s.powerups & ( 1 << PW_HIROGEN_SHIELD ) )
|
|
return;
|
|
}
|
|
}
|
|
|
|
client = targ->client;
|
|
|
|
if ( client ) {
|
|
if ( client->noclip ) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
if ( dflags&DAMAGE_NO_DAMAGE )
|
|
{
|
|
damage = 0;
|
|
}
|
|
|
|
if ( dir == NULL )
|
|
{
|
|
dflags |= DAMAGE_NO_KNOCKBACK;
|
|
}
|
|
else
|
|
{
|
|
VectorNormalize2( dir, newDir );
|
|
}
|
|
|
|
if ( targ->s.number != 0 )
|
|
{//not the player
|
|
if ( (targ->flags&FL_GODMODE) || (targ->flags&FL_UNDYING) )
|
|
{//have god or undying on, so ignore no protection flag
|
|
dflags &= ~DAMAGE_NO_PROTECTION;
|
|
}
|
|
}
|
|
|
|
if(PM_InOnGroundAnim(targ))
|
|
{
|
|
dflags |= DAMAGE_NO_KNOCKBACK;
|
|
}
|
|
|
|
knockback = damage;
|
|
|
|
//Attempt to apply extra knockback
|
|
if ( dflags & DAMAGE_EXTRA_KNOCKBACK )
|
|
{
|
|
knockback *= 2;
|
|
}
|
|
|
|
if ( knockback > 200 ) {
|
|
knockback = 200;
|
|
}
|
|
|
|
if ( targ->flags & FL_NO_KNOCKBACK )
|
|
{
|
|
knockback = 0;
|
|
}
|
|
else if ( targ->client && attacker->client && targ->client->playerTeam == attacker->client->playerTeam )
|
|
{
|
|
knockback = 0;
|
|
}
|
|
else if ( dflags & DAMAGE_NO_KNOCKBACK )
|
|
{
|
|
knockback = 0;
|
|
}
|
|
else if ( mod == MOD_STASIS )
|
|
{//HACK, need a dflags field on ents
|
|
knockback = 0;
|
|
}
|
|
|
|
// figure momentum add, even if the damage won't be taken
|
|
if ( knockback && targ->client && !(dflags&DAMAGE_DEATH_KNOCKBACK) )
|
|
{
|
|
G_ApplyKnockback( targ, newDir, knockback );
|
|
}
|
|
|
|
// check for godmode, completely getting out of the damage
|
|
if ( targ->flags & FL_GODMODE && !(dflags&DAMAGE_NO_PROTECTION) )
|
|
{
|
|
if ( targ->client && attacker->client && targ->client->playerTeam == attacker->client->playerTeam )
|
|
{//complain, but don't turn on them
|
|
G_FriendlyFireReaction( targ, attacker, qtrue );
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Check for team damage
|
|
if ( targ != attacker && !(dflags&DAMAGE_IGNORE_TEAM) && OnSameTeam (targ, attacker) )
|
|
{//on same team
|
|
if ( !targ->client )
|
|
{//a non-player object should never take damage from an ent on the same team
|
|
return;
|
|
}
|
|
|
|
if ( attacker->client && attacker->client->playerTeam == targ->noDamageTeam )
|
|
{//NPC or player shot an object on his own team
|
|
return;
|
|
}
|
|
|
|
if ( attacker->s.number != 0 && targ->s.number != 0 &&//player not involved in any way in this exchange
|
|
attacker->client && targ->client &&//two NPCs
|
|
attacker->client->playerTeam == targ->client->playerTeam ) //on the same team
|
|
{//NPCs on same team don't hurt each other
|
|
return;
|
|
}
|
|
|
|
if ( targ->s.number == 0 &&//player was hit
|
|
attacker->client && targ->client &&//by an NPC
|
|
attacker->client->playerTeam == TEAM_STARFLEET ) //on the same team
|
|
{
|
|
if ( attacker->enemy != targ )//by accident
|
|
{//do no damage, no armor loss, no reaction, run no scripts
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// add to the attacker's hit counter
|
|
if ( attacker->client && targ != attacker && targ->health > 0 ) {
|
|
if ( OnSameTeam( targ, attacker ) ) {
|
|
attacker->client->ps.persistant[PERS_HITS] -= damage;
|
|
} else {
|
|
attacker->client->ps.persistant[PERS_HITS] += damage;
|
|
}
|
|
}
|
|
|
|
take = damage;
|
|
save = 0;
|
|
|
|
//FIXME: Do not use this method of difficulty changing
|
|
// Scale the amount of damage given to the player based on the skill setting
|
|
/*
|
|
if ( targ->s.number == 0 && targ != attacker )
|
|
{
|
|
take *= ( g_spskill->integer + 1) * 0.75;
|
|
}
|
|
|
|
if ( take < 1 ) {
|
|
take = 1;
|
|
}
|
|
*/
|
|
|
|
//don't lose armor if on same team
|
|
// save some from armor
|
|
asave = CheckArmor (targ, take, dflags);
|
|
take -= asave;
|
|
|
|
if ( ( targ->client != NULL ) && ( targ->client->playerTeam == TEAM_HIROGEN ) )
|
|
{
|
|
if ( ( Q_stricmp( targ->NPC_type, "hirogenalpha" ) == 0 ) && !( targ->s.powerups & ( 1 << PW_HIROGEN_SHIELD ) ) )
|
|
{
|
|
take = 1;
|
|
targ->NPC->localState++;
|
|
targ->NPC->squadState = 0;
|
|
}
|
|
}
|
|
|
|
if ( g_debugDamage->integer ) {
|
|
gi.Printf( "[%d]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 ) {
|
|
client->ps.persistant[PERS_ATTACKER] = attacker->s.number; //attack can be the world ent
|
|
client->damage_armor += asave;
|
|
client->damage_blood += take;
|
|
client->damage_knockback += knockback;
|
|
if ( dir ) { //can't check newdir since it's local, newdir is dir normalized
|
|
VectorCopy ( newDir, client->damage_from );
|
|
client->damage_fromWorld = qfalse;
|
|
} else {
|
|
VectorCopy ( targ->currentOrigin, client->damage_from );
|
|
client->damage_fromWorld = qtrue;
|
|
}
|
|
}
|
|
|
|
// do the damage
|
|
if ( take || (dflags&DAMAGE_NO_DAMAGE) )
|
|
{
|
|
if ( !targ->client || !attacker->client )
|
|
{
|
|
targ->health = targ->health - take;
|
|
if ( (targ->flags&FL_UNDYING) && !(dflags&DAMAGE_NO_PROTECTION) )
|
|
{
|
|
if(targ->health < 1)
|
|
{
|
|
if ( VALIDSTRING( targ->behaviorSet[BSET_DEATH] ) )
|
|
{
|
|
G_ActivateBehavior( targ, BSET_DEATH );
|
|
}
|
|
|
|
targ->health = 1;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{//two clients
|
|
team_t targTeam = TEAM_FREE;
|
|
team_t attackerTeam = TEAM_FREE;
|
|
|
|
if ( targ->client ) {
|
|
targTeam = targ->client->playerTeam;
|
|
}
|
|
else {
|
|
targTeam = targ->noDamageTeam;
|
|
}
|
|
if ( targTeam == TEAM_DISGUISE ) {
|
|
targTeam = TEAM_STARFLEET;
|
|
}
|
|
if ( attacker->client ) {
|
|
attackerTeam = attacker->client->playerTeam;
|
|
}
|
|
else {
|
|
attackerTeam = attacker->noDamageTeam;
|
|
}
|
|
if ( attackerTeam == TEAM_DISGUISE ) {
|
|
attackerTeam = TEAM_STARFLEET;
|
|
}
|
|
|
|
if ( targTeam != attackerTeam )
|
|
{//on the same team
|
|
targ->health = targ->health - take;
|
|
|
|
//MCG - Falling should never kill player- only if a trigger_hurt does so.
|
|
if ( mod == MOD_FALLING && targ->s.number == 0 && targ->health < 1 )
|
|
{
|
|
targ->health = 1;
|
|
}
|
|
|
|
if ( (targ->flags&FL_UNDYING) && !(dflags&DAMAGE_NO_PROTECTION) )
|
|
{
|
|
if ( targ->health < 1 )
|
|
{
|
|
if ( VALIDSTRING( targ->behaviorSet[BSET_DEATH] ) )
|
|
{
|
|
G_ActivateBehavior( targ, BSET_DEATH );
|
|
}
|
|
|
|
//Turn off Hirogen boss' shield upon "death"
|
|
if ( ( targ->client != NULL ) && ( targ->client->playerTeam == TEAM_HIROGEN ) )
|
|
{
|
|
if ( Q_stricmp( targ->NPC_type, "hirogenalpha" ) == 0 )
|
|
{
|
|
targ->s.powerups &= ~( 1 << PW_HIROGEN_SHIELD );
|
|
targ->client->ps.powerups[ PW_HIROGEN_SHIELD ] = -1;
|
|
targ->NPC->ignorePain = qtrue;
|
|
}
|
|
}
|
|
|
|
targ->health = 1;
|
|
}
|
|
}
|
|
else if ( targ->health < 1 && attacker->client )
|
|
{
|
|
// The player or NPC just killed an enemy so increment the kills counter
|
|
attacker->client->ps.persistant[PERS_ENEMIES_KILLED]++;
|
|
}
|
|
}
|
|
else if ( targTeam == TEAM_STARFLEET )
|
|
{//on the same team, and target is a starfleet officer
|
|
qboolean inCombat = qfalse;
|
|
qboolean takeDamage = qtrue;
|
|
qboolean yellAtAttacker = qtrue;
|
|
|
|
if ( level.time - teamLastEnemyTime[TEAM_STARFLEET] < 10000 )
|
|
{//Team is in combat
|
|
inCombat = qtrue;
|
|
}
|
|
|
|
//1) player doesn't take damage from teammates unless they're angry at him
|
|
if ( targ->s.number == 0 )
|
|
{//the player
|
|
if ( attacker->enemy != targ && attacker != targ )
|
|
{//an NPC shot the player by accident
|
|
takeDamage = qfalse;
|
|
}
|
|
}
|
|
//2) NPCs don't take any damage from player during combat
|
|
else
|
|
{//an NPC
|
|
if ( (inCombat || (dflags & DAMAGE_RADIUS)) && !(dflags&DAMAGE_IGNORE_TEAM) )
|
|
{//An NPC got hit by player and this is during combat or it was slash damage
|
|
//NOTE: though it's not realistic to have teammates not take splash damage,
|
|
// even when not in combat, it feels really bad to have them able to
|
|
// actually be killed by the player's splash damage
|
|
takeDamage = qfalse;
|
|
}
|
|
|
|
if ( inCombat && (dflags & DAMAGE_RADIUS) )
|
|
{//you're fighting and it's just radius damage, so don't even mention it
|
|
yellAtAttacker = qfalse;
|
|
}
|
|
}
|
|
|
|
if ( takeDamage )
|
|
{
|
|
targ->health = targ->health - take;
|
|
}
|
|
|
|
if ( (targ->flags&FL_UNDYING) && !(dflags&DAMAGE_NO_PROTECTION) && attacker->s.number != 0 )
|
|
{//guy is marked undying and we're not the player or we're in combat
|
|
if ( targ->health < 1 )
|
|
{
|
|
if ( VALIDSTRING( targ->behaviorSet[BSET_DEATH] ) )
|
|
{
|
|
G_ActivateBehavior( targ, BSET_DEATH );
|
|
}
|
|
|
|
targ->health = 1;
|
|
}
|
|
}
|
|
|
|
if ( yellAtAttacker )
|
|
{
|
|
G_FriendlyFireReaction( targ, attacker, inCombat );
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( targ->client ) {
|
|
targ->client->ps.stats[STAT_HEALTH] = targ->health;
|
|
g_lastClientDamaged = targ;
|
|
}
|
|
|
|
if ( targ->health <= 0 )
|
|
{
|
|
if ( knockback && targ->client && (dflags&DAMAGE_DEATH_KNOCKBACK) )
|
|
{//only do knockback on death
|
|
G_ApplyKnockback( targ, newDir, knockback );
|
|
}
|
|
|
|
if ( client )
|
|
targ->flags |= FL_NO_KNOCKBACK;
|
|
|
|
if (targ->health < -999)
|
|
targ->health = -999;
|
|
|
|
targ->enemy = attacker;
|
|
targ->infoString = NULL;//Dead things have no ID
|
|
GEntity_DieFunc (targ, inflictor, attacker, take, mod);
|
|
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
GEntity_PainFunc(targ, attacker, take);
|
|
if ( targ->s.number == 0 )
|
|
{//player run painscript
|
|
if ( VALIDSTRING( targ->behaviorSet[BSET_PAIN] ) )
|
|
{
|
|
G_ActivateBehavior( targ, BSET_PAIN );
|
|
}
|
|
if ( targ->health <= 25 )
|
|
{
|
|
if ( VALIDSTRING( targ->behaviorSet[BSET_FLEE] ) )
|
|
{
|
|
G_ActivateBehavior( targ, BSET_FLEE );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/*
|
|
============
|
|
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->absmin, targ->absmax, midpoint);
|
|
VectorScale (midpoint, 0.5, midpoint);
|
|
|
|
VectorCopy (midpoint, dest);
|
|
gi.trace ( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID);
|
|
if (tr.fraction == 1.0)
|
|
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;
|
|
gi.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;
|
|
gi.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;
|
|
gi.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;
|
|
gi.trace ( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID);
|
|
if (tr.fraction == 1.0)
|
|
return qtrue;
|
|
|
|
|
|
return qfalse;
|
|
}
|
|
|
|
|
|
/*
|
|
============
|
|
G_RadiusDamage
|
|
============
|
|
*/
|
|
void G_RadiusDamage ( vec3_t origin, gentity_t *attacker, float damage, float radius,
|
|
gentity_t *ignore, int mod) {
|
|
float points, dist;
|
|
gentity_t *ent;
|
|
gentity_t *entityList[MAX_GENTITIES];
|
|
int numListedEntities;
|
|
vec3_t mins, maxs;
|
|
vec3_t v;
|
|
vec3_t dir;
|
|
int i, e;
|
|
|
|
if ( radius < 1 ) {
|
|
radius = 1;
|
|
}
|
|
|
|
for ( i = 0 ; i < 3 ; i++ ) {
|
|
mins[i] = origin[i] - radius;
|
|
maxs[i] = origin[i] + radius;
|
|
}
|
|
|
|
numListedEntities = gi.EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
|
|
|
|
for ( e = 0 ; e < numListedEntities ; e++ ) {
|
|
ent = entityList[ e ];
|
|
|
|
if ( ent == ignore )
|
|
continue;
|
|
if ( !ent->takedamage )
|
|
continue;
|
|
if ( !ent->contents )
|
|
continue;
|
|
|
|
// find the distance from the edge of the bounding box
|
|
for ( i = 0 ; i < 3 ; i++ ) {
|
|
if ( origin[i] < ent->absmin[i] ) {
|
|
v[i] = ent->absmin[i] - origin[i];
|
|
} else if ( origin[i] > ent->absmax[i] ) {
|
|
v[i] = origin[i] - ent->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->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);
|
|
}
|
|
}
|
|
}
|