123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524 |
- // Copyright (c) ZeniMax Media Inc.
- // Licensed under the GNU General Public License 2.0.
- /*
- ==============================================================================
- GUARDIAN
- ==============================================================================
- */
- #include "g_local.h"
- #include "m_guardian.h"
- #include "m_flash.h"
- //
- // stand
- //
- mframe_t guardian_frames_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 },
- { 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(guardian_move_stand) = { FRAME_idle1, FRAME_idle52, guardian_frames_stand, nullptr };
- MONSTERINFO_STAND(guardian_stand) (edict_t *self) -> void
- {
- M_SetAnimation(self, &guardian_move_stand);
- }
- //
- // walk
- //
- static cached_soundindex sound_step;
- void guardian_footstep(edict_t *self)
- {
- gi.sound(self, CHAN_BODY, sound_step, 1.f, ATTN_NORM, 0.0f);
- }
- mframe_t guardian_frames_walk[] = {
- { ai_walk, 8 },
- { ai_walk, 8 },
- { ai_walk, 8 },
- { ai_walk, 8 },
- { ai_walk, 8 },
- { ai_walk, 8 },
- { ai_walk, 8 },
- { ai_walk, 8, guardian_footstep },
- { ai_walk, 8 },
- { ai_walk, 8 },
- { ai_walk, 8 },
- { ai_walk, 8 },
- { ai_walk, 8 },
- { ai_walk, 8 },
- { ai_walk, 8 },
- { ai_walk, 8 },
- { ai_walk, 8 },
- { ai_walk, 8, guardian_footstep },
- { ai_walk, 8 }
- };
- MMOVE_T(guardian_move_walk) = { FRAME_walk1, FRAME_walk19, guardian_frames_walk, nullptr };
- MONSTERINFO_WALK(guardian_walk) (edict_t *self) -> void
- {
- M_SetAnimation(self, &guardian_move_walk);
- }
- //
- // run
- //
- mframe_t guardian_frames_run[] = {
- { ai_run, 8 },
- { ai_run, 8 },
- { ai_run, 8 },
- { ai_run, 8 },
- { ai_run, 8 },
- { ai_run, 8 },
- { ai_run, 8 },
- { ai_run, 8, guardian_footstep },
- { ai_run, 8 },
- { ai_run, 8 },
- { ai_run, 8 },
- { ai_run, 8 },
- { ai_run, 8 },
- { ai_run, 8 },
- { ai_run, 8 },
- { ai_run, 8 },
- { ai_run, 8 },
- { ai_run, 8, guardian_footstep },
- { ai_run, 8 }
- };
- MMOVE_T(guardian_move_run) = { FRAME_walk1, FRAME_walk19, guardian_frames_run, nullptr };
- MONSTERINFO_RUN(guardian_run) (edict_t *self) -> void
- {
- if (self->monsterinfo.aiflags & AI_STAND_GROUND)
- {
- M_SetAnimation(self, &guardian_move_stand);
- return;
- }
- M_SetAnimation(self, &guardian_move_run);
- }
- //
- // pain
- //
- mframe_t guardian_frames_pain1[] = {
- { ai_move },
- { ai_move },
- { ai_move },
- { ai_move },
- { ai_move },
- { ai_move },
- { ai_move },
- { ai_move }
- };
- MMOVE_T(guardian_move_pain1) = { FRAME_pain1_1, FRAME_pain1_8, guardian_frames_pain1, guardian_run };
- PAIN(guardian_pain) (edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod) -> void
- {
- if (mod.id != MOD_CHAINFIST && damage <= 10)
- return;
- if (level.time < self->pain_debounce_time)
- return;
- if (mod.id != MOD_CHAINFIST && damage <= 75)
- if (frandom() > 0.2f)
- return;
- // don't go into pain while attacking
- if ((self->s.frame >= FRAME_atk1_spin1) && (self->s.frame <= FRAME_atk1_spin15))
- return;
- if ((self->s.frame >= FRAME_atk2_fire1) && (self->s.frame <= FRAME_atk2_fire4))
- return;
- if ((self->s.frame >= FRAME_kick_in1) && (self->s.frame <= FRAME_kick_in13))
- 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
- M_SetAnimation(self, &guardian_move_pain1);
- self->monsterinfo.weapon_sound = 0;
- }
- mframe_t guardian_frames_atk1_out[] = {
- { ai_charge },
- { ai_charge },
- { ai_charge }
- };
- MMOVE_T(guardian_atk1_out) = { FRAME_atk1_out1, FRAME_atk1_out3, guardian_frames_atk1_out, guardian_run };
- void guardian_atk1_finish(edict_t *self)
- {
- M_SetAnimation(self, &guardian_atk1_out);
- self->monsterinfo.weapon_sound = 0;
- }
- static cached_soundindex sound_charge;
- static cached_soundindex sound_spin_loop;
- void guardian_atk1_charge(edict_t *self)
- {
- self->monsterinfo.weapon_sound = sound_spin_loop;
- gi.sound(self, CHAN_WEAPON, sound_charge, 1.f, ATTN_NORM, 0.f);
- }
- void guardian_fire_blaster(edict_t *self)
- {
- vec3_t forward, right, target;
- vec3_t start;
- monster_muzzleflash_id_t id = MZ2_GUARDIAN_BLASTER;
- AngleVectors(self->s.angles, forward, right, nullptr);
- start = M_ProjectFlashSource(self, monster_flash_offset[id], forward, right);
- target = self->enemy->s.origin;
- target[2] += self->enemy->viewheight;
- for (int i = 0; i < 3; i++)
- target[i] += crandom_open() * 5.f;
- forward = target - start;
- forward.normalize();
- monster_fire_blaster(self, start, forward, 2, 1000, id, (self->s.frame % 4) ? EF_NONE : EF_HYPERBLASTER);
- if (self->enemy && self->enemy->health > 0 &&
- self->s.frame == FRAME_atk1_spin12 && self->timestamp > level.time && visible(self, self->enemy))
- self->monsterinfo.nextframe = FRAME_atk1_spin5;
- }
- mframe_t guardian_frames_atk1_spin[] = {
- { ai_charge, 0, guardian_atk1_charge },
- { ai_charge },
- { ai_charge },
- { ai_charge },
- { ai_charge, 0, guardian_fire_blaster },
- { ai_charge, 0, guardian_fire_blaster },
- { ai_charge, 0, guardian_fire_blaster },
- { ai_charge, 0, guardian_fire_blaster },
- { ai_charge, 0, guardian_fire_blaster },
- { ai_charge, 0, guardian_fire_blaster },
- { ai_charge, 0, guardian_fire_blaster },
- { ai_charge, 0, guardian_fire_blaster },
- { ai_charge, 0 },
- { ai_charge, 0 },
- { ai_charge, 0 }
- };
- MMOVE_T(guardian_move_atk1_spin) = { FRAME_atk1_spin1, FRAME_atk1_spin15, guardian_frames_atk1_spin, guardian_atk1_finish };
- void guardian_atk1(edict_t *self)
- {
- M_SetAnimation(self, &guardian_move_atk1_spin);
- self->timestamp = level.time + 650_ms + random_time(1.5_sec);
- }
- mframe_t guardian_frames_atk1_in[] = {
- { ai_charge },
- { ai_charge },
- { ai_charge }
- };
- MMOVE_T(guardian_move_atk1_in) = { FRAME_atk1_in1, FRAME_atk1_in3, guardian_frames_atk1_in, guardian_atk1 };
- mframe_t guardian_frames_atk2_out[] = {
- { ai_charge },
- { ai_charge },
- { ai_charge },
- { ai_charge },
- { ai_charge, 0, guardian_footstep },
- { ai_charge },
- { ai_charge }
- };
- MMOVE_T(guardian_move_atk2_out) = { FRAME_atk2_out1, FRAME_atk2_out7, guardian_frames_atk2_out, guardian_run };
- void guardian_atk2_out(edict_t *self)
- {
- M_SetAnimation(self, &guardian_move_atk2_out);
- }
- static cached_soundindex sound_laser;
- constexpr vec3_t laser_positions[] = {
- { 125.0f, -70.f, 60.f },
- { 112.0f, -62.f, 60.f }
- };
- PRETHINK(guardian_fire_update) (edict_t *laser) -> void
- {
- edict_t *self = laser->owner;
- vec3_t forward, right, target;
- vec3_t start;
- AngleVectors(self->s.angles, forward, right, nullptr);
- start = M_ProjectFlashSource(self, laser_positions[1 - (self->s.frame & 1)], forward, right);
- target = self->enemy->s.origin + self->enemy->mins;
- for (int i = 0; i < 3; i++)
- target[i] += frandom() * self->enemy->size[i];
- forward = target - start;
- forward.normalize();
- laser->s.origin = start;
- laser->movedir = forward;
- gi.linkentity(laser);
- dabeam_update(laser, false);
- }
- void guardian_laser_fire(edict_t *self)
- {
- gi.sound(self, CHAN_WEAPON, sound_laser, 1.f, ATTN_NORM, 0.f);
- monster_fire_dabeam(self, 25, self->s.frame & 1, guardian_fire_update);
- }
- mframe_t guardian_frames_atk2_fire[] = {
- { ai_charge, 0, guardian_laser_fire },
- { ai_charge, 0, guardian_laser_fire },
- { ai_charge, 0, guardian_laser_fire },
- { ai_charge, 0, guardian_laser_fire }
- };
- MMOVE_T(guardian_move_atk2_fire) = { FRAME_atk2_fire1, FRAME_atk2_fire4, guardian_frames_atk2_fire, guardian_atk2_out };
- void guardian_atk2(edict_t *self)
- {
- M_SetAnimation(self, &guardian_move_atk2_fire);
- }
- mframe_t guardian_frames_atk2_in[] = {
- { ai_charge, 0, guardian_footstep },
- { ai_charge },
- { ai_charge },
- { ai_charge, 0, guardian_footstep },
- { ai_charge },
- { ai_charge },
- { ai_charge },
- { ai_charge, 0, guardian_footstep },
- { ai_charge },
- { ai_charge },
- { ai_charge },
- { ai_charge }
- };
- MMOVE_T(guardian_move_atk2_in) = { FRAME_atk2_in1, FRAME_atk2_in12, guardian_frames_atk2_in, guardian_atk2 };
- void guardian_kick(edict_t *self)
- {
- if (!fire_hit(self, { MELEE_DISTANCE, 0, -80 }, 85, 700))
- self->monsterinfo.melee_debounce_time = level.time + 1000_ms;
- }
- mframe_t guardian_frames_kick[] = {
- { ai_charge },
- { ai_charge, 0, guardian_footstep },
- { ai_charge },
- { ai_charge },
- { ai_charge },
- { ai_charge, 0, guardian_kick },
- { ai_charge },
- { ai_charge },
- { ai_charge },
- { ai_charge },
- { ai_charge, 0, guardian_footstep },
- { ai_charge },
- { ai_charge }
- };
- MMOVE_T(guardian_move_kick) = { FRAME_kick_in1, FRAME_kick_in13, guardian_frames_kick, guardian_run };
- MONSTERINFO_ATTACK(guardian_attack) (edict_t *self) -> void
- {
- if (!self->enemy || !self->enemy->inuse)
- return;
- float r = range_to(self, self->enemy);
- if (r > RANGE_NEAR)
- M_SetAnimation(self, &guardian_move_atk2_in);
- else if (self->monsterinfo.melee_debounce_time < level.time && r < 120.f)
- M_SetAnimation(self, &guardian_move_kick);
- else
- M_SetAnimation(self, &guardian_move_atk1_in);
- }
- //
- // death
- //
- void guardian_explode(edict_t *self)
- {
- gi.WriteByte(svc_temp_entity);
- gi.WriteByte(TE_EXPLOSION1_BIG);
- gi.WritePosition((self->s.origin + self->mins) + vec3_t { frandom() * self->size[0], frandom() * self->size[1], frandom() * self->size[2] });
- gi.multicast(self->s.origin, MULTICAST_ALL, false);
- }
- constexpr const char *gibs[] = {
- "models/monsters/guardian/gib1.md2",
- "models/monsters/guardian/gib2.md2",
- "models/monsters/guardian/gib3.md2",
- "models/monsters/guardian/gib4.md2",
- "models/monsters/guardian/gib5.md2",
- "models/monsters/guardian/gib6.md2",
- "models/monsters/guardian/gib7.md2"
- };
- void guardian_dead(edict_t *self)
- {
- for (int i = 0; i < 3; i++)
- guardian_explode(self);
- ThrowGibs(self, 125, {
- { 2, "models/objects/gibs/sm_meat/tris.md2" },
- { 4, "models/objects/gibs/sm_metal/tris.md2", GIB_METALLIC },
- { 2, gibs[0], GIB_METALLIC },
- { 2, gibs[1], GIB_METALLIC },
- { 2, gibs[2], GIB_METALLIC },
- { 2, gibs[3], GIB_METALLIC },
- { 2, gibs[4], GIB_METALLIC },
- { 2, gibs[5], GIB_METALLIC },
- { gibs[6], GIB_METALLIC | GIB_HEAD }
- });
- }
- mframe_t guardian_frames_death1[FRAME_death26 - FRAME_death1 + 1] = {
- { ai_move, 0, BossExplode },
- { 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(guardian_move_death) = { FRAME_death1, FRAME_death26, guardian_frames_death1, guardian_dead };
- DIE(guardian_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void
- {
- // regular death
- //gi.sound(self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0);
- self->monsterinfo.weapon_sound = 0;
- self->deadflag = true;
- self->takedamage = true;
- M_SetAnimation(self, &guardian_move_death);
- }
- //
- // monster_tank
- //
- /*QUAKED monster_guardian (1 .5 0) (-96 -96 -66) (96 96 62) Ambush Trigger_Spawn Sight
- */
- void SP_monster_guardian(edict_t *self)
- {
- if ( !M_AllowSpawn( self ) ) {
- G_FreeEdict( self );
- return;
- }
- sound_step.assign("zortemp/step.wav");
- sound_charge.assign("weapons/hyprbu1a.wav");
- sound_spin_loop.assign("weapons/hyprbl1a.wav");
- sound_laser.assign("weapons/laser2.wav");
- for (auto &gib : gibs)
- gi.modelindex(gib);
- self->s.modelindex = gi.modelindex("models/monsters/guardian/tris.md2");
- self->mins = { -96, -96, -66 };
- self->maxs = { 96, 96, 62 };
- self->movetype = MOVETYPE_STEP;
- self->solid = SOLID_BBOX;
- self->health = 2500 * st.health_multiplier;
- self->gib_health = -200;
- self->monsterinfo.scale = MODEL_SCALE;
- self->mass = 850;
- self->pain = guardian_pain;
- self->die = guardian_die;
- self->monsterinfo.stand = guardian_stand;
- self->monsterinfo.walk = guardian_walk;
- self->monsterinfo.run = guardian_run;
- self->monsterinfo.attack = guardian_attack;
- gi.linkentity(self);
- M_SetAnimation(self, &guardian_move_stand);
- walkmonster_start(self);
- }
|