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 "b_local.h"
27 #include "bg_saga.h"
28
29 extern int G_ShipSurfaceForSurfName( const char *surfaceName );
30 extern qboolean G_FlyVehicleDestroySurface( gentity_t *veh, int surface );
31 extern void G_VehicleSetDamageLocFlags( gentity_t *veh, int impactDir, int deathPoint );
32 extern void G_VehUpdateShields( gentity_t *targ );
33 extern void G_LetGoOfWall( gentity_t *ent );
34 extern void BG_ClearRocketLock( playerState_t *ps );
35 //rww - pd
36 void BotDamageNotification(gclient_t *bot, gentity_t *attacker);
37 //end rww
38
39 void ThrowSaberToAttacker(gentity_t *self, gentity_t *attacker);
40
ObjectDie(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,int damage,int meansOfDeath)41 void ObjectDie (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath )
42 {
43 if(self->target)
44 {
45 G_UseTargets(self, attacker);
46 }
47
48 //remove my script_targetname
49 G_FreeEntity( self );
50 }
51
G_HeavyMelee(gentity_t * attacker)52 qboolean G_HeavyMelee( gentity_t *attacker )
53 {
54 if (level.gametype == GT_SIEGE
55 && attacker
56 && attacker->client
57 && attacker->client->siegeClass != -1
58 && (bgSiegeClasses[attacker->client->siegeClass].classflags & (1<<CFL_HEAVYMELEE)) )
59 {
60 return qtrue;
61 }
62 return qfalse;
63 }
64
G_GetHitLocation(gentity_t * target,vec3_t ppoint)65 int G_GetHitLocation(gentity_t *target, vec3_t ppoint)
66 {
67 vec3_t point, point_dir;
68 vec3_t forward, right, up;
69 vec3_t tangles, tcenter;
70 // float tradius;
71 float udot, fdot, rdot;
72 int Vertical, Forward, Lateral;
73 int HitLoc;
74
75 // Get target forward, right and up.
76 if(target->client)
77 {
78 // Ignore player's pitch and roll.
79 VectorSet(tangles, 0, target->r.currentAngles[YAW], 0);
80 }
81
82 AngleVectors(tangles, forward, right, up);
83
84 // Get center of target.
85 VectorAdd(target->r.absmin, target->r.absmax, tcenter);
86 VectorScale(tcenter, 0.5, tcenter);
87
88 // Get radius width of target.
89 // tradius = (fabs(target->r.maxs[0]) + fabs(target->r.maxs[1]) + fabs(target->r.mins[0]) + fabs(target->r.mins[1]))/4;
90
91 // Get impact point.
92 if(ppoint && !VectorCompare(ppoint, vec3_origin))
93 {
94 VectorCopy(ppoint, point);
95 }
96 else
97 {
98 return HL_NONE;
99 }
100
101 /*
102 //get impact dir
103 if(pdir && !VectorCompare(pdir, vec3_origin))
104 {
105 VectorCopy(pdir, dir);
106 }
107 else
108 {
109 return;
110 }
111
112 //put point at controlled distance from center
113 VectorSubtract(point, tcenter, tempvec);
114 tempvec[2] = 0;
115 hdist = VectorLength(tempvec);
116
117 VectorMA(point, hdist - tradius, dir, point);
118 //now a point on the surface of a cylinder with a radius of tradius
119 */
120 VectorSubtract(point, tcenter, point_dir);
121 VectorNormalize(point_dir);
122
123 // Get bottom to top (vertical) position index
124 udot = DotProduct(up, point_dir);
125 if(udot>.800)
126 {
127 Vertical = 4;
128 }
129 else if(udot>.400)
130 {
131 Vertical = 3;
132 }
133 else if(udot>-.333)
134 {
135 Vertical = 2;
136 }
137 else if(udot>-.666)
138 {
139 Vertical = 1;
140 }
141 else
142 {
143 Vertical = 0;
144 }
145
146 // Get back to front (forward) position index.
147 fdot = DotProduct(forward, point_dir);
148 if(fdot>.666)
149 {
150 Forward = 4;
151 }
152 else if(fdot>.333)
153 {
154 Forward = 3;
155 }
156 else if(fdot>-.333)
157 {
158 Forward = 2;
159 }
160 else if(fdot>-.666)
161 {
162 Forward = 1;
163 }
164 else
165 {
166 Forward = 0;
167 }
168
169 // Get left to right (lateral) position index.
170 rdot = DotProduct(right, point_dir);
171 if(rdot>.666)
172 {
173 Lateral = 4;
174 }
175 else if(rdot>.333)
176 {
177 Lateral = 3;
178 }
179 else if(rdot>-.333)
180 {
181 Lateral = 2;
182 }
183 else if(rdot>-.666)
184 {
185 Lateral = 1;
186 }
187 else
188 {
189 Lateral = 0;
190 }
191
192 HitLoc = Vertical * 25 + Forward * 5 + Lateral;
193
194 if(HitLoc <= 10)
195 {
196 // Feet.
197 if ( rdot > 0 )
198 {
199 return HL_FOOT_RT;
200 }
201 else
202 {
203 return HL_FOOT_LT;
204 }
205 }
206 else if(HitLoc <= 50)
207 {
208 // Legs.
209 if ( rdot > 0 )
210 {
211 return HL_LEG_RT;
212 }
213 else
214 {
215 return HL_LEG_LT;
216 }
217 }
218 else if(HitLoc == 56||HitLoc == 60||HitLoc == 61||HitLoc == 65||HitLoc == 66||HitLoc == 70)
219 {
220 // Hands.
221 if ( rdot > 0 )
222 {
223 return HL_HAND_RT;
224 }
225 else
226 {
227 return HL_HAND_LT;
228 }
229 }
230 else if(HitLoc == 83||HitLoc == 87||HitLoc == 88||HitLoc == 92||HitLoc == 93||HitLoc == 97)
231 {
232 // Arms.
233 if ( rdot > 0 )
234 {
235 return HL_ARM_RT;
236 }
237 else
238 {
239 return HL_ARM_LT;
240 }
241 }
242 else if((HitLoc >= 107 && HitLoc <= 109)||(HitLoc >= 112 && HitLoc <= 114)||(HitLoc >= 117 && HitLoc <= 119))
243 {
244 // Head.
245 return HL_HEAD;
246 }
247 else
248 {
249 if(udot < 0.3)
250 {
251 return HL_WAIST;
252 }
253 else if(fdot < 0)
254 {
255 if(rdot > 0.4)
256 {
257 return HL_BACK_RT;
258 }
259 else if(rdot < -0.4)
260 {
261 return HL_BACK_LT;
262 }
263 else if(fdot < 0)
264 {
265 return HL_BACK;
266 }
267 }
268 else
269 {
270 if(rdot > 0.3)
271 {
272 return HL_CHEST_RT;
273 }
274 else if(rdot < -0.3)
275 {
276 return HL_CHEST_LT;
277 }
278 else if(fdot < 0)
279 {
280 return HL_CHEST;
281 }
282 }
283 }
284 return HL_NONE;
285 }
286
287 /*
288 int G_PickPainAnim( gentity_t *self, vec3_t point, int damage )
289 {
290 switch( G_GetHitLocation( self, point ) )
291 {
292 case HL_FOOT_RT:
293 return BOTH_PAIN12;
294 //PAIN12 = right foot
295 break;
296 case HL_FOOT_LT:
297 return -1;
298 break;
299 case HL_LEG_RT:
300 if ( !Q_irand( 0, 1 ) )
301 {
302 return BOTH_PAIN11;
303 }
304 else
305 {
306 return BOTH_PAIN13;
307 }
308 //PAIN11 = twitch right leg
309 //PAIN13 = right knee
310 break;
311 case HL_LEG_LT:
312 return BOTH_PAIN14;
313 //PAIN14 = twitch left leg
314 break;
315 case HL_BACK_RT:
316 return BOTH_PAIN7;
317 //PAIN7 = med left shoulder
318 break;
319 case HL_BACK_LT:
320 return Q_irand( BOTH_PAIN15, BOTH_PAIN16 );
321 //PAIN15 = med right shoulder
322 //PAIN16 = twitch right shoulder
323 break;
324 case HL_BACK:
325 if ( !Q_irand( 0, 1 ) )
326 {
327 return BOTH_PAIN1;
328 }
329 else
330 {
331 return BOTH_PAIN5;
332 }
333 //PAIN1 = back
334 //PAIN5 = same as 1
335 break;
336 case HL_CHEST_RT:
337 return BOTH_PAIN3;
338 //PAIN3 = long, right shoulder
339 break;
340 case HL_CHEST_LT:
341 return BOTH_PAIN2;
342 //PAIN2 = long, left shoulder
343 break;
344 case HL_WAIST:
345 case HL_CHEST:
346 if ( !Q_irand( 0, 3 ) )
347 {
348 return BOTH_PAIN6;
349 }
350 else if ( !Q_irand( 0, 2 ) )
351 {
352 return BOTH_PAIN8;
353 }
354 else if ( !Q_irand( 0, 1 ) )
355 {
356 return BOTH_PAIN17;
357 }
358 else
359 {
360 return BOTH_PAIN19;
361 }
362 //PAIN6 = gut
363 //PAIN8 = chest
364 //PAIN17 = twitch crotch
365 //PAIN19 = med crotch
366 break;
367 case HL_ARM_RT:
368 case HL_HAND_RT:
369 return BOTH_PAIN9;
370 //PAIN9 = twitch right arm
371 break;
372 case HL_ARM_LT:
373 case HL_HAND_LT:
374 return BOTH_PAIN10;
375 //PAIN10 = twitch left arm
376 break;
377 case HL_HEAD:
378 return BOTH_PAIN4;
379 //PAIN4 = head
380 break;
381 default:
382 return -1;
383 break;
384 }
385 }
386 */
387
ExplodeDeath(gentity_t * self)388 void ExplodeDeath( gentity_t *self )
389 {
390 // gentity_t *tent;
391 vec3_t forward;
392
393 self->takedamage = qfalse;//stop chain reaction runaway loops
394
395 self->s.loopSound = 0;
396 self->s.loopIsSoundset = qfalse;
397
398 VectorCopy( self->r.currentOrigin, self->s.pos.trBase );
399
400 // tent = G_TempEntity( self->s.origin, EV_FX_EXPLOSION );
401 AngleVectors(self->s.angles, forward, NULL, NULL);
402
403 /*
404 if ( self->fxID > 0 )
405 {
406 G_PlayEffect( self->fxID, self->r.currentOrigin, forward );
407 }
408 else
409 */
410
411 {
412 // CG_SurfaceExplosion( self->r.currentOrigin, forward, 20.0f, 12.0f, ((self->spawnflags&4)==qfalse) ); //FIXME: This needs to be consistent to all exploders!
413 // G_Sound(self, self->sounds );
414 }
415
416 if(self->splashDamage > 0 && self->splashRadius > 0)
417 {
418 gentity_t *attacker = self;
419 if ( self->parent )
420 {
421 attacker = self->parent;
422 }
423 G_RadiusDamage( self->r.currentOrigin, attacker, self->splashDamage, self->splashRadius,
424 attacker, NULL, MOD_UNKNOWN );
425 }
426
427 ObjectDie( self, self, self, 20, 0 );
428 }
429
430
431 /*
432 ============
433 ScorePlum
434 ============
435 */
ScorePlum(gentity_t * ent,vec3_t origin,int score)436 void ScorePlum( gentity_t *ent, vec3_t origin, int score ) {
437 gentity_t *plum;
438
439 plum = G_TempEntity( origin, EV_SCOREPLUM );
440 // only send this temp entity to a single client
441 plum->r.svFlags |= SVF_SINGLECLIENT;
442 plum->r.singleClient = ent->s.number;
443 //
444 plum->s.otherEntityNum = ent->s.number;
445 plum->s.time = score;
446 }
447
448 /*
449 ============
450 AddScore
451
452 Adds score to both the client and his team
453 ============
454 */
455 extern qboolean g_dontPenalizeTeam; //g_cmds.c
AddScore(gentity_t * ent,vec3_t origin,int score)456 void AddScore( gentity_t *ent, vec3_t origin, int score )
457 {
458 /*
459 if (level.gametype == GT_SIEGE)
460 { //no scoring in this gametype at all.
461 return;
462 }
463 */
464
465 if ( !ent->client ) {
466 return;
467 }
468 // no scoring during pre-match warmup
469 if ( level.warmupTime ) {
470 return;
471 }
472 // show score plum
473 //ScorePlum(ent, origin, score);
474 //
475 ent->client->ps.persistant[PERS_SCORE] += score;
476 if ( level.gametype == GT_TEAM && !g_dontPenalizeTeam )
477 level.teamScores[ ent->client->ps.persistant[PERS_TEAM] ] += score;
478 CalculateRanks();
479 }
480
481 /*
482 =================
483 TossClientItems
484
485 rww - Toss the weapon away from the player in the specified direction
486 =================
487 */
TossClientWeapon(gentity_t * self,vec3_t direction,float speed)488 void TossClientWeapon(gentity_t *self, vec3_t direction, float speed)
489 {
490 vec3_t vel;
491 gitem_t *item;
492 gentity_t *launched;
493 int weapon = self->s.weapon;
494 int ammoSub;
495
496 if (level.gametype == GT_SIEGE)
497 { //no dropping weaps
498 return;
499 }
500
501 if (weapon <= WP_BRYAR_PISTOL)
502 { //can't have this
503 return;
504 }
505
506 if (weapon == WP_EMPLACED_GUN ||
507 weapon == WP_TURRET)
508 {
509 return;
510 }
511
512 // find the item type for this weapon
513 item = BG_FindItemForWeapon( weapon );
514
515 ammoSub = (self->client->ps.ammo[weaponData[weapon].ammoIndex] - bg_itemlist[BG_GetItemIndexByTag(weapon, IT_WEAPON)].quantity);
516
517 if (ammoSub < 0)
518 {
519 int ammoQuan = item->quantity;
520 ammoQuan -= (-ammoSub);
521
522 if (ammoQuan <= 0)
523 { //no ammo
524 return;
525 }
526 }
527
528 vel[0] = direction[0]*speed;
529 vel[1] = direction[1]*speed;
530 vel[2] = direction[2]*speed;
531
532 launched = LaunchItem(item, self->client->ps.origin, vel);
533
534 launched->s.generic1 = self->s.number;
535 launched->s.powerups = level.time + 1500;
536
537 launched->count = bg_itemlist[BG_GetItemIndexByTag(weapon, IT_WEAPON)].quantity;
538
539 self->client->ps.ammo[weaponData[weapon].ammoIndex] -= bg_itemlist[BG_GetItemIndexByTag(weapon, IT_WEAPON)].quantity;
540
541 if (self->client->ps.ammo[weaponData[weapon].ammoIndex] < 0)
542 {
543 launched->count -= (-self->client->ps.ammo[weaponData[weapon].ammoIndex]);
544 self->client->ps.ammo[weaponData[weapon].ammoIndex] = 0;
545 }
546
547 if ((self->client->ps.ammo[weaponData[weapon].ammoIndex] < 1 && weapon != WP_DET_PACK) ||
548 (weapon != WP_THERMAL && weapon != WP_DET_PACK && weapon != WP_TRIP_MINE))
549 {
550 int i = 0;
551 int weap = -1;
552
553 self->client->ps.stats[STAT_WEAPONS] &= ~(1 << weapon);
554
555 while (i < WP_NUM_WEAPONS)
556 {
557 if ((self->client->ps.stats[STAT_WEAPONS] & (1 << i)) && i != WP_NONE)
558 { //this one's good
559 weap = i;
560 break;
561 }
562 i++;
563 }
564
565 if (weap != -1)
566 {
567 self->s.weapon = weap;
568 self->client->ps.weapon = weap;
569 }
570 else
571 {
572 self->s.weapon = 0;
573 self->client->ps.weapon = 0;
574 }
575
576 G_AddEvent(self, EV_NOAMMO, weapon);
577 }
578 }
579
580 /*
581 =================
582 TossClientItems
583
584 Toss the weapon and powerups for the killed player
585 =================
586 */
TossClientItems(gentity_t * self)587 void TossClientItems( gentity_t *self ) {
588 gitem_t *item;
589 int weapon;
590 float angle;
591 int i;
592 gentity_t *drop;
593
594 if (level.gametype == GT_SIEGE)
595 { //just don't drop anything then
596 return;
597 }
598
599 // drop the weapon if not a gauntlet or machinegun
600 weapon = self->s.weapon;
601
602 // make a special check to see if they are changing to a new
603 // weapon that isn't the mg or gauntlet. Without this, a client
604 // can pick up a weapon, be killed, and not drop the weapon because
605 // their weapon change hasn't completed yet and they are still holding the MG.
606 if ( weapon == WP_BRYAR_PISTOL) {
607 if ( self->client->ps.weaponstate == WEAPON_DROPPING ) {
608 weapon = self->client->pers.cmd.weapon;
609 }
610 if ( !( self->client->ps.stats[STAT_WEAPONS] & ( 1 << weapon ) ) ) {
611 weapon = WP_NONE;
612 }
613 }
614
615 self->s.bolt2 = weapon;
616
617 if ( weapon > WP_BRYAR_PISTOL &&
618 weapon != WP_EMPLACED_GUN &&
619 weapon != WP_TURRET &&
620 self->client->ps.ammo[ weaponData[weapon].ammoIndex ] ) {
621 gentity_t *te;
622
623 // find the item type for this weapon
624 item = BG_FindItemForWeapon( weapon );
625
626 // tell all clients to remove the weapon model on this guy until he respawns
627 te = G_TempEntity( vec3_origin, EV_DESTROY_WEAPON_MODEL );
628 te->r.svFlags |= SVF_BROADCAST;
629 te->s.eventParm = self->s.number;
630
631 // spawn the item
632 Drop_Item( self, item, 0 );
633 }
634
635 // drop all the powerups if not in teamplay
636 if ( level.gametype != GT_TEAM && level.gametype != GT_SIEGE ) {
637 angle = 45;
638 for ( i = 1 ; i < PW_NUM_POWERUPS ; i++ ) {
639 if ( self->client->ps.powerups[ i ] > level.time ) {
640 item = BG_FindItemForPowerup( i );
641 if ( !item ) {
642 continue;
643 }
644 drop = Drop_Item( self, item, angle );
645 // decide how many seconds it has left
646 drop->count = ( self->client->ps.powerups[ i ] - level.time ) / 1000;
647 if ( drop->count < 1 ) {
648 drop->count = 1;
649 }
650 angle += 45;
651 }
652 }
653 }
654 }
655
656
657 /*
658 ==================
659 LookAtKiller
660 ==================
661 */
LookAtKiller(gentity_t * self,gentity_t * inflictor,gentity_t * attacker)662 void LookAtKiller( gentity_t *self, gentity_t *inflictor, gentity_t *attacker ) {
663 vec3_t dir;
664
665 if ( attacker && attacker != self )
666 VectorSubtract (attacker->s.pos.trBase, self->s.pos.trBase, dir);
667 else if ( inflictor && inflictor != self )
668 VectorSubtract (inflictor->s.pos.trBase, self->s.pos.trBase, dir);
669 else {
670 self->client->ps.stats[STAT_DEAD_YAW] = self->s.angles[YAW];
671 return;
672 }
673
674 self->client->ps.stats[STAT_DEAD_YAW] = vectoyaw ( dir );
675 }
676
677 /*
678 ==================
679 GibEntity
680 ==================
681 */
GibEntity(gentity_t * self,int killer)682 void GibEntity( gentity_t *self, int killer ) {
683 G_AddEvent( self, EV_GIB_PLAYER, killer );
684 self->takedamage = qfalse;
685 self->s.eType = ET_INVISIBLE;
686 self->r.contents = 0;
687 }
688
BodyRid(gentity_t * ent)689 void BodyRid(gentity_t *ent)
690 {
691 trap->UnlinkEntity( (sharedEntity_t *)ent );
692 ent->physicsObject = qfalse;
693 }
694
695 /*
696 ==================
697 body_die
698 ==================
699 */
body_die(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,int damage,int meansOfDeath)700 void body_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath ) {
701 // NOTENOTE No gibbing right now, this is star wars.
702 qboolean doDisint = qfalse;
703
704 if (self->s.eType == ET_NPC)
705 { //well, just rem it then, so long as it's done with its death anim and it's not a standard weapon.
706 if ( self->client && self->client->ps.torsoTimer <= 0 &&
707 (meansOfDeath == MOD_UNKNOWN ||
708 meansOfDeath == MOD_WATER ||
709 meansOfDeath == MOD_SLIME ||
710 meansOfDeath == MOD_LAVA ||
711 meansOfDeath == MOD_CRUSH ||
712 meansOfDeath == MOD_TELEFRAG ||
713 meansOfDeath == MOD_FALLING ||
714 meansOfDeath == MOD_SUICIDE ||
715 meansOfDeath == MOD_TARGET_LASER ||
716 meansOfDeath == MOD_TRIGGER_HURT) )
717 {
718 self->think = G_FreeEntity;
719 self->nextthink = level.time;
720 }
721 return;
722 }
723
724 if (self->health < (GIB_HEALTH+1))
725 {
726 self->health = GIB_HEALTH+1;
727
728 if (self->client && (level.time - self->client->respawnTime) < 2000)
729 {
730 doDisint = qfalse;
731 }
732 else
733 {
734 doDisint = qtrue;
735 }
736 }
737
738 if (self->client && (self->client->ps.eFlags & EF_DISINTEGRATION))
739 {
740 return;
741 }
742 else if (self->s.eFlags & EF_DISINTEGRATION)
743 {
744 return;
745 }
746
747 if (doDisint)
748 {
749 if (self->client)
750 {
751 self->client->ps.eFlags |= EF_DISINTEGRATION;
752 VectorCopy(self->client->ps.origin, self->client->ps.lastHitLoc);
753 }
754 else
755 {
756 self->s.eFlags |= EF_DISINTEGRATION;
757 VectorCopy(self->r.currentOrigin, self->s.origin2);
758
759 //since it's the corpse entity, tell it to "remove" itself
760 self->think = BodyRid;
761 self->nextthink = level.time + 1000;
762 }
763 return;
764 }
765 }
766
767
768 // these are just for logging, the client prints its own messages
769 char *modNames[MOD_MAX] = {
770 "MOD_UNKNOWN",
771 "MOD_STUN_BATON",
772 "MOD_MELEE",
773 "MOD_SABER",
774 "MOD_BRYAR_PISTOL",
775 "MOD_BRYAR_PISTOL_ALT",
776 "MOD_BLASTER",
777 "MOD_TURBLAST",
778 "MOD_DISRUPTOR",
779 "MOD_DISRUPTOR_SPLASH",
780 "MOD_DISRUPTOR_SNIPER",
781 "MOD_BOWCASTER",
782 "MOD_REPEATER",
783 "MOD_REPEATER_ALT",
784 "MOD_REPEATER_ALT_SPLASH",
785 "MOD_DEMP2",
786 "MOD_DEMP2_ALT",
787 "MOD_FLECHETTE",
788 "MOD_FLECHETTE_ALT_SPLASH",
789 "MOD_ROCKET",
790 "MOD_ROCKET_SPLASH",
791 "MOD_ROCKET_HOMING",
792 "MOD_ROCKET_HOMING_SPLASH",
793 "MOD_THERMAL",
794 "MOD_THERMAL_SPLASH",
795 "MOD_TRIP_MINE_SPLASH",
796 "MOD_TIMED_MINE_SPLASH",
797 "MOD_DET_PACK_SPLASH",
798 "MOD_VEHICLE",
799 "MOD_CONC",
800 "MOD_CONC_ALT",
801 "MOD_FORCE_DARK",
802 "MOD_SENTRY",
803 "MOD_WATER",
804 "MOD_SLIME",
805 "MOD_LAVA",
806 "MOD_CRUSH",
807 "MOD_TELEFRAG",
808 "MOD_FALLING",
809 "MOD_SUICIDE",
810 "MOD_TARGET_LASER",
811 "MOD_TRIGGER_HURT"
812 };
813
814
815 /*
816 ==================
817 CheckAlmostCapture
818 ==================
819 */
CheckAlmostCapture(gentity_t * self,gentity_t * attacker)820 void CheckAlmostCapture( gentity_t *self, gentity_t *attacker ) {
821 #if 0
822 gentity_t *ent;
823 vec3_t dir;
824 char *classname;
825
826 // if this player was carrying a flag
827 if ( self->client->ps.powerups[PW_REDFLAG] ||
828 self->client->ps.powerups[PW_BLUEFLAG] ||
829 self->client->ps.powerups[PW_NEUTRALFLAG] ) {
830 // get the goal flag this player should have been going for
831 if ( level.gametype == GT_CTF || level.gametype == GT_CTY ) {
832 if ( self->client->sess.sessionTeam == TEAM_BLUE ) {
833 classname = "team_CTF_blueflag";
834 }
835 else {
836 classname = "team_CTF_redflag";
837 }
838 }
839 else {
840 if ( self->client->sess.sessionTeam == TEAM_BLUE ) {
841 classname = "team_CTF_redflag";
842 }
843 else {
844 classname = "team_CTF_blueflag";
845 }
846 }
847 ent = NULL;
848 do
849 {
850 ent = G_Find(ent, FOFS(classname), classname);
851 } while (ent && (ent->flags & FL_DROPPED_ITEM));
852 // if we found the destination flag and it's not picked up
853 if (ent && !(ent->r.svFlags & SVF_NOCLIENT) ) {
854 // if the player was *very* close
855 VectorSubtract( self->client->ps.origin, ent->s.origin, dir );
856 if ( VectorLength(dir) < 200 ) {
857 self->client->ps.persistant[PERS_PLAYEREVENTS] ^= PLAYEREVENT_HOLYSHIT;
858 if ( attacker->client ) {
859 attacker->client->ps.persistant[PERS_PLAYEREVENTS] ^= PLAYEREVENT_HOLYSHIT;
860 }
861 }
862 }
863 }
864 #endif
865 }
866
G_InKnockDown(playerState_t * ps)867 qboolean G_InKnockDown( playerState_t *ps )
868 {
869 switch ( (ps->legsAnim) )
870 {
871 case BOTH_KNOCKDOWN1:
872 case BOTH_KNOCKDOWN2:
873 case BOTH_KNOCKDOWN3:
874 case BOTH_KNOCKDOWN4:
875 case BOTH_KNOCKDOWN5:
876 return qtrue;
877 break;
878 case BOTH_GETUP1:
879 case BOTH_GETUP2:
880 case BOTH_GETUP3:
881 case BOTH_GETUP4:
882 case BOTH_GETUP5:
883 case BOTH_FORCE_GETUP_F1:
884 case BOTH_FORCE_GETUP_F2:
885 case BOTH_FORCE_GETUP_B1:
886 case BOTH_FORCE_GETUP_B2:
887 case BOTH_FORCE_GETUP_B3:
888 case BOTH_FORCE_GETUP_B4:
889 case BOTH_FORCE_GETUP_B5:
890 return qtrue;
891 break;
892 }
893 return qfalse;
894 }
895
G_CheckSpecialDeathAnim(gentity_t * self,vec3_t point,int damage,int mod,int hitLoc)896 static int G_CheckSpecialDeathAnim( gentity_t *self, vec3_t point, int damage, int mod, int hitLoc )
897 {
898 int deathAnim = -1;
899
900 if ( BG_InRoll( &self->client->ps, self->client->ps.legsAnim ) )
901 {
902 deathAnim = BOTH_DEATH_ROLL; //# Death anim from a roll
903 }
904 else if ( BG_FlippingAnim( self->client->ps.legsAnim ) )
905 {
906 deathAnim = BOTH_DEATH_FLIP; //# Death anim from a flip
907 }
908 else if ( G_InKnockDown( &self->client->ps ) )
909 {//since these happen a lot, let's handle them case by case
910 int animLength = bgAllAnims[self->localAnimIndex].anims[self->client->ps.legsAnim].numFrames * fabs((float)(bgHumanoidAnimations[self->client->ps.legsAnim].frameLerp));
911 switch ( self->client->ps.legsAnim )
912 {
913 case BOTH_KNOCKDOWN1:
914 if ( animLength - self->client->ps.legsTimer > 100 )
915 {//on our way down
916 if ( self->client->ps.legsTimer > 600 )
917 {//still partially up
918 deathAnim = BOTH_DEATH_FALLING_UP;
919 }
920 else
921 {//down
922 deathAnim = BOTH_DEATH_LYING_UP;
923 }
924 }
925 break;
926 case BOTH_KNOCKDOWN2:
927 if ( animLength - self->client->ps.legsTimer > 700 )
928 {//on our way down
929 if ( self->client->ps.legsTimer > 600 )
930 {//still partially up
931 deathAnim = BOTH_DEATH_FALLING_UP;
932 }
933 else
934 {//down
935 deathAnim = BOTH_DEATH_LYING_UP;
936 }
937 }
938 break;
939 case BOTH_KNOCKDOWN3:
940 if ( animLength - self->client->ps.legsTimer > 100 )
941 {//on our way down
942 if ( self->client->ps.legsTimer > 1300 )
943 {//still partially up
944 deathAnim = BOTH_DEATH_FALLING_DN;
945 }
946 else
947 {//down
948 deathAnim = BOTH_DEATH_LYING_DN;
949 }
950 }
951 break;
952 case BOTH_KNOCKDOWN4:
953 if ( animLength - self->client->ps.legsTimer > 300 )
954 {//on our way down
955 if ( self->client->ps.legsTimer > 350 )
956 {//still partially up
957 deathAnim = BOTH_DEATH_FALLING_UP;
958 }
959 else
960 {//down
961 deathAnim = BOTH_DEATH_LYING_UP;
962 }
963 }
964 else
965 {//crouch death
966 vec3_t fwd;
967 float thrown = 0;
968
969 AngleVectors( self->client->ps.viewangles, fwd, NULL, NULL );
970 thrown = DotProduct( fwd, self->client->ps.velocity );
971
972 if ( thrown < -150 )
973 {
974 deathAnim = BOTH_DEATHBACKWARD1; //# Death anim when crouched and thrown back
975 }
976 else
977 {
978 deathAnim = BOTH_DEATH_CROUCHED; //# Death anim when crouched
979 }
980 }
981 break;
982 case BOTH_KNOCKDOWN5:
983 if ( self->client->ps.legsTimer < 750 )
984 {//flat
985 deathAnim = BOTH_DEATH_LYING_DN;
986 }
987 break;
988 case BOTH_GETUP1:
989 if ( self->client->ps.legsTimer < 350 )
990 {//standing up
991 }
992 else if ( self->client->ps.legsTimer < 800 )
993 {//crouching
994 vec3_t fwd;
995 float thrown = 0;
996
997 AngleVectors( self->client->ps.viewangles, fwd, NULL, NULL );
998 thrown = DotProduct( fwd, self->client->ps.velocity );
999 if ( thrown < -150 )
1000 {
1001 deathAnim = BOTH_DEATHBACKWARD1; //# Death anim when crouched and thrown back
1002 }
1003 else
1004 {
1005 deathAnim = BOTH_DEATH_CROUCHED; //# Death anim when crouched
1006 }
1007 }
1008 else
1009 {//lying down
1010 if ( animLength - self->client->ps.legsTimer > 450 )
1011 {//partially up
1012 deathAnim = BOTH_DEATH_FALLING_UP;
1013 }
1014 else
1015 {//down
1016 deathAnim = BOTH_DEATH_LYING_UP;
1017 }
1018 }
1019 break;
1020 case BOTH_GETUP2:
1021 if ( self->client->ps.legsTimer < 150 )
1022 {//standing up
1023 }
1024 else if ( self->client->ps.legsTimer < 850 )
1025 {//crouching
1026 vec3_t fwd;
1027 float thrown = 0;
1028
1029 AngleVectors( self->client->ps.viewangles, fwd, NULL, NULL );
1030 thrown = DotProduct( fwd, self->client->ps.velocity );
1031
1032 if ( thrown < -150 )
1033 {
1034 deathAnim = BOTH_DEATHBACKWARD1; //# Death anim when crouched and thrown back
1035 }
1036 else
1037 {
1038 deathAnim = BOTH_DEATH_CROUCHED; //# Death anim when crouched
1039 }
1040 }
1041 else
1042 {//lying down
1043 if ( animLength - self->client->ps.legsTimer > 500 )
1044 {//partially up
1045 deathAnim = BOTH_DEATH_FALLING_UP;
1046 }
1047 else
1048 {//down
1049 deathAnim = BOTH_DEATH_LYING_UP;
1050 }
1051 }
1052 break;
1053 case BOTH_GETUP3:
1054 if ( self->client->ps.legsTimer < 250 )
1055 {//standing up
1056 }
1057 else if ( self->client->ps.legsTimer < 600 )
1058 {//crouching
1059 vec3_t fwd;
1060 float thrown = 0;
1061 AngleVectors( self->client->ps.viewangles, fwd, NULL, NULL );
1062 thrown = DotProduct( fwd, self->client->ps.velocity );
1063
1064 if ( thrown < -150 )
1065 {
1066 deathAnim = BOTH_DEATHBACKWARD1; //# Death anim when crouched and thrown back
1067 }
1068 else
1069 {
1070 deathAnim = BOTH_DEATH_CROUCHED; //# Death anim when crouched
1071 }
1072 }
1073 else
1074 {//lying down
1075 if ( animLength - self->client->ps.legsTimer > 150 )
1076 {//partially up
1077 deathAnim = BOTH_DEATH_FALLING_DN;
1078 }
1079 else
1080 {//down
1081 deathAnim = BOTH_DEATH_LYING_DN;
1082 }
1083 }
1084 break;
1085 case BOTH_GETUP4:
1086 if ( self->client->ps.legsTimer < 250 )
1087 {//standing up
1088 }
1089 else if ( self->client->ps.legsTimer < 600 )
1090 {//crouching
1091 vec3_t fwd;
1092 float thrown = 0;
1093
1094 AngleVectors( self->client->ps.viewangles, fwd, NULL, NULL );
1095 thrown = DotProduct( fwd, self->client->ps.velocity );
1096
1097 if ( thrown < -150 )
1098 {
1099 deathAnim = BOTH_DEATHBACKWARD1; //# Death anim when crouched and thrown back
1100 }
1101 else
1102 {
1103 deathAnim = BOTH_DEATH_CROUCHED; //# Death anim when crouched
1104 }
1105 }
1106 else
1107 {//lying down
1108 if ( animLength - self->client->ps.legsTimer > 850 )
1109 {//partially up
1110 deathAnim = BOTH_DEATH_FALLING_DN;
1111 }
1112 else
1113 {//down
1114 deathAnim = BOTH_DEATH_LYING_UP;
1115 }
1116 }
1117 break;
1118 case BOTH_GETUP5:
1119 if ( self->client->ps.legsTimer > 850 )
1120 {//lying down
1121 if ( animLength - self->client->ps.legsTimer > 1500 )
1122 {//partially up
1123 deathAnim = BOTH_DEATH_FALLING_DN;
1124 }
1125 else
1126 {//down
1127 deathAnim = BOTH_DEATH_LYING_DN;
1128 }
1129 }
1130 break;
1131 case BOTH_GETUP_CROUCH_B1:
1132 if ( self->client->ps.legsTimer < 800 )
1133 {//crouching
1134 vec3_t fwd;
1135 float thrown = 0;
1136
1137 AngleVectors( self->client->ps.viewangles, fwd, NULL, NULL );
1138 thrown = DotProduct( fwd, self->client->ps.velocity );
1139
1140 if ( thrown < -150 )
1141 {
1142 deathAnim = BOTH_DEATHBACKWARD1; //# Death anim when crouched and thrown back
1143 }
1144 else
1145 {
1146 deathAnim = BOTH_DEATH_CROUCHED; //# Death anim when crouched
1147 }
1148 }
1149 else
1150 {//lying down
1151 if ( animLength - self->client->ps.legsTimer > 400 )
1152 {//partially up
1153 deathAnim = BOTH_DEATH_FALLING_UP;
1154 }
1155 else
1156 {//down
1157 deathAnim = BOTH_DEATH_LYING_UP;
1158 }
1159 }
1160 break;
1161 case BOTH_GETUP_CROUCH_F1:
1162 if ( self->client->ps.legsTimer < 800 )
1163 {//crouching
1164 vec3_t fwd;
1165 float thrown = 0;
1166
1167 AngleVectors( self->client->ps.viewangles, fwd, NULL, NULL );
1168 thrown = DotProduct( fwd, self->client->ps.velocity );
1169
1170 if ( thrown < -150 )
1171 {
1172 deathAnim = BOTH_DEATHBACKWARD1; //# Death anim when crouched and thrown back
1173 }
1174 else
1175 {
1176 deathAnim = BOTH_DEATH_CROUCHED; //# Death anim when crouched
1177 }
1178 }
1179 else
1180 {//lying down
1181 if ( animLength - self->client->ps.legsTimer > 150 )
1182 {//partially up
1183 deathAnim = BOTH_DEATH_FALLING_DN;
1184 }
1185 else
1186 {//down
1187 deathAnim = BOTH_DEATH_LYING_DN;
1188 }
1189 }
1190 break;
1191 case BOTH_FORCE_GETUP_B1:
1192 if ( self->client->ps.legsTimer < 325 )
1193 {//standing up
1194 }
1195 else if ( self->client->ps.legsTimer < 725 )
1196 {//spinning up
1197 deathAnim = BOTH_DEATH_SPIN_180; //# Death anim when facing backwards
1198 }
1199 else if ( self->client->ps.legsTimer < 900 )
1200 {//crouching
1201 vec3_t fwd;
1202 float thrown = 0;
1203
1204 AngleVectors( self->client->ps.viewangles, fwd, NULL, NULL );
1205 thrown = DotProduct( fwd, self->client->ps.velocity );
1206
1207 if ( thrown < -150 )
1208 {
1209 deathAnim = BOTH_DEATHBACKWARD1; //# Death anim when crouched and thrown back
1210 }
1211 else
1212 {
1213 deathAnim = BOTH_DEATH_CROUCHED; //# Death anim when crouched
1214 }
1215 }
1216 else
1217 {//lying down
1218 if ( animLength - self->client->ps.legsTimer > 50 )
1219 {//partially up
1220 deathAnim = BOTH_DEATH_FALLING_UP;
1221 }
1222 else
1223 {//down
1224 deathAnim = BOTH_DEATH_LYING_UP;
1225 }
1226 }
1227 break;
1228 case BOTH_FORCE_GETUP_B2:
1229 if ( self->client->ps.legsTimer < 575 )
1230 {//standing up
1231 }
1232 else if ( self->client->ps.legsTimer < 875 )
1233 {//spinning up
1234 deathAnim = BOTH_DEATH_SPIN_180; //# Death anim when facing backwards
1235 }
1236 else if ( self->client->ps.legsTimer < 900 )
1237 {//crouching
1238 vec3_t fwd;
1239 float thrown = 0;
1240
1241 AngleVectors( self->client->ps.viewangles, fwd, NULL, NULL );
1242 thrown = DotProduct( fwd, self->client->ps.velocity );
1243
1244 if ( thrown < -150 )
1245 {
1246 deathAnim = BOTH_DEATHBACKWARD1; //# Death anim when crouched and thrown back
1247 }
1248 else
1249 {
1250 deathAnim = BOTH_DEATH_CROUCHED; //# Death anim when crouched
1251 }
1252 }
1253 else
1254 {//lying down
1255 //partially up
1256 deathAnim = BOTH_DEATH_FALLING_UP;
1257 }
1258 break;
1259 case BOTH_FORCE_GETUP_B3:
1260 if ( self->client->ps.legsTimer < 150 )
1261 {//standing up
1262 }
1263 else if ( self->client->ps.legsTimer < 775 )
1264 {//flipping
1265 deathAnim = BOTH_DEATHBACKWARD2; //backflip
1266 }
1267 else
1268 {//lying down
1269 //partially up
1270 deathAnim = BOTH_DEATH_FALLING_UP;
1271 }
1272 break;
1273 case BOTH_FORCE_GETUP_B4:
1274 if ( self->client->ps.legsTimer < 325 )
1275 {//standing up
1276 }
1277 else
1278 {//lying down
1279 if ( animLength - self->client->ps.legsTimer > 150 )
1280 {//partially up
1281 deathAnim = BOTH_DEATH_FALLING_UP;
1282 }
1283 else
1284 {//down
1285 deathAnim = BOTH_DEATH_LYING_UP;
1286 }
1287 }
1288 break;
1289 case BOTH_FORCE_GETUP_B5:
1290 if ( self->client->ps.legsTimer < 550 )
1291 {//standing up
1292 }
1293 else if ( self->client->ps.legsTimer < 1025 )
1294 {//kicking up
1295 deathAnim = BOTH_DEATHBACKWARD2; //backflip
1296 }
1297 else
1298 {//lying down
1299 if ( animLength - self->client->ps.legsTimer > 50 )
1300 {//partially up
1301 deathAnim = BOTH_DEATH_FALLING_UP;
1302 }
1303 else
1304 {//down
1305 deathAnim = BOTH_DEATH_LYING_UP;
1306 }
1307 }
1308 break;
1309 case BOTH_FORCE_GETUP_B6:
1310 if ( self->client->ps.legsTimer < 225 )
1311 {//standing up
1312 }
1313 else if ( self->client->ps.legsTimer < 425 )
1314 {//crouching up
1315 vec3_t fwd;
1316 float thrown = 0;
1317
1318 AngleVectors( self->client->ps.viewangles, fwd, NULL, NULL );
1319 thrown = DotProduct( fwd, self->client->ps.velocity );
1320
1321 if ( thrown < -150 )
1322 {
1323 deathAnim = BOTH_DEATHBACKWARD1; //# Death anim when crouched and thrown back
1324 }
1325 else
1326 {
1327 deathAnim = BOTH_DEATH_CROUCHED; //# Death anim when crouched
1328 }
1329 }
1330 else if ( self->client->ps.legsTimer < 825 )
1331 {//flipping up
1332 deathAnim = BOTH_DEATHFORWARD3; //backflip
1333 }
1334 else
1335 {//lying down
1336 if ( animLength - self->client->ps.legsTimer > 225 )
1337 {//partially up
1338 deathAnim = BOTH_DEATH_FALLING_UP;
1339 }
1340 else
1341 {//down
1342 deathAnim = BOTH_DEATH_LYING_UP;
1343 }
1344 }
1345 break;
1346 case BOTH_FORCE_GETUP_F1:
1347 if ( self->client->ps.legsTimer < 275 )
1348 {//standing up
1349 }
1350 else if ( self->client->ps.legsTimer < 750 )
1351 {//flipping
1352 deathAnim = BOTH_DEATH14;
1353 }
1354 else
1355 {//lying down
1356 if ( animLength - self->client->ps.legsTimer > 100 )
1357 {//partially up
1358 deathAnim = BOTH_DEATH_FALLING_DN;
1359 }
1360 else
1361 {//down
1362 deathAnim = BOTH_DEATH_LYING_DN;
1363 }
1364 }
1365 break;
1366 case BOTH_FORCE_GETUP_F2:
1367 if ( self->client->ps.legsTimer < 1200 )
1368 {//standing
1369 }
1370 else
1371 {//lying down
1372 if ( animLength - self->client->ps.legsTimer > 225 )
1373 {//partially up
1374 deathAnim = BOTH_DEATH_FALLING_DN;
1375 }
1376 else
1377 {//down
1378 deathAnim = BOTH_DEATH_LYING_DN;
1379 }
1380 }
1381 break;
1382 }
1383 }
1384
1385 return deathAnim;
1386 }
1387
G_PickDeathAnim(gentity_t * self,vec3_t point,int damage,int mod,int hitLoc)1388 int G_PickDeathAnim( gentity_t *self, vec3_t point, int damage, int mod, int hitLoc )
1389 {//FIXME: play dead flop anims on body if in an appropriate _DEAD anim when this func is called
1390 int deathAnim = -1;
1391 int max_health;
1392 int legAnim = 0;
1393 vec3_t objVelocity;
1394
1395 if (!self || !self->client)
1396 {
1397 if (!self || self->s.eType != ET_NPC)
1398 { //g2animent
1399 return 0;
1400 }
1401 }
1402
1403 if (self->client)
1404 {
1405 max_health = self->client->ps.stats[STAT_MAX_HEALTH];
1406
1407 if (self->client->inSpaceIndex && self->client->inSpaceIndex != ENTITYNUM_NONE)
1408 {
1409 return BOTH_CHOKE3;
1410 }
1411 }
1412 else
1413 {
1414 max_health = 60;
1415 }
1416
1417 if (self->client)
1418 {
1419 VectorCopy(self->client->ps.velocity, objVelocity);
1420 }
1421 else
1422 {
1423 VectorCopy(self->s.pos.trDelta, objVelocity);
1424 }
1425
1426 if ( hitLoc == HL_NONE )
1427 {
1428 hitLoc = G_GetHitLocation( self, point );//self->hitLoc
1429 }
1430
1431 if (self->client)
1432 {
1433 legAnim = self->client->ps.legsAnim;
1434 }
1435 else
1436 {
1437 legAnim = self->s.legsAnim;
1438 }
1439
1440 if (gGAvoidDismember)
1441 {
1442 return BOTH_RIGHTHANDCHOPPEDOFF;
1443 }
1444
1445 //dead flops
1446 switch( legAnim )
1447 {
1448 case BOTH_DEATH1: //# First Death anim
1449 case BOTH_DEAD1:
1450 case BOTH_DEATH2: //# Second Death anim
1451 case BOTH_DEAD2:
1452 case BOTH_DEATH8: //#
1453 case BOTH_DEAD8:
1454 case BOTH_DEATH13: //#
1455 case BOTH_DEAD13:
1456 case BOTH_DEATH14: //#
1457 case BOTH_DEAD14:
1458 case BOTH_DEATH16: //#
1459 case BOTH_DEAD16:
1460 case BOTH_DEADBACKWARD1: //# First thrown backward death finished pose
1461 case BOTH_DEADBACKWARD2: //# Second thrown backward death finished pose
1462 deathAnim = -2;
1463 break;
1464 /*
1465 if ( PM_FinishedCurrentLegsAnim( self ) )
1466 {//done with the anim
1467 deathAnim = BOTH_DEADFLOP2;
1468 }
1469 else
1470 {
1471 deathAnim = -2;
1472 }
1473 break;
1474 case BOTH_DEADFLOP2:
1475 deathAnim = BOTH_DEADFLOP2;
1476 break;
1477 */
1478 case BOTH_DEATH10: //#
1479 case BOTH_DEAD10:
1480 case BOTH_DEATH15: //#
1481 case BOTH_DEAD15:
1482 case BOTH_DEADFORWARD1: //# First thrown forward death finished pose
1483 case BOTH_DEADFORWARD2: //# Second thrown forward death finished pose
1484 deathAnim = -2;
1485 break;
1486 /*
1487 if ( PM_FinishedCurrentLegsAnim( self ) )
1488 {//done with the anim
1489 deathAnim = BOTH_DEADFLOP1;
1490 }
1491 else
1492 {
1493 deathAnim = -2;
1494 }
1495 break;
1496 */
1497 case BOTH_DEADFLOP1:
1498 deathAnim = -2;
1499 //deathAnim = BOTH_DEADFLOP1;
1500 break;
1501 case BOTH_DEAD3: //# Third Death finished pose
1502 case BOTH_DEAD4: //# Fourth Death finished pose
1503 case BOTH_DEAD5: //# Fifth Death finished pose
1504 case BOTH_DEAD6: //# Sixth Death finished pose
1505 case BOTH_DEAD7: //# Seventh Death finished pose
1506 case BOTH_DEAD9: //#
1507 case BOTH_DEAD11: //#
1508 case BOTH_DEAD12: //#
1509 case BOTH_DEAD17: //#
1510 case BOTH_DEAD18: //#
1511 case BOTH_DEAD19: //#
1512 case BOTH_LYINGDEAD1: //# Killed lying down death finished pose
1513 case BOTH_STUMBLEDEAD1: //# Stumble forward death finished pose
1514 case BOTH_FALLDEAD1LAND: //# Fall forward and splat death finished pose
1515 case BOTH_DEATH3: //# Third Death anim
1516 case BOTH_DEATH4: //# Fourth Death anim
1517 case BOTH_DEATH5: //# Fifth Death anim
1518 case BOTH_DEATH6: //# Sixth Death anim
1519 case BOTH_DEATH7: //# Seventh Death anim
1520 case BOTH_DEATH9: //#
1521 case BOTH_DEATH11: //#
1522 case BOTH_DEATH12: //#
1523 case BOTH_DEATH17: //#
1524 case BOTH_DEATH18: //#
1525 case BOTH_DEATH19: //#
1526 case BOTH_DEATHFORWARD1: //# First Death in which they get thrown forward
1527 case BOTH_DEATHFORWARD2: //# Second Death in which they get thrown forward
1528 case BOTH_DEATHBACKWARD1: //# First Death in which they get thrown backward
1529 case BOTH_DEATHBACKWARD2: //# Second Death in which they get thrown backward
1530 case BOTH_DEATH1IDLE: //# Idle while close to death
1531 case BOTH_LYINGDEATH1: //# Death to play when killed lying down
1532 case BOTH_STUMBLEDEATH1: //# Stumble forward and fall face first death
1533 case BOTH_FALLDEATH1: //# Fall forward off a high cliff and splat death - start
1534 case BOTH_FALLDEATH1INAIR: //# Fall forward off a high cliff and splat death - loop
1535 case BOTH_FALLDEATH1LAND: //# Fall forward off a high cliff and splat death - hit bottom
1536 deathAnim = -2;
1537 break;
1538 }
1539 if ( deathAnim == -1 )
1540 {
1541 if (self->client)
1542 {
1543 deathAnim = G_CheckSpecialDeathAnim( self, point, damage, mod, hitLoc );
1544 }
1545
1546 if (deathAnim == -1)
1547 {
1548 //death anims
1549 switch( hitLoc )
1550 {
1551 case HL_FOOT_RT:
1552 case HL_FOOT_LT:
1553 if ( mod == MOD_SABER && !Q_irand( 0, 2 ) )
1554 {
1555 return BOTH_DEATH10;//chest: back flip
1556 }
1557 else if ( !Q_irand( 0, 2 ) )
1558 {
1559 deathAnim = BOTH_DEATH4;//back: forward
1560 }
1561 else if ( !Q_irand( 0, 1 ) )
1562 {
1563 deathAnim = BOTH_DEATH5;//same as 4
1564 }
1565 else
1566 {
1567 deathAnim = BOTH_DEATH15;//back: forward
1568 }
1569 break;
1570 case HL_LEG_RT:
1571 if ( !Q_irand( 0, 2 ) )
1572 {
1573 deathAnim = BOTH_DEATH4;//back: forward
1574 }
1575 else if ( !Q_irand( 0, 1 ) )
1576 {
1577 deathAnim = BOTH_DEATH5;//same as 4
1578 }
1579 else
1580 {
1581 deathAnim = BOTH_DEATH15;//back: forward
1582 }
1583 break;
1584 case HL_LEG_LT:
1585 if ( !Q_irand( 0, 2 ) )
1586 {
1587 deathAnim = BOTH_DEATH4;//back: forward
1588 }
1589 else if ( !Q_irand( 0, 1 ) )
1590 {
1591 deathAnim = BOTH_DEATH5;//same as 4
1592 }
1593 else
1594 {
1595 deathAnim = BOTH_DEATH15;//back: forward
1596 }
1597 break;
1598 case HL_BACK:
1599 if ( !VectorLengthSquared( objVelocity ) )
1600 {
1601 deathAnim = BOTH_DEATH17;//head/back: croak
1602 }
1603 else
1604 {
1605 if ( !Q_irand( 0, 2 ) )
1606 {
1607 deathAnim = BOTH_DEATH4;//back: forward
1608 }
1609 else if ( !Q_irand( 0, 1 ) )
1610 {
1611 deathAnim = BOTH_DEATH5;//same as 4
1612 }
1613 else
1614 {
1615 deathAnim = BOTH_DEATH15;//back: forward
1616 }
1617 }
1618 break;
1619 case HL_CHEST_RT:
1620 case HL_ARM_RT:
1621 case HL_HAND_RT:
1622 case HL_BACK_RT:
1623 if ( damage <= max_health*0.25 )
1624 {
1625 deathAnim = BOTH_DEATH9;//chest right: snap, fall forward
1626 }
1627 else if ( damage <= max_health*0.5 )
1628 {
1629 deathAnim = BOTH_DEATH3;//chest right: back
1630 }
1631 else if ( damage <= max_health*0.75 )
1632 {
1633 deathAnim = BOTH_DEATH6;//chest right: spin
1634 }
1635 else
1636 {
1637 //TEMP HACK: play spinny deaths less often
1638 if ( Q_irand( 0, 1 ) )
1639 {
1640 deathAnim = BOTH_DEATH8;//chest right: spin high
1641 }
1642 else
1643 {
1644 switch ( Q_irand( 0, 2 ) )
1645 {
1646 default:
1647 case 0:
1648 deathAnim = BOTH_DEATH9;//chest right: snap, fall forward
1649 break;
1650 case 1:
1651 deathAnim = BOTH_DEATH3;//chest right: back
1652 break;
1653 case 2:
1654 deathAnim = BOTH_DEATH6;//chest right: spin
1655 break;
1656 }
1657 }
1658 }
1659 break;
1660 case HL_CHEST_LT:
1661 case HL_ARM_LT:
1662 case HL_HAND_LT:
1663 case HL_BACK_LT:
1664 if ( damage <= max_health*0.25 )
1665 {
1666 deathAnim = BOTH_DEATH11;//chest left: snap, fall forward
1667 }
1668 else if ( damage <= max_health*0.5 )
1669 {
1670 deathAnim = BOTH_DEATH7;//chest left: back
1671 }
1672 else if ( damage <= max_health*0.75 )
1673 {
1674 deathAnim = BOTH_DEATH12;//chest left: spin
1675 }
1676 else
1677 {
1678 //TEMP HACK: play spinny deaths less often
1679 if ( Q_irand( 0, 1 ) )
1680 {
1681 deathAnim = BOTH_DEATH14;//chest left: spin high
1682 }
1683 else
1684 {
1685 switch ( Q_irand( 0, 2 ) )
1686 {
1687 default:
1688 case 0:
1689 deathAnim = BOTH_DEATH11;//chest left: snap, fall forward
1690 break;
1691 case 1:
1692 deathAnim = BOTH_DEATH7;//chest left: back
1693 break;
1694 case 2:
1695 deathAnim = BOTH_DEATH12;//chest left: spin
1696 break;
1697 }
1698 }
1699 }
1700 break;
1701 case HL_CHEST:
1702 case HL_WAIST:
1703 if ( damage <= max_health*0.25 || !VectorLengthSquared( objVelocity ) )
1704 {
1705 if ( !Q_irand( 0, 1 ) )
1706 {
1707 deathAnim = BOTH_DEATH18;//gut: fall right
1708 }
1709 else
1710 {
1711 deathAnim = BOTH_DEATH19;//gut: fall left
1712 }
1713 }
1714 else if ( damage <= max_health*0.5 )
1715 {
1716 deathAnim = BOTH_DEATH2;//chest: backward short
1717 }
1718 else if ( damage <= max_health*0.75 )
1719 {
1720 if ( !Q_irand( 0, 1 ) )
1721 {
1722 deathAnim = BOTH_DEATH1;//chest: backward med
1723 }
1724 else
1725 {
1726 deathAnim = BOTH_DEATH16;//same as 1
1727 }
1728 }
1729 else
1730 {
1731 deathAnim = BOTH_DEATH10;//chest: back flip
1732 }
1733 break;
1734 case HL_HEAD:
1735 if ( damage <= max_health*0.5 )
1736 {
1737 deathAnim = BOTH_DEATH17;//head/back: croak
1738 }
1739 else
1740 {
1741 deathAnim = BOTH_DEATH13;//head: stumble, fall back
1742 }
1743 break;
1744 default:
1745 break;
1746 }
1747 }
1748 }
1749
1750 // Validate.....
1751 if ( deathAnim == -1 || !BG_HasAnimation( self->localAnimIndex, deathAnim ))
1752 {
1753 // I guess we'll take what we can get.....
1754 deathAnim = BG_PickAnim( self->localAnimIndex, BOTH_DEATH1, BOTH_DEATH25 );
1755 }
1756
1757 return deathAnim;
1758 }
1759
G_GetJediMaster(void)1760 gentity_t *G_GetJediMaster(void)
1761 {
1762 int i = 0;
1763 gentity_t *ent;
1764
1765 while (i < MAX_CLIENTS)
1766 {
1767 ent = &g_entities[i];
1768
1769 if (ent && ent->inuse && ent->client && ent->client->ps.isJediMaster)
1770 {
1771 return ent;
1772 }
1773
1774 i++;
1775 }
1776
1777 return NULL;
1778 }
1779
1780 /*
1781 -------------------------
1782 G_AlertTeam
1783 -------------------------
1784 */
1785
G_AlertTeam(gentity_t * victim,gentity_t * attacker,float radius,float soundDist)1786 void G_AlertTeam( gentity_t *victim, gentity_t *attacker, float radius, float soundDist )
1787 {
1788 int radiusEnts[ 128 ];
1789 gentity_t *check;
1790 vec3_t mins, maxs;
1791 int numEnts;
1792 int i;
1793 float distSq, sndDistSq = (soundDist*soundDist);
1794
1795 if ( attacker == NULL || attacker->client == NULL )
1796 return;
1797
1798 //Setup the bbox to search in
1799 for ( i = 0; i < 3; i++ )
1800 {
1801 mins[i] = victim->r.currentOrigin[i] - radius;
1802 maxs[i] = victim->r.currentOrigin[i] + radius;
1803 }
1804
1805 //Get the number of entities in a given space
1806 numEnts = trap->EntitiesInBox( mins, maxs, radiusEnts, 128 );
1807
1808 //Cull this list
1809 for ( i = 0; i < numEnts; i++ )
1810 {
1811 check = &g_entities[radiusEnts[i]];
1812
1813 //Validate clients
1814 if ( check->client == NULL )
1815 continue;
1816
1817 //only want NPCs
1818 if ( check->NPC == NULL )
1819 continue;
1820
1821 //Don't bother if they're ignoring enemies
1822 // if ( check->svFlags & SVF_IGNORE_ENEMIES )
1823 // continue;
1824
1825 //This NPC specifically flagged to ignore alerts
1826 if ( check->NPC->scriptFlags & SCF_IGNORE_ALERTS )
1827 continue;
1828
1829 //This NPC specifically flagged to ignore alerts
1830 if ( !(check->NPC->scriptFlags&SCF_LOOK_FOR_ENEMIES) )
1831 continue;
1832
1833 //this ent does not participate in group AI
1834 if ( (check->NPC->scriptFlags&SCF_NO_GROUPS) )
1835 continue;
1836
1837 //Skip the requested avoid check if present
1838 if ( check == victim )
1839 continue;
1840
1841 //Skip the attacker
1842 if ( check == attacker )
1843 continue;
1844
1845 //Must be on the same team
1846 if ( check->client->playerTeam != victim->client->playerTeam )
1847 continue;
1848
1849 //Must be alive
1850 if ( check->health <= 0 )
1851 continue;
1852
1853 if ( check->enemy == NULL )
1854 {//only do this if they're not already mad at someone
1855 distSq = DistanceSquared( check->r.currentOrigin, victim->r.currentOrigin );
1856 if ( distSq > 16384 /*128 squared*/ && !trap->InPVS( victim->r.currentOrigin, check->r.currentOrigin ) )
1857 {//not even potentially visible/hearable
1858 continue;
1859 }
1860 //NOTE: this allows sound alerts to still go through doors/PVS if the teammate is within 128 of the victim...
1861 if ( soundDist <= 0 || distSq > sndDistSq )
1862 {//out of sound range
1863 if ( !InFOV( victim, check, check->NPC->stats.hfov, check->NPC->stats.vfov )
1864 || !NPC_ClearLOS2( check, victim->r.currentOrigin ) )
1865 {//out of FOV or no LOS
1866 continue;
1867 }
1868 }
1869
1870 //FIXME: This can have a nasty cascading effect if setup wrong...
1871 G_SetEnemy( check, attacker );
1872 }
1873 }
1874 }
1875
1876 /*
1877 -------------------------
1878 G_DeathAlert
1879 -------------------------
1880 */
1881
1882 #define DEATH_ALERT_RADIUS 512
1883 #define DEATH_ALERT_SOUND_RADIUS 512
1884
G_DeathAlert(gentity_t * victim,gentity_t * attacker)1885 void G_DeathAlert( gentity_t *victim, gentity_t *attacker )
1886 {//FIXME: with all the other alert stuff, do we really need this?
1887 G_AlertTeam( victim, attacker, DEATH_ALERT_RADIUS, DEATH_ALERT_SOUND_RADIUS );
1888 }
1889
1890 /*
1891 ----------------------------------------
1892 DeathFX
1893
1894 Applies appropriate special effects that occur while the entity is dying
1895 Not to be confused with NPC_RemoveBodyEffects (NPC.cpp), which only applies effect when removing the body
1896 ----------------------------------------
1897 */
1898
DeathFX(gentity_t * ent)1899 void DeathFX( gentity_t *ent )
1900 {
1901 vec3_t effectPos, right;
1902 vec3_t defaultDir;
1903
1904 if ( !ent || !ent->client )
1905 return;
1906
1907 VectorSet(defaultDir, 0, 0, 1);
1908
1909 // team no longer indicates species/race. NPC_class should be used to identify certain npc types
1910 switch(ent->client->NPC_class)
1911 {
1912 case CLASS_MOUSE:
1913 VectorCopy( ent->r.currentOrigin, effectPos );
1914 effectPos[2] -= 20;
1915 G_PlayEffectID( G_EffectIndex("env/small_explode"), effectPos, defaultDir );
1916 G_Sound( ent, CHAN_AUTO, G_SoundIndex("sound/chars/mouse/misc/death1") );
1917 break;
1918
1919 case CLASS_PROBE:
1920 VectorCopy( ent->r.currentOrigin, effectPos );
1921 effectPos[2] += 50;
1922 G_PlayEffectID( G_EffectIndex("explosions/probeexplosion1"), effectPos, defaultDir );
1923 break;
1924
1925 case CLASS_ATST:
1926 AngleVectors( ent->r.currentAngles, NULL, right, NULL );
1927 VectorMA( ent->r.currentOrigin, 20, right, effectPos );
1928 effectPos[2] += 180;
1929 G_PlayEffectID( G_EffectIndex("explosions/droidexplosion1"), effectPos, defaultDir );
1930 VectorMA( effectPos, -40, right, effectPos );
1931 G_PlayEffectID( G_EffectIndex("explosions/droidexplosion1"), effectPos, defaultDir );
1932 break;
1933
1934 case CLASS_SEEKER:
1935 case CLASS_REMOTE:
1936 G_PlayEffectID( G_EffectIndex("env/small_explode"), ent->r.currentOrigin, defaultDir );
1937 break;
1938
1939 case CLASS_GONK:
1940 VectorCopy( ent->r.currentOrigin, effectPos );
1941 effectPos[2] -= 5;
1942 // statusTextIndex = Q_irand( IGT_RESISTANCEISFUTILE, IGT_NAMEIS8OF12 );
1943 G_Sound( ent, CHAN_AUTO, G_SoundIndex(va("sound/chars/gonk/misc/death%d.wav",Q_irand( 1, 3 ))) );
1944 G_PlayEffectID( G_EffectIndex("env/med_explode"), effectPos, defaultDir );
1945 break;
1946
1947 // should list all remaining droids here, hope I didn't miss any
1948 case CLASS_R2D2:
1949 VectorCopy( ent->r.currentOrigin, effectPos );
1950 effectPos[2] -= 10;
1951 G_PlayEffectID( G_EffectIndex("env/med_explode"), effectPos, defaultDir );
1952 G_Sound( ent, CHAN_AUTO, G_SoundIndex("sound/chars/mark2/misc/mark2_explo") );
1953 break;
1954
1955 case CLASS_PROTOCOL: //c3p0
1956 case CLASS_R5D2:
1957 VectorCopy( ent->r.currentOrigin, effectPos );
1958 effectPos[2] -= 10;
1959 G_PlayEffectID( G_EffectIndex("env/med_explode"), effectPos, defaultDir );
1960 G_Sound( ent, CHAN_AUTO, G_SoundIndex("sound/chars/mark2/misc/mark2_explo") );
1961 break;
1962
1963 case CLASS_MARK2:
1964 VectorCopy( ent->r.currentOrigin, effectPos );
1965 effectPos[2] -= 15;
1966 G_PlayEffectID( G_EffectIndex("explosions/droidexplosion1"), effectPos, defaultDir );
1967 G_Sound( ent, CHAN_AUTO, G_SoundIndex("sound/chars/mark2/misc/mark2_explo") );
1968 break;
1969
1970 case CLASS_INTERROGATOR:
1971 VectorCopy( ent->r.currentOrigin, effectPos );
1972 effectPos[2] -= 15;
1973 G_PlayEffectID( G_EffectIndex("explosions/droidexplosion1"), effectPos, defaultDir );
1974 G_Sound( ent, CHAN_AUTO, G_SoundIndex("sound/chars/interrogator/misc/int_droid_explo") );
1975 break;
1976
1977 case CLASS_MARK1:
1978 AngleVectors( ent->r.currentAngles, NULL, right, NULL );
1979 VectorMA( ent->r.currentOrigin, 10, right, effectPos );
1980 effectPos[2] -= 15;
1981 G_PlayEffectID( G_EffectIndex("explosions/droidexplosion1"), effectPos, defaultDir );
1982 VectorMA( effectPos, -20, right, effectPos );
1983 G_PlayEffectID( G_EffectIndex("explosions/droidexplosion1"), effectPos, defaultDir );
1984 VectorMA( effectPos, -20, right, effectPos );
1985 G_PlayEffectID( G_EffectIndex("explosions/droidexplosion1"), effectPos, defaultDir );
1986 G_Sound( ent, CHAN_AUTO, G_SoundIndex("sound/chars/mark1/misc/mark1_explo") );
1987 break;
1988
1989 case CLASS_SENTRY:
1990 G_Sound( ent, CHAN_AUTO, G_SoundIndex("sound/chars/sentry/misc/sentry_explo") );
1991 VectorCopy( ent->r.currentOrigin, effectPos );
1992 G_PlayEffectID( G_EffectIndex("env/med_explode"), effectPos, defaultDir );
1993 break;
1994
1995 default:
1996 break;
1997
1998 }
1999
2000 }
2001
G_CheckVictoryScript(gentity_t * self)2002 void G_CheckVictoryScript(gentity_t *self)
2003 {
2004 if ( !G_ActivateBehavior( self, BSET_VICTORY ) )
2005 {
2006 if ( self->NPC && self->s.weapon == WP_SABER )
2007 {//Jedi taunt from within their AI
2008 self->NPC->blockedSpeechDebounceTime = 0;//get them ready to taunt
2009 return;
2010 }
2011 if ( self->client && self->client->NPC_class == CLASS_GALAKMECH )
2012 {
2013 self->wait = 1;
2014 TIMER_Set( self, "gloatTime", Q_irand( 5000, 8000 ) );
2015 self->NPC->blockedSpeechDebounceTime = 0;//get him ready to taunt
2016 return;
2017 }
2018 //FIXME: any way to not say this *right away*? Wait for victim's death anim/scream to finish?
2019 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 ) )
2020 {//sometimes have the group commander speak instead
2021 self->NPC->group->commander->NPC->greetingDebounceTime = level.time + Q_irand( 2000, 5000 );
2022 //G_AddVoiceEvent( self->NPC->group->commander, Q_irand(EV_VICTORY1, EV_VICTORY3), 2000 );
2023 }
2024 else if ( self->NPC )
2025 {
2026 self->NPC->greetingDebounceTime = level.time + Q_irand( 2000, 5000 );
2027 //G_AddVoiceEvent( self, Q_irand(EV_VICTORY1, EV_VICTORY3), 2000 );
2028 }
2029 }
2030 }
2031
G_AddPowerDuelScore(int team,int score)2032 void G_AddPowerDuelScore(int team, int score)
2033 {
2034 int i = 0;
2035 gentity_t *check;
2036
2037 while (i < MAX_CLIENTS)
2038 {
2039 check = &g_entities[i];
2040 if (check->inuse && check->client &&
2041 check->client->pers.connected == CON_CONNECTED && !check->client->iAmALoser &&
2042 check->client->ps.stats[STAT_HEALTH] > 0 &&
2043 check->client->sess.sessionTeam != TEAM_SPECTATOR &&
2044 check->client->sess.duelTeam == team)
2045 { //found a living client on the specified team
2046 check->client->sess.wins += score;
2047 ClientUserinfoChanged(check->s.number);
2048 }
2049 i++;
2050 }
2051 }
2052
G_AddPowerDuelLoserScore(int team,int score)2053 void G_AddPowerDuelLoserScore(int team, int score)
2054 {
2055 int i = 0;
2056 gentity_t *check;
2057
2058 while (i < MAX_CLIENTS)
2059 {
2060 check = &g_entities[i];
2061 if (check->inuse && check->client &&
2062 check->client->pers.connected == CON_CONNECTED &&
2063 (check->client->iAmALoser || (check->client->ps.stats[STAT_HEALTH] <= 0 && check->client->sess.sessionTeam != TEAM_SPECTATOR)) &&
2064 check->client->sess.duelTeam == team)
2065 { //found a living client on the specified team
2066 check->client->sess.losses += score;
2067 ClientUserinfoChanged(check->s.number);
2068 }
2069 i++;
2070 }
2071 }
2072
2073 /*
2074 ==================
2075 player_die
2076 ==================
2077 */
2078 extern stringID_table_t animTable[MAX_ANIMATIONS+1];
2079
2080 extern void AI_DeleteSelfFromGroup( gentity_t *self );
2081 extern void AI_GroupMemberKilled( gentity_t *self );
2082 extern void Boba_FlyStop( gentity_t *self );
2083 extern qboolean Jedi_WaitingAmbush( gentity_t *self );
2084 void CheckExitRules( void );
2085 extern void Rancor_DropVictim( gentity_t *self );
2086
2087 extern qboolean g_dontFrickinCheck;
2088 extern qboolean g_endPDuel;
2089 extern qboolean g_noPDuelCheck;
2090 extern void saberReactivate(gentity_t *saberent, gentity_t *saberOwner);
2091 extern void saberBackToOwner(gentity_t *saberent);
player_die(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,int damage,int meansOfDeath)2092 void player_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath ) {
2093 gentity_t *ent;
2094 int anim;
2095 int killer;
2096 int i;
2097 char *killerName, *obit;
2098 qboolean wasJediMaster = qfalse;
2099 int sPMType = 0;
2100 char buf[512] = {0};
2101
2102 if ( self->client->ps.pm_type == PM_DEAD ) {
2103 return;
2104 }
2105
2106 if ( level.intermissiontime ) {
2107 return;
2108 }
2109
2110 if ( !attacker )
2111 return;
2112
2113 //check player stuff
2114 g_dontFrickinCheck = qfalse;
2115
2116 if (level.gametype == GT_POWERDUEL)
2117 { //don't want to wait til later in the frame if this is the case
2118 CheckExitRules();
2119
2120 if ( level.intermissiontime )
2121 {
2122 return;
2123 }
2124 }
2125
2126 if (self->s.eType == ET_NPC &&
2127 self->s.NPC_class == CLASS_VEHICLE &&
2128 self->m_pVehicle &&
2129 !self->m_pVehicle->m_pVehicleInfo->explosionDelay &&
2130 (self->m_pVehicle->m_pPilot || self->m_pVehicle->m_iNumPassengers > 0 || self->m_pVehicle->m_pDroidUnit))
2131 { //kill everyone on board in the name of the attacker... if the vehicle has no death delay
2132 gentity_t *murderer = NULL;
2133 gentity_t *killEnt;
2134
2135 if (self->client->ps.otherKillerTime >= level.time)
2136 { //use the last attacker
2137 murderer = &g_entities[self->client->ps.otherKiller];
2138 if (!murderer->inuse || !murderer->client)
2139 {
2140 murderer = NULL;
2141 }
2142 else
2143 {
2144 if (murderer->s.number >= MAX_CLIENTS &&
2145 murderer->s.eType == ET_NPC &&
2146 murderer->s.NPC_class == CLASS_VEHICLE &&
2147 murderer->m_pVehicle &&
2148 murderer->m_pVehicle->m_pPilot)
2149 {
2150 gentity_t *murderPilot = &g_entities[murderer->m_pVehicle->m_pPilot->s.number];
2151 if (murderPilot->inuse && murderPilot->client)
2152 { //give the pilot of the offending vehicle credit for the kill
2153 murderer = murderPilot;
2154 }
2155 }
2156 }
2157 }
2158 else if (attacker && attacker->inuse && attacker->client)
2159 {
2160 if (attacker->s.number >= MAX_CLIENTS &&
2161 attacker->s.eType == ET_NPC &&
2162 attacker->s.NPC_class == CLASS_VEHICLE &&
2163 attacker->m_pVehicle &&
2164 attacker->m_pVehicle->m_pPilot)
2165 { //set vehicles pilot's killer as murderer
2166 murderer = &g_entities[attacker->m_pVehicle->m_pPilot->s.number];
2167 if (murderer->inuse && murderer->client &&murderer->client->ps.otherKillerTime >= level.time)
2168 {
2169 murderer = &g_entities[murderer->client->ps.otherKiller];
2170 if (!murderer->inuse || !murderer->client)
2171 {
2172 murderer = NULL;
2173 }
2174 }
2175 else
2176 {
2177 murderer = NULL;
2178 }
2179 }
2180 else
2181 {
2182 murderer = &g_entities[attacker->s.number];
2183 }
2184 }
2185 else if (self->m_pVehicle->m_pPilot)
2186 {
2187 murderer = (gentity_t *)self->m_pVehicle->m_pPilot;
2188 if (!murderer->inuse || !murderer->client)
2189 {
2190 murderer = NULL;
2191 }
2192 }
2193
2194 //no valid murderer.. just use self I guess
2195 if (!murderer)
2196 {
2197 murderer = self;
2198 }
2199
2200 if ( self->m_pVehicle->m_pVehicleInfo->hideRider )
2201 {//pilot is *inside* me, so kill him, too
2202 killEnt = (gentity_t *)self->m_pVehicle->m_pPilot;
2203 if (killEnt && killEnt->inuse && killEnt->client)
2204 {
2205 G_Damage(killEnt, murderer, murderer, NULL, killEnt->client->ps.origin, 99999, DAMAGE_NO_PROTECTION, MOD_BLASTER);
2206 }
2207 if ( self->m_pVehicle->m_pVehicleInfo )
2208 {//FIXME: this wile got stuck in an endless loop, that's BAD!! This method SUCKS (not initting "i", not incrementing it or using it directly, all sorts of badness), so I'm rewriting it
2209 //while (i < self->m_pVehicle->m_iNumPassengers)
2210 int numPass = self->m_pVehicle->m_iNumPassengers;
2211 for ( i = 0; i < numPass && self->m_pVehicle->m_iNumPassengers; i++ )
2212 {//go through and eject the last passenger
2213 killEnt = (gentity_t *)self->m_pVehicle->m_ppPassengers[self->m_pVehicle->m_iNumPassengers-1];
2214 if ( killEnt )
2215 {
2216 self->m_pVehicle->m_pVehicleInfo->Eject(self->m_pVehicle, (bgEntity_t *)killEnt, qtrue);
2217 if ( killEnt->inuse && killEnt->client )
2218 {
2219 G_Damage(killEnt, murderer, murderer, NULL, killEnt->client->ps.origin, 99999, DAMAGE_NO_PROTECTION, MOD_BLASTER);
2220 }
2221 }
2222 }
2223 }
2224 }
2225 killEnt = (gentity_t *)self->m_pVehicle->m_pDroidUnit;
2226 if (killEnt && killEnt->inuse && killEnt->client)
2227 {
2228 killEnt->flags &= ~FL_UNDYING;
2229 G_Damage(killEnt, murderer, murderer, NULL, killEnt->client->ps.origin, 99999, DAMAGE_NO_PROTECTION, MOD_BLASTER);
2230 }
2231 }
2232
2233 self->client->ps.emplacedIndex = 0;
2234
2235 G_BreakArm(self, 0); //unbreak anything we have broken
2236 self->client->ps.saberEntityNum = self->client->saberStoredIndex; //in case we died while our saber was knocked away.
2237
2238 if (self->client->ps.weapon == WP_SABER && self->client->saberKnockedTime)
2239 {
2240 gentity_t *saberEnt = &g_entities[self->client->ps.saberEntityNum];
2241 //trap->Print("DEBUG: Running saber cleanup for %s\n", self->client->pers.netname);
2242 self->client->saberKnockedTime = 0;
2243 saberReactivate(saberEnt, self);
2244 saberEnt->r.contents = CONTENTS_LIGHTSABER;
2245 saberEnt->think = saberBackToOwner;
2246 saberEnt->nextthink = level.time;
2247 G_RunObject(saberEnt);
2248 }
2249
2250 self->client->bodyGrabIndex = ENTITYNUM_NONE;
2251 self->client->bodyGrabTime = 0;
2252
2253 if (self->client->holdingObjectiveItem > 0)
2254 { //carrying a siege objective item - make sure it updates and removes itself from us now in case this is an instant death-respawn situation
2255 gentity_t *objectiveItem = &g_entities[self->client->holdingObjectiveItem];
2256
2257 if (objectiveItem->inuse && objectiveItem->think)
2258 {
2259 objectiveItem->think(objectiveItem);
2260 }
2261 }
2262
2263 if ( (self->client->inSpaceIndex && self->client->inSpaceIndex != ENTITYNUM_NONE) ||
2264 (self->client->ps.eFlags2 & EF2_SHIP_DEATH) )
2265 {
2266 self->client->noCorpse = qtrue;
2267 }
2268
2269 if ( self->client->NPC_class != CLASS_VEHICLE
2270 && self->client->ps.m_iVehicleNum )
2271 { //I'm riding a vehicle
2272 //tell it I'm getting off
2273 gentity_t *veh = &g_entities[self->client->ps.m_iVehicleNum];
2274
2275 if (veh->inuse && veh->client && veh->m_pVehicle)
2276 {
2277 veh->m_pVehicle->m_pVehicleInfo->Eject(veh->m_pVehicle, (bgEntity_t *)self, qtrue);
2278
2279 if (veh->m_pVehicle->m_pVehicleInfo->type == VH_FIGHTER)
2280 { //go into "die in ship" mode with flag
2281 self->client->ps.eFlags2 |= EF2_SHIP_DEATH;
2282
2283 //put me over where my vehicle exploded
2284 G_SetOrigin(self, veh->client->ps.origin);
2285 VectorCopy(veh->client->ps.origin, self->client->ps.origin);
2286 }
2287 }
2288 //droids throw heads if they haven't yet
2289 switch(self->client->NPC_class)
2290 {
2291 case CLASS_R2D2:
2292 if ( !trap->G2API_GetSurfaceRenderStatus( self->ghoul2, 0, "head" ) )
2293 {
2294 vec3_t up;
2295 AngleVectors( self->r.currentAngles, NULL, NULL, up );
2296 G_PlayEffectID( G_EffectIndex("chunks/r2d2head_veh"), self->r.currentOrigin, up );
2297 }
2298 break;
2299
2300 case CLASS_R5D2:
2301 if ( !trap->G2API_GetSurfaceRenderStatus( self->ghoul2, 0, "head" ) )
2302 {
2303 vec3_t up;
2304 AngleVectors( self->r.currentAngles, NULL, NULL, up );
2305 G_PlayEffectID( G_EffectIndex("chunks/r5d2head_veh"), self->r.currentOrigin, up );
2306 }
2307 break;
2308 default:
2309 break;
2310 }
2311 }
2312
2313 if ( self->NPC )
2314 {
2315 if ( self->client && Jedi_WaitingAmbush( self ) )
2316 {//ambushing trooper
2317 self->client->noclip = qfalse;
2318 }
2319 NPC_FreeCombatPoint( self->NPC->combatPoint, qfalse );
2320 if ( self->NPC->group )
2321 {
2322 //lastInGroup = (self->NPC->group->numGroup < 2);
2323 AI_GroupMemberKilled( self );
2324 AI_DeleteSelfFromGroup( self );
2325 }
2326
2327 if ( self->NPC->tempGoal )
2328 {
2329 G_FreeEntity( self->NPC->tempGoal );
2330 self->NPC->tempGoal = NULL;
2331 }
2332 /*
2333 if ( self->s.eFlags & EF_LOCKED_TO_WEAPON )
2334 {
2335 // dumb, just get the NPC out of the chair
2336 extern void RunEmplacedWeapon( gentity_t *ent, usercmd_t **ucmd );
2337
2338 usercmd_t cmd, *ad_cmd;
2339
2340 memset( &cmd, 0, sizeof( usercmd_t ));
2341
2342 //gentity_t *old = self->owner;
2343
2344 if ( self->owner )
2345 {
2346 self->owner->s.frame = self->owner->startFrame = self->owner->endFrame = 0;
2347 self->owner->svFlags &= ~SVF_ANIMATING;
2348 }
2349
2350 cmd.buttons |= BUTTON_USE;
2351 ad_cmd = &cmd;
2352 RunEmplacedWeapon( self, &ad_cmd );
2353 //self->owner = old;
2354 }
2355 */
2356 if ( self->client->NPC_class == CLASS_BOBAFETT && self->client->ps.eFlags2 & EF2_FLYING )
2357 Boba_FlyStop( self );
2358 if ( self->s.NPC_class == CLASS_RANCOR )
2359 Rancor_DropVictim( self );
2360 }
2361 if ( attacker && attacker->NPC && attacker->NPC->group && attacker->NPC->group->enemy == self )
2362 {
2363 attacker->NPC->group->enemy = NULL;
2364 }
2365
2366 //Cheap method until/if I decide to put fancier stuff in (e.g. sabers falling out of hand and slowly
2367 //holstering on death like sp)
2368 if (self->client->ps.weapon == WP_SABER &&
2369 !self->client->ps.saberHolstered &&
2370 self->client->ps.saberEntityNum)
2371 {
2372 if (!self->client->ps.saberInFlight &&
2373 self->client->saber[0].soundOff)
2374 {
2375 G_Sound(self, CHAN_AUTO, self->client->saber[0].soundOff);
2376 }
2377 if (self->client->saber[1].soundOff &&
2378 self->client->saber[1].model[0])
2379 {
2380 G_Sound(self, CHAN_AUTO, self->client->saber[1].soundOff);
2381 }
2382 }
2383
2384 //Use any target we had
2385 G_UseTargets( self, self );
2386
2387 if (g_slowmoDuelEnd.integer && (level.gametype == GT_DUEL || level.gametype == GT_POWERDUEL) && attacker && attacker->inuse && attacker->client)
2388 {
2389 if (!gDoSlowMoDuel)
2390 {
2391 gDoSlowMoDuel = qtrue;
2392 gSlowMoDuelTime = level.time;
2393 }
2394 }
2395 /*
2396 else if (self->NPC && attacker && attacker->client && attacker->s.number < MAX_CLIENTS && !gDoSlowMoDuel)
2397 {
2398 gDoSlowMoDuel = qtrue;
2399 gSlowMoDuelTime = level.time;
2400 }
2401 */
2402
2403 //Make sure the jetpack is turned off.
2404 Jetpack_Off(self);
2405
2406 self->client->ps.heldByClient = 0;
2407 self->client->beingThrown = 0;
2408 self->client->doingThrow = 0;
2409 BG_ClearRocketLock( &self->client->ps );
2410 self->client->isHacking = 0;
2411 self->client->ps.hackingTime = 0;
2412
2413 if (inflictor && inflictor->activator && !inflictor->client && !attacker->client &&
2414 inflictor->activator->client && inflictor->activator->inuse &&
2415 inflictor->s.weapon == WP_TURRET)
2416 {
2417 attacker = inflictor->activator;
2418 }
2419
2420 if (self->client && self->client->ps.isJediMaster)
2421 {
2422 wasJediMaster = qtrue;
2423 }
2424
2425 //if he was charging or anything else, kill the sound
2426 G_MuteSound(self->s.number, CHAN_WEAPON);
2427
2428 //FIXME: this may not be enough
2429 if ( level.gametype == GT_SIEGE && meansOfDeath == MOD_TEAM_CHANGE )
2430 RemoveDetpacks( self );
2431 else
2432 BlowDetpacks(self); //blow detpacks if they're planted
2433
2434 self->client->ps.fd.forceDeactivateAll = 1;
2435
2436 if ((self == attacker || (attacker && !attacker->client)) &&
2437 (meansOfDeath == MOD_CRUSH || meansOfDeath == MOD_FALLING || meansOfDeath == MOD_TRIGGER_HURT || meansOfDeath == MOD_UNKNOWN) &&
2438 self->client->ps.otherKillerTime > level.time)
2439 {
2440 attacker = &g_entities[self->client->ps.otherKiller];
2441 }
2442
2443 // check for an almost capture
2444 CheckAlmostCapture( self, attacker );
2445
2446 self->client->ps.pm_type = PM_DEAD;
2447 self->client->ps.pm_flags &= ~PMF_STUCK_TO_WALL;
2448
2449 if ( attacker ) {
2450 killer = attacker->s.number;
2451 if ( attacker->client ) {
2452 killerName = attacker->client->pers.netname;
2453 } else {
2454 killerName = "<non-client>";
2455 }
2456 } else {
2457 killer = ENTITYNUM_WORLD;
2458 killerName = "<world>";
2459 }
2460
2461 if ( killer < 0 || killer >= MAX_CLIENTS ) {
2462 killer = ENTITYNUM_WORLD;
2463 killerName = "<world>";
2464 }
2465
2466 if ( meansOfDeath < 0 || meansOfDeath >= sizeof( modNames ) / sizeof( modNames[0] ) ) {
2467 obit = "<bad obituary>";
2468 } else {
2469 obit = modNames[ meansOfDeath ];
2470 }
2471
2472 // log the victim and attacker's names with the method of death
2473 Com_sprintf( buf, sizeof( buf ), "Kill: %i %i %i: %s killed ", killer, self->s.number, meansOfDeath, killerName );
2474 if ( self->s.eType == ET_NPC ) {
2475 // check for named NPCs
2476 if ( self->targetname )
2477 Q_strcat( buf, sizeof( buf ), va( "%s (%s) by %s\n", self->NPC_type, self->targetname, obit ) );
2478 else
2479 Q_strcat( buf, sizeof( buf ), va( "%s by %s\n", self->NPC_type, obit ) );
2480 }
2481 else
2482 Q_strcat( buf, sizeof( buf ), va( "%s by %s\n", self->client->pers.netname, obit ) );
2483 G_LogPrintf( "%s", buf );
2484
2485 if ( g_austrian.integer
2486 && level.gametype == GT_DUEL
2487 && level.numPlayingClients >= 2 )
2488 {
2489 int spawnTime = (level.clients[level.sortedClients[0]].respawnTime > level.clients[level.sortedClients[1]].respawnTime) ? level.clients[level.sortedClients[0]].respawnTime : level.clients[level.sortedClients[1]].respawnTime;
2490 G_LogPrintf("Duel Kill Details:\n");
2491 G_LogPrintf("Kill Time: %d\n", level.time-spawnTime );
2492 G_LogPrintf("victim: %s, hits on enemy %d\n", self->client->pers.netname, self->client->ps.persistant[PERS_HITS] );
2493 if ( attacker && attacker->client )
2494 {
2495 G_LogPrintf("killer: %s, hits on enemy %d, health: %d\n", attacker->client->pers.netname, attacker->client->ps.persistant[PERS_HITS], attacker->health );
2496 //also - if MOD_SABER, list the animation and saber style
2497 if ( meansOfDeath == MOD_SABER )
2498 {
2499 G_LogPrintf("killer saber style: %d, killer saber anim %s\n", attacker->client->ps.fd.saberAnimLevel, animTable[(attacker->client->ps.torsoAnim)].name );
2500 }
2501 }
2502 }
2503
2504 G_LogWeaponKill(killer, meansOfDeath);
2505 G_LogWeaponDeath(self->s.number, self->s.weapon);
2506 if (attacker && attacker->client && attacker->inuse)
2507 {
2508 G_LogWeaponFrag(killer, self->s.number);
2509 }
2510
2511 // broadcast the death event to everyone
2512 if (self->s.eType != ET_NPC && !g_noPDuelCheck)
2513 {
2514 ent = G_TempEntity( self->r.currentOrigin, EV_OBITUARY );
2515 ent->s.eventParm = meansOfDeath;
2516 ent->s.otherEntityNum = self->s.number;
2517 ent->s.otherEntityNum2 = killer;
2518 ent->r.svFlags = SVF_BROADCAST; // send to everyone
2519 ent->s.isJediMaster = wasJediMaster;
2520 }
2521
2522 self->enemy = attacker;
2523
2524 self->client->ps.persistant[PERS_KILLED]++;
2525
2526 if (self == attacker)
2527 {
2528 self->client->ps.fd.suicides++;
2529 }
2530
2531 if (attacker && attacker->client) {
2532 attacker->client->lastkilled_client = self->s.number;
2533
2534 G_CheckVictoryScript(attacker);
2535
2536 if ( attacker == self || OnSameTeam (self, attacker ) ) {
2537 if (level.gametype == GT_DUEL)
2538 { //in duel, if you kill yourself, the person you are dueling against gets a kill for it
2539 int otherClNum = -1;
2540 if (level.sortedClients[0] == self->s.number)
2541 {
2542 otherClNum = level.sortedClients[1];
2543 }
2544 else if (level.sortedClients[1] == self->s.number)
2545 {
2546 otherClNum = level.sortedClients[0];
2547 }
2548
2549 if (otherClNum >= 0 && otherClNum < MAX_CLIENTS &&
2550 g_entities[otherClNum].inuse && g_entities[otherClNum].client &&
2551 otherClNum != attacker->s.number)
2552 {
2553 AddScore( &g_entities[otherClNum], self->r.currentOrigin, 1 );
2554 }
2555 else
2556 {
2557 AddScore( attacker, self->r.currentOrigin, -1 );
2558 }
2559 }
2560 else
2561 {
2562 AddScore( attacker, self->r.currentOrigin, -1 );
2563 }
2564 if (level.gametype == GT_JEDIMASTER)
2565 {
2566 if (self->client && self->client->ps.isJediMaster)
2567 { //killed ourself so return the saber to the original position
2568 //(to avoid people jumping off ledges and making the saber
2569 //unreachable for 60 seconds)
2570 ThrowSaberToAttacker(self, NULL);
2571 self->client->ps.isJediMaster = qfalse;
2572 }
2573 }
2574 } else {
2575 if (level.gametype == GT_JEDIMASTER)
2576 {
2577 if ((attacker->client && attacker->client->ps.isJediMaster) ||
2578 (self->client && self->client->ps.isJediMaster))
2579 {
2580 AddScore( attacker, self->r.currentOrigin, 1 );
2581
2582 if (self->client && self->client->ps.isJediMaster)
2583 {
2584 ThrowSaberToAttacker(self, attacker);
2585 self->client->ps.isJediMaster = qfalse;
2586 }
2587 }
2588 else
2589 {
2590 gentity_t *jmEnt = G_GetJediMaster();
2591
2592 if (jmEnt && jmEnt->client)
2593 {
2594 AddScore( jmEnt, self->r.currentOrigin, 1 );
2595 }
2596 }
2597 }
2598 else
2599 {
2600 AddScore( attacker, self->r.currentOrigin, 1 );
2601 }
2602
2603 if( meansOfDeath == MOD_STUN_BATON ) {
2604
2605 // play humiliation on player
2606 attacker->client->ps.persistant[PERS_GAUNTLET_FRAG_COUNT]++;
2607
2608 attacker->client->rewardTime = level.time + REWARD_SPRITE_TIME;
2609
2610 // also play humiliation on target
2611 self->client->ps.persistant[PERS_PLAYEREVENTS] ^= PLAYEREVENT_GAUNTLETREWARD;
2612 }
2613
2614 // check for two kills in a short amount of time
2615 // if this is close enough to the last kill, give a reward sound
2616 if ( level.time - attacker->client->lastKillTime < CARNAGE_REWARD_TIME ) {
2617 // play excellent on player
2618 attacker->client->ps.persistant[PERS_EXCELLENT_COUNT]++;
2619
2620 attacker->client->rewardTime = level.time + REWARD_SPRITE_TIME;
2621 }
2622 attacker->client->lastKillTime = level.time;
2623
2624 }
2625 } else {
2626 if (self->client && self->client->ps.isJediMaster)
2627 { //killed ourself so return the saber to the original position
2628 //(to avoid people jumping off ledges and making the saber
2629 //unreachable for 60 seconds)
2630 ThrowSaberToAttacker(self, NULL);
2631 self->client->ps.isJediMaster = qfalse;
2632 }
2633
2634 if (level.gametype == GT_DUEL)
2635 { //in duel, if you kill yourself, the person you are dueling against gets a kill for it
2636 int otherClNum = -1;
2637 if (level.sortedClients[0] == self->s.number)
2638 {
2639 otherClNum = level.sortedClients[1];
2640 }
2641 else if (level.sortedClients[1] == self->s.number)
2642 {
2643 otherClNum = level.sortedClients[0];
2644 }
2645
2646 if (otherClNum >= 0 && otherClNum < MAX_CLIENTS &&
2647 g_entities[otherClNum].inuse && g_entities[otherClNum].client &&
2648 otherClNum != self->s.number)
2649 {
2650 AddScore( &g_entities[otherClNum], self->r.currentOrigin, 1 );
2651 }
2652 else
2653 {
2654 AddScore( self, self->r.currentOrigin, -1 );
2655 }
2656 }
2657 else
2658 {
2659 AddScore( self, self->r.currentOrigin, -1 );
2660 }
2661 }
2662
2663 // Add team bonuses
2664 Team_FragBonuses(self, inflictor, attacker);
2665
2666 // if I committed suicide, the flag does not fall, it returns.
2667 if (meansOfDeath == MOD_SUICIDE) {
2668 if ( self->client->ps.powerups[PW_NEUTRALFLAG] ) { // only happens in One Flag CTF
2669 Team_ReturnFlag( TEAM_FREE );
2670 self->client->ps.powerups[PW_NEUTRALFLAG] = 0;
2671 }
2672 else if ( self->client->ps.powerups[PW_REDFLAG] ) { // only happens in standard CTF
2673 Team_ReturnFlag( TEAM_RED );
2674 self->client->ps.powerups[PW_REDFLAG] = 0;
2675 }
2676 else if ( self->client->ps.powerups[PW_BLUEFLAG] ) { // only happens in standard CTF
2677 Team_ReturnFlag( TEAM_BLUE );
2678 self->client->ps.powerups[PW_BLUEFLAG] = 0;
2679 }
2680 }
2681
2682 if (!self->client->ps.fallingToDeath) {
2683 if (self->s.eType != ET_NPC)
2684 {
2685 TossClientItems( self );
2686 }
2687 }
2688 else {
2689 if ( self->client->ps.powerups[PW_NEUTRALFLAG] ) { // only happens in One Flag CTF
2690 Team_ReturnFlag( TEAM_FREE );
2691 self->client->ps.powerups[PW_NEUTRALFLAG] = 0;
2692 }
2693 else if ( self->client->ps.powerups[PW_REDFLAG] ) { // only happens in standard CTF
2694 Team_ReturnFlag( TEAM_RED );
2695 self->client->ps.powerups[PW_REDFLAG] = 0;
2696 }
2697 else if ( self->client->ps.powerups[PW_BLUEFLAG] ) { // only happens in standard CTF
2698 Team_ReturnFlag( TEAM_BLUE );
2699 self->client->ps.powerups[PW_BLUEFLAG] = 0;
2700 }
2701 }
2702
2703 if ( MOD_TEAM_CHANGE == meansOfDeath )
2704 {
2705 // Give them back a point since they didn't really die.
2706 AddScore( self, self->r.currentOrigin, 1 );
2707 }
2708 else
2709 {
2710 Cmd_Score_f( self ); // show scores
2711 }
2712
2713 // send updated scores to any clients that are following this one,
2714 // or they would get stale scoreboards
2715 for ( i = 0 ; i < level.maxclients ; i++ ) {
2716 gclient_t *cl = &level.clients[i];
2717 if ( cl->pers.connected != CON_CONNECTED ) {
2718 continue;
2719 }
2720 if ( cl->sess.sessionTeam != TEAM_SPECTATOR ) {
2721 continue;
2722 }
2723 if ( cl->sess.spectatorClient == self->s.number ) {
2724 Cmd_Score_f( g_entities + i );
2725 }
2726 }
2727
2728 self->takedamage = qtrue; // can still be gibbed
2729
2730 self->s.weapon = WP_NONE;
2731 self->s.powerups = 0;
2732 if (self->s.eType != ET_NPC)
2733 { //handled differently for NPCs
2734 self->r.contents = CONTENTS_CORPSE;
2735 }
2736 self->client->ps.zoomMode = 0; // Turn off zooming when we die
2737
2738 //rww - 07/19/02 - I removed this because it isn't working and it's ugly (for people on the outside)
2739 /*
2740 self->s.angles[0] = 0;
2741 self->s.angles[2] = 0;
2742 LookAtKiller (self, inflictor, attacker);
2743
2744 VectorCopy( self->s.angles, self->client->ps.viewangles );
2745 */
2746
2747 self->s.loopSound = 0;
2748 self->s.loopIsSoundset = qfalse;
2749
2750 if (self->s.eType != ET_NPC)
2751 { //handled differently for NPCs
2752 self->r.maxs[2] = -8;
2753 }
2754
2755 // don't allow respawn until the death anim is done
2756 // g_forcerespawn may force spawning at some later time
2757 self->client->respawnTime = level.time + 1700;
2758
2759 // remove powerups
2760 memset( self->client->ps.powerups, 0, sizeof(self->client->ps.powerups) );
2761
2762 self->client->ps.stats[STAT_HOLDABLE_ITEMS] = 0;
2763 self->client->ps.stats[STAT_HOLDABLE_ITEM] = 0;
2764
2765 // NOTENOTE No gib deaths right now, this is star wars.
2766 /*
2767 // never gib in a nodrop
2768 if ( (self->health <= GIB_HEALTH && !(contents & CONTENTS_NODROP) && g_blood.integer) || meansOfDeath == MOD_SUICIDE)
2769 {
2770 // gib death
2771 GibEntity( self, killer );
2772 }
2773 else
2774 */
2775 {
2776 // normal death
2777
2778 static int deathAnim;
2779
2780 anim = G_PickDeathAnim(self, self->pos1, damage, meansOfDeath, HL_NONE);
2781
2782 if (anim >= 1)
2783 { //Some droids don't have death anims
2784 // for the no-blood option, we need to prevent the health
2785 // from going to gib level
2786 if ( self->health <= GIB_HEALTH ) {
2787 self->health = GIB_HEALTH+1;
2788 }
2789
2790 self->client->respawnTime = level.time + 1000;//((self->client->animations[anim].numFrames*40)/(50.0f / self->client->animations[anim].frameLerp))+300;
2791
2792 sPMType = self->client->ps.pm_type;
2793 self->client->ps.pm_type = PM_NORMAL; //don't want pm type interfering with our setanim calls.
2794
2795 if (self->inuse)
2796 { //not disconnecting
2797 G_SetAnim(self, NULL, SETANIM_BOTH, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD|SETANIM_FLAG_RESTART, 0);
2798 }
2799
2800 self->client->ps.pm_type = sPMType;
2801
2802 if (meansOfDeath == MOD_SABER || (meansOfDeath == MOD_MELEE && G_HeavyMelee( attacker )) )//saber or heavy melee (claws)
2803 { //update the anim on the actual skeleton (so bolt point will reflect the correct position) and then check for dismem
2804 G_UpdateClientAnims(self, 1.0f);
2805 G_CheckForDismemberment(self, attacker, self->pos1, damage, anim, qfalse);
2806 }
2807 }
2808 else if (self->NPC && self->client && self->client->NPC_class != CLASS_MARK1 &&
2809 self->client->NPC_class != CLASS_VEHICLE)
2810 { //in this case if we're an NPC it's my guess that we want to get removed straight away.
2811 self->think = G_FreeEntity;
2812 self->nextthink = level.time;
2813 }
2814
2815 //self->client->ps.legsAnim = anim;
2816 //self->client->ps.torsoAnim = anim;
2817 // self->client->ps.pm_flags |= PMF_UPDATE_ANIM; // Make sure the pmove sets up the GHOUL2 anims.
2818
2819 //rww - do this on respawn, not death
2820 //CopyToBodyQue (self);
2821
2822 //G_AddEvent( self, EV_DEATH1 + i, killer );
2823 if (wasJediMaster)
2824 {
2825 G_AddEvent( self, EV_DEATH1 + deathAnim, 1 );
2826 }
2827 else
2828 {
2829 G_AddEvent( self, EV_DEATH1 + deathAnim, 0 );
2830 }
2831
2832 if (self != attacker)
2833 { //don't make NPCs want to murder you on respawn for killing yourself!
2834 G_DeathAlert( self, attacker );
2835 }
2836
2837 // the body can still be gibbed
2838 if (!self->NPC)
2839 { //don't remove NPCs like this!
2840 self->die = body_die;
2841 }
2842
2843 //It won't gib, it will disintegrate (because this is Star Wars).
2844 self->takedamage = qtrue;
2845
2846 // globally cycle through the different death animations
2847 deathAnim = ( deathAnim + 1 ) % 3;
2848 }
2849
2850 if ( self->NPC )
2851 {//If an NPC, make sure we start running our scripts again- this gets set to infinite while we fall to our deaths
2852 self->NPC->nextBStateThink = level.time;
2853 }
2854
2855 if ( G_ActivateBehavior( self, BSET_DEATH ) )
2856 {
2857 //deathScript = qtrue;
2858 }
2859
2860 if ( self->NPC && (self->NPC->scriptFlags&SCF_FFDEATH) )
2861 {
2862 if ( G_ActivateBehavior( self, BSET_FFDEATH ) )
2863 {//FIXME: should running this preclude running the normal deathscript?
2864 //deathScript = qtrue;
2865 }
2866 G_UseTargets2( self, self, self->target4 );
2867 }
2868
2869 /*
2870 if ( !deathScript && !(self->svFlags&SVF_KILLED_SELF) )
2871 {
2872 //Should no longer run scripts
2873 //WARNING!!! DO NOT DO THIS WHILE RUNNING A SCRIPT, ICARUS WILL CRASH!!!
2874 //FIXME: shouldn't ICARUS handle this internally?
2875 ICARUS_FreeEnt(self);
2876 }
2877 */
2878 //rwwFIXMEFIXME: Do this too?
2879
2880 // Free up any timers we may have on us.
2881 TIMER_Clear2( self );
2882
2883 trap->LinkEntity ((sharedEntity_t *)self);
2884
2885 if ( self->NPC )
2886 {
2887 self->NPC->timeOfDeath = level.time;//this will change - used for debouncing post-death events
2888 }
2889
2890 // Start any necessary death fx for this entity
2891 if ( self->NPC )
2892 DeathFX( self );
2893
2894
2895 if (level.gametype == GT_POWERDUEL && !g_noPDuelCheck)
2896 { //powerduel checks
2897 if (self->client->sess.duelTeam == DUELTEAM_LONE)
2898 { //automatically means a win as there is only one
2899 G_AddPowerDuelScore(DUELTEAM_DOUBLE, 1);
2900 G_AddPowerDuelLoserScore(DUELTEAM_LONE, 1);
2901 g_endPDuel = qtrue;
2902 }
2903 else if (self->client->sess.duelTeam == DUELTEAM_DOUBLE)
2904 {
2905 gentity_t *check;
2906 qboolean heLives = qfalse;
2907
2908 for ( i=0; i<MAX_CLIENTS; i++ )
2909 {
2910 check = &g_entities[i];
2911 if (check->inuse && check->client && check->s.number != self->s.number &&
2912 check->client->pers.connected == CON_CONNECTED && !check->client->iAmALoser &&
2913 check->client->ps.stats[STAT_HEALTH] > 0 &&
2914 check->client->sess.sessionTeam != TEAM_SPECTATOR &&
2915 check->client->sess.duelTeam == DUELTEAM_DOUBLE)
2916 { //still an active living paired duelist so it's not over yet.
2917 heLives = qtrue;
2918 break;
2919 }
2920 }
2921
2922 if (!heLives)
2923 { //they're all dead, give the lone duelist the win.
2924 G_AddPowerDuelScore(DUELTEAM_LONE, 1);
2925 G_AddPowerDuelLoserScore(DUELTEAM_DOUBLE, 1);
2926 g_endPDuel = qtrue;
2927 }
2928 }
2929 }
2930 }
2931
2932
2933 /*
2934 ================
2935 CheckArmor
2936 ================
2937 */
CheckArmor(gentity_t * ent,int damage,int dflags)2938 int CheckArmor (gentity_t *ent, int damage, int dflags)
2939 {
2940 gclient_t *client;
2941 int save;
2942 int count;
2943
2944 if (!damage)
2945 return 0;
2946
2947 client = ent->client;
2948
2949 if (!client)
2950 return 0;
2951
2952 if (dflags & DAMAGE_NO_ARMOR)
2953 return 0;
2954
2955 if ( client->NPC_class == CLASS_VEHICLE
2956 && ent->m_pVehicle
2957 && ent->client->ps.electrifyTime > level.time )
2958 {//ion-cannon has disabled this ship's shields, take damage on hull!
2959 return 0;
2960 }
2961 // armor
2962 count = client->ps.stats[STAT_ARMOR];
2963
2964 if (dflags & DAMAGE_HALF_ABSORB)
2965 { // Half the damage gets absorbed by the shields, rather than 100%
2966 save = ceil( damage * ARMOR_PROTECTION );
2967 }
2968 else
2969 { // All the damage gets absorbed by the shields.
2970 save = damage;
2971 }
2972
2973 // save is the most damage that the armor is elibigle to protect, of course, but it's limited by the total armor.
2974 if (save >= count)
2975 save = count;
2976
2977 if (!save)
2978 return 0;
2979
2980 if (dflags & DAMAGE_HALF_ARMOR_REDUCTION) // Armor isn't whittled so easily by sniper shots.
2981 {
2982 client->ps.stats[STAT_ARMOR] -= (int)(save*ARMOR_REDUCTION_FACTOR);
2983 }
2984 else
2985 {
2986 client->ps.stats[STAT_ARMOR] -= save;
2987 }
2988
2989 return save;
2990 }
2991
2992
G_ApplyKnockback(gentity_t * targ,vec3_t newDir,float knockback)2993 void G_ApplyKnockback( gentity_t *targ, vec3_t newDir, float knockback )
2994 {
2995 vec3_t kvel;
2996 float mass;
2997
2998 if ( targ->physicsBounce > 0 ) //overide the mass
2999 mass = targ->physicsBounce;
3000 else
3001 mass = 200;
3002
3003 if ( g_gravity.value > 0 )
3004 {
3005 VectorScale( newDir, g_knockback.value * (float)knockback / mass * 0.8, kvel );
3006 kvel[2] = newDir[2] * g_knockback.value * (float)knockback / mass * 1.5;
3007 }
3008 else
3009 {
3010 VectorScale( newDir, g_knockback.value * (float)knockback / mass, kvel );
3011 }
3012
3013 if ( targ->client )
3014 {
3015 VectorAdd( targ->client->ps.velocity, kvel, targ->client->ps.velocity );
3016 }
3017 else if ( targ->s.pos.trType != TR_STATIONARY && targ->s.pos.trType != TR_LINEAR_STOP && targ->s.pos.trType != TR_NONLINEAR_STOP )
3018 {
3019 VectorAdd( targ->s.pos.trDelta, kvel, targ->s.pos.trDelta );
3020 VectorCopy( targ->r.currentOrigin, targ->s.pos.trBase );
3021 targ->s.pos.trTime = level.time;
3022 }
3023
3024 // set the timer so that the other client can't cancel
3025 // out the movement immediately
3026 if ( targ->client && !targ->client->ps.pm_time )
3027 {
3028 int t;
3029
3030 t = knockback * 2;
3031 if ( t < 50 ) {
3032 t = 50;
3033 }
3034 if ( t > 200 ) {
3035 t = 200;
3036 }
3037 targ->client->ps.pm_time = t;
3038 targ->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
3039 }
3040 }
3041
3042 /*
3043 ================
3044 RaySphereIntersections
3045 ================
3046 */
RaySphereIntersections(vec3_t origin,float radius,vec3_t point,vec3_t dir,vec3_t intersections[2])3047 int RaySphereIntersections( vec3_t origin, float radius, vec3_t point, vec3_t dir, vec3_t intersections[2] ) {
3048 float b, c, d, t;
3049
3050 // | origin - (point + t * dir) | = radius
3051 // a = dir[0]^2 + dir[1]^2 + dir[2]^2;
3052 // b = 2 * (dir[0] * (point[0] - origin[0]) + dir[1] * (point[1] - origin[1]) + dir[2] * (point[2] - origin[2]));
3053 // c = (point[0] - origin[0])^2 + (point[1] - origin[1])^2 + (point[2] - origin[2])^2 - radius^2;
3054
3055 // normalize dir so a = 1
3056 VectorNormalize(dir);
3057 b = 2 * (dir[0] * (point[0] - origin[0]) + dir[1] * (point[1] - origin[1]) + dir[2] * (point[2] - origin[2]));
3058 c = (point[0] - origin[0]) * (point[0] - origin[0]) +
3059 (point[1] - origin[1]) * (point[1] - origin[1]) +
3060 (point[2] - origin[2]) * (point[2] - origin[2]) -
3061 radius * radius;
3062
3063 d = b * b - 4 * c;
3064 if (d > 0) {
3065 t = (- b + sqrt(d)) / 2;
3066 VectorMA(point, t, dir, intersections[0]);
3067 t = (- b - sqrt(d)) / 2;
3068 VectorMA(point, t, dir, intersections[1]);
3069 return 2;
3070 }
3071 else if (d == 0) {
3072 t = (- b ) / 2;
3073 VectorMA(point, t, dir, intersections[0]);
3074 return 1;
3075 }
3076 return 0;
3077 }
3078
3079 /*
3080 ===================================
3081 rww - beginning of the majority of the dismemberment and location based damage code.
3082 ===================================
3083 */
3084 char *hitLocName[HL_MAX] =
3085 {
3086 "none", //HL_NONE = 0,
3087 "right foot", //HL_FOOT_RT,
3088 "left foot", //HL_FOOT_LT,
3089 "right leg", //HL_LEG_RT,
3090 "left leg", //HL_LEG_LT,
3091 "waist", //HL_WAIST,
3092 "back right shoulder", //HL_BACK_RT,
3093 "back left shoulder", //HL_BACK_LT,
3094 "back", //HL_BACK,
3095 "front right shouler", //HL_CHEST_RT,
3096 "front left shoulder", //HL_CHEST_LT,
3097 "chest", //HL_CHEST,
3098 "right arm", //HL_ARM_RT,
3099 "left arm", //HL_ARM_LT,
3100 "right hand", //HL_HAND_RT,
3101 "left hand", //HL_HAND_LT,
3102 "head", //HL_HEAD
3103 "generic1", //HL_GENERIC1,
3104 "generic2", //HL_GENERIC2,
3105 "generic3", //HL_GENERIC3,
3106 "generic4", //HL_GENERIC4,
3107 "generic5", //HL_GENERIC5,
3108 "generic6" //HL_GENERIC6
3109 };
3110
G_GetDismemberLoc(gentity_t * self,vec3_t boltPoint,int limbType)3111 void G_GetDismemberLoc(gentity_t *self, vec3_t boltPoint, int limbType)
3112 { //Just get the general area without using server-side ghoul2
3113 vec3_t fwd, right, up;
3114
3115 AngleVectors(self->r.currentAngles, fwd, right, up);
3116
3117 VectorCopy(self->r.currentOrigin, boltPoint);
3118
3119 switch (limbType)
3120 {
3121 case G2_MODELPART_HEAD:
3122 boltPoint[0] += up[0]*24;
3123 boltPoint[1] += up[1]*24;
3124 boltPoint[2] += up[2]*24;
3125 break;
3126 case G2_MODELPART_WAIST:
3127 boltPoint[0] += up[0]*4;
3128 boltPoint[1] += up[1]*4;
3129 boltPoint[2] += up[2]*4;
3130 break;
3131 case G2_MODELPART_LARM:
3132 boltPoint[0] += up[0]*18;
3133 boltPoint[1] += up[1]*18;
3134 boltPoint[2] += up[2]*18;
3135
3136 boltPoint[0] -= right[0]*10;
3137 boltPoint[1] -= right[1]*10;
3138 boltPoint[2] -= right[2]*10;
3139 break;
3140 case G2_MODELPART_RARM:
3141 boltPoint[0] += up[0]*18;
3142 boltPoint[1] += up[1]*18;
3143 boltPoint[2] += up[2]*18;
3144
3145 boltPoint[0] += right[0]*10;
3146 boltPoint[1] += right[1]*10;
3147 boltPoint[2] += right[2]*10;
3148 break;
3149 case G2_MODELPART_RHAND:
3150 boltPoint[0] += up[0]*8;
3151 boltPoint[1] += up[1]*8;
3152 boltPoint[2] += up[2]*8;
3153
3154 boltPoint[0] += right[0]*10;
3155 boltPoint[1] += right[1]*10;
3156 boltPoint[2] += right[2]*10;
3157 break;
3158 case G2_MODELPART_LLEG:
3159 boltPoint[0] -= up[0]*4;
3160 boltPoint[1] -= up[1]*4;
3161 boltPoint[2] -= up[2]*4;
3162
3163 boltPoint[0] -= right[0]*10;
3164 boltPoint[1] -= right[1]*10;
3165 boltPoint[2] -= right[2]*10;
3166 break;
3167 case G2_MODELPART_RLEG:
3168 boltPoint[0] -= up[0]*4;
3169 boltPoint[1] -= up[1]*4;
3170 boltPoint[2] -= up[2]*4;
3171
3172 boltPoint[0] += right[0]*10;
3173 boltPoint[1] += right[1]*10;
3174 boltPoint[2] += right[2]*10;
3175 break;
3176 default:
3177 break;
3178 }
3179
3180 return;
3181 }
3182
G_GetDismemberBolt(gentity_t * self,vec3_t boltPoint,int limbType)3183 void G_GetDismemberBolt(gentity_t *self, vec3_t boltPoint, int limbType)
3184 {
3185 int useBolt = self->genericValue5;
3186 vec3_t properOrigin, properAngles, addVel;
3187 //matrix3_t legAxis;
3188 mdxaBone_t boltMatrix;
3189 float fVSpeed = 0;
3190 char *rotateBone = NULL;
3191
3192 switch (limbType)
3193 {
3194 case G2_MODELPART_HEAD:
3195 rotateBone = "cranium";
3196 break;
3197 case G2_MODELPART_WAIST:
3198 if (self->localAnimIndex <= 1)
3199 { //humanoid
3200 rotateBone = "thoracic";
3201 }
3202 else
3203 {
3204 rotateBone = "pelvis";
3205 }
3206 break;
3207 case G2_MODELPART_LARM:
3208 rotateBone = "lradius";
3209 break;
3210 case G2_MODELPART_RARM:
3211 rotateBone = "rradius";
3212 break;
3213 case G2_MODELPART_RHAND:
3214 rotateBone = "rhand";
3215 break;
3216 case G2_MODELPART_LLEG:
3217 rotateBone = "ltibia";
3218 break;
3219 case G2_MODELPART_RLEG:
3220 rotateBone = "rtibia";
3221 break;
3222 default:
3223 rotateBone = "rtibia";
3224 break;
3225 }
3226
3227 useBolt = trap->G2API_AddBolt(self->ghoul2, 0, rotateBone);
3228
3229 VectorCopy(self->client->ps.origin, properOrigin);
3230 VectorCopy(self->client->ps.viewangles, properAngles);
3231
3232 //try to predict the origin based on velocity so it's more like what the client is seeing
3233 VectorCopy(self->client->ps.velocity, addVel);
3234 VectorNormalize(addVel);
3235
3236 if (self->client->ps.velocity[0] < 0)
3237 {
3238 fVSpeed += (-self->client->ps.velocity[0]);
3239 }
3240 else
3241 {
3242 fVSpeed += self->client->ps.velocity[0];
3243 }
3244 if (self->client->ps.velocity[1] < 0)
3245 {
3246 fVSpeed += (-self->client->ps.velocity[1]);
3247 }
3248 else
3249 {
3250 fVSpeed += self->client->ps.velocity[1];
3251 }
3252 if (self->client->ps.velocity[2] < 0)
3253 {
3254 fVSpeed += (-self->client->ps.velocity[2]);
3255 }
3256 else
3257 {
3258 fVSpeed += self->client->ps.velocity[2];
3259 }
3260
3261 fVSpeed *= 0.08f;
3262
3263 properOrigin[0] += addVel[0]*fVSpeed;
3264 properOrigin[1] += addVel[1]*fVSpeed;
3265 properOrigin[2] += addVel[2]*fVSpeed;
3266
3267 properAngles[0] = 0;
3268 properAngles[1] = self->client->ps.viewangles[YAW];
3269 properAngles[2] = 0;
3270
3271 trap->G2API_GetBoltMatrix(self->ghoul2, 0, useBolt, &boltMatrix, properAngles, properOrigin, level.time, NULL, self->modelScale);
3272
3273 boltPoint[0] = boltMatrix.matrix[0][3];
3274 boltPoint[1] = boltMatrix.matrix[1][3];
3275 boltPoint[2] = boltMatrix.matrix[2][3];
3276
3277 trap->G2API_GetBoltMatrix(self->ghoul2, 1, 0, &boltMatrix, properAngles, properOrigin, level.time, NULL, self->modelScale);
3278
3279 if (self->client && limbType == G2_MODELPART_RHAND)
3280 { //Make some saber hit sparks over the severed wrist area
3281 vec3_t boltAngles;
3282 gentity_t *te;
3283
3284 boltAngles[0] = -boltMatrix.matrix[0][1];
3285 boltAngles[1] = -boltMatrix.matrix[1][1];
3286 boltAngles[2] = -boltMatrix.matrix[2][1];
3287
3288 te = G_TempEntity( boltPoint, EV_SABER_HIT );
3289 te->s.otherEntityNum = self->s.number;
3290 te->s.otherEntityNum2 = ENTITYNUM_NONE;
3291 te->s.weapon = 0;//saberNum
3292 te->s.legsAnim = 0;//bladeNum
3293
3294 VectorCopy(boltPoint, te->s.origin);
3295 VectorCopy(boltAngles, te->s.angles);
3296
3297 if (!te->s.angles[0] && !te->s.angles[1] && !te->s.angles[2])
3298 { //don't let it play with no direction
3299 te->s.angles[1] = 1;
3300 }
3301
3302 te->s.eventParm = 16; //lots of sparks
3303 }
3304 }
3305
LimbTouch(gentity_t * self,gentity_t * other,trace_t * trace)3306 void LimbTouch( gentity_t *self, gentity_t *other, trace_t *trace )
3307 {
3308 }
3309
LimbThink(gentity_t * ent)3310 void LimbThink( gentity_t *ent )
3311 {
3312 float gravity = 3.0f;
3313 float mass = 0.09f;
3314 float bounce = 1.3f;
3315
3316 switch (ent->s.modelGhoul2)
3317 {
3318 case G2_MODELPART_HEAD:
3319 mass = 0.08f;
3320 bounce = 1.4f;
3321 break;
3322 case G2_MODELPART_WAIST:
3323 mass = 0.1f;
3324 bounce = 1.2f;
3325 break;
3326 case G2_MODELPART_LARM:
3327 case G2_MODELPART_RARM:
3328 case G2_MODELPART_RHAND:
3329 case G2_MODELPART_LLEG:
3330 case G2_MODELPART_RLEG:
3331 default:
3332 break;
3333 }
3334
3335 if (ent->speed < level.time)
3336 {
3337 ent->think = G_FreeEntity;
3338 ent->nextthink = level.time;
3339 return;
3340 }
3341
3342 if (ent->genericValue5 <= level.time)
3343 { //this will be every frame by standard, but we want to compensate in case sv_fps is not 20.
3344 G_RunExPhys(ent, gravity, mass, bounce, qtrue, NULL, 0);
3345 ent->genericValue5 = level.time + 50;
3346 }
3347
3348 ent->nextthink = level.time;
3349 }
3350
3351 extern qboolean BG_GetRootSurfNameWithVariant( void *ghoul2, const char *rootSurfName, char *returnSurfName, int returnSize );
3352
G_Dismember(gentity_t * ent,gentity_t * enemy,vec3_t point,int limbType,float limbRollBase,float limbPitchBase,int deathAnim,qboolean postDeath)3353 void G_Dismember( gentity_t *ent, gentity_t *enemy, vec3_t point, int limbType, float limbRollBase, float limbPitchBase, int deathAnim, qboolean postDeath )
3354 {
3355 vec3_t newPoint, dir, vel;
3356 gentity_t *limb;
3357 char limbName[MAX_QPATH];
3358 char stubName[MAX_QPATH];
3359 char stubCapName[MAX_QPATH];
3360
3361 if (limbType == G2_MODELPART_HEAD)
3362 {
3363 Q_strncpyz( limbName , "head", sizeof( limbName ) );
3364 Q_strncpyz( stubCapName, "torso_cap_head", sizeof( stubCapName ) );
3365 }
3366 else if (limbType == G2_MODELPART_WAIST)
3367 {
3368 Q_strncpyz( limbName, "torso", sizeof( limbName ) );
3369 Q_strncpyz( stubCapName, "hips_cap_torso", sizeof( stubCapName ) );
3370 }
3371 else if (limbType == G2_MODELPART_LARM)
3372 {
3373 BG_GetRootSurfNameWithVariant( ent->ghoul2, "l_arm", limbName, sizeof(limbName) );
3374 BG_GetRootSurfNameWithVariant( ent->ghoul2, "torso", stubName, sizeof(stubName) );
3375 Com_sprintf( stubCapName, sizeof( stubCapName), "%s_cap_l_arm", stubName );
3376 }
3377 else if (limbType == G2_MODELPART_RARM)
3378 {
3379 BG_GetRootSurfNameWithVariant( ent->ghoul2, "r_arm", limbName, sizeof(limbName) );
3380 BG_GetRootSurfNameWithVariant( ent->ghoul2, "torso", stubName, sizeof(stubName) );
3381 Com_sprintf( stubCapName, sizeof( stubCapName), "%s_cap_r_arm", stubName );
3382 }
3383 else if (limbType == G2_MODELPART_RHAND)
3384 {
3385 BG_GetRootSurfNameWithVariant( ent->ghoul2, "r_hand", limbName, sizeof(limbName) );
3386 BG_GetRootSurfNameWithVariant( ent->ghoul2, "r_arm", stubName, sizeof(stubName) );
3387 Com_sprintf( stubCapName, sizeof( stubCapName), "%s_cap_r_hand", stubName );
3388 }
3389 else if (limbType == G2_MODELPART_LLEG)
3390 {
3391 BG_GetRootSurfNameWithVariant( ent->ghoul2, "l_leg", limbName, sizeof(limbName) );
3392 BG_GetRootSurfNameWithVariant( ent->ghoul2, "hips", stubName, sizeof(stubName) );
3393 Com_sprintf( stubCapName, sizeof( stubCapName), "%s_cap_l_leg", stubName );
3394 }
3395 else if (limbType == G2_MODELPART_RLEG)
3396 {
3397 BG_GetRootSurfNameWithVariant( ent->ghoul2, "r_leg", limbName, sizeof(limbName) );
3398 BG_GetRootSurfNameWithVariant( ent->ghoul2, "hips", stubName, sizeof(stubName) );
3399 Com_sprintf( stubCapName, sizeof( stubCapName), "%s_cap_r_leg", stubName );
3400 }
3401 else
3402 {//umm... just default to the right leg, I guess (same as on client)
3403 BG_GetRootSurfNameWithVariant( ent->ghoul2, "r_leg", limbName, sizeof(limbName) );
3404 BG_GetRootSurfNameWithVariant( ent->ghoul2, "hips", stubName, sizeof(stubName) );
3405 Com_sprintf( stubCapName, sizeof( stubCapName), "%s_cap_r_leg", stubName );
3406 }
3407
3408 if (ent->ghoul2 && limbName[0] && trap->G2API_GetSurfaceRenderStatus(ent->ghoul2, 0, limbName))
3409 { //is it already off? If so there's no reason to be doing it again, so get out of here.
3410 return;
3411 }
3412
3413 VectorCopy( point, newPoint );
3414 limb = G_Spawn();
3415 limb->classname = "playerlimb";
3416
3417 /*
3418 if (limbType == G2_MODELPART_WAIST)
3419 { //slight hack
3420 newPoint[2] += 1;
3421 }
3422 */
3423
3424 G_SetOrigin( limb, newPoint );
3425 VectorCopy( newPoint, limb->s.pos.trBase );
3426 limb->think = LimbThink;
3427 limb->touch = LimbTouch;
3428 limb->speed = level.time + Q_irand(8000, 16000);
3429 limb->nextthink = level.time + FRAMETIME;
3430
3431 limb->r.svFlags = SVF_USE_CURRENT_ORIGIN;
3432 limb->clipmask = MASK_SOLID;
3433 limb->r.contents = CONTENTS_TRIGGER;
3434 limb->physicsObject = qtrue;
3435 VectorSet( limb->r.mins, -6.0f, -6.0f, -3.0f );
3436 VectorSet( limb->r.maxs, 6.0f, 6.0f, 6.0f );
3437
3438 limb->s.g2radius = 200;
3439
3440 limb->s.eType = ET_GENERAL;
3441 limb->s.weapon = G2_MODEL_PART;
3442 limb->s.modelGhoul2 = limbType;
3443 limb->s.modelindex = ent->s.number;
3444 if (!ent->client)
3445 {
3446 limb->s.modelindex = -1;
3447 limb->s.otherEntityNum2 = ent->s.number;
3448 }
3449
3450 VectorClear(limb->s.apos.trDelta);
3451
3452 if (ent->client)
3453 {
3454 VectorCopy(ent->client->ps.viewangles, limb->r.currentAngles);
3455 VectorCopy(ent->client->ps.viewangles, limb->s.apos.trBase);
3456 }
3457 else
3458 {
3459 VectorCopy(ent->r.currentAngles, limb->r.currentAngles);
3460 VectorCopy(ent->r.currentAngles, limb->s.apos.trBase);
3461 }
3462
3463 //Set up the ExPhys values for the entity.
3464 limb->epGravFactor = 0;
3465 VectorClear(limb->epVelocity);
3466 VectorSubtract( point, ent->r.currentOrigin, dir );
3467 VectorNormalize( dir );
3468 if (ent->client)
3469 {
3470 VectorCopy(ent->client->ps.velocity, vel);
3471 }
3472 else
3473 {
3474 VectorCopy(ent->s.pos.trDelta, vel);
3475 }
3476 VectorMA( vel, 80, dir, limb->epVelocity );
3477
3478 //add some vertical velocity
3479 if (limbType == G2_MODELPART_HEAD ||
3480 limbType == G2_MODELPART_WAIST)
3481 {
3482 limb->epVelocity[2] += 10;
3483 }
3484
3485 if (enemy && enemy->client && ent && ent != enemy && ent->s.number != enemy->s.number &&
3486 enemy->client->ps.weapon == WP_SABER && enemy->client->olderIsValid &&
3487 (level.time - enemy->client->lastSaberStorageTime) < 200)
3488 { //The enemy has valid saber positions between this and last frame. Use them to factor in direction of the limb.
3489 vec3_t dif;
3490 float totalDistance;
3491 const float distScale = 1.2f;
3492
3493 //scale down the initial velocity first, which is based on the speed of the limb owner.
3494 //ExPhys object velocity operates on a slightly different scale than Q3-based physics velocity.
3495 VectorScale(limb->epVelocity, 0.4f, limb->epVelocity);
3496
3497 VectorSubtract(enemy->client->lastSaberBase_Always, enemy->client->olderSaberBase, dif);
3498 totalDistance = VectorNormalize(dif);
3499
3500 VectorScale(dif, totalDistance*distScale, dif);
3501 VectorAdd(limb->epVelocity, dif, limb->epVelocity);
3502
3503 if (ent->client && (ent->client->ps.torsoTimer > 0 || !BG_InDeathAnim(ent->client->ps.torsoAnim)))
3504 { //if he's done with his death anim we don't actually want the limbs going far
3505 vec3_t preVel;
3506
3507 VectorCopy(limb->epVelocity, preVel);
3508 preVel[2] = 0;
3509 totalDistance = VectorNormalize(preVel);
3510
3511 if (totalDistance < 40.0f)
3512 {
3513 float mAmt = 40.0f;//60.0f/totalDistance;
3514
3515 limb->epVelocity[0] = preVel[0]*mAmt;
3516 limb->epVelocity[1] = preVel[1]*mAmt;
3517 }
3518 }
3519 else if (ent->client)
3520 {
3521 VectorScale(limb->epVelocity, 0.3f, limb->epVelocity);
3522 }
3523 }
3524
3525 if (ent->s.eType == ET_NPC && ent->ghoul2 && limbName[0] && stubCapName[0])
3526 { //if it's an npc remove these surfs on the server too. For players we don't even care cause there's no further dismemberment after death.
3527 trap->G2API_SetSurfaceOnOff(ent->ghoul2, limbName, 0x00000100);
3528 trap->G2API_SetSurfaceOnOff(ent->ghoul2, stubCapName, 0);
3529 }
3530
3531 if ( level.gametype >= GT_TEAM && ent->s.eType != ET_NPC )
3532 {//Team game
3533 switch ( ent->client->sess.sessionTeam )
3534 {
3535 case TEAM_RED:
3536 limb->s.customRGBA[0] = 255;
3537 limb->s.customRGBA[1] = 0;
3538 limb->s.customRGBA[2] = 0;
3539 break;
3540
3541 case TEAM_BLUE:
3542 limb->s.customRGBA[0] = 0;
3543 limb->s.customRGBA[1] = 0;
3544 limb->s.customRGBA[2] = 255;
3545 break;
3546
3547 default:
3548 limb->s.customRGBA[0] = ent->s.customRGBA[0];
3549 limb->s.customRGBA[1] = ent->s.customRGBA[1];
3550 limb->s.customRGBA[2] = ent->s.customRGBA[2];
3551 limb->s.customRGBA[3] = ent->s.customRGBA[3];
3552 break;
3553 }
3554 }
3555 else
3556 {//FFA
3557 limb->s.customRGBA[0] = ent->s.customRGBA[0];
3558 limb->s.customRGBA[1] = ent->s.customRGBA[1];
3559 limb->s.customRGBA[2] = ent->s.customRGBA[2];
3560 limb->s.customRGBA[3] = ent->s.customRGBA[3];
3561 }
3562
3563 trap->LinkEntity( (sharedEntity_t *)limb );
3564 }
3565
DismembermentTest(gentity_t * self)3566 void DismembermentTest(gentity_t *self)
3567 {
3568 int sect = G2_MODELPART_HEAD;
3569 vec3_t boltPoint;
3570
3571 while (sect <= G2_MODELPART_RLEG)
3572 {
3573 G_GetDismemberBolt(self, boltPoint, sect);
3574 G_Dismember( self, self, boltPoint, sect, 90, 0, BOTH_DEATH1, qfalse );
3575 sect++;
3576 }
3577 }
3578
DismembermentByNum(gentity_t * self,int num)3579 void DismembermentByNum(gentity_t *self, int num)
3580 {
3581 int sect = G2_MODELPART_HEAD;
3582 vec3_t boltPoint;
3583
3584 switch (num)
3585 {
3586 case 0:
3587 sect = G2_MODELPART_HEAD;
3588 break;
3589 case 1:
3590 sect = G2_MODELPART_WAIST;
3591 break;
3592 case 2:
3593 sect = G2_MODELPART_LARM;
3594 break;
3595 case 3:
3596 sect = G2_MODELPART_RARM;
3597 break;
3598 case 4:
3599 sect = G2_MODELPART_RHAND;
3600 break;
3601 case 5:
3602 sect = G2_MODELPART_LLEG;
3603 break;
3604 case 6:
3605 sect = G2_MODELPART_RLEG;
3606 break;
3607 default:
3608 break;
3609 }
3610
3611 G_GetDismemberBolt(self, boltPoint, sect);
3612 G_Dismember( self, self, boltPoint, sect, 90, 0, BOTH_DEATH1, qfalse );
3613 }
3614
G_GetHitQuad(gentity_t * self,vec3_t hitloc)3615 int G_GetHitQuad( gentity_t *self, vec3_t hitloc )
3616 {
3617 vec3_t diff, fwdangles={0,0,0}, right;
3618 vec3_t clEye;
3619 float rightdot;
3620 float zdiff;
3621 int hitLoc = gPainHitLoc;
3622
3623 if (self->client)
3624 {
3625 VectorCopy(self->client->ps.origin, clEye);
3626 clEye[2] += self->client->ps.viewheight;
3627 }
3628 else
3629 {
3630 VectorCopy(self->s.pos.trBase, clEye);
3631 clEye[2] += 16;
3632 }
3633
3634 VectorSubtract( hitloc, clEye, diff );
3635 diff[2] = 0;
3636 VectorNormalize( diff );
3637
3638 if (self->client)
3639 {
3640 fwdangles[1] = self->client->ps.viewangles[1];
3641 }
3642 else
3643 {
3644 fwdangles[1] = self->s.apos.trBase[1];
3645 }
3646 // Ultimately we might care if the shot was ahead or behind, but for now, just quadrant is fine.
3647 AngleVectors( fwdangles, NULL, right, NULL );
3648
3649 rightdot = DotProduct(right, diff);
3650 zdiff = hitloc[2] - clEye[2];
3651
3652 if ( zdiff > 0 )
3653 {
3654 if ( rightdot > 0.3 )
3655 {
3656 hitLoc = G2_MODELPART_RARM;
3657 }
3658 else if ( rightdot < -0.3 )
3659 {
3660 hitLoc = G2_MODELPART_LARM;
3661 }
3662 else
3663 {
3664 hitLoc = G2_MODELPART_HEAD;
3665 }
3666 }
3667 else if ( zdiff > -20 )
3668 {
3669 if ( rightdot > 0.1 )
3670 {
3671 hitLoc = G2_MODELPART_RARM;
3672 }
3673 else if ( rightdot < -0.1 )
3674 {
3675 hitLoc = G2_MODELPART_LARM;
3676 }
3677 else
3678 {
3679 hitLoc = G2_MODELPART_HEAD;
3680 }
3681 }
3682 else
3683 {
3684 if ( rightdot >= 0 )
3685 {
3686 hitLoc = G2_MODELPART_RLEG;
3687 }
3688 else
3689 {
3690 hitLoc = G2_MODELPART_LLEG;
3691 }
3692 }
3693
3694 return hitLoc;
3695 }
3696
3697 int gGAvoidDismember = 0;
3698
3699 void UpdateClientRenderBolts(gentity_t *self, vec3_t renderOrigin, vec3_t renderAngles);
3700
G_GetHitLocFromSurfName(gentity_t * ent,const char * surfName,int * hitLoc,vec3_t point,vec3_t dir,vec3_t bladeDir,int mod)3701 qboolean G_GetHitLocFromSurfName( gentity_t *ent, const char *surfName, int *hitLoc, vec3_t point, vec3_t dir, vec3_t bladeDir, int mod )
3702 {
3703 qboolean dismember = qfalse;
3704 int actualTime;
3705 int kneeLBolt = -1;
3706 int kneeRBolt = -1;
3707 int handRBolt = -1;
3708 int handLBolt = -1;
3709 int footRBolt = -1;
3710 int footLBolt = -1;
3711
3712 *hitLoc = HL_NONE;
3713
3714 if ( !surfName || !surfName[0] )
3715 {
3716 return qfalse;
3717 }
3718
3719 if( !ent->client )
3720 {
3721 return qfalse;
3722 }
3723
3724 if (!point)
3725 {
3726 return qfalse;
3727 }
3728
3729 if ( ent->client
3730 && ( ent->client->NPC_class == CLASS_R2D2
3731 || ent->client->NPC_class == CLASS_R5D2
3732 || ent->client->NPC_class == CLASS_GONK
3733 || ent->client->NPC_class == CLASS_MOUSE
3734 || ent->client->NPC_class == CLASS_SENTRY
3735 || ent->client->NPC_class == CLASS_INTERROGATOR
3736 || ent->client->NPC_class == CLASS_PROBE ) )
3737 {//we don't care about per-surface hit-locations or dismemberment for these guys
3738 return qfalse;
3739 }
3740
3741 if (ent->localAnimIndex <= 1)
3742 { //humanoid
3743 handLBolt = trap->G2API_AddBolt(ent->ghoul2, 0, "*l_hand");
3744 handRBolt = trap->G2API_AddBolt(ent->ghoul2, 0, "*r_hand");
3745 kneeLBolt = trap->G2API_AddBolt(ent->ghoul2, 0, "*hips_l_knee");
3746 kneeRBolt = trap->G2API_AddBolt(ent->ghoul2, 0, "*hips_r_knee");
3747 footLBolt = trap->G2API_AddBolt(ent->ghoul2, 0, "*l_leg_foot");
3748 footRBolt = trap->G2API_AddBolt(ent->ghoul2, 0, "*r_leg_foot");
3749 }
3750
3751 if ( ent->client && (ent->client->NPC_class == CLASS_ATST) )
3752 {
3753 //FIXME: almost impossible to hit these... perhaps we should
3754 // check for splashDamage and do radius damage to these parts?
3755 // Or, if we ever get bbox G2 traces, that may fix it, too
3756 if (!Q_stricmp("head_light_blaster_cann",surfName))
3757 {
3758 *hitLoc = HL_ARM_LT;
3759 }
3760 else if (!Q_stricmp("head_concussion_charger",surfName))
3761 {
3762 *hitLoc = HL_ARM_RT;
3763 }
3764 return(qfalse);
3765 }
3766 else if ( ent->client && (ent->client->NPC_class == CLASS_MARK1) )
3767 {
3768 if (!Q_stricmp("l_arm",surfName))
3769 {
3770 *hitLoc = HL_ARM_LT;
3771 }
3772 else if (!Q_stricmp("r_arm",surfName))
3773 {
3774 *hitLoc = HL_ARM_RT;
3775 }
3776 else if (!Q_stricmp("torso_front",surfName))
3777 {
3778 *hitLoc = HL_CHEST;
3779 }
3780 else if (!Q_stricmp("torso_tube1",surfName))
3781 {
3782 *hitLoc = HL_GENERIC1;
3783 }
3784 else if (!Q_stricmp("torso_tube2",surfName))
3785 {
3786 *hitLoc = HL_GENERIC2;
3787 }
3788 else if (!Q_stricmp("torso_tube3",surfName))
3789 {
3790 *hitLoc = HL_GENERIC3;
3791 }
3792 else if (!Q_stricmp("torso_tube4",surfName))
3793 {
3794 *hitLoc = HL_GENERIC4;
3795 }
3796 else if (!Q_stricmp("torso_tube5",surfName))
3797 {
3798 *hitLoc = HL_GENERIC5;
3799 }
3800 else if (!Q_stricmp("torso_tube6",surfName))
3801 {
3802 *hitLoc = HL_GENERIC6;
3803 }
3804 return(qfalse);
3805 }
3806 else if ( ent->client && (ent->client->NPC_class == CLASS_MARK2) )
3807 {
3808 if (!Q_stricmp("torso_canister1",surfName))
3809 {
3810 *hitLoc = HL_GENERIC1;
3811 }
3812 else if (!Q_stricmp("torso_canister2",surfName))
3813 {
3814 *hitLoc = HL_GENERIC2;
3815 }
3816 else if (!Q_stricmp("torso_canister3",surfName))
3817 {
3818 *hitLoc = HL_GENERIC3;
3819 }
3820 return(qfalse);
3821 }
3822 else if ( ent->client && (ent->client->NPC_class == CLASS_GALAKMECH) )
3823 {
3824 if (!Q_stricmp("torso_antenna",surfName)||!Q_stricmp("torso_antenna_base",surfName))
3825 {
3826 *hitLoc = HL_GENERIC1;
3827 }
3828 else if (!Q_stricmp("torso_shield",surfName))
3829 {
3830 *hitLoc = HL_GENERIC2;
3831 }
3832 else
3833 {
3834 *hitLoc = HL_CHEST;
3835 }
3836 return(qfalse);
3837 }
3838
3839 //FIXME: check the hitLoc and hitDir against the cap tag for the place
3840 //where the split will be- if the hit dir is roughly perpendicular to
3841 //the direction of the cap, then the split is allowed, otherwise we
3842 //hit it at the wrong angle and should not dismember...
3843 actualTime = level.time;
3844 if ( !Q_strncmp( "hips", surfName, 4 ) )
3845 {//FIXME: test properly for legs
3846 *hitLoc = HL_WAIST;
3847 if ( ent->client != NULL && ent->ghoul2 )
3848 {
3849 mdxaBone_t boltMatrix;
3850 vec3_t tagOrg, angles;
3851
3852 VectorSet( angles, 0, ent->r.currentAngles[YAW], 0 );
3853 if (kneeLBolt>=0)
3854 {
3855 trap->G2API_GetBoltMatrix( ent->ghoul2, 0, kneeLBolt,
3856 &boltMatrix, angles, ent->r.currentOrigin,
3857 actualTime, NULL, ent->modelScale );
3858 BG_GiveMeVectorFromMatrix( &boltMatrix, ORIGIN, tagOrg );
3859 if ( DistanceSquared( point, tagOrg ) < 100 )
3860 {//actually hit the knee
3861 *hitLoc = HL_LEG_LT;
3862 }
3863 }
3864 if (*hitLoc == HL_WAIST)
3865 {
3866 if (kneeRBolt>=0)
3867 {
3868 trap->G2API_GetBoltMatrix( ent->ghoul2, 0, kneeRBolt,
3869 &boltMatrix, angles, ent->r.currentOrigin,
3870 actualTime, NULL, ent->modelScale );
3871 BG_GiveMeVectorFromMatrix( &boltMatrix, ORIGIN, tagOrg );
3872 if ( DistanceSquared( point, tagOrg ) < 100 )
3873 {//actually hit the knee
3874 *hitLoc = HL_LEG_RT;
3875 }
3876 }
3877 }
3878 }
3879 }
3880 else if ( !Q_strncmp( "torso", surfName, 5 ) )
3881 {
3882 if ( !ent->client )
3883 {
3884 *hitLoc = HL_CHEST;
3885 }
3886 else
3887 {
3888 vec3_t t_fwd, t_rt, t_up, dirToImpact;
3889 float frontSide, rightSide, upSide;
3890 AngleVectors( ent->client->renderInfo.torsoAngles, t_fwd, t_rt, t_up );
3891
3892 if (ent->client->renderInfo.boltValidityTime != level.time)
3893 {
3894 vec3_t renderAng;
3895
3896 renderAng[0] = 0;
3897 renderAng[1] = ent->client->ps.viewangles[YAW];
3898 renderAng[2] = 0;
3899
3900 UpdateClientRenderBolts(ent, ent->client->ps.origin, renderAng);
3901 }
3902
3903 VectorSubtract( point, ent->client->renderInfo.torsoPoint, dirToImpact );
3904 frontSide = DotProduct( t_fwd, dirToImpact );
3905 rightSide = DotProduct( t_rt, dirToImpact );
3906 upSide = DotProduct( t_up, dirToImpact );
3907 if ( upSide < -10 )
3908 {//hit at waist
3909 *hitLoc = HL_WAIST;
3910 }
3911 else
3912 {//hit on upper torso
3913 if ( rightSide > 4 )
3914 {
3915 *hitLoc = HL_ARM_RT;
3916 }
3917 else if ( rightSide < -4 )
3918 {
3919 *hitLoc = HL_ARM_LT;
3920 }
3921 else if ( rightSide > 2 )
3922 {
3923 if ( frontSide > 0 )
3924 {
3925 *hitLoc = HL_CHEST_RT;
3926 }
3927 else
3928 {
3929 *hitLoc = HL_BACK_RT;
3930 }
3931 }
3932 else if ( rightSide < -2 )
3933 {
3934 if ( frontSide > 0 )
3935 {
3936 *hitLoc = HL_CHEST_LT;
3937 }
3938 else
3939 {
3940 *hitLoc = HL_BACK_LT;
3941 }
3942 }
3943 else if ( upSide > -3 && mod == MOD_SABER )
3944 {
3945 *hitLoc = HL_HEAD;
3946 }
3947 else if ( frontSide > 0 )
3948 {
3949 *hitLoc = HL_CHEST;
3950 }
3951 else
3952 {
3953 *hitLoc = HL_BACK;
3954 }
3955 }
3956 }
3957 }
3958 else if ( !Q_strncmp( "head", surfName, 4 ) )
3959 {
3960 *hitLoc = HL_HEAD;
3961 }
3962 else if ( !Q_strncmp( "r_arm", surfName, 5 ) )
3963 {
3964 *hitLoc = HL_ARM_RT;
3965 if ( ent->client != NULL && ent->ghoul2 )
3966 {
3967 mdxaBone_t boltMatrix;
3968 vec3_t tagOrg, angles;
3969
3970 VectorSet( angles, 0, ent->r.currentAngles[YAW], 0 );
3971 if (handRBolt>=0)
3972 {
3973 trap->G2API_GetBoltMatrix( ent->ghoul2, 0, handRBolt,
3974 &boltMatrix, angles, ent->r.currentOrigin,
3975 actualTime, NULL, ent->modelScale );
3976 BG_GiveMeVectorFromMatrix( &boltMatrix, ORIGIN, tagOrg );
3977 if ( DistanceSquared( point, tagOrg ) < 256 )
3978 {//actually hit the hand
3979 *hitLoc = HL_HAND_RT;
3980 }
3981 }
3982 }
3983 }
3984 else if ( !Q_strncmp( "l_arm", surfName, 5 ) )
3985 {
3986 *hitLoc = HL_ARM_LT;
3987 if ( ent->client != NULL && ent->ghoul2 )
3988 {
3989 mdxaBone_t boltMatrix;
3990 vec3_t tagOrg, angles;
3991
3992 VectorSet( angles, 0, ent->r.currentAngles[YAW], 0 );
3993 if (handLBolt>=0)
3994 {
3995 trap->G2API_GetBoltMatrix( ent->ghoul2, 0, handLBolt,
3996 &boltMatrix, angles, ent->r.currentOrigin,
3997 actualTime, NULL, ent->modelScale );
3998 BG_GiveMeVectorFromMatrix( &boltMatrix, ORIGIN, tagOrg );
3999 if ( DistanceSquared( point, tagOrg ) < 256 )
4000 {//actually hit the hand
4001 *hitLoc = HL_HAND_LT;
4002 }
4003 }
4004 }
4005 }
4006 else if ( !Q_strncmp( "r_leg", surfName, 5 ) )
4007 {
4008 *hitLoc = HL_LEG_RT;
4009 if ( ent->client != NULL && ent->ghoul2 )
4010 {
4011 mdxaBone_t boltMatrix;
4012 vec3_t tagOrg, angles;
4013
4014 VectorSet( angles, 0, ent->r.currentAngles[YAW], 0 );
4015 if (footRBolt>=0)
4016 {
4017 trap->G2API_GetBoltMatrix( ent->ghoul2, 0, footRBolt,
4018 &boltMatrix, angles, ent->r.currentOrigin,
4019 actualTime, NULL, ent->modelScale );
4020 BG_GiveMeVectorFromMatrix( &boltMatrix, ORIGIN, tagOrg );
4021 if ( DistanceSquared( point, tagOrg ) < 100 )
4022 {//actually hit the foot
4023 *hitLoc = HL_FOOT_RT;
4024 }
4025 }
4026 }
4027 }
4028 else if ( !Q_strncmp( "l_leg", surfName, 5 ) )
4029 {
4030 *hitLoc = HL_LEG_LT;
4031 if ( ent->client != NULL && ent->ghoul2 )
4032 {
4033 mdxaBone_t boltMatrix;
4034 vec3_t tagOrg, angles;
4035
4036 VectorSet( angles, 0, ent->r.currentAngles[YAW], 0 );
4037 if (footLBolt>=0)
4038 {
4039 trap->G2API_GetBoltMatrix( ent->ghoul2, 0, footLBolt,
4040 &boltMatrix, angles, ent->r.currentOrigin,
4041 actualTime, NULL, ent->modelScale );
4042 BG_GiveMeVectorFromMatrix( &boltMatrix, ORIGIN, tagOrg );
4043 if ( DistanceSquared( point, tagOrg ) < 100 )
4044 {//actually hit the foot
4045 *hitLoc = HL_FOOT_LT;
4046 }
4047 }
4048 }
4049 }
4050 else if ( !Q_strncmp( "r_hand", surfName, 6 ) || !Q_strncmp( "w_", surfName, 2 ) )
4051 {//right hand or weapon
4052 *hitLoc = HL_HAND_RT;
4053 }
4054 else if ( !Q_strncmp( "l_hand", surfName, 6 ) )
4055 {
4056 *hitLoc = HL_HAND_LT;
4057 }
4058 /*
4059 #ifdef _DEBUG
4060 else
4061 {
4062 Com_Printf( "ERROR: surface %s does not belong to any hitLocation!!!\n", surfName );
4063 }
4064 #endif //_DEBUG
4065 */
4066
4067 //if ( g_dismemberment->integer >= 11381138 || !ent->client->dismembered )
4068 if (g_dismember.integer == 100)
4069 { //full probability...
4070 if ( ent->client && ent->client->NPC_class == CLASS_PROTOCOL )
4071 {
4072 dismember = qtrue;
4073 }
4074 else if ( dir && (dir[0] || dir[1] || dir[2]) &&
4075 bladeDir && (bladeDir[0] || bladeDir[1] || bladeDir[2]) )
4076 {//we care about direction (presumably for dismemberment)
4077 //if ( g_dismemberProbabilities->value<=0.0f||G_Dismemberable( ent, *hitLoc ) )
4078 if (1) //Fix me?
4079 {//either we don't care about probabilties or the probability let us continue
4080 char *tagName = NULL;
4081 float aoa = 0.5f;
4082 //dir must be roughly perpendicular to the hitLoc's cap bolt
4083 switch ( *hitLoc )
4084 {
4085 case HL_LEG_RT:
4086 tagName = "*hips_cap_r_leg";
4087 break;
4088 case HL_LEG_LT:
4089 tagName = "*hips_cap_l_leg";
4090 break;
4091 case HL_WAIST:
4092 tagName = "*hips_cap_torso";
4093 aoa = 0.25f;
4094 break;
4095 case HL_CHEST_RT:
4096 case HL_ARM_RT:
4097 case HL_BACK_LT:
4098 tagName = "*torso_cap_r_arm";
4099 break;
4100 case HL_CHEST_LT:
4101 case HL_ARM_LT:
4102 case HL_BACK_RT:
4103 tagName = "*torso_cap_l_arm";
4104 break;
4105 case HL_HAND_RT:
4106 tagName = "*r_arm_cap_r_hand";
4107 break;
4108 case HL_HAND_LT:
4109 tagName = "*l_arm_cap_l_hand";
4110 break;
4111 case HL_HEAD:
4112 tagName = "*torso_cap_head";
4113 aoa = 0.25f;
4114 break;
4115 case HL_CHEST:
4116 case HL_BACK:
4117 case HL_FOOT_RT:
4118 case HL_FOOT_LT:
4119 default:
4120 //no dismemberment possible with these, so no checks needed
4121 break;
4122 }
4123 if ( tagName )
4124 {
4125 int tagBolt = trap->G2API_AddBolt( ent->ghoul2, 0, tagName );
4126 if ( tagBolt != -1 )
4127 {
4128 mdxaBone_t boltMatrix;
4129 vec3_t tagOrg, tagDir, angles;
4130
4131 VectorSet( angles, 0, ent->r.currentAngles[YAW], 0 );
4132 trap->G2API_GetBoltMatrix( ent->ghoul2, 0, tagBolt,
4133 &boltMatrix, angles, ent->r.currentOrigin,
4134 actualTime, NULL, ent->modelScale );
4135 BG_GiveMeVectorFromMatrix( &boltMatrix, ORIGIN, tagOrg );
4136 BG_GiveMeVectorFromMatrix( &boltMatrix, NEGATIVE_Y, tagDir );
4137 if ( DistanceSquared( point, tagOrg ) < 256 )
4138 {//hit close
4139 float dot = DotProduct( dir, tagDir );
4140 if ( dot < aoa && dot > -aoa )
4141 {//hit roughly perpendicular
4142 dot = DotProduct( bladeDir, tagDir );
4143 if ( dot < aoa && dot > -aoa )
4144 {//blade was roughly perpendicular
4145 dismember = qtrue;
4146 }
4147 }
4148 }
4149 }
4150 }
4151 }
4152 }
4153 else
4154 { //hmm, no direction supplied.
4155 dismember = qtrue;
4156 }
4157 }
4158 return dismember;
4159 }
4160
G_CheckForDismemberment(gentity_t * ent,gentity_t * enemy,vec3_t point,int damage,int deathAnim,qboolean postDeath)4161 void G_CheckForDismemberment(gentity_t *ent, gentity_t *enemy, vec3_t point, int damage, int deathAnim, qboolean postDeath)
4162 {
4163 int hitLoc = -1, hitLocUse = -1;
4164 vec3_t boltPoint;
4165 int dismember = g_dismember.integer;
4166
4167 if (ent->localAnimIndex > 1)
4168 {
4169 if (!ent->NPC)
4170 {
4171 return;
4172 }
4173
4174 if (ent->client->NPC_class != CLASS_PROTOCOL)
4175 { //this is the only non-humanoid allowed to do dismemberment.
4176 return;
4177 }
4178 }
4179
4180 if (!dismember)
4181 {
4182 return;
4183 }
4184
4185 if (gGAvoidDismember == 1)
4186 {
4187 return;
4188 }
4189
4190 if (gGAvoidDismember != 2)
4191 { //this means do the dismemberment regardless of randomness and damage
4192 if (Q_irand(0, 100) > dismember)
4193 {
4194 return;
4195 }
4196
4197 if (damage < 5)
4198 {
4199 return;
4200 }
4201 }
4202
4203 if (gGAvoidDismember == 2)
4204 {
4205 hitLoc = HL_HAND_RT;
4206 }
4207 else
4208 {
4209 if (d_saberGhoul2Collision.integer && ent->client && ent->client->g2LastSurfaceTime == level.time)
4210 {
4211 char hitSurface[MAX_QPATH];
4212
4213 trap->G2API_GetSurfaceName(ent->ghoul2, ent->client->g2LastSurfaceHit, 0, hitSurface);
4214
4215 if (hitSurface[0])
4216 {
4217 G_GetHitLocFromSurfName(ent, hitSurface, &hitLoc, point, vec3_origin, vec3_origin, MOD_UNKNOWN);
4218 }
4219 }
4220
4221 if (hitLoc == -1)
4222 {
4223 hitLoc = G_GetHitLocation( ent, point );
4224 }
4225 }
4226
4227 switch(hitLoc)
4228 {
4229 case HL_FOOT_RT:
4230 case HL_LEG_RT:
4231 hitLocUse = G2_MODELPART_RLEG;
4232 break;
4233 case HL_FOOT_LT:
4234 case HL_LEG_LT:
4235 hitLocUse = G2_MODELPART_LLEG;
4236 break;
4237
4238 case HL_WAIST:
4239 hitLocUse = G2_MODELPART_WAIST;
4240 break;
4241 /*
4242 case HL_BACK_RT:
4243 case HL_BACK_LT:
4244 case HL_BACK:
4245 case HL_CHEST_RT:
4246 case HL_CHEST_LT:
4247 case HL_CHEST:
4248 break;
4249 */
4250 case HL_ARM_RT:
4251 hitLocUse = G2_MODELPART_RARM;
4252 break;
4253 case HL_HAND_RT:
4254 hitLocUse = G2_MODELPART_RHAND;
4255 break;
4256 case HL_ARM_LT:
4257 case HL_HAND_LT:
4258 hitLocUse = G2_MODELPART_LARM;
4259 break;
4260 case HL_HEAD:
4261 hitLocUse = G2_MODELPART_HEAD;
4262 break;
4263 default:
4264 hitLocUse = G_GetHitQuad(ent, point);
4265 break;
4266 }
4267
4268 if (hitLocUse == -1)
4269 {
4270 return;
4271 }
4272
4273 if (ent->client)
4274 {
4275 G_GetDismemberBolt(ent, boltPoint, hitLocUse);
4276 if ( g_austrian.integer
4277 && (level.gametype == GT_DUEL || level.gametype == GT_POWERDUEL) )
4278 {
4279 G_LogPrintf( "Duel Dismemberment: %s dismembered at %s\n", ent->client->pers.netname, hitLocName[hitLoc] );
4280 }
4281 }
4282 else
4283 {
4284 G_GetDismemberLoc(ent, boltPoint, hitLocUse);
4285 }
4286 G_Dismember(ent, enemy, boltPoint, hitLocUse, 90, 0, deathAnim, postDeath);
4287 }
4288
G_LocationBasedDamageModifier(gentity_t * ent,vec3_t point,int mod,int dflags,int * damage)4289 void G_LocationBasedDamageModifier(gentity_t *ent, vec3_t point, int mod, int dflags, int *damage)
4290 {
4291 int hitLoc = -1;
4292
4293 if (!g_locationBasedDamage.integer)
4294 { //then leave it alone
4295 return;
4296 }
4297
4298 if ( (dflags&DAMAGE_NO_HIT_LOC) )
4299 { //then leave it alone
4300 return;
4301 }
4302
4303 if (mod == MOD_SABER && *damage <= 1)
4304 { //don't bother for idle damage
4305 return;
4306 }
4307
4308 if (!point)
4309 {
4310 return;
4311 }
4312
4313 if ( ent->client && ent->client->NPC_class == CLASS_VEHICLE )
4314 {//no location-based damage on vehicles
4315 return;
4316 }
4317
4318 if ((d_saberGhoul2Collision.integer && ent->client && ent->client->g2LastSurfaceTime == level.time && mod == MOD_SABER) || //using ghoul2 collision? Then if the mod is a saber we should have surface data from the last hit (unless thrown).
4319 (d_projectileGhoul2Collision.integer && ent->client && ent->client->g2LastSurfaceTime == level.time)) //It's safe to assume we died from the projectile that just set our surface index. So, go ahead and use that as the surf I guess.
4320 {
4321 char hitSurface[MAX_QPATH];
4322
4323 trap->G2API_GetSurfaceName(ent->ghoul2, ent->client->g2LastSurfaceHit, 0, hitSurface);
4324
4325 if (hitSurface[0])
4326 {
4327 G_GetHitLocFromSurfName(ent, hitSurface, &hitLoc, point, vec3_origin, vec3_origin, MOD_UNKNOWN);
4328 }
4329 }
4330
4331 if (hitLoc == -1)
4332 {
4333 hitLoc = G_GetHitLocation( ent, point );
4334 }
4335
4336 switch (hitLoc)
4337 {
4338 case HL_FOOT_RT:
4339 case HL_FOOT_LT:
4340 *damage *= 0.5;
4341 break;
4342 case HL_LEG_RT:
4343 case HL_LEG_LT:
4344 *damage *= 0.7;
4345 break;
4346 case HL_WAIST:
4347 case HL_BACK_RT:
4348 case HL_BACK_LT:
4349 case HL_BACK:
4350 case HL_CHEST_RT:
4351 case HL_CHEST_LT:
4352 case HL_CHEST:
4353 break; //normal damage
4354 case HL_ARM_RT:
4355 case HL_ARM_LT:
4356 *damage *= 0.85;
4357 break;
4358 case HL_HAND_RT:
4359 case HL_HAND_LT:
4360 *damage *= 0.6;
4361 break;
4362 case HL_HEAD:
4363 *damage *= 1.3;
4364 break;
4365 default:
4366 break; //do nothing then
4367 }
4368 }
4369 /*
4370 ===================================
4371 rww - end dismemberment/lbd
4372 ===================================
4373 */
4374
G_ThereIsAMaster(void)4375 qboolean G_ThereIsAMaster(void)
4376 {
4377 int i = 0;
4378 gentity_t *ent;
4379
4380 while (i < MAX_CLIENTS)
4381 {
4382 ent = &g_entities[i];
4383
4384 if (ent && ent->client && ent->client->ps.isJediMaster)
4385 {
4386 return qtrue;
4387 }
4388
4389 i++;
4390 }
4391
4392 return qfalse;
4393 }
4394
G_Knockdown(gentity_t * victim)4395 void G_Knockdown( gentity_t *victim )
4396 {
4397 if ( victim && victim->client && BG_KnockDownable(&victim->client->ps) )
4398 {
4399 victim->client->ps.forceHandExtend = HANDEXTEND_KNOCKDOWN;
4400 victim->client->ps.forceDodgeAnim = 0;
4401 victim->client->ps.forceHandExtendTime = level.time + 1100;
4402 victim->client->ps.quickerGetup = qfalse;
4403 }
4404 }
4405
4406 /*
4407 ============
4408 G_Damage
4409
4410 targ entity that is being damaged
4411 inflictor entity that is causing the damage
4412 attacker entity that caused the inflictor to damage targ
4413 example: targ=monster, inflictor=rocket, attacker=player
4414
4415 dir direction of the attack for knockback
4416 point point at which the damage is being inflicted, used for headshots
4417 damage amount of damage being inflicted
4418 knockback force to be applied against targ as a result of the damage
4419
4420 inflictor, attacker, dir, and point can be NULL for environmental effects
4421
4422 dflags these flags are used to control how G_Damage works
4423 DAMAGE_RADIUS damage was indirect (from a nearby explosion)
4424 DAMAGE_NO_ARMOR armor does not protect from this damage
4425 DAMAGE_NO_KNOCKBACK do not affect velocity, just view angles
4426 DAMAGE_NO_PROTECTION kills godmode, armor, everything
4427 DAMAGE_HALF_ABSORB half shields, half health
4428 DAMAGE_HALF_ARMOR_REDUCTION Any damage that shields incur is halved
4429 ============
4430 */
4431 extern qboolean gSiegeRoundBegun;
4432
4433 int gPainMOD = 0;
4434 int gPainHitLoc = -1;
4435 vec3_t gPainPoint;
4436
G_Damage(gentity_t * targ,gentity_t * inflictor,gentity_t * attacker,vec3_t dir,vec3_t point,int damage,int dflags,int mod)4437 void G_Damage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker, vec3_t dir, vec3_t point, int damage, int dflags, int mod ) {
4438 gclient_t *client;
4439 int take, asave, max, subamt = 0, knockback;
4440 float famt = 0, hamt = 0, shieldAbsorbed = 0;
4441
4442 if (!targ)
4443 return;
4444
4445 if (targ && targ->damageRedirect)
4446 {
4447 G_Damage(&g_entities[targ->damageRedirectTo], inflictor, attacker, dir, point, damage, dflags, mod);
4448 return;
4449 }
4450
4451 if (mod == MOD_DEMP2 && targ && targ->inuse && targ->client)
4452 {
4453 if ( targ->client->ps.electrifyTime < level.time )
4454 {//electrocution effect
4455 if (targ->s.eType == ET_NPC && targ->s.NPC_class == CLASS_VEHICLE &&
4456 targ->m_pVehicle && (targ->m_pVehicle->m_pVehicleInfo->type == VH_SPEEDER || targ->m_pVehicle->m_pVehicleInfo->type == VH_WALKER))
4457 { //do some extra stuff to speeders/walkers
4458 targ->client->ps.electrifyTime = level.time + Q_irand( 3000, 4000 );
4459 }
4460 else if ( targ->s.NPC_class != CLASS_VEHICLE
4461 || (targ->m_pVehicle && targ->m_pVehicle->m_pVehicleInfo->type != VH_FIGHTER) )
4462 {//don't do this to fighters
4463 targ->client->ps.electrifyTime = level.time + Q_irand( 300, 800 );
4464 }
4465 }
4466 }
4467
4468 if (level.gametype == GT_SIEGE &&
4469 !gSiegeRoundBegun)
4470 { //nothing can be damaged til the round starts.
4471 return;
4472 }
4473
4474 if (!targ->takedamage) {
4475 return;
4476 }
4477
4478 if ( (targ->flags&FL_SHIELDED) && mod != MOD_SABER && !targ->client)
4479 {//magnetically protected, this thing can only be damaged by lightsabers
4480 return;
4481 }
4482
4483 if ((targ->flags & FL_DMG_BY_SABER_ONLY) && mod != MOD_SABER)
4484 { //saber-only damage
4485 return;
4486 }
4487
4488 if ( targ->client )
4489 {//don't take damage when in a walker, or fighter
4490 //unless the walker/fighter is dead!!! -rww
4491 if ( targ->client->ps.clientNum < MAX_CLIENTS && targ->client->ps.m_iVehicleNum )
4492 {
4493 gentity_t *veh = &g_entities[targ->client->ps.m_iVehicleNum];
4494 if ( veh->m_pVehicle && veh->health > 0 )
4495 {
4496 if ( veh->m_pVehicle->m_pVehicleInfo->type == VH_WALKER ||
4497 veh->m_pVehicle->m_pVehicleInfo->type == VH_FIGHTER)
4498 {
4499 if (!(dflags & DAMAGE_NO_PROTECTION))
4500 {
4501 return;
4502 }
4503 }
4504 }
4505 }
4506 }
4507
4508 if ((targ->flags & FL_DMG_BY_HEAVY_WEAP_ONLY))
4509 { //only take damage from explosives and such
4510 if (mod != MOD_REPEATER_ALT &&
4511 mod != MOD_ROCKET &&
4512 mod != MOD_FLECHETTE_ALT_SPLASH &&
4513 mod != MOD_ROCKET_HOMING &&
4514 mod != MOD_THERMAL &&
4515 mod != MOD_THERMAL_SPLASH &&
4516 mod != MOD_TRIP_MINE_SPLASH &&
4517 mod != MOD_TIMED_MINE_SPLASH &&
4518 mod != MOD_DET_PACK_SPLASH &&
4519 mod != MOD_VEHICLE &&
4520 mod != MOD_CONC &&
4521 mod != MOD_CONC_ALT &&
4522 mod != MOD_SABER &&
4523 mod != MOD_TURBLAST &&
4524 mod != MOD_SUICIDE &&
4525 mod != MOD_FALLING &&
4526 mod != MOD_CRUSH &&
4527 mod != MOD_TELEFRAG &&
4528 mod != MOD_TRIGGER_HURT)
4529 {
4530 if ( mod != MOD_MELEE || !G_HeavyMelee( attacker ) )
4531 { //let classes with heavy melee ability damage heavy wpn dmg doors with fists
4532 return;
4533 }
4534 }
4535 }
4536
4537 if (targ->flags & FL_BBRUSH)
4538 {
4539 if (mod == MOD_DEMP2 ||
4540 mod == MOD_DEMP2_ALT ||
4541 mod == MOD_BRYAR_PISTOL ||
4542 mod == MOD_BRYAR_PISTOL_ALT ||
4543 mod == MOD_MELEE)
4544 { //these don't damage bbrushes.. ever
4545 if ( mod != MOD_MELEE || !G_HeavyMelee( attacker ) )
4546 { //let classes with heavy melee ability damage breakable brushes with fists
4547 return;
4548 }
4549 }
4550 }
4551
4552 if (targ && targ->client && targ->client->ps.duelInProgress)
4553 {
4554 if (attacker && attacker->client && attacker->s.number != targ->client->ps.duelIndex)
4555 {
4556 return;
4557 }
4558 else if (attacker && attacker->client && mod != MOD_SABER)
4559 {
4560 return;
4561 }
4562 }
4563 if (attacker && attacker->client && attacker->client->ps.duelInProgress)
4564 {
4565 if (targ && targ->client && targ->s.number != attacker->client->ps.duelIndex)
4566 {
4567 return;
4568 }
4569 else if (targ && targ->client && mod != MOD_SABER)
4570 {
4571 return;
4572 }
4573 }
4574
4575 if ( !(dflags & DAMAGE_NO_PROTECTION) )
4576 {//rage overridden by no_protection
4577 if (targ && targ->client && (targ->client->ps.fd.forcePowersActive & (1 << FP_RAGE)))
4578 {
4579 damage *= 0.5;
4580 }
4581 }
4582
4583 // the intermission has allready been qualified for, so don't
4584 // allow any extra scoring
4585 if ( level.intermissionQueued ) {
4586 return;
4587 }
4588 if ( !inflictor ) {
4589 inflictor = &g_entities[ENTITYNUM_WORLD];
4590 }
4591 if ( !attacker ) {
4592 attacker = &g_entities[ENTITYNUM_WORLD];
4593 }
4594
4595 // shootable doors / buttons don't actually have any health
4596
4597 //if genericValue4 == 1 then it's glass or a breakable and those do have health
4598 if ( targ->s.eType == ET_MOVER && targ->genericValue4 != 1 ) {
4599 if ( targ->use && targ->moverState == MOVER_POS1 ) {
4600 GlobalUse( targ, inflictor, attacker );
4601 }
4602 return;
4603 }
4604 // reduce damage by the attacker's handicap value
4605 // unless they are rocket jumping
4606 if ( attacker->client
4607 && attacker != targ
4608 && attacker->s.eType == ET_PLAYER
4609 && level.gametype != GT_SIEGE )
4610 {
4611 max = attacker->client->ps.stats[STAT_MAX_HEALTH];
4612 damage = damage * max / 100;
4613 }
4614
4615 if ( !(dflags&DAMAGE_NO_HIT_LOC) )
4616 {//see if we should modify it by damage location
4617 if (targ->inuse && (targ->client || targ->s.eType == ET_NPC) &&
4618 attacker->inuse && (attacker->client || attacker->s.eType == ET_NPC))
4619 { //check for location based damage stuff.
4620 G_LocationBasedDamageModifier(targ, point, mod, dflags, &damage);
4621 }
4622 }
4623
4624 if ( targ->client
4625 && targ->client->NPC_class == CLASS_RANCOR
4626 && (!attacker||!attacker->client||attacker->client->NPC_class!=CLASS_RANCOR) )
4627 {
4628 // I guess always do 10 points of damage...feel free to tweak as needed
4629 if ( damage < 10 )
4630 {//ignore piddly little damage
4631 damage = 0;
4632 }
4633 else if ( damage >= 10 )
4634 {
4635 damage = 10;
4636 }
4637 }
4638
4639 client = targ->client;
4640
4641 if ( client ) {
4642 if ( client->noclip ) {
4643 return;
4644 }
4645 }
4646
4647 if ( !dir ) {
4648 dflags |= DAMAGE_NO_KNOCKBACK;
4649 } else {
4650 VectorNormalize(dir);
4651 }
4652
4653 knockback = damage;
4654 if ( knockback > 200 ) {
4655 knockback = 200;
4656 }
4657 if ( targ->flags & FL_NO_KNOCKBACK ) {
4658 knockback = 0;
4659 }
4660 if ( dflags & DAMAGE_NO_KNOCKBACK ) {
4661 knockback = 0;
4662 }
4663
4664 // figure momentum add, even if the damage won't be taken
4665 if ( knockback && targ->client ) {
4666 vec3_t kvel;
4667 float mass;
4668
4669 mass = 200;
4670
4671 if (mod == MOD_SABER)
4672 {
4673 float saberKnockbackScale = g_saberDmgVelocityScale.value;
4674 if ( (dflags&DAMAGE_SABER_KNOCKBACK1)
4675 || (dflags&DAMAGE_SABER_KNOCKBACK2) )
4676 {//saber does knockback, scale it by the right number
4677 if ( !saberKnockbackScale )
4678 {
4679 saberKnockbackScale = 1.0f;
4680 }
4681 if ( attacker
4682 && attacker->client )
4683 {
4684 if ( (dflags&DAMAGE_SABER_KNOCKBACK1) )
4685 {
4686 if ( attacker && attacker->client )
4687 {
4688 saberKnockbackScale *= attacker->client->saber[0].knockbackScale;
4689 }
4690 }
4691 if ( (dflags&DAMAGE_SABER_KNOCKBACK1_B2) )
4692 {
4693 if ( attacker && attacker->client )
4694 {
4695 saberKnockbackScale *= attacker->client->saber[0].knockbackScale2;
4696 }
4697 }
4698 if ( (dflags&DAMAGE_SABER_KNOCKBACK2) )
4699 {
4700 if ( attacker && attacker->client )
4701 {
4702 saberKnockbackScale *= attacker->client->saber[1].knockbackScale;
4703 }
4704 }
4705 if ( (dflags&DAMAGE_SABER_KNOCKBACK2_B2) )
4706 {
4707 if ( attacker && attacker->client )
4708 {
4709 saberKnockbackScale *= attacker->client->saber[1].knockbackScale2;
4710 }
4711 }
4712 }
4713 }
4714 VectorScale (dir, (g_knockback.value * (float)knockback / mass)*saberKnockbackScale, kvel);
4715 }
4716 else
4717 {
4718 VectorScale (dir, g_knockback.value * (float)knockback / mass, kvel);
4719 }
4720 VectorAdd (targ->client->ps.velocity, kvel, targ->client->ps.velocity);
4721
4722 if (attacker && attacker->client && attacker != targ)
4723 {
4724 float dur = 5000;
4725 float dur2 = 100;
4726 if (targ->client && targ->s.eType == ET_NPC && targ->s.NPC_class == CLASS_VEHICLE)
4727 {
4728 dur = 25000;
4729 dur2 = 25000;
4730 }
4731 targ->client->ps.otherKiller = attacker->s.number;
4732 targ->client->ps.otherKillerTime = level.time + dur;
4733 targ->client->ps.otherKillerDebounceTime = level.time + dur2;
4734 }
4735 // set the timer so that the other client can't cancel
4736 // out the movement immediately
4737 if ( !targ->client->ps.pm_time && (g_saberDmgVelocityScale.integer || mod != MOD_SABER || (dflags&DAMAGE_SABER_KNOCKBACK1) || (dflags&DAMAGE_SABER_KNOCKBACK2) || (dflags&DAMAGE_SABER_KNOCKBACK1_B2) || (dflags&DAMAGE_SABER_KNOCKBACK2_B2) ) ) {
4738 int t;
4739
4740 t = knockback * 2;
4741 if ( t < 50 ) {
4742 t = 50;
4743 }
4744 if ( t > 200 ) {
4745 t = 200;
4746 }
4747 targ->client->ps.pm_time = t;
4748 targ->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
4749 }
4750 }
4751 else if (targ->client && targ->s.eType == ET_NPC && targ->s.NPC_class == CLASS_VEHICLE && attacker != targ)
4752 {
4753 targ->client->ps.otherKiller = attacker->s.number;
4754 targ->client->ps.otherKillerTime = level.time + 25000;
4755 targ->client->ps.otherKillerDebounceTime = level.time + 25000;
4756 }
4757
4758
4759 if ( (g_jediVmerc.integer || level.gametype == GT_SIEGE)
4760 && client )
4761 {//less explosive damage for jedi, more saber damage for non-jedi
4762 if ( client->ps.trueJedi
4763 || (level.gametype == GT_SIEGE&&client->ps.weapon == WP_SABER))
4764 {//if the target is a trueJedi, reduce splash and explosive damage to 1/2
4765 switch ( mod )
4766 {
4767 case MOD_REPEATER_ALT:
4768 case MOD_REPEATER_ALT_SPLASH:
4769 case MOD_DEMP2_ALT:
4770 case MOD_FLECHETTE_ALT_SPLASH:
4771 case MOD_ROCKET:
4772 case MOD_ROCKET_SPLASH:
4773 case MOD_ROCKET_HOMING:
4774 case MOD_ROCKET_HOMING_SPLASH:
4775 case MOD_THERMAL:
4776 case MOD_THERMAL_SPLASH:
4777 case MOD_TRIP_MINE_SPLASH:
4778 case MOD_TIMED_MINE_SPLASH:
4779 case MOD_DET_PACK_SPLASH:
4780 damage *= 0.75;
4781 break;
4782 }
4783 }
4784 else if ( (client->ps.trueNonJedi || (level.gametype == GT_SIEGE&&client->ps.weapon != WP_SABER))
4785 && mod == MOD_SABER )
4786 {//if the target is a trueNonJedi, take more saber damage... combined with the 1.5 in the w_saber stuff, this is 6 times damage!
4787 if ( damage < 100 )
4788 {
4789 damage *= 4;
4790 if ( damage > 100 )
4791 {
4792 damage = 100;
4793 }
4794 }
4795 }
4796 }
4797
4798 if (attacker->client && targ->client && level.gametype == GT_SIEGE &&
4799 targ->client->siegeClass != -1 && (bgSiegeClasses[targ->client->siegeClass].classflags & (1<<CFL_STRONGAGAINSTPHYSICAL)))
4800 { //this class is flagged to take less damage from physical attacks.
4801 //For now I'm just decreasing against any client-based attack, this can be changed later I guess.
4802 damage *= 0.5;
4803 }
4804
4805 // check for completely getting out of the damage
4806 if ( !(dflags & DAMAGE_NO_PROTECTION) ) {
4807
4808 // if TF_NO_FRIENDLY_FIRE is set, don't do damage to the target
4809 // if the attacker was on the same team
4810 if ( targ != attacker)
4811 {
4812 if (OnSameTeam (targ, attacker))
4813 {
4814 if ( !g_friendlyFire.integer )
4815 {
4816 return;
4817 }
4818 }
4819 else if (attacker && attacker->inuse &&
4820 !attacker->client && attacker->activator &&
4821 targ != attacker->activator &&
4822 attacker->activator->inuse && attacker->activator->client)
4823 { //emplaced guns don't hurt teammates of user
4824 if (OnSameTeam (targ, attacker->activator))
4825 {
4826 if ( !g_friendlyFire.integer )
4827 {
4828 return;
4829 }
4830 }
4831 }
4832 else if (targ->inuse && targ->client &&
4833 level.gametype >= GT_TEAM &&
4834 attacker->s.number >= MAX_CLIENTS &&
4835 attacker->alliedTeam &&
4836 targ->client->sess.sessionTeam == attacker->alliedTeam &&
4837 !g_friendlyFire.integer)
4838 { //things allied with my team should't hurt me.. I guess
4839 return;
4840 }
4841 }
4842
4843 if (level.gametype == GT_JEDIMASTER && !g_friendlyFire.integer &&
4844 targ && targ->client && attacker && attacker->client &&
4845 targ != attacker && !targ->client->ps.isJediMaster && !attacker->client->ps.isJediMaster &&
4846 G_ThereIsAMaster())
4847 {
4848 return;
4849 }
4850
4851 if (targ->s.number >= MAX_CLIENTS && targ->client
4852 && targ->s.shouldtarget && targ->s.teamowner &&
4853 attacker && attacker->inuse && attacker->client && targ->s.owner >= 0 && targ->s.owner < MAX_CLIENTS)
4854 {
4855 gentity_t *targown = &g_entities[targ->s.owner];
4856
4857 if (targown && targown->inuse && targown->client && OnSameTeam(targown, attacker))
4858 {
4859 if (!g_friendlyFire.integer)
4860 {
4861 return;
4862 }
4863 }
4864 }
4865
4866 // check for godmode
4867 if ( (targ->flags & FL_GODMODE) && targ->s.eType != ET_NPC ) {
4868 return;
4869 }
4870
4871 if (targ && targ->client && (targ->client->ps.eFlags & EF_INVULNERABLE) &&
4872 attacker && attacker->client && targ != attacker)
4873 {
4874 if (targ->client->invulnerableTimer <= level.time)
4875 {
4876 targ->client->ps.eFlags &= ~EF_INVULNERABLE;
4877 }
4878 else
4879 {
4880 return;
4881 }
4882 }
4883 }
4884
4885 //check for teamnodmg
4886 //NOTE: non-client objects hitting clients (and clients hitting clients) purposely doesn't obey this teamnodmg (for emplaced guns)
4887 if ( attacker && !targ->client )
4888 {//attacker hit a non-client
4889 if ( level.gametype == GT_SIEGE &&
4890 !g_ff_objectives.integer )
4891 {//in siege mode (and...?)
4892 if ( targ->teamnodmg )
4893 {//targ shouldn't take damage from a certain team
4894 if ( attacker->client )
4895 {//a client hit a non-client object
4896 if ( targ->teamnodmg == attacker->client->sess.sessionTeam )
4897 {
4898 return;
4899 }
4900 }
4901 else if ( attacker->teamnodmg )
4902 {//a non-client hit a non-client object
4903 //FIXME: maybe check alliedTeam instead?
4904 if ( targ->teamnodmg == attacker->teamnodmg )
4905 {
4906 if (attacker->activator &&
4907 attacker->activator->inuse &&
4908 attacker->activator->s.number < MAX_CLIENTS &&
4909 attacker->activator->client &&
4910 attacker->activator->client->sess.sessionTeam != targ->teamnodmg)
4911 { //uh, let them damage it I guess.
4912 }
4913 else
4914 {
4915 return;
4916 }
4917 }
4918 }
4919 }
4920 }
4921 }
4922
4923 #ifdef BASE_COMPAT
4924 // battlesuit protects from all radius damage (but takes knockback)
4925 // and protects 50% against all damage
4926 if ( client && client->ps.powerups[PW_BATTLESUIT] ) {
4927 G_AddEvent( targ, EV_POWERUP_BATTLESUIT, 0 );
4928 if ( ( dflags & DAMAGE_RADIUS ) || ( mod == MOD_FALLING ) ) {
4929 return;
4930 }
4931 damage *= 0.5;
4932 }
4933 #endif
4934
4935 // add to the attacker's hit counter (if the target isn't a general entity like a prox mine)
4936 if ( attacker->client && targ != attacker && targ->health > 0
4937 && targ->s.eType != ET_MISSILE
4938 && targ->s.eType != ET_GENERAL
4939 && client) {
4940 if ( OnSameTeam( targ, attacker ) ) {
4941 attacker->client->ps.persistant[PERS_HITS]--;
4942 } else {
4943 attacker->client->ps.persistant[PERS_HITS]++;
4944 }
4945 attacker->client->ps.persistant[PERS_ATTACKEE_ARMOR] = (targ->health<<8)|(client->ps.stats[STAT_ARMOR]);
4946 }
4947
4948 // always give half damage if hurting self... but not in siege. Heavy weapons need a counter.
4949 // calculated after knockback, so rocket jumping works
4950 if ( targ == attacker && !(dflags & DAMAGE_NO_SELF_PROTECTION)) {
4951 if ( level.gametype == GT_SIEGE )
4952 {
4953 damage *= 1.5;
4954 }
4955 else
4956 {
4957 damage *= 0.5;
4958 }
4959 }
4960
4961 if ( damage < 1 ) {
4962 damage = 1;
4963 }
4964 take = damage;
4965
4966 // save some from armor
4967 asave = CheckArmor (targ, take, dflags);
4968
4969 if (asave)
4970 {
4971 shieldAbsorbed = asave;
4972 }
4973
4974 take -= asave;
4975 if ( targ->client )
4976 {//update vehicle shields and armor, check for explode
4977 if ( targ->client->NPC_class == CLASS_VEHICLE &&
4978 targ->m_pVehicle )
4979 {//FIXME: should be in its own function in g_vehicles.c now, too big to be here
4980 int surface = -1;
4981 if ( attacker )
4982 {//so we know the last guy who shot at us
4983 targ->enemy = attacker;
4984 }
4985
4986 if ( targ->m_pVehicle->m_pVehicleInfo->type == VH_ANIMAL )
4987 {
4988 //((CVehicleNPC *)targ->NPC)->m_ulFlags |= CVehicleNPC::VEH_BUCKING;
4989 }
4990
4991 targ->m_pVehicle->m_iShields = targ->client->ps.stats[STAT_ARMOR];
4992 G_VehUpdateShields( targ );
4993 targ->m_pVehicle->m_iArmor -= take;
4994 if ( targ->m_pVehicle->m_iArmor <= 0 )
4995 {
4996 targ->s.eFlags |= EF_DEAD;
4997 targ->client->ps.eFlags |= EF_DEAD;
4998 targ->m_pVehicle->m_iArmor = 0;
4999 }
5000 if ( targ->m_pVehicle->m_pVehicleInfo->type == VH_FIGHTER )
5001 {//get the last surf that was hit
5002 if ( targ->client && targ->client->g2LastSurfaceTime == level.time)
5003 {
5004 char hitSurface[MAX_QPATH];
5005
5006 trap->G2API_GetSurfaceName(targ->ghoul2, targ->client->g2LastSurfaceHit, 0, hitSurface);
5007
5008 if (hitSurface[0])
5009 {
5010 surface = G_ShipSurfaceForSurfName( &hitSurface[0] );
5011
5012 if ( take && surface > 0 )
5013 {//hit a certain part of the ship
5014 int deathPoint = 0;
5015
5016 targ->locationDamage[surface] += take;
5017
5018 switch(surface)
5019 {
5020 case SHIPSURF_FRONT:
5021 deathPoint = targ->m_pVehicle->m_pVehicleInfo->health_front;
5022 break;
5023 case SHIPSURF_BACK:
5024 deathPoint = targ->m_pVehicle->m_pVehicleInfo->health_back;
5025 break;
5026 case SHIPSURF_RIGHT:
5027 deathPoint = targ->m_pVehicle->m_pVehicleInfo->health_right;
5028 break;
5029 case SHIPSURF_LEFT:
5030 deathPoint = targ->m_pVehicle->m_pVehicleInfo->health_left;
5031 break;
5032 default:
5033 break;
5034 }
5035
5036 //presume 0 means it wasn't set and so it should never die.
5037 if ( deathPoint )
5038 {
5039 if ( targ->locationDamage[surface] >= deathPoint)
5040 { //this area of the ship is now dead
5041 if ( G_FlyVehicleDestroySurface( targ, surface ) )
5042 {//actually took off a surface
5043 G_VehicleSetDamageLocFlags( targ, surface, deathPoint );
5044 }
5045 }
5046 else
5047 {
5048 G_VehicleSetDamageLocFlags( targ, surface, deathPoint );
5049 }
5050 }
5051 }
5052 }
5053 }
5054 }
5055 if ( targ->m_pVehicle->m_pVehicleInfo->type != VH_ANIMAL )
5056 {
5057 /*
5058 if ( targ->m_pVehicle->m_iArmor <= 0 )
5059 {//vehicle all out of armor
5060 Vehicle_t *pVeh = targ->m_pVehicle;
5061 if ( pVeh->m_iDieTime == 0 )
5062 {//just start the flaming effect and explosion delay, if it's not going already...
5063 pVeh->m_pVehicleInfo->StartDeathDelay( pVeh, 0 );
5064 }
5065 }
5066 else*/
5067 if ( attacker
5068 //&& attacker->client
5069 && targ != attacker
5070 && point
5071 && !VectorCompare( targ->client->ps.origin, point )
5072 && targ->m_pVehicle->m_LandTrace.fraction >= 1.0f)
5073 {//just took a hit, knock us around
5074 vec3_t vUp, impactDir;
5075 float impactStrength = (damage/200.0f)*10.0f;
5076 float dot = 0.0f;
5077 if ( impactStrength > 10.0f )
5078 {
5079 impactStrength = 10.0f;
5080 }
5081 //pitch or roll us based on where we were hit
5082 AngleVectors( targ->m_pVehicle->m_vOrientation, NULL, NULL, vUp );
5083 VectorSubtract( point, targ->r.currentOrigin, impactDir );
5084 VectorNormalize( impactDir );
5085 if ( surface <= 0 )
5086 {//no surf guess where we were hit, then
5087 vec3_t vFwd, vRight;
5088 AngleVectors( targ->m_pVehicle->m_vOrientation, vFwd, vRight, vUp );
5089 dot = DotProduct( vRight, impactDir );
5090 if ( dot > 0.4f )
5091 {
5092 surface = SHIPSURF_RIGHT;
5093 }
5094 else if ( dot < -0.4f )
5095 {
5096 surface = SHIPSURF_LEFT;
5097 }
5098 else
5099 {
5100 dot = DotProduct( vFwd, impactDir );
5101 if ( dot > 0.0f )
5102 {
5103 surface = SHIPSURF_FRONT;
5104 }
5105 else
5106 {
5107 surface = SHIPSURF_BACK;
5108 }
5109 }
5110 }
5111 switch ( surface )
5112 {
5113 case SHIPSURF_FRONT:
5114 dot = DotProduct( vUp, impactDir );
5115 if ( dot > 0 )
5116 {
5117 targ->m_pVehicle->m_vOrientation[PITCH] += impactStrength;
5118 }
5119 else
5120 {
5121 targ->m_pVehicle->m_vOrientation[PITCH] -= impactStrength;
5122 }
5123 break;
5124 case SHIPSURF_BACK:
5125 dot = DotProduct( vUp, impactDir );
5126 if ( dot > 0 )
5127 {
5128 targ->m_pVehicle->m_vOrientation[PITCH] -= impactStrength;
5129 }
5130 else
5131 {
5132 targ->m_pVehicle->m_vOrientation[PITCH] += impactStrength;
5133 }
5134 break;
5135 case SHIPSURF_RIGHT:
5136 dot = DotProduct( vUp, impactDir );
5137 if ( dot > 0 )
5138 {
5139 targ->m_pVehicle->m_vOrientation[ROLL] -= impactStrength;
5140 }
5141 else
5142 {
5143 targ->m_pVehicle->m_vOrientation[ROLL] += impactStrength;
5144 }
5145 break;
5146 case SHIPSURF_LEFT:
5147 dot = DotProduct( vUp, impactDir );
5148 if ( dot > 0 )
5149 {
5150 targ->m_pVehicle->m_vOrientation[ROLL] += impactStrength;
5151 }
5152 else
5153 {
5154 targ->m_pVehicle->m_vOrientation[ROLL] -= impactStrength;
5155 }
5156 break;
5157 }
5158
5159 }
5160 }
5161 }
5162 }
5163
5164 if ( mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT )
5165 {//FIXME: screw with non-animal vehicles, too?
5166 if ( client )
5167 {
5168 if ( client->NPC_class == CLASS_VEHICLE
5169 && targ->m_pVehicle
5170 && targ->m_pVehicle->m_pVehicleInfo
5171 && targ->m_pVehicle->m_pVehicleInfo->type == VH_FIGHTER )
5172 {//all damage goes into the disruption of shields and systems
5173 take = 0;
5174 }
5175 else
5176 {
5177
5178 if (client->jetPackOn)
5179 { //disable jetpack temporarily
5180 Jetpack_Off(targ);
5181 client->jetPackToggleTime = level.time + Q_irand(3000, 10000);
5182 }
5183
5184 if ( client->NPC_class == CLASS_PROTOCOL || client->NPC_class == CLASS_SEEKER ||
5185 client->NPC_class == CLASS_R2D2 || client->NPC_class == CLASS_R5D2 ||
5186 client->NPC_class == CLASS_MOUSE || client->NPC_class == CLASS_GONK )
5187 {
5188 // DEMP2 does more damage to these guys.
5189 take *= 2;
5190 }
5191 else if ( client->NPC_class == CLASS_PROBE || client->NPC_class == CLASS_INTERROGATOR ||
5192 client->NPC_class == CLASS_MARK1 || client->NPC_class == CLASS_MARK2 || client->NPC_class == CLASS_SENTRY ||
5193 client->NPC_class == CLASS_ATST )
5194 {
5195 // DEMP2 does way more damage to these guys.
5196 take *= 5;
5197 }
5198 else
5199 {
5200 if (take > 0)
5201 {
5202 take /= 3;
5203 if (take < 1)
5204 {
5205 take = 1;
5206 }
5207 }
5208 }
5209 }
5210 }
5211 }
5212
5213 if ( g_debugDamage.integer ) {
5214 trap->Print( "%i: client:%i health:%i damage:%i armor:%i\n", level.time, targ->s.number,
5215 targ->health, take, asave );
5216 }
5217
5218 // add to the damage inflicted on a player this frame
5219 // the total will be turned into screen blends and view angle kicks
5220 // at the end of the frame
5221 if ( client ) {
5222 if ( attacker ) {
5223 client->ps.persistant[PERS_ATTACKER] = attacker->s.number;
5224 } else {
5225 client->ps.persistant[PERS_ATTACKER] = ENTITYNUM_WORLD;
5226 }
5227 client->damage_armor += asave;
5228 client->damage_blood += take;
5229 client->damage_knockback += knockback;
5230 if ( dir ) {
5231 VectorCopy ( dir, client->damage_from );
5232 client->damage_fromWorld = qfalse;
5233 } else {
5234 VectorCopy ( targ->r.currentOrigin, client->damage_from );
5235 client->damage_fromWorld = qtrue;
5236 }
5237
5238 if (attacker && attacker->client)
5239 {
5240 BotDamageNotification(client, attacker);
5241 }
5242 else if (inflictor && inflictor->client)
5243 {
5244 BotDamageNotification(client, inflictor);
5245 }
5246 }
5247
5248 // See if it's the player hurting the emeny flag carrier
5249 if( level.gametype == GT_CTF || level.gametype == GT_CTY) {
5250 Team_CheckHurtCarrier(targ, attacker);
5251 }
5252
5253 if (targ->client) {
5254 // set the last client who damaged the target
5255 targ->client->lasthurt_client = attacker->s.number;
5256 targ->client->lasthurt_mod = mod;
5257 }
5258
5259 if ( !(dflags & DAMAGE_NO_PROTECTION) )
5260 {//protect overridden by no_protection
5261 if (take && targ->client && (targ->client->ps.fd.forcePowersActive & (1 << FP_PROTECT)))
5262 {
5263 if (targ->client->ps.fd.forcePower)
5264 {
5265 int maxtake = take;
5266
5267 //G_Sound(targ, CHAN_AUTO, protectHitSound);
5268 if (targ->client->forcePowerSoundDebounce < level.time)
5269 {
5270 G_PreDefSound(targ->client->ps.origin, PDSOUND_PROTECTHIT);
5271 targ->client->forcePowerSoundDebounce = level.time + 400;
5272 }
5273
5274 if (targ->client->ps.fd.forcePowerLevel[FP_PROTECT] == FORCE_LEVEL_1)
5275 {
5276 famt = 1;
5277 hamt = 0.40f;
5278
5279 if (maxtake > 100)
5280 {
5281 maxtake = 100;
5282 }
5283 }
5284 else if (targ->client->ps.fd.forcePowerLevel[FP_PROTECT] == FORCE_LEVEL_2)
5285 {
5286 famt = 0.5f;
5287 hamt = 0.60f;
5288
5289 if (maxtake > 200)
5290 {
5291 maxtake = 200;
5292 }
5293 }
5294 else if (targ->client->ps.fd.forcePowerLevel[FP_PROTECT] == FORCE_LEVEL_3)
5295 {
5296 famt = 0.25f;
5297 hamt = 0.80f;
5298
5299 if (maxtake > 400)
5300 {
5301 maxtake = 400;
5302 }
5303 }
5304
5305 if (!targ->client->ps.powerups[PW_FORCE_BOON])
5306 {
5307 targ->client->ps.fd.forcePower -= maxtake*famt;
5308 }
5309 else
5310 {
5311 targ->client->ps.fd.forcePower -= (maxtake*famt)/2;
5312 }
5313 subamt = (maxtake*hamt)+(take-maxtake);
5314 if (targ->client->ps.fd.forcePower < 0)
5315 {
5316 subamt += targ->client->ps.fd.forcePower;
5317 targ->client->ps.fd.forcePower = 0;
5318 }
5319 if (subamt)
5320 {
5321 take -= subamt;
5322
5323 if (take < 0)
5324 {
5325 take = 0;
5326 }
5327 }
5328 }
5329 }
5330 }
5331
5332 if (shieldAbsorbed)
5333 {
5334 /*
5335 if ( targ->client->NPC_class == CLASS_VEHICLE )
5336 {
5337 targ->client->ps.electrifyTime = level.time + Q_irand( 500, 1000 );
5338 }
5339 else
5340 */
5341 {
5342 gentity_t *evEnt;
5343
5344 // Send off an event to show a shield shell on the player, pointing in the right direction.
5345 //evEnt = G_TempEntity(vec3_origin, EV_SHIELD_HIT);
5346 //rww - er.. what the? This isn't broadcast, why is it being set on vec3_origin?!
5347 evEnt = G_TempEntity(targ->r.currentOrigin, EV_SHIELD_HIT);
5348 evEnt->s.otherEntityNum = targ->s.number;
5349 evEnt->s.eventParm = DirToByte(dir);
5350 evEnt->s.time2=shieldAbsorbed;
5351 /*
5352 shieldAbsorbed *= 20;
5353
5354 if (shieldAbsorbed > 1500)
5355 {
5356 shieldAbsorbed = 1500;
5357 }
5358 if (shieldAbsorbed < 200)
5359 {
5360 shieldAbsorbed = 200;
5361 }
5362
5363 if (targ->client->ps.powerups[PW_SHIELDHIT] < (level.time + shieldAbsorbed))
5364 {
5365 targ->client->ps.powerups[PW_SHIELDHIT] = level.time + shieldAbsorbed;
5366 }
5367 //flicker for as many ms as damage was absorbed (*20)
5368 //therefore 10 damage causes 1/5 of a seond of flickering, whereas
5369 //a full 100 causes 2 seconds (but is reduced to 1.5 seconds due to the max)
5370
5371 */
5372 }
5373 }
5374
5375 // do the damage
5376 if (take)
5377 {
5378 if (targ->client && targ->s.number < MAX_CLIENTS &&
5379 (mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT))
5380 { //uh.. shock them or something. what the hell, I don't know.
5381 if (targ->client->ps.weaponTime <= 0)
5382 { //yeah, we were supposed to be beta a week ago, I don't feel like
5383 //breaking the game so I'm gonna be safe and only do this only
5384 //if your weapon is not busy
5385 targ->client->ps.weaponTime = 2000;
5386 targ->client->ps.electrifyTime = level.time + 2000;
5387 if (targ->client->ps.weaponstate == WEAPON_CHARGING ||
5388 targ->client->ps.weaponstate == WEAPON_CHARGING_ALT)
5389 {
5390 targ->client->ps.weaponstate = WEAPON_READY;
5391 }
5392 }
5393 }
5394
5395 if ( !(dflags & DAMAGE_NO_PROTECTION) )
5396 {//rage overridden by no_protection
5397 if (targ->client && (targ->client->ps.fd.forcePowersActive & (1 << FP_RAGE)) && (inflictor->client || attacker->client))
5398 {
5399 take /= (targ->client->ps.fd.forcePowerLevel[FP_RAGE]+1);
5400 }
5401 }
5402 targ->health = targ->health - take;
5403
5404 if ( (targ->flags&FL_UNDYING) )
5405 {//take damage down to 1, but never die
5406 if ( targ->health < 1 )
5407 {
5408 targ->health = 1;
5409 }
5410 }
5411
5412 if ( targ->client ) {
5413 targ->client->ps.stats[STAT_HEALTH] = targ->health;
5414 }
5415
5416 if ( !(dflags & DAMAGE_NO_PROTECTION) )
5417 {//rage overridden by no_protection
5418 if (targ->client && (targ->client->ps.fd.forcePowersActive & (1 << FP_RAGE)) && (inflictor->client || attacker->client))
5419 {
5420 if (targ->health <= 0)
5421 {
5422 targ->health = 1;
5423 }
5424 if (targ->client->ps.stats[STAT_HEALTH] <= 0)
5425 {
5426 targ->client->ps.stats[STAT_HEALTH] = 1;
5427 }
5428 }
5429 }
5430
5431 //We want to go ahead and set gPainHitLoc regardless of if we have a pain func,
5432 //so we can adjust the location damage too.
5433 if (targ->client && targ->ghoul2 && targ->client->g2LastSurfaceTime == level.time)
5434 { //We updated the hit surface this frame, so it's valid.
5435 char hitSurface[MAX_QPATH];
5436
5437 trap->G2API_GetSurfaceName(targ->ghoul2, targ->client->g2LastSurfaceHit, 0, hitSurface);
5438
5439 if (hitSurface[0])
5440 {
5441 G_GetHitLocFromSurfName(targ, hitSurface, &gPainHitLoc, point, dir, vec3_origin, mod);
5442 }
5443 else
5444 {
5445 gPainHitLoc = -1;
5446 }
5447
5448 if (gPainHitLoc < HL_MAX && gPainHitLoc >= 0 && targ->locationDamage[gPainHitLoc] < Q3_INFINITE &&
5449 (targ->s.eType == ET_PLAYER || targ->s.NPC_class != CLASS_VEHICLE))
5450 {
5451 targ->locationDamage[gPainHitLoc] += take;
5452
5453 if (g_armBreakage.integer && !targ->client->ps.brokenLimbs &&
5454 targ->client->ps.stats[STAT_HEALTH] > 0 && targ->health > 0 &&
5455 !(targ->s.eFlags & EF_DEAD))
5456 { //check for breakage
5457 if (targ->locationDamage[HL_ARM_RT]+targ->locationDamage[HL_HAND_RT] >= 80)
5458 {
5459 G_BreakArm(targ, BROKENLIMB_RARM);
5460 }
5461 else if (targ->locationDamage[HL_ARM_LT]+targ->locationDamage[HL_HAND_LT] >= 80)
5462 {
5463 G_BreakArm(targ, BROKENLIMB_LARM);
5464 }
5465 }
5466 }
5467 }
5468 else
5469 {
5470 gPainHitLoc = -1;
5471 }
5472
5473 if (targ->maxHealth)
5474 { //if this is non-zero this guy should be updated his s.health to send to the client
5475 G_ScaleNetHealth(targ);
5476 }
5477
5478 if ( targ->health <= 0 ) {
5479 if ( client )
5480 {
5481 targ->flags |= FL_NO_KNOCKBACK;
5482
5483 if (point)
5484 {
5485 VectorCopy( point, targ->pos1 );
5486 }
5487 else
5488 {
5489 VectorCopy(targ->client->ps.origin, targ->pos1);
5490 }
5491 }
5492 else if (targ->s.eType == ET_NPC)
5493 { //g2animent
5494 VectorCopy(point, targ->pos1);
5495 }
5496
5497 if (targ->health < -999)
5498 targ->health = -999;
5499
5500 // If we are a breaking glass brush, store the damage point so we can do cool things with it.
5501 if ( targ->r.svFlags & SVF_GLASS_BRUSH )
5502 {
5503 VectorCopy( point, targ->pos1 );
5504 if (dir)
5505 {
5506 VectorCopy( dir, targ->pos2 );
5507 }
5508 else
5509 {
5510 VectorClear(targ->pos2);
5511 }
5512 }
5513
5514 if (targ->s.eType == ET_NPC &&
5515 targ->client &&
5516 (targ->s.eFlags & EF_DEAD))
5517 { //an NPC that's already dead. Maybe we can cut some more limbs off!
5518 if ( (mod == MOD_SABER || (mod == MOD_MELEE && G_HeavyMelee( attacker )) )//saber or heavy melee (claws)
5519 && take > 2
5520 && !(dflags&DAMAGE_NO_DISMEMBER) )
5521 {
5522 G_CheckForDismemberment(targ, attacker, targ->pos1, take, targ->client->ps.torsoAnim, qtrue);
5523 }
5524 }
5525
5526 targ->enemy = attacker;
5527 targ->die (targ, inflictor, attacker, take, mod);
5528 G_ActivateBehavior( targ, BSET_DEATH );
5529 return;
5530 }
5531 else
5532 {
5533 if ( g_debugMelee.integer )
5534 {//getting hurt makes you let go of the wall
5535 if ( targ->client && (targ->client->ps.pm_flags&PMF_STUCK_TO_WALL) )
5536 {
5537 G_LetGoOfWall( targ );
5538 }
5539 }
5540 if ( targ->pain )
5541 {
5542 if (targ->s.eType != ET_NPC || mod != MOD_SABER || take > 1)
5543 { //don't even notify NPCs of pain if it's just idle saber damage
5544 gPainMOD = mod;
5545 if (point)
5546 {
5547 VectorCopy(point, gPainPoint);
5548 }
5549 else
5550 {
5551 VectorCopy(targ->r.currentOrigin, gPainPoint);
5552 }
5553 targ->pain (targ, attacker, take);
5554 }
5555 }
5556 }
5557
5558 G_LogWeaponDamage(attacker->s.number, mod, take);
5559 }
5560
5561 }
5562
5563
5564 /*
5565 ============
5566 CanDamage
5567
5568 Returns qtrue if the inflictor can directly damage the target. Used for
5569 explosions and melee attacks.
5570 ============
5571 */
CanDamage(gentity_t * targ,vec3_t origin)5572 qboolean CanDamage (gentity_t *targ, vec3_t origin) {
5573 vec3_t dest;
5574 trace_t tr;
5575 vec3_t midpoint;
5576
5577 // use the midpoint of the bounds instead of the origin, because
5578 // bmodels may have their origin is 0,0,0
5579 VectorAdd (targ->r.absmin, targ->r.absmax, midpoint);
5580 VectorScale (midpoint, 0.5, midpoint);
5581
5582 VectorCopy (midpoint, dest);
5583 trap->Trace ( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID, qfalse, 0, 0);
5584 if (tr.fraction == 1.0 || tr.entityNum == targ->s.number)
5585 return qtrue;
5586
5587 // this should probably check in the plane of projection,
5588 // rather than in world coordinate, and also include Z
5589 VectorCopy (midpoint, dest);
5590 dest[0] += 15.0;
5591 dest[1] += 15.0;
5592 trap->Trace ( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID, qfalse, 0, 0);
5593 if (tr.fraction == 1.0)
5594 return qtrue;
5595
5596 VectorCopy (midpoint, dest);
5597 dest[0] += 15.0;
5598 dest[1] -= 15.0;
5599 trap->Trace ( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID, qfalse, 0, 0);
5600 if (tr.fraction == 1.0)
5601 return qtrue;
5602
5603 VectorCopy (midpoint, dest);
5604 dest[0] -= 15.0;
5605 dest[1] += 15.0;
5606 trap->Trace ( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID, qfalse, 0, 0);
5607 if (tr.fraction == 1.0)
5608 return qtrue;
5609
5610 VectorCopy (midpoint, dest);
5611 dest[0] -= 15.0;
5612 dest[1] -= 15.0;
5613 trap->Trace ( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID, qfalse, 0, 0);
5614 if (tr.fraction == 1.0)
5615 return qtrue;
5616
5617
5618 return qfalse;
5619 }
5620
5621
5622 /*
5623 ============
5624 G_RadiusDamage
5625 ============
5626 */
G_RadiusDamage(vec3_t origin,gentity_t * attacker,float damage,float radius,gentity_t * ignore,gentity_t * missile,int mod)5627 qboolean G_RadiusDamage ( vec3_t origin, gentity_t *attacker, float damage, float radius,
5628 gentity_t *ignore, gentity_t *missile, int mod) {
5629 float points, dist;
5630 gentity_t *ent;
5631 int entityList[MAX_GENTITIES];
5632 int numListedEntities;
5633 vec3_t mins, maxs;
5634 vec3_t v;
5635 vec3_t dir;
5636 int i, e;
5637 qboolean hitClient = qfalse;
5638 qboolean roastPeople = qfalse;
5639
5640 /*
5641 if (missile && !missile->client && missile->s.weapon > WP_NONE &&
5642 missile->s.weapon < WP_NUM_WEAPONS && missile->r.ownerNum >= 0 &&
5643 (missile->r.ownerNum < MAX_CLIENTS || g_entities[missile->r.ownerNum].s.eType == ET_NPC))
5644 { //sounds like it's a valid weapon projectile.. is it a valid explosive to create marks from?
5645 switch(missile->s.weapon)
5646 {
5647 case WP_FLECHETTE: //flechette issuing this will be alt-fire
5648 case WP_ROCKET_LAUNCHER:
5649 case WP_THERMAL:
5650 case WP_TRIP_MINE:
5651 case WP_DET_PACK:
5652 roastPeople = qtrue; //Then create explosive marks
5653 break;
5654 default:
5655 break;
5656 }
5657 }
5658 */
5659 //oh well.. maybe sometime? I am trying to cut down on tempent use.
5660
5661 if ( radius < 1 ) {
5662 radius = 1;
5663 }
5664
5665 for ( i = 0 ; i < 3 ; i++ ) {
5666 mins[i] = origin[i] - radius;
5667 maxs[i] = origin[i] + radius;
5668 }
5669
5670 numListedEntities = trap->EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
5671
5672 for ( e = 0 ; e < numListedEntities ; e++ ) {
5673 ent = &g_entities[entityList[ e ]];
5674
5675 if (ent == ignore)
5676 continue;
5677 if (!ent->takedamage)
5678 continue;
5679
5680 // find the distance from the edge of the bounding box
5681 for ( i = 0 ; i < 3 ; i++ ) {
5682 if ( origin[i] < ent->r.absmin[i] ) {
5683 v[i] = ent->r.absmin[i] - origin[i];
5684 } else if ( origin[i] > ent->r.absmax[i] ) {
5685 v[i] = origin[i] - ent->r.absmax[i];
5686 } else {
5687 v[i] = 0;
5688 }
5689 }
5690
5691 dist = VectorLength( v );
5692 if ( dist >= radius ) {
5693 continue;
5694 }
5695
5696 // if ( ent->health <= 0 )
5697 // continue;
5698
5699 points = damage * ( 1.0 - dist / radius );
5700
5701 if( CanDamage (ent, origin) ) {
5702 if( LogAccuracyHit( ent, attacker ) ) {
5703 hitClient = qtrue;
5704 }
5705 VectorSubtract (ent->r.currentOrigin, origin, dir);
5706 // push the center of mass higher than the origin so players
5707 // get knocked into the air more
5708 dir[2] += 24;
5709 if (attacker && attacker->inuse && attacker->client &&
5710 attacker->s.eType == ET_NPC && attacker->s.NPC_class == CLASS_VEHICLE &&
5711 attacker->m_pVehicle && attacker->m_pVehicle->m_pPilot)
5712 { //say my pilot did it.
5713 G_Damage (ent, NULL, (gentity_t *)attacker->m_pVehicle->m_pPilot, dir, origin, (int)points, DAMAGE_RADIUS, mod);
5714 }
5715 else
5716 {
5717 G_Damage (ent, NULL, attacker, dir, origin, (int)points, DAMAGE_RADIUS, mod);
5718 }
5719
5720 if (ent && ent->client && roastPeople && missile &&
5721 !VectorCompare(ent->r.currentOrigin, missile->r.currentOrigin))
5722 { //the thing calling this function can create burn marks on people, so create an event to do so
5723 gentity_t *evEnt = G_TempEntity(ent->r.currentOrigin, EV_GHOUL2_MARK);
5724
5725 evEnt->s.otherEntityNum = ent->s.number; //the entity the mark should be placed on
5726 evEnt->s.weapon = WP_ROCKET_LAUNCHER; //always say it's rocket so we make the right mark
5727
5728 //Try to place the decal by going from the missile location to the location of the person that was hit
5729 VectorCopy(missile->r.currentOrigin, evEnt->s.origin);
5730 VectorCopy(ent->r.currentOrigin, evEnt->s.origin2);
5731
5732 //it's hacky, but we want to move it up so it's more likely to hit
5733 //the torso.
5734 if (missile->r.currentOrigin[2] < ent->r.currentOrigin[2])
5735 { //move it up less so the decal is placed lower on the model then
5736 evEnt->s.origin2[2] += 8;
5737 }
5738 else
5739 {
5740 evEnt->s.origin2[2] += 24;
5741 }
5742
5743 //Special col check
5744 evEnt->s.eventParm = 1;
5745 }
5746 }
5747 }
5748
5749 return hitClient;
5750 }
5751