codemp/game/g_items.c

Go to the documentation of this file.
00001 // Copyright (C) 1999-2000 Id Software, Inc.
00002 //
00003 #include "g_local.h"
00004 #include "../ghoul2/G2.h"
00005 #include "q_shared.h"
00006 
00007 /*
00008 
00009   Items are any object that a player can touch to gain some effect.
00010 
00011   Pickup will return the number of seconds until they should respawn.
00012 
00013   all items should pop when dropped in lava or slime
00014 
00015   Respawnable items don't actually go away when picked up, they are
00016   just made invisible and untouchable.  This allows them to ride
00017   movers and respawn apropriately.
00018 */
00019 
00020 
00021 #define RESPAWN_ARMOR           20
00022 #define RESPAWN_TEAM_WEAPON     30
00023 #define RESPAWN_HEALTH          30
00024 #define RESPAWN_AMMO            40
00025 #define RESPAWN_HOLDABLE        60
00026 #define RESPAWN_MEGAHEALTH      120
00027 #define RESPAWN_POWERUP         120
00028 
00029 // Item Spawn flags
00030 #define ITMSF_SUSPEND           1
00031 #define ITMSF_NOPLAYER          2
00032 #define ITMSF_ALLOWNPC          4
00033 #define ITMSF_NOTSOLID          8
00034 #define ITMSF_VERTICAL          16
00035 #define ITMSF_INVISIBLE         32
00036 
00037 extern gentity_t *droppedRedFlag;
00038 extern gentity_t *droppedBlueFlag;
00039 
00040 
00041 //======================================================================
00042 #define MAX_MEDPACK_HEAL_AMOUNT         25
00043 #define MAX_MEDPACK_BIG_HEAL_AMOUNT     50
00044 #define MAX_SENTRY_DISTANCE                     256
00045 
00046 // For more than four players, adjust the respawn times, up to 1/4.
00047 int adjustRespawnTime(float preRespawnTime, int itemType, int itemTag)
00048 {
00049         float respawnTime = preRespawnTime;
00050 
00051         if (itemType == IT_WEAPON)
00052         {
00053                 if (itemTag == WP_THERMAL ||
00054                         itemTag == WP_TRIP_MINE ||
00055                         itemTag == WP_DET_PACK)
00056                 { //special case for these, use ammo respawn rate
00057                         respawnTime = RESPAWN_AMMO;
00058                 }
00059         }
00060 
00061         if (!g_adaptRespawn.integer)
00062         {
00063                 return((int)respawnTime);
00064         }
00065 
00066         if (level.numPlayingClients > 4)
00067         {       // Start scaling the respawn times.
00068                 if (level.numPlayingClients > 32)
00069                 {       // 1/4 time minimum.
00070                         respawnTime *= 0.25;
00071                 }
00072                 else if (level.numPlayingClients > 12)
00073                 {       // From 12-32, scale from 0.5 to 0.25;
00074                         respawnTime *= 20.0 / (float)(level.numPlayingClients + 8);
00075                 }
00076                 else 
00077                 {       // From 4-12, scale from 1.0 to 0.5;
00078                         respawnTime *= 8.0 / (float)(level.numPlayingClients + 4);
00079                 }
00080         }
00081 
00082         if (respawnTime < 1.0)
00083         {       // No matter what, don't go lower than 1 second, or the pickups become very noisy!
00084                 respawnTime = 1.0;
00085         }
00086 
00087         return ((int)respawnTime);
00088 }
00089 
00090 
00091 #define SHIELD_HEALTH                           250
00092 #define SHIELD_HEALTH_DEC                       10              // 25 seconds   
00093 #define MAX_SHIELD_HEIGHT                       254
00094 #define MAX_SHIELD_HALFWIDTH            255
00095 #define SHIELD_HALFTHICKNESS            4
00096 #define SHIELD_PLACEDIST                        64
00097 
00098 #define SHIELD_SIEGE_HEALTH                     2000
00099 #define SHIELD_SIEGE_HEALTH_DEC         (SHIELD_SIEGE_HEALTH/25)        // still 25 seconds.
00100 
00101 static qhandle_t        shieldLoopSound=0;
00102 static qhandle_t        shieldAttachSound=0;
00103 static qhandle_t        shieldActivateSound=0;
00104 static qhandle_t        shieldDeactivateSound=0;
00105 static qhandle_t        shieldDamageSound=0;
00106 
00107 
00108 void ShieldRemove(gentity_t *self)
00109 {
00110         self->think = G_FreeEntity;
00111         self->nextthink = level.time + 100;
00112 
00113         // Play kill sound...
00114         G_AddEvent(self, EV_GENERAL_SOUND, shieldDeactivateSound);
00115         self->s.loopSound = 0;
00116         self->s.loopIsSoundset = qfalse;
00117 
00118         return;
00119 }
00120 
00121 
00122 // Count down the health of the shield.
00123 void ShieldThink(gentity_t *self)
00124 {
00125         self->s.trickedentindex = 0;
00126 
00127         if ( g_gametype.integer == GT_SIEGE )
00128         {
00129                 self->health -= SHIELD_SIEGE_HEALTH_DEC;
00130         }
00131         else
00132         {
00133                 self->health -= SHIELD_HEALTH_DEC;
00134         }
00135         self->nextthink = level.time + 1000;
00136         if (self->health <= 0)
00137         {
00138                 ShieldRemove(self);
00139         }
00140         return;
00141 }
00142 
00143 
00144 // The shield was damaged to below zero health.
00145 void ShieldDie(gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod)
00146 {
00147         // Play damaging sound...
00148         G_AddEvent(self, EV_GENERAL_SOUND, shieldDamageSound);
00149 
00150         ShieldRemove(self);
00151 }
00152 
00153 
00154 // The shield had damage done to it.  Make it flicker.
00155 void ShieldPain(gentity_t *self, gentity_t *attacker, int damage)
00156 {
00157         // Set the itemplaceholder flag to indicate the the shield drawing that the shield pain should be drawn.
00158         self->think = ShieldThink;
00159         self->nextthink = level.time + 400;
00160 
00161         // Play damaging sound...
00162         G_AddEvent(self, EV_GENERAL_SOUND, shieldDamageSound);
00163 
00164         self->s.trickedentindex = 1;
00165 
00166         return;
00167 }
00168 
00169 
00170 // Try to turn the shield back on after a delay.
00171 void ShieldGoSolid(gentity_t *self)
00172 {
00173         trace_t         tr;
00174 
00175         // see if we're valid
00176         self->health--;
00177         if (self->health <= 0)
00178         {
00179                 ShieldRemove(self);
00180                 return;
00181         }
00182         
00183         trap_Trace (&tr, self->r.currentOrigin, self->r.mins, self->r.maxs, self->r.currentOrigin, self->s.number, CONTENTS_BODY );
00184         if(tr.startsolid)
00185         {       // gah, we can't activate yet
00186                 self->nextthink = level.time + 200;
00187                 self->think = ShieldGoSolid;
00188                 trap_LinkEntity(self);
00189         }
00190         else
00191         { // get hard... huh-huh...
00192                 self->s.eFlags &= ~EF_NODRAW;
00193 
00194                 self->r.contents = CONTENTS_SOLID;
00195                 self->nextthink = level.time + 1000;
00196                 self->think = ShieldThink;
00197                 self->takedamage = qtrue;
00198                 trap_LinkEntity(self);
00199 
00200                 // Play raising sound...
00201                 G_AddEvent(self, EV_GENERAL_SOUND, shieldActivateSound);
00202                 self->s.loopSound = shieldLoopSound;
00203                 self->s.loopIsSoundset = qfalse;
00204         }
00205 
00206         return;
00207 }
00208 
00209 
00210 // Turn the shield off to allow a friend to pass through.
00211 void ShieldGoNotSolid(gentity_t *self)
00212 {
00213         // make the shield non-solid very briefly
00214         self->r.contents = 0;
00215         self->s.eFlags |= EF_NODRAW;
00216         // nextthink needs to have a large enough interval to avoid excess accumulation of Activate messages
00217         self->nextthink = level.time + 200;
00218         self->think = ShieldGoSolid;
00219         self->takedamage = qfalse;
00220         trap_LinkEntity(self);
00221 
00222         // Play kill sound...
00223         G_AddEvent(self, EV_GENERAL_SOUND, shieldDeactivateSound);
00224         self->s.loopSound = 0;
00225         self->s.loopIsSoundset = qfalse;
00226 }
00227 
00228 
00229 // Somebody (a player) has touched the shield.  See if it is a "friend".
00230 void ShieldTouch(gentity_t *self, gentity_t *other, trace_t *trace)
00231 {
00232         if (g_gametype.integer >= GT_TEAM)
00233         { // let teammates through
00234                 // compare the parent's team to the "other's" team
00235                 if (self->parent && ( self->parent->client) && (other->client))
00236                 {
00237                         if (OnSameTeam(self->parent, other))
00238                         {
00239                                 ShieldGoNotSolid(self);
00240                         }
00241                 }
00242         }
00243         else
00244         {//let the person who dropped the shield through
00245                 if (self->parent && self->parent->s.number == other->s.number)
00246                 {
00247                         ShieldGoNotSolid(self);
00248                 }
00249         }
00250 }
00251 
00252 
00253 // After a short delay, create the shield by expanding in all directions.
00254 void CreateShield(gentity_t *ent)
00255 {
00256         trace_t         tr;
00257         vec3_t          mins, maxs, end, posTraceEnd, negTraceEnd, start;
00258         int                     height, posWidth, negWidth, halfWidth = 0;
00259         qboolean        xaxis;
00260         int                     paramData = 0;
00261         static int      shieldID;
00262 
00263         // trace upward to find height of shield
00264         VectorCopy(ent->r.currentOrigin, end);
00265         end[2] += MAX_SHIELD_HEIGHT;
00266         trap_Trace (&tr, ent->r.currentOrigin, NULL, NULL, end, ent->s.number, MASK_SHOT );
00267         height = (int)(MAX_SHIELD_HEIGHT * tr.fraction);
00268 
00269         // use angles to find the proper axis along which to align the shield
00270         VectorSet(mins, -SHIELD_HALFTHICKNESS, -SHIELD_HALFTHICKNESS, 0);
00271         VectorSet(maxs, SHIELD_HALFTHICKNESS, SHIELD_HALFTHICKNESS, height);
00272         VectorCopy(ent->r.currentOrigin, posTraceEnd);
00273         VectorCopy(ent->r.currentOrigin, negTraceEnd);
00274 
00275         if ((int)(ent->s.angles[YAW]) == 0) // shield runs along y-axis
00276         {
00277                 posTraceEnd[1]+=MAX_SHIELD_HALFWIDTH;
00278                 negTraceEnd[1]-=MAX_SHIELD_HALFWIDTH;
00279                 xaxis = qfalse;
00280         }
00281         else  // shield runs along x-axis
00282         {
00283                 posTraceEnd[0]+=MAX_SHIELD_HALFWIDTH;
00284                 negTraceEnd[0]-=MAX_SHIELD_HALFWIDTH;
00285                 xaxis = qtrue;
00286         }
00287 
00288         // trace horizontally to find extend of shield
00289         // positive trace
00290         VectorCopy(ent->r.currentOrigin, start);
00291         start[2] += (height>>1);
00292         trap_Trace (&tr, start, 0, 0, posTraceEnd, ent->s.number, MASK_SHOT );
00293         posWidth = MAX_SHIELD_HALFWIDTH * tr.fraction;
00294         // negative trace
00295         trap_Trace (&tr, start, 0, 0, negTraceEnd, ent->s.number, MASK_SHOT );
00296         negWidth = MAX_SHIELD_HALFWIDTH * tr.fraction;
00297 
00298         // kef -- monkey with dimensions and place origin in center
00299         halfWidth = (posWidth + negWidth)>>1;
00300         if (xaxis)
00301         {
00302                 ent->r.currentOrigin[0] = ent->r.currentOrigin[0] - negWidth + halfWidth;
00303         }
00304         else
00305         {
00306                 ent->r.currentOrigin[1] = ent->r.currentOrigin[1] - negWidth + halfWidth;
00307         }
00308         ent->r.currentOrigin[2] += (height>>1);
00309 
00310         // set entity's mins and maxs to new values, make it solid, and link it
00311         if (xaxis)
00312         {
00313                 VectorSet(ent->r.mins, -halfWidth, -SHIELD_HALFTHICKNESS, -(height>>1));
00314                 VectorSet(ent->r.maxs, halfWidth, SHIELD_HALFTHICKNESS, height>>1);
00315         }
00316         else
00317         {
00318                 VectorSet(ent->r.mins, -SHIELD_HALFTHICKNESS, -halfWidth, -(height>>1));
00319                 VectorSet(ent->r.maxs, SHIELD_HALFTHICKNESS, halfWidth, height);
00320         }
00321         ent->clipmask = MASK_SHOT;
00322 
00323         // Information for shield rendering.
00324 
00325 //      xaxis - 1 bit
00326 //      height - 0-254 8 bits
00327 //      posWidth - 0-255 8 bits
00328 //  negWidth - 0 - 255 8 bits
00329 
00330         paramData = (xaxis << 24) | (height << 16) | (posWidth << 8) | (negWidth);
00331         ent->s.time2 = paramData;
00332 
00333         if ( g_gametype.integer == GT_SIEGE )
00334         {
00335                 ent->health = ceil((float)(SHIELD_SIEGE_HEALTH*1));
00336         }
00337         else
00338         {
00339                 ent->health = ceil((float)(SHIELD_HEALTH*1));
00340         }
00341 
00342         ent->s.time = ent->health;//???
00343         ent->pain = ShieldPain;
00344         ent->die = ShieldDie;
00345         ent->touch = ShieldTouch;
00346 
00347         // see if we're valid
00348         trap_Trace (&tr, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, ent->r.currentOrigin, ent->s.number, CONTENTS_BODY ); 
00349 
00350         if (tr.startsolid)
00351         {       // Something in the way!
00352                 // make the shield non-solid very briefly
00353                 ent->r.contents = 0;
00354                 ent->s.eFlags |= EF_NODRAW;
00355                 // nextthink needs to have a large enough interval to avoid excess accumulation of Activate messages
00356                 ent->nextthink = level.time + 200;
00357                 ent->think = ShieldGoSolid;
00358                 ent->takedamage = qfalse;
00359                 trap_LinkEntity(ent);
00360         }
00361         else
00362         {       // Get solid.
00363                 ent->r.contents = CONTENTS_PLAYERCLIP|CONTENTS_SHOTCLIP;//CONTENTS_SOLID;
00364 
00365                 ent->nextthink = level.time;
00366                 ent->think = ShieldThink;
00367 
00368                 ent->takedamage = qtrue;
00369                 trap_LinkEntity(ent);
00370 
00371                 // Play raising sound...
00372                 G_AddEvent(ent, EV_GENERAL_SOUND, shieldActivateSound);
00373                 ent->s.loopSound = shieldLoopSound;
00374                 ent->s.loopIsSoundset = qfalse;
00375         }
00376 
00377         ShieldGoSolid(ent);
00378 
00379         return;
00380 }
00381 
00382 qboolean PlaceShield(gentity_t *playerent)
00383 {
00384         static const gitem_t *shieldItem = NULL;
00385         gentity_t       *shield = NULL;
00386         trace_t         tr;
00387         vec3_t          fwd, pos, dest, mins = {-4,-4, 0}, maxs = {4,4,4};
00388 
00389         if (shieldAttachSound==0)
00390         {
00391                 shieldLoopSound = G_SoundIndex("sound/movers/doors/forcefield_lp.wav");
00392                 shieldAttachSound = G_SoundIndex("sound/weapons/detpack/stick.wav");
00393                 shieldActivateSound = G_SoundIndex("sound/movers/doors/forcefield_on.wav");
00394                 shieldDeactivateSound = G_SoundIndex("sound/movers/doors/forcefield_off.wav");
00395                 shieldDamageSound = G_SoundIndex("sound/effects/bumpfield.wav");
00396                 shieldItem = BG_FindItemForHoldable(HI_SHIELD);
00397         }
00398 
00399         // can we place this in front of us?
00400         AngleVectors (playerent->client->ps.viewangles, fwd, NULL, NULL);
00401         fwd[2] = 0;
00402         VectorMA(playerent->client->ps.origin, SHIELD_PLACEDIST, fwd, dest);
00403         trap_Trace (&tr, playerent->client->ps.origin, mins, maxs, dest, playerent->s.number, MASK_SHOT );
00404         if (tr.fraction > 0.9)
00405         {//room in front
00406                 VectorCopy(tr.endpos, pos);
00407                 // drop to floor
00408                 VectorSet( dest, pos[0], pos[1], pos[2] - 4096 );
00409                 trap_Trace( &tr, pos, mins, maxs, dest, playerent->s.number, MASK_SOLID );
00410                 if ( !tr.startsolid && !tr.allsolid )
00411                 {
00412                         // got enough room so place the portable shield
00413                         shield = G_Spawn();
00414 
00415                         // Figure out what direction the shield is facing.
00416                         if (fabs(fwd[0]) > fabs(fwd[1]))
00417                         {       // shield is north/south, facing east.
00418                                 shield->s.angles[YAW] = 0;
00419                         }
00420                         else
00421                         {       // shield is along the east/west axis, facing north
00422                                 shield->s.angles[YAW] = 90;
00423                         }
00424                         shield->think = CreateShield;
00425                         shield->nextthink = level.time + 500;   // power up after .5 seconds
00426                         shield->parent = playerent;
00427 
00428                         // Set team number.
00429                         shield->s.otherEntityNum2 = playerent->client->sess.sessionTeam;
00430 
00431                         shield->s.eType = ET_SPECIAL;
00432                         shield->s.modelindex =  HI_SHIELD;      // this'll be used in CG_Useable() for rendering.
00433                         shield->classname = shieldItem->classname;
00434 
00435                         shield->r.contents = CONTENTS_TRIGGER;
00436 
00437                         shield->touch = 0;
00438                         // using an item causes it to respawn
00439                         shield->use = 0; //Use_Item;
00440 
00441                         // allow to ride movers
00442                         shield->s.groundEntityNum = tr.entityNum;
00443 
00444                         G_SetOrigin( shield, tr.endpos );
00445 
00446                         shield->s.eFlags &= ~EF_NODRAW;
00447                         shield->r.svFlags &= ~SVF_NOCLIENT;
00448 
00449                         trap_LinkEntity (shield);
00450 
00451                         shield->s.owner = playerent->s.number;
00452                         shield->s.shouldtarget = qtrue;
00453                         if (g_gametype.integer >= GT_TEAM)
00454                         {
00455                                 shield->s.teamowner = playerent->client->sess.sessionTeam;
00456                         }
00457                         else
00458                         {
00459                                 shield->s.teamowner = 16;
00460                         }
00461 
00462                         // Play placing sound...
00463                         G_AddEvent(shield, EV_GENERAL_SOUND, shieldAttachSound);
00464 
00465                         return qtrue;
00466                 }
00467         }
00468         // no room
00469         return qfalse;
00470 }
00471 
00472 void ItemUse_Binoculars(gentity_t *ent)
00473 {
00474         if (!ent || !ent->client)
00475         {
00476                 return;
00477         }
00478 
00479         if (ent->client->ps.weaponstate != WEAPON_READY)
00480         { //So we can't fool it and reactivate while switching to the saber or something.
00481                 return;
00482         }
00483 
00484         /*
00485         if (ent->client->ps.weapon == WP_SABER)
00486         { //No.
00487                 return;
00488         }
00489         */
00490 
00491         if (ent->client->ps.zoomMode == 0) // not zoomed or currently zoomed with the disruptor
00492         {
00493                 ent->client->ps.zoomMode = 2;
00494                 ent->client->ps.zoomLocked = qfalse;
00495                 ent->client->ps.zoomFov = 40.0f;
00496         }
00497         else if (ent->client->ps.zoomMode == 2)
00498         {
00499                 ent->client->ps.zoomMode = 0;
00500                 ent->client->ps.zoomTime = level.time;
00501         }
00502 }
00503 
00504 void ItemUse_Shield(gentity_t *ent)
00505 {
00506         PlaceShield(ent);
00507 }
00508 
00509 //--------------------------
00510 // PERSONAL ASSAULT SENTRY
00511 //--------------------------
00512 
00513 #define PAS_DAMAGE      2
00514 
00515 void SentryTouch(gentity_t *ent, gentity_t *other, trace_t *trace)
00516 {
00517         return;
00518 }
00519 
00520 //----------------------------------------------------------------
00521 void pas_fire( gentity_t *ent )
00522 //----------------------------------------------------------------
00523 {
00524         vec3_t fwd, myOrg, enOrg;
00525 
00526         VectorCopy(ent->r.currentOrigin, myOrg);
00527         myOrg[2] += 24;
00528 
00529         VectorCopy(ent->enemy->client->ps.origin, enOrg);
00530         enOrg[2] += 24;
00531 
00532         VectorSubtract(enOrg, myOrg, fwd);
00533         VectorNormalize(fwd);
00534         
00535         myOrg[0] += fwd[0]*16;
00536         myOrg[1] += fwd[1]*16;
00537         myOrg[2] += fwd[2]*16;
00538 
00539         WP_FireTurretMissile(&g_entities[ent->genericValue3], myOrg, fwd, qfalse, 10, 2300, MOD_SENTRY, ent );
00540 
00541         G_RunObject(ent);
00542 }
00543 
00544 #define TURRET_RADIUS 800
00545 
00546 //-----------------------------------------------------
00547 static qboolean pas_find_enemies( gentity_t *self )
00548 //-----------------------------------------------------
00549 {
00550         qboolean        found = qfalse;
00551         int                     count, i;
00552         float           bestDist = TURRET_RADIUS*TURRET_RADIUS;
00553         float           enemyDist;
00554         vec3_t          enemyDir, org, org2;
00555         gentity_t       *entity_list[MAX_GENTITIES], *target;
00556         trace_t         tr;
00557 
00558         if ( self->aimDebounceTime > level.time ) // time since we've been shut off
00559         {
00560                 // We were active and alert, i.e. had an enemy in the last 3 secs
00561                 if ( self->painDebounceTime < level.time )
00562                 {
00563                         G_Sound(self, CHAN_BODY, G_SoundIndex( "sound/chars/turret/ping.wav" ));
00564                         self->painDebounceTime = level.time + 1000;
00565                 }
00566         }
00567 
00568         VectorCopy(self->s.pos.trBase, org2);
00569 
00570         count = G_RadiusList( org2, TURRET_RADIUS, self, qtrue, entity_list );
00571 
00572         for ( i = 0; i < count; i++ )
00573         {
00574                 target = entity_list[i];
00575 
00576                 if ( !target->client )
00577                 {
00578                         continue;
00579                 }
00580                 if ( target == self || !target->takedamage || target->health <= 0 || ( target->flags & FL_NOTARGET ))
00581                 {
00582                         continue;
00583                 }
00584                 if ( self->alliedTeam && target->client->sess.sessionTeam == self->alliedTeam )
00585                 { 
00586                         continue;
00587                 }
00588                 if (self->genericValue3 == target->s.number)
00589                 {
00590                         continue;
00591                 }
00592                 if ( !trap_InPVS( org2, target->r.currentOrigin ))
00593                 {
00594                         continue;
00595                 }
00596 
00597                 if (target->s.eType == ET_NPC &&
00598                         target->s.NPC_class == CLASS_VEHICLE)
00599                 { //don't get mad at vehicles, silly.
00600                         continue;
00601                 }
00602 
00603                 if ( target->client )
00604                 {
00605                         VectorCopy( target->client->ps.origin, org );
00606                 }
00607                 else
00608                 {
00609                         VectorCopy( target->r.currentOrigin, org );
00610                 }
00611 
00612                 trap_Trace( &tr, org2, NULL, NULL, org, self->s.number, MASK_SHOT );
00613 
00614                 if ( !tr.allsolid && !tr.startsolid && ( tr.fraction == 1.0 || tr.entityNum == target->s.number ))
00615                 {
00616                         // Only acquire if have a clear shot, Is it in range and closer than our best?
00617                         VectorSubtract( target->r.currentOrigin, self->r.currentOrigin, enemyDir );
00618                         enemyDist = VectorLengthSquared( enemyDir );
00619 
00620                         if ( enemyDist < bestDist )// all things equal, keep current
00621                         {
00622                                 if ( self->attackDebounceTime + 100 < level.time )
00623                                 {
00624                                         // We haven't fired or acquired an enemy in the last 2 seconds-start-up sound
00625                                         G_Sound( self, CHAN_BODY, G_SoundIndex( "sound/chars/turret/startup.wav" ));
00626 
00627                                         // Wind up turrets for a bit
00628                                         self->attackDebounceTime = level.time + 900 + random() * 200;
00629                                 }
00630 
00631                                 G_SetEnemy( self, target );
00632                                 bestDist = enemyDist;
00633                                 found = qtrue;
00634                         }
00635                 }
00636         }
00637 
00638         return found;
00639 }
00640 
00641 //---------------------------------
00642 void pas_adjust_enemy( gentity_t *ent )
00643 //---------------------------------
00644 {
00645         trace_t tr;
00646         qboolean keep = qtrue;
00647 
00648         if ( ent->enemy->health <= 0 )
00649         {
00650                 keep = qfalse;
00651         }
00652         else
00653         {
00654                 vec3_t          org, org2;
00655 
00656                 VectorCopy(ent->s.pos.trBase, org2);
00657 
00658                 if ( ent->enemy->client )
00659                 {
00660                         VectorCopy( ent->enemy->client->ps.origin, org );
00661                         org[2] -= 15;
00662                 }
00663                 else
00664                 {
00665                         VectorCopy( ent->enemy->r.currentOrigin, org );
00666                 }
00667 
00668                 trap_Trace( &tr, org2, NULL, NULL, org, ent->s.number, MASK_SHOT );
00669 
00670                 if ( tr.allsolid || tr.startsolid || tr.fraction < 0.9f || tr.entityNum == ent->s.number )
00671                 {
00672                         if (tr.entityNum != ent->enemy->s.number)
00673                         {
00674                                 // trace failed
00675                                 keep = qfalse;
00676                         }
00677                 }
00678         }
00679 
00680         if ( keep )
00681         {
00682                 //ent->bounceCount = level.time + 500 + random() * 150;
00683         }
00684         else if ( ent->bounceCount < level.time && ent->enemy ) // don't ping pong on and off
00685         {
00686                 ent->enemy = NULL;
00687                 // shut-down sound
00688                 G_Sound( ent, CHAN_BODY, G_SoundIndex( "sound/chars/turret/shutdown.wav" ));
00689         
00690                 ent->bounceCount = level.time + 500 + random() * 150;
00691 
00692                 // make turret play ping sound for 5 seconds
00693                 ent->aimDebounceTime = level.time + 5000;
00694         }
00695 }
00696 
00697 #define TURRET_DEATH_DELAY 2000
00698 #define TURRET_LIFETIME 60000
00699 
00700 void turret_die(gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod);
00701 
00702 void sentryExpire(gentity_t *self)
00703 {
00704         turret_die(self, self, self, 1000, MOD_UNKNOWN);        
00705 }
00706 
00707 //---------------------------------
00708 void pas_think( gentity_t *ent )
00709 //---------------------------------
00710 {
00711         qboolean        moved;
00712         float           diffYaw, diffPitch;
00713         vec3_t          enemyDir, org;
00714         vec3_t          frontAngles, backAngles;
00715         vec3_t          desiredAngles;
00716         int                     iEntityList[MAX_GENTITIES];
00717         int                     numListedEntities;
00718         int                     i = 0;
00719         qboolean        clTrapped = qfalse;
00720         vec3_t          testMins, testMaxs;
00721 
00722         testMins[0] = ent->r.currentOrigin[0] + ent->r.mins[0]+4;
00723         testMins[1] = ent->r.currentOrigin[1] + ent->r.mins[1]+4;
00724         testMins[2] = ent->r.currentOrigin[2] + ent->r.mins[2]+4;
00725 
00726         testMaxs[0] = ent->r.currentOrigin[0] + ent->r.maxs[0]-4;
00727         testMaxs[1] = ent->r.currentOrigin[1] + ent->r.maxs[1]-4;
00728         testMaxs[2] = ent->r.currentOrigin[2] + ent->r.maxs[2]-4;
00729 
00730         numListedEntities = trap_EntitiesInBox( testMins, testMaxs, iEntityList, MAX_GENTITIES );
00731 
00732         while (i < numListedEntities)
00733         {
00734                 if (iEntityList[i] < MAX_CLIENTS)
00735                 { //client stuck inside me. go nonsolid.
00736                         int clNum = iEntityList[i];
00737 
00738                         numListedEntities = trap_EntitiesInBox( g_entities[clNum].r.absmin, g_entities[clNum].r.absmax, iEntityList, MAX_GENTITIES );
00739 
00740                         i = 0;
00741                         while (i < numListedEntities)
00742                         {
00743                                 if (iEntityList[i] == ent->s.number)
00744                                 {
00745                                         clTrapped = qtrue;
00746                                         break;
00747                                 }
00748                                 i++;
00749                         }
00750                         break;
00751                 }
00752 
00753                 i++;
00754         }
00755 
00756         if (clTrapped)
00757         {
00758                 ent->r.contents = 0;
00759                 ent->s.fireflag = 0;
00760                 ent->nextthink = level.time + FRAMETIME;
00761                 return;
00762         }
00763         else
00764         {
00765                 ent->r.contents = CONTENTS_SOLID;
00766         }
00767 
00768         if (!g_entities[ent->genericValue3].inuse || !g_entities[ent->genericValue3].client ||
00769                 g_entities[ent->genericValue3].client->sess.sessionTeam != ent->genericValue2)
00770         {
00771                 ent->think = G_FreeEntity;
00772                 ent->nextthink = level.time;
00773                 return;
00774         }
00775 
00776 //      G_RunObject(ent);
00777 
00778         if ( !ent->damage )
00779         {
00780                 ent->damage = 1;
00781                 ent->nextthink = level.time + FRAMETIME;
00782                 return;
00783         }
00784 
00785         if ((ent->genericValue8+TURRET_LIFETIME) < level.time)
00786         {
00787                 G_Sound( ent, CHAN_BODY, G_SoundIndex( "sound/chars/turret/shutdown.wav" ));
00788                 ent->s.bolt2 = ENTITYNUM_NONE;
00789                 ent->s.fireflag = 2;
00790 
00791                 ent->think = sentryExpire;
00792                 ent->nextthink = level.time + TURRET_DEATH_DELAY;
00793                 return;
00794         }
00795 
00796         ent->nextthink = level.time + FRAMETIME;
00797 
00798         if ( ent->enemy )
00799         {
00800                 // make sure that the enemy is still valid
00801                 pas_adjust_enemy( ent );
00802         }
00803 
00804         if (ent->enemy)
00805         {
00806                 if (!ent->enemy->client)
00807                 {
00808                         ent->enemy = NULL;
00809                 }
00810                 else if (ent->enemy->s.number == ent->s.number)
00811                 {
00812                         ent->enemy = NULL;
00813                 }
00814                 else if (ent->enemy->health < 1)
00815                 {
00816                         ent->enemy = NULL;
00817                 }
00818         }
00819 
00820         if ( !ent->enemy )
00821         {
00822                 pas_find_enemies( ent );
00823         }
00824 
00825         if (ent->enemy)
00826         {
00827                 ent->s.bolt2 = ent->enemy->s.number;
00828         }
00829         else
00830         {
00831                 ent->s.bolt2 = ENTITYNUM_NONE;
00832         }
00833 
00834         moved = qfalse;
00835         diffYaw = 0.0f; diffPitch = 0.0f;
00836 
00837         ent->speed = AngleNormalize360( ent->speed );
00838         ent->random = AngleNormalize360( ent->random );
00839 
00840         if ( ent->enemy )
00841         {
00842                 // ...then we'll calculate what new aim adjustments we should attempt to make this frame
00843                 // Aim at enemy
00844                 if ( ent->enemy->client )
00845                 {
00846                         VectorCopy( ent->enemy->client->ps.origin, org );
00847                 }
00848                 else
00849                 {
00850                         VectorCopy( ent->enemy->r.currentOrigin, org );
00851                 }
00852 
00853                 VectorSubtract( org, ent->r.currentOrigin, enemyDir );
00854                 vectoangles( enemyDir, desiredAngles );
00855 
00856                 diffYaw = AngleSubtract( ent->speed, desiredAngles[YAW] );
00857                 diffPitch = AngleSubtract( ent->random, desiredAngles[PITCH] );
00858         }
00859         else
00860         {
00861                 // no enemy, so make us slowly sweep back and forth as if searching for a new one
00862                 diffYaw = sin( level.time * 0.0001f + ent->count ) * 2.0f;
00863         }
00864 
00865         if ( fabs(diffYaw) > 0.25f )
00866         {
00867                 moved = qtrue;
00868 
00869                 if ( fabs(diffYaw) > 10.0f )
00870                 {
00871                         // cap max speed
00872                         ent->speed += (diffYaw > 0.0f) ? -10.0f : 10.0f;
00873                 }
00874                 else
00875                 {
00876                         // small enough
00877                         ent->speed -= diffYaw;
00878                 }
00879         }
00880 
00881 
00882         if ( fabs(diffPitch) > 0.25f )
00883         {
00884                 moved = qtrue;
00885 
00886                 if ( fabs(diffPitch) > 4.0f )
00887                 {
00888                         // cap max speed
00889                         ent->random += (diffPitch > 0.0f) ? -4.0f : 4.0f;
00890                 }
00891                 else
00892                 {
00893                         // small enough
00894                         ent->random -= diffPitch;
00895                 }
00896         }
00897 
00898         // the bone axes are messed up, so hence some dumbness here
00899         VectorSet( frontAngles, -ent->random, 0.0f, 0.0f );
00900         VectorSet( backAngles, 0.0f, 0.0f, ent->speed );
00901 
00902         if ( moved )
00903         {
00904         //ent->s.loopSound = G_SoundIndex( "sound/chars/turret/move.wav" );
00905         }
00906         else
00907         {
00908                 ent->s.loopSound = 0;
00909                 ent->s.loopIsSoundset = qfalse;
00910         }
00911 
00912         if ( ent->enemy && ent->attackDebounceTime < level.time )
00913         {
00914                 ent->count--;
00915 
00916                 if ( ent->count )
00917                 {
00918                         pas_fire( ent );
00919                         ent->s.fireflag = 1;
00920                         ent->attackDebounceTime = level.time + 200;
00921                 }
00922                 else
00923                 {
00924                         //ent->nextthink = 0;
00925                         G_Sound( ent, CHAN_BODY, G_SoundIndex( "sound/chars/turret/shutdown.wav" ));
00926                         ent->s.bolt2 = ENTITYNUM_NONE;
00927                         ent->s.fireflag = 2;
00928 
00929                         ent->think = sentryExpire;
00930                         ent->nextthink = level.time + TURRET_DEATH_DELAY;
00931                 }
00932         }
00933         else
00934         {
00935                 ent->s.fireflag = 0;
00936         }
00937 }
00938 
00939 //------------------------------------------------------------------------------------------------------------
00940 void turret_die(gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod)
00941 //------------------------------------------------------------------------------------------------------------
00942 {
00943         // Turn off the thinking of the base & use it's targets
00944         self->think = 0;//NULL;
00945         self->use = 0;//NULL;
00946 
00947         if ( self->target )
00948         {
00949                 G_UseTargets( self, attacker );
00950         }
00951 
00952         if (!g_entities[self->genericValue3].inuse || !g_entities[self->genericValue3].client)
00953         {
00954                 G_FreeEntity(self);
00955                 return;
00956         }
00957 
00958         // clear my data
00959         self->die  = 0;//NULL;
00960         self->takedamage = qfalse;
00961         self->health = 0;
00962 
00963         // hack the effect angle so that explode death can orient the effect properly
00964         VectorSet( self->s.angles, 0, 0, 1 );
00965 
00966         G_PlayEffect(EFFECT_EXPLOSION_PAS, self->s.pos.trBase, self->s.angles);
00967         G_RadiusDamage(self->s.pos.trBase, &g_entities[self->genericValue3], 30, 256, self, self, MOD_UNKNOWN);
00968 
00969         g_entities[self->genericValue3].client->ps.fd.sentryDeployed = qfalse;
00970 
00971         //ExplodeDeath( self );
00972         G_FreeEntity( self );
00973 }
00974 
00975 #define TURRET_AMMO_COUNT 40
00976 
00977 //---------------------------------
00978 void SP_PAS( gentity_t *base )
00979 //---------------------------------
00980 {
00981         if ( base->count == 0 )
00982         {
00983                 // give ammo
00984                 base->count = TURRET_AMMO_COUNT;
00985         }
00986 
00987         base->s.bolt1 = 1; //This is a sort of hack to indicate that this model needs special turret things done to it
00988         base->s.bolt2 = ENTITYNUM_NONE; //store our current enemy index
00989 
00990         base->damage = 0; // start animation flag
00991 
00992         VectorSet( base->r.mins, -8, -8, 0 );
00993         VectorSet( base->r.maxs, 8, 8, 24 );
00994 
00995         G_RunObject(base);
00996 
00997         base->think = pas_think;
00998         base->nextthink = level.time + FRAMETIME;
00999 
01000         if ( !base->health )
01001         {
01002                 base->health = 50;
01003         }
01004 
01005         base->takedamage = qtrue;
01006         base->die  = turret_die;
01007 
01008         base->physicsObject = qtrue;
01009 
01010         G_Sound( base, CHAN_BODY, G_SoundIndex( "sound/chars/turret/startup.wav" ));
01011 }
01012 
01013 //------------------------------------------------------------------------
01014 void ItemUse_Sentry( gentity_t *ent )
01015 //------------------------------------------------------------------------
01016 {
01017         vec3_t fwd, fwdorg;
01018         vec3_t yawonly;
01019         vec3_t mins, maxs;
01020         gentity_t *sentry;
01021 
01022         if (!ent || !ent->client)
01023         {
01024                 return;
01025         }
01026 
01027         VectorSet( mins, -8, -8, 0 );
01028         VectorSet( maxs, 8, 8, 24 );
01029 
01030 
01031         yawonly[ROLL] = 0;
01032         yawonly[PITCH] = 0;
01033         yawonly[YAW] = ent->client->ps.viewangles[YAW];
01034 
01035         AngleVectors(yawonly, fwd, NULL, NULL);
01036 
01037         fwdorg[0] = ent->client->ps.origin[0] + fwd[0]*64;
01038         fwdorg[1] = ent->client->ps.origin[1] + fwd[1]*64;
01039         fwdorg[2] = ent->client->ps.origin[2] + fwd[2]*64;
01040 
01041         sentry = G_Spawn();
01042 
01043         sentry->classname = "sentryGun";
01044         sentry->s.modelindex = G_ModelIndex("models/items/psgun.glm"); //replace ASAP
01045 
01046         sentry->s.g2radius = 30.0f;
01047         sentry->s.modelGhoul2 = 1;
01048 
01049         G_SetOrigin(sentry, fwdorg);
01050         sentry->parent = ent;
01051         sentry->r.contents = CONTENTS_SOLID;
01052         sentry->s.solid = 2;
01053         sentry->clipmask = MASK_SOLID;
01054         VectorCopy(mins, sentry->r.mins);
01055         VectorCopy(maxs, sentry->r.maxs);
01056         sentry->genericValue3 = ent->s.number;
01057         sentry->genericValue2 = ent->client->sess.sessionTeam; //so we can remove ourself if our owner changes teams
01058         sentry->r.absmin[0] = sentry->s.pos.trBase[0] + sentry->r.mins[0];
01059         sentry->r.absmin[1] = sentry->s.pos.trBase[1] + sentry->r.mins[1];
01060         sentry->r.absmin[2] = sentry->s.pos.trBase[2] + sentry->r.mins[2];
01061         sentry->r.absmax[0] = sentry->s.pos.trBase[0] + sentry->r.maxs[0];
01062         sentry->r.absmax[1] = sentry->s.pos.trBase[1] + sentry->r.maxs[1];
01063         sentry->r.absmax[2] = sentry->s.pos.trBase[2] + sentry->r.maxs[2];
01064         sentry->s.eType = ET_GENERAL;
01065         sentry->s.pos.trType = TR_GRAVITY;//STATIONARY;
01066         sentry->s.pos.trTime = level.time;
01067         sentry->touch = SentryTouch;
01068         sentry->nextthink = level.time;
01069         sentry->genericValue4 = ENTITYNUM_NONE; //genericValue4 used as enemy index
01070 
01071         sentry->genericValue5 = 1000;
01072 
01073         sentry->genericValue8 = level.time;
01074 
01075         sentry->alliedTeam = ent->client->sess.sessionTeam;
01076 
01077         ent->client->ps.fd.sentryDeployed = qtrue;
01078 
01079         trap_LinkEntity(sentry);
01080 
01081         sentry->s.owner = ent->s.number;
01082         sentry->s.shouldtarget = qtrue;
01083         if (g_gametype.integer >= GT_TEAM)
01084         {
01085                 sentry->s.teamowner = ent->client->sess.sessionTeam;
01086         }
01087         else
01088         {
01089                 sentry->s.teamowner = 16;
01090         }
01091 
01092         SP_PAS( sentry );
01093 }
01094 
01095 extern gentity_t *NPC_SpawnType( gentity_t *ent, char *npc_type, char *targetname, qboolean isVehicle );
01096 void ItemUse_Seeker(gentity_t *ent)
01097 {
01098         if ( g_gametype.integer == GT_SIEGE && d_siegeSeekerNPC.integer )
01099         {//actualy spawn a remote NPC
01100                 gentity_t *remote = NPC_SpawnType( ent, "remote", NULL, qfalse );
01101                 if ( remote && remote->client )
01102                 {//set it to my team
01103                         remote->s.owner = remote->r.ownerNum = ent->s.number;
01104                         remote->activator = ent;
01105                         if ( ent->client->sess.sessionTeam == TEAM_BLUE )
01106                         {
01107                                 remote->client->playerTeam = NPCTEAM_PLAYER;
01108                         }
01109                         else if ( ent->client->sess.sessionTeam == TEAM_RED )
01110                         {
01111                                 remote->client->playerTeam = NPCTEAM_ENEMY;
01112                         }
01113                         else
01114                         {
01115                                 remote->client->playerTeam = NPCTEAM_NEUTRAL;
01116                         }
01117                 }       
01118         }
01119         else
01120         {
01121                 ent->client->ps.eFlags |= EF_SEEKERDRONE;
01122                 ent->client->ps.droneExistTime = level.time + 30000;
01123                 ent->client->ps.droneFireTime = level.time + 1500;
01124         }
01125 }
01126 
01127 static void MedPackGive(gentity_t *ent, int amount)
01128 {
01129         if (!ent || !ent->client)
01130         {
01131                 return;
01132         }
01133 
01134         if (ent->health <= 0 ||
01135                 ent->client->ps.stats[STAT_HEALTH] <= 0 ||
01136                 (ent->client->ps.eFlags & EF_DEAD))
01137         {
01138                 return;
01139         }
01140 
01141         if (ent->health >= ent->client->ps.stats[STAT_MAX_HEALTH])
01142         {
01143                 return;
01144         }
01145 
01146         ent->health += amount;
01147 
01148         if (ent->health > ent->client->ps.stats[STAT_MAX_HEALTH])
01149         {
01150                 ent->health = ent->client->ps.stats[STAT_MAX_HEALTH];
01151         }
01152 }
01153 
01154 void ItemUse_MedPack_Big(gentity_t *ent)
01155 {
01156         MedPackGive(ent, MAX_MEDPACK_BIG_HEAL_AMOUNT);
01157 }
01158 
01159 void ItemUse_MedPack(gentity_t *ent)
01160 {
01161         MedPackGive(ent, MAX_MEDPACK_HEAL_AMOUNT);
01162 }
01163 
01164 #define JETPACK_TOGGLE_TIME                     1000
01165 void Jetpack_Off(gentity_t *ent)
01166 { //create effects?
01167         assert(ent && ent->client);
01168 
01169         if (!ent->client->jetPackOn)
01170         { //aready off
01171                 return;
01172         }
01173 
01174         ent->client->jetPackOn = qfalse;
01175 }
01176 
01177 void Jetpack_On(gentity_t *ent)
01178 { //create effects?
01179         assert(ent && ent->client);
01180 
01181         if (ent->client->jetPackOn)
01182         { //aready on
01183                 return;
01184         }
01185 
01186         if (ent->client->ps.fd.forceGripBeingGripped >= level.time)
01187         { //can't turn on during grip interval
01188                 return;
01189         }
01190 
01191         if (ent->client->ps.fallingToDeath)
01192         { //too late!
01193                 return;
01194         }
01195 
01196         G_Sound(ent, CHAN_AUTO, G_SoundIndex("sound/boba/JETON"));
01197 
01198         ent->client->jetPackOn = qtrue;
01199 }
01200 
01201 void ItemUse_Jetpack( gentity_t *ent )
01202 {
01203         assert(ent && ent->client);
01204 
01205         if (ent->client->jetPackToggleTime >= level.time)
01206         {
01207                 return;
01208         }
01209 
01210         if (ent->health <= 0 ||
01211                 ent->client->ps.stats[STAT_HEALTH] <= 0 ||
01212                 (ent->client->