yquake2remaster/original/xatrix/monster/fixbot/fixbot.c

1686 lines
31 KiB
C

/*
* Copyright (c) ZeniMax Media Inc.
* Licensed under the GNU General Public License 2.0.
*/
/* ==============================================================
*
* Fixbot
*
* =======================================================================
*/
#include "../../header/local.h"
#include "fixbot.h"
#define MZ2_fixbot_BLASTER_1 MZ2_HOVER_BLASTER_1
#define FIXBOT_MAX_STUCK_FRAMES 10
#define FIXBOT_GOAL_TIMEOUT 15
#define FIXBOT_WELD_GOAL_TIMEOUT 15
qboolean visible(edict_t *self, edict_t *other);
qboolean infront(edict_t *self, edict_t *other);
static int sound_pain1;
static int sound_die;
static int sound_weld1;
static int sound_weld2;
static int sound_weld3;
void fixbot_run(edict_t *self);
void fixbot_stand(edict_t *self);
void fixbot_dead(edict_t *self);
void fixbot_attack(edict_t *self);
void fixbot_fire_blaster(edict_t *self);
void fixbot_fire_welder(edict_t *self);
void fixbot_die(edict_t *self, edict_t *inflictor, edict_t *attacker,
int damage, vec3_t point);
void M_MoveToGoal(edict_t *ent, float dist);
void use_scanner(edict_t *self);
void change_to_roam(edict_t *self);
void fly_vertical(edict_t *self);
extern mmove_t fixbot_move_forward;
extern mmove_t fixbot_move_stand;
extern mmove_t fixbot_move_stand2;
extern mmove_t fixbot_move_roamgoal;
extern mmove_t fixbot_move_weld_start;
extern mmove_t fixbot_move_weld;
extern mmove_t fixbot_move_weld_end;
extern mmove_t fixbot_move_takeoff;
extern mmove_t fixbot_move_landing;
extern mmove_t fixbot_move_turn;
extern void roam_goal(edict_t *self);
void ED_CallSpawn(edict_t *ent);
float
crand(void)
{
return (rand() & 32767) * (2.0 / 32767) - 1;
}
edict_t *
fixbot_FindDeadMonster(edict_t *self)
{
edict_t *ent = NULL;
edict_t *best = NULL;
if (!self)
{
return NULL;
}
while ((ent = findradius(ent, self->s.origin, 1024)) != NULL)
{
if (ent == self)
{
continue;
}
if (!(ent->svflags & SVF_MONSTER))
{
continue;
}
if (ent->monsterinfo.aiflags & AI_GOOD_GUY)
{
continue;
}
if (ent->owner)
{
continue;
}
if (ent->health > 0)
{
continue;
}
if (ent->nextthink)
{
continue;
}
if (!visible(self, ent))
{
continue;
}
if (!best)
{
best = ent;
continue;
}
if (ent->max_health <= best->max_health)
{
continue;
}
best = ent;
}
return best;
}
int
fixbot_search(edict_t *self)
{
edict_t *ent;
if (!self)
{
return 0;
}
if (!self->goalentity)
{
ent = fixbot_FindDeadMonster(self);
if (ent)
{
self->oldenemy = self->enemy;
self->enemy = ent;
self->enemy->owner = self;
self->monsterinfo.aiflags |= AI_MEDIC;
FoundTarget(self);
return 1;
}
}
return 0;
}
void
bot_goal_think(edict_t *self)
{
if (!self)
{
return;
}
/* clean up the bot_goal if the fixbot loses it (to avoid entity leaks) */
if (!self->owner || !self->owner->inuse || self->owner->goalentity != self)
{
G_FreeEdict(self);
}
else
{
self->nextthink = level.time + FRAMETIME;
}
}
static edict_t *
make_bot_goal(edict_t *self)
{
edict_t *ent = G_Spawn();
ent->classname = "bot_goal";
ent->solid = SOLID_BBOX;
ent->owner = self;
ent->think = bot_goal_think;
ent->nextthink = level.time + FRAMETIME;
ent->touch_debounce_time = level.time + FIXBOT_GOAL_TIMEOUT;
return ent;
}
void
landing_goal(edict_t *self)
{
trace_t tr;
vec3_t forward, right, up;
vec3_t end;
edict_t *ent;
if (!self)
{
return;
}
ent = make_bot_goal(self);
VectorSet(ent->mins, -32, -32, -24);
VectorSet(ent->maxs, 32, 32, 24);
gi.linkentity(ent);
AngleVectors(self->s.angles, forward, right, up);
VectorMA(self->s.origin, 32, forward, end);
VectorMA(self->s.origin, -8096, up, end);
tr = gi.trace(self->s.origin, ent->mins, ent->maxs,
end, self, MASK_MONSTERSOLID);
VectorCopy(tr.endpos, ent->s.origin);
gi.linkentity(ent);
self->goalentity = self->enemy = ent;
self->monsterinfo.currentmove = &fixbot_move_landing;
}
void
takeoff_goal(edict_t *self)
{
trace_t tr;
vec3_t forward, right, up;
vec3_t end;
edict_t *ent;
if (!self)
{
return;
}
ent = make_bot_goal(self);
VectorSet(ent->mins, -32, -32, -24);
VectorSet(ent->maxs, 32, 32, 24);
gi.linkentity(ent);
AngleVectors(self->s.angles, forward, right, up);
VectorMA(self->s.origin, 32, forward, end);
VectorMA(self->s.origin, 128, up, end);
tr = gi.trace(self->s.origin, ent->mins, ent->maxs,
end, self, MASK_MONSTERSOLID);
VectorCopy(tr.endpos, ent->s.origin);
gi.linkentity(ent);
self->goalentity = self->enemy = ent;
self->monsterinfo.currentmove = &fixbot_move_takeoff;
}
void
change_to_roam(edict_t *self)
{
if (!self)
{
return;
}
if (fixbot_search(self))
{
return;
}
self->monsterinfo.currentmove = &fixbot_move_roamgoal;
if (self->spawnflags & 16)
{
landing_goal(self);
self->monsterinfo.currentmove = &fixbot_move_landing;
self->spawnflags = 32;
}
if (self->spawnflags & 8)
{
takeoff_goal(self);
self->monsterinfo.currentmove = &fixbot_move_takeoff;
self->spawnflags = 32;
}
if (self->spawnflags & 4)
{
self->monsterinfo.currentmove = &fixbot_move_roamgoal;
self->spawnflags = 32;
}
if (!self->spawnflags)
{
self->monsterinfo.currentmove = &fixbot_move_stand2;
}
}
void
roam_goal(edict_t *self)
{
trace_t tr;
vec3_t forward;
vec3_t end;
edict_t *ent;
vec3_t dang;
int len, oldlen, i;
vec3_t vec;
vec3_t whichvec;
if (!self)
{
return;
}
VectorClear(whichvec);
oldlen = 0;
for (i = 0; i < 12; i++)
{
VectorCopy(self->s.angles, dang);
if (i < 6)
{
dang[YAW] += 30 * i;
}
else
{
dang[YAW] -= 30 * (i - 6);
}
AngleVectors(dang, forward, NULL, NULL);
VectorMA(self->s.origin, 8192, forward, end);
tr = gi.trace(self->s.origin, NULL, NULL, end, self, MASK_SHOT);
VectorSubtract(self->s.origin, tr.endpos, vec);
len = VectorLength(vec);
if (len > oldlen)
{
oldlen = len;
VectorCopy(tr.endpos, whichvec);
}
}
ent = make_bot_goal(self);
VectorCopy(whichvec, ent->s.origin);
gi.linkentity(ent);
self->goalentity = self->enemy = ent;
self->monsterinfo.currentmove = &fixbot_move_turn;
}
void
use_scanner(edict_t *self)
{
edict_t *ent = NULL;
vec3_t vec;
if (!self)
{
return;
}
if (self->fly_sound_debounce_time < level.time &&
strcmp(self->goalentity->classname, "object_repair") != 0)
{
while ((ent = findradius(ent, self->s.origin, 1024)) != NULL)
{
if (strcmp(ent->classname, "object_repair") != 0)
{
continue;
}
if (ent->health < 100)
{
continue;
}
if (!visible(self, ent))
{
continue;
}
/* remove the old one */
if (strcmp(self->goalentity->classname, "bot_goal") == 0)
{
self->goalentity->nextthink = level.time + 0.1;
self->goalentity->think = G_FreeEdict;
}
self->goalentity = self->enemy = ent;
break;
}
}
if (strcmp(self->goalentity->classname, "object_repair") == 0)
{
VectorSubtract(self->s.origin, self->goalentity->s.origin, vec);
if (VectorLength(vec) < 56)
{
self->monsterinfo.currentmove = &fixbot_move_weld_start;
return;
}
}
else if (strcmp(self->goalentity->classname, "bot_goal") == 0)
{
VectorSubtract(self->s.origin, self->goalentity->s.origin, vec);
if (self->goalentity->touch_debounce_time < level.time || VectorLength(vec) < 32)
{
self->goalentity->nextthink = level.time + 0.1;
self->goalentity->think = G_FreeEdict;
self->goalentity = self->enemy = NULL;
self->monsterinfo.currentmove = &fixbot_move_stand;
return;
}
}
VectorSubtract(self->s.origin, self->s.old_origin, vec);
if (VectorLength(vec) == 0)
{
self->count++;
}
else
{
self->count = 0;
}
if (self->count > FIXBOT_MAX_STUCK_FRAMES)
{
/* bot is stuck, get new goalentity */
self->count = 0;
if (strcmp(self->goalentity->classname, "bot_goal") == 0)
{
self->goalentity->nextthink = level.time + 0.1;
self->goalentity->think = G_FreeEdict;
self->goalentity = self->enemy = NULL;
}
else if (strcmp(self->goalentity->classname, "object_repair") == 0)
{
/* don't try to go for welding targets again for a while */
self->fly_sound_debounce_time = level.time + FIXBOT_WELD_GOAL_TIMEOUT;
}
self->monsterinfo.currentmove = &fixbot_move_stand;
}
}
/*
* When the bot has found a landing pad
* it will proceed to its goalentity
* just above the landing pad and
* decend translated along the z the current
* frames are at 10fps
*/
void
blastoff(edict_t *self, vec3_t start, vec3_t aimdir, int damage,
int kick, int te_impact, int hspread, int vspread)
{
trace_t tr;
vec3_t dir;
vec3_t forward, right, up;
vec3_t end;
float r;
float u;
vec3_t water_start;
qboolean water = false;
int content_mask = MASK_SHOT | MASK_WATER;
if (!self)
{
return;
}
hspread += (self->s.frame - FRAME_takeoff_01);
vspread += (self->s.frame - FRAME_takeoff_01);
tr = gi.trace(self->s.origin, NULL, NULL, start, self, MASK_SHOT);
if (!(tr.fraction < 1.0))
{
vectoangles(aimdir, dir);
AngleVectors(dir, forward, right, up);
r = crandom() * hspread;
u = crandom() * vspread;
VectorMA(start, 8192, forward, end);
VectorMA(end, r, right, end);
VectorMA(end, u, up, end);
if (gi.pointcontents(start) & MASK_WATER)
{
water = true;
VectorCopy(start, water_start);
content_mask &= ~MASK_WATER;
}
tr = gi.trace(start, NULL, NULL, end, self, content_mask);
/* see if we hit water */
if (tr.contents & MASK_WATER)
{
int color;
water = true;
VectorCopy(tr.endpos, water_start);
if (!VectorCompare(start, tr.endpos))
{
if (tr.contents & CONTENTS_WATER)
{
if (strcmp(tr.surface->name, "*brwater") == 0)
{
color = SPLASH_BROWN_WATER;
}
else
{
color = SPLASH_BLUE_WATER;
}
}
else if (tr.contents & CONTENTS_SLIME)
{
color = SPLASH_SLIME;
}
else if (tr.contents & CONTENTS_LAVA)
{
color = SPLASH_LAVA;
}
else
{
color = SPLASH_UNKNOWN;
}
if (color != SPLASH_UNKNOWN)
{
gi.WriteByte(svc_temp_entity);
gi.WriteByte(TE_SPLASH);
gi.WriteByte(8);
gi.WritePosition(tr.endpos);
gi.WriteDir(tr.plane.normal);
gi.WriteByte(color);
gi.multicast(tr.endpos, MULTICAST_PVS);
}
/* change bullet's course when it enters water */
VectorSubtract(end, start, dir);
vectoangles(dir, dir);
AngleVectors(dir, forward, right, up);
r = crandom() * hspread * 2;
u = crandom() * vspread * 2;
VectorMA(water_start, 8192, forward, end);
VectorMA(end, r, right, end);
VectorMA(end, u, up, end);
}
/* re-trace ignoring water this time */
tr = gi.trace(water_start, NULL, NULL, end, self, MASK_SHOT);
}
}
/* send gun puff / flash */
if (!((tr.surface) && (tr.surface->flags & SURF_SKY)))
{
if (tr.fraction < 1.0)
{
if (tr.ent->takedamage)
{
T_Damage(tr.ent, self, self, aimdir, tr.endpos, tr.plane.normal,
damage, kick, DAMAGE_BULLET, MOD_BLASTOFF);
}
else
{
if (strncmp(tr.surface->name, "sky", 3) != 0)
{
gi.WriteByte(svc_temp_entity);
gi.WriteByte(te_impact);
gi.WritePosition(tr.endpos);
gi.WriteDir(tr.plane.normal);
gi.multicast(tr.endpos, MULTICAST_PVS);
if (self->client)
{
PlayerNoise(self, tr.endpos, PNOISE_IMPACT);
}
}
}
}
}
/* if went through water, determine where
the end and make a bubble trail */
if (water)
{
vec3_t pos;
VectorSubtract(tr.endpos, water_start, dir);
VectorNormalize(dir);
VectorMA(tr.endpos, -2, dir, pos);
if (gi.pointcontents(pos) & MASK_WATER)
{
VectorCopy(pos, tr.endpos);
}
else
{
tr = gi.trace(pos, NULL, NULL, water_start, tr.ent, MASK_WATER);
}
VectorAdd(water_start, tr.endpos, pos);
VectorScale(pos, 0.5, pos);
gi.WriteByte(svc_temp_entity);
gi.WriteByte(TE_BUBBLETRAIL);
gi.WritePosition(water_start);
gi.WritePosition(tr.endpos);
gi.multicast(pos, MULTICAST_PVS);
}
}
void
fly_vertical(edict_t *self)
{
int i;
vec3_t v;
vec3_t forward, right, up;
vec3_t start;
vec3_t tempvec;
if (!self)
{
return;
}
VectorSubtract(self->goalentity->s.origin, self->s.origin, v);
self->ideal_yaw = vectoyaw(v);
M_ChangeYaw(self);
if ((self->s.frame == FRAME_landing_58) ||
(self->s.frame == FRAME_takeoff_16))
{
self->goalentity->nextthink = level.time + 0.1;
self->goalentity->think = G_FreeEdict;
self->monsterinfo.currentmove = &fixbot_move_stand;
self->goalentity = self->enemy = NULL;
}
/* kick up some particles */
VectorCopy(self->s.angles, tempvec);
tempvec[PITCH] += 90;
AngleVectors(tempvec, forward, right, up);
VectorCopy(self->s.origin, start);
for (i = 0; i < 10; i++)
{
blastoff(self, start, forward, 2, 1, TE_SHOTGUN,
DEFAULT_SHOTGUN_HSPREAD, DEFAULT_SHOTGUN_VSPREAD);
}
}
void
fly_vertical2(edict_t *self)
{
vec3_t v;
int len;
if (!self)
{
return;
}
VectorSubtract(self->goalentity->s.origin, self->s.origin, v);
len = VectorLength(v);
self->ideal_yaw = vectoyaw(v);
M_ChangeYaw(self);
if (len < 32)
{
self->goalentity->nextthink = level.time + 0.1;
self->goalentity->think = G_FreeEdict;
self->monsterinfo.currentmove = &fixbot_move_stand;
self->goalentity = self->enemy = NULL;
}
}
mframe_t fixbot_frames_landing[] = {
{ai_move, 0, NULL},
{ai_move, 0, fly_vertical2},
{ai_move, 0, fly_vertical2},
{ai_move, 0, fly_vertical2},
{ai_move, 0, fly_vertical2},
{ai_move, 0, fly_vertical2},
{ai_move, 0, fly_vertical2},
{ai_move, 0, fly_vertical2},
{ai_move, 0, fly_vertical2},
{ai_move, 0, fly_vertical2},
{ai_move, 0, fly_vertical2},
{ai_move, 0, fly_vertical2},
{ai_move, 0, fly_vertical2},
{ai_move, 0, fly_vertical2},
{ai_move, 0, fly_vertical2},
{ai_move, 0, fly_vertical2},
{ai_move, 0, fly_vertical2},
{ai_move, 0, fly_vertical2},
{ai_move, 0, fly_vertical2},
{ai_move, 0, fly_vertical2},
{ai_move, 0, fly_vertical2},
{ai_move, 0, fly_vertical2},
{ai_move, 0, fly_vertical2},
{ai_move, 0, fly_vertical2},
{ai_move, 0, fly_vertical2},
{ai_move, 0, fly_vertical2},
{ai_move, 0, fly_vertical2},
{ai_move, 0, fly_vertical2},
{ai_move, 0, fly_vertical2},
{ai_move, 0, fly_vertical2},
{ai_move, 0, fly_vertical2},
{ai_move, 0, fly_vertical2},
{ai_move, 0, fly_vertical2},
{ai_move, 0, fly_vertical2},
{ai_move, 0, fly_vertical2},
{ai_move, 0, fly_vertical2},
{ai_move, 0, fly_vertical2},
{ai_move, 0, fly_vertical2},
{ai_move, 0, fly_vertical2},
{ai_move, 0, fly_vertical2},
{ai_move, 0, fly_vertical2},
{ai_move, 0, fly_vertical2},
{ai_move, 0, fly_vertical2},
{ai_move, 0, fly_vertical2},
{ai_move, 0, fly_vertical2},
{ai_move, 0, fly_vertical2},
{ai_move, 0, fly_vertical2},
{ai_move, 0, fly_vertical2},
{ai_move, 0, fly_vertical2},
{ai_move, 0, fly_vertical2},
{ai_move, 0, fly_vertical2},
{ai_move, 0, fly_vertical2},
{ai_move, 0, fly_vertical2},
{ai_move, 0, fly_vertical2},
{ai_move, 0, fly_vertical2},
{ai_move, 0, fly_vertical2},
{ai_move, 0, fly_vertical2},
{ai_move, 0, fly_vertical2}
};
mmove_t fixbot_move_landing = {
FRAME_landing_01,
FRAME_landing_58,
fixbot_frames_landing,
NULL
};
/* generic ambient stand */
mframe_t fixbot_frames_stand[] = {
{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, NULL},
{ai_move, 0, NULL},
{ai_move, 0, NULL},
{ai_move, 0, change_to_roam}
};
mmove_t fixbot_move_stand = {
FRAME_ambient_01,
FRAME_ambient_19,
fixbot_frames_stand,
NULL
};
mframe_t fixbot_frames_stand2[] = {
{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 fixbot_move_stand2 = {
FRAME_ambient_01,
FRAME_ambient_19,
fixbot_frames_stand2,
NULL
};
/*
* will need the pickup offset for the front pincers
* object will need to stop forward of the object
* and take the object with it ( this may require a
* variant of liftoff and landing )
*/
mframe_t fixbot_frames_pickup[] = {
{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, 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}
};
mmove_t fixbot_move_pickup = {
FRAME_pickup_01,
FRAME_pickup_27,
fixbot_frames_pickup,
NULL
};
/* generic frame to move bot */
mframe_t fixbot_frames_roamgoal[] = {
{ai_move, 0, roam_goal}
};
mmove_t fixbot_move_roamgoal = {
FRAME_freeze_01,
FRAME_freeze_01,
fixbot_frames_roamgoal,
NULL
};
void
ai_facing(edict_t *self, float dist)
{
vec3_t v;
if (!self)
{
return;
}
if (infront(self, self->goalentity))
{
self->monsterinfo.currentmove = &fixbot_move_forward;
}
else
{
VectorSubtract(self->goalentity->s.origin, self->s.origin, v);
self->ideal_yaw = vectoyaw(v);
M_ChangeYaw(self);
}
}
mframe_t fixbot_frames_turn[] = {
{ai_facing, 0, NULL}
};
mmove_t fixbot_move_turn = {
FRAME_freeze_01,
FRAME_freeze_01,
fixbot_frames_turn,
NULL
};
void
go_roam(edict_t *self)
{
if (!self)
{
return;
}
self->monsterinfo.currentmove = &fixbot_move_stand;
}
/* takeoff */
mframe_t fixbot_frames_takeoff[] = {
{ai_move, 0.01, fly_vertical},
{ai_move, 0.01, fly_vertical},
{ai_move, 0.01, fly_vertical},
{ai_move, 0.01, fly_vertical},
{ai_move, 0.01, fly_vertical},
{ai_move, 0.01, fly_vertical},
{ai_move, 0.01, fly_vertical},
{ai_move, 0.01, fly_vertical},
{ai_move, 0.01, fly_vertical},
{ai_move, 0.01, fly_vertical},
{ai_move, 0.01, fly_vertical},
{ai_move, 0.01, fly_vertical},
{ai_move, 0.01, fly_vertical},
{ai_move, 0.01, fly_vertical},
{ai_move, 0.01, fly_vertical},
{ai_move, 0.01, fly_vertical}
};
mmove_t fixbot_move_takeoff = {
FRAME_takeoff_01,
FRAME_takeoff_16,
fixbot_frames_takeoff,
NULL
};
/* findout what this is */
mframe_t fixbot_frames_paina[] = {
{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 fixbot_move_paina = {
FRAME_paina_01,
FRAME_paina_06,
fixbot_frames_paina,
fixbot_run
};
/* findout what this is */
mframe_t fixbot_frames_painb[] = {
{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 fixbot_move_painb = {
FRAME_painb_01,
FRAME_painb_08,
fixbot_frames_painb,
fixbot_run
};
/*
* backup from pain
* call a generic painsound
* some spark effects
*/
mframe_t fixbot_frames_pain3[] = {
{ai_move, -1, NULL}
};
mmove_t fixbot_move_pain3 = {
FRAME_freeze_01,
FRAME_freeze_01,
fixbot_frames_pain3,
fixbot_run
};
/*
* bot has compleated landing
* and is now on the grownd
* ( may need second land if the
* bot is releasing jib into jib vat )
*/
mframe_t fixbot_frames_land[] = {
{ai_move, 0, NULL}
};
mmove_t fixbot_move_land = {
FRAME_freeze_01,
FRAME_freeze_01,
fixbot_frames_land,
NULL
};
void
ai_movetogoal(edict_t *self, float dist)
{
if (!self)
{
return;
}
M_MoveToGoal(self, dist);
}
mframe_t fixbot_frames_forward[] = {
{ai_movetogoal, 5, use_scanner}
};
mmove_t fixbot_move_forward = {
FRAME_freeze_01,
FRAME_freeze_01,
fixbot_frames_forward,
NULL
};
mframe_t fixbot_frames_walk[] = {
{ai_walk, 5, NULL}
};
mmove_t fixbot_move_walk = {
FRAME_freeze_01,
FRAME_freeze_01,
fixbot_frames_walk,
NULL
};
mframe_t fixbot_frames_run[] = {
{ai_run, 10, NULL}
};
mmove_t fixbot_move_run = {
FRAME_freeze_01,
FRAME_freeze_01,
fixbot_frames_run,
NULL
};
mframe_t fixbot_frames_death1[] = {
{ai_move, 0, NULL}
};
mmove_t fixbot_move_death1 = {
FRAME_freeze_01,
FRAME_freeze_01,
fixbot_frames_death1,
fixbot_dead
};
mframe_t fixbot_frames_backward[] = {
{ai_move, 0, NULL}
};
mmove_t fixbot_move_backward = {
FRAME_freeze_01,
FRAME_freeze_01,
fixbot_frames_backward,
NULL
};
mframe_t fixbot_frames_start_attack[] = {
{ai_charge, 0, NULL}
};
mmove_t fixbot_move_start_attack = {
FRAME_freeze_01,
FRAME_freeze_01,
fixbot_frames_start_attack,
fixbot_attack
};
mframe_t fixbot_frames_attack1[] = {
{ai_charge, 0, NULL},
{ai_charge, 0, NULL},
{ai_charge, 0, NULL},
{ai_charge, 0, NULL},
{ai_charge, 0, NULL},
{ai_charge, -10, fixbot_fire_blaster}
};
mmove_t fixbot_move_attack1 = {
FRAME_shoot_01,
FRAME_shoot_06,
fixbot_frames_attack1,
NULL
};
int
check_telefrag(edict_t *self)
{
vec3_t end, up;
trace_t tr;
if (!self)
{
return 0;
}
AngleVectors(self->enemy->s.angles, NULL, NULL, up);
VectorMA(self->enemy->s.origin, 48, up, end);
tr = gi.trace(self->enemy->s.origin, self->enemy->mins, self->enemy->maxs,
end, self, MASK_MONSTERSOLID);
if (tr.ent && tr.ent->takedamage)
{
tr.ent->health = 0;
T_Damage(tr.ent, self, self,
vec3_origin, vec3_origin, vec3_origin,
10000, 0, 0, MOD_UNKNOWN);
return 0;
}
return 1;
}
void
fixbot_fire_laser(edict_t *self)
{
vec3_t forward, right, up;
vec3_t tempang, start;
vec3_t dir, angles, end;
edict_t *ent;
if (!self)
{
return;
}
/* critter dun got blown up while bein' fixed */
if (self->enemy->health <= self->enemy->gib_health)
{
self->monsterinfo.currentmove = &fixbot_move_stand;
self->monsterinfo.aiflags &= ~AI_MEDIC;
return;
}
gi.sound(self, CHAN_AUTO, gi.soundindex("misc/lasfly.wav"),
1, ATTN_STATIC, 0);
VectorCopy(self->s.origin, start);
VectorCopy(self->enemy->s.origin, end);
VectorSubtract(end, start, dir);
vectoangles(dir, angles);
ent = G_Spawn();
VectorCopy(self->s.origin, ent->s.origin);
VectorCopy(angles, tempang);
AngleVectors(tempang, forward, right, up);
VectorCopy(tempang, ent->s.angles);
VectorCopy(ent->s.origin, start);
VectorMA(start, 16, forward, start);
VectorCopy(start, ent->s.origin);
ent->enemy = self->enemy;
ent->owner = self;
ent->dmg = -1;
monster_dabeam(ent);
if (self->enemy->health > (self->enemy->mass / 10))
{
if (check_telefrag(self))
{
self->enemy->spawnflags = 0;
self->enemy->monsterinfo.aiflags = 0;
self->enemy->target = NULL;
self->enemy->targetname = NULL;
self->enemy->combattarget = NULL;
self->enemy->deathtarget = NULL;
self->enemy->owner = self;
ED_CallSpawn(self->enemy);
self->enemy->owner = NULL;
self->s.origin[2] += 1;
self->enemy->monsterinfo.aiflags &= ~AI_RESURRECTING;
self->monsterinfo.currentmove = &fixbot_move_stand;
self->monsterinfo.aiflags &= ~AI_MEDIC;
}
}
else
{
self->enemy->monsterinfo.aiflags |= AI_RESURRECTING;
}
}
mframe_t fixbot_frames_laserattack[] = {
{ai_charge, 0, fixbot_fire_laser},
{ai_charge, 0, fixbot_fire_laser},
{ai_charge, 0, fixbot_fire_laser},
{ai_charge, 0, fixbot_fire_laser},
{ai_charge, 0, fixbot_fire_laser},
{ai_charge, 0, fixbot_fire_laser}
};
mmove_t fixbot_move_laserattack = {
FRAME_shoot_01,
FRAME_shoot_06,
fixbot_frames_laserattack,
NULL
};
/* need to get forward translation
data for the charge attack */
mframe_t fixbot_frames_attack2[] = {
{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},
{ai_charge, -10, NULL},
{ai_charge, -10, NULL},
{ai_charge, -10, NULL},
{ai_charge, -10, NULL},
{ai_charge, -10, NULL},
{ai_charge, -10, NULL},
{ai_charge, -10, NULL},
{ai_charge, -10, NULL},
{ai_charge, -10, NULL},
{ai_charge, -10, NULL},
{ai_charge, 0, fixbot_fire_blaster},
{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}
};
mmove_t fixbot_move_attack2 = {
FRAME_charging_01,
FRAME_charging_31,
fixbot_frames_attack2,
fixbot_run
};
void
weldstate(edict_t *self)
{
if (!self)
{
return;
}
if (self->s.frame == FRAME_weldstart_10)
{
self->monsterinfo.currentmove = &fixbot_move_weld;
}
else if (self->s.frame == FRAME_weldmiddle_07)
{
if (self->goalentity->health < 0)
{
self->enemy->owner = NULL;
self->monsterinfo.currentmove = &fixbot_move_weld_end;
}
else
{
self->goalentity->health -= 10;
}
}
else
{
self->goalentity = self->enemy = NULL;
self->monsterinfo.currentmove = &fixbot_move_stand;
}
}
void
ai_move2(edict_t *self, float dist)
{
vec3_t v;
if (!self)
{
return;
}
if (dist)
{
M_walkmove(self, self->s.angles[YAW], dist);
}
VectorSubtract(self->goalentity->s.origin, self->s.origin, v);
self->ideal_yaw = vectoyaw(v);
M_ChangeYaw(self);
}
mframe_t fixbot_frames_weld_start[] = {
{ai_move2, 0, NULL},
{ai_move2, 0, NULL},
{ai_move2, 0, NULL},
{ai_move2, 0, NULL},
{ai_move2, 0, NULL},
{ai_move2, 0, NULL},
{ai_move2, 0, NULL},
{ai_move2, 0, NULL},
{ai_move2, 0, NULL},
{ai_move2, 0, weldstate}
};
mmove_t fixbot_move_weld_start = {
FRAME_weldstart_01,
FRAME_weldstart_10,
fixbot_frames_weld_start,
NULL
};
mframe_t fixbot_frames_weld[] = {
{ai_move2, 0, fixbot_fire_welder},
{ai_move2, 0, fixbot_fire_welder},
{ai_move2, 0, fixbot_fire_welder},
{ai_move2, 0, fixbot_fire_welder},
{ai_move2, 0, fixbot_fire_welder},
{ai_move2, 0, fixbot_fire_welder},
{ai_move2, 0, weldstate}
};
mmove_t fixbot_move_weld = {
FRAME_weldmiddle_01,
FRAME_weldmiddle_07,
fixbot_frames_weld,
NULL
};
mframe_t fixbot_frames_weld_end[] = {
{ai_move2, -2, NULL},
{ai_move2, -2, NULL},
{ai_move2, -2, NULL},
{ai_move2, -2, NULL},
{ai_move2, -2, NULL},
{ai_move2, -2, NULL},
{ai_move2, -2, weldstate}
};
mmove_t fixbot_move_weld_end = {
FRAME_weldend_01,
FRAME_weldend_07,
fixbot_frames_weld_end,
NULL
};
void
fixbot_fire_welder(edict_t *self)
{
vec3_t start;
vec3_t forward, right, up;
vec3_t vec;
float r;
if (!self)
{
return;
}
if (!self->enemy)
{
return;
}
vec[0] = 24.0;
vec[1] = -0.8;
vec[2] = -10.0;
AngleVectors(self->s.angles, forward, right, up);
G_ProjectSource(self->s.origin, vec, forward, right, start);
gi.WriteByte(svc_temp_entity);
gi.WriteByte(TE_WELDING_SPARKS);
gi.WriteByte(10);
gi.WritePosition(start);
gi.WriteDir(vec3_origin);
gi.WriteByte(0xe0 + (rand() & 7));
gi.multicast(self->s.origin, MULTICAST_PVS);
if (random() > 0.8)
{
r = random();
if (r < 0.33)
{
gi.sound(self, CHAN_VOICE, sound_weld1, 1, ATTN_IDLE, 0);
}
else if (r < 0.66)
{
gi.sound(self, CHAN_VOICE, sound_weld2, 1, ATTN_IDLE, 0);
}
else
{
gi.sound(self, CHAN_VOICE, sound_weld3, 1, ATTN_IDLE, 0);
}
}
}
void
fixbot_fire_blaster(edict_t *self)
{
vec3_t start;
vec3_t forward, right, up;
vec3_t end;
vec3_t dir;
if (!self)
{
return;
}
if (!visible(self, self->enemy))
{
self->monsterinfo.currentmove = &fixbot_move_run;
}
AngleVectors(self->s.angles, forward, right, up);
G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_fixbot_BLASTER_1],
forward, right, start);
VectorCopy(self->enemy->s.origin, end);
end[2] += self->enemy->viewheight;
VectorSubtract(end, start, dir);
monster_fire_blaster(self, start, dir, 15, 1000, MZ2_fixbot_BLASTER_1,
EF_BLASTER);
}
void
fixbot_stand(edict_t *self)
{
if (!self)
{
return;
}
self->monsterinfo.currentmove = &fixbot_move_stand;
}
void
fixbot_run(edict_t *self)
{
if (!self)
{
return;
}
if (self->monsterinfo.aiflags & AI_STAND_GROUND)
{
self->monsterinfo.currentmove = &fixbot_move_stand;
}
else
{
self->monsterinfo.currentmove = &fixbot_move_run;
}
}
void
fixbot_walk(edict_t *self)
{
vec3_t vec;
int len;
if (!self)
{
return;
}
if (strcmp(self->goalentity->classname, "object_repair") == 0)
{
VectorSubtract(self->s.origin, self->goalentity->s.origin, vec);
len = VectorLength(vec);
if (len < 32)
{
self->monsterinfo.currentmove = &fixbot_move_weld_start;
return;
}
}
self->monsterinfo.currentmove = &fixbot_move_walk;
}
void
fixbot_start_attack(edict_t *self)
{
if (!self)
{
return;
}
self->monsterinfo.currentmove = &fixbot_move_start_attack;
}
void
fixbot_attack(edict_t *self)
{
vec3_t vec;
int len;
if (!self)
{
return;
}
if (self->monsterinfo.aiflags & AI_MEDIC)
{
if (!visible(self, self->goalentity))
{
return;
}
VectorSubtract(self->s.origin, self->enemy->s.origin, vec);
len = VectorLength(vec);
if (len > 128)
{
return;
}
else
{
self->monsterinfo.currentmove = &fixbot_move_laserattack;
}
}
else
{
self->monsterinfo.currentmove = &fixbot_move_attack2;
}
}
void
fixbot_pain(edict_t *self, edict_t *other /* unused */,
float kick /* unused */, int damage /* unused */)
{
if (!self)
{
return;
}
if (level.time < self->pain_debounce_time)
{
return;
}
self->pain_debounce_time = level.time + 3;
gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
if (damage <= 10)
{
self->monsterinfo.currentmove = &fixbot_move_pain3;
}
else if (damage <= 25)
{
self->monsterinfo.currentmove = &fixbot_move_painb;
}
else
{
self->monsterinfo.currentmove = &fixbot_move_paina;
}
}
void
fixbot_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);
}
void
fixbot_die(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker /* unused */,
int damage /* unused */, vec3_t point /* unused */)
{
if (!self)
{
return;
}
gi.sound(self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0);
BecomeExplosion1(self);
}
/*
* QUAKED monster_fixbot (1 .5 0) (-32 -32 -24) (32 32 24) Ambush Trigger_Spawn Fixit Takeoff Landing
*/
void
SP_monster_fixbot(edict_t *self)
{
if (!self)
{
return;
}
if (deathmatch->value)
{
G_FreeEdict(self);
return;
}
sound_pain1 = gi.soundindex("flyer/flypain1.wav");
sound_die = gi.soundindex("flyer/flydeth1.wav");
sound_weld1 = gi.soundindex("misc/welder1.wav");
sound_weld2 = gi.soundindex("misc/welder2.wav");
sound_weld3 = gi.soundindex("misc/welder3.wav");
self->s.modelindex = gi.modelindex("models/monsters/fixbot/tris.md2");
VectorSet(self->mins, -32, -32, -24);
VectorSet(self->maxs, 32, 32, 24);
self->movetype = MOVETYPE_STEP;
self->solid = SOLID_BBOX;
self->health = 150;
self->mass = 150;
self->viewheight = 16;
self->pain = fixbot_pain;
self->die = fixbot_die;
self->monsterinfo.stand = fixbot_stand;
self->monsterinfo.walk = fixbot_walk;
self->monsterinfo.run = fixbot_run;
self->monsterinfo.attack = fixbot_attack;
gi.linkentity(self);
self->monsterinfo.currentmove = &fixbot_move_stand;
self->monsterinfo.scale = MODEL_SCALE;
flymonster_start(self);
}