codemp/game/NPC_AI_Stormtrooper.c

Go to the documentation of this file.
00001 #include "b_local.h"
00002 #include "g_nav.h"
00003 #include "anims.h"
00004 
00005 extern void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime );
00006 extern void AI_GroupUpdateSquadstates( AIGroupInfo_t *group, gentity_t *member, int newSquadState );
00007 extern qboolean AI_GroupContainsEntNum( AIGroupInfo_t *group, int entNum );
00008 extern void AI_GroupUpdateEnemyLastSeen( AIGroupInfo_t *group, vec3_t spot );
00009 extern void AI_GroupUpdateClearShotTime( AIGroupInfo_t *group );
00010 extern void NPC_TempLookTarget( gentity_t *self, int lookEntNum, int minLookTime, int maxLookTime );
00011 extern qboolean G_ExpandPointToBBox( vec3_t point, const vec3_t mins, const vec3_t maxs, int ignore, int clipmask );
00012 extern void ChangeWeapon( gentity_t *ent, int newWeapon );
00013 extern void NPC_CheckGetNewWeapon( void );
00014 extern int GetTime ( int lastTime );
00015 extern void NPC_AimAdjust( int change );
00016 extern qboolean FlyingCreature( gentity_t *ent );
00017 
00018 extern  vmCvar_t                d_asynchronousGroupAI;
00019 
00020 #define MAX_VIEW_DIST           1024
00021 #define MAX_VIEW_SPEED          250
00022 #define MAX_LIGHT_INTENSITY 255
00023 #define MIN_LIGHT_THRESHOLD     0.1
00024 #define ST_MIN_LIGHT_THRESHOLD 30
00025 #define ST_MAX_LIGHT_THRESHOLD 180
00026 #define DISTANCE_THRESHOLD      0.075f
00027 
00028 #define DISTANCE_SCALE          0.35f   //These first three get your base detection rating, ideally add up to 1
00029 #define FOV_SCALE                       0.40f   //
00030 #define LIGHT_SCALE                     0.25f   //
00031 
00032 #define SPEED_SCALE                     0.25f   //These next two are bonuses
00033 #define TURNING_SCALE           0.25f   //
00034 
00035 #define REALIZE_THRESHOLD       0.6f
00036 #define CAUTIOUS_THRESHOLD      ( REALIZE_THRESHOLD * 0.75 )
00037 
00038 qboolean NPC_CheckPlayerTeamStealth( void );
00039 
00040 static qboolean enemyLOS;
00041 static qboolean enemyCS;
00042 static qboolean enemyInFOV;
00043 static qboolean hitAlly;
00044 static qboolean faceEnemy;
00045 static qboolean move;
00046 static qboolean shoot;
00047 static float    enemyDist;
00048 static vec3_t   impactPos;
00049 
00050 int groupSpeechDebounceTime[TEAM_NUM_TEAMS];//used to stop several group AI from speaking all at once
00051 
00052 //Local state enums
00053 enum
00054 {
00055         LSTATE_NONE = 0,
00056         LSTATE_UNDERFIRE,
00057         LSTATE_INVESTIGATE,
00058 };
00059 
00060 void ST_AggressionAdjust( gentity_t *self, int change )
00061 {
00062         int     upper_threshold, lower_threshold;
00063 
00064         self->NPC->stats.aggression += change;
00065         
00066         //FIXME: base this on initial NPC stats
00067         if ( self->client->playerTeam == NPCTEAM_PLAYER )
00068         {//good guys are less aggressive
00069                 upper_threshold = 7;
00070                 lower_threshold = 1;
00071         }
00072         else
00073         {//bad guys are more aggressive
00074                 upper_threshold = 10;
00075                 lower_threshold = 3;
00076         }
00077 
00078         if ( self->NPC->stats.aggression > upper_threshold )
00079         {
00080                 self->NPC->stats.aggression = upper_threshold;
00081         }
00082         else if ( self->NPC->stats.aggression < lower_threshold )
00083         {
00084                 self->NPC->stats.aggression = lower_threshold;
00085         }
00086 }
00087 
00088 void ST_ClearTimers( gentity_t *ent )
00089 {
00090         TIMER_Set( ent, "chatter", 0 );
00091         TIMER_Set( ent, "duck", 0 );
00092         TIMER_Set( ent, "stand", 0 );
00093         TIMER_Set( ent, "shuffleTime", 0 );
00094         TIMER_Set( ent, "sleepTime", 0 );
00095         TIMER_Set( ent, "enemyLastVisible", 0 );
00096         TIMER_Set( ent, "roamTime", 0 );
00097         TIMER_Set( ent, "hideTime", 0 );
00098         TIMER_Set( ent, "attackDelay", 0 );     //FIXME: Slant for difficulty levels
00099         TIMER_Set( ent, "stick", 0 );
00100         TIMER_Set( ent, "scoutTime", 0 );
00101         TIMER_Set( ent, "flee", 0 );
00102         TIMER_Set( ent, "interrogating", 0 );
00103         TIMER_Set( ent, "verifyCP", 0 );
00104 }
00105 
00106 enum
00107 {
00108         SPEECH_CHASE,
00109         SPEECH_CONFUSED,
00110         SPEECH_COVER,
00111         SPEECH_DETECTED,
00112         SPEECH_GIVEUP,
00113         SPEECH_LOOK,
00114         SPEECH_LOST,
00115         SPEECH_OUTFLANK,
00116         SPEECH_ESCAPING,
00117         SPEECH_SIGHT,
00118         SPEECH_SOUND,
00119         SPEECH_SUSPICIOUS,
00120         SPEECH_YELL,
00121         SPEECH_PUSHED
00122 };
00123 
00124 static void ST_Speech( gentity_t *self, int speechType, float failChance )
00125 {
00126         if ( random() < failChance )
00127         {
00128                 return;
00129         }
00130 
00131         if ( failChance >= 0 )
00132         {//a negative failChance makes it always talk
00133                 if ( self->NPC->group )
00134                 {//group AI speech debounce timer
00135                         if ( self->NPC->group->speechDebounceTime > level.time )
00136                         {
00137                                 return;
00138                         }
00139                         /*
00140                         else if ( !self->NPC->group->enemy )
00141                         {
00142                                 if ( groupSpeechDebounceTime[self->client->playerTeam] > level.time )
00143                                 {
00144                                         return;
00145                                 }
00146                         }
00147                         */
00148                 }
00149                 else if ( !TIMER_Done( self, "chatter" ) )
00150                 {//personal timer
00151                         return;
00152                 }
00153                 else if ( groupSpeechDebounceTime[self->client->playerTeam] > level.time )
00154                 {//for those not in group AI
00155                         //FIXME: let certain speech types interrupt others?  Let closer NPCs interrupt farther away ones?
00156                         return;
00157                 }
00158         }
00159 
00160         if ( self->NPC->group )
00161         {//So they don't all speak at once...
00162                 //FIXME: if they're not yet mad, they have no group, so distracting a group of them makes them all speak!
00163                 self->NPC->group->speechDebounceTime = level.time + Q_irand( 2000, 4000 );
00164         }
00165         else
00166         {
00167                 TIMER_Set( self, "chatter", Q_irand( 2000, 4000 ) );
00168         }
00169         groupSpeechDebounceTime[self->client->playerTeam] = level.time + Q_irand( 2000, 4000 );
00170 
00171         if ( self->NPC->blockedSpeechDebounceTime > level.time )
00172         {
00173                 return;
00174         }
00175 
00176         switch( speechType )
00177         {
00178         case SPEECH_CHASE:
00179                 G_AddVoiceEvent( self, Q_irand(EV_CHASE1, EV_CHASE3), 2000 );
00180                 break;
00181         case SPEECH_CONFUSED:
00182                 G_AddVoiceEvent( self, Q_irand(EV_CONFUSE1, EV_CONFUSE3), 2000 );
00183                 break;
00184         case SPEECH_COVER:
00185                 G_AddVoiceEvent( self, Q_irand(EV_COVER1, EV_COVER5), 2000 );
00186                 break;
00187         case SPEECH_DETECTED:
00188                 G_AddVoiceEvent( self, Q_irand(EV_DETECTED1, EV_DETECTED5), 2000 );
00189                 break;
00190         case SPEECH_GIVEUP:
00191                 G_AddVoiceEvent( self, Q_irand(EV_GIVEUP1, EV_GIVEUP4), 2000 );
00192                 break;
00193         case SPEECH_LOOK:
00194                 G_AddVoiceEvent( self, Q_irand(EV_LOOK1, EV_LOOK2), 2000 );
00195                 break;
00196         case SPEECH_LOST:
00197                 G_AddVoiceEvent( self, EV_LOST1, 2000 );
00198                 break;
00199         case SPEECH_OUTFLANK:
00200                 G_AddVoiceEvent( self, Q_irand(EV_OUTFLANK1, EV_OUTFLANK2), 2000 );
00201                 break;
00202         case SPEECH_ESCAPING:
00203                 G_AddVoiceEvent( self, Q_irand(EV_ESCAPING1, EV_ESCAPING3), 2000 );
00204                 break;
00205         case SPEECH_SIGHT:
00206                 G_AddVoiceEvent( self, Q_irand(EV_SIGHT1, EV_SIGHT3), 2000 );
00207                 break;
00208         case SPEECH_SOUND:
00209                 G_AddVoiceEvent( self, Q_irand(EV_SOUND1, EV_SOUND3), 2000 );
00210                 break;
00211         case SPEECH_SUSPICIOUS:
00212                 G_AddVoiceEvent( self, Q_irand(EV_SUSPICIOUS1, EV_SUSPICIOUS5), 2000 );
00213                 break;
00214         case SPEECH_YELL:
00215                 G_AddVoiceEvent( self, Q_irand( EV_ANGER1, EV_ANGER3 ), 2000 );
00216                 break;
00217         case SPEECH_PUSHED:
00218                 G_AddVoiceEvent( self, Q_irand( EV_PUSHED1, EV_PUSHED3 ), 2000 );
00219                 break;
00220         default:
00221                 break;
00222         }
00223 
00224         self->NPC->blockedSpeechDebounceTime = level.time + 2000;
00225 }
00226 
00227 void ST_MarkToCover( gentity_t *self )
00228 {
00229         if ( !self || !self->NPC )
00230         {
00231                 return;
00232         }
00233         self->NPC->localState = LSTATE_UNDERFIRE;
00234         TIMER_Set( self, "attackDelay", Q_irand( 500, 2500 ) );
00235         ST_AggressionAdjust( self, -3 );
00236         if ( self->NPC->group && self->NPC->group->numGroup > 1 )
00237         {
00238                 ST_Speech( self, SPEECH_COVER, 0 );//FIXME: flee sound?
00239         }
00240 }
00241 
00242 void ST_StartFlee( gentity_t *self, gentity_t *enemy, vec3_t dangerPoint, int dangerLevel, int minTime, int maxTime )
00243 {
00244         if ( !self || !self->NPC )
00245         {
00246                 return;
00247         }
00248         G_StartFlee( self, enemy, dangerPoint, dangerLevel, minTime, maxTime );
00249         if ( self->NPC->group && self->NPC->group->numGroup > 1 )
00250         {
00251                 ST_Speech( self, SPEECH_COVER, 0 );//FIXME: flee sound?
00252         }
00253 }
00254 /*
00255 -------------------------
00256 NPC_ST_Pain
00257 -------------------------
00258 */
00259 
00260 void NPC_ST_Pain(gentity_t *self, gentity_t *attacker, int damage)
00261 {
00262         self->NPC->localState = LSTATE_UNDERFIRE;
00263 
00264         TIMER_Set( self, "duck", -1 );
00265         TIMER_Set( self, "hideTime", -1 );
00266         TIMER_Set( self, "stand", 2000 );
00267 
00268         NPC_Pain( self, attacker, damage );
00269 
00270         if ( !damage && self->health > 0 )
00271         {//FIXME: better way to know I was pushed
00272                 G_AddVoiceEvent( self, Q_irand(EV_PUSHED1, EV_PUSHED3), 2000 );
00273         }
00274 }
00275 
00276 /*
00277 -------------------------
00278 ST_HoldPosition
00279 -------------------------
00280 */
00281 
00282 static void ST_HoldPosition( void )
00283 {
00284         if ( NPCInfo->squadState == SQUAD_RETREAT )
00285         {
00286                 TIMER_Set( NPC, "flee", -level.time );
00287         }
00288         TIMER_Set( NPC, "verifyCP", Q_irand( 1000, 3000 ) );//don't look for another one for a few seconds
00289         NPC_FreeCombatPoint( NPCInfo->combatPoint, qtrue );
00290         //NPCInfo->combatPoint = -1;//???
00291         if ( !trap_ICARUS_TaskIDPending( NPC, TID_MOVE_NAV ) )
00292         {//don't have a script waiting for me to get to my point, okay to stop trying and stand
00293                 AI_GroupUpdateSquadstates( NPCInfo->group, NPC, SQUAD_STAND_AND_SHOOT );
00294                 NPCInfo->goalEntity = NULL;
00295         }
00296         
00297         /*if ( TIMER_Done( NPC, "stand" ) )
00298         {//FIXME: what if can't shoot from this pos?
00299                 TIMER_Set( NPC, "duck", Q_irand( 2000, 4000 ) );
00300         }
00301         */
00302 }
00303 
00304 void NPC_ST_SayMovementSpeech( void )
00305 {
00306         if ( !NPCInfo->movementSpeech )
00307         {
00308                 return;
00309         }
00310         if ( NPCInfo->group && 
00311                 NPCInfo->group->commander && 
00312                 NPCInfo->group->commander->client && 
00313                 NPCInfo->group->commander->client->NPC_class == CLASS_IMPERIAL && 
00314                 !Q_irand( 0, 3 ) )
00315         {//imperial (commander) gives the order
00316                 ST_Speech( NPCInfo->group->commander, NPCInfo->movementSpeech, NPCInfo->movementSpeechChance );
00317         }
00318         else
00319         {//really don't want to say this unless we can actually get there...
00320                 ST_Speech( NPC, NPCInfo->movementSpeech, NPCInfo->movementSpeechChance );
00321         }
00322 
00323         NPCInfo->movementSpeech = 0;
00324         NPCInfo->movementSpeechChance = 0.0f;
00325 }
00326 
00327 void NPC_ST_StoreMovementSpeech( int speech, float chance )
00328 {
00329         NPCInfo->movementSpeech = speech;
00330         NPCInfo->movementSpeechChance = chance;
00331 }
00332 /*
00333 -------------------------
00334 ST_Move
00335 -------------------------
00336 */
00337 void ST_TransferMoveGoal( gentity_t *self, gentity_t *other );
00338 static qboolean ST_Move( void )
00339 {
00340         qboolean        moved;
00341         navInfo_t       info;
00342 
00343         NPCInfo->combatMove = qtrue;//always move straight toward our goal
00344 
00345         moved = NPC_MoveToGoal( qtrue );
00346         
00347         //Get the move info
00348         NAV_GetLastMove( &info );
00349 
00350         //FIXME: if we bump into another one of our guys and can't get around him, just stop!
00351         //If we hit our target, then stop and fire!
00352         if ( info.flags & NIF_COLLISION ) 
00353         {
00354                 if ( info.blocker == NPC->enemy )
00355                 {
00356                         ST_HoldPosition();
00357                 }
00358         }
00359 
00360         //If our move failed, then reset
00361         if ( moved == qfalse )
00362         {//FIXME: if we're going to a combat point, need to pick a different one
00363                 if ( !trap_ICARUS_TaskIDPending( NPC, TID_MOVE_NAV ) )
00364                 {//can't transfer movegoal or stop when a script we're running is waiting to complete
00365                         if ( info.blocker && info.blocker->NPC && NPCInfo->group != NULL && info.blocker->NPC->group == NPCInfo->group )//(NPCInfo->aiFlags&NPCAI_BLOCKED) && NPCInfo->group != NULL )
00366                         {//dammit, something is in our way
00367                                 //see if it's one of ours
00368                                 int j;
00369 
00370                                 for ( j = 0; j < NPCInfo->group->numGroup; j++ )
00371                                 {
00372                                         if ( NPCInfo->group->member[j].number == NPCInfo->blockingEntNum )
00373                                         {//we're being blocked by one of our own, pass our goal onto them and I'll stand still
00374                                                 ST_TransferMoveGoal( NPC, &g_entities[NPCInfo->group->member[j].number] );
00375                                                 break;
00376                                         }
00377                                 }
00378                         }
00379                         
00380                         ST_HoldPosition();
00381                 }
00382         }
00383         else
00384         {
00385                 //First time you successfully move, say what it is you're doing
00386                 NPC_ST_SayMovementSpeech();
00387         }
00388 
00389         return moved;
00390 }
00391 
00392 
00393 /*
00394 -------------------------
00395 NPC_ST_SleepShuffle
00396 -------------------------
00397 */
00398 
00399 static void NPC_ST_SleepShuffle( void )
00400 {
00401         //Play an awake script if we have one
00402         if ( G_ActivateBehavior( NPC, BSET_AWAKE) )
00403         {
00404                 return;
00405         }
00406 
00407         //Automate some movement and noise
00408         if ( TIMER_Done( NPC, "shuffleTime" ) )
00409         {
00410                 
00411                 //TODO: Play sleeping shuffle animation
00412 
00413                 //int   soundIndex = Q_irand( 0, 1 );
00414 
00415                 /*
00416                 switch ( soundIndex )
00417                 {
00418                 case 0:
00419                         G_Sound( NPC, G_SoundIndex("sound/chars/imperialsleeper1/scav4/hunh.mp3") );
00420                         break;
00421 
00422                 case 1:
00423                         G_Sound( NPC, G_SoundIndex("sound/chars/imperialsleeper3/scav4/tryingtosleep.wav") );
00424                         break;
00425                 }
00426                 */
00427 
00428                 TIMER_Set( NPC, "shuffleTime", 4000 );
00429                 TIMER_Set( NPC, "sleepTime", 2000 );
00430                 return;
00431         }
00432 
00433         //They made another noise while we were stirring, see if we can see them
00434         if ( TIMER_Done( NPC, "sleepTime" ) )
00435         {
00436                 NPC_CheckPlayerTeamStealth();
00437                 TIMER_Set( NPC, "sleepTime", 2000 );
00438         }
00439 }
00440 
00441 /*
00442 -------------------------
00443 NPC_ST_Sleep
00444 -------------------------
00445 */
00446 
00447 void NPC_BSST_Sleep( void )
00448 {
00449         int alertEvent = NPC_CheckAlertEvents( qfalse, qtrue, -1, qfalse, AEL_MINOR );//only check sounds since we're alseep!
00450 
00451         //There is an event we heard
00452         if ( alertEvent >= 0 )
00453         {
00454                 //See if it was enough to wake us up
00455                 if ( level.alertEvents[alertEvent].level == AEL_DISCOVERED && (NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES) )
00456                 { //rwwFIXMEFIXME: Care about all clients not just 0
00457                         if ( &g_entities[0] && g_entities[0].health > 0 )
00458                         {
00459                                 G_SetEnemy( NPC, &g_entities[0] );
00460                                 return;
00461                         }
00462                 }
00463 
00464                 //Otherwise just stir a bit
00465                 NPC_ST_SleepShuffle();
00466                 return;
00467         }
00468 }
00469 
00470 /*
00471 -------------------------
00472 NPC_CheckEnemyStealth
00473 -------------------------
00474 */
00475 
00476 qboolean NPC_CheckEnemyStealth( gentity_t *target )
00477 {
00478         float           target_dist, minDist = 40;//any closer than 40 and we definitely notice
00479         float           maxViewDist;
00480         qboolean        clearLOS;
00481 
00482         //In case we aquired one some other way
00483         if ( NPC->enemy != NULL )
00484                 return qtrue;
00485 
00486         //Ignore notarget
00487         if ( target->flags & FL_NOTARGET )
00488                 return qfalse;
00489 
00490         if ( target->health <= 0 )
00491         {
00492                 return qfalse;
00493         }
00494 
00495         if ( target->client->ps.weapon == WP_SABER && !target->client->ps.saberHolstered && !target->client->ps.saberInFlight )
00496         {//if target has saber in hand and activated, we wake up even sooner even if not facing him
00497                 minDist = 100;
00498         }
00499 
00500         target_dist = DistanceSquared( target->r.currentOrigin, NPC->r.currentOrigin );
00501 
00502         //If the target is this close, then wake up regardless
00503         if ( !(target->client->ps.pm_flags&PMF_DUCKED)
00504                 && (NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES)
00505                 && (target_dist) < (minDist*minDist) )
00506         {
00507                 G_SetEnemy( NPC, target );
00508                 NPCInfo->enemyLastSeenTime = level.time;
00509                 TIMER_Set( NPC, "attackDelay", Q_irand( 500, 2500 ) );
00510                 return qtrue;
00511         }
00512 
00513         maxViewDist                     = MAX_VIEW_DIST;
00514 
00515         if ( NPCInfo->stats.visrange > maxViewDist )
00516         {//FIXME: should we always just set maxViewDist to this?
00517                 maxViewDist = NPCInfo->stats.visrange;
00518         }
00519 
00520         if ( target_dist > (maxViewDist*maxViewDist) )
00521         {//out of possible visRange
00522                 return qfalse;
00523         }
00524 
00525         //Check FOV first
00526         if ( InFOV( target, NPC, NPCInfo->stats.hfov, NPCInfo->stats.vfov ) == qfalse )
00527                 return qfalse;
00528 
00529         //clearLOS = ( target->client->ps.leanofs ) ? NPC_ClearLOS5( target->client->renderInfo.eyePoint ) : NPC_ClearLOS4( target );
00530         clearLOS = NPC_ClearLOS4( target );
00531 
00532         //Now check for clear line of vision
00533         if ( clearLOS )
00534         {
00535                 vec3_t  targ_org;
00536                 float   hAngle_perc;
00537                 float   vAngle_perc;
00538                 float   target_speed;
00539                 int             target_crouching;
00540                 float   dist_rating;
00541                 float   speed_rating;
00542                 float   turning_rating;
00543                 float   light_level;
00544                 float   FOV_perc;
00545                 float   vis_rating;
00546                 float   dist_influence;
00547                 float   fov_influence;
00548                 float   light_influence;
00549                 float   target_rating;
00550                 int             contents;
00551                 float   realize, cautious;
00552 
00553                 if ( target->client->NPC_class == CLASS_ATST )
00554                 {//can't miss 'em!
00555                         G_SetEnemy( NPC, target );
00556                         TIMER_Set( NPC, "attackDelay", Q_irand( 500, 2500 ) );
00557                         return qtrue;
00558                 }
00559                 VectorSet(targ_org, target->r.currentOrigin[0],target->r.currentOrigin[1],target->r.currentOrigin[2]+target->r.maxs[2]-4);
00560                 hAngle_perc                     = NPC_GetHFOVPercentage( targ_org, NPC->client->renderInfo.eyePoint, NPC->client->renderInfo.eyeAngles, NPCInfo->stats.hfov );
00561                 vAngle_perc                     = NPC_GetVFOVPercentage( targ_org, NPC->client->renderInfo.eyePoint, NPC->client->renderInfo.eyeAngles, NPCInfo->stats.vfov );
00562 
00563                 //Scale them vertically some, and horizontally pretty harshly
00564                 vAngle_perc *= vAngle_perc;//( vAngle_perc * vAngle_perc );
00565                 hAngle_perc *= ( hAngle_perc * hAngle_perc );
00566 
00567                 //Cap our vertical vision severely
00568                 //if ( vAngle_perc <= 0.3f ) // was 0.5f
00569                 //      return qfalse;
00570 
00571                 //Assess the player's current status
00572                 target_dist                     = Distance( target->r.currentOrigin, NPC->r.currentOrigin );
00573 
00574                 target_speed            = VectorLength( target->client->ps.velocity );
00575                 target_crouching        = ( target->client->pers.cmd.upmove < 0 );
00576                 dist_rating                     = ( target_dist / maxViewDist );
00577                 speed_rating            = ( target_speed / MAX_VIEW_SPEED );
00578                 turning_rating          = 5.0f;//AngleDelta( target->client->ps.viewangles[PITCH], target->lastAngles[PITCH] )/180.0f + AngleDelta( target->client->ps.viewangles[YAW], target->lastAngles[YAW] )/180.0f;
00579                 light_level                     = (255/MAX_LIGHT_INTENSITY); //( target->lightLevel / MAX_LIGHT_INTENSITY );
00580                 FOV_perc                        = 1.0f - ( hAngle_perc + vAngle_perc ) * 0.5f;  //FIXME: Dunno about the average...
00581                 vis_rating                      = 0.0f;
00582                 
00583                 //Too dark
00584                 if ( light_level < MIN_LIGHT_THRESHOLD )
00585                         return qfalse;
00586 
00587                 //Too close?
00588                 if ( dist_rating < DISTANCE_THRESHOLD )
00589                 {
00590                         G_SetEnemy( NPC, target );
00591                         TIMER_Set( NPC, "attackDelay", Q_irand( 500, 2500 ) );
00592                         return qtrue;
00593                 }
00594 
00595                 //Out of range
00596                 if ( dist_rating > 1.0f )
00597                         return qfalse;
00598                 
00599                 //Cap our speed checks
00600                 if ( speed_rating > 1.0f )
00601                         speed_rating = 1.0f;
00602                 
00603 
00604                 //Calculate the distance, fov and light influences
00605                 //...Visibilty linearly wanes over distance
00606                 dist_influence  = DISTANCE_SCALE * ( ( 1.0f - dist_rating ) );
00607                 //...As the percentage out of the FOV increases, straight perception suffers on an exponential scale
00608                 fov_influence           = FOV_SCALE * ( 1.0f - FOV_perc );
00609                 //...Lack of light hides, abundance of light exposes
00610                 light_influence = ( light_level - 0.5f ) * LIGHT_SCALE;
00611                 
00612                 //Calculate our base rating
00613                 target_rating           = dist_influence + fov_influence + light_influence;
00614                 
00615                 //Now award any final bonuses to this number
00616                 contents = trap_PointContents( targ_org, target->s.number );
00617                 if ( contents&CONTENTS_WATER )
00618                 {
00619                         int myContents = trap_PointContents( NPC->client->renderInfo.eyePoint, NPC->s.number );
00620                         if ( !(myContents&CONTENTS_WATER) )
00621                         {//I'm not in water
00622                                 if ( NPC->client->NPC_class == CLASS_SWAMPTROOPER )
00623                                 {//these guys can see in in/through water pretty well
00624                                         vis_rating = 0.10f;//10% bonus
00625                                 }
00626                                 else
00627                                 {
00628                                         vis_rating = 0.35f;//35% bonus
00629                                 }
00630                         }
00631                         else
00632                         {//else, if we're both in water
00633                                 if ( NPC->client->NPC_class == CLASS_SWAMPTROOPER )
00634                                 {//I can see him just fine
00635                                 }
00636                                 else
00637                                 {
00638                                         vis_rating = 0.15f;//15% bonus
00639                                 }
00640                         }
00641                 }
00642                 else 
00643                 {//not in water
00644                         if ( contents&CONTENTS_FOG )
00645                         {
00646                                 vis_rating = 0.15f;//15% bonus
00647                         }
00648                 }
00649 
00650                 target_rating *= (1.0f - vis_rating);
00651 
00652                 //...Motion draws the eye quickly
00653                 target_rating += speed_rating * SPEED_SCALE;
00654                 target_rating += turning_rating * TURNING_SCALE;
00655                 //FIXME: check to see if they're animating, too?  But can we do something as simple as frame != oldframe?
00656 
00657                 //...Smaller targets are harder to indentify
00658                 if ( target_crouching )
00659                 {
00660                         target_rating *= 0.9f;  //10% bonus
00661                 }       
00662         
00663                 //If he's violated the threshold, then realize him
00664                 //float difficulty_scale = 1.0f + (2.0f-g_spskill.value);//if playing on easy, 20% harder to be seen...?
00665                 if ( NPC->client->NPC_class == CLASS_SWAMPTROOPER )
00666                 {//swamptroopers can see much better
00667                         realize = (float)CAUTIOUS_THRESHOLD;
00668                         cautious = (float)CAUTIOUS_THRESHOLD * 0.75f;
00669                 }
00670                 else
00671                 {
00672                         realize = (float)REALIZE_THRESHOLD;
00673                         cautious = (float)CAUTIOUS_THRESHOLD * 0.75f;
00674                 }
00675 
00676                 if ( target_rating > realize && (NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES) )
00677                 {
00678                         G_SetEnemy( NPC, target );
00679                         NPCInfo->enemyLastSeenTime = level.time;
00680                         TIMER_Set( NPC, "attackDelay", Q_irand( 500, 2500 ) );
00681                         return qtrue;
00682                 }
00683 
00684                 //If he's above the caution threshold, then realize him in a few seconds unless he moves to cover
00685                 if ( target_rating > cautious && !(NPCInfo->scriptFlags&SCF_IGNORE_ALERTS) )
00686                 {//FIXME: ambushing guys should never talk
00687                         if ( TIMER_Done( NPC, "enemyLastVisible" ) )
00688                         {//If we haven't already, start the counter
00689                                 int     lookTime = Q_irand( 4500, 8500 );
00690                                 //NPCInfo->timeEnemyLastVisible = level.time + 2000;
00691                                 TIMER_Set( NPC, "enemyLastVisible", lookTime );
00692                                 //TODO: Play a sound along the lines of, "Huh?  What was that?"
00693                                 ST_Speech( NPC, SPEECH_SIGHT, 0 );
00694                                 NPC_TempLookTarget( NPC, target->s.number, lookTime, lookTime );
00695                                 //FIXME: set desired yaw and pitch towards this guy?
00696                         }
00697                         else if ( TIMER_Get( NPC, "enemyLastVisible" ) <= level.time + 500 && (NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES) )     //FIXME: Is this reliable?
00698                         {
00699                                 if ( NPCInfo->rank < RANK_LT && !Q_irand( 0, 2 ) )
00700                                 {
00701                                         int     interrogateTime = Q_irand( 2000, 4000 );
00702                                         ST_Speech( NPC, SPEECH_SUSPICIOUS, 0 );
00703                                         TIMER_Set( NPC, "interrogating", interrogateTime );
00704                                         G_SetEnemy( NPC, target );
00705                                         NPCInfo->enemyLastSeenTime = level.time;
00706                                         TIMER_Set( NPC, "attackDelay", interrogateTime );
00707                                         TIMER_Set( NPC, "stand", interrogateTime );
00708                                 }
00709                                 else
00710                                 {
00711                                         G_SetEnemy( NPC, target );
00712                                         NPCInfo->enemyLastSeenTime = level.time;
00713                                         //FIXME: ambush guys (like those popping out of water) shouldn't delay...
00714                                         TIMER_Set( NPC, "attackDelay", Q_irand( 500, 2500 ) );
00715                                         TIMER_Set( NPC, "stand", Q_irand( 500, 2500 ) );
00716                                 }
00717                                 return qtrue;
00718                         }
00719 
00720                         return qfalse;
00721                 }
00722         }
00723 
00724         return qfalse;
00725 }
00726 
00727 qboolean NPC_CheckPlayerTeamStealth( void )
00728 {
00729         /*
00730         //NOTENOTE: For now, all stealh checks go against the player, since
00731         //                      he is the main focus.  Squad members and rivals do not
00732         //                      fall into this category and will be ignored.
00733 
00734         NPC_CheckEnemyStealth( &g_entities[0] );        //Change this pointer to assess other entities
00735         */
00736         gentity_t *enemy;
00737         int i;
00738 
00739         for ( i = 0; i < ENTITYNUM_WORLD; i++ )
00740         {
00741                 enemy = &g_entities[i];
00742 
00743                 if (!enemy->inuse)
00744                 {
00745                         continue;
00746                 }
00747 
00748                 if ( enemy && enemy->client && NPC_ValidEnemy( enemy ) && enemy->client->playerTeam == NPC->client->enemyTeam )
00749                 {
00750                         if ( NPC_CheckEnemyStealth( enemy ) )   //Change this pointer to assess other entities
00751                         {
00752                                 return qtrue;
00753                         }
00754                 }
00755         }
00756         return qfalse;
00757 }
00758 /*
00759 -------------------------
00760 NPC_ST_InvestigateEvent
00761 -------------------------
00762 */
00763 
00764 #define MAX_CHECK_THRESHOLD     1
00765 
00766 static qboolean NPC_ST_InvestigateEvent( int eventID, qboolean extraSuspicious )
00767 {
00768         //If they've given themselves away, just take them as an enemy
00769         if ( NPCInfo->confusionTime < level.time )
00770         {
00771                 if ( level.alertEvents[eventID].level == AEL_DISCOVERED && (NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES) )
00772                 {
00773                         NPCInfo->lastAlertID = level.alertEvents[eventID].ID;
00774                         if ( !level.alertEvents[eventID].owner || 
00775                                 !level.alertEvents[eventID].owner->client || 
00776                                 level.alertEvents[eventID].owner->health <= 0 ||
00777                                 level.alertEvents[eventID].owner->client->playerTeam != NPC->client->enemyTeam )
00778                         {//not an enemy
00779                                 return qfalse;
00780                         }
00781                         //FIXME: what if can't actually see enemy, don't know where he is... should we make them just become very alert and start looking for him?  Or just let combat AI handle this... (act as if you lost him)
00782                         //ST_Speech( NPC, SPEECH_CHARGE, 0 );
00783                         G_SetEnemy( NPC, level.alertEvents[eventID].owner );
00784                         NPCInfo->enemyLastSeenTime = level.time;
00785                         TIMER_Set( NPC, "attackDelay", Q_irand( 500, 2500 ) );
00786                         if ( level.alertEvents[eventID].type == AET_SOUND )
00787                         {//heard him, didn't see him, stick for a bit
00788                                 TIMER_Set( NPC, "roamTime", Q_irand( 500, 2500 ) );
00789                         }
00790                         return qtrue;
00791                 }
00792         }
00793 
00794         //don't look at the same alert twice
00795         if ( level.alertEvents[eventID].ID == NPCInfo->lastAlertID )
00796         {
00797                 return qfalse;
00798         }
00799         NPCInfo->lastAlertID = level.alertEvents[eventID].ID;
00800 
00801         //Must be ready to take another sound event
00802         /*
00803         if ( NPCInfo->investigateSoundDebounceTime > level.time )
00804         {
00805                 return qfalse;
00806         }
00807         */
00808 
00809         if ( level.alertEvents[eventID].type == AET_SIGHT )
00810         {//sight alert, check the light level
00811                 if ( level.alertEvents[eventID].light < Q_irand( ST_MIN_LIGHT_THRESHOLD, ST_MAX_LIGHT_THRESHOLD ) )
00812                 {//below my threshhold of potentially seeing
00813                         return qfalse;
00814                 }
00815         }
00816 
00817         //Save the position for movement (if necessary)
00818         VectorCopy( level.alertEvents[eventID].position, NPCInfo->investigateGoal );
00819 
00820         //First awareness of it
00821         NPCInfo->investigateCount += ( extraSuspicious ) ? 2 : 1;
00822 
00823         //Clamp the value
00824         if ( NPCInfo->investigateCount > 4 )
00825                 NPCInfo->investigateCount = 4;
00826 
00827         //See if we should walk over and investigate
00828         if ( level.alertEvents[eventID].level > AEL_MINOR && NPCInfo->investigateCount > 1 && (NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) )
00829         {
00830                 //make it so they can walk right to this point and look at it rather than having to use combatPoints
00831                 if ( G_ExpandPointToBBox( NPCInfo->investigateGoal, NPC->r.mins, NPC->r.maxs, NPC->s.number, ((NPC->clipmask&~CONTENTS_BODY)|CONTENTS_BOTCLIP) ) )
00832                 {//we were able to move the investigateGoal to a point in which our bbox would fit
00833                         //drop the goal to the ground so we can get at it
00834                         vec3_t  end;
00835                         trace_t trace;
00836                         VectorCopy( NPCInfo->investigateGoal, end );
00837                         end[2] -= 512;//FIXME: not always right?  What if it's even higher, somehow?
00838                         trap_Trace( &trace, NPCInfo->investigateGoal, NPC->r.mins, NPC->r.maxs, end, ENTITYNUM_NONE, ((NPC->clipmask&~CONTENTS_BODY)|CONTENTS_BOTCLIP) );
00839                         if ( trace.fraction >= 1.0f )
00840                         {//too high to even bother
00841                                 //FIXME: look at them???
00842                         }
00843                         else
00844                         {
00845                                 VectorCopy( trace.endpos, NPCInfo->investigateGoal );
00846                                 NPC_SetMoveGoal( NPC, NPCInfo->investigateGoal, 16, qtrue, -1, NULL );
00847                                 NPCInfo->localState = LSTATE_INVESTIGATE;
00848                         }
00849                 }
00850                 else
00851                 {
00852                         int id = NPC_FindCombatPoint( NPCInfo->investigateGoal, NPCInfo->investigateGoal, NPCInfo->investigateGoal, CP_INVESTIGATE|CP_HAS_ROUTE, 0, -1 );
00853 
00854                         if ( id != -1 )
00855                         {
00856                                 NPC_SetMoveGoal( NPC, level.combatPoints[id].origin, 16, qtrue, id, NULL );
00857                                 NPCInfo->localState = LSTATE_INVESTIGATE;
00858                         }
00859                 }
00860                 //Say something
00861                 //FIXME: only if have others in group... these should be responses?
00862                 if ( NPCInfo->investigateDebounceTime+NPCInfo->pauseTime > level.time )
00863                 {//was already investigating
00864                         if ( NPCInfo->group && 
00865                                 NPCInfo->group->commander && 
00866                                 NPCInfo->group->commander->client && 
00867                                 NPCInfo->group->commander->client->NPC_class == CLASS_IMPERIAL && 
00868                                 !Q_irand( 0, 3 ) )
00869                         {
00870                                 ST_Speech( NPCInfo->group->commander, SPEECH_LOOK, 0 );//FIXME: "I'll go check it out" type sounds
00871                         }
00872                         else
00873                         {
00874                                 ST_Speech( NPC, SPEECH_LOOK, 0 );//FIXME: "I'll go check it out" type sounds
00875                         }
00876                 }
00877                 else
00878                 {
00879                         if ( level.alertEvents[eventID].type == AET_SIGHT )
00880                         {
00881                                 ST_Speech( NPC, SPEECH_SIGHT, 0 );
00882                         }
00883                         else if ( level.alertEvents[eventID].type == AET_SOUND )
00884                         {
00885                                 ST_Speech( NPC, SPEECH_SOUND, 0 );
00886                         }
00887                 }
00888                 //Setup the debounce info
00889                 NPCInfo->investigateDebounceTime                = NPCInfo->investigateCount * 5000;
00890                 NPCInfo->investigateSoundDebounceTime   = level.time + 2000;
00891                 NPCInfo->pauseTime                                              = level.time;
00892         }
00893         else
00894         {//just look?
00895                 //Say something
00896                 if ( level.alertEvents[eventID].type == AET_SIGHT )
00897                 {
00898                         ST_Speech( NPC, SPEECH_SIGHT, 0 );
00899                 }
00900                 else if ( level.alertEvents[eventID].type == AET_SOUND )
00901                 {
00902                         ST_Speech( NPC, SPEECH_SOUND, 0 );
00903                 }
00904                 //Setup the debounce info
00905                 NPCInfo->investigateDebounceTime                = NPCInfo->investigateCount * 1000;
00906                 NPCInfo->investigateSoundDebounceTime   = level.time + 1000;
00907                 NPCInfo->pauseTime                                              = level.time;
00908                 VectorCopy( level.alertEvents[eventID].position, NPCInfo->investigateGoal );
00909         }
00910 
00911         if ( level.alertEvents[eventID].level >= AEL_DANGER )
00912         {
00913                 NPCInfo->investigateDebounceTime = Q_irand( 500, 2500 );
00914         }
00915 
00916         //Start investigating
00917         NPCInfo->tempBehavior = BS_INVESTIGATE;
00918         return qtrue;
00919 }
00920 
00921 /*
00922 -------------------------
00923 ST_OffsetLook
00924 -------------------------
00925 */
00926 
00927 static void ST_OffsetLook( float offset, vec3_t out )
00928 {
00929         vec3_t  angles, forward, temp;
00930 
00931         GetAnglesForDirection( NPC->r.currentOrigin, NPCInfo->investigateGoal, angles );
00932         angles[YAW] += offset;
00933         AngleVectors( angles, forward, NULL, NULL );
00934         VectorMA( NPC->r.currentOrigin, 64, forward, out );
00935         
00936         CalcEntitySpot( NPC, SPOT_HEAD, temp );
00937         out[2] = temp[2];
00938 }
00939 
00940 /*
00941 -------------------------
00942 ST_LookAround
00943 -------------------------
00944 */
00945 
00946 static void ST_LookAround( void )
00947 {
00948         vec3_t  lookPos;
00949         float   perc = (float) ( level.time - NPCInfo->pauseTime ) / (float) NPCInfo->investigateDebounceTime;
00950 
00951         //Keep looking at the spot
00952         if ( perc < 0.25 )
00953         {
00954                 VectorCopy( NPCInfo->investigateGoal, lookPos );
00955         }
00956         else if ( perc < 0.5f )         //Look up but straight ahead
00957         {
00958                 ST_OffsetLook( 0.0f, lookPos );
00959         }
00960         else if ( perc < 0.75f )        //Look right
00961         {
00962                 ST_OffsetLook( 45.0f, lookPos );
00963         }
00964         else    //Look left
00965         {
00966                 ST_OffsetLook( -45.0f, lookPos );
00967         }
00968 
00969         NPC_FacePosition( lookPos, qtrue );
00970 }
00971 
00972 /*
00973 -------------------------
00974 NPC_BSST_Investigate
00975 -------------------------
00976 */
00977 
00978 void NPC_BSST_Investigate( void )
00979 {
00980         //get group- mainly for group speech debouncing, but may use for group scouting/investigating AI, too
00981         AI_GetGroup( NPC );
00982 
00983         if( NPCInfo->scriptFlags & SCF_FIRE_WEAPON )
00984         {
00985                 WeaponThink( qtrue );
00986         }
00987 
00988         if ( NPCInfo->confusionTime < level.time )
00989         {
00990                 if ( NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES )
00991                 {
00992                         //Look for an enemy
00993                         if ( NPC_CheckPlayerTeamStealth() )
00994                         {
00995                                 //NPCInfo->behaviorState        = BS_HUNT_AND_KILL;//should be auto now
00996                                 ST_Speech( NPC, SPEECH_DETECTED, 0 );
00997                                 NPCInfo->tempBehavior   = BS_DEFAULT;
00998                                 NPC_UpdateAngles( qtrue, qtrue );
00999                                 return;
01000                         }
01001                 }
01002         }
01003 
01004         if ( !(NPCInfo->scriptFlags&SCF_IGNORE_ALERTS) )
01005         {
01006                 int alertEvent = NPC_CheckAlertEvents( qtrue, qtrue, NPCInfo->lastAlertID, qfalse, AEL_MINOR );
01007 
01008                 //There is an event to look at
01009                 if ( alertEvent >= 0 )
01010                 {
01011                         if ( NPCInfo->confusionTime < level.time )
01012                         {
01013                                 if ( NPC_CheckForDanger( alertEvent ) )
01014                                 {//running like hell
01015                                         ST_Speech( NPC, SPEECH_COVER, 0 );//FIXME: flee sound?
01016                                         return;
01017                                 }
01018                         }
01019 
01020                         if ( level.alertEvents[alertEvent].ID != NPCInfo->lastAlertID )
01021                         {
01022                                 NPC_ST_InvestigateEvent( alertEvent, qtrue );
01023                         }
01024                 }
01025         }
01026 
01027         //If we're done looking, then just return to what we were doing
01028         if ( ( NPCInfo->investigateDebounceTime + NPCInfo->pauseTime ) < level.time )
01029         {
01030                 NPCInfo->tempBehavior = BS_DEFAULT;
01031                 NPCInfo->goalEntity = UpdateGoal();
01032                 
01033                 NPC_UpdateAngles( qtrue, qtrue );
01034                 //Say something
01035                 ST_Speech( NPC, SPEECH_GIVEUP, 0 );
01036                 return;
01037         }
01038 
01039         //FIXME: else, look for new alerts
01040 
01041         //See if we're searching for the noise's origin
01042         if ( NPCInfo->localState == LSTATE_INVESTIGATE && (NPCInfo->goalEntity!=NULL) )
01043         {
01044                 //See if we're there
01045                 if ( NAV_HitNavGoal( NPC->r.currentOrigin, NPC->r.mins, NPC->r.maxs, NPCInfo->goalEntity->r.currentOrigin, 32, FlyingCreature( NPC ) ) == qfalse )
01046                 {
01047                         ucmd.buttons |= BUTTON_WALKING;
01048 
01049                         //Try and move there
01050                         if ( NPC_MoveToGoal( qtrue )  )
01051                         {
01052                                 //Bump our times
01053                                 NPCInfo->investigateDebounceTime        = NPCInfo->investigateCount * 5000;
01054                                 NPCInfo->pauseTime                                      = level.time;
01055 
01056                                 NPC_UpdateAngles( qtrue, qtrue );
01057                                 return;
01058                         }
01059                 }
01060 
01061                 //Otherwise we're done or have given up
01062                 //Say something
01063                 //ST_Speech( NPC, SPEECH_LOOK, 0.33f );
01064                 NPCInfo->localState = LSTATE_NONE;
01065         }
01066 
01067         //Look around
01068         ST_LookAround();
01069 }
01070 
01071 /*
01072 -------------------------
01073 NPC_BSST_Patrol
01074 -------------------------
01075 */
01076 
01077 void NPC_BSST_Patrol( void )
01078 {//FIXME: pick up on bodies of dead buddies?
01079         
01080         //get group- mainly for group speech debouncing, but may use for group scouting/investigating AI, too
01081         AI_GetGroup( NPC );
01082 
01083         if ( NPCInfo->confusionTime < level.time )
01084         {
01085                 //Look for any enemies
01086                 if ( NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES )
01087                 {
01088                         if ( NPC_CheckPlayerTeamStealth() )
01089                         {
01090                                 //NPCInfo->behaviorState = BS_HUNT_AND_KILL;//should be auto now
01091                                 //NPC_AngerSound();
01092                                 NPC_UpdateAngles( qtrue, qtrue );
01093                                 return;
01094                         }
01095                 }
01096         }
01097 
01098         if ( !(NPCInfo->scriptFlags&SCF_IGNORE_ALERTS) )
01099         {
01100                 int alertEvent = NPC_CheckAlertEvents( qtrue, qtrue, -1, qfalse, AEL_MINOR );
01101 
01102                 //There is an event to look at
01103                 if ( alertEvent >= 0 )
01104                 {
01105                         if ( NPC_ST_InvestigateEvent( alertEvent, qfalse ) )
01106                         {//actually going to investigate it
01107                                 NPC_UpdateAngles( qtrue, qtrue );
01108                                 return;
01109                         }
01110                 }
01111         }
01112 
01113         //If we have somewhere to go, then do that
01114         if ( UpdateGoal() )
01115         {
01116                 ucmd.buttons |= BUTTON_WALKING;
01117                 //ST_Move( NPCInfo->goalEntity );
01118                 NPC_MoveToGoal( qtrue );
01119         }
01120         else// if ( !(NPCInfo->scriptFlags&SCF_IGNORE_ALERTS) )
01121         {
01122                 if ( NPC->client->NPC_class != CLASS_IMPERIAL && NPC->client->NPC_class != CLASS_IMPWORKER )
01123                 {//imperials do not look around
01124                         if ( TIMER_Done( NPC, "enemyLastVisible" ) )
01125                         {//nothing suspicious, look around
01126                                 if ( !Q_irand( 0, 30 ) )
01127                                 {
01128                                         NPCInfo->desiredYaw = NPC->s.angles[1] + Q_irand( -90, 90 );
01129                                 }
01130                                 if ( !Q_irand( 0, 30 ) )
01131                                 {
01132                                         NPCInfo->desiredPitch = Q_irand( -20, 20 );
01133                                 }
01134