m_rogue_stalker.cpp 25 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042
  1. // Copyright (c) ZeniMax Media Inc.
  2. // Licensed under the GNU General Public License 2.0.
  3. /*
  4. ==============================================================================
  5. stalker
  6. ==============================================================================
  7. */
  8. #include "../g_local.h"
  9. #include "m_rogue_stalker.h"
  10. #include <float.h>
  11. static cached_soundindex sound_pain;
  12. static cached_soundindex sound_die;
  13. static cached_soundindex sound_sight;
  14. static cached_soundindex sound_punch_hit1;
  15. static cached_soundindex sound_punch_hit2;
  16. static cached_soundindex sound_idle;
  17. bool stalker_do_pounce(edict_t *self, const vec3_t &dest);
  18. void stalker_walk(edict_t *self);
  19. void stalker_dodge_jump(edict_t *self);
  20. void stalker_swing_attack(edict_t *self);
  21. void stalker_jump_straightup(edict_t *self);
  22. void stalker_jump_wait_land(edict_t *self);
  23. void stalker_false_death(edict_t *self);
  24. void stalker_false_death_start(edict_t *self);
  25. bool stalker_ok_to_transition(edict_t *self);
  26. void stalker_stand(edict_t *self);
  27. inline bool STALKER_ON_CEILING(edict_t *ent)
  28. {
  29. return (ent->gravityVector[2] > 0);
  30. }
  31. //=========================
  32. //=========================
  33. bool stalker_ok_to_transition(edict_t *self)
  34. {
  35. trace_t trace;
  36. vec3_t pt, start;
  37. float max_dist;
  38. float margin;
  39. float end_height;
  40. if (STALKER_ON_CEILING(self))
  41. {
  42. // [Paril-KEX] if we get knocked off the ceiling, always
  43. // fall downwards
  44. if (!self->groundentity)
  45. return true;
  46. max_dist = -384;
  47. margin = self->mins[2] - 8;
  48. }
  49. else
  50. {
  51. // her stalkers are just better
  52. if (self->monsterinfo.aiflags & AI_SPAWNED_WIDOW)
  53. max_dist = 256;
  54. else
  55. max_dist = 180;
  56. margin = self->maxs[2] + 8;
  57. }
  58. pt = self->s.origin;
  59. pt[2] += max_dist;
  60. trace = gi.trace(self->s.origin, self->mins, self->maxs, pt, self, MASK_MONSTERSOLID);
  61. if (trace.fraction == 1.0f ||
  62. !(trace.contents & CONTENTS_SOLID) ||
  63. (trace.ent != world))
  64. {
  65. if (STALKER_ON_CEILING(self))
  66. {
  67. if (trace.plane.normal[2] < 0.9f)
  68. return false;
  69. }
  70. else
  71. {
  72. if (trace.plane.normal[2] > -0.9f)
  73. return false;
  74. }
  75. }
  76. end_height = trace.endpos[2];
  77. // check the four corners, tracing only to the endpoint of the center trace (vertically).
  78. pt[0] = self->absmin[0];
  79. pt[1] = self->absmin[1];
  80. pt[2] = trace.endpos[2] + margin; // give a little margin of error to allow slight inclines
  81. start = pt;
  82. start[2] = self->s.origin[2];
  83. trace = gi.traceline(start, pt, self, MASK_MONSTERSOLID);
  84. if (trace.fraction == 1.0f || !(trace.contents & CONTENTS_SOLID) || (trace.ent != world))
  85. return false;
  86. if (fabsf(end_height + margin - trace.endpos[2]) > 8)
  87. return false;
  88. pt[0] = self->absmax[0];
  89. pt[1] = self->absmin[1];
  90. start = pt;
  91. start[2] = self->s.origin[2];
  92. trace = gi.traceline(start, pt, self, MASK_MONSTERSOLID);
  93. if (trace.fraction == 1.0f || !(trace.contents & CONTENTS_SOLID) || (trace.ent != world))
  94. return false;
  95. if (fabsf(end_height + margin - trace.endpos[2]) > 8)
  96. return false;
  97. pt[0] = self->absmax[0];
  98. pt[1] = self->absmax[1];
  99. start = pt;
  100. start[2] = self->s.origin[2];
  101. trace = gi.traceline(start, pt, self, MASK_MONSTERSOLID);
  102. if (trace.fraction == 1.0f || !(trace.contents & CONTENTS_SOLID) || (trace.ent != world))
  103. return false;
  104. if (fabsf(end_height + margin - trace.endpos[2]) > 8)
  105. return false;
  106. pt[0] = self->absmin[0];
  107. pt[1] = self->absmax[1];
  108. start = pt;
  109. start[2] = self->s.origin[2];
  110. trace = gi.traceline(start, pt, self, MASK_MONSTERSOLID);
  111. if (trace.fraction == 1.0f || !(trace.contents & CONTENTS_SOLID) || (trace.ent != world))
  112. return false;
  113. if (fabsf(end_height + margin - trace.endpos[2]) > 8)
  114. return false;
  115. return true;
  116. }
  117. //=========================
  118. //=========================
  119. MONSTERINFO_SIGHT(stalker_sight) (edict_t *self, edict_t *other) -> void
  120. {
  121. gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
  122. }
  123. // ******************
  124. // IDLE
  125. // ******************
  126. void stalker_idle_noise(edict_t *self)
  127. {
  128. gi.sound(self, CHAN_VOICE, sound_idle, 0.5, ATTN_IDLE, 0);
  129. }
  130. mframe_t stalker_frames_idle[] = {
  131. { ai_stand },
  132. { ai_stand },
  133. { ai_stand },
  134. { ai_stand },
  135. { ai_stand },
  136. { ai_stand },
  137. { ai_stand, 0, stalker_idle_noise },
  138. { ai_stand },
  139. { ai_stand },
  140. { ai_stand },
  141. { ai_stand },
  142. { ai_stand },
  143. { ai_stand },
  144. { ai_stand },
  145. { ai_stand },
  146. { ai_stand },
  147. { ai_stand },
  148. { ai_stand },
  149. { ai_stand },
  150. { ai_stand },
  151. { ai_stand }
  152. };
  153. MMOVE_T(stalker_move_idle) = { FRAME_idle01, FRAME_idle21, stalker_frames_idle, stalker_stand };
  154. mframe_t stalker_frames_idle2[] = {
  155. { ai_stand },
  156. { ai_stand },
  157. { ai_stand },
  158. { ai_stand },
  159. { ai_stand },
  160. { ai_stand },
  161. { ai_stand },
  162. { ai_stand },
  163. { ai_stand },
  164. { ai_stand },
  165. { ai_stand },
  166. { ai_stand },
  167. { ai_stand }
  168. };
  169. MMOVE_T(stalker_move_idle2) = { FRAME_idle201, FRAME_idle213, stalker_frames_idle2, stalker_stand };
  170. MONSTERINFO_IDLE(stalker_idle) (edict_t *self) -> void
  171. {
  172. if (frandom() < 0.35f)
  173. M_SetAnimation(self, &stalker_move_idle);
  174. else
  175. M_SetAnimation(self, &stalker_move_idle2);
  176. }
  177. // ******************
  178. // STAND
  179. // ******************
  180. mframe_t stalker_frames_stand[] = {
  181. { ai_stand },
  182. { ai_stand },
  183. { ai_stand },
  184. { ai_stand },
  185. { ai_stand },
  186. { ai_stand },
  187. { ai_stand, 0, stalker_idle_noise },
  188. { ai_stand },
  189. { ai_stand },
  190. { ai_stand },
  191. { ai_stand },
  192. { ai_stand },
  193. { ai_stand },
  194. { ai_stand },
  195. { ai_stand },
  196. { ai_stand },
  197. { ai_stand },
  198. { ai_stand },
  199. { ai_stand },
  200. { ai_stand },
  201. { ai_stand }
  202. };
  203. MMOVE_T(stalker_move_stand) = { FRAME_idle01, FRAME_idle21, stalker_frames_stand, stalker_stand };
  204. MONSTERINFO_STAND(stalker_stand) (edict_t *self) -> void
  205. {
  206. if (frandom() < 0.25f)
  207. M_SetAnimation(self, &stalker_move_stand);
  208. else
  209. M_SetAnimation(self, &stalker_move_idle2);
  210. }
  211. // ******************
  212. // RUN
  213. // ******************
  214. mframe_t stalker_frames_run[] = {
  215. { ai_run, 13, monster_footstep },
  216. { ai_run, 17 },
  217. { ai_run, 21, monster_footstep },
  218. { ai_run, 18 }
  219. };
  220. MMOVE_T(stalker_move_run) = { FRAME_run01, FRAME_run04, stalker_frames_run, nullptr };
  221. MONSTERINFO_RUN(stalker_run) (edict_t *self) -> void
  222. {
  223. if (self->monsterinfo.aiflags & AI_STAND_GROUND)
  224. M_SetAnimation(self, &stalker_move_stand);
  225. else
  226. M_SetAnimation(self, &stalker_move_run);
  227. }
  228. // ******************
  229. // WALK
  230. // ******************
  231. mframe_t stalker_frames_walk[] = {
  232. { ai_walk, 4, monster_footstep },
  233. { ai_walk, 6 },
  234. { ai_walk, 8 },
  235. { ai_walk, 5 },
  236. { ai_walk, 4, monster_footstep },
  237. { ai_walk, 6 },
  238. { ai_walk, 8 },
  239. { ai_walk, 4 }
  240. };
  241. MMOVE_T(stalker_move_walk) = { FRAME_walk01, FRAME_walk08, stalker_frames_walk, stalker_walk };
  242. MONSTERINFO_WALK(stalker_walk) (edict_t *self) -> void
  243. {
  244. M_SetAnimation(self, &stalker_move_walk);
  245. }
  246. // ******************
  247. // false death
  248. // ******************
  249. mframe_t stalker_frames_reactivate[] = {
  250. { ai_move },
  251. { ai_move },
  252. { ai_move },
  253. { ai_move, 0, monster_footstep }
  254. };
  255. MMOVE_T(stalker_move_false_death_end) = { FRAME_reactive01, FRAME_reactive04, stalker_frames_reactivate, stalker_run };
  256. void stalker_reactivate(edict_t *self)
  257. {
  258. self->monsterinfo.aiflags &= ~AI_STAND_GROUND;
  259. M_SetAnimation(self, &stalker_move_false_death_end);
  260. }
  261. void stalker_heal(edict_t *self)
  262. {
  263. if (skill->integer == 2)
  264. self->health += 2;
  265. else if (skill->integer == 3)
  266. self->health += 3;
  267. else
  268. self->health++;
  269. self->monsterinfo.setskin(self);
  270. if (self->health >= self->max_health)
  271. {
  272. self->health = self->max_health;
  273. stalker_reactivate(self);
  274. }
  275. }
  276. mframe_t stalker_frames_false_death[] = {
  277. { ai_move, 0, stalker_heal },
  278. { ai_move, 0, stalker_heal },
  279. { ai_move, 0, stalker_heal },
  280. { ai_move, 0, stalker_heal },
  281. { ai_move, 0, stalker_heal },
  282. { ai_move, 0, stalker_heal },
  283. { ai_move, 0, stalker_heal },
  284. { ai_move, 0, stalker_heal },
  285. { ai_move, 0, stalker_heal },
  286. { ai_move, 0, stalker_heal }
  287. };
  288. MMOVE_T(stalker_move_false_death) = { FRAME_twitch01, FRAME_twitch10, stalker_frames_false_death, stalker_false_death };
  289. void stalker_false_death(edict_t *self)
  290. {
  291. M_SetAnimation(self, &stalker_move_false_death);
  292. }
  293. mframe_t stalker_frames_false_death_start[] = {
  294. { ai_move },
  295. { ai_move },
  296. { ai_move },
  297. { ai_move },
  298. { ai_move },
  299. { ai_move },
  300. { ai_move },
  301. { ai_move },
  302. { ai_move },
  303. };
  304. MMOVE_T(stalker_move_false_death_start) = { FRAME_death01, FRAME_death09, stalker_frames_false_death_start, stalker_false_death };
  305. void stalker_false_death_start(edict_t *self)
  306. {
  307. self->s.angles[2] = 0;
  308. self->gravityVector = { 0, 0, -1 };
  309. self->monsterinfo.aiflags |= AI_STAND_GROUND;
  310. M_SetAnimation(self, &stalker_move_false_death_start);
  311. }
  312. // ******************
  313. // PAIN
  314. // ******************
  315. mframe_t stalker_frames_pain[] = {
  316. { ai_move },
  317. { ai_move },
  318. { ai_move },
  319. { ai_move }
  320. };
  321. MMOVE_T(stalker_move_pain) = { FRAME_pain01, FRAME_pain04, stalker_frames_pain, stalker_run };
  322. PAIN(stalker_pain) (edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod) -> void
  323. {
  324. if (self->deadflag)
  325. return;
  326. if (self->groundentity == nullptr)
  327. return;
  328. // if we're reactivating or false dying, ignore the pain.
  329. if (self->monsterinfo.active_move == &stalker_move_false_death_end ||
  330. self->monsterinfo.active_move == &stalker_move_false_death_start)
  331. return;
  332. if (self->monsterinfo.active_move == &stalker_move_false_death)
  333. {
  334. stalker_reactivate(self);
  335. return;
  336. }
  337. if ((self->health > 0) && (self->health < (self->max_health / 4)))
  338. {
  339. if (frandom() < 0.30f)
  340. {
  341. if (!STALKER_ON_CEILING(self) || stalker_ok_to_transition(self))
  342. {
  343. stalker_false_death_start(self);
  344. return;
  345. }
  346. }
  347. }
  348. if (level.time < self->pain_debounce_time)
  349. return;
  350. self->pain_debounce_time = level.time + 3_sec;
  351. gi.sound(self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0);
  352. if (mod.id == MOD_CHAINFIST || damage > 10) // don't react unless the damage was significant
  353. {
  354. // stalker should dodge jump periodically to help avoid damage.
  355. if (self->groundentity && (frandom() < 0.5f))
  356. stalker_dodge_jump(self);
  357. else if (M_ShouldReactToPain(self, mod)) // no pain anims in nightmare
  358. M_SetAnimation(self, &stalker_move_pain);
  359. }
  360. }
  361. MONSTERINFO_SETSKIN(stalker_setskin) (edict_t *self) -> void
  362. {
  363. if (self->health < (self->max_health / 2))
  364. self->s.skinnum = 1;
  365. else
  366. self->s.skinnum = 0;
  367. }
  368. // ******************
  369. // STALKER ATTACK
  370. // ******************
  371. void stalker_shoot_attack(edict_t *self)
  372. {
  373. vec3_t offset, start, f, r, dir;
  374. vec3_t end;
  375. float dist;
  376. trace_t trace;
  377. if (!has_valid_enemy(self))
  378. return;
  379. if (self->groundentity && frandom() < 0.33f)
  380. {
  381. dir = self->enemy->s.origin - self->s.origin;
  382. dist = dir.length();
  383. if ((dist > 256) || (frandom() < 0.5f))
  384. stalker_do_pounce(self, self->enemy->s.origin);
  385. else
  386. stalker_jump_straightup(self);
  387. }
  388. AngleVectors(self->s.angles, f, r, nullptr);
  389. offset = { 24, 0, 6 };
  390. start = M_ProjectFlashSource(self, offset, f, r);
  391. dir = self->enemy->s.origin - start;
  392. if (frandom() < 0.3f)
  393. PredictAim(self, self->enemy, start, 1000, true, 0, &dir, &end);
  394. else
  395. end = self->enemy->s.origin;
  396. trace = gi.traceline(start, end, self, MASK_PROJECTILE);
  397. if (trace.ent == self->enemy || trace.ent == world)
  398. {
  399. dir.normalize();
  400. monster_fire_blaster2(self, start, dir, 5, 800, MZ2_STALKER_BLASTER, EF_BLASTER);
  401. }
  402. }
  403. void stalker_shoot_attack2(edict_t *self)
  404. {
  405. if (frandom() < 0.5)
  406. stalker_shoot_attack(self);
  407. }
  408. mframe_t stalker_frames_shoot[] = {
  409. { ai_charge, 13 },
  410. { ai_charge, 17, stalker_shoot_attack },
  411. { ai_charge, 21 },
  412. { ai_charge, 18, stalker_shoot_attack2 }
  413. };
  414. MMOVE_T(stalker_move_shoot) = { FRAME_run01, FRAME_run04, stalker_frames_shoot, stalker_run };
  415. MONSTERINFO_ATTACK(stalker_attack_ranged) (edict_t *self) -> void
  416. {
  417. if (!has_valid_enemy(self))
  418. return;
  419. // PMM - circle strafe stuff
  420. if (frandom() > 0.5f)
  421. {
  422. self->monsterinfo.attack_state = AS_STRAIGHT;
  423. }
  424. else
  425. {
  426. if (frandom() <= 0.5f) // switch directions
  427. self->monsterinfo.lefty = !self->monsterinfo.lefty;
  428. self->monsterinfo.attack_state = AS_SLIDING;
  429. }
  430. M_SetAnimation(self, &stalker_move_shoot);
  431. }
  432. // ******************
  433. // close combat
  434. // ******************
  435. void stalker_swing_attack(edict_t *self)
  436. {
  437. vec3_t aim = { MELEE_DISTANCE, 0, 0 };
  438. if (fire_hit(self, aim, irandom(5, 10), 50))
  439. {
  440. if (self->s.frame < FRAME_attack08)
  441. gi.sound(self, CHAN_WEAPON, sound_punch_hit2, 1, ATTN_NORM, 0);
  442. else
  443. gi.sound(self, CHAN_WEAPON, sound_punch_hit1, 1, ATTN_NORM, 0);
  444. }
  445. else
  446. self->monsterinfo.melee_debounce_time = level.time + 0.8_sec;
  447. }
  448. mframe_t stalker_frames_swing_l[] = {
  449. { ai_charge, 2 },
  450. { ai_charge, 4 },
  451. { ai_charge, 6 },
  452. { ai_charge, 10, monster_footstep },
  453. { ai_charge, 5, stalker_swing_attack },
  454. { ai_charge, 5 },
  455. { ai_charge, 5 },
  456. { ai_charge, 5, monster_footstep } // stalker_swing_check_l
  457. };
  458. MMOVE_T(stalker_move_swing_l) = { FRAME_attack01, FRAME_attack08, stalker_frames_swing_l, stalker_run };
  459. mframe_t stalker_frames_swing_r[] = {
  460. { ai_charge, 4 },
  461. { ai_charge, 6, monster_footstep },
  462. { ai_charge, 6, stalker_swing_attack },
  463. { ai_charge, 10 },
  464. { ai_charge, 5, monster_footstep } // stalker_swing_check_r
  465. };
  466. MMOVE_T(stalker_move_swing_r) = { FRAME_attack11, FRAME_attack15, stalker_frames_swing_r, stalker_run };
  467. MONSTERINFO_MELEE(stalker_attack_melee) (edict_t *self) -> void
  468. {
  469. if (!has_valid_enemy(self))
  470. return;
  471. if (frandom() < 0.5f)
  472. M_SetAnimation(self, &stalker_move_swing_l);
  473. else
  474. M_SetAnimation(self, &stalker_move_swing_r);
  475. }
  476. // ******************
  477. // POUNCE
  478. // ******************
  479. // ====================
  480. // ====================
  481. bool stalker_check_lz(edict_t *self, edict_t *target, const vec3_t &dest)
  482. {
  483. if ((gi.pointcontents(dest) & MASK_WATER) || (target->waterlevel))
  484. return false;
  485. if (!target->groundentity)
  486. return false;
  487. vec3_t jumpLZ;
  488. // check under the player's four corners
  489. // if they're not solid, bail.
  490. jumpLZ[0] = self->enemy->mins[0];
  491. jumpLZ[1] = self->enemy->mins[1];
  492. jumpLZ[2] = self->enemy->mins[2] - 0.25f;
  493. if (!(gi.pointcontents(jumpLZ) & MASK_SOLID))
  494. return false;
  495. jumpLZ[0] = self->enemy->maxs[0];
  496. jumpLZ[1] = self->enemy->mins[1];
  497. if (!(gi.pointcontents(jumpLZ) & MASK_SOLID))
  498. return false;
  499. jumpLZ[0] = self->enemy->maxs[0];
  500. jumpLZ[1] = self->enemy->maxs[1];
  501. if (!(gi.pointcontents(jumpLZ) & MASK_SOLID))
  502. return false;
  503. jumpLZ[0] = self->enemy->mins[0];
  504. jumpLZ[1] = self->enemy->maxs[1];
  505. if (!(gi.pointcontents(jumpLZ) & MASK_SOLID))
  506. return false;
  507. return true;
  508. }
  509. // ====================
  510. // ====================
  511. bool stalker_do_pounce(edict_t *self, const vec3_t &dest)
  512. {
  513. vec3_t dist;
  514. float length;
  515. vec3_t jumpAngles;
  516. vec3_t jumpLZ;
  517. float velocity = 400.1f;
  518. // don't pounce when we're on the ceiling
  519. if (STALKER_ON_CEILING(self))
  520. return false;
  521. if (!stalker_check_lz(self, self->enemy, dest))
  522. return false;
  523. dist = dest - self->s.origin;
  524. // make sure we're pointing in that direction 15deg margin of error.
  525. jumpAngles = vectoangles(dist);
  526. if (fabsf(jumpAngles[YAW] - self->s.angles[YAW]) > 45)
  527. return false; // not facing the player...
  528. if (isnan(jumpAngles[YAW]))
  529. return false; // Switch why
  530. self->ideal_yaw = jumpAngles[YAW];
  531. M_ChangeYaw(self);
  532. length = dist.length();
  533. if (length > 450)
  534. return false; // can't jump that far...
  535. jumpLZ = dest;
  536. vec3_t dir = dist.normalized();
  537. // find a valid angle/velocity combination
  538. while (velocity <= 800)
  539. {
  540. if (M_CalculatePitchToFire(self, jumpLZ, self->s.origin, dir, velocity, 3, false, true))
  541. break;
  542. velocity += 200;
  543. }
  544. // nothing found
  545. if (velocity > 800)
  546. return false;
  547. self->velocity = dir * velocity;
  548. return true;
  549. }
  550. // ******************
  551. // DODGE
  552. // ******************
  553. //===================
  554. // stalker_jump_straightup
  555. //===================
  556. void stalker_jump_straightup(edict_t *self)
  557. {
  558. if (self->deadflag)
  559. return;
  560. if (STALKER_ON_CEILING(self))
  561. {
  562. if (stalker_ok_to_transition(self))
  563. {
  564. self->gravityVector[2] = -1;
  565. self->s.angles[2] += 180.0f;
  566. if (self->s.angles[2] > 360.0f)
  567. self->s.angles[2] -= 360.0f;
  568. self->groundentity = nullptr;
  569. }
  570. }
  571. else if (self->groundentity) // make sure we're standing on SOMETHING...
  572. {
  573. self->velocity[0] += crandom() * 5;
  574. self->velocity[1] += crandom() * 5;
  575. self->velocity[2] += -400 * self->gravityVector[2];
  576. if (stalker_ok_to_transition(self))
  577. {
  578. self->gravityVector[2] = 1;
  579. self->s.angles[2] = 180.0;
  580. self->groundentity = nullptr;
  581. }
  582. }
  583. }
  584. mframe_t stalker_frames_jump_straightup[] = {
  585. { ai_move, 1, stalker_jump_straightup },
  586. { ai_move, 1, stalker_jump_wait_land },
  587. { ai_move, -1, monster_footstep },
  588. { ai_move, -1 }
  589. };
  590. MMOVE_T(stalker_move_jump_straightup) = { FRAME_jump04, FRAME_jump07, stalker_frames_jump_straightup, stalker_run };
  591. //===================
  592. // stalker_dodge_jump - abstraction so pain function can trigger a dodge jump too without
  593. // faking the inputs to stalker_dodge
  594. //===================
  595. void stalker_dodge_jump(edict_t *self)
  596. {
  597. M_SetAnimation(self, &stalker_move_jump_straightup);
  598. }
  599. #if 0
  600. mframe_t stalker_frames_dodge_run[] = {
  601. { ai_run, 13 },
  602. { ai_run, 17 },
  603. { ai_run, 21 },
  604. { ai_run, 18, monster_done_dodge }
  605. };
  606. MMOVE_T(stalker_move_dodge_run) = { FRAME_run01, FRAME_run04, stalker_frames_dodge_run, nullptr };
  607. #endif
  608. MONSTERINFO_DODGE(stalker_dodge) (edict_t *self, edict_t *attacker, gtime_t eta, trace_t *tr, bool gravity) -> void
  609. {
  610. if (!self->groundentity || self->health <= 0)
  611. return;
  612. if (!self->enemy)
  613. {
  614. self->enemy = attacker;
  615. FoundTarget(self);
  616. return;
  617. }
  618. // PMM - don't bother if it's going to hit anyway; fix for weird in-your-face etas (I was
  619. // seeing numbers like 13 and 14)
  620. if ((eta < FRAME_TIME_MS) || (eta > 5_sec))
  621. return;
  622. if (self->timestamp > level.time)
  623. return;
  624. self->timestamp = level.time + random_time(1_sec, 5_sec);
  625. // this will override the foundtarget call of stalker_run
  626. stalker_dodge_jump(self);
  627. }
  628. // ******************
  629. // Jump onto / off of things
  630. // ******************
  631. //===================
  632. //===================
  633. void stalker_jump_down(edict_t *self)
  634. {
  635. vec3_t forward, up;
  636. AngleVectors(self->s.angles, forward, nullptr, up);
  637. self->velocity += (forward * 100);
  638. self->velocity += (up * 300);
  639. }
  640. //===================
  641. //===================
  642. void stalker_jump_up(edict_t *self)
  643. {
  644. vec3_t forward, up;
  645. AngleVectors(self->s.angles, forward, nullptr, up);
  646. self->velocity += (forward * 200);
  647. self->velocity += (up * 450);
  648. }
  649. //===================
  650. //===================
  651. void stalker_jump_wait_land(edict_t *self)
  652. {
  653. if ((frandom() < 0.4f) && (level.time >= self->monsterinfo.attack_finished))
  654. {
  655. self->monsterinfo.attack_finished = level.time + 300_ms;
  656. stalker_shoot_attack(self);
  657. }
  658. if (self->groundentity == nullptr)
  659. {
  660. self->gravity = 1.3f;
  661. self->monsterinfo.nextframe = self->s.frame;
  662. if (monster_jump_finished(self))
  663. {
  664. self->gravity = 1;
  665. self->monsterinfo.nextframe = self->s.frame + 1;
  666. }
  667. }
  668. else
  669. {
  670. self->gravity = 1;
  671. self->monsterinfo.nextframe = self->s.frame + 1;
  672. }
  673. }
  674. mframe_t stalker_frames_jump_up[] = {
  675. { ai_move, -8 },
  676. { ai_move, -8 },
  677. { ai_move, -8 },
  678. { ai_move, -8 },
  679. { ai_move, 0, stalker_jump_up },
  680. { ai_move, 0, stalker_jump_wait_land },
  681. { ai_move, 0, monster_footstep }
  682. };
  683. MMOVE_T(stalker_move_jump_up) = { FRAME_jump01, FRAME_jump07, stalker_frames_jump_up, stalker_run };
  684. mframe_t stalker_frames_jump_down[] = {
  685. { ai_move },
  686. { ai_move },
  687. { ai_move },
  688. { ai_move },
  689. { ai_move, 0, stalker_jump_down },
  690. { ai_move, 0, stalker_jump_wait_land },
  691. { ai_move, 0, monster_footstep }
  692. };
  693. MMOVE_T(stalker_move_jump_down) = { FRAME_jump01, FRAME_jump07, stalker_frames_jump_down, stalker_run };
  694. //============
  695. // stalker_jump - this is only used for jumping onto or off of things. for dodge jumping,
  696. // use stalker_dodge_jump
  697. //============
  698. void stalker_jump(edict_t *self, blocked_jump_result_t result)
  699. {
  700. if (!self->enemy)
  701. return;
  702. if (result == blocked_jump_result_t::JUMP_JUMP_UP)
  703. M_SetAnimation(self, &stalker_move_jump_up);
  704. else
  705. M_SetAnimation(self, &stalker_move_jump_down);
  706. }
  707. // ******************
  708. // Blocked
  709. // ******************
  710. MONSTERINFO_BLOCKED(stalker_blocked) (edict_t *self, float dist) -> bool
  711. {
  712. if (!has_valid_enemy(self))
  713. return false;
  714. bool onCeiling = STALKER_ON_CEILING(self);
  715. if (!onCeiling)
  716. {
  717. if (auto result = blocked_checkjump(self, dist); result != blocked_jump_result_t::NO_JUMP)
  718. {
  719. if (result != blocked_jump_result_t::JUMP_TURN)
  720. stalker_jump(self, result);
  721. return true;
  722. }
  723. if (blocked_checkplat(self, dist))
  724. return true;
  725. if (visible(self, self->enemy) && frandom() < 0.1f)
  726. {
  727. stalker_do_pounce(self, self->enemy->s.origin);
  728. return true;
  729. }
  730. }
  731. else
  732. {
  733. if (stalker_ok_to_transition(self))
  734. {
  735. self->gravityVector[2] = -1;
  736. self->s.angles[2] += 180.0f;
  737. if (self->s.angles[2] > 360.0f)
  738. self->s.angles[2] -= 360.0f;
  739. self->groundentity = nullptr;
  740. return true;
  741. }
  742. }
  743. return false;
  744. }
  745. // [Paril-KEX] quick patch-job to fix stalkers endlessly floating up into the sky
  746. MONSTERINFO_PHYSCHANGED(stalker_physics_change) (edict_t *self) -> void
  747. {
  748. if (STALKER_ON_CEILING(self) && !self->groundentity)
  749. {
  750. self->gravityVector[2] = -1;
  751. self->s.angles[2] += 180.0f;
  752. if (self->s.angles[2] > 360.0f)
  753. self->s.angles[2] -= 360.0f;
  754. }
  755. }
  756. // ******************
  757. // Death
  758. // ******************
  759. void stalker_dead(edict_t *self)
  760. {
  761. self->mins = { -28, -28, -18 };
  762. self->maxs = { 28, 28, -4 };
  763. monster_dead(self);
  764. }
  765. mframe_t stalker_frames_death[] = {
  766. { ai_move },
  767. { ai_move, -5 },
  768. { ai_move, -10 },
  769. { ai_move, -20 },
  770. { ai_move, -10 },
  771. { ai_move, -10 },
  772. { ai_move, -5 },
  773. { ai_move, -5 },
  774. { ai_move, 0, monster_footstep }
  775. };
  776. MMOVE_T(stalker_move_death) = { FRAME_death01, FRAME_death09, stalker_frames_death, stalker_dead };
  777. DIE(stalker_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void
  778. {
  779. // dude bit it, make him fall!
  780. self->movetype = MOVETYPE_TOSS;
  781. self->s.angles[2] = 0;
  782. self->gravityVector = { 0, 0, -1 };
  783. // check for gib
  784. if (M_CheckGib(self, mod))
  785. {
  786. gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0);
  787. self->s.skinnum /= 2;
  788. ThrowGibs(self, damage, {
  789. { 2, "models/objects/gibs/sm_meat/tris.md2" },
  790. { 2, "models/objects/gibs/sm_metal/tris.md2", GIB_METALLIC },
  791. { "models/monsters/stalker/gibs/bodya.md2", GIB_SKINNED },
  792. { "models/monsters/stalker/gibs/bodyb.md2", GIB_SKINNED },
  793. { 2, "models/monsters/stalker/gibs/claw.md2", GIB_SKINNED | GIB_UPRIGHT },
  794. { 2, "models/monsters/stalker/gibs/leg.md2", GIB_SKINNED | GIB_UPRIGHT },
  795. { 2, "models/monsters/stalker/gibs/foot.md2", GIB_SKINNED },
  796. { "models/monsters/stalker/gibs/head.md2", GIB_SKINNED | GIB_HEAD }
  797. });
  798. self->deadflag = true;
  799. return;
  800. }
  801. if (self->deadflag)
  802. return;
  803. // regular death
  804. gi.sound(self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0);
  805. self->deadflag = true;
  806. self->takedamage = true;
  807. M_SetAnimation(self, &stalker_move_death);
  808. }
  809. // ******************
  810. // SPAWN
  811. // ******************
  812. /*QUAKED monster_stalker (1 .5 0) (-28 -28 -18) (28 28 18) Ambush Trigger_Spawn Sight OnRoof NoJumping
  813. Spider Monster
  814. ONROOF - Monster starts sticking to the roof.
  815. */
  816. constexpr spawnflags_t SPAWNFLAG_STALKER_ONROOF = 8_spawnflag;
  817. constexpr spawnflags_t SPAWNFLAG_STALKER_NOJUMPING = 16_spawnflag;
  818. void SP_monster_stalker(edict_t *self)
  819. {
  820. if ( !M_AllowSpawn( self ) ) {
  821. G_FreeEdict( self );
  822. return;
  823. }
  824. sound_pain.assign("stalker/pain.wav");
  825. sound_die.assign("stalker/death.wav");
  826. sound_sight.assign("stalker/sight.wav");
  827. sound_punch_hit1.assign("stalker/melee1.wav");
  828. sound_punch_hit2.assign("stalker/melee2.wav");
  829. sound_idle.assign("stalker/idle.wav");
  830. // PMM - precache bolt2
  831. gi.modelindex("models/objects/laser/tris.md2");
  832. self->s.modelindex = gi.modelindex("models/monsters/stalker/tris.md2");
  833. gi.modelindex("models/monsters/stalker/gibs/bodya.md2");
  834. gi.modelindex("models/monsters/stalker/gibs/bodyb.md2");
  835. gi.modelindex("models/monsters/stalker/gibs/claw.md2");
  836. gi.modelindex("models/monsters/stalker/gibs/foot.md2");
  837. gi.modelindex("models/monsters/stalker/gibs/head.md2");
  838. gi.modelindex("models/monsters/stalker/gibs/leg.md2");
  839. self->mins = { -28, -28, -18 };
  840. self->maxs = { 28, 28, 18 };
  841. self->movetype = MOVETYPE_STEP;
  842. self->solid = SOLID_BBOX;
  843. self->health = 250 * st.health_multiplier;
  844. self->gib_health = -50;
  845. self->mass = 250;
  846. self->pain = stalker_pain;
  847. self->die = stalker_die;
  848. self->monsterinfo.stand = stalker_stand;
  849. self->monsterinfo.walk = stalker_walk;
  850. self->monsterinfo.run = stalker_run;
  851. self->monsterinfo.attack = stalker_attack_ranged;
  852. self->monsterinfo.sight = stalker_sight;
  853. self->monsterinfo.idle = stalker_idle;
  854. self->monsterinfo.dodge = stalker_dodge;
  855. self->monsterinfo.blocked = stalker_blocked;
  856. self->monsterinfo.melee = stalker_attack_melee;
  857. self->monsterinfo.setskin = stalker_setskin;
  858. self->monsterinfo.physics_change = stalker_physics_change;
  859. gi.linkentity(self);
  860. M_SetAnimation(self, &stalker_move_stand);
  861. self->monsterinfo.scale = MODEL_SCALE;
  862. if (self->spawnflags.has(SPAWNFLAG_STALKER_ONROOF))
  863. {
  864. self->s.angles[2] = 180;
  865. self->gravityVector[2] = 1;
  866. }
  867. self->monsterinfo.can_jump = !self->spawnflags.has(SPAWNFLAG_STALKER_NOJUMPING);
  868. self->monsterinfo.drop_height = 256;
  869. self->monsterinfo.jump_height = 68;
  870. walkmonster_start(self);
  871. }