mirror of
https://github.com/blendogames/thirtyflightsofloving.git
synced 2024-11-15 00:41:21 +00:00
0d4e872ce9
Added plasma guards (monster_soldier_plasma_re and monster_soldier_plasma_sp) from LM Escape to missionpack DLL. Added Zaero items/weapons to missionpack DLL. Added support for Zaero doors to missionpack DLL. Fixed crash caused by killtargeting sentien (laser edict not freed) in missionpack DLL. Fixed bug with broken Rogue turrets in missionpack DLL. Fixed crash in g_combat.c->M_ReactToDamage() caused by attacker with NULL classname in missionpack DLL.
1444 lines
40 KiB
C
1444 lines
40 KiB
C
/*
|
|
==============================================================================
|
|
|
|
carrier
|
|
|
|
==============================================================================
|
|
*/
|
|
|
|
// self->timestamp used for frame calculations in grenade & spawn code
|
|
// self->wait used to prevent rapid refire of rocket launcher
|
|
|
|
#include "g_local.h"
|
|
#include "m_carrier.h"
|
|
|
|
#define CARRIER_ROCKET_TIME 2 // number of seconds between rocket shots
|
|
#define CARRIER_ROCKET_SPEED 750
|
|
#define NUM_FLYERS_SPAWNED 6 // max # of flyers he can spawn
|
|
|
|
#define RAIL_FIRE_TIME 3
|
|
|
|
void Grenade_Explode (edict_t *ent);
|
|
|
|
qboolean infront (edict_t *self, edict_t *other);
|
|
qboolean inback (edict_t *self, edict_t *other);
|
|
qboolean below (edict_t *self, edict_t *other);
|
|
void drawbbox (edict_t *self);
|
|
|
|
//char *ED_NewString (char *string);
|
|
void ED_CallSpawn (edict_t *ent);
|
|
|
|
static int sound_pain1;
|
|
static int sound_pain2;
|
|
static int sound_pain3;
|
|
static int sound_death;
|
|
//static int sound_search1;
|
|
static int sound_sight;
|
|
static int sound_rail;
|
|
static int sound_spawn;
|
|
|
|
float orig_yaw_speed;
|
|
|
|
vec3_t flyer_mins = {-16, -16, -24};
|
|
vec3_t flyer_maxs = {16, 16, 16};
|
|
|
|
extern mmove_t flyer_move_attack2, flyer_move_attack3, flyer_move_kamikaze;
|
|
|
|
|
|
void carrier_run (edict_t *self);
|
|
void carrier_stand (edict_t *self);
|
|
void carrier_dead (edict_t *self);
|
|
void carrier_attack (edict_t *self);
|
|
void carrier_attack_mg (edict_t *self);
|
|
void carrier_reattack_mg (edict_t *self);
|
|
void carrier_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point);
|
|
|
|
void carrier_attack_gren (edict_t *self);
|
|
void carrier_reattack_gren (edict_t *self);
|
|
|
|
void carrier_start_spawn (edict_t *self);
|
|
void carrier_spawn_check (edict_t *self);
|
|
void carrier_prep_spawn (edict_t *self);
|
|
|
|
void CarrierMachineGunHold (edict_t *self);
|
|
void CarrierRocket (edict_t *self);
|
|
|
|
|
|
void carrier_sight (edict_t *self, edict_t *other)
|
|
{
|
|
gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
|
|
}
|
|
|
|
// code starts here
|
|
//void carrier_search (edict_t *self)
|
|
//{
|
|
// if (random() < 0.5)
|
|
// gi.sound (self, CHAN_VOICE, sound_search1, 1, ATTN_NONE, 0);
|
|
//}
|
|
|
|
//
|
|
// this is the smarts for the rocket launcher in coop
|
|
//
|
|
// if there is a player behind/below the carrier, and we can shoot, and we can trace a LOS to them ..
|
|
// pick one of the group, and let it rip
|
|
void CarrierCoopCheck (edict_t *self)
|
|
{
|
|
// no more than 4 players in coop, so..
|
|
edict_t *targets[4];
|
|
int num_targets = 0, target, player;
|
|
edict_t *ent;
|
|
trace_t tr;
|
|
|
|
// if we're not in coop, this is a noop
|
|
if (!coop || !coop->value)
|
|
return;
|
|
// if we are, and we have recently fired, bail
|
|
if (self->wait > level.time)
|
|
return;
|
|
|
|
memset (targets, 0, 4*sizeof(edict_t *));
|
|
|
|
// cycle through players
|
|
for (player = 1; player <= game.maxclients; player++)
|
|
{
|
|
ent = &g_edicts[player];
|
|
if (!ent->inuse)
|
|
continue;
|
|
if (!ent->client)
|
|
continue;
|
|
if (inback(self, ent) || below(self, ent))
|
|
{
|
|
tr = gi.trace (self->s.origin, NULL, NULL, ent->s.origin, self, MASK_SOLID);
|
|
if (tr.fraction == 1.0)
|
|
{
|
|
// if ((g_showlogic) && (g_showlogic->value))
|
|
// gi.dprintf ("Carrier: found a player who I can shoot\n");
|
|
targets[num_targets++] = ent;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!num_targets)
|
|
return;
|
|
|
|
// get a number from 0 to (num_targets-1)
|
|
target = random() * num_targets;
|
|
|
|
// just in case we got a 1.0 from random
|
|
if (target == num_targets)
|
|
target--;
|
|
|
|
// make sure to prevent rapid fire rockets
|
|
self->wait = level.time + CARRIER_ROCKET_TIME;
|
|
|
|
// save off the real enemy
|
|
ent = self->enemy;
|
|
// set the new guy as temporary enemy
|
|
self->enemy = targets[target];
|
|
CarrierRocket (self);
|
|
// put the real enemy back
|
|
self->enemy = ent;
|
|
|
|
// we're done
|
|
return;
|
|
}
|
|
|
|
void CarrierGrenade (edict_t *self)
|
|
{
|
|
vec3_t start;
|
|
vec3_t forward, right, up;
|
|
vec3_t vec, aim;
|
|
int flash_number;
|
|
float direction; // from lower left to upper right, or lower right to upper left
|
|
float spreadR, spreadU;
|
|
int mytime;
|
|
|
|
CarrierCoopCheck(self);
|
|
|
|
if (!self->enemy)
|
|
return;
|
|
|
|
if (random() < 0.5)
|
|
direction = -1.0;
|
|
else
|
|
direction = 1.0;
|
|
|
|
mytime = (int)((level.time - self->timestamp)/0.4);
|
|
|
|
if (mytime == 0)
|
|
{
|
|
spreadR = 0.15 * direction;
|
|
// spreadU = 0.1 * direction;
|
|
spreadU = 0.1 - 0.1 * direction;
|
|
}
|
|
else if (mytime == 1)
|
|
{
|
|
spreadR = 0;
|
|
// spreadU = 0;
|
|
spreadU = 0.1;
|
|
}
|
|
else if (mytime == 2)
|
|
{
|
|
spreadR = -0.15 * direction;
|
|
// spreadU = -0.1 * direction;
|
|
spreadU = 0.1 - -0.1 * direction;
|
|
}
|
|
else if (mytime == 3)
|
|
{
|
|
spreadR = 0;
|
|
// spreadU = 0;
|
|
spreadU = 0.1;
|
|
}
|
|
else
|
|
{
|
|
// error, shoot straight
|
|
// if ((g_showlogic) && (g_showlogic->value))
|
|
// gi.dprintf ("CarrierGrenade: bad time %2.2f %2.2f\n", level.time, self->timestamp);
|
|
spreadR = 0;
|
|
spreadU = 0;
|
|
}
|
|
|
|
AngleVectors (self->s.angles, forward, right, up);
|
|
G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_CARRIER_GRENADE], forward, right, start);
|
|
VectorCopy (self->enemy->s.origin, vec);
|
|
|
|
// Lazarus fog reduction of accuracy
|
|
if (self->monsterinfo.visibility < FOG_CANSEEGOOD)
|
|
{
|
|
vec[0] += crandom() * 640 * (FOG_CANSEEGOOD - self->monsterinfo.visibility);
|
|
vec[1] += crandom() * 640 * (FOG_CANSEEGOOD - self->monsterinfo.visibility);
|
|
vec[2] += crandom() * 320 * (FOG_CANSEEGOOD - self->monsterinfo.visibility);
|
|
}
|
|
|
|
VectorSubtract (vec, start, aim);
|
|
VectorNormalize (aim);
|
|
|
|
VectorMA (aim, spreadR, right, aim);
|
|
VectorMA (aim, spreadU, up, aim);
|
|
|
|
if (aim[2] > 0.15)
|
|
aim[2] = 0.15;
|
|
else if (aim[2] < -0.5)
|
|
aim[2] = -0.5;
|
|
|
|
flash_number = MZ2_GUNNER_GRENADE_1;
|
|
monster_fire_grenade (self, start, aim, 50, 600, flash_number);
|
|
}
|
|
|
|
void CarrierPredictiveRocket (edict_t *self)
|
|
{
|
|
vec3_t forward, right;
|
|
vec3_t start;
|
|
vec3_t dir;
|
|
|
|
// if ((g_showlogic) && (g_showlogic->value))
|
|
// gi.dprintf("predictive fire\n");
|
|
|
|
AngleVectors (self->s.angles, forward, right, NULL);
|
|
|
|
//1
|
|
G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_CARRIER_ROCKET_1], forward, right, start);
|
|
PredictAim (self->enemy, start, CARRIER_ROCKET_SPEED, false, -0.3, dir, NULL);
|
|
// monster_fire_rocket (self, start, dir, 50, CARRIER_ROCKET_SPEED, MZ2_CARRIER_ROCKET_1);
|
|
monster_fire_rocket (self, start, dir, 50, CARRIER_ROCKET_SPEED, MZ2_CARRIER_ROCKET_1,
|
|
(self->spawnflags & SF_MONSTER_SPECIAL ? self->enemy : NULL) );
|
|
|
|
//2
|
|
G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_CARRIER_ROCKET_2], forward, right, start);
|
|
PredictAim (self->enemy, start, CARRIER_ROCKET_SPEED, false, -0.15, dir, NULL);
|
|
// monster_fire_rocket (self, start, dir, 50, CARRIER_ROCKET_SPEED, MZ2_CARRIER_ROCKET_2);
|
|
monster_fire_rocket (self, start, dir, 50, CARRIER_ROCKET_SPEED, MZ2_CARRIER_ROCKET_2,
|
|
(self->spawnflags & SF_MONSTER_SPECIAL ? self->enemy : NULL) );
|
|
|
|
//3
|
|
G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_CARRIER_ROCKET_3], forward, right, start);
|
|
PredictAim (self->enemy, start, CARRIER_ROCKET_SPEED, false, 0, dir, NULL);
|
|
// monster_fire_rocket (self, start, dir, 50, CARRIER_ROCKET_SPEED, MZ2_CARRIER_ROCKET_3);
|
|
monster_fire_rocket (self, start, dir, 50, CARRIER_ROCKET_SPEED, MZ2_CARRIER_ROCKET_3,
|
|
(self->spawnflags & SF_MONSTER_SPECIAL ? self->enemy : NULL) );
|
|
|
|
//4
|
|
G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_CARRIER_ROCKET_4], forward, right, start);
|
|
PredictAim (self->enemy, start, CARRIER_ROCKET_SPEED, false, 0.15, dir, NULL);
|
|
// monster_fire_rocket (self, start, dir, 50, CARRIER_ROCKET_SPEED, MZ2_CARRIER_ROCKET_4);
|
|
monster_fire_rocket (self, start, dir, 50, CARRIER_ROCKET_SPEED, MZ2_CARRIER_ROCKET_4,
|
|
(self->spawnflags & SF_MONSTER_SPECIAL ? self->enemy : NULL) );
|
|
}
|
|
|
|
void CarrierRocket (edict_t *self)
|
|
{
|
|
vec3_t forward, right;
|
|
vec3_t start;
|
|
vec3_t dir;
|
|
vec3_t vec;
|
|
|
|
if (self->enemy)
|
|
{
|
|
if (self->enemy->client && random() < 0.5)
|
|
{
|
|
CarrierPredictiveRocket(self);
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
return;
|
|
|
|
AngleVectors (self->s.angles, forward, right, NULL);
|
|
|
|
//1
|
|
G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_CARRIER_ROCKET_1], forward, right, start);
|
|
VectorCopy (self->enemy->s.origin, vec);
|
|
// vec[2] += self->enemy->viewheight;
|
|
vec[2] -= 15;
|
|
|
|
// Lazarus fog reduction of accuracy
|
|
if (self->monsterinfo.visibility < FOG_CANSEEGOOD)
|
|
{
|
|
vec[0] += crandom() * 640 * (FOG_CANSEEGOOD - self->monsterinfo.visibility);
|
|
vec[1] += crandom() * 640 * (FOG_CANSEEGOOD - self->monsterinfo.visibility);
|
|
vec[2] += crandom() * 320 * (FOG_CANSEEGOOD - self->monsterinfo.visibility);
|
|
}
|
|
|
|
VectorSubtract (vec, start, dir);
|
|
VectorNormalize (dir);
|
|
VectorMA (dir, 0.4, right, dir);
|
|
VectorNormalize (dir);
|
|
// monster_fire_rocket (self, start, dir, 50, 500, MZ2_CARRIER_ROCKET_1);
|
|
monster_fire_rocket (self, start, dir, 50, 500, MZ2_CARRIER_ROCKET_1,
|
|
(self->spawnflags & SF_MONSTER_SPECIAL ? self->enemy : NULL) );
|
|
|
|
//2
|
|
G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_CARRIER_ROCKET_2], forward, right, start);
|
|
VectorCopy (self->enemy->s.origin, vec);
|
|
// vec[2] += self->enemy->viewheight;
|
|
|
|
// Lazarus fog reduction of accuracy
|
|
if (self->monsterinfo.visibility < FOG_CANSEEGOOD)
|
|
{
|
|
vec[0] += crandom() * 640 * (FOG_CANSEEGOOD - self->monsterinfo.visibility);
|
|
vec[1] += crandom() * 640 * (FOG_CANSEEGOOD - self->monsterinfo.visibility);
|
|
vec[2] += crandom() * 320 * (FOG_CANSEEGOOD - self->monsterinfo.visibility);
|
|
}
|
|
|
|
VectorSubtract (vec, start, dir);
|
|
VectorNormalize (dir);
|
|
VectorMA (dir, 0.025, right, dir);
|
|
VectorNormalize (dir);
|
|
// monster_fire_rocket (self, start, dir, 50, 500, MZ2_CARRIER_ROCKET_2);
|
|
monster_fire_rocket (self, start, dir, 50, 500, MZ2_CARRIER_ROCKET_2,
|
|
(self->spawnflags & SF_MONSTER_SPECIAL ? self->enemy : NULL) );
|
|
|
|
//3
|
|
G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_CARRIER_ROCKET_3], forward, right, start);
|
|
VectorCopy (self->enemy->s.origin, vec);
|
|
// vec[2] += self->enemy->viewheight;
|
|
|
|
// Lazarus fog reduction of accuracy
|
|
if (self->monsterinfo.visibility < FOG_CANSEEGOOD)
|
|
{
|
|
vec[0] += crandom() * 640 * (FOG_CANSEEGOOD - self->monsterinfo.visibility);
|
|
vec[1] += crandom() * 640 * (FOG_CANSEEGOOD - self->monsterinfo.visibility);
|
|
vec[2] += crandom() * 320 * (FOG_CANSEEGOOD - self->monsterinfo.visibility);
|
|
}
|
|
|
|
VectorSubtract (vec, start, dir);
|
|
VectorNormalize (dir);
|
|
VectorMA (dir, -0.025, right, dir);
|
|
VectorNormalize (dir);
|
|
// monster_fire_rocket (self, start, dir, 50, 500, MZ2_CARRIER_ROCKET_3);
|
|
monster_fire_rocket (self, start, dir, 50, 500, MZ2_CARRIER_ROCKET_3,
|
|
(self->spawnflags & SF_MONSTER_SPECIAL ? self->enemy : NULL) );
|
|
|
|
//4
|
|
G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_CARRIER_ROCKET_4], forward, right, start);
|
|
VectorCopy (self->enemy->s.origin, vec);
|
|
// vec[2] += self->enemy->viewheight;
|
|
|
|
// Lazarus fog reduction of accuracy
|
|
if (self->monsterinfo.visibility < FOG_CANSEEGOOD)
|
|
{
|
|
vec[0] += crandom() * 640 * (FOG_CANSEEGOOD - self->monsterinfo.visibility);
|
|
vec[1] += crandom() * 640 * (FOG_CANSEEGOOD - self->monsterinfo.visibility);
|
|
vec[2] += crandom() * 320 * (FOG_CANSEEGOOD - self->monsterinfo.visibility);
|
|
}
|
|
|
|
vec[2] -= 15;
|
|
VectorSubtract (vec, start, dir);
|
|
VectorNormalize (dir);
|
|
VectorMA (dir, -0.4, right, dir);
|
|
VectorNormalize (dir);
|
|
// monster_fire_rocket (self, start, dir, 50, 500, MZ2_CARRIER_ROCKET_4);
|
|
monster_fire_rocket (self, start, dir, 50, 500, MZ2_CARRIER_ROCKET_4,
|
|
(self->spawnflags & SF_MONSTER_SPECIAL ? self->enemy : NULL) );
|
|
|
|
//5
|
|
// G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_BOSS2_ROCKET_4], 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, 500, MZ2_BOSS2_ROCKET_2);
|
|
}
|
|
|
|
void carrier_firebullet_right (edict_t *self)
|
|
{
|
|
vec3_t forward, right, target;
|
|
vec3_t start;
|
|
int flashnum;
|
|
|
|
// if we're in manual steering mode, it means we're leaning down .. use the lower shot
|
|
if (self->monsterinfo.aiflags & AI_MANUAL_STEERING)
|
|
flashnum = MZ2_CARRIER_MACHINEGUN_R2;
|
|
else
|
|
flashnum = MZ2_CARRIER_MACHINEGUN_R1;
|
|
|
|
AngleVectors (self->s.angles, forward, right, NULL);
|
|
G_ProjectSource (self->s.origin, monster_flash_offset[flashnum], forward, right, start);
|
|
|
|
// VectorMA (self->enemy->s.origin, -0.2, self->enemy->velocity, target);
|
|
VectorMA (self->enemy->s.origin, 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);
|
|
}
|
|
/*
|
|
gi.WriteByte (svc_temp_entity);
|
|
gi.WriteByte (TE_DEBUGTRAIL);
|
|
gi.WritePosition (start);
|
|
gi.WritePosition (target);
|
|
gi.multicast (start, MULTICAST_ALL);
|
|
*/
|
|
VectorSubtract (target, start, forward);
|
|
VectorNormalize (forward);
|
|
|
|
// Zaero add
|
|
if (EMPNukeCheck(self, start))
|
|
{
|
|
gi.sound (self, CHAN_AUTO, gi.soundindex("items/empnuke/emp_missfire.wav"), 1, ATTN_NORM, 0);
|
|
return;
|
|
}
|
|
// end Zaero
|
|
|
|
monster_fire_bullet (self, start, forward, 6, 4, DEFAULT_BULLET_HSPREAD*3, DEFAULT_BULLET_VSPREAD, flashnum);
|
|
}
|
|
|
|
void carrier_firebullet_left (edict_t *self)
|
|
{
|
|
vec3_t forward, right, target;
|
|
vec3_t start;
|
|
int flashnum;
|
|
|
|
// if we're in manual steering mode, it means we're leaning down .. use the lower shot
|
|
if (self->monsterinfo.aiflags & AI_MANUAL_STEERING)
|
|
flashnum = MZ2_CARRIER_MACHINEGUN_L2;
|
|
else
|
|
flashnum = MZ2_CARRIER_MACHINEGUN_L1;
|
|
|
|
AngleVectors (self->s.angles, forward, right, NULL);
|
|
G_ProjectSource (self->s.origin, monster_flash_offset[flashnum], forward, right, start);
|
|
|
|
// VectorMA (self->enemy->s.origin, 0.2, self->enemy->velocity, target);
|
|
VectorMA (self->enemy->s.origin, -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);
|
|
}
|
|
/*
|
|
gi.WriteByte (svc_temp_entity);
|
|
gi.WriteByte (TE_DEBUGTRAIL);
|
|
gi.WritePosition (start);
|
|
gi.WritePosition (target);
|
|
gi.multicast (start, MULTICAST_ALL);
|
|
*/
|
|
VectorSubtract (target, start, forward);
|
|
VectorNormalize (forward);
|
|
|
|
// Zaero add
|
|
if (EMPNukeCheck(self, start))
|
|
{
|
|
gi.sound (self, CHAN_AUTO, gi.soundindex("items/empnuke/emp_missfire.wav"), 1, ATTN_NORM, 0);
|
|
return;
|
|
}
|
|
// end Zaero
|
|
|
|
monster_fire_bullet (self, start, forward, 6, 4, DEFAULT_BULLET_HSPREAD*3, DEFAULT_BULLET_VSPREAD, flashnum);
|
|
}
|
|
|
|
void CarrierMachineGun (edict_t *self)
|
|
{
|
|
CarrierCoopCheck(self);
|
|
if (self->enemy)
|
|
carrier_firebullet_left(self);
|
|
if (self->enemy)
|
|
carrier_firebullet_right(self);
|
|
}
|
|
|
|
void CarrierSpawn (edict_t *self)
|
|
{
|
|
vec3_t f, r, offset, startpoint, spawnpoint;
|
|
edict_t *ent;
|
|
int mytime;
|
|
|
|
// VectorSet (offset, 105, 0, -30); // real distance needed is (sqrt (56*56*2) + sqrt(16*16*2)) or 101.8
|
|
VectorSet (offset, 105, 0, -58); // real distance needed is (sqrt (56*56*2) + sqrt(16*16*2)) or 101.8
|
|
AngleVectors (self->s.angles, f, r, NULL);
|
|
|
|
G_ProjectSource (self->s.origin, offset, f, r, startpoint);
|
|
|
|
// the +0.1 is because level.time is sometimes a little low
|
|
mytime = (int)((level.time + 0.1 - self->timestamp)/0.5);
|
|
// if ((g_showlogic) && (g_showlogic->value))
|
|
// gi.dprintf ("mytime = %d, (%2.2f)\n", mytime, level.time - self->timestamp);
|
|
|
|
if (FindSpawnPoint (startpoint, flyer_mins, flyer_maxs, spawnpoint, 32))
|
|
{
|
|
// the second flier should be a kamikaze flyer
|
|
if (mytime != 2)
|
|
ent = CreateMonster (spawnpoint, self->s.angles, "monster_flyer");
|
|
else
|
|
ent = CreateMonster (spawnpoint, self->s.angles, "monster_kamikaze");
|
|
|
|
if (!ent)
|
|
return;
|
|
|
|
gi.sound (self, CHAN_BODY, sound_spawn, 1, ATTN_NONE, 0);
|
|
|
|
self->monsterinfo.monster_slots--;
|
|
// if ((g_showlogic) && (g_showlogic->value))
|
|
// gi.dprintf ("carrier: post-spawn : %d slots left\n", self->monsterinfo.monster_slots);
|
|
|
|
ent->nextthink = level.time;
|
|
ent->think (ent);
|
|
|
|
//ent->monsterinfo.aiflags |= AI_SPAWNED_CARRIER|AI_DO_NOT_COUNT|AI_IGNORE_SHOTS;
|
|
ent->monsterinfo.aiflags |= AI_IGNORE_SHOTS;
|
|
ent->monsterinfo.monsterflags |= MFL_SPAWNED_CARRIER|MFL_DO_NOT_COUNT;
|
|
|
|
ent->monsterinfo.commander = self;
|
|
|
|
if ((self->enemy->inuse) && (self->enemy->health > 0))
|
|
{
|
|
ent->enemy = self->enemy;
|
|
FoundTarget (ent);
|
|
if (mytime == 1)
|
|
{
|
|
ent->monsterinfo.lefty = 0;
|
|
ent->monsterinfo.attack_state = AS_SLIDING;
|
|
ent->monsterinfo.currentmove = &flyer_move_attack3;
|
|
}
|
|
else if (mytime == 2)
|
|
{
|
|
ent->monsterinfo.lefty = 0;
|
|
ent->monsterinfo.attack_state = AS_STRAIGHT;
|
|
ent->monsterinfo.currentmove = &flyer_move_kamikaze;
|
|
ent->mass = 100;
|
|
ent->monsterinfo.aiflags |= AI_CHARGING;
|
|
}
|
|
else if (mytime == 3)
|
|
{
|
|
ent->monsterinfo.lefty = 1;
|
|
ent->monsterinfo.attack_state = AS_SLIDING;
|
|
ent->monsterinfo.currentmove = &flyer_move_attack3;
|
|
}
|
|
// else if ((g_showlogic) && (g_showlogic->value))
|
|
// gi.dprintf ("carrier: unexpected time %d!\n", mytime);
|
|
}
|
|
}
|
|
}
|
|
|
|
void carrier_prep_spawn (edict_t *self)
|
|
{
|
|
CarrierCoopCheck(self);
|
|
self->monsterinfo.aiflags |= AI_MANUAL_STEERING;
|
|
self->timestamp = level.time;
|
|
self->yaw_speed = 10;
|
|
CarrierMachineGun(self);
|
|
}
|
|
|
|
void carrier_spawn_check (edict_t *self)
|
|
{
|
|
// gi.dprintf ("times - %2.2f %2.2f\n", level.time, self->timestamp);
|
|
CarrierCoopCheck(self);
|
|
CarrierMachineGun(self);
|
|
CarrierSpawn (self);
|
|
|
|
if (level.time > (self->timestamp + 1.1)) // 0.5 seconds per flyer. this gets three
|
|
{
|
|
self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
|
|
self->yaw_speed = orig_yaw_speed;
|
|
return;
|
|
}
|
|
else
|
|
self->monsterinfo.nextframe = FRAME_spawn08;
|
|
}
|
|
|
|
void carrier_ready_spawn (edict_t *self)
|
|
{
|
|
float current_yaw;
|
|
vec3_t offset, f, r, startpoint, spawnpoint;
|
|
|
|
CarrierCoopCheck(self);
|
|
CarrierMachineGun(self);
|
|
|
|
current_yaw = anglemod(self->s.angles[YAW]);
|
|
|
|
// gi.dprintf ("yaws = %2.2f %2.2f\n", current_yaw, self->ideal_yaw);
|
|
|
|
if (fabs(current_yaw - self->ideal_yaw) > 0.1)
|
|
{
|
|
self->monsterinfo.aiflags |= AI_HOLD_FRAME;
|
|
self->timestamp += FRAMETIME;
|
|
return;
|
|
}
|
|
|
|
self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
|
|
|
|
VectorSet (offset, 105,0,-58);
|
|
AngleVectors (self->s.angles, f, r, NULL);
|
|
G_ProjectSource (self->s.origin, offset, f, r, startpoint);
|
|
if (FindSpawnPoint (startpoint, flyer_mins, flyer_maxs, spawnpoint, 32))
|
|
{
|
|
SpawnGrow_Spawn (spawnpoint, 0);
|
|
}
|
|
}
|
|
|
|
void carrier_start_spawn (edict_t *self)
|
|
{
|
|
int mytime;
|
|
float enemy_yaw;
|
|
vec3_t temp;
|
|
// vec3_t offset, f, r, startpoint;
|
|
|
|
CarrierCoopCheck(self);
|
|
if (!orig_yaw_speed)
|
|
orig_yaw_speed = self->yaw_speed;
|
|
|
|
if (!self->enemy)
|
|
return;
|
|
|
|
mytime = (int)((level.time - self->timestamp)/0.5);
|
|
|
|
VectorSubtract (self->enemy->s.origin, self->s.origin, temp);
|
|
enemy_yaw = vectoyaw2(temp);
|
|
|
|
// note that the offsets are based on a forward of 105 from the end angle
|
|
if (mytime == 0)
|
|
{
|
|
self->ideal_yaw = anglemod(enemy_yaw - 30);
|
|
// VectorSet (offset, 90.9, 52.5, 0);
|
|
}
|
|
else if (mytime == 1)
|
|
{
|
|
self->ideal_yaw = anglemod(enemy_yaw);
|
|
// VectorSet (offset, 90.9, -52.5, 0);
|
|
}
|
|
else if (mytime == 2)
|
|
{
|
|
self->ideal_yaw = anglemod(enemy_yaw + 30);
|
|
// VectorSet (offset, 90.9, -52.5, 0);
|
|
}
|
|
// else if ((g_showlogic) && (g_showlogic->value))
|
|
// gi.dprintf ("carrier: bad spawntime\n");
|
|
|
|
CarrierMachineGun (self);
|
|
}
|
|
|
|
mframe_t carrier_frames_stand [] =
|
|
{
|
|
// ai_stand, 0, drawbbox,
|
|
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 carrier_move_stand = {FRAME_search01, FRAME_search13, carrier_frames_stand, NULL};
|
|
|
|
mframe_t carrier_frames_walk [] =
|
|
{
|
|
// ai_walk, 12, drawbbox,
|
|
ai_walk, 4, NULL,
|
|
ai_walk, 4, NULL,
|
|
ai_walk, 4, NULL,
|
|
ai_walk, 4, NULL,
|
|
ai_walk, 4, NULL,
|
|
ai_walk, 4, NULL,
|
|
ai_walk, 4, NULL,
|
|
ai_walk, 4, NULL,
|
|
ai_walk, 4, NULL,
|
|
ai_walk, 4, NULL,
|
|
ai_walk, 4, NULL,
|
|
ai_walk, 4, NULL,
|
|
ai_walk, 4, NULL
|
|
};
|
|
mmove_t carrier_move_walk = {FRAME_search01, FRAME_search13, carrier_frames_walk, NULL};
|
|
|
|
|
|
mframe_t carrier_frames_run [] =
|
|
{
|
|
// ai_run, 12, drawbbox,
|
|
ai_run, 6, CarrierCoopCheck,
|
|
ai_run, 6, CarrierCoopCheck,
|
|
ai_run, 6, CarrierCoopCheck,
|
|
ai_run, 6, CarrierCoopCheck,
|
|
ai_run, 6, CarrierCoopCheck,
|
|
ai_run, 6, CarrierCoopCheck,
|
|
ai_run, 6, CarrierCoopCheck,
|
|
ai_run, 6, CarrierCoopCheck,
|
|
ai_run, 6, CarrierCoopCheck,
|
|
ai_run, 6, CarrierCoopCheck,
|
|
ai_run, 6, CarrierCoopCheck,
|
|
ai_run, 6, CarrierCoopCheck,
|
|
ai_run, 6, CarrierCoopCheck
|
|
};
|
|
mmove_t carrier_move_run = {FRAME_search01, FRAME_search13, carrier_frames_run, NULL};
|
|
|
|
mframe_t carrier_frames_attack_pre_mg [] =
|
|
{
|
|
ai_charge, 4, CarrierCoopCheck,
|
|
ai_charge, 4, CarrierCoopCheck,
|
|
ai_charge, 4, CarrierCoopCheck,
|
|
ai_charge, 4, CarrierCoopCheck,
|
|
ai_charge, 4, CarrierCoopCheck,
|
|
ai_charge, 4, CarrierCoopCheck,
|
|
ai_charge, 4, CarrierCoopCheck,
|
|
ai_charge, 4, carrier_attack_mg
|
|
};
|
|
mmove_t carrier_move_attack_pre_mg = {FRAME_firea01, FRAME_firea08, carrier_frames_attack_pre_mg, NULL};
|
|
|
|
|
|
// Loop this
|
|
mframe_t carrier_frames_attack_mg [] =
|
|
{
|
|
ai_charge, -2, CarrierMachineGun,
|
|
ai_charge, -2, CarrierMachineGun,
|
|
ai_charge, -2, carrier_reattack_mg
|
|
/*
|
|
ai_charge, 0, CarrierMachineGunHold,
|
|
// ai_charge, 0, CarrierMachineGun,
|
|
ai_charge, 0, CarrierMachineGun,
|
|
ai_charge, 0, carrier_reattack_mg
|
|
*/
|
|
};
|
|
mmove_t carrier_move_attack_mg = {FRAME_firea09, FRAME_firea11, carrier_frames_attack_mg, NULL};
|
|
|
|
mframe_t carrier_frames_attack_post_mg [] =
|
|
{
|
|
ai_charge, 4, CarrierCoopCheck,
|
|
ai_charge, 4, CarrierCoopCheck,
|
|
ai_charge, 4, CarrierCoopCheck,
|
|
ai_charge, 4, CarrierCoopCheck
|
|
};
|
|
mmove_t carrier_move_attack_post_mg = {FRAME_firea12, FRAME_firea15, carrier_frames_attack_post_mg, carrier_run};
|
|
|
|
mframe_t carrier_frames_attack_pre_gren [] =
|
|
{
|
|
ai_charge, 4, CarrierCoopCheck,
|
|
ai_charge, 4, CarrierCoopCheck,
|
|
ai_charge, 4, CarrierCoopCheck,
|
|
ai_charge, 4, CarrierCoopCheck,
|
|
ai_charge, 4, CarrierCoopCheck,
|
|
ai_charge, 4, carrier_attack_gren
|
|
};
|
|
mmove_t carrier_move_attack_pre_gren = {FRAME_fireb01, FRAME_fireb06, carrier_frames_attack_pre_gren, NULL};
|
|
|
|
mframe_t carrier_frames_attack_gren [] =
|
|
{
|
|
ai_charge, -15, CarrierGrenade,
|
|
ai_charge, 4, CarrierCoopCheck,
|
|
ai_charge, 4, CarrierCoopCheck,
|
|
ai_charge, 4, carrier_reattack_gren
|
|
};
|
|
mmove_t carrier_move_attack_gren = {FRAME_fireb07, FRAME_fireb10, carrier_frames_attack_gren, NULL};
|
|
|
|
mframe_t carrier_frames_attack_post_gren [] =
|
|
{
|
|
ai_charge, 4, CarrierCoopCheck,
|
|
ai_charge, 4, CarrierCoopCheck,
|
|
ai_charge, 4, CarrierCoopCheck,
|
|
ai_charge, 4, CarrierCoopCheck,
|
|
ai_charge, 4, CarrierCoopCheck,
|
|
ai_charge, 4, CarrierCoopCheck
|
|
};
|
|
mmove_t carrier_move_attack_post_gren = {FRAME_fireb11, FRAME_fireb16, carrier_frames_attack_post_gren, carrier_run};
|
|
|
|
mframe_t carrier_frames_attack_rocket [] =
|
|
{
|
|
ai_charge, 15, CarrierRocket
|
|
};
|
|
mmove_t carrier_move_attack_rocket = {FRAME_fireb01, FRAME_fireb01, carrier_frames_attack_rocket, carrier_run};
|
|
|
|
void CarrierRail (edict_t *self)
|
|
{
|
|
vec3_t start;
|
|
vec3_t dir;
|
|
vec3_t forward, right;
|
|
|
|
CarrierCoopCheck(self);
|
|
AngleVectors (self->s.angles, forward, right, NULL);
|
|
G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_CARRIER_RAILGUN], forward, right, start);
|
|
|
|
// Lazarus fog reduction of accuracy
|
|
if (self->monsterinfo.visibility < FOG_CANSEEGOOD)
|
|
{
|
|
self->pos1[0] += crandom() * 640 * (FOG_CANSEEGOOD - self->monsterinfo.visibility);
|
|
self->pos1[1] += crandom() * 640 * (FOG_CANSEEGOOD - self->monsterinfo.visibility);
|
|
self->pos1[2] += crandom() * 320 * (FOG_CANSEEGOOD - self->monsterinfo.visibility);
|
|
}
|
|
|
|
// calc direction to where we targeted
|
|
VectorSubtract (self->pos1, start, dir);
|
|
VectorNormalize (dir);
|
|
|
|
monster_fire_railgun (self, start, dir, 50, 100, MZ2_CARRIER_RAILGUN);
|
|
self->monsterinfo.attack_finished = level.time + RAIL_FIRE_TIME;
|
|
}
|
|
|
|
void CarrierSaveLoc (edict_t *self)
|
|
{
|
|
CarrierCoopCheck(self);
|
|
VectorCopy (self->enemy->s.origin, self->pos1); //save for aiming the shot
|
|
self->pos1[2] += self->enemy->viewheight;
|
|
};
|
|
|
|
mframe_t carrier_frames_attack_rail [] =
|
|
{
|
|
ai_charge, 2, CarrierCoopCheck,
|
|
ai_charge, 2, CarrierSaveLoc,
|
|
ai_charge, 2, CarrierCoopCheck,
|
|
ai_charge, -20, CarrierRail,
|
|
ai_charge, 2, CarrierCoopCheck,
|
|
ai_charge, 2, CarrierCoopCheck,
|
|
ai_charge, 2, CarrierCoopCheck,
|
|
ai_charge, 2, CarrierCoopCheck,
|
|
ai_charge, 2, CarrierCoopCheck
|
|
};
|
|
mmove_t carrier_move_attack_rail = {FRAME_search01, FRAME_search09, carrier_frames_attack_rail, carrier_run};
|
|
|
|
mframe_t carrier_frames_spawn [] =
|
|
{
|
|
ai_charge, -2, CarrierMachineGun,
|
|
ai_charge, -2, CarrierMachineGun,
|
|
ai_charge, -2, CarrierMachineGun,
|
|
ai_charge, -2, CarrierMachineGun,
|
|
ai_charge, -2, CarrierMachineGun,
|
|
ai_charge, -2, CarrierMachineGun,
|
|
ai_charge, -2, carrier_prep_spawn, // 7 - end of wind down
|
|
ai_charge, -2, carrier_start_spawn, // 8 - start of spawn
|
|
ai_charge, -2, carrier_ready_spawn,
|
|
ai_charge, -2, CarrierMachineGun,
|
|
ai_charge, -2, CarrierMachineGun,
|
|
ai_charge, -10, carrier_spawn_check, //12 - actual spawn
|
|
ai_charge, -2, CarrierMachineGun, //13 - begin of wind down
|
|
ai_charge, -2, CarrierMachineGun,
|
|
ai_charge, -2, CarrierMachineGun,
|
|
ai_charge, -2, CarrierMachineGun,
|
|
ai_charge, -2, CarrierMachineGun,
|
|
ai_charge, -2, carrier_reattack_mg //18 - end of wind down
|
|
};
|
|
mmove_t carrier_move_spawn = {FRAME_spawn01, FRAME_spawn18, carrier_frames_spawn, NULL};
|
|
|
|
mframe_t carrier_frames_pain_heavy [] =
|
|
{
|
|
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, NULL,
|
|
ai_move, 0, NULL,
|
|
ai_move, 0, NULL,
|
|
ai_move, 0, NULL
|
|
};
|
|
mmove_t carrier_move_pain_heavy = {FRAME_death01, FRAME_death10, carrier_frames_pain_heavy, carrier_run};
|
|
|
|
mframe_t carrier_frames_pain_light [] =
|
|
{
|
|
ai_move, 0, NULL,
|
|
ai_move, 0, NULL,
|
|
ai_move, 0, NULL,
|
|
ai_move, 0, NULL
|
|
};
|
|
mmove_t carrier_move_pain_light = {FRAME_spawn01, FRAME_spawn04, carrier_frames_pain_light, carrier_run};
|
|
|
|
mframe_t carrier_frames_death [] =
|
|
{
|
|
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, 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, 0, NULL,
|
|
ai_move, 0, NULL,
|
|
ai_move, 0, BossExplode
|
|
};
|
|
mmove_t carrier_move_death = {FRAME_death01, FRAME_death16, carrier_frames_death, carrier_dead};
|
|
|
|
void carrier_stand (edict_t *self)
|
|
{
|
|
// gi.dprintf ("carrier stand\n");
|
|
self->monsterinfo.currentmove = &carrier_move_stand;
|
|
}
|
|
|
|
void carrier_run (edict_t *self)
|
|
{
|
|
|
|
// gi.dprintf ("carrier run - %2.2f - %s \n", level.time, self->enemy->classname);
|
|
self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
|
|
|
|
if (self->monsterinfo.aiflags & AI_STAND_GROUND)
|
|
self->monsterinfo.currentmove = &carrier_move_stand;
|
|
else
|
|
self->monsterinfo.currentmove = &carrier_move_run;
|
|
}
|
|
|
|
void carrier_walk (edict_t *self)
|
|
{
|
|
self->monsterinfo.currentmove = &carrier_move_walk;
|
|
}
|
|
|
|
void CarrierMachineGunHold (edict_t *self)
|
|
{
|
|
// self->monsterinfo.aiflags |= AI_HOLD_FRAME;
|
|
// self->yaw_speed = 0;
|
|
// self->monsterinfo.currentmove = &carrier_move_attack_mg;
|
|
CarrierMachineGun (self);
|
|
}
|
|
|
|
void carrier_attack (edict_t *self)
|
|
{
|
|
vec3_t vec;
|
|
float range, luck;
|
|
qboolean enemy_inback, enemy_infront, enemy_below;
|
|
|
|
// gi.dprintf ("carrier attack\n");
|
|
|
|
self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
|
|
|
|
if ((!self->enemy) || (!self->enemy->inuse))
|
|
return;
|
|
|
|
enemy_inback = inback(self, self->enemy);
|
|
enemy_infront = infront (self, self->enemy);
|
|
enemy_below = below (self, self->enemy);
|
|
|
|
if (self->bad_area)
|
|
{
|
|
if ((enemy_inback) || (enemy_below))
|
|
self->monsterinfo.currentmove = &carrier_move_attack_rocket;
|
|
else if ((random() < 0.1) || (level.time < self->monsterinfo.attack_finished))
|
|
self->monsterinfo.currentmove = &carrier_move_attack_pre_mg;
|
|
else
|
|
{
|
|
gi.sound (self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0);
|
|
self->monsterinfo.currentmove = &carrier_move_attack_rail;
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (self->monsterinfo.attack_state == AS_BLIND)
|
|
{
|
|
self->monsterinfo.currentmove = &carrier_move_spawn;
|
|
return;
|
|
}
|
|
|
|
if (!enemy_inback && !enemy_infront && !enemy_below) // to side and not under
|
|
{
|
|
if ((random() < 0.1) || (level.time < self->monsterinfo.attack_finished))
|
|
self->monsterinfo.currentmove = &carrier_move_attack_pre_mg;
|
|
else
|
|
{
|
|
gi.sound (self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0);
|
|
self->monsterinfo.currentmove = &carrier_move_attack_rail;
|
|
}
|
|
return;
|
|
}
|
|
|
|
/* if ((g_showlogic) && (g_showlogic->value))
|
|
{
|
|
gi.dprintf ("checking enemy ..");
|
|
if (enemy_inback)
|
|
gi.dprintf (" in back\n");
|
|
else if (enemy_infront)
|
|
gi.dprintf (" in front\n");
|
|
else
|
|
gi.dprintf (" inaccessible\n");
|
|
}
|
|
*/
|
|
if (enemy_infront)
|
|
{
|
|
VectorSubtract (self->enemy->s.origin, self->s.origin, vec);
|
|
range = VectorLength (vec);
|
|
if (range <= 125)
|
|
{
|
|
if ((random() < 0.8) || (level.time < self->monsterinfo.attack_finished))
|
|
self->monsterinfo.currentmove = &carrier_move_attack_pre_mg;
|
|
else
|
|
{
|
|
gi.sound (self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0);
|
|
self->monsterinfo.currentmove = &carrier_move_attack_rail;
|
|
}
|
|
}
|
|
else if (range < 600)
|
|
{
|
|
luck = random();
|
|
if (self->monsterinfo.monster_slots > 2)
|
|
{
|
|
if (luck <= 0.20)
|
|
self->monsterinfo.currentmove = &carrier_move_attack_pre_mg;
|
|
else if (luck <= 0.40)
|
|
self->monsterinfo.currentmove = &carrier_move_attack_pre_gren;
|
|
else if ((luck <= 0.7) && !(level.time < self->monsterinfo.attack_finished))
|
|
{
|
|
gi.sound (self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0);
|
|
self->monsterinfo.currentmove = &carrier_move_attack_rail;
|
|
}
|
|
else
|
|
self->monsterinfo.currentmove = &carrier_move_spawn;
|
|
}
|
|
else
|
|
{
|
|
if (luck <= 0.30)
|
|
self->monsterinfo.currentmove = &carrier_move_attack_pre_mg;
|
|
else if (luck <= 0.65)
|
|
self->monsterinfo.currentmove = &carrier_move_attack_pre_gren;
|
|
else if (level.time >= self->monsterinfo.attack_finished)
|
|
{
|
|
gi.sound (self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0);
|
|
self->monsterinfo.currentmove = &carrier_move_attack_rail;
|
|
}
|
|
else
|
|
self->monsterinfo.currentmove = &carrier_move_attack_pre_mg;
|
|
}
|
|
}
|
|
else // won't use grenades at this range
|
|
{
|
|
luck = random();
|
|
if (self->monsterinfo.monster_slots > 2)
|
|
{
|
|
if (luck < 0.3)
|
|
self->monsterinfo.currentmove = &carrier_move_attack_pre_mg;
|
|
else if ((luck < 0.65) && !(level.time < self->monsterinfo.attack_finished))
|
|
{
|
|
gi.sound (self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0);
|
|
VectorCopy (self->enemy->s.origin, self->pos1); //save for aiming the shot
|
|
self->pos1[2] += self->enemy->viewheight;
|
|
self->monsterinfo.currentmove = &carrier_move_attack_rail;
|
|
}
|
|
else
|
|
self->monsterinfo.currentmove = &carrier_move_spawn;
|
|
}
|
|
else
|
|
{
|
|
if ((luck < 0.45) || (level.time < self->monsterinfo.attack_finished))
|
|
self->monsterinfo.currentmove = &carrier_move_attack_pre_mg;
|
|
else
|
|
{
|
|
gi.sound (self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0);
|
|
self->monsterinfo.currentmove = &carrier_move_attack_rail;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if ((enemy_below) || (enemy_inback))
|
|
{
|
|
self->monsterinfo.currentmove = &carrier_move_attack_rocket;
|
|
}
|
|
}
|
|
|
|
void carrier_attack_mg (edict_t *self)
|
|
{
|
|
CarrierCoopCheck(self);
|
|
self->monsterinfo.currentmove = &carrier_move_attack_mg;
|
|
}
|
|
|
|
void carrier_reattack_mg (edict_t *self)
|
|
{
|
|
CarrierCoopCheck(self);
|
|
if ( infront(self, self->enemy) )
|
|
if (random() <= 0.5)
|
|
if ((random() < 0.7) || (self->monsterinfo.monster_slots <= 2))
|
|
self->monsterinfo.currentmove = &carrier_move_attack_mg;
|
|
else
|
|
self->monsterinfo.currentmove = &carrier_move_spawn;
|
|
else
|
|
self->monsterinfo.currentmove = &carrier_move_attack_post_mg;
|
|
else
|
|
self->monsterinfo.currentmove = &carrier_move_attack_post_mg;
|
|
}
|
|
|
|
|
|
void carrier_attack_gren (edict_t *self)
|
|
{
|
|
// gi.dprintf ("carrier_attack_gren - %2.2f\n",level.time);
|
|
CarrierCoopCheck(self);
|
|
self->timestamp = level.time;
|
|
self->monsterinfo.currentmove = &carrier_move_attack_gren;
|
|
}
|
|
|
|
void carrier_reattack_gren (edict_t *self)
|
|
{
|
|
CarrierCoopCheck(self);
|
|
// gi.dprintf ("carrier_reattack - %2.2f", level.time);
|
|
if ( infront(self, self->enemy) )
|
|
if (self->timestamp + 1.3 > level.time ) // four grenades
|
|
{
|
|
// gi.dprintf (" attacking\n");
|
|
self->monsterinfo.currentmove = &carrier_move_attack_gren;
|
|
return;
|
|
}
|
|
// gi.dprintf ("not attacking\n");
|
|
self->monsterinfo.currentmove = &carrier_move_attack_post_gren;
|
|
}
|
|
|
|
|
|
void carrier_pain (edict_t *self, edict_t *other, float kick, int damage)
|
|
{
|
|
qboolean changed = false;
|
|
|
|
if (self->health < (self->max_health / 2))
|
|
{
|
|
self->s.skinnum |= 1;
|
|
if (!(self->fogclip & 2)) // custom bloodtype flag check
|
|
self->blood_type = 3; // sparks and blood
|
|
}
|
|
|
|
if (skill->value == 3)
|
|
return; // no pain anims in nightmare
|
|
|
|
// gi.dprintf ("carrier pain\n");
|
|
if (level.time < self->pain_debounce_time)
|
|
return;
|
|
|
|
self->pain_debounce_time = level.time + 5;
|
|
|
|
if (damage < 10)
|
|
{
|
|
gi.sound (self, CHAN_VOICE, sound_pain3, 1, ATTN_NONE, 0);
|
|
}
|
|
else if (damage < 30)
|
|
{
|
|
gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NONE, 0);
|
|
if (random() < 0.5)
|
|
{
|
|
changed = true;
|
|
self->monsterinfo.currentmove = &carrier_move_pain_light;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NONE, 0);
|
|
self->monsterinfo.currentmove = &carrier_move_pain_heavy;
|
|
changed = true;
|
|
}
|
|
|
|
// if we changed frames, clean up our little messes
|
|
if (changed)
|
|
{
|
|
self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
|
|
self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
|
|
self->yaw_speed = orig_yaw_speed;
|
|
}
|
|
}
|
|
|
|
void carrier_dead (edict_t *self)
|
|
{
|
|
VectorSet (self->mins, -56, -56, 0);
|
|
VectorSet (self->maxs, 56, 56, 80);
|
|
self->movetype = MOVETYPE_TOSS;
|
|
self->svflags |= SVF_DEADMONSTER;
|
|
self->nextthink = 0;
|
|
gi.linkentity (self);
|
|
}
|
|
|
|
void carrier_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
|
|
{
|
|
self->s.skinnum |= 1;
|
|
if (!(self->fogclip& 2)) //custom bloodtype flag check
|
|
self->blood_type = 3; //sparks and blood
|
|
self->monsterinfo.power_armor_type = POWER_ARMOR_NONE;
|
|
|
|
gi.sound (self, CHAN_VOICE, sound_death, 1, ATTN_NONE, 0);
|
|
self->deadflag = DEAD_DEAD;
|
|
self->takedamage = DAMAGE_NO;
|
|
self->count = 0;
|
|
self->monsterinfo.currentmove = &carrier_move_death;
|
|
}
|
|
|
|
qboolean Carrier_CheckAttack (edict_t *self)
|
|
{
|
|
vec3_t spot1, spot2;
|
|
vec3_t temp;
|
|
float chance;
|
|
trace_t tr;
|
|
qboolean enemy_infront, enemy_inback, enemy_below;
|
|
int enemy_range;
|
|
float enemy_yaw;
|
|
|
|
if (self->enemy->health > 0)
|
|
{
|
|
// see if any entities are in the way of the shot
|
|
VectorCopy (self->s.origin, spot1);
|
|
spot1[2] += self->viewheight;
|
|
VectorCopy (self->enemy->s.origin, spot2);
|
|
spot2[2] += self->enemy->viewheight;
|
|
|
|
tr = gi.trace (spot1, NULL, NULL, spot2, self, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_SLIME|CONTENTS_LAVA);
|
|
|
|
// do we have a clear shot?
|
|
if (tr.ent != self->enemy)
|
|
{
|
|
// go ahead and spawn stuff if we're mad a a client
|
|
if (self->enemy->client && self->monsterinfo.monster_slots > 2)
|
|
{
|
|
self->monsterinfo.attack_state = AS_BLIND;
|
|
return true;
|
|
}
|
|
|
|
// PGM - we want them to go ahead and shoot at info_notnulls if they can.
|
|
if (self->enemy->solid != SOLID_NOT || tr.fraction < 1.0) //PGM
|
|
return false;
|
|
}
|
|
}
|
|
|
|
enemy_infront = infront(self, self->enemy);
|
|
enemy_inback = inback(self, self->enemy);
|
|
enemy_below = below (self, self->enemy);
|
|
|
|
enemy_range = range(self, self->enemy);
|
|
VectorSubtract (self->enemy->s.origin, self->s.origin, temp);
|
|
enemy_yaw = vectoyaw2(temp);
|
|
|
|
self->ideal_yaw = enemy_yaw;
|
|
|
|
// PMM - shoot out the back if appropriate
|
|
if ((enemy_inback) || (!enemy_infront && enemy_below))
|
|
{
|
|
// this is using wait because the attack is supposed to be independent
|
|
if (level.time >= self->wait)
|
|
{
|
|
self->wait = level.time + CARRIER_ROCKET_TIME;
|
|
self->monsterinfo.attack(self);
|
|
if (random() < 0.6)
|
|
self->monsterinfo.attack_state = AS_SLIDING;
|
|
else
|
|
self->monsterinfo.attack_state = AS_STRAIGHT;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// melee attack
|
|
if (enemy_range == RANGE_MELEE)
|
|
{
|
|
self->monsterinfo.attack_state = AS_MISSILE;
|
|
return true;
|
|
}
|
|
|
|
// if (level.time < self->monsterinfo.attack_finished)
|
|
// return false;
|
|
|
|
if (self->monsterinfo.aiflags & AI_STAND_GROUND)
|
|
{
|
|
chance = 0.4;
|
|
}
|
|
else if (enemy_range == RANGE_MELEE)
|
|
{
|
|
chance = 0.8;
|
|
}
|
|
else if (enemy_range == RANGE_NEAR)
|
|
{
|
|
chance = 0.8;
|
|
}
|
|
else if (enemy_range == RANGE_MID)
|
|
{
|
|
chance = 0.8;
|
|
}
|
|
else if (enemy_range == RANGE_FAR)
|
|
{
|
|
chance = 0.5;
|
|
}
|
|
|
|
// PGM - go ahead and shoot every time if it's a info_notnull
|
|
if ((random () < chance) || (self->enemy->solid == SOLID_NOT))
|
|
{
|
|
self->monsterinfo.attack_state = AS_MISSILE;
|
|
// self->monsterinfo.attack_finished = level.time + 2*random();
|
|
return true;
|
|
}
|
|
|
|
if (self->flags & FL_FLY)
|
|
{
|
|
if (random() < 0.6)
|
|
self->monsterinfo.attack_state = AS_SLIDING;
|
|
else
|
|
self->monsterinfo.attack_state = AS_STRAIGHT;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void CarrierPrecache ()
|
|
{
|
|
gi.soundindex ("flyer/flysght1.wav");
|
|
gi.soundindex ("flyer/flysrch1.wav");
|
|
gi.soundindex ("flyer/flypain1.wav");
|
|
gi.soundindex ("flyer/flypain2.wav");
|
|
gi.soundindex ("flyer/flyatck2.wav");
|
|
gi.soundindex ("flyer/flyatck1.wav");
|
|
gi.soundindex ("flyer/flydeth1.wav");
|
|
gi.soundindex ("flyer/flyatck3.wav");
|
|
gi.soundindex ("flyer/flyidle1.wav");
|
|
gi.soundindex ("weapons/rockfly.wav");
|
|
gi.soundindex ("infantry/infatck1.wav");
|
|
gi.soundindex ("gunner/gunatck3.wav");
|
|
gi.soundindex ("weapons/grenlb1b.wav");
|
|
gi.soundindex ("tank/rocket.wav");
|
|
|
|
gi.modelindex ("models/monsters/flyer/tris.md2");
|
|
gi.modelindex ("models/objects/rocket/tris.md2");
|
|
gi.modelindex ("models/objects/debris2/tris.md2");
|
|
gi.modelindex ("models/objects/grenade/tris.md2");
|
|
gi.modelindex("models/items/spawngro/tris.md2");
|
|
gi.modelindex("models/items/spawngro2/tris.md2");
|
|
gi.modelindex ("models/objects/gibs/sm_metal/tris.md2");
|
|
gi.modelindex ("models/objects/gibs/gear/tris.md2");
|
|
}
|
|
|
|
|
|
/*QUAKED monster_carrier (1 .5 0) (-56 -56 -44) (56 56 44) Ambush Trigger_Spawn Sight GoodGuy x HomingRockets
|
|
*/
|
|
void SP_monster_carrier (edict_t *self)
|
|
{
|
|
if (deathmatch->value)
|
|
{
|
|
G_FreeEdict (self);
|
|
return;
|
|
}
|
|
|
|
sound_pain1 = gi.soundindex ("carrier/pain_md.wav");
|
|
sound_pain2 = gi.soundindex ("carrier/pain_lg.wav");
|
|
sound_pain3 = gi.soundindex ("carrier/pain_sm.wav");
|
|
sound_death = gi.soundindex ("carrier/death.wav");
|
|
// sound_search1 = gi.soundindex ("bosshovr/bhvunqv1.wav");
|
|
sound_rail = gi.soundindex ("gladiator/railgun.wav");
|
|
sound_sight = gi.soundindex ("carrier/sight.wav");
|
|
sound_spawn = gi.soundindex ("medic_commander/monsterspawn1.wav");
|
|
|
|
self->s.sound = gi.soundindex ("bosshovr/bhvengn1.wav");
|
|
|
|
self->movetype = MOVETYPE_STEP;
|
|
self->solid = SOLID_BBOX;
|
|
|
|
// Lazarus: special purpose skins
|
|
if ( self->style )
|
|
{
|
|
PatchMonsterModel("models/monsters/carrier/tris.md2");
|
|
self->s.skinnum = self->style * 2;
|
|
// self->style = 0; //clear for custom bloodtype flag
|
|
}
|
|
|
|
self->s.modelindex = gi.modelindex ("models/monsters/carrier/tris.md2");
|
|
VectorSet (self->mins, -56, -56, -44);
|
|
VectorSet (self->maxs, 56, 56, 44);
|
|
|
|
// 2000 - 4000 health
|
|
if (!self->health)
|
|
self->health = max (2000, 2000 + 1000*((skill->value)-1));
|
|
// add health in coop (500 * skill)
|
|
if (coop->value)
|
|
self->health += 500*(skill->value);
|
|
if (!self->gib_health)
|
|
self->gib_health = -200;
|
|
if (!self->mass)
|
|
self->mass = 1000;
|
|
|
|
self->yaw_speed = 15;
|
|
orig_yaw_speed = self->yaw_speed;
|
|
// self->yaw_speed = 1;
|
|
|
|
self->flags |= FL_IMMUNE_LASER;
|
|
self->monsterinfo.aiflags |= AI_IGNORE_SHOTS;
|
|
|
|
self->pain = carrier_pain;
|
|
self->die = carrier_die;
|
|
|
|
self->monsterinfo.melee = NULL;
|
|
self->monsterinfo.stand = carrier_stand;
|
|
self->monsterinfo.walk = carrier_walk;
|
|
self->monsterinfo.run = carrier_run;
|
|
self->monsterinfo.attack = carrier_attack;
|
|
// self->monsterinfo.search = carrier_search;
|
|
self->monsterinfo.sight = carrier_sight;
|
|
self->monsterinfo.checkattack = Carrier_CheckAttack;
|
|
|
|
if (!self->blood_type)
|
|
self->blood_type = 2; //sparks
|
|
else
|
|
self->fogclip |= 2; //custom bloodtype flag
|
|
|
|
// 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 = "Carrier";
|
|
|
|
gi.linkentity (self);
|
|
|
|
self->monsterinfo.currentmove = &carrier_move_stand;
|
|
self->monsterinfo.scale = MODEL_SCALE;
|
|
|
|
CarrierPrecache();
|
|
|
|
flymonster_start (self);
|
|
|
|
self->monsterinfo.attack_finished = 0;
|
|
switch ((int)skill->value)
|
|
{
|
|
case 0:
|
|
self->monsterinfo.monster_slots = 3;
|
|
break;
|
|
case 1:
|
|
case 2:
|
|
self->monsterinfo.monster_slots = 6;
|
|
break;
|
|
case 3:
|
|
self->monsterinfo.monster_slots = 9;
|
|
break;
|
|
default:
|
|
self->monsterinfo.monster_slots = 6;
|
|
break;
|
|
}
|
|
}
|
|
|