mirror of
https://github.com/id-Software/quake2-rerelease-dll.git
synced 2025-02-24 20:21:57 +00:00
920 lines
22 KiB
C++
920 lines
22 KiB
C++
// Copyright (c) ZeniMax Media Inc.
|
|
// Licensed under the GNU General Public License 2.0.
|
|
/*
|
|
==============================================================================
|
|
|
|
GUNNER
|
|
|
|
==============================================================================
|
|
*/
|
|
|
|
#include "g_local.h"
|
|
#include "m_gunner.h"
|
|
#include "m_flash.h"
|
|
|
|
static int sound_pain;
|
|
static int sound_pain2;
|
|
static int sound_death;
|
|
static int sound_idle;
|
|
static int sound_open;
|
|
static int sound_search;
|
|
static int sound_sight;
|
|
|
|
void gunner_idlesound(edict_t *self)
|
|
{
|
|
gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0);
|
|
}
|
|
|
|
MONSTERINFO_SIGHT(gunner_sight) (edict_t *self, edict_t *other) -> void
|
|
{
|
|
gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
|
|
}
|
|
|
|
MONSTERINFO_SEARCH(gunner_search) (edict_t *self) -> void
|
|
{
|
|
gi.sound(self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0);
|
|
}
|
|
|
|
void GunnerGrenade(edict_t *self);
|
|
void GunnerFire(edict_t *self);
|
|
void gunner_fire_chain(edict_t *self);
|
|
void gunner_refire_chain(edict_t *self);
|
|
|
|
void gunner_stand(edict_t *self);
|
|
|
|
mframe_t gunner_frames_fidget[] = {
|
|
{ ai_stand },
|
|
{ ai_stand },
|
|
{ ai_stand },
|
|
{ ai_stand },
|
|
{ ai_stand },
|
|
{ ai_stand },
|
|
{ ai_stand },
|
|
{ ai_stand, 0, gunner_idlesound },
|
|
{ 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(gunner_move_fidget) = { FRAME_stand31, FRAME_stand70, gunner_frames_fidget, gunner_stand };
|
|
|
|
void gunner_fidget(edict_t *self)
|
|
{
|
|
if (self->monsterinfo.aiflags & AI_STAND_GROUND)
|
|
return;
|
|
else if (self->enemy)
|
|
return;
|
|
if (frandom() <= 0.05f)
|
|
M_SetAnimation(self, &gunner_move_fidget);
|
|
}
|
|
|
|
mframe_t gunner_frames_stand[] = {
|
|
{ ai_stand },
|
|
{ ai_stand },
|
|
{ ai_stand },
|
|
{ ai_stand },
|
|
{ ai_stand },
|
|
{ ai_stand },
|
|
{ ai_stand },
|
|
{ ai_stand },
|
|
{ ai_stand },
|
|
{ ai_stand, 0, gunner_fidget },
|
|
|
|
{ ai_stand },
|
|
{ ai_stand },
|
|
{ ai_stand },
|
|
{ ai_stand },
|
|
{ ai_stand },
|
|
{ ai_stand },
|
|
{ ai_stand },
|
|
{ ai_stand },
|
|
{ ai_stand },
|
|
{ ai_stand, 0, gunner_fidget },
|
|
|
|
{ ai_stand },
|
|
{ ai_stand },
|
|
{ ai_stand },
|
|
{ ai_stand },
|
|
{ ai_stand },
|
|
{ ai_stand },
|
|
{ ai_stand },
|
|
{ ai_stand },
|
|
{ ai_stand },
|
|
{ ai_stand, 0, gunner_fidget }
|
|
};
|
|
MMOVE_T(gunner_move_stand) = { FRAME_stand01, FRAME_stand30, gunner_frames_stand, nullptr };
|
|
|
|
MONSTERINFO_STAND(gunner_stand) (edict_t *self) -> void
|
|
{
|
|
M_SetAnimation(self, &gunner_move_stand);
|
|
}
|
|
|
|
mframe_t gunner_frames_walk[] = {
|
|
{ ai_walk },
|
|
{ ai_walk, 3 },
|
|
{ ai_walk, 4 },
|
|
{ ai_walk, 5 },
|
|
{ ai_walk, 7 },
|
|
{ ai_walk, 2, monster_footstep },
|
|
{ ai_walk, 6 },
|
|
{ ai_walk, 4 },
|
|
{ ai_walk, 2 },
|
|
{ ai_walk, 7 },
|
|
{ ai_walk, 5 },
|
|
{ ai_walk, 7 },
|
|
{ ai_walk, 4, monster_footstep }
|
|
};
|
|
MMOVE_T(gunner_move_walk) = { FRAME_walk07, FRAME_walk19, gunner_frames_walk, nullptr };
|
|
|
|
MONSTERINFO_WALK(gunner_walk) (edict_t *self) -> void
|
|
{
|
|
M_SetAnimation(self, &gunner_move_walk);
|
|
}
|
|
|
|
mframe_t gunner_frames_run[] = {
|
|
{ ai_run, 26 },
|
|
{ ai_run, 9, monster_footstep },
|
|
{ ai_run, 9 },
|
|
{ ai_run, 9, monster_done_dodge },
|
|
{ ai_run, 15 },
|
|
{ ai_run, 10, monster_footstep },
|
|
{ ai_run, 13 },
|
|
{ ai_run, 6 }
|
|
};
|
|
|
|
MMOVE_T(gunner_move_run) = { FRAME_run01, FRAME_run08, gunner_frames_run, nullptr };
|
|
|
|
MONSTERINFO_RUN(gunner_run) (edict_t *self) -> void
|
|
{
|
|
monster_done_dodge(self);
|
|
if (self->monsterinfo.aiflags & AI_STAND_GROUND)
|
|
M_SetAnimation(self, &gunner_move_stand);
|
|
else
|
|
M_SetAnimation(self, &gunner_move_run);
|
|
}
|
|
|
|
mframe_t gunner_frames_runandshoot[] = {
|
|
{ ai_run, 32 },
|
|
{ ai_run, 15 },
|
|
{ ai_run, 10 },
|
|
{ ai_run, 18 },
|
|
{ ai_run, 8 },
|
|
{ ai_run, 20 }
|
|
};
|
|
|
|
MMOVE_T(gunner_move_runandshoot) = { FRAME_runs01, FRAME_runs06, gunner_frames_runandshoot, nullptr };
|
|
|
|
void gunner_runandshoot(edict_t *self)
|
|
{
|
|
M_SetAnimation(self, &gunner_move_runandshoot);
|
|
}
|
|
|
|
mframe_t gunner_frames_pain3[] = {
|
|
{ ai_move, -3 },
|
|
{ ai_move, 1 },
|
|
{ ai_move, 1 },
|
|
{ ai_move },
|
|
{ ai_move, 1 }
|
|
};
|
|
MMOVE_T(gunner_move_pain3) = { FRAME_pain301, FRAME_pain305, gunner_frames_pain3, gunner_run };
|
|
|
|
mframe_t gunner_frames_pain2[] = {
|
|
{ ai_move, -2 },
|
|
{ ai_move, 11 },
|
|
{ ai_move, 6, monster_footstep },
|
|
{ ai_move, 2 },
|
|
{ ai_move, -1 },
|
|
{ ai_move, -7 },
|
|
{ ai_move, -2 },
|
|
{ ai_move, -7, monster_footstep }
|
|
};
|
|
MMOVE_T(gunner_move_pain2) = { FRAME_pain201, FRAME_pain208, gunner_frames_pain2, gunner_run };
|
|
|
|
mframe_t gunner_frames_pain1[] = {
|
|
{ ai_move, 2 },
|
|
{ ai_move },
|
|
{ ai_move, -5 },
|
|
{ ai_move, 3 },
|
|
{ ai_move, -1, monster_footstep },
|
|
{ ai_move },
|
|
{ ai_move },
|
|
{ ai_move },
|
|
{ ai_move },
|
|
{ ai_move, 1 },
|
|
{ ai_move, 1 },
|
|
{ ai_move, 2 },
|
|
{ ai_move, 1, monster_footstep },
|
|
{ ai_move },
|
|
{ ai_move, -2 },
|
|
{ ai_move, -2 },
|
|
{ ai_move },
|
|
{ ai_move, 0, monster_footstep }
|
|
};
|
|
MMOVE_T(gunner_move_pain1) = { FRAME_pain101, FRAME_pain118, gunner_frames_pain1, gunner_run };
|
|
|
|
extern const mmove_t gunner_move_jump;
|
|
extern const mmove_t gunner_move_jump2;
|
|
|
|
PAIN(gunner_pain) (edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod) -> void
|
|
{
|
|
monster_done_dodge(self);
|
|
|
|
if (self->monsterinfo.active_move == &gunner_move_jump ||
|
|
self->monsterinfo.active_move == &gunner_move_jump2)
|
|
return;
|
|
|
|
if (level.time < self->pain_debounce_time)
|
|
return;
|
|
|
|
self->pain_debounce_time = level.time + 3_sec;
|
|
|
|
if (brandom())
|
|
gi.sound(self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0);
|
|
else
|
|
gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0);
|
|
|
|
if (!M_ShouldReactToPain(self, mod))
|
|
return; // no pain anims in nightmare
|
|
|
|
if (damage <= 10)
|
|
M_SetAnimation(self, &gunner_move_pain3);
|
|
else if (damage <= 25)
|
|
M_SetAnimation(self, &gunner_move_pain2);
|
|
else
|
|
M_SetAnimation(self, &gunner_move_pain1);
|
|
|
|
self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
|
|
|
|
// PMM - clear duck flag
|
|
if (self->monsterinfo.aiflags & AI_DUCKED)
|
|
monster_duck_up(self);
|
|
}
|
|
|
|
MONSTERINFO_SETSKIN(gunner_setskin) (edict_t *self) -> void
|
|
{
|
|
if (self->health < (self->max_health / 2))
|
|
self->s.skinnum = 1;
|
|
else
|
|
self->s.skinnum = 0;
|
|
}
|
|
|
|
void gunner_dead(edict_t *self)
|
|
{
|
|
self->mins = { -16, -16, -24 };
|
|
self->maxs = { 16, 16, -8 };
|
|
monster_dead(self);
|
|
}
|
|
|
|
static void gunner_shrink(edict_t *self)
|
|
{
|
|
self->maxs[2] = -4;
|
|
self->svflags |= SVF_DEADMONSTER;
|
|
gi.linkentity(self);
|
|
}
|
|
|
|
mframe_t gunner_frames_death[] = {
|
|
{ ai_move },
|
|
{ ai_move },
|
|
{ ai_move, 0, monster_footstep },
|
|
{ ai_move, -7, gunner_shrink },
|
|
{ ai_move, -3 },
|
|
{ ai_move, -5 },
|
|
{ ai_move, 8 },
|
|
{ ai_move, 6 },
|
|
{ ai_move, 0, monster_footstep },
|
|
{ ai_move },
|
|
{ ai_move }
|
|
};
|
|
MMOVE_T(gunner_move_death) = { FRAME_death01, FRAME_death11, gunner_frames_death, gunner_dead };
|
|
|
|
DIE(gunner_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void
|
|
{
|
|
// 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;
|
|
|
|
ThrowGibs(self, damage, {
|
|
{ 2, "models/objects/gibs/bone/tris.md2" },
|
|
{ 2, "models/objects/gibs/sm_meat/tris.md2" },
|
|
{ "models/monsters/gunner/gibs/chest.md2", GIB_SKINNED },
|
|
{ "models/monsters/gunner/gibs/garm.md2", GIB_SKINNED | GIB_UPRIGHT },
|
|
{ "models/monsters/gunner/gibs/gun.md2", GIB_SKINNED | GIB_UPRIGHT },
|
|
{ "models/monsters/gunner/gibs/foot.md2", GIB_SKINNED },
|
|
{ "models/monsters/gunner/gibs/head.md2", GIB_SKINNED | GIB_HEAD }
|
|
});
|
|
|
|
self->deadflag = true;
|
|
return;
|
|
}
|
|
|
|
if (self->deadflag)
|
|
return;
|
|
|
|
// regular death
|
|
gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0);
|
|
self->deadflag = true;
|
|
self->takedamage = true;
|
|
M_SetAnimation(self, &gunner_move_death);
|
|
}
|
|
|
|
// PMM - changed to duck code for new dodge
|
|
|
|
mframe_t gunner_frames_duck[] = {
|
|
{ ai_move, 1, monster_duck_down },
|
|
{ ai_move, 1 },
|
|
{ ai_move, 1, monster_duck_hold },
|
|
{ ai_move },
|
|
{ ai_move, -1 },
|
|
{ ai_move, -1 },
|
|
{ ai_move, 0, monster_duck_up },
|
|
{ ai_move, -1 }
|
|
};
|
|
MMOVE_T(gunner_move_duck) = { FRAME_duck01, FRAME_duck08, gunner_frames_duck, gunner_run };
|
|
|
|
// PMM - gunner dodge moved below so I know about attack sequences
|
|
|
|
void gunner_opengun(edict_t *self)
|
|
{
|
|
gi.sound(self, CHAN_VOICE, sound_open, 1, ATTN_IDLE, 0);
|
|
}
|
|
|
|
void GunnerFire(edict_t *self)
|
|
{
|
|
vec3_t start;
|
|
vec3_t forward, right;
|
|
vec3_t aim;
|
|
monster_muzzleflash_id_t flash_number;
|
|
|
|
if (!self->enemy || !self->enemy->inuse) // PGM
|
|
return; // PGM
|
|
|
|
flash_number = static_cast<monster_muzzleflash_id_t>(MZ2_GUNNER_MACHINEGUN_1 + (self->s.frame - FRAME_attak216));
|
|
|
|
AngleVectors(self->s.angles, forward, right, nullptr);
|
|
start = M_ProjectFlashSource(self, monster_flash_offset[flash_number], forward, right);
|
|
PredictAim(self, self->enemy, start, 0, true, -0.2f, &aim, nullptr);
|
|
monster_fire_bullet(self, start, aim, 3, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, flash_number);
|
|
}
|
|
|
|
bool gunner_grenade_check(edict_t *self)
|
|
{
|
|
vec3_t dir;
|
|
|
|
if (!self->enemy)
|
|
return false;
|
|
|
|
vec3_t start;
|
|
|
|
if (!M_CheckClearShot(self, monster_flash_offset[MZ2_GUNNER_GRENADE_1], start))
|
|
return false;
|
|
|
|
vec3_t target;
|
|
|
|
// check for flag telling us that we're blindfiring
|
|
if (self->monsterinfo.aiflags & AI_MANUAL_STEERING)
|
|
target = self->monsterinfo.blind_fire_target;
|
|
else
|
|
target = self->enemy->s.origin;
|
|
|
|
// see if we're too close
|
|
dir = target - start;
|
|
|
|
if (dir.length() < 100)
|
|
return false;
|
|
|
|
// check to see that we can trace to the player before we start
|
|
// tossing grenades around.
|
|
vec3_t aim = dir.normalized();
|
|
return M_CalculatePitchToFire(self, target, start, aim, 600, 2.5f, false);
|
|
}
|
|
|
|
void GunnerGrenade(edict_t *self)
|
|
{
|
|
vec3_t start;
|
|
vec3_t forward, right, up;
|
|
vec3_t aim;
|
|
monster_muzzleflash_id_t flash_number;
|
|
float spread;
|
|
float pitch = 0;
|
|
// PMM
|
|
vec3_t target;
|
|
bool blindfire = false;
|
|
|
|
if (!self->enemy || !self->enemy->inuse) // PGM
|
|
return; // PGM
|
|
|
|
// pmm
|
|
if (self->monsterinfo.aiflags & AI_MANUAL_STEERING)
|
|
blindfire = true;
|
|
|
|
if (self->s.frame == FRAME_attak105 || self->s.frame == FRAME_attak309)
|
|
{
|
|
spread = -0.10f;
|
|
flash_number = MZ2_GUNNER_GRENADE_1;
|
|
}
|
|
else if (self->s.frame == FRAME_attak108 || self->s.frame == FRAME_attak312)
|
|
{
|
|
spread = -0.05f;
|
|
flash_number = MZ2_GUNNER_GRENADE_2;
|
|
}
|
|
else if (self->s.frame == FRAME_attak111 || self->s.frame == FRAME_attak315)
|
|
{
|
|
spread = 0.05f;
|
|
flash_number = MZ2_GUNNER_GRENADE_3;
|
|
}
|
|
else // (self->s.frame == FRAME_attak114)
|
|
{
|
|
self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
|
|
spread = 0.10f;
|
|
flash_number = MZ2_GUNNER_GRENADE_4;
|
|
}
|
|
|
|
if (self->s.frame >= FRAME_attak301 && self->s.frame <= FRAME_attak324)
|
|
flash_number = static_cast<monster_muzzleflash_id_t>(MZ2_GUNNER_GRENADE2_1 + (MZ2_GUNNER_GRENADE_4 - flash_number));
|
|
|
|
// pmm
|
|
// if we're shooting blind and we still can't see our enemy
|
|
if ((blindfire) && (!visible(self, self->enemy)))
|
|
{
|
|
// and we have a valid blind_fire_target
|
|
if (!self->monsterinfo.blind_fire_target)
|
|
return;
|
|
|
|
target = self->monsterinfo.blind_fire_target;
|
|
}
|
|
else
|
|
target = self->enemy->s.origin;
|
|
// pmm
|
|
|
|
AngleVectors(self->s.angles, forward, right, up); // PGM
|
|
start = M_ProjectFlashSource(self, monster_flash_offset[flash_number], forward, right);
|
|
|
|
// PGM
|
|
if (self->enemy)
|
|
{
|
|
float dist;
|
|
|
|
aim = target - self->s.origin;
|
|
dist = aim.length();
|
|
|
|
// aim up if they're on the same level as me and far away.
|
|
if ((dist > 512) && (aim[2] < 64) && (aim[2] > -64))
|
|
{
|
|
aim[2] += (dist - 512);
|
|
}
|
|
|
|
aim.normalize();
|
|
pitch = aim[2];
|
|
if (pitch > 0.4f)
|
|
pitch = 0.4f;
|
|
else if (pitch < -0.5f)
|
|
pitch = -0.5f;
|
|
}
|
|
// PGM
|
|
|
|
aim = forward + (right * spread);
|
|
aim += (up * pitch);
|
|
|
|
// try search for best pitch
|
|
if (M_CalculatePitchToFire(self, target, start, aim, 600, 2.5f, false))
|
|
monster_fire_grenade(self, start, aim, 50, 600, flash_number, (crandom_open() * 10.0f), frandom() * 10.f);
|
|
else
|
|
// normal shot
|
|
monster_fire_grenade(self, start, aim, 50, 600, flash_number, (crandom_open() * 10.0f), 200.f + (crandom_open() * 10.0f));
|
|
}
|
|
|
|
mframe_t gunner_frames_attack_chain[] = {
|
|
{ ai_charge, 0, gunner_opengun },
|
|
{ ai_charge },
|
|
{ ai_charge },
|
|
{ ai_charge },
|
|
{ ai_charge },
|
|
{ ai_charge },
|
|
{ ai_charge }
|
|
};
|
|
MMOVE_T(gunner_move_attack_chain) = { FRAME_attak209, FRAME_attak215, gunner_frames_attack_chain, gunner_fire_chain };
|
|
|
|
mframe_t gunner_frames_fire_chain[] = {
|
|
{ ai_charge, 0, GunnerFire },
|
|
{ ai_charge, 0, GunnerFire },
|
|
{ ai_charge, 0, GunnerFire },
|
|
{ ai_charge, 0, GunnerFire },
|
|
{ ai_charge, 0, GunnerFire },
|
|
{ ai_charge, 0, GunnerFire },
|
|
{ ai_charge, 0, GunnerFire },
|
|
{ ai_charge, 0, GunnerFire }
|
|
};
|
|
MMOVE_T(gunner_move_fire_chain) = { FRAME_attak216, FRAME_attak223, gunner_frames_fire_chain, gunner_refire_chain };
|
|
|
|
mframe_t gunner_frames_endfire_chain[] = {
|
|
{ ai_charge },
|
|
{ ai_charge },
|
|
{ ai_charge },
|
|
{ ai_charge },
|
|
{ ai_charge },
|
|
{ ai_charge },
|
|
{ ai_charge, 0, monster_footstep }
|
|
};
|
|
MMOVE_T(gunner_move_endfire_chain) = { FRAME_attak224, FRAME_attak230, gunner_frames_endfire_chain, gunner_run };
|
|
|
|
void gunner_blind_check(edict_t *self)
|
|
{
|
|
vec3_t aim;
|
|
|
|
if (self->monsterinfo.aiflags & AI_MANUAL_STEERING)
|
|
{
|
|
aim = self->monsterinfo.blind_fire_target - self->s.origin;
|
|
self->ideal_yaw = vectoyaw(aim);
|
|
}
|
|
}
|
|
|
|
mframe_t gunner_frames_attack_grenade[] = {
|
|
{ ai_charge, 0, gunner_blind_check },
|
|
{ ai_charge },
|
|
{ ai_charge },
|
|
{ ai_charge },
|
|
{ ai_charge, 0, GunnerGrenade },
|
|
{ ai_charge },
|
|
{ ai_charge },
|
|
{ ai_charge, 0, GunnerGrenade },
|
|
{ ai_charge },
|
|
{ ai_charge },
|
|
{ ai_charge, 0, GunnerGrenade },
|
|
{ ai_charge },
|
|
{ ai_charge },
|
|
{ ai_charge, 0, GunnerGrenade },
|
|
{ ai_charge },
|
|
{ ai_charge },
|
|
{ ai_charge },
|
|
{ ai_charge },
|
|
{ ai_charge },
|
|
{ ai_charge },
|
|
{ ai_charge }
|
|
};
|
|
MMOVE_T(gunner_move_attack_grenade) = { FRAME_attak101, FRAME_attak121, gunner_frames_attack_grenade, gunner_run };
|
|
|
|
mframe_t gunner_frames_attack_grenade2[] = {
|
|
//{ ai_charge },
|
|
//{ ai_charge },
|
|
//{ ai_charge },
|
|
//{ ai_charge },
|
|
|
|
{ ai_charge, 0, gunner_blind_check },
|
|
{ ai_charge },
|
|
{ ai_charge },
|
|
{ ai_charge },
|
|
{ ai_charge, 0, GunnerGrenade },
|
|
{ ai_charge },
|
|
{ ai_charge },
|
|
{ ai_charge, 0, GunnerGrenade },
|
|
{ ai_charge },
|
|
{ ai_charge },
|
|
{ ai_charge, 0, GunnerGrenade },
|
|
{ ai_charge },
|
|
{ ai_charge },
|
|
{ ai_charge, 0, GunnerGrenade },
|
|
{ ai_charge },
|
|
{ ai_charge },
|
|
{ ai_charge },
|
|
{ ai_charge },
|
|
{ ai_charge },
|
|
{ ai_charge }
|
|
};
|
|
MMOVE_T(gunner_move_attack_grenade2) = { FRAME_attak305, FRAME_attak324, gunner_frames_attack_grenade2, gunner_run };
|
|
|
|
MONSTERINFO_ATTACK(gunner_attack) (edict_t *self) -> void
|
|
{
|
|
float chance, r;
|
|
|
|
monster_done_dodge(self);
|
|
|
|
// PMM
|
|
if (self->monsterinfo.attack_state == AS_BLIND)
|
|
{
|
|
if (self->timestamp > level.time)
|
|
return;
|
|
|
|
// 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;
|
|
|
|
if (gunner_grenade_check(self))
|
|
{
|
|
// if the check passes, go for the attack
|
|
M_SetAnimation(self, brandom() ? &gunner_move_attack_grenade2 : &gunner_move_attack_grenade);
|
|
self->monsterinfo.attack_finished = level.time + random_time(2_sec);
|
|
}
|
|
else
|
|
// turn off blindfire flag
|
|
self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
|
|
|
|
self->timestamp = level.time + random_time(2_sec, 3_sec);
|
|
|
|
return;
|
|
}
|
|
// pmm
|
|
|
|
// PGM - gunner needs to use his chaingun if he's being attacked by a tesla.
|
|
if (self->bad_area || self->timestamp > level.time ||
|
|
(range_to(self, self->enemy) <= RANGE_NEAR * 0.35f && M_CheckClearShot(self, monster_flash_offset[MZ2_GUNNER_MACHINEGUN_1])))
|
|
{
|
|
M_SetAnimation(self, &gunner_move_attack_chain);
|
|
}
|
|
else
|
|
{
|
|
if (self->timestamp <= level.time && frandom() <= 0.5f && gunner_grenade_check(self))
|
|
{
|
|
M_SetAnimation(self, brandom() ? &gunner_move_attack_grenade2 : &gunner_move_attack_grenade);
|
|
self->timestamp = level.time + random_time(2_sec, 3_sec);
|
|
}
|
|
else if (M_CheckClearShot(self, monster_flash_offset[MZ2_GUNNER_MACHINEGUN_1]))
|
|
M_SetAnimation(self, &gunner_move_attack_chain);
|
|
}
|
|
}
|
|
|
|
void gunner_fire_chain(edict_t *self)
|
|
{
|
|
M_SetAnimation(self, &gunner_move_fire_chain);
|
|
}
|
|
|
|
void gunner_refire_chain(edict_t *self)
|
|
{
|
|
if (self->enemy->health > 0)
|
|
if (visible(self, self->enemy))
|
|
if (frandom() <= 0.5f)
|
|
{
|
|
M_SetAnimation(self, &gunner_move_fire_chain, false);
|
|
return;
|
|
}
|
|
M_SetAnimation(self, &gunner_move_endfire_chain, false);
|
|
}
|
|
|
|
//===========
|
|
// PGM
|
|
void gunner_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 gunner_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 gunner_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 gunner_frames_jump[] = {
|
|
{ ai_move },
|
|
{ ai_move },
|
|
{ ai_move },
|
|
{ ai_move, 0, gunner_jump_now },
|
|
{ ai_move },
|
|
{ ai_move },
|
|
{ ai_move, 0, gunner_jump_wait_land },
|
|
{ ai_move },
|
|
{ ai_move },
|
|
{ ai_move }
|
|
};
|
|
MMOVE_T(gunner_move_jump) = { FRAME_jump01, FRAME_jump10, gunner_frames_jump, gunner_run };
|
|
|
|
mframe_t gunner_frames_jump2[] = {
|
|
{ ai_move, -8 },
|
|
{ ai_move, -4 },
|
|
{ ai_move, -4 },
|
|
{ ai_move, 0, gunner_jump2_now },
|
|
{ ai_move },
|
|
{ ai_move },
|
|
{ ai_move, 0, gunner_jump_wait_land },
|
|
{ ai_move },
|
|
{ ai_move },
|
|
{ ai_move }
|
|
};
|
|
MMOVE_T(gunner_move_jump2) = { FRAME_jump01, FRAME_jump10, gunner_frames_jump2, gunner_run };
|
|
|
|
void gunner_jump(edict_t *self, blocked_jump_result_t result)
|
|
{
|
|
if (!self->enemy)
|
|
return;
|
|
|
|
monster_done_dodge(self);
|
|
|
|
if (result == blocked_jump_result_t::JUMP_JUMP_UP)
|
|
M_SetAnimation(self, &gunner_move_jump2);
|
|
else
|
|
M_SetAnimation(self, &gunner_move_jump);
|
|
}
|
|
|
|
//===========
|
|
// PGM
|
|
MONSTERINFO_BLOCKED(gunner_blocked) (edict_t *self, float dist) -> bool
|
|
{
|
|
if (blocked_checkplat(self, dist))
|
|
return true;
|
|
|
|
if (auto result = blocked_checkjump(self, dist); result != blocked_jump_result_t::NO_JUMP)
|
|
{
|
|
if (result != blocked_jump_result_t::JUMP_TURN)
|
|
gunner_jump(self, result);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
// PGM
|
|
//===========
|
|
|
|
// PMM - new duck code
|
|
MONSTERINFO_DUCK(gunner_duck) (edict_t *self, gtime_t eta) -> bool
|
|
{
|
|
if ((self->monsterinfo.active_move == &gunner_move_jump2) ||
|
|
(self->monsterinfo.active_move == &gunner_move_jump))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if ((self->monsterinfo.active_move == &gunner_move_attack_chain) ||
|
|
(self->monsterinfo.active_move == &gunner_move_fire_chain) ||
|
|
(self->monsterinfo.active_move == &gunner_move_attack_grenade) ||
|
|
(self->monsterinfo.active_move == &gunner_move_attack_grenade2))
|
|
{
|
|
// if we're shooting don't dodge
|
|
self->monsterinfo.unduck(self);
|
|
return false;
|
|
}
|
|
|
|
if (frandom() > 0.5f)
|
|
GunnerGrenade(self);
|
|
|
|
M_SetAnimation(self, &gunner_move_duck);
|
|
|
|
return true;
|
|
}
|
|
|
|
MONSTERINFO_SIDESTEP(gunner_sidestep) (edict_t *self) -> bool
|
|
{
|
|
if ((self->monsterinfo.active_move == &gunner_move_jump2) ||
|
|
(self->monsterinfo.active_move == &gunner_move_jump) ||
|
|
(self->monsterinfo.active_move == &gunner_move_pain1))
|
|
return false;
|
|
|
|
if ((self->monsterinfo.active_move == &gunner_move_attack_chain) ||
|
|
(self->monsterinfo.active_move == &gunner_move_fire_chain) ||
|
|
(self->monsterinfo.active_move == &gunner_move_attack_grenade) ||
|
|
(self->monsterinfo.active_move == &gunner_move_attack_grenade2))
|
|
{
|
|
// if we're shooting, don't dodge
|
|
return false;
|
|
}
|
|
|
|
if (self->monsterinfo.active_move != &gunner_move_run)
|
|
M_SetAnimation(self, &gunner_move_run);
|
|
|
|
return true;
|
|
}
|
|
|
|
constexpr spawnflags_t SPAWNFLAG_GUNNER_NOJUMPING = 8_spawnflag;
|
|
|
|
/*QUAKED monster_gunner (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight NoJumping
|
|
model="models/monsters/gunner/tris.md2"
|
|
*/
|
|
void SP_monster_gunner(edict_t *self)
|
|
{
|
|
if ( !M_AllowSpawn( self ) ) {
|
|
G_FreeEdict( self );
|
|
return;
|
|
}
|
|
|
|
sound_death = gi.soundindex("gunner/death1.wav");
|
|
sound_pain = gi.soundindex("gunner/gunpain2.wav");
|
|
sound_pain2 = gi.soundindex("gunner/gunpain1.wav");
|
|
sound_idle = gi.soundindex("gunner/gunidle1.wav");
|
|
sound_open = gi.soundindex("gunner/gunatck1.wav");
|
|
sound_search = gi.soundindex("gunner/gunsrch1.wav");
|
|
sound_sight = gi.soundindex("gunner/sight1.wav");
|
|
|
|
gi.soundindex("gunner/gunatck2.wav");
|
|
gi.soundindex("gunner/gunatck3.wav");
|
|
|
|
self->movetype = MOVETYPE_STEP;
|
|
self->solid = SOLID_BBOX;
|
|
self->s.modelindex = gi.modelindex("models/monsters/gunner/tris.md2");
|
|
|
|
gi.modelindex("models/monsters/gunner/gibs/chest.md2");
|
|
gi.modelindex("models/monsters/gunner/gibs/foot.md2");
|
|
gi.modelindex("models/monsters/gunner/gibs/garm.md2");
|
|
gi.modelindex("models/monsters/gunner/gibs/gun.md2");
|
|
gi.modelindex("models/monsters/gunner/gibs/head.md2");
|
|
|
|
self->mins = { -16, -16, -24 };
|
|
self->maxs = { 16, 16, 36 };
|
|
|
|
self->health = 175 * st.health_multiplier;
|
|
self->gib_health = -70;
|
|
self->mass = 200;
|
|
|
|
self->pain = gunner_pain;
|
|
self->die = gunner_die;
|
|
|
|
self->monsterinfo.stand = gunner_stand;
|
|
self->monsterinfo.walk = gunner_walk;
|
|
self->monsterinfo.run = gunner_run;
|
|
// pmm
|
|
self->monsterinfo.dodge = M_MonsterDodge;
|
|
self->monsterinfo.duck = gunner_duck;
|
|
self->monsterinfo.unduck = monster_duck_up;
|
|
self->monsterinfo.sidestep = gunner_sidestep;
|
|
self->monsterinfo.blocked = gunner_blocked; // PGM
|
|
// pmm
|
|
self->monsterinfo.attack = gunner_attack;
|
|
self->monsterinfo.melee = nullptr;
|
|
self->monsterinfo.sight = gunner_sight;
|
|
self->monsterinfo.search = gunner_search;
|
|
self->monsterinfo.setskin = gunner_setskin;
|
|
|
|
gi.linkentity(self);
|
|
|
|
M_SetAnimation(self, &gunner_move_stand);
|
|
self->monsterinfo.scale = MODEL_SCALE;
|
|
|
|
// PMM
|
|
self->monsterinfo.blindfire = true;
|
|
self->monsterinfo.can_jump = !self->spawnflags.has(SPAWNFLAG_GUNNER_NOJUMPING);
|
|
self->monsterinfo.drop_height = 192;
|
|
self->monsterinfo.jump_height = 40;
|
|
|
|
walkmonster_start(self);
|
|
}
|