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