1 /*
2 ===========================================================================
3 
4 Return to Castle Wolfenstein single player GPL Source Code
5 Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company.
6 
7 This file is part of the Return to Castle Wolfenstein single player GPL Source Code (“RTCW SP Source Code”).
8 
9 RTCW SP Source Code is free software: you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation, either version 3 of the License, or
12 (at your option) any later version.
13 
14 RTCW SP Source Code is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 GNU General Public License for more details.
18 
19 You should have received a copy of the GNU General Public License
20 along with RTCW SP Source Code.  If not, see <http://www.gnu.org/licenses/>.
21 
22 In addition, the RTCW SP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW SP Source Code.  If not, please request a copy in writing from id Software at the address below.
23 
24 If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
25 
26 ===========================================================================
27 */
28 
29 /*
30  * name:		g_combat.c
31  *
32  * desc:
33  *
34 */
35 
36 #include "g_local.h"
37 
38 /*
39 ============
40 AddScore
41 
42 Adds score to both the client and his team
43 ============
44 */
AddScore(gentity_t * ent,int score)45 void AddScore( gentity_t *ent, int score ) {
46 	if ( !ent->client ) {
47 		return;
48 	}
49 	// no scoring during pre-match warmup
50 	if ( level.warmupTime ) {
51 		return;
52 	}
53 
54 	// Ridah, no scoring during single player
55 	// DHM - Nerve :: fix typo
56 	if ( g_gametype.integer == GT_SINGLE_PLAYER ) {
57 		return;
58 	}
59 	// done.
60 
61 
62 	ent->client->ps.persistant[PERS_SCORE] += score;
63 	if ( g_gametype.integer == GT_TEAM ) {
64 		level.teamScores[ ent->client->ps.persistant[PERS_TEAM] ] += score;
65 	}
66 	CalculateRanks();
67 }
68 
69 
70 
71 extern qboolean G_ThrowChair( gentity_t *ent, vec3_t dir, qboolean force );
72 
73 /*
74 =================
75 TossClientItems
76 
77 Toss the weapon and powerups for the killed player
78 =================
79 */
TossClientItems(gentity_t * self)80 void TossClientItems( gentity_t *self ) {
81 	gitem_t     *item;
82 	vec3_t forward;
83 	int weapon;
84 	float angle;
85 	int i;
86 	gentity_t   *drop = 0;
87 
88 	// drop the weapon if not a gauntlet or machinegun
89 	weapon = self->s.weapon;
90 
91 	switch ( self->aiCharacter ) {
92 	case AICHAR_ZOMBIE:
93 	case AICHAR_WARZOMBIE:
94 	case AICHAR_LOPER:
95 		return;         //----(SA)	removed DK's special case
96 	default:
97 		break;
98 	}
99 
100 	AngleVectors( self->r.currentAngles, forward, NULL, NULL );
101 
102 	G_ThrowChair( self, forward, qtrue ); // drop chair if you're holding one  //----(SA)	added
103 
104 	// make a special check to see if they are changing to a new
105 	// weapon that isn't the mg or gauntlet.  Without this, a client
106 	// can pick up a weapon, be killed, and not drop the weapon because
107 	// their weapon change hasn't completed yet and they are still holding the MG.
108 
109 // (SA) always drop what you were switching to
110 	if ( 1 ) {
111 //	if ( weapon == WP_MACHINEGUN || weapon == WP_GRAPPLING_HOOK ) {
112 		if ( self->client->ps.weaponstate == WEAPON_DROPPING || self->client->ps.weaponstate == WEAPON_DROPPING_TORELOAD ) {
113 			weapon = self->client->pers.cmd.weapon;
114 		}
115 		if ( !( COM_BitCheck( self->client->ps.weapons, weapon ) ) ) {
116 			weapon = WP_NONE;
117 		}
118 	}
119 
120 //----(SA)	added
121 	if ( weapon == WP_SNOOPERSCOPE ) {
122 		weapon = WP_GARAND;
123 	}
124 	if ( weapon == WP_FG42SCOPE ) {
125 		weapon = WP_FG42;
126 	}
127 	if ( weapon == WP_AKIMBO ) { //----(SA)	added
128 		weapon = WP_COLT;
129 	}
130 //----(SA)	end
131 
132 
133 	if ( weapon > WP_NONE && weapon < WP_MONSTER_ATTACK1 && self->client->ps.ammo[ BG_FindAmmoForWeapon( weapon )] ) {
134 		// find the item type for this weapon
135 		item = BG_FindItemForWeapon( weapon );
136 		// spawn the item
137 
138 		// Rafael
139 		if ( !( self->client->ps.persistant[PERS_HWEAPON_USE] ) ) {
140 			drop = Drop_Item( self, item, 0, qfalse );
141 		}
142 	}
143 
144 	if ( g_gametype.integer == GT_SINGLE_PLAYER ) {  // dropped items stay forever in SP
145 		if ( drop ) {
146 			drop->nextthink = 0;
147 		}
148 	}
149 
150 	if ( g_gametype.integer != GT_TEAM ) {  // drop all the powerups if not in teamplay
151 		angle = 45;
152 		for ( i = 1 ; i < PW_NUM_POWERUPS ; i++ ) {
153 			if ( self->client->ps.powerups[ i ] > level.time ) {
154 				item = BG_FindItemForPowerup( i );
155 				if ( !item ) {
156 					continue;
157 				}
158 				drop = Drop_Item( self, item, angle, qfalse );
159 				// decide how many seconds it has left
160 				drop->count = ( self->client->ps.powerups[ i ] - level.time ) / 1000;
161 				if ( drop->count < 1 ) {
162 					drop->count = 1;
163 				}
164 				drop->nextthink = 0;    // stay forever
165 				angle += 45;
166 			}
167 		}
168 	}
169 }
170 
171 
172 /*
173 ==================
174 LookAtKiller
175 ==================
176 */
LookAtKiller(gentity_t * self,gentity_t * inflictor,gentity_t * attacker)177 void LookAtKiller( gentity_t *self, gentity_t *inflictor, gentity_t *attacker ) {
178 	vec3_t dir;
179 
180 	if ( attacker && attacker != self ) {
181 		VectorSubtract( attacker->s.pos.trBase, self->s.pos.trBase, dir );
182 	} else if ( inflictor && inflictor != self ) {
183 		VectorSubtract( inflictor->s.pos.trBase, self->s.pos.trBase, dir );
184 	} else {
185 		self->client->ps.stats[STAT_DEAD_YAW] = self->s.angles[YAW];
186 		return;
187 	}
188 
189 	self->client->ps.stats[STAT_DEAD_YAW] = vectoyaw( dir );
190 }
191 
192 
193 /*
194 ==============
195 GibHead
196 ==============
197 */
GibHead(gentity_t * self,int killer)198 void GibHead( gentity_t *self, int killer ) {
199 	G_AddEvent( self, EV_GIB_HEAD, killer );
200 }
201 
202 /*
203 ==================
204 GibEntity
205 ==================
206 */
GibEntity(gentity_t * self,int killer)207 void GibEntity( gentity_t *self, int killer ) {
208 	gentity_t *other = &g_entities[killer];
209 	vec3_t dir;
210 
211 	VectorClear( dir );
212 	if ( other->inuse ) {
213 		if ( other->client ) {
214 			VectorSubtract( self->r.currentOrigin, other->r.currentOrigin, dir );
215 			VectorNormalize( dir );
216 		} else if ( !VectorCompare( other->s.pos.trDelta, vec3_origin ) ) {
217 			VectorNormalize2( other->s.pos.trDelta, dir );
218 		}
219 	}
220 
221 	G_AddEvent( self, EV_GIB_PLAYER, DirToByte( dir ) );
222 	self->takedamage = qfalse;
223 	self->s.eType = ET_INVISIBLE;
224 	self->r.contents = 0;
225 }
226 
227 /*
228 ==================
229 body_die
230 ==================
231 */
body_die(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,int damage,int meansOfDeath)232 void body_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath ) {
233 	if ( self->health > GIB_HEALTH ) {
234 		return;
235 	}
236 	if ( !g_blood.integer ) {
237 		self->health = GIB_HEALTH + 1;
238 		return;
239 	}
240 	if ( self->aiCharacter == AICHAR_HEINRICH || self->aiCharacter == AICHAR_HELGA || self->aiCharacter == AICHAR_SUPERSOLDIER || self->aiCharacter == AICHAR_PROTOSOLDIER ) {
241 		if ( self->health <= GIB_HEALTH ) {
242 			self->health = -1;
243 			return;
244 		}
245 	}
246 
247 	GibEntity( self, 0 );
248 }
249 
250 
251 // these are just for logging, the client prints its own messages
252 char    *modNames[] = {
253 	"MOD_UNKNOWN",
254 	"MOD_SHOTGUN",
255 	"MOD_GAUNTLET",
256 	"MOD_MACHINEGUN",
257 	"MOD_GRENADE",
258 	"MOD_GRENADE_SPLASH",
259 	"MOD_ROCKET",
260 	"MOD_ROCKET_SPLASH",
261 	"MOD_RAILGUN",
262 	"MOD_LIGHTNING",
263 	"MOD_BFG",
264 	"MOD_BFG_SPLASH",
265 	"MOD_KNIFE",
266 	"MOD_KNIFE2",
267 	"MOD_KNIFE_STEALTH",
268 	"MOD_LUGER",
269 	"MOD_COLT",
270 	"MOD_MP40",
271 	"MOD_THOMPSON",
272 	"MOD_STEN",
273 	"MOD_MAUSER",
274 	"MOD_SNIPERRIFLE",
275 	"MOD_GARAND",
276 	"MOD_SNOOPERSCOPE",
277 	"MOD_SILENCER", //----(SA)
278 	"MOD_AKIMBO",    //----(SA)
279 	"MOD_BAR",   //----(SA)
280 	"MOD_FG42",
281 	"MOD_FG42SCOPE",
282 	"MOD_PANZERFAUST",
283 	"MOD_ROCKET_LAUNCHER",
284 	"MOD_GRENADE_LAUNCHER",
285 	"MOD_VENOM",
286 	"MOD_VENOM_FULL",
287 	"MOD_FLAMETHROWER",
288 	"MOD_TESLA",
289 	"MOD_SPEARGUN",
290 	"MOD_SPEARGUN_CO2",
291 	"MOD_GRENADE_PINEAPPLE",
292 	"MOD_CROSS",
293 	"MOD_MORTAR",
294 	"MOD_MORTAR_SPLASH",
295 	"MOD_KICKED",
296 	"MOD_GRABBER",
297 	"MOD_DYNAMITE",
298 	"MOD_DYNAMITE_SPLASH",
299 	"MOD_AIRSTRIKE", // JPW NERVE
300 	"MOD_WATER",
301 	"MOD_SLIME",
302 	"MOD_LAVA",
303 	"MOD_CRUSH",
304 	"MOD_TELEFRAG",
305 	"MOD_FALLING",
306 	"MOD_SUICIDE",
307 	"MOD_TARGET_LASER",
308 	"MOD_TRIGGER_HURT",
309 	"MOD_GRAPPLE",
310 	"MOD_EXPLOSIVE",
311 	"MOD_POISONGAS",
312 	"MOD_ZOMBIESPIT",
313 	"MOD_ZOMBIESPIT_SPLASH",
314 	"MOD_ZOMBIESPIRIT",
315 	"MOD_ZOMBIESPIRIT_SPLASH",
316 	"MOD_LOPER_LEAP",
317 	"MOD_LOPER_GROUND",
318 	"MOD_LOPER_HIT",
319 // JPW NERVE
320 	"MOD_LT_ARTILLERY",
321 	"MOD_LT_AIRSTRIKE",
322 	"MOD_ENGINEER",  // not sure if we'll use
323 	"MOD_MEDIC",     // these like this or not
324 // jpw
325 	"MOD_BAT"
326 };
327 
328 /*
329 ==================
330 player_die
331 ==================
332 */
player_die(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,int damage,int meansOfDeath)333 void player_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath ) {
334 	gentity_t   *ent;
335 	int anim;
336 	int contents = 0;
337 	int killer;
338 	int i;
339 	char        *killerName, *obit;
340 	qboolean nogib = qtrue;
341 	gitem_t     *item = NULL; // JPW NERVE for flag drop
342 	vec3_t launchvel;      // JPW NERVE
343 	gentity_t   *flag; // JPW NERVE
344 
345 	if ( self->client->ps.pm_type == PM_DEAD ) {
346 		return;
347 	}
348 
349 	if ( level.intermissiontime ) {
350 		return;
351 	}
352 
353 //----(SA) commented out as we have no hook
354 //	if (self->client && self->client->hook)
355 //		Weapon_HookFree(self->client->hook);
356 
357 	self->client->ps.pm_type = PM_DEAD;
358 
359 	if ( attacker ) {
360 		killer = attacker->s.number;
361 		if ( attacker->client ) {
362 			killerName = attacker->client->pers.netname;
363 		} else {
364 			killerName = "<non-client>";
365 		}
366 	} else {
367 		killer = ENTITYNUM_WORLD;
368 		killerName = "<world>";
369 	}
370 
371 	if ( killer < 0 || killer >= MAX_CLIENTS ) {
372 		killer = ENTITYNUM_WORLD;
373 		killerName = "<world>";
374 	}
375 
376 	if ( meansOfDeath < 0 || meansOfDeath >= ARRAY_LEN( modNames ) ) {
377 		obit = "<bad obituary>";
378 	} else {
379 		obit = modNames[ meansOfDeath ];
380 	}
381 
382 	G_LogPrintf( "Kill: %i %i %i: %s killed %s by %s\n",
383 				 killer, self->s.number, meansOfDeath, killerName,
384 				 self->client->pers.netname, obit );
385 
386 	// broadcast the death event to everyone
387 	ent = G_TempEntity( self->r.currentOrigin, EV_OBITUARY );
388 	ent->s.eventParm = meansOfDeath;
389 	ent->s.otherEntityNum = self->s.number;
390 	ent->s.otherEntityNum2 = killer;
391 	ent->r.svFlags = SVF_BROADCAST; // send to everyone
392 
393 	self->enemy = attacker;
394 
395 	self->client->ps.persistant[PERS_KILLED]++;
396 
397 	if ( attacker && attacker->client ) {
398 		if ( attacker == self || OnSameTeam( self, attacker ) ) {
399 			AddScore( attacker, -1 );
400 		} else {
401 			AddScore( attacker, 1 );
402 
403 			// Ridah, not in single player
404 			if ( g_gametype.integer != GT_SINGLE_PLAYER ) {
405 				// done.
406 				if ( meansOfDeath == MOD_GAUNTLET ) {
407 					attacker->client->ps.persistant[PERS_GAUNTLET_FRAG_COUNT]++;
408 					attacker->client->ps.persistant[PERS_REWARD] = REWARD_GAUNTLET;
409 					attacker->client->ps.persistant[PERS_REWARD_COUNT]++;
410 
411 					// add the sprite over the player's head
412 //					attacker->client->ps.eFlags &= ~(EF_AWARD_IMPRESSIVE | EF_AWARD_EXCELLENT /*| EF_AWARD_GAUNTLET*/ );
413 					//attacker->client->ps.eFlags |= EF_AWARD_GAUNTLET;
414 					attacker->client->rewardTime = level.time + REWARD_SPRITE_TIME;
415 
416 					// also play humiliation on target
417 					self->client->ps.persistant[PERS_REWARD] = REWARD_GAUNTLET;
418 					self->client->ps.persistant[PERS_REWARD_COUNT]++;
419 				}
420 
421 				// check for two kills in a short amount of time
422 				// if this is close enough to the last kill, give a reward sound
423 				if ( level.time - attacker->client->lastKillTime < CARNAGE_REWARD_TIME ) {
424 					attacker->client->ps.persistant[PERS_REWARD_COUNT]++;
425 					attacker->client->ps.persistant[PERS_REWARD] = REWARD_EXCELLENT;
426 					attacker->client->ps.persistant[PERS_EXCELLENT_COUNT]++;
427 
428 					// add the sprite over the player's head
429 //					attacker->client->ps.eFlags &= ~(EF_AWARD_IMPRESSIVE | EF_AWARD_EXCELLENT /*| EF_AWARD_GAUNTLET*/ );
430 //					attacker->client->ps.eFlags |= EF_AWARD_EXCELLENT;
431 					attacker->client->rewardTime = level.time + REWARD_SPRITE_TIME;
432 				}
433 				// Ridah
434 			}
435 			// done.
436 			attacker->client->lastKillTime = level.time;
437 		}
438 	} else {
439 		AddScore( self, -1 );
440 	}
441 
442 	// Add team bonuses
443 	Team_FragBonuses( self, inflictor, attacker );
444 
445 	// if client is in a nodrop area, don't drop anything
446 // JPW NERVE new drop behavior
447 	if ( g_gametype.integer == GT_SINGLE_PLAYER ) {   // only drop here in single player; in multiplayer, drop @ limbo
448 		contents = trap_PointContents( self->r.currentOrigin, -1 );
449 		if ( !( contents & CONTENTS_NODROP ) ) {
450 			TossClientItems( self );
451 		}
452 	}
453 
454 	// drop flag regardless
455 	if ( g_gametype.integer != GT_SINGLE_PLAYER ) {
456 		if ( self->client->ps.powerups[PW_REDFLAG] ) {
457 			item = BG_FindItem( "Red Flag" );
458 		}
459 		if ( self->client->ps.powerups[PW_BLUEFLAG] ) {
460 			item = BG_FindItem( "Blue Flag" );
461 		}
462 		launchvel[0] = crandom() * 20;
463 		launchvel[1] = crandom() * 20;
464 		launchvel[2] = 10 + random() * 10;
465 		if ( item ) {
466 			flag = LaunchItem( item,self->r.currentOrigin,launchvel );
467 			flag->s.modelindex2 = self->s.otherEntityNum2; // JPW NERVE FIXME set player->otherentitynum2 with old modelindex2 from flag and restore here
468 		}
469 	}
470 // jpw
471 
472 	Cmd_Score_f( self );        // show scores
473 	// send updated scores to any clients that are following this one,
474 	// or they would get stale scoreboards
475 	for ( i = 0 ; i < level.maxclients ; i++ ) {
476 		gclient_t   *client;
477 
478 		client = &level.clients[i];
479 		if ( client->pers.connected != CON_CONNECTED ) {
480 			continue;
481 		}
482 		if ( client->sess.sessionTeam != TEAM_SPECTATOR ) {
483 			continue;
484 		}
485 		if ( client->sess.spectatorClient == self->s.number ) {
486 			Cmd_Score_f( g_entities + i );
487 		}
488 	}
489 
490 	self->takedamage = qtrue;   // can still be gibbed
491 
492 	self->s.powerups = 0;
493 // JPW NERVE -- only corpse in SP; in MP, need CONTENTS_BODY so medic can operate
494 	if ( g_gametype.integer == GT_SINGLE_PLAYER ) {
495 		self->r.contents = CONTENTS_CORPSE;
496 		self->s.weapon = WP_NONE;
497 	} else {
498 		self->client->limboDropWeapon = self->s.weapon; // store this so it can be dropped in limbo
499 	}
500 // jpw
501 	self->s.angles[0] = 0;
502 	self->s.angles[2] = 0;
503 	LookAtKiller( self, inflictor, attacker );
504 
505 	VectorCopy( self->s.angles, self->client->ps.viewangles );
506 
507 	self->s.loopSound = 0;
508 
509 	self->r.maxs[2] = -8;
510 
511 	// don't allow respawn until the death anim is done
512 	// g_forcerespawn may force spawning at some later time
513 	self->client->respawnTime = level.time + 1700;
514 
515 	// remove powerups
516 	memset( self->client->ps.powerups, 0, sizeof( self->client->ps.powerups ) );
517 
518 	if ( g_gametype.integer == GT_SINGLE_PLAYER ) {
519 		trap_SendServerCommand( -1, "mu_play sound/music/l_failed_1.wav 0\n" );
520 		trap_SetConfigstring( CS_MUSIC_QUEUE, "" );  // clear queue so it'll be quiet after hit
521 		trap_SendServerCommand( -1, "cp missionfail0" );
522 	}
523 
524 
525 	// never gib in a nodrop
526 	contents = trap_PointContents( self->r.currentOrigin, -1 );
527 
528 	if ( self->health <= GIB_HEALTH && !( contents & CONTENTS_NODROP ) && g_blood.integer ) {
529 //		if(self->client->ps.eFlags & EF_HEADSHOT)
530 //		{
531 //			GibHead(self, killer);
532 //		}
533 //		else	// gib death
534 //		{
535 		GibEntity( self, killer );
536 		nogib = qfalse;
537 //		}
538 	}
539 
540 	if ( nogib ) {
541 		// normal death
542 		static int i;
543 
544 		switch ( i ) {
545 		case 0:
546 			anim = BOTH_DEATH1;
547 			break;
548 		case 1:
549 			anim = BOTH_DEATH2;
550 			break;
551 		case 2:
552 		default:
553 			anim = BOTH_DEATH3;
554 			break;
555 		}
556 
557 		// for the no-blood option, we need to prevent the health
558 		// from going to gib level
559 		if ( self->health <= GIB_HEALTH ) {
560 			self->health = GIB_HEALTH + 1;
561 		}
562 
563 // JPW NERVE for medic
564 		self->client->medicHealAmt = 0;
565 // jpw
566 
567 		self->client->ps.legsAnim =
568 			( ( self->client->ps.legsAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim;
569 		self->client->ps.torsoAnim =
570 			( ( self->client->ps.torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim;
571 
572 		G_AddEvent( self, EV_DEATH1 + 1, killer );
573 
574 		// the body can still be gibbed
575 		self->die = body_die;
576 
577 		// globally cycle through the different death animations
578 		i = ( i + 1 ) % 3;
579 	}
580 
581 	trap_LinkEntity( self );
582 
583 	if ( g_gametype.integer == GT_SINGLE_PLAYER ) {
584 		AICast_ScriptEvent( AICast_GetCastState( self->s.number ), "death", "" );
585 	}
586 }
587 
588 
589 /*
590 ================
591 CheckArmor
592 ================
593 */
CheckArmor(gentity_t * ent,int damage,int dflags)594 int CheckArmor( gentity_t *ent, int damage, int dflags ) {
595 	gclient_t   *client;
596 	int save;
597 	int count;
598 
599 	if ( !damage ) {
600 		return 0;
601 	}
602 
603 	client = ent->client;
604 
605 	if ( !client ) {
606 		return 0;
607 	}
608 
609 	if ( dflags & DAMAGE_NO_ARMOR ) {
610 		return 0;
611 	}
612 
613 	// armor
614 	count = client->ps.stats[STAT_ARMOR];
615 	save = ceil( damage * ARMOR_PROTECTION );
616 	if ( save >= count ) {
617 		save = count;
618 	}
619 
620 	if ( !save ) {
621 		return 0;
622 	}
623 
624 	client->ps.stats[STAT_ARMOR] -= save;
625 
626 	return save;
627 }
628 
629 
630 /*
631 ==============
632 IsHeadShotWeapon
633 ==============
634 */
IsHeadShotWeapon(int mod,gentity_t * targ,gentity_t * attacker)635 qboolean IsHeadShotWeapon( int mod, gentity_t *targ, gentity_t *attacker ) {
636 	// distance rejection
637 	if ( DistanceSquared( targ->r.currentOrigin, attacker->r.currentOrigin )  >  ( g_headshotMaxDist.integer * g_headshotMaxDist.integer ) ) {
638 		return qfalse;
639 	}
640 
641 	if ( attacker->aiCharacter ) {
642 		// ai's are always allowed headshots from these weapons
643 		if ( mod == MOD_SNIPERRIFLE ||
644 			 mod == MOD_SNOOPERSCOPE ) {
645 			return qtrue;
646 		}
647 
648 		if ( g_gameskill.integer != GSKILL_MAX ) {
649 			// ai's allowed headshots in skill==GSKILL_MAX
650 			return qfalse;
651 		}
652 	}
653 
654 	switch ( targ->aiCharacter ) {
655 		// get out quick for ai's that don't take headshots
656 	case AICHAR_ZOMBIE:
657 	case AICHAR_WARZOMBIE:
658 	case AICHAR_HELGA:      // boss1 (beast)
659 	case AICHAR_LOPER:
660 	case AICHAR_VENOM:      //----(SA)	added
661 		return qfalse;
662 	default:
663 		break;
664 	}
665 
666 	switch ( mod ) {
667 		// players are allowed headshots from these weapons
668 	case MOD_LUGER:
669 	case MOD_COLT:
670 	case MOD_AKIMBO:
671 	case MOD_MP40:
672 	case MOD_THOMPSON:
673 	case MOD_STEN:
674 	case MOD_BAR:
675 	case MOD_FG42:
676 	case MOD_MAUSER:
677 	case MOD_GARAND:
678 	case MOD_SILENCER:
679 	case MOD_FG42SCOPE:
680 	case MOD_SNOOPERSCOPE:
681 	case MOD_SNIPERRIFLE:
682 		return qtrue;
683 	}
684 
685 	return qfalse;
686 }
687 
688 
689 /*
690 ==============
691 IsHeadShot
692 ==============
693 */
IsHeadShot(gentity_t * targ,gentity_t * attacker,vec3_t dir,vec3_t point,int mod)694 qboolean IsHeadShot( gentity_t *targ, gentity_t *attacker, vec3_t dir, vec3_t point, int mod ) {
695 	gentity_t   *head;
696 	trace_t tr;
697 	vec3_t start, end;
698 	gentity_t   *traceEnt;
699 	orientation_t or;
700 
701 	qboolean head_shot_weapon = qfalse;
702 
703 	// not a player or critter so bail
704 	if ( !( targ->client ) ) {
705 		return qfalse;
706 	}
707 
708 	if ( targ->health <= 0 ) {
709 		return qfalse;
710 	}
711 
712 	head_shot_weapon = IsHeadShotWeapon( mod, targ, attacker );
713 
714 	if ( head_shot_weapon ) {
715 		head = G_Spawn();
716 
717 		G_SetOrigin( head, targ->r.currentOrigin );
718 
719 		// RF, if there is a valid tag_head for this entity, then use that
720 		if ( ( targ->r.svFlags & SVF_CASTAI ) && trap_GetTag( targ->s.number, "tag_head", &or ) ) {
721 			VectorCopy( or.origin, head->r.currentOrigin );
722 			VectorMA( head->r.currentOrigin, 6, or.axis[2], head->r.currentOrigin );    // tag is at base of neck
723 		} else if ( targ->client->ps.pm_flags & PMF_DUCKED ) { // closer fake offset for 'head' box when crouching
724 			head->r.currentOrigin[2] += targ->client->ps.crouchViewHeight + 8; // JPW NERVE 16 is kludge to get head height to match up
725 		}
726 		//else if(targ->client->ps.legsAnim == LEGS_IDLE && targ->aiCharacter == AICHAR_SOLDIER)	// standing with legs bent (about a head shorter than other leg poses)
727 		//	head->r.currentOrigin[2] += targ->client->ps.viewheight;
728 		else {
729 			head->r.currentOrigin[2] += targ->client->ps.viewheight; // JPW NERVE pulled this	// 6 is fudged "head height" value
730 
731 		}
732 		VectorCopy( head->r.currentOrigin, head->s.origin );
733 		VectorCopy( targ->r.currentAngles, head->s.angles );
734 		VectorCopy( head->s.angles, head->s.apos.trBase );
735 		VectorCopy( head->s.angles, head->s.apos.trDelta );
736 		VectorSet( head->r.mins, -6, -6, -6 ); // JPW NERVE changed this z from -12 to -6 for crouching, also removed standing offset
737 		VectorSet( head->r.maxs, 6, 6, 6 ); // changed this z from 0 to 6
738 		head->clipmask = CONTENTS_SOLID;
739 		head->r.contents = CONTENTS_SOLID;
740 
741 		trap_LinkEntity( head );
742 
743 		// trace another shot see if we hit the head
744 		VectorCopy( point, start );
745 		VectorMA( start, 64, dir, end );
746 		trap_Trace( &tr, start, NULL, NULL, end, targ->s.number, MASK_SHOT );
747 
748 		traceEnt = &g_entities[ tr.entityNum ];
749 
750 		if ( g_debugBullets.integer >= 3 ) {   // show hit player head bb
751 			gentity_t *tent;
752 			vec3_t b1, b2;
753 			VectorCopy( head->r.currentOrigin, b1 );
754 			VectorCopy( head->r.currentOrigin, b2 );
755 			VectorAdd( b1, head->r.mins, b1 );
756 			VectorAdd( b2, head->r.maxs, b2 );
757 			tent = G_TempEntity( b1, EV_RAILTRAIL );
758 			VectorCopy( b2, tent->s.origin2 );
759 			tent->s.dmgFlags = 1;
760 
761 			// show headshot trace
762 			// end the headshot trace at the head box if it hits
763 			if ( tr.fraction != 1 ) {
764 				VectorMA( start, ( tr.fraction * 64 ), dir, end );
765 			}
766 			tent = G_TempEntity( start, EV_RAILTRAIL );
767 			VectorCopy( end, tent->s.origin2 );
768 			tent->s.dmgFlags = 0;
769 		}
770 
771 		G_FreeEntity( head );
772 
773 		if ( traceEnt == head ) {
774 			return qtrue;
775 		}
776 	}
777 
778 	return qfalse;
779 }
780 
781 
782 
783 
784 
785 /*
786 ==============
787 G_ArmorDamage
788 	brokeparts is how many should be broken off now
789 	curbroke is how many are broken
790 	the difference is how many to pop off this time
791 ==============
792 */
G_ArmorDamage(gentity_t * targ)793 void G_ArmorDamage( gentity_t *targ ) {
794 	int brokeparts, curbroke;
795 	int numParts;
796 	int dmgbits = 16;   // 32/2;
797 	int i;
798 
799 	if ( !targ->client ) {
800 		return;
801 	}
802 
803 	if ( targ->s.aiChar == AICHAR_PROTOSOLDIER ) {
804 		numParts = 9;
805 	} else if ( targ->s.aiChar == AICHAR_SUPERSOLDIER ) {
806 		numParts = 14;
807 	} else if ( targ->s.aiChar == AICHAR_HEINRICH ) {
808 		numParts = 20;
809 	} else {
810 		return;
811 	}
812 
813 	if ( numParts > dmgbits ) {
814 		numParts = dmgbits; // lock this down so it doesn't overwrite any bits that it shouldn't.  TODO: fix this
815 
816 
817 	}
818 	// determined here (on server) by location of hit and existing armor, you're updating here so
819 	// the client knows which pieces are still in place, and by difference with previous state, which
820 	// pieces to play an effect where the part is blown off.
821 	// Need to do it here so we have info on where the hit registered (head, torso, legs or if we go with more detail; arm, leg, chest, codpiece, etc)
822 
823 	// ... Ick, just discovered that the refined hit detection ("hit nearest to which tag") is clientside...
824 
825 	// For now, I'll randomly pick a part that hasn't been cleared.  This might end up looking okay, and we won't need the refined hits.
826 	//	however, we still have control on the server-side of which parts come off, regardless of what shceme is used.
827 
828 	brokeparts = (int)( ( 1 - ( (float)( targ->health ) / (float)( targ->client->ps.stats[STAT_MAX_HEALTH] ) ) ) * numParts );
829 
830 	// RF, remove flame protection after enough parts gone
831 	if ( AICast_NoFlameDamage( targ->s.number ) && ( (float)brokeparts / (float)numParts >= 5.0 / 6.0 ) ) { // figure from DM
832 		AICast_SetFlameDamage( targ->s.number, qfalse );
833 	}
834 
835 	if ( brokeparts && ( ( targ->s.dmgFlags & ( ( 1 << numParts ) - 1 ) ) != ( 1 << numParts ) - 1 ) ) {   // there are still parts left to clear
836 
837 		// how many are removed already?
838 		curbroke = 0;
839 		for ( i = 0; i < numParts; i++ ) {
840 			if ( targ->s.dmgFlags & ( 1 << i ) ) {
841 				curbroke++;
842 			}
843 		}
844 
845 		// need to remove more
846 		if ( brokeparts - curbroke >= 1 && curbroke < numParts ) {
847 			for ( i = 0; i < ( brokeparts - curbroke ); i++ ) {
848 				int remove = rand() % ( numParts );
849 
850 				if ( !( ( targ->s.dmgFlags & ( ( 1 << numParts ) - 1 ) ) != ( 1 << numParts ) - 1 ) ) { // no parts are available any more
851 					break;
852 				}
853 
854 				// FIXME: lose the 'while' loop.  Still should be safe though, since the check above verifies that it will eventually find a valid part
855 				while ( targ->s.dmgFlags & ( 1 << remove ) ) {
856 					remove = rand() % ( numParts );
857 				}
858 
859 				targ->s.dmgFlags |= ( 1 << remove );    // turn off 'undamaged' part
860 				if ( (int)( random() + 0.5 ) ) {                       // choose one of two possible replacements
861 					targ->s.dmgFlags |= ( 1 << ( numParts + remove ) );
862 				}
863 			}
864 		}
865 	}
866 }
867 /*
868 ============
869 G_Damage
870 
871 targ		entity that is being damaged
872 inflictor	entity that is causing the damage
873 attacker	entity that caused the inflictor to damage targ
874 	example: targ=monster, inflictor=rocket, attacker=player
875 
876 dir			direction of the attack for knockback
877 point		point at which the damage is being inflicted, used for headshots
878 damage		amount of damage being inflicted
879 knockback	force to be applied against targ as a result of the damage
880 
881 inflictor, attacker, dir, and point can be NULL for environmental effects
882 
883 dflags		these flags are used to control how T_Damage works
884 	DAMAGE_RADIUS			damage was indirect (from a nearby explosion)
885 	DAMAGE_NO_ARMOR			armor does not protect from this damage
886 	DAMAGE_NO_KNOCKBACK		do not affect velocity, just view angles
887 	DAMAGE_NO_PROTECTION	kills godmode, armor, everything
888 ============
889 */
890 
G_Damage(gentity_t * targ,gentity_t * inflictor,gentity_t * attacker,vec3_t dir,vec3_t point,int damage,int dflags,int mod)891 void G_Damage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker,
892 			   vec3_t dir, vec3_t point, int damage, int dflags, int mod ) {
893 	gclient_t   *client;
894 	int take;
895 	int asave;
896 	int knockback;
897 
898 	if ( !targ->takedamage ) {
899 		return;
900 	}
901 
902 //----(SA)	added
903 	if ( g_gametype.integer == GT_SINGLE_PLAYER && !targ->aiCharacter && targ->client && targ->client->cameraPortal ) {
904 		// get out of damage in sp if in cutscene.
905 		return;
906 	}
907 //----(SA)	end
908 
909 //	if (reloading || saveGamePending) {	// map transition is happening, don't do anything
910 	if ( g_reloading.integer || saveGamePending ) {
911 		return;
912 	}
913 
914 	// the intermission has already been qualified for, so don't
915 	// allow any extra scoring
916 	if ( level.intermissionQueued ) {
917 		return;
918 	}
919 
920 	// RF, track pain for player
921 	// This is used by AI to determine how long it has been since their enemy was injured
922 
923 	if ( attacker ) { // (SA) whoops, for falling damage there's no attacker
924 		if ( targ->client && attacker->client && !( targ->r.svFlags & SVF_CASTAI ) && ( attacker->r.svFlags & SVF_CASTAI ) ) {
925 			AICast_RegisterPain( targ->s.number );
926 		}
927 	}
928 
929 	if ( ( g_gametype.integer == GT_SINGLE_PLAYER ) && !( targ->r.svFlags & SVF_CASTAI ) ) { // the player
930 		switch ( mod )
931 		{
932 		case MOD_GRENADE:
933 		case MOD_GRENADE_SPLASH:
934 		case MOD_ROCKET:
935 		case MOD_ROCKET_SPLASH:
936 			// Rafael - had to change this since the
937 			// we added a new lvl of diff
938 			if ( g_gameskill.integer == GSKILL_EASY ) {
939 				damage *= 0.25;
940 			} else if ( g_gameskill.integer == GSKILL_MEDIUM ) {
941 				damage *= 0.75;
942 			} else if ( g_gameskill.integer == GSKILL_HARD ) {
943 				damage *= 0.9;
944 			} else {
945 				damage *= 0.9;
946 			}
947 		default:
948 			break;
949 		}
950 	}
951 
952 	if ( !inflictor ) {
953 		inflictor = &g_entities[ENTITYNUM_WORLD];
954 	}
955 	if ( !attacker ) {
956 		attacker = &g_entities[ENTITYNUM_WORLD];
957 	}
958 
959 	// shootable doors / buttons don't actually have any health
960 	if ( targ->s.eType == ET_MOVER && !( targ->aiName ) && !( targ->isProp ) && !targ->scriptName ) {
961 		if ( targ->use && targ->moverState == MOVER_POS1 ) {
962 			targ->use( targ, inflictor, attacker );
963 		}
964 		return;
965 	}
966 
967 	if ( targ->s.eType == ET_MOVER && targ->aiName && !( targ->isProp ) && !targ->scriptName ) {
968 		switch ( mod ) {
969 		case MOD_GRENADE:
970 		case MOD_GRENADE_SPLASH:
971 		case MOD_ROCKET:
972 		case MOD_ROCKET_SPLASH:
973 			break;
974 		default:
975 			return; // no damage from other weapons
976 		}
977 	} else if ( targ->s.eType == ET_EXPLOSIVE )   {
978 		// 32 Explosive
979 		// 64 Dynamite only
980 		if ( ( targ->spawnflags & 32 ) || ( targ->spawnflags & 64 ) ) {
981 			switch ( mod ) {
982 			case MOD_GRENADE:
983 			case MOD_GRENADE_SPLASH:
984 			case MOD_ROCKET:
985 			case MOD_ROCKET_SPLASH:
986 			case MOD_AIRSTRIKE:
987 			case MOD_GRENADE_PINEAPPLE:
988 			case MOD_MORTAR:
989 			case MOD_MORTAR_SPLASH:
990 			case MOD_EXPLOSIVE:
991 				if ( targ->spawnflags & 64 ) {
992 					return;
993 				}
994 
995 				break;
996 
997 			case MOD_DYNAMITE:
998 			case MOD_DYNAMITE_SPLASH:
999 				break;
1000 
1001 			default:
1002 				return;
1003 			}
1004 		}
1005 	}
1006 
1007 	// reduce damage by the attacker's handicap value
1008 	// unless they are rocket jumping
1009 
1010 	// Ridah, not in single player (skill levels?)
1011 // JPW NERVE pulled this from multiplayer too
1012 /*
1013 	if (g_gametype.integer != GT_SINGLE_PLAYER)
1014 	// done.
1015 	if ( attacker->client && attacker != targ ) {
1016 		damage = damage * attacker->client->ps.stats[STAT_MAX_HEALTH] / 100;
1017 	}
1018 */
1019 // jpw
1020 
1021 	// Ridah, Cast AI's don't hurt other Cast AI's as much
1022 	if ( ( attacker->r.svFlags & SVF_CASTAI ) && ( targ->r.svFlags & SVF_CASTAI ) ) {
1023 		if ( !AICast_AIDamageOK( AICast_GetCastState( targ->s.number ), AICast_GetCastState( attacker->s.number ) ) ) {
1024 			return;
1025 		}
1026 		damage = (int)( ceil( (float)damage * 0.5 ) );
1027 	}
1028 	// done.
1029 
1030 	client = targ->client;
1031 
1032 	if ( client ) {
1033 		if ( client->noclip ) {
1034 			return;
1035 		}
1036 	}
1037 
1038 	if ( !dir ) {
1039 		dflags |= DAMAGE_NO_KNOCKBACK;
1040 	} else {
1041 		VectorNormalize( dir );
1042 	}
1043 
1044 	knockback = damage;
1045 
1046 //	if ( knockback > 200 )
1047 //		knockback = 200;
1048 	if ( knockback > 60 ) { // /way/ lessened for SP.  keeps dynamite-jumping potential down
1049 		knockback = 60;
1050 	}
1051 
1052 	if ( targ->flags & FL_NO_KNOCKBACK ) {
1053 		knockback = 0;
1054 	}
1055 	if ( dflags & DAMAGE_NO_KNOCKBACK ) {
1056 		knockback = 0;
1057 	}
1058 
1059 	// figure momentum add, even if the damage won't be taken
1060 	if ( knockback && targ->client ) {
1061 		vec3_t kvel;
1062 		float mass;
1063 
1064 		mass = 200;
1065 
1066 		if ( mod == MOD_LIGHTNING && !( ( level.time + targ->s.number * 50 ) % 400 ) ) {
1067 			knockback = 60;
1068 			dir[2] = 0.3;
1069 		}
1070 
1071 		VectorScale( dir, g_knockback.value * (float)knockback / mass, kvel );
1072 		VectorAdd( targ->client->ps.velocity, kvel, targ->client->ps.velocity );
1073 
1074 		if ( targ == attacker && !(  mod != MOD_ROCKET &&
1075 									 mod != MOD_ROCKET_SPLASH &&
1076 									 mod != MOD_GRENADE &&
1077 									 mod != MOD_GRENADE_SPLASH &&
1078 									 mod != MOD_DYNAMITE ) ) {
1079 			targ->client->ps.velocity[2] *= 0.25;
1080 		}
1081 
1082 		// set the timer so that the other client can't cancel
1083 		// out the movement immediately
1084 		if ( !targ->client->ps.pm_time ) {
1085 			int t;
1086 
1087 			t = knockback * 2;
1088 			if ( t < 50 ) {
1089 				t = 50;
1090 			}
1091 			if ( t > 200 ) {
1092 				t = 200;
1093 			}
1094 			targ->client->ps.pm_time = t;
1095 			targ->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
1096 		}
1097 	}
1098 
1099 	// check for completely getting out of the damage
1100 	if ( !( dflags & DAMAGE_NO_PROTECTION ) ) {
1101 
1102 		// if TF_NO_FRIENDLY_FIRE is set, don't do damage to the target
1103 		// if the attacker was on the same team
1104 		if ( targ != attacker && OnSameTeam( targ, attacker )  ) {
1105 			if ( !g_friendlyFire.integer ) {
1106 				return;
1107 			}
1108 		}
1109 
1110 		// check for godmode
1111 		if ( targ->flags & FL_GODMODE ) {
1112 			return;
1113 		}
1114 
1115 		// RF, warzombie defense position is basically godmode for the time being
1116 		if ( targ->flags & FL_DEFENSE_GUARD ) {
1117 			return;
1118 		}
1119 
1120 		// check for invulnerability // (SA) moved from below so DAMAGE_NO_PROTECTION will still work
1121 		if ( client && client->ps.powerups[PW_INVULNERABLE] ) { //----(SA)	added
1122 			return;
1123 		}
1124 
1125 	}
1126 
1127 	// battlesuit protects from all radius damage (but takes knockback)
1128 	// and protects 50% against all damage
1129 	if ( client && client->ps.powerups[PW_BATTLESUIT] ) {
1130 		G_AddEvent( targ, EV_POWERUP_BATTLESUIT, 0 );
1131 		if ( dflags & DAMAGE_RADIUS ) {
1132 			return;
1133 		}
1134 		damage *= 0.5;
1135 	}
1136 
1137 	// Ridah, don't play these in single player
1138 	if ( g_gametype.integer != GT_SINGLE_PLAYER ) {
1139 		// done.
1140 		// add to the attacker's hit counter
1141 		if ( attacker->client && client && targ != attacker && targ->health > 0 ) {
1142 			if ( OnSameTeam( targ, attacker ) ) {
1143 				attacker->client->ps.persistant[PERS_HITS] -= damage;
1144 			} else {
1145 				attacker->client->ps.persistant[PERS_HITS] += damage;
1146 			}
1147 		}
1148 	}
1149 
1150 	// always give half damage if hurting self
1151 	// calculated after knockback, so rocket jumping works
1152 	if ( g_gametype.integer == GT_SINGLE_PLAYER ) {     // JPW NERVE -- removed from multiplayer -- plays havoc with pfaust & demolition balancing
1153 
1154 		qboolean dynamite = (qboolean)( mod == MOD_DYNAMITE || mod == MOD_DYNAMITE_SPLASH );
1155 
1156 		if ( targ == attacker ) {
1157 			if ( !dynamite ) {
1158 				damage *= 0.5;
1159 			}
1160 		}
1161 
1162 		if ( dynamite && targ->aiCharacter == AICHAR_HELGA ) {
1163 			//helga gets special dynamite damage
1164 			damage *= 0.5;
1165 		}
1166 
1167 	}
1168 
1169 	if ( damage < 1 ) {
1170 		damage = 1;
1171 	}
1172 	take = damage;
1173 
1174 	// save some from armor
1175 	asave = CheckArmor( targ, take, dflags );
1176 	take -= asave;
1177 
1178 
1179 	if ( IsHeadShot( targ, attacker, dir, point, mod ) ) {
1180 		// JPW NERVE -- different headshot behavior in multiplayer
1181 		if ( g_gametype.integer != GT_SINGLE_PLAYER ) {
1182 			if ( take * 2 < 50 ) { // head shots, all weapons, do minimum 50 points damage
1183 				take = 50;
1184 			} else {
1185 				take *= 2; // sniper rifles can do full-kill (and knock into limbo)
1186 			}
1187 			if ( !( targ->client->ps.eFlags & EF_HEADSHOT ) ) {  // only toss hat on first headshot
1188 				G_AddEvent( targ, EV_LOSE_HAT, DirToByte( dir ) );
1189 			}
1190 		} // jpw
1191 		else {
1192 			// by default, a headshot means damage x2
1193 			take *= 2;
1194 
1195 			// RF, allow headshot damage multiplier (helmets, etc)
1196 			// yes, headshotDamageScale of 0 gives no damage, thats because
1197 			// the bullet hit the head which is fully protected.
1198 			take *= targ->headshotDamageScale;
1199 
1200 			// player only code
1201 			if ( !attacker->aiCharacter ) {
1202 				// (SA) id reqests one-shot kills for head shots on common humanoids
1203 
1204 				// (SA) except pistols.
1205 				// first pistol head shot does normal 2x damage and flings hat, second gets kill
1206 				//			if((mod != MOD_LUGER && mod != MOD_COLT ) || (targ->client->ps.eFlags & EF_HEADSHOT))	{	// (SA) DM requests removing double shot pistol head shots (3/19)
1207 
1208 				// (SA) removed BG for DM.
1209 
1210 				if ( !( dflags & DAMAGE_PASSTHRU ) ) {     // ignore headshot 2x damage and snooper-instant-death if the bullet passed through something.  just do reg damage.
1211 					switch ( targ->aiCharacter ) {
1212 					case AICHAR_BLACKGUARD:
1213 						if ( !( targ->client->ps.eFlags & EF_HEADSHOT ) ) { // only obliterate him after he's lost his helmet
1214 							break;
1215 						}
1216 					case AICHAR_SOLDIER:
1217 					case AICHAR_AMERICAN:
1218 					case AICHAR_ELITEGUARD:
1219 					case AICHAR_PARTISAN:
1220 					case AICHAR_CIVILIAN:
1221 						take = 200;
1222 						break;
1223 					default:
1224 						break;
1225 					}
1226 				}
1227 
1228 				if ( !( targ->client->ps.eFlags & EF_HEADSHOT ) ) {  // only toss hat on first headshot
1229 					G_AddEvent( targ, EV_LOSE_HAT, DirToByte( dir ) );
1230 				}
1231 			}
1232 		} // JPW
1233 
1234 		// shared by both player and ai
1235 		targ->client->ps.eFlags |= EF_HEADSHOT;
1236 
1237 	} else {    // non headshot
1238 
1239 		if ( !( dflags & DAMAGE_PASSTHRU ) ) {     // ignore headshot 2x damage and snooper-instant-death if the bullet passed through something.  just do reg damage.
1240 			// snooper kills these types in one shot with any contact
1241 			if ( ( mod == MOD_SNOOPERSCOPE || mod == MOD_GARAND ) && !( attacker->aiCharacter ) ) {
1242 				switch ( targ->aiCharacter ) {
1243 				case AICHAR_SOLDIER:
1244 				case AICHAR_AMERICAN:
1245 				case AICHAR_ELITEGUARD:
1246 				case AICHAR_BLACKGUARD:
1247 				case AICHAR_PARTISAN:
1248 				case AICHAR_CIVILIAN:
1249 					take = 200;
1250 					break;
1251 				default:
1252 					break;
1253 				}
1254 			}
1255 		}
1256 	}
1257 
1258 
1259 	if ( g_debugDamage.integer ) {
1260 		G_Printf( "client:%i health:%i damage:%i armor:%i\n", targ->s.number,
1261 				  targ->health, take, asave );
1262 	}
1263 
1264 	// add to the damage inflicted on a player this frame
1265 	// the total will be turned into screen blends and view angle kicks
1266 	// at the end of the frame
1267 	if ( client ) {
1268 		if ( attacker ) {
1269 			client->ps.persistant[PERS_ATTACKER] = attacker->s.number;
1270 		} else {
1271 			client->ps.persistant[PERS_ATTACKER] = ENTITYNUM_WORLD;
1272 		}
1273 		client->damage_armor += asave;
1274 		client->damage_blood += take;
1275 		client->damage_knockback += knockback;
1276 
1277 		if ( dir ) {
1278 			VectorCopy( dir, client->damage_from );
1279 			client->damage_fromWorld = qfalse;
1280 		} else {
1281 			VectorCopy( targ->r.currentOrigin, client->damage_from );
1282 			client->damage_fromWorld = qtrue;
1283 		}
1284 	}
1285 
1286 	// See if it's the player hurting the emeny flag carrier
1287 	Team_CheckHurtCarrier( targ, attacker );
1288 
1289 	if ( targ->client ) {
1290 		// set the last client who damaged the target
1291 		targ->client->lasthurt_client = attacker->s.number;
1292 		targ->client->lasthurt_mod = mod;
1293 	}
1294 
1295 	// do the damage
1296 	if ( take ) {
1297 		targ->health = targ->health - take;
1298 
1299 		// Ridah, can't gib with bullet weapons (except VENOM)
1300 		if ( targ->client ) {
1301 			if ( mod != MOD_VENOM && attacker == inflictor && targ->health <= GIB_HEALTH ) {
1302 				if ( targ->aiCharacter != AICHAR_ZOMBIE ) { // zombie needs to be able to gib so we can kill him (although he doesn't actually GIB, he just dies)
1303 					targ->health = GIB_HEALTH + 1;
1304 				}
1305 			}
1306 		}
1307 
1308 		//G_Printf("health at: %d\n", targ->health);
1309 		if ( targ->health <= 0 ) {
1310 			if ( client ) {
1311 				targ->flags |= FL_NO_KNOCKBACK;
1312 			}
1313 
1314 			if ( targ->health < -999 ) {
1315 				targ->health = -999;
1316 			}
1317 
1318 			targ->enemy = attacker;
1319 			if ( targ->die ) { // Ridah, mg42 doesn't have die func (FIXME)
1320 				targ->die( targ, inflictor, attacker, take, mod );
1321 			}
1322 
1323 			// if we freed ourselves in death function
1324 			if ( !targ->inuse ) {
1325 				return;
1326 			}
1327 
1328 			// RF, entity scripting
1329 			if ( targ->s.number >= MAX_CLIENTS && targ->health <= 0 ) { // might have revived itself in death function
1330 				G_Script_ScriptEvent( targ, "death", "" );
1331 			}
1332 
1333 		} else if ( targ->pain ) {
1334 			if ( dir ) {  // Ridah, had to add this to fix NULL dir crash
1335 				VectorCopy( dir, targ->rotate );
1336 				VectorCopy( point, targ->pos3 ); // this will pass loc of hit
1337 			} else {
1338 				VectorClear( targ->rotate );
1339 				VectorClear( targ->pos3 );
1340 			}
1341 
1342 			targ->pain( targ, attacker, take, point );
1343 		}
1344 
1345 		G_ArmorDamage( targ );    //----(SA)	moved out to separate routine
1346 
1347 		// Ridah, this needs to be done last, incase the health is altered in one of the event calls
1348 		if ( targ->client ) {
1349 			targ->client->ps.stats[STAT_HEALTH] = targ->health;
1350 		}
1351 	}
1352 
1353 }
1354 
1355 
1356 /*
1357 ============
1358 CanDamage
1359 
1360 Returns qtrue if the inflictor can directly damage the target.  Used for
1361 explosions and melee attacks.
1362 ============
1363 */
CanDamage(gentity_t * targ,vec3_t origin)1364 qboolean CanDamage( gentity_t *targ, vec3_t origin ) {
1365 	vec3_t dest;
1366 	trace_t tr;
1367 	vec3_t midpoint;
1368 	vec3_t	offsetmins = {-15, -15, -15};
1369 	vec3_t	offsetmaxs = {15, 15, 15};
1370 
1371 	// use the midpoint of the bounds instead of the origin, because
1372 	// bmodels may have their origin is 0,0,0
1373 	VectorAdd( targ->r.absmin, targ->r.absmax, midpoint );
1374 	VectorScale( midpoint, 0.5, midpoint );
1375 
1376 	VectorCopy(midpoint, dest);
1377 	trap_Trace(&tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID);
1378 
1379 	if ( tr.fraction == 1.0 ) {
1380 		return qtrue;
1381 	}
1382 
1383 
1384 
1385 	if ( &g_entities[tr.entityNum] == targ ) {
1386 		return qtrue;
1387 	}
1388 
1389 	// this should probably check in the plane of projection,
1390 	// rather than in world coordinate
1391 	VectorCopy(midpoint, dest);
1392 	dest[0] += offsetmaxs[0];
1393 	dest[1] += offsetmaxs[1];
1394 	dest[2] += offsetmaxs[2];
1395 	trap_Trace(&tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID);
1396 
1397 	if (tr.fraction == 1.0)
1398 		return qtrue;
1399 
1400 	VectorCopy(midpoint, dest);
1401 	dest[0] += offsetmaxs[0];
1402 	dest[1] += offsetmins[1];
1403 	dest[2] += offsetmaxs[2];
1404 	trap_Trace(&tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID);
1405 
1406 	if ( tr.fraction == 1.0 ) {
1407 		return qtrue;
1408 	}
1409 
1410 	VectorCopy(midpoint, dest);
1411 	dest[0] += offsetmins[0];
1412 	dest[1] += offsetmaxs[1];
1413 	dest[2] += offsetmaxs[2];
1414 	trap_Trace(&tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID);
1415 
1416 	if ( tr.fraction == 1.0 ) {
1417 		return qtrue;
1418 	}
1419 
1420 	VectorCopy(midpoint, dest);
1421 	dest[0] += offsetmins[0];
1422 	dest[1] += offsetmins[1];
1423 	dest[2] += offsetmaxs[2];
1424 	trap_Trace(&tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID);
1425 
1426 	if ( tr.fraction == 1.0 ) {
1427 		return qtrue;
1428 	}
1429 
1430 	VectorCopy(midpoint, dest);
1431 	dest[0] += offsetmaxs[0];
1432 	dest[1] += offsetmaxs[1];
1433 	dest[2] += offsetmins[2];
1434 	trap_Trace(&tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID);
1435 
1436 	if ( tr.fraction == 1.0 ) {
1437 		return qtrue;
1438 	}
1439 
1440 	VectorCopy(midpoint, dest);
1441 	dest[0] += offsetmaxs[0];
1442 	dest[1] += offsetmins[1];
1443 	dest[2] += offsetmins[2];
1444 	trap_Trace(&tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID);
1445 
1446 	if (tr.fraction == 1.0)
1447 		return qtrue;
1448 
1449 	VectorCopy(midpoint, dest);
1450 	dest[0] += offsetmins[0];
1451 	dest[1] += offsetmaxs[1];
1452 	dest[2] += offsetmins[2];
1453 	trap_Trace(&tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID);
1454 
1455 	if (tr.fraction == 1.0)
1456 		return qtrue;
1457 
1458 	VectorCopy(midpoint, dest);
1459 	dest[0] += offsetmins[0];
1460 	dest[1] += offsetmins[1];
1461 	dest[2] += offsetmins[2];
1462 	trap_Trace(&tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID);
1463 
1464 	if (tr.fraction == 1.0)
1465 		return qtrue;
1466 
1467 	return qfalse;
1468 }
1469 
1470 
1471 /*
1472 ============
1473 G_RadiusDamage
1474 ============
1475 */
G_RadiusDamage(vec3_t origin,gentity_t * attacker,float damage,float radius,gentity_t * ignore,int mod)1476 qboolean G_RadiusDamage( vec3_t origin, gentity_t *attacker, float damage, float radius,
1477 						 gentity_t *ignore, int mod ) {
1478 	float points, dist;
1479 	gentity_t   *ent;
1480 	int entityList[MAX_GENTITIES];
1481 	int numListedEntities;
1482 	vec3_t mins, maxs;
1483 	vec3_t v;
1484 	vec3_t dir;
1485 	int i, e;
1486 	qboolean hitClient = qfalse;
1487 // JPW NERVE
1488 	float boxradius;
1489 	vec3_t dest;
1490 	trace_t tr;
1491 	vec3_t midpoint;
1492 // jpw
1493 
1494 
1495 	if ( radius < 1 ) {
1496 		radius = 1;
1497 	}
1498 
1499 	boxradius = 1.41421356 * radius; // radius * sqrt(2) for bounding box enlargement --
1500 	// bounding box was checking against radius / sqrt(2) if collision is along box plane
1501 	for ( i = 0 ; i < 3 ; i++ ) {
1502 		mins[i] = origin[i] - boxradius; // JPW NERVE
1503 		maxs[i] = origin[i] + boxradius; // JPW NERVE
1504 	}
1505 
1506 	numListedEntities = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
1507 
1508 	for ( e = 0 ; e < numListedEntities ; e++ ) {
1509 		ent = &g_entities[entityList[ e ]];
1510 
1511 		if ( ent == ignore ) {
1512 			continue;
1513 		}
1514 		if ( !ent->takedamage ) {
1515 			continue;
1516 		}
1517 
1518 /* JPW NERVE -- we can put this back if we need to, but it kinna sucks for human-sized bboxes
1519 		// find the distance from the edge of the bounding box
1520 		for ( i = 0 ; i < 3 ; i++ ) {
1521 			if ( origin[i] < ent->r.absmin[i] ) {
1522 				v[i] = ent->r.absmin[i] - origin[i];
1523 			} else if ( origin[i] > ent->r.absmax[i] ) {
1524 				v[i] = origin[i] - ent->r.absmax[i];
1525 			} else {
1526 				v[i] = 0;
1527 			}
1528 		}
1529 */
1530 // JPW NERVE
1531 		if ( !ent->r.bmodel ) {
1532 			VectorSubtract( ent->r.currentOrigin,origin,v ); // JPW NERVE simpler centroid check that doesn't have box alignment weirdness
1533 		} else {
1534 			for ( i = 0 ; i < 3 ; i++ ) {
1535 				if ( origin[i] < ent->r.absmin[i] ) {
1536 					v[i] = ent->r.absmin[i] - origin[i];
1537 				} else if ( origin[i] > ent->r.absmax[i] ) {
1538 					v[i] = origin[i] - ent->r.absmax[i];
1539 				} else {
1540 					v[i] = 0;
1541 				}
1542 			}
1543 		}
1544 // jpw
1545 		dist = VectorLength( v );
1546 
1547 		if ( dist >= radius ) {
1548 			continue;
1549 		}
1550 
1551 		points = damage * ( 1.0 - dist / radius );
1552 
1553 // JPW NERVE -- different radiusdmg behavior for MP -- big explosions should do less damage (over less distance) through failed traces
1554 		if ( CanDamage( ent, origin ) ) {
1555 			if ( LogAccuracyHit( ent, attacker ) ) {
1556 				hitClient = qtrue;
1557 			}
1558 			VectorSubtract( ent->r.currentOrigin, origin, dir );
1559 			// push the center of mass higher than the origin so players
1560 			// get knocked into the air more
1561 			dir[2] += 24;
1562 
1563 			G_Damage( ent, NULL, attacker, dir, origin, (int)points, DAMAGE_RADIUS, mod );
1564 		}
1565 // JPW NERVE --  MP weapons should do 1/8 damage through walls over 1/8th distance
1566 		else {
1567 			if ( g_gametype.integer != GT_SINGLE_PLAYER ) {
1568 				VectorAdd( ent->r.absmin, ent->r.absmax, midpoint );
1569 				VectorScale( midpoint, 0.5, midpoint );
1570 				VectorCopy( midpoint, dest );
1571 
1572 				trap_Trace( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID );
1573 				if ( tr.fraction < 1.0 ) {
1574 					VectorSubtract( dest,origin,dest );
1575 					dist = VectorLength( dest );
1576 					if ( dist < radius * 0.2f ) { // closer than 1/4 dist
1577 						if ( LogAccuracyHit( ent, attacker ) ) {
1578 							hitClient = qtrue;
1579 						}
1580 						VectorSubtract( ent->r.currentOrigin, origin, dir );
1581 						dir[2] += 24;
1582 						G_Damage( ent, NULL, attacker, dir, origin, (int)( points * 0.1f ), DAMAGE_RADIUS, mod );
1583 					}
1584 				}
1585 			}
1586 		}
1587 // jpw
1588 	}
1589 	return hitClient;
1590 }
1591