//////////////////////////////////////////////////////////////////////////////
//
// AVP_Behaviour_FaceHugger_Movement.cpp
//
// Copyright (C) 2008 Rebellion. All Rights Reserved.
//
//////////////////////////////////////////////////////////////////////////////

// I N C L U D E S ///////////////////////////////////////////////////////////

// core
#include "Core/Asura.h"

// header
#include "Behaviour/BehaviourTypes/FaceHugger/AVP_Behaviour_FaceHugger_Movement.h"

// asura
#ifndef ASURA_NO_DEBUG_OUTPUT
#include "Debugging/Asura_DebugLineRender.h"
#include "Rendering/Asura_Font_System.h"
#endif

// axon
#include "Behaviour/BehaviourTypes/AVP_Behaviour_Types.h"
#include "Core/BrainTypes/AVP_Brain_Actor_FaceHugger.h"

// avp
#include "Actor/Vertebrates/FaceHugger/AVP_FaceHugger_AIAnimStateMachine.h"
#include "Core/AVP_Plane.h"
#include "Entity/AVP_Messages.h"
#include "Entity/Controller/AVP_ServerEntity_Team_Alien.h"
#include "Entity/PhysicalObject/Actor/Vertebrates/FaceHugger/AVP_ServerActor_FaceHugger.h"
#include "Moves/Vertebrates/FaceHugger/AVP_FaceHugger_MovesJump.h"


//////////////////////////////////////////////////////////////////////////////
//
// AVP_FaceHuggerAvoidenceCostCalc
// Class that calculate cost for A* algorithm trying to avoid moving near a position
//
//////////////////////////////////////////////////////////////////////////////

class AVP_FaceHuggerAvoidenceCostCalc : public AVP_Navigation::FaceHuggerAStarFunctor
{
public:
	AVP_FaceHuggerAvoidenceCostCalc(const Asura_Vector_3 &xAvoidPosition, float fMinDistance, float fMaxDistance, const Asura_Vector_3& fPrefDirection)
		: m_xAvoidPosition(xAvoidPosition)
		, m_fMinDistanceSquared(fMinDistance * fMinDistance)
		, m_fMaxDistanceSquared(fMaxDistance * fMaxDistance)
		, m_fPrefDirection(fPrefDirection)
	{}

	virtual float EstimateTraversalCost(const int iWaypointIndex, const int iLinkIndex, const u_int uPathfinderFlags);

private:
	const Asura_Vector_3 &m_xAvoidPosition;
	const float m_fMinDistanceSquared;
	const float m_fMaxDistanceSquared;
	const Asura_Vector_3 m_fPrefDirection;

	typedef AVP_Navigation::FaceHuggerAStarFunctor PARENT; 
};

float AVP_FaceHuggerAvoidenceCostCalc::EstimateTraversalCost(const int iWaypointIndex, const int iLinkIndex, const u_int uPathfinderFlags)
{
	float fBaseCost = PARENT::EstimateTraversalCost( iWaypointIndex, iLinkIndex, uPathfinderFlags );
	
	if (fBaseCost < 0.0f)
	{
		return -1.0f;
	}

	const Asura_AI_NavVertex* pxFromWP = Asura_AI_Navigation_System::GetWaypoint( iWaypointIndex );
	const Asura_AI_NavVertex* pxToWP = Asura_AI_Navigation_System::GetWaypoint( pxFromWP->GetLink(iLinkIndex).GetIndex() );

	// try to not go too close to the avoided position
	Asura_Vector_3 xNearestPoint;
	Asura_Maths::GetNearestPointOnLineSegment(pxFromWP->GetPosition(), pxToWP->GetPosition(), m_xAvoidPosition, xNearestPoint);
	float fDistanceSquared = (m_xAvoidPosition - xNearestPoint).MagnitudeSquared();
	Asura_Vector_3 xWPDirection = (pxToWP->GetPosition() - m_xAvoidPosition );
	xWPDirection.Normalise();
	float fCosBetweenWPAndPrefDirection = xWPDirection * m_fPrefDirection;

	float fCostDueToDistance = fBaseCost;
	float fCostDueToDirection = 0;

	if (fDistanceSquared < 1.0f*1.0f)
	{
		fCostDueToDistance *= 100.0f;
	}
	else if(fDistanceSquared < m_fMinDistanceSquared)
	{
		fCostDueToDistance *=  20 * (m_fMinDistanceSquared - fDistanceSquared)/ (m_fMinDistanceSquared - 1.0f *1.0f);
		if (fCosBetweenWPAndPrefDirection < AVP_Brain_Actor_FaceHugger::s_fFrontalAngleCosine )
		{
			fCostDueToDirection = fBaseCost * 10 * (1-fCosBetweenWPAndPrefDirection);
		}
	}
	else if(fDistanceSquared > m_fMaxDistanceSquared)
	{
		fCostDueToDistance *= 100.0f;
	}

	return fBaseCost + fCostDueToDistance + fCostDueToDirection;
}

//////////////////////////////////////////////////////////////////////////////
//
// AVP_Behaviour_FaceHugger_Movement
//
//////////////////////////////////////////////////////////////////////////////

DEBUGVAR float dbg_fCollisionAvoidOverallTightMultiplier    = 0.15f;
DEBUGVAR float dbg_fCollisionAvoidOverallScalar     = 1.5f;
DEBUGVAR float dbg_fCollisionAvoidLocalScalar       = 2.0f;
DEBUGVAR float dbg_fLightAvoidLocalScalar			= 3.0f;
DEBUGVAR float dbg_fCollisionAvoidRangeBias         = 0.8f;
DEBUGVAR float dbg_fCollisionAvoidMinimumAngleDeg   = 35.0f;
DEBUGVAR float dbg_fCollisionPredictionTime		    = 0.5f;
DEBUGVAR float dbg_fCollisionMaxVelocityEffect		= 1.0f;
DEBUGVAR float dbg_fCollisionMaxFrontalMultiplier	= 2.0f;
DEBUGVAR float dbg_fCollisionMinRange				= 0.5f;
DEBUGVAR float dbg_fLightMinRange					= 1.0f;
DEBUGVAR float dbg_fCollisionAlienExtraRange		= 1.5f;
DEBUGVAR float dbg_fTargetForwardCosine = Asura_Maths::Cosine(Asura_Maths::DegToRad(30.0f));
DEBUGVAR float  dbg_fLightRangeFactor = 1.5f;
DEBUGVAR bool  dbg_bCollisionAvoidDoGlobalAngleHack = true;

DEBUGVAR float dbg_fDefaultArriveRange = 0.75f;

static int WaypointIDCompareFunction(const u_short& xKey1, const u_short& xKey2)
{
	return static_cast<int>(xKey2 - xKey1);
}

AVP_Behaviour_FaceHugger_Movement::AVP_Behaviour_FaceHugger_Movement( AVP_Brain_Actor* pxBrain )
: PARENT( pxBrain, AVP_BEHAVIOUR_TYPE_FACEHUGGER_MOVEMENT )
, m_eCurrentState( UNSET )
, m_fTimeInState( 0.0f )
, m_fArrivalRange( dbg_fDefaultArriveRange )
, m_xPositionOnRoute( Asura_Maths::ZeroVector3 )
, m_xFollowPoint( Asura_Maths::ZeroVector3 )
, m_usFollowPointWPIndex(ASURA_INVALID_WAYPOINT_ID)
, m_xDestination( Asura_Maths::ZeroVector3 )
, m_xAvoidPosition( Asura_Maths::ZeroVector3 )
, m_fAvoidDistance(0.0f)
, m_bAvoiding(false)
, m_bStuck(false)
, m_eLimitMoveMode(EMM_FAST)
, m_eRequestedMoveMode(EMM_IDLE)
, m_eActualMoveMode(EMM_IDLE)
, m_fRouteTraversedDistance( 0.0f )
, m_fRouteTotalDistance( 0.0f )
, m_usNextValidForJumpIndex(static_cast<u_short>(-1))
, m_usLastValidForJumpIndex(static_cast<u_short>(-1))
, m_xNextJumpPoint( Asura_Maths::ZeroVector3 )
, m_xWPDistanceFromStartCache(WaypointIDCompareFunction)
, m_fTimeInStuckOnPath( 0.0f )
, m_fFramesInStuckOnPath( 0.0f )
, m_fTimeCheckOrientationAndPositionStuck( 0.0f )
, m_fFramesCheckOrientationAndPositionStuck( 0.0f )
, m_fMaxRouteTraversedDistance( 0.0f )
, m_fLastMaxRouteFraction( 0.0f )
, m_xLastOrientation( Asura_Maths::IdentityMatrix3x3 )
, m_xLastPosition( Asura_Maths::ZeroVector3 )
, m_bTryToUnstuckByJumping(false)
, m_bAvoidLight(false)
#ifndef ASURA_NO_DEBUG_OUTPUT
, m_xFollowSteering( Asura_Maths::ZeroVector3 )
, m_xAvoidanceSteering( Asura_Maths::ZeroVector3 )
, m_xFinalSteering( Asura_Maths::ZeroVector3 )
#endif
{
}

bool AVP_Behaviour_FaceHugger_Movement::GetToPosition( const Asura_Vector_3& xDestination, bool bRefineRoot )
{

	AVP_Navigation::FaceHuggerAStarFunctor xFunctor;
	return GetToPosition(xDestination, xFunctor, dbg_fDefaultArriveRange, bRefineRoot);
}

bool AVP_Behaviour_FaceHugger_Movement::GetToPosition(const Asura_Vector_3& xDestination, 
													  const Asura_Vector_3& xAvoidPosition, 
													  float fMinDistance, 
													  float fMaxDistance,
													  const Asura_Vector_3& xPrefDirection)
{
	AVP_FaceHuggerAvoidenceCostCalc xFunctor(xAvoidPosition, fMinDistance, fMaxDistance, xPrefDirection);
	return GetToPosition(xDestination, xFunctor, dbg_fDefaultArriveRange, false);
}

bool AVP_Behaviour_FaceHugger_Movement::GetToPosition(const Asura_Vector_3& xDestination, AVP_Navigation::AStarFunctorBase& xFunctor, float fArriveRange, bool bRefineRoot)
{

	Asura_Assert(GetActorBrain()->CanPlanMove(), "GetToPosition is called while cannot plan a move");

	m_fArrivalRange = fArriveRange;
	m_bAvoiding = false;
	m_bStuck = false;
	m_xDestination = xDestination;
	SetInError( false );
	SetFinished( false );

	AVP_Navigation::NavData& xNavData = GetActorBrain()->GetNavData();

	xNavData.m_xStartPosition       = GetActorBrain()->GetActor()->GetPosition();
	xNavData.m_xDestinationPosition = xDestination;
	// Our current waypoint ID should already be up to date, as we keep it up to date when moving.
	// Update our destination waypoint ID.
	const AVP_Navigation::TestParams xTestParams( xNavData.m_uPathfinderFlags, xNavData.m_usCurrentZone );
	if ( !AVP_Navigation::GetNearestWaypointID( xDestination, xNavData.m_usDestinationWPID, xTestParams ) )
	{
		SetInError();
		SwitchToState( FINISHED );
		return false;
	}
	if ( ( xNavData.m_xStartPosition - xNavData.m_xDestinationPosition ).MagnitudeSquared() < m_fArrivalRange * m_fArrivalRange )
	{
		// If we're already near enough the destination then there's nothing to do.
		SwitchToState( FINISHED );
	}
	else
	{
		// Clear out the old route and find the new one.
		xNavData.ResetRoute();		
		if ( !AVP_Navigation::FindRoute( xNavData, &xFunctor, bRefineRoot ) )
		{
			SetInError();
			SwitchToState( FINISHED );
			return false;
		}

		ClearWPDistanceCache();
		if (xNavData.m_usRouteLength > 0)
		{
			m_fRouteTotalDistance = FindDistanceOfWPFromStart(xNavData.m_usRouteLength-1);
		}
		else
		{
			m_fRouteTotalDistance = 0.0f;
		}
		m_usNextValidForJumpIndex = static_cast<u_short>(-1);
		FindNextJumpIndex();
		m_fTimeInStuckOnPath = 0.0f;
		m_fFramesInStuckOnPath = 0.0f;
		m_fTimeCheckOrientationAndPositionStuck = 0.0f;
		m_fFramesCheckOrientationAndPositionStuck = 0.0f;
		m_fMaxRouteTraversedDistance = 0.0f;
		m_fLastMaxRouteFraction = 0.0f;
		m_xLastOrientation = GetActorBrain()->GetActor()->GetOrientation();
		m_xLastPosition = GetActorBrain()->GetActor()->GetPosition();
		m_bTryToUnstuckByJumping = false;

		SwitchToState( TRAVERSING_STANDARD_LINK );
	}

	return true;
}

void AVP_Behaviour_FaceHugger_Movement::SetMovementMode(AVP_Behaviour_FaceHugger_Movement::ExMovementMode eMoveMode)
{
	m_eRequestedMoveMode = eMoveMode;
}

void AVP_Behaviour_FaceHugger_Movement::SetAvoidLight()
{
	m_bAvoidLight = true;
}


float AVP_Behaviour_FaceHugger_Movement::GetLengthLeftOnPath() const
{
	return m_fRouteTotalDistance > m_fRouteTraversedDistance ? m_fRouteTotalDistance - m_fRouteTraversedDistance : 0.0f;
}


int AVP_Behaviour_FaceHugger_Movement::GetAnimStateFromMovementMode( AVP_Behaviour_FaceHugger_Movement::ExMovementMode eMovementMode )
{
	switch ( eMovementMode )
	{
	case EMM_SLOW:	return AVP_Vertebrate_AIAnimStateMachine::VERTEBRATE_STATE_MOVE_SLOW;
	case EMM_MEDIUM:	return AVP_Vertebrate_AIAnimStateMachine::VERTEBRATE_STATE_MOVE_MEDIUM;
	case EMM_FAST:	return AVP_Vertebrate_AIAnimStateMachine::VERTEBRATE_STATE_MOVE_FAST;
	case EMM_IDLE: return AVP_Vertebrate_AIAnimStateMachine::ACTOR_STATE_IDLE;
	default:
		Asura_Assert(false, "Unexpected Move mode");
		return AVP_Vertebrate_AIAnimStateMachine::ACTOR_STATE_IDLE;
	}
}


bool AVP_Behaviour_FaceHugger_Movement::HandleMessage( const Asura_Message* pxMessage )
{

	switch (pxMessage->GetMsgID())
	{
	case ASURA_MSG_MOVECHAINCOMPLETE: // end of jump
	case ASURA_MSG_MOVE_ABORT:
		if (m_eCurrentState == TRAVERSING_JUMP_LINK) 
		{
			SwitchToState(TRAVERSING_STANDARD_LINK);
			return true;

		}
		else if (m_eCurrentState == RESOLVE_STUCK_BY_JUMPING)
		{
			SetInError(true);
			SwitchToState(FINISHED);
			return true;
		}
		else if (m_eCurrentState == FALLING)
		{
			SetInError(true);
			SwitchToState(FINISHED);
			return true;
		}
		break;
	case AVP_MSGID_PHYSICS_REORIENTATION_RESET:
		if (m_eCurrentState != FALLING )
		{
			// If we've fallen off the environment we need to stop and then refind a route once we're on the ground again.
			SwitchToState( FALLING );
			return true;
		} 
	default:
		break;
	}

	return PARENT::HandleMessage( pxMessage );
}

bool AVP_Behaviour_FaceHugger_Movement::Activate()
{
#ifndef ASURA_NO_DEBUG_OUTPUT
	GetActorBrain()->SetRenderRoute( true );
#endif

	return PARENT::Activate();
}

void AVP_Behaviour_FaceHugger_Movement::Deactivate()
{
	// If the brain is not active this indicates we're shutting down. We don't access the actor/etc if this is the case
	// as the virtual table is unlikely to be valid any more.
	if ( GetActorBrain()->IsActive() )
	{
		GetActorBrain()->GetActor()->GetAIAnimStateMachine()->RequestIdle();
	}

#ifndef ASURA_NO_DEBUG_OUTPUT
	GetActorBrain()->SetRenderRoute( false );
#endif

	PARENT::Deactivate();
}

void AVP_Behaviour_FaceHugger_Movement::Update( const float fTimeslice )
{
	PARENT::Update( fTimeslice );

	switch ( m_eCurrentState )
	{
	case MOVING_TO_DESTINATION:
	case TRAVERSING_STANDARD_LINK:
	case RESOLVE_STUCK_BY_JUMPING:
	case TRAVERSING_JUMP_LINK:
	case FALLING:
		break;
	case FINISHED:
		break;

	case UNSET:
	case NUM_STATES:
	default:
		Asura_Assert( false, "AVP_Behaviour_FaceHugger_Movement::Update - bad state" );
		SetInError();
		SetFinished();
		break;
	}
}

DEBUGVAR float dbg_fLargeTurnCosine = Asura_Maths::Cosine(Asura_Maths::DegToRad(120.0f));
DEBUGVAR float dbg_fMediumTurnCosine = Asura_Maths::Cosine(Asura_Maths::DegToRad(45.0f));
DEBUGVAR float dbg_fLargeReorientCosine = Asura_Maths::Cosine(Asura_Maths::DegToRad(30.0f));
DEBUGVAR float dbg_fCloseToJumpPointSquared = 2.5f;

void AVP_Behaviour_FaceHugger_Movement::PerFrameUpdate( const float fTimeslice )
{
	PARENT::PerFrameUpdate( fTimeslice );

	m_fTimeInState += fTimeslice;

	if ( IsInError() || IsFinished() )
	{
		return;
	}

	if (NeedToRestartAfterFalling(fTimeslice))
	{
		SetInError(true);
		SwitchToState(FINISHED);
		return;
	}

	UpdatePositionOnRoute();
	UpdateMovementMode();

	switch ( m_eCurrentState )
	{
	case TRAVERSING_STANDARD_LINK:
		{
			AVP_Navigation::NavData& xNavData = GetActorBrain()->GetNavData();
			if (AVP_Navigation::IsRouteBlocked(xNavData) )
			{
				SetInError(true);
				SwitchToState(FINISHED);
				return;
			}

			// if we close enough then just stop here.
			AVP_Navigation::APParams xAPParams;
			xAPParams.m_fMaxCoplanarOffset = m_fArrivalRange;
			xAPParams.m_xDestinationNormal = Asura_AI_Navigation_System::GetWaypoint( xNavData.m_usDestinationWPID )->GetNormal();
			if ( AVP_Navigation::AtPosition( GetActorBrain()->GetActor()->GetPosition(), xNavData.m_xDestinationPosition, xAPParams ) )
			{
				SwitchToState( FINISHED );
				return;
			}


			// If we're on the last link of our route and in our last waypoint then head towards our destination.
			if ( xNavData.m_usRouteLength == 1 || 
				( xNavData.m_usRouteSegmentIndex == xNavData.m_usRouteLength - 2 && xNavData.m_usCurrentWPID == xNavData.m_usDestinationWPID ) )
			{
				SwitchToState( MOVING_TO_DESTINATION );
				return;
			}

			UpdateFollowPoint();

			// if that segment wasn't jumped already
			if (m_usNextValidForJumpIndex != static_cast<u_short>(-1))
			{
				const Asura_AI_NavVertex* pxNextJumpWP = Asura_AI_Navigation_System::GetWaypoint( xNavData.m_pusRoute[m_usNextValidForJumpIndex] );
				Asura_Vector_3 xCurrentPosition = GetActorBrain()->GetActor()->GetPosition();
				if (xNavData.m_usRouteSegmentIndex == m_usNextValidForJumpIndex || ( xCurrentPosition - m_xNextJumpPoint).MagnitudeSquared() < dbg_fCloseToJumpPointSquared || pxNextJumpWP->Contains(xCurrentPosition) )
				{
					SwitchToState(TRAVERSING_JUMP_LINK);
					return;		
				}

			}

			Asura_Vector_3 xCurrentPosition = GetActorBrain()->GetActor()->GetPosition();
			Asura_Vector_3 xToFollowPoint = m_xFollowPoint - xCurrentPosition;
			xToFollowPoint.Normalise();

			Asura_Vector_3 xAvoidanceSteering = Asura_Maths::ZeroVector3;
			CalculateAvoidanceSteering( xToFollowPoint, xAvoidanceSteering );
			bool bLargeAvoidanceSteering = false;
			if (xAvoidanceSteering.MagnitudeSquared() > 0.25 * xToFollowPoint.MagnitudeSquared())
			{
				bLargeAvoidanceSteering = true;
			}

			Asura_Vector_3 xFinalSteering = MergeSteering(xToFollowPoint, xAvoidanceSteering);
			xFinalSteering.Normalise();

			GetActorBrain()->SetMovementDirection( xFinalSteering );
			GetActorBrain()->SetLookAtPosition(m_xFollowPoint);
			GetActorBrain()->SetAimDirection(xFinalSteering);

			Asura_Vector_3 xCurrentForward;
			GetActorBrain()->GetActor()->GetHeadFowardVector(xCurrentForward);
			const Asura_Vector_3 &xNextWPNormal = Asura_AI_Navigation_System::GetWaypoint( xNavData.m_pusRoute[ xNavData.m_usRouteSegmentIndex + 1 ] )->GetNormal(); 
			const Asura_Vector_3 xOurNormal = Asura_Vector_3(-GetActorBrain()->GetActor()->GetCurrentGravityDirection());

			if (xFinalSteering * xCurrentForward < dbg_fLargeTurnCosine )
			{
				m_eLimitMoveMode = EMM_SLOW;
			}
			else if (xNextWPNormal * xOurNormal < dbg_fLargeReorientCosine)
			{
				m_eLimitMoveMode = EMM_MEDIUM;
			}
			else if (xFinalSteering * xCurrentForward < dbg_fMediumTurnCosine )
			{
				m_eLimitMoveMode = EMM_MEDIUM;
			}
			else
			{
				m_eLimitMoveMode = EMM_FAST;
			}

			UpdateStuckDetection(fTimeslice, bLargeAvoidanceSteering);

			if (m_bStuck && ! (m_bTryToUnstuckByJumping && AttemptJumpToUnStuck() ))
			{
				SetInError();
				SwitchToState( FINISHED );
			}

			break;
		}

	case TRAVERSING_JUMP_LINK:
	case RESOLVE_STUCK_BY_JUMPING:
		if (m_fTimeInState > 2.0f)
		{
			SetInError();
			SwitchToState( FINISHED );
		}
		break;

	case MOVING_TO_DESTINATION:
		{
			AVP_Navigation::NavData& xNavData = GetActorBrain()->GetNavData();
			Asura_Vector_3 xToDestination = xNavData.m_xDestinationPosition - GetActorBrain()->GetActor()->GetPosition();
			xToDestination.Normalise();

			Asura_Vector_3 xAvoidanceSteering = Asura_Maths::ZeroVector3;

			CalculateAvoidanceSteering( xToDestination, xAvoidanceSteering );

			bool bLargeAvoidanceSteering = false;
			if (xAvoidanceSteering.MagnitudeSquared() > 0.25 * xToDestination.MagnitudeSquared())
			{
				bLargeAvoidanceSteering = true;
			}

			Asura_Vector_3 xFinalSteering = MergeSteering(xToDestination, xAvoidanceSteering);

			GetActorBrain()->SetMovementDirection( xToDestination );
			GetActorBrain()->SetLookAtPosition(xNavData.m_xDestinationPosition);
			GetActorBrain()->SetAimDirection(xToDestination);

			// If we've arrived we're done.
			AVP_Navigation::APParams xAPParams;
			xAPParams.m_fMaxCoplanarOffset = m_fArrivalRange;
			xAPParams.m_xDestinationNormal = Asura_AI_Navigation_System::GetWaypoint( xNavData.m_usDestinationWPID )->GetNormal();
			if ( AVP_Navigation::AtPosition( GetActorBrain()->GetActor()->GetPosition(), xNavData.m_xDestinationPosition, xAPParams ) )
			{
				SwitchToState( FINISHED );
				return;
			}

			UpdateStuckDetection(fTimeslice, bLargeAvoidanceSteering);
			if (m_bStuck && ! (m_bTryToUnstuckByJumping && AttemptJumpToUnStuck() ))
			{
				SetInError();
				SwitchToState( FINISHED );
			}

			break;
		}

	case FALLING:
	case FINISHED:
		break;

	case UNSET:
	case NUM_STATES:
	default:
		{
			Asura_Assert( false, "AVP_Behaviour_FaceHugger_Movement::PerFrameUpdate - bad state" );
			SetInError();
			SetFinished();
			break;
		}
	}
}

#ifndef ASURA_NO_DEBUG_OUTPUT
static const char* s_aszStateStrings[] =
{
	"UNSET",
	"TRAVERSING_STANDARD_LINK",
	"TRAVERSING_JUMP_LINK",
	"MOVING_TO_DESTINATION",
	"RESOLVE_STUCK_BY_JUMPING",
	"FALLING",
	"FINISHED",
	"NUM_STATES"
};

void AVP_Behaviour_FaceHugger_Movement::RenderDebugInfo()
{
	PARENT::RenderDebugInfo();

	char cRequestedEMM = '?';
	char cLimitedEMM = '?';
	char cActualEMM = '?';

	switch (m_eLimitMoveMode)
	{
	case EMM_IDLE:
		cLimitedEMM = 'I';
		break;
	case EMM_SLOW:
		cLimitedEMM = 'S';
		break;
	case EMM_MEDIUM:
		cLimitedEMM = 'M';
		break;
	case EMM_FAST:
		cLimitedEMM = 'F';
		break;
	}

	switch (m_eRequestedMoveMode)
	{
	case EMM_IDLE:
		cRequestedEMM = 'I';
		break;
	case EMM_SLOW:
		cRequestedEMM = 'S';
		break;
	case EMM_MEDIUM:
		cRequestedEMM = 'M';
		break;
	case EMM_FAST:
		cRequestedEMM = 'F';
		break;
	}

	switch (m_eActualMoveMode)
	{
	case EMM_IDLE:
		cActualEMM = 'I';
		break;
	case EMM_SLOW:
		cActualEMM = 'S';
		break;
	case EMM_MEDIUM:
		cActualEMM = 'M';
		break;
	case EMM_FAST:
		cActualEMM = 'F';
		break;
	}


	if(m_bAvoiding)
	{
		float fDistance = (GetActorBrain()->GetActor()->GetPosition() - m_xAvoidPosition).Magnitude();
		Asura_Font_System::Print
			(
			GetProjectBrain()->GetDebugOutputPos().x,
			GetProjectBrain()->GetDebugOutputPos().y,
			0.5f,
			0xffffffff,
			ASURA_TEXTFORMAT_CENTREJUSTIFIED | ASURA_TEXTFORMAT_FORMATSTRING,
			"%s distance from target: %f %s %c %c %c ", s_aszStateStrings[ m_eCurrentState ], fDistance , 
			(fDistance < m_fAvoidDistance ? "TOO CLOSE" : " "), cActualEMM, cRequestedEMM, cLimitedEMM
			);
	}
	else
	{
		Asura_Font_System::Print
			(
			GetProjectBrain()->GetDebugOutputPos().x,
			GetProjectBrain()->GetDebugOutputPos().y,
			0.5f,
			0xffffffff,
			ASURA_TEXTFORMAT_CENTREJUSTIFIED | ASURA_TEXTFORMAT_FORMATSTRING,
			"%s %c %c %c", s_aszStateStrings[ m_eCurrentState ], cActualEMM, cRequestedEMM, cLimitedEMM 
			);
	}
	GetProjectBrain()->GetDebugOutputPos().y += 10.0f;

	Asura_DebugLineRender::AddSphere( m_xPositionOnRoute, 0.1f, 0xff0000ff, 4, 2 );
	Asura_DebugLineRender::AddSphere( m_xFollowPoint, 0.1f, 0xff00ff00, 4, 2 );
}
#endif

AVP_Brain_Actor_FaceHugger* AVP_Behaviour_FaceHugger_Movement::GetActorBrain() const
{
	return static_cast< AVP_Brain_Actor_FaceHugger* >( GetProjectBrain() );
}

void AVP_Behaviour_FaceHugger_Movement::SwitchToState( const State eNextState )
{
	// Do any work we need to exit our current state.
	switch ( m_eCurrentState )
	{
	case TRAVERSING_JUMP_LINK:
		FindNextJumpIndex();
		GetActorBrain()->SetJumping(false);
		break;
	case RESOLVE_STUCK_BY_JUMPING:
		GetActorBrain()->SetJumping(false);
		break;
	case FALLING:
		GetActorBrain()->SetFalling(false);
		break;
	case UNSET:
	case TRAVERSING_STANDARD_LINK:
	case MOVING_TO_DESTINATION:
	case FINISHED:
	case NUM_STATES:
	default:
		break;
	}

	// Do any work we need to change into our next state.
	m_eCurrentState = eNextState;
	m_fTimeInState  = 0.0f;
	switch ( m_eCurrentState )
	{
	case TRAVERSING_JUMP_LINK:
		GetActorBrain()->SetJumping(true);
		RequestJumpLink();
		break;
	case RESOLVE_STUCK_BY_JUMPING:
		GetActorBrain()->SetJumping(true);
		break;
	case MOVING_TO_DESTINATION:
	case TRAVERSING_STANDARD_LINK:
		break;	
	case FALLING:
		GetActorBrain()->SetFalling(true);
		RequestJumpMove( AVP_Move_FaceHugger_Falling::GetStaticHash(), Asura_Maths::ZeroVector3, Asura_Vector_3( 0.0f, -1.0f, 0.0f ));
		break;
	case FINISHED:
		GetActorBrain()->GetActor()->GetAIAnimStateMachine()->RequestIdle();

		Asura_Assert(!GetActorBrain()->IsJumping(), "Movement finished while still in jump");
		if (GetActorBrain()->IsJumping())
		{
			GetActorBrain()->SetJumping(false);
		}
		Asura_Assert(!GetActorBrain()->IsFalling(), "Movement finished while still in fall");
		if (GetActorBrain()->IsFalling())
		{
			GetActorBrain()->SetFalling(false);
		}
		SetFinished();
		break;
	case UNSET:
	case NUM_STATES:
	default:
		break;
	}
}

void AVP_Behaviour_FaceHugger_Movement::UpdateFollowPoint()
{
	const Asura_Vector_3& xCurrentPosition = GetActorBrain()->GetActor()->GetPosition();
	Asura_Vector_3 xActorGravityDirection;
	GetActorBrain()->GetActor()->GetPhysicsObjectHandler()->GetCurrentGravityDirection( xActorGravityDirection );
	FindNearestPointOnRoute( GetActorBrain()->GetNavData(), xCurrentPosition, -xActorGravityDirection, 2.0f, m_xFollowPoint, m_usFollowPointWPIndex);
	m_fMaxRouteTraversedDistance = Asura_Maths::Max( m_fMaxRouteTraversedDistance, m_fRouteTraversedDistance);
}

void AVP_Behaviour_FaceHugger_Movement::UpdatePositionOnRoute()
{
	AVP_Navigation::NavData& xNavData = GetActorBrain()->GetNavData();

	Asura_Vector_3 xActorGravityDirection;
	GetActorBrain()->GetActor()->GetPhysicsObjectHandler()->GetCurrentGravityDirection( xActorGravityDirection );
	FindNearestPointOnRoute( xNavData, GetActorBrain()->GetActor()->GetPosition(), -xActorGravityDirection, 0.0f, m_xPositionOnRoute, xNavData.m_usRouteSegmentIndex );
	Asura_Assert( xNavData.m_usRouteSegmentIndex < xNavData.m_usRouteLength, "AVP_Behaviour_FaceHugger_Movement::PerFrameUpdate - bug in AVP_Behaviour_FaceHugger_Movement::FindNearestPointOnRoute" );

}

void AVP_Behaviour_FaceHugger_Movement::CalculateAvoidanceSteering( const Asura_Vector_3& xFollowPointSteering, Asura_Vector_3& xSteering ) const
// Avoiding both nearby peeps and lights.
{
	// Avoiding both nearby peeps and lights.

	const Asura_Matrix_3x3& xOurOrientation = GetActorBrain()->GetActor()->GetOrientation();
	const Asura_Vector_3    xOurNormal( -GetActorBrain()->GetActor()->GetCurrentGravityDirection() );
	const Asura_Vector_3 xRight(xOurOrientation.e11, xOurOrientation.e21, xOurOrientation.e31);
	const Asura_Vector_3 xForward(xOurOrientation.e13, xOurOrientation.e23, xOurOrientation.e33);

	Asura_Vector_3 xNormalisedFollowPointSteering = xFollowPointSteering;
	xNormalisedFollowPointSteering.Normalise();
	const Asura_Vector_3 & xOurPosition = GetActorBrain()->GetActor()->GetPosition();
	u_int uNumAvoidVectors = 0;

	const float fAngleHackMinAngle	  = Asura_Maths::DegToRad( dbg_fCollisionAvoidMinimumAngleDeg );
	const float fAngleHackMinAngleDot = Asura_Maths::Cosine( fAngleHackMinAngle );

	const u_int uNumNearbyGuids = GetActorBrain()->GetNumNearbyGuids();
	for ( u_int uNearbyGuid = 0; uNearbyGuid < uNumNearbyGuids; ++uNearbyGuid )
	{
		const AVP_Brain_Actor::NearbyGuidData& xNearbyGuidData = GetActorBrain()->GetNearbyGuidData()[ uNearbyGuid ];

		for (int i = 0; i < 1; ++i) // Should be 2 - for now don't use velocity 
		{
			Asura_Vector_3 xVelocityEffect = i == 0 ? Asura_Maths::ZeroVector3 : xNearbyGuidData.m_xVelocity * dbg_fCollisionPredictionTime;
			if (xVelocityEffect.MagnitudeSquared() > dbg_fCollisionMaxVelocityEffect * dbg_fCollisionMaxVelocityEffect)
			{
				xVelocityEffect.Normalise(); xVelocityEffect *= dbg_fCollisionMaxVelocityEffect;
			}

			Asura_Vector_3 xFromVector = xOurPosition - (xNearbyGuidData.m_xPosition + xVelocityEffect );
			float fRange = xFromVector.Normalise();
			fRange = Asura_Maths::Max( fRange - dbg_fCollisionAvoidRangeBias, dbg_fCollisionMinRange );

			// If this is a fellow Alien and it's taking damage then we avoid it even more.
			if ( xNearbyGuidData.m_uClassification == AVP_ENTITYCLASS_ALIEN && xNearbyGuidData.m_bTakingDamage )
			{
				fRange = Asura_Maths::Max( fRange - dbg_fCollisionAlienExtraRange, dbg_fCollisionMinRange );
			}


			// We need to resolve the from vector into our plane using our normal.
			Asura_Vector_3 xPlaneVector = xFromVector ^ xOurNormal;
			xPlaneVector = xOurNormal ^ xPlaneVector;
			xPlaneVector.Normalise();

			float fMultiplier = 0.5f * ( 1.0f + (-xPlaneVector * xForward) );
			if ((-xPlaneVector * xForward) > dbg_fTargetForwardCosine)
			{
				fMultiplier *= dbg_fCollisionMaxFrontalMultiplier;
			}

			xSteering += xPlaneVector * fMultiplier * ( dbg_fCollisionAvoidLocalScalar / ( fRange ) );
			++uNumAvoidVectors;
		}

	}

	if (m_bAvoidLight && !GetActorBrain()->IsTightAvoidance())
	{
		const u_int uNumLights = AVP_ServerEntity_Team_Alien::GetInstance()->GetNumLights();
		for ( u_int uLight = 0; uLight < uNumLights; ++uLight )
		{

			const AVP_ServerEntity_Team_Alien::LightInfo& xLightInfo = AVP_ServerEntity_Team_Alien::GetInstance()->GetLightInfo( uLight );
			Asura_Vector_3 xClosestPositionOnLightDirection;
			Asura_Maths::GetNearestPointOnLineSegment(xLightInfo.m_xPosition, xLightInfo.m_xSourcePosition, xOurPosition, xClosestPositionOnLightDirection);
			if ((xOurPosition-xClosestPositionOnLightDirection).MagnitudeSquared() < xLightInfo.m_fRadius * xLightInfo.m_fRadius) // if we already inside the light then just ignore it ...
			{
				continue;
			}
			Asura_Vector_3 xLightDirection = xLightInfo.m_xPosition - xLightInfo.m_xSourcePosition;
			xLightDirection.Normalise();
			for (float fDistanceFromSource = 0.0f; fDistanceFromSource < xLightInfo.m_fDistance + xLightInfo.m_fRadius * dbg_fLightRangeFactor; fDistanceFromSource += 2.0f )
			{
				Asura_Vector_3 xPosition = xLightInfo.m_xSourcePosition + xLightDirection * fDistanceFromSource;
				Asura_Vector_3 xFromVector = xOurPosition - xPosition;
				float fRange= xFromVector.Normalise();
				fRange = Asura_Maths::Max(fRange - xLightInfo.m_fRadius - dbg_fCollisionAlienExtraRange, dbg_fLightMinRange);

				// We need to resolve the from vector into our plane using our normal.
				Asura_Vector_3 xPlaneVector = xFromVector ^ xOurNormal;
				xPlaneVector = xOurNormal ^ xPlaneVector;
				xPlaneVector.Normalise();

				float fMultiplier = 0.5f * ( 1.0f + (-xPlaneVector * xForward) );
				if ((-xPlaneVector * xForward) > dbg_fTargetForwardCosine)
				{
					fMultiplier *= dbg_fCollisionMaxFrontalMultiplier;
				}

				xSteering += xPlaneVector * fMultiplier * ( dbg_fLightAvoidLocalScalar / ( fRange ) );
				++uNumAvoidVectors;

			}
		}
	}

	if ( uNumAvoidVectors > 1 )
	{
		xSteering /= static_cast< float >( uNumAvoidVectors );
	}

	xSteering *= dbg_fCollisionAvoidOverallScalar;

	if ( dbg_bCollisionAvoidDoGlobalAngleHack )
	{
		Asura_Vector_3 xGlobalPlaneVector = xSteering;
		float          fGlobalPlaneVector = xGlobalPlaneVector.Normalise();

		// Make sure the from vector meets the follow point steering at a minimum angle,
		// so that we don't end up with sudden movements when meeting an obstacle head on.
		const float fDot = -xGlobalPlaneVector * xNormalisedFollowPointSteering;
		if ( fDot > fAngleHackMinAngleDot )
		{

			float fTurnSign = 1.0f;

			if(xRight * xGlobalPlaneVector < 0.0f ) // turn left 
			{
				fTurnSign = -1.0f;
			}

			// Work out the direction of the rotation using the plane vector in our local space.
			// 			const Asura_Vector_3 xLocalPlaneVector = xOurOrientation.ApplyTranspose( xGlobalPlaneVector );
			// 
			// 			// Rotate our backward direction by the required amount (in the required direction).
			// 			const Asura_Vector_3 xOurUp( xOurOrientation.e12, xOurOrientation.e22, xOurOrientation.e32 );
			Asura_Quat xRotationQuat;
			xRotationQuat.FromRotation( fTurnSign * fAngleHackMinAngle, xOurNormal );
			xGlobalPlaneVector = xRotationQuat.RotateVector( -xNormalisedFollowPointSteering);
		}

		xGlobalPlaneVector *= fGlobalPlaneVector;
		xSteering = xGlobalPlaneVector;
	}

	if (GetActorBrain()->IsTightAvoidance())
	{
		xSteering *= dbg_fCollisionAvoidOverallTightMultiplier;
	}
}


Asura_Vector_3 AVP_Behaviour_FaceHugger_Movement::MergeSteering(const Asura_Vector_3& xToDirection, const Asura_Vector_3& xAvoidanceDirection) const
{
	AVP_Navigation::NavData& xNavData = GetActorBrain()->GetNavData();
	if ( xAvoidanceDirection == Asura_Maths::ZeroVector3 )
	{
		return xToDirection;
	}

	Asura_Vector_3 xFinalSteering = ( xToDirection + xAvoidanceDirection ) * 0.5f;

	// check that we are not going to end outside of the waypoint map
	Asura_Vector_3 xCurrentPosition = GetActorBrain()->GetActor()->GetPosition();
	Asura_Vector_3 xFuturePosition = xCurrentPosition + xFinalSteering * 2.0f;
	const AVP_Navigation::TestParams xTestParams( xNavData.m_uPathfinderFlags, xNavData.m_usCurrentZone );
	u_short usWPID;
	if( !AVP_Navigation::GetNearestWaypointID( xFuturePosition, usWPID, xTestParams ) )
	{
		Asura_Assert(false, "Nearest waypoint not found while navigating.");
		xFinalSteering = xToDirection;
	}
	else
	{   // if the future position is not inside the waypoint space - almost ignore the steering correction.
		// 
		if (!Asura_AI_Navigation_System::IsPositionInWaypoint(xFuturePosition, usWPID))
		{
			xFinalSteering = xToDirection * 0.9f + xAvoidanceDirection * 0.1f;
		}
	}

	return xFinalSteering;
}

void AVP_Behaviour_FaceHugger_Movement::UpdateMovementMode()
{

	AVP_Brain_Actor* pxBrain = GetActorBrain();

	AVP_Vertebrate_AIAnimStateMachine* pxVertStateMachine = static_cast< AVP_Vertebrate_AIAnimStateMachine* >( GetActorBrain()->GetActor()->GetAIAnimStateMachine() );
	m_eActualMoveMode = Asura_Maths::Min(m_eRequestedMoveMode, m_eLimitMoveMode);
	switch ( m_eActualMoveMode )
	{
	case EMM_IDLE:
		{
			float fNewVel = 8.0f * Asura_Maths::Pi;
			pxBrain->SetMaxMoveDirAngularVelocity( fNewVel );
			pxBrain->SetMaxAimDirAngularVelocity( fNewVel );
			pxBrain->SetMaxLookDirAngularVelocity( fNewVel );
			pxVertStateMachine->RequestIdle();
		}
		break;
	case EMM_SLOW:
		{
			float fNewVel = 8.0f * Asura_Maths::Pi;
			pxBrain->SetMaxMoveDirAngularVelocity( fNewVel );
			pxBrain->SetMaxAimDirAngularVelocity( fNewVel );
			pxBrain->SetMaxLookDirAngularVelocity( fNewVel );
			pxVertStateMachine->RequestMoveSlow();
		}
		break;
	case EMM_FAST:
		{
			float fNewVel = 2.0f * Asura_Maths::Pi;
			pxBrain->SetMaxMoveDirAngularVelocity( fNewVel );
			pxBrain->SetMaxAimDirAngularVelocity( fNewVel );
			pxBrain->SetMaxLookDirAngularVelocity( fNewVel );
			pxVertStateMachine->RequestMoveFast();
		}
		break;
	case EMM_MEDIUM:
		{
			float fNewVel = 6.0f * Asura_Maths::Pi;
			pxBrain->SetMaxMoveDirAngularVelocity( fNewVel );
			pxBrain->SetMaxAimDirAngularVelocity( fNewVel );
			pxBrain->SetMaxLookDirAngularVelocity( fNewVel );
			pxVertStateMachine->RequestMoveMedium();
		}
		break;

	} // end switch MovementMode
}


bool AVP_Behaviour_FaceHugger_Movement::NeedToRestartAfterFalling( float fTimeslice )
{
	// we were falling and now landed
	// if we landed outside the next waypoint be conservative and fail the movement 
	// let higher level figure it out.
	if (static_cast< AVP_Brain_Actor_FaceHugger*>(GetActorBrain())->JustFinishedFalling())
	{
		AVP_Navigation::NavData& xNavData = GetActorBrain()->GetNavData();
		const Asura_Vector_3& xCurrentPosition = GetActorBrain()->GetActor()->GetPosition();
		if (xNavData.m_usRouteSegmentIndex+1 < xNavData.m_usRouteLength)
		{
			const Asura_AI_NavVertex* pxWP = Asura_AI_Navigation_System::GetWaypoint( xNavData.m_pusRoute[xNavData.m_usRouteSegmentIndex+1] );
			Asura_Assert(pxWP, "Could not find waypoint while moving");
			if (pxWP->Contains(xCurrentPosition))
			{
				return true;
			}
		}
	}

	return false; 
}

bool AVP_Behaviour_FaceHugger_Movement::FindNearestPointOnRoute( const AVP_Navigation::NavData& xNavData, const Asura_Vector_3& xCurrentPosition, const Asura_Vector_3& xActorUp,
																 float fLookAheadDistance, Asura_Vector_3& xNearestPosition, u_short& usNearestRouteSegmentIndex )
{
	m_fRouteTraversedDistance = 0.0f;

	if ( xNavData.m_usRouteLength == 0 )
	{
		Asura_Assert( false, "AVP_Behaviour_FaceHugger_Movement::FindNearestPointOnRoute - zero route length" );
		xNearestPosition = xCurrentPosition;
		usNearestRouteSegmentIndex = 0;
		return false;
	}
	if ( xNavData.m_usRouteSegmentIndex >= xNavData.m_usRouteLength - 1 )
	{
		const Asura_AI_NavVertex* pxDestWP = Asura_AI_Navigation_System::GetWaypoint( xNavData.m_usDestinationWPID );
		Asura_Assert( pxDestWP, "AVP_Behaviour_FaceHugger_Movement::FindNearestPointOnRoute - no destination waypoint found" );
		if ( pxDestWP )
		{
			xNearestPosition = pxDestWP->m_xPosition;
		}
		usNearestRouteSegmentIndex = 0;
		m_fRouteTraversedDistance = FindDistanceOfWPFromStart(xNavData.m_usRouteLength-1);
		return true;
	}

	Asura_Vector_3 xCurrentSegmentStart;
	Asura_Vector_3 xCurrentSegmentEnd;

	usNearestRouteSegmentIndex = xNavData.m_usRouteSegmentIndex;

	float fLeastDistanceSq = Asura_Maths::LargeFloat;
	for ( u_short usSegmentIndex = xNavData.m_usRouteSegmentIndex;
		usSegmentIndex < xNavData.m_usRouteLength - 1 && usSegmentIndex < xNavData.m_usRouteSegmentIndex + 5 && 
		usSegmentIndex < m_usNextValidForJumpIndex;
		++usSegmentIndex )
	{
		const Asura_AI_NavVertex* pxCurrentWP = Asura_AI_Navigation_System::GetWaypoint( xNavData.m_pusRoute[ usSegmentIndex ] );
		xCurrentSegmentStart = pxCurrentWP->m_xPosition;
		xCurrentSegmentEnd   = Asura_AI_Navigation_System::GetWaypoint( xNavData.m_pusRoute[ usSegmentIndex + 1 ] )->m_xPosition;

		Asura_Vector_3 xPosition;
		Asura_Maths::GetNearestPointOnLineSegment( xCurrentSegmentStart, xCurrentSegmentEnd, xCurrentPosition, xPosition );
		const float fDistanceSq = ( xPosition - xCurrentPosition ).MagnitudeSquared();
		if ( fDistanceSq < fLeastDistanceSq )
		{
			fLeastDistanceSq           = fDistanceSq;
			usNearestRouteSegmentIndex = usSegmentIndex;
			xNearestPosition           = xPosition;
		}
	}

	const Asura_AI_NavVertex* pxFromWP = Asura_AI_Navigation_System::GetWaypoint( xNavData.m_pusRoute[ usNearestRouteSegmentIndex ] );
	const Asura_AI_NavVertex* pxToWP   = Asura_AI_Navigation_System::GetWaypoint( xNavData.m_pusRoute[ usNearestRouteSegmentIndex + 1 ] );
	xCurrentSegmentStart = pxFromWP->m_xPosition;
	xCurrentSegmentEnd   = pxToWP->m_xPosition;

	Asura_Assert(usNearestRouteSegmentIndex < xNavData.m_usRouteLength - 1, "Segment mismatch");

	if ( usNearestRouteSegmentIndex == xNavData.m_usRouteLength - 2 && xNearestPosition.ApproximatelyEquals( xCurrentSegmentEnd ) )
	{
		m_fRouteTraversedDistance = FindDistanceOfWPFromStart(xNavData.m_usRouteLength-1);
		return true;
	}

	// calculate the position in our plane and project back to the segment - this will prevent going backward.
	AVP_Plane xOurPlane(xCurrentPosition, xActorUp );
	Asura_Vector_3 xCurrentSegmentStartInOurPlane;
	Asura_Vector_3 xCurrentSegmentEndInOurPlane;
	xOurPlane.CalculateClosestPoint(xCurrentSegmentStart, xCurrentSegmentStartInOurPlane);
	xOurPlane.CalculateClosestPoint(xCurrentSegmentEnd, xCurrentSegmentEndInOurPlane);

	float fLamda;

	const AVP_Navigation::LinkTransitionType eLTT = AVP_Navigation::GetLinkTransitionType( xNavData.m_pusRoute[usNearestRouteSegmentIndex], xNavData.m_pusRoute[ usNearestRouteSegmentIndex + 1 ] );
	if (eLTT == AVP_Navigation::LTT_FLOOR_TO_CEILING || 
		eLTT == AVP_Navigation::LTT_CEILING_TO_FLOOR ||
		eLTT == AVP_Navigation::LTT_WALL_TO_WALL_180)
	{
		fLamda = 0.0f; // for jump links make sure we dont skip them
	}
	else
	{
		if (xCurrentSegmentStartInOurPlane.ApproximatelyEquals(xCurrentSegmentEndInOurPlane)) // if the projection is almost a point select the start segment as our position
		{
			fLamda = 1.0f;
		}
		else
		{
			Asura_Vector_3 xPositionInOurPlane;
			Asura_Maths::GetNearestPointOnLineSegment( xCurrentSegmentStartInOurPlane, xCurrentSegmentEndInOurPlane, xCurrentPosition, xPositionInOurPlane );

			fLamda = (xPositionInOurPlane - xCurrentSegmentStartInOurPlane).Magnitude() / (xCurrentSegmentEndInOurPlane-xCurrentSegmentStartInOurPlane).Magnitude();		
		}
	}


	xNearestPosition = xCurrentSegmentStart + (xCurrentSegmentEnd-xCurrentSegmentStart) * fLamda;

	m_fRouteTraversedDistance = FindDistanceOfWPFromStart(usNearestRouteSegmentIndex) + (xNearestPosition-xCurrentSegmentStart).Magnitude();

	const Asura_AI_NavConnection* pxSegmentLink = pxFromWP->GetLinkTo( xNavData.m_pusRoute[ usNearestRouteSegmentIndex + 1 ] );
	// These always pass first time, because we always want to move along the segment we're currently on.
	bool bDone = false;
	float fActorUpFromNormalDot = 1.0f;
	while ( fLookAheadDistance > 0.0f && !bDone )
	{
		if ( usNearestRouteSegmentIndex == m_usNextValidForJumpIndex || fActorUpFromNormalDot < dbg_fLargeReorientCosine )
		{
			bDone = true;
		}
		else
		{
			Asura_Vector_3 xSegmentDirection = xCurrentSegmentEnd - xCurrentSegmentStart;
			xSegmentDirection.Normalise();
			const float fToSegmentEnd = ( xCurrentSegmentEnd - xNearestPosition ).Magnitude();

			xNearestPosition   += xSegmentDirection * Asura_Maths::Min( fLookAheadDistance, fToSegmentEnd );
			fLookAheadDistance -= fToSegmentEnd;
			if ( fLookAheadDistance > 0.0f )
			{
				if ( ++usNearestRouteSegmentIndex < xNavData.m_usRouteLength - 1 )
				{
					pxFromWP = Asura_AI_Navigation_System::GetWaypoint( xNavData.m_pusRoute[ usNearestRouteSegmentIndex ] );
					pxToWP   = Asura_AI_Navigation_System::GetWaypoint( xNavData.m_pusRoute[ usNearestRouteSegmentIndex + 1 ] );
					xCurrentSegmentStart = pxFromWP->m_xPosition;
					xCurrentSegmentEnd   = pxToWP->m_xPosition;

					pxSegmentLink = pxFromWP->GetLinkTo( xNavData.m_pusRoute[ usNearestRouteSegmentIndex + 1 ] );
					fActorUpFromNormalDot = xActorUp * pxFromWP->GetNormal();
				}
				else
				{
					usNearestRouteSegmentIndex = xNavData.m_usRouteLength - 1;
					xNearestPosition = Asura_AI_Navigation_System::GetWaypoint( xNavData.m_usDestinationWPID )->m_xPosition;
					return true;
				}
			}
		}
	}

	return false;
}

void AVP_Behaviour_FaceHugger_Movement::ClearWPDistanceCache()
{
	m_xWPDistanceFromStartCache.Reset();

}

float AVP_Behaviour_FaceHugger_Movement::FindDistanceOfWPFromStart(u_short usWPNumber)
{
	if (usWPNumber < m_xWPDistanceFromStartCache.GetNumberOfNodes())
	{
		const float* pfDistance = m_xWPDistanceFromStartCache.GetData(usWPNumber);
		Asura_Assert(pfDistance, "Distance on route mismatch");
		if (pfDistance)
		{
			return *pfDistance;
		}
		else
		{
			return 0.0f;
		}
	}

	AVP_Navigation::NavData& xNavData = GetActorBrain()->GetNavData();
	Asura_Assert(usWPNumber < xNavData.m_usRouteLength, "Requesting disatnce for waypoint not on route");

	if (m_xWPDistanceFromStartCache.GetNumberOfNodes() == 0)
	{
		u_short usZero = 0;
		const float* pfSuccess = m_xWPDistanceFromStartCache.AddNode(usZero, 0.0f);
		Asura_Assert(pfSuccess, "Updating distance cache failed");
		if (!pfSuccess)
		{
			return 0.0f;
		}
	}

	const float* pfDistance = m_xWPDistanceFromStartCache.GetData(static_cast<u_short>(m_xWPDistanceFromStartCache.GetNumberOfNodes()-1));
	Asura_Assert(pfDistance, "Updating distance cache failed");
	if (!pfDistance)
	{
		return 0.0f;
	}

	float fDistance = *pfDistance;

	u_short usCurrWPNumber = static_cast<u_short>(m_xWPDistanceFromStartCache.GetNumberOfNodes());

	while (usCurrWPNumber <= usWPNumber)
	{
		const Asura_Vector_3& xPrevWPPosition = Asura_AI_Navigation_System::GetWaypoint( xNavData.m_pusRoute[ usCurrWPNumber-1 ] )->m_xPosition;
		const Asura_Vector_3& xCurrPosition = Asura_AI_Navigation_System::GetWaypoint( xNavData.m_pusRoute[ usCurrWPNumber ] )->m_xPosition;

		fDistance += (xCurrPosition -  xPrevWPPosition).Magnitude();
		m_xWPDistanceFromStartCache.AddNode(usCurrWPNumber, fDistance);
		++usCurrWPNumber;
	}

	return fDistance;
}

void AVP_Behaviour_FaceHugger_Movement::FindNextJumpIndex()
{

	u_short usNextValidForJumpIndex;

	if (m_usNextValidForJumpIndex == static_cast<u_short>(-1))
	{
		usNextValidForJumpIndex = 0;
		m_usLastValidForJumpIndex= 0;
	}
	else
	{
		usNextValidForJumpIndex = m_usNextValidForJumpIndex + 1;
		m_usLastValidForJumpIndex = m_usNextValidForJumpIndex ;

	}

	m_usNextValidForJumpIndex = static_cast<u_short>(-1);

	AVP_Navigation::NavData& xNavData = GetActorBrain()->GetNavData();
	while (usNextValidForJumpIndex < xNavData.m_usRouteLength -1 )
	{
		const AVP_Navigation::LinkTransitionType eLTT = AVP_Navigation::GetLinkTransitionType( xNavData.m_pusRoute[usNextValidForJumpIndex], xNavData.m_pusRoute[ usNextValidForJumpIndex + 1 ] );
		if (eLTT == AVP_Navigation::LTT_FLOOR_TO_CEILING || 
			eLTT == AVP_Navigation::LTT_CEILING_TO_FLOOR ||
			eLTT == AVP_Navigation::LTT_WALL_TO_WALL_180)
		{
			const Asura_AI_NavVertex* pxWP = Asura_AI_Navigation_System::GetWaypoint( xNavData.m_pusRoute[usNextValidForJumpIndex] );
			Asura_Assert(pxWP, "Waypoint not found while on route");
			if (pxWP)
			{
				m_xNextJumpPoint = pxWP->GetPosition();
			}
			else
			{
				m_xNextJumpPoint = GetActorBrain()->GetActor()->GetPosition(); 
			}
			m_usNextValidForJumpIndex = usNextValidForJumpIndex;
			return;
		}
		++usNextValidForJumpIndex;
	}		
}

DEBUGVAR float dbg_fTestTimeScaleForFastSpeed = 1.0f;
DEBUGVAR float dbg_fTestTimeScaleForMediumSpeed = 1.5f;
DEBUGVAR float dbg_fTestTimeScaleForSlowSpeed = 3.0f;
DEBUGVAR float dbg_fTestTimeScaleAtStartOfPath = 3.0f;
DEBUGVAR float dbg_fTestTimeScaleAtAvoidanceOfPath = 4.0f;
DEBUGVAR float dbg_fTestTimeForMovementOnPath = 1.0f;
DEBUGVAR float dbg_fTestFramesForMovementOnPath = dbg_fTestTimeForMovementOnPath * 20.0f; // assume 20 frames per seconds
DEBUGVAR float dbg_fTestTimeForMovementAndReorintation = 0.3f;
DEBUGVAR float dbg_fTestFramesForMovementAndReorintation = dbg_fTestTimeForMovementAndReorintation * 20.0f; // assume 20 frames per seconds
DEBUGVAR float dbg_fTestVelocityScale = 0.5f;
DEBUGVAR float dbg_fTestMinimalOrientationCos = Asura_Maths::Cosine(Asura_Maths::DegToRad(2.0f));

void AVP_Behaviour_FaceHugger_Movement::UpdateStuckDetection( const float fTimeslice, bool bLargeAvoidanceSteering )
{
	if (m_bStuck)
	{
		return;
	}

	// check movement on the path
	if (m_fMaxRouteTraversedDistance > m_fLastMaxRouteFraction + 1.0f * fTimeslice )
	{
		m_fTimeInStuckOnPath = 0.0f;
		m_fFramesInStuckOnPath = 0.0f;
		m_fLastMaxRouteFraction = m_fMaxRouteTraversedDistance;
	}
	else
	{
		float fSpeedScale = 1.0f;
		switch( m_eActualMoveMode)
		{
		case AVP_Navigation::MM_FAST:
			fSpeedScale = dbg_fTestTimeScaleForFastSpeed;
			break;
		default:
		case AVP_Navigation::MM_MEDIUM:
			fSpeedScale = dbg_fTestTimeScaleForMediumSpeed;
			break;
		case AVP_Navigation::MM_SLOW:
			fSpeedScale = dbg_fTestTimeScaleForSlowSpeed;
			break;
		}
		if (m_fMaxRouteTraversedDistance == 0.0f)
		{
			m_fTimeInStuckOnPath += fTimeslice / (dbg_fTestTimeScaleAtStartOfPath * fSpeedScale);
			m_fFramesInStuckOnPath += 1.0f / (dbg_fTestTimeScaleAtStartOfPath * fSpeedScale);
		}
		else if (bLargeAvoidanceSteering)
		{
			m_fTimeInStuckOnPath += fTimeslice / (dbg_fTestTimeScaleAtAvoidanceOfPath * fSpeedScale);
			m_fFramesInStuckOnPath += 1.0f / (dbg_fTestTimeScaleAtAvoidanceOfPath * fSpeedScale);
		}
		else
		{
			m_fTimeInStuckOnPath += fTimeslice / fSpeedScale;
			m_fFramesInStuckOnPath += 1.0f / fSpeedScale;
		}

		if ( m_fTimeInStuckOnPath > dbg_fTestTimeForMovementOnPath && m_fFramesInStuckOnPath > dbg_fTestFramesForMovementOnPath )
		{
#if !defined(ASURA_NO_DEBUG_OUTPUT) && defined(_DEBUG)
			Asura_Vector_3 xPosition = GetActorBrain()->GetActor()->GetPosition();
			Asura_OutputToDebugger(("%d FaceHugger %d stuck - did not advance on its path for %f seconds and %f frames, position %.2f,%.2f,%.2f\n",
				Asura_Timers::GetFrameNumber(), GetActorBrain()->GetActor()->GetGuid(), m_fTimeInStuckOnPath, m_fFramesInStuckOnPath, 
				xPosition.x, xPosition.y, xPosition.z));
#endif
			m_bStuck = true;
			return;
		}
	}

	// check reorientation and position
	m_fTimeCheckOrientationAndPositionStuck += fTimeslice;
	m_fFramesCheckOrientationAndPositionStuck += 1.0f;

	if (m_fTimeCheckOrientationAndPositionStuck > dbg_fTestTimeForMovementAndReorintation && 
		m_fFramesCheckOrientationAndPositionStuck > dbg_fTestFramesForMovementAndReorintation)
	{
		bool bStuckPosition = true;
		bool bStuckOrientation = true;

		const Asura_Vector_3& xCurrentPosition = GetActorBrain()->GetActor()->GetPosition();	


		float fAvarageVelocity = (xCurrentPosition - m_xLastPosition).Magnitude() / m_fTimeCheckOrientationAndPositionStuck;
		m_xLastPosition = xCurrentPosition;

		if (fAvarageVelocity  > 1.0f * dbg_fTestVelocityScale )
		{
			bStuckPosition = false;
		}

		const Asura_Matrix_3x3& xCurentOrientation = GetActorBrain()->GetActor()->GetOrientation();
		Asura_Vector_3 xCurrVector(xCurentOrientation.e13, xCurentOrientation.e23, xCurentOrientation.e33);
		Asura_Vector_3 xLastVector(m_xLastOrientation.e13, m_xLastOrientation.e23, m_xLastOrientation.e33);

		if (xCurrVector * xLastVector < dbg_fTestMinimalOrientationCos)
		{
			bStuckOrientation = false;
		}
		else
		{
			xCurrVector = Asura_Vector_3(xCurentOrientation.e12, xCurentOrientation.e22, xCurentOrientation.e32);
			xLastVector = Asura_Vector_3(m_xLastOrientation.e12, m_xLastOrientation.e22, m_xLastOrientation.e32);
			if (xCurrVector * xLastVector < dbg_fTestMinimalOrientationCos)
			{
				bStuckOrientation = false;
			}
		}

		m_xLastOrientation = xCurentOrientation;
		m_bStuck = bStuckOrientation & bStuckPosition;
		m_bTryToUnstuckByJumping = bStuckOrientation & bStuckPosition; // if we really got stuck because lack of movement - can try to resolve this by jumping to the next waypoint

#if !defined(ASURA_NO_DEBUG_OUTPUT) && defined(_DEBUG)
		if (m_bStuck)
		{
			Asura_Vector_3 xPosition = GetActorBrain()->GetActor()->GetPosition();
			Asura_OutputToDebugger(("%d FaceHugger %d stuck - did not orientation and position did not change for %f seconds and %f frames, position %.2f,%.2f,%.2f (try to unstuck by jumping)\n",
				Asura_Timers::GetFrameNumber(), GetActorBrain()->GetActor()->GetGuid(), m_fTimeCheckOrientationAndPositionStuck, m_fFramesCheckOrientationAndPositionStuck,
				xPosition.x, xPosition.y, xPosition.z));
		}
#endif
		m_fFramesCheckOrientationAndPositionStuck = 0.0f;
		m_fTimeCheckOrientationAndPositionStuck = 0.0f;
	}
}

DEBUGVAR float dbg_fMaxJumpDistanceSquared = 6.0f*6.0f;

bool AVP_Behaviour_FaceHugger_Movement::AttemptJumpToUnStuck()
{

	const AVP_Navigation::NavData& xNavData = GetActorBrain()->GetNavData();
	const Asura_Vector_3& xCurrentPosition = GetActorBrain()->GetActor()->GetPosition();

	const Asura_AI_NavVertex* pxCurrentWP = Asura_AI_Navigation_System::GetWaypoint( xNavData.m_usCurrentWPID );
	if (!pxCurrentWP)
	{
		return false;
	}
	const Asura_AI_NavVertex* pxDestinationWP;
	u_short usDestinationWPID;

	if (xNavData.m_usRouteLength == 0)
	{
		return false;
	}
	else if (xNavData.m_usRouteSegmentIndex > xNavData.m_usRouteLength - 2 )
	{
		usDestinationWPID = xNavData.m_pusRoute[xNavData.m_usRouteLength - 1];

	}
	else
	{
		usDestinationWPID = xNavData.m_pusRoute[xNavData.m_usRouteSegmentIndex + 1];
	}

	pxDestinationWP = Asura_AI_Navigation_System::GetWaypoint( usDestinationWPID );
	if ( !pxDestinationWP )
	{
		return false;	
	}

	if((pxDestinationWP->GetPosition() - xCurrentPosition).MagnitudeSquared() > dbg_fMaxJumpDistanceSquared)
	{
		AVP_Navigation::NavData xTempNavData;
		xNavData.CopyConfigTo(xTempNavData);
		xTempNavData.ResetRoute();
		xTempNavData.m_xStartPosition       = GetActorBrain()->GetActor()->GetPosition();
		xTempNavData.m_usDestinationWPID	= usDestinationWPID;
		xTempNavData.m_xDestinationPosition = pxDestinationWP->GetPosition();

		AVP_Navigation::FaceHuggerAStarFunctor xFunctor;		
		if ( AVP_Navigation::FindRoute( xTempNavData, &xFunctor, false ) )
		{
			return false;
		}

		pxDestinationWP = NULL;
		float fMaxDistanceSquared =0.0f;

		for (u_short u = 1; u < xTempNavData.m_usRouteLength; ++u )
		{
			const Asura_AI_NavVertex* pxCandidateWP = Asura_AI_Navigation_System::GetWaypoint( xNavData.m_pusRoute[u] );
			if (!pxCandidateWP)
			{
				continue;
			}
			float fCandidateDistanceSquared = (pxCandidateWP->GetPosition() - xCurrentPosition).MagnitudeSquared();
			if (fCandidateDistanceSquared > fMaxDistanceSquared &&  
				fCandidateDistanceSquared < dbg_fMaxJumpDistanceSquared)
			{
				fMaxDistanceSquared = fCandidateDistanceSquared;
				pxDestinationWP = pxCandidateWP;
			}
		}
	}

	if (pxDestinationWP == NULL)
	{
		return false;
	}

	Asura_Hash_ID uJumpMoveHashID = AVP_Move_FaceHugger_Jump::GetStaticHash();
	const Asura_Movement_System* pxMovementSystem = GetActorBrain()->GetClientActor()->GetMovementSystem();
	if ( !pxMovementSystem ) 
	{
		return false; 
	}
	const AVP_Move* pxJumpMove = static_cast< const AVP_Move* >( pxMovementSystem->GetMove( uJumpMoveHashID ) );

	if ( !pxJumpMove ) 
	{
		return false; 
	}

	const Asura_Hash_ID uJumpAnimHashID = pxJumpMove->GetThirdPersonAnimHash();
	if ( uJumpAnimHashID == ASURA_HASH_ID_UNSET ) { return false; }

	// Fire the jump move off.
	
	RequestJumpMove( uJumpMoveHashID, pxDestinationWP->GetPosition(), pxDestinationWP->GetNormal());
	SwitchToState( RESOLVE_STUCK_BY_JUMPING );
	return true;
}

void AVP_Behaviour_FaceHugger_Movement::RequestJumpLink()
{
	AVP_Navigation::NavData& xNavData = GetActorBrain()->GetNavData();
	const Asura_AI_NavVertex* pxDestWP = Asura_AI_Navigation_System::GetWaypoint( xNavData.m_pusRoute[ m_usNextValidForJumpIndex + 1 ] );

	Asura_Hash_ID uJumpMoveHashID = AVP_Move_FaceHugger_Jump::GetStaticHash();
	const Asura_Movement_System* pxMovementSystem = GetActorBrain()->GetClientActor()->GetMovementSystem();
	if ( !pxMovementSystem ) 
	{
		SetInError(true);
		SwitchToState(FINISHED);
		return; 
	}
	const AVP_Move* pxJumpMove = static_cast< const AVP_Move* >( pxMovementSystem->GetMove( uJumpMoveHashID ) );

	if ( !pxJumpMove ) 
	{
		SetInError(true);
		SwitchToState(FINISHED);
		return; 
	}

	const Asura_Hash_ID uJumpAnimHashID = pxJumpMove->GetThirdPersonAnimHash();
	if ( uJumpAnimHashID == ASURA_HASH_ID_UNSET ) 
	{
		SetInError(true);
		SwitchToState(FINISHED);
		return; 
	}

	// Fire the jump move off.

	RequestJumpMove( uJumpMoveHashID, pxDestWP->GetPosition(), pxDestWP->GetNormal());
}


void AVP_Behaviour_FaceHugger_Movement::RequestJumpMove( Asura_Hash_ID uMoveHashID, const Asura_Vector_3& xDestPos, const Asura_Vector_3& xDestNormal )
{
	Asura_Message* pxMessage = Asura_Network::CreateMessage( ASURA_MSG_MOVE_START, sizeof( AVP_Message_FaceHugger_Move_JumpStart ) );
	Asura_Assert(pxMessage, "RequestJump create message failed");
	if ( pxMessage != NULL )
	{
		AVP_Message_FaceHugger_Move_JumpStart* pxData = static_cast< AVP_Message_FaceHugger_Move_JumpStart* >( pxMessage->GetData() );

		pxMessage->SetFromGuid( GetActorBrain()->GetOwnerGuid() );
		pxMessage->SetToGuid( GetActorBrain()->GetOwnerGuid() );

		pxData->ClearFlags();
		// pxData->SetForceAbort( true );
		pxData->SetIgnoredByOwner( false );
		pxData->SetMoveID( uMoveHashID );

		Asura_Matrix_3x3 xOrientation;
		CalculateJumpDestinationOrientation(xOrientation, xDestPos, xDestNormal);

		pxData->SetDestinationOrientation( xOrientation );
		pxData->SetDestinationPosition( xDestPos );
		
		u_int uAnimState = GetAnimStateFromMovementMode(m_eActualMoveMode);

		AVP_Actor_AIAnimStateMachine* pxVertStateHandler = GetActorBrain()->GetActor()->GetAIAnimStateMachine();
		pxVertStateHandler->RequestInMove( pxMessage, uAnimState );
	}
	else
	{
		SetInError(true);
		SwitchToState(FINISHED);
	}
}

DEBUGVAR float dbg_fSmallRotationToResolveAmbiguity = Asura_Maths::DegToRad(5.0f);

void AVP_Behaviour_FaceHugger_Movement::CalculateJumpDestinationOrientation(Asura_Matrix_3x3& xOrientation, Asura_Vector_3 xDestinationPosition, Asura_Vector_3 xDestinationNormal) const
{
	xOrientation = GetActorBrain()->GetActor()->GetOrientation();
	Asura_Vector_3 xStartPosition = GetActorBrain()->GetActor()->GetPosition();		
	Asura_Vector_3 xStartNormal(-xOrientation.e12, -xOrientation.e22, -xOrientation.e32);
	Asura_Vector_3 xStartForward(xOrientation.e13, xOrientation.e23, xOrientation.e33);

	Asura_Quat xTurnQuad;

	// need to avoid 180 deg turn as in this case the turn is arbitrary 
	if (xStartNormal * xDestinationNormal < -0.99)
	{
		xTurnQuad.FromRotation(Asura_Random::GetInt(0,1) == 0 ? dbg_fSmallRotationToResolveAmbiguity : -dbg_fSmallRotationToResolveAmbiguity , xStartForward);
		xStartNormal = xTurnQuad.RotateVector(xStartNormal);
	}

	xTurnQuad.RotationFromAToB(xStartNormal, xDestinationNormal);
	Asura_Matrix_3x3 xRotation;
	xTurnQuad.ToMatrix(xRotation);
	xOrientation = xRotation * xOrientation;
}

//////////////////////////////////////////////////////////////////////////////
//
// AVP_Behaviour_FaceHugger_GS2_Movement
//
//////////////////////////////////////////////////////////////////////////////

DEBUGVAR int dbg_iMaxRetries(3); 

AVP_Behaviour_FaceHugger_GS2_Movement::AVP_Behaviour_FaceHugger_GS2_Movement( AVP_Brain_Actor* pxBrain )
: PARENT( pxBrain, AVP_BEHAVIOUR_TYPE_FACEHUGGER_GS2_MOVEMENT )
, m_bIsMovePending(false)
, m_xPosition(Asura_Maths::ZeroVector3)
, m_iRetries(dbg_iMaxRetries)
{
}

bool AVP_Behaviour_FaceHugger_GS2_Movement::GetToPosition( const Asura_Vector_3& xPosition )
{
	m_xPosition = xPosition;

	if (GetActorBrain()->CanPlanMove())
	{
		bool bSuccess = false;

		AVP_Behaviour_FaceHugger_Movement* pxFacehuggerBehaviour =
			static_cast< AVP_Behaviour_FaceHugger_Movement* >( CreateChildWithType( AVP_BEHAVIOUR_TYPE_FACEHUGGER_MOVEMENT ) );
		if ( pxFacehuggerBehaviour)
		{
			bSuccess = pxFacehuggerBehaviour->GetToPosition( m_xPosition );
		}
		if (bSuccess)
		{
			pxFacehuggerBehaviour->SetMovementMode(AVP_Behaviour_FaceHugger_Movement::EMM_MEDIUM);
		}

		m_bIsMovePending = false;
		return bSuccess;
	}
	else
	{
		m_bIsMovePending = true;
		return true;
	}
}

void AVP_Behaviour_FaceHugger_GS2_Movement::ReadFromChunkStream( Asura_Chunk_Stream& xStream )
{
	int iVersion;
	xStream >> iVersion;

	PARENT::ReadFromChunkStream( xStream );

	if ( iVersion == CURRENT_CHUNK_VERSION )
	{
		xStream >> m_xPosition;
		GetToPosition( m_xPosition );
	}
	else
	{
		Asura_Assertf( false, ( "AVP_Behaviour_FaceHugger_GS2_Movement::ReadFromChunkStream - unhandled chunk version (%d)", iVersion ) );
	}
}

void AVP_Behaviour_FaceHugger_GS2_Movement::WriteToChunkStream( Asura_Chunk_Stream& xStream ) const
{
	xStream << CURRENT_CHUNK_VERSION;

	PARENT::WriteToChunkStream( xStream );

	xStream << m_xPosition;
}

void AVP_Behaviour_FaceHugger_GS2_Movement::PerFrameUpdate( const float fTimeslice )
{
	PARENT::PerFrameUpdate( fTimeslice );

	if (m_bIsMovePending)
	{
		GetToPosition( m_xPosition );
		return;
	}
	AVP_Behaviour_FaceHugger_Movement* pxMovementBehaviour = static_cast< AVP_Behaviour_FaceHugger_Movement* >( GetChildWithType( this, AVP_BEHAVIOUR_TYPE_FACEHUGGER_MOVEMENT ) );
	Asura_Assert(pxMovementBehaviour, "Move behaviour not found");
	if (!pxMovementBehaviour)
	{
		SetInError(true);
		SetFinished();
	}
	else if ( pxMovementBehaviour->IsInError() )
	{
		--m_iRetries;
		if (m_iRetries < 0 )
		{
			Asura_Assert(false, "Alien MovementWrapper - failed to get to position");
			SetInError();
		}
		else
		{
			GetToPosition( m_xPosition );
		}
	}
	else if ( pxMovementBehaviour->IsFinished() )
	{
		GetActorBrain()->ResetSpawnPoint();
		SetFinished();
	}

}

AVP_Brain_Actor_FaceHugger* AVP_Behaviour_FaceHugger_GS2_Movement::GetActorBrain() const
{
	return static_cast< AVP_Brain_Actor_FaceHugger* >( GetProjectBrain() );
}


//////////////////////////////////////////////////////////////////////////////
//
// 
//
//////////////////////////////////////////////////////////////////////////////
