//////////////////////////////////////////////////////////////////////////////
//
// AVP_Behaviour_AVP3Alien_GetToPosition.cpp
//
// Copyright (C) 2007 Rebellion. All Rights Reserved.
//
//////////////////////////////////////////////////////////////////////////////

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

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

// header
#include "Behaviour\BehaviourTypes\AVP3Alien\AVP_Behaviour_AVP3Alien_Movement.h"

// asura
#include "AI/Asura_AI_Navigation.h"
#ifndef ASURA_NO_DEBUG_OUTPUT
#include "Debugging/Asura_DebugLineRender.h"
#include "Rendering/Asura_Font_System.h"
#endif
#include "Network/Asura_Message.h"
#include "Network/Asura_Network.h"

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

// avp
#include "Actor/AVP_Actor_PhysicsObjectHandler.h"
#include "Actor/Vertebrates/Alien/AVP_Alien_Description.h"
#include "Actor/Vertebrates/Alien/AVP_Alien_StateHandler.h"
#include "Entity/PhysicalObject/Actor/AVP_ServerEntity_Actor.h"
#include "Entity/PhysicalObject/Actor/Vertebrates/Alien/AVP_Messages_Alien.h"
#include "Moves/Vertebrates/Alien/AVP_Alien_Moves_Jump.h"

//////////////////////////////////////////////////////////////////////////////
//
// AVP_Behaviour_AVP3Alien_GetToPosition
//
//////////////////////////////////////////////////////////////////////////////

float s_fCollisionAvoidOverallScalar     = 1.2f;
int   s_iCollisionAvoidRangePower        = 2;
float s_fCollisionAvoidLocalScalar       = 2.0f;
float s_fCollisionAvoidRangeBias         = 0.0f;
float s_fCollisionAvoidMinimumAngleDeg   = 45.0f;
bool  s_bCollisionAvoidDoLocalAngleHack  = false;
bool  s_bCollisionAvoidDoGlobalAngleHack = true;
Asura_Guid s_uFocusGuid = ASURA_GUID_UNREGISTERED;
static Asura_Vector_3 s_axAvoidVectors[ 8 ];
static Asura_Vector_3 s_axAvoidPositions[ 8 ];
static u_int          s_uNumAvoids       = 0;

float s_fToFollowPointScaling = 0.25f;

AVP_Behaviour_AVP3Alien_Movement::AVP_Behaviour_AVP3Alien_Movement( Axon_Project_Brain* pxProjectBrain )
: PARENT( pxProjectBrain, AXON_BEHAVIOUR_TYPE_AVP3ALIEN_MOVEMENT )
, m_eCurrentState( UNSET )
, m_fTimeInState( 0.0f )
, m_xNearestPositionOnRoute( Asura_Maths::ZeroVector3 )
, m_xFollowPoint( Asura_Maths::ZeroVector3 )
, m_bPointAtDestination( false )
, m_fArrivalRange( 0.75f )
, m_xPrevNearestPositionOnRoute( Asura_Maths::ZeroVector3 )
, m_fSmoothedPORVelocity( 0.0f )
, m_usNumTransitions( 0 )
, m_usTransitionIndex( 0 )
, m_fNearbyUpdateTimer( 0.0f )
, m_fNearbyBaseRange( 0.0f )
, m_uNumNearbyGuids( 0 )
, m_fDbgNearbyRange( 0.0f )
, m_xDbgCollisionAvoidanceVector( Asura_Maths::ZeroVector3 )
, m_xDbgRouteSteeringVector( Asura_Maths::ZeroVector3 )
, m_xDbgFinalSteeringVector( Asura_Maths::ZeroVector3 )
{
    // Base range is 1m out past our maximum extent.
    m_fNearbyBaseRange = AVP_Alien_Description::GetCollisionObject().GetBoundingBox().CalculateRadius() + 1.0f;
}

bool AVP_Behaviour_AVP3Alien_Movement::HandleMessage( const Asura_Message* pxMessage )
{
    if ( m_eCurrentState == TRANSITION_JUMP && pxMessage->GetMsgID() == ASURA_MSG_MOVE_FINISH )
    {
        const Asura_Msg_Move_Finish* pxData = static_cast< const Asura_Msg_Move_Finish* >( pxMessage->GetData() );
        if ( pxData &&
            ( pxData->GetMoveID() == AVP_Move_Alien_JumpSurfaceConcave90::GetStaticHash() || pxData->GetMoveID() == AVP_Move_Alien_JumpSurface180::GetStaticHash() ) )
        {
            UpdateFollowPoint();
            UpdateCurrentWaypoint();

            const AVP_Navigation::NavData& xNavData = GetProjectBrain()->GetNavData();
            if ( m_bPointAtDestination || xNavData.m_usCurrentWPID == xNavData.m_usDestinationWPID )
            {
                SwitchToState( MOVING_TO_DESTINATION );
            }
            else
            {
                const Asura_Vector_3& xOurPosition = GetProjectBrain()->GetActor()->GetPosition();

                AVP_Navigation::APParams xAPParams;
                xAPParams.m_fMaxCoplanarOffset = m_fArrivalRange;
                Asura_AI_Navigation_System::GetWaypoint( xNavData.m_usDestinationWPID )->GetNormal( xAPParams.m_xDestinationNormal );
                if ( AVP_Navigation::AtPosition( xOurPosition, xNavData.m_xDestinationPosition, xAPParams ) )
                {
                    SwitchToState( FINISHED );
                }
                else
                {
                    SwitchToState( MOVING_TO_POINT );
                }
            }

            return true;
        }
    }

    return PARENT::HandleMessage( pxMessage );
}

bool AVP_Behaviour_AVP3Alien_Movement::Activate()
{
    const bool bParentOK = PARENT::Activate();

    //GetProjectBrain()->SetRenderRoute( true );

    return bParentOK;
}

void AVP_Behaviour_AVP3Alien_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 ( GetProjectBrain()->IsActive() )
    {
#ifdef AVP_USE_ANIM_STATE_MACHINE_FOR_AI
        GetProjectBrain()->GetActor()->GetActorStateHandler().RequestStateForAI( AVP_Alien_StateHandler::IDLE );
#else
        GetProjectBrain()->RequestAnimation( AVP_Alien_Description::ALIEN_ANIM_SNEAK_REST );
#endif
    }

    //GetProjectBrain()->SetRenderRoute( false );

    PARENT::Deactivate();
}

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

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

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

    switch ( m_eCurrentState )
    {
        case MOVING_TO_POINT:
        {
            if ( m_bPointAtDestination || xNavData.m_usCurrentWPID == xNavData.m_usDestinationWPID )
            {
                SwitchToState( MOVING_TO_DESTINATION );
            }
            else
            {
                AVP_Navigation::APParams xAPParams;
                xAPParams.m_fMaxCoplanarOffset = m_fArrivalRange;
                Asura_AI_Navigation_System::GetWaypoint( xNavData.m_usDestinationWPID )->GetNormal( xAPParams.m_xDestinationNormal );
                if ( AVP_Navigation::AtPosition( xOurPosition, xNavData.m_xDestinationPosition, xAPParams ) )
                {
                    SwitchToState( FINISHED );
                }
            }

#ifndef AVP_USE_ANIM_STATE_MACHINE_FOR_AI
            GetProjectBrain()->RequestAnimation( GetMovementAnim( xNavData.m_eMovementMode ) );
#endif

            break;
        }

        case TRANSITION_JUMP:
        {
            // Wait for the move finished message to come back.
            break;
        }

        case TRANSITION_SLOW:
        case TRANSITION_STANDARD:
        {
            if ( m_bPointAtDestination || xNavData.m_usCurrentWPID == xNavData.m_usDestinationWPID )
            {
                SwitchToState( MOVING_TO_DESTINATION );
            }
            else if ( xNavData.m_usRouteIndex > m_axTransitionData[ m_usTransitionIndex ].m_usRouteIndex )
            {
                AVP_Navigation::APParams xAPParams;
                xAPParams.m_fMaxCoplanarOffset = m_fArrivalRange;
                Asura_AI_Navigation_System::GetWaypoint( xNavData.m_usDestinationWPID )->GetNormal( xAPParams.m_xDestinationNormal );
                if ( AVP_Navigation::AtPosition( xOurPosition, xNavData.m_xDestinationPosition, xAPParams ) )
                {
                    SwitchToState( FINISHED );
                }
                else
                {
                    SwitchToState( MOVING_TO_POINT );
                }
            }

            break;
        }

        case MOVING_TO_DESTINATION:
        {
            AVP_Navigation::APParams xAPParams;
            xAPParams.m_fMaxCoplanarOffset = m_fArrivalRange;
            Asura_AI_Navigation_System::GetWaypoint( xNavData.m_usDestinationWPID )->GetNormal( xAPParams.m_xDestinationNormal );
            if ( AVP_Navigation::AtPosition( xOurPosition, xNavData.m_xDestinationPosition, xAPParams ) )
            {
                SwitchToState( FINISHED );
            }

#ifndef AVP_USE_ANIM_STATE_MACHINE_FOR_AI
            GetProjectBrain()->RequestAnimation( GetMovementAnim( xNavData.m_eMovementMode ) );
#endif

            break;
        }

        case FINISHED:
        case UNSET:
        case NUM_STATES:
        {
            break;
        }
    }
}

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

    m_fTimeInState += fTimeslice;

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

    if ( m_eCurrentState != TRANSITION_JUMP )
    {
        // If we're jumping we're going to be in mid-air, so no point in updating the current waypoint.
        UpdateCurrentWaypoint();
    }
    UpdateNearbyGuidData( fTimeslice );

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

    // Update our position on the route.
    m_xPrevNearestPositionOnRoute = m_xNearestPositionOnRoute;
    Asura_Vector_3 xNearestPosition;
    AVP_Navigation::FindNearestPointOnRoute( xNavData, xOurPosition, 0.0f, m_xNearestPositionOnRoute, xNavData.m_usRouteIndex );
    Asura_Assert( xNavData.m_usRouteIndex < xNavData.m_usRouteLength, "AVP_Behaviour_AVP3Alien_GetToPosition::PerFrameUpdate - bug in AVP_Navigation::FindNearestPointOnRoute" );

    //// If we're not already transitioning.
    //if ( m_eCurrentState == MOVING_TO_POINT )
    //{
    //    // If there is a transition coming up.
    //    if ( m_usTransitionIndex < m_usNumTransitions )
    //    {
    //        // The transition look-ahead point is 1.5m further along the route than us. We use this to switch to the transition slightly before time.
    //        Asura_Vector_3 xDummy;
    //        u_short        usLookAheadRouteIndex;
    //        AVP_Navigation::FindNearestPointOnRoute( xNavData, xOurPosition, 1.5f, xDummy, usLookAheadRouteIndex );

    //        // If it's due.
    //        if ( usLookAheadRouteIndex == m_axTransitionData[ m_usTransitionIndex ].m_usRouteIndex )
    //        {
    //            SwitchToTransitionState();
    //        }
    //    }
    //}

    m_xDbgCollisionAvoidanceVector = Asura_Maths::ZeroVector3;
    m_xDbgRouteSteeringVector      = Asura_Maths::ZeroVector3;
    m_xDbgFinalSteeringVector      = Asura_Maths::ZeroVector3;
    if ( m_eCurrentState == MOVING_TO_POINT || m_eCurrentState == TRANSITION_SLOW || m_eCurrentState == TRANSITION_STANDARD )
    {
        UpdateFollowPoint();

        Asura_Vector_3 xFollowPointSteering;
        CalculateFollowPointSteering( xFollowPointSteering );

        // We only calculate the avoidance steering if we're en-route. If we're moving to the destination we need to make sure we arrive properly.
        Asura_Vector_3 xCollisionAvoidanceSteering;
        CalculateCollisionAvoidanceSteering( xFollowPointSteering, xCollisionAvoidanceSteering );
        if ( xCollisionAvoidanceSteering != Asura_Maths::ZeroVector3 )
        {
            Asura_Vector_3 xFinalSteering = ( xFollowPointSteering + xCollisionAvoidanceSteering ) * 0.5f;

            static float s_fFinalScaling = 1.0f;
            xFinalSteering *= s_fFinalScaling;
            m_xDbgFinalSteeringVector = xFinalSteering;

            OrientToDirection( m_xDbgFinalSteeringVector, fTimeslice );
        }
        else
        {
            OrientToDirection( xFollowPointSteering, fTimeslice );
        }
    }
    else if ( m_eCurrentState == MOVING_TO_DESTINATION )
    {
        OrientToPosition( &xNavData.m_xDestinationPosition, fTimeslice );
    }

    //## This needs re-writing. What if xNavData.m_usRouteIndex is at the end of the route (xNavData.m_usRouteLength - 1)?
    //## For now always allow reorientation.
    // If our current link is a transition link then allow reorientation.
    //bool bAllowReorientation = false;
    //const Asura_AI_NavVertex* pxFromWP = Asura_AI_Navigation_System::GetWaypoint( xNavData.m_pusRoute[ xNavData.m_usRouteIndex ] );
    //Asura_Assert( pxFromWP, "AVP_Behaviour_AVP3Alien_GetToPosition::PerFrameUpdate - unable to get waypoint" );
    //const u_short             usToWPID = xNavData.m_pusRoute[ xNavData.m_usRouteIndex + 1 ];
    //for ( u_char ucLinkIndex = 0; pxFromWP && ucLinkIndex < pxFromWP->m_ucNumConnections; ++ucLinkIndex )
    //{
    //    if ( pxFromWP->m_pxConnections[ ucLinkIndex ].m_usIndex == usToWPID )
    //    {
    //        bAllowReorientation = AVP_Navigation::IsTransitionLink( pxFromWP->m_pxConnections[ ucLinkIndex ].m_usFlags );
    //    }
    //}
    //if ( bAllowReorientation )
    //{
    //    GetProjectBrain()->GetActor()->GetPhysicsObjectHandler()->EnableSurfaceReorientation( 0.1f );
    //}
    //else
    //{
    //    GetProjectBrain()->GetActor()->GetPhysicsObjectHandler()->DisableSurfaceReorientation();
    //}

    // Update our stuck detection stuff if we are moving.
    if ( m_eCurrentState != UNSET && m_eCurrentState != FINISHED && m_eCurrentState != NUM_STATES )
    {
        if ( fTimeslice > 0.0f )
        {
            const float fPORVelocity = ( m_xNearestPositionOnRoute - m_xPrevNearestPositionOnRoute ).Magnitude() / fTimeslice;
            m_fSmoothedPORVelocity += ( fPORVelocity - m_fSmoothedPORVelocity ) * 0.5f * fTimeslice;
            if ( m_fSmoothedPORVelocity < 0.1f )
            {
                // We're stuck.
                SetInError();
                SetFinished();
            }
        }
    }
}

bool AVP_Behaviour_AVP3Alien_Movement::GetToPosition( const Asura_Vector_3& xDestination )
{
    SetInError( false );
    SetFinished( false );

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

    xNavData.m_xStartPosition       = GetProjectBrain()->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.
    xNavData.m_usStartWPID          = xNavData.m_usCurrentWPID;
    // Update our destination waypoint ID.
    AVP_Navigation::TestParams xTestParams;
    xTestParams.m_uPathfinderFlags = xNavData.m_uPathfinderFlags;
    xTestParams.m_ucZone           = xNavData.m_ucCurrentZone;
    if ( !AVP_Navigation::GetNearestWaypointID( xDestination, xNavData.m_usDestinationWPID, xTestParams ) )
    {
        SetInError();
        SwitchToState( UNSET );
        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 ) )
        {
            SetInError();
            SwitchToState( UNSET );
            return false;
        }

        m_usNumTransitions = 0;
        CalculateRouteTransitions();

        m_xPrevNearestPositionOnRoute = Asura_AI_Navigation_System::GetWaypoint( xNavData.m_pusRoute[ 0 ] )->m_xPosition;
        m_fSmoothedPORVelocity        = 5.0f;
        UpdateFollowPoint();
        SwitchToState( MOVING_TO_POINT );
    }

    return true;
}

bool AVP_Behaviour_AVP3Alien_Movement::GetAwayFromPosition( const Asura_Vector_3& xPosition, const float fRange )
{
    SetInError( false );
    SetFinished( false );

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

    xNavData.m_xStartPosition = GetProjectBrain()->GetActor()->GetPosition();
    // Our current waypoint ID should already be up to date, as we keep it up to date when moving.
    xNavData.m_usStartWPID    = xNavData.m_usCurrentWPID;

    // Clear out the old route and find the new one.
    xNavData.ResetRoute();
    if ( !AVP_Navigation::FindRouteAwayFrom( xNavData, xPosition, fRange ) )
    {
        SetInError();
        SwitchToState( UNSET );
        return false;
    }

    m_usNumTransitions = 0;
    CalculateRouteTransitions();

    m_xPrevNearestPositionOnRoute  = Asura_AI_Navigation_System::GetWaypoint( xNavData.m_pusRoute[ 0 ] )->m_xPosition;
    m_fSmoothedPORVelocity         = 5.0f;
    UpdateFollowPoint();
    SwitchToState( MOVING_TO_POINT );

    return true;
}

bool AVP_Behaviour_AVP3Alien_Movement::UpdateDestination( const Asura_Vector_3& xDestination )
{
    return GetToPosition( xDestination );
}

#ifndef ASURA_NO_DEBUG_OUTPUT
static const char* s_aszStateStrings[] =
{
    "UNSET",	
    "MOVING_TO_POINT",
    "TRANSITION_SLOW",
    "TRANSITION_JUMP",
    "TRANSITION_STANDARD",
    "MOVING_TO_DESTINATION",
    "FINISHED"
};

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

    Asura_Font_System::Print( m_pxBrain->GetDebugOutputPos().x, m_pxBrain->GetDebugOutputPos().y, 0.5f, 0xffffffff,
        ASURA_TEXTFORMAT_CENTREJUSTIFIED | ASURA_TEXTFORMAT_FORMATSTRING, "%s", s_aszStateStrings[ m_eCurrentState ] );
    m_pxBrain->GetDebugOutputPos().y += 10.0f;
    Asura_Font_System::Print( m_pxBrain->GetDebugOutputPos().x, m_pxBrain->GetDebugOutputPos().y, 0.5f, 0xffffffff,
        ASURA_TEXTFORMAT_CENTREJUSTIFIED | ASURA_TEXTFORMAT_FORMATSTRING, "smoothed POR vel: %f", m_fSmoothedPORVelocity );
    m_pxBrain->GetDebugOutputPos().y += 10.0f;

    //const Asura_Vector_3 xALittleBitUp( 0.0f, -0.5f, 0.0f );
    //if ( s_uFocusGuid == ASURA_GUID_UNREGISTERED )
    //{
    //    //Asura_DebugLineRender::AddSphere( GetProjectBrain()->GetActor()->GetPosition(), s_fDbgNearbyRange, 0xffffffff, 8, 4 );
    //    Asura_DebugLineRender::AddLine( GetProjectBrain()->GetActor()->GetPosition() + xALittleBitUp, GetProjectBrain()->GetActor()->GetPosition() + xALittleBitUp + m_xDbgCollisionAvoidanceVector, 0xffff0000 );
    //    Asura_DebugLineRender::AddLine( GetProjectBrain()->GetActor()->GetPosition() + xALittleBitUp, GetProjectBrain()->GetActor()->GetPosition() + xALittleBitUp + m_xDbgRouteSteeringVector, 0xff00ff00 );
    //    Asura_DebugLineRender::AddLine( GetProjectBrain()->GetActor()->GetPosition() + xALittleBitUp, GetProjectBrain()->GetActor()->GetPosition() + xALittleBitUp + m_xDbgFinalSteeringVector, 0xff0000ff );
    //}
    //else if ( s_uFocusGuid == GetProjectBrain()->GetOwnerGuid() )
    //{
    //    const u_int uColours[ 8 ] =
    //    {
    //        0xff6f3700,
    //        0xff914800,
    //        0xffbf6000,
    //        0xffe67300,
    //        0xffff8f20,
    //        0xffffaa55,
    //        0xffffd1a4,
    //        0xffffffff
    //    };

    //    for ( u_int uAvoid = 0; uAvoid < s_uNumAvoids; ++uAvoid )
    //    {
    //        Asura_DebugLineRender::AddSphere( s_axAvoidPositions[ uAvoid ], 0.5f, uColours[ uAvoid ], 8, 4 );
    //        Asura_DebugLineRender::AddLine( GetProjectBrain()->GetActor()->GetPosition() + ( xALittleBitUp * 2.0f ),
    //            GetProjectBrain()->GetActor()->GetPosition() + ( xALittleBitUp * 2.0f )+ s_axAvoidVectors[ uAvoid ], uColours[ uAvoid ] );
    //    }

    //    Asura_DebugLineRender::AddLine( GetProjectBrain()->GetActor()->GetPosition() + xALittleBitUp, GetProjectBrain()->GetActor()->GetPosition() + xALittleBitUp + m_xDbgCollisionAvoidanceVector, 0xffff0000 );
    //    Asura_DebugLineRender::AddLine( GetProjectBrain()->GetActor()->GetPosition() + xALittleBitUp, GetProjectBrain()->GetActor()->GetPosition() + xALittleBitUp + m_xDbgRouteSteeringVector, 0xff00ff00 );
    //    Asura_DebugLineRender::AddLine( GetProjectBrain()->GetActor()->GetPosition() + xALittleBitUp, GetProjectBrain()->GetActor()->GetPosition() + xALittleBitUp + m_xDbgFinalSteeringVector, 0xff0000ff );
    //}

    // Also render our nearest position on the route, our follow point and our current waypoint.
    //Asura_DebugLineRender::AddSphere( m_xNearestPositionOnRoute, 0.5f, 0xff00b0ff, 8, 4 );
    //Asura_DebugLineRender::AddSphere( m_xFollowPoint, 0.5f, 0xff00b0ff, 8, 4 );
    //if ( xNavData.m_usCurrentWPID != ASURA_INVALID_WAYPOINT_ID )
    //{
    //    const Asura_AI_NavVertex* pxCurrentWP = Asura_AI_Navigation_System::GetWaypoint( xNavData.m_usCurrentWPID );
    //    Asura_DebugLineRender::AddSphere( pxCurrentWP->m_xPosition, pxCurrentWP->m_fRadius, 0xffffffff, 8, 4 );
    //}
}
#endif

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

void AVP_Behaviour_AVP3Alien_Movement::SwitchToState( const State eNextState )
{
    // Do any work we need to exit our current state.
    switch ( m_eCurrentState )
    {
        case TRANSITION_JUMP:
        case TRANSITION_STANDARD:
        case TRANSITION_SLOW:
        {
            // Now we've made our transition we move the current transition on ready for our next.
            ++m_usTransitionIndex;

            // We will have changed surface, and we may have slowed ourselves down for the transition, so request our correct movement animation.
            const AVP_Navigation::NavData& xNavData = GetProjectBrain()->GetNavData();
            GetProjectBrain()->ForceAnimation( GetMovementAnim( xNavData.m_eMovementMode ) );

#ifdef AVP_USE_ANIM_STATE_MACHINE_FOR_AI
            Asura_AI_NavVertex* pxCurrentWaypoint = Asura_AI_Navigation_System::GetWaypoint( xNavData.m_usCurrentWPID );
            if ( pxCurrentWaypoint && ( pxCurrentWaypoint->IsCeilingWaypoint() || pxCurrentWaypoint->IsWallWaypoint() ) )
            {
                GetProjectBrain()->GetActor()->GetActorStateHandler().AddStateModifierFlags( AVP_Alien_StateHandler::ASMV_NON_FLOOR );
            }
            else
            {
                GetProjectBrain()->GetActor()->GetActorStateHandler().RemoveStateModifierFlags( AVP_Alien_StateHandler::ASMV_NON_FLOOR );
            }
#endif

            break;
        }

        case MOVING_TO_POINT:
        case MOVING_TO_DESTINATION:
        case FINISHED:
        case UNSET:
        case NUM_STATES:
        {
            break;
        }
    }

    // Do any work we need to change into our next state.
    m_eCurrentState = eNextState;
    m_fTimeInState  = 0.0f;
    switch ( m_eCurrentState )
    {
        case FINISHED:
        {
#ifdef AVP_USE_ANIM_STATE_MACHINE_FOR_AI
            GetProjectBrain()->GetActor()->GetActorStateHandler().RequestStateForAI( AVP_Alien_StateHandler::IDLE );
#else
            const AVP_Navigation::NavData& xNavData = GetProjectBrain()->GetNavData();
            Asura_AI_NavVertex* pxCurrentWaypoint = Asura_AI_Navigation_System::GetWaypoint( xNavData.m_usCurrentWPID );
            if ( pxCurrentWaypoint && ( pxCurrentWaypoint->IsCeilingWaypoint() || pxCurrentWaypoint->IsWallWaypoint() ) )
            {
                GetProjectBrain()->RequestAnimation( AVP_Alien_Description::ALIEN_ANIM_WALL_IDLE );
            }
            else
            {
                GetProjectBrain()->RequestAnimation( AVP_Alien_Description::ALIEN_ANIM_SNEAK_REST );
            }
#endif

            SetFinished();
            break;
        }

        case MOVING_TO_POINT:
        case MOVING_TO_DESTINATION:
        {
#ifdef AVP_USE_ANIM_STATE_MACHINE_FOR_AI
            const AVP_Navigation::NavData& xNavData = GetProjectBrain()->GetNavData();
            Asura_AI_NavVertex* pxCurrentWaypoint = Asura_AI_Navigation_System::GetWaypoint( xNavData.m_usCurrentWPID );
            const bool bNonFloor = ( pxCurrentWaypoint && ( pxCurrentWaypoint->IsCeilingWaypoint() || pxCurrentWaypoint->IsWallWaypoint() ) );
            if ( bNonFloor )
            {
                GetProjectBrain()->GetActor()->GetActorStateHandler().AddStateModifierFlags( AVP_Alien_StateHandler::ASMV_NON_FLOOR );
            }
            else
            {
                GetProjectBrain()->GetActor()->GetActorStateHandler().RemoveStateModifierFlags( AVP_Alien_StateHandler::ASMV_NON_FLOOR );
            }
            GetProjectBrain()->GetActor()->GetActorStateHandler().RequestStateForAI( GetAlienAnimState( xNavData.m_eMovementMode ) );
#endif
            break;
        }

        case TRANSITION_JUMP:
        {
            // Send our jump movement message.
            Asura_Message* pxMessage = Asura_Network::CreateMessage( ASURA_MSG_MOVE_START, sizeof( AVP_Message_Alien_Move_JumpSurfaceStart ) );
            if ( pxMessage )
            {
                AVP_Message_Alien_Move_JumpSurfaceStart* pxData = static_cast< AVP_Message_Alien_Move_JumpSurfaceStart* >( pxMessage->GetData() );

                pxData->SetClientGuid( GetProjectBrain()->GetOwnerGuid() );
                pxData->SetMoveID(
                    AVP_Navigation::GetJumpMoveHashIDFromLinkTransitionType(
                        static_cast< AVP_Navigation::LinkTransitionType >( m_axTransitionData[ m_usTransitionIndex ].m_usLinkTransitionType ) ) );

                pxData->SetForceAbort( true );
                pxData->SetFromPlayerSystem( false );
                pxData->SetIsAI( true );
                pxData->SetDirection( Asura_Maths::ZeroVector3 );

                pxData->SetDestinationNormal( m_axTransitionData[ m_usTransitionIndex ].m_xDestinationNormal );
                pxData->SetDestinationPosition( m_axTransitionData[ m_usTransitionIndex ].m_xDestinationPosition );
                pxData->SetJumpType(
                    AVP_Navigation::GetJumpTypeFromLinkTransitionType(
                        static_cast< AVP_Navigation::LinkTransitionType >( m_axTransitionData[ m_usTransitionIndex ].m_usLinkTransitionType ) ) );

                pxMessage->SetFromGuid( GetProjectBrain()->GetOwnerGuid() );
                pxMessage->SetToGuid( ASURA_GUID_MY_CLIENT_ENTITIES );
                Asura_Network::Send( pxMessage );
            }

            break;
        }

        case TRANSITION_SLOW:
        {
            // We may need to slow down if we're travelling quickly.
            const AVP_Navigation::NavData& xNavData = GetProjectBrain()->GetNavData();
            //## Missing movement animations, so this'll need fixing up.
            if ( xNavData.m_eMovementMode != AVP_Navigation::MM_SLOWEST )
            {
                GetProjectBrain()->RequestAnimation( AVP_Alien_Description::ALIEN_ANIM_SNEAK );
            }

            break;
        }

        case TRANSITION_STANDARD:
        {
            // The transition may be the first link in our route, so we need to request the correct anim on entry to this state also.
            GetProjectBrain()->RequestAnimation( GetMovementAnim( GetProjectBrain()->GetNavData().m_eMovementMode, true ) );
            break;
        }

        case UNSET:
        case NUM_STATES:
        {
            break;
        }
    }
}

void AVP_Behaviour_AVP3Alien_Movement::UpdateCurrentWaypoint()
{
    AVP_Navigation::NavData& xNavData = GetProjectBrain()->GetNavData();

    AVP_Navigation::TestParams xTestParams;
    xTestParams.m_uPathfinderFlags     = xNavData.m_uPathfinderFlags;
    xTestParams.m_bRejectNonalignedWPs = true;
    xTestParams.m_xNormal              = -GetProjectBrain()->GetActor()->GetCurrentGravityDirection();
    xTestParams.m_ucZone               = xNavData.m_ucCurrentZone;
    u_short usCurrentWaypointID = xNavData.m_usCurrentWPID;
    AVP_Navigation::UpdateCurrentWaypointID( GetProjectBrain()->GetActor()->GetPosition(), usCurrentWaypointID, xTestParams );
    if ( usCurrentWaypointID != ASURA_INVALID_WAYPOINT_ID )
    {
        xNavData.m_usCurrentWPID = usCurrentWaypointID;
    }
}

void AVP_Behaviour_AVP3Alien_Movement::UpdateFollowPoint()
{
    const Asura_Vector_3& xCurrentPosition = GetProjectBrain()->GetActor()->GetPosition();
    u_short               usNearestRouteIndex;
    //## We could alter our look ahead distance based on the speed we're travelling at and the type of link (transition or not) we're on.
    m_bPointAtDestination = AVP_Navigation::FindNearestPointOnRoute( GetProjectBrain()->GetNavData(), xCurrentPosition, 2.5f, m_xFollowPoint, usNearestRouteIndex );
}

void AVP_Behaviour_AVP3Alien_Movement::UpdateNearbyGuidData( const float fTimeslice )
{
    static const float s_fUpdatePeriod = 0.125f;

    m_fNearbyUpdateTimer -= fTimeslice;
    if ( m_fNearbyUpdateTimer <= 0.0f )
    {
        FindNearbyGuidData();
        m_fNearbyUpdateTimer += s_fUpdatePeriod;
    }
    else
    {
        // We only need update the data if we've not (re)found it this update.
        const Asura_Vector_3& xOurPosition = GetProjectBrain()->GetActor()->GetPosition();
        for ( u_int uNearbyGuid = 0; uNearbyGuid < m_uNumNearbyGuids; ++uNearbyGuid )
        {
            const AVP_ServerEntity_Actor* pxActor = AVP_ServerEntity_Actor::FindEntity( m_axNearbyGuidData[ uNearbyGuid ].m_uGuid );
            if ( !pxActor ) { continue; }

            m_axNearbyGuidData[ uNearbyGuid ].m_xFromGuid = xOurPosition - pxActor->GetPosition();
            m_axNearbyGuidData[ uNearbyGuid ].m_fRange    = m_axNearbyGuidData[ uNearbyGuid ].m_xFromGuid.Normalise();
        }
    }
}

void AVP_Behaviour_AVP3Alien_Movement::FindNearbyGuidData()
{
    const Asura_Vector_3& xOurPosition = GetProjectBrain()->GetActor()->GetPosition();

    // Give us the base range plus 2 seconds of leeway.
    const float fNearbyRange = m_fNearbyBaseRange + GetProjectBrain()->GetActor()->GetVelocity().Magnitude() * 2.0f;
    m_fDbgNearbyRange = fNearbyRange;

    // I'm sure there's a better way of doing this, but this'll do for testing purposes.
    m_uNumNearbyGuids = 0;
    for ( Asura_Hashed_List_ConstIt< AVP_ServerEntity_Actor > xActorIter( AVP_ServerEntity_Actor::GetList() );
        !xActorIter.Done() && m_uNumNearbyGuids < MAX_NEARBY_GUIDS;
        xActorIter.Next() )
    {
        const AVP_ServerEntity_Actor* pxActor = xActorIter.GetCurrent();
        if ( !pxActor || pxActor->GetGuid() == GetProjectBrain()->GetOwnerGuid() || pxActor->IsDead() || !pxActor->IsActive() )
        {
            continue;
        }

        Asura_Vector_3 xFromActor = xOurPosition - pxActor->GetPosition();
        float          fFromActor = xFromActor.Normalise();
        if ( fFromActor < fNearbyRange )
        {
            m_axNearbyGuidData[ m_uNumNearbyGuids ].m_uGuid     = pxActor->GetGuid();
            m_axNearbyGuidData[ m_uNumNearbyGuids ].m_xFromGuid = xFromActor;
            m_axNearbyGuidData[ m_uNumNearbyGuids ].m_fRange    = fFromActor;
            ++m_uNumNearbyGuids;
        }
    }
}

void AVP_Behaviour_AVP3Alien_Movement::CalculateCollisionAvoidanceSteering( const Asura_Vector_3& xFollowPointSteering, Asura_Vector_3& xSteering )
{
    if ( s_uFocusGuid == GetProjectBrain()->GetOwnerGuid() )
    {
        s_uNumAvoids = 0;
    }

    const Asura_Matrix_3x3& xOurOrientation = GetProjectBrain()->GetActor()->GetOrientation();
    const Asura_Vector_3    xOurNormal( -GetProjectBrain()->GetActor()->GetCurrentGravityDirection() );
    Asura_Vector_3 xNormalisedFollowPointSteering = xFollowPointSteering;
    xNormalisedFollowPointSteering.Normalise();

    xSteering = Asura_Maths::ZeroVector3;
    for ( u_int uNearbyGuid = 0; uNearbyGuid < m_uNumNearbyGuids; ++uNearbyGuid )
    {
        const float    fRange      = Asura_Maths::Max( m_axNearbyGuidData[ uNearbyGuid ].m_fRange + s_fCollisionAvoidRangeBias, 1.0f );
        Asura_Vector_3 xFromVector = m_axNearbyGuidData[ uNearbyGuid ].m_xFromGuid;

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

        if ( s_bCollisionAvoidDoLocalAngleHack )
        {
            // 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 fMinimumAngle    = Asura_Maths::DegToRad( s_fCollisionAvoidMinimumAngleDeg );
            const float fMinimumAngleDot = Asura_Maths::Cosine( fMinimumAngle );
            const float fDot = -xPlaneVector * xNormalisedFollowPointSteering;
            if ( fDot > fMinimumAngleDot )
            {
                // Work out the direction of the rotation using the plane vector in our local space.
                const Asura_Vector_3 xLocalPlaneVector = xOurOrientation.ApplyTranspose( xPlaneVector );

                // 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( -Asura_Maths::Sign( xLocalPlaneVector.x ) * fMinimumAngle, xOurUp );
                xRotationQuat.RotateVector( -xNormalisedFollowPointSteering, xPlaneVector );
            }
        }

        xSteering += xPlaneVector * ( s_fCollisionAvoidLocalScalar / static_cast< float >( pow( fRange, s_iCollisionAvoidRangePower ) ) );

        if ( s_uFocusGuid == GetProjectBrain()->GetOwnerGuid() )
        {
            s_axAvoidVectors[ s_uNumAvoids ] = xPlaneVector * ( s_fCollisionAvoidLocalScalar / static_cast< float >( pow( fRange, s_iCollisionAvoidRangePower ) ) );
            s_axAvoidPositions[ s_uNumAvoids ] = AVP_ServerEntity_Actor::FindEntity( m_axNearbyGuidData[ uNearbyGuid ].m_uGuid )->GetPosition();
            ++s_uNumAvoids;
        }
    }

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

    xSteering *= s_fCollisionAvoidOverallScalar;

    if ( s_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 fMinimumAngle    = Asura_Maths::DegToRad( s_fCollisionAvoidMinimumAngleDeg );
        const float fMinimumAngleDot = Asura_Maths::Cosine( fMinimumAngle );
        const float fDot = -xGlobalPlaneVector * xNormalisedFollowPointSteering;
        if ( fDot > fMinimumAngleDot )
        {
            // 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( -Asura_Maths::Sign( xLocalPlaneVector.x ) * fMinimumAngle, xOurUp );
            xRotationQuat.RotateVector( -xNormalisedFollowPointSteering, xGlobalPlaneVector );
        }

        xGlobalPlaneVector *= fGlobalPlaneVector;
        xSteering = xGlobalPlaneVector;
    }

    m_xDbgCollisionAvoidanceVector = xSteering;
}

void AVP_Behaviour_AVP3Alien_Movement::CalculateFollowPointSteering( Asura_Vector_3& xSteering )
{
    const Asura_Vector_3& xOurPosition = GetProjectBrain()->GetActor()->GetPosition();
    const Asura_Vector_3  xOurNormal( -GetProjectBrain()->GetActor()->GetCurrentGravityDirection() );

    Asura_Vector_3 xToFollowPoint = m_xFollowPoint - xOurPosition;
    float          fToFollowPoint = xToFollowPoint.Normalise();

    // We need to resolve the follow point vector into our plane using our normal.
    Asura_Vector_3 xPlaneVector = xToFollowPoint ^ xOurNormal;
    xPlaneVector = xOurNormal ^ xPlaneVector;
    xPlaneVector.Normalise();
    xPlaneVector *= fToFollowPoint;

    xSteering = xPlaneVector * s_fToFollowPointScaling;

    m_xDbgRouteSteeringVector = xSteering;
}

void AVP_Behaviour_AVP3Alien_Movement::CalculateRouteTransitions()
{
    AVP_Navigation::NavData& xNavData = GetProjectBrain()->GetNavData();
    for ( u_short usRouteIndex = 0; usRouteIndex < xNavData.m_usRouteLength - 1; ++usRouteIndex )
    {
        const AVP_Navigation::LinkTransitionType eLTT = AVP_Navigation::GetLinkTransitionType( xNavData.m_pusRoute[ usRouteIndex ], xNavData.m_pusRoute[ usRouteIndex + 1 ] );
        if ( eLTT != AVP_Navigation::LTT_NO_TRANSITION && eLTT != AVP_Navigation::NUM_LINK_TRANSITION_TYPES )
        {
            m_axTransitionData[ m_usNumTransitions ].m_usLinkTransitionType = static_cast< u_short >( eLTT );
            m_axTransitionData[ m_usNumTransitions ].m_usRouteIndex         = usRouteIndex;

            const Asura_AI_NavVertex* pxToWP = Asura_AI_Navigation_System::GetWaypoint( xNavData.m_pusRoute[ usRouteIndex + 1 ] );
            if ( pxToWP )
            {
                m_axTransitionData[ m_usNumTransitions ].m_xDestinationPosition = pxToWP->m_xPosition;
                pxToWP->GetNormal( m_axTransitionData[ m_usNumTransitions ].m_xDestinationNormal );
            }

            ++m_usNumTransitions;
        }
    }
}

u_int AVP_Behaviour_AVP3Alien_Movement::GetAlienAnimState( const AVP_Navigation::MovementMode eMovementMode ) const
{
    switch ( eMovementMode )
    {
        case AVP_Navigation::MM_SLOWEST: return AVP_Alien_StateHandler::FORWARD_SLOW;
        default:                        // fallthrough
        case AVP_Navigation::MM_SLOW:    return AVP_Alien_StateHandler::FORWARD_MEDIUM;
        case AVP_Navigation::MM_FAST:   // fallthrough
        case AVP_Navigation::MM_FASTEST: return AVP_Alien_StateHandler::FORWARD_FAST;
    }
}

u_int AVP_Behaviour_AVP3Alien_Movement::GetMovementAnim( const AVP_Navigation::MovementMode eMovementMode, const bool bForceOnWall /*= false*/ ) const
{
    AVP_Navigation::NavData& xNavData     = GetProjectBrain()->GetNavData();
    Asura_AI_NavVertex* pxCurrentWaypoint = Asura_AI_Navigation_System::GetWaypoint( xNavData.m_usCurrentWPID );
    const bool bOnWall = bForceOnWall || ( pxCurrentWaypoint && ( pxCurrentWaypoint->IsCeilingWaypoint() || pxCurrentWaypoint->IsWallWaypoint() ) );

    switch ( eMovementMode )
    {
        case AVP_Navigation::MM_SLOWEST: return AVP_Alien_Description::ALIEN_ANIM_SNEAK;
        default:                        // fallthrough
        case AVP_Navigation::MM_SLOW:   // fallthrough
        case AVP_Navigation::MM_FAST:   // fallthrough
        case AVP_Navigation::MM_FASTEST: return bOnWall ? AVP_Alien_Description::ALIEN_ANIM_WALL_CRAWL : AVP_Alien_Description::ALIEN_ANIM_CRAWL;
    }
}

void AVP_Behaviour_AVP3Alien_Movement::SwitchToTransitionState()
{
    const AVP_Navigation::NavData& xNavData = GetProjectBrain()->GetNavData();
    switch ( static_cast< AVP_Navigation::LinkTransitionType >( m_axTransitionData[ m_usTransitionIndex ].m_usLinkTransitionType ) )
    {
        case AVP_Navigation::LTT_FLOOR_TO_WALL_CONCAVE:
        case AVP_Navigation::LTT_WALL_TO_FLOOR_CONCAVE:
        {
            // If we're travelling at speed we should jump.
            if ( xNavData.m_eMovementMode == AVP_Navigation::MM_FAST || xNavData.m_eMovementMode == AVP_Navigation::MM_FASTEST )
            {
                SwitchToState( TRANSITION_JUMP );
            }
            else
            {
                SwitchToState( TRANSITION_STANDARD );
            }
            break;
        }

        // We don't have animations for these yet.
        case AVP_Navigation::LTT_WALL_TO_WALL_90:
        case AVP_Navigation::LTT_WALL_TO_CEILING_CONCAVE:
        case AVP_Navigation::LTT_CEILING_TO_WALL_CONCAVE:
        {
            SwitchToState( TRANSITION_STANDARD );
            break;
        }

        case AVP_Navigation::LTT_FLOOR_TO_WALL_CONVEX:
        case AVP_Navigation::LTT_WALL_TO_FLOOR_CONVEX:
        case AVP_Navigation::LTT_WALL_TO_CEILING_CONVEX:
        case AVP_Navigation::LTT_CEILING_TO_WALL_CONVEX:
        {
            // If we're travelling at speed we should slow down.
            if ( xNavData.m_eMovementMode == AVP_Navigation::MM_FAST || xNavData.m_eMovementMode == AVP_Navigation::MM_FASTEST )
            {
                SwitchToState( TRANSITION_SLOW );
            }
            else
            {
                SwitchToState( TRANSITION_STANDARD );
            }
            break;
        }

        case AVP_Navigation::LTT_FLOOR_TO_CEILING:
        case AVP_Navigation::LTT_CEILING_TO_FLOOR:
        case AVP_Navigation::LTT_WALL_TO_WALL_180:
        {
            // We need to jump for these.
            SwitchToState( TRANSITION_JUMP );
            break;
        }

        case AVP_Navigation::LTT_NO_TRANSITION:
        case AVP_Navigation::NUM_LINK_TRANSITION_TYPES:
        {
            Asura_Assert( false, "AVP_Behaviour_AVP3Alien_Movement::SwitchToTransitionState - bad transition type" );
            break;
        }
    }
}

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