codemp/game/NPC_AI_Seeker.c

Go to the documentation of this file.
00001 #include "b_local.h"
00002 #include "g_nav.h"
00003 
00004 extern void Boba_FireDecide( void );
00005 
00006 void Seeker_Strafe( void );
00007 
00008 #define VELOCITY_DECAY          0.7f
00009 
00010 #define MIN_MELEE_RANGE         320
00011 #define MIN_MELEE_RANGE_SQR     ( MIN_MELEE_RANGE * MIN_MELEE_RANGE )
00012 
00013 #define MIN_DISTANCE            80
00014 #define MIN_DISTANCE_SQR        ( MIN_DISTANCE * MIN_DISTANCE )
00015 
00016 #define SEEKER_STRAFE_VEL       100
00017 #define SEEKER_STRAFE_DIS       200
00018 #define SEEKER_UPWARD_PUSH      32
00019 
00020 #define SEEKER_FORWARD_BASE_SPEED       10
00021 #define SEEKER_FORWARD_MULTIPLIER       2
00022 
00023 #define SEEKER_SEEK_RADIUS                      1024
00024 
00025 //------------------------------------
00026 void NPC_Seeker_Precache(void)
00027 {
00028         G_SoundIndex("sound/chars/seeker/misc/fire.wav");
00029         G_SoundIndex( "sound/chars/seeker/misc/hiss.wav");
00030         G_EffectIndex( "env/small_explode");
00031 }
00032 
00033 //------------------------------------
00034 void NPC_Seeker_Pain(gentity_t *self, gentity_t *attacker, int damage)
00035 {
00036         if ( !(self->NPC->aiFlags&NPCAI_CUSTOM_GRAVITY ))
00037         {//void G_Damage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker, vec3_t dir, vec3_t point, int damage, int dflags, int mod, int hitLoc=HL_NONE );
00038                 G_Damage( self, NULL, NULL, (float*)vec3_origin, (float*)vec3_origin, 999, 0, MOD_FALLING );
00039         }
00040 
00041         SaveNPCGlobals();
00042         SetNPCGlobals( self );
00043         Seeker_Strafe();
00044         RestoreNPCGlobals();
00045         NPC_Pain( self, attacker, damage );
00046 }
00047 
00048 //------------------------------------
00049 void Seeker_MaintainHeight( void )
00050 {       
00051         float   dif;
00052 
00053         // Update our angles regardless
00054         NPC_UpdateAngles( qtrue, qtrue );
00055 
00056         // If we have an enemy, we should try to hover at or a little below enemy eye level
00057         if ( NPC->enemy )
00058         {
00059                 if (TIMER_Done( NPC, "heightChange" ))
00060                 {
00061                         float difFactor;
00062 
00063                         TIMER_Set( NPC,"heightChange",Q_irand( 1000, 3000 ));
00064 
00065                         // Find the height difference
00066                         dif = (NPC->enemy->r.currentOrigin[2] +  flrand( NPC->enemy->r.maxs[2]/2, NPC->enemy->r.maxs[2]+8 )) - NPC->r.currentOrigin[2]; 
00067 
00068                         difFactor = 1.0f;
00069                         if ( NPC->client->NPC_class == CLASS_BOBAFETT )
00070                         {
00071                                 if ( TIMER_Done( NPC, "flameTime" ) )
00072                                 {
00073                                         difFactor = 10.0f;
00074                                 }
00075                         }
00076 
00077                         // cap to prevent dramatic height shifts
00078                         if ( fabs( dif ) > 2*difFactor )
00079                         {
00080                                 if ( fabs( dif ) > 24*difFactor )
00081                                 {
00082                                         dif = ( dif < 0 ? -24*difFactor : 24*difFactor );
00083                                 }
00084 
00085                                 NPC->client->ps.velocity[2] = (NPC->client->ps.velocity[2]+dif)/2;
00086                         }
00087                         if ( NPC->client->NPC_class == CLASS_BOBAFETT )
00088                         {
00089                                 NPC->client->ps.velocity[2] *= flrand( 0.85f, 3.0f );
00090                         }
00091                 }
00092         }
00093         else
00094         {
00095                 gentity_t *goal = NULL;
00096 
00097                 if ( NPCInfo->goalEntity )      // Is there a goal?
00098                 {
00099                         goal = NPCInfo->goalEntity;
00100                 }
00101                 else
00102                 {
00103                         goal = NPCInfo->lastGoalEntity;
00104                 }
00105                 if ( goal )
00106                 {
00107                         dif = goal->r.currentOrigin[2] - NPC->r.currentOrigin[2];
00108 
00109                         if ( fabs( dif ) > 24 )
00110                         {
00111                                 ucmd.upmove = ( ucmd.upmove < 0 ? -4 : 4 );
00112                         }
00113                         else
00114                         {
00115                                 if ( NPC->client->ps.velocity[2] )
00116                                 {
00117                                         NPC->client->ps.velocity[2] *= VELOCITY_DECAY;
00118 
00119                                         if ( fabs( NPC->client->ps.velocity[2] ) < 2 )
00120                                         {
00121                                                 NPC->client->ps.velocity[2] = 0;
00122                                         }
00123                                 }
00124                         }
00125                 }
00126         }
00127 
00128         // Apply friction
00129         if ( NPC->client->ps.velocity[0] )
00130         {
00131                 NPC->client->ps.velocity[0] *= VELOCITY_DECAY;
00132 
00133                 if ( fabs( NPC->client->ps.velocity[0] ) < 1 )
00134                 {
00135                         NPC->client->ps.velocity[0] = 0;
00136                 }
00137         }
00138 
00139         if ( NPC->client->ps.velocity[1] )
00140         {
00141                 NPC->client->ps.velocity[1] *= VELOCITY_DECAY;
00142 
00143                 if ( fabs( NPC->client->ps.velocity[1] ) < 1 )
00144                 {
00145                         NPC->client->ps.velocity[1] = 0;
00146                 }
00147         }
00148 }
00149 
00150 //------------------------------------
00151 void Seeker_Strafe( void )
00152 {
00153         int             side;
00154         vec3_t  end, right, dir;
00155         trace_t tr;
00156 
00157         if ( random() > 0.7f || !NPC->enemy || !NPC->enemy->client )
00158         {
00159                 // Do a regular style strafe
00160                 AngleVectors( NPC->client->renderInfo.eyeAngles, NULL, right, NULL );
00161 
00162                 // Pick a random strafe direction, then check to see if doing a strafe would be
00163                 //      reasonably valid
00164                 side = ( rand() & 1 ) ? -1 : 1;
00165                 VectorMA( NPC->r.currentOrigin, SEEKER_STRAFE_DIS * side, right, end );
00166 
00167                 trap_Trace( &tr, NPC->r.currentOrigin, NULL, NULL, end, NPC->s.number, MASK_SOLID );
00168 
00169                 // Close enough
00170                 if ( tr.fraction > 0.9f )
00171                 {
00172                         float vel = SEEKER_STRAFE_VEL;
00173                         float upPush = SEEKER_UPWARD_PUSH;
00174                         if ( NPC->client->NPC_class != CLASS_BOBAFETT )
00175                         {
00176                                 G_Sound( NPC, CHAN_AUTO, G_SoundIndex( "sound/chars/seeker/misc/hiss" ));
00177                         }
00178                         else
00179                         {
00180                                 vel *= 3.0f;
00181                                 upPush *= 4.0f;
00182                         }
00183                         VectorMA( NPC->client->ps.velocity, vel*side, right, NPC->client->ps.velocity );
00184                         // Add a slight upward push
00185                         NPC->client->ps.velocity[2] += upPush;
00186 
00187                         NPCInfo->standTime = level.time + 1000 + random() * 500;
00188                 }
00189         }
00190         else
00191         {
00192                 float stDis;
00193 
00194                 // Do a strafe to try and keep on the side of their enemy
00195                 AngleVectors( NPC->enemy->client->renderInfo.eyeAngles, dir, right, NULL );
00196 
00197                 // Pick a random side
00198                 side = ( rand() & 1 ) ? -1 : 1;
00199                 stDis = SEEKER_STRAFE_DIS;
00200                 if ( NPC->client->NPC_class == CLASS_BOBAFETT )
00201                 {
00202                         stDis *= 2.0f;
00203                 }
00204                 VectorMA( NPC->enemy->r.currentOrigin, stDis * side, right, end );
00205 
00206                 // then add a very small bit of random in front of/behind the player action
00207                 VectorMA( end, crandom() * 25, dir, end );
00208 
00209                 trap_Trace( &tr, NPC->r.currentOrigin, NULL, NULL, end, NPC->s.number, MASK_SOLID );
00210 
00211                 // Close enough
00212                 if ( tr.fraction > 0.9f )
00213                 {
00214                         float dis, upPush;
00215 
00216                         VectorSubtract( tr.endpos, NPC->r.currentOrigin, dir );
00217                         dir[2] *= 0.25; // do less upward change
00218                         dis = VectorNormalize( dir );
00219 
00220                         // Try to move the desired enemy side
00221                         VectorMA( NPC->client->ps.velocity, dis, dir, NPC->client->ps.velocity );
00222 
00223                         upPush = SEEKER_UPWARD_PUSH;
00224                         if ( NPC->client->NPC_class != CLASS_BOBAFETT )
00225                         {
00226                                 G_Sound( NPC, CHAN_AUTO, G_SoundIndex( "sound/chars/seeker/misc/hiss" ));
00227                         }
00228                         else
00229                         {
00230                                 upPush *= 4.0f;
00231                         }
00232 
00233                         // Add a slight upward push
00234                         NPC->client->ps.velocity[2] += upPush;
00235 
00236                         NPCInfo->standTime = level.time + 2500 + random() * 500;
00237                 }
00238         }
00239 }
00240 
00241 //------------------------------------
00242 void Seeker_Hunt( qboolean visible, qboolean advance )
00243 {
00244         float   distance, speed;
00245         vec3_t  forward;
00246 
00247         NPC_FaceEnemy( qtrue );
00248 
00249         // If we're not supposed to stand still, pursue the player
00250         if ( NPCInfo->standTime < level.time )
00251         {
00252                 // Only strafe when we can see the player
00253                 if ( visible )
00254                 {
00255                         Seeker_Strafe();
00256                         return;
00257                 }
00258         }
00259 
00260         // If we don't want to advance, stop here
00261         if ( advance == qfalse )
00262         {
00263                 return;
00264         }
00265 
00266         // Only try and navigate if the player is visible
00267         if ( visible == qfalse )
00268         {
00269                 // Move towards our goal
00270                 NPCInfo->goalEntity = NPC->enemy;
00271                 NPCInfo->goalRadius = 24;
00272 
00273                 // Get our direction from the navigator if we can't see our target
00274                 if ( NPC_GetMoveDirection( forward, &distance ) == qfalse )
00275                 {
00276                         return;
00277                 }
00278         }
00279         else
00280         {
00281                 VectorSubtract( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin, forward );
00282                 distance = VectorNormalize( forward );
00283         }
00284 
00285         speed = SEEKER_FORWARD_BASE_SPEED + SEEKER_FORWARD_MULTIPLIER * g_spskill.integer;
00286         VectorMA( NPC->client->ps.velocity, speed, forward, NPC->client->ps.velocity );
00287 }
00288 
00289 //------------------------------------
00290 void Seeker_Fire( void )
00291 {
00292         vec3_t          dir, enemy_org, muzzle;
00293         gentity_t       *missile;
00294 
00295         CalcEntitySpot( NPC->enemy, SPOT_HEAD, enemy_org );
00296         VectorSubtract( enemy_org, NPC->r.currentOrigin, dir );
00297         VectorNormalize( dir );
00298 
00299         // move a bit forward in the direction we shall shoot in so that the bolt doesn't poke out the other side of the seeker
00300         VectorMA( NPC->r.currentOrigin, 15, dir, muzzle );
00301 
00302         missile = CreateMissile( muzzle, dir, 1000, 10000, NPC, qfalse );
00303 
00304         G_PlayEffectID( G_EffectIndex("blaster/muzzle_flash"), NPC->r.currentOrigin, dir );
00305 
00306         missile->classname = "blaster";
00307         missile->s.weapon = WP_BLASTER;
00308 
00309         missile->damage = 5;
00310         missile->dflags = DAMAGE_DEATH_KNOCKBACK;
00311         missile->methodOfDeath = MOD_BLASTER;
00312         missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
00313         if ( NPC->r.ownerNum < ENTITYNUM_NONE )
00314         {
00315                 missile->r.ownerNum = NPC->r.ownerNum;
00316         }
00317 }
00318 
00319 //------------------------------------
00320 void Seeker_Ranged( qboolean visible, qboolean advance )
00321 {
00322         if ( NPC->client->NPC_class != CLASS_BOBAFETT )
00323         {
00324                 if ( NPC->count > 0 )
00325                 {
00326                         if ( TIMER_Done( NPC, "attackDelay" ))  // Attack?
00327                         {
00328                                 TIMER_Set( NPC, "attackDelay", Q_irand( 250, 2500 ));
00329                                 Seeker_Fire();
00330                                 NPC->count--;
00331                         }
00332                 }
00333                 else
00334                 {
00335                         // out of ammo, so let it die...give it a push up so it can fall more and blow up on impact
00336         //              NPC->client->ps.gravity = 900;
00337         //              NPC->svFlags &= ~SVF_CUSTOM_GRAVITY;
00338         //              NPC->client->ps.velocity[2] += 16;
00339                         G_Damage( NPC, NPC, NPC, NULL, NULL, 999, 0, MOD_UNKNOWN );
00340                 }
00341         }
00342 
00343         if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES )
00344         {
00345                 Seeker_Hunt( visible, advance );
00346         }
00347 } 
00348 
00349 //------------------------------------
00350 void Seeker_Attack( void )
00351 {
00352         float           distance;
00353         qboolean        visible;
00354         qboolean        advance;
00355 
00356         // Always keep a good height off the ground
00357         Seeker_MaintainHeight();
00358 
00359         // Rate our distance to the target, and our visibilty
00360         distance        = DistanceHorizontalSquared( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin );       
00361         visible         = NPC_ClearLOS4( NPC->enemy );
00362         advance         = (qboolean)(distance > MIN_DISTANCE_SQR);
00363 
00364         if ( NPC->client->NPC_class == CLASS_BOBAFETT )
00365         {
00366                 advance = (qboolean)(distance>(200.0f*200.0f));
00367         }
00368 
00369         // If we cannot see our target, move to see it
00370         if ( visible == qfalse )
00371         {
00372                 if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES )
00373                 {
00374                         Seeker_Hunt( visible, advance );
00375                         return;
00376                 }
00377         }
00378 
00379         Seeker_Ranged( visible, advance );
00380 }
00381 
00382 //------------------------------------
00383 void Seeker_FindEnemy( void )
00384 {
00385         int                     numFound;
00386         float           dis, bestDis = SEEKER_SEEK_RADIUS * SEEKER_SEEK_RADIUS + 1;
00387         vec3_t          mins, maxs;
00388         int                     entityList[MAX_GENTITIES];
00389         gentity_t       *ent, *best = NULL;
00390         int                     i;
00391 
00392         VectorSet( maxs, SEEKER_SEEK_RADIUS, SEEKER_SEEK_RADIUS, SEEKER_SEEK_RADIUS );
00393         VectorScale( maxs, -1, mins );
00394 
00395         numFound = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
00396 
00397         for ( i = 0 ; i < numFound ; i++ ) 
00398         {
00399                 ent = &g_entities[entityList[i]];
00400 
00401                 if ( ent->s.number == NPC->s.number 
00402                         || !ent->client //&& || !ent->NPC 
00403                         || ent->health <= 0 
00404                         || !ent->inuse )
00405                 {
00406                         continue;
00407                 }
00408 
00409                 if ( ent->client->playerTeam == NPC->client->playerTeam || ent->client->playerTeam == NPCTEAM_NEUTRAL ) // don't attack same team or bots
00410                 {
00411                         continue;
00412                 }
00413 
00414                 // try to find the closest visible one
00415                 if ( !NPC_ClearLOS4( ent ))
00416                 {
00417                         continue;
00418                 }
00419 
00420                 dis = DistanceHorizontalSquared( NPC->r.currentOrigin, ent->r.currentOrigin );
00421 
00422                 if ( dis <= bestDis )
00423                 {
00424                         bestDis = dis;
00425                         best = ent;
00426                 }
00427         }
00428 
00429         if ( best )
00430         {
00431                 // used to offset seekers around a circle so they don't occupy the same spot.  This is not a fool-proof method.
00432                 NPC->random = random() * 6.3f; // roughly 2pi
00433 
00434                 NPC->enemy = best;
00435         }
00436 }
00437 
00438 //------------------------------------
00439 void Seeker_FollowOwner( void )
00440 {
00441         float   dis, minDistSqr;
00442         vec3_t  pt, dir;
00443         gentity_t       *owner = &g_entities[NPC->s.owner];
00444 
00445         Seeker_MaintainHeight();
00446 
00447         if ( NPC->client->NPC_class == CLASS_BOBAFETT )
00448         {
00449                 owner = NPC->enemy;
00450         }
00451         if ( !owner || owner == NPC || !owner->client )
00452         {
00453                 return;
00454         }
00455         //rwwFIXMEFIXME: Care about all clients not just 0
00456         dis     = DistanceHorizontalSquared( NPC->r.currentOrigin, owner->r.currentOrigin );
00457         
00458         minDistSqr = MIN_DISTANCE_SQR;
00459 
00460         if ( NPC->client->NPC_class == CLASS_BOBAFETT )
00461         {
00462                 if ( TIMER_Done( NPC, "flameTime" ) )
00463                 {
00464                         minDistSqr = 200*200;
00465                 }
00466         }
00467 
00468         if ( dis < minDistSqr )
00469         {
00470                 // generally circle the player closely till we take an enemy..this is our target point
00471                 if ( NPC->client->NPC_class == CLASS_BOBAFETT )
00472                 {
00473                         pt[0] = owner->r.currentOrigin[0] + cos( level.time * 0.001f + NPC->random ) * 250;
00474                         pt[1] = owner->r.currentOrigin[1] + sin( level.time * 0.001f + NPC->random ) * 250;
00475                         if ( NPC->client->jetPackTime < level.time )
00476                         {
00477                                 pt[2] = NPC->r.currentOrigin[2] - 64;
00478                         }
00479                         else
00480                         {
00481                                 pt[2] = owner->r.currentOrigin[2] + 200;
00482                         }
00483                 }
00484                 else
00485                 {
00486                         pt[0] = owner->r.currentOrigin[0] + cos( level.time * 0.001f + NPC->random ) * 56;
00487                         pt[1] = owner->r.currentOrigin[1] + sin( level.time * 0.001f + NPC->random ) * 56;
00488                         pt[2] = owner->r.currentOrigin[2] + 40;
00489                 }
00490 
00491                 VectorSubtract( pt, NPC->r.currentOrigin, dir );
00492                 VectorMA( NPC->client->ps.velocity, 0.8f, dir, NPC->client->ps.velocity );
00493         }
00494         else
00495         {
00496                 if ( NPC->client->NPC_class != CLASS_BOBAFETT )
00497                 {
00498                         if ( TIMER_Done( NPC, "seekerhiss" ))
00499                         {
00500                                 TIMER_Set( NPC, "seekerhiss", 1000 + random() * 1000 );
00501                                 G_Sound( NPC, CHAN_AUTO, G_SoundIndex( "sound/chars/seeker/misc/hiss" ));
00502                         }
00503                 }
00504 
00505                 // Hey come back!
00506                 NPCInfo->goalEntity = owner;
00507                 NPCInfo->goalRadius = 32;
00508                 NPC_MoveToGoal( qtrue );
00509                 NPC->parent = owner;
00510         }
00511 
00512         if ( NPCInfo->enemyCheckDebounceTime < level.time )
00513         {
00514                 // check twice a second to find a new enemy
00515                 Seeker_FindEnemy();
00516                 NPCInfo->enemyCheckDebounceTime = level.time + 500;
00517         }
00518 
00519         NPC_UpdateAngles( qtrue, qtrue );
00520 }
00521 
00522 //------------------------------------
00523 void NPC_BSSeeker_Default( void )
00524 {
00525         /*
00526         if ( in_camera )
00527         {
00528                 if ( NPC->client->NPC_class != CLASS_BOBAFETT )
00529                 {
00530                         // cameras make me commit suicide....
00531                         G_Damage( NPC, NPC, NPC, NULL, NULL, 999, 0, MOD_UNKNOWN );
00532                 }
00533         }
00534         */
00535         //N/A for MP.
00536         if ( NPC->r.ownerNum < ENTITYNUM_NONE )
00537         {
00538                 gentity_t *owner = &g_entities[0];
00539                 if ( owner->health <= 0 
00540                         || (owner->client && owner->client->pers.connected == CON_DISCONNECTED) )
00541                 {//owner is dead or gone
00542                         //remove me
00543                         G_Damage( NPC, NULL, NULL, NULL, NULL, 10000, DAMAGE_NO_PROTECTION, MOD_TELEFRAG );
00544                         return;
00545                 }
00546         }
00547 
00548         if ( NPC->random == 0.0f )
00549         {
00550                 // used to offset seekers around a circle so they don't occupy the same spot.  This is not a fool-proof method.
00551                 NPC->random = random() * 6.3f; // roughly 2pi
00552         }
00553 
00554         if ( NPC->enemy && NPC->enemy->health && NPC->enemy->inuse )
00555         {
00556                 if ( NPC->client->NPC_class != CLASS_BOBAFETT && ( NPC->enemy->s.number == 0 || ( NPC->enemy->client && NPC->enemy->client->NPC_class == CLASS_SEEKER )) )
00557                 {
00558                         //hacked to never take the player as an enemy, even if the player shoots at it
00559                         NPC->enemy = NULL;
00560                 }
00561                 else
00562                 {
00563                         Seeker_Attack();
00564                         if ( NPC->client->NPC_class == CLASS_BOBAFETT )
00565                         {
00566                                 Boba_FireDecide();
00567                         }
00568                         return;
00569                 }
00570         }
00571 
00572         // In all other cases, follow the player and look for enemies to take on
00573         Seeker_FollowOwner();
00574 }