/*
 * Copyright (C) 1997-2001 Id Software, Inc.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or (at
 * your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *
 * See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
 * 02111-1307, USA.
 *
 * =======================================================================
 *
 * The basic AI functions like enemy detection, attacking and so on.
 *
 * =======================================================================
 */

#include "header/local.h"

extern cvar_t *maxclients;

static qboolean enemy_vis;
static qboolean enemy_infront;
static int enemy_range;
static float enemy_yaw;

qboolean FindTarget(edict_t *self);
qboolean ai_checkattack(edict_t *self);

/*
 * Called once each frame to set level.sight_client
 * to the player to be checked for in findtarget.
 * If all clients are either dead or in notarget,
 * sight_client will be null.
 * In coop games, sight_client will cycle
 * between the clients.
 */
void
AI_SetSightClient(void)
{
	edict_t *ent;
	int start, check;

	if (level.sight_client == NULL)
	{
		start = 1;
	}
	else
	{
		start = level.sight_client - g_edicts;
	}

	check = start;

	while (1)
	{
		check++;

		if (check > game.maxclients)
		{
			check = 1;
		}

		ent = &g_edicts[check];

		if (ent->inuse &&
			(ent->health > 0) &&
			!(ent->flags & FL_NOTARGET))
		{
			level.sight_client = ent;
			return; /* got one */
		}

		if (check == start)
		{
			level.sight_client = NULL;
			return; /* nobody to see */
		}
	}
}

/*
 * Move the specified distance at current facing.
 */
void
ai_move(edict_t *self, float dist)
{
	if (!self)
	{
		return;
	}

	M_walkmove(self, self->s.angles[YAW], dist);
}

/*
 *
 * Used for standing around and looking
 * for players Distance is for slight
 * position adjustments needed by the
 * animations
 */
void
ai_stand(edict_t *self, float dist)
{
	vec3_t v;

	if (!self)
	{
		return;
	}

	if (dist)
	{
		M_walkmove(self, self->s.angles[YAW], dist);
	}

	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
	{
		if (self->enemy)
		{
			VectorSubtract(self->enemy->s.origin, self->s.origin, v);
			self->ideal_yaw = vectoyaw(v);

			if ((self->s.angles[YAW] != self->ideal_yaw) &&
				self->monsterinfo.aiflags & AI_TEMP_STAND_GROUND)
			{
				self->monsterinfo.aiflags &=
					~(AI_STAND_GROUND | AI_TEMP_STAND_GROUND);
				self->monsterinfo.run(self);
			}

			M_ChangeYaw(self);
			ai_checkattack(self);
		}
		else
		{
			FindTarget(self);
		}

		return;
	}

	if (FindTarget(self))
	{
		return;
	}

	if (level.time > self->monsterinfo.pausetime)
	{
		self->monsterinfo.walk(self);
		return;
	}

	if (!(self->spawnflags & 1) && (self->monsterinfo.idle) &&
		(level.time > self->monsterinfo.idle_time))
	{
		if (self->monsterinfo.idle_time)
		{
			self->monsterinfo.idle(self);
			self->monsterinfo.idle_time = level.time + 15 + random() * 15;
		}
		else
		{
			self->monsterinfo.idle_time = level.time + random() * 15;
		}
	}
}

/*
 * The monster is walking it's beat
 */
void
ai_walk(edict_t *self, float dist)
{
	if (!self)
	{
		return;
	}

	M_MoveToGoal(self, dist);

	/* check for noticing a player */
	if (FindTarget(self))
	{
		return;
	}

	if ((self->monsterinfo.search) && (level.time > self->monsterinfo.idle_time))
	{
		if (self->monsterinfo.idle_time)
		{
			self->monsterinfo.search(self);
			self->monsterinfo.idle_time = level.time + 15 + random() * 15;
		}
		else
		{
			self->monsterinfo.idle_time = level.time + random() * 15;
		}
	}
}

/*
 * Turns towards target and advances
 * Use this call with a distance of 0
 * to replace ai_face
 */
void
ai_charge(edict_t *self, float dist)
{
	vec3_t v;

	if (!self)
	{
		return;
	}

	if(self->enemy)
	{
		VectorSubtract(self->enemy->s.origin, self->s.origin, v);
	}

	self->ideal_yaw = vectoyaw(v);
	M_ChangeYaw(self);

	if (dist)
	{
		M_walkmove(self, self->s.angles[YAW], dist);
	}
}

/*
 * Don't move, but turn towards
 * ideal_yaw. Distance is for
 * slight position adjustments
 * needed by the animations
 */
void
ai_turn(edict_t *self, float dist)
{
	if (!self)
	{
		return;
	}

	if (dist)
	{
		M_walkmove(self, self->s.angles[YAW], dist);
	}

	if (FindTarget(self))
	{
		return;
	}

	M_ChangeYaw(self);
}

/* ============================================================================ */

/*
 * .enemy
 * Will be world if not currently angry at anyone.
 *
 * .movetarget
 * The next path spot to walk toward.  If .enemy, ignore .movetarget.
 * When an enemy is killed, the monster will try to return to it's path.
 *
 * .hunt_time
 * Set to time + something when the player is in sight, but movement straight for
 * him is blocked.  This causes the monster to use wall following code for
 * movement direction instead of sighting on the player.
 *
 * .ideal_yaw
 * A yaw angle of the intended direction, which will be turned towards at up
 * to 45 deg / state.  If the enemy is in view and hunt_time is not active,
 * this will be the exact line towards the enemy.
 *
 * .pausetime
 * A monster will leave it's stand state and head towards it's .movetarget when
 * time > .pausetime.
 */

/* ============================================================================ */

/*
 * returns the range categorization of an entity relative to self
 * 0	melee range, will become hostile even if back is turned
 * 1	visibility and infront, or visibility and show hostile
 * 2	infront and show hostile
 * 3	only triggered by damage
 */
int
range(edict_t *self, edict_t *other)
{
	vec3_t v;
	float len;

 	if (!self || !other)
	{
		return 0;
	}

	VectorSubtract(self->s.origin, other->s.origin, v);
	len = VectorLength(v);

	if (len < MELEE_DISTANCE)
	{
		return RANGE_MELEE;
	}

	if (len < 500)
	{
		return RANGE_NEAR;
	}

	if (len < 1000)
	{
		return RANGE_MID;
	}

	return RANGE_FAR;
}

/*
 * returns 1 if the entity is visible
 * to self, even if not infront
 */
qboolean
visible(edict_t *self, edict_t *other)
{
	vec3_t spot1;
	vec3_t spot2;
	trace_t trace;

	if (!self || !other)
	{
		return false;
	}

	VectorCopy(self->s.origin, spot1);
	spot1[2] += self->viewheight;
	VectorCopy(other->s.origin, spot2);
	spot2[2] += other->viewheight;
	trace = gi.trace(spot1, vec3_origin, vec3_origin, spot2, self, MASK_OPAQUE);

	if (trace.fraction == 1.0)
	{
		return true;
	}

	return false;
}

/*
 * returns 1 if the entity is in
 * front (in sight) of self
 */
qboolean
infront(edict_t *self, edict_t *other)
{
	vec3_t vec;
	float dot;
	vec3_t forward;

	if (!self || !other)
	{
		return false;
	}

	AngleVectors(self->s.angles, forward, NULL, NULL);

	VectorSubtract(other->s.origin, self->s.origin, vec);
	VectorNormalize(vec);
	dot = DotProduct(vec, forward);

	if (dot > 0.3)
	{
		return true;
	}

	return false;
}


/* ============================================================================ */

void
HuntTarget(edict_t *self)
{
	vec3_t vec;

	if (!self)
	{
		return;
	}

	self->goalentity = self->enemy;

	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
	{
		self->monsterinfo.stand(self);
	}
	else
	{
		self->monsterinfo.run(self);
	}

	if(visible(self, self->enemy))
	{
		VectorSubtract(self->enemy->s.origin, self->s.origin, vec);
	}
	else
	{
		VectorClear(vec);
	}

	self->ideal_yaw = vectoyaw(vec);

	/* wait a while before first attack */
	if (!(self->monsterinfo.aiflags & AI_STAND_GROUND))
	{
		AttackFinished(self, 1);
	}
}

void
FoundTarget(edict_t *self)
{
	if (!self|| !self->enemy || !self->enemy->inuse)
	{
		return;
	}

	/* let other monsters see this monster for a while */
	if (self->enemy->client)
	{
		level.sight_entity = self;
		level.sight_entity_framenum = level.framenum;
		level.sight_entity->light_level = 128;
	}

	self->show_hostile = level.time + 1; /* wake up other monsters */

	VectorCopy(self->enemy->s.origin, self->monsterinfo.last_sighting);
	self->monsterinfo.trail_time = level.time;

	if (!self->combattarget)
	{
		HuntTarget(self);
		return;
	}

	self->goalentity = self->movetarget = G_PickTarget(self->combattarget);

	if (!self->movetarget)
	{
		self->goalentity = self->movetarget = self->enemy;
		HuntTarget(self);
		gi.dprintf("%s at %s, combattarget %s not found\n",
				self->classname,
				vtos(self->s.origin),
				self->combattarget);
		return;
	}

	/* clear out our combattarget, these are a one shot deal */
	self->combattarget = NULL;
	self->monsterinfo.aiflags |= AI_COMBAT_POINT;

	/* clear the targetname, that point is ours! */
	self->movetarget->targetname = NULL;
	self->monsterinfo.pausetime = 0;

	/* run for it */
	self->monsterinfo.run(self);
}

/*
 * Self is currently not attacking anything,
 * so try to find a target
 *
 * Returns TRUE if an enemy was sighted
 *
 * When a player fires a missile, the point
 * of impact becomes a fakeplayer so that
 * monsters that see the impact will respond
 * as if they had seen the player.
 *
 * To avoid spending too much time, only
 * a single client (or fakeclient) is
 * checked each frame. This means multi
 * player games will have slightly
 * slower noticing monsters.
 */
qboolean
FindTarget(edict_t *self)
{
	edict_t *client;
	qboolean heardit;
	int r;

	if (!self)
	{
		return false;
	}

	if (self->monsterinfo.aiflags & AI_GOOD_GUY)
	{
		return false;
	}

	/* if we're going to a combat point, just proceed */
	if (self->monsterinfo.aiflags & AI_COMBAT_POINT)
	{
		return false;
	}

	/* if the first spawnflag bit is set, the monster
	   will only wake up on really seeing the player,
	   not another monster getting angry or hearing
	   something */

	heardit = false;

	if ((level.sight_entity_framenum >= (level.framenum - 1)) &&
		!(self->spawnflags & 1))
	{
		client = level.sight_entity;

		if (client->enemy == self->enemy)
		{
			return false;
		}
	}
	else if (level.sound_entity_framenum >= (level.framenum - 1))
	{
		client = level.sound_entity;
		heardit = true;
	}
	else if (!(self->enemy) &&
			 (level.sound2_entity_framenum >= (level.framenum - 1)) &&
			 !(self->spawnflags & 1))
	{
		client = level.sound2_entity;
		heardit = true;
	}
	else
	{
		client = level.sight_client;
	}

	/* if the entity went away, forget it */
	if (!client || !client->inuse ||
		(client->client && level.intermissiontime))
	{
		return false;
	}

	if (client == self->enemy)
	{
		return true;
	}

	if (client->client)
	{
		if (client->flags & FL_NOTARGET)
		{
			return false;
		}
	}
	else if (client->svflags & SVF_MONSTER)
	{
		if (!client->enemy)
		{
			return false;
		}

		if (client->enemy->flags & FL_NOTARGET)
		{
			return false;
		}
	}
	else if (heardit)
	{
		if (client->owner->flags & FL_NOTARGET)
		{
			return false;
		}
	}
	else
	{
		return false;
	}

	if (!heardit)
	{
		r = range(self, client);

		if (r == RANGE_FAR)
		{
			return false;
		}

		/* is client in an spot too dark to be seen? */
		if (client->light_level <= 5)
		{
			return false;
		}

		if (!visible(self, client))
		{
			return false;
		}

		if (r == RANGE_NEAR)
		{
			if ((client->show_hostile < level.time) && !infront(self, client))
			{
				return false;
			}
		}
		else if (r == RANGE_MID)
		{
			if (!infront(self, client))
			{
				return false;
			}
		}

		self->enemy = client;

		if (strcmp(self->enemy->classname, "player_noise") != 0)
		{
			self->monsterinfo.aiflags &= ~AI_SOUND_TARGET;

			if (!self->enemy->client)
			{
				self->enemy = self->enemy->enemy;

				if (!self->enemy->client)
				{
					self->enemy = NULL;
					return false;
				}
			}
		}
	}
	else /* heardit */
	{
		vec3_t temp;

		if (self->spawnflags & 1)
		{
			if (!visible(self, client))
			{
				return false;
			}
		}
		else
		{
			if (!gi.inPHS(self->s.origin, client->s.origin))
			{
				return false;
			}
		}

		VectorSubtract(client->s.origin, self->s.origin, temp);

		if (VectorLength(temp) > 1000) /* too far to hear */
		{
			return false;
		}

		/* check area portals - if they are different
		   and not connected then we can't hear it */
		if (client->areanum != self->areanum)
		{
			if (!gi.AreasConnected(self->areanum, client->areanum))
			{
				return false;
			}
		}

		self->ideal_yaw = vectoyaw(temp);
		M_ChangeYaw(self);

		/* hunt the sound for a bit; hopefully find the real player */
		self->monsterinfo.aiflags |= AI_SOUND_TARGET;
		self->enemy = client;
	}

	FoundTarget(self);

	if (!(self->monsterinfo.aiflags & AI_SOUND_TARGET) &&
		(self->monsterinfo.sight))
	{
		self->monsterinfo.sight(self, self->enemy);
	}

	return true;
}

/* ============================================================================= */

qboolean
FacingIdeal(edict_t *self)
{
	float delta;

	if (!self)
	{
		return false;
	}

	delta = anglemod(self->s.angles[YAW] - self->ideal_yaw);

	if ((delta > 45) && (delta < 315))
	{
		return false;
	}

	return true;
}

/* ============================================================================= */

qboolean
M_CheckAttack(edict_t *self)
{
	vec3_t spot1, spot2;
	float chance;
	trace_t tr;

	if (!self || !self->enemy || !self->enemy->inuse)
	{
		return false;
	}

	if (self->enemy->health > 0)
	{
		/* see if any entities are in the way of the shot */
		VectorCopy(self->s.origin, spot1);
		spot1[2] += self->viewheight;
		VectorCopy(self->enemy->s.origin, spot2);
		spot2[2] += self->enemy->viewheight;

		tr = gi.trace(spot1, NULL, NULL, spot2, self,
				CONTENTS_SOLID | CONTENTS_MONSTER | CONTENTS_SLIME |
				CONTENTS_LAVA | CONTENTS_WINDOW);

		/* do we have a clear shot? */
		if (tr.ent != self->enemy)
		{
			return false;
		}
	}

	/* melee attack */
	if (enemy_range == RANGE_MELEE)
	{
		/* don't always melee in easy mode */
		if ((skill->value == SKILL_EASY) && (randk() & 3))
		{
			return false;
		}

		if (self->monsterinfo.melee)
		{
			self->monsterinfo.attack_state = AS_MELEE;
		}
		else
		{
			self->monsterinfo.attack_state = AS_MISSILE;
		}

		return true;
	}

	/* missile attack */
	if (!self->monsterinfo.attack)
	{
		return false;
	}

	if (level.time < self->monsterinfo.attack_finished)
	{
		return false;
	}

	if (enemy_range == RANGE_FAR)
	{
		return false;
	}

	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
	{
		chance = 0.4;
	}
	else if (enemy_range == RANGE_NEAR)
	{
		chance = 0.1;
	}
	else if (enemy_range == RANGE_MID)
	{
		chance = 0.02;
	}
	else
	{
		return false;
	}

	if (skill->value == SKILL_EASY)
	{
		chance *= 0.5;
	}
	else if (skill->value >= SKILL_HARD)
	{
		chance *= 2;
	}

	if (random() < chance)
	{
		self->monsterinfo.attack_state = AS_MISSILE;
		self->monsterinfo.attack_finished = level.time + 2 * random();
		return true;
	}

	if (self->flags & FL_FLY)
	{
		if (random() < 0.3)
		{
			self->monsterinfo.attack_state = AS_SLIDING;
		}
		else
		{
			self->monsterinfo.attack_state = AS_STRAIGHT;
		}
	}

	return false;
}

/*
 * Turn and close until within an
 * angle to launch a melee attack
 */
void
ai_run_melee(edict_t *self)
{
	if (!self)
	{
		return;
	}

	self->ideal_yaw = enemy_yaw;
	M_ChangeYaw(self);

	if (FacingIdeal(self))
	{
		if (self->monsterinfo.melee) {
			self->monsterinfo.melee(self);
			self->monsterinfo.attack_state = AS_STRAIGHT;
		}
	}
}

/*
 * Turn in place until within an
 * angle to launch a missile attack
 */
void
ai_run_missile(edict_t *self)
{
	if (!self)
	{
		return;
	}

	self->ideal_yaw = enemy_yaw;
	M_ChangeYaw(self);

	if (FacingIdeal(self))
	{
		if (self->monsterinfo.attack) {
			self->monsterinfo.attack(self);
			self->monsterinfo.attack_state = AS_STRAIGHT;
		}
	}
}

/*
 * Strafe sideways, but stay at
 * approximately the same range
 */
void
ai_run_slide(edict_t *self, float distance)
{
	float ofs;

	if (!self)
	{
		return;
	}

	self->ideal_yaw = enemy_yaw;
	M_ChangeYaw(self);

	if (self->monsterinfo.lefty)
	{
		ofs = 90;
	}
	else
	{
		ofs = -90;
	}

	if (M_walkmove(self, self->ideal_yaw + ofs, distance))
	{
		return;
	}

	self->monsterinfo.lefty = 1 - self->monsterinfo.lefty;
	M_walkmove(self, self->ideal_yaw - ofs, distance);
}

/*
 * Decides if we're going to attack
 * or do something else used by
 * ai_run and ai_stand
 */
static qboolean
hesDeadJim(const edict_t *self)
{
	const edict_t *enemy = self->enemy;

	if (!enemy || !enemy->inuse)
	{
		return true;
	}

	if (self->monsterinfo.aiflags & AI_MEDIC)
	{
		return (enemy->health > 0);
	}

	if (enemy->client && level.intermissiontime)
	{
		return true;
	}

	if (self->monsterinfo.aiflags & AI_BRUTAL)
	{
		return (enemy->health <= -80);
	}

	return (enemy->health <= 0);
}

qboolean
ai_checkattack(edict_t *self)
{
	vec3_t temp;

	if (!self)
	{
		enemy_vis = false;

		return false;
	}

	/* this causes monsters to run blindly
	   to the combat point w/o firing */
	if (self->goalentity)
	{
		if (self->monsterinfo.aiflags & AI_COMBAT_POINT)
		{
			return false;
		}

		if ((self->monsterinfo.aiflags & AI_SOUND_TARGET) && !visible(self, self->goalentity))
		{
			if ((level.time - self->enemy->last_sound_time) > 5.0)
			{
				if (self->goalentity == self->enemy)
				{
					if (self->movetarget)
					{
						self->goalentity = self->movetarget;
					}
					else
					{
						self->goalentity = NULL;
					}
				}

				self->monsterinfo.aiflags &= ~AI_SOUND_TARGET;

				if (self->monsterinfo.aiflags & AI_TEMP_STAND_GROUND)
				{
					self->monsterinfo.aiflags &=
							~(AI_STAND_GROUND | AI_TEMP_STAND_GROUND);
				}
			}
			else
			{
				self->show_hostile = level.time + 1;
				return false;
			}
		}
	}

	enemy_vis = false;

	/* see if the enemy is dead */
	if (hesDeadJim(self))
	{
		self->enemy = NULL;
		self->monsterinfo.aiflags &= ~AI_MEDIC;

		if (self->oldenemy && (self->oldenemy->health > 0))
		{
			self->enemy = self->oldenemy;
			self->oldenemy = NULL;
			HuntTarget(self);
		}
		else
		{
			if (self->movetarget)
			{
				self->goalentity = self->movetarget;
				self->monsterinfo.walk(self);
			}
			else
			{
				/* we need the pausetime otherwise the stand code
				   will just revert to walking with no target and
				   the monsters will wonder around aimlessly trying
				   to hunt the world entity */
				self->monsterinfo.pausetime = level.time + 100000000;
				self->monsterinfo.stand(self);
			}

			return true;
		}
	}

	/* wake up other monsters */
	self->show_hostile = level.time + 1;

	/* check knowledge of enemy */
	enemy_vis = visible(self, self->enemy);

	if (enemy_vis)
	{
		self->monsterinfo.search_time = level.time + 5;
		VectorCopy(self->enemy->s.origin, self->monsterinfo.last_sighting);
	}

	/* look for other coop players here */
	if (coop->value && (self->monsterinfo.search_time < level.time))
	{
		if (FindTarget(self))
		{
			return true;
		}
	}

	if (self->enemy)
	{
		enemy_infront = infront(self, self->enemy);
		enemy_range = range(self, self->enemy);
		VectorSubtract(self->enemy->s.origin, self->s.origin, temp);
		enemy_yaw = vectoyaw(temp);
	}

	if (self->monsterinfo.attack_state == AS_MISSILE)
	{
		ai_run_missile(self);
		return true;
	}

	if (self->monsterinfo.attack_state == AS_MELEE)
	{
		ai_run_melee(self);
		return true;
	}

	/* if enemy is not currently visible,
	   we will never attack */
	if (!enemy_vis)
	{
		return false;
	}

	return self->monsterinfo.checkattack(self);
}

/*
 * The monster has an enemy
 * it is trying to kill
 */
void
ai_run(edict_t *self, float dist)
{
	vec3_t v;
	edict_t *tempgoal;
	edict_t *save;
	qboolean new;
	edict_t *marker;
	float d1, d2;
	trace_t tr;
	vec3_t v_forward, v_right;
	float left, center, right;
	vec3_t left_target, right_target;

	if (!self)
	{
		return;
	}

	/* if we're going to a combat point, just proceed */
	if (self->monsterinfo.aiflags & AI_COMBAT_POINT)
	{
		M_MoveToGoal(self, dist);
		return;
	}

	if (self->monsterinfo.aiflags & AI_SOUND_TARGET)
	{
		/* Special case: Some projectiles like grenades or rockets are
		   classified as an enemy. When they explode they generate a
		   sound entity, triggering this code path. Since they're gone
		   after the explosion their entity pointer is NULL. Therefor
		   self->enemy is also NULL and we're crashing. Work around
		   this by predending that the enemy is still there, and move
		   to it. */
		if (self->enemy)
		{
			VectorSubtract(self->s.origin, self->enemy->s.origin, v);

			if (VectorLength(v) < 64)
			{
				self->monsterinfo.aiflags |= (AI_STAND_GROUND | AI_TEMP_STAND_GROUND);
				self->monsterinfo.stand(self);
				return;
			}
		}

		M_MoveToGoal(self, dist);

		if (!FindTarget(self))
		{
			return;
		}
	}

	if (ai_checkattack(self))
	{
		return;
	}

	if (self->monsterinfo.attack_state == AS_SLIDING)
	{
		ai_run_slide(self, dist);
		return;
	}

	if (enemy_vis)
	{
		M_MoveToGoal(self, dist);
		self->monsterinfo.aiflags &= ~AI_LOST_SIGHT;
		VectorCopy(self->enemy->s.origin, self->monsterinfo.last_sighting);
		self->monsterinfo.trail_time = level.time;
		return;
	}

	if ((self->monsterinfo.search_time) &&
		(level.time > (self->monsterinfo.search_time + 20)))
	{
		M_MoveToGoal(self, dist);
		self->monsterinfo.search_time = 0;
		return;
	}

	tempgoal = G_SpawnOptional();

	if (!tempgoal)
	{
		M_MoveToGoal(self, dist);
		return;
	}

	save = self->goalentity;
	self->goalentity = tempgoal;

	new = false;

	if (!(self->monsterinfo.aiflags & AI_LOST_SIGHT))
	{
		/* just lost sight of the player, decide where to go first */
		self->monsterinfo.aiflags |= (AI_LOST_SIGHT | AI_PURSUIT_LAST_SEEN);
		self->monsterinfo.aiflags &= ~(AI_PURSUE_NEXT | AI_PURSUE_TEMP);
		new = true;
	}

	if (self->monsterinfo.aiflags & AI_PURSUE_NEXT)
	{
		self->monsterinfo.aiflags &= ~AI_PURSUE_NEXT;

		/* give ourself more time since we got this far */
		self->monsterinfo.search_time = level.time + 5;

		if (self->monsterinfo.aiflags & AI_PURSUE_TEMP)
		{
			self->monsterinfo.aiflags &= ~AI_PURSUE_TEMP;
			marker = NULL;
			VectorCopy(self->monsterinfo.saved_goal,
					self->monsterinfo.last_sighting);
			new = true;
		}
		else if (self->monsterinfo.aiflags & AI_PURSUIT_LAST_SEEN)
		{
			self->monsterinfo.aiflags &= ~AI_PURSUIT_LAST_SEEN;
			marker = PlayerTrail_PickFirst(self);
		}
		else
		{
			marker = PlayerTrail_PickNext(self);
		}

		if (marker)
		{
			VectorCopy(marker->s.origin, self->monsterinfo.last_sighting);
			self->monsterinfo.trail_time = marker->timestamp;
			self->s.angles[YAW] = self->ideal_yaw = marker->s.angles[YAW];
			new = true;
		}
	}

	VectorSubtract(self->s.origin, self->monsterinfo.last_sighting, v);
	d1 = VectorLength(v);

	if (d1 <= dist)
	{
		self->monsterinfo.aiflags |= AI_PURSUE_NEXT;
		dist = d1;
	}

	VectorCopy(self->monsterinfo.last_sighting, self->goalentity->s.origin);

	if (new)
	{
		tr = gi.trace(self->s.origin, self->mins, self->maxs,
				self->monsterinfo.last_sighting, self,
				MASK_PLAYERSOLID);

		if (tr.fraction < 1)
		{
			VectorSubtract(self->goalentity->s.origin, self->s.origin, v);
			d1 = VectorLength(v);
			center = tr.fraction;
			d2 = d1 * ((center + 1) / 2);
			self->s.angles[YAW] = self->ideal_yaw = vectoyaw(v);
			AngleVectors(self->s.angles, v_forward, v_right, NULL);

			VectorSet(v, d2, -16, 0);
			G_ProjectSource(self->s.origin, v, v_forward, v_right, left_target);
			tr = gi.trace(self->s.origin, self->mins, self->maxs, left_target,
					self, MASK_PLAYERSOLID);
			left = tr.fraction;

			VectorSet(v, d2, 16, 0);
			G_ProjectSource(self->s.origin, v, v_forward, v_right, right_target);
			tr = gi.trace(self->s.origin, self->mins, self->maxs, right_target,
					self, MASK_PLAYERSOLID);
			right = tr.fraction;

			center = (d1 * center) / d2;

			if ((left >= center) && (left > right))
			{
				if (left < 1)
				{
					VectorSet(v, d2 * left * 0.5, -16, 0);
					G_ProjectSource(self->s.origin, v, v_forward,
							v_right, left_target);
				}

				VectorCopy(self->monsterinfo.last_sighting,
						self->monsterinfo.saved_goal);
				self->monsterinfo.aiflags |= AI_PURSUE_TEMP;
				VectorCopy(left_target, self->goalentity->s.origin);
				VectorCopy(left_target, self->monsterinfo.last_sighting);
				VectorSubtract(self->goalentity->s.origin, self->s.origin, v);
				self->s.angles[YAW] = self->ideal_yaw = vectoyaw(v);
			}
			else if ((right >= center) && (right > left))
			{
				if (right < 1)
				{
					VectorSet(v, d2 * right * 0.5, 16, 0);
					G_ProjectSource(self->s.origin, v, v_forward, v_right,
							right_target);
				}

				VectorCopy(self->monsterinfo.last_sighting,
						self->monsterinfo.saved_goal);
				self->monsterinfo.aiflags |= AI_PURSUE_TEMP;
				VectorCopy(right_target, self->goalentity->s.origin);
				VectorCopy(right_target, self->monsterinfo.last_sighting);
				VectorSubtract(self->goalentity->s.origin, self->s.origin, v);
				self->s.angles[YAW] = self->ideal_yaw = vectoyaw(v);
			}
		}
	}

	M_MoveToGoal(self, dist);

	G_FreeEdict(tempgoal);

	self->goalentity = save;
}
