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                         }
01135                 }
01136         }
01137 
01138         NPC_UpdateAngles( qtrue, qtrue );
01139         //TEMP hack for Imperial stand anim
01140         if ( NPC->client->NPC_class == CLASS_IMPERIAL || NPC->client->NPC_class == CLASS_IMPWORKER )
01141         {//hack
01142                 if ( ucmd.forwardmove || ucmd.rightmove || ucmd.upmove )
01143                 {//moving
01144         
01145                         if( (NPC->client->ps.torsoTimer <= 0) || (NPC->client->ps.torsoAnim == BOTH_STAND4) )                   
01146                         {
01147                                 if ( (ucmd.buttons&BUTTON_WALKING) && !(NPCInfo->scriptFlags&SCF_RUNNING) )
01148                                 {//not running, only set upper anim
01149                                         //  No longer overrides scripted anims
01150                                         NPC_SetAnim( NPC, SETANIM_TORSO, BOTH_STAND4, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
01151                                         NPC->client->ps.torsoTimer = 200;
01152                                 }
01153                         }
01154                 }
01155                 else
01156                 {//standing still, set both torso and legs anim
01157                         //  No longer overrides scripted anims
01158                         if( ( NPC->client->ps.torsoTimer <= 0 || (NPC->client->ps.torsoAnim == BOTH_STAND4) ) &&
01159                                 ( NPC->client->ps.legsTimer <= 0  || (NPC->client->ps.legsAnim == BOTH_STAND4) ) )
01160                         {
01161                                 NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_STAND4, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
01162                                 NPC->client->ps.torsoTimer = NPC->client->ps.legsTimer = 200;
01163                         }
01164                 }
01165                 //FIXME: this is a disgusting hack that is supposed to make the Imperials start with their weapon holstered- need a better way
01166                 if ( NPC->client->ps.weapon != WP_NONE )
01167                 {
01168                         ChangeWeapon( NPC, WP_NONE );
01169                         NPC->client->ps.weapon = WP_NONE;
01170                         NPC->client->ps.weaponstate = WEAPON_READY;
01171                         /*
01172                         if ( NPC->weaponModel[0] > 0 )
01173                         {
01174                                 gi.G2API_RemoveGhoul2Model( NPC->ghoul2, NPC->weaponModel[0] );
01175                                 NPC->weaponModel[0] = -1;
01176                         }
01177                         */
01178                         //rwwFIXMEFIXME: Do this?
01179                 }
01180         }
01181 }
01182 
01183 /*
01184 -------------------------
01185 NPC_BSST_Idle
01186 -------------------------
01187 */
01188 /*
01189 void NPC_BSST_Idle( void )
01190 {
01191         int alertEvent = NPC_CheckAlertEvents( qtrue, qtrue );
01192 
01193         //There is an event to look at
01194         if ( alertEvent >= 0 )
01195         {
01196                 NPC_ST_InvestigateEvent( alertEvent, qfalse );
01197                 NPC_UpdateAngles( qtrue, qtrue );
01198                 return;
01199         }
01200 
01201         TIMER_Set( NPC, "roamTime", 2000 + Q_irand( 1000, 2000 ) );
01202 
01203         NPC_UpdateAngles( qtrue, qtrue );
01204 }
01205 */
01206 /*
01207 -------------------------
01208 ST_CheckMoveState
01209 -------------------------
01210 */
01211 
01212 static void ST_CheckMoveState( void )
01213 {
01214         if ( trap_ICARUS_TaskIDPending( NPC, TID_MOVE_NAV ) )
01215         {//moving toward a goal that a script is waiting on, so don't stop for anything!
01216                 move = qtrue;
01217         }
01218         //See if we're a scout
01219         else if ( NPCInfo->squadState == SQUAD_SCOUT )
01220         {
01221                 //If we're supposed to stay put, then stand there and fire
01222                 if ( TIMER_Done( NPC, "stick" ) == qfalse )
01223                 {
01224                         move = qfalse;
01225                         return;
01226                 }
01227 
01228                 //Otherwise, if we can see our target, just shoot
01229                 if ( enemyLOS )
01230                 {
01231                         if ( enemyCS )
01232                         {
01233                                 //if we're going after our enemy, we can stop now
01234                                 if ( NPCInfo->goalEntity == NPC->enemy )
01235                                 {
01236                                         AI_GroupUpdateSquadstates( NPCInfo->group, NPC, SQUAD_STAND_AND_SHOOT );
01237                                         move = qfalse;
01238                                         return;
01239                                 }
01240                         }
01241                 }
01242                 else
01243                 {
01244                         //Move to find our target
01245                         faceEnemy = qfalse;
01246                 }
01247 
01248                 /*
01249                 if ( TIMER_Done( NPC, "scoutTime" ) )
01250                 {//we can't scout to him, someone else give it a try
01251                         AI_GroupUpdateSquadstates( NPCInfo->group, NPC, SQUAD_STAND_AND_SHOOT );
01252                         TIMER_Set( NPC, "roamTime", Q_irand( 1000, 2000 ) );
01253                         move = qfalse;
01254                         return;
01255                 }
01256                 */
01257 
01258                 //ucmd.buttons |= BUTTON_CAREFUL;
01259         }
01260         //See if we're running away
01261         else if ( NPCInfo->squadState == SQUAD_RETREAT )
01262         {
01263                 if ( NPCInfo->goalEntity )
01264                 {
01265                         faceEnemy = qfalse;
01266                 }
01267                 else
01268                 {//um, lost our goal?  Just stand and shoot, then
01269                         NPCInfo->squadState = SQUAD_STAND_AND_SHOOT;
01270                 }
01271         }
01272         //see if we're heading to some other combatPoint
01273         else if ( NPCInfo->squadState == SQUAD_TRANSITION )
01274         {
01275                 //ucmd.buttons |= BUTTON_CAREFUL;
01276                 if ( !NPCInfo->goalEntity )
01277                 {//um, lost our goal?  Just stand and shoot, then
01278                         NPCInfo->squadState = SQUAD_STAND_AND_SHOOT;
01279                 }
01280         }
01281         //see if we're at point, duck and fire
01282         else if ( NPCInfo->squadState == SQUAD_POINT )
01283         {
01284                 if ( TIMER_Done( NPC, "stick" ) )
01285                 {
01286                         AI_GroupUpdateSquadstates( NPCInfo->group, NPC, SQUAD_STAND_AND_SHOOT );
01287                         return;
01288                 }
01289 
01290                 move = qfalse;
01291                 return;
01292         }
01293         //see if we're just standing around
01294         else if ( NPCInfo->squadState == SQUAD_STAND_AND_SHOOT )
01295         {//from this squadState we can transition to others?
01296                 move = qfalse;
01297                 return;
01298         }
01299         //see if we're hiding
01300         else if ( NPCInfo->squadState == SQUAD_COVER )
01301         {
01302                 //Should we duck?
01303                 move = qfalse;
01304                 return;
01305         }
01306         //see if we're just standing around
01307         else if ( NPCInfo->squadState == SQUAD_IDLE )
01308         {
01309                 if ( !NPCInfo->goalEntity )
01310                 {
01311                         move = qfalse;
01312                         return;
01313                 }
01314         }
01315         //??
01316         else
01317         {//invalid squadState!
01318         }
01319 
01320         //See if we're moving towards a goal, not the enemy
01321         if ( ( NPCInfo->goalEntity != NPC->enemy ) && ( NPCInfo->goalEntity != NULL ) )
01322         {
01323                 //Did we make it?
01324                 if ( NAV_HitNavGoal( NPC->r.currentOrigin, NPC->r.mins, NPC->r.maxs, NPCInfo->goalEntity->r.currentOrigin, 16, FlyingCreature( NPC ) ) || 
01325                         ( !trap_ICARUS_TaskIDPending( NPC, TID_MOVE_NAV ) && NPCInfo->squadState == SQUAD_SCOUT && enemyLOS && enemyDist <= 10000 ) )
01326                 {//either hit our navgoal or our navgoal was not a crucial (scripted) one (maybe a combat point) and we're scouting and found our enemy
01327                         int     newSquadState = SQUAD_STAND_AND_SHOOT;
01328                         //we got where we wanted to go, set timers based on why we were running
01329                         switch ( NPCInfo->squadState )
01330                         {
01331                         case SQUAD_RETREAT://was running away
01332                                 //done fleeing, obviously
01333                                 TIMER_Set( NPC, "duck", (NPC->client->pers.maxHealth - NPC->health) * 100 );
01334                                 TIMER_Set( NPC, "hideTime", Q_irand( 3000, 7000 ) );
01335                                 TIMER_Set( NPC, "flee", -level.time );
01336                                 newSquadState = SQUAD_COVER;
01337                                 break;
01338                         case SQUAD_TRANSITION://was heading for a combat point
01339                                 TIMER_Set( NPC, "hideTime", Q_irand( 2000, 4000 ) );
01340                                 break;
01341                         case SQUAD_SCOUT://was running after player
01342                                 break;
01343                         default:
01344                                 break;
01345                         }
01346                         AI_GroupUpdateSquadstates( NPCInfo->group, NPC, newSquadState );
01347                         NPC_ReachedGoal();
01348                         //don't attack right away
01349                         TIMER_Set( NPC, "attackDelay", Q_irand( 250, 500 ) );   //FIXME: Slant for difficulty levels
01350                         //don't do something else just yet
01351                         TIMER_Set( NPC, "roamTime", Q_irand( 1000, 4000 ) );
01352                         return;
01353                 }
01354 
01355                 //keep going, hold of roamTimer until we get there
01356                 TIMER_Set( NPC, "roamTime", Q_irand( 4000, 8000 ) );
01357         }
01358 }
01359 
01360 void ST_ResolveBlockedShot( int hit )
01361 {
01362         int     stuckTime;
01363         //figure out how long we intend to stand here, max
01364         if ( TIMER_Get( NPC, "roamTime" ) > TIMER_Get( NPC, "stick" ) )
01365         {
01366                 stuckTime = TIMER_Get( NPC, "roamTime" )-level.time;
01367         }
01368         else
01369         {
01370                 stuckTime = TIMER_Get( NPC, "stick" )-level.time;
01371         }
01372 
01373         if ( TIMER_Done( NPC, "duck" ) )
01374         {//we're not ducking
01375                 if ( AI_GroupContainsEntNum( NPCInfo->group, hit ) )
01376                 {
01377                         gentity_t *member = &g_entities[hit];
01378                         if ( TIMER_Done( member, "duck" ) )
01379                         {//they aren't ducking
01380                                 if ( TIMER_Done( member, "stand" ) )
01381                                 {//they're not being forced to stand
01382                                         //tell them to duck at least as long as I'm not moving
01383                                         TIMER_Set( member, "duck", stuckTime );
01384                                         return;
01385                                 }
01386                         }
01387                 }
01388         }
01389         else
01390         {//maybe we should stand
01391                 if ( TIMER_Done( NPC, "stand" ) )
01392                 {//stand for as long as we'll be here
01393                         TIMER_Set( NPC, "stand", stuckTime );
01394                         return;
01395                 }
01396         }
01397         //Hmm, can't resolve this by telling them to duck or telling me to stand
01398         //We need to move!
01399         TIMER_Set( NPC, "roamTime", -1 );
01400         TIMER_Set( NPC, "stick", -1 );
01401         TIMER_Set( NPC, "duck", -1 );
01402         TIMER_Set( NPC, "attakDelay", Q_irand( 1000, 3000 ) );
01403 }
01404 
01405 /*
01406 -------------------------
01407 ST_CheckFireState
01408 -------------------------
01409 */
01410 
01411 static void ST_CheckFireState( void )
01412 {
01413         if ( enemyCS )
01414         {//if have a clear shot, always try
01415                 return;
01416         }
01417 
01418         if ( NPCInfo->squadState == SQUAD_RETREAT || NPCInfo->squadState == SQUAD_TRANSITION || NPCInfo->squadState == SQUAD_SCOUT )
01419         {//runners never try to fire at the last pos
01420                 return;
01421         }
01422 
01423         if ( !VectorCompare( NPC->client->ps.velocity, vec3_origin ) )
01424         {//if moving at all, don't do this
01425                 return;
01426         }
01427 
01428         //See if we should continue to fire on their last position
01430         if ( !hitAlly //we're not going to hit an ally
01431                 && enemyInFOV //enemy is in our FOV //FIXME: or we don't have a clear LOS?
01432                 && NPCInfo->enemyLastSeenTime > 0 //we've seen the enemy
01433                 && NPCInfo->group //have a group
01434                 && (NPCInfo->group->numState[SQUAD_RETREAT]>0||NPCInfo->group->numState[SQUAD_TRANSITION]>0||NPCInfo->group->numState[SQUAD_SCOUT]>0) )//laying down covering fire
01435         {
01436                 if ( level.time - NPCInfo->enemyLastSeenTime < 10000 &&//we have seem the enemy in the last 10 seconds
01437                         (!NPCInfo->group || level.time - NPCInfo->group->lastSeenEnemyTime < 10000 ))//we are not in a group or the group has seen the enemy in the last 10 seconds
01438                 {
01439                         if ( !Q_irand( 0, 10 ) )
01440                         {
01441                                 //Fire on the last known position
01442                                 vec3_t  muzzle, dir, angles;
01443                                 qboolean tooClose = qfalse;
01444                                 qboolean tooFar = qfalse;
01445                                 float distThreshold;
01446                                 float dist;
01447 
01448                                 CalcEntitySpot( NPC, SPOT_HEAD, muzzle );
01449                                 if ( VectorCompare( impactPos, vec3_origin ) )
01450                                 {//never checked ShotEntity this frame, so must do a trace...
01451                                         trace_t tr;
01452                                         //vec3_t        mins = {-2,-2,-2}, maxs = {2,2,2};
01453                                         vec3_t  forward, end;
01454                                         AngleVectors( NPC->client->ps.viewangles, forward, NULL, NULL );
01455                                         VectorMA( muzzle, 8192, forward, end );
01456                                         trap_Trace( &tr, muzzle, vec3_origin, vec3_origin, end, NPC->s.number, MASK_SHOT );
01457                                         VectorCopy( tr.endpos, impactPos );
01458                                 }
01459 
01460                                 //see if impact would be too close to me
01461                                 distThreshold = 16384/*128*128*/;//default
01462                                 switch ( NPC->s.weapon )
01463                                 {
01464                                 case WP_ROCKET_LAUNCHER:
01465                                 case WP_FLECHETTE:
01466                                 case WP_THERMAL:
01467                                 case WP_TRIP_MINE:
01468                                 case WP_DET_PACK:
01469                                         distThreshold = 65536/*256*256*/;
01470                                         break;
01471                                 case WP_REPEATER:
01472                                         if ( NPCInfo->scriptFlags&SCF_ALT_FIRE )
01473                                         {
01474                                                 distThreshold = 65536/*256*256*/;
01475                                         }
01476                                         break;
01477                                 default:
01478                                         break;
01479                                 }
01480 
01481                                 dist = DistanceSquared( impactPos, muzzle );
01482 
01483                                 if ( dist < distThreshold )
01484                                 {//impact would be too close to me
01485                                         tooClose = qtrue;
01486                                 }
01487                                 else if ( level.time - NPCInfo->enemyLastSeenTime > 5000 ||
01488                                         (NPCInfo->group && level.time - NPCInfo->group->lastSeenEnemyTime > 5000 ))
01489                                 {//we've haven't seen them in the last 5 seconds
01490                                         //see if it's too far from where he is
01491                                         distThreshold = 65536/*256*256*/;//default
01492                                         switch ( NPC->s.weapon )
01493                                         {
01494                                         case WP_ROCKET_LAUNCHER:
01495                                         case WP_FLECHETTE:
01496                                         case WP_THERMAL:
01497                                         case WP_TRIP_MINE:
01498                                         case WP_DET_PACK:
01499                                                 distThreshold = 262144/*512*512*/;
01500                                                 break;
01501                                         case WP_REPEATER:
01502                                                 if ( NPCInfo->scriptFlags&SCF_ALT_FIRE )
01503                                                 {
01504                                                         distThreshold = 262144/*512*512*/;
01505                                                 }
01506                                                 break;
01507                                         default:
01508                                                 break;
01509                                         }
01510                                         dist = DistanceSquared( impactPos, NPCInfo->enemyLastSeenLocation );
01511                                         if ( dist > distThreshold )
01512                                         {//impact would be too far from enemy
01513                                                 tooFar = qtrue;
01514                                         }
01515                                 }
01516 
01517                                 if ( !tooClose && !tooFar )
01518                                 {//okay too shoot at last pos
01519                                         VectorSubtract( NPCInfo->enemyLastSeenLocation, muzzle, dir );
01520                                         VectorNormalize( dir );
01521                                         vectoangles( dir, angles );
01522 
01523                                         NPCInfo->desiredYaw             = angles[YAW];
01524                                         NPCInfo->desiredPitch   = angles[PITCH];
01525 
01526                                         shoot = qtrue;
01527                                         faceEnemy = qfalse;
01528                                         //AI_GroupUpdateSquadstates( NPCInfo->group, NPC, SQUAD_STAND_AND_SHOOT );
01529                                         return;
01530                                 }
01531                         }
01532                 }
01533         }
01534 }
01535 
01536 void ST_TrackEnemy( gentity_t *self, vec3_t enemyPos )
01537 {
01538         //clear timers
01539         TIMER_Set( self, "attackDelay", Q_irand( 1000, 2000 ) );
01540         //TIMER_Set( self, "duck", -1 );
01541         TIMER_Set( self, "stick", Q_irand( 500, 1500 ) );
01542         TIMER_Set( self, "stand", -1 );
01543         TIMER_Set( self, "scoutTime", TIMER_Get( self, "stick" )-level.time+Q_irand(5000, 10000) );
01544         //leave my combat point
01545         NPC_FreeCombatPoint( self->NPC->combatPoint, qfalse );
01546         //go after his last seen pos
01547         NPC_SetMoveGoal( self, enemyPos, 16, qfalse, -1, NULL );
01548 }
01549 
01550 int ST_ApproachEnemy( gentity_t *self )
01551 {
01552         TIMER_Set( self, "attackDelay", Q_irand( 250, 500 ) );
01553         //TIMER_Set( self, "duck", -1 );
01554         TIMER_Set( self, "stick", Q_irand( 1000, 2000 ) );
01555         TIMER_Set( self, "stand", -1 );
01556         TIMER_Set( self, "scoutTime", TIMER_Get( self, "stick" )-level.time+Q_irand(5000, 10000) );
01557         //leave my combat point
01558         NPC_FreeCombatPoint( self->NPC->combatPoint, qfalse );
01559         //return the relevant combat point flags
01560         return (CP_CLEAR|CP_CLOSEST);
01561 }
01562 
01563 void ST_HuntEnemy( gentity_t *self )
01564 {
01565         //TIMER_Set( NPC, "attackDelay", Q_irand( 250, 500 ) );//Disabled this for now, guys who couldn't hunt would never attack
01566         //TIMER_Set( NPC, "duck", -1 );
01567         TIMER_Set( NPC, "stick", Q_irand( 250, 1000 ) );
01568         TIMER_Set( NPC, "stand", -1 );
01569         TIMER_Set( NPC, "scoutTime", TIMER_Get( NPC, "stick" )-level.time+Q_irand(5000, 10000) );
01570         //leave my combat point
01571         NPC_FreeCombatPoint( NPCInfo->combatPoint, qfalse );
01572         //go directly after the enemy
01573         if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES )
01574         {
01575                 self->NPC->goalEntity = NPC->enemy;
01576         }
01577 }
01578 
01579 void ST_TransferTimers( gentity_t *self, gentity_t *other )
01580 {
01581         TIMER_Set( other, "attackDelay", TIMER_Get( self, "attackDelay" )-level.time );
01582         TIMER_Set( other, "duck", TIMER_Get( self, "duck" )-level.time );
01583         TIMER_Set( other, "stick", TIMER_Get( self, "stick" )-level.time );
01584         TIMER_Set( other, "scoutTime", TIMER_Get( self, "scout" )-level.time );
01585         TIMER_Set( other, "roamTime", TIMER_Get( self, "roamTime" )-level.time );
01586         TIMER_Set( other, "stand", TIMER_Get( self, "stand" )-level.time );
01587         TIMER_Set( self, "attackDelay", -1 );
01588         TIMER_Set( self, "duck", -1 );
01589         TIMER_Set( self, "stick", -1 );
01590         TIMER_Set( self, "scoutTime", -1 );
01591         TIMER_Set( self, "roamTime", -1 );
01592         TIMER_Set( self, "stand", -1 );
01593 }
01594 
01595 void ST_TransferMoveGoal( gentity_t *self, gentity_t *other )
01596 {
01597         if ( trap_ICARUS_TaskIDPending( self, TID_MOVE_NAV ) )
01598         {//can't transfer movegoal when a script we're running is waiting to complete
01599                 return;
01600         }
01601         if ( self->NPC->combatPoint != -1 )
01602         {//I've got a combatPoint I'm going to, give it to him
01603                 self->NPC->lastFailedCombatPoint = other->NPC->combatPoint = self->NPC->combatPoint;
01604                 self->NPC->combatPoint = -1;
01605         }
01606         else
01607         {//I must be going for a goal, give that to him instead
01608                 if ( self->NPC->goalEntity == self->NPC->tempGoal )
01609                 {
01610                         NPC_SetMoveGoal( other, self->NPC->tempGoal->r.currentOrigin, self->NPC->goalRadius, ((self->NPC->tempGoal->flags&FL_NAVGOAL)?qtrue:qfalse), -1, NULL );
01611                 }
01612                 else
01613                 {
01614                         other->NPC->goalEntity = self->NPC->goalEntity;
01615                 }
01616         }
01617         //give him my squadstate
01618         AI_GroupUpdateSquadstates( self->NPC->group, other, NPCInfo->squadState );
01619 
01620         //give him my timers and clear mine
01621         ST_TransferTimers( self, other );
01622 
01623         //now make me stand around for a second or two at least
01624         AI_GroupUpdateSquadstates( self->NPC->group, self, SQUAD_STAND_AND_SHOOT );
01625         TIMER_Set( self, "stand", Q_irand( 1000, 3000 ) );
01626 }
01627 
01628 int ST_GetCPFlags( void )
01629 {
01630         int cpFlags = 0;
01631         if ( NPC && NPCInfo->group )
01632         {
01633                 if ( NPC == NPCInfo->group->commander && NPC->client->NPC_class == CLASS_IMPERIAL )
01634                 {//imperials hang back and give orders
01635                         if ( NPCInfo->group->numGroup > 1 && Q_irand( -3, NPCInfo->group->numGroup ) > 1 )
01636                         {//FIXME: make sure he;s giving orders with these lines
01637                                 if ( Q_irand( 0, 1 ) )
01638                                 {
01639                                         ST_Speech( NPC, SPEECH_CHASE, 0.5 );
01640                                 }
01641                                 else
01642                                 {
01643                                         ST_Speech( NPC, SPEECH_YELL, 0.5 );
01644                                 }
01645                         }
01646                         cpFlags = (CP_CLEAR|CP_COVER|CP_AVOID|CP_SAFE|CP_RETREAT);
01647                 }
01648                 else if ( NPCInfo->group->morale < 0 )
01649                 {//hide
01650                         cpFlags = (CP_COVER|CP_AVOID|CP_SAFE|CP_RETREAT);
01651                 }
01652                 else if ( NPCInfo->group->morale < NPCInfo->group->numGroup )
01653                 {//morale is low for our size
01654                         int moraleDrop = NPCInfo->group->numGroup - NPCInfo->group->morale;
01655                         if ( moraleDrop < -6 )
01656                         {//flee (no clear shot needed)
01657                                 cpFlags = (CP_FLEE|CP_RETREAT|CP_COVER|CP_AVOID|CP_SAFE);
01658                         }
01659                         else if ( moraleDrop < -3 )
01660                         {//retreat (no clear shot needed)
01661                                 cpFlags = (CP_RETREAT|CP_COVER|CP_AVOID|CP_SAFE);
01662                         }
01663                         else if ( moraleDrop < 0 )
01664                         {//cover (no clear shot needed)
01665                                 cpFlags = (CP_COVER|CP_AVOID|CP_SAFE);
01666                         }
01667                 }
01668                 else
01669                 {
01670                         int moraleBoost = NPCInfo->group->morale - NPCInfo->group->numGroup;
01671                         if ( moraleBoost > 20 )
01672                         {//charge to any one and outflank (no cover needed)
01673                                 cpFlags = (CP_CLEAR|CP_FLANK|CP_APPROACH_ENEMY);
01674                         }
01675                         else if ( moraleBoost > 15 )
01676                         {//charge to closest one (no cover needed)
01677                                 cpFlags = (CP_CLEAR|CP_CLOSEST|CP_APPROACH_ENEMY);
01678                         }
01679                         else if ( moraleBoost > 10 )
01680                         {//charge closer (no cover needed)
01681                                 cpFlags = (CP_CLEAR|CP_APPROACH_ENEMY);
01682                         }
01683                 }
01684         }
01685         if ( !cpFlags )
01686         {
01687                 //at some medium level of morale
01688                 switch( Q_irand( 0, 3 ) )
01689                 {
01690                 case 0://just take the nearest one
01691                         cpFlags = (CP_CLEAR|CP_COVER|CP_NEAREST);
01692                         break;
01693                 case 1://take one closer to the enemy
01694                         cpFlags = (CP_CLEAR|CP_COVER|CP_APPROACH_ENEMY);
01695                         break;
01696                 case 2://take the one closest to the enemy
01697                         cpFlags = (CP_CLEAR|CP_COVER|CP_CLOSEST|CP_APPROACH_ENEMY);
01698                         break;
01699                 case 3://take the one on the other side of the enemy
01700                         cpFlags = (CP_CLEAR|CP_COVER|CP_FLANK|CP_APPROACH_ENEMY);
01701                         break;
01702                 }
01703         }
01704         if ( NPC && (NPCInfo->scriptFlags&SCF_USE_CP_NEAREST) )
01705         {
01706                 cpFlags &= ~(CP_FLANK|CP_APPROACH_ENEMY|CP_CLOSEST);
01707                 cpFlags |= CP_NEAREST;
01708         }
01709         return cpFlags;
01710 }
01711 /*
01712 -------------------------
01713 ST_Commander
01714 
01715   Make decisions about who should go where, etc.
01716 
01717 FIXME: leader (group-decision-making) AI?
01718 FIXME: need alternate routes!
01719 FIXME: more group voice interaction
01720 FIXME: work in pairs?
01721 
01722 -------------------------
01723 */
01724 void ST_Commander( void )
01725 {
01726         int             i, j;
01727         int             cp, cpFlags_org, cpFlags;
01728         AIGroupInfo_t   *group = NPCInfo->group;
01729         gentity_t       *member;//, *buddy;
01730         qboolean        runner = qfalse;
01731         qboolean        enemyLost = qfalse;
01732         qboolean        enemyProtected = qfalse;
01733         qboolean        scouting = qfalse;
01734         int                     squadState;
01735         int                     curMemberNum, lastMemberNum;
01736         float           avoidDist;
01737 
01738         group->processed = qtrue;
01739 
01740         if ( group->enemy == NULL || group->enemy->client == NULL )
01741         {//hmm, no enemy...?!
01742                 return;
01743         }
01744 
01745         //FIXME: have this group commander check the enemy group (if any) and see if they have 
01746         //              superior numbers.  If they do, fall back rather than advance.  If you have
01747         //              superior numbers, advance on them.
01748         //FIXME: find the group commander and have him occasionally give orders when there is speech
01749         //FIXME: start fleeing when only a couple of you vs. a lightsaber, possibly give up if the only one left
01750 
01751         SaveNPCGlobals();
01752 
01753         if ( group->lastSeenEnemyTime < level.time - 180000 )
01754         {//dissolve the group
01755                 ST_Speech( NPC, SPEECH_LOST, 0.0f );
01756                 group->enemy->waypoint = NAV_FindClosestWaypointForEnt( group->enemy, WAYPOINT_NONE );
01757                 for ( i = 0; i < group->numGroup; i++ )
01758                 {
01759                         member = &g_entities[group->member[i].number];
01760                         SetNPCGlobals( member );
01761                         if ( trap_ICARUS_TaskIDPending( NPC, TID_MOVE_NAV ) )
01762                         {//running somewhere that a script requires us to go, don't break from that
01763                                 continue;
01764                         }
01765                         if ( !(NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) )
01766                         {//not allowed to move on my own
01767                                 continue;
01768                         }
01769                         //Lost enemy for three minutes?  go into search mode?
01770                         G_ClearEnemy( NPC );
01771                         NPC->waypoint = NAV_FindClosestWaypointForEnt( NPC, group->enemy->waypoint );
01772                         if ( NPC->waypoint == WAYPOINT_NONE )
01773                         {
01774                                 NPCInfo->behaviorState = BS_DEFAULT;//BS_PATROL;
01775                         }
01776                         else if ( group->enemy->waypoint == WAYPOINT_NONE || (trap_Nav_GetPathCost( NPC->waypoint, group->enemy->waypoint ) >= Q3_INFINITE) )
01777                         {
01778                                 NPC_BSSearchStart( NPC->waypoint, BS_SEARCH );
01779                         }
01780                         else
01781                         {
01782                                 NPC_BSSearchStart( group->enemy->waypoint, BS_SEARCH );
01783                         }
01784                 }
01785                 group->enemy = NULL;
01786                 RestoreNPCGlobals();
01787                 return;
01788         }
01789 
01790 
01791         //See if anyone in our group is not alerted and alert them
01792         /*
01793         for ( i = 0; i < group->numGroup; i++ )
01794         {
01795                 member = &g_entities[group->member[i].number];
01796                 if ( !member->enemy )
01797                 {//he's not mad, so get him mad
01798                         //Have his buddy tell him to get mad
01799                         if ( group->member[i].closestBuddy != ENTITYNUM_NONE )
01800                         {
01801                                 buddy = &g_entities[group->member[i].closestBuddy];
01802                                 if ( buddy->enemy == group->enemy )
01803                                 {
01804                                         SetNPCGlobals( buddy );
01805                                         ST_Speech( NPC, SPEECH_CHARGE, 0.7f );
01806                                 }
01807                         }
01808                         SetNPCGlobals( member );
01809                         G_SetEnemy( member, group->enemy );
01810                 }
01811         }
01812         */
01813         //Okay, everyone is mad
01814 
01815         //see if anyone is running
01816         if ( group->numState[SQUAD_SCOUT] > 0 || 
01817                 group->numState[SQUAD_TRANSITION] > 0 || 
01818                 group->numState[SQUAD_RETREAT] > 0 )
01819         {//someone is running
01820                 runner = qtrue;
01821         }
01822 
01823         if (  group->lastSeenEnemyTime > level.time - 32000 && group->lastSeenEnemyTime < level.time - 30000 )
01824         {//no-one has seen the enemy for 30 seconds// and no-one is running after him
01825                 if ( group->commander && !Q_irand( 0, 1 ) )
01826                 {
01827                         ST_Speech( group->commander, SPEECH_ESCAPING, 0.0f );
01828                 }
01829                 else
01830                 {
01831                         ST_Speech( NPC, SPEECH_ESCAPING, 0.0f );
01832                 }
01833                 //don't say this again
01834                 NPCInfo->blockedSpeechDebounceTime = level.time + 3000;
01835         }
01836 
01837         if ( group->lastSeenEnemyTime < level.time - 10000 )
01838         {//no-one has seen the enemy for at least 10 seconds!  Should send a scout
01839                 enemyLost = qtrue;
01840         }
01841 
01842         if ( group->lastClearShotTime < level.time - 5000 )
01843         {//no-one has had a clear shot for 5 seconds!
01844                 enemyProtected = qtrue;
01845         }
01846 
01847         //Go through the list:
01848 
01849         //Everyone should try to get to a combat point if possible
01850         if ( d_asynchronousGroupAI.integer )
01851         {//do one member a turn
01852                 group->activeMemberNum++;
01853                 if ( group->activeMemberNum >= group->numGroup )
01854                 {
01855                         group->activeMemberNum = 0;
01856                 }
01857                 curMemberNum = group->activeMemberNum;
01858                 lastMemberNum = curMemberNum + 1;
01859         }
01860         else
01861         {
01862                 curMemberNum = 0;
01863                 lastMemberNum = group->numGroup;
01864         }
01865         for ( i = curMemberNum; i < lastMemberNum; i++ )
01866         {
01867                 //reset combat point flags
01868                 cp = -1;
01869                 cpFlags = 0;
01870                 squadState = SQUAD_IDLE;
01871                 avoidDist = 0;
01872                 scouting = qfalse;
01873 
01874                 //get the next guy
01875                 member = &g_entities[group->member[i].number];
01876                 if ( !member->enemy )
01877                 {//don't include guys that aren't angry
01878                         continue;
01879                 }
01880                 SetNPCGlobals( member );
01881 
01882                 if ( !TIMER_Done( NPC, "flee" ) )
01883                 {//running away
01884                         continue;
01885                 }
01886 
01887                 if ( trap_ICARUS_TaskIDPending( NPC, TID_MOVE_NAV ) )
01888                 {//running somewhere that a script requires us to go
01889                         continue;
01890                 }
01891 
01892                 if ( NPC->s.weapon == WP_NONE 
01893                         && NPCInfo->goalEntity 
01894                         && NPCInfo->goalEntity == NPCInfo->tempGoal
01895                         && NPCInfo->goalEntity->enemy
01896                         && NPCInfo->goalEntity->enemy->s.eType == ET_ITEM )
01897                 {//running to pick up a gun, don't do other logic
01898                         continue;
01899                 }
01900 
01901                 //see if this member should start running (only if have no officer... FIXME: should always run from AEL_DANGER_GREAT?)
01902                 if ( !group->commander || group->commander->NPC->rank < RANK_ENSIGN )
01903                 {
01904                         if ( NPC_CheckForDanger( NPC_CheckAlertEvents( qtrue, qtrue, -1, qfalse, AEL_DANGER ) ) )
01905                         {//going to run
01906                                 ST_Speech( NPC, SPEECH_COVER, 0 );
01907                                 continue;
01908                         }
01909                 }
01910                 
01911                 if ( !(NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) )
01912                 {//not allowed to do combat-movement
01913                         continue;
01914                 }
01915 
01916                 //check the local state
01917                 if ( NPCInfo->squadState != SQUAD_RETREAT )
01918                 {//not already retreating
01919                         if ( NPC->client->ps.weapon == WP_NONE )
01920                         {//weaponless, should be hiding
01921                                 if ( NPCInfo->goalEntity == NULL || NPCInfo->goalEntity->enemy == NULL || NPCInfo->goalEntity->enemy->s.eType != ET_ITEM )
01922                                 {//not running after a pickup
01923                                         if ( TIMER_Done( NPC, "hideTime" ) || (DistanceSquared( group->enemy->r.currentOrigin, NPC->r.currentOrigin ) < 65536 && NPC_ClearLOS4( NPC->enemy )) )
01924                                         {//done hiding or enemy near and can see us
01925                                                 //er, start another flee I guess?
01926                                                 NPC_StartFlee( NPC->enemy, NPC->enemy->r.currentOrigin, AEL_DANGER_GREAT, 5000, 10000 );
01927                                         }//else, just hang here
01928                                 }
01929                                 continue;
01930                         }
01931                         if ( TIMER_Done( NPC, "roamTime" ) && TIMER_Done( NPC, "hideTime" ) && NPC->health > 10 && !trap_InPVS( group->enemy->r.currentOrigin, NPC->r.currentOrigin ) )
01932                         {//cant even see enemy
01933                                 //better go after him
01934                                 cpFlags |= (CP_CLEAR|CP_COVER);
01935                         }
01936                         else if ( NPCInfo->localState == LSTATE_UNDERFIRE )
01937                         {//we've been shot
01938                                 switch( group->enemy->client->ps.weapon )
01939                                 {
01940                                 case WP_SABER:
01941                                         if ( DistanceSquared( group->enemy->r.currentOrigin, NPC->r.currentOrigin ) < 65536 )//256 squared
01942                                         {
01943                                                 cpFlags |= (CP_AVOID_ENEMY|CP_COVER|CP_AVOID|CP_RETREAT);
01944                                                 if ( !group->commander || group->commander->NPC->rank  < RANK_ENSIGN )
01945                                                 {
01946                                                         squadState = SQUAD_RETREAT;
01947                                                 }
01948                                                 avoidDist = 256;
01949                                         }
01950                                         break;
01951                                 default:
01952                                 case WP_BLASTER:
01953                                         cpFlags |= (CP_COVER);
01954                                         break;
01955                                 }
01956                                 if ( NPC->health <= 10 )
01957                                 {
01958                                         if ( !group->commander || group->commander->NPC->rank < RANK_ENSIGN )
01959                                         {
01960                                                 cpFlags |= (CP_FLEE|CP_AVOID|CP_RETREAT);
01961                                                 squadState = SQUAD_RETREAT;
01962                                         }
01963                                 }
01964                         }
01965                         else
01966                         {//not hit, see if there are other reasons we should run
01967                                 if ( trap_InPVS( NPC->r.currentOrigin, group->enemy->r.currentOrigin ) )
01968                                 {//in the same room as enemy
01969                                         if ( NPC->client->ps.weapon == WP_ROCKET_LAUNCHER &&
01970                                                 DistanceSquared( group->enemy->r.currentOrigin, NPC->r.currentOrigin ) < MIN_ROCKET_DIST_SQUARED &&
01971                                                 NPCInfo->squadState != SQUAD_TRANSITION )
01972                                         {//too close for me to fire my weapon and I'm not already on the move
01973                                                 cpFlags |= (CP_AVOID_ENEMY|CP_CLEAR|CP_AVOID);
01974                                                 avoidDist = 256;
01975                                         }
01976                                         else
01977                                         {
01978                                                 switch( group->enemy->client->ps.weapon )
01979                                                 {
01980                                                 case WP_SABER:
01981                                                         //if ( group->enemy->client->ps.SaberLength() > 0 )
01982                                                         if (!group->enemy->client->ps.saberHolstered)
01983                                                         {
01984                                                                 if ( DistanceSquared( group->enemy->r.currentOrigin, NPC->r.currentOrigin ) < 65536 )
01985                                                                 {
01986                                                                         if ( TIMER_Done( NPC, "hideTime" ) )
01987                                                                         {
01988                                                                                 if ( NPCInfo->squadState != SQUAD_TRANSITION )
01989                                                                                 {//not already moving: FIXME: we need to see if where we're going is good now?
01990                                                                                         cpFlags |= (CP_AVOID_ENEMY|CP_CLEAR|CP_AVOID);
01991                                                                                         avoidDist = 256;
01992                                                                                 }
01993                                                                         }
01994                                                                 }
01995                                                         }
01996                                                 default:
01997                                                         break;
01998                                                 }
01999                                         }
02000                                 }
02001                         }
02002                 }
02003 
02004                 if ( !cpFlags )
02005                 {//okay, we have no new enemy-driven reason to run... let's use tactics now
02006                         if ( runner && NPCInfo->combatPoint != -1 )
02007                         {//someone is running and we have a combat point already
02008                                 if ( NPCInfo->squadState != SQUAD_SCOUT &&
02009                                         NPCInfo->squadState != SQUAD_TRANSITION &&
02010                                         NPCInfo->squadState != SQUAD_RETREAT )
02011                                 {//it's not us
02012                                         if ( TIMER_Done( NPC, "verifyCP" ) && DistanceSquared( NPC->r.currentOrigin, level.combatPoints[NPCInfo->combatPoint].origin ) > 64*64 )
02013                                         {//1 - 3 seconds have passed since you chose a CP, see if you're there since, for some reason, you've stopped running...
02014                                                 //uh, WTF, we're not on our combat point?
02015                                                 //er, try again, I guess?
02016                                                 cp = NPCInfo->combatPoint;
02017                                                 cpFlags |= ST_GetCPFlags();
02018                                         }
02019                                         else
02020                                         {//cover them
02021                                                 //stop ducking
02022                                                 TIMER_Set( NPC, "duck", -1 );
02023                                                 //start shooting
02024                                                 TIMER_Set( NPC, "attackDelay", -1 );
02025                                                 //AI should take care of the rest - fire at enemy
02026                                         }
02027                                 }
02028                                 else
02029                                 {//we're running
02030                                         //see if we're blocked
02031                                         if ( NPCInfo->aiFlags & NPCAI_BLOCKED )
02032                                         {//dammit, something is in our way
02033                                                 //see if it's one of ours
02034                                                 for ( j = 0; j < group->numGroup; j++ )
02035                                                 {
02036                                                         if ( group->member[j].number == NPCInfo->blockingEntNum )
02037                                                         {//we're being blocked by one of our own, pass our goal onto them and I'll stand still
02038                                                                 ST_TransferMoveGoal( NPC, &g_entities[group->member[j].number] );
02039                                                                 break;
02040                                                         }
02041                                                 }
02042                                         }
02043                                         //we don't need to do anything else
02044                                         continue;
02045                                 }
02046                         }
02047                         else
02048                         {//okay no-one is running, use some tactics
02049                                 if ( NPCInfo->combatPoint != -1 )
02050                                 {//we have a combat point we're supposed to be running to
02051                                         if ( NPCInfo->squadState != SQUAD_SCOUT &&
02052                                                 NPCInfo->squadState != SQUAD_TRANSITION &&
02053                                                 NPCInfo->squadState != SQUAD_RETREAT )
02054                                         {//but we're not running
02055                                                 if ( TIMER_Done( NPC, "verifyCP" ) )
02056                                                 {//1 - 3 seconds have passed since you chose a CP, see if you're there since, for some reason, you've stopped running...
02057                                                         if ( DistanceSquared( NPC->r.currentOrigin, level.combatPoints[NPCInfo->combatPoint].origin ) > 64*64 )
02058                                                         {//uh, WTF, we're not on our combat point?
02059                                                                 //er, try again, I guess?
02060                                                                 cp = NPCInfo->combatPoint;
02061                                                                 cpFlags |= ST_GetCPFlags();
02062                                                         }
02063                                                 }
02064                                         }
02065                                 }
02066                                 if ( enemyLost )
02067                                 {//if no-one has seen the enemy for a while, send a scout
02068                                         //ask where he went
02069                                         if ( group->numState[SQUAD_SCOUT] <= 0 )
02070                                         {
02071                                                 scouting = qtrue;
02072                                                 NPC_ST_StoreMovementSpeech( SPEECH_CHASE, 0.0f );
02073                                         }
02074                                         //Since no-one else has done this, I should be the closest one, so go after him...
02075                                         ST_TrackEnemy( NPC, group->enemyLastSeenPos );
02076                                         //set me into scout mode
02077                                         AI_GroupUpdateSquadstates( group, NPC, SQUAD_SCOUT );
02078                                         //we're not using a cp, so we need to set runner to true right here
02079                                         runner = qtrue;
02080                                 }
02081                                 else if ( enemyProtected )
02082                                 {//if no-one has a clear shot at the enemy, someone should go after him
02083                                         //FIXME: if I'm in an area where no safe combat points have a clear shot at me, they don't come after me... they should anyway, though after some extra hesitation.
02084                                         //ALSO: seem to give up when behind an area portal?
02085                                         //since no-one else here has done this, I should be the closest one
02086                                         if ( TIMER_Done( NPC, "roamTime" ) && !Q_irand( 0, group->numGroup) )
02087                                         {//only do this if we're ready to move again and we feel like it
02088                                                 cpFlags |= ST_ApproachEnemy( NPC );
02089                                                 //set me into scout mode
02090                                                 AI_GroupUpdateSquadstates( group, NPC, SQUAD_SCOUT );
02091                                         }
02092                                 }
02093                                 else
02094                                 {//group can see and has been shooting at the enemy
02095                                         //see if we should do something fancy?
02096                                         
02097                                         {//we're ready to move
02098                                                 if ( NPCInfo->combatPoint == -1 )
02099                                                 {//we're not on a combat point
02100                                                         if ( 1 )
02101                                                         {//we should go for a combat point
02102                                                                 cpFlags |= ST_GetCPFlags();
02103                                                         }
02104                                                         else
02105                                                         {
02106                                                                 TIMER_Set( NPC, "stick", Q_irand( 2000, 4000 ) );
02107                                                                 TIMER_Set( NPC, "roamTime", Q_irand( 1000, 3000 ) );
02108                                                         }
02109                                                 }
02110                                                 else if ( TIMER_Done( NPC, "roamTime" ) )
02111                                                 {//we are already on a combat point
02112                                                         if ( i == 0 )
02113                                                         {//we're the closest
02114                                                                 if ( (group->morale-group->numGroup>0) && !Q_irand( 0, 4 ) )
02115                                                                 {//try to outflank him
02116                                                                         cpFlags |= (CP_CLEAR|CP_COVER|CP_FLANK|CP_APPROACH_ENEMY);
02117                                                                 }
02118                                                                 else if ( (group->morale-group->numGroup<0) )
02119                                                                 {//better move!
02120                                                                         cpFlags |= ST_GetCPFlags();
02121                                                                 }
02122                                                                 else
02123                                                                 {//If we're point, then get down
02124                                                                         TIMER_Set( NPC, "roamTime", Q_irand( 2000, 5000 ) );
02125                                                                         TIMER_Set( NPC, "stick", Q_irand( 2000, 5000 ) );
02126                                                                         //FIXME: what if we can't shoot from a ducked pos?
02127                                                                         TIMER_Set( NPC, "duck", Q_irand( 3000, 4000 ) );
02128                                                                         AI_GroupUpdateSquadstates( group, NPC, SQUAD_POINT );
02129                                                                 }
02130                                                         }
02131                                                         else if ( i == group->numGroup - 1 )
02132                                                         {//farthest from the enemy
02133                                                                 if ( (group->morale-group->numGroup<0) )
02134                                                                 {//low morale, just hang here
02135                                                                         TIMER_Set( NPC, "roamTime", Q_irand( 2000, 5000 ) );
02136                                                                         TIMER_Set( NPC, "stick", Q_irand( 2000, 5000 ) );
02137                                                                 }
02138                                                                 else if ( (group->morale-group->numGroup>0) )
02139                                                                 {//try to move in on the enemy
02140                                                                         cpFlags |= ST_ApproachEnemy( NPC );
02141                                                                         //set me into scout mode
02142                                                                         AI_GroupUpdateSquadstates( group, NPC, SQUAD_SCOUT );
02143                                                                 }
02144                                                                 else
02145                                                                 {//use normal decision making process
02146                                                                         cpFlags |= ST_GetCPFlags();
02147                                                                 }
02148                                                         }
02149                                                         else
02150                                                         {//someone in-between
02151                                                                 if ( (group->morale-group->numGroup<0) || !Q_irand( 0, 4 ) )
02152                                                                 {//do something
02153                                                                         cpFlags |= ST_GetCPFlags();
02154                                                                 }
02155                                                                 else
02156                                                                 {
02157                                                                         TIMER_Set( NPC, "stick", Q_irand( 2000, 4000 ) );
02158                                                                         TIMER_Set( NPC, "roamTime", Q_irand( 2000, 4000 ) );
02159                                                                 }
02160                                                         }
02161                                                 }
02162                                         }
02163                                         if ( !cpFlags )
02164                                         {//still not moving
02165                                                 //see if we should say something?
02166                                                 /*
02167                                                 if ( NPC->attackDebounceTime < level.time - 2000 )
02168                                                 {//we, personally, haven't shot for 2 seconds
02169                                                         //maybe yell at the enemy?
02170                                                         ST_Speech( NPC, SPEECH_CHARGE, 0.9f );
02171                                                 }
02172                                                 */
02173 
02174                                                 //see if we should do other fun stuff
02175                                                 //toy with ducking
02176                                                 if ( TIMER_Done( NPC, "duck" ) )
02177                                                 {//not ducking
02178                                                         if ( TIMER_Done( NPC, "stand" ) )
02179                                                         {//don't have to keep standing
02180                                                                 if ( NPCInfo->combatPoint == -1 || (level.combatPoints[NPCInfo->combatPoint].flags&CPF_DUCK) )
02181                                                                 {//okay to duck here
02182                                                                         if ( !Q_irand( 0, 3 ) )
02183                                                                         {
02184                                                                                 TIMER_Set( NPC, "duck", Q_irand( 1000, 3000 ) );
02185                                                                         }
02186                                                                 }
02187                                                         }
02188                                                 }
02189                                                 //FIXME: what about CPF_LEAN?
02190                                         }
02191                                 }
02192                         }
02193                 }
02194 
02195                 //clear the local state
02196                 NPCInfo->localState = LSTATE_NONE;
02197 
02198                 if ( NPCInfo->scriptFlags&SCF_USE_CP_NEAREST )
02199                 {
02200                         cpFlags &= ~(CP_FLANK|CP_APPROACH_ENEMY|CP_CLOSEST);
02201                         cpFlags |= CP_NEAREST;
02202                 }
02203                 //Assign combat points
02204                 if ( cpFlags )
02205                 {//we want to run to a combat point
02206                         /*
02207                         if ( NPCInfo->combatPoint != -1 )
02208                         {//if we're on a combat point, we obviously don't want the one we're closest to
02209                                 cpFlags |= CP_AVOID;
02210                         }
02211                         */
02212 
02213                         if ( group->enemy->client->ps.weapon == WP_SABER && /*group->enemy->client->ps.SaberLength() > 0*/!group->enemy->client->ps.saberHolstered )
02214                         {//we obviously want to avoid the enemy if he has a saber
02215                                 cpFlags |= CP_AVOID_ENEMY;
02216                                 avoidDist = 256;
02217                         }
02218 
02219                         //remember what we *wanted* to do...
02220                         cpFlags_org = cpFlags;
02221 
02222                         //now get a combat point
02223                         if ( cp == -1 )
02224                         {//may have had sone set above
02225                                 cp = NPC_FindCombatPoint( NPC->r.currentOrigin, NPC->r.currentOrigin, group->enemy->r.currentOrigin, cpFlags|CP_HAS_ROUTE, avoidDist, NPCInfo->lastFailedCombatPoint );
02226                         }
02227                         while ( cp == -1 && cpFlags != CP_ANY )
02228                         {//start "OR"ing out certain flags to see if we can find *any* point
02229                                 if ( cpFlags & CP_INVESTIGATE )
02230                                 {//don't need to investigate
02231                                         cpFlags &= ~CP_INVESTIGATE;
02232                                 }
02233                                 else if ( cpFlags & CP_SQUAD )
02234                                 {//don't need to stick to squads
02235                                         cpFlags &= ~CP_SQUAD;
02236                                 }
02237                                 else if ( cpFlags & CP_DUCK )
02238                                 {//don't need to duck
02239                                         cpFlags &= ~CP_DUCK;
02240                                 }
02241                                 else if ( cpFlags & CP_NEAREST )
02242                                 {//don't need closest one to me
02243                                         cpFlags &= ~CP_NEAREST;
02244                                 }
02245                                 else if ( cpFlags & CP_FLANK )
02246                                 {//don't need to flank enemy
02247                                         cpFlags &= ~CP_FLANK;
02248                                 }
02249                                 else if ( cpFlags & CP_SAFE )
02250                                 {//don't need one that hasn't been shot at recently
02251                                         cpFlags &= ~CP_SAFE;
02252                                 }
02253                                 else if ( cpFlags & CP_CLOSEST )
02254                                 {//don't need to get closest to enemy
02255                                         cpFlags &= ~CP_CLOSEST;
02256                                         //but let's try to approach at least
02257                                         cpFlags |= CP_APPROACH_ENEMY;
02258                                 }
02259                                 else if ( cpFlags & CP_APPROACH_ENEMY )
02260                                 {//don't need to approach enemy
02261                                         cpFlags &= ~CP_APPROACH_ENEMY;
02262                                 }
02263                                 else if ( cpFlags & CP_COVER )
02264                                 {//don't need cover
02265                                         cpFlags &= ~CP_COVER;
02266                                         //but let's pick one that makes us duck
02267                                         cpFlags |= CP_DUCK;
02268                                 }
02269                                 else if ( cpFlags & CP_CLEAR )
02270                                 {//don't need a clear shot to enemy
02271                                         cpFlags &= ~CP_CLEAR;
02272                                 }
02273                                 else if ( cpFlags & CP_AVOID_ENEMY )
02274                                 {//don't need to avoid enemy
02275                                         cpFlags &= ~CP_AVOID_ENEMY;
02276                                 }
02277                                 else if ( cpFlags & CP_RETREAT )
02278                                 {//don't need to retreat
02279                                         cpFlags &= ~CP_RETREAT;
02280                                 }
02281                                 else if ( cpFlags &CP_FLEE )
02282                                 {//don't need to flee
02283                                         cpFlags &= ~CP_FLEE;
02284                                         //but at least avoid enemy and pick one that gives cover
02285                                         cpFlags |= (CP_COVER|CP_AVOID_ENEMY);
02286                                 }
02287                                 else if ( cpFlags & CP_AVOID )
02288                                 {//okay, even pick one right by me
02289                                         cpFlags &= ~CP_AVOID;
02290                                 }
02291                                 else
02292                                 {
02293                                         cpFlags = CP_ANY;
02294                                 }
02295                                 //now try again
02296                                 cp = NPC_FindCombatPoint( NPC->r.currentOrigin, NPC->r.currentOrigin, group->enemy->r.currentOrigin, cpFlags|CP_HAS_ROUTE, avoidDist, -1 );
02297                         } 
02298                         //see if we got a valid one
02299                         if ( cp != -1 )
02300                         {//found a combat point
02301                                 //let others know that someone is now running
02302                                 runner = qtrue;
02303                                 //don't change course again until we get to where we're going
02304                                 TIMER_Set( NPC, "roamTime", Q3_INFINITE );
02305                                 TIMER_Set( NPC, "verifyCP", Q_irand( 1000, 3000 ) );//don't make sure you're in your CP for 1 - 3 seconds
02306                                 NPC_SetCombatPoint( cp );
02307                                 NPC_SetMoveGoal( NPC, level.combatPoints[cp].origin, 8, qtrue, cp, NULL );
02308                                 //okay, try a move right now to see if we can even get there
02309 
02310                                 //if ( ST_Move() )
02311                                 {//we actually can get to it, so okay to say you're going there.
02312                                         //FIXME: Hmm... any way we can store this move info so we don't have to do it again
02313                                         //              when our turn to think comes up?
02314 
02315                                         //set us up so others know we're on the move
02316                                         if ( squadState != SQUAD_IDLE )
02317                                         {
02318                                                 AI_GroupUpdateSquadstates( group, NPC, squadState );
02319                                         }
02320                                         else if ( cpFlags&CP_FLEE )
02321                                         {//outright running for your life
02322                                                 AI_GroupUpdateSquadstates( group, NPC, SQUAD_RETREAT );
02323                                         }
02324                                         else
02325                                         {//any other kind of transition between combat points
02326                                                 AI_GroupUpdateSquadstates( group, NPC, SQUAD_TRANSITION );
02327                                         }
02328                                         
02329                                         //unless we're trying to flee, walk slowly
02330                                         if ( !(cpFlags_org&CP_FLEE) )
02331                                         {
02332                                                 //ucmd.buttons |= BUTTON_CAREFUL;
02333                                         }
02334 
02335                                         /*
02336                                         if ( scouting )
02337                                         {//successfully chasing enemy
02338                                                 ST_Speech( NPC, SPEECH_CHASE, 0.0f );
02339                                                 //don't say this again
02340                                                 //group->speechDebounceTime = level.time + 5000;
02341                                         }
02342                                         //flanking:
02343                                         else */if ( cpFlags & CP_FLANK )
02344                                         {
02345                                                 if ( group->numGroup > 1 )
02346                                                 {
02347                                                         NPC_ST_StoreMovementSpeech( SPEECH_OUTFLANK, -1 );
02348                                                 }
02349                                         }
02350                                         else 
02351                                         {//okay, let's cheat
02352                                                 if ( group->numGroup > 1 )
02353                                                 {
02354                                                         float   dot = 1.0f;
02355                                                         if ( !Q_irand( 0, 3 ) )
02356                                                         {//25% of the time, see if we're flanking the enemy
02357                                                                 vec3_t  eDir2Me, eDir2CP;
02358                                                                 
02359                                                                 VectorSubtract( NPC->r.currentOrigin, group->enemy->r.currentOrigin, eDir2Me );
02360                                                                 VectorNormalize( eDir2Me );
02361 
02362                                                                 VectorSubtract( level.combatPoints[NPCInfo->combatPoint].origin, group->enemy->r.currentOrigin, eDir2CP );
02363                                                                 VectorNormalize( eDir2CP );
02364 
02365                                                                 dot = DotProduct( eDir2Me, eDir2CP );
02366                                                         }
02367                                                         
02368                                                         if ( dot < 0.4 )
02369                                                         {//flanking!
02370                                                                 NPC_ST_StoreMovementSpeech( SPEECH_OUTFLANK, -1 );
02371                                                         }
02372                                                         else if ( !Q_irand( 0, 10 ) )
02373                                                         {//regular movement
02374                                                                 NPC_ST_StoreMovementSpeech( SPEECH_YELL, 0.2f );//was SPEECH_COVER
02375                                                         }
02376                                                 }
02377                                         }
02378                                         /*
02379                                         else if ( cpFlags & CP_CLOSEST || cpFlags & CP_APPROACH_ENEMY )
02380                                         {
02381                                                 if ( group->numGroup > 1 )
02382                                                 {
02383                                                         NPC_ST_StoreMovementSpeech( SPEECH_CHASE, 0.4f );
02384                                                 }
02385                                         }
02386                                         */
02387                                 }//else: nothing, a failed move should clear the combatPoint and you can try again next frame
02388                         }
02389                         else if ( NPCInfo->squadState == SQUAD_SCOUT )
02390                         {//we couldn't find a combatPoint by the player, so just go after him directly
02391                                 ST_HuntEnemy( NPC );
02392                                 //set me into scout mode
02393                                 AI_GroupUpdateSquadstates( group, NPC, SQUAD_SCOUT );
02394                                 //AI should take care of rest
02395                         }
02396                 }
02397         }
02398 
02399         RestoreNPCGlobals();
02400         return;
02401 }
02402 
02403 /*
02404 -------------------------
02405 NPC_BSST_Attack
02406 -------------------------
02407 */
02408 
02409 void NPC_BSST_Attack( void )
02410 {
02411         vec3_t  enemyDir, shootDir;
02412         float dot;
02413 
02414         //Don't do anything if we're hurt
02415         if ( NPC->painDebounceTime > level.time )
02416         {
02417                 NPC_UpdateAngles( qtrue, qtrue );
02418                 return;
02419         }
02420 
02421         //NPC_CheckEnemy( qtrue, qfalse );
02422         //If we don't have an enemy, just idle
02423         if ( NPC_CheckEnemyExt(qfalse) == qfalse )
02424         {
02425                 NPC->enemy = NULL;
02426                 if( NPC->client->playerTeam == NPCTEAM_PLAYER )
02427                 {
02428                         NPC_BSPatrol();
02429                 }
02430                 else
02431                 {
02432                         NPC_BSST_Patrol();//FIXME: or patrol?
02433                 }
02434                 return;
02435         }
02436 
02437         //FIXME: put some sort of delay into the guys depending on how they saw you...?
02438 
02439         //Get our group info
02440         if ( TIMER_Done( NPC, "interrogating" ) )
02441         {
02442                 AI_GetGroup( NPC );//, 45, 512, NPC->enemy );
02443         }
02444         else
02445         {
02446                 //FIXME: when done interrogating, I should send out a team alert!
02447         }
02448 
02449         if ( NPCInfo->group )
02450         {//I belong to a squad of guys - we should *always* have a group
02451                 if ( !NPCInfo->group->processed )
02452                 {//I'm the first ent in my group, I'll make the command decisions
02453 #if     AI_TIMERS
02454                         int     startTime = GetTime(0);
02455 #endif//        AI_TIMERS
02456                         ST_Commander();
02457 #if     AI_TIMERS
02458                         int commTime = GetTime ( startTime );
02459                         if ( commTime > 20 )
02460                         {
02461                                 gi.Printf( S_COLOR_RED"ERROR: Commander time: %d\n", commTime );
02462                         }
02463                         else if ( commTime > 10 )
02464                         {
02465                                 gi.Printf( S_COLOR_YELLOW"WARNING: Commander time: %d\n", commTime );
02466                         }
02467                         else if ( commTime > 2 )
02468                         {
02469                                 gi.Printf( S_COLOR_GREEN"Commander time: %d\n", commTime );
02470                         }
02471 #endif//        AI_TIMERS
02472                 }
02473         }
02474         else if ( TIMER_Done( NPC, "flee" ) && NPC_CheckForDanger( NPC_CheckAlertEvents( qtrue, qtrue, -1, qfalse, AEL_DANGER ) ) )
02475         {//not already fleeing, and going to run
02476                 ST_Speech( NPC, SPEECH_COVER, 0 );
02477                 NPC_UpdateAngles( qtrue, qtrue );
02478                 return;
02479         }
02480 
02481         if ( !NPC->enemy )
02482         {//WTF?  somehow we lost our enemy?
02483                 NPC_BSST_Patrol();//FIXME: or patrol?
02484                 return;
02485         }
02486 
02487         enemyLOS = enemyCS = enemyInFOV = qfalse;
02488         move = qtrue;
02489         faceEnemy = qfalse;
02490         shoot = qfalse;
02491         hitAlly = qfalse;
02492         VectorClear( impactPos );
02493         enemyDist = DistanceSquared( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin );
02494 
02495         VectorSubtract( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin, enemyDir );
02496         VectorNormalize( enemyDir );
02497         AngleVectors( NPC->client->ps.viewangles, shootDir, NULL, NULL );
02498         dot = DotProduct( enemyDir, shootDir );
02499         if ( dot > 0.5f ||( enemyDist * (1.0f-dot)) < 10000 )
02500         {//enemy is in front of me or they're very close and not behind me
02501                 enemyInFOV = qtrue;
02502         }
02503 
02504         if ( enemyDist < MIN_ROCKET_DIST_SQUARED )//128
02505         {//enemy within 128
02506                 if ( (NPC->client->ps.weapon == WP_FLECHETTE || NPC->client->ps.weapon == WP_REPEATER) && 
02507                         (NPCInfo->scriptFlags & SCF_ALT_FIRE) )
02508                 {//shooting an explosive, but enemy too close, switch to primary fire
02509                         NPCInfo->scriptFlags &= ~SCF_ALT_FIRE;
02510                         //FIXME: we can never go back to alt-fire this way since, after this, we don't know if we were initially supposed to use alt-fire or not...
02511                 }
02512         }
02513         else if ( enemyDist > 65536 )//256 squared
02514         {
02515                 if ( NPC->client->ps.weapon == WP_DISRUPTOR )
02516                 {//sniping... should be assumed
02517                         if ( !(NPCInfo->scriptFlags&SCF_ALT_FIRE) )
02518                         {//use primary fire
02519                                 NPCInfo->scriptFlags |= SCF_ALT_FIRE;
02520                                 //reset fire-timing variables
02521                                 NPC_ChangeWeapon( WP_DISRUPTOR );
02522                                 NPC_UpdateAngles( qtrue, qtrue );
02523                                 return;
02524                         }
02525                 }
02526         }
02527 
02528         //can we see our target?
02529         if ( NPC_ClearLOS4( NPC->enemy ) )
02530         {
02531                 AI_GroupUpdateEnemyLastSeen( NPCInfo->group, NPC->enemy->r.currentOrigin );
02532                 NPCInfo->enemyLastSeenTime = level.time;
02533                 enemyLOS = qtrue;
02534 
02535                 if ( NPC->client->ps.weapon == WP_NONE )
02536                 {
02537                         enemyCS = qfalse;//not true, but should stop us from firing
02538                         NPC_AimAdjust( -1 );//adjust aim worse longer we have no weapon
02539                 }
02540                 else
02541                 {//can we shoot our target?
02542                         if ( (NPC->client->ps.weapon == WP_ROCKET_LAUNCHER || (NPC->client->ps.weapon == WP_FLECHETTE && (NPCInfo->scriptFlags&SCF_ALT_FIRE))) && enemyDist < MIN_ROCKET_DIST_SQUARED )//128*128
02543                         {
02544                                 enemyCS = qfalse;//not true, but should stop us from firing
02545                                 hitAlly = qtrue;//us!
02546                                 //FIXME: if too close, run away!
02547                         }
02548                         else if ( enemyInFOV )
02549                         {//if enemy is FOV, go ahead and check for shooting
02550                                 int hit = NPC_ShotEntity( NPC->enemy, impactPos );
02551                                 gentity_t *hitEnt = &g_entities[hit];
02552 
02553                                 if ( hit == NPC->enemy->s.number 
02554                                         || ( hitEnt && hitEnt->client && hitEnt->client->playerTeam == NPC->client->enemyTeam )
02555                                         || ( hitEnt && hitEnt->takedamage && ((hitEnt->r.svFlags&SVF_GLASS_BRUSH)||hitEnt->health < 40||NPC->s.weapon == WP_EMPLACED_GUN) ) )
02556                                 {//can hit enemy or enemy ally or will hit glass or other minor breakable (or in emplaced gun), so shoot anyway
02557                                         AI_GroupUpdateClearShotTime( NPCInfo->group );
02558                                         enemyCS = qtrue;
02559                                         NPC_AimAdjust( 2 );//adjust aim better longer we have clear shot at enemy
02560                                         VectorCopy( NPC->enemy->r.currentOrigin, NPCInfo->enemyLastSeenLocation );
02561                                 }
02562                                 else
02563                                 {//Hmm, have to get around this bastard
02564                                         NPC_AimAdjust( 1 );//adjust aim better longer we can see enemy
02565                                         ST_ResolveBlockedShot( hit );
02566                                         if ( hitEnt && hitEnt->client && hitEnt->client->playerTeam == NPC->client->playerTeam )
02567                                         {//would hit an ally, don't fire!!!
02568                                                 hitAlly = qtrue;
02569                                         }
02570                                         else
02571                                         {//Check and see where our shot *would* hit... if it's not close to the enemy (within 256?), then don't fire
02572                                         }
02573                                 }
02574                         }
02575                         else
02576                         {
02577                                 enemyCS = qfalse;//not true, but should stop us from firing
02578                         }
02579                 }
02580         }
02581         else if ( trap_InPVS( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin ) )
02582         {
02583                 NPCInfo->enemyLastSeenTime = level.time;
02584                 faceEnemy = qtrue;
02585                 NPC_AimAdjust( -1 );//adjust aim worse longer we cannot see enemy
02586         }
02587 
02588         if ( NPC->client->ps.weapon == WP_NONE )
02589         {
02590                 faceEnemy = qfalse;
02591                 shoot = qfalse;
02592         }
02593         else
02594         {
02595                 if ( enemyLOS )
02596                 {//FIXME: no need to face enemy if we're moving to some other goal and he's too far away to shoot?
02597                         faceEnemy = qtrue;
02598                 }
02599                 if ( enemyCS )
02600                 {
02601                         shoot = qtrue;
02602                 }
02603         }
02604 
02605         //Check for movement to take care of
02606         ST_CheckMoveState();
02607 
02608         //See if we should override shooting decision with any special considerations
02609         ST_CheckFireState();
02610 
02611         if ( faceEnemy )
02612         {//face the enemy
02613                 NPC_FaceEnemy( qtrue );
02614         }
02615 
02616         if ( !(NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) )
02617         {//not supposed to chase my enemies
02618                 if ( NPCInfo->goalEntity == NPC->enemy )
02619                 {//goal is my entity, so don't move
02620                         move = qfalse;
02621                 }
02622         }
02623 
02624         if ( NPC->client->ps.weaponTime > 0 && NPC->s.weapon == WP_ROCKET_LAUNCHER )
02625         {
02626                 move = qfalse;
02627         }
02628 
02629         if ( move )
02630         {//move toward goal
02631                 if ( NPCInfo->goalEntity )//&& ( NPCInfo->goalEntity != NPC->enemy || enemyDist > 10000 ) )//100 squared
02632                 {
02633                         move = ST_Move();
02634                 }
02635                 else
02636                 {
02637                         move = qfalse;
02638                 }
02639         }
02640 
02641         if ( !move )
02642         {
02643                 if ( !TIMER_Done( NPC, "duck" ) )
02644                 {
02645                         ucmd.upmove = -127;
02646                 }
02647                 //FIXME: what about leaning?
02648         }
02649         else
02650         {//stop ducking!
02651                 TIMER_Set( NPC, "duck", -1 );
02652         }
02653 
02654         if ( !TIMER_Done( NPC, "flee" ) )
02655         {//running away
02656                 faceEnemy = qfalse;
02657         }
02658 
02659         //FIXME: check scf_face_move_dir here?
02660 
02661         if ( !faceEnemy )
02662         {//we want to face in the dir we're running
02663                 if ( !move )
02664                 {//if we haven't moved, we should look in the direction we last looked?
02665                         VectorCopy( NPC->client->ps.viewangles, NPCInfo->lastPathAngles );
02666                 }
02667                 NPCInfo->desiredYaw = NPCInfo->lastPathAngles[YAW];
02668                 NPCInfo->desiredPitch = 0;
02669                 NPC_UpdateAngles( qtrue, qtrue );
02670                 if ( move )
02671                 {//don't run away and shoot
02672                         shoot = qfalse;
02673                 }
02674         }
02675 
02676         if ( NPCInfo->scriptFlags & SCF_DONT_FIRE )
02677         {
02678                 shoot = qfalse;
02679         }
02680 
02681         if ( NPC->enemy && NPC->enemy->enemy )
02682         {
02683                 if ( NPC->enemy->s.weapon == WP_SABER && NPC->enemy->enemy->s.weapon == WP_SABER )
02684                 {//don't shoot at an enemy jedi who is fighting another jedi, for fear of injuring one or causing rogue blaster deflections (a la Obi Wan/Vader duel at end of ANH)
02685                         shoot = qfalse;
02686                 }
02687         }
02688         //FIXME: don't shoot right away!
02689         if ( NPC->client->ps.weaponTime > 0 )
02690         {
02691                 if ( NPC->s.weapon == WP_ROCKET_LAUNCHER )
02692                 {
02693                         if ( !enemyLOS || !enemyCS )
02694                         {//cancel it
02695                                 NPC->client->ps.weaponTime = 0;
02696                         }
02697                         else
02698                         {//delay our next attempt
02699                                 TIMER_Set( NPC, "attackDelay", Q_irand( 3000, 5000 ) );
02700                         }
02701                 }
02702         }
02703         else if ( shoot )
02704         {//try to shoot if it's time
02705                 if ( TIMER_Done( NPC, "attackDelay" ) )
02706                 {
02707                         if( !(NPCInfo->scriptFlags & SCF_FIRE_WEAPON) ) // we've already fired, no need to do it again here
02708                         {
02709                                 WeaponThink( qtrue );
02710                         }
02711                         //NASTY
02712                         if ( NPC->s.weapon == WP_ROCKET_LAUNCHER 
02713                                 && (ucmd.buttons&BUTTON_ATTACK) 
02714                                 && !move
02715                                 && g_spskill.integer > 1 
02716                                 && !Q_irand( 0, 3 ) )
02717                         {//every now and then, shoot a homing rocket
02718                                 ucmd.buttons &= ~BUTTON_ATTACK;
02719                                 ucmd.buttons |= BUTTON_ALT_ATTACK;
02720                                 NPC->client->ps.weaponTime = Q_irand( 1000, 2500 );
02721                         }
02722                 }
02723         }
02724 }
02725 
02726 void NPC_BSST_Default( void )
02727 {
02728         if( NPCInfo->scriptFlags & SCF_FIRE_WEAPON )
02729         {
02730                 WeaponThink( qtrue );
02731         }
02732         
02733         if( !NPC->enemy )
02734         {//don't have an enemy, look for one
02735                 NPC_BSST_Patrol();
02736         }
02737         else //if ( NPC->enemy )
02738         {//have an enemy
02739                 NPC_CheckGetNewWeapon();
02740                 NPC_BSST_Attack();
02741         }
02742 }