mirror of
https://github.com/yquake2/yquake2remaster.git
synced 2025-03-09 02:41:07 +00:00
Update to: * https://github.com/yquake2/xatrix/releases/tag/XATRIX_2_10 * https://github.com/yquake2/rogue/releases/tag/ROGUE_2_09 * https://github.com/yquake2/ctf/releases/tag/CTF_1_09 Full sync required: * https://github.com/yquake2/rogue/issues/122 * https://github.com/yquake2/xatrix/issues/96
1686 lines
31 KiB
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);
|
|
}
|