/* * Copyright (c) ZeniMax Media Inc. * Licensed under the GNU General Public License 2.0. */ /* ======================================================================= * * The berserker. * * ======================================================================= */ #include "../../header/local.h" #include "berserker.h" static int sound_pain; static int sound_die; static int sound_idle; static int sound_punch; static int sound_sight; static int sound_search; void berserk_sight(edict_t *self, edict_t *other /* unused */) { if (!self) { return; } gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); } void berserk_search(edict_t *self) { if (!self) { return; } 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, 0, NULL}, {ai_stand, 0, NULL}, {ai_stand, 0, NULL}, {ai_stand, 0, NULL} }; mmove_t berserk_move_stand = { FRAME_stand1, FRAME_stand5, berserk_frames_stand, NULL }; void berserk_stand(edict_t *self) { if (!self) { return; } self->monsterinfo.currentmove = &berserk_move_stand; } mframe_t berserk_frames_stand_fidget[] = { {ai_stand, 0, NULL}, {ai_stand, 0, NULL}, {ai_stand, 0, NULL}, {ai_stand, 0, NULL}, {ai_stand, 0, NULL}, {ai_stand, 0, NULL}, {ai_stand, 0, NULL}, {ai_stand, 0, NULL}, {ai_stand, 0, NULL}, {ai_stand, 0, NULL}, {ai_stand, 0, NULL}, {ai_stand, 0, NULL}, {ai_stand, 0, NULL}, {ai_stand, 0, NULL}, {ai_stand, 0, NULL}, {ai_stand, 0, NULL}, {ai_stand, 0, NULL}, {ai_stand, 0, NULL}, {ai_stand, 0, NULL}, {ai_stand, 0, NULL} }; mmove_t berserk_move_stand_fidget = { FRAME_standb1, FRAME_standb20, berserk_frames_stand_fidget, berserk_stand }; void berserk_fidget(edict_t *self) { if (!self) { return; } if (self->enemy) { return; } if (self->monsterinfo.aiflags & AI_STAND_GROUND) { return; } if (random() > 0.15) { return; } self->monsterinfo.currentmove = &berserk_move_stand_fidget; gi.sound(self, CHAN_WEAPON, sound_idle, 1, ATTN_IDLE, 0); } mframe_t berserk_frames_walk[] = { {ai_walk, 9.1, NULL}, {ai_walk, 6.3, NULL}, {ai_walk, 4.9, NULL}, {ai_walk, 6.7, NULL}, {ai_walk, 6.0, NULL}, {ai_walk, 8.2, NULL}, {ai_walk, 7.2, NULL}, {ai_walk, 6.1, NULL}, {ai_walk, 4.9, NULL}, {ai_walk, 4.7, NULL}, {ai_walk, 4.7, NULL}, {ai_walk, 4.8, NULL} }; mmove_t berserk_move_walk = { FRAME_walkc1, FRAME_walkc11, berserk_frames_walk, NULL }; void berserk_walk(edict_t *self) { if (!self) { return; } self->monsterinfo.currentmove = &berserk_move_walk; } mframe_t berserk_frames_run1[] = { {ai_run, 21, NULL}, {ai_run, 11, NULL}, {ai_run, 21, NULL}, {ai_run, 25, NULL}, {ai_run, 18, NULL}, {ai_run, 19, NULL} }; mmove_t berserk_move_run1 = { FRAME_run1, FRAME_run6, berserk_frames_run1, NULL }; void berserk_run(edict_t *self) { if (!self) { return; } if (self->monsterinfo.aiflags & AI_STAND_GROUND) { self->monsterinfo.currentmove = &berserk_move_stand; } else { self->monsterinfo.currentmove = &berserk_move_run1; } } void berserk_attack_spike(edict_t *self) { static vec3_t aim = {MELEE_DISTANCE, 0, -24}; if (!self) { return; } fire_hit(self, aim, (15 + (rand() % 6)), 400); /* Faster attack -- upwards and backwards */ } void berserk_swing(edict_t *self) { if (!self) { return; } gi.sound(self, CHAN_WEAPON, sound_punch, 1, ATTN_NORM, 0); } mframe_t berserk_frames_attack_spike[] = { {ai_charge, 0, NULL}, {ai_charge, 0, NULL}, {ai_charge, 0, berserk_swing}, {ai_charge, 0, berserk_attack_spike}, {ai_charge, 0, NULL}, {ai_charge, 0, NULL}, {ai_charge, 0, NULL}, {ai_charge, 0, NULL} }; 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; if (!self) { return; } VectorSet(aim, MELEE_DISTANCE, self->mins[0], -4); fire_hit(self, aim, (5 + (rand() % 6)), 400); /* Slower attack */ } mframe_t berserk_frames_attack_club[] = { {ai_charge, 0, NULL}, {ai_charge, 0, NULL}, {ai_charge, 0, NULL}, {ai_charge, 0, NULL}, {ai_charge, 0, berserk_swing}, {ai_charge, 0, NULL}, {ai_charge, 0, NULL}, {ai_charge, 0, NULL}, {ai_charge, 0, berserk_attack_club}, {ai_charge, 0, NULL}, {ai_charge, 0, NULL}, {ai_charge, 0, NULL} }; mmove_t berserk_move_attack_club = { FRAME_att_c9, FRAME_att_c20, berserk_frames_attack_club, berserk_run }; void berserk_strike(edict_t *self) { /* Unused, but removal is very PITA. Let it be... */ } mframe_t berserk_frames_attack_strike[] = { {ai_move, 0, NULL}, {ai_move, 0, NULL}, {ai_move, 0, NULL}, {ai_move, 0, berserk_swing}, {ai_move, 0, NULL}, {ai_move, 0, NULL}, {ai_move, 0, NULL}, {ai_move, 0, berserk_strike}, {ai_move, 0, NULL}, {ai_move, 0, NULL}, {ai_move, 0, NULL}, {ai_move, 0, NULL}, {ai_move, 9.7, NULL}, {ai_move, 13.6, NULL} }; mmove_t berserk_move_attack_strike = { FRAME_att_c21, FRAME_att_c34, berserk_frames_attack_strike, berserk_run }; void berserk_melee(edict_t *self) { if (!self) { return; } if ((rand() % 2) == 0) { self->monsterinfo.currentmove = &berserk_move_attack_spike; } else { self->monsterinfo.currentmove = &berserk_move_attack_club; } } mframe_t berserk_frames_pain1[] = { {ai_move, 0, NULL}, {ai_move, 0, NULL}, {ai_move, 0, NULL}, {ai_move, 0, NULL} }; mmove_t berserk_move_pain1 = { FRAME_painc1, FRAME_painc4, berserk_frames_pain1, berserk_run }; mframe_t berserk_frames_pain2[] = { {ai_move, 0, NULL}, {ai_move, 0, NULL}, {ai_move, 0, NULL}, {ai_move, 0, NULL}, {ai_move, 0, NULL}, {ai_move, 0, NULL}, {ai_move, 0, NULL}, {ai_move, 0, NULL}, {ai_move, 0, NULL}, {ai_move, 0, NULL}, {ai_move, 0, NULL}, {ai_move, 0, NULL}, {ai_move, 0, NULL}, {ai_move, 0, NULL}, {ai_move, 0, NULL}, {ai_move, 0, NULL}, {ai_move, 0, NULL}, {ai_move, 0, NULL}, {ai_move, 0, NULL}, {ai_move, 0, NULL} }; mmove_t berserk_move_pain2 = { FRAME_painb1, FRAME_painb20, berserk_frames_pain2, berserk_run }; void berserk_pain(edict_t *self, edict_t *other /* unsued */, float kick /* unused */, int damage) { if (!self) { return; } if (self->health < (self->max_health / 2)) { self->s.skinnum = 1; } if (level.time < self->pain_debounce_time) { return; } self->pain_debounce_time = level.time + 3; gi.sound(self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0); if (skill->value == SKILL_HARDPLUS) { return; /* no pain anims in nightmare */ } if ((damage < 20) || (random() < 0.5)) { self->monsterinfo.currentmove = &berserk_move_pain1; } else { self->monsterinfo.currentmove = &berserk_move_pain2; } } void berserk_dead(edict_t *self) { if (!self) { return; } VectorSet(self->mins, -16, -16, -24); VectorSet(self->maxs, 16, 16, -8); self->movetype = MOVETYPE_TOSS; self->svflags |= SVF_DEADMONSTER; self->nextthink = 0; gi.linkentity(self); } mframe_t berserk_frames_death1[] = { {ai_move, 0, NULL}, {ai_move, 0, NULL}, {ai_move, 0, NULL}, {ai_move, 0, NULL}, {ai_move, 0, NULL}, {ai_move, 0, NULL}, {ai_move, 0, NULL}, {ai_move, 0, NULL}, {ai_move, 0, NULL}, {ai_move, 0, NULL}, {ai_move, 0, NULL}, {ai_move, 0, NULL}, {ai_move, 0, NULL} }; mmove_t berserk_move_death1 = { FRAME_death1, FRAME_death13, berserk_frames_death1, berserk_dead }; mframe_t berserk_frames_death2[] = { {ai_move, 0, NULL}, {ai_move, 0, NULL}, {ai_move, 0, NULL}, {ai_move, 0, NULL}, {ai_move, 0, NULL}, {ai_move, 0, NULL}, {ai_move, 0, NULL}, {ai_move, 0, NULL} }; mmove_t berserk_move_death2 = { FRAME_deathc1, FRAME_deathc8, berserk_frames_death2, berserk_dead }; void berserk_die(edict_t *self, edict_t *inflictor /* unsued */, edict_t *attacker /* unused */, int damage, vec3_t point /* unused */) { int n; if (!self) { return; } if (self->health <= self->gib_health) { gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0); for (n = 0; n < 2; n++) { ThrowGib(self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC); } for (n = 0; n < 4; n++) { ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); } ThrowHead(self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC); self->deadflag = DEAD_DEAD; return; } if (self->deadflag == DEAD_DEAD) { return; } gi.sound(self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0); self->deadflag = DEAD_DEAD; self->takedamage = DAMAGE_YES; if (damage >= 50) { self->monsterinfo.currentmove = &berserk_move_death1; } else { self->monsterinfo.currentmove = &berserk_move_death2; } } /* * QUAKED monster_berserk (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight */ void SP_monster_berserk(edict_t *self) { if (!self) { return; } if (deathmatch->value) { 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_punch = gi.soundindex("berserk/attack.wav"); sound_search = gi.soundindex("berserk/bersrch1.wav"); sound_sight = gi.soundindex("berserk/sight.wav"); self->s.modelindex = gi.modelindex("models/monsters/berserk/tris.md2"); VectorSet(self->mins, -16, -16, -24); VectorSet(self->maxs, 16, 16, 32); self->movetype = MOVETYPE_STEP; self->solid = SOLID_BBOX; self->health = 240; 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; self->monsterinfo.dodge = NULL; self->monsterinfo.attack = NULL; self->monsterinfo.melee = berserk_melee; self->monsterinfo.sight = berserk_sight; self->monsterinfo.search = berserk_search; self->monsterinfo.currentmove = &berserk_move_stand; self->monsterinfo.scale = MODEL_SCALE; gi.linkentity(self); walkmonster_start(self); }