1 /*
2 ===========================================================================
3 Copyright (C) 1999-2005 Id Software, Inc.
4
5 This file is part of Quake III Arena source code.
6
7 Quake III Arena source code is free software; you can redistribute it
8 and/or modify it under the terms of the GNU General Public License as
9 published by the Free Software Foundation; either version 2 of the License,
10 or (at your option) any later version.
11
12 Quake III Arena source code is distributed in the hope that it will be
13 useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with Quake III Arena source code; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
20 ===========================================================================
21 */
22 //
23 // g_combat.c
24
25 #include "g_local.h"
26
27
28 /*
29 ============
30 ScorePlum
31 ============
32 */
ScorePlum(gentity_t * ent,vec3_t origin,int score)33 void ScorePlum( gentity_t *ent, vec3_t origin, int score ) {
34 gentity_t *plum;
35
36 plum = G_TempEntity( origin, EV_SCOREPLUM );
37 // only send this temp entity to a single client
38 plum->r.svFlags |= SVF_SINGLECLIENT;
39 plum->r.singleClient = ent->s.number;
40 //
41 plum->s.otherEntityNum = ent->s.number;
42 plum->s.time = score;
43 }
44
45 /*
46 ============
47 AddScore
48
49 Adds score to both the client and his team
50 ============
51 */
AddScore(gentity_t * ent,vec3_t origin,int score)52 void AddScore( gentity_t *ent, vec3_t origin, int score ) {
53 if ( !ent->client ) {
54 return;
55 }
56 // no scoring during pre-match warmup
57 if ( level.warmupTime ) {
58 return;
59 }
60 // show score plum
61 ScorePlum(ent, origin, score);
62 //
63 ent->client->ps.persistant[PERS_SCORE] += score;
64 if ( g_gametype.integer == GT_TEAM )
65 level.teamScores[ ent->client->ps.persistant[PERS_TEAM] ] += score;
66 CalculateRanks();
67 }
68
69 /*
70 =================
71 TossClientItems
72
73 Toss the weapon and powerups for the killed player
74 =================
75 */
TossClientItems(gentity_t * self)76 void TossClientItems( gentity_t *self ) {
77 gitem_t *item;
78 int weapon;
79 float angle;
80 int i;
81 gentity_t *drop;
82
83 // drop the weapon if not a gauntlet or machinegun
84 weapon = self->s.weapon;
85
86 // make a special check to see if they are changing to a new
87 // weapon that isn't the mg or gauntlet. Without this, a client
88 // can pick up a weapon, be killed, and not drop the weapon because
89 // their weapon change hasn't completed yet and they are still holding the MG.
90 if ( weapon == WP_MACHINEGUN || weapon == WP_GRAPPLING_HOOK ) {
91 if ( self->client->ps.weaponstate == WEAPON_DROPPING ) {
92 weapon = self->client->pers.cmd.weapon;
93 }
94 if ( !( self->client->ps.stats[STAT_WEAPONS] & ( 1 << weapon ) ) ) {
95 weapon = WP_NONE;
96 }
97 }
98
99 if ( weapon > WP_MACHINEGUN && weapon != WP_GRAPPLING_HOOK &&
100 self->client->ps.ammo[ weapon ] ) {
101 // find the item type for this weapon
102 item = BG_FindItemForWeapon( weapon );
103
104 // spawn the item
105 Drop_Item( self, item, 0 );
106 }
107
108 // drop all the powerups if not in teamplay
109 if ( g_gametype.integer != GT_TEAM ) {
110 angle = 45;
111 for ( i = 1 ; i < PW_NUM_POWERUPS ; i++ ) {
112 if ( self->client->ps.powerups[ i ] > level.time ) {
113 item = BG_FindItemForPowerup( i );
114 if ( !item ) {
115 continue;
116 }
117 drop = Drop_Item( self, item, angle );
118 // decide how many seconds it has left
119 drop->count = ( self->client->ps.powerups[ i ] - level.time ) / 1000;
120 if ( drop->count < 1 ) {
121 drop->count = 1;
122 }
123 angle += 45;
124 }
125 }
126 }
127 }
128
129 #ifdef MISSIONPACK
130
131 /*
132 =================
133 TossClientCubes
134 =================
135 */
136 extern gentity_t *neutralObelisk;
137
TossClientCubes(gentity_t * self)138 void TossClientCubes( gentity_t *self ) {
139 gitem_t *item;
140 gentity_t *drop;
141 vec3_t velocity;
142 vec3_t angles;
143 vec3_t origin;
144
145 self->client->ps.generic1 = 0;
146
147 // this should never happen but we should never
148 // get the server to crash due to skull being spawned in
149 if (!G_EntitiesFree()) {
150 return;
151 }
152
153 if( self->client->sess.sessionTeam == TEAM_RED ) {
154 item = BG_FindItem( "Red Cube" );
155 }
156 else {
157 item = BG_FindItem( "Blue Cube" );
158 }
159
160 angles[YAW] = (float)(level.time % 360);
161 angles[PITCH] = 0; // always forward
162 angles[ROLL] = 0;
163
164 AngleVectors( angles, velocity, NULL, NULL );
165 VectorScale( velocity, 150, velocity );
166 velocity[2] += 200 + crandom() * 50;
167
168 if( neutralObelisk ) {
169 VectorCopy( neutralObelisk->s.pos.trBase, origin );
170 origin[2] += 44;
171 } else {
172 VectorClear( origin ) ;
173 }
174
175 drop = LaunchItem( item, origin, velocity );
176
177 drop->nextthink = level.time + g_cubeTimeout.integer * 1000;
178 drop->think = G_FreeEntity;
179 drop->spawnflags = self->client->sess.sessionTeam;
180 }
181
182
183 /*
184 =================
185 TossClientPersistantPowerups
186 =================
187 */
TossClientPersistantPowerups(gentity_t * ent)188 void TossClientPersistantPowerups( gentity_t *ent ) {
189 gentity_t *powerup;
190
191 if( !ent->client ) {
192 return;
193 }
194
195 if( !ent->client->persistantPowerup ) {
196 return;
197 }
198
199 powerup = ent->client->persistantPowerup;
200
201 powerup->r.svFlags &= ~SVF_NOCLIENT;
202 powerup->s.eFlags &= ~EF_NODRAW;
203 powerup->r.contents = CONTENTS_TRIGGER;
204 trap_LinkEntity( powerup );
205
206 ent->client->ps.stats[STAT_PERSISTANT_POWERUP] = 0;
207 ent->client->persistantPowerup = NULL;
208 }
209 #endif
210
211
212 /*
213 ==================
214 LookAtKiller
215 ==================
216 */
LookAtKiller(gentity_t * self,gentity_t * inflictor,gentity_t * attacker)217 void LookAtKiller( gentity_t *self, gentity_t *inflictor, gentity_t *attacker ) {
218 vec3_t dir;
219 vec3_t angles;
220
221 if ( attacker && attacker != self ) {
222 VectorSubtract (attacker->s.pos.trBase, self->s.pos.trBase, dir);
223 } else if ( inflictor && inflictor != self ) {
224 VectorSubtract (inflictor->s.pos.trBase, self->s.pos.trBase, dir);
225 } else {
226 self->client->ps.stats[STAT_DEAD_YAW] = self->s.angles[YAW];
227 return;
228 }
229
230 self->client->ps.stats[STAT_DEAD_YAW] = vectoyaw ( dir );
231
232 angles[YAW] = vectoyaw ( dir );
233 angles[PITCH] = 0;
234 angles[ROLL] = 0;
235 }
236
237 /*
238 ==================
239 GibEntity
240 ==================
241 */
GibEntity(gentity_t * self,int killer)242 void GibEntity( gentity_t *self, int killer ) {
243 gentity_t *ent;
244 int i;
245
246 //if this entity still has kamikaze
247 if (self->s.eFlags & EF_KAMIKAZE) {
248 // check if there is a kamikaze timer around for this owner
249 for (i = 0; i < MAX_GENTITIES; i++) {
250 ent = &g_entities[i];
251 if (!ent->inuse)
252 continue;
253 if (ent->activator != self)
254 continue;
255 if (strcmp(ent->classname, "kamikaze timer"))
256 continue;
257 G_FreeEntity(ent);
258 break;
259 }
260 }
261 G_AddEvent( self, EV_GIB_PLAYER, killer );
262 self->takedamage = qfalse;
263 self->s.eType = ET_INVISIBLE;
264 self->r.contents = 0;
265 }
266
267 /*
268 ==================
269 body_die
270 ==================
271 */
body_die(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,int damage,int meansOfDeath)272 void body_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath ) {
273 if ( self->health > GIB_HEALTH ) {
274 return;
275 }
276 if ( !g_blood.integer ) {
277 self->health = GIB_HEALTH+1;
278 return;
279 }
280
281 GibEntity( self, 0 );
282 }
283
284
285 // these are just for logging, the client prints its own messages
286 char *modNames[] = {
287 "MOD_UNKNOWN",
288 "MOD_SHOTGUN",
289 "MOD_GAUNTLET",
290 "MOD_MACHINEGUN",
291 "MOD_GRENADE",
292 "MOD_GRENADE_SPLASH",
293 "MOD_ROCKET",
294 "MOD_ROCKET_SPLASH",
295 "MOD_PLASMA",
296 "MOD_PLASMA_SPLASH",
297 "MOD_RAILGUN",
298 "MOD_LIGHTNING",
299 "MOD_BFG",
300 "MOD_BFG_SPLASH",
301 "MOD_WATER",
302 "MOD_SLIME",
303 "MOD_LAVA",
304 "MOD_CRUSH",
305 "MOD_TELEFRAG",
306 "MOD_FALLING",
307 "MOD_SUICIDE",
308 "MOD_TARGET_LASER",
309 "MOD_TRIGGER_HURT",
310 #ifdef MISSIONPACK
311 "MOD_NAIL",
312 "MOD_CHAINGUN",
313 "MOD_PROXIMITY_MINE",
314 "MOD_KAMIKAZE",
315 "MOD_JUICED",
316 #endif
317 "MOD_GRAPPLE"
318 };
319
320 #ifdef MISSIONPACK
321 /*
322 ==================
323 Kamikaze_DeathActivate
324 ==================
325 */
Kamikaze_DeathActivate(gentity_t * ent)326 void Kamikaze_DeathActivate( gentity_t *ent ) {
327 G_StartKamikaze(ent);
328 G_FreeEntity(ent);
329 }
330
331 /*
332 ==================
333 Kamikaze_DeathTimer
334 ==================
335 */
Kamikaze_DeathTimer(gentity_t * self)336 void Kamikaze_DeathTimer( gentity_t *self ) {
337 gentity_t *ent;
338
339 ent = G_Spawn();
340 ent->classname = "kamikaze timer";
341 VectorCopy(self->s.pos.trBase, ent->s.pos.trBase);
342 ent->r.svFlags |= SVF_NOCLIENT;
343 ent->think = Kamikaze_DeathActivate;
344 ent->nextthink = level.time + 5 * 1000;
345
346 ent->activator = self;
347 }
348
349 #endif
350
351 /*
352 ==================
353 CheckAlmostCapture
354 ==================
355 */
CheckAlmostCapture(gentity_t * self,gentity_t * attacker)356 void CheckAlmostCapture( gentity_t *self, gentity_t *attacker ) {
357 gentity_t *ent;
358 vec3_t dir;
359 char *classname;
360
361 // if this player was carrying a flag
362 if ( self->client->ps.powerups[PW_REDFLAG] ||
363 self->client->ps.powerups[PW_BLUEFLAG] ||
364 self->client->ps.powerups[PW_NEUTRALFLAG] ) {
365 // get the goal flag this player should have been going for
366 if ( g_gametype.integer == GT_CTF ) {
367 if ( self->client->sess.sessionTeam == TEAM_BLUE ) {
368 classname = "team_CTF_blueflag";
369 }
370 else {
371 classname = "team_CTF_redflag";
372 }
373 }
374 else {
375 if ( self->client->sess.sessionTeam == TEAM_BLUE ) {
376 classname = "team_CTF_redflag";
377 }
378 else {
379 classname = "team_CTF_blueflag";
380 }
381 }
382 ent = NULL;
383 do
384 {
385 ent = G_Find(ent, FOFS(classname), classname);
386 } while (ent && (ent->flags & FL_DROPPED_ITEM));
387 // if we found the destination flag and it's not picked up
388 if (ent && !(ent->r.svFlags & SVF_NOCLIENT) ) {
389 // if the player was *very* close
390 VectorSubtract( self->client->ps.origin, ent->s.origin, dir );
391 if ( VectorLength(dir) < 200 ) {
392 self->client->ps.persistant[PERS_PLAYEREVENTS] ^= PLAYEREVENT_HOLYSHIT;
393 if ( attacker->client ) {
394 attacker->client->ps.persistant[PERS_PLAYEREVENTS] ^= PLAYEREVENT_HOLYSHIT;
395 }
396 }
397 }
398 }
399 }
400
401 /*
402 ==================
403 CheckAlmostScored
404 ==================
405 */
CheckAlmostScored(gentity_t * self,gentity_t * attacker)406 void CheckAlmostScored( gentity_t *self, gentity_t *attacker ) {
407 gentity_t *ent;
408 vec3_t dir;
409 char *classname;
410
411 // if the player was carrying cubes
412 if ( self->client->ps.generic1 ) {
413 if ( self->client->sess.sessionTeam == TEAM_BLUE ) {
414 classname = "team_redobelisk";
415 }
416 else {
417 classname = "team_blueobelisk";
418 }
419 ent = G_Find(NULL, FOFS(classname), classname);
420 // if we found the destination obelisk
421 if ( ent ) {
422 // if the player was *very* close
423 VectorSubtract( self->client->ps.origin, ent->s.origin, dir );
424 if ( VectorLength(dir) < 200 ) {
425 self->client->ps.persistant[PERS_PLAYEREVENTS] ^= PLAYEREVENT_HOLYSHIT;
426 if ( attacker->client ) {
427 attacker->client->ps.persistant[PERS_PLAYEREVENTS] ^= PLAYEREVENT_HOLYSHIT;
428 }
429 }
430 }
431 }
432 }
433
434 /*
435 ==================
436 player_die
437 ==================
438 */
player_die(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,int damage,int meansOfDeath)439 void player_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath ) {
440 gentity_t *ent;
441 int anim;
442 int contents;
443 int killer;
444 int i;
445 char *killerName, *obit;
446
447 if ( self->client->ps.pm_type == PM_DEAD ) {
448 return;
449 }
450
451 if ( level.intermissiontime ) {
452 return;
453 }
454
455 // check for an almost capture
456 CheckAlmostCapture( self, attacker );
457 // check for a player that almost brought in cubes
458 CheckAlmostScored( self, attacker );
459
460 if (self->client && self->client->hook) {
461 Weapon_HookFree(self->client->hook);
462 }
463 #ifdef MISSIONPACK
464 if ((self->client->ps.eFlags & EF_TICKING) && self->activator) {
465 self->client->ps.eFlags &= ~EF_TICKING;
466 self->activator->think = G_FreeEntity;
467 self->activator->nextthink = level.time;
468 }
469 #endif
470 self->client->ps.pm_type = PM_DEAD;
471
472 if ( attacker ) {
473 killer = attacker->s.number;
474 if ( attacker->client ) {
475 killerName = attacker->client->pers.netname;
476 } else {
477 killerName = "<non-client>";
478 }
479 } else {
480 killer = ENTITYNUM_WORLD;
481 killerName = "<world>";
482 }
483
484 if ( killer < 0 || killer >= MAX_CLIENTS ) {
485 killer = ENTITYNUM_WORLD;
486 killerName = "<world>";
487 }
488
489 if ( meansOfDeath < 0 || meansOfDeath >= sizeof( modNames ) / sizeof( modNames[0] ) ) {
490 obit = "<bad obituary>";
491 } else {
492 obit = modNames[meansOfDeath];
493 }
494
495 G_LogPrintf("Kill: %i %i %i: %s killed %s by %s\n",
496 killer, self->s.number, meansOfDeath, killerName,
497 self->client->pers.netname, obit );
498
499 // broadcast the death event to everyone
500 ent = G_TempEntity( self->r.currentOrigin, EV_OBITUARY );
501 ent->s.eventParm = meansOfDeath;
502 ent->s.otherEntityNum = self->s.number;
503 ent->s.otherEntityNum2 = killer;
504 ent->r.svFlags = SVF_BROADCAST; // send to everyone
505
506 self->enemy = attacker;
507
508 self->client->ps.persistant[PERS_KILLED]++;
509
510 if (attacker && attacker->client) {
511 attacker->client->lastkilled_client = self->s.number;
512
513 if ( attacker == self || OnSameTeam (self, attacker ) ) {
514 AddScore( attacker, self->r.currentOrigin, -1 );
515 } else {
516 AddScore( attacker, self->r.currentOrigin, 1 );
517
518 if( meansOfDeath == MOD_GAUNTLET ) {
519
520 // play humiliation on player
521 attacker->client->ps.persistant[PERS_GAUNTLET_FRAG_COUNT]++;
522
523 // add the sprite over the player's head
524 attacker->client->ps.eFlags &= ~(EF_AWARD_IMPRESSIVE | EF_AWARD_EXCELLENT | EF_AWARD_GAUNTLET | EF_AWARD_ASSIST | EF_AWARD_DEFEND | EF_AWARD_CAP );
525 attacker->client->ps.eFlags |= EF_AWARD_GAUNTLET;
526 attacker->client->rewardTime = level.time + REWARD_SPRITE_TIME;
527
528 // also play humiliation on target
529 self->client->ps.persistant[PERS_PLAYEREVENTS] ^= PLAYEREVENT_GAUNTLETREWARD;
530 }
531
532 // check for two kills in a short amount of time
533 // if this is close enough to the last kill, give a reward sound
534 if ( level.time - attacker->client->lastKillTime < CARNAGE_REWARD_TIME ) {
535 // play excellent on player
536 attacker->client->ps.persistant[PERS_EXCELLENT_COUNT]++;
537
538 // add the sprite over the player's head
539 attacker->client->ps.eFlags &= ~(EF_AWARD_IMPRESSIVE | EF_AWARD_EXCELLENT | EF_AWARD_GAUNTLET | EF_AWARD_ASSIST | EF_AWARD_DEFEND | EF_AWARD_CAP );
540 attacker->client->ps.eFlags |= EF_AWARD_EXCELLENT;
541 attacker->client->rewardTime = level.time + REWARD_SPRITE_TIME;
542 }
543 attacker->client->lastKillTime = level.time;
544
545 }
546 } else {
547 AddScore( self, self->r.currentOrigin, -1 );
548 }
549
550 // Add team bonuses
551 Team_FragBonuses(self, inflictor, attacker);
552
553 // if I committed suicide, the flag does not fall, it returns.
554 if (meansOfDeath == MOD_SUICIDE) {
555 if ( self->client->ps.powerups[PW_NEUTRALFLAG] ) { // only happens in One Flag CTF
556 Team_ReturnFlag( TEAM_FREE );
557 self->client->ps.powerups[PW_NEUTRALFLAG] = 0;
558 }
559 else if ( self->client->ps.powerups[PW_REDFLAG] ) { // only happens in standard CTF
560 Team_ReturnFlag( TEAM_RED );
561 self->client->ps.powerups[PW_REDFLAG] = 0;
562 }
563 else if ( self->client->ps.powerups[PW_BLUEFLAG] ) { // only happens in standard CTF
564 Team_ReturnFlag( TEAM_BLUE );
565 self->client->ps.powerups[PW_BLUEFLAG] = 0;
566 }
567 }
568
569 // if client is in a nodrop area, don't drop anything (but return CTF flags!)
570 contents = trap_PointContents( self->r.currentOrigin, -1 );
571 if ( !( contents & CONTENTS_NODROP )) {
572 TossClientItems( self );
573 }
574 else {
575 if ( self->client->ps.powerups[PW_NEUTRALFLAG] ) { // only happens in One Flag CTF
576 Team_ReturnFlag( TEAM_FREE );
577 }
578 else if ( self->client->ps.powerups[PW_REDFLAG] ) { // only happens in standard CTF
579 Team_ReturnFlag( TEAM_RED );
580 }
581 else if ( self->client->ps.powerups[PW_BLUEFLAG] ) { // only happens in standard CTF
582 Team_ReturnFlag( TEAM_BLUE );
583 }
584 }
585 #ifdef MISSIONPACK
586 TossClientPersistantPowerups( self );
587 if( g_gametype.integer == GT_HARVESTER ) {
588 TossClientCubes( self );
589 }
590 #endif
591
592 Cmd_Score_f( self ); // show scores
593 // send updated scores to any clients that are following this one,
594 // or they would get stale scoreboards
595 for ( i = 0 ; i < level.maxclients ; i++ ) {
596 gclient_t *client;
597
598 client = &level.clients[i];
599 if ( client->pers.connected != CON_CONNECTED ) {
600 continue;
601 }
602 if ( client->sess.sessionTeam != TEAM_SPECTATOR ) {
603 continue;
604 }
605 if ( client->sess.spectatorClient == self->s.number ) {
606 Cmd_Score_f( g_entities + i );
607 }
608 }
609
610 self->takedamage = qtrue; // can still be gibbed
611
612 self->s.weapon = WP_NONE;
613 self->s.powerups = 0;
614 self->r.contents = CONTENTS_CORPSE;
615
616 self->s.angles[0] = 0;
617 self->s.angles[2] = 0;
618 LookAtKiller (self, inflictor, attacker);
619
620 VectorCopy( self->s.angles, self->client->ps.viewangles );
621
622 self->s.loopSound = 0;
623
624 self->r.maxs[2] = -8;
625
626 // don't allow respawn until the death anim is done
627 // g_forcerespawn may force spawning at some later time
628 self->client->respawnTime = level.time + 1700;
629
630 // remove powerups
631 memset( self->client->ps.powerups, 0, sizeof(self->client->ps.powerups) );
632
633 // never gib in a nodrop
634 if ( (self->health <= GIB_HEALTH && !(contents & CONTENTS_NODROP) && g_blood.integer) || meansOfDeath == MOD_SUICIDE) {
635 // gib death
636 GibEntity( self, killer );
637 } else {
638 // normal death
639 static int i;
640
641 switch ( i ) {
642 case 0:
643 anim = BOTH_DEATH1;
644 break;
645 case 1:
646 anim = BOTH_DEATH2;
647 break;
648 case 2:
649 default:
650 anim = BOTH_DEATH3;
651 break;
652 }
653
654 // for the no-blood option, we need to prevent the health
655 // from going to gib level
656 if ( self->health <= GIB_HEALTH ) {
657 self->health = GIB_HEALTH+1;
658 }
659
660 self->client->ps.legsAnim =
661 ( ( self->client->ps.legsAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim;
662 self->client->ps.torsoAnim =
663 ( ( self->client->ps.torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim;
664
665 G_AddEvent( self, EV_DEATH1 + i, killer );
666
667 // the body can still be gibbed
668 self->die = body_die;
669
670 // globally cycle through the different death animations
671 i = ( i + 1 ) % 3;
672
673 #ifdef MISSIONPACK
674 if (self->s.eFlags & EF_KAMIKAZE) {
675 Kamikaze_DeathTimer( self );
676 }
677 #endif
678 }
679
680 trap_LinkEntity (self);
681
682 }
683
684
685 /*
686 ================
687 CheckArmor
688 ================
689 */
CheckArmor(gentity_t * ent,int damage,int dflags)690 int CheckArmor (gentity_t *ent, int damage, int dflags)
691 {
692 gclient_t *client;
693 int save;
694 int count;
695
696 if (!damage)
697 return 0;
698
699 client = ent->client;
700
701 if (!client)
702 return 0;
703
704 if (dflags & DAMAGE_NO_ARMOR)
705 return 0;
706
707 // armor
708 count = client->ps.stats[STAT_ARMOR];
709 save = ceil( damage * ARMOR_PROTECTION );
710 if (save >= count)
711 save = count;
712
713 if (!save)
714 return 0;
715
716 client->ps.stats[STAT_ARMOR] -= save;
717
718 return save;
719 }
720
721 /*
722 ================
723 RaySphereIntersections
724 ================
725 */
RaySphereIntersections(vec3_t origin,float radius,vec3_t point,vec3_t dir,vec3_t intersections[2])726 int RaySphereIntersections( vec3_t origin, float radius, vec3_t point, vec3_t dir, vec3_t intersections[2] ) {
727 float b, c, d, t;
728
729 // | origin - (point + t * dir) | = radius
730 // a = dir[0]^2 + dir[1]^2 + dir[2]^2;
731 // b = 2 * (dir[0] * (point[0] - origin[0]) + dir[1] * (point[1] - origin[1]) + dir[2] * (point[2] - origin[2]));
732 // c = (point[0] - origin[0])^2 + (point[1] - origin[1])^2 + (point[2] - origin[2])^2 - radius^2;
733
734 // normalize dir so a = 1
735 VectorNormalize(dir);
736 b = 2 * (dir[0] * (point[0] - origin[0]) + dir[1] * (point[1] - origin[1]) + dir[2] * (point[2] - origin[2]));
737 c = (point[0] - origin[0]) * (point[0] - origin[0]) +
738 (point[1] - origin[1]) * (point[1] - origin[1]) +
739 (point[2] - origin[2]) * (point[2] - origin[2]) -
740 radius * radius;
741
742 d = b * b - 4 * c;
743 if (d > 0) {
744 t = (- b + sqrt(d)) / 2;
745 VectorMA(point, t, dir, intersections[0]);
746 t = (- b - sqrt(d)) / 2;
747 VectorMA(point, t, dir, intersections[1]);
748 return 2;
749 }
750 else if (d == 0) {
751 t = (- b ) / 2;
752 VectorMA(point, t, dir, intersections[0]);
753 return 1;
754 }
755 return 0;
756 }
757
758 #ifdef MISSIONPACK
759 /*
760 ================
761 G_InvulnerabilityEffect
762 ================
763 */
G_InvulnerabilityEffect(gentity_t * targ,vec3_t dir,vec3_t point,vec3_t impactpoint,vec3_t bouncedir)764 int G_InvulnerabilityEffect( gentity_t *targ, vec3_t dir, vec3_t point, vec3_t impactpoint, vec3_t bouncedir ) {
765 gentity_t *impact;
766 vec3_t intersections[2], vec;
767 int n;
768
769 if ( !targ->client ) {
770 return qfalse;
771 }
772 VectorCopy(dir, vec);
773 VectorInverse(vec);
774 // sphere model radius = 42 units
775 n = RaySphereIntersections( targ->client->ps.origin, 42, point, vec, intersections);
776 if (n > 0) {
777 impact = G_TempEntity( targ->client->ps.origin, EV_INVUL_IMPACT );
778 VectorSubtract(intersections[0], targ->client->ps.origin, vec);
779 vectoangles(vec, impact->s.angles);
780 impact->s.angles[0] += 90;
781 if (impact->s.angles[0] > 360)
782 impact->s.angles[0] -= 360;
783 if ( impactpoint ) {
784 VectorCopy( intersections[0], impactpoint );
785 }
786 if ( bouncedir ) {
787 VectorCopy( vec, bouncedir );
788 VectorNormalize( bouncedir );
789 }
790 return qtrue;
791 }
792 else {
793 return qfalse;
794 }
795 }
796 #endif
797 /*
798 ============
799 T_Damage
800
801 targ entity that is being damaged
802 inflictor entity that is causing the damage
803 attacker entity that caused the inflictor to damage targ
804 example: targ=monster, inflictor=rocket, attacker=player
805
806 dir direction of the attack for knockback
807 point point at which the damage is being inflicted, used for headshots
808 damage amount of damage being inflicted
809 knockback force to be applied against targ as a result of the damage
810
811 inflictor, attacker, dir, and point can be NULL for environmental effects
812
813 dflags these flags are used to control how T_Damage works
814 DAMAGE_RADIUS damage was indirect (from a nearby explosion)
815 DAMAGE_NO_ARMOR armor does not protect from this damage
816 DAMAGE_NO_KNOCKBACK do not affect velocity, just view angles
817 DAMAGE_NO_PROTECTION kills godmode, armor, everything
818 ============
819 */
820
G_Damage(gentity_t * targ,gentity_t * inflictor,gentity_t * attacker,vec3_t dir,vec3_t point,int damage,int dflags,int mod)821 void G_Damage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker,
822 vec3_t dir, vec3_t point, int damage, int dflags, int mod ) {
823 gclient_t *client;
824 int take;
825 int save;
826 int asave;
827 int knockback;
828 int max;
829 #ifdef MISSIONPACK
830 vec3_t bouncedir, impactpoint;
831 #endif
832
833 if (!targ->takedamage) {
834 return;
835 }
836
837 // the intermission has allready been qualified for, so don't
838 // allow any extra scoring
839 if ( level.intermissionQueued ) {
840 return;
841 }
842 #ifdef MISSIONPACK
843 if ( targ->client && mod != MOD_JUICED) {
844 if ( targ->client->invulnerabilityTime > level.time) {
845 if ( dir && point ) {
846 G_InvulnerabilityEffect( targ, dir, point, impactpoint, bouncedir );
847 }
848 return;
849 }
850 }
851 #endif
852 if ( !inflictor ) {
853 inflictor = &g_entities[ENTITYNUM_WORLD];
854 }
855 if ( !attacker ) {
856 attacker = &g_entities[ENTITYNUM_WORLD];
857 }
858
859 // shootable doors / buttons don't actually have any health
860 if ( targ->s.eType == ET_MOVER ) {
861 if ( targ->use && targ->moverState == MOVER_POS1 ) {
862 targ->use( targ, inflictor, attacker );
863 }
864 return;
865 }
866 #ifdef MISSIONPACK
867 if( g_gametype.integer == GT_OBELISK && CheckObeliskAttack( targ, attacker ) ) {
868 return;
869 }
870 #endif
871 // reduce damage by the attacker's handicap value
872 // unless they are rocket jumping
873 if ( attacker->client && attacker != targ ) {
874 max = attacker->client->ps.stats[STAT_MAX_HEALTH];
875 #ifdef MISSIONPACK
876 if( bg_itemlist[attacker->client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_GUARD ) {
877 max /= 2;
878 }
879 #endif
880 damage = damage * max / 100;
881 }
882
883 client = targ->client;
884
885 if ( client ) {
886 if ( client->noclip ) {
887 return;
888 }
889 }
890
891 if ( !dir ) {
892 dflags |= DAMAGE_NO_KNOCKBACK;
893 } else {
894 VectorNormalize(dir);
895 }
896
897 knockback = damage;
898 if ( knockback > 200 ) {
899 knockback = 200;
900 }
901 if ( targ->flags & FL_NO_KNOCKBACK ) {
902 knockback = 0;
903 }
904 if ( dflags & DAMAGE_NO_KNOCKBACK ) {
905 knockback = 0;
906 }
907
908 // figure momentum add, even if the damage won't be taken
909 if ( knockback && targ->client ) {
910 vec3_t kvel;
911 float mass;
912
913 mass = 200;
914
915 VectorScale (dir, g_knockback.value * (float)knockback / mass, kvel);
916 VectorAdd (targ->client->ps.velocity, kvel, targ->client->ps.velocity);
917
918 // set the timer so that the other client can't cancel
919 // out the movement immediately
920 if ( !targ->client->ps.pm_time ) {
921 int t;
922
923 t = knockback * 2;
924 if ( t < 50 ) {
925 t = 50;
926 }
927 if ( t > 200 ) {
928 t = 200;
929 }
930 targ->client->ps.pm_time = t;
931 targ->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
932 }
933 }
934
935 // check for completely getting out of the damage
936 if ( !(dflags & DAMAGE_NO_PROTECTION) ) {
937
938 // if TF_NO_FRIENDLY_FIRE is set, don't do damage to the target
939 // if the attacker was on the same team
940 #ifdef MISSIONPACK
941 if ( mod != MOD_JUICED && targ != attacker && !(dflags & DAMAGE_NO_TEAM_PROTECTION) && OnSameTeam (targ, attacker) ) {
942 #else
943 if ( targ != attacker && OnSameTeam (targ, attacker) ) {
944 #endif
945 if ( !g_friendlyFire.integer ) {
946 return;
947 }
948 }
949 #ifdef MISSIONPACK
950 if (mod == MOD_PROXIMITY_MINE) {
951 if (inflictor && inflictor->parent && OnSameTeam(targ, inflictor->parent)) {
952 return;
953 }
954 if (targ == attacker) {
955 return;
956 }
957 }
958 #endif
959
960 // check for godmode
961 if ( targ->flags & FL_GODMODE ) {
962 return;
963 }
964 }
965
966 // battlesuit protects from all radius damage (but takes knockback)
967 // and protects 50% against all damage
968 if ( client && client->ps.powerups[PW_BATTLESUIT] ) {
969 G_AddEvent( targ, EV_POWERUP_BATTLESUIT, 0 );
970 if ( ( dflags & DAMAGE_RADIUS ) || ( mod == MOD_FALLING ) ) {
971 return;
972 }
973 damage *= 0.5;
974 }
975
976 // add to the attacker's hit counter (if the target isn't a general entity like a prox mine)
977 if ( attacker->client && client
978 && targ != attacker && targ->health > 0
979 && targ->s.eType != ET_MISSILE
980 && targ->s.eType != ET_GENERAL) {
981 if ( OnSameTeam( targ, attacker ) ) {
982 attacker->client->ps.persistant[PERS_HITS]--;
983 } else {
984 attacker->client->ps.persistant[PERS_HITS]++;
985 }
986 attacker->client->ps.persistant[PERS_ATTACKEE_ARMOR] = (targ->health<<8)|(client->ps.stats[STAT_ARMOR]);
987 }
988
989 // always give half damage if hurting self
990 // calculated after knockback, so rocket jumping works
991 if ( targ == attacker) {
992 damage *= 0.5;
993 }
994
995 if ( damage < 1 ) {
996 damage = 1;
997 }
998 take = damage;
999 save = 0;
1000
1001 // save some from armor
1002 asave = CheckArmor (targ, take, dflags);
1003 take -= asave;
1004
1005 if ( g_debugDamage.integer ) {
1006 G_Printf( "%i: client:%i health:%i damage:%i armor:%i\n", level.time, targ->s.number,
1007 targ->health, take, asave );
1008 }
1009
1010 // add to the damage inflicted on a player this frame
1011 // the total will be turned into screen blends and view angle kicks
1012 // at the end of the frame
1013 if ( client ) {
1014 if ( attacker ) {
1015 client->ps.persistant[PERS_ATTACKER] = attacker->s.number;
1016 } else {
1017 client->ps.persistant[PERS_ATTACKER] = ENTITYNUM_WORLD;
1018 }
1019 client->damage_armor += asave;
1020 client->damage_blood += take;
1021 client->damage_knockback += knockback;
1022 if ( dir ) {
1023 VectorCopy ( dir, client->damage_from );
1024 client->damage_fromWorld = qfalse;
1025 } else {
1026 VectorCopy ( targ->r.currentOrigin, client->damage_from );
1027 client->damage_fromWorld = qtrue;
1028 }
1029 }
1030
1031 // See if it's the player hurting the emeny flag carrier
1032 #ifdef MISSIONPACK
1033 if( g_gametype.integer == GT_CTF || g_gametype.integer == GT_1FCTF ) {
1034 #else
1035 if( g_gametype.integer == GT_CTF) {
1036 #endif
1037 Team_CheckHurtCarrier(targ, attacker);
1038 }
1039
1040 if (targ->client) {
1041 // set the last client who damaged the target
1042 targ->client->lasthurt_client = attacker->s.number;
1043 targ->client->lasthurt_mod = mod;
1044 }
1045
1046 // do the damage
1047 if (take) {
1048 targ->health = targ->health - take;
1049 if ( targ->client ) {
1050 targ->client->ps.stats[STAT_HEALTH] = targ->health;
1051 }
1052
1053 if ( targ->health <= 0 ) {
1054 if ( client )
1055 targ->flags |= FL_NO_KNOCKBACK;
1056
1057 if (targ->health < -999)
1058 targ->health = -999;
1059
1060 targ->enemy = attacker;
1061 targ->die (targ, inflictor, attacker, take, mod);
1062 return;
1063 } else if ( targ->pain ) {
1064 targ->pain (targ, attacker, take);
1065 }
1066 }
1067
1068 }
1069
1070
1071 /*
1072 ============
1073 CanDamage
1074
1075 Returns qtrue if the inflictor can directly damage the target. Used for
1076 explosions and melee attacks.
1077 ============
1078 */
1079 qboolean CanDamage (gentity_t *targ, vec3_t origin) {
1080 vec3_t dest;
1081 trace_t tr;
1082 vec3_t midpoint;
1083
1084 // use the midpoint of the bounds instead of the origin, because
1085 // bmodels may have their origin is 0,0,0
1086 VectorAdd (targ->r.absmin, targ->r.absmax, midpoint);
1087 VectorScale (midpoint, 0.5, midpoint);
1088
1089 VectorCopy (midpoint, dest);
1090 trap_Trace ( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID);
1091 if (tr.fraction == 1.0 || tr.entityNum == targ->s.number)
1092 return qtrue;
1093
1094 // this should probably check in the plane of projection,
1095 // rather than in world coordinate, and also include Z
1096 VectorCopy (midpoint, dest);
1097 dest[0] += 15.0;
1098 dest[1] += 15.0;
1099 trap_Trace ( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID);
1100 if (tr.fraction == 1.0)
1101 return qtrue;
1102
1103 VectorCopy (midpoint, dest);
1104 dest[0] += 15.0;
1105 dest[1] -= 15.0;
1106 trap_Trace ( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID);
1107 if (tr.fraction == 1.0)
1108 return qtrue;
1109
1110 VectorCopy (midpoint, dest);
1111 dest[0] -= 15.0;
1112 dest[1] += 15.0;
1113 trap_Trace ( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID);
1114 if (tr.fraction == 1.0)
1115 return qtrue;
1116
1117 VectorCopy (midpoint, dest);
1118 dest[0] -= 15.0;
1119 dest[1] -= 15.0;
1120 trap_Trace ( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID);
1121 if (tr.fraction == 1.0)
1122 return qtrue;
1123
1124
1125 return qfalse;
1126 }
1127
1128
1129 /*
1130 ============
1131 G_RadiusDamage
1132 ============
1133 */
1134 qboolean G_RadiusDamage ( vec3_t origin, gentity_t *attacker, float damage, float radius,
1135 gentity_t *ignore, int mod) {
1136 float points, dist;
1137 gentity_t *ent;
1138 int entityList[MAX_GENTITIES];
1139 int numListedEntities;
1140 vec3_t mins, maxs;
1141 vec3_t v;
1142 vec3_t dir;
1143 int i, e;
1144 qboolean hitClient = qfalse;
1145
1146 if ( radius < 1 ) {
1147 radius = 1;
1148 }
1149
1150 for ( i = 0 ; i < 3 ; i++ ) {
1151 mins[i] = origin[i] - radius;
1152 maxs[i] = origin[i] + radius;
1153 }
1154
1155 numListedEntities = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
1156
1157 for ( e = 0 ; e < numListedEntities ; e++ ) {
1158 ent = &g_entities[entityList[ e ]];
1159
1160 if (ent == ignore)
1161 continue;
1162 if (!ent->takedamage)
1163 continue;
1164
1165 // find the distance from the edge of the bounding box
1166 for ( i = 0 ; i < 3 ; i++ ) {
1167 if ( origin[i] < ent->r.absmin[i] ) {
1168 v[i] = ent->r.absmin[i] - origin[i];
1169 } else if ( origin[i] > ent->r.absmax[i] ) {
1170 v[i] = origin[i] - ent->r.absmax[i];
1171 } else {
1172 v[i] = 0;
1173 }
1174 }
1175
1176 dist = VectorLength( v );
1177 if ( dist >= radius ) {
1178 continue;
1179 }
1180
1181 points = damage * ( 1.0 - dist / radius );
1182
1183 if( CanDamage (ent, origin) ) {
1184 if( LogAccuracyHit( ent, attacker ) ) {
1185 hitClient = qtrue;
1186 }
1187 VectorSubtract (ent->r.currentOrigin, origin, dir);
1188 // push the center of mass higher than the origin so players
1189 // get knocked into the air more
1190 dir[2] += 24;
1191 G_Damage (ent, NULL, attacker, dir, origin, (int)points, DAMAGE_RADIUS, mod);
1192 }
1193 }
1194
1195 return hitClient;
1196 }
1197