Main Page | Class Hierarchy | Alphabetical List | Data Structures | Directories | File List | Data Fields | Globals

g_combat.c

Go to the documentation of this file.
00001 /*
00002 ===========================================================================
00003 Copyright (C) 1999-2005 Id Software, Inc.
00004 
00005 This file is part of Quake III Arena source code.
00006 
00007 Quake III Arena source code is free software; you can redistribute it
00008 and/or modify it under the terms of the GNU General Public License as
00009 published by the Free Software Foundation; either version 2 of the License,
00010 or (at your option) any later version.
00011 
00012 Quake III Arena source code is distributed in the hope that it will be
00013 useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
00014 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00015 GNU General Public License for more details.
00016 
00017 You should have received a copy of the GNU General Public License
00018 along with Foobar; if not, write to the Free Software
00019 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
00020 ===========================================================================
00021 */
00022 //
00023 // g_combat.c
00024 
00025 #include "g_local.h"
00026 
00027 
00028 /*
00029 ============
00030 ScorePlum
00031 ============
00032 */
00033 void ScorePlum( gentity_t *ent, vec3_t origin, int score ) {
00034     gentity_t *plum;
00035 
00036     plum = G_TempEntity( origin, EV_SCOREPLUM );
00037     // only send this temp entity to a single client
00038     plum->r.svFlags |= SVF_SINGLECLIENT;
00039     plum->r.singleClient = ent->s.number;
00040     //
00041     plum->s.otherEntityNum = ent->s.number;
00042     plum->s.time = score;
00043 }
00044 
00045 /*
00046 ============
00047 AddScore
00048 
00049 Adds score to both the client and his team
00050 ============
00051 */
00052 void AddScore( gentity_t *ent, vec3_t origin, int score ) {
00053     if ( !ent->client ) {
00054         return;
00055     }
00056     // no scoring during pre-match warmup
00057     if ( level.warmupTime ) {
00058         return;
00059     }
00060     // show score plum
00061     ScorePlum(ent, origin, score);
00062     //
00063     ent->client->ps.persistant[PERS_SCORE] += score;
00064     if ( g_gametype.integer == GT_TEAM )
00065         level.teamScores[ ent->client->ps.persistant[PERS_TEAM] ] += score;
00066     CalculateRanks();
00067 }
00068 
00069 /*
00070 =================
00071 TossClientItems
00072 
00073 Toss the weapon and powerups for the killed player
00074 =================
00075 */
00076 void TossClientItems( gentity_t *self ) {
00077     gitem_t     *item;
00078     int         weapon;
00079     float       angle;
00080     int         i;
00081     gentity_t   *drop;
00082 
00083     // drop the weapon if not a gauntlet or machinegun
00084     weapon = self->s.weapon;
00085 
00086     // make a special check to see if they are changing to a new
00087     // weapon that isn't the mg or gauntlet.  Without this, a client
00088     // can pick up a weapon, be killed, and not drop the weapon because
00089     // their weapon change hasn't completed yet and they are still holding the MG.
00090     if ( weapon == WP_MACHINEGUN || weapon == WP_GRAPPLING_HOOK ) {
00091         if ( self->client->ps.weaponstate == WEAPON_DROPPING ) {
00092             weapon = self->client->pers.cmd.weapon;
00093         }
00094         if ( !( self->client->ps.stats[STAT_WEAPONS] & ( 1 << weapon ) ) ) {
00095             weapon = WP_NONE;
00096         }
00097     }
00098 
00099     if ( weapon > WP_MACHINEGUN && weapon != WP_GRAPPLING_HOOK && 
00100         self->client->ps.ammo[ weapon ] ) {
00101         // find the item type for this weapon
00102         item = BG_FindItemForWeapon( weapon );
00103 
00104         // spawn the item
00105         Drop_Item( self, item, 0 );
00106     }
00107 
00108     // drop all the powerups if not in teamplay
00109     if ( g_gametype.integer != GT_TEAM ) {
00110         angle = 45;
00111         for ( i = 1 ; i < PW_NUM_POWERUPS ; i++ ) {
00112             if ( self->client->ps.powerups[ i ] > level.time ) {
00113                 item = BG_FindItemForPowerup( i );
00114                 if ( !item ) {
00115                     continue;
00116                 }
00117                 drop = Drop_Item( self, item, angle );
00118                 // decide how many seconds it has left
00119                 drop->count = ( self->client->ps.powerups[ i ] - level.time ) / 1000;
00120                 if ( drop->count < 1 ) {
00121                     drop->count = 1;
00122                 }
00123                 angle += 45;
00124             }
00125         }
00126     }
00127 }
00128 
00129 #ifdef MISSIONPACK
00130 
00131 /*
00132 =================
00133 TossClientCubes
00134 =================
00135 */
00136 extern gentity_t    *neutralObelisk;
00137 
00138 void TossClientCubes( gentity_t *self ) {
00139     gitem_t     *item;
00140     gentity_t   *drop;
00141     vec3_t      velocity;
00142     vec3_t      angles;
00143     vec3_t      origin;
00144 
00145     self->client->ps.generic1 = 0;
00146 
00147     // this should never happen but we should never
00148     // get the server to crash due to skull being spawned in
00149     if (!G_EntitiesFree()) {
00150         return;
00151     }
00152 
00153     if( self->client->sess.sessionTeam == TEAM_RED ) {
00154         item = BG_FindItem( "Red Cube" );
00155     }
00156     else {
00157         item = BG_FindItem( "Blue Cube" );
00158     }
00159 
00160     angles[YAW] = (float)(level.time % 360);
00161     angles[PITCH] = 0;  // always forward
00162     angles[ROLL] = 0;
00163 
00164     AngleVectors( angles, velocity, NULL, NULL );
00165     VectorScale( velocity, 150, velocity );
00166     velocity[2] += 200 + crandom() * 50;
00167 
00168     if( neutralObelisk ) {
00169         VectorCopy( neutralObelisk->s.pos.trBase, origin );
00170         origin[2] += 44;
00171     } else {
00172         VectorClear( origin ) ;
00173     }
00174 
00175     drop = LaunchItem( item, origin, velocity );
00176 
00177     drop->nextthink = level.time + g_cubeTimeout.integer * 1000;
00178     drop->think = G_FreeEntity;
00179     drop->spawnflags = self->client->sess.sessionTeam;
00180 }
00181 
00182 
00183 /*
00184 =================
00185 TossClientPersistantPowerups
00186 =================
00187 */
00188 void TossClientPersistantPowerups( gentity_t *ent ) {
00189     gentity_t   *powerup;
00190 
00191     if( !ent->client ) {
00192         return;
00193     }
00194 
00195     if( !ent->client->persistantPowerup ) {
00196         return;
00197     }
00198 
00199     powerup = ent->client->persistantPowerup;
00200 
00201     powerup->r.svFlags &= ~SVF_NOCLIENT;
00202     powerup->s.eFlags &= ~EF_NODRAW;
00203     powerup->r.contents = CONTENTS_TRIGGER;
00204     trap_LinkEntity( powerup );
00205 
00206     ent->client->ps.stats[STAT_PERSISTANT_POWERUP] = 0;
00207     ent->client->persistantPowerup = NULL;
00208 }
00209 #endif
00210 
00211 
00212 /*
00213 ==================
00214 LookAtKiller
00215 ==================
00216 */
00217 void LookAtKiller( gentity_t *self, gentity_t *inflictor, gentity_t *attacker ) {
00218     vec3_t      dir;
00219     vec3_t      angles;
00220 
00221     if ( attacker && attacker != self ) {
00222         VectorSubtract (attacker->s.pos.trBase, self->s.pos.trBase, dir);
00223     } else if ( inflictor && inflictor != self ) {
00224         VectorSubtract (inflictor->s.pos.trBase, self->s.pos.trBase, dir);
00225     } else {
00226         self->client->ps.stats[STAT_DEAD_YAW] = self->s.angles[YAW];
00227         return;
00228     }
00229 
00230     self->client->ps.stats[STAT_DEAD_YAW] = vectoyaw ( dir );
00231 
00232     angles[YAW] = vectoyaw ( dir );
00233     angles[PITCH] = 0; 
00234     angles[ROLL] = 0;
00235 }
00236 
00237 /*
00238 ==================
00239 GibEntity
00240 ==================
00241 */
00242 void GibEntity( gentity_t *self, int killer ) {
00243     gentity_t *ent;
00244     int i;
00245 
00246     //if this entity still has kamikaze
00247     if (self->s.eFlags & EF_KAMIKAZE) {
00248         // check if there is a kamikaze timer around for this owner
00249         for (i = 0; i < MAX_GENTITIES; i++) {
00250             ent = &g_entities[i];
00251             if (!ent->inuse)
00252                 continue;
00253             if (ent->activator != self)
00254                 continue;
00255             if (strcmp(ent->classname, "kamikaze timer"))
00256                 continue;
00257             G_FreeEntity(ent);
00258             break;
00259         }
00260     }
00261     G_AddEvent( self, EV_GIB_PLAYER, killer );
00262     self->takedamage = qfalse;
00263     self->s.eType = ET_INVISIBLE;
00264     self->r.contents = 0;
00265 }
00266 
00267 /*
00268 ==================
00269 body_die
00270 ==================
00271 */
00272 void body_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath ) {
00273     if ( self->health > GIB_HEALTH ) {
00274         return;
00275     }
00276     if ( !g_blood.integer ) {
00277         self->health = GIB_HEALTH+1;
00278         return;
00279     }
00280 
00281     GibEntity( self, 0 );
00282 }
00283 
00284 
00285 // these are just for logging, the client prints its own messages
00286 char    *modNames[] = {
00287     "MOD_UNKNOWN",
00288     "MOD_SHOTGUN",
00289     "MOD_GAUNTLET",
00290     "MOD_MACHINEGUN",
00291     "MOD_GRENADE",
00292     "MOD_GRENADE_SPLASH",
00293     "MOD_ROCKET",
00294     "MOD_ROCKET_SPLASH",
00295     "MOD_PLASMA",
00296     "MOD_PLASMA_SPLASH",
00297     "MOD_RAILGUN",
00298     "MOD_LIGHTNING",
00299     "MOD_BFG",
00300     "MOD_BFG_SPLASH",
00301     "MOD_WATER",
00302     "MOD_SLIME",
00303     "MOD_LAVA",
00304     "MOD_CRUSH",
00305     "MOD_TELEFRAG",
00306     "MOD_FALLING",
00307     "MOD_SUICIDE",
00308     "MOD_TARGET_LASER",
00309     "MOD_TRIGGER_HURT",
00310 #ifdef MISSIONPACK
00311     "MOD_NAIL",
00312     "MOD_CHAINGUN",
00313     "MOD_PROXIMITY_MINE",
00314     "MOD_KAMIKAZE",
00315     "MOD_JUICED",
00316 #endif
00317     "MOD_GRAPPLE"
00318 };
00319 
00320 #ifdef MISSIONPACK
00321 /*
00322 ==================
00323 Kamikaze_DeathActivate
00324 ==================
00325 */
00326 void Kamikaze_DeathActivate( gentity_t *ent ) {
00327     G_StartKamikaze(ent);
00328     G_FreeEntity(ent);
00329 }
00330 
00331 /*
00332 ==================
00333 Kamikaze_DeathTimer
00334 ==================
00335 */
00336 void Kamikaze_DeathTimer( gentity_t *self ) {
00337     gentity_t *ent;
00338 
00339     ent = G_Spawn();
00340     ent->classname = "kamikaze timer";
00341     VectorCopy(self->s.pos.trBase, ent->s.pos.trBase);
00342     ent->r.svFlags |= SVF_NOCLIENT;
00343     ent->think = Kamikaze_DeathActivate;
00344     ent->nextthink = level.time + 5 * 1000;
00345 
00346     ent->activator = self;
00347 }
00348 
00349 #endif
00350 
00351 /*
00352 ==================
00353 CheckAlmostCapture
00354 ==================
00355 */
00356 void CheckAlmostCapture( gentity_t *self, gentity_t *attacker ) {
00357     gentity_t   *ent;
00358     vec3_t      dir;
00359     char        *classname;
00360 
00361     // if this player was carrying a flag
00362     if ( self->client->ps.powerups[PW_REDFLAG] ||
00363         self->client->ps.powerups[PW_BLUEFLAG] ||
00364         self->client->ps.powerups[PW_NEUTRALFLAG] ) {
00365         // get the goal flag this player should have been going for
00366         if ( g_gametype.integer == GT_CTF ) {
00367             if ( self->client->sess.sessionTeam == TEAM_BLUE ) {
00368                 classname = "team_CTF_blueflag";
00369             }
00370             else {
00371                 classname = "team_CTF_redflag";
00372             }
00373         }
00374         else {
00375             if ( self->client->sess.sessionTeam == TEAM_BLUE ) {
00376                 classname = "team_CTF_redflag";
00377             }
00378             else {
00379                 classname = "team_CTF_blueflag";
00380             }
00381         }
00382         ent = NULL;
00383         do
00384         {
00385             ent = G_Find(ent, FOFS(classname), classname);
00386         } while (ent && (ent->flags & FL_DROPPED_ITEM));
00387         // if we found the destination flag and it's not picked up
00388         if (ent && !(ent->r.svFlags & SVF_NOCLIENT) ) {
00389             // if the player was *very* close
00390             VectorSubtract( self->client->ps.origin, ent->s.origin, dir );
00391             if ( VectorLength(dir) < 200 ) {
00392                 self->client->ps.persistant[PERS_PLAYEREVENTS] ^= PLAYEREVENT_HOLYSHIT;
00393                 if ( attacker->client ) {
00394                     attacker->client->ps.persistant[PERS_PLAYEREVENTS] ^= PLAYEREVENT_HOLYSHIT;
00395                 }
00396             }
00397         }
00398     }
00399 }
00400 
00401 /*
00402 ==================
00403 CheckAlmostScored
00404 ==================
00405 */
00406 void CheckAlmostScored( gentity_t *self, gentity_t *attacker ) {
00407     gentity_t   *ent;
00408     vec3_t      dir;
00409     char        *classname;
00410 
00411     // if the player was carrying cubes
00412     if ( self->client->ps.generic1 ) {
00413         if ( self->client->sess.sessionTeam == TEAM_BLUE ) {
00414             classname = "team_redobelisk";
00415         }
00416         else {
00417             classname = "team_blueobelisk";
00418         }
00419         ent = G_Find(NULL, FOFS(classname), classname);
00420         // if we found the destination obelisk
00421         if ( ent ) {
00422             // if the player was *very* close
00423             VectorSubtract( self->client->ps.origin, ent->s.origin, dir );
00424             if ( VectorLength(dir) < 200 ) {
00425                 self->client->ps.persistant[PERS_PLAYEREVENTS] ^= PLAYEREVENT_HOLYSHIT;
00426                 if ( attacker->client ) {
00427                     attacker->client->ps.persistant[PERS_PLAYEREVENTS] ^= PLAYEREVENT_HOLYSHIT;
00428                 }
00429             }
00430         }
00431     }
00432 }
00433 
00434 /*
00435 ==================
00436 player_die
00437 ==================
00438 */
00439 void player_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath ) {
00440     gentity_t   *ent;
00441     int         anim;
00442     int         contents;
00443     int         killer;
00444     int         i;
00445     char        *killerName, *obit;
00446 
00447     if ( self->client->ps.pm_type == PM_DEAD ) {
00448         return;
00449     }
00450 
00451     if ( level.intermissiontime ) {
00452         return;
00453     }
00454 
00455     // check for an almost capture
00456     CheckAlmostCapture( self, attacker );
00457     // check for a player that almost brought in cubes
00458     CheckAlmostScored( self, attacker );
00459 
00460     if (self->client && self->client->hook) {
00461         Weapon_HookFree(self->client->hook);
00462     }
00463 #ifdef MISSIONPACK
00464     if ((self->client->ps.eFlags & EF_TICKING) && self->activator) {
00465         self->client->ps.eFlags &= ~EF_TICKING;
00466         self->activator->think = G_FreeEntity;
00467         self->activator->nextthink = level.time;
00468     }
00469 #endif
00470     self->client->ps.pm_type = PM_DEAD;
00471 
00472     if ( attacker ) {
00473         killer = attacker->s.number;
00474         if ( attacker->client ) {
00475             killerName = attacker->client->pers.netname;
00476         } else {
00477             killerName = "<non-client>";
00478         }
00479     } else {
00480         killer = ENTITYNUM_WORLD;
00481         killerName = "<world>";
00482     }
00483 
00484     if ( killer < 0 || killer >= MAX_CLIENTS ) {
00485         killer = ENTITYNUM_WORLD;
00486         killerName = "<world>";
00487     }
00488 
00489     if ( meansOfDeath < 0 || meansOfDeath >= sizeof( modNames ) / sizeof( modNames[0] ) ) {
00490         obit = "<bad obituary>";
00491     } else {
00492         obit = modNames[ meansOfDeath ];
00493     }
00494 
00495     G_LogPrintf("Kill: %i %i %i: %s killed %s by %s\n", 
00496         killer, self->s.number, meansOfDeath, killerName, 
00497         self->client->pers.netname, obit );
00498 
00499     // broadcast the death event to everyone
00500     ent = G_TempEntity( self->r.currentOrigin, EV_OBITUARY );
00501     ent->s.eventParm = meansOfDeath;
00502     ent->s.otherEntityNum = self->s.number;
00503     ent->s.otherEntityNum2 = killer;
00504     ent->r.svFlags = SVF_BROADCAST; // send to everyone
00505 
00506     self->enemy = attacker;
00507 
00508     self->client->ps.persistant[PERS_KILLED]++;
00509 
00510     if (attacker && attacker->client) {
00511         attacker->client->lastkilled_client = self->s.number;
00512 
00513         if ( attacker == self || OnSameTeam (self, attacker ) ) {
00514             AddScore( attacker, self->r.currentOrigin, -1 );
00515         } else {
00516             AddScore( attacker, self->r.currentOrigin, 1 );
00517 
00518             if( meansOfDeath == MOD_GAUNTLET ) {
00519                 
00520                 // play humiliation on player
00521                 attacker->client->ps.persistant[PERS_GAUNTLET_FRAG_COUNT]++;
00522 
00523                 // add the sprite over the player's head
00524                 attacker->client->ps.eFlags &= ~(EF_AWARD_IMPRESSIVE | EF_AWARD_EXCELLENT | EF_AWARD_GAUNTLET | EF_AWARD_ASSIST | EF_AWARD_DEFEND | EF_AWARD_CAP );
00525                 attacker->client->ps.eFlags |= EF_AWARD_GAUNTLET;
00526                 attacker->client->rewardTime = level.time + REWARD_SPRITE_TIME;
00527 
00528                 // also play humiliation on target
00529                 self->client->ps.persistant[PERS_PLAYEREVENTS] ^= PLAYEREVENT_GAUNTLETREWARD;
00530             }
00531 
00532             // check for two kills in a short amount of time
00533             // if this is close enough to the last kill, give a reward sound
00534             if ( level.time - attacker->client->lastKillTime < CARNAGE_REWARD_TIME ) {
00535                 // play excellent on player
00536                 attacker->client->ps.persistant[PERS_EXCELLENT_COUNT]++;
00537 
00538                 // add the sprite over the player's head
00539                 attacker->client->ps.eFlags &= ~(EF_AWARD_IMPRESSIVE | EF_AWARD_EXCELLENT | EF_AWARD_GAUNTLET | EF_AWARD_ASSIST | EF_AWARD_DEFEND | EF_AWARD_CAP );
00540                 attacker->client->ps.eFlags |= EF_AWARD_EXCELLENT;
00541                 attacker->client->rewardTime = level.time + REWARD_SPRITE_TIME;
00542             }
00543             attacker->client->lastKillTime = level.time;
00544 
00545         }
00546     } else {
00547         AddScore( self, self->r.currentOrigin, -1 );
00548     }
00549 
00550     // Add team bonuses
00551     Team_FragBonuses(self, inflictor, attacker);
00552 
00553     // if I committed suicide, the flag does not fall, it returns.
00554     if (meansOfDeath == MOD_SUICIDE) {
00555         if ( self->client->ps.powerups[PW_NEUTRALFLAG] ) {      // only happens in One Flag CTF
00556             Team_ReturnFlag( TEAM_FREE );
00557             self->client->ps.powerups[PW_NEUTRALFLAG] = 0;
00558         }
00559         else if ( self->client->ps.powerups[PW_REDFLAG] ) {     // only happens in standard CTF
00560             Team_ReturnFlag( TEAM_RED );
00561             self->client->ps.powerups[PW_REDFLAG] = 0;
00562         }
00563         else if ( self->client->ps.powerups[PW_BLUEFLAG] ) {    // only happens in standard CTF
00564             Team_ReturnFlag( TEAM_BLUE );
00565             self->client->ps.powerups[PW_BLUEFLAG] = 0;
00566         }
00567     }
00568 
00569     // if client is in a nodrop area, don't drop anything (but return CTF flags!)
00570     contents = trap_PointContents( self->r.currentOrigin, -1 );
00571     if ( !( contents & CONTENTS_NODROP )) {
00572         TossClientItems( self );
00573     }
00574     else {
00575         if ( self->client->ps.powerups[PW_NEUTRALFLAG] ) {      // only happens in One Flag CTF
00576             Team_ReturnFlag( TEAM_FREE );
00577         }
00578         else if ( self->client->ps.powerups[PW_REDFLAG] ) {     // only happens in standard CTF
00579             Team_ReturnFlag( TEAM_RED );
00580         }
00581         else if ( self->client->ps.powerups[PW_BLUEFLAG] ) {    // only happens in standard CTF
00582             Team_ReturnFlag( TEAM_BLUE );
00583         }
00584     }
00585 #ifdef MISSIONPACK
00586     TossClientPersistantPowerups( self );
00587     if( g_gametype.integer == GT_HARVESTER ) {
00588         TossClientCubes( self );
00589     }
00590 #endif
00591 
00592     Cmd_Score_f( self );        // show scores
00593     // send updated scores to any clients that are following this one,
00594     // or they would get stale scoreboards
00595     for ( i = 0 ; i < level.maxclients ; i++ ) {
00596         gclient_t   *client;
00597 
00598         client = &level.clients[i];
00599         if ( client->pers.connected != CON_CONNECTED ) {
00600             continue;
00601         }
00602         if ( client->sess.sessionTeam != TEAM_SPECTATOR ) {
00603             continue;
00604         }
00605         if ( client->sess.spectatorClient == self->s.number ) {
00606             Cmd_Score_f( g_entities + i );
00607         }
00608     }
00609 
00610     self->takedamage = qtrue;   // can still be gibbed
00611 
00612     self->s.weapon = WP_NONE;
00613     self->s.powerups = 0;
00614     self->r.contents = CONTENTS_CORPSE;
00615 
00616     self->s.angles[0] = 0;
00617     self->s.angles[2] = 0;
00618     LookAtKiller (self, inflictor, attacker);
00619 
00620     VectorCopy( self->s.angles, self->client->ps.viewangles );
00621 
00622     self->s.loopSound = 0;
00623 
00624     self->r.maxs[2] = -8;
00625 
00626     // don't allow respawn until the death anim is done
00627     // g_forcerespawn may force spawning at some later time
00628     self->client->respawnTime = level.time + 1700;
00629 
00630     // remove powerups
00631     memset( self->client->ps.powerups, 0, sizeof(self->client->ps.powerups) );
00632 
00633     // never gib in a nodrop
00634     if ( (self->health <= GIB_HEALTH && !(contents & CONTENTS_NODROP) && g_blood.integer) || meansOfDeath == MOD_SUICIDE) {
00635         // gib death
00636         GibEntity( self, killer );
00637     } else {
00638         // normal death
00639         static int i;
00640 
00641         switch ( i ) {
00642         case 0:
00643             anim = BOTH_DEATH1;
00644             break;
00645         case 1:
00646             anim = BOTH_DEATH2;
00647             break;
00648         case 2:
00649         default:
00650             anim = BOTH_DEATH3;
00651             break;
00652         }
00653 
00654         // for the no-blood option, we need to prevent the health
00655         // from going to gib level
00656         if ( self->health <= GIB_HEALTH ) {
00657             self->health = GIB_HEALTH+1;
00658         }
00659 
00660         self->client->ps.legsAnim = 
00661             ( ( self->client->ps.legsAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim;
00662         self->client->ps.torsoAnim = 
00663             ( ( self->client->ps.torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim;
00664 
00665         G_AddEvent( self, EV_DEATH1 + i, killer );
00666 
00667         // the body can still be gibbed
00668         self->die = body_die;
00669 
00670         // globally cycle through the different death animations
00671         i = ( i + 1 ) % 3;
00672 
00673 #ifdef MISSIONPACK
00674         if (self->s.eFlags & EF_KAMIKAZE) {
00675             Kamikaze_DeathTimer( self );
00676         }
00677 #endif
00678     }
00679 
00680     trap_LinkEntity (self);
00681 
00682 }
00683 
00684 
00685 /*
00686 ================
00687 CheckArmor
00688 ================
00689 */
00690 int CheckArmor (gentity_t *ent, int damage, int dflags)
00691 {
00692     gclient_t   *client;
00693     int         save;
00694     int         count;
00695 
00696     if (!damage)
00697         return 0;
00698 
00699     client = ent->client;
00700 
00701     if (!client)
00702         return 0;
00703 
00704     if (dflags & DAMAGE_NO_ARMOR)
00705         return 0;
00706 
00707     // armor
00708     count = client->ps.stats[STAT_ARMOR];
00709     save = ceil( damage * ARMOR_PROTECTION );
00710     if (save >= count)
00711         save = count;
00712 
00713     if (!save)
00714         return 0;
00715 
00716     client->ps.stats[STAT_ARMOR] -= save;
00717 
00718     return save;
00719 }
00720 
00721 /*
00722 ================
00723 RaySphereIntersections
00724 ================
00725 */
00726 int RaySphereIntersections( vec3_t origin, float radius, vec3_t point, vec3_t dir, vec3_t intersections[2] ) {
00727     float b, c, d, t;
00728 
00729     //  | origin - (point + t * dir) | = radius
00730     //  a = dir[0]^2 + dir[1]^2 + dir[2]^2;
00731     //  b = 2 * (dir[0] * (point[0] - origin[0]) + dir[1] * (point[1] - origin[1]) + dir[2] * (point[2] - origin[2]));
00732     //  c = (point[0] - origin[0])^2 + (point[1] - origin[1])^2 + (point[2] - origin[2])^2 - radius^2;
00733 
00734     // normalize dir so a = 1
00735     VectorNormalize(dir);
00736     b = 2 * (dir[0] * (point[0] - origin[0]) + dir[1] * (point[1] - origin[1]) + dir[2] * (point[2] - origin[2]));
00737     c = (point[0] - origin[0]) * (point[0] - origin[0]) +
00738         (point[1] - origin[1]) * (point[1] - origin[1]) +
00739         (point[2] - origin[2]) * (point[2] - origin[2]) -
00740         radius * radius;
00741 
00742     d = b * b - 4 * c;
00743     if (d > 0) {
00744         t = (- b + sqrt(d)) / 2;
00745         VectorMA(point, t, dir, intersections[0]);
00746         t = (- b - sqrt(d)) / 2;
00747         VectorMA(point, t, dir, intersections[1]);
00748         return 2;
00749     }
00750     else if (d == 0) {
00751         t = (- b ) / 2;
00752         VectorMA(point, t, dir, intersections[0]);
00753         return 1;
00754     }
00755     return 0;
00756 }
00757 
00758 #ifdef MISSIONPACK
00759 /*
00760 ================
00761 G_InvulnerabilityEffect
00762 ================
00763 */
00764 int G_InvulnerabilityEffect( gentity_t *targ, vec3_t dir, vec3_t point, vec3_t impactpoint, vec3_t bouncedir ) {
00765     gentity_t   *impact;
00766     vec3_t      intersections[2], vec;
00767     int         n;
00768 
00769     if ( !targ->client ) {
00770         return qfalse;
00771     }
00772     VectorCopy(dir, vec);
00773     VectorInverse(vec);
00774     // sphere model radius = 42 units
00775     n = RaySphereIntersections( targ->client->ps.origin, 42, point, vec, intersections);
00776     if (n > 0) {
00777         impact = G_TempEntity( targ->client->ps.origin, EV_INVUL_IMPACT );
00778         VectorSubtract(intersections[0], targ->client->ps.origin, vec);
00779         vectoangles(vec, impact->s.angles);
00780         impact->s.angles[0] += 90;
00781         if (impact->s.angles[0] > 360)
00782             impact->s.angles[0] -= 360;
00783         if ( impactpoint ) {
00784             VectorCopy( intersections[0], impactpoint );
00785         }
00786         if ( bouncedir ) {
00787             VectorCopy( vec, bouncedir );
00788             VectorNormalize( bouncedir );
00789         }
00790         return qtrue;
00791     }
00792     else {
00793         return qfalse;
00794     }
00795 }
00796 #endif
00797 /*
00798 ============
00799 T_Damage
00800 
00801 targ        entity that is being damaged
00802 inflictor   entity that is causing the damage
00803 attacker    entity that caused the inflictor to damage targ
00804     example: targ=monster, inflictor=rocket, attacker=player
00805 
00806 dir         direction of the attack for knockback
00807 point       point at which the damage is being inflicted, used for headshots
00808 damage      amount of damage being inflicted
00809 knockback   force to be applied against targ as a result of the damage
00810 
00811 inflictor, attacker, dir, and point can be NULL for environmental effects
00812 
00813 dflags      these flags are used to control how T_Damage works
00814     DAMAGE_RADIUS           damage was indirect (from a nearby explosion)
00815     DAMAGE_NO_ARMOR         armor does not protect from this damage
00816     DAMAGE_NO_KNOCKBACK     do not affect velocity, just view angles
00817     DAMAGE_NO_PROTECTION    kills godmode, armor, everything
00818 ============
00819 */
00820 
00821 void G_Damage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker,
00822                vec3_t dir, vec3_t point, int damage, int dflags, int mod ) {
00823     gclient_t   *client;
00824     int         take;
00825     int         save;
00826     int         asave;
00827     int         knockback;
00828     int         max;
00829 #ifdef MISSIONPACK
00830     vec3_t      bouncedir, impactpoint;
00831 #endif
00832 
00833     if (!targ->takedamage) {
00834         return;
00835     }
00836 
00837     // the intermission has allready been qualified for, so don't
00838     // allow any extra scoring
00839     if ( level.intermissionQueued ) {
00840         return;
00841     }
00842 #ifdef MISSIONPACK
00843     if ( targ->client && mod != MOD_JUICED) {
00844         if ( targ->client->invulnerabilityTime > level.time) {
00845             if ( dir && point ) {
00846                 G_InvulnerabilityEffect( targ, dir, point, impactpoint, bouncedir );
00847             }
00848             return;
00849         }
00850     }
00851 #endif
00852     if ( !inflictor ) {
00853         inflictor = &g_entities[ENTITYNUM_WORLD];
00854     }
00855     if ( !attacker ) {
00856         attacker = &g_entities[ENTITYNUM_WORLD];
00857     }
00858 
00859     // shootable doors / buttons don't actually have any health
00860     if ( targ->s.eType == ET_MOVER ) {
00861         if ( targ->use && targ->moverState == MOVER_POS1 ) {
00862             targ->use( targ, inflictor, attacker );
00863         }
00864         return;
00865     }
00866 #ifdef MISSIONPACK
00867     if( g_gametype.integer == GT_OBELISK && CheckObeliskAttack( targ, attacker ) ) {
00868         return;
00869     }
00870 #endif
00871     // reduce damage by the attacker's handicap value
00872     // unless they are rocket jumping
00873     if ( attacker->client && attacker != targ ) {
00874         max = attacker->client->ps.stats[STAT_MAX_HEALTH];
00875 #ifdef MISSIONPACK
00876         if( bg_itemlist[attacker->client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_GUARD ) {
00877             max /= 2;
00878         }
00879 #endif
00880         damage = damage * max / 100;
00881     }
00882 
00883     client = targ->client;
00884 
00885     if ( client ) {
00886         if ( client->noclip ) {
00887             return;
00888         }
00889     }
00890 
00891     if ( !dir ) {
00892         dflags |= DAMAGE_NO_KNOCKBACK;
00893     } else {
00894         VectorNormalize(dir);
00895     }
00896 
00897     knockback = damage;
00898     if ( knockback > 200 ) {
00899         knockback = 200;
00900     }
00901     if ( targ->flags & FL_NO_KNOCKBACK ) {
00902         knockback = 0;
00903     }
00904     if ( dflags & DAMAGE_NO_KNOCKBACK ) {
00905         knockback = 0;
00906     }
00907 
00908     // figure momentum add, even if the damage won't be taken
00909     if ( knockback && targ->client ) {
00910         vec3_t  kvel;
00911         float   mass;
00912 
00913         mass = 200;
00914 
00915         VectorScale (dir, g_knockback.value * (float)knockback / mass, kvel);
00916         VectorAdd (targ->client->ps.velocity, kvel, targ->client->ps.velocity);
00917 
00918         // set the timer so that the other client can't cancel
00919         // out the movement immediately
00920         if ( !targ->client->ps.pm_time ) {
00921             int     t;
00922 
00923             t = knockback * 2;
00924             if ( t < 50 ) {
00925                 t = 50;
00926             }
00927             if ( t > 200 ) {
00928                 t = 200;
00929             }
00930             targ->client->ps.pm_time = t;
00931             targ->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
00932         }
00933     }
00934 
00935     // check for completely getting out of the damage
00936     if ( !(dflags & DAMAGE_NO_PROTECTION) ) {
00937 
00938         // if TF_NO_FRIENDLY_FIRE is set, don't do damage to the target
00939         // if the attacker was on the same team
00940 #ifdef MISSIONPACK
00941         if ( mod != MOD_JUICED && targ != attacker && !(dflags & DAMAGE_NO_TEAM_PROTECTION) && OnSameTeam (targ, attacker)  ) {
00942 #else   
00943         if ( targ != attacker && OnSameTeam (targ, attacker)  ) {
00944 #endif
00945             if ( !g_friendlyFire.integer ) {
00946                 return;
00947             }
00948         }
00949 #ifdef MISSIONPACK
00950         if (mod == MOD_PROXIMITY_MINE) {
00951             if (inflictor && inflictor->parent && OnSameTeam(targ, inflictor->parent)) {
00952                 return;
00953             }
00954             if (targ == attacker) {
00955                 return;
00956             }
00957         }
00958 #endif
00959 
00960         // check for godmode
00961         if ( targ->flags & FL_GODMODE ) {
00962             return;
00963         }
00964     }
00965 
00966     // battlesuit protects from all radius damage (but takes knockback)
00967     // and protects 50% against all damage
00968     if ( client && client->ps.powerups[PW_BATTLESUIT] ) {
00969         G_AddEvent( targ, EV_POWERUP_BATTLESUIT, 0 );
00970         if ( ( dflags & DAMAGE_RADIUS ) || ( mod == MOD_FALLING ) ) {
00971             return;
00972         }
00973         damage *= 0.5;
00974     }
00975 
00976     // add to the attacker's hit counter (if the target isn't a general entity like a prox mine)
00977     if ( attacker->client && targ != attacker && targ->health > 0
00978             && targ->s.eType != ET_MISSILE
00979             && targ->s.eType != ET_GENERAL) {
00980         if ( OnSameTeam( targ, attacker ) ) {
00981             attacker->client->ps.persistant[PERS_HITS]--;
00982         } else {
00983             attacker->client->ps.persistant[PERS_HITS]++;
00984         }
00985         attacker->client->ps.persistant[PERS_ATTACKEE_ARMOR] = (targ->health<<8)|(client->ps.stats[STAT_ARMOR]);
00986     }
00987 
00988     // always give half damage if hurting self
00989     // calculated after knockback, so rocket jumping works
00990     if ( targ == attacker) {
00991         damage *= 0.5;
00992     }
00993 
00994     if ( damage < 1 ) {
00995         damage = 1;
00996     }
00997     take = damage;
00998     save = 0;
00999 
01000     // save some from armor
01001     asave = CheckArmor (targ, take, dflags);
01002     take -= asave;
01003 
01004     if ( g_debugDamage.integer ) {
01005         G_Printf( "%i: client:%i health:%i damage:%i armor:%i\n", level.time, targ->s.number,
01006             targ->health, take, asave );
01007     }
01008 
01009     // add to the damage inflicted on a player this frame
01010     // the total will be turned into screen blends and view angle kicks
01011     // at the end of the frame
01012     if ( client ) {
01013         if ( attacker ) {
01014             client->ps.persistant[PERS_ATTACKER] = attacker->s.number;
01015         } else {
01016             client->ps.persistant[PERS_ATTACKER] = ENTITYNUM_WORLD;
01017         }
01018         client->damage_armor += asave;
01019         client->damage_blood += take;
01020         client->damage_knockback += knockback;
01021         if ( dir ) {
01022             VectorCopy ( dir, client->damage_from );
01023             client->damage_fromWorld = qfalse;
01024         } else {
01025             VectorCopy ( targ->r.currentOrigin, client->damage_from );
01026             client->damage_fromWorld = qtrue;
01027         }
01028     }
01029 
01030     // See if it's the player hurting the emeny flag carrier
01031 #ifdef MISSIONPACK
01032     if( g_gametype.integer == GT_CTF || g_gametype.integer == GT_1FCTF ) {
01033 #else   
01034     if( g_gametype.integer == GT_CTF) {
01035 #endif
01036         Team_CheckHurtCarrier(targ, attacker);
01037     }
01038 
01039     if (targ->client) {
01040         // set the last client who damaged the target
01041         targ->client->lasthurt_client = attacker->s.number;
01042         targ->client->lasthurt_mod = mod;
01043     }
01044 
01045     // do the damage
01046     if (take) {
01047         targ->health = targ->health - take;
01048         if ( targ->client ) {
01049             targ->client->ps.stats[STAT_HEALTH] = targ->health;
01050         }
01051             
01052         if ( targ->health <= 0 ) {
01053             if ( client )
01054                 targ->flags |= FL_NO_KNOCKBACK;
01055 
01056             if (targ->health < -999)
01057                 targ->health = -999;
01058 
01059             targ->enemy = attacker;
01060             targ->die (targ, inflictor, attacker, take, mod);
01061             return;
01062         } else if ( targ->pain ) {
01063             targ->pain (targ, attacker, take);
01064         }
01065     }
01066 
01067 }
01068 
01069 
01070 /*
01071 ============
01072 CanDamage
01073 
01074 Returns qtrue if the inflictor can directly damage the target.  Used for
01075 explosions and melee attacks.
01076 ============
01077 */
01078 qboolean CanDamage (gentity_t *targ, vec3_t origin) {
01079     vec3_t  dest;
01080     trace_t tr;
01081     vec3_t  midpoint;
01082 
01083     // use the midpoint of the bounds instead of the origin, because
01084     // bmodels may have their origin is 0,0,0
01085     VectorAdd (targ->r.absmin, targ->r.absmax, midpoint);
01086     VectorScale (midpoint, 0.5, midpoint);
01087 
01088     VectorCopy (midpoint, dest);
01089     trap_Trace ( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID);
01090     if (tr.fraction == 1.0 || tr.entityNum == targ->s.number)
01091         return qtrue;
01092 
01093     // this should probably check in the plane of projection, 
01094     // rather than in world coordinate, and also include Z
01095     VectorCopy (midpoint, dest);
01096     dest[0] += 15.0;
01097     dest[1] += 15.0;
01098     trap_Trace ( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID);
01099     if (tr.fraction == 1.0)
01100         return qtrue;
01101 
01102     VectorCopy (midpoint, dest);
01103     dest[0] += 15.0;
01104     dest[1] -= 15.0;
01105     trap_Trace ( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID);
01106     if (tr.fraction == 1.0)
01107         return qtrue;
01108 
01109     VectorCopy (midpoint, dest);
01110     dest[0] -= 15.0;
01111     dest[1] += 15.0;
01112     trap_Trace ( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID);
01113     if (tr.fraction == 1.0)
01114         return qtrue;
01115 
01116     VectorCopy (midpoint, dest);
01117     dest[0] -= 15.0;
01118     dest[1] -= 15.0;
01119     trap_Trace ( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID);
01120     if (tr.fraction == 1.0)
01121         return qtrue;
01122 
01123 
01124     return qfalse;
01125 }
01126 
01127 
01128 /*
01129 ============
01130 G_RadiusDamage
01131 ============
01132 */
01133 qboolean G_RadiusDamage ( vec3_t origin, gentity_t *attacker, float damage, float radius,
01134                      gentity_t *ignore, int mod) {
01135     float       points, dist;
01136     gentity_t   *ent;
01137     int         entityList[MAX_GENTITIES];
01138     int         numListedEntities;
01139     vec3_t      mins, maxs;
01140     vec3_t      v;
01141     vec3_t      dir;
01142     int         i, e;
01143     qboolean    hitClient = qfalse;
01144 
01145     if ( radius < 1 ) {
01146         radius = 1;
01147     }
01148 
01149     for ( i = 0 ; i < 3 ; i++ ) {
01150         mins[i] = origin[i] - radius;
01151         maxs[i] = origin[i] + radius;
01152     }
01153 
01154     numListedEntities = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
01155 
01156     for ( e = 0 ; e < numListedEntities ; e++ ) {
01157         ent = &g_entities[entityList[ e ]];
01158 
01159         if (ent == ignore)
01160             continue;
01161         if (!ent->takedamage)
01162             continue;
01163 
01164         // find the distance from the edge of the bounding box
01165         for ( i = 0 ; i < 3 ; i++ ) {
01166             if ( origin[i] < ent->r.absmin[i] ) {
01167                 v[i] = ent->r.absmin[i] - origin[i];
01168             } else if ( origin[i] > ent->r.absmax[i] ) {
01169                 v[i] = origin[i] - ent->r.absmax[i];
01170             } else {
01171                 v[i] = 0;
01172             }
01173         }
01174 
01175         dist = VectorLength( v );
01176         if ( dist >= radius ) {
01177             continue;
01178         }
01179 
01180         points = damage * ( 1.0 - dist / radius );
01181 
01182         if( CanDamage (ent, origin) ) {
01183             if( LogAccuracyHit( ent, attacker ) ) {
01184                 hitClient = qtrue;
01185             }
01186             VectorSubtract (ent->r.currentOrigin, origin, dir);
01187             // push the center of mass higher than the origin so players
01188             // get knocked into the air more
01189             dir[2] += 24;
01190             G_Damage (ent, NULL, attacker, dir, origin, (int)points, DAMAGE_RADIUS, mod);
01191         }
01192     }
01193 
01194     return hitClient;
01195 }

Generated on Thu Aug 25 12:37:31 2005 for Quake III Arena by  doxygen 1.3.9.1