/* ==============================================================
 *
 * 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;
	}
}

static 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 */
static 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
};

static 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 )
 */
static 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 */
static 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);
	}
}

static 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 */
static 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 */
static 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 */
static 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
 */
static 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 )
 */
static 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);
}

static 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
};

static mframe_t fixbot_frames_walk[] = {
	{ai_walk, 5, NULL}
};

mmove_t fixbot_move_walk = {
	FRAME_freeze_01,
   	FRAME_freeze_01,
   	fixbot_frames_walk,
	NULL
};

static mframe_t fixbot_frames_run[] = {
	{ai_run, 10, NULL}
};

mmove_t fixbot_move_run = {
	FRAME_freeze_01,
   	FRAME_freeze_01,
   	fixbot_frames_run,
   	NULL
};

static 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
};

static mframe_t fixbot_frames_backward[] = {
	{ai_move, 0, NULL}
};

mmove_t fixbot_move_backward = {
	FRAME_freeze_01,
   	FRAME_freeze_01,
   	fixbot_frames_backward,
   	NULL
};

static 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
};

static 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;
	}
}

static 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 */
static 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);
}

static 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
};

static 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
};

static 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);
}
