AI_BobaFett.cpp 37 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244
  1. ////////////////////////////////////////////////////////////////////////////////////////
  2. // RAVEN SOFTWARE - STAR WARS: JK II
  3. // (c) 2002 Activision
  4. //
  5. // Boba Fett
  6. // ---------
  7. // Ah yes, this file is pretty messy. I've tried to move everything in here, but in fact
  8. // a lot of his AI occurs in the seeker and jedi AI files. Some of these functions
  9. //
  10. //
  11. //
  12. ////////////////////////////////////////////////////////////////////////////////////////
  13. #include "g_headers.h"
  14. #include "b_local.h"
  15. ////////////////////////////////////////////////////////////////////////////////////////
  16. // Forward References Of Functions
  17. ////////////////////////////////////////////////////////////////////////////////////////
  18. void Boba_Precache( void );
  19. void Boba_DustFallNear(const vec3_t origin, int dustcount);
  20. void Boba_ChangeWeapon(int wp);
  21. qboolean Boba_StopKnockdown(gentity_t *self, gentity_t *pusher, const vec3_t pushDir, qboolean forceKnockdown = qfalse);
  22. // Flight Related Functions (also used by Rocket Trooper)
  23. //--------------------------------------------------------
  24. qboolean Boba_Flying( gentity_t *self );
  25. void Boba_FlyStart( gentity_t *self );
  26. void Boba_FlyStop( gentity_t *self );
  27. // Called From NPC_Pain()
  28. //-----------------------------
  29. void Boba_Pain( gentity_t *self, gentity_t *inflictor, int damage, int mod);
  30. // Local: Flame Thrower Weapon
  31. //-----------------------------
  32. void Boba_FireFlameThrower( gentity_t *self );
  33. void Boba_StopFlameThrower( gentity_t *self );
  34. void Boba_StartFlameThrower( gentity_t *self );
  35. void Boba_DoFlameThrower( gentity_t *self );
  36. // Local: Other Tactics
  37. //----------------------
  38. void Boba_DoAmbushWait( gentity_t *self);
  39. void Boba_DoSniper( gentity_t *self);
  40. // Local: Respawning
  41. //-------------------
  42. bool Boba_Respawn();
  43. // Called From Within AI_Jedi && AI_Seeker
  44. //-----------------------------------------
  45. void Boba_Fire();
  46. void Boba_FireDecide();
  47. // Local: Called From Tactics()
  48. //----------------------------
  49. void Boba_TacticsSelect();
  50. bool Boba_CanSeeEnemy( gentity_t *self );
  51. // Called From NPC_RunBehavior()
  52. //-------------------------------
  53. void Boba_Update(); // Always Called First, Before Any Other Thinking
  54. bool Boba_Tactics(); // If returns true, Jedi and Seeker AI not used
  55. bool Boba_Flee(); // If returns true, Jedi and Seeker AI not used
  56. ////////////////////////////////////////////////////////////////////////////////////////
  57. // External Functions
  58. ////////////////////////////////////////////////////////////////////////////////////////
  59. extern void G_SoundAtSpot( vec3_t org, int soundIndex, qboolean broadcast );
  60. extern void G_CreateG2AttachedWeaponModel( gentity_t *ent, const char *weaponModel, int boltNum, int weaponNum );
  61. extern void ChangeWeapon( gentity_t *ent, int newWeapon );
  62. extern void WP_ResistForcePush( gentity_t *self, gentity_t *pusher, qboolean noPenalty );
  63. extern void ForceJump( gentity_t *self, usercmd_t *ucmd );
  64. extern void G_Knockdown( gentity_t *self, gentity_t *attacker, const vec3_t pushDir, float strength, qboolean breakSaberLock );
  65. ////////////////////////////////////////////////////////////////////////////////////////
  66. // External Data
  67. ////////////////////////////////////////////////////////////////////////////////////////
  68. extern cvar_t* g_bobaDebug;
  69. ////////////////////////////////////////////////////////////////////////////////////////
  70. // Boba Debug Output
  71. ////////////////////////////////////////////////////////////////////////////////////////
  72. #ifndef FINAL_BUILD
  73. #if !defined(CTYPE_H_INC)
  74. #include <ctype.h>
  75. #define CTYPE_H_INC
  76. #endif
  77. #if !defined(STDARG_H_INC)
  78. #include <stdarg.h>
  79. #define STDARG_H_INC
  80. #endif
  81. #if !defined(STDIO_H_INC)
  82. #include <stdio.h>
  83. #define STDIO_H_INC
  84. #endif
  85. void Boba_Printf(const char * format, ...)
  86. {
  87. if (g_bobaDebug->integer==0)
  88. {
  89. return;
  90. }
  91. static char string[2][1024]; // in case this is called by nested functions
  92. static int index = 0;
  93. static char nFormat[300];
  94. char* buf;
  95. // Tack On The Standard Format Around The Given Format
  96. //-----------------------------------------------------
  97. sprintf(nFormat, "[BOBA %8d] %s\n", level.time, format);
  98. // Resolve Remaining Elipsis Parameters Into Newly Formated String
  99. //-----------------------------------------------------------------
  100. buf = string[index & 1];
  101. index++;
  102. va_list argptr;
  103. va_start (argptr, format);
  104. vsprintf (buf, nFormat, argptr);
  105. va_end (argptr);
  106. // Print It To Debug Output Console
  107. //----------------------------------
  108. gi.Printf(buf);
  109. }
  110. #else
  111. void Boba_Printf(const char * format, ...)
  112. {
  113. }
  114. #endif
  115. ////////////////////////////////////////////////////////////////////////////////////////
  116. // Defines
  117. ////////////////////////////////////////////////////////////////////////////////////////
  118. #define BOBA_FLAMEDURATION 3000
  119. #define BOBA_FLAMETHROWRANGE 128
  120. #define BOBA_FLAMETHROWSIZE 40
  121. #define BOBA_FLAMETHROWDAMAGEMIN 1//10
  122. #define BOBA_FLAMETHROWDAMAGEMAX 5//40
  123. #define BOBA_ROCKETRANGEMIN 300
  124. #define BOBA_ROCKETRANGEMAX 2000
  125. ////////////////////////////////////////////////////////////////////////////////////////
  126. // Global Data
  127. ////////////////////////////////////////////////////////////////////////////////////////
  128. bool BobaHadDeathScript = false;
  129. bool BobaActive = false;
  130. vec3_t BobaFootStepLoc;
  131. int BobaFootStepCount = 0;
  132. vec3_t AverageEnemyDirection;
  133. int AverageEnemyDirectionSamples;
  134. ////////////////////////////////////////////////////////////////////////////////////////
  135. // Enums
  136. ////////////////////////////////////////////////////////////////////////////////////////
  137. enum EBobaTacticsState
  138. {
  139. BTS_NONE,
  140. // Attack
  141. //--------
  142. BTS_RIFLE, // Uses Jedi / Seeker Movement
  143. BTS_MISSILE, // Uses Jedi / Seeker Movement
  144. BTS_SNIPER, // Uses Special Movement Internal To This File
  145. BTS_FLAMETHROW, // Locked In Place
  146. // Waiting
  147. //---------
  148. BTS_AMBUSHWAIT, // Goto CP & Wait
  149. BTS_MAX
  150. };
  151. ////////////////////////////////////////////////////////////////////////////////////////
  152. //
  153. ////////////////////////////////////////////////////////////////////////////////////////
  154. void Boba_Precache( void )
  155. {
  156. G_SoundIndex( "sound/chars/boba/bf_blast-off.wav" );
  157. G_SoundIndex( "sound/chars/boba/bf_jetpack_lp.wav" );
  158. G_SoundIndex( "sound/chars/boba/bf_land.wav" );
  159. G_SoundIndex( "sound/weapons/boba/bf_flame.mp3" );
  160. G_SoundIndex( "sound/player/footsteps/boot1" );
  161. G_SoundIndex( "sound/player/footsteps/boot2" );
  162. G_SoundIndex( "sound/player/footsteps/boot3" );
  163. G_SoundIndex( "sound/player/footsteps/boot4" );
  164. G_EffectIndex( "boba/jetSP" );
  165. G_EffectIndex( "boba/fthrw" );
  166. G_EffectIndex( "volumetric/black_smoke" );
  167. G_EffectIndex( "chunks/dustFall" );
  168. AverageEnemyDirectionSamples = 0;
  169. VectorClear(AverageEnemyDirection);
  170. BobaHadDeathScript = false;
  171. BobaActive = true;
  172. BobaFootStepCount = 0;
  173. }
  174. ////////////////////////////////////////////////////////////////////////////////////////
  175. //
  176. ////////////////////////////////////////////////////////////////////////////////////////
  177. void Boba_DustFallNear(const vec3_t origin, int dustcount)
  178. {
  179. if (!BobaActive)
  180. {
  181. return;
  182. }
  183. trace_t testTrace;
  184. vec3_t testDirection;
  185. vec3_t testStartPos;
  186. vec3_t testEndPos;
  187. VectorCopy(origin, testStartPos);
  188. for (int i=0; i<dustcount; i++)
  189. {
  190. testDirection[0] = (random() * 2.0f) - 1.0f;
  191. testDirection[1] = (random() * 2.0f) - 1.0f;
  192. testDirection[2] = 1.0f;
  193. VectorMA(origin, 1000.0f, testDirection, testEndPos);
  194. gi.trace (&testTrace, origin, NULL, NULL, testEndPos, (player && player->inuse)?(0):(ENTITYNUM_NONE), MASK_SHOT );
  195. if (!testTrace.startsolid &&
  196. !testTrace.allsolid &&
  197. testTrace.fraction>0.1f &&
  198. testTrace.fraction<0.9f)
  199. {
  200. G_PlayEffect( "chunks/dustFall", testTrace.endpos, testTrace.plane.normal );
  201. }
  202. }
  203. }
  204. ////////////////////////////////////////////////////////////////////////////////////////
  205. // This is just a super silly wrapper around NPC_Change Weapon
  206. ////////////////////////////////////////////////////////////////////////////////////////
  207. void Boba_ChangeWeapon( int wp )
  208. {
  209. if ( NPC->s.weapon == wp )
  210. {
  211. return;
  212. }
  213. NPC_ChangeWeapon( wp );
  214. G_AddEvent( NPC, EV_GENERAL_SOUND, G_SoundIndex( "sound/weapons/change.wav" ));
  215. }
  216. ////////////////////////////////////////////////////////////////////////////////////////
  217. // Choose an "anti-knockdown" response
  218. ////////////////////////////////////////////////////////////////////////////////////////
  219. qboolean Boba_StopKnockdown( gentity_t *self, gentity_t *pusher, const vec3_t pushDir, qboolean forceKnockdown )
  220. {
  221. if ( self->client->NPC_class != CLASS_BOBAFETT )
  222. {
  223. return qfalse;
  224. }
  225. if ( self->client->moveType == MT_FLYSWIM )
  226. {//can't knock me down when I'm flying
  227. return qtrue;
  228. }
  229. vec3_t pDir, fwd, right, ang = {0, self->currentAngles[YAW], 0};
  230. float fDot, rDot;
  231. int strafeTime = Q_irand( 1000, 2000 );
  232. AngleVectors( ang, fwd, right, NULL );
  233. VectorNormalize2( pushDir, pDir );
  234. fDot = DotProduct( pDir, fwd );
  235. rDot = DotProduct( pDir, right );
  236. if ( Q_irand( 0, 2 ) )
  237. {//flip or roll with it
  238. usercmd_t tempCmd;
  239. if ( fDot >= 0.4f )
  240. {
  241. tempCmd.forwardmove = 127;
  242. TIMER_Set( self, "moveforward", strafeTime );
  243. }
  244. else if ( fDot <= -0.4f )
  245. {
  246. tempCmd.forwardmove = -127;
  247. TIMER_Set( self, "moveback", strafeTime );
  248. }
  249. else if ( rDot > 0 )
  250. {
  251. tempCmd.rightmove = 127;
  252. TIMER_Set( self, "strafeRight", strafeTime );
  253. TIMER_Set( self, "strafeLeft", -1 );
  254. }
  255. else
  256. {
  257. tempCmd.rightmove = -127;
  258. TIMER_Set( self, "strafeLeft", strafeTime );
  259. TIMER_Set( self, "strafeRight", -1 );
  260. }
  261. G_AddEvent( self, EV_JUMP, 0 );
  262. if ( !Q_irand( 0, 1 ) )
  263. {//flip
  264. self->client->ps.forceJumpCharge = 280;//FIXME: calc this intelligently?
  265. ForceJump( self, &tempCmd );
  266. }
  267. else
  268. {//roll
  269. TIMER_Set( self, "duck", strafeTime );
  270. }
  271. self->painDebounceTime = 0;//so we do something
  272. }
  273. else if ( !Q_irand( 0, 1 ) && forceKnockdown )
  274. {//resist
  275. WP_ResistForcePush( self, pusher, qtrue );
  276. }
  277. else
  278. {//fall down
  279. return qfalse;
  280. }
  281. return qtrue;
  282. }
  283. ////////////////////////////////////////////////////////////////////////////////////////
  284. // Is this entity flying
  285. ////////////////////////////////////////////////////////////////////////////////////////
  286. qboolean Boba_Flying( gentity_t *self )
  287. {
  288. assert(self && self->NPC && self->client && self->client->NPC_class==CLASS_BOBAFETT);
  289. return ((qboolean)(self->client->moveType==MT_FLYSWIM));
  290. }
  291. ////////////////////////////////////////////////////////////////////////////////////////
  292. //
  293. ////////////////////////////////////////////////////////////////////////////////////////
  294. bool Boba_CanSeeEnemy( gentity_t *self )
  295. {
  296. assert(self && self->NPC && self->client && self->client->NPC_class==CLASS_BOBAFETT);
  297. return ((level.time - self->NPC->enemyLastSeenTime)<1000);
  298. }
  299. ////////////////////////////////////////////////////////////////////////////////////////
  300. //
  301. ////////////////////////////////////////////////////////////////////////////////////////
  302. void Boba_Pain( gentity_t *self, gentity_t *inflictor, int damage, int mod)
  303. {
  304. if (mod==MOD_SABER && !(NPCInfo->aiFlags&NPCAI_FLAMETHROW))
  305. {
  306. TIMER_Set( self, "Boba_TacticsSelect", 0); // Hurt By The Saber, Time To Try Something New
  307. }
  308. if (self->NPC->aiFlags&NPCAI_FLAMETHROW)
  309. {
  310. NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCELIGHTNING_HOLD, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
  311. self->client->ps.torsoAnimTimer = level.time - TIMER_Get(self, "falmeTime");
  312. }
  313. }
  314. ////////////////////////////////////////////////////////////////////////////////////////
  315. //
  316. ////////////////////////////////////////////////////////////////////////////////////////
  317. void Boba_FlyStart( gentity_t *self )
  318. {//switch to seeker AI for a while
  319. if ( TIMER_Done( self, "jetRecharge" )
  320. && !Boba_Flying( self ) )
  321. {
  322. self->client->ps.gravity = 0;
  323. self->svFlags |= SVF_CUSTOM_GRAVITY;
  324. self->client->moveType = MT_FLYSWIM;
  325. //start jet effect
  326. self->client->jetPackTime = level.time + Q_irand( 3000, 10000 );
  327. if ( self->genericBolt1 != -1 )
  328. {
  329. G_PlayEffect( G_EffectIndex( "boba/jetSP" ), self->playerModel, self->genericBolt1, self->s.number, self->currentOrigin, qtrue, qtrue );
  330. }
  331. if ( self->genericBolt2 != -1 )
  332. {
  333. G_PlayEffect( G_EffectIndex( "boba/jetSP" ), self->playerModel, self->genericBolt2, self->s.number, self->currentOrigin, qtrue, qtrue );
  334. }
  335. //take-off sound
  336. G_SoundOnEnt( self, CHAN_ITEM, "sound/chars/boba/bf_blast-off.wav" );
  337. //jet loop sound
  338. self->s.loopSound = G_SoundIndex( "sound/chars/boba/bf_jetpack_lp.wav" );
  339. if ( self->NPC )
  340. {
  341. self->count = Q3_INFINITE; // SEEKER shot ammo count
  342. }
  343. }
  344. }
  345. ////////////////////////////////////////////////////////////////////////////////////////
  346. //
  347. ////////////////////////////////////////////////////////////////////////////////////////
  348. void Boba_FlyStop( gentity_t *self )
  349. {
  350. self->client->ps.gravity = g_gravity->value;
  351. self->svFlags &= ~SVF_CUSTOM_GRAVITY;
  352. self->client->moveType = MT_RUNJUMP;
  353. //Stop effect
  354. self->client->jetPackTime = 0;
  355. if ( self->genericBolt1 != -1 )
  356. {
  357. G_StopEffect( "boba/jetSP", self->playerModel, self->genericBolt1, self->s.number );
  358. }
  359. if ( self->genericBolt2 != -1 )
  360. {
  361. G_StopEffect( "boba/jetSP", self->playerModel, self->genericBolt2, self->s.number );
  362. }
  363. //stop jet loop sound
  364. G_SoundOnEnt( self, CHAN_ITEM, "sound/chars/boba/bf_land.wav" );
  365. self->s.loopSound = 0;
  366. if ( self->NPC )
  367. {
  368. self->count = 0; // SEEKER shot ammo count
  369. TIMER_Set( self, "jetRecharge", Q_irand( 1000, 5000 ) );
  370. TIMER_Set( self, "jumpChaseDebounce", Q_irand( 500, 2000 ) );
  371. }
  372. }
  373. ////////////////////////////////////////////////////////////////////////////////////////
  374. // This func actually does the damage inflicting traces
  375. ////////////////////////////////////////////////////////////////////////////////////////
  376. void Boba_FireFlameThrower( gentity_t *self )
  377. {
  378. trace_t tr;
  379. vec3_t start, end, dir;
  380. CVec3 traceMins(self->mins);
  381. CVec3 traceMaxs(self->maxs);
  382. gentity_t* traceEnt = NULL;
  383. int damage = Q_irand( BOBA_FLAMETHROWDAMAGEMIN, BOBA_FLAMETHROWDAMAGEMAX );
  384. AngleVectors(self->currentAngles, dir, 0, 0);
  385. dir[2] = 0.0f;
  386. VectorCopy(self->currentOrigin, start);
  387. traceMins *= 0.5f;
  388. traceMaxs *= 0.5f;
  389. start[2] += 40.0f;
  390. VectorMA( start, 150.0f, dir, end );
  391. if (g_bobaDebug->integer)
  392. {
  393. CG_DrawEdge(start, end, EDGE_IMPACT_POSSIBLE);
  394. }
  395. gi.trace( &tr, start, self->mins, self->maxs, end, self->s.number, MASK_SHOT);
  396. traceEnt = &g_entities[tr.entityNum];
  397. if ( tr.entityNum < ENTITYNUM_WORLD && traceEnt->takedamage )
  398. {
  399. G_Damage( traceEnt, self, self, dir, tr.endpos, damage, DAMAGE_NO_ARMOR|DAMAGE_NO_KNOCKBACK|DAMAGE_NO_HIT_LOC|DAMAGE_IGNORE_TEAM, MOD_LAVA, HL_NONE );
  400. if (traceEnt->health>0)
  401. {
  402. // G_Knockdown( traceEnt, self, dir, Q_irand(200, 330), qfalse);
  403. G_Throw(traceEnt, dir, 30);
  404. }
  405. }
  406. }
  407. ////////////////////////////////////////////////////////////////////////////////////////
  408. //
  409. ////////////////////////////////////////////////////////////////////////////////////////
  410. void Boba_StopFlameThrower( gentity_t *self )
  411. {
  412. if ((NPCInfo->aiFlags&NPCAI_FLAMETHROW))
  413. {
  414. self->NPC->aiFlags &= ~NPCAI_FLAMETHROW;
  415. self->client->ps.torsoAnimTimer = 0;
  416. TIMER_Set( self, "flameTime", 0);
  417. TIMER_Set( self, "nextAttackDelay", 0);
  418. TIMER_Set( self, "Boba_TacticsSelect", 0);
  419. // G_SoundOnEnt( self, CHAN_WEAPON, "sound/effects/flameoff.mp3" );
  420. G_StopEffect( G_EffectIndex("boba/fthrw"), self->playerModel, self->genericBolt3, self->s.number);
  421. Boba_Printf("FlameThrower OFF");
  422. }
  423. }
  424. ////////////////////////////////////////////////////////////////////////////////////////
  425. //
  426. ////////////////////////////////////////////////////////////////////////////////////////
  427. void Boba_StartFlameThrower( gentity_t *self )
  428. {
  429. if (!(NPCInfo->aiFlags&NPCAI_FLAMETHROW))
  430. {
  431. NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCELIGHTNING_HOLD, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
  432. self->NPC->aiFlags |= NPCAI_FLAMETHROW;
  433. self->client->ps.torsoAnimTimer = BOBA_FLAMEDURATION;
  434. TIMER_Set( self, "flameTime", BOBA_FLAMEDURATION);
  435. TIMER_Set( self, "nextAttackDelay", BOBA_FLAMEDURATION);
  436. TIMER_Set( self, "nextFlameDelay", BOBA_FLAMEDURATION*2);
  437. TIMER_Set( self, "Boba_TacticsSelect", BOBA_FLAMEDURATION);
  438. G_SoundOnEnt( self, CHAN_WEAPON, "sound/weapons/boba/bf_flame.mp3" );
  439. G_PlayEffect( G_EffectIndex("boba/fthrw"), self->playerModel, self->genericBolt3, self->s.number, self->s.origin, 1 );
  440. Boba_Printf("FlameThrower ON");
  441. }
  442. }
  443. ////////////////////////////////////////////////////////////////////////////////////////
  444. //
  445. ////////////////////////////////////////////////////////////////////////////////////////
  446. void Boba_DoFlameThrower( gentity_t *self )
  447. {
  448. if (!(NPCInfo->aiFlags&NPCAI_FLAMETHROW) && TIMER_Done(self, "nextAttackDelay"))
  449. {
  450. Boba_StartFlameThrower( self );
  451. }
  452. if ( (NPCInfo->aiFlags&NPCAI_FLAMETHROW))
  453. {
  454. Boba_FireFlameThrower( self );
  455. }
  456. }
  457. ////////////////////////////////////////////////////////////////////////////////////////
  458. //
  459. ////////////////////////////////////////////////////////////////////////////////////////
  460. void Boba_DoAmbushWait( gentity_t *self)
  461. {
  462. }
  463. ////////////////////////////////////////////////////////////////////////////////////////
  464. //
  465. ////////////////////////////////////////////////////////////////////////////////////////
  466. void Boba_DoSniper( gentity_t *self)
  467. {
  468. if (TIMER_Done(NPC, "PickNewSniperPoint"))
  469. {
  470. TIMER_Set(NPC, "PickNewSniperPoint", Q_irand(15000, 25000));
  471. int SniperPoint = NPC_FindCombatPoint(NPC->currentOrigin, 0, NPC->currentOrigin, CP_SNIPE|CP_CLEAR|CP_HAS_ROUTE|CP_TRYFAR|CP_HORZ_DIST_COLL, 0, -1);
  472. if (SniperPoint!=-1)
  473. {
  474. NPC_SetCombatPoint(SniperPoint);
  475. NPC_SetMoveGoal( NPC, level.combatPoints[SniperPoint].origin, 20, qtrue, SniperPoint );
  476. }
  477. }
  478. if (Distance(NPC->currentOrigin, level.combatPoints[NPCInfo->combatPoint].origin)<50.0f)
  479. {
  480. Boba_FireDecide();
  481. }
  482. bool IsOnAPath = !!NPC_MoveToGoal(qtrue);
  483. // Resolve Blocked Problems
  484. //--------------------------
  485. if (NPCInfo->aiFlags&NPCAI_BLOCKED &&
  486. NPC->client->moveType!=MT_FLYSWIM &&
  487. ((level.time - NPCInfo->blockedDebounceTime)>3000)
  488. )
  489. {
  490. Boba_Printf("BLOCKED: Attempting Jump");
  491. if (IsOnAPath)
  492. {
  493. if (!NPC_TryJump(NPCInfo->blockedTargetPosition))
  494. {
  495. Boba_Printf(" Failed");
  496. }
  497. }
  498. }
  499. NPC_FaceEnemy(qtrue);
  500. NPC_UpdateAngles( qtrue, qtrue );
  501. }
  502. ////////////////////////////////////////////////////////////////////////////////////////
  503. // Call This function to make Boba actually shoot his current weapon
  504. ////////////////////////////////////////////////////////////////////////////////////////
  505. void Boba_Fire()
  506. {
  507. WeaponThink(qtrue);
  508. // If Actually Fired, Decide To Apply Alt Fire And Calc Next Attack Delay
  509. //------------------------------------------------------------------------
  510. if (ucmd.buttons&BUTTON_ATTACK)
  511. {
  512. switch (NPC->s.weapon)
  513. {
  514. case WP_ROCKET_LAUNCHER:
  515. TIMER_Set( NPC, "nextAttackDelay", Q_irand(1000, 2000));
  516. // Occasionally Shoot A Homing Missile
  517. //-------------------------------------
  518. if (!Q_irand(0,3))
  519. {
  520. ucmd.buttons &= ~BUTTON_ATTACK;
  521. ucmd.buttons |= BUTTON_ALT_ATTACK;
  522. NPC->client->fireDelay = Q_irand( 1000, 3000 );
  523. }
  524. break;
  525. case WP_DISRUPTOR:
  526. TIMER_Set(NPC, "nextAttackDelay", Q_irand(1000, 4000));
  527. // Occasionally Alt-Fire
  528. //-----------------------
  529. if (!Q_irand(0,3))
  530. {
  531. ucmd.buttons &= ~BUTTON_ATTACK;
  532. ucmd.buttons |= BUTTON_ALT_ATTACK;
  533. NPC->client->fireDelay = Q_irand( 1000, 3000 );
  534. }
  535. break;
  536. case WP_BLASTER:
  537. if (TIMER_Done(NPC, "nextBlasterAltFireDecide"))
  538. {
  539. if (Q_irand(0, (NPC->count*2)+3)>2)
  540. {
  541. TIMER_Set(NPC, "nextBlasterAltFireDecide", Q_irand(3000, 8000));
  542. if (!(NPCInfo->scriptFlags&SCF_ALT_FIRE))
  543. {
  544. Boba_Printf("ALT FIRE On");
  545. NPCInfo->scriptFlags |= SCF_ALT_FIRE;
  546. NPC_ChangeWeapon(WP_BLASTER); // Update Delay Timers
  547. }
  548. }
  549. else
  550. {
  551. TIMER_Set(NPC, "nextBlasterAltFireDecide", Q_irand(2000, 5000));
  552. if ( (NPCInfo->scriptFlags&SCF_ALT_FIRE))
  553. {
  554. Boba_Printf("ALT FIRE Off");
  555. NPCInfo->scriptFlags &=~SCF_ALT_FIRE;
  556. NPC_ChangeWeapon(WP_BLASTER); // Update Delay Timers
  557. }
  558. }
  559. }
  560. // Occasionally Alt Fire
  561. //-----------------------
  562. if (NPCInfo->scriptFlags&SCF_ALT_FIRE)
  563. {
  564. ucmd.buttons &= ~BUTTON_ATTACK;
  565. ucmd.buttons |= BUTTON_ALT_ATTACK;
  566. }
  567. break;
  568. }
  569. }
  570. }
  571. ////////////////////////////////////////////////////////////////////////////////////////
  572. // Call this function to see if Fett should fire his current weapon
  573. ////////////////////////////////////////////////////////////////////////////////////////
  574. void Boba_FireDecide( void )
  575. {
  576. // Any Reason Not To Shoot?
  577. //--------------------------
  578. if (!NPC || // Only NPCs
  579. !NPC->client || // Only Clients
  580. NPC->client->NPC_class!=CLASS_BOBAFETT || // Only Boba
  581. !NPC->enemy || // Only If There Is An Enemy
  582. NPC->s.weapon==WP_NONE || // Only If Using A Valid Weapon
  583. !TIMER_Done(NPC, "nextAttackDelay") || // Only If Ready To Shoot Again
  584. !Boba_CanSeeEnemy(NPC) // Only If Enemy Recently Seen
  585. )
  586. {
  587. return;
  588. }
  589. // Now Check Weapon Specific Parameters To See If We Should Shoot Or Not
  590. //-----------------------------------------------------------------------
  591. switch (NPC->s.weapon)
  592. {
  593. case WP_ROCKET_LAUNCHER:
  594. if (Distance(NPC->currentOrigin, NPC->enemy->currentOrigin)>400.0f)
  595. {
  596. Boba_Fire();
  597. }
  598. break;
  599. case WP_DISRUPTOR:
  600. // TODO: Add Conditions Here
  601. Boba_Fire();
  602. break;
  603. case WP_BLASTER:
  604. // TODO: Add Conditions Here
  605. Boba_Fire();
  606. break;
  607. }
  608. }
  609. ////////////////////////////////////////////////////////////////////////////////////////
  610. // Tactics avaliable to Boba Fett:
  611. // --------------------------------
  612. // BTS_RIFLE, // Uses Jedi / Seeker Movement
  613. // BTS_MISSILE, // Uses Jedi / Seeker Movement
  614. // BTS_SNIPER, // Uses Special Movement Internal To This File
  615. // BTS_FLAMETHROW, // Locked In Place
  616. // BTS_AMBUSHWAIT, // Goto CP & Wait
  617. //
  618. //
  619. // Weapons available to Boba Fett:
  620. // --------------------------------
  621. // WP_NONE (Flame Thrower)
  622. // WP_ROCKET_LAUNCHER
  623. // WP_BLASTER
  624. // WP_DISRUPTOR
  625. //
  626. ////////////////////////////////////////////////////////////////////////////////////////
  627. void Boba_TacticsSelect()
  628. {
  629. // Don't Change Tactics For A Little While
  630. //------------------------------------------
  631. TIMER_Set(NPC, "Boba_TacticsSelect", Q_irand(8000, 15000));
  632. int nextState = NPCInfo->localState;
  633. // Get Some Data That Will Help With The Selection Of The Next Tactic
  634. //--------------------------------------------------------------------
  635. bool enemyAlive = (NPC->enemy->health>0);
  636. float enemyDistance = Distance(NPC->currentOrigin, NPC->enemy->currentOrigin);
  637. bool enemyInFlameRange = (enemyDistance<BOBA_FLAMETHROWRANGE);
  638. bool enemyInRocketRange = (enemyDistance>BOBA_ROCKETRANGEMIN && enemyDistance<BOBA_ROCKETRANGEMAX);
  639. bool enemyRecentlySeen = Boba_CanSeeEnemy(NPC);
  640. // Enemy Is Really Close
  641. //-----------------------
  642. if (!enemyAlive)
  643. {
  644. nextState = BTS_RIFLE;
  645. }
  646. else if (enemyInFlameRange)
  647. {
  648. // If It's Been Long Enough Since Our Last Flame Blast, Try To Torch The Enemy
  649. //-----------------------------------------------------------------------------
  650. if (TIMER_Done(NPC, "nextFlameDelay"))
  651. {
  652. nextState = BTS_FLAMETHROW;
  653. }
  654. // Otherwise, He's Probably Too Close, So Try To Get Clear Of Him
  655. //----------------------------------------------------------------
  656. else
  657. {
  658. nextState = BTS_RIFLE;
  659. }
  660. }
  661. // Recently Saw The Enemy, Time For Some Good Ole Fighten!
  662. //---------------------------------------------------------
  663. else if (enemyRecentlySeen)
  664. {
  665. // At First, Boba will prefer to use his blaster against the player, but
  666. // the more times he is driven away (NPC->count), he will be less likely to
  667. // choose the blaster, and more likely to go for the missile launcher
  668. nextState = (!enemyInRocketRange || Q_irand(0, NPC->count)<1)?(BTS_RIFLE):(BTS_MISSILE);
  669. }
  670. // Hmmm... Havn't Seen The Player In A While, We Might Want To Try Something Sneaky
  671. //-----------------------------------------------------------------------------------
  672. else
  673. {
  674. bool SnipePointsNear = false; // TODO
  675. bool AmbushPointNear = false; // TODO
  676. if (Q_irand(0, NPC->count)>0)
  677. {
  678. int SniperPoint = NPC_FindCombatPoint(NPC->currentOrigin, 0, NPC->currentOrigin, CP_SNIPE|CP_CLEAR|CP_HAS_ROUTE|CP_TRYFAR|CP_HORZ_DIST_COLL, 0, -1);
  679. if (SniperPoint!=-1)
  680. {
  681. NPC_SetCombatPoint(SniperPoint);
  682. NPC_SetMoveGoal( NPC, level.combatPoints[SniperPoint].origin, 20, qtrue, SniperPoint );
  683. TIMER_Set(NPC, "PickNewSniperPoint", Q_irand(15000, 25000));
  684. SnipePointsNear = true;
  685. }
  686. }
  687. if (SnipePointsNear && TIMER_Done(NPC, "Boba_NoSniperTime"))
  688. {
  689. TIMER_Set(NPC, "Boba_NoSniperTime", 120000); // Don't snipe again for a while
  690. TIMER_Set(NPC, "Boba_TacticsSelect", Q_irand(35000, 45000));// More patience here
  691. nextState = BTS_SNIPER;
  692. }
  693. else if (AmbushPointNear)
  694. {
  695. TIMER_Set(NPC, "Boba_TacticsSelect", Q_irand(15000, 25000));// More patience here
  696. nextState = BTS_AMBUSHWAIT;
  697. }
  698. else
  699. {
  700. nextState = (!enemyInRocketRange || Q_irand(0, NPC->count)<1)?(BTS_RIFLE):(BTS_MISSILE);
  701. }
  702. }
  703. // The Next State Has Been Selected, Now Change Weapon If Necessary
  704. //------------------------------------------------------------------
  705. if (nextState!=NPCInfo->localState)
  706. {
  707. NPCInfo->localState = nextState;
  708. switch (NPCInfo->localState)
  709. {
  710. case BTS_FLAMETHROW:
  711. Boba_Printf("NEW TACTIC: Flame Thrower");
  712. Boba_ChangeWeapon(WP_NONE);
  713. Boba_DoFlameThrower(NPC);
  714. break;
  715. case BTS_RIFLE:
  716. Boba_Printf("NEW TACTIC: Rifle");
  717. Boba_ChangeWeapon(WP_BLASTER);
  718. break;
  719. case BTS_MISSILE:
  720. Boba_Printf("NEW TACTIC: Rocket Launcher");
  721. Boba_ChangeWeapon(WP_ROCKET_LAUNCHER);
  722. break;
  723. case BTS_SNIPER:
  724. Boba_Printf("NEW TACTIC: Sniper");
  725. Boba_ChangeWeapon(WP_DISRUPTOR);
  726. break;
  727. case BTS_AMBUSHWAIT:
  728. Boba_Printf("NEW TACTIC: Ambush");
  729. Boba_ChangeWeapon(WP_NONE);
  730. break;
  731. }
  732. }
  733. }
  734. ////////////////////////////////////////////////////////////////////////////////////////
  735. // Tactics
  736. //
  737. // This function is called right after Update()
  738. // If returns true, Jedi and Seeker AI not used for movement
  739. //
  740. ////////////////////////////////////////////////////////////////////////////////////////
  741. bool Boba_Tactics()
  742. {
  743. if (!NPC->enemy)
  744. {
  745. return false;
  746. }
  747. // Think About Changing Tactics
  748. //------------------------------
  749. if (TIMER_Done(NPC, "Boba_TacticsSelect"))
  750. {
  751. Boba_TacticsSelect();
  752. }
  753. // These Tactics Require Seeker & Jedi Movement
  754. //----------------------------------------------
  755. if (!NPCInfo->localState ||
  756. NPCInfo->localState==BTS_RIFLE ||
  757. NPCInfo->localState==BTS_MISSILE)
  758. {
  759. return false;
  760. }
  761. // Flame Thrower - Locked In Place
  762. //---------------------------------
  763. if (NPCInfo->localState==BTS_FLAMETHROW)
  764. {
  765. Boba_DoFlameThrower( NPC );
  766. }
  767. // Sniper - Move Around, And Take Shots
  768. //--------------------------------------
  769. else if (NPCInfo->localState==BTS_SNIPER)
  770. {
  771. Boba_DoSniper( NPC );
  772. }
  773. // Ambush Wait
  774. //------------
  775. else if (NPCInfo->localState==BTS_AMBUSHWAIT)
  776. {
  777. Boba_DoAmbushWait( NPC );
  778. }
  779. NPC_FacePosition( NPC->enemy->currentOrigin, qtrue);
  780. NPC_UpdateAngles(qtrue, qtrue);
  781. return true; // Do Not Use Normal Jedi Or Seeker Movement
  782. }
  783. ////////////////////////////////////////////////////////////////////////////////////////
  784. //
  785. ////////////////////////////////////////////////////////////////////////////////////////
  786. bool Boba_Respawn()
  787. {
  788. int cp = -1;
  789. // Try To Predict Where The Enemy Is Going
  790. //-----------------------------------------
  791. if (AverageEnemyDirectionSamples && NPC->behaviorSet[BSET_DEATH]==0)
  792. {
  793. vec3_t endPos;
  794. VectorMA(NPC->enemy->currentOrigin, 1000.0f / (float)AverageEnemyDirectionSamples, AverageEnemyDirection, endPos);
  795. cp = NPC_FindCombatPoint(endPos, 0, endPos, CP_FLEE|CP_TRYFAR|CP_HORZ_DIST_COLL, 0, -1);
  796. Boba_Printf("Attempting Predictive Spawn Point");
  797. }
  798. // If That Failed, Try To Go Directly To The Enemy
  799. //-------------------------------------------------
  800. if (cp==-1)
  801. {
  802. cp = NPC_FindCombatPoint(NPC->enemy->currentOrigin, 0, NPC->enemy->currentOrigin, CP_FLEE|CP_TRYFAR|CP_HORZ_DIST_COLL, 0, -1);
  803. Boba_Printf("Attempting Closest Current Spawn Point");
  804. }
  805. // If We've Found One, Go There
  806. //------------------------------
  807. if (cp!=-1)
  808. {
  809. NPC_SetCombatPoint( cp );
  810. NPCInfo->surrenderTime = 0;
  811. NPC->health = NPC->max_health;
  812. NPC->svFlags &=~SVF_NOCLIENT;
  813. NPC->count ++; // This is the number of times spawned
  814. G_SetOrigin(NPC, level.combatPoints[cp].origin);
  815. AverageEnemyDirectionSamples = 0;
  816. VectorClear(AverageEnemyDirection);
  817. Boba_Printf("Found Spawn Point (%d)", cp);
  818. return true;
  819. }
  820. assert(0); // Yea, that's bad...
  821. Boba_Printf("FAILED TO FIND SPAWN POINT");
  822. return false;
  823. }
  824. ////////////////////////////////////////////////////////////////////////////////////////
  825. //
  826. ////////////////////////////////////////////////////////////////////////////////////////
  827. void Boba_Update()
  828. {
  829. // Never Forget The Player... Never.
  830. //-----------------------------------
  831. if (player && player->inuse && !NPC->enemy)
  832. {
  833. G_SetEnemy(NPC, player);
  834. NPC->svFlags |= SVF_LOCKEDENEMY; // Don't forget about the enemy once you've found him
  835. }
  836. // Hey, This Is Boba, He Tests The Trace All The Time
  837. //----------------------------------------------------
  838. if (NPC->enemy)
  839. {
  840. if (!(NPC->svFlags&SVF_NOCLIENT))
  841. {
  842. trace_t testTrace;
  843. vec3_t eyes;
  844. CalcEntitySpot( NPC, SPOT_HEAD_LEAN, eyes );
  845. gi.trace (&testTrace, eyes, NULL, NULL, NPC->enemy->currentOrigin, NPC->s.number, MASK_SHOT);
  846. bool wasSeen = Boba_CanSeeEnemy(NPC);
  847. if (!testTrace.startsolid &&
  848. !testTrace.allsolid &&
  849. testTrace.entityNum == NPC->enemy->s.number)
  850. {
  851. NPCInfo->enemyLastSeenTime = level.time;
  852. NPCInfo->enemyLastHeardTime = level.time;
  853. VectorCopy(NPC->enemy->currentOrigin, NPCInfo->enemyLastSeenLocation);
  854. VectorCopy(NPC->enemy->currentOrigin, NPCInfo->enemyLastHeardLocation);
  855. }
  856. else if (gi.inPVS( NPC->enemy->currentOrigin, NPC->currentOrigin))
  857. {
  858. NPCInfo->enemyLastHeardTime = level.time;
  859. VectorCopy(NPC->enemy->currentOrigin, NPCInfo->enemyLastHeardLocation);
  860. }
  861. if (g_bobaDebug->integer)
  862. {
  863. bool nowSeen = Boba_CanSeeEnemy(NPC);
  864. if (!wasSeen && nowSeen)
  865. {
  866. Boba_Printf("Enemy Seen");
  867. }
  868. if (wasSeen && !nowSeen)
  869. {
  870. Boba_Printf("Enemy Lost");
  871. }
  872. CG_DrawEdge(NPC->currentOrigin, NPC->enemy->currentOrigin, (nowSeen)?(EDGE_IMPACT_SAFE):(EDGE_IMPACT_POSSIBLE));
  873. }
  874. }
  875. if (!NPCInfo->surrenderTime)
  876. {
  877. if ((level.time - NPCInfo->enemyLastSeenTime)>20000 && TIMER_Done(NPC, "TooLongGoneRespawn"))
  878. {
  879. TIMER_Set(NPC, "TooLongGoneRespawn", 30000); // Give him some time to get to you before trying again
  880. Boba_Printf("Gone Too Long, Attempting Respawn Even Though Not Hiding");
  881. Boba_Respawn();
  882. }
  883. }
  884. }
  885. // Make Sure He Always Appears In The Last Area With Full Health When His Death Script Is Turned On
  886. //--------------------------------------------------------------------------------------------------
  887. if (!BobaHadDeathScript && NPC->behaviorSet[BSET_DEATH]!=0)
  888. {
  889. if (!gi.inPVS(NPC->enemy->currentOrigin, NPC->currentOrigin))
  890. {
  891. Boba_Printf("Attempting Final Battle Spawn...");
  892. if (Boba_Respawn())
  893. {
  894. BobaHadDeathScript = true;
  895. }
  896. else
  897. {
  898. Boba_Printf("Failed");
  899. }
  900. }
  901. }
  902. // Don't Forget To Turn Off That Flame Thrower, Mr. Fett - You're Waisting Precious Natural Gases
  903. //------------------------------------------------------------------------------------------------
  904. if ((NPCInfo->aiFlags&NPCAI_FLAMETHROW) && (TIMER_Done(NPC, "flameTime")))
  905. {
  906. Boba_StopFlameThrower(NPC);
  907. }
  908. // Occasionally A Jump Turns Into A Rocket Fly
  909. //---------------------------------------------
  910. if ( NPC->client->ps.groundEntityNum == ENTITYNUM_NONE
  911. && NPC->client->ps.forceJumpZStart
  912. && !Q_irand( 0, 10 ) )
  913. {//take off
  914. Boba_FlyStart( NPC );
  915. }
  916. // If Hurting, Try To Run Away
  917. //-----------------------------
  918. if (!NPCInfo->surrenderTime && (NPC->health<NPC->max_health/10))
  919. {
  920. Boba_Printf("Time To Surrender, Searching For Flee Point");
  921. // Find The Closest Flee Point That I Can Get To
  922. //-----------------------------------------------
  923. int cp = NPC_FindCombatPoint(NPC->currentOrigin, 0, NPC->currentOrigin, CP_FLEE|CP_HAS_ROUTE|CP_TRYFAR|CP_HORZ_DIST_COLL, 0, -1);
  924. if (cp!=-1)
  925. {
  926. NPC_SetCombatPoint( cp );
  927. NPC_SetMoveGoal( NPC, level.combatPoints[cp].origin, 8, qtrue, cp );
  928. if (NPC->count<6)
  929. {
  930. NPCInfo->surrenderTime = level.time + Q_irand(5000, 10000) + 1000*(6-NPC->count);
  931. }
  932. else
  933. {
  934. NPCInfo->surrenderTime = level.time + Q_irand(5000, 10000);
  935. }
  936. }
  937. else
  938. {
  939. Boba_Printf(" Failure");
  940. }
  941. }
  942. }
  943. ////////////////////////////////////////////////////////////////////////////////////////
  944. //
  945. ////////////////////////////////////////////////////////////////////////////////////////
  946. bool Boba_Flee()
  947. {
  948. bool EnemyRecentlySeen = ((level.time - NPCInfo->enemyLastSeenTime)<10000);
  949. bool ReachedEscapePoint = (Distance(level.combatPoints[NPCInfo->combatPoint].origin, NPC->currentOrigin)<50.0f);
  950. bool HasBeenGoneEnough = (level.time>NPCInfo->surrenderTime || (level.time - NPCInfo->enemyLastSeenTime)>400000);
  951. // Is It Time To Come Back For Some More?
  952. //----------------------------------------
  953. if (!EnemyRecentlySeen || ReachedEscapePoint)
  954. {
  955. NPC->svFlags |= SVF_NOCLIENT;
  956. if (HasBeenGoneEnough)
  957. {
  958. if ((level.time - NPCInfo->enemyLastSeenTime)>400000)
  959. {
  960. Boba_Printf(" Gone Too Long, Attempting Respawn");
  961. }
  962. if (Boba_Respawn())
  963. {
  964. return true;
  965. }
  966. }
  967. else if (ReachedEscapePoint && (NPCInfo->surrenderTime - level.time)>3000)
  968. {
  969. if (TIMER_Done(NPC, "SpookPlayerTimer"))
  970. {
  971. vec3_t testDirection;
  972. TIMER_Set(NPC, "SpookPlayerTimer", Q_irand(2000, 10000));
  973. switch(Q_irand(0, 1))
  974. {
  975. case 0:
  976. Boba_Printf("SPOOK: Dust");
  977. Boba_DustFallNear(NPC->enemy->currentOrigin, Q_irand(1,2));
  978. break;
  979. case 1:
  980. Boba_Printf("SPOOK: Footsteps");
  981. testDirection[0] = (random() * 0.5f) - 1.0f;
  982. testDirection[0] += (testDirection[0]>0.0f)?(0.5f):(-0.5f);
  983. testDirection[1] = (random() * 0.5f) - 1.0f;
  984. testDirection[1] += (testDirection[1]>0.0f)?(0.5f):(-0.5f);
  985. testDirection[2] = 1.0f;
  986. VectorMA(NPC->enemy->currentOrigin, 400.0f, testDirection, BobaFootStepLoc);
  987. BobaFootStepCount = Q_irand(3,8);
  988. break;
  989. }
  990. }
  991. if (BobaFootStepCount && TIMER_Done(NPC, "BobaFootStepFakeTimer"))
  992. {
  993. TIMER_Set(NPC, "BobaFootStepFakeTimer", Q_irand(300, 800));
  994. BobaFootStepCount --;
  995. G_SoundAtSpot(BobaFootStepLoc, G_SoundIndex(va("sound/player/footsteps/boot%d", Q_irand(1,4))), qtrue);
  996. }
  997. if (TIMER_Done(NPC, "ResampleEnemyDirection") && NPC->enemy->resultspeed>10.0f)
  998. {
  999. TIMER_Set(NPC, "ResampleEnemyDirection", Q_irand(500, 1000));
  1000. AverageEnemyDirectionSamples ++;
  1001. vec3_t moveDir;
  1002. VectorCopy(NPC->enemy->client->ps.velocity, moveDir);
  1003. VectorNormalize(moveDir);
  1004. VectorAdd(AverageEnemyDirection, moveDir, AverageEnemyDirection);
  1005. }
  1006. if (g_bobaDebug->integer && AverageEnemyDirectionSamples)
  1007. {
  1008. vec3_t endPos;
  1009. VectorMA(NPC->enemy->currentOrigin, 500.0f / (float)AverageEnemyDirectionSamples, AverageEnemyDirection, endPos);
  1010. CG_DrawEdge(NPC->enemy->currentOrigin, endPos, EDGE_IMPACT_POSSIBLE);
  1011. }
  1012. }
  1013. }
  1014. else
  1015. {
  1016. NPCInfo->surrenderTime += 100;
  1017. }
  1018. // Finish The Flame Thrower First...
  1019. //-----------------------------------
  1020. if (NPCInfo->aiFlags&NPCAI_FLAMETHROW)
  1021. {
  1022. Boba_DoFlameThrower( NPC );
  1023. NPC_FacePosition( NPC->enemy->currentOrigin, qtrue);
  1024. NPC_UpdateAngles(qtrue, qtrue);
  1025. return true;
  1026. }
  1027. bool IsOnAPath = !!NPC_MoveToGoal(qtrue);
  1028. if (!ReachedEscapePoint &&
  1029. NPCInfo->aiFlags&NPCAI_BLOCKED &&
  1030. NPC->client->moveType!=MT_FLYSWIM &&
  1031. ((level.time - NPCInfo->blockedDebounceTime)>1000)
  1032. )
  1033. {
  1034. if (!Boba_CanSeeEnemy(NPC) && Distance(NPC->currentOrigin, level.combatPoints[NPCInfo->combatPoint].origin)<200)
  1035. {
  1036. Boba_Printf("BLOCKED: Just Teleporting There");
  1037. G_SetOrigin(NPC, level.combatPoints[NPCInfo->combatPoint].origin);
  1038. }
  1039. else
  1040. {
  1041. Boba_Printf("BLOCKED: Attempting Jump");
  1042. if (IsOnAPath)
  1043. {
  1044. if (NPC_TryJump(NPCInfo->blockedTargetPosition))
  1045. {
  1046. }
  1047. else
  1048. {
  1049. Boba_Printf(" Failed");
  1050. }
  1051. }
  1052. else if (EnemyRecentlySeen)
  1053. {
  1054. if (NPC_TryJump(NPCInfo->enemyLastSeenLocation))
  1055. {
  1056. }
  1057. else
  1058. {
  1059. Boba_Printf(" Failed");
  1060. }
  1061. }
  1062. }
  1063. }
  1064. NPC_UpdateAngles( qtrue, qtrue );
  1065. return true;
  1066. }