thirtyflightsofloving/missionpack/m_gunner.c
Knightmare66 3ce18b138c Refactored spincontrol menu control with value lists, simplifying menu implementations.
Simplified loading and saving of cvars in slider menu control.
Enlarged text in Game, Multiplayer, and Options menus.
Fixed repeat of failed file causing HTTP downloads to restart.
Added cl_zoommode cvar to simplify Lazarus zoom command.
Changed zoom command to use new cl_zoommode cvar in default Lazarus and missionpack DLLs.
Removed unused "crossh" cvar in default Lazarus and missionpack DLLs.
Fixed Makron not having a classname when spawned from Jorg in default Lazarus and missionpack DLLs.
Made Tactician Gunner ignore small amounts of damage in missionpack DLL.
2021-09-04 02:18:06 -04:00

1573 lines
43 KiB
C

/*
==============================================================================
GUNNER
==============================================================================
*/
#include "g_local.h"
#include "m_gunner.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;
// Knightmare- Tactician Gunner sounds
#ifndef KMQUAKE2_ENGINE_MOD
static int tactician_sound_fire_flechette;
#endif // KMQUAKE2_ENGINE_MOD
/*
static int tactician_sound_pain;
static int tactician_sound_pain2;
static int tactician_sound_death;
static int tactician_sound_idle;
static int tactician_sound_open;
static int tactician_sound_search;
static int tactician_sound_sight;
*/
// end Knightmare
// NOTE: Original gunner grenade velocity was 600 units/sec, but then
// fire_grenade added 200 units/sec in a direction perpendicular
// to the aim direction. We've removed that from fire_grenade
// (for the gunner, not for players) since the gunner now shoots
// smarter, and adjusted things so that the initial velocity out
// of the barrel is the same.
#define GRENADE_VELOCITY 632.4555320337f
#define GRENADE_VELOCITY_SQUARED 400000.0f
// Knightmare- placement spread for Tactician Gunner prox mines
#define GUNNER_PROX_SPREAD 48.0f
#define HALF_GUNNER_PROX_SPREAD (GUNNER_PROX_SPREAD * 0.5f)
void gunner_idlesound (edict_t *self)
{
if ( !(self->spawnflags & SF_MONSTER_AMBUSH) ) {
/* if (self->moreflags & FL2_COMMANDER)
gi.sound (self, CHAN_VOICE, tactician_sound_idle, 1, ATTN_IDLE, 0);
else */
gi.sound (self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0);
}
}
void gunner_sight (edict_t *self, edict_t *other)
{
/* if (self->moreflags & FL2_COMMANDER)
gi.sound (self, CHAN_VOICE, tactician_sound_sight, 1, ATTN_NORM, 0);
else */
gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
}
void gunner_search (edict_t *self)
{
/* if (self->moreflags & FL2_COMMANDER)
gi.sound (self, CHAN_VOICE, tactician_sound_search, 1, ATTN_NORM, 0);
else */
gi.sound (self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0);
}
qboolean visible (edict_t *self, edict_t *other);
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, 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, gunner_idlesound,
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,
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,
ai_stand, 0, NULL
};
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;
if (random() <= 0.05)
self->monsterinfo.currentmove = &gunner_move_fidget;
}
mframe_t gunner_frames_stand [] =
{
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, gunner_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, gunner_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, gunner_fidget
};
mmove_t gunner_move_stand = {FRAME_stand01, FRAME_stand30, gunner_frames_stand, NULL};
void gunner_stand (edict_t *self)
{
self->monsterinfo.currentmove = &gunner_move_stand;
}
mframe_t gunner_frames_walk [] =
{
ai_walk, 0, NULL,
ai_walk, 3, NULL,
ai_walk, 4, NULL,
ai_walk, 5, NULL,
ai_walk, 7, NULL,
ai_walk, 2, NULL,
ai_walk, 6, NULL,
ai_walk, 4, NULL,
ai_walk, 2, NULL,
ai_walk, 7, NULL,
ai_walk, 5, NULL,
ai_walk, 7, NULL,
ai_walk, 4, NULL
};
mmove_t gunner_move_walk = {FRAME_walk07, FRAME_walk19, gunner_frames_walk, NULL};
void gunner_walk (edict_t *self)
{
self->monsterinfo.currentmove = &gunner_move_walk;
}
mframe_t gunner_frames_run [] =
{
ai_run, 26, NULL,
ai_run, 9, NULL,
ai_run, 9, NULL,
ai_run, 9, monster_done_dodge,
ai_run, 15, NULL,
ai_run, 10, NULL,
ai_run, 13, NULL,
ai_run, 6, NULL
};
mmove_t gunner_move_run = {FRAME_run01, FRAME_run08, gunner_frames_run, NULL};
void gunner_run (edict_t *self)
{
monster_done_dodge(self);
if (self->monsterinfo.aiflags & AI_STAND_GROUND)
self->monsterinfo.currentmove = &gunner_move_stand;
else
self->monsterinfo.currentmove = &gunner_move_run;
}
mframe_t gunner_frames_runandshoot [] =
{
ai_run, 32, NULL,
ai_run, 15, NULL,
ai_run, 10, NULL,
ai_run, 18, NULL,
ai_run, 8, NULL,
ai_run, 20, NULL
};
mmove_t gunner_move_runandshoot = {FRAME_runs01, FRAME_runs06, gunner_frames_runandshoot, NULL};
void gunner_runandshoot (edict_t *self)
{
self->monsterinfo.currentmove = &gunner_move_runandshoot;
}
mframe_t gunner_frames_pain3 [] =
{
ai_move, -3, NULL,
ai_move, 1, NULL,
ai_move, 1, NULL,
ai_move, 0, NULL,
ai_move, 1, NULL
};
mmove_t gunner_move_pain3 = {FRAME_pain301, FRAME_pain305, gunner_frames_pain3, gunner_run};
mframe_t gunner_frames_pain2 [] =
{
ai_move, -2, NULL,
ai_move, 11, NULL,
ai_move, 6, NULL,
ai_move, 2, NULL,
ai_move, -1, NULL,
ai_move, -7, NULL,
ai_move, -2, NULL,
ai_move, -7, NULL
};
mmove_t gunner_move_pain2 = {FRAME_pain201, FRAME_pain208, gunner_frames_pain2, gunner_run};
mframe_t gunner_frames_pain1 [] =
{
ai_move, 2, NULL,
ai_move, 0, NULL,
ai_move, -5, NULL,
ai_move, 3, NULL,
ai_move, -1, NULL,
ai_move, 0, NULL,
ai_move, 0, NULL,
ai_move, 0, NULL,
ai_move, 0, NULL,
ai_move, 1, NULL,
ai_move, 1, NULL,
ai_move, 2, NULL,
ai_move, 1, NULL,
ai_move, 0, NULL,
ai_move, -2, NULL,
ai_move, -2, NULL,
ai_move, 0, NULL,
ai_move, 0, NULL
};
mmove_t gunner_move_pain1 = {FRAME_pain101, FRAME_pain118, gunner_frames_pain1, gunner_run};
void gunner_pain (edict_t *self, edict_t *other, float kick, int damage)
{
int smallDamage, bigDamage;
if (self->health < (self->max_health / 2))
self->s.skinnum |= 1;
monster_done_dodge (self);
// Tactician Gunner shrugs off small damage
if ( (self->moreflags & FL2_COMMANDER) && (damage < 10) )
return;
if (!self->groundentity)
{
// if ((g_showlogic) && (g_showlogic->value))
// gi.dprintf ("gunner: pain avoided due to no ground\n");
return;
}
if (level.time < self->pain_debounce_time)
return;
self->pain_debounce_time = level.time + 3;
if (rand() & 1) {
/* if (self->moreflags & FL2_COMMANDER)
gi.sound (self, CHAN_VOICE, tactician_sound_pain, 1, ATTN_NORM, 0);
else */
gi.sound (self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0);
}
else {
/* if (self->moreflags & FL2_COMMANDER)
gi.sound (self, CHAN_VOICE, tactician_sound_pain2, 1, ATTN_NORM, 0);
else */
gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0);
}
if (skill->value == 3)
return; // no pain anims in nightmare
if (self->moreflags & FL2_COMMANDER) {
smallDamage = 20;
bigDamage = 50;
}
else {
smallDamage = 10;
bigDamage = 25;
}
// if (damage <= 10)
if (damage <= smallDamage)
self->monsterinfo.currentmove = &gunner_move_pain3;
// else if (damage <= 25)
else if (damage <= bigDamage)
self->monsterinfo.currentmove = &gunner_move_pain2;
else
self->monsterinfo.currentmove = &gunner_move_pain1;
self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
// PMM - clear duck flag
if (self->monsterinfo.aiflags & AI_DUCKED)
monster_duck_up (self);
}
void gunner_dead (edict_t *self)
{
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);
M_FlyCheck (self);
// Lazarus monster fade
if (world->effects & FX_WORLDSPAWN_CORPSEFADE)
{
self->think = FadeDieSink;
self->nextthink = level.time+corpse_fadetime->value;
}
}
mframe_t gunner_frames_death [] =
{
ai_move, 0, NULL,
ai_move, 0, NULL,
ai_move, 0, NULL,
ai_move, -7, NULL,
ai_move, -3, NULL,
ai_move, -5, NULL,
ai_move, 8, NULL,
ai_move, 6, NULL,
ai_move, 0, NULL,
ai_move, 0, NULL,
ai_move, 0, NULL
};
mmove_t gunner_move_death = {FRAME_death01, FRAME_death11, gunner_frames_death, gunner_dead};
void gunner_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
{
int n;
self->monsterinfo.power_armor_type = POWER_ARMOR_NONE;
// check for gib
if (self->health <= self->gib_health && !(self->spawnflags & SF_MONSTER_NOGIB))
{
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", 0, 0, damage, GIB_ORGANIC);
for (n = 0; n < 4; n++)
ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", 0, 0, damage, GIB_ORGANIC);
ThrowGib (self, "models/objects/gibs/chest/tris.md2", 0, 0, damage, GIB_ORGANIC);
ThrowHead (self, "models/objects/gibs/head2/tris.md2", 0, 0, damage, GIB_ORGANIC);
self->deadflag = DEAD_DEAD;
return;
}
if (self->deadflag == DEAD_DEAD)
return;
// regular death
/* if (self->moreflags & FL2_COMMANDER)
gi.sound (self, CHAN_VOICE, tactician_sound_death, 1, ATTN_NORM, 0);
else */
gi.sound (self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0);
self->s.skinnum |= 1;
self->deadflag = DEAD_DEAD;
self->takedamage = DAMAGE_YES;
self->monsterinfo.currentmove = &gunner_move_death;
}
// Knightmare added
// This calcs horizontal spread of prox mines based on distance.
// Then it does a short-range trace at blast radius, to ensure we won't clip a wall.
#define GUNNER_PROX_DANGER_RANGE 256.0f
qboolean gunner_prox_safety_check (edict_t *self, vec3_t start, vec3_t target)
{
trace_t tr;
vec3_t closeCheckMins, closeCheckMaxs, dir, dangerOffset, dangerTarget;
float dist, dangerSpread;
// get dist to target
VectorSubtract (target, start, dir);
dist = VectorLength (dir);
// get spread at damger range
dangerSpread = (HALF_GUNNER_PROX_SPREAD / dist) * GUNNER_PROX_DANGER_RANGE;
dangerSpread += PROX_TEST_SIZE; // add bounds of prox mine + 1
VectorSet (closeCheckMins, -dangerSpread, -dangerSpread, -12.0f);
VectorSet (closeCheckMaxs, dangerSpread, dangerSpread, 12.0f);
// if ((g_showlogic) && (g_showlogic->value))
// gi.dprintf ("Tactician Gunner: perfoming close-range safety check with radius of %5.2f- ", dangerSpread);
// extrapolate point on path to target at danger range
VectorNormalize (dir);
VectorScale (dir, GUNNER_PROX_DANGER_RANGE, dangerOffset);
VectorAdd (start, dangerOffset, dangerTarget);
tr = gi.trace(start, closeCheckMins, closeCheckMaxs, dangerTarget, self, MASK_SHOT);
if (tr.fraction < 1.0) {
// if (g_showlogic && g_showlogic->value)
// gi.dprintf ("failed!\n");
return false;
}
// if (g_showlogic && g_showlogic->value)
// gi.dprintf ("succeeded!\n");
return true;
}
// Knightmare added
// This does a short-range trace at blast radius, to ensure we won't clip a wall.
#define GUNNER_CTGRENADE_DANGER_RANGE 128.0f
qboolean gunner_ctgrenade_safety_check (edict_t *self, vec3_t start, vec3_t target)
{
trace_t tr;
vec3_t dir, dangerOffset, dangerTarget;
float dist;
// get dist to target
VectorSubtract (target, start, dir);
dist = VectorLength (dir);
// if ((g_showlogic) && (g_showlogic->value))
// gi.dprintf ("Gunner: perfoming close-range contactgrenade safety check- ");
// extrapolate point on path to target at danger range
VectorNormalize (dir);
VectorScale (dir, GUNNER_CTGRENADE_DANGER_RANGE, dangerOffset);
VectorAdd (start, dangerOffset, dangerTarget);
tr = gi.trace(start, vec3_origin, vec3_origin, dangerTarget, self, MASK_SHOT);
if (tr.fraction < 1.0) {
// if (g_showlogic && g_showlogic->value)
// gi.dprintf ("failed!\n");
return false;
}
// if (g_showlogic && g_showlogic->value)
// gi.dprintf ("succeeded!\n");
return true;
}
qboolean gunner_grenade_check (edict_t *self)
{
vec3_t start;
vec3_t forward, right;
vec3_t target;
trace_t tr;
vec3_t dir;
vec3_t vhorz;
float horz, vertmax, dangerClose;
qboolean isProx = (self->moreflags & FL2_COMMANDER);
qboolean isContact = (self->spawnflags & SF_MONSTER_SPECIAL);
// Knightmare- Tactician Gunner fires prox mines in a spread,
// so we need a wider safety bounds check
vec3_t checkMins, checkMaxs;
vec3_t proxMins = {-PROX_TEST_SIZE, -PROX_TEST_SIZE, -PROX_TEST_SIZE};
vec3_t proxMaxs = {PROX_TEST_SIZE, PROX_TEST_SIZE, PROX_TEST_SIZE};
if (!self->enemy)
return false;
// if the player is above my head, use machinegun.
// if (self->absmax[2] <= self->enemy->absmin[2])
// return false;
// Lazarus: We can do better than that... see below
// Knightmare- use appropriate trace mins/maxs based on projectile
if (isProx) {
VectorCopy (proxMins, checkMins);
VectorCopy (proxMaxs, checkMaxs);
}
else {
VectorCopy (vec3_origin, checkMins);
VectorCopy (vec3_origin, checkMaxs);
}
// check to see that we can trace to the player before we start
// tossing grenades around.
AngleVectors (self->s.angles, forward, right, NULL);
G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_GUNNER_GRENADE_1], forward, right, start);
// see if we're too close
// Knightmare- Tactician Gunner's prox mines stick around, so only use at longer range
// Also use longer range for contact grenades
if (isProx)
dangerClose = 320.0f;
else if (isContact)
dangerClose = 128.0f;
else
dangerClose = 100.0f;
VectorSubtract (self->enemy->s.origin, self->s.origin, dir);
if (VectorLength(dir) < dangerClose)
return false;
// Lazarus: Max vertical distance - this is approximate and conservative
VectorCopy (dir, vhorz);
vhorz[2] = 0;
horz = VectorLength (vhorz);
vertmax = (GRENADE_VELOCITY_SQUARED) / (2 * sv_gravity->value) -
0.5 * sv_gravity->value * horz * horz / GRENADE_VELOCITY_SQUARED;
if (dir[2] > vertmax)
return false;
// Lazarus: Make sure there's a more-or-less clear flight path to target
// Rogue checked target origin, but if target is above gunner then the trace
// would almost always hit the platform the target was standing on
VectorCopy (self->enemy->s.origin, target);
target[2] = self->enemy->absmax[2];
tr = gi.trace(start, checkMins, checkMaxs, target, self, MASK_SHOT);
if (tr.ent == self->enemy || tr.fraction == 1)
{
VectorCopy (target, self->aim_point); // save this aim location in case later safety check fails
// Knightmare- added close-range prox and contact grenade safety checks
if (isProx) {
if ( gunner_prox_safety_check(self, start, target) )
return true;
}
else if (isContact) {
if ( gunner_ctgrenade_safety_check(self, start, target) )
return true;
}
else
return true;
}
// Repeat for feet... in case we're looking down at a target standing under,
// for example, a short doorway
target[2] = self->enemy->absmin[2];
tr = gi.trace(start, checkMins, checkMaxs, target, self, MASK_SHOT);
if (tr.ent == self->enemy || tr.fraction == 1)
{
VectorCopy (target, self->aim_point); // save this aim location in case later safety check fails
// Knightmare- added close-range prox and contact grenade safety checks
if (isProx) {
if ( gunner_prox_safety_check(self, start, target) )
return true;
}
else if (isContact) {
if ( gunner_ctgrenade_safety_check(self, start, target) )
return true;
}
else
return true;
}
return false;
}
// PMM - changed to duck code for new dodge
//
// this is specific to the gunner, leave it be
//
void gunner_duck_down (edict_t *self)
{
// if (self->monsterinfo.aiflags & AI_DUCKED)
// return;
self->monsterinfo.aiflags |= AI_DUCKED;
if (skill->value >= 2)
{
// Lazarus: Added check for goodness of grenade firing
if (random() > 0.5 && gunner_grenade_check(self))
GunnerGrenade (self);
}
// self->maxs[2] -= 32;
self->maxs[2] = self->monsterinfo.base_height - 32;
self->takedamage = DAMAGE_YES;
if (self->monsterinfo.duck_wait_time < level.time)
self->monsterinfo.duck_wait_time = level.time + 1;
gi.linkentity (self);
}
mframe_t gunner_frames_duck [] =
{
ai_move, 1, gunner_duck_down,
ai_move, 1, NULL,
ai_move, 1, monster_duck_hold,
ai_move, 0, NULL,
ai_move, -1, NULL,
ai_move, -1, NULL,
ai_move, 0, monster_duck_up,
ai_move, -1, NULL
};
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)
{
/* if (self->moreflags & FL2_COMMANDER)
gi.sound (self, CHAN_VOICE, tactician_sound_open, 1, ATTN_IDLE, 0);
else */
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 target;
vec3_t aim;
vec3_t targ_vel;
int flash_number;
float dist, time, flechetteSpeed = 850.0f;
if (!self->enemy || !self->enemy->inuse) //PGM
return; //PGM
#ifdef KMQUAKE2_ENGINE_MOD // Knightmare- unique muzzle flash for Tactician Gunner's flechettes
if (self->moreflags & FL2_COMMANDER)
flash_number = MZ2_GUNNER_ETF_RIFLE_1 + (self->s.frame - FRAME_attak216);
else
#endif // KMQUAKE2_ENGINE_MOD
flash_number = MZ2_GUNNER_MACHINEGUN_1 + (self->s.frame - FRAME_attak216);
AngleVectors (self->s.angles, forward, right, NULL);
G_ProjectSource (self->s.origin, monster_flash_offset[flash_number], forward, right, start);
// project enemy back a bit and target there
VectorCopy (self->enemy->s.origin, target);
if ( !(self->moreflags & FL2_COMMANDER) )
{ // Tactician Gunner fires projectiles, so no backward projection
VectorMA (target, -0.2, self->enemy->velocity, target);
}
target[2] += self->enemy->viewheight;
// Lazarus fog reduction of accuracy
if (self->monsterinfo.visibility < FOG_CANSEEGOOD)
{
target[0] += crandom() * 640 * (FOG_CANSEEGOOD - self->monsterinfo.visibility);
target[1] += crandom() * 640 * (FOG_CANSEEGOOD - self->monsterinfo.visibility);
target[2] += crandom() * 320 * (FOG_CANSEEGOOD - self->monsterinfo.visibility);
}
// Knightmare- Tactician Gunner leads the target
if ( (self->moreflags & FL2_COMMANDER) && (self->monsterinfo.aiflags2 & AI2_LEAD_TARGET) )
{
VectorSubtract (target, start, aim);
dist = VectorLength(aim);
time = dist / flechetteSpeed; // was 1000.0f
VectorCopy (self->enemy->velocity, targ_vel);
targ_vel[2] = min(targ_vel[2], 0.0f); // ignore z-velocity of player jumping
VectorMA (target, time, targ_vel, target);
}
VectorSubtract (target, start, aim);
VectorNormalize (aim);
// Knightmare- Tactician Gunner fires flechettes
if (self->moreflags & FL2_COMMANDER) {
#ifndef KMQUAKE2_ENGINE_MOD // Knightmare- silent muzzleflash and play fire sound directly for vanilla Q2
gi.WriteByte (svc_muzzleflash);
gi.WriteShort (self-g_edicts);
gi.WriteByte (MZ_MACHINEGUN | 128);
gi.multicast (self->s.origin, MULTICAST_PVS);
gi.sound (self, CHAN_WEAPON|CHAN_RELIABLE, tactician_sound_fire_flechette, 1.0, ATTN_NORM, 0);
#endif // KMQUAKE2_ENGINE_MOD
monster_fire_flechette (self, start, aim, 4, flechetteSpeed, 30, 8, flash_number); // was damage_radius 75, reduced to limit self-damage
}
else
monster_fire_bullet (self, start, aim, 3, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, flash_number);
}
void GunnerGrenade (edict_t *self)
{
vec3_t start;
vec3_t forward, right, up;
vec3_t aim;
vec_t monster_speed;
int flash_number;
float spread;
// float pitch;
// PMM
vec3_t target, leadTarget;
qboolean blindfire = false;
qboolean leadingTarget = false;
qboolean targetSafe = false;
qboolean leadSafe = false;
qboolean isProx = (self->moreflags & FL2_COMMANDER);
qboolean isContact = (self->spawnflags & SF_MONSTER_SPECIAL);
//PGM
if (!self->enemy || !self->enemy->inuse)
return;
// pmm
if (self->monsterinfo.aiflags & AI_MANUAL_STEERING)
blindfire = true;
if (self->s.frame == FRAME_attak105)
{
spread = .02;
flash_number = MZ2_GUNNER_GRENADE_1;
}
else if (self->s.frame == FRAME_attak108)
{
spread = .05;
flash_number = MZ2_GUNNER_GRENADE_2;
}
else if (self->s.frame == FRAME_attak111)
{
spread = .08;
flash_number = MZ2_GUNNER_GRENADE_3;
}
else // (self->s.frame == FRAME_attak114)
{
self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
spread = .11;
flash_number = MZ2_GUNNER_GRENADE_4;
}
// 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 (VectorCompare (self->monsterinfo.blind_fire_target, vec3_origin))
return;
// gi.dprintf ("blind_fire_target = %s\n", vtos (self->monsterinfo.blind_fire_target));
// gi.dprintf ("GunnerGrenade: ideal yaw is %f\n", self->ideal_yaw);
VectorCopy (self->monsterinfo.blind_fire_target, target);
}
else
VectorCopy (self->s.origin, target);
// pmm
AngleVectors (self->s.angles, forward, right, up); // PGM
G_ProjectSource (self->s.origin, monster_flash_offset[flash_number], forward, right, start);
//PGM
if (self->enemy)
{
float range;
VectorSubtract (target, self->s.origin, aim);
range = VectorLength (aim);
// aim at enemy's feet if he's at same elevation or lower, otherwise aim at origin
VectorCopy (self->enemy->s.origin, target);
if (self->enemy->absmin[2] <= self->absmax[2])
target[2] = self->enemy->absmin[2];
// lead target... 20, 35, 50, 65 chance of leading
if ( random() < (0.2 + skill->value * 0.15) )
{
float dist, time;
VectorSubtract (target, start, aim);
dist = VectorLength (aim);
time = dist / GRENADE_VELOCITY; // Not correct, but better than nothin'
VectorMA (target, time, self->enemy->velocity, leadTarget);
if (!isProx && !isContact) // delay copying for prox/ctgrenade safety check
VectorCopy (leadTarget, target);
leadingTarget = true;
}
// Knightmare- run another safety check before firing, so players can't trick us into self-damage
if (isProx)
{
if ( gunner_prox_safety_check(self, start, target) ) {
VectorCopy (target, self->aim_point); // save this target point
targetSafe = true;
}
if ( leadingTarget && gunner_prox_safety_check(self, start, leadTarget) ) {
VectorCopy (leadTarget, target); // copy lead point over target
leadSafe = true;
}
if ( !targetSafe && !leadSafe ) {
VectorCopy (self->aim_point, target); // revert to prev target point
}
}
else if (isContact)
{
if ( gunner_ctgrenade_safety_check(self, start, target) ) {
VectorCopy (target, self->aim_point); // save this target point
targetSafe = true;
}
if ( leadingTarget && gunner_ctgrenade_safety_check(self, start, leadTarget) ) {
VectorCopy (leadTarget, target); // copy lead point over target
leadSafe = true;
}
if ( !targetSafe && !leadSafe ) {
VectorCopy (self->aim_point, target); // revert to prev target point
}
}
/* if ( (isProx || isContact) && (g_showlogic) && (g_showlogic->value) )
{
if ( targetSafe && leadSafe )
gi.dprintf ("GunnerGrenade: safe to fire at and lead target, saving target point.\n");
else if ( targetSafe && leadingTarget && !leadSafe )
gi.dprintf ("GunnerGrenade: safe to fire at but not lead target, saving target point.\n");
else if ( targetSafe && !leadingTarget )
gi.dprintf ("GunnerGrenade: safe to fire at target, saving target point.\n");
else if ( !targetSafe && leadSafe )
gi.dprintf ("GunnerGrenade: safe to lead target only, not saving target point.\n");
else if ( !targetSafe && !leadSafe && leadingTarget )
gi.dprintf ("GunnerGrenade: NOT safe to fire at or lead target, reverting to prev target point.\n");
else if ( !targetSafe && !leadSafe && !leadingTarget )
gi.dprintf ("GunnerGrenade: NOT safe to fire at target, reverting to prev target point.\n");
} */
// Knightmare- spread out Tactician Gunner's prox mines so they don't collide
if (isProx)
{
target[0] += crandom() * GUNNER_PROX_SPREAD;
target[1] += crandom() * GUNNER_PROX_SPREAD;
}
// Lazarus fog reduction of accuracy
if ( self->monsterinfo.visibility < FOG_CANSEEGOOD )
{
target[0] += crandom() * 640 * (FOG_CANSEEGOOD - self->monsterinfo.visibility);
target[1] += crandom() * 640 * (FOG_CANSEEGOOD - self->monsterinfo.visibility);
target[2] += crandom() * 320 * (FOG_CANSEEGOOD - self->monsterinfo.visibility);
}
// Leading code was here
// aim up if they're on the same level as me and far away.
// if ((range > 512) && (aim[2] < 64) && (aim[2] > -64))
// aim[2] += (range - 512);
/* VectorNormalize (aim);
pitch = aim[2];
if (pitch > 0.4)
pitch = 0.4;
else if (pitch < -0.5)
pitch = -0.5; */
}
//PGM
AimGrenade (self, start, target, GRENADE_VELOCITY, aim, (self->moreflags & FL2_COMMANDER));
// Lazarus - take into account (sort of) feature of adding shooter's velocity to
// grenade velocity
monster_speed = VectorLength(self->velocity);
if (monster_speed > 0)
{
vec3_t v1;
vec_t delta;
VectorCopy (self->velocity, v1);
VectorNormalize (v1);
delta = -monster_speed / GRENADE_VELOCITY;
VectorMA (aim, delta, v1, aim);
VectorNormalize (aim);
}
// FIXME : do a spread -225 -75 75 225 degrees around forward
// VectorCopy (forward, aim);
// VectorMA (forward, spread, right, aim);
// VectorMA (aim, pitch, up, aim);
if (isProx) // Knightmare- Tactician Gunner fires prox mines
{
float prox_timer = (blindfire) ? 60.0f : 30.0f;
monster_fire_prox (self, start, aim, 90, 1, GRENADE_VELOCITY, 20, prox_timer, 192, flash_number);
}
else
monster_fire_grenade (self, start, aim, 50, GRENADE_VELOCITY, flash_number, isContact);
}
mframe_t gunner_frames_attack_chain [] =
{
/*
ai_charge, 0, NULL,
ai_charge, 0, NULL,
ai_charge, 0, NULL,
ai_charge, 0, NULL,
ai_charge, 0, NULL,
ai_charge, 0, NULL,
ai_charge, 0, NULL,
ai_charge, 0, NULL,
*/
ai_charge, 0, gunner_opengun,
ai_charge, 0, NULL,
ai_charge, 0, NULL,
ai_charge, 0, NULL,
ai_charge, 0, NULL,
ai_charge, 0, NULL,
ai_charge, 0, NULL
};
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, 0, NULL,
ai_charge, 0, NULL,
ai_charge, 0, NULL,
ai_charge, 0, NULL,
ai_charge, 0, NULL,
ai_charge, 0, NULL,
ai_charge, 0, NULL
};
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)
{
VectorSubtract(self->monsterinfo.blind_fire_target, self->s.origin, aim);
self->ideal_yaw = vectoyaw(aim);
// gi.dprintf ("blind_fire_target = %s\n", vtos (self->monsterinfo.blind_fire_target));
// gi.dprintf ("gunner_attack: ideal yaw is %f\n", self->ideal_yaw);
}
}
mframe_t gunner_frames_attack_grenade [] =
{
ai_charge, 0, gunner_blind_check,
ai_charge, 0, NULL,
ai_charge, 0, NULL,
ai_charge, 0, NULL,
ai_charge, 0, GunnerGrenade,
ai_charge, 0, NULL,
ai_charge, 0, NULL,
ai_charge, 0, GunnerGrenade,
ai_charge, 0, NULL,
ai_charge, 0, NULL,
ai_charge, 0, GunnerGrenade,
ai_charge, 0, NULL,
ai_charge, 0, NULL,
ai_charge, 0, GunnerGrenade,
ai_charge, 0, NULL,
ai_charge, 0, NULL,
ai_charge, 0, NULL,
ai_charge, 0, NULL,
ai_charge, 0, NULL,
ai_charge, 0, NULL,
ai_charge, 0, NULL
};
mmove_t gunner_move_attack_grenade = {FRAME_attak101, FRAME_attak121, gunner_frames_attack_grenade, gunner_run};
void gunner_attack (edict_t *self)
{
float chance, r;
monster_done_dodge(self);
// PMM
if (self->monsterinfo.attack_state == AS_BLIND)
{
// setup shot probabilities
if (self->monsterinfo.blind_fire_delay < 1.0)
chance = 1.0;
else if (self->monsterinfo.blind_fire_delay < 7.5)
chance = 0.4;
else
chance = 0.1;
r = random();
// minimum of 2 seconds, plus 0-3, after the shots are done
self->monsterinfo.blind_fire_delay += 2.1 + 2.0 + random()*3.0;
// don't shoot at the origin
if (VectorCompare (self->monsterinfo.blind_fire_target, vec3_origin))
return;
// don't shoot if the dice say not to
if (r > chance)
{
// if ((g_showlogic) && (g_showlogic->value))
// gi.dprintf ("blindfire - NO SHOT\n");
return;
}
// turn on manual steering to signal both manual steering and blindfire
// self->monsterinfo.aiflags |= AI_MANUAL_STEERING;
self->monsterinfo.monsterflags |= AI_MANUAL_STEERING;
if ( gunner_grenade_check(self) )
{
// if the check passes, go for the attack
self->monsterinfo.currentmove = &gunner_move_attack_grenade;
self->monsterinfo.attack_finished = level.time + 2 * random();
}
// pmm - should this be active?
// else
// self->monsterinfo.currentmove = &gunner_move_attack_chain;
// if ((g_showlogic) && (g_showlogic->value))
// gi.dprintf ("blind grenade check failed, doing nothing\n");
// turn off blindfire flag
self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
return;
}
// pmm
self->monsterinfo.aiflags2 &= ~AI2_LEAD_TARGET; // Knightmare- reset Tactican Gunner leading target flag
// PGM - gunner needs to use his chaingun if he's being attacked by a tesla.
if ((range (self, self->enemy) == RANGE_MELEE) || self->bad_area)
{
self->monsterinfo.currentmove = &gunner_move_attack_chain;
}
else
{
if (random() <= 0.5 && gunner_grenade_check(self)) {
self->monsterinfo.currentmove = &gunner_move_attack_grenade;
}
else
{
// Knightmare- Tactician Gunner leads the target
if (self->moreflags & FL2_COMMANDER)
{
vec3_t forward, right, start, target, aim;
float dist, chance;
AngleVectors (self->s.angles, forward, right, NULL);
G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_GUNNER_MACHINEGUN_1], forward, right, start);
VectorCopy (self->enemy->s.origin, target);
target[2] += self->enemy->viewheight;
// Lazarus fog reduction of accuracy
if (self->monsterinfo.visibility < FOG_CANSEEGOOD)
{
target[0] += crandom() * 640 * (FOG_CANSEEGOOD - self->monsterinfo.visibility);
target[1] += crandom() * 640 * (FOG_CANSEEGOOD - self->monsterinfo.visibility);
target[2] += crandom() * 320 * (FOG_CANSEEGOOD - self->monsterinfo.visibility);
}
VectorSubtract (target, start, aim);
dist = VectorLength(aim);
if (dist < 640.0f) // chance for leading fire if distance is less than 640
{
// if ((g_showlogic) && (g_showlogic->value))
// gi.dprintf ("Tactician Gunner: target in range, rolling for chance to lead- ");
chance = random(); // chance = 50% easy, 60% medium, 70% hard, 80% hard+
chance += (3.0f - skill->value) * 0.1f;
if (chance < 0.8f)
{
// if ((g_showlogic) && (g_showlogic->value))
// gi.dprintf ("chance passed, leading target\n");
self->monsterinfo.aiflags2 |= AI2_LEAD_TARGET;
}
// else if ((g_showlogic) && (g_showlogic->value))
// gi.dprintf ("chance failed, not leading target\n");
}
}
// end Knightmare
self->monsterinfo.currentmove = &gunner_move_attack_chain;
}
}
}
void gunner_fire_chain (edict_t *self)
{
self->monsterinfo.currentmove = &gunner_move_fire_chain;
}
void gunner_refire_chain (edict_t *self)
{
if (self->enemy->health > 0)
if ( visible (self, self->enemy) )
if (random() <= 0.5)
{
self->monsterinfo.currentmove = &gunner_move_fire_chain;
return;
}
self->monsterinfo.currentmove = &gunner_move_endfire_chain;
}
/*
void gunner_dodge (edict_t *self, edict_t *attacker, float eta, trace_t *tr)
{
// original quake2 dodge code
if (random() > 0.25)
return;
if (!self->enemy)
self->enemy = attacker;
self->monsterinfo.currentmove = &gunner_move_duck;
//===========
//PMM - rogue rewrite of gunner dodge code.
float r;
float height;
int shooting = 0;
if (!self->enemy)
{
self->enemy = attacker;
FoundTarget (self);
}
// PMM - don't bother if it's going to hit anyway; fix for weird in-your-face etas (I was
// seeing numbers like 13 and 14)
if ((eta < 0.1) || (eta > 5))
return;
r = random();
if (r > (0.25*((skill->value)+1)))
return;
if ((self->monsterinfo.currentmove == &gunner_move_attack_chain) ||
(self->monsterinfo.currentmove == &gunner_move_fire_chain) ||
(self->monsterinfo.currentmove == &gunner_move_attack_grenade)
)
{
shooting = 1;
}
if (self->monsterinfo.aiflags & AI_DODGING)
{
height = self->absmax[2];
}
else
{
height = self->absmax[2]-32-1; // the -1 is because the absmax is s.origin + maxs + 1
}
// check to see if it makes sense to duck
if (tr->endpos[2] <= height)
{
vec3_t right, diff;
if (shooting)
{
self->monsterinfo.attack_state = AS_SLIDING;
return;
}
AngleVectors (self->s.angles, NULL, right, NULL);
VectorSubtract (tr->endpos, self->s.origin, diff);
if (DotProduct (right, diff) < 0)
{
self->monsterinfo.lefty = 1;
}
// if it doesn't sense to duck, try to strafe away
monster_done_dodge (self);
self->monsterinfo.currentmove = &gunner_move_run;
self->monsterinfo.attack_state = AS_SLIDING;
return;
}
if (skill->value == 0)
{
self->monsterinfo.currentmove = &gunner_move_duck;
// PMM - stupid dodge
self->monsterinfo.duck_wait_time = level.time + eta + 1;
self->monsterinfo.aiflags |= AI_DODGING;
return;
}
if (!shooting)
{
self->monsterinfo.currentmove = &gunner_move_duck;
self->monsterinfo.duck_wait_time = level.time + eta + (0.1 * (3 - skill->value));
self->monsterinfo.aiflags |= AI_DODGING;
}
return;
//PMM
//===========
}
*/
//===========
//PGM
void gunner_jump_now (edict_t *self)
{
vec3_t forward,up;
monster_jump_start (self);
AngleVectors (self->s.angles, forward, NULL, up);
VectorMA(self->velocity, 100, forward, self->velocity);
VectorMA(self->velocity, 300, up, self->velocity);
}
void gunner_jump2_now (edict_t *self)
{
vec3_t forward,up;
monster_jump_start (self);
AngleVectors (self->s.angles, forward, NULL, up);
VectorMA(self->velocity, 150, forward, self->velocity);
VectorMA(self->velocity, 400, up, self->velocity);
}
void gunner_jump_wait_land (edict_t *self)
{
if (self->groundentity == NULL)
{
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, 0, NULL,
ai_move, 0, NULL,
ai_move, 0, NULL,
ai_move, 0, gunner_jump_now,
ai_move, 0, NULL,
ai_move, 0, NULL,
ai_move, 0, gunner_jump_wait_land,
ai_move, 0, NULL,
ai_move, 0, NULL,
ai_move, 0, NULL
};
mmove_t gunner_move_jump = { FRAME_jump01, FRAME_jump10, gunner_frames_jump, gunner_run };
mframe_t gunner_frames_jump2 [] =
{
ai_move, -8, NULL,
ai_move, -4, NULL,
ai_move, -4, NULL,
ai_move, 0, gunner_jump_now,
ai_move, 0, NULL,
ai_move, 0, NULL,
ai_move, 0, gunner_jump_wait_land,
ai_move, 0, NULL,
ai_move, 0, NULL,
ai_move, 0, NULL
};
mmove_t gunner_move_jump2 = { FRAME_jump01, FRAME_jump10, gunner_frames_jump2, gunner_run };
void gunner_jump (edict_t *self)
{
if (!self->enemy)
return;
monster_done_dodge (self);
if (self->enemy->s.origin[2] > self->s.origin[2])
self->monsterinfo.currentmove = &gunner_move_jump2;
else
self->monsterinfo.currentmove = &gunner_move_jump;
}
//===========
//PGM
qboolean gunner_blocked (edict_t *self, float dist)
{
if (blocked_checkshot (self, 0.25 + (0.05 * skill->value) ))
return true;
if (blocked_checkplat (self, dist))
return true;
if (blocked_checkjump (self, dist, 192, 40))
{
gunner_jump (self);
return true;
}
return false;
}
//PGM
//===========
// PMM - new duck code
void gunner_duck (edict_t *self, float eta)
{
if ((self->monsterinfo.currentmove == &gunner_move_jump2) ||
(self->monsterinfo.currentmove == &gunner_move_jump))
{
return;
}
if ((self->monsterinfo.currentmove == &gunner_move_attack_chain) ||
(self->monsterinfo.currentmove == &gunner_move_fire_chain) ||
(self->monsterinfo.currentmove == &gunner_move_attack_grenade)
)
{
// if we're shooting, and not on easy, don't dodge
if (skill->value)
{
self->monsterinfo.aiflags &= ~AI_DUCKED;
return;
}
}
if (skill->value == 0)
// PMM - stupid dodge
self->monsterinfo.duck_wait_time = level.time + eta + 1;
else
self->monsterinfo.duck_wait_time = level.time + eta + (0.1 * (3 - skill->value));
// has to be done immediately otherwise he can get stuck
gunner_duck_down(self);
self->monsterinfo.nextframe = FRAME_duck01;
self->monsterinfo.currentmove = &gunner_move_duck;
return;
}
void gunner_sidestep (edict_t *self)
{
if ((self->monsterinfo.currentmove == &gunner_move_jump2) ||
(self->monsterinfo.currentmove == &gunner_move_jump))
{
return;
}
if ((self->monsterinfo.currentmove == &gunner_move_attack_chain) ||
(self->monsterinfo.currentmove == &gunner_move_fire_chain) ||
(self->monsterinfo.currentmove == &gunner_move_attack_grenade)
)
{
// if we're shooting, and not on easy, don't dodge
if (skill->value)
{
self->monsterinfo.aiflags &= ~AI_DODGING;
return;
}
}
if (self->monsterinfo.currentmove != &gunner_move_run)
self->monsterinfo.currentmove = &gunner_move_run;
}
/*QUAKED monster_gunner (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight GoodGuy NoGib ContactGrenades
*/
/*QUAKED monster_gunner_tactician (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight GoodGuy NoGib
*/
void SP_monster_gunner (edict_t *self)
{
if (deathmatch->value)
{
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"); // not used by Tactician Gunner
gi.soundindex ("gunner/gunatck3.wav");
self->movetype = MOVETYPE_STEP;
self->solid = SOLID_BBOX;
// Lazarus: special purpose skins
if (strcmp(self->classname, "monster_gunner_tactician") == 0)
{
self->s.skinnum = 2;
self->moreflags |= FL2_COMMANDER;
}
if ( self->style )
{
PatchMonsterModel("models/monsters/gunner/tris.md2");
self->s.skinnum += self->style * 4;
}
self->s.modelindex = gi.modelindex ("models/monsters/gunner/tris.md2");
{ // Coconut Monkey 2 sombrero
cvar_t *gamedir = gi.cvar("gamedir", "", 0);
if (strlen(gamedir->string) && !strcmp(gamedir->string, "coconut2"))
self->s.modelindex2 = gi.modelindex ("models/monsters/gunner/gear.md2");
}
VectorSet (self->mins, -16, -16, -24);
VectorSet (self->maxs, 16, 16, 32);
if (strcmp(self->classname, "monster_gunner_tactician") == 0)
{ // precache
gi.modelindex ("models/weapons/g_prox/tris.md2");
gi.modelindex ("models/proj/flechette/tris.md2");
gi.soundindex ("weapons/proxopen.wav");
gi.soundindex ("weapons/proxwarn.wav");
#ifdef KMQUAKE2_ENGINE_MOD
gi.soundindex ("weapons/nail1.wav");
#else
tactician_sound_fire_flechette = gi.soundindex ("weapons/nail1.wav");
#endif // KMQUAKE2_ENGINE_MOD
/*
tactician_sound_death = gi.soundindex ("tactician_gunner/death1.wav");
tactician_sound_pain = gi.soundindex ("tactician_gunner/gunpain2.wav");
tactician_sound_pain2 = gi.soundindex ("tactician_gunner/gunpain1.wav");
tactician_sound_idle = gi.soundindex ("tactician_gunner/gunidle1.wav");
tactician_sound_open = gi.soundindex ("tactician_gunner/gunatck1.wav");
tactician_sound_search = gi.soundindex ("tactician_gunner/gunsrch1.wav");
tactician_sound_sight = gi.soundindex ("tactician_gunner/sight1.wav");
*/
if (!self->health)
self->health = 400;
if (!self->gib_health)
self->gib_health = -250;
if (!self->mass)
self->mass = 300;
// Lazarus
if (self->powerarmor)
{
if (self->powerarmortype == 1)
self->monsterinfo.power_armor_type = POWER_ARMOR_SCREEN;
else
self->monsterinfo.power_armor_type = POWER_ARMOR_SHIELD;
self->monsterinfo.power_armor_power = self->powerarmor;
}
else
{
self->monsterinfo.power_armor_type = POWER_ARMOR_SHIELD;
self->monsterinfo.power_armor_power = 300;
}
self->common_name = "Tactician Gunner";
self->class_id = ENTITY_MONSTER_GUNNER_TACTICIAN;
self->monsterinfo.monsterflags |= MFL_KNOWS_PROX_MINES; // Tactician Gunner avoids prox mines
}
else
{ // precache
gi.modelindex ("models/objects/grenade/tris.md2");
gi.soundindex ("gunner/gunatck2.wav");
if (!self->health)
self->health = 175;
if (!self->gib_health)
self->gib_health = -150;
if (!self->mass)
self->mass = 200;
// Lazarus
if (self->powerarmor)
{
if (self->powerarmortype == 1)
self->monsterinfo.power_armor_type = POWER_ARMOR_SCREEN;
else
self->monsterinfo.power_armor_type = POWER_ARMOR_SHIELD;
self->monsterinfo.power_armor_power = self->powerarmor;
}
self->common_name = "Gunner";
self->class_id = ENTITY_MONSTER_GUNNER;
}
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.dodge = gunner_dodge;
// pmm
self->monsterinfo.attack = gunner_attack;
self->monsterinfo.melee = NULL;
self->monsterinfo.sight = gunner_sight;
self->monsterinfo.search = gunner_search;
self->monsterinfo.blocked = gunner_blocked; //PGM
if (!self->blood_type)
self->blood_type = 3; // sparks and blood
if ( !self->monsterinfo.flies && strcmp(self->classname, "monster_gunner_tactician") == 0 )
self->monsterinfo.flies = 0.20;
else if (!self->monsterinfo.flies)
self->monsterinfo.flies = 0.30;
if (monsterjump->value)
{
self->monsterinfo.jump = gunner_jump;
self->monsterinfo.jumpup = 48;
self->monsterinfo.jumpdn = 64;
}
gi.linkentity (self);
self->monsterinfo.currentmove = &gunner_move_stand;
if (self->health < 0)
{
mmove_t *deathmoves[] = {&gunner_move_death,
NULL};
M_SetDeath (self, (mmove_t **)&deathmoves);
}
self->monsterinfo.scale = MODEL_SCALE;
// PMM
self->monsterinfo.blindfire = true;
walkmonster_start (self);
}