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 Quake III Arena source code; 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 */
ScorePlum(gentity_t * ent,vec3_t origin,int score)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 */
AddScore(gentity_t * ent,vec3_t origin,int score)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 */
TossClientItems(gentity_t * self)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 
TossClientCubes(gentity_t * self)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 */
TossClientPersistantPowerups(gentity_t * ent)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 */
LookAtKiller(gentity_t * self,gentity_t * inflictor,gentity_t * attacker)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 */
GibEntity(gentity_t * self,int killer)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 */
body_die(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,int damage,int meansOfDeath)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 */
Kamikaze_DeathActivate(gentity_t * ent)326 void Kamikaze_DeathActivate( gentity_t *ent ) {
327 	G_StartKamikaze(ent);
328 	G_FreeEntity(ent);
329 }
330 
331 /*
332 ==================
333 Kamikaze_DeathTimer
334 ==================
335 */
Kamikaze_DeathTimer(gentity_t * self)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 */
CheckAlmostCapture(gentity_t * self,gentity_t * attacker)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 */
CheckAlmostScored(gentity_t * self,gentity_t * attacker)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 */
player_die(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,int damage,int meansOfDeath)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 */
CheckArmor(gentity_t * ent,int damage,int dflags)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 */
RaySphereIntersections(vec3_t origin,float radius,vec3_t point,vec3_t dir,vec3_t intersections[2])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 */
G_InvulnerabilityEffect(gentity_t * targ,vec3_t dir,vec3_t point,vec3_t impactpoint,vec3_t bouncedir)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 
G_Damage(gentity_t * targ,gentity_t * inflictor,gentity_t * attacker,vec3_t dir,vec3_t point,int damage,int dflags,int mod)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 && client
978 			&& targ != attacker && targ->health > 0
979 			&& targ->s.eType != ET_MISSILE
980 			&& targ->s.eType != ET_GENERAL) {
981 		if ( OnSameTeam( targ, attacker ) ) {
982 			attacker->client->ps.persistant[PERS_HITS]--;
983 		} else {
984 			attacker->client->ps.persistant[PERS_HITS]++;
985 		}
986 		attacker->client->ps.persistant[PERS_ATTACKEE_ARMOR] = (targ->health<<8)|(client->ps.stats[STAT_ARMOR]);
987 	}
988 
989 	// always give half damage if hurting self
990 	// calculated after knockback, so rocket jumping works
991 	if ( targ == attacker) {
992 		damage *= 0.5;
993 	}
994 
995 	if ( damage < 1 ) {
996 		damage = 1;
997 	}
998 	take = damage;
999 	save = 0;
1000 
1001 	// save some from armor
1002 	asave = CheckArmor (targ, take, dflags);
1003 	take -= asave;
1004 
1005 	if ( g_debugDamage.integer ) {
1006 		G_Printf( "%i: client:%i health:%i damage:%i armor:%i\n", level.time, targ->s.number,
1007 			targ->health, take, asave );
1008 	}
1009 
1010 	// add to the damage inflicted on a player this frame
1011 	// the total will be turned into screen blends and view angle kicks
1012 	// at the end of the frame
1013 	if ( client ) {
1014 		if ( attacker ) {
1015 			client->ps.persistant[PERS_ATTACKER] = attacker->s.number;
1016 		} else {
1017 			client->ps.persistant[PERS_ATTACKER] = ENTITYNUM_WORLD;
1018 		}
1019 		client->damage_armor += asave;
1020 		client->damage_blood += take;
1021 		client->damage_knockback += knockback;
1022 		if ( dir ) {
1023 			VectorCopy ( dir, client->damage_from );
1024 			client->damage_fromWorld = qfalse;
1025 		} else {
1026 			VectorCopy ( targ->r.currentOrigin, client->damage_from );
1027 			client->damage_fromWorld = qtrue;
1028 		}
1029 	}
1030 
1031 	// See if it's the player hurting the emeny flag carrier
1032 #ifdef MISSIONPACK
1033 	if( g_gametype.integer == GT_CTF || g_gametype.integer == GT_1FCTF ) {
1034 #else
1035 	if( g_gametype.integer == GT_CTF) {
1036 #endif
1037 		Team_CheckHurtCarrier(targ, attacker);
1038 	}
1039 
1040 	if (targ->client) {
1041 		// set the last client who damaged the target
1042 		targ->client->lasthurt_client = attacker->s.number;
1043 		targ->client->lasthurt_mod = mod;
1044 	}
1045 
1046 	// do the damage
1047 	if (take) {
1048 		targ->health = targ->health - take;
1049 		if ( targ->client ) {
1050 			targ->client->ps.stats[STAT_HEALTH] = targ->health;
1051 		}
1052 
1053 		if ( targ->health <= 0 ) {
1054 			if ( client )
1055 				targ->flags |= FL_NO_KNOCKBACK;
1056 
1057 			if (targ->health < -999)
1058 				targ->health = -999;
1059 
1060 			targ->enemy = attacker;
1061 			targ->die (targ, inflictor, attacker, take, mod);
1062 			return;
1063 		} else if ( targ->pain ) {
1064 			targ->pain (targ, attacker, take);
1065 		}
1066 	}
1067 
1068 }
1069 
1070 
1071 /*
1072 ============
1073 CanDamage
1074 
1075 Returns qtrue if the inflictor can directly damage the target.  Used for
1076 explosions and melee attacks.
1077 ============
1078 */
1079 qboolean CanDamage (gentity_t *targ, vec3_t origin) {
1080 	vec3_t	dest;
1081 	trace_t	tr;
1082 	vec3_t	midpoint;
1083 
1084 	// use the midpoint of the bounds instead of the origin, because
1085 	// bmodels may have their origin is 0,0,0
1086 	VectorAdd (targ->r.absmin, targ->r.absmax, midpoint);
1087 	VectorScale (midpoint, 0.5, midpoint);
1088 
1089 	VectorCopy (midpoint, dest);
1090 	trap_Trace ( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID);
1091 	if (tr.fraction == 1.0 || tr.entityNum == targ->s.number)
1092 		return qtrue;
1093 
1094 	// this should probably check in the plane of projection,
1095 	// rather than in world coordinate, and also include Z
1096 	VectorCopy (midpoint, dest);
1097 	dest[0] += 15.0;
1098 	dest[1] += 15.0;
1099 	trap_Trace ( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID);
1100 	if (tr.fraction == 1.0)
1101 		return qtrue;
1102 
1103 	VectorCopy (midpoint, dest);
1104 	dest[0] += 15.0;
1105 	dest[1] -= 15.0;
1106 	trap_Trace ( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID);
1107 	if (tr.fraction == 1.0)
1108 		return qtrue;
1109 
1110 	VectorCopy (midpoint, dest);
1111 	dest[0] -= 15.0;
1112 	dest[1] += 15.0;
1113 	trap_Trace ( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID);
1114 	if (tr.fraction == 1.0)
1115 		return qtrue;
1116 
1117 	VectorCopy (midpoint, dest);
1118 	dest[0] -= 15.0;
1119 	dest[1] -= 15.0;
1120 	trap_Trace ( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID);
1121 	if (tr.fraction == 1.0)
1122 		return qtrue;
1123 
1124 
1125 	return qfalse;
1126 }
1127 
1128 
1129 /*
1130 ============
1131 G_RadiusDamage
1132 ============
1133 */
1134 qboolean G_RadiusDamage ( vec3_t origin, gentity_t *attacker, float damage, float radius,
1135 					 gentity_t *ignore, int mod) {
1136 	float		points, dist;
1137 	gentity_t	*ent;
1138 	int			entityList[MAX_GENTITIES];
1139 	int			numListedEntities;
1140 	vec3_t		mins, maxs;
1141 	vec3_t		v;
1142 	vec3_t		dir;
1143 	int			i, e;
1144 	qboolean	hitClient = qfalse;
1145 
1146 	if ( radius < 1 ) {
1147 		radius = 1;
1148 	}
1149 
1150 	for ( i = 0 ; i < 3 ; i++ ) {
1151 		mins[i] = origin[i] - radius;
1152 		maxs[i] = origin[i] + radius;
1153 	}
1154 
1155 	numListedEntities = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
1156 
1157 	for ( e = 0 ; e < numListedEntities ; e++ ) {
1158 		ent = &g_entities[entityList[ e ]];
1159 
1160 		if (ent == ignore)
1161 			continue;
1162 		if (!ent->takedamage)
1163 			continue;
1164 
1165 		// find the distance from the edge of the bounding box
1166 		for ( i = 0 ; i < 3 ; i++ ) {
1167 			if ( origin[i] < ent->r.absmin[i] ) {
1168 				v[i] = ent->r.absmin[i] - origin[i];
1169 			} else if ( origin[i] > ent->r.absmax[i] ) {
1170 				v[i] = origin[i] - ent->r.absmax[i];
1171 			} else {
1172 				v[i] = 0;
1173 			}
1174 		}
1175 
1176 		dist = VectorLength( v );
1177 		if ( dist >= radius ) {
1178 			continue;
1179 		}
1180 
1181 		points = damage * ( 1.0 - dist / radius );
1182 
1183 		if( CanDamage (ent, origin) ) {
1184 			if( LogAccuracyHit( ent, attacker ) ) {
1185 				hitClient = qtrue;
1186 			}
1187 			VectorSubtract (ent->r.currentOrigin, origin, dir);
1188 			// push the center of mass higher than the origin so players
1189 			// get knocked into the air more
1190 			dir[2] += 24;
1191 			G_Damage (ent, NULL, attacker, dir, origin, (int)points, DAMAGE_RADIUS, mod);
1192 		}
1193 	}
1194 
1195 	return hitClient;
1196 }
1197