#include "MW4Headers.hpp"

#include "AI_Action.hpp"

#include "AI_FireStyle.hpp"
#include "AI_SituationalAnalysis.hpp"
#include "AI_FireData.hpp"
#include "Mech.hpp"
#include "JumpJet.hpp"
#include "Airplane.hpp"
#include "MWMission.hpp"
#include "Torso.hpp"
#include "Boat.hpp"


extern __int64 tCombatAITime_Track;

using namespace MW4AI;
using namespace MW4AI::Actions;
using namespace MW4AI::SituationalAnalysis;

namespace MW4AI
{
	extern Stuff::Scalar MinZ, MaxZ, MinX, MaxX;
};

bool Actions::g_HelicoptersIgnoreMissionBounds = false;

void Actions::Fire(TacticInterface& i,
				   FireStyles::FireStyleID style)
{
	AILOGFUNC("Actions::Fire");
	AILOG1(style);

	i.Fire(style);
}

const Stuff::Scalar circle_radians = Stuff::Pi_Over_6;
const Stuff::Scalar stop_radius = 35.0f;
const Stuff::Scalar min_move_radius = 50.0f;
const Stuff::Scalar min_movement_score_to_recalc_multiplier = 0.5f;
const Stuff::Scalar bad_shot_dodge_time = 5.0f;

// TODO: pull ShouldReevaluate() upwards, into Behaviors!!!  ...

bool ShouldReevaluate(MW4AI::TacticInterface& i, Stuff::Scalar distance)
{
	AILOGFUNC("ShouldReevaluate");
	AILOG1(distance);

	if (i.CanMove() == false)
	{
		LOG_AND_RETURN(false);
	}
	if (i.ShouldForceDestinationRecalc() == true)
	{
		LOG_AND_RETURN(true);
	}

	if (i.MoveRequestPending() == true)
	{
		LOG_AND_RETURN(false);
	}

	if (i.GetDistanceFromDestinationSquared() < (distance * distance))
	{
		LOG_AND_RETURN(true);
	}

	Stuff::Point3D last_move_dest;
	if (i.GetLastMoveDest(last_move_dest) == false)
	{
		LOG_AND_RETURN(true);
	}

	if (i.MovedWithin(5.0f) == false)
	{
		LOG_AND_RETURN(true);
	}

	LOG_AND_RETURN(false);
}

inline bool MoveTypeFlagToBool(TacticInterface& i, const Stuff::Point3D& dest, MoveType move_type)
{
	switch (move_type)
	{
		case MOVE_FORWARD:
		{
			return (true);
		}
		case MOVE_BACKWARD:
		{
			return (false);
		}
		case MOVE_TO_FACE_TARGET:
		{
			return (ShouldMoveForwardToPoint(i,dest));
		}
	}

	Verify(!"Move type flag not found");
	return (true);
}

inline bool MovingForwardIsEasier(const Stuff::LinearMatrix4D& matrix, const Stuff::Point3D& dest)
{
	if (GetSquaredDistToMatrixForward(dest,matrix) < GetSquaredDistToMatrixBackward(dest,matrix))
	{
		return (false);
	}

	return (true);
}

inline void FindEscapeMoveDest(MW4AI::TacticInterface& i, MoveType move_type)
{
	Stuff::Scalar radius(stop_radius + 30.0f);
	Generator_Circle generator(FROM_SELF,radius);
	Evaluator_Random evaluator;
		
	Stuff::Point3D dest(Evaluate(i,generator,evaluator,radius));
	if (i.PointIsValid(dest,true) == true)
	{
		if ((move_type != MOVE_FORWARD) &&
			(MovingForwardIsEasier(i.GetSelf().GetLocalToWorld(),dest) == false))
		{
			move_type = MOVE_BACKWARD;
		}

		i.MoveTo(dest,MoveTypeFlagToBool(i,dest,move_type));
		i.SetMovementScore(1.0f);
	}
}

void FindMoveDest(MW4AI::TacticInterface& i,
				  const MW4AI::SituationalAnalysis::RegionGenerator& generator,
				  const MW4AI::SituationalAnalysis::PointEvaluator& evaluator,
				  Stuff::Scalar radius,
				  MoveType move_type)
{
	if (i.RecentPathsFailed() == false)
	{
		Stuff::Point3D dest(Evaluate(i,generator,evaluator,radius));
		Fuzzy score(EvaluatePoint(i,dest,evaluator));

		if (i.PointIsValid(dest) == true)
		{
			i.MoveTo(dest,MoveTypeFlagToBool(i,dest,move_type));
			i.SetMovementScore(score);
			return;
		}
	}

	Scalar radius2 = i.GetLeashRadius();

	if (radius2 != 0)
	{
		radius2 *= 0.9f;
		Generator_Circle generator2(i.GetLeashCenter(),radius2);

		Stuff::Point3D dest(Evaluate(i,generator2,evaluator,radius2));
		Fuzzy score(EvaluatePoint(i,dest,evaluator));

		if (i.PointIsValid(dest) == true)
		{
			i.MoveTo(dest,MoveTypeFlagToBool(i,dest,move_type));
			i.SetMovementScore(score);
			return;
		}
	}

	FindEscapeMoveDest(i,move_type);
}



void Actions::ApproachTarget(TacticInterface& i, MoveType move_type)
{
	AILOGFUNC("Actions::ApproachTarget");

	if (ShouldReevaluate(i,stop_radius) == false)
	{
		return;
	}

	Evaluator_DistanceFrom evaluator(FROM_SELF,0,800,NEAREST);
	Generator_Circle generator(FROM_TARGET,25.0f);

	FindMoveDest(i,generator,evaluator,min_move_radius,move_type);
}

void Actions::ApproachTarget(TacticInterface& i, Stuff::Scalar when)
{
	AILOGFUNC("Actions::ApproachTarget");
	AILOG1(when);

	Stuff::Point3D target_pos;
	i.GetTarget().EstimateFuturePosition(&target_pos,(Scalar)when,true);

	Evaluator_DistanceFrom evaluator(FROM_SELF,0,800,FARTHEST);
	Generator_Circle generator(target_pos,10.0f);

	FindMoveDest(i,generator,evaluator,min_move_radius,MOVE_TO_FACE_TARGET);
}

void Actions::FleeTarget(TacticInterface& i)
{
	AILOGFUNC("Actions::FleeTarget");

	if (ShouldReevaluate(i,stop_radius) == false)
	{
		return;
	}

	Evaluator_DistanceFrom distance_from_self(FROM_SELF,0,800,NEAREST);
	Evaluator_DistanceFrom distance_from_enemies(FROM_ALL_ENEMIES,0,800,FARTHEST);
	Evaluator_InFrontOf in_front_of(FROM_SELF,true);
	Evaluator_WeightedAverage evaluator(0.35f,distance_from_self,
										0.15f,in_front_of,
										0.5f,distance_from_enemies);
	Generator_Circle generator(FROM_SELF,stop_radius + 45);

	FindMoveDest(i,generator,evaluator,min_move_radius,MOVE_FORWARD);
}

void Actions::FleeNearest(TacticInterface& i, Stuff::Scalar distance)
{
	AILOGFUNC("Actions::FleeNearest");
	AILOG(distance);

	if (ShouldReevaluate(i,stop_radius) == false)
	{
		return;
	}

	Evaluator_DistanceFrom evaluator(FROM_NEAREST_ENEMY,0,800,FARTHEST);
	if (distance < min_move_radius + 10)
	{
		distance = min_move_radius + 10;
	}

	Generator_Circle generator(FROM_SELF,distance);

	FindMoveDest(i,generator,evaluator,min_move_radius,MOVE_TO_FACE_TARGET);
}

void Actions::Disengage(TacticInterface& i, Stuff::Scalar distance)
{
	AILOGFUNC("Actions::Disengage");
	AILOG(distance);

	MechWarrior4::MWObject* nearest = i.GetNearest();

	if (nearest == 0)
	{
		GoToDistanceFromTarget(i,distance,MOVE_TO_FACE_TARGET);
		return;
	}

	Stuff::LinearMatrix4D my_matrix(i.GetSelf().GetLocalToWorld());
	Stuff::Point3D my_pos(my_matrix);

	Stuff::UnitVector3D unit_forward;
	Stuff::UnitVector3D unit_backward;

	my_matrix.GetLocalForwardInWorld(&unit_forward);
	my_matrix.GetLocalBackwardInWorld(&unit_backward);

	Stuff::Point3D forward(unit_forward);
	forward *= distance;
	forward += my_pos;

	Stuff::Point3D backward(unit_backward);
	backward *= distance;
	backward += my_pos;

	Stuff::Point3D nearest_pos(nearest->GetLocalToWorld());
	Stuff::Point3D dest;
	bool move_forward;

	if (GetSquaredDistToMatrixForward(nearest_pos,my_matrix) < GetSquaredDistToMatrixBackward(nearest_pos,my_matrix))
	{
		dest = backward;
		move_forward = false;
	}
	else
	{
		dest = forward;
		move_forward = true;
	}

	if (GetLengthSquared(dest,i.GetMoveDest()) < 12.0f * 12.0f)
	{
		return;
	}

	if (i.PointIsValid(dest,true) == false)
	{
		GoToDistanceFromTarget(i,distance,MOVE_TO_FACE_TARGET);
		return;
	}

	i.MoveTo(dest,move_forward,true,nearest);
	i.ThrottleOverride(6.0f,true);
}

void Actions::GoToDistanceFromTarget(TacticInterface& i, Stuff::Scalar distance, MoveType move_type)
{
	AILOGFUNC("Actions::GoToDistanceFromTarget");
	AILOG2(distance,move_type);

	if (ShouldReevaluate(i,stop_radius) == false)
	{
		return;
	}

	Evaluator_DistanceFrom distance_from_self(FROM_SELF,0,800,NEAREST);
	Evaluator_DistanceFrom distance_from_enemies(FROM_ALL_ENEMIES,0,800,FARTHEST);
	Evaluator_WeightedAverage evaluator(0.7f,distance_from_self,
										0.3f,distance_from_enemies);

	Generator_Circle generator(FROM_TARGET,distance);

	FindMoveDest(i,generator,evaluator,min_move_radius,move_type);
}

void Actions::GoToDistanceFromNearestEnemy(TacticInterface& i, Stuff::Scalar distance, MoveType move_type)
{
	AILOGFUNC("Actions::GoToDistanceFromNearestEnemy");
	AILOG2(distance,move_type);

	if (ShouldReevaluate(i,stop_radius) == false)
	{
		return;
	}

	Evaluator_DistanceFrom distance_from_self(FROM_SELF,10,distance * 1.5f,NEAREST);
	Evaluator_DistanceFrom distance_from_enemies(FROM_ALL_ENEMIES,10,distance * 1.5f,FARTHEST);
	Evaluator_WeightedAverage evaluator(0.5f,distance_from_self,
										0.5f,distance_from_enemies);

	Generator_Circle generator(FROM_NEAREST_ENEMY,distance);

	FindMoveDest(i,generator,evaluator,min_move_radius,move_type);
}

void Actions::GoToOtherSideOfTarget(TacticInterface& i, Stuff::Scalar distance)
{
	AILOGFUNC("Actions::GoToOtherSideOfTarget");
	AILOG1(distance);

	if (ShouldReevaluate(i,stop_radius) == false)
	{
		return;
	}

	Evaluator_DistanceFrom far_from_self(FROM_SELF,0,distance * 2,FARTHEST);
	Evaluator_DistanceFrom far_from_enemies(FROM_ALL_ENEMIES,0,distance * 2,FARTHEST);
	Evaluator_WeightedAverage evaluator(0.7f,far_from_self,
										0.3f,far_from_enemies);

	Generator_Circle generator(FROM_TARGET,distance);

	FindMoveDest(i,generator,evaluator,min_move_radius,MOVE_TO_FACE_TARGET);
}

void Actions::StopIfMoving(TacticInterface& i)
{
	AILOGFUNC("Actions::StopIfMoving");

	if (i.Moving() == true)
	{
		i.Stop();
	}
}

void Actions::Circle(TacticInterface& i, MoveType move_type, Stuff::Scalar distance)
{
	AILOGFUNC("Actions::Circle");
	AILOG2(move_type,distance);

	if (ShouldReevaluate(i,stop_radius) == false)
	{
		return;
	}

	Evaluator_DistanceFrom near_self(FROM_SELF,0,500,NEAREST);
	Evaluator_InFrontOf in_front_of(FROM_SELF,true);
	Evaluator_DistanceFrom distance_from_enemies(FROM_ALL_ENEMIES,0,500,FARTHEST);
	Evaluator_WeightedAverage evaluator(0.5f,near_self,
										0.25f,in_front_of,
										0.25f,distance_from_enemies);

	if (distance == 0)
	{
		distance = GetApproximateLength((Stuff::Point3D)i.GetSelf().GetLocalToWorld(),
										(Stuff::Point3D)i.GetTarget().GetLocalToWorld());
	}

	Generator_Circle generator(FROM_TARGET,distance,7);
	FindMoveDest(i,generator,evaluator,min_move_radius,move_type);
}

void Actions::CircleTargetNearEnemies(TacticInterface& i, Stuff::Scalar distance)
{
	AILOGFUNC("Actions::CircleTargetNearEnemies");

	if (ShouldReevaluate(i,stop_radius) == false)
	{
		return;
	}

	Evaluator_DistanceFrom near_self(FROM_SELF,0,200,NEAREST);
	Evaluator_InFrontOf in_front_of(FROM_SELF,true);
	Evaluator_DistanceFrom distance_from_enemies(FROM_ALL_ENEMIES,0,200,NEAREST);
	Evaluator_WeightedAverage evaluator(0.4f,near_self,
										0.2f,in_front_of,
										0.4f,distance_from_enemies);

	Generator_Circle generator(FROM_TARGET,distance,12.0f,0.6f);
	FindMoveDest(i,generator,evaluator,min_move_radius,MOVE_FORWARD);
}

void Actions::CircleRandomly(TacticInterface& i)
{
	AILOGFUNC("Actions::CircleRandomly");

	if (ShouldReevaluate(i,stop_radius) == false)
	{
		return;
	}

	Stuff::Scalar distance = GetApproximateLength((Stuff::Point3D)i.GetSelf().GetLocalToWorld(),
												  (Stuff::Point3D)i.GetTarget().GetLocalToWorld());

	Evaluator_DistanceFrom near_self(FROM_SELF,0,1000,NEAREST);
	Evaluator_DistanceFrom distance_from_enemies(FROM_ALL_ENEMIES,0,800,FARTHEST);
	Evaluator_WeightedAverage evaluator(0.8f,near_self,
										0.2f,distance_from_enemies);

	Generator_Circle generator(FROM_TARGET,distance,5);
	FindMoveDest(i,generator,evaluator,min_move_radius,MOVE_TO_FACE_TARGET);
}

void Actions::CircleFloat(TacticInterface& i, Stuff::Scalar distance)
{
	AILOGFUNC("Actions::CircleRandomly");

	if (i.GetSelf().IsDerivedFrom(Airplane::DefaultData) == false)
	{
		return;
	}

	Stuff::Point3D last_move_dest;
	i.GetLastMoveDest(last_move_dest,true);

	Stuff::Point3D my_pos(i.GetSelf().GetLocalToWorld());

	last_move_dest.y = 0;
	my_pos.y = 0;

	if ((last_move_dest.GetLengthSquared() == 0) ||
		(GetApproximateLength(my_pos,last_move_dest) < 5.0f))
	{
		Generator_Circle generator(FROM_TARGET,distance,9);

		SituationalAnalysis::point_list points;
		generator.Generate(i,points);

		// TODO: move all this elsewhere
		Stuff::Scalar best_score(-1);
		SituationalAnalysis::point_list::const_iterator best = points.end();

		Stuff::LinearMatrix4D relative_to;
		if ((i.GetTarget().GetTorso() != 0) &&
			(i.GetTarget().GetTorso()->twistJoint != 0))
		{
			Check_Object(i.GetTarget().GetTorso());
			relative_to = i.GetTarget().GetTorso()->twistJoint->GetLocalToWorld();
		}
		else
		{
			relative_to = i.GetTarget().GetLocalToWorld();
		}

		{for (SituationalAnalysis::point_list::const_iterator i_points = points.begin();
			  i_points != points.end();
			  ++i_points)
		{
			Stuff::Scalar distance = GetApproximateLength(*i_points,(Stuff::Point3D)(i.GetSelf().GetLocalToWorld()));

			if (distance < 50.0f)
			{
				continue;
			}

			if (distance < 0)
			{
				distance = 0;
			}

			if (distance > 500)
			{
				distance = 500;
			}

			Stuff::Scalar bearing(YawToPoint(relative_to,*i_points));
			bearing = Stuff::Fabs(bearing);

			Stuff::Scalar score = (500 - distance) + (500 * bearing);

			if (score > best_score)
			{
				if (((*i_points).x <= MinX) ||
					((*i_points).x >= MaxX) ||
					((*i_points).z <= MinZ) ||
					((*i_points).z >= MaxZ))
				{
					continue;
				}

				if (g_HelicoptersIgnoreMissionBounds == false)
				{
					Verify(Adept::Mission::GetInstance() != 0);
					Verify(Adept::Mission::GetInstance()->IsDerivedFrom(MechWarrior4::MWMission::DefaultData) == true);
					MechWarrior4::MWMission* mission = Cast_Object(MechWarrior4::MWMission*,Adept::Mission::GetInstance());

					if (mission->GetMissionArea(*i_points) != MWMission::InMissionArea)
					{
						continue;
					}
				}

				best_score = score;
				best = i_points;
			}
		}}

		if (best == points.end())
		{
			return;
		}

		i.FloatToPoint(*best);
	}
}

void Actions::CircleToBehindTarget(TacticInterface& i, Stuff::Scalar maximum)
{
	AILOGFUNC("Actions::CircleToBehindTarget");
	AILOG1(maximum);

	if (ShouldReevaluate(i,stop_radius) == false)
	{
		return;
	}

	Stuff::Scalar distance = GetApproximateLength((Stuff::Point3D)i.GetAttackOrderPosition(),
												  (Stuff::Point3D)i.GetTarget().GetLocalToWorld());

	if (distance > maximum)
	{
		distance = maximum;
	}

	Evaluator_InFrontOf behind_enemy(FROM_TARGET,false,true);
	Evaluator_DistanceFrom near_self2(FROM_SELF,0,distance * 2,NEAREST);
	Evaluator_DistanceFrom distance_from_enemies2(FROM_ALL_ENEMIES,0,distance * 2,FARTHEST);
	Evaluator_WeightedAverage evaluator2(0.5f,behind_enemy,
										 0.4f,near_self2,
										 0.1f,distance_from_enemies2);

	Generator_Circle generator(FROM_TARGET,distance);
	FindMoveDest(i,generator,evaluator2,min_move_radius,MOVE_TO_FACE_TARGET);
}

// TODO: share code here with CircleToBehindTarget()  ...  these are both very similar
void Actions::CircleToFrontOfTarget(TacticInterface& i, Stuff::Scalar maximum)
{
	AILOGFUNC("Actions::CircleToFrontOfTarget");
	AILOG1(maximum);

	if (ShouldReevaluate(i,stop_radius) == false)
	{
		return;
	}

	Stuff::Scalar distance = GetApproximateLength((Stuff::Point3D)i.GetAttackOrderPosition(),
												  (Stuff::Point3D)i.GetTarget().GetLocalToWorld());

	if (distance > maximum)
	{
		distance = maximum;
	}

	Stuff::Scalar distance2 = GetApproximateLength((Stuff::Point3D)i.GetSelf().GetLocalToWorld(),
												   (Stuff::Point3D)i.GetTarget().GetLocalToWorld());

	Evaluator_InFrontOf in_front_of(FROM_TARGET,true,true);
	Evaluator_DistanceFrom near_self2(FROM_SELF,0,distance2 * 2,NEAREST);
	Evaluator_DistanceFrom distance_from_enemies2(FROM_ALL_ENEMIES,0,distance2 * 2,FARTHEST);
	Evaluator_WeightedAverage evaluator2(0.5f,in_front_of,
										 0.4f,near_self2,
										 0.1f,distance_from_enemies2);

	Generator_Circle generator(FROM_TARGET,distance);
	FindMoveDest(i,generator,evaluator2,min_move_radius,MOVE_TO_FACE_TARGET);
}

void Actions::DoFastCircle(TacticInterface& i, FastCircleMoveType move_type)
{
	AILOGFUNC("Actions::DoFastCircle");

	if (ShouldReevaluate(i,stop_radius * 0.75f) == false)
	{
		return;
	}

	Generator_Circle generator(FROM_SELF,(stop_radius * 2.0f) + (Stuff::Random::GetFraction() * stop_radius),9);

	switch (move_type)
	{
		case MOVE_CLOSER:
		{
			Evaluator_45DegreesInFront evaluator_45(false);
			Evaluator_DistanceFrom evaluator_distance(FROM_TARGET,20,600,NEAREST);
			Evaluator_WeightedAverage evaluator(0.5f,evaluator_45,
												0.5f,evaluator_distance);
			FindMoveDest(i,generator,evaluator,min_move_radius,MOVE_FORWARD);
			break;
		}
		case MOVE_FARTHER:
		{
			Evaluator_45DegreesInFront evaluator_45(false);
			Evaluator_DistanceFrom evaluator_distance(FROM_TARGET,20,600,FARTHEST);
			Evaluator_WeightedAverage evaluator(0.5f,evaluator_45,
												0.5f,evaluator_distance);
			FindMoveDest(i,generator,evaluator,min_move_radius,MOVE_FORWARD);
			break;
		}
		case MOVE_RANDOM:
		{
			Evaluator_45DegreesInFront evaluator_45(true);
			Evaluator_DistanceFrom evaluator_distance(FROM_TARGET,20,600,NEAREST);
			Evaluator_WeightedAverage evaluator(0.8f,evaluator_45,
												0.2f,evaluator_distance);
			FindMoveDest(i,generator,evaluator,min_move_radius,MOVE_FORWARD);
			break;
		}
		default:
		{
			Verify(!"Invalid enumerated type.");
			break;
		}
	}
}

void Actions::Dodge(TacticInterface& i, Stuff::Scalar distance, MoveType move_type)
{
	AILOGFUNC("Actions::Dodge");
	AILOG1(distance);

	if (ShouldReevaluate(i,stop_radius) == false)
	{
		return;
	}

	Evaluator_DistanceFrom distance_from_self(FROM_SELF,10,400,NEAREST);
	Evaluator_ToSideOf to_side_of_target(FROM_TARGET,true);
	Evaluator_DistanceFrom distance_from_enemies(FROM_ALL_ENEMIES,10,400,FARTHEST);
	Evaluator_WeightedAverage evaluator(0.6f,to_side_of_target,
										0.25f,distance_from_self,
										0.15f,distance_from_enemies);

	Generator_Circle generator(FROM_TARGET,distance * 1.5f);
	FindMoveDest(i,generator,evaluator,min_move_radius,move_type);
}

void Actions::MoveForward(TacticInterface& i, Stuff::Scalar distance)
{
	AILOGFUNC("Actions::MoveForward");
	AILOG1(distance);

	if (i.RecentPathsFailed() == true)
	{
		FindEscapeMoveDest(i,MOVE_FORWARD);
		return;
	}

	if (ShouldReevaluate(i,stop_radius) == false)
	{
		return;
	}

	Stuff::UnitVector3D forward_vector;
	i.GetSelf().GetLocalToWorld().GetLocalForwardInWorld(&forward_vector);
	Stuff::Point3D move_to(forward_vector);

	move_to *= distance;
	move_to += (Stuff::Point3D)i.GetSelf().GetLocalToWorld();

	if (i.PointIsValid(move_to) == true)
	{
		i.MoveTo(move_to);
	}
}

void Actions::Crouch(TacticInterface& i)
{
	AILOGFUNC("Actions::Crouch");

	i.Crouch();
}

void Actions::ThrottleOverride(TacticInterface& i)
{
	AILOGFUNC("Actions::ThrottleOverride");

	i.ThrottleOverride(4.0f);
}

void Actions::Jump(TacticInterface& i)
{
	AILOGFUNC("Actions::Jump");

	i.Jump();
}

void Actions::FindBetterLineOfFire(TacticInterface& i)
{
	AILOGFUNC("Actions::FindBetterLineOfFire");

	if (ShouldReevaluate(i,stop_radius) == false)
	{
		return;
	}

	Stuff::Scalar distance_from_target = GetApproximateLength((Stuff::Point3D)i.GetSelf().GetLocalToWorld(),
															  (Stuff::Point3D)i.GetTarget().GetLocalToWorld());

	Evaluator_DistanceFrom distance_from_self(FROM_SELF,20,distance_from_target * 2,NEAREST);
	Evaluator_DistanceFrom distance_from_enemies(FROM_ALL_ENEMIES,20,distance_from_target * 2,FARTHEST);
	Evaluator_WeightedAverage evaluator(0.7f,distance_from_self,
										0.3f,distance_from_enemies);


	FireData fire_data(i.GetTarget(),
					  i.GetTarget().GetLineOfSight(),
					  (Stuff::Point3D)i.GetSelf().GetLocalToWorld());

	fire_data.Project();

	Stuff::Scalar line_length = fire_data.GetLine().m_length;

	if ((line_length > 20) &&
		(line_length < distance_from_target * 0.7))
	{
		Stuff::Point3D delta;
		delta.Subtract((Stuff::Point3D)i.GetSelf().GetLocalToWorld(),
					   (Stuff::Point3D)i.GetTarget().GetLocalToWorld());

		delta *= (line_length / distance_from_target);
		delta += (Stuff::Point3D)i.GetTarget().GetLocalToWorld();

		if (i.PointIsValid(delta) == true)
		{
			i.MoveTo(delta);
			return;
		}
	}

	Generator_Circle generator(FROM_TARGET,distance_from_target * 0.7f);

	FindMoveDest(i,generator,evaluator,min_move_radius,MOVE_TO_FACE_TARGET);
}

void Actions::GoBehindTarget(TacticInterface& i, Stuff::Scalar min_distance)
{
	AILOGFUNC("Actions::GoBehindTarget");

	if (ShouldReevaluate(i,stop_radius) == false)
	{
		return;
	}

	Stuff::Scalar distance_from_target = GetApproximateLength((Stuff::Point3D)i.GetSelf().GetLocalToWorld(),
															  (Stuff::Point3D)i.GetTarget().GetLocalToWorld());

	Evaluator_InFrontOf behind_enemy(FROM_TARGET,false,true);
	Evaluator_DistanceFrom distance_from_enemies(FROM_ALL_ENEMIES,10,distance_from_target * 2,FARTHEST);
	Evaluator_WeightedAverage evaluator(0.8f,behind_enemy,
										0.2f,distance_from_enemies);

	Stuff::Scalar desired_distance = distance_from_target * 0.5f;
	if (desired_distance < min_distance)
	{
		desired_distance = min_distance;
	}

	Generator_Circle generator(FROM_TARGET,
							   desired_distance);

	FindMoveDest(i,generator,evaluator,min_move_radius,MOVE_TO_FACE_TARGET);
}

// TODO: share code here with GoBehindTarget()
void Actions::GoInFrontOfTarget(TacticInterface& i, Stuff::Scalar min_distance)
{
	AILOGFUNC("Actions::GoInFrontOfTarget");

	if (ShouldReevaluate(i,stop_radius) == false)
	{
		return;
	}

	Stuff::Scalar distance_from_target = GetApproximateLength((Stuff::Point3D)i.GetSelf().GetLocalToWorld(),
															  (Stuff::Point3D)i.GetTarget().GetLocalToWorld());

	Evaluator_InFrontOf in_front_of(FROM_TARGET,true,true);
	Evaluator_DistanceFrom distance_from_enemies(FROM_ALL_ENEMIES,10,distance_from_target * 2,FARTHEST);
	Evaluator_WeightedAverage evaluator(0.8f,in_front_of,
										0.2f,distance_from_enemies);

	Stuff::Scalar desired_distance = distance_from_target * 0.5f;
	if (desired_distance < min_distance)
	{
		desired_distance = min_distance;
	}

	Generator_Circle generator(FROM_TARGET,
							   desired_distance);

	FindMoveDest(i,generator,evaluator,min_move_radius,MOVE_TO_FACE_TARGET);
}

void Actions::PatrolWithinRadiusOfAttackOrderPosition(TacticInterface& i, Stuff::Scalar inner_radius, Stuff::Scalar outer_radius)
{
	AILOGFUNC("Actions::PatrolWithinRadiusOfAttackOrderPosition");
	AILOG2(inner_radius,outer_radius);

	if (ShouldReevaluate(i,stop_radius) == false)
	{
		return;
	}

	Evaluator_InFrontOf behind_enemy(FROM_TARGET,false,true);
	Evaluator_DistanceFrom distance_from_self(FROM_SELF,10,outer_radius * 2,FARTHEST);
	Evaluator_WeightedAverage evaluator(0.4f,behind_enemy,
										0.6f,distance_from_self);

	Stuff::Scalar distance = NormalizedInterpolation(Stuff::Random::GetFraction(),inner_radius,outer_radius);
	Generator_Circle generator((Stuff::Point3D)i.GetAttackOrderPosition(),distance);

	FindMoveDest(i,generator,evaluator,min_move_radius,MOVE_TO_FACE_TARGET);
}

void Actions::ForceDestinationRecalc(TacticInterface& i)
{
	AILOGFUNC("Actions::ForceDestinationRecalc");

	i.ForceDestinationRecalc();
}

void Actions::RamTarget(TacticInterface& i, Stuff::Scalar recalc_tolerance)
{
	AILOGFUNC("Actions::RamTarget");
	AILOG(recalc_tolerance);

	Verify(i.GetSelf_AsVehicle() != 0);

	if (i.RecentPathsFailed() == true)
	{
		FindEscapeMoveDest(i,MOVE_FORWARD);
		return;
	}

	Stuff::Scalar target_velocity(0);

	if (i.GetTarget_AsVehicle() != 0)
	{
		target_velocity = i.GetTarget_AsVehicle()->currentSpeedMPS;
	}

	i.ThrottleOverride(5.0f);

	Stuff::Scalar my_speed((i.GetSelf_AsVehicle()->GetGameModel()->maxSpeed + i.GetSelf_AsVehicle()->currentSpeedMPS) * 0.5f); // average my current speed with my max speed for a good guess
	Stuff::Scalar time_to_collide = TimeToCollide(i.GetTarget().GetLocalToWorld(),
												  target_velocity,
												  (Stuff::Point3D)(i.GetSelf().GetLocalToWorld()),
												  my_speed);

	Stuff::Point3D new_pos;

	if (time_to_collide > 0)
	{
		if (time_to_collide > 8)
		{
			time_to_collide = 8;
		}

		i.GetTarget().EstimateFuturePosition(&new_pos,time_to_collide,false);
	}
	else
	{
		new_pos = (Stuff::Point3D)i.GetTarget().GetLocalToWorld();
	}

	const Stuff::Point3D old_pos(i.GetMoveDest());

	if ((i.ShouldForceDestinationRecalc() == true) ||
		(GetLengthSquared(new_pos,old_pos) > recalc_tolerance * recalc_tolerance))
	{
		i.MoveTo(new_pos,true,true,&(i.GetTarget()));
	}
}

void Actions::JumpToHeight(TacticInterface& i, Stuff::Scalar height)
{
	if (i.GetSelf().IsDerivedFrom(MechWarrior4::Mech::DefaultData) == false)
	{
		return;
	}

	MechWarrior4::Mech* mech = Cast_Object(MechWarrior4::Mech*,&i.GetSelf());
	if (mech->GetJumpJet() == 0)
	{
		return;
	}

	const Stuff::Point3D my_pos(i.GetSelf().GetLocalToWorld());
	BYTE tempmaterial;
	const Stuff::Scalar current_height = my_pos.y - GetMapY(my_pos.x,my_pos.z,&i.GetSelf(),tempmaterial);
	const Stuff::Scalar vertical_velocity = i.GetSelf().worldSpaceVelocity.linearMotion.y;

	if (mech->GetJumpJet()->executionState->GetState() != JumpJet::ExecutionStateEngine::JumpingState)
	{
		if ((current_height < height) ||
			(vertical_velocity < -25.0f) ||
			(current_height + (vertical_velocity * 2.0f) < height))
		{
			if (vertical_velocity < 20.0f)
			{
				Stuff::Scalar desired_time = height - current_height;
				desired_time += ((20.0f - vertical_velocity) * 2);

				desired_time /= 100;
				if (desired_time > 0.4f)
				{
					desired_time = 0.4f;
				}
				if (desired_time < 0.25f)
				{
					desired_time = 0.25f;
				}

				i.Jump(desired_time);
			}
		}
	}
}

void Actions::Track(TacticInterface& i, TrackTarget target, bool fTrack, bool fTurn, bool fPitch)
{
	AILOGFUNC("Actions::Track");
	AILOG3(target,fTrack,fTurn);

	TIME_FUNCTION(tCombatAITime_Track);

	if (fTrack == true)
	{
		fTrack = i.CanTrack();
	}

	if (fTurn == true)
	{
		fTurn = i.CanTurn();
	}

	if ((fTrack == false) &&
		(fTurn == false) &&
		(fPitch == false))
	{
		return;
	}

	MechWarrior4::MWObject* who_to_track = 0;

	switch (target)
	{
		case TO_TARGET:
		{
			who_to_track = &(i.GetTarget());
			break;
		}

		case TO_NEAREST_ENEMY:
		case TO_NEAREST_ENEMY_OR_TARGET:
		{
			Adept::Entity* nearest_enemy = i.GetNearest(true);
			if (nearest_enemy != 0)
			{
				Check_Object(nearest_enemy);
				if (nearest_enemy->IsDerivedFrom(MechWarrior4::MWObject::DefaultData) == true)
				{
					who_to_track = Cast_Object(MechWarrior4::MWObject*,nearest_enemy);
				}
			}
			else
			{
				if (target == TO_NEAREST_ENEMY_OR_TARGET)
				{
					who_to_track = &(i.GetTarget());
				}
			}

			break;
		}
	}

	if (who_to_track == 0)
	{
		Stuff::Point3D my_pos(i.GetSelf().GetLocalToWorld());
		Stuff::Point3D delta;
		delta.Subtract(my_pos,(Stuff::Point3D)i.GetTarget().GetLocalToWorld());
		delta += my_pos;

		if (fTrack == true)
		{
			i.TrackTo(delta);
		}

		if (fTurn == true)
		{
			i.TurnTo(delta);
		}

		if ((fPitch == true) &&
			(i.GetSelf_AsVehicle() != 0))
		{
			i.PitchTo(delta);
		}

		return;
	}

	Stuff::Point3D point(who_to_track->GetLineOfSight());

	if (fTrack == true)
	{
		i.TrackTo(point);
	}

	if (fTurn == true)
	{
		const Stuff::Scalar current_vulnerable_component = i.GetCurrentVulnerableComponent();

		if (current_vulnerable_component != 0)
		{
			Stuff::Point3D my_pos((Stuff::Point3D)i.GetSelf().GetLocalToWorld());
			my_pos.y = point.y;

			Stuff::Point3D delta;
			delta.Subtract(my_pos,point);

			Stuff::Point3D new_delta(0,0,0);

			if (current_vulnerable_component < 0)
			{
				my_pos.x += delta.z;
				my_pos.z -= delta.x;
			}
			else
			{
				my_pos.x -= delta.z;
				my_pos.z += delta.x;
			}

			i.TurnTo(my_pos,false);
		}
		else
		{
			i.TurnTo(point);
		}
	}

	if ((fPitch == true) &&
		(i.GetSelf_AsVehicle() != 0))
	{
		i.PitchTo(point);
	}
}


bool Actions::ShouldMoveForwardToPoint(TacticInterface& i, const Stuff::Point3D& dest)
{
	if ((i.NumLegsDestroyed() > 0) ||
		(i.GetSelf().IsDerivedFrom(Boat::DefaultData) == true))
	{
		return (true);								// if gimped, we should move forwards
	}

	const Stuff::Point3D my_pos(i.GetSelf().GetLocalToWorld());
	const Stuff::Point3D target_pos(i.GetTarget().GetLocalToWorld());

	const Stuff::Scalar pos_to_dest = GetApproximateLength(my_pos,dest);
	const Stuff::Scalar dest_to_target = GetApproximateLength(target_pos,dest);
	const Stuff::Scalar pos_to_target = GetApproximateLength(target_pos,my_pos);

	if ((pos_to_dest > pos_to_target) &&
		(pos_to_dest > dest_to_target))
	{
		return (true);								// if the point is way off on the other side of my target, move forwards
	}

	if (dest_to_target > (pos_to_target + 30.0f))
	{
		return (false);								// if the point is significantly farther than I am, walk backwards
	}

	return (true);									// otherwise, the point is closer -- walk forwards
}
