m_shambler.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599
  1. // Copyright (c) ZeniMax Media Inc.
  2. // Licensed under the GNU General Public License 2.0.
  3. /*
  4. ==============================================================================
  5. SHAMBLER
  6. ==============================================================================
  7. */
  8. #include "g_local.h"
  9. #include "m_shambler.h"
  10. #include "m_flash.h"
  11. static cached_soundindex sound_pain;
  12. static cached_soundindex sound_idle;
  13. static cached_soundindex sound_die;
  14. static cached_soundindex sound_sight;
  15. static cached_soundindex sound_windup;
  16. static cached_soundindex sound_melee1;
  17. static cached_soundindex sound_melee2;
  18. static cached_soundindex sound_smack;
  19. static cached_soundindex sound_boom;
  20. //
  21. // misc
  22. //
  23. MONSTERINFO_SIGHT(shambler_sight) (edict_t* self, edict_t* other) -> void
  24. {
  25. gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
  26. }
  27. constexpr vec3_t lightning_left_hand[] = {
  28. { 44, 36, 25 },
  29. { 10, 44, 57 },
  30. { -1, 40, 70 },
  31. { -10, 34, 75 },
  32. { 7.4f, 24, 89 }
  33. };
  34. constexpr vec3_t lightning_right_hand[] = {
  35. { 28, -38, 25 },
  36. { 31, -7, 70 },
  37. { 20, 0, 80 },
  38. { 16, 1.2f, 81 },
  39. { 27, -11, 83 }
  40. };
  41. static void shambler_lightning_update(edict_t *self)
  42. {
  43. edict_t *lightning = self->beam;
  44. if (self->s.frame >= FRAME_magic01 + q_countof(lightning_left_hand))
  45. {
  46. G_FreeEdict(lightning);
  47. self->beam = nullptr;
  48. return;
  49. }
  50. vec3_t f, r;
  51. AngleVectors(self->s.angles, f, r, nullptr);
  52. lightning->s.origin = M_ProjectFlashSource(self, lightning_left_hand[self->s.frame - FRAME_magic01], f, r);
  53. lightning->s.old_origin = M_ProjectFlashSource(self, lightning_right_hand[self->s.frame - FRAME_magic01], f, r);
  54. gi.linkentity(lightning);
  55. }
  56. void shambler_windup(edict_t* self)
  57. {
  58. gi.sound(self, CHAN_WEAPON, sound_windup, 1, ATTN_NORM, 0);
  59. edict_t *lightning = self->beam = G_Spawn();
  60. lightning->s.modelindex = gi.modelindex("models/proj/lightning/tris.md2");
  61. lightning->s.renderfx |= RF_BEAM;
  62. lightning->owner = self;
  63. shambler_lightning_update(self);
  64. }
  65. MONSTERINFO_IDLE(shambler_idle) (edict_t* self) -> void
  66. {
  67. gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0);
  68. }
  69. void shambler_maybe_idle(edict_t* self)
  70. {
  71. if (frandom() > 0.8)
  72. gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0);
  73. }
  74. //
  75. // stand
  76. //
  77. mframe_t shambler_frames_stand[] = {
  78. { ai_stand },
  79. { ai_stand },
  80. { ai_stand },
  81. { ai_stand },
  82. { ai_stand },
  83. { ai_stand },
  84. { ai_stand },
  85. { ai_stand },
  86. { ai_stand },
  87. { ai_stand },
  88. { ai_stand },
  89. { ai_stand },
  90. { ai_stand },
  91. { ai_stand },
  92. { ai_stand },
  93. { ai_stand },
  94. { ai_stand }
  95. };
  96. MMOVE_T(shambler_move_stand) = { FRAME_stand01, FRAME_stand17, shambler_frames_stand, nullptr };
  97. MONSTERINFO_STAND(shambler_stand) (edict_t* self) -> void
  98. {
  99. M_SetAnimation(self, &shambler_move_stand);
  100. }
  101. //
  102. // walk
  103. //
  104. void shambler_walk(edict_t* self);
  105. mframe_t shambler_frames_walk[] = {
  106. { ai_walk, 10 }, // FIXME: add footsteps?
  107. { ai_walk, 9 },
  108. { ai_walk, 9 },
  109. { ai_walk, 5 },
  110. { ai_walk, 6 },
  111. { ai_walk, 12 },
  112. { ai_walk, 8 },
  113. { ai_walk, 3 },
  114. { ai_walk, 13 },
  115. { ai_walk, 9 },
  116. { ai_walk, 7, shambler_maybe_idle },
  117. { ai_walk, 5 },
  118. };
  119. MMOVE_T(shambler_move_walk) = { FRAME_walk01, FRAME_walk12, shambler_frames_walk, nullptr };
  120. MONSTERINFO_WALK(shambler_walk) (edict_t* self) -> void
  121. {
  122. M_SetAnimation(self, &shambler_move_walk);
  123. }
  124. //
  125. // run
  126. //
  127. void shambler_run(edict_t* self);
  128. mframe_t shambler_frames_run[] = {
  129. { ai_run, 20 }, // FIXME: add footsteps?
  130. { ai_run, 24 },
  131. { ai_run, 20 },
  132. { ai_run, 20 },
  133. { ai_run, 24 },
  134. { ai_run, 20, shambler_maybe_idle },
  135. };
  136. MMOVE_T(shambler_move_run) = { FRAME_run01, FRAME_run06, shambler_frames_run, nullptr };
  137. MONSTERINFO_RUN(shambler_run) (edict_t* self) -> void
  138. {
  139. if (self->enemy && self->enemy->client)
  140. self->monsterinfo.aiflags |= AI_BRUTAL;
  141. else
  142. self->monsterinfo.aiflags &= ~AI_BRUTAL;
  143. if (self->monsterinfo.aiflags & AI_STAND_GROUND)
  144. {
  145. M_SetAnimation(self, &shambler_move_stand);
  146. return;
  147. }
  148. M_SetAnimation(self, &shambler_move_run);
  149. }
  150. //
  151. // pain
  152. //
  153. // FIXME: needs halved explosion damage
  154. mframe_t shambler_frames_pain[] = {
  155. { ai_move },
  156. { ai_move },
  157. { ai_move },
  158. { ai_move },
  159. { ai_move },
  160. { ai_move },
  161. };
  162. MMOVE_T(shambler_move_pain) = { FRAME_pain01, FRAME_pain06, shambler_frames_pain, shambler_run };
  163. PAIN(shambler_pain) (edict_t* self, edict_t* other, float kick, int damage, const mod_t &mod) -> void
  164. {
  165. if (level.time < self->timestamp)
  166. return;
  167. self->timestamp = level.time + 1_ms;
  168. gi.sound(self, CHAN_AUTO, sound_pain, 1, ATTN_NORM, 0);
  169. if (mod.id != MOD_CHAINFIST && damage <= 30 && frandom() > 0.2f)
  170. return;
  171. // If hard or nightmare, don't go into pain while attacking
  172. if (skill->integer >= 2)
  173. {
  174. if ((self->s.frame >= FRAME_smash01) && (self->s.frame <= FRAME_smash12))
  175. return;
  176. if ((self->s.frame >= FRAME_swingl01) && (self->s.frame <= FRAME_swingl09))
  177. return;
  178. if ((self->s.frame >= FRAME_swingr01) && (self->s.frame <= FRAME_swingr09))
  179. return;
  180. }
  181. if (!M_ShouldReactToPain(self, mod))
  182. return; // no pain anims in nightmare
  183. if (level.time < self->pain_debounce_time)
  184. return;
  185. self->pain_debounce_time = level.time + 2_sec;
  186. M_SetAnimation(self, &shambler_move_pain);
  187. }
  188. MONSTERINFO_SETSKIN(shambler_setskin) (edict_t* self) -> void
  189. {
  190. // FIXME: create pain skin?
  191. //if (self->health < (self->max_health / 2))
  192. // self->s.skinnum |= 1;
  193. //else
  194. // self->s.skinnum &= ~1;
  195. }
  196. //
  197. // attacks
  198. //
  199. /*
  200. void() sham_magic3 =[ $magic3, sham_magic4 ] {
  201. ai_face();
  202. self.nextthink = self.nextthink + 0.2;
  203. local entity o;
  204. self.effects = self.effects | EF_MUZZLEFLASH;
  205. ai_face();
  206. self.owner = spawn();
  207. o = self.owner;
  208. setmodel (o, "progs/s_light.mdl");
  209. setorigin (o, self.origin);
  210. o.angles = self.angles;
  211. o.nextthink = time + 0.7;
  212. o.think = SUB_Remove;
  213. };
  214. */
  215. void ShamblerSaveLoc(edict_t* self)
  216. {
  217. self->pos1 = self->enemy->s.origin; // save for aiming the shot
  218. self->pos1[2] += self->enemy->viewheight;
  219. self->monsterinfo.nextframe = FRAME_magic09;
  220. gi.sound(self, CHAN_WEAPON, sound_boom, 1, ATTN_NORM, 0);
  221. shambler_lightning_update(self);
  222. }
  223. constexpr spawnflags_t SPAWNFLAG_SHAMBLER_PRECISE = 1_spawnflag;
  224. vec3_t FindShamblerOffset(edict_t *self)
  225. {
  226. vec3_t offset = { 0, 0, 48.f };
  227. for (int i = 0; i < 8; i++)
  228. {
  229. if (M_CheckClearShot(self, offset))
  230. return offset;
  231. offset.z -= 4.f;
  232. }
  233. return { 0, 0, 48.f };
  234. }
  235. void ShamblerCastLightning(edict_t* self)
  236. {
  237. if (!self->enemy)
  238. return;
  239. vec3_t start;
  240. vec3_t dir;
  241. vec3_t forward, right;
  242. vec3_t offset = FindShamblerOffset(self);
  243. AngleVectors(self->s.angles, forward, right, nullptr);
  244. start = M_ProjectFlashSource(self, offset, forward, right);
  245. // calc direction to where we targted
  246. PredictAim(self, self->enemy, start, 0, false, self->spawnflags.has(SPAWNFLAG_SHAMBLER_PRECISE) ? 0.f : 0.1f, &dir, nullptr);
  247. vec3_t end = start + (dir * 8192);
  248. trace_t tr = gi.traceline(start, end, self, MASK_PROJECTILE | CONTENTS_SLIME | CONTENTS_LAVA);
  249. gi.WriteByte(svc_temp_entity);
  250. gi.WriteByte(TE_LIGHTNING);
  251. gi.WriteEntity(self); // source entity
  252. gi.WriteEntity(world); // destination entity
  253. gi.WritePosition(start);
  254. gi.WritePosition(tr.endpos);
  255. gi.multicast(start, MULTICAST_PVS, false);
  256. fire_bullet(self, start, dir, irandom(8, 12), 15, 0, 0, MOD_TESLA);
  257. }
  258. mframe_t shambler_frames_magic[] = {
  259. { ai_charge, 0, shambler_windup },
  260. { ai_charge, 0, shambler_lightning_update },
  261. { ai_charge, 0, shambler_lightning_update },
  262. { ai_move, 0, shambler_lightning_update },
  263. { ai_move, 0, shambler_lightning_update },
  264. { ai_move, 0, ShamblerSaveLoc},
  265. { ai_move },
  266. { ai_charge },
  267. { ai_move, 0, ShamblerCastLightning },
  268. { ai_move, 0, ShamblerCastLightning },
  269. { ai_move, 0, ShamblerCastLightning },
  270. { ai_move },
  271. };
  272. MMOVE_T(shambler_attack_magic) = { FRAME_magic01, FRAME_magic12, shambler_frames_magic, shambler_run };
  273. MONSTERINFO_ATTACK(shambler_attack) (edict_t* self) -> void
  274. {
  275. M_SetAnimation(self, &shambler_attack_magic);
  276. }
  277. //
  278. // melee
  279. //
  280. void shambler_melee1(edict_t* self)
  281. {
  282. gi.sound(self, CHAN_WEAPON, sound_melee1, 1, ATTN_NORM, 0);
  283. }
  284. void shambler_melee2(edict_t* self)
  285. {
  286. gi.sound(self, CHAN_WEAPON, sound_melee2, 1, ATTN_NORM, 0);
  287. }
  288. void sham_swingl9(edict_t* self);
  289. void sham_swingr9(edict_t* self);
  290. void sham_smash10(edict_t* self)
  291. {
  292. if (!self->enemy)
  293. return;
  294. ai_charge(self, 0);
  295. if (!CanDamage(self->enemy, self))
  296. return;
  297. vec3_t aim = { MELEE_DISTANCE, self->mins[0], -4 };
  298. bool hit = fire_hit(self, aim, irandom(110, 120), 120); // Slower attack
  299. if (hit)
  300. gi.sound(self, CHAN_WEAPON, sound_smack, 1, ATTN_NORM, 0);
  301. // SpawnMeatSpray(self.origin + v_forward * 16, crandom() * 100 * v_right);
  302. // SpawnMeatSpray(self.origin + v_forward * 16, crandom() * 100 * v_right);
  303. };
  304. void ShamClaw(edict_t* self)
  305. {
  306. if (!self->enemy)
  307. return;
  308. ai_charge(self, 10);
  309. if (!CanDamage(self->enemy, self))
  310. return;
  311. vec3_t aim = { MELEE_DISTANCE, self->mins[0], -4 };
  312. bool hit = fire_hit(self, aim, irandom(70, 80), 80); // Slower attack
  313. if (hit)
  314. gi.sound(self, CHAN_WEAPON, sound_smack, 1, ATTN_NORM, 0);
  315. // 250 if left, -250 if right
  316. /*
  317. if (side)
  318. {
  319. makevectorsfixed(self.angles);
  320. SpawnMeatSpray(self.origin + v_forward * 16, side * v_right);
  321. }
  322. */
  323. };
  324. mframe_t shambler_frames_smash[] = {
  325. { ai_charge, 2, shambler_melee1 },
  326. { ai_charge, 6 },
  327. { ai_charge, 6 },
  328. { ai_charge, 5 },
  329. { ai_charge, 4 },
  330. { ai_charge, 1 },
  331. { ai_charge, 0 },
  332. { ai_charge, 0 },
  333. { ai_charge, 0 },
  334. { ai_charge, 0, sham_smash10 },
  335. { ai_charge, 5 },
  336. { ai_charge, 4 },
  337. };
  338. MMOVE_T(shambler_attack_smash) = { FRAME_smash01, FRAME_smash12, shambler_frames_smash, shambler_run };
  339. mframe_t shambler_frames_swingl[] = {
  340. { ai_charge, 5, shambler_melee1 },
  341. { ai_charge, 3 },
  342. { ai_charge, 7 },
  343. { ai_charge, 3 },
  344. { ai_charge, 7 },
  345. { ai_charge, 9 },
  346. { ai_charge, 5, ShamClaw },
  347. { ai_charge, 4 },
  348. { ai_charge, 8, sham_swingl9 },
  349. };
  350. MMOVE_T(shambler_attack_swingl) = { FRAME_swingl01, FRAME_swingl09, shambler_frames_swingl, shambler_run };
  351. mframe_t shambler_frames_swingr[] = {
  352. { ai_charge, 1, shambler_melee2 },
  353. { ai_charge, 8 },
  354. { ai_charge, 14 },
  355. { ai_charge, 7 },
  356. { ai_charge, 3 },
  357. { ai_charge, 6 },
  358. { ai_charge, 6, ShamClaw },
  359. { ai_charge, 3 },
  360. { ai_charge, 8, sham_swingr9 },
  361. };
  362. MMOVE_T(shambler_attack_swingr) = { FRAME_swingr01, FRAME_swingr09, shambler_frames_swingr, shambler_run };
  363. void sham_swingl9(edict_t* self)
  364. {
  365. ai_charge(self, 8);
  366. if (brandom() && self->enemy && range_to(self, self->enemy) < MELEE_DISTANCE)
  367. M_SetAnimation(self, &shambler_attack_swingr);
  368. }
  369. void sham_swingr9(edict_t* self)
  370. {
  371. ai_charge(self, 1);
  372. ai_charge(self, 10);
  373. if (brandom() && self->enemy && range_to(self, self->enemy) < MELEE_DISTANCE)
  374. M_SetAnimation(self, &shambler_attack_swingl);
  375. }
  376. MONSTERINFO_MELEE(shambler_melee) (edict_t* self) -> void
  377. {
  378. float chance = frandom();
  379. if (chance > 0.6 || self->health == 600)
  380. M_SetAnimation(self, &shambler_attack_smash);
  381. else if (chance > 0.3)
  382. M_SetAnimation(self, &shambler_attack_swingl);
  383. else
  384. M_SetAnimation(self, &shambler_attack_swingr);
  385. }
  386. //
  387. // death
  388. //
  389. void shambler_dead(edict_t* self)
  390. {
  391. self->mins = { -16, -16, -24 };
  392. self->maxs = { 16, 16, -0 };
  393. monster_dead(self);
  394. }
  395. static void shambler_shrink(edict_t* self)
  396. {
  397. self->maxs[2] = 0;
  398. self->svflags |= SVF_DEADMONSTER;
  399. gi.linkentity(self);
  400. }
  401. mframe_t shambler_frames_death[] = {
  402. { ai_move, 0 },
  403. { ai_move, 0 },
  404. { ai_move, 0, shambler_shrink },
  405. { ai_move, 0 },
  406. { ai_move, 0 },
  407. { ai_move, 0 },
  408. { ai_move, 0 },
  409. { ai_move, 0 },
  410. { ai_move, 0 },
  411. { ai_move, 0 },
  412. { ai_move, 0 }, // FIXME: thud?
  413. };
  414. MMOVE_T(shambler_move_death) = { FRAME_death01, FRAME_death11, shambler_frames_death, shambler_dead };
  415. DIE(shambler_die) (edict_t* self, edict_t* inflictor, edict_t* attacker, int damage, const vec3_t& point, const mod_t &mod) -> void
  416. {
  417. if (self->beam)
  418. {
  419. G_FreeEdict(self->beam);
  420. self->beam = nullptr;
  421. }
  422. if (self->beam2)
  423. {
  424. G_FreeEdict(self->beam2);
  425. self->beam2 = nullptr;
  426. }
  427. // check for gib
  428. if (M_CheckGib(self, mod))
  429. {
  430. gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0);
  431. // FIXME: better gibs for shambler, shambler head
  432. ThrowGibs(self, damage, {
  433. { "models/objects/gibs/sm_meat/tris.md2" },
  434. { "models/objects/gibs/chest/tris.md2" },
  435. { "models/objects/gibs/head2/tris.md2", GIB_HEAD }
  436. });
  437. self->deadflag = true;
  438. return;
  439. }
  440. if (self->deadflag)
  441. return;
  442. // regular death
  443. gi.sound(self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0);
  444. self->deadflag = true;
  445. self->takedamage = true;
  446. M_SetAnimation(self, &shambler_move_death);
  447. }
  448. void SP_monster_shambler(edict_t* self)
  449. {
  450. if ( !M_AllowSpawn( self ) ) {
  451. G_FreeEdict( self );
  452. return;
  453. }
  454. self->s.modelindex = gi.modelindex("models/monsters/shambler/tris.md2");
  455. self->mins = { -32, -32, -24 };
  456. self->maxs = { 32, 32, 64 };
  457. self->movetype = MOVETYPE_STEP;
  458. self->solid = SOLID_BBOX;
  459. gi.modelindex("models/proj/lightning/tris.md2");
  460. sound_pain.assign("shambler/shurt2.wav");
  461. sound_idle.assign("shambler/sidle.wav");
  462. sound_die.assign("shambler/sdeath.wav");
  463. sound_windup.assign("shambler/sattck1.wav");
  464. sound_melee1.assign("shambler/melee1.wav");
  465. sound_melee2.assign("shambler/melee2.wav");
  466. sound_sight.assign("shambler/ssight.wav");
  467. sound_smack.assign("shambler/smack.wav");
  468. sound_boom.assign("shambler/sboom.wav");
  469. self->health = 600 * st.health_multiplier;
  470. self->gib_health = -60;
  471. self->mass = 500;
  472. self->pain = shambler_pain;
  473. self->die = shambler_die;
  474. self->monsterinfo.stand = shambler_stand;
  475. self->monsterinfo.walk = shambler_walk;
  476. self->monsterinfo.run = shambler_run;
  477. self->monsterinfo.dodge = nullptr;
  478. self->monsterinfo.attack = shambler_attack;
  479. self->monsterinfo.melee = shambler_melee;
  480. self->monsterinfo.sight = shambler_sight;
  481. self->monsterinfo.idle = shambler_idle;
  482. self->monsterinfo.blocked = nullptr;
  483. self->monsterinfo.setskin = shambler_setskin;
  484. gi.linkentity(self);
  485. if (self->spawnflags.has(SPAWNFLAG_SHAMBLER_PRECISE))
  486. self->monsterinfo.aiflags |= AI_IGNORE_SHOTS;
  487. M_SetAnimation(self, &shambler_move_stand);
  488. self->monsterinfo.scale = MODEL_SCALE;
  489. walkmonster_start(self);
  490. }