m_soldier.cpp 46 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050
  1. // Copyright (c) ZeniMax Media Inc.
  2. // Licensed under the GNU General Public License 2.0.
  3. /*
  4. ==============================================================================
  5. SOLDIER
  6. ==============================================================================
  7. */
  8. #include "g_local.h"
  9. #include "m_soldier.h"
  10. #include "m_flash.h"
  11. static cached_soundindex sound_idle;
  12. static cached_soundindex sound_sight1;
  13. static cached_soundindex sound_sight2;
  14. static cached_soundindex sound_pain_light;
  15. static cached_soundindex sound_pain;
  16. static cached_soundindex sound_pain_ss;
  17. static cached_soundindex sound_death_light;
  18. static cached_soundindex sound_death;
  19. static cached_soundindex sound_death_ss;
  20. static cached_soundindex sound_cock;
  21. void soldier_start_charge(edict_t *self)
  22. {
  23. self->monsterinfo.aiflags |= AI_CHARGING;
  24. }
  25. void soldier_stop_charge(edict_t *self)
  26. {
  27. self->monsterinfo.aiflags &= ~AI_CHARGING;
  28. }
  29. void soldier_idle(edict_t *self)
  30. {
  31. if (frandom() > 0.8f)
  32. gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0);
  33. }
  34. void soldier_cock(edict_t *self)
  35. {
  36. if (self->s.frame == FRAME_stand322)
  37. gi.sound(self, CHAN_WEAPON, sound_cock, 1, ATTN_IDLE, 0);
  38. else
  39. gi.sound(self, CHAN_WEAPON, sound_cock, 1, ATTN_NORM, 0);
  40. // [Paril-KEX] reset cockness
  41. self->dmg = 0;
  42. }
  43. // RAFAEL
  44. void soldierh_hyper_laser_sound_start(edict_t *self)
  45. {
  46. if (self->style == 1)
  47. {
  48. if (self->count >= 2 && self->count < 4)
  49. self->monsterinfo.weapon_sound = gi.soundindex("weapons/hyprbl1a.wav");
  50. }
  51. }
  52. void soldierh_hyper_laser_sound_end(edict_t *self)
  53. {
  54. if (self->monsterinfo.weapon_sound)
  55. {
  56. if (self->count >= 2 && self->count < 4)
  57. gi.sound(self, CHAN_AUTO, gi.soundindex("weapons/hyprbd1a.wav"), 1, ATTN_NORM, 0);
  58. self->monsterinfo.weapon_sound = 0;
  59. }
  60. }
  61. // RAFAEL
  62. // STAND
  63. void soldier_stand(edict_t *self);
  64. mframe_t soldier_frames_stand1[] = {
  65. { ai_stand, 0, soldier_idle },
  66. { ai_stand },
  67. { ai_stand },
  68. { ai_stand },
  69. { ai_stand },
  70. { ai_stand },
  71. { ai_stand },
  72. { ai_stand },
  73. { ai_stand },
  74. { ai_stand },
  75. { ai_stand },
  76. { ai_stand },
  77. { ai_stand },
  78. { ai_stand },
  79. { ai_stand },
  80. { ai_stand },
  81. { ai_stand },
  82. { ai_stand },
  83. { 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(soldier_move_stand1) = { FRAME_stand101, FRAME_stand130, soldier_frames_stand1, soldier_stand };
  97. mframe_t soldier_frames_stand2[] = {
  98. { ai_stand },
  99. { ai_stand },
  100. { ai_stand },
  101. { ai_stand },
  102. { ai_stand },
  103. { ai_stand },
  104. { ai_stand },
  105. { ai_stand },
  106. { ai_stand },
  107. { ai_stand },
  108. { ai_stand },
  109. { ai_stand },
  110. { ai_stand },
  111. { ai_stand, 0, monster_footstep },
  112. { ai_stand },
  113. { ai_stand },
  114. { ai_stand },
  115. { ai_stand },
  116. { ai_stand },
  117. { ai_stand },
  118. { ai_stand },
  119. { ai_stand },
  120. { ai_stand },
  121. { ai_stand },
  122. { ai_stand },
  123. { ai_stand },
  124. { ai_stand },
  125. { ai_stand },
  126. { ai_stand },
  127. { ai_stand },
  128. { ai_stand },
  129. { ai_stand },
  130. { ai_stand },
  131. { ai_stand },
  132. { ai_stand },
  133. { ai_stand },
  134. { ai_stand },
  135. { ai_stand },
  136. { ai_stand },
  137. { ai_stand, 0, monster_footstep }
  138. };
  139. MMOVE_T(soldier_move_stand2) = { FRAME_stand201, FRAME_stand240, soldier_frames_stand2, soldier_stand };
  140. mframe_t soldier_frames_stand3[] = {
  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. { ai_stand },
  153. { ai_stand },
  154. { ai_stand },
  155. { ai_stand },
  156. { ai_stand },
  157. { ai_stand },
  158. { ai_stand },
  159. { ai_stand },
  160. { ai_stand },
  161. { ai_stand },
  162. { ai_stand, 0, soldier_cock },
  163. { ai_stand },
  164. { ai_stand },
  165. { ai_stand },
  166. { ai_stand },
  167. { ai_stand },
  168. { ai_stand },
  169. { ai_stand },
  170. { ai_stand },
  171. { ai_stand },
  172. { ai_stand },
  173. { ai_stand },
  174. { ai_stand },
  175. { ai_stand },
  176. { ai_stand },
  177. { ai_stand },
  178. { ai_stand },
  179. { ai_stand }
  180. };
  181. MMOVE_T(soldier_move_stand3) = { FRAME_stand301, FRAME_stand339, soldier_frames_stand3, soldier_stand };
  182. MONSTERINFO_STAND(soldier_stand) (edict_t *self) -> void
  183. {
  184. float r = frandom();
  185. if ((self->monsterinfo.active_move != &soldier_move_stand1) || (r < 0.6f))
  186. M_SetAnimation(self, &soldier_move_stand1);
  187. else if (r < 0.8f)
  188. M_SetAnimation(self, &soldier_move_stand2);
  189. else
  190. M_SetAnimation(self, &soldier_move_stand3);
  191. soldierh_hyper_laser_sound_end(self);
  192. }
  193. //
  194. // WALK
  195. //
  196. void soldier_walk1_random(edict_t *self)
  197. {
  198. if (frandom() > 0.1f)
  199. self->monsterinfo.nextframe = FRAME_walk101;
  200. }
  201. mframe_t soldier_frames_walk1[] = {
  202. { ai_walk, 3 },
  203. { ai_walk, 6 },
  204. { ai_walk, 2 },
  205. { ai_walk, 2, monster_footstep },
  206. { ai_walk, 2 },
  207. { ai_walk, 1 },
  208. { ai_walk, 6 },
  209. { ai_walk, 5 },
  210. { ai_walk, 3, monster_footstep },
  211. { ai_walk, -1, soldier_walk1_random },
  212. { ai_walk },
  213. { ai_walk },
  214. { ai_walk },
  215. { ai_walk },
  216. { ai_walk },
  217. { ai_walk },
  218. { ai_walk },
  219. { ai_walk },
  220. { ai_walk },
  221. { ai_walk },
  222. { ai_walk },
  223. { ai_walk },
  224. { ai_walk },
  225. { ai_walk },
  226. { ai_walk },
  227. { ai_walk },
  228. { ai_walk },
  229. { ai_walk },
  230. { ai_walk },
  231. { ai_walk },
  232. { ai_walk },
  233. { ai_walk },
  234. { ai_walk }
  235. };
  236. MMOVE_T(soldier_move_walk1) = { FRAME_walk101, FRAME_walk133, soldier_frames_walk1, nullptr };
  237. mframe_t soldier_frames_walk2[] = {
  238. { ai_walk, 4, monster_footstep },
  239. { ai_walk, 4 },
  240. { ai_walk, 9 },
  241. { ai_walk, 8 },
  242. { ai_walk, 5 },
  243. { ai_walk, 1, monster_footstep },
  244. { ai_walk, 3 },
  245. { ai_walk, 7 },
  246. { ai_walk, 6 },
  247. { ai_walk, 7 }
  248. };
  249. MMOVE_T(soldier_move_walk2) = { FRAME_walk209, FRAME_walk218, soldier_frames_walk2, nullptr };
  250. MONSTERINFO_WALK(soldier_walk) (edict_t *self) -> void
  251. {
  252. // [Paril-KEX] during N64 cutscene, always use fast walk or we bog down the line
  253. if (!(self->hackflags & HACKFLAG_END_CUTSCENE) && frandom() < 0.5f)
  254. M_SetAnimation(self, &soldier_move_walk1);
  255. else
  256. M_SetAnimation(self, &soldier_move_walk2);
  257. }
  258. //
  259. // RUN
  260. //
  261. void soldier_run(edict_t *self);
  262. mframe_t soldier_frames_start_run[] = {
  263. { ai_run, 7 },
  264. { ai_run, 5 }
  265. };
  266. MMOVE_T(soldier_move_start_run) = { FRAME_run01, FRAME_run02, soldier_frames_start_run, soldier_run };
  267. mframe_t soldier_frames_run[] = {
  268. { ai_run, 10 },
  269. { ai_run, 11, [](edict_t *self) { monster_done_dodge(self); monster_footstep(self); } },
  270. { ai_run, 11 },
  271. { ai_run, 16 },
  272. { ai_run, 10, monster_footstep },
  273. { ai_run, 15, monster_done_dodge }
  274. };
  275. MMOVE_T(soldier_move_run) = { FRAME_run03, FRAME_run08, soldier_frames_run, nullptr };
  276. MONSTERINFO_RUN(soldier_run) (edict_t *self) -> void
  277. {
  278. monster_done_dodge(self);
  279. soldierh_hyper_laser_sound_end(self);
  280. if (self->monsterinfo.aiflags & AI_STAND_GROUND)
  281. {
  282. M_SetAnimation(self, &soldier_move_stand1);
  283. return;
  284. }
  285. if (self->monsterinfo.active_move == &soldier_move_walk1 ||
  286. self->monsterinfo.active_move == &soldier_move_walk2 ||
  287. self->monsterinfo.active_move == &soldier_move_start_run ||
  288. self->monsterinfo.active_move == &soldier_move_run)
  289. {
  290. M_SetAnimation(self, &soldier_move_run);
  291. }
  292. else
  293. {
  294. M_SetAnimation(self, &soldier_move_start_run);
  295. }
  296. }
  297. //
  298. // PAIN
  299. //
  300. mframe_t soldier_frames_pain1[] = {
  301. { ai_move, -3 },
  302. { ai_move, 4 },
  303. { ai_move, 1 },
  304. { ai_move, 1 },
  305. { ai_move }
  306. };
  307. MMOVE_T(soldier_move_pain1) = { FRAME_pain101, FRAME_pain105, soldier_frames_pain1, soldier_run };
  308. mframe_t soldier_frames_pain2[] = {
  309. { ai_move, -13 },
  310. { ai_move, -1 },
  311. { ai_move, 2 },
  312. { ai_move, 4 },
  313. { ai_move, 2 },
  314. { ai_move, 3 },
  315. { ai_move, 2 }
  316. };
  317. MMOVE_T(soldier_move_pain2) = { FRAME_pain201, FRAME_pain207, soldier_frames_pain2, soldier_run };
  318. mframe_t soldier_frames_pain3[] = {
  319. { ai_move, -8 },
  320. { ai_move, 10 },
  321. { ai_move, -4, monster_footstep },
  322. { ai_move, -1 },
  323. { ai_move, -3 },
  324. { ai_move },
  325. { ai_move, 3 },
  326. { ai_move },
  327. { ai_move },
  328. { ai_move },
  329. { ai_move },
  330. { ai_move, 1 },
  331. { ai_move },
  332. { ai_move, 1 },
  333. { ai_move, 2 },
  334. { ai_move, 4 },
  335. { ai_move, 3 },
  336. { ai_move, 2, monster_footstep }
  337. };
  338. MMOVE_T(soldier_move_pain3) = { FRAME_pain301, FRAME_pain318, soldier_frames_pain3, soldier_run };
  339. mframe_t soldier_frames_pain4[] = {
  340. { ai_move },
  341. { ai_move },
  342. { ai_move },
  343. { ai_move, -10 },
  344. { ai_move, -6 },
  345. { ai_move, 8 },
  346. { ai_move, 4 },
  347. { ai_move, 1 },
  348. { ai_move },
  349. { ai_move, 2 },
  350. { ai_move, 5 },
  351. { ai_move, 2 },
  352. { ai_move, -1 },
  353. { ai_move, -1 },
  354. { ai_move, 3 },
  355. { ai_move, 2 },
  356. { ai_move }
  357. };
  358. MMOVE_T(soldier_move_pain4) = { FRAME_pain401, FRAME_pain417, soldier_frames_pain4, soldier_run };
  359. PAIN(soldier_pain) (edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod) -> void
  360. {
  361. float r;
  362. int n;
  363. monster_done_dodge(self);
  364. soldier_stop_charge(self);
  365. // if we're blind firing, this needs to be turned off here
  366. self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
  367. if (level.time < self->pain_debounce_time)
  368. {
  369. if ((self->velocity[2] > 100) && ((self->monsterinfo.active_move == &soldier_move_pain1) || (self->monsterinfo.active_move == &soldier_move_pain2) || (self->monsterinfo.active_move == &soldier_move_pain3)))
  370. {
  371. // PMM - clear duck flag
  372. if (self->monsterinfo.aiflags & AI_DUCKED)
  373. monster_duck_up(self);
  374. M_SetAnimation(self, &soldier_move_pain4);
  375. soldierh_hyper_laser_sound_end(self);
  376. }
  377. return;
  378. }
  379. self->pain_debounce_time = level.time + 3_sec;
  380. n = self->count | 1;
  381. if (n == 1)
  382. gi.sound(self, CHAN_VOICE, sound_pain_light, 1, ATTN_NORM, 0);
  383. else if (n == 3)
  384. gi.sound(self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0);
  385. else
  386. gi.sound(self, CHAN_VOICE, sound_pain_ss, 1, ATTN_NORM, 0);
  387. if (self->velocity[2] > 100)
  388. {
  389. // PMM - clear duck flag
  390. if (self->monsterinfo.aiflags & AI_DUCKED)
  391. monster_duck_up(self);
  392. M_SetAnimation(self, &soldier_move_pain4);
  393. soldierh_hyper_laser_sound_end(self);
  394. return;
  395. }
  396. if (!M_ShouldReactToPain(self, mod))
  397. return; // no pain anims in nightmare
  398. r = frandom();
  399. if (r < 0.33f)
  400. M_SetAnimation(self, &soldier_move_pain1);
  401. else if (r < 0.66f)
  402. M_SetAnimation(self, &soldier_move_pain2);
  403. else
  404. M_SetAnimation(self, &soldier_move_pain3);
  405. // PMM - clear duck flag
  406. if (self->monsterinfo.aiflags & AI_DUCKED)
  407. monster_duck_up(self);
  408. soldierh_hyper_laser_sound_end(self);
  409. }
  410. MONSTERINFO_SETSKIN(soldier_setskin) (edict_t *self) -> void
  411. {
  412. if (self->health < (self->max_health / 2))
  413. self->s.skinnum |= 1;
  414. else
  415. self->s.skinnum &= ~1;
  416. }
  417. //
  418. // ATTACK
  419. //
  420. constexpr monster_muzzleflash_id_t blaster_flash[] = { MZ2_SOLDIER_BLASTER_1, MZ2_SOLDIER_BLASTER_2, MZ2_SOLDIER_BLASTER_3, MZ2_SOLDIER_BLASTER_4, MZ2_SOLDIER_BLASTER_5, MZ2_SOLDIER_BLASTER_6, MZ2_SOLDIER_BLASTER_7, MZ2_SOLDIER_BLASTER_8, MZ2_SOLDIER_BLASTER_9 };
  421. constexpr monster_muzzleflash_id_t shotgun_flash[] = { MZ2_SOLDIER_SHOTGUN_1, MZ2_SOLDIER_SHOTGUN_2, MZ2_SOLDIER_SHOTGUN_3, MZ2_SOLDIER_SHOTGUN_4, MZ2_SOLDIER_SHOTGUN_5, MZ2_SOLDIER_SHOTGUN_6, MZ2_SOLDIER_SHOTGUN_7, MZ2_SOLDIER_SHOTGUN_8, MZ2_SOLDIER_SHOTGUN_9 };
  422. constexpr monster_muzzleflash_id_t machinegun_flash[] = { MZ2_SOLDIER_MACHINEGUN_1, MZ2_SOLDIER_MACHINEGUN_2, MZ2_SOLDIER_MACHINEGUN_3, MZ2_SOLDIER_MACHINEGUN_4, MZ2_SOLDIER_MACHINEGUN_5, MZ2_SOLDIER_MACHINEGUN_6, MZ2_SOLDIER_MACHINEGUN_7, MZ2_SOLDIER_MACHINEGUN_8, MZ2_SOLDIER_MACHINEGUN_9 };
  423. void soldier_fire_vanilla(edict_t *self, int flash_number, bool angle_limited)
  424. {
  425. vec3_t start;
  426. vec3_t forward, right, up;
  427. vec3_t aim;
  428. vec3_t dir;
  429. vec3_t end;
  430. float r, u;
  431. monster_muzzleflash_id_t flash_index;
  432. vec3_t aim_norm;
  433. float angle;
  434. vec3_t aim_good;
  435. if (self->count < 2)
  436. flash_index = blaster_flash[flash_number];
  437. else if (self->count < 4)
  438. flash_index = shotgun_flash[flash_number];
  439. else
  440. flash_index = machinegun_flash[flash_number];
  441. AngleVectors(self->s.angles, forward, right, nullptr);
  442. start = M_ProjectFlashSource(self, monster_flash_offset[flash_index], forward, right);
  443. if (flash_number == 5 || flash_number == 6) // he's dead
  444. {
  445. if (self->spawnflags.has(SPAWNFLAG_MONSTER_DEAD))
  446. return;
  447. aim = forward;
  448. }
  449. else
  450. {
  451. if ((!self->enemy) || (!self->enemy->inuse))
  452. {
  453. self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
  454. return;
  455. }
  456. // PMM
  457. if (self->monsterinfo.attack_state == AS_BLIND)
  458. end = self->monsterinfo.blind_fire_target;
  459. else
  460. end = self->enemy->s.origin;
  461. // pmm
  462. end[2] += self->enemy->viewheight;
  463. aim = end - start;
  464. aim_good = end;
  465. // PMM
  466. if (angle_limited)
  467. {
  468. aim_norm = aim;
  469. aim_norm.normalize();
  470. angle = aim_norm.dot(forward);
  471. if (angle < 0.5f) // ~25 degree angle
  472. {
  473. if (level.time >= self->monsterinfo.fire_wait)
  474. self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
  475. else
  476. self->monsterinfo.aiflags |= AI_HOLD_FRAME;
  477. return;
  478. }
  479. }
  480. //-PMM
  481. dir = vectoangles(aim);
  482. AngleVectors(dir, forward, right, up);
  483. r = crandom() * 1000;
  484. u = crandom() * 500;
  485. end = start + (forward * 8192);
  486. end += (right * r);
  487. end += (up * u);
  488. aim = end - start;
  489. aim.normalize();
  490. }
  491. if (self->count <= 1)
  492. {
  493. monster_fire_blaster(self, start, aim, 5, 600, flash_index, EF_BLASTER);
  494. }
  495. else if (self->count <= 3)
  496. {
  497. monster_fire_shotgun(self, start, aim, 2, 1, 1500, 750, 9, flash_index);
  498. // [Paril-KEX] indicates to soldier that he must cock
  499. self->dmg = 1;
  500. }
  501. else
  502. {
  503. // PMM - changed to wait from pausetime to not interfere with dodge code
  504. if (!(self->monsterinfo.aiflags & AI_HOLD_FRAME))
  505. self->monsterinfo.fire_wait = level.time + random_time(300_ms, 1.1_sec);
  506. monster_fire_bullet(self, start, aim, 2, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, flash_index);
  507. if (level.time >= self->monsterinfo.fire_wait)
  508. self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
  509. else
  510. self->monsterinfo.aiflags |= AI_HOLD_FRAME;
  511. }
  512. }
  513. PRETHINK(soldierh_laser_update) (edict_t *laser) -> void
  514. {
  515. edict_t *self = laser->owner;
  516. vec3_t forward, right, up;
  517. vec3_t start;
  518. vec3_t tempvec;
  519. AngleVectors(self->s.angles, forward, right, up);
  520. start = self->s.origin;
  521. tempvec = monster_flash_offset[self->radius_dmg];
  522. start += (forward * tempvec[0]);
  523. start += (right * tempvec[1]);
  524. start += (up * (tempvec[2] + 6));
  525. if (!self->deadflag)
  526. PredictAim(self, self->enemy, start, 0, false, frandom(0.1f, 0.2f), &forward, nullptr);
  527. laser->s.origin = start;
  528. laser->movedir = forward;
  529. gi.linkentity(laser);
  530. dabeam_update(laser, false);
  531. }
  532. // RAFAEL
  533. void soldierh_laserbeam(edict_t *self, int flash_index)
  534. {
  535. self->radius_dmg = flash_index;
  536. monster_fire_dabeam(self, 1, false, soldierh_laser_update);
  537. }
  538. constexpr monster_muzzleflash_id_t ripper_flash[] = { MZ2_SOLDIER_RIPPER_1, MZ2_SOLDIER_RIPPER_2, MZ2_SOLDIER_RIPPER_3, MZ2_SOLDIER_RIPPER_4, MZ2_SOLDIER_RIPPER_5, MZ2_SOLDIER_RIPPER_6, MZ2_SOLDIER_RIPPER_7, MZ2_SOLDIER_RIPPER_8, MZ2_SOLDIER_RIPPER_9 };
  539. constexpr monster_muzzleflash_id_t hyper_flash[] = { MZ2_SOLDIER_HYPERGUN_1, MZ2_SOLDIER_HYPERGUN_2, MZ2_SOLDIER_HYPERGUN_3, MZ2_SOLDIER_HYPERGUN_4, MZ2_SOLDIER_HYPERGUN_5, MZ2_SOLDIER_HYPERGUN_6, MZ2_SOLDIER_HYPERGUN_7, MZ2_SOLDIER_HYPERGUN_8, MZ2_SOLDIER_HYPERGUN_9 };
  540. void soldier_fire_xatrix(edict_t *self, int flash_number, bool angle_limited)
  541. {
  542. vec3_t start;
  543. vec3_t forward, right, up;
  544. vec3_t aim;
  545. vec3_t dir;
  546. vec3_t end;
  547. float r, u;
  548. monster_muzzleflash_id_t flash_index;
  549. vec3_t aim_norm;
  550. float angle;
  551. vec3_t aim_good;
  552. if (self->count < 2)
  553. flash_index = ripper_flash[flash_number]; // ripper
  554. else if (self->count < 4)
  555. flash_index = hyper_flash[flash_number]; // hyperblaster
  556. else
  557. flash_index = machinegun_flash[flash_number]; // laserbeam
  558. AngleVectors(self->s.angles, forward, right, nullptr);
  559. start = M_ProjectFlashSource(self, monster_flash_offset[flash_index], forward, right);
  560. if (flash_number == 5 || flash_number == 6)
  561. {
  562. if (self->spawnflags.has(SPAWNFLAG_MONSTER_DEAD))
  563. return;
  564. aim = forward;
  565. }
  566. else
  567. {
  568. // [Paril-KEX] no enemy = no fire
  569. if ((!self->enemy) || (!self->enemy->inuse))
  570. {
  571. self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
  572. return;
  573. }
  574. // PMM
  575. if (self->monsterinfo.attack_state == AS_BLIND)
  576. end = self->monsterinfo.blind_fire_target;
  577. else
  578. end = self->enemy->s.origin;
  579. // pmm
  580. end[2] += self->enemy->viewheight;
  581. aim = end - start;
  582. aim_good = end;
  583. // PMM
  584. if (angle_limited)
  585. {
  586. aim_norm = aim;
  587. aim_norm.normalize();
  588. angle = aim_norm.dot(forward);
  589. if (angle < 0.5f) // ~25 degree angle
  590. {
  591. if (level.time >= self->monsterinfo.fire_wait)
  592. self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
  593. else
  594. self->monsterinfo.aiflags |= AI_HOLD_FRAME;
  595. return;
  596. }
  597. }
  598. //-PMM
  599. dir = vectoangles(aim);
  600. AngleVectors(dir, forward, right, up);
  601. r = crandom() * 100;
  602. u = crandom() * 50;
  603. end = start + (forward * 8192);
  604. end += (right * r);
  605. end += (up * u);
  606. aim = end - start;
  607. aim.normalize();
  608. }
  609. if (self->count <= 1)
  610. {
  611. // RAFAEL 24-APR-98
  612. // droped the damage from 15 to 5
  613. monster_fire_ionripper(self, start, aim, 5, 600, flash_index, EF_IONRIPPER);
  614. }
  615. else if (self->count <= 3)
  616. {
  617. monster_fire_blueblaster(self, start, aim, 1, 600, flash_index, EF_BLUEHYPERBLASTER);
  618. }
  619. else
  620. {
  621. // PMM - changed to wait from pausetime to not interfere with dodge code
  622. if (!(self->monsterinfo.aiflags & AI_HOLD_FRAME))
  623. self->monsterinfo.fire_wait = level.time + random_time(300_ms, 1.1_sec);
  624. soldierh_laserbeam(self, flash_index);
  625. if (level.time >= self->monsterinfo.fire_wait)
  626. self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
  627. else
  628. self->monsterinfo.aiflags |= AI_HOLD_FRAME;
  629. }
  630. }
  631. // RAFAEL
  632. void soldier_fire(edict_t *self, int flash_number, bool angle_limited)
  633. {
  634. // RAFAEL
  635. if (self->style == 1)
  636. soldier_fire_xatrix(self, flash_number, angle_limited);
  637. else
  638. // RAFAEL
  639. soldier_fire_vanilla(self, flash_number, angle_limited);
  640. }
  641. // ATTACK1 (blaster/shotgun)
  642. void soldier_fire1(edict_t *self)
  643. {
  644. soldier_fire(self, 0, false);
  645. }
  646. void soldier_attack1_refire1(edict_t *self)
  647. {
  648. // [Paril-KEX]
  649. if (self->count <= 0)
  650. self->monsterinfo.nextframe = FRAME_attak110;
  651. // PMM - blindfire
  652. if (self->monsterinfo.aiflags & AI_MANUAL_STEERING)
  653. {
  654. self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
  655. return;
  656. }
  657. // pmm
  658. if (!self->enemy)
  659. return;
  660. if (self->count > 1)
  661. return;
  662. if (self->enemy->health <= 0)
  663. return;
  664. if (((frandom() < 0.5f) && visible(self, self->enemy)) || (range_to(self, self->enemy) <= RANGE_MELEE))
  665. self->monsterinfo.nextframe = FRAME_attak102;
  666. else
  667. self->monsterinfo.nextframe = FRAME_attak110;
  668. }
  669. void soldier_attack1_refire2(edict_t *self)
  670. {
  671. if (!self->enemy)
  672. return;
  673. if (self->count < 2)
  674. return;
  675. if (self->enemy->health <= 0)
  676. return;
  677. if (((self->radius_dmg || frandom() < 0.5f) && visible(self, self->enemy)) || (range_to(self, self->enemy) <= RANGE_MELEE))
  678. {
  679. self->monsterinfo.nextframe = FRAME_attak102;
  680. self->radius_dmg = 0;
  681. }
  682. }
  683. static void soldier_attack1_shotgun_check(edict_t *self)
  684. {
  685. if (self->dmg)
  686. {
  687. self->monsterinfo.nextframe = FRAME_attak106;
  688. // [Paril-KEX] indicate that we should force a refire
  689. self->radius_dmg = 1;
  690. }
  691. }
  692. static void soldier_blind_check(edict_t *self)
  693. {
  694. if (self->monsterinfo.aiflags & AI_MANUAL_STEERING)
  695. {
  696. vec3_t aim = self->monsterinfo.blind_fire_target - self->s.origin;
  697. self->ideal_yaw = vectoyaw(aim);
  698. }
  699. }
  700. mframe_t soldier_frames_attack1[] = {
  701. { ai_charge, 0, soldier_blind_check },
  702. { ai_charge, 0, soldier_attack1_shotgun_check },
  703. { ai_charge, 0, soldier_fire1 },
  704. { ai_charge },
  705. { ai_charge },
  706. { ai_charge, 0, soldier_attack1_refire1 },
  707. { ai_charge },
  708. { ai_charge, 0, soldier_cock },
  709. { ai_charge, 0, soldier_attack1_refire2 },
  710. { ai_charge },
  711. { ai_charge },
  712. { ai_charge }
  713. };
  714. MMOVE_T(soldier_move_attack1) = { FRAME_attak101, FRAME_attak112, soldier_frames_attack1, soldier_run };
  715. // ATTACK1 (blaster/shotgun)
  716. void soldierh_hyper_refire1(edict_t *self)
  717. {
  718. if (!self->enemy)
  719. return;
  720. if (self->count >= 2 && self->count < 4)
  721. {
  722. if (frandom() < 0.7f && visible(self, self->enemy))
  723. self->s.frame = FRAME_attak103;
  724. }
  725. }
  726. void soldierh_hyperripper1(edict_t *self)
  727. {
  728. if (self->count < 4)
  729. soldier_fire(self, 0, false);
  730. }
  731. mframe_t soldierh_frames_attack1[] = {
  732. { ai_charge, 0, soldier_blind_check },
  733. { ai_charge, 0, soldierh_hyper_laser_sound_start },
  734. { ai_charge, 0, soldier_fire1 },
  735. { ai_charge, 0, soldierh_hyperripper1 },
  736. { ai_charge, 0, soldierh_hyperripper1 },
  737. { ai_charge, 0, soldier_attack1_refire1 },
  738. { ai_charge, 0, soldierh_hyper_refire1 },
  739. { ai_charge, 0, soldier_cock },
  740. { ai_charge, 0, soldier_attack1_refire2 },
  741. { ai_charge, 0, soldierh_hyper_laser_sound_end },
  742. { ai_charge },
  743. { ai_charge }
  744. };
  745. MMOVE_T(soldierh_move_attack1) = { FRAME_attak101, FRAME_attak112, soldierh_frames_attack1, soldier_run };
  746. // ATTACK2 (blaster/shotgun)
  747. void soldier_fire2(edict_t *self)
  748. {
  749. soldier_fire(self, 1, false);
  750. }
  751. void soldier_attack2_refire1(edict_t *self)
  752. {
  753. if (self->count <= 0)
  754. self->monsterinfo.nextframe = FRAME_attak216;
  755. if (!self->enemy)
  756. return;
  757. if (self->count > 1)
  758. return;
  759. if (self->enemy->health <= 0)
  760. return;
  761. if (((frandom() < 0.5f) && visible(self, self->enemy)) || (range_to(self, self->enemy) <= RANGE_MELEE))
  762. self->monsterinfo.nextframe = FRAME_attak204;
  763. }
  764. void soldier_attack2_refire2(edict_t *self)
  765. {
  766. if (!self->enemy)
  767. return;
  768. if (self->count < 2)
  769. return;
  770. if (self->enemy->health <= 0)
  771. return;
  772. // RAFAEL
  773. if (((self->radius_dmg || frandom() < 0.5f) && visible(self, self->enemy)) || ((self->style == 0 || self->count < 4) && (range_to(self, self->enemy) <= RANGE_MELEE)))
  774. {
  775. // RAFAEL
  776. self->monsterinfo.nextframe = FRAME_attak204;
  777. self->radius_dmg = 0;
  778. }
  779. }
  780. static void soldier_attack2_shotgun_check(edict_t *self)
  781. {
  782. if (self->dmg)
  783. {
  784. self->monsterinfo.nextframe = FRAME_attak210;
  785. // [Paril-KEX] indicate that we should force a refire
  786. self->radius_dmg = 1;
  787. }
  788. }
  789. mframe_t soldier_frames_attack2[] = {
  790. { ai_charge },
  791. { ai_charge },
  792. { ai_charge, 0, soldier_attack2_shotgun_check },
  793. { ai_charge },
  794. { ai_charge, 0, soldier_fire2 },
  795. { ai_charge },
  796. { ai_charge },
  797. { ai_charge, 0, soldier_attack2_refire1 },
  798. { ai_charge },
  799. { ai_charge },
  800. { ai_charge },
  801. { ai_charge },
  802. { ai_charge, 0, soldier_cock },
  803. { ai_charge },
  804. { ai_charge, 0, soldier_attack2_refire2 },
  805. { ai_charge },
  806. { ai_charge },
  807. { ai_charge }
  808. };
  809. MMOVE_T(soldier_move_attack2) = { FRAME_attak201, FRAME_attak218, soldier_frames_attack2, soldier_run };
  810. // RAFAEL
  811. void soldierh_hyper_refire2(edict_t *self)
  812. {
  813. if (!self->enemy)
  814. return;
  815. if (self->count < 2)
  816. return;
  817. else if (self->count < 4)
  818. {
  819. if (frandom() < 0.7f && visible(self, self->enemy))
  820. self->s.frame = FRAME_attak205;
  821. }
  822. }
  823. void soldierh_hyperripper2(edict_t *self)
  824. {
  825. if (self->count < 4)
  826. soldier_fire(self, 1, false);
  827. }
  828. mframe_t soldierh_frames_attack2[] = {
  829. { ai_charge },
  830. { ai_charge },
  831. { ai_charge },
  832. { ai_charge, 0, soldierh_hyper_laser_sound_start },
  833. { ai_charge, 0, soldier_fire2 },
  834. { ai_charge, 0, soldierh_hyperripper2 },
  835. { ai_charge, 0, soldierh_hyperripper2 },
  836. { ai_charge, 0, soldier_attack2_refire1 },
  837. { ai_charge, 0, soldierh_hyper_refire2 },
  838. { ai_charge },
  839. { ai_charge },
  840. { ai_charge },
  841. { ai_charge, 0, soldier_cock },
  842. { ai_charge },
  843. { ai_charge, 0, soldier_attack2_refire2 },
  844. { ai_charge, 0, soldierh_hyper_laser_sound_end },
  845. { ai_charge },
  846. { ai_charge }
  847. };
  848. MMOVE_T(soldierh_move_attack2) = { FRAME_attak201, FRAME_attak218, soldierh_frames_attack2, soldier_run };
  849. // RAFAEL
  850. // ATTACK3 (duck and shoot)
  851. void soldier_fire3(edict_t *self)
  852. {
  853. soldier_fire(self, 2, false);
  854. }
  855. void soldierh_hyperripper3(edict_t *self)
  856. {
  857. if (self->s.skinnum >= 6 && self->count < 4)
  858. soldier_fire(self, 2, false);
  859. }
  860. void soldier_attack3_refire(edict_t *self)
  861. {
  862. if (self->dmg)
  863. monster_duck_hold(self);
  864. else if ((level.time + 400_ms) < self->monsterinfo.duck_wait_time)
  865. self->monsterinfo.nextframe = FRAME_attak303;
  866. }
  867. mframe_t soldier_frames_attack3[] = {
  868. { ai_charge, 0, monster_duck_down },
  869. { ai_charge, 0, soldierh_hyper_laser_sound_start },
  870. { ai_charge, 0, soldier_fire3 },
  871. { ai_charge, 0, soldierh_hyperripper3 },
  872. { ai_charge, 0, soldierh_hyperripper3 },
  873. { ai_charge, 0, soldier_attack3_refire },
  874. { ai_charge, 0, monster_duck_up },
  875. { ai_charge, 0, soldierh_hyper_laser_sound_end },
  876. { ai_charge }
  877. };
  878. MMOVE_T(soldier_move_attack3) = { FRAME_attak301, FRAME_attak309, soldier_frames_attack3, soldier_run };
  879. // ATTACK4 (machinegun)
  880. void soldier_fire4(edict_t *self)
  881. {
  882. soldier_fire(self, 3, false);
  883. }
  884. mframe_t soldier_frames_attack4[] = {
  885. { ai_charge },
  886. { ai_charge, 0, soldierh_hyper_laser_sound_start },
  887. { ai_charge, 0, soldier_fire4 },
  888. { ai_charge, 0, soldierh_hyper_laser_sound_end },
  889. { ai_charge },
  890. { ai_charge }
  891. };
  892. MMOVE_T(soldier_move_attack4) = { FRAME_attak401, FRAME_attak406, soldier_frames_attack4, soldier_run };
  893. // ATTACK6 (run & shoot)
  894. void soldier_fire8(edict_t *self)
  895. {
  896. soldier_fire(self, 7, true);
  897. }
  898. void soldier_attack6_refire1(edict_t *self)
  899. {
  900. // PMM - make sure dodge & charge bits are cleared
  901. monster_done_dodge(self);
  902. soldier_stop_charge(self);
  903. if (!self->enemy)
  904. return;
  905. if (self->count > 1)
  906. return;
  907. if (self->enemy->health <= 0 ||
  908. range_to(self, self->enemy) < RANGE_NEAR ||
  909. !visible(self, self->enemy)) // don't endlessly run into walls
  910. {
  911. soldier_run(self);
  912. return;
  913. }
  914. if (frandom() < 0.25f)
  915. self->monsterinfo.nextframe = FRAME_runs03;
  916. else
  917. soldier_run(self);
  918. }
  919. void soldier_attack6_refire2(edict_t *self)
  920. {
  921. // PMM - make sure dodge & charge bits are cleared
  922. monster_done_dodge(self);
  923. soldier_stop_charge(self);
  924. if (!self->enemy || self->count <= 0)
  925. return;
  926. if (self->enemy->health <= 0 ||
  927. (!self->radius_dmg && range_to(self, self->enemy) < RANGE_NEAR) ||
  928. !visible(self, self->enemy)) // don't endlessly run into walls
  929. {
  930. soldierh_hyper_laser_sound_end(self);
  931. return;
  932. }
  933. if (self->radius_dmg || frandom() < 0.25f)
  934. {
  935. self->monsterinfo.nextframe = FRAME_runs03;
  936. self->radius_dmg = 0;
  937. }
  938. }
  939. static void soldier_attack6_shotgun_check(edict_t *self)
  940. {
  941. if (self->dmg)
  942. {
  943. self->monsterinfo.nextframe = FRAME_runs09;
  944. // [Paril-KEX] indicate that we should force a refire
  945. self->radius_dmg = 1;
  946. }
  947. }
  948. void soldierh_hyperripper8(edict_t *self)
  949. {
  950. if (self->s.skinnum >= 6 && self->count < 4)
  951. soldier_fire(self, 7, true);
  952. }
  953. mframe_t soldier_frames_attack6[] = {
  954. { ai_run, 10, soldier_start_charge },
  955. { ai_run, 4, soldier_attack6_shotgun_check },
  956. { ai_run, 12, soldierh_hyper_laser_sound_start },
  957. { ai_run, 11, [](edict_t *self) { soldier_fire8(self); monster_footstep(self); } },
  958. { ai_run, 13, [](edict_t *self ) { soldierh_hyperripper8(self); monster_done_dodge(self); } },
  959. { ai_run, 18, soldierh_hyperripper8 },
  960. { ai_run, 15, monster_footstep },
  961. { ai_run, 14, soldier_attack6_refire1 },
  962. { ai_run, 11 },
  963. { ai_run, 8, monster_footstep },
  964. { ai_run, 11, soldier_cock },
  965. { ai_run, 12 },
  966. { ai_run, 12, monster_footstep },
  967. { ai_run, 17, soldier_attack6_refire2 }
  968. };
  969. MMOVE_T(soldier_move_attack6) = { FRAME_runs01, FRAME_runs14, soldier_frames_attack6, soldier_run, 0.65f };
  970. MONSTERINFO_ATTACK(soldier_attack) (edict_t *self) -> void
  971. {
  972. float r, chance;
  973. monster_done_dodge(self);
  974. // PMM - blindfire!
  975. if (self->monsterinfo.attack_state == AS_BLIND)
  976. {
  977. // setup shot probabilities
  978. if (self->monsterinfo.blind_fire_delay < 1_sec)
  979. chance = 1.0f;
  980. else if (self->monsterinfo.blind_fire_delay < 7.5_sec)
  981. chance = 0.4f;
  982. else
  983. chance = 0.1f;
  984. r = frandom();
  985. // minimum of 4.1 seconds, plus 0-3, after the shots are done
  986. self->monsterinfo.blind_fire_delay += 4.1_sec + random_time(3_sec);
  987. // don't shoot at the origin
  988. if (!self->monsterinfo.blind_fire_target)
  989. return;
  990. // don't shoot if the dice say not to
  991. if (r > chance)
  992. return;
  993. // turn on manual steering to signal both manual steering and blindfire
  994. self->monsterinfo.aiflags |= AI_MANUAL_STEERING;
  995. // RAFAEL
  996. if (self->style == 1)
  997. M_SetAnimation(self, &soldierh_move_attack1);
  998. else
  999. // RAFAEL
  1000. M_SetAnimation(self, &soldier_move_attack1);
  1001. self->monsterinfo.attack_finished = level.time + random_time(1.5_sec, 2.5_sec);
  1002. return;
  1003. }
  1004. // pmm
  1005. // PMM - added this so the soldiers now run toward you and shoot instead of just stopping and shooting
  1006. r = frandom();
  1007. // nb: run-shoot not limited by `M_CheckClearShot` since they will be far enough
  1008. // away that it doesn't matter
  1009. if ((!(self->monsterinfo.aiflags & (AI_BLOCKED | AI_STAND_GROUND))) &&
  1010. (r < 0.25f &&
  1011. (self->count <= 3)) &&
  1012. (range_to(self, self->enemy) >= (RANGE_NEAR * 0.5f)))
  1013. {
  1014. M_SetAnimation(self, &soldier_move_attack6);
  1015. }
  1016. else
  1017. {
  1018. if (self->count < 4)
  1019. {
  1020. bool attack1_possible;
  1021. // [Paril-KEX] shotgun guard only uses attack2 at close range
  1022. if ((!self->style && self->count >= 2 && self->count <= 3) && range_to(self, self->enemy) <= (RANGE_NEAR * 0.65f))
  1023. attack1_possible = false;
  1024. else
  1025. attack1_possible = M_CheckClearShot(self, monster_flash_offset[MZ2_SOLDIER_BLASTER_1]);
  1026. bool attack2_possible = M_CheckClearShot(self, monster_flash_offset[MZ2_SOLDIER_BLASTER_2]);
  1027. if (attack1_possible && (!attack2_possible || frandom() < 0.5f))
  1028. {
  1029. // RAFAEL
  1030. if (self->style == 1)
  1031. M_SetAnimation(self, &soldierh_move_attack1);
  1032. else
  1033. // RAFAEL
  1034. M_SetAnimation(self, &soldier_move_attack1);
  1035. }
  1036. else if (attack2_possible)
  1037. {
  1038. // RAFAEL
  1039. if (self->style == 1)
  1040. M_SetAnimation(self, &soldierh_move_attack2);
  1041. else
  1042. // RAFAEL
  1043. M_SetAnimation(self, &soldier_move_attack2);
  1044. }
  1045. }
  1046. else if (M_CheckClearShot(self, monster_flash_offset[MZ2_SOLDIER_MACHINEGUN_4]))
  1047. {
  1048. M_SetAnimation(self, &soldier_move_attack4);
  1049. }
  1050. }
  1051. }
  1052. //
  1053. // SIGHT
  1054. //
  1055. MONSTERINFO_SIGHT(soldier_sight) (edict_t *self, edict_t *other) -> void
  1056. {
  1057. if (frandom() < 0.5f)
  1058. gi.sound(self, CHAN_VOICE, sound_sight1, 1, ATTN_NORM, 0);
  1059. else
  1060. gi.sound(self, CHAN_VOICE, sound_sight2, 1, ATTN_NORM, 0);
  1061. if (self->enemy && (range_to(self, self->enemy) >= RANGE_NEAR) &&
  1062. visible(self, self->enemy) // Paril: don't run-shoot if we can't see them
  1063. )
  1064. {
  1065. // RAFAEL
  1066. if (self->style == 1 || frandom() > 0.75f)
  1067. // RAFAEL
  1068. {
  1069. // RAFAEL + legacy bug fix
  1070. // don't use run+shoot for machinegun/laser because
  1071. // the animation is a bit weird
  1072. if (self->count < 4)
  1073. M_SetAnimation(self, &soldier_move_attack6);
  1074. else if (M_CheckClearShot(self, monster_flash_offset[MZ2_SOLDIER_MACHINEGUN_4]))
  1075. // RAFAEL
  1076. M_SetAnimation(self, &soldier_move_attack4);
  1077. }
  1078. }
  1079. }
  1080. //
  1081. // DUCK
  1082. //
  1083. mframe_t soldier_frames_duck[] = {
  1084. { ai_move, 5, monster_duck_down },
  1085. { ai_move, -1, monster_duck_hold },
  1086. { ai_move, 1 },
  1087. { ai_move, 0, monster_duck_up },
  1088. { ai_move, 5 }
  1089. };
  1090. MMOVE_T(soldier_move_duck) = { FRAME_duck01, FRAME_duck05, soldier_frames_duck, soldier_run };
  1091. extern const mmove_t soldier_move_trip;
  1092. static void soldier_stand_up(edict_t *self)
  1093. {
  1094. soldierh_hyper_laser_sound_end(self);
  1095. M_SetAnimation(self, &soldier_move_trip, false);
  1096. self->monsterinfo.nextframe = FRAME_runt08;
  1097. }
  1098. static bool soldier_prone_shoot_ok(edict_t *self)
  1099. {
  1100. if (!self->enemy || !self->enemy->inuse)
  1101. return false;
  1102. vec3_t fwd;
  1103. AngleVectors(self->s.angles, fwd, nullptr, nullptr);
  1104. vec3_t diff = self->enemy->s.origin - self->s.origin;
  1105. diff.z = 0;
  1106. diff.normalize();
  1107. float v = fwd.dot(diff);
  1108. if (v < 0.80f)
  1109. return false;
  1110. return true;
  1111. }
  1112. static void ai_soldier_move(edict_t *self, float dist)
  1113. {
  1114. ai_move(self, dist);
  1115. if (!soldier_prone_shoot_ok(self))
  1116. {
  1117. soldier_stand_up(self);
  1118. return;
  1119. }
  1120. }
  1121. void soldier_fire5(edict_t *self)
  1122. {
  1123. soldier_fire(self, 8, true);
  1124. }
  1125. void soldierh_hyperripper5(edict_t *self)
  1126. {
  1127. if (self->style && self->count < 4)
  1128. soldier_fire(self, 8, true);
  1129. }
  1130. mframe_t soldier_frames_attack5[] = {
  1131. { ai_move, 18, monster_duck_down },
  1132. { ai_move, 11, monster_footstep },
  1133. { ai_move, 0, monster_footstep },
  1134. { ai_soldier_move },
  1135. { ai_soldier_move, 0, soldierh_hyper_laser_sound_start },
  1136. { ai_soldier_move, 0, soldier_fire5 },
  1137. { ai_soldier_move, 0, soldierh_hyperripper5 },
  1138. { ai_soldier_move, 0, soldierh_hyperripper5 },
  1139. };
  1140. MMOVE_T(soldier_move_attack5) = { FRAME_attak501, FRAME_attak508, soldier_frames_attack5, soldier_stand_up };
  1141. static void monster_check_prone(edict_t *self)
  1142. {
  1143. // we're a shotgun guard waiting to cock
  1144. if (!self->style && self->count >= 2 && self->count <= 3 && self->dmg)
  1145. return;
  1146. // not going to shoot at this angle
  1147. if (!soldier_prone_shoot_ok(self))
  1148. return;
  1149. M_SetAnimation(self, &soldier_move_attack5, false);
  1150. }
  1151. mframe_t soldier_frames_trip[] = {
  1152. { ai_move, 10 },
  1153. { ai_move, 2, monster_check_prone },
  1154. { ai_move, 18, monster_duck_down },
  1155. { ai_move, 11, monster_footstep },
  1156. { ai_move, 9 },
  1157. { ai_move, -11, monster_footstep },
  1158. { ai_move, -2 },
  1159. { ai_move, 0 },
  1160. { ai_move, 6 },
  1161. { ai_move, -5 },
  1162. { ai_move, 0 },
  1163. { ai_move, 1 },
  1164. { ai_move, 0, monster_footstep },
  1165. { ai_move, 0, monster_duck_up },
  1166. { ai_move, 3 },
  1167. { ai_move, 2, monster_footstep },
  1168. { ai_move, -1 },
  1169. { ai_move, 2 },
  1170. { ai_move, 0 },
  1171. };
  1172. MMOVE_T(soldier_move_trip) = { FRAME_runt01, FRAME_runt19, soldier_frames_trip, soldier_run };
  1173. // pmm - blocking code
  1174. MONSTERINFO_BLOCKED(soldier_blocked) (edict_t *self, float dist) -> bool
  1175. {
  1176. // don't do anything if you're dodging
  1177. if ((self->monsterinfo.aiflags & AI_DODGING) || (self->monsterinfo.aiflags & AI_DUCKED))
  1178. return false;
  1179. return blocked_checkplat(self, dist);
  1180. }
  1181. //
  1182. // DEATH
  1183. //
  1184. void soldier_fire6(edict_t *self)
  1185. {
  1186. soldier_fire(self, 5, false);
  1187. if (self->dmg)
  1188. self->monsterinfo.nextframe = FRAME_death126;
  1189. }
  1190. void soldier_fire7(edict_t *self)
  1191. {
  1192. soldier_fire(self, 6, false);
  1193. }
  1194. void soldier_dead(edict_t *self)
  1195. {
  1196. self->mins = { -16, -16, -24 };
  1197. self->maxs = { 16, 16, -8 };
  1198. monster_dead(self);
  1199. }
  1200. static void soldier_death_shrink(edict_t *self)
  1201. {
  1202. self->svflags |= SVF_DEADMONSTER;
  1203. self->maxs[2] = 0;
  1204. gi.linkentity(self);
  1205. }
  1206. mframe_t soldier_frames_death1[] = {
  1207. { ai_move },
  1208. { ai_move, -10 },
  1209. { ai_move, -10 },
  1210. { ai_move, -10, soldier_death_shrink },
  1211. { ai_move, -5 },
  1212. { ai_move },
  1213. { ai_move },
  1214. { ai_move },
  1215. { ai_move },
  1216. { ai_move },
  1217. { ai_move },
  1218. { ai_move },
  1219. { ai_move },
  1220. { ai_move },
  1221. { ai_move },
  1222. { ai_move },
  1223. { ai_move },
  1224. { ai_move },
  1225. { ai_move },
  1226. { ai_move },
  1227. { ai_move, 0, soldierh_hyper_laser_sound_start },
  1228. { ai_move, 0, soldier_fire6 },
  1229. { ai_move },
  1230. { ai_move },
  1231. { ai_move, 0, soldier_fire7 },
  1232. { ai_move, 0, soldierh_hyper_laser_sound_end },
  1233. { ai_move },
  1234. { ai_move },
  1235. { ai_move },
  1236. { ai_move },
  1237. { ai_move },
  1238. { ai_move },
  1239. { ai_move },
  1240. { ai_move },
  1241. { ai_move },
  1242. { ai_move }
  1243. };
  1244. MMOVE_T(soldier_move_death1) = { FRAME_death101, FRAME_death136, soldier_frames_death1, soldier_dead };
  1245. mframe_t soldier_frames_death2[] = {
  1246. { ai_move, -5 },
  1247. { ai_move, -5 },
  1248. { ai_move, -5 },
  1249. { ai_move, 0, soldier_death_shrink },
  1250. { ai_move },
  1251. { ai_move },
  1252. { ai_move },
  1253. { ai_move },
  1254. { ai_move },
  1255. { ai_move },
  1256. { ai_move },
  1257. { ai_move },
  1258. { ai_move },
  1259. { ai_move },
  1260. { ai_move },
  1261. { ai_move },
  1262. { ai_move },
  1263. { ai_move },
  1264. { ai_move },
  1265. { ai_move },
  1266. { ai_move },
  1267. { ai_move },
  1268. { ai_move },
  1269. { ai_move },
  1270. { ai_move },
  1271. { ai_move },
  1272. { ai_move },
  1273. { ai_move },
  1274. { ai_move },
  1275. { ai_move },
  1276. { ai_move },
  1277. { ai_move },
  1278. { ai_move },
  1279. { ai_move },
  1280. { ai_move }
  1281. };
  1282. MMOVE_T(soldier_move_death2) = { FRAME_death201, FRAME_death235, soldier_frames_death2, soldier_dead };
  1283. mframe_t soldier_frames_death3[] = {
  1284. { ai_move, -5 },
  1285. { ai_move, -5 },
  1286. { ai_move, -5 },
  1287. { ai_move, 0, soldier_death_shrink },
  1288. { ai_move },
  1289. { ai_move },
  1290. { ai_move },
  1291. { ai_move },
  1292. { ai_move },
  1293. { ai_move },
  1294. { ai_move },
  1295. { ai_move },
  1296. { ai_move },
  1297. { ai_move },
  1298. { ai_move },
  1299. { ai_move },
  1300. { ai_move },
  1301. { ai_move },
  1302. { ai_move },
  1303. { ai_move },
  1304. { ai_move },
  1305. { ai_move },
  1306. { ai_move },
  1307. { ai_move },
  1308. { ai_move },
  1309. { ai_move },
  1310. { ai_move },
  1311. { ai_move },
  1312. { ai_move },
  1313. { ai_move },
  1314. { ai_move },
  1315. { ai_move },
  1316. { ai_move },
  1317. { ai_move },
  1318. { ai_move },
  1319. { ai_move },
  1320. { ai_move },
  1321. { ai_move },
  1322. { ai_move },
  1323. { ai_move },
  1324. { ai_move },
  1325. { ai_move },
  1326. { ai_move },
  1327. { ai_move },
  1328. { ai_move },
  1329. };
  1330. MMOVE_T(soldier_move_death3) = { FRAME_death301, FRAME_death345, soldier_frames_death3, soldier_dead };
  1331. mframe_t soldier_frames_death4[] = {
  1332. { ai_move },
  1333. { ai_move },
  1334. { ai_move, 1.5f },
  1335. { ai_move, 2.5f },
  1336. { ai_move, -1.5f },
  1337. { ai_move },
  1338. { ai_move },
  1339. { ai_move },
  1340. { ai_move, -0.5f },
  1341. { ai_move },
  1342. { ai_move },
  1343. { ai_move, 4.0f },
  1344. { ai_move, 4.0f },
  1345. { ai_move, 8.0f, soldier_death_shrink },
  1346. { ai_move, 8.0f },
  1347. { ai_move },
  1348. { ai_move },
  1349. { ai_move },
  1350. { ai_move },
  1351. { ai_move },
  1352. { ai_move },
  1353. { ai_move },
  1354. { ai_move },
  1355. { ai_move },
  1356. { ai_move },
  1357. { ai_move },
  1358. { ai_move },
  1359. { ai_move },
  1360. { ai_move },
  1361. { ai_move },
  1362. { ai_move },
  1363. { ai_move },
  1364. { ai_move },
  1365. { ai_move },
  1366. { ai_move },
  1367. { ai_move },
  1368. { ai_move },
  1369. { ai_move },
  1370. { ai_move },
  1371. { ai_move },
  1372. { ai_move },
  1373. { ai_move },
  1374. { ai_move },
  1375. { ai_move },
  1376. { ai_move },
  1377. { ai_move },
  1378. { ai_move },
  1379. { ai_move },
  1380. { ai_move },
  1381. { ai_move, 5.5f },
  1382. { ai_move, 2.5f },
  1383. { ai_move, -2.0f },
  1384. { ai_move, -2.0f }
  1385. };
  1386. MMOVE_T(soldier_move_death4) = { FRAME_death401, FRAME_death453, soldier_frames_death4, soldier_dead };
  1387. mframe_t soldier_frames_death5[] = {
  1388. { ai_move, -5 },
  1389. { ai_move, -5 },
  1390. { ai_move, -5 },
  1391. { ai_move },
  1392. { ai_move },
  1393. { ai_move, 0, soldier_death_shrink },
  1394. { ai_move },
  1395. { ai_move },
  1396. { ai_move },
  1397. { ai_move },
  1398. { ai_move },
  1399. { ai_move },
  1400. { ai_move },
  1401. { ai_move },
  1402. { ai_move },
  1403. { ai_move },
  1404. { ai_move },
  1405. { ai_move },
  1406. { ai_move },
  1407. { ai_move },
  1408. { ai_move },
  1409. { ai_move },
  1410. { ai_move },
  1411. { ai_move }
  1412. };
  1413. MMOVE_T(soldier_move_death5) = { FRAME_death501, FRAME_death524, soldier_frames_death5, soldier_dead };
  1414. mframe_t soldier_frames_death6[] = {
  1415. { ai_move },
  1416. { ai_move },
  1417. { ai_move },
  1418. { ai_move },
  1419. { ai_move },
  1420. { ai_move, 0, soldier_death_shrink },
  1421. { ai_move },
  1422. { ai_move },
  1423. { ai_move },
  1424. { ai_move }
  1425. };
  1426. MMOVE_T(soldier_move_death6) = { FRAME_death601, FRAME_death610, soldier_frames_death6, soldier_dead };
  1427. DIE(soldier_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void
  1428. {
  1429. int n;
  1430. soldierh_hyper_laser_sound_end(self);
  1431. // check for gib
  1432. if (M_CheckGib(self, mod))
  1433. {
  1434. gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0);
  1435. self->s.skinnum /= 2;
  1436. if (self->beam)
  1437. {
  1438. G_FreeEdict(self->beam);
  1439. self->beam = nullptr;
  1440. }
  1441. ThrowGibs(self, damage, {
  1442. { 3, "models/objects/gibs/sm_meat/tris.md2" },
  1443. { "models/objects/gibs/bone2/tris.md2" },
  1444. { "models/objects/gibs/bone/tris.md2" },
  1445. { "models/monsters/soldier/gibs/arm.md2", GIB_SKINNED },
  1446. { "models/monsters/soldier/gibs/gun.md2", GIB_SKINNED | GIB_UPRIGHT },
  1447. { "models/monsters/soldier/gibs/chest.md2", GIB_SKINNED },
  1448. { "models/monsters/soldier/gibs/head.md2", GIB_HEAD | GIB_SKINNED }
  1449. });
  1450. self->deadflag = true;
  1451. return;
  1452. }
  1453. if (self->deadflag)
  1454. return;
  1455. // regular death
  1456. self->deadflag = true;
  1457. self->takedamage = true;
  1458. n = self->count | 1;
  1459. if (n == 1)
  1460. gi.sound(self, CHAN_VOICE, sound_death_light, 1, ATTN_NORM, 0);
  1461. else if (n == 3)
  1462. gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0);
  1463. else // (n == 5)
  1464. gi.sound(self, CHAN_VOICE, sound_death_ss, 1, ATTN_NORM, 0);
  1465. if (fabsf((self->s.origin[2] + self->viewheight) - point[2]) <= 4 &&
  1466. self->velocity.z < 65.f)
  1467. {
  1468. // head shot
  1469. M_SetAnimation(self, &soldier_move_death3);
  1470. return;
  1471. }
  1472. // if we die while on the ground, do a quicker death4
  1473. if (self->monsterinfo.active_move == &soldier_move_trip ||
  1474. self->monsterinfo.active_move == &soldier_move_attack5)
  1475. {
  1476. M_SetAnimation(self, &soldier_move_death4);
  1477. self->monsterinfo.nextframe = FRAME_death413;
  1478. soldier_death_shrink(self);
  1479. return;
  1480. }
  1481. // only do the spin-death if we have enough velocity to justify it
  1482. if (self->velocity.z > 65.f || self->velocity.length() > 150.f)
  1483. n = irandom(5);
  1484. else
  1485. n = irandom(4);
  1486. if (n == 0)
  1487. M_SetAnimation(self, &soldier_move_death1);
  1488. else if (n == 1)
  1489. M_SetAnimation(self, &soldier_move_death2);
  1490. else if (n == 2)
  1491. M_SetAnimation(self, &soldier_move_death4);
  1492. else if (n == 3)
  1493. M_SetAnimation(self, &soldier_move_death5);
  1494. else
  1495. M_SetAnimation(self, &soldier_move_death6);
  1496. }
  1497. //
  1498. // NEW DODGE CODE
  1499. //
  1500. MONSTERINFO_SIDESTEP(soldier_sidestep) (edict_t *self) -> bool
  1501. {
  1502. // don't sidestep during trip or up pain
  1503. if (self->monsterinfo.active_move == &soldier_move_trip ||
  1504. self->monsterinfo.active_move == &soldier_move_attack5 ||
  1505. self->monsterinfo.active_move == &soldier_move_pain4)
  1506. return false;
  1507. if (self->count <= 3)
  1508. {
  1509. if (self->monsterinfo.active_move != &soldier_move_attack6)
  1510. {
  1511. M_SetAnimation(self, &soldier_move_attack6);
  1512. soldierh_hyper_laser_sound_end(self);
  1513. }
  1514. }
  1515. else
  1516. {
  1517. if (self->monsterinfo.active_move != &soldier_move_start_run &&
  1518. self->monsterinfo.active_move != &soldier_move_run)
  1519. {
  1520. M_SetAnimation(self, &soldier_move_start_run);
  1521. soldierh_hyper_laser_sound_end(self);
  1522. }
  1523. }
  1524. return true;
  1525. }
  1526. MONSTERINFO_DUCK(soldier_duck) (edict_t *self, gtime_t eta) -> bool
  1527. {
  1528. self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
  1529. if (self->monsterinfo.active_move == &soldier_move_attack6)
  1530. {
  1531. M_SetAnimation(self, &soldier_move_trip);
  1532. }
  1533. else if (self->dmg || brandom())
  1534. {
  1535. M_SetAnimation(self, &soldier_move_duck);
  1536. }
  1537. else
  1538. {
  1539. M_SetAnimation(self, &soldier_move_attack3);
  1540. }
  1541. soldierh_hyper_laser_sound_end(self);
  1542. return true;
  1543. }
  1544. //=========
  1545. // ROGUE
  1546. void soldier_blind(edict_t *self);
  1547. mframe_t soldier_frames_blind[] = {
  1548. { ai_move, 0, soldier_idle },
  1549. { ai_move },
  1550. { ai_move },
  1551. { ai_move },
  1552. { ai_move },
  1553. { ai_move },
  1554. { ai_move },
  1555. { ai_move },
  1556. { ai_move },
  1557. { ai_move },
  1558. { ai_move },
  1559. { ai_move },
  1560. { ai_move },
  1561. { ai_move },
  1562. { ai_move },
  1563. { ai_move },
  1564. { ai_move },
  1565. { ai_move },
  1566. { ai_move },
  1567. { ai_move },
  1568. { ai_move },
  1569. { ai_move },
  1570. { ai_move },
  1571. { ai_move },
  1572. { ai_move },
  1573. { ai_move },
  1574. { ai_move },
  1575. { ai_move },
  1576. { ai_move },
  1577. { ai_move }
  1578. };
  1579. MMOVE_T(soldier_move_blind) = { FRAME_stand101, FRAME_stand130, soldier_frames_blind, soldier_blind };
  1580. MONSTERINFO_STAND(soldier_blind) (edict_t *self) -> void
  1581. {
  1582. M_SetAnimation(self, &soldier_move_blind);
  1583. }
  1584. // ROGUE
  1585. //=========
  1586. //
  1587. // SPAWN
  1588. //
  1589. constexpr spawnflags_t SPAWNFLAG_SOLDIER_BLIND = 8_spawnflag;
  1590. void SP_monster_soldier_x(edict_t *self)
  1591. {
  1592. self->s.modelindex = gi.modelindex("models/monsters/soldier/tris.md2");
  1593. self->monsterinfo.scale = MODEL_SCALE;
  1594. self->mins = { -16, -16, -24 };
  1595. self->maxs = { 16, 16, 32 };
  1596. self->movetype = MOVETYPE_STEP;
  1597. self->solid = SOLID_BBOX;
  1598. sound_idle.assign("soldier/solidle1.wav");
  1599. sound_sight1.assign("soldier/solsght1.wav");
  1600. sound_sight2.assign("soldier/solsrch1.wav");
  1601. sound_cock.assign("infantry/infatck3.wav");
  1602. gi.modelindex("models/monsters/soldier/gibs/head.md2");
  1603. gi.modelindex("models/monsters/soldier/gibs/gun.md2");
  1604. gi.modelindex("models/monsters/soldier/gibs/arm.md2");
  1605. gi.modelindex("models/monsters/soldier/gibs/chest.md2");
  1606. self->mass = 100;
  1607. self->pain = soldier_pain;
  1608. self->die = soldier_die;
  1609. self->monsterinfo.stand = soldier_stand;
  1610. self->monsterinfo.walk = soldier_walk;
  1611. self->monsterinfo.run = soldier_run;
  1612. self->monsterinfo.dodge = M_MonsterDodge;
  1613. self->monsterinfo.attack = soldier_attack;
  1614. self->monsterinfo.melee = nullptr;
  1615. self->monsterinfo.sight = soldier_sight;
  1616. self->monsterinfo.setskin = soldier_setskin;
  1617. //=====
  1618. // ROGUE
  1619. self->monsterinfo.blocked = soldier_blocked;
  1620. self->monsterinfo.duck = soldier_duck;
  1621. self->monsterinfo.unduck = monster_duck_up;
  1622. self->monsterinfo.sidestep = soldier_sidestep;
  1623. if (self->spawnflags.has(SPAWNFLAG_SOLDIER_BLIND)) // blind
  1624. self->monsterinfo.stand = soldier_blind;
  1625. // ROGUE
  1626. //=====
  1627. gi.linkentity(self);
  1628. self->monsterinfo.stand(self);
  1629. walkmonster_start(self);
  1630. }
  1631. void SP_monster_soldier_vanilla(edict_t *self)
  1632. {
  1633. SP_monster_soldier_x(self);
  1634. }
  1635. /*QUAKED monster_soldier_light (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight
  1636. */
  1637. void SP_monster_soldier_light(edict_t *self)
  1638. {
  1639. if ( !M_AllowSpawn( self ) ) {
  1640. G_FreeEdict( self );
  1641. return;
  1642. }
  1643. SP_monster_soldier_x(self);
  1644. sound_pain_light.assign("soldier/solpain2.wav");
  1645. sound_death_light.assign("soldier/soldeth2.wav");
  1646. gi.modelindex("models/objects/laser/tris.md2");
  1647. gi.soundindex("misc/lasfly.wav");
  1648. gi.soundindex("soldier/solatck2.wav");
  1649. self->s.skinnum = 0;
  1650. self->count = self->s.skinnum;
  1651. self->health = self->max_health = 20 * st.health_multiplier;
  1652. self->gib_health = -30;
  1653. // PMM - blindfire
  1654. self->monsterinfo.blindfire = true;
  1655. }
  1656. /*QUAKED monster_soldier (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight
  1657. */
  1658. void SP_monster_soldier(edict_t *self)
  1659. {
  1660. if( !M_AllowSpawn( self ) ) {
  1661. G_FreeEdict( self );
  1662. return;
  1663. }
  1664. SP_monster_soldier_x(self);
  1665. sound_pain.assign("soldier/solpain1.wav");
  1666. sound_death.assign("soldier/soldeth1.wav");
  1667. gi.soundindex("soldier/solatck1.wav");
  1668. self->s.skinnum = 2;
  1669. self->count = self->s.skinnum;
  1670. self->health = self->max_health = 30 * st.health_multiplier;
  1671. self->gib_health = -30;
  1672. }
  1673. /*QUAKED monster_soldier_ss (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight
  1674. */
  1675. void SP_monster_soldier_ss(edict_t *self)
  1676. {
  1677. if ( !M_AllowSpawn( self ) ) {
  1678. G_FreeEdict( self );
  1679. return;
  1680. }
  1681. SP_monster_soldier_x(self);
  1682. sound_pain_ss.assign("soldier/solpain3.wav");
  1683. sound_death_ss.assign("soldier/soldeth3.wav");
  1684. gi.soundindex("soldier/solatck3.wav");
  1685. self->s.skinnum = 4;
  1686. self->count = self->s.skinnum;
  1687. self->health = self->max_health = 40 * st.health_multiplier;
  1688. self->gib_health = -30;
  1689. }
  1690. //
  1691. // SPAWN
  1692. //
  1693. void SP_monster_soldier_h(edict_t *self)
  1694. {
  1695. SP_monster_soldier_x(self);
  1696. self->style = 1;
  1697. }
  1698. /*QUAKED monster_soldier_ripper (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight
  1699. */
  1700. void SP_monster_soldier_ripper(edict_t *self)
  1701. {
  1702. if ( !M_AllowSpawn( self ) ) {
  1703. G_FreeEdict( self );
  1704. return;
  1705. }
  1706. SP_monster_soldier_h(self);
  1707. sound_pain_light.assign("soldier/solpain2.wav");
  1708. sound_death_light.assign("soldier/soldeth2.wav");
  1709. gi.modelindex("models/objects/boomrang/tris.md2");
  1710. gi.soundindex("misc/lasfly.wav");
  1711. gi.soundindex("soldier/solatck2.wav");
  1712. self->s.skinnum = 6;
  1713. self->count = self->s.skinnum - 6;
  1714. self->health = self->max_health = 50 * st.health_multiplier;
  1715. self->gib_health = -30;
  1716. // PMM - blindfire
  1717. self->monsterinfo.blindfire = true;
  1718. }
  1719. /*QUAKED monster_soldier_hypergun (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight
  1720. */
  1721. void SP_monster_soldier_hypergun(edict_t *self)
  1722. {
  1723. if ( !M_AllowSpawn( self ) ) {
  1724. G_FreeEdict( self );
  1725. return;
  1726. }
  1727. SP_monster_soldier_h(self);
  1728. gi.modelindex("models/objects/laser/tris.md2");
  1729. sound_pain.assign("soldier/solpain1.wav");
  1730. sound_death.assign("soldier/soldeth1.wav");
  1731. gi.soundindex("soldier/solatck1.wav");
  1732. gi.soundindex("weapons/hyprbd1a.wav");
  1733. gi.soundindex("weapons/hyprbl1a.wav");
  1734. self->s.skinnum = 8;
  1735. self->count = self->s.skinnum - 6;
  1736. self->health = self->max_health = 60 * st.health_multiplier;
  1737. self->gib_health = -30;
  1738. // PMM - blindfire
  1739. self->monsterinfo.blindfire = true;
  1740. }
  1741. /*QUAKED monster_soldier_lasergun (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight
  1742. */
  1743. void SP_monster_soldier_lasergun(edict_t *self)
  1744. {
  1745. if ( !M_AllowSpawn( self ) ) {
  1746. G_FreeEdict( self );
  1747. return;
  1748. }
  1749. SP_monster_soldier_h(self);
  1750. sound_pain_ss.assign("soldier/solpain3.wav");
  1751. sound_death_ss.assign("soldier/soldeth3.wav");
  1752. gi.soundindex("soldier/solatck3.wav");
  1753. self->s.skinnum = 10;
  1754. self->count = self->s.skinnum - 6;
  1755. self->health = self->max_health = 70 * st.health_multiplier;
  1756. self->gib_health = -30;
  1757. }
  1758. // END 13-APR-98