
///////////////////////////////////////////////////////////////////////////////
//
// File   : AVP_Human_JumpHandler.cpp
// Author : Anthony Gregson
// Date   : 30/07/08
//
// Copyright (C) 2008 Rebellion, All Rights Reserved.
//
///////////////////////////////////////////////////////////////////////////////

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

#include "Core/Asura.h"

// This file's header...
#include "Actor/Vertebrates/Human/AVP_Human_JumpHandler.h"

// Asura independent header-files...
#include "Attractors/Asura_Attractor_System.h"
#include "Network/Asura_Message.h"

// Game independent header-files...
#include "Attractors/AVP_Attractors_System.h"
#include "Blueprint/AVP_Blueprint_HashIDs.h"
#include "Entity/PhysicalObject/Actor/Vertebrates/Human/AVP_ClientActor_Human.h"
#include "Inventory/AVP_Inventory_HashIDs.h"
#include "Inventory/AVP_Inventory_Item.h"
#include "Inventory/AVP_Inventory_System.h"
#include "Moves/Vertebrates/Human/AVP_Human_MovesMantle.h"

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

////////////////////////////////////////////////////////////////////////////////
// Function     : AVP_Human_JumpHandler::AVP_Human_JumpHandler
// Author       : Anthony Gregson
// Date         : 30/07/08
////////////////////////////////////////////////////////////////////////////////

AVP_Human_JumpHandler::AVP_Human_JumpHandler(AVP_ClientActor_Human& m_xOwner)
: PARENT(m_xOwner)
, m_uCurrentMantleFirstPersonAnim(ASURA_HASH_ID_UNSET)
, m_uCurrentMantleThirdPersonAnim(ASURA_HASH_ID_UNSET)
, m_xCurrentMantleTargetPosition(Asura_Maths::ZeroVector3)
, m_xCurrentMantleTargetOrientation(Asura_Maths::IdentityQuat)
, m_bCurrentMantleRequiresJump(false)
, m_bMantleAvailable(false)
{
}

////////////////////////////////////////////////////////////////////////////////
// Function     : AVP_Human_JumpHandler::~AVP_Human_JumpHandler
// Author       : Anthony Gregson
// Date         : 30/07/08
////////////////////////////////////////////////////////////////////////////////

AVP_Human_JumpHandler::~AVP_Human_JumpHandler()
{
}

////////////////////////////////////////////////////////////////////////////////
// Function     : AVP_Human_JumpHandler::Update
// Author       : Anthony Gregson
// Date         : 30/07/08
////////////////////////////////////////////////////////////////////////////////
DEBUGVAR float dbg_fHumanMantleRangeSquared = 4.0f;

void AVP_Human_JumpHandler::Update()
{
	const AVP_Inventory_System* pxInventory = m_xOwner.GetInventorySystem();
	if (pxInventory)
	{
		const AVP_Inventory_Item* pxCurrentWeapon = pxInventory->GetCurrentItem(uAVP_INVENTORY_GROUPHASH_WEAPON);
		const Asura_Blueprint* pxWeaponBlueprint = pxCurrentWeapon ? pxCurrentWeapon->GetBlueprint() : NULL;
		if (pxWeaponBlueprint)
		{
			Asura_Quat xCurrentOrientation;
			Asura_AVector3 xAAimDir;
			m_xOwner.GetAimDirection(xAAimDir);
			Asura_Vector_3 xAimDir(xAAimDir);
			xAimDir.y = 0.0f;
			if (xAimDir.ApproximatelyEquals(Asura_Maths::ZeroVector3))
			{
				xCurrentOrientation.FromMatrix(m_xOwner.GetCurrentOrientation());
			}
			else
			{
				xAimDir.Normalise();
				Asura_Matrix_3x3 xMat;
				Asura_Maths::OrientationFromDirection(xMat, xAimDir);
				xCurrentOrientation.FromMatrix(xMat);
			}

			const Asura_Vector_3& xCurrentPosition = m_xOwner.GetCurrentPosition();

			m_bMantleAvailable = GetMantleTarget(m_xOwner.GetCurrentPosition(), xCurrentOrientation, pxWeaponBlueprint, m_xCurrentMantleTargetPosition, m_xCurrentMantleTargetOrientation, m_uCurrentMantleFirstPersonAnim, m_uCurrentMantleThirdPersonAnim, m_bCurrentMantleRequiresJump);

			if (m_bMantleAvailable)
			{
				// Since different things may want different ranges, cull on that here
				Asura_Vector_3 xFlatOffset(xCurrentPosition - m_xCurrentMantleTargetPosition);
				xFlatOffset.y = 0.0f;
				m_bMantleAvailable = xFlatOffset.MagnitudeSquared() <= dbg_fHumanMantleRangeSquared;
			}
		}
		else
		{
			m_bMantleAvailable = false;
		}
	}
	else
	{
		m_bMantleAvailable = false;
	}

	PARENT::Update();
}

////////////////////////////////////////////////////////////////////////////////
// Function     : AVP_Human_JumpHandler::HandleJump
// Author       : Anthony Gregson
// Date         : 30/07/08
////////////////////////////////////////////////////////////////////////////////

bool AVP_Human_JumpHandler::HandleJump(const float fJumpValue)
{
	const Asura_Movement_System*	pxMovementSystem		= m_xOwner.GetMovementSystem();
	const Asura_Move*				pxCurrentMove			= pxMovementSystem ? pxMovementSystem->GetCurrentMove() : NULL;
	const bool						bMantling				= pxCurrentMove ? (pxCurrentMove->GetHash() == AVP_Move_Human_Mantle::GetStaticHash()) : false;

	if (bMantling)
	{
		return false;
	}
	else
	{
		const AVP_Inventory_System*		pxInventory				= m_xOwner.GetInventorySystem();
		const AVP_Inventory_Item*		pxStim					= pxInventory ? pxInventory->GetItem(uAVP_BLUEPRINT_PARAM_STIM) : NULL;
		const AVP_Inventory_Item*		pxCurrentWeapon			= pxInventory ? pxInventory->GetCurrentItem(uAVP_INVENTORY_GROUPHASH_WEAPON) : NULL;

		if ( pxCurrentWeapon->IsEncumberment() )
		{
			return false;
		}

		const bool						bStimIdling				= pxStim ? pxStim->IsIdling(true) : true;
		const bool						bWeaponIdling			= pxCurrentWeapon ? pxCurrentWeapon->IsIdling(true) : true;

		if ((fJumpValue > 0.0f) && GetJumpRequestMantleAvailable() && bStimIdling && bWeaponIdling)
		{
			SendMantleMoveRequest();
			return true;
		}
		else
		{
			return PARENT::HandleJump(fJumpValue);
		}
	}
}

////////////////////////////////////////////////////////////////////////////////
// Function     : AVP_Human_JumpHandler::SendMantleMoveRequest
// Author       : Anthony Gregson
// Date         : 30/07/08
////////////////////////////////////////////////////////////////////////////////

void AVP_Human_JumpHandler::SendMantleMoveRequest() const
{
	Asura_Assert(m_bMantleAvailable, "Shouldn't be trying to mantle when we can't");

	Asura_Message* pxMessage = Asura_Network::CreateMessage(ASURA_MSG_MOVE_START, sizeof(AVP_Msg_Move_StartMantle));
	if (pxMessage)
	{
		AVP_Msg_Move_StartMantle* pxData = static_cast<AVP_Msg_Move_StartMantle*>(pxMessage->GetData());

		pxData->ClearFlags();
		pxData->SetMoveID(AVP_Move_Human_Mantle::GetStaticHash());

		pxData->SetTargetPosition(m_xCurrentMantleTargetPosition);
		pxData->SetTargetOrientation(m_xCurrentMantleTargetOrientation);
		pxData->SetAnimationHashes(m_uCurrentMantleFirstPersonAnim, m_uCurrentMantleThirdPersonAnim);
		pxData->SetRequiresJump(m_bCurrentMantleRequiresJump);

		// Do our usual handle-locally-immediately-then-send-to-server thing.
		pxMessage->SetToGuid(m_xOwner.GetGuid());
		pxMessage->SetFromGuid(m_xOwner.GetGuid());

		m_xOwner.HandleMessage(pxMessage);

		pxData->SetIgnoredByOwner(true);
		Asura_Network::Send(pxMessage);
	}
}


bool AVP_Human_JumpHandler::GetJumpRequestMantleAvailable() const
{
	return m_bMantleAvailable && m_bCurrentMantleRequiresJump
		&& ((m_xOwner.GetGroundType() == AVP_Actor_PhysicsObjectHandler::ON_FLOOR) || (m_xOwner.GetConstrainedMovementAttractorID() != uASURA_ATTRACTOR_ID_NONE))
		&& (!m_xOwner.GetMovementSystem() || !m_xOwner.GetMovementSystem()->IsMoveInProgress());
}

bool AVP_Human_JumpHandler::GetNonJumpRequestMantleAvailable() const
{
	return m_bMantleAvailable && !m_bCurrentMantleRequiresJump
		&& (!m_xOwner.GetMovementSystem() || !m_xOwner.GetMovementSystem()->IsMoveInProgress());
}


DEBUGVAR float dbg_fMantleSearchRange = 2.0f;
DEBUGVAR float dbg_fMinMantleHeight = 0.15f;
static const Asura_Vector_3 g_xMantleAttractorOriginOffset(0.0f, -1.0f, 0.0f);
const int iAVP_MANTLE_MAX_DEFINITIONS = 8;

bool AVP_Human_JumpHandler::GetMantleTarget(const Asura_Vector_3& xCurrentPosition, const Asura_Quat& xCurrentOrientation, const Asura_Blueprint* pxCurrentWeaponBlueprint, Asura_Vector_3& xTargetPosition, Asura_Quat& xTargetOrientation, Asura_Hash_ID& uFirstPersonAnim, Asura_Hash_ID& uThirdPersonAnim, bool& bRequiresJump)
{
	// Generate a list of mantle types to check against.
	if (!pxCurrentWeaponBlueprint) { return false; }

	MantleDefinition axMantles[iAVP_MANTLE_MAX_DEFINITIONS];
	AVP_Blueprint_ParameterEnumerator_MantleDefinitions xEnumerator(axMantles, iAVP_MANTLE_MAX_DEFINITIONS);
	
	// Enumerate parameters.
	int iNumMantles = pxCurrentWeaponBlueprint->EnumerateParameters(szAVP_BLUEPRINT_PARAM_MANTLE, 2, xEnumerator);

	// Set up data for the attractor callback
	Asura_Matrix_3x3 xMat;
	xCurrentOrientation.ToMatrix(xMat);

	CallbackData xData;
	xData.m_xInitialPosition = xCurrentPosition;
	xData.m_xFacingDirection = Asura_Vector_3(xMat.e13, xMat.e23, xMat.e33);
	xData.m_pxMantleDefinitions = axMantles;
	xData.m_iNumMantleDefinitions = iNumMantles;
	xData.m_iBestMantleDefinition = -1;
	xData.m_xBestTargetPosition = Asura_Maths::ZeroVector3;
	xData.m_xBestTargetOri = Asura_Maths::IdentityQuat;

	// Search through the attractors for a sensible mantle one.
	Asura_Attractor_System::GetInstance()->CollectAttractors(xCurrentPosition + g_xMantleAttractorOriginOffset, xMat, 0.0f, dbg_fMantleSearchRange, GetMantleTarget_Callback, &xData);

	// Did we find one?
	if (xData.m_iBestMantleDefinition >= 0)
	{
		const MantleDefinition& xChosenMantle = axMantles[xData.m_iBestMantleDefinition];
		uFirstPersonAnim = xChosenMantle.m_uFirstPersonAnim;
		uThirdPersonAnim = xChosenMantle.m_uThirdPersonAnim;
		xTargetPosition = xData.m_xBestTargetPosition;
		xTargetOrientation = xData.m_xBestTargetOri;
		bRequiresJump = xChosenMantle.m_bRequiresJump;

		return true;
	}
	else
	{
		return false;
	}
}

DEBUGVAR float dbg_fMantleTargetPosForwardOffset = 0.3f;
DEBUGVAR float dbg_fMantleTargetPosVerticalOffset = -0.001f;

void AVP_Human_JumpHandler::GetMantleTarget_Callback(void* pData, const Asura_Attractor_Compiled* const pxAttractor)
{
	if (!pData || !pxAttractor) { return; }
	if (pxAttractor->GetType() != uAVP_ATTRACTOR_TYPE_MANTLE) { return; }

	CallbackData* pxData = static_cast<CallbackData*>(pData);

	// Make sure we're on the correct side.
	Asura_Vector_3 xNormal;
	pxAttractor->GetNormal(xNormal);
	if ((xNormal * pxData->m_xFacingDirection) >= 0.0f) { return; }

	// Pick the most appropriate point on the attractor to go to.
	const Asura_Vector_3& xNode0Pos = pxAttractor->GetNode(0)->GetPosition();
	const Asura_Vector_3 xAttractorVector = pxAttractor->GetNode(1)->GetPosition() - xNode0Pos;

	Asura_Vector_3 xPosOnAttractor, xPosOffset;
	Asura_Maths::GetLeastOffsetBetweenLineSegmentAndRay(pxData->m_xInitialPosition, pxData->m_xFacingDirection, xNode0Pos, xAttractorVector, xPosOffset, &xPosOnAttractor);

	// Make sure it isn't behind or below us.
	const Asura_Vector_3 xOffset = xPosOnAttractor - pxData->m_xInitialPosition;
	if (xOffset.y >= -dbg_fMinMantleHeight) { return; }

	Asura_Vector_3 xDirection = xOffset;
	xDirection.y = 0.0f;
	if (xDirection.ApproximatelyEquals(Asura_Maths::ZeroVector3)) { return; }

	xDirection.Normalise();

	const float fDP = (xDirection * pxData->m_xFacingDirection);
	if (fDP <= 0.0f) { return; }

	// Work out if there's an appropriate mantle definition.
	// We want the least max height that our offset is less than.
	const float fHeight = -xOffset.y;
	float fBestHeight = Asura_Maths::LargeFloat;
	int iBestMantle = -1;
	for (int i=0; i<pxData->m_iNumMantleDefinitions; ++i)
	{
		const MantleDefinition& xMantle = pxData->m_pxMantleDefinitions[i];
		if (xMantle.m_fMaxHeight < fHeight) { continue; }
		if (xMantle.m_fMaxHeight >= fBestHeight) { continue; }

		fBestHeight = xMantle.m_fMaxHeight;
		iBestMantle = i;
	}

	// See if this is a better choice of attractor than a previous one we've found.
	if (pxData->m_iBestMantleDefinition >= 0)
	{
		Asura_Vector_3 xBestDir = pxData->m_xBestTargetPosition - pxData->m_xInitialPosition;
		xBestDir.y = 0.0f;
		xBestDir.Normalise();
		const float fBestDP = xBestDir * pxData->m_xFacingDirection;

		if (fBestDP >= fDP) { return; }
	}

	if (iBestMantle < 0) { return; }

	// Found a valid mantle which suits better than the previous. Store its details in the callback data.
	pxData->m_iBestMantleDefinition = iBestMantle;
	// Calculate the appropriate target orientation.
	// If we have to press a button to trigger the move, assume we need to face the wall fully, and even if we don't have a button press it's handy to have that same orientation for testing.
	// Get a perpendicular to the attractor direction.
	Asura_Vector_3 xCross = xAttractorVector ^ Asura_Vector_3(0.0f, 1.0f, 0.0f);
	// Make sure this is on the correct side of the line
	if ((xCross * xDirection) < 0.0f)
	{
		xCross = -xCross;
	}
	xCross.Normalise();
	Asura_Matrix_3x3 xOri;
	Asura_Maths::OrientationFromDirection(xOri, xCross);
	pxData->m_xBestTargetOri.FromMatrix(xOri);

	// Add on a small offset to the target position, so that we're not teetering right on the edge.
	pxData->m_xBestTargetPosition = xPosOnAttractor + (Asura_Vector_3(0.0f, dbg_fMantleTargetPosVerticalOffset, dbg_fMantleTargetPosForwardOffset) * xOri);
}


// Parameter enumerator for a mantle set.

AVP_Blueprint_ParameterEnumerator_MantleDefinitions::AVP_Blueprint_ParameterEnumerator_MantleDefinitions(AVP_Human_JumpHandler::MantleDefinition* pxMantleStorage, int iMaxEntries)
: PARENT()
, m_pxMantleDefinitions(pxMantleStorage)
, m_iMaximumMantles(iMaxEntries)
, m_iCurrentNumMantles(0)
{
}

void AVP_Blueprint_ParameterEnumerator_MantleDefinitions::BeginEnumeration()
{
	m_iCurrentNumMantles = 0;
}

bool AVP_Blueprint_ParameterEnumerator_MantleDefinitions::Enumerate(const u_int uIndex, char const* const szName, const Asura_Value& xValue)
{
	if (m_iCurrentNumMantles >= m_iMaximumMantles)
	{
		Asura_Assert(false, "Too many mantles, ignoring the rest.");
		return false;
	}

	const Asura_Blueprint* pxBlueprint = Asura_Blueprint_System::GetBlueprintByHashID(uAVP_BLUEPRINT_TYPE_MOVESHUMAN, xValue.GetHashIDValue());
	if (!pxBlueprint)
	{
		Asura_Assert(false, "Failed to find mantle blueprint.");
		// Since we're relying on the count returned being accurate, return failure. If this is a problem we need to return the number found in a different way.
		return false;
	}

	Asura_Value xMantleValue;
	if (pxBlueprint->GetParameterValueByHashID(uAVP_BLUEPRINT_PARAM_MANTLE_MAXHEIGHT, xMantleValue))
	{
		m_pxMantleDefinitions[m_iCurrentNumMantles].m_fMaxHeight = xMantleValue.GetFloatValue();
	}
	else
	{
		m_pxMantleDefinitions[m_iCurrentNumMantles].m_fMaxHeight = 0.0f;
	}

	if (pxBlueprint->GetParameterValueByHashID(uAVP_BLUEPRINT_PARAM_FIRSTPERSONANIM, xMantleValue))
	{
		m_pxMantleDefinitions[m_iCurrentNumMantles].m_uFirstPersonAnim = xMantleValue.GetHashIDValue();
	}
	else
	{
		m_pxMantleDefinitions[m_iCurrentNumMantles].m_fMaxHeight = ASURA_HASH_ID_UNSET;
	}

	if (pxBlueprint->GetParameterValueByHashID(uAVP_BLUEPRINT_PARAM_THIRDPERSONANIM, xMantleValue))
	{
		m_pxMantleDefinitions[m_iCurrentNumMantles].m_uThirdPersonAnim = xMantleValue.GetHashIDValue();
	}
	else
	{
		m_pxMantleDefinitions[m_iCurrentNumMantles].m_uThirdPersonAnim = ASURA_HASH_ID_UNSET;
	}

	if (pxBlueprint->GetParameterValueByHashID(uAVP_BLUEPRINT_PARAM_MANTLE_REQUIRESJUMP, xMantleValue))
	{
		m_pxMantleDefinitions[m_iCurrentNumMantles].m_bRequiresJump = xMantleValue.GetBoolValue();
	}
	else
	{
		m_pxMantleDefinitions[m_iCurrentNumMantles].m_bRequiresJump = true;
	}

	++m_iCurrentNumMantles;

	return true;
}

void AVP_Blueprint_ParameterEnumerator_MantleDefinitions::EndEnumeration(const bool bSuccess)
{
	// Nothing to do here, as we've already filled out the data.
	// Success is irrelevant, as we should still have a valid array of animations, we just happened to find more than we could buffer up.
}

