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->ps.eFlags & EF_DEAD) ||
01213                 ent->client->ps.pm_type == PM_DEAD)
01214         { //can't use it when dead under any circumstances.
01215                 return;
01216         }
01217 
01218         if (!ent->client->jetPackOn &&
01219                 ent->client->ps.jetpackFuel < 5)
01220         { //too low on fuel to start it up
01221                 return;
01222         }
01223 
01224         if (ent->client->jetPackOn)
01225         {
01226                 Jetpack_Off(ent);
01227         }
01228         else
01229         {
01230                 Jetpack_On(ent);
01231         }
01232 
01233         ent->client->jetPackToggleTime = level.time + JETPACK_TOGGLE_TIME;
01234 }
01235 
01236 #define CLOAK_TOGGLE_TIME                       1000
01237 extern void Jedi_Cloak( gentity_t *self );
01238 extern void Jedi_Decloak( gentity_t *self );
01239 void ItemUse_UseCloak( gentity_t *ent )
01240 {
01241         assert(ent && ent->client);
01242 
01243         if (ent->client->cloakToggleTime >= level.time)
01244         {
01245                 return;
01246         }
01247 
01248         if (ent->health <= 0 ||
01249                 ent->client->ps.stats[STAT_HEALTH] <= 0 ||
01250                 (ent->client->ps.eFlags & EF_DEAD) ||
01251                 ent->client->ps.pm_type == PM_DEAD)
01252         { //can't use it when dead under any circumstances.
01253                 return;
01254         }
01255 
01256         if (!ent->client->ps.powerups[PW_CLOAKED] &&
01257                 ent->client->ps.cloakFuel < 5)
01258         { //too low on fuel to start it up
01259                 return;
01260         }
01261 
01262         if ( ent->client->ps.powerups[PW_CLOAKED] )
01263         {//decloak
01264                 Jedi_Decloak( ent );
01265         }
01266         else
01267         {//cloak
01268                 Jedi_Cloak( ent );
01269         }
01270 
01271         ent->client->cloakToggleTime = level.time + CLOAK_TOGGLE_TIME;
01272 }
01273 
01274 #define TOSSED_ITEM_STAY_PERIOD                 20000
01275 #define TOSSED_ITEM_OWNER_NOTOUCH_DUR   1000
01276 
01277 void SpecialItemThink(gentity_t *ent)
01278 {
01279         float gravity = 3.0f;
01280         float mass = 0.09f;
01281         float bounce = 1.1f;
01282 
01283         if (ent->genericValue5 < level.time)
01284         {
01285                 ent->think = G_FreeEntity;
01286                 ent->nextthink = level.time;
01287                 return;
01288         }
01289 
01290         G_RunExPhys(ent, gravity, mass, bounce, qfalse, NULL, 0);
01291         VectorCopy(ent->r.currentOrigin, ent->s.origin);
01292         ent->nextthink = level.time + 50;
01293 }
01294 
01295 void G_SpecialSpawnItem(gentity_t *ent, gitem_t *item)
01296 {
01297         RegisterItem( item );
01298         ent->item = item;
01299 
01300         //go away if no one wants me
01301         ent->genericValue5 = level.time + TOSSED_ITEM_STAY_PERIOD;
01302         ent->think = SpecialItemThink;
01303         ent->nextthink = level.time + 50;
01304         ent->clipmask = MASK_SOLID;
01305 
01306         ent->physicsBounce = 0.50;              // items are bouncy
01307         VectorSet (ent->r.mins, -8, -8, -0);
01308         VectorSet (ent->r.maxs, 8, 8, 16);
01309 
01310         ent->s.eType = ET_ITEM;
01311         ent->s.modelindex = ent->item - bg_itemlist;            // store item number in modelindex
01312 
01313         ent->r.contents = CONTENTS_TRIGGER;
01314         ent->touch = Touch_Item;
01315 
01316         //can't touch owner for x seconds
01317         ent->genericValue11 = ent->r.ownerNum;
01318         ent->genericValue10 = level.time + TOSSED_ITEM_OWNER_NOTOUCH_DUR;
01319 
01320         //so we know to remove when picked up, not respawn
01321         ent->genericValue9 = 1;
01322 
01323         //kind of a lame value to use, but oh well. This means don't
01324         //pick up this item clientside with prediction, because we
01325         //aren't sending over all the data necessary for the player
01326         //to know if he can.
01327         ent->s.brokenLimbs = 1;
01328 
01329         //since it uses my server-only physics
01330         ent->s.eFlags |= EF_CLIENTSMOOTH;
01331 }
01332 
01333 #define DISP_HEALTH_ITEM                "item_medpak_instant"
01334 #define DISP_AMMO_ITEM                  "ammo_all"
01335 
01336 void G_PrecacheDispensers(void)
01337 {
01338         gitem_t *item;
01339                 
01340         item = BG_FindItem(DISP_HEALTH_ITEM);
01341         if (item)
01342         {
01343                 RegisterItem(item);
01344         }
01345 
01346         item = BG_FindItem(DISP_AMMO_ITEM);
01347         if (item)
01348         {
01349                 RegisterItem(item);
01350         }
01351 }
01352 
01353 void ItemUse_UseDisp(gentity_t *ent, int type)
01354 {
01355         gitem_t *item = NULL;
01356         gentity_t *eItem;
01357 
01358         if (!ent->client ||
01359                 ent->client->tossableItemDebounce > level.time)
01360         { //can't use it again yet
01361                 return;
01362         }
01363 
01364         if (ent->client->ps.weaponTime > 0 ||
01365                 ent->client->ps.forceHandExtend != HANDEXTEND_NONE)
01366         { //busy doing something else
01367                 return;
01368         }
01369         
01370         ent->client->tossableItemDebounce = level.time + TOSS_DEBOUNCE_TIME;
01371         
01372         if (type == HI_HEALTHDISP)
01373         {
01374                 item = BG_FindItem(DISP_HEALTH_ITEM);
01375         }
01376         else
01377         {
01378                 item = BG_FindItem(DISP_AMMO_ITEM);
01379         }
01380 
01381         if (item)
01382         {
01383                 vec3_t fwd, pos;
01384                 gentity_t       *te;
01385 
01386                 eItem = G_Spawn();
01387                 eItem->r.ownerNum = ent->s.number;
01388                 eItem->classname = item->classname;
01389 
01390                 VectorCopy(ent->client->ps.origin, pos);
01391                 pos[2] += ent->client->ps.viewheight;
01392 
01393                 G_SetOrigin(eItem, pos);
01394                 VectorCopy(eItem->r.currentOrigin, eItem->s.origin);
01395                 trap_LinkEntity(eItem);
01396 
01397                 G_SpecialSpawnItem(eItem, item);
01398 
01399                 AngleVectors(ent->client->ps.viewangles, fwd, NULL, NULL);
01400                 VectorScale(fwd, 128.0f, eItem->epVelocity);
01401                 eItem->epVelocity[2] = 16.0f;
01402 
01403         //      G_SetAnim( ent, NULL, SETANIM_TORSO, BOTH_THERMAL_THROW, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0 );
01404 
01405                 te = G_TempEntity( ent->client->ps.origin, EV_LOCALTIMER );
01406                 te->s.time = level.time;
01407                 te->s.time2 = TOSS_DEBOUNCE_TIME;
01408                 te->s.owner = ent->client->ps.clientNum;
01409         }
01410 }
01411 
01412 
01413 //===============================================
01414 //Portable E-Web -rww
01415 //===============================================
01416 //put the e-web away/remove it from the owner
01417 void EWebDisattach(gentity_t *owner, gentity_t *eweb)
01418 {
01419     owner->client->ewebIndex = 0;
01420         owner->client->ps.emplacedIndex = 0;
01421         if (owner->health > 0)
01422         {
01423                 owner->client->ps.stats[STAT_WEAPONS] = eweb->genericValue11;
01424         }
01425         else
01426         {
01427                 owner->client->ps.stats[STAT_WEAPONS] = 0;
01428         }
01429         eweb->think = G_FreeEntity;
01430         eweb->nextthink = level.time;
01431 }
01432 
01433 //precache misc e-web assets
01434 void EWebPrecache(void)
01435 {
01436         RegisterItem( BG_FindItemForWeapon(WP_TURRET) );
01437         G_EffectIndex("detpack/explosion.efx");
01438         G_EffectIndex("turret/muzzle_flash.efx");
01439 }
01440 
01441 //e-web death
01442 #define EWEB_DEATH_RADIUS               128
01443 #define EWEB_DEATH_DMG                  90
01444 
01445 #include "../namespace_begin.h"
01446 extern void BG_CycleInven(playerState_t *ps, int direction); //bg_misc.c
01447 #include "../namespace_end.h"
01448 
01449 void EWebDie(gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod)
01450 {
01451         vec3_t fxDir;
01452 
01453         G_RadiusDamage(self->r.currentOrigin, self, EWEB_DEATH_DMG, EWEB_DEATH_RADIUS, self, self, MOD_SUICIDE);
01454 
01455         VectorSet(fxDir, 1.0f, 0.0f, 0.0f);
01456         G_PlayEffect(EFFECT_EXPLOSION_DETPACK, self->r.currentOrigin, fxDir);
01457 
01458         if (self->r.ownerNum != ENTITYNUM_NONE)
01459         {
01460                 gentity_t *owner = &g_entities[self->r.ownerNum];
01461 
01462                 if (owner->inuse && owner->client)
01463                 {
01464                         EWebDisattach(owner, self);
01465 
01466                         //make sure it resets next time we spawn one in case we someone obtain one before death
01467                         owner->client->ewebHealth = -1;
01468 
01469                         //take it away from him, it is gone forever.
01470                         owner->client->ps.stats[STAT_HOLDABLE_ITEMS] &= ~(1<<HI_EWEB);
01471 
01472                         if (owner->client->ps.stats[STAT_HOLDABLE_ITEM] > 0 &&
01473                                 bg_itemlist[owner->client->ps.stats[STAT_HOLDABLE_ITEM]].giType == IT_HOLDABLE &&
01474                                 bg_itemlist[owner->client->ps.stats[STAT_HOLDABLE_ITEM]].giTag == HI_EWEB)
01475                         { //he has it selected so deselect it and select the first thing available
01476                                 owner->client->ps.stats[STAT_HOLDABLE_ITEM] = 0;
01477                                 BG_CycleInven(&owner->client->ps, 1);
01478                         }
01479                 }
01480         }
01481 }
01482 
01483 //e-web pain
01484 void EWebPain(gentity_t *self, gentity_t *attacker, int damage)
01485 {
01486         //update the owner's health status of me
01487         if (self->r.ownerNum != ENTITYNUM_NONE)
01488         {
01489                 gentity_t *owner = &g_entities[self->r.ownerNum];
01490 
01491                 if (owner->inuse && owner->client)
01492                 {
01493                         owner->client->ewebHealth = self->health;
01494                 }
01495         }
01496 }
01497 
01498 //special routine for tracking angles between client and server
01499 void EWeb_SetBoneAngles(gentity_t *ent, char *bone, vec3_t angles)
01500 {
01501 #ifdef _XBOX
01502         byte *thebone = &ent->s.boneIndex1;
01503         byte *firstFree = NULL;
01504 #else
01505         int *thebone = &ent->s.boneIndex1;
01506         int *firstFree = NULL;
01507 #endif
01508         int i = 0;
01509         int boneIndex = G_BoneIndex(bone);
01510         int flags, up, right, forward;
01511         vec3_t *boneVector = &ent->s.boneAngles1;
01512         vec3_t *freeBoneVec = NULL;
01513 
01514         while (thebone)
01515         {
01516                 if (!*thebone && !firstFree)
01517                 { //if the value is 0 then this index is clear, we can use it if we don't find the bone we want already existing.
01518                         firstFree = thebone;
01519                         freeBoneVec = boneVector;
01520                 }
01521                 else if (*thebone)
01522                 {
01523                         if (*thebone == boneIndex)
01524                         { //this is it
01525                                 break;
01526                         }
01527                 }
01528 
01529                 switch (i)
01530                 {
01531                 case 0:
01532                         thebone = &ent->s.boneIndex2;
01533                         boneVector = &ent->s.boneAngles2;
01534                         break;
01535                 case 1:
01536                         thebone = &ent->s.boneIndex3;
01537                         boneVector = &ent->s.boneAngles3;
01538                         break;
01539                 case 2:
01540                         thebone = &ent->s.boneIndex4;
01541                         boneVector = &ent->s.boneAngles4;
01542                         break;
01543                 default:
01544                         thebone = NULL;
01545                         boneVector = NULL;
01546                         break;
01547                 }
01548 
01549                 i++;
01550         }
01551 
01552         if (!thebone)
01553         { //didn't find it, create it
01554                 if (!firstFree)
01555                 { //no free bones.. can't do a thing then.
01556                         Com_Printf("WARNING: E-Web has no free bone indexes\n");
01557                         return;
01558                 }
01559 
01560                 thebone = firstFree;
01561 
01562                 *thebone = boneIndex;
01563                 boneVector = freeBoneVec;
01564         }
01565 
01566         //If we got here then we have a vector and an index.
01567 
01568         //Copy the angles over the vector in the entitystate, so we can use the corresponding index
01569         //to set the bone angles on the client.
01570         VectorCopy(angles, *boneVector);
01571 
01572         //Now set the angles on our server instance if we have one.
01573 
01574         if (!ent->ghoul2)
01575         {
01576                 return;
01577         }
01578 
01579         flags = BONE_ANGLES_POSTMULT;
01580         up = POSITIVE_Y;
01581         right = NEGATIVE_Z;
01582         forward = NEGATIVE_X;
01583 
01584         //first 3 bits is forward, second 3 bits is right, third 3 bits is up
01585         ent->s.boneOrient = ((forward)|(right<<3)|(up<<6));
01586 
01587         trap_G2API_SetBoneAngles( ent->ghoul2,
01588                                         0,
01589                                         bone,
01590                                         angles, 
01591                                         flags,
01592                                         up,
01593                                         right,
01594                                         forward,
01595                                         NULL,
01596                                         100,
01597                                         level.time ); 
01598 }
01599 
01600 //start an animation on model_root both server side and client side
01601 void EWeb_SetBoneAnim(gentity_t *eweb, int startFrame, int endFrame)
01602 {
01603         //set info on the entity so it knows to start the anim on the client next snapshot.
01604         eweb->s.eFlags |= EF_G2ANIMATING;
01605 
01606         if (eweb->s.torsoAnim == startFrame && eweb->s.legsAnim == endFrame)
01607         { //already playing this anim, let's flag it to restart
01608                 eweb->s.torsoFlip = !eweb->s.torsoFlip;
01609         }
01610         else
01611         {
01612                 eweb->s.torsoAnim = startFrame;
01613                 eweb->s.legsAnim = endFrame;
01614         }
01615 
01616         //now set the animation on the server ghoul2 instance.
01617         assert(eweb->ghoul2);
01618         trap_G2API_SetBoneAnim(eweb->ghoul2, 0, "model_root", startFrame, endFrame,
01619                 (BONE_ANIM_OVERRIDE_FREEZE|BONE_ANIM_BLEND), 1.0f, level.time, -1, 100);
01620 }
01621 
01622 //fire a shot off
01623 #define EWEB_MISSILE_DAMAGE                     20
01624 void EWebFire(gentity_t *owner, gentity_t *eweb)
01625 {
01626         mdxaBone_t boltMatrix;
01627         gentity_t *missile;
01628         vec3_t p, d, bPoint;
01629 
01630         if (eweb->genericValue10 == -1)
01631         { //oh no
01632                 assert(!"Bad e-web bolt");
01633                 return;
01634         }
01635 
01636         //get the muzzle point
01637         trap_G2API_GetBoltMatrix(eweb->ghoul2, 0, eweb->genericValue10, &boltMatrix, eweb->s.apos.trBase, eweb->r.currentOrigin, level.time, NULL, eweb->modelScale);
01638         BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, p);
01639         BG_GiveMeVectorFromMatrix(&boltMatrix, NEGATIVE_Y, d);
01640 
01641         //Start the thing backwards into the bounding box so it can't start inside other solid things
01642         VectorMA(p, -16.0f, d, bPoint);
01643 
01644         //create the missile
01645         missile = CreateMissile( bPoint, d, 1200.0f, 10000, owner, qfalse );
01646 
01647         missile->classname = "generic_proj";
01648         missile->s.weapon = WP_TURRET;
01649 
01650         missile->damage = EWEB_MISSILE_DAMAGE;
01651         missile->dflags = DAMAGE_DEATH_KNOCKBACK;
01652         missile->methodOfDeath = MOD_TURBLAST;
01653         missile->clipmask = (MASK_SHOT|CONTENTS_LIGHTSABER);
01654 
01655         //ignore the e-web entity
01656         missile->passThroughNum = eweb->s.number+1;
01657 
01658         //times it can bounce before it dies
01659         missile->bounceCount = 8;
01660 
01661         //play the muzzle flash
01662         vectoangles(d, d);
01663         G_PlayEffectID(G_EffectIndex("turret/muzzle_flash.efx"), p, d);
01664 }
01665 
01666 //lock the owner into place relative to the cannon pos
01667 void EWebPositionUser(gentity_t *owner, gentity_t *eweb)
01668 {
01669         mdxaBone_t boltMatrix;
01670         vec3_t p, d;
01671         trace_t tr;
01672 
01673         trap_G2API_GetBoltMatrix(eweb->ghoul2, 0, eweb->genericValue9, &boltMatrix, eweb->s.apos.trBase, eweb->r.currentOrigin, level.time, NULL, eweb->modelScale);
01674         BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, p);
01675         BG_GiveMeVectorFromMatrix(&boltMatrix, NEGATIVE_X, d);
01676 
01677         VectorMA(p, 32.0f, d, p);
01678         p[2] = eweb->r.currentOrigin[2];
01679 
01680         p[2] += 4.0f;
01681 
01682         trap_Trace(&tr, owner->client->ps.origin, owner->r.mins, owner->r.maxs, p, owner->s.number, MASK_PLAYERSOLID);
01683 
01684         if (!tr.startsolid && !tr.allsolid && tr.fraction == 1.0f)
01685         { //all clear, we can move there
01686                 vec3_t pDown;
01687 
01688                 VectorCopy(p, pDown);
01689                 pDown[2] -= 7.0f;
01690                 trap_Trace(&tr, p, owner->r.mins, owner->r.maxs, pDown, owner->s.number, MASK_PLAYERSOLID);
01691 
01692                 if (!tr.startsolid && !tr.allsolid)
01693                 {
01694                         VectorSubtract(owner->client->ps.origin, tr.endpos, d);
01695                         if (VectorLength(d) > 1.0f)
01696                         { //we moved, do some animating
01697                                 vec3_t dAng;
01698                                 int aFlags = SETANIM_FLAG_HOLD;
01699 
01700                                 vectoangles(d, dAng);
01701                                 dAng[YAW] = AngleSubtract(owner->client->ps.viewangles[YAW], dAng[YAW]);
01702                                 if (dAng[YAW] > 0.0f)
01703                                 {
01704                                         if (owner->client->ps.legsAnim == BOTH_STRAFE_RIGHT1)
01705                                         { //reset to change direction
01706                                                 aFlags |= SETANIM_FLAG_OVERRIDE;
01707                                         }
01708                                         G_SetAnim(owner, &owner->client->pers.cmd, SETANIM_LEGS, BOTH_STRAFE_LEFT1, aFlags, 0);
01709                                 }
01710                                 else
01711                                 {
01712                                         if (owner->client->ps.legsAnim == BOTH_STRAFE_LEFT1)
01713                                         { //reset to change direction
01714                                                 aFlags |= SETANIM_FLAG_OVERRIDE;
01715                                         }
01716                                         G_SetAnim(owner, &owner->client->pers.cmd, SETANIM_LEGS, BOTH_STRAFE_RIGHT1, aFlags, 0);
01717                                 }
01718                         }
01719                         else if (owner->client->ps.legsAnim == BOTH_STRAFE_RIGHT1 || owner->client->ps.legsAnim == BOTH_STRAFE_LEFT1)
01720                         { //don't keep animating in place
01721                                 owner->client->ps.legsTimer = 0;
01722                         }
01723 
01724                         G_SetOrigin(owner, tr.endpos);
01725                         VectorCopy(tr.endpos, owner->client->ps.origin);
01726                 }
01727         }
01728         else
01729         { //can't move here.. stop using the thing I guess
01730                 EWebDisattach(owner, eweb);
01731         }
01732 }
01733 
01734 //keep the bone angles updated based on the owner's look dir
01735 void EWebUpdateBoneAngles(gentity_t *owner, gentity_t *eweb)
01736 {
01737         vec3_t yAng;
01738         float ideal;
01739         float incr;
01740         const float turnCap = 4.0f; //max degrees we can turn per update
01741         
01742         VectorClear(yAng);
01743         ideal = AngleSubtract(owner->client->ps.viewangles[YAW], eweb->s.angles[YAW]);
01744         incr = AngleSubtract(ideal, eweb->angle);
01745 
01746         if (incr > turnCap)
01747         {
01748                 incr = turnCap;
01749         }
01750         else if (incr < -turnCap)
01751         {
01752                 incr = -turnCap;
01753         }
01754 
01755         eweb->angle += incr;
01756 
01757         yAng[0] = eweb->angle;
01758         EWeb_SetBoneAngles(eweb, "cannon_Yrot", yAng);
01759 
01760         EWebPositionUser(owner, eweb);
01761         if (!owner->client->ewebIndex)
01762         { //was removed during position function
01763                 return;
01764         }
01765 
01766         VectorClear(yAng);
01767         yAng[2] = AngleSubtract(owner->client->ps.viewangles[PITCH], eweb->s.angles[PITCH])*0.8f;
01768         EWeb_SetBoneAngles(eweb, "cannon_Xrot", yAng);
01769 }
01770 
01771 //keep it updated
01772 #include "../namespace_begin.h"
01773 extern int BG_EmplacedView(vec3_t baseAngles, vec3_t angles, float *newYaw, float constraint); //bg_misc.c
01774 #include "../namespace_end.h"
01775 
01776 void EWebThink(gentity_t *self)
01777 {
01778         qboolean killMe = qfalse;
01779         const float gravity = 3.0f;
01780         const float mass = 0.09f;
01781         const float bounce = 1.1f;
01782 
01783         if (self->r.ownerNum == ENTITYNUM_NONE)
01784         {
01785                 killMe = qtrue;
01786         }
01787         else
01788         {
01789                 gentity_t *owner = &g_entities[self->r.ownerNum];
01790 
01791                 if (!owner->inuse || !owner->client || owner->client->pers.connected != CON_CONNECTED ||
01792                         owner->client->ewebIndex != self->s.number || owner->health < 1)
01793                 {
01794                         killMe = qtrue;
01795                 }
01796                 else if (owner->client->ps.emplacedIndex != self->s.number)
01797                 { //just go back to the inventory then
01798                         EWebDisattach(owner, self);
01799                         return;
01800                 }
01801 
01802                 if (!killMe)
01803                 {
01804                         float yaw;
01805 
01806                         if (BG_EmplacedView(owner->client->ps.viewangles, self->s.angles, &yaw, self->s.origin2[0]))
01807                         {
01808                                 owner->client->ps.viewangles[YAW] = yaw;
01809                         }
01810                         owner->client->ps.weapon = WP_EMPLACED_GUN;
01811                         owner->client->ps.stats[STAT_WEAPONS] = WP_EMPLACED_GUN;
01812 
01813                         if (self->genericValue8 < level.time)
01814                         { //make sure the anim timer is done
01815                                 EWebUpdateBoneAngles(owner, self);
01816                                 if (!owner->client->ewebIndex)
01817                                 { //was removed during position function
01818                                         return;
01819                                 }
01820 
01821                                 if (owner->client->pers.cmd.buttons & BUTTON_ATTACK)
01822                                 {
01823                                         if (self->genericValue5 < level.time)
01824                                         { //we can fire another shot off
01825                                                 EWebFire(owner, self);
01826 
01827                                                 //cheap firing anim
01828                                                 EWeb_SetBoneAnim(self, 2, 4);
01829                                                 self->genericValue3 = 1;
01830 
01831                                                 //set fire debounce time
01832                                                 self->genericValue5 = level.time + 100;
01833                                         }
01834                                 }
01835                                 else if (self->genericValue5 < level.time && self->genericValue3)
01836                                 { //reset the anim back to non-firing
01837                                         EWeb_SetBoneAnim(self, 0, 1);
01838                                         self->genericValue3 = 0;
01839                                 }
01840                         }
01841                 }
01842         }
01843 
01844         if (killMe)
01845         { //something happened to the owner, let's explode
01846                 EWebDie(self, self, self, 999, MOD_SUICIDE);
01847                 return;
01848         }
01849 
01850         //run some physics on it real quick so it falls and stuff properly
01851         G_RunExPhys(self, gravity, mass, bounce, qfalse, NULL, 0);
01852 
01853         self->nextthink = level.time;
01854 }
01855 
01856 #define EWEB_HEALTH                     200
01857 
01858 //spawn and set up an e-web ent
01859 gentity_t *EWeb_Create(gentity_t *spawner)
01860 {
01861         const char *modelName = "models/map_objects/hoth/eweb_model.glm";
01862         int failSound = G_SoundIndex("sound/interface/shieldcon_empty");
01863         gentity_t *ent;
01864         trace_t tr;
01865         vec3_t fAng, fwd, pos, downPos, s;
01866         vec3_t mins, maxs;
01867 
01868         VectorSet(mins, -32, -32, -24);
01869         VectorSet(maxs, 32, 32, 24);
01870 
01871         VectorSet(fAng, 0, spawner->client->ps.viewangles[1], 0);
01872         AngleVectors(fAng, fwd, 0, 0);
01873 
01874         VectorCopy(spawner->client->ps.origin, s);
01875         //allow some fudge
01876         s[2] += 12.0f;
01877 
01878         VectorMA(s, 48.0f, fwd, pos);
01879 
01880         trap_Trace(&tr, s, mins, maxs, pos, spawner->s.number, MASK_PLAYERSOLID);
01881 
01882         if (tr.allsolid || tr.startsolid || tr.fraction != 1.0f)
01883         { //can't spawn here, we are in solid
01884                 G_Sound(spawner, CHAN_AUTO, failSound);
01885                 return NULL;
01886         }
01887 
01888         ent = G_Spawn();
01889 
01890         ent->clipmask = MASK_PLAYERSOLID;
01891         ent->r.contents = MASK_PLAYERSOLID;
01892 
01893         ent->physicsObject = qtrue;
01894 
01895         //for the sake of being able to differentiate client-side between this and an emplaced gun
01896         ent->s.weapon = WP_NONE;
01897 
01898         VectorCopy(pos, downPos);
01899         downPos[2] -= 18.0f;
01900         trap_Trace(&tr, pos, mins, maxs, downPos, spawner->s.number, MASK_PLAYERSOLID);
01901 
01902         if (tr.startsolid || tr.allsolid || tr.fraction == 1.0f || tr.entityNum < ENTITYNUM_WORLD)
01903         { //didn't hit ground.
01904                 G_FreeEntity(ent);
01905                 G_Sound(spawner, CHAN_AUTO, failSound);
01906                 return NULL;
01907         }
01908 
01909         VectorCopy(tr.endpos, pos);
01910 
01911         G_SetOrigin(ent, pos);
01912 
01913         VectorCopy(fAng, ent->s.apos.trBase);
01914         VectorCopy(fAng, ent->r.currentAngles);
01915 
01916         ent->s.owner = spawner->s.number;
01917         ent->s.teamowner = spawner->client->sess.sessionTeam;
01918 
01919         ent->takedamage = qtrue;
01920 
01921         if (spawner->client->ewebHealth <= 0)
01922         { //refresh the owner's e-web health if its last e-web did not exist or was killed
01923                 spawner->client->ewebHealth = EWEB_HEALTH;
01924         }
01925 
01926         //resume health of last deployment
01927         ent->maxHealth = EWEB_HEALTH;
01928         ent->health = spawner->client->ewebHealth;
01929         G_ScaleNetHealth(ent);
01930 
01931         ent->die = EWebDie;
01932         ent->pain = EWebPain;
01933 
01934         ent->think = EWebThink;
01935         ent->nextthink = level.time;
01936 
01937         //set up the g2 model info
01938         ent->s.modelGhoul2 = 1;
01939         ent->s.g2radius = 128;
01940         ent->s.modelindex = G_ModelIndex((char *)modelName);
01941 
01942         trap_G2API_InitGhoul2Model(&ent->ghoul2, modelName, 0, 0, 0, 0, 0);
01943 
01944         if (!ent->ghoul2)
01945         { //should not happen, but just to be safe.
01946                 G_FreeEntity(ent);
01947                 return NULL;
01948         }
01949 
01950         //initialize bone angles
01951         EWeb_SetBoneAngles(ent, "cannon_Yrot", vec3_origin);
01952         EWeb_SetBoneAngles(ent, "cannon_Xrot", vec3_origin);
01953 
01954         ent->genericValue10 = trap_G2API_AddBolt(ent->ghoul2, 0, "*cannonflash"); //muzzle bolt
01955         ent->genericValue9 = trap_G2API_AddBolt(ent->ghoul2, 0, "cannon_Yrot"); //for placing the owner relative to rotation
01956 
01957         //set the constraints for this guy as an emplaced weapon, and his constraint angles
01958         ent->s.origin2[0] = 360.0f; //360 degrees in either direction
01959 
01960         VectorCopy(fAng, ent->s.angles); //consider "angle 0" for constraint
01961 
01962         //angle of y rot bone
01963         ent->angle = 0.0f;
01964 
01965         ent->r.ownerNum = spawner->s.number;
01966         trap_LinkEntity(ent);
01967 
01968         //store off the owner's current weapons, we will be forcing him to use the "emplaced" weapon
01969         ent->genericValue11 = spawner->client->ps.stats[STAT_WEAPONS];
01970 
01971         //start the "unfolding" anim
01972         EWeb_SetBoneAnim(ent, 4, 20);
01973         //don't allow use until the anim is done playing (rough time estimate)
01974         ent->genericValue8 = level.time + 500;
01975 
01976         VectorCopy(mins, ent->r.mins);
01977         VectorCopy(maxs, ent->r.maxs);
01978 
01979         return ent;
01980 }
01981 
01982 #define EWEB_USE_DEBOUNCE               1000
01983 //use the e-web
01984 void ItemUse_UseEWeb(gentity_t *ent)
01985 {
01986         if (ent->client->ewebTime > level.time)
01987         { //can't use again yet
01988                 return;
01989         }
01990 
01991         if (ent->client->ps.weaponTime > 0 ||
01992                 ent->client->ps.forceHandExtend != HANDEXTEND_NONE)
01993         { //busy doing something else
01994                 return;
01995         }
01996 
01997         if (ent->client->ps.emplacedIndex && !ent->client->ewebIndex)
01998         { //using an emplaced gun already that isn't our own e-web
01999                 return;
02000         }
02001 
02002         if (ent->client->ewebIndex)
02003         { //put it away
02004                 EWebDisattach(ent, &g_entities[ent->client->ewebIndex]);
02005         }
02006         else
02007         { //create it
02008                 gentity_t *eweb = EWeb_Create(ent);
02009 
02010                 if (eweb)
02011                 { //if it's null the thing couldn't spawn (probably no room)
02012                         ent->client->ewebIndex = eweb->s.number;
02013                         ent->client->ps.emplacedIndex = eweb->s.number;
02014                 }
02015         }
02016 
02017         ent->client->ewebTime = level.time + EWEB_USE_DEBOUNCE;
02018 }
02019 //===============================================
02020 //End E-Web
02021 //===============================================
02022 
02023 
02024 int Pickup_Powerup( gentity_t *ent, gentity_t *other ) {
02025         int                     quantity;
02026         int                     i;
02027         gclient_t       *client;
02028 
02029         if ( !other->client->ps.powerups[ent->item->giTag] ) {
02030                 // round timing to seconds to make multiple powerup timers
02031                 // count in sync
02032                 other->client->ps.powerups[ent->item->giTag] = 
02033                         level.time - ( level.time % 1000 );
02034 
02035                 G_LogWeaponPowerup(other->s.number, ent->item->giTag);
02036         }
02037 
02038         if ( ent->count ) {
02039                 quantity = ent->count;
02040         } else {
02041                 quantity = ent->item->quantity;
02042         }
02043 
02044         other->client->ps.powerups[ent->item->giTag] += quantity * 1000;
02045 
02046         if (ent->item->giTag == PW_YSALAMIRI)
02047         {
02048                 other->client->ps.powerups[PW_FORCE_ENLIGHTENED_LIGHT] = 0;
02049                 other->client->ps.powerups[PW_FORCE_ENLIGHTENED_DARK] = 0;
02050                 other->client->ps.powerups[PW_FORCE_BOON] = 0;
02051         }
02052 
02053         // give any nearby players a "denied" anti-reward
02054         for ( i = 0 ; i < level.maxclients ; i++ ) {
02055                 vec3_t          delta;
02056                 float           len;
02057                 vec3_t          forward;
02058                 trace_t         tr;
02059 
02060                 client = &level.clients[i];
02061                 if ( client == other->client ) {
02062                         continue;
02063                 }
02064                 if ( client->pers.connected == CON_DISCONNECTED ) {
02065                         continue;
02066                 }
02067                 if ( client->ps.stats[STAT_HEALTH] <= 0 ) {
02068                         continue;
02069                 }
02070 
02071     // if same team in team game, no sound
02072     // cannot use OnSameTeam as it expects to g_entities, not clients
02073         if ( g_gametype.integer >= GT_TEAM && other->client->sess.sessionTeam == client->sess.sessionTeam  ) {
02074       continue;
02075     }
02076 
02077                 // if too far away, no sound
02078                 VectorSubtract( ent->s.pos.trBase, client->ps.origin, delta );
02079                 len = VectorNormalize( delta );
02080                 if ( len > 192 ) {
02081                         continue;
02082                 }
02083 
02084                 // if not facing, no sound
02085                 AngleVectors( client->ps.viewangles, forward, NULL, NULL );
02086                 if ( DotProduct( delta, forward ) < 0.4 ) {
02087                         continue;
02088                 }
02089 
02090                 // if not line of sight, no sound
02091                 trap_Trace( &tr, client->ps.origin, NULL, NULL, ent->s.pos.trBase, ENTITYNUM_NONE, CONTENTS_SOLID );
02092                 if ( tr.fraction != 1.0 ) {
02093                         continue;
02094                 }
02095 
02096                 // anti-reward
02097                 client->ps.persistant[PERS_PLAYEREVENTS] ^= PLAYEREVENT_DENIEDREWARD;
02098         }
02099         return RESPAWN_POWERUP;
02100 }
02101 
02102 //======================================================================
02103 
02104 int Pickup_Holdable( gentity_t *ent, gentity_t *other ) {
02105 
02106         other->client->ps.stats[STAT_HOLDABLE_ITEM] = ent->item - bg_itemlist;
02107 
02108         other->client->ps.stats[STAT_HOLDABLE_ITEMS] |= (1 << ent->item->giTag);
02109 
02110         G_LogWeaponItem(other->s.number, ent->item->giTag);
02111 
02112         return adjustRespawnTime(RESPAWN_HOLDABLE, ent->item->giType, ent->item->giTag);
02113 }
02114 
02115 
02116 //======================================================================
02117 
02118 void Add_Ammo (gentity_t *ent, int weapon, int count)
02119 {
02120         if ( ent->client->ps.ammo[weapon] < ammoData[weapon].max )
02121         {
02122                 ent->client->ps.ammo[weapon] += count;
02123                 if ( ent->client->ps.ammo[weapon] > ammoData[weapon].max )
02124                 {
02125                         ent->client->ps.ammo[weapon] = ammoData[weapon].max;
02126                 }
02127         }
02128 }
02129 
02130 int Pickup_Ammo (gentity_t *ent, gentity_t *other)
02131 {
02132         int             quantity;
02133 
02134         if ( ent->count ) {
02135                 quantity = ent->count;
02136         } else {
02137                 quantity = ent->item->quantity;
02138         }
02139 
02140         if (ent->item->giTag == -1)
02141         { //an ammo_all, give them a bit of everything
02142                 if ( g_gametype.integer == GT_SIEGE )   // complaints that siege tech's not giving enough ammo.  Does anything else use ammo all?
02143                 {
02144                         Add_Ammo(other, AMMO_BLASTER, 100);
02145                         Add_Ammo(other, AMMO_POWERCELL, 100);
02146                         Add_Ammo(other, AMMO_METAL_BOLTS, 100);
02147                         Add_Ammo(other, AMMO_ROCKETS, 5);
02148                         if (other->client->ps.stats[STAT_WEAPONS] & (1<<WP_DET_PACK))
02149                         {
02150                                 Add_Ammo(other, AMMO_DETPACK, 2);
02151                         }
02152                         if (other->client->ps.stats[STAT_WEAPONS] & (1<<WP_THERMAL))
02153                         {
02154                                 Add_Ammo(other, AMMO_THERMAL, 2);
02155                         }
02156                         if (other->client->ps.stats[STAT_WEAPONS] & (1<<WP_TRIP_MINE))
02157                         {
02158                                 Add_Ammo(other, AMMO_TRIPMINE, 2);
02159                         }
02160                 }
02161                 else
02162                 {
02163                         Add_Ammo(other, AMMO_BLASTER, 50);
02164                         Add_Ammo(other, AMMO_POWERCELL, 50);
02165                         Add_Ammo(other, AMMO_METAL_BOLTS, 50);
02166                         Add_Ammo(other, AMMO_ROCKETS, 2);
02167                 }
02168         }
02169         else
02170         {
02171                 Add_Ammo (other, ent->item->giTag, quantity);
02172         }
02173 
02174         return adjustRespawnTime(RESPAWN_AMMO, ent->item->giType, ent->item->giTag);
02175 }
02176 
02177 //======================================================================
02178 
02179 
02180 int Pickup_Weapon (gentity_t *ent, gentity_t *other) {
02181         int             quantity;
02182 
02183         if ( ent->count < 0 ) {
02184                 quantity = 0; // None for you, sir!
02185         } else {
02186                 if ( ent->count ) {
02187                         quantity = ent->count;
02188                 } else {
02189                         quantity = ent->item->quantity;
02190                 }
02191 
02192                 // dropped items and teamplay weapons always have full ammo
02193                 if ( ! (ent->flags & FL_DROPPED_ITEM) && g_gametype.integer != GT_TEAM ) {
02194                         // respawning rules
02195 
02196                         // New method:  If the player has less than half the minimum, give them the minimum, else add 1/2 the min.
02197 
02198                         // drop the quantity if the already have over the minimum
02199                         if ( other->client->ps.ammo[ ent->item->giTag ] < quantity*0.5 ) {
02200                                 quantity = quantity - other->client->ps.ammo[ ent->item->giTag ];
02201                         } else {
02202                                 quantity = quantity*0.5;                // only add half the value.
02203                         }
02204 
02205                         // Old method:  If the player has less than the minimum, give them the minimum, else just add 1.
02206 /*
02207                         // drop the quantity if the already have over the minimum
02208                         if ( other->client->ps.ammo[ ent->item->giTag ] < quantity ) {
02209                                 quantity = quantity - other->client->ps.ammo[ ent->item->giTag ];
02210                         } else {
02211                                 quantity = 1;           // only add a single shot
02212                         }
02213                         */
02214                 }
02215         }
02216 
02217         // add the weapon
02218         other->client->ps.stats[STAT_WEAPONS] |= ( 1 << ent->item->giTag );
02219 
02220         //Add_Ammo( other, ent->item->giTag, quantity );
02221         Add_Ammo( other, weaponData[ent->item->giTag].ammoIndex, quantity );
02222 
02223         G_LogWeaponPickup(other->s.number, ent->item->giTag);
02224         
02225         // team deathmatch has slow weapon respawns
02226         if ( g_gametype.integer == GT_TEAM ) 
02227         {
02228                 return adjustRespawnTime(RESPAWN_TEAM_WEAPON, ent->item->giType, ent->item->giTag);
02229         }
02230 
02231         return adjustRespawnTime(g_weaponRespawn.integer, ent->item->giType, ent->item->giTag);
02232 }
02233 
02234 
02235 //======================================================================
02236 
02237 int Pickup_Health (gentity_t *ent, gentity_t *other) {
02238         int                     max;
02239         int                     quantity;
02240 
02241         // small and mega healths will go over the max
02242         if ( ent->item->quantity != 5 && ent->item->quantity != 100 ) {
02243                 max = other->client->ps.stats[STAT_MAX_HEALTH];
02244         } else {
02245                 max = other->client->ps.stats[STAT_MAX_HEALTH] * 2;
02246         }
02247 
02248         if ( ent->count ) {
02249                 quantity = ent->count;
02250         } else {
02251                 quantity = ent->item->quantity;
02252         }
02253 
02254         other->health += quantity;
02255 
02256         if (other->health > max ) {
02257                 other->health = max;
02258         }
02259         other->client->ps.stats[STAT_HEALTH] = other->health;
02260 
02261         if ( ent->item->quantity == 100 ) {             // mega health respawns slow
02262                 return RESPAWN_MEGAHEALTH;
02263         }
02264 
02265         return adjustRespawnTime(RESPAWN_HEALTH, ent->item->giType, ent->item->giTag);
02266 }
02267 
02268 //======================================================================
02269 
02270 int Pickup_Armor( gentity_t *ent, gentity_t *other ) 
02271 {
02272         other->client->ps.stats[STAT_ARMOR] += ent->item->quantity;
02273         if ( other->client->ps.stats[STAT_ARMOR] > other->client->ps.stats[STAT_MAX_HEALTH] * ent->item->giTag ) 
02274         {
02275                 other->client->ps.stats[STAT_ARMOR] = other->client->ps.stats[STAT_MAX_HEALTH] * ent->item->giTag;
02276         }
02277 
02278         return adjustRespawnTime(RESPAWN_ARMOR, ent->item->giType, ent->item->giTag);
02279 }
02280 
02281 //======================================================================
02282 
02283 /*
02284 ===============
02285 RespawnItem
02286 ===============
02287 */
02288 void RespawnItem( gentity_t *ent ) {
02289         // randomly select from teamed entities
02290         if (ent->team) {
02291                 gentity_t       *master;
02292                 int     count;
02293                 int choice;
02294 
02295                 if ( !ent->teammaster ) {
02296                         G_Error( "RespawnItem: bad teammaster");
02297                 }
02298                 master = ent->teammaster;
02299 
02300                 for (count = 0, ent = master; ent; ent = ent->teamchain, count++)
02301                         ;
02302 
02303                 choice = rand() % count;
02304 
02305                 for (count = 0, ent = master; count < choice; ent = ent->teamchain, count++)
02306                         ;
02307         }
02308 
02309         ent->r.contents = CONTENTS_TRIGGER;
02310         //ent->s.eFlags &= ~EF_NODRAW;
02311         ent->s.eFlags &= ~(EF_NODRAW | EF_ITEMPLACEHOLDER);
02312         ent->r.svFlags &= ~SVF_NOCLIENT;
02313         trap_LinkEntity (ent);
02314 
02315         if ( ent->item->giType == IT_POWERUP ) {
02316                 // play powerup spawn sound to all clients
02317                 gentity_t       *te;
02318 
02319                 // if the powerup respawn sound should Not be global
02320                 if (ent->speed) {
02321                         te = G_TempEntity( ent->s.pos.trBase, EV_GENERAL_SOUND );
02322                 }
02323                 else {
02324                         te = G_TempEntity( ent->s.pos.trBase, EV_GLOBAL_SOUND );
02325                 }
02326                 te->s.eventParm = G_SoundIndex( "sound/items/respawn1" );
02327                 te->r.svFlags |= SVF_BROADCAST;
02328         }
02329 
02330         // play the normal respawn sound only to nearby clients
02331         G_AddEvent( ent, EV_ITEM_RESPAWN, 0 );
02332 
02333         ent->nextthink = 0;
02334 }
02335 
02336 qboolean CheckItemCanBePickedUpByNPC( gentity_t *item, gentity_t *pickerupper )
02337 {
02338         if ( (item->flags&FL_DROPPED_ITEM) 
02339                 && item->activator != &g_entities[0] 
02340                 && pickerupper->s.number 
02341                 && pickerupper->s.weapon == WP_NONE 
02342                 && pickerupper->enemy 
02343                 && pickerupper->painDebounceTime < level.time
02344                 && pickerupper->NPC && pickerupper->NPC->surrenderTime < level.time //not surrendering
02345                 && !(pickerupper->NPC->scriptFlags&SCF_FORCED_MARCH) //not being forced to march
02346                 /*&& item->item->giTag != INV_SECURITY_KEY*/ )
02347         {//non-player, in combat, picking up a dropped item that does NOT belong to the player and it *not* a security key
02348                 if ( level.time - item->s.time < 3000 )//was 5000
02349                 {
02350                         return qfalse;
02351                 }
02352                 return qtrue;
02353         }
02354         return qfalse;
02355 }
02356 
02357 /*
02358 ===============
02359 Touch_Item
02360 ===============
02361 */
02362 void Touch_Item (gentity_t *ent, gentity_t *other, trace_t *trace) {
02363         int                     respawn;
02364         qboolean        predict;
02365 
02366         if (ent->genericValue10 > level.time &&
02367                 other &&
02368                 other->s.number == ent->genericValue11)
02369         { //this is the ent that we don't want to be able to touch us for x seconds
02370                 return;
02371         }
02372 
02373         if (ent->s.eFlags & EF_ITEMPLACEHOLDER)
02374         {
02375                 return;
02376         }
02377 
02378         if (ent->s.eFlags & EF_NODRAW)
02379         {
02380                 return;
02381         }
02382 
02383         if (ent->item->giType == IT_WEAPON &&
02384                 ent->s.powerups &&
02385                 ent->s.powerups < level.time)
02386         {
02387                 ent->s.generic1 = 0;
02388                 ent->s.powerups = 0;
02389         }
02390 
02391         if (!other->client)
02392                 return;
02393         if (other->health < 1)
02394                 return;         // dead people can't pickup
02395 
02396         if (ent->item->giType == IT_POWERUP &&
02397                 (ent->item->giTag == PW_FORCE_ENLIGHTENED_LIGHT || ent->item->giTag == PW_FORCE_ENLIGHTENED_DARK))
02398         {
02399                 if (ent->item->giTag == PW_FORCE_ENLIGHTENED_LIGHT)
02400                 {
02401                         if (other->client->ps.fd.forceSide != FORCE_LIGHTSIDE)
02402                         {
02403                                 return;
02404                         }
02405                 }
02406                 else
02407                 {
02408                         if (other->client->ps.fd.forceSide != FORCE_DARKSIDE)
02409                         {
02410                                 return;
02411                         }
02412                 }
02413         }
02414 
02415         // the same pickup rules are used for client side and server side
02416         if ( !BG_CanItemBeGrabbed( g_gametype.integer, &ent->s, &other->client->ps ) ) {
02417                 return;
02418         }
02419 
02420         
02421         if ( other->client->NPC_class == CLASS_ATST || 
02422                 other->client->NPC_class == CLASS_GONK || 
02423                 other->client->NPC_class == CLASS_MARK1 || 
02424                 other->client->NPC_class == CLASS_MARK2 || 
02425                 other->client->NPC_class == CLASS_MOUSE || 
02426                 other->client->NPC_class == CLASS_PROBE || 
02427                 other->client->NPC_class == CLASS_PROTOCOL || 
02428                 other->client->NPC_class == CLASS_R2D2 || 
02429                 other->client->NPC_class == CLASS_R5D2 || 
02430                 other->client->NPC_class == CLASS_SEEKER || 
02431                 other->client->NPC_class == CLASS_REMOTE || 
02432                 other->client->NPC_class == CLASS_RANCOR || 
02433                 other->client->NPC_class == CLASS_WAMPA || 
02434                 //other->client->NPC_class == CLASS_JAWA || //FIXME: in some cases it's okay?
02435                 other->client->NPC_class == CLASS_UGNAUGHT || //FIXME: in some cases it's okay?
02436                 other->client->NPC_class == CLASS_SENTRY )
02437         {//FIXME: some flag would be better
02438                 //droids can't pick up items/weapons!
02439                 return;
02440         }
02441 
02442         if ( CheckItemCanBePickedUpByNPC( ent, other ) )
02443         {
02444                 if ( other->NPC && other->NPC->goalEntity && other->NPC->goalEntity->enemy == ent )
02445                 {//they were running to pick me up, they did, so clear goal
02446                         other->NPC->goalEntity = NULL;
02447                         other->NPC->squadState = SQUAD_STAND_AND_SHOOT;
02448                 }
02449         }
02450         else if ( !(ent->spawnflags &  ITMSF_ALLOWNPC) )
02451         {// NPCs cannot pick it up
02452                 if ( other->s.eType == ET_NPC )
02453                 {// Not the player?
02454                         qboolean dontGo = qfalse;
02455                         if (ent->item->giType == IT_AMMO &&
02456                                 ent->item->giTag == -1 &&
02457                                 other->s.NPC_class == CLASS_VEHICLE &&
02458                                 other->m_pVehicle &&
02459                                 other->m_pVehicle->m_pVehicleInfo->type == VH_WALKER)
02460                         { //yeah, uh, atst gets healed by these things
02461                 if (other->maxHealth &&
02462                                         other->health < other->maxHealth)
02463                                 {
02464                                         other->health += 80;
02465                                         if (other->health > other->maxHealth)
02466                                         {
02467                                                 other->health = other->maxHealth;
02468                                         }
02469                                         G_ScaleNetHealth(other);
02470                                         dontGo = qtrue;
02471                                 }
02472                         }
02473 
02474                         if (!dontGo)
02475                         {
02476                                 return;
02477                         }
02478                 }
02479         }
02480 
02481         G_LogPrintf( "Item: %i %s\n", other->s.number, ent->item->classname );
02482 
02483         predict = other->client->pers.predictItemPickup;
02484 
02485         // call the item-specific pickup function
02486         switch( ent->item->giType ) {
02487         case IT_WEAPON:
02488                 respawn = Pickup_Weapon(ent, other);
02489 //              predict = qfalse;
02490                 predict = qtrue;
02491                 break;
02492         case IT_AMMO:
02493                 respawn = Pickup_Ammo(ent, other);
02494                 if (ent->item->giTag == AMMO_THERMAL || ent->item->giTag == AMMO_TRIPMINE || ent->item->giTag == AMMO_DETPACK)
02495                 {
02496                         int weapForAmmo = 0;
02497 
02498                         if (ent->item->giTag == AMMO_THERMAL)
02499                         {
02500                                 weapForAmmo = WP_THERMAL;
02501                         }
02502                         else if (ent->item->giTag == AMMO_TRIPMINE)
02503                         {
02504                                 weapForAmmo = WP_TRIP_MINE;
02505                         }
02506                         else
02507                         {
02508                                 weapForAmmo = WP_DET_PACK;
02509                         }
02510 
02511                         if (other && other->client && other->client->ps.ammo[weaponData[weapForAmmo].ammoIndex] > 0 )
02512                         {
02513                                 other->client->ps.stats[STAT_WEAPONS] |= (1 << weapForAmmo);
02514                         }
02515                 }
02516 //              predict = qfalse;
02517                 predict = qtrue;
02518                 break;
02519         case IT_ARMOR:
02520                 respawn = Pickup_Armor(ent, other);
02521 //              predict = qfalse;
02522                 predict = qtrue;
02523                 break;
02524         case IT_HEALTH:
02525                 respawn = Pickup_Health(ent, other);
02526 //              predict = qfalse;
02527                 predict = qtrue;
02528                 break;
02529         case IT_POWERUP:
02530                 respawn = Pickup_Powerup(ent, other);
02531                 predict = qfalse;
02532 //              predict = qtrue;
02533                 break;
02534         case IT_TEAM:
02535                 respawn = Pickup_Team(ent, other);
02536                 break;
02537         case IT_HOLDABLE:
02538                 respawn = Pickup_Holdable(ent, other);
02539                 break;
02540         default:
02541                 return;
02542         }
02543 
02544         if ( !respawn ) {
02545                 return;
02546         }
02547 
02548         // play the normal pickup sound
02549         if (predict) {
02550                 if (other->client)
02551                 {
02552                         BG_AddPredictableEventToPlayerstate( EV_ITEM_PICKUP, ent->s.number, &other->client->ps);
02553                 }
02554                 else
02555                 {
02556                         G_AddPredictableEvent( other, EV_ITEM_PICKUP, ent->s.number );
02557                 }
02558         } else {
02559                 G_AddEvent( other, EV_ITEM_PICKUP, ent->s.number );
02560         }
02561 
02562         // powerup pickups are global broadcasts
02563         if ( /*ent->item->giType == IT_POWERUP ||*/ ent->item->giType == IT_TEAM) {
02564                 // if we want the global sound to play
02565                 if (!ent->speed) {
02566                         gentity_t       *te;
02567 
02568                         te = G_TempEntity( ent->s.pos.trBase, EV_GLOBAL_ITEM_PICKUP );
02569                         te->s.eventParm = ent->s.modelindex;
02570                         te->r.svFlags |= SVF_BROADCAST;
02571                 } else {
02572                         gentity_t       *te;
02573 
02574                         te = G_TempEntity( ent->s.pos.trBase, EV_GLOBAL_ITEM_PICKUP );
02575                         te->s.eventParm = ent->s.modelindex;
02576                         // only send this temp entity to a single client
02577                         te->r.svFlags |= SVF_SINGLECLIENT;
02578                         te->r.singleClient = other->s.number;
02579                 }
02580         }
02581 
02582         // fire item targets
02583         G_UseTargets (ent, other);
02584 
02585         // wait of -1 will not respawn
02586         if ( ent->wait == -1 ) {
02587                 ent->r.svFlags |= SVF_NOCLIENT;
02588                 ent->s.eFlags |= EF_NODRAW;
02589                 ent->r.contents = 0;
02590                 ent->unlinkAfterEvent = qtrue;
02591                 return;
02592         }
02593 
02594         // non zero wait overrides respawn time
02595         if ( ent->wait ) {
02596                 respawn = ent->wait;
02597         }
02598 
02599         // random can be used to vary the respawn time
02600         if ( ent->random ) {
02601                 respawn += crandom() * ent->random;
02602                 if ( respawn < 1 ) {
02603                         respawn = 1;
02604                 }
02605         }
02606 
02607         // dropped items will not respawn
02608         if ( ent->flags & FL_DROPPED_ITEM ) {
02609                 ent->freeAfterEvent = qtrue;
02610         }
02611 
02612         // picked up items still stay around, they just don't
02613         // draw anything.  This allows respawnable items
02614         // to be placed on movers.
02615         if (!(ent->flags & FL_DROPPED_ITEM) && (ent->item->giType==IT_WEAPON || ent->item->giType==IT_POWERUP))
02616         {
02617                 ent->s.eFlags |= EF_ITEMPLACEHOLDER;
02618                 ent->s.eFlags &= ~EF_NODRAW;
02619         }
02620         else
02621         {
02622                 ent->s.eFlags |= EF_NODRAW;
02623                 ent->r.svFlags |= SVF_NOCLIENT;
02624         }
02625         ent->r.contents = 0;
02626 
02627         if (ent->genericValue9)
02628         { //dropped item, should be removed when picked up
02629                 ent->think = G_FreeEntity;
02630                 ent->nextthink = level.time;
02631                 return;
02632         }
02633 
02634         // ZOID
02635         // A negative respawn times means to never respawn this item (but don't 
02636         // delete it).  This is used by items that are respawned by third party 
02637         // events such as ctf flags
02638         if ( respawn <= 0 ) {
02639                 ent->nextthink = 0;
02640                 ent->think = 0;
02641         } else {
02642                 ent->nextthink = level.time + respawn * 1000;
02643                 ent->think = RespawnItem;
02644         }
02645         trap_LinkEntity( ent );
02646 }
02647 
02648 
02649 //======================================================================
02650 
02651 /*
02652 ================
02653 LaunchItem
02654 
02655 Spawns an item and tosses it forward
02656 ================
02657 */
02658 gentity_t *LaunchItem( gitem_t *item, vec3_t origin, vec3_t velocity ) {
02659         gentity_t       *dropped;
02660 
02661         dropped = G_Spawn();
02662 
02663         dropped->s.eType = ET_ITEM;
02664         dropped->s.modelindex = item - bg_itemlist;     // store item number in modelindex
02665         if (dropped->s.modelindex < 0)
02666         {
02667                 dropped->s.modelindex = 0;
02668         }
02669         dropped->s.modelindex2 = 1; // This is non-zero is it's a dropped item
02670 
02671         dropped->classname = item->classname;
02672         dropped->item = item;
02673         VectorSet (dropped->r.mins, -ITEM_RADIUS, -ITEM_RADIUS, -ITEM_RADIUS);
02674         VectorSet (dropped->r.maxs, ITEM_RADIUS, ITEM_RADIUS, ITEM_RADIUS);
02675 
02676         dropped->r.contents = CONTENTS_TRIGGER;
02677 
02678         dropped->touch = Touch_Item;
02679 
02680         G_SetOrigin( dropped, origin );
02681         dropped->s.pos.trType = TR_GRAVITY;
02682         dropped->s.pos.trTime = level.time;
02683         VectorCopy( velocity, dropped->s.pos.trDelta );
02684 
02685         dropped->flags |= FL_BOUNCE_HALF;
02686         if ((g_gametype.integer == GT_CTF || g_gametype.integer == GT_CTY) && item->giType == IT_TEAM) { // Special case for CTF flags
02687                 dropped->think = Team_DroppedFlagThink;
02688                 dropped->nextthink = level.time + 30000;
02689                 Team_CheckDroppedItem( dropped );
02690 
02691                 //rww - so bots know
02692                 if (strcmp(dropped->classname, "team_CTF_redflag") == 0)
02693                 {
02694                         droppedRedFlag = dropped;
02695                 }
02696                 else if (strcmp(dropped->classname, "team_CTF_blueflag") == 0)
02697                 {
02698                         droppedBlueFlag = dropped;
02699                 }
02700         } else { // auto-remove after 30 seconds
02701                 dropped->think = G_FreeEntity;
02702                 dropped->nextthink = level.time + 30000;
02703         }
02704 
02705         dropped->flags = FL_DROPPED_ITEM;
02706 
02707         if (item->giType == IT_WEAPON || item->giType == IT_POWERUP)
02708         {
02709                 dropped->s.eFlags |= EF_DROPPEDWEAPON;
02710         }
02711 
02712         vectoangles(velocity, dropped->s.angles);
02713         dropped->s.angles[PITCH] = 0;
02714 
02715         if (item->giTag == WP_TRIP_MINE ||
02716                 item->giTag == WP_DET_PACK)
02717         {
02718                 dropped->s.angles[PITCH] = -90;
02719         }
02720 
02721         if (item->giTag != WP_BOWCASTER &&
02722                 item->giTag != WP_DET_PACK &&
02723                 item->giTag != WP_THERMAL)
02724         {
02725                 dropped->s.angles[ROLL] = -90;
02726         }
02727 
02728         dropped->physicsObject = qtrue;
02729 
02730         trap_LinkEntity (dropped);
02731 
02732         return dropped;
02733 }
02734 
02735 /*
02736 ================
02737 Drop_Item
02738 
02739 Spawns an item and tosses it forward
02740 ================
02741 */
02742 gentity_t *Drop_Item( gentity_t *ent, gitem_t *item, float angle ) {
02743         vec3_t  velocity;
02744         vec3_t  angles;
02745 
02746         VectorCopy( ent->s.apos.trBase, angles );
02747         angles[YAW] += angle;
02748         angles[PITCH] = 0;      // always forward
02749 
02750         AngleVectors( angles, velocity, NULL, NULL );
02751         VectorScale( velocity, 150, velocity );
02752         velocity[2] += 200 + crandom() * 50;
02753         
02754         return LaunchItem( item, ent->s.pos.trBase, velocity );
02755 }
02756 
02757 
02758 /*
02759 ================
02760 Use_Item
02761 
02762 Respawn the item
02763 ================
02764 */
02765 void Use_Item( gentity_t *ent, gentity_t *other, gentity_t *activator ) {
02766         RespawnItem( ent );
02767 }
02768 
02769 //======================================================================
02770 
02771 /*
02772 ================
02773 FinishSpawningItem
02774 
02775 Traces down to find where an item should rest, instead of letting them
02776 free fall from their spawn points
02777 ================
02778 */
02779 void FinishSpawningItem( gentity_t *ent ) {
02780         trace_t         tr;
02781         vec3_t          dest;
02782 //      gitem_t         *item;
02783 
02784 //      VectorSet( ent->r.mins, -ITEM_RADIUS, -ITEM_RADIUS, -ITEM_RADIUS );
02785 //      VectorSet( ent->r.maxs, ITEM_RADIUS, ITEM_RADIUS, ITEM_RADIUS );
02786 
02787         if (g_gametype.integer == GT_SIEGE)
02788         { //in siege remove all powerups
02789                 if (ent->item->giType == IT_POWERUP)
02790                 {
02791                         G_FreeEntity(ent);
02792                         return;
02793                 }
02794         }
02795 
02796         if (g_gametype.integer != GT_JEDIMASTER)
02797         {
02798                 if (HasSetSaberOnly())
02799                 {
02800                         if (ent->item->giType == IT_AMMO)
02801                         {
02802                                 G_FreeEntity( ent );
02803                                 return;
02804                         }
02805 
02806                         if (ent->item->giType == IT_HOLDABLE)
02807                         {
02808                                 if (ent->item->giTag == HI_SEEKER ||
02809                                         ent->item->giTag == HI_SHIELD ||
02810                                         ent->item->giTag == HI_SENTRY_GUN)
02811                                 {
02812                                         G_FreeEntity( ent );
02813                                         return;
02814                                 }
02815                         }
02816                 }
02817         }
02818         else
02819         { //no powerups in jedi master
02820                 if (ent->item->giType == IT_POWERUP)
02821                 {
02822                         G_FreeEntity(ent);
02823                         return;
02824                 }
02825         }
02826 
02827         if (g_gametype.integer == GT_HOLOCRON)
02828         {
02829                 if (ent->item->giType == IT_POWERUP)
02830                 {
02831                         if (ent->item->giTag == PW_FORCE_ENLIGHTENED_LIGHT ||
02832                                 ent->item->giTag == PW_FORCE_ENLIGHTENED_DARK)
02833                         {
02834                                 G_FreeEntity(ent);
02835                                 return;
02836                         }
02837                 }
02838         }
02839 
02840         if (g_forcePowerDisable.integer)
02841         { //if force powers disabled, don't add force powerups
02842                 if (ent->item->giType == IT_POWERUP)
02843                 {
02844                         if (ent->item->giTag == PW_FORCE_ENLIGHTENED_LIGHT ||
02845                                 ent->item->giTag == PW_FORCE_ENLIGHTENED_DARK ||
02846                                 ent->item->giTag == PW_FORCE_BOON)
02847                         {
02848                                 G_FreeEntity(ent);
02849                                 return;
02850                         }
02851                 }
02852         }
02853 
02854         if (g_gametype.integer == GT_DUEL || g_gametype.integer == GT_POWERDUEL)
02855         {
02856                 if ( ent->item->giType == IT_ARMOR ||
02857                         ent->item->giType == IT_HEALTH ||
02858                         (ent->item->giType == IT_HOLDABLE && (ent->item->giTag == HI_MEDPAC || ent->item->giTag == HI_MEDPAC_BIG)) )
02859                 {
02860                         G_FreeEntity(ent);
02861                         return;
02862                 }
02863         }
02864 
02865         if (g_gametype.integer != GT_CTF &&
02866                 g_gametype.integer != GT_CTY &&
02867                 ent->item->giType == IT_TEAM)
02868         {
02869                 int killMe = 0;
02870 
02871                 switch (ent->item->giTag)
02872                 {
02873                 case PW_REDFLAG:
02874                         killMe = 1;
02875                         break;
02876                 case PW_BLUEFLAG:
02877                         killMe = 1;
02878                         break;
02879                 case PW_NEUTRALFLAG:
02880                         killMe = 1;
02881                         break;
02882                 default:
02883                         break;
02884                 }
02885 
02886                 if (killMe)
02887                 {
02888                         G_FreeEntity( ent );
02889                         return;
02890                 }
02891         }
02892 
02893         VectorSet (ent->r.mins, -8, -8, -0);
02894         VectorSet (ent->r.maxs, 8, 8, 16);
02895 
02896         ent->s.eType = ET_ITEM;
02897         ent->s.modelindex = ent->item - bg_itemlist;            // store item number in modelindex
02898         ent->s.modelindex2 = 0; // zero indicates this isn't a dropped item
02899 
02900         ent->r.contents = CONTENTS_TRIGGER;
02901         ent->touch = Touch_Item;
02902         // useing an item causes it to respawn
02903         ent->use = Use_Item;
02904 
02905         // create a Ghoul2 model if the world model is a glm
02906 /*      item = &bg_itemlist[ ent->s.modelindex ];
02907         if (!stricmp(&item->world_model[0][strlen(item->world_model[0]) - 4], ".glm"))
02908         {
02909                 trap_G2API_InitGhoul2Model(&ent->s, item->world_model[0], G_ModelIndex(item->world_model[0] ), 0, 0, 0, 0);
02910                 ent->s.radius = 60;
02911         }
02912 */
02913         if ( ent->spawnflags & ITMSF_SUSPEND ) {
02914                 // suspended
02915                 G_SetOrigin( ent, ent->s.origin );
02916         } else {
02917                 // drop to floor
02918 
02919                 //if it is directly even with the floor it will return startsolid, so raise up by 0.1
02920                 //and temporarily subtract 0.1 from the z maxs so that going up doesn't push into the ceiling
02921                 ent->s.origin[2] += 0.1;
02922                 ent->r.maxs[2] -= 0.1;
02923 
02924                 VectorSet( dest, ent->s.origin[0], ent->s.origin[1], ent->s.origin[2] - 4096 );
02925                 trap_Trace( &tr, ent->s.origin, ent->r.mins, ent->r.maxs, dest, ent->s.number, MASK_SOLID );
02926                 if ( tr.startsolid ) {
02927                         G_Printf ("FinishSpawningItem: %s startsolid at %s\n", ent->classname, vtos(ent->s.origin));
02928                         G_FreeEntity( ent );
02929                         return;
02930                 }
02931 
02932                 //add the 0.1 back after the trace
02933                 ent->r.maxs[2] += 0.1;
02934 
02935                 // allow to ride movers
02936                 ent->s.groundEntityNum = tr.entityNum;
02937 
02938                 G_SetOrigin( ent, tr.endpos );
02939         }
02940 
02941         // team slaves and targeted items aren't present at start
02942         if ( ( ent->flags & FL_TEAMSLAVE ) || ent->targetname ) {
02943                 ent->s.eFlags |= EF_NODRAW;
02944                 ent->r.contents = 0;
02945                 return;
02946         }
02947 
02948         // powerups don't spawn in for a while
02949         /*
02950         if ( ent->item->giType == IT_POWERUP ) {
02951                 float   respawn;
02952 
02953                 respawn = 45 + crandom() * 15;
02954                 ent->s.eFlags |= EF_NODRAW;
02955                 ent->r.contents = 0;
02956                 ent->nextthink = level.time + respawn * 1000;
02957                 ent->think = RespawnItem;
02958                 return;
02959         }
02960         */
02961 
02962         trap_LinkEntity (ent);
02963 }
02964 
02965 
02966 qboolean        itemRegistered[MAX_ITEMS];
02967 
02968 /*
02969 ==================
02970 G_CheckTeamItems
02971 ==================
02972 */
02973 void G_CheckTeamItems( void ) {
02974 
02975         // Set up team stuff
02976         Team_InitGame();
02977 
02978         if( g_gametype.integer == GT_CTF || g_gametype.integer == GT_CTY ) {
02979                 gitem_t *item;
02980 
02981                 // check for the two flags
02982                 item = BG_FindItem( "team_CTF_redflag" );
02983                 if ( !item || !itemRegistered[ item - bg_itemlist ] ) {
02984                         G_Printf( S_COLOR_YELLOW "WARNING: No team_CTF_redflag in map" );
02985                 }
02986                 item = BG_FindItem( "team_CTF_blueflag" );
02987                 if ( !item || !itemRegistered[ item - bg_itemlist ] ) {
02988                         G_Printf( S_COLOR_YELLOW "WARNING: No team_CTF_blueflag in map" );
02989                 }
02990         }
02991 }
02992 
02993 /*
02994 ==============
02995 ClearRegisteredItems
02996 ==============
02997 */
02998 void ClearRegisteredItems( void ) {
02999         memset( itemRegistered, 0, sizeof( itemRegistered ) );
03000 
03001         // players always start with the base weapon
03002         RegisterItem( BG_FindItemForWeapon( WP_BRYAR_PISTOL ) );
03003         RegisterItem( BG_FindItemForWeapon( WP_STUN_BATON ) );
03004         RegisterItem( BG_FindItemForWeapon( WP_MELEE ) );
03005         RegisterItem( BG_FindItemForWeapon( WP_SABER ) );
03006 
03007         if (g_gametype.integer == GT_SIEGE)
03008         { //kind of cheesy, maybe check if siege class with disp's is gonna be on this map too
03009                 G_PrecacheDispensers();
03010         }
03011 }
03012 
03013 /*
03014 ===============
03015 RegisterItem
03016 
03017 The item will be added to the precache list
03018 ===============
03019 */
03020 void RegisterItem( gitem_t *item ) {
03021         if ( !item ) {
03022                 G_Error( "RegisterItem: NULL" );
03023         }
03024         itemRegistered[ item - bg_itemlist ] = qtrue;
03025 }
03026 
03027 
03028 /*
03029 ===============
03030 SaveRegisteredItems
03031 
03032 Write the needed items to a config string
03033 so the client will know which ones to precache
03034 ===============
03035 */
03036 void SaveRegisteredItems( void ) {
03037         char    string[MAX_ITEMS+1];
03038         int             i;
03039         int             count;
03040 
03041         count = 0;
03042         for ( i = 0 ; i < bg_numItems ; i++ ) {
03043                 if ( itemRegistered[i] ) {
03044                         count++;
03045                         string[i] = '1';
03046                 } else {
03047                         string[i] = '0';
03048                 }
03049         }
03050         string[ bg_numItems ] = 0;
03051 
03052 //      G_Printf( "%i items registered\n", count );
03053         trap_SetConfigstring(CS_ITEMS, string);
03054 }
03055 
03056 /*
03057 ============
03058 G_ItemDisabled
03059 ============
03060 */
03061 int G_ItemDisabled( gitem_t *item ) {
03062 
03063         char name[128];
03064 
03065         Com_sprintf(name, sizeof(name), "disable_%s", item->classname);
03066         return trap_Cvar_VariableIntegerValue( name );
03067 }
03068 
03069 /*
03070 ============
03071 G_SpawnItem
03072 
03073 Sets the clipping size and plants the object on the floor.
03074 
03075 Items can't be immediately dropped to floor, because they might
03076 be on an entity that hasn't spawned yet.
03077 ============
03078 */
03079 void G_SpawnItem (gentity_t *ent, gitem_t *item) {
03080         int wDisable = 0;
03081 
03082         G_SpawnFloat( "random", "0", &ent->random );
03083         G_SpawnFloat( "wait", "0", &ent->wait );
03084 
03085         if (g_gametype.integer == GT_DUEL || g_gametype.integer == GT_POWERDUEL)
03086         {
03087                 wDisable = g_duelWeaponDisable.integer;
03088         }
03089         else
03090         {
03091                 wDisable = g_weaponDisable.integer;
03092         }
03093 
03094         if (item->giType == IT_WEAPON &&
03095                 wDisable &&
03096                 (wDisable & (1 << item->giTag)))
03097         {
03098                 if (g_gametype.integer != GT_JEDIMASTER)
03099                 {
03100                         G_FreeEntity( ent );
03101                         return;
03102                 }
03103         }
03104 
03105         RegisterItem( item );
03106         if ( G_ItemDisabled(item) )
03107                 return;
03108 
03109         ent->item = item;
03110         // some movers spawn on the second frame, so delay item
03111         // spawns until the third frame so they can ride trains
03112         ent->nextthink = level.time + FRAMETIME * 2;
03113         ent->think = FinishSpawningItem;
03114 
03115         ent->physicsBounce = 0.50;              // items are bouncy
03116 
03117         if ( item->giType == IT_POWERUP ) {
03118                 G_SoundIndex( "sound/items/respawn1" );
03119                 G_SpawnFloat( "noglobalsound", "0", &ent->speed);
03120         }
03121 }
03122 
03123 
03124 /*
03125 ================
03126 G_BounceItem
03127 
03128 ================
03129 */
03130 void G_BounceItem( gentity_t *ent, trace_t *trace ) {
03131         vec3_t  velocity;
03132         float   dot;
03133         int             hitTime;
03134 
03135         // reflect the velocity on the trace plane
03136         hitTime = level.previousTime + ( level.time - level.previousTime ) * trace->fraction;
03137         BG_EvaluateTrajectoryDelta( &ent->s.pos, hitTime, velocity );
03138         dot = DotProduct( velocity, trace->plane.normal );
03139         VectorMA( velocity, -2*dot, trace->plane.normal, ent->s.pos.trDelta );
03140 
03141         // cut the velocity to keep from bouncing forever
03142         VectorScale( ent->s.pos.trDelta, ent->physicsBounce, ent->s.pos.trDelta );
03143 
03144         if ((ent->s.weapon == WP_DET_PACK && ent->s.eType == ET_GENERAL && ent->physicsObject))
03145         { //detpacks only
03146                 if (ent->touch)
03147                 {
03148                         ent->touch(ent, &g_entities[trace->entityNum], trace);
03149                         return;
03150                 }
03151         }
03152 
03153         // check for stop
03154         if ( trace->plane.normal[2] > 0 && ent->s.pos.trDelta[2] < 40 ) {
03155                 trace->endpos[2] += 1.0;        // make sure it is off ground
03156                 SnapVector( trace->endpos );
03157                 G_SetOrigin( ent, trace->endpos );
03158                 ent->s.groundEntityNum = trace->entityNum;
03159                 return;
03160         }
03161 
03162         VectorAdd( ent->r.currentOrigin, trace->plane.normal, ent->r.currentOrigin);
03163         VectorCopy( ent->r.currentOrigin, ent->s.pos.trBase );
03164         ent->s.pos.trTime = level.time;
03165 
03166         if (ent->s.eType == ET_HOLOCRON ||
03167                 (ent->s.shouldtarget && ent->s.eType == ET_GENERAL && ent->physicsObject))
03168         { //holocrons and sentry guns
03169                 if (ent->touch)
03170                 {
03171                         ent->touch(ent, &g_entities[trace->entityNum], trace);
03172                 }
03173         }
03174 }
03175 
03176 
03177 /*
03178 ================
03179 G_RunItem
03180 
03181 ================
03182 */
03183 void G_RunItem( gentity_t *ent ) {
03184         vec3_t          origin;
03185         trace_t         tr;
03186         int                     contents;
03187         int                     mask;
03188 
03189         // if groundentity has been set to -1, it may have been pushed off an edge
03190         if ( ent->s.groundEntityNum == -1 ) {
03191                 if ( ent->s.pos.trType != TR_GRAVITY ) {
03192                         ent->s.pos.trType = TR_GRAVITY;
03193                         ent->s.pos.trTime = level.time;
03194                 }
03195         }
03196 
03197         if ( ent->s.pos.trType == TR_STATIONARY ) {
03198                 // check think function
03199                 G_RunThink( ent );
03200                 return;
03201         }
03202 
03203         // get current position
03204         BG_EvaluateTrajectory( &ent->s.pos, level.time, origin );
03205 
03206         // trace a line from the previous position to the current position
03207         if ( ent->clipmask ) {
03208                 mask = ent->clipmask;
03209         } else {
03210                 mask = MASK_PLAYERSOLID & ~CONTENTS_BODY;//MASK_SOLID;
03211         }
03212         trap_Trace( &tr, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, origin, 
03213                 ent->r.ownerNum, mask );
03214 
03215         VectorCopy( tr.endpos, ent->r.currentOrigin );
03216 
03217         if ( tr.startsolid ) {
03218                 tr.fraction = 0;
03219         }
03220 
03221         trap_LinkEntity( ent ); // FIXME: avoid this for stationary?
03222 
03223         // check think function
03224         G_RunThink( ent );
03225 
03226         if ( tr.fraction == 1 ) {
03227                 return;
03228         }
03229 
03230         // if it is in a nodrop volume, remove it
03231         contents = trap_PointContents( ent->r.currentOrigin, -1 );
03232         if ( contents & CONTENTS_NODROP ) {
03233                 if (ent->item && ent->item->giType == IT_TEAM) {
03234                         Team_FreeEntity(ent);
03235                 } else {
03236                         G_FreeEntity( ent );
03237                 }
03238                 return;
03239         }
03240 
03241         G_BounceItem( ent, &tr );
03242 }
03243