// Copyright (c) ZeniMax Media Inc. // Licensed under the GNU General Public License 2.0. /* ============================================================================== BERSERK ============================================================================== */ #include "g_local.h" #include "m_berserk.h" constexpr spawnflags_t SPAWNFLAG_BERSERK_NOJUMPING = 8_spawnflag; static int sound_pain; static int sound_die; static int sound_idle; static int sound_idle2; static int sound_punch; static int sound_sight; static int sound_search; static int sound_thud; static int sound_jump; MONSTERINFO_SIGHT(berserk_sight) (edict_t *self, edict_t *other) -> void { gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); } MONSTERINFO_SEARCH(berserk_search) (edict_t *self) -> void { if (brandom()) gi.sound(self, CHAN_VOICE, sound_idle2, 1, ATTN_NORM, 0); else gi.sound(self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0); } void berserk_fidget(edict_t *self); mframe_t berserk_frames_stand[] = { { ai_stand, 0, berserk_fidget }, { ai_stand }, { ai_stand }, { ai_stand }, { ai_stand } }; MMOVE_T(berserk_move_stand) = { FRAME_stand1, FRAME_stand5, berserk_frames_stand, nullptr }; MONSTERINFO_STAND(berserk_stand) (edict_t *self) -> void { M_SetAnimation(self, &berserk_move_stand); } mframe_t berserk_frames_stand_fidget[] = { { 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(berserk_move_stand_fidget) = { FRAME_standb1, FRAME_standb20, berserk_frames_stand_fidget, berserk_stand }; void berserk_fidget(edict_t *self) { if (self->monsterinfo.aiflags & AI_STAND_GROUND) return; else if (self->enemy) return; if (frandom() > 0.15f) return; M_SetAnimation(self, &berserk_move_stand_fidget); gi.sound(self, CHAN_WEAPON, sound_idle, 1, ATTN_IDLE, 0); } mframe_t berserk_frames_walk[] = { { ai_walk, 9.1f }, { ai_walk, 6.3f }, { ai_walk, 4.9f }, { ai_walk, 6.7f, monster_footstep }, { ai_walk, 6.0f }, { ai_walk, 8.2f }, { ai_walk, 7.2f }, { ai_walk, 6.1f }, { ai_walk, 4.9f }, { ai_walk, 4.7f, monster_footstep }, { ai_walk, 4.7f } }; MMOVE_T(berserk_move_walk) = { FRAME_walkc1, FRAME_walkc11, berserk_frames_walk, nullptr }; MONSTERINFO_WALK(berserk_walk) (edict_t *self) -> void { M_SetAnimation(self, &berserk_move_walk); } /* ***************************** SKIPPED THIS FOR NOW! ***************************** Running -> Arm raised in air void() berserk_runb1 =[ $r_att1 , berserk_runb2 ] {ai_run(21);}; void() berserk_runb2 =[ $r_att2 , berserk_runb3 ] {ai_run(11);}; void() berserk_runb3 =[ $r_att3 , berserk_runb4 ] {ai_run(21);}; void() berserk_runb4 =[ $r_att4 , berserk_runb5 ] {ai_run(25);}; void() berserk_runb5 =[ $r_att5 , berserk_runb6 ] {ai_run(18);}; void() berserk_runb6 =[ $r_att6 , berserk_runb7 ] {ai_run(19);}; // running with arm in air : start loop void() berserk_runb7 =[ $r_att7 , berserk_runb8 ] {ai_run(21);}; void() berserk_runb8 =[ $r_att8 , berserk_runb9 ] {ai_run(11);}; void() berserk_runb9 =[ $r_att9 , berserk_runb10 ] {ai_run(21);}; void() berserk_runb10 =[ $r_att10 , berserk_runb11 ] {ai_run(25);}; void() berserk_runb11 =[ $r_att11 , berserk_runb12 ] {ai_run(18);}; void() berserk_runb12 =[ $r_att12 , berserk_runb7 ] {ai_run(19);}; // running with arm in air : end loop */ mframe_t berserk_frames_run1[] = { { ai_run, 21 }, { ai_run, 11, monster_footstep }, { ai_run, 21 }, { ai_run, 25, monster_done_dodge }, { ai_run, 18, monster_footstep }, { ai_run, 19 } }; MMOVE_T(berserk_move_run1) = { FRAME_run1, FRAME_run6, berserk_frames_run1, nullptr }; MONSTERINFO_RUN(berserk_run) (edict_t *self) -> void { monster_done_dodge(self); if (self->monsterinfo.aiflags & AI_STAND_GROUND) M_SetAnimation(self, &berserk_move_stand); else M_SetAnimation(self, &berserk_move_run1); } void berserk_attack_spike(edict_t *self) { constexpr vec3_t aim = { MELEE_DISTANCE, 0, -24 }; if (!fire_hit(self, aim, irandom(5, 11), 80)) // Faster attack -- upwards and backwards self->monsterinfo.melee_debounce_time = level.time + 1.2_sec; } void berserk_swing(edict_t *self) { gi.sound(self, CHAN_WEAPON, sound_punch, 1, ATTN_NORM, 0); } mframe_t berserk_frames_attack_spike[] = { { ai_charge }, { ai_charge }, { ai_charge, 0, berserk_swing }, { ai_charge, 0, berserk_attack_spike }, { ai_charge }, { ai_charge }, { ai_charge }, { ai_charge } }; MMOVE_T(berserk_move_attack_spike) = { FRAME_att_c1, FRAME_att_c8, berserk_frames_attack_spike, berserk_run }; void berserk_attack_club(edict_t *self) { vec3_t aim = { MELEE_DISTANCE, self->mins[0], -4 }; if (!fire_hit(self, aim, irandom(15, 21), 400)) // Slower attack self->monsterinfo.melee_debounce_time = level.time + 2.5_sec; } mframe_t berserk_frames_attack_club[] = { { ai_charge }, { ai_charge }, { ai_charge, 0, monster_footstep }, { ai_charge }, { ai_charge, 0, berserk_swing }, { ai_charge }, { ai_charge, 0, berserk_attack_club }, { ai_charge }, { ai_charge }, { ai_charge }, { ai_charge }, { ai_charge } }; MMOVE_T(berserk_move_attack_club) = { FRAME_att_c9, FRAME_att_c20, berserk_frames_attack_club, berserk_run }; /* ============ T_RadiusDamage ============ */ void T_SlamRadiusDamage(vec3_t point, edict_t *inflictor, edict_t *attacker, float damage, float kick, edict_t *ignore, float radius, mod_t mod) { float points; edict_t *ent = nullptr; vec3_t v; vec3_t dir; while ((ent = findradius(ent, inflictor->s.origin, radius)) != nullptr) { if (ent == ignore) continue; if (!ent->takedamage) continue; if (!CanDamage(ent, inflictor)) continue; v = closest_point_to_box(point, ent->s.origin + ent->mins, ent->s.origin + ent->maxs) - point; points = damage - 0.5f * v.length(); if (ent == attacker) points = points * 0.5f; points = max(1.f, points); dir = (ent->s.origin - point).normalized(); // keep the point at their feet so they always get knocked up point[2] = ent->absmin[2]; T_Damage(ent, inflictor, attacker, dir, point, dir, (int) points, (int) kick, DAMAGE_RADIUS, mod); if (ent->client) ent->velocity.z = max(270.f, ent->velocity.z); } } static void berserk_attack_slam(edict_t *self) { gi.sound(self, CHAN_WEAPON, sound_thud, 1, ATTN_NORM, 0); gi.WriteByte(svc_temp_entity); gi.WriteByte(TE_BERSERK_SLAM); vec3_t f, r, start; AngleVectors(self->s.angles, f, r, nullptr); start = M_ProjectFlashSource(self, { 20.f, -14.3f, -21.f }, f, r); trace_t tr = gi.traceline(self->s.origin, start, self, MASK_SOLID); gi.WritePosition(tr.endpos); gi.WriteDir({ 0.f, 0.f, 1.f }); gi.multicast(tr.endpos, MULTICAST_PHS, false); self->gravity = 1.0f; self->velocity = {}; self->flags |= FL_KILL_VELOCITY; T_SlamRadiusDamage(tr.endpos, self, self, 35, 150.f, self, 275, MOD_UNKNOWN); } TOUCH(berserk_jump_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void { if (self->health <= 0) { self->touch = nullptr; return; } if (self->groundentity) { self->s.frame = FRAME_slam18; if (self->touch) berserk_attack_slam(self); self->touch = nullptr; } } static void berserk_high_gravity(edict_t *self) { if (self->velocity[2] < 0) self->gravity = 2.25f * (800.f / level.gravity); else self->gravity = 5.25f * (800.f / level.gravity); } void berserk_jump_takeoff(edict_t *self) { vec3_t forward; if (!self->enemy) return; // immediately turn to where we need to go float length = (self->s.origin - self->enemy->s.origin).length(); float fwd_speed = length * 1.95f; vec3_t dir; PredictAim(self, self->enemy, self->s.origin, fwd_speed, false, 0.f, &dir, nullptr); self->s.angles[1] = vectoyaw(dir); AngleVectors(self->s.angles, forward, nullptr, nullptr); self->s.origin[2] += 1; self->velocity = forward * fwd_speed; self->velocity[2] = 450; self->groundentity = nullptr; self->monsterinfo.aiflags |= AI_DUCKED; self->monsterinfo.attack_finished = level.time + 3_sec; self->touch = berserk_jump_touch; gi.sound(self, CHAN_WEAPON, sound_jump, 1, ATTN_NORM, 0); berserk_high_gravity(self); } void berserk_check_landing(edict_t *self) { berserk_high_gravity(self); if (self->groundentity) { self->monsterinfo.attack_finished = 0_ms; self->monsterinfo.unduck(self); self->s.frame = FRAME_slam18; if (self->touch) { berserk_attack_slam(self); self->touch = nullptr; } self->flags &= ~FL_KILL_VELOCITY; return; } if (level.time > self->monsterinfo.attack_finished) self->monsterinfo.nextframe = FRAME_slam2; else self->monsterinfo.nextframe = FRAME_slam5; } mframe_t berserk_frames_attack_strike[] = { { ai_charge }, { ai_charge, 0, berserk_jump_takeoff }, { ai_move, 0, berserk_high_gravity }, { ai_move, 0, berserk_high_gravity }, { ai_move, 0, berserk_check_landing }, { ai_move, 0, monster_footstep }, { ai_move }, { ai_move, 0, monster_footstep }, { 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, monster_footstep } }; MMOVE_T(berserk_move_attack_strike) = { FRAME_slam1, FRAME_slam23, berserk_frames_attack_strike, berserk_run }; extern const mmove_t berserk_move_run_attack1; MONSTERINFO_MELEE(berserk_melee) (edict_t *self) -> void { if (self->monsterinfo.melee_debounce_time > level.time) return; // if we're *almost* ready to land down the hammer from run-attack // don't switch us else if (self->monsterinfo.active_move == &berserk_move_run_attack1 && self->s.frame >= FRAME_r_att13) { self->monsterinfo.attack_state = AS_STRAIGHT; self->monsterinfo.attack_finished = 0_ms; return; } monster_done_dodge(self); if (brandom()) M_SetAnimation(self, &berserk_move_attack_spike); else M_SetAnimation(self, &berserk_move_attack_club); } static void berserk_run_attack_speed(edict_t *self) { if (self->enemy && range_to(self, self->enemy) < MELEE_DISTANCE) { self->monsterinfo.nextframe = self->s.frame + 6; monster_done_dodge(self); } } static void berserk_run_swing(edict_t *self) { berserk_swing(self); self->monsterinfo.melee_debounce_time = level.time + 0.6_sec; if (self->monsterinfo.attack_state == AS_SLIDING) { self->monsterinfo.attack_state = AS_STRAIGHT; monster_done_dodge(self); } } mframe_t berserk_frames_run_attack1[] = { { ai_run, 21, berserk_run_attack_speed }, { ai_run, 11, [](edict_t *self) { berserk_run_attack_speed(self); monster_footstep(self); } }, { ai_run, 21, berserk_run_attack_speed }, { ai_run, 25, [](edict_t *self) { berserk_run_attack_speed(self); monster_done_dodge(self); } }, { ai_run, 18, [](edict_t *self) { berserk_run_attack_speed(self); monster_footstep(self); } }, { ai_run, 19, berserk_run_attack_speed }, { ai_run, 21 }, { ai_run, 11, monster_footstep }, { ai_run, 21 }, { ai_run, 25 }, { ai_run, 18, monster_footstep }, { ai_run, 19 }, { ai_run, 21, berserk_run_swing }, { ai_run, 11, monster_footstep }, { ai_run, 21 }, { ai_run, 25 }, { ai_run, 18, monster_footstep }, { ai_run, 19, berserk_attack_club } }; MMOVE_T(berserk_move_run_attack1) = { FRAME_r_att1, FRAME_r_att18, berserk_frames_run_attack1, berserk_run }; MONSTERINFO_ATTACK(berserk_attack) (edict_t *self) -> void { if (self->monsterinfo.melee_debounce_time <= level.time && (range_to(self, self->enemy) < MELEE_DISTANCE)) berserk_melee(self); // only jump if they are far enough away for it to make sense (otherwise // it gets annoying to have them keep hopping over and over again) else if (!self->spawnflags.has(SPAWNFLAG_BERSERK_NOJUMPING) && (self->timestamp < level.time && brandom()) && range_to(self, self->enemy) > 150.f) { M_SetAnimation(self, &berserk_move_attack_strike); // don't do this for a while, otherwise we just keep doing it self->timestamp = level.time + 5_sec; } else if (self->monsterinfo.active_move == &berserk_move_run1 && (range_to(self, self->enemy) <= RANGE_NEAR)) { M_SetAnimation(self, &berserk_move_run_attack1); self->monsterinfo.nextframe = FRAME_r_att1 + (self->s.frame - FRAME_run1) + 1; } } mframe_t berserk_frames_pain1[] = { { ai_move }, { ai_move }, { ai_move }, { ai_move } }; MMOVE_T(berserk_move_pain1) = { FRAME_painc1, FRAME_painc4, berserk_frames_pain1, berserk_run }; mframe_t berserk_frames_pain2[] = { { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move, 0, monster_footstep }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move, 0, monster_footstep }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move, 0, monster_footstep } }; MMOVE_T(berserk_move_pain2) = { FRAME_painb1, FRAME_painb20, berserk_frames_pain2, berserk_run }; extern const mmove_t berserk_move_jump, berserk_move_jump2; PAIN(berserk_pain) (edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod) -> void { // if we're jumping, don't pain if ((self->monsterinfo.active_move == &berserk_move_jump) || (self->monsterinfo.active_move == &berserk_move_jump2) || (self->monsterinfo.active_move == &berserk_move_attack_strike)) { return; } if (level.time < self->pain_debounce_time) return; self->pain_debounce_time = level.time + 3_sec; gi.sound(self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0); if (!M_ShouldReactToPain(self, mod)) return; // no pain anims in nightmare monster_done_dodge(self); if ((damage <= 50) || (frandom() < 0.5f)) M_SetAnimation(self, &berserk_move_pain1); else M_SetAnimation(self, &berserk_move_pain2); } MONSTERINFO_SETSKIN(berserk_setskin) (edict_t *self) -> void { if (self->health < (self->max_health / 2)) self->s.skinnum = 1; else self->s.skinnum = 0; } void berserk_dead(edict_t *self) { self->mins = { -16, -16, -24 }; self->maxs = { 16, 16, -8 }; monster_dead(self); } static void berserk_shrink(edict_t *self) { self->maxs[2] = 0; self->svflags |= SVF_DEADMONSTER; gi.linkentity(self); } mframe_t berserk_frames_death1[] = { { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move, 0, monster_footstep }, { ai_move }, { ai_move, 0, berserk_shrink }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move }, { ai_move } }; MMOVE_T(berserk_move_death1) = { FRAME_death1, FRAME_death13, berserk_frames_death1, berserk_dead }; mframe_t berserk_frames_death2[] = { { ai_move }, { ai_move }, { ai_move }, { ai_move, 0, berserk_shrink }, { ai_move, 0, monster_footstep }, { ai_move }, { ai_move }, { ai_move } }; MMOVE_T(berserk_move_death2) = { FRAME_deathc1, FRAME_deathc8, berserk_frames_death2, berserk_dead }; DIE(berserk_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void { if (M_CheckGib(self, mod)) { gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0); self->s.skinnum = 0; ThrowGibs(self, damage, { { 2, "models/objects/gibs/bone/tris.md2" }, { 3, "models/objects/gibs/sm_meat/tris.md2" }, { 1, "models/objects/gibs/gear/tris.md2" }, { "models/monsters/berserk/gibs/chest.md2", GIB_SKINNED }, { "models/monsters/berserk/gibs/hammer.md2", GIB_SKINNED | GIB_UPRIGHT }, { "models/monsters/berserk/gibs/thigh.md2", GIB_SKINNED }, { "models/monsters/berserk/gibs/head.md2", GIB_HEAD | GIB_SKINNED } }); self->deadflag = true; return; } if (self->deadflag) return; gi.sound(self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0); self->deadflag = true; self->takedamage = true; if (damage >= 50) M_SetAnimation(self, &berserk_move_death1); else M_SetAnimation(self, &berserk_move_death2); } //=========== // PGM void berserk_jump_now(edict_t *self) { vec3_t forward, up; AngleVectors(self->s.angles, forward, nullptr, up); self->velocity += (forward * 100); self->velocity += (up * 300); } void berserk_jump2_now(edict_t *self) { vec3_t forward, up; AngleVectors(self->s.angles, forward, nullptr, up); self->velocity += (forward * 150); self->velocity += (up * 400); } void berserk_jump_wait_land(edict_t *self) { if (self->groundentity == nullptr) { self->monsterinfo.nextframe = self->s.frame; if (monster_jump_finished(self)) self->monsterinfo.nextframe = self->s.frame + 1; } else self->monsterinfo.nextframe = self->s.frame + 1; } mframe_t berserk_frames_jump[] = { { ai_move }, { ai_move }, { ai_move }, { ai_move, 0, berserk_jump_now }, { ai_move }, { ai_move }, { ai_move, 0, berserk_jump_wait_land }, { ai_move }, { ai_move } }; MMOVE_T(berserk_move_jump) = { FRAME_jump1, FRAME_jump9, berserk_frames_jump, berserk_run }; mframe_t berserk_frames_jump2[] = { { ai_move, -8 }, { ai_move, -4 }, { ai_move, -4 }, { ai_move, 0, berserk_jump2_now }, { ai_move }, { ai_move }, { ai_move, 0, berserk_jump_wait_land }, { ai_move }, { ai_move } }; MMOVE_T(berserk_move_jump2) = { FRAME_jump1, FRAME_jump9, berserk_frames_jump2, berserk_run }; void berserk_jump(edict_t *self, blocked_jump_result_t result) { if (!self->enemy) return; if (result == blocked_jump_result_t::JUMP_JUMP_UP) M_SetAnimation(self, &berserk_move_jump2); else M_SetAnimation(self, &berserk_move_jump); } MONSTERINFO_BLOCKED(berserk_blocked) (edict_t *self, float dist) -> bool { if (auto result = blocked_checkjump(self, dist); result != blocked_jump_result_t::NO_JUMP) { if (result != blocked_jump_result_t::JUMP_TURN) berserk_jump(self, result); return true; } if (blocked_checkplat(self, dist)) return true; return false; } // PGM //=========== MONSTERINFO_SIDESTEP(berserk_sidestep) (edict_t *self) -> bool { // if we're jumping or in long pain, don't dodge if ((self->monsterinfo.active_move == &berserk_move_jump) || (self->monsterinfo.active_move == &berserk_move_jump2) || (self->monsterinfo.active_move == &berserk_move_attack_strike) || (self->monsterinfo.active_move == &berserk_move_pain2)) return false; if (self->monsterinfo.active_move != &berserk_move_run1) M_SetAnimation(self, &berserk_move_run1); return true; } mframe_t berserk_frames_duck[] = { { ai_move, 0, monster_duck_down }, { ai_move }, { ai_move }, { ai_move }, { ai_move, 0, monster_duck_hold }, { ai_move }, { ai_move }, { ai_move, 0, monster_duck_up }, { ai_move }, { ai_move } }; MMOVE_T(berserk_move_duck) = { FRAME_duck1, FRAME_duck10, berserk_frames_duck, berserk_run }; mframe_t berserk_frames_duck2[] = { { ai_move, 21, monster_duck_down }, { ai_move, 28 }, { ai_move, 20 }, { ai_move, 12, monster_footstep }, { ai_move, 7 }, { ai_move, 0, monster_footstep }, { ai_move }, { ai_move, 0, monster_duck_hold }, { ai_move, 0 }, { ai_move, 0 }, { ai_move, 0 }, { ai_move, 0 }, { ai_move, 0, monster_footstep }, { ai_move, 0, monster_duck_up }, { ai_move }, { ai_move }, { ai_move, 0, monster_footstep }, }; MMOVE_T(berserk_move_duck2) = { FRAME_fall2, FRAME_fall18, berserk_frames_duck2, berserk_run }; MONSTERINFO_DUCK(berserk_duck) (edict_t *self, gtime_t eta) -> bool { // berserk only dives forward, and very rarely if (frandom() >= 0.05f) { return false; } // if we're jumping, don't dodge if ((self->monsterinfo.active_move == &berserk_move_jump) || (self->monsterinfo.active_move == &berserk_move_jump2)) { return false; } M_SetAnimation(self, &berserk_move_duck2); return true; } /*QUAKED monster_berserk (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight */ void SP_monster_berserk(edict_t *self) { if ( !M_AllowSpawn( self ) ) { G_FreeEdict( self ); return; } // pre-caches sound_pain = gi.soundindex("berserk/berpain2.wav"); sound_die = gi.soundindex("berserk/berdeth2.wav"); sound_idle = gi.soundindex("berserk/beridle1.wav"); sound_idle2 = gi.soundindex("berserk/idle.wav"); sound_punch = gi.soundindex("berserk/attack.wav"); sound_search = gi.soundindex("berserk/bersrch1.wav"); sound_sight = gi.soundindex("berserk/sight.wav"); sound_thud = gi.soundindex("mutant/thud1.wav"); sound_jump = gi.soundindex("berserk/jump.wav"); self->s.modelindex = gi.modelindex("models/monsters/berserk/tris.md2"); gi.modelindex("models/monsters/berserk/gibs/head.md2"); gi.modelindex("models/monsters/berserk/gibs/chest.md2"); gi.modelindex("models/monsters/berserk/gibs/hammer.md2"); gi.modelindex("models/monsters/berserk/gibs/thigh.md2"); self->mins = { -16, -16, -24 }; self->maxs = { 16, 16, 32 }; self->movetype = MOVETYPE_STEP; self->solid = SOLID_BBOX; self->health = 240 * st.health_multiplier; self->gib_health = -60; self->mass = 250; self->pain = berserk_pain; self->die = berserk_die; self->monsterinfo.stand = berserk_stand; self->monsterinfo.walk = berserk_walk; self->monsterinfo.run = berserk_run; // pmm self->monsterinfo.dodge = M_MonsterDodge; self->monsterinfo.duck = berserk_duck; self->monsterinfo.unduck = monster_duck_up; self->monsterinfo.sidestep = berserk_sidestep; self->monsterinfo.blocked = berserk_blocked; // PGM // pmm self->monsterinfo.attack = berserk_attack; self->monsterinfo.melee = berserk_melee; self->monsterinfo.sight = berserk_sight; self->monsterinfo.search = berserk_search; self->monsterinfo.setskin = berserk_setskin; M_SetAnimation(self, &berserk_move_stand); self->monsterinfo.scale = MODEL_SCALE; self->monsterinfo.combat_style = COMBAT_MELEE; self->monsterinfo.can_jump = !self->spawnflags.has(SPAWNFLAG_BERSERK_NOJUMPING); self->monsterinfo.drop_height = 256; self->monsterinfo.jump_height = 40; gi.linkentity(self); walkmonster_start(self); }