codemp/game/NPC_AI_Sniper.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 G_SoundOnEnt( gentity_t *ent, soundChannel_t channel, const char *soundPath );
00007 extern void NPC_TempLookTarget( gentity_t *self, int lookEntNum, int minLookTime, int maxLookTime );
00008 extern qboolean G_ExpandPointToBBox( vec3_t point, const vec3_t mins, const vec3_t maxs, int ignore, int clipmask );
00009 extern qboolean FlyingCreature( gentity_t *ent );
00010 
00011 #define SPF_NO_HIDE                     2
00012 
00013 #define MAX_VIEW_DIST           1024
00014 #define MAX_VIEW_SPEED          250
00015 #define MAX_LIGHT_INTENSITY 255
00016 #define MIN_LIGHT_THRESHOLD     0.1
00017 
00018 #define DISTANCE_SCALE          0.25f
00019 #define DISTANCE_THRESHOLD      0.075f
00020 #define SPEED_SCALE                     0.25f
00021 #define FOV_SCALE                       0.5f
00022 #define LIGHT_SCALE                     0.25f
00023 
00024 #define REALIZE_THRESHOLD       0.6f
00025 #define CAUTIOUS_THRESHOLD      ( REALIZE_THRESHOLD * 0.75 )
00026 
00027 qboolean NPC_CheckPlayerTeamStealth( void );
00028 
00029 static qboolean enemyLOS2;
00030 static qboolean enemyCS2;
00031 static qboolean faceEnemy2;
00032 static qboolean move2;
00033 static qboolean shoot2;
00034 static float    enemyDist2;
00035 
00036 //Local state enums
00037 enum
00038 {
00039         LSTATE_NONE = 0,
00040         LSTATE_UNDERFIRE,
00041         LSTATE_INVESTIGATE,
00042 };
00043 
00044 void Sniper_ClearTimers( gentity_t *ent )
00045 {
00046         TIMER_Set( ent, "chatter", 0 );
00047         TIMER_Set( ent, "duck", 0 );
00048         TIMER_Set( ent, "stand", 0 );
00049         TIMER_Set( ent, "shuffleTime", 0 );
00050         TIMER_Set( ent, "sleepTime", 0 );
00051         TIMER_Set( ent, "enemyLastVisible", 0 );
00052         TIMER_Set( ent, "roamTime", 0 );
00053         TIMER_Set( ent, "hideTime", 0 );
00054         TIMER_Set( ent, "attackDelay", 0 );     //FIXME: Slant for difficulty levels
00055         TIMER_Set( ent, "stick", 0 );
00056         TIMER_Set( ent, "scoutTime", 0 );
00057         TIMER_Set( ent, "flee", 0 );
00058 }
00059 
00060 void NPC_Sniper_PlayConfusionSound( gentity_t *self )
00061 {//FIXME: make this a custom sound in sound set
00062         if ( self->health > 0 )
00063         {
00064                 G_AddVoiceEvent( self, Q_irand(EV_CONFUSE1, EV_CONFUSE3), 2000 );
00065         }
00066         //reset him to be totally unaware again
00067         TIMER_Set( self, "enemyLastVisible", 0 );
00068         TIMER_Set( self, "flee", 0 );
00069         self->NPC->squadState = SQUAD_IDLE;
00070         self->NPC->tempBehavior = BS_DEFAULT;
00071 
00072         //self->NPC->behaviorState = BS_PATROL;
00073         G_ClearEnemy( self );//FIXME: or just self->enemy = NULL;?
00074 
00075         self->NPC->investigateCount = 0;
00076 }
00077 
00078 
00079 /*
00080 -------------------------
00081 NPC_ST_Pain
00082 -------------------------
00083 */
00084 
00085 void NPC_Sniper_Pain(gentity_t *self, gentity_t *attacker, int damage)
00086 {
00087         self->NPC->localState = LSTATE_UNDERFIRE;
00088 
00089         TIMER_Set( self, "duck", -1 );
00090         TIMER_Set( self, "stand", 2000 );
00091 
00092         NPC_Pain( self, attacker, damage );
00093 
00094         if ( !damage && self->health > 0 )
00095         {//FIXME: better way to know I was pushed
00096                 G_AddVoiceEvent( self, Q_irand(EV_PUSHED1, EV_PUSHED3), 2000 );
00097         }
00098 }
00099 
00100 /*
00101 -------------------------
00102 ST_HoldPosition
00103 -------------------------
00104 */
00105 
00106 static void Sniper_HoldPosition( void )
00107 {
00108         NPC_FreeCombatPoint( NPCInfo->combatPoint, qtrue );
00109         NPCInfo->goalEntity = NULL;
00110         
00111         /*if ( TIMER_Done( NPC, "stand" ) )
00112         {//FIXME: what if can't shoot from this pos?
00113                 TIMER_Set( NPC, "duck", Q_irand( 2000, 4000 ) );
00114         }
00115         */
00116 }
00117 
00118 /*
00119 -------------------------
00120 ST_Move
00121 -------------------------
00122 */
00123 
00124 static qboolean Sniper_Move( void )
00125 {
00126         qboolean        moved;
00127         navInfo_t       info;
00128 
00129         NPCInfo->combatMove = qtrue;//always move straight toward our goal
00130 
00131         moved = NPC_MoveToGoal( qtrue );
00132         
00133         //Get the move info
00134         NAV_GetLastMove( &info );
00135 
00136         //FIXME: if we bump into another one of our guys and can't get around him, just stop!
00137         //If we hit our target, then stop and fire!
00138         if ( info.flags & NIF_COLLISION ) 
00139         {
00140                 if ( info.blocker == NPC->enemy )
00141                 {
00142                         Sniper_HoldPosition();
00143                 }
00144         }
00145 
00146         //If our move failed, then reset
00147         if ( moved == qfalse )
00148         {//couldn't get to enemy
00149                 if ( (NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) && NPCInfo->goalEntity && NPCInfo->goalEntity == NPC->enemy )
00150                 {//we were running after enemy
00151                         //Try to find a combat point that can hit the enemy
00152                         int cpFlags = (CP_CLEAR|CP_HAS_ROUTE);
00153                         int cp;
00154                         if ( NPCInfo->scriptFlags&SCF_USE_CP_NEAREST )
00155                         {
00156                                 cpFlags &= ~(CP_FLANK|CP_APPROACH_ENEMY|CP_CLOSEST);
00157                                 cpFlags |= CP_NEAREST;
00158                         }
00159                         cp = NPC_FindCombatPoint( NPC->r.currentOrigin, NPC->r.currentOrigin, NPC->r.currentOrigin, cpFlags, 32, -1 );
00160                         if ( cp == -1 && !(NPCInfo->scriptFlags&SCF_USE_CP_NEAREST) )
00161                         {//okay, try one by the enemy
00162                                 cp = NPC_FindCombatPoint( NPC->r.currentOrigin, NPC->r.currentOrigin, NPC->enemy->r.currentOrigin, CP_CLEAR|CP_HAS_ROUTE|CP_HORZ_DIST_COLL, 32, -1 );
00163                         }
00164                         //NOTE: there may be a perfectly valid one, just not one within CP_COLLECT_RADIUS of either me or him...
00165                         if ( cp != -1 )
00166                         {//found a combat point that has a clear shot to enemy
00167                                 NPC_SetCombatPoint( cp );
00168                                 NPC_SetMoveGoal( NPC, level.combatPoints[cp].origin, 8, qtrue, cp, NULL );
00169                                 return moved;
00170                         }
00171                 }
00172                 //just hang here
00173                 Sniper_HoldPosition();
00174         }
00175 
00176         return moved;
00177 }
00178 
00179 /*
00180 -------------------------
00181 NPC_BSSniper_Patrol
00182 -------------------------
00183 */
00184 
00185 void NPC_BSSniper_Patrol( void )
00186 {//FIXME: pick up on bodies of dead buddies?
00187         NPC->count = 0;
00188 
00189         if ( NPCInfo->confusionTime < level.time )
00190         {
00191                 //Look for any enemies
00192                 if ( NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES )
00193                 {
00194                         if ( NPC_CheckPlayerTeamStealth() )
00195                         {
00196                                 //NPCInfo->behaviorState = BS_HUNT_AND_KILL;//Should be auto now
00197                                 //NPC_AngerSound();
00198                                 NPC_UpdateAngles( qtrue, qtrue );
00199                                 return;
00200                         }
00201                 }
00202 
00203                 if ( !(NPCInfo->scriptFlags&SCF_IGNORE_ALERTS) )
00204                 {
00205                         //Is there danger nearby
00206                         int alertEvent = NPC_CheckAlertEvents( qtrue, qtrue, -1, qfalse, AEL_SUSPICIOUS );
00207                         if ( NPC_CheckForDanger( alertEvent ) )
00208                         {
00209                                 NPC_UpdateAngles( qtrue, qtrue );
00210                                 return;
00211                         }
00212                         else
00213                         {//check for other alert events
00214                                 //There is an event to look at
00215                                 if ( alertEvent >= 0 && level.alertEvents[alertEvent].ID != NPCInfo->lastAlertID )
00216                                 {
00217                                         NPCInfo->lastAlertID = level.alertEvents[alertEvent].ID;
00218                                         if ( level.alertEvents[alertEvent].level == AEL_DISCOVERED )
00219                                         {
00220                                                 if ( level.alertEvents[alertEvent].owner && 
00221                                                         level.alertEvents[alertEvent].owner->client && 
00222                                                         level.alertEvents[alertEvent].owner->health >= 0 &&
00223                                                         level.alertEvents[alertEvent].owner->client->playerTeam == NPC->client->enemyTeam )
00224                                                 {//an enemy
00225                                                         G_SetEnemy( NPC, level.alertEvents[alertEvent].owner );
00226                                                         //NPCInfo->enemyLastSeenTime = level.time;
00227                                                         TIMER_Set( NPC, "attackDelay", Q_irand( (6-NPCInfo->stats.aim)*100, (6-NPCInfo->stats.aim)*500 ) );
00228                                                 }
00229                                         }
00230                                         else
00231                                         {//FIXME: get more suspicious over time?
00232                                                 //Save the position for movement (if necessary)
00233                                                 //FIXME: sound?
00234                                                 VectorCopy( level.alertEvents[alertEvent].position, NPCInfo->investigateGoal );
00235                                                 NPCInfo->investigateDebounceTime = level.time + Q_irand( 500, 1000 );
00236                                                 if ( level.alertEvents[alertEvent].level == AEL_SUSPICIOUS )
00237                                                 {//suspicious looks longer
00238                                                         NPCInfo->investigateDebounceTime += Q_irand( 500, 2500 );
00239                                                 }
00240                                         }
00241                                 }
00242                         }
00243 
00244                         if ( NPCInfo->investigateDebounceTime > level.time )
00245                         {//FIXME: walk over to it, maybe?  Not if not chase enemies flag
00246                                 //NOTE: stops walking or doing anything else below
00247                                 vec3_t  dir, angles;
00248                                 float   o_yaw, o_pitch;
00249                                 
00250                                 VectorSubtract( NPCInfo->investigateGoal, NPC->client->renderInfo.eyePoint, dir );
00251                                 vectoangles( dir, angles );
00252                                 
00253                                 o_yaw = NPCInfo->desiredYaw;
00254                                 o_pitch = NPCInfo->desiredPitch;
00255                                 NPCInfo->desiredYaw = angles[YAW];
00256                                 NPCInfo->desiredPitch = angles[PITCH];
00257                                 
00258                                 NPC_UpdateAngles( qtrue, qtrue );
00259 
00260                                 NPCInfo->desiredYaw = o_yaw;
00261                                 NPCInfo->desiredPitch = o_pitch;
00262                                 return;
00263                         }
00264                 }
00265         }
00266 
00267         //If we have somewhere to go, then do that
00268         if ( UpdateGoal() )
00269         {
00270                 ucmd.buttons |= BUTTON_WALKING;
00271                 NPC_MoveToGoal( qtrue );
00272         }
00273 
00274         NPC_UpdateAngles( qtrue, qtrue );
00275 }
00276 
00277 /*
00278 -------------------------
00279 NPC_BSSniper_Idle
00280 -------------------------
00281 */
00282 /*
00283 void NPC_BSSniper_Idle( void )
00284 {
00285         //reset our shotcount
00286         NPC->count = 0;
00287 
00288         //FIXME: check for other alert events?
00289 
00290         //Is there danger nearby?
00291         if ( NPC_CheckForDanger( NPC_CheckAlertEvents( qtrue, qtrue ) ) )
00292         {
00293                 NPC_UpdateAngles( qtrue, qtrue );
00294                 return;
00295         }
00296 
00297         TIMER_Set( NPC, "roamTime", 2000 + Q_irand( 1000, 2000 ) );
00298 
00299         NPC_UpdateAngles( qtrue, qtrue );
00300 }
00301 */
00302 /*
00303 -------------------------
00304 ST_CheckMoveState
00305 -------------------------
00306 */
00307 
00308 static void Sniper_CheckMoveState( void )
00309 {
00310         //See if we're a scout
00311         if ( !(NPCInfo->scriptFlags & SCF_CHASE_ENEMIES) )//NPCInfo->behaviorState == BS_STAND_AND_SHOOT )
00312         {
00313                 if ( NPCInfo->goalEntity == NPC->enemy )
00314                 {
00315                         move2 = qfalse;
00316                         return;
00317                 }
00318         }
00319         //See if we're running away
00320         else if ( NPCInfo->squadState == SQUAD_RETREAT )
00321         {
00322                 if ( TIMER_Done( NPC, "flee" ) )
00323                 {
00324                         NPCInfo->squadState = SQUAD_IDLE;
00325                 }
00326                 else
00327                 {
00328                         faceEnemy2 = qfalse;
00329                 }
00330         }
00331         else if ( NPCInfo->squadState == SQUAD_IDLE )
00332         {
00333                 if ( !NPCInfo->goalEntity )
00334                 {
00335                         move2 = qfalse;
00336                         return;
00337                 }
00338         }
00339 
00340         //See if we're moving towards a goal, not the enemy
00341         if ( ( NPCInfo->goalEntity != NPC->enemy ) && ( NPCInfo->goalEntity != NULL ) )
00342         {
00343                 //Did we make it?
00344                 if ( NAV_HitNavGoal( NPC->r.currentOrigin, NPC->r.mins, NPC->r.maxs, NPCInfo->goalEntity->r.currentOrigin, 16, FlyingCreature( NPC ) ) || 
00345                         ( NPCInfo->squadState == SQUAD_SCOUT && enemyLOS2 && enemyDist2 <= 10000 ) )
00346                 {
00347                         int     newSquadState = SQUAD_STAND_AND_SHOOT;
00348                         //we got where we wanted to go, set timers based on why we were running
00349                         switch ( NPCInfo->squadState )
00350                         {
00351                         case SQUAD_RETREAT://was running away
00352                                 TIMER_Set( NPC, "duck", (NPC->client->pers.maxHealth - NPC->health) * 100 );
00353                                 TIMER_Set( NPC, "hideTime", Q_irand( 3000, 7000 ) );
00354                                 newSquadState = SQUAD_COVER;
00355                                 break;
00356                         case SQUAD_TRANSITION://was heading for a combat point
00357                                 TIMER_Set( NPC, "hideTime", Q_irand( 2000, 4000 ) );
00358                                 break;
00359                         case SQUAD_SCOUT://was running after player
00360                                 break;
00361                         default:
00362                                 break;
00363                         }
00364                         NPC_ReachedGoal();
00365                         //don't attack right away
00366                         TIMER_Set( NPC, "attackDelay", Q_irand( (6-NPCInfo->stats.aim)*50, (6-NPCInfo->stats.aim)*100 ) );      //FIXME: Slant for difficulty levels, too?
00367                         //don't do something else just yet
00368                         TIMER_Set( NPC, "roamTime", Q_irand( 1000, 4000 ) );
00369                         //stop fleeing
00370                         if ( NPCInfo->squadState == SQUAD_RETREAT )
00371                         {
00372                                 TIMER_Set( NPC, "flee", -level.time );
00373                                 NPCInfo->squadState = SQUAD_IDLE;
00374                         }
00375                         return;
00376                 }
00377 
00378                 //keep going, hold of roamTimer until we get there
00379                 TIMER_Set( NPC, "roamTime", Q_irand( 4000, 8000 ) );
00380         }
00381 }
00382 
00383 static void Sniper_ResolveBlockedShot( void )
00384 {
00385         if ( TIMER_Done( NPC, "duck" ) )
00386         {//we're not ducking
00387                 if ( TIMER_Done( NPC, "roamTime" ) )
00388                 {//not roaming
00389                         //FIXME: try to find another spot from which to hit the enemy
00390                         if ( (NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) && (!NPCInfo->goalEntity || NPCInfo->goalEntity == NPC->enemy) )
00391                         {//we were running after enemy
00392                                 //Try to find a combat point that can hit the enemy
00393                                 int cpFlags = (CP_CLEAR|CP_HAS_ROUTE);
00394                                 int cp;
00395 
00396                                 if ( NPCInfo->scriptFlags&SCF_USE_CP_NEAREST )
00397                                 {
00398                                         cpFlags &= ~(CP_FLANK|CP_APPROACH_ENEMY|CP_CLOSEST);
00399                                         cpFlags |= CP_NEAREST;
00400                                 }
00401                                 cp = NPC_FindCombatPoint( NPC->r.currentOrigin, NPC->r.currentOrigin, NPC->r.currentOrigin, cpFlags, 32, -1 );
00402                                 if ( cp == -1 && !(NPCInfo->scriptFlags&SCF_USE_CP_NEAREST) )
00403                                 {//okay, try one by the enemy
00404                                         cp = NPC_FindCombatPoint( NPC->r.currentOrigin, NPC->r.currentOrigin, NPC->enemy->r.currentOrigin, CP_CLEAR|CP_HAS_ROUTE|CP_HORZ_DIST_COLL, 32, -1 );
00405                                 }
00406                                 //NOTE: there may be a perfectly valid one, just not one within CP_COLLECT_RADIUS of either me or him...
00407                                 if ( cp != -1 )
00408                                 {//found a combat point that has a clear shot to enemy
00409                                         NPC_SetCombatPoint( cp );
00410                                         NPC_SetMoveGoal( NPC, level.combatPoints[cp].origin, 8, qtrue, cp, NULL );
00411                                         TIMER_Set( NPC, "duck", -1 );
00412                                         TIMER_Set( NPC, "attackDelay", Q_irand( 1000, 3000 ) );
00413                                         return;
00414                                 }
00415                         }
00416                 }
00417         }
00418         /*
00419         else
00420         {//maybe we should stand
00421                 if ( TIMER_Done( NPC, "stand" ) )
00422                 {//stand for as long as we'll be here
00423                         TIMER_Set( NPC, "stand", Q_irand( 500, 2000 ) );
00424                         return;
00425                 }
00426         }
00427         //Hmm, can't resolve this by telling them to duck or telling me to stand
00428         //We need to move!
00429         TIMER_Set( NPC, "roamTime", -1 );
00430         TIMER_Set( NPC, "stick", -1 );
00431         TIMER_Set( NPC, "duck", -1 );
00432         TIMER_Set( NPC, "attackDelay", Q_irand( 1000, 3000 ) );
00433         */
00434 }
00435 
00436 /*
00437 -------------------------
00438 ST_CheckFireState
00439 -------------------------
00440 */
00441 
00442 static void Sniper_CheckFireState( void )
00443 {
00444         if ( enemyCS2 )
00445         {//if have a clear shot, always try
00446                 return;
00447         }
00448 
00449         if ( NPCInfo->squadState == SQUAD_RETREAT || NPCInfo->squadState == SQUAD_TRANSITION || NPCInfo->squadState == SQUAD_SCOUT )
00450         {//runners never try to fire at the last pos
00451                 return;
00452         }
00453 
00454         if ( !VectorCompare( NPC->client->ps.velocity, vec3_origin ) )
00455         {//if moving at all, don't do this
00456                 return;
00457         }
00458 
00459         //continue to fire on their last position
00460         if ( !Q_irand( 0, 1 ) && NPCInfo->enemyLastSeenTime && level.time - NPCInfo->enemyLastSeenTime < ((5-NPCInfo->stats.aim)*1000) )//FIXME: incorporate skill too?
00461         {
00462                 if ( !VectorCompare( vec3_origin, NPCInfo->enemyLastSeenLocation ) )
00463                 {
00464                         //Fire on the last known position
00465                         vec3_t  muzzle, dir, angles;
00466 
00467                         CalcEntitySpot( NPC, SPOT_WEAPON, muzzle );
00468                         VectorSubtract( NPCInfo->enemyLastSeenLocation, muzzle, dir );
00469 
00470                         VectorNormalize( dir );
00471 
00472                         vectoangles( dir, angles );
00473 
00474                         NPCInfo->desiredYaw             = angles[YAW];
00475                         NPCInfo->desiredPitch   = angles[PITCH];
00476 
00477                         shoot2 = qtrue;
00478                         //faceEnemy2 = qfalse;
00479                 }
00480                 return;
00481         }
00482         else if ( level.time - NPCInfo->enemyLastSeenTime > 10000 )
00483         {//next time we see him, we'll miss few times first
00484                 NPC->count = 0;
00485         }
00486 }
00487 
00488 qboolean Sniper_EvaluateShot( int hit )
00489 {
00490         gentity_t *hitEnt;
00491 
00492         if ( !NPC->enemy )
00493         {
00494                 return qfalse;
00495         }
00496 
00497         hitEnt = &g_entities[hit];
00498         if ( hit == NPC->enemy->s.number 
00499                 || ( hitEnt && hitEnt->client && hitEnt->client->playerTeam == NPC->client->enemyTeam )
00500                 || ( hitEnt && hitEnt->takedamage && ((hitEnt->r.svFlags&SVF_GLASS_BRUSH)||hitEnt->health < 40||NPC->s.weapon == WP_EMPLACED_GUN) )
00501                 || ( hitEnt && (hitEnt->r.svFlags&SVF_GLASS_BRUSH)) )
00502         {//can hit enemy or will hit glass, so shoot anyway
00503                 return qtrue;
00504         }
00505         return qfalse;
00506 }
00507 
00508 void Sniper_FaceEnemy( void )
00509 {
00510         //FIXME: the ones behind kill holes are facing some arbitrary direction and not firing
00511         //FIXME: If actually trying to hit enemy, don't fire unless enemy is at least in front of me?
00512         //FIXME: need to give designers option to make them not miss first few shots
00513         if ( NPC->enemy )
00514         {
00515                 vec3_t  muzzle, target, angles, forward, right, up;
00516                 //Get the positions
00517                 AngleVectors( NPC->client->ps.viewangles, forward, right, up );
00518                 CalcMuzzlePoint( NPC, forward, right, up, muzzle );
00519                 //CalcEntitySpot( NPC, SPOT_WEAPON, muzzle );
00520                 CalcEntitySpot( NPC->enemy, SPOT_ORIGIN, target );
00521 
00522                 if ( enemyDist2 > 65536 && NPCInfo->stats.aim < 5 )//is 256 squared, was 16384 (128*128)
00523                 {
00524                         if ( NPC->count < (5-NPCInfo->stats.aim) )
00525                         {//miss a few times first
00526                                 if ( shoot2 && TIMER_Done( NPC, "attackDelay" ) && level.time >= NPCInfo->shotTime )
00527                                 {//ready to fire again
00528                                         qboolean        aimError = qfalse;
00529                                         qboolean        hit = qtrue;
00530                                         int                     tryMissCount = 0;
00531                                         trace_t         trace;
00532 
00533                                         GetAnglesForDirection( muzzle, target, angles );
00534                                         AngleVectors( angles, forward, right, up );
00535 
00536                                         while ( hit && tryMissCount < 10 )
00537                                         {
00538                                                 tryMissCount++;
00539                                                 if ( !Q_irand( 0, 1 ) )
00540                                                 {
00541                                                         aimError = qtrue;
00542                                                         if ( !Q_irand( 0, 1 ) )
00543                                                         {
00544                                                                 VectorMA( target, NPC->enemy->r.maxs[2]*flrand(1.5, 4), right, target );
00545                                                         }
00546                                                         else
00547                                                         {
00548                                                                 VectorMA( target, NPC->enemy->r.mins[2]*flrand(1.5, 4), right, target );
00549                                                         }
00550                                                 }
00551                                                 if ( !aimError || !Q_irand( 0, 1 ) )
00552                                                 {
00553                                                         if ( !Q_irand( 0, 1 ) )
00554                                                         {
00555                                                                 VectorMA( target, NPC->enemy->r.maxs[2]*flrand(1.5, 4), up, target );
00556                                                         }
00557                                                         else
00558                                                         {
00559                                                                 VectorMA( target, NPC->enemy->r.mins[2]*flrand(1.5, 4), up, target );
00560                                                         }
00561                                                 }
00562                                                 trap_Trace( &trace, muzzle, vec3_origin, vec3_origin, target, NPC->s.number, MASK_SHOT );
00563                                                 hit = Sniper_EvaluateShot( trace.entityNum );
00564                                         }
00565                                         NPC->count++;
00566                                 }
00567                                 else
00568                                 {
00569                                         if ( !enemyLOS2 )
00570                                         {
00571                                                 NPC_UpdateAngles( qtrue, qtrue );
00572                                                 return;
00573                                         }
00574                                 }
00575                         }
00576                         else
00577                         {//based on distance, aim value, difficulty and enemy movement, miss
00578                                 //FIXME: incorporate distance as a factor?
00579                                 int missFactor = 8-(NPCInfo->stats.aim+g_spskill.integer) * 3;
00580                                 if ( missFactor > ENEMY_POS_LAG_STEPS )
00581                                 {
00582                                         missFactor = ENEMY_POS_LAG_STEPS;
00583                                 }
00584                                 else if ( missFactor < 0 )
00585                                 {//???
00586                                         missFactor = 0 ;
00587                                 }
00588                                 VectorCopy( NPCInfo->enemyLaggedPos[missFactor], target );
00589                         }
00590                         GetAnglesForDirection( muzzle, target, angles );
00591                 }
00592                 else
00593                 {
00594                         target[2] += flrand( 0, NPC->enemy->r.maxs[2] );
00595                         //CalcEntitySpot( NPC->enemy, SPOT_HEAD_LEAN, target );
00596                         GetAnglesForDirection( muzzle, target, angles );
00597                 }
00598 
00599                 NPCInfo->desiredYaw             = AngleNormalize360( angles[YAW] );
00600                 NPCInfo->desiredPitch   = AngleNormalize360( angles[PITCH] );
00601         }
00602         NPC_UpdateAngles( qtrue, qtrue );
00603 }
00604 
00605 void Sniper_UpdateEnemyPos( void )
00606 {
00607         int index;
00608         int i;
00609 
00610         for ( i = MAX_ENEMY_POS_LAG-ENEMY_POS_LAG_INTERVAL; i >= 0; i -= ENEMY_POS_LAG_INTERVAL )
00611         {
00612                 index = i/ENEMY_POS_LAG_INTERVAL;
00613                 if ( !index )
00614                 {
00615                         CalcEntitySpot( NPC->enemy, SPOT_HEAD_LEAN, NPCInfo->enemyLaggedPos[index] );
00616                         NPCInfo->enemyLaggedPos[index][2] -= flrand( 2, 16 );
00617                 }
00618                 else
00619                 {
00620                         VectorCopy( NPCInfo->enemyLaggedPos[index-1], NPCInfo->enemyLaggedPos[index] );
00621                 }
00622         }
00623 }
00624 
00625 /*
00626 -------------------------
00627 NPC_BSSniper_Attack
00628 -------------------------
00629 */
00630 
00631 void Sniper_StartHide( void )
00632 {
00633         int duckTime = Q_irand( 2000, 5000 );
00634         
00635         TIMER_Set( NPC, "duck", duckTime );
00636         TIMER_Set( NPC, "watch", 500 );
00637         TIMER_Set( NPC, "attackDelay", duckTime + Q_irand( 500, 2000 ) );
00638 }
00639 
00640 void NPC_BSSniper_Attack( void )
00641 {
00642         //Don't do anything if we're hurt
00643         if ( NPC->painDebounceTime > level.time )
00644         {
00645                 NPC_UpdateAngles( qtrue, qtrue );
00646                 return;
00647         }
00648 
00649         //NPC_CheckEnemy( qtrue, qfalse );
00650         //If we don't have an enemy, just idle
00651         if ( NPC_CheckEnemyExt(qfalse) == qfalse )
00652         {
00653                 NPC->enemy = NULL;
00654                 NPC_BSSniper_Patrol();//FIXME: or patrol?
00655                 return;
00656         }
00657 
00658         if ( TIMER_Done( NPC, "flee" ) && NPC_CheckForDanger( NPC_CheckAlertEvents( qtrue, qtrue, -1, qfalse, AEL_DANGER ) ) )
00659         {//going to run
00660                 NPC_UpdateAngles( qtrue, qtrue );
00661                 return;
00662         }
00663 
00664         if ( !NPC->enemy )
00665         {//WTF?  somehow we lost our enemy?
00666                 NPC_BSSniper_Patrol();//FIXME: or patrol?
00667                 return;
00668         }
00669 
00670         enemyLOS2 = enemyCS2 = qfalse;
00671         move2 = qtrue;
00672         faceEnemy2 = qfalse;
00673         shoot2 = qfalse;
00674         enemyDist2 = DistanceSquared( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin );
00675         if ( enemyDist2 < 16384 )//128 squared
00676         {//too close, so switch to primary fire
00677                 if ( NPC->client->ps.weapon == WP_DISRUPTOR )
00678                 {//sniping... should be assumed
00679                         if ( NPCInfo->scriptFlags & SCF_ALT_FIRE )
00680                         {//use primary fire
00681                                 trace_t trace;
00682                                 trap_Trace ( &trace, NPC->enemy->r.currentOrigin, NPC->enemy->r.mins, NPC->enemy->r.maxs, NPC->r.currentOrigin, NPC->enemy->s.number, NPC->enemy->clipmask );
00683                                 if ( !trace.allsolid && !trace.startsolid && (trace.fraction == 1.0 || trace.entityNum == NPC->s.number ) )
00684                                 {//he can get right to me
00685                                         NPCInfo->scriptFlags &= ~SCF_ALT_FIRE;
00686                                         //reset fire-timing variables
00687                                         NPC_ChangeWeapon( WP_DISRUPTOR );
00688                                         NPC_UpdateAngles( qtrue, qtrue );
00689                                         return;
00690                                 }
00691                         }
00692                         //FIXME: switch back if he gets far away again?
00693                 }
00694         }
00695         else if ( enemyDist2 > 65536 )//256 squared
00696         {
00697                 if ( NPC->client->ps.weapon == WP_DISRUPTOR )
00698                 {//sniping... should be assumed
00699                         if ( !(NPCInfo->scriptFlags&SCF_ALT_FIRE) )
00700                         {//use primary fire
00701                                 NPCInfo->scriptFlags |= SCF_ALT_FIRE;
00702                                 //reset fire-timing variables
00703                                 NPC_ChangeWeapon( WP_DISRUPTOR );
00704                                 NPC_UpdateAngles( qtrue, qtrue );
00705                                 return;
00706                         }
00707                 }
00708         }
00709 
00710         Sniper_UpdateEnemyPos();
00711         //can we see our target?
00712         if ( NPC_ClearLOS4( NPC->enemy ) )//|| (NPCInfo->stats.aim >= 5 && gi.inPVS( NPC->client->renderInfo.eyePoint, NPC->enemy->currentOrigin )) )
00713         {
00714                 float maxShootDist;
00715 
00716                 NPCInfo->enemyLastSeenTime = level.time;
00717                 VectorCopy( NPC->enemy->r.currentOrigin, NPCInfo->enemyLastSeenLocation );
00718                 enemyLOS2 = qtrue;
00719                 maxShootDist = NPC_MaxDistSquaredForWeapon();
00720                 if ( enemyDist2 < maxShootDist )
00721                 {
00722                         vec3_t fwd, right, up, muzzle, end;
00723                         trace_t tr;
00724                         int hit;
00725 
00726                         AngleVectors( NPC->client->ps.viewangles, fwd, right, up );
00727                         CalcMuzzlePoint( NPC, fwd, right, up, muzzle );
00728                         VectorMA( muzzle, 8192, fwd, end );
00729                         trap_Trace ( &tr, muzzle, NULL, NULL, end, NPC->s.number, MASK_SHOT );
00730 
00731                         hit = tr.entityNum;
00732                         //can we shoot our target?
00733                         if ( Sniper_EvaluateShot( hit ) )
00734                         {
00735                                 enemyCS2 = qtrue;
00736                         }
00737                 }
00738         }
00739         /*
00740         else if ( gi.inPVS( NPC->enemy->currentOrigin, NPC->currentOrigin ) )
00741         {
00742                 NPCInfo->enemyLastSeenTime = level.time;
00743                 faceEnemy2 = qtrue;
00744         }
00745         */
00746 
00747         if ( enemyLOS2 )
00748         {//FIXME: no need to face enemy if we're moving to some other goal and he's too far away to shoot?
00749                 faceEnemy2 = qtrue;
00750         }
00751         if ( enemyCS2 )
00752         {
00753                 shoot2 = qtrue;
00754         }
00755         else if ( level.time - NPCInfo->enemyLastSeenTime > 3000 )
00756         {//Hmm, have to get around this bastard... FIXME: this NPCInfo->enemyLastSeenTime builds up when ducked seems to make them want to run when they uncrouch
00757                 Sniper_ResolveBlockedShot();
00758         }
00759 
00760         //Check for movement to take care of
00761         Sniper_CheckMoveState();
00762 
00763         //See if we should override shooting decision with any special considerations
00764         Sniper_CheckFireState();
00765 
00766         if ( move2 )
00767         {//move toward goal
00768                 if ( NPCInfo->goalEntity )//&& ( NPCInfo->goalEntity != NPC->enemy || enemyDist2 > 10000 ) )//100 squared
00769                 {
00770                         move2 = Sniper_Move();
00771                 }
00772                 else
00773                 {
00774                         move2 = qfalse;
00775                 }
00776         }
00777 
00778         if ( !move2 )
00779         {
00780                 if ( !TIMER_Done( NPC, "duck" ) )
00781                 {
00782                         if ( TIMER_Done( NPC, "watch" ) )
00783                         {//not while watching
00784                                 ucmd.upmove = -127;
00785                         }
00786                 }
00787                 //FIXME: what about leaning?
00788                 //FIXME: also, when stop ducking, start looking, if enemy can see me, chance of ducking back down again
00789         }
00790         else
00791         {//stop ducking!
00792                 TIMER_Set( NPC, "duck", -1 );
00793         }
00794 
00795         if ( TIMER_Done( NPC, "duck" ) 
00796                 && TIMER_Done( NPC, "watch" ) 
00797                 && (TIMER_Get( NPC, "attackDelay" )-level.time) > 1000 
00798                 && NPC->attackDebounceTime < level.time )
00799         {
00800                 if ( enemyLOS2 && (NPCInfo->scriptFlags&SCF_ALT_FIRE) )
00801                 {
00802                         if ( NPC->fly_sound_debounce_time < level.time )
00803                         {
00804                                 NPC->fly_sound_debounce_time = level.time + 2000;
00805                         }
00806                 }
00807         }
00808 
00809         if ( !faceEnemy2 )
00810         {//we want to face in the dir we're running
00811                 if ( move2 )
00812                 {//don't run away and shoot
00813                         NPCInfo->desiredYaw = NPCInfo->lastPathAngles[YAW];
00814                         NPCInfo->desiredPitch = 0;
00815                         shoot2 = qfalse;
00816                 }
00817                 NPC_UpdateAngles( qtrue, qtrue );
00818         }
00819         else// if ( faceEnemy2 )
00820         {//face the enemy
00821                 Sniper_FaceEnemy();
00822         }
00823 
00824         if ( NPCInfo->scriptFlags&SCF_DONT_FIRE )
00825         {
00826                 shoot2 = qfalse;
00827         }
00828 
00829         //FIXME: don't shoot right away!
00830         if ( shoot2 )
00831         {//try to shoot if it's time
00832                 if ( TIMER_Done( NPC, "attackDelay" ) )
00833                 {
00834                         WeaponThink( qtrue );
00835                         if ( ucmd.buttons&(BUTTON_ATTACK|BUTTON_ALT_ATTACK) )
00836                         {
00837                                 G_SoundOnEnt( NPC, CHAN_WEAPON, "sound/null.wav" );
00838                         }
00839 
00840                         //took a shot, now hide
00841                         if ( !(NPC->spawnflags&SPF_NO_HIDE) && !Q_irand( 0, 1 ) )
00842                         {
00843                                 //FIXME: do this if in combat point and combat point has duck-type cover... also handle lean-type cover
00844                                 Sniper_StartHide();
00845                         }
00846                         else
00847                         {
00848                                 TIMER_Set( NPC, "attackDelay", NPCInfo->shotTime-level.time );
00849                         }
00850                 }
00851         }
00852 }
00853 
00854 void NPC_BSSniper_Default( void )
00855 {
00856         if( !NPC->enemy )
00857         {//don't have an enemy, look for one
00858                 NPC_BSSniper_Patrol();
00859         }
00860         else//if ( NPC->enemy )
00861         {//have an enemy
00862                 NPC_BSSniper_Attack();
00863         }
00864 }