AI_Droid.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557
  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. //static void R5D2_LookAround( void );
  5. float NPC_GetPainChance( gentity_t *self, int damage );
  6. #define TURN_OFF 0x00000100
  7. //Local state enums
  8. enum
  9. {
  10. LSTATE_NONE = 0,
  11. LSTATE_BACKINGUP,
  12. LSTATE_SPINNING,
  13. LSTATE_PAIN,
  14. LSTATE_DROP
  15. };
  16. /*
  17. -------------------------
  18. R2D2_PartsMove
  19. -------------------------
  20. */
  21. void R2D2_PartsMove(void)
  22. {
  23. // Front 'eye' lense
  24. if ( TIMER_Done(NPC,"eyeDelay") )
  25. {
  26. NPC->pos1[1] = AngleNormalize360( NPC->pos1[1]);
  27. NPC->pos1[0]+=Q_irand( -20, 20 ); // Roll
  28. NPC->pos1[1]=Q_irand( -20, 20 );
  29. NPC->pos1[2]=Q_irand( -20, 20 );
  30. if (NPC->genericBone1)
  31. {
  32. gi.G2API_SetBoneAnglesIndex( &NPC->ghoul2[NPC->playerModel], NPC->genericBone1, NPC->pos1, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL );
  33. }
  34. TIMER_Set( NPC, "eyeDelay", Q_irand( 100, 1000 ) );
  35. }
  36. }
  37. /*
  38. -------------------------
  39. NPC_BSDroid_Idle
  40. -------------------------
  41. */
  42. void Droid_Idle( void )
  43. {
  44. // VectorCopy( NPCInfo->investigateGoal, lookPos );
  45. // NPC_FacePosition( lookPos );
  46. }
  47. /*
  48. -------------------------
  49. R2D2_TurnAnims
  50. -------------------------
  51. */
  52. void R2D2_TurnAnims ( void )
  53. {
  54. float turndelta;
  55. int anim;
  56. turndelta = AngleDelta(NPC->currentAngles[YAW], NPCInfo->desiredYaw);
  57. if ((fabs(turndelta) > 20) && ((NPC->client->NPC_class == CLASS_R2D2) || (NPC->client->NPC_class == CLASS_R5D2)))
  58. {
  59. anim = NPC->client->ps.legsAnim;
  60. if (turndelta<0)
  61. {
  62. if (anim != BOTH_TURN_LEFT1)
  63. {
  64. NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_TURN_LEFT1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
  65. }
  66. }
  67. else
  68. {
  69. if (anim != BOTH_TURN_RIGHT1)
  70. {
  71. NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_TURN_RIGHT1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
  72. }
  73. }
  74. }
  75. else
  76. {
  77. NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_RUN1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
  78. }
  79. }
  80. /*
  81. -------------------------
  82. Droid_Patrol
  83. -------------------------
  84. */
  85. void Droid_Patrol( void )
  86. {
  87. NPC->pos1[1] = AngleNormalize360( NPC->pos1[1]);
  88. if ( NPC->client && NPC->client->NPC_class != CLASS_GONK )
  89. {
  90. R2D2_PartsMove(); // Get his eye moving.
  91. R2D2_TurnAnims();
  92. }
  93. //If we have somewhere to go, then do that
  94. if ( UpdateGoal() )
  95. {
  96. ucmd.buttons |= BUTTON_WALKING;
  97. NPC_MoveToGoal( qtrue );
  98. if( NPC->client && NPC->client->NPC_class == CLASS_MOUSE )
  99. {
  100. NPCInfo->desiredYaw += sin(level.time*.5) * 25; // Weaves side to side a little
  101. if (TIMER_Done(NPC,"patrolNoise"))
  102. {
  103. G_SoundOnEnt( NPC, CHAN_VOICE_ATTEN, va("sound/chars/mouse/misc/mousego%d.wav", Q_irand(1, 3)) );
  104. TIMER_Set( NPC, "patrolNoise", Q_irand( 2000, 4000 ) );
  105. }
  106. }
  107. else if( NPC->client && NPC->client->NPC_class == CLASS_R2D2 )
  108. {
  109. if (TIMER_Done(NPC,"patrolNoise"))
  110. {
  111. G_SoundOnEnt( NPC, CHAN_VOICE_ATTEN, va("sound/chars/r2d2/misc/r2d2talk0%d.wav", Q_irand(1, 3)) );
  112. TIMER_Set( NPC, "patrolNoise", Q_irand( 2000, 4000 ) );
  113. }
  114. }
  115. else if( NPC->client && NPC->client->NPC_class == CLASS_R5D2 )
  116. {
  117. if (TIMER_Done(NPC,"patrolNoise"))
  118. {
  119. G_SoundOnEnt( NPC, CHAN_VOICE_ATTEN, va("sound/chars/r5d2/misc/r5talk%d.wav", Q_irand(1, 4)) );
  120. TIMER_Set( NPC, "patrolNoise", Q_irand( 2000, 4000 ) );
  121. }
  122. }
  123. if( NPC->client && NPC->client->NPC_class == CLASS_GONK )
  124. {
  125. if (TIMER_Done(NPC,"patrolNoise"))
  126. {
  127. G_SoundOnEnt( NPC, CHAN_VOICE_ATTEN, va("sound/chars/gonk/misc/gonktalk%d.wav", Q_irand(1, 2)) );
  128. TIMER_Set( NPC, "patrolNoise", Q_irand( 2000, 4000 ) );
  129. }
  130. }
  131. // else
  132. // {
  133. // R5D2_LookAround();
  134. // }
  135. }
  136. NPC_UpdateAngles( qtrue, qtrue );
  137. }
  138. /*
  139. -------------------------
  140. Droid_Run
  141. -------------------------
  142. */
  143. void Droid_Run( void )
  144. {
  145. R2D2_PartsMove();
  146. if ( NPCInfo->localState == LSTATE_BACKINGUP )
  147. {
  148. ucmd.forwardmove = -127;
  149. NPCInfo->desiredYaw += 5;
  150. NPCInfo->localState = LSTATE_NONE; // So he doesn't constantly backup.
  151. }
  152. else
  153. {
  154. ucmd.forwardmove = 64;
  155. //If we have somewhere to go, then do that
  156. if ( UpdateGoal() )
  157. {
  158. if (NPC_MoveToGoal( qfalse ))
  159. {
  160. NPCInfo->desiredYaw += sin(level.time*.5) * 5; // Weaves side to side a little
  161. }
  162. }
  163. }
  164. NPC_UpdateAngles( qtrue, qtrue );
  165. }
  166. /*
  167. -------------------------
  168. void Droid_Spin( void )
  169. -------------------------
  170. */
  171. void Droid_Spin( void )
  172. {
  173. vec3_t dir = {0,0,1};
  174. R2D2_TurnAnims();
  175. // Head is gone, spin and spark
  176. if ( NPC->client->NPC_class == CLASS_R5D2 )
  177. {
  178. // No head?
  179. if (gi.G2API_GetSurfaceRenderStatus( &NPC->ghoul2[NPC->playerModel], "head" ))
  180. {
  181. if (TIMER_Done(NPC,"smoke") && !TIMER_Done(NPC,"droidsmoketotal"))
  182. {
  183. TIMER_Set( NPC, "smoke", 100);
  184. G_PlayEffect( "volumetric/droid_smoke" , NPC->currentOrigin,dir);
  185. }
  186. if (TIMER_Done(NPC,"droidspark"))
  187. {
  188. TIMER_Set( NPC, "droidspark", Q_irand(100,500));
  189. G_PlayEffect( "sparks/spark", NPC->currentOrigin,dir);
  190. }
  191. ucmd.forwardmove = Q_irand( -64, 64);
  192. if (TIMER_Done(NPC,"roam"))
  193. {
  194. TIMER_Set( NPC, "roam", Q_irand( 250, 1000 ) );
  195. NPCInfo->desiredYaw = Q_irand( 0, 360 ); // Go in random directions
  196. }
  197. }
  198. else
  199. {
  200. if (TIMER_Done(NPC,"roam"))
  201. {
  202. NPCInfo->localState = LSTATE_NONE;
  203. }
  204. else
  205. {
  206. NPCInfo->desiredYaw = AngleNormalize360(NPCInfo->desiredYaw + 40); // Spin around
  207. }
  208. }
  209. }
  210. else
  211. {
  212. if (TIMER_Done(NPC,"roam"))
  213. {
  214. NPCInfo->localState = LSTATE_NONE;
  215. }
  216. else
  217. {
  218. NPCInfo->desiredYaw = AngleNormalize360(NPCInfo->desiredYaw + 40); // Spin around
  219. }
  220. }
  221. NPC_UpdateAngles( qtrue, qtrue );
  222. }
  223. /*
  224. -------------------------
  225. NPC_BSDroid_Pain
  226. -------------------------
  227. */
  228. void NPC_Droid_Pain( gentity_t *self, gentity_t *inflictor, gentity_t *other, const vec3_t point, int damage, int mod,int hitLoc )
  229. {
  230. int anim;
  231. float pain_chance;
  232. if ( self->NPC && self->NPC->ignorePain )
  233. {
  234. return;
  235. }
  236. VectorCopy( self->NPC->lastPathAngles, self->s.angles );
  237. if ( self->client->NPC_class == CLASS_R5D2 )
  238. {
  239. pain_chance = NPC_GetPainChance( self, damage );
  240. // Put it in pain
  241. if ( mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT || random() < pain_chance ) // Spin around in pain? Demp2 always does this
  242. {
  243. // Health is between 0-30 or was hit by a DEMP2 so pop his head
  244. if ( self->health < 30 || mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT )
  245. {
  246. if (!(self->spawnflags & 2)) // Doesn't have to ALWAYSDIE
  247. {
  248. if ((self->NPC->localState != LSTATE_SPINNING) &&
  249. (!gi.G2API_GetSurfaceRenderStatus( &self->ghoul2[self->playerModel], "head" )))
  250. {
  251. gi.G2API_SetSurfaceOnOff( &self->ghoul2[self->playerModel], "head", TURN_OFF );
  252. // G_PlayEffect( "small_chunks" , self->currentOrigin );
  253. G_PlayEffect( "chunks/r5d2head", self->currentOrigin );
  254. self->s.powerups |= ( 1 << PW_SHOCKED );
  255. self->client->ps.powerups[PW_SHOCKED] = level.time + 3000;
  256. TIMER_Set( self, "droidsmoketotal", 5000);
  257. TIMER_Set( self, "droidspark", 100);
  258. self->NPC->localState = LSTATE_SPINNING;
  259. }
  260. }
  261. }
  262. // Just give him normal pain for a little while
  263. else
  264. {
  265. anim = self->client->ps.legsAnim;
  266. if ( anim == BOTH_STAND2 ) // On two legs?
  267. {
  268. anim = BOTH_PAIN1;
  269. }
  270. else // On three legs
  271. {
  272. anim = BOTH_PAIN2;
  273. }
  274. NPC_SetAnim( self, SETANIM_BOTH, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
  275. // Spin around in pain
  276. self->NPC->localState = LSTATE_SPINNING;
  277. TIMER_Set( self, "roam", Q_irand(1000,2000));
  278. }
  279. }
  280. }
  281. else if (self->client->NPC_class == CLASS_MOUSE)
  282. {
  283. if ( mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT )
  284. {
  285. self->NPC->localState = LSTATE_SPINNING;
  286. self->s.powerups |= ( 1 << PW_SHOCKED );
  287. self->client->ps.powerups[PW_SHOCKED] = level.time + 3000;
  288. }
  289. else
  290. {
  291. self->NPC->localState = LSTATE_BACKINGUP;
  292. }
  293. self->NPC->scriptFlags &= ~SCF_LOOK_FOR_ENEMIES;
  294. }
  295. else if ((self->client->NPC_class == CLASS_R2D2))
  296. {
  297. pain_chance = NPC_GetPainChance( self, damage );
  298. if ( mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT || random() < pain_chance ) // Spin around in pain? Demp2 always does this
  299. {
  300. anim = self->client->ps.legsAnim;
  301. if ( anim == BOTH_STAND2 ) // On two legs?
  302. {
  303. anim = BOTH_PAIN1;
  304. }
  305. else // On three legs
  306. {
  307. anim = BOTH_PAIN2;
  308. }
  309. NPC_SetAnim( self, SETANIM_BOTH, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
  310. // Spin around in pain
  311. self->NPC->localState = LSTATE_SPINNING;
  312. TIMER_Set( self, "roam", Q_irand(1000,2000));
  313. }
  314. }
  315. else if ( self->client->NPC_class == CLASS_INTERROGATOR && ( mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT ) && other )
  316. {
  317. vec3_t dir;
  318. VectorSubtract( self->currentOrigin, other->currentOrigin, dir );
  319. VectorNormalize( dir );
  320. VectorMA( self->client->ps.velocity, 550, dir, self->client->ps.velocity );
  321. self->client->ps.velocity[2] -= 127;
  322. }
  323. NPC_Pain( self, inflictor, other, point, damage, mod);
  324. }
  325. /*
  326. -------------------------
  327. Droid_Pain
  328. -------------------------
  329. */
  330. void Droid_Pain(void)
  331. {
  332. if (TIMER_Done(NPC,"droidpain")) //He's done jumping around
  333. {
  334. NPCInfo->localState = LSTATE_NONE;
  335. }
  336. }
  337. /*
  338. -------------------------
  339. NPC_Mouse_Precache
  340. -------------------------
  341. */
  342. void NPC_Mouse_Precache( void )
  343. {
  344. int i;
  345. for (i = 1; i < 4; i++)
  346. {
  347. G_SoundIndex( va( "sound/chars/mouse/misc/mousego%d.wav", i ) );
  348. }
  349. G_EffectIndex( "env/small_explode" );
  350. G_SoundIndex( "sound/chars/mouse/misc/death1" );
  351. G_SoundIndex( "sound/chars/mouse/misc/mouse_lp" );
  352. }
  353. /*
  354. -------------------------
  355. NPC_R5D2_Precache
  356. -------------------------
  357. */
  358. void NPC_R5D2_Precache(void)
  359. {
  360. for ( int i = 1; i < 5; i++)
  361. {
  362. G_SoundIndex( va( "sound/chars/r5d2/misc/r5talk%d.wav", i ) );
  363. }
  364. G_SoundIndex( "sound/chars/mark2/misc/mark2_explo" ); // ??
  365. G_SoundIndex( "sound/chars/r2d2/misc/r2_move_lp2.wav" );
  366. G_EffectIndex( "env/med_explode");
  367. G_EffectIndex( "volumetric/droid_smoke" );
  368. G_EffectIndex( "chunks/r5d2head");
  369. }
  370. /*
  371. -------------------------
  372. NPC_R2D2_Precache
  373. -------------------------
  374. */
  375. void NPC_R2D2_Precache(void)
  376. {
  377. for ( int i = 1; i < 4; i++)
  378. {
  379. G_SoundIndex( va( "sound/chars/r2d2/misc/r2d2talk0%d.wav", i ) );
  380. }
  381. G_SoundIndex( "sound/chars/mark2/misc/mark2_explo" ); // ??
  382. G_SoundIndex( "sound/chars/r2d2/misc/r2_move_lp.wav" );
  383. G_EffectIndex( "env/med_explode");
  384. }
  385. /*
  386. -------------------------
  387. NPC_Gonk_Precache
  388. -------------------------
  389. */
  390. void NPC_Gonk_Precache( void )
  391. {
  392. G_SoundIndex("sound/chars/gonk/misc/gonktalk1.wav");
  393. G_SoundIndex("sound/chars/gonk/misc/gonktalk2.wav");
  394. G_SoundIndex("sound/chars/gonk/misc/death1.wav");
  395. G_SoundIndex("sound/chars/gonk/misc/death2.wav");
  396. G_SoundIndex("sound/chars/gonk/misc/death3.wav");
  397. G_EffectIndex( "env/med_explode");
  398. }
  399. /*
  400. -------------------------
  401. NPC_Protocol_Precache
  402. -------------------------
  403. */
  404. void NPC_Protocol_Precache( void )
  405. {
  406. G_SoundIndex( "sound/chars/mark2/misc/mark2_explo" );
  407. G_EffectIndex( "env/med_explode");
  408. }
  409. /*
  410. static void R5D2_OffsetLook( float offset, vec3_t out )
  411. {
  412. vec3_t angles, forward, temp;
  413. GetAnglesForDirection( NPC->currentOrigin, NPCInfo->investigateGoal, angles );
  414. angles[YAW] += offset;
  415. AngleVectors( angles, forward, NULL, NULL );
  416. VectorMA( NPC->currentOrigin, 64, forward, out );
  417. CalcEntitySpot( NPC, SPOT_HEAD, temp );
  418. out[2] = temp[2];
  419. }
  420. */
  421. /*
  422. -------------------------
  423. R5D2_LookAround
  424. -------------------------
  425. */
  426. /*
  427. static void R5D2_LookAround( void )
  428. {
  429. vec3_t lookPos;
  430. float perc = (float) ( level.time - NPCInfo->pauseTime ) / (float) NPCInfo->investigateDebounceTime;
  431. //Keep looking at the spot
  432. if ( perc < 0.25 )
  433. {
  434. VectorCopy( NPCInfo->investigateGoal, lookPos );
  435. }
  436. else if ( perc < 0.5f ) //Look up but straight ahead
  437. {
  438. R5D2_OffsetLook( 0.0f, lookPos );
  439. }
  440. else if ( perc < 0.75f ) //Look right
  441. {
  442. R5D2_OffsetLook( 45.0f, lookPos );
  443. }
  444. else //Look left
  445. {
  446. R5D2_OffsetLook( -45.0f, lookPos );
  447. }
  448. NPC_FacePosition( lookPos );
  449. }
  450. */
  451. /*
  452. -------------------------
  453. NPC_BSDroid_Default
  454. -------------------------
  455. */
  456. void NPC_BSDroid_Default( void )
  457. {
  458. if ( NPCInfo->localState == LSTATE_SPINNING )
  459. {
  460. Droid_Spin();
  461. }
  462. else if ( NPCInfo->localState == LSTATE_PAIN )
  463. {
  464. Droid_Pain();
  465. }
  466. else if ( NPCInfo->localState == LSTATE_DROP )
  467. {
  468. NPC_UpdateAngles( qtrue, qtrue );
  469. ucmd.upmove = crandom() * 64;
  470. }
  471. else if ( NPCInfo->scriptFlags & SCF_LOOK_FOR_ENEMIES )
  472. {
  473. Droid_Patrol();
  474. }
  475. else
  476. {
  477. Droid_Run();
  478. }
  479. }