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_local.h"
27 #include "b_local.h"
28 #include "g_functions.h"
29 #include "anims.h"
30 #include "objectives.h"
31 #include "../cgame/cg_local.h"
32 #include "wp_saber.h"
33 #include "g_vehicles.h"
34 #include "Q3_Interface.h"
35 #include "g_navigator.h"
36
37 #define TURN_OFF 0x00000100
38
39 extern qboolean Rosh_TwinPresent( gentity_t *self );
40 extern void G_CheckCharmed( gentity_t *self );
41 extern qboolean Wampa_CheckDropVictim( gentity_t *self, qboolean excludeMe );
42
43 extern cvar_t *g_debugDamage;
44 extern qboolean stop_icarus;
45 extern cvar_t *g_dismemberment;
46 extern cvar_t *g_saberRealisticCombat;
47 extern cvar_t *g_saberPickuppableDroppedSabers;
48 extern cvar_t *g_timescale;
49 extern cvar_t *d_slowmodeath;
50 extern gentity_t *player;
51 extern cvar_t *debug_subdivision;
52 extern cvar_t *g_dismemberProbabilities;
53
54 gentity_t *g_lastClientDamaged;
55
56 extern int killPlayerTimer;
57
58 extern void G_VehicleStartExplosionDelay( gentity_t *self );
59 extern void NPC_TempLookTarget ( gentity_t *self, int lookEntNum, int minLookTime, int maxLookTime );
60 extern void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime );
61 extern qboolean PM_HasAnimation( gentity_t *ent, int animation );
62 extern qboolean G_TeamEnemy( gentity_t *self );
63 extern void CG_ChangeWeapon( int num );
64 extern void ChangeWeapon( gentity_t *ent, int newWeapon );
65
66 extern void G_SetEnemy( gentity_t *self, gentity_t *enemy );
67 extern void PM_SetLegsAnimTimer( gentity_t *ent, int *legsAnimTimer, int time );
68 extern void PM_SetTorsoAnimTimer( gentity_t *ent, int *torsoAnimTimer, int time );
69 extern int PM_PickAnim( gentity_t *self, int minAnim, int maxAnim );
70 extern qboolean PM_InOnGroundAnim ( playerState_t *ps );
71 extern void G_ATSTCheckPain( gentity_t *self, gentity_t *other, const vec3_t point, int damage, int mod,int hitLoc );
72 extern qboolean Jedi_WaitingAmbush( gentity_t *self );
73 extern qboolean G_ClearViewEntity( gentity_t *ent );
74 extern qboolean PM_CrouchAnim( int anim );
75 extern qboolean PM_InKnockDown( playerState_t *ps );
76 extern qboolean PM_InRoll( playerState_t *ps );
77 extern qboolean PM_SpinningAnim( int anim );
78 extern qboolean PM_RunningAnim( int anim );
79 extern int PM_PowerLevelForSaberAnim( playerState_t *ps, int saberNum = 0 );
80 extern qboolean PM_SaberInSpecialAttack( int anim );
81 extern qboolean PM_SpinningSaberAnim( int anim );
82 extern qboolean PM_FlippingAnim( int anim );
83 extern qboolean PM_InSpecialJump( int anim );
84 extern qboolean PM_RollingAnim( int anim );
85 extern qboolean PM_InAnimForSaberMove( int anim, int saberMove );
86 extern qboolean PM_SaberInStart( int move );
87 extern qboolean PM_SaberInReturn( int move );
88 extern int PM_AnimLength( int index, animNumber_t anim );
89 extern qboolean PM_LockedAnim( int anim );
90 extern qboolean PM_KnockDownAnim( int anim );
91 extern void G_SpeechEvent( gentity_t *self, int event );
92 extern qboolean Rosh_BeingHealed( gentity_t *self );
93
94 static int G_CheckForLedge( gentity_t *self, vec3_t fallCheckDir, float checkDist );
95 static int G_CheckSpecialDeathAnim( gentity_t *self, vec3_t point, int damage, int mod, int hitLoc );
96 static int G_PickDeathAnim( gentity_t *self, vec3_t point, int damage, int mod, int hitLoc );
97 static void G_TrackWeaponUsage( gentity_t *self, gentity_t *inflictor, int add, int mod );
98 static qboolean G_Dismemberable( gentity_t *self, int hitLoc );
99 extern gitem_t *FindItemForAmmo( ammo_t ammo );
100 extern void WP_RemoveSaber( gentity_t *ent, int saberNum );
101
102
103 qboolean G_GetRootSurfNameWithVariant( gentity_t *ent, const char *rootSurfName, char *returnSurfName, int returnSize );
104 /*
105 ============
106 AddScore
107
108 Adds score to both the client and his team
109 ============
110 */
AddScore(gentity_t * ent,int score)111 void AddScore( gentity_t *ent, int score ) {
112 if ( !ent->client ) {
113 return;
114 }
115 // no scoring during pre-match warmup
116 ent->client->ps.persistant[PERS_SCORE] += score;
117 }
118
119 /*
120 =================
121 TossClientItems
122
123 Toss the weapon and powerups for the killed player
124 =================
125 */
126 extern gentity_t *WP_DropThermal( gentity_t *ent );
127 extern qboolean WP_SaberLose( gentity_t *self, vec3_t throwDir );
TossClientItems(gentity_t * self)128 gentity_t *TossClientItems( gentity_t *self )
129 {
130 //FIXME: drop left-hand weapon, too?
131 gentity_t *dropped = NULL;
132 gitem_t *item = NULL;
133 int weapon;
134
135 if ( self->client->NPC_class == CLASS_SEEKER
136 || self->client->NPC_class == CLASS_REMOTE
137 || self->client->NPC_class == CLASS_SABER_DROID
138 || self->client->NPC_class == CLASS_VEHICLE
139 || self->client->NPC_class == CLASS_ATST)
140 {
141 // these things are so small that they shouldn't bother throwing anything
142 return NULL;
143 }
144
145 // drop the weapon if not a saber or enemy-only weapon
146 weapon = self->s.weapon;
147 if ( weapon == WP_SABER )
148 {
149 if ( self->weaponModel[0] < 0 )
150 {//don't have one in right hand
151 self->s.weapon = WP_NONE;
152 }
153 else if ( !(self->client->ps.saber[0].saberFlags&SFL_NOT_DISARMABLE)
154 || g_saberPickuppableDroppedSabers->integer )
155 {//okay to drop it
156 if ( WP_SaberLose( self, NULL ) )
157 {
158 self->s.weapon = WP_NONE;
159 }
160 }
161 if ( g_saberPickuppableDroppedSabers->integer )
162 {//drop your left one, too
163 if ( self->weaponModel[1] >= 0 )
164 {//have one in left
165 if ( !(self->client->ps.saber[0].saberFlags&SFL_NOT_DISARMABLE)
166 || g_saberPickuppableDroppedSabers->integer )
167 {//okay to drop it
168 //just drop an item
169 if ( self->client->ps.saber[1].name
170 && self->client->ps.saber[1].name[0] )
171 {//have a valid string to use for saberType
172 //turn it into a pick-uppable item!
173 if ( G_DropSaberItem( self->client->ps.saber[1].name, self->client->ps.saber[1].blade[0].color, self->client->renderInfo.handLPoint, self->client->ps.velocity, self->currentAngles ) != NULL )
174 {//dropped it
175 WP_RemoveSaber( self, 1 );
176 }
177 }
178 }
179 }
180 }
181 }
182 else if ( weapon == WP_BLASTER_PISTOL )
183 {//FIXME: either drop the pistol and make the pickup only give ammo or drop ammo
184 }
185 else if ( weapon == WP_STUN_BATON
186 || weapon == WP_MELEE )
187 {//never drop these
188 }
189 else if ( weapon > WP_SABER && weapon <= MAX_PLAYER_WEAPONS )//&& self->client->ps.ammo[ weaponData[weapon].ammoIndex ]
190 {
191 self->s.weapon = WP_NONE;
192
193 if ( weapon == WP_THERMAL && self->client->ps.torsoAnim == BOTH_ATTACK10 )
194 {//we were getting ready to throw the thermal, drop it!
195 self->client->ps.weaponChargeTime = level.time - FRAMETIME;//so it just kind of drops it
196 dropped = WP_DropThermal( self );
197 }
198 else
199 {// find the item type for this weapon
200 item = FindItemForWeapon( (weapon_t) weapon );
201 }
202 if ( item && !dropped )
203 {
204 // spawn the item
205 dropped = Drop_Item( self, item, 0, qtrue );
206 //TEST: dropped items never go away
207 dropped->e_ThinkFunc = thinkF_NULL;
208 dropped->nextthink = -1;
209
210 if ( !self->s.number )
211 {//player's dropped items never go away
212 //dropped->e_ThinkFunc = thinkF_NULL;
213 //dropped->nextthink = -1;
214 dropped->count = 0;//no ammo
215 }
216 else
217 {//FIXME: base this on the NPC's actual amount of ammo he's used up...
218 switch ( weapon )
219 {
220 case WP_BRYAR_PISTOL:
221 case WP_BLASTER_PISTOL:
222 dropped->count = 20;
223 break;
224 case WP_BLASTER:
225 dropped->count = 15;
226 break;
227 case WP_DISRUPTOR:
228 dropped->count = 20;
229 break;
230 case WP_BOWCASTER:
231 dropped->count = 5;
232 break;
233 case WP_REPEATER:
234 dropped->count = 20;
235 break;
236 case WP_DEMP2:
237 dropped->count = 10;
238 break;
239 case WP_FLECHETTE:
240 dropped->count = 30;
241 break;
242 case WP_ROCKET_LAUNCHER:
243 dropped->count = 3;
244 break;
245 case WP_CONCUSSION:
246 dropped->count = 200;//12;
247 break;
248 case WP_THERMAL:
249 dropped->count = 4;
250 break;
251 case WP_TRIP_MINE:
252 dropped->count = 3;
253 break;
254 case WP_DET_PACK:
255 dropped->count = 1;
256 break;
257 case WP_STUN_BATON:
258 dropped->count = 20;
259 break;
260 default:
261 dropped->count = 0;
262 break;
263 }
264 }
265 // 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
266 if ( weapon != WP_THERMAL
267 && weapon != WP_TRIP_MINE
268 && weapon != WP_DET_PACK )
269 {
270 gi.G2API_InitGhoul2Model( dropped->ghoul2, item->world_model, G_ModelIndex( item->world_model ), NULL_HANDLE, NULL_HANDLE, 0, 0);
271 dropped->s.radius = 10;
272 }
273 }
274 }
275 // else if (( self->client->NPC_class == CLASS_SENTRY ) || ( self->client->NPC_class == CLASS_PROBE )) // Looks dumb, Steve told us to take it out.
276 // {
277 // item = FindItemForAmmo( AMMO_BLASTER );
278 // Drop_Item( self, item, 0, qtrue );
279 // }
280 else if ( self->client->NPC_class == CLASS_MARK1 )
281 {
282
283 if (Q_irand( 1, 2 )>1)
284 {
285 item = FindItemForAmmo( AMMO_METAL_BOLTS );
286 }
287 else
288 {
289 item = FindItemForAmmo( AMMO_BLASTER );
290 }
291 Drop_Item( self, item, 0, qtrue );
292 }
293 else if ( self->client->NPC_class == CLASS_MARK2 )
294 {
295
296 if (Q_irand( 1, 2 )>1)
297 {
298 item = FindItemForAmmo( AMMO_METAL_BOLTS );
299 }
300 else
301 {
302 item = FindItemForAmmo( AMMO_POWERCELL );
303 }
304 Drop_Item( self, item, 0, qtrue );
305 }
306
307 return dropped;//NOTE: presumes only drop one thing
308 }
309
G_DropKey(gentity_t * self)310 void G_DropKey( gentity_t *self )
311 {//drop whatever security key I was holding
312 gitem_t *item = NULL;
313 if ( !Q_stricmp( "goodie", self->message ) )
314 {
315 item = FindItemForInventory( INV_GOODIE_KEY );
316 }
317 else
318 {
319 item = FindItemForInventory( INV_SECURITY_KEY );
320 }
321 gentity_t *dropped = Drop_Item( self, item, 0, qtrue );
322 //Don't throw the key
323 VectorClear( dropped->s.pos.trDelta );
324 dropped->message = self->message;
325 self->message = NULL;
326 }
327
ObjectDie(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,int damage,int meansOfDeath)328 void ObjectDie (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath )
329 {
330 if(self->target)
331 G_UseTargets(self, attacker);
332
333 //remove my script_targetname
334 G_FreeEntity( self );
335 }
336 /*
337 ==================
338 ExplodeDeath
339 ==================
340 */
341
342 //FIXME: all hacked up...
343
ExplodeDeath(gentity_t * self)344 void ExplodeDeath( gentity_t *self )
345 {
346 // gentity_t *tent;
347 vec3_t forward;
348
349 self->takedamage = qfalse;//stop chain reaction runaway loops
350
351 self->s.loopSound = 0;
352
353 VectorCopy( self->currentOrigin, self->s.pos.trBase );
354
355 // tent = G_TempEntity( self->s.origin, EV_FX_EXPLOSION );
356 AngleVectors(self->s.angles, forward, NULL, NULL); // FIXME: letting effect always shoot up? Might be ok.
357
358 if ( self->fxID > 0 )
359 {
360 G_PlayEffect( self->fxID, self->currentOrigin, forward );
361 }
362 // else
363 // {
364 // CG_SurfaceExplosion( self->currentOrigin, forward, 20.0f, 12.0f, ((self->spawnflags&4)==qfalse) ); //FIXME: This needs to be consistent to all exploders!
365 // G_Sound(self, self->sounds );
366 // }
367
368 if(self->splashDamage > 0 && self->splashRadius > 0)
369 {
370 gentity_t *attacker = self;
371 if ( self->owner )
372 {
373 attacker = self->owner;
374 }
375 G_RadiusDamage( self->currentOrigin, attacker, self->splashDamage, self->splashRadius,
376 attacker, MOD_UNKNOWN );
377 }
378
379 ObjectDie( self, self, self, 20, 0 );
380 }
381
ExplodeDeath_Wait(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,int damage,int meansOfDeath,int dFlags,int hitLoc)382 void ExplodeDeath_Wait( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath,int dFlags,int hitLoc )
383 {
384 self->e_DieFunc = dieF_NULL;
385 self->nextthink = level.time + Q_irand(100, 500);
386 self->e_ThinkFunc = thinkF_ExplodeDeath;
387 }
388
ExplodeDeath(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,int damage,int meansOfDeath,int dFlags,int hitLoc)389 void ExplodeDeath( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath,int dFlags,int hitLoc )
390 {
391 self->currentOrigin[2] += 16; // me bad for hacking this. should either do it in the effect file or make a custom explode death??
392 ExplodeDeath( self );
393 }
394
GoExplodeDeath(gentity_t * self,gentity_t * other,gentity_t * activator)395 void GoExplodeDeath( gentity_t *self, gentity_t *other, gentity_t *activator)
396 {
397 G_ActivateBehavior(self,BSET_USE);
398
399 self->targetname = NULL; //Make sure this entity cannot be told to explode again (recursive death fix)
400
401 ExplodeDeath( self );
402 }
403
404 qboolean G_ActivateBehavior (gentity_t *self, int bset );
G_CheckVictoryScript(gentity_t * self)405 void G_CheckVictoryScript(gentity_t *self)
406 {
407 if ( !G_ActivateBehavior( self, BSET_VICTORY ) )
408 {
409 if ( self->NPC && self->s.weapon == WP_SABER )
410 {//Jedi taunt from within their AI
411 self->NPC->blockedSpeechDebounceTime = 0;//get them ready to taunt
412 return;
413 }
414 if ( self->client && self->client->NPC_class == CLASS_GALAKMECH )
415 {
416 self->wait = 1;
417 TIMER_Set( self, "gloatTime", Q_irand( 5000, 8000 ) );
418 self->NPC->blockedSpeechDebounceTime = 0;//get him ready to taunt
419 return;
420 }
421 //FIXME: any way to not say this *right away*? Wait for victim's death anim/scream to finish?
422 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 ) )
423 {//sometimes have the group commander speak instead
424 self->NPC->group->commander->NPC->greetingDebounceTime = level.time + Q_irand( 2000, 5000 );
425 //G_AddVoiceEvent( self->NPC->group->commander, Q_irand(EV_VICTORY1, EV_VICTORY3), 2000 );
426 }
427 else if ( self->NPC )
428 {
429 self->NPC->greetingDebounceTime = level.time + Q_irand( 2000, 5000 );
430 //G_AddVoiceEvent( self, Q_irand(EV_VICTORY1, EV_VICTORY3), 2000 );
431 }
432 }
433 }
434
OnSameTeam(gentity_t * ent1,gentity_t * ent2)435 qboolean OnSameTeam( gentity_t *ent1, gentity_t *ent2 )
436 {
437 if ( ent1->s.number < MAX_CLIENTS
438 && ent1->client
439 && ent1->client->playerTeam == TEAM_FREE )
440 {//evil player *has* no allies
441 return qfalse;
442 }
443 if ( ent2->s.number < MAX_CLIENTS
444 && ent2->client
445 && ent2->client->playerTeam == TEAM_FREE )
446 {//evil player *has* no allies
447 return qfalse;
448 }
449 if ( !ent1->client || !ent2->client )
450 {
451 if ( ent1->noDamageTeam )
452 {
453 if ( ent2->client && ent2->client->playerTeam == ent1->noDamageTeam )
454 {
455 return qtrue;
456 }
457 else if ( ent2->noDamageTeam == ent1->noDamageTeam )
458 {
459 if ( ent1->splashDamage && ent2->splashDamage && Q_stricmp("ambient_etherian_fliers", ent1->classname) != 0 )
460 {//Barrels, exploding breakables and mines will blow each other up
461 return qfalse;
462 }
463 else
464 {
465 return qtrue;
466 }
467 }
468 }
469 return qfalse;
470 }
471
472 // shouldn't need this anymore, there were problems with certain droids, but now they have been labeled TEAM_ENEMY so this isn't needed
473 // if ((( ent1->client->playerTeam == TEAM_IMPERIAL ) && ( ent1->client->playerTeam == TEAM_BOTS )) ||
474 // (( ent1->client->playerTeam == TEAM_BOTS ) && ( ent1->client->playerTeam == TEAM_IMPERIAL )))
475 // {
476 // return qtrue;
477 // }
478
479 return (qboolean)( ent1->client->playerTeam == ent2->client->playerTeam );
480 }
481
482
483 /*
484 -------------------------
485 G_AlertTeam
486 -------------------------
487 */
488
G_AlertTeam(gentity_t * victim,gentity_t * attacker,float radius,float soundDist)489 void G_AlertTeam( gentity_t *victim, gentity_t *attacker, float radius, float soundDist )
490 {
491 gentity_t *radiusEnts[ 128 ];
492 vec3_t mins, maxs;
493 int numEnts;
494 int i;
495 float distSq, sndDistSq = (soundDist*soundDist);
496
497 if ( attacker == NULL || attacker->client == NULL )
498 return;
499
500 //Setup the bbox to search in
501 for ( i = 0; i < 3; i++ )
502 {
503 mins[i] = victim->currentOrigin[i] - radius;
504 maxs[i] = victim->currentOrigin[i] + radius;
505 }
506
507 //Get the number of entities in a given space
508 numEnts = gi.EntitiesInBox( mins, maxs, radiusEnts, 128 );
509
510 //Cull this list
511 for ( i = 0; i < numEnts; i++ )
512 {
513 //Validate clients
514 if ( radiusEnts[i]->client == NULL )
515 continue;
516
517 //only want NPCs
518 if ( radiusEnts[i]->NPC == NULL )
519 continue;
520
521 //Don't bother if they're ignoring enemies
522 if ( radiusEnts[i]->svFlags & SVF_IGNORE_ENEMIES )
523 continue;
524
525 //This NPC specifically flagged to ignore alerts
526 if ( radiusEnts[i]->NPC->scriptFlags & SCF_IGNORE_ALERTS )
527 continue;
528
529 //This NPC specifically flagged to ignore alerts
530 if ( !(radiusEnts[i]->NPC->scriptFlags&SCF_LOOK_FOR_ENEMIES) )
531 continue;
532
533 //this ent does not participate in group AI
534 if ( (radiusEnts[i]->NPC->scriptFlags&SCF_NO_GROUPS) )
535 continue;
536
537 //Skip the requested avoid radiusEnts[i] if present
538 if ( radiusEnts[i] == victim )
539 continue;
540
541 //Skip the attacker
542 if ( radiusEnts[i] == attacker )
543 continue;
544
545 //Must be on the same team
546 if ( radiusEnts[i]->client->playerTeam != victim->client->playerTeam )
547 continue;
548
549 //Must be alive
550 if ( radiusEnts[i]->health <= 0 )
551 continue;
552
553 if ( radiusEnts[i]->enemy == NULL )
554 {//only do this if they're not already mad at someone
555 distSq = DistanceSquared( radiusEnts[i]->currentOrigin, victim->currentOrigin );
556 if ( distSq > 16384 /*128 squared*/ && !gi.inPVS( victim->currentOrigin, radiusEnts[i]->currentOrigin ) )
557 {//not even potentially visible/hearable
558 continue;
559 }
560 //NOTE: this allows sound alerts to still go through doors/PVS if the teammate is within 128 of the victim...
561 if ( soundDist <= 0 || distSq > sndDistSq )
562 {//out of sound range
563 if ( !InFOV( victim, radiusEnts[i], radiusEnts[i]->NPC->stats.hfov, radiusEnts[i]->NPC->stats.vfov )
564 || !NPC_ClearLOS( radiusEnts[i], victim->currentOrigin ) )
565 {//out of FOV or no LOS
566 continue;
567 }
568 }
569
570 //FIXME: This can have a nasty cascading effect if setup wrong...
571 G_SetEnemy( radiusEnts[i], attacker );
572 }
573 }
574 }
575
576 /*
577 -------------------------
578 G_DeathAlert
579 -------------------------
580 */
581
582 #define DEATH_ALERT_RADIUS 512
583 #define DEATH_ALERT_SOUND_RADIUS 512
584
G_DeathAlert(gentity_t * victim,gentity_t * attacker)585 void G_DeathAlert( gentity_t *victim, gentity_t *attacker )
586 {//FIXME: with all the other alert stuff, do we really need this?
587 G_AlertTeam( victim, attacker, DEATH_ALERT_RADIUS, DEATH_ALERT_SOUND_RADIUS );
588 }
589
590 /*
591 ----------------------------------------
592 DeathFX
593
594 Applies appropriate special effects that occur while the entity is dying
595 Not to be confused with NPC_RemoveBodyEffects (NPC.cpp), which only applies effect when removing the body
596 ----------------------------------------
597 */
598
DeathFX(gentity_t * ent)599 void DeathFX( gentity_t *ent )
600 {
601 if ( !ent || !ent->client )
602 return;
603 /*
604 switch( ent->client->playerTeam )
605 {
606 case TEAM_BOTS:
607 if (!Q_stricmp( ent->NPC_type, "mouse" ))
608 {
609 vec3_t effectPos;
610 VectorCopy( ent->currentOrigin, effectPos );
611 effectPos[2] -= 20;
612
613 G_PlayEffect( "mouseexplosion1", effectPos );
614 G_PlayEffect( "smaller_chunks", effectPos );
615
616 }
617 else if (!Q_stricmp( ent->NPC_type, "probe" ))
618 {
619 vec3_t effectPos;
620 VectorCopy( ent->currentOrigin, effectPos );
621 effectPos[2] += 50;
622
623 G_PlayEffect( "probeexplosion1", effectPos );
624 G_PlayEffect( "small_chunks", effectPos );
625 }
626 else
627 {
628 vec3_t effectPos;
629 VectorCopy( ent->currentOrigin, effectPos );
630 effectPos[2] -= 15;
631 G_PlayEffect( "droidexplosion1", effectPos );
632 G_PlayEffect( "small_chunks", effectPos );
633 }
634
635 break;
636
637 default:
638 break;
639 }
640 */
641 // team no longer indicates species/race. NPC_class should be used to identify certain npc types
642 vec3_t effectPos, right;
643 switch(ent->client->NPC_class)
644 {
645 case CLASS_MOUSE:
646 VectorCopy( ent->currentOrigin, effectPos );
647 effectPos[2] -= 20;
648 G_PlayEffect( "env/small_explode", effectPos );
649 G_SoundOnEnt( ent, CHAN_AUTO, "sound/chars/mouse/misc/death1" );
650 break;
651
652 case CLASS_PROBE:
653 VectorCopy( ent->currentOrigin, effectPos );
654 effectPos[2] += 50;
655 G_PlayEffect( "explosions/probeexplosion1", effectPos );
656 break;
657
658 case CLASS_ATST:
659 AngleVectors( ent->currentAngles, NULL, right, NULL );
660 VectorMA( ent->currentOrigin, 20, right, effectPos );
661 effectPos[2] += 180;
662 G_PlayEffect( "explosions/droidexplosion1", effectPos );
663 VectorMA( effectPos, -40, right, effectPos );
664 G_PlayEffect( "explosions/droidexplosion1", effectPos );
665 break;
666
667 case CLASS_SEEKER:
668 case CLASS_REMOTE:
669 G_PlayEffect( "env/small_explode", ent->currentOrigin );
670 break;
671
672 case CLASS_GONK:
673 VectorCopy( ent->currentOrigin, effectPos );
674 effectPos[2] -= 5;
675 // statusTextIndex = Q_irand( IGT_RESISTANCEISFUTILE, IGT_NAMEIS8OF12 );
676 G_SoundOnEnt( ent, CHAN_AUTO, va("sound/chars/gonk/misc/death%d.wav",Q_irand( 1, 3 )) );
677 G_PlayEffect( "env/med_explode", effectPos );
678 break;
679
680 // should list all remaining droids here, hope I didn't miss any
681 case CLASS_R2D2:
682 VectorCopy( ent->currentOrigin, effectPos );
683 effectPos[2] -= 10;
684 G_PlayEffect( "env/med_explode", effectPos );
685 G_SoundOnEnt( ent, CHAN_AUTO, "sound/chars/mark2/misc/mark2_explo" );
686 break;
687
688 case CLASS_PROTOCOL://??
689 case CLASS_R5D2:
690 VectorCopy( ent->currentOrigin, effectPos );
691 effectPos[2] -= 10;
692 G_PlayEffect( "env/med_explode", effectPos );
693 G_SoundOnEnt( ent, CHAN_AUTO, "sound/chars/mark2/misc/mark2_explo" );
694 break;
695
696 case CLASS_MARK2:
697 VectorCopy( ent->currentOrigin, effectPos );
698 effectPos[2] -= 15;
699 G_PlayEffect( "explosions/droidexplosion1", effectPos );
700 G_SoundOnEnt( ent, CHAN_AUTO, "sound/chars/mark2/misc/mark2_explo" );
701 break;
702
703 case CLASS_INTERROGATOR:
704 VectorCopy( ent->currentOrigin, effectPos );
705 effectPos[2] -= 15;
706 G_PlayEffect( "explosions/droidexplosion1", effectPos );
707 G_SoundOnEnt( ent, CHAN_AUTO, "sound/chars/interrogator/misc/int_droid_explo" );
708 break;
709
710 case CLASS_MARK1:
711 AngleVectors( ent->currentAngles, NULL, right, NULL );
712 VectorMA( ent->currentOrigin, 10, right, effectPos );
713 effectPos[2] -= 15;
714 G_PlayEffect( "explosions/droidexplosion1", effectPos );
715 VectorMA( effectPos, -20, right, effectPos );
716 G_PlayEffect( "explosions/droidexplosion1", effectPos );
717 VectorMA( effectPos, -20, right, effectPos );
718 G_PlayEffect( "explosions/droidexplosion1", effectPos );
719 G_SoundOnEnt( ent, CHAN_AUTO, "sound/chars/mark1/misc/mark1_explo" );
720 break;
721
722 case CLASS_SENTRY:
723 G_SoundOnEnt( ent, CHAN_AUTO, "sound/chars/sentry/misc/sentry_explo" );
724 VectorCopy( ent->currentOrigin, effectPos );
725 G_PlayEffect( "env/med_explode", effectPos );
726 break;
727
728 default:
729 break;
730
731 }
732
733 }
734
G_SetMissionStatusText(gentity_t * attacker,int mod)735 void G_SetMissionStatusText( gentity_t *attacker, int mod )
736 {
737 if ( statusTextIndex >= 0 )
738 {
739 return;
740 }
741
742 if ( mod == MOD_FALLING )
743 {//fell to your death
744 statusTextIndex = STAT_WATCHYOURSTEP;
745 }
746 else if ( mod == MOD_CRUSH )
747 {//crushed
748 statusTextIndex = STAT_JUDGEMENTMUCHDESIRED;
749 }
750 else if ( attacker && Q_stricmp( "trigger_hurt", attacker->classname ) == 0 )
751 {//Killed by something that should have been clearly dangerous
752 // statusTextIndex = Q_irand( IGT_JUDGEMENTDESIRED, IGT_JUDGEMENTMUCHDESIRED );
753 statusTextIndex = STAT_JUDGEMENTMUCHDESIRED;
754 }
755 else if ( attacker && attacker->s.number != 0 && attacker->client && attacker->client->playerTeam == TEAM_PLAYER )
756 {//killed by a teammate
757 statusTextIndex = STAT_INSUBORDINATION;
758 }
759 }
760
G_MakeTeamVulnerable(void)761 void G_MakeTeamVulnerable( void )
762 {
763 int i, newhealth;
764 gentity_t *ent;
765 gentity_t *self = &g_entities[0];
766 if ( !self->client )
767 {
768 return;
769 }
770
771 // for ( i = 0; i < globals.num_entities ; i++, ent++)
772 for ( i = 0; 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->client )
786 {
787 continue;
788 }
789 if ( ent->client->playerTeam != TEAM_PLAYER )
790 {
791 continue;
792 }
793 if ( !(ent->flags&FL_UNDYING) )
794 {
795 continue;
796 }
797 ent->flags &= ~FL_UNDYING;
798 newhealth = Q_irand( 5, 40 );
799 if ( ent->health > newhealth )
800 {
801 ent->health = newhealth;
802 }
803 }
804 }
805
G_StartMatrixEffect(gentity_t * ent,int meFlags=0,int length=1000,float timeScale=0.0f,int spinTime=0)806 void G_StartMatrixEffect( gentity_t *ent, int meFlags = 0, int length = 1000, float timeScale = 0.0f, int spinTime = 0 )
807 {
808 //FIXME: allow them to specify a different focal entity or point?
809 if ( g_timescale->value != 1.0 || in_camera )
810 {//already in some slow-mo mode or in_camera
811 return;
812 }
813
814 gentity_t *matrix = G_Spawn();
815 if ( matrix )
816 {
817 G_SetOrigin( matrix, ent->currentOrigin );
818 gi.linkentity( matrix );
819 matrix->s.otherEntityNum = ent->s.number;
820 matrix->e_clThinkFunc = clThinkF_CG_MatrixEffect;
821 matrix->s.eType = ET_THINKER;
822 matrix->svFlags |= SVF_BROADCAST;// Broadcast to all clients
823 matrix->s.time = level.time;
824 matrix->s.eventParm = length;
825 //now the cgame decides when to remove us... in case the framerate chugs so severely that it never finishes the effect before it removes itself!
826 //matrix->e_ThinkFunc = thinkF_G_FreeEntity;
827 //matrix->nextthink = level.time + length + 500;
828 matrix->s.boltInfo = meFlags;
829 matrix->s.time2 = spinTime;
830 matrix->s.angles2[0] = timeScale;
831 }
832 }
833
G_JediInRoom(vec3_t from)834 qboolean G_JediInRoom( vec3_t from )
835 {
836 gentity_t *ent;
837 int i;
838 // for ( i = 1, ent = &g_entities[1]; i < globals.num_entities; i++, ent++ )
839 for ( i = 1; i < globals.num_entities; i++)
840 {
841 if(!PInUse(i))
842 continue;
843 // if ( !ent->inuse )
844 // {
845 // continue;
846 // }
847 // if ( !ent )
848 // {
849 // continue;
850 // }
851 ent = &g_entities[i];
852 if ( !ent->NPC )
853 {
854 continue;
855 }
856 if ( ent->health <= 0 )
857 {
858 continue;
859 }
860 if ( ent->s.eFlags&EF_NODRAW )
861 {
862 continue;
863 }
864 if ( ent->s.weapon != WP_SABER )
865 {
866 continue;
867 }
868 if ( !gi.inPVS( ent->currentOrigin, from ) )
869 {
870 continue;
871 }
872 return qtrue;
873 }
874 return qfalse;
875 }
876
G_GetHitLocFromSurfName(gentity_t * ent,const char * surfName,int * hitLoc,vec3_t point,vec3_t dir,vec3_t bladeDir,int mod,saberType_t saberType)877 qboolean G_GetHitLocFromSurfName( gentity_t *ent, const char *surfName, int *hitLoc, vec3_t point, vec3_t dir, vec3_t bladeDir, int mod, saberType_t saberType )
878 {
879 qboolean dismember = qfalse;
880
881 *hitLoc = HL_NONE;
882
883 if ( !surfName || !surfName[0] )
884 {
885 return qfalse;
886 }
887
888 if( !ent->client )
889 {
890 return qfalse;
891 }
892
893 if ( ent->client
894 && ( ent->client->NPC_class == CLASS_R2D2
895 || ent->client->NPC_class == CLASS_R5D2
896 || ent->client->NPC_class == CLASS_GONK
897 || ent->client->NPC_class == CLASS_MOUSE
898 || ent->client->NPC_class == CLASS_SENTRY
899 || ent->client->NPC_class == CLASS_INTERROGATOR
900 || ent->client->NPC_class == CLASS_PROBE ) )
901 {//we don't care about per-surface hit-locations or dismemberment for these guys
902 return qfalse;
903 }
904
905 if ( ent->client && (ent->client->NPC_class == CLASS_ATST) )
906 {
907 //FIXME: almost impossible to hit these... perhaps we should
908 // check for splashDamage and do radius damage to these parts?
909 // Or, if we ever get bbox G2 traces, that may fix it, too
910 if (!Q_stricmp("head_light_blaster_cann",surfName))
911 {
912 *hitLoc = HL_ARM_LT;
913 }
914 else if (!Q_stricmp("head_concussion_charger",surfName))
915 {
916 *hitLoc = HL_ARM_RT;
917 }
918 return(qfalse);
919 }
920 else if ( ent->client && (ent->client->NPC_class == CLASS_MARK1) )
921 {
922 if (!Q_stricmp("l_arm",surfName))
923 {
924 *hitLoc = HL_ARM_LT;
925 }
926 else if (!Q_stricmp("r_arm",surfName))
927 {
928 *hitLoc = HL_ARM_RT;
929 }
930 else if (!Q_stricmp("torso_front",surfName))
931 {
932 *hitLoc = HL_CHEST;
933 }
934 else if (!Q_stricmp("torso_tube1",surfName))
935 {
936 *hitLoc = HL_GENERIC1;
937 }
938 else if (!Q_stricmp("torso_tube2",surfName))
939 {
940 *hitLoc = HL_GENERIC2;
941 }
942 else if (!Q_stricmp("torso_tube3",surfName))
943 {
944 *hitLoc = HL_GENERIC3;
945 }
946 else if (!Q_stricmp("torso_tube4",surfName))
947 {
948 *hitLoc = HL_GENERIC4;
949 }
950 else if (!Q_stricmp("torso_tube5",surfName))
951 {
952 *hitLoc = HL_GENERIC5;
953 }
954 else if (!Q_stricmp("torso_tube6",surfName))
955 {
956 *hitLoc = HL_GENERIC6;
957 }
958 return(qfalse);
959 }
960 else if ( ent->client && (ent->client->NPC_class == CLASS_MARK2) )
961 {
962 if (!Q_stricmp("torso_canister1",surfName))
963 {
964 *hitLoc = HL_GENERIC1;
965 }
966 else if (!Q_stricmp("torso_canister2",surfName))
967 {
968 *hitLoc = HL_GENERIC2;
969 }
970 else if (!Q_stricmp("torso_canister3",surfName))
971 {
972 *hitLoc = HL_GENERIC3;
973 }
974 return(qfalse);
975 }
976 else if ( ent->client && (ent->client->NPC_class == CLASS_GALAKMECH) )
977 {
978 if (!Q_stricmp("torso_antenna",surfName)||!Q_stricmp("torso_antenna_base",surfName))
979 {
980 *hitLoc = HL_GENERIC1;
981 }
982 else if (!Q_stricmp("torso_shield",surfName))
983 {
984 *hitLoc = HL_GENERIC2;
985 }
986 else
987 {
988 *hitLoc = HL_CHEST;
989 }
990 return(qfalse);
991 }
992
993
994 //FIXME: check the hitLoc and hitDir against the cap tag for the place
995 //where the split will be- if the hit dir is roughly perpendicular to
996 //the direction of the cap, then the split is allowed, otherwise we
997 //hit it at the wrong angle and should not dismember...
998 int actualTime = (cg.time?cg.time:level.time);
999 if ( !Q_stricmpn( "hips", surfName, 4 ) )
1000 {//FIXME: test properly for legs
1001 *hitLoc = HL_WAIST;
1002 if ( ent->client != NULL && ent->ghoul2.size() )
1003 {
1004 mdxaBone_t boltMatrix;
1005 vec3_t tagOrg, angles;
1006
1007 VectorSet( angles, 0, ent->currentAngles[YAW], 0 );
1008 if (ent->kneeLBolt>=0)
1009 {
1010 gi.G2API_GetBoltMatrix( ent->ghoul2, ent->playerModel, ent->kneeLBolt,
1011 &boltMatrix, angles, ent->currentOrigin,
1012 actualTime, NULL, ent->s.modelScale );
1013 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, tagOrg );
1014 if ( DistanceSquared( point, tagOrg ) < 100 )
1015 {//actually hit the knee
1016 *hitLoc = HL_LEG_LT;
1017 }
1018 }
1019 if (*hitLoc == HL_WAIST)
1020 {
1021 if (ent->kneeRBolt>=0)
1022 {
1023 gi.G2API_GetBoltMatrix( ent->ghoul2, ent->playerModel, ent->kneeRBolt,
1024 &boltMatrix, angles, ent->currentOrigin,
1025 actualTime, NULL, ent->s.modelScale );
1026 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, tagOrg );
1027 if ( DistanceSquared( point, tagOrg ) < 100 )
1028 {//actually hit the knee
1029 *hitLoc = HL_LEG_RT;
1030 }
1031 }
1032 }
1033 }
1034 }
1035 else if ( !Q_stricmpn( "torso", surfName, 5 ) )
1036 {
1037 if ( !ent->client )
1038 {
1039 *hitLoc = HL_CHEST;
1040 }
1041 else
1042 {
1043 vec3_t t_fwd, t_rt, t_up, dirToImpact;
1044 float frontSide, rightSide, upSide;
1045 AngleVectors( ent->client->renderInfo.torsoAngles, t_fwd, t_rt, t_up );
1046 VectorSubtract( point, ent->client->renderInfo.torsoPoint, dirToImpact );
1047 frontSide = DotProduct( t_fwd, dirToImpact );
1048 rightSide = DotProduct( t_rt, dirToImpact );
1049 upSide = DotProduct( t_up, dirToImpact );
1050 if ( upSide < -10 )
1051 {//hit at waist
1052 *hitLoc = HL_WAIST;
1053 }
1054 else
1055 {//hit on upper torso
1056 if ( rightSide > 4 )
1057 {
1058 *hitLoc = HL_ARM_RT;
1059 }
1060 else if ( rightSide < -4 )
1061 {
1062 *hitLoc = HL_ARM_LT;
1063 }
1064 else if ( rightSide > 2 )
1065 {
1066 if ( frontSide > 0 )
1067 {
1068 *hitLoc = HL_CHEST_RT;
1069 }
1070 else
1071 {
1072 *hitLoc = HL_BACK_RT;
1073 }
1074 }
1075 else if ( rightSide < -2 )
1076 {
1077 if ( frontSide > 0 )
1078 {
1079 *hitLoc = HL_CHEST_LT;
1080 }
1081 else
1082 {
1083 *hitLoc = HL_BACK_LT;
1084 }
1085 }
1086 else if ( upSide > -3 && mod == MOD_SABER )
1087 {
1088 *hitLoc = HL_HEAD;
1089 }
1090 else if ( frontSide > 0 )
1091 {
1092 *hitLoc = HL_CHEST;
1093 }
1094 else
1095 {
1096 *hitLoc = HL_BACK;
1097 }
1098 }
1099 }
1100 }
1101 else if ( !Q_stricmpn( "head", surfName, 4 ) )
1102 {
1103 *hitLoc = HL_HEAD;
1104 }
1105 else if ( !Q_stricmpn( "r_arm", surfName, 5 ) )
1106 {
1107 *hitLoc = HL_ARM_RT;
1108 if ( ent->client != NULL && ent->ghoul2.size() )
1109 {
1110 mdxaBone_t boltMatrix;
1111 vec3_t tagOrg, angles;
1112
1113 VectorSet( angles, 0, ent->currentAngles[YAW], 0 );
1114 if (ent->handRBolt>=0)
1115 {
1116 gi.G2API_GetBoltMatrix( ent->ghoul2, ent->playerModel, ent->handRBolt,
1117 &boltMatrix, angles, ent->currentOrigin,
1118 actualTime, NULL, ent->s.modelScale );
1119 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, tagOrg );
1120 if ( DistanceSquared( point, tagOrg ) < 256 )
1121 {//actually hit the hand
1122 *hitLoc = HL_HAND_RT;
1123 }
1124 }
1125 }
1126 }
1127 else if ( !Q_stricmpn( "l_arm", surfName, 5 ) )
1128 {
1129 *hitLoc = HL_ARM_LT;
1130 if ( ent->client != NULL && ent->ghoul2.size() )
1131 {
1132 mdxaBone_t boltMatrix;
1133 vec3_t tagOrg, angles;
1134
1135 VectorSet( angles, 0, ent->currentAngles[YAW], 0 );
1136 if (ent->handLBolt>=0)
1137 {
1138 gi.G2API_GetBoltMatrix( ent->ghoul2, ent->playerModel, ent->handLBolt,
1139 &boltMatrix, angles, ent->currentOrigin,
1140 actualTime, NULL, ent->s.modelScale );
1141 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, tagOrg );
1142 if ( DistanceSquared( point, tagOrg ) < 256 )
1143 {//actually hit the hand
1144 *hitLoc = HL_HAND_LT;
1145 }
1146 }
1147 }
1148 }
1149 else if ( !Q_stricmpn( "r_leg", surfName, 5 ) )
1150 {
1151 *hitLoc = HL_LEG_RT;
1152 if ( ent->client != NULL && ent->ghoul2.size() )
1153 {
1154 mdxaBone_t boltMatrix;
1155 vec3_t tagOrg, angles;
1156
1157 VectorSet( angles, 0, ent->currentAngles[YAW], 0 );
1158 if (ent->footRBolt>=0)
1159 {
1160 gi.G2API_GetBoltMatrix( ent->ghoul2, ent->playerModel, ent->footRBolt,
1161 &boltMatrix, angles, ent->currentOrigin,
1162 actualTime, NULL, ent->s.modelScale );
1163 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, tagOrg );
1164 if ( DistanceSquared( point, tagOrg ) < 100 )
1165 {//actually hit the foot
1166 *hitLoc = HL_FOOT_RT;
1167 }
1168 }
1169 }
1170 }
1171 else if ( !Q_stricmpn( "l_leg", surfName, 5 ) )
1172 {
1173 *hitLoc = HL_LEG_LT;
1174 if ( ent->client != NULL && ent->ghoul2.size() )
1175 {
1176 mdxaBone_t boltMatrix;
1177 vec3_t tagOrg, angles;
1178
1179 VectorSet( angles, 0, ent->currentAngles[YAW], 0 );
1180 if (ent->footLBolt>=0)
1181 {
1182 gi.G2API_GetBoltMatrix( ent->ghoul2, ent->playerModel, ent->footLBolt,
1183 &boltMatrix, angles, ent->currentOrigin,
1184 actualTime, NULL, ent->s.modelScale );
1185 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, tagOrg );
1186 if ( DistanceSquared( point, tagOrg ) < 100 )
1187 {//actually hit the foot
1188 *hitLoc = HL_FOOT_LT;
1189 }
1190 }
1191 }
1192 }
1193 else if ( mod == MOD_SABER && WP_BreakSaber( ent, surfName, saberType ) )
1194 {//saber hit and broken
1195 *hitLoc = HL_HAND_RT;
1196 }
1197 else if ( !Q_stricmpn( "r_hand", surfName, 6 ) || !Q_stricmpn( "w_", surfName, 2 ) )
1198 {//right hand or weapon
1199 //FIXME: if hit weapon, chance of breaking saber (if sabers.cfg entry shows it as breakable)
1200 // if breaks, remove saber and replace with the 2 replacement sabers (preserve color, length, etc.)
1201 *hitLoc = HL_HAND_RT;
1202 }
1203 else if ( !Q_stricmpn( "l_hand", surfName, 6 ) )
1204 {
1205 *hitLoc = HL_HAND_LT;
1206 }
1207 else if ( ent->client && ent->client->ps.powerups[PW_GALAK_SHIELD] && !Q_stricmp( "force_shield", surfName ) )
1208 {
1209 *hitLoc = HL_GENERIC2;
1210
1211 }
1212 #ifdef _DEBUG
1213 else
1214 {
1215 Com_Printf( "ERROR: surface %s does not belong to any hitLocation!!!\n", surfName );
1216 }
1217 #endif //_DEBUG
1218
1219 if ( g_saberRealisticCombat->integer > 1
1220 || debug_subdivision->integer )
1221 {
1222 dismember = qtrue;
1223 }
1224 else if ( ent->client && ent->client->NPC_class == CLASS_PROTOCOL )
1225 {
1226 dismember = qtrue;
1227 }
1228 else if ( ent->client && ent->client->NPC_class == CLASS_ASSASSIN_DROID )
1229 {
1230 dismember = qtrue;
1231 }
1232 else if ( ent->client && ent->client->NPC_class == CLASS_SABER_DROID )
1233 {
1234 dismember = qtrue;
1235 }
1236 else if ( debug_subdivision->integer || !ent->client->dismembered )
1237 {
1238 if ( dir && (dir[0] || dir[1] || dir[2]) &&
1239 bladeDir && (bladeDir[0] || bladeDir[1] || bladeDir[2]) )
1240 {//we care about direction (presumably for dismemberment)
1241 if ( g_dismemberProbabilities->value<=0.0f||G_Dismemberable( ent, *hitLoc ) )
1242 {//the probability let us continue
1243 const char *tagName = NULL;
1244 float aoa = 0.5f;
1245 //dir must be roughly perpendicular to the hitLoc's cap bolt
1246 switch ( *hitLoc )
1247 {
1248 case HL_LEG_RT:
1249 tagName = "*hips_cap_r_leg";
1250 break;
1251 case HL_LEG_LT:
1252 tagName = "*hips_cap_l_leg";
1253 break;
1254 case HL_WAIST:
1255 tagName = "*hips_cap_torso";
1256 aoa = 0.25f;
1257 break;
1258 case HL_CHEST_RT:
1259 case HL_ARM_RT:
1260 case HL_BACK_LT:
1261 tagName = "*torso_cap_r_arm";
1262 break;
1263 case HL_CHEST_LT:
1264 case HL_ARM_LT:
1265 case HL_BACK_RT:
1266 tagName = "*torso_cap_l_arm";
1267 break;
1268 case HL_HAND_RT:
1269 tagName = "*r_arm_cap_r_hand";
1270 break;
1271 case HL_HAND_LT:
1272 tagName = "*l_arm_cap_l_hand";
1273 break;
1274 case HL_HEAD:
1275 tagName = "*torso_cap_head";
1276 aoa = 0.25f;
1277 break;
1278 case HL_CHEST:
1279 case HL_BACK:
1280 case HL_FOOT_RT:
1281 case HL_FOOT_LT:
1282 default:
1283 //no dismemberment possible with these, so no checks needed
1284 break;
1285 }
1286 if ( tagName )
1287 {
1288 int tagBolt = gi.G2API_AddBolt( &ent->ghoul2[ent->playerModel], tagName );
1289 if ( tagBolt != -1 )
1290 {
1291 mdxaBone_t boltMatrix;
1292 vec3_t tagOrg, tagDir, angles;
1293 VectorSet( angles, 0, ent->currentAngles[YAW], 0 );
1294 gi.G2API_GetBoltMatrix( ent->ghoul2, ent->playerModel, tagBolt,
1295 &boltMatrix, angles, ent->currentOrigin,
1296 actualTime, NULL, ent->s.modelScale );
1297 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, tagOrg );
1298 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, NEGATIVE_Y, tagDir );
1299 if ( DistanceSquared( point, tagOrg ) < 256 )
1300 {//hit close
1301 float dot = DotProduct( dir, tagDir );
1302 if ( dot < aoa && dot > -aoa )
1303 {//hit roughly perpendicular
1304 dot = DotProduct( bladeDir, tagDir );
1305 if ( dot < aoa && dot > -aoa )
1306 {//blade was roughly perpendicular
1307 dismember = qtrue;
1308 }
1309 }
1310 }
1311 }
1312 }
1313 }
1314 }
1315 }
1316 return dismember;
1317 }
1318
G_GetHitLocation(gentity_t * target,const vec3_t ppoint)1319 int G_GetHitLocation ( gentity_t *target, const vec3_t ppoint )
1320 {
1321 vec3_t point, point_dir;
1322 vec3_t forward, right, up;
1323 vec3_t tangles, tcenter;
1324 float udot, fdot, rdot;
1325 int Vertical, Forward, Lateral;
1326 int HitLoc;
1327
1328 //get target forward, right and up
1329 if(target->client)
1330 {//ignore player's pitch and roll
1331 VectorSet(tangles, 0, target->currentAngles[YAW], 0);
1332 }
1333
1334 AngleVectors(tangles, forward, right, up);
1335
1336 //get center of target
1337 VectorAdd(target->absmin, target->absmax, tcenter);
1338 VectorScale(tcenter, 0.5, tcenter);
1339
1340 //get impact point
1341 if(ppoint && !VectorCompare(ppoint, vec3_origin))
1342 {
1343 VectorCopy(ppoint, point);
1344 }
1345 else
1346 {
1347 return HL_NONE;
1348 }
1349
1350 /*
1351 //get impact dir
1352 if(pdir && !VectorCompare(pdir, vec3_origin))
1353 {
1354 VectorCopy(pdir, dir);
1355 }
1356 else
1357 {
1358 return;
1359 }
1360
1361 //put point at controlled distance from center
1362 VectorSubtract(point, tcenter, tempvec);
1363 tempvec[2] = 0;
1364 hdist = VectorLength(tempvec);
1365
1366 VectorMA(point, hdist - tradius, dir, point);
1367 //now a point on the surface of a cylinder with a radius of tradius
1368 */
1369 VectorSubtract(point, tcenter, point_dir);
1370 VectorNormalize(point_dir);
1371
1372 //Get bottom to top (Vertical) position index
1373 udot = DotProduct(up, point_dir);
1374 if(udot>.800)
1375 Vertical = 4;
1376 else if(udot>.400)
1377 Vertical = 3;
1378 else if(udot>-.333)
1379 Vertical = 2;
1380 else if(udot>-.666)
1381 Vertical = 1;
1382 else
1383 Vertical = 0;
1384
1385 //Get back to front (Forward) position index
1386 fdot = DotProduct(forward, point_dir);
1387 if(fdot>.666)
1388 Forward = 4;
1389 else if(fdot>.333)
1390 Forward = 3;
1391 else if(fdot>-.333)
1392 Forward = 2;
1393 else if(fdot>-.666)
1394 Forward = 1;
1395 else
1396 Forward = 0;
1397
1398 //Get left to right (Lateral) position index
1399 rdot = DotProduct(right, point_dir);
1400 if(rdot>.666)
1401 Lateral = 4;
1402 else if(rdot>.333)
1403 Lateral = 3;
1404 else if(rdot>-.333)
1405 Lateral = 2;
1406 else if(rdot>-.666)
1407 Lateral = 1;
1408 else
1409 Lateral = 0;
1410
1411 HitLoc = Vertical * 25 + Forward * 5 + Lateral;
1412
1413 if(HitLoc <= 10)
1414 {//feet
1415 if ( rdot > 0 )
1416 {
1417 return HL_FOOT_RT;
1418 }
1419 else
1420 {
1421 return HL_FOOT_LT;
1422 }
1423 }
1424 else if(HitLoc <= 50)
1425 {//legs
1426 if ( rdot > 0 )
1427 {
1428 return HL_LEG_RT;
1429 }
1430 else
1431 {
1432 return HL_LEG_LT;
1433 }
1434 }
1435 else if ( HitLoc == 56||HitLoc == 60||HitLoc == 61||HitLoc == 65||HitLoc == 66||HitLoc == 70 )
1436 {//hands
1437 if ( rdot > 0 )
1438 {
1439 return HL_HAND_RT;
1440 }
1441 else
1442 {
1443 return HL_HAND_LT;
1444 }
1445 }
1446 else if ( HitLoc == 83||HitLoc == 87||HitLoc == 88||HitLoc == 92||HitLoc == 93||HitLoc == 97 )
1447 {//arms
1448 if ( rdot > 0 )
1449 {
1450 return HL_ARM_RT;
1451 }
1452 else
1453 {
1454 return HL_ARM_LT;
1455 }
1456 }
1457 else if((HitLoc >= 107 && HitLoc <= 109)||
1458 (HitLoc >= 112 && HitLoc <= 114)||
1459 (HitLoc >= 117 && HitLoc <= 119))
1460 {//head
1461 return HL_HEAD;
1462 }
1463 else
1464 {
1465 if ( udot < 0.3 )
1466 {
1467 return HL_WAIST;
1468 }
1469 else if ( fdot < 0 )
1470 {
1471 if ( rdot > 0.4 )
1472 {
1473 return HL_BACK_RT;
1474 }
1475 else if ( rdot < -0.4 )
1476 {
1477 return HL_BACK_LT;
1478 }
1479 else
1480 {
1481 return HL_BACK;
1482 }
1483 }
1484 else
1485 {
1486 if ( rdot > 0.3 )
1487 {
1488 return HL_CHEST_RT;
1489 }
1490 else if ( rdot < -0.3 )
1491 {
1492 return HL_CHEST_LT;
1493 }
1494 else
1495 {
1496 return HL_CHEST;
1497 }
1498 }
1499 }
1500 //return HL_NONE;
1501 }
1502
G_PickPainAnim(gentity_t * self,const vec3_t point,int damage,int hitLoc=HL_NONE)1503 int G_PickPainAnim( gentity_t *self, const vec3_t point, int damage, int hitLoc = HL_NONE )
1504 {
1505 if ( hitLoc == HL_NONE )
1506 {
1507 hitLoc = G_GetHitLocation( self, point );
1508 }
1509 switch( hitLoc )
1510 {
1511 case HL_FOOT_RT:
1512 return BOTH_PAIN12;
1513 //PAIN12 = right foot
1514 break;
1515 case HL_FOOT_LT:
1516 return -1;
1517 break;
1518 case HL_LEG_RT:
1519 if ( !Q_irand( 0, 1 ) )
1520 {
1521 return BOTH_PAIN11;
1522 }
1523 else
1524 {
1525 return BOTH_PAIN13;
1526 }
1527 //PAIN11 = twitch right leg
1528 //PAIN13 = right knee
1529 break;
1530 case HL_LEG_LT:
1531 return BOTH_PAIN14;
1532 //PAIN14 = twitch left leg
1533 break;
1534 case HL_BACK_RT:
1535 return BOTH_PAIN7;
1536 //PAIN7 = med left shoulder
1537 break;
1538 case HL_BACK_LT:
1539 return Q_irand( BOTH_PAIN15, BOTH_PAIN16 );
1540 //PAIN15 = med right shoulder
1541 //PAIN16 = twitch right shoulder
1542 break;
1543 case HL_BACK:
1544 if ( !Q_irand( 0, 1 ) )
1545 {
1546 return BOTH_PAIN1;
1547 }
1548 else
1549 {
1550 return BOTH_PAIN5;
1551 }
1552 //PAIN1 = back
1553 //PAIN5 = same as 1
1554 break;
1555 case HL_CHEST_RT:
1556 return BOTH_PAIN3;
1557 //PAIN3 = long, right shoulder
1558 break;
1559 case HL_CHEST_LT:
1560 return BOTH_PAIN2;
1561 //PAIN2 = long, left shoulder
1562 break;
1563 case HL_WAIST:
1564 case HL_CHEST:
1565 if ( !Q_irand( 0, 3 ) )
1566 {
1567 return BOTH_PAIN6;
1568 }
1569 else if ( !Q_irand( 0, 2 ) )
1570 {
1571 return BOTH_PAIN8;
1572 }
1573 else if ( !Q_irand( 0, 1 ) )
1574 {
1575 return BOTH_PAIN17;
1576 }
1577 else
1578 {
1579 return BOTH_PAIN18;
1580 }
1581 //PAIN6 = gut
1582 //PAIN8 = chest
1583 //PAIN17 = twitch crotch
1584 //PAIN19 = med crotch
1585 break;
1586 case HL_ARM_RT:
1587 case HL_HAND_RT:
1588 return BOTH_PAIN9;
1589 //PAIN9 = twitch right arm
1590 break;
1591 case HL_ARM_LT:
1592 case HL_HAND_LT:
1593 return BOTH_PAIN10;
1594 //PAIN10 = twitch left arm
1595 break;
1596 case HL_HEAD:
1597 return BOTH_PAIN4;
1598 //PAIN4 = head
1599 break;
1600 default:
1601 return -1;
1602 break;
1603 }
1604 }
1605
1606 extern void G_BounceMissile( gentity_t *ent, trace_t *trace );
LimbThink(gentity_t * ent)1607 void LimbThink( gentity_t *ent )
1608 {//FIXME: just use object thinking?
1609 vec3_t origin;
1610 trace_t tr;
1611
1612
1613 ent->nextthink = level.time + FRAMETIME;
1614 if ( ent->owner
1615 && ent->owner->client
1616 && (ent->owner->client->ps.eFlags&EF_HELD_BY_RANCOR) )
1617 {
1618 ent->e_ThinkFunc = thinkF_G_FreeEntity;
1619 return;
1620 }
1621
1622 if ( ent->enemy )
1623 {//alert people that I am a piece of one of their friends
1624 AddSightEvent( ent->enemy, ent->currentOrigin, 384, AEL_DISCOVERED );
1625 }
1626
1627 if ( ent->s.pos.trType == TR_STATIONARY )
1628 {//stopped
1629 if ( level.time > ent->s.apos.trTime + ent->s.apos.trDuration )
1630 {
1631 if (ent->owner && ent->owner->m_pVehicle)
1632 {
1633 ent->nextthink = level.time + Q_irand( 10000, 15000 );
1634 }
1635 else
1636 {
1637 ent->nextthink = level.time + Q_irand( 5000, 15000 );
1638 }
1639
1640 ent->e_ThinkFunc = thinkF_G_FreeEntity;
1641 //FIXME: these keep drawing for a frame or so after being freed?! See them lerp to origin of world...
1642 }
1643 else
1644 {
1645 EvaluateTrajectory( &ent->s.apos, level.time, ent->currentAngles );
1646 }
1647 return;
1648 }
1649
1650 // get current position
1651 EvaluateTrajectory( &ent->s.pos, level.time, origin );
1652 // get current angles
1653 EvaluateTrajectory( &ent->s.apos, level.time, ent->currentAngles );
1654
1655 // trace a line from the previous position to the current position,
1656 // ignoring interactions with the missile owner
1657 gi.trace( &tr, ent->currentOrigin, ent->mins, ent->maxs, origin,
1658 ent->owner ? ent->owner->s.number : ENTITYNUM_NONE, ent->clipmask, (EG2_Collision)0, 0 );
1659
1660 VectorCopy( tr.endpos, ent->currentOrigin );
1661 if ( tr.startsolid )
1662 {
1663 tr.fraction = 0;
1664 }
1665
1666
1667 gi.linkentity( ent );
1668
1669 if ( tr.fraction != 1 )
1670 {
1671 G_BounceMissile( ent, &tr );
1672 if ( ent->s.pos.trType == TR_STATIONARY )
1673 {//stopped, stop spinning
1674 //lay flat
1675 //pitch
1676 VectorCopy( ent->currentAngles, ent->s.apos.trBase );
1677 vec3_t flatAngles;
1678 if ( ent->s.angles2[0] == -1 )
1679 {//any pitch is okay
1680 flatAngles[0] = ent->currentAngles[0];
1681 }
1682 else
1683 {//lay flat
1684 if ( ent->owner
1685 && ent->owner->client
1686 && ent->owner->client->NPC_class == CLASS_PROTOCOL
1687 && ent->count == BOTH_DISMEMBER_TORSO1 )
1688 {
1689 if ( ent->currentAngles[0] > 0 || ent->currentAngles[0] < -180 )
1690 {
1691 flatAngles[0] = -90;
1692 }
1693 else
1694 {
1695 flatAngles[0] = 90;
1696 }
1697 }
1698 else
1699 {
1700 if ( ent->currentAngles[0] > 90 || ent->currentAngles[0] < -90 )
1701 {
1702 flatAngles[0] = 180;
1703 }
1704 else
1705 {
1706 flatAngles[0] = 0;
1707 }
1708 }
1709 }
1710 //yaw
1711 flatAngles[1] = ent->currentAngles[1];
1712 //roll
1713 if ( ent->s.angles2[2] == -1 )
1714 {//any roll is okay
1715 flatAngles[2] = ent->currentAngles[2];
1716 }
1717 else
1718 {
1719 if ( ent->currentAngles[2] > 90 || ent->currentAngles[2] < -90 )
1720 {
1721 flatAngles[2] = 180;
1722 }
1723 else
1724 {
1725 flatAngles[2] = 0;
1726 }
1727 }
1728 VectorSubtract( flatAngles, ent->s.apos.trBase, ent->s.apos.trDelta );
1729 for ( int i = 0; i < 3; i++ )
1730 {
1731 ent->s.apos.trDelta[i] = AngleNormalize180( ent->s.apos.trDelta[i] );
1732 }
1733 ent->s.apos.trTime = level.time;
1734 ent->s.apos.trDuration = 1000;
1735 ent->s.apos.trType = TR_LINEAR_STOP;
1736 //VectorClear( ent->s.apos.trDelta );
1737 }
1738 }
1739 }
1740
1741 float hitLocHealthPercentage[HL_MAX] =
1742 {
1743 0.0f, //HL_NONE = 0,
1744 0.05f, //HL_FOOT_RT,
1745 0.05f, //HL_FOOT_LT,
1746 0.20f, //HL_LEG_RT,
1747 0.20f, //HL_LEG_LT,
1748 0.30f, //HL_WAIST,
1749 0.15f, //HL_BACK_RT,
1750 0.15f, //HL_BACK_LT,
1751 0.30f, //HL_BACK,
1752 0.15f, //HL_CHEST_RT,
1753 0.15f, //HL_CHEST_LT,
1754 0.30f, //HL_CHEST,
1755 0.05f, //HL_ARM_RT,
1756 0.05f, //HL_ARM_LT,
1757 0.01f, //HL_HAND_RT,
1758 0.01f, //HL_HAND_LT,
1759 0.10f, //HL_HEAD
1760 0.0f, //HL_GENERIC1,
1761 0.0f, //HL_GENERIC2,
1762 0.0f, //HL_GENERIC3,
1763 0.0f, //HL_GENERIC4,
1764 0.0f, //HL_GENERIC5,
1765 0.0f //HL_GENERIC6
1766 };
1767
1768 const char *hitLocName[HL_MAX] =
1769 {
1770 "none", //HL_NONE = 0,
1771 "right foot", //HL_FOOT_RT,
1772 "left foot", //HL_FOOT_LT,
1773 "right leg", //HL_LEG_RT,
1774 "left leg", //HL_LEG_LT,
1775 "waist", //HL_WAIST,
1776 "back right shoulder", //HL_BACK_RT,
1777 "back left shoulder", //HL_BACK_LT,
1778 "back", //HL_BACK,
1779 "front right shouler", //HL_CHEST_RT,
1780 "front left shoulder", //HL_CHEST_LT,
1781 "chest", //HL_CHEST,
1782 "right arm", //HL_ARM_RT,
1783 "left arm", //HL_ARM_LT,
1784 "right hand", //HL_HAND_RT,
1785 "left hand", //HL_HAND_LT,
1786 "head", //HL_HEAD
1787 "generic1", //HL_GENERIC1,
1788 "generic2", //HL_GENERIC2,
1789 "generic3", //HL_GENERIC3,
1790 "generic4", //HL_GENERIC4,
1791 "generic5", //HL_GENERIC5,
1792 "generic6" //HL_GENERIC6
1793 };
1794
G_LimbLost(gentity_t * ent,int hitLoc)1795 qboolean G_LimbLost( gentity_t *ent, int hitLoc )
1796 {
1797 switch ( hitLoc )
1798 {
1799 case HL_FOOT_RT:
1800 if ( ent->locationDamage[HL_FOOT_RT] >= Q3_INFINITE )
1801 {
1802 return qtrue;
1803 }
1804 //NOTE: falls through
1805 case HL_LEG_RT:
1806 //NOTE: feet fall through
1807 if ( ent->locationDamage[HL_LEG_RT] >= Q3_INFINITE )
1808 {
1809 return qtrue;
1810 }
1811 return qfalse;
1812 break;
1813
1814 case HL_FOOT_LT:
1815 if ( ent->locationDamage[HL_FOOT_LT] >= Q3_INFINITE )
1816 {
1817 return qtrue;
1818 }
1819 //NOTE: falls through
1820 case HL_LEG_LT:
1821 //NOTE: feet fall through
1822 if ( ent->locationDamage[HL_LEG_LT] >= Q3_INFINITE )
1823 {
1824 return qtrue;
1825 }
1826 return qfalse;
1827 break;
1828
1829 case HL_HAND_LT:
1830 if ( ent->locationDamage[HL_HAND_LT] >= Q3_INFINITE )
1831 {
1832 return qtrue;
1833 }
1834 //NOTE: falls through
1835 case HL_ARM_LT:
1836 case HL_CHEST_LT:
1837 case HL_BACK_RT:
1838 //NOTE: hand falls through
1839 if ( ent->locationDamage[HL_ARM_LT] >= Q3_INFINITE
1840 || ent->locationDamage[HL_CHEST_LT] >= Q3_INFINITE
1841 || ent->locationDamage[HL_BACK_RT] >= Q3_INFINITE
1842 || ent->locationDamage[HL_WAIST] >= Q3_INFINITE )
1843 {
1844 return qtrue;
1845 }
1846 return qfalse;
1847 break;
1848
1849 case HL_HAND_RT:
1850 if ( ent->locationDamage[HL_HAND_RT] >= Q3_INFINITE )
1851 {
1852 return qtrue;
1853 }
1854 //NOTE: falls through
1855 case HL_ARM_RT:
1856 case HL_CHEST_RT:
1857 case HL_BACK_LT:
1858 //NOTE: hand falls through
1859 if ( ent->locationDamage[HL_ARM_RT] >= Q3_INFINITE
1860 || ent->locationDamage[HL_CHEST_RT] >= Q3_INFINITE
1861 || ent->locationDamage[HL_BACK_LT] >= Q3_INFINITE
1862 || ent->locationDamage[HL_WAIST] >= Q3_INFINITE )
1863 {
1864 return qtrue;
1865 }
1866 return qfalse;
1867 break;
1868
1869 case HL_HEAD:
1870 if ( ent->locationDamage[HL_HEAD] >= Q3_INFINITE )
1871 {
1872 return qtrue;
1873 }
1874 //NOTE: falls through
1875 case HL_WAIST:
1876 //NOTE: head falls through
1877 if ( ent->locationDamage[HL_WAIST] >= Q3_INFINITE )
1878 {
1879 return qtrue;
1880 }
1881 return qfalse;
1882 default:
1883 return (qboolean)(ent->locationDamage[hitLoc]>=Q3_INFINITE);
1884 }
1885 }
1886
1887 extern qboolean G_GetRootSurfNameWithVariant( gentity_t *ent, const char *rootSurfName, char *returnSurfName, int returnSize );
G_RemoveWeaponsWithLimbs(gentity_t * ent,gentity_t * limb,int limbAnim)1888 void G_RemoveWeaponsWithLimbs( gentity_t *ent, gentity_t *limb, int limbAnim )
1889 {
1890 int weaponModelNum = 0, checkAnim;
1891 char handName[MAX_QPATH];
1892
1893 for ( weaponModelNum = 0; weaponModelNum < MAX_INHAND_WEAPONS; weaponModelNum++ )
1894 {
1895 if ( ent->weaponModel[weaponModelNum] >= 0 )
1896 {//have a weapon in this hand
1897 if ( weaponModelNum == 0 && ent->client->ps.saberInFlight )
1898 {//this is the right-hand weapon and it's a saber in-flight (i.e.: not in-hand, so no need to worry about it)
1899 continue;
1900 }
1901 //otherwise, the corpse hasn't dropped their weapon
1902 switch ( weaponModelNum )
1903 {
1904 case 0://right hand
1905 checkAnim = BOTH_DISMEMBER_RARM;
1906 G_GetRootSurfNameWithVariant( ent, "r_hand", handName, sizeof(handName) );
1907 break;
1908 case 1://left hand
1909 checkAnim = BOTH_DISMEMBER_LARM;
1910 G_GetRootSurfNameWithVariant( ent, "l_hand", handName, sizeof(handName) );
1911 break;
1912 default://not handled/valid
1913 continue;
1914 break;
1915 }
1916
1917 if ( limbAnim == checkAnim || limbAnim == BOTH_DISMEMBER_TORSO1 )//either/both hands
1918 {//FIXME: is this first check needed with this lower one?
1919 if ( !gi.G2API_GetSurfaceRenderStatus( &limb->ghoul2[0], handName ) )
1920 {//only copy the weapon over if the hand is actually on this limb...
1921 //copy it to limb
1922 if ( ent->s.weapon != WP_NONE )
1923 {//only if they actually still have a weapon
1924 limb->s.weapon = ent->s.weapon;
1925 limb->weaponModel[weaponModelNum] = ent->weaponModel[weaponModelNum];
1926 }//else - weaponModel is not -1 but don't have a weapon? Oops, somehow G2 model wasn't removed?
1927 //remove it on owner
1928 if ( ent->weaponModel[weaponModelNum] > 0 )
1929 {
1930 gi.G2API_RemoveGhoul2Model( ent->ghoul2, ent->weaponModel[weaponModelNum] );
1931 ent->weaponModel[weaponModelNum] = -1;
1932 }
1933 if ( !ent->client->ps.saberInFlight )
1934 {//saberent isn't flying through the air, it's in-hand and attached to player
1935 if ( ent->client->ps.saberEntityNum != ENTITYNUM_NONE && ent->client->ps.saberEntityNum > 0 )
1936 {//remove the owner ent's saber model and entity
1937 if ( g_entities[ent->client->ps.saberEntityNum].inuse )
1938 {
1939 G_FreeEntity( &g_entities[ent->client->ps.saberEntityNum] );
1940 }
1941 ent->client->ps.saberEntityNum = ENTITYNUM_NONE;
1942 }
1943 }
1944 }
1945 else
1946 {//the hand had already been removed
1947 if ( ent->weaponModel[weaponModelNum] > 0 )
1948 {//still a weapon associated with it, remove it from the limb
1949 gi.G2API_RemoveGhoul2Model( limb->ghoul2, ent->weaponModel[weaponModelNum] );
1950 limb->weaponModel[weaponModelNum] = -1;
1951 }
1952 }
1953 }
1954 else
1955 {//this weapon isn't affected by this dismemberment
1956 if ( ent->weaponModel[weaponModelNum] > 0 )
1957 {//but a weapon was copied over to the limb, so remove it
1958 gi.G2API_RemoveGhoul2Model( limb->ghoul2, ent->weaponModel[weaponModelNum] );
1959 limb->weaponModel[weaponModelNum] = -1;
1960 }
1961 }
1962 }
1963 }
1964 }
1965
G_Dismember(gentity_t * ent,vec3_t point,const char * limbBone,const char * rotateBone,const char * limbName,const char * limbCapName,const char * stubCapName,const char * limbTagName,const char * stubTagName,int limbAnim,float limbRollBase,float limbPitchBase,int damage,int hitLoc)1966 static qboolean G_Dismember( gentity_t *ent, vec3_t point,
1967 const char *limbBone, const char *rotateBone, const char *limbName,
1968 const char *limbCapName, const char *stubCapName, const char *limbTagName, const char *stubTagName,
1969 int limbAnim, float limbRollBase, float limbPitchBase,
1970 int damage, int hitLoc )
1971 {
1972 //int newBolt;
1973 vec3_t dir, newPoint, limbAngles = {0,ent->client->ps.legsYaw,0};
1974 gentity_t *limb;
1975 trace_t trace;
1976
1977 //make sure this limb hasn't been lopped off already!
1978 if ( gi.G2API_GetSurfaceRenderStatus( &ent->ghoul2[ent->playerModel], limbName ) == 0x00000100/*G2SURFACEFLAG_NODESCENDANTS*/ )
1979 {//already lost this limb
1980 //NOTE: we now check for off wth no decendants
1981 //because the torso surface can be off with
1982 //the torso variations on when this is one of
1983 //our "choose your own jedi" models
1984 return qfalse;
1985 }
1986
1987 //NOTE: only reason I have this next part is because G2API_GetSurfaceRenderStatus is *not* working
1988 if ( G_LimbLost( ent, hitLoc ) )
1989 {//already lost this limb
1990 return qfalse;
1991 }
1992
1993 //FIXME: when timescale is high, can sometimes cut off a surf that includes a surf that was already cut off
1994 //0) create a limb ent
1995 VectorCopy( point, newPoint );
1996 newPoint[2] += 6;
1997 limb = G_Spawn();
1998 G_SetOrigin( limb, newPoint );
1999 //VectorCopy(ent->currentAngles,limbAngles);
2000 //G_SetAngles( limb, ent->currentAngles );
2001 VectorCopy( newPoint, limb->s.pos.trBase );
2002 //1) copy the g2 instance of the victim into the limb
2003 gi.G2API_CopyGhoul2Instance( ent->ghoul2, limb->ghoul2, 0 );
2004 limb->playerModel = 0;//assumption!
2005 limb->craniumBone = ent->craniumBone;
2006 limb->cervicalBone = ent->cervicalBone;
2007 limb->thoracicBone = ent->thoracicBone;
2008 limb->upperLumbarBone = ent->upperLumbarBone;
2009 limb->lowerLumbarBone = ent->lowerLumbarBone;
2010 limb->hipsBone = ent->hipsBone;
2011 limb->rootBone = ent->rootBone;
2012 //2) set the root surf on the limb
2013 if ( limbTagName )
2014 {//add smoke to cap tag
2015 /*newBolt = gi.G2API_AddBolt( &limb->ghoul2[limb->playerModel], limbTagName );
2016 if ( newBolt != -1 )
2017 {
2018 G_PlayEffect( G_EffectIndex("saber/limb_bolton"), limb->playerModel, newBolt, limb->s.number, newPoint);
2019 }*/
2020 }
2021 /*
2022 if ( limbBone && hitLoc == HL_HEAD )
2023 {//stop the current anim on the limb?
2024 gi.G2API_StopBoneAnim( &limb->ghoul2[limb->playerModel], "model_root" );
2025 gi.G2API_StopBoneAnim( &limb->ghoul2[limb->playerModel], "motion" );
2026 gi.G2API_StopBoneAnim( &limb->ghoul2[limb->playerModel], "upper_lumbar" );
2027 }
2028 */
2029 gi.G2API_StopBoneAnimIndex( &limb->ghoul2[limb->playerModel], limb->hipsBone );
2030
2031 gi.G2API_SetRootSurface( limb->ghoul2, limb->playerModel, limbName );
2032 /*
2033 if ( limbBone && hitLoc != HL_WAIST )
2034 {//play the dismember anim on the limb?
2035 //FIXME: screws up origin
2036 animation_t *animations = level.knownAnimFileSets[ent->client->clientInfo.animFileIndex].animations;
2037 //play the proper dismember anim on the limb
2038 gi.G2API_SetBoneAnim(&limb->ghoul2[limb->playerModel], 0, animations[limbAnim].firstFrame - 1,
2039 animations[limbAnim].numFrames + animations[limbAnim].firstFrame - 1,
2040 BONE_ANIM_OVERRIDE_FREEZE, 1, cg.time);
2041 }
2042 */
2043 if ( limbBone && hitLoc == HL_WAIST && ent->client->NPC_class == CLASS_PROTOCOL )
2044 {//play the dismember anim on the limb?
2045 gi.G2API_StopBoneAnim( &limb->ghoul2[limb->playerModel], "model_root" );
2046 gi.G2API_StopBoneAnim( &limb->ghoul2[limb->playerModel], "motion" );
2047 gi.G2API_StopBoneAnim( &limb->ghoul2[limb->playerModel], "pelvis" );
2048 gi.G2API_StopBoneAnim( &limb->ghoul2[limb->playerModel], "upper_lumbar" );
2049 //FIXME: screws up origin
2050 animation_t *animations = level.knownAnimFileSets[ent->client->clientInfo.animFileIndex].animations;
2051 //play the proper dismember anim on the limb
2052 gi.G2API_SetBoneAnim(&limb->ghoul2[limb->playerModel], 0, animations[limbAnim].firstFrame,
2053 animations[limbAnim].numFrames + animations[limbAnim].firstFrame,
2054 BONE_ANIM_OVERRIDE_FREEZE, 1, cg.time, -1, -1 );
2055 }
2056 if ( rotateBone )
2057 {
2058 gi.G2API_SetNewOrigin( &limb->ghoul2[0], gi.G2API_AddBolt( &limb->ghoul2[0], rotateBone ) );
2059
2060 //now let's try to position the limb at the *exact* right spot
2061 int newBolt = gi.G2API_AddBolt( &ent->ghoul2[0], rotateBone );
2062 if ( newBolt != -1 )
2063 {
2064 int actualTime = (cg.time?cg.time:level.time);
2065 mdxaBone_t boltMatrix;
2066 vec3_t angles;
2067
2068 VectorSet( angles, 0, ent->currentAngles[YAW], 0 );
2069 gi.G2API_GetBoltMatrix( ent->ghoul2, ent->playerModel, newBolt,
2070 &boltMatrix, angles, ent->currentOrigin,
2071 actualTime, NULL, ent->s.modelScale );
2072 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, limb->s.origin );
2073 G_SetOrigin( limb, limb->s.origin );
2074 VectorCopy( limb->s.origin, limb->s.pos.trBase );
2075 //angles, too
2076 /*
2077 vec3_t limbF, limbR;
2078 newBolt = gi.G2API_AddBolt( &ent->ghoul2[0], limbBone );
2079 if ( newBolt != -1 )
2080 {
2081 gi.G2API_GetBoltMatrix( ent->ghoul2, ent->playerModel, newBolt,
2082 &boltMatrix, angles, ent->currentOrigin,
2083 actualTime, NULL, ent->s.modelScale );
2084 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, POSITIVE_X, limbF );
2085 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, NEGATIVE_Y, limbR );
2086 vectoangles( limbF, limbAngles );
2087 vectoangles( limbR, angles );
2088 limbAngles[YAW] += 180;
2089 limbAngles[ROLL] = angles[PITCH]*-1.0f;
2090 }
2091 */
2092 }
2093 }
2094 if ( limbCapName )
2095 {//turn on caps
2096 gi.G2API_SetSurfaceOnOff( &limb->ghoul2[limb->playerModel], limbCapName, 0 );
2097 }
2098 //3) turn off w/descendants that surf in original model
2099 //NOTE: we actually change the ent's stuff on the cgame side so that there is no 50ms lag
2100 // this is neccessary because the Ghoul2 info does not have to go over the network,
2101 // cgame looks at game's data. The new limb ent will be delayed so we will see
2102 // that a limb is missing before the chopped off limb appears.
2103 // also, if the limb was going to start in solid, we can delete it and return
2104 if ( stubTagName )
2105 {//add smoke to cap surf, spawn effect
2106 //do it later
2107 limb->target = G_NewString( stubTagName );
2108 /*
2109 newBolt = gi.G2API_AddBolt( &ent->ghoul2[ent->playerModel], stubTagName );
2110 if ( newBolt != -1 )
2111 {
2112 G_PlayEffect( "blaster/smoke_bolton", ent->playerModel, newBolt, ent->s.number);
2113 }
2114 */
2115 }
2116 if ( limbName )
2117 {
2118 limb->target2 = G_NewString( limbName );
2119 //gi.G2API_SetSurfaceOnOff( &ent->ghoul2[ent->playerModel], limbName, 0x00000100 );//G2SURFACEFLAG_NODESCENDANTS
2120 }
2121 if ( stubCapName )
2122 {//turn on caps
2123 limb->target3 = G_NewString( stubCapName );
2124 //gi.G2API_SetSurfaceOnOff( &ent->ghoul2[ent->playerModel], stubCapName, 0 );
2125 }
2126 limb->count = limbAnim;
2127 //
2128 limb->s.radius = 60;
2129 //4) toss the limb away
2130 limb->classname = "limb";
2131 limb->owner = ent;
2132 limb->enemy = ent->enemy;
2133
2134 //remove weapons/move them to limb (if applicable in this dismemberment)
2135 G_RemoveWeaponsWithLimbs( ent, limb, limbAnim );
2136
2137 limb->e_clThinkFunc = clThinkF_CG_Limb;
2138 limb->e_ThinkFunc = thinkF_LimbThink;
2139 limb->nextthink = level.time + FRAMETIME;
2140 gi.linkentity( limb );
2141 //need size, contents, clipmask
2142 limb->svFlags = SVF_USE_CURRENT_ORIGIN;
2143 limb->clipmask = MASK_SOLID;
2144 limb->contents = CONTENTS_CORPSE;
2145 VectorSet( limb->mins, -3.0f, -3.0f, -6.0f );
2146 VectorSet( limb->maxs, 3.0f, 3.0f, 6.0f );
2147
2148 //make sure it doesn't start in solid
2149 gi.trace( &trace, limb->s.pos.trBase, limb->mins, limb->maxs, limb->s.pos.trBase, limb->s.number, limb->clipmask, (EG2_Collision)0, 0 );
2150 if ( trace.startsolid )
2151 {
2152 limb->s.pos.trBase[2] -= limb->mins[2];
2153 gi.trace( &trace, limb->s.pos.trBase, limb->mins, limb->maxs, limb->s.pos.trBase, limb->s.number, limb->clipmask, (EG2_Collision)0, 0 );
2154 if ( trace.startsolid )
2155 {
2156 limb->s.pos.trBase[2] += limb->mins[2];
2157 gi.trace( &trace, limb->s.pos.trBase, limb->mins, limb->maxs, limb->s.pos.trBase, limb->s.number, limb->clipmask, (EG2_Collision)0, 0 );
2158 if ( trace.startsolid )
2159 {//stuck? don't remove
2160 G_FreeEntity( limb );
2161 return qfalse;
2162 }
2163 }
2164 }
2165
2166 //move it
2167 VectorCopy( limb->s.pos.trBase, limb->currentOrigin );
2168 gi.linkentity( limb );
2169
2170 limb->s.eType = ET_THINKER;//ET_GENERAL;
2171 limb->physicsBounce = 0.2f;
2172 limb->s.pos.trType = TR_GRAVITY;
2173 limb->s.pos.trTime = level.time; // move a bit on the very first frame
2174 VectorSubtract( point, ent->currentOrigin, dir );
2175 VectorNormalize( dir );
2176 //no trDuration?
2177 //spin it
2178 //new way- try to preserve the exact angle and position of the limb as it was when attached
2179 VectorSet( limb->s.angles2, limbPitchBase, 0, limbRollBase );
2180 VectorCopy( limbAngles, limb->s.apos.trBase );
2181 /*
2182 //old way- just set an angle...
2183 limb->s.apos.trBase[0] += limbPitchBase;
2184 limb->s.apos.trBase[1] = ent->client->ps.viewangles[1];
2185 limb->s.apos.trBase[2] += limbRollBase;
2186 */
2187 limb->s.apos.trTime = level.time;
2188 limb->s.apos.trType = TR_LINEAR;
2189 VectorClear( limb->s.apos.trDelta );
2190
2191 if ( hitLoc == HL_HAND_RT || hitLoc == HL_HAND_LT )
2192 {//hands fly farther
2193 VectorMA( ent->client->ps.velocity, 200, dir, limb->s.pos.trDelta );
2194 //make it bounce some
2195 limb->s.eFlags |= EF_BOUNCE_HALF;
2196 limb->s.apos.trDelta[0] = Q_irand( -300, 300 );
2197 limb->s.apos.trDelta[1] = Q_irand( -800, 800 );
2198 }
2199 else if ( limbAnim == BOTH_DISMEMBER_HEAD1
2200 || limbAnim == BOTH_DISMEMBER_LARM
2201 || limbAnim == BOTH_DISMEMBER_RARM )
2202 {//head and arms don't fly as far
2203 limb->s.eFlags |= EF_BOUNCE_SHRAPNEL;
2204 VectorMA( ent->client->ps.velocity, 150, dir, limb->s.pos.trDelta );
2205 limb->s.apos.trDelta[0] = Q_irand( -200, 200 );
2206 limb->s.apos.trDelta[1] = Q_irand( -400, 400 );
2207 }
2208 else// if ( limbAnim == BOTH_DISMEMBER_TORSO1 || limbAnim == BOTH_DISMEMBER_LLEG || limbAnim == BOTH_DISMEMBER_RLEG )
2209 {//everything else just kinda falls off
2210 limb->s.eFlags |= EF_BOUNCE_SHRAPNEL;
2211 VectorMA( ent->client->ps.velocity, 100, dir, limb->s.pos.trDelta );
2212 limb->s.apos.trDelta[0] = Q_irand( -100, 100 );
2213 limb->s.apos.trDelta[1] = Q_irand( -200, 200 );
2214 }
2215 //roll? No, doesn't work...
2216 //limb->s.apos.trDelta[2] = Q_irand( -300, 300 );//FIXME: this scales it down @ 80% and does weird stuff in timescale != 1.0
2217 //limb->s.apos.trDelta[2] = limbRoll;
2218
2219 //preserve scale so giants don't have tiny limbs
2220 VectorCopy( ent->s.modelScale, limb->s.modelScale );
2221
2222 //mark ent as dismembered
2223 ent->locationDamage[hitLoc] = Q3_INFINITE;//mark this limb as gone
2224 ent->client->dismembered = true;
2225
2226 //copy the custom RGB to the limb (for skin coloring)
2227 limb->startRGBA[0] = ent->client->renderInfo.customRGBA[0];
2228 limb->startRGBA[1] = ent->client->renderInfo.customRGBA[1];
2229 limb->startRGBA[2] = ent->client->renderInfo.customRGBA[2];
2230
2231 return qtrue;
2232 }
2233
G_Dismemberable(gentity_t * self,int hitLoc)2234 static qboolean G_Dismemberable( gentity_t *self, int hitLoc )
2235 {
2236 if ( self->client->dismembered )
2237 {//cannot dismember me right now
2238 return qfalse;
2239 }
2240 if ( !debug_subdivision->integer && g_saberRealisticCombat->integer < 2 )
2241 {
2242 if ( g_dismemberProbabilities->value > 0.0f )
2243 {//use the ent-specific dismemberProbabilities
2244 float dismemberProb = 0;
2245 // check which part of the body it is. Then check the npc's probability
2246 // of that body part coming off, if it doesn't pass, return out.
2247 switch ( hitLoc )
2248 {
2249 case HL_LEG_RT:
2250 case HL_LEG_LT:
2251 dismemberProb = self->client->dismemberProbLegs;
2252 break;
2253 case HL_WAIST:
2254 dismemberProb = self->client->dismemberProbWaist;
2255 break;
2256 case HL_BACK_RT:
2257 case HL_BACK_LT:
2258 case HL_CHEST_RT:
2259 case HL_CHEST_LT:
2260 case HL_ARM_RT:
2261 case HL_ARM_LT:
2262 dismemberProb = self->client->dismemberProbArms;
2263 break;
2264 case HL_HAND_RT:
2265 case HL_HAND_LT:
2266 dismemberProb = self->client->dismemberProbHands;
2267 break;
2268 case HL_HEAD:
2269 dismemberProb = self->client->dismemberProbHead;
2270 break;
2271 default:
2272 return qfalse;
2273 break;
2274 }
2275
2276 //check probability of this happening on this npc
2277 if ( floor((Q_flrand( 1, 100 )*g_dismemberProbabilities->value)) > dismemberProb*2.0f )//probabilities seemed really really low, had to crank them up
2278 {
2279 return qfalse;
2280 }
2281 }
2282 }
2283 return qtrue;
2284 }
2285
G_Dismemberable2(gentity_t * self,int hitLoc)2286 static qboolean G_Dismemberable2( gentity_t *self, int hitLoc )
2287 {
2288 if ( self->client->dismembered )
2289 {//cannot dismember me right now
2290 return qfalse;
2291 }
2292 if ( !debug_subdivision->integer && g_saberRealisticCombat->integer < 2 )
2293 {
2294 if ( g_dismemberProbabilities->value <= 0.0f )
2295 {//add the passed-in damage to the locationDamage array, check to see if it's taken enough damage to actually dismember
2296 if ( self->locationDamage[hitLoc] < (self->client->ps.stats[STAT_MAX_HEALTH]*hitLocHealthPercentage[hitLoc]) )
2297 {//this location has not taken enough damage to dismember
2298 return qfalse;
2299 }
2300 }
2301 }
2302 return qtrue;
2303 }
2304
2305 #define MAX_VARIANTS 8
G_GetRootSurfNameWithVariant(gentity_t * ent,const char * rootSurfName,char * returnSurfName,int returnSize)2306 qboolean G_GetRootSurfNameWithVariant( gentity_t *ent, const char *rootSurfName, char *returnSurfName, int returnSize )
2307 {
2308 if ( !gi.G2API_GetSurfaceRenderStatus( &ent->ghoul2[ent->playerModel], rootSurfName ) )
2309 {//see if the basic name without variants is on
2310 Q_strncpyz( returnSurfName, rootSurfName, returnSize );
2311 return qtrue;
2312 }
2313 else
2314 {//check variants
2315 int i;
2316 for ( i = 0; i < MAX_VARIANTS; i++ )
2317 {
2318 Com_sprintf( returnSurfName, returnSize, "%s%c", rootSurfName, 'a'+i );
2319 if ( !gi.G2API_GetSurfaceRenderStatus( &ent->ghoul2[ent->playerModel], returnSurfName ) )
2320 {
2321 return qtrue;
2322 }
2323 }
2324 }
2325 Q_strncpyz( returnSurfName, rootSurfName, returnSize );
2326 return qfalse;
2327 }
2328
2329 extern qboolean G_StandardHumanoid( gentity_t *self );
G_DoDismemberment(gentity_t * self,vec3_t point,int mod,int damage,int hitLoc,qboolean force=qfalse)2330 qboolean G_DoDismemberment( gentity_t *self, vec3_t point, int mod, int damage, int hitLoc, qboolean force = qfalse )
2331 {
2332 //extern cvar_t *g_iscensored;
2333 // dismemberment -- FIXME: should have a check for how long npc has been dead so people can't
2334 // continue to dismember a dead body long after it's been dead
2335 //NOTE that you can only cut one thing off unless the debug_subdivisions is on
2336 if ( ( g_dismemberment->integer || g_saberRealisticCombat->integer > 1 ) && mod == MOD_SABER )//only lightsaber
2337 {//FIXME: don't do strcmps here
2338 if ( G_StandardHumanoid( self )
2339 && (force||g_dismemberProbabilities->value>0.0f||G_Dismemberable2( self, hitLoc )) )
2340 {//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
2341 //FIXME: check the hitLoc and hitDir against the cap tag for the place
2342 //where the split will be- if the hit dir is roughly perpendicular to
2343 //the direction of the cap, then the split is allowed, otherwise we
2344 //hit it at the wrong angle and should not dismember...
2345 const char *limbBone = NULL, *rotateBone = NULL, *limbTagName = NULL, *stubTagName = NULL;
2346 int anim = -1;
2347 float limbRollBase = 0, limbPitchBase = 0;
2348 qboolean doDismemberment = qfalse;
2349 char limbName[MAX_QPATH];
2350 char stubName[MAX_QPATH];
2351 char limbCapName[MAX_QPATH];
2352 char stubCapName[MAX_QPATH];
2353
2354 switch( hitLoc )//self->hitLoc
2355 {
2356 case HL_LEG_RT:
2357 if ( g_dismemberment->integer > 1 )
2358 {
2359 doDismemberment = qtrue;
2360 limbBone = "rtibia";
2361 rotateBone = "rtalus";
2362 G_GetRootSurfNameWithVariant( self, "r_leg", limbName, sizeof(limbName) );
2363 G_GetRootSurfNameWithVariant( self, "hips", stubName, sizeof(stubName) );
2364 Com_sprintf( limbCapName, sizeof( limbCapName ), "%s_cap_hips", limbName );
2365 Com_sprintf( stubCapName, sizeof( stubCapName), "%s_cap_r_leg", stubName );
2366 limbTagName = "*r_leg_cap_hips";
2367 stubTagName = "*hips_cap_r_leg";
2368 anim = BOTH_DISMEMBER_RLEG;
2369 limbRollBase = 0;
2370 limbPitchBase = 0;
2371 }
2372 break;
2373 case HL_LEG_LT:
2374 if ( g_dismemberment->integer > 1 )
2375 {
2376 doDismemberment = qtrue;
2377 limbBone = "ltibia";
2378 rotateBone = "ltalus";
2379 G_GetRootSurfNameWithVariant( self, "l_leg", limbName, sizeof(limbName) );
2380 G_GetRootSurfNameWithVariant( self, "hips", stubName, sizeof(stubName) );
2381 Com_sprintf( limbCapName, sizeof( limbCapName ), "%s_cap_hips", limbName );
2382 Com_sprintf( stubCapName, sizeof( stubCapName), "%s_cap_l_leg", stubName );
2383 limbTagName = "*l_leg_cap_hips";
2384 stubTagName = "*hips_cap_l_leg";
2385 anim = BOTH_DISMEMBER_LLEG;
2386 limbRollBase = 0;
2387 limbPitchBase = 0;
2388 }
2389 break;
2390 case HL_WAIST:
2391 if ( g_dismemberment->integer > 2 &&
2392 (!self->s.number||!self->message))
2393 {
2394 doDismemberment = qtrue;
2395 limbBone = "pelvis";
2396 rotateBone = "thoracic";
2397 Q_strncpyz( limbName, "torso", sizeof( limbName ) );
2398 Q_strncpyz( limbCapName, "torso_cap_hips", sizeof( limbCapName ) );
2399 Q_strncpyz( stubCapName, "hips_cap_torso", sizeof( stubCapName ) );
2400 limbTagName = "*torso_cap_hips";
2401 stubTagName = "*hips_cap_torso";
2402 anim = BOTH_DISMEMBER_TORSO1;
2403 limbRollBase = 0;
2404 limbPitchBase = 0;
2405 }
2406 break;
2407 case HL_CHEST_RT:
2408 case HL_ARM_RT:
2409 case HL_BACK_RT:
2410 if ( g_dismemberment->integer )
2411 {
2412 doDismemberment = qtrue;
2413 limbBone = "rhumerus";
2414 rotateBone = "rradius";
2415 G_GetRootSurfNameWithVariant( self, "r_arm", limbName, sizeof(limbName) );
2416 G_GetRootSurfNameWithVariant( self, "torso", stubName, sizeof(stubName) );
2417 Com_sprintf( limbCapName, sizeof( limbCapName ), "%s_cap_torso", limbName );
2418 Com_sprintf( stubCapName, sizeof( stubCapName), "%s_cap_r_arm", stubName );
2419 limbTagName = "*r_arm_cap_torso";
2420 stubTagName = "*torso_cap_r_arm";
2421 anim = BOTH_DISMEMBER_RARM;
2422 limbRollBase = 0;
2423 limbPitchBase = 0;
2424 }
2425 break;
2426 case HL_CHEST_LT:
2427 case HL_ARM_LT:
2428 case HL_BACK_LT:
2429 if ( g_dismemberment->integer &&
2430 (!self->s.number||!self->message))
2431 {//either the player or not carrying a key on my arm
2432 doDismemberment = qtrue;
2433 limbBone = "lhumerus";
2434 rotateBone = "lradius";
2435 G_GetRootSurfNameWithVariant( self, "l_arm", limbName, sizeof(limbName) );
2436 G_GetRootSurfNameWithVariant( self, "torso", stubName, sizeof(stubName) );
2437 Com_sprintf( limbCapName, sizeof( limbCapName ), "%s_cap_torso", limbName );
2438 Com_sprintf( stubCapName, sizeof( stubCapName), "%s_cap_l_arm", stubName );
2439 limbTagName = "*l_arm_cap_torso";
2440 stubTagName = "*torso_cap_l_arm";
2441 anim = BOTH_DISMEMBER_LARM;
2442 limbRollBase = 0;
2443 limbPitchBase = 0;
2444 }
2445 break;
2446 case HL_HAND_RT:
2447 if ( g_dismemberment->integer )
2448 {
2449 doDismemberment = qtrue;
2450 limbBone = "rradiusX";
2451 rotateBone = "rhand";
2452 G_GetRootSurfNameWithVariant( self, "r_hand", limbName, sizeof(limbName) );
2453 G_GetRootSurfNameWithVariant( self, "r_arm", stubName, sizeof(stubName) );
2454 Com_sprintf( limbCapName, sizeof( limbCapName ), "%s_cap_r_arm", limbName );
2455 Com_sprintf( stubCapName, sizeof( stubCapName), "%s_cap_r_hand", stubName );
2456 limbTagName = "*r_hand_cap_r_arm";
2457 stubTagName = "*r_arm_cap_r_hand";
2458 anim = BOTH_DISMEMBER_RARM;
2459 limbRollBase = 0;
2460 limbPitchBase = 0;
2461 }
2462 break;
2463 case HL_HAND_LT:
2464 if ( g_dismemberment->integer )
2465 {
2466 doDismemberment = qtrue;
2467 limbBone = "lradiusX";
2468 rotateBone = "lhand";
2469 G_GetRootSurfNameWithVariant( self, "l_hand", limbName, sizeof(limbName) );
2470 G_GetRootSurfNameWithVariant( self, "l_arm", stubName, sizeof(stubName) );
2471 Com_sprintf( limbCapName, sizeof( limbCapName ), "%s_cap_l_arm", limbName );
2472 Com_sprintf( stubCapName, sizeof( stubCapName), "%s_cap_l_hand", stubName );
2473 limbTagName = "*l_hand_cap_l_arm";
2474 stubTagName = "*l_arm_cap_l_hand";
2475 anim = BOTH_DISMEMBER_LARM;
2476 limbRollBase = 0;
2477 limbPitchBase = 0;
2478 }
2479 break;
2480 case HL_HEAD:
2481 if ( g_dismemberment->integer > 2 )
2482 {
2483 doDismemberment = qtrue;
2484 limbBone = "cervical";
2485 rotateBone = "cranium";
2486 Q_strncpyz( limbName, "head", sizeof( limbName ) );
2487 Q_strncpyz( limbCapName, "head_cap_torso", sizeof( limbCapName ) );
2488 Q_strncpyz( stubCapName, "torso_cap_head", sizeof( stubCapName ) );
2489 limbTagName = "*head_cap_torso";
2490 stubTagName = "*torso_cap_head";
2491 anim = BOTH_DISMEMBER_HEAD1;
2492 limbRollBase = -1;
2493 limbPitchBase = -1;
2494 }
2495 break;
2496 case HL_FOOT_RT:
2497 case HL_FOOT_LT:
2498 case HL_CHEST:
2499 case HL_BACK:
2500 default:
2501 break;
2502 }
2503 if ( doDismemberment )
2504 {
2505 return G_Dismember( self, point, limbBone, rotateBone, limbName,
2506 limbCapName, stubCapName, limbTagName, stubTagName,
2507 anim, limbRollBase, limbPitchBase, damage, hitLoc );
2508 }
2509 }
2510 }
2511 return qfalse;
2512 }
2513
G_CheckSpecialDeathAnim(gentity_t * self,vec3_t point,int damage,int mod,int hitLoc)2514 static int G_CheckSpecialDeathAnim( gentity_t *self, vec3_t point, int damage, int mod, int hitLoc )
2515 {
2516 int deathAnim = -1;
2517
2518 if ( self->client->ps.legsAnim == BOTH_GETUP_BROLL_L
2519 || self->client->ps.legsAnim == BOTH_GETUP_BROLL_R )
2520 {//rolling away to the side on our back
2521 deathAnim = BOTH_DEATH_LYING_UP;
2522 }
2523 else if ( self->client->ps.legsAnim == BOTH_GETUP_FROLL_L
2524 || self->client->ps.legsAnim == BOTH_GETUP_FROLL_R )
2525 {//rolling away to the side on our front
2526 deathAnim = BOTH_DEATH_LYING_DN;
2527 }
2528 else if ( self->client->ps.legsAnim == BOTH_GETUP_BROLL_F
2529 && self->client->ps.legsAnimTimer > 350 )
2530 {//kicking up
2531 deathAnim = BOTH_DEATH_FALLING_UP;
2532 }
2533 else if ( self->client->ps.legsAnim == BOTH_GETUP_BROLL_B
2534 && self->client->ps.legsAnimTimer > 950 )
2535 {//on back, rolling back to get up
2536 deathAnim = BOTH_DEATH_LYING_UP;
2537 }
2538 else if ( self->client->ps.legsAnim == BOTH_GETUP_BROLL_B
2539 && self->client->ps.legsAnimTimer <= 950
2540 && self->client->ps.legsAnimTimer > 250 )
2541 {//flipping over backwards
2542 deathAnim = BOTH_FALLDEATH1LAND;
2543 }
2544 else if ( self->client->ps.legsAnim == BOTH_GETUP_FROLL_B
2545 && self->client->ps.legsAnimTimer <= 1100
2546 && self->client->ps.legsAnimTimer > 250 )
2547 {//flipping over backwards
2548 deathAnim = BOTH_FALLDEATH1LAND;
2549 }
2550 else if ( PM_InRoll( &self->client->ps ) )
2551 {
2552 deathAnim = BOTH_DEATH_ROLL; //# Death anim from a roll
2553 }
2554 else if ( PM_FlippingAnim( self->client->ps.legsAnim ) )
2555 {
2556 deathAnim = BOTH_DEATH_FLIP; //# Death anim from a flip
2557 }
2558 else if ( PM_SpinningAnim( self->client->ps.legsAnim ) )
2559 {
2560 float yawDiff = AngleNormalize180(AngleNormalize180(self->client->renderInfo.torsoAngles[YAW]) - AngleNormalize180(self->client->ps.viewangles[YAW]));
2561 if ( yawDiff > 135 || yawDiff < -135 )
2562 {
2563 deathAnim = BOTH_DEATH_SPIN_180; //# Death anim when facing backwards
2564 }
2565 else if ( yawDiff < -60 )
2566 {
2567 deathAnim = BOTH_DEATH_SPIN_90_R; //# Death anim when facing 90 degrees right
2568 }
2569 else if ( yawDiff > 60 )
2570 {
2571 deathAnim = BOTH_DEATH_SPIN_90_L; //# Death anim when facing 90 degrees left
2572 }
2573 }
2574 else if ( PM_InKnockDown( &self->client->ps ) )
2575 {//since these happen a lot, let's handle them case by case
2576 int animLength = PM_AnimLength( self->client->clientInfo.animFileIndex, (animNumber_t)self->client->ps.legsAnim );
2577 if ( self->s.number < MAX_CLIENTS )
2578 {
2579 switch ( self->client->ps.legsAnim )
2580 {
2581 case BOTH_KNOCKDOWN1:
2582 case BOTH_KNOCKDOWN2:
2583 case BOTH_KNOCKDOWN3:
2584 case BOTH_KNOCKDOWN4:
2585 case BOTH_KNOCKDOWN5:
2586 //case BOTH_PLAYER_PA_3_FLY:
2587 animLength += PLAYER_KNOCKDOWN_HOLD_EXTRA_TIME;
2588 break;
2589 }
2590 }
2591 switch ( self->client->ps.legsAnim )
2592 {
2593 case BOTH_KNOCKDOWN1:
2594 if ( animLength - self->client->ps.legsAnimTimer > 100 )
2595 {//on our way down
2596 if ( self->client->ps.legsAnimTimer > 600 )
2597 {//still partially up
2598 deathAnim = BOTH_DEATH_FALLING_UP;
2599 }
2600 else
2601 {//down
2602 deathAnim = BOTH_DEATH_LYING_UP;
2603 }
2604 }
2605 break;
2606 case BOTH_PLAYER_PA_3_FLY:
2607 case BOTH_KNOCKDOWN2:
2608 if ( animLength - self->client->ps.legsAnimTimer > 700 )
2609 {//on our way down
2610 if ( self->client->ps.legsAnimTimer > 600 )
2611 {//still partially up
2612 deathAnim = BOTH_DEATH_FALLING_UP;
2613 }
2614 else
2615 {//down
2616 deathAnim = BOTH_DEATH_LYING_UP;
2617 }
2618 }
2619 break;
2620 case BOTH_KNOCKDOWN3:
2621 if ( animLength - self->client->ps.legsAnimTimer > 100 )
2622 {//on our way down
2623 if ( self->client->ps.legsAnimTimer > 1300 )
2624 {//still partially up
2625 deathAnim = BOTH_DEATH_FALLING_DN;
2626 }
2627 else
2628 {//down
2629 deathAnim = BOTH_DEATH_LYING_DN;
2630 }
2631 }
2632 break;
2633 case BOTH_KNOCKDOWN4:
2634 case BOTH_RELEASED:
2635 if ( animLength - self->client->ps.legsAnimTimer > 300 )
2636 {//on our way down
2637 if ( self->client->ps.legsAnimTimer > 350 )
2638 {//still partially up
2639 deathAnim = BOTH_DEATH_FALLING_UP;
2640 }
2641 else
2642 {//down
2643 deathAnim = BOTH_DEATH_LYING_UP;
2644 }
2645 }
2646 else
2647 {//crouch death
2648 vec3_t fwd;
2649 AngleVectors( self->currentAngles, fwd, NULL, NULL );
2650 float thrown = DotProduct( fwd, self->client->ps.velocity );
2651 if ( thrown < -150 )
2652 {
2653 deathAnim = BOTH_DEATHBACKWARD1; //# Death anim when crouched and thrown back
2654 }
2655 else
2656 {
2657 deathAnim = BOTH_DEATH_CROUCHED; //# Death anim when crouched
2658 }
2659 }
2660 break;
2661 case BOTH_KNOCKDOWN5:
2662 case BOTH_LK_DL_ST_T_SB_1_L:
2663 if ( self->client->ps.legsAnimTimer < 750 )
2664 {//flat
2665 deathAnim = BOTH_DEATH_LYING_DN;
2666 }
2667 break;
2668 case BOTH_GETUP1:
2669 if ( self->client->ps.legsAnimTimer < 350 )
2670 {//standing up
2671 }
2672 else if ( self->client->ps.legsAnimTimer < 800 )
2673 {//crouching
2674 vec3_t fwd;
2675 AngleVectors( self->currentAngles, fwd, NULL, NULL );
2676 float thrown = DotProduct( fwd, self->client->ps.velocity );
2677 if ( thrown < -150 )
2678 {
2679 deathAnim = BOTH_DEATHBACKWARD1; //# Death anim when crouched and thrown back
2680 }
2681 else
2682 {
2683 deathAnim = BOTH_DEATH_CROUCHED; //# Death anim when crouched
2684 }
2685 }
2686 else
2687 {//lying down
2688 if ( animLength - self->client->ps.legsAnimTimer > 450 )
2689 {//partially up
2690 deathAnim = BOTH_DEATH_FALLING_UP;
2691 }
2692 else
2693 {//down
2694 deathAnim = BOTH_DEATH_LYING_UP;
2695 }
2696 }
2697 break;
2698 case BOTH_GETUP2:
2699 if ( self->client->ps.legsAnimTimer < 150 )
2700 {//standing up
2701 }
2702 else if ( self->client->ps.legsAnimTimer < 850 )
2703 {//crouching
2704 vec3_t fwd;
2705 AngleVectors( self->currentAngles, fwd, NULL, NULL );
2706 float thrown = DotProduct( fwd, self->client->ps.velocity );
2707 if ( thrown < -150 )
2708 {
2709 deathAnim = BOTH_DEATHBACKWARD1; //# Death anim when crouched and thrown back
2710 }
2711 else
2712 {
2713 deathAnim = BOTH_DEATH_CROUCHED; //# Death anim when crouched
2714 }
2715 }
2716 else
2717 {//lying down
2718 if ( animLength - self->client->ps.legsAnimTimer > 500 )
2719 {//partially up
2720 deathAnim = BOTH_DEATH_FALLING_UP;
2721 }
2722 else
2723 {//down
2724 deathAnim = BOTH_DEATH_LYING_UP;
2725 }
2726 }
2727 break;
2728 case BOTH_GETUP3:
2729 if ( self->client->ps.legsAnimTimer < 250 )
2730 {//standing up
2731 }
2732 else if ( self->client->ps.legsAnimTimer < 600 )
2733 {//crouching
2734 vec3_t fwd;
2735 AngleVectors( self->currentAngles, fwd, NULL, NULL );
2736 float thrown = DotProduct( fwd, self->client->ps.velocity );
2737 if ( thrown < -150 )
2738 {
2739 deathAnim = BOTH_DEATHBACKWARD1; //# Death anim when crouched and thrown back
2740 }
2741 else
2742 {
2743 deathAnim = BOTH_DEATH_CROUCHED; //# Death anim when crouched
2744 }
2745 }
2746 else
2747 {//lying down
2748 if ( animLength - self->client->ps.legsAnimTimer > 150 )
2749 {//partially up
2750 deathAnim = BOTH_DEATH_FALLING_DN;
2751 }
2752 else
2753 {//down
2754 deathAnim = BOTH_DEATH_LYING_DN;
2755 }
2756 }
2757 break;
2758 case BOTH_GETUP4:
2759 if ( self->client->ps.legsAnimTimer < 250 )
2760 {//standing up
2761 }
2762 else if ( self->client->ps.legsAnimTimer < 600 )
2763 {//crouching
2764 vec3_t fwd;
2765 AngleVectors( self->currentAngles, fwd, NULL, NULL );
2766 float thrown = DotProduct( fwd, self->client->ps.velocity );
2767 if ( thrown < -150 )
2768 {
2769 deathAnim = BOTH_DEATHBACKWARD1; //# Death anim when crouched and thrown back
2770 }
2771 else
2772 {
2773 deathAnim = BOTH_DEATH_CROUCHED; //# Death anim when crouched
2774 }
2775 }
2776 else
2777 {//lying down
2778 if ( animLength - self->client->ps.legsAnimTimer > 850 )
2779 {//partially up
2780 deathAnim = BOTH_DEATH_FALLING_DN;
2781 }
2782 else
2783 {//down
2784 deathAnim = BOTH_DEATH_LYING_UP;
2785 }
2786 }
2787 break;
2788 case BOTH_GETUP5:
2789 if ( self->client->ps.legsAnimTimer > 850 )
2790 {//lying down
2791 if ( animLength - self->client->ps.legsAnimTimer > 1500 )
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 case BOTH_GETUP_CROUCH_B1:
2802 if ( self->client->ps.legsAnimTimer < 800 )
2803 {//crouching
2804 vec3_t fwd;
2805 AngleVectors( self->currentAngles, fwd, NULL, NULL );
2806 float thrown = DotProduct( fwd, self->client->ps.velocity );
2807 if ( thrown < -150 )
2808 {
2809 deathAnim = BOTH_DEATHBACKWARD1; //# Death anim when crouched and thrown back
2810 }
2811 else
2812 {
2813 deathAnim = BOTH_DEATH_CROUCHED; //# Death anim when crouched
2814 }
2815 }
2816 else
2817 {//lying down
2818 if ( animLength - self->client->ps.legsAnimTimer > 400 )
2819 {//partially up
2820 deathAnim = BOTH_DEATH_FALLING_UP;
2821 }
2822 else
2823 {//down
2824 deathAnim = BOTH_DEATH_LYING_UP;
2825 }
2826 }
2827 break;
2828 case BOTH_GETUP_CROUCH_F1:
2829 if ( self->client->ps.legsAnimTimer < 800 )
2830 {//crouching
2831 vec3_t fwd;
2832 AngleVectors( self->currentAngles, fwd, NULL, NULL );
2833 float thrown = DotProduct( fwd, self->client->ps.velocity );
2834 if ( thrown < -150 )
2835 {
2836 deathAnim = BOTH_DEATHBACKWARD1; //# Death anim when crouched and thrown back
2837 }
2838 else
2839 {
2840 deathAnim = BOTH_DEATH_CROUCHED; //# Death anim when crouched
2841 }
2842 }
2843 else
2844 {//lying down
2845 if ( animLength - self->client->ps.legsAnimTimer > 150 )
2846 {//partially up
2847 deathAnim = BOTH_DEATH_FALLING_DN;
2848 }
2849 else
2850 {//down
2851 deathAnim = BOTH_DEATH_LYING_DN;
2852 }
2853 }
2854 break;
2855 case BOTH_FORCE_GETUP_B1:
2856 if ( self->client->ps.legsAnimTimer < 325 )
2857 {//standing up
2858 }
2859 else if ( self->client->ps.legsAnimTimer < 725 )
2860 {//spinning up
2861 deathAnim = BOTH_DEATH_SPIN_180; //# Death anim when facing backwards
2862 }
2863 else if ( self->client->ps.legsAnimTimer < 900 )
2864 {//crouching
2865 vec3_t fwd;
2866 AngleVectors( self->currentAngles, fwd, NULL, NULL );
2867 float thrown = DotProduct( fwd, self->client->ps.velocity );
2868 if ( thrown < -150 )
2869 {
2870 deathAnim = BOTH_DEATHBACKWARD1; //# Death anim when crouched and thrown back
2871 }
2872 else
2873 {
2874 deathAnim = BOTH_DEATH_CROUCHED; //# Death anim when crouched
2875 }
2876 }
2877 else
2878 {//lying down
2879 if ( animLength - self->client->ps.legsAnimTimer > 50 )
2880 {//partially up
2881 deathAnim = BOTH_DEATH_FALLING_UP;
2882 }
2883 else
2884 {//down
2885 deathAnim = BOTH_DEATH_LYING_UP;
2886 }
2887 }
2888 break;
2889 case BOTH_FORCE_GETUP_B2:
2890 if ( self->client->ps.legsAnimTimer < 575 )
2891 {//standing up
2892 }
2893 else if ( self->client->ps.legsAnimTimer < 875 )
2894 {//spinning up
2895 deathAnim = BOTH_DEATH_SPIN_180; //# Death anim when facing backwards
2896 }
2897 else if ( self->client->ps.legsAnimTimer < 900 )
2898 {//crouching
2899 vec3_t fwd;
2900 AngleVectors( self->currentAngles, fwd, NULL, NULL );
2901 float thrown = DotProduct( fwd, self->client->ps.velocity );
2902 if ( thrown < -150 )
2903 {
2904 deathAnim = BOTH_DEATHBACKWARD1; //# Death anim when crouched and thrown back
2905 }
2906 else
2907 {
2908 deathAnim = BOTH_DEATH_CROUCHED; //# Death anim when crouched
2909 }
2910 }
2911 else
2912 {//lying down
2913 //partially up
2914 deathAnim = BOTH_DEATH_FALLING_UP;
2915 }
2916 break;
2917 case BOTH_FORCE_GETUP_B3:
2918 if ( self->client->ps.legsAnimTimer < 150 )
2919 {//standing up
2920 }
2921 else if ( self->client->ps.legsAnimTimer < 775 )
2922 {//flipping
2923 deathAnim = BOTH_DEATHBACKWARD2; //backflip
2924 }
2925 else
2926 {//lying down
2927 //partially up
2928 deathAnim = BOTH_DEATH_FALLING_UP;
2929 }
2930 break;
2931 case BOTH_FORCE_GETUP_B4:
2932 if ( self->client->ps.legsAnimTimer < 325 )
2933 {//standing up
2934 }
2935 else
2936 {//lying down
2937 if ( animLength - self->client->ps.legsAnimTimer > 150 )
2938 {//partially up
2939 deathAnim = BOTH_DEATH_FALLING_UP;
2940 }
2941 else
2942 {//down
2943 deathAnim = BOTH_DEATH_LYING_UP;
2944 }
2945 }
2946 break;
2947 case BOTH_FORCE_GETUP_B5:
2948 if ( self->client->ps.legsAnimTimer < 550 )
2949 {//standing up
2950 }
2951 else if ( self->client->ps.legsAnimTimer < 1025 )
2952 {//kicking up
2953 deathAnim = BOTH_DEATHBACKWARD2; //backflip
2954 }
2955 else
2956 {//lying down
2957 if ( animLength - self->client->ps.legsAnimTimer > 50 )
2958 {//partially up
2959 deathAnim = BOTH_DEATH_FALLING_UP;
2960 }
2961 else
2962 {//down
2963 deathAnim = BOTH_DEATH_LYING_UP;
2964 }
2965 }
2966 break;
2967 case BOTH_FORCE_GETUP_B6:
2968 if ( self->client->ps.legsAnimTimer < 225 )
2969 {//standing up
2970 }
2971 else if ( self->client->ps.legsAnimTimer < 425 )
2972 {//crouching up
2973 vec3_t fwd;
2974 AngleVectors( self->currentAngles, fwd, NULL, NULL );
2975 float thrown = DotProduct( fwd, self->client->ps.velocity );
2976 if ( thrown < -150 )
2977 {
2978 deathAnim = BOTH_DEATHBACKWARD1; //# Death anim when crouched and thrown back
2979 }
2980 else
2981 {
2982 deathAnim = BOTH_DEATH_CROUCHED; //# Death anim when crouched
2983 }
2984 }
2985 else if ( self->client->ps.legsAnimTimer < 825 )
2986 {//flipping up
2987 deathAnim = BOTH_DEATHFORWARD3; //backflip
2988 }
2989 else
2990 {//lying down
2991 if ( animLength - self->client->ps.legsAnimTimer > 225 )
2992 {//partially up
2993 deathAnim = BOTH_DEATH_FALLING_UP;
2994 }
2995 else
2996 {//down
2997 deathAnim = BOTH_DEATH_LYING_UP;
2998 }
2999 }
3000 break;
3001 case BOTH_FORCE_GETUP_F1:
3002 if ( self->client->ps.legsAnimTimer < 275 )
3003 {//standing up
3004 }
3005 else if ( self->client->ps.legsAnimTimer < 750 )
3006 {//flipping
3007 deathAnim = BOTH_DEATH14;
3008 }
3009 else
3010 {//lying down
3011 if ( animLength - self->client->ps.legsAnimTimer > 100 )
3012 {//partially up
3013 deathAnim = BOTH_DEATH_FALLING_DN;
3014 }
3015 else
3016 {//down
3017 deathAnim = BOTH_DEATH_LYING_DN;
3018 }
3019 }
3020 break;
3021 case BOTH_FORCE_GETUP_F2:
3022 if ( self->client->ps.legsAnimTimer < 1200 )
3023 {//standing
3024 }
3025 else
3026 {//lying down
3027 if ( animLength - self->client->ps.legsAnimTimer > 225 )
3028 {//partially up
3029 deathAnim = BOTH_DEATH_FALLING_DN;
3030 }
3031 else
3032 {//down
3033 deathAnim = BOTH_DEATH_LYING_DN;
3034 }
3035 }
3036 break;
3037 }
3038 }
3039 else if ( PM_InOnGroundAnim( &self->client->ps ) )
3040 {
3041 if ( AngleNormalize180(self->client->renderInfo.torsoAngles[PITCH]) < 0 )
3042 {
3043 deathAnim = BOTH_DEATH_LYING_UP; //# Death anim when lying on back
3044 }
3045 else
3046 {
3047 deathAnim = BOTH_DEATH_LYING_DN; //# Death anim when lying on front
3048 }
3049 }
3050 else if ( PM_CrouchAnim( self->client->ps.legsAnim ) )
3051 {
3052 vec3_t fwd;
3053 AngleVectors( self->currentAngles, fwd, NULL, NULL );
3054 float thrown = DotProduct( fwd, self->client->ps.velocity );
3055 if ( thrown < -200 )
3056 {
3057 deathAnim = BOTH_DEATHBACKWARD1; //# Death anim when crouched and thrown back
3058 if ( self->client->ps.velocity[2] > 0 && self->client->ps.velocity[2] < 100 )
3059 {
3060 self->client->ps.velocity[2] = 100;
3061 }
3062 }
3063 else
3064 {
3065 deathAnim = BOTH_DEATH_CROUCHED; //# Death anim when crouched
3066 }
3067 }
3068
3069 return deathAnim;
3070 }
3071 extern qboolean PM_FinishedCurrentLegsAnim( gentity_t *self );
G_PickDeathAnim(gentity_t * self,vec3_t point,int damage,int mod,int hitLoc)3072 static int G_PickDeathAnim( gentity_t *self, vec3_t point, int damage, int mod, int hitLoc )
3073 {//FIXME: play dead flop anims on body if in an appropriate _DEAD anim when this func is called
3074 int deathAnim = -1;
3075 if ( hitLoc == HL_NONE )
3076 {
3077 hitLoc = G_GetHitLocation( self, point );//self->hitLoc
3078 }
3079 //dead flops...if you are already playing a death animation, I guess it can just return directly
3080 switch( self->client->ps.legsAnim )
3081 {
3082 case BOTH_DEATH1: //# First Death anim
3083 case BOTH_DEAD1:
3084 case BOTH_DEATH2: //# Second Death anim
3085 case BOTH_DEAD2:
3086 case BOTH_DEATH8: //#
3087 case BOTH_DEAD8:
3088 case BOTH_DEATH13: //#
3089 case BOTH_DEAD13:
3090 case BOTH_DEATH14: //#
3091 case BOTH_DEAD14:
3092 case BOTH_DEATH16: //#
3093 case BOTH_DEAD16:
3094 case BOTH_DEADBACKWARD1: //# First thrown backward death finished pose
3095 case BOTH_DEADBACKWARD2: //# Second thrown backward death finished pose
3096 //return -2;
3097 //break;
3098 if ( PM_FinishedCurrentLegsAnim( self ) )
3099 {//done with the anim
3100 deathAnim = BOTH_DEADFLOP2;
3101 }
3102 else
3103 {
3104 deathAnim = -2;
3105 }
3106 return deathAnim;
3107 break;
3108 case BOTH_DEADFLOP2:
3109 //return -2;
3110 return BOTH_DEADFLOP2;
3111 break;
3112 case BOTH_DEATH10: //#
3113 case BOTH_DEAD10:
3114 case BOTH_DEATH15: //#
3115 case BOTH_DEAD15:
3116 case BOTH_DEADFORWARD1: //# First thrown forward death finished pose
3117 case BOTH_DEADFORWARD2: //# Second thrown forward death finished pose
3118 //return -2;
3119 //break;
3120 if ( PM_FinishedCurrentLegsAnim( self ) )
3121 {//done with the anim
3122 deathAnim = BOTH_DEADFLOP1;
3123 }
3124 else
3125 {
3126 deathAnim = -2;
3127 }
3128 return deathAnim;
3129 break;
3130 case BOTH_DEADFLOP1:
3131 //return -2;
3132 return BOTH_DEADFLOP1;
3133 break;
3134 case BOTH_DEAD3: //# Third Death finished pose
3135 case BOTH_DEAD4: //# Fourth Death finished pose
3136 case BOTH_DEAD5: //# Fifth Death finished pose
3137 case BOTH_DEAD6: //# Sixth Death finished pose
3138 case BOTH_DEAD7: //# Seventh Death finished pose
3139 case BOTH_DEAD9: //#
3140 case BOTH_DEAD11: //#
3141 case BOTH_DEAD12: //#
3142 case BOTH_DEAD17: //#
3143 case BOTH_DEAD18: //#
3144 case BOTH_DEAD19: //#
3145 case BOTH_DEAD20: //#
3146 case BOTH_DEAD21: //#
3147 case BOTH_DEAD22: //#
3148 case BOTH_DEAD23: //#
3149 case BOTH_DEAD24: //#
3150 case BOTH_DEAD25: //#
3151 case BOTH_LYINGDEAD1: //# Killed lying down death finished pose
3152 case BOTH_STUMBLEDEAD1: //# Stumble forward death finished pose
3153 case BOTH_FALLDEAD1LAND: //# Fall forward and splat death finished pose
3154 case BOTH_DEATH3: //# Third Death anim
3155 case BOTH_DEATH4: //# Fourth Death anim
3156 case BOTH_DEATH5: //# Fifth Death anim
3157 case BOTH_DEATH6: //# Sixth Death anim
3158 case BOTH_DEATH7: //# Seventh Death anim
3159 case BOTH_DEATH9: //#
3160 case BOTH_DEATH11: //#
3161 case BOTH_DEATH12: //#
3162 case BOTH_DEATH17: //#
3163 case BOTH_DEATH18: //#
3164 case BOTH_DEATH19: //#
3165 case BOTH_DEATH20: //#
3166 case BOTH_DEATH21: //#
3167 case BOTH_DEATH22: //#
3168 case BOTH_DEATH23: //#
3169 case BOTH_DEATH24: //#
3170 case BOTH_DEATH25: //#
3171 case BOTH_DEATHFORWARD1: //# First Death in which they get thrown forward
3172 case BOTH_DEATHFORWARD2: //# Second Death in which they get thrown forward
3173 case BOTH_DEATHFORWARD3: //# Second Death in which they get thrown forward
3174 case BOTH_DEATHBACKWARD1: //# First Death in which they get thrown backward
3175 case BOTH_DEATHBACKWARD2: //# Second Death in which they get thrown backward
3176 case BOTH_DEATH1IDLE: //# Idle while close to death
3177 case BOTH_LYINGDEATH1: //# Death to play when killed lying down
3178 case BOTH_STUMBLEDEATH1: //# Stumble forward and fall face first death
3179 case BOTH_FALLDEATH1: //# Fall forward off a high cliff and splat death - start
3180 case BOTH_FALLDEATH1INAIR: //# Fall forward off a high cliff and splat death - loop
3181 case BOTH_FALLDEATH1LAND: //# Fall forward off a high cliff and splat death - hit bottom
3182 return -2;
3183 break;
3184 case BOTH_DEATH_ROLL: //# Death anim from a roll
3185 case BOTH_DEATH_FLIP: //# Death anim from a flip
3186 case BOTH_DEATH_SPIN_90_R: //# Death anim when facing 90 degrees right
3187 case BOTH_DEATH_SPIN_90_L: //# Death anim when facing 90 degrees left
3188 case BOTH_DEATH_SPIN_180: //# Death anim when facing backwards
3189 case BOTH_DEATH_LYING_UP: //# Death anim when lying on back
3190 case BOTH_DEATH_LYING_DN: //# Death anim when lying on front
3191 case BOTH_DEATH_FALLING_DN: //# Death anim when falling on face
3192 case BOTH_DEATH_FALLING_UP: //# Death anim when falling on back
3193 case BOTH_DEATH_CROUCHED: //# Death anim when crouched
3194 case BOTH_RIGHTHANDCHOPPEDOFF:
3195 return -2;
3196 break;
3197 }
3198 // Not currently playing a death animation, so try and get an appropriate one now.
3199 if ( deathAnim == -1 )
3200 {
3201 deathAnim = G_CheckSpecialDeathAnim( self, point, damage, mod, hitLoc );
3202
3203 if ( deathAnim == -1 )
3204 {//base on hitLoc
3205 vec3_t fwd;
3206 AngleVectors( self->currentAngles, fwd, NULL, NULL );
3207 float thrown = DotProduct( fwd, self->client->ps.velocity );
3208 //death anims
3209 switch( hitLoc )
3210 {
3211 case HL_FOOT_RT:
3212 if ( !Q_irand( 0, 2 ) && thrown < 250 )
3213 {
3214 deathAnim = BOTH_DEATH24;//right foot trips up, spin
3215 }
3216 else if ( !Q_irand( 0, 1 ) )
3217 {
3218 if ( !Q_irand( 0, 1 ) )
3219 {
3220 deathAnim = BOTH_DEATH4;//back: forward
3221 }
3222 else
3223 {
3224 deathAnim = BOTH_DEATH16;//same as 1
3225 }
3226 }
3227 else
3228 {
3229 deathAnim = BOTH_DEATH5;//same as 4
3230 }
3231 break;
3232 case HL_FOOT_LT:
3233 if ( !Q_irand( 0, 2 ) && thrown < 250 )
3234 {
3235 deathAnim = BOTH_DEATH25;//left foot trips up, spin
3236 }
3237 else if ( !Q_irand( 0, 1 ) )
3238 {
3239 if ( !Q_irand( 0, 1 ) )
3240 {
3241 deathAnim = BOTH_DEATH4;//back: forward
3242 }
3243 else
3244 {
3245 deathAnim = BOTH_DEATH16;//same as 1
3246 }
3247 }
3248 else
3249 {
3250 deathAnim = BOTH_DEATH5;//same as 4
3251 }
3252 break;
3253 case HL_LEG_RT:
3254 if ( !Q_irand( 0, 2 ) && thrown < 250 )
3255 {
3256 deathAnim = BOTH_DEATH3;//right leg collapse
3257 }
3258 else if ( !Q_irand( 0, 1 ) )
3259 {
3260 deathAnim = BOTH_DEATH5;//same as 4
3261 }
3262 else
3263 {
3264 if ( !Q_irand( 0, 1 ) )
3265 {
3266 deathAnim = BOTH_DEATH4;//back: forward
3267 }
3268 else
3269 {
3270 deathAnim = BOTH_DEATH16;//same as 1
3271 }
3272 }
3273 break;
3274 case HL_LEG_LT:
3275 if ( !Q_irand( 0, 2 ) && thrown < 250 )
3276 {
3277 deathAnim = BOTH_DEATH7;//left leg collapse
3278 }
3279 else if ( !Q_irand( 0, 1 ) )
3280 {
3281 deathAnim = BOTH_DEATH5;//same as 4
3282 }
3283 else
3284 {
3285 if ( !Q_irand( 0, 1 ) )
3286 {
3287 deathAnim = BOTH_DEATH4;//back: forward
3288 }
3289 else
3290 {
3291 deathAnim = BOTH_DEATH16;//same as 1
3292 }
3293 }
3294 break;
3295 case HL_BACK:
3296 if ( fabs(thrown) < 50 || (fabs(thrown) < 200&&!Q_irand(0,3)) )
3297 {
3298 if ( Q_irand( 0, 1 ) )
3299 {
3300 deathAnim = BOTH_DEATH17;//head/back: croak
3301 }
3302 else
3303 {
3304 deathAnim = BOTH_DEATH10;//back: bend back, fall forward
3305 }
3306 }
3307 else
3308 {
3309 if ( !Q_irand( 0, 2 ) )
3310 {
3311 deathAnim = BOTH_DEATH4;//back: forward
3312 }
3313 else if ( !Q_irand( 0, 1 ) )
3314 {
3315 deathAnim = BOTH_DEATH5;//back: forward
3316 }
3317 else
3318 {
3319 deathAnim = BOTH_DEATH16;//same as 1
3320 }
3321 }
3322 break;
3323 case HL_HAND_RT:
3324 case HL_CHEST_RT:
3325 case HL_ARM_RT:
3326 case HL_BACK_LT:
3327 if ( (damage <= self->max_health*0.25&&Q_irand(0,1)) || (fabs(thrown)<200&&!Q_irand(0,2)) || !Q_irand( 0, 10 ) )
3328 {
3329 if ( Q_irand( 0, 1 ) )
3330 {
3331 deathAnim = BOTH_DEATH9;//chest right: snap, fall forward
3332 }
3333 else
3334 {
3335 deathAnim = BOTH_DEATH20;//chest right: snap, fall forward
3336 }
3337 }
3338 else if ( (damage <= self->max_health*0.5&&Q_irand(0,1)) || !Q_irand( 0, 10 ) )
3339 {
3340 deathAnim = BOTH_DEATH3;//chest right: back
3341 }
3342 else if ( (damage <= self->max_health*0.75&&Q_irand(0,1)) || !Q_irand( 0, 10 ) )
3343 {
3344 deathAnim = BOTH_DEATH6;//chest right: spin
3345 }
3346 else
3347 {
3348 //TEMP HACK: play spinny deaths less often
3349 if ( Q_irand( 0, 1 ) )
3350 {
3351 deathAnim = BOTH_DEATH8;//chest right: spin high
3352 }
3353 else
3354 {
3355 switch ( Q_irand( 0, 3 ) )
3356 {
3357 default:
3358 case 0:
3359 deathAnim = BOTH_DEATH9;//chest right: snap, fall forward
3360 break;
3361 case 1:
3362 deathAnim = BOTH_DEATH3;//chest right: back
3363 break;
3364 case 2:
3365 deathAnim = BOTH_DEATH6;//chest right: spin
3366 break;
3367 case 3:
3368 deathAnim = BOTH_DEATH20;//chest right: spin
3369 break;
3370 }
3371 }
3372 }
3373 break;
3374 case HL_CHEST_LT:
3375 case HL_ARM_LT:
3376 case HL_HAND_LT:
3377 case HL_BACK_RT:
3378 if ( (damage <= self->max_health*0.25&&Q_irand(0,1)) || (fabs(thrown)<200&&!Q_irand(0,2)) || !Q_irand(0, 10) )
3379 {
3380 if ( Q_irand( 0, 1 ) )
3381 {
3382 deathAnim = BOTH_DEATH11;//chest left: snap, fall forward
3383 }
3384 else
3385 {
3386 deathAnim = BOTH_DEATH21;//chest left: snap, fall forward
3387 }
3388 }
3389 else if ( (damage <= self->max_health*0.5&&Q_irand(0,1)) || !Q_irand(0, 10) )
3390 {
3391 deathAnim = BOTH_DEATH7;//chest left: back
3392 }
3393 else if ( (damage <= self->max_health*0.75&&Q_irand(0,1)) || !Q_irand(0, 10) )
3394 {
3395 deathAnim = BOTH_DEATH12;//chest left: spin
3396 }
3397 else
3398 {
3399 //TEMP HACK: play spinny deaths less often
3400 if ( Q_irand( 0, 1 ) )
3401 {
3402 deathAnim = BOTH_DEATH14;//chest left: spin high
3403 }
3404 else
3405 {
3406 switch ( Q_irand( 0, 3 ) )
3407 {
3408 default:
3409 case 0:
3410 deathAnim = BOTH_DEATH11;//chest left: snap, fall forward
3411 break;
3412 case 1:
3413 deathAnim = BOTH_DEATH7;//chest left: back
3414 break;
3415 case 2:
3416 deathAnim = BOTH_DEATH12;//chest left: spin
3417 break;
3418 case 3:
3419 deathAnim = BOTH_DEATH21;//chest left: spin
3420 break;
3421 }
3422 }
3423 }
3424 break;
3425 case HL_CHEST:
3426 case HL_WAIST:
3427 if ( (damage <= self->max_health*0.25&&Q_irand(0,1)) || thrown > -50 )
3428 {
3429 if ( !Q_irand( 0, 1 ) )
3430 {
3431 deathAnim = BOTH_DEATH18;//gut: fall right
3432 }
3433 else
3434 {
3435 deathAnim = BOTH_DEATH19;//gut: fall left
3436 }
3437 }
3438 else if ( (damage <= self->max_health*0.5&&!Q_irand(0,1)) || (fabs(thrown)<200&&!Q_irand(0,3)) )
3439 {
3440 if ( Q_irand( 0, 2 ) )
3441 {
3442 deathAnim = BOTH_DEATH2;//chest: backward short
3443 }
3444 else if ( Q_irand( 0, 1 ) )
3445 {
3446 deathAnim = BOTH_DEATH22;//chest: backward short
3447 }
3448 else
3449 {
3450 deathAnim = BOTH_DEATH23;//chest: backward short
3451 }
3452 }
3453 else if ( thrown < -300 && Q_irand( 0, 1 ) )
3454 {
3455 if ( Q_irand( 0, 1 ) )
3456 {
3457 deathAnim = BOTH_DEATHBACKWARD1;//chest: fly back
3458 }
3459 else
3460 {
3461 deathAnim = BOTH_DEATHBACKWARD2;//chest: flip back
3462 }
3463 }
3464 else if ( thrown < -200 && Q_irand( 0, 1 ) )
3465 {
3466 deathAnim = BOTH_DEATH15;//chest: roll backward
3467 }
3468 else
3469 {
3470 deathAnim = BOTH_DEATH1;//chest: backward med
3471 }
3472 break;
3473 case HL_HEAD:
3474 if ( damage <= self->max_health*0.5 && Q_irand(0,2) )
3475 {
3476 deathAnim = BOTH_DEATH17;//head/back: croak
3477 }
3478 else
3479 {
3480 if ( Q_irand( 0, 2 ) )
3481 {
3482 deathAnim = BOTH_DEATH13;//head: stumble, fall back
3483 }
3484 else
3485 {
3486 deathAnim = BOTH_DEATH10;//head: stumble, fall back
3487 }
3488 }
3489 break;
3490 default:
3491 break;
3492 }
3493 }
3494 }
3495
3496 // Validate.....
3497 if ( deathAnim == -1 || !PM_HasAnimation( self, deathAnim ))
3498 {
3499 if ( deathAnim == BOTH_DEADFLOP1
3500 || deathAnim == BOTH_DEADFLOP2 )
3501 {//if don't have deadflop, don't do anything
3502 deathAnim = -1;
3503 }
3504 else
3505 {
3506 // I guess we'll take what we can get.....
3507 deathAnim = PM_PickAnim( self, BOTH_DEATH1, BOTH_DEATH25 );
3508 }
3509 }
3510
3511 return deathAnim;
3512 }
3513
G_CheckLedgeDive(gentity_t * self,float checkDist,const vec3_t checkVel,qboolean tryOpposite,qboolean tryPerp)3514 int G_CheckLedgeDive( gentity_t *self, float checkDist, const vec3_t checkVel, qboolean tryOpposite, qboolean tryPerp )
3515 {
3516 // Intelligent Ledge-Diving Deaths:
3517 // If I'm an NPC, check for nearby ledges and fall off it if possible
3518 // How should/would/could this interact with knockback if we already have some?
3519 // Ideally - apply knockback if there are no ledges or a ledge in that dir
3520 // But if there is a ledge and it's not in the dir of my knockback, fall off the ledge instead
3521 if ( !self || !self->client )
3522 {
3523 return 0;
3524 }
3525
3526 vec3_t fallForwardDir, fallRightDir;
3527 vec3_t angles = {0};
3528 int cliff_fall = 0;
3529
3530 if ( checkVel && !VectorCompare( checkVel, vec3_origin ) )
3531 {//already moving in a dir
3532 angles[1] = vectoyaw( self->client->ps.velocity );
3533 AngleVectors( angles, fallForwardDir, fallRightDir, NULL );
3534 }
3535 else
3536 {//try forward first
3537 angles[1] = self->client->ps.viewangles[1];
3538 AngleVectors( angles, fallForwardDir, fallRightDir, NULL );
3539 }
3540 VectorNormalize( fallForwardDir );
3541 float fallDist = G_CheckForLedge( self, fallForwardDir, checkDist );
3542 if ( fallDist >= 128 )
3543 {
3544 VectorClear( self->client->ps.velocity );
3545 G_Throw( self, fallForwardDir, 85 );
3546 self->client->ps.velocity[2] = 100;
3547 self->client->ps.groundEntityNum = ENTITYNUM_NONE;
3548 }
3549 else if ( tryOpposite )
3550 {
3551 VectorScale( fallForwardDir, -1, fallForwardDir );
3552 fallDist = G_CheckForLedge( self, fallForwardDir, checkDist );
3553 if ( fallDist >= 128 )
3554 {
3555 VectorClear( self->client->ps.velocity );
3556 G_Throw( self, fallForwardDir, 85 );
3557 self->client->ps.velocity[2] = 100;
3558 self->client->ps.groundEntityNum = ENTITYNUM_NONE;
3559 }
3560 }
3561 if ( !cliff_fall && tryPerp )
3562 {//try sides
3563 VectorNormalize( fallRightDir );
3564 fallDist = G_CheckForLedge( self, fallRightDir, checkDist );
3565 if ( fallDist >= 128 )
3566 {
3567 VectorClear( self->client->ps.velocity );
3568 G_Throw( self, fallRightDir, 85 );
3569 self->client->ps.velocity[2] = 100;
3570 }
3571 else
3572 {
3573 VectorScale( fallRightDir, -1, fallRightDir );
3574 fallDist = G_CheckForLedge( self, fallRightDir, checkDist );
3575 if ( fallDist >= 128 )
3576 {
3577 VectorClear( self->client->ps.velocity );
3578 G_Throw( self, fallRightDir, 85 );
3579 self->client->ps.velocity[2] = 100;
3580 }
3581 }
3582 }
3583 if ( fallDist >= 256 )
3584 {
3585 cliff_fall = 2;
3586 }
3587 else if ( fallDist >= 128 )
3588 {
3589 cliff_fall = 1;
3590 }
3591 return cliff_fall;
3592 }
3593
3594 /*
3595 ==================
3596 player_die
3597 ==================
3598 */
3599 void NPC_SetAnim(gentity_t *ent,int setAnimParts,int anim,int setAnimFlags, int iBlend);
3600 extern void AI_DeleteSelfFromGroup( gentity_t *self );
3601 extern void AI_GroupMemberKilled( gentity_t *self );
3602 extern qboolean FlyingCreature( gentity_t *ent );
3603 extern void G_DrivableATSTDie( gentity_t *self );
3604 extern void JET_FlyStop( gentity_t *self );
3605 extern void VehicleExplosionDelay( gentity_t *self );
3606 extern void NPC_LeaveTroop(gentity_t* actor);
3607 extern void Rancor_DropVictim( gentity_t *self );
3608 extern void Wampa_DropVictim( gentity_t *self );
3609 extern void WP_StopForceHealEffects( gentity_t *self );
player_die(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,int damage,int meansOfDeath,int dflags,int hitLoc)3610 void player_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath, int dflags, int hitLoc )
3611 {
3612 int anim;
3613 int contents;
3614 qboolean deathScript = qfalse;
3615 qboolean lastInGroup = qfalse;
3616 qboolean specialAnim = qfalse;
3617 qboolean holdingSaber = qfalse;
3618 int cliff_fall = 0;
3619
3620 //FIXME: somehow people are sometimes not completely dying???
3621 if ( self->client->ps.pm_type == PM_DEAD && (meansOfDeath != MOD_SNIPER || (self->flags & FL_DISINTEGRATED)) )
3622 {//do dismemberment/twitching
3623 if ( self->client->NPC_class == CLASS_MARK1 )
3624 {
3625 DeathFX(self);
3626 self->takedamage = qfalse;
3627 self->client->ps.eFlags |= EF_NODRAW;
3628 self->contents = 0;
3629 // G_FreeEntity( self ); // Is this safe? I can't see why we'd mark it nodraw and then just leave it around??
3630 self->e_ThinkFunc = thinkF_G_FreeEntity;
3631 self->nextthink = level.time + FRAMETIME;
3632 }
3633 else
3634 {
3635 anim = G_PickDeathAnim( self, self->pos1, damage, meansOfDeath, hitLoc );
3636 if ( dflags & DAMAGE_DISMEMBER )
3637 {
3638 G_DoDismemberment( self, self->pos1, meansOfDeath, damage, hitLoc );
3639 }
3640 if ( anim >= 0 )
3641 {
3642 NPC_SetAnim(self, SETANIM_BOTH, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_RESTART|SETANIM_FLAG_HOLD);
3643 }
3644 }
3645 return;
3646 }
3647
3648 // If the entity is in a vehicle.
3649 if ( self->client && self->client->NPC_class != CLASS_VEHICLE && self->s.m_iVehicleNum != 0 )
3650 {
3651 Vehicle_t *pVeh = g_entities[self->s.m_iVehicleNum].m_pVehicle;
3652 if (pVeh)
3653 {
3654 if ( pVeh->m_pOldPilot != self
3655 && pVeh->m_pPilot != self )
3656 {//whaaa? I'm not on this bike? er....
3657 assert(!!"How did we get to this point?");
3658 }
3659 else
3660 { // Get thrown out.
3661 pVeh->m_pVehicleInfo->Eject( pVeh, self, qtrue );
3662
3663 // Now Send The Vehicle Flying To It's Death
3664 if (pVeh->m_pVehicleInfo->type==VH_SPEEDER && pVeh->m_pParentEntity && pVeh->m_pParentEntity->client)
3665 {
3666 gentity_t* parent = pVeh->m_pParentEntity;
3667 float CurSpeed = VectorLength(parent->client->ps.velocity);
3668
3669 // If Moving
3670 //-----------
3671 if (CurSpeed>(pVeh->m_pVehicleInfo->speedMax*0.5f))
3672 {
3673 // Send The Bike Out Of Control
3674 //------------------------------
3675 pVeh->m_pVehicleInfo->StartDeathDelay(pVeh, 10000);
3676 pVeh->m_ulFlags |= (VEH_OUTOFCONTROL);
3677 VectorScale(parent->client->ps.velocity, 1.25f, parent->pos3);
3678
3679
3680 // Try To Accelerate A Slowing Moving Vehicle To Full Speed
3681 //----------------------------------------------------------
3682 if (CurSpeed<(pVeh->m_pVehicleInfo->speedMax*0.9f))
3683 {
3684 VectorNormalize(parent->pos3);
3685 if (fabsf(parent->pos3[2])<0.3f)
3686 {
3687 VectorScale(parent->pos3, (pVeh->m_pVehicleInfo->speedMax * 1.25f), parent->pos3);
3688 }
3689 else
3690 {
3691 VectorClear(parent->pos3);
3692 }
3693 }
3694
3695 // Throw The Pilot
3696 //----------------
3697 if (parent->pos3[0] || parent->pos3[1])
3698 {
3699 vec3_t throwDir;
3700
3701 VectorCopy(parent->client->ps.velocity, throwDir);
3702 VectorNormalize(throwDir);
3703 throwDir[2] += 0.3f; // up a little
3704
3705 self->client->noRagTime = -1; // no ragdoll for you
3706 CurSpeed /= 10.0f;
3707 if (CurSpeed<50.0)
3708 {
3709 CurSpeed = 50.0f;
3710 }
3711 if (throwDir[2]<0.0f)
3712 {
3713 throwDir[2] = fabsf(throwDir[2]);
3714 }
3715 if (fabsf(throwDir[0])<0.2f)
3716 {
3717 throwDir[0] = Q_flrand(-0.5f, 0.5f);
3718 }
3719 if (fabsf(throwDir[1])<0.2f)
3720 {
3721 throwDir[1] = Q_flrand(-0.5f, 0.5f);
3722 }
3723 G_Throw(self, throwDir, CurSpeed);
3724 }
3725 }
3726 }
3727 }
3728 }
3729 else
3730 {
3731 assert(!!"How did we get to this point?");
3732 }
3733 }
3734
3735 #ifndef FINAL_BUILD
3736 if ( d_saberCombat->integer && attacker && attacker->client )
3737 {
3738 gi.Printf( S_COLOR_YELLOW"combatant %s died, killer anim = %s\n", self->targetname, animTable[attacker->client->ps.torsoAnim].name );
3739 }
3740 #endif//FINAL_BUILD
3741 if ( self->NPC )
3742 {
3743 if (NAV::HasPath(self))
3744 {
3745 NAV::ClearPath(self);
3746 }
3747 if (self->NPC->troop)
3748 {
3749 NPC_LeaveTroop(self);
3750 }
3751 // STEER_TODO: Do we need to free the steer user too?
3752
3753 //clear charmed
3754 G_CheckCharmed( self );
3755
3756 // Remove The Bubble Shield From The Assassin Droid
3757 if (self->client && self->client->NPC_class==CLASS_ASSASSIN_DROID && (self->flags&FL_SHIELDED))
3758 {
3759 self->flags &= ~FL_SHIELDED;
3760 self->client->ps.stats[STAT_ARMOR] = 0;
3761 self->client->ps.powerups[PW_GALAK_SHIELD] = 0;
3762 gi.G2API_SetSurfaceOnOff( &self->ghoul2[self->playerModel], "force_shield", TURN_OFF );
3763 }
3764
3765 if (self->client && self->client->NPC_class==CLASS_HOWLER)
3766 {
3767 G_StopEffect( G_EffectIndex( "howler/sonic" ), self->playerModel, self->genericBolt1, self->s.number );
3768 }
3769
3770
3771
3772 if ( self->client && Jedi_WaitingAmbush( self ) )
3773 {//ambushing trooper
3774 self->client->noclip = false;
3775 }
3776 NPC_FreeCombatPoint( self->NPC->combatPoint );
3777 if ( self->NPC->group )
3778 {
3779 lastInGroup = (qboolean)(self->NPC->group->numGroup < 2);
3780 AI_GroupMemberKilled( self );
3781 AI_DeleteSelfFromGroup( self );
3782 }
3783
3784 if ( self->NPC->tempGoal )
3785 {
3786 G_FreeEntity( self->NPC->tempGoal );
3787 self->NPC->tempGoal = NULL;
3788 }
3789 if ( self->s.eFlags & EF_LOCKED_TO_WEAPON )
3790 {
3791 // dumb, just get the NPC out of the chair
3792 extern void RunEmplacedWeapon( gentity_t *ent, usercmd_t **ucmd );
3793
3794 usercmd_t cmd, *ad_cmd;
3795
3796 memset( &cmd, 0, sizeof( usercmd_t ));
3797
3798 //gentity_t *old = self->owner;
3799
3800 if ( self->owner )
3801 {
3802 self->owner->s.frame = self->owner->startFrame = self->owner->endFrame = 0;
3803 self->owner->svFlags &= ~SVF_ANIMATING;
3804 }
3805
3806 cmd.buttons |= BUTTON_USE;
3807 ad_cmd = &cmd;
3808 RunEmplacedWeapon( self, &ad_cmd );
3809 //self->owner = old;
3810 }
3811 if ( self->client->NPC_class == CLASS_BOBAFETT
3812 || self->client->NPC_class == CLASS_ROCKETTROOPER )
3813 {
3814 if ( self->client->moveType == MT_FLYSWIM )
3815 {
3816 JET_FlyStop( self );
3817 }
3818 }
3819 if ( self->client->NPC_class == CLASS_ROCKETTROOPER )
3820 {
3821 self->client->ps.eFlags &= ~EF_SPOTLIGHT;
3822 }
3823 if ( self->client->NPC_class == CLASS_SAND_CREATURE )
3824 {
3825 self->client->ps.eFlags &= ~EF_NODRAW;
3826 self->s.eFlags &= ~EF_NODRAW;
3827 }
3828 if ( self->client->NPC_class == CLASS_RANCOR )
3829 {
3830 if ( self->count )
3831 {
3832 Rancor_DropVictim( self );
3833 }
3834 }
3835 if ( self->client->NPC_class == CLASS_WAMPA )
3836 {
3837 if ( self->count )
3838 {
3839 if ( self->activator && attacker == self->activator && meansOfDeath == MOD_SABER )
3840 {
3841 self->client->dismembered = false;
3842 //FIXME: the limb should just disappear, cuz I ate it
3843 G_DoDismemberment( self, self->currentOrigin, MOD_SABER, 1000, HL_ARM_RT, qtrue );
3844 }
3845 Wampa_DropVictim( self );
3846 }
3847 }
3848 if ( (self->NPC->aiFlags&NPCAI_HEAL_ROSH) )
3849 {
3850 if ( self->client->leader )
3851 {
3852 self->client->leader->flags &= ~FL_UNDYING;
3853 if ( self->client->leader->client )
3854 {
3855 self->client->leader->client->ps.forcePowersKnown &= ~FORCE_POWERS_ROSH_FROM_TWINS;
3856 }
3857 }
3858 }
3859 if ( (self->client->ps.stats[STAT_WEAPONS]&(1<<WP_SCEPTER)) )
3860 {
3861 G_StopEffect( G_EffectIndex( "scepter/beam_warmup.efx" ), self->weaponModel[1], self->genericBolt1, self->s.number );
3862 G_StopEffect( G_EffectIndex( "scepter/beam.efx" ), self->weaponModel[1], self->genericBolt1, self->s.number );
3863 G_StopEffect( G_EffectIndex( "scepter/slam_warmup.efx" ), self->weaponModel[1], self->genericBolt1, self->s.number );
3864 self->s.loopSound = 0;
3865 }
3866 }
3867 if ( attacker && attacker->NPC && attacker->NPC->group && attacker->NPC->group->enemy == self )
3868 {
3869 attacker->NPC->group->enemy = NULL;
3870 }
3871 if ( self->s.weapon == WP_SABER )
3872 {
3873 holdingSaber = qtrue;
3874 }
3875 if ( self->client->ps.saberEntityNum != ENTITYNUM_NONE && self->client->ps.saberEntityNum > 0 )
3876 {
3877 if ( self->client->ps.saberInFlight )
3878 {//just drop it
3879 self->client->ps.saber[0].Deactivate();
3880 }
3881 else
3882 {
3883 if ( g_saberPickuppableDroppedSabers->integer )
3884 {//always drop your sabers
3885 TossClientItems( self );
3886 self->client->ps.weapon = self->s.weapon = WP_NONE;
3887 }
3888 else if ( (
3889 (hitLoc != HL_HAND_RT&&hitLoc !=HL_CHEST_RT&&hitLoc!=HL_ARM_RT&&hitLoc!=HL_BACK_LT)
3890 || self->client->dismembered
3891 || meansOfDeath != MOD_SABER
3892 )//if might get hand cut off, leave saber in hand
3893 && holdingSaber
3894 && ( Q_irand( 0, 1 )
3895 || meansOfDeath == MOD_EXPLOSIVE
3896 || meansOfDeath == MOD_REPEATER_ALT
3897 || meansOfDeath == MOD_FLECHETTE_ALT
3898 || meansOfDeath == MOD_ROCKET
3899 || meansOfDeath == MOD_ROCKET_ALT
3900 || meansOfDeath == MOD_CONC
3901 || meansOfDeath == MOD_CONC_ALT
3902 || meansOfDeath == MOD_THERMAL
3903 || meansOfDeath == MOD_THERMAL_ALT
3904 || meansOfDeath == MOD_DETPACK
3905 || meansOfDeath == MOD_LASERTRIP
3906 || meansOfDeath == MOD_LASERTRIP_ALT
3907 || meansOfDeath == MOD_MELEE
3908 || meansOfDeath == MOD_FORCE_GRIP
3909 || meansOfDeath == MOD_KNOCKOUT
3910 || meansOfDeath == MOD_CRUSH
3911 || meansOfDeath == MOD_IMPACT
3912 || meansOfDeath == MOD_FALLING
3913 || meansOfDeath == MOD_EXPLOSIVE_SPLASH ) )
3914 {//drop it
3915 TossClientItems( self );
3916 self->client->ps.weapon = self->s.weapon = WP_NONE;
3917 }
3918 else
3919 {//just free it
3920 if ( g_entities[self->client->ps.saberEntityNum].inuse )
3921 {
3922 G_FreeEntity( &g_entities[self->client->ps.saberEntityNum] );
3923 }
3924 self->client->ps.saberEntityNum = ENTITYNUM_NONE;
3925 }
3926 }
3927 }
3928 if ( self->client->NPC_class == CLASS_SHADOWTROOPER )
3929 {//drop a force crystal
3930 if ( Q_stricmpn("shadowtrooper", self->NPC_type, 13 ) == 0 )
3931 {
3932 gitem_t *item;
3933 item = FindItemForAmmo( AMMO_FORCE );
3934 Drop_Item( self, item, 0, qtrue );
3935 }
3936 }
3937 //Use any target we had
3938 if ( meansOfDeath != MOD_KNOCKOUT )
3939 {
3940 G_UseTargets( self, self );
3941 }
3942
3943 if ( attacker )
3944 {
3945 if ( attacker->client && !attacker->s.number )
3946 {
3947 if ( self->client )
3948 {//killed a client
3949 if ( self->client->playerTeam == TEAM_ENEMY
3950 || self->client->playerTeam == TEAM_FREE
3951 || (self->NPC && self->NPC->charmedTime > level.time) )
3952 {//killed an enemy
3953 attacker->client->sess.missionStats.enemiesKilled++;
3954 }
3955 }
3956 if ( attacker != self )
3957 {
3958 G_TrackWeaponUsage( attacker, inflictor, 30, meansOfDeath );
3959 }
3960 }
3961 G_CheckVictoryScript(attacker);
3962 //player killing a jedi with a lightsaber spawns a matrix-effect entity
3963 if ( d_slowmodeath->integer )
3964 {
3965 if ( !self->s.number )
3966 {//what the hell, always do slow-mo when player dies
3967 //FIXME: don't do this when crushed to death?
3968 if ( meansOfDeath == MOD_FALLING && self->client->ps.groundEntityNum == ENTITYNUM_NONE )
3969 {//falling to death, have not hit yet
3970 G_StartMatrixEffect( self, (MEF_NO_VERTBOB|MEF_HIT_GROUND_STOP|MEF_MULTI_SPIN), 10000, 0.25f );
3971 }
3972 else if ( meansOfDeath != MOD_CRUSH )
3973 {//for all deaths except being crushed
3974 G_StartMatrixEffect( self );
3975 }
3976 }
3977 else if ( d_slowmodeath->integer < 4 )
3978 {//any jedi killed by player-saber
3979 if ( d_slowmodeath->integer < 3 )
3980 {//must be the last jedi in the room
3981 if ( !G_JediInRoom( attacker->currentOrigin ) )
3982 {
3983 lastInGroup = qtrue;
3984 }
3985 else
3986 {
3987 lastInGroup = qfalse;
3988 }
3989 }
3990 if ( !attacker->s.number
3991 && (holdingSaber||self->client->NPC_class==CLASS_WAMPA)
3992 && meansOfDeath == MOD_SABER
3993 && attacker->client
3994 && attacker->client->ps.weapon == WP_SABER
3995 && !attacker->client->ps.saberInFlight //FIXME: if dualSabers, should still do slowmo if this killing blow was struck with the left-hand saber...
3996 && (d_slowmodeath->integer > 2||lastInGroup) )//either slow mo death level 3 (any jedi) or 2 and I was the last jedi in the room
3997 {//Matrix!
3998 if ( attacker->client->ps.torsoAnim == BOTH_A6_SABERPROTECT )
3999 {//don't override the range and vertbob
4000 G_StartMatrixEffect( self, (MEF_NO_RANGEVAR|MEF_NO_VERTBOB) );
4001 }
4002 else
4003 {
4004 G_StartMatrixEffect( self );
4005 }
4006 }
4007 }
4008 else
4009 {//all player-saber kills
4010 if ( !attacker->s.number
4011 && meansOfDeath == MOD_SABER
4012 && attacker->client
4013 && attacker->client->ps.weapon == WP_SABER
4014 && !attacker->client->ps.saberInFlight
4015 && (d_slowmodeath->integer > 4||lastInGroup||holdingSaber||self->client->NPC_class==CLASS_WAMPA))//either slow mo death level 5 (any enemy) or 4 and I was the last in my group or I'm a saber user
4016 {//Matrix!
4017 if ( attacker->client->ps.torsoAnim == BOTH_A6_SABERPROTECT )
4018 {//don't override the range and vertbob
4019 G_StartMatrixEffect( self, (MEF_NO_RANGEVAR|MEF_NO_VERTBOB) );
4020 }
4021 else
4022 {
4023 G_StartMatrixEffect( self );
4024 }
4025 }
4026 }
4027 }
4028 }
4029
4030 self->enemy = attacker;
4031 self->client->renderInfo.lookTarget = ENTITYNUM_NONE;
4032
4033 self->client->ps.persistant[PERS_KILLED]++;
4034 if ( self->client->playerTeam == TEAM_PLAYER )
4035 {//FIXME: just HazTeam members in formation on away missions?
4036 //or more controlled- via deathscripts?
4037 // Don't count player
4038 if (( &g_entities[0] != NULL && g_entities[0].client ) && (self->s.number != 0))
4039 {//add to the number of teammates lost
4040 g_entities[0].client->ps.persistant[PERS_TEAMMATES_KILLED]++;
4041 }
4042 else // Player died, fire off scoreboard soon
4043 {
4044 cg.missionStatusDeadTime = level.time + 1000; // Too long?? Too short??
4045 cg.zoomMode = 0; // turn off zooming when we die
4046 }
4047 }
4048
4049 if ( self->s.number == 0 && attacker )
4050 {
4051 // G_SetMissionStatusText( attacker, meansOfDeath );
4052 //TEST: If player killed, unmark all teammates from being undying so they can buy it too
4053 //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.
4054 G_MakeTeamVulnerable();
4055 }
4056
4057 if ( attacker && attacker->client)
4058 {
4059 if ( attacker == self || OnSameTeam (self, attacker ) )
4060 {
4061 AddScore( attacker, -1 );
4062 }
4063 else
4064 {
4065 AddScore( attacker, 1 );
4066 }
4067 }
4068 else
4069 {
4070 AddScore( self, -1 );
4071 }
4072
4073 // if client is in a nodrop area, don't drop anything
4074 contents = gi.pointcontents( self->currentOrigin, -1 );
4075 if ( !holdingSaber
4076 //&& self->s.number != 0
4077 && !( contents & CONTENTS_NODROP )
4078 && meansOfDeath != MOD_SNIPER
4079 && (!self->client||self->client->NPC_class!=CLASS_GALAKMECH))
4080 {
4081 TossClientItems( self );
4082 }
4083
4084 if ( meansOfDeath == MOD_SNIPER )
4085 {//I was disintegrated
4086 if ( self->message )
4087 {//I was holding a key
4088 //drop the key
4089 G_DropKey( self );
4090 }
4091 }
4092
4093 if ( holdingSaber )
4094 {//never drop a lightsaber!
4095 if ( self->client->ps.SaberActive() )
4096 {
4097 self->client->ps.SaberDeactivate();
4098 G_SoundIndexOnEnt( self, CHAN_AUTO, self->client->ps.saber[0].soundOff );
4099 }
4100 }
4101 else if ( self->s.weapon != WP_BRYAR_PISTOL )
4102 {//since player can't pick up bryar pistols, never drop those
4103 self->s.weapon = WP_NONE;
4104 G_RemoveWeaponModels( self );
4105 }
4106
4107 self->s.powerups &= ~PW_REMOVE_AT_DEATH;//removes everything but electricity and force push
4108
4109 //FIXME: do this on a callback? So people can't walk through long death anims?
4110 //Maybe set on last frame? Would be cool for big blocking corpses if the never got set?
4111 //self->contents = CONTENTS_CORPSE;//now done a second after death
4112 /*
4113 self->takedamage = qfalse; // no gibbing
4114 if ( self->client->playerTeam == TEAM_PARASITE )
4115 {
4116 self->contents = CONTENTS_NONE; // FIXME: temp fix
4117 }
4118 else
4119 {
4120 self->contents = CONTENTS_CORPSE;
4121 self->maxs[2] = -8;
4122 }
4123 */
4124 if ( !self->s.number )
4125 {//player
4126 self->contents = CONTENTS_CORPSE;
4127 self->maxs[2] = -8;
4128 }
4129 self->clipmask&=~(CONTENTS_MONSTERCLIP|CONTENTS_BOTCLIP);//so dead NPC can fly off ledges
4130
4131 //FACING==========================================================
4132 if ( attacker && self->s.number == 0 )
4133 {
4134 self->client->ps.stats[STAT_DEAD_YAW] = AngleNormalize180( self->client->ps.viewangles[YAW] );
4135 }
4136 self->currentAngles[PITCH] = 0;
4137 self->currentAngles[ROLL] = 0;
4138 if ( self->NPC )
4139 {
4140 self->NPC->desiredYaw = 0;
4141 self->NPC->desiredPitch = 0;
4142 self->NPC->confusionTime = 0;
4143 self->NPC->charmedTime = 0;
4144 if ( self->ghoul2.size() )
4145 {
4146 if ( self->chestBolt != -1 )
4147 {
4148 G_StopEffect("force/rage2", self->playerModel, self->chestBolt, self->s.number );
4149 }
4150 if ( self->headBolt != -1 )
4151 {
4152 G_StopEffect("force/confusion", self->playerModel, self->headBolt, self->s.number );
4153 }
4154 WP_StopForceHealEffects( self );
4155 }
4156 }
4157 VectorCopy( self->currentAngles, self->client->ps.viewangles );
4158 //FACING==========================================================
4159 if ( player && player->client && player->client->ps.viewEntity == self->s.number )
4160 {//I was the player's viewentity and I died, kick him back to his normal view
4161 G_ClearViewEntity( player );
4162 }
4163 else if ( !self->s.number && self->client->ps.viewEntity > 0 && self->client->ps.viewEntity < ENTITYNUM_NONE )
4164 {
4165 G_ClearViewEntity( self );
4166 }
4167 else if ( !self->s.number && self->client->ps.viewEntity > 0 && self->client->ps.viewEntity < ENTITYNUM_NONE )
4168 {
4169 G_ClearViewEntity( self );
4170 }
4171
4172 self->s.loopSound = 0;
4173
4174 // remove powerups
4175 memset( self->client->ps.powerups, 0, sizeof(self->client->ps.powerups) );
4176
4177 if ( (self->client->ps.eFlags&EF_HELD_BY_RANCOR)
4178 || (self->client->ps.eFlags&EF_HELD_BY_SAND_CREATURE)
4179 || (self->client->ps.eFlags&EF_HELD_BY_WAMPA) )
4180 {//do nothing special here
4181 }
4182 else if ( self->client->NPC_class == CLASS_MARK1 )
4183 {
4184 Mark1_die( self, inflictor, attacker, damage, meansOfDeath, dflags, hitLoc );
4185 }
4186 else if ( self->client->NPC_class == CLASS_INTERROGATOR )
4187 {
4188 Interrogator_die( self, inflictor, attacker, damage, meansOfDeath, dflags, hitLoc );
4189 }
4190 else if ( self->client->NPC_class == CLASS_GALAKMECH )
4191 {//FIXME: need keyframed explosions?
4192 NPC_SetAnim( self, SETANIM_BOTH, BOTH_DEATH1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
4193 G_AddEvent( self, Q_irand(EV_DEATH1, EV_DEATH3), self->health );
4194 }
4195 else if ( self->client->NPC_class == CLASS_ATST )
4196 {//FIXME: need keyframed explosions
4197 if ( !self->s.number )
4198 {
4199 G_DrivableATSTDie( self );
4200 }
4201 anim = PM_PickAnim( self, BOTH_DEATH1, BOTH_DEATH25 ); //initialize to good data
4202 if ( anim != -1 )
4203 {
4204 NPC_SetAnim( self, SETANIM_BOTH, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
4205 }
4206 }
4207 else if ( self->s.number && self->message && meansOfDeath != MOD_SNIPER )
4208 {//imp with a key on his arm
4209 //pick a death anim that leaves key visible
4210 switch ( Q_irand( 0, 3 ) )
4211 {
4212 case 0:
4213 anim = BOTH_DEATH4;
4214 break;
4215 case 1:
4216 anim = BOTH_DEATH21;
4217 break;
4218 case 2:
4219 anim = BOTH_DEATH17;
4220 break;
4221 case 3:
4222 default:
4223 anim = BOTH_DEATH18;
4224 break;
4225 }
4226 //FIXME: verify we have this anim?
4227 NPC_SetAnim( self, SETANIM_BOTH, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
4228 if ( meansOfDeath == MOD_KNOCKOUT || meansOfDeath == MOD_MELEE )
4229 {
4230 G_AddEvent( self, EV_JUMP, 0 );
4231 }
4232 else if ( meansOfDeath == MOD_FORCE_DRAIN )
4233 {
4234 G_AddEvent( self, EV_WATER_DROWN, 0 );
4235 }
4236 else if ( meansOfDeath == MOD_GAS )
4237 {
4238 G_AddEvent( self, EV_WATER_DROWN, 0 );
4239 }
4240 else
4241 {
4242 G_AddEvent( self, Q_irand(EV_DEATH1, EV_DEATH3), self->health );
4243 }
4244 }
4245 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) )
4246 {
4247 //FIXME: no good way to predict you're going to fall to your death... need falling bushes/triggers?
4248 if ( self->client->ps.groundEntityNum == ENTITYNUM_NONE //in the air
4249 && self->client->ps.velocity[2] < 0 //falling
4250 && self->client->ps.legsAnim != BOTH_FALLDEATH1INAIR //not already in falling loop
4251 && self->client->ps.torsoAnim != BOTH_FALLDEATH1INAIR )//not already in falling loop
4252 {
4253 NPC_SetAnim(self, SETANIM_BOTH, BOTH_FALLDEATH1INAIR, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD);
4254 if ( !self->NPC )
4255 {
4256 G_SoundOnEnt( self, CHAN_VOICE, "*falling1.wav" );//CHAN_VOICE_ATTEN
4257 }
4258 else if (!(self->NPC->aiFlags&NPCAI_DIE_ON_IMPACT) )
4259 {
4260 G_SoundOnEnt( self, CHAN_VOICE, "*falling1.wav" );//CHAN_VOICE_ATTEN
4261 //so we don't do this again
4262 self->NPC->aiFlags |= NPCAI_DIE_ON_IMPACT;
4263 //self->client->ps.gravity *= 0.5;//Fall a bit slower
4264 self->client->ps.friction = 1;
4265 }
4266 }
4267 else
4268 {
4269 int deathAnim = BOTH_FALLDEATH1LAND;
4270 if ( PM_InOnGroundAnim( &self->client->ps ) )
4271 {
4272 if ( AngleNormalize180(self->client->renderInfo.torsoAngles[PITCH]) < 0 )
4273 {
4274 deathAnim = BOTH_DEATH_LYING_UP; //# Death anim when lying on back
4275 }
4276 else
4277 {
4278 deathAnim = BOTH_DEATH_LYING_DN; //# Death anim when lying on front
4279 }
4280 }
4281 else if ( PM_InKnockDown( &self->client->ps ) )
4282 {
4283 if ( AngleNormalize180(self->client->renderInfo.torsoAngles[PITCH]) < 0 )
4284 {
4285 deathAnim = BOTH_DEATH_FALLING_UP; //# Death anim when falling on back
4286 }
4287 else
4288 {
4289 deathAnim = BOTH_DEATH_FALLING_DN; //# Death anim when falling on face
4290 }
4291 }
4292 NPC_SetAnim(self, SETANIM_BOTH, deathAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD);
4293 //HMM: check for nodrop?
4294 G_SoundOnEnt( self, CHAN_BODY, "sound/player/fallsplat.wav" );
4295 if ( gi.VoiceVolume[self->s.number]
4296 && self->NPC && (self->NPC->aiFlags&NPCAI_DIE_ON_IMPACT) )
4297 {//I was talking, so cut it off... with a jump sound?
4298 G_SoundOnEnt( self, CHAN_VOICE_ATTEN, "*pain100.wav" );
4299 }
4300 }
4301 }
4302 else
4303 {// normal death
4304 anim = G_CheckSpecialDeathAnim( self, self->pos1, damage, meansOfDeath, hitLoc );
4305 if ( anim == -1 )
4306 {
4307 if ( PM_InOnGroundAnim( &self->client->ps ) && PM_HasAnimation( self, BOTH_LYINGDEATH1 ) )
4308 {//on ground, need different death anim
4309 anim = BOTH_LYINGDEATH1;
4310 }
4311 else if ( meansOfDeath == MOD_TRIGGER_HURT && (self->s.powerups&(1<<PW_SHOCKED)) )
4312 {//electrocuted
4313 anim = BOTH_DEATH17;
4314 }
4315 else if ( meansOfDeath == MOD_WATER || meansOfDeath == MOD_GAS || meansOfDeath == MOD_FORCE_DRAIN )
4316 {//drowned
4317 anim = BOTH_DEATH17;
4318 }
4319 else if ( meansOfDeath != MOD_SNIPER //disintegrates
4320 && meansOfDeath != MOD_CONC_ALT )//does its own death throw
4321 {
4322 cliff_fall = G_CheckLedgeDive( self, 128, self->client->ps.velocity, qtrue, qfalse );
4323 if ( cliff_fall == 2 )
4324 {
4325 if ( !FlyingCreature( self ) && g_gravity->value > 0 )
4326 {
4327 if ( !self->NPC )
4328 {
4329 G_SoundOnEnt( self, CHAN_VOICE, "*falling1.wav" );//CHAN_VOICE_ATTEN
4330 }
4331 else if (!(self->NPC->aiFlags&NPCAI_DIE_ON_IMPACT) )
4332 {
4333 G_SoundOnEnt( self, CHAN_VOICE, "*falling1.wav" );//CHAN_VOICE_ATTEN
4334 self->NPC->aiFlags |= NPCAI_DIE_ON_IMPACT;
4335 self->client->ps.friction = 0;
4336 }
4337 }
4338 }
4339 if ( self->client->ps.pm_time > 0 && self->client->ps.pm_flags & PMF_TIME_KNOCKBACK && self->client->ps.velocity[2] > 0 )
4340 {
4341 float thrown, dot;
4342 vec3_t throwdir, forward;
4343
4344 AngleVectors(self->currentAngles, forward, NULL, NULL);
4345 thrown = VectorNormalize2(self->client->ps.velocity, throwdir);
4346 dot = DotProduct(forward, throwdir);
4347 if ( thrown > 100 )
4348 {
4349 if ( dot > 0.3 )
4350 {//falling forward
4351 if ( cliff_fall == 2 && PM_HasAnimation( self, BOTH_FALLDEATH1 ) )
4352 {
4353 anim = BOTH_FALLDEATH1;
4354 }
4355 else
4356 {
4357 switch ( Q_irand( 0, 7 ) )
4358 {
4359 case 0:
4360 case 1:
4361 anim = BOTH_DEATH4;
4362 break;
4363 case 2:
4364 anim = BOTH_DEATH16;
4365 break;
4366 case 3:
4367 case 4:
4368 case 5:
4369 anim = BOTH_DEATH5;
4370 break;
4371 case 6:
4372 anim = BOTH_DEATH8;
4373 break;
4374 case 7:
4375 anim = BOTH_DEATH14;
4376 break;
4377 }
4378 if ( PM_HasAnimation( self, anim ))
4379 {
4380 self->client->ps.gravity *= 0.8;
4381 self->client->ps.friction = 0;
4382 if ( self->client->ps.velocity[2] > 0 && self->client->ps.velocity[2] < 100 )
4383 {
4384 self->client->ps.velocity[2] = 100;
4385 }
4386 }
4387 else
4388 {
4389 anim = -1;
4390 }
4391 }
4392 }
4393 else if ( dot < -0.3 )
4394 {
4395 if ( thrown >= 250 && !Q_irand( 0, 3 ) )
4396 {
4397 if ( Q_irand( 0, 1 ) )
4398 {
4399 anim = BOTH_DEATHBACKWARD1;
4400 }
4401 else
4402 {
4403 anim = BOTH_DEATHBACKWARD2;
4404 }
4405 }
4406 else
4407 {
4408 switch ( Q_irand( 0, 7 ) )
4409 {
4410 case 0:
4411 case 1:
4412 anim = BOTH_DEATH1;
4413 break;
4414 case 2:
4415 case 3:
4416 anim = BOTH_DEATH2;
4417 break;
4418 case 4:
4419 case 5:
4420 anim = BOTH_DEATH22;
4421 break;
4422 case 6:
4423 case 7:
4424 anim = BOTH_DEATH23;
4425 break;
4426 }
4427 }
4428 if ( PM_HasAnimation( self, anim ) )
4429 {
4430 self->client->ps.gravity *= 0.8;
4431 self->client->ps.friction = 0;
4432 if ( self->client->ps.velocity[2] > 0 && self->client->ps.velocity[2] < 100 )
4433 {
4434 self->client->ps.velocity[2] = 100;
4435 }
4436 }
4437 else
4438 {
4439 anim = -1;
4440 }
4441 }
4442 else
4443 {//falling to one of the sides
4444 if ( cliff_fall == 2 && PM_HasAnimation( self, BOTH_FALLDEATH1 ) )
4445 {
4446 anim = BOTH_FALLDEATH1;
4447 if ( self->client->ps.velocity[2] > 0 && self->client->ps.velocity[2] < 100 )
4448 {
4449 self->client->ps.velocity[2] = 100;
4450 }
4451 }
4452 }
4453 }
4454 }
4455 }
4456 }
4457 else
4458 {
4459 specialAnim = qtrue;
4460 }
4461
4462 if ( anim == -1 )
4463 {
4464 if ( meansOfDeath == MOD_ELECTROCUTE
4465 || (meansOfDeath == MOD_CRUSH && self->s.eFlags&EF_FORCE_GRIPPED)
4466 || (meansOfDeath == MOD_FORCE_DRAIN && self->s.eFlags&EF_FORCE_DRAINED))
4467 {//electrocuted or choked to death
4468 anim = BOTH_DEATH17;
4469 }
4470 else
4471 {
4472 anim = G_PickDeathAnim( self, self->pos1, damage, meansOfDeath, hitLoc );
4473 }
4474 }
4475 if ( anim == -1 )
4476 {
4477 anim = PM_PickAnim( self, BOTH_DEATH1, BOTH_DEATH25 ); //initialize to good data
4478 //TEMP HACK: these spinny deaths should happen less often
4479 if ( ( anim == BOTH_DEATH8 || anim == BOTH_DEATH14 ) && Q_irand( 0, 1 ) )
4480 {
4481 anim = PM_PickAnim( self, BOTH_DEATH1, BOTH_DEATH25 ); //initialize to good data
4482 }
4483 }
4484 else if ( !PM_HasAnimation( self, anim ) )
4485 {//crap, still missing an anim, so pick one that we do have
4486 anim = PM_PickAnim( self, BOTH_DEATH1, BOTH_DEATH25 ); //initialize to good data
4487 }
4488
4489
4490 if ( meansOfDeath == MOD_KNOCKOUT )
4491 {
4492 //FIXME: knock-out sound, and don't remove me
4493 G_AddEvent( self, EV_JUMP, 0 );
4494 G_UseTargets2( self, self, self->target2 );
4495 G_AlertTeam( self, attacker, 512, 32 );
4496 if ( self->NPC )
4497 {//stick around for a while
4498 self->NPC->timeOfDeath = level.time + 10000;
4499 }
4500 }
4501 else if ( meansOfDeath == MOD_GAS || meansOfDeath == MOD_FORCE_DRAIN )
4502 {
4503 G_AddEvent( self, EV_WATER_DROWN, 0 );
4504 G_AlertTeam( self, attacker, 512, 32 );
4505 if ( self->NPC )
4506 {//stick around for a while
4507 self->NPC->timeOfDeath = level.time + 10000;
4508 }
4509 }
4510 else if ( meansOfDeath == MOD_SNIPER )
4511 {
4512 gentity_t *tent;
4513 vec3_t spot;
4514
4515 VectorCopy( self->currentOrigin, spot );
4516
4517 self->flags |= FL_DISINTEGRATED;
4518 self->svFlags |= SVF_BROADCAST;
4519 tent = G_TempEntity( spot, EV_DISINTEGRATION );
4520 tent->s.eventParm = PW_DISRUPTION;
4521 tent->svFlags |= SVF_BROADCAST;
4522 tent->owner = self;
4523
4524 G_AlertTeam( self, attacker, 512, 88 );
4525
4526 if ( self->playerModel >= 0 )
4527 {
4528 // don't let 'em animate
4529 gi.G2API_PauseBoneAnimIndex( &self->ghoul2[self->playerModel], self->rootBone, cg.time );
4530 gi.G2API_PauseBoneAnimIndex( &self->ghoul2[self->playerModel], self->motionBone, cg.time );
4531 gi.G2API_PauseBoneAnimIndex( &self->ghoul2[self->playerModel], self->lowerLumbarBone, cg.time );
4532 anim = -1;
4533 }
4534
4535 //not solid anymore
4536 self->contents = 0;
4537 self->maxs[2] = -8;
4538
4539 if ( self->NPC )
4540 {
4541 //need to pad deathtime some to stick around long enough for death effect to play
4542 self->NPC->timeOfDeath = level.time + 2000;
4543 }
4544 }
4545 else
4546 {
4547 if ( hitLoc == HL_HEAD
4548 && !(dflags&DAMAGE_RADIUS)
4549 && meansOfDeath!=MOD_REPEATER_ALT
4550 && meansOfDeath!=MOD_FLECHETTE_ALT
4551 && meansOfDeath!=MOD_ROCKET
4552 && meansOfDeath!=MOD_ROCKET_ALT
4553 && meansOfDeath!=MOD_CONC
4554 && meansOfDeath!=MOD_THERMAL
4555 && meansOfDeath!=MOD_THERMAL_ALT
4556 && meansOfDeath!=MOD_DETPACK
4557 && meansOfDeath!=MOD_LASERTRIP
4558 && meansOfDeath!=MOD_LASERTRIP_ALT
4559 && meansOfDeath!=MOD_EXPLOSIVE
4560 && meansOfDeath!=MOD_EXPLOSIVE_SPLASH )
4561 {//no sound when killed by headshot (explosions don't count)
4562 G_AlertTeam( self, attacker, 512, 0 );
4563 if ( gi.VoiceVolume[self->s.number] )
4564 {//I was talking, so cut it off... with a jump sound?
4565 G_SoundOnEnt( self, CHAN_VOICE, "*jump1.wav" );
4566 }
4567 }
4568 else
4569 {
4570 if ( (self->client->ps.eFlags&EF_FORCE_GRIPPED) || (self->client->ps.eFlags&EF_FORCE_DRAINED) )
4571 {//killed while gripped - no loud scream
4572 G_AlertTeam( self, attacker, 512, 32 );
4573 }
4574 else if ( cliff_fall != 2 )
4575 {
4576 if ( meansOfDeath == MOD_KNOCKOUT || meansOfDeath == MOD_MELEE )
4577 {
4578 G_AddEvent( self, EV_JUMP, 0 );
4579 }
4580 else if ( meansOfDeath == MOD_GAS || meansOfDeath == MOD_FORCE_DRAIN )
4581 {
4582 G_AddEvent( self, EV_WATER_DROWN, 0 );
4583 }
4584 else
4585 {
4586 G_AddEvent( self, Q_irand(EV_DEATH1, EV_DEATH3), self->health );
4587 }
4588 G_DeathAlert( self, attacker );
4589 }
4590 else
4591 {//screaming death is louder
4592 G_AlertTeam( self, attacker, 512, 1024 );
4593 }
4594 }
4595 }
4596
4597 if ( attacker && attacker->s.number == 0 )
4598 {//killed by player
4599 //FIXME: this should really be wherever my body comes to rest...
4600 AddSightEvent( attacker, self->currentOrigin, 384, AEL_DISCOVERED, 10 );
4601 //FIXME: danger event so that others will run away from this area since it's obviously dangerous
4602 }
4603
4604 if ( anim >= 0 )//can be -1 if it fails, -2 if it's already in a death anim
4605 {
4606 NPC_SetAnim(self, SETANIM_BOTH, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD);
4607 }
4608 }
4609
4610 //do any dismemberment if there's any to do...
4611 if ( (dflags&DAMAGE_DISMEMBER)
4612 && G_DoDismemberment( self, self->pos1, meansOfDeath, damage, hitLoc )
4613 && !specialAnim )
4614 {//we did dismemberment and our death anim is okay to override
4615 if ( hitLoc == HL_HAND_RT && self->locationDamage[hitLoc] >= Q3_INFINITE && cliff_fall != 2 && self->client->ps.groundEntityNum != ENTITYNUM_NONE )
4616 {//just lost our right hand and we're on the ground, use the special anim
4617 NPC_SetAnim( self, SETANIM_BOTH, BOTH_RIGHTHANDCHOPPEDOFF, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
4618 }
4619 }
4620
4621 // don't allow player to respawn for a few seconds
4622 self->client->respawnTime = level.time + 2000;//self->client->ps.legsAnimTimer;
4623
4624 //rww - RAGDOLL_BEGIN
4625 if (gi.Cvar_VariableIntegerValue("broadsword"))
4626 {
4627 if ( self->client && (!self->NPC || !G_StandardHumanoid( self ) ) )
4628 {
4629 PM_SetLegsAnimTimer( self, &self->client->ps.legsAnimTimer, -1 );
4630 PM_SetTorsoAnimTimer( self, &self->client->ps.torsoAnimTimer, -1 );
4631 }
4632 }
4633 else
4634 {
4635 if ( self->client )
4636 {
4637 PM_SetLegsAnimTimer( self, &self->client->ps.legsAnimTimer, -1 );
4638 PM_SetTorsoAnimTimer( self, &self->client->ps.torsoAnimTimer, -1 );
4639 }
4640 }
4641 //rww - RAGDOLL_END
4642
4643 //Flying creatures should drop when killed
4644 //FIXME: This may screw up certain things that expect to float even while dead <?>
4645 self->svFlags &= ~SVF_CUSTOM_GRAVITY;
4646
4647 self->client->ps.pm_type = PM_DEAD;
4648 self->client->ps.pm_flags &= ~PMF_STUCK_TO_WALL;
4649 //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
4650 self->client->ps.stats[STAT_HEALTH] = self->health;
4651
4652 if ( self->NPC )
4653 {//If an NPC, make sure we start running our scripts again- this gets set to infinite while we fall to our deaths
4654 self->NPC->nextBStateThink = level.time;
4655 }
4656
4657 if ( G_ActivateBehavior( self, BSET_DEATH ) )
4658 {
4659 deathScript = qtrue;
4660 }
4661
4662 if ( self->NPC && (self->NPC->scriptFlags&SCF_FFDEATH) )
4663 {
4664 if ( G_ActivateBehavior( self, BSET_FFDEATH ) )
4665 {//FIXME: should running this preclude running the normal deathscript?
4666 deathScript = qtrue;
4667 }
4668 G_UseTargets2( self, self, self->target4 );
4669 }
4670
4671 if ( !deathScript && !(self->svFlags&SVF_KILLED_SELF) )
4672 {
4673 //Should no longer run scripts
4674 //WARNING!!! DO NOT DO THIS WHILE RUNNING A SCRIPT, ICARUS WILL HANDLE IT, but it's bad
4675 Quake3Game()->FreeEntity( self );
4676 }
4677
4678 // Free up any timers we may have on us.
4679 TIMER_Clear( self->s.number );
4680
4681 // Set pending objectives to failed
4682 OBJ_SetPendingObjectives(self);
4683
4684 gi.linkentity (self);
4685
4686 self->bounceCount = -1; // This is a cheap hack for optimizing the pointcontents check in deadthink
4687 if ( self->NPC )
4688 {
4689 self->NPC->timeOfDeath = level.time;//this will change - used for debouncing post-death events
4690 self->s.time = level.time;//this will not chage- this is actual time of death
4691 }
4692
4693 // Start any necessary death fx for this entity
4694 DeathFX( self );
4695 }
4696
G_CheckForStrongAttackMomentum(gentity_t * self)4697 qboolean G_CheckForStrongAttackMomentum( gentity_t *self )
4698 {//see if our saber attack has too much momentum to be interrupted
4699 if ( PM_PowerLevelForSaberAnim( &self->client->ps ) > FORCE_LEVEL_2 )
4700 {//strong attacks can't be interrupted
4701 if ( PM_InAnimForSaberMove( self->client->ps.torsoAnim, self->client->ps.saberMove ) )
4702 {//our saberMove was not already interupted by some other anim (like pain)
4703 if ( PM_SaberInStart( self->client->ps.saberMove ) )
4704 {
4705 float animLength = PM_AnimLength( self->client->clientInfo.animFileIndex, (animNumber_t)self->client->ps.torsoAnim );
4706 if ( animLength - self->client->ps.torsoAnimTimer > 750 )
4707 {//start anim is already 3/4 of a second into it, can't interrupt it now
4708 return qtrue;
4709 }
4710 }
4711 else if ( PM_SaberInReturn( self->client->ps.saberMove ) )
4712 {
4713 if ( self->client->ps.torsoAnimTimer > 750 )
4714 {//still have a good amount of time left in the return anim, can't interrupt it
4715 return qtrue;
4716 }
4717 }
4718 else
4719 {//cannot interrupt actual transitions and attacks
4720 return qtrue;
4721 }
4722 }
4723 }
4724 return qfalse;
4725 }
4726
PlayerPain(gentity_t * self,gentity_t * inflictor,gentity_t * other,const vec3_t point,int damage,int mod,int hitLoc)4727 void PlayerPain( gentity_t *self, gentity_t *inflictor, gentity_t *other, const vec3_t point, int damage, int mod, int hitLoc )
4728 {
4729 if ( self->client->NPC_class == CLASS_ATST )
4730 {//different kind of pain checking altogether
4731 G_ATSTCheckPain( self, other, point, damage, mod, hitLoc );
4732 int blasterTest = gi.G2API_GetSurfaceRenderStatus( &self->ghoul2[self->playerModel], "head_light_blaster_cann" );
4733 int chargerTest = gi.G2API_GetSurfaceRenderStatus( &self->ghoul2[self->playerModel], "head_concussion_charger" );
4734 if ( blasterTest && chargerTest )
4735 {//lost both side guns
4736 //take away that weapon
4737 self->client->ps.stats[STAT_WEAPONS] &= ~( 1 << WP_ATST_SIDE );
4738 //switch to primary guns
4739 if ( self->client->ps.weapon == WP_ATST_SIDE )
4740 {
4741 CG_ChangeWeapon( WP_ATST_MAIN );
4742 }
4743 }
4744 }
4745 else
4746 {
4747 // play an apropriate pain sound
4748 if ( level.time > self->painDebounceTime && !(self->flags & FL_GODMODE) )
4749 {//first time hit this frame and not in godmode
4750 self->client->ps.damageEvent++;
4751 if ( !Q3_TaskIDPending( self, TID_CHAN_VOICE ) )
4752 {
4753 if ( self->client->damage_blood )
4754 {//took damage myself, not just armor
4755 if ( mod == MOD_GAS )
4756 {
4757 //SIGH... because our choke sounds are inappropriately long, I have to debounce them in code!
4758 if ( TIMER_Done( self, "gasChokeSound" ) )
4759 {
4760 TIMER_Set( self, "gasChokeSound", Q_irand( 1000, 2000 ) );
4761 G_SpeechEvent( self, Q_irand(EV_CHOKE1, EV_CHOKE3) );
4762 }
4763 if ( self->painDebounceTime <= level.time )
4764 {
4765 self->painDebounceTime = level.time + 50;
4766 }
4767 }
4768 else
4769 {
4770 G_AddEvent( self, EV_PAIN, self->health );
4771 }
4772 }
4773 }
4774 }
4775 if ( damage != -1 && (mod==MOD_MELEE || damage==0/*fake damage*/ || (Q_irand( 0, 10 ) <= damage && self->client->damage_blood)) )
4776 {//-1 == don't play pain anim
4777 if ( ( ((mod==MOD_SABER||mod==MOD_MELEE)&&self->client->damage_blood) || mod == MOD_CRUSH ) && (self->s.weapon == WP_SABER||self->s.weapon==WP_MELEE||cg.renderingThirdPerson) )//FIXME: not only if using saber, but if in third person at all? But then 1st/third person functionality is different...
4778 {//FIXME: only strong-level saber attacks should make me play pain anim?
4779 if ( !G_CheckForStrongAttackMomentum( self ) && !PM_SpinningSaberAnim( self->client->ps.legsAnim )
4780 && !PM_SaberInSpecialAttack( self->client->ps.torsoAnim )
4781 && !PM_InKnockDown( &self->client->ps ) )
4782 {//strong attacks and spins cannot be interrupted by pain, no pain when in knockdown
4783 int parts = SETANIM_BOTH;
4784 if ( self->client->ps.groundEntityNum != ENTITYNUM_NONE &&
4785 !PM_SpinningSaberAnim( self->client->ps.legsAnim ) &&
4786 !PM_FlippingAnim( self->client->ps.legsAnim ) &&
4787 !PM_InSpecialJump( self->client->ps.legsAnim ) &&
4788 !PM_RollingAnim( self->client->ps.legsAnim )&&
4789 !PM_CrouchAnim( self->client->ps.legsAnim )&&
4790 !PM_RunningAnim( self->client->ps.legsAnim ))
4791 {//if on a surface and not in a spin or flip, play full body pain
4792 parts = SETANIM_BOTH;
4793 }
4794 else
4795 {//play pain just in torso
4796 parts = SETANIM_TORSO;
4797 }
4798 if ( self->painDebounceTime < level.time )
4799 {
4800 //temp HACK: these are the only 2 pain anims that look good when holding a saber
4801 NPC_SetAnim( self, parts, PM_PickAnim( self, BOTH_PAIN2, BOTH_PAIN3 ), SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
4802 self->client->ps.saberMove = LS_READY;//don't finish whatever saber move you may have been in
4803 //WTF - insn't working
4804 if ( self->health < 10 && d_slowmodeath->integer > 5 )
4805 {
4806 G_StartMatrixEffect( self );
4807 }
4808 }
4809 if ( (parts == SETANIM_BOTH && damage > 30) || (self->painDebounceTime>level.time&&damage>10))
4810 {//took a lot of damage in 1 hit //or took 2 hits in quick succession
4811 self->aimDebounceTime = level.time + self->client->ps.torsoAnimTimer;
4812 self->client->ps.pm_time = self->client->ps.torsoAnimTimer;
4813 self->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
4814 }
4815 self->client->ps.weaponTime = self->client->ps.torsoAnimTimer;
4816 self->attackDebounceTime = level.time + self->client->ps.torsoAnimTimer;
4817 }
4818 self->painDebounceTime = level.time + self->client->ps.torsoAnimTimer;
4819 }
4820 }
4821 }
4822 if ( mod != MOD_GAS && self->painDebounceTime <= level.time )
4823 {
4824 self->painDebounceTime = level.time + 700;
4825 }
4826 }
4827 /*
4828 ================
4829 CheckArmor
4830 ================
4831 */
CheckArmor(gentity_t * ent,int damage,int dflags,int mod)4832 int CheckArmor (gentity_t *ent, int damage, int dflags, int mod)
4833 {
4834 gclient_t *client;
4835 int save;
4836 int count;
4837
4838 if (!damage)
4839 return 0;
4840
4841 client = ent->client;
4842
4843 if (!client)
4844 return 0;
4845
4846 if ( (dflags&DAMAGE_NO_ARMOR) )
4847 {
4848 // If this isn't a vehicle, leave.
4849 if ( client->NPC_class != CLASS_VEHICLE )
4850 {
4851 return 0;
4852 }
4853 }
4854
4855 if (client->NPC_class==CLASS_ASSASSIN_DROID)
4856 {
4857 // The Assassin Always Completely Ignores These Damage Types
4858 //-----------------------------------------------------------
4859 if (mod==MOD_GAS || mod==MOD_IMPACT || mod==MOD_LAVA || mod==MOD_SLIME || mod==MOD_WATER ||
4860 mod==MOD_FORCE_GRIP || mod==MOD_FORCE_DRAIN || mod==MOD_SEEKER || mod==MOD_MELEE ||
4861 mod==MOD_BOWCASTER || mod==MOD_BRYAR || mod==MOD_BRYAR_ALT || mod==MOD_BLASTER || mod==MOD_BLASTER_ALT ||
4862 mod==MOD_SNIPER || mod==MOD_BOWCASTER || mod==MOD_BOWCASTER_ALT || mod==MOD_REPEATER || mod==MOD_REPEATER_ALT)
4863 {
4864 return damage;
4865 }
4866
4867 // The Assassin Always Takes Half Of These Damage Types
4868 //------------------------------------------------------
4869 if (mod==MOD_GAS || mod==MOD_IMPACT || mod==MOD_LAVA || mod==MOD_SLIME || mod==MOD_WATER)
4870 {
4871 return damage/2;
4872 }
4873
4874 // If The Shield Is Not On, No Additional Protection
4875 //---------------------------------------------------
4876 if (!(ent->flags&FL_SHIELDED))
4877 {
4878 // He Does Ignore Half Saber Damage, Even Shield Down
4879 //----------------------------------------------------
4880 if (mod==MOD_SABER)
4881 {
4882 return (int)((float)(damage)*0.75f);
4883 }
4884 return 0;
4885 }
4886
4887 // If The Shield Is Up, He Ignores These Damage Types
4888 //----------------------------------------------------
4889 if (mod==MOD_SABER || mod==MOD_FLECHETTE || mod==MOD_FLECHETTE_ALT || mod==MOD_DISRUPTOR)
4890 {
4891 return damage;
4892 }
4893
4894 // The Demp Completely Destroys The Shield
4895 //-----------------------------------------
4896 if (mod==MOD_DEMP2 || mod==MOD_DEMP2_ALT)
4897 {
4898 client->ps.stats[STAT_ARMOR] = 0;
4899 return 0;
4900 }
4901
4902 // Otherwise, The Shield Absorbs As Much Damage As Possible
4903 //----------------------------------------------------------
4904 int previousArmor = client->ps.stats[STAT_ARMOR];
4905 client->ps.stats[STAT_ARMOR] -= damage;
4906 if (client->ps.stats[STAT_ARMOR]<0)
4907 {
4908 client->ps.stats[STAT_ARMOR] = 0;
4909 }
4910 return (previousArmor - client->ps.stats[STAT_ARMOR]);
4911 }
4912
4913
4914
4915 if ( client->NPC_class == CLASS_GALAKMECH)
4916 {//special case
4917 if ( client->ps.stats[STAT_ARMOR] <= 0 )
4918 {//no shields
4919 client->ps.powerups[PW_GALAK_SHIELD] = 0;
4920 return 0;
4921 }
4922 else
4923 {//shields take all the damage
4924 client->ps.stats[STAT_ARMOR] -= damage;
4925 if ( client->ps.stats[STAT_ARMOR] <= 0 )
4926 {
4927 client->ps.powerups[PW_GALAK_SHIELD] = 0;
4928 client->ps.stats[STAT_ARMOR] = 0;
4929 }
4930 return damage;
4931 }
4932 }
4933 else
4934 {
4935 // armor
4936 count = client->ps.stats[STAT_ARMOR];
4937
4938 // No damage to entity until armor is at less than 50% strength
4939 if (count > (client->ps.stats[STAT_MAX_HEALTH]/2)) // MAX_HEALTH is considered max armor. Or so I'm told.
4940 {
4941 save = damage;
4942 }
4943 else
4944 {
4945 if ( !ent->s.number && client->NPC_class == CLASS_ATST )
4946 {//player in ATST... armor takes *all* the damage
4947 save = damage;
4948 }
4949 else
4950 {
4951 save = ceil( (float) damage * ARMOR_PROTECTION );
4952 }
4953 }
4954
4955 //Always round up
4956 if (damage == 1)
4957 {
4958 if ( client->ps.stats[STAT_ARMOR] > 0 )
4959 client->ps.stats[STAT_ARMOR] -= save;
4960 //WTF? returns false 0 if armor absorbs only 1 point of damage
4961 return 0;
4962 }
4963
4964 if (save >= count)
4965 save = count;
4966
4967 if (!save)
4968 return 0;
4969
4970 client->ps.stats[STAT_ARMOR] -= save;
4971
4972 return save;
4973 }
4974 }
4975
4976 extern void NPC_SetPainEvent( gentity_t *self );
4977 extern qboolean Boba_StopKnockdown( gentity_t *self, gentity_t *pusher, const vec3_t pushDir, qboolean forceKnockdown = qfalse );
4978 extern qboolean Jedi_StopKnockdown( gentity_t *self, gentity_t *pusher, const vec3_t pushDir );
G_Knockdown(gentity_t * self,gentity_t * attacker,const vec3_t pushDir,float strength,qboolean breakSaberLock)4979 void G_Knockdown( gentity_t *self, gentity_t *attacker, const vec3_t pushDir, float strength, qboolean breakSaberLock )
4980 {
4981 if ( !self || !self->client || !attacker || !attacker->client )
4982 {
4983 return;
4984 }
4985
4986 if ( self->client->NPC_class == CLASS_ROCKETTROOPER )
4987 {
4988 return;
4989 }
4990
4991 if ( Boba_StopKnockdown( self, attacker, pushDir ) )
4992 {
4993 return;
4994 }
4995 else if ( Jedi_StopKnockdown( self, attacker, pushDir ) )
4996 {//They can sometimes backflip instead of be knocked down
4997 return;
4998 }
4999 else if ( PM_LockedAnim( self->client->ps.legsAnim ) )
5000 {//stuck doing something else
5001 return;
5002 }
5003 else if ( Rosh_BeingHealed( self ) )
5004 {
5005 return;
5006 }
5007
5008 //break out of a saberLock?
5009 if ( self->client->ps.saberLockTime > level.time )
5010 {
5011 if ( breakSaberLock )
5012 {
5013 self->client->ps.saberLockTime = 0;
5014 self->client->ps.saberLockEnemy = ENTITYNUM_NONE;
5015 }
5016 else
5017 {
5018 return;
5019 }
5020 }
5021
5022 if ( self->health > 0 )
5023 {
5024 if ( !self->s.number )
5025 {
5026 NPC_SetPainEvent( self );
5027 }
5028 else
5029 {
5030 GEntity_PainFunc( self, attacker, attacker, self->currentOrigin, 0, MOD_MELEE );
5031 }
5032
5033 G_CheckLedgeDive( self, 72, pushDir, qfalse, qfalse );
5034
5035 if ( !PM_SpinningSaberAnim( self->client->ps.legsAnim )
5036 && !PM_FlippingAnim( self->client->ps.legsAnim )
5037 && !PM_RollingAnim( self->client->ps.legsAnim )
5038 && !PM_InKnockDown( &self->client->ps ) )
5039 {
5040 int knockAnim = BOTH_KNOCKDOWN1;//default knockdown
5041 if ( !self->s.number && ( strength < 300 ) )//!g_spskill->integer ||
5042 {//player only knocked down if pushed *hard*
5043 return;
5044 }
5045 else if ( PM_CrouchAnim( self->client->ps.legsAnim ) )
5046 {//crouched knockdown
5047 knockAnim = BOTH_KNOCKDOWN4;
5048 }
5049 else
5050 {//plain old knockdown
5051 vec3_t pLFwd, pLAngles = {0,self->client->ps.viewangles[YAW],0};
5052 AngleVectors( pLAngles, pLFwd, NULL, NULL );
5053 if ( DotProduct( pLFwd, pushDir ) > 0.2f )
5054 {//pushing him from behind
5055 knockAnim = BOTH_KNOCKDOWN3;
5056 }
5057 else
5058 {//pushing him from front
5059 knockAnim = BOTH_KNOCKDOWN1;
5060 }
5061 }
5062 if ( knockAnim == BOTH_KNOCKDOWN1 && strength > 150 )
5063 {//push *hard*
5064 knockAnim = BOTH_KNOCKDOWN2;
5065 }
5066 NPC_SetAnim( self, SETANIM_BOTH, knockAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
5067 if ( self->s.number >= MAX_CLIENTS )
5068 {//randomize getup times
5069 int addTime = Q_irand( -200, 200 );
5070 self->client->ps.legsAnimTimer += addTime;
5071 self->client->ps.torsoAnimTimer += addTime;
5072 }
5073 else
5074 {//player holds extra long so you have more time to decide to do the quick getup
5075 if ( PM_KnockDownAnim( self->client->ps.legsAnim ) )
5076 {
5077 self->client->ps.legsAnimTimer += PLAYER_KNOCKDOWN_HOLD_EXTRA_TIME;
5078 self->client->ps.torsoAnimTimer += PLAYER_KNOCKDOWN_HOLD_EXTRA_TIME;
5079 }
5080 }
5081 }
5082 }
5083 }
5084
G_CheckKnockdown(gentity_t * targ,gentity_t * attacker,vec3_t newDir,int dflags,int mod)5085 void G_CheckKnockdown( gentity_t *targ, gentity_t *attacker, vec3_t newDir, int dflags, int mod )
5086 {
5087 if ( !targ || !attacker )
5088 {
5089 return;
5090 }
5091 if ( !(dflags&DAMAGE_RADIUS) )
5092 {//not inherently explosive damage, check mod
5093 if ( mod!=MOD_REPEATER_ALT
5094 &&mod!=MOD_FLECHETTE_ALT
5095 &&mod!=MOD_ROCKET
5096 &&mod!=MOD_ROCKET_ALT
5097 &&mod!=MOD_CONC
5098 &&mod!=MOD_CONC_ALT
5099 &&mod!=MOD_THERMAL
5100 &&mod!=MOD_THERMAL_ALT
5101 &&mod!=MOD_DETPACK
5102 &&mod!=MOD_LASERTRIP
5103 &&mod!=MOD_LASERTRIP_ALT
5104 &&mod!=MOD_EXPLOSIVE
5105 &&mod!=MOD_EXPLOSIVE_SPLASH )
5106 {
5107 return;
5108 }
5109 }
5110
5111 if ( !targ->client || targ->client->NPC_class == CLASS_PROTOCOL || !G_StandardHumanoid( targ ) )
5112 {
5113 return;
5114 }
5115
5116 if ( targ->client->ps.groundEntityNum == ENTITYNUM_NONE )
5117 {//already in air
5118 return;
5119 }
5120
5121 if ( !targ->s.number )
5122 {//player less likely to be knocked down
5123 if ( !g_spskill->integer )
5124 {//never in easy
5125 return;
5126 }
5127 if ( !cg.renderingThirdPerson || cg.zoomMode )
5128 {//never if not in chase camera view (so more likely with saber out)
5129 return;
5130 }
5131 if ( g_spskill->integer == 1 )
5132 {//33% chance on medium
5133 if ( Q_irand( 0, 2 ) )
5134 {
5135 return;
5136 }
5137 }
5138 else
5139 {//50% chance on hard
5140 if ( Q_irand( 0, 1 ) )
5141 {
5142 return;
5143 }
5144 }
5145 }
5146
5147 float strength = VectorLength( targ->client->ps.velocity );
5148 if ( targ->client->ps.velocity[2] > 100 && strength > Q_irand( 150, 350 ) )//600 ) )
5149 {//explosive concussion possibly do a knockdown?
5150 G_Knockdown( targ, attacker, newDir, strength, qtrue );
5151 }
5152 }
5153
G_ApplyKnockback(gentity_t * targ,vec3_t newDir,float knockback)5154 void G_ApplyKnockback( gentity_t *targ, vec3_t newDir, float knockback )
5155 {
5156 vec3_t kvel;
5157 float mass;
5158
5159 if ( targ
5160 && targ->client
5161 && ( targ->client->NPC_class == CLASS_ATST
5162 || targ->client->NPC_class == CLASS_RANCOR
5163 || targ->client->NPC_class == CLASS_SAND_CREATURE
5164 || targ->client->NPC_class == CLASS_WAMPA) )
5165 {//much to large to *ever* throw
5166 return;
5167 }
5168
5169 //--- TEMP TEST
5170 if ( newDir[2] <= 0.0f )
5171 {
5172
5173 newDir[2] += (( 0.0f - newDir[2] ) * 1.2f );
5174 }
5175
5176 knockback *= 2.0f;
5177
5178 if ( knockback > 120 )
5179 {
5180 knockback = 120;
5181 }
5182 //--- TEMP TEST
5183
5184 if ( targ->physicsBounce > 0 ) //overide the mass
5185 mass = targ->physicsBounce;
5186 else
5187 mass = 200;
5188
5189 if ( g_gravity->value > 0 )
5190 {
5191 VectorScale( newDir, g_knockback->value * (float)knockback / mass * 0.8, kvel );
5192 kvel[2] = newDir[2] * ( g_knockback->value * (float)knockback ) / ( mass * 1.5 ) + 20;
5193 }
5194 else
5195 {
5196 VectorScale( newDir, g_knockback->value * (float)knockback / mass, kvel );
5197 }
5198
5199 if ( targ->client )
5200 {
5201 VectorAdd( targ->client->ps.velocity, kvel, targ->client->ps.velocity );
5202 }
5203 else if ( targ->s.pos.trType != TR_STATIONARY && targ->s.pos.trType != TR_LINEAR_STOP && targ->s.pos.trType != TR_NONLINEAR_STOP )
5204 {
5205 VectorAdd( targ->s.pos.trDelta, kvel, targ->s.pos.trDelta );
5206 VectorCopy( targ->currentOrigin, targ->s.pos.trBase );
5207 targ->s.pos.trTime = level.time;
5208 }
5209
5210 // set the timer so that the other client can't cancel
5211 // out the movement immediately
5212 if ( targ->client && !targ->client->ps.pm_time )
5213 {
5214 int t;
5215
5216 t = knockback * 2;
5217 if ( t < 50 ) {
5218 t = 50;
5219 }
5220 if ( t > 200 ) {
5221 t = 200;
5222 }
5223 targ->client->ps.pm_time = t;
5224 targ->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
5225 }
5226 }
5227
G_CheckForLedge(gentity_t * self,vec3_t fallCheckDir,float checkDist)5228 static int G_CheckForLedge( gentity_t *self, vec3_t fallCheckDir, float checkDist )
5229 {
5230 vec3_t start, end;
5231 trace_t tr;
5232
5233 VectorMA( self->currentOrigin, checkDist, fallCheckDir, end );
5234 //Should have clip burshes masked out by now and have bbox resized to death size
5235 gi.trace( &tr, self->currentOrigin, self->mins, self->maxs, end, self->s.number, self->clipmask, (EG2_Collision)0, 0 );
5236 if ( tr.allsolid || tr.startsolid )
5237 {
5238 return 0;
5239 }
5240 VectorCopy( tr.endpos, start );
5241 VectorCopy( start, end );
5242 end[2] -= 256;
5243
5244 gi.trace( &tr, start, self->mins, self->maxs, end, self->s.number, self->clipmask, (EG2_Collision)0, 0 );
5245 if ( tr.allsolid || tr.startsolid )
5246 {
5247 return 0;
5248 }
5249 if ( tr.fraction >= 1.0 )
5250 {
5251 return (start[2]-tr.endpos[2]);
5252 }
5253 return 0;
5254 }
5255
G_FriendlyFireReaction(gentity_t * self,gentity_t * other,int dflags)5256 static void G_FriendlyFireReaction( gentity_t *self, gentity_t *other, int dflags )
5257 {
5258 if ( (!player->client->ps.viewEntity || other->s.number != player->client->ps.viewEntity))
5259 {//hit by a teammate
5260 if ( other != self->enemy && self != other->enemy )
5261 {//we weren't already enemies
5262 if ( self->enemy || other->enemy || (other->s.number&&other->s.number!=player->client->ps.viewEntity) )
5263 {//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?)
5264 return;
5265 }
5266 else if ( self->NPC && !other->s.number )//should be assumed, but...
5267 {//dammit, stop that!
5268 if ( !(dflags&DAMAGE_RADIUS) )
5269 {
5270 //if it's radius damage, ignore it
5271 if ( self->NPC->ffireDebounce < level.time )
5272 {
5273 //FIXME: way something? NEED DIALOGUE
5274 self->NPC->ffireCount++;
5275 //Com_Printf( "incr: %d < %d\n", self->NPC->ffireCount, 3+((2-g_spskill->integer)*2) );
5276 self->NPC->ffireDebounce = level.time + 500;
5277 }
5278 }
5279 }
5280 }
5281 }
5282 }
5283
5284 float damageModifier[HL_MAX] =
5285 {
5286 1.0f, //HL_NONE,
5287 0.25f, //HL_FOOT_RT,
5288 0.25f, //HL_FOOT_LT,
5289 0.75f, //HL_LEG_RT,
5290 0.75f, //HL_LEG_LT,
5291 1.0f, //HL_WAIST,
5292 1.0f, //HL_BACK_RT,
5293 1.0f, //HL_BACK_LT,
5294 1.0f, //HL_BACK,
5295 1.0f, //HL_CHEST_RT,
5296 1.0f, //HL_CHEST_LT,
5297 1.0f, //HL_CHEST,
5298 0.5f, //HL_ARM_RT,
5299 0.5f, //HL_ARM_LT,
5300 0.25f, //HL_HAND_RT,
5301 0.25f, //HL_HAND_LT,
5302 2.0f, //HL_HEAD,
5303 1.0f, //HL_GENERIC1,
5304 1.0f, //HL_GENERIC2,
5305 1.0f, //HL_GENERIC3,
5306 1.0f, //HL_GENERIC4,
5307 1.0f, //HL_GENERIC5,
5308 1.0f, //HL_GENERIC6,
5309 };
5310
G_TrackWeaponUsage(gentity_t * self,gentity_t * inflictor,int add,int mod)5311 void G_TrackWeaponUsage( gentity_t *self, gentity_t *inflictor, int add, int mod )
5312 {
5313 if ( !self || !self->client || self->s.number )
5314 {//player only
5315 return;
5316 }
5317 int weapon = WP_NONE;
5318 //FIXME: need to check the MOD to find out what weapon (if *any*) actually did the killing
5319 if ( inflictor && !inflictor->client && mod != MOD_SABER && inflictor->lastEnemy && inflictor->lastEnemy != self )
5320 {//a missile that was reflected, ie: not owned by me originally
5321 if ( inflictor->owner == self && self->s.weapon == WP_SABER )
5322 {//we reflected it
5323 weapon = WP_SABER;
5324 }
5325 }
5326 if ( weapon == WP_NONE )
5327 {
5328 switch ( mod )
5329 {
5330 case MOD_SABER:
5331 weapon = WP_SABER;
5332 break;
5333 case MOD_BRYAR:
5334 case MOD_BRYAR_ALT:
5335 weapon = WP_BRYAR_PISTOL;
5336 break;
5337 case MOD_BLASTER:
5338 case MOD_BLASTER_ALT:
5339 weapon = WP_BLASTER;
5340 break;
5341 case MOD_DISRUPTOR:
5342 case MOD_SNIPER:
5343 weapon = WP_DISRUPTOR;
5344 break;
5345 case MOD_BOWCASTER:
5346 case MOD_BOWCASTER_ALT:
5347 weapon = WP_BOWCASTER;
5348 break;
5349 case MOD_REPEATER:
5350 case MOD_REPEATER_ALT:
5351 weapon = WP_REPEATER;
5352 break;
5353 case MOD_DEMP2:
5354 case MOD_DEMP2_ALT:
5355 weapon = WP_DEMP2;
5356 break;
5357 case MOD_FLECHETTE:
5358 case MOD_FLECHETTE_ALT:
5359 weapon = WP_FLECHETTE;
5360 break;
5361 case MOD_ROCKET:
5362 case MOD_ROCKET_ALT:
5363 weapon = WP_ROCKET_LAUNCHER;
5364 break;
5365 case MOD_CONC:
5366 case MOD_CONC_ALT:
5367 weapon = WP_CONCUSSION;
5368 break;
5369 case MOD_THERMAL:
5370 case MOD_THERMAL_ALT:
5371 weapon = WP_THERMAL;
5372 break;
5373 case MOD_DETPACK:
5374 weapon = WP_DET_PACK;
5375 break;
5376 case MOD_LASERTRIP:
5377 case MOD_LASERTRIP_ALT:
5378 weapon = WP_TRIP_MINE;
5379 break;
5380 case MOD_MELEE:
5381 if ( self->s.weapon == WP_STUN_BATON )
5382 {
5383 weapon = WP_STUN_BATON;
5384 }
5385 else if ( self->s.weapon == WP_MELEE )
5386 {
5387 weapon = WP_MELEE;
5388 }
5389 break;
5390 }
5391 }
5392 if ( weapon != WP_NONE )
5393 {
5394 self->client->sess.missionStats.weaponUsed[weapon] += add;
5395 }
5396 }
5397
G_NonLocationSpecificDamage(int meansOfDeath)5398 qboolean G_NonLocationSpecificDamage( int meansOfDeath )
5399 {
5400 if ( meansOfDeath == MOD_EXPLOSIVE
5401 || meansOfDeath == MOD_REPEATER_ALT
5402 || meansOfDeath == MOD_FLECHETTE_ALT
5403 || meansOfDeath == MOD_ROCKET
5404 || meansOfDeath == MOD_ROCKET_ALT
5405 || meansOfDeath == MOD_CONC
5406 || meansOfDeath == MOD_THERMAL
5407 || meansOfDeath == MOD_THERMAL_ALT
5408 || meansOfDeath == MOD_DETPACK
5409 || meansOfDeath == MOD_LASERTRIP
5410 || meansOfDeath == MOD_LASERTRIP_ALT
5411 || meansOfDeath == MOD_MELEE
5412 || meansOfDeath == MOD_FORCE_GRIP
5413 || meansOfDeath == MOD_KNOCKOUT
5414 || meansOfDeath == MOD_CRUSH
5415 || meansOfDeath == MOD_EXPLOSIVE_SPLASH )
5416 {
5417 return qtrue;
5418 }
5419 return qfalse;
5420 }
5421
G_ImmuneToGas(gentity_t * ent)5422 qboolean G_ImmuneToGas( gentity_t *ent )
5423 {
5424 if ( !ent || !ent->client )
5425 {//only effects living clients
5426 return qtrue;
5427 }
5428 if ( ent->s.weapon == WP_NOGHRI_STICK//assumes user is immune
5429 || ent->client->NPC_class == CLASS_HAZARD_TROOPER
5430 || ent->client->NPC_class == CLASS_ATST
5431 || ent->client->NPC_class == CLASS_GONK
5432 || ent->client->NPC_class == CLASS_SAND_CREATURE
5433 || ent->client->NPC_class == CLASS_INTERROGATOR
5434 || ent->client->NPC_class == CLASS_MARK1
5435 || ent->client->NPC_class == CLASS_MARK2
5436 || ent->client->NPC_class == CLASS_GALAKMECH
5437 || ent->client->NPC_class == CLASS_MOUSE
5438 || ent->client->NPC_class == CLASS_PROBE // droid
5439 || ent->client->NPC_class == CLASS_PROTOCOL // droid
5440 || ent->client->NPC_class == CLASS_R2D2 // droid
5441 || ent->client->NPC_class == CLASS_R5D2 // droid
5442 || ent->client->NPC_class == CLASS_REMOTE
5443 || ent->client->NPC_class == CLASS_SEEKER // droid
5444 || ent->client->NPC_class == CLASS_SENTRY
5445 || ent->client->NPC_class == CLASS_SWAMPTROOPER
5446 || ent->client->NPC_class == CLASS_TUSKEN
5447 || ent->client->NPC_class == CLASS_BOBAFETT
5448 || ent->client->NPC_class == CLASS_ROCKETTROOPER
5449 || ent->client->NPC_class == CLASS_SABER_DROID
5450 || ent->client->NPC_class == CLASS_ASSASSIN_DROID
5451 || ent->client->NPC_class == CLASS_HAZARD_TROOPER
5452 || ent->client->NPC_class == CLASS_VEHICLE )
5453 {
5454 return qtrue;
5455 }
5456 return qfalse;
5457 }
5458
5459 extern Vehicle_t *G_IsRidingVehicle( gentity_t *ent );
5460 extern void G_StartRoll( gentity_t *ent, int anim );
5461 extern void WP_ForcePowerStart( gentity_t *self, forcePowers_t forcePower, int overrideAmt );
5462
5463 /*
5464 ============
5465 T_Damage
5466
5467 targ entity that is being damaged
5468 inflictor entity that is causing the damage
5469 attacker entity that caused the inflictor to damage targ
5470 example: targ=monster, inflictor=rocket, attacker=player
5471
5472 dir direction of the attack for knockback
5473 point point at which the damage is being inflicted, used for headshots
5474 damage amount of damage being inflicted
5475 knockback force to be applied against targ as a result of the damage
5476
5477 inflictor, attacker, dir, and point can be NULL for environmental effects
5478
5479 dflags these flags are used to control how T_Damage works
5480 DAMAGE_RADIUS damage was indirect (from a nearby explosion)
5481 DAMAGE_NO_ARMOR armor does not protect from this damage
5482 DAMAGE_NO_KNOCKBACK do not affect velocity, just view angles
5483 DAMAGE_NO_PROTECTION kills godmode, armor, everything
5484 DAMAGE_NO_HIT_LOC Damage not based on hit location
5485 ============
5486 */
G_Damage(gentity_t * targ,gentity_t * inflictor,gentity_t * attacker,const vec3_t dir,const vec3_t point,int damage,int dflags,int mod,int hitLoc)5487 void G_Damage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker, const vec3_t dir, const vec3_t point, int damage, int dflags, int mod, int hitLoc )
5488 {
5489 gclient_t *client;
5490 int take;
5491 int asave = 0;
5492 int knockback;
5493 vec3_t newDir;
5494 qboolean alreadyDead = qfalse;
5495
5496 if (!targ->takedamage) {
5497 if ( targ->client //client
5498 && targ->client->NPC_class == CLASS_SAND_CREATURE//sand creature
5499 && targ->activator//something in our mouth
5500 && targ->activator == inflictor )//inflictor of damage is the thing in our mouth
5501 {//being damaged by the thing in our mouth, allow the damage
5502 }
5503 else
5504 {
5505 return;
5506 }
5507 }
5508
5509 if ( targ->health <= 0 && !targ->client )
5510 { // allow corpses to be disintegrated
5511 if( mod != MOD_SNIPER || (targ->flags & FL_DISINTEGRATED) )
5512 return;
5513 }
5514
5515 // if we are the player and we are locked to an emplaced gun, we have to reroute damage to the gun....sigh.
5516 if ( targ->s.eFlags & EF_LOCKED_TO_WEAPON
5517 && targ->s.number == 0
5518 && targ->owner
5519 && !targ->owner->bounceCount //not an EWeb
5520 && !( targ->owner->flags & FL_GODMODE ))
5521 {
5522 // swapping the gun into our place to absorb our damage
5523 targ = targ->owner;
5524 }
5525
5526 if ( (targ->flags&FL_SHIELDED) && mod != MOD_SABER && !targ->client)
5527 {//magnetically protected, this thing can only be damaged by lightsabers
5528 return;
5529 }
5530
5531 if ( (targ->flags&FL_DMG_BY_SABER_ONLY) && mod != MOD_SABER )
5532 {//can only be damaged by lightsabers (but no shield... yeah, it's redundant, but whattayagonnado?)
5533 return;
5534 }
5535
5536 if (( targ->flags & FL_DMG_BY_HEAVY_WEAP_ONLY ) && !( dflags & DAMAGE_HEAVY_WEAP_CLASS ))
5537 {
5538 // 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
5539 return;
5540 }
5541
5542 if ( (targ->svFlags&SVF_BBRUSH)
5543 || (!targ->client && Q_stricmp( "misc_model_breakable", targ->classname ) == 0 ) )//FIXME: flag misc_model_breakables?
5544 {//breakable brush or misc_model_breakable
5545 if ( targ->NPC_targetname )
5546 {//only a certain attacker can destroy this
5547 if ( !attacker
5548 || !attacker->targetname
5549 || Q_stricmp( targ->NPC_targetname, attacker->targetname ) != 0 )
5550 {//and it's not this one, do nothing
5551 return;
5552 }
5553 }
5554 }
5555
5556 if ( targ->client && targ->client->NPC_class == CLASS_ATST )
5557 {
5558 // extra checks can be done here
5559 if ( mod == MOD_SNIPER
5560 || mod == MOD_DISRUPTOR
5561 || mod == MOD_CONC_ALT )
5562 {
5563 // disruptor does not hurt an atst
5564 return;
5565 }
5566 }
5567 if ( targ->client
5568 && targ->client->NPC_class == CLASS_RANCOR
5569 && (!attacker||!attacker->client||attacker->client->NPC_class!=CLASS_RANCOR) )
5570 {
5571 // I guess always do 10 points of damage...feel free to tweak as needed
5572 if ( damage < 10 )
5573 {//ignore piddly little damage
5574 damage = 0;
5575 }
5576 else if ( damage >= 10 )
5577 {
5578 damage = 10;
5579 }
5580 }
5581 else if ( mod == MOD_SABER )
5582 {//sabers do less damage to mark1's and atst's, and to hazard troopers and assassin droids
5583 if ( targ->client )
5584 {
5585 if ( targ->client->NPC_class == CLASS_ATST
5586 || targ->client->NPC_class == CLASS_MARK1 )
5587 {
5588 // I guess always do 5 points of damage...feel free to tweak as needed
5589 if ( damage > 5 )
5590 {
5591 damage = 5;
5592 }
5593 }
5594 /*
5595 //NOTE: a more controlled way to do the class-specific saber immunities, if we want
5596 else if ( targ->client->NPC_class == CLASS_ASSASSIN_DROID )
5597 {//takes 2 hits to kill on easy, 3 on medium, 4 on hard
5598 int maxDamage = ceil((float)targ->max_health/(2.0f+g_spskill->value));
5599 if ( damage > maxDamage )
5600 {
5601 damage = maxDamage;
5602 }
5603 }
5604 else if ( targ->client->NPC_class == CLASS_HAZARD_TROOPER )
5605 {//takes 3 hits to kill on easy, 4 on medium, 5 on hard
5606 int maxDamage = ceil((float)targ->max_health/(3.0f+g_spskill->value));
5607 if ( damage > maxDamage )
5608 {
5609 damage = maxDamage;
5610 }
5611 }
5612 */
5613 }
5614 }
5615
5616 if ( !inflictor ) {
5617 inflictor = &g_entities[ENTITYNUM_WORLD];
5618 }
5619 if ( !attacker ) {
5620 attacker = &g_entities[ENTITYNUM_WORLD];
5621 }
5622
5623 // no more weakling allies!
5624 // if ( attacker->s.number != 0 && damage >= 2 && targ->s.number != 0 && attacker->client && attacker->client->playerTeam == TEAM_PLAYER )
5625 // {//player-helpers do only half damage to enemies
5626 // damage = ceil((float)damage/2.0f);
5627 // }
5628
5629 client = targ->client;
5630
5631 if ( client ) {
5632 if ( client->noclip && !targ->s.number ) {
5633 return;
5634 }
5635 }
5636
5637 if ( mod == MOD_GAS )
5638 {//certain NPCs are immune
5639 if ( G_ImmuneToGas( targ ) )
5640 {//immune
5641 return;
5642 }
5643 dflags |= DAMAGE_NO_ARMOR;
5644 }
5645 if ( dflags&DAMAGE_NO_DAMAGE )
5646 {
5647 damage = 0;
5648 }
5649
5650 if ( dir == NULL )
5651 {
5652 dflags |= DAMAGE_NO_KNOCKBACK;
5653 }
5654 else
5655 {
5656 VectorNormalize2( dir, newDir );
5657 }
5658
5659 if ( targ->s.number != 0 )
5660 {//not the player
5661 if ( (targ->flags&FL_GODMODE) || (targ->flags&FL_UNDYING) )
5662 {//have god or undying on, so ignore no protection flag
5663 dflags &= ~DAMAGE_NO_PROTECTION;
5664 }
5665 }
5666
5667 if ( client && PM_InOnGroundAnim( &client->ps ))
5668 {
5669 dflags |= DAMAGE_NO_KNOCKBACK;
5670 }
5671 if ( !attacker->s.number && targ->client && attacker->client && targ->client->playerTeam == attacker->client->playerTeam )
5672 {//player doesn't do knockback against allies unless he kills them
5673 dflags |= DAMAGE_DEATH_KNOCKBACK;
5674 }
5675
5676 if (targ->client && (mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT) )
5677 {
5678 TIMER_Set(targ, "DEMP2_StunTime", Q_irand(1000, 2000));
5679 }
5680
5681 if ((client) &&
5682 (mod==MOD_DEMP2 || mod==MOD_DEMP2_ALT) &&
5683 (
5684 client->NPC_class == CLASS_SABER_DROID ||
5685 client->NPC_class == CLASS_ASSASSIN_DROID ||
5686 client->NPC_class == CLASS_GONK ||
5687 client->NPC_class == CLASS_MOUSE ||
5688 client->NPC_class == CLASS_PROBE ||
5689 client->NPC_class == CLASS_PROTOCOL ||
5690 client->NPC_class == CLASS_R2D2 ||
5691 client->NPC_class == CLASS_R5D2 ||
5692 client->NPC_class == CLASS_SEEKER ||
5693 client->NPC_class == CLASS_INTERROGATOR
5694 )
5695 )
5696 {
5697 damage *= 7;
5698 }
5699
5700 if ( client && client->NPC_class == CLASS_HAZARD_TROOPER )
5701 {
5702 if ( mod == MOD_SABER
5703 && damage>0
5704 && !(dflags&DAMAGE_NO_PROTECTION) )
5705 {
5706 damage /= 10;
5707 }
5708 }
5709
5710 if ( client
5711 && client->NPC_class == CLASS_GALAKMECH
5712 && !(dflags&DAMAGE_NO_PROTECTION) )
5713 {//hit Galak
5714 if ( client->ps.stats[STAT_ARMOR] > 0 )
5715 {//shields are up
5716 dflags &= ~DAMAGE_NO_ARMOR;//always affect armor
5717 if ( mod == MOD_ELECTROCUTE
5718 || mod == MOD_DEMP2
5719 || mod == MOD_DEMP2_ALT )
5720 {//shield protects us from this
5721 damage = 0;
5722 }
5723 }
5724 else
5725 {//shields down
5726 if ( mod == MOD_MELEE
5727 || (mod == MOD_CRUSH && attacker && attacker->client) )
5728 {//Galak takes no impact damage
5729 return;
5730 }
5731 if ( (dflags & DAMAGE_RADIUS)
5732 || mod == MOD_REPEATER_ALT
5733 || mod == MOD_FLECHETTE_ALT
5734 || mod == MOD_ROCKET
5735 || mod == MOD_ROCKET_ALT
5736 || mod == MOD_CONC
5737 || mod == MOD_THERMAL
5738 || mod == MOD_THERMAL_ALT
5739 || mod == MOD_DETPACK
5740 || mod == MOD_LASERTRIP
5741 || mod == MOD_LASERTRIP_ALT
5742 || mod == MOD_EXPLOSIVE_SPLASH
5743 || mod == MOD_ENERGY_SPLASH
5744 || mod == MOD_SABER )
5745 {//galak without shields takes quarter damage from explosives and lightsaber
5746 damage = ceil((float)damage/4.0f);
5747 }
5748 }
5749 }
5750
5751 if ( mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT )
5752 {
5753 if ( client )
5754 {
5755 if ( client->NPC_class == CLASS_PROTOCOL || client->NPC_class == CLASS_SEEKER ||
5756 client->NPC_class == CLASS_R2D2 || client->NPC_class == CLASS_R5D2 ||
5757 client->NPC_class == CLASS_MOUSE || client->NPC_class == CLASS_GONK )
5758 {
5759 // DEMP2 does more damage to these guys.
5760 damage *= 2;
5761 }
5762 else if ( client->NPC_class == CLASS_PROBE || client->NPC_class == CLASS_INTERROGATOR ||
5763 client->NPC_class == CLASS_MARK1 || client->NPC_class == CLASS_MARK2 || client->NPC_class == CLASS_SENTRY ||
5764 client->NPC_class == CLASS_ATST )
5765 {
5766 // DEMP2 does way more damage to these guys.
5767 damage *= 5;
5768 }
5769 }
5770 else if ( targ->s.weapon == WP_TURRET )
5771 {
5772 damage *= 6;// more damage to turret things
5773 }
5774 }
5775
5776 if (targ
5777 && targ->client
5778 && !(dflags&DAMAGE_NO_PROTECTION)
5779 && !(dflags&DAMAGE_DIE_ON_IMPACT) )//falling to you death ignores force protect and force rage (but obeys godmode and undying flags)
5780 {//force protections
5781 //rage
5782 if ( (targ->client->ps.forcePowersActive & (1 << FP_RAGE)))
5783 {
5784 damage = floor((float)damage/(float)(targ->client->ps.forcePowerLevel[FP_RAGE]*2));
5785 }
5786 //protect
5787 if ( (targ->client->ps.forcePowersActive & (1 << FP_PROTECT)) )
5788 {
5789 /*
5790 qboolean doSound = qfalse;
5791 switch ( targ->client->ps.forcePowerLevel[FP_PROTECT] )
5792 {
5793 case FORCE_LEVEL_3:
5794 //NOTE: purposely falls through
5795 switch ( mod )
5796 {
5797 case MOD_REPEATER_ALT:
5798 case MOD_FLECHETTE_ALT:
5799 case MOD_ROCKET:
5800 case MOD_ROCKET_ALT:
5801 case MOD_CONC:
5802 case MOD_THERMAL:
5803 case MOD_THERMAL_ALT:
5804 case MOD_DETPACK:
5805 case MOD_LASERTRIP:
5806 case MOD_LASERTRIP_ALT:
5807 case MOD_EMPLACED:
5808 case MOD_EXPLOSIVE:
5809 case MOD_EXPLOSIVE_SPLASH:
5810 case MOD_CRUSH:
5811 doSound = (Q_irand(0,4)==0);
5812 damage = floor((float)damage/(float)(targ->client->ps.forcePowerLevel[FP_PROTECT]-1));
5813 break;
5814 }
5815 case FORCE_LEVEL_2:
5816 //NOTE: purposely falls through
5817 switch ( mod )
5818 {
5819 case MOD_SABER:
5820 case MOD_DISRUPTOR:
5821 case MOD_SNIPER:
5822 case MOD_CONC_ALT:
5823 case MOD_BOWCASTER:
5824 case MOD_BOWCASTER_ALT:
5825 case MOD_DEMP2:
5826 case MOD_DEMP2_ALT:
5827 case MOD_ENERGY:
5828 case MOD_ENERGY_SPLASH:
5829 case MOD_ELECTROCUTE:
5830 doSound = (Q_irand(0,4)==0);
5831 damage = floor((float)damage/(float)(targ->client->ps.forcePowerLevel[FP_PROTECT]));
5832 break;
5833 }
5834 case FORCE_LEVEL_1:
5835 switch ( mod )
5836 {
5837 case MOD_BRYAR:
5838 case MOD_BRYAR_ALT:
5839 case MOD_BLASTER:
5840 case MOD_BLASTER_ALT:
5841 case MOD_REPEATER:
5842 case MOD_FLECHETTE:
5843 case MOD_WATER:
5844 case MOD_SLIME:
5845 case MOD_LAVA:
5846 case MOD_FALLING:
5847 doSound = (Q_irand(0,4)==0);
5848 damage = floor((float)damage/(float)(targ->client->ps.forcePowerLevel[FP_PROTECT]+1));
5849 break;
5850 }
5851 break;
5852 }
5853 */
5854 //New way: just cut all physical damage by force level
5855 if ( mod == MOD_FALLING
5856 && targ->NPC
5857 && (targ->NPC->aiFlags&NPCAI_DIE_ON_IMPACT) )
5858 {//if falling to your death, protect can't save you
5859 }
5860 else
5861 {
5862 qboolean doSound = qfalse;
5863 switch ( mod )
5864 {
5865 case MOD_CRUSH:
5866 if ( attacker && attacker->client )
5867 {//need to still be crushed by AT-ST
5868 break;
5869 }
5870 case MOD_REPEATER_ALT:
5871 case MOD_FLECHETTE_ALT:
5872 case MOD_ROCKET:
5873 case MOD_ROCKET_ALT:
5874 case MOD_CONC:
5875 case MOD_THERMAL:
5876 case MOD_THERMAL_ALT:
5877 case MOD_DETPACK:
5878 case MOD_LASERTRIP:
5879 case MOD_LASERTRIP_ALT:
5880 case MOD_EMPLACED:
5881 case MOD_EXPLOSIVE:
5882 case MOD_EXPLOSIVE_SPLASH:
5883 case MOD_SABER:
5884 case MOD_DISRUPTOR:
5885 case MOD_SNIPER:
5886 case MOD_CONC_ALT:
5887 case MOD_BOWCASTER:
5888 case MOD_BOWCASTER_ALT:
5889 case MOD_DEMP2:
5890 case MOD_DEMP2_ALT:
5891 case MOD_ENERGY:
5892 case MOD_ENERGY_SPLASH:
5893 case MOD_ELECTROCUTE:
5894 case MOD_BRYAR:
5895 case MOD_BRYAR_ALT:
5896 case MOD_BLASTER:
5897 case MOD_BLASTER_ALT:
5898 case MOD_REPEATER:
5899 case MOD_FLECHETTE:
5900 case MOD_WATER:
5901 case MOD_SLIME:
5902 case MOD_LAVA:
5903 case MOD_FALLING:
5904 case MOD_MELEE:
5905 doSound = (qboolean)(Q_irand(0,4)==0);
5906 switch ( targ->client->ps.forcePowerLevel[FP_PROTECT] )
5907 {
5908 case FORCE_LEVEL_4:
5909 //je suis invincible!!!
5910 if ( targ->client
5911 && attacker->client
5912 && targ->client->playerTeam == attacker->client->playerTeam
5913 && (!targ->NPC || !targ->NPC->charmedTime) )
5914 {//complain, but don't turn on them
5915 G_FriendlyFireReaction( targ, attacker, dflags );
5916 }
5917 return;
5918 break;
5919 case FORCE_LEVEL_3:
5920 //one-tenth damage
5921 if ( damage <= 1 )
5922 {
5923 damage = 0;
5924 }
5925 else
5926 {
5927 damage = ceil((float)damage*0.25f);//was 0.1f);
5928 }
5929 break;
5930 case FORCE_LEVEL_2:
5931 //half damage
5932 if ( damage <= 1 )
5933 {
5934 damage = 0;
5935 }
5936 else
5937 {
5938 damage = ceil((float)damage*0.5f);
5939 }
5940 break;
5941 case FORCE_LEVEL_1:
5942 //three-quarters damage
5943 if ( damage <= 1 )
5944 {
5945 damage = 0;
5946 }
5947 else
5948 {
5949 damage = ceil((float)damage*0.75f);
5950 }
5951 break;
5952 }
5953 break;
5954 }
5955 if ( doSound )
5956 {
5957 //make protect sound
5958 G_SoundOnEnt( targ, CHAN_ITEM, "sound/weapons/force/protecthit.wav" );
5959 }
5960 }
5961 }
5962 //absorb
5963 /*
5964 if ( (targ->client->ps.forcePowersActive & (1 << FP_ABSORB)) )
5965 {
5966 if ( mod == MOD_FORCE_LIGHTNING
5967 || mod == MOD_FORCE_GRIP
5968 || mod == MOD_FORCE_DRAIN )
5969 {
5970 int absorbed = targ->client->ps.forcePowerLevel[FP_ABSORB]*5;
5971 damage -= absorbed;
5972 if ( damage < 0 )
5973 {
5974 absorbed += damage;
5975 damage = 0;
5976 }
5977 //absorb the energy
5978 //make absorb sound
5979 G_SoundOnEnt( targ, CHAN_ITEM, "sound/weapons/force/absorbhit.wav" );
5980 targ->client->ps.forcePower += absorbed;
5981 }
5982 }
5983 */
5984 }
5985
5986 knockback = damage;
5987
5988 //Attempt to apply extra knockback
5989 if ( dflags & DAMAGE_EXTRA_KNOCKBACK )
5990 {
5991 knockback *= 2;
5992 }
5993
5994 if ( knockback > 200 ) {
5995 knockback = 200;
5996 }
5997
5998 if ( targ->client
5999 && (targ->client->ps.forcePowersActive&(1<<FP_PROTECT))
6000 && targ->client->ps.forcePowerLevel[FP_PROTECT] == FORCE_LEVEL_3 )
6001 {//pretend there was no damage?
6002 knockback = 0;
6003 }
6004 else if ( mod == MOD_CRUSH )
6005 {
6006 knockback = 0;
6007 }
6008 else if ( targ->flags & FL_NO_KNOCKBACK )
6009 {
6010 knockback = 0;
6011 }
6012 else if ( targ->NPC
6013 && targ->NPC->jumpState == JS_JUMPING )
6014 {
6015 knockback = 0;
6016 }
6017 else if ( attacker->s.number >= MAX_CLIENTS//an NPC fired
6018 && targ->client //hit a client
6019 && attacker->client //attacker is a client
6020 && targ->client->playerTeam == attacker->client->playerTeam )//on same team
6021 {//crap, ignore knockback
6022 knockback = 0;
6023 }
6024 else if ( dflags & DAMAGE_NO_KNOCKBACK )
6025 {
6026 knockback = 0;
6027 }
6028
6029 if ( (dflags&DAMAGE_SABER_KNOCKBACK1) )
6030 {
6031 if ( attacker && attacker->client )
6032 {
6033 knockback *= attacker->client->ps.saber[0].knockbackScale;
6034 }
6035 }
6036 if ( (dflags&DAMAGE_SABER_KNOCKBACK1_B2) )
6037 {
6038 if ( attacker && attacker->client )
6039 {
6040 knockback *= attacker->client->ps.saber[0].knockbackScale2;
6041 }
6042 }
6043 if ( (dflags&DAMAGE_SABER_KNOCKBACK2) )
6044 {
6045 if ( attacker && attacker->client )
6046 {
6047 knockback *= attacker->client->ps.saber[1].knockbackScale;
6048 }
6049 }
6050 if ( (dflags&DAMAGE_SABER_KNOCKBACK2_B2) )
6051 {
6052 if ( attacker && attacker->client )
6053 {
6054 knockback *= attacker->client->ps.saber[1].knockbackScale2;
6055 }
6056 }
6057 // figure momentum add, even if the damage won't be taken
6058 if ( knockback && !(dflags&DAMAGE_DEATH_KNOCKBACK) ) //&& targ->client
6059 {
6060 G_ApplyKnockback( targ, newDir, knockback );
6061 G_CheckKnockdown( targ, attacker, newDir, dflags, mod );
6062 }
6063
6064 // check for godmode, completely getting out of the damage
6065 if ( ( (targ->flags&FL_GODMODE) || (targ->client&&targ->client->ps.powerups[PW_INVINCIBLE]>level.time) )
6066 && !(dflags&DAMAGE_NO_PROTECTION) )
6067 {
6068 if ( targ->client
6069 && attacker->client
6070 && targ->client->playerTeam == attacker->client->playerTeam
6071 && (!targ->NPC || !targ->NPC->charmedTime) )
6072 {//complain, but don't turn on them
6073 G_FriendlyFireReaction( targ, attacker, dflags );
6074 }
6075 return;
6076 }
6077
6078 // Check for team damage
6079 /*
6080 if ( targ != attacker && !(dflags&DAMAGE_IGNORE_TEAM) && OnSameTeam (targ, attacker) )
6081 {//on same team
6082 if ( !targ->client )
6083 {//a non-player object should never take damage from an ent on the same team
6084 return;
6085 }
6086
6087 if ( attacker->client && attacker->client->playerTeam == targ->noDamageTeam )
6088 {//NPC or player shot an object on his own team
6089 return;
6090 }
6091
6092 if ( attacker->s.number != 0 && targ->s.number != 0 &&//player not involved in any way in this exchange
6093 attacker->client && targ->client &&//two NPCs
6094 attacker->client->playerTeam == targ->client->playerTeam ) //on the same team
6095 {//NPCs on same team don't hurt each other
6096 return;
6097 }
6098
6099 if ( targ->s.number == 0 &&//player was hit
6100 attacker->client && targ->client &&//by an NPC
6101 attacker->client->playerTeam == TEAM_PLAYER ) //on the same team
6102 {
6103 if ( attacker->enemy != targ )//by accident
6104 {//do no damage, no armor loss, no reaction, run no scripts
6105 return;
6106 }
6107 }
6108 }
6109 */
6110
6111 // add to the attacker's hit counter
6112 if ( attacker->client && targ != attacker && targ->health > 0 ) {
6113 if ( OnSameTeam( targ, attacker ) ) {
6114 // attacker->client->ps.persistant[PERS_HITS] -= damage;
6115 } else {
6116 // attacker->client->ps.persistant[PERS_HITS] += damage;
6117 }
6118 }
6119
6120 take = damage;
6121
6122 //FIXME: Do not use this method of difficulty changing
6123 // Scale the amount of damage given to the player based on the skill setting
6124 /*
6125 if ( targ->s.number == 0 && targ != attacker )
6126 {
6127 take *= ( g_spskill->integer + 1) * 0.75;
6128 }
6129
6130 if ( take < 1 ) {
6131 take = 1;
6132 }
6133 */
6134 if ( client )
6135 {
6136 //don't lose armor if on same team
6137 // save some from armor
6138 asave = CheckArmor (targ, take, dflags, mod);
6139 if ( !asave )
6140 {//nothing was absorbed (or just ran out?)
6141 }
6142 else if ( targ->client->NPC_class != CLASS_VEHICLE )
6143 {//vehicles don't have personal shields
6144 targ->client->ps.powerups[PW_BATTLESUIT] = level.time + ARMOR_EFFECT_TIME;
6145 if ( targ->client->ps.stats[STAT_ARMOR] <= 0 )
6146 {//all out of armor
6147 //remove Galak's shield
6148 targ->client->ps.powerups[PW_BATTLESUIT] = 0;
6149 }
6150 }
6151
6152 if (mod==MOD_SLIME || mod==MOD_LAVA)
6153 {
6154 // Hazard Troopers Don't Mind Acid Rain
6155 if (targ->client->NPC_class == CLASS_HAZARD_TROOPER
6156 && !(dflags&DAMAGE_NO_PROTECTION) )
6157 {
6158 take = 0;
6159 }
6160
6161 if (mod==MOD_SLIME)
6162 {
6163 trace_t testTrace;
6164 vec3_t testDirection;
6165 vec3_t testStartPos;
6166 vec3_t testEndPos;
6167 //int numPuffs = Q_irand(1, 2);
6168
6169 //for (int i=0; i<numPuffs; i++)
6170 {
6171 testDirection[0] = (Q_flrand(0.0f, 1.0f) * 0.5f) - 0.25f;
6172 testDirection[1] = (Q_flrand(0.0f, 1.0f) * 0.5f) - 0.25f;
6173 testDirection[2] = 1.0f;
6174 VectorMA(targ->currentOrigin, 60.0f, testDirection, testStartPos);
6175 VectorCopy(targ->currentOrigin, testEndPos);
6176 testEndPos[0] += (Q_flrand(0.0f, 1.0f) * 8.0f) - 4.0f;
6177 testEndPos[1] += (Q_flrand(0.0f, 1.0f) * 8.0f) - 4.0f;
6178 testEndPos[2] += (Q_flrand(0.0f, 1.0f) * 8.0f);
6179
6180 gi.trace (&testTrace, testStartPos, NULL, NULL, testEndPos, ENTITYNUM_NONE, MASK_SHOT, G2_COLLIDE, 0);
6181
6182 if (!testTrace.startsolid &&
6183 !testTrace.allsolid &&
6184 testTrace.entityNum==targ->s.number &&
6185 testTrace.G2CollisionMap[0].mEntityNum!=-1)
6186 {
6187 G_PlayEffect( "world/acid_fizz", testTrace.G2CollisionMap[0].mCollisionPosition );
6188 }
6189 // CG_DrawEdge(testStartPos, testEndPos, EDGE_IMPACT_POSSIBLE);
6190 float chanceOfFizz = gi.WE_GetChanceOfSaberFizz();
6191 TIMER_Set(targ, "AcidPainDebounce", 200 + (10000.0f * Q_flrand(0.0f, 1.0f) * chanceOfFizz));
6192 hitLoc = HL_CHEST;
6193 }
6194 }
6195 }
6196
6197 take -= asave;
6198
6199 if ( targ->client->NPC_class == CLASS_VEHICLE )
6200 {
6201 if ( targ->m_pVehicle->m_pVehicleInfo->type == VH_ANIMAL )
6202 {
6203 //((CVehicleNPC *)targ->NPC)->m_ulFlags |= CVehicleNPC::VEH_BUCKING;
6204 }
6205
6206 if ( (damage > 0) && // Actually took some damage
6207 (mod!=MOD_SABER) && // and damage didn't come from a saber
6208 (targ->m_pVehicle->m_pVehicleInfo->type==VH_SPEEDER) && // and is a speeder
6209 // (targ->client->ps.speed > 30.0f) && // and is moving
6210 (attacker) && // and there is an attacker
6211 (attacker->client) && // who is a client
6212 (attacker->s.number<MAX_CLIENTS) && // who is the player
6213 (G_IsRidingVehicle(attacker)) // who is riding a bike
6214 )
6215 {
6216 vec3_t vehFwd;
6217 vec3_t actorFwd;
6218 AngleVectors(targ->currentAngles, actorFwd, 0, 0);
6219
6220
6221 Vehicle_t* pVeh = G_IsRidingVehicle(attacker);
6222 VectorCopy(pVeh->m_pParentEntity->client->ps.velocity, vehFwd);
6223 VectorNormalize(vehFwd);
6224
6225 if (DotProduct(vehFwd, actorFwd)>0.5)
6226 {
6227 damage *= 10.0f;
6228 }
6229 }
6230
6231 if ( (damage > 0) && // Actually took some damage
6232 (mod==MOD_SABER) && // If Attacked By A Saber
6233 (targ->m_pVehicle->m_pVehicleInfo->type==VH_SPEEDER) && // and is a speeder
6234 !(targ->m_pVehicle->m_ulFlags & VEH_OUTOFCONTROL) && // and is not already spinning
6235 (targ->client->ps.speed > 30.0f) && // and is moving
6236 (attacker==inflictor || Q_irand(0, 30)==0) // and EITHER saber is held, or 1 in 30 chance of hitting when thrown
6237 )
6238 {
6239 Vehicle_t* pVeh = targ->m_pVehicle;
6240 gentity_t* parent = pVeh->m_pParentEntity;
6241 float CurSpeed = VectorLength(parent->client->ps.velocity);
6242 pVeh->m_iArmor = 0; // Remove all remaining Armor
6243 pVeh->m_pVehicleInfo->StartDeathDelay(pVeh, 10000);
6244 pVeh->m_ulFlags |= (VEH_OUTOFCONTROL|VEH_SPINNING);
6245 VectorScale(parent->client->ps.velocity, 1.25f, parent->pos3);
6246 if (CurSpeed<pVeh->m_pVehicleInfo->speedMax)
6247 {
6248 VectorNormalize(parent->pos3);
6249 if (CurSpeed<pVeh->m_pVehicleInfo->speedMax)
6250 {
6251 VectorNormalize(parent->pos3);
6252 if (fabsf(parent->pos3[2])<0.25f)
6253 {
6254 VectorScale(parent->pos3, (pVeh->m_pVehicleInfo->speedMax * 1.25f), parent->pos3);
6255 }
6256 else
6257 {
6258 VectorScale(parent->client->ps.velocity, 1.25f, parent->pos3);
6259 }
6260 }
6261 }
6262
6263
6264 // TODO: Play Huge Spark Effect & Start Rolling Sound
6265 if (attacker==inflictor && (!G_IsRidingVehicle(attacker) || Q_irand(0, 3)==0))
6266 {
6267 attacker->lastEnemy = targ;
6268 G_StartMatrixEffect(attacker, MEF_LOOK_AT_ENEMY|MEF_NO_RANGEVAR|MEF_NO_VERTBOB|MEF_NO_SPIN, 1000);
6269 if (!G_IsRidingVehicle(attacker))
6270 {
6271 G_StartRoll(attacker, (Q_irand(0,1)==0)?(BOTH_ROLL_L):(BOTH_ROLL_R));
6272 }
6273 }
6274
6275 if (targ->m_pVehicle->m_pPilot && targ->m_pVehicle->m_pPilot->s.number>=MAX_CLIENTS)
6276 {
6277 G_SoundOnEnt(targ->m_pVehicle->m_pPilot, CHAN_VOICE, "*falling1.wav" );
6278 }
6279
6280
6281
6282 // DISMEMBER THE FRONT PART OF THE MODEL
6283 {
6284 trace_t trace;
6285
6286 gentity_t *limb = G_Spawn();
6287
6288
6289 // Setup Basic Limb Entity Properties
6290 //------------------------------------
6291 limb->s.radius = 60;
6292 limb->s.eType = ET_THINKER;
6293 limb->s.eFlags |= EF_BOUNCE_HALF;
6294 limb->classname = "limb";
6295 limb->owner = targ;
6296 limb->enemy = targ->enemy;
6297 limb->svFlags = SVF_USE_CURRENT_ORIGIN;
6298 limb->playerModel = 0;
6299 limb->clipmask = MASK_SOLID;
6300 limb->contents = CONTENTS_CORPSE;
6301 limb->e_clThinkFunc = clThinkF_CG_Limb;
6302 limb->e_ThinkFunc = thinkF_LimbThink;
6303 limb->nextthink = level.time + FRAMETIME;
6304 limb->physicsBounce = 0.2f;
6305 limb->craniumBone = targ->craniumBone;
6306 limb->cervicalBone = targ->cervicalBone;
6307 limb->thoracicBone = targ->thoracicBone;
6308 limb->upperLumbarBone = targ->upperLumbarBone;
6309 limb->lowerLumbarBone = targ->lowerLumbarBone;
6310 limb->hipsBone = targ->hipsBone;
6311 limb->rootBone = targ->rootBone;
6312
6313
6314 // Calculate The Location Of The New Limb
6315 //----------------------------------------
6316 G_SetOrigin( limb, targ->currentOrigin );
6317
6318 VectorCopy( targ->currentOrigin, limb->s.pos.trBase );
6319 VectorSet( limb->mins, -3.0f, -3.0f, -6.0f );
6320 VectorSet( limb->maxs, 3.0f, 3.0f, 6.0f );
6321 VectorCopy( targ->s.modelScale, limb->s.modelScale );
6322
6323
6324
6325
6326 //copy the g2 instance of the victim into the limb
6327 //-------------------------------------------------
6328 gi.G2API_CopyGhoul2Instance(targ->ghoul2, limb->ghoul2, -1);
6329 gi.G2API_SetRootSurface(limb->ghoul2, limb->playerModel, "lfront");
6330 gi.G2API_SetSurfaceOnOff(&targ->ghoul2[targ->playerModel], "lfront", TURN_OFF);
6331 animation_t *animations = level.knownAnimFileSets[targ->client->clientInfo.animFileIndex].animations;
6332
6333 //play the proper dismember anim on the limb
6334 gi.G2API_SetBoneAnim(&limb->ghoul2[limb->playerModel], 0, animations[BOTH_A1_BL_TR].firstFrame,
6335 animations[BOTH_A1_BL_TR].numFrames + animations[BOTH_A1_BL_TR].firstFrame,
6336 BONE_ANIM_OVERRIDE_FREEZE, 1, level.time, -1, -1 );
6337
6338
6339 // Check For Start In Solid
6340 //--------------------------
6341 gi.linkentity( limb );
6342 gi.trace( &trace, limb->s.pos.trBase, limb->mins, limb->maxs, limb->s.pos.trBase, limb->s.number, limb->clipmask, (EG2_Collision)0, 0 );
6343 if ( trace.startsolid )
6344 {
6345 limb->s.pos.trBase[2] -= limb->mins[2];
6346 gi.trace( &trace, limb->s.pos.trBase, limb->mins, limb->maxs, limb->s.pos.trBase, limb->s.number, limb->clipmask, (EG2_Collision)0, 0 );
6347 if ( trace.startsolid )
6348 {
6349 limb->s.pos.trBase[2] += limb->mins[2];
6350 gi.trace( &trace, limb->s.pos.trBase, limb->mins, limb->maxs, limb->s.pos.trBase, limb->s.number, limb->clipmask, (EG2_Collision)0, 0 );
6351
6352 }
6353 }
6354
6355 // If Started In Solid, Remove
6356 //-----------------------------
6357 if ( trace.startsolid )
6358 {
6359 G_FreeEntity( limb );
6360 }
6361
6362 // Otherwise, Send It Flying
6363 //---------------------------
6364 else
6365 {
6366 VectorCopy( limb->s.pos.trBase, limb->currentOrigin );
6367 VectorScale( targ->client->ps.velocity, 1.0f, limb->s.pos.trDelta );
6368 limb->s.pos.trType = TR_GRAVITY;
6369 limb->s.pos.trTime = level.time;
6370
6371 VectorCopy( targ->currentAngles, limb->s.apos.trBase );
6372 VectorClear( limb->s.apos.trDelta );
6373 limb->s.apos.trTime = level.time;
6374 limb->s.apos.trType = TR_LINEAR;
6375 limb->s.apos.trDelta[0] = Q_irand( -300, 300 );
6376 limb->s.apos.trDelta[1] = Q_irand( -800, 800 );
6377
6378 gi.linkentity( limb );
6379 }
6380 }
6381 }
6382
6383 targ->m_pVehicle->m_iShields = targ->client->ps.stats[STAT_ARMOR];
6384 targ->m_pVehicle->m_iArmor -= take;
6385 if ( targ->m_pVehicle->m_iArmor < 0 )
6386 {
6387 targ->m_pVehicle->m_iArmor = 0;
6388 }
6389 if ( ( targ->m_pVehicle->m_iArmor <= 0 )
6390 && targ->m_pVehicle->m_pVehicleInfo->type != VH_ANIMAL )
6391 {//vehicle all out of armor
6392 Vehicle_t *pVeh = targ->m_pVehicle;
6393 if (dflags&DAMAGE_IMPACT_DIE)
6394 {
6395 // kill it now
6396 pVeh->m_pVehicleInfo->StartDeathDelay( pVeh, -1/* -1 causes instant death */ );
6397 }
6398 else
6399 {
6400 if ( pVeh->m_iDieTime == 0 )
6401 {//just start the flaming effect and explosion delay, if it's not going already...
6402 pVeh->m_pVehicleInfo->StartDeathDelay( pVeh, Q_irand( 4000, 5500 ) );
6403 }
6404 }
6405 }
6406 else if (targ->m_pVehicle->m_pVehicleInfo->type != VH_ANIMAL)
6407 {
6408 take = 0;
6409 }
6410 }
6411 }
6412 if ( !(dflags&DAMAGE_NO_HIT_LOC) || !(dflags&DAMAGE_RADIUS))
6413 {
6414 if ( !G_NonLocationSpecificDamage( mod ) )
6415 {//certain kinds of damage don't care about hitlocation
6416 take = ceil( (float)take*damageModifier[hitLoc] );
6417 }
6418 }
6419
6420 if ( g_debugDamage->integer ) {
6421 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] );
6422 }
6423
6424 // add to the damage inflicted on a player this frame
6425 // the total will be turned into screen blends and view angle kicks
6426 // at the end of the frame
6427 if ( client ) {
6428 client->ps.persistant[PERS_ATTACKER] = attacker->s.number; //attack can be the world ent
6429 client->damage_armor += asave;
6430 client->damage_blood += take;
6431 if ( dir ) { //can't check newdir since it's local, newdir is dir normalized
6432 VectorCopy ( newDir, client->damage_from );
6433 client->damage_fromWorld = false;
6434 } else {
6435 VectorCopy ( targ->currentOrigin, client->damage_from );
6436 client->damage_fromWorld = true;
6437 }
6438 }
6439
6440 // do the damage
6441 if ( targ->health <= 0 )
6442 {
6443 alreadyDead = qtrue;
6444 }
6445
6446 // Undying If:
6447 //--------------------------------------------------------------------------
6448 qboolean targUndying = (qboolean)(
6449 !alreadyDead &&
6450 !(dflags & DAMAGE_NO_PROTECTION) &&
6451 ((targ->flags&FL_UNDYING) ||
6452 (dflags&DAMAGE_NO_KILL) ||
6453 ((targ->client) &&
6454 (targ->client->ps.forcePowersActive & (1 << FP_RAGE)) &&
6455 !(dflags&DAMAGE_NO_PROTECTION) &&
6456 !(dflags&DAMAGE_DIE_ON_IMPACT))));
6457
6458 if ( targ->client
6459 && targ->client->NPC_class == CLASS_WAMPA
6460 && targ->count
6461 && take >= targ->health )
6462 {//wampa holding someone, don't die unless you can release them!
6463 qboolean removeArm = qfalse;
6464 if ( targ->activator
6465 && attacker == targ->activator
6466 && mod == MOD_SABER )
6467 {
6468 removeArm = qtrue;
6469 }
6470 if ( Wampa_CheckDropVictim( targ, qtrue ) )
6471 {//released our victim
6472 if ( removeArm )
6473 {
6474 targ->client->dismembered = false;
6475 //FIXME: the limb should just disappear, cuz I ate it
6476 G_DoDismemberment( targ, targ->currentOrigin, MOD_SABER, 1000, HL_ARM_RT, qtrue );
6477 }
6478 }
6479 else
6480 {//couldn't release him
6481 targUndying = qtrue;
6482 }
6483 }
6484
6485 if ( attacker && attacker->client && !attacker->s.number )
6486 {
6487 if ( !alreadyDead )
6488 {
6489 int add;
6490 if ( take > targ->health )
6491 {
6492 add = targ->health;
6493 }
6494 else
6495 {
6496 add = take;
6497 }
6498 add += asave;
6499 add = ceil(add/10.0f);
6500 if ( attacker != targ )
6501 {
6502 G_TrackWeaponUsage( attacker, inflictor, add, mod );
6503 }
6504 }
6505 }
6506
6507 if ( take || (dflags&DAMAGE_NO_DAMAGE) )
6508 {
6509 if ( !targ->client || !attacker->client )
6510 {
6511 targ->health = targ->health - take;
6512 if (targ->health < 0)
6513 {
6514 targ->health = 0;
6515 }
6516 if ( targUndying )
6517 {
6518 if(targ->health < 1)
6519 {
6520 G_ActivateBehavior( targ, BSET_DEATH );
6521 targ->health = 1;
6522 }
6523 }
6524 }
6525 else
6526 {//two clients
6527 team_t targTeam = TEAM_FREE;
6528 team_t attackerTeam = TEAM_FREE;
6529
6530 if ( player->client->ps.viewEntity && targ->s.number == player->client->ps.viewEntity )
6531 {
6532 targTeam = player->client->playerTeam;
6533 }
6534 else if ( targ->client ) {
6535 targTeam = targ->client->playerTeam;
6536 }
6537 else {
6538 targTeam = targ->noDamageTeam;
6539 }
6540 // if ( targTeam == TEAM_DISGUISE ) {
6541 // targTeam = TEAM_PLAYER;
6542 // }
6543 if ( player->client->ps.viewEntity && attacker->s.number == player->client->ps.viewEntity )
6544 {
6545 attackerTeam = player->client->playerTeam;
6546 }
6547 else if ( attacker->client ) {
6548 attackerTeam = attacker->client->playerTeam;
6549 }
6550 else {
6551 attackerTeam = attacker->noDamageTeam;
6552 }
6553 // if ( attackerTeam == TEAM_DISGUISE ) {
6554 // attackerTeam = TEAM_PLAYER;
6555 // }
6556
6557 if ( targTeam != attackerTeam
6558 || (targ->s.number < MAX_CLIENTS && targTeam == TEAM_FREE)//evil player hit
6559 || (attacker && attacker->s.number < MAX_CLIENTS && attackerTeam == TEAM_FREE) )//evil player attacked
6560 {//on opposite team
6561 targ->health = targ->health - take;
6562
6563 //MCG - Falling should never kill player- only if a trigger_hurt does so.
6564 if ( mod == MOD_FALLING && targ->s.number == 0 && targ->health < 1 )
6565 {
6566 targ->health = 1;
6567 }
6568 else if (targ->health < 0)
6569 {
6570 targ->health = 0;
6571 }
6572
6573 if (targUndying)
6574 {
6575 if ( targ->health < 1 )
6576 {
6577 if ( targ->NPC == NULL || !(targ->NPC->aiFlags&NPCAI_ROSH) || !Rosh_TwinPresent( targ ) )
6578 {//NOTE: Rosh won't run his deathscript until he doesn't have the twins to heal him
6579 G_ActivateBehavior( targ, BSET_DEATH );
6580 }
6581 targ->health = 1;
6582 }
6583 }
6584 else if ( targ->health < 1 && attacker->client )
6585 { // The player or NPC just killed an enemy so increment the kills counter
6586 attacker->client->ps.persistant[PERS_ENEMIES_KILLED]++;
6587 }
6588 }
6589 else if ( targTeam == TEAM_PLAYER )
6590 {//on the same team, and target is an ally
6591 qboolean takeDamage = qtrue;
6592 qboolean yellAtAttacker = qtrue;
6593
6594 //1) player doesn't take damage from teammates unless they're angry at him
6595 if ( targ->s.number == 0 )
6596 {//the player
6597 if ( attacker->enemy != targ && attacker != targ )
6598 {//an NPC shot the player by accident
6599 takeDamage = qfalse;
6600 }
6601 }
6602 //2) NPCs don't take any damage from player during combat
6603 else
6604 {//an NPC
6605 if ( ((dflags & DAMAGE_RADIUS)) && !(dflags&DAMAGE_IGNORE_TEAM) )
6606 {//An NPC got hit by player and this is during combat or it was slash damage
6607 //NOTE: though it's not realistic to have teammates not take splash damage,
6608 // even when not in combat, it feels really bad to have them able to
6609 // actually be killed by the player's splash damage
6610 takeDamage = qfalse;
6611 }
6612
6613 if ( (dflags & DAMAGE_RADIUS) )
6614 {//you're fighting and it's just radius damage, so don't even mention it
6615 yellAtAttacker = qfalse;
6616 }
6617 }
6618
6619 if ( takeDamage )
6620 {
6621 targ->health = targ->health - take;
6622 if ( !alreadyDead && ((((targ->flags&FL_UNDYING)||targ->client->ps.forcePowersActive & (1 << FP_RAGE)) && !(dflags&DAMAGE_NO_PROTECTION) && attacker->s.number != 0) || (dflags&DAMAGE_NO_KILL) ) )
6623 {//guy is marked undying and we're not the player or we're in combat
6624 if ( targ->health < 1 )
6625 {
6626 G_ActivateBehavior( targ, BSET_DEATH );
6627
6628 targ->health = 1;
6629 }
6630 }
6631 else if ( !alreadyDead && ((((targ->flags&FL_UNDYING)||targ->client->ps.forcePowersActive & (1 << FP_RAGE)) && !(dflags&DAMAGE_NO_PROTECTION) && !attacker->s.number && !targ->s.number) || (dflags&DAMAGE_NO_KILL)) )
6632 {// player is undying and he's attacking himself, don't let him die
6633 if ( targ->health < 1 )
6634 {
6635 G_ActivateBehavior( targ, BSET_DEATH );
6636
6637 targ->health = 1;
6638 }
6639 }
6640 else if ( targ->health < 0 )
6641 {
6642 targ->health = 0;
6643 if ( attacker->s.number == 0 && targ->NPC )
6644 {
6645 targ->NPC->scriptFlags |= SCF_FFDEATH;
6646 }
6647 }
6648 }
6649
6650 if ( yellAtAttacker )
6651 {
6652 if ( !targ->NPC || !targ->NPC->charmedTime )
6653 {
6654 G_FriendlyFireReaction( targ, attacker, dflags );
6655 }
6656 }
6657 }
6658 else
6659 {
6660
6661 }
6662 }
6663
6664 if ( targ->client ) {
6665 targ->client->ps.stats[STAT_HEALTH] = targ->health;
6666 g_lastClientDamaged = targ;
6667 }
6668
6669 //TEMP HACK FOR PLAYER LOOK AT ENEMY CODE
6670 //FIXME: move this to a player pain func?
6671 if ( targ->s.number == 0 )
6672 {
6673 if ( !targ->enemy //player does not have an enemy yet
6674 || targ->enemy->s.weapon != WP_SABER //or player's enemy is not a jedi
6675 || attacker->s.weapon == WP_SABER )//and attacker is a jedi
6676 //keep enemy jedi over shooters
6677 {
6678 if ( attacker->enemy == targ || !OnSameTeam( targ, attacker ) )
6679 {//don't set player's enemy to teammates that hit him by accident
6680 targ->enemy = attacker;
6681 }
6682 NPC_SetLookTarget( targ, attacker->s.number, level.time+1000 );
6683 }
6684 }
6685 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
6686 {//this looks dumb when they're on the ground and you keep hitting them, so only do this when first kill them
6687 if ( !OnSameTeam( targ, attacker ) )
6688 {//don't set player's enemy to teammates that he hits by accident
6689 attacker->enemy = targ;
6690 }
6691 NPC_SetLookTarget( attacker, targ->s.number, level.time+1000 );
6692 }
6693 //TEMP HACK FOR PLAYER LOOK AT ENEMY CODE
6694
6695 //add up the damage to the location
6696 if ( targ->client )
6697 {
6698 if ( targ->locationDamage[hitLoc] < Q3_INFINITE )
6699 {
6700 targ->locationDamage[hitLoc] += take;
6701 }
6702 }
6703
6704
6705 if ( targ->health > 0 && targ->NPC && targ->NPC->surrenderTime > level.time )
6706 {//he was surrendering, goes down with one hit
6707 if (!targ->client || targ->client->NPC_class!=CLASS_BOBAFETT)
6708 {
6709 targ->health = 0;
6710 }
6711 }
6712
6713 if ( targ->health <= 0 )
6714 {
6715 if ( knockback && (dflags&DAMAGE_DEATH_KNOCKBACK) )//&& targ->client
6716 {//only do knockback on death
6717 if ( mod == MOD_FLECHETTE )
6718 {//special case because this is shotgun-ish damage, we need to multiply the knockback
6719 knockback *= 12;//*6 for 6 flechette shots
6720 }
6721 G_ApplyKnockback( targ, newDir, knockback );
6722 }
6723
6724 /*
6725 if ( client )
6726 targ->flags |= FL_NO_KNOCKBACK;
6727 */
6728
6729 if (targ->health < -999)
6730 targ->health = -999;
6731
6732 // If we are a breaking glass brush, store the damage point so we can do cool things with it.
6733 if ( targ->svFlags & SVF_GLASS_BRUSH )
6734 {
6735 VectorCopy( point, targ->pos1 );
6736 VectorCopy( dir, targ->pos2 );
6737 }
6738 if ( targ->client )
6739 {//HACK
6740 if ( point )
6741 {
6742 VectorCopy( point, targ->pos1 );
6743 }
6744 else
6745 {
6746 VectorCopy( targ->currentOrigin, targ->pos1 );
6747 }
6748 }
6749 if ( !alreadyDead && !targ->enemy )
6750 {//just killed and didn't have an enemy before
6751 targ->enemy = attacker;
6752 }
6753
6754 GEntity_DieFunc( targ, inflictor, attacker, take, mod, dflags, hitLoc );
6755 }
6756 else
6757 {
6758 GEntity_PainFunc( targ, inflictor, attacker, point, take, mod, hitLoc );
6759 if ( targ->s.number == 0 )
6760 {//player run painscript
6761 G_ActivateBehavior( targ, BSET_PAIN );
6762 if ( targ->health <= 25 )
6763 {
6764 G_ActivateBehavior( targ, BSET_FLEE );
6765 }
6766 }
6767 }
6768 }
6769 }
6770
6771
6772 /*
6773 ============
6774 CanDamage
6775
6776 Returns qtrue if the inflictor can directly damage the target. Used for
6777 explosions and melee attacks.
6778 ============
6779 */
CanDamage(gentity_t * targ,const vec3_t origin)6780 qboolean CanDamage (gentity_t *targ, const vec3_t origin) {
6781 vec3_t dest;
6782 trace_t tr;
6783 vec3_t midpoint;
6784 qboolean cantHitEnt = qtrue;
6785
6786 if ( (targ->contents&MASK_SOLID) )
6787 {//can hit it
6788 if ( targ->s.solid == SOLID_BMODEL )
6789 {//but only if it's a brushmodel
6790 cantHitEnt = qfalse;
6791 }
6792 }
6793
6794 // use the midpoint of the bounds instead of the origin, because
6795 // bmodels may have their origin at 0,0,0
6796 VectorAdd (targ->absmin, targ->absmax, midpoint);
6797 VectorScale (midpoint, 0.5, midpoint);
6798
6799 VectorCopy (midpoint, dest);
6800 /*
6801 vec3_t blah;
6802 VectorCopy( origin, blah);
6803 G_DebugLine(blah, dest, 5000, 0x0000ff, qtrue );
6804 */
6805 gi.trace ( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID, (EG2_Collision)0, 0);
6806 if (( tr.fraction == 1.0 && cantHitEnt) || tr.entityNum == targ->s.number ) // if we also test the entitynum's we can bust up bbrushes better!
6807 return qtrue;
6808
6809 // this should probably check in the plane of projection,
6810 // rather than in world coordinate, and also include Z
6811 VectorCopy (midpoint, dest);
6812 dest[0] += 15.0;
6813 dest[1] += 15.0;
6814 gi.trace ( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID, (EG2_Collision)0, 0);
6815 if (( tr.fraction == 1.0 && cantHitEnt) || tr.entityNum == targ->s.number )
6816 return qtrue;
6817
6818 VectorCopy (midpoint, dest);
6819 dest[0] += 15.0;
6820 dest[1] -= 15.0;
6821 gi.trace ( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID, (EG2_Collision)0, 0);
6822 if (( tr.fraction == 1.0 && cantHitEnt) || tr.entityNum == targ->s.number )
6823 return qtrue;
6824
6825 VectorCopy (midpoint, dest);
6826 dest[0] -= 15.0;
6827 dest[1] += 15.0;
6828 gi.trace ( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID, (EG2_Collision)0, 0);
6829 if (( tr.fraction == 1.0 && cantHitEnt) || tr.entityNum == targ->s.number )
6830 return qtrue;
6831
6832 VectorCopy (midpoint, dest);
6833 dest[0] -= 15.0;
6834 dest[1] -= 15.0;
6835 gi.trace ( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID, (EG2_Collision)0, 0);
6836 if (( tr.fraction == 1.0 && cantHitEnt) || tr.entityNum == targ->s.number )
6837 return qtrue;
6838
6839
6840 return qfalse;
6841 }
6842
6843 extern void Boba_DustFallNear(const vec3_t origin, int dustcount);
6844 extern void G_GetMassAndVelocityForEnt( gentity_t *ent, float *mass, vec3_t velocity );
6845 /*
6846 ============
6847 G_RadiusDamage
6848 ============
6849 */
G_RadiusDamage(const vec3_t origin,gentity_t * attacker,float damage,float radius,gentity_t * ignore,int mod)6850 void G_RadiusDamage ( const vec3_t origin, gentity_t *attacker, float damage, float radius,
6851 gentity_t *ignore, int mod) {
6852 float points, dist;
6853 gentity_t *ent;
6854 gentity_t *entityList[MAX_GENTITIES];
6855 int numListedEntities;
6856 vec3_t mins, maxs;
6857 vec3_t v;
6858 vec3_t dir;
6859 int i, e;
6860 int dFlags = DAMAGE_RADIUS;
6861
6862 if ( radius < 1 ) {
6863 radius = 1;
6864 }
6865
6866 for ( i = 0 ; i < 3 ; i++ ) {
6867 mins[i] = origin[i] - radius;
6868 maxs[i] = origin[i] + radius;
6869 }
6870
6871 if (mod==MOD_ROCKET)
6872 {
6873 Boba_DustFallNear(origin, 10);
6874 }
6875
6876 if ( mod == MOD_GAS )
6877 {
6878 dFlags |= DAMAGE_NO_KNOCKBACK;
6879 }
6880
6881 numListedEntities = gi.EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
6882
6883 for ( e = 0 ; e < numListedEntities ; e++ ) {
6884 ent = entityList[ e ];
6885
6886 if ( ent == ignore )
6887 continue;
6888 if ( !ent->takedamage )
6889 continue;
6890 if ( !ent->contents )
6891 continue;
6892
6893 // find the distance from the edge of the bounding box
6894 for ( i = 0 ; i < 3 ; i++ ) {
6895 if ( origin[i] < ent->absmin[i] ) {
6896 v[i] = ent->absmin[i] - origin[i];
6897 } else if ( origin[i] > ent->absmax[i] ) {
6898 v[i] = origin[i] - ent->absmax[i];
6899 } else {
6900 v[i] = 0;
6901 }
6902 }
6903
6904 dist = VectorLength( v );
6905 if ( dist >= radius ) {
6906 continue;
6907 }
6908
6909 points = damage * ( 1.0 - dist / radius );
6910
6911 // Lessen damage to vehicles that are moving away from the explosion
6912 if (ent->client && (ent->client->NPC_class==CLASS_VEHICLE || G_IsRidingVehicle(ent)))
6913 {
6914 gentity_t* bike = ent;
6915
6916 if (G_IsRidingVehicle(ent) && ent->owner)
6917 {
6918 bike = ent->owner;
6919 }
6920
6921 vec3_t vehMoveDirection;
6922 float vehMoveSpeed;
6923
6924 vec3_t explosionDirection;
6925 float explosionDirectionSimilarity;
6926
6927 float mass;
6928 G_GetMassAndVelocityForEnt( bike, &mass, vehMoveDirection );
6929 vehMoveSpeed = VectorNormalize(vehMoveDirection);
6930 if (vehMoveSpeed>300.0f)
6931 {
6932 VectorSubtract(bike->currentOrigin, origin, explosionDirection);
6933 VectorNormalize(explosionDirection);
6934
6935 explosionDirectionSimilarity = DotProduct(vehMoveDirection, explosionDirection);
6936 if (explosionDirectionSimilarity>0.0f)
6937 {
6938 points *= (1.0f - explosionDirectionSimilarity);
6939 }
6940 }
6941 }
6942
6943 if (CanDamage (ent, origin))
6944 {//FIXME: still do a little damage in in PVS and close?
6945 if ( ent->svFlags & (SVF_GLASS_BRUSH|SVF_BBRUSH) )
6946 {
6947 VectorAdd( ent->absmin, ent->absmax, v );
6948 VectorScale( v, 0.5f, v );
6949 }
6950 else
6951 {
6952 VectorCopy( ent->currentOrigin, v );
6953 }
6954
6955 VectorSubtract( v, origin, dir);
6956 // push the center of mass higher than the origin so players
6957 // get knocked into the air more
6958 dir[2] += 24;
6959
6960 if ( ent->svFlags & SVF_GLASS_BRUSH )
6961 {
6962 if ( points > 1.0f )
6963 {
6964 // we want to cap this at some point, otherwise it just gets crazy
6965 if ( points > 6.0f )
6966 {
6967 VectorScale( dir, 6.0f, dir );
6968 }
6969 else
6970 {
6971 VectorScale( dir, points, dir );
6972 }
6973 }
6974
6975 ent->splashRadius = radius;// * ( 1.0 - dist / radius );
6976 }
6977
6978 G_Damage (ent, NULL, attacker, dir, origin, (int)points, dFlags, mod);
6979 }
6980 }
6981 }
6982