AI_Mark1.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746
  1. // leave this line at the top of all AI_xxxx.cpp files for PCH reasons...
  2. #include "g_headers.h"
  3. #include "b_local.h"
  4. #include "g_nav.h"
  5. #define MIN_MELEE_RANGE 320
  6. #define MIN_MELEE_RANGE_SQR ( MIN_MELEE_RANGE * MIN_MELEE_RANGE )
  7. #define MIN_DISTANCE 128
  8. #define MIN_DISTANCE_SQR ( MIN_DISTANCE * MIN_DISTANCE )
  9. #define TURN_OFF 0x00000100
  10. #define LEFT_ARM_HEALTH 40
  11. #define RIGHT_ARM_HEALTH 40
  12. #define AMMO_POD_HEALTH 40
  13. #define BOWCASTER_VELOCITY 1300
  14. #define BOWCASTER_NPC_DAMAGE_EASY 12
  15. #define BOWCASTER_NPC_DAMAGE_NORMAL 24
  16. #define BOWCASTER_NPC_DAMAGE_HARD 36
  17. #define BOWCASTER_SIZE 2
  18. #define BOWCASTER_SPLASH_DAMAGE 0
  19. #define BOWCASTER_SPLASH_RADIUS 0
  20. //Local state enums
  21. enum
  22. {
  23. LSTATE_NONE = 0,
  24. LSTATE_ASLEEP,
  25. LSTATE_WAKEUP,
  26. LSTATE_FIRED0,
  27. LSTATE_FIRED1,
  28. LSTATE_FIRED2,
  29. LSTATE_FIRED3,
  30. LSTATE_FIRED4,
  31. };
  32. qboolean NPC_CheckPlayerTeamStealth( void );
  33. gentity_t *CreateMissile( vec3_t org, vec3_t dir, float vel, int life, gentity_t *owner, qboolean altFire = qfalse );
  34. void Mark1_BlasterAttack(qboolean advance);
  35. void DeathFX( gentity_t *ent );
  36. extern gitem_t *FindItemForAmmo( ammo_t ammo );
  37. /*
  38. -------------------------
  39. NPC_Mark1_Precache
  40. -------------------------
  41. */
  42. void NPC_Mark1_Precache(void)
  43. {
  44. G_SoundIndex( "sound/chars/mark1/misc/mark1_wakeup");
  45. G_SoundIndex( "sound/chars/mark1/misc/shutdown");
  46. G_SoundIndex( "sound/chars/mark1/misc/walk");
  47. G_SoundIndex( "sound/chars/mark1/misc/run");
  48. G_SoundIndex( "sound/chars/mark1/misc/death1");
  49. G_SoundIndex( "sound/chars/mark1/misc/death2");
  50. G_SoundIndex( "sound/chars/mark1/misc/anger");
  51. G_SoundIndex( "sound/chars/mark1/misc/mark1_fire");
  52. G_SoundIndex( "sound/chars/mark1/misc/mark1_pain");
  53. G_SoundIndex( "sound/chars/mark1/misc/mark1_explo");
  54. // G_EffectIndex( "small_chunks");
  55. G_EffectIndex( "env/med_explode2");
  56. G_EffectIndex( "explosions/probeexplosion1");
  57. G_EffectIndex( "blaster/smoke_bolton");
  58. G_EffectIndex( "bryar/muzzle_flash");
  59. G_EffectIndex( "explosions/droidexplosion1" );
  60. RegisterItem( FindItemForAmmo( AMMO_METAL_BOLTS));
  61. RegisterItem( FindItemForAmmo( AMMO_BLASTER ));
  62. RegisterItem( FindItemForWeapon( WP_BOWCASTER ));
  63. RegisterItem( FindItemForWeapon( WP_BRYAR_PISTOL ));
  64. }
  65. /*
  66. -------------------------
  67. NPC_Mark1_Part_Explode
  68. -------------------------
  69. */
  70. void NPC_Mark1_Part_Explode( gentity_t *self, int bolt )
  71. {
  72. if ( bolt >=0 )
  73. {
  74. mdxaBone_t boltMatrix;
  75. vec3_t org, dir;
  76. gi.G2API_GetBoltMatrix( self->ghoul2, self->playerModel,
  77. bolt,
  78. &boltMatrix, self->currentAngles, self->currentOrigin, (cg.time?cg.time:level.time),
  79. NULL, self->s.modelScale );
  80. gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, org );
  81. gi.G2API_GiveMeVectorFromMatrix( boltMatrix, NEGATIVE_Y, dir );
  82. G_PlayEffect( "env/med_explode2", org, dir );
  83. G_PlayEffect( G_EffectIndex("blaster/smoke_bolton"), self->playerModel, bolt, self->s.number, org );
  84. }
  85. }
  86. /*
  87. -------------------------
  88. Mark1_Idle
  89. -------------------------
  90. */
  91. void Mark1_Idle( void )
  92. {
  93. NPC_BSIdle();
  94. NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_SLEEP1, SETANIM_FLAG_NORMAL );
  95. }
  96. /*
  97. -------------------------
  98. Mark1Dead_FireRocket
  99. - Shoot the left weapon, the multi-blaster
  100. -------------------------
  101. */
  102. void Mark1Dead_FireRocket (void)
  103. {
  104. mdxaBone_t boltMatrix;
  105. vec3_t muzzle1,muzzle_dir;
  106. int damage = 50;
  107. gi.G2API_GetBoltMatrix( NPC->ghoul2, NPC->playerModel,
  108. NPC->genericBolt5,
  109. &boltMatrix, NPC->currentAngles, NPC->currentOrigin, (cg.time?cg.time:level.time),
  110. NULL, NPC->s.modelScale );
  111. gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, muzzle1 );
  112. gi.G2API_GiveMeVectorFromMatrix( boltMatrix, NEGATIVE_Y, muzzle_dir );
  113. G_PlayEffect( "bryar/muzzle_flash", muzzle1, muzzle_dir );
  114. G_Sound( NPC, G_SoundIndex("sound/chars/mark1/misc/mark1_fire"));
  115. gentity_t *missile = CreateMissile( muzzle1, muzzle_dir, BOWCASTER_VELOCITY, 10000, NPC );
  116. missile->classname = "bowcaster_proj";
  117. missile->s.weapon = WP_BOWCASTER;
  118. VectorSet( missile->maxs, BOWCASTER_SIZE, BOWCASTER_SIZE, BOWCASTER_SIZE );
  119. VectorScale( missile->maxs, -1, missile->mins );
  120. missile->damage = damage;
  121. missile->dflags = DAMAGE_DEATH_KNOCKBACK;
  122. missile->methodOfDeath = MOD_ENERGY;
  123. missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
  124. missile->splashDamage = BOWCASTER_SPLASH_DAMAGE;
  125. missile->splashRadius = BOWCASTER_SPLASH_RADIUS;
  126. // we don't want it to bounce
  127. missile->bounceCount = 0;
  128. }
  129. /*
  130. -------------------------
  131. Mark1Dead_FireBlaster
  132. - Shoot the left weapon, the multi-blaster
  133. -------------------------
  134. */
  135. void Mark1Dead_FireBlaster (void)
  136. {
  137. vec3_t muzzle1,muzzle_dir;
  138. gentity_t *missile;
  139. mdxaBone_t boltMatrix;
  140. int bolt;
  141. bolt = NPC->genericBolt1;
  142. gi.G2API_GetBoltMatrix( NPC->ghoul2, NPC->playerModel,
  143. bolt,
  144. &boltMatrix, NPC->currentAngles, NPC->currentOrigin, (cg.time?cg.time:level.time),
  145. NULL, NPC->s.modelScale );
  146. gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, muzzle1 );
  147. gi.G2API_GiveMeVectorFromMatrix( boltMatrix, NEGATIVE_Y, muzzle_dir );
  148. G_PlayEffect( "bryar/muzzle_flash", muzzle1, muzzle_dir );
  149. missile = CreateMissile( muzzle1, muzzle_dir, 1600, 10000, NPC );
  150. G_Sound( NPC, G_SoundIndex("sound/chars/mark1/misc/mark1_fire"));
  151. missile->classname = "bryar_proj";
  152. missile->s.weapon = WP_BRYAR_PISTOL;
  153. missile->damage = 1;
  154. missile->dflags = DAMAGE_DEATH_KNOCKBACK;
  155. missile->methodOfDeath = MOD_ENERGY;
  156. missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
  157. }
  158. /*
  159. -------------------------
  160. Mark1_die
  161. -------------------------
  162. */
  163. void Mark1_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod,int dFlags,int hitLoc )
  164. {
  165. /*
  166. int anim;
  167. // Is he dead already?
  168. anim = self->client->ps.legsAnim;
  169. if (((anim==BOTH_DEATH1) || (anim==BOTH_DEATH2)) && (self->client->ps.torsoAnimTimer==0))
  170. { // This is because self->health keeps getting zeroed out. HL_NONE acts as health in this case.
  171. self->locationDamage[HL_NONE] += damage;
  172. if (self->locationDamage[HL_NONE] > 50)
  173. {
  174. DeathFX(self);
  175. self->client->ps.eFlags |= EF_NODRAW;
  176. self->contents = CONTENTS_CORPSE;
  177. // G_FreeEntity( self ); // Is this safe? I can't see why we'd mark it nodraw and then just leave it around??
  178. self->e_ThinkFunc = thinkF_G_FreeEntity;
  179. self->nextthink = level.time + FRAMETIME;
  180. }
  181. return;
  182. }
  183. */
  184. G_Sound( self, G_SoundIndex(va("sound/chars/mark1/misc/death%d.wav",Q_irand( 1, 2))));
  185. // Choose a death anim
  186. if (Q_irand( 1, 10) > 5)
  187. {
  188. NPC_SetAnim( self, SETANIM_BOTH, BOTH_DEATH2, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
  189. }
  190. else
  191. {
  192. NPC_SetAnim( self, SETANIM_BOTH, BOTH_DEATH1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
  193. }
  194. }
  195. /*
  196. -------------------------
  197. Mark1_dying
  198. -------------------------
  199. */
  200. void Mark1_dying( gentity_t *self )
  201. {
  202. int num,newBolt;
  203. if (self->client->ps.torsoAnimTimer>0)
  204. {
  205. if (TIMER_Done(self,"dyingExplosion"))
  206. {
  207. num = Q_irand( 1, 3);
  208. // Find place to generate explosion
  209. if (num == 1)
  210. {
  211. num = Q_irand( 8, 10);
  212. newBolt = gi.G2API_AddBolt( &self->ghoul2[self->playerModel], va("*flash%d",num) );
  213. NPC_Mark1_Part_Explode(self,newBolt);
  214. }
  215. else
  216. {
  217. num = Q_irand( 1, 6);
  218. newBolt = gi.G2API_AddBolt( &self->ghoul2[self->playerModel], va("*torso_tube%d",num) );
  219. NPC_Mark1_Part_Explode(self,newBolt);
  220. gi.G2API_SetSurfaceOnOff( &self->ghoul2[self->playerModel], va("torso_tube%d",num), TURN_OFF );
  221. }
  222. TIMER_Set( self, "dyingExplosion", Q_irand( 300, 1000 ) );
  223. }
  224. // int dir;
  225. // vec3_t right;
  226. // Shove to the side
  227. // AngleVectors( self->client->renderInfo.eyeAngles, NULL, right, NULL );
  228. // VectorMA( self->client->ps.velocity, -80, right, self->client->ps.velocity );
  229. // See which weapons are there
  230. // Randomly fire blaster
  231. if (!gi.G2API_GetSurfaceRenderStatus( &self->ghoul2[self->playerModel], "l_arm" )) // Is the blaster still on the model?
  232. {
  233. if (Q_irand( 1, 5) == 1)
  234. {
  235. SaveNPCGlobals();
  236. SetNPCGlobals( self );
  237. Mark1Dead_FireBlaster();
  238. RestoreNPCGlobals();
  239. }
  240. }
  241. // Randomly fire rocket
  242. if (!gi.G2API_GetSurfaceRenderStatus( &self->ghoul2[self->playerModel], "r_arm" )) // Is the rocket still on the model?
  243. {
  244. if (Q_irand( 1, 10) == 1)
  245. {
  246. SaveNPCGlobals();
  247. SetNPCGlobals( self );
  248. Mark1Dead_FireRocket();
  249. RestoreNPCGlobals();
  250. }
  251. }
  252. }
  253. }
  254. /*
  255. -------------------------
  256. NPC_Mark1_Pain
  257. - look at what was hit and see if it should be removed from the model.
  258. -------------------------
  259. */
  260. void NPC_Mark1_Pain( gentity_t *self, gentity_t *inflictor, gentity_t *other, const vec3_t point, int damage, int mod,int hitLoc )
  261. {
  262. int newBolt,i,chance;
  263. NPC_Pain( self, inflictor, other, point, damage, mod );
  264. G_Sound( self, G_SoundIndex("sound/chars/mark1/misc/mark1_pain"));
  265. // Hit in the CHEST???
  266. if (hitLoc==HL_CHEST)
  267. {
  268. chance = Q_irand( 1, 4);
  269. if ((chance == 1) && (damage > 5))
  270. {
  271. NPC_SetAnim( self, SETANIM_BOTH, BOTH_PAIN1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
  272. }
  273. }
  274. // Hit in the left arm?
  275. else if ((hitLoc==HL_ARM_LT) && (self->locationDamage[HL_ARM_LT] > LEFT_ARM_HEALTH))
  276. {
  277. if (self->locationDamage[hitLoc] >= LEFT_ARM_HEALTH) // Blow it up?
  278. {
  279. newBolt = gi.G2API_AddBolt( &self->ghoul2[self->playerModel], "*flash3" );
  280. if ( newBolt != -1 )
  281. {
  282. NPC_Mark1_Part_Explode(self,newBolt);
  283. }
  284. gi.G2API_SetSurfaceOnOff( &self->ghoul2[self->playerModel], "l_arm", TURN_OFF );
  285. }
  286. }
  287. // Hit in the right arm?
  288. else if ((hitLoc==HL_ARM_RT) && (self->locationDamage[HL_ARM_RT] > RIGHT_ARM_HEALTH)) // Blow it up?
  289. {
  290. if (self->locationDamage[hitLoc] >= RIGHT_ARM_HEALTH)
  291. {
  292. newBolt = gi.G2API_AddBolt( &self->ghoul2[self->playerModel], "*flash4" );
  293. if ( newBolt != -1 )
  294. {
  295. // G_PlayEffect( "small_chunks", self->playerModel, self->genericBolt2, self->s.number);
  296. NPC_Mark1_Part_Explode( self, newBolt );
  297. }
  298. gi.G2API_SetSurfaceOnOff( &self->ghoul2[self->playerModel], "r_arm", TURN_OFF );
  299. }
  300. }
  301. // Check ammo pods
  302. else
  303. {
  304. for (i=0;i<6;i++)
  305. {
  306. if ((hitLoc==HL_GENERIC1+i) && (self->locationDamage[HL_GENERIC1+i] > AMMO_POD_HEALTH)) // Blow it up?
  307. {
  308. if (self->locationDamage[hitLoc] >= AMMO_POD_HEALTH)
  309. {
  310. newBolt = gi.G2API_AddBolt( &self->ghoul2[self->playerModel], va("*torso_tube%d",(i+1)) );
  311. if ( newBolt != -1 )
  312. {
  313. NPC_Mark1_Part_Explode(self,newBolt);
  314. }
  315. gi.G2API_SetSurfaceOnOff( &self->ghoul2[self->playerModel], va("torso_tube%d",(i+1)), TURN_OFF );
  316. NPC_SetAnim( self, SETANIM_BOTH, BOTH_PAIN1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
  317. break;
  318. }
  319. }
  320. }
  321. }
  322. // Are both guns shot off?
  323. if ((gi.G2API_GetSurfaceRenderStatus( &self->ghoul2[self->playerModel], "l_arm" )) &&
  324. (gi.G2API_GetSurfaceRenderStatus( &self->ghoul2[self->playerModel], "r_arm" )))
  325. {
  326. G_Damage(self,NULL,NULL,NULL,NULL,self->health,0,MOD_UNKNOWN);
  327. }
  328. }
  329. /*
  330. -------------------------
  331. Mark1_Hunt
  332. - look for enemy.
  333. -------------------------`
  334. */
  335. void Mark1_Hunt(void)
  336. {
  337. if ( NPCInfo->goalEntity == NULL )
  338. {
  339. NPCInfo->goalEntity = NPC->enemy;
  340. }
  341. NPC_FaceEnemy( qtrue );
  342. NPCInfo->combatMove = qtrue;
  343. NPC_MoveToGoal( qtrue );
  344. }
  345. /*
  346. -------------------------
  347. Mark1_FireBlaster
  348. - Shoot the left weapon, the multi-blaster
  349. -------------------------
  350. */
  351. void Mark1_FireBlaster(void)
  352. {
  353. vec3_t muzzle1,enemy_org1,delta1,angleToEnemy1;
  354. static vec3_t forward, vright, up;
  355. static vec3_t muzzle;
  356. gentity_t *missile;
  357. mdxaBone_t boltMatrix;
  358. int bolt;
  359. // Which muzzle to fire from?
  360. if ((NPCInfo->localState <= LSTATE_FIRED0) || (NPCInfo->localState == LSTATE_FIRED4))
  361. {
  362. NPCInfo->localState = LSTATE_FIRED1;
  363. bolt = NPC->genericBolt1;
  364. }
  365. else if (NPCInfo->localState == LSTATE_FIRED1)
  366. {
  367. NPCInfo->localState = LSTATE_FIRED2;
  368. bolt = NPC->genericBolt2;
  369. }
  370. else if (NPCInfo->localState == LSTATE_FIRED2)
  371. {
  372. NPCInfo->localState = LSTATE_FIRED3;
  373. bolt = NPC->genericBolt3;
  374. }
  375. else
  376. {
  377. NPCInfo->localState = LSTATE_FIRED4;
  378. bolt = NPC->genericBolt4;
  379. }
  380. gi.G2API_GetBoltMatrix( NPC->ghoul2, NPC->playerModel,
  381. bolt,
  382. &boltMatrix, NPC->currentAngles, NPC->currentOrigin, (cg.time?cg.time:level.time),
  383. NULL, NPC->s.modelScale );
  384. gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, muzzle1 );
  385. if (NPC->health)
  386. {
  387. CalcEntitySpot( NPC->enemy, SPOT_HEAD, enemy_org1 );
  388. VectorSubtract (enemy_org1, muzzle1, delta1);
  389. vectoangles ( delta1, angleToEnemy1 );
  390. AngleVectors (angleToEnemy1, forward, vright, up);
  391. }
  392. else
  393. {
  394. AngleVectors (NPC->currentAngles, forward, vright, up);
  395. }
  396. G_PlayEffect( "bryar/muzzle_flash", muzzle1, forward );
  397. G_Sound( NPC, G_SoundIndex("sound/chars/mark1/misc/mark1_fire"));
  398. missile = CreateMissile( muzzle1, forward, 1600, 10000, NPC );
  399. missile->classname = "bryar_proj";
  400. missile->s.weapon = WP_BRYAR_PISTOL;
  401. missile->damage = 1;
  402. missile->dflags = DAMAGE_DEATH_KNOCKBACK;
  403. missile->methodOfDeath = MOD_ENERGY;
  404. missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
  405. }
  406. /*
  407. -------------------------
  408. Mark1_BlasterAttack
  409. -------------------------
  410. */
  411. void Mark1_BlasterAttack(qboolean advance )
  412. {
  413. int chance;
  414. if ( TIMER_Done( NPC, "attackDelay" ) ) // Attack?
  415. {
  416. chance = Q_irand( 1, 5);
  417. NPCInfo->burstCount++;
  418. if (NPCInfo->burstCount<3) // Too few shots this burst?
  419. {
  420. chance = 2; // Force it to keep firing.
  421. }
  422. else if (NPCInfo->burstCount>12) // Too many shots fired this burst?
  423. {
  424. NPCInfo->burstCount = 0;
  425. chance = 1; // Force it to stop firing.
  426. }
  427. // Stop firing.
  428. if (chance == 1)
  429. {
  430. NPCInfo->burstCount = 0;
  431. TIMER_Set( NPC, "attackDelay", Q_irand( 1000, 3000) );
  432. NPC->client->ps.torsoAnimTimer=0; // Just in case the firing anim is running.
  433. }
  434. else
  435. {
  436. if (TIMER_Done( NPC, "attackDelay2" )) // Can't be shooting every frame.
  437. {
  438. TIMER_Set( NPC, "attackDelay2", Q_irand( 50, 50) );
  439. Mark1_FireBlaster();
  440. NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_ATTACK1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
  441. }
  442. return;
  443. }
  444. }
  445. else if (advance)
  446. {
  447. if ( NPC->client->ps.torsoAnim == BOTH_ATTACK1 )
  448. {
  449. NPC->client->ps.torsoAnimTimer=0; // Just in case the firing anim is running.
  450. }
  451. Mark1_Hunt();
  452. }
  453. else // Make sure he's not firing.
  454. {
  455. if ( NPC->client->ps.torsoAnim == BOTH_ATTACK1 )
  456. {
  457. NPC->client->ps.torsoAnimTimer=0; // Just in case the firing anim is running.
  458. }
  459. }
  460. }
  461. /*
  462. -------------------------
  463. Mark1_FireRocket
  464. -------------------------
  465. */
  466. void Mark1_FireRocket(void)
  467. {
  468. mdxaBone_t boltMatrix;
  469. vec3_t muzzle1,enemy_org1,delta1,angleToEnemy1;
  470. static vec3_t forward, vright, up;
  471. int damage = 50;
  472. gi.G2API_GetBoltMatrix( NPC->ghoul2, NPC->playerModel,
  473. NPC->genericBolt5,
  474. &boltMatrix, NPC->currentAngles, NPC->currentOrigin, (cg.time?cg.time:level.time),
  475. NULL, NPC->s.modelScale );
  476. gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, muzzle1 );
  477. // G_PlayEffect( "blaster/muzzle_flash", muzzle1 );
  478. CalcEntitySpot( NPC->enemy, SPOT_HEAD, enemy_org1 );
  479. VectorSubtract (enemy_org1, muzzle1, delta1);
  480. vectoangles ( delta1, angleToEnemy1 );
  481. AngleVectors (angleToEnemy1, forward, vright, up);
  482. G_Sound( NPC, G_SoundIndex("sound/chars/mark1/misc/mark1_fire" ));
  483. gentity_t *missile = CreateMissile( muzzle1, forward, BOWCASTER_VELOCITY, 10000, NPC );
  484. missile->classname = "bowcaster_proj";
  485. missile->s.weapon = WP_BOWCASTER;
  486. VectorSet( missile->maxs, BOWCASTER_SIZE, BOWCASTER_SIZE, BOWCASTER_SIZE );
  487. VectorScale( missile->maxs, -1, missile->mins );
  488. missile->damage = damage;
  489. missile->dflags = DAMAGE_DEATH_KNOCKBACK;
  490. missile->methodOfDeath = MOD_ENERGY;
  491. missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER;
  492. missile->splashDamage = BOWCASTER_SPLASH_DAMAGE;
  493. missile->splashRadius = BOWCASTER_SPLASH_RADIUS;
  494. // we don't want it to bounce
  495. missile->bounceCount = 0;
  496. }
  497. /*
  498. -------------------------
  499. Mark1_RocketAttack
  500. -------------------------
  501. */
  502. void Mark1_RocketAttack( qboolean advance )
  503. {
  504. if ( TIMER_Done( NPC, "attackDelay" ) ) // Attack?
  505. {
  506. TIMER_Set( NPC, "attackDelay", Q_irand( 1000, 3000) );
  507. NPC_SetAnim( NPC, SETANIM_TORSO, BOTH_ATTACK2, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
  508. Mark1_FireRocket();
  509. }
  510. else if (advance)
  511. {
  512. Mark1_Hunt();
  513. }
  514. }
  515. /*
  516. -------------------------
  517. Mark1_AttackDecision
  518. -------------------------
  519. */
  520. void Mark1_AttackDecision( void )
  521. {
  522. int blasterTest,rocketTest;
  523. //randomly talk
  524. if ( TIMER_Done(NPC,"patrolNoise") )
  525. {
  526. if (TIMER_Done(NPC,"angerNoise"))
  527. {
  528. // G_Sound( NPC, G_SoundIndex(va("sound/chars/mark1/misc/talk%d.wav", Q_irand(1, 4))));
  529. TIMER_Set( NPC, "patrolNoise", Q_irand( 4000, 10000 ) );
  530. }
  531. }
  532. // Enemy is dead or he has no enemy.
  533. if ((NPC->enemy->health<1) || ( NPC_CheckEnemyExt() == qfalse ))
  534. {
  535. NPC->enemy = NULL;
  536. return;
  537. }
  538. // Rate our distance to the target and visibility
  539. float distance = (int) DistanceHorizontalSquared( NPC->currentOrigin, NPC->enemy->currentOrigin );
  540. distance_e distRate = ( distance > MIN_MELEE_RANGE_SQR ) ? DIST_LONG : DIST_MELEE;
  541. qboolean visible = NPC_ClearLOS( NPC->enemy );
  542. qboolean advance = (qboolean)(distance > MIN_DISTANCE_SQR);
  543. // If we cannot see our target, move to see it
  544. if ((!visible) || (!NPC_FaceEnemy(qtrue)))
  545. {
  546. Mark1_Hunt();
  547. return;
  548. }
  549. // See if the side weapons are there
  550. blasterTest = gi.G2API_GetSurfaceRenderStatus( &NPC->ghoul2[NPC->playerModel], "l_arm" );
  551. rocketTest = gi.G2API_GetSurfaceRenderStatus( &NPC->ghoul2[NPC->playerModel], "r_arm" );
  552. // It has both side weapons
  553. if (!blasterTest && !rocketTest)
  554. {
  555. ; // So do nothing.
  556. }
  557. else if (blasterTest)
  558. {
  559. distRate = DIST_LONG;
  560. }
  561. else if (rocketTest)
  562. {
  563. distRate = DIST_MELEE;
  564. }
  565. else // It should never get here, but just in case
  566. {
  567. NPC->health = 0;
  568. NPC->client->ps.stats[STAT_HEALTH] = 0;
  569. GEntity_DieFunc(NPC, NPC, NPC, 100, MOD_UNKNOWN);
  570. }
  571. // We can see enemy so shoot him if timers let you.
  572. NPC_FaceEnemy( qtrue );
  573. if (distRate == DIST_MELEE)
  574. {
  575. Mark1_BlasterAttack(advance);
  576. }
  577. else if (distRate == DIST_LONG)
  578. {
  579. Mark1_RocketAttack(advance);
  580. }
  581. }
  582. /*
  583. -------------------------
  584. Mark1_Patrol
  585. -------------------------
  586. */
  587. void Mark1_Patrol( void )
  588. {
  589. if ( NPC_CheckPlayerTeamStealth() )
  590. {
  591. G_Sound( NPC, G_SoundIndex("sound/chars/mark1/misc/mark1_wakeup"));
  592. NPC_UpdateAngles( qtrue, qtrue );
  593. return;
  594. }
  595. //If we have somewhere to go, then do that
  596. if (!NPC->enemy)
  597. {
  598. if ( UpdateGoal() )
  599. {
  600. ucmd.buttons |= BUTTON_WALKING;
  601. NPC_MoveToGoal( qtrue );
  602. NPC_UpdateAngles( qtrue, qtrue );
  603. }
  604. //randomly talk
  605. // if (TIMER_Done(NPC,"patrolNoise"))
  606. // {
  607. // G_Sound( NPC, G_SoundIndex(va("sound/chars/mark1/misc/talk%d.wav", Q_irand(1, 4))));
  608. //
  609. // TIMER_Set( NPC, "patrolNoise", Q_irand( 2000, 4000 ) );
  610. // }
  611. }
  612. }
  613. /*
  614. -------------------------
  615. NPC_BSMark1_Default
  616. -------------------------
  617. */
  618. void NPC_BSMark1_Default( void )
  619. {
  620. //NPC->e_DieFunc = dieF_Mark1_die;
  621. if ( NPC->enemy )
  622. {
  623. NPCInfo->goalEntity = NPC->enemy;
  624. Mark1_AttackDecision();
  625. }
  626. else if ( NPCInfo->scriptFlags & SCF_LOOK_FOR_ENEMIES )
  627. {
  628. Mark1_Patrol();
  629. }
  630. else
  631. {
  632. Mark1_Idle();
  633. }
  634. }