AI_Seeker.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539
  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. extern void NPC_BSST_Patrol( void );
  6. extern void Boba_FireDecide( void );
  7. extern gentity_t *CreateMissile( vec3_t org, vec3_t dir, float vel, int life, gentity_t *owner, qboolean altFire = qfalse );
  8. void Seeker_Strafe( void );
  9. #define VELOCITY_DECAY 0.7f
  10. #define MIN_MELEE_RANGE 320
  11. #define MIN_MELEE_RANGE_SQR ( MIN_MELEE_RANGE * MIN_MELEE_RANGE )
  12. #define MIN_DISTANCE 80
  13. #define MIN_DISTANCE_SQR ( MIN_DISTANCE * MIN_DISTANCE )
  14. #define SEEKER_STRAFE_VEL 100
  15. #define SEEKER_STRAFE_DIS 200
  16. #define SEEKER_UPWARD_PUSH 32
  17. #define SEEKER_FORWARD_BASE_SPEED 10
  18. #define SEEKER_FORWARD_MULTIPLIER 2
  19. #define SEEKER_SEEK_RADIUS 1024
  20. //------------------------------------
  21. void NPC_Seeker_Precache(void)
  22. {
  23. G_SoundIndex("sound/chars/seeker/misc/fire.wav");
  24. G_SoundIndex( "sound/chars/seeker/misc/hiss.wav");
  25. G_EffectIndex( "env/small_explode");
  26. }
  27. //------------------------------------
  28. void NPC_Seeker_Pain( gentity_t *self, gentity_t *inflictor, gentity_t *other, const vec3_t point, int damage, int mod,int hitLoc )
  29. {
  30. if ( !(self->svFlags & SVF_CUSTOM_GRAVITY ))
  31. {//void G_Damage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker, vec3_t dir, vec3_t point, int damage, int dflags, int mod, int hitLoc=HL_NONE );
  32. G_Damage( self, NULL, NULL, vec3_origin, (float*)vec3_origin, 999, 0, MOD_FALLING );
  33. }
  34. SaveNPCGlobals();
  35. SetNPCGlobals( self );
  36. Seeker_Strafe();
  37. RestoreNPCGlobals();
  38. NPC_Pain( self, inflictor, other, point, damage, mod );
  39. }
  40. //------------------------------------
  41. void Seeker_MaintainHeight( void )
  42. {
  43. float dif;
  44. // Update our angles regardless
  45. NPC_UpdateAngles( qtrue, qtrue );
  46. // If we have an enemy, we should try to hover at or a little below enemy eye level
  47. if ( NPC->enemy )
  48. {
  49. if (TIMER_Done( NPC, "heightChange" ))
  50. {
  51. TIMER_Set( NPC,"heightChange",Q_irand( 1000, 3000 ));
  52. // Find the height difference
  53. dif = (NPC->enemy->currentOrigin[2] + Q_flrand( NPC->enemy->maxs[2]/2, NPC->enemy->maxs[2]+8 )) - NPC->currentOrigin[2];
  54. float difFactor = 1.0f;
  55. if ( NPC->client->NPC_class == CLASS_BOBAFETT )
  56. {
  57. if ( TIMER_Done( NPC, "flameTime" ) )
  58. {
  59. difFactor = 10.0f;
  60. }
  61. }
  62. // cap to prevent dramatic height shifts
  63. if ( fabs( dif ) > 2*difFactor )
  64. {
  65. if ( fabs( dif ) > 24*difFactor )
  66. {
  67. dif = ( dif < 0 ? -24*difFactor : 24*difFactor );
  68. }
  69. NPC->client->ps.velocity[2] = (NPC->client->ps.velocity[2]+dif)/2;
  70. }
  71. if ( NPC->client->NPC_class == CLASS_BOBAFETT )
  72. {
  73. NPC->client->ps.velocity[2] *= Q_flrand( 0.85f, 3.0f );
  74. }
  75. }
  76. }
  77. else
  78. {
  79. gentity_t *goal = NULL;
  80. if ( NPCInfo->goalEntity ) // Is there a goal?
  81. {
  82. goal = NPCInfo->goalEntity;
  83. }
  84. else
  85. {
  86. goal = NPCInfo->lastGoalEntity;
  87. }
  88. if ( goal )
  89. {
  90. dif = goal->currentOrigin[2] - NPC->currentOrigin[2];
  91. if ( fabs( dif ) > 24 )
  92. {
  93. ucmd.upmove = ( ucmd.upmove < 0 ? -4 : 4 );
  94. }
  95. else
  96. {
  97. if ( NPC->client->ps.velocity[2] )
  98. {
  99. NPC->client->ps.velocity[2] *= VELOCITY_DECAY;
  100. if ( fabs( NPC->client->ps.velocity[2] ) < 2 )
  101. {
  102. NPC->client->ps.velocity[2] = 0;
  103. }
  104. }
  105. }
  106. }
  107. }
  108. // Apply friction
  109. if ( NPC->client->ps.velocity[0] )
  110. {
  111. NPC->client->ps.velocity[0] *= VELOCITY_DECAY;
  112. if ( fabs( NPC->client->ps.velocity[0] ) < 1 )
  113. {
  114. NPC->client->ps.velocity[0] = 0;
  115. }
  116. }
  117. if ( NPC->client->ps.velocity[1] )
  118. {
  119. NPC->client->ps.velocity[1] *= VELOCITY_DECAY;
  120. if ( fabs( NPC->client->ps.velocity[1] ) < 1 )
  121. {
  122. NPC->client->ps.velocity[1] = 0;
  123. }
  124. }
  125. }
  126. //------------------------------------
  127. void Seeker_Strafe( void )
  128. {
  129. int side;
  130. vec3_t end, right, dir;
  131. trace_t tr;
  132. if ( random() > 0.7f || !NPC->enemy || !NPC->enemy->client )
  133. {
  134. // Do a regular style strafe
  135. AngleVectors( NPC->client->renderInfo.eyeAngles, NULL, right, NULL );
  136. // Pick a random strafe direction, then check to see if doing a strafe would be
  137. // reasonably valid
  138. side = ( rand() & 1 ) ? -1 : 1;
  139. VectorMA( NPC->currentOrigin, SEEKER_STRAFE_DIS * side, right, end );
  140. gi.trace( &tr, NPC->currentOrigin, NULL, NULL, end, NPC->s.number, MASK_SOLID );
  141. // Close enough
  142. if ( tr.fraction > 0.9f )
  143. {
  144. float vel = SEEKER_STRAFE_VEL;
  145. float upPush = SEEKER_UPWARD_PUSH;
  146. if ( NPC->client->NPC_class != CLASS_BOBAFETT )
  147. {
  148. G_Sound( NPC, G_SoundIndex( "sound/chars/seeker/misc/hiss" ));
  149. }
  150. else
  151. {
  152. vel *= 3.0f;
  153. upPush *= 4.0f;
  154. }
  155. VectorMA( NPC->client->ps.velocity, vel*side, right, NPC->client->ps.velocity );
  156. // Add a slight upward push
  157. NPC->client->ps.velocity[2] += upPush;
  158. NPCInfo->standTime = level.time + 1000 + random() * 500;
  159. }
  160. }
  161. else
  162. {
  163. // Do a strafe to try and keep on the side of their enemy
  164. AngleVectors( NPC->enemy->client->renderInfo.eyeAngles, dir, right, NULL );
  165. // Pick a random side
  166. side = ( rand() & 1 ) ? -1 : 1;
  167. float stDis = SEEKER_STRAFE_DIS;
  168. if ( NPC->client->NPC_class == CLASS_BOBAFETT )
  169. {
  170. stDis *= 2.0f;
  171. }
  172. VectorMA( NPC->enemy->currentOrigin, stDis * side, right, end );
  173. // then add a very small bit of random in front of/behind the player action
  174. VectorMA( end, crandom() * 25, dir, end );
  175. gi.trace( &tr, NPC->currentOrigin, NULL, NULL, end, NPC->s.number, MASK_SOLID );
  176. // Close enough
  177. if ( tr.fraction > 0.9f )
  178. {
  179. VectorSubtract( tr.endpos, NPC->currentOrigin, dir );
  180. dir[2] *= 0.25; // do less upward change
  181. float dis = VectorNormalize( dir );
  182. // Try to move the desired enemy side
  183. VectorMA( NPC->client->ps.velocity, dis, dir, NPC->client->ps.velocity );
  184. float upPush = SEEKER_UPWARD_PUSH;
  185. if ( NPC->client->NPC_class != CLASS_BOBAFETT )
  186. {
  187. G_Sound( NPC, G_SoundIndex( "sound/chars/seeker/misc/hiss" ));
  188. }
  189. else
  190. {
  191. upPush *= 4.0f;
  192. }
  193. // Add a slight upward push
  194. NPC->client->ps.velocity[2] += upPush;
  195. NPCInfo->standTime = level.time + 2500 + random() * 500;
  196. }
  197. }
  198. }
  199. //------------------------------------
  200. void Seeker_Hunt( qboolean visible, qboolean advance )
  201. {
  202. float distance, speed;
  203. vec3_t forward;
  204. NPC_FaceEnemy( qtrue );
  205. // If we're not supposed to stand still, pursue the player
  206. if ( NPCInfo->standTime < level.time )
  207. {
  208. // Only strafe when we can see the player
  209. if ( visible )
  210. {
  211. Seeker_Strafe();
  212. return;
  213. }
  214. }
  215. // If we don't want to advance, stop here
  216. if ( advance == qfalse )
  217. {
  218. return;
  219. }
  220. // Only try and navigate if the player is visible
  221. if ( visible == qfalse )
  222. {
  223. // Move towards our goal
  224. NPCInfo->goalEntity = NPC->enemy;
  225. NPCInfo->goalRadius = 24;
  226. NPC_MoveToGoal(qtrue);
  227. return;
  228. }
  229. else
  230. {
  231. VectorSubtract( NPC->enemy->currentOrigin, NPC->currentOrigin, forward );
  232. distance = VectorNormalize( forward );
  233. }
  234. speed = SEEKER_FORWARD_BASE_SPEED + SEEKER_FORWARD_MULTIPLIER * g_spskill->integer;
  235. VectorMA( NPC->client->ps.velocity, speed, forward, NPC->client->ps.velocity );
  236. }
  237. //------------------------------------
  238. void Seeker_Fire( void )
  239. {
  240. vec3_t dir, enemy_org, muzzle;
  241. gentity_t *missile;
  242. CalcEntitySpot( NPC->enemy, SPOT_HEAD, enemy_org );
  243. VectorSubtract( enemy_org, NPC->currentOrigin, dir );
  244. VectorNormalize( dir );
  245. // move a bit forward in the direction we shall shoot in so that the bolt doesn't poke out the other side of the seeker
  246. VectorMA( NPC->currentOrigin, 15, dir, muzzle );
  247. missile = CreateMissile( muzzle, dir, 1000, 10000, NPC );
  248. G_PlayEffect( "blaster/muzzle_flash", NPC->currentOrigin, dir );
  249. missile->classname = "blaster";
  250. missile->s.weapon = WP_BLASTER;
  251. missile->damage = 5;
  252. missile->dflags = DAMAGE_DEATH_KNOCKBACK;
  253. missile->methodOfDeath = MOD_ENERGY;
  254. missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
  255. }
  256. //------------------------------------
  257. void Seeker_Ranged( qboolean visible, qboolean advance )
  258. {
  259. if ( NPC->client->NPC_class != CLASS_BOBAFETT )
  260. {
  261. if ( NPC->count > 0 )
  262. {
  263. if ( TIMER_Done( NPC, "attackDelay" )) // Attack?
  264. {
  265. TIMER_Set( NPC, "attackDelay", Q_irand( 250, 2500 ));
  266. Seeker_Fire();
  267. NPC->count--;
  268. }
  269. }
  270. else
  271. {
  272. // out of ammo, so let it die...give it a push up so it can fall more and blow up on impact
  273. // NPC->client->ps.gravity = 900;
  274. // NPC->svFlags &= ~SVF_CUSTOM_GRAVITY;
  275. // NPC->client->ps.velocity[2] += 16;
  276. G_Damage( NPC, NPC, NPC, NULL, NULL, 999, 0, MOD_UNKNOWN );
  277. }
  278. }
  279. if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES )
  280. {
  281. Seeker_Hunt( visible, advance );
  282. }
  283. }
  284. //------------------------------------
  285. void Seeker_Attack( void )
  286. {
  287. // Always keep a good height off the ground
  288. Seeker_MaintainHeight();
  289. // Rate our distance to the target, and our visibilty
  290. float distance = DistanceHorizontalSquared( NPC->currentOrigin, NPC->enemy->currentOrigin );
  291. qboolean visible = NPC_ClearLOS( NPC->enemy );
  292. qboolean advance = (qboolean)(distance > MIN_DISTANCE_SQR);
  293. if ( NPC->client->NPC_class == CLASS_BOBAFETT )
  294. {
  295. advance = (qboolean)(distance>(200.0f*200.0f));
  296. }
  297. // If we cannot see our target, move to see it
  298. if ( visible == qfalse )
  299. {
  300. if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES )
  301. {
  302. Seeker_Hunt( visible, advance );
  303. return;
  304. }
  305. }
  306. Seeker_Ranged( visible, advance );
  307. }
  308. //------------------------------------
  309. void Seeker_FindEnemy( void )
  310. {
  311. int numFound;
  312. float dis, bestDis = SEEKER_SEEK_RADIUS * SEEKER_SEEK_RADIUS + 1;
  313. vec3_t mins, maxs;
  314. gentity_t *entityList[MAX_GENTITIES], *ent, *best = NULL;
  315. VectorSet( maxs, SEEKER_SEEK_RADIUS, SEEKER_SEEK_RADIUS, SEEKER_SEEK_RADIUS );
  316. VectorScale( maxs, -1, mins );
  317. numFound = gi.EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
  318. for ( int i = 0 ; i < numFound ; i++ )
  319. {
  320. ent = entityList[i];
  321. if ( ent->s.number == NPC->s.number || !ent->client || !ent->NPC || ent->health <= 0 || !ent->inuse )
  322. {
  323. continue;
  324. }
  325. if ( ent->client->playerTeam == NPC->client->playerTeam || ent->client->playerTeam == TEAM_NEUTRAL ) // don't attack same team or bots
  326. {
  327. continue;
  328. }
  329. // try to find the closest visible one
  330. if ( !NPC_ClearLOS( ent ))
  331. {
  332. continue;
  333. }
  334. dis = DistanceHorizontalSquared( NPC->currentOrigin, ent->currentOrigin );
  335. if ( dis <= bestDis )
  336. {
  337. bestDis = dis;
  338. best = ent;
  339. }
  340. }
  341. if ( best )
  342. {
  343. // used to offset seekers around a circle so they don't occupy the same spot. This is not a fool-proof method.
  344. NPC->random = random() * 6.3f; // roughly 2pi
  345. NPC->enemy = best;
  346. }
  347. }
  348. //------------------------------------
  349. void Seeker_FollowPlayer( void )
  350. {
  351. Seeker_MaintainHeight();
  352. float dis = DistanceHorizontalSquared( NPC->currentOrigin, g_entities[0].currentOrigin );
  353. vec3_t pt, dir;
  354. float minDistSqr = MIN_DISTANCE_SQR;
  355. if ( NPC->client->NPC_class == CLASS_BOBAFETT )
  356. {
  357. if ( TIMER_Done( NPC, "flameTime" ) )
  358. {
  359. minDistSqr = 200*200;
  360. }
  361. }
  362. if ( dis < minDistSqr )
  363. {
  364. // generally circle the player closely till we take an enemy..this is our target point
  365. if ( NPC->client->NPC_class == CLASS_BOBAFETT )
  366. {
  367. pt[0] = g_entities[0].currentOrigin[0] + cos( level.time * 0.001f + NPC->random ) * 250;
  368. pt[1] = g_entities[0].currentOrigin[1] + sin( level.time * 0.001f + NPC->random ) * 250;
  369. if ( NPC->client->jetPackTime < level.time )
  370. {
  371. pt[2] = NPC->currentOrigin[2] - 64;
  372. }
  373. else
  374. {
  375. pt[2] = g_entities[0].currentOrigin[2] + 200;
  376. }
  377. }
  378. else
  379. {
  380. pt[0] = g_entities[0].currentOrigin[0] + cos( level.time * 0.001f + NPC->random ) * 56;
  381. pt[1] = g_entities[0].currentOrigin[1] + sin( level.time * 0.001f + NPC->random ) * 56;
  382. pt[2] = g_entities[0].currentOrigin[2] + 40;
  383. }
  384. VectorSubtract( pt, NPC->currentOrigin, dir );
  385. VectorMA( NPC->client->ps.velocity, 0.8f, dir, NPC->client->ps.velocity );
  386. }
  387. else
  388. {
  389. if ( NPC->client->NPC_class != CLASS_BOBAFETT )
  390. {
  391. if ( TIMER_Done( NPC, "seekerhiss" ))
  392. {
  393. TIMER_Set( NPC, "seekerhiss", 1000 + random() * 1000 );
  394. G_Sound( NPC, G_SoundIndex( "sound/chars/seeker/misc/hiss" ));
  395. }
  396. }
  397. // Hey come back!
  398. NPCInfo->goalEntity = &g_entities[0];
  399. NPCInfo->goalRadius = 32;
  400. NPC_MoveToGoal( qtrue );
  401. NPC->owner = &g_entities[0];
  402. }
  403. if ( NPCInfo->enemyCheckDebounceTime < level.time )
  404. {
  405. // check twice a second to find a new enemy
  406. Seeker_FindEnemy();
  407. NPCInfo->enemyCheckDebounceTime = level.time + 500;
  408. }
  409. NPC_UpdateAngles( qtrue, qtrue );
  410. }
  411. //------------------------------------
  412. void NPC_BSSeeker_Default( void )
  413. {
  414. if ( in_camera )
  415. {
  416. if ( NPC->client->NPC_class != CLASS_BOBAFETT )
  417. {
  418. // cameras make me commit suicide....
  419. G_Damage( NPC, NPC, NPC, NULL, NULL, 999, 0, MOD_UNKNOWN );
  420. }
  421. }
  422. if ( NPC->random == 0.0f )
  423. {
  424. // used to offset seekers around a circle so they don't occupy the same spot. This is not a fool-proof method.
  425. NPC->random = random() * 6.3f; // roughly 2pi
  426. }
  427. if ( NPC->enemy && NPC->enemy->health && NPC->enemy->inuse )
  428. {
  429. if ( NPC->client->NPC_class != CLASS_BOBAFETT
  430. && ( NPC->enemy->s.number == 0 || ( NPC->enemy->client && NPC->enemy->client->NPC_class == CLASS_SEEKER )) )
  431. {
  432. //hacked to never take the player as an enemy, even if the player shoots at it
  433. NPC->enemy = NULL;
  434. }
  435. else
  436. {
  437. Seeker_Attack();
  438. if ( NPC->client->NPC_class == CLASS_BOBAFETT )
  439. {
  440. Boba_FireDecide();
  441. }
  442. return;
  443. }
  444. }
  445. else if ( NPC->client->NPC_class == CLASS_BOBAFETT )
  446. {
  447. NPC_BSST_Patrol();
  448. return;
  449. }
  450. // In all other cases, follow the player and look for enemies to take on
  451. Seeker_FollowPlayer();
  452. }