Quake-III-Arena

Quake III Arena GPL Source Release
Log | Files | Refs

g_combat.c (29735B)


      1 /*
      2 ===========================================================================
      3 Copyright (C) 1999-2005 Id Software, Inc.
      4 
      5 This file is part of Quake III Arena source code.
      6 
      7 Quake III Arena source code is free software; you can redistribute it
      8 and/or modify it under the terms of the GNU General Public License as
      9 published by the Free Software Foundation; either version 2 of the License,
     10 or (at your option) any later version.
     11 
     12 Quake III Arena source code is distributed in the hope that it will be
     13 useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
     14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     15 GNU General Public License for more details.
     16 
     17 You should have received a copy of the GNU General Public License
     18 along with Foobar; if not, write to the Free Software
     19 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
     20 ===========================================================================
     21 */
     22 //
     23 // g_combat.c
     24 
     25 #include "g_local.h"
     26 
     27 
     28 /*
     29 ============
     30 ScorePlum
     31 ============
     32 */
     33 void ScorePlum( gentity_t *ent, vec3_t origin, int score ) {
     34 	gentity_t *plum;
     35 
     36 	plum = G_TempEntity( origin, EV_SCOREPLUM );
     37 	// only send this temp entity to a single client
     38 	plum->r.svFlags |= SVF_SINGLECLIENT;
     39 	plum->r.singleClient = ent->s.number;
     40 	//
     41 	plum->s.otherEntityNum = ent->s.number;
     42 	plum->s.time = score;
     43 }
     44 
     45 /*
     46 ============
     47 AddScore
     48 
     49 Adds score to both the client and his team
     50 ============
     51 */
     52 void AddScore( gentity_t *ent, vec3_t origin, int score ) {
     53 	if ( !ent->client ) {
     54 		return;
     55 	}
     56 	// no scoring during pre-match warmup
     57 	if ( level.warmupTime ) {
     58 		return;
     59 	}
     60 	// show score plum
     61 	ScorePlum(ent, origin, score);
     62 	//
     63 	ent->client->ps.persistant[PERS_SCORE] += score;
     64 	if ( g_gametype.integer == GT_TEAM )
     65 		level.teamScores[ ent->client->ps.persistant[PERS_TEAM] ] += score;
     66 	CalculateRanks();
     67 }
     68 
     69 /*
     70 =================
     71 TossClientItems
     72 
     73 Toss the weapon and powerups for the killed player
     74 =================
     75 */
     76 void TossClientItems( gentity_t *self ) {
     77 	gitem_t		*item;
     78 	int			weapon;
     79 	float		angle;
     80 	int			i;
     81 	gentity_t	*drop;
     82 
     83 	// drop the weapon if not a gauntlet or machinegun
     84 	weapon = self->s.weapon;
     85 
     86 	// make a special check to see if they are changing to a new
     87 	// weapon that isn't the mg or gauntlet.  Without this, a client
     88 	// can pick up a weapon, be killed, and not drop the weapon because
     89 	// their weapon change hasn't completed yet and they are still holding the MG.
     90 	if ( weapon == WP_MACHINEGUN || weapon == WP_GRAPPLING_HOOK ) {
     91 		if ( self->client->ps.weaponstate == WEAPON_DROPPING ) {
     92 			weapon = self->client->pers.cmd.weapon;
     93 		}
     94 		if ( !( self->client->ps.stats[STAT_WEAPONS] & ( 1 << weapon ) ) ) {
     95 			weapon = WP_NONE;
     96 		}
     97 	}
     98 
     99 	if ( weapon > WP_MACHINEGUN && weapon != WP_GRAPPLING_HOOK && 
    100 		self->client->ps.ammo[ weapon ] ) {
    101 		// find the item type for this weapon
    102 		item = BG_FindItemForWeapon( weapon );
    103 
    104 		// spawn the item
    105 		Drop_Item( self, item, 0 );
    106 	}
    107 
    108 	// drop all the powerups if not in teamplay
    109 	if ( g_gametype.integer != GT_TEAM ) {
    110 		angle = 45;
    111 		for ( i = 1 ; i < PW_NUM_POWERUPS ; i++ ) {
    112 			if ( self->client->ps.powerups[ i ] > level.time ) {
    113 				item = BG_FindItemForPowerup( i );
    114 				if ( !item ) {
    115 					continue;
    116 				}
    117 				drop = Drop_Item( self, item, angle );
    118 				// decide how many seconds it has left
    119 				drop->count = ( self->client->ps.powerups[ i ] - level.time ) / 1000;
    120 				if ( drop->count < 1 ) {
    121 					drop->count = 1;
    122 				}
    123 				angle += 45;
    124 			}
    125 		}
    126 	}
    127 }
    128 
    129 #ifdef MISSIONPACK
    130 
    131 /*
    132 =================
    133 TossClientCubes
    134 =================
    135 */
    136 extern gentity_t	*neutralObelisk;
    137 
    138 void TossClientCubes( gentity_t *self ) {
    139 	gitem_t		*item;
    140 	gentity_t	*drop;
    141 	vec3_t		velocity;
    142 	vec3_t		angles;
    143 	vec3_t		origin;
    144 
    145 	self->client->ps.generic1 = 0;
    146 
    147 	// this should never happen but we should never
    148 	// get the server to crash due to skull being spawned in
    149 	if (!G_EntitiesFree()) {
    150 		return;
    151 	}
    152 
    153 	if( self->client->sess.sessionTeam == TEAM_RED ) {
    154 		item = BG_FindItem( "Red Cube" );
    155 	}
    156 	else {
    157 		item = BG_FindItem( "Blue Cube" );
    158 	}
    159 
    160 	angles[YAW] = (float)(level.time % 360);
    161 	angles[PITCH] = 0;	// always forward
    162 	angles[ROLL] = 0;
    163 
    164 	AngleVectors( angles, velocity, NULL, NULL );
    165 	VectorScale( velocity, 150, velocity );
    166 	velocity[2] += 200 + crandom() * 50;
    167 
    168 	if( neutralObelisk ) {
    169 		VectorCopy( neutralObelisk->s.pos.trBase, origin );
    170 		origin[2] += 44;
    171 	} else {
    172 		VectorClear( origin ) ;
    173 	}
    174 
    175 	drop = LaunchItem( item, origin, velocity );
    176 
    177 	drop->nextthink = level.time + g_cubeTimeout.integer * 1000;
    178 	drop->think = G_FreeEntity;
    179 	drop->spawnflags = self->client->sess.sessionTeam;
    180 }
    181 
    182 
    183 /*
    184 =================
    185 TossClientPersistantPowerups
    186 =================
    187 */
    188 void TossClientPersistantPowerups( gentity_t *ent ) {
    189 	gentity_t	*powerup;
    190 
    191 	if( !ent->client ) {
    192 		return;
    193 	}
    194 
    195 	if( !ent->client->persistantPowerup ) {
    196 		return;
    197 	}
    198 
    199 	powerup = ent->client->persistantPowerup;
    200 
    201 	powerup->r.svFlags &= ~SVF_NOCLIENT;
    202 	powerup->s.eFlags &= ~EF_NODRAW;
    203 	powerup->r.contents = CONTENTS_TRIGGER;
    204 	trap_LinkEntity( powerup );
    205 
    206 	ent->client->ps.stats[STAT_PERSISTANT_POWERUP] = 0;
    207 	ent->client->persistantPowerup = NULL;
    208 }
    209 #endif
    210 
    211 
    212 /*
    213 ==================
    214 LookAtKiller
    215 ==================
    216 */
    217 void LookAtKiller( gentity_t *self, gentity_t *inflictor, gentity_t *attacker ) {
    218 	vec3_t		dir;
    219 	vec3_t		angles;
    220 
    221 	if ( attacker && attacker != self ) {
    222 		VectorSubtract (attacker->s.pos.trBase, self->s.pos.trBase, dir);
    223 	} else if ( inflictor && inflictor != self ) {
    224 		VectorSubtract (inflictor->s.pos.trBase, self->s.pos.trBase, dir);
    225 	} else {
    226 		self->client->ps.stats[STAT_DEAD_YAW] = self->s.angles[YAW];
    227 		return;
    228 	}
    229 
    230 	self->client->ps.stats[STAT_DEAD_YAW] = vectoyaw ( dir );
    231 
    232 	angles[YAW] = vectoyaw ( dir );
    233 	angles[PITCH] = 0; 
    234 	angles[ROLL] = 0;
    235 }
    236 
    237 /*
    238 ==================
    239 GibEntity
    240 ==================
    241 */
    242 void GibEntity( gentity_t *self, int killer ) {
    243 	gentity_t *ent;
    244 	int i;
    245 
    246 	//if this entity still has kamikaze
    247 	if (self->s.eFlags & EF_KAMIKAZE) {
    248 		// check if there is a kamikaze timer around for this owner
    249 		for (i = 0; i < MAX_GENTITIES; i++) {
    250 			ent = &g_entities[i];
    251 			if (!ent->inuse)
    252 				continue;
    253 			if (ent->activator != self)
    254 				continue;
    255 			if (strcmp(ent->classname, "kamikaze timer"))
    256 				continue;
    257 			G_FreeEntity(ent);
    258 			break;
    259 		}
    260 	}
    261 	G_AddEvent( self, EV_GIB_PLAYER, killer );
    262 	self->takedamage = qfalse;
    263 	self->s.eType = ET_INVISIBLE;
    264 	self->r.contents = 0;
    265 }
    266 
    267 /*
    268 ==================
    269 body_die
    270 ==================
    271 */
    272 void body_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath ) {
    273 	if ( self->health > GIB_HEALTH ) {
    274 		return;
    275 	}
    276 	if ( !g_blood.integer ) {
    277 		self->health = GIB_HEALTH+1;
    278 		return;
    279 	}
    280 
    281 	GibEntity( self, 0 );
    282 }
    283 
    284 
    285 // these are just for logging, the client prints its own messages
    286 char	*modNames[] = {
    287 	"MOD_UNKNOWN",
    288 	"MOD_SHOTGUN",
    289 	"MOD_GAUNTLET",
    290 	"MOD_MACHINEGUN",
    291 	"MOD_GRENADE",
    292 	"MOD_GRENADE_SPLASH",
    293 	"MOD_ROCKET",
    294 	"MOD_ROCKET_SPLASH",
    295 	"MOD_PLASMA",
    296 	"MOD_PLASMA_SPLASH",
    297 	"MOD_RAILGUN",
    298 	"MOD_LIGHTNING",
    299 	"MOD_BFG",
    300 	"MOD_BFG_SPLASH",
    301 	"MOD_WATER",
    302 	"MOD_SLIME",
    303 	"MOD_LAVA",
    304 	"MOD_CRUSH",
    305 	"MOD_TELEFRAG",
    306 	"MOD_FALLING",
    307 	"MOD_SUICIDE",
    308 	"MOD_TARGET_LASER",
    309 	"MOD_TRIGGER_HURT",
    310 #ifdef MISSIONPACK
    311 	"MOD_NAIL",
    312 	"MOD_CHAINGUN",
    313 	"MOD_PROXIMITY_MINE",
    314 	"MOD_KAMIKAZE",
    315 	"MOD_JUICED",
    316 #endif
    317 	"MOD_GRAPPLE"
    318 };
    319 
    320 #ifdef MISSIONPACK
    321 /*
    322 ==================
    323 Kamikaze_DeathActivate
    324 ==================
    325 */
    326 void Kamikaze_DeathActivate( gentity_t *ent ) {
    327 	G_StartKamikaze(ent);
    328 	G_FreeEntity(ent);
    329 }
    330 
    331 /*
    332 ==================
    333 Kamikaze_DeathTimer
    334 ==================
    335 */
    336 void Kamikaze_DeathTimer( gentity_t *self ) {
    337 	gentity_t *ent;
    338 
    339 	ent = G_Spawn();
    340 	ent->classname = "kamikaze timer";
    341 	VectorCopy(self->s.pos.trBase, ent->s.pos.trBase);
    342 	ent->r.svFlags |= SVF_NOCLIENT;
    343 	ent->think = Kamikaze_DeathActivate;
    344 	ent->nextthink = level.time + 5 * 1000;
    345 
    346 	ent->activator = self;
    347 }
    348 
    349 #endif
    350 
    351 /*
    352 ==================
    353 CheckAlmostCapture
    354 ==================
    355 */
    356 void CheckAlmostCapture( gentity_t *self, gentity_t *attacker ) {
    357 	gentity_t	*ent;
    358 	vec3_t		dir;
    359 	char		*classname;
    360 
    361 	// if this player was carrying a flag
    362 	if ( self->client->ps.powerups[PW_REDFLAG] ||
    363 		self->client->ps.powerups[PW_BLUEFLAG] ||
    364 		self->client->ps.powerups[PW_NEUTRALFLAG] ) {
    365 		// get the goal flag this player should have been going for
    366 		if ( g_gametype.integer == GT_CTF ) {
    367 			if ( self->client->sess.sessionTeam == TEAM_BLUE ) {
    368 				classname = "team_CTF_blueflag";
    369 			}
    370 			else {
    371 				classname = "team_CTF_redflag";
    372 			}
    373 		}
    374 		else {
    375 			if ( self->client->sess.sessionTeam == TEAM_BLUE ) {
    376 				classname = "team_CTF_redflag";
    377 			}
    378 			else {
    379 				classname = "team_CTF_blueflag";
    380 			}
    381 		}
    382 		ent = NULL;
    383 		do
    384 		{
    385 			ent = G_Find(ent, FOFS(classname), classname);
    386 		} while (ent && (ent->flags & FL_DROPPED_ITEM));
    387 		// if we found the destination flag and it's not picked up
    388 		if (ent && !(ent->r.svFlags & SVF_NOCLIENT) ) {
    389 			// if the player was *very* close
    390 			VectorSubtract( self->client->ps.origin, ent->s.origin, dir );
    391 			if ( VectorLength(dir) < 200 ) {
    392 				self->client->ps.persistant[PERS_PLAYEREVENTS] ^= PLAYEREVENT_HOLYSHIT;
    393 				if ( attacker->client ) {
    394 					attacker->client->ps.persistant[PERS_PLAYEREVENTS] ^= PLAYEREVENT_HOLYSHIT;
    395 				}
    396 			}
    397 		}
    398 	}
    399 }
    400 
    401 /*
    402 ==================
    403 CheckAlmostScored
    404 ==================
    405 */
    406 void CheckAlmostScored( gentity_t *self, gentity_t *attacker ) {
    407 	gentity_t	*ent;
    408 	vec3_t		dir;
    409 	char		*classname;
    410 
    411 	// if the player was carrying cubes
    412 	if ( self->client->ps.generic1 ) {
    413 		if ( self->client->sess.sessionTeam == TEAM_BLUE ) {
    414 			classname = "team_redobelisk";
    415 		}
    416 		else {
    417 			classname = "team_blueobelisk";
    418 		}
    419 		ent = G_Find(NULL, FOFS(classname), classname);
    420 		// if we found the destination obelisk
    421 		if ( ent ) {
    422 			// if the player was *very* close
    423 			VectorSubtract( self->client->ps.origin, ent->s.origin, dir );
    424 			if ( VectorLength(dir) < 200 ) {
    425 				self->client->ps.persistant[PERS_PLAYEREVENTS] ^= PLAYEREVENT_HOLYSHIT;
    426 				if ( attacker->client ) {
    427 					attacker->client->ps.persistant[PERS_PLAYEREVENTS] ^= PLAYEREVENT_HOLYSHIT;
    428 				}
    429 			}
    430 		}
    431 	}
    432 }
    433 
    434 /*
    435 ==================
    436 player_die
    437 ==================
    438 */
    439 void player_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath ) {
    440 	gentity_t	*ent;
    441 	int			anim;
    442 	int			contents;
    443 	int			killer;
    444 	int			i;
    445 	char		*killerName, *obit;
    446 
    447 	if ( self->client->ps.pm_type == PM_DEAD ) {
    448 		return;
    449 	}
    450 
    451 	if ( level.intermissiontime ) {
    452 		return;
    453 	}
    454 
    455 	// check for an almost capture
    456 	CheckAlmostCapture( self, attacker );
    457 	// check for a player that almost brought in cubes
    458 	CheckAlmostScored( self, attacker );
    459 
    460 	if (self->client && self->client->hook) {
    461 		Weapon_HookFree(self->client->hook);
    462 	}
    463 #ifdef MISSIONPACK
    464 	if ((self->client->ps.eFlags & EF_TICKING) && self->activator) {
    465 		self->client->ps.eFlags &= ~EF_TICKING;
    466 		self->activator->think = G_FreeEntity;
    467 		self->activator->nextthink = level.time;
    468 	}
    469 #endif
    470 	self->client->ps.pm_type = PM_DEAD;
    471 
    472 	if ( attacker ) {
    473 		killer = attacker->s.number;
    474 		if ( attacker->client ) {
    475 			killerName = attacker->client->pers.netname;
    476 		} else {
    477 			killerName = "<non-client>";
    478 		}
    479 	} else {
    480 		killer = ENTITYNUM_WORLD;
    481 		killerName = "<world>";
    482 	}
    483 
    484 	if ( killer < 0 || killer >= MAX_CLIENTS ) {
    485 		killer = ENTITYNUM_WORLD;
    486 		killerName = "<world>";
    487 	}
    488 
    489 	if ( meansOfDeath < 0 || meansOfDeath >= sizeof( modNames ) / sizeof( modNames[0] ) ) {
    490 		obit = "<bad obituary>";
    491 	} else {
    492 		obit = modNames[ meansOfDeath ];
    493 	}
    494 
    495 	G_LogPrintf("Kill: %i %i %i: %s killed %s by %s\n", 
    496 		killer, self->s.number, meansOfDeath, killerName, 
    497 		self->client->pers.netname, obit );
    498 
    499 	// broadcast the death event to everyone
    500 	ent = G_TempEntity( self->r.currentOrigin, EV_OBITUARY );
    501 	ent->s.eventParm = meansOfDeath;
    502 	ent->s.otherEntityNum = self->s.number;
    503 	ent->s.otherEntityNum2 = killer;
    504 	ent->r.svFlags = SVF_BROADCAST;	// send to everyone
    505 
    506 	self->enemy = attacker;
    507 
    508 	self->client->ps.persistant[PERS_KILLED]++;
    509 
    510 	if (attacker && attacker->client) {
    511 		attacker->client->lastkilled_client = self->s.number;
    512 
    513 		if ( attacker == self || OnSameTeam (self, attacker ) ) {
    514 			AddScore( attacker, self->r.currentOrigin, -1 );
    515 		} else {
    516 			AddScore( attacker, self->r.currentOrigin, 1 );
    517 
    518 			if( meansOfDeath == MOD_GAUNTLET ) {
    519 				
    520 				// play humiliation on player
    521 				attacker->client->ps.persistant[PERS_GAUNTLET_FRAG_COUNT]++;
    522 
    523 				// add the sprite over the player's head
    524 				attacker->client->ps.eFlags &= ~(EF_AWARD_IMPRESSIVE | EF_AWARD_EXCELLENT | EF_AWARD_GAUNTLET | EF_AWARD_ASSIST | EF_AWARD_DEFEND | EF_AWARD_CAP );
    525 				attacker->client->ps.eFlags |= EF_AWARD_GAUNTLET;
    526 				attacker->client->rewardTime = level.time + REWARD_SPRITE_TIME;
    527 
    528 				// also play humiliation on target
    529 				self->client->ps.persistant[PERS_PLAYEREVENTS] ^= PLAYEREVENT_GAUNTLETREWARD;
    530 			}
    531 
    532 			// check for two kills in a short amount of time
    533 			// if this is close enough to the last kill, give a reward sound
    534 			if ( level.time - attacker->client->lastKillTime < CARNAGE_REWARD_TIME ) {
    535 				// play excellent on player
    536 				attacker->client->ps.persistant[PERS_EXCELLENT_COUNT]++;
    537 
    538 				// add the sprite over the player's head
    539 				attacker->client->ps.eFlags &= ~(EF_AWARD_IMPRESSIVE | EF_AWARD_EXCELLENT | EF_AWARD_GAUNTLET | EF_AWARD_ASSIST | EF_AWARD_DEFEND | EF_AWARD_CAP );
    540 				attacker->client->ps.eFlags |= EF_AWARD_EXCELLENT;
    541 				attacker->client->rewardTime = level.time + REWARD_SPRITE_TIME;
    542 			}
    543 			attacker->client->lastKillTime = level.time;
    544 
    545 		}
    546 	} else {
    547 		AddScore( self, self->r.currentOrigin, -1 );
    548 	}
    549 
    550 	// Add team bonuses
    551 	Team_FragBonuses(self, inflictor, attacker);
    552 
    553 	// if I committed suicide, the flag does not fall, it returns.
    554 	if (meansOfDeath == MOD_SUICIDE) {
    555 		if ( self->client->ps.powerups[PW_NEUTRALFLAG] ) {		// only happens in One Flag CTF
    556 			Team_ReturnFlag( TEAM_FREE );
    557 			self->client->ps.powerups[PW_NEUTRALFLAG] = 0;
    558 		}
    559 		else if ( self->client->ps.powerups[PW_REDFLAG] ) {		// only happens in standard CTF
    560 			Team_ReturnFlag( TEAM_RED );
    561 			self->client->ps.powerups[PW_REDFLAG] = 0;
    562 		}
    563 		else if ( self->client->ps.powerups[PW_BLUEFLAG] ) {	// only happens in standard CTF
    564 			Team_ReturnFlag( TEAM_BLUE );
    565 			self->client->ps.powerups[PW_BLUEFLAG] = 0;
    566 		}
    567 	}
    568 
    569 	// if client is in a nodrop area, don't drop anything (but return CTF flags!)
    570 	contents = trap_PointContents( self->r.currentOrigin, -1 );
    571 	if ( !( contents & CONTENTS_NODROP )) {
    572 		TossClientItems( self );
    573 	}
    574 	else {
    575 		if ( self->client->ps.powerups[PW_NEUTRALFLAG] ) {		// only happens in One Flag CTF
    576 			Team_ReturnFlag( TEAM_FREE );
    577 		}
    578 		else if ( self->client->ps.powerups[PW_REDFLAG] ) {		// only happens in standard CTF
    579 			Team_ReturnFlag( TEAM_RED );
    580 		}
    581 		else if ( self->client->ps.powerups[PW_BLUEFLAG] ) {	// only happens in standard CTF
    582 			Team_ReturnFlag( TEAM_BLUE );
    583 		}
    584 	}
    585 #ifdef MISSIONPACK
    586 	TossClientPersistantPowerups( self );
    587 	if( g_gametype.integer == GT_HARVESTER ) {
    588 		TossClientCubes( self );
    589 	}
    590 #endif
    591 
    592 	Cmd_Score_f( self );		// show scores
    593 	// send updated scores to any clients that are following this one,
    594 	// or they would get stale scoreboards
    595 	for ( i = 0 ; i < level.maxclients ; i++ ) {
    596 		gclient_t	*client;
    597 
    598 		client = &level.clients[i];
    599 		if ( client->pers.connected != CON_CONNECTED ) {
    600 			continue;
    601 		}
    602 		if ( client->sess.sessionTeam != TEAM_SPECTATOR ) {
    603 			continue;
    604 		}
    605 		if ( client->sess.spectatorClient == self->s.number ) {
    606 			Cmd_Score_f( g_entities + i );
    607 		}
    608 	}
    609 
    610 	self->takedamage = qtrue;	// can still be gibbed
    611 
    612 	self->s.weapon = WP_NONE;
    613 	self->s.powerups = 0;
    614 	self->r.contents = CONTENTS_CORPSE;
    615 
    616 	self->s.angles[0] = 0;
    617 	self->s.angles[2] = 0;
    618 	LookAtKiller (self, inflictor, attacker);
    619 
    620 	VectorCopy( self->s.angles, self->client->ps.viewangles );
    621 
    622 	self->s.loopSound = 0;
    623 
    624 	self->r.maxs[2] = -8;
    625 
    626 	// don't allow respawn until the death anim is done
    627 	// g_forcerespawn may force spawning at some later time
    628 	self->client->respawnTime = level.time + 1700;
    629 
    630 	// remove powerups
    631 	memset( self->client->ps.powerups, 0, sizeof(self->client->ps.powerups) );
    632 
    633 	// never gib in a nodrop
    634 	if ( (self->health <= GIB_HEALTH && !(contents & CONTENTS_NODROP) && g_blood.integer) || meansOfDeath == MOD_SUICIDE) {
    635 		// gib death
    636 		GibEntity( self, killer );
    637 	} else {
    638 		// normal death
    639 		static int i;
    640 
    641 		switch ( i ) {
    642 		case 0:
    643 			anim = BOTH_DEATH1;
    644 			break;
    645 		case 1:
    646 			anim = BOTH_DEATH2;
    647 			break;
    648 		case 2:
    649 		default:
    650 			anim = BOTH_DEATH3;
    651 			break;
    652 		}
    653 
    654 		// for the no-blood option, we need to prevent the health
    655 		// from going to gib level
    656 		if ( self->health <= GIB_HEALTH ) {
    657 			self->health = GIB_HEALTH+1;
    658 		}
    659 
    660 		self->client->ps.legsAnim = 
    661 			( ( self->client->ps.legsAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim;
    662 		self->client->ps.torsoAnim = 
    663 			( ( self->client->ps.torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim;
    664 
    665 		G_AddEvent( self, EV_DEATH1 + i, killer );
    666 
    667 		// the body can still be gibbed
    668 		self->die = body_die;
    669 
    670 		// globally cycle through the different death animations
    671 		i = ( i + 1 ) % 3;
    672 
    673 #ifdef MISSIONPACK
    674 		if (self->s.eFlags & EF_KAMIKAZE) {
    675 			Kamikaze_DeathTimer( self );
    676 		}
    677 #endif
    678 	}
    679 
    680 	trap_LinkEntity (self);
    681 
    682 }
    683 
    684 
    685 /*
    686 ================
    687 CheckArmor
    688 ================
    689 */
    690 int CheckArmor (gentity_t *ent, int damage, int dflags)
    691 {
    692 	gclient_t	*client;
    693 	int			save;
    694 	int			count;
    695 
    696 	if (!damage)
    697 		return 0;
    698 
    699 	client = ent->client;
    700 
    701 	if (!client)
    702 		return 0;
    703 
    704 	if (dflags & DAMAGE_NO_ARMOR)
    705 		return 0;
    706 
    707 	// armor
    708 	count = client->ps.stats[STAT_ARMOR];
    709 	save = ceil( damage * ARMOR_PROTECTION );
    710 	if (save >= count)
    711 		save = count;
    712 
    713 	if (!save)
    714 		return 0;
    715 
    716 	client->ps.stats[STAT_ARMOR] -= save;
    717 
    718 	return save;
    719 }
    720 
    721 /*
    722 ================
    723 RaySphereIntersections
    724 ================
    725 */
    726 int RaySphereIntersections( vec3_t origin, float radius, vec3_t point, vec3_t dir, vec3_t intersections[2] ) {
    727 	float b, c, d, t;
    728 
    729 	//	| origin - (point + t * dir) | = radius
    730 	//	a = dir[0]^2 + dir[1]^2 + dir[2]^2;
    731 	//	b = 2 * (dir[0] * (point[0] - origin[0]) + dir[1] * (point[1] - origin[1]) + dir[2] * (point[2] - origin[2]));
    732 	//	c = (point[0] - origin[0])^2 + (point[1] - origin[1])^2 + (point[2] - origin[2])^2 - radius^2;
    733 
    734 	// normalize dir so a = 1
    735 	VectorNormalize(dir);
    736 	b = 2 * (dir[0] * (point[0] - origin[0]) + dir[1] * (point[1] - origin[1]) + dir[2] * (point[2] - origin[2]));
    737 	c = (point[0] - origin[0]) * (point[0] - origin[0]) +
    738 		(point[1] - origin[1]) * (point[1] - origin[1]) +
    739 		(point[2] - origin[2]) * (point[2] - origin[2]) -
    740 		radius * radius;
    741 
    742 	d = b * b - 4 * c;
    743 	if (d > 0) {
    744 		t = (- b + sqrt(d)) / 2;
    745 		VectorMA(point, t, dir, intersections[0]);
    746 		t = (- b - sqrt(d)) / 2;
    747 		VectorMA(point, t, dir, intersections[1]);
    748 		return 2;
    749 	}
    750 	else if (d == 0) {
    751 		t = (- b ) / 2;
    752 		VectorMA(point, t, dir, intersections[0]);
    753 		return 1;
    754 	}
    755 	return 0;
    756 }
    757 
    758 #ifdef MISSIONPACK
    759 /*
    760 ================
    761 G_InvulnerabilityEffect
    762 ================
    763 */
    764 int G_InvulnerabilityEffect( gentity_t *targ, vec3_t dir, vec3_t point, vec3_t impactpoint, vec3_t bouncedir ) {
    765 	gentity_t	*impact;
    766 	vec3_t		intersections[2], vec;
    767 	int			n;
    768 
    769 	if ( !targ->client ) {
    770 		return qfalse;
    771 	}
    772 	VectorCopy(dir, vec);
    773 	VectorInverse(vec);
    774 	// sphere model radius = 42 units
    775 	n = RaySphereIntersections( targ->client->ps.origin, 42, point, vec, intersections);
    776 	if (n > 0) {
    777 		impact = G_TempEntity( targ->client->ps.origin, EV_INVUL_IMPACT );
    778 		VectorSubtract(intersections[0], targ->client->ps.origin, vec);
    779 		vectoangles(vec, impact->s.angles);
    780 		impact->s.angles[0] += 90;
    781 		if (impact->s.angles[0] > 360)
    782 			impact->s.angles[0] -= 360;
    783 		if ( impactpoint ) {
    784 			VectorCopy( intersections[0], impactpoint );
    785 		}
    786 		if ( bouncedir ) {
    787 			VectorCopy( vec, bouncedir );
    788 			VectorNormalize( bouncedir );
    789 		}
    790 		return qtrue;
    791 	}
    792 	else {
    793 		return qfalse;
    794 	}
    795 }
    796 #endif
    797 /*
    798 ============
    799 T_Damage
    800 
    801 targ		entity that is being damaged
    802 inflictor	entity that is causing the damage
    803 attacker	entity that caused the inflictor to damage targ
    804 	example: targ=monster, inflictor=rocket, attacker=player
    805 
    806 dir			direction of the attack for knockback
    807 point		point at which the damage is being inflicted, used for headshots
    808 damage		amount of damage being inflicted
    809 knockback	force to be applied against targ as a result of the damage
    810 
    811 inflictor, attacker, dir, and point can be NULL for environmental effects
    812 
    813 dflags		these flags are used to control how T_Damage works
    814 	DAMAGE_RADIUS			damage was indirect (from a nearby explosion)
    815 	DAMAGE_NO_ARMOR			armor does not protect from this damage
    816 	DAMAGE_NO_KNOCKBACK		do not affect velocity, just view angles
    817 	DAMAGE_NO_PROTECTION	kills godmode, armor, everything
    818 ============
    819 */
    820 
    821 void G_Damage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker,
    822 			   vec3_t dir, vec3_t point, int damage, int dflags, int mod ) {
    823 	gclient_t	*client;
    824 	int			take;
    825 	int			save;
    826 	int			asave;
    827 	int			knockback;
    828 	int			max;
    829 #ifdef MISSIONPACK
    830 	vec3_t		bouncedir, impactpoint;
    831 #endif
    832 
    833 	if (!targ->takedamage) {
    834 		return;
    835 	}
    836 
    837 	// the intermission has allready been qualified for, so don't
    838 	// allow any extra scoring
    839 	if ( level.intermissionQueued ) {
    840 		return;
    841 	}
    842 #ifdef MISSIONPACK
    843 	if ( targ->client && mod != MOD_JUICED) {
    844 		if ( targ->client->invulnerabilityTime > level.time) {
    845 			if ( dir && point ) {
    846 				G_InvulnerabilityEffect( targ, dir, point, impactpoint, bouncedir );
    847 			}
    848 			return;
    849 		}
    850 	}
    851 #endif
    852 	if ( !inflictor ) {
    853 		inflictor = &g_entities[ENTITYNUM_WORLD];
    854 	}
    855 	if ( !attacker ) {
    856 		attacker = &g_entities[ENTITYNUM_WORLD];
    857 	}
    858 
    859 	// shootable doors / buttons don't actually have any health
    860 	if ( targ->s.eType == ET_MOVER ) {
    861 		if ( targ->use && targ->moverState == MOVER_POS1 ) {
    862 			targ->use( targ, inflictor, attacker );
    863 		}
    864 		return;
    865 	}
    866 #ifdef MISSIONPACK
    867 	if( g_gametype.integer == GT_OBELISK && CheckObeliskAttack( targ, attacker ) ) {
    868 		return;
    869 	}
    870 #endif
    871 	// reduce damage by the attacker's handicap value
    872 	// unless they are rocket jumping
    873 	if ( attacker->client && attacker != targ ) {
    874 		max = attacker->client->ps.stats[STAT_MAX_HEALTH];
    875 #ifdef MISSIONPACK
    876 		if( bg_itemlist[attacker->client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_GUARD ) {
    877 			max /= 2;
    878 		}
    879 #endif
    880 		damage = damage * max / 100;
    881 	}
    882 
    883 	client = targ->client;
    884 
    885 	if ( client ) {
    886 		if ( client->noclip ) {
    887 			return;
    888 		}
    889 	}
    890 
    891 	if ( !dir ) {
    892 		dflags |= DAMAGE_NO_KNOCKBACK;
    893 	} else {
    894 		VectorNormalize(dir);
    895 	}
    896 
    897 	knockback = damage;
    898 	if ( knockback > 200 ) {
    899 		knockback = 200;
    900 	}
    901 	if ( targ->flags & FL_NO_KNOCKBACK ) {
    902 		knockback = 0;
    903 	}
    904 	if ( dflags & DAMAGE_NO_KNOCKBACK ) {
    905 		knockback = 0;
    906 	}
    907 
    908 	// figure momentum add, even if the damage won't be taken
    909 	if ( knockback && targ->client ) {
    910 		vec3_t	kvel;
    911 		float	mass;
    912 
    913 		mass = 200;
    914 
    915 		VectorScale (dir, g_knockback.value * (float)knockback / mass, kvel);
    916 		VectorAdd (targ->client->ps.velocity, kvel, targ->client->ps.velocity);
    917 
    918 		// set the timer so that the other client can't cancel
    919 		// out the movement immediately
    920 		if ( !targ->client->ps.pm_time ) {
    921 			int		t;
    922 
    923 			t = knockback * 2;
    924 			if ( t < 50 ) {
    925 				t = 50;
    926 			}
    927 			if ( t > 200 ) {
    928 				t = 200;
    929 			}
    930 			targ->client->ps.pm_time = t;
    931 			targ->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
    932 		}
    933 	}
    934 
    935 	// check for completely getting out of the damage
    936 	if ( !(dflags & DAMAGE_NO_PROTECTION) ) {
    937 
    938 		// if TF_NO_FRIENDLY_FIRE is set, don't do damage to the target
    939 		// if the attacker was on the same team
    940 #ifdef MISSIONPACK
    941 		if ( mod != MOD_JUICED && targ != attacker && !(dflags & DAMAGE_NO_TEAM_PROTECTION) && OnSameTeam (targ, attacker)  ) {
    942 #else	
    943 		if ( targ != attacker && OnSameTeam (targ, attacker)  ) {
    944 #endif
    945 			if ( !g_friendlyFire.integer ) {
    946 				return;
    947 			}
    948 		}
    949 #ifdef MISSIONPACK
    950 		if (mod == MOD_PROXIMITY_MINE) {
    951 			if (inflictor && inflictor->parent && OnSameTeam(targ, inflictor->parent)) {
    952 				return;
    953 			}
    954 			if (targ == attacker) {
    955 				return;
    956 			}
    957 		}
    958 #endif
    959 
    960 		// check for godmode
    961 		if ( targ->flags & FL_GODMODE ) {
    962 			return;
    963 		}
    964 	}
    965 
    966 	// battlesuit protects from all radius damage (but takes knockback)
    967 	// and protects 50% against all damage
    968 	if ( client && client->ps.powerups[PW_BATTLESUIT] ) {
    969 		G_AddEvent( targ, EV_POWERUP_BATTLESUIT, 0 );
    970 		if ( ( dflags & DAMAGE_RADIUS ) || ( mod == MOD_FALLING ) ) {
    971 			return;
    972 		}
    973 		damage *= 0.5;
    974 	}
    975 
    976 	// add to the attacker's hit counter (if the target isn't a general entity like a prox mine)
    977 	if ( attacker->client && targ != attacker && targ->health > 0
    978 			&& targ->s.eType != ET_MISSILE
    979 			&& targ->s.eType != ET_GENERAL) {
    980 		if ( OnSameTeam( targ, attacker ) ) {
    981 			attacker->client->ps.persistant[PERS_HITS]--;
    982 		} else {
    983 			attacker->client->ps.persistant[PERS_HITS]++;
    984 		}
    985 		attacker->client->ps.persistant[PERS_ATTACKEE_ARMOR] = (targ->health<<8)|(client->ps.stats[STAT_ARMOR]);
    986 	}
    987 
    988 	// always give half damage if hurting self
    989 	// calculated after knockback, so rocket jumping works
    990 	if ( targ == attacker) {
    991 		damage *= 0.5;
    992 	}
    993 
    994 	if ( damage < 1 ) {
    995 		damage = 1;
    996 	}
    997 	take = damage;
    998 	save = 0;
    999 
   1000 	// save some from armor
   1001 	asave = CheckArmor (targ, take, dflags);
   1002 	take -= asave;
   1003 
   1004 	if ( g_debugDamage.integer ) {
   1005 		G_Printf( "%i: client:%i health:%i damage:%i armor:%i\n", level.time, targ->s.number,
   1006 			targ->health, take, asave );
   1007 	}
   1008 
   1009 	// add to the damage inflicted on a player this frame
   1010 	// the total will be turned into screen blends and view angle kicks
   1011 	// at the end of the frame
   1012 	if ( client ) {
   1013 		if ( attacker ) {
   1014 			client->ps.persistant[PERS_ATTACKER] = attacker->s.number;
   1015 		} else {
   1016 			client->ps.persistant[PERS_ATTACKER] = ENTITYNUM_WORLD;
   1017 		}
   1018 		client->damage_armor += asave;
   1019 		client->damage_blood += take;
   1020 		client->damage_knockback += knockback;
   1021 		if ( dir ) {
   1022 			VectorCopy ( dir, client->damage_from );
   1023 			client->damage_fromWorld = qfalse;
   1024 		} else {
   1025 			VectorCopy ( targ->r.currentOrigin, client->damage_from );
   1026 			client->damage_fromWorld = qtrue;
   1027 		}
   1028 	}
   1029 
   1030 	// See if it's the player hurting the emeny flag carrier
   1031 #ifdef MISSIONPACK
   1032 	if( g_gametype.integer == GT_CTF || g_gametype.integer == GT_1FCTF ) {
   1033 #else	
   1034 	if( g_gametype.integer == GT_CTF) {
   1035 #endif
   1036 		Team_CheckHurtCarrier(targ, attacker);
   1037 	}
   1038 
   1039 	if (targ->client) {
   1040 		// set the last client who damaged the target
   1041 		targ->client->lasthurt_client = attacker->s.number;
   1042 		targ->client->lasthurt_mod = mod;
   1043 	}
   1044 
   1045 	// do the damage
   1046 	if (take) {
   1047 		targ->health = targ->health - take;
   1048 		if ( targ->client ) {
   1049 			targ->client->ps.stats[STAT_HEALTH] = targ->health;
   1050 		}
   1051 			
   1052 		if ( targ->health <= 0 ) {
   1053 			if ( client )
   1054 				targ->flags |= FL_NO_KNOCKBACK;
   1055 
   1056 			if (targ->health < -999)
   1057 				targ->health = -999;
   1058 
   1059 			targ->enemy = attacker;
   1060 			targ->die (targ, inflictor, attacker, take, mod);
   1061 			return;
   1062 		} else if ( targ->pain ) {
   1063 			targ->pain (targ, attacker, take);
   1064 		}
   1065 	}
   1066 
   1067 }
   1068 
   1069 
   1070 /*
   1071 ============
   1072 CanDamage
   1073 
   1074 Returns qtrue if the inflictor can directly damage the target.  Used for
   1075 explosions and melee attacks.
   1076 ============
   1077 */
   1078 qboolean CanDamage (gentity_t *targ, vec3_t origin) {
   1079 	vec3_t	dest;
   1080 	trace_t	tr;
   1081 	vec3_t	midpoint;
   1082 
   1083 	// use the midpoint of the bounds instead of the origin, because
   1084 	// bmodels may have their origin is 0,0,0
   1085 	VectorAdd (targ->r.absmin, targ->r.absmax, midpoint);
   1086 	VectorScale (midpoint, 0.5, midpoint);
   1087 
   1088 	VectorCopy (midpoint, dest);
   1089 	trap_Trace ( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID);
   1090 	if (tr.fraction == 1.0 || tr.entityNum == targ->s.number)
   1091 		return qtrue;
   1092 
   1093 	// this should probably check in the plane of projection, 
   1094 	// rather than in world coordinate, and also include Z
   1095 	VectorCopy (midpoint, dest);
   1096 	dest[0] += 15.0;
   1097 	dest[1] += 15.0;
   1098 	trap_Trace ( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID);
   1099 	if (tr.fraction == 1.0)
   1100 		return qtrue;
   1101 
   1102 	VectorCopy (midpoint, dest);
   1103 	dest[0] += 15.0;
   1104 	dest[1] -= 15.0;
   1105 	trap_Trace ( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID);
   1106 	if (tr.fraction == 1.0)
   1107 		return qtrue;
   1108 
   1109 	VectorCopy (midpoint, dest);
   1110 	dest[0] -= 15.0;
   1111 	dest[1] += 15.0;
   1112 	trap_Trace ( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID);
   1113 	if (tr.fraction == 1.0)
   1114 		return qtrue;
   1115 
   1116 	VectorCopy (midpoint, dest);
   1117 	dest[0] -= 15.0;
   1118 	dest[1] -= 15.0;
   1119 	trap_Trace ( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID);
   1120 	if (tr.fraction == 1.0)
   1121 		return qtrue;
   1122 
   1123 
   1124 	return qfalse;
   1125 }
   1126 
   1127 
   1128 /*
   1129 ============
   1130 G_RadiusDamage
   1131 ============
   1132 */
   1133 qboolean G_RadiusDamage ( vec3_t origin, gentity_t *attacker, float damage, float radius,
   1134 					 gentity_t *ignore, int mod) {
   1135 	float		points, dist;
   1136 	gentity_t	*ent;
   1137 	int			entityList[MAX_GENTITIES];
   1138 	int			numListedEntities;
   1139 	vec3_t		mins, maxs;
   1140 	vec3_t		v;
   1141 	vec3_t		dir;
   1142 	int			i, e;
   1143 	qboolean	hitClient = qfalse;
   1144 
   1145 	if ( radius < 1 ) {
   1146 		radius = 1;
   1147 	}
   1148 
   1149 	for ( i = 0 ; i < 3 ; i++ ) {
   1150 		mins[i] = origin[i] - radius;
   1151 		maxs[i] = origin[i] + radius;
   1152 	}
   1153 
   1154 	numListedEntities = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
   1155 
   1156 	for ( e = 0 ; e < numListedEntities ; e++ ) {
   1157 		ent = &g_entities[entityList[ e ]];
   1158 
   1159 		if (ent == ignore)
   1160 			continue;
   1161 		if (!ent->takedamage)
   1162 			continue;
   1163 
   1164 		// find the distance from the edge of the bounding box
   1165 		for ( i = 0 ; i < 3 ; i++ ) {
   1166 			if ( origin[i] < ent->r.absmin[i] ) {
   1167 				v[i] = ent->r.absmin[i] - origin[i];
   1168 			} else if ( origin[i] > ent->r.absmax[i] ) {
   1169 				v[i] = origin[i] - ent->r.absmax[i];
   1170 			} else {
   1171 				v[i] = 0;
   1172 			}
   1173 		}
   1174 
   1175 		dist = VectorLength( v );
   1176 		if ( dist >= radius ) {
   1177 			continue;
   1178 		}
   1179 
   1180 		points = damage * ( 1.0 - dist / radius );
   1181 
   1182 		if( CanDamage (ent, origin) ) {
   1183 			if( LogAccuracyHit( ent, attacker ) ) {
   1184 				hitClient = qtrue;
   1185 			}
   1186 			VectorSubtract (ent->r.currentOrigin, origin, dir);
   1187 			// push the center of mass higher than the origin so players
   1188 			// get knocked into the air more
   1189 			dir[2] += 24;
   1190 			G_Damage (ent, NULL, attacker, dir, origin, (int)points, DAMAGE_RADIUS, mod);
   1191 		}
   1192 	}
   1193 
   1194 	return hitClient;
   1195 }