m_berserk.cpp 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837
  1. // Copyright (c) ZeniMax Media Inc.
  2. // Licensed under the GNU General Public License 2.0.
  3. /*
  4. ==============================================================================
  5. BERSERK
  6. ==============================================================================
  7. */
  8. #include "g_local.h"
  9. #include "m_berserk.h"
  10. constexpr spawnflags_t SPAWNFLAG_BERSERK_NOJUMPING = 8_spawnflag;
  11. static cached_soundindex sound_pain;
  12. static cached_soundindex sound_die;
  13. static cached_soundindex sound_idle;
  14. static cached_soundindex sound_idle2;
  15. static cached_soundindex sound_punch;
  16. static cached_soundindex sound_sight;
  17. static cached_soundindex sound_search;
  18. static cached_soundindex sound_thud;
  19. static cached_soundindex sound_explod;
  20. static cached_soundindex sound_jump;
  21. MONSTERINFO_SIGHT(berserk_sight) (edict_t *self, edict_t *other) -> void
  22. {
  23. gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
  24. }
  25. MONSTERINFO_SEARCH(berserk_search) (edict_t *self) -> void
  26. {
  27. if (brandom())
  28. gi.sound(self, CHAN_VOICE, sound_idle2, 1, ATTN_NORM, 0);
  29. else
  30. gi.sound(self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0);
  31. }
  32. void berserk_fidget(edict_t *self);
  33. mframe_t berserk_frames_stand[] = {
  34. { ai_stand, 0, berserk_fidget },
  35. { ai_stand },
  36. { ai_stand },
  37. { ai_stand },
  38. { ai_stand }
  39. };
  40. MMOVE_T(berserk_move_stand) = { FRAME_stand1, FRAME_stand5, berserk_frames_stand, nullptr };
  41. MONSTERINFO_STAND(berserk_stand) (edict_t *self) -> void
  42. {
  43. M_SetAnimation(self, &berserk_move_stand);
  44. }
  45. mframe_t berserk_frames_stand_fidget[] = {
  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. };
  67. MMOVE_T(berserk_move_stand_fidget) = { FRAME_standb1, FRAME_standb20, berserk_frames_stand_fidget, berserk_stand };
  68. void berserk_fidget(edict_t *self)
  69. {
  70. if (self->monsterinfo.aiflags & AI_STAND_GROUND)
  71. return;
  72. else if (self->enemy)
  73. return;
  74. if (frandom() > 0.15f)
  75. return;
  76. M_SetAnimation(self, &berserk_move_stand_fidget);
  77. gi.sound(self, CHAN_WEAPON, sound_idle, 1, ATTN_IDLE, 0);
  78. }
  79. mframe_t berserk_frames_walk[] = {
  80. { ai_walk, 9.1f },
  81. { ai_walk, 6.3f },
  82. { ai_walk, 4.9f },
  83. { ai_walk, 6.7f, monster_footstep },
  84. { ai_walk, 6.0f },
  85. { ai_walk, 8.2f },
  86. { ai_walk, 7.2f },
  87. { ai_walk, 6.1f },
  88. { ai_walk, 4.9f },
  89. { ai_walk, 4.7f, monster_footstep },
  90. { ai_walk, 4.7f }
  91. };
  92. MMOVE_T(berserk_move_walk) = { FRAME_walkc1, FRAME_walkc11, berserk_frames_walk, nullptr };
  93. MONSTERINFO_WALK(berserk_walk) (edict_t *self) -> void
  94. {
  95. M_SetAnimation(self, &berserk_move_walk);
  96. }
  97. /*
  98. *****************************
  99. SKIPPED THIS FOR NOW!
  100. *****************************
  101. Running -> Arm raised in air
  102. void() berserk_runb1 =[ $r_att1 , berserk_runb2 ] {ai_run(21);};
  103. void() berserk_runb2 =[ $r_att2 , berserk_runb3 ] {ai_run(11);};
  104. void() berserk_runb3 =[ $r_att3 , berserk_runb4 ] {ai_run(21);};
  105. void() berserk_runb4 =[ $r_att4 , berserk_runb5 ] {ai_run(25);};
  106. void() berserk_runb5 =[ $r_att5 , berserk_runb6 ] {ai_run(18);};
  107. void() berserk_runb6 =[ $r_att6 , berserk_runb7 ] {ai_run(19);};
  108. // running with arm in air : start loop
  109. void() berserk_runb7 =[ $r_att7 , berserk_runb8 ] {ai_run(21);};
  110. void() berserk_runb8 =[ $r_att8 , berserk_runb9 ] {ai_run(11);};
  111. void() berserk_runb9 =[ $r_att9 , berserk_runb10 ] {ai_run(21);};
  112. void() berserk_runb10 =[ $r_att10 , berserk_runb11 ] {ai_run(25);};
  113. void() berserk_runb11 =[ $r_att11 , berserk_runb12 ] {ai_run(18);};
  114. void() berserk_runb12 =[ $r_att12 , berserk_runb7 ] {ai_run(19);};
  115. // running with arm in air : end loop
  116. */
  117. mframe_t berserk_frames_run1[] = {
  118. { ai_run, 21 },
  119. { ai_run, 11, monster_footstep },
  120. { ai_run, 21 },
  121. { ai_run, 25, monster_done_dodge },
  122. { ai_run, 18, monster_footstep },
  123. { ai_run, 19 }
  124. };
  125. MMOVE_T(berserk_move_run1) = { FRAME_run1, FRAME_run6, berserk_frames_run1, nullptr };
  126. MONSTERINFO_RUN(berserk_run) (edict_t *self) -> void
  127. {
  128. monster_done_dodge(self);
  129. if (self->monsterinfo.aiflags & AI_STAND_GROUND)
  130. M_SetAnimation(self, &berserk_move_stand);
  131. else
  132. M_SetAnimation(self, &berserk_move_run1);
  133. }
  134. void berserk_attack_spike(edict_t *self)
  135. {
  136. constexpr vec3_t aim = { MELEE_DISTANCE, 0, -24 };
  137. if (!fire_hit(self, aim, irandom(5, 11), 80)) // Faster attack -- upwards and backwards
  138. self->monsterinfo.melee_debounce_time = level.time + 1.2_sec;
  139. }
  140. void berserk_swing(edict_t *self)
  141. {
  142. gi.sound(self, CHAN_WEAPON, sound_punch, 1, ATTN_NORM, 0);
  143. }
  144. mframe_t berserk_frames_attack_spike[] = {
  145. { ai_charge },
  146. { ai_charge },
  147. { ai_charge, 0, berserk_swing },
  148. { ai_charge, 0, berserk_attack_spike },
  149. { ai_charge },
  150. { ai_charge },
  151. { ai_charge },
  152. { ai_charge }
  153. };
  154. MMOVE_T(berserk_move_attack_spike) = { FRAME_att_c1, FRAME_att_c8, berserk_frames_attack_spike, berserk_run };
  155. void berserk_attack_club(edict_t *self)
  156. {
  157. vec3_t aim = { MELEE_DISTANCE, self->mins[0], -4 };
  158. if (!fire_hit(self, aim, irandom(15, 21), 400)) // Slower attack
  159. self->monsterinfo.melee_debounce_time = level.time + 2.5_sec;
  160. }
  161. mframe_t berserk_frames_attack_club[] = {
  162. { ai_charge },
  163. { ai_charge },
  164. { ai_charge, 0, monster_footstep },
  165. { ai_charge },
  166. { ai_charge, 0, berserk_swing },
  167. { ai_charge },
  168. { ai_charge, 0, berserk_attack_club },
  169. { ai_charge },
  170. { ai_charge },
  171. { ai_charge },
  172. { ai_charge },
  173. { ai_charge }
  174. };
  175. MMOVE_T(berserk_move_attack_club) = { FRAME_att_c9, FRAME_att_c20, berserk_frames_attack_club, berserk_run };
  176. /*
  177. ============
  178. T_RadiusDamage
  179. ============
  180. */
  181. void T_SlamRadiusDamage(vec3_t point, edict_t *inflictor, edict_t *attacker, float damage, float kick, edict_t *ignore, float radius, mod_t mod)
  182. {
  183. float points;
  184. edict_t *ent = nullptr;
  185. vec3_t v;
  186. vec3_t dir;
  187. while ((ent = findradius(ent, inflictor->s.origin, radius * 2.f)) != nullptr)
  188. {
  189. if (ent == ignore)
  190. continue;
  191. if (!ent->takedamage)
  192. continue;
  193. if (!CanDamage(ent, inflictor))
  194. continue;
  195. // don't hit players in mid air
  196. if (ent->client && !ent->groundentity)
  197. continue;
  198. v = closest_point_to_box(point, ent->s.origin + ent->mins, ent->s.origin + ent->maxs) - point;
  199. // calculate contribution amount
  200. float amount = min(1.f, 1.f - (v.length() / radius));
  201. // too far away
  202. if (amount <= 0.f)
  203. continue;
  204. amount *= amount;
  205. // damage & kick are exponentially scaled
  206. points = max(1.f, damage * amount);
  207. dir = (ent->s.origin - point).normalized();
  208. // keep the point at their feet so they always get knocked up
  209. point[2] = ent->absmin[2];
  210. T_Damage(ent, inflictor, attacker, dir, point, dir, (int) points, (int) (kick * amount),
  211. DAMAGE_RADIUS, mod);
  212. if (ent->client)
  213. ent->velocity.z = max(270.f, ent->velocity.z);
  214. }
  215. }
  216. static void berserk_attack_slam(edict_t *self)
  217. {
  218. gi.sound(self, CHAN_WEAPON, sound_thud, 1, ATTN_NORM, 0);
  219. gi.sound(self, CHAN_AUTO, sound_explod, 0.75f, ATTN_NORM, 0);
  220. gi.WriteByte(svc_temp_entity);
  221. gi.WriteByte(TE_BERSERK_SLAM);
  222. vec3_t f, r, start;
  223. AngleVectors(self->s.angles, f, r, nullptr);
  224. start = M_ProjectFlashSource(self, { 20.f, -14.3f, -21.f }, f, r);
  225. trace_t tr = gi.traceline(self->s.origin, start, self, MASK_SOLID);
  226. gi.WritePosition(tr.endpos);
  227. gi.WriteDir({ 0.f, 0.f, 1.f });
  228. gi.multicast(tr.endpos, MULTICAST_PHS, false);
  229. self->gravity = 1.0f;
  230. self->velocity = {};
  231. self->flags |= FL_KILL_VELOCITY;
  232. T_SlamRadiusDamage(tr.endpos, self, self, 8, 300.f, self, 165, MOD_UNKNOWN);
  233. }
  234. TOUCH(berserk_jump_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void
  235. {
  236. if (self->health <= 0)
  237. {
  238. self->touch = nullptr;
  239. return;
  240. }
  241. if (self->groundentity)
  242. {
  243. self->s.frame = FRAME_slam18;
  244. if (self->touch)
  245. berserk_attack_slam(self);
  246. self->touch = nullptr;
  247. }
  248. }
  249. static void berserk_high_gravity(edict_t *self)
  250. {
  251. if (self->velocity[2] < 0)
  252. self->gravity = 2.25f * (800.f / level.gravity);
  253. else
  254. self->gravity = 5.25f * (800.f / level.gravity);
  255. }
  256. void berserk_jump_takeoff(edict_t *self)
  257. {
  258. vec3_t forward;
  259. if (!self->enemy)
  260. return;
  261. // immediately turn to where we need to go
  262. float length = (self->s.origin - self->enemy->s.origin).length();
  263. float fwd_speed = length * 1.95f;
  264. vec3_t dir;
  265. PredictAim(self, self->enemy, self->s.origin, fwd_speed, false, 0.f, &dir, nullptr);
  266. self->s.angles[1] = vectoyaw(dir);
  267. AngleVectors(self->s.angles, forward, nullptr, nullptr);
  268. self->s.origin[2] += 1;
  269. self->velocity = forward * fwd_speed;
  270. self->velocity[2] = 450;
  271. self->groundentity = nullptr;
  272. self->monsterinfo.aiflags |= AI_DUCKED;
  273. self->monsterinfo.attack_finished = level.time + 3_sec;
  274. self->touch = berserk_jump_touch;
  275. berserk_high_gravity(self);
  276. }
  277. void berserk_check_landing(edict_t *self)
  278. {
  279. berserk_high_gravity(self);
  280. if (self->groundentity)
  281. {
  282. self->monsterinfo.attack_finished = 0_ms;
  283. self->monsterinfo.unduck(self);
  284. self->s.frame = FRAME_slam18;
  285. if (self->touch)
  286. {
  287. berserk_attack_slam(self);
  288. self->touch = nullptr;
  289. }
  290. self->flags &= ~FL_KILL_VELOCITY;
  291. return;
  292. }
  293. if (level.time > self->monsterinfo.attack_finished)
  294. self->monsterinfo.nextframe = FRAME_slam3;
  295. else
  296. self->monsterinfo.nextframe = FRAME_slam5;
  297. }
  298. mframe_t berserk_frames_attack_strike[] = {
  299. { ai_charge },
  300. { ai_charge },
  301. { ai_move, 0, berserk_jump_takeoff },
  302. { ai_move, 0, berserk_high_gravity },
  303. { ai_move, 0, berserk_check_landing },
  304. { ai_move, 0, monster_footstep },
  305. { ai_move },
  306. { ai_move, 0, monster_footstep },
  307. { ai_move },
  308. { ai_move },
  309. { ai_move },
  310. { ai_move },
  311. { ai_move },
  312. { ai_move },
  313. { ai_move },
  314. { ai_move },
  315. { ai_move },
  316. { ai_move },
  317. { ai_move },
  318. { ai_move },
  319. { ai_move },
  320. { ai_move },
  321. { ai_move, 0, monster_footstep }
  322. };
  323. MMOVE_T(berserk_move_attack_strike) = { FRAME_slam1, FRAME_slam23, berserk_frames_attack_strike, berserk_run };
  324. extern const mmove_t berserk_move_run_attack1;
  325. MONSTERINFO_MELEE(berserk_melee) (edict_t *self) -> void
  326. {
  327. if (self->monsterinfo.melee_debounce_time > level.time)
  328. return;
  329. // if we're *almost* ready to land down the hammer from run-attack
  330. // don't switch us
  331. else if (self->monsterinfo.active_move == &berserk_move_run_attack1 && self->s.frame >= FRAME_r_att13)
  332. {
  333. self->monsterinfo.attack_state = AS_STRAIGHT;
  334. self->monsterinfo.attack_finished = 0_ms;
  335. return;
  336. }
  337. monster_done_dodge(self);
  338. if (brandom())
  339. M_SetAnimation(self, &berserk_move_attack_spike);
  340. else
  341. M_SetAnimation(self, &berserk_move_attack_club);
  342. }
  343. static void berserk_run_attack_speed(edict_t *self)
  344. {
  345. if (self->enemy && range_to(self, self->enemy) < MELEE_DISTANCE)
  346. {
  347. self->monsterinfo.nextframe = self->s.frame + 6;
  348. monster_done_dodge(self);
  349. }
  350. }
  351. static void berserk_run_swing(edict_t *self)
  352. {
  353. berserk_swing(self);
  354. self->monsterinfo.melee_debounce_time = level.time + 0.6_sec;
  355. if (self->monsterinfo.attack_state == AS_SLIDING)
  356. {
  357. self->monsterinfo.attack_state = AS_STRAIGHT;
  358. monster_done_dodge(self);
  359. }
  360. }
  361. mframe_t berserk_frames_run_attack1[] = {
  362. { ai_run, 21, berserk_run_attack_speed },
  363. { ai_run, 11, [](edict_t *self) { berserk_run_attack_speed(self); monster_footstep(self); } },
  364. { ai_run, 21, berserk_run_attack_speed },
  365. { ai_run, 25, [](edict_t *self) { berserk_run_attack_speed(self); monster_done_dodge(self); } },
  366. { ai_run, 18, [](edict_t *self) { berserk_run_attack_speed(self); monster_footstep(self); } },
  367. { ai_run, 19, berserk_run_attack_speed },
  368. { ai_run, 21 },
  369. { ai_run, 11, monster_footstep },
  370. { ai_run, 21 },
  371. { ai_run, 25 },
  372. { ai_run, 18, monster_footstep },
  373. { ai_run, 19 },
  374. { ai_run, 21, berserk_run_swing },
  375. { ai_run, 11, monster_footstep },
  376. { ai_run, 21 },
  377. { ai_run, 25 },
  378. { ai_run, 18, monster_footstep },
  379. { ai_run, 19, berserk_attack_club }
  380. };
  381. MMOVE_T(berserk_move_run_attack1) = { FRAME_r_att1, FRAME_r_att18, berserk_frames_run_attack1, berserk_run };
  382. MONSTERINFO_ATTACK(berserk_attack) (edict_t *self) -> void
  383. {
  384. if (self->monsterinfo.melee_debounce_time <= level.time && (range_to(self, self->enemy) < MELEE_DISTANCE))
  385. berserk_melee(self);
  386. // only jump if they are far enough away for it to make sense (otherwise
  387. // it gets annoying to have them keep hopping over and over again)
  388. else if (!self->spawnflags.has(SPAWNFLAG_BERSERK_NOJUMPING) && (self->timestamp < level.time && brandom()) && range_to(self, self->enemy) > 150.f)
  389. {
  390. M_SetAnimation(self, &berserk_move_attack_strike);
  391. // don't do this for a while, otherwise we just keep doing it
  392. gi.sound(self, CHAN_WEAPON, sound_jump, 1, ATTN_NORM, 0);
  393. self->timestamp = level.time + 5_sec;
  394. }
  395. else if (self->monsterinfo.active_move == &berserk_move_run1 && (range_to(self, self->enemy) <= RANGE_NEAR))
  396. {
  397. M_SetAnimation(self, &berserk_move_run_attack1);
  398. self->monsterinfo.nextframe = FRAME_r_att1 + (self->s.frame - FRAME_run1) + 1;
  399. }
  400. }
  401. mframe_t berserk_frames_pain1[] = {
  402. { ai_move },
  403. { ai_move },
  404. { ai_move },
  405. { ai_move }
  406. };
  407. MMOVE_T(berserk_move_pain1) = { FRAME_painc1, FRAME_painc4, berserk_frames_pain1, berserk_run };
  408. mframe_t berserk_frames_pain2[] = {
  409. { ai_move },
  410. { ai_move },
  411. { ai_move },
  412. { ai_move },
  413. { ai_move, 0, monster_footstep },
  414. { ai_move },
  415. { ai_move },
  416. { ai_move },
  417. { ai_move },
  418. { ai_move },
  419. { ai_move },
  420. { ai_move },
  421. { ai_move },
  422. { ai_move, 0, monster_footstep },
  423. { ai_move },
  424. { ai_move },
  425. { ai_move },
  426. { ai_move },
  427. { ai_move },
  428. { ai_move, 0, monster_footstep }
  429. };
  430. MMOVE_T(berserk_move_pain2) = { FRAME_painb1, FRAME_painb20, berserk_frames_pain2, berserk_run };
  431. extern const mmove_t berserk_move_jump, berserk_move_jump2;
  432. PAIN(berserk_pain) (edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod) -> void
  433. {
  434. // if we're jumping, don't pain
  435. if ((self->monsterinfo.active_move == &berserk_move_jump) ||
  436. (self->monsterinfo.active_move == &berserk_move_jump2) ||
  437. (self->monsterinfo.active_move == &berserk_move_attack_strike))
  438. {
  439. return;
  440. }
  441. if (level.time < self->pain_debounce_time)
  442. return;
  443. self->pain_debounce_time = level.time + 3_sec;
  444. gi.sound(self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0);
  445. if (!M_ShouldReactToPain(self, mod))
  446. return; // no pain anims in nightmare
  447. monster_done_dodge(self);
  448. if ((damage <= 50) || (frandom() < 0.5f))
  449. M_SetAnimation(self, &berserk_move_pain1);
  450. else
  451. M_SetAnimation(self, &berserk_move_pain2);
  452. }
  453. MONSTERINFO_SETSKIN(berserk_setskin) (edict_t *self) -> void
  454. {
  455. if (self->health < (self->max_health / 2))
  456. self->s.skinnum = 1;
  457. else
  458. self->s.skinnum = 0;
  459. }
  460. void berserk_dead(edict_t *self)
  461. {
  462. self->mins = { -16, -16, -24 };
  463. self->maxs = { 16, 16, -8 };
  464. monster_dead(self);
  465. }
  466. static void berserk_shrink(edict_t *self)
  467. {
  468. self->maxs[2] = 0;
  469. self->svflags |= SVF_DEADMONSTER;
  470. gi.linkentity(self);
  471. }
  472. mframe_t berserk_frames_death1[] = {
  473. { ai_move },
  474. { ai_move },
  475. { ai_move },
  476. { ai_move },
  477. { ai_move, 0, monster_footstep },
  478. { ai_move },
  479. { ai_move, 0, berserk_shrink },
  480. { ai_move },
  481. { ai_move },
  482. { ai_move },
  483. { ai_move },
  484. { ai_move },
  485. { ai_move }
  486. };
  487. MMOVE_T(berserk_move_death1) = { FRAME_death1, FRAME_death13, berserk_frames_death1, berserk_dead };
  488. mframe_t berserk_frames_death2[] = {
  489. { ai_move },
  490. { ai_move },
  491. { ai_move },
  492. { ai_move, 0, berserk_shrink },
  493. { ai_move, 0, monster_footstep },
  494. { ai_move },
  495. { ai_move },
  496. { ai_move }
  497. };
  498. MMOVE_T(berserk_move_death2) = { FRAME_deathc1, FRAME_deathc8, berserk_frames_death2, berserk_dead };
  499. DIE(berserk_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void
  500. {
  501. if (M_CheckGib(self, mod))
  502. {
  503. gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0);
  504. self->s.skinnum = 0;
  505. ThrowGibs(self, damage, {
  506. { 2, "models/objects/gibs/bone/tris.md2" },
  507. { 3, "models/objects/gibs/sm_meat/tris.md2" },
  508. { 1, "models/objects/gibs/gear/tris.md2" },
  509. { "models/monsters/berserk/gibs/chest.md2", GIB_SKINNED },
  510. { "models/monsters/berserk/gibs/hammer.md2", GIB_SKINNED | GIB_UPRIGHT },
  511. { "models/monsters/berserk/gibs/thigh.md2", GIB_SKINNED },
  512. { "models/monsters/berserk/gibs/head.md2", GIB_HEAD | GIB_SKINNED }
  513. });
  514. self->deadflag = true;
  515. return;
  516. }
  517. if (self->deadflag)
  518. return;
  519. gi.sound(self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0);
  520. self->deadflag = true;
  521. self->takedamage = true;
  522. if (damage >= 50)
  523. M_SetAnimation(self, &berserk_move_death1);
  524. else
  525. M_SetAnimation(self, &berserk_move_death2);
  526. }
  527. //===========
  528. // PGM
  529. void berserk_jump_now(edict_t *self)
  530. {
  531. vec3_t forward, up;
  532. AngleVectors(self->s.angles, forward, nullptr, up);
  533. self->velocity += (forward * 100);
  534. self->velocity += (up * 300);
  535. }
  536. void berserk_jump2_now(edict_t *self)
  537. {
  538. vec3_t forward, up;
  539. AngleVectors(self->s.angles, forward, nullptr, up);
  540. self->velocity += (forward * 150);
  541. self->velocity += (up * 400);
  542. }
  543. void berserk_jump_wait_land(edict_t *self)
  544. {
  545. if (self->groundentity == nullptr)
  546. {
  547. self->monsterinfo.nextframe = self->s.frame;
  548. if (monster_jump_finished(self))
  549. self->monsterinfo.nextframe = self->s.frame + 1;
  550. }
  551. else
  552. self->monsterinfo.nextframe = self->s.frame + 1;
  553. }
  554. mframe_t berserk_frames_jump[] = {
  555. { ai_move },
  556. { ai_move },
  557. { ai_move },
  558. { ai_move, 0, berserk_jump_now },
  559. { ai_move },
  560. { ai_move },
  561. { ai_move, 0, berserk_jump_wait_land },
  562. { ai_move },
  563. { ai_move }
  564. };
  565. MMOVE_T(berserk_move_jump) = { FRAME_jump1, FRAME_jump9, berserk_frames_jump, berserk_run };
  566. mframe_t berserk_frames_jump2[] = {
  567. { ai_move, -8 },
  568. { ai_move, -4 },
  569. { ai_move, -4 },
  570. { ai_move, 0, berserk_jump2_now },
  571. { ai_move },
  572. { ai_move },
  573. { ai_move, 0, berserk_jump_wait_land },
  574. { ai_move },
  575. { ai_move }
  576. };
  577. MMOVE_T(berserk_move_jump2) = { FRAME_jump1, FRAME_jump9, berserk_frames_jump2, berserk_run };
  578. void berserk_jump(edict_t *self, blocked_jump_result_t result)
  579. {
  580. if (!self->enemy)
  581. return;
  582. if (result == blocked_jump_result_t::JUMP_JUMP_UP)
  583. M_SetAnimation(self, &berserk_move_jump2);
  584. else
  585. M_SetAnimation(self, &berserk_move_jump);
  586. }
  587. MONSTERINFO_BLOCKED(berserk_blocked) (edict_t *self, float dist) -> bool
  588. {
  589. if (auto result = blocked_checkjump(self, dist); result != blocked_jump_result_t::NO_JUMP)
  590. {
  591. if (result != blocked_jump_result_t::JUMP_TURN)
  592. berserk_jump(self, result);
  593. return true;
  594. }
  595. if (blocked_checkplat(self, dist))
  596. return true;
  597. return false;
  598. }
  599. // PGM
  600. //===========
  601. MONSTERINFO_SIDESTEP(berserk_sidestep) (edict_t *self) -> bool
  602. {
  603. // if we're jumping or in long pain, don't dodge
  604. if ((self->monsterinfo.active_move == &berserk_move_jump) ||
  605. (self->monsterinfo.active_move == &berserk_move_jump2) ||
  606. (self->monsterinfo.active_move == &berserk_move_attack_strike) ||
  607. (self->monsterinfo.active_move == &berserk_move_pain2))
  608. return false;
  609. if (self->monsterinfo.active_move != &berserk_move_run1)
  610. M_SetAnimation(self, &berserk_move_run1);
  611. return true;
  612. }
  613. mframe_t berserk_frames_duck[] = {
  614. { ai_move, 0, monster_duck_down },
  615. { ai_move },
  616. { ai_move },
  617. { ai_move },
  618. { ai_move, 0, monster_duck_hold },
  619. { ai_move },
  620. { ai_move },
  621. { ai_move, 0, monster_duck_up },
  622. { ai_move },
  623. { ai_move }
  624. };
  625. MMOVE_T(berserk_move_duck) = { FRAME_duck1, FRAME_duck10, berserk_frames_duck, berserk_run };
  626. mframe_t berserk_frames_duck2[] = {
  627. { ai_move, 21, monster_duck_down },
  628. { ai_move, 28 },
  629. { ai_move, 20 },
  630. { ai_move, 12, monster_footstep },
  631. { ai_move, 7 },
  632. { ai_move, 0, monster_footstep },
  633. { ai_move },
  634. { ai_move, 0, monster_duck_hold },
  635. { ai_move, 0 },
  636. { ai_move, 0 },
  637. { ai_move, 0 },
  638. { ai_move, 0 },
  639. { ai_move, 0, monster_footstep },
  640. { ai_move, 0, monster_duck_up },
  641. { ai_move },
  642. { ai_move },
  643. { ai_move, 0, monster_footstep },
  644. };
  645. MMOVE_T(berserk_move_duck2) = { FRAME_fall2, FRAME_fall18, berserk_frames_duck2, berserk_run };
  646. MONSTERINFO_DUCK(berserk_duck) (edict_t *self, gtime_t eta) -> bool
  647. {
  648. // berserk only dives forward, and very rarely
  649. if (frandom() >= 0.05f)
  650. {
  651. return false;
  652. }
  653. // if we're jumping, don't dodge
  654. if ((self->monsterinfo.active_move == &berserk_move_jump) ||
  655. (self->monsterinfo.active_move == &berserk_move_jump2))
  656. {
  657. return false;
  658. }
  659. M_SetAnimation(self, &berserk_move_duck2);
  660. return true;
  661. }
  662. /*QUAKED monster_berserk (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight
  663. */
  664. void SP_monster_berserk(edict_t *self)
  665. {
  666. if ( !M_AllowSpawn( self ) ) {
  667. G_FreeEdict( self );
  668. return;
  669. }
  670. // pre-caches
  671. sound_pain.assign("berserk/berpain2.wav");
  672. sound_die.assign("berserk/berdeth2.wav");
  673. sound_idle.assign("berserk/beridle1.wav");
  674. sound_idle2.assign("berserk/idle.wav");
  675. sound_punch.assign("berserk/attack.wav");
  676. sound_search.assign("berserk/bersrch1.wav");
  677. sound_sight.assign("berserk/sight.wav");
  678. sound_thud.assign("mutant/thud1.wav");
  679. sound_explod.assign("world/explod2.wav");
  680. sound_jump.assign("berserk/jump.wav");
  681. self->s.modelindex = gi.modelindex("models/monsters/berserk/tris.md2");
  682. gi.modelindex("models/monsters/berserk/gibs/head.md2");
  683. gi.modelindex("models/monsters/berserk/gibs/chest.md2");
  684. gi.modelindex("models/monsters/berserk/gibs/hammer.md2");
  685. gi.modelindex("models/monsters/berserk/gibs/thigh.md2");
  686. self->mins = { -16, -16, -24 };
  687. self->maxs = { 16, 16, 32 };
  688. self->movetype = MOVETYPE_STEP;
  689. self->solid = SOLID_BBOX;
  690. self->health = 240 * st.health_multiplier;
  691. self->gib_health = -60;
  692. self->mass = 250;
  693. self->pain = berserk_pain;
  694. self->die = berserk_die;
  695. self->monsterinfo.stand = berserk_stand;
  696. self->monsterinfo.walk = berserk_walk;
  697. self->monsterinfo.run = berserk_run;
  698. // pmm
  699. self->monsterinfo.dodge = M_MonsterDodge;
  700. self->monsterinfo.duck = berserk_duck;
  701. self->monsterinfo.unduck = monster_duck_up;
  702. self->monsterinfo.sidestep = berserk_sidestep;
  703. self->monsterinfo.blocked = berserk_blocked; // PGM
  704. // pmm
  705. self->monsterinfo.attack = berserk_attack;
  706. self->monsterinfo.melee = berserk_melee;
  707. self->monsterinfo.sight = berserk_sight;
  708. self->monsterinfo.search = berserk_search;
  709. self->monsterinfo.setskin = berserk_setskin;
  710. M_SetAnimation(self, &berserk_move_stand);
  711. self->monsterinfo.scale = MODEL_SCALE;
  712. self->monsterinfo.combat_style = COMBAT_MELEE;
  713. self->monsterinfo.can_jump = !self->spawnflags.has(SPAWNFLAG_BERSERK_NOJUMPING);
  714. self->monsterinfo.drop_height = 256;
  715. self->monsterinfo.jump_height = 40;
  716. gi.linkentity(self);
  717. walkmonster_start(self);
  718. }