codemp/game/NPC_AI_Grenadier.c

Go to the documentation of this file.
00001 #include "b_local.h"
00002 #include "g_nav.h"
00003 #include "anims.h"
00004 //#include "g_navigator.h"
00005 
00006 #include "../namespace_begin.h"
00007 extern qboolean BG_SabersOff( playerState_t *ps );
00008 #include "../namespace_end.h"
00009 
00010 extern void CG_DrawAlert( vec3_t origin, float rating );
00011 extern void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime );
00012 extern void G_SoundOnEnt( gentity_t *ent, soundChannel_t channel, const char *soundPath );
00013 extern void NPC_TempLookTarget( gentity_t *self, int lookEntNum, int minLookTime, int maxLookTime );
00014 extern qboolean G_ExpandPointToBBox( vec3_t point, const vec3_t mins, const vec3_t maxs, int ignore, int clipmask );
00015 extern void NPC_AimAdjust( int change );
00016 extern qboolean FlyingCreature( gentity_t *ent );
00017 
00018 #define MAX_VIEW_DIST           1024
00019 #define MAX_VIEW_SPEED          250
00020 #define MAX_LIGHT_INTENSITY 255
00021 #define MIN_LIGHT_THRESHOLD     0.1
00022 
00023 #define DISTANCE_SCALE          0.25f
00024 #define DISTANCE_THRESHOLD      0.075f
00025 #define SPEED_SCALE                     0.25f
00026 #define FOV_SCALE                       0.5f
00027 #define LIGHT_SCALE                     0.25f
00028 
00029 #define REALIZE_THRESHOLD       0.6f
00030 #define CAUTIOUS_THRESHOLD      ( REALIZE_THRESHOLD * 0.75 )
00031 
00032 qboolean NPC_CheckPlayerTeamStealth( void );
00033 
00034 static qboolean enemyLOS3;
00035 static qboolean enemyCS3;
00036 static qboolean faceEnemy3;
00037 static qboolean move3;
00038 static qboolean shoot3;
00039 static float    enemyDist3;
00040 
00041 //Local state enums
00042 enum
00043 {
00044         LSTATE_NONE = 0,
00045         LSTATE_UNDERFIRE,
00046         LSTATE_INVESTIGATE,
00047 };
00048 
00049 void Grenadier_ClearTimers( gentity_t *ent )
00050 {
00051         TIMER_Set( ent, "chatter", 0 );
00052         TIMER_Set( ent, "duck", 0 );
00053         TIMER_Set( ent, "stand", 0 );
00054         TIMER_Set( ent, "shuffleTime", 0 );
00055         TIMER_Set( ent, "sleepTime", 0 );
00056         TIMER_Set( ent, "enemyLastVisible", 0 );
00057         TIMER_Set( ent, "roamTime", 0 );
00058         TIMER_Set( ent, "hideTime", 0 );
00059         TIMER_Set( ent, "attackDelay", 0 );     //FIXME: Slant for difficulty levels
00060         TIMER_Set( ent, "stick", 0 );
00061         TIMER_Set( ent, "scoutTime", 0 );
00062         TIMER_Set( ent, "flee", 0 );
00063 }
00064 
00065 void NPC_Grenadier_PlayConfusionSound( gentity_t *self )
00066 {//FIXME: make this a custom sound in sound set
00067         if ( self->health > 0 )
00068         {
00069                 G_AddVoiceEvent( self, Q_irand(EV_CONFUSE1, EV_CONFUSE3), 2000 );
00070         }
00071         //reset him to be totally unaware again
00072         TIMER_Set( self, "enemyLastVisible", 0 );
00073         TIMER_Set( self, "flee", 0 );
00074         self->NPC->squadState = SQUAD_IDLE;
00075         self->NPC->tempBehavior = BS_DEFAULT;
00076         
00077         //self->NPC->behaviorState = BS_PATROL;
00078         G_ClearEnemy( self );//FIXME: or just self->enemy = NULL;?
00079 
00080         self->NPC->investigateCount = 0;
00081 }
00082 
00083 
00084 /*
00085 -------------------------
00086 NPC_ST_Pain
00087 -------------------------
00088 */
00089 
00090 void NPC_Grenadier_Pain(gentity_t *self, gentity_t *attacker, int damage) 
00091 {
00092         self->NPC->localState = LSTATE_UNDERFIRE;
00093 
00094         TIMER_Set( self, "duck", -1 );
00095         TIMER_Set( self, "stand", 2000 );
00096 
00097         NPC_Pain( self, attacker, damage );
00098 
00099         if ( !damage && self->health > 0 )
00100         {//FIXME: better way to know I was pushed
00101                 G_AddVoiceEvent( self, Q_irand(EV_PUSHED1, EV_PUSHED3), 2000 );
00102         }
00103 }
00104 
00105 /*
00106 -------------------------
00107 ST_HoldPosition
00108 -------------------------
00109 */
00110 
00111 static void Grenadier_HoldPosition( void )
00112 {
00113         NPC_FreeCombatPoint( NPCInfo->combatPoint, qtrue );
00114         NPCInfo->goalEntity = NULL;
00115         
00116         /*if ( TIMER_Done( NPC, "stand" ) )
00117         {//FIXME: what if can't shoot from this pos?
00118                 TIMER_Set( NPC, "duck", Q_irand( 2000, 4000 ) );
00119         }
00120         */
00121 }
00122 
00123 /*
00124 -------------------------
00125 ST_Move
00126 -------------------------
00127 */
00128 
00129 static qboolean Grenadier_Move( void )
00130 {
00131         qboolean        moved;
00132         navInfo_t       info;
00133 
00134         NPCInfo->combatMove = qtrue;//always move straight toward our goal
00135         moved = NPC_MoveToGoal( qtrue );
00136         
00137         //Get the move info
00138         NAV_GetLastMove( &info );
00139 
00140         //FIXME: if we bump into another one of our guys and can't get around him, just stop!
00141         //If we hit our target, then stop and fire!
00142         if ( info.flags & NIF_COLLISION ) 
00143         {
00144                 if ( info.blocker == NPC->enemy )
00145                 {
00146                         Grenadier_HoldPosition();
00147                 }
00148         }
00149 
00150         //If our move failed, then reset
00151         if ( moved == qfalse )
00152         {//couldn't get to enemy
00153                 if ( (NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) && NPC->client->ps.weapon == WP_THERMAL && NPCInfo->goalEntity && NPCInfo->goalEntity == NPC->enemy )
00154                 {//we were running after enemy
00155                         //Try to find a combat point that can hit the enemy
00156                         int cpFlags = (CP_CLEAR|CP_HAS_ROUTE);
00157                         int cp;
00158 
00159                         if ( NPCInfo->scriptFlags&SCF_USE_CP_NEAREST )
00160                         {
00161                                 cpFlags &= ~(CP_FLANK|CP_APPROACH_ENEMY|CP_CLOSEST);
00162                                 cpFlags |= CP_NEAREST;
00163                         }
00164                         cp = NPC_FindCombatPoint( NPC->r.currentOrigin, NPC->r.currentOrigin, NPC->r.currentOrigin, cpFlags, 32, -1 );
00165                         if ( cp == -1 && !(NPCInfo->scriptFlags&SCF_USE_CP_NEAREST) )
00166                         {//okay, try one by the enemy
00167                                 cp = NPC_FindCombatPoint( NPC->r.currentOrigin, NPC->r.currentOrigin, NPC->enemy->r.currentOrigin, CP_CLEAR|CP_HAS_ROUTE|CP_HORZ_DIST_COLL, 32, -1 );
00168                         }
00169                         //NOTE: there may be a perfectly valid one, just not one within CP_COLLECT_RADIUS of either me or him...
00170                         if ( cp != -1 )
00171                         {//found a combat point that has a clear shot to enemy
00172                                 NPC_SetCombatPoint( cp );
00173                                 NPC_SetMoveGoal( NPC, level.combatPoints[cp].origin, 8, qtrue, cp, NULL );
00174                                 return moved;
00175                         }
00176                 }
00177                 //just hang here
00178                 Grenadier_HoldPosition();
00179         }
00180 
00181         return moved;
00182 }
00183 
00184 /*
00185 -------------------------
00186 NPC_BSGrenadier_Patrol
00187 -------------------------
00188 */
00189 
00190 void NPC_BSGrenadier_Patrol( void )
00191 {//FIXME: pick up on bodies of dead buddies?
00192         if ( NPCInfo->confusionTime < level.time )
00193         {
00194                 //Look for any enemies
00195                 if ( NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES )
00196                 {
00197                         if ( NPC_CheckPlayerTeamStealth() )
00198                         {
00199                                 //NPCInfo->behaviorState = BS_HUNT_AND_KILL;//should be automatic now
00200                                 //NPC_AngerSound();
00201                                 NPC_UpdateAngles( qtrue, qtrue );
00202                                 return;
00203                         }
00204                 }
00205 
00206                 if ( !(NPCInfo->scriptFlags&SCF_IGNORE_ALERTS) )
00207                 {
00208                         //Is there danger nearby
00209                         int alertEvent = NPC_CheckAlertEvents( qtrue, qtrue, -1, qfalse, AEL_SUSPICIOUS );
00210                         if ( NPC_CheckForDanger( alertEvent ) )
00211                         {
00212                                 NPC_UpdateAngles( qtrue, qtrue );
00213                                 return;
00214                         }
00215                         else
00216                         {//check for other alert events
00217                                 //There is an event to look at
00218                                 if ( alertEvent >= 0 && level.alertEvents[alertEvent].ID != NPCInfo->lastAlertID )
00219                                 {
00220                                         NPCInfo->lastAlertID = level.alertEvents[alertEvent].ID;
00221                                         if ( level.alertEvents[alertEvent].level == AEL_DISCOVERED )
00222                                         {
00223                                                 if ( level.alertEvents[alertEvent].owner && 
00224                                                         level.alertEvents[alertEvent].owner->client && 
00225                                                         level.alertEvents[alertEvent].owner->health >= 0 &&
00226                                                         level.alertEvents[alertEvent].owner->client->playerTeam == NPC->client->enemyTeam )
00227                                                 {//an enemy
00228                                                         G_SetEnemy( NPC, level.alertEvents[alertEvent].owner );
00229                                                         //NPCInfo->enemyLastSeenTime = level.time;
00230                                                         TIMER_Set( NPC, "attackDelay", Q_irand( 500, 2500 ) );
00231                                                 }
00232                                         }
00233                                         else
00234                                         {//FIXME: get more suspicious over time?
00235                                                 //Save the position for movement (if necessary)
00236                                                 VectorCopy( level.alertEvents[alertEvent].position, NPCInfo->investigateGoal );
00237                                                 NPCInfo->investigateDebounceTime = level.time + Q_irand( 500, 1000 );
00238                                                 if ( level.alertEvents[alertEvent].level == AEL_SUSPICIOUS )
00239                                                 {//suspicious looks longer
00240                                                         NPCInfo->investigateDebounceTime += Q_irand( 500, 2500 );
00241                                                 }
00242                                         }
00243                                 }
00244                         }
00245 
00246                         if ( NPCInfo->investigateDebounceTime > level.time )
00247                         {//FIXME: walk over to it, maybe?  Not if not chase enemies
00248                                 //NOTE: stops walking or doing anything else below
00249                                 vec3_t  dir, angles;
00250                                 float   o_yaw, o_pitch;
00251                                 
00252                                 VectorSubtract( NPCInfo->investigateGoal, NPC->client->renderInfo.eyePoint, dir );
00253                                 vectoangles( dir, angles );
00254                                 
00255                                 o_yaw = NPCInfo->desiredYaw;
00256                                 o_pitch = NPCInfo->desiredPitch;
00257                                 NPCInfo->desiredYaw = angles[YAW];
00258                                 NPCInfo->desiredPitch = angles[PITCH];
00259                                 
00260                                 NPC_UpdateAngles( qtrue, qtrue );
00261 
00262                                 NPCInfo->desiredYaw = o_yaw;
00263                                 NPCInfo->desiredPitch = o_pitch;
00264                                 return;
00265                         }
00266                 }
00267         }
00268 
00269         //If we have somewhere to go, then do that
00270         if ( UpdateGoal() )
00271         {
00272                 ucmd.buttons |= BUTTON_WALKING;
00273                 NPC_MoveToGoal( qtrue );
00274         }
00275 
00276         NPC_UpdateAngles( qtrue, qtrue );
00277 }
00278 
00279 /*
00280 -------------------------
00281 NPC_BSGrenadier_Idle
00282 -------------------------
00283 */
00284 /*
00285 void NPC_BSGrenadier_Idle( void )
00286 {
00287         //FIXME: check for other alert events?
00288 
00289         //Is there danger nearby?
00290         if ( NPC_CheckForDanger( NPC_CheckAlertEvents( qtrue, qtrue, -1, qfalse, AEL_DANGER ) ) )
00291         {
00292                 NPC_UpdateAngles( qtrue, qtrue );
00293                 return;
00294         }
00295 
00296         TIMER_Set( NPC, "roamTime", 2000 + Q_irand( 1000, 2000 ) );
00297 
00298         NPC_UpdateAngles( qtrue, qtrue );
00299 }
00300 */
00301 /*
00302 -------------------------
00303 ST_CheckMoveState
00304 -------------------------
00305 */
00306 
00307 static void Grenadier_CheckMoveState( void )
00308 {
00309         //See if we're a scout
00310         if ( !(NPCInfo->scriptFlags & SCF_CHASE_ENEMIES) )//behaviorState == BS_STAND_AND_SHOOT )
00311         {
00312                 if ( NPCInfo->goalEntity == NPC->enemy )
00313                 {
00314                         move3 = qfalse;
00315                         return;
00316                 }
00317         }
00318         //See if we're running away
00319         else if ( NPCInfo->squadState == SQUAD_RETREAT )
00320         {
00321                 if ( TIMER_Done( NPC, "flee" ) )
00322                 {
00323                         NPCInfo->squadState = SQUAD_IDLE;
00324                 }
00325                 else
00326                 {
00327                         faceEnemy3 = qfalse;
00328                 }
00329         }
00330         /*
00331         else if ( NPCInfo->squadState == SQUAD_IDLE )
00332         {
00333                 if ( !NPCInfo->goalEntity )
00334                 {
00335                         move3 = qfalse;
00336                         return;
00337                 }
00338                 //Should keep moving toward player when we're out of range... right?
00339         }
00340         */
00341 
00342         //See if we're moving towards a goal, not the enemy
00343         if ( ( NPCInfo->goalEntity != NPC->enemy ) && ( NPCInfo->goalEntity != NULL ) )
00344         {
00345                 //Did we make it?
00346                 if ( NAV_HitNavGoal( NPC->r.currentOrigin, NPC->r.mins, NPC->r.maxs, NPCInfo->goalEntity->r.currentOrigin, 16, FlyingCreature( NPC ) ) || 
00347                         ( NPCInfo->squadState == SQUAD_SCOUT && enemyLOS3 && enemyDist3 <= 10000 ) )
00348                 {
00349                         int     newSquadState = SQUAD_STAND_AND_SHOOT;
00350                         //we got where we wanted to go, set timers based on why we were running
00351                         switch ( NPCInfo->squadState )
00352                         {
00353                         case SQUAD_RETREAT://was running away
00354                                 TIMER_Set( NPC, "duck", (NPC->client->pers.maxHealth - NPC->health) * 100 );
00355                                 TIMER_Set( NPC, "hideTime", Q_irand( 3000, 7000 ) );
00356                                 newSquadState = SQUAD_COVER;
00357                                 break;
00358                         case SQUAD_TRANSITION://was heading for a combat point
00359                                 TIMER_Set( NPC, "hideTime", Q_irand( 2000, 4000 ) );
00360                                 break;
00361                         case SQUAD_SCOUT://was running after player
00362                                 break;
00363                         default:
00364                                 break;
00365                         }
00366                         NPC_ReachedGoal();
00367                         //don't attack right away
00368                         TIMER_Set( NPC, "attackDelay", Q_irand( 250, 500 ) );   //FIXME: Slant for difficulty levels
00369                         //don't do something else just yet
00370                         TIMER_Set( NPC, "roamTime", Q_irand( 1000, 4000 ) );
00371                         //stop fleeing
00372                         if ( NPCInfo->squadState == SQUAD_RETREAT )
00373                         {
00374                                 TIMER_Set( NPC, "flee", -level.time );
00375                                 NPCInfo->squadState = SQUAD_IDLE;
00376                         }
00377                         return;
00378                 }
00379 
00380                 //keep going, hold of roamTimer until we get there
00381                 TIMER_Set( NPC, "roamTime", Q_irand( 4000, 8000 ) );
00382         }
00383 
00384         if ( !NPCInfo->goalEntity )
00385         {
00386                 if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES )
00387                 {
00388                         NPCInfo->goalEntity = NPC->enemy;
00389                 }
00390         }
00391 }
00392 
00393 /*
00394 -------------------------
00395 ST_CheckFireState
00396 -------------------------
00397 */
00398 
00399 static void Grenadier_CheckFireState( void )
00400 {
00401         if ( enemyCS3 )
00402         {//if have a clear shot, always try
00403                 return;
00404         }
00405 
00406         if ( NPCInfo->squadState == SQUAD_RETREAT || NPCInfo->squadState == SQUAD_TRANSITION || NPCInfo->squadState == SQUAD_SCOUT )
00407         {//runners never try to fire at the last pos
00408                 return;
00409         }
00410 
00411         if ( !VectorCompare( NPC->client->ps.velocity, vec3_origin ) )
00412         {//if moving at all, don't do this
00413                 return;
00414         }
00415 
00416         //continue to fire on their last position
00417         /*
00418         if ( !Q_irand( 0, 1 ) && NPCInfo->enemyLastSeenTime && level.time - NPCInfo->enemyLastSeenTime < 4000 )
00419         {
00420                 //Fire on the last known position
00421                 vec3_t  muzzle, dir, angles;
00422 
00423                 CalcEntitySpot( NPC, SPOT_WEAPON, muzzle );
00424                 VectorSubtract( NPCInfo->enemyLastSeenLocation, muzzle, dir );
00425 
00426                 VectorNormalize( dir );
00427 
00428                 vectoangles( dir, angles );
00429 
00430                 NPCInfo->desiredYaw             = angles[YAW];
00431                 NPCInfo->desiredPitch   = angles[PITCH];
00432                 //FIXME: they always throw toward enemy, so this will be very odd...
00433                 shoot3 = qtrue;
00434                 faceEnemy3 = qfalse;
00435 
00436                 return;
00437         }
00438         */
00439 }
00440 
00441 qboolean Grenadier_EvaluateShot( int hit )
00442 {
00443         if ( !NPC->enemy )
00444         {
00445                 return qfalse;
00446         }
00447 
00448         if ( hit == NPC->enemy->s.number || (&g_entities[hit] != NULL && (g_entities[hit].r.svFlags&SVF_GLASS_BRUSH)) )
00449         {//can hit enemy or will hit glass, so shoot anyway
00450                 return qtrue;
00451         }
00452         return qfalse;
00453 }
00454 
00455 /*
00456 -------------------------
00457 NPC_BSGrenadier_Attack
00458 -------------------------
00459 */
00460 
00461 void NPC_BSGrenadier_Attack( void )
00462 {
00463         //Don't do anything if we're hurt
00464         if ( NPC->painDebounceTime > level.time )
00465         {
00466                 NPC_UpdateAngles( qtrue, qtrue );
00467                 return;
00468         }
00469 
00470         //NPC_CheckEnemy( qtrue, qfalse );
00471         //If we don't have an enemy, just idle
00472         if ( NPC_CheckEnemyExt(qfalse) == qfalse )
00473         {
00474                 NPC->enemy = NULL;
00475                 NPC_BSGrenadier_Patrol();//FIXME: or patrol?
00476                 return;
00477         }
00478 
00479         if ( TIMER_Done( NPC, "flee" ) && NPC_CheckForDanger( NPC_CheckAlertEvents( qtrue, qtrue, -1, qfalse, AEL_DANGER ) ) )
00480         {//going to run
00481                 NPC_UpdateAngles( qtrue, qtrue );
00482                 return;
00483         }
00484 
00485         if ( !NPC->enemy )
00486         {//WTF?  somehow we lost our enemy?
00487                 NPC_BSGrenadier_Patrol();//FIXME: or patrol?
00488                 return;
00489         }
00490 
00491         enemyLOS3 = enemyCS3 = qfalse;
00492         move3 = qtrue;
00493         faceEnemy3 = qfalse;
00494         shoot3 = qfalse;
00495         enemyDist3 = DistanceSquared( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin );
00496 
00497         //See if we should switch to melee attack
00498         if ( enemyDist3 < 16384 //128
00499                 && (!NPC->enemy->client
00500                         || NPC->enemy->client->ps.weapon != WP_SABER
00501                         || BG_SabersOff( &NPC->enemy->client->ps ) 
00502                         ) 
00503                 )
00504         {//enemy is close and not using saber
00505                 if ( NPC->client->ps.weapon == WP_THERMAL )
00506                 {//grenadier
00507                         trace_t trace;
00508                         trap_Trace ( &trace, NPC->r.currentOrigin, NPC->enemy->r.mins, NPC->enemy->r.maxs, NPC->enemy->r.currentOrigin, NPC->s.number, NPC->enemy->clipmask );
00509                         if ( !trace.allsolid && !trace.startsolid && (trace.fraction == 1.0 || trace.entityNum == NPC->enemy->s.number ) )
00510                         {//I can get right to him
00511                                 //reset fire-timing variables
00512                                 NPC_ChangeWeapon( WP_STUN_BATON );
00513                                 if ( !(NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) )//NPCInfo->behaviorState == BS_STAND_AND_SHOOT )
00514                                 {//FIXME: should we be overriding scriptFlags?
00515                                         NPCInfo->scriptFlags |= SCF_CHASE_ENEMIES;//NPCInfo->behaviorState = BS_HUNT_AND_KILL;
00516                                 }
00517                         }
00518                 }
00519         }
00520         else if ( enemyDist3 > 65536 || (NPC->enemy->client && NPC->enemy->client->ps.weapon == WP_SABER && !NPC->enemy->client->ps.saberHolstered) )//256
00521         {//enemy is far or using saber
00522                 if ( NPC->client->ps.weapon == WP_STUN_BATON && (NPC->client->ps.stats[STAT_WEAPONS]&(1<<WP_THERMAL)) )
00523                 {//fisticuffs, make switch to thermal if have it
00524                         //reset fire-timing variables
00525                         NPC_ChangeWeapon( WP_THERMAL );
00526                 }
00527         }
00528 
00529         //can we see our target?
00530         if ( NPC_ClearLOS4( NPC->enemy ) )
00531         {
00532                 NPCInfo->enemyLastSeenTime = level.time;
00533                 enemyLOS3 = qtrue;
00534 
00535                 if ( NPC->client->ps.weapon == WP_STUN_BATON )
00536                 {
00537                         if ( enemyDist3 <= 4096 && InFOV3( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin, NPC->client->ps.viewangles, 90, 45 ) )//within 64 & infront
00538                         {
00539                                 VectorCopy( NPC->enemy->r.currentOrigin, NPCInfo->enemyLastSeenLocation );
00540                                 enemyCS3 = qtrue;
00541                         }
00542                 }
00543                 else if ( InFOV3( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin, NPC->client->ps.viewangles, 45, 90 ) )
00544                 {//in front of me 
00545                         //can we shoot our target?
00546                         //FIXME: how accurate/necessary is this check?
00547                         int hit = NPC_ShotEntity( NPC->enemy, NULL );
00548                         gentity_t *hitEnt = &g_entities[hit];
00549                         if ( hit == NPC->enemy->s.number 
00550                                 || ( hitEnt && hitEnt->client && hitEnt->client->playerTeam == NPC->client->enemyTeam ) )
00551                         {
00552                                 float enemyHorzDist;
00553 
00554                                 VectorCopy( NPC->enemy->r.currentOrigin, NPCInfo->enemyLastSeenLocation );
00555                                 enemyHorzDist = DistanceHorizontalSquared( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin );
00556                                 if ( enemyHorzDist < 1048576 )
00557                                 {//within 1024
00558                                         enemyCS3 = qtrue;
00559                                         NPC_AimAdjust( 2 );//adjust aim better longer we have clear shot at enemy
00560                                 }
00561                                 else
00562                                 {
00563                                         NPC_AimAdjust( 1 );//adjust aim better longer we can see enemy
00564                                 }
00565                         }
00566                 }
00567         }
00568         else
00569         {
00570                 NPC_AimAdjust( -1 );//adjust aim worse longer we cannot see enemy
00571         }
00572         /*
00573         else if ( trap_InPVS( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin ) )
00574         {
00575                 NPCInfo->enemyLastSeenTime = level.time;
00576                 faceEnemy3 = qtrue;
00577         }
00578         */
00579 
00580         if ( enemyLOS3 )
00581         {//FIXME: no need to face enemy if we're moving to some other goal and he's too far away to shoot?
00582                 faceEnemy3 = qtrue;
00583         }
00584 
00585         if ( enemyCS3 )
00586         {
00587                 shoot3 = qtrue;
00588                 if ( NPC->client->ps.weapon == WP_THERMAL )
00589                 {//don't chase and throw
00590                         move3 = qfalse;
00591                 }
00592                 else if ( NPC->client->ps.weapon == WP_STUN_BATON && enemyDist3 < (NPC->r.maxs[0]+NPC->enemy->r.maxs[0]+16)*(NPC->r.maxs[0]+NPC->enemy->r.maxs[0]+16) )
00593                 {//close enough
00594                         move3 = qfalse;
00595                 }
00596         }//this should make him chase enemy when out of range...?
00597 
00598         //Check for movement to take care of
00599         Grenadier_CheckMoveState();
00600 
00601         //See if we should override shooting decision with any special considerations
00602         Grenadier_CheckFireState();
00603 
00604         if ( move3 )
00605         {//move toward goal
00606                 if ( NPCInfo->goalEntity )//&& ( NPCInfo->goalEntity != NPC->enemy || enemyDist3 > 10000 ) )//100 squared
00607                 {
00608                         move3 = Grenadier_Move();
00609                 }
00610                 else
00611                 {
00612                         move3 = qfalse;
00613                 }
00614         }
00615 
00616         if ( !move3 )
00617         {
00618                 if ( !TIMER_Done( NPC, "duck" ) )
00619                 {
00620                         ucmd.upmove = -127;
00621                 }
00622                 //FIXME: what about leaning?
00623         }
00624         else
00625         {//stop ducking!
00626                 TIMER_Set( NPC, "duck", -1 );
00627         }
00628 
00629         if ( !faceEnemy3 )
00630         {//we want to face in the dir we're running
00631                 if ( move3 )
00632                 {//don't run away and shoot
00633                         NPCInfo->desiredYaw = NPCInfo->lastPathAngles[YAW];
00634                         NPCInfo->desiredPitch = 0;
00635                         shoot3 = qfalse;
00636                 }
00637                 NPC_UpdateAngles( qtrue, qtrue );
00638         }
00639         else// if ( faceEnemy3 )
00640         {//face the enemy
00641                 NPC_FaceEnemy(qtrue);
00642         }
00643 
00644         if ( NPCInfo->scriptFlags&SCF_DONT_FIRE )
00645         {
00646                 shoot3 = qfalse;
00647         }
00648 
00649         //FIXME: don't shoot right away!
00650         if ( shoot3 )
00651         {//try to shoot if it's time
00652                 if ( TIMER_Done( NPC, "attackDelay" ) )
00653                 {       
00654                         if( !(NPCInfo->scriptFlags & SCF_FIRE_WEAPON) ) // we've already fired, no need to do it again here
00655                         {
00656                                 WeaponThink( qtrue );
00657                                 TIMER_Set( NPC, "attackDelay", NPCInfo->shotTime-level.time );
00658                         }
00659                         
00660                 }
00661         }
00662 }
00663 
00664 void NPC_BSGrenadier_Default( void )
00665 {
00666         if( NPCInfo->scriptFlags & SCF_FIRE_WEAPON )
00667         {
00668                 WeaponThink( qtrue );
00669         }
00670 
00671         if( !NPC->enemy )
00672         {//don't have an enemy, look for one
00673                 NPC_BSGrenadier_Patrol();
00674         }
00675         else//if ( NPC->enemy )
00676         {//have an enemy
00677                 NPC_BSGrenadier_Attack();
00678         }
00679 }