codemp/game/g_turret.c

Go to the documentation of this file.
00001 // Copyright (C) 1999-2000 Id Software, Inc.
00002 //
00003 #include "g_local.h"
00004 #include "q_shared.h"
00005 
00006 void G_SetEnemy( gentity_t *self, gentity_t *enemy );
00007 qboolean turret_base_spawn_top( gentity_t *base );
00008 void ObjectDie (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath );
00009 
00010 //------------------------------------------------------------------------------------------------------------
00011 void TurretPain( gentity_t *self, gentity_t *attacker, int damage )
00012 //------------------------------------------------------------------------------------------------------------
00013 {
00014         if (self->target_ent)
00015         {
00016                 self->target_ent->health = self->health;
00017                 if (self->target_ent->maxHealth)
00018                 {
00019                         G_ScaleNetHealth(self->target_ent);
00020                 }
00021         }
00022 
00023         if ( attacker->client && attacker->client->ps.weapon == WP_DEMP2 )
00024         {
00025                 self->attackDebounceTime = level.time + 800 + random() * 500;
00026                 self->painDebounceTime = self->attackDebounceTime;
00027         }
00028         if ( !self->enemy )
00029         {//react to being hit
00030                 G_SetEnemy( self, attacker );
00031         }
00032 }
00033 
00034 //------------------------------------------------------------------------------------------------------------
00035 void TurretBasePain( gentity_t *self, gentity_t *attacker, int damage )
00036 //------------------------------------------------------------------------------------------------------------
00037 {
00038         if (self->target_ent)
00039         {
00040                 self->target_ent->health = self->health;
00041                 if (self->target_ent->maxHealth)
00042                 {
00043                         G_ScaleNetHealth(self->target_ent);
00044                 }
00045 
00046                 TurretPain(self->target_ent, attacker, damage);
00047         }
00048 }
00049 
00050 //------------------------------------------------------------------------------------------------------------
00051 void auto_turret_die ( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath )
00052 //------------------------------------------------------------------------------------------------------------
00053 {
00054         vec3_t  forward = { 0,0, 1 }, pos;
00055 
00056         // Turn off the thinking of the base & use it's targets
00057         g_entities[self->r.ownerNum].think = NULL;
00058         g_entities[self->r.ownerNum].use = NULL;
00059 
00060         // clear my data
00061         self->die = NULL;
00062         self->takedamage = qfalse;
00063         self->s.health = self->health = 0;
00064         self->s.loopSound = 0;
00065         self->s.shouldtarget = qfalse;
00066         //self->s.owner = MAX_CLIENTS; //not owned by any client
00067 
00068         VectorCopy( self->r.currentOrigin, pos );
00069         pos[2] += self->r.maxs[2]*0.5f;
00070         G_PlayEffect( EFFECT_EXPLOSION_TURRET, pos, forward );
00071         G_PlayEffectID( G_EffectIndex( "turret/explode" ), pos, forward );
00072         
00073         if ( self->splashDamage > 0 && self->splashRadius > 0 )
00074         {
00075                 G_RadiusDamage( self->r.currentOrigin, 
00076                                                 attacker, 
00077                                                 self->splashDamage, 
00078                                                 self->splashRadius, 
00079                                                 attacker,
00080                                                 NULL,
00081                                                 MOD_UNKNOWN );
00082         }
00083 
00084         self->s.weapon = 0; // crosshair code uses this to mark crosshair red
00085 
00086 
00087         if ( self->s.modelindex2 )
00088         {
00089                 // switch to damage model if we should
00090                 self->s.modelindex = self->s.modelindex2;
00091 
00092                 if (self->target_ent && self->target_ent->s.modelindex2)
00093                 {
00094                         self->target_ent->s.modelindex = self->target_ent->s.modelindex2;
00095                 }
00096 
00097                 VectorCopy( self->r.currentAngles, self->s.apos.trBase );
00098                 VectorClear( self->s.apos.trDelta );
00099                 
00100                 if ( self->target )
00101                 {
00102                         G_UseTargets( self, attacker );
00103                 }
00104         }
00105         else
00106         {
00107                 ObjectDie( self, inflictor, attacker, damage, meansOfDeath );
00108         }
00109 }
00110 
00111 //------------------------------------------------------------------------------------------------------------
00112 void bottom_die ( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath )
00113 //------------------------------------------------------------------------------------------------------------
00114 {
00115         if (self->target_ent && self->target_ent->health > 0)
00116         {
00117                 self->target_ent->health = self->health;
00118                 if (self->target_ent->maxHealth)
00119                 {
00120                         G_ScaleNetHealth(self->target_ent);
00121                 }
00122                 auto_turret_die(self->target_ent, inflictor, attacker, damage, meansOfDeath);
00123         }
00124 }
00125 
00126 #define START_DIS 15
00127 
00128 //----------------------------------------------------------------
00129 static void turret_fire ( gentity_t *ent, vec3_t start, vec3_t dir )
00130 //----------------------------------------------------------------
00131 {
00132         vec3_t          org;
00133         gentity_t       *bolt;
00134 
00135         if ( (trap_PointContents( start, ent->s.number )&MASK_SHOT) )
00136         {
00137                 return;
00138         }
00139 
00140         VectorMA( start, -START_DIS, dir, org ); // dumb....
00141         G_PlayEffectID( ent->genericValue13, org, dir );
00142 
00143         bolt = G_Spawn();
00144         
00145         //use a custom shot effect
00146         bolt->s.otherEntityNum2 = ent->genericValue14;
00147         //use a custom impact effect
00148         bolt->s.emplacedOwner = ent->genericValue15;
00149 
00150         bolt->classname = "turret_proj";
00151         bolt->nextthink = level.time + 10000;
00152         bolt->think = G_FreeEntity;
00153         bolt->s.eType = ET_MISSILE;
00154         bolt->s.weapon = WP_EMPLACED_GUN;
00155         bolt->r.ownerNum = ent->s.number;
00156         bolt->damage = ent->damage;
00157         bolt->alliedTeam = ent->alliedTeam;
00158         bolt->teamnodmg = ent->teamnodmg;
00159         //bolt->dflags = DAMAGE_NO_KNOCKBACK;// | DAMAGE_HEAVY_WEAP_CLASS;              // Don't push them around, or else we are constantly re-aiming
00160         bolt->splashDamage = ent->damage;
00161         bolt->splashRadius = 100;
00162         bolt->methodOfDeath = MOD_TARGET_LASER;
00163         bolt->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
00164         //bolt->trigger_formation = qfalse;             // don't draw tail on first frame       
00165 
00166         VectorSet( bolt->r.maxs, 1.5, 1.5, 1.5 );
00167         VectorScale( bolt->r.maxs, -1, bolt->r.mins );
00168         bolt->s.pos.trType = TR_LINEAR;
00169         bolt->s.pos.trTime = level.time;
00170         VectorCopy( start, bolt->s.pos.trBase );
00171         VectorScale( dir, ent->mass, bolt->s.pos.trDelta );
00172         SnapVector( bolt->s.pos.trDelta );              // save net bandwidth
00173         VectorCopy( start, bolt->r.currentOrigin);
00174 
00175         bolt->parent = ent;
00176 }
00177 
00178 //-----------------------------------------------------
00179 void turret_head_think( gentity_t *self )
00180 //-----------------------------------------------------
00181 {
00182         gentity_t *top = &g_entities[self->r.ownerNum];
00183         if ( !top )
00184         {
00185                 return;
00186         }
00187         if ( self->painDebounceTime > level.time )
00188         {
00189                 vec3_t  v_up;
00190                 VectorSet( v_up, 0, 0, 1 );
00191                 G_PlayEffect( EFFECT_SPARKS, self->r.currentOrigin, v_up );
00192                 if ( Q_irand( 0, 3) )
00193                 {//25% chance of still firing
00194                         return;
00195                 }
00196         }
00197         // if it's time to fire and we have an enemy, then gun 'em down!  pushDebounce time controls next fire time
00198         if ( self->enemy && self->setTime < level.time && self->attackDebounceTime < level.time )
00199         {
00200                 vec3_t          fwd, org;
00201                 // set up our next fire time
00202                 self->setTime = level.time + self->wait;
00203 
00204                 /*
00205                 mdxaBone_t      boltMatrix;
00206 
00207                 // Getting the flash bolt here
00208                 gi.G2API_GetBoltMatrix( self->ghoul2, self->playerModel, 
00209                                         self->torsoBolt,
00210                                         &boltMatrix, self->r.currentAngles, self->r.currentOrigin, (cg.time?cg.time:level.time),
00211                                         NULL, self->s.modelScale );
00212 
00213                 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, org );
00214                 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, POSITIVE_Y, fwd );
00215                 */
00216                 VectorCopy( top->r.currentOrigin, org );
00217                 org[2] += top->r.maxs[2]-8;
00218                 AngleVectors( top->r.currentAngles, fwd, NULL, NULL );
00219 
00220                 VectorMA( org, START_DIS, fwd, org );
00221 
00222                 turret_fire( top, org, fwd );
00223                 self->fly_sound_debounce_time = level.time;//used as lastShotTime
00224         }
00225 }
00226 
00227 //-----------------------------------------------------
00228 static void turret_aim( gentity_t *self )
00229 //-----------------------------------------------------
00230 {
00231         vec3_t  enemyDir, org, org2;
00232         vec3_t  desiredAngles, setAngle;
00233         float   diffYaw = 0.0f, diffPitch = 0.0f, turnSpeed;
00234         const float pitchCap = 40.0f;
00235         gentity_t *top = &g_entities[self->r.ownerNum];
00236         if ( !top )
00237         {
00238                 return;
00239         }
00240 
00241         // move our gun base yaw to where we should be at this time....
00242         BG_EvaluateTrajectory( &top->s.apos, level.time, top->r.currentAngles );
00243         top->r.currentAngles[YAW] = AngleNormalize180( top->r.currentAngles[YAW] );
00244         top->r.currentAngles[PITCH] = AngleNormalize180( top->r.currentAngles[PITCH] );
00245         turnSpeed = top->speed;
00246 
00247         if ( self->painDebounceTime > level.time )
00248         {
00249                 desiredAngles[YAW] = top->r.currentAngles[YAW]+flrand(-45,45);
00250                 desiredAngles[PITCH] = top->r.currentAngles[PITCH]+flrand(-10,10);
00251 
00252                 if (desiredAngles[PITCH] < -pitchCap)
00253                 {
00254                         desiredAngles[PITCH] = -pitchCap;
00255                 }
00256                 else if (desiredAngles[PITCH] > pitchCap)
00257                 {
00258                         desiredAngles[PITCH] = pitchCap;
00259                 }
00260 
00261                 diffYaw = AngleSubtract( desiredAngles[YAW], top->r.currentAngles[YAW] );
00262                 diffPitch = AngleSubtract( desiredAngles[PITCH], top->r.currentAngles[PITCH] );
00263                 turnSpeed = flrand( -5, 5 );
00264         }
00265         else if ( self->enemy )
00266         {
00267                 // ...then we'll calculate what new aim adjustments we should attempt to make this frame
00268                 // Aim at enemy
00269                 VectorCopy( self->enemy->r.currentOrigin, org );
00270                 org[2]+=self->enemy->r.maxs[2]*0.5f;
00271                 if (self->enemy->s.eType == ET_NPC &&
00272                         self->enemy->s.NPC_class == CLASS_VEHICLE &&
00273                         self->enemy->m_pVehicle &&
00274                         self->enemy->m_pVehicle->m_pVehicleInfo->type == VH_WALKER)
00275                 { //hack!
00276                         org[2] += 32.0f;
00277                 }
00278                 /*
00279                 mdxaBone_t      boltMatrix;
00280 
00281                 // Getting the "eye" here
00282                 gi.G2API_GetBoltMatrix( self->ghoul2, self->playerModel, 
00283                                         self->torsoBolt,
00284                                         &boltMatrix, self->r.currentAngles, self->s.origin, (cg.time?cg.time:level.time),
00285                                         NULL, self->s.modelScale );
00286 
00287                 gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, org2 );
00288                 */
00289                 VectorCopy( top->r.currentOrigin, org2 );
00290 
00291                 VectorSubtract( org, org2, enemyDir );
00292                 vectoangles( enemyDir, desiredAngles );
00293                 desiredAngles[PITCH] = AngleNormalize180(desiredAngles[PITCH]);
00294 
00295                 if (desiredAngles[PITCH] < -pitchCap)
00296                 {
00297                         desiredAngles[PITCH] = -pitchCap;
00298                 }
00299                 else if (desiredAngles[PITCH] > pitchCap)
00300                 {
00301                         desiredAngles[PITCH] = pitchCap;
00302                 }
00303 
00304                 diffYaw = AngleSubtract( desiredAngles[YAW], top->r.currentAngles[YAW] );
00305                 diffPitch = AngleSubtract( desiredAngles[PITCH], top->r.currentAngles[PITCH] );
00306         }
00307         else
00308         {//FIXME: Pan back and forth in original facing
00309                 // no enemy, so make us slowly sweep back and forth as if searching for a new one
00310                 desiredAngles[YAW] = sin( level.time * 0.0001f + top->count );
00311                 desiredAngles[YAW] *=  60.0f;
00312                 desiredAngles[YAW] += self->s.angles[YAW];
00313                 desiredAngles[YAW] = AngleNormalize180( desiredAngles[YAW] );
00314                 diffYaw = AngleSubtract( desiredAngles[YAW], top->r.currentAngles[YAW] );
00315                 diffPitch = AngleSubtract( 0, top->r.currentAngles[PITCH] );
00316                 turnSpeed = 1.0f;
00317         }
00318 
00319         if ( diffYaw )
00320         {
00321                 // cap max speed....
00322                 if ( fabs(diffYaw) > turnSpeed )
00323                 {
00324                         diffYaw = ( diffYaw >= 0 ? turnSpeed : -turnSpeed );
00325                 }
00326         }
00327         if ( diffPitch )
00328         {
00329                 if ( fabs(diffPitch) > turnSpeed )
00330                 {
00331                         // cap max speed
00332                         diffPitch = (diffPitch > 0.0f ? turnSpeed : -turnSpeed );
00333                 }
00334         }
00335         // ...then set up our desired yaw
00336         VectorSet( setAngle, diffPitch, diffYaw, 0 );
00337 
00338         VectorCopy( top->r.currentAngles, top->s.apos.trBase );
00339         VectorScale( setAngle, (1000/FRAMETIME), top->s.apos.trDelta );
00340         top->s.apos.trTime = level.time;
00341         top->s.apos.trType = TR_LINEAR_STOP;
00342         top->s.apos.trDuration = FRAMETIME;
00343 
00344         if ( diffYaw || diffPitch )
00345         {
00346                 top->s.loopSound = G_SoundIndex( "sound/vehicles/weapons/hoth_turret/turn.wav" );
00347         }
00348         else
00349         {
00350                 top->s.loopSound = 0;
00351         }
00352 }
00353 
00354 //-----------------------------------------------------
00355 static void turret_turnoff( gentity_t *self )
00356 //-----------------------------------------------------
00357 {
00358         gentity_t *top = &g_entities[self->r.ownerNum];
00359         if ( top != NULL )
00360         {//still have a top
00361                 //stop it from rotating
00362                 VectorCopy( top->r.currentAngles, top->s.apos.trBase );
00363                 VectorClear( top->s.apos.trDelta );
00364                 top->s.apos.trTime = level.time;
00365                 top->s.apos.trType = TR_STATIONARY;
00366         }
00367 
00368         self->s.loopSound = 0;
00369         // shut-down sound
00370         //G_Sound( self, CHAN_BODY, G_SoundIndex( "sound/chars/turret/shutdown.wav" ));
00371 
00372         // Clear enemy
00373         self->enemy = NULL;
00374 }
00375 
00376 //-----------------------------------------------------
00377 static void turret_sleep( gentity_t *self )
00378 //-----------------------------------------------------
00379 {
00380         if ( self->enemy == NULL )
00381         {
00382                 // we don't need to play sound
00383                 return;
00384         }
00385 
00386         // make turret play ping sound for 5 seconds
00387         self->aimDebounceTime = level.time + 5000;
00388 
00389         // Clear enemy
00390         self->enemy = NULL;
00391 }
00392 
00393 //-----------------------------------------------------
00394 static qboolean turret_find_enemies( gentity_t *self )
00395 //-----------------------------------------------------
00396 {
00397         qboolean        found = qfalse;
00398         int                     i, count;
00399         float           bestDist = self->radius * self->radius;
00400         float           enemyDist;
00401         vec3_t          enemyDir, org, org2;
00402         gentity_t       *entity_list[MAX_GENTITIES], *target, *bestTarget = NULL;
00403         trace_t         tr;
00404         gentity_t *top = &g_entities[self->r.ownerNum];
00405         if ( !top )
00406         {
00407                 return qfalse;
00408         }
00409 
00410         if ( self->aimDebounceTime > level.time ) // time since we've been shut off
00411         {
00412                 // We were active and alert, i.e. had an enemy in the last 3 secs
00413                 if ( self->timestamp < level.time )
00414                 {
00415                         //G_Sound(self, CHAN_BODY, G_SoundIndex( "sound/chars/turret/ping.wav" ));
00416                         self->timestamp = level.time + 1000;
00417                 }
00418         }
00419 
00420         VectorCopy( top->r.currentOrigin, org2 );
00421 
00422         count = G_RadiusList( org2, self->radius, self, qtrue, entity_list );
00423 
00424         for ( i = 0; i < count; i++ )
00425         {
00426                 target = entity_list[i];
00427 
00428                 if ( !target->client )
00429                 {
00430                         // only attack clients
00431                         continue;
00432                 }
00433                 if ( target == self || !target->takedamage || target->health <= 0 || ( target->flags & FL_NOTARGET ))
00434                 {
00435                         continue;
00436                 }
00437                 if ( target->client->sess.sessionTeam == TEAM_SPECTATOR )
00438                 {
00439                         continue;
00440                 }
00441                 if ( self->alliedTeam )
00442                 {
00443                         if ( target->client )
00444                         {
00445                                 if ( target->client->sess.sessionTeam == self->alliedTeam )
00446                                 { 
00447                                         // A bot/client/NPC we don't want to shoot
00448                                         continue;
00449                                 }
00450                         }
00451                         else if ( target->teamnodmg == self->alliedTeam )
00452                         { 
00453                                 // An ent we don't want to shoot
00454                                 continue;
00455                         }
00456                 }
00457                 if ( !trap_InPVS( org2, target->r.currentOrigin ))
00458                 {
00459                         continue;
00460                 }
00461 
00462                 VectorCopy( target->r.currentOrigin, org );
00463                 org[2] += target->r.maxs[2]*0.5f;
00464 
00465                 trap_Trace( &tr, org2, NULL, NULL, org, self->s.number, MASK_SHOT );
00466 
00467                 if ( !tr.allsolid && !tr.startsolid && ( tr.fraction == 1.0 || tr.entityNum == target->s.number ))
00468                 {
00469                         // Only acquire if have a clear shot, Is it in range and closer than our best?
00470                         VectorSubtract( target->r.currentOrigin, top->r.currentOrigin, enemyDir );
00471                         enemyDist = VectorLengthSquared( enemyDir );
00472 
00473                         if ( enemyDist < bestDist // all things equal, keep current
00474                                 || (!Q_stricmp( "atst_vehicle", target->NPC_type ) && bestTarget && Q_stricmp( "atst_vehicle", bestTarget->NPC_type ) ) )//target AT-STs over non-AT-STs... FIXME: must be a better, easier way to tell this, no?
00475                         {
00476                                 if ( self->attackDebounceTime < level.time )
00477                                 {
00478                                         // We haven't fired or acquired an enemy in the last 2 seconds-start-up sound
00479                                         //G_Sound( self, CHAN_BODY, G_SoundIndex( "sound/chars/turret/startup.wav" ));
00480 
00481                                         // Wind up turrets for a bit
00482                                         self->attackDebounceTime = level.time + 1400;
00483                                 }
00484 
00485                                 bestTarget = target;
00486                                 bestDist = enemyDist;
00487                                 found = qtrue;
00488                         }
00489                 }
00490         }
00491 
00492         if ( found )
00493         {
00494                 G_SetEnemy( self, bestTarget );
00495                 if ( VALIDSTRING( self->target2 ))
00496                 {
00497                         G_UseTargets2( self, self, self->target2 );
00498                 }
00499         }
00500 
00501         return found;
00502 }
00503 
00504 //-----------------------------------------------------
00505 void turret_base_think( gentity_t *self )
00506 //-----------------------------------------------------
00507 {
00508         qboolean        turnOff = qtrue;
00509         float           enemyDist;
00510         vec3_t          enemyDir, org, org2;
00511 
00512         if ( self->spawnflags & 1 )
00513         {
00514                 // not turned on
00515                 turret_turnoff( self );
00516 
00517                 // No target
00518                 self->flags |= FL_NOTARGET;
00519                 self->nextthink = -1;//never think again
00520                 return;
00521         }
00522         else
00523         {
00524                 // I'm all hot and bothered
00525                 self->flags &= ~FL_NOTARGET;
00526                 //remember to keep thinking!
00527                 self->nextthink = level.time + FRAMETIME;
00528         }
00529 
00530         if ( !self->enemy )
00531         {
00532                 if ( turret_find_enemies( self ))
00533                 {
00534                         turnOff = qfalse;
00535                 }
00536         }
00537         else if ( self->enemy->client && self->enemy->client->sess.sessionTeam == TEAM_SPECTATOR )
00538         {//don't keep going after spectators
00539                 self->enemy = NULL;
00540         }
00541         else
00542         {//FIXME: remain single-minded or look for a new enemy every now and then?
00543                 if ( self->enemy->health > 0 )
00544                 {
00545                         // enemy is alive
00546                         VectorSubtract( self->enemy->r.currentOrigin, self->r.currentOrigin, enemyDir );
00547                         enemyDist = VectorLengthSquared( enemyDir );
00548 
00549                         if ( enemyDist < (self->radius * self->radius) )
00550                         {
00551                                 // was in valid radius
00552                                 if ( trap_InPVS( self->r.currentOrigin, self->enemy->r.currentOrigin ) )
00553                                 {
00554                                         // Every now and again, check to see if we can even trace to the enemy
00555                                         trace_t tr;
00556 
00557                                         if ( self->enemy->client )
00558                                         {
00559                                                 VectorCopy( self->enemy->client->renderInfo.eyePoint, org );
00560                                         }
00561                                         else
00562                                         {
00563                                                 VectorCopy( self->enemy->r.currentOrigin, org );
00564                                         }
00565                                         VectorCopy( self->r.currentOrigin, org2 );
00566                                         if ( self->spawnflags & 2 )
00567                                         {
00568                                                 org2[2] += 10;
00569                                         }
00570                                         else
00571                                         {
00572                                                 org2[2] -= 10;
00573                                         }
00574                                         trap_Trace( &tr, org2, NULL, NULL, org, self->s.number, MASK_SHOT );
00575 
00576                                         if ( !tr.allsolid && !tr.startsolid && tr.entityNum == self->enemy->s.number )
00577                                         {
00578                                                 turnOff = qfalse;       // Can see our enemy
00579                                         }
00580                                 }
00581                         }
00582                 }
00583 
00584                 turret_head_think( self );
00585         }
00586 
00587         if ( turnOff )
00588         {
00589                 if ( self->bounceCount < level.time ) // bounceCount is used to keep the thing from ping-ponging from on to off
00590                 {
00591                         turret_sleep( self );
00592                 }
00593         }
00594         else
00595         {
00596                 // keep our enemy for a minimum of 2 seconds from now
00597                 self->bounceCount = level.time + 2000 + random() * 150;
00598         }
00599 
00600         turret_aim( self );
00601 }
00602 
00603 //-----------------------------------------------------------------------------
00604 void turret_base_use( gentity_t *self, gentity_t *other, gentity_t *activator )
00605 //-----------------------------------------------------------------------------
00606 {
00607         // Toggle on and off
00608         self->spawnflags = (self->spawnflags ^ 1);
00609 
00610         /*
00611         if (( self->s.eFlags & EF_SHADER_ANIM ) && ( self->spawnflags & 1 )) // Start_Off
00612         {
00613                 self->s.frame = 1; // black
00614         }
00615         else
00616         {
00617                 self->s.frame = 0; // glow
00618         }
00619         */
00620 }
00621 
00622 
00623 /*QUAKED misc_turret (1 0 0) (-48 -48 0) (48 48 144) START_OFF
00624 Large 2-piece turbolaser turret
00625 
00626   START_OFF - Starts off
00627 
00628   radius - How far away an enemy can be for it to pick it up (default 1024)
00629   wait  - Time between shots (default 300 ms)
00630   dmg   - How much damage each shot does (default 100)
00631   health - How much damage it can take before exploding (default 3000)
00632   speed - how fast it turns (default 10)
00633   
00634   splashDamage - How much damage the explosion does (300)
00635   splashRadius - The radius of the explosion (128)
00636 
00637   shotspeed - speed at which projectiles will move
00638   
00639   targetname - Toggles it on/off
00640   target - What to use when destroyed
00641   target2 - What to use when it decides to start shooting at an enemy
00642 
00643   showhealth - set to 1 to show health bar on this entity when crosshair is over it
00644   
00645   teamowner - crosshair shows green for this team, red for opposite team
00646         0 - none
00647         1 - red
00648         2 - blue
00649 
00650   alliedTeam - team that this turret won't target
00651         0 - none
00652         1 - red
00653         2 - blue
00654 
00655   teamnodmg - team that turret does not take damage from
00656         0 - none
00657         1 - red
00658         2 - blue
00659 
00660 "icon" - icon that represents the objective on the radar
00661 */
00662 //-----------------------------------------------------
00663 void SP_misc_turret( gentity_t *base )
00664 //-----------------------------------------------------
00665 {
00666         char* s;
00667 
00668         base->s.modelindex2 = G_ModelIndex( "models/map_objects/hoth/turret_bottom.md3" );
00669         base->s.modelindex = G_ModelIndex( "models/map_objects/hoth/turret_base.md3" );
00670         //base->playerModel = gi.G2API_InitGhoul2Model( base->ghoul2, "models/map_objects/imp_mine/turret_canon.glm", base->s.modelindex );
00671         //base->s.radius = 80.0f;
00672 
00673         //gi.G2API_SetBoneAngles( &base->ghoul2[base->playerModel], "Bone_body", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_Y, POSITIVE_Z, POSITIVE_X, NULL ); 
00674         //base->torsoBolt = gi.G2API_AddBolt( &base->ghoul2[base->playerModel], "*flash03" );
00675 
00676         G_SpawnString( "icon", "", &s );
00677         if (s && s[0])
00678         { 
00679                 // We have an icon, so index it now.  We are reusing the genericenemyindex
00680                 // variable rather than adding a new one to the entity state.
00681                 base->s.genericenemyindex = G_IconIndex(s);
00682         }
00683 
00684         G_SetAngles( base, base->s.angles );
00685         G_SetOrigin( base, base->s.origin );
00686 
00687         base->r.contents = CONTENTS_BODY;
00688 
00689         VectorSet( base->r.maxs, 32.0f, 32.0f, 128.0f );
00690         VectorSet( base->r.mins, -32.0f, -32.0f, 0.0f );
00691 
00692         base->use = turret_base_use;
00693         base->think = turret_base_think;
00694         // don't start working right away
00695         base->nextthink = level.time + FRAMETIME * 5;
00696 
00697         trap_LinkEntity( base );
00698 
00699         if ( !turret_base_spawn_top( base ) )
00700         {
00701                 G_FreeEntity( base );
00702         }
00703 }
00704 
00705 //-----------------------------------------------------
00706 qboolean turret_base_spawn_top( gentity_t *base )
00707 {
00708         vec3_t          org;
00709         int                     t;
00710 
00711         gentity_t *top = G_Spawn();
00712         if ( !top )
00713         {
00714                 return qfalse;
00715         }
00716 
00717         top->s.modelindex = G_ModelIndex( "models/map_objects/hoth/turret_top_new.md3" );
00718         top->s.modelindex2 = G_ModelIndex( "models/map_objects/hoth/turret_top.md3" );
00719         G_SetAngles( top, base->s.angles );
00720         VectorCopy( base->s.origin, org );
00721         org[2] += 128;
00722         G_SetOrigin( top, org );
00723 
00724         base->r.ownerNum = top->s.number;
00725         top->r.ownerNum = base->s.number;
00726 
00727         if ( base->team && base->team[0] && //g_gametype.integer == GT_SIEGE &&
00728                 !base->teamnodmg)
00729         {
00730                 base->teamnodmg = atoi(base->team);
00731         }
00732         base->team = NULL;
00733         top->teamnodmg = base->teamnodmg;
00734         top->alliedTeam = base->alliedTeam;
00735 
00736         base->s.eType = ET_GENERAL;
00737 
00738         // Set up our explosion effect for the ExplodeDeath code....
00739         G_EffectIndex( "turret/explode" );
00740         G_EffectIndex( "sparks/spark_exp_nosnd" );
00741         G_EffectIndex( "turret/hoth_muzzle_flash" );
00742 
00743         // this is really the pitch angle.....
00744         top->speed = 0;
00745 
00746         // this is a random time offset for the no-enemy-search-around-mode
00747         top->count = random() * 9000;
00748 
00749         if ( !base->health )
00750         {
00751                 base->health = 3000;
00752         }
00753         top->health = base->health;
00754 
00755         G_SpawnInt( "showhealth", "0", &t );
00756 
00757         if (t)
00758         { //a non-0 maxhealth value will mean we want to show the health on the hud
00759                 top->maxHealth = base->health; //acts as "maxhealth"
00760                 G_ScaleNetHealth(top);
00761 
00762                 base->maxHealth = base->health;
00763                 G_ScaleNetHealth(base);
00764         }
00765 
00766         base->takedamage = qtrue;
00767         base->pain = TurretBasePain;
00768         base->die = bottom_die;
00769 
00770         //design specified shot speed
00771         G_SpawnFloat( "shotspeed", "1100", &base->mass );
00772         top->mass = base->mass;
00773 
00774         //even if we don't want to show health, let's at least light the crosshair up properly over ourself
00775         if ( !top->s.teamowner )
00776         {
00777                 top->s.teamowner = top->alliedTeam;
00778         }
00779 
00780         base->alliedTeam = top->alliedTeam;
00781         base->s.teamowner = top->s.teamowner;
00782 
00783         base->s.shouldtarget = qtrue;
00784         top->s.shouldtarget = qtrue;
00785 
00786         //link them to each other
00787         base->target_ent = top;
00788         top->target_ent = base;
00789 
00790         //top->s.owner = MAX_CLIENTS; //not owned by any client
00791 
00792         // search radius
00793         if ( !base->radius )
00794         {
00795                 base->radius = 1024;
00796         }
00797         top->radius = base->radius;
00798 
00799         // How quickly to fire
00800         if ( !base->wait )
00801         {
00802                 base->wait = 300 + random() * 55;
00803         }
00804         top->wait = base->wait;
00805 
00806         if ( !base->splashDamage )
00807         {
00808                 base->splashDamage = 300;
00809         }
00810         top->splashDamage = base->splashDamage;
00811 
00812         if ( !base->splashRadius )
00813         {
00814                 base->splashRadius = 128;
00815         }
00816         top->splashRadius = base->splashRadius;
00817 
00818         // how much damage each shot does
00819         if ( !base->damage )
00820         {
00821                 base->damage = 100;
00822         }
00823         top->damage = base->damage;
00824 
00825         // how fast it turns
00826         if ( !base->speed )
00827         {
00828                 base->speed = 20;
00829         }
00830         top->speed = base->speed;
00831 
00832         VectorSet( top->r.maxs, 48.0f, 48.0f, 16.0f );
00833         VectorSet( top->r.mins, -48.0f, -48.0f, 0.0f );
00834         // Precache moving sounds
00835         //G_SoundIndex( "sound/chars/turret/startup.wav" );
00836         //G_SoundIndex( "sound/chars/turret/shutdown.wav" );
00837         //G_SoundIndex( "sound/chars/turret/ping.wav" );
00838         G_SoundIndex( "sound/vehicles/weapons/hoth_turret/turn.wav" );
00839         top->genericValue13 = G_EffectIndex( "turret/hoth_muzzle_flash" );
00840         top->genericValue14 = G_EffectIndex( "turret/hoth_shot" );
00841         top->genericValue15 = G_EffectIndex( "turret/hoth_impact" );
00842 
00843         top->r.contents = CONTENTS_BODY;
00844 
00845         //base->max_health = base->health;
00846         top->takedamage = qtrue;
00847         top->pain = TurretPain;
00848         top->die  = auto_turret_die;
00849 
00850         top->material = MAT_METAL;
00851         //base->r.svFlags |= SVF_NO_TELEPORT|SVF_NONNPC_ENEMY|SVF_SELF_ANIMATING;
00852 
00853         // Register this so that we can use it for the missile effect
00854         RegisterItem( BG_FindItemForWeapon( WP_EMPLACED_GUN ));
00855 
00856         // But set us as a turret so that we can be identified as a turret
00857         top->s.weapon = WP_EMPLACED_GUN;
00858 
00859         trap_LinkEntity( top );
00860         return qtrue;
00861 }