Files
quake2-rerelease-dll/rerelease/m_guardian.cpp
2023-10-03 14:43:06 -04:00

524 lines
12 KiB
C++

// 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);
}