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 && targ != attacker && targ->health > 0
978 && targ->s.eType != ET_MISSILE
979 && targ->s.eType != ET_GENERAL) {
980 if ( OnSameTeam( targ, attacker ) ) {
981 attacker->client->ps.persistant[PERS_HITS]--;
982 } else {
983 attacker->client->ps.persistant[PERS_HITS]++;
984 }
985 attacker->client->ps.persistant[PERS_ATTACKEE_ARMOR] = (targ->health<<8)|(client->ps.stats[STAT_ARMOR]);
986 }
987
988 // always give half damage if hurting self
989 // calculated after knockback, so rocket jumping works
990 if ( targ == attacker) {
991 damage *= 0.5;
992 }
993
994 if ( damage < 1 ) {
995 damage = 1;
996 }
997 take = damage;
998 save = 0;
999
1000 // save some from armor
1001 asave = CheckArmor (targ, take, dflags);
1002 take -= asave;
1003
1004 if ( g_debugDamage.integer ) {
1005 G_Printf( "%i: client:%i health:%i damage:%i armor:%i\n", level.time, targ->s.number,
1006 targ->health, take, asave );
1007 }
1008
1009 // add to the damage inflicted on a player this frame
1010 // the total will be turned into screen blends and view angle kicks
1011 // at the end of the frame
1012 if ( client ) {
1013 if ( attacker ) {
1014 client->ps.persistant[PERS_ATTACKER] = attacker->s.number;
1015 } else {
1016 client->ps.persistant[PERS_ATTACKER] = ENTITYNUM_WORLD;
1017 }
1018 client->damage_armor += asave;
1019 client->damage_blood += take;
1020 client->damage_knockback += knockback;
1021 if ( dir ) {
1022 VectorCopy ( dir, client->damage_from );
1023 client->damage_fromWorld = qfalse;
1024 } else {
1025 VectorCopy ( targ->r.currentOrigin, client->damage_from );
1026 client->damage_fromWorld = qtrue;
1027 }
1028 }
1029
1030 // See if it's the player hurting the emeny flag carrier
1031 #ifdef MISSIONPACK
1032 if( g_gametype.integer == GT_CTF || g_gametype.integer == GT_1FCTF ) {
1033 #else
1034 if( g_gametype.integer == GT_CTF) {
1035 #endif
1036 Team_CheckHurtCarrier(targ, attacker);
1037 }
1038
1039 if (targ->client) {
1040 // set the last client who damaged the target
1041 targ->client->lasthurt_client = attacker->s.number;
1042 targ->client->lasthurt_mod = mod;
1043 }
1044
1045 // do the damage
1046 if (take) {
1047 targ->health = targ->health - take;
1048 if ( targ->client ) {
1049 targ->client->ps.stats[STAT_HEALTH] = targ->health;
1050 }
1051
1052 if ( targ->health <= 0 ) {
1053 if ( client )
1054 targ->flags |= FL_NO_KNOCKBACK;
1055
1056 if (targ->health < -999)
1057 targ->health = -999;
1058
1059 targ->enemy = attacker;
1060 targ->die (targ, inflictor, attacker, take, mod);
1061 return;
1062 } else if ( targ->pain ) {
1063 targ->pain (targ, attacker, take);
1064 }
1065 }
1066
1067 }
1068
1069
1070 /*
1071 ============
1072 CanDamage
1073
1074 Returns qtrue if the inflictor can directly damage the target. Used for
1075 explosions and melee attacks.
1076 ============
1077 */
1078 qboolean CanDamage (gentity_t *targ, vec3_t origin) {
1079 vec3_t dest;
1080 trace_t tr;
1081 vec3_t midpoint;
1082
1083 // use the midpoint of the bounds instead of the origin, because
1084 // bmodels may have their origin is 0,0,0
1085 VectorAdd (targ->r.absmin, targ->r.absmax, midpoint);
1086 VectorScale (midpoint, 0.5, midpoint);
1087
1088 VectorCopy (midpoint, dest);
1089 trap_Trace ( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID);
1090 if (tr.fraction == 1.0 || tr.entityNum == targ->s.number)
1091 return qtrue;
1092
1093 // this should probably check in the plane of projection,
1094 // rather than in world coordinate, and also include Z
1095 VectorCopy (midpoint, dest);
1096 dest[0] += 15.0;
1097 dest[1] += 15.0;
1098 trap_Trace ( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID);
1099 if (tr.fraction == 1.0)
1100 return qtrue;
1101
1102 VectorCopy (midpoint, dest);
1103 dest[0] += 15.0;
1104 dest[1] -= 15.0;
1105 trap_Trace ( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID);
1106 if (tr.fraction == 1.0)
1107 return qtrue;
1108
1109 VectorCopy (midpoint, dest);
1110 dest[0] -= 15.0;
1111 dest[1] += 15.0;
1112 trap_Trace ( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID);
1113 if (tr.fraction == 1.0)
1114 return qtrue;
1115
1116 VectorCopy (midpoint, dest);
1117 dest[0] -= 15.0;
1118 dest[1] -= 15.0;
1119 trap_Trace ( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID);
1120 if (tr.fraction == 1.0)
1121 return qtrue;
1122
1123
1124 return qfalse;
1125 }
1126
1127
1128 /*
1129 ============
1130 G_RadiusDamage
1131 ============
1132 */
1133 qboolean G_RadiusDamage ( vec3_t origin, gentity_t *attacker, float damage, float radius,
1134 gentity_t *ignore, int mod) {
1135 float points, dist;
1136 gentity_t *ent;
1137 int entityList[MAX_GENTITIES];
1138 int numListedEntities;
1139 vec3_t mins, maxs;
1140 vec3_t v;
1141 vec3_t dir;
1142 int i, e;
1143 qboolean hitClient = qfalse;
1144
1145 if ( radius < 1 ) {
1146 radius = 1;
1147 }
1148
1149 for ( i = 0 ; i < 3 ; i++ ) {
1150 mins[i] = origin[i] - radius;
1151 maxs[i] = origin[i] + radius;
1152 }
1153
1154 numListedEntities = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
1155
1156 for ( e = 0 ; e < numListedEntities ; e++ ) {
1157 ent = &g_entities[entityList[ e ]];
1158
1159 if (ent == ignore)
1160 continue;
1161 if (!ent->takedamage)
1162 continue;
1163
1164 // find the distance from the edge of the bounding box
1165 for ( i = 0 ; i < 3 ; i++ ) {
1166 if ( origin[i] < ent->r.absmin[i] ) {
1167 v[i] = ent->r.absmin[i] - origin[i];
1168 } else if ( origin[i] > ent->r.absmax[i] ) {
1169 v[i] = origin[i] - ent->r.absmax[i];
1170 } else {
1171 v[i] = 0;
1172 }
1173 }
1174
1175 dist = VectorLength( v );
1176 if ( dist >= radius ) {
1177 continue;
1178 }
1179
1180 points = damage * ( 1.0 - dist / radius );
1181
1182 if( CanDamage (ent, origin) ) {
1183 if( LogAccuracyHit( ent, attacker ) ) {
1184 hitClient = qtrue;
1185 }
1186 VectorSubtract (ent->r.currentOrigin, origin, dir);
1187 // push the center of mass higher than the origin so players
1188 // get knocked into the air more
1189 dir[2] += 24;
1190 G_Damage (ent, NULL, attacker, dir, origin, (int)points, DAMAGE_RADIUS, mod);
1191 }
1192 }
1193
1194 return hitClient;
1195 }
1196