/*
 * 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.
 *
 */
/* g_ai.c */

#include "g_local.h"

qboolean	FindTarget(edict_t * self);
extern cvar_t  *maxclients;

qboolean	ai_checkattack(edict_t * self, float dist);

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

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


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

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

/*
 * ============= ai_move
 *
 * Move the specified distance at current facing. This replaces the QC
 * functions: ai_forward, ai_back, ai_pain, and ai_painforward ==============
 */
void
ai_move(edict_t * self, float dist)
{
	M_walkmove(self, self->s.angles[YAW], dist);
}


/*
 * ============= ai_stand
 *
 * 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 (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, 0);
		} 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;
		}
	}
}


/*
 * ============= ai_walk
 *
 * The monster is walking it's beat =============
 */
void
ai_walk(edict_t * self, float dist)
{
	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;
		}
	}
}


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

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


/*
 * ============= ai_turn
 *
 * 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 (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.
 *
 * walkmove(angle, speed) primitive is all or nothing */

/*
 * ============= range
 *
 * returns the range catagorization of an entity reletive 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;

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

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

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


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

	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;

	self->goalentity = self->enemy;
	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
		self->monsterinfo.stand(self);
	else
		self->monsterinfo.run(self);
	VectorSubtract(self->enemy->s.origin, self->s.origin, 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)
{
	/* 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);
}


/*
 * =========== FindTarget
 *
 * 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->monsterinfo.aiflags & AI_GOOD_GUY) {
		if (self->goalentity && self->goalentity->inuse && self->goalentity->classname) {
			if (strcmp(self->goalentity->classname, "target_actor") == 0)
				return false;
		}
		/* FIXME look for monsters? */
		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 */

	/*
	 * revised behavior so they will wake up if they "see" a player make
	 * a noise
	 */
	/* but not weapon impact/explosion noises */

	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 (!client)
			return false;	/* no clients to get mad at */
	}

	/* if the entity went away, forget it */
	if (!client->inuse)
		return false;

	if (client == self->enemy)
		return true;	/* JDC false; */

	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;

		/* this is where we would check invisibility */

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

	//
	/* got one */
	    //
	    FoundTarget(self);

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

	return true;
}


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

/*
 * ============ FacingIdeal
 *
 * ============
 */
qboolean
FacingIdeal(edict_t * self)
{
	float		delta;

	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->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 == 0 && (rand() & 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_MELEE) {
		chance = 0.2;
	} else if (enemy_range == RANGE_NEAR) {
		chance = 0.1;
	} else if (enemy_range == RANGE_MID) {
		chance = 0.02;
	} else {
		return false;
	}

	if (skill->value == 0)
		chance *= 0.5;
	else if (skill->value >= 2)
		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;
}


/*
 * ============= ai_run_melee
 *
 * Turn and close until within an angle to launch a melee attack =============
 */
void
ai_run_melee(edict_t * self)
{
	self->ideal_yaw = enemy_yaw;
	M_ChangeYaw(self);

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


/*
 * ============= ai_run_missile
 *
 * Turn in place until within an angle to launch a missile attack =============
 */
void
ai_run_missile(edict_t * self)
{
	self->ideal_yaw = enemy_yaw;
	M_ChangeYaw(self);

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


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

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


/*
 * ============= ai_checkattack
 *
 * Decides if we're going to attack or do something else used by ai_run and
 * ai_stand =============
 */
qboolean
ai_checkattack(edict_t * self, float dist)
{
	vec3_t		temp;
	qboolean	hesDeadJim;

	/* 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) {
			if ((level.time - self->enemy->teleport_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 */
	hesDeadJim = false;
	if ((!self->enemy) || (!self->enemy->inuse)) {
		hesDeadJim = true;
	} else if (self->monsterinfo.aiflags & AI_MEDIC) {
		if (self->enemy->health > 0) {
			hesDeadJim = true;
			self->monsterinfo.aiflags &= ~AI_MEDIC;
		}
	} else {
		if (self->monsterinfo.aiflags & AI_BRUTAL) {
			if (self->enemy->health <= -80)
				hesDeadJim = true;
		} else {
			if (self->enemy->health <= 0)
				hesDeadJim = true;
		}
	}

	if (hesDeadJim) {
		self->enemy = NULL;
		/* FIXME: look all around for other targets */
		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;
		}
	}
	self->show_hostile = level.time + 1;	/* wake up other monsters */

	/* 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 && self->monsterinfo.search_time < level.time) */
	/* { */
	/* if (FindTarget (self)) */
	/* return true; */
	/* } */

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


	/* JDC self->ideal_yaw = enemy_yaw; */

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


/*
 * ============= ai_run
 *
 * 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 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) {
		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, dist))
		return;

	if (self->monsterinfo.attack_state == AS_SLIDING) {
		ai_run_slide(self, dist);
		return;
	}
	if (enemy_vis) {
		/* if (self.aiflags & AI_LOST_SIGHT) */
		/* dprint("regained sight\n"); */
		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;
	}
	/* coop will change to another enemy if visible */
	if (coop->value) {	/* FIXME: insane guys get mad with this,
				 * which causes crashes! */
		if (FindTarget(self))
			return;
	}
	if ((self->monsterinfo.search_time) && (level.time > (self->monsterinfo.search_time + 20))) {
		M_MoveToGoal(self, dist);
		self->monsterinfo.search_time = 0;
		/* dprint("search timeout\n"); */
		return;
	}
	save = self->goalentity;
	tempgoal = G_Spawn();
	self->goalentity = tempgoal;

	new = false;

	if (!(self->monsterinfo.aiflags & AI_LOST_SIGHT)) {
		/* just lost sight of the player, decide where to go first */

		/*
		 * dprint("lost sight of player, last seen at ");
		 * dprint(vtos(self.last_sighting)); dprint("\n");
		 */
		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;

		/*
		 * dprint("reached current goal: ");
		 * dprint(vtos(self.origin)); dprint(" ");
		 * dprint(vtos(self.last_sighting)); dprint(" ");
		 * dprint(ftos(vlen(self.origin - self.last_sighting)));
		 * dprint("\n");
		 */

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

		if (self->monsterinfo.aiflags & AI_PURSUE_TEMP) {
			/* dprint("was temp goal; retrying original\n"); */
			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];

			/*
			 * dprint("heading is ");
			 * dprint(ftos(self.ideal_yaw)); dprint("\n");
			 */

			/*
			 * debug_drawline(self.origin, self.last_sighting,
			 * 52);
			 */
			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) {
		/* gi.dprintf("checking for course correction\n"); */

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

					/*
					 * gi.dprintf("incomplete path, go
					 * part way and adjust again\n");
					 */
				}
				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);
				/* gi.dprintf("adjusted left\n"); */

				/*
				 * debug_drawline(self.origin,
				 * self.last_sighting, 152);
				 */
			} 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);

					/*
					 * gi.dprintf("incomplete path, go
					 * part way and adjust again\n");
					 */
				}
				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);
				/* gi.dprintf("adjusted right\n"); */

				/*
				 * debug_drawline(self.origin,
				 * self.last_sighting, 152);
				 */
			}
		}
		/* else gi.dprintf("course was fine\n"); */
	}
	M_MoveToGoal(self, dist);

	G_FreeEdict(tempgoal);

	if (self)
		self->goalentity = save;
}
