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