///////////////////////////////////////////////////////////////////////////////
//	ai_utils.cpp
//
//	Only from the mind of the Amoeba
///////////////////////////////////////////////////////////////////////////////

#include	"world.h"

#include	"ai.h"			//	stuff global to all ai_ files
#include	"ai_utils.h"

#include	"actorlist.h"

///////////////////////////////////////////////////////////////////////////////
//	ai_debug_info
//
///////////////////////////////////////////////////////////////////////////////

void	ai_debug_info (userEntity_t *self, char *routine, char* extra)
{
	playerHook_t	*hook = (playerHook_t *) self->userHook;
	char			msg [256];
	
	if (self->flags & FL_DEBUG)
	{
		gstate->printxy (0, 32, "%s", routine);
		gstate->Con_Dprintf ("--- %s ---\n", routine);

		switch	(hook->goal_type)
		{
			case	GOAL_TEMP:
				gstate->printxy (0, 40, "GOAL_TEMP");
				break;
			case	GOAL_NODE:
				gstate->printxy (0, 40, "GOAL_NODE");
				break;
			case	GOAL_PATH:
				gstate->printxy (0, 40, "GOAL_PATH");
				break;
			case	GOAL_ENEMY:
				gstate->printxy (0, 40, "GOAL_ENEMY");
				break;
			default:
				gstate->printxy (0, 40, "GOAL UNKNOWN");
				break;
		}

		gstate->printxy (0, 48, "%s", extra);

		msg [0] = 0x00;

		if (self->flags & FL_FORCEANGLES)
			strcat (msg, "FA ");
		if (self->flags & FL_FIXANGLES)
			strcat (msg, "FX ");
		if (self->flags & FL_FAKEENTITY)
			strcat (msg, "FE ");
		if (self->flags & FL_CLIENT)
			strcat (msg, "CL ");
		if (self->flags & FL_INWATER)
			strcat (msg, "IW ");
		if (self->flags & FL_MONSTER)
			strcat (msg, "MN ");
		if (self->flags & FL_SWIM)
			strcat (msg, "SW ");
		if (self->flags & FL_NOTARGET)
			strcat (msg, "NT ");
		if (self->flags & FL_ITEM)
			strcat (msg, "IT ");
		if (self->flags & FL_ONGROUND)
			strcat (msg, "OG ");
		if (self->flags & FL_PARTIALGROUND)
			strcat (msg, "PG ");
		if (self->flags & FL_WATERJUMP)
			strcat (msg, "WJ ");
		if (self->flags & FL_JUMPRELEASED)
			strcat (msg, "JR ");
		if (self->flags & FL_BOT)
			strcat (msg, "BT ");
		if (self->flags & FL_EXPLOSIVE)
			strcat (msg, "EX ");
		if (self->flags & FL_ATTACKHOLD)
			strcat (msg, "AH ");
		if (self->flags & FL_HCHILD)
			strcat (msg, "SC ");
		if (self->flags & FL_HPARENT)
			strcat (msg, "SP ");
		if (self->flags & FL_BODY)
			strcat (msg, "BD ");
		if (self->flags & FL_INWARP)
			strcat (msg, "IW ");
		if (self->flags & FL_CAMERAMODE)
			strcat (msg, "CM ");
		if (self->flags & FL_DEBUG)
			strcat (msg, "DB ");
		if (self->flags & FL_IGNOREUSETARGET)
			strcat (msg, "IU ");
		if (self->flags & FL_NORESPAWN)
			strcat (msg, "NR ");

		gstate->printxy (0, 56, "flags %s", msg);

		msg [0] = 0x00;

		if (hook->th_state == TH_FOLLOW)
			strcat (msg, "FO ");
		else if (hook->th_state == TH_PATH_FOLLOW)
			strcat (msg, "PF ");
		else if (hook->th_state == TH_STAND)
			strcat (msg, "ST ");
		else if (hook->th_state == TH_NODE_WANDER)
			strcat (msg, "NW ");
		else if (hook->th_state == TH_ATTACK)
			strcat (msg, "ST ");
		else if (hook->th_state == TH_WANDER)
			strcat (msg, "WN ");
		else if (hook->th_state == TH_PATH_WANDER)
			strcat (msg, "PW ");
		else if (hook->th_state == TH_TRANSITION)
			strcat (msg, "TR ");
		else if (hook->th_state == TH_NODE_RETREAT)
			strcat (msg, "NR ");
		else if (hook->th_state == TH_TIME_FOLLOW)
			strcat (msg, "TF ");

		gstate->printxy (0, 64, "flags %s", msg);

		msg [0] = 0x00;

		if (hook->ai_flags & AI_CONTINUE)
			strcat (msg, "CO ");
		else if (hook->ai_flags & AI_SPECIAL)
			strcat (msg, "SP ");
		else if (hook->ai_flags & AI_END_ATTACK)
			strcat (msg, "EA ");
		else if (hook->ai_flags & AI_JUMP)
			strcat (msg, "JM ");
		else if (hook->ai_flags & AI_TURN)
			strcat (msg, "TR ");
		else if (hook->ai_flags & AI_ACTION)
			strcat (msg, "AC ");

		gstate->printxy (0, 72, "ai_fl %s", msg);
		
		gstate->printxy (0, 80, "water = %i", self->waterlevel);
	}
}

///////////////////////////////////////////////////////////////////////////////
//	ai_debug_print
//
//	prints to the console if FL_DEBUG bit is set in self->flags
///////////////////////////////////////////////////////////////////////////////

void	ai_debug_print (userEntity_t *self, char *fmt, ...)
{
	char		msg	[255];
	va_list		argptr;
	
	if (self->flags & FL_DEBUG)
	{
		va_start (argptr,fmt);
		vsprintf (msg,fmt,argptr);
		va_end (argptr);

		gstate->Con_Dprintf ("%s", msg);
	}
}

///////////////////////////////////////////////////////////////////////////////
//	ai_skill_rand
//
//	generates a random number between 1 and AI_SKILL_MAX.  if that number is
//	less than or equal to gstate->skill, returns TRUE
//
//	used to determine if an entity should perform a function based on the game's
//	difficulty setting
///////////////////////////////////////////////////////////////////////////////

int	ai_skill_rand (void)
{
	if (rand () % 100 <= gstate->skill)
		return	TRUE;
	else
		return	FALSE;
}

///////////////////////////////////////////////////////////////////////////////
//	ai_setfov
//
//	sets up a bots field of view parameter
//	set's hook->fov to cos (fov_degrees / 2)
///////////////////////////////////////////////////////////////////////////////

void	ai_setfov (userEntity_t *self, float fov_degrees)
{
	playerHook_t	*hook = (playerHook_t *) self->userHook;
	float			rad;

	// convert degrees to radians
	rad = (fov_degrees / 2.0) * (M_PI * 2 / 360.0);
	hook->fov = cos (rad);
}

///////////////////////////////////////////////////////////////////////////////
//	ai_goal_dist
//
//	returns distance from self to self->goalentity
//
//	if self is flying, then only x/y distance is taken into account
///////////////////////////////////////////////////////////////////////////////

float	ai_goal_dist (userEntity_t *self, userEntity_t *goal)
{
	if (self->movetype == MOVETYPE_FLY)
		return sqrt ((self->s.origin [0] - goal->s.origin [0]) * (self->s.origin [0] - goal->s.origin [0]) +
					(self->s.origin [1] - goal->s.origin [1]) * (self->s.origin [1] - goal->s.origin [1]));
	else
		return	qv_dist (self->s.origin, self->goalentity->s.origin);
}

///////////////////////////////////////////////////////////////////////////////
//	ai_infront
//
//	determines if target is within 
//	self's fov (normally 90 for monsters)
///////////////////////////////////////////////////////////////////////////////

int		ai_infront (userEntity_t *self, userEntity_t *target)
{
	playerHook_t	*hook = (playerHook_t *) self->userHook;
	Vector	ang, f, r, u, dir;
	float	dp;

	// get facing vector
	ang = Vector (self->s.angles);
	ang.AngleVectors (f, r, u);
	
	// get vector to target
	// FIXME: make z's the same before getting dir
	dir = Vector (target->s.origin) - Vector (self->s.origin);
	dir.Normalize ();

	dp = dir ^ f;

	if (dp > hook->fov)
		return	TRUE;
	else
		return	FALSE;
}

///////////////////////////////////////////////////////////////////////////////
//	ai_visible
//
//	determines if targ is within the fov of self and if a line
//	can be traces between the two
///////////////////////////////////////////////////////////////////////////////

int		ai_visible (userEntity_t *self, userEntity_t *targ)
{
	Vector			org, end;

	if (ai_infront (self, targ))
	{
		end = ((Vector (targ->absmin) + Vector (targ->absmax)) * 0.5) + targ->view_ofs;
		org = Vector (self->s.origin) + Vector (self->view_ofs);

		gstate->TraceLine (org.vec (), end.vec (), TRUE, self, &trace);

		if (trace.fraction == 1.0 || trace.ent == targ)
		{
			org = org - Vector (trace.endpos);
			
			// return the distance to target
			return	org.vlen ();
		}
	}

	return	FALSE;
}

///////////////////////////////////////////////////////////////////////////////
//	ai_set_think
//
///////////////////////////////////////////////////////////////////////////////

void	ai_set_think (userEntity_t *self, think_t think_func, char *caller)
{
	int		i;
	
	if (self->flags & (FL_MONSTER + FL_BOT + FL_CLIENT))
	{
		playerHook_t *hook = (playerHook_t *) self->userHook;

		if (!hook->caller [0] || stricmp (hook->caller [0], caller))
		{
			for (i = 9; i--; i > 0)
				hook->caller [i] = hook->caller [i - 1];
			hook->caller [0] = caller;
		}

		self->nextthink = gstate->time + hook->think_time;
	}
	else
		self->nextthink = gstate->time + THINK_TIME;

	self->think = think_func;
}

///////////////////////////////////////////////////////////////////////////////
//	ai_client_near
//
//	determines if a client or viewentity is within
//	hook->active_distance and returns TRUE if so
///////////////////////////////////////////////////////////////////////////////

int	ai_client_near (userEntity_t *self)
{
	userEntity_t	*head;
	playerHook_t	*hook = (playerHook_t *) self->userHook;
	int				i;

	head = alist_FirstEntity (client_list);

	for (i = 0; head != NULL; i++)
	{
		if (qv_dist (self->s.origin, head->s.origin) < hook->active_distance)
			return	TRUE;
/*
		tr = gstate->trace_q2 (head->s.origin, NULL, NULL, self->s.origin, self, MASK_OPAQUE);
		if (tr.fraction == 1.0 || tr.ent == self)
			return	TRUE;
*/
		head = alist_NextEntity (client_list);
	}

	return	FALSE;
}

///////////////////////////////////////////////////////////////////////////////
//	ai_set_ideal
//
//	only sets ideal yaw so that hierarchical models aren't screwed
///////////////////////////////////////////////////////////////////////////////

int	ai_set_ideal (userEntity_t *self)
{
	playerHook_t	*hook = (playerHook_t *) self->userHook;
	Vector			dir, ang;

	if (hook->ai_flags & AI_TURN)
		//	never reset ideal_ang if in turning
		return	TRUE;

	if (hook->dflags & DFL_FACEENEMY)
	{
		if (!self->enemy)
			return	FALSE;
		dir = Vector (self->enemy->s.origin) - Vector (self->s.origin);
	}
	else if (hook->dflags & DFL_FACEOWNER)
	{
		if (!self->enemy)
			return	FALSE;
		dir = Vector (hook->owner->s.origin) - Vector (self->s.origin);
	}
	else
	{
		if (!self->goalentity)
			return	FALSE;
		dir = Vector (self->goalentity->s.origin) - Vector (self->s.origin);
	}
	
	dir.Normalize ();

	com->VecToAngles (ang, dir);
	self->ideal_ang [YAW] = ang.y;

	if (hook->dflags & DFL_FACEPITCH)
	{
		self->ideal_ang [PITCH] = ang.x;
			
		if (hook->dflags & DFL_HIERARCHICAL && self->ideal_ang [PITCH] >= 180)
			self->ideal_ang [PITCH] = self->ideal_ang [PITCH] - 360;

		self->ideal_ang [PITCH] /= 3.0;
	}

	return	TRUE;
}

///////////////////////////////////////////////////////////////////////////////
//	ai_face_goal
//
///////////////////////////////////////////////////////////////////////////////

void	ai_face_goal	(userEntity_t *self)
{
	Vector			dir;
	float			yaw;
	playerHook_t	*hook = (playerHook_t *) self->userHook;

	if (!(hook->dflags & DFL_FACENONE))
		return;

	dir = Vector (self->goalentity->s.origin) - Vector (self->s.origin);
	dir.Normalize ();

	if (hook->dflags & DFL_HIERARCHICAL)
	{
/*
		com->VecToAngles (ang, dir);
		self->ideal_ang [YAW] = ang.y;
		if (ang.x >= 180.0)
			self->ideal_ang [PITCH] = (ang.x - 360) / 3;
		else
			self->ideal_ang [PITCH] = ang.x / 3;

		com->ChangePitch (self);
*/
//		self->s.angles [PITCH] = 0;
	}

	self->ideal_ang [YAW] = com->VecToYaw (dir);

	com->ChangeYaw (self);
}

///////////////////////////////////////////////////////////////////////////////
//	ai_face_enemy
//
///////////////////////////////////////////////////////////////////////////////

void	ai_face_enemy	(userEntity_t *self)
{
	Vector			dir, ang;
	playerHook_t	*hook = (playerHook_t *) self->userHook;

	if (!(hook->dflags & DFL_FACENONE))
		return;

	dir = Vector (self->enemy->s.origin) - Vector (self->s.origin);
	dir.Normalize ();

	if (hook->dflags & DFL_HIERARCHICAL)
	{
/*
		com->VecToAngles (ang, dir);
		self->ideal_ang [YAW] = ang.y;
		if (ang.x >= 180.0)
			self->ideal_ang [PITCH] = (ang.x - 360) / 3;
		else
			self->ideal_ang [PITCH] = ang.x / 3;

		com->ChangePitch (self);
*/
		self->s.angles [PITCH] = 0;
	}

	self->ideal_ang [YAW] = com->VecToYaw (dir);

	com->ChangeYaw (self);
}

///////////////////////////////////////////////////////////////////////////////
//	ai_face_owner
//
///////////////////////////////////////////////////////////////////////////////

void	ai_face_owner (userEntity_t *self)
{
	Vector			dir, ang;
	playerHook_t	*hook = (playerHook_t *) self->userHook;

	if (!(hook->dflags & DFL_FACENONE))
		return;

	dir = Vector (hook->owner->s.origin) - Vector (self->s.origin);
	dir.Normalize ();

	if (hook->dflags & DFL_HIERARCHICAL)
	{
		com->VecToAngles (ang, dir);
		self->ideal_ang [YAW] = ang.y;
		if (ang.x >= 180.0)
			self->ideal_ang [PITCH] = (ang.x - 360) / 3;
		else
			self->ideal_ang [PITCH] = ang.x / 3;

		com->ChangePitch (self);
	}
	else
		self->ideal_ang [YAW] = com->VecToYaw (dir);

	com->ChangeYaw (self);
}

////////////////////////////////////////////////////////////////////////////
//	ai_max_height
//
//	returns the maximum height an entity can jump to starting with upward
//	velocity v0
//
//	accounts for self->gravity variations
////////////////////////////////////////////////////////////////////////////

float	ai_max_height (userEntity_t *self, float v0)
{
	float	g;

	if (self->gravity != 0.0)
		g = gstate->GetCvar ("sv_gravity") * self->gravity;
	else
		g = gstate->GetCvar ("sv_gravity");

	//	scale by .75 to account for variance in frame rates
	return	((v0 * v0) / (2 * g)) * 0.75;
}
////////////////////////////////////////////////////////////////////////////
//	ai_jump_vel
//
//	sets the correct forward velocity need to jump distxy units forward and
//	land distz units below current position
/////////////////////////////////////////////////////////////////////////

float	ai_jump_vel	(userEntity_t *self, float distxy, float distz, float up_vel)
{
	float			t, g, y0, y, root;
	playerHook_t	*hook = (playerHook_t *) self->userHook;

	//	FIXME:	use gstate->gravity instead of GetCVar!!  Slow!!!
	if (self->gravity != 0.0)
		g = gstate->GetCvar ("sv_gravity") * self->gravity;
	else
		g = gstate->GetCvar ("sv_gravity");

//	hook->upward_vel = up_vel;

	y0 = self->s.origin [2];
	y = self->s.origin [2] + distz;

	root = y - y0;
	root = (up_vel * up_vel) - (2 * g * (y - y0));

	// negative value means we're trying to jump to a higher ledge!
	if (root < 0)
		return	root;

	t = (up_vel + sqrt (root)) / g;

	ai_debug_print (self, "ai_jump_vel: air time=%.3f\n", t);	
	
	return	distxy / t;
/*
	// only works for y = y0
	t = 2.0 * (up_vel / g);
	
	ai_debug_print (self, "ai_jump_vel: air time=%.3f\n", t);	
	return	distxy / t;
*/
};
