codemp/game/g_turret_G2.c

Go to the documentation of this file.
00001 #include "g_local.h"
00002 #include "../ghoul2/G2.h"
00003 #include "q_shared.h"
00004 
00005 void G_SetEnemy( gentity_t *self, gentity_t *enemy );
00006 void finish_spawning_turretG2( gentity_t *base );
00007 void ObjectDie (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath );
00008 void turretG2_base_use( gentity_t *self, gentity_t *other, gentity_t *activator );
00009 
00010 
00011 #define SPF_TURRETG2_CANRESPAWN 4
00012 #define SPF_TURRETG2_TURBO              8
00013 #define SPF_TURRETG2_LEAD_ENEMY 16
00014 #define SPF_SHOWONRADAR                 32
00015 
00016 #define ARM_ANGLE_RANGE         60
00017 #define HEAD_ANGLE_RANGE        90
00018 
00019 #define name "models/map_objects/imp_mine/turret_canon.glm"
00020 #define name2 "models/map_objects/imp_mine/turret_damage.md3"
00021 #define name3 "models/map_objects/wedge/laser_cannon_model.glm"
00022 
00023 //special routine for tracking angles between client and server -rww
00024 void G2Tur_SetBoneAngles(gentity_t *ent, char *bone, vec3_t angles)
00025 {
00026 #ifdef _XBOX
00027         byte *thebone = &ent->s.boneIndex1;
00028         byte *firstFree = NULL;
00029 #else
00030         int *thebone = &ent->s.boneIndex1;
00031         int *firstFree = NULL;
00032 #endif
00033         int i = 0;
00034         int boneIndex = G_BoneIndex(bone);
00035         int flags, up, right, forward;
00036         vec3_t *boneVector = &ent->s.boneAngles1;
00037         vec3_t *freeBoneVec = NULL;
00038 
00039         while (thebone)
00040         {
00041                 if (!*thebone && !firstFree)
00042                 { //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.
00043                         firstFree = thebone;
00044                         freeBoneVec = boneVector;
00045                 }
00046                 else if (*thebone)
00047                 {
00048                         if (*thebone == boneIndex)
00049                         { //this is it
00050                                 break;
00051                         }
00052                 }
00053 
00054                 switch (i)
00055                 {
00056                 case 0:
00057                         thebone = &ent->s.boneIndex2;
00058                         boneVector = &ent->s.boneAngles2;
00059                         break;
00060                 case 1:
00061                         thebone = &ent->s.boneIndex3;
00062                         boneVector = &ent->s.boneAngles3;
00063                         break;
00064                 case 2:
00065                         thebone = &ent->s.boneIndex4;
00066                         boneVector = &ent->s.boneAngles4;
00067                         break;
00068                 default:
00069                         thebone = NULL;
00070                         boneVector = NULL;
00071                         break;
00072                 }
00073 
00074                 i++;
00075         }
00076 
00077         if (!thebone)
00078         { //didn't find it, create it
00079                 if (!firstFree)
00080                 { //no free bones.. can't do a thing then.
00081                         Com_Printf("WARNING: NPC has no free bone indexes\n");
00082                         return;
00083                 }
00084 
00085                 thebone = firstFree;
00086 
00087                 *thebone = boneIndex;
00088                 boneVector = freeBoneVec;
00089         }
00090 
00091         //If we got here then we have a vector and an index.
00092 
00093         //Copy the angles over the vector in the entitystate, so we can use the corresponding index
00094         //to set the bone angles on the client.
00095         VectorCopy(angles, *boneVector);
00096 
00097         //Now set the angles on our server instance if we have one.
00098 
00099         if (!ent->ghoul2)
00100         {
00101                 return;
00102         }
00103 
00104         flags = BONE_ANGLES_POSTMULT;
00105         up = POSITIVE_Y;
00106         right = NEGATIVE_Z;
00107         forward = NEGATIVE_X;
00108 
00109         //first 3 bits is forward, second 3 bits is right, third 3 bits is up
00110         ent->s.boneOrient = ((forward)|(right<<3)|(up<<6));
00111 
00112         trap_G2API_SetBoneAngles( ent->ghoul2,
00113                                         0,
00114                                         bone,
00115                                         angles, 
00116                                         flags,
00117                                         up,
00118                                         right,
00119                                         forward,
00120                                         NULL,
00121                                         100,
00122                                         level.time ); 
00123 }
00124 
00125 void turretG2_set_models( gentity_t *self, qboolean dying )
00126 {
00127         if ( dying )
00128         {
00129                 if ( !(self->spawnflags&SPF_TURRETG2_TURBO) )
00130                 {
00131                         self->s.modelindex = G_ModelIndex( name2 );
00132                         self->s.modelindex2 = G_ModelIndex( name );
00133                 }
00134                 
00135                 trap_G2API_RemoveGhoul2Model( &self->ghoul2, 0 );
00136                 G_KillG2Queue( self->s.number );
00137                 self->s.modelGhoul2 = 0;
00138                 /*
00139                 trap_G2API_InitGhoul2Model( &self->ghoul2,
00140                                                                         name2,
00141                                                                         0, //base->s.modelindex,
00142                                                                         //note, this is not the same kind of index - this one's referring to the actual
00143                                                                         //index of the model in the g2 instance, whereas modelindex is the index of a
00144                                                                         //configstring -rww
00145                                                                         0,
00146                                                                         0,
00147                                                                         0,
00148                                                                         0);
00149                 */
00150         }
00151         else
00152         {
00153                 if ( !(self->spawnflags&SPF_TURRETG2_TURBO) )
00154                 {
00155                         self->s.modelindex = G_ModelIndex( name );
00156                         self->s.modelindex2 = G_ModelIndex( name2 );
00157                         //set the new onw
00158                         trap_G2API_InitGhoul2Model( &self->ghoul2,
00159                                                                                 name,
00160                                                                                 0, //base->s.modelindex,
00161                                                                                 //note, this is not the same kind of index - this one's referring to the actual
00162                                                                                 //index of the model in the g2 instance, whereas modelindex is the index of a
00163                                                                                 //configstring -rww
00164                                                                                 0,
00165                                                                                 0,
00166                                                                                 0,
00167                                                                                 0);
00168                 }
00169                 else
00170                 {
00171                         self->s.modelindex = G_ModelIndex( name3 );
00172                         //set the new onw
00173                         trap_G2API_InitGhoul2Model( &self->ghoul2,
00174                                                                                 name3,
00175                                                                                 0, //base->s.modelindex,
00176                                                                                 //note, this is not the same kind of index - this one's referring to the actual
00177                                                                                 //index of the model in the g2 instance, whereas modelindex is the index of a
00178                                                                                 //configstring -rww
00179                                                                                 0,
00180                                                                                 0,
00181                                                                                 0,
00182                                                                                 0);
00183                 }
00184                 
00185                 self->s.modelGhoul2 = 1;
00186                 if ( (self->spawnflags&SPF_TURRETG2_TURBO) )
00187                 {//larger
00188                         self->s.g2radius = 128;
00189                 }
00190                 else
00191                 {
00192                         self->s.g2radius = 80;
00193                 }
00194 
00195                 if ( (self->spawnflags&SPF_TURRETG2_TURBO) )
00196                 {//different pitch bone and muzzle flash points
00197                         G2Tur_SetBoneAngles(self, "pitch", vec3_origin);
00198                         self->genericValue11 = trap_G2API_AddBolt( self->ghoul2, 0, "*muzzle1" );
00199                         self->genericValue12 = trap_G2API_AddBolt( self->ghoul2, 0, "*muzzle2" );
00200                 }
00201                 else
00202                 {
00203                         G2Tur_SetBoneAngles(self, "Bone_body", vec3_origin);
00204                         self->genericValue11 = trap_G2API_AddBolt( self->ghoul2, 0, "*flash03" );
00205                 }
00206         }
00207 }
00208 
00209 //------------------------------------------------------------------------------------------------------------
00210 void TurretG2Pain( gentity_t *self, gentity_t *attacker, int damage )
00211 //------------------------------------------------------------------------------------------------------------
00212 {
00213         if (self->paintarget && self->paintarget[0])
00214         {
00215                 if (self->genericValue8 < level.time)
00216                 {
00217                         G_UseTargets2(self, self, self->paintarget);
00218                         self->genericValue8 = level.time + self->genericValue4;
00219                 }
00220         }
00221 
00222         if ( attacker->client && attacker->client->ps.weapon == WP_DEMP2 )
00223         {
00224                 self->attackDebounceTime = level.time + 2000 + random() * 500;
00225                 self->painDebounceTime = self->attackDebounceTime;
00226         }
00227         if ( !self->enemy )
00228         {//react to being hit
00229                 G_SetEnemy( self, attacker );
00230         }
00231         //self->s.health = self->health;
00232         //mmm..yes..bad.
00233 }
00234 
00235 //------------------------------------------------------------------------------------------------------------
00236 void turretG2_die ( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath )
00237 //------------------------------------------------------------------------------------------------------------
00238 {
00239         vec3_t  forward = { 0,0,-1 }, pos;
00240 
00241         // Turn off the thinking of the base & use it's targets
00242         //self->think = NULL;
00243         self->use = NULL;
00244 
00245         // clear my data
00246         self->die  = NULL;
00247         self->pain = NULL;
00248         self->takedamage = qfalse;
00249         self->s.health = self->health = 0;
00250         self->s.loopSound = 0;
00251         self->s.shouldtarget = qfalse;
00252         //self->s.owner = MAX_CLIENTS; //not owned by any client
00253 
00254         // hack the effect angle so that explode death can orient the effect properly
00255         if ( self->spawnflags & 2 )
00256         {
00257                 VectorSet( forward, 0, 0, 1 );
00258         }
00259 
00260 //      VectorCopy( self->r.currentOrigin, self->s.pos.trBase );
00261 
00262         VectorMA( self->r.currentOrigin, 12, forward, pos );
00263         G_PlayEffect( EFFECT_EXPLOSION_TURRET, pos, forward );
00264         
00265         if ( self->splashDamage > 0 && self->splashRadius > 0 )
00266         {
00267                 G_RadiusDamage( self->r.currentOrigin,
00268                                                 attacker,
00269                                                 self->splashDamage,
00270                                                 self->splashRadius,
00271                                                 attacker,
00272                                                 NULL,
00273                                                 MOD_UNKNOWN );
00274         }
00275 
00276         if ( self->s.eFlags & EF_SHADER_ANIM )
00277         {
00278                 self->s.frame = 1; // black
00279         }
00280 
00281         self->s.weapon = 0; // crosshair code uses this to mark crosshair red
00282 
00283         if ( self->s.modelindex2 )
00284         {
00285                 // switch to damage model if we should
00286                 turretG2_set_models( self, qtrue );
00287 
00288                 VectorCopy( self->r.currentAngles, self->s.apos.trBase );
00289                 VectorClear( self->s.apos.trDelta );
00290                 
00291                 if ( self->target )
00292                 {
00293                         G_UseTargets( self, attacker );
00294                 }
00295                 
00296                 if (self->spawnflags & SPF_TURRETG2_CANRESPAWN)
00297                 {//respawn
00298                         if (self->health < 1 && !self->genericValue5)
00299                         { //we are dead, set our respawn delay if we have one
00300                                 self->genericValue5 = level.time + self->count;
00301                         }
00302                 }
00303         }
00304         else
00305         {
00306                 ObjectDie( self, inflictor, attacker, damage, meansOfDeath );
00307         }
00308 }
00309 
00310 #define START_DIS 15
00311 
00312 //start an animation on model_root both server side and client side
00313 void TurboLaser_SetBoneAnim(gentity_t *eweb, int startFrame, int endFrame)
00314 {
00315         //set info on the entity so it knows to start the anim on the client next snapshot.
00316         eweb->s.eFlags |= EF_G2ANIMATING;
00317 
00318         if (eweb->s.torsoAnim == startFrame && eweb->s.legsAnim == endFrame)
00319         { //already playing this anim, let's flag it to restart
00320                 eweb->s.torsoFlip = !eweb->s.torsoFlip;
00321         }
00322         else
00323         {
00324                 eweb->s.torsoAnim = startFrame;
00325                 eweb->s.legsAnim = endFrame;
00326         }
00327 
00328         //now set the animation on the server ghoul2 instance.
00329         assert(eweb->ghoul2);
00330         trap_G2API_SetBoneAnim(eweb->ghoul2, 0, "model_root", startFrame, endFrame,
00331                 (BONE_ANIM_OVERRIDE_FREEZE|BONE_ANIM_BLEND), 1.0f, level.time, -1, 100);
00332 }
00333 
00334 extern void WP_FireTurboLaserMissile( gentity_t *ent, vec3_t start, vec3_t dir );
00335 //----------------------------------------------------------------
00336 static void turretG2_fire ( gentity_t *ent, vec3_t start, vec3_t dir )
00337 //----------------------------------------------------------------
00338 {
00339         vec3_t          org, ang;
00340         gentity_t       *bolt;
00341 
00342         if ( (trap_PointContents( start, ent->s.number )&MASK_SHOT) )
00343         {
00344                 return;
00345         }
00346 
00347         VectorMA( start, -START_DIS, dir, org ); // dumb....
00348 
00349         if ( ent->random )
00350         {
00351                 vectoangles( dir, ang );
00352                 ang[PITCH] += flrand( -ent->random, ent->random );
00353                 ang[YAW] += flrand( -ent->random, ent->random );
00354                 AngleVectors( ang, dir, NULL, NULL );
00355         }
00356 
00357         vectoangles(dir, ang);
00358 
00359         if ( (ent->spawnflags&SPF_TURRETG2_TURBO) )
00360         {
00361                 //muzzle flash
00362                 G_PlayEffectID( ent->genericValue13, org, ang );
00363                 WP_FireTurboLaserMissile( ent, start, dir );
00364                 if ( ent->alt_fire )
00365                 {
00366                         TurboLaser_SetBoneAnim( ent, 2, 3 );
00367                 }
00368                 else
00369                 {
00370                         TurboLaser_SetBoneAnim( ent, 0, 1 );
00371                 }
00372         }
00373         else
00374         {
00375                 G_PlayEffectID( G_EffectIndex("blaster/muzzle_flash"), org, ang );
00376                 bolt = G_Spawn();
00377                 
00378                 bolt->classname = "turret_proj";
00379                 bolt->nextthink = level.time + 10000;
00380                 bolt->think = G_FreeEntity;
00381                 bolt->s.eType = ET_MISSILE;
00382                 bolt->s.weapon = WP_BLASTER;
00383                 bolt->r.ownerNum = ent->s.number;
00384                 bolt->damage = ent->damage;
00385                 bolt->alliedTeam = ent->alliedTeam;
00386                 bolt->teamnodmg = ent->teamnodmg;
00387                 bolt->dflags = (DAMAGE_NO_KNOCKBACK|DAMAGE_HEAVY_WEAP_CLASS);           // Don't push them around, or else we are constantly re-aiming
00388                 bolt->splashDamage = ent->splashDamage;
00389                 bolt->splashRadius = ent->splashDamage;
00390                 bolt->methodOfDeath = MOD_TARGET_LASER;//MOD_ENERGY;
00391                 bolt->splashMethodOfDeath = MOD_TARGET_LASER;//MOD_ENERGY;
00392                 bolt->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
00393                 //bolt->trigger_formation = qfalse;             // don't draw tail on first frame       
00394 
00395                 VectorSet( bolt->r.maxs, 1.5, 1.5, 1.5 );
00396                 VectorScale( bolt->r.maxs, -1, bolt->r.mins );
00397                 bolt->s.pos.trType = TR_LINEAR;
00398                 bolt->s.pos.trTime = level.time;
00399                 VectorCopy( start, bolt->s.pos.trBase );
00400                 VectorScale( dir, ent->mass, bolt->s.pos.trDelta );
00401                 SnapVector( bolt->s.pos.trDelta );              // save net bandwidth
00402                 VectorCopy( start, bolt->r.currentOrigin);
00403         }
00404 }
00405 
00406 void turretG2_respawn( gentity_t *self )
00407 {
00408         self->use = turretG2_base_use;
00409         self->pain = TurretG2Pain;
00410         self->die  = turretG2_die;
00411         self->takedamage = qtrue;
00412         self->s.shouldtarget = qtrue;
00413         //self->s.owner = MAX_CLIENTS; //not owned by any client
00414         if ( self->s.eFlags & EF_SHADER_ANIM )
00415         {
00416                 self->s.frame = 0; // normal
00417         }
00418         self->s.weapon = WP_TURRET; // crosshair code uses this to mark crosshair red
00419 
00420         turretG2_set_models( self, qfalse );
00421         self->s.health = self->health = self->genericValue6;
00422         if (self->maxHealth) {
00423                 G_ScaleNetHealth(self);
00424         }
00425         self->genericValue5 = 0;//clear this now
00426 }
00427 
00428 //-----------------------------------------------------
00429 void turretG2_head_think( gentity_t *self )
00430 //-----------------------------------------------------
00431 {
00432         // if it's time to fire and we have an enemy, then gun 'em down!  pushDebounce time controls next fire time
00433         if ( self->enemy 
00434                 && self->setTime < level.time 
00435                 && self->attackDebounceTime < level.time )
00436         {
00437                 vec3_t          fwd, org;
00438                 mdxaBone_t      boltMatrix;
00439 
00440                 // set up our next fire time
00441                 self->setTime = level.time + self->wait;
00442 
00443                 // Getting the flash bolt here
00444                 trap_G2API_GetBoltMatrix( self->ghoul2,
00445                                         0, 
00446                                         (self->alt_fire?self->genericValue12:self->genericValue11),
00447                                         &boltMatrix, 
00448                                         self->r.currentAngles, 
00449                                         self->r.currentOrigin, 
00450                                         level.time,
00451                                         NULL, 
00452                                         self->modelScale );
00453                 if ( (self->spawnflags&SPF_TURRETG2_TURBO) )
00454                 {
00455                         self->alt_fire = !self->alt_fire;
00456                 }
00457 
00458                 BG_GiveMeVectorFromMatrix( &boltMatrix, ORIGIN, org );
00459                 //BG_GiveMeVectorFromMatrix( &boltMatrix, POSITIVE_Y, fwd );
00460                 if ( (self->spawnflags&SPF_TURRETG2_TURBO) )
00461                 {
00462                         BG_GiveMeVectorFromMatrix( &boltMatrix, POSITIVE_X, fwd );
00463                 }
00464                 else
00465                 {
00466                         BG_GiveMeVectorFromMatrix( &boltMatrix, NEGATIVE_X, fwd );
00467                 }
00468 
00469                 VectorMA( org, START_DIS, fwd, org );
00470 
00471                 turretG2_fire( self, org, fwd );
00472                 self->fly_sound_debounce_time = level.time;//used as lastShotTime
00473         }
00474 }
00475 
00476 //-----------------------------------------------------
00477 static void turretG2_aim( gentity_t *self )
00478 //-----------------------------------------------------
00479 {
00480         vec3_t  enemyDir, org, org2;
00481         vec3_t  desiredAngles, setAngle;
00482         float   diffYaw = 0.0f, diffPitch = 0.0f;
00483         float   maxYawSpeed = (self->spawnflags&SPF_TURRETG2_TURBO)?30.0f:14.0f;
00484         float   maxPitchSpeed = (self->spawnflags&SPF_TURRETG2_TURBO)?15.0f:3.0f;
00485 
00486         // move our gun base yaw to where we should be at this time....
00487         BG_EvaluateTrajectory( &self->s.apos, level.time, self->r.currentAngles );
00488         self->r.currentAngles[YAW] = AngleNormalize360( self->r.currentAngles[YAW] );
00489         self->speed = AngleNormalize360( self->speed );
00490 
00491         if ( self->enemy )
00492         {
00493                 mdxaBone_t      boltMatrix;
00494                 // ...then we'll calculate what new aim adjustments we should attempt to make this frame
00495                 // Aim at enemy
00496                 if ( self->enemy->client )
00497                 {
00498                         VectorCopy( self->enemy->client->renderInfo.eyePoint, org );
00499                 }
00500                 else
00501                 {
00502                         VectorCopy( self->enemy->r.currentOrigin, org );
00503                 }
00504                 if ( self->spawnflags & 2 )
00505                 {
00506                         org[2] -= 15;
00507                 }
00508                 else
00509                 {
00510                         org[2] -= 5;
00511                 }
00512 
00513                 if ( (self->spawnflags&SPF_TURRETG2_LEAD_ENEMY) )
00514                 {//we want to lead them a bit
00515                         vec3_t diff, velocity;
00516                         float dist;
00517                         VectorSubtract( org, self->s.origin, diff );
00518                         dist = VectorNormalize( diff );
00519                         if ( self->enemy->client )
00520                         {
00521                                 VectorCopy( self->enemy->client->ps.velocity, velocity );
00522                         }
00523                         else
00524                         {
00525                                 VectorCopy( self->enemy->s.pos.trDelta, velocity );
00526                         }
00527                         VectorMA( org, (dist/self->mass), velocity, org );
00528                 }
00529 
00530                 // Getting the "eye" here
00531                 trap_G2API_GetBoltMatrix( self->ghoul2,
00532                                         0, 
00533                                         (self->alt_fire?self->genericValue12:self->genericValue11),
00534                                         &boltMatrix,
00535                                         self->r.currentAngles,
00536                                         self->s.origin,
00537                                         level.time,
00538                                         NULL,
00539                                         self->modelScale );
00540 
00541                 BG_GiveMeVectorFromMatrix( &boltMatrix, ORIGIN, org2 );
00542 
00543                 VectorSubtract( org, org2, enemyDir );
00544                 vectoangles( enemyDir, desiredAngles );
00545 
00546                 diffYaw = AngleSubtract( self->r.currentAngles[YAW], desiredAngles[YAW] );
00547                 diffPitch = AngleSubtract( self->speed, desiredAngles[PITCH] );
00548         }
00549         else
00550         {
00551                 // no enemy, so make us slowly sweep back and forth as if searching for a new one
00552 //              diffYaw = sin( level.time * 0.0001f + self->count ) * 5.0f;     // don't do this for now since it can make it go into walls.
00553         }
00554 
00555         if ( diffYaw )
00556         {
00557                 // cap max speed....
00558                 if ( fabs(diffYaw) > maxYawSpeed )
00559                 {
00560                         diffYaw = ( diffYaw >= 0 ? maxYawSpeed : -maxYawSpeed );
00561                 }
00562 
00563                 // ...then set up our desired yaw
00564                 VectorSet( setAngle, 0.0f, diffYaw, 0.0f );
00565 
00566                 VectorCopy( self->r.currentAngles, self->s.apos.trBase );
00567                 VectorScale( setAngle,- 5, self->s.apos.trDelta );
00568                 self->s.apos.trTime = level.time;
00569                 self->s.apos.trType = TR_LINEAR;
00570         }
00571 
00572         if ( diffPitch )
00573         {
00574                 if ( fabs(diffPitch) > maxPitchSpeed )
00575                 {
00576                         // cap max speed
00577                         self->speed += (diffPitch > 0.0f) ? -maxPitchSpeed : maxPitchSpeed;
00578                 }
00579                 else
00580                 {
00581                         // small enough, so just add half the diff so we smooth out the stopping
00582                         self->speed -= ( diffPitch );//desiredAngles[PITCH];
00583                 }
00584 
00585                 // Note that this is NOT interpolated, so it will be less smooth...On the other hand, it does use Ghoul2 to blend, so it may smooth it out a bit?
00586                 if ( (self->spawnflags&SPF_TURRETG2_TURBO) )
00587                 {
00588                         if ( self->spawnflags & 2 )
00589                         {
00590                                 VectorSet( desiredAngles, 0.0f, 0.0f, -self->speed );
00591                         }
00592                         else
00593                         {
00594                                 VectorSet( desiredAngles, 0.0f, 0.0f, self->speed );
00595                         }
00596                         G2Tur_SetBoneAngles(self, "pitch", desiredAngles);
00597                 }
00598                 else
00599                 {
00600                         if ( self->spawnflags & 2 )
00601                         {
00602                                 VectorSet( desiredAngles, self->speed, 0.0f, 0.0f );
00603                         }
00604                         else
00605                         {
00606                                 VectorSet( desiredAngles, -self->speed, 0.0f, 0.0f );
00607                         }
00608                         G2Tur_SetBoneAngles(self, "Bone_body", desiredAngles);
00609                 }
00610                 /*
00611                 trap_G2API_SetBoneAngles( self->ghoul2,
00612                                                 0,
00613                                                 "Bone_body",
00614                                                 desiredAngles, 
00615                                                 BONE_ANGLES_POSTMULT,
00616                                                 POSITIVE_Y,
00617                                                 POSITIVE_Z,
00618                                                 POSITIVE_X,
00619                                                 NULL,
00620                                                 100,
00621                                                 level.time ); 
00622                                                 */
00623         }
00624 
00625         if ( diffYaw || diffPitch )
00626         {//FIXME: turbolaser sounds
00627                 if ( (self->spawnflags&SPF_TURRETG2_TURBO) )
00628                 {
00629                         self->s.loopSound = G_SoundIndex( "sound/vehicles/weapons/turbolaser/turn.wav" );
00630                 }
00631                 else
00632                 {
00633                         self->s.loopSound = G_SoundIndex( "sound/chars/turret/move.wav" );
00634                 }
00635         }
00636         else
00637         {
00638                 self->s.loopSound = 0;
00639         }
00640 }
00641 
00642 //-----------------------------------------------------
00643 static void turretG2_turnoff( gentity_t *self )
00644 //-----------------------------------------------------
00645 {
00646         if ( self->enemy == NULL )
00647         {
00648                 // we don't need to turnoff
00649                 return;
00650         }
00651         if ( (self->spawnflags&SPF_TURRETG2_TURBO) )
00652         {
00653                 TurboLaser_SetBoneAnim( self, 4, 5 );
00654         }
00655         // shut-down sound
00656         if ( !(self->spawnflags&SPF_TURRETG2_TURBO) )
00657         {
00658                 G_Sound( self, CHAN_BODY, G_SoundIndex( "sound/chars/turret/shutdown.wav" ));
00659         }
00660         
00661         // make turret play ping sound for 5 seconds
00662         self->aimDebounceTime = level.time + 5000;
00663 
00664         // Clear enemy
00665         self->enemy = NULL;
00666 }
00667 
00668 //-----------------------------------------------------
00669 static qboolean turretG2_find_enemies( gentity_t *self )
00670 //-----------------------------------------------------
00671 {
00672         qboolean        found = qfalse;
00673         int                     i, count;
00674         float           bestDist = self->radius * self->radius;
00675         float           enemyDist;
00676         vec3_t          enemyDir, org, org2;
00677         qboolean        foundClient = qfalse;
00678         gentity_t       *entity_list[MAX_GENTITIES], *target, *bestTarget = NULL;
00679 
00680         if ( self->aimDebounceTime > level.time ) // time since we've been shut off
00681         {
00682                 // We were active and alert, i.e. had an enemy in the last 3 secs
00683                 if ( self->painDebounceTime < level.time )
00684                 {
00685                         if ( !(self->spawnflags&SPF_TURRETG2_TURBO) )
00686                         {
00687                                 G_Sound(self, CHAN_BODY, G_SoundIndex( "sound/chars/turret/ping.wav" ));
00688                         }
00689                         self->painDebounceTime = level.time + 1000;
00690                 }
00691         }
00692 
00693         VectorCopy( self->r.currentOrigin, org2 );
00694         if ( self->spawnflags & 2 )
00695         {
00696                 org2[2] += 20;
00697         }
00698         else
00699         {
00700                 org2[2] -= 20;
00701         }
00702 
00703         count = G_RadiusList( org2, self->radius, self, qtrue, entity_list );
00704 
00705         for ( i = 0; i < count; i++ )
00706         {
00707                 trace_t tr;
00708                 target = entity_list[i];
00709 
00710                 if ( !target->client )
00711                 {
00712                         // only attack clients
00713                         if ( !(target->flags&FL_BBRUSH)//not a breakable brush
00714                                 || !target->takedamage//is a bbrush, but invincible
00715                                 || (target->NPC_targetname&&self->targetname&&Q_stricmp(target->NPC_targetname,self->targetname)!=0) )//not in invicible bbrush, but can only be broken by an NPC that is not me
00716                         {
00717                                 continue;
00718                         }
00719                         //else: we will shoot at bbrushes!
00720                 }
00721                 if ( target == self || !target->takedamage || target->health <= 0 || ( target->flags & FL_NOTARGET ))
00722                 {
00723                         continue;
00724                 }
00725                 if ( target->client && target->client->sess.sessionTeam == TEAM_SPECTATOR )
00726                 {
00727                         continue;
00728                 }
00729                 if ( self->alliedTeam )
00730                 {
00731                         if ( target->client )
00732                         {
00733                                 if ( target->client->sess.sessionTeam == self->alliedTeam )
00734                                 { 
00735                                         // A bot/client/NPC we don't want to shoot
00736                                         continue;
00737                                 }
00738                         }
00739                         else if ( target->teamnodmg == self->alliedTeam )
00740                         { 
00741                                 // An ent we don't want to shoot
00742                                 continue;
00743                         }
00744                 }
00745                 if ( !trap_InPVS( org2, target->r.currentOrigin ))
00746                 {
00747                         continue;
00748                 }
00749 
00750                 if ( target->client )
00751                 {
00752                         VectorCopy( target->client->renderInfo.eyePoint, org );
00753                 }
00754                 else
00755                 {
00756                         VectorCopy( target->r.currentOrigin, org );
00757                 }
00758 
00759                 if ( self->spawnflags & 2 )
00760                 {
00761                         org[2] -= 15;
00762                 }
00763                 else
00764                 {
00765                         org[2] += 5;
00766                 }
00767 
00768                 trap_Trace( &tr, org2, NULL, NULL, org, self->s.number, MASK_SHOT );
00769 
00770                 if ( !tr.allsolid && !tr.startsolid && ( tr.fraction == 1.0 || tr.entityNum == target->s.number ))
00771                 {
00772                         // Only acquire if have a clear shot, Is it in range and closer than our best?
00773                         VectorSubtract( target->r.currentOrigin, self->r.currentOrigin, enemyDir );
00774                         enemyDist = VectorLengthSquared( enemyDir );
00775 
00776                         if ( enemyDist < bestDist || (target->client && !foundClient))// all things equal, keep current
00777                         {
00778                                 if ( self->attackDebounceTime < level.time )
00779                                 {
00780                                         // We haven't fired or acquired an enemy in the last 2 seconds-start-up sound
00781                                         if ( !(self->spawnflags&SPF_TURRETG2_TURBO) )
00782                                         {
00783                                                 G_Sound( self, CHAN_BODY, G_SoundIndex( "sound/chars/turret/startup.wav" ));
00784                                         }
00785 
00786                                         // Wind up turrets for a bit
00787                                         self->attackDebounceTime = level.time + 1400;
00788                                 }
00789 
00790                                 bestTarget = target;
00791                                 bestDist = enemyDist;
00792                                 found = qtrue;
00793                                 if ( target->client )
00794                                 {//prefer clients over non-clients
00795                                         foundClient = qtrue;
00796                                 }
00797                         }
00798                 }
00799         }
00800 
00801         if ( found )
00802         {
00803                 /*
00804                 if ( !self->enemy )
00805                 {//just aquired one
00806                         AddSoundEvent( bestTarget, self->r.currentOrigin, 256, AEL_DISCOVERED );
00807                         AddSightEvent( bestTarget, self->r.currentOrigin, 512, AEL_DISCOVERED, 20 );
00808                 }
00809                 */
00810                 G_SetEnemy( self, bestTarget );
00811                 if ( VALIDSTRING( self->target2 ))
00812                 {
00813                         G_UseTargets2( self, self, self->target2 );
00814                 }
00815         }
00816 
00817         return found;
00818 }
00819 
00820 //-----------------------------------------------------
00821 void turretG2_base_think( gentity_t *self )
00822 //-----------------------------------------------------
00823 {
00824         qboolean        turnOff = qtrue;
00825         float           enemyDist;
00826         vec3_t          enemyDir, org, org2;
00827 
00828         self->nextthink = level.time + FRAMETIME;
00829 
00830         if ( self->health <= 0 )
00831         {//dead
00832                 if (self->spawnflags & SPF_TURRETG2_CANRESPAWN)
00833                 {//can respawn
00834                         if ( self->genericValue5 && self->genericValue5 < level.time )
00835                         { //we are dead, see if it's time to respawn
00836                                 turretG2_respawn( self );
00837                         }
00838                 }
00839                 return;
00840         }
00841         else if ( self->spawnflags & 1 )
00842         {// not turned on
00843                 turretG2_turnoff( self );
00844                 turretG2_aim( self );
00845 
00846                 // No target
00847                 self->flags |= FL_NOTARGET;
00848                 return;
00849         }
00850         else
00851         {
00852                 // I'm all hot and bothered
00853                 self->flags &= ~FL_NOTARGET;
00854         }
00855 
00856         if ( self->enemy )
00857         {
00858                 if ( self->enemy->health < 0 
00859                         || !self->enemy->inuse )
00860                 {
00861                         self->enemy = NULL;
00862                 }
00863         }
00864 
00865         if ( self->last_move_time < level.time )
00866         {//MISNOMER: used a enemy recalcing debouncer
00867                 if ( turretG2_find_enemies( self ) )
00868                 {//found one
00869                         turnOff = qfalse;
00870                         if ( self->enemy->client )
00871                         {//hold on to clients for a min of 3 seconds
00872                                 self->last_move_time = level.time + 3000;
00873                         }
00874                         else
00875                         {//hold less
00876                                 self->last_move_time = level.time + 500;
00877                         }
00878                 }
00879         }
00880 
00881         if ( self->enemy != NULL )
00882         {
00883                 if ( self->enemy->client && self->enemy->client->sess.sessionTeam == TEAM_SPECTATOR )
00884                 {//don't keep going after spectators
00885                         self->enemy = NULL;
00886                 }
00887                 else
00888                 {//FIXME: remain single-minded or look for a new enemy every now and then?
00889                         // enemy is alive
00890                         VectorSubtract( self->enemy->r.currentOrigin, self->r.currentOrigin, enemyDir );
00891                         enemyDist = VectorLengthSquared( enemyDir );
00892 
00893                         if ( enemyDist < self->radius * self->radius )
00894                         {
00895                                 // was in valid radius
00896                                 if ( trap_InPVS( self->r.currentOrigin, self->enemy->r.currentOrigin ) )
00897                                 {
00898                                         // Every now and again, check to see if we can even trace to the enemy
00899                                         trace_t tr;
00900 
00901                                         if ( self->enemy->client )
00902                                         {
00903                                                 VectorCopy( self->enemy->client->renderInfo.eyePoint, org );
00904                                         }
00905                                         else
00906                                         {
00907                                                 VectorCopy( self->enemy->r.currentOrigin, org );
00908                                         }
00909                                         VectorCopy( self->r.currentOrigin, org2 );
00910                                         if ( self->spawnflags & 2 )
00911                                         {
00912                                                 org2[2] += 10;
00913                                         }
00914                                         else
00915                                         {
00916                                                 org2[2] -= 10;
00917                                         }
00918                                         trap_Trace( &tr, org2, NULL, NULL, org, self->s.number, MASK_SHOT );
00919 
00920                                         if ( !tr.allsolid && !tr.startsolid && tr.entityNum == self->enemy->s.number )
00921                                         {
00922                                                 turnOff = qfalse;       // Can see our enemy
00923                                         }
00924                                 }
00925                         }
00926 
00927                 }
00928         }
00929 
00930         if ( turnOff )
00931         {
00932                 if ( self->bounceCount < level.time ) // bounceCount is used to keep the thing from ping-ponging from on to off
00933                 {
00934                         turretG2_turnoff( self );
00935                 }
00936         }
00937         else
00938         {
00939                 // keep our enemy for a minimum of 2 seconds from now
00940                 self->bounceCount = level.time + 2000 + random() * 150;
00941         }
00942 
00943         turretG2_aim( self );
00944         if ( !turnOff )
00945         {
00946                 turretG2_head_think( self );
00947         }
00948 }
00949 
00950 //-----------------------------------------------------------------------------
00951 void turretG2_base_use( gentity_t *self, gentity_t *other, gentity_t *activator )
00952 //-----------------------------------------------------------------------------
00953 {
00954         // Toggle on and off
00955         self->spawnflags = (self->spawnflags ^ 1);
00956 
00957         if (( self->s.eFlags & EF_SHADER_ANIM ) && ( self->spawnflags & 1 )) // Start_Off
00958         {
00959                 self->s.frame = 1; // black
00960         }
00961         else
00962         {
00963                 self->s.frame = 0; // glow
00964         }
00965 }
00966 
00967 
00968 /*QUAKED misc_turretG2 (1 0 0) (-8 -8 -22) (8 8 0) START_OFF UPSIDE_DOWN CANRESPAWN TURBO LEAD SHOWRADAR
00969 
00970 Turret that hangs from the ceiling, will aim and shoot at enemies
00971 
00972   START_OFF - Starts off
00973   UPSIDE_DOWN - make it rest on a surface/floor instead of hanging from the ceiling
00974   CANRESPAWN - will respawn after being killed (use count)
00975   TURBO - Big-ass, Boxy Death Star Turbo Laser version
00976   LEAD - Turret will aim ahead of moving targets ("lead" them)
00977   SHOWRADAR - show on radar
00978 
00979   radius - How far away an enemy can be for it to pick it up (default 512)
00980   wait  - Time between shots (default 150 ms)
00981   dmg   - How much damage each shot does (default 5)
00982   health - How much damage it can take before exploding (default 100)
00983   count - if CANRESPAWN spawnflag, decides how long it is before gun respawns (in ms) - defaults to 20000 (20 seconds)
00984 
00985   paintarget - target to fire off upon being hurt
00986   painwait - ms to wait between firing off pain targets
00987 
00988   random - random error (in degrees) of projectile direction when it comes out of the muzzle (default is 2)
00989 
00990   shotspeed - the speed of the missile it fires travels at (default is 1100 for regular turrets, 20000 for TURBOLASERS)
00991 
00992   splashDamage - How much damage the explosion does
00993   splashRadius - The radius of the explosion
00994   
00995   targetname - Toggles it on/off
00996   target - What to use when destroyed
00997   target2 - What to use when it decides to start shooting at an enemy
00998 
00999   showhealth - set to 1 to show health bar on this entity when crosshair is over it
01000   
01001   teamowner - crosshair shows green for this team, red for opposite team
01002         0 - none
01003         1 - red
01004         2 - blue
01005 
01006   alliedTeam - team that this turret won't target
01007         0 - none
01008         1 - red
01009         2 - blue
01010 
01011   teamnodmg - team that turret does not take damage from
01012         0 - none
01013         1 - red
01014         2 - blue
01015 
01016   customscale - custom scaling size. 100 is normal size, 1024 is the max scaling. this will change the bounding box size, so be careful of starting in solid!
01017 
01018 "icon" - icon that represents the objective on the radar
01019 */
01020 //-----------------------------------------------------
01021 void SP_misc_turretG2( gentity_t *base )
01022 //-----------------------------------------------------
01023 {
01024         int customscaleVal;
01025         char* s;
01026 
01027         turretG2_set_models( base, qfalse );
01028 
01029         G_SpawnInt("painwait", "0", &base->genericValue4);
01030         base->genericValue8 = 0;
01031 
01032         G_SpawnInt("customscale", "0", &customscaleVal);
01033         base->s.iModelScale = customscaleVal;
01034         if (base->s.iModelScale)
01035         {
01036                 if (base->s.iModelScale > 1023)
01037                 {
01038                         base->s.iModelScale = 1023;
01039                 }
01040                 base->modelScale[0] = base->modelScale[1] = base->modelScale[2] = base->s.iModelScale/100.0f;
01041         }
01042 
01043         G_SpawnString( "icon", "", &s );
01044         if (s && s[0])
01045         { 
01046                 // We have an icon, so index it now.  We are reusing the genericenemyindex
01047                 // variable rather than adding a new one to the entity state.
01048                 base->s.genericenemyindex = G_IconIndex(s);
01049         }
01050 
01051         finish_spawning_turretG2( base );
01052 
01053         if (( base->spawnflags & 1 )) // Start_Off
01054         {
01055                 base->s.frame = 1; // black
01056         }
01057         else
01058         {
01059                 base->s.frame = 0; // glow
01060         }
01061         if ( !(base->spawnflags&SPF_TURRETG2_TURBO) )
01062         {
01063                 base->s.eFlags |= EF_SHADER_ANIM;
01064         }
01065 
01066         if (base->spawnflags & SPF_SHOWONRADAR)
01067         {
01068                 base->s.eFlags |= EF_RADAROBJECT;
01069         }
01070 #undef name
01071 #undef name2
01072 #undef name3
01073 }
01074 
01075 //-----------------------------------------------------
01076 void finish_spawning_turretG2( gentity_t *base )
01077 {
01078         vec3_t          fwd;
01079         int                     t;
01080 
01081         if ( (base->spawnflags&2) )
01082         {
01083                 base->s.angles[ROLL] += 180;
01084                 base->s.origin[2] -= 22.0f;
01085         }
01086 
01087         G_SetAngles( base, base->s.angles );
01088         AngleVectors( base->r.currentAngles, fwd, NULL, NULL );
01089 
01090         G_SetOrigin(base, base->s.origin);
01091 
01092         base->s.eType = ET_GENERAL;
01093 
01094         if ( base->team && base->team[0] && //g_gametype.integer == GT_SIEGE &&
01095                 !base->teamnodmg)
01096         {
01097                 base->teamnodmg = atoi(base->team);
01098         }
01099         base->team = NULL;
01100 
01101         // Set up our explosion effect for the ExplodeDeath code....
01102         G_EffectIndex( "turret/explode" );
01103         G_EffectIndex( "sparks/spark_exp_nosnd" );
01104 
01105         base->use = turretG2_base_use;
01106         base->pain = TurretG2Pain;
01107 
01108         // don't start working right away
01109         base->think = turretG2_base_think;
01110         base->nextthink = level.time + FRAMETIME * 5;
01111 
01112         // this is really the pitch angle.....
01113         base->speed = 0;
01114 
01115         // respawn time defaults to 20 seconds
01116         if ( (base->spawnflags&SPF_TURRETG2_CANRESPAWN) && !base->count )
01117         {
01118                 base->count = 20000;
01119         }
01120 
01121         G_SpawnFloat( "shotspeed", "0", &base->mass );
01122         if ( (base->spawnflags&SPF_TURRETG2_TURBO) )
01123         {
01124                 if ( !base->random )
01125                 {//error worked into projectile direction
01126                         base->random = 2.0f;
01127                 }
01128 
01129                 if ( !base->mass )
01130                 {//misnomer: speed of projectile
01131                         base->mass = 20000;
01132                 }
01133 
01134                 if ( !base->health )
01135                 {
01136                         base->health = 2000;
01137                 }
01138 
01139                 // search radius
01140                 if ( !base->radius )
01141                 {
01142                         base->radius = 32768;
01143                 }
01144 
01145                 // How quickly to fire
01146                 if ( !base->wait )
01147                 {
01148                         base->wait = 1000;// + random() * 500;
01149                 }
01150 
01151                 if ( !base->splashDamage )
01152                 {
01153                         base->splashDamage = 200;
01154                 }
01155 
01156                 if ( !base->splashRadius )
01157                 {
01158                         base->splashRadius = 500;
01159                 }
01160 
01161                 // how much damage each shot does
01162                 if ( !base->damage )
01163                 {
01164                         base->damage = 500;
01165                 }
01166 
01167                 if ( (base->spawnflags&SPF_TURRETG2_TURBO) )
01168                 {
01169                         VectorSet( base->r.maxs, 64.0f, 64.0f, 30.0f );
01170                         VectorSet( base->r.mins, -64.0f, -64.0f, -30.0f );
01171                 }
01172                 //start in "off" anim
01173                 TurboLaser_SetBoneAnim( base, 4, 5 );
01174                 if ( g_gametype.integer == GT_SIEGE )
01175                 {//FIXME: designer-specified?
01176                         //FIXME: put on other entities, too, particularly siege objectives and bbrushes...
01177                         base->s.eFlags2 |= EF2_BRACKET_ENTITY;
01178                 }
01179         }
01180         else
01181         {
01182                 if ( !base->random )
01183                 {//error worked into projectile direction
01184                         base->random = 2.0f;
01185                 }
01186 
01187                 if ( !base->mass )
01188                 {//misnomer: speed of projectile
01189                         base->mass = 1100;
01190                 }
01191 
01192                 if ( !base->health )
01193                 {
01194                         base->health = 100;
01195                 }
01196 
01197                 // search radius
01198                 if ( !base->radius )
01199                 {
01200                         base->radius = 512;
01201                 }
01202 
01203                 // How quickly to fire
01204                 if ( !base->wait )
01205                 {
01206                         base->wait = 150 + random() * 55;
01207                 }
01208 
01209                 if ( !base->splashDamage )
01210                 {
01211                         base->splashDamage = 10;
01212                 }
01213 
01214                 if ( !base->splashRadius )
01215                 {
01216                         base->splashRadius = 25;
01217                 }
01218 
01219                 // how much damage each shot does
01220                 if ( !base->damage )
01221                 {
01222                         base->damage = 5;
01223                 }
01224 
01225                 if ( base->spawnflags & 2 )
01226                 {//upside-down, invert r.mins and maxe
01227                         VectorSet( base->r.maxs, 10.0f, 10.0f, 30.0f );
01228                         VectorSet( base->r.mins, -10.0f, -10.0f, 0.0f );
01229                 }
01230                 else
01231                 {
01232                         VectorSet( base->r.maxs, 10.0f, 10.0f, 0.0f );
01233                         VectorSet( base->r.mins, -10.0f, -10.0f, -30.0f );
01234                 }
01235         }
01236 
01237         //stash health off for respawn.  NOTE: cannot use maxhealth because that might not be set if not showing the health bar
01238         base->genericValue6 = base->health;
01239 
01240         G_SpawnInt( "showhealth", "0", &t );
01241         if (t)
01242         { //a non-0 maxhealth value will mean we want to show the health on the hud
01243                 base->maxHealth = base->health;
01244                 G_ScaleNetHealth(base);
01245                 base->s.shouldtarget = qtrue;
01246                 //base->s.owner = MAX_CLIENTS; //not owned by any client
01247         }
01248 
01249         if (base->s.iModelScale)
01250         { //let's scale the bbox too...
01251                 float fScale = base->s.iModelScale/100.0f;
01252                 VectorScale(base->r.mins, fScale, base->r.mins);
01253                 VectorScale(base->r.maxs, fScale, base->r.maxs);
01254         }
01255 
01256         // Precache special FX and moving sounds
01257         if ( (base->spawnflags&SPF_TURRETG2_TURBO) )
01258         {
01259                 base->genericValue13 = G_EffectIndex( "turret/turb_muzzle_flash" );
01260                 base->genericValue14 = G_EffectIndex( "turret/turb_shot" );
01261                 base->genericValue15 = G_EffectIndex( "turret/turb_impact" );
01262                 //FIXME: Turbo Laser Cannon sounds!
01263                 G_SoundIndex( "sound/vehicles/weapons/turbolaser/turn.wav" );
01264         }
01265         else
01266         {
01267                 G_SoundIndex( "sound/chars/turret/startup.wav" );
01268                 G_SoundIndex( "sound/chars/turret/shutdown.wav" );
01269                 G_SoundIndex( "sound/chars/turret/ping.wav" );
01270                 G_SoundIndex( "sound/chars/turret/move.wav" );
01271         }
01272 
01273         base->r.contents = CONTENTS_BODY|CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP|CONTENTS_SHOTCLIP;
01274 
01275         //base->max_health = base->health;
01276         base->takedamage = qtrue;
01277         base->die  = turretG2_die;
01278 
01279         base->material = MAT_METAL;
01280         //base->r.svFlags |= SVF_NO_TELEPORT|SVF_NONNPC_ENEMY|SVF_SELF_ANIMATING;
01281 
01282         // Register this so that we can use it for the missile effect
01283         RegisterItem( BG_FindItemForWeapon( WP_BLASTER ));
01284 
01285         // But set us as a turret so that we can be identified as a turret
01286         base->s.weapon = WP_TURRET;
01287 
01288         trap_LinkEntity( base );
01289 }