1 /*
2 ===========================================================================
3 Copyright (C) 1999-2005 Id Software, Inc.
4 Copyright (C) 2000-2006 Tim Angus
5
6 This file is part of Tremulous.
7
8 Tremulous is free software; you can redistribute it
9 and/or modify it under the terms of the GNU General Public License as
10 published by the Free Software Foundation; either version 2 of the License,
11 or (at your option) any later version.
12
13 Tremulous is distributed in the hope that it will be
14 useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with Tremulous; if not, write to the Free Software
20 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
21 ===========================================================================
22 */
23
24 #include "g_local.h"
25
26 damageRegion_t g_damageRegions[ PCL_NUM_CLASSES ][ MAX_LOCDAMAGE_REGIONS ];
27 int g_numDamageRegions[ PCL_NUM_CLASSES ];
28
29 armourRegion_t g_armourRegions[ UP_NUM_UPGRADES ][ MAX_ARMOUR_REGIONS ];
30 int g_numArmourRegions[ UP_NUM_UPGRADES ];
31
32 /*
33 ============
34 AddScore
35
36 Adds score to both the client and his team
37 ============
38 */
AddScore(gentity_t * ent,int score)39 void AddScore( gentity_t *ent, int score )
40 {
41 if( !ent->client )
42 return;
43
44 // no scoring during pre-match warmup
45 if( level.warmupTime )
46 return;
47
48 ent->client->ps.persistant[ PERS_SCORE ] += score;
49 CalculateRanks( );
50 }
51
52 /*
53 ==================
54 LookAtKiller
55 ==================
56 */
LookAtKiller(gentity_t * self,gentity_t * inflictor,gentity_t * attacker)57 void LookAtKiller( gentity_t *self, gentity_t *inflictor, gentity_t *attacker )
58 {
59 vec3_t dir;
60
61 if ( attacker && attacker != self )
62 VectorSubtract( attacker->s.pos.trBase, self->s.pos.trBase, dir );
63 else if( inflictor && inflictor != self )
64 VectorSubtract( inflictor->s.pos.trBase, self->s.pos.trBase, dir );
65 else
66 {
67 self->client->ps.stats[ STAT_VIEWLOCK ] = self->s.angles[ YAW ];
68 return;
69 }
70
71 self->client->ps.stats[ STAT_VIEWLOCK ] = vectoyaw( dir );
72 }
73
74 // these are just for logging, the client prints its own messages
75 char *modNames[ ] =
76 {
77 "MOD_UNKNOWN",
78 "MOD_SHOTGUN",
79 "MOD_BLASTER",
80 "MOD_PAINSAW",
81 "MOD_MACHINEGUN",
82 "MOD_CHAINGUN",
83 "MOD_PRIFLE",
84 "MOD_MDRIVER",
85 "MOD_LASGUN",
86 "MOD_LCANNON",
87 "MOD_LCANNON_SPLASH",
88 "MOD_FLAMER",
89 "MOD_FLAMER_SPLASH",
90 "MOD_GRENADE",
91 "MOD_WATER",
92 "MOD_SLIME",
93 "MOD_LAVA",
94 "MOD_CRUSH",
95 "MOD_TELEFRAG",
96 "MOD_FALLING",
97 "MOD_SUICIDE",
98 "MOD_TARGET_LASER",
99 "MOD_TRIGGER_HURT",
100
101 "MOD_ABUILDER_CLAW",
102 "MOD_LEVEL0_BITE",
103 "MOD_LEVEL1_CLAW",
104 "MOD_LEVEL1_PCLOUD",
105 "MOD_LEVEL3_CLAW",
106 "MOD_LEVEL3_POUNCE",
107 "MOD_LEVEL3_BOUNCEBALL",
108 "MOD_LEVEL2_CLAW",
109 "MOD_LEVEL2_ZAP",
110 "MOD_LEVEL4_CLAW",
111 "MOD_LEVEL4_CHARGE",
112
113 "MOD_SLOWBLOB",
114 "MOD_POISON",
115 "MOD_SWARM",
116
117 "MOD_HSPAWN",
118 "MOD_TESLAGEN",
119 "MOD_MGTURRET",
120 "MOD_REACTOR",
121
122 "MOD_ASPAWN",
123 "MOD_ATUBE",
124 "MOD_OVERMIND"
125 };
126
127 /*
128 ==================
129 player_die
130 ==================
131 */
player_die(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,int damage,int meansOfDeath)132 void player_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath )
133 {
134 gentity_t *ent;
135 int anim;
136 int killer;
137 int i, j;
138 char *killerName, *obit;
139 float totalDamage = 0.0f;
140 gentity_t *player;
141
142
143 if( self->client->ps.pm_type == PM_DEAD )
144 return;
145
146 if( level.intermissiontime )
147 return;
148
149 // stop any following clients
150 for( i = 0; i < level.maxclients; i++ )
151 {
152 if( level.clients[ i ].sess.sessionTeam == TEAM_SPECTATOR &&
153 level.clients[ i ].sess.spectatorState == SPECTATOR_FOLLOW &&
154 level.clients[ i ].sess.spectatorClient == self->client->ps.clientNum )
155 {
156 if( !G_FollowNewClient( &g_entities[ i ], 1 ) )
157 G_StopFollowing( &g_entities[ i ] );
158 }
159 }
160
161 self->client->ps.pm_type = PM_DEAD;
162 self->suicideTime = 0;
163
164 if( attacker )
165 {
166 killer = attacker->s.number;
167
168 if( attacker->client )
169 killerName = attacker->client->pers.netname;
170 else
171 killerName = "<non-client>";
172 }
173 else
174 {
175 killer = ENTITYNUM_WORLD;
176 killerName = "<world>";
177 }
178
179 if( killer < 0 || killer >= MAX_CLIENTS )
180 {
181 killer = ENTITYNUM_WORLD;
182 killerName = "<world>";
183 }
184
185 if( meansOfDeath < 0 || meansOfDeath >= sizeof( modNames ) / sizeof( modNames[0] ) )
186 obit = "<bad obituary>";
187 else
188 obit = modNames[ meansOfDeath ];
189
190 G_LogPrintf("Kill: %i %i %i: %s killed %s by %s\n",
191 killer, self->s.number, meansOfDeath, killerName,
192 self->client->pers.netname, obit );
193
194 //TA: close any menus the client has open
195 G_CloseMenus( self->client->ps.clientNum );
196
197 //TA: deactivate all upgrades
198 for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ )
199 BG_DeactivateUpgrade( i, self->client->ps.stats );
200
201 // broadcast the death event to everyone
202 ent = G_TempEntity( self->r.currentOrigin, EV_OBITUARY );
203 ent->s.eventParm = meansOfDeath;
204 ent->s.otherEntityNum = self->s.number;
205 ent->s.otherEntityNum2 = killer;
206 ent->r.svFlags = SVF_BROADCAST; // send to everyone
207
208 self->enemy = attacker;
209
210 self->client->ps.persistant[ PERS_KILLED ]++;
211
212 if( attacker && attacker->client )
213 {
214 attacker->client->lastkilled_client = self->s.number;
215
216 if( attacker == self || OnSameTeam( self, attacker ) )
217 {
218 AddScore( attacker, -1 );
219
220 //punish team kills and suicides
221 if( attacker->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
222 G_AddCreditToClient( attacker->client, -1, qtrue );
223 else if( attacker->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
224 G_AddCreditToClient( attacker->client, -ASPAWN_VALUE, qtrue );
225 }
226 else
227 {
228 AddScore( attacker, 1 );
229
230 attacker->client->lastKillTime = level.time;
231 }
232 }
233 else if( attacker->s.eType != ET_BUILDABLE )
234 AddScore( self, -1 );
235
236 //total up all the damage done by every client
237 for( i = 0; i < MAX_CLIENTS; i++ )
238 totalDamage += (float)self->credits[ i ];
239
240 // if players did more than DAMAGE_FRACTION_FOR_KILL increment the stage counters
241 if( totalDamage >= ( self->client->ps.stats[ STAT_MAX_HEALTH ] * DAMAGE_FRACTION_FOR_KILL ) )
242 {
243 if( self->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
244 trap_Cvar_Set( "g_alienKills", va( "%d", g_alienKills.integer + 1 ) );
245 else if( self->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
246 trap_Cvar_Set( "g_humanKills", va( "%d", g_humanKills.integer + 1 ) );
247 }
248
249 if( totalDamage > 0.0f )
250 {
251 if( self->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
252 {
253 //nice simple happy bouncy human land
254 float classValue = BG_FindValueOfClass( self->client->ps.stats[ STAT_PCLASS ] );
255
256 for( i = 0; i < MAX_CLIENTS; i++ )
257 {
258 player = g_entities + i;
259
260 if( !player->client )
261 continue;
262
263 if( player->client->ps.stats[ STAT_PTEAM ] != PTE_HUMANS )
264 continue;
265
266 if( !self->credits[ i ] )
267 continue;
268
269 //add credit
270 G_AddCreditToClient( player->client,
271 (int)( classValue * ( (float)self->credits[ i ] / totalDamage ) ), qtrue );
272 }
273 }
274 else if( self->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
275 {
276 //horribly complex nasty alien land
277 float humanValue = BG_GetValueOfHuman( &self->client->ps );
278 int frags;
279 int unclaimedFrags = (int)humanValue;
280
281 for( i = 0; i < MAX_CLIENTS; i++ )
282 {
283 player = g_entities + i;
284
285 if( !player->client )
286 continue;
287
288 if( player->client->ps.stats[ STAT_PTEAM ] != PTE_ALIENS )
289 continue;
290
291 //this client did no damage
292 if( !self->credits[ i ] )
293 continue;
294
295 //nothing left to claim
296 if( !unclaimedFrags )
297 break;
298
299 frags = (int)floor( humanValue * ( (float)self->credits[ i ] / totalDamage ) );
300
301 if( frags > 0 )
302 {
303 //add kills
304 G_AddCreditToClient( player->client, frags, qtrue );
305
306 //can't revist this account later
307 self->credits[ i ] = 0;
308
309 //reduce frags left to be claimed
310 unclaimedFrags -= frags;
311 }
312 }
313
314 //there are frags still to be claimed
315 if( unclaimedFrags )
316 {
317 //the clients remaining at this point do not
318 //have enough credit to claim even one frag
319 //so simply give the top <unclaimedFrags> clients
320 //a frag each
321
322 for( i = 0; i < unclaimedFrags; i++ )
323 {
324 int maximum = 0;
325 int topClient = 0;
326
327 for( j = 0; j < MAX_CLIENTS; j++ )
328 {
329 //this client did no damage
330 if( !self->credits[ j ] )
331 continue;
332
333 if( self->credits[ j ] > maximum )
334 {
335 maximum = self->credits[ j ];
336 topClient = j;
337 }
338 }
339
340 if( maximum > 0 )
341 {
342 player = g_entities + topClient;
343
344 //add kills
345 G_AddCreditToClient( player->client, 1, qtrue );
346
347 //can't revist this account again
348 self->credits[ topClient ] = 0;
349 }
350 }
351 }
352 }
353 }
354
355 Cmd_Score_f( self ); // show scores
356
357 // send updated scores to any clients that are following this one,
358 // or they would get stale scoreboards
359 for( i = 0 ; i < level.maxclients ; i++ )
360 {
361 gclient_t *client;
362
363 client = &level.clients[ i ];
364 if( client->pers.connected != CON_CONNECTED )
365 continue;
366
367 if( client->sess.sessionTeam != TEAM_SPECTATOR )
368 continue;
369
370 if( client->sess.spectatorClient == self->s.number )
371 Cmd_Score_f( g_entities + i );
372 }
373
374 self->client->pers.classSelection = PCL_NONE; //TA: reset the classtype
375 VectorCopy( self->s.origin, self->client->pers.lastDeathLocation );
376
377 self->takedamage = qfalse; // can still be gibbed
378
379 self->s.weapon = WP_NONE;
380 self->r.contents = CONTENTS_CORPSE;
381
382 self->s.angles[ PITCH ] = 0;
383 self->s.angles[ ROLL ] = 0;
384 self->s.angles[ YAW ] = self->s.apos.trBase[ YAW ];
385 LookAtKiller( self, inflictor, attacker );
386
387 VectorCopy( self->s.angles, self->client->ps.viewangles );
388
389 self->s.loopSound = 0;
390
391 self->r.maxs[ 2 ] = -8;
392
393 // don't allow respawn until the death anim is done
394 // g_forcerespawn may force spawning at some later time
395 self->client->respawnTime = level.time + 1700;
396
397 // remove powerups
398 memset( self->client->ps.powerups, 0, sizeof( self->client->ps.powerups ) );
399
400 {
401 // normal death
402 static int i;
403
404 if( !( self->client->ps.persistant[ PERS_STATE ] & PS_NONSEGMODEL ) )
405 {
406 switch( i )
407 {
408 case 0:
409 anim = BOTH_DEATH1;
410 break;
411 case 1:
412 anim = BOTH_DEATH2;
413 break;
414 case 2:
415 default:
416 anim = BOTH_DEATH3;
417 break;
418 }
419 }
420 else
421 {
422 switch( i )
423 {
424 case 0:
425 anim = NSPA_DEATH1;
426 break;
427 case 1:
428 anim = NSPA_DEATH2;
429 break;
430 case 2:
431 default:
432 anim = NSPA_DEATH3;
433 break;
434 }
435 }
436
437 self->client->ps.legsAnim =
438 ( ( self->client->ps.legsAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim;
439
440 if( !( self->client->ps.persistant[ PERS_STATE ] & PS_NONSEGMODEL ) )
441 {
442 self->client->ps.torsoAnim =
443 ( ( self->client->ps.torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim;
444 }
445
446 G_AddEvent( self, EV_DEATH1 + i, killer );
447
448 // globally cycle through the different death animations
449 i = ( i + 1 ) % 3;
450 }
451
452 trap_LinkEntity( self );
453 }
454
455
456 ////////TA: locdamage
457
458 /*
459 ===============
460 G_ParseArmourScript
461 ===============
462 */
G_ParseArmourScript(char * buf,int upgrade)463 void G_ParseArmourScript( char *buf, int upgrade )
464 {
465 char *token;
466 int count;
467
468 count = 0;
469
470 while( 1 )
471 {
472 token = COM_Parse( &buf );
473
474 if( !token[0] )
475 break;
476
477 if( strcmp( token, "{" ) )
478 {
479 G_Printf( "Missing { in armour file\n" );
480 break;
481 }
482
483 if( count == MAX_ARMOUR_REGIONS )
484 {
485 G_Printf( "Max armour regions exceeded in locdamage file\n" );
486 break;
487 }
488
489 //default
490 g_armourRegions[ upgrade ][ count ].minHeight = 0.0;
491 g_armourRegions[ upgrade ][ count ].maxHeight = 1.0;
492 g_armourRegions[ upgrade ][ count ].minAngle = 0;
493 g_armourRegions[ upgrade ][ count ].maxAngle = 360;
494 g_armourRegions[ upgrade ][ count ].modifier = 1.0;
495 g_armourRegions[ upgrade ][ count ].crouch = qfalse;
496
497 while( 1 )
498 {
499 token = COM_ParseExt( &buf, qtrue );
500
501 if( !token[0] )
502 {
503 G_Printf( "Unexpected end of armour file\n" );
504 break;
505 }
506
507 if( !Q_stricmp( token, "}" ) )
508 {
509 break;
510 }
511 else if( !strcmp( token, "minHeight" ) )
512 {
513 token = COM_ParseExt( &buf, qfalse );
514
515 if ( !token[0] )
516 strcpy( token, "0" );
517
518 g_armourRegions[ upgrade ][ count ].minHeight = atof( token );
519 }
520 else if( !strcmp( token, "maxHeight" ) )
521 {
522 token = COM_ParseExt( &buf, qfalse );
523
524 if ( !token[0] )
525 strcpy( token, "100" );
526
527 g_armourRegions[ upgrade ][ count ].maxHeight = atof( token );
528 }
529 else if( !strcmp( token, "minAngle" ) )
530 {
531 token = COM_ParseExt( &buf, qfalse );
532
533 if ( !token[0] )
534 strcpy( token, "0" );
535
536 g_armourRegions[ upgrade ][ count ].minAngle = atoi( token );
537 }
538 else if( !strcmp( token, "maxAngle" ) )
539 {
540 token = COM_ParseExt( &buf, qfalse );
541
542 if ( !token[0] )
543 strcpy( token, "360" );
544
545 g_armourRegions[ upgrade ][ count ].maxAngle = atoi( token );
546 }
547 else if( !strcmp( token, "modifier" ) )
548 {
549 token = COM_ParseExt( &buf, qfalse );
550
551 if ( !token[0] )
552 strcpy( token, "1.0" );
553
554 g_armourRegions[ upgrade ][ count ].modifier = atof( token );
555 }
556 else if( !strcmp( token, "crouch" ) )
557 {
558 g_armourRegions[ upgrade ][ count ].crouch = qtrue;
559 }
560 }
561
562 g_numArmourRegions[ upgrade ]++;
563 count++;
564 }
565 }
566
567
568 /*
569 ===============
570 G_ParseDmgScript
571 ===============
572 */
G_ParseDmgScript(char * buf,int class)573 void G_ParseDmgScript( char *buf, int class )
574 {
575 char *token;
576 int count;
577
578 count = 0;
579
580 while( 1 )
581 {
582 token = COM_Parse( &buf );
583
584 if( !token[0] )
585 break;
586
587 if( strcmp( token, "{" ) )
588 {
589 G_Printf( "Missing { in locdamage file\n" );
590 break;
591 }
592
593 if( count == MAX_LOCDAMAGE_REGIONS )
594 {
595 G_Printf( "Max damage regions exceeded in locdamage file\n" );
596 break;
597 }
598
599 //default
600 g_damageRegions[ class ][ count ].minHeight = 0.0;
601 g_damageRegions[ class ][ count ].maxHeight = 1.0;
602 g_damageRegions[ class ][ count ].minAngle = 0;
603 g_damageRegions[ class ][ count ].maxAngle = 360;
604 g_damageRegions[ class ][ count ].modifier = 1.0;
605 g_damageRegions[ class ][ count ].crouch = qfalse;
606
607 while( 1 )
608 {
609 token = COM_ParseExt( &buf, qtrue );
610
611 if( !token[0] )
612 {
613 G_Printf( "Unexpected end of locdamage file\n" );
614 break;
615 }
616
617 if( !Q_stricmp( token, "}" ) )
618 {
619 break;
620 }
621 else if( !strcmp( token, "minHeight" ) )
622 {
623 token = COM_ParseExt( &buf, qfalse );
624
625 if ( !token[0] )
626 strcpy( token, "0" );
627
628 g_damageRegions[ class ][ count ].minHeight = atof( token );
629 }
630 else if( !strcmp( token, "maxHeight" ) )
631 {
632 token = COM_ParseExt( &buf, qfalse );
633
634 if ( !token[0] )
635 strcpy( token, "100" );
636
637 g_damageRegions[ class ][ count ].maxHeight = atof( token );
638 }
639 else if( !strcmp( token, "minAngle" ) )
640 {
641 token = COM_ParseExt( &buf, qfalse );
642
643 if ( !token[0] )
644 strcpy( token, "0" );
645
646 g_damageRegions[ class ][ count ].minAngle = atoi( token );
647 }
648 else if( !strcmp( token, "maxAngle" ) )
649 {
650 token = COM_ParseExt( &buf, qfalse );
651
652 if ( !token[0] )
653 strcpy( token, "360" );
654
655 g_damageRegions[ class ][ count ].maxAngle = atoi( token );
656 }
657 else if( !strcmp( token, "modifier" ) )
658 {
659 token = COM_ParseExt( &buf, qfalse );
660
661 if ( !token[0] )
662 strcpy( token, "1.0" );
663
664 g_damageRegions[ class ][ count ].modifier = atof( token );
665 }
666 else if( !strcmp( token, "crouch" ) )
667 {
668 g_damageRegions[ class ][ count ].crouch = qtrue;
669 }
670 }
671
672 g_numDamageRegions[ class ]++;
673 count++;
674 }
675 }
676
677
678 /*
679 ============
680 G_CalcDamageModifier
681 ============
682 */
G_CalcDamageModifier(vec3_t point,gentity_t * targ,gentity_t * attacker,int class,int dflags)683 static float G_CalcDamageModifier( vec3_t point, gentity_t *targ, gentity_t *attacker, int class, int dflags )
684 {
685 vec3_t bulletPath;
686 vec3_t bulletAngle;
687 vec3_t pMINUSfloor, floor, normal;
688
689 float clientHeight, hitRelative, hitRatio;
690 int bulletRotation, clientRotation, hitRotation;
691 float modifier = 1.0f;
692 int i, j;
693
694 if( point == NULL )
695 return 1.0f;
696
697 clientHeight = targ->r.maxs[ 2 ] - targ->r.mins[ 2 ];
698
699 if( targ->client->ps.stats[ STAT_STATE ] & SS_WALLCLIMBING )
700 VectorCopy( targ->client->ps.grapplePoint, normal );
701 else
702 VectorSet( normal, 0, 0, 1 );
703
704 VectorMA( targ->r.currentOrigin, targ->r.mins[ 2 ], normal, floor );
705 VectorSubtract( point, floor, pMINUSfloor );
706
707 hitRelative = DotProduct( normal, pMINUSfloor ) / VectorLength( normal );
708
709 if( hitRelative < 0.0f )
710 hitRelative = 0.0f;
711
712 if( hitRelative > clientHeight )
713 hitRelative = clientHeight;
714
715 hitRatio = hitRelative / clientHeight;
716
717 VectorSubtract( targ->r.currentOrigin, point, bulletPath );
718 vectoangles( bulletPath, bulletAngle );
719
720 clientRotation = targ->client->ps.viewangles[ YAW ];
721 bulletRotation = bulletAngle[ YAW ];
722
723 hitRotation = abs( clientRotation - bulletRotation );
724
725 hitRotation = hitRotation % 360; // Keep it in the 0-359 range
726
727 if( dflags & DAMAGE_NO_LOCDAMAGE )
728 {
729 for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ )
730 {
731 float totalModifier = 0.0f;
732 float averageModifier = 1.0f;
733
734 //average all of this upgrade's armour regions together
735 if( BG_InventoryContainsUpgrade( i, targ->client->ps.stats ) )
736 {
737 for( j = 0; j < g_numArmourRegions[ i ]; j++ )
738 totalModifier += g_armourRegions[ i ][ j ].modifier;
739
740 if( g_numArmourRegions[ i ] )
741 averageModifier = totalModifier / g_numArmourRegions[ i ];
742 else
743 averageModifier = 1.0f;
744 }
745
746 modifier *= averageModifier;
747 }
748 }
749 else
750 {
751 for( i = 0; i < g_numDamageRegions[ class ]; i++ )
752 {
753 qboolean rotationBound;
754
755 if( g_damageRegions[ class ][ i ].minAngle >
756 g_damageRegions[ class ][ i ].maxAngle )
757 {
758 rotationBound = ( hitRotation >= g_damageRegions[ class ][ i ].minAngle &&
759 hitRotation <= 360 ) || ( hitRotation >= 0 &&
760 hitRotation <= g_damageRegions[ class ][ i ].maxAngle );
761 }
762 else
763 {
764 rotationBound = ( hitRotation >= g_damageRegions[ class ][ i ].minAngle &&
765 hitRotation <= g_damageRegions[ class ][ i ].maxAngle );
766 }
767
768 if( rotationBound &&
769 hitRatio >= g_damageRegions[ class ][ i ].minHeight &&
770 hitRatio <= g_damageRegions[ class ][ i ].maxHeight &&
771 ( g_damageRegions[ class ][ i ].crouch ==
772 ( targ->client->ps.pm_flags & PMF_DUCKED ) ) )
773 modifier *= g_damageRegions[ class ][ i ].modifier;
774 }
775
776 for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ )
777 {
778 if( BG_InventoryContainsUpgrade( i, targ->client->ps.stats ) )
779 {
780 for( j = 0; j < g_numArmourRegions[ i ]; j++ )
781 {
782 qboolean rotationBound;
783
784 if( g_armourRegions[ i ][ j ].minAngle >
785 g_armourRegions[ i ][ j ].maxAngle )
786 {
787 rotationBound = ( hitRotation >= g_armourRegions[ i ][ j ].minAngle &&
788 hitRotation <= 360 ) || ( hitRotation >= 0 &&
789 hitRotation <= g_armourRegions[ i ][ j ].maxAngle );
790 }
791 else
792 {
793 rotationBound = ( hitRotation >= g_armourRegions[ i ][ j ].minAngle &&
794 hitRotation <= g_armourRegions[ i ][ j ].maxAngle );
795 }
796
797 if( rotationBound &&
798 hitRatio >= g_armourRegions[ i ][ j ].minHeight &&
799 hitRatio <= g_armourRegions[ i ][ j ].maxHeight &&
800 ( g_armourRegions[ i ][ j ].crouch ==
801 ( targ->client->ps.pm_flags & PMF_DUCKED ) ) )
802 modifier *= g_armourRegions[ i ][ j ].modifier;
803 }
804 }
805 }
806 }
807
808 return modifier;
809 }
810
811
812 /*
813 ============
814 G_InitDamageLocations
815 ============
816 */
G_InitDamageLocations(void)817 void G_InitDamageLocations( void )
818 {
819 char *modelName;
820 char filename[ MAX_QPATH ];
821 int i;
822 int len;
823 fileHandle_t fileHandle;
824 char buffer[ MAX_LOCDAMAGE_TEXT ];
825
826 for( i = PCL_NONE + 1; i < PCL_NUM_CLASSES; i++ )
827 {
828 modelName = BG_FindModelNameForClass( i );
829 Com_sprintf( filename, sizeof( filename ), "models/players/%s/locdamage.cfg", modelName );
830
831 len = trap_FS_FOpenFile( filename, &fileHandle, FS_READ );
832 if ( !fileHandle )
833 {
834 G_Printf( va( S_COLOR_RED "file not found: %s\n", filename ) );
835 continue;
836 }
837
838 if( len >= MAX_LOCDAMAGE_TEXT )
839 {
840 G_Printf( va( S_COLOR_RED "file too large: %s is %i, max allowed is %i", filename, len, MAX_LOCDAMAGE_TEXT ) );
841 trap_FS_FCloseFile( fileHandle );
842 continue;
843 }
844
845 trap_FS_Read( buffer, len, fileHandle );
846 buffer[len] = 0;
847 trap_FS_FCloseFile( fileHandle );
848
849 G_ParseDmgScript( buffer, i );
850 }
851
852 for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ )
853 {
854 modelName = BG_FindNameForUpgrade( i );
855 Com_sprintf( filename, sizeof( filename ), "armour/%s.armour", modelName );
856
857 len = trap_FS_FOpenFile( filename, &fileHandle, FS_READ );
858
859 //no file - no parsage
860 if ( !fileHandle )
861 continue;
862
863 if( len >= MAX_LOCDAMAGE_TEXT )
864 {
865 G_Printf( va( S_COLOR_RED "file too large: %s is %i, max allowed is %i", filename, len, MAX_LOCDAMAGE_TEXT ) );
866 trap_FS_FCloseFile( fileHandle );
867 continue;
868 }
869
870 trap_FS_Read( buffer, len, fileHandle );
871 buffer[len] = 0;
872 trap_FS_FCloseFile( fileHandle );
873
874 G_ParseArmourScript( buffer, i );
875 }
876 }
877
878 ////////TA: locdamage
879
880
881 /*
882 ============
883 T_Damage
884
885 targ entity that is being damaged
886 inflictor entity that is causing the damage
887 attacker entity that caused the inflictor to damage targ
888 example: targ=monster, inflictor=rocket, attacker=player
889
890 dir direction of the attack for knockback
891 point point at which the damage is being inflicted, used for headshots
892 damage amount of damage being inflicted
893 knockback force to be applied against targ as a result of the damage
894
895 inflictor, attacker, dir, and point can be NULL for environmental effects
896
897 dflags these flags are used to control how T_Damage works
898 DAMAGE_RADIUS damage was indirect (from a nearby explosion)
899 DAMAGE_NO_ARMOR armor does not protect from this damage
900 DAMAGE_NO_KNOCKBACK do not affect velocity, just view angles
901 DAMAGE_NO_PROTECTION kills godmode, armor, everything
902 ============
903 */
904
905 //TA: team is the team that is immune to this damage
G_SelectiveDamage(gentity_t * targ,gentity_t * inflictor,gentity_t * attacker,vec3_t dir,vec3_t point,int damage,int dflags,int mod,int team)906 void G_SelectiveDamage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker,
907 vec3_t dir, vec3_t point, int damage, int dflags, int mod, int team )
908 {
909 if( targ->client && ( team != targ->client->ps.stats[ STAT_PTEAM ] ) )
910 G_Damage( targ, inflictor, attacker, dir, point, damage, dflags, mod );
911 }
912
G_Damage(gentity_t * targ,gentity_t * inflictor,gentity_t * attacker,vec3_t dir,vec3_t point,int damage,int dflags,int mod)913 void G_Damage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker,
914 vec3_t dir, vec3_t point, int damage, int dflags, int mod )
915 {
916 gclient_t *client;
917 int take;
918 int save;
919 int asave = 0;
920 int knockback;
921
922 if( !targ->takedamage )
923 return;
924
925 // the intermission has allready been qualified for, so don't
926 // allow any extra scoring
927 if( level.intermissionQueued )
928 return;
929
930 if( !inflictor )
931 inflictor = &g_entities[ ENTITYNUM_WORLD ];
932
933 if( !attacker )
934 attacker = &g_entities[ ENTITYNUM_WORLD ];
935
936 // shootable doors / buttons don't actually have any health
937 if( targ->s.eType == ET_MOVER )
938 {
939 if( targ->use && ( targ->moverState == MOVER_POS1 ||
940 targ->moverState == ROTATOR_POS1 ) )
941 targ->use( targ, inflictor, attacker );
942
943 return;
944 }
945
946 client = targ->client;
947
948 if( client )
949 {
950 if( client->noclip )
951 return;
952 }
953
954 if( !dir )
955 dflags |= DAMAGE_NO_KNOCKBACK;
956 else
957 VectorNormalize( dir );
958
959 knockback = damage;
960
961 // silly hack to give norf his knockbacking teslas
962 if( !strcmp( inflictor->classname, "team_human_tesla" ) )
963 knockback *= 4;
964
965 // ...and for goon pouncing
966 if( mod == MOD_LEVEL3_POUNCE )
967 knockback *= 3;
968
969 if( targ->client )
970 {
971 knockback = (int)( (float)knockback *
972 BG_FindKnockbackScaleForClass( targ->client->ps.stats[ STAT_PCLASS ] ) );
973 }
974
975 if( knockback > 200 )
976 knockback = 200;
977
978 if( targ->flags & FL_NO_KNOCKBACK )
979 knockback = 0;
980
981 if( dflags & DAMAGE_NO_KNOCKBACK )
982 knockback = 0;
983
984 // figure momentum add, even if the damage won't be taken
985 if( knockback && targ->client )
986 {
987 vec3_t kvel;
988 float mass;
989
990 mass = 200;
991
992 VectorScale( dir, g_knockback.value * (float)knockback / mass, kvel );
993 VectorAdd( targ->client->ps.velocity, kvel, targ->client->ps.velocity );
994
995 // set the timer so that the other client can't cancel
996 // out the movement immediately
997 if( !targ->client->ps.pm_time )
998 {
999 int t;
1000
1001 t = knockback * 2;
1002 if( t < 50 )
1003 t = 50;
1004
1005 if( t > 200 )
1006 t = 200;
1007
1008 targ->client->ps.pm_time = t;
1009 targ->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
1010 }
1011 }
1012
1013 // check for completely getting out of the damage
1014 if( !( dflags & DAMAGE_NO_PROTECTION ) )
1015 {
1016
1017 // if TF_NO_FRIENDLY_FIRE is set, don't do damage to the target
1018 // if the attacker was on the same team
1019 if( targ != attacker && OnSameTeam( targ, attacker ) )
1020 {
1021 if( !g_friendlyFire.integer )
1022 return;
1023 }
1024
1025 // check for godmode
1026 if ( targ->flags & FL_GODMODE )
1027 return;
1028 }
1029
1030 // add to the attacker's hit counter
1031 if( attacker->client && targ != attacker && targ->health > 0
1032 && targ->s.eType != ET_MISSILE
1033 && targ->s.eType != ET_GENERAL )
1034 {
1035 if( OnSameTeam( targ, attacker ) )
1036 attacker->client->ps.persistant[ PERS_HITS ]--;
1037 else
1038 attacker->client->ps.persistant[ PERS_HITS ]++;
1039 }
1040
1041 take = damage;
1042 save = 0;
1043
1044 // add to the damage inflicted on a player this frame
1045 // the total will be turned into screen blends and view angle kicks
1046 // at the end of the frame
1047 if( client )
1048 {
1049 if( attacker )
1050 client->ps.persistant[ PERS_ATTACKER ] = attacker->s.number;
1051 else
1052 client->ps.persistant[ PERS_ATTACKER ] = ENTITYNUM_WORLD;
1053
1054 client->damage_armor += asave;
1055 client->damage_blood += take;
1056 client->damage_knockback += knockback;
1057
1058 if( dir )
1059 {
1060 VectorCopy ( dir, client->damage_from );
1061 client->damage_fromWorld = qfalse;
1062 }
1063 else
1064 {
1065 VectorCopy ( targ->r.currentOrigin, client->damage_from );
1066 client->damage_fromWorld = qtrue;
1067 }
1068
1069 // set the last client who damaged the target
1070 targ->client->lasthurt_client = attacker->s.number;
1071 targ->client->lasthurt_mod = mod;
1072 take = (int)( (float)take * G_CalcDamageModifier( point, targ, attacker,
1073 client->ps.stats[ STAT_PCLASS ], dflags ) );
1074
1075 //if boosted poison every attack
1076 if( attacker->client && attacker->client->ps.stats[ STAT_STATE ] & SS_BOOSTED )
1077 {
1078 if( !( targ->client->ps.stats[ STAT_STATE ] & SS_POISONED ) &&
1079 !BG_InventoryContainsUpgrade( UP_BATTLESUIT, targ->client->ps.stats ) &&
1080 mod != MOD_LEVEL2_ZAP &&
1081 targ->client->poisonImmunityTime < level.time )
1082 {
1083 targ->client->ps.stats[ STAT_STATE ] |= SS_POISONED;
1084 targ->client->lastPoisonTime = level.time;
1085 targ->client->lastPoisonClient = attacker;
1086 }
1087 }
1088 }
1089
1090 if( take < 1 )
1091 take = 1;
1092
1093 if( g_debugDamage.integer )
1094 {
1095 G_Printf( "%i: client:%i health:%i damage:%i armor:%i\n", level.time, targ->s.number,
1096 targ->health, take, asave );
1097 }
1098
1099 // do the damage
1100 if( take )
1101 {
1102 targ->health = targ->health - take;
1103
1104 if( targ->client )
1105 targ->client->ps.stats[ STAT_HEALTH ] = targ->health;
1106
1107 targ->lastDamageTime = level.time;
1108
1109 //TA: add to the attackers "account" on the target
1110 if( targ->client && attacker->client )
1111 {
1112 if( attacker != targ && !OnSameTeam( targ, attacker ) )
1113 targ->credits[ attacker->client->ps.clientNum ] += take;
1114 }
1115
1116 if( targ->health <= 0 )
1117 {
1118 if( client )
1119 targ->flags |= FL_NO_KNOCKBACK;
1120
1121 if( targ->health < -999 )
1122 targ->health = -999;
1123
1124 targ->enemy = attacker;
1125 targ->die( targ, inflictor, attacker, take, mod );
1126 return;
1127 }
1128 else if( targ->pain )
1129 targ->pain( targ, attacker, take );
1130 }
1131 }
1132
1133
1134 /*
1135 ============
1136 CanDamage
1137
1138 Returns qtrue if the inflictor can directly damage the target. Used for
1139 explosions and melee attacks.
1140 ============
1141 */
CanDamage(gentity_t * targ,vec3_t origin)1142 qboolean CanDamage( gentity_t *targ, vec3_t origin )
1143 {
1144 vec3_t dest;
1145 trace_t tr;
1146 vec3_t midpoint;
1147
1148 // use the midpoint of the bounds instead of the origin, because
1149 // bmodels may have their origin is 0,0,0
1150 VectorAdd( targ->r.absmin, targ->r.absmax, midpoint );
1151 VectorScale( midpoint, 0.5, midpoint );
1152
1153 VectorCopy( midpoint, dest );
1154 trap_Trace( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID );
1155 if( tr.fraction == 1.0 || tr.entityNum == targ->s.number )
1156 return qtrue;
1157
1158 // this should probably check in the plane of projection,
1159 // rather than in world coordinate, and also include Z
1160 VectorCopy( midpoint, dest );
1161 dest[ 0 ] += 15.0;
1162 dest[ 1 ] += 15.0;
1163 trap_Trace( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID );
1164 if( tr.fraction == 1.0 )
1165 return qtrue;
1166
1167 VectorCopy( midpoint, dest );
1168 dest[ 0 ] += 15.0;
1169 dest[ 1 ] -= 15.0;
1170 trap_Trace( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID );
1171 if( tr.fraction == 1.0 )
1172 return qtrue;
1173
1174 VectorCopy( midpoint, dest );
1175 dest[ 0 ] -= 15.0;
1176 dest[ 1 ] += 15.0;
1177 trap_Trace( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID );
1178 if( tr.fraction == 1.0 )
1179 return qtrue;
1180
1181 VectorCopy( midpoint, dest );
1182 dest[ 0 ] -= 15.0;
1183 dest[ 1 ] -= 15.0;
1184 trap_Trace( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID );
1185 if( tr.fraction == 1.0 )
1186 return qtrue;
1187
1188 return qfalse;
1189 }
1190
1191
1192 //TA:
1193 /*
1194 ============
1195 G_SelectiveRadiusDamage
1196 ============
1197 */
G_SelectiveRadiusDamage(vec3_t origin,gentity_t * attacker,float damage,float radius,gentity_t * ignore,int mod,int team)1198 qboolean G_SelectiveRadiusDamage( vec3_t origin, gentity_t *attacker, float damage,
1199 float radius, gentity_t *ignore, int mod, int team )
1200 {
1201 float points, dist;
1202 gentity_t *ent;
1203 int entityList[ MAX_GENTITIES ];
1204 int numListedEntities;
1205 vec3_t mins, maxs;
1206 vec3_t v;
1207 vec3_t dir;
1208 int i, e;
1209 qboolean hitClient = qfalse;
1210
1211 if( radius < 1 )
1212 radius = 1;
1213
1214 for( i = 0; i < 3; i++ )
1215 {
1216 mins[ i ] = origin[ i ] - radius;
1217 maxs[ i ] = origin[ i ] + radius;
1218 }
1219
1220 numListedEntities = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
1221
1222 for( e = 0; e < numListedEntities; e++ )
1223 {
1224 ent = &g_entities[ entityList[ e ] ];
1225
1226 if( ent == ignore )
1227 continue;
1228
1229 if( !ent->takedamage )
1230 continue;
1231
1232 // find the distance from the edge of the bounding box
1233 for( i = 0 ; i < 3 ; i++ )
1234 {
1235 if( origin[ i ] < ent->r.absmin[ i ] )
1236 v[ i ] = ent->r.absmin[ i ] - origin[ i ];
1237 else if( origin[ i ] > ent->r.absmax[ i ] )
1238 v[ i ] = origin[ i ] - ent->r.absmax[ i ];
1239 else
1240 v[ i ] = 0;
1241 }
1242
1243 dist = VectorLength( v );
1244 if( dist >= radius )
1245 continue;
1246
1247 points = damage * ( 1.0 - dist / radius );
1248
1249 if( CanDamage( ent, origin ) )
1250 {
1251 VectorSubtract( ent->r.currentOrigin, origin, dir );
1252 // push the center of mass higher than the origin so players
1253 // get knocked into the air more
1254 dir[ 2 ] += 24;
1255 G_SelectiveDamage( ent, NULL, attacker, dir, origin,
1256 (int)points, DAMAGE_RADIUS|DAMAGE_NO_LOCDAMAGE, mod, team );
1257 }
1258 }
1259
1260 return hitClient;
1261 }
1262
1263
1264 /*
1265 ============
1266 G_RadiusDamage
1267 ============
1268 */
G_RadiusDamage(vec3_t origin,gentity_t * attacker,float damage,float radius,gentity_t * ignore,int mod)1269 qboolean G_RadiusDamage( vec3_t origin, gentity_t *attacker, float damage,
1270 float radius, gentity_t *ignore, int mod )
1271 {
1272 float points, dist;
1273 gentity_t *ent;
1274 int entityList[ MAX_GENTITIES ];
1275 int numListedEntities;
1276 vec3_t mins, maxs;
1277 vec3_t v;
1278 vec3_t dir;
1279 int i, e;
1280 qboolean hitClient = qfalse;
1281
1282 if( radius < 1 )
1283 radius = 1;
1284
1285 for( i = 0; i < 3; i++ )
1286 {
1287 mins[ i ] = origin[ i ] - radius;
1288 maxs[ i ] = origin[ i ] + radius;
1289 }
1290
1291 numListedEntities = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
1292
1293 for( e = 0; e < numListedEntities; e++ )
1294 {
1295 ent = &g_entities[ entityList[ e ] ];
1296
1297 if( ent == ignore )
1298 continue;
1299
1300 if( !ent->takedamage )
1301 continue;
1302
1303 // find the distance from the edge of the bounding box
1304 for( i = 0; i < 3; i++ )
1305 {
1306 if( origin[ i ] < ent->r.absmin[ i ] )
1307 v[ i ] = ent->r.absmin[ i ] - origin[ i ];
1308 else if( origin[ i ] > ent->r.absmax[ i ] )
1309 v[ i ] = origin[ i ] - ent->r.absmax[ i ];
1310 else
1311 v[ i ] = 0;
1312 }
1313
1314 dist = VectorLength( v );
1315 if( dist >= radius )
1316 continue;
1317
1318 points = damage * ( 1.0 - dist / radius );
1319
1320 if( CanDamage( ent, origin ) )
1321 {
1322 VectorSubtract( ent->r.currentOrigin, origin, dir );
1323 // push the center of mass higher than the origin so players
1324 // get knocked into the air more
1325 dir[ 2 ] += 24;
1326 G_Damage( ent, NULL, attacker, dir, origin,
1327 (int)points, DAMAGE_RADIUS|DAMAGE_NO_LOCDAMAGE, mod );
1328 }
1329 }
1330
1331 return hitClient;
1332 }
1333