AI_Sentry.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570
  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. gentity_t *CreateMissile( vec3_t org, vec3_t dir, float vel, int life, gentity_t *owner, qboolean altFire = qfalse );
  6. extern gitem_t *FindItemForAmmo( ammo_t ammo );
  7. #define MIN_DISTANCE 256
  8. #define MIN_DISTANCE_SQR ( MIN_DISTANCE * MIN_DISTANCE )
  9. #define SENTRY_FORWARD_BASE_SPEED 10
  10. #define SENTRY_FORWARD_MULTIPLIER 5
  11. #define SENTRY_VELOCITY_DECAY 0.85f
  12. #define SENTRY_STRAFE_VEL 256
  13. #define SENTRY_STRAFE_DIS 200
  14. #define SENTRY_UPWARD_PUSH 32
  15. #define SENTRY_HOVER_HEIGHT 24
  16. //Local state enums
  17. enum
  18. {
  19. LSTATE_NONE = 0,
  20. LSTATE_ASLEEP,
  21. LSTATE_WAKEUP,
  22. LSTATE_ACTIVE,
  23. LSTATE_POWERING_UP,
  24. LSTATE_ATTACKING,
  25. };
  26. /*
  27. -------------------------
  28. NPC_Sentry_Precache
  29. -------------------------
  30. */
  31. void NPC_Sentry_Precache(void)
  32. {
  33. G_SoundIndex( "sound/chars/sentry/misc/sentry_explo" );
  34. G_SoundIndex( "sound/chars/sentry/misc/sentry_pain" );
  35. G_SoundIndex( "sound/chars/sentry/misc/sentry_shield_open" );
  36. G_SoundIndex( "sound/chars/sentry/misc/sentry_shield_close" );
  37. G_SoundIndex( "sound/chars/sentry/misc/sentry_hover_1_lp" );
  38. G_SoundIndex( "sound/chars/sentry/misc/sentry_hover_2_lp" );
  39. for ( int i = 1; i < 4; i++)
  40. {
  41. G_SoundIndex( va( "sound/chars/sentry/misc/talk%d", i ) );
  42. }
  43. G_EffectIndex( "bryar/muzzle_flash");
  44. G_EffectIndex( "env/med_explode");
  45. RegisterItem( FindItemForAmmo( AMMO_BLASTER ));
  46. }
  47. /*
  48. ================
  49. sentry_use
  50. ================
  51. */
  52. void sentry_use( gentity_t *self, gentity_t *other, gentity_t *activator)
  53. {
  54. G_ActivateBehavior(self,BSET_USE);
  55. self->flags &= ~FL_SHIELDED;
  56. NPC_SetAnim( self, SETANIM_BOTH, BOTH_POWERUP1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
  57. // self->NPC->localState = LSTATE_WAKEUP;
  58. self->NPC->localState = LSTATE_ACTIVE;
  59. }
  60. /*
  61. -------------------------
  62. NPC_Sentry_Pain
  63. -------------------------
  64. */
  65. void NPC_Sentry_Pain( gentity_t *self, gentity_t *inflictor, gentity_t *other, const vec3_t point, int damage, int mod,int hitLoc )
  66. {
  67. NPC_Pain( self, inflictor, other, point, damage, mod );
  68. if ( mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT )
  69. {
  70. self->NPC->burstCount = 0;
  71. TIMER_Set( self, "attackDelay", Q_irand( 9000, 12000) );
  72. self->flags |= FL_SHIELDED;
  73. NPC_SetAnim( self, SETANIM_BOTH, BOTH_FLY_SHIELDED, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
  74. G_SoundOnEnt( self, CHAN_AUTO, "sound/chars/sentry/misc/sentry_pain" );
  75. self->NPC->localState = LSTATE_ACTIVE;
  76. }
  77. // You got hit, go after the enemy
  78. // if (self->NPC->localState == LSTATE_ASLEEP)
  79. // {
  80. // G_Sound( self, G_SoundIndex("sound/chars/sentry/misc/shieldsopen.wav"));
  81. //
  82. // self->flags &= ~FL_SHIELDED;
  83. // NPC_SetAnim( self, SETANIM_BOTH, BOTH_POWERUP1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
  84. // self->NPC->localState = LSTATE_WAKEUP;
  85. // }
  86. }
  87. /*
  88. -------------------------
  89. Sentry_Fire
  90. -------------------------
  91. */
  92. void Sentry_Fire (void)
  93. {
  94. vec3_t muzzle;
  95. static vec3_t forward, vright, up;
  96. gentity_t *missile;
  97. mdxaBone_t boltMatrix;
  98. int bolt;
  99. NPC->flags &= ~FL_SHIELDED;
  100. if ( NPCInfo->localState == LSTATE_POWERING_UP )
  101. {
  102. if ( TIMER_Done( NPC, "powerup" ))
  103. {
  104. NPCInfo->localState = LSTATE_ATTACKING;
  105. NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_ATTACK1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
  106. }
  107. else
  108. {
  109. // can't do anything right now
  110. return;
  111. }
  112. }
  113. else if ( NPCInfo->localState == LSTATE_ACTIVE )
  114. {
  115. NPCInfo->localState = LSTATE_POWERING_UP;
  116. G_SoundOnEnt( NPC, CHAN_AUTO, "sound/chars/sentry/misc/sentry_shield_open" );
  117. NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_POWERUP1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
  118. TIMER_Set( NPC, "powerup", 250 );
  119. return;
  120. }
  121. else if ( NPCInfo->localState != LSTATE_ATTACKING )
  122. {
  123. // bad because we are uninitialized
  124. NPCInfo->localState = LSTATE_ACTIVE;
  125. return;
  126. }
  127. // Which muzzle to fire from?
  128. int which = NPCInfo->burstCount % 3;
  129. switch( which )
  130. {
  131. case 0:
  132. bolt = NPC->genericBolt1;
  133. break;
  134. case 1:
  135. bolt = NPC->genericBolt2;
  136. break;
  137. case 2:
  138. default:
  139. bolt = NPC->genericBolt3;
  140. }
  141. gi.G2API_GetBoltMatrix( NPC->ghoul2, NPC->playerModel,
  142. bolt,
  143. &boltMatrix, NPC->currentAngles, NPC->currentOrigin, (cg.time?cg.time:level.time),
  144. NULL, NPC->s.modelScale );
  145. gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, muzzle );
  146. AngleVectors( NPC->currentAngles, forward, vright, up );
  147. // G_Sound( NPC, G_SoundIndex("sound/chars/sentry/misc/shoot.wav"));
  148. G_PlayEffect( "bryar/muzzle_flash", muzzle, forward );
  149. missile = CreateMissile( muzzle, forward, 1600, 10000, NPC );
  150. missile->classname = "bryar_proj";
  151. missile->s.weapon = WP_BRYAR_PISTOL;
  152. missile->dflags = DAMAGE_DEATH_KNOCKBACK;
  153. missile->methodOfDeath = MOD_ENERGY;
  154. missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
  155. NPCInfo->burstCount++;
  156. NPC->attackDebounceTime = level.time + 50;
  157. missile->damage = 5;
  158. // now scale for difficulty
  159. if ( g_spskill->integer == 0 )
  160. {
  161. NPC->attackDebounceTime += 200;
  162. missile->damage = 1;
  163. }
  164. else if ( g_spskill->integer == 1 )
  165. {
  166. NPC->attackDebounceTime += 100;
  167. missile->damage = 3;
  168. }
  169. }
  170. /*
  171. -------------------------
  172. Sentry_MaintainHeight
  173. -------------------------
  174. */
  175. void Sentry_MaintainHeight( void )
  176. {
  177. float dif;
  178. NPC->s.loopSound = G_SoundIndex( "sound/chars/sentry/misc/sentry_hover_1_lp" );
  179. // Update our angles regardless
  180. NPC_UpdateAngles( qtrue, qtrue );
  181. // If we have an enemy, we should try to hover at about enemy eye level
  182. if ( NPC->enemy )
  183. {
  184. // Find the height difference
  185. dif = (NPC->enemy->currentOrigin[2]+NPC->enemy->maxs[2]) - NPC->currentOrigin[2];
  186. // cap to prevent dramatic height shifts
  187. if ( fabs( dif ) > 8 )
  188. {
  189. if ( fabs( dif ) > SENTRY_HOVER_HEIGHT )
  190. {
  191. dif = ( dif < 0 ? -24 : 24 );
  192. }
  193. NPC->client->ps.velocity[2] = (NPC->client->ps.velocity[2]+dif)/2;
  194. }
  195. }
  196. else
  197. {
  198. gentity_t *goal = NULL;
  199. if ( NPCInfo->goalEntity ) // Is there a goal?
  200. {
  201. goal = NPCInfo->goalEntity;
  202. }
  203. else
  204. {
  205. goal = NPCInfo->lastGoalEntity;
  206. }
  207. if (goal)
  208. {
  209. dif = goal->currentOrigin[2] - NPC->currentOrigin[2];
  210. if ( fabs( dif ) > SENTRY_HOVER_HEIGHT )
  211. {
  212. ucmd.upmove = ( ucmd.upmove < 0 ? -4 : 4 );
  213. }
  214. else
  215. {
  216. if ( NPC->client->ps.velocity[2] )
  217. {
  218. NPC->client->ps.velocity[2] *= SENTRY_VELOCITY_DECAY;
  219. if ( fabs( NPC->client->ps.velocity[2] ) < 2 )
  220. {
  221. NPC->client->ps.velocity[2] = 0;
  222. }
  223. }
  224. }
  225. }
  226. // Apply friction to Z
  227. else if ( NPC->client->ps.velocity[2] )
  228. {
  229. NPC->client->ps.velocity[2] *= SENTRY_VELOCITY_DECAY;
  230. if ( fabs( NPC->client->ps.velocity[2] ) < 1 )
  231. {
  232. NPC->client->ps.velocity[2] = 0;
  233. }
  234. }
  235. }
  236. // Apply friction
  237. if ( NPC->client->ps.velocity[0] )
  238. {
  239. NPC->client->ps.velocity[0] *= SENTRY_VELOCITY_DECAY;
  240. if ( fabs( NPC->client->ps.velocity[0] ) < 1 )
  241. {
  242. NPC->client->ps.velocity[0] = 0;
  243. }
  244. }
  245. if ( NPC->client->ps.velocity[1] )
  246. {
  247. NPC->client->ps.velocity[1] *= SENTRY_VELOCITY_DECAY;
  248. if ( fabs( NPC->client->ps.velocity[1] ) < 1 )
  249. {
  250. NPC->client->ps.velocity[1] = 0;
  251. }
  252. }
  253. NPC_FaceEnemy( qtrue );
  254. }
  255. /*
  256. -------------------------
  257. Sentry_Idle
  258. -------------------------
  259. */
  260. void Sentry_Idle( void )
  261. {
  262. Sentry_MaintainHeight();
  263. // Is he waking up?
  264. if (NPCInfo->localState == LSTATE_WAKEUP)
  265. {
  266. if (NPC->client->ps.torsoAnimTimer<=0)
  267. {
  268. NPCInfo->scriptFlags |= SCF_LOOK_FOR_ENEMIES;
  269. NPCInfo->burstCount = 0;
  270. }
  271. }
  272. else
  273. {
  274. NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_SLEEP1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
  275. NPC->flags |= FL_SHIELDED;
  276. NPC_BSIdle();
  277. }
  278. }
  279. /*
  280. -------------------------
  281. Sentry_Strafe
  282. -------------------------
  283. */
  284. void Sentry_Strafe( void )
  285. {
  286. int dir;
  287. vec3_t end, right;
  288. trace_t tr;
  289. AngleVectors( NPC->client->renderInfo.eyeAngles, NULL, right, NULL );
  290. // Pick a random strafe direction, then check to see if doing a strafe would be
  291. // reasonable valid
  292. dir = ( rand() & 1 ) ? -1 : 1;
  293. VectorMA( NPC->currentOrigin, SENTRY_STRAFE_DIS * dir, right, end );
  294. gi.trace( &tr, NPC->currentOrigin, NULL, NULL, end, NPC->s.number, MASK_SOLID );
  295. // Close enough
  296. if ( tr.fraction > 0.9f )
  297. {
  298. VectorMA( NPC->client->ps.velocity, SENTRY_STRAFE_VEL * dir, right, NPC->client->ps.velocity );
  299. // Add a slight upward push
  300. NPC->client->ps.velocity[2] += SENTRY_UPWARD_PUSH;
  301. // Set the strafe start time so we can do a controlled roll
  302. NPC->fx_time = level.time;
  303. NPCInfo->standTime = level.time + 3000 + random() * 500;
  304. }
  305. }
  306. /*
  307. -------------------------
  308. Sentry_Hunt
  309. -------------------------
  310. */
  311. void Sentry_Hunt( qboolean visible, qboolean advance )
  312. {
  313. float distance, speed;
  314. vec3_t forward;
  315. //If we're not supposed to stand still, pursue the player
  316. if ( NPCInfo->standTime < level.time )
  317. {
  318. // Only strafe when we can see the player
  319. if ( visible )
  320. {
  321. Sentry_Strafe();
  322. return;
  323. }
  324. }
  325. //If we don't want to advance, stop here
  326. if ( !advance && visible )
  327. return;
  328. //Only try and navigate if the player is visible
  329. if ( visible == qfalse )
  330. {
  331. // Move towards our goal
  332. NPCInfo->goalEntity = NPC->enemy;
  333. NPCInfo->goalRadius = 12;
  334. NPC_MoveToGoal(qtrue);
  335. return;
  336. }
  337. else
  338. {
  339. VectorSubtract( NPC->enemy->currentOrigin, NPC->currentOrigin, forward );
  340. distance = VectorNormalize( forward );
  341. }
  342. speed = SENTRY_FORWARD_BASE_SPEED + SENTRY_FORWARD_MULTIPLIER * g_spskill->integer;
  343. VectorMA( NPC->client->ps.velocity, speed, forward, NPC->client->ps.velocity );
  344. }
  345. /*
  346. -------------------------
  347. Sentry_RangedAttack
  348. -------------------------
  349. */
  350. void Sentry_RangedAttack( qboolean visible, qboolean advance )
  351. {
  352. if ( TIMER_Done( NPC, "attackDelay" ) && NPC->attackDebounceTime < level.time && visible ) // Attack?
  353. {
  354. if ( NPCInfo->burstCount > 6 )
  355. {
  356. if ( !NPC->fly_sound_debounce_time )
  357. {//delay closing down to give the player an opening
  358. NPC->fly_sound_debounce_time = level.time + Q_irand( 500, 2000 );
  359. }
  360. else if ( NPC->fly_sound_debounce_time < level.time )
  361. {
  362. NPCInfo->localState = LSTATE_ACTIVE;
  363. NPC->fly_sound_debounce_time = NPCInfo->burstCount = 0;
  364. TIMER_Set( NPC, "attackDelay", Q_irand( 2000, 3500) );
  365. NPC->flags |= FL_SHIELDED;
  366. NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_FLY_SHIELDED, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
  367. G_SoundOnEnt( NPC, CHAN_AUTO, "sound/chars/sentry/misc/sentry_shield_close" );
  368. }
  369. }
  370. else
  371. {
  372. Sentry_Fire();
  373. }
  374. }
  375. if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES )
  376. {
  377. Sentry_Hunt( visible, advance );
  378. }
  379. }
  380. /*
  381. -------------------------
  382. Sentry_AttackDecision
  383. -------------------------
  384. */
  385. void Sentry_AttackDecision( void )
  386. {
  387. // Always keep a good height off the ground
  388. Sentry_MaintainHeight();
  389. NPC->s.loopSound = G_SoundIndex( "sound/chars/sentry/misc/sentry_hover_2_lp" );
  390. //randomly talk
  391. if ( TIMER_Done(NPC,"patrolNoise") )
  392. {
  393. if (TIMER_Done(NPC,"angerNoise"))
  394. {
  395. G_SoundOnEnt( NPC, CHAN_AUTO, va("sound/chars/sentry/misc/talk%d", Q_irand(1, 3)) );
  396. TIMER_Set( NPC, "patrolNoise", Q_irand( 4000, 10000 ) );
  397. }
  398. }
  399. // He's dead.
  400. if (NPC->enemy->health<1)
  401. {
  402. NPC->enemy = NULL;
  403. Sentry_Idle();
  404. return;
  405. }
  406. // If we don't have an enemy, just idle
  407. if ( NPC_CheckEnemyExt() == qfalse )
  408. {
  409. Sentry_Idle();
  410. return;
  411. }
  412. // Rate our distance to the target and visibilty
  413. float distance = (int) DistanceHorizontalSquared( NPC->currentOrigin, NPC->enemy->currentOrigin );
  414. qboolean visible = NPC_ClearLOS( NPC->enemy );
  415. qboolean advance = (qboolean)(distance > MIN_DISTANCE_SQR);
  416. // If we cannot see our target, move to see it
  417. if ( visible == qfalse )
  418. {
  419. if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES )
  420. {
  421. Sentry_Hunt( visible, advance );
  422. return;
  423. }
  424. }
  425. NPC_FaceEnemy( qtrue );
  426. Sentry_RangedAttack( visible, advance );
  427. }
  428. qboolean NPC_CheckPlayerTeamStealth( void );
  429. /*
  430. -------------------------
  431. NPC_Sentry_Patrol
  432. -------------------------
  433. */
  434. void NPC_Sentry_Patrol( void )
  435. {
  436. Sentry_MaintainHeight();
  437. //If we have somewhere to go, then do that
  438. if (!NPC->enemy)
  439. {
  440. if ( NPC_CheckPlayerTeamStealth() )
  441. {
  442. //NPC_AngerSound();
  443. NPC_UpdateAngles( qtrue, qtrue );
  444. return;
  445. }
  446. if ( UpdateGoal() )
  447. {
  448. //start loop sound once we move
  449. ucmd.buttons |= BUTTON_WALKING;
  450. NPC_MoveToGoal( qtrue );
  451. }
  452. //randomly talk
  453. if (TIMER_Done(NPC,"patrolNoise"))
  454. {
  455. G_SoundOnEnt( NPC, CHAN_AUTO, va("sound/chars/sentry/misc/talk%d", Q_irand(1, 3)) );
  456. TIMER_Set( NPC, "patrolNoise", Q_irand( 2000, 4000 ) );
  457. }
  458. }
  459. NPC_UpdateAngles( qtrue, qtrue );
  460. }
  461. /*
  462. -------------------------
  463. NPC_BSSentry_Default
  464. -------------------------
  465. */
  466. void NPC_BSSentry_Default( void )
  467. {
  468. if ( NPC->targetname )
  469. {
  470. NPC->e_UseFunc = useF_sentry_use;
  471. }
  472. if (( NPC->enemy ) && (NPCInfo->localState != LSTATE_WAKEUP))
  473. {
  474. // Don't attack if waking up or if no enemy
  475. Sentry_AttackDecision();
  476. }
  477. else if ( NPCInfo->scriptFlags & SCF_LOOK_FOR_ENEMIES )
  478. {
  479. NPC_Sentry_Patrol();
  480. }
  481. else
  482. {
  483. Sentry_Idle();
  484. }
  485. }