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