AI_RocketTrooper.cpp 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912
  1. #include "g_headers.h"
  2. #include "b_local.h"
  3. //#include "g_nav.h"
  4. //#include "anims.h"
  5. //#include "wp_saber.h"
  6. extern qboolean PM_FlippingAnim( int anim );
  7. extern void NPC_BSST_Patrol( void );
  8. extern void RT_FlyStart( gentity_t *self );
  9. extern qboolean Q3_TaskIDPending( gentity_t *ent, taskID_t taskType );
  10. #define VELOCITY_DECAY 0.7f
  11. #define RT_FLYING_STRAFE_VEL 60
  12. #define RT_FLYING_STRAFE_DIS 200
  13. #define RT_FLYING_UPWARD_PUSH 150
  14. #define RT_FLYING_FORWARD_BASE_SPEED 50
  15. #define RT_FLYING_FORWARD_MULTIPLIER 10
  16. void RT_Precache( void )
  17. {
  18. G_SoundIndex( "sound/chars/boba/bf_blast-off.wav" );
  19. G_SoundIndex( "sound/chars/boba/bf_jetpack_lp.wav" );
  20. G_SoundIndex( "sound/chars/boba/bf_land.wav" );
  21. G_EffectIndex( "rockettrooper/flameNEW" );
  22. G_EffectIndex( "rockettrooper/light_cone" );//extern this? At least use a different one
  23. }
  24. extern void NPC_BehaviorSet_Stormtrooper( int bState );
  25. void RT_RunStormtrooperAI( void )
  26. {
  27. int bState;
  28. //Execute our bState
  29. if(NPCInfo->tempBehavior)
  30. {//Overrides normal behavior until cleared
  31. bState = NPCInfo->tempBehavior;
  32. }
  33. else
  34. {
  35. if(!NPCInfo->behaviorState)
  36. NPCInfo->behaviorState = NPCInfo->defaultBehavior;
  37. bState = NPCInfo->behaviorState;
  38. }
  39. NPC_BehaviorSet_Stormtrooper( bState );
  40. }
  41. void RT_FireDecide( void )
  42. {
  43. qboolean enemyLOS = qfalse;
  44. qboolean enemyCS = qfalse;
  45. qboolean enemyInFOV = qfalse;
  46. //qboolean move = qtrue;
  47. qboolean faceEnemy = qfalse;
  48. qboolean shoot = qfalse;
  49. qboolean hitAlly = qfalse;
  50. vec3_t impactPos;
  51. float enemyDist;
  52. if ( NPC->client->ps.groundEntityNum == ENTITYNUM_NONE
  53. && NPC->client->ps.forceJumpZStart
  54. && !PM_FlippingAnim( NPC->client->ps.legsAnim )
  55. && !Q_irand( 0, 10 ) )
  56. {//take off
  57. RT_FlyStart( NPC );
  58. }
  59. if ( !NPC->enemy )
  60. {
  61. return;
  62. }
  63. VectorClear( impactPos );
  64. enemyDist = DistanceSquared( NPC->currentOrigin, NPC->enemy->currentOrigin );
  65. vec3_t enemyDir, shootDir;
  66. VectorSubtract( NPC->enemy->currentOrigin, NPC->currentOrigin, enemyDir );
  67. VectorNormalize( enemyDir );
  68. AngleVectors( NPC->client->ps.viewangles, shootDir, NULL, NULL );
  69. float dot = DotProduct( enemyDir, shootDir );
  70. if ( dot > 0.5f ||( enemyDist * (1.0f-dot)) < 10000 )
  71. {//enemy is in front of me or they're very close and not behind me
  72. enemyInFOV = qtrue;
  73. }
  74. if ( enemyDist < MIN_ROCKET_DIST_SQUARED )//128
  75. {//enemy within 128
  76. if ( (NPC->client->ps.weapon == WP_FLECHETTE || NPC->client->ps.weapon == WP_REPEATER) &&
  77. (NPCInfo->scriptFlags & SCF_ALT_FIRE) )
  78. {//shooting an explosive, but enemy too close, switch to primary fire
  79. NPCInfo->scriptFlags &= ~SCF_ALT_FIRE;
  80. //FIXME: we can never go back to alt-fire this way since, after this, we don't know if we were initially supposed to use alt-fire or not...
  81. }
  82. }
  83. //can we see our target?
  84. if ( TIMER_Done( NPC, "nextAttackDelay" ) && TIMER_Done( NPC, "flameTime" ) )
  85. {
  86. if ( NPC_ClearLOS( NPC->enemy ) )
  87. {
  88. NPCInfo->enemyLastSeenTime = level.time;
  89. enemyLOS = qtrue;
  90. if ( NPC->client->ps.weapon == WP_NONE )
  91. {
  92. enemyCS = qfalse;//not true, but should stop us from firing
  93. }
  94. else
  95. {//can we shoot our target?
  96. if ( (NPC->client->ps.weapon == WP_ROCKET_LAUNCHER
  97. || (NPC->client->ps.weapon == WP_CONCUSSION && !(NPCInfo->scriptFlags&SCF_ALT_FIRE))
  98. || (NPC->client->ps.weapon == WP_FLECHETTE && (NPCInfo->scriptFlags&SCF_ALT_FIRE))) && enemyDist < MIN_ROCKET_DIST_SQUARED )//128*128
  99. {
  100. enemyCS = qfalse;//not true, but should stop us from firing
  101. hitAlly = qtrue;//us!
  102. //FIXME: if too close, run away!
  103. }
  104. else if ( enemyInFOV )
  105. {//if enemy is FOV, go ahead and check for shooting
  106. int hit = NPC_ShotEntity( NPC->enemy, impactPos );
  107. gentity_t *hitEnt = &g_entities[hit];
  108. if ( hit == NPC->enemy->s.number
  109. || ( hitEnt && hitEnt->client && hitEnt->client->playerTeam == NPC->client->enemyTeam )
  110. || ( hitEnt && hitEnt->takedamage && ((hitEnt->svFlags&SVF_GLASS_BRUSH)||hitEnt->health < 40||NPC->s.weapon == WP_EMPLACED_GUN) ) )
  111. {//can hit enemy or enemy ally or will hit glass or other minor breakable (or in emplaced gun), so shoot anyway
  112. enemyCS = qtrue;
  113. //NPC_AimAdjust( 2 );//adjust aim better longer we have clear shot at enemy
  114. VectorCopy( NPC->enemy->currentOrigin, NPCInfo->enemyLastSeenLocation );
  115. }
  116. else
  117. {//Hmm, have to get around this bastard
  118. //NPC_AimAdjust( 1 );//adjust aim better longer we can see enemy
  119. if ( hitEnt && hitEnt->client && hitEnt->client->playerTeam == NPC->client->playerTeam )
  120. {//would hit an ally, don't fire!!!
  121. hitAlly = qtrue;
  122. }
  123. else
  124. {//Check and see where our shot *would* hit... if it's not close to the enemy (within 256?), then don't fire
  125. }
  126. }
  127. }
  128. else
  129. {
  130. enemyCS = qfalse;//not true, but should stop us from firing
  131. }
  132. }
  133. }
  134. else if ( gi.inPVS( NPC->enemy->currentOrigin, NPC->currentOrigin ) )
  135. {
  136. NPCInfo->enemyLastSeenTime = level.time;
  137. faceEnemy = qtrue;
  138. //NPC_AimAdjust( -1 );//adjust aim worse longer we cannot see enemy
  139. }
  140. if ( NPC->client->ps.weapon == WP_NONE )
  141. {
  142. faceEnemy = qfalse;
  143. shoot = qfalse;
  144. }
  145. else
  146. {
  147. if ( enemyLOS )
  148. {//FIXME: no need to face enemy if we're moving to some other goal and he's too far away to shoot?
  149. faceEnemy = qtrue;
  150. }
  151. if ( enemyCS )
  152. {
  153. shoot = qtrue;
  154. }
  155. }
  156. if ( !enemyCS )
  157. {//if have a clear shot, always try
  158. //See if we should continue to fire on their last position
  159. //!TIMER_Done( NPC, "stick" ) ||
  160. if ( !hitAlly //we're not going to hit an ally
  161. && enemyInFOV //enemy is in our FOV //FIXME: or we don't have a clear LOS?
  162. && NPCInfo->enemyLastSeenTime > 0 )//we've seen the enemy
  163. {
  164. if ( level.time - NPCInfo->enemyLastSeenTime < 10000 )//we have seem the enemy in the last 10 seconds
  165. {
  166. if ( !Q_irand( 0, 10 ) )
  167. {
  168. //Fire on the last known position
  169. vec3_t muzzle, dir, angles;
  170. qboolean tooClose = qfalse;
  171. qboolean tooFar = qfalse;
  172. CalcEntitySpot( NPC, SPOT_HEAD, muzzle );
  173. if ( VectorCompare( impactPos, vec3_origin ) )
  174. {//never checked ShotEntity this frame, so must do a trace...
  175. trace_t tr;
  176. //vec3_t mins = {-2,-2,-2}, maxs = {2,2,2};
  177. vec3_t forward, end;
  178. AngleVectors( NPC->client->ps.viewangles, forward, NULL, NULL );
  179. VectorMA( muzzle, 8192, forward, end );
  180. gi.trace( &tr, muzzle, vec3_origin, vec3_origin, end, NPC->s.number, MASK_SHOT );
  181. VectorCopy( tr.endpos, impactPos );
  182. }
  183. //see if impact would be too close to me
  184. float distThreshold = 16384/*128*128*/;//default
  185. switch ( NPC->s.weapon )
  186. {
  187. case WP_ROCKET_LAUNCHER:
  188. case WP_FLECHETTE:
  189. case WP_THERMAL:
  190. case WP_TRIP_MINE:
  191. case WP_DET_PACK:
  192. distThreshold = 65536/*256*256*/;
  193. break;
  194. case WP_REPEATER:
  195. if ( NPCInfo->scriptFlags&SCF_ALT_FIRE )
  196. {
  197. distThreshold = 65536/*256*256*/;
  198. }
  199. break;
  200. case WP_CONCUSSION:
  201. if ( !(NPCInfo->scriptFlags&SCF_ALT_FIRE) )
  202. {
  203. distThreshold = 65536/*256*256*/;
  204. }
  205. break;
  206. default:
  207. break;
  208. }
  209. float dist = DistanceSquared( impactPos, muzzle );
  210. if ( dist < distThreshold )
  211. {//impact would be too close to me
  212. tooClose = qtrue;
  213. }
  214. else if ( level.time - NPCInfo->enemyLastSeenTime > 5000 ||
  215. (NPCInfo->group && level.time - NPCInfo->group->lastSeenEnemyTime > 5000 ))
  216. {//we've haven't seen them in the last 5 seconds
  217. //see if it's too far from where he is
  218. distThreshold = 65536/*256*256*/;//default
  219. switch ( NPC->s.weapon )
  220. {
  221. case WP_ROCKET_LAUNCHER:
  222. case WP_FLECHETTE:
  223. case WP_THERMAL:
  224. case WP_TRIP_MINE:
  225. case WP_DET_PACK:
  226. distThreshold = 262144/*512*512*/;
  227. break;
  228. case WP_REPEATER:
  229. if ( NPCInfo->scriptFlags&SCF_ALT_FIRE )
  230. {
  231. distThreshold = 262144/*512*512*/;
  232. }
  233. break;
  234. case WP_CONCUSSION:
  235. if ( !(NPCInfo->scriptFlags&SCF_ALT_FIRE) )
  236. {
  237. distThreshold = 262144/*512*512*/;
  238. }
  239. break;
  240. default:
  241. break;
  242. }
  243. dist = DistanceSquared( impactPos, NPCInfo->enemyLastSeenLocation );
  244. if ( dist > distThreshold )
  245. {//impact would be too far from enemy
  246. tooFar = qtrue;
  247. }
  248. }
  249. if ( !tooClose && !tooFar )
  250. {//okay too shoot at last pos
  251. VectorSubtract( NPCInfo->enemyLastSeenLocation, muzzle, dir );
  252. VectorNormalize( dir );
  253. vectoangles( dir, angles );
  254. NPCInfo->desiredYaw = angles[YAW];
  255. NPCInfo->desiredPitch = angles[PITCH];
  256. shoot = qtrue;
  257. faceEnemy = qfalse;
  258. }
  259. }
  260. }
  261. }
  262. }
  263. //FIXME: don't shoot right away!
  264. if ( NPC->client->fireDelay )
  265. {
  266. if ( NPC->s.weapon == WP_ROCKET_LAUNCHER
  267. || (NPC->s.weapon == WP_CONCUSSION&&!(NPCInfo->scriptFlags&SCF_ALT_FIRE)) )
  268. {
  269. if ( !enemyLOS || !enemyCS )
  270. {//cancel it
  271. NPC->client->fireDelay = 0;
  272. }
  273. else
  274. {//delay our next attempt
  275. TIMER_Set( NPC, "nextAttackDelay", Q_irand( 1000, 3000 ) );//FIXME: base on g_spskill
  276. }
  277. }
  278. }
  279. else if ( shoot )
  280. {//try to shoot if it's time
  281. if ( TIMER_Done( NPC, "nextAttackDelay" ) )
  282. {
  283. if( !(NPCInfo->scriptFlags & SCF_FIRE_WEAPON) ) // we've already fired, no need to do it again here
  284. {
  285. WeaponThink( qtrue );
  286. }
  287. //NASTY
  288. int altChance = 6;//FIXME: base on g_spskill
  289. if ( NPC->s.weapon == WP_ROCKET_LAUNCHER )
  290. {
  291. if ( (ucmd.buttons&BUTTON_ATTACK)
  292. && !Q_irand( 0, altChance ) )
  293. {//every now and then, shoot a homing rocket
  294. ucmd.buttons &= ~BUTTON_ATTACK;
  295. ucmd.buttons |= BUTTON_ALT_ATTACK;
  296. NPC->client->fireDelay = Q_irand( 1000, 3000 );//FIXME: base on g_spskill
  297. }
  298. }
  299. else if ( NPC->s.weapon == WP_CONCUSSION )
  300. {
  301. if ( (ucmd.buttons&BUTTON_ATTACK)
  302. && Q_irand( 0, altChance*5 ) )
  303. {//fire the beam shot
  304. ucmd.buttons &= ~BUTTON_ATTACK;
  305. ucmd.buttons |= BUTTON_ALT_ATTACK;
  306. TIMER_Set( NPC, "nextAttackDelay", Q_irand( 1500, 2500 ) );//FIXME: base on g_spskill
  307. }
  308. else
  309. {//fire the rocket-like shot
  310. TIMER_Set( NPC, "nextAttackDelay", Q_irand( 3000, 5000 ) );//FIXME: base on g_spskill
  311. }
  312. }
  313. }
  314. }
  315. }
  316. }
  317. //=====================================================================================
  318. //FLYING behavior
  319. //=====================================================================================
  320. qboolean RT_Flying( gentity_t *self )
  321. {
  322. return ((qboolean)(self->client->moveType==MT_FLYSWIM));
  323. }
  324. void RT_FlyStart( gentity_t *self )
  325. {//switch to seeker AI for a while
  326. if ( TIMER_Done( self, "jetRecharge" )
  327. && !RT_Flying( self ) )
  328. {
  329. self->client->ps.gravity = 0;
  330. self->svFlags |= SVF_CUSTOM_GRAVITY;
  331. self->client->moveType = MT_FLYSWIM;
  332. //Inform NPC_HandleAIFlags we want to fly
  333. self->NPC->aiFlags |= NPCAI_FLY;
  334. self->lastInAirTime = level.time;
  335. //start jet effect
  336. self->client->jetPackTime = Q3_INFINITE;
  337. if ( self->genericBolt1 != -1 )
  338. {
  339. G_PlayEffect( G_EffectIndex( "rockettrooper/flameNEW" ), self->playerModel, self->genericBolt1, self->s.number, self->currentOrigin, qtrue, qtrue );
  340. }
  341. if ( self->genericBolt2 != -1 )
  342. {
  343. G_PlayEffect( G_EffectIndex( "rockettrooper/flameNEW" ), self->playerModel, self->genericBolt2, self->s.number, self->currentOrigin, qtrue, qtrue );
  344. }
  345. //take-off sound
  346. G_SoundOnEnt( self, CHAN_ITEM, "sound/chars/boba/bf_blast-off.wav" );
  347. //jet loop sound
  348. self->s.loopSound = G_SoundIndex( "sound/chars/boba/bf_jetpack_lp.wav" );
  349. if ( self->NPC )
  350. {
  351. self->count = Q3_INFINITE; // SEEKER shot ammo count
  352. }
  353. }
  354. }
  355. void RT_FlyStop( gentity_t *self )
  356. {
  357. self->client->ps.gravity = g_gravity->value;
  358. self->svFlags &= ~SVF_CUSTOM_GRAVITY;
  359. self->client->moveType = MT_RUNJUMP;
  360. //Stop the effect
  361. self->client->jetPackTime = 0;
  362. if ( self->genericBolt1 != -1 )
  363. {
  364. G_StopEffect("rockettrooper/flameNEW", self->playerModel, self->genericBolt1, self->s.number );
  365. }
  366. if ( self->genericBolt2 != -1 )
  367. {
  368. G_StopEffect("rockettrooper/flameNEW", self->playerModel, self->genericBolt2, self->s.number );
  369. }
  370. //stop jet loop sound
  371. self->s.loopSound = 0;
  372. G_SoundOnEnt( self, CHAN_ITEM, "sound/chars/boba/bf_land.wav" );
  373. if ( self->NPC )
  374. {
  375. self->count = 0; // SEEKER shot ammo count
  376. TIMER_Set( self, "jetRecharge", Q_irand( 1000, 5000 ) );
  377. TIMER_Set( self, "jumpChaseDebounce", Q_irand( 500, 2000 ) );
  378. }
  379. }
  380. void RT_JetPackEffect( int duration )
  381. {
  382. if ( NPC->genericBolt1 != -1 )
  383. {
  384. G_PlayEffect( G_EffectIndex( "rockettrooper/flameNEW" ), NPC->playerModel, NPC->genericBolt1, NPC->s.number, NPC->currentOrigin, duration, qtrue );
  385. }
  386. if ( NPC->genericBolt2 != -1 )
  387. {
  388. G_PlayEffect( G_EffectIndex( "rockettrooper/flameNEW" ), NPC->playerModel, NPC->genericBolt2, NPC->s.number, NPC->currentOrigin, duration, qtrue );
  389. }
  390. //take-off sound
  391. G_SoundOnEnt( NPC, CHAN_ITEM, "sound/chars/boba/bf_blast-off.wav" );
  392. }
  393. void RT_Flying_ApplyFriction( float frictionScale )
  394. {
  395. if ( NPC->client->ps.velocity[0] )
  396. {
  397. NPC->client->ps.velocity[0] *= VELOCITY_DECAY;///frictionScale;
  398. if ( fabs( NPC->client->ps.velocity[0] ) < 1 )
  399. {
  400. NPC->client->ps.velocity[0] = 0;
  401. }
  402. }
  403. if ( NPC->client->ps.velocity[1] )
  404. {
  405. NPC->client->ps.velocity[1] *= VELOCITY_DECAY;///frictionScale;
  406. if ( fabs( NPC->client->ps.velocity[1] ) < 1 )
  407. {
  408. NPC->client->ps.velocity[1] = 0;
  409. }
  410. }
  411. }
  412. void RT_Flying_MaintainHeight( void )
  413. {
  414. float dif = 0;
  415. // Update our angles regardless
  416. NPC_UpdateAngles( qtrue, qtrue );
  417. if ( NPC->forcePushTime > level.time )
  418. {//if being pushed, we don't have control over our movement
  419. return;
  420. }
  421. if ( (NPC->client->ps.pm_flags&PMF_TIME_KNOCKBACK) )
  422. {//don't slow down for a bit
  423. if ( NPC->client->ps.pm_time > 0 )
  424. {
  425. VectorScale( NPC->client->ps.velocity, 0.9f, NPC->client->ps.velocity );
  426. return;
  427. }
  428. }
  429. /*
  430. if ( (NPC->client->ps.eFlags&EF_FORCE_GRIPPED) )
  431. {
  432. RT_Flying_ApplyFriction( 3.0f );
  433. return;
  434. }
  435. */
  436. // If we have an enemy, we should try to hover at or a little below enemy eye level
  437. if ( NPC->enemy
  438. && (!Q3_TaskIDPending( NPC, TID_MOVE_NAV ) || !NPCInfo->goalEntity ) )
  439. {
  440. if (TIMER_Done( NPC, "heightChange" ))
  441. {
  442. TIMER_Set( NPC,"heightChange",Q_irand( 1000, 3000 ));
  443. float enemyZHeight = NPC->enemy->currentOrigin[2];
  444. if ( NPC->enemy->client
  445. && NPC->enemy->client->ps.groundEntityNum == ENTITYNUM_NONE
  446. && (NPC->enemy->client->ps.forcePowersActive&(1<<FP_LEVITATION)) )
  447. {//so we don't go up when they force jump up at us
  448. enemyZHeight = NPC->enemy->client->ps.forceJumpZStart;
  449. }
  450. // Find the height difference
  451. dif = (enemyZHeight + Q_flrand( NPC->enemy->maxs[2]/2, NPC->enemy->maxs[2]+8 )) - NPC->currentOrigin[2];
  452. float difFactor = 10.0f;
  453. // cap to prevent dramatic height shifts
  454. if ( fabs( dif ) > 2*difFactor )
  455. {
  456. if ( fabs( dif ) > 20*difFactor )
  457. {
  458. dif = ( dif < 0 ? -20*difFactor : 20*difFactor );
  459. }
  460. NPC->client->ps.velocity[2] = (NPC->client->ps.velocity[2]+dif)/2;
  461. }
  462. NPC->client->ps.velocity[2] *= Q_flrand( 0.85f, 1.25f );
  463. }
  464. else
  465. {//don't get too far away from height of enemy...
  466. float enemyZHeight = NPC->enemy->currentOrigin[2];
  467. if ( NPC->enemy->client
  468. && NPC->enemy->client->ps.groundEntityNum == ENTITYNUM_NONE
  469. && (NPC->enemy->client->ps.forcePowersActive&(1<<FP_LEVITATION)) )
  470. {//so we don't go up when they force jump up at us
  471. enemyZHeight = NPC->enemy->client->ps.forceJumpZStart;
  472. }
  473. dif = NPC->currentOrigin[2] - (enemyZHeight+64);
  474. float maxHeight = 200;
  475. float hDist = DistanceHorizontal( NPC->enemy->currentOrigin, NPC->currentOrigin );
  476. if ( hDist < 512 )
  477. {
  478. maxHeight *= hDist/512;
  479. }
  480. if ( dif > maxHeight )
  481. {
  482. if ( NPC->client->ps.velocity[2] > 0 )//FIXME: or: we can't see him anymore
  483. {//slow down
  484. if ( NPC->client->ps.velocity[2] )
  485. {
  486. NPC->client->ps.velocity[2] *= VELOCITY_DECAY;
  487. if ( fabs( NPC->client->ps.velocity[2] ) < 2 )
  488. {
  489. NPC->client->ps.velocity[2] = 0;
  490. }
  491. }
  492. }
  493. else
  494. {//start coming back down
  495. NPC->client->ps.velocity[2] -= 4;
  496. }
  497. }
  498. else if ( dif < -200 && NPC->client->ps.velocity[2] < 0 )//we're way below him
  499. {
  500. if ( NPC->client->ps.velocity[2] < 0 )//FIXME: or: we can't see him anymore
  501. {//slow down
  502. if ( NPC->client->ps.velocity[2] )
  503. {
  504. NPC->client->ps.velocity[2] *= VELOCITY_DECAY;
  505. if ( fabs( NPC->client->ps.velocity[2] ) > -2 )
  506. {
  507. NPC->client->ps.velocity[2] = 0;
  508. }
  509. }
  510. }
  511. else
  512. {//start going back up
  513. NPC->client->ps.velocity[2] += 4;
  514. }
  515. }
  516. }
  517. }
  518. else
  519. {
  520. gentity_t *goal = NULL;
  521. if ( NPCInfo->goalEntity ) // Is there a goal?
  522. {
  523. goal = NPCInfo->goalEntity;
  524. }
  525. else
  526. {
  527. goal = NPCInfo->lastGoalEntity;
  528. }
  529. if ( goal )
  530. {
  531. dif = goal->currentOrigin[2] - NPC->currentOrigin[2];
  532. }
  533. else if ( VectorCompare( NPC->pos1, vec3_origin ) )
  534. {//have a starting position as a reference point
  535. dif = NPC->pos1[2] - NPC->currentOrigin[2];
  536. }
  537. if ( fabs( dif ) > 24 )
  538. {
  539. ucmd.upmove = ( ucmd.upmove < 0 ? -4 : 4 );
  540. }
  541. else
  542. {
  543. if ( NPC->client->ps.velocity[2] )
  544. {
  545. NPC->client->ps.velocity[2] *= VELOCITY_DECAY;
  546. if ( fabs( NPC->client->ps.velocity[2] ) < 2 )
  547. {
  548. NPC->client->ps.velocity[2] = 0;
  549. }
  550. }
  551. }
  552. }
  553. // Apply friction
  554. RT_Flying_ApplyFriction( 1.0f );
  555. }
  556. void RT_Flying_Strafe( void )
  557. {
  558. int side;
  559. vec3_t end, right, dir;
  560. trace_t tr;
  561. if ( random() > 0.7f
  562. || !NPC->enemy
  563. || !NPC->enemy->client )
  564. {
  565. // Do a regular style strafe
  566. AngleVectors( NPC->client->renderInfo.eyeAngles, NULL, right, NULL );
  567. // Pick a random strafe direction, then check to see if doing a strafe would be
  568. // reasonably valid
  569. side = ( rand() & 1 ) ? -1 : 1;
  570. VectorMA( NPC->currentOrigin, RT_FLYING_STRAFE_DIS * side, right, end );
  571. gi.trace( &tr, NPC->currentOrigin, NULL, NULL, end, NPC->s.number, MASK_SOLID );
  572. // Close enough
  573. if ( tr.fraction > 0.9f )
  574. {
  575. float vel = RT_FLYING_STRAFE_VEL+Q_flrand(-20,20);
  576. VectorMA( NPC->client->ps.velocity, vel*side, right, NPC->client->ps.velocity );
  577. if ( !Q_irand( 0, 3 ) )
  578. {
  579. // Add a slight upward push
  580. float upPush = RT_FLYING_UPWARD_PUSH;
  581. if ( NPC->client->ps.velocity[2] < 300 )
  582. {
  583. if ( NPC->client->ps.velocity[2] < 300+upPush )
  584. {
  585. NPC->client->ps.velocity[2] += upPush;
  586. }
  587. else
  588. {
  589. NPC->client->ps.velocity[2] = 300;
  590. }
  591. }
  592. }
  593. // NPCInfo->standTime = level.time + 1000 + random() * 500; // Original
  594. // NPCInfo->standTime = level.time + 2000 + random() * 500; // Revision 1
  595. NPCInfo->standTime = level.time + 1500 + random() * 500; // Revision 2
  596. }
  597. }
  598. else
  599. {
  600. // Do a strafe to try and keep on the side of their enemy
  601. AngleVectors( NPC->enemy->client->renderInfo.eyeAngles, dir, right, NULL );
  602. // Pick a random side
  603. side = ( rand() & 1 ) ? -1 : 1;
  604. float stDis = RT_FLYING_STRAFE_DIS*2.0f;
  605. VectorMA( NPC->enemy->currentOrigin, stDis * side, right, end );
  606. // then add a very small bit of random in front of/behind the player action
  607. VectorMA( end, crandom() * 25, dir, end );
  608. gi.trace( &tr, NPC->currentOrigin, NULL, NULL, end, NPC->s.number, MASK_SOLID );
  609. // Close enough
  610. if ( tr.fraction > 0.9f )
  611. {
  612. float vel = (RT_FLYING_STRAFE_VEL*4)+Q_flrand(-20,20);
  613. VectorSubtract( tr.endpos, NPC->currentOrigin, dir );
  614. dir[2] *= 0.25; // do less upward change
  615. float dis = VectorNormalize( dir );
  616. if ( dis > vel )
  617. {
  618. dis = vel;
  619. }
  620. // Try to move the desired enemy side
  621. VectorMA( NPC->client->ps.velocity, dis, dir, NPC->client->ps.velocity );
  622. if ( !Q_irand( 0, 3 ) )
  623. {
  624. float upPush = RT_FLYING_UPWARD_PUSH;
  625. // Add a slight upward push
  626. if ( NPC->client->ps.velocity[2] < 300 )
  627. {
  628. if ( NPC->client->ps.velocity[2] < 300+upPush )
  629. {
  630. NPC->client->ps.velocity[2] += upPush;
  631. }
  632. else
  633. {
  634. NPC->client->ps.velocity[2] = 300;
  635. }
  636. }
  637. else if ( NPC->client->ps.velocity[2] > 300 )
  638. {
  639. NPC->client->ps.velocity[2] = 300;
  640. }
  641. }
  642. // NPCInfo->standTime = level.time + 2500 + random() * 500; // Original
  643. // NPCInfo->standTime = level.time + 5000 + random() * 500; // Revision 1
  644. NPCInfo->standTime = level.time + 3500 + random() * 500; // Revision 2
  645. }
  646. }
  647. }
  648. void RT_Flying_Hunt( qboolean visible, qboolean advance )
  649. {
  650. float distance, speed;
  651. vec3_t forward;
  652. if ( NPC->forcePushTime >= level.time )
  653. //|| (NPC->client->ps.eFlags&EF_FORCE_GRIPPED) )
  654. {//if being pushed, we don't have control over our movement
  655. NPC->delay = 0;
  656. return;
  657. }
  658. NPC_FaceEnemy( qtrue );
  659. // If we're not supposed to stand still, pursue the player
  660. if ( NPCInfo->standTime < level.time )
  661. {
  662. // Only strafe when we can see the player
  663. if ( visible )
  664. {
  665. NPC->delay = 0;
  666. RT_Flying_Strafe();
  667. return;
  668. }
  669. }
  670. // If we don't want to advance, stop here
  671. if ( advance )
  672. {
  673. // Only try and navigate if the player is visible
  674. if ( visible == qfalse )
  675. {
  676. // Move towards our goal
  677. NPCInfo->goalEntity = NPC->enemy;
  678. NPCInfo->goalRadius = 24;
  679. NPC->delay = 0;
  680. NPC_MoveToGoal(qtrue);
  681. return;
  682. }
  683. }
  684. //else move straight at/away from him
  685. VectorSubtract( NPC->enemy->currentOrigin, NPC->currentOrigin, forward );
  686. forward[2] *= 0.1f;
  687. distance = VectorNormalize( forward );
  688. speed = RT_FLYING_FORWARD_BASE_SPEED + RT_FLYING_FORWARD_MULTIPLIER * g_spskill->integer;
  689. if ( advance && distance < Q_flrand( 256, 3096 ) )
  690. {
  691. NPC->delay = 0;
  692. VectorMA( NPC->client->ps.velocity, speed, forward, NPC->client->ps.velocity );
  693. }
  694. else if ( distance < Q_flrand( 0, 128 ) )
  695. {
  696. if ( NPC->health <= 50 )
  697. {//always back off
  698. NPC->delay = 0;
  699. }
  700. else if ( !TIMER_Done( NPC, "backoffTime" ) )
  701. {//still backing off from end of last delay
  702. NPC->delay = 0;
  703. }
  704. else if ( !NPC->delay )
  705. {//start a new delay
  706. NPC->delay = Q_irand( 0, 10+(20*(2-g_spskill->integer)) );
  707. }
  708. else
  709. {//continue the current delay
  710. NPC->delay--;
  711. }
  712. if ( !NPC->delay )
  713. {//delay done, now back off for a few seconds!
  714. TIMER_Set( NPC, "backoffTime", Q_irand( 2000, 5000 ) );
  715. VectorMA( NPC->client->ps.velocity, speed*-2, forward, NPC->client->ps.velocity );
  716. }
  717. }
  718. else
  719. {
  720. NPC->delay = 0;
  721. }
  722. }
  723. void RT_Flying_Ranged( qboolean visible, qboolean advance )
  724. {
  725. if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES )
  726. {
  727. RT_Flying_Hunt( visible, advance );
  728. }
  729. }
  730. void RT_Flying_Attack( void )
  731. {
  732. // Always keep a good height off the ground
  733. RT_Flying_MaintainHeight();
  734. // Rate our distance to the target, and our visibilty
  735. float distance = DistanceHorizontalSquared( NPC->currentOrigin, NPC->enemy->currentOrigin );
  736. qboolean visible = NPC_ClearLOS( NPC->enemy );
  737. qboolean advance = (qboolean)(distance>(256.0f*256.0f));
  738. // If we cannot see our target, move to see it
  739. if ( visible == qfalse )
  740. {
  741. if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES )
  742. {
  743. RT_Flying_Hunt( visible, advance );
  744. return;
  745. }
  746. }
  747. RT_Flying_Ranged( visible, advance );
  748. }
  749. void RT_Flying_Think( void )
  750. {
  751. if ( Q3_TaskIDPending( NPC, TID_MOVE_NAV )
  752. && UpdateGoal() )
  753. {//being scripted to go to a certain spot, don't maintain height
  754. if ( NPC_MoveToGoal( qtrue ) )
  755. {//we could macro-nav to our goal
  756. if ( NPC->enemy && NPC->enemy->health && NPC->enemy->inuse )
  757. {
  758. NPC_FaceEnemy( qtrue );
  759. RT_FireDecide();
  760. }
  761. }
  762. else
  763. {//frick, no where to nav to, keep us in the air!
  764. RT_Flying_MaintainHeight();
  765. }
  766. return;
  767. }
  768. if ( NPC->random == 0.0f )
  769. {
  770. // used to offset seekers around a circle so they don't occupy the same spot. This is not a fool-proof method.
  771. NPC->random = random() * 6.3f; // roughly 2pi
  772. }
  773. if ( NPC->enemy && NPC->enemy->health && NPC->enemy->inuse )
  774. {
  775. RT_Flying_Attack();
  776. RT_FireDecide();
  777. return;
  778. }
  779. else
  780. {
  781. RT_Flying_MaintainHeight();
  782. RT_RunStormtrooperAI();
  783. return;
  784. }
  785. }
  786. //=====================================================================================
  787. //ON GROUND WITH ENEMY behavior
  788. //=====================================================================================
  789. //=====================================================================================
  790. //DEFAULT behavior
  791. //=====================================================================================
  792. extern void RT_CheckJump( void );
  793. void NPC_BSRT_Default( void )
  794. {
  795. //FIXME: custom pain and death funcs:
  796. //pain3 is in air
  797. //die in air is both_falldeath1
  798. //attack1 is on ground, attack2 is in air
  799. //FIXME: this doesn't belong here
  800. if ( NPC->client->ps.groundEntityNum != ENTITYNUM_NONE )
  801. {
  802. if ( NPCInfo->rank >= RANK_LT )//&& !Q_irand( 0, 50 ) )
  803. {//officers always stay in the air
  804. NPC->client->ps.velocity[2] = Q_irand( 50, 125 );
  805. NPC->NPC->aiFlags |= NPCAI_FLY; //fixme also, Inform NPC_HandleAIFlags we want to fly
  806. }
  807. }
  808. if ( RT_Flying( NPC ) )
  809. {//FIXME: only officers need do this, right?
  810. RT_Flying_Think();
  811. }
  812. else if ( NPC->enemy != NULL )
  813. {//rocketrooper on ground with enemy
  814. UpdateGoal();
  815. RT_RunStormtrooperAI();
  816. RT_CheckJump();
  817. //NPC_BSST_Default();//FIXME: add missile avoidance
  818. //RT_Hunt();//NPC_BehaviorSet_Jedi( bState );
  819. }
  820. else
  821. {//shouldn't have gotten in here
  822. RT_RunStormtrooperAI();
  823. //NPC_BSST_Patrol();
  824. }
  825. }