#include "MW4Headers.hpp"

#include "AI_LancemateCommands.hpp"

#include "CombatAI.hpp"
#include "MWMission.hpp"
#include "AI_FindObject.hpp"
#include "AI_Groups.hpp"
#include "VehicleInterface.hpp"
#include "Flag.hpp"
#include "Field_Base.hpp"
#include "AI_Damage.hpp"
#include "NavPoint.hpp"
#include <Adept\AudioCommand.hpp>
#include <Adept\AudioRenderer.hpp>
#include <Stuff\Auto_Ptr.hpp>
#include "lancemate.hpp"
#include <Adept\CollisionVolume.hpp>
#pragma warning(push)
#include <stlport\algorithm>
#pragma warning(pop)


using namespace MechWarrior4;
using namespace MW4AI;
using namespace MW4AI::LancemateCommands;
using namespace MW4AI::LancemateAudio;


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Adept::Entity* AttackTargetIfOK(CombatAI& combat_ai, Adept::Entity* target, LancemateAudio::AudioManager& audio_manager,bool bPlayAudio = true);

MWObject* MWObjectForID(Adept::ObjectID id)
{
	Adept::Entity* entity = NameTable::GetInstance()->FindData(id);

	if ((entity == 0) ||
		(entity->IsDestroyed() == true) ||
		(entity->IsDerivedFrom(MWObject::DefaultData) == false))
	{
		return (0);
	}

	return (Cast_Object(MWObject*,entity));
}

inline MWObject* GetLeader(Group& group)
{
	return (MWObjectForID(group.GetElements()[0]));
}

inline MWObject* GetLeader(MWObject& mwobject)
{
	Group& group = Groups::GetGroup(*(mwobject.GetGroups().begin()));
	return (GetLeader(group));
}

int GetMemberIndex(Group& group, CombatAI& combat_ai)
{
	int index = index_of(group.GetElements().begin(),group.GetElements().end(),combat_ai.GetSelf().objectID);
	Verify(index < group.GetElements().size());							// if this fails, the lancemate does not belong to the group

	return (index);
}

void EnterFormationAtIndex(CombatAI& combat_ai, int index, Adept::Entity* who_to_follow)
{
	int offset_x(0);
	int offset_y(0);

	if (combat_ai.Crouching() == true)
	{
		combat_ai.Crouch();
	}

	switch (index)
	{
		case 0:
		{
			return;
		}

		case 1:
		{
			offset_x = 30;
			offset_y = -30;
			break;
		}

		case 2:
		{
			offset_x = -30;
			offset_y = -30;
			break;
		}

		case 3:
		{
			offset_x = 0;
			offset_y = -60;
			break;
		}

		default:
		{
			Verify(!"Lancemate::OrderFormation() not yet designed for groups of more than 4 units.");
			break;
		}
	}

	combat_ai.orderMoveFollow(who_to_follow,offset_x,offset_y,1000);
}

void EnterFormation(CombatAI& combat_ai,
					MWObject& leader)
{
	if (!&leader || (leader.objectID == combat_ai.GetSelf().objectID))
	{
		return;
	}

	combat_ai.Reset(true);

	MWObject::GroupList::iterator shared_group = std::find_first_of(leader.GetGroups().begin(),
																	leader.GetGroups().end(),
																	combat_ai.GetSelf().GetGroups().begin(),
																	combat_ai.GetSelf().GetGroups().end());

    if (shared_group == leader.GetGroups().end()) { // jcem
        if (combat_ai.GetSelf().GetGroups().size()) {
            shared_group = combat_ai.GetSelf().GetGroups().begin();
        } else {
            return;
        }
    }
    int index = GetMemberIndex(Groups::GetGroup(*shared_group),combat_ai);
	if (index == 0)
	{
		index = 3; // return...
	}

	EnterFormationAtIndex(combat_ai,index,&leader);
}

enum FaceForward_Result
{
	DOING_SOMETHING,
	FACING,
	UN_CROUCHING
};

FaceForward_Result FaceForwardIfInFormation(CombatAI& combat_ai,
											MWObject& leader)
{
	if ((combat_ai.MoveDone() == true) &&
		(combat_ai.GetAttackState() == CombatAI::NOT_ATTACKING))
	{
		if (leader.IsDerivedFrom(Vehicle::DefaultData) == true)
		{
			Vehicle* vehicle = Cast_Object(Vehicle*,&leader);

			if ((vehicle->currentSpeedKPH > 10.0f) ||
				(GetLengthSquared((Stuff::Point3D)combat_ai.GetSelf().GetLocalToWorld(),(Stuff::Point3D)leader.GetLocalToWorld()) > 80.0f * 80.0f))
			{
				EnterFormation(combat_ai,leader);
				return (DOING_SOMETHING);
			}
		}

		Stuff::UnitVector3D unit_forward;
		leader.GetLocalToWorld().GetLocalForwardInWorld(&unit_forward);
		Stuff::Point3D forward(unit_forward);

		Stuff::UnitVector3D my_unit_forward;
		combat_ai.GetSelf().GetLocalToWorld().GetLocalForwardInWorld(&my_unit_forward);
		Stuff::Point3D my_forward(my_unit_forward);

		Vector3D cross_product;
		cross_product.Cross(forward,my_forward);

		if ((cross_product.GetLengthSquared() > (0.25f * 0.25f)) &&
			(combat_ai.Crouching() == true))
		{
			combat_ai.Crouch();
			return (UN_CROUCHING);
		}

		forward *= 100.0f;
		forward += (Stuff::Point3D)combat_ai.GetSelf().GetLocalToWorld();

		combat_ai.FacePointOrLookOut(forward);

		return (FACING);
	}

	return (DOING_SOMETHING);
}

void EnsureActive(CombatAI& combat_ai, bool stop_holding_fire = true)
{
	combat_ai.Reset(true);

	if (stop_holding_fire == true)
	{
		combat_ai.SetHoldingFire(false);
	}

	if (combat_ai.CanAct() == false)
	{
		combat_ai.Startup();
	}
	if (combat_ai.Crouching() == true)
	{
		combat_ai.Crouch();
	}
}

Adept::Entity* AttackTargetIfOK(CombatAI& combat_ai, Adept::Entity* target, LancemateAudio::AudioManager& audio_manager,bool bPlayAudio/* = true*/)
{
	if ((target == 0) ||
		(target->IsDestroyed() == true))
	{
		return (0);
	}

	if (target->GetAlignment() == combat_ai.GetSelf().GetAlignment())
	{
        if (bPlayAudio)
		audio_manager.PlayAudio(AUDIO_NO,combat_ai);
		return (0);
	}

	if (target->IsDerivedFrom(MWObject::DefaultData) == true)
	{
		MWObject* mwobject = Cast_Object(MWObject*,target);

		if (mwobject->GetTargetDesirability() == MWObject::desirability_never)
		{
            if (bPlayAudio)
			audio_manager.PlayAudio(AUDIO_NO,combat_ai);
			return (0);
		}
	}

	combat_ai.Reset();
	combat_ai.Target(target);

	if (combat_ai.Target() == 0)
	{
        if (bPlayAudio)
		audio_manager.PlayAudio(AUDIO_NO,combat_ai);
		return (0);
	}

	combat_ai.OrderAttack();

	return (target);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//

LancemateCommand::LancemateCommand(CombatAI& combat_ai,
								   MWObject& leader,
								   LancemateAudio::AudioManager& audio_manager,
                                   Adept::Entity* pTarget2/* = NULL*/)
    : m_CombatAI(combat_ai), m_AudioManager(audio_manager)
{
	m_Leader.Add(&leader);
    if (pTarget2)
        m_pTarget2.Add(pTarget2);
	Check_Object(&combat_ai);
	Check_Object(&leader);
}

LancemateCommand::~LancemateCommand()
{
}

bool LancemateCommand::OnLeash() const
{
	return (true);
}

Stuff::Point3D LancemateCommand::GetLeashPoint() const
{
    if (m_pTarget2.GetCurrent())
        return ((Stuff::Point3D)m_pTarget2.GetCurrent()->GetLocalToWorld());
    if (m_Leader.GetCurrent())
	    return ((Stuff::Point3D)m_Leader.GetCurrent()->GetLocalToWorld());
    return Stuff::Point3D(0.0, 0.0, 0.0);
}

Stuff::Scalar LancemateCommand::GetLeashRadius() const
{
	return (UserConstants::Instance()->Get(UserConstants::max_lancemate_distance_from_leader));
}

bool LancemateCommand::CanDistract() const
{
	return (false);
}

bool LancemateCommand::CanDeviateFromPath() const
{
	return (false);
}

MWObject* LancemateCommand::FindSomeoneToAttack(const Adept::ObjectID* except_who, FindObject::Criteria criteria, Stuff::Scalar radius) const
{
	if ((m_CombatAI.GetSelf().GetSensor() == 0) ||
		(m_CombatAI.GetHoldingFire() == true))
	{
		return (0);
	}

	Adept::ObjectID id = FindObject::Find(m_CombatAI.GetSelf(),
										  FindObject::FA_ENEMY,
										  FindObject::FT_DEFAULT,
										  criteria,
										  FindObject::FF_WHO_SHOT + FindObject::FF_SEEPLAYER,
										  radius,
										  except_who);

	if (id <= 0)
	{
		if (except_who != 0)
		{
			id = FindObject::Find(m_CombatAI.GetSelf(),
								  FindObject::FA_ENEMY,
								  FindObject::FT_DEFAULT,
								  criteria,
								  FindObject::FF_WHO_SHOT,
								  radius,
								  0);

			if (id <= 0)
			{
				return (0);
			}
		}
		else
		{
			return (0);
		}
	}

	Adept::Entity* entity = NameTable::GetInstance()->FindData(id);

	if ((entity != 0) &&
		(entity->IsDestroyed() == false) &&
		(entity->IsDerivedFrom(MWObject::DefaultData) == true))
	{
		return (Cast_Object(MWObject*,entity));
	}

	return (0);
}

void LancemateCommand::NotifyNoPath()
{
}


namespace MW4AI
{
	namespace LancemateCommands
	{
		class AttackPlayersTarget		: public LancemateCommand { DERIVED_LancemateCommand(AttackPlayersTarget);
			virtual Stuff::Point3D GetLeashPoint() const;
		  private:
			SlotOf<Adept::Entity*> m_Target;
		};
		class DefendPlayersTarget		: public LancemateCommand { DERIVED_LancemateCommand(DefendPlayersTarget);
			virtual Stuff::Point3D GetLeashPoint() const;
		  private:
			SlotOf<Adept::Entity*> m_Target;
		};
		class FormOnMe					: public LancemateCommand { DERIVED_LancemateCommand(FormOnMe);
			virtual bool CanDistract() const;
			virtual void NotifyNoPath();
		  private:
			bool m_GivingUp;
		};
		class HoldFire					: public LancemateCommand { DERIVED_LancemateCommand(HoldFire); };
		class GoToMyNavPoint			: public LancemateCommand { DERIVED_LancemateCommand(GoToMyNavPoint);
			virtual Stuff::Point3D GetLeashPoint() const;
			virtual void NotifyNoPath();
		  private:
			Stuff::Point3D m_NavPos;
			bool m_PlayedReachedSound;
			bool m_GivingUp;
		};
		class Stop						: public LancemateCommand { DERIVED_LancemateCommand(Stop); };
		class Shutdown					: public LancemateCommand { DERIVED_LancemateCommand(Shutdown);
		  private:
			bool m_ShuttingDown;
		};
		class AttackNearestThreat		: public LancemateCommand { DERIVED_LancemateCommand(AttackNearestThreat);
			virtual Stuff::Point3D GetLeashPoint() const;
		  private:
			SlotOf<Adept::Entity*> m_Target;
		};

		// not yet implemented  ...
		class RepairAtNearestRepairBay	: public LancemateCommand { DERIVED_LancemateCommand(RepairAtNearestRepairBay);
			virtual Stuff::Point3D GetLeashPoint() const;
			virtual ~RepairAtNearestRepairBay();
			virtual void NotifyNoPath();
			virtual bool CanInterrupt() const;

		  private:
			void StartMoving();
			bool Reset();
		  private:
			Adept::ObjectID m_MFB;
			Stuff::Point3D m_LastMovePoint;
			Stuff::Point3D m_MovePoints[2];
			int m_NumRetries;
			bool m_IssuedSetMoveHead;
		};
	};
};

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//

Stuff::Auto_Ptr<LancemateCommands::LancemateCommand> LancemateCommands::CreateLancemateCommand(ID command,
																							   CombatAI& combat_ai,
																							   MWObject& leader,
																							   LancemateAudio::AudioManager& audio_manager,
                                                                                               Adept::Entity* pTarget2/* = NULL*/)
{
	Verify(command > LANCEMATE_FIRST);
	Verify(command < LANCEMATE_LAST);

	switch (command)
	{
		case LANCEMATE_DEFAULT:						{ return (new Default(combat_ai,leader,audio_manager,pTarget2)); }
		case LANCEMATE_ATTACKPLAYERSTARGET:			{ return (new AttackPlayersTarget(combat_ai,leader,audio_manager,pTarget2)); }
		case LANCEMATE_DEFENDPLAYERSTARGET:			{ return (new DefendPlayersTarget(combat_ai,leader,audio_manager,pTarget2)); }
		case LANCEMATE_FORMONME:					{ return (new FormOnMe(combat_ai,leader,audio_manager,pTarget2)); }
		case LANCEMATE_HOLDFIRE:					{ return (new HoldFire(combat_ai,leader,audio_manager,pTarget2)); }
		case LANCEMATE_GOTOMYNAVPOINT:				{ return (new GoToMyNavPoint(combat_ai,leader,audio_manager,pTarget2)); }
		case LANCEMATE_STOP:						{ return (new Stop(combat_ai,leader,audio_manager,pTarget2)); }
		case LANCEMATE_SHUTDOWN:					{ return (new Shutdown(combat_ai,leader,audio_manager,pTarget2)); }
		case LANCEMATE_ATTACKNEARESTTHREAT:			{ return (new AttackNearestThreat(combat_ai,leader,audio_manager,pTarget2)); }
		case LANCEMATE_REPAIRATNEARESTREPAIRBAY:	{ return (new RepairAtNearestRepairBay(combat_ai,leader,audio_manager,pTarget2)); }
	}

	return (0);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//

bool Default::Finished() const
{
	return (false);
}

void Default::Init()
{
	MechWarrior4::MWObject* pLeader = m_Leader.GetCurrent();
	if (m_CombatAI.GetSelf().IsPlayerVehicle() == true)
	{
		return;
	}

	m_LastTarget.Remove();
	m_LastTimeNotWaiting = (Stuff::Scalar)gos_GetElapsedTime();
	EnterFormation(m_CombatAI,*pLeader);
	m_CombatAI.SetAutoTargeting(true);
}

void Default::Update()
{
	MechWarrior4::MWObject* pLeader = m_Leader.GetCurrent();
    if (!pLeader)
        return;
	if (m_CombatAI.GetSelf().IsPlayerVehicle() == true)
	{
		return;
	}

	if (m_CombatAI.GetTimeTargetedByFriendly() > 0.5f)
	{
		EnterFormation(m_CombatAI,*pLeader);
	}
	else
	{
		if (m_LastTarget.GetCurrent() != m_CombatAI.Target())
		{
			if ((m_CombatAI.Target() == 0) ||
				(m_CombatAI.Target()->IsDestroyed() == true))
			{
				EnterFormation(m_CombatAI,*pLeader);
			}
		}
	}

    m_LastTarget.Remove();
    if (m_CombatAI.Target())
	    m_LastTarget.Add(m_CombatAI.Target());

	switch (FaceForwardIfInFormation(m_CombatAI,*pLeader))
	{
		case DOING_SOMETHING:
		{
			m_LastTimeNotWaiting = (Stuff::Scalar)gos_GetElapsedTime();
			break;
		}

		case UN_CROUCHING:
		{
			m_LastTimeNotWaiting = (Stuff::Scalar)gos_GetElapsedTime() - 8.0f;
			break;
		}

		case FACING:
		{
			Stuff::Scalar wait_time = 20.0f;
			wait_time += (Stuff::Scalar)(m_CombatAI.GetSelf().objectID % 0x08);

			if ((m_LastTimeNotWaiting + wait_time < gos_GetElapsedTime()) &&
				(m_CombatAI.Crouching() == false))
			{
				m_CombatAI.Crouch();
			}
			break;
		}
	}
}

bool Default::CanDistract() const
{
	return (true);
}

bool Default::CanDeviateFromPath() const
{
	return (true);
}

void Default::NotifyNoPath()
{
    m_CombatAI.Reset();
	Init();
}


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//

bool AttackPlayersTarget::Finished() const
{
    Adept::Entity* pTarget = m_Target.GetCurrent();
	if (pTarget == 0)
	{
		return (true);
	}
    Adept::Entity* pTarget2 = m_pTarget2.GetCurrent();

	if (pTarget->IsDestroyed() == true)
	{
        if (!pTarget2)
		m_AudioManager.PlayAudio(LancemateAudio::AUDIO_TARGETDESTROYED,m_CombatAI);

		return (true);
	}

	return (false);
}

void AttackPlayersTarget::Init()
{
    Adept::Entity* pTarget2 = m_pTarget2.GetCurrent();
	MechWarrior4::MWObject* pLeader = m_Leader.GetCurrent();

    if (!pTarget2) // jcem
	m_AudioManager.PlayAudio(AUDIO_OK,m_CombatAI);
	EnsureActive(m_CombatAI);
	m_CombatAI.SetAutoTargeting(false);

	Adept::Entity* pTarget = NULL;
    m_Target.Remove();

    if (!pTarget2) { // jcem
	if (pLeader->IsDerivedFrom(Vehicle::DefaultData) == true)
	{
		Vehicle* leader_as_vehicle = Cast_Object(Vehicle*,pLeader);
		if ((leader_as_vehicle->vehicleInterface != 0) &&
			(leader_as_vehicle->vehicleInterface->m_interfaceTarget.GetCurrent() != 0))
		{
			Adept::Entity* entity = leader_as_vehicle->vehicleInterface->m_interfaceTarget.GetCurrent();

			if (entity->IsDerivedFrom(MWObject::DefaultData) == true)
			{
				MWObject* mwobject = Cast_Object(MWObject*,entity);

				if ((m_CombatAI.GetAttackState() != CombatAI::ATTACKING) ||
					(m_CombatAI.Target() != mwobject))
				{
					pTarget = mwobject;
				}
			}
		}
	}

	if ((pTarget == 0) &&
		(pLeader->GetAI() != 0) &&
		(pLeader->GetAI()->IsDerivedFrom(CombatAI::DefaultData) == false))
	{
		CombatAI* combat_ai = Cast_Object(CombatAI*,pLeader->GetAI());
		Check_Object(combat_ai);

		pTarget = combat_ai->Target();
	}
    } else { // jcem
		if ((m_CombatAI.GetAttackState() != CombatAI::ATTACKING) ||
			(m_CombatAI.Target() != pTarget2))
		{
			pTarget = pTarget2;
		}
    }

    pTarget = AttackTargetIfOK(m_CombatAI,pTarget,m_AudioManager,m_pTarget2.GetCurrent() ? false: true);
    if (pTarget)
    m_Target.Add(pTarget);
}

void AttackPlayersTarget::Update()
{
}

Stuff::Point3D AttackPlayersTarget::GetLeashPoint() const
{
    Adept::Entity* pTarget = m_Target.GetCurrent();
	if ((pTarget == 0) ||
		(pTarget->IsDestroyed() == true))
	{
		return (LancemateCommand::GetLeashPoint());
	}

	return ((Stuff::Point3D)pTarget->GetLocalToWorld());
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//

bool DefendPlayersTarget::Finished() const
{
    Adept::Entity* pTarget = m_Target.GetCurrent();
	if ((pTarget == 0) ||
		(pTarget->IsDestroyed() == true))
	{
		return (true);
	}

	return (false);
}

void DefendPlayersTarget::Init()
{
    Adept::Entity* pTarget2 = m_pTarget2.GetCurrent();
    MechWarrior4::MWObject* pLeader = m_Leader.GetCurrent();

	EnsureActive(m_CombatAI);
	m_CombatAI.SetAutoTargeting(false);

	Adept::Entity* pTarget = NULL;
    m_Target.Remove();

    if (!pTarget2) { // jcem
	if (pLeader->IsDerivedFrom(Vehicle::DefaultData) == true)
	{
		Vehicle* leader_as_vehicle = Cast_Object(Vehicle*,pLeader);
		if ((leader_as_vehicle->vehicleInterface != 0) &&
			(leader_as_vehicle->vehicleInterface->m_interfaceTarget.GetCurrent() != 0))
		{
			Adept::Entity* entity = leader_as_vehicle->vehicleInterface->m_interfaceTarget.GetCurrent();

			if (entity->IsDerivedFrom(MWObject::DefaultData) == true)
			{
				MWObject* mwobject = Cast_Object(MWObject*,entity);

				if ((m_CombatAI.GetAttackState() != CombatAI::ATTACKING) ||
					(m_CombatAI.GetCurrentTactic() != Tactics::TACTIC_DEFEND) ||
					(m_CombatAI.Target() != mwobject))
				{
					if (mwobject->GetAlignment() != m_CombatAI.GetSelf().GetAlignment())
					{
						m_AudioManager.PlayAudio(AUDIO_NO,m_CombatAI);
						return;
					}
					else
					{
						pTarget = mwobject;
					}
				}
			}
		}
	}

	if ((pTarget == 0) &&
		(pLeader->GetAI() != 0) &&
		(pLeader->GetAI()->IsDerivedFrom(CombatAI::DefaultData) == false))
	{
		CombatAI* combat_ai = Cast_Object(CombatAI*,pLeader->GetAI());
		Check_Object(combat_ai);

		if (combat_ai->Target()->GetAlignment() == m_CombatAI.GetSelf().GetAlignment())
		{
			pTarget = combat_ai->Target();
		}
	}
    } else { // jcem
		if ((m_CombatAI.GetAttackState() != CombatAI::ATTACKING) ||
			(m_CombatAI.GetCurrentTactic() != Tactics::TACTIC_DEFEND) ||
			(m_CombatAI.Target() != pTarget2))
		{
			if (pTarget2->GetAlignment() != m_CombatAI.GetSelf().GetAlignment())
			{
				return;
			}
			else
			{
				pTarget = pTarget2;
			}
		}
    }

	if ((pTarget != 0) &&
		(pTarget->IsDestroyed() == true))
	{
		pTarget = 0;
	}

	if (pTarget != 0)
	{
        m_Target.Add(pTarget);
		m_CombatAI.Reset();
		m_CombatAI.Target(pTarget);
		m_CombatAI.OrderAttackTactic(Tactics::TACTIC_DEFEND);

        if (!pTarget2)
		m_AudioManager.PlayAudio(AUDIO_OK,m_CombatAI);
	}
}

void DefendPlayersTarget::Update()
{
}

Stuff::Point3D DefendPlayersTarget::GetLeashPoint() const
{
    Adept::Entity* pTarget = m_Target.GetCurrent();
	if ((pTarget == 0) ||
		(pTarget->IsDestroyed() == true))
	{
		return (LancemateCommand::GetLeashPoint());
	}

	return ((Stuff::Point3D)pTarget->GetLocalToWorld());
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//

bool FormOnMe::Finished() const
{
    if (m_CombatAI.MoveDone() == true) {
        Adept::Entity* pTarget2 = m_pTarget2.GetCurrent();
        MechWarrior4::MWObject* pLeader = m_Leader.GetCurrent();

        if (pTarget2) {
            if (GetLengthSquared((Stuff::Point3D)m_CombatAI.GetSelf().GetLocalToWorld(),(Stuff::Point3D)pTarget2->GetLocalToWorld()) < 80.0f * 80.0f)
    		    return (true);
        } else if (pLeader) {
            if (GetLengthSquared((Stuff::Point3D)m_CombatAI.GetSelf().GetLocalToWorld(),(Stuff::Point3D)pLeader->GetLocalToWorld()) < 80.0f * 80.0f)
    		    return (true);
        }
    }

	return (m_GivingUp);
}

bool FormOnMe::CanDistract() const
{
    if (m_CombatAI.MoveDone() == true) {
        Adept::Entity* pTarget2 = m_pTarget2.GetCurrent();
        MechWarrior4::MWObject* pLeader = m_Leader.GetCurrent();

        if (pTarget2) {
		    if (GetLengthSquared((Stuff::Point3D)m_CombatAI.GetSelf().GetLocalToWorld(),(Stuff::Point3D)pTarget2->GetLocalToWorld()) < 80.0f * 80.0f)
		        return (true);
        } else {
		    if (GetLengthSquared((Stuff::Point3D)m_CombatAI.GetSelf().GetLocalToWorld(),(Stuff::Point3D)pLeader->GetLocalToWorld()) < 80.0f * 80.0f)
		        return (true);
        }
	}

	return (false);
}

void FormOnMe::Init()
{
    Adept::Entity* pTarget2 = m_pTarget2.GetCurrent();
    MechWarrior4::MWObject* pLeader = m_Leader.GetCurrent();

    if (!pTarget2)
	m_AudioManager.PlayAudio(AUDIO_OK,m_CombatAI);
	EnsureActive(m_CombatAI,false);
	m_CombatAI.SetAutoTargeting(true);

	m_GivingUp = false;

    if (pTarget2)
        EnterFormation(m_CombatAI,*(MWObject*)pTarget2);
    else
	    EnterFormation(m_CombatAI,*pLeader);
}

void FormOnMe::Update()
{
    Adept::Entity* pTarget2 = m_pTarget2.GetCurrent();
    MechWarrior4::MWObject* pLeader = m_Leader.GetCurrent();

    if (pTarget2)
        FaceForwardIfInFormation(m_CombatAI,*(MWObject*)pTarget2);
    else
	    FaceForwardIfInFormation(m_CombatAI,*pLeader);
}

void FormOnMe::NotifyNoPath()
{
	m_GivingUp = true;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//

bool HoldFire::Finished() const
{
	return (false);
}

void HoldFire::Init()
{
	m_AudioManager.PlayAudio(AUDIO_OK,m_CombatAI);
	if (m_CombatAI.GetHoldingFire() == true)
	{
		m_CombatAI.SetHoldingFire(false);
	}
	else
	{
		m_CombatAI.SetHoldingFire(true);
	}
}

void HoldFire::Update()
{
}


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//

bool GoToMyNavPoint::Finished() const
{
	return (m_GivingUp);
}

void GoToMyNavPoint::Init()
{
    MechWarrior4::MWObject* pLeader = m_Leader.GetCurrent();

	m_AudioManager.PlayAudio(AUDIO_OK,m_CombatAI);
	EnsureActive(m_CombatAI,false);
	m_CombatAI.SetAutoTargeting(true);

	m_PlayedReachedSound = false;
	m_GivingUp = false;

	if (m_CombatAI.GetSelf().IsDerivedFrom(Vehicle::DefaultData) == false)
	{
		m_NavPos = LancemateCommand::GetLeashPoint();
		return;
	}

	Vehicle* leader_as_vehicle = Cast_Object(Vehicle*,pLeader);
	if ((leader_as_vehicle->vehicleInterface == 0) ||
		(leader_as_vehicle->vehicleInterface->m_selectedNavPoint.GetCurrent() == 0))
	{
		m_NavPos = LancemateCommand::GetLeashPoint();
		return;
	}

	m_NavPos = (Stuff::Point3D)(leader_as_vehicle->vehicleInterface->m_selectedNavPoint.GetCurrent()->GetLocalToWorld());

	m_CombatAI.Reset();
	m_CombatAI.MoveToPoint(m_NavPos);
}

void GoToMyNavPoint::Update()
{
	if ((m_PlayedReachedSound == false) &&
		(GetLengthSquared((Stuff::Point3D)m_CombatAI.GetSelf().GetLocalToWorld(),m_NavPos) < 150.0f * 150.0f))
	{
		m_PlayedReachedSound = true;

		m_AudioManager.PlayAudio(LancemateAudio::AUDIO_ATNAV,m_CombatAI);
	}
}

Stuff::Point3D GoToMyNavPoint::GetLeashPoint() const
{
	return (m_NavPos);
}

void GoToMyNavPoint::NotifyNoPath()
{
	m_GivingUp = true;
}


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//

bool Stop::Finished() const
{
	return (false);
}

void Stop::Init()
{
	m_AudioManager.PlayAudio(AUDIO_OK,m_CombatAI);
	m_CombatAI.Reset();
	m_CombatAI.SetAutoTargeting(true);
}

void Stop::Update()
{
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//

bool Shutdown::Finished() const
{
	if (m_ShuttingDown == true)
	{
		if (m_CombatAI.CanAct() == false)
		{
			return (true);
		}
	}
	else
	{
		if (m_CombatAI.CanAct() == true)
		{
			return (true);
		}
	}

	return (false);
}

void Shutdown::Init()
{
	m_AudioManager.PlayAudio(AUDIO_OK,m_CombatAI);
	if (m_CombatAI.Crouching() == true)
	{
		m_CombatAI.Crouch();
	}

	if (m_CombatAI.CanAct() == true)
	{
		m_ShuttingDown = true;
		m_CombatAI.Shutdown();
	}
	else
	{
		m_ShuttingDown = false;
		m_CombatAI.Startup();
	}
}

void Shutdown::Update()
{
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//

bool AttackNearestThreat::Finished() const
{
    Adept::Entity* pTarget = m_Target.GetCurrent();
	if ((pTarget == 0) ||
		(pTarget->IsDestroyed() == true))
	{
		return (true);
	}

	return (false);
}

Stuff::Point3D AttackNearestThreat::GetLeashPoint() const
{
    Adept::Entity* pTarget = m_Target.GetCurrent();
	if ((pTarget == 0) ||
		(pTarget->IsDestroyed() == true))
	{
		return (LancemateCommand::GetLeashPoint());
	}

	return ((Stuff::Point3D)pTarget->GetLocalToWorld());
}

void AttackNearestThreat::Init()
{
    Adept::Entity* pTarget2 = m_pTarget2.GetCurrent();

	m_AudioManager.PlayAudio(AUDIO_OK,m_CombatAI);
	EnsureActive(m_CombatAI);
	m_CombatAI.SetAutoTargeting(true);

	m_Target.Remove();

	Update();
	MWObject* nearest_enemy = FindSomeoneToAttack(0,FindObject::FC_NEAREST);

    Adept::Entity* pTarget = AttackTargetIfOK(m_CombatAI,nearest_enemy,m_AudioManager,pTarget2 ? false: true);
    if (pTarget)
	    m_Target.Add(pTarget);
}

void AttackNearestThreat::Update()
{
}


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//

	template <class T>
	T* FindByID(Adept::ObjectID id, Adept::Entity::ClassData* classdata)
	{
		if (id == -2)
		{
			return (0);
		}

		Adept::Entity* entity = NameTable::GetInstance()->FindData(id);

		if ((entity == 0) ||
			(entity->IsDestroyed() == true) ||
			(entity->IsDerivedFrom(classdata) == false))
		{
			return (0);
		}

		return (Cast_Object(T*,entity));
	}

	Adept::ObjectID FindNearestFieldBase(const Stuff::Point3D& pos, Stuff::ChainOf<MFB*>& chain)
	{
		Stuff::ChainIteratorOf<MFB*> i(&chain);

		MFB* t = 0;
		Stuff::Scalar least_distance = 0;
		MFB* best = 0;
		while ((t = i.ReadAndNext()) != 0)
		{
			if (t->IsDerivedFrom(FieldBase::DefaultData) == true)
			{
				Stuff::Scalar distance(GetLengthSquared(pos,(Stuff::Point3D)t->GetLocalToWorld()));
				if ((least_distance == 0) ||
					(distance < least_distance))
				{
					best = t;
					least_distance = distance;
				}
			}
		}

		if (best == 0)
		{
			return (-2);
		}

		return (best->objectID);
	}

	Stuff::Point3D GetPointInFrontOfObject(Adept::Entity& entity, bool front, Stuff::Scalar collision_volume_radius_multiplier)
	{
		const Stuff::LinearMatrix4D& matrix = entity.GetLocalToWorld();

		Stuff::UnitVector3D unit_vector;

		if (front == true)
		{
			matrix.GetLocalForwardInWorld(&unit_vector);
		}
		else
		{
			matrix.GetLocalBackwardInWorld(&unit_vector);
		}

		CollisionVolume* cv = entity.GetSolidVolume();

		Stuff::Point3D rv(matrix);
		if (cv == 0)
		{
			return (rv);
		}

		Stuff::Scalar radius = cv->m_worldSpaceBounds.sphereRadius * collision_volume_radius_multiplier;

		Stuff::Point3D add(unit_vector);
		add *= radius;
		
		rv += add;

		return (rv);
	}

bool RepairAtNearestRepairBay::Finished() const
{
	if (m_NumRetries > 3)
	{
		return (true);
	}

	if (m_CombatAI.ExitedFieldBaseRecently() == true)
	{
		return (true);
	}

	MFB* mfb = FindByID<MFB>(m_MFB,MFB::DefaultData);

	if (mfb == 0)
	{
		return (true);
	}

	return (false);
}

bool RepairAtNearestRepairBay::Reset()
{
	EnsureActive(m_CombatAI,false);

	m_LastMovePoint = Stuff::Point3D(0,0,0);
	m_MovePoints[0] = m_LastMovePoint;
	m_MovePoints[1] = m_LastMovePoint;

	m_MFB = FindNearestFieldBase((Stuff::Point3D)m_CombatAI.GetSelf().GetLocalToWorld(),*(MFB::s_MFBs));
	MFB* mfb = FindByID<MFB>(m_MFB,MFB::DefaultData);

	if ((mfb && (mfb->m_CurrentMechUsing != &m_CombatAI) && (mfb->m_CurrentMechUsing != NULL)) || 
			 (MW4AI::Damage::CanRepair(m_CombatAI.GetSelf()) == false))
	{
		return (false);
	}

	StartMoving();
	return (true);
}

void RepairAtNearestRepairBay::Init()
{
	Verify(MFB::s_MFBs != 0);

	m_AudioManager.PlayAudio(AUDIO_OK,m_CombatAI);
	m_NumRetries = 0;
	m_IssuedSetMoveHead = false;

	if (Reset() == false)
	{
		m_AudioManager.PlayAudio(LancemateAudio::AUDIO_NO,m_CombatAI);
		m_NumRetries = 4;
	}
}

void RepairAtNearestRepairBay::StartMoving()
{
	MFB* mfb = FindByID<MFB>(m_MFB,MFB::DefaultData);

	if (mfb == 0)
	{
		return;
	}

	mfb->m_CurrentMechUsing = &m_CombatAI;
	Stuff::Point3D forward = GetPointInFrontOfObject(*mfb,true,1.9f);
	Stuff::Point3D backward = GetPointInFrontOfObject(*mfb,false,1.9f);

	if (GetLengthSquared((Stuff::Point3D)m_CombatAI.GetSelf().GetLocalToWorld(),forward) <
		GetLengthSquared((Stuff::Point3D)m_CombatAI.GetSelf().GetLocalToWorld(),backward))
	{
		m_MovePoints[0] = forward;
		m_MovePoints[1] = GetPointInFrontOfObject(*mfb,true,0.5f);
	}
	else
	{
		m_MovePoints[0] = backward;
		m_MovePoints[1] = GetPointInFrontOfObject(*mfb,false,0.5f);
	}

	m_LastMovePoint = m_MovePoints[0];

	m_CombatAI.Reset();
	m_CombatAI.orderMoveToLocPoint(m_LastMovePoint,60,true,false);
}

void RepairAtNearestRepairBay::Update()
{
	MFB* mfb = FindByID<MFB>(m_MFB,MFB::DefaultData);

	if (mfb == 0)
	{
		m_MFB = FindNearestFieldBase((Stuff::Point3D)m_CombatAI.GetSelf().GetLocalToWorld(),*(MFB::s_MFBs));
		mfb = FindByID<MFB>(m_MFB,MFB::DefaultData);

		if (mfb != 0)
		{
			StartMoving();
		}
	}

	if (mfb != 0)
	{
		if 	((m_CombatAI.CanAct() == true) &&
			 (m_CombatAI.MovedWithin(4.0f) == false))
		{
			++m_NumRetries;

			if (m_NumRetries > 3)
			{
				m_AudioManager.PlayAudio(AUDIO_NO,m_CombatAI);
			}
			else
			{
				m_CombatAI.m_LastTimeMoved = (Stuff::Scalar)gos_GetElapsedTime();

				if (m_NumRetries > 2)
				{
					NotifyNoPath();
				}
				else
				{
					Reset();
				}
			}
		}
		else
		{
			if (m_LastMovePoint == m_MovePoints[0])
			{
				Stuff::Point3D pos(m_CombatAI.GetSelf().GetLocalToWorld());
				Stuff::Point3D dest = m_LastMovePoint;

				if (m_CombatAI.getCurPath() != 0)
				{
					dest = m_CombatAI.getCurPath()->End();
				}

				if (GetLengthSquared(pos,dest) < 20 * 20)
				{
					m_LastMovePoint = m_MovePoints[1];

					m_CombatAI.ClearMoveOrder ();
					m_CombatAI.Reset();
					m_CombatAI.SetRamTarget(mfb);

					Check_Object (m_CombatAI.getVehicle()->GetGameModel ());
					const Vehicle__GameModel *model = static_cast<const Vehicle__GameModel *> (m_CombatAI.getVehicle()->GetGameModel ()) ;
					Scalar normalspeed = ((float) ConvertKPHtoMPS(30)) / ((float) model->maxSpeed);
					Scalar endspeed = ((float) ConvertKPHtoMPS(30)) / ((float) model->maxSpeed);

					m_CombatAI.SetMoveHead (m_LastMovePoint,normalspeed,Point3D (-1,-1,-1),endspeed);
					m_IssuedSetMoveHead = true;
	//				m_CombatAI.orderMoveToLocPoint(m_LastMovePoint,600,true,false);
				}
			}
		}
	}
}

Stuff::Point3D RepairAtNearestRepairBay::GetLeashPoint() const
{
	MFB* mfb = FindByID<MFB>(m_MFB,MFB::DefaultData);

	if (mfb != 0)
	{
		return ((Stuff::Point3D)mfb->GetLocalToWorld());
	}

	return (LancemateCommand::GetLeashPoint());
}

RepairAtNearestRepairBay::~RepairAtNearestRepairBay()
{
	MFB* mfb = FindByID<MFB>(m_MFB,MFB::DefaultData);

	if ((mfb != 0) &&
		(mfb->m_CurrentMechUsing == &m_CombatAI))
	{
		mfb->m_CurrentMechUsing = 0;
	}
}

void RepairAtNearestRepairBay::NotifyNoPath()
{
	MFB* mfb = FindByID<MFB>(m_MFB,MFB::DefaultData);

	if (mfb == 0)
	{
		Reset();
		return;
	}

	mfb->m_CurrentMechUsing = &m_CombatAI;
	Stuff::Point3D forward = GetPointInFrontOfObject(*mfb,true,1.9f);
	Stuff::Point3D backward = GetPointInFrontOfObject(*mfb,false,1.9f);

	if (m_MovePoints[0] == backward)
	{
		m_MovePoints[0] = forward;
		m_MovePoints[1] = GetPointInFrontOfObject(*mfb,true,0.5f);
	}
	else
	{
		m_MovePoints[0] = backward;
		m_MovePoints[1] = GetPointInFrontOfObject(*mfb,false,0.5f);
	}

	m_LastMovePoint = m_MovePoints[0];

	m_CombatAI.Reset();
	m_CombatAI.orderMoveToLocPoint(m_LastMovePoint,600,true,false);
}

bool RepairAtNearestRepairBay::CanInterrupt() const
{
	if (m_IssuedSetMoveHead == true)
	{
		return (false);
	}

	return ((m_LastMovePoint == m_MovePoints[0]) ||
			(m_NumRetries > 0));
}



	void StandardLancemateSound(const Stuff::MString& prefix, Stuff::MString& s)
	{
		s = "VO\\Generic\\";
		s += prefix;
		s += (gos_rand() & 0x01) ? "1" : "2";
		s += "_Ian.wav{handle}";
	}


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// ExecuteForAllLancemates(): executes a LancemateFunction functor for a given list of lancemates
//

void ExecuteForAllLancemates(const lancemate_list& lancemates, LancemateCommands::ID command_id)
{
	Verify(NameTable::GetInstance() != 0);

	MWObject* leader = 0;
	{for (lancemate_list::const_iterator i = lancemates.begin();
		  i != lancemates.end();
		  ++i)
	{
		MWObject* mwobject = MWObjectForID(*i);

		if (mwobject != 0)
		{
			Check_Object(mwobject);
			MWObject* l = GetLeader(*mwobject);

			if (l != 0)
			{
				Check_Object(l);
				leader = l;
				break;
			}
		}
	}}

	if (leader == 0)
	{
		return;
	}

	bool notified(false);

	{for (lancemate_list::const_iterator i = lancemates.begin();
		  i != lancemates.end();
		  ++i)
	{
		MWObject* mwobject = MWObjectForID(*i);

		if ((mwobject != 0) &&
			(mwobject != leader) &&
			(mwobject->GetAI() != 0) &&
			(mwobject->GetAI()->IsDerivedFrom(CombatAI::DefaultData) == true))
		{

			if ((mwobject->GetAI() == 0) ||
				(mwobject->GetAI()->IsDerivedFrom(CombatAI::DefaultData) == false))
			{
				continue;
			}

			CombatAI* m_CombatAI = Cast_Object(CombatAI*,mwobject->GetAI());

			if ((m_CombatAI->GetSquadOrders() != 0) &&
				(m_CombatAI->GetSquadOrders()->CanExecuteCommands() == true))
			{
				if (notified == false)
				{
					notified = true;

					Stuff::MString s("");
					switch (command_id)
					{
						case LANCEMATE_ATTACKPLAYERSTARGET:		StandardLancemateSound("Attack",s);	break;
						case LANCEMATE_DEFENDPLAYERSTARGET:		StandardLancemateSound("Defend",s);	break;
						case LANCEMATE_FORMONME:				StandardLancemateSound("Form",s);	break;
						case LANCEMATE_HOLDFIRE:				StandardLancemateSound("Hold",s);	break;	
						case LANCEMATE_GOTOMYNAVPOINT:			StandardLancemateSound("Goto",s);	break;
						case LANCEMATE_STOP:					StandardLancemateSound("Stop",s);	break;
						case LANCEMATE_SHUTDOWN:				StandardLancemateSound("Shutdown",s);	break;
						case LANCEMATE_ATTACKNEARESTTHREAT:		StandardLancemateSound("AttackNear",s);	break;
						case LANCEMATE_REPAIRATNEARESTREPAIRBAY:	StandardLancemateSound("Repair",s);	break;
					}

					if (s.GetLength() > 0)
					{
						AudioCommand *command = AudioCommand::Create(AudioRenderer::VOType, s, 1.0f, 1.0f);
						if (command)
						{
							Check_Object(command);
							command->Play();
						}
					}
				}

				Stuff::Auto_Ptr<LancemateCommand> lancemate_command(m_CombatAI->GetSquadOrders()->CreateCommand(command_id));
//				Stuff::Auto_Ptr<LancemateCommand> lancemate_command(CreateLancemateCommand(command_id,*m_CombatAI,*leader,m_CombatAI->GetSquadOrders()->GetAudioManager()));

				if (lancemate_command.GetPointer() != 0)
				{
					m_CombatAI->GetSquadOrders()->IssueCommand(lancemate_command);
				}
			}
		}
	}}
}


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Execute(): the core, public execution function
//

void LancemateCommands::Execute(ID command, const lancemate_list& lancemates)
{
	Verify(command > LANCEMATE_FIRST);
	Verify(command < LANCEMATE_LAST);

	gos_PushCurrentHeap(Heap);

	ExecuteForAllLancemates(lancemates,command);

	gos_PopCurrentHeap();
}

void LancemateCommands::ExecuteForAllLancemates2(Mech* paMechs[], int nMechs, LancemateCommands::ID command_id, MWObject* pLeader, MWObject* pTarget)
{
	Verify(command_id > LANCEMATE_FIRST);
	Verify(command_id < LANCEMATE_LAST);

	Verify(NameTable::GetInstance() != 0);

	gos_PushCurrentHeap(Heap);

	for (int i = 0; i < nMechs; ++i) {
		MWObject* mwobject = paMechs[i];

        if ((mwobject != 0) && (mwobject != pLeader)) {
            AI* pAI = mwobject->GetAI();
            if ((pAI != 0) && (pAI->IsDerivedFrom(CombatAI::DefaultData) == true)) {
			    CombatAI* pCombatAI = Cast_Object(CombatAI*,pAI);

			    if ((pCombatAI->GetSquadOrders() != 0) && (pCombatAI->GetSquadOrders()->CanExecuteCommands() == true))
			    {
				    Stuff::Auto_Ptr<LancemateCommand> lancemate_command(pCombatAI->GetSquadOrders()->CreateCommand(command_id,pTarget));
//				    Stuff::Auto_Ptr<LancemateCommand> lancemate_command(CreateLancemateCommand(command_id,m_CombatAI,*leader,m_CombatAI.GetSquadOrders()->GetAudioManager()));

				    if (lancemate_command.GetPointer() != 0)
				    {
					    pCombatAI->GetSquadOrders()->IssueCommand(lancemate_command);
				    }
                }
			}
		}
	}

	gos_PopCurrentHeap();
}

