m_guardian.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524
  1. // Copyright (c) ZeniMax Media Inc.
  2. // Licensed under the GNU General Public License 2.0.
  3. /*
  4. ==============================================================================
  5. GUARDIAN
  6. ==============================================================================
  7. */
  8. #include "g_local.h"
  9. #include "m_guardian.h"
  10. #include "m_flash.h"
  11. //
  12. // stand
  13. //
  14. mframe_t guardian_frames_stand[] = {
  15. { ai_stand },
  16. { ai_stand },
  17. { ai_stand },
  18. { ai_stand },
  19. { ai_stand },
  20. { ai_stand },
  21. { ai_stand },
  22. { ai_stand },
  23. { ai_stand },
  24. { ai_stand },
  25. { ai_stand },
  26. { ai_stand },
  27. { ai_stand },
  28. { ai_stand },
  29. { ai_stand },
  30. { ai_stand },
  31. { ai_stand },
  32. { ai_stand },
  33. { ai_stand },
  34. { ai_stand },
  35. { ai_stand },
  36. { ai_stand },
  37. { ai_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. };
  68. MMOVE_T(guardian_move_stand) = { FRAME_idle1, FRAME_idle52, guardian_frames_stand, nullptr };
  69. MONSTERINFO_STAND(guardian_stand) (edict_t *self) -> void
  70. {
  71. M_SetAnimation(self, &guardian_move_stand);
  72. }
  73. //
  74. // walk
  75. //
  76. static cached_soundindex sound_step;
  77. void guardian_footstep(edict_t *self)
  78. {
  79. gi.sound(self, CHAN_BODY, sound_step, 1.f, ATTN_NORM, 0.0f);
  80. }
  81. mframe_t guardian_frames_walk[] = {
  82. { ai_walk, 8 },
  83. { ai_walk, 8 },
  84. { ai_walk, 8 },
  85. { ai_walk, 8 },
  86. { ai_walk, 8 },
  87. { ai_walk, 8 },
  88. { ai_walk, 8 },
  89. { ai_walk, 8, guardian_footstep },
  90. { ai_walk, 8 },
  91. { ai_walk, 8 },
  92. { ai_walk, 8 },
  93. { ai_walk, 8 },
  94. { ai_walk, 8 },
  95. { ai_walk, 8 },
  96. { ai_walk, 8 },
  97. { ai_walk, 8 },
  98. { ai_walk, 8 },
  99. { ai_walk, 8, guardian_footstep },
  100. { ai_walk, 8 }
  101. };
  102. MMOVE_T(guardian_move_walk) = { FRAME_walk1, FRAME_walk19, guardian_frames_walk, nullptr };
  103. MONSTERINFO_WALK(guardian_walk) (edict_t *self) -> void
  104. {
  105. M_SetAnimation(self, &guardian_move_walk);
  106. }
  107. //
  108. // run
  109. //
  110. mframe_t guardian_frames_run[] = {
  111. { ai_run, 8 },
  112. { ai_run, 8 },
  113. { ai_run, 8 },
  114. { ai_run, 8 },
  115. { ai_run, 8 },
  116. { ai_run, 8 },
  117. { ai_run, 8 },
  118. { ai_run, 8, guardian_footstep },
  119. { ai_run, 8 },
  120. { ai_run, 8 },
  121. { ai_run, 8 },
  122. { ai_run, 8 },
  123. { ai_run, 8 },
  124. { ai_run, 8 },
  125. { ai_run, 8 },
  126. { ai_run, 8 },
  127. { ai_run, 8 },
  128. { ai_run, 8, guardian_footstep },
  129. { ai_run, 8 }
  130. };
  131. MMOVE_T(guardian_move_run) = { FRAME_walk1, FRAME_walk19, guardian_frames_run, nullptr };
  132. MONSTERINFO_RUN(guardian_run) (edict_t *self) -> void
  133. {
  134. if (self->monsterinfo.aiflags & AI_STAND_GROUND)
  135. {
  136. M_SetAnimation(self, &guardian_move_stand);
  137. return;
  138. }
  139. M_SetAnimation(self, &guardian_move_run);
  140. }
  141. //
  142. // pain
  143. //
  144. mframe_t guardian_frames_pain1[] = {
  145. { ai_move },
  146. { ai_move },
  147. { ai_move },
  148. { ai_move },
  149. { ai_move },
  150. { ai_move },
  151. { ai_move },
  152. { ai_move }
  153. };
  154. MMOVE_T(guardian_move_pain1) = { FRAME_pain1_1, FRAME_pain1_8, guardian_frames_pain1, guardian_run };
  155. PAIN(guardian_pain) (edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod) -> void
  156. {
  157. if (mod.id != MOD_CHAINFIST && damage <= 10)
  158. return;
  159. if (level.time < self->pain_debounce_time)
  160. return;
  161. if (mod.id != MOD_CHAINFIST && damage <= 75)
  162. if (frandom() > 0.2f)
  163. return;
  164. // don't go into pain while attacking
  165. if ((self->s.frame >= FRAME_atk1_spin1) && (self->s.frame <= FRAME_atk1_spin15))
  166. return;
  167. if ((self->s.frame >= FRAME_atk2_fire1) && (self->s.frame <= FRAME_atk2_fire4))
  168. return;
  169. if ((self->s.frame >= FRAME_kick_in1) && (self->s.frame <= FRAME_kick_in13))
  170. return;
  171. self->pain_debounce_time = level.time + 3_sec;
  172. //gi.sound(self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0);
  173. if (!M_ShouldReactToPain(self, mod))
  174. return; // no pain anims in nightmare
  175. M_SetAnimation(self, &guardian_move_pain1);
  176. self->monsterinfo.weapon_sound = 0;
  177. }
  178. mframe_t guardian_frames_atk1_out[] = {
  179. { ai_charge },
  180. { ai_charge },
  181. { ai_charge }
  182. };
  183. MMOVE_T(guardian_atk1_out) = { FRAME_atk1_out1, FRAME_atk1_out3, guardian_frames_atk1_out, guardian_run };
  184. void guardian_atk1_finish(edict_t *self)
  185. {
  186. M_SetAnimation(self, &guardian_atk1_out);
  187. self->monsterinfo.weapon_sound = 0;
  188. }
  189. static cached_soundindex sound_charge;
  190. static cached_soundindex sound_spin_loop;
  191. void guardian_atk1_charge(edict_t *self)
  192. {
  193. self->monsterinfo.weapon_sound = sound_spin_loop;
  194. gi.sound(self, CHAN_WEAPON, sound_charge, 1.f, ATTN_NORM, 0.f);
  195. }
  196. void guardian_fire_blaster(edict_t *self)
  197. {
  198. vec3_t forward, right, target;
  199. vec3_t start;
  200. monster_muzzleflash_id_t id = MZ2_GUARDIAN_BLASTER;
  201. AngleVectors(self->s.angles, forward, right, nullptr);
  202. start = M_ProjectFlashSource(self, monster_flash_offset[id], forward, right);
  203. target = self->enemy->s.origin;
  204. target[2] += self->enemy->viewheight;
  205. for (int i = 0; i < 3; i++)
  206. target[i] += crandom_open() * 5.f;
  207. forward = target - start;
  208. forward.normalize();
  209. monster_fire_blaster(self, start, forward, 2, 1000, id, (self->s.frame % 4) ? EF_NONE : EF_HYPERBLASTER);
  210. if (self->enemy && self->enemy->health > 0 &&
  211. self->s.frame == FRAME_atk1_spin12 && self->timestamp > level.time && visible(self, self->enemy))
  212. self->monsterinfo.nextframe = FRAME_atk1_spin5;
  213. }
  214. mframe_t guardian_frames_atk1_spin[] = {
  215. { ai_charge, 0, guardian_atk1_charge },
  216. { ai_charge },
  217. { ai_charge },
  218. { ai_charge },
  219. { ai_charge, 0, guardian_fire_blaster },
  220. { ai_charge, 0, guardian_fire_blaster },
  221. { ai_charge, 0, guardian_fire_blaster },
  222. { ai_charge, 0, guardian_fire_blaster },
  223. { ai_charge, 0, guardian_fire_blaster },
  224. { ai_charge, 0, guardian_fire_blaster },
  225. { ai_charge, 0, guardian_fire_blaster },
  226. { ai_charge, 0, guardian_fire_blaster },
  227. { ai_charge, 0 },
  228. { ai_charge, 0 },
  229. { ai_charge, 0 }
  230. };
  231. MMOVE_T(guardian_move_atk1_spin) = { FRAME_atk1_spin1, FRAME_atk1_spin15, guardian_frames_atk1_spin, guardian_atk1_finish };
  232. void guardian_atk1(edict_t *self)
  233. {
  234. M_SetAnimation(self, &guardian_move_atk1_spin);
  235. self->timestamp = level.time + 650_ms + random_time(1.5_sec);
  236. }
  237. mframe_t guardian_frames_atk1_in[] = {
  238. { ai_charge },
  239. { ai_charge },
  240. { ai_charge }
  241. };
  242. MMOVE_T(guardian_move_atk1_in) = { FRAME_atk1_in1, FRAME_atk1_in3, guardian_frames_atk1_in, guardian_atk1 };
  243. mframe_t guardian_frames_atk2_out[] = {
  244. { ai_charge },
  245. { ai_charge },
  246. { ai_charge },
  247. { ai_charge },
  248. { ai_charge, 0, guardian_footstep },
  249. { ai_charge },
  250. { ai_charge }
  251. };
  252. MMOVE_T(guardian_move_atk2_out) = { FRAME_atk2_out1, FRAME_atk2_out7, guardian_frames_atk2_out, guardian_run };
  253. void guardian_atk2_out(edict_t *self)
  254. {
  255. M_SetAnimation(self, &guardian_move_atk2_out);
  256. }
  257. static cached_soundindex sound_laser;
  258. constexpr vec3_t laser_positions[] = {
  259. { 125.0f, -70.f, 60.f },
  260. { 112.0f, -62.f, 60.f }
  261. };
  262. PRETHINK(guardian_fire_update) (edict_t *laser) -> void
  263. {
  264. edict_t *self = laser->owner;
  265. vec3_t forward, right, target;
  266. vec3_t start;
  267. AngleVectors(self->s.angles, forward, right, nullptr);
  268. start = M_ProjectFlashSource(self, laser_positions[1 - (self->s.frame & 1)], forward, right);
  269. target = self->enemy->s.origin + self->enemy->mins;
  270. for (int i = 0; i < 3; i++)
  271. target[i] += frandom() * self->enemy->size[i];
  272. forward = target - start;
  273. forward.normalize();
  274. laser->s.origin = start;
  275. laser->movedir = forward;
  276. gi.linkentity(laser);
  277. dabeam_update(laser, false);
  278. }
  279. void guardian_laser_fire(edict_t *self)
  280. {
  281. gi.sound(self, CHAN_WEAPON, sound_laser, 1.f, ATTN_NORM, 0.f);
  282. monster_fire_dabeam(self, 25, self->s.frame & 1, guardian_fire_update);
  283. }
  284. mframe_t guardian_frames_atk2_fire[] = {
  285. { ai_charge, 0, guardian_laser_fire },
  286. { ai_charge, 0, guardian_laser_fire },
  287. { ai_charge, 0, guardian_laser_fire },
  288. { ai_charge, 0, guardian_laser_fire }
  289. };
  290. MMOVE_T(guardian_move_atk2_fire) = { FRAME_atk2_fire1, FRAME_atk2_fire4, guardian_frames_atk2_fire, guardian_atk2_out };
  291. void guardian_atk2(edict_t *self)
  292. {
  293. M_SetAnimation(self, &guardian_move_atk2_fire);
  294. }
  295. mframe_t guardian_frames_atk2_in[] = {
  296. { ai_charge, 0, guardian_footstep },
  297. { ai_charge },
  298. { ai_charge },
  299. { ai_charge, 0, guardian_footstep },
  300. { ai_charge },
  301. { ai_charge },
  302. { ai_charge },
  303. { ai_charge, 0, guardian_footstep },
  304. { ai_charge },
  305. { ai_charge },
  306. { ai_charge },
  307. { ai_charge }
  308. };
  309. MMOVE_T(guardian_move_atk2_in) = { FRAME_atk2_in1, FRAME_atk2_in12, guardian_frames_atk2_in, guardian_atk2 };
  310. void guardian_kick(edict_t *self)
  311. {
  312. if (!fire_hit(self, { MELEE_DISTANCE, 0, -80 }, 85, 700))
  313. self->monsterinfo.melee_debounce_time = level.time + 1000_ms;
  314. }
  315. mframe_t guardian_frames_kick[] = {
  316. { ai_charge },
  317. { ai_charge, 0, guardian_footstep },
  318. { ai_charge },
  319. { ai_charge },
  320. { ai_charge },
  321. { ai_charge, 0, guardian_kick },
  322. { ai_charge },
  323. { ai_charge },
  324. { ai_charge },
  325. { ai_charge },
  326. { ai_charge, 0, guardian_footstep },
  327. { ai_charge },
  328. { ai_charge }
  329. };
  330. MMOVE_T(guardian_move_kick) = { FRAME_kick_in1, FRAME_kick_in13, guardian_frames_kick, guardian_run };
  331. MONSTERINFO_ATTACK(guardian_attack) (edict_t *self) -> void
  332. {
  333. if (!self->enemy || !self->enemy->inuse)
  334. return;
  335. float r = range_to(self, self->enemy);
  336. if (r > RANGE_NEAR)
  337. M_SetAnimation(self, &guardian_move_atk2_in);
  338. else if (self->monsterinfo.melee_debounce_time < level.time && r < 120.f)
  339. M_SetAnimation(self, &guardian_move_kick);
  340. else
  341. M_SetAnimation(self, &guardian_move_atk1_in);
  342. }
  343. //
  344. // death
  345. //
  346. void guardian_explode(edict_t *self)
  347. {
  348. gi.WriteByte(svc_temp_entity);
  349. gi.WriteByte(TE_EXPLOSION1_BIG);
  350. gi.WritePosition((self->s.origin + self->mins) + vec3_t { frandom() * self->size[0], frandom() * self->size[1], frandom() * self->size[2] });
  351. gi.multicast(self->s.origin, MULTICAST_ALL, false);
  352. }
  353. constexpr const char *gibs[] = {
  354. "models/monsters/guardian/gib1.md2",
  355. "models/monsters/guardian/gib2.md2",
  356. "models/monsters/guardian/gib3.md2",
  357. "models/monsters/guardian/gib4.md2",
  358. "models/monsters/guardian/gib5.md2",
  359. "models/monsters/guardian/gib6.md2",
  360. "models/monsters/guardian/gib7.md2"
  361. };
  362. void guardian_dead(edict_t *self)
  363. {
  364. for (int i = 0; i < 3; i++)
  365. guardian_explode(self);
  366. ThrowGibs(self, 125, {
  367. { 2, "models/objects/gibs/sm_meat/tris.md2" },
  368. { 4, "models/objects/gibs/sm_metal/tris.md2", GIB_METALLIC },
  369. { 2, gibs[0], GIB_METALLIC },
  370. { 2, gibs[1], GIB_METALLIC },
  371. { 2, gibs[2], GIB_METALLIC },
  372. { 2, gibs[3], GIB_METALLIC },
  373. { 2, gibs[4], GIB_METALLIC },
  374. { 2, gibs[5], GIB_METALLIC },
  375. { gibs[6], GIB_METALLIC | GIB_HEAD }
  376. });
  377. }
  378. mframe_t guardian_frames_death1[FRAME_death26 - FRAME_death1 + 1] = {
  379. { ai_move, 0, BossExplode },
  380. { ai_move },
  381. { ai_move },
  382. { ai_move },
  383. { ai_move },
  384. { ai_move },
  385. { ai_move },
  386. { ai_move },
  387. { ai_move },
  388. { ai_move },
  389. { ai_move },
  390. { ai_move },
  391. { ai_move },
  392. { ai_move },
  393. { ai_move },
  394. { ai_move },
  395. { ai_move },
  396. { ai_move },
  397. { ai_move },
  398. { ai_move },
  399. { ai_move },
  400. { ai_move },
  401. { ai_move },
  402. { ai_move },
  403. { ai_move },
  404. { ai_move }
  405. };
  406. MMOVE_T(guardian_move_death) = { FRAME_death1, FRAME_death26, guardian_frames_death1, guardian_dead };
  407. DIE(guardian_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void
  408. {
  409. // regular death
  410. //gi.sound(self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0);
  411. self->monsterinfo.weapon_sound = 0;
  412. self->deadflag = true;
  413. self->takedamage = true;
  414. M_SetAnimation(self, &guardian_move_death);
  415. }
  416. //
  417. // monster_tank
  418. //
  419. /*QUAKED monster_guardian (1 .5 0) (-96 -96 -66) (96 96 62) Ambush Trigger_Spawn Sight
  420. */
  421. void SP_monster_guardian(edict_t *self)
  422. {
  423. if ( !M_AllowSpawn( self ) ) {
  424. G_FreeEdict( self );
  425. return;
  426. }
  427. sound_step.assign("zortemp/step.wav");
  428. sound_charge.assign("weapons/hyprbu1a.wav");
  429. sound_spin_loop.assign("weapons/hyprbl1a.wav");
  430. sound_laser.assign("weapons/laser2.wav");
  431. for (auto &gib : gibs)
  432. gi.modelindex(gib);
  433. self->s.modelindex = gi.modelindex("models/monsters/guardian/tris.md2");
  434. self->mins = { -96, -96, -66 };
  435. self->maxs = { 96, 96, 62 };
  436. self->movetype = MOVETYPE_STEP;
  437. self->solid = SOLID_BBOX;
  438. self->health = 2500 * st.health_multiplier;
  439. self->gib_health = -200;
  440. self->monsterinfo.scale = MODEL_SCALE;
  441. self->mass = 850;
  442. self->pain = guardian_pain;
  443. self->die = guardian_die;
  444. self->monsterinfo.stand = guardian_stand;
  445. self->monsterinfo.walk = guardian_walk;
  446. self->monsterinfo.run = guardian_run;
  447. self->monsterinfo.attack = guardian_attack;
  448. gi.linkentity(self);
  449. M_SetAnimation(self, &guardian_move_stand);
  450. walkmonster_start(self);
  451. }