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".
1097 lines
20 KiB
C
1097 lines
20 KiB
C
/* =======================================================================
|
|
*
|
|
* Tank and Tank Commander.
|
|
*
|
|
* =======================================================================
|
|
*/
|
|
|
|
#include "../../header/local.h"
|
|
#include "tank.h"
|
|
|
|
void tank_refire_rocket(edict_t *self);
|
|
void tank_doattack_rocket(edict_t *self);
|
|
void tank_reattack_blaster(edict_t *self);
|
|
void tank_walk(edict_t *self);
|
|
void tank_run(edict_t *self);
|
|
|
|
static int sound_thud;
|
|
static int sound_pain;
|
|
static int sound_idle;
|
|
static int sound_die;
|
|
static int sound_step;
|
|
static int sound_sight;
|
|
static int sound_windup;
|
|
static int sound_strike;
|
|
|
|
void
|
|
tank_sight(edict_t *self, edict_t *other /* unused */)
|
|
{
|
|
if (!self)
|
|
{
|
|
return;
|
|
}
|
|
|
|
gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
|
|
}
|
|
|
|
void
|
|
tank_footstep(edict_t *self)
|
|
{
|
|
if (!self)
|
|
{
|
|
return;
|
|
}
|
|
|
|
gi.sound(self, CHAN_BODY, sound_step, 1, ATTN_NORM, 0);
|
|
}
|
|
|
|
void
|
|
tank_thud(edict_t *self)
|
|
{
|
|
if (!self)
|
|
{
|
|
return;
|
|
}
|
|
|
|
gi.sound(self, CHAN_BODY, sound_thud, 1, ATTN_NORM, 0);
|
|
}
|
|
|
|
void
|
|
tank_windup(edict_t *self)
|
|
{
|
|
if (!self)
|
|
{
|
|
return;
|
|
}
|
|
|
|
gi.sound(self, CHAN_WEAPON, sound_windup, 1, ATTN_NORM, 0);
|
|
}
|
|
|
|
void
|
|
tank_idle(edict_t *self)
|
|
{
|
|
if (!self)
|
|
{
|
|
return;
|
|
}
|
|
|
|
gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0);
|
|
}
|
|
|
|
mframe_t tank_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, 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 tank_move_stand = {
|
|
FRAME_stand01,
|
|
FRAME_stand30,
|
|
tank_frames_stand,
|
|
NULL
|
|
};
|
|
|
|
void
|
|
tank_stand(edict_t *self)
|
|
{
|
|
if (!self)
|
|
{
|
|
return;
|
|
}
|
|
|
|
self->monsterinfo.currentmove = &tank_move_stand;
|
|
}
|
|
|
|
mframe_t tank_frames_start_walk[] = {
|
|
{ai_walk, 0, NULL},
|
|
{ai_walk, 6, NULL},
|
|
{ai_walk, 6, NULL},
|
|
{ai_walk, 11, tank_footstep}
|
|
};
|
|
|
|
mmove_t tank_move_start_walk = {
|
|
FRAME_walk01,
|
|
FRAME_walk04,
|
|
tank_frames_start_walk,
|
|
tank_walk
|
|
};
|
|
|
|
mframe_t tank_frames_walk[] = {
|
|
{ai_walk, 4, NULL},
|
|
{ai_walk, 5, NULL},
|
|
{ai_walk, 3, NULL},
|
|
{ai_walk, 2, NULL},
|
|
{ai_walk, 5, NULL},
|
|
{ai_walk, 5, NULL},
|
|
{ai_walk, 4, NULL},
|
|
{ai_walk, 4, tank_footstep},
|
|
{ai_walk, 3, NULL},
|
|
{ai_walk, 5, NULL},
|
|
{ai_walk, 4, NULL},
|
|
{ai_walk, 5, NULL},
|
|
{ai_walk, 7, NULL},
|
|
{ai_walk, 7, NULL},
|
|
{ai_walk, 6, NULL},
|
|
{ai_walk, 6, tank_footstep}
|
|
};
|
|
|
|
mmove_t tank_move_walk = {
|
|
FRAME_walk05,
|
|
FRAME_walk20,
|
|
tank_frames_walk,
|
|
NULL
|
|
};
|
|
|
|
mframe_t tank_frames_stop_walk[] = {
|
|
{ai_walk, 3, NULL},
|
|
{ai_walk, 3, NULL},
|
|
{ai_walk, 2, NULL},
|
|
{ai_walk, 2, NULL},
|
|
{ai_walk, 4, tank_footstep}
|
|
};
|
|
|
|
mmove_t tank_move_stop_walk = {
|
|
FRAME_walk21,
|
|
FRAME_walk25,
|
|
tank_frames_stop_walk,
|
|
tank_stand
|
|
};
|
|
|
|
void
|
|
tank_walk(edict_t *self)
|
|
{
|
|
if (!self)
|
|
{
|
|
return;
|
|
}
|
|
|
|
self->monsterinfo.currentmove = &tank_move_walk;
|
|
}
|
|
|
|
mframe_t tank_frames_start_run[] = {
|
|
{ai_run, 0, NULL},
|
|
{ai_run, 6, NULL},
|
|
{ai_run, 6, NULL},
|
|
{ai_run, 11, tank_footstep}
|
|
};
|
|
|
|
mmove_t tank_move_start_run = {
|
|
FRAME_walk01,
|
|
FRAME_walk04,
|
|
tank_frames_start_run,
|
|
tank_run
|
|
};
|
|
|
|
mframe_t tank_frames_run[] = {
|
|
{ai_run, 4, NULL},
|
|
{ai_run, 5, NULL},
|
|
{ai_run, 3, NULL},
|
|
{ai_run, 2, NULL},
|
|
{ai_run, 5, NULL},
|
|
{ai_run, 5, NULL},
|
|
{ai_run, 4, NULL},
|
|
{ai_run, 4, tank_footstep},
|
|
{ai_run, 3, NULL},
|
|
{ai_run, 5, NULL},
|
|
{ai_run, 4, NULL},
|
|
{ai_run, 5, NULL},
|
|
{ai_run, 7, NULL},
|
|
{ai_run, 7, NULL},
|
|
{ai_run, 6, NULL},
|
|
{ai_run, 6, tank_footstep}
|
|
};
|
|
|
|
mmove_t tank_move_run = {
|
|
FRAME_walk05,
|
|
FRAME_walk20,
|
|
tank_frames_run,
|
|
NULL
|
|
};
|
|
|
|
mframe_t tank_frames_stop_run[] = {
|
|
{ai_run, 3, NULL},
|
|
{ai_run, 3, NULL},
|
|
{ai_run, 2, NULL},
|
|
{ai_run, 2, NULL},
|
|
{ai_run, 4, tank_footstep}
|
|
};
|
|
|
|
mmove_t tank_move_stop_run = {
|
|
FRAME_walk21,
|
|
FRAME_walk25,
|
|
tank_frames_stop_run,
|
|
tank_walk
|
|
};
|
|
|
|
void
|
|
tank_run(edict_t *self)
|
|
{
|
|
if (!self)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (self->enemy && self->enemy->client)
|
|
{
|
|
self->monsterinfo.aiflags |= AI_BRUTAL;
|
|
}
|
|
else
|
|
{
|
|
self->monsterinfo.aiflags &= ~AI_BRUTAL;
|
|
}
|
|
|
|
if (self->monsterinfo.aiflags & AI_STAND_GROUND)
|
|
{
|
|
self->monsterinfo.currentmove = &tank_move_stand;
|
|
return;
|
|
}
|
|
|
|
if ((self->monsterinfo.currentmove == &tank_move_walk) ||
|
|
(self->monsterinfo.currentmove == &tank_move_start_run))
|
|
{
|
|
self->monsterinfo.currentmove = &tank_move_run;
|
|
}
|
|
else
|
|
{
|
|
self->monsterinfo.currentmove = &tank_move_start_run;
|
|
}
|
|
}
|
|
|
|
mframe_t tank_frames_pain1[] = {
|
|
{ai_move, 0, NULL},
|
|
{ai_move, 0, NULL},
|
|
{ai_move, 0, NULL},
|
|
{ai_move, 0, NULL}
|
|
};
|
|
|
|
mmove_t tank_move_pain1 = {
|
|
FRAME_pain101,
|
|
FRAME_pain104,
|
|
tank_frames_pain1,
|
|
tank_run
|
|
};
|
|
|
|
mframe_t tank_frames_pain2[] = {
|
|
{ai_move, 0, NULL},
|
|
{ai_move, 0, NULL},
|
|
{ai_move, 0, NULL},
|
|
{ai_move, 0, NULL},
|
|
{ai_move, 0, NULL}
|
|
};
|
|
|
|
mmove_t tank_move_pain2 = {
|
|
FRAME_pain201,
|
|
FRAME_pain205,
|
|
tank_frames_pain2,
|
|
tank_run
|
|
};
|
|
|
|
mframe_t tank_frames_pain3[] = {
|
|
{ai_move, -7, NULL},
|
|
{ai_move, 0, NULL},
|
|
{ai_move, 0, NULL},
|
|
{ai_move, 0, NULL},
|
|
{ai_move, 2, NULL},
|
|
{ai_move, 0, NULL},
|
|
{ai_move, 0, NULL},
|
|
{ai_move, 3, NULL},
|
|
{ai_move, 0, NULL},
|
|
{ai_move, 2, NULL},
|
|
{ai_move, 0, NULL},
|
|
{ai_move, 0, NULL},
|
|
{ai_move, 0, NULL},
|
|
{ai_move, 0, NULL},
|
|
{ai_move, 0, NULL},
|
|
{ai_move, 0, tank_footstep}
|
|
};
|
|
|
|
mmove_t tank_move_pain3 = {
|
|
FRAME_pain301,
|
|
FRAME_pain316,
|
|
tank_frames_pain3,
|
|
tank_run
|
|
};
|
|
|
|
void
|
|
tank_pain(edict_t *self, edict_t *other /* other */,
|
|
float kick /* other */, int damage)
|
|
{
|
|
if (!self)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (self->health < (self->max_health / 2))
|
|
{
|
|
self->s.skinnum |= 1;
|
|
}
|
|
|
|
if (damage <= 10)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (level.time < self->pain_debounce_time)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (damage <= 30)
|
|
{
|
|
if (random() > 0.2)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* If hard or nightmare, don't go into pain while attacking */
|
|
if (skill->value >= 2)
|
|
{
|
|
if ((self->s.frame >= FRAME_attak301) &&
|
|
(self->s.frame <= FRAME_attak330))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ((self->s.frame >= FRAME_attak101) &&
|
|
(self->s.frame <= FRAME_attak116))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
self->pain_debounce_time = level.time + 3;
|
|
gi.sound(self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0);
|
|
|
|
if (skill->value == 3)
|
|
{
|
|
return; /* no pain anims in nightmare */
|
|
}
|
|
|
|
if (damage <= 30)
|
|
{
|
|
self->monsterinfo.currentmove = &tank_move_pain1;
|
|
}
|
|
else if (damage <= 60)
|
|
{
|
|
self->monsterinfo.currentmove = &tank_move_pain2;
|
|
}
|
|
else
|
|
{
|
|
self->monsterinfo.currentmove = &tank_move_pain3;
|
|
}
|
|
}
|
|
|
|
void
|
|
TankBlaster(edict_t *self)
|
|
{
|
|
vec3_t forward, right;
|
|
vec3_t start;
|
|
vec3_t end;
|
|
vec3_t dir;
|
|
int flash_number;
|
|
|
|
if (!self)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (self->s.frame == FRAME_attak110)
|
|
{
|
|
flash_number = MZ2_TANK_BLASTER_1;
|
|
}
|
|
else if (self->s.frame == FRAME_attak113)
|
|
{
|
|
flash_number = MZ2_TANK_BLASTER_2;
|
|
}
|
|
else
|
|
{
|
|
flash_number = MZ2_TANK_BLASTER_3;
|
|
}
|
|
|
|
AngleVectors(self->s.angles, forward, right, NULL);
|
|
G_ProjectSource(self->s.origin, monster_flash_offset[flash_number],
|
|
forward, right, start);
|
|
|
|
VectorCopy(self->enemy->s.origin, end);
|
|
end[2] += self->enemy->viewheight;
|
|
VectorSubtract(end, start, dir);
|
|
|
|
monster_fire_blaster(self, start, dir, 30, 800, flash_number, EF_BLASTER);
|
|
}
|
|
|
|
void
|
|
TankStrike(edict_t *self)
|
|
{
|
|
if (!self)
|
|
{
|
|
return;
|
|
}
|
|
|
|
gi.sound(self, CHAN_WEAPON, sound_strike, 1, ATTN_NORM, 0);
|
|
}
|
|
|
|
void
|
|
TankRocket(edict_t *self)
|
|
{
|
|
vec3_t forward, right;
|
|
vec3_t start;
|
|
vec3_t dir;
|
|
vec3_t vec;
|
|
int flash_number;
|
|
|
|
if (!self)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (self->s.frame == FRAME_attak324)
|
|
{
|
|
flash_number = MZ2_TANK_ROCKET_1;
|
|
}
|
|
else if (self->s.frame == FRAME_attak327)
|
|
{
|
|
flash_number = MZ2_TANK_ROCKET_2;
|
|
}
|
|
else
|
|
{
|
|
flash_number = MZ2_TANK_ROCKET_3;
|
|
}
|
|
|
|
AngleVectors(self->s.angles, forward, right, NULL);
|
|
G_ProjectSource(self->s.origin, monster_flash_offset[flash_number],
|
|
forward, right, start);
|
|
|
|
VectorCopy(self->enemy->s.origin, vec);
|
|
vec[2] += self->enemy->viewheight;
|
|
VectorSubtract(vec, start, dir);
|
|
VectorNormalize(dir);
|
|
|
|
monster_fire_rocket(self, start, dir, 50, 550, flash_number);
|
|
}
|
|
|
|
void
|
|
TankMachineGun(edict_t *self)
|
|
{
|
|
vec3_t dir;
|
|
vec3_t vec;
|
|
vec3_t start;
|
|
vec3_t forward, right;
|
|
int flash_number;
|
|
|
|
if (!self)
|
|
{
|
|
return;
|
|
}
|
|
|
|
flash_number = MZ2_TANK_MACHINEGUN_1 + (self->s.frame - FRAME_attak406);
|
|
|
|
AngleVectors(self->s.angles, forward, right, NULL);
|
|
G_ProjectSource(self->s.origin, monster_flash_offset[flash_number],
|
|
forward, right, start);
|
|
|
|
if (self->enemy)
|
|
{
|
|
VectorCopy(self->enemy->s.origin, vec);
|
|
vec[2] += self->enemy->viewheight;
|
|
VectorSubtract(vec, start, vec);
|
|
vectoangles(vec, vec);
|
|
dir[0] = vec[0];
|
|
}
|
|
else
|
|
{
|
|
dir[0] = 0;
|
|
}
|
|
|
|
if (self->s.frame <= FRAME_attak415)
|
|
{
|
|
dir[1] = self->s.angles[1] - 8 * (self->s.frame - FRAME_attak411);
|
|
}
|
|
else
|
|
{
|
|
dir[1] = self->s.angles[1] + 8 * (self->s.frame - FRAME_attak419);
|
|
}
|
|
|
|
dir[2] = 0;
|
|
|
|
AngleVectors(dir, forward, NULL, NULL);
|
|
|
|
monster_fire_bullet(self, start, forward, 20, 4, DEFAULT_BULLET_HSPREAD,
|
|
DEFAULT_BULLET_VSPREAD, flash_number);
|
|
}
|
|
|
|
mframe_t tank_frames_attack_blast[] = {
|
|
{ai_charge, 0, NULL},
|
|
{ai_charge, 0, NULL},
|
|
{ai_charge, 0, NULL},
|
|
{ai_charge, 0, NULL},
|
|
{ai_charge, -1, NULL},
|
|
{ai_charge, -2, NULL},
|
|
{ai_charge, -1, NULL},
|
|
{ai_charge, -1, NULL},
|
|
{ai_charge, 0, NULL},
|
|
{ai_charge, 0, TankBlaster}, /* 10 */
|
|
{ai_charge, 0, NULL},
|
|
{ai_charge, 0, NULL},
|
|
{ai_charge, 0, TankBlaster},
|
|
{ai_charge, 0, NULL},
|
|
{ai_charge, 0, NULL},
|
|
{ai_charge, 0, TankBlaster} /* 16 */
|
|
};
|
|
|
|
mmove_t tank_move_attack_blast = {
|
|
FRAME_attak101,
|
|
FRAME_attak116,
|
|
tank_frames_attack_blast,
|
|
tank_reattack_blaster
|
|
};
|
|
|
|
mframe_t tank_frames_reattack_blast[] = {
|
|
{ai_charge, 0, NULL},
|
|
{ai_charge, 0, NULL},
|
|
{ai_charge, 0, TankBlaster},
|
|
{ai_charge, 0, NULL},
|
|
{ai_charge, 0, NULL},
|
|
{ai_charge, 0, TankBlaster} /* 16 */
|
|
};
|
|
|
|
mmove_t tank_move_reattack_blast = {
|
|
FRAME_attak111,
|
|
FRAME_attak116,
|
|
tank_frames_reattack_blast,
|
|
tank_reattack_blaster
|
|
};
|
|
|
|
mframe_t tank_frames_attack_post_blast[] = {
|
|
{ai_move, 0, NULL}, /* 17 */
|
|
{ai_move, 0, NULL},
|
|
{ai_move, 2, NULL},
|
|
{ai_move, 3, NULL},
|
|
{ai_move, 2, NULL},
|
|
{ai_move, -2, tank_footstep} /* 22 */
|
|
};
|
|
|
|
mmove_t tank_move_attack_post_blast = {
|
|
FRAME_attak117,
|
|
FRAME_attak122,
|
|
tank_frames_attack_post_blast,
|
|
tank_run
|
|
};
|
|
|
|
void
|
|
tank_reattack_blaster(edict_t *self)
|
|
{
|
|
if (!self)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (skill->value >= 2)
|
|
{
|
|
if (visible(self, self->enemy))
|
|
{
|
|
if (self->enemy->health > 0)
|
|
{
|
|
if (random() <= 0.6)
|
|
{
|
|
self->monsterinfo.currentmove = &tank_move_reattack_blast;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
self->monsterinfo.currentmove = &tank_move_attack_post_blast;
|
|
}
|
|
|
|
void
|
|
tank_poststrike(edict_t *self)
|
|
{
|
|
self->enemy = NULL;
|
|
tank_run(self);
|
|
}
|
|
|
|
mframe_t tank_frames_attack_strike[] = {
|
|
{ai_move, 3, NULL},
|
|
{ai_move, 2, NULL},
|
|
{ai_move, 2, NULL},
|
|
{ai_move, 1, NULL},
|
|
{ai_move, 6, NULL},
|
|
{ai_move, 7, NULL},
|
|
{ai_move, 9, tank_footstep},
|
|
{ai_move, 2, NULL},
|
|
{ai_move, 1, NULL},
|
|
{ai_move, 2, NULL},
|
|
{ai_move, 2, tank_footstep},
|
|
{ai_move, 2, NULL},
|
|
{ai_move, 0, NULL},
|
|
{ai_move, 0, NULL},
|
|
{ai_move, 0, NULL},
|
|
{ai_move, 0, NULL},
|
|
{ai_move, -2, NULL},
|
|
{ai_move, -2, NULL},
|
|
{ai_move, 0, tank_windup},
|
|
{ai_move, 0, NULL},
|
|
{ai_move, 0, NULL},
|
|
{ai_move, 0, NULL},
|
|
{ai_move, 0, NULL},
|
|
{ai_move, 0, NULL},
|
|
{ai_move, 0, NULL},
|
|
{ai_move, 0, TankStrike},
|
|
{ai_move, 0, NULL},
|
|
{ai_move, -1, NULL},
|
|
{ai_move, -1, NULL},
|
|
{ai_move, -1, NULL},
|
|
{ai_move, -1, NULL},
|
|
{ai_move, -1, NULL},
|
|
{ai_move, -3, NULL},
|
|
{ai_move, -10, NULL},
|
|
{ai_move, -10, NULL},
|
|
{ai_move, -2, NULL},
|
|
{ai_move, -3, NULL},
|
|
{ai_move, -2, tank_footstep}
|
|
};
|
|
|
|
mmove_t tank_move_attack_strike = {
|
|
FRAME_attak201,
|
|
FRAME_attak238,
|
|
tank_frames_attack_strike,
|
|
tank_poststrike
|
|
};
|
|
|
|
mframe_t tank_frames_attack_pre_rocket[] = {
|
|
{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, NULL},
|
|
{ai_charge, 0, NULL}, /* 10 */
|
|
|
|
{ai_charge, 0, NULL},
|
|
{ai_charge, 1, NULL},
|
|
{ai_charge, 2, NULL},
|
|
{ai_charge, 7, NULL},
|
|
{ai_charge, 7, NULL},
|
|
{ai_charge, 7, tank_footstep},
|
|
{ai_charge, 0, NULL},
|
|
{ai_charge, 0, NULL},
|
|
{ai_charge, 0, NULL},
|
|
{ai_charge, 0, NULL}, /* 20 */
|
|
|
|
{ai_charge, -3, NULL}
|
|
};
|
|
|
|
mmove_t tank_move_attack_pre_rocket = {
|
|
FRAME_attak301,
|
|
FRAME_attak321,
|
|
tank_frames_attack_pre_rocket,
|
|
tank_doattack_rocket
|
|
};
|
|
|
|
mframe_t tank_frames_attack_fire_rocket[] = {
|
|
{ai_charge, -3, NULL}, /* Loop Start 22 */
|
|
{ai_charge, 0, NULL},
|
|
{ai_charge, 0, TankRocket}, /* 24 */
|
|
{ai_charge, 0, NULL},
|
|
{ai_charge, 0, NULL},
|
|
{ai_charge, 0, TankRocket},
|
|
{ai_charge, 0, NULL},
|
|
{ai_charge, 0, NULL},
|
|
{ai_charge, -1, TankRocket} /* 30 Loop End */
|
|
};
|
|
|
|
mmove_t tank_move_attack_fire_rocket = {
|
|
FRAME_attak322,
|
|
FRAME_attak330,
|
|
tank_frames_attack_fire_rocket,
|
|
tank_refire_rocket
|
|
};
|
|
|
|
mframe_t tank_frames_attack_post_rocket[] = {
|
|
{ai_charge, 0, NULL}, /* 31 */
|
|
{ai_charge, -1, NULL},
|
|
{ai_charge, -1, NULL},
|
|
{ai_charge, 0, NULL},
|
|
{ai_charge, 2, NULL},
|
|
{ai_charge, 3, NULL},
|
|
{ai_charge, 4, NULL},
|
|
{ai_charge, 2, NULL},
|
|
{ai_charge, 0, NULL},
|
|
{ai_charge, 0, NULL}, /* 40 */
|
|
|
|
{ai_charge, 0, NULL},
|
|
{ai_charge, -9, NULL},
|
|
{ai_charge, -8, NULL},
|
|
{ai_charge, -7, NULL},
|
|
{ai_charge, -1, NULL},
|
|
{ai_charge, -1, tank_footstep},
|
|
{ai_charge, 0, NULL},
|
|
{ai_charge, 0, NULL},
|
|
{ai_charge, 0, NULL},
|
|
{ai_charge, 0, NULL}, /* 50 */
|
|
|
|
{ai_charge, 0, NULL},
|
|
{ai_charge, 0, NULL},
|
|
{ai_charge, 0, NULL}
|
|
};
|
|
|
|
mmove_t tank_move_attack_post_rocket = {
|
|
FRAME_attak331,
|
|
FRAME_attak353,
|
|
tank_frames_attack_post_rocket,
|
|
tank_run
|
|
};
|
|
|
|
mframe_t tank_frames_attack_chain[] = {
|
|
{ai_charge, 0, NULL},
|
|
{ai_charge, 0, NULL},
|
|
{ai_charge, 0, NULL},
|
|
{ai_charge, 0, NULL},
|
|
{ai_charge, 0, NULL},
|
|
{NULL, 0, TankMachineGun},
|
|
{NULL, 0, TankMachineGun},
|
|
{NULL, 0, TankMachineGun},
|
|
{NULL, 0, TankMachineGun},
|
|
{NULL, 0, TankMachineGun},
|
|
{NULL, 0, TankMachineGun},
|
|
{NULL, 0, TankMachineGun},
|
|
{NULL, 0, TankMachineGun},
|
|
{NULL, 0, TankMachineGun},
|
|
{NULL, 0, TankMachineGun},
|
|
{NULL, 0, TankMachineGun},
|
|
{NULL, 0, TankMachineGun},
|
|
{NULL, 0, TankMachineGun},
|
|
{NULL, 0, TankMachineGun},
|
|
{NULL, 0, TankMachineGun},
|
|
{NULL, 0, TankMachineGun},
|
|
{NULL, 0, TankMachineGun},
|
|
{NULL, 0, TankMachineGun},
|
|
{NULL, 0, TankMachineGun},
|
|
{ai_charge, 0, NULL},
|
|
{ai_charge, 0, NULL},
|
|
{ai_charge, 0, NULL},
|
|
{ai_charge, 0, NULL},
|
|
{ai_charge, 0, NULL}
|
|
};
|
|
|
|
mmove_t tank_move_attack_chain = {
|
|
FRAME_attak401,
|
|
FRAME_attak429,
|
|
tank_frames_attack_chain,
|
|
tank_run
|
|
};
|
|
|
|
void
|
|
tank_refire_rocket(edict_t *self)
|
|
{
|
|
if (!self)
|
|
{
|
|
return;
|
|
}
|
|
|
|
/* Only on hard or nightmare */
|
|
if (skill->value >= 2)
|
|
{
|
|
if (self->enemy->health > 0)
|
|
{
|
|
if (visible(self, self->enemy))
|
|
{
|
|
if (random() <= 0.4)
|
|
{
|
|
self->monsterinfo.currentmove = &tank_move_attack_fire_rocket;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
self->monsterinfo.currentmove = &tank_move_attack_post_rocket;
|
|
}
|
|
|
|
void
|
|
tank_doattack_rocket(edict_t *self)
|
|
{
|
|
if (!self)
|
|
{
|
|
return;
|
|
}
|
|
|
|
self->monsterinfo.currentmove = &tank_move_attack_fire_rocket;
|
|
}
|
|
|
|
void
|
|
tank_attack(edict_t *self)
|
|
{
|
|
vec3_t vec;
|
|
float range;
|
|
float r;
|
|
|
|
if (!self)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (self->enemy->health < 0)
|
|
{
|
|
self->monsterinfo.currentmove = &tank_move_attack_strike;
|
|
self->monsterinfo.aiflags &= ~AI_BRUTAL;
|
|
return;
|
|
}
|
|
|
|
VectorSubtract(self->enemy->s.origin, self->s.origin, vec);
|
|
range = VectorLength(vec);
|
|
|
|
r = random();
|
|
|
|
if (range <= 125)
|
|
{
|
|
if (r < 0.4)
|
|
{
|
|
self->monsterinfo.currentmove = &tank_move_attack_chain;
|
|
}
|
|
else
|
|
{
|
|
self->monsterinfo.currentmove = &tank_move_attack_blast;
|
|
}
|
|
}
|
|
else if (range <= 250)
|
|
{
|
|
if (r < 0.5)
|
|
{
|
|
self->monsterinfo.currentmove = &tank_move_attack_chain;
|
|
}
|
|
else
|
|
{
|
|
self->monsterinfo.currentmove = &tank_move_attack_blast;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (r < 0.33)
|
|
{
|
|
self->monsterinfo.currentmove = &tank_move_attack_chain;
|
|
}
|
|
else if (r < 0.66)
|
|
{
|
|
self->monsterinfo.currentmove = &tank_move_attack_pre_rocket;
|
|
self->pain_debounce_time = level.time + 5.0; /* no pain for a while */
|
|
}
|
|
else
|
|
{
|
|
self->monsterinfo.currentmove = &tank_move_attack_blast;
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
tank_dead(edict_t *self)
|
|
{
|
|
if (!self)
|
|
{
|
|
return;
|
|
}
|
|
|
|
VectorSet(self->mins, -16, -16, -16);
|
|
VectorSet(self->maxs, 16, 16, -0);
|
|
self->movetype = MOVETYPE_TOSS;
|
|
self->svflags |= SVF_DEADMONSTER;
|
|
self->nextthink = 0;
|
|
gi.linkentity(self);
|
|
}
|
|
|
|
mframe_t tank_frames_death1[] = {
|
|
{ai_move, -7, NULL},
|
|
{ai_move, -2, NULL},
|
|
{ai_move, -2, NULL},
|
|
{ai_move, 1, NULL},
|
|
{ai_move, 3, NULL},
|
|
{ai_move, 6, NULL},
|
|
{ai_move, 1, NULL},
|
|
{ai_move, 1, NULL},
|
|
{ai_move, 2, NULL},
|
|
{ai_move, 0, NULL},
|
|
{ai_move, 0, NULL},
|
|
{ai_move, 0, NULL},
|
|
{ai_move, -2, NULL},
|
|
{ai_move, 0, NULL},
|
|
{ai_move, 0, NULL},
|
|
{ai_move, -3, NULL},
|
|
{ai_move, 0, NULL},
|
|
{ai_move, 0, NULL},
|
|
{ai_move, 0, NULL},
|
|
{ai_move, 0, NULL},
|
|
{ai_move, 0, NULL},
|
|
{ai_move, 0, NULL},
|
|
{ai_move, -4, NULL},
|
|
{ai_move, -6, NULL},
|
|
{ai_move, -4, NULL},
|
|
{ai_move, -5, NULL},
|
|
{ai_move, -7, NULL},
|
|
{ai_move, -15, tank_thud},
|
|
{ai_move, -5, NULL},
|
|
{ai_move, 0, NULL},
|
|
{ai_move, 0, NULL},
|
|
{ai_move, 0, NULL}
|
|
};
|
|
|
|
mmove_t tank_move_death = {
|
|
FRAME_death101,
|
|
FRAME_death132,
|
|
tank_frames_death1,
|
|
tank_dead
|
|
};
|
|
|
|
void
|
|
tank_die(edict_t *self, edict_t *inflictor /* unused */,
|
|
edict_t *attacker /* unused */, int damage,
|
|
vec3_t point /* unused */)
|
|
{
|
|
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 < 1 /*4*/; n++)
|
|
{
|
|
ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
|
|
}
|
|
|
|
for (n = 0; n < 4; n++)
|
|
{
|
|
ThrowGib(self, "models/objects/gibs/sm_metal/tris.md2", damage, GIB_METALLIC);
|
|
}
|
|
|
|
ThrowGib(self, "models/objects/gibs/chest/tris.md2", damage, GIB_ORGANIC);
|
|
ThrowHead(self, "models/objects/gibs/gear/tris.md2", damage, GIB_METALLIC);
|
|
self->deadflag = DEAD_DEAD;
|
|
return;
|
|
}
|
|
|
|
if (self->deadflag == DEAD_DEAD)
|
|
{
|
|
return;
|
|
}
|
|
|
|
/* regular death */
|
|
gi.sound(self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0);
|
|
self->deadflag = DEAD_DEAD;
|
|
self->takedamage = DAMAGE_YES;
|
|
|
|
self->monsterinfo.currentmove = &tank_move_death;
|
|
}
|
|
|
|
/*
|
|
* QUAKED monster_tank (1 .5 0) (-32 -32 -16) (32 32 72) Ambush Trigger_Spawn Sight
|
|
* QUAKED monster_tank_commander (1 .5 0) (-32 -32 -16) (32 32 72) Ambush Trigger_Spawn Sight
|
|
*/
|
|
void
|
|
SP_monster_tank(edict_t *self)
|
|
{
|
|
if (!self)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (deathmatch->value)
|
|
{
|
|
G_FreeEdict(self);
|
|
return;
|
|
}
|
|
|
|
self->s.modelindex = gi.modelindex("models/monsters/tank/tris.md2");
|
|
VectorSet(self->mins, -32, -32, -16);
|
|
VectorSet(self->maxs, 32, 32, 72);
|
|
self->movetype = MOVETYPE_STEP;
|
|
self->solid = SOLID_BBOX;
|
|
|
|
sound_pain = gi.soundindex("tank/tnkpain2.wav");
|
|
sound_thud = gi.soundindex("tank/tnkdeth2.wav");
|
|
sound_idle = gi.soundindex("tank/tnkidle1.wav");
|
|
sound_die = gi.soundindex("tank/death.wav");
|
|
sound_step = gi.soundindex("tank/step.wav");
|
|
sound_windup = gi.soundindex("tank/tnkatck4.wav");
|
|
sound_strike = gi.soundindex("tank/tnkatck5.wav");
|
|
sound_sight = gi.soundindex("tank/sight1.wav");
|
|
|
|
gi.soundindex("tank/tnkatck1.wav");
|
|
gi.soundindex("tank/tnkatk2a.wav");
|
|
gi.soundindex("tank/tnkatk2b.wav");
|
|
gi.soundindex("tank/tnkatk2c.wav");
|
|
gi.soundindex("tank/tnkatk2d.wav");
|
|
gi.soundindex("tank/tnkatk2e.wav");
|
|
gi.soundindex("tank/tnkatck3.wav");
|
|
|
|
if (strcmp(self->classname, "monster_tank_commander") == 0)
|
|
{
|
|
self->health = 1000;
|
|
self->gib_health = -225;
|
|
}
|
|
else
|
|
{
|
|
self->health = 750;
|
|
self->gib_health = -200;
|
|
}
|
|
|
|
self->mass = 500;
|
|
|
|
self->pain = tank_pain;
|
|
self->die = tank_die;
|
|
self->monsterinfo.stand = tank_stand;
|
|
self->monsterinfo.walk = tank_walk;
|
|
self->monsterinfo.run = tank_run;
|
|
self->monsterinfo.dodge = NULL;
|
|
self->monsterinfo.attack = tank_attack;
|
|
self->monsterinfo.melee = NULL;
|
|
self->monsterinfo.sight = tank_sight;
|
|
self->monsterinfo.idle = tank_idle;
|
|
|
|
gi.linkentity(self);
|
|
|
|
self->monsterinfo.currentmove = &tank_move_stand;
|
|
self->monsterinfo.scale = MODEL_SCALE;
|
|
|
|
walkmonster_start(self);
|
|
|
|
if (strcmp(self->classname, "monster_tank_commander") == 0)
|
|
{
|
|
self->s.skinnum = 2;
|
|
}
|
|
}
|