m_flyer.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785
  1. // Copyright (c) ZeniMax Media Inc.
  2. // Licensed under the GNU General Public License 2.0.
  3. /*
  4. ==============================================================================
  5. flyer
  6. ==============================================================================
  7. */
  8. #include "g_local.h"
  9. #include "m_flyer.h"
  10. #include "m_flash.h"
  11. static cached_soundindex sound_sight;
  12. static cached_soundindex sound_idle;
  13. static cached_soundindex sound_pain1;
  14. static cached_soundindex sound_pain2;
  15. static cached_soundindex sound_slash;
  16. static cached_soundindex sound_sproing;
  17. static cached_soundindex sound_die;
  18. void flyer_check_melee(edict_t *self);
  19. void flyer_loop_melee(edict_t *self);
  20. void flyer_setstart(edict_t *self);
  21. // ROGUE - kamikaze stuff
  22. void flyer_kamikaze(edict_t *self);
  23. void flyer_kamikaze_check(edict_t *self);
  24. void flyer_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod);
  25. MONSTERINFO_SIGHT(flyer_sight) (edict_t *self, edict_t *other) -> void
  26. {
  27. gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
  28. }
  29. MONSTERINFO_IDLE(flyer_idle) (edict_t *self) -> void
  30. {
  31. gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0);
  32. }
  33. void flyer_pop_blades(edict_t *self)
  34. {
  35. gi.sound(self, CHAN_VOICE, sound_sproing, 1, ATTN_NORM, 0);
  36. }
  37. mframe_t flyer_frames_stand[] = {
  38. { ai_stand },
  39. { ai_stand },
  40. { ai_stand },
  41. { ai_stand },
  42. { ai_stand },
  43. { ai_stand },
  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. { ai_stand },
  77. { ai_stand },
  78. { ai_stand },
  79. { ai_stand },
  80. { ai_stand },
  81. { ai_stand },
  82. { ai_stand }
  83. };
  84. MMOVE_T(flyer_move_stand) = { FRAME_stand01, FRAME_stand45, flyer_frames_stand, nullptr };
  85. mframe_t flyer_frames_walk[] = {
  86. { ai_walk, 5 },
  87. { ai_walk, 5 },
  88. { ai_walk, 5 },
  89. { ai_walk, 5 },
  90. { ai_walk, 5 },
  91. { ai_walk, 5 },
  92. { ai_walk, 5 },
  93. { ai_walk, 5 },
  94. { ai_walk, 5 },
  95. { ai_walk, 5 },
  96. { ai_walk, 5 },
  97. { ai_walk, 5 },
  98. { ai_walk, 5 },
  99. { ai_walk, 5 },
  100. { ai_walk, 5 },
  101. { ai_walk, 5 },
  102. { ai_walk, 5 },
  103. { ai_walk, 5 },
  104. { ai_walk, 5 },
  105. { ai_walk, 5 },
  106. { ai_walk, 5 },
  107. { ai_walk, 5 },
  108. { ai_walk, 5 },
  109. { ai_walk, 5 },
  110. { ai_walk, 5 },
  111. { ai_walk, 5 },
  112. { ai_walk, 5 },
  113. { ai_walk, 5 },
  114. { ai_walk, 5 },
  115. { ai_walk, 5 },
  116. { ai_walk, 5 },
  117. { ai_walk, 5 },
  118. { ai_walk, 5 },
  119. { ai_walk, 5 },
  120. { ai_walk, 5 },
  121. { ai_walk, 5 },
  122. { ai_walk, 5 },
  123. { ai_walk, 5 },
  124. { ai_walk, 5 },
  125. { ai_walk, 5 },
  126. { ai_walk, 5 },
  127. { ai_walk, 5 },
  128. { ai_walk, 5 },
  129. { ai_walk, 5 },
  130. { ai_walk, 5 }
  131. };
  132. MMOVE_T(flyer_move_walk) = { FRAME_stand01, FRAME_stand45, flyer_frames_walk, nullptr };
  133. mframe_t flyer_frames_run[] = {
  134. { ai_run, 10 },
  135. { ai_run, 10 },
  136. { ai_run, 10 },
  137. { ai_run, 10 },
  138. { ai_run, 10 },
  139. { ai_run, 10 },
  140. { ai_run, 10 },
  141. { ai_run, 10 },
  142. { ai_run, 10 },
  143. { ai_run, 10 },
  144. { ai_run, 10 },
  145. { ai_run, 10 },
  146. { ai_run, 10 },
  147. { ai_run, 10 },
  148. { ai_run, 10 },
  149. { ai_run, 10 },
  150. { ai_run, 10 },
  151. { ai_run, 10 },
  152. { ai_run, 10 },
  153. { ai_run, 10 },
  154. { ai_run, 10 },
  155. { ai_run, 10 },
  156. { ai_run, 10 },
  157. { ai_run, 10 },
  158. { ai_run, 10 },
  159. { ai_run, 10 },
  160. { ai_run, 10 },
  161. { ai_run, 10 },
  162. { ai_run, 10 },
  163. { ai_run, 10 },
  164. { ai_run, 10 },
  165. { ai_run, 10 },
  166. { ai_run, 10 },
  167. { ai_run, 10 },
  168. { ai_run, 10 },
  169. { ai_run, 10 },
  170. { ai_run, 10 },
  171. { ai_run, 10 },
  172. { ai_run, 10 },
  173. { ai_run, 10 },
  174. { ai_run, 10 },
  175. { ai_run, 10 },
  176. { ai_run, 10 },
  177. { ai_run, 10 },
  178. { ai_run, 10 }
  179. };
  180. MMOVE_T(flyer_move_run) = { FRAME_stand01, FRAME_stand45, flyer_frames_run, nullptr };
  181. mframe_t flyer_frames_kamizake[] = {
  182. { ai_charge, 40, flyer_kamikaze_check },
  183. { ai_charge, 40, flyer_kamikaze_check },
  184. { ai_charge, 40, flyer_kamikaze_check },
  185. { ai_charge, 40, flyer_kamikaze_check },
  186. { ai_charge, 40, flyer_kamikaze_check }
  187. };
  188. MMOVE_T(flyer_move_kamikaze) = { FRAME_rollr02, FRAME_rollr06, flyer_frames_kamizake, flyer_kamikaze };
  189. MONSTERINFO_RUN(flyer_run) (edict_t *self) -> void
  190. {
  191. if (self->mass > 50)
  192. M_SetAnimation(self, &flyer_move_kamikaze);
  193. else if (self->monsterinfo.aiflags & AI_STAND_GROUND)
  194. M_SetAnimation(self, &flyer_move_stand);
  195. else
  196. M_SetAnimation(self, &flyer_move_run);
  197. }
  198. MONSTERINFO_WALK(flyer_walk) (edict_t *self) -> void
  199. {
  200. if (self->mass > 50)
  201. flyer_run(self);
  202. else
  203. M_SetAnimation(self, &flyer_move_walk);
  204. }
  205. MONSTERINFO_STAND(flyer_stand) (edict_t *self) -> void
  206. {
  207. if (self->mass > 50)
  208. flyer_run(self);
  209. else
  210. M_SetAnimation(self, &flyer_move_stand);
  211. }
  212. // ROGUE - kamikaze stuff
  213. void flyer_kamikaze_explode(edict_t *self)
  214. {
  215. vec3_t dir;
  216. if (self->monsterinfo.commander && self->monsterinfo.commander->inuse &&
  217. !strcmp(self->monsterinfo.commander->classname, "monster_carrier"))
  218. self->monsterinfo.commander->monsterinfo.monster_slots++;
  219. if (self->enemy)
  220. {
  221. dir = self->enemy->s.origin - self->s.origin;
  222. T_Damage(self->enemy, self, self, dir, self->s.origin, vec3_origin, (int) 50, (int) 50, DAMAGE_RADIUS, MOD_UNKNOWN);
  223. }
  224. flyer_die(self, nullptr, nullptr, 0, dir, MOD_EXPLOSIVE);
  225. }
  226. void flyer_kamikaze(edict_t *self)
  227. {
  228. M_SetAnimation(self, &flyer_move_kamikaze);
  229. }
  230. void flyer_kamikaze_check(edict_t *self)
  231. {
  232. float dist;
  233. // PMM - this needed because we could have gone away before we get here (blocked code)
  234. if (!self->inuse)
  235. return;
  236. if ((!self->enemy) || (!self->enemy->inuse))
  237. {
  238. flyer_kamikaze_explode(self);
  239. return;
  240. }
  241. self->s.angles[0] = vectoangles(self->enemy->s.origin - self->s.origin).x;
  242. self->goalentity = self->enemy;
  243. dist = realrange(self, self->enemy);
  244. if (dist < 90)
  245. flyer_kamikaze_explode(self);
  246. }
  247. #if 0
  248. mframe_t flyer_frames_rollright[] = {
  249. { ai_move },
  250. { ai_move },
  251. { ai_move },
  252. { ai_move },
  253. { ai_move },
  254. { ai_move },
  255. { ai_move },
  256. { ai_move },
  257. { ai_move }
  258. };
  259. MMOVE_T(flyer_move_rollright) = { FRAME_rollr01, FRAME_rollr09, flyer_frames_rollright, nullptr };
  260. mframe_t flyer_frames_rollleft[] = {
  261. { ai_move },
  262. { ai_move },
  263. { ai_move },
  264. { ai_move },
  265. { ai_move },
  266. { ai_move },
  267. { ai_move },
  268. { ai_move },
  269. { ai_move }
  270. };
  271. MMOVE_T(flyer_move_rollleft) = { FRAME_rollf01, FRAME_rollf09, flyer_frames_rollleft, nullptr };
  272. #endif
  273. mframe_t flyer_frames_pain3[] = {
  274. { ai_move },
  275. { ai_move },
  276. { ai_move },
  277. { ai_move }
  278. };
  279. MMOVE_T(flyer_move_pain3) = { FRAME_pain301, FRAME_pain304, flyer_frames_pain3, flyer_run };
  280. mframe_t flyer_frames_pain2[] = {
  281. { ai_move },
  282. { ai_move },
  283. { ai_move },
  284. { ai_move }
  285. };
  286. MMOVE_T(flyer_move_pain2) = { FRAME_pain201, FRAME_pain204, flyer_frames_pain2, flyer_run };
  287. mframe_t flyer_frames_pain1[] = {
  288. { ai_move },
  289. { ai_move },
  290. { ai_move },
  291. { ai_move },
  292. { ai_move },
  293. { ai_move },
  294. { ai_move },
  295. { ai_move },
  296. { ai_move }
  297. };
  298. MMOVE_T(flyer_move_pain1) = { FRAME_pain101, FRAME_pain109, flyer_frames_pain1, flyer_run };
  299. #if 0
  300. mframe_t flyer_frames_defense[] = {
  301. { ai_move },
  302. { ai_move },
  303. { ai_move }, // Hold this frame
  304. { ai_move },
  305. { ai_move },
  306. { ai_move }
  307. };
  308. MMOVE_T(flyer_move_defense) = { FRAME_defens01, FRAME_defens06, flyer_frames_defense, nullptr };
  309. mframe_t flyer_frames_bankright[] = {
  310. { ai_move },
  311. { ai_move },
  312. { ai_move },
  313. { ai_move },
  314. { ai_move },
  315. { ai_move },
  316. { ai_move }
  317. };
  318. MMOVE_T(flyer_move_bankright) = { FRAME_bankr01, FRAME_bankr07, flyer_frames_bankright, nullptr };
  319. mframe_t flyer_frames_bankleft[] = {
  320. { ai_move },
  321. { ai_move },
  322. { ai_move },
  323. { ai_move },
  324. { ai_move },
  325. { ai_move },
  326. { ai_move }
  327. };
  328. MMOVE_T(flyer_move_bankleft) = { FRAME_bankl01, FRAME_bankl07, flyer_frames_bankleft, nullptr };
  329. #endif
  330. void flyer_fire(edict_t *self, monster_muzzleflash_id_t flash_number)
  331. {
  332. vec3_t start;
  333. vec3_t forward, right;
  334. vec3_t end;
  335. vec3_t dir;
  336. if (!self->enemy || !self->enemy->inuse) // PGM
  337. return; // PGM
  338. AngleVectors(self->s.angles, forward, right, nullptr);
  339. start = M_ProjectFlashSource(self, monster_flash_offset[flash_number], forward, right);
  340. end = self->enemy->s.origin;
  341. end[2] += self->enemy->viewheight;
  342. dir = end - start;
  343. dir.normalize();
  344. monster_fire_blaster(self, start, dir, 1, 1000, flash_number, (self->s.frame % 4) ? EF_NONE : EF_HYPERBLASTER);
  345. }
  346. void flyer_fireleft(edict_t *self)
  347. {
  348. flyer_fire(self, MZ2_FLYER_BLASTER_1);
  349. }
  350. void flyer_fireright(edict_t *self)
  351. {
  352. flyer_fire(self, MZ2_FLYER_BLASTER_2);
  353. }
  354. mframe_t flyer_frames_attack2[] = {
  355. { ai_charge },
  356. { ai_charge },
  357. { ai_charge },
  358. { ai_charge, -10, flyer_fireleft }, // left gun
  359. { ai_charge, -10, flyer_fireright }, // right gun
  360. { ai_charge, -10, flyer_fireleft }, // left gun
  361. { ai_charge, -10, flyer_fireright }, // right gun
  362. { ai_charge, -10, flyer_fireleft }, // left gun
  363. { ai_charge, -10, flyer_fireright }, // right gun
  364. { ai_charge, -10, flyer_fireleft }, // left gun
  365. { ai_charge, -10, flyer_fireright }, // right gun
  366. { ai_charge },
  367. { ai_charge },
  368. { ai_charge },
  369. { ai_charge },
  370. { ai_charge },
  371. { ai_charge }
  372. };
  373. MMOVE_T(flyer_move_attack2) = { FRAME_attak201, FRAME_attak217, flyer_frames_attack2, flyer_run };
  374. // PMM
  375. // circle strafe frames
  376. mframe_t flyer_frames_attack3[] = {
  377. { ai_charge, 10 },
  378. { ai_charge, 10 },
  379. { ai_charge, 10 },
  380. { ai_charge, 10, flyer_fireleft }, // left gun
  381. { ai_charge, 10, flyer_fireright }, // right gun
  382. { ai_charge, 10, flyer_fireleft }, // left gun
  383. { ai_charge, 10, flyer_fireright }, // right gun
  384. { ai_charge, 10, flyer_fireleft }, // left gun
  385. { ai_charge, 10, flyer_fireright }, // right gun
  386. { ai_charge, 10, flyer_fireleft }, // left gun
  387. { ai_charge, 10, flyer_fireright }, // right gun
  388. { ai_charge, 10 },
  389. { ai_charge, 10 },
  390. { ai_charge, 10 },
  391. { ai_charge, 10 },
  392. { ai_charge, 10 },
  393. { ai_charge, 10 }
  394. };
  395. MMOVE_T(flyer_move_attack3) = { FRAME_attak201, FRAME_attak217, flyer_frames_attack3, flyer_run };
  396. // pmm
  397. void flyer_slash_left(edict_t *self)
  398. {
  399. vec3_t aim = { MELEE_DISTANCE, self->mins[0], 0 };
  400. if (!fire_hit(self, aim, 5, 0))
  401. self->monsterinfo.melee_debounce_time = level.time + 1.5_sec;
  402. gi.sound(self, CHAN_WEAPON, sound_slash, 1, ATTN_NORM, 0);
  403. }
  404. void flyer_slash_right(edict_t *self)
  405. {
  406. vec3_t aim = { MELEE_DISTANCE, self->maxs[0], 0 };
  407. if (!fire_hit(self, aim, 5, 0))
  408. self->monsterinfo.melee_debounce_time = level.time + 1.5_sec;
  409. gi.sound(self, CHAN_WEAPON, sound_slash, 1, ATTN_NORM, 0);
  410. }
  411. mframe_t flyer_frames_start_melee[] = {
  412. { ai_charge, 0, flyer_pop_blades },
  413. { ai_charge },
  414. { ai_charge },
  415. { ai_charge },
  416. { ai_charge },
  417. { ai_charge }
  418. };
  419. MMOVE_T(flyer_move_start_melee) = { FRAME_attak101, FRAME_attak106, flyer_frames_start_melee, flyer_loop_melee };
  420. mframe_t flyer_frames_end_melee[] = {
  421. { ai_charge },
  422. { ai_charge },
  423. { ai_charge }
  424. };
  425. MMOVE_T(flyer_move_end_melee) = { FRAME_attak119, FRAME_attak121, flyer_frames_end_melee, flyer_run };
  426. mframe_t flyer_frames_loop_melee[] = {
  427. { ai_charge }, // Loop Start
  428. { ai_charge },
  429. { ai_charge, 0, flyer_slash_left }, // Left Wing Strike
  430. { ai_charge },
  431. { ai_charge },
  432. { ai_charge },
  433. { ai_charge },
  434. { ai_charge, 0, flyer_slash_right }, // Right Wing Strike
  435. { ai_charge },
  436. { ai_charge },
  437. { ai_charge },
  438. { ai_charge } // Loop Ends
  439. };
  440. MMOVE_T(flyer_move_loop_melee) = { FRAME_attak107, FRAME_attak118, flyer_frames_loop_melee, flyer_check_melee };
  441. void flyer_loop_melee(edict_t *self)
  442. {
  443. M_SetAnimation(self, &flyer_move_loop_melee);
  444. }
  445. static void flyer_set_fly_parameters(edict_t *self, bool melee)
  446. {
  447. if (melee)
  448. {
  449. // engage thrusters for a slice
  450. self->monsterinfo.fly_pinned = false;
  451. self->monsterinfo.fly_thrusters = true;
  452. self->monsterinfo.fly_position_time = 0_sec;
  453. self->monsterinfo.fly_acceleration = 20.f;
  454. self->monsterinfo.fly_speed = 210.f;
  455. self->monsterinfo.fly_min_distance = 0.f;
  456. self->monsterinfo.fly_max_distance = 10.f;
  457. }
  458. else
  459. {
  460. self->monsterinfo.fly_thrusters = false;
  461. self->monsterinfo.fly_acceleration = 15.f;
  462. self->monsterinfo.fly_speed = 165.f;
  463. self->monsterinfo.fly_min_distance = 45.f;
  464. self->monsterinfo.fly_max_distance = 200.f;
  465. }
  466. }
  467. MONSTERINFO_ATTACK(flyer_attack) (edict_t *self) -> void
  468. {
  469. if (self->mass > 50)
  470. {
  471. flyer_run(self);
  472. return;
  473. }
  474. float range = range_to(self, self->enemy);
  475. if (self->enemy && visible(self, self->enemy) && range <= 225.f && frandom() > (range / 225.f) * 0.35f)
  476. {
  477. // fly-by slicing!
  478. self->monsterinfo.attack_state = AS_STRAIGHT;
  479. M_SetAnimation(self, &flyer_move_start_melee);
  480. flyer_set_fly_parameters(self, true);
  481. }
  482. else
  483. {
  484. self->monsterinfo.attack_state = AS_STRAIGHT;
  485. M_SetAnimation(self, &flyer_move_attack2);
  486. }
  487. // [Paril-KEX] for alternate fly mode, sometimes we'll pin us
  488. // down, kind of like a pseudo-stand ground
  489. if (!self->monsterinfo.fly_pinned && brandom() && self->enemy && visible(self, self->enemy))
  490. {
  491. self->monsterinfo.fly_pinned = true;
  492. self->monsterinfo.fly_position_time = max(self->monsterinfo.fly_position_time, self->monsterinfo.fly_position_time + 1.7_sec); // make sure there's enough time for attack2/3
  493. if (brandom())
  494. self->monsterinfo.fly_ideal_position = self->s.origin + (self->velocity * frandom()); // pin to our current position
  495. else
  496. self->monsterinfo.fly_ideal_position += self->enemy->s.origin; // make un-relative
  497. }
  498. // if we're currently pinned, fly_position_time will unpin us eventually
  499. }
  500. MONSTERINFO_MELEE(flyer_melee) (edict_t *self) -> void
  501. {
  502. if (self->mass > 50)
  503. flyer_run(self);
  504. else
  505. {
  506. M_SetAnimation(self, &flyer_move_start_melee);
  507. flyer_set_fly_parameters(self, true);
  508. }
  509. }
  510. void flyer_check_melee(edict_t *self)
  511. {
  512. if (range_to(self, self->enemy) <= RANGE_MELEE)
  513. {
  514. if (self->monsterinfo.melee_debounce_time <= level.time)
  515. {
  516. M_SetAnimation(self, &flyer_move_loop_melee);
  517. return;
  518. }
  519. }
  520. M_SetAnimation(self, &flyer_move_end_melee);
  521. flyer_set_fly_parameters(self, false);
  522. }
  523. PAIN(flyer_pain) (edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod) -> void
  524. {
  525. int n;
  526. // pmm - kamikaze's don't feel pain
  527. if (self->mass != 50)
  528. return;
  529. // pmm
  530. if (level.time < self->pain_debounce_time)
  531. return;
  532. self->pain_debounce_time = level.time + 3_sec;
  533. n = irandom(3);
  534. if (n == 0)
  535. gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
  536. else if (n == 1)
  537. gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0);
  538. else
  539. gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
  540. if (!M_ShouldReactToPain(self, mod))
  541. return; // no pain anims in nightmare
  542. flyer_set_fly_parameters(self, false);
  543. if (n == 0)
  544. M_SetAnimation(self, &flyer_move_pain1);
  545. else if (n == 1)
  546. M_SetAnimation(self, &flyer_move_pain2);
  547. else
  548. M_SetAnimation(self, &flyer_move_pain3);
  549. }
  550. MONSTERINFO_SETSKIN(flyer_setskin) (edict_t *self) -> void
  551. {
  552. if (self->health < (self->max_health / 2))
  553. self->s.skinnum = 1;
  554. else
  555. self->s.skinnum = 0;
  556. }
  557. DIE(flyer_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void
  558. {
  559. gi.sound(self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0);
  560. gi.WriteByte(svc_temp_entity);
  561. gi.WriteByte(TE_EXPLOSION1);
  562. gi.WritePosition(self->s.origin);
  563. gi.multicast(self->s.origin, MULTICAST_PHS, false);
  564. self->s.skinnum /= 2;
  565. ThrowGibs(self, 55, {
  566. { 2, "models/objects/gibs/sm_metal/tris.md2" },
  567. { 2, "models/objects/gibs/sm_meat/tris.md2" },
  568. { "models/monsters/flyer/gibs/base.md2", GIB_SKINNED },
  569. { 2, "models/monsters/flyer/gibs/gun.md2", GIB_SKINNED },
  570. { 2, "models/monsters/flyer/gibs/wing.md2", GIB_SKINNED },
  571. { "models/monsters/flyer/gibs/head.md2", GIB_SKINNED | GIB_HEAD }
  572. });
  573. self->touch = nullptr;
  574. }
  575. // PMM - kamikaze code .. blow up if blocked
  576. MONSTERINFO_BLOCKED(flyer_blocked) (edict_t *self, float dist) -> bool
  577. {
  578. // kamikaze = 100, normal = 50
  579. if (self->mass == 100)
  580. {
  581. flyer_kamikaze_check(self);
  582. // if the above didn't blow us up (i.e. I got blocked by the player)
  583. if (self->inuse)
  584. T_Damage(self, self, self, vec3_origin, self->s.origin, vec3_origin, 9999, 100, DAMAGE_NONE, MOD_UNKNOWN);
  585. return true;
  586. }
  587. return false;
  588. }
  589. TOUCH(kamikaze_touch) (edict_t *ent, edict_t *other, const trace_t &tr, bool other_touching_self) -> void
  590. {
  591. T_Damage(ent, ent, ent, ent->velocity.normalized(), ent->s.origin, ent->velocity.normalized(), 9999, 100, DAMAGE_NONE, MOD_UNKNOWN);
  592. }
  593. TOUCH(flyer_touch) (edict_t *ent, edict_t *other, const trace_t &tr, bool other_touching_self) -> void
  594. {
  595. if ((other->monsterinfo.aiflags & AI_ALTERNATE_FLY) && (other->flags & FL_FLY) &&
  596. (ent->monsterinfo.duck_wait_time < level.time))
  597. {
  598. ent->monsterinfo.duck_wait_time = level.time + 1_sec;
  599. ent->monsterinfo.fly_thrusters = false;
  600. vec3_t dir = (ent->s.origin - other->s.origin).normalized();
  601. ent->velocity = dir * 500.f;
  602. gi.WriteByte(svc_temp_entity);
  603. gi.WriteByte(TE_SPLASH);
  604. gi.WriteByte(32);
  605. gi.WritePosition(tr.endpos);
  606. gi.WriteDir(dir);
  607. gi.WriteByte(SPLASH_SPARKS);
  608. gi.multicast(tr.endpos, MULTICAST_PVS, false);
  609. }
  610. }
  611. /*QUAKED monster_flyer (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight
  612. */
  613. void SP_monster_flyer(edict_t *self)
  614. {
  615. if ( !M_AllowSpawn( self ) ) {
  616. G_FreeEdict( self );
  617. return;
  618. }
  619. sound_sight.assign("flyer/flysght1.wav");
  620. sound_idle.assign("flyer/flysrch1.wav");
  621. sound_pain1.assign("flyer/flypain1.wav");
  622. sound_pain2.assign("flyer/flypain2.wav");
  623. sound_slash.assign("flyer/flyatck2.wav");
  624. sound_sproing.assign("flyer/flyatck1.wav");
  625. sound_die.assign("flyer/flydeth1.wav");
  626. gi.soundindex("flyer/flyatck3.wav");
  627. self->s.modelindex = gi.modelindex("models/monsters/flyer/tris.md2");
  628. gi.modelindex("models/monsters/flyer/gibs/base.md2");
  629. gi.modelindex("models/monsters/flyer/gibs/wing.md2");
  630. gi.modelindex("models/monsters/flyer/gibs/gun.md2");
  631. gi.modelindex("models/monsters/flyer/gibs/head.md2");
  632. self->mins = { -16, -16, -24 };
  633. // PMM - shortened to 16 from 32
  634. self->maxs = { 16, 16, 16 };
  635. self->movetype = MOVETYPE_STEP;
  636. self->solid = SOLID_BBOX;
  637. self->viewheight = 12;
  638. self->monsterinfo.engine_sound = gi.soundindex("flyer/flyidle1.wav");
  639. self->health = 50 * st.health_multiplier;
  640. self->mass = 50;
  641. self->pain = flyer_pain;
  642. self->die = flyer_die;
  643. self->monsterinfo.stand = flyer_stand;
  644. self->monsterinfo.walk = flyer_walk;
  645. self->monsterinfo.run = flyer_run;
  646. self->monsterinfo.attack = flyer_attack;
  647. self->monsterinfo.melee = flyer_melee;
  648. self->monsterinfo.sight = flyer_sight;
  649. self->monsterinfo.idle = flyer_idle;
  650. self->monsterinfo.blocked = flyer_blocked;
  651. self->monsterinfo.setskin = flyer_setskin;
  652. gi.linkentity(self);
  653. M_SetAnimation(self, &flyer_move_stand);
  654. self->monsterinfo.scale = MODEL_SCALE;
  655. if (self->s.effects & EF_ROCKET)
  656. {
  657. // PMM - normal flyer has mass of 50
  658. self->mass = 100;
  659. self->yaw_speed = 5;
  660. self->touch = kamikaze_touch;
  661. }
  662. else
  663. {
  664. self->monsterinfo.aiflags |= AI_ALTERNATE_FLY;
  665. self->monsterinfo.fly_buzzard = true;
  666. flyer_set_fly_parameters(self, false);
  667. self->touch = flyer_touch;
  668. }
  669. flymonster_start(self);
  670. }
  671. // PMM - suicide fliers
  672. void SP_monster_kamikaze(edict_t *self)
  673. {
  674. if ( !M_AllowSpawn( self ) ) {
  675. G_FreeEdict( self );
  676. return;
  677. }
  678. self->s.effects |= EF_ROCKET;
  679. SP_monster_flyer(self);
  680. }