AI_Sniper.cpp 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912
  1. // leave this line at the top of all AI_xxxx.cpp files for PCH reasons...
  2. #include "g_headers.h"
  3. #include "b_local.h"
  4. #include "g_nav.h"
  5. #include "anims.h"
  6. #include "g_navigator.h"
  7. extern void CG_DrawAlert( vec3_t origin, float rating );
  8. extern void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime );
  9. extern void NPC_TempLookTarget( gentity_t *self, int lookEntNum, int minLookTime, int maxLookTime );
  10. extern qboolean G_ExpandPointToBBox( vec3_t point, const vec3_t mins, const vec3_t maxs, int ignore, int clipmask );
  11. extern qboolean FlyingCreature( gentity_t *ent );
  12. extern void Saboteur_Cloak( gentity_t *self );
  13. //extern CNavigator navigator;
  14. #define SPF_NO_HIDE 2
  15. #define MAX_VIEW_DIST 1024
  16. #define MAX_VIEW_SPEED 250
  17. #define MAX_LIGHT_INTENSITY 255
  18. #define MIN_LIGHT_THRESHOLD 0.1
  19. #define DISTANCE_SCALE 0.25f
  20. #define DISTANCE_THRESHOLD 0.075f
  21. #define SPEED_SCALE 0.25f
  22. #define FOV_SCALE 0.5f
  23. #define LIGHT_SCALE 0.25f
  24. #define REALIZE_THRESHOLD 0.6f
  25. #define CAUTIOUS_THRESHOLD ( REALIZE_THRESHOLD * 0.75 )
  26. extern void NPC_Tusken_Taunt( void );
  27. qboolean NPC_CheckPlayerTeamStealth( void );
  28. static qboolean enemyLOS;
  29. static qboolean enemyCS;
  30. static qboolean faceEnemy;
  31. static qboolean move;
  32. static qboolean shoot;
  33. static float enemyDist;
  34. //Local state enums
  35. enum
  36. {
  37. LSTATE_NONE = 0,
  38. LSTATE_UNDERFIRE,
  39. LSTATE_INVESTIGATE,
  40. };
  41. void Sniper_ClearTimers( gentity_t *ent )
  42. {
  43. TIMER_Set( ent, "chatter", 0 );
  44. TIMER_Set( ent, "duck", 0 );
  45. TIMER_Set( ent, "stand", 0 );
  46. TIMER_Set( ent, "shuffleTime", 0 );
  47. TIMER_Set( ent, "sleepTime", 0 );
  48. TIMER_Set( ent, "enemyLastVisible", 0 );
  49. TIMER_Set( ent, "roamTime", 0 );
  50. TIMER_Set( ent, "hideTime", 0 );
  51. TIMER_Set( ent, "attackDelay", 0 ); //FIXME: Slant for difficulty levels
  52. TIMER_Set( ent, "stick", 0 );
  53. TIMER_Set( ent, "scoutTime", 0 );
  54. TIMER_Set( ent, "flee", 0 );
  55. TIMER_Set( ent, "taunting", 0 );
  56. }
  57. void NPC_Sniper_PlayConfusionSound( gentity_t *self )
  58. {//FIXME: make this a custom sound in sound set
  59. if ( self->health > 0 )
  60. {
  61. G_AddVoiceEvent( self, Q_irand(EV_CONFUSE1, EV_CONFUSE3), 2000 );
  62. }
  63. //reset him to be totally unaware again
  64. TIMER_Set( self, "enemyLastVisible", 0 );
  65. TIMER_Set( self, "flee", 0 );
  66. self->NPC->squadState = SQUAD_IDLE;
  67. self->NPC->tempBehavior = BS_DEFAULT;
  68. //self->NPC->behaviorState = BS_PATROL;
  69. G_ClearEnemy( self );//FIXME: or just self->enemy = NULL;?
  70. self->NPC->investigateCount = 0;
  71. }
  72. /*
  73. -------------------------
  74. NPC_ST_Pain
  75. -------------------------
  76. */
  77. void NPC_Sniper_Pain( gentity_t *self, gentity_t *inflictor, gentity_t *other, vec3_t point, int damage, int mod )
  78. {
  79. self->NPC->localState = LSTATE_UNDERFIRE;
  80. if ( self->client->NPC_class == CLASS_SABOTEUR )
  81. {
  82. Saboteur_Decloak( self );
  83. }
  84. TIMER_Set( self, "duck", -1 );
  85. TIMER_Set( self, "stand", 2000 );
  86. NPC_Pain( self, inflictor, other, point, damage, mod );
  87. if ( !damage && self->health > 0 )
  88. {//FIXME: better way to know I was pushed
  89. G_AddVoiceEvent( self, Q_irand(EV_PUSHED1, EV_PUSHED3), 2000 );
  90. }
  91. }
  92. /*
  93. -------------------------
  94. ST_HoldPosition
  95. -------------------------
  96. */
  97. static void Sniper_HoldPosition( void )
  98. {
  99. NPC_FreeCombatPoint( NPCInfo->combatPoint, qtrue );
  100. NPCInfo->goalEntity = NULL;
  101. /*if ( TIMER_Done( NPC, "stand" ) )
  102. {//FIXME: what if can't shoot from this pos?
  103. TIMER_Set( NPC, "duck", Q_irand( 2000, 4000 ) );
  104. }
  105. */
  106. }
  107. /*
  108. -------------------------
  109. ST_Move
  110. -------------------------
  111. */
  112. static qboolean Sniper_Move( void )
  113. {
  114. NPCInfo->combatMove = qtrue;//always move straight toward our goal
  115. qboolean moved = NPC_MoveToGoal( qtrue );
  116. // navInfo_t info;
  117. //Get the move info
  118. // NAV_GetLastMove( info );
  119. //FIXME: if we bump into another one of our guys and can't get around him, just stop!
  120. //If we hit our target, then stop and fire!
  121. // if ( info.flags & NIF_COLLISION )
  122. // {
  123. // if ( info.blocker == NPC->enemy )
  124. // {
  125. // Sniper_HoldPosition();
  126. // }
  127. // }
  128. //If our move failed, then reset
  129. if ( moved == qfalse )
  130. {//couldn't get to enemy
  131. if ( (NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) && NPCInfo->goalEntity && NPCInfo->goalEntity == NPC->enemy )
  132. {//we were running after enemy
  133. //Try to find a combat point that can hit the enemy
  134. int cpFlags = (CP_CLEAR|CP_HAS_ROUTE);
  135. if ( NPCInfo->scriptFlags&SCF_USE_CP_NEAREST )
  136. {
  137. cpFlags &= ~(CP_FLANK|CP_APPROACH_ENEMY|CP_CLOSEST);
  138. cpFlags |= CP_NEAREST;
  139. }
  140. int cp = NPC_FindCombatPoint( NPC->currentOrigin, NPC->currentOrigin, NPC->currentOrigin, cpFlags, 32 );
  141. if ( cp == -1 && !(NPCInfo->scriptFlags&SCF_USE_CP_NEAREST) )
  142. {//okay, try one by the enemy
  143. cp = NPC_FindCombatPoint( NPC->currentOrigin, NPC->currentOrigin, NPC->enemy->currentOrigin, CP_CLEAR|CP_HAS_ROUTE|CP_HORZ_DIST_COLL, 32 );
  144. }
  145. //NOTE: there may be a perfectly valid one, just not one within CP_COLLECT_RADIUS of either me or him...
  146. if ( cp != -1 )
  147. {//found a combat point that has a clear shot to enemy
  148. NPC_SetCombatPoint( cp );
  149. NPC_SetMoveGoal( NPC, level.combatPoints[cp].origin, 8, qtrue, cp );
  150. return moved;
  151. }
  152. }
  153. //just hang here
  154. Sniper_HoldPosition();
  155. }
  156. return moved;
  157. }
  158. /*
  159. -------------------------
  160. NPC_BSSniper_Patrol
  161. -------------------------
  162. */
  163. void NPC_BSSniper_Patrol( void )
  164. {//FIXME: pick up on bodies of dead buddies?
  165. NPC->count = 0;
  166. if ( NPCInfo->confusionTime < level.time )
  167. {
  168. //Look for any enemies
  169. if ( NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES )
  170. {
  171. if ( NPC_CheckPlayerTeamStealth() )
  172. {
  173. //NPCInfo->behaviorState = BS_HUNT_AND_KILL;//Should be auto now
  174. //NPC_AngerSound();
  175. NPC_UpdateAngles( qtrue, qtrue );
  176. return;
  177. }
  178. }
  179. if ( !(NPCInfo->scriptFlags&SCF_IGNORE_ALERTS) )
  180. {
  181. //Is there danger nearby
  182. int alertEvent = NPC_CheckAlertEvents( qtrue, qtrue, -1, qfalse, AEL_SUSPICIOUS );
  183. if ( NPC_CheckForDanger( alertEvent ) )
  184. {
  185. NPC_UpdateAngles( qtrue, qtrue );
  186. return;
  187. }
  188. else
  189. {//check for other alert events
  190. //There is an event to look at
  191. if ( alertEvent >= 0 )//&& level.alertEvents[alertEvent].ID != NPCInfo->lastAlertID )
  192. {
  193. //NPCInfo->lastAlertID = level.alertEvents[alertEvent].ID;
  194. if ( level.alertEvents[alertEvent].level == AEL_DISCOVERED )
  195. {
  196. if ( level.alertEvents[alertEvent].owner &&
  197. level.alertEvents[alertEvent].owner->client &&
  198. level.alertEvents[alertEvent].owner->health >= 0 &&
  199. level.alertEvents[alertEvent].owner->client->playerTeam == NPC->client->enemyTeam )
  200. {//an enemy
  201. G_SetEnemy( NPC, level.alertEvents[alertEvent].owner );
  202. //NPCInfo->enemyLastSeenTime = level.time;
  203. TIMER_Set( NPC, "attackDelay", Q_irand( (6-NPCInfo->stats.aim)*100, (6-NPCInfo->stats.aim)*500 ) );
  204. }
  205. }
  206. else
  207. {//FIXME: get more suspicious over time?
  208. //Save the position for movement (if necessary)
  209. //FIXME: sound?
  210. VectorCopy( level.alertEvents[alertEvent].position, NPCInfo->investigateGoal );
  211. NPCInfo->investigateDebounceTime = level.time + Q_irand( 500, 1000 );
  212. if ( level.alertEvents[alertEvent].level == AEL_SUSPICIOUS )
  213. {//suspicious looks longer
  214. NPCInfo->investigateDebounceTime += Q_irand( 500, 2500 );
  215. }
  216. }
  217. }
  218. }
  219. if ( NPCInfo->investigateDebounceTime > level.time )
  220. {//FIXME: walk over to it, maybe? Not if not chase enemies flag
  221. //NOTE: stops walking or doing anything else below
  222. vec3_t dir, angles;
  223. float o_yaw, o_pitch;
  224. VectorSubtract( NPCInfo->investigateGoal, NPC->client->renderInfo.eyePoint, dir );
  225. vectoangles( dir, angles );
  226. o_yaw = NPCInfo->desiredYaw;
  227. o_pitch = NPCInfo->desiredPitch;
  228. NPCInfo->desiredYaw = angles[YAW];
  229. NPCInfo->desiredPitch = angles[PITCH];
  230. NPC_UpdateAngles( qtrue, qtrue );
  231. NPCInfo->desiredYaw = o_yaw;
  232. NPCInfo->desiredPitch = o_pitch;
  233. return;
  234. }
  235. }
  236. }
  237. //If we have somewhere to go, then do that
  238. if ( UpdateGoal() )
  239. {
  240. ucmd.buttons |= BUTTON_WALKING;
  241. NPC_MoveToGoal( qtrue );
  242. }
  243. NPC_UpdateAngles( qtrue, qtrue );
  244. }
  245. /*
  246. -------------------------
  247. NPC_BSSniper_Idle
  248. -------------------------
  249. */
  250. /*
  251. void NPC_BSSniper_Idle( void )
  252. {
  253. //reset our shotcount
  254. NPC->count = 0;
  255. //FIXME: check for other alert events?
  256. //Is there danger nearby?
  257. if ( NPC_CheckForDanger( NPC_CheckAlertEvents( qtrue, qtrue ) ) )
  258. {
  259. NPC_UpdateAngles( qtrue, qtrue );
  260. return;
  261. }
  262. TIMER_Set( NPC, "roamTime", 2000 + Q_irand( 1000, 2000 ) );
  263. NPC_UpdateAngles( qtrue, qtrue );
  264. }
  265. */
  266. /*
  267. -------------------------
  268. ST_CheckMoveState
  269. -------------------------
  270. */
  271. static void Sniper_CheckMoveState( void )
  272. {
  273. //See if we're a scout
  274. if ( !(NPCInfo->scriptFlags & SCF_CHASE_ENEMIES) )//NPCInfo->behaviorState == BS_STAND_AND_SHOOT )
  275. {
  276. if ( NPCInfo->goalEntity == NPC->enemy )
  277. {
  278. move = qfalse;
  279. return;
  280. }
  281. }
  282. //See if we're running away
  283. else if ( NPCInfo->squadState == SQUAD_RETREAT )
  284. {
  285. if ( TIMER_Done( NPC, "flee" ) )
  286. {
  287. NPCInfo->squadState = SQUAD_IDLE;
  288. }
  289. else
  290. {
  291. faceEnemy = qfalse;
  292. }
  293. }
  294. else if ( NPCInfo->squadState == SQUAD_IDLE )
  295. {
  296. if ( !NPCInfo->goalEntity )
  297. {
  298. move = qfalse;
  299. return;
  300. }
  301. }
  302. if ( !TIMER_Done( NPC, "taunting" ) )
  303. {//no move while taunting
  304. move = qfalse;
  305. return;
  306. }
  307. //See if we're moving towards a goal, not the enemy
  308. if ( ( NPCInfo->goalEntity != NPC->enemy ) && ( NPCInfo->goalEntity != NULL ) )
  309. {
  310. //Did we make it?
  311. if ( STEER::Reached(NPC, NPCInfo->goalEntity, 16, !!FlyingCreature(NPC)) ||
  312. ( NPCInfo->squadState == SQUAD_SCOUT && enemyLOS && enemyDist <= 10000 ) )
  313. {
  314. int newSquadState = SQUAD_STAND_AND_SHOOT;
  315. //we got where we wanted to go, set timers based on why we were running
  316. switch ( NPCInfo->squadState )
  317. {
  318. case SQUAD_RETREAT://was running away
  319. if ( NPC->client->NPC_class == CLASS_SABOTEUR )
  320. {
  321. Saboteur_Cloak( NPC );
  322. }
  323. TIMER_Set( NPC, "duck", (NPC->max_health - NPC->health) * 100 );
  324. TIMER_Set( NPC, "hideTime", Q_irand( 3000, 7000 ) );
  325. newSquadState = SQUAD_COVER;
  326. break;
  327. case SQUAD_TRANSITION://was heading for a combat point
  328. TIMER_Set( NPC, "hideTime", Q_irand( 2000, 4000 ) );
  329. break;
  330. case SQUAD_SCOUT://was running after player
  331. break;
  332. default:
  333. break;
  334. }
  335. NPC_ReachedGoal();
  336. //don't attack right away
  337. TIMER_Set( NPC, "attackDelay", Q_irand( (6-NPCInfo->stats.aim)*50, (6-NPCInfo->stats.aim)*100 ) ); //FIXME: Slant for difficulty levels, too?
  338. //don't do something else just yet
  339. TIMER_Set( NPC, "roamTime", Q_irand( 1000, 4000 ) );
  340. //stop fleeing
  341. if ( NPCInfo->squadState == SQUAD_RETREAT )
  342. {
  343. TIMER_Set( NPC, "flee", -level.time );
  344. NPCInfo->squadState = SQUAD_IDLE;
  345. }
  346. return;
  347. }
  348. //keep going, hold of roamTimer until we get there
  349. TIMER_Set( NPC, "roamTime", Q_irand( 4000, 8000 ) );
  350. }
  351. }
  352. static void Sniper_ResolveBlockedShot( void )
  353. {
  354. if ( TIMER_Done( NPC, "duck" ) )
  355. {//we're not ducking
  356. if ( TIMER_Done( NPC, "roamTime" ) )
  357. {//not roaming
  358. //FIXME: try to find another spot from which to hit the enemy
  359. if ( (NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) && (!NPCInfo->goalEntity || NPCInfo->goalEntity == NPC->enemy) )
  360. {//we were running after enemy
  361. //Try to find a combat point that can hit the enemy
  362. int cpFlags = (CP_CLEAR|CP_HAS_ROUTE);
  363. if ( NPCInfo->scriptFlags&SCF_USE_CP_NEAREST )
  364. {
  365. cpFlags &= ~(CP_FLANK|CP_APPROACH_ENEMY|CP_CLOSEST);
  366. cpFlags |= CP_NEAREST;
  367. }
  368. int cp = NPC_FindCombatPoint( NPC->currentOrigin, NPC->currentOrigin, NPC->currentOrigin, cpFlags, 32 );
  369. if ( cp == -1 && !(NPCInfo->scriptFlags&SCF_USE_CP_NEAREST) )
  370. {//okay, try one by the enemy
  371. cp = NPC_FindCombatPoint( NPC->currentOrigin, NPC->currentOrigin, NPC->enemy->currentOrigin, CP_CLEAR|CP_HAS_ROUTE|CP_HORZ_DIST_COLL, 32 );
  372. }
  373. //NOTE: there may be a perfectly valid one, just not one within CP_COLLECT_RADIUS of either me or him...
  374. if ( cp != -1 )
  375. {//found a combat point that has a clear shot to enemy
  376. NPC_SetCombatPoint( cp );
  377. NPC_SetMoveGoal( NPC, level.combatPoints[cp].origin, 8, qtrue, cp );
  378. TIMER_Set( NPC, "duck", -1 );
  379. if ( NPC->client->NPC_class == CLASS_SABOTEUR )
  380. {
  381. Saboteur_Decloak( NPC );
  382. }
  383. TIMER_Set( NPC, "attackDelay", Q_irand( 1000, 3000 ) );
  384. return;
  385. }
  386. }
  387. }
  388. }
  389. /*
  390. else
  391. {//maybe we should stand
  392. if ( TIMER_Done( NPC, "stand" ) )
  393. {//stand for as long as we'll be here
  394. TIMER_Set( NPC, "stand", Q_irand( 500, 2000 ) );
  395. return;
  396. }
  397. }
  398. //Hmm, can't resolve this by telling them to duck or telling me to stand
  399. //We need to move!
  400. TIMER_Set( NPC, "roamTime", -1 );
  401. TIMER_Set( NPC, "stick", -1 );
  402. TIMER_Set( NPC, "duck", -1 );
  403. TIMER_Set( NPC, "attackDelay", Q_irand( 1000, 3000 ) );
  404. */
  405. }
  406. /*
  407. -------------------------
  408. ST_CheckFireState
  409. -------------------------
  410. */
  411. static void Sniper_CheckFireState( void )
  412. {
  413. if ( enemyCS )
  414. {//if have a clear shot, always try
  415. return;
  416. }
  417. if ( NPCInfo->squadState == SQUAD_RETREAT || NPCInfo->squadState == SQUAD_TRANSITION || NPCInfo->squadState == SQUAD_SCOUT )
  418. {//runners never try to fire at the last pos
  419. return;
  420. }
  421. if ( !VectorCompare( NPC->client->ps.velocity, vec3_origin ) )
  422. {//if moving at all, don't do this
  423. return;
  424. }
  425. if ( !TIMER_Done( NPC, "taunting" ) )
  426. {//no shoot while taunting
  427. return;
  428. }
  429. //continue to fire on their last position
  430. if ( !Q_irand( 0, 1 )
  431. && NPCInfo->enemyLastSeenTime
  432. && level.time - NPCInfo->enemyLastSeenTime < ((5-NPCInfo->stats.aim)*1000) )//FIXME: incorporate skill too?
  433. {
  434. if ( !VectorCompare( vec3_origin, NPCInfo->enemyLastSeenLocation ) )
  435. {
  436. //Fire on the last known position
  437. vec3_t muzzle, dir, angles;
  438. CalcEntitySpot( NPC, SPOT_WEAPON, muzzle );
  439. VectorSubtract( NPCInfo->enemyLastSeenLocation, muzzle, dir );
  440. VectorNormalize( dir );
  441. vectoangles( dir, angles );
  442. NPCInfo->desiredYaw = angles[YAW];
  443. NPCInfo->desiredPitch = angles[PITCH];
  444. shoot = qtrue;
  445. //faceEnemy = qfalse;
  446. }
  447. return;
  448. }
  449. else if ( level.time - NPCInfo->enemyLastSeenTime > 10000 )
  450. {//next time we see him, we'll miss few times first
  451. NPC->count = 0;
  452. }
  453. }
  454. qboolean Sniper_EvaluateShot( int hit )
  455. {
  456. if ( !NPC->enemy )
  457. {
  458. return qfalse;
  459. }
  460. gentity_t *hitEnt = &g_entities[hit];
  461. if ( hit == NPC->enemy->s.number
  462. || ( hitEnt && hitEnt->client && hitEnt->client->playerTeam == NPC->client->enemyTeam )
  463. || ( hitEnt && hitEnt->takedamage && ((hitEnt->svFlags&SVF_GLASS_BRUSH)||hitEnt->health < 40||NPC->s.weapon == WP_EMPLACED_GUN) )
  464. || ( hitEnt && (hitEnt->svFlags&SVF_GLASS_BRUSH)) )
  465. {//can hit enemy or will hit glass, so shoot anyway
  466. return qtrue;
  467. }
  468. return qfalse;
  469. }
  470. void Sniper_FaceEnemy( void )
  471. {
  472. //FIXME: the ones behind kill holes are facing some arbitrary direction and not firing
  473. //FIXME: If actually trying to hit enemy, don't fire unless enemy is at least in front of me?
  474. //FIXME: need to give designers option to make them not miss first few shots
  475. if ( NPC->enemy )
  476. {
  477. vec3_t muzzle, target, angles, forward, right, up;
  478. //Get the positions
  479. AngleVectors( NPC->client->ps.viewangles, forward, right, up );
  480. CalcMuzzlePoint( NPC, forward, right, up, muzzle, 0 );
  481. //CalcEntitySpot( NPC, SPOT_WEAPON, muzzle );
  482. CalcEntitySpot( NPC->enemy, SPOT_ORIGIN, target );
  483. if ( enemyDist > 65536 && NPCInfo->stats.aim < 5 )//is 256 squared, was 16384 (128*128)
  484. {
  485. if ( NPC->count < (5-NPCInfo->stats.aim) )
  486. {//miss a few times first
  487. if ( shoot && TIMER_Done( NPC, "attackDelay" ) && level.time >= NPCInfo->shotTime )
  488. {//ready to fire again
  489. qboolean aimError = qfalse;
  490. qboolean hit = qtrue;
  491. int tryMissCount = 0;
  492. trace_t trace;
  493. GetAnglesForDirection( muzzle, target, angles );
  494. AngleVectors( angles, forward, right, up );
  495. while ( hit && tryMissCount < 10 )
  496. {
  497. tryMissCount++;
  498. if ( !Q_irand( 0, 1 ) )
  499. {
  500. aimError = qtrue;
  501. if ( !Q_irand( 0, 1 ) )
  502. {
  503. VectorMA( target, NPC->enemy->maxs[2]*Q_flrand(1.5, 4), right, target );
  504. }
  505. else
  506. {
  507. VectorMA( target, NPC->enemy->mins[2]*Q_flrand(1.5, 4), right, target );
  508. }
  509. }
  510. if ( !aimError || !Q_irand( 0, 1 ) )
  511. {
  512. if ( !Q_irand( 0, 1 ) )
  513. {
  514. VectorMA( target, NPC->enemy->maxs[2]*Q_flrand(1.5, 4), up, target );
  515. }
  516. else
  517. {
  518. VectorMA( target, NPC->enemy->mins[2]*Q_flrand(1.5, 4), up, target );
  519. }
  520. }
  521. gi.trace( &trace, muzzle, vec3_origin, vec3_origin, target, NPC->s.number, MASK_SHOT );
  522. hit = Sniper_EvaluateShot( trace.entityNum );
  523. }
  524. NPC->count++;
  525. }
  526. else
  527. {
  528. if ( !enemyLOS )
  529. {
  530. NPC_UpdateAngles( qtrue, qtrue );
  531. return;
  532. }
  533. }
  534. }
  535. else
  536. {//based on distance, aim value, difficulty and enemy movement, miss
  537. //FIXME: incorporate distance as a factor?
  538. int missFactor = 8-(NPCInfo->stats.aim+g_spskill->integer) * 3;
  539. if ( missFactor > ENEMY_POS_LAG_STEPS )
  540. {
  541. missFactor = ENEMY_POS_LAG_STEPS;
  542. }
  543. else if ( missFactor < 0 )
  544. {//???
  545. missFactor = 0 ;
  546. }
  547. VectorCopy( NPCInfo->enemyLaggedPos[missFactor], target );
  548. }
  549. GetAnglesForDirection( muzzle, target, angles );
  550. }
  551. else
  552. {
  553. target[2] += Q_flrand( 0, NPC->enemy->maxs[2] );
  554. //CalcEntitySpot( NPC->enemy, SPOT_HEAD_LEAN, target );
  555. GetAnglesForDirection( muzzle, target, angles );
  556. }
  557. NPCInfo->desiredYaw = AngleNormalize360( angles[YAW] );
  558. NPCInfo->desiredPitch = AngleNormalize360( angles[PITCH] );
  559. }
  560. NPC_UpdateAngles( qtrue, qtrue );
  561. }
  562. void Sniper_UpdateEnemyPos( void )
  563. {
  564. int index;
  565. for ( int i = MAX_ENEMY_POS_LAG-ENEMY_POS_LAG_INTERVAL; i >= 0; i -= ENEMY_POS_LAG_INTERVAL )
  566. {
  567. index = i/ENEMY_POS_LAG_INTERVAL;
  568. if ( !index )
  569. {
  570. CalcEntitySpot( NPC->enemy, SPOT_HEAD_LEAN, NPCInfo->enemyLaggedPos[index] );
  571. NPCInfo->enemyLaggedPos[index][2] -= Q_flrand( 2, 16 );
  572. }
  573. else
  574. {
  575. VectorCopy( NPCInfo->enemyLaggedPos[index-1], NPCInfo->enemyLaggedPos[index] );
  576. }
  577. }
  578. }
  579. /*
  580. -------------------------
  581. NPC_BSSniper_Attack
  582. -------------------------
  583. */
  584. void Sniper_StartHide( void )
  585. {
  586. int duckTime = Q_irand( 2000, 5000 );
  587. TIMER_Set( NPC, "duck", duckTime );
  588. if ( NPC->client->NPC_class == CLASS_SABOTEUR )
  589. {
  590. Saboteur_Cloak( NPC );
  591. }
  592. TIMER_Set( NPC, "watch", 500 );
  593. TIMER_Set( NPC, "attackDelay", duckTime + Q_irand( 500, 2000 ) );
  594. }
  595. void NPC_BSSniper_Attack( void )
  596. {
  597. //Don't do anything if we're hurt
  598. if ( NPC->painDebounceTime > level.time )
  599. {
  600. NPC_UpdateAngles( qtrue, qtrue );
  601. return;
  602. }
  603. //NPC_CheckEnemy( qtrue, qfalse );
  604. //If we don't have an enemy, just idle
  605. if ( NPC_CheckEnemyExt() == qfalse )//!NPC->enemy )//
  606. {
  607. NPC_BSSniper_Patrol();//FIXME: or patrol?
  608. return;
  609. }
  610. if ( TIMER_Done( NPC, "flee" ) && NPC_CheckForDanger( NPC_CheckAlertEvents( qtrue, qtrue, -1, qfalse, AEL_DANGER ) ) )
  611. {//going to run
  612. NPC_UpdateAngles( qtrue, qtrue );
  613. return;
  614. }
  615. if ( !NPC->enemy )
  616. {//WTF? somehow we lost our enemy?
  617. NPC_BSSniper_Patrol();//FIXME: or patrol?
  618. return;
  619. }
  620. enemyLOS = enemyCS = qfalse;
  621. move = qtrue;
  622. faceEnemy = qfalse;
  623. shoot = qfalse;
  624. enemyDist = DistanceSquared( NPC->currentOrigin, NPC->enemy->currentOrigin );
  625. if ( enemyDist < 16384 )//128 squared
  626. {//too close, so switch to primary fire
  627. if ( NPC->client->ps.weapon == WP_DISRUPTOR
  628. || NPC->client->ps.weapon == WP_TUSKEN_RIFLE )
  629. {//sniping... should be assumed
  630. if ( NPCInfo->scriptFlags & SCF_ALT_FIRE )
  631. {//use primary fire
  632. trace_t trace;
  633. gi.trace ( &trace, NPC->enemy->currentOrigin, NPC->enemy->mins, NPC->enemy->maxs, NPC->currentOrigin, NPC->enemy->s.number, NPC->enemy->clipmask );
  634. if ( !trace.allsolid && !trace.startsolid && (trace.fraction == 1.0 || trace.entityNum == NPC->s.number ) )
  635. {//he can get right to me
  636. NPCInfo->scriptFlags &= ~SCF_ALT_FIRE;
  637. //reset fire-timing variables
  638. NPC_ChangeWeapon( NPC->client->ps.weapon );
  639. NPC_UpdateAngles( qtrue, qtrue );
  640. return;
  641. }
  642. }
  643. //FIXME: switch back if he gets far away again?
  644. }
  645. }
  646. else if ( enemyDist > 65536 )//256 squared
  647. {
  648. if ( NPC->client->ps.weapon == WP_DISRUPTOR
  649. || NPC->client->ps.weapon == WP_TUSKEN_RIFLE )
  650. {//sniping... should be assumed
  651. if ( !(NPCInfo->scriptFlags&SCF_ALT_FIRE) )
  652. {//use primary fire
  653. NPCInfo->scriptFlags |= SCF_ALT_FIRE;
  654. //reset fire-timing variables
  655. NPC_ChangeWeapon( NPC->client->ps.weapon );
  656. NPC_UpdateAngles( qtrue, qtrue );
  657. return;
  658. }
  659. }
  660. }
  661. Sniper_UpdateEnemyPos();
  662. //can we see our target?
  663. if ( NPC_ClearLOS( NPC->enemy ) )//|| (NPCInfo->stats.aim >= 5 && gi.inPVS( NPC->client->renderInfo.eyePoint, NPC->enemy->currentOrigin )) )
  664. {
  665. NPCInfo->enemyLastSeenTime = level.time;
  666. VectorCopy( NPC->enemy->currentOrigin, NPCInfo->enemyLastSeenLocation );
  667. enemyLOS = qtrue;
  668. float maxShootDist = NPC_MaxDistSquaredForWeapon();
  669. if ( enemyDist < maxShootDist )
  670. {
  671. vec3_t fwd, right, up, muzzle, end;
  672. trace_t tr;
  673. AngleVectors( NPC->client->ps.viewangles, fwd, right, up );
  674. CalcMuzzlePoint( NPC, fwd, right, up, muzzle, 0 );
  675. VectorMA( muzzle, 8192, fwd, end );
  676. gi.trace ( &tr, muzzle, NULL, NULL, end, NPC->s.number, MASK_SHOT, G2_RETURNONHIT, 0 );
  677. int hit = tr.entityNum;
  678. //can we shoot our target?
  679. if ( Sniper_EvaluateShot( hit ) )
  680. {
  681. enemyCS = qtrue;
  682. }
  683. }
  684. }
  685. /*
  686. else if ( gi.inPVS( NPC->enemy->currentOrigin, NPC->currentOrigin ) )
  687. {
  688. NPCInfo->enemyLastSeenTime = level.time;
  689. faceEnemy = qtrue;
  690. }
  691. */
  692. if ( enemyLOS )
  693. {//FIXME: no need to face enemy if we're moving to some other goal and he's too far away to shoot?
  694. faceEnemy = qtrue;
  695. }
  696. if ( !TIMER_Done( NPC, "taunting" ) )
  697. {
  698. move = qfalse;
  699. shoot = qfalse;
  700. }
  701. else if ( enemyCS )
  702. {
  703. shoot = qtrue;
  704. }
  705. else if ( level.time - NPCInfo->enemyLastSeenTime > 3000 )
  706. {//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
  707. Sniper_ResolveBlockedShot();
  708. }
  709. else if ( NPC->client->ps.weapon == WP_TUSKEN_RIFLE && !Q_irand( 0, 100 ) )
  710. {//start a taunt
  711. NPC_Tusken_Taunt();
  712. TIMER_Set( NPC, "duck", -1 );
  713. move = qfalse;
  714. }
  715. //Check for movement to take care of
  716. Sniper_CheckMoveState();
  717. //See if we should override shooting decision with any special considerations
  718. Sniper_CheckFireState();
  719. if ( move )
  720. {//move toward goal
  721. if ( NPCInfo->goalEntity )//&& ( NPCInfo->goalEntity != NPC->enemy || enemyDist > 10000 ) )//100 squared
  722. {
  723. move = Sniper_Move();
  724. }
  725. else
  726. {
  727. move = qfalse;
  728. }
  729. }
  730. if ( !move )
  731. {
  732. if ( !TIMER_Done( NPC, "duck" ) )
  733. {
  734. if ( TIMER_Done( NPC, "watch" ) )
  735. {//not while watching
  736. ucmd.upmove = -127;
  737. if ( NPC->client->NPC_class == CLASS_SABOTEUR )
  738. {
  739. Saboteur_Cloak( NPC );
  740. }
  741. }
  742. }
  743. //FIXME: what about leaning?
  744. //FIXME: also, when stop ducking, start looking, if enemy can see me, chance of ducking back down again
  745. }
  746. else
  747. {//stop ducking!
  748. TIMER_Set( NPC, "duck", -1 );
  749. if ( NPC->client->NPC_class == CLASS_SABOTEUR )
  750. {
  751. Saboteur_Decloak( NPC );
  752. }
  753. }
  754. if ( TIMER_Done( NPC, "duck" )
  755. && TIMER_Done( NPC, "watch" )
  756. && (TIMER_Get( NPC, "attackDelay" )-level.time) > 1000
  757. && NPC->attackDebounceTime < level.time )
  758. {
  759. if ( enemyLOS && (NPCInfo->scriptFlags&SCF_ALT_FIRE) )
  760. {
  761. if ( NPC->fly_sound_debounce_time < level.time )
  762. {
  763. NPC->fly_sound_debounce_time = level.time + 2000;
  764. }
  765. }
  766. }
  767. if ( !faceEnemy )
  768. {//we want to face in the dir we're running
  769. if ( move )
  770. {//don't run away and shoot
  771. NPCInfo->desiredYaw = NPCInfo->lastPathAngles[YAW];
  772. NPCInfo->desiredPitch = 0;
  773. shoot = qfalse;
  774. }
  775. NPC_UpdateAngles( qtrue, qtrue );
  776. }
  777. else// if ( faceEnemy )
  778. {//face the enemy
  779. Sniper_FaceEnemy();
  780. }
  781. if ( NPCInfo->scriptFlags&SCF_DONT_FIRE )
  782. {
  783. shoot = qfalse;
  784. }
  785. //FIXME: don't shoot right away!
  786. if ( shoot )
  787. {//try to shoot if it's time
  788. if ( TIMER_Done( NPC, "attackDelay" ) )
  789. {
  790. WeaponThink( qtrue );
  791. if ( ucmd.buttons&(BUTTON_ATTACK|BUTTON_ALT_ATTACK) )
  792. {
  793. G_SoundOnEnt( NPC, CHAN_WEAPON, "sound/null.wav" );
  794. }
  795. //took a shot, now hide
  796. if ( !(NPC->spawnflags&SPF_NO_HIDE) && !Q_irand( 0, 1 ) )
  797. {
  798. //FIXME: do this if in combat point and combat point has duck-type cover... also handle lean-type cover
  799. Sniper_StartHide();
  800. }
  801. else
  802. {
  803. TIMER_Set( NPC, "attackDelay", NPCInfo->shotTime-level.time );
  804. }
  805. }
  806. }
  807. }
  808. void NPC_BSSniper_Default( void )
  809. {
  810. if( !NPC->enemy )
  811. {//don't have an enemy, look for one
  812. NPC_BSSniper_Patrol();
  813. }
  814. else//if ( NPC->enemy )
  815. {//have an enemy
  816. NPC_BSSniper_Attack();
  817. }
  818. }