1 /*
2 ===========================================================================
3 Copyright (C) 1999 - 2005, Id Software, Inc.
4 Copyright (C) 2000 - 2013, Raven Software, Inc.
5 Copyright (C) 2001 - 2013, Activision, Inc.
6 Copyright (C) 2013 - 2015, OpenJK contributors
7 
8 This file is part of the OpenJK source code.
9 
10 OpenJK is free software; you can redistribute it and/or modify it
11 under the terms of the GNU General Public License version 2 as
12 published by the Free Software Foundation.
13 
14 This program 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 this program; if not, see <http://www.gnu.org/licenses/>.
21 ===========================================================================
22 */
23 
24 // g_combat.c
25 
26 #include "g_headers.h"
27 
28 #include "g_local.h"
29 #include "b_local.h"
30 #include "g_functions.h"
31 #include "anims.h"
32 #include "objectives.h"
33 #include "../cgame/cg_local.h"
34 #include "g_icarus.h"
35 #include "wp_saber.h"
36 #include "Q3_Interface.h"
37 #include "../../code/qcommon/strippublic.h"
38 
39 extern	cvar_t	*g_debugDamage;
40 extern qboolean	stop_icarus;
41 extern cvar_t	*g_dismemberment;
42 extern cvar_t	*g_dismemberProbabilities;
43 extern cvar_t	*g_saberRealisticCombat;
44 extern cvar_t		*g_timescale;
45 extern cvar_t		*d_slowmodeath;
46 extern gentity_t *player;
47 
48 gentity_t *g_lastClientDamaged;
49 
50 extern int killPlayerTimer;
51 
52 extern void NPC_TempLookTarget ( gentity_t *self, int lookEntNum, int minLookTime, int maxLookTime );
53 extern void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime );
54 extern qboolean PM_HasAnimation( gentity_t *ent, int animation );
55 extern qboolean G_TeamEnemy( gentity_t *self );
56 extern void CG_ChangeWeapon( int num );
57 extern void ChangeWeapon( gentity_t *ent, int newWeapon );
58 
59 extern void G_SetEnemy( gentity_t *self, gentity_t *enemy );
60 extern void PM_SetLegsAnimTimer( gentity_t *ent, int *legsAnimTimer, int time );
61 extern void PM_SetTorsoAnimTimer( gentity_t *ent, int *torsoAnimTimer, int time );
62 extern int PM_PickAnim( gentity_t *self, int minAnim, int maxAnim );
63 extern qboolean PM_InOnGroundAnim ( playerState_t *ps );
64 extern void G_SoundOnEnt( gentity_t *ent, soundChannel_t channel, const char *soundPath );
65 extern void G_ATSTCheckPain( gentity_t *self, gentity_t *other, vec3_t point, int damage, int mod,int hitLoc );
66 extern qboolean Jedi_WaitingAmbush( gentity_t *self );
67 extern qboolean G_ClearViewEntity( gentity_t *ent );
68 extern qboolean PM_CrouchAnim( int anim );
69 extern qboolean PM_InKnockDown( playerState_t *ps );
70 extern qboolean PM_InRoll( playerState_t *ps );
71 extern qboolean PM_SpinningAnim( int anim );
72 extern qboolean PM_RunningAnim( int anim );
73 extern int PM_PowerLevelForSaberAnim( playerState_t *ps );
74 extern qboolean PM_SaberInSpecialAttack( int anim );
75 extern qboolean PM_SpinningSaberAnim( int anim );
76 extern qboolean PM_FlippingAnim( int anim );
77 extern qboolean PM_InSpecialJump( int anim );
78 extern qboolean PM_RollingAnim( int anim );
79 extern qboolean PM_InAnimForSaberMove( int anim, int saberMove );
80 extern qboolean PM_SaberInStart( int move );
81 extern qboolean PM_SaberInReturn( int move );
82 extern int PM_AnimLength( int index, animNumber_t anim );
83 
84 static int G_CheckForLedge( gentity_t *self, vec3_t fallCheckDir, float checkDist );
85 static int G_CheckSpecialDeathAnim( gentity_t *self, vec3_t point, int damage, int mod, int hitLoc );
86 static int G_PickDeathAnim( gentity_t *self, vec3_t point, int damage, int mod, int hitLoc );
87 static void G_TrackWeaponUsage( gentity_t *self, gentity_t *inflictor, int add, int mod );
88 static qboolean G_Dismemberable( gentity_t *self, int hitLoc );
89 extern gitem_t	*FindItemForAmmo( ammo_t ammo );
90 /*
91 ============
92 AddScore
93 
94 Adds score to both the client and his team
95 ============
96 */
AddScore(gentity_t * ent,int score)97 void AddScore( gentity_t *ent, int score ) {
98 	if ( !ent->client ) {
99 		return;
100 	}
101 	// no scoring during pre-match warmup
102 	ent->client->ps.persistant[PERS_SCORE] += score;
103 }
104 
105 /*
106 =================
107 TossClientItems
108 
109 Toss the weapon and powerups for the killed player
110 =================
111 */
112 extern gentity_t *WP_DropThermal( gentity_t *ent );
113 extern qboolean WP_SaberLose( gentity_t *self, vec3_t throwDir );
TossClientItems(gentity_t * self)114 gentity_t *TossClientItems( gentity_t *self )
115 {
116 	gentity_t	*dropped = NULL;
117 	gitem_t		*item = NULL;
118 	int			weapon;
119 
120 	if ( self->client->NPC_class == CLASS_SEEKER || self->client->NPC_class == CLASS_REMOTE )
121 	{
122 		// these things are so small that they shouldn't bother throwing anything
123 		return NULL;
124 	}
125 
126 	// drop the weapon if not a saber or enemy-only weapon
127 	weapon = self->s.weapon;
128 	if ( weapon == WP_SABER )
129 	{
130 		if ( self->weaponModel < 0 || WP_SaberLose( self, NULL ) )
131 		{
132 			self->s.weapon = WP_NONE;
133 		}
134 	}
135 	else if ( weapon == WP_BLASTER_PISTOL )
136 	{//FIXME: either drop the pistol and make the pickup only give ammo or drop ammo
137 	}
138 	else if ( weapon > WP_SABER && weapon <= MAX_PLAYER_WEAPONS )//&& self->client->ps.ammo[ weaponData[weapon].ammoIndex ]
139 	{
140 		self->s.weapon = WP_NONE;
141 
142 		if ( weapon == WP_THERMAL && self->client->ps.torsoAnim == BOTH_ATTACK10 )
143 		{//we were getting ready to throw the thermal, drop it!
144 			self->client->ps.weaponChargeTime = level.time - FRAMETIME;//so it just kind of drops it
145 			dropped = WP_DropThermal( self );
146 		}
147 		else
148 		{// find the item type for this weapon
149 			item = FindItemForWeapon( (weapon_t) weapon );
150 		}
151 		if ( item && !dropped )
152 		{
153 			// spawn the item
154 			dropped = Drop_Item( self, item, 0, qtrue );
155 			//TEST: dropped items never go away
156 			dropped->e_ThinkFunc = thinkF_NULL;
157 			dropped->nextthink = -1;
158 
159 			if ( !self->s.number )
160 			{//player's dropped items never go away
161 				//dropped->e_ThinkFunc = thinkF_NULL;
162 				//dropped->nextthink = -1;
163 				dropped->count = 0;//no ammo
164 			}
165 			else
166 			{//FIXME: base this on the NPC's actual amount of ammo he's used up...
167 				switch ( weapon )
168 				{
169 				case WP_BRYAR_PISTOL:
170 					dropped->count = 20;
171 					break;
172 				case WP_BLASTER:
173 					dropped->count = 15;
174 					break;
175 				case WP_DISRUPTOR:
176 					dropped->count = 20;
177 					break;
178 				case WP_BOWCASTER:
179 					dropped->count = 5;
180 					break;
181 				case WP_REPEATER:
182 					dropped->count = 20;
183 					break;
184 				case WP_DEMP2:
185 					dropped->count = 10;
186 					break;
187 				case WP_FLECHETTE:
188 					dropped->count = 30;
189 					break;
190 				case WP_ROCKET_LAUNCHER:
191 					dropped->count = 3;
192 					break;
193 				case WP_THERMAL:
194 					dropped->count = 4;
195 					break;
196 				case WP_TRIP_MINE:
197 					dropped->count = 3;
198 					break;
199 				case WP_DET_PACK:
200 					dropped->count = 1;
201 					break;
202 				case WP_STUN_BATON:
203 					dropped->count = 20;
204 					break;
205 				default:
206 					dropped->count = 0;
207 					break;
208 				}
209 			}
210 			// well, dropped weapons are G2 models, so they have to be initialised if they want to draw..give us a radius so we don't get prematurely culled
211 			if ( weapon != WP_THERMAL
212 				&& weapon != WP_TRIP_MINE
213 				&& weapon != WP_DET_PACK )
214 			{
215 				gi.G2API_InitGhoul2Model( dropped->ghoul2, item->world_model, G_ModelIndex( item->world_model ), NULL_HANDLE, NULL_HANDLE, 0, 0);
216 				dropped->s.radius = 10;
217 			}
218 		}
219 	}
220 //	else if (( self->client->NPC_class == CLASS_SENTRY ) || ( self->client->NPC_class == CLASS_PROBE )) // Looks dumb, Steve told us to take it out.
221 //	{
222 //		item = FindItemForAmmo( AMMO_BLASTER );
223 //		Drop_Item( self, item, 0, qtrue );
224 //	}
225 	else if ( self->client->NPC_class == CLASS_MARK1 )
226 	{
227 
228 		if (Q_irand( 1, 2 )>1)
229 		{
230 			item = FindItemForAmmo( AMMO_METAL_BOLTS );
231 		}
232 		else
233 		{
234 			item = FindItemForAmmo( AMMO_BLASTER );
235 		}
236 		Drop_Item( self, item, 0, qtrue );
237 	}
238 	else if ( self->client->NPC_class == CLASS_MARK2 )
239 	{
240 
241 		if (Q_irand( 1, 2 )>1)
242 		{
243 			item = FindItemForAmmo( AMMO_METAL_BOLTS );
244 		}
245 		else
246 		{
247 			item = FindItemForAmmo( AMMO_POWERCELL );
248 		}
249 		Drop_Item( self, item, 0, qtrue );
250 	}
251 
252 	return dropped;//NOTE: presumes only drop one thing
253 }
254 
G_DropKey(gentity_t * self)255 void G_DropKey( gentity_t *self )
256 {//drop whatever security key I was holding
257 	gitem_t		*item = NULL;
258 	if ( !Q_stricmp( "goodie", self->message ) )
259 	{
260 		item = FindItemForInventory( INV_GOODIE_KEY );
261 	}
262 	else
263 	{
264 		item = FindItemForInventory( INV_SECURITY_KEY );
265 	}
266 	gentity_t	*dropped = Drop_Item( self, item, 0, qtrue );
267 	//Don't throw the key
268 	VectorClear( dropped->s.pos.trDelta );
269 	dropped->message = G_NewString( self->message );
270 	self->message = NULL;
271 }
272 
ObjectDie(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,int damage,int meansOfDeath)273 void ObjectDie (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath )
274 {
275 	if(self->target)
276 		G_UseTargets(self, attacker);
277 
278 	//remove my script_targetname
279 	G_FreeEntity( self );
280 }
281 /*
282 ==================
283 ExplodeDeath
284 ==================
285 */
286 
287 //FIXME: all hacked up...
288 
289 //void CG_SurfaceExplosion( vec3_t origin, vec3_t normal, float radius, float shake_speed, qboolean smoke );
ExplodeDeath(gentity_t * self)290 void ExplodeDeath( gentity_t *self )
291 {
292 //	gentity_t	*tent;
293 	vec3_t		forward;
294 
295 	self->takedamage = qfalse;//stop chain reaction runaway loops
296 
297 	self->s.loopSound = 0;
298 
299 	VectorCopy( self->currentOrigin, self->s.pos.trBase );
300 
301 //	tent = G_TempEntity( self->s.origin, EV_FX_EXPLOSION );
302 	AngleVectors(self->s.angles, forward, NULL, NULL);  // FIXME: letting effect always shoot up?  Might be ok.
303 
304 	if ( self->fxID > 0 )
305 	{
306 		G_PlayEffect( self->fxID, self->currentOrigin, forward );
307 	}
308 //	else
309 //	{
310 //		CG_SurfaceExplosion( self->currentOrigin, forward, 20.0f, 12.0f, ((self->spawnflags&4)==qfalse) );	//FIXME: This needs to be consistent to all exploders!
311 //		G_Sound(self, self->sounds );
312 //	}
313 
314 	if(self->splashDamage > 0 && self->splashRadius > 0)
315 	{
316 		gentity_t *attacker = self;
317 		if ( self->owner )
318 		{
319 			attacker = self->owner;
320 		}
321 		G_RadiusDamage( self->currentOrigin, attacker, self->splashDamage, self->splashRadius,
322 				attacker, MOD_UNKNOWN );
323 	}
324 
325 	ObjectDie( self, self, self, 20, 0 );
326 }
327 
ExplodeDeath_Wait(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,int damage,int meansOfDeath,int dFlags,int hitLoc)328 void ExplodeDeath_Wait( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath,int dFlags,int hitLoc )
329 {
330 	self->e_DieFunc = dieF_NULL;
331 	self->nextthink = level.time + Q_irand(100, 500);
332 	self->e_ThinkFunc = thinkF_ExplodeDeath;
333 }
334 
ExplodeDeath(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,int damage,int meansOfDeath,int dFlags,int hitLoc)335 void ExplodeDeath( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath,int dFlags,int hitLoc )
336 {
337 	self->currentOrigin[2] += 16; // me bad for hacking this.  should either do it in the effect file or make a custom explode death??
338 	ExplodeDeath( self );
339 }
340 
GoExplodeDeath(gentity_t * self,gentity_t * other,gentity_t * activator)341 void GoExplodeDeath( gentity_t *self, gentity_t *other, gentity_t *activator)
342 {
343 	G_ActivateBehavior(self,BSET_USE);
344 
345 	self->targetname = "";	//Make sure this entity cannot be told to explode again (recursive death fix)
346 
347 	ExplodeDeath( self );
348 }
349 
350 qboolean G_ActivateBehavior (gentity_t *self, int bset );
G_CheckVictoryScript(gentity_t * self)351 void G_CheckVictoryScript(gentity_t *self)
352 {
353 	if ( !G_ActivateBehavior( self, BSET_VICTORY ) )
354 	{
355 		if ( self->NPC && self->s.weapon == WP_SABER )
356 		{//Jedi taunt from within their AI
357 			self->NPC->blockedSpeechDebounceTime = 0;//get them ready to taunt
358 			return;
359 		}
360 		if ( self->client && self->client->NPC_class == CLASS_GALAKMECH )
361 		{
362 			self->wait = 1;
363 			TIMER_Set( self, "gloatTime", Q_irand( 5000, 8000 ) );
364 			self->NPC->blockedSpeechDebounceTime = 0;//get him ready to taunt
365 			return;
366 		}
367 		//FIXME: any way to not say this *right away*?  Wait for victim's death anim/scream to finish?
368 		if ( self->NPC && self->NPC->group && self->NPC->group->commander && self->NPC->group->commander->NPC && self->NPC->group->commander->NPC->rank > self->NPC->rank && !Q_irand( 0, 2 ) )
369 		{//sometimes have the group commander speak instead
370 			self->NPC->group->commander->NPC->greetingDebounceTime = level.time + Q_irand( 2000, 5000 );
371 			//G_AddVoiceEvent( self->NPC->group->commander, Q_irand(EV_VICTORY1, EV_VICTORY3), 2000 );
372 		}
373 		else if ( self->NPC )
374 		{
375 			self->NPC->greetingDebounceTime = level.time + Q_irand( 2000, 5000 );
376 			//G_AddVoiceEvent( self, Q_irand(EV_VICTORY1, EV_VICTORY3), 2000 );
377 		}
378 	}
379 }
380 
OnSameTeam(gentity_t * ent1,gentity_t * ent2)381 qboolean OnSameTeam( gentity_t *ent1, gentity_t *ent2 )
382 {
383 	if ( !ent1->client || !ent2->client )
384 	{
385 		if ( ent1->noDamageTeam )
386 		{
387 			if ( ent2->client && ent2->client->playerTeam == ent1->noDamageTeam )
388 			{
389 				return qtrue;
390 			}
391 			else if ( ent2->noDamageTeam == ent1->noDamageTeam )
392 			{
393 				if ( ent1->splashDamage && ent2->splashDamage && Q_stricmp("ambient_etherian_fliers", ent1->classname) != 0 )
394 				{//Barrels, exploding breakables and mines will blow each other up
395 					return qfalse;
396 				}
397 				else
398 				{
399 					return qtrue;
400 				}
401 			}
402 		}
403 		return qfalse;
404 	}
405 
406 	// shouldn't need this anymore, there were problems with certain droids, but now they have been labeled TEAM_ENEMY so this isn't needed
407 //	if ((( ent1->client->playerTeam == TEAM_IMPERIAL ) && ( ent1->client->playerTeam == TEAM_BOTS )) ||
408 //		(( ent1->client->playerTeam == TEAM_BOTS ) && ( ent1->client->playerTeam == TEAM_IMPERIAL )))
409 //	{
410 //		return qtrue;
411 //	}
412 
413 	return (qboolean)( ent1->client->playerTeam == ent2->client->playerTeam );
414 }
415 
416 
417 /*
418 -------------------------
419 G_AlertTeam
420 -------------------------
421 */
422 
G_AlertTeam(gentity_t * victim,gentity_t * attacker,float radius,float soundDist)423 void G_AlertTeam( gentity_t *victim, gentity_t *attacker, float radius, float soundDist )
424 {
425 	gentity_t	*radiusEnts[ 128 ];
426 	vec3_t		mins, maxs;
427 	int			numEnts;
428 	float		distSq, sndDistSq = (soundDist*soundDist);
429 	int i;
430 
431 	if ( attacker == NULL || attacker->client == NULL )
432 		return;
433 
434 	//Setup the bbox to search in
435 	for ( i = 0; i < 3; i++ )
436 	{
437 		mins[i] = victim->currentOrigin[i] - radius;
438 		maxs[i] = victim->currentOrigin[i] + radius;
439 	}
440 
441 	//Get the number of entities in a given space
442 	numEnts = gi.EntitiesInBox( mins, maxs, radiusEnts, 128 );
443 
444 	//Cull this list
445 	for ( i = 0; i < numEnts; i++ )
446 	{
447 		//Validate clients
448 		if ( radiusEnts[i]->client == NULL )
449 			continue;
450 
451 		//only want NPCs
452 		if ( radiusEnts[i]->NPC == NULL )
453 			continue;
454 
455 		//Don't bother if they're ignoring enemies
456 		if ( radiusEnts[i]->svFlags & SVF_IGNORE_ENEMIES )
457 			continue;
458 
459 		//This NPC specifically flagged to ignore alerts
460 		if ( radiusEnts[i]->NPC->scriptFlags & SCF_IGNORE_ALERTS )
461 			continue;
462 
463 		//This NPC specifically flagged to ignore alerts
464 		if ( !(radiusEnts[i]->NPC->scriptFlags&SCF_LOOK_FOR_ENEMIES) )
465 			continue;
466 
467 		//this ent does not participate in group AI
468 		if ( (radiusEnts[i]->NPC->scriptFlags&SCF_NO_GROUPS) )
469 			continue;
470 
471 		//Skip the requested avoid radiusEnts[i] if present
472 		if ( radiusEnts[i] == victim )
473 			continue;
474 
475 		//Skip the attacker
476 		if ( radiusEnts[i] == attacker )
477 			continue;
478 
479 		//Must be on the same team
480 		if ( radiusEnts[i]->client->playerTeam != victim->client->playerTeam )
481 			continue;
482 
483 		//Must be alive
484 		if ( radiusEnts[i]->health <= 0 )
485 			continue;
486 
487 		if ( radiusEnts[i]->enemy == NULL )
488 		{//only do this if they're not already mad at someone
489 			distSq = DistanceSquared( radiusEnts[i]->currentOrigin, victim->currentOrigin );
490 			if ( distSq > 16384 /*128 squared*/ && !gi.inPVS( victim->currentOrigin, radiusEnts[i]->currentOrigin ) )
491 			{//not even potentially visible/hearable
492 				continue;
493 			}
494 			//NOTE: this allows sound alerts to still go through doors/PVS if the teammate is within 128 of the victim...
495 			if ( soundDist <= 0 || distSq > sndDistSq )
496 			{//out of sound range
497 				if ( !InFOV( victim, radiusEnts[i], radiusEnts[i]->NPC->stats.hfov, radiusEnts[i]->NPC->stats.vfov )
498 					||  !NPC_ClearLOS( radiusEnts[i], victim->currentOrigin ) )
499 				{//out of FOV or no LOS
500 					continue;
501 				}
502 			}
503 
504 			//FIXME: This can have a nasty cascading effect if setup wrong...
505 			G_SetEnemy( radiusEnts[i], attacker );
506 		}
507 	}
508 }
509 
510 /*
511 -------------------------
512 G_DeathAlert
513 -------------------------
514 */
515 
516 #define	DEATH_ALERT_RADIUS			512
517 #define	DEATH_ALERT_SOUND_RADIUS	512
518 
G_DeathAlert(gentity_t * victim,gentity_t * attacker)519 void G_DeathAlert( gentity_t *victim, gentity_t *attacker )
520 {//FIXME: with all the other alert stuff, do we really need this?
521 	G_AlertTeam( victim, attacker, DEATH_ALERT_RADIUS, DEATH_ALERT_SOUND_RADIUS );
522 }
523 
524 /*
525 ----------------------------------------
526 DeathFX
527 
528 Applies appropriate special effects that occur while the entity is dying
529 Not to be confused with NPC_RemoveBodyEffects (NPC.cpp), which only applies effect when removing the body
530 ----------------------------------------
531 */
532 
DeathFX(gentity_t * ent)533 void DeathFX( gentity_t *ent )
534 {
535 	if ( !ent || !ent->client )
536 		return;
537 /*
538 	switch( ent->client->playerTeam )
539 	{
540 	case TEAM_BOTS:
541 		if (!Q_stricmp( ent->NPC_type, "mouse" ))
542 		{
543 			vec3_t		effectPos;
544 			VectorCopy( ent->currentOrigin, effectPos );
545 			effectPos[2] -= 20;
546 
547 			G_PlayEffect( "mouseexplosion1", effectPos );
548 			G_PlayEffect( "smaller_chunks", effectPos );
549 
550 		}
551 		else if (!Q_stricmp( ent->NPC_type, "probe" ))
552 		{
553 			vec3_t		effectPos;
554 			VectorCopy( ent->currentOrigin, effectPos );
555 			effectPos[2] += 50;
556 
557 			G_PlayEffect( "probeexplosion1", effectPos );
558 			G_PlayEffect( "small_chunks", effectPos );
559 		}
560 		else
561 		{
562 			vec3_t		effectPos;
563 			VectorCopy( ent->currentOrigin, effectPos );
564 			effectPos[2] -= 15;
565 			G_PlayEffect( "droidexplosion1", effectPos );
566 			G_PlayEffect( "small_chunks", effectPos );
567 		}
568 
569 		break;
570 
571 	default:
572 		break;
573 	}
574 */
575 	// team no longer indicates species/race.  NPC_class should be used to identify certain npc types
576 	vec3_t		effectPos, right;
577 	switch(ent->client->NPC_class)
578 	{
579 	case CLASS_MOUSE:
580 		VectorCopy( ent->currentOrigin, effectPos );
581 		effectPos[2] -= 20;
582 		G_PlayEffect( "env/small_explode", effectPos );
583 		G_SoundOnEnt( ent, CHAN_AUTO, "sound/chars/mouse/misc/death1" );
584 		break;
585 
586 	case CLASS_PROBE:
587 		VectorCopy( ent->currentOrigin, effectPos );
588 		effectPos[2] += 50;
589 		G_PlayEffect( "probeexplosion1", effectPos );
590 		break;
591 
592 	case CLASS_ATST:
593 		AngleVectors( ent->currentAngles, NULL, right, NULL );
594 		VectorMA( ent->currentOrigin, 20, right, effectPos );
595 		effectPos[2] += 180;
596 		G_PlayEffect( "droidexplosion1", effectPos );
597 		VectorMA( effectPos, -40, right, effectPos );
598 		G_PlayEffect( "droidexplosion1", effectPos );
599 		break;
600 
601 	case CLASS_SEEKER:
602 	case CLASS_REMOTE:
603 		G_PlayEffect( "env/small_explode", ent->currentOrigin );
604 		break;
605 
606 	case CLASS_GONK:
607 		VectorCopy( ent->currentOrigin, effectPos );
608 		effectPos[2] -= 5;
609 //		statusTextIndex = Q_irand( IGT_RESISTANCEISFUTILE, IGT_NAMEIS8OF12 );
610 		G_SoundOnEnt( ent, CHAN_AUTO, va("sound/chars/gonk/misc/death%d.wav",Q_irand( 1, 3 )) );
611 		G_PlayEffect( "env/med_explode", effectPos );
612 		break;
613 
614 	// should list all remaining droids here, hope I didn't miss any
615 	case CLASS_R2D2:
616 		VectorCopy( ent->currentOrigin, effectPos );
617 		effectPos[2] -= 10;
618 		G_PlayEffect( "env/med_explode", effectPos );
619 		G_SoundOnEnt( ent, CHAN_AUTO, "sound/chars/mark2/misc/mark2_explo" );
620 		break;
621 
622 	case CLASS_PROTOCOL://??
623 	case CLASS_R5D2:
624 		VectorCopy( ent->currentOrigin, effectPos );
625 		effectPos[2] -= 10;
626 		G_PlayEffect( "env/med_explode", effectPos );
627 		G_SoundOnEnt( ent, CHAN_AUTO, "sound/chars/mark2/misc/mark2_explo" );
628 		break;
629 
630 	case CLASS_MARK2:
631 		VectorCopy( ent->currentOrigin, effectPos );
632 		effectPos[2] -= 15;
633 		G_PlayEffect( "droidexplosion1", effectPos );
634 		G_SoundOnEnt( ent, CHAN_AUTO, "sound/chars/mark2/misc/mark2_explo" );
635 		break;
636 
637 	case CLASS_INTERROGATOR:
638 		VectorCopy( ent->currentOrigin, effectPos );
639 		effectPos[2] -= 15;
640 		G_PlayEffect( "droidexplosion1", effectPos );
641 		G_SoundOnEnt( ent, CHAN_AUTO, "sound/chars/interrogator/misc/int_droid_explo" );
642 		break;
643 
644 	case CLASS_MARK1:
645 		AngleVectors( ent->currentAngles, NULL, right, NULL );
646 		VectorMA( ent->currentOrigin, 10, right, effectPos );
647 		effectPos[2] -= 15;
648 		G_PlayEffect( "droidexplosion1", effectPos );
649 		VectorMA( effectPos, -20, right, effectPos );
650 		G_PlayEffect( "droidexplosion1", effectPos );
651 		VectorMA( effectPos, -20, right, effectPos );
652 		G_PlayEffect( "droidexplosion1", effectPos );
653 		G_SoundOnEnt( ent, CHAN_AUTO, "sound/chars/mark1/misc/mark1_explo" );
654 		break;
655 
656 	case CLASS_SENTRY:
657 		G_SoundOnEnt( ent, CHAN_AUTO, "sound/chars/sentry/misc/sentry_explo" );
658 		VectorCopy( ent->currentOrigin, effectPos );
659 		G_PlayEffect( "env/med_explode", effectPos );
660 		break;
661 
662 	default:
663 		break;
664 
665 	}
666 
667 }
668 
G_SetMissionStatusText(gentity_t * attacker,int mod)669 void G_SetMissionStatusText( gentity_t *attacker, int mod )
670 {
671 	if ( statusTextIndex >= 0 )
672 	{
673 		return;
674 	}
675 
676 	if ( mod == MOD_FALLING )
677 	{//fell to your death
678 		statusTextIndex = STAT_WATCHYOURSTEP;
679 	}
680 	else if ( mod == MOD_CRUSH )
681 	{//crushed
682 		statusTextIndex = STAT_JUDGEMENTMUCHDESIRED;
683 	}
684 	else if ( attacker && Q_stricmp( "trigger_hurt", attacker->classname ) == 0 )
685 	{//Killed by something that should have been clearly dangerous
686 //		statusTextIndex = Q_irand( IGT_JUDGEMENTDESIRED, IGT_JUDGEMENTMUCHDESIRED );
687 		statusTextIndex = STAT_JUDGEMENTMUCHDESIRED;
688 	}
689 	else if ( attacker && attacker->s.number != 0 && attacker->client && attacker->client->playerTeam == TEAM_PLAYER )
690 	{//killed by a teammate
691 		statusTextIndex = STAT_INSUBORDINATION;
692 	}
693 }
694 
G_MakeTeamVulnerable(void)695 void G_MakeTeamVulnerable( void )
696 {
697 	int i, newhealth;
698 	gentity_t *ent;
699 	gentity_t *self = &g_entities[0];
700 	if ( !self->client )
701 	{
702 		return;
703 	}
704 
705 //	for ( i = 0; i < globals.num_entities ; i++, ent++)
706 	for ( i = 0; i < globals.num_entities ; i++)
707 	{
708 		if(!PInUse(i))
709 			continue;
710 //		if ( !ent->inuse  )
711 //		{
712 //			continue;
713 //		}
714 //		if ( !ent )
715 //		{
716 //			continue;
717 //		}
718 		ent=&g_entities[i];
719 		if ( !ent->client  )
720 		{
721 			continue;
722 		}
723 		if ( ent->client->playerTeam != TEAM_PLAYER )
724 		{
725 			continue;
726 		}
727 		if ( !(ent->flags&FL_UNDYING) )
728 		{
729 			continue;
730 		}
731 		ent->flags &= ~FL_UNDYING;
732 		newhealth = Q_irand( 5, 40 );
733 		if ( ent->health > newhealth )
734 		{
735 			ent->health = newhealth;
736 		}
737 	}
738 }
739 
G_StartMatrixEffect(gentity_t * ent,qboolean falling=qfalse,int length=1000)740 void G_StartMatrixEffect( gentity_t *ent, qboolean falling = qfalse, int length = 1000 )
741 {//FIXME: only do this if no other enemies around?
742 	if ( g_timescale->value != 1.0 )
743 	{//already in some slow-mo mode
744 		return;
745 	}
746 
747 	gentity_t	*matrix = G_Spawn();
748 	if ( matrix )
749 	{
750 		G_SetOrigin( matrix, ent->currentOrigin );
751 		gi.linkentity( matrix );
752 		matrix->s.otherEntityNum = ent->s.number;
753 		matrix->e_clThinkFunc = clThinkF_CG_MatrixEffect;
754 		matrix->s.eType = ET_THINKER;
755 		matrix->svFlags |= SVF_BROADCAST;// Broadcast to all clients
756 		matrix->s.time = level.time;
757 		matrix->s.eventParm = length;
758 		matrix->e_ThinkFunc = thinkF_G_FreeEntity;
759 		matrix->nextthink = level.time + length + 500;
760 		if ( falling )
761 		{//no timescale or vert bob
762 			matrix->s.weapon = 1;
763 		}
764 	}
765 }
766 
G_JediInRoom(vec3_t from)767 qboolean G_JediInRoom( vec3_t from )
768 {
769 	gentity_t *ent;
770 	int i;
771 //	for ( i = 1, ent = &g_entities[1]; i < globals.num_entities; i++, ent++ )
772 	for ( i = 1; i < globals.num_entities; i++)
773 	{
774 		if(!PInUse(i))
775 			continue;
776 //		if ( !ent->inuse )
777 //		{
778 //			continue;
779 //		}
780 //		if ( !ent )
781 //		{
782 //			continue;
783 //		}
784 		ent = &g_entities[i];
785 		if ( !ent->NPC )
786 		{
787 			continue;
788 		}
789 		if ( ent->health <= 0 )
790 		{
791 			continue;
792 		}
793 		if ( ent->s.eFlags&EF_NODRAW )
794 		{
795 			continue;
796 		}
797 		if ( ent->s.weapon != WP_SABER )
798 		{
799 			continue;
800 		}
801 		if ( !gi.inPVS( ent->currentOrigin, from ) )
802 		{
803 			continue;
804 		}
805 		return qtrue;
806 	}
807 	return qfalse;
808 }
809 
G_GetHitLocFromSurfName(gentity_t * ent,const char * surfName,int * hitLoc,vec3_t point,vec3_t dir,vec3_t bladeDir,int mod)810 qboolean G_GetHitLocFromSurfName( gentity_t *ent, const char *surfName, int *hitLoc, vec3_t point, vec3_t dir, vec3_t bladeDir, int mod )
811 {
812 	qboolean dismember = qfalse;
813 
814 	*hitLoc = HL_NONE;
815 
816 	if ( !surfName || !surfName[0] )
817 	{
818 		return qfalse;
819 	}
820 
821 	if( !ent->client )
822 	{
823 		return qfalse;
824 	}
825 
826 	if ( ent->client
827 		&& ( ent->client->NPC_class == CLASS_R2D2
828 			|| ent->client->NPC_class == CLASS_R5D2
829 			|| ent->client->NPC_class == CLASS_GONK
830 			|| ent->client->NPC_class == CLASS_MOUSE
831 			|| ent->client->NPC_class == CLASS_SEEKER
832 			|| ent->client->NPC_class == CLASS_INTERROGATOR
833 			|| ent->client->NPC_class == CLASS_SENTRY
834 			|| ent->client->NPC_class == CLASS_PROBE ) )
835 	{//we don't care about per-surface hit-locations or dismemberment for these guys
836 		return qfalse;
837 	}
838 
839 	if ( ent->client && (ent->client->NPC_class == CLASS_ATST) )
840 	{
841 		//FIXME: almost impossible to hit these... perhaps we should
842 		//		check for splashDamage and do radius damage to these parts?
843 		//		Or, if we ever get bbox G2 traces, that may fix it, too
844 		if (!Q_stricmp("head_light_blaster_cann",surfName))
845 		{
846 			*hitLoc = HL_ARM_LT;
847 		}
848 		else if (!Q_stricmp("head_concussion_charger",surfName))
849 		{
850 			*hitLoc = HL_ARM_RT;
851 		}
852 		return(qfalse);
853 	}
854 	else if ( ent->client && (ent->client->NPC_class == CLASS_MARK1) )
855 	{
856 		if (!Q_stricmp("l_arm",surfName))
857 		{
858 			*hitLoc = HL_ARM_LT;
859 		}
860 		else if (!Q_stricmp("r_arm",surfName))
861 		{
862 			*hitLoc = HL_ARM_RT;
863 		}
864 		else if (!Q_stricmp("torso_front",surfName))
865 		{
866 			*hitLoc = HL_CHEST;
867 		}
868 		else if (!Q_stricmp("torso_tube1",surfName))
869 		{
870 			*hitLoc = HL_GENERIC1;
871 		}
872 		else if (!Q_stricmp("torso_tube2",surfName))
873 		{
874 			*hitLoc = HL_GENERIC2;
875 		}
876 		else if (!Q_stricmp("torso_tube3",surfName))
877 		{
878 			*hitLoc = HL_GENERIC3;
879 		}
880 		else if (!Q_stricmp("torso_tube4",surfName))
881 		{
882 			*hitLoc = HL_GENERIC4;
883 		}
884 		else if (!Q_stricmp("torso_tube5",surfName))
885 		{
886 			*hitLoc = HL_GENERIC5;
887 		}
888 		else if (!Q_stricmp("torso_tube6",surfName))
889 		{
890 			*hitLoc = HL_GENERIC6;
891 		}
892 		return(qfalse);
893 	}
894 	else if ( ent->client && (ent->client->NPC_class == CLASS_MARK2) )
895 	{
896 		if (!Q_stricmp("torso_canister1",surfName))
897 		{
898 			*hitLoc = HL_GENERIC1;
899 		}
900 		else if (!Q_stricmp("torso_canister2",surfName))
901 		{
902 			*hitLoc = HL_GENERIC2;
903 		}
904 		else if (!Q_stricmp("torso_canister3",surfName))
905 		{
906 			*hitLoc = HL_GENERIC3;
907 		}
908 		return(qfalse);
909 	}
910 	else if ( ent->client && (ent->client->NPC_class == CLASS_GALAKMECH) )
911 	{
912 		if (!Q_stricmp("torso_antenna",surfName)||!Q_stricmp("torso_antenna_base",surfName))
913 		{
914 			*hitLoc = HL_GENERIC1;
915 		}
916 		else if (!Q_stricmp("torso_shield_off",surfName))
917 		{
918 			*hitLoc = HL_GENERIC2;
919 		}
920 		else
921 		{
922 			*hitLoc = HL_CHEST;
923 		}
924 		return(qfalse);
925 	}
926 
927 	//FIXME: check the hitLoc and hitDir against the cap tag for the place
928 	//where the split will be- if the hit dir is roughly perpendicular to
929 	//the direction of the cap, then the split is allowed, otherwise we
930 	//hit it at the wrong angle and should not dismember...
931 	int	actualTime = (cg.time?cg.time:level.time);
932 	if ( !Q_strncmp( "hips", surfName, 4 ) )
933 	{//FIXME: test properly for legs
934 		*hitLoc = HL_WAIST;
935 		if ( ent->client != NULL && ent->ghoul2.size() )
936 		{
937 			mdxaBone_t	boltMatrix;
938 			vec3_t	tagOrg, angles;
939 
940 			VectorSet( angles, 0, ent->currentAngles[YAW], 0 );
941 			if (ent->kneeLBolt>=0)
942 			{
943 				gi.G2API_GetBoltMatrix( ent->ghoul2, ent->playerModel, ent->kneeLBolt,
944 								&boltMatrix, angles, ent->currentOrigin,
945 								actualTime, NULL, ent->s.modelScale );
946 				gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, tagOrg );
947 				if ( DistanceSquared( point, tagOrg ) < 100 )
948 				{//actually hit the knee
949 					*hitLoc = HL_LEG_LT;
950 				}
951 			}
952 			if (*hitLoc == HL_WAIST)
953 			{
954 				if (ent->kneeRBolt>=0)
955 				{
956 					gi.G2API_GetBoltMatrix( ent->ghoul2, ent->playerModel, ent->kneeRBolt,
957 									&boltMatrix, angles, ent->currentOrigin,
958 									actualTime, NULL, ent->s.modelScale );
959 					gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, tagOrg );
960 					if ( DistanceSquared( point, tagOrg ) < 100 )
961 					{//actually hit the knee
962 						*hitLoc = HL_LEG_RT;
963 					}
964 				}
965 			}
966 		}
967 	}
968 	else if ( !Q_strncmp( "torso", surfName, 5 ) )
969 	{
970 		if ( !ent->client )
971 		{
972 			*hitLoc = HL_CHEST;
973 		}
974 		else
975 		{
976 			vec3_t	t_fwd, t_rt, t_up, dirToImpact;
977 			float frontSide, rightSide, upSide;
978 			AngleVectors( ent->client->renderInfo.torsoAngles, t_fwd, t_rt, t_up );
979 			VectorSubtract( point, ent->client->renderInfo.torsoPoint, dirToImpact );
980 			frontSide = DotProduct( t_fwd, dirToImpact );
981 			rightSide = DotProduct( t_rt, dirToImpact );
982 			upSide = DotProduct( t_up, dirToImpact );
983 			if ( upSide < -10 )
984 			{//hit at waist
985 				*hitLoc = HL_WAIST;
986 			}
987 			else
988 			{//hit on upper torso
989 				if ( rightSide > 4 )
990 				{
991 					*hitLoc = HL_ARM_RT;
992 				}
993 				else if ( rightSide < -4 )
994 				{
995 					*hitLoc = HL_ARM_LT;
996 				}
997 				else if ( rightSide > 2 )
998 				{
999 					if ( frontSide > 0 )
1000 					{
1001 						*hitLoc = HL_CHEST_RT;
1002 					}
1003 					else
1004 					{
1005 						*hitLoc = HL_BACK_RT;
1006 					}
1007 				}
1008 				else if ( rightSide < -2 )
1009 				{
1010 					if ( frontSide > 0 )
1011 					{
1012 						*hitLoc = HL_CHEST_LT;
1013 					}
1014 					else
1015 					{
1016 						*hitLoc = HL_BACK_LT;
1017 					}
1018 				}
1019 				else if ( upSide > -3 && mod == MOD_SABER )
1020 				{
1021 					*hitLoc = HL_HEAD;
1022 				}
1023 				else if ( frontSide > 0 )
1024 				{
1025 					*hitLoc = HL_CHEST;
1026 				}
1027 				else
1028 				{
1029 					*hitLoc = HL_BACK;
1030 				}
1031 			}
1032 		}
1033 	}
1034 	else if ( !Q_strncmp( "head", surfName, 4 ) )
1035 	{
1036 		*hitLoc = HL_HEAD;
1037 	}
1038 	else if ( !Q_strncmp( "r_arm", surfName, 5 ) )
1039 	{
1040 		*hitLoc = HL_ARM_RT;
1041 		if ( ent->client != NULL && ent->ghoul2.size() )
1042 		{
1043 			mdxaBone_t	boltMatrix;
1044 			vec3_t	tagOrg, angles;
1045 
1046 			VectorSet( angles, 0, ent->currentAngles[YAW], 0 );
1047 			if (ent->handRBolt>=0)
1048 			{
1049 				gi.G2API_GetBoltMatrix( ent->ghoul2, ent->playerModel, ent->handRBolt,
1050 								&boltMatrix, angles, ent->currentOrigin,
1051 								actualTime, NULL, ent->s.modelScale );
1052 				gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, tagOrg );
1053 				if ( DistanceSquared( point, tagOrg ) < 256 )
1054 				{//actually hit the hand
1055 					*hitLoc = HL_HAND_RT;
1056 				}
1057 			}
1058 		}
1059 	}
1060 	else if ( !Q_strncmp( "l_arm", surfName, 5 ) )
1061 	{
1062 		*hitLoc = HL_ARM_LT;
1063 		if ( ent->client != NULL && ent->ghoul2.size() )
1064 		{
1065 			mdxaBone_t	boltMatrix;
1066 			vec3_t	tagOrg, angles;
1067 
1068 			VectorSet( angles, 0, ent->currentAngles[YAW], 0 );
1069 			if (ent->handLBolt>=0)
1070 			{
1071 				gi.G2API_GetBoltMatrix( ent->ghoul2, ent->playerModel, ent->handLBolt,
1072 								&boltMatrix, angles, ent->currentOrigin,
1073 								actualTime, NULL, ent->s.modelScale );
1074 				gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, tagOrg );
1075 				if ( DistanceSquared( point, tagOrg ) < 256 )
1076 				{//actually hit the hand
1077 					*hitLoc = HL_HAND_LT;
1078 				}
1079 			}
1080 		}
1081 	}
1082 	else if ( !Q_strncmp( "r_leg", surfName, 5 ) )
1083 	{
1084 		*hitLoc = HL_LEG_RT;
1085 		if ( ent->client != NULL && ent->ghoul2.size() )
1086 		{
1087 			mdxaBone_t	boltMatrix;
1088 			vec3_t	tagOrg, angles;
1089 
1090 			VectorSet( angles, 0, ent->currentAngles[YAW], 0 );
1091 			if (ent->footRBolt>=0)
1092 			{
1093 				gi.G2API_GetBoltMatrix( ent->ghoul2, ent->playerModel, ent->footRBolt,
1094 								&boltMatrix, angles, ent->currentOrigin,
1095 								actualTime, NULL, ent->s.modelScale );
1096 				gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, tagOrg );
1097 				if ( DistanceSquared( point, tagOrg ) < 100 )
1098 				{//actually hit the foot
1099 					*hitLoc = HL_FOOT_RT;
1100 				}
1101 			}
1102 		}
1103 	}
1104 	else if ( !Q_strncmp( "l_leg", surfName, 5 ) )
1105 	{
1106 		*hitLoc = HL_LEG_LT;
1107 		if ( ent->client != NULL && ent->ghoul2.size() )
1108 		{
1109 			mdxaBone_t	boltMatrix;
1110 			vec3_t	tagOrg, angles;
1111 
1112 			VectorSet( angles, 0, ent->currentAngles[YAW], 0 );
1113 			if (ent->footLBolt>=0)
1114 			{
1115 				gi.G2API_GetBoltMatrix( ent->ghoul2, ent->playerModel, ent->footLBolt,
1116 								&boltMatrix, angles, ent->currentOrigin,
1117 								actualTime, NULL, ent->s.modelScale );
1118 				gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, tagOrg );
1119 				if ( DistanceSquared( point, tagOrg ) < 100 )
1120 				{//actually hit the foot
1121 					*hitLoc = HL_FOOT_LT;
1122 				}
1123 			}
1124 		}
1125 	}
1126 	else if ( !Q_strncmp( "r_hand", surfName, 6 ) || !Q_strncmp( "w_", surfName, 2 ) )
1127 	{//right hand or weapon
1128 		*hitLoc = HL_HAND_RT;
1129 	}
1130 	else if ( !Q_strncmp( "l_hand", surfName, 6 ) )
1131 	{
1132 		*hitLoc = HL_HAND_LT;
1133 	}
1134 #ifdef _DEBUG
1135 	else
1136 	{
1137 		Com_Printf( "ERROR: surface %s does not belong to any hitLocation!!!\n", surfName );
1138 	}
1139 #endif //_DEBUG
1140 
1141 	if ( g_saberRealisticCombat->integer )
1142 	{
1143 		dismember = qtrue;
1144 	}
1145 	else if ( g_dismemberment->integer >= 11381138 || !ent->client->dismembered )
1146 	{
1147 		if ( ent->client && ent->client->NPC_class == CLASS_PROTOCOL )
1148 		{
1149 			dismember = qtrue;
1150 		}
1151 		else if ( dir && (dir[0] || dir[1] || dir[2]) &&
1152 			bladeDir && (bladeDir[0] || bladeDir[1] || bladeDir[2]) )
1153 		{//we care about direction (presumably for dismemberment)
1154 			if ( g_dismemberProbabilities->value<=0.0f||G_Dismemberable( ent, *hitLoc ) )
1155 			{//either we don't care about probabilties or the probability let us continue
1156 				char *tagName = NULL;
1157 				float	aoa = 0.5f;
1158 				//dir must be roughly perpendicular to the hitLoc's cap bolt
1159 				switch ( *hitLoc )
1160 				{
1161 					case HL_LEG_RT:
1162 						tagName = "*hips_cap_r_leg";
1163 						break;
1164 					case HL_LEG_LT:
1165 						tagName = "*hips_cap_l_leg";
1166 						break;
1167 					case HL_WAIST:
1168 						tagName = "*hips_cap_torso";
1169 						aoa = 0.25f;
1170 						break;
1171 					case HL_CHEST_RT:
1172 					case HL_ARM_RT:
1173 					case HL_BACK_LT:
1174 						tagName = "*torso_cap_r_arm";
1175 						break;
1176 					case HL_CHEST_LT:
1177 					case HL_ARM_LT:
1178 					case HL_BACK_RT:
1179 						tagName = "*torso_cap_l_arm";
1180 						break;
1181 					case HL_HAND_RT:
1182 						tagName = "*r_arm_cap_r_hand";
1183 						break;
1184 					case HL_HAND_LT:
1185 						tagName = "*l_arm_cap_l_hand";
1186 						break;
1187 					case HL_HEAD:
1188 						tagName = "*torso_cap_head";
1189 						aoa = 0.25f;
1190 						break;
1191 					case HL_CHEST:
1192 					case HL_BACK:
1193 					case HL_FOOT_RT:
1194 					case HL_FOOT_LT:
1195 					default:
1196 						//no dismemberment possible with these, so no checks needed
1197 						break;
1198 				}
1199 				if ( tagName )
1200 				{
1201 					int tagBolt = gi.G2API_AddBolt( &ent->ghoul2[ent->playerModel], tagName );
1202 					if ( tagBolt != -1 )
1203 					{
1204 						mdxaBone_t	boltMatrix;
1205 						vec3_t	tagOrg, tagDir, angles;
1206 						VectorSet( angles, 0, ent->currentAngles[YAW], 0 );
1207 						gi.G2API_GetBoltMatrix( ent->ghoul2, ent->playerModel, tagBolt,
1208 										&boltMatrix, angles, ent->currentOrigin,
1209 										actualTime, NULL, ent->s.modelScale );
1210 						gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, tagOrg );
1211 						gi.G2API_GiveMeVectorFromMatrix( boltMatrix, NEGATIVE_Y, tagDir );
1212 						if ( DistanceSquared( point, tagOrg ) < 256 )
1213 						{//hit close
1214 							float dot = DotProduct( dir, tagDir );
1215 							if ( dot < aoa && dot > -aoa )
1216 							{//hit roughly perpendicular
1217 								dot = DotProduct( bladeDir, tagDir );
1218 								if ( dot < aoa && dot > -aoa )
1219 								{//blade was roughly perpendicular
1220 									dismember = qtrue;
1221 								}
1222 							}
1223 						}
1224 					}
1225 				}
1226 			}
1227 		}
1228 	}
1229 	return dismember;
1230 }
1231 
G_GetHitLocation(gentity_t * target,vec3_t ppoint)1232 int G_GetHitLocation ( gentity_t *target, vec3_t ppoint )
1233 {
1234 	vec3_t			point, point_dir;
1235 	vec3_t			forward, right, up;
1236 	vec3_t			tangles, tcenter;
1237 	float			udot, fdot, rdot;
1238 	int				Vertical, Forward, Lateral;
1239 	int				HitLoc;
1240 
1241 //get target forward, right and up
1242 	if(target->client)
1243 	{//ignore player's pitch and roll
1244 		VectorSet(tangles, 0, target->currentAngles[YAW], 0);
1245 	}
1246 
1247 	AngleVectors(tangles, forward, right, up);
1248 
1249 //get center of target
1250 	VectorAdd(target->absmin, target->absmax, tcenter);
1251 	VectorScale(tcenter, 0.5, tcenter);
1252 
1253 //get impact point
1254 	if(ppoint && !VectorCompare(ppoint, vec3_origin))
1255 	{
1256 		VectorCopy(ppoint, point);
1257 	}
1258 	else
1259 	{
1260 		return HL_NONE;
1261 	}
1262 
1263 /*
1264 //get impact dir
1265 	if(pdir && !VectorCompare(pdir, vec3_origin))
1266 	{
1267 		VectorCopy(pdir, dir);
1268 	}
1269 	else
1270 	{
1271 		return;
1272 	}
1273 
1274 //put point at controlled distance from center
1275 	VectorSubtract(point, tcenter, tempvec);
1276 	tempvec[2] = 0;
1277 	hdist = VectorLength(tempvec);
1278 
1279 	VectorMA(point, hdist - tradius, dir, point);
1280 	//now a point on the surface of a cylinder with a radius of tradius
1281 */
1282 	VectorSubtract(point, tcenter, point_dir);
1283 	VectorNormalize(point_dir);
1284 
1285 	//Get bottom to top (Vertical) position index
1286 	udot = DotProduct(up, point_dir);
1287 	if(udot>.800)
1288 		Vertical = 4;
1289 	else if(udot>.400)
1290 		Vertical = 3;
1291 	else if(udot>-.333)
1292 		Vertical = 2;
1293 	else if(udot>-.666)
1294 		Vertical = 1;
1295 	else
1296 		Vertical = 0;
1297 
1298 	//Get back to front (Forward) position index
1299 	fdot = DotProduct(forward, point_dir);
1300 	if(fdot>.666)
1301 		Forward = 4;
1302 	else if(fdot>.333)
1303 		Forward = 3;
1304 	else if(fdot>-.333)
1305 		Forward = 2;
1306 	else if(fdot>-.666)
1307 		Forward = 1;
1308 	else
1309 		Forward = 0;
1310 
1311 	//Get left to right (Lateral) position index
1312 	rdot = DotProduct(right, point_dir);
1313 	if(rdot>.666)
1314 		Lateral = 4;
1315 	else if(rdot>.333)
1316 		Lateral = 3;
1317 	else if(rdot>-.333)
1318 		Lateral = 2;
1319 	else if(rdot>-.666)
1320 		Lateral = 1;
1321 	else
1322 		Lateral = 0;
1323 
1324 	HitLoc = Vertical * 25 + Forward * 5 + Lateral;
1325 
1326 	if(HitLoc <= 10)
1327 	{//feet
1328 		if ( rdot > 0 )
1329 		{
1330 			return HL_FOOT_RT;
1331 		}
1332 		else
1333 		{
1334 			return HL_FOOT_LT;
1335 		}
1336 	}
1337 	else if(HitLoc <= 50)
1338 	{//legs
1339 		if ( rdot > 0 )
1340 		{
1341 			return HL_LEG_RT;
1342 		}
1343 		else
1344 		{
1345 			return HL_LEG_LT;
1346 		}
1347 	}
1348 	else if ( HitLoc == 56||HitLoc == 60||HitLoc == 61||HitLoc == 65||HitLoc == 66||HitLoc == 70 )
1349 	{//hands
1350 		if ( rdot > 0 )
1351 		{
1352 			return HL_HAND_RT;
1353 		}
1354 		else
1355 		{
1356 			return HL_HAND_LT;
1357 		}
1358 	}
1359 	else if ( HitLoc == 83||HitLoc == 87||HitLoc == 88||HitLoc == 92||HitLoc == 93||HitLoc == 97 )
1360 	{//arms
1361 		if ( rdot > 0 )
1362 		{
1363 			return HL_ARM_RT;
1364 		}
1365 		else
1366 		{
1367 			return HL_ARM_LT;
1368 		}
1369 	}
1370 	else if((HitLoc >= 107 && HitLoc <= 109)||
1371 		(HitLoc >= 112 && HitLoc <= 114)||
1372 		(HitLoc >= 117 && HitLoc <= 119))
1373 	{//head
1374 		return HL_HEAD;
1375 	}
1376 	else
1377 	{
1378 		if ( udot < 0.3 )
1379 		{
1380 			return HL_WAIST;
1381 		}
1382 		else if ( fdot < 0 )
1383 		{
1384 			if ( rdot > 0.4 )
1385 			{
1386 				return HL_BACK_RT;
1387 			}
1388 			else if ( rdot < -0.4 )
1389 			{
1390 				return HL_BACK_LT;
1391 			}
1392 			else
1393 			{
1394 				return HL_BACK;
1395 			}
1396 		}
1397 		else
1398 		{
1399 			if ( rdot > 0.3 )
1400 			{
1401 				return HL_CHEST_RT;
1402 			}
1403 			else if ( rdot < -0.3 )
1404 			{
1405 				return HL_CHEST_LT;
1406 			}
1407 			else
1408 			{
1409 				return HL_CHEST;
1410 			}
1411 		}
1412 	}
1413 	//return HL_NONE;
1414 }
1415 
G_PickPainAnim(gentity_t * self,vec3_t point,int damage,int hitLoc=HL_NONE)1416 int G_PickPainAnim( gentity_t *self, vec3_t point, int damage, int hitLoc = HL_NONE )
1417 {
1418 	if ( hitLoc == HL_NONE )
1419 	{
1420 		hitLoc = G_GetHitLocation( self, point );
1421 	}
1422 	switch( hitLoc )
1423 	{
1424 	case HL_FOOT_RT:
1425 		return BOTH_PAIN12;
1426 		//PAIN12 = right foot
1427 		break;
1428 	case HL_FOOT_LT:
1429 		return -1;
1430 		break;
1431 	case HL_LEG_RT:
1432 		if ( !Q_irand( 0, 1 ) )
1433 		{
1434 			return BOTH_PAIN11;
1435 		}
1436 		else
1437 		{
1438 			return BOTH_PAIN13;
1439 		}
1440 		//PAIN11 = twitch right leg
1441 		//PAIN13 = right knee
1442 		break;
1443 	case HL_LEG_LT:
1444 		return BOTH_PAIN14;
1445 		//PAIN14 = twitch left leg
1446 		break;
1447 	case HL_BACK_RT:
1448 		return BOTH_PAIN7;
1449 		//PAIN7 = med left shoulder
1450 		break;
1451 	case HL_BACK_LT:
1452 		return Q_irand( BOTH_PAIN15, BOTH_PAIN16 );
1453 		//PAIN15 = med right shoulder
1454 		//PAIN16 = twitch right shoulder
1455 		break;
1456 	case HL_BACK:
1457 		if ( !Q_irand( 0, 1 ) )
1458 		{
1459 			return BOTH_PAIN1;
1460 		}
1461 		else
1462 		{
1463 			return BOTH_PAIN5;
1464 		}
1465 		//PAIN1 = back
1466 		//PAIN5 = same as 1
1467 		break;
1468 	case HL_CHEST_RT:
1469 		return BOTH_PAIN3;
1470 		//PAIN3 = long, right shoulder
1471 		break;
1472 	case HL_CHEST_LT:
1473 		return BOTH_PAIN2;
1474 		//PAIN2 = long, left shoulder
1475 		break;
1476 	case HL_WAIST:
1477 	case HL_CHEST:
1478 		if ( !Q_irand( 0, 3 ) )
1479 		{
1480 			return BOTH_PAIN6;
1481 		}
1482 		else if ( !Q_irand( 0, 2 ) )
1483 		{
1484 			return BOTH_PAIN8;
1485 		}
1486 		else if ( !Q_irand( 0, 1 ) )
1487 		{
1488 			return BOTH_PAIN17;
1489 		}
1490 		else
1491 		{
1492 			return BOTH_PAIN19;
1493 		}
1494 		//PAIN6 = gut
1495 		//PAIN8 = chest
1496 		//PAIN17 = twitch crotch
1497 		//PAIN19 = med crotch
1498 		break;
1499 	case HL_ARM_RT:
1500 	case HL_HAND_RT:
1501 		return BOTH_PAIN9;
1502 		//PAIN9 = twitch right arm
1503 		break;
1504 	case HL_ARM_LT:
1505 	case HL_HAND_LT:
1506 		return BOTH_PAIN10;
1507 		//PAIN10 = twitch left arm
1508 		break;
1509 	case HL_HEAD:
1510 		return BOTH_PAIN4;
1511 		//PAIN4 = head
1512 		break;
1513 	default:
1514 		return -1;
1515 		break;
1516 	}
1517 }
1518 
1519 extern void G_BounceMissile( gentity_t *ent, trace_t *trace );
LimbThink(gentity_t * ent)1520 void LimbThink( gentity_t *ent )
1521 {//FIXME: just use object thinking?
1522 	vec3_t		origin;
1523 	trace_t		tr;
1524 
1525 	ent->nextthink = level.time + FRAMETIME;
1526 
1527 	if ( ent->enemy )
1528 	{//alert people that I am a piece of one of their friends
1529 		AddSightEvent( ent->enemy, ent->currentOrigin, 384, AEL_DISCOVERED );
1530 	}
1531 
1532 	if ( ent->s.pos.trType == TR_STATIONARY )
1533 	{//stopped
1534 		if ( level.time > ent->s.apos.trTime + ent->s.apos.trDuration )
1535 		{
1536 			ent->nextthink = level.time + Q_irand( 5000, 15000 );
1537 			ent->e_ThinkFunc = thinkF_G_FreeEntity;
1538 			//FIXME: these keep drawing for a frame or so after being freed?!  See them lerp to origin of world...
1539 		}
1540 		else
1541 		{
1542 			EvaluateTrajectory( &ent->s.apos, level.time, ent->currentAngles );
1543 		}
1544 		return;
1545 	}
1546 
1547 	// get current position
1548 	EvaluateTrajectory( &ent->s.pos, level.time, origin );
1549 	// get current angles
1550 	EvaluateTrajectory( &ent->s.apos, level.time, ent->currentAngles );
1551 
1552 	// trace a line from the previous position to the current position,
1553 	// ignoring interactions with the missile owner
1554 	gi.trace( &tr, ent->currentOrigin, ent->mins, ent->maxs, origin,
1555 		ent->owner ? ent->owner->s.number : ENTITYNUM_NONE, ent->clipmask, G2_NOCOLLIDE, 0 );
1556 
1557 	VectorCopy( tr.endpos, ent->currentOrigin );
1558 	if ( tr.startsolid )
1559 	{
1560 		tr.fraction = 0;
1561 	}
1562 
1563 
1564 	gi.linkentity( ent );
1565 
1566 	if ( tr.fraction != 1 )
1567 	{
1568 		G_BounceMissile( ent, &tr );
1569 		if ( ent->s.pos.trType == TR_STATIONARY )
1570 		{//stopped, stop spinning
1571 			//lay flat
1572 			//pitch
1573 			VectorCopy( ent->currentAngles, ent->s.apos.trBase );
1574 			vec3_t	flatAngles;
1575 			if ( ent->s.angles2[0] == -1 )
1576 			{//any pitch is okay
1577 				flatAngles[0] = ent->currentAngles[0];
1578 			}
1579 			else
1580 			{//lay flat
1581 				if ( ent->owner
1582 					&& ent->owner->client
1583 					&& ent->owner->client->NPC_class == CLASS_PROTOCOL
1584 					&& ent->count == BOTH_DISMEMBER_TORSO1 )
1585 				{
1586 					if ( ent->currentAngles[0] > 0 || ent->currentAngles[0] < -180 )
1587 					{
1588 						flatAngles[0] = -90;
1589 					}
1590 					else
1591 					{
1592 						flatAngles[0] = 90;
1593 					}
1594 				}
1595 				else
1596 				{
1597 					if ( ent->currentAngles[0] > 90 || ent->currentAngles[0] < -90 )
1598 					{
1599 						flatAngles[0] = 180;
1600 					}
1601 					else
1602 					{
1603 						flatAngles[0] = 0;
1604 					}
1605 				}
1606 			}
1607 			//yaw
1608 			flatAngles[1] = ent->currentAngles[1];
1609 			//roll
1610 			if ( ent->s.angles2[2] == -1 )
1611 			{//any roll is okay
1612 				flatAngles[2] = ent->currentAngles[2];
1613 			}
1614 			else
1615 			{
1616 				if ( ent->currentAngles[2] > 90 || ent->currentAngles[2] < -90 )
1617 				{
1618 					flatAngles[2] = 180;
1619 				}
1620 				else
1621 				{
1622 					flatAngles[2] = 0;
1623 				}
1624 			}
1625 			VectorSubtract( flatAngles, ent->s.apos.trBase, ent->s.apos.trDelta );
1626 			for ( int i = 0; i < 3; i++ )
1627 			{
1628 				ent->s.apos.trDelta[i] = AngleNormalize180( ent->s.apos.trDelta[i] );
1629 			}
1630 			ent->s.apos.trTime = level.time;
1631 			ent->s.apos.trDuration = 1000;
1632 			ent->s.apos.trType = TR_LINEAR_STOP;
1633 			//VectorClear( ent->s.apos.trDelta );
1634 		}
1635 	}
1636 }
1637 
1638 float hitLocHealthPercentage[HL_MAX] =
1639 {
1640 	0.0f,	//HL_NONE = 0,
1641 	0.05f,	//HL_FOOT_RT,
1642 	0.05f,	//HL_FOOT_LT,
1643 	0.20f,	//HL_LEG_RT,
1644 	0.20f,	//HL_LEG_LT,
1645 	0.30f,	//HL_WAIST,
1646 	0.15f,	//HL_BACK_RT,
1647 	0.15f,	//HL_BACK_LT,
1648 	0.30f,	//HL_BACK,
1649 	0.15f,	//HL_CHEST_RT,
1650 	0.15f,	//HL_CHEST_LT,
1651 	0.30f,	//HL_CHEST,
1652 	0.05f,	//HL_ARM_RT,
1653 	0.05f,	//HL_ARM_LT,
1654 	0.01f,	//HL_HAND_RT,
1655 	0.01f,	//HL_HAND_LT,
1656 	0.10f,	//HL_HEAD
1657 	0.0f,	//HL_GENERIC1,
1658 	0.0f,	//HL_GENERIC2,
1659 	0.0f,	//HL_GENERIC3,
1660 	0.0f,	//HL_GENERIC4,
1661 	0.0f,	//HL_GENERIC5,
1662 	0.0f	//HL_GENERIC6
1663 };
1664 
1665 char *hitLocName[HL_MAX] =
1666 {
1667 	"none",	//HL_NONE = 0,
1668 	"right foot",	//HL_FOOT_RT,
1669 	"left foot",	//HL_FOOT_LT,
1670 	"right leg",	//HL_LEG_RT,
1671 	"left leg",	//HL_LEG_LT,
1672 	"waist",	//HL_WAIST,
1673 	"back right shoulder",	//HL_BACK_RT,
1674 	"back left shoulder",	//HL_BACK_LT,
1675 	"back",	//HL_BACK,
1676 	"front right shouler",	//HL_CHEST_RT,
1677 	"front left shoulder",	//HL_CHEST_LT,
1678 	"chest",	//HL_CHEST,
1679 	"right arm",	//HL_ARM_RT,
1680 	"left arm",	//HL_ARM_LT,
1681 	"right hand",	//HL_HAND_RT,
1682 	"left hand",	//HL_HAND_LT,
1683 	"head",	//HL_HEAD
1684 	"generic1",	//HL_GENERIC1,
1685 	"generic2",	//HL_GENERIC2,
1686 	"generic3",	//HL_GENERIC3,
1687 	"generic4",	//HL_GENERIC4,
1688 	"generic5",	//HL_GENERIC5,
1689 	"generic6"	//HL_GENERIC6
1690 };
1691 
G_LimbLost(gentity_t * ent,int hitLoc)1692 qboolean G_LimbLost( gentity_t *ent, int hitLoc )
1693 {
1694 	switch ( hitLoc )
1695 	{
1696 	case HL_FOOT_RT:
1697 		if ( ent->locationDamage[HL_FOOT_RT] >= Q3_INFINITE )
1698 		{
1699 			return qtrue;
1700 		}
1701 		//NOTE: falls through
1702 	case HL_LEG_RT:
1703 		//NOTE: feet fall through
1704 		if ( ent->locationDamage[HL_LEG_RT] >= Q3_INFINITE )
1705 		{
1706 			return qtrue;
1707 		}
1708 		return qfalse;
1709 
1710 	case HL_FOOT_LT:
1711 		if ( ent->locationDamage[HL_FOOT_LT] >= Q3_INFINITE )
1712 		{
1713 			return qtrue;
1714 		}
1715 		//NOTE: falls through
1716 	case HL_LEG_LT:
1717 		//NOTE: feet fall through
1718 		if ( ent->locationDamage[HL_LEG_LT] >= Q3_INFINITE )
1719 		{
1720 			return qtrue;
1721 		}
1722 		return qfalse;
1723 
1724 	case HL_HAND_LT:
1725 		if ( ent->locationDamage[HL_HAND_LT] >= Q3_INFINITE )
1726 		{
1727 			return qtrue;
1728 		}
1729 		//NOTE: falls through
1730 	case HL_ARM_LT:
1731 	case HL_CHEST_LT:
1732 	case HL_BACK_RT:
1733 		//NOTE: hand falls through
1734 		if ( ent->locationDamage[HL_ARM_LT] >= Q3_INFINITE
1735 			|| ent->locationDamage[HL_CHEST_LT] >= Q3_INFINITE
1736 			|| ent->locationDamage[HL_BACK_RT] >= Q3_INFINITE
1737 			|| ent->locationDamage[HL_WAIST] >= Q3_INFINITE )
1738 		{
1739 			return qtrue;
1740 		}
1741 		return qfalse;
1742 
1743 	case HL_HAND_RT:
1744 		if ( ent->locationDamage[HL_HAND_RT] >= Q3_INFINITE )
1745 		{
1746 			return qtrue;
1747 		}
1748 		//NOTE: falls through
1749 	case HL_ARM_RT:
1750 	case HL_CHEST_RT:
1751 	case HL_BACK_LT:
1752 		//NOTE: hand falls through
1753 		if ( ent->locationDamage[HL_ARM_RT] >= Q3_INFINITE
1754 			|| ent->locationDamage[HL_CHEST_RT] >= Q3_INFINITE
1755 			|| ent->locationDamage[HL_BACK_LT] >= Q3_INFINITE
1756 			|| ent->locationDamage[HL_WAIST] >= Q3_INFINITE )
1757 		{
1758 			return qtrue;
1759 		}
1760 		return qfalse;
1761 
1762 	case HL_HEAD:
1763 		if ( ent->locationDamage[HL_HEAD] >= Q3_INFINITE )
1764 		{
1765 			return qtrue;
1766 		}
1767 		//NOTE: falls through
1768 	case HL_WAIST:
1769 		//NOTE: head falls through
1770 		if ( ent->locationDamage[HL_WAIST] >= Q3_INFINITE )
1771 		{
1772 			return qtrue;
1773 		}
1774 		return qfalse;
1775 	default:
1776 		return (qboolean)(ent->locationDamage[hitLoc] >= Q3_INFINITE);
1777 	}
1778 }
1779 
G_Dismember(gentity_t * ent,vec3_t point,const char * limbBone,const char * rotateBone,char * limbName,char * limbCapName,char * stubCapName,char * limbTagName,char * stubTagName,int limbAnim,float limbRollBase,float limbPitchBase,int damage,int hitLoc)1780 static qboolean G_Dismember( gentity_t *ent, vec3_t point,
1781 				 const char *limbBone, const char *rotateBone, char *limbName,
1782 				 char *limbCapName, char *stubCapName, char *limbTagName, char *stubTagName,
1783 				 int limbAnim, float limbRollBase, float limbPitchBase,
1784 				 int damage, int hitLoc )
1785 {
1786 	int newBolt;
1787 	vec3_t	dir, newPoint, limbAngles = {0,ent->client->ps.legsYaw,0};
1788 	gentity_t *limb;
1789 	trace_t	trace;
1790 
1791 	//make sure this limb hasn't been lopped off already!
1792 	if ( gi.G2API_GetSurfaceRenderStatus( &ent->ghoul2[ent->playerModel], limbName ) )
1793 	{//already lost this limb
1794 		return qfalse;
1795 	}
1796 
1797 	//NOTE: only reason I have this next part is because G2API_GetSurfaceRenderStatus is *not* working
1798 	if ( G_LimbLost( ent, hitLoc ) )
1799 	{//already lost this limb
1800 		return qfalse;
1801 	}
1802 
1803 	//FIXME: when timescale is high, can sometimes cut off a surf that includes a surf that was already cut off
1804 //0) create a limb ent
1805 	VectorCopy( point, newPoint );
1806 	newPoint[2] += 6;
1807 	limb = G_Spawn();
1808 	G_SetOrigin( limb, newPoint );
1809 	//VectorCopy(ent->currentAngles,limbAngles);
1810 	//G_SetAngles( limb, ent->currentAngles );
1811 	VectorCopy( newPoint, limb->s.pos.trBase );
1812 //1) copy the g2 instance of the victim into the limb
1813 	gi.G2API_CopyGhoul2Instance( ent->ghoul2, limb->ghoul2, -1 );
1814 	limb->playerModel = 0;//assumption!
1815 	limb->craniumBone = ent->craniumBone;
1816 	limb->cervicalBone = ent->cervicalBone;
1817 	limb->thoracicBone = ent->thoracicBone;
1818 	limb->upperLumbarBone = ent->upperLumbarBone;
1819 	limb->lowerLumbarBone = ent->lowerLumbarBone;
1820 	limb->hipsBone = ent->hipsBone;
1821 	limb->rootBone = ent->rootBone;
1822 //2) set the root surf on the limb
1823 	if ( limbTagName )
1824 	{//add smoke to cap tag
1825 		newBolt = gi.G2API_AddBolt( &limb->ghoul2[limb->playerModel], limbTagName );
1826 		if ( newBolt != -1 )
1827 		{
1828 			G_PlayEffect( "blaster/smoke_bolton", limb->playerModel, newBolt, limb->s.number);
1829 		}
1830 	}
1831 	/*
1832 	if ( limbBone && hitLoc == HL_HEAD )
1833 	{//stop the current anim on the limb?
1834 		gi.G2API_StopBoneAnim( &limb->ghoul2[limb->playerModel], "model_root" );
1835 		gi.G2API_StopBoneAnim( &limb->ghoul2[limb->playerModel], "motion" );
1836 		gi.G2API_StopBoneAnim( &limb->ghoul2[limb->playerModel], "upper_lumbar" );
1837 	}
1838 	*/
1839 	gi.G2API_StopBoneAnimIndex( &limb->ghoul2[limb->playerModel], limb->hipsBone );
1840 
1841 	gi.G2API_SetRootSurface( limb->ghoul2, limb->playerModel, limbName );
1842 	/*
1843 	if ( limbBone && hitLoc != HL_WAIST )
1844 	{//play the dismember anim on the limb?
1845 		//FIXME: screws up origin
1846 		animation_t *animations = level.knownAnimFileSets[ent->client->clientInfo.animFileIndex].animations;
1847 		//play the proper dismember anim on the limb
1848 		gi.G2API_SetBoneAnim(&limb->ghoul2[limb->playerModel], 0, animations[limbAnim].firstFrame - 1,
1849 							animations[limbAnim].numFrames + animations[limbAnim].firstFrame - 1,
1850 							BONE_ANIM_OVERRIDE_FREEZE, 1, cg.time);
1851 	}
1852 	*/
1853 	if ( limbBone && hitLoc == HL_WAIST && ent->client->NPC_class == CLASS_PROTOCOL )
1854 	{//play the dismember anim on the limb?
1855 		gi.G2API_StopBoneAnim( &limb->ghoul2[limb->playerModel], "model_root" );
1856 		gi.G2API_StopBoneAnim( &limb->ghoul2[limb->playerModel], "motion" );
1857 		gi.G2API_StopBoneAnim( &limb->ghoul2[limb->playerModel], "pelvis" );
1858 		gi.G2API_StopBoneAnim( &limb->ghoul2[limb->playerModel], "upper_lumbar" );
1859 		//FIXME: screws up origin
1860 		animation_t *animations = level.knownAnimFileSets[ent->client->clientInfo.animFileIndex].animations;
1861 		//play the proper dismember anim on the limb
1862 		gi.G2API_SetBoneAnim(&limb->ghoul2[limb->playerModel], 0, animations[limbAnim].firstFrame,
1863 							animations[limbAnim].numFrames + animations[limbAnim].firstFrame,
1864 							BONE_ANIM_OVERRIDE_FREEZE, 1, cg.time, -1, -1 );
1865 	}
1866 	if ( rotateBone )
1867 	{
1868  		gi.G2API_SetNewOrigin( &limb->ghoul2[0], gi.G2API_AddBolt( &limb->ghoul2[0], rotateBone ) );
1869 
1870 		//now let's try to position the limb at the *exact* right spot
1871 		int newBolt = gi.G2API_AddBolt( &ent->ghoul2[0], rotateBone );
1872 		if ( newBolt != -1 )
1873 		{
1874 			int	actualTime = (cg.time?cg.time:level.time);
1875 			mdxaBone_t	boltMatrix;
1876 			vec3_t	angles;
1877 
1878 			VectorSet( angles, 0, ent->currentAngles[YAW], 0 );
1879 			gi.G2API_GetBoltMatrix( ent->ghoul2, ent->playerModel, newBolt,
1880 							&boltMatrix, angles, ent->currentOrigin,
1881 							actualTime, NULL, ent->s.modelScale );
1882 			gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, limb->s.origin );
1883 			G_SetOrigin( limb, limb->s.origin );
1884 			VectorCopy( limb->s.origin, limb->s.pos.trBase );
1885 			//angles, too
1886 			/*
1887 			vec3_t	limbF, limbR;
1888 			newBolt = gi.G2API_AddBolt( &ent->ghoul2[0], limbBone );
1889 			if ( newBolt != -1 )
1890 			{
1891 				gi.G2API_GetBoltMatrix( ent->ghoul2, ent->playerModel, newBolt,
1892 								&boltMatrix, angles, ent->currentOrigin,
1893 								actualTime, NULL, ent->s.modelScale );
1894 				gi.G2API_GiveMeVectorFromMatrix( boltMatrix, POSITIVE_X, limbF );
1895 				gi.G2API_GiveMeVectorFromMatrix( boltMatrix, NEGATIVE_Y, limbR );
1896 				vectoangles( limbF, limbAngles );
1897 				vectoangles( limbR, angles );
1898 				limbAngles[YAW] += 180;
1899 				limbAngles[ROLL] = angles[PITCH]*-1.0f;
1900 			}
1901 			*/
1902 		}
1903 	}
1904 	if ( limbCapName )
1905 	{//turn on caps
1906 		gi.G2API_SetSurfaceOnOff( &limb->ghoul2[limb->playerModel], limbCapName, 0 );
1907 	}
1908 //3) turn off w/descendants that surf in original model
1909 //NOTE: we actually change the ent's stuff on the cgame side so that there is no 50ms lag
1910 //		also, if the limb was going to start in solid, we can delete it and return
1911 	if ( stubTagName )
1912 	{//add smoke to cap surf, spawn effect
1913 		limb->target = G_NewString( stubTagName );
1914 		/*
1915 		newBolt = gi.G2API_AddBolt( &ent->ghoul2[ent->playerModel], stubTagName );
1916 		if ( newBolt != -1 )
1917 		{
1918 			G_PlayEffect( "blaster/smoke_bolton", ent->playerModel, newBolt, ent->s.number);
1919 		}
1920 		*/
1921 	}
1922 	if ( limbName )
1923 	{
1924 		limb->target2 = G_NewString( limbName );
1925 		//gi.G2API_SetSurfaceOnOff( &ent->ghoul2[ent->playerModel], limbName, 0x00000100 );//G2SURFACEFLAG_NODESCENDANTS
1926 	}
1927 	if ( stubCapName )
1928 	{//turn on caps
1929 		limb->target3 = G_NewString( stubCapName );
1930 		//gi.G2API_SetSurfaceOnOff( &ent->ghoul2[ent->playerModel], stubCapName, 0 );
1931 	}
1932 	limb->count = limbAnim;
1933 //
1934 	limb->s.radius = 60;
1935 //4) toss the limb away
1936 	limb->classname = "limb";
1937 	limb->owner = ent;
1938 	limb->enemy = ent->enemy;
1939 	if ( ent->weaponModel >= 0 && !ent->client->ps.saberInFlight )
1940 	{//the corpse hasn't dropped their weapon
1941 		if ( limbAnim == BOTH_DISMEMBER_RARM || limbAnim == BOTH_DISMEMBER_TORSO1 )//&& ent->s.weapon == WP_SABER && ent->weaponModel != -1 )
1942 		{//FIXME: is this first check needed with this lower one?
1943 			if ( !gi.G2API_GetSurfaceRenderStatus( &limb->ghoul2[0], "r_hand" ) )
1944 			{//only copy the weapon over if the right hand is actually on this limb...
1945 				//copy it to limb
1946 				if ( ent->s.weapon != WP_NONE )
1947 				{//only if they actually still have a weapon
1948 					limb->s.weapon = ent->s.weapon;
1949 					limb->weaponModel = ent->weaponModel;
1950 				}//else - weaponModel is not -1 but don't have a weapon?  Oops, somehow G2 model wasn't removed?
1951 				//remove it on owner
1952 				if ( ent->weaponModel >= 0 )
1953 				{
1954 					gi.G2API_RemoveGhoul2Model( ent->ghoul2, ent->weaponModel );
1955 					ent->weaponModel = -1;
1956 				}
1957 				if ( ent->client->ps.saberEntityNum != ENTITYNUM_NONE && ent->client->ps.saberEntityNum > 0 )
1958 				{//remove the owner ent's saber model and entity
1959 					if ( g_entities[ent->client->ps.saberEntityNum].inuse )
1960 					{
1961 						G_FreeEntity( &g_entities[ent->client->ps.saberEntityNum] );
1962 					}
1963 					ent->client->ps.saberEntityNum = ENTITYNUM_NONE;
1964 				}
1965 			}
1966 			else
1967 			{
1968 				if ( ent->weaponModel >= 0 )
1969 				{
1970 					gi.G2API_RemoveGhoul2Model( limb->ghoul2, ent->weaponModel );
1971 					limb->weaponModel = -1;
1972 				}
1973 			}
1974 		}
1975 		else
1976 		{
1977 			if ( ent->weaponModel >= 0 )
1978 			{
1979 				gi.G2API_RemoveGhoul2Model( limb->ghoul2, ent->weaponModel );
1980 				limb->weaponModel = -1;
1981 			}
1982 		}
1983 	}
1984 
1985 	limb->e_clThinkFunc = clThinkF_CG_Limb;
1986 	limb->e_ThinkFunc = thinkF_LimbThink;
1987 	limb->nextthink = level.time + FRAMETIME;
1988 	gi.linkentity( limb );
1989 	//need size, contents, clipmask
1990 	limb->svFlags = SVF_USE_CURRENT_ORIGIN;
1991 	limb->clipmask = MASK_SOLID;
1992 	limb->contents = CONTENTS_CORPSE;
1993 	VectorSet( limb->mins, -3.0f, -3.0f, -6.0f );
1994 	VectorSet( limb->maxs, 3.0f, 3.0f, 6.0f );
1995 
1996 	//make sure it doesn't start in solid
1997 	gi.trace( &trace, limb->s.pos.trBase, limb->mins, limb->maxs, limb->s.pos.trBase, limb->s.number, limb->clipmask, G2_NOCOLLIDE, 0 );
1998 	if ( trace.startsolid )
1999 	{
2000 		limb->s.pos.trBase[2] -= limb->mins[2];
2001 		gi.trace( &trace, limb->s.pos.trBase, limb->mins, limb->maxs, limb->s.pos.trBase, limb->s.number, limb->clipmask, G2_NOCOLLIDE, 0 );
2002 		if ( trace.startsolid )
2003 		{
2004 			limb->s.pos.trBase[2] += limb->mins[2];
2005 			gi.trace( &trace, limb->s.pos.trBase, limb->mins, limb->maxs, limb->s.pos.trBase, limb->s.number, limb->clipmask, G2_NOCOLLIDE, 0 );
2006 			if ( trace.startsolid )
2007 			{//stuck?  don't remove
2008 				G_FreeEntity( limb );
2009 				return qfalse;
2010 			}
2011 		}
2012 	}
2013 
2014 	//move it
2015 	VectorCopy( limb->s.pos.trBase, limb->currentOrigin );
2016 	gi.linkentity( limb );
2017 
2018 	limb->s.eType = ET_THINKER;//ET_GENERAL;
2019 	limb->physicsBounce = 0.2f;
2020 	limb->s.pos.trType = TR_GRAVITY;
2021 	limb->s.pos.trTime = level.time;								// move a bit on the very first frame
2022 	VectorSubtract( point, ent->currentOrigin, dir );
2023 	VectorNormalize( dir );
2024 	//no trDuration?
2025 	//spin it
2026 	//new way- try to preserve the exact angle and position of the limb as it was when attached
2027 	VectorSet( limb->s.angles2, limbPitchBase, 0, limbRollBase );
2028 	VectorCopy( limbAngles, limb->s.apos.trBase );
2029 	/*
2030 	//old way- just set an angle...
2031 	limb->s.apos.trBase[0] += limbPitchBase;
2032 	limb->s.apos.trBase[1] = ent->client->ps.viewangles[1];
2033 	limb->s.apos.trBase[2] += limbRollBase;
2034 	*/
2035 	limb->s.apos.trTime = level.time;
2036 	limb->s.apos.trType = TR_LINEAR;
2037 	VectorClear( limb->s.apos.trDelta );
2038 
2039 	if ( hitLoc == HL_HAND_RT || hitLoc == HL_HAND_LT )
2040 	{//hands fly farther
2041 		VectorMA( ent->client->ps.velocity, 200, dir, limb->s.pos.trDelta );
2042 		//make it bounce some
2043 		limb->s.eFlags |= EF_BOUNCE_HALF;
2044 		limb->s.apos.trDelta[0] = Q_irand( -300, 300 );
2045 		limb->s.apos.trDelta[1] = Q_irand( -800, 800 );
2046 	}
2047 	else if ( limbAnim == BOTH_DISMEMBER_HEAD1
2048 		|| limbAnim == BOTH_DISMEMBER_LARM
2049 		|| limbAnim == BOTH_DISMEMBER_RARM )
2050 	{//head and arms don't fly as far
2051 		limb->s.eFlags |= EF_BOUNCE_SHRAPNEL;
2052 		VectorMA( ent->client->ps.velocity, 150, dir, limb->s.pos.trDelta );
2053 		limb->s.apos.trDelta[0] = Q_irand( -200, 200 );
2054 		limb->s.apos.trDelta[1] = Q_irand( -400, 400 );
2055 	}
2056 	else// if ( limbAnim == BOTH_DISMEMBER_TORSO1 || limbAnim == BOTH_DISMEMBER_LLEG || limbAnim == BOTH_DISMEMBER_RLEG )
2057 	{//everything else just kinda falls off
2058 		limb->s.eFlags |= EF_BOUNCE_SHRAPNEL;
2059 		VectorMA( ent->client->ps.velocity, 100, dir, limb->s.pos.trDelta );
2060 		limb->s.apos.trDelta[0] = Q_irand( -100, 100 );
2061 		limb->s.apos.trDelta[1] = Q_irand( -200, 200 );
2062 	}
2063 	//roll? No, doesn't work...
2064 	//limb->s.apos.trDelta[2] = Q_irand( -300, 300 );//FIXME: this scales it down @ 80% and does weird stuff in timescale != 1.0
2065 	//limb->s.apos.trDelta[2] = limbRoll;
2066 
2067 	//preserve scale so giants don't have tiny limbs
2068 	VectorCopy( ent->s.modelScale, limb->s.modelScale );
2069 
2070 	//mark ent as dismembered
2071 	ent->locationDamage[hitLoc] = Q3_INFINITE;//mark this limb as gone
2072 	ent->client->dismembered = qtrue;
2073 
2074 	return qtrue;
2075 }
2076 
G_Dismemberable(gentity_t * self,int hitLoc)2077 static qboolean G_Dismemberable( gentity_t *self, int hitLoc )
2078 {
2079 	if ( self->client->dismembered )
2080 	{//cannot dismember me right now
2081 		return qfalse;
2082 	}
2083 	if ( g_dismemberment->integer < 11381138 && !g_saberRealisticCombat->integer )
2084 	{
2085 		if ( g_dismemberProbabilities->value > 0.0f )
2086 		{//use the ent-specific dismemberProbabilities
2087 			float dismemberProb = 0;
2088 			// check which part of the body it is. Then check the npc's probability
2089 			// of that body part coming off, if it doesn't pass, return out.
2090 			switch ( hitLoc )
2091 			{
2092 			case HL_LEG_RT:
2093 			case HL_LEG_LT:
2094 				dismemberProb = self->client->dismemberProbLegs;
2095 				break;
2096 			case HL_WAIST:
2097 				dismemberProb = self->client->dismemberProbWaist;
2098 				break;
2099 			case HL_BACK_RT:
2100 			case HL_BACK_LT:
2101 			case HL_CHEST_RT:
2102 			case HL_CHEST_LT:
2103 			case HL_ARM_RT:
2104 			case HL_ARM_LT:
2105 				dismemberProb = self->client->dismemberProbArms;
2106 				break;
2107 			case HL_HAND_RT:
2108 			case HL_HAND_LT:
2109 				dismemberProb = self->client->dismemberProbHands;
2110 				break;
2111 			case HL_HEAD:
2112 				dismemberProb = self->client->dismemberProbHead;
2113 				break;
2114 			default:
2115 				return qfalse;
2116 				break;
2117 			}
2118 
2119 			//check probability of this happening on this npc
2120 			if ( floor((Q_flrand( 1, 100 )*g_dismemberProbabilities->value)) > dismemberProb*2.0f )//probabilities seemed really really low, had to crank them up
2121 			{
2122 				return qfalse;
2123 			}
2124 		}
2125 	}
2126 	return qtrue;
2127 }
2128 
G_Dismemberable2(gentity_t * self,int hitLoc)2129 static qboolean G_Dismemberable2( gentity_t *self, int hitLoc )
2130 {
2131 	if ( self->client->dismembered )
2132 	{//cannot dismember me right now
2133 		return qfalse;
2134 	}
2135 	if ( g_dismemberment->integer < 11381138 && !g_saberRealisticCombat->integer )
2136 	{
2137 		if ( g_dismemberProbabilities->value <= 0.0f )
2138 		{//add the passed-in damage to the locationDamage array, check to see if it's taken enough damage to actually dismember
2139 			if ( self->locationDamage[hitLoc] < (self->client->ps.stats[STAT_MAX_HEALTH]*hitLocHealthPercentage[hitLoc]) )
2140 			{//this location has not taken enough damage to dismember
2141 				return qfalse;
2142 			}
2143 		}
2144 	}
2145 	return qtrue;
2146 }
2147 
2148 extern qboolean G_StandardHumanoid( const char *modelName );
G_DoDismemberment(gentity_t * self,vec3_t point,int mod,int damage,int hitLoc,qboolean force=qfalse)2149 qboolean G_DoDismemberment( gentity_t *self, vec3_t point, int mod, int damage, int hitLoc, qboolean force = qfalse )
2150 {
2151 extern cvar_t	*g_iscensored;
2152 	// dismemberment -- FIXME: should have a check for how long npc has been dead so people can't
2153 	// continue to dismember a dead body long after it's been dead
2154 	//NOTE that you can only cut one thing off unless the dismemberment is >= 11381138
2155 #ifdef GERMAN_CENSORED
2156 	if ( 0 ) //germany == censorship
2157 #else
2158 	if ( !g_iscensored->integer && ( g_dismemberment->integer || g_saberRealisticCombat->integer > 1 ) && mod == MOD_SABER )//only lightsaber
2159 #endif
2160 	{//FIXME: don't do strcmps here
2161 		if ( G_StandardHumanoid( self->NPC_type )
2162 			&& (force||g_dismemberProbabilities->value>0.0f||G_Dismemberable2( self, hitLoc )) )
2163 		{//either it's a forced dismemberment or we're using probabilities (which are checked before this) or we've done enough damage to this location
2164 			//FIXME: check the hitLoc and hitDir against the cap tag for the place
2165 			//where the split will be- if the hit dir is roughly perpendicular to
2166 			//the direction of the cap, then the split is allowed, otherwise we
2167 			//hit it at the wrong angle and should not dismember...
2168 			char	*limbBone = NULL, *rotateBone = NULL, *limbName = NULL, *limbCapName = NULL, *stubCapName = NULL, *limbTagName = NULL, *stubTagName = NULL;
2169 			int		anim = -1;
2170 			float	limbRollBase = 0, limbPitchBase = 0;
2171 			qboolean doDismemberment = qfalse;
2172 
2173 			switch( hitLoc )//self->hitLoc
2174 			{
2175 			case HL_LEG_RT:
2176 				if ( g_dismemberment->integer > 1 )
2177 				{
2178 					doDismemberment = qtrue;
2179 					limbBone = "rtibia";
2180 					rotateBone = "rtalus";
2181 					limbName = "r_leg";
2182 					limbCapName = "r_leg_cap_hips_off";
2183 					stubCapName = "hips_cap_r_leg_off";
2184 					limbTagName = "*r_leg_cap_hips";
2185 					stubTagName = "*hips_cap_r_leg";
2186 					anim = BOTH_DISMEMBER_RLEG;
2187 					limbRollBase = 0;
2188 					limbPitchBase = 0;
2189 				}
2190 				break;
2191 			case HL_LEG_LT:
2192 				if ( g_dismemberment->integer > 1 )
2193 				{
2194 					doDismemberment = qtrue;
2195 					limbBone = "ltibia";
2196 					rotateBone = "ltalus";
2197 					limbName = "l_leg";
2198 					limbCapName = "l_leg_cap_hips_off";
2199 					stubCapName = "hips_cap_l_leg_off";
2200 					limbTagName = "*l_leg_cap_hips";
2201 					stubTagName = "*hips_cap_l_leg";
2202 					anim = BOTH_DISMEMBER_LLEG;
2203 					limbRollBase = 0;
2204 					limbPitchBase = 0;
2205 				}
2206 				break;
2207 			case HL_WAIST:
2208 				if ( g_dismemberment->integer > 2 &&
2209 					(!self->s.number||!self->message))
2210 				{
2211 					doDismemberment = qtrue;
2212 					limbBone = "pelvis";
2213 					rotateBone = "thoracic";
2214 					limbName = "torso";
2215 					limbCapName = "torso_cap_hips_off";
2216 					stubCapName = "hips_cap_torso_off";
2217 					limbTagName = "*torso_cap_hips";
2218 					stubTagName = "*hips_cap_torso";
2219 					anim = BOTH_DISMEMBER_TORSO1;
2220 					limbRollBase = 0;
2221 					limbPitchBase = 0;
2222 				}
2223 				break;
2224 			case HL_CHEST_RT:
2225 			case HL_ARM_RT:
2226 			case HL_BACK_RT:
2227 				if ( g_dismemberment->integer )
2228 				{
2229 					doDismemberment = qtrue;
2230 					limbBone = "rhumerus";
2231 					rotateBone = "rradius";
2232 					limbName = "r_arm";
2233 					limbCapName = "r_arm_cap_torso_off";
2234 					stubCapName = "torso_cap_r_arm_off";
2235 					limbTagName = "*r_arm_cap_torso";
2236 					stubTagName = "*torso_cap_r_arm";
2237 					anim = BOTH_DISMEMBER_RARM;
2238 					limbRollBase = 0;
2239 					limbPitchBase = 0;
2240 				}
2241 				break;
2242 			case HL_CHEST_LT:
2243 			case HL_ARM_LT:
2244 			case HL_BACK_LT:
2245 				if ( g_dismemberment->integer &&
2246 					(!self->s.number||!self->message))
2247 				{//either the player or not carrying a key on my arm
2248 					doDismemberment = qtrue;
2249 					limbBone = "lhumerus";
2250 					rotateBone = "lradius";
2251 					limbName = "l_arm";
2252 					limbCapName = "l_arm_cap_torso_off";
2253 					stubCapName = "torso_cap_l_arm_off";
2254 					limbTagName = "*l_arm_cap_torso";
2255 					stubTagName = "*torso_cap_l_arm";
2256 					anim = BOTH_DISMEMBER_LARM;
2257 					limbRollBase = 0;
2258 					limbPitchBase = 0;
2259 				}
2260 				break;
2261 			case HL_HAND_RT:
2262 				if ( g_dismemberment->integer )
2263 				{
2264 					doDismemberment = qtrue;
2265 					limbBone = "rradiusX";
2266 					rotateBone = "rhand";
2267 					limbName = "r_hand";
2268 					limbCapName = "r_hand_cap_r_arm_off";
2269 					stubCapName = "r_arm_cap_r_hand_off";
2270 					limbTagName = "*r_hand_cap_r_arm";
2271 					stubTagName = "*r_arm_cap_r_hand";
2272 					anim = BOTH_DISMEMBER_RARM;
2273 					limbRollBase = 0;
2274 					limbPitchBase = 0;
2275 				}
2276 				break;
2277 			case HL_HAND_LT:
2278 				if ( g_dismemberment->integer )
2279 				{
2280 					doDismemberment = qtrue;
2281 					limbBone = "lradiusX";
2282 					rotateBone = "lhand";
2283 					limbName = "l_hand";
2284 					limbCapName = "l_hand_cap_l_arm_off";
2285 					stubCapName = "l_arm_cap_l_hand_off";
2286 					limbTagName = "*l_hand_cap_l_arm";
2287 					stubTagName = "*l_arm_cap_l_hand";
2288 					anim = BOTH_DISMEMBER_RARM;
2289 					limbRollBase = 0;
2290 					limbPitchBase = 0;
2291 				}
2292 				break;
2293 			case HL_HEAD:
2294 				if ( g_dismemberment->integer > 2 )
2295 				{
2296 					doDismemberment = qtrue;
2297 					limbBone = "cervical";
2298 					rotateBone = "cranium";
2299 					limbName = "head";
2300 					limbCapName = "head_cap_torso_off";
2301 					stubCapName = "torso_cap_head_off";
2302 					limbTagName = "*head_cap_torso";
2303 					stubTagName = "*torso_cap_head";
2304 					anim = BOTH_DISMEMBER_HEAD1;
2305 					limbRollBase = -1;
2306 					limbPitchBase = -1;
2307 				}
2308 				break;
2309 			case HL_FOOT_RT:
2310 			case HL_FOOT_LT:
2311 			case HL_CHEST:
2312 			case HL_BACK:
2313 			default:
2314 				break;
2315 			}
2316 			if ( doDismemberment )
2317 			{
2318 				return G_Dismember( self, point, limbBone, rotateBone, limbName,
2319 					limbCapName, stubCapName, limbTagName, stubTagName,
2320 					anim, limbRollBase, limbPitchBase, damage, hitLoc );
2321 			}
2322 		}
2323 	}
2324 	return qfalse;
2325 }
2326 
G_CheckSpecialDeathAnim(gentity_t * self,vec3_t point,int damage,int mod,int hitLoc)2327 static int G_CheckSpecialDeathAnim( gentity_t *self, vec3_t point, int damage, int mod, int hitLoc )
2328 {
2329 	int deathAnim = -1;
2330 
2331 	if ( PM_InRoll( &self->client->ps ) )
2332 	{
2333 		deathAnim = BOTH_DEATH_ROLL;		//# Death anim from a roll
2334 	}
2335 	else if ( PM_FlippingAnim( self->client->ps.legsAnim ) )
2336 	{
2337 		deathAnim = BOTH_DEATH_FLIP;		//# Death anim from a flip
2338 	}
2339 	else if ( PM_SpinningAnim( self->client->ps.legsAnim ) )
2340 	{
2341 		float yawDiff = AngleNormalize180(AngleNormalize180(self->client->renderInfo.torsoAngles[YAW]) - AngleNormalize180(self->client->ps.viewangles[YAW]));
2342 		if ( yawDiff > 135 || yawDiff < -135 )
2343 		{
2344 			deathAnim = BOTH_DEATH_SPIN_180;	//# Death anim when facing backwards
2345 		}
2346 		else if ( yawDiff < -60 )
2347 		{
2348 			deathAnim = BOTH_DEATH_SPIN_90_R;	//# Death anim when facing 90 degrees right
2349 		}
2350 		else if ( yawDiff > 60 )
2351 		{
2352 			deathAnim = BOTH_DEATH_SPIN_90_L;	//# Death anim when facing 90 degrees left
2353 		}
2354 	}
2355 	else if ( PM_InKnockDown( &self->client->ps ) )
2356 	{//since these happen a lot, let's handle them case by case
2357 		int animLength = PM_AnimLength( self->client->clientInfo.animFileIndex, (animNumber_t)self->client->ps.legsAnim );
2358 		switch ( self->client->ps.legsAnim )
2359 		{
2360 		case BOTH_KNOCKDOWN1:
2361 			if ( animLength - self->client->ps.legsAnimTimer > 100 )
2362 			{//on our way down
2363 				if ( self->client->ps.legsAnimTimer > 600 )
2364 				{//still partially up
2365 					deathAnim = BOTH_DEATH_FALLING_UP;
2366 				}
2367 				else
2368 				{//down
2369 					deathAnim = BOTH_DEATH_LYING_UP;
2370 				}
2371 			}
2372 			break;
2373 		case BOTH_KNOCKDOWN2:
2374 			if ( animLength - self->client->ps.legsAnimTimer > 700 )
2375 			{//on our way down
2376 				if ( self->client->ps.legsAnimTimer > 600 )
2377 				{//still partially up
2378 					deathAnim = BOTH_DEATH_FALLING_UP;
2379 				}
2380 				else
2381 				{//down
2382 					deathAnim = BOTH_DEATH_LYING_UP;
2383 				}
2384 			}
2385 			break;
2386 		case BOTH_KNOCKDOWN3:
2387 			if ( animLength - self->client->ps.legsAnimTimer > 100 )
2388 			{//on our way down
2389 				if ( self->client->ps.legsAnimTimer > 1300 )
2390 				{//still partially up
2391 					deathAnim = BOTH_DEATH_FALLING_DN;
2392 				}
2393 				else
2394 				{//down
2395 					deathAnim = BOTH_DEATH_LYING_DN;
2396 				}
2397 			}
2398 			break;
2399 		case BOTH_KNOCKDOWN4:
2400 			if ( animLength - self->client->ps.legsAnimTimer > 300 )
2401 			{//on our way down
2402 				if ( self->client->ps.legsAnimTimer > 350 )
2403 				{//still partially up
2404 					deathAnim = BOTH_DEATH_FALLING_UP;
2405 				}
2406 				else
2407 				{//down
2408 					deathAnim = BOTH_DEATH_LYING_UP;
2409 				}
2410 			}
2411 			else
2412 			{//crouch death
2413 				vec3_t fwd;
2414 				AngleVectors( self->currentAngles, fwd, NULL, NULL );
2415 				float	thrown = DotProduct( fwd, self->client->ps.velocity );
2416 				if ( thrown < -150 )
2417 				{
2418 					deathAnim = BOTH_DEATHBACKWARD1;	//# Death anim when crouched and thrown back
2419 				}
2420 				else
2421 				{
2422 					deathAnim = BOTH_DEATH_CROUCHED;	//# Death anim when crouched
2423 				}
2424 			}
2425 			break;
2426 		case BOTH_KNOCKDOWN5:
2427 			if ( self->client->ps.legsAnimTimer < 750 )
2428 			{//flat
2429 				deathAnim = BOTH_DEATH_LYING_DN;
2430 			}
2431 			break;
2432 		case BOTH_GETUP1:
2433 			if ( self->client->ps.legsAnimTimer < 350 )
2434 			{//standing up
2435 			}
2436 			else if ( self->client->ps.legsAnimTimer < 800 )
2437 			{//crouching
2438 				vec3_t fwd;
2439 				AngleVectors( self->currentAngles, fwd, NULL, NULL );
2440 				float	thrown = DotProduct( fwd, self->client->ps.velocity );
2441 				if ( thrown < -150 )
2442 				{
2443 					deathAnim = BOTH_DEATHBACKWARD1;	//# Death anim when crouched and thrown back
2444 				}
2445 				else
2446 				{
2447 					deathAnim = BOTH_DEATH_CROUCHED;	//# Death anim when crouched
2448 				}
2449 			}
2450 			else
2451 			{//lying down
2452 				if ( animLength - self->client->ps.legsAnimTimer > 450 )
2453 				{//partially up
2454 					deathAnim = BOTH_DEATH_FALLING_UP;
2455 				}
2456 				else
2457 				{//down
2458 					deathAnim = BOTH_DEATH_LYING_UP;
2459 				}
2460 			}
2461 			break;
2462 		case BOTH_GETUP2:
2463 			if ( self->client->ps.legsAnimTimer < 150 )
2464 			{//standing up
2465 			}
2466 			else if ( self->client->ps.legsAnimTimer < 850 )
2467 			{//crouching
2468 				vec3_t fwd;
2469 				AngleVectors( self->currentAngles, fwd, NULL, NULL );
2470 				float	thrown = DotProduct( fwd, self->client->ps.velocity );
2471 				if ( thrown < -150 )
2472 				{
2473 					deathAnim = BOTH_DEATHBACKWARD1;	//# Death anim when crouched and thrown back
2474 				}
2475 				else
2476 				{
2477 					deathAnim = BOTH_DEATH_CROUCHED;	//# Death anim when crouched
2478 				}
2479 			}
2480 			else
2481 			{//lying down
2482 				if ( animLength - self->client->ps.legsAnimTimer > 500 )
2483 				{//partially up
2484 					deathAnim = BOTH_DEATH_FALLING_UP;
2485 				}
2486 				else
2487 				{//down
2488 					deathAnim = BOTH_DEATH_LYING_UP;
2489 				}
2490 			}
2491 			break;
2492 		case BOTH_GETUP3:
2493 			if ( self->client->ps.legsAnimTimer < 250 )
2494 			{//standing up
2495 			}
2496 			else if ( self->client->ps.legsAnimTimer < 600 )
2497 			{//crouching
2498 				vec3_t fwd;
2499 				AngleVectors( self->currentAngles, fwd, NULL, NULL );
2500 				float	thrown = DotProduct( fwd, self->client->ps.velocity );
2501 				if ( thrown < -150 )
2502 				{
2503 					deathAnim = BOTH_DEATHBACKWARD1;	//# Death anim when crouched and thrown back
2504 				}
2505 				else
2506 				{
2507 					deathAnim = BOTH_DEATH_CROUCHED;	//# Death anim when crouched
2508 				}
2509 			}
2510 			else
2511 			{//lying down
2512 				if ( animLength - self->client->ps.legsAnimTimer > 150 )
2513 				{//partially up
2514 					deathAnim = BOTH_DEATH_FALLING_DN;
2515 				}
2516 				else
2517 				{//down
2518 					deathAnim = BOTH_DEATH_LYING_DN;
2519 				}
2520 			}
2521 			break;
2522 		case BOTH_GETUP4:
2523 			if ( self->client->ps.legsAnimTimer < 250 )
2524 			{//standing up
2525 			}
2526 			else if ( self->client->ps.legsAnimTimer < 600 )
2527 			{//crouching
2528 				vec3_t fwd;
2529 				AngleVectors( self->currentAngles, fwd, NULL, NULL );
2530 				float	thrown = DotProduct( fwd, self->client->ps.velocity );
2531 				if ( thrown < -150 )
2532 				{
2533 					deathAnim = BOTH_DEATHBACKWARD1;	//# Death anim when crouched and thrown back
2534 				}
2535 				else
2536 				{
2537 					deathAnim = BOTH_DEATH_CROUCHED;	//# Death anim when crouched
2538 				}
2539 			}
2540 			else
2541 			{//lying down
2542 				if ( animLength - self->client->ps.legsAnimTimer > 850 )
2543 				{//partially up
2544 					deathAnim = BOTH_DEATH_FALLING_DN;
2545 				}
2546 				else
2547 				{//down
2548 					deathAnim = BOTH_DEATH_LYING_UP;
2549 				}
2550 			}
2551 			break;
2552 		case BOTH_GETUP5:
2553 			if ( self->client->ps.legsAnimTimer > 850 )
2554 			{//lying down
2555 				if ( animLength - self->client->ps.legsAnimTimer > 1500 )
2556 				{//partially up
2557 					deathAnim = BOTH_DEATH_FALLING_DN;
2558 				}
2559 				else
2560 				{//down
2561 					deathAnim = BOTH_DEATH_LYING_DN;
2562 				}
2563 			}
2564 			break;
2565 		case BOTH_GETUP_CROUCH_B1:
2566 			if ( self->client->ps.legsAnimTimer < 800 )
2567 			{//crouching
2568 				vec3_t fwd;
2569 				AngleVectors( self->currentAngles, fwd, NULL, NULL );
2570 				float	thrown = DotProduct( fwd, self->client->ps.velocity );
2571 				if ( thrown < -150 )
2572 				{
2573 					deathAnim = BOTH_DEATHBACKWARD1;	//# Death anim when crouched and thrown back
2574 				}
2575 				else
2576 				{
2577 					deathAnim = BOTH_DEATH_CROUCHED;	//# Death anim when crouched
2578 				}
2579 			}
2580 			else
2581 			{//lying down
2582 				if ( animLength - self->client->ps.legsAnimTimer > 400 )
2583 				{//partially up
2584 					deathAnim = BOTH_DEATH_FALLING_UP;
2585 				}
2586 				else
2587 				{//down
2588 					deathAnim = BOTH_DEATH_LYING_UP;
2589 				}
2590 			}
2591 			break;
2592 		case BOTH_GETUP_CROUCH_F1:
2593 			if ( self->client->ps.legsAnimTimer < 800 )
2594 			{//crouching
2595 				vec3_t fwd;
2596 				AngleVectors( self->currentAngles, fwd, NULL, NULL );
2597 				float	thrown = DotProduct( fwd, self->client->ps.velocity );
2598 				if ( thrown < -150 )
2599 				{
2600 					deathAnim = BOTH_DEATHBACKWARD1;	//# Death anim when crouched and thrown back
2601 				}
2602 				else
2603 				{
2604 					deathAnim = BOTH_DEATH_CROUCHED;	//# Death anim when crouched
2605 				}
2606 			}
2607 			else
2608 			{//lying down
2609 				if ( animLength - self->client->ps.legsAnimTimer > 150 )
2610 				{//partially up
2611 					deathAnim = BOTH_DEATH_FALLING_DN;
2612 				}
2613 				else
2614 				{//down
2615 					deathAnim = BOTH_DEATH_LYING_DN;
2616 				}
2617 			}
2618 			break;
2619 		case BOTH_FORCE_GETUP_B1:
2620 			if ( self->client->ps.legsAnimTimer < 325 )
2621 			{//standing up
2622 			}
2623 			else if ( self->client->ps.legsAnimTimer < 725 )
2624 			{//spinning up
2625 				deathAnim = BOTH_DEATH_SPIN_180;	//# Death anim when facing backwards
2626 			}
2627 			else if ( self->client->ps.legsAnimTimer < 900 )
2628 			{//crouching
2629 				vec3_t fwd;
2630 				AngleVectors( self->currentAngles, fwd, NULL, NULL );
2631 				float	thrown = DotProduct( fwd, self->client->ps.velocity );
2632 				if ( thrown < -150 )
2633 				{
2634 					deathAnim = BOTH_DEATHBACKWARD1;	//# Death anim when crouched and thrown back
2635 				}
2636 				else
2637 				{
2638 					deathAnim = BOTH_DEATH_CROUCHED;	//# Death anim when crouched
2639 				}
2640 			}
2641 			else
2642 			{//lying down
2643 				if ( animLength - self->client->ps.legsAnimTimer > 50 )
2644 				{//partially up
2645 					deathAnim = BOTH_DEATH_FALLING_UP;
2646 				}
2647 				else
2648 				{//down
2649 					deathAnim = BOTH_DEATH_LYING_UP;
2650 				}
2651 			}
2652 			break;
2653 		case BOTH_FORCE_GETUP_B2:
2654 			if ( self->client->ps.legsAnimTimer < 575 )
2655 			{//standing up
2656 			}
2657 			else if ( self->client->ps.legsAnimTimer < 875 )
2658 			{//spinning up
2659 				deathAnim = BOTH_DEATH_SPIN_180;	//# Death anim when facing backwards
2660 			}
2661 			else if ( self->client->ps.legsAnimTimer < 900 )
2662 			{//crouching
2663 				vec3_t fwd;
2664 				AngleVectors( self->currentAngles, fwd, NULL, NULL );
2665 				float	thrown = DotProduct( fwd, self->client->ps.velocity );
2666 				if ( thrown < -150 )
2667 				{
2668 					deathAnim = BOTH_DEATHBACKWARD1;	//# Death anim when crouched and thrown back
2669 				}
2670 				else
2671 				{
2672 					deathAnim = BOTH_DEATH_CROUCHED;	//# Death anim when crouched
2673 				}
2674 			}
2675 			else
2676 			{//lying down
2677 				//partially up
2678 				deathAnim = BOTH_DEATH_FALLING_UP;
2679 			}
2680 			break;
2681 		case BOTH_FORCE_GETUP_B3:
2682 			if ( self->client->ps.legsAnimTimer < 150 )
2683 			{//standing up
2684 			}
2685 			else if ( self->client->ps.legsAnimTimer < 775 )
2686 			{//flipping
2687 				deathAnim = BOTH_DEATHBACKWARD2; //backflip
2688 			}
2689 			else
2690 			{//lying down
2691 				//partially up
2692 				deathAnim = BOTH_DEATH_FALLING_UP;
2693 			}
2694 			break;
2695 		case BOTH_FORCE_GETUP_B4:
2696 			if ( self->client->ps.legsAnimTimer < 325 )
2697 			{//standing up
2698 			}
2699 			else
2700 			{//lying down
2701 				if ( animLength - self->client->ps.legsAnimTimer > 150 )
2702 				{//partially up
2703 					deathAnim = BOTH_DEATH_FALLING_UP;
2704 				}
2705 				else
2706 				{//down
2707 					deathAnim = BOTH_DEATH_LYING_UP;
2708 				}
2709 			}
2710 			break;
2711 		case BOTH_FORCE_GETUP_B5:
2712 			if ( self->client->ps.legsAnimTimer < 550 )
2713 			{//standing up
2714 			}
2715 			else if ( self->client->ps.legsAnimTimer < 1025 )
2716 			{//kicking up
2717 				deathAnim = BOTH_DEATHBACKWARD2; //backflip
2718 			}
2719 			else
2720 			{//lying down
2721 				if ( animLength - self->client->ps.legsAnimTimer > 50 )
2722 				{//partially up
2723 					deathAnim = BOTH_DEATH_FALLING_UP;
2724 				}
2725 				else
2726 				{//down
2727 					deathAnim = BOTH_DEATH_LYING_UP;
2728 				}
2729 			}
2730 			break;
2731 		case BOTH_FORCE_GETUP_B6:
2732 			if ( self->client->ps.legsAnimTimer < 225 )
2733 			{//standing up
2734 			}
2735 			else if ( self->client->ps.legsAnimTimer < 425 )
2736 			{//crouching up
2737 				vec3_t fwd;
2738 				AngleVectors( self->currentAngles, fwd, NULL, NULL );
2739 				float	thrown = DotProduct( fwd, self->client->ps.velocity );
2740 				if ( thrown < -150 )
2741 				{
2742 					deathAnim = BOTH_DEATHBACKWARD1;	//# Death anim when crouched and thrown back
2743 				}
2744 				else
2745 				{
2746 					deathAnim = BOTH_DEATH_CROUCHED;	//# Death anim when crouched
2747 				}
2748 			}
2749 			else if ( self->client->ps.legsAnimTimer < 825 )
2750 			{//flipping up
2751 				deathAnim = BOTH_DEATHFORWARD3; //backflip
2752 			}
2753 			else
2754 			{//lying down
2755 				if ( animLength - self->client->ps.legsAnimTimer > 225 )
2756 				{//partially up
2757 					deathAnim = BOTH_DEATH_FALLING_UP;
2758 				}
2759 				else
2760 				{//down
2761 					deathAnim = BOTH_DEATH_LYING_UP;
2762 				}
2763 			}
2764 			break;
2765 		case BOTH_FORCE_GETUP_F1:
2766 			if ( self->client->ps.legsAnimTimer < 275 )
2767 			{//standing up
2768 			}
2769 			else if ( self->client->ps.legsAnimTimer < 750 )
2770 			{//flipping
2771 				deathAnim = BOTH_DEATH14;
2772 			}
2773 			else
2774 			{//lying down
2775 				if ( animLength - self->client->ps.legsAnimTimer > 100 )
2776 				{//partially up
2777 					deathAnim = BOTH_DEATH_FALLING_DN;
2778 				}
2779 				else
2780 				{//down
2781 					deathAnim = BOTH_DEATH_LYING_DN;
2782 				}
2783 			}
2784 			break;
2785 		case BOTH_FORCE_GETUP_F2:
2786 			if ( self->client->ps.legsAnimTimer < 1200 )
2787 			{//standing
2788 			}
2789 			else
2790 			{//lying down
2791 				if ( animLength - self->client->ps.legsAnimTimer > 225 )
2792 				{//partially up
2793 					deathAnim = BOTH_DEATH_FALLING_DN;
2794 				}
2795 				else
2796 				{//down
2797 					deathAnim = BOTH_DEATH_LYING_DN;
2798 				}
2799 			}
2800 			break;
2801 		}
2802 	}
2803 	else if ( PM_InOnGroundAnim( &self->client->ps ) )
2804 	{
2805 		if ( AngleNormalize180(self->client->renderInfo.torsoAngles[PITCH]) < 0 )
2806 		{
2807 			deathAnim = BOTH_DEATH_LYING_UP;	//# Death anim when lying on back
2808 		}
2809 		else
2810 		{
2811 			deathAnim = BOTH_DEATH_LYING_DN;	//# Death anim when lying on front
2812 		}
2813 	}
2814 	else if ( PM_CrouchAnim( self->client->ps.legsAnim ) )
2815 	{
2816 		vec3_t fwd;
2817 		AngleVectors( self->currentAngles, fwd, NULL, NULL );
2818 		float	thrown = DotProduct( fwd, self->client->ps.velocity );
2819 		if ( thrown < -200 )
2820 		{
2821 			deathAnim = BOTH_DEATHBACKWARD1;	//# Death anim when crouched and thrown back
2822 			if ( self->client->ps.velocity[2] > 0 && self->client->ps.velocity[2] < 100 )
2823 			{
2824 				self->client->ps.velocity[2] = 100;
2825 			}
2826 		}
2827 		else
2828 		{
2829 			deathAnim = BOTH_DEATH_CROUCHED;	//# Death anim when crouched
2830 		}
2831 	}
2832 
2833 	return deathAnim;
2834 }
2835 extern qboolean PM_FinishedCurrentLegsAnim( gentity_t *self );
G_PickDeathAnim(gentity_t * self,vec3_t point,int damage,int mod,int hitLoc)2836 static int G_PickDeathAnim( gentity_t *self, vec3_t point, int damage, int mod, int hitLoc )
2837 {//FIXME: play dead flop anims on body if in an appropriate _DEAD anim when this func is called
2838 	int deathAnim = -1;
2839 	if ( hitLoc == HL_NONE )
2840 	{
2841 		hitLoc = G_GetHitLocation( self, point );//self->hitLoc
2842 	}
2843 	//dead flops...if you are already playing a death animation, I guess it can just return directly
2844 	switch( self->client->ps.legsAnim )
2845 	{
2846 	case BOTH_DEATH1:		//# First Death anim
2847 	case BOTH_DEAD1:
2848 	case BOTH_DEATH2:			//# Second Death anim
2849 	case BOTH_DEAD2:
2850 	case BOTH_DEATH8:			//#
2851 	case BOTH_DEAD8:
2852 	case BOTH_DEATH13:			//#
2853 	case BOTH_DEAD13:
2854 	case BOTH_DEATH14:			//#
2855 	case BOTH_DEAD14:
2856 	case BOTH_DEATH16:			//#
2857 	case BOTH_DEAD16:
2858 	case BOTH_DEADBACKWARD1:		//# First thrown backward death finished pose
2859 	case BOTH_DEADBACKWARD2:		//# Second thrown backward death finished pose
2860 		return -2;
2861 		break;
2862 		/*
2863 		if ( PM_FinishedCurrentLegsAnim( self ) )
2864 		{//done with the anim
2865 			deathAnim = BOTH_DEADFLOP2;
2866 		}
2867 		else
2868 		{
2869 			deathAnim = -2;
2870 		}
2871 		break;
2872 	case BOTH_DEADFLOP2:
2873 		deathAnim = BOTH_DEADFLOP2;
2874 		break;
2875 		*/
2876 	case BOTH_DEATH10:			//#
2877 	case BOTH_DEAD10:
2878 	case BOTH_DEATH15:			//#
2879 	case BOTH_DEAD15:
2880 	case BOTH_DEADFORWARD1:		//# First thrown forward death finished pose
2881 	case BOTH_DEADFORWARD2:		//# Second thrown forward death finished pose
2882 		return -2;
2883 		break;
2884 		/*
2885 		if ( PM_FinishedCurrentLegsAnim( self ) )
2886 		{//done with the anim
2887 			deathAnim = BOTH_DEADFLOP1;
2888 		}
2889 		else
2890 		{
2891 			deathAnim = -2;
2892 		}
2893 		break;
2894 		*/
2895 	case BOTH_DEADFLOP1:
2896 		//deathAnim = BOTH_DEADFLOP1;
2897 		return -2;
2898 		break;
2899 	case BOTH_DEAD3:				//# Third Death finished pose
2900 	case BOTH_DEAD4:				//# Fourth Death finished pose
2901 	case BOTH_DEAD5:				//# Fifth Death finished pose
2902 	case BOTH_DEAD6:				//# Sixth Death finished pose
2903 	case BOTH_DEAD7:				//# Seventh Death finished pose
2904 	case BOTH_DEAD9:				//#
2905 	case BOTH_DEAD11:			//#
2906 	case BOTH_DEAD12:			//#
2907 	case BOTH_DEAD17:			//#
2908 	case BOTH_DEAD18:			//#
2909 	case BOTH_DEAD19:			//#
2910 	case BOTH_DEAD20:			//#
2911 	case BOTH_DEAD21:			//#
2912 	case BOTH_DEAD22:			//#
2913 	case BOTH_DEAD23:			//#
2914 	case BOTH_DEAD24:			//#
2915 	case BOTH_DEAD25:			//#
2916 	case BOTH_LYINGDEAD1:		//# Killed lying down death finished pose
2917 	case BOTH_STUMBLEDEAD1:		//# Stumble forward death finished pose
2918 	case BOTH_FALLDEAD1LAND:		//# Fall forward and splat death finished pose
2919 	case BOTH_DEATH3:			//# Third Death anim
2920 	case BOTH_DEATH4:			//# Fourth Death anim
2921 	case BOTH_DEATH5:			//# Fifth Death anim
2922 	case BOTH_DEATH6:			//# Sixth Death anim
2923 	case BOTH_DEATH7:			//# Seventh Death anim
2924 	case BOTH_DEATH9:			//#
2925 	case BOTH_DEATH11:			//#
2926 	case BOTH_DEATH12:			//#
2927 	case BOTH_DEATH17:			//#
2928 	case BOTH_DEATH18:			//#
2929 	case BOTH_DEATH19:			//#
2930 	case BOTH_DEATH20:			//#
2931 	case BOTH_DEATH21:			//#
2932 	case BOTH_DEATH22:			//#
2933 	case BOTH_DEATH23:			//#
2934 	case BOTH_DEATH24:			//#
2935 	case BOTH_DEATH25:			//#
2936 	case BOTH_DEATHFORWARD1:		//# First Death in which they get thrown forward
2937 	case BOTH_DEATHFORWARD2:		//# Second Death in which they get thrown forward
2938 	case BOTH_DEATHFORWARD3:		//# Second Death in which they get thrown forward
2939 	case BOTH_DEATHBACKWARD1:	//# First Death in which they get thrown backward
2940 	case BOTH_DEATHBACKWARD2:	//# Second Death in which they get thrown backward
2941 	case BOTH_DEATH1IDLE:		//# Idle while close to death
2942 	case BOTH_LYINGDEATH1:		//# Death to play when killed lying down
2943 	case BOTH_STUMBLEDEATH1:		//# Stumble forward and fall face first death
2944 	case BOTH_FALLDEATH1:		//# Fall forward off a high cliff and splat death - start
2945 	case BOTH_FALLDEATH1INAIR:	//# Fall forward off a high cliff and splat death - loop
2946 	case BOTH_FALLDEATH1LAND:	//# Fall forward off a high cliff and splat death - hit bottom
2947 		return -2;
2948 		break;
2949 	case BOTH_DEATH_ROLL:		//# Death anim from a roll
2950 	case BOTH_DEATH_FLIP:		//# Death anim from a flip
2951 	case BOTH_DEATH_SPIN_90_R:	//# Death anim when facing 90 degrees right
2952 	case BOTH_DEATH_SPIN_90_L:	//# Death anim when facing 90 degrees left
2953 	case BOTH_DEATH_SPIN_180:	//# Death anim when facing backwards
2954 	case BOTH_DEATH_LYING_UP:	//# Death anim when lying on back
2955 	case BOTH_DEATH_LYING_DN:	//# Death anim when lying on front
2956 	case BOTH_DEATH_FALLING_DN:	//# Death anim when falling on face
2957 	case BOTH_DEATH_FALLING_UP:	//# Death anim when falling on back
2958 	case BOTH_DEATH_CROUCHED:	//# Death anim when crouched
2959 	case BOTH_RIGHTHANDCHOPPEDOFF:
2960 		return -2;
2961 		break;
2962 	}
2963 	// Not currently playing a death animation, so try and get an appropriate one now.
2964 	if ( deathAnim == -1 )
2965 	{
2966 		deathAnim = G_CheckSpecialDeathAnim( self, point, damage, mod, hitLoc );
2967 
2968 		if ( deathAnim == -1 )
2969 		{//base on hitLoc
2970 			vec3_t fwd;
2971 			AngleVectors( self->currentAngles, fwd, NULL, NULL );
2972 			float	thrown = DotProduct( fwd, self->client->ps.velocity );
2973 			//death anims
2974 			switch( hitLoc )
2975 			{
2976 			case HL_FOOT_RT:
2977 				if ( !Q_irand( 0, 2 ) && thrown < 250 )
2978 				{
2979 					deathAnim = BOTH_DEATH24;//right foot trips up, spin
2980 				}
2981 				else if ( !Q_irand( 0, 1 ) )
2982 				{
2983 					deathAnim = BOTH_DEATH4;//back: forward
2984 				}
2985 				else
2986 				{
2987 					deathAnim = BOTH_DEATH5;//same as 4
2988 				}
2989 				break;
2990 			case HL_FOOT_LT:
2991 				if ( !Q_irand( 0, 2 ) && thrown < 250 )
2992 				{
2993 					deathAnim = BOTH_DEATH25;//left foot trips up, spin
2994 				}
2995 				else if ( !Q_irand( 0, 1 ) )
2996 				{
2997 					deathAnim = BOTH_DEATH4;//back: forward
2998 				}
2999 				else
3000 				{
3001 					deathAnim = BOTH_DEATH5;//same as 4
3002 				}
3003 				break;
3004 			case HL_LEG_RT:
3005 				if ( !Q_irand( 0, 2 ) && thrown < 250 )
3006 				{
3007 					deathAnim = BOTH_DEATH3;//right leg collapse
3008 				}
3009 				else if ( !Q_irand( 0, 1 ) )
3010 				{
3011 					deathAnim = BOTH_DEATH5;//same as 4
3012 				}
3013 				else
3014 				{
3015 					deathAnim = BOTH_DEATH4;//back: forward
3016 				}
3017 				break;
3018 			case HL_LEG_LT:
3019 				if ( !Q_irand( 0, 2 ) && thrown < 250 )
3020 				{
3021 					deathAnim = BOTH_DEATH7;//left leg collapse
3022 				}
3023 				else if ( !Q_irand( 0, 1 ) )
3024 				{
3025 					deathAnim = BOTH_DEATH5;//same as 4
3026 				}
3027 				else
3028 				{
3029 					deathAnim = BOTH_DEATH4;//back: forward
3030 				}
3031 				break;
3032 			case HL_BACK:
3033 				if ( fabs(thrown) < 50 || (fabs(thrown) < 200&&!Q_irand(0,3)) )
3034 				{
3035 					if ( Q_irand( 0, 1 ) )
3036 					{
3037 						deathAnim = BOTH_DEATH17;//head/back: croak
3038 					}
3039 					else
3040 					{
3041 						deathAnim = BOTH_DEATH10;//back: bend back, fall forward
3042 					}
3043 				}
3044 				else
3045 				{
3046 					if ( !Q_irand( 0, 1 ) )
3047 					{
3048 						deathAnim = BOTH_DEATH4;//back: forward
3049 					}
3050 					else
3051 					{
3052 						deathAnim = BOTH_DEATH5;//same as 4
3053 					}
3054 				}
3055 				break;
3056 			case HL_HAND_RT:
3057 			case HL_CHEST_RT:
3058 			case HL_ARM_RT:
3059 			case HL_BACK_LT:
3060 				if ( (damage <= self->max_health*0.25&&Q_irand(0,1)) || (fabs(thrown)<200&&!Q_irand(0,2)) || !Q_irand( 0, 10 ) )
3061 				{
3062 					if ( Q_irand( 0, 1 ) )
3063 					{
3064 						deathAnim = BOTH_DEATH9;//chest right: snap, fall forward
3065 					}
3066 					else
3067 					{
3068 						deathAnim = BOTH_DEATH20;//chest right: snap, fall forward
3069 					}
3070 				}
3071 				else if ( (damage <= self->max_health*0.5&&Q_irand(0,1)) || !Q_irand( 0, 10 ) )
3072 				{
3073 					deathAnim = BOTH_DEATH3;//chest right: back
3074 				}
3075 				else if ( (damage <= self->max_health*0.75&&Q_irand(0,1)) || !Q_irand( 0, 10 ) )
3076 				{
3077 					deathAnim = BOTH_DEATH6;//chest right: spin
3078 				}
3079 				else
3080 				{
3081 					//TEMP HACK: play spinny deaths less often
3082 					if ( Q_irand( 0, 1 ) )
3083 					{
3084 						deathAnim = BOTH_DEATH8;//chest right: spin high
3085 					}
3086 					else
3087 					{
3088 						switch ( Q_irand( 0, 3 ) )
3089 						{
3090 						default:
3091 						case 0:
3092 							deathAnim = BOTH_DEATH9;//chest right: snap, fall forward
3093 							break;
3094 						case 1:
3095 							deathAnim = BOTH_DEATH3;//chest right: back
3096 							break;
3097 						case 2:
3098 							deathAnim = BOTH_DEATH6;//chest right: spin
3099 							break;
3100 						case 3:
3101 							deathAnim = BOTH_DEATH20;//chest right: spin
3102 							break;
3103 						}
3104 					}
3105 				}
3106 				break;
3107 			case HL_CHEST_LT:
3108 			case HL_ARM_LT:
3109 			case HL_HAND_LT:
3110 			case HL_BACK_RT:
3111 				if ( (damage <= self->max_health*0.25&&Q_irand(0,1)) || (fabs(thrown)<200&&!Q_irand(0,2)) || !Q_irand(0, 10) )
3112 				{
3113 					if ( Q_irand( 0, 1 ) )
3114 					{
3115 						deathAnim = BOTH_DEATH11;//chest left: snap, fall forward
3116 					}
3117 					else
3118 					{
3119 						deathAnim = BOTH_DEATH21;//chest left: snap, fall forward
3120 					}
3121 				}
3122 				else if ( (damage <= self->max_health*0.5&&Q_irand(0,1)) || !Q_irand(0, 10) )
3123 				{
3124 					deathAnim = BOTH_DEATH7;//chest left: back
3125 				}
3126 				else if ( (damage <= self->max_health*0.75&&Q_irand(0,1)) || !Q_irand(0, 10) )
3127 				{
3128 					deathAnim = BOTH_DEATH12;//chest left: spin
3129 				}
3130 				else
3131 				{
3132 					//TEMP HACK: play spinny deaths less often
3133 					if ( Q_irand( 0, 1 ) )
3134 					{
3135 						deathAnim = BOTH_DEATH14;//chest left: spin high
3136 					}
3137 					else
3138 					{
3139 						switch ( Q_irand( 0, 3 ) )
3140 						{
3141 						default:
3142 						case 0:
3143 							deathAnim = BOTH_DEATH11;//chest left: snap, fall forward
3144 							break;
3145 						case 1:
3146 							deathAnim = BOTH_DEATH7;//chest left: back
3147 							break;
3148 						case 2:
3149 							deathAnim = BOTH_DEATH12;//chest left: spin
3150 							break;
3151 						case 3:
3152 							deathAnim = BOTH_DEATH21;//chest left: spin
3153 							break;
3154 						}
3155 					}
3156 				}
3157 				break;
3158 			case HL_CHEST:
3159 			case HL_WAIST:
3160 				if ( (damage <= self->max_health*0.25&&Q_irand(0,1)) || thrown > -50 )
3161 				{
3162 					if ( !Q_irand( 0, 1 ) )
3163 					{
3164 						deathAnim = BOTH_DEATH18;//gut: fall right
3165 					}
3166 					else
3167 					{
3168 						deathAnim = BOTH_DEATH19;//gut: fall left
3169 					}
3170 				}
3171 				else if ( (damage <= self->max_health*0.5&&!Q_irand(0,1)) || (fabs(thrown)<200&&!Q_irand(0,3)) )
3172 				{
3173 					if ( Q_irand( 0, 2 ) )
3174 					{
3175 						deathAnim = BOTH_DEATH2;//chest: backward short
3176 					}
3177 					else if ( Q_irand( 0, 1 ) )
3178 					{
3179 						deathAnim = BOTH_DEATH22;//chest: backward short
3180 					}
3181 					else
3182 					{
3183 						deathAnim = BOTH_DEATH23;//chest: backward short
3184 					}
3185 				}
3186 				else if ( thrown < -300 && Q_irand( 0, 1 ) )
3187 				{
3188 					if ( Q_irand( 0, 1 ) )
3189 					{
3190 						deathAnim = BOTH_DEATHBACKWARD1;//chest: fly back
3191 					}
3192 					else
3193 					{
3194 						deathAnim = BOTH_DEATHBACKWARD2;//chest: flip back
3195 					}
3196 				}
3197 				else if ( thrown < -200 && Q_irand( 0, 1 ) )
3198 				{
3199 					deathAnim = BOTH_DEATH15;//chest: roll backward
3200 				}
3201 				else
3202 				{
3203 					if ( !Q_irand( 0, 1 ) )
3204 					{
3205 						deathAnim = BOTH_DEATH1;//chest: backward med
3206 					}
3207 					else
3208 					{
3209 						deathAnim = BOTH_DEATH16;//same as 1
3210 					}
3211 				}
3212 				break;
3213 			case HL_HEAD:
3214 				if ( damage <= self->max_health*0.5 && Q_irand(0,2) )
3215 				{
3216 					deathAnim = BOTH_DEATH17;//head/back: croak
3217 				}
3218 				else
3219 				{
3220 					if ( Q_irand( 0, 2 ) )
3221 					{
3222 						deathAnim = BOTH_DEATH13;//head: stumble, fall back
3223 					}
3224 					else
3225 					{
3226 						deathAnim = BOTH_DEATH10;//head: stumble, fall back
3227 					}
3228 				}
3229 				break;
3230 			default:
3231 				break;
3232 			}
3233 		}
3234 	}
3235 
3236 	// Validate.....
3237 	if ( deathAnim == -1 || !PM_HasAnimation( self, deathAnim ))
3238 	{
3239 		// I guess we'll take what we can get.....
3240 		deathAnim = PM_PickAnim( self, BOTH_DEATH1, BOTH_DEATH25 );
3241 	}
3242 
3243 	return deathAnim;
3244 }
3245 
G_CheckLedgeDive(gentity_t * self,float checkDist,vec3_t checkVel,qboolean tryOpposite,qboolean tryPerp)3246 int G_CheckLedgeDive( gentity_t *self, float checkDist, vec3_t checkVel, qboolean tryOpposite, qboolean tryPerp )
3247 {
3248 	//		Intelligent Ledge-Diving Deaths:
3249 	//		If I'm an NPC, check for nearby ledges and fall off it if possible
3250 	//		How should/would/could this interact with knockback if we already have some?
3251 	//		Ideally - apply knockback if there are no ledges or a ledge in that dir
3252 	//		But if there is a ledge and it's not in the dir of my knockback, fall off the ledge instead
3253 	if ( !self || !self->client )
3254 	{
3255 		return 0;
3256 	}
3257 
3258 	vec3_t	fallForwardDir, fallRightDir;
3259 	vec3_t	angles = {0};
3260 	int		cliff_fall = 0;
3261 
3262 	if ( checkVel && !VectorCompare( checkVel, vec3_origin ) )
3263 	{//already moving in a dir
3264 		angles[1] = vectoyaw( self->client->ps.velocity );
3265 		AngleVectors( angles, fallForwardDir, fallRightDir, NULL );
3266 	}
3267 	else
3268 	{//try forward first
3269 		angles[1] = self->client->ps.viewangles[1];
3270 		AngleVectors( angles, fallForwardDir, fallRightDir, NULL );
3271 	}
3272 	VectorNormalize( fallForwardDir );
3273 	float fallDist = G_CheckForLedge( self, fallForwardDir, checkDist );
3274 	if ( fallDist >= 128 )
3275 	{
3276 		VectorClear( self->client->ps.velocity );
3277 		G_Throw( self, fallForwardDir, 85 );
3278 		self->client->ps.velocity[2] = 100;
3279 		self->client->ps.groundEntityNum = ENTITYNUM_NONE;
3280 	}
3281 	else if ( tryOpposite )
3282 	{
3283 		VectorScale( fallForwardDir, -1, fallForwardDir );
3284 		fallDist = G_CheckForLedge( self, fallForwardDir, checkDist );
3285 		if ( fallDist >= 128 )
3286 		{
3287 			VectorClear( self->client->ps.velocity );
3288 			G_Throw( self, fallForwardDir, 85 );
3289 			self->client->ps.velocity[2] = 100;
3290 			self->client->ps.groundEntityNum = ENTITYNUM_NONE;
3291 		}
3292 	}
3293 	if ( !cliff_fall && tryPerp )
3294 	{//try sides
3295 		VectorNormalize( fallRightDir );
3296 		fallDist = G_CheckForLedge( self, fallRightDir, checkDist );
3297 		if ( fallDist >= 128 )
3298 		{
3299 			VectorClear( self->client->ps.velocity );
3300 			G_Throw( self, fallRightDir, 85 );
3301 			self->client->ps.velocity[2] = 100;
3302 		}
3303 		else
3304 		{
3305 			VectorScale( fallRightDir, -1, fallRightDir );
3306 			fallDist = G_CheckForLedge( self, fallRightDir, checkDist );
3307 			if ( fallDist >= 128 )
3308 			{
3309 				VectorClear( self->client->ps.velocity );
3310 				G_Throw( self, fallRightDir, 85 );
3311 				self->client->ps.velocity[2] = 100;
3312 			}
3313 		}
3314 	}
3315 	if ( fallDist >= 256 )
3316 	{
3317 		cliff_fall = 2;
3318 	}
3319 	else if ( fallDist >= 128 )
3320 	{
3321 		cliff_fall = 1;
3322 	}
3323 	return cliff_fall;
3324 }
3325 /*
3326 ==================
3327 player_die
3328 ==================
3329 */
3330 void NPC_SetAnim(gentity_t	*ent,int type,int anim,int priority);
3331 extern void AI_DeleteSelfFromGroup( gentity_t *self );
3332 extern void AI_GroupMemberKilled( gentity_t *self );
3333 extern qboolean FlyingCreature( gentity_t *ent );
3334 extern void G_DrivableATSTDie( gentity_t *self );
player_die(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,int damage,int meansOfDeath,int dflags,int hitLoc)3335 void player_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath, int dflags, int hitLoc )
3336 {
3337 	int			anim;
3338 	int			contents;
3339 	qboolean	deathScript = qfalse;
3340 	qboolean	lastInGroup = qfalse;
3341 	qboolean	specialAnim = qfalse;
3342 	qboolean	holdingSaber = qfalse;
3343 	int			cliff_fall = 0;
3344 
3345 	//FIXME: somehow people are sometimes not completely dying???
3346 	if ( self->client->ps.pm_type == PM_DEAD && (meansOfDeath != MOD_SNIPER || (self->flags & FL_DISINTEGRATED)) )
3347 	{//do dismemberment/twitching
3348 		if ( self->client->NPC_class == CLASS_MARK1 )
3349 		{
3350 			DeathFX(self);
3351 			self->takedamage = qfalse;
3352 			self->client->ps.eFlags |= EF_NODRAW;
3353 			self->contents = 0;
3354 			// G_FreeEntity( self ); // Is this safe?  I can't see why we'd mark it nodraw and then just leave it around??
3355 			self->e_ThinkFunc = thinkF_G_FreeEntity;
3356 			self->nextthink = level.time + FRAMETIME;
3357 		}
3358 		else
3359 		{
3360 			anim = G_PickDeathAnim( self, self->pos1, damage, meansOfDeath, hitLoc );
3361 			if ( dflags & DAMAGE_DISMEMBER )
3362 			{
3363 				G_DoDismemberment( self, self->pos1, meansOfDeath, damage, hitLoc );
3364 			}
3365 			if ( anim >= 0 )
3366 			{
3367 				NPC_SetAnim(self, SETANIM_BOTH, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_RESTART|SETANIM_FLAG_HOLD);
3368 			}
3369 		}
3370 		return;
3371 	}
3372 
3373 #ifndef FINAL_BUILD
3374 	if ( d_saberCombat->integer && attacker && attacker->client )
3375 	{
3376 		gi.Printf( S_COLOR_YELLOW"combatant %s died, killer anim = %s\n", self->targetname, animTable[attacker->client->ps.torsoAnim].name );
3377 	}
3378 #endif//FINAL_BUILD
3379 
3380 	if ( self->NPC )
3381 	{
3382 		if ( self->client && Jedi_WaitingAmbush( self ) )
3383 		{//ambushing trooper
3384 			self->client->noclip = qfalse;
3385 		}
3386 		NPC_FreeCombatPoint( self->NPC->combatPoint );
3387 		if ( self->NPC->group )
3388 		{
3389 			lastInGroup = (qboolean)(self->NPC->group->numGroup < 2);
3390 			AI_GroupMemberKilled( self );
3391 			AI_DeleteSelfFromGroup( self );
3392 		}
3393 
3394 		if ( self->NPC->tempGoal )
3395 		{
3396 			G_FreeEntity( self->NPC->tempGoal );
3397 			self->NPC->tempGoal = NULL;
3398 		}
3399 		if ( self->s.eFlags & EF_LOCKED_TO_WEAPON )
3400 		{
3401 			// dumb, just get the NPC out of the chair
3402 extern void RunEmplacedWeapon( gentity_t *ent, usercmd_t **ucmd );
3403 
3404 			usercmd_t cmd, *ad_cmd;
3405 
3406 			memset( &cmd, 0, sizeof( usercmd_t ));
3407 
3408 			//gentity_t *old = self->owner;
3409 
3410 			if ( self->owner )
3411 			{
3412 				self->owner->s.frame = self->owner->startFrame = self->owner->endFrame = 0;
3413 				self->owner->svFlags &= ~SVF_ANIMATING;
3414 			}
3415 
3416 			cmd.buttons |= BUTTON_USE;
3417 			ad_cmd = &cmd;
3418 			RunEmplacedWeapon( self, &ad_cmd );
3419 			//self->owner = old;
3420 		}
3421 	}
3422 	if ( attacker && attacker->NPC && attacker->NPC->group && attacker->NPC->group->enemy == self )
3423 	{
3424 		attacker->NPC->group->enemy = NULL;
3425 	}
3426 	if ( self->s.weapon == WP_SABER )
3427 	{
3428 		holdingSaber = qtrue;
3429 	}
3430 	if ( self->client->ps.saberEntityNum != ENTITYNUM_NONE && self->client->ps.saberEntityNum > 0 )
3431 	{
3432 		if ( self->client->ps.saberInFlight )
3433 		{//just drop it
3434 			self->client->ps.saberActive = qfalse;
3435 		}
3436 		else
3437 		{
3438 			if ( (hitLoc != HL_HAND_RT
3439 				|| self->client->dismembered
3440 				|| meansOfDeath != MOD_SABER )//if might get hand cut off, leave saber in hand
3441 				&& holdingSaber
3442 				&& ( Q_irand( 0, 1 )
3443 					|| meansOfDeath == MOD_EXPLOSIVE
3444 					|| meansOfDeath == MOD_REPEATER_ALT
3445 					|| meansOfDeath == MOD_FLECHETTE_ALT
3446 					|| meansOfDeath == MOD_ROCKET
3447 					|| meansOfDeath == MOD_ROCKET_ALT
3448 					|| meansOfDeath == MOD_THERMAL
3449 					|| meansOfDeath == MOD_THERMAL_ALT
3450 					|| meansOfDeath == MOD_DETPACK
3451 					|| meansOfDeath == MOD_LASERTRIP
3452 					|| meansOfDeath == MOD_LASERTRIP_ALT
3453 					|| meansOfDeath == MOD_MELEE
3454 					|| meansOfDeath == MOD_FORCE_GRIP
3455 					|| meansOfDeath == MOD_KNOCKOUT
3456 					|| meansOfDeath == MOD_CRUSH
3457 					|| meansOfDeath == MOD_IMPACT
3458 					|| meansOfDeath == MOD_FALLING
3459 					|| meansOfDeath == MOD_EXPLOSIVE_SPLASH ) )
3460 			{//drop it
3461 				TossClientItems( self );
3462 			}
3463 			else
3464 			{//just free it
3465 				if ( g_entities[self->client->ps.saberEntityNum].inuse )
3466 				{
3467 					G_FreeEntity( &g_entities[self->client->ps.saberEntityNum] );
3468 				}
3469 				self->client->ps.saberEntityNum = ENTITYNUM_NONE;
3470 			}
3471 		}
3472 	}
3473 	if ( self->client->NPC_class == CLASS_SHADOWTROOPER )
3474 	{//drop a force crystal
3475 		gitem_t		*item;
3476 		item = FindItemForAmmo( AMMO_FORCE );
3477 		Drop_Item( self, item, 0, qtrue );
3478 	}
3479 	//Use any target we had
3480 	if ( meansOfDeath != MOD_KNOCKOUT )
3481 	{
3482 		G_UseTargets( self, self );
3483 	}
3484 
3485 	if ( attacker )
3486 	{
3487 		if ( attacker->client && !attacker->s.number )
3488 		{
3489 			if ( self->client )
3490 			{//killed a client
3491 				if ( self->client->playerTeam == TEAM_ENEMY || (self->NPC && self->NPC->charmedTime > level.time) )
3492 				{//killed an enemy
3493 					attacker->client->sess.missionStats.enemiesKilled++;
3494 				}
3495 			}
3496 			if ( attacker != self )
3497 			{
3498 				G_TrackWeaponUsage( attacker, inflictor, 30, meansOfDeath );
3499 			}
3500 		}
3501 		G_CheckVictoryScript(attacker);
3502 		//player killing a jedi with a lightsaber spawns a matrix-effect entity
3503 		if ( d_slowmodeath->integer )
3504 		{
3505 			if ( !self->s.number )
3506 			{//what the hell, always do slow-mo when player dies
3507 				//FIXME: don't do this when crushed to death?
3508 				if ( meansOfDeath == MOD_FALLING && self->client->ps.groundEntityNum == ENTITYNUM_NONE )
3509 				{//falling to death, have not hit yet
3510 					G_StartMatrixEffect( self, qtrue, 10000 );
3511 				}
3512 				else if ( meansOfDeath != MOD_CRUSH )
3513 				{//for all deaths except being crushed
3514 					G_StartMatrixEffect( self );
3515 				}
3516 			}
3517 			else if ( d_slowmodeath->integer < 4 )
3518 			{//any jedi killed by player-saber
3519 				if ( d_slowmodeath->integer < 3 )
3520 				{//must be the last jedi in the room
3521 					if ( !G_JediInRoom( attacker->currentOrigin ) )
3522 					{
3523 						lastInGroup = qtrue;
3524 					}
3525 					else
3526 					{
3527 						lastInGroup = qfalse;
3528 					}
3529 				}
3530 				if ( !attacker->s.number
3531 					&& holdingSaber
3532 					&& meansOfDeath == MOD_SABER
3533 					&& attacker->client
3534 					&& attacker->client->ps.weapon == WP_SABER
3535 					&& !attacker->client->ps.saberInFlight
3536 					&& (d_slowmodeath->integer > 2||lastInGroup) )//either slow mo death level 3 (any jedi) or 2 and I was the last jedi in the room
3537 				{//Matrix!
3538 					G_StartMatrixEffect( self );
3539 				}
3540 			}
3541 			else
3542 			{//all player-saber kills
3543 				if ( !attacker->s.number
3544 					&& meansOfDeath == MOD_SABER
3545 					&& attacker->client
3546 					&& attacker->client->ps.weapon == WP_SABER
3547 					&& !attacker->client->ps.saberInFlight
3548 					&& (d_slowmodeath->integer > 4||lastInGroup||holdingSaber))//either slow mo death level 5 (any enemy) or 4 and I was the last in my group or I'm a saber user
3549 				{//Matrix!
3550 					G_StartMatrixEffect( self );
3551 				}
3552 			}
3553 		}
3554 	}
3555 
3556 	self->enemy = attacker;
3557 	self->client->renderInfo.lookTarget = ENTITYNUM_NONE;
3558 
3559 	self->client->ps.persistant[PERS_KILLED]++;
3560 	if ( self->client->playerTeam == TEAM_PLAYER )
3561 	{//FIXME: just HazTeam members in formation on away missions?
3562 		//or more controlled- via deathscripts?
3563 		// Don't count player
3564 		if (( &g_entities[0] != NULL && g_entities[0].client ) && (self->s.number != 0))
3565 		{//add to the number of teammates lost
3566 			g_entities[0].client->ps.persistant[PERS_TEAMMATES_KILLED]++;
3567 		}
3568 		else	// Player died, fire off scoreboard soon
3569 		{
3570 			cg.missionStatusDeadTime = level.time + 1000;	// Too long?? Too short??
3571 			cg.zoomMode = 0; // turn off zooming when we die
3572 		}
3573 	}
3574 
3575 	if ( self->s.number == 0 && attacker )
3576 	{
3577 //		G_SetMissionStatusText( attacker, meansOfDeath );
3578 		//TEST: If player killed, unmark all teammates from being undying so they can buy it too
3579 		//NOTE: we want this to happen ONLY on our squad ONLY on missions... in the tutorial or on voyager levels this could be really weird.
3580 		G_MakeTeamVulnerable();
3581 	}
3582 
3583 	if ( attacker && attacker->client)
3584 	{
3585 		if ( attacker == self || OnSameTeam (self, attacker ) )
3586 		{
3587 			AddScore( attacker, -1 );
3588 		}
3589 		else
3590 		{
3591 			AddScore( attacker, 1 );
3592 		}
3593 	}
3594 	else
3595 	{
3596 		AddScore( self, -1 );
3597 	}
3598 
3599 	// if client is in a nodrop area, don't drop anything
3600 	contents = gi.pointcontents( self->currentOrigin, -1 );
3601 	if ( !holdingSaber
3602 		//&& self->s.number != 0
3603 		&& !( contents & CONTENTS_NODROP )
3604 		&& meansOfDeath != MOD_SNIPER
3605 		&& (!self->client||self->client->NPC_class!=CLASS_GALAKMECH))
3606 	{
3607 		TossClientItems( self );
3608 	}
3609 
3610 	if ( meansOfDeath == MOD_SNIPER )
3611 	{//I was disintegrated
3612 		if ( self->message )
3613 		{//I was holding a key
3614 			//drop the key
3615 			G_DropKey( self );
3616 		}
3617 	}
3618 
3619 	if ( holdingSaber )
3620 	{//never drop a lightsaber!
3621 		if ( self->client->ps.saberActive )
3622 		{
3623 			self->client->ps.saberActive = qfalse;
3624 			if ( self->client->playerTeam == TEAM_PLAYER )
3625 			{
3626 				G_SoundOnEnt( self, CHAN_AUTO, "sound/weapons/saber/saberoff.wav" );
3627 			}
3628 			else
3629 			{
3630 				G_SoundOnEnt( self, CHAN_AUTO, "sound/weapons/saber/enemy_saber_off.wav" );
3631 			}
3632 		}
3633 	}
3634 	else if ( self->s.weapon != WP_BLASTER_PISTOL )
3635 	{// Sigh...borg shouldn't drop their weapon attachments when they die..
3636 		self->s.weapon = WP_NONE;
3637 		if ( self->weaponModel >= 0 && self->ghoul2.size())
3638 		{
3639 			gi.G2API_RemoveGhoul2Model( self->ghoul2, self->weaponModel );
3640 			self->weaponModel = -1;
3641 		}
3642 	}
3643 
3644 	self->s.powerups &= ~PW_REMOVE_AT_DEATH;//removes everything but electricity and force push
3645 
3646 	//FIXME: do this on a callback?  So people can't walk through long death anims?
3647 	//Maybe set on last frame?  Would be cool for big blocking corpses if the never got set?
3648 	//self->contents = CONTENTS_CORPSE;//now done a second after death
3649 	/*
3650 	self->takedamage = qfalse;	// no gibbing
3651 	if ( self->client->playerTeam == TEAM_PARASITE )
3652 	{
3653 		self->contents = CONTENTS_NONE; // FIXME: temp fix
3654 	}
3655 	else
3656 	{
3657 		self->contents = CONTENTS_CORPSE;
3658 		self->maxs[2] = -8;
3659 	}
3660 	*/
3661 	if ( !self->s.number )
3662 	{//player
3663 		self->contents = CONTENTS_CORPSE;
3664 		self->maxs[2] = -8;
3665 	}
3666 	self->clipmask&=~(CONTENTS_MONSTERCLIP|CONTENTS_BOTCLIP);//so dead NPC can fly off ledges
3667 
3668 	//FACING==========================================================
3669 	if ( attacker && self->s.number == 0 )
3670 	{
3671 		self->client->ps.stats[STAT_DEAD_YAW] = AngleNormalize180( self->client->ps.viewangles[YAW] );
3672 	}
3673 	self->currentAngles[PITCH] = 0;
3674 	self->currentAngles[ROLL] = 0;
3675 	if ( self->NPC )
3676 	{
3677 		self->NPC->desiredYaw = 0;
3678 		self->NPC->desiredPitch = 0;
3679 		self->NPC->confusionTime = 0;
3680 		self->NPC->charmedTime = 0;
3681 	}
3682 	VectorCopy( self->currentAngles, self->client->ps.viewangles );
3683 	//FACING==========================================================
3684 	if ( player && player->client && player->client->ps.viewEntity == self->s.number )
3685 	{//I was the player's viewentity and I died, kick him back to his normal view
3686 		G_ClearViewEntity( player );
3687 	}
3688 	else if ( !self->s.number && self->client->ps.viewEntity > 0 && self->client->ps.viewEntity < ENTITYNUM_NONE )
3689 	{
3690 		G_ClearViewEntity( self );
3691 	}
3692 	else if ( !self->s.number && self->client->ps.viewEntity > 0 && self->client->ps.viewEntity < ENTITYNUM_NONE )
3693 	{
3694 		G_ClearViewEntity( self );
3695 	}
3696 
3697 	self->s.loopSound = 0;
3698 
3699 	// remove powerups
3700 	memset( self->client->ps.powerups, 0, sizeof(self->client->ps.powerups) );
3701 
3702 	if ( self->client->NPC_class == CLASS_MARK1 )
3703 	{
3704 		Mark1_die( self, inflictor, attacker, damage, meansOfDeath, dflags, hitLoc );
3705 	}
3706 	else if ( self->client->NPC_class == CLASS_INTERROGATOR )
3707 	{
3708 		Interrogator_die( self, inflictor, attacker, damage, meansOfDeath, dflags, hitLoc );
3709 	}
3710 	else if ( self->client->NPC_class == CLASS_GALAKMECH )
3711 	{//FIXME: need keyframed explosions?
3712 		NPC_SetAnim( self, SETANIM_BOTH, BOTH_DEATH1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
3713 		G_AddEvent( self, Q_irand(EV_DEATH1, EV_DEATH3), self->health );
3714 	}
3715 	else if ( self->client->NPC_class == CLASS_ATST )
3716 	{//FIXME: need keyframed explosions
3717 		if ( !self->s.number )
3718 		{
3719 			G_DrivableATSTDie( self );
3720 		}
3721 		anim = PM_PickAnim( self, BOTH_DEATH1, BOTH_DEATH25 );	//initialize to good data
3722 		if ( anim != -1 )
3723 		{
3724 			NPC_SetAnim( self, SETANIM_BOTH, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
3725 		}
3726 	}
3727 	else if ( self->s.number && self->message && meansOfDeath != MOD_SNIPER )
3728 	{//imp with a key on his arm
3729 		//pick a death anim that leaves key visible
3730 		switch ( Q_irand( 0, 3 ) )
3731 		{
3732 		case 0:
3733 			anim = BOTH_DEATH4;
3734 			break;
3735 		case 1:
3736 			anim = BOTH_DEATH21;
3737 			break;
3738 		case 2:
3739 			anim = BOTH_DEATH17;
3740 			break;
3741 		case 3:
3742 		default:
3743 			anim = BOTH_DEATH18;
3744 			break;
3745 		}
3746 		//FIXME: verify we have this anim?
3747 		NPC_SetAnim( self, SETANIM_BOTH, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
3748 		if ( meansOfDeath == MOD_KNOCKOUT || meansOfDeath == MOD_MELEE )
3749 		{
3750 			G_AddEvent( self, EV_JUMP, 0 );
3751 		}
3752 		else
3753 		{
3754 			G_AddEvent( self, Q_irand(EV_DEATH1, EV_DEATH3), self->health );
3755 		}
3756 	}
3757 	else if ( meansOfDeath == MOD_FALLING || (self->client->ps.legsAnim == BOTH_FALLDEATH1INAIR && self->client->ps.torsoAnim == BOTH_FALLDEATH1INAIR) || (self->client->ps.legsAnim == BOTH_FALLDEATH1 && self->client->ps.torsoAnim == BOTH_FALLDEATH1) )
3758 	{
3759 		//FIXME: no good way to predict you're going to fall to your death... need falling bushes/triggers?
3760 		if ( self->client->ps.groundEntityNum == ENTITYNUM_NONE //in the air
3761 			&& self->client->ps.velocity[2] < 0 //falling
3762 			&& self->client->ps.legsAnim != BOTH_FALLDEATH1INAIR //not already in falling loop
3763 			&& self->client->ps.torsoAnim != BOTH_FALLDEATH1INAIR )//not already in falling loop
3764 		{
3765 			NPC_SetAnim(self, SETANIM_BOTH, BOTH_FALLDEATH1INAIR, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD);
3766 			if ( !self->NPC )
3767 			{
3768 				G_SoundOnEnt( self, CHAN_VOICE, "*falling1.wav" );//CHAN_VOICE_ATTEN
3769 			}
3770 			else if (!(self->NPC->aiFlags&NPCAI_DIE_ON_IMPACT) )
3771 			{
3772 				G_SoundOnEnt( self, CHAN_VOICE, "*falling1.wav" );//CHAN_VOICE_ATTEN
3773 				//so we don't do this again
3774 				self->NPC->aiFlags |= NPCAI_DIE_ON_IMPACT;
3775 				//self->client->ps.gravity *= 0.5;//Fall a bit slower
3776 				self->client->ps.friction = 1;
3777 			}
3778 		}
3779 		else
3780 		{
3781 			int	deathAnim = BOTH_FALLDEATH1LAND;
3782 			if ( PM_InOnGroundAnim( &self->client->ps ) )
3783 			{
3784 				if ( AngleNormalize180(self->client->renderInfo.torsoAngles[PITCH]) < 0 )
3785 				{
3786 					deathAnim = BOTH_DEATH_LYING_UP;	//# Death anim when lying on back
3787 				}
3788 				else
3789 				{
3790 					deathAnim = BOTH_DEATH_LYING_DN;	//# Death anim when lying on front
3791 				}
3792 			}
3793 			else if ( PM_InKnockDown( &self->client->ps ) )
3794 			{
3795 				if ( AngleNormalize180(self->client->renderInfo.torsoAngles[PITCH]) < 0 )
3796 				{
3797 					deathAnim = BOTH_DEATH_FALLING_UP;	//# Death anim when falling on back
3798 				}
3799 				else
3800 				{
3801 					deathAnim = BOTH_DEATH_FALLING_DN;	//# Death anim when falling on face
3802 				}
3803 			}
3804 			NPC_SetAnim(self, SETANIM_BOTH, deathAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD);
3805 			//HMM: check for nodrop?
3806 			G_SoundOnEnt( self, CHAN_BODY, "sound/player/fallsplat.wav" );
3807 			if ( gi.VoiceVolume[self->s.number]
3808 				&& self->NPC && (self->NPC->aiFlags&NPCAI_DIE_ON_IMPACT) )
3809 			{//I was talking, so cut it off... with a jump sound?
3810 				G_SoundOnEnt( self, CHAN_VOICE_ATTEN, "*pain100.wav" );
3811 			}
3812 		}
3813 	}
3814 	else
3815 	{// normal death
3816 		anim = G_CheckSpecialDeathAnim( self, self->pos1, damage, meansOfDeath, hitLoc );
3817 		if ( anim == -1 )
3818 		{
3819 			if ( self->s.legsAnim == BOTH_RESTRAINED1 || self->s.legsAnim == BOTH_RESTRAINED1POINT )
3820 			{//super special case, floating in air lying down
3821 				anim = BOTH_RESTRAINED1;
3822 			}
3823 			else if ( PM_InOnGroundAnim( &self->client->ps ) && PM_HasAnimation( self, BOTH_LYINGDEATH1 ) )
3824 			{//on ground, need different death anim
3825 				anim = BOTH_LYINGDEATH1;
3826 			}
3827 			else if ( meansOfDeath == MOD_TRIGGER_HURT && (self->s.powerups&(1<<PW_SHOCKED)) )
3828 			{//electrocuted
3829 				anim = BOTH_DEATH17;
3830 			}
3831 			else if ( meansOfDeath == MOD_WATER )
3832 			{//drowned
3833 				anim = BOTH_DEATH17;
3834 			}
3835 			else if ( meansOfDeath != MOD_SNIPER )
3836 			{
3837 				cliff_fall = G_CheckLedgeDive( self, 128, self->client->ps.velocity, qtrue, qfalse );
3838 				if ( cliff_fall == 2 )
3839 				{
3840 					if ( !FlyingCreature( self ) && g_gravity->value > 0 )
3841 					{
3842 						if ( !self->NPC )
3843 						{
3844 							G_SoundOnEnt( self, CHAN_VOICE, "*falling1.wav" );//CHAN_VOICE_ATTEN
3845 						}
3846 						else if (!(self->NPC->aiFlags&NPCAI_DIE_ON_IMPACT) )
3847 						{
3848 							G_SoundOnEnt( self, CHAN_VOICE, "*falling1.wav" );//CHAN_VOICE_ATTEN
3849 							self->NPC->aiFlags |= NPCAI_DIE_ON_IMPACT;
3850 							self->client->ps.friction = 0;
3851 						}
3852 					}
3853 				}
3854 				if ( self->client->ps.pm_time > 0 && self->client->ps.pm_flags & PMF_TIME_KNOCKBACK && self->client->ps.velocity[2] > 0 )
3855 				{
3856 					float	thrown, dot;
3857 					vec3_t	throwdir, forward;
3858 
3859 					AngleVectors(self->currentAngles, forward, NULL, NULL);
3860 					thrown = VectorNormalize2(self->client->ps.velocity, throwdir);
3861 					dot = DotProduct(forward, throwdir);
3862 					if ( thrown > 100 )
3863 					{
3864 						if ( dot > 0.3 )
3865 						{//falling forward
3866 							if ( cliff_fall == 2 && PM_HasAnimation( self, BOTH_FALLDEATH1 ) )
3867 							{
3868 								anim = BOTH_FALLDEATH1;
3869 							}
3870 							else
3871 							{
3872 								switch ( Q_irand( 0, 7 ) )
3873 								{
3874 								case 0:
3875 								case 1:
3876 								case 2:
3877 									anim = BOTH_DEATH4;
3878 									break;
3879 								case 3:
3880 								case 4:
3881 								case 5:
3882 									anim = BOTH_DEATH5;
3883 									break;
3884 								case 6:
3885 									anim = BOTH_DEATH8;
3886 									break;
3887 								case 7:
3888 									anim = BOTH_DEATH14;
3889 									break;
3890 								}
3891 								if ( PM_HasAnimation( self, anim ))
3892 								{
3893 									self->client->ps.gravity *= 0.8;
3894 									self->client->ps.friction = 0;
3895 									if ( self->client->ps.velocity[2] > 0 && self->client->ps.velocity[2] < 100 )
3896 									{
3897 										self->client->ps.velocity[2] = 100;
3898 									}
3899 								}
3900 								else
3901 								{
3902 									anim = -1;
3903 								}
3904 							}
3905 						}
3906 						else if ( dot < -0.3 )
3907 						{
3908 							if ( thrown >= 250 && !Q_irand( 0, 3 ) )
3909 							{
3910 								if ( Q_irand( 0, 1 ) )
3911 								{
3912 									anim = BOTH_DEATHBACKWARD1;
3913 								}
3914 								else
3915 								{
3916 									anim = BOTH_DEATHBACKWARD2;
3917 								}
3918 							}
3919 							else
3920 							{
3921 								switch ( Q_irand( 0, 7 ) )
3922 								{
3923 								case 0:
3924 								case 1:
3925 									anim = BOTH_DEATH1;
3926 									break;
3927 								case 2:
3928 								case 3:
3929 									anim = BOTH_DEATH2;
3930 									break;
3931 								case 4:
3932 								case 5:
3933 									anim = BOTH_DEATH22;
3934 									break;
3935 								case 6:
3936 								case 7:
3937 									anim = BOTH_DEATH23;
3938 									break;
3939 								}
3940 							}
3941 							if ( PM_HasAnimation( self, anim ) )
3942 							{
3943 								self->client->ps.gravity *= 0.8;
3944 								self->client->ps.friction = 0;
3945 								if ( self->client->ps.velocity[2] > 0 && self->client->ps.velocity[2] < 100 )
3946 								{
3947 									self->client->ps.velocity[2] = 100;
3948 								}
3949 							}
3950 							else
3951 							{
3952 								anim = -1;
3953 							}
3954 						}
3955 						else
3956 						{//falling to one of the sides
3957 							if ( cliff_fall == 2 && PM_HasAnimation( self, BOTH_FALLDEATH1 ) )
3958 							{
3959 								anim = BOTH_FALLDEATH1;
3960 								if ( self->client->ps.velocity[2] > 0 && self->client->ps.velocity[2] < 100 )
3961 								{
3962 									self->client->ps.velocity[2] = 100;
3963 								}
3964 							}
3965 						}
3966 					}
3967 				}
3968 			}
3969 		}
3970 		else
3971 		{
3972 			specialAnim = qtrue;
3973 		}
3974 
3975 		if ( anim == -1 )
3976 		{
3977 			if ( meansOfDeath == MOD_ELECTROCUTE
3978 				|| (meansOfDeath == MOD_CRUSH && self->s.eFlags&EF_FORCE_GRIPPED) )
3979 			{//electrocuted or choked to death
3980 				anim = BOTH_DEATH17;
3981 			}
3982 			else
3983 			{
3984 				anim = G_PickDeathAnim( self, self->pos1, damage, meansOfDeath, hitLoc );
3985 			}
3986 		}
3987 		if ( anim == -1 )
3988 		{
3989 			anim = PM_PickAnim( self, BOTH_DEATH1, BOTH_DEATH25 );	//initialize to good data
3990 			//TEMP HACK: these spinny deaths should happen less often
3991 			if ( ( anim == BOTH_DEATH8 || anim == BOTH_DEATH14 ) && Q_irand( 0, 1 ) )
3992 			{
3993 				anim = PM_PickAnim( self, BOTH_DEATH1, BOTH_DEATH25 );	//initialize to good data
3994 			}
3995 		}
3996 
3997 
3998 		if ( meansOfDeath == MOD_KNOCKOUT )
3999 		{
4000 			//FIXME: knock-out sound, and don't remove me
4001 			G_AddEvent( self, EV_JUMP, 0 );
4002 			G_UseTargets2( self, self, self->target2 );
4003 			G_AlertTeam( self, attacker, 512, 32 );
4004 			if ( self->NPC )
4005 			{//stick around for a while
4006 				self->NPC->timeOfDeath = level.time + 10000;
4007 			}
4008 		}
4009 
4010 		else if ( meansOfDeath == MOD_SNIPER )
4011 		{
4012 			gentity_t	*tent;
4013 			vec3_t		spot;
4014 
4015 			VectorCopy( self->currentOrigin, spot );
4016 
4017 			self->flags |= FL_DISINTEGRATED;
4018 			self->svFlags |= SVF_BROADCAST;
4019 			tent = G_TempEntity( spot, EV_DISINTEGRATION );
4020 			tent->s.eventParm = PW_DISRUPTION;
4021 			tent->svFlags |= SVF_BROADCAST;
4022 			tent->owner = self;
4023 
4024 			G_AlertTeam( self, attacker, 512, 88 );
4025 
4026 			if ( self->playerModel >= 0 )
4027 			{
4028 				// don't let 'em animate
4029 				gi.G2API_PauseBoneAnimIndex( &self->ghoul2[self->playerModel], self->rootBone, cg.time );
4030 				gi.G2API_PauseBoneAnimIndex( &self->ghoul2[self->playerModel], self->motionBone, cg.time );
4031 				gi.G2API_PauseBoneAnimIndex( &self->ghoul2[self->playerModel], self->lowerLumbarBone, cg.time );
4032 				anim = -1;
4033 			}
4034 
4035 			//not solid anymore
4036 			self->contents = 0;
4037 			self->maxs[2] = -8;
4038 
4039 			if ( self->NPC )
4040 			{
4041 				//need to pad deathtime some to stick around long enough for death effect to play
4042 				self->NPC->timeOfDeath = level.time + 2000;
4043 			}
4044 		}
4045 		else
4046 		{
4047 			if ( hitLoc == HL_HEAD
4048 				&& !(dflags&DAMAGE_RADIUS)
4049 				&& meansOfDeath!=MOD_REPEATER_ALT
4050 				&& meansOfDeath!=MOD_FLECHETTE_ALT
4051 				&& meansOfDeath!=MOD_ROCKET
4052 				&& meansOfDeath!=MOD_ROCKET_ALT
4053 				&& meansOfDeath!=MOD_THERMAL
4054 				&& meansOfDeath!=MOD_THERMAL_ALT
4055 				&& meansOfDeath!=MOD_DETPACK
4056 				&& meansOfDeath!=MOD_LASERTRIP
4057 				&& meansOfDeath!=MOD_LASERTRIP_ALT
4058 				&& meansOfDeath!=MOD_EXPLOSIVE
4059 				&& meansOfDeath!=MOD_EXPLOSIVE_SPLASH )
4060 			{//no sound when killed by headshot (explosions don't count)
4061 				G_AlertTeam( self, attacker, 512, 0 );
4062 				if ( gi.VoiceVolume[self->s.number] )
4063 				{//I was talking, so cut it off... with a jump sound?
4064 					G_SoundOnEnt( self, CHAN_VOICE, "*jump1.wav" );
4065 				}
4066 			}
4067 			else
4068 			{
4069 				if ( (self->client->ps.eFlags&EF_FORCE_GRIPPED) )
4070 				{//killed while gripped - no loud scream
4071 					G_AlertTeam( self, attacker, 512, 32 );
4072 				}
4073 				else if ( cliff_fall != 2 )
4074 				{
4075 					if ( meansOfDeath == MOD_KNOCKOUT || meansOfDeath == MOD_MELEE )
4076 					{
4077 						G_AddEvent( self, EV_JUMP, 0 );
4078 					}
4079 					else
4080 					{
4081 						G_AddEvent( self, Q_irand(EV_DEATH1, EV_DEATH3), self->health );
4082 					}
4083 					G_DeathAlert( self, attacker );
4084 				}
4085 				else
4086 				{//screaming death is louder
4087 					G_AlertTeam( self, attacker, 512, 1024 );
4088 				}
4089 			}
4090 		}
4091 
4092 		if ( attacker && attacker->s.number == 0 )
4093 		{//killed by player
4094 			//FIXME: this should really be wherever my body comes to rest...
4095 			AddSightEvent( attacker, self->currentOrigin, 384, AEL_DISCOVERED, 10 );
4096 			//FIXME: danger event so that others will run away from this area since it's obviously dangerous
4097 		}
4098 
4099 		if ( anim >= 0 )//can be -1 if it fails, -2 if it's already in a death anim
4100 		{
4101 			NPC_SetAnim(self, SETANIM_BOTH, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD);
4102 		}
4103 	}
4104 
4105 	//do any dismemberment if there's any to do...
4106 	if ( (dflags&DAMAGE_DISMEMBER) && G_DoDismemberment( self, self->pos1, meansOfDeath, damage, hitLoc ) && !specialAnim )
4107 	{//we did dismemberment and our death anim is okay to override
4108 		if ( hitLoc == HL_HAND_RT && self->locationDamage[hitLoc] >= Q3_INFINITE && cliff_fall != 2 && self->client->ps.groundEntityNum != ENTITYNUM_NONE )
4109 		{//just lost our right hand and we're on the ground, use the special anim
4110 			NPC_SetAnim( self, SETANIM_BOTH, BOTH_RIGHTHANDCHOPPEDOFF, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
4111 		}
4112 	}
4113 
4114 	// don't allow player to respawn for a few seconds
4115 	self->client->respawnTime = level.time + 2000;//self->client->ps.legsAnimTimer;
4116 
4117 	//lock it to this anim forever
4118 	if ( self->client )
4119 	{
4120 		PM_SetLegsAnimTimer( self, &self->client->ps.legsAnimTimer, -1 );
4121 		PM_SetTorsoAnimTimer( self, &self->client->ps.torsoAnimTimer, -1 );
4122 	}
4123 
4124 	//Flying creatures should drop when killed
4125 	//FIXME: This may screw up certain things that expect to float even while dead <?>
4126 	self->svFlags &= ~SVF_CUSTOM_GRAVITY;
4127 
4128 	self->client->ps.pm_type = PM_DEAD;
4129 	//need to update STAT_HEALTH here because ClientThink_real for self may happen before STAT_HEALTH is updated from self->health and pmove will stomp death anim with a move anim
4130 	self->client->ps.stats[STAT_HEALTH] = self->health;
4131 
4132 	if ( self->NPC )
4133 	{//If an NPC, make sure we start running our scripts again- this gets set to infinite while we fall to our deaths
4134 		self->NPC->nextBStateThink = level.time;
4135 	}
4136 
4137 	if ( G_ActivateBehavior( self, BSET_DEATH ) )
4138 	{
4139 		deathScript = qtrue;
4140 	}
4141 
4142 	if ( self->NPC && (self->NPC->scriptFlags&SCF_FFDEATH) )
4143 	{
4144 		if ( G_ActivateBehavior( self, BSET_FFDEATH ) )
4145 		{//FIXME: should running this preclude running the normal deathscript?
4146 			deathScript = qtrue;
4147 		}
4148 		G_UseTargets2( self, self, self->target4 );
4149 	}
4150 
4151 	if ( !deathScript && !(self->svFlags&SVF_KILLED_SELF) )
4152 	{
4153 		//Should no longer run scripts
4154 		//WARNING!!! DO NOT DO THIS WHILE RUNNING A SCRIPT, ICARUS WILL CRASH!!!
4155 		//FIXME: shouldn't ICARUS handle this internally?
4156 		ICARUS_FreeEnt(self);
4157 	}
4158 
4159 	// Free up any timers we may have on us.
4160 	TIMER_Clear( self );
4161 
4162 	// Set pending objectives to failed
4163 	OBJ_SetPendingObjectives(self);
4164 
4165 	gi.linkentity (self);
4166 
4167 	self->bounceCount = -1; // This is a cheap hack for optimizing the pointcontents check in deadthink
4168 	if ( self->NPC )
4169 	{
4170 		self->NPC->timeOfDeath = level.time;//this will change - used for debouncing post-death events
4171 		self->s.time = level.time;//this will not chage- this is actual time of death
4172 	}
4173 
4174 	// Start any necessary death fx for this entity
4175 	DeathFX( self );
4176 }
4177 
G_CheckForStrongAttackMomentum(gentity_t * self)4178 qboolean G_CheckForStrongAttackMomentum( gentity_t *self )
4179 {//see if our saber attack has too much momentum to be interrupted
4180 	if ( PM_PowerLevelForSaberAnim( &self->client->ps ) > FORCE_LEVEL_2 )
4181 	{//strong attacks can't be interrupted
4182 		if ( PM_InAnimForSaberMove( self->client->ps.torsoAnim, self->client->ps.saberMove ) )
4183 		{//our saberMove was not already interupted by some other anim (like pain)
4184 			if ( PM_SaberInStart( self->client->ps.saberMove ) )
4185 			{
4186 				float animLength = PM_AnimLength( self->client->clientInfo.animFileIndex, (animNumber_t)self->client->ps.torsoAnim );
4187 				if ( animLength - self->client->ps.torsoAnimTimer > 750 )
4188 				{//start anim is already 3/4 of a second into it, can't interrupt it now
4189 					return qtrue;
4190 				}
4191 			}
4192 			else if ( PM_SaberInReturn( self->client->ps.saberMove ) )
4193 			{
4194 				if ( self->client->ps.torsoAnimTimer > 750 )
4195 				{//still have a good amount of time left in the return anim, can't interrupt it
4196 					return qtrue;
4197 				}
4198 			}
4199 			else
4200 			{//cannot interrupt actual transitions and attacks
4201 				return qtrue;
4202 			}
4203 		}
4204 	}
4205 	return qfalse;
4206 }
4207 
PlayerPain(gentity_t * self,gentity_t * inflictor,gentity_t * other,vec3_t point,int damage,int mod,int hitLoc)4208 void PlayerPain( gentity_t *self, gentity_t *inflictor, gentity_t *other, vec3_t point, int damage, int mod, int hitLoc )
4209 {
4210 	if ( self->client->NPC_class == CLASS_ATST )
4211 	{//different kind of pain checking altogether
4212 		G_ATSTCheckPain( self, other, point, damage, mod, hitLoc );
4213 		int blasterTest = gi.G2API_GetSurfaceRenderStatus( &self->ghoul2[self->playerModel], "head_light_blaster_cann" );
4214 		int chargerTest = gi.G2API_GetSurfaceRenderStatus( &self->ghoul2[self->playerModel], "head_concussion_charger" );
4215 		if ( blasterTest && chargerTest )
4216 		{//lost both side guns
4217 			//take away that weapon
4218 			self->client->ps.stats[STAT_WEAPONS] &= ~( 1 << WP_ATST_SIDE );
4219 			//switch to primary guns
4220 			if ( self->client->ps.weapon == WP_ATST_SIDE )
4221 			{
4222 				CG_ChangeWeapon( WP_ATST_MAIN );
4223 			}
4224 		}
4225 	}
4226 	else
4227 	{
4228 		// play an apropriate pain sound
4229 		if ( level.time > self->painDebounceTime && !(self->flags & FL_GODMODE) )
4230 		{//first time hit this frame and not in godmode
4231 			self->client->ps.damageEvent++;
4232 			if ( !Q3_TaskIDPending( self, TID_CHAN_VOICE ) )
4233 			{
4234 				if ( self->client->damage_blood )
4235 				{//took damage myself, not just armor
4236 					G_AddEvent( self, EV_PAIN, self->health );
4237 				}
4238 			}
4239 		}
4240 		if ( damage != -1 && (mod==MOD_MELEE || damage==0/*fake damage*/ || (Q_irand( 0, 10 ) <= damage && self->client->damage_blood)) )
4241 		{//-1 == don't play pain anim
4242 			if ( ( ((mod==MOD_SABER||mod==MOD_MELEE)&&self->client->damage_blood) || mod == MOD_CRUSH ) && (self->s.weapon == WP_SABER||self->s.weapon==WP_MELEE) )//FIXME: not only if using saber, but if in third person at all?  But then 1st/third person functionality is different...
4243 			{//FIXME: only strong-level saber attacks should make me play pain anim?
4244 				if ( !G_CheckForStrongAttackMomentum( self ) && !PM_SpinningSaberAnim( self->client->ps.legsAnim )
4245 					&& !PM_SaberInSpecialAttack( self->client->ps.torsoAnim )
4246 					&& !PM_InKnockDown( &self->client->ps ) )
4247 				{//strong attacks and spins cannot be interrupted by pain, no pain when in knockdown
4248 					int	parts = SETANIM_BOTH;
4249 					if ( self->client->ps.groundEntityNum != ENTITYNUM_NONE &&
4250 						!PM_SpinningSaberAnim( self->client->ps.legsAnim ) &&
4251 						!PM_FlippingAnim( self->client->ps.legsAnim ) &&
4252 						!PM_InSpecialJump( self->client->ps.legsAnim ) &&
4253 						!PM_RollingAnim( self->client->ps.legsAnim )&&
4254 						!PM_CrouchAnim( self->client->ps.legsAnim )&&
4255 						!PM_RunningAnim( self->client->ps.legsAnim ))
4256 					{//if on a surface and not in a spin or flip, play full body pain
4257 						parts = SETANIM_BOTH;
4258 					}
4259 					else
4260 					{//play pain just in torso
4261 						parts = SETANIM_TORSO;
4262 					}
4263 					if ( self->painDebounceTime < level.time )
4264 					{
4265 						//temp HACK: these are the only 2 pain anims that look good when holding a saber
4266 						NPC_SetAnim( self, parts, PM_PickAnim( self, BOTH_PAIN2, BOTH_PAIN3 ), SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
4267 						self->client->ps.saberMove = LS_READY;//don't finish whatever saber move you may have been in
4268 						//WTF - insn't working
4269 						if ( self->health < 10 && d_slowmodeath->integer > 5 )
4270 						{
4271 							G_StartMatrixEffect( self );
4272 						}
4273 					}
4274 					if ( parts == SETANIM_BOTH && (damage > 30 || (self->painDebounceTime > level.time
4275 						&& damage > 10)) )
4276 					{//took a lot of damage in 1 hit //or took 2 hits in quick succession
4277 						self->aimDebounceTime = level.time + self->client->ps.torsoAnimTimer;
4278 						self->client->ps.pm_time = self->client->ps.torsoAnimTimer;
4279 						self->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
4280 					}
4281 					self->client->ps.weaponTime = self->client->ps.torsoAnimTimer;
4282 					self->attackDebounceTime = level.time + self->client->ps.torsoAnimTimer;
4283 				}
4284 				self->painDebounceTime = level.time + self->client->ps.torsoAnimTimer;
4285 			}
4286 		}
4287 	}
4288 	if ( self->painDebounceTime <= level.time )
4289 	{
4290 		self->painDebounceTime = level.time + 700;
4291 	}
4292 }
4293 /*
4294 ================
4295 CheckArmor
4296 ================
4297 */
CheckArmor(gentity_t * ent,int damage,int dflags)4298 int CheckArmor (gentity_t *ent, int damage, int dflags)
4299 {
4300 	gclient_t	*client;
4301 	int			save;
4302 	int			count;
4303 
4304 	if (!damage)
4305 		return 0;
4306 
4307 	client = ent->client;
4308 
4309 	if (!client)
4310 		return 0;
4311 
4312 	if ( (dflags&DAMAGE_NO_ARMOR) )
4313 		return 0;
4314 
4315 	if ( client->NPC_class == CLASS_GALAKMECH )
4316 	{//special case
4317 		if ( client->ps.stats[STAT_ARMOR] <= 0 )
4318 		{//no shields
4319 			client->ps.powerups[PW_GALAK_SHIELD] = 0;
4320 			return 0;
4321 		}
4322 		else
4323 		{//shields take all the damage
4324 			client->ps.stats[STAT_ARMOR] -= damage;
4325 			if ( client->ps.stats[STAT_ARMOR] <= 0 )
4326 			{
4327 				client->ps.powerups[PW_GALAK_SHIELD] = 0;
4328 				client->ps.stats[STAT_ARMOR] = 0;
4329 			}
4330 			return damage;
4331 		}
4332 	}
4333 	else
4334 	{
4335 		// armor
4336 		count = client->ps.stats[STAT_ARMOR];
4337 
4338 		// No damage to entity until armor is at less than 50% strength
4339 		if (count > (client->ps.stats[STAT_MAX_HEALTH]/2)) // MAX_HEALTH is considered max armor. Or so I'm told.
4340 		{
4341 			save = damage;
4342 		}
4343 		else
4344 		{
4345 			if ( !ent->s.number && client->NPC_class == CLASS_ATST )
4346 			{//player in ATST... armor takes *all* the damage
4347 				save = damage;
4348 			}
4349 			else
4350 			{
4351 				save = ceil( (float) damage * ARMOR_PROTECTION );
4352 			}
4353 		}
4354 
4355 		//Always round up
4356 		if (damage == 1)
4357 		{
4358 			if ( client->ps.stats[STAT_ARMOR] > 0 )
4359 				client->ps.stats[STAT_ARMOR] -= save;
4360 
4361 			return 0;
4362 		}
4363 
4364 		if (save >= count)
4365 			save = count;
4366 
4367 		if (!save)
4368 			return 0;
4369 
4370 		client->ps.stats[STAT_ARMOR] -= save;
4371 
4372 		return save;
4373 	}
4374 }
4375 
4376 extern void NPC_SetPainEvent( gentity_t *self );
G_Knockdown(gentity_t * self,gentity_t * attacker,vec3_t pushDir,float strength,qboolean breakSaberLock)4377 void G_Knockdown( gentity_t *self, gentity_t *attacker, vec3_t pushDir, float strength, qboolean breakSaberLock )
4378 {
4379 	if ( !self || !self->client || !attacker || !attacker->client )
4380 	{
4381 		return;
4382 	}
4383 
4384 	//break out of a saberLock?
4385 	if ( breakSaberLock )
4386 	{
4387 		self->client->ps.saberLockTime = 0;
4388 		self->client->ps.saberLockEnemy = ENTITYNUM_NONE;
4389 	}
4390 
4391 	if ( self->health > 0 )
4392 	{
4393 		if ( !self->s.number )
4394 		{
4395 			NPC_SetPainEvent( self );
4396 		}
4397 		else
4398 		{
4399 			GEntity_PainFunc( self, attacker, attacker, self->currentOrigin, 0, MOD_MELEE );
4400 		}
4401 		G_CheckLedgeDive( self, 72, pushDir, qfalse, qfalse );
4402 
4403 		if ( !PM_SpinningSaberAnim( self->client->ps.legsAnim )
4404 			&& !PM_FlippingAnim( self->client->ps.legsAnim )
4405 			&& !PM_RollingAnim( self->client->ps.legsAnim )
4406 			&& !PM_InKnockDown( &self->client->ps ) )
4407 		{
4408 			int knockAnim = BOTH_KNOCKDOWN1;//default knockdown
4409 			if ( !self->s.number && ( !g_spskill->integer || strength < 300 ) )
4410 			{//player only knocked down if pushed *hard*
4411 				return;
4412 			}
4413 			else if ( PM_CrouchAnim( self->client->ps.legsAnim ) )
4414 			{//crouched knockdown
4415 				knockAnim = BOTH_KNOCKDOWN4;
4416 			}
4417 			else
4418 			{//plain old knockdown
4419 				vec3_t pLFwd, pLAngles = {0,self->client->ps.viewangles[YAW],0};
4420 				AngleVectors( pLAngles, pLFwd, NULL, NULL );
4421 				if ( DotProduct( pLFwd, pushDir ) > 0.2f )
4422 				{//pushing him from behind
4423 					knockAnim = BOTH_KNOCKDOWN3;
4424 				}
4425 				else
4426 				{//pushing him from front
4427 					knockAnim = BOTH_KNOCKDOWN1;
4428 				}
4429 			}
4430 			if ( knockAnim == BOTH_KNOCKDOWN1 && strength > 150 )
4431 			{//push *hard*
4432 				knockAnim = BOTH_KNOCKDOWN2;
4433 			}
4434 			NPC_SetAnim( self, SETANIM_BOTH, knockAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
4435 			if ( self->s.number )
4436 			{//randomize getup times
4437 				int addTime = Q_irand( -300, 1000 );
4438 				self->client->ps.legsAnimTimer += addTime;
4439 				self->client->ps.torsoAnimTimer += addTime;
4440 			}
4441 		}
4442 	}
4443 }
4444 
G_CheckKnockdown(gentity_t * targ,gentity_t * attacker,vec3_t newDir,int dflags,int mod)4445 void G_CheckKnockdown( gentity_t *targ, gentity_t *attacker, vec3_t newDir, int dflags, int mod )
4446 {
4447 	if ( !targ || !attacker )
4448 	{
4449 		return;
4450 	}
4451 	if ( !(dflags&DAMAGE_RADIUS) )
4452 	{//not inherently explosive damage, check mod
4453 		if ( mod!=MOD_REPEATER_ALT
4454 			&&mod!=MOD_FLECHETTE_ALT
4455 			&&mod!=MOD_ROCKET
4456 			&&mod!=MOD_ROCKET_ALT
4457 			&&mod!=MOD_THERMAL
4458 			&&mod!=MOD_THERMAL_ALT
4459 			&&mod!=MOD_DETPACK
4460 			&&mod!=MOD_LASERTRIP
4461 			&&mod!=MOD_LASERTRIP_ALT
4462 			&&mod!=MOD_EXPLOSIVE
4463 			&&mod!=MOD_EXPLOSIVE_SPLASH )
4464 		{
4465 			return;
4466 		}
4467 	}
4468 
4469 	if ( !targ->client || targ->client->NPC_class == CLASS_PROTOCOL || !G_StandardHumanoid( targ->NPC_type ) )
4470 	{
4471 		return;
4472 	}
4473 
4474 	if ( targ->client->ps.groundEntityNum == ENTITYNUM_NONE )
4475 	{//already in air
4476 		return;
4477 	}
4478 
4479 	if ( !targ->s.number )
4480 	{//player less likely to be knocked down
4481 		if ( !g_spskill->integer )
4482 		{//never in easy
4483 			return;
4484 		}
4485 		if ( !cg.renderingThirdPerson || cg.zoomMode )
4486 		{//never if not in chase camera view (so more likely with saber out)
4487 			return;
4488 		}
4489 		if ( g_spskill->integer == 1 )
4490 		{//33% chance on medium
4491 			if ( Q_irand( 0, 2 ) )
4492 			{
4493 				return;
4494 			}
4495 		}
4496 		else
4497 		{//50% chance on hard
4498 			if ( Q_irand( 0, 1 ) )
4499 			{
4500 				return;
4501 			}
4502 		}
4503 	}
4504 
4505 	float strength = VectorLength( targ->client->ps.velocity );
4506 	if ( targ->client->ps.velocity[2] > 100 && strength > Q_irand( 150, 350 ) )//600 ) )
4507 	{//explosive concussion possibly do a knockdown?
4508 		G_Knockdown( targ, attacker, newDir, strength, qtrue );
4509 	}
4510 }
4511 
G_ApplyKnockback(gentity_t * targ,vec3_t newDir,float knockback)4512 void G_ApplyKnockback( gentity_t *targ, vec3_t newDir, float knockback )
4513 {
4514 	vec3_t	kvel;
4515 	float	mass;
4516 
4517 	//--- TEMP TEST
4518 	if ( newDir[2] <= 0.0f )
4519 	{
4520 
4521 		newDir[2] += (( 0.0f - newDir[2] ) * 1.2f );
4522 	}
4523 
4524 	knockback *= 2.0f;
4525 
4526 	if ( knockback > 120 )
4527 	{
4528 		knockback = 120;
4529 	}
4530 	//--- TEMP TEST
4531 
4532 	if ( targ->physicsBounce > 0 )	//overide the mass
4533 		mass = targ->physicsBounce;
4534 	else
4535 		mass = 200;
4536 
4537 	if ( g_gravity->value > 0 )
4538 	{
4539 		VectorScale( newDir, g_knockback->value * (float)knockback / mass * 0.8, kvel );
4540 		kvel[2] = newDir[2] * ( g_knockback->value * (float)knockback ) / ( mass * 1.5 ) + 20;
4541 	}
4542 	else
4543 	{
4544 		VectorScale( newDir, g_knockback->value * (float)knockback / mass, kvel );
4545 	}
4546 
4547 	if ( targ->client )
4548 	{
4549 		VectorAdd( targ->client->ps.velocity, kvel, targ->client->ps.velocity );
4550 	}
4551 	else if ( targ->s.pos.trType != TR_STATIONARY && targ->s.pos.trType != TR_LINEAR_STOP && targ->s.pos.trType != TR_NONLINEAR_STOP )
4552 	{
4553 		VectorAdd( targ->s.pos.trDelta, kvel, targ->s.pos.trDelta );
4554 		VectorCopy( targ->currentOrigin, targ->s.pos.trBase );
4555 		targ->s.pos.trTime = level.time;
4556 	}
4557 
4558 	// set the timer so that the other client can't cancel
4559 	// out the movement immediately
4560 	if ( targ->client && !targ->client->ps.pm_time )
4561 	{
4562 		int		t;
4563 
4564 		t = knockback * 2;
4565 		if ( t < 50 ) {
4566 			t = 50;
4567 		}
4568 		if ( t > 200 ) {
4569 			t = 200;
4570 		}
4571 		targ->client->ps.pm_time = t;
4572 		targ->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
4573 	}
4574 }
4575 
G_CheckForLedge(gentity_t * self,vec3_t fallCheckDir,float checkDist)4576 static int G_CheckForLedge( gentity_t *self, vec3_t fallCheckDir, float checkDist )
4577 {
4578 	vec3_t	start, end;
4579 	trace_t	tr;
4580 
4581 	VectorMA( self->currentOrigin, checkDist, fallCheckDir, end );
4582 	//Should have clip burshes masked out by now and have bbox resized to death size
4583 	gi.trace( &tr, self->currentOrigin, self->mins, self->maxs, end, self->s.number, self->clipmask, G2_NOCOLLIDE, 0 );
4584 	if ( tr.allsolid || tr.startsolid )
4585 	{
4586 		return 0;
4587 	}
4588 	VectorCopy( tr.endpos, start );
4589 	VectorCopy( start, end );
4590 	end[2] -= 256;
4591 
4592 	gi.trace( &tr, start, self->mins, self->maxs, end, self->s.number, self->clipmask, G2_NOCOLLIDE, 0 );
4593 	if ( tr.allsolid || tr.startsolid )
4594 	{
4595 		return 0;
4596 	}
4597 	if ( tr.fraction >= 1.0 )
4598 	{
4599 		return (start[2]-tr.endpos[2]);
4600 	}
4601 	return 0;
4602 }
4603 
G_FriendlyFireReaction(gentity_t * self,gentity_t * other,int dflags)4604 static void G_FriendlyFireReaction( gentity_t *self, gentity_t *other, int dflags )
4605 {
4606 	if ( (!player->client->ps.viewEntity || other->s.number != player->client->ps.viewEntity))
4607 	{//hit by a teammate
4608 		if ( other != self->enemy && self != other->enemy )
4609 		{//we weren't already enemies
4610 			if ( self->enemy || other->enemy || (other->s.number&&other->s.number!=player->client->ps.viewEntity) )
4611 			{//if one of us actually has an enemy already, it's okay, just an accident OR wasn't hit by player or someone controlled by player OR player hit ally and didn't get 25% chance of getting mad (FIXME:accumulate anger+base on diff?)
4612 				return;
4613 			}
4614 			else if ( self->NPC && !other->s.number )//should be assumed, but...
4615 			{//dammit, stop that!
4616 				if ( !(dflags&DAMAGE_RADIUS) )
4617 				{
4618 					//if it's radius damage, ignore it
4619 					if ( self->NPC->ffireDebounce < level.time )
4620 					{
4621 						//FIXME: way something?  NEED DIALOGUE
4622 						self->NPC->ffireCount++;
4623 						//Com_Printf( "incr: %d < %d\n", self->NPC->ffireCount, 3+((2-g_spskill->integer)*2) );
4624 						self->NPC->ffireDebounce = level.time + 500;
4625 					}
4626 				}
4627 			}
4628 		}
4629 	}
4630 }
4631 
4632 float damageModifier[HL_MAX] =
4633 {
4634 	1.0f,	//HL_NONE,
4635 	0.25f,	//HL_FOOT_RT,
4636 	0.25f,	//HL_FOOT_LT,
4637 	0.75f,	//HL_LEG_RT,
4638 	0.75f,	//HL_LEG_LT,
4639 	1.0f,	//HL_WAIST,
4640 	1.0f,	//HL_BACK_RT,
4641 	1.0f,	//HL_BACK_LT,
4642 	1.0f,	//HL_BACK,
4643 	1.0f,	//HL_CHEST_RT,
4644 	1.0f,	//HL_CHEST_LT,
4645 	1.0f,	//HL_CHEST,
4646 	0.5f,	//HL_ARM_RT,
4647 	0.5f,	//HL_ARM_LT,
4648 	0.25f,	//HL_HAND_RT,
4649 	0.25f,	//HL_HAND_LT,
4650 	2.0f,	//HL_HEAD,
4651 	1.0f,	//HL_GENERIC1,
4652 	1.0f,	//HL_GENERIC2,
4653 	1.0f,	//HL_GENERIC3,
4654 	1.0f,	//HL_GENERIC4,
4655 	1.0f,	//HL_GENERIC5,
4656 	1.0f,	//HL_GENERIC6,
4657 };
4658 
G_TrackWeaponUsage(gentity_t * self,gentity_t * inflictor,int add,int mod)4659 void G_TrackWeaponUsage( gentity_t *self, gentity_t *inflictor, int add, int mod )
4660 {
4661 	if ( !self || !self->client || self->s.number )
4662 	{//player only
4663 		return;
4664 	}
4665 	int weapon = WP_NONE;
4666 	//FIXME: need to check the MOD to find out what weapon (if *any*) actually did the killing
4667 	if ( inflictor && !inflictor->client && mod != MOD_SABER && inflictor->lastEnemy && inflictor->lastEnemy != self )
4668 	{//a missile that was reflected, ie: not owned by me originally
4669 		if ( inflictor->owner == self && self->s.weapon == WP_SABER )
4670 		{//we reflected it
4671 			weapon = WP_SABER;
4672 		}
4673 	}
4674 	if ( weapon == WP_NONE )
4675 	{
4676 		switch ( mod )
4677 		{
4678 		case MOD_SABER:
4679 			weapon = WP_SABER;
4680 			break;
4681 		case MOD_BRYAR:
4682 		case MOD_BRYAR_ALT:
4683 			weapon = WP_BRYAR_PISTOL;
4684 			break;
4685 		case MOD_BLASTER:
4686 		case MOD_BLASTER_ALT:
4687 			weapon = WP_BLASTER;
4688 			break;
4689 		case MOD_DISRUPTOR:
4690 		case MOD_SNIPER:
4691 			weapon = WP_DISRUPTOR;
4692 			break;
4693 		case MOD_BOWCASTER:
4694 		case MOD_BOWCASTER_ALT:
4695 			weapon = WP_BOWCASTER;
4696 			break;
4697 		case MOD_REPEATER:
4698 		case MOD_REPEATER_ALT:
4699 			weapon = WP_REPEATER;
4700 			break;
4701 		case MOD_DEMP2:
4702 		case MOD_DEMP2_ALT:
4703 			weapon = WP_DEMP2;
4704 			break;
4705 		case MOD_FLECHETTE:
4706 		case MOD_FLECHETTE_ALT:
4707 			weapon = WP_FLECHETTE;
4708 			break;
4709 		case MOD_ROCKET:
4710 		case MOD_ROCKET_ALT:
4711 			weapon = WP_ROCKET_LAUNCHER;
4712 			break;
4713 		case MOD_THERMAL:
4714 		case MOD_THERMAL_ALT:
4715 			weapon = WP_THERMAL;
4716 			break;
4717 		case MOD_DETPACK:
4718 			weapon = WP_DET_PACK;
4719 			break;
4720 		case MOD_LASERTRIP:
4721 		case MOD_LASERTRIP_ALT:
4722 			weapon = WP_TRIP_MINE;
4723 			break;
4724 		case MOD_MELEE:
4725 			if ( self->s.weapon == WP_STUN_BATON )
4726 			{
4727 				weapon = WP_STUN_BATON;
4728 			}
4729 			else if ( self->s.weapon == WP_MELEE )
4730 			{
4731 				weapon = WP_MELEE;
4732 			}
4733 			break;
4734 		}
4735 	}
4736 	if ( weapon != WP_NONE )
4737 	{
4738 		self->client->sess.missionStats.weaponUsed[weapon] += add;
4739 	}
4740 }
4741 
G_NonLocationSpecificDamage(int meansOfDeath)4742 qboolean G_NonLocationSpecificDamage( int meansOfDeath )
4743 {
4744 	if ( meansOfDeath == MOD_EXPLOSIVE
4745 		|| meansOfDeath == MOD_REPEATER_ALT
4746 		|| meansOfDeath == MOD_FLECHETTE_ALT
4747 		|| meansOfDeath == MOD_ROCKET
4748 		|| meansOfDeath == MOD_ROCKET_ALT
4749 		|| meansOfDeath == MOD_THERMAL
4750 		|| meansOfDeath == MOD_THERMAL_ALT
4751 		|| meansOfDeath == MOD_DETPACK
4752 		|| meansOfDeath == MOD_LASERTRIP
4753 		|| meansOfDeath == MOD_LASERTRIP_ALT
4754 		|| meansOfDeath == MOD_MELEE
4755 		|| meansOfDeath == MOD_FORCE_GRIP
4756 		|| meansOfDeath == MOD_KNOCKOUT
4757 		|| meansOfDeath == MOD_CRUSH
4758 		|| meansOfDeath == MOD_EXPLOSIVE_SPLASH )
4759 	{
4760 		return qtrue;
4761 	}
4762 	return qfalse;
4763 }
4764 /*
4765 ============
4766 T_Damage
4767 
4768 targ		entity that is being damaged
4769 inflictor	entity that is causing the damage
4770 attacker	entity that caused the inflictor to damage targ
4771 	example: targ=monster, inflictor=rocket, attacker=player
4772 
4773 dir			direction of the attack for knockback
4774 point		point at which the damage is being inflicted, used for headshots
4775 damage		amount of damage being inflicted
4776 knockback	force to be applied against targ as a result of the damage
4777 
4778 inflictor, attacker, dir, and point can be NULL for environmental effects
4779 
4780 dflags		these flags are used to control how T_Damage works
4781 	DAMAGE_RADIUS			damage was indirect (from a nearby explosion)
4782 	DAMAGE_NO_ARMOR			armor does not protect from this damage
4783 	DAMAGE_NO_KNOCKBACK		do not affect velocity, just view angles
4784 	DAMAGE_NO_PROTECTION	kills godmode, armor, everything
4785 	DAMAGE_NO_HIT_LOC		Damage not based on hit location
4786 ============
4787 */
4788 
G_Damage(gentity_t * targ,gentity_t * inflictor,gentity_t * attacker,vec3_t dir,vec3_t point,int damage,int dflags,int mod,int hitLoc)4789 void G_Damage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker, vec3_t dir, vec3_t point, int damage, int dflags, int mod, int hitLoc )
4790 {
4791 	gclient_t	*client;
4792 	int			take;
4793 	int			asave = 0;
4794 	int			knockback;
4795 	vec3_t		newDir;
4796 	qboolean	alreadyDead = qfalse;
4797 
4798 	if (!targ->takedamage) {
4799 		return;
4800 	}
4801 
4802 	if ( targ->health <= 0 && !targ->client )
4803 	{	// allow corpses to be disintegrated
4804 		if( mod != MOD_SNIPER || (targ->flags & FL_DISINTEGRATED) )
4805 		return;
4806 	}
4807 
4808 	// if we are the player and we are locked to an emplaced gun, we have to reroute damage to the gun....sigh.
4809 	if ( targ->s.eFlags & EF_LOCKED_TO_WEAPON && targ->s.number == 0 && targ->owner && !( targ->owner->flags & FL_GODMODE ))
4810 	{
4811 		// swapping the gun into our place to absorb our damage
4812 		targ = targ->owner;
4813 	}
4814 
4815 	if ( (targ->flags&FL_SHIELDED) && mod != MOD_SABER  && !targ->client)
4816 	{//magnetically protected, this thing can only be damaged by lightsabers
4817 		return;
4818 	}
4819 
4820 	if ( (targ->flags&FL_DMG_BY_SABER_ONLY) && mod != MOD_SABER )
4821 	{//can only be damaged by lightsabers (but no shield... yeah, it's redundant, but whattayagonnado?)
4822 		return;
4823 	}
4824 
4825 	if (( targ->flags & FL_DMG_BY_HEAVY_WEAP_ONLY ) && !( dflags & DAMAGE_HEAVY_WEAP_CLASS ))
4826 	{
4827 		// can only be damaged by an heavy type weapon...but impacting missile was in the heavy weap class...so we just aren't taking damage from this missile
4828 		return;
4829 	}
4830 
4831 	if ( targ->client && targ->client->NPC_class == CLASS_ATST )
4832 	{
4833 		// extra checks can be done here
4834 		if ( mod == MOD_SNIPER || mod == MOD_DISRUPTOR )
4835 		{
4836 			// disruptor does not hurt an atst
4837 			return;
4838 		}
4839 	}
4840 	if ( mod == MOD_SABER )
4841 	{//sabers do less damage to mark1's and atst's
4842 		if ( targ->client && (targ->client->NPC_class == CLASS_ATST || targ->client->NPC_class == CLASS_MARK1) )
4843 		{
4844 			// I guess always do 5 points of damage...feel free to tweak as needed
4845 			if ( damage > 5 )
4846 			{
4847 				damage = 5;
4848 			}
4849 		}
4850 	}
4851 
4852 	if ( !inflictor ) {
4853 		inflictor = &g_entities[ENTITYNUM_WORLD];
4854 	}
4855 	if ( !attacker ) {
4856 		attacker = &g_entities[ENTITYNUM_WORLD];
4857 	}
4858 
4859 	// no more weakling allies!
4860 //	if ( attacker->s.number != 0 && damage >= 2 && targ->s.number != 0 && attacker->client && attacker->client->playerTeam == TEAM_PLAYER )
4861 //	{//player-helpers do only half damage to enemies
4862 //		damage = ceil((float)damage/2.0f);
4863 //	}
4864 
4865 	client = targ->client;
4866 
4867 	if ( client ) {
4868 		if ( client->noclip && !targ->s.number ) {
4869 			return;
4870 		}
4871 	}
4872 
4873 	if ( dflags&DAMAGE_NO_DAMAGE )
4874 	{
4875 		damage = 0;
4876 	}
4877 
4878 	if ( dir == NULL )
4879 	{
4880 		dflags |= DAMAGE_NO_KNOCKBACK;
4881 	}
4882 	else
4883 	{
4884 		VectorNormalize2( dir, newDir );
4885 	}
4886 
4887 	if ( targ->s.number != 0 )
4888 	{//not the player
4889 		if ( (targ->flags&FL_GODMODE) || (targ->flags&FL_UNDYING) )
4890 		{//have god or undying on, so ignore no protection flag
4891 			dflags &= ~DAMAGE_NO_PROTECTION;
4892 		}
4893 	}
4894 
4895 	if ( client && PM_InOnGroundAnim( &client->ps ))
4896 	{
4897 		dflags |= DAMAGE_NO_KNOCKBACK;
4898 	}
4899 	if ( !attacker->s.number && targ->client && attacker->client && targ->client->playerTeam == attacker->client->playerTeam )
4900 	{//player doesn't do knockback against allies unless he kills them
4901 		dflags |= DAMAGE_DEATH_KNOCKBACK;
4902 	}
4903 
4904 	if ( client && client->NPC_class == CLASS_GALAKMECH )
4905 	{//hit Galak
4906 		if ( client->ps.stats[STAT_ARMOR] > 0 )
4907 		{//shields are up
4908 			dflags &= ~DAMAGE_NO_ARMOR;//always affect armor
4909 			if ( mod == MOD_ELECTROCUTE
4910 				|| mod == MOD_DEMP2
4911 				|| mod == MOD_DEMP2_ALT )
4912 			{//shield protects us from this
4913 				damage = 0;
4914 			}
4915 		}
4916 		else
4917 		{//shields down
4918 			if ( mod == MOD_MELEE
4919 				|| (mod == MOD_CRUSH && attacker && attacker->client) )
4920 			{//Galak takes no impact damage
4921 				return;
4922 			}
4923 			if ( (dflags & DAMAGE_RADIUS)
4924 				|| mod == MOD_REPEATER_ALT
4925 				|| mod == MOD_FLECHETTE_ALT
4926 				|| mod == MOD_ROCKET
4927 				|| mod == MOD_ROCKET_ALT
4928 				|| mod == MOD_THERMAL
4929 				|| mod == MOD_THERMAL_ALT
4930 				|| mod == MOD_DETPACK
4931 				|| mod == MOD_LASERTRIP
4932 				|| mod == MOD_LASERTRIP_ALT
4933 				|| mod == MOD_EXPLOSIVE_SPLASH
4934 				|| mod == MOD_ENERGY_SPLASH
4935 				|| mod == MOD_SABER )
4936 			{//galak without shields takes quarter damage from explosives and lightsaber
4937 				damage = ceil((float)damage/4.0f);
4938 			}
4939 		}
4940 	}
4941 
4942 	if ( mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT )
4943 	{
4944 		if ( client )
4945 		{
4946 			if ( client->NPC_class == CLASS_PROTOCOL || client->NPC_class == CLASS_SEEKER ||
4947 				client->NPC_class == CLASS_R2D2 || client->NPC_class == CLASS_R5D2 ||
4948 				client->NPC_class == CLASS_MOUSE || client->NPC_class == CLASS_GONK )
4949 			{
4950 				// DEMP2 does more damage to these guys.
4951 				damage *= 2;
4952 			}
4953 			else if ( client->NPC_class == CLASS_PROBE || client->NPC_class == CLASS_INTERROGATOR ||
4954 						client->NPC_class == CLASS_MARK1 || client->NPC_class == CLASS_MARK2 || client->NPC_class == CLASS_SENTRY ||
4955 						client->NPC_class == CLASS_ATST )
4956 			{
4957 				// DEMP2 does way more damage to these guys.
4958 				damage *= 5;
4959 			}
4960 		}
4961 		else if ( targ->s.weapon == WP_TURRET )
4962 		{
4963 			damage *= 6;// more damage to turret things
4964 		}
4965 	}
4966 	knockback = damage;
4967 
4968 	//Attempt to apply extra knockback
4969 	if ( dflags & DAMAGE_EXTRA_KNOCKBACK )
4970 	{
4971 		knockback *= 2;
4972 	}
4973 
4974 	if ( knockback > 200 ) {
4975 		knockback = 200;
4976 	}
4977 
4978 	if ( mod == MOD_CRUSH )
4979 	{
4980 		knockback = 0;
4981 	}
4982 	else if ( targ->flags & FL_NO_KNOCKBACK )
4983 	{
4984 		knockback = 0;
4985 	}
4986 	else if ( targ->client && attacker->client && targ->client->playerTeam == attacker->client->playerTeam )
4987 	{
4988 		knockback = 0;
4989 	}
4990 	else if ( dflags & DAMAGE_NO_KNOCKBACK )
4991 	{
4992 		knockback = 0;
4993 	}
4994 	// figure momentum add, even if the damage won't be taken
4995 	if ( knockback && !(dflags&DAMAGE_DEATH_KNOCKBACK) ) //&& targ->client
4996 	{
4997 		G_ApplyKnockback( targ, newDir, knockback );
4998 		G_CheckKnockdown( targ, attacker, newDir, dflags, mod );
4999 	}
5000 
5001 	// check for godmode, completely getting out of the damage
5002 	if ( targ->flags & FL_GODMODE && !(dflags&DAMAGE_NO_PROTECTION) )
5003 	{
5004 		if ( targ->client
5005 			&& attacker->client
5006 			&& targ->client->playerTeam == attacker->client->playerTeam
5007 			&& (!targ->NPC || !targ->NPC->charmedTime) )
5008 		{//complain, but don't turn on them
5009 			G_FriendlyFireReaction( targ, attacker, dflags );
5010 		}
5011 		return;
5012 	}
5013 
5014 	// Check for team damage
5015 	/*
5016 	if ( targ != attacker && !(dflags&DAMAGE_IGNORE_TEAM) && OnSameTeam (targ, attacker)  )
5017 	{//on same team
5018 		if ( !targ->client )
5019 		{//a non-player object should never take damage from an ent on the same team
5020 			return;
5021 		}
5022 
5023 		if ( attacker->client && attacker->client->playerTeam == targ->noDamageTeam )
5024 		{//NPC or player shot an object on his own team
5025 			return;
5026 		}
5027 
5028 		if ( attacker->s.number != 0 && targ->s.number != 0 &&//player not involved in any way in this exchange
5029 			attacker->client && targ->client &&//two NPCs
5030 			attacker->client->playerTeam == targ->client->playerTeam ) //on the same team
5031 		{//NPCs on same team don't hurt each other
5032 			return;
5033 		}
5034 
5035 		if ( targ->s.number == 0 &&//player was hit
5036 			attacker->client && targ->client &&//by an NPC
5037 			attacker->client->playerTeam == TEAM_PLAYER ) //on the same team
5038 		{
5039 			if ( attacker->enemy != targ )//by accident
5040 			{//do no damage, no armor loss, no reaction, run no scripts
5041 				return;
5042 			}
5043 		}
5044 	}
5045 	*/
5046 
5047 	// add to the attacker's hit counter
5048 	if ( attacker->client && targ != attacker && targ->health > 0 ) {
5049 		if ( OnSameTeam( targ, attacker ) ) {
5050 //			attacker->client->ps.persistant[PERS_HITS] -= damage;
5051 		} else {
5052 //			attacker->client->ps.persistant[PERS_HITS] += damage;
5053 		}
5054 	}
5055 
5056 	take = damage;
5057 
5058 	//FIXME: Do not use this method of difficulty changing
5059 	// Scale the amount of damage given to the player based on the skill setting
5060 	/*
5061 	if ( targ->s.number == 0 && targ != attacker )
5062 	{
5063 		take *= ( g_spskill->integer + 1) * 0.75;
5064 	}
5065 
5066 	if ( take < 1 ) {
5067 		take = 1;
5068 	}
5069 	*/
5070 	if ( client )
5071 	{
5072 		//don't lose armor if on same team
5073 		// save some from armor
5074 		asave = CheckArmor (targ, take, dflags);
5075 		if ( !asave )
5076 		{//all out of armor
5077 			targ->client->ps.powerups[PW_BATTLESUIT] = 0;
5078 		}
5079 		else
5080 		{
5081 			targ->client->ps.powerups[PW_BATTLESUIT] = level.time + ARMOR_EFFECT_TIME;
5082 		}
5083 		take -= asave;
5084 	}
5085 	if ( !(dflags&DAMAGE_NO_HIT_LOC) || !(dflags&DAMAGE_RADIUS))
5086 	{
5087 		if ( !G_NonLocationSpecificDamage( mod ) )
5088 		{//certain kinds of damage don't care about hitlocation
5089 			take = ceil( (float)take*damageModifier[hitLoc] );
5090 		}
5091 	}
5092 
5093 	if ( g_debugDamage->integer ) {
5094 		gi.Printf( "[%d]client:%i health:%i damage:%i armor:%i hitloc:%s\n", level.time, targ->s.number, targ->health, take, asave, hitLocName[hitLoc] );
5095 	}
5096 
5097 	// add to the damage inflicted on a player this frame
5098 	// the total will be turned into screen blends and view angle kicks
5099 	// at the end of the frame
5100 	if ( client ) {
5101 		client->ps.persistant[PERS_ATTACKER] = attacker->s.number;	//attack can be the world ent
5102 		client->damage_armor += asave;
5103 		client->damage_blood += take;
5104 		client->damage_knockback += knockback;
5105 		if ( dir ) {	//can't check newdir since it's local, newdir is dir normalized
5106 			VectorCopy ( newDir, client->damage_from );
5107 			client->damage_fromWorld = qfalse;
5108 		} else {
5109 			VectorCopy ( targ->currentOrigin, client->damage_from );
5110 			client->damage_fromWorld = qtrue;
5111 		}
5112 	}
5113 
5114 	// do the damage
5115 	if ( targ->health <= 0 )
5116 	{
5117 		alreadyDead = qtrue;
5118 	}
5119 
5120 	if ( attacker && attacker->client && !attacker->s.number )
5121 	{
5122 		if ( !alreadyDead )
5123 		{
5124 			int add;
5125 			if ( take > targ->health )
5126 			{
5127 				add = targ->health;
5128 			}
5129 			else
5130 			{
5131 				add = take;
5132 			}
5133 			add += asave;
5134 			add = ceil(add/10.0f);
5135 			if ( attacker != targ )
5136 			{
5137 				G_TrackWeaponUsage( attacker, inflictor, add, mod );
5138 			}
5139 		}
5140 	}
5141 	if ( take || (dflags&DAMAGE_NO_DAMAGE) )
5142 	{
5143 		if ( !targ->client || !attacker->client )
5144 		{
5145 			targ->health = targ->health - take;
5146 			if (targ->health < 0)
5147 			{
5148 				targ->health = 0;
5149 			}
5150 			if ( !alreadyDead && ( ((targ->flags&FL_UNDYING) && !(dflags&DAMAGE_NO_PROTECTION)) || (dflags&DAMAGE_NO_KILL)) )
5151 			{
5152 				if(targ->health < 1)
5153 				{
5154 					G_ActivateBehavior( targ, BSET_DEATH );
5155 					targ->health = 1;
5156 				}
5157 			}
5158 		}
5159 		else
5160 		{//two clients
5161 			team_t		targTeam = TEAM_FREE;
5162 			team_t		attackerTeam = TEAM_FREE;
5163 
5164 			if ( player->client->ps.viewEntity && targ->s.number == player->client->ps.viewEntity )
5165 			{
5166 				targTeam = player->client->playerTeam;
5167 			}
5168 			else if ( targ->client ) {
5169 				targTeam = targ->client->playerTeam;
5170 			}
5171 			else {
5172 				targTeam = targ->noDamageTeam;
5173 			}
5174 		//	if ( targTeam == TEAM_DISGUISE ) {
5175 		//		targTeam = TEAM_PLAYER;
5176 		//	}
5177 			if ( player->client->ps.viewEntity && attacker->s.number == player->client->ps.viewEntity )
5178 			{
5179 				attackerTeam = player->client->playerTeam;
5180 			}
5181 			else if ( attacker->client ) {
5182 				attackerTeam = attacker->client->playerTeam;
5183 			}
5184 			else {
5185 				attackerTeam = attacker->noDamageTeam;
5186 			}
5187 		//	if ( attackerTeam == TEAM_DISGUISE ) {
5188 		//		attackerTeam = TEAM_PLAYER;
5189 		//	}
5190 
5191 			if ( targTeam != attackerTeam )
5192 			{//on the same team
5193 				targ->health = targ->health - take;
5194 
5195 				//MCG - Falling should never kill player- only if a trigger_hurt does so.
5196 				if ( mod == MOD_FALLING && targ->s.number == 0 && targ->health < 1 )
5197 				{
5198 					targ->health = 1;
5199 				}
5200 				else if (targ->health < 0)
5201 				{
5202 					targ->health = 0;
5203 				}
5204 
5205 				if ( !alreadyDead && ( ((targ->flags&FL_UNDYING) && !(dflags&DAMAGE_NO_PROTECTION)) || (dflags&DAMAGE_NO_KILL) ) )
5206 				{
5207 					if ( targ->health < 1 )
5208 					{
5209 						G_ActivateBehavior( targ, BSET_DEATH );
5210 						targ->health = 1;
5211 					}
5212 				}
5213 				else if ( targ->health < 1 && attacker->client )
5214 				{	// The player or NPC just killed an enemy so increment the kills counter
5215 					attacker->client->ps.persistant[PERS_ENEMIES_KILLED]++;
5216 				}
5217 			}
5218 			else if ( targTeam == TEAM_PLAYER )
5219 			{//on the same team, and target is an ally
5220 				qboolean takeDamage = qtrue;
5221 				qboolean yellAtAttacker = qtrue;
5222 
5223 				//1) player doesn't take damage from teammates unless they're angry at him
5224 				if ( targ->s.number == 0 )
5225 				{//the player
5226 					if ( attacker->enemy != targ && attacker != targ )
5227 					{//an NPC shot the player by accident
5228 						takeDamage = qfalse;
5229 					}
5230 				}
5231 				//2) NPCs don't take any damage from player during combat
5232 				else
5233 				{//an NPC
5234 					if ( ((dflags & DAMAGE_RADIUS)) && !(dflags&DAMAGE_IGNORE_TEAM) )
5235 					{//An NPC got hit by player and this is during combat or it was slash damage
5236 						//NOTE: though it's not realistic to have teammates not take splash damage,
5237 						//		even when not in combat, it feels really bad to have them able to
5238 						//		actually be killed by the player's splash damage
5239 						takeDamage = qfalse;
5240 					}
5241 
5242 					if ( (dflags & DAMAGE_RADIUS) )
5243 					{//you're fighting and it's just radius damage, so don't even mention it
5244 						yellAtAttacker = qfalse;
5245 					}
5246 				}
5247 
5248 				if ( takeDamage )
5249 				{
5250 					targ->health = targ->health - take;
5251 					if ( !alreadyDead && (((targ->flags&FL_UNDYING) && !(dflags&DAMAGE_NO_PROTECTION) && attacker->s.number != 0) || (dflags&DAMAGE_NO_KILL) ) )
5252 					{//guy is marked undying and we're not the player or we're in combat
5253 						if ( targ->health < 1 )
5254 						{
5255 							G_ActivateBehavior( targ, BSET_DEATH );
5256 
5257 							targ->health = 1;
5258 						}
5259 					}
5260 					else if ( !alreadyDead && (((targ->flags&FL_UNDYING) && !(dflags&DAMAGE_NO_PROTECTION) && !attacker->s.number && !targ->s.number) || (dflags&DAMAGE_NO_KILL)) )
5261 					{// player is undying and he's attacking himself, don't let him die
5262 						if ( targ->health < 1 )
5263 						{
5264 							G_ActivateBehavior( targ, BSET_DEATH );
5265 
5266 							targ->health = 1;
5267 						}
5268 					}
5269 					else if ( targ->health < 0 )
5270 					{
5271 						targ->health = 0;
5272 						if ( attacker->s.number == 0 && targ->NPC )
5273 						{
5274 							targ->NPC->scriptFlags |= SCF_FFDEATH;
5275 						}
5276 					}
5277 				}
5278 
5279 				if ( yellAtAttacker )
5280 				{
5281 					if ( !targ->NPC || !targ->NPC->charmedTime )
5282 					{
5283 						G_FriendlyFireReaction( targ, attacker, dflags );
5284 					}
5285 				}
5286 			}
5287 		}
5288 
5289 		if ( targ->client ) {
5290 			targ->client->ps.stats[STAT_HEALTH] = targ->health;
5291 			g_lastClientDamaged = targ;
5292 		}
5293 
5294 		//TEMP HACK FOR PLAYER LOOK AT ENEMY CODE
5295 		//FIXME: move this to a player pain func?
5296 		if ( targ->s.number == 0 )
5297 		{
5298 			if ( !targ->enemy //player does not have an enemy yet
5299 				|| targ->enemy->s.weapon != WP_SABER //or player's enemy is not a jedi
5300 				|| attacker->s.weapon == WP_SABER )//and attacker is a jedi
5301 				//keep enemy jedi over shooters
5302 			{
5303 				if ( attacker->enemy == targ || !OnSameTeam( targ, attacker ) )
5304 				{//don't set player's enemy to teammates that hit him by accident
5305 					targ->enemy = attacker;
5306 				}
5307 				NPC_SetLookTarget( targ, attacker->s.number, level.time+1000 );
5308 			}
5309 		}
5310 		else if ( attacker->s.number == 0 && (!targ->NPC || !targ->NPC->timeOfDeath) && (mod == MOD_SABER || attacker->s.weapon != WP_SABER || !attacker->enemy || attacker->enemy->s.weapon != WP_SABER) )//keep enemy jedi over shooters
5311 		{//this looks dumb when they're on the ground and you keep hitting them, so only do this when first kill them
5312 			if ( !OnSameTeam( targ, attacker ) )
5313 			{//don't set player's enemy to teammates that he hits by accident
5314 				attacker->enemy = targ;
5315 			}
5316 			NPC_SetLookTarget( attacker, targ->s.number, level.time+1000 );
5317 		}
5318 		//TEMP HACK FOR PLAYER LOOK AT ENEMY CODE
5319 
5320 		//add up the damage to the location
5321 		if ( targ->client )
5322 		{
5323 			if ( targ->locationDamage[hitLoc] < Q3_INFINITE )
5324 			{
5325 				targ->locationDamage[hitLoc] += take;
5326 			}
5327 		}
5328 		if ( targ->health > 0 && targ->NPC && targ->NPC->surrenderTime > level.time )
5329 		{//he was surrendering, goes down with one hit
5330 			targ->health = 0;
5331 		}
5332 		if ( targ->health <= 0 )
5333 		{
5334 			if ( knockback && (dflags&DAMAGE_DEATH_KNOCKBACK) )//&& targ->client
5335 			{//only do knockback on death
5336 				if ( mod == MOD_FLECHETTE )
5337 				{//special case because this is shotgun-ish damage, we need to multiply the knockback
5338 					knockback *= 12;//*6 for 6 flechette shots
5339 				}
5340 				G_ApplyKnockback( targ, newDir, knockback );
5341 			}
5342 
5343 			if ( client )
5344 				targ->flags |= FL_NO_KNOCKBACK;
5345 
5346 			if (targ->health < -999)
5347 				targ->health = -999;
5348 
5349 			// If we are a breaking glass brush, store the damage point so we can do cool things with it.
5350 			if ( targ->svFlags & SVF_GLASS_BRUSH )
5351 			{
5352 				VectorCopy( point, targ->pos1 );
5353 				VectorCopy( dir, targ->pos2 );
5354 			}
5355 			if ( targ->client )
5356 			{//HACK
5357 				if ( point )
5358 				{
5359 					VectorCopy( point, targ->pos1 );
5360 				}
5361 				else
5362 				{
5363 					VectorCopy( targ->currentOrigin, targ->pos1 );
5364 				}
5365 			}
5366 			if ( !alreadyDead && !targ->enemy )
5367 			{//just killed and didn't have an enemy before
5368 				targ->enemy = attacker;
5369 			}
5370 			GEntity_DieFunc( targ, inflictor, attacker, take, mod, dflags, hitLoc );
5371 		}
5372 		else
5373 		{
5374 			GEntity_PainFunc( targ, inflictor, attacker, point, take, mod, hitLoc );
5375 			if ( targ->s.number == 0 )
5376 			{//player run painscript
5377 				G_ActivateBehavior( targ, BSET_PAIN );
5378 				if ( targ->health <= 25 )
5379 				{
5380 					G_ActivateBehavior( targ, BSET_FLEE );
5381 				}
5382 			}
5383 		}
5384 	}
5385 }
5386 
5387 
5388 /*
5389 ============
5390 CanDamage
5391 
5392 Returns qtrue if the inflictor can directly damage the target.  Used for
5393 explosions and melee attacks.
5394 ============
5395 */
CanDamage(gentity_t * targ,vec3_t origin)5396 qboolean CanDamage (gentity_t *targ, vec3_t origin) {
5397 	vec3_t	dest;
5398 	trace_t	tr;
5399 	vec3_t	midpoint;
5400 
5401 	// use the midpoint of the bounds instead of the origin, because
5402 	// bmodels may have their origin at 0,0,0
5403 	VectorAdd (targ->absmin, targ->absmax, midpoint);
5404 	VectorScale (midpoint, 0.5, midpoint);
5405 
5406 	VectorCopy (midpoint, dest);
5407 	gi.trace ( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID, G2_NOCOLLIDE, 0);
5408 	if (( tr.fraction == 1.0 && !(targ->contents & MASK_SOLID)) || tr.entityNum == targ->s.number ) // if we also test the entitynum's we can bust up bbrushes better!
5409 		return qtrue;
5410 
5411 	// this should probably check in the plane of projection,
5412 	// rather than in world coordinate, and also include Z
5413 	VectorCopy (midpoint, dest);
5414 	dest[0] += 15.0;
5415 	dest[1] += 15.0;
5416 	gi.trace ( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID, G2_NOCOLLIDE, 0);
5417 	if (( tr.fraction == 1.0 && !(targ->contents & MASK_SOLID)) || tr.entityNum == targ->s.number )
5418 		return qtrue;
5419 
5420 	VectorCopy (midpoint, dest);
5421 	dest[0] += 15.0;
5422 	dest[1] -= 15.0;
5423 	gi.trace ( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID, G2_NOCOLLIDE, 0);
5424 	if (( tr.fraction == 1.0 && !(targ->contents & MASK_SOLID)) || tr.entityNum == targ->s.number )
5425 		return qtrue;
5426 
5427 	VectorCopy (midpoint, dest);
5428 	dest[0] -= 15.0;
5429 	dest[1] += 15.0;
5430 	gi.trace ( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID, G2_NOCOLLIDE, 0);
5431 	if (( tr.fraction == 1.0 && !(targ->contents & MASK_SOLID)) || tr.entityNum == targ->s.number )
5432 		return qtrue;
5433 
5434 	VectorCopy (midpoint, dest);
5435 	dest[0] -= 15.0;
5436 	dest[1] -= 15.0;
5437 	gi.trace ( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID, G2_NOCOLLIDE, 0);
5438 	if (( tr.fraction == 1.0 && !(targ->contents & MASK_SOLID)) || tr.entityNum == targ->s.number )
5439 		return qtrue;
5440 
5441 
5442 	return qfalse;
5443 }
5444 
5445 
5446 /*
5447 ============
5448 G_RadiusDamage
5449 ============
5450 */
G_RadiusDamage(vec3_t origin,gentity_t * attacker,float damage,float radius,gentity_t * ignore,int mod)5451 void G_RadiusDamage ( vec3_t origin, gentity_t *attacker, float damage, float radius,
5452 					 gentity_t *ignore, int mod) {
5453 	float		points, dist;
5454 	gentity_t	*ent;
5455 	gentity_t	*entityList[MAX_GENTITIES];
5456 	int			numListedEntities;
5457 	vec3_t		mins, maxs;
5458 	vec3_t		v;
5459 	vec3_t		dir;
5460 	int			i, e;
5461 
5462 	if ( radius < 1 ) {
5463 		radius = 1;
5464 	}
5465 
5466 	for ( i = 0 ; i < 3 ; i++ ) {
5467 		mins[i] = origin[i] - radius;
5468 		maxs[i] = origin[i] + radius;
5469 	}
5470 
5471 	numListedEntities = gi.EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
5472 
5473 	for ( e = 0 ; e < numListedEntities ; e++ ) {
5474 		ent = entityList[ e ];
5475 
5476 		if ( ent == ignore )
5477 			continue;
5478 		if ( !ent->takedamage )
5479 			continue;
5480 		if ( !ent->contents )
5481 			continue;
5482 
5483 		// find the distance from the edge of the bounding box
5484 		for ( i = 0 ; i < 3 ; i++ ) {
5485 			if ( origin[i] < ent->absmin[i] ) {
5486 				v[i] = ent->absmin[i] - origin[i];
5487 			} else if ( origin[i] > ent->absmax[i] ) {
5488 				v[i] = origin[i] - ent->absmax[i];
5489 			} else {
5490 				v[i] = 0;
5491 			}
5492 		}
5493 
5494 		dist = VectorLength( v );
5495 		if ( dist >= radius ) {
5496 			continue;
5497 		}
5498 
5499 		points = damage * ( 1.0 - dist / radius );
5500 
5501 		if (CanDamage (ent, origin))
5502 		{//FIXME: still do a little damage in in PVS and close?
5503 			if ( ent->svFlags & (SVF_GLASS_BRUSH|SVF_BBRUSH) )
5504 			{
5505 				VectorAdd( ent->absmin, ent->absmax, v );
5506 				VectorScale( v, 0.5f, v );
5507 			}
5508 			else
5509 			{
5510 				VectorCopy( ent->currentOrigin, v );
5511 			}
5512 
5513 			VectorSubtract( v, origin, dir);
5514 			// push the center of mass higher than the origin so players
5515 			// get knocked into the air more
5516 			dir[2] += 24;
5517 
5518 			if ( ent->svFlags & SVF_GLASS_BRUSH )
5519 			{
5520 				if ( points > 1.0f )
5521 				{
5522 					// we want to cap this at some point, otherwise it just gets crazy
5523 					if ( points > 6.0f )
5524 					{
5525 						VectorScale( dir, 6.0f, dir );
5526 					}
5527 					else
5528 					{
5529 						VectorScale( dir, points, dir );
5530 					}
5531 				}
5532 
5533 				ent->splashRadius = radius;// * ( 1.0 - dist / radius );
5534 			}
5535 
5536 			G_Damage (ent, NULL, attacker, dir, origin, (int)points, DAMAGE_RADIUS, mod);
5537 		}
5538 	}
5539 }
5540