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
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 );
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 {
00062 if ( self->health > 0 )
00063 {
00064 G_AddVoiceEvent( self, Q_irand(EV_CONFUSE1, EV_CONFUSE3), 2000 );
00065 }
00066
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
00073 G_ClearEnemy( self );
00074
00075 self->NPC->investigateCount = 0;
00076 }
00077
00078
00079
00080
00081
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 {
00096 G_AddVoiceEvent( self, Q_irand(EV_PUSHED1, EV_PUSHED3), 2000 );
00097 }
00098 }
00099
00100
00101
00102
00103
00104
00105
00106 static void Sniper_HoldPosition( void )
00107 {
00108 NPC_FreeCombatPoint( NPCInfo->combatPoint, qtrue );
00109 NPCInfo->goalEntity = NULL;
00110
00111
00112
00113
00114
00115
00116 }
00117
00118
00119
00120
00121
00122
00123
00124 static qboolean Sniper_Move( void )
00125 {
00126 qboolean moved;
00127 navInfo_t info;
00128
00129 NPCInfo->combatMove = qtrue;
00130
00131 moved = NPC_MoveToGoal( qtrue );
00132
00133
00134 NAV_GetLastMove( &info );
00135
00136
00137
00138 if ( info.flags & NIF_COLLISION )
00139 {
00140 if ( info.blocker == NPC->enemy )
00141 {
00142 Sniper_HoldPosition();
00143 }
00144 }
00145
00146
00147 if ( moved == qfalse )
00148 {
00149 if ( (NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) && NPCInfo->goalEntity && NPCInfo->goalEntity == NPC->enemy )
00150 {
00151
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 {
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
00165 if ( cp != -1 )
00166 {
00167 NPC_SetCombatPoint( cp );
00168 NPC_SetMoveGoal( NPC, level.combatPoints[cp].origin, 8, qtrue, cp, NULL );
00169 return moved;
00170 }
00171 }
00172
00173 Sniper_HoldPosition();
00174 }
00175
00176 return moved;
00177 }
00178
00179
00180
00181
00182
00183
00184
00185 void NPC_BSSniper_Patrol( void )
00186 {
00187 NPC->count = 0;
00188
00189 if ( NPCInfo->confusionTime < level.time )
00190 {
00191
00192 if ( NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES )
00193 {
00194 if ( NPC_CheckPlayerTeamStealth() )
00195 {
00196
00197
00198 NPC_UpdateAngles( qtrue, qtrue );
00199 return;
00200 }
00201 }
00202
00203 if ( !(NPCInfo->scriptFlags&SCF_IGNORE_ALERTS) )
00204 {
00205
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 {
00214
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 {
00225 G_SetEnemy( NPC, level.alertEvents[alertEvent].owner );
00226
00227 TIMER_Set( NPC, "attackDelay", Q_irand( (6-NPCInfo->stats.aim)*100, (6-NPCInfo->stats.aim)*500 ) );
00228 }
00229 }
00230 else
00231 {
00232
00233
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 {
00238 NPCInfo->investigateDebounceTime += Q_irand( 500, 2500 );
00239 }
00240 }
00241 }
00242 }
00243
00244 if ( NPCInfo->investigateDebounceTime > level.time )
00245 {
00246
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
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
00280
00281
00282
00283
00284
00285
00286
00287
00288
00289
00290
00291
00292
00293
00294
00295
00296
00297
00298
00299
00300
00301
00302
00303
00304
00305
00306
00307
00308 static void Sniper_CheckMoveState( void )
00309 {
00310
00311 if ( !(NPCInfo->scriptFlags & SCF_CHASE_ENEMIES) )
00312 {
00313 if ( NPCInfo->goalEntity == NPC->enemy )
00314 {
00315 move2 = qfalse;
00316 return;
00317 }
00318 }
00319
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
00341 if ( ( NPCInfo->goalEntity != NPC->enemy ) && ( NPCInfo->goalEntity != NULL ) )
00342 {
00343
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
00349 switch ( NPCInfo->squadState )
00350 {
00351 case SQUAD_RETREAT:
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:
00357 TIMER_Set( NPC, "hideTime", Q_irand( 2000, 4000 ) );
00358 break;
00359 case SQUAD_SCOUT:
00360 break;
00361 default:
00362 break;
00363 }
00364 NPC_ReachedGoal();
00365
00366 TIMER_Set( NPC, "attackDelay", Q_irand( (6-NPCInfo->stats.aim)*50, (6-NPCInfo->stats.aim)*100 ) );
00367
00368 TIMER_Set( NPC, "roamTime", Q_irand( 1000, 4000 ) );
00369
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
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 {
00387 if ( TIMER_Done( NPC, "roamTime" ) )
00388 {
00389
00390 if ( (NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) && (!NPCInfo->goalEntity || NPCInfo->goalEntity == NPC->enemy) )
00391 {
00392
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 {
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
00407 if ( cp != -1 )
00408 {
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
00420
00421
00422
00423
00424
00425
00426
00427
00428
00429
00430
00431
00432
00433
00434 }
00435
00436
00437
00438
00439
00440
00441
00442 static void Sniper_CheckFireState( void )
00443 {
00444 if ( enemyCS2 )
00445 {
00446 return;
00447 }
00448
00449 if ( NPCInfo->squadState == SQUAD_RETREAT || NPCInfo->squadState == SQUAD_TRANSITION || NPCInfo->squadState == SQUAD_SCOUT )
00450 {
00451 return;
00452 }
00453
00454 if ( !VectorCompare( NPC->client->ps.velocity, vec3_origin ) )
00455 {
00456 return;
00457 }
00458
00459
00460 if ( !Q_irand( 0, 1 ) && NPCInfo->enemyLastSeenTime && level.time - NPCInfo->enemyLastSeenTime < ((5-NPCInfo->stats.aim)*1000) )
00461 {
00462 if ( !VectorCompare( vec3_origin, NPCInfo->enemyLastSeenLocation ) )
00463 {
00464
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
00479 }
00480 return;
00481 }
00482 else if ( level.time - NPCInfo->enemyLastSeenTime > 10000 )
00483 {
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 {
00503 return qtrue;
00504 }
00505 return qfalse;
00506 }
00507
00508 void Sniper_FaceEnemy( void )
00509 {
00510
00511
00512
00513 if ( NPC->enemy )
00514 {
00515 vec3_t muzzle, target, angles, forward, right, up;
00516
00517 AngleVectors( NPC->client->ps.viewangles, forward, right, up );
00518 CalcMuzzlePoint( NPC, forward, right, up, muzzle );
00519
00520 CalcEntitySpot( NPC->enemy, SPOT_ORIGIN, target );
00521
00522 if ( enemyDist2 > 65536 && NPCInfo->stats.aim < 5 )
00523 {
00524 if ( NPC->count < (5-NPCInfo->stats.aim) )
00525 {
00526 if ( shoot2 && TIMER_Done( NPC, "attackDelay" ) && level.time >= NPCInfo->shotTime )
00527 {
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 {
00578
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
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
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
00643 if ( NPC->painDebounceTime > level.time )
00644 {
00645 NPC_UpdateAngles( qtrue, qtrue );
00646 return;
00647 }
00648
00649
00650
00651 if ( NPC_CheckEnemyExt(qfalse) == qfalse )
00652 {
00653 NPC->enemy = NULL;
00654 NPC_BSSniper_Patrol();
00655 return;
00656 }
00657
00658 if ( TIMER_Done( NPC, "flee" ) && NPC_CheckForDanger( NPC_CheckAlertEvents( qtrue, qtrue, -1, qfalse, AEL_DANGER ) ) )
00659 {
00660 NPC_UpdateAngles( qtrue, qtrue );
00661 return;
00662 }
00663
00664 if ( !NPC->enemy )
00665 {
00666 NPC_BSSniper_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 )
00676 {
00677 if ( NPC->client->ps.weapon == WP_DISRUPTOR )
00678 {
00679 if ( NPCInfo->scriptFlags & SCF_ALT_FIRE )
00680 {
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 {
00685 NPCInfo->scriptFlags &= ~SCF_ALT_FIRE;
00686
00687 NPC_ChangeWeapon( WP_DISRUPTOR );
00688 NPC_UpdateAngles( qtrue, qtrue );
00689 return;
00690 }
00691 }
00692
00693 }
00694 }
00695 else if ( enemyDist2 > 65536 )
00696 {
00697 if ( NPC->client->ps.weapon == WP_DISRUPTOR )
00698 {
00699 if ( !(NPCInfo->scriptFlags&SCF_ALT_FIRE) )
00700 {
00701 NPCInfo->scriptFlags |= SCF_ALT_FIRE;
00702
00703 NPC_ChangeWeapon( WP_DISRUPTOR );
00704 NPC_UpdateAngles( qtrue, qtrue );
00705 return;
00706 }
00707 }
00708 }
00709
00710 Sniper_UpdateEnemyPos();
00711
00712 if ( NPC_ClearLOS4( NPC->enemy ) )
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
00733 if ( Sniper_EvaluateShot( hit ) )
00734 {
00735 enemyCS2 = qtrue;
00736 }
00737 }
00738 }
00739
00740
00741
00742
00743
00744
00745
00746
00747 if ( enemyLOS2 )
00748 {
00749 faceEnemy2 = qtrue;
00750 }
00751 if ( enemyCS2 )
00752 {
00753 shoot2 = qtrue;
00754 }
00755 else if ( level.time - NPCInfo->enemyLastSeenTime > 3000 )
00756 {
00757 Sniper_ResolveBlockedShot();
00758 }
00759
00760
00761 Sniper_CheckMoveState();
00762
00763
00764 Sniper_CheckFireState();
00765
00766 if ( move2 )
00767 {
00768 if ( NPCInfo->goalEntity )
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 {
00784 ucmd.upmove = -127;
00785 }
00786 }
00787
00788
00789 }
00790 else
00791 {
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 {
00811 if ( move2 )
00812 {
00813 NPCInfo->desiredYaw = NPCInfo->lastPathAngles[YAW];
00814 NPCInfo->desiredPitch = 0;
00815 shoot2 = qfalse;
00816 }
00817 NPC_UpdateAngles( qtrue, qtrue );
00818 }
00819 else
00820 {
00821 Sniper_FaceEnemy();
00822 }
00823
00824 if ( NPCInfo->scriptFlags&SCF_DONT_FIRE )
00825 {
00826 shoot2 = qfalse;
00827 }
00828
00829
00830 if ( shoot2 )
00831 {
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
00841 if ( !(NPC->spawnflags&SPF_NO_HIDE) && !Q_irand( 0, 1 ) )
00842 {
00843
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 {
00858 NPC_BSSniper_Patrol();
00859 }
00860 else
00861 {
00862 NPC_BSSniper_Attack();
00863 }
00864 }