mirror of
https://github.com/yquake2/xatrix.git
synced 2025-06-01 17:42:02 +00:00
With this commit 'xatrix' is cleaned up. Every single line was audited, many bugs removed and hundered of sanity checks added. Additionally the code was reformated. All in all 'Xatrix' should now be much more reliable and for the first time in ~15 years completeable without any crashes, logic bugs or the like. Until now about 130 hours spread over more than a year were spend for this project. Of course this code is totaly untested. The next step is testing, testing and testing. After that the spawn points needs to be fixed so that all maps can pe started by "map $mapname".
860 lines
15 KiB
C
860 lines
15 KiB
C
/* =======================================================================
|
|
*
|
|
* Gunner.
|
|
*
|
|
* =======================================================================
|
|
*/
|
|
|
|
#include "../../header/local.h"
|
|
#include "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;
|
|
|
|
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);
|
|
|
|
void
|
|
gunner_idlesound(edict_t *self)
|
|
{
|
|
if (!self)
|
|
{
|
|
return;
|
|
}
|
|
|
|
gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0);
|
|
}
|
|
|
|
void
|
|
gunner_sight(edict_t *self, edict_t *other)
|
|
{
|
|
if (!self)
|
|
{
|
|
return;
|
|
}
|
|
|
|
gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
|
|
}
|
|
|
|
void
|
|
gunner_search(edict_t *self)
|
|
{
|
|
if (!self)
|
|
{
|
|
return;
|
|
}
|
|
|
|
gi.sound(self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0);
|
|
}
|
|
|
|
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)
|
|
{
|
|
return;
|
|
}
|
|
|
|
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)
|
|
{
|
|
if (!self)
|
|
{
|
|
return;
|
|
}
|
|
|
|
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)
|
|
{
|
|
if (!self)
|
|
{
|
|
return;
|
|
}
|
|
|
|
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, NULL},
|
|
{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)
|
|
{
|
|
if (!self)
|
|
{
|
|
return;
|
|
}
|
|
|
|
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)
|
|
{
|
|
if (!self)
|
|
{
|
|
return;
|
|
}
|
|
|
|
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 /* unused */,
|
|
float kick /* unused */, int damage)
|
|
{
|
|
if (!self)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (self->health < (self->max_health / 2))
|
|
{
|
|
self->s.skinnum = 1;
|
|
}
|
|
|
|
if (level.time < self->pain_debounce_time)
|
|
{
|
|
return;
|
|
}
|
|
|
|
self->pain_debounce_time = level.time + 3;
|
|
|
|
if (rand() & 1)
|
|
{
|
|
gi.sound(self, CHAN_VOICE, sound_pain, 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 (damage <= 10)
|
|
{
|
|
self->monsterinfo.currentmove = &gunner_move_pain3;
|
|
}
|
|
else if (damage <= 25)
|
|
{
|
|
self->monsterinfo.currentmove = &gunner_move_pain2;
|
|
}
|
|
else
|
|
{
|
|
self->monsterinfo.currentmove = &gunner_move_pain1;
|
|
}
|
|
}
|
|
|
|
void
|
|
gunner_dead(edict_t *self)
|
|
{
|
|
if (!self)
|
|
{
|
|
return;
|
|
}
|
|
|
|
VectorSet(self->mins, -16, -16, -24);
|
|
VectorSet(self->maxs, 16, 16, -8);
|
|
self->movetype = MOVETYPE_TOSS;
|
|
self->svflags |= SVF_DEADMONSTER;
|
|
self->nextthink = 0;
|
|
gi.linkentity(self);
|
|
}
|
|
|
|
mframe_t 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 /* unused */,
|
|
edict_t *attacker /* unused */, int damage /* unused */,
|
|
vec3_t point)
|
|
{
|
|
int n;
|
|
|
|
if (!self)
|
|
{
|
|
return;
|
|
}
|
|
|
|
/* check for gib */
|
|
if (self->health <= self->gib_health)
|
|
{
|
|
gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0);
|
|
|
|
for (n = 0; n < 2; n++)
|
|
{
|
|
ThrowGib(self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC);
|
|
}
|
|
|
|
for (n = 0; n < 4; n++)
|
|
{
|
|
ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
|
|
}
|
|
|
|
ThrowHead(self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC);
|
|
self->deadflag = DEAD_DEAD;
|
|
return;
|
|
}
|
|
|
|
if (self->deadflag == DEAD_DEAD)
|
|
{
|
|
return;
|
|
}
|
|
|
|
/* regular death */
|
|
gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0);
|
|
self->deadflag = DEAD_DEAD;
|
|
self->takedamage = DAMAGE_YES;
|
|
self->monsterinfo.currentmove = &gunner_move_death;
|
|
}
|
|
|
|
void
|
|
gunner_duck_down(edict_t *self)
|
|
{
|
|
if (!self)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (self->monsterinfo.aiflags & AI_DUCKED)
|
|
{
|
|
return;
|
|
}
|
|
|
|
self->monsterinfo.aiflags |= AI_DUCKED;
|
|
|
|
if (skill->value >= 2)
|
|
{
|
|
if (random() > 0.5)
|
|
{
|
|
GunnerGrenade(self);
|
|
}
|
|
}
|
|
|
|
self->maxs[2] -= 32;
|
|
self->takedamage = DAMAGE_YES;
|
|
self->monsterinfo.pausetime = level.time + 1;
|
|
gi.linkentity(self);
|
|
}
|
|
|
|
void
|
|
gunner_duck_hold(edict_t *self)
|
|
{
|
|
if (!self)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (level.time >= self->monsterinfo.pausetime)
|
|
{
|
|
self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
|
|
}
|
|
else
|
|
{
|
|
self->monsterinfo.aiflags |= AI_HOLD_FRAME;
|
|
}
|
|
}
|
|
|
|
void
|
|
gunner_duck_up(edict_t *self)
|
|
{
|
|
if (!self)
|
|
{
|
|
return;
|
|
}
|
|
|
|
self->monsterinfo.aiflags &= ~AI_DUCKED;
|
|
self->maxs[2] += 32;
|
|
self->takedamage = DAMAGE_AIM;
|
|
gi.linkentity(self);
|
|
}
|
|
|
|
mframe_t gunner_frames_duck[] = {
|
|
{ai_move, 1, gunner_duck_down},
|
|
{ai_move, 1, NULL},
|
|
{ai_move, 1, gunner_duck_hold},
|
|
{ai_move, 0, NULL},
|
|
{ai_move, -1, NULL},
|
|
{ai_move, -1, NULL},
|
|
{ai_move, 0, gunner_duck_up},
|
|
{ai_move, -1, NULL}
|
|
};
|
|
|
|
mmove_t gunner_move_duck = {
|
|
FRAME_duck01,
|
|
FRAME_duck08,
|
|
gunner_frames_duck,
|
|
gunner_run
|
|
};
|
|
|
|
void
|
|
gunner_dodge(edict_t *self, edict_t *attacker, float eta /* unsued */)
|
|
{
|
|
if (!self || !attacker)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (random() > 0.25)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!self->enemy)
|
|
{
|
|
self->enemy = attacker;
|
|
}
|
|
|
|
self->monsterinfo.currentmove = &gunner_move_duck;
|
|
}
|
|
|
|
void
|
|
gunner_opengun(edict_t *self)
|
|
{
|
|
if (!self)
|
|
{
|
|
return;
|
|
}
|
|
|
|
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;
|
|
int flash_number;
|
|
|
|
if (!self)
|
|
{
|
|
return;
|
|
}
|
|
|
|
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);
|
|
VectorMA(target, -0.2, self->enemy->velocity, target);
|
|
target[2] += self->enemy->viewheight;
|
|
|
|
VectorSubtract(target, start, aim);
|
|
VectorNormalize(aim);
|
|
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;
|
|
vec3_t aim;
|
|
int flash_number;
|
|
|
|
if (!self)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (self->s.frame == FRAME_attak105)
|
|
{
|
|
flash_number = MZ2_GUNNER_GRENADE_1;
|
|
}
|
|
else if (self->s.frame == FRAME_attak108)
|
|
{
|
|
flash_number = MZ2_GUNNER_GRENADE_2;
|
|
}
|
|
else if (self->s.frame == FRAME_attak111)
|
|
{
|
|
flash_number = MZ2_GUNNER_GRENADE_3;
|
|
}
|
|
else
|
|
{
|
|
flash_number = MZ2_GUNNER_GRENADE_4;
|
|
}
|
|
|
|
AngleVectors(self->s.angles, forward, right, NULL);
|
|
G_ProjectSource(self->s.origin, monster_flash_offset[flash_number],
|
|
forward, right, start);
|
|
VectorCopy(forward, aim);
|
|
monster_fire_grenade(self, start, aim, 50, 600, flash_number);
|
|
}
|
|
|
|
mframe_t gunner_frames_attack_chain[] = {
|
|
{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
|
|
};
|
|
|
|
mframe_t gunner_frames_attack_grenade[] = {
|
|
{ai_charge, 0, NULL},
|
|
{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)
|
|
{
|
|
if (!self)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (range(self, self->enemy) == RANGE_MELEE)
|
|
{
|
|
self->monsterinfo.currentmove = &gunner_move_attack_chain;
|
|
}
|
|
else
|
|
{
|
|
if (random() <= 0.5)
|
|
{
|
|
self->monsterinfo.currentmove = &gunner_move_attack_grenade;
|
|
}
|
|
else
|
|
{
|
|
self->monsterinfo.currentmove = &gunner_move_attack_chain;
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
gunner_fire_chain(edict_t *self)
|
|
{
|
|
if (!self)
|
|
{
|
|
return;
|
|
}
|
|
|
|
self->monsterinfo.currentmove = &gunner_move_fire_chain;
|
|
}
|
|
|
|
void
|
|
gunner_refire_chain(edict_t *self)
|
|
{
|
|
if (!self)
|
|
{
|
|
return;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* QUAKED monster_gunner (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight
|
|
*/
|
|
void
|
|
SP_monster_gunner(edict_t *self)
|
|
{
|
|
if (!self)
|
|
{
|
|
return;
|
|
}
|
|
|
|
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");
|
|
gi.soundindex("gunner/gunatck3.wav");
|
|
|
|
self->movetype = MOVETYPE_STEP;
|
|
self->solid = SOLID_BBOX;
|
|
self->s.modelindex = gi.modelindex("models/monsters/gunner/tris.md2");
|
|
VectorSet(self->mins, -16, -16, -24);
|
|
VectorSet(self->maxs, 16, 16, 32);
|
|
|
|
self->health = 175;
|
|
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;
|
|
self->monsterinfo.dodge = gunner_dodge;
|
|
self->monsterinfo.attack = gunner_attack;
|
|
self->monsterinfo.melee = NULL;
|
|
self->monsterinfo.sight = gunner_sight;
|
|
self->monsterinfo.search = gunner_search;
|
|
|
|
gi.linkentity(self);
|
|
|
|
self->monsterinfo.currentmove = &gunner_move_stand;
|
|
self->monsterinfo.scale = MODEL_SCALE;
|
|
|
|
walkmonster_start(self);
|
|
}
|
|
|