// Copyright (c) ZeniMax Media Inc. // Licensed under the GNU General Public License 2.0. /* ============================================================================== SOLDIER ============================================================================== */ #include "g_local.h" #include "m_soldier.h" #include "m_flash.h" static int sound_idle; static int sound_sight1; static int sound_sight2; static int sound_pain_light; static int sound_pain; static int sound_pain_ss; static int sound_death_light; static int sound_death; static int sound_death_ss; static int sound_cock; void soldier_start_charge(edict_t *self) { self->monsterinfo.aiflags |= AI_CHARGING; } void soldier_stop_charge(edict_t *self) { self->monsterinfo.aiflags &= ~AI_CHARGING; } void soldier_idle(edict_t *self) { if (frandom() > 0.8f) gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0); } void soldier_cock(edict_t *self) { if (self->s.frame == FRAME_stand322) gi.sound(self, CHAN_WEAPON, sound_cock, 1, ATTN_IDLE, 0); else gi.sound(self, CHAN_WEAPON, sound_cock, 1, ATTN_NORM, 0); // [Paril-KEX] reset cockness self->dmg = 0; } // RAFAEL void soldierh_hyper_laser_sound_start(edict_t *self) { if (self->style == 1) { if (self->count >= 2 && self->count < 4) self->monsterinfo.weapon_sound = gi.soundindex("weapons/hyprbl1a.wav"); } } void soldierh_hyper_laser_sound_end(edict_t *self) { if (self->monsterinfo.weapon_sound) { if (self->count >= 2 && self->count < 4) gi.sound(self, CHAN_AUTO, gi.soundindex("weapons/hyprbd1a.wav"), 1, ATTN_NORM, 0); self->monsterinfo.weapon_sound = 0; } } // RAFAEL // STAND void soldier_stand(edict_t *self); mframe_t soldier_frames_stand1[] = { { ai_stand, 0, soldier_idle }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand } }; MMOVE_T(soldier_move_stand1) = { FRAME_stand101, FRAME_stand130, soldier_frames_stand1, soldier_stand }; mframe_t soldier_frames_stand2[] = { { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand, 0, monster_footstep }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand, 0, monster_footstep } }; MMOVE_T(soldier_move_stand2) = { FRAME_stand201, FRAME_stand240, soldier_frames_stand2, soldier_stand }; mframe_t soldier_frames_stand3[] = { { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand, 0, soldier_cock }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand } }; MMOVE_T(soldier_move_stand3) = { FRAME_stand301, FRAME_stand339, soldier_frames_stand3, soldier_stand }; MONSTERINFO_STAND(soldier_stand) (edict_t *self) -> void { float r = frandom(); if ((self->monsterinfo.active_move != &soldier_move_stand1) || (r < 0.6f)) M_SetAnimation(self, &soldier_move_stand1); else if (r < 0.8f) M_SetAnimation(self, &soldier_move_stand2); else M_SetAnimation(self, &soldier_move_stand3); soldierh_hyper_laser_sound_end(self); } // // WALK // void soldier_walk1_random(edict_t *self) { if (frandom() > 0.1f) self->monsterinfo.nextframe = FRAME_walk101; } mframe_t soldier_frames_walk1[] = { { ai_walk, 3 }, { ai_walk, 6 }, { ai_walk, 2 }, { ai_walk, 2, monster_footstep }, { ai_walk, 2 }, { ai_walk, 1 }, { ai_walk, 6 }, { ai_walk, 5 }, { ai_walk, 3, monster_footstep }, { ai_walk, -1, soldier_walk1_random }, { ai_walk }, { ai_walk }, { ai_walk }, { ai_walk }, { ai_walk }, { ai_walk }, { ai_walk }, { ai_walk }, { ai_walk }, { ai_walk }, { ai_walk }, { ai_walk }, { ai_walk }, { ai_walk }, { ai_walk }, { ai_walk }, { ai_walk }, { ai_walk }, { ai_walk }, { ai_walk }, { ai_walk }, { ai_walk }, { ai_walk } }; MMOVE_T(soldier_move_walk1) = { FRAME_walk101, FRAME_walk133, soldier_frames_walk1, nullptr }; mframe_t soldier_frames_walk2[] = { { ai_walk, 4, monster_footstep }, { ai_walk, 4 }, { ai_walk, 9 }, { ai_walk, 8 }, { ai_walk, 5 }, { ai_walk, 1, monster_footstep }, { ai_walk, 3 }, { ai_walk, 7 }, { ai_walk, 6 }, { ai_walk, 7 } }; MMOVE_T(soldier_move_walk2) = { FRAME_walk209, FRAME_walk218, soldier_frames_walk2, nullptr }; MONSTERINFO_WALK(soldier_walk) (edict_t *self) -> void { // [Paril-KEX] during N64 cutscene, always use fast walk or we bog down the line if (!(self->hackflags & HACKFLAG_END_CUTSCENE) && frandom() < 0.5f) M_SetAnimation(self, &soldier_move_walk1); else M_SetAnimation(self, &soldier_move_walk2); } // // RUN // void soldier_run(edict_t *self); mframe_t soldier_frames_start_run[] = { { ai_run, 7 }, { ai_run, 5 } }; MMOVE_T(soldier_move_start_run) = { FRAME_run01, FRAME_run02, soldier_frames_start_run, soldier_run }; mframe_t soldier_frames_run[] = { { ai_run, 10 }, { ai_run, 11, [](edict_t *self) { monster_done_dodge(self); monster_footstep(self); } }, { ai_run, 11 }, { ai_run, 16 }, { ai_run, 10, monster_footstep }, { ai_run, 15, monster_done_dodge } }; MMOVE_T(soldier_move_run) = { FRAME_run03, FRAME_run08, soldier_frames_run, nullptr }; MONSTERINFO_RUN(soldier_run) (edict_t *self) -> void { monster_done_dodge(self); soldierh_hyper_laser_sound_end(self); if (self->monsterinfo.aiflags & AI_STAND_GROUND) { M_SetAnimation(self, &soldier_move_stand1); return; } if (self->monsterinfo.active_move == &soldier_move_walk1 || self->monsterinfo.active_move == &soldier_move_walk2 || self->monsterinfo.active_move == &soldier_move_start_run || self->monsterinfo.active_move == &soldier_move_run) { M_SetAnimation(self, &soldier_move_run); } else { M_SetAnimation(self, &soldier_move_start_run); } } // // PAIN // mframe_t soldier_frames_pain1[] = { { ai_move, -3 }, { ai_move, 4 }, { ai_move, 1 }, { ai_move, 1 }, { ai_move } }; MMOVE_T(soldier_move_pain1) = { FRAME_pain101, FRAME_pain105, soldier_frames_pain1, soldier_run }; mframe_t soldier_frames_pain2[] = { { ai_move, -13 }, { ai_move, -1 }, { ai_move, 2 }, { ai_move, 4 }, { ai_move, 2 }, { ai_move, 3 }, { ai_move, 2 } }; MMOVE_T(soldier_move_pain2) = { FRAME_pain201, FRAME_pain207, soldier_frames_pain2, soldier_run }; mframe_t soldier_frames_pain3[] = { { ai_move, -8 }, { ai_move, 10 }, { ai_move, -4, monster_footstep }, { ai_move, -1 }, { ai_move, -3 }, { ai_move }, { ai_move, 3 }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move, 1 }, { ai_move }, { ai_move, 1 }, { ai_move, 2 }, { ai_move, 4 }, { ai_move, 3 }, { ai_move, 2, monster_footstep } }; MMOVE_T(soldier_move_pain3) = { FRAME_pain301, FRAME_pain318, soldier_frames_pain3, soldier_run }; mframe_t soldier_frames_pain4[] = { { ai_move }, { ai_move }, { ai_move }, { ai_move, -10 }, { ai_move, -6 }, { ai_move, 8 }, { ai_move, 4 }, { ai_move, 1 }, { ai_move }, { ai_move, 2 }, { ai_move, 5 }, { ai_move, 2 }, { ai_move, -1 }, { ai_move, -1 }, { ai_move, 3 }, { ai_move, 2 }, { ai_move } }; MMOVE_T(soldier_move_pain4) = { FRAME_pain401, FRAME_pain417, soldier_frames_pain4, soldier_run }; PAIN(soldier_pain) (edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod) -> void { float r; int n; monster_done_dodge(self); soldier_stop_charge(self); // if we're blind firing, this needs to be turned off here self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; if (level.time < self->pain_debounce_time) { 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))) { // PMM - clear duck flag if (self->monsterinfo.aiflags & AI_DUCKED) monster_duck_up(self); M_SetAnimation(self, &soldier_move_pain4); soldierh_hyper_laser_sound_end(self); } return; } self->pain_debounce_time = level.time + 3_sec; n = self->count | 1; if (n == 1) gi.sound(self, CHAN_VOICE, sound_pain_light, 1, ATTN_NORM, 0); else if (n == 3) gi.sound(self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0); else gi.sound(self, CHAN_VOICE, sound_pain_ss, 1, ATTN_NORM, 0); if (self->velocity[2] > 100) { // PMM - clear duck flag if (self->monsterinfo.aiflags & AI_DUCKED) monster_duck_up(self); M_SetAnimation(self, &soldier_move_pain4); soldierh_hyper_laser_sound_end(self); return; } if (!M_ShouldReactToPain(self, mod)) return; // no pain anims in nightmare r = frandom(); if (r < 0.33f) M_SetAnimation(self, &soldier_move_pain1); else if (r < 0.66f) M_SetAnimation(self, &soldier_move_pain2); else M_SetAnimation(self, &soldier_move_pain3); // PMM - clear duck flag if (self->monsterinfo.aiflags & AI_DUCKED) monster_duck_up(self); soldierh_hyper_laser_sound_end(self); } MONSTERINFO_SETSKIN(soldier_setskin) (edict_t *self) -> void { if (self->health < (self->max_health / 2)) self->s.skinnum |= 1; else self->s.skinnum &= ~1; } // // ATTACK // 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 }; 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 }; 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 }; void soldier_fire_vanilla(edict_t *self, int flash_number, bool angle_limited) { vec3_t start; vec3_t forward, right, up; vec3_t aim; vec3_t dir; vec3_t end; float r, u; monster_muzzleflash_id_t flash_index; vec3_t aim_norm; float angle; vec3_t aim_good; if (self->count < 2) flash_index = blaster_flash[flash_number]; else if (self->count < 4) flash_index = shotgun_flash[flash_number]; else flash_index = machinegun_flash[flash_number]; AngleVectors(self->s.angles, forward, right, nullptr); start = M_ProjectFlashSource(self, monster_flash_offset[flash_index], forward, right); if (flash_number == 5 || flash_number == 6) // he's dead { if (self->spawnflags.has(SPAWNFLAG_MONSTER_DEAD)) return; aim = forward; } else { if ((!self->enemy) || (!self->enemy->inuse)) { self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; return; } // PMM if (self->monsterinfo.attack_state == AS_BLIND) end = self->monsterinfo.blind_fire_target; else end = self->enemy->s.origin; // pmm end[2] += self->enemy->viewheight; aim = end - start; aim_good = end; // PMM if (angle_limited) { aim_norm = aim; aim_norm.normalize(); angle = aim_norm.dot(forward); if (angle < 0.5f) // ~25 degree angle { if (level.time >= self->monsterinfo.fire_wait) self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; else self->monsterinfo.aiflags |= AI_HOLD_FRAME; return; } } //-PMM dir = vectoangles(aim); AngleVectors(dir, forward, right, up); r = crandom() * 1000; u = crandom() * 500; end = start + (forward * 8192); end += (right * r); end += (up * u); aim = end - start; aim.normalize(); } if (self->count <= 1) { monster_fire_blaster(self, start, aim, 5, 600, flash_index, EF_BLASTER); } else if (self->count <= 3) { monster_fire_shotgun(self, start, aim, 2, 1, 1500, 750, 9, flash_index); // [Paril-KEX] indicates to soldier that he must cock self->dmg = 1; } else { // PMM - changed to wait from pausetime to not interfere with dodge code if (!(self->monsterinfo.aiflags & AI_HOLD_FRAME)) self->monsterinfo.fire_wait = level.time + random_time(300_ms, 1.1_sec); monster_fire_bullet(self, start, aim, 2, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, flash_index); if (level.time >= self->monsterinfo.fire_wait) self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; else self->monsterinfo.aiflags |= AI_HOLD_FRAME; } } PRETHINK(soldierh_laser_update) (edict_t *laser) -> void { edict_t *self = laser->owner; vec3_t forward, right, up; vec3_t start; vec3_t tempvec; AngleVectors(self->s.angles, forward, right, up); start = self->s.origin; tempvec = monster_flash_offset[self->radius_dmg]; start += (forward * tempvec[0]); start += (right * tempvec[1]); start += (up * (tempvec[2] + 6)); if (!self->deadflag) PredictAim(self, self->enemy, start, 0, false, frandom(0.1f, 0.2f), &forward, nullptr); laser->s.origin = start; laser->movedir = forward; gi.linkentity(laser); dabeam_update(laser, false); } // RAFAEL void soldierh_laserbeam(edict_t *self, int flash_index) { self->radius_dmg = flash_index; monster_fire_dabeam(self, 1, false, soldierh_laser_update); } 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 }; 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 }; void soldier_fire_xatrix(edict_t *self, int flash_number, bool angle_limited) { vec3_t start; vec3_t forward, right, up; vec3_t aim; vec3_t dir; vec3_t end; float r, u; monster_muzzleflash_id_t flash_index; vec3_t aim_norm; float angle; vec3_t aim_good; if (self->count < 2) flash_index = ripper_flash[flash_number]; // ripper else if (self->count < 4) flash_index = hyper_flash[flash_number]; // hyperblaster else flash_index = machinegun_flash[flash_number]; // laserbeam AngleVectors(self->s.angles, forward, right, nullptr); start = M_ProjectFlashSource(self, monster_flash_offset[flash_index], forward, right); if (flash_number == 5 || flash_number == 6) { if (self->spawnflags.has(SPAWNFLAG_MONSTER_DEAD)) return; aim = forward; } else { // PMM if (self->monsterinfo.attack_state == AS_BLIND) end = self->monsterinfo.blind_fire_target; else end = self->enemy->s.origin; // pmm end[2] += self->enemy->viewheight; aim = end - start; aim_good = end; // PMM if (angle_limited) { aim_norm = aim; aim_norm.normalize(); angle = aim_norm.dot(forward); if (angle < 0.5f) // ~25 degree angle { if (level.time >= self->monsterinfo.fire_wait) self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; else self->monsterinfo.aiflags |= AI_HOLD_FRAME; return; } } //-PMM dir = vectoangles(aim); AngleVectors(dir, forward, right, up); r = crandom() * 100; u = crandom() * 50; end = start + (forward * 8192); end += (right * r); end += (up * u); aim = end - start; aim.normalize(); } if (self->count <= 1) { // RAFAEL 24-APR-98 // droped the damage from 15 to 5 monster_fire_ionripper(self, start, aim, 5, 600, flash_index, EF_IONRIPPER); } else if (self->count <= 3) { monster_fire_blueblaster(self, start, aim, 1, 600, flash_index, EF_BLUEHYPERBLASTER); } else { // PMM - changed to wait from pausetime to not interfere with dodge code if (!(self->monsterinfo.aiflags & AI_HOLD_FRAME)) self->monsterinfo.fire_wait = level.time + random_time(300_ms, 1.1_sec); soldierh_laserbeam(self, flash_index); if (level.time >= self->monsterinfo.fire_wait) self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; else self->monsterinfo.aiflags |= AI_HOLD_FRAME; } } // RAFAEL void soldier_fire(edict_t *self, int flash_number, bool angle_limited) { // RAFAEL if (self->style == 1) soldier_fire_xatrix(self, flash_number, angle_limited); else // RAFAEL soldier_fire_vanilla(self, flash_number, angle_limited); } // ATTACK1 (blaster/shotgun) void soldier_fire1(edict_t *self) { soldier_fire(self, 0, false); } void soldier_attack1_refire1(edict_t *self) { // [Paril-KEX] if (self->count <= 0) self->monsterinfo.nextframe = FRAME_attak110; // PMM - blindfire if (self->monsterinfo.aiflags & AI_MANUAL_STEERING) { self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; return; } // pmm if (!self->enemy) return; if (self->count > 1) return; if (self->enemy->health <= 0) return; if (((frandom() < 0.5f) && visible(self, self->enemy)) || (range_to(self, self->enemy) <= RANGE_MELEE)) self->monsterinfo.nextframe = FRAME_attak102; else self->monsterinfo.nextframe = FRAME_attak110; } void soldier_attack1_refire2(edict_t *self) { if (!self->enemy) return; if (self->count < 2) return; if (self->enemy->health <= 0) return; if (((self->radius_dmg || frandom() < 0.5f) && visible(self, self->enemy)) || (range_to(self, self->enemy) <= RANGE_MELEE)) { self->monsterinfo.nextframe = FRAME_attak102; self->radius_dmg = 0; } } static void soldier_attack1_shotgun_check(edict_t *self) { if (self->dmg) { self->monsterinfo.nextframe = FRAME_attak106; // [Paril-KEX] indicate that we should force a refire self->radius_dmg = 1; } } static void soldier_blind_check(edict_t *self) { if (self->monsterinfo.aiflags & AI_MANUAL_STEERING) { vec3_t aim = self->monsterinfo.blind_fire_target - self->s.origin; self->ideal_yaw = vectoyaw(aim); } } mframe_t soldier_frames_attack1[] = { { ai_charge, 0, soldier_blind_check }, { ai_charge, 0, soldier_attack1_shotgun_check }, { ai_charge, 0, soldier_fire1 }, { ai_charge }, { ai_charge }, { ai_charge, 0, soldier_attack1_refire1 }, { ai_charge }, { ai_charge, 0, soldier_cock }, { ai_charge, 0, soldier_attack1_refire2 }, { ai_charge }, { ai_charge }, { ai_charge } }; MMOVE_T(soldier_move_attack1) = { FRAME_attak101, FRAME_attak112, soldier_frames_attack1, soldier_run }; // ATTACK1 (blaster/shotgun) void soldierh_hyper_refire1(edict_t *self) { if (!self->enemy) return; if (self->count >= 2 && self->count < 4) { if (frandom() < 0.7f && visible(self, self->enemy)) self->s.frame = FRAME_attak103; } } void soldierh_hyperripper1(edict_t *self) { if (self->count < 4) soldier_fire(self, 0, false); } mframe_t soldierh_frames_attack1[] = { { ai_charge, 0, soldier_blind_check }, { ai_charge, 0, soldierh_hyper_laser_sound_start }, { ai_charge, 0, soldier_fire1 }, { ai_charge, 0, soldierh_hyperripper1 }, { ai_charge, 0, soldierh_hyperripper1 }, { ai_charge, 0, soldier_attack1_refire1 }, { ai_charge, 0, soldierh_hyper_refire1 }, { ai_charge, 0, soldier_cock }, { ai_charge, 0, soldier_attack1_refire2 }, { ai_charge, 0, soldierh_hyper_laser_sound_end }, { ai_charge }, { ai_charge } }; MMOVE_T(soldierh_move_attack1) = { FRAME_attak101, FRAME_attak112, soldierh_frames_attack1, soldier_run }; // ATTACK2 (blaster/shotgun) void soldier_fire2(edict_t *self) { soldier_fire(self, 1, false); } void soldier_attack2_refire1(edict_t *self) { if (self->count <= 0) self->monsterinfo.nextframe = FRAME_attak216; if (!self->enemy) return; if (self->count > 1) return; if (self->enemy->health <= 0) return; if (((frandom() < 0.5f) && visible(self, self->enemy)) || (range_to(self, self->enemy) <= RANGE_MELEE)) self->monsterinfo.nextframe = FRAME_attak204; } void soldier_attack2_refire2(edict_t *self) { if (!self->enemy) return; if (self->count < 2) return; if (self->enemy->health <= 0) return; // RAFAEL if (((self->radius_dmg || frandom() < 0.5f) && visible(self, self->enemy)) || ((self->style == 0 || self->count < 4) && (range_to(self, self->enemy) <= RANGE_MELEE))) { // RAFAEL self->monsterinfo.nextframe = FRAME_attak204; self->radius_dmg = 0; } } static void soldier_attack2_shotgun_check(edict_t *self) { if (self->dmg) { self->monsterinfo.nextframe = FRAME_attak210; // [Paril-KEX] indicate that we should force a refire self->radius_dmg = 1; } } mframe_t soldier_frames_attack2[] = { { ai_charge }, { ai_charge }, { ai_charge, 0, soldier_attack2_shotgun_check }, { ai_charge }, { ai_charge, 0, soldier_fire2 }, { ai_charge }, { ai_charge }, { ai_charge, 0, soldier_attack2_refire1 }, { ai_charge }, { ai_charge }, { ai_charge }, { ai_charge }, { ai_charge, 0, soldier_cock }, { ai_charge }, { ai_charge, 0, soldier_attack2_refire2 }, { ai_charge }, { ai_charge }, { ai_charge } }; MMOVE_T(soldier_move_attack2) = { FRAME_attak201, FRAME_attak218, soldier_frames_attack2, soldier_run }; // RAFAEL void soldierh_hyper_refire2(edict_t *self) { if (!self->enemy) return; if (self->count < 2) return; else if (self->count < 4) { if (frandom() < 0.7f && visible(self, self->enemy)) self->s.frame = FRAME_attak205; } } void soldierh_hyperripper2(edict_t *self) { if (self->count < 4) soldier_fire(self, 1, false); } mframe_t soldierh_frames_attack2[] = { { ai_charge }, { ai_charge }, { ai_charge }, { ai_charge, 0, soldierh_hyper_laser_sound_start }, { ai_charge, 0, soldier_fire2 }, { ai_charge, 0, soldierh_hyperripper2 }, { ai_charge, 0, soldierh_hyperripper2 }, { ai_charge, 0, soldier_attack2_refire1 }, { ai_charge, 0, soldierh_hyper_refire2 }, { ai_charge }, { ai_charge }, { ai_charge }, { ai_charge, 0, soldier_cock }, { ai_charge }, { ai_charge, 0, soldier_attack2_refire2 }, { ai_charge, 0, soldierh_hyper_laser_sound_end }, { ai_charge }, { ai_charge } }; MMOVE_T(soldierh_move_attack2) = { FRAME_attak201, FRAME_attak218, soldierh_frames_attack2, soldier_run }; // RAFAEL // ATTACK3 (duck and shoot) void soldier_fire3(edict_t *self) { soldier_fire(self, 2, false); } void soldierh_hyperripper3(edict_t *self) { if (self->s.skinnum >= 6 && self->count < 4) soldier_fire(self, 2, false); } void soldier_attack3_refire(edict_t *self) { if (self->dmg) monster_duck_hold(self); else if ((level.time + 400_ms) < self->monsterinfo.duck_wait_time) self->monsterinfo.nextframe = FRAME_attak303; } mframe_t soldier_frames_attack3[] = { { ai_charge, 0, monster_duck_down }, { ai_charge, 0, soldierh_hyper_laser_sound_start }, { ai_charge, 0, soldier_fire3 }, { ai_charge, 0, soldierh_hyperripper3 }, { ai_charge, 0, soldierh_hyperripper3 }, { ai_charge, 0, soldier_attack3_refire }, { ai_charge, 0, monster_duck_up }, { ai_charge, 0, soldierh_hyper_laser_sound_end }, { ai_charge } }; MMOVE_T(soldier_move_attack3) = { FRAME_attak301, FRAME_attak309, soldier_frames_attack3, soldier_run }; // ATTACK4 (machinegun) void soldier_fire4(edict_t *self) { soldier_fire(self, 3, false); } mframe_t soldier_frames_attack4[] = { { ai_charge }, { ai_charge, 0, soldierh_hyper_laser_sound_start }, { ai_charge, 0, soldier_fire4 }, { ai_charge, 0, soldierh_hyper_laser_sound_end }, { ai_charge }, { ai_charge } }; MMOVE_T(soldier_move_attack4) = { FRAME_attak401, FRAME_attak406, soldier_frames_attack4, soldier_run }; // ATTACK6 (run & shoot) void soldier_fire8(edict_t *self) { soldier_fire(self, 7, true); } void soldier_attack6_refire1(edict_t *self) { // PMM - make sure dodge & charge bits are cleared monster_done_dodge(self); soldier_stop_charge(self); if (!self->enemy) return; if (self->count > 1) return; if (self->enemy->health <= 0 || range_to(self, self->enemy) < RANGE_NEAR || !visible(self, self->enemy)) // don't endlessly run into walls { soldier_run(self); return; } if (frandom() < 0.25f) self->monsterinfo.nextframe = FRAME_runs03; else soldier_run(self); } void soldier_attack6_refire2(edict_t *self) { // PMM - make sure dodge & charge bits are cleared monster_done_dodge(self); soldier_stop_charge(self); if (!self->enemy || self->count <= 0) return; if (self->enemy->health <= 0 || (!self->radius_dmg && range_to(self, self->enemy) < RANGE_NEAR) || !visible(self, self->enemy)) // don't endlessly run into walls { soldierh_hyper_laser_sound_end(self); return; } if (self->radius_dmg || frandom() < 0.25f) { self->monsterinfo.nextframe = FRAME_runs03; self->radius_dmg = 0; } } static void soldier_attack6_shotgun_check(edict_t *self) { if (self->dmg) { self->monsterinfo.nextframe = FRAME_runs09; // [Paril-KEX] indicate that we should force a refire self->radius_dmg = 1; } } void soldierh_hyperripper8(edict_t *self) { if (self->s.skinnum >= 6 && self->count < 4) soldier_fire(self, 7, true); } mframe_t soldier_frames_attack6[] = { { ai_run, 10, soldier_start_charge }, { ai_run, 4, soldier_attack6_shotgun_check }, { ai_run, 12, soldierh_hyper_laser_sound_start }, { ai_run, 11, [](edict_t *self) { soldier_fire8(self); monster_footstep(self); } }, { ai_run, 13, [](edict_t *self ) { soldierh_hyperripper8(self); monster_done_dodge(self); } }, { ai_run, 18, soldierh_hyperripper8 }, { ai_run, 15, monster_footstep }, { ai_run, 14, soldier_attack6_refire1 }, { ai_run, 11 }, { ai_run, 8, monster_footstep }, { ai_run, 11, soldier_cock }, { ai_run, 12 }, { ai_run, 12, monster_footstep }, { ai_run, 17, soldier_attack6_refire2 } }; MMOVE_T(soldier_move_attack6) = { FRAME_runs01, FRAME_runs14, soldier_frames_attack6, soldier_run, 0.65f }; MONSTERINFO_ATTACK(soldier_attack) (edict_t *self) -> void { float r, chance; monster_done_dodge(self); // PMM - blindfire! if (self->monsterinfo.attack_state == AS_BLIND) { // setup shot probabilities if (self->monsterinfo.blind_fire_delay < 1_sec) chance = 1.0f; else if (self->monsterinfo.blind_fire_delay < 7.5_sec) chance = 0.4f; else chance = 0.1f; r = frandom(); // minimum of 4.1 seconds, plus 0-3, after the shots are done self->monsterinfo.blind_fire_delay += 4.1_sec + random_time(3_sec); // don't shoot at the origin if (!self->monsterinfo.blind_fire_target) return; // don't shoot if the dice say not to if (r > chance) return; // turn on manual steering to signal both manual steering and blindfire self->monsterinfo.aiflags |= AI_MANUAL_STEERING; // RAFAEL if (self->style == 1) M_SetAnimation(self, &soldierh_move_attack1); else // RAFAEL M_SetAnimation(self, &soldier_move_attack1); self->monsterinfo.attack_finished = level.time + random_time(1.5_sec, 2.5_sec); return; } // pmm // PMM - added this so the soldiers now run toward you and shoot instead of just stopping and shooting r = frandom(); // nb: run-shoot not limited by `M_CheckClearShot` since they will be far enough // away that it doesn't matter if ((!(self->monsterinfo.aiflags & (AI_BLOCKED | AI_STAND_GROUND))) && (r < 0.25f && (self->count <= 3)) && (range_to(self, self->enemy) >= (RANGE_NEAR * 0.5f))) { M_SetAnimation(self, &soldier_move_attack6); } else { if (self->count < 4) { bool attack1_possible; // [Paril-KEX] shotgun guard only uses attack2 at close range if ((!self->style && self->count >= 2 && self->count <= 3) && range_to(self, self->enemy) <= (RANGE_NEAR * 0.65f)) attack1_possible = false; else attack1_possible = M_CheckClearShot(self, monster_flash_offset[MZ2_SOLDIER_BLASTER_1]); bool attack2_possible = M_CheckClearShot(self, monster_flash_offset[MZ2_SOLDIER_BLASTER_2]); if (attack1_possible && (!attack2_possible || frandom() < 0.5f)) { // RAFAEL if (self->style == 1) M_SetAnimation(self, &soldierh_move_attack1); else // RAFAEL M_SetAnimation(self, &soldier_move_attack1); } else if (attack2_possible) { // RAFAEL if (self->style == 1) M_SetAnimation(self, &soldierh_move_attack2); else // RAFAEL M_SetAnimation(self, &soldier_move_attack2); } } else if (M_CheckClearShot(self, monster_flash_offset[MZ2_SOLDIER_MACHINEGUN_4])) { M_SetAnimation(self, &soldier_move_attack4); } } } // // SIGHT // MONSTERINFO_SIGHT(soldier_sight) (edict_t *self, edict_t *other) -> void { if (frandom() < 0.5f) gi.sound(self, CHAN_VOICE, sound_sight1, 1, ATTN_NORM, 0); else gi.sound(self, CHAN_VOICE, sound_sight2, 1, ATTN_NORM, 0); if (self->enemy && (range_to(self, self->enemy) >= RANGE_NEAR) && visible(self, self->enemy) // Paril: don't run-shoot if we can't see them ) { // RAFAEL if (self->style == 1 || frandom() > 0.75f) // RAFAEL { // RAFAEL + legacy bug fix // don't use run+shoot for machinegun/laser because // the animation is a bit weird if (self->count < 4) M_SetAnimation(self, &soldier_move_attack6); else if (M_CheckClearShot(self, monster_flash_offset[MZ2_SOLDIER_MACHINEGUN_4])) // RAFAEL M_SetAnimation(self, &soldier_move_attack4); } } } // // DUCK // mframe_t soldier_frames_duck[] = { { ai_move, 5, monster_duck_down }, { ai_move, -1, monster_duck_hold }, { ai_move, 1 }, { ai_move, 0, monster_duck_up }, { ai_move, 5 } }; MMOVE_T(soldier_move_duck) = { FRAME_duck01, FRAME_duck05, soldier_frames_duck, soldier_run }; extern const mmove_t soldier_move_trip; static void soldier_stand_up(edict_t *self) { soldierh_hyper_laser_sound_end(self); M_SetAnimation(self, &soldier_move_trip, false); self->monsterinfo.nextframe = FRAME_runt08; } static bool soldier_prone_shoot_ok(edict_t *self) { if (!self->enemy || !self->enemy->inuse) return false; vec3_t fwd; AngleVectors(self->s.angles, fwd, nullptr, nullptr); vec3_t diff = self->enemy->s.origin - self->s.origin; diff.z = 0; diff.normalize(); float v = fwd.dot(diff); if (v < 0.80f) return false; return true; } static void ai_soldier_move(edict_t *self, float dist) { ai_move(self, dist); if (!soldier_prone_shoot_ok(self)) { soldier_stand_up(self); return; } } void soldier_fire5(edict_t *self) { soldier_fire(self, 8, true); } void soldierh_hyperripper5(edict_t *self) { if (self->style && self->count < 4) soldier_fire(self, 8, true); } mframe_t soldier_frames_attack5[] = { { ai_move, 18, monster_duck_down }, { ai_move, 11, monster_footstep }, { ai_move, 0, monster_footstep }, { ai_soldier_move }, { ai_soldier_move, 0, soldierh_hyper_laser_sound_start }, { ai_soldier_move, 0, soldier_fire5 }, { ai_soldier_move, 0, soldierh_hyperripper5 }, { ai_soldier_move, 0, soldierh_hyperripper5 }, }; MMOVE_T(soldier_move_attack5) = { FRAME_attak501, FRAME_attak508, soldier_frames_attack5, soldier_stand_up }; static void monster_check_prone(edict_t *self) { // we're a shotgun guard waiting to cock if (!self->style && self->count >= 2 && self->count <= 3 && self->dmg) return; // not going to shoot at this angle if (!soldier_prone_shoot_ok(self)) return; M_SetAnimation(self, &soldier_move_attack5, false); } mframe_t soldier_frames_trip[] = { { ai_move, 10 }, { ai_move, 2, monster_check_prone }, { ai_move, 18, monster_duck_down }, { ai_move, 11, monster_footstep }, { ai_move, 9 }, { ai_move, -11, monster_footstep }, { ai_move, -2 }, { ai_move, 0 }, { ai_move, 6 }, { ai_move, -5 }, { ai_move, 0 }, { ai_move, 1 }, { ai_move, 0, monster_footstep }, { ai_move, 0, monster_duck_up }, { ai_move, 3 }, { ai_move, 2, monster_footstep }, { ai_move, -1 }, { ai_move, 2 }, { ai_move, 0 }, }; MMOVE_T(soldier_move_trip) = { FRAME_runt01, FRAME_runt19, soldier_frames_trip, soldier_run }; // pmm - blocking code MONSTERINFO_BLOCKED(soldier_blocked) (edict_t *self, float dist) -> bool { // don't do anything if you're dodging if ((self->monsterinfo.aiflags & AI_DODGING) || (self->monsterinfo.aiflags & AI_DUCKED)) return false; return blocked_checkplat(self, dist); } // // DEATH // void soldier_fire6(edict_t *self) { soldier_fire(self, 5, false); if (self->dmg) self->monsterinfo.nextframe = FRAME_death126; } void soldier_fire7(edict_t *self) { soldier_fire(self, 6, false); } void soldier_dead(edict_t *self) { self->mins = { -16, -16, -24 }; self->maxs = { 16, 16, -8 }; monster_dead(self); } static void soldier_death_shrink(edict_t *self) { self->svflags |= SVF_DEADMONSTER; self->maxs[2] = 0; gi.linkentity(self); } mframe_t soldier_frames_death1[] = { { ai_move }, { ai_move, -10 }, { ai_move, -10 }, { ai_move, -10, soldier_death_shrink }, { ai_move, -5 }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move, 0, soldierh_hyper_laser_sound_start }, { ai_move, 0, soldier_fire6 }, { ai_move }, { ai_move }, { ai_move, 0, soldier_fire7 }, { ai_move, 0, soldierh_hyper_laser_sound_end }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move } }; MMOVE_T(soldier_move_death1) = { FRAME_death101, FRAME_death136, soldier_frames_death1, soldier_dead }; mframe_t soldier_frames_death2[] = { { ai_move, -5 }, { ai_move, -5 }, { ai_move, -5 }, { ai_move, 0, soldier_death_shrink }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move } }; MMOVE_T(soldier_move_death2) = { FRAME_death201, FRAME_death235, soldier_frames_death2, soldier_dead }; mframe_t soldier_frames_death3[] = { { ai_move, -5 }, { ai_move, -5 }, { ai_move, -5 }, { ai_move, 0, soldier_death_shrink }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, }; MMOVE_T(soldier_move_death3) = { FRAME_death301, FRAME_death345, soldier_frames_death3, soldier_dead }; mframe_t soldier_frames_death4[] = { { ai_move }, { ai_move }, { ai_move, 1.5f }, { ai_move, 2.5f }, { ai_move, -1.5f }, { ai_move }, { ai_move }, { ai_move }, { ai_move, -0.5f }, { ai_move }, { ai_move }, { ai_move, 4.0f }, { ai_move, 4.0f }, { ai_move, 8.0f, soldier_death_shrink }, { ai_move, 8.0f }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move, 5.5f }, { ai_move, 2.5f }, { ai_move, -2.0f }, { ai_move, -2.0f } }; MMOVE_T(soldier_move_death4) = { FRAME_death401, FRAME_death453, soldier_frames_death4, soldier_dead }; mframe_t soldier_frames_death5[] = { { ai_move, -5 }, { ai_move, -5 }, { ai_move, -5 }, { ai_move }, { ai_move }, { ai_move, 0, soldier_death_shrink }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move } }; MMOVE_T(soldier_move_death5) = { FRAME_death501, FRAME_death524, soldier_frames_death5, soldier_dead }; mframe_t soldier_frames_death6[] = { { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move, 0, soldier_death_shrink }, { ai_move }, { ai_move }, { ai_move }, { ai_move } }; MMOVE_T(soldier_move_death6) = { FRAME_death601, FRAME_death610, soldier_frames_death6, soldier_dead }; DIE(soldier_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void { int n; soldierh_hyper_laser_sound_end(self); // check for gib if (M_CheckGib(self, mod)) { gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0); self->s.skinnum /= 2; if (self->beam) { G_FreeEdict(self->beam); self->beam = nullptr; } ThrowGibs(self, damage, { { 3, "models/objects/gibs/sm_meat/tris.md2" }, { "models/objects/gibs/bone2/tris.md2" }, { "models/objects/gibs/bone/tris.md2" }, { "models/monsters/soldier/gibs/arm.md2", GIB_SKINNED }, { "models/monsters/soldier/gibs/gun.md2", GIB_SKINNED | GIB_UPRIGHT }, { "models/monsters/soldier/gibs/chest.md2", GIB_SKINNED }, { "models/monsters/soldier/gibs/head.md2", GIB_HEAD | GIB_SKINNED } }); self->deadflag = true; return; } if (self->deadflag) return; // regular death self->deadflag = true; self->takedamage = true; n = self->count | 1; if (n == 1) gi.sound(self, CHAN_VOICE, sound_death_light, 1, ATTN_NORM, 0); else if (n == 3) gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); else // (n == 5) gi.sound(self, CHAN_VOICE, sound_death_ss, 1, ATTN_NORM, 0); if (fabsf((self->s.origin[2] + self->viewheight) - point[2]) <= 4 && self->velocity.z < 65.f) { // head shot M_SetAnimation(self, &soldier_move_death3); return; } // if we die while on the ground, do a quicker death4 if (self->monsterinfo.active_move == &soldier_move_trip || self->monsterinfo.active_move == &soldier_move_attack5) { M_SetAnimation(self, &soldier_move_death4); self->monsterinfo.nextframe = FRAME_death413; soldier_death_shrink(self); return; } // only do the spin-death if we have enough velocity to justify it if (self->velocity.z > 65.f || self->velocity.length() > 150.f) n = irandom(5); else n = irandom(4); if (n == 0) M_SetAnimation(self, &soldier_move_death1); else if (n == 1) M_SetAnimation(self, &soldier_move_death2); else if (n == 2) M_SetAnimation(self, &soldier_move_death4); else if (n == 3) M_SetAnimation(self, &soldier_move_death5); else M_SetAnimation(self, &soldier_move_death6); } // // NEW DODGE CODE // MONSTERINFO_SIDESTEP(soldier_sidestep) (edict_t *self) -> bool { // don't sidestep during trip or up pain if (self->monsterinfo.active_move == &soldier_move_trip || self->monsterinfo.active_move == &soldier_move_attack5 || self->monsterinfo.active_move == &soldier_move_pain4) return false; if (self->count <= 3) { if (self->monsterinfo.active_move != &soldier_move_attack6) { M_SetAnimation(self, &soldier_move_attack6); soldierh_hyper_laser_sound_end(self); } } else { if (self->monsterinfo.active_move != &soldier_move_start_run && self->monsterinfo.active_move != &soldier_move_run) { M_SetAnimation(self, &soldier_move_start_run); soldierh_hyper_laser_sound_end(self); } } return true; } MONSTERINFO_DUCK(soldier_duck) (edict_t *self, gtime_t eta) -> bool { self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; if (self->monsterinfo.active_move == &soldier_move_attack6) { M_SetAnimation(self, &soldier_move_trip); } else if (self->dmg || brandom()) { M_SetAnimation(self, &soldier_move_duck); } else { M_SetAnimation(self, &soldier_move_attack3); } soldierh_hyper_laser_sound_end(self); return true; } //========= // ROGUE void soldier_blind(edict_t *self); mframe_t soldier_frames_blind[] = { { ai_move, 0, soldier_idle }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move } }; MMOVE_T(soldier_move_blind) = { FRAME_stand101, FRAME_stand130, soldier_frames_blind, soldier_blind }; MONSTERINFO_STAND(soldier_blind) (edict_t *self) -> void { M_SetAnimation(self, &soldier_move_blind); } // ROGUE //========= // // SPAWN // constexpr spawnflags_t SPAWNFLAG_SOLDIER_BLIND = 8_spawnflag; void SP_monster_soldier_x(edict_t *self) { self->s.modelindex = gi.modelindex("models/monsters/soldier/tris.md2"); self->monsterinfo.scale = MODEL_SCALE; self->mins = { -16, -16, -24 }; self->maxs = { 16, 16, 32 }; self->movetype = MOVETYPE_STEP; self->solid = SOLID_BBOX; sound_idle = gi.soundindex("soldier/solidle1.wav"); sound_sight1 = gi.soundindex("soldier/solsght1.wav"); sound_sight2 = gi.soundindex("soldier/solsrch1.wav"); sound_cock = gi.soundindex("infantry/infatck3.wav"); gi.modelindex("models/monsters/soldier/gibs/head.md2"); gi.modelindex("models/monsters/soldier/gibs/gun.md2"); gi.modelindex("models/monsters/soldier/gibs/arm.md2"); gi.modelindex("models/monsters/soldier/gibs/chest.md2"); self->mass = 100; self->pain = soldier_pain; self->die = soldier_die; self->monsterinfo.stand = soldier_stand; self->monsterinfo.walk = soldier_walk; self->monsterinfo.run = soldier_run; self->monsterinfo.dodge = M_MonsterDodge; self->monsterinfo.attack = soldier_attack; self->monsterinfo.melee = nullptr; self->monsterinfo.sight = soldier_sight; self->monsterinfo.setskin = soldier_setskin; //===== // ROGUE self->monsterinfo.blocked = soldier_blocked; self->monsterinfo.duck = soldier_duck; self->monsterinfo.unduck = monster_duck_up; self->monsterinfo.sidestep = soldier_sidestep; if (self->spawnflags.has(SPAWNFLAG_SOLDIER_BLIND)) // blind self->monsterinfo.stand = soldier_blind; // ROGUE //===== gi.linkentity(self); self->monsterinfo.stand(self); walkmonster_start(self); } void SP_monster_soldier_vanilla(edict_t *self) { SP_monster_soldier_x(self); } /*QUAKED monster_soldier_light (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight */ void SP_monster_soldier_light(edict_t *self) { if ( !M_AllowSpawn( self ) ) { G_FreeEdict( self ); return; } SP_monster_soldier_x(self); sound_pain_light = gi.soundindex("soldier/solpain2.wav"); sound_death_light = gi.soundindex("soldier/soldeth2.wav"); gi.modelindex("models/objects/laser/tris.md2"); gi.soundindex("misc/lasfly.wav"); gi.soundindex("soldier/solatck2.wav"); self->s.skinnum = 0; self->count = self->s.skinnum; self->health = self->max_health = 20 * st.health_multiplier; self->gib_health = -30; // PMM - blindfire self->monsterinfo.blindfire = true; } /*QUAKED monster_soldier (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight */ void SP_monster_soldier(edict_t *self) { if( !M_AllowSpawn( self ) ) { G_FreeEdict( self ); return; } SP_monster_soldier_x(self); sound_pain = gi.soundindex("soldier/solpain1.wav"); sound_death = gi.soundindex("soldier/soldeth1.wav"); gi.soundindex("soldier/solatck1.wav"); self->s.skinnum = 2; self->count = self->s.skinnum; self->health = self->max_health = 30 * st.health_multiplier; self->gib_health = -30; } /*QUAKED monster_soldier_ss (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight */ void SP_monster_soldier_ss(edict_t *self) { if ( !M_AllowSpawn( self ) ) { G_FreeEdict( self ); return; } SP_monster_soldier_x(self); sound_pain_ss = gi.soundindex("soldier/solpain3.wav"); sound_death_ss = gi.soundindex("soldier/soldeth3.wav"); gi.soundindex("soldier/solatck3.wav"); self->s.skinnum = 4; self->count = self->s.skinnum; self->health = self->max_health = 40 * st.health_multiplier; self->gib_health = -30; } // // SPAWN // void SP_monster_soldier_h(edict_t *self) { SP_monster_soldier_x(self); self->style = 1; } /*QUAKED monster_soldier_ripper (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight */ void SP_monster_soldier_ripper(edict_t *self) { if ( !M_AllowSpawn( self ) ) { G_FreeEdict( self ); return; } SP_monster_soldier_h(self); sound_pain_light = gi.soundindex("soldier/solpain2.wav"); sound_death_light = gi.soundindex("soldier/soldeth2.wav"); gi.modelindex("models/objects/boomrang/tris.md2"); gi.soundindex("misc/lasfly.wav"); gi.soundindex("soldier/solatck2.wav"); self->s.skinnum = 6; self->count = self->s.skinnum - 6; self->health = self->max_health = 50 * st.health_multiplier; self->gib_health = -30; // PMM - blindfire self->monsterinfo.blindfire = true; } /*QUAKED monster_soldier_hypergun (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight */ void SP_monster_soldier_hypergun(edict_t *self) { if ( !M_AllowSpawn( self ) ) { G_FreeEdict( self ); return; } SP_monster_soldier_h(self); gi.modelindex("models/objects/laser/tris.md2"); sound_pain = gi.soundindex("soldier/solpain1.wav"); sound_death = gi.soundindex("soldier/soldeth1.wav"); gi.soundindex("soldier/solatck1.wav"); gi.soundindex("weapons/hyprbd1a.wav"); gi.soundindex("weapons/hyprbl1a.wav"); self->s.skinnum = 8; self->count = self->s.skinnum - 6; self->health = self->max_health = 60 * st.health_multiplier; self->gib_health = -30; // PMM - blindfire self->monsterinfo.blindfire = true; } /*QUAKED monster_soldier_lasergun (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight */ void SP_monster_soldier_lasergun(edict_t *self) { if ( !M_AllowSpawn( self ) ) { G_FreeEdict( self ); return; } SP_monster_soldier_h(self); sound_pain_ss = gi.soundindex("soldier/solpain3.wav"); sound_death_ss = gi.soundindex("soldier/soldeth3.wav"); gi.soundindex("soldier/solatck3.wav"); self->s.skinnum = 10; self->count = self->s.skinnum - 6; self->health = self->max_health = 70 * st.health_multiplier; self->gib_health = -30; } // END 13-APR-98