// 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); } } }