
///////////////////////////////////////////////////////////////////////////////
//
// File   : AVP_Predator_Focus.cpp
// Author : Anthony Gregson
// Date   : 31/01/08
//
// Copyright (C) 2008 Rebellion, All Rights Reserved.
//
///////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// Include files...
////////////////////////////////////////////////////////////////////////////////

#include "Core/Asura.h"

// This file's header...
#include "Actor/Vertebrates/Predator/AVP_Predator_Focus.h"

// Asura independent header-files...
#include "Attractors/Asura_Attractor_System.h"
#include "Blueprint/Asura_Blueprint_System.h"
#include "Camera/Asura_Camera.h"
#if !defined(ASURA_NO_DEBUG_OUTPUT)
#include "Debugging/Asura_DebugLineRender.h"
#endif	// !defined(ASURA_NO_DEBUG_OUTPUT)
#include "Entity/PhysicalObject/Moves/Asura_Moves.h"
#include "Interface/Asura_CommandConsole.h"
#include "Network/Asura_Message.h"
#include "Network/Asura_Network.h"
#include "Physics/Asura_Physics.h"
#include "ProjectSettings/Asura_PhysicsConfiguration.h"
#include "Rendering/Asura_Font_System.h"
#include "SpecialFX/PFX/Asura_PFX_Entity_Effect.h"
#include "SpecialFX/PFX/Asura_PFX_System.h"

// Game independent header-files...
#include "Actor/AVP_Actor_AnimationHandler.h"
#include "Actor/Vertebrates/Predator/AVP_Predator_HUDHandler.h"
#include "Actor/Vertebrates/Predator/AVP_Predator_Recon.h"
#include "Actor/Vertebrates/AVP_Vertebrate_Description.h"
#include "Actor/Vertebrates/Predator/AVP_Predator_InputHandler.h"
#include "Actor/Vertebrates/Predator/AVP_Predator_LockOn.h"
#include "Actor/Vertebrates/Predator/AVP_Predator_MeleeCounter_System.h"
#include "Blueprint/AVP_Blueprint_HashIDs.h"
#include "Entity/AVP_Entity_Classification.h"
#include "Entity/PhysicalObject/Actor/Vertebrates/Predator/AVP_ClientActor_Predator.h"
#include "Inventory/AVP_Inventory_HashIDs.h"
#include "Inventory/AVP_Inventory_Item.h"
#include "Inventory/AVP_Inventory_System.h"
#include "Inventory/Items/AVP_InventoryItem_VisionMode.h"
#include "Inventory/Items/AVP_InventoryItem_Weapon.h"
#include "Inventory/Items/Weapons/AVP_InventoryWeapon_RangedWeapon.h"
#include "Inventory/Items/Weapons/AVP_RangedWeapon_SubType.h"
#include "Moves/Vertebrates/Predator/AVP_Predator_MovesJump.h"

// Game axon
#include "Core/AVP_Navigation.h"
#include "Tactical/AVP_Tactical_System.h"

////////////////////////////////////////////////////////////////////////////////
// Constant declarations...
////////////////////////////////////////////////////////////////////////////////

DEBUGVAR bool dbg_bPredatorFocusRenderDebugInfo = false;

static const Asura_Vector_3 xUP(0.0f, -1.0f, 0.0f);
static const Asura_Vector_3 xDOWN(0.0f, 1.0f, 0.0f);
static const Asura_Vector_3 xFORWARD(0.0f, 0.0f, 1.0f);
static const Asura_Vector_3 xSCREEN_CENTRE(0.5f, 0.5f, 0.0f);

const Asura_Hash_ID uPREDATOR_JUMP_BLUEPRINT_HASH_MINDISTANCE = 0x2F88DD4F;						//Asura_GetHashID("JumpDistanceMin");
const Asura_Hash_ID uPREDATOR_JUMP_BLUEPRINT_HASH_MAXDISTANCE = 0x2F88DC61;						//Asura_GetHashID("JumpDistanceMax");
const Asura_Hash_ID uPREDATOR_JUMP_BLUEPRINT_HASH_MAXHEIGHT = 0x2BB80E6F;						//Asura_GetHashID("JumpHeightMax");
const Asura_Hash_ID uPREDATOR_JUMP_BLUEPRINT_HASH_LOSRANGE = 0xA4EF01C8;						//Asura_GetHashID("JumpLOSLength");
const Asura_Hash_ID uPREDATOR_JUMP_BLUEPRINT_HASH_MAXATTRACTORRENDERINGDISTANCE = 0xC70486CB;	//Asura_GetHashID("JumpMaxRenderingDistance");
const Asura_Hash_ID uPREDATOR_JUMP_BLUEPRINT_HASH_STICKYTIME = 0xAC437AE4;						//Asura_GetHashID("JumpStickyTime");

static const Asura_Hash_ID s_uFocusMeleeTargetPFX				 = 0x460656f3;	// Focus.MeleeTargetPFX
static const Asura_Hash_ID s_uFocusJumpTargetPFX				 = 0xc5409f0d;	// Focus.JumpTargetPFX
static const Asura_Hash_ID s_uFocusCantJumpTargetPFX			 = 0x4fb0c9e9;	// Focus.CantJumpTargetPFX
static const Asura_Hash_ID s_uFocusNoisyJumpTargetPFX			 = 0xCDF67A0B;	// Focus.NoisyJumpTargetPFX

#define AVP_HUD_PREDATOR_JUMP_POSITION_VALID		L"AVP.HUD.Predator.Jump.PositionValid"
#define AVP_HUD_PREDATOR_JUMP_ALLOWED				L"AVP.HUD.Predator.Jump.Allowed"
#define AVP_HUD_PREDATOR_JUMP_HEIGHT_INVALID		L"AVP.HUD.Predator.Jump.HeightInvalid"
#define AVP_HUD_PREDATOR_JUMP_DISTANCE_INVALID		L"AVP.HUD.Predator.Jump.DistanceInvalid"
#define AVP_HUD_PREDATOR_JUMP_HEIGHT				L"AVP.HUD.Predator.Jump.Height"
#define AVP_HUD_PREDATOR_JUMP_DISTANCE				L"AVP.HUD.Predator.Jump.Distance"

///////////////////////////////////////////////////////////////////////////////
// Static variable declarations...
////////////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////////////
// Private functions...
////////////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////////////
// Protected functions...
////////////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////////////
// Public functions...
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// Function     : AVP_Predator_Focus::AVP_Predator_Focus
// Author       : Anthony Gregson
// Date         : 31/01/08
////////////////////////////////////////////////////////////////////////////////

AVP_Predator_Focus::AVP_Predator_Focus(AVP_ClientActor_Predator& xOwner)
: PARENT(xOwner)
, m_xJumpDestinationPosition(Asura_Maths::ZeroVector3)
, m_iJumpVariation(0)
, m_uMeleeIndicatorPFXID(ASURA_PFX_INVALID_EFFECT_ID)
, m_uJumpIndicatorPFXID(ASURA_PFX_INVALID_EFFECT_ID)
, m_uCantJumpIndicatorPFXID(ASURA_PFX_INVALID_EFFECT_ID)
, m_uNoisyJumpIndicatorPFXID(ASURA_PFX_INVALID_EFFECT_ID)
, m_fJumpDistance(0.0f)
, m_fJumpHeight(0.0f)
, m_fJumpAttractorStickyTimer(0.0f)
, m_bDestinationPositionValid(false)
, m_bJumpAvailable(false)
, m_bInvalidDistance(false)
, m_bInvalidHeight(false)
, m_bNoisyJump(false)
, m_fMaxLOSRange(20.0f)
, m_fMinJumpDistance(5.0f)
, m_fMaxJumpDistance(25.0f)
, m_fMaxJumpHeight(8.0f)
, m_fMaxJumpAttractorRenderingDistance(50.0f)
, m_fJumpAttractorStickyTimeLimit(0.5f)
{
	AVP_Predator_LockOn::GetSingleton().SetOwnerGuid( xOwner.GetGuid() );

	if ( !Asura_CommandConsole::VarExists( AVP_HUD_PREDATOR_JUMP_POSITION_VALID ) )
	{
		Asura_CommandConsole::AddVar( AVP_HUD_PREDATOR_JUMP_POSITION_VALID, &m_bDestinationPositionValid );
	}
	if ( !Asura_CommandConsole::VarExists( AVP_HUD_PREDATOR_JUMP_DISTANCE_INVALID ) )
	{
		Asura_CommandConsole::AddVar( AVP_HUD_PREDATOR_JUMP_DISTANCE_INVALID, &m_bInvalidDistance );
	}
	if ( !Asura_CommandConsole::VarExists( AVP_HUD_PREDATOR_JUMP_HEIGHT_INVALID ) )
	{
		Asura_CommandConsole::AddVar( AVP_HUD_PREDATOR_JUMP_HEIGHT_INVALID, &m_bInvalidHeight );
	}
	if ( !Asura_CommandConsole::VarExists( AVP_HUD_PREDATOR_JUMP_HEIGHT_INVALID ) )
	{
		Asura_CommandConsole::AddVar( AVP_HUD_PREDATOR_JUMP_HEIGHT_INVALID, &m_bInvalidHeight );
	}
	if ( !Asura_CommandConsole::VarExists( AVP_HUD_PREDATOR_JUMP_HEIGHT ) )
	{
		Asura_CommandConsole::AddVar( AVP_HUD_PREDATOR_JUMP_HEIGHT, &m_fJumpHeight );
	}
	if ( !Asura_CommandConsole::VarExists( AVP_HUD_PREDATOR_JUMP_DISTANCE ) )
	{
		Asura_CommandConsole::AddVar( AVP_HUD_PREDATOR_JUMP_DISTANCE, &m_fJumpDistance );
	}
}

/// End of function 'AVP_Predator_Focus::AVP_Predator_Focus'. ///



////////////////////////////////////////////////////////////////////////////////
// Function     : AVP_Predator_Focus::~AVP_Predator_Focus
// Author       : Anthony Gregson
// Date         : 31/01/08
////////////////////////////////////////////////////////////////////////////////

AVP_Predator_Focus::~AVP_Predator_Focus()
{
	AVP_Predator_LockOn& xLockOn = AVP_Predator_LockOn::GetSingleton();
	xLockOn.Shutdown();

	if (m_uMeleeIndicatorPFXID != ASURA_PFX_INVALID_EFFECT_ID)
	{
		Asura_PFX_System::DestroyEffect(m_uMeleeIndicatorPFXID);
	}

	if (m_uJumpIndicatorPFXID != ASURA_PFX_INVALID_EFFECT_ID)
	{
		Asura_PFX_System::DestroyEffect(m_uJumpIndicatorPFXID);
	}

	if (m_uCantJumpIndicatorPFXID != ASURA_PFX_INVALID_EFFECT_ID)
	{
		Asura_PFX_System::DestroyEffect(m_uCantJumpIndicatorPFXID);
	}

	if (m_uNoisyJumpIndicatorPFXID != ASURA_PFX_INVALID_EFFECT_ID)
	{
		Asura_PFX_System::DestroyEffect(m_uNoisyJumpIndicatorPFXID);
	}

	if ( Asura_CommandConsole::VarExists( AVP_HUD_PREDATOR_JUMP_POSITION_VALID ) )
	{
		Asura_CommandConsole::RemoveVar( AVP_HUD_PREDATOR_JUMP_POSITION_VALID );
	}
	if ( Asura_CommandConsole::VarExists( AVP_HUD_PREDATOR_JUMP_DISTANCE_INVALID ) )
	{
		Asura_CommandConsole::RemoveVar( AVP_HUD_PREDATOR_JUMP_DISTANCE_INVALID );
	}
	if ( Asura_CommandConsole::VarExists( AVP_HUD_PREDATOR_JUMP_HEIGHT_INVALID ) )
	{
		Asura_CommandConsole::RemoveVar( AVP_HUD_PREDATOR_JUMP_HEIGHT_INVALID );
	}
	if ( Asura_CommandConsole::VarExists( AVP_HUD_PREDATOR_JUMP_HEIGHT_INVALID ) )
	{
		Asura_CommandConsole::RemoveVar( AVP_HUD_PREDATOR_JUMP_HEIGHT_INVALID );
	}
	if ( Asura_CommandConsole::VarExists( AVP_HUD_PREDATOR_JUMP_HEIGHT ) )
	{
		Asura_CommandConsole::RemoveVar( AVP_HUD_PREDATOR_JUMP_HEIGHT );
	}
	if ( Asura_CommandConsole::VarExists( AVP_HUD_PREDATOR_JUMP_DISTANCE ) )
	{
		Asura_CommandConsole::RemoveVar( AVP_HUD_PREDATOR_JUMP_DISTANCE );
	}
}

/// End of function 'AVP_Predator_Focus::~AVP_Predator_Focus'. ///


////////////////////////////////////////////////////////////////////////////////
// Function     : AVP_Predator_Focus::Update
// Author       : Anthony Gregson
// Date         : 31/01/08
////////////////////////////////////////////////////////////////////////////////

void AVP_Predator_Focus::Update()
{
	// Call the parent function...
	PARENT::Update();

	// Are there any enemies or jump targets?
	UpdateTargets();

	UpdateJumpTargetValues();

	AVP_Predator_LockOn::GetSingleton().Update();
}

/// End of function 'AVP_Predator_JumpHandler::Update'. ///

void AVP_Predator_Focus::UpdateJumpTargetValues( void )
{
	if (m_bDestinationPositionValid)
	{
		Asura_Vector_3 xOffset = m_xJumpDestinationPosition - *Asura_Camera::GetPosition();
		float fDistance = xOffset.Magnitude();
		float fHeight = xOffset.y;

		m_bInvalidDistance = false;
		m_bInvalidHeight = false;

		if (!m_bJumpAvailable)
		{
			// Check which text should be changing colour
			bool bChanged = false;
			if (fDistance > m_fMaxJumpDistance)
			{
				m_bInvalidDistance = true;
				bChanged = true;
			}
			if (fHeight < -m_fMaxJumpHeight)
			{
				m_bInvalidHeight = true;
				bChanged = true;
			}

			// If we haven't changed either, we must be somewhere in the height adjustment's odd range. Just flag both.
			if (!bChanged)
			{
				m_bInvalidDistance = true;
				m_bInvalidHeight = true;
			}
		}

		m_fJumpDistance = 1.0f - (fDistance/m_fMaxJumpDistance);
		m_fJumpHeight = 1.0f - (fHeight/m_fMaxJumpHeight);
	}
}

////////////////////////////////////////////////////////////////////////////////
// Function     : AVP_Predator_Focus::Render
// Author       : Anthony Gregson
// Date         : 31/01/08
////////////////////////////////////////////////////////////////////////////////
DEBUGVAR Asura_Vector_3 dbg_xPredatorJumpIndicatorOffset = Asura_Vector_3(0.0f, -1.0f, 0.0f);
//DEBUGVAR u_int dbg_uPredatorJumpValidTextColour = 0xFFFFFFFF;
//DEBUGVAR u_int dbg_uPredatorJumpInvalidTextColour = 0xFFFF4444;
DEBUGVAR Asura_Vector_2 dbg_xDistanceTextOffset = Asura_Vector_2(0.02f, 0.0f);
DEBUGVAR Asura_Vector_2 dbg_xHeightTextOffset = Asura_Vector_2(0.0f, 0.03f);

void AVP_Predator_Focus::Render()
{
	// Call the base-function...
	PARENT::Render();

	Asura_PFX_Entity_Effect* pxEffect = Asura_PFX_System::GetEntityEffect(m_uMeleeIndicatorPFXID);

	const AVP_ClientActor_Vertebrate* pxMeleeTarget = AVP_ClientActor_Vertebrate::FindEntity(GetMeleeTarget());

	if (pxEffect)
	{
		if (pxMeleeTarget)
		{
			Asura_Vector_3 xPosition;
			pxMeleeTarget->GetChestPosition(xPosition);
			pxEffect->SetPosition(xPosition);
			if (pxEffect->GetState() != ASURA_PFX_EFFECT_RUNNING)
			{
				pxEffect->SetVelocity( Asura_Maths::ZeroVector3 );
				pxEffect->Start();
			}
		}
		else if (pxEffect->GetState() == ASURA_PFX_EFFECT_RUNNING)
		{
			pxEffect->Stop(true);
		}
	}

	const Asura_Movement_System* pxMS = m_xOwner.GetMovementSystem();
	const Asura_Move* pxMove = pxMS ? pxMS->GetCurrentMove() : NULL;
	if ( !pxMove || pxMove->GetHash() != AVP_Move_Predator_JumpTarget::GetStaticHash() ) // lock the noisyness while in the jump move
	{
		m_bNoisyJump = ( m_xJumpDestinationPosition - m_xOwner.GetCurrentPosition() ).MagnitudeSquared() >= ( m_xOwner.GetMinDistanceForJumpLandNormalEffect() * m_xOwner.GetMinDistanceForJumpLandNormalEffect() );
	}

	pxEffect = Asura_PFX_System::GetEntityEffect(m_uJumpIndicatorPFXID);

	if (pxEffect)
	{
		if (m_bJumpAvailable && !m_bNoisyJump)
		{
			Asura_Assert(m_bDestinationPositionValid, "The destination position should be valid if the jump is available.");
			pxEffect->SetPosition(m_xJumpDestinationPosition + dbg_xPredatorJumpIndicatorOffset);
			if (pxEffect->GetState() != ASURA_PFX_EFFECT_RUNNING)
			{
				pxEffect->SetVelocity( Asura_Maths::ZeroVector3 );
				pxEffect->Start();
			}
		}
		else if (pxEffect->GetState() == ASURA_PFX_EFFECT_RUNNING)
		{
			pxEffect->Stop(true);
		}
	}

	pxEffect = Asura_PFX_System::GetEntityEffect(m_uNoisyJumpIndicatorPFXID);

	if (pxEffect)
	{
		if (m_bJumpAvailable && m_bNoisyJump)
		{
			Asura_Assert(m_bDestinationPositionValid, "The destination position should be valid if the jump is available.");
			pxEffect->SetPosition(m_xJumpDestinationPosition + dbg_xPredatorJumpIndicatorOffset);
			if (pxEffect->GetState() != ASURA_PFX_EFFECT_RUNNING)
			{
				pxEffect->SetVelocity( Asura_Maths::ZeroVector3 );
				pxEffect->Start();
			}
		}
		else if (pxEffect->GetState() == ASURA_PFX_EFFECT_RUNNING)
		{
			pxEffect->Stop(true);
		}
	}

	pxEffect = Asura_PFX_System::GetEntityEffect(m_uCantJumpIndicatorPFXID);

	if (pxEffect)
	{
		if (!m_bJumpAvailable && m_bDestinationPositionValid)
		{
			pxEffect->SetPosition(m_xJumpDestinationPosition + dbg_xPredatorJumpIndicatorOffset);
			if (pxEffect->GetState() != ASURA_PFX_EFFECT_RUNNING)
			{
				pxEffect->SetVelocity( Asura_Maths::ZeroVector3 );
				pxEffect->Start();
			}
		}
		else if (pxEffect->GetState() == ASURA_PFX_EFFECT_RUNNING)
		{
			pxEffect->Stop(true);
		}
	}

	// AJK; E3 turn jump number off
	// Render distances.
	// TODO - probably need a much more in-character HUD display for this
	//if (m_bDestinationPositionValid)
	//{
	//	Asura_Vector_3 xOffset = m_xJumpDestinationPosition - m_xOwner.GetCurrentPosition();
	//	float fDistance = xOffset.Magnitude();

	//	u_int uDistanceTextColour = dbg_uPredatorJumpValidTextColour;
	//	u_int uHeightTextColour = dbg_uPredatorJumpValidTextColour;

	//	if (!m_bJumpAvailable)
	//	{
	//		// Check which text should be changing colour
	//		bool bChanged = false;
	//		if (fDistance > m_fMaxJumpDistance)
	//		{
	//			uDistanceTextColour = dbg_uPredatorJumpInvalidTextColour;
	//			bChanged = true;
	//		}
	//		if (xOffset.y < -m_fMaxJumpHeight)
	//		{
	//			uHeightTextColour = dbg_uPredatorJumpInvalidTextColour;
	//			bChanged = true;
	//		}

	//		// If we haven't changed either, we must be somewhere in the height adjustment's odd range. Just flag both.
	//		if (!bChanged)
	//		{
	//			uDistanceTextColour = dbg_uPredatorJumpInvalidTextColour;
	//			uHeightTextColour = dbg_uPredatorJumpInvalidTextColour;
	//		}
	//	}

	//	// Positive y means down to Asura, but not to people.
	//	xOffset.y = -xOffset.y;
	//	if (Asura_Maths::Abs(xOffset.y) < 0.01f) { xOffset.y = 0.0f; }

	//	// Base the text on the PFX position
	//	Asura_Vector_3 xScreenPos = m_xJumpDestinationPosition + dbg_xPredatorJumpIndicatorOffset;

	//	Asura_Camera::ProjectWorldCoordsToScreenSpace(xScreenPos);

	//	u_int uFormatFlags = ASURA_TEXTFORMAT_RELATIVEPOSITION | ASURA_TEXTFORMAT_VERTICALCENTREJUSTIFIED | ASURA_TEXTFORMAT_FORMATSTRING;
	//	Asura_Vector_2 xRenderPos(xScreenPos.x + dbg_xDistanceTextOffset.x, xScreenPos.y + dbg_xDistanceTextOffset.y);
	//
	//	// AJK; E3 turn jump number off
	//	//Asura_Font_System::Print(xRenderPos.x, xRenderPos.y, uDistanceTextColour, uFormatFlags, "%.1f", fDistance);

	//	uFormatFlags = ASURA_TEXTFORMAT_RELATIVEPOSITION | ASURA_TEXTFORMAT_FORMATSTRING | ASURA_TEXTFORMAT_CENTREJUSTIFIED;
	//	xRenderPos.x = xScreenPos.x + dbg_xHeightTextOffset.x;
	//	if (xOffset.y < 0.0f)
	//	{
	//		xRenderPos.y = xScreenPos.y + dbg_xHeightTextOffset.y;
	//	}
	//	else
	//	{
	//		xRenderPos.y = xScreenPos.y - dbg_xHeightTextOffset.y;
	//		uFormatFlags |= ASURA_TEXTFORMAT_VERTICALBOTTOMJUSTIFIED;
	//	}

	//	// AJK; E3 turn jump number off
	//	//Asura_Font_System::Print(xRenderPos.x, xRenderPos.y, uHeightTextColour, uFormatFlags, "%.1f", xOffset.y);
	//}

#if !defined(ASURA_NO_DEBUG_OUTPUT)
	// Do we need to do any debug rendering?
	if(dbg_bPredatorFocusRenderDebugInfo && m_bJumpAvailable)
	{
		const u_int uColour = 0x80FFFF00;

		Asura_DebugLineRender::AddArrow(m_xJumpDestinationPosition, Asura_Vector_3(m_xJumpDestinationPosition + xUP), uColour, 0.125f, 0.100f);
	}
#endif	// !defined(ASURA_NO_DEBUG_OUTPUT)
}

/// End of function 'AVP_Predator_Focus::Render'. ///

////////////////////////////////////////////////////////////////////////////////
// Function     : AVP_Predator_Focus::UpdateTargets
// Author       : Anthony Gregson
// Date         : 31/01/08
////////////////////////////////////////////////////////////////////////////////
DEBUGVAR float dbg_fFocusOnWallThreshold = 0.707f;
DEBUGVAR float dbg_fSphereStackLeeway = 0.1f;

void AVP_Predator_Focus::UpdateTargets()
{
	Asura_Vector_3 xCameraPosition;
	Asura_Matrix_3x3 xCameraOrientation;

	Asura_Hash_ID uCameraHashID = AVP_Vertebrate_Description::GetMarkerPointHashID( AVP_Vertebrate_Description::MPID_EYES ); 

	if( m_xOwner.GetWorldSpaceMarkerPoint( uCameraHashID, &xCameraPosition, &xCameraOrientation) ) // Camera
	{
		// Always perform the LOS-test from the camera...
		UpdateEnemyTarget(xCameraPosition, xCameraOrientation);
		UpdateJumpTarget(xCameraPosition, xCameraOrientation);
		AVP_Predator_HUDHandler::SetLeapPointInRange( m_bJumpAvailable );
	}
	else
	{
		Asura_Assert(false, "Cannot find camera to use when checking for Predator jump and melee targets.");
	}
}

/// End of function 'AVP_Predator_Focus::CheckForJump'. ///

void AVP_Predator_Focus::UpdateEnemyTarget(  Asura_Vector_3& xPosition, Asura_Matrix_3x3& xOrientation )
{
	const Asura_Vector_3 xCameraDirection(xOrientation.e13, xOrientation.e23, xOrientation.e33);

	// focus lock only works with weapon with lock on.
	Asura_Guid uPreviousTarget = m_uTargetEnemyGuid;
	m_uTargetEnemyGuid = ASURA_GUID_UNREGISTERED;

	const AVP_Inventory_System* pxInventory = m_xOwner.GetInventorySystem();
	const AVP_InventoryItem_Weapon* pxInvWeapon = pxInventory != NULL ? static_cast< const AVP_InventoryItem_Weapon* >( pxInventory->GetCurrentItem( uAVP_INVENTORY_GROUPHASH_WEAPON ) ) : NULL;

	const bool bMeleeWeapon = pxInvWeapon && (pxInvWeapon->GetItemType() == AVP_INVENTORYITEMTYPE_WEAPON_MELEE);

	if ( pxInvWeapon && pxInvWeapon->GetUsesLockOn() )
	{
		// Get the maximum distances from the vision mode
		const AVP_InventoryItem_VisionMode* pxVision = pxInventory != NULL ? static_cast< const AVP_InventoryItem_VisionMode* >( pxInventory->GetCurrentItem(uAVP_INVENTORY_GROUPHASH_VISIONMODE ) ) : NULL;

		Asura_Vector_2 xBestScreenPos(1.0f, 1.0f); // Given we re-centre the positions, this is twice as far from the centre than any screen corner.

		// First check if our current target is still acceptable. If it is, there's no need to go through everyone.
		bool bUsePrevious = false;
		bool bUsePreviousIfNoHighThreat = false;
		float fFocusTime = 1.0f;
		float fFocusRange = 1000.0f;
		float fBestFocusTime = 1.0f;
		float fBestFocusRange = 1000.0f;
		const AVP_ClientEntity_Actor* pxEntity = AVP_ClientEntity_Actor::FindEntity( uPreviousTarget );

		if ( pxEntity && !pxEntity->IsDead() && !pxEntity->IsDestroyed() && !pxEntity->IsInvisible() )
		{
			float fVisionMult = pxVision != NULL ? pxVision->GetVisionModeMultiplier( static_cast< AVP_ENTITY_CLASSIFICATION >( pxEntity->GetClassification() ) ) : 1.0f;

			fFocusRange = fVisionMult * pxInvWeapon->GetLockOnRange();
			fFocusTime = pxInvWeapon->GetLockOnTime() / fVisionMult;

			if (fFocusRange > 0.0f)
			{
				if ( GetActive() && bMeleeWeapon )
				{
					Asura_Vector_2 xScreenPos;
					if (TestEntityAgainstScreenArea(*pxEntity, fFocusRange, xPosition, xBestScreenPos, xScreenPos))
					{
						fBestFocusRange = fFocusRange;
						fBestFocusTime = fFocusTime;
						m_uTargetEnemyGuid = uPreviousTarget;
						xBestScreenPos = xScreenPos;

						const AVP_Inventory_System* pxInventory = pxEntity->GetInventorySystem();
						const AVP_Inventory_Item* pxWeapon = pxInventory ? pxInventory->GetCurrentItem(uAVP_INVENTORY_GROUPHASH_WEAPON) : NULL;
						const bool bHighThreatTarget = pxWeapon ? (pxWeapon->GetItemType() != AVP_INVENTORYITEMTYPE_UNARMED) : true;

						bUsePrevious = bHighThreatTarget;
						bUsePreviousIfNoHighThreat = !bHighThreatTarget;
					}
				}
				else if ( !bMeleeWeapon )
				{
					if(AVP_Predator_LockOn::GetSingleton().GetLockState() == AVP_Predator_LockOn::TARGET_LOCKED)
					{
						m_uTargetEnemyGuid = AVP_Predator_LockOn::GetSingleton().GetLockTargetGuid();
						fBestFocusRange = fFocusRange;
						fBestFocusTime = fFocusTime;
						bUsePrevious = true;
						bUsePreviousIfNoHighThreat = false;
					}
					else if(AVP_Predator_LockOn::GetSingleton().GetLockState() == AVP_Predator_LockOn::TARGET_LOCKING)
					{
						if(m_xOwner.GetInputHandler()->GetControlMapValue( AVP_Vertebrate_InputHandler::PRIMARY_ATTACK_HELD ) > 0.0f)
						{
							AVP_InventoryWeapon_RangedWeapon* pxItem = static_cast<AVP_InventoryWeapon_RangedWeapon*>(m_xOwner.GetInventorySystem()->GetCurrentItem(uAVP_INVENTORY_GROUPHASH_WEAPON));
							AVP_RangedWeapon_SubType* pxSubType = static_cast<AVP_RangedWeapon_SubType*>(pxItem->GetSubType(0));
							if(pxSubType && pxSubType->GetCurrentQuantity() >= pxSubType->GetMinChargePerShot())
							{
								m_uTargetEnemyGuid = AVP_Predator_LockOn::GetSingleton().GetLockTargetGuid();
								fBestFocusRange = fFocusRange;
								fBestFocusTime = fFocusTime;
								bUsePrevious = true;
								bUsePreviousIfNoHighThreat = false;
							}
							else
							{
								// no energy, stop
								m_uTargetEnemyGuid = ASURA_HASH_ID_UNSET;
								AVP_Predator_LockOn::GetSingleton().UnLock();
								return;
							}
						}
					}
				}
			}
		}
		
		if(AVP_Actor_CameraHandler::GetFlyCam())
		{
			bUsePrevious = true; // force same target if using flycam
		}

		if ( !bUsePrevious )
		{
			bool bHasHighThreatTarget = false;

			// The only real way to do this right now is to cycle through all actors.
			// reset best...
			m_uBestTargetGuid = ASURA_GUID_UNREGISTERED;
			for (Asura_Hashed_List_ConstIt<AVP_ClientEntity_Actor> xIt(AVP_ClientEntity_Actor::GetList()); !xIt.Done(); xIt.Next())
			{
				pxEntity = xIt.GetCurrent();
				if ( pxEntity && !pxEntity->IsDead() && !pxEntity->IsDestroyed() && !pxEntity->IsInvisible() && !pxEntity->IsInvulnerable() && (pxEntity->GetGuid() != m_xOwner.GetGuid() ) )
				{
					if (AVP_Tactical_System::GetAllegiance(m_xOwner, *pxEntity) != AVP_ALLEGIANCE_FRIEND)
					{
						float fVisionMult = pxVision != NULL ? pxVision->GetVisionModeMultiplier( static_cast< AVP_ENTITY_CLASSIFICATION>( pxEntity->GetClassification() ) ) : 1.0f;
						fFocusRange = fVisionMult * pxInvWeapon->GetLockOnRange();

						if (fFocusRange > 0.0f)
						{
							const AVP_Inventory_System* pxInventory = pxEntity->GetInventorySystem();
							const AVP_Inventory_Item* pxWeapon = pxInventory ? pxInventory->GetCurrentItem(uAVP_INVENTORY_GROUPHASH_WEAPON) : NULL;
							const bool bHighThreatTarget = pxWeapon ? (pxWeapon->GetItemType() != AVP_INVENTORYITEMTYPE_UNARMED) : true;

							// Don't go on if this is a low-threat target and we have a high-threat target
							if (bHighThreatTarget || (!bUsePreviousIfNoHighThreat && !bHasHighThreatTarget))
							{
								const bool bNewHighThreatTarget = bHighThreatTarget && !bHasHighThreatTarget;

								Asura_Vector_2 xScreenPos;
								if (!TestEntityAgainstScreenArea(*pxEntity, fFocusRange, xPosition, (bNewHighThreatTarget ? Asura_Vector_2(1.0f, 1.0f) : xBestScreenPos), xScreenPos)) { continue; }

								// New best.
								bHasHighThreatTarget = bNewHighThreatTarget;
								fBestFocusTime = pxInvWeapon->GetLockOnTime() / fVisionMult;
								fBestFocusRange = fFocusRange;
								xBestScreenPos = xScreenPos;
								m_uBestTargetGuid = pxEntity->GetGuid();
								if((GetActive() && bMeleeWeapon) || (!bMeleeWeapon && (m_xOwner.GetInputHandler()->GetControlMapValue( AVP_Vertebrate_InputHandler::PRIMARY_ATTACK_HELD ) > 0.0f)))
								{
									m_uTargetEnemyGuid = pxEntity->GetGuid();
								}
							}
						}
					}
				}
			}
		}
	
		if ( bMeleeWeapon )
		{
			if (m_uTargetEnemyGuid != ASURA_GUID_UNREGISTERED)
			{
				// We need an additional test to make sure we can actually use the pounce type required for this enemy.
				const AVP_ClientEntity_Actor* pxTarget = AVP_ClientEntity_Actor::FindEntity(m_uTargetEnemyGuid);
				if (pxTarget)
				{
					if (GetCanPounceToTarget(*pxTarget))
					{
						m_bTargetIsLockOn = false;
						AVP_Predator_LockOn::GetSingleton().SetLockTarget( ASURA_GUID_UNREGISTERED, ASURA_HASH_ID_UNSET );
					}
					else
					{
						m_uTargetEnemyGuid = ASURA_GUID_UNREGISTERED;
					}
				}
				else
				{
					Asura_Assert(false, "This should be impossible.");
					m_uTargetEnemyGuid = ASURA_GUID_UNREGISTERED;
				}
			}
		}
		else
		{
			AVP_Predator_LockOn::GetSingleton().SetLockTarget( m_uTargetEnemyGuid, 0xe5a85d03 ); // target_1
			AVP_Predator_LockOn::GetSingleton().SetMaxRange( fBestFocusRange );
			AVP_Predator_LockOn::GetSingleton().SetLockTime( fBestFocusTime );
			m_bTargetIsLockOn = true;
		}
	}
}

void AVP_Predator_Focus::UpdateJumpTarget( Asura_Vector_3& xPosition, Asura_Matrix_3x3& xOrientation )
{
	// Next, update our jump target.
	const bool bActive = GetActive();
	// If we're using recon we don't get to jump either.
	const bool bReconActive = AVP_Predator_Recon::GetRecon().GetReconIsActive();

	m_fJumpAttractorStickyTimer -= Asura_Timers::GetGameTimeSlice();
	if (!bActive || bReconActive || !m_bJumpAvailable || (m_fJumpAttractorStickyTimer <= 0.0f))
	{
		m_fJumpAttractorStickyTimer = 0.0f;
		m_bDestinationPositionValid = false;
		m_bJumpAvailable = false;
	}

	const Asura_Vector_3 xCameraDirection(xOrientation.e13, xOrientation.e23, xOrientation.e33);

	if (bActive && !bReconActive)
	{
		const Asura_Movement_System* pxMS = m_xOwner.GetMovementSystem();
		const Asura_Move* pxMove = pxMS ? pxMS->GetCurrentMove() : NULL;
		if ( pxMove && pxMove->GetHash() == AVP_Move_Predator_JumpTarget::GetStaticHash() )			
		{
			m_bJumpAvailable = true;
			m_bDestinationPositionValid = true;
		}
		else
		{
			JumpTargetData xJTData(*this, xPosition, xCameraDirection);

			const AVP_ClientActor_Predator& xPredOwner = static_cast<const AVP_ClientActor_Predator&>(m_xOwner);
			const u_short usFlags = m_xOwner.GetFlagsToIgnore() | AsuraPolygonFlag_NoCollisions;

			// First, see if we're looking at a usable point in waypointed space.
			Asura_LOS_InputData xInput(xPosition, xCameraDirection, m_xOwner.GetGuid(), m_fMaxLOSRange, false, usFlags);
			const Asura_LOS_OutputData* pxOutput = Asura_LOS::TestAgainstEverything(&xInput);
			if ((pxOutput->m_fDistanceToIntersection < m_fMaxLOSRange) && (pxOutput->m_fDistanceToIntersection >= m_fMinJumpDistance))
			{
				if ((pxOutput->m_xNormal * xUP) >= dbg_fFocusOnWallThreshold)
				{
					xJTData.m_xJumpDestinationPosition = xPosition + (xCameraDirection * pxOutput->m_fDistanceToIntersection);

					// See if this is actually in a valid predator waypoint.
					AVP_Navigation::TestParams xTestParams = AVP_Navigation::s_xTPStandardParams;
					xTestParams.m_uPathfinderFlags = AVP_Navigation::AVP_AI_PATHFINDER_FLAG_PREDATOR;
					u_short usWP;
					if (AVP_Navigation::GetNearestWaypointID(xJTData.m_xJumpDestinationPosition, usWP, xTestParams))
					{
						xJTData.m_bDestinationPositionValid = Asura_AI_Navigation_System::IsPositionInWaypoint(xJTData.m_xJumpDestinationPosition, usWP);
					}

					// Also check if we can put our collision object in the new location. Use the sphere stack test to allow a little leeway.
					const Asura_Physics_Object* pxPhysObj = xPredOwner.GetPhysicsObject();
					if (pxPhysObj)
					{
						const Asura_Collision_Object* pxCollObj = pxPhysObj->GetCollisionObject();
						if (pxCollObj)
						{
							xJTData.m_bDestinationPositionValid &= Asura_Physics_System::CanPlaceSphereStackAt(pxCollObj->GetBoundingBox(), xJTData.m_xJumpDestinationPosition, usFlags, dbg_fSphereStackLeeway);
						}
					}
					// Finally, do our waypoint test to see if there's a simple way to walk there.
					xJTData.m_bDestinationPositionValid &= !m_xOwner.IsShortRouteAvailable(xJTData.m_xJumpDestinationPosition, m_fMinJumpDistance, m_fMaxJumpDistance);

					xJTData.m_bJumpAvailable = xJTData.m_bDestinationPositionValid && (pxOutput->m_fDistanceToIntersection <= m_fMaxJumpDistance);
					xJTData.m_bJumpAvailable &= xPredOwner.GetCanUseTargetedJump(xJTData.m_xJumpDestinationPosition, xJTData.m_iJumpVariation);
				}
			}

			// If the waypoint check failed, next see if there's an attractor to jump to.
			if (!xJTData.m_bJumpAvailable)
			{
				// All remaining set-up is done in the callback.
				Asura_Attractor_System::GetInstance()->CollectAttractors(xPosition, xOrientation, 0.0f, m_fMaxLOSRange, CheckForJump_Callback, &xJTData);

				if (xJTData.m_bDestinationPositionValid)
				{
					// We want to cancel the choice with a LOS test. However, we can't do that during the search through the attractors because both LOS and CollectAttractors use Asura_LBAABB_Tree::TestTreeWithRay,
					// and that function has static data which prevents it from working from two calls simultaneously.
					// AJG - 01/02/08 - Note that the above may not be true any more, even if it was when I wrote it. However, it's still more efficient to only LOS test the one point.
					// Hopefully testing on just our chosen point won't be too noticeable, but it will definitely cause some false negatives.
					// AJG - 30/05/08 - Now using the more complex test to cancel the target and pick a variation.

					if (xJTData.m_bJumpAvailable)
					{
						xJTData.m_bJumpAvailable = xPredOwner.GetCanUseTargetedJump(xJTData.m_xJumpDestinationPosition, xJTData.m_iJumpVariation);
					}
				}
			}

			// If one of the above found a position, but the jump was prevented for some reason, see if it's at least visible to know if we should be rendering it.
			if (xJTData.m_bDestinationPositionValid && !xJTData.m_bJumpAvailable)
			{
				const Asura_Vector_3 xLOSStart = xPosition;
				const Asura_Vector_3 xLOSEnd = xJTData.m_xJumpDestinationPosition + dbg_xPredatorJumpIndicatorOffset;
				Asura_Vector_3 xLOSDirection = xLOSEnd - xLOSStart;
				const float fLOSDistance = xLOSDirection.Normalise();
				Asura_LOS_InputData xInput(xLOSStart, xLOSDirection, m_xOwner.GetGuid(), fLOSDistance, false, m_xOwner.GetFlagsToIgnore() | AsuraPolygonFlag_NoCollisions);
				const Asura_LOS_OutputData* pxOutput = Asura_LOS::TestAgainstEverything(&xInput);

				// TODO - This probably only needs to test a small subset of entities
				// Crap, it still does the bizarre "entity-guid-equals-ASURA_GUID_SERVER-if-LOS-hit-nothing" thing.
				if (pxOutput->m_bHitEnvironment || (pxOutput->m_uHitEntity && (pxOutput->m_uHitEntity != ASURA_GUID_UNREGISTERED)))
				{
					xJTData.m_bDestinationPositionValid = false;
				}
			}

			if (xJTData.m_bDestinationPositionValid)
			{
				// New position valid, update to that and reset the timer.
				m_xJumpDestinationPosition = xJTData.m_xJumpDestinationPosition;
				m_iJumpVariation = xJTData.m_iJumpVariation;
				m_bDestinationPositionValid = xJTData.m_bDestinationPositionValid;
				m_bJumpAvailable = xJTData.m_bJumpAvailable;
				m_fJumpAttractorStickyTimer = m_fJumpAttractorStickyTimeLimit;
			}
			else if (m_bDestinationPositionValid)
			{
				// Old position sticking around, make sure it's still valid.
				// This has the slightly odd effect of meaning once it goes invalid it can never become valid, but this is preferable than having to do all the attractor tests again as we don't have all the data available to do that.
				// With a short sticky time this shouldn't be noticable.
				if (m_bJumpAvailable)
				{
					m_bJumpAvailable = xPredOwner.GetCanUseTargetedJump(m_xJumpDestinationPosition, m_iJumpVariation);
				}

				if (!m_bJumpAvailable)
				{
					const Asura_Vector_3 xLOSStart = xPosition;
					const Asura_Vector_3 xLOSEnd = m_xJumpDestinationPosition + dbg_xPredatorJumpIndicatorOffset;
					Asura_Vector_3 xLOSDirection = xLOSEnd - xLOSStart;
					const float fLOSDistance = xLOSDirection.Normalise();
					Asura_LOS_InputData xInput(xLOSStart, xLOSDirection, m_xOwner.GetGuid(), fLOSDistance, false, m_xOwner.GetFlagsToIgnore() | AsuraPolygonFlag_NoCollisions);
					const Asura_LOS_OutputData* pxOutput = Asura_LOS::TestAgainstEverything(&xInput);

					// TODO - This probably only needs to test a small subset of entities
					// Crap, it still does the bizarre "entity-guid-equals-ASURA_GUID_SERVER-if-LOS-hit-nothing" thing.
					if (pxOutput->m_bHitEnvironment || (pxOutput->m_uHitEntity && (pxOutput->m_uHitEntity != ASURA_GUID_UNREGISTERED)))
					{
						m_bDestinationPositionValid = false;
					}
				}
			}
		}
	}
}

DEBUGVAR float dbg_fScreenSpaceMeleeLockFromCentreLimit = 0.25f;

bool AVP_Predator_Focus::TestEntityAgainstScreenArea(const AVP_ClientEntity_Actor& xEntity, float fLockRange, const Asura_Vector_3& xCameraPosition, const Asura_Vector_2& xPreviousBestScreenPos, Asura_Vector_2& xEntityScreenPos) const
{
	Asura_Vector_3 xChestPosition;
	Asura_Vector_3 xHeadPosition;
	xEntity.GetChestPosition(xChestPosition);
	xEntity.GetHeadPosition(xHeadPosition);
	Asura_Vector_3 xChestCameraSpacePos = xChestPosition;
	Asura_Vector_3 xHeadCameraSpacePos = xHeadPosition;
	// TODO - make sure this is the correct camera.
	Asura_Camera::ProjectWorldCoordsToScreenSpace(xChestCameraSpacePos);
	Asura_Camera::ProjectWorldCoordsToScreenSpace(xHeadCameraSpacePos);

	if (xChestCameraSpacePos.z <= 0.0f && xHeadCameraSpacePos.z < 0.0f) { return false; } // Behind the camera
	if (xChestCameraSpacePos.z > fLockRange && xHeadCameraSpacePos.z > fLockRange) { return false; } // Too far away from the camera. TODO - Possibly should replace this with a more traditional distance-from-camera-position check.

	xChestCameraSpacePos -= xSCREEN_CENTRE;
	xChestCameraSpacePos.z = 0.0f;
	xHeadCameraSpacePos -= xSCREEN_CENTRE;
	xHeadCameraSpacePos.z = 0.0f;

	// The positions may straddle the allowed area, so the test is a little more complex than normal.
	if (xChestCameraSpacePos.x < -dbg_fScreenSpaceMeleeLockFromCentreLimit && xHeadCameraSpacePos.x < -dbg_fScreenSpaceMeleeLockFromCentreLimit) { return false; }
	if (xChestCameraSpacePos.x > dbg_fScreenSpaceMeleeLockFromCentreLimit && xHeadCameraSpacePos.x > dbg_fScreenSpaceMeleeLockFromCentreLimit) { return false; }
	if (xChestCameraSpacePos.y < -dbg_fScreenSpaceMeleeLockFromCentreLimit && xHeadCameraSpacePos.y < -dbg_fScreenSpaceMeleeLockFromCentreLimit) { return false; }
	if (xChestCameraSpacePos.y > dbg_fScreenSpaceMeleeLockFromCentreLimit && xHeadCameraSpacePos.y > dbg_fScreenSpaceMeleeLockFromCentreLimit) { return false; }

	Asura_Vector_3 xWorldspacePos;
	if (xHeadCameraSpacePos.MagnitudeSquared() < xChestCameraSpacePos.MagnitudeSquared())
	{
		// Head is more central
		xEntityScreenPos.x = xHeadCameraSpacePos.x;
		xEntityScreenPos.y = xHeadCameraSpacePos.y;
		xWorldspacePos = xHeadPosition;
	}
	else
	{
		// Chest is more central
		xEntityScreenPos.x = xChestCameraSpacePos.x;
		xEntityScreenPos.y = xChestCameraSpacePos.y;
		xWorldspacePos = xChestPosition;
	}

	if (xEntityScreenPos.MagnitudeSquared() >= xPreviousBestScreenPos.MagnitudeSquared())
	{
		// Already have a more central target.
		return false;
	}

	// Finally, absolute last check, LOS test.
	Asura_Vector_3 xToEntity = xWorldspacePos - xCameraPosition;
	float fDist = xToEntity.Normalise();
	Asura_LOS_InputData xInput(xCameraPosition, xToEntity, m_xOwner.GetGuid(), fDist);
	const Asura_LOS_OutputData* pxOutput = Asura_LOS::TestAgainstEverything(&xInput);

	return(pxOutput->m_uHitEntity == xEntity.GetGuid());
}


////////////////////////////////////////////////////////////////////////////////
// Function     : AVP_Predator_Focus::CheckForJump_Callback
// Author       : Anthony Gregson
// Date         : 31/01/08
////////////////////////////////////////////////////////////////////////////////

void AVP_Predator_Focus::CheckForJump_Callback(void* pData, const Asura_Attractor_Compiled* const pxAttractor)
{
	Asura_Assert(pData && pxAttractor, "Both pointers should be valid");

	if (pData && pxAttractor)
	{
		JumpTargetData* pxData = static_cast<JumpTargetData*>(pData);

		// All important data is set up in the following function
		pxData->m_xFocus.TestJumpAttractor(*pxAttractor, *pxData);
	}
}

/// End of function 'AVP_Predator_Focus::CheckForJump_Callback'. ///


////////////////////////////////////////////////////////////////////////////////
// Function     : AVP_Predator_Focus::TestJumpAttractor
// Author       : Anthony Gregson
// Date         : 31/01/08
////////////////////////////////////////////////////////////////////////////////
const Asura_Hash_ID uJUMP_TARGET_ATTRACTOR_TYPE = 0x14C80B3F; //Asura_GetHashID("JumpTarget");
const Asura_Hash_ID uBRANCH_ATTRACTOR_TYPE = 0xADAF25A2; //Asura_GetHashID("Branch");
DEBUGVAR float dbg_fBranchMinDotProductForJump = 0.707f;

void AVP_Predator_Focus::TestJumpAttractor(const Asura_Attractor_Compiled& xAttractor, JumpTargetData& xJTData)
{
	// Make sure it's the right type of attractor.
	if ((xAttractor.GetType() != uJUMP_TARGET_ATTRACTOR_TYPE) && (xAttractor.GetType() != uBRANCH_ATTRACTOR_TYPE)) { return; }

	if (xAttractor.GetType() == uBRANCH_ATTRACTOR_TYPE)
	{
		// For branch attractors, we need to make sure this is the on-top one.
		Asura_Vector_3 xNormal;
		xAttractor.GetNormal(xNormal);
		// dot product with up, i.e. (0,-1,0), is equal to -y.
		if (-xNormal.y < dbg_fBranchMinDotProductForJump) { return; }
	}

	// Calculate the nearest point on the attractor to the LOS
	// We need a sensible position to get the nearest point on the line. Fortunately, I've just added a new function for this purpose.
	Asura_Vector_3 xLeastOffset, xPositionOnAttractor;
	Asura_Maths::GetLeastOffsetBetweenLineSegmentAndRay(xJTData.m_xOrigin, xJTData.m_xDirection, xAttractor.GetNode(0)->GetPosition(), xAttractor.GetNode(1)->GetPosition() - xAttractor.GetNode(0)->GetPosition(), xLeastOffset, &xPositionOnAttractor);

	// Test the distance between the position and the camera.
	Asura_Vector_3 xRawOffset = xPositionOnAttractor - xJTData.m_xOrigin;
	float fRawMagSquared = xRawOffset.MagnitudeSquared();
	if (fRawMagSquared < (m_fMinJumpDistance * m_fMinJumpDistance)) { return; }

	Asura_Vector_3 xSquashedOffset = xRawOffset;
	// Only fiddle with the vertical value if the position is above the origin.
	if (xSquashedOffset.y < 0.0f)
	{
		// Squashed sphere test - multiply the height to make it into a sphere test.
		xSquashedOffset.y *= m_fMaxJumpDistance / m_fMaxJumpHeight;
	}

	bool bCanJump = true;

	float fSquashedMagSquared = xSquashedOffset.MagnitudeSquared();
	if (fSquashedMagSquared > (m_fMaxJumpDistance * m_fMaxJumpDistance))
	{
		// See if we want to check for a too-far jump
		if (xJTData.m_bJumpAvailable) { return; } // If there's a valid jump, there's no point in checking for invalid ones any more
		if (fRawMagSquared > (m_fMaxJumpAttractorRenderingDistance * m_fMaxJumpAttractorRenderingDistance)) { return; } // Too far even for rendering

		// We'll test this, but make sure we know we're not actually going to make it.
		bCanJump = false;
	}

	// Make sure this position is usable.
	Asura_Vector_3 xScreenSpacePos = xPositionOnAttractor;
	// This checks if it's on screen, giving the distance in front of the camera as its z value.
	if (!Asura_Camera::ProjectWorldCoordsToScreenSpace(xScreenSpacePos)) { return; }
	if (xScreenSpacePos.z <= 0.0f) { return; }

	// Do the comparison if we're testing for a new best valid case, or if we're testing for a new best invalid case
	// (i.e. the first valid jump passes automatically, and the first invalid jump passes automatically if it's made it this far).
	if (xJTData.m_bJumpAvailable || (!bCanJump && xJTData.m_bDestinationPositionValid))
	{
		// We've already picked a valid jump location, see if this one is any better.
		Asura_Vector_3 xOldScreenSpacePos = xJTData.m_xJumpDestinationPosition;
		Asura_Camera::ProjectWorldCoordsToScreenSpace(xOldScreenSpacePos);

		const Asura_Vector_3 xOffset(0.5f, 0.5f, 0.0f);
		xScreenSpacePos -= xOffset;
		xOldScreenSpacePos -= xOffset;
		xScreenSpacePos.z = xOldScreenSpacePos.z = 0.0f;
		if (xScreenSpacePos.MagnitudeSquared() >= xOldScreenSpacePos.MagnitudeSquared()) { return; }
	}

	// Finally, since it's probably the slowest part, see if it can be quickly reached by a routefind.
	if (m_xOwner.IsShortRouteAvailable(xPositionOnAttractor, m_fMinJumpDistance, m_fMaxJumpDistance)) { return; }

	// Success! A new best option has been found. Store it.
	xJTData.m_bDestinationPositionValid = true;
	xJTData.m_bJumpAvailable = bCanJump;
	xJTData.m_xJumpDestinationPosition = xPositionOnAttractor;
}

/// End of function 'AVP_Predator_Focus::TestJumpAttractor'. ///


void AVP_Predator_Focus::PostCreateInitialise()
{
	const Asura_Hash_ID		uBlueprintHash	= m_xOwner.GetBlueprintHashID();
	const Asura_Hash_ID		uTypeHash		= uAVP_BLUEPRINT_TYPE_PLAYERS;

	const Asura_Blueprint*	pxBlueprint		= Asura_Blueprint_System::GetBlueprintByHashID(uTypeHash, uBlueprintHash);
	if(pxBlueprint)
	{
		Asura_Value xValue;
		if (pxBlueprint->GetParameterValueByHashID(uPREDATOR_JUMP_BLUEPRINT_HASH_MINDISTANCE, xValue))
		{
			m_fMinJumpDistance = xValue.GetFloatValue();
		}
		if (pxBlueprint->GetParameterValueByHashID(uPREDATOR_JUMP_BLUEPRINT_HASH_MAXDISTANCE, xValue))
		{
			m_fMaxJumpDistance = xValue.GetFloatValue();
		}
		if (pxBlueprint->GetParameterValueByHashID(uPREDATOR_JUMP_BLUEPRINT_HASH_MAXHEIGHT, xValue))
		{
			m_fMaxJumpHeight = xValue.GetFloatValue();
			Asura_Assert(m_fMaxJumpHeight > 0.0f, "Jump height zero is invalid, ignoring.");
			if (m_fMaxJumpHeight == 0.0f)
			{
				m_fMaxJumpHeight = m_fMaxJumpDistance;
			}
		}
		if (pxBlueprint->GetParameterValueByHashID(uPREDATOR_JUMP_BLUEPRINT_HASH_LOSRANGE, xValue))
		{
			m_fMaxLOSRange = xValue.GetFloatValue();
		}
		if (pxBlueprint->GetParameterValueByHashID(uPREDATOR_JUMP_BLUEPRINT_HASH_MAXATTRACTORRENDERINGDISTANCE, xValue))
		{
			m_fMaxJumpAttractorRenderingDistance = xValue.GetFloatValue();
		}
		if (pxBlueprint->GetParameterValueByHashID(uPREDATOR_JUMP_BLUEPRINT_HASH_STICKYTIME, xValue))
		{
			m_fJumpAttractorStickyTimeLimit = xValue.GetFloatValue();
		}

		const u_short usPFXFlags = ASURA_PFX_EFFECT_CAMERA_FLAGS | ASURA_PFX_EFFECT_PRESERVE_WHILE_INACTIVE | ASURA_PFX_EFFECT_START_INACTIVE | ASURA_PFX_EFFECT_ALWAYS_ON_TOP | ASURA_PFX_EFFECT_HUD_EFFECT;

		if ( pxBlueprint->GetParameterValueByHashID(s_uFocusMeleeTargetPFX, xValue ) )
		{
			m_uMeleeIndicatorPFXID = Asura_PFX_Entity_Effect::Create(m_xJumpDestinationPosition, xFORWARD, xValue.GetHashIDValue(), NULL, usPFXFlags);
		}

		if ( pxBlueprint->GetParameterValueByHashID(s_uFocusJumpTargetPFX, xValue ) )
		{
			m_uJumpIndicatorPFXID = Asura_PFX_Entity_Effect::Create(m_xJumpDestinationPosition, xFORWARD, xValue.GetHashIDValue(), NULL, usPFXFlags);
		}

		if ( pxBlueprint->GetParameterValueByHashID(s_uFocusCantJumpTargetPFX, xValue ) )
		{
			m_uCantJumpIndicatorPFXID = Asura_PFX_Entity_Effect::Create(m_xJumpDestinationPosition, xFORWARD, xValue.GetHashIDValue(), NULL, usPFXFlags);
		}

		if ( pxBlueprint->GetParameterValueByHashID(s_uFocusNoisyJumpTargetPFX, xValue ) )
		{
			m_uNoisyJumpIndicatorPFXID = Asura_PFX_Entity_Effect::Create(m_xJumpDestinationPosition, xFORWARD, xValue.GetHashIDValue(), NULL, usPFXFlags);
		}
	}

	// Initialising some parameters requires another blueprint.
	const AVP_Inventory_System* pxInventory = m_xOwner.GetInventorySystem();
	if (pxInventory)
	{
		const AVP_InventoryGroup_Weapons* pxWeaponsGroup = static_cast<const AVP_InventoryGroup_Weapons*>(pxInventory->GetGroup(uAVP_INVENTORY_GROUPHASH_WEAPON));
		if (pxWeaponsGroup)
		{
			const AVP_Inventory_Item* pxMeleeItem = pxWeaponsGroup->GetItemOfType(AVP_INVENTORYITEMTYPE_WEAPON_MELEE);
			if (pxMeleeItem)
			{
				const Asura_Blueprint* pxItemBlueprint = Asura_Blueprint_System::GetBlueprintByHashID(uAVP_BLUEPRINT_TYPE_INVENTORY_ITEMS, pxMeleeItem->GetBlueprintHash());
				if (pxItemBlueprint)
				{
					Asura_Value xValue;

					AVP_Blueprint_ParameterEnumerator_PounceVariations_FocusData xEnumerator(uAVP_BLUEPRINT_TYPE_MOVESPREDATOR, *this);
					pxItemBlueprint->EnumerateParameters(AVP_Vertebrate_MeleeCounter_System::GetPounceAttackBaseParameterName(), 2, xEnumerator);
				}
			}
		}
	}
}

/// End of file. ///
