//-----------------------------------------------------------------------------
//
//  $Logfile:: /Quake 2 Engine/Sin/code/game/steering.cpp                     $
// $Revision:: 21                                                             $
//   $Author:: Markd                                                          $
//     $Date:: 11/18/98 8:53p                                                 $
//
// Copyright (C) 1998 by Ritual Entertainment, Inc.
// All rights reserved.
//
// This source may not be distributed and/or modified without
// expressly written permission by Ritual Entertainment, Inc.
//
// $Log:: /Quake 2 Engine/Sin/code/game/steering.cpp                          $
// 
// 21    11/18/98 8:53p Markd
// backed out change for NearestNode
// 
// 20    11/18/98 7:46p Markd
// made it so if path finding fails on the first node it will try without the
// current bounds and try again
// 
// 19    10/26/98 2:19p Markd
// Only jump if last_jump_time is less than level.time
// 
// 18    10/25/98 4:58a Jimdose
// added goalnode to chase so that when going to a pathnode we don't do a seek
// to the node after the path is done
// made chase's wander code check for ground
// 
// 17    10/23/98 11:33p Markd
// fixed some chase code stuff
// 
// 16    10/23/98 6:24p Jimdose
// FindCurrentNode wasn't advancing the node pointer, effectively duplicating
// the second node, causing the guys to sometimes spin on their path
// 
// 15    10/23/98 5:13a Jimdose
// Fixed followpath so that they don't turn the wrong direction at the end of
// the path
// 
// 14    10/19/98 11:45p Jimdose
// added FindCurrentNode to FollowPath
// 
// 13    10/18/98 3:21a Jimdose
// Simplified FollowPath
// 
// 12    10/17/98 4:43p Markd
// made ChooseRandomDirection choose backup as its default move
// 
// 11    10/16/98 1:55a Jimdose
// Added another NextNode function for finding the next node after the
// specified node.
// Made FollowPath check the node following the jump node to see if it should
// jump
// 
// 10    10/14/98 9:43p Markd
// Bullet proofed nextnode stuff in AI_JUMP for steering
// 
// 9     10/14/98 9:03p Markd
// tweaked jumping stuff
// 
// 8     10/14/98 5:21p Markd
// Added Jumping to the Chase steering method
// 
// 7     10/10/98 5:01p Markd
// Changed trace masks to edict->clipmasks
// 
// 6     10/10/98 4:35p Markd
// Fixed some avoidvec behavior for the characters
// 
// 5     10/04/98 5:31p Markd
// Fixed chase problems
// 
// 4     9/22/98 5:11p Jimdose
// Added Turn
// Added wander code to chase
// 
// 3     9/22/98 1:56a Jimdose
// Added ShowInfo
// Added ObstacleAvoidance2 temporarily
// 
// 2     9/18/98 10:57p Jimdose
// Separated from Behavior.cpp
// 
// 1     9/18/98 5:01p Jimdose
//
// DESCRIPTION:
// Steering behaviors for AI.
// 

#include "g_local.h"
#include "steering.h"
#include "actor.h"

/****************************************************************************

  Steering Class Definition

****************************************************************************/

CLASS_DECLARATION( Listener, Steering, NULL );

ResponseDef Steering::Responses[] =
	{
		{ NULL, NULL }
	};

Steering::Steering()
	{
	steeringforce = vec_zero;

	origin = vec_zero;
	movedir  = vec_zero;
	maxspeed = 320;
	}

void Steering::ShowInfo
	(
	Actor &self
	)

	{
   gi.printf( "steeringforce: ( %f, %f, %f )\n", steeringforce.x, steeringforce.y, steeringforce.z );
   gi.printf( "origin: ( %f, %f, %f )\n", origin.x, origin.y, origin.z );
   gi.printf( "movedir: ( %f, %f, %f )\n", movedir.x, movedir.y, movedir.z );
   gi.printf( "maxspeed: %f\n", maxspeed );
	}

void Steering::Begin
	(
	Actor &self
	)

	{
	}

qboolean	Steering::Evaluate
	(
	Actor &self
	)

	{
	return false;
	}

void Steering::End
	(
	Actor &self
	)

	{
	}

void Steering::DrawForces
	(
	void
	)

	{
	G_Color3f( 0.3, 0.5, 1 );
	G_BeginLine();
	G_Vertex( origin );
	G_Vertex( origin + steeringforce * FRAMETIME );
	G_EndLine();

	G_Color3f( 1, 0, 1 );
	G_BeginLine();
	G_Vertex( origin );
	G_Vertex( origin + movedir * maxspeed * FRAMETIME );
	G_EndLine();
	}

void Steering::SetPosition
	(
	Vector pos
	)

	{
	origin = pos;
	}

void Steering::SetDir
	(
	Vector dir
	)

	{
	movedir = dir;
	}

void Steering::SetMaxSpeed
	(
	float speed
	)

	{
	maxspeed = speed;
	}


void Steering::ResetForces
	(
	void
	)

	{
	steeringforce = vec_zero;
	}

/****************************************************************************

  Seek Class Definition

****************************************************************************/

CLASS_DECLARATION( Steering, Seek, NULL );

ResponseDef Seek::Responses[] =
	{
		{ NULL, NULL }
	};

Seek::Seek()
	{
	targetposition = vec_zero;
	targetvelocity = vec_zero;
	}

void Seek::SetTargetPosition
	(
	Vector pos
	)

	{
	targetposition = pos;
	}

void Seek::SetTargetVelocity
	(
	Vector vel
	)

	{
	targetvelocity = vel;
	}

void Seek::ShowInfo
	(
	Actor &self
	)

	{
   Steering::ShowInfo( self );

   gi.printf( "\ntargetposition: ( %f, %f, %f )\n", targetposition.x, targetposition.y, targetposition.z );
   gi.printf( "targetvelocity: ( %f, %f, %f )\n", targetvelocity.x, targetvelocity.y, targetvelocity.z );
	}

qboolean	Seek::Evaluate
	(
	Actor &self
	)

	{
	Vector	predictedposition;
	Vector	dir;
   Vector   xydelta;
	Vector	delta;
	Vector	ang1;
	Vector	ang2;
	float		dist;
   float    xydist;
	float		l;

	ResetForces();

	delta = targetposition - origin;
	dist = delta.length();
   //
   // null out z component
   //
   delta.z = 0;
	xydist = delta.length();

	predictedposition = targetposition + targetvelocity * ( dist / maxspeed );

	dir = predictedposition - origin;
	dir.normalize();

	ang1 = dir.toAngles();
	ang2 = movedir.toAngles();

	steeringforce.x = ang1.x - ang2.x;
	if ( steeringforce.x <= -180 )
		{
		steeringforce.x += 360;
		}
	if ( steeringforce.x >= 180 )
		{
		steeringforce.x -= 360;
		}

	steeringforce.y = ang1.y - ang2.y;
	if ( steeringforce.y <= -180 )
		{
		steeringforce.y += 360;
		}
	if ( steeringforce.y >= 180 )
		{
		steeringforce.y -= 360;
		}

	// if we're nearly there, turn directly toward our goal
	if ( xydist > self.movespeed )
		{
		if ( fabs( steeringforce.x ) > 1 )
			{
			steeringforce.x *= 0.4;
			}

		if ( fabs( steeringforce.y ) > 1 )
			{
			steeringforce.y *= 0.4;
			}
		}
	else 
		{
		l = self.total_delta.length();
		if ( xydist <= l )
			{
         //steeringforce = vec_zero;
			self.total_delta = self.animdir * xydist;
			return false;
			}
		}

	steeringforce.z = 0;
	
	return true;
	}

/****************************************************************************

  ObstacleAvoidance Class Definition

****************************************************************************/

CLASS_DECLARATION( Steering, ObstacleAvoidance, NULL );

ResponseDef ObstacleAvoidance::Responses[] =
	{
		{ NULL, NULL }
	};

ObstacleAvoidance::ObstacleAvoidance()
   {
   avoidwalls = true;
   }

void ObstacleAvoidance::AvoidWalls
   (
   qboolean avoid
   )

   {
   avoidwalls = avoid;
   }

void ObstacleAvoidance::ShowInfo
	(
	Actor &self
	)

	{
   Steering::ShowInfo( self );

   gi.printf( "\navoidwalls: %d\n", avoidwalls );
	}

qboolean	ObstacleAvoidance::Evaluate
	(
	Actor &self
	)

	{
	Vector	predictedposition;
	Vector	normal;
	Vector	angles;
	Vector	dir;
	Vector	right;
	Vector	delta;
	float		urgency;
	float		dot;
	trace_t	tracef;
#if 0
	trace_t	tracel;
	trace_t	tracer;
	Vector	leftposition;
	Vector	rightposition;
#endif
	Entity	*ent;

	ResetForces();

	angles = self.movedir.toAngles();
	angles.AngleVectors( NULL, &right, NULL );

   origin = self.worldorigin;
   origin.z += 1;
	predictedposition = origin + self.movedir * self.movespeed;//maxspeed;
#if 0
	leftposition = origin - right * 8;
	rightposition = origin + right * 8;
#endif

#if 0
	G_Color3f( 1, 1, 1 );
	G_BeginLine();
	G_Vertex( origin );
	G_Vertex( predictedposition );
	G_Vertex( origin );
	G_Vertex( leftposition );
	G_Vertex( origin );
	G_Vertex( rightposition );
	G_EndLine();
#endif

	tracef = G_Trace( origin, self.mins, self.maxs, predictedposition, &self, self.edict->clipmask, "ObstacleAvoidance forward" );
#if 0
	tracel = G_Trace( origin, self.mins, self.maxs, leftposition, &self, MASK_PLAYERSOLID, "ObstacleAvoidance left" );
	tracer = G_Trace( origin, self.mins, self.maxs, rightposition, &self, MASK_PLAYERSOLID, "ObstacleAvoidance right" );
   if ( tracel.fraction < 1 )
		{
		urgency = 1.1 - tracel.fraction;
		normal = tracel.plane.normal;
		ent = tracel.ent->entity;
		steeringforce = Vector( 0, -90, 0 );;
		}
	else if ( tracer.fraction < 1 )
		{
		urgency = 1.1 - tracer.fraction;
		normal = tracer.plane.normal;
		ent = tracer.ent->entity;
		steeringforce = Vector( 0, 90, 0 );;
		}
	else 
#endif
	if ( tracef.fraction < 1 )
		{
		urgency = 1.0 - tracef.fraction;
		normal = tracef.plane.normal;
		ent = tracef.ent->entity;
		if ( ent->getSolidType() != SOLID_BSP )
			{
			dot = -( right * ( ent->worldorigin - self.worldorigin ) );
			}
		else
			{
         if ( !avoidwalls )
            {
   			return true;
            }
         
         dot = right * normal;
			}

		if ( dot < 0 )
			{
			// turn left
			steeringforce = Vector( 0, 90, 0 );;
			}
		else
			{
			// turn right
			steeringforce = Vector( 0, -90, 0 );;
			}
		}
	else
		{
		return true;
		}

	steeringforce *= urgency;

	return true;
	}

/****************************************************************************

  ObstacleAvoidance2 Class Definition

****************************************************************************/

CLASS_DECLARATION( Steering, ObstacleAvoidance2, NULL );

ResponseDef ObstacleAvoidance2::Responses[] =
	{
		{ NULL, NULL }
	};

ObstacleAvoidance2::ObstacleAvoidance2()
   {
   avoidwalls = true;
   }

void ObstacleAvoidance2::AvoidWalls
   (
   qboolean avoid
   )

   {
   avoidwalls = avoid;
   }

void ObstacleAvoidance2::ShowInfo
	(
	Actor &self
	)

	{
   Steering::ShowInfo( self );

   gi.printf( "\navoidwalls: %d\n", avoidwalls );
	}

qboolean	ObstacleAvoidance2::Evaluate
	(
	Actor &self
	)

	{
	Vector	predictedposition;
	Vector	normal;
	Vector	angles;
	Vector	dir;
	Vector	right;
	Vector	delta;
	float		urgency;
	float		dot;
	trace_t	tracef;
#if 0
	trace_t	tracel;
	trace_t	tracer;
	Vector	leftposition;
	Vector	rightposition;
#endif
	Entity	*ent;

	ResetForces();

	angles = self.movedir.toAngles();
	angles.AngleVectors( NULL, &right, NULL );

   origin = self.worldorigin;
   origin.z += 1;
	predictedposition = origin + self.movedir * self.movespeed;//maxspeed;
#if 0
	leftposition = origin - right * 8;
	rightposition = origin + right * 8;
#endif

#if 0
	G_Color3f( 1, 1, 1 );
	G_BeginLine();
	G_Vertex( origin );
	G_Vertex( predictedposition );
	G_Vertex( origin );
	G_Vertex( leftposition );
	G_Vertex( origin );
	G_Vertex( rightposition );
	G_EndLine();
#endif

	tracef = G_Trace( origin, self.mins, self.maxs, predictedposition, &self, self.edict->clipmask, "ObstacleAvoidance2 forward" );
#if 0
	tracel = G_Trace( origin, self.mins, self.maxs, leftposition, &self, MASK_PLAYERSOLID, "ObstacleAvoidance2 left" );
	tracer = G_Trace( origin, self.mins, self.maxs, rightposition, &self, MASK_PLAYERSOLID, "ObstacleAvoidance2 right" );
   if ( tracel.fraction < 1 )
		{
		urgency = 1.1 - tracel.fraction;
		normal = tracel.plane.normal;
		ent = tracel.ent->entity;
		steeringforce = Vector( 0, -90, 0 );;
		}
	else if ( tracer.fraction < 1 )
		{
		urgency = 1.1 - tracer.fraction;
		normal = tracer.plane.normal;
		ent = tracer.ent->entity;
		steeringforce = Vector( 0, 90, 0 );;
		}
	else 
#endif
	if ( tracef.fraction < 1 )
		{
		urgency = 1.0 - tracef.fraction;
		normal = tracef.plane.normal;
		ent = tracef.ent->entity;
		if ( ent->getSolidType() != SOLID_BSP )
			{
			dot = -( right * ( ent->worldorigin - self.worldorigin ) );
			}
		else
			{
         if ( !avoidwalls )
            {
   			return true;
            }
         
         dot = right * normal;
			}

		if ( dot < 0 )
			{
			// turn left
			steeringforce = Vector( 0, 22, 0 ) * urgency;
			}
		else
			{
			// turn right
			steeringforce = Vector( 0, 22, 0 ) * urgency;
			}
		}
	else
		{
		return true;
		}

	steeringforce *= urgency;

	return true;
	}

/****************************************************************************

  FollowPath Class Definition

****************************************************************************/

CLASS_DECLARATION( Steering, FollowPath, NULL );

ResponseDef FollowPath::Responses[] =
	{
		{ NULL, NULL }
	};

FollowPath::FollowPath()
	{
	path = NULL;
   currentNode = NULL;
	}

FollowPath::~FollowPath()
	{
   currentNode = NULL;
   if ( path )
      {
	   delete path;
      }
	}

void FollowPath::FindCurrentNode
   (
   Actor &self
   )

   {
   // Sometimes the second node on the path is the proper node to start from.
   // This happens because instead of creating the shortest path from the actor,
   // we create the shortest path from his nearest node.  Often, this creates a
   // path where he may already be further along the path than the first node,
   // causing him to "go back" along the path.  By checking if we can get to the
   // second node, we get rid of the backtracking.
   PathNode *node;

   if ( !path )
      {
      currentNode = NULL;
      return;
      }

   currentNode = path->NextNode();
   if ( path->NumNodes() < 2 )
      {
      return;
      }

   node = path->GetNode( 2 );
   if ( self.CanMoveTo( node->worldorigin ) )
      {
      currentNode = path->NextNode();
      }
   }

void FollowPath::SetPath
	(
	Path *newpath
	)

	{
	if ( path )
		{
		delete path;
		}

   currentNode = NULL;

   path = newpath;
	}

Path *FollowPath::SetPath
	(
	Actor &self,
	Vector from,
	Vector to
	)

	{
	PathNode *goal;
	PathNode *node;
	StandardMovePath find;

	if ( path )
		{
		delete path;
		path = NULL;
		}

   currentNode = NULL;

   goal = PathManager.NearestNode( to, &self );
	if ( !goal )
		{
	   return NULL;
		}

	node = PathManager.NearestNode( from, &self );
	if ( !node || ( goal == node ) )
		{
		return NULL;
		}

	find.heuristic.setSize( self.size );
	find.heuristic.entnum = self.entnum;

	path = find.FindPath( node, goal );

	return path;
	}

void FollowPath::DrawForces
	(
	void
	)

	{
   seek.DrawForces();
	}

qboolean FollowPath::DoneWithPath
	(
	Actor &self
	)

	{
	if ( !path )
		{
		return true;
		}

	return ( currentNode == NULL );
	}

void FollowPath::ShowInfo
	(
	Actor &self
	)

	{
   Steering::ShowInfo( self );

   if ( path )
      {
      gi.printf( "\npath : ( %f, %f, %f ) to ( %f, %f, %f )\n", 
         path->Start()->worldorigin.x, path->Start()->worldorigin.y, path->Start()->worldorigin.z, 
         path->End()->worldorigin.x, path->End()->worldorigin.y, path->End()->worldorigin.z );
      }
   else
      {
      gi.printf( "\npath : NULL\n" );
      }

   gi.printf( "seek:\n" );
   seek.ShowInfo( self );

   if ( currentNode )
      {
      gi.printf( "currentNode: ( %f, %f, %f )\n", 
         currentNode->worldorigin.x, currentNode->worldorigin.y, currentNode->worldorigin.z );
      }
   else
      {
      gi.printf( "currentNode: NULL\n" );
      }
	}

void FollowPath::Begin
	(
	Actor &self
	)

	{
	seek.Begin( self );
	}

qboolean	FollowPath::Evaluate
	(
	Actor &self
	)

	{
	PathNode *lastnode;
   Vector delta;
   Vector targetpos;

   ResetForces();

   if ( !path )
      {
      return false;
      }
   
   // the first time we come through here with a path, currentNode is NULL.
   if ( !currentNode )
      {
      FindCurrentNode( self );
      if ( !currentNode )
         {
         delete path;
         path = NULL;
         return false;
         }
      }

   targetpos = currentNode->worldorigin;

   // check if the remaining distance is less than the
   // distance we'll travel this frame.
   delta = targetpos - self.worldorigin;

   // check if the squared distance remaining is less than 
   // the squared distance we'll travel
   if ( delta * delta <= self.frame_delta * self.frame_delta )
      {
      lastnode = currentNode;
      currentNode = path->NextNode();

      // check if we should jump to our next node
      if ( currentNode && ( lastnode->nodeflags & AI_JUMP ) && ( currentNode->targetname == lastnode->target ) )
         {
         if ( self.last_jump_time < level.time )
            {
      	   self.SetVariable( "jumptarget", lastnode->target.c_str() );
            self.ForceAction( "jump" );
            }

			return true;
         }

      // if we're not done with the path, steer toward the next node
      if ( currentNode )
         {
         targetpos = currentNode->worldorigin;
         }
      else
         {
         delete path;
         path = NULL;

         return false;
         }
		}

   // steer toward our next path node
	seek.SetTargetPosition( targetpos );
	seek.SetTargetVelocity( vec_zero );
	seek.SetMaxSpeed( self.movespeed );
	seek.SetPosition( origin );
	seek.SetDir( self.movedir );
	seek.Evaluate( self );

	steeringforce = seek.steeringforce;

	return ( currentNode != NULL );
	}

void FollowPath::End
	(
	Actor &self
	)

	{
	seek.End( self );
	}

/****************************************************************************

  Turn Class Definition

****************************************************************************/

CLASS_DECLARATION( Steering, Turn, NULL );

ResponseDef Turn::Responses[] =
	{
		{ NULL, NULL }
	};

Turn::Turn()
	{
	dir = Vector( 1, 0, 0 );
	mode = 0;
	ent = NULL;
	}

void Turn::SetDirection
	(
	float yaw
	)

	{
	Vector ang;

	ang = Vector( 0, yaw, 0 );
	this->yaw = anglemod( yaw );
	ang.AngleVectors( &dir, NULL, NULL );
	mode = 1;
	}

void Turn::SetTarget
	(
	Entity *ent
	)

	{
	this->ent = ent;
	mode = 2;
	}

void Turn::ShowInfo
	(
	Actor &self
	)

	{
   Steering::ShowInfo( self );

   gi.printf( "\nseek:\n" );
   seek.ShowInfo( self );

   if ( ent )
      {
      gi.printf( "\nent: #%d '%s'\n", ent->entnum, ent->targetname.c_str() );
      }
   else
      {
      gi.printf( "\nent: NULL\n" );
      }
   
   gi.printf( "dir: ( %f, %f, %f )\n", dir.x, dir.y, dir.z );
   gi.printf( "yaw: %f\n", yaw );
   gi.printf( "mode: %d\n", mode );
	}

void Turn::Begin
	(
	Actor &self
	)

	{
	seek.Begin( self );
	}

extern float angledist( float ang );

qboolean	Turn::Evaluate
	(
	Actor &self
	)

	{
	Vector delta;
	float ang;

	switch( mode )
		{
		case 1 :
			ang = angledist( yaw - self.angles.yaw() );
			if ( fabs( ang ) < 1 )
				{
				steeringforce = Vector( 0, ang, 0 );
				return false;
				}

			seek.SetTargetPosition( self.worldorigin + dir );
			seek.SetTargetVelocity( vec_zero );
			break;

		case 2 :
			if ( !ent )
				{
				return false;
				}

			delta = ent->worldorigin - self.worldorigin;
			yaw = delta.toYaw();
			//if ( self.angles.yaw() == yaw )
			//	{
			//	return false;
			//	}

			seek.SetTargetPosition( ent->worldorigin );
			seek.SetTargetVelocity( vec_zero );
			break;

		default :
			return false;
		}

	seek.SetPosition( self.worldorigin );
	seek.SetDir( self.movedir );
	seek.SetMaxSpeed( self.movespeed );
	seek.Evaluate( self );
	//seek.DrawForces();

	steeringforce = seek.steeringforce;

	return true;
	}

void Turn::End
	(
	Actor &self
	)

	{
	seek.End( self );
	}

/****************************************************************************

  Chase Class Definition

****************************************************************************/

CLASS_DECLARATION( Steering, Chase, NULL );

ResponseDef Chase::Responses[] =
	{
		{ NULL, NULL }
	};

Chase::Chase()
	{
	goalent = NULL;
	goal = vec_zero;
	goalnode = NULL;
   usegoal = false;
   newpathrate = 2;
	}

void Chase::SetPath
	(
	Path *newpath
	)

	{
	follow.SetPath( newpath );
	path = newpath;	
	}

void Chase::SetGoalPos
	(
	Vector goalpos
	)

	{
	goal = goalpos;
   usegoal = true;
	goalent = NULL;
   goalnode = NULL;
	}

void Chase::SetGoal
	(
	PathNode *node
	)

	{
   goalnode = node;
   usegoal = false;
   goalent = NULL;
	}

void Chase::SetTarget
	(
	Entity *ent
	)

	{
	goalent = ent;
   goalnode = NULL;
   usegoal = false;
	}

void Chase::SetPathRate
   (
   float rate
   )

   {
   newpathrate = rate;
   }

void Chase::ShowInfo
	(
	Actor &self
	)

	{
   Steering::ShowInfo( self );

   gi.printf( "\nseek:\n" );
   seek.ShowInfo( self );

   gi.printf( "\nfollow:\n" );
   follow.ShowInfo( self );

   gi.printf( "\nnextpathtime: %f\n", nextpathtime );

   if ( path )
      {
      gi.printf( "\npath : ( %f, %f, %f ) to ( %f, %f, %f )\n", 
         path->Start()->worldorigin.x, path->Start()->worldorigin.y, path->Start()->worldorigin.z, 
         path->End()->worldorigin.x, path->End()->worldorigin.y, path->End()->worldorigin.z );
      }
   else
      {
      gi.printf( "\npath : NULL\n" );
      }

   gi.printf( "goal: ( %f, %f, %f )\n", goal.x, goal.y, goal.z );

   if ( goalent )
      {
      gi.printf( "\ngoalent: #%d '%s'\n", goalent->entnum, goalent->targetname.c_str() );
      }
   else
      {
      gi.printf( "\ngoalent: NULL\n" );
      }

   if ( goalnode )
      {
      gi.printf( "\ngoalnode: #%d '%s' ( %f, %f, %f )\n", goalnode->nodenum, goalnode->targetname.c_str(),
         goalnode->worldorigin.x, goalnode->worldorigin.y, goalnode->worldorigin.z );
      }
   else
      {
      gi.printf( "\ngoalnode: NULL\n" );
      }

   gi.printf( "avoid:\n" );
   avoid.ShowInfo( self );

   gi.printf( "\ntime: %f\n", avoidtime );

   gi.printf( "usegoal: %d\n", usegoal );
   gi.printf( "newpathrate: %f\n", newpathrate );
   gi.printf( "wander: %d\n", wander );
   gi.printf( "stuck: %d\n", stuck );
   gi.printf( "avoidvec : ( %f, %f, %f )\n", avoidvec.x, avoidvec.y, avoidvec.z );
	}

void Chase::Begin
	(
	Actor &self
	)

	{
	nextpathtime = 0;
	path = NULL;
	seek.Begin( self );
	follow.Begin( self );
   avoid.AvoidWalls( false );
	avoid.Begin( self );
 	turnto.Begin( self );
   anim = self.animname;
   stuck = 0;
   wander = 0;
	}

Vector Chase::ChooseRandomDirection
   (
   Actor &self
   )

   {
   Vector dir;
   Vector ang;
   Vector bestdir;
   float bestfraction;
   trace_t trace;
   trace_t groundtrace;
   int i;
   int j;
   int t;
   int u;
   Vector s;
   Vector start;
   Vector end;
   Vector groundend;

	s = Vector( 0, 0, STEPSIZE );
	start = self.worldorigin + s;

   // quantize to nearest 45 degree
   u = ( ( int )( self.worldangles.y * ( 1 / 45 ) + 22.5 ) ) * 45;
   bestfraction = -1;
   //
   // in case we don't find anything!
   //
   bestdir = self.worldorigin - ( Vector( self.orientation[ 0 ] ) * 100 );

   for( i = 0; i <= 180; i += 20 )
      {
      if ( rand() < 0.3 )
         {
         i += 20;
         }
      t = i;
      if ( rand() < 0.5 )
         {
         // sometimes we choose left first, other times right.
         t = -t;
         }
      for( j = -1; j < 2; j += 2 )
         {
         if ( ( j == 1 ) && ( i == 180 ) )
            {
            ang.y = self.worldangles.y + ( t * j );
            }
         else
            {
            ang.y = u + t * j;
            }

         ang.AngleVectors( &dir, NULL, NULL );

         end = self.worldorigin + dir * 140 + s;
         trace = G_Trace( start, self.mins, self.maxs, end, &self, 
            self.edict->clipmask, "Chase::ChooseRandomDirection 1" );
         if ( ( trace.fraction > bestfraction ) && ( !trace.startsolid ) && !( trace.allsolid ) )
            {
            if ( trace.endpos != avoidvec )
               {
               // check if we're near the ground 
               end = self.worldorigin + dir * 32 + s;
               groundend = end;
               groundend.z -= STEPSIZE * 2;
               groundtrace = G_Trace( end, self.mins, self.maxs, groundend, &self, 
                  self.edict->clipmask, "Chase::ChooseRandomDirection 2" );
               if ( groundtrace.fraction != 1 )
                  {
                  bestdir = trace.endpos;
                  bestfraction = trace.fraction;
                  }
               }
            }

         if ( i == 0 )
            {
            break;
            }
         }
      }

   return bestdir;
   }

qboolean	Chase::Evaluate
	(
	Actor &self
	)

	{
	qboolean result;
	trace_t trace;

	if ( !usegoal && !goalnode && ( !goalent || goalent->deadflag ) )
		{
		return false;
		}

	ResetForces();

   if ( !wander )
      {
      if ( self.lastmove == STEPMOVE_OK )
         {
         stuck = 0;
         }
      else
         {
         stuck++;
         if ( stuck >= 2 )
            {
            stuck = 3;
            wander = 1;
            }
         }
      }

   switch( wander )
      {
      case 1 :
         stuck--;
         if ( !stuck )
            {
            wander = 0;
            nextpathtime = 0;
            path = NULL;
            break;
            }
         wanderstart = self.worldorigin;
         avoidvec = ChooseRandomDirection( self );
         wandertime = level.time + 1;
         wander = 2;
         
      case 2 :
		   seek.SetTargetPosition( avoidvec );
		   seek.SetTargetVelocity( vec_zero );
		   seek.SetPosition( self.worldorigin );
		   seek.SetDir( self.movedir );
		   seek.SetMaxSpeed( self.movespeed );
		   result = seek.Evaluate( self );
         if ( result )
            {
            if ( ( level.time > wandertime ) && ( self.lastmove != STEPMOVE_OK ) )
               {
               wander = 0;
               stuck = 0;
               }
         	self.Accelerate( seek.steeringforce );
            return true;
            }
         wander = 0;
         nextpathtime = 0;
         path = NULL;
         break;

         //self.SetAnim( "idle" );
         turnto.SetDirection( ( wanderstart - self.worldorigin ).toYaw() );
         wander = 3;
         wandertime = level.time + 1;

      case 3 :
         if ( level.time < wandertime )
            {
            turnto.Evaluate( self );
         	self.Accelerate( turnto.steeringforce );
            return true;
            }

         //self.SetAnim( anim );
         wander = 0;
         nextpathtime = 0;
         path = NULL;
         break;
      }

	if ( path && follow.DoneWithPath( self ) )
		{
		path = NULL;
		nextpathtime = 0;
		}

	if ( goalent && ( goalent->edict->solid != SOLID_NOT ) && ( goalent->edict->solid != SOLID_TRIGGER ) )
		{
		trace = G_Trace( self.worldorigin, self.mins, self.maxs, self.worldorigin + 
			Vector( self.orientation[ 0 ] ) * self.movespeed * 0.1, &self, self.edict->clipmask, "Chase" );
		if ( trace.ent->entity == goalent )
			{
			return false;
			}
      }

	if ( nextpathtime < level.time )
		{
		nextpathtime = level.time + newpathrate;
      if ( goalnode )
         {
         path = follow.SetPath( self, self.worldorigin, goalnode->worldorigin );
         }
      else if ( goalent )
			{
			path = follow.SetPath( self, self.worldorigin, goalent->worldorigin );
			}
		else
			{
			path = follow.SetPath( self, self.worldorigin, goal );
			}
		}

	if ( !path )
		{
      if ( goalnode )
         {
			seek.SetTargetPosition( goalnode->worldorigin );
			seek.SetTargetVelocity( vec_zero );
         }
		else if ( goalent )
			{
			seek.SetTargetPosition( goalent->worldorigin );
			seek.SetTargetVelocity( goalent->velocity );
			}
		else
			{
			seek.SetTargetPosition( goal );
			seek.SetTargetVelocity( vec_zero );
			}

		seek.SetPosition( self.worldorigin );
		seek.SetDir( self.movedir );
		seek.SetMaxSpeed( self.movespeed );
		result = seek.Evaluate( self );
		//seek.DrawForces();

		steeringforce = seek.steeringforce;

		if ( !result )
			{
			return false;
			}
		}
	else
		{
		follow.SetPosition( self.worldorigin );
		follow.SetDir( self.movedir );
		follow.SetMaxSpeed( self.movespeed );
      if ( !follow.Evaluate( self ) )
         {
         nextpathtime = 0;
         if ( goalnode )
            {
            self.frame_delta = goalnode->worldorigin - self.worldorigin;
            return false;
            }
         }
		//follow.DrawForces();
		steeringforce = follow.steeringforce;
		}

	if ( avoidtime < level.time )
		{
		avoid.SetMaxSpeed( self.movespeed );
		avoid.SetPosition( self.worldorigin );
		avoid.SetDir( self.movedir );
		avoid.Evaluate( self );

		if ( avoid.steeringforce == vec_zero )
			{
			avoidtime = level.time + 0.1;
			}
		else
			{
			steeringforce += avoid.steeringforce;
			}
		}

	self.Accelerate( steeringforce );

	return true;
	}

void Chase::End
	(
	Actor &self
	)

	{
   //if ( wander && ( self.newanimnum != -1 ) )
      //{
      //self.SetAnim( anim );
      //}
	seek.End( self );
	follow.End( self );
	avoid.End( self );
	path = NULL;
   turnto.End( self );
	}
