m_gunner.cpp 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921
  1. // Copyright (c) ZeniMax Media Inc.
  2. // Licensed under the GNU General Public License 2.0.
  3. /*
  4. ==============================================================================
  5. GUNNER
  6. ==============================================================================
  7. */
  8. #include "g_local.h"
  9. #include "m_gunner.h"
  10. #include "m_flash.h"
  11. static cached_soundindex sound_pain;
  12. static cached_soundindex sound_pain2;
  13. static cached_soundindex sound_death;
  14. static cached_soundindex sound_idle;
  15. static cached_soundindex sound_open;
  16. static cached_soundindex sound_search;
  17. static cached_soundindex sound_sight;
  18. void gunner_idlesound(edict_t *self)
  19. {
  20. gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0);
  21. }
  22. MONSTERINFO_SIGHT(gunner_sight) (edict_t *self, edict_t *other) -> void
  23. {
  24. gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
  25. }
  26. MONSTERINFO_SEARCH(gunner_search) (edict_t *self) -> void
  27. {
  28. gi.sound(self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0);
  29. }
  30. void GunnerGrenade(edict_t *self);
  31. void GunnerFire(edict_t *self);
  32. void gunner_fire_chain(edict_t *self);
  33. void gunner_refire_chain(edict_t *self);
  34. void gunner_stand(edict_t *self);
  35. mframe_t gunner_frames_fidget[] = {
  36. { ai_stand },
  37. { ai_stand },
  38. { ai_stand },
  39. { ai_stand },
  40. { ai_stand },
  41. { ai_stand },
  42. { ai_stand },
  43. { ai_stand, 0, gunner_idlesound },
  44. { ai_stand },
  45. { ai_stand },
  46. { ai_stand },
  47. { ai_stand },
  48. { ai_stand },
  49. { ai_stand },
  50. { ai_stand },
  51. { ai_stand },
  52. { ai_stand },
  53. { ai_stand },
  54. { ai_stand },
  55. { ai_stand },
  56. { ai_stand },
  57. { ai_stand },
  58. { ai_stand },
  59. { ai_stand },
  60. { ai_stand },
  61. { ai_stand },
  62. { ai_stand },
  63. { ai_stand },
  64. { ai_stand },
  65. { ai_stand },
  66. { ai_stand },
  67. { ai_stand },
  68. { ai_stand },
  69. { ai_stand },
  70. { ai_stand },
  71. { ai_stand },
  72. { ai_stand },
  73. { ai_stand },
  74. { ai_stand },
  75. { ai_stand }
  76. };
  77. MMOVE_T(gunner_move_fidget) = { FRAME_stand31, FRAME_stand70, gunner_frames_fidget, gunner_stand };
  78. void gunner_fidget(edict_t *self)
  79. {
  80. if (self->monsterinfo.aiflags & AI_STAND_GROUND)
  81. return;
  82. else if (self->enemy)
  83. return;
  84. if (frandom() <= 0.05f)
  85. M_SetAnimation(self, &gunner_move_fidget);
  86. }
  87. mframe_t gunner_frames_stand[] = {
  88. { ai_stand },
  89. { ai_stand },
  90. { ai_stand },
  91. { ai_stand },
  92. { ai_stand },
  93. { ai_stand },
  94. { ai_stand },
  95. { ai_stand },
  96. { ai_stand },
  97. { ai_stand, 0, gunner_fidget },
  98. { ai_stand },
  99. { ai_stand },
  100. { ai_stand },
  101. { ai_stand },
  102. { ai_stand },
  103. { ai_stand },
  104. { ai_stand },
  105. { ai_stand },
  106. { ai_stand },
  107. { ai_stand, 0, gunner_fidget },
  108. { ai_stand },
  109. { ai_stand },
  110. { ai_stand },
  111. { ai_stand },
  112. { ai_stand },
  113. { ai_stand },
  114. { ai_stand },
  115. { ai_stand },
  116. { ai_stand },
  117. { ai_stand, 0, gunner_fidget }
  118. };
  119. MMOVE_T(gunner_move_stand) = { FRAME_stand01, FRAME_stand30, gunner_frames_stand, nullptr };
  120. MONSTERINFO_STAND(gunner_stand) (edict_t *self) -> void
  121. {
  122. M_SetAnimation(self, &gunner_move_stand);
  123. }
  124. mframe_t gunner_frames_walk[] = {
  125. { ai_walk },
  126. { ai_walk, 3 },
  127. { ai_walk, 4 },
  128. { ai_walk, 5 },
  129. { ai_walk, 7 },
  130. { ai_walk, 2, monster_footstep },
  131. { ai_walk, 6 },
  132. { ai_walk, 4 },
  133. { ai_walk, 2 },
  134. { ai_walk, 7 },
  135. { ai_walk, 5 },
  136. { ai_walk, 7 },
  137. { ai_walk, 4, monster_footstep }
  138. };
  139. MMOVE_T(gunner_move_walk) = { FRAME_walk07, FRAME_walk19, gunner_frames_walk, nullptr };
  140. MONSTERINFO_WALK(gunner_walk) (edict_t *self) -> void
  141. {
  142. M_SetAnimation(self, &gunner_move_walk);
  143. }
  144. mframe_t gunner_frames_run[] = {
  145. { ai_run, 26 },
  146. { ai_run, 9, monster_footstep },
  147. { ai_run, 9 },
  148. { ai_run, 9, monster_done_dodge },
  149. { ai_run, 15 },
  150. { ai_run, 10, monster_footstep },
  151. { ai_run, 13 },
  152. { ai_run, 6 }
  153. };
  154. MMOVE_T(gunner_move_run) = { FRAME_run01, FRAME_run08, gunner_frames_run, nullptr };
  155. MONSTERINFO_RUN(gunner_run) (edict_t *self) -> void
  156. {
  157. monster_done_dodge(self);
  158. if (self->monsterinfo.aiflags & AI_STAND_GROUND)
  159. M_SetAnimation(self, &gunner_move_stand);
  160. else
  161. M_SetAnimation(self, &gunner_move_run);
  162. }
  163. mframe_t gunner_frames_runandshoot[] = {
  164. { ai_run, 32 },
  165. { ai_run, 15 },
  166. { ai_run, 10 },
  167. { ai_run, 18 },
  168. { ai_run, 8 },
  169. { ai_run, 20 }
  170. };
  171. MMOVE_T(gunner_move_runandshoot) = { FRAME_runs01, FRAME_runs06, gunner_frames_runandshoot, nullptr };
  172. void gunner_runandshoot(edict_t *self)
  173. {
  174. M_SetAnimation(self, &gunner_move_runandshoot);
  175. }
  176. mframe_t gunner_frames_pain3[] = {
  177. { ai_move, -3 },
  178. { ai_move, 1 },
  179. { ai_move, 1 },
  180. { ai_move },
  181. { ai_move, 1 }
  182. };
  183. MMOVE_T(gunner_move_pain3) = { FRAME_pain301, FRAME_pain305, gunner_frames_pain3, gunner_run };
  184. mframe_t gunner_frames_pain2[] = {
  185. { ai_move, -2 },
  186. { ai_move, 11 },
  187. { ai_move, 6, monster_footstep },
  188. { ai_move, 2 },
  189. { ai_move, -1 },
  190. { ai_move, -7 },
  191. { ai_move, -2 },
  192. { ai_move, -7, monster_footstep }
  193. };
  194. MMOVE_T(gunner_move_pain2) = { FRAME_pain201, FRAME_pain208, gunner_frames_pain2, gunner_run };
  195. mframe_t gunner_frames_pain1[] = {
  196. { ai_move, 2 },
  197. { ai_move },
  198. { ai_move, -5 },
  199. { ai_move, 3 },
  200. { ai_move, -1, monster_footstep },
  201. { ai_move },
  202. { ai_move },
  203. { ai_move },
  204. { ai_move },
  205. { ai_move, 1 },
  206. { ai_move, 1 },
  207. { ai_move, 2 },
  208. { ai_move, 1, monster_footstep },
  209. { ai_move },
  210. { ai_move, -2 },
  211. { ai_move, -2 },
  212. { ai_move },
  213. { ai_move, 0, monster_footstep }
  214. };
  215. MMOVE_T(gunner_move_pain1) = { FRAME_pain101, FRAME_pain118, gunner_frames_pain1, gunner_run };
  216. extern const mmove_t gunner_move_jump;
  217. extern const mmove_t gunner_move_jump2;
  218. PAIN(gunner_pain) (edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod) -> void
  219. {
  220. monster_done_dodge(self);
  221. if (self->monsterinfo.active_move == &gunner_move_jump ||
  222. self->monsterinfo.active_move == &gunner_move_jump2)
  223. return;
  224. if (level.time < self->pain_debounce_time)
  225. return;
  226. self->pain_debounce_time = level.time + 3_sec;
  227. if (brandom())
  228. gi.sound(self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0);
  229. else
  230. gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0);
  231. if (!M_ShouldReactToPain(self, mod))
  232. return; // no pain anims in nightmare
  233. if (damage <= 10)
  234. M_SetAnimation(self, &gunner_move_pain3);
  235. else if (damage <= 25)
  236. M_SetAnimation(self, &gunner_move_pain2);
  237. else
  238. M_SetAnimation(self, &gunner_move_pain1);
  239. self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
  240. // PMM - clear duck flag
  241. if (self->monsterinfo.aiflags & AI_DUCKED)
  242. monster_duck_up(self);
  243. }
  244. MONSTERINFO_SETSKIN(gunner_setskin) (edict_t *self) -> void
  245. {
  246. if (self->health < (self->max_health / 2))
  247. self->s.skinnum = 1;
  248. else
  249. self->s.skinnum = 0;
  250. }
  251. void gunner_dead(edict_t *self)
  252. {
  253. self->mins = { -16, -16, -24 };
  254. self->maxs = { 16, 16, -8 };
  255. monster_dead(self);
  256. }
  257. static void gunner_shrink(edict_t *self)
  258. {
  259. self->maxs[2] = -4;
  260. self->svflags |= SVF_DEADMONSTER;
  261. gi.linkentity(self);
  262. }
  263. mframe_t gunner_frames_death[] = {
  264. { ai_move },
  265. { ai_move },
  266. { ai_move, 0, monster_footstep },
  267. { ai_move, -7, gunner_shrink },
  268. { ai_move, -3 },
  269. { ai_move, -5 },
  270. { ai_move, 8 },
  271. { ai_move, 6 },
  272. { ai_move, 0, monster_footstep },
  273. { ai_move },
  274. { ai_move }
  275. };
  276. MMOVE_T(gunner_move_death) = { FRAME_death01, FRAME_death11, gunner_frames_death, gunner_dead };
  277. DIE(gunner_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void
  278. {
  279. // check for gib
  280. if (M_CheckGib(self, mod))
  281. {
  282. gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0);
  283. self->s.skinnum /= 2;
  284. ThrowGibs(self, damage, {
  285. { 2, "models/objects/gibs/bone/tris.md2" },
  286. { 2, "models/objects/gibs/sm_meat/tris.md2" },
  287. { "models/monsters/gunner/gibs/chest.md2", GIB_SKINNED },
  288. { "models/monsters/gunner/gibs/garm.md2", GIB_SKINNED | GIB_UPRIGHT },
  289. { "models/monsters/gunner/gibs/gun.md2", GIB_SKINNED | GIB_UPRIGHT },
  290. { "models/monsters/gunner/gibs/foot.md2", GIB_SKINNED },
  291. { "models/monsters/gunner/gibs/head.md2", GIB_SKINNED | GIB_HEAD }
  292. });
  293. self->deadflag = true;
  294. return;
  295. }
  296. if (self->deadflag)
  297. return;
  298. // regular death
  299. gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0);
  300. self->deadflag = true;
  301. self->takedamage = true;
  302. M_SetAnimation(self, &gunner_move_death);
  303. }
  304. // PMM - changed to duck code for new dodge
  305. mframe_t gunner_frames_duck[] = {
  306. { ai_move, 1, monster_duck_down },
  307. { ai_move, 1 },
  308. { ai_move, 1, monster_duck_hold },
  309. { ai_move },
  310. { ai_move, -1 },
  311. { ai_move, -1 },
  312. { ai_move, 0, monster_duck_up },
  313. { ai_move, -1 }
  314. };
  315. MMOVE_T(gunner_move_duck) = { FRAME_duck01, FRAME_duck08, gunner_frames_duck, gunner_run };
  316. // PMM - gunner dodge moved below so I know about attack sequences
  317. void gunner_opengun(edict_t *self)
  318. {
  319. gi.sound(self, CHAN_VOICE, sound_open, 1, ATTN_IDLE, 0);
  320. }
  321. void GunnerFire(edict_t *self)
  322. {
  323. vec3_t start;
  324. vec3_t forward, right;
  325. vec3_t aim;
  326. monster_muzzleflash_id_t flash_number;
  327. if (!self->enemy || !self->enemy->inuse) // PGM
  328. return; // PGM
  329. flash_number = static_cast<monster_muzzleflash_id_t>(MZ2_GUNNER_MACHINEGUN_1 + (self->s.frame - FRAME_attak216));
  330. AngleVectors(self->s.angles, forward, right, nullptr);
  331. start = M_ProjectFlashSource(self, monster_flash_offset[flash_number], forward, right);
  332. PredictAim(self, self->enemy, start, 0, true, -0.2f, &aim, nullptr);
  333. monster_fire_bullet(self, start, aim, 3, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, flash_number);
  334. }
  335. bool gunner_grenade_check(edict_t *self)
  336. {
  337. vec3_t dir;
  338. if (!self->enemy)
  339. return false;
  340. vec3_t start;
  341. if (!M_CheckClearShot(self, monster_flash_offset[MZ2_GUNNER_GRENADE_1], start))
  342. return false;
  343. vec3_t target;
  344. // check for flag telling us that we're blindfiring
  345. if (self->monsterinfo.aiflags & AI_MANUAL_STEERING)
  346. target = self->monsterinfo.blind_fire_target;
  347. else
  348. target = self->enemy->s.origin;
  349. // see if we're too close
  350. dir = target - start;
  351. if (dir.length() < 100)
  352. return false;
  353. // check to see that we can trace to the player before we start
  354. // tossing grenades around.
  355. vec3_t aim = dir.normalized();
  356. return M_CalculatePitchToFire(self, target, start, aim, 600, 2.5f, false);
  357. }
  358. void GunnerGrenade(edict_t *self)
  359. {
  360. vec3_t start;
  361. vec3_t forward, right, up;
  362. vec3_t aim;
  363. monster_muzzleflash_id_t flash_number;
  364. float spread;
  365. float pitch = 0;
  366. // PMM
  367. vec3_t target;
  368. bool blindfire = false;
  369. if (!self->enemy || !self->enemy->inuse) // PGM
  370. return; // PGM
  371. // pmm
  372. if (self->monsterinfo.aiflags & AI_MANUAL_STEERING)
  373. blindfire = true;
  374. if (self->s.frame == FRAME_attak105 || self->s.frame == FRAME_attak309)
  375. {
  376. spread = -0.10f;
  377. flash_number = MZ2_GUNNER_GRENADE_1;
  378. }
  379. else if (self->s.frame == FRAME_attak108 || self->s.frame == FRAME_attak312)
  380. {
  381. spread = -0.05f;
  382. flash_number = MZ2_GUNNER_GRENADE_2;
  383. }
  384. else if (self->s.frame == FRAME_attak111 || self->s.frame == FRAME_attak315)
  385. {
  386. spread = 0.05f;
  387. flash_number = MZ2_GUNNER_GRENADE_3;
  388. }
  389. else // (self->s.frame == FRAME_attak114)
  390. {
  391. self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
  392. spread = 0.10f;
  393. flash_number = MZ2_GUNNER_GRENADE_4;
  394. }
  395. if (self->s.frame >= FRAME_attak301 && self->s.frame <= FRAME_attak324)
  396. flash_number = static_cast<monster_muzzleflash_id_t>(MZ2_GUNNER_GRENADE2_1 + (MZ2_GUNNER_GRENADE_4 - flash_number));
  397. // pmm
  398. // if we're shooting blind and we still can't see our enemy
  399. if ((blindfire) && (!visible(self, self->enemy)))
  400. {
  401. // and we have a valid blind_fire_target
  402. if (!self->monsterinfo.blind_fire_target)
  403. return;
  404. target = self->monsterinfo.blind_fire_target;
  405. }
  406. else
  407. target = self->enemy->s.origin;
  408. // pmm
  409. AngleVectors(self->s.angles, forward, right, up); // PGM
  410. start = M_ProjectFlashSource(self, monster_flash_offset[flash_number], forward, right);
  411. // PGM
  412. if (self->enemy)
  413. {
  414. float dist;
  415. aim = target - self->s.origin;
  416. dist = aim.length();
  417. // aim up if they're on the same level as me and far away.
  418. if ((dist > 512) && (aim[2] < 64) && (aim[2] > -64))
  419. {
  420. aim[2] += (dist - 512);
  421. }
  422. aim.normalize();
  423. pitch = aim[2];
  424. if (pitch > 0.4f)
  425. pitch = 0.4f;
  426. else if (pitch < -0.5f)
  427. pitch = -0.5f;
  428. }
  429. // PGM
  430. aim = forward + (right * spread);
  431. aim += (up * pitch);
  432. // try search for best pitch
  433. if (M_CalculatePitchToFire(self, target, start, aim, 600, 2.5f, false))
  434. monster_fire_grenade(self, start, aim, 50, 600, flash_number, (crandom_open() * 10.0f), frandom() * 10.f);
  435. else
  436. // normal shot
  437. monster_fire_grenade(self, start, aim, 50, 600, flash_number, (crandom_open() * 10.0f), 200.f + (crandom_open() * 10.0f));
  438. }
  439. mframe_t gunner_frames_attack_chain[] = {
  440. { ai_charge, 0, gunner_opengun },
  441. { ai_charge },
  442. { ai_charge },
  443. { ai_charge },
  444. { ai_charge },
  445. { ai_charge },
  446. { ai_charge }
  447. };
  448. MMOVE_T(gunner_move_attack_chain) = { FRAME_attak209, FRAME_attak215, gunner_frames_attack_chain, gunner_fire_chain };
  449. mframe_t gunner_frames_fire_chain[] = {
  450. { ai_charge, 0, GunnerFire },
  451. { ai_charge, 0, GunnerFire },
  452. { ai_charge, 0, GunnerFire },
  453. { ai_charge, 0, GunnerFire },
  454. { ai_charge, 0, GunnerFire },
  455. { ai_charge, 0, GunnerFire },
  456. { ai_charge, 0, GunnerFire },
  457. { ai_charge, 0, GunnerFire }
  458. };
  459. MMOVE_T(gunner_move_fire_chain) = { FRAME_attak216, FRAME_attak223, gunner_frames_fire_chain, gunner_refire_chain };
  460. mframe_t gunner_frames_endfire_chain[] = {
  461. { ai_charge },
  462. { ai_charge },
  463. { ai_charge },
  464. { ai_charge },
  465. { ai_charge },
  466. { ai_charge },
  467. { ai_charge, 0, monster_footstep }
  468. };
  469. MMOVE_T(gunner_move_endfire_chain) = { FRAME_attak224, FRAME_attak230, gunner_frames_endfire_chain, gunner_run };
  470. void gunner_blind_check(edict_t *self)
  471. {
  472. vec3_t aim;
  473. if (self->monsterinfo.aiflags & AI_MANUAL_STEERING)
  474. {
  475. aim = self->monsterinfo.blind_fire_target - self->s.origin;
  476. self->ideal_yaw = vectoyaw(aim);
  477. }
  478. }
  479. mframe_t gunner_frames_attack_grenade[] = {
  480. { ai_charge, 0, gunner_blind_check },
  481. { ai_charge },
  482. { ai_charge },
  483. { ai_charge },
  484. { ai_charge, 0, GunnerGrenade },
  485. { ai_charge },
  486. { ai_charge },
  487. { ai_charge, 0, GunnerGrenade },
  488. { ai_charge },
  489. { ai_charge },
  490. { ai_charge, 0, GunnerGrenade },
  491. { ai_charge },
  492. { ai_charge },
  493. { ai_charge, 0, GunnerGrenade },
  494. { ai_charge },
  495. { ai_charge },
  496. { ai_charge },
  497. { ai_charge },
  498. { ai_charge },
  499. { ai_charge },
  500. { ai_charge }
  501. };
  502. MMOVE_T(gunner_move_attack_grenade) = { FRAME_attak101, FRAME_attak121, gunner_frames_attack_grenade, gunner_run };
  503. mframe_t gunner_frames_attack_grenade2[] = {
  504. //{ ai_charge },
  505. //{ ai_charge },
  506. //{ ai_charge },
  507. //{ ai_charge },
  508. { ai_charge, 0, gunner_blind_check },
  509. { ai_charge },
  510. { ai_charge },
  511. { ai_charge },
  512. { ai_charge, 0, GunnerGrenade },
  513. { ai_charge },
  514. { ai_charge },
  515. { ai_charge, 0, GunnerGrenade },
  516. { ai_charge },
  517. { ai_charge },
  518. { ai_charge, 0, GunnerGrenade },
  519. { ai_charge },
  520. { ai_charge },
  521. { ai_charge, 0, GunnerGrenade },
  522. { ai_charge },
  523. { ai_charge },
  524. { ai_charge },
  525. { ai_charge },
  526. { ai_charge },
  527. { ai_charge }
  528. };
  529. MMOVE_T(gunner_move_attack_grenade2) = { FRAME_attak305, FRAME_attak324, gunner_frames_attack_grenade2, gunner_run };
  530. MONSTERINFO_ATTACK(gunner_attack) (edict_t *self) -> void
  531. {
  532. float chance, r;
  533. monster_done_dodge(self);
  534. // PMM
  535. if (self->monsterinfo.attack_state == AS_BLIND)
  536. {
  537. if (self->timestamp > level.time)
  538. return;
  539. // setup shot probabilities
  540. if (self->monsterinfo.blind_fire_delay < 1_sec)
  541. chance = 1.0f;
  542. else if (self->monsterinfo.blind_fire_delay < 7.5_sec)
  543. chance = 0.4f;
  544. else
  545. chance = 0.1f;
  546. r = frandom();
  547. // minimum of 4.1 seconds, plus 0-3, after the shots are done
  548. self->monsterinfo.blind_fire_delay += 4.1_sec + random_time(3_sec);
  549. // don't shoot at the origin
  550. if (!self->monsterinfo.blind_fire_target)
  551. return;
  552. // don't shoot if the dice say not to
  553. if (r > chance)
  554. return;
  555. // turn on manual steering to signal both manual steering and blindfire
  556. self->monsterinfo.aiflags |= AI_MANUAL_STEERING;
  557. if (gunner_grenade_check(self))
  558. {
  559. // if the check passes, go for the attack
  560. M_SetAnimation(self, brandom() ? &gunner_move_attack_grenade2 : &gunner_move_attack_grenade);
  561. self->monsterinfo.attack_finished = level.time + random_time(2_sec);
  562. }
  563. else
  564. // turn off blindfire flag
  565. self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
  566. self->timestamp = level.time + random_time(2_sec, 3_sec);
  567. return;
  568. }
  569. // pmm
  570. // PGM - gunner needs to use his chaingun if he's being attacked by a tesla.
  571. if (self->bad_area || self->timestamp > level.time ||
  572. (range_to(self, self->enemy) <= RANGE_NEAR * 0.35f && M_CheckClearShot(self, monster_flash_offset[MZ2_GUNNER_MACHINEGUN_1])))
  573. {
  574. M_SetAnimation(self, &gunner_move_attack_chain);
  575. }
  576. else
  577. {
  578. if (self->timestamp <= level.time && frandom() <= 0.5f && gunner_grenade_check(self))
  579. {
  580. M_SetAnimation(self, brandom() ? &gunner_move_attack_grenade2 : &gunner_move_attack_grenade);
  581. self->timestamp = level.time + random_time(2_sec, 3_sec);
  582. }
  583. else if (M_CheckClearShot(self, monster_flash_offset[MZ2_GUNNER_MACHINEGUN_1]))
  584. M_SetAnimation(self, &gunner_move_attack_chain);
  585. }
  586. }
  587. void gunner_fire_chain(edict_t *self)
  588. {
  589. M_SetAnimation(self, &gunner_move_fire_chain);
  590. }
  591. void gunner_refire_chain(edict_t *self)
  592. {
  593. if (self->enemy->health > 0)
  594. if (visible(self, self->enemy))
  595. if (frandom() <= 0.5f)
  596. {
  597. M_SetAnimation(self, &gunner_move_fire_chain, false);
  598. return;
  599. }
  600. M_SetAnimation(self, &gunner_move_endfire_chain, false);
  601. }
  602. //===========
  603. // PGM
  604. void gunner_jump_now(edict_t *self)
  605. {
  606. vec3_t forward, up;
  607. AngleVectors(self->s.angles, forward, nullptr, up);
  608. self->velocity += (forward * 100);
  609. self->velocity += (up * 300);
  610. }
  611. void gunner_jump2_now(edict_t *self)
  612. {
  613. vec3_t forward, up;
  614. AngleVectors(self->s.angles, forward, nullptr, up);
  615. self->velocity += (forward * 150);
  616. self->velocity += (up * 400);
  617. }
  618. void gunner_jump_wait_land(edict_t *self)
  619. {
  620. if (self->groundentity == nullptr)
  621. {
  622. self->monsterinfo.nextframe = self->s.frame;
  623. if (monster_jump_finished(self))
  624. self->monsterinfo.nextframe = self->s.frame + 1;
  625. }
  626. else
  627. self->monsterinfo.nextframe = self->s.frame + 1;
  628. }
  629. mframe_t gunner_frames_jump[] = {
  630. { ai_move },
  631. { ai_move },
  632. { ai_move },
  633. { ai_move, 0, gunner_jump_now },
  634. { ai_move },
  635. { ai_move },
  636. { ai_move, 0, gunner_jump_wait_land },
  637. { ai_move },
  638. { ai_move },
  639. { ai_move }
  640. };
  641. MMOVE_T(gunner_move_jump) = { FRAME_jump01, FRAME_jump10, gunner_frames_jump, gunner_run };
  642. mframe_t gunner_frames_jump2[] = {
  643. { ai_move, -8 },
  644. { ai_move, -4 },
  645. { ai_move, -4 },
  646. { ai_move, 0, gunner_jump2_now },
  647. { ai_move },
  648. { ai_move },
  649. { ai_move, 0, gunner_jump_wait_land },
  650. { ai_move },
  651. { ai_move },
  652. { ai_move }
  653. };
  654. MMOVE_T(gunner_move_jump2) = { FRAME_jump01, FRAME_jump10, gunner_frames_jump2, gunner_run };
  655. void gunner_jump(edict_t *self, blocked_jump_result_t result)
  656. {
  657. if (!self->enemy)
  658. return;
  659. monster_done_dodge(self);
  660. if (result == blocked_jump_result_t::JUMP_JUMP_UP)
  661. M_SetAnimation(self, &gunner_move_jump2);
  662. else
  663. M_SetAnimation(self, &gunner_move_jump);
  664. }
  665. //===========
  666. // PGM
  667. MONSTERINFO_BLOCKED(gunner_blocked) (edict_t *self, float dist) -> bool
  668. {
  669. if (blocked_checkplat(self, dist))
  670. return true;
  671. if (auto result = blocked_checkjump(self, dist); result != blocked_jump_result_t::NO_JUMP)
  672. {
  673. if (result != blocked_jump_result_t::JUMP_TURN)
  674. gunner_jump(self, result);
  675. return true;
  676. }
  677. return false;
  678. }
  679. // PGM
  680. //===========
  681. // PMM - new duck code
  682. MONSTERINFO_DUCK(gunner_duck) (edict_t *self, gtime_t eta) -> bool
  683. {
  684. if ((self->monsterinfo.active_move == &gunner_move_jump2) ||
  685. (self->monsterinfo.active_move == &gunner_move_jump))
  686. {
  687. return false;
  688. }
  689. if ((self->monsterinfo.active_move == &gunner_move_attack_chain) ||
  690. (self->monsterinfo.active_move == &gunner_move_fire_chain) ||
  691. (self->monsterinfo.active_move == &gunner_move_attack_grenade) ||
  692. (self->monsterinfo.active_move == &gunner_move_attack_grenade2))
  693. {
  694. // if we're shooting don't dodge
  695. self->monsterinfo.unduck(self);
  696. return false;
  697. }
  698. if (frandom() > 0.5f)
  699. GunnerGrenade(self);
  700. M_SetAnimation(self, &gunner_move_duck);
  701. return true;
  702. }
  703. MONSTERINFO_SIDESTEP(gunner_sidestep) (edict_t *self) -> bool
  704. {
  705. if ((self->monsterinfo.active_move == &gunner_move_jump2) ||
  706. (self->monsterinfo.active_move == &gunner_move_jump) ||
  707. (self->monsterinfo.active_move == &gunner_move_pain1))
  708. return false;
  709. if ((self->monsterinfo.active_move == &gunner_move_attack_chain) ||
  710. (self->monsterinfo.active_move == &gunner_move_fire_chain) ||
  711. (self->monsterinfo.active_move == &gunner_move_attack_grenade) ||
  712. (self->monsterinfo.active_move == &gunner_move_attack_grenade2))
  713. {
  714. // if we're shooting, don't dodge
  715. return false;
  716. }
  717. if (self->monsterinfo.active_move != &gunner_move_run)
  718. M_SetAnimation(self, &gunner_move_run);
  719. return true;
  720. }
  721. constexpr spawnflags_t SPAWNFLAG_GUNNER_NOJUMPING = 8_spawnflag;
  722. /*QUAKED monster_gunner (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight NoJumping
  723. model="models/monsters/gunner/tris.md2"
  724. */
  725. void SP_monster_gunner(edict_t *self)
  726. {
  727. if ( !M_AllowSpawn( self ) ) {
  728. G_FreeEdict( self );
  729. return;
  730. }
  731. sound_death.assign("gunner/death1.wav");
  732. sound_pain.assign("gunner/gunpain2.wav");
  733. sound_pain2.assign("gunner/gunpain1.wav");
  734. sound_idle.assign("gunner/gunidle1.wav");
  735. sound_open.assign("gunner/gunatck1.wav");
  736. sound_search.assign("gunner/gunsrch1.wav");
  737. sound_sight.assign("gunner/sight1.wav");
  738. gi.soundindex("gunner/gunatck2.wav");
  739. gi.soundindex("gunner/gunatck3.wav");
  740. self->movetype = MOVETYPE_STEP;
  741. self->solid = SOLID_BBOX;
  742. self->s.modelindex = gi.modelindex("models/monsters/gunner/tris.md2");
  743. gi.modelindex("models/monsters/gunner/gibs/chest.md2");
  744. gi.modelindex("models/monsters/gunner/gibs/foot.md2");
  745. gi.modelindex("models/monsters/gunner/gibs/garm.md2");
  746. gi.modelindex("models/monsters/gunner/gibs/gun.md2");
  747. gi.modelindex("models/monsters/gunner/gibs/head.md2");
  748. self->mins = { -16, -16, -24 };
  749. self->maxs = { 16, 16, 36 };
  750. self->health = 175 * st.health_multiplier;
  751. self->gib_health = -70;
  752. self->mass = 200;
  753. self->pain = gunner_pain;
  754. self->die = gunner_die;
  755. self->monsterinfo.stand = gunner_stand;
  756. self->monsterinfo.walk = gunner_walk;
  757. self->monsterinfo.run = gunner_run;
  758. // pmm
  759. self->monsterinfo.dodge = M_MonsterDodge;
  760. self->monsterinfo.duck = gunner_duck;
  761. self->monsterinfo.unduck = monster_duck_up;
  762. self->monsterinfo.sidestep = gunner_sidestep;
  763. self->monsterinfo.blocked = gunner_blocked; // PGM
  764. // pmm
  765. self->monsterinfo.attack = gunner_attack;
  766. self->monsterinfo.melee = nullptr;
  767. self->monsterinfo.sight = gunner_sight;
  768. self->monsterinfo.search = gunner_search;
  769. self->monsterinfo.setskin = gunner_setskin;
  770. gi.linkentity(self);
  771. M_SetAnimation(self, &gunner_move_stand);
  772. self->monsterinfo.scale = MODEL_SCALE;
  773. // PMM
  774. self->monsterinfo.blindfire = true;
  775. self->monsterinfo.can_jump = !self->spawnflags.has(SPAWNFLAG_GUNNER_NOJUMPING);
  776. self->monsterinfo.drop_height = 192;
  777. self->monsterinfo.jump_height = 40;
  778. walkmonster_start(self);
  779. }