
///////////////////////////////////////////////////////////////////////////////
//
// File   : AVP_Alien_Focus.cpp
// Author : Anthony Gregson
// Date   : 22/02/08
//
// Copyright (C) 2008 Rebellion, All Rights Reserved.
//
///////////////////////////////////////////////////////////////////////////////

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

#include "Core/Asura.h"

// This file's header...
#include "Actor/Vertebrates/Alien/AVP_Alien_Focus.h"

// Asura independent header-files...
#include "Blueprint/Asura_Blueprint_System.h"
#include "Camera/Asura_Camera.h"
#include "Network/Asura_Message.h"
#include "Network/Asura_Network.h"
#include "ProjectSettings/Asura_PhysicsConfiguration.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/Alien/AVP_Alien_HUDHandler.h"
#include "Actor/Vertebrates/AVP_Vertebrate_Description.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/Alien/AVP_ClientActor_Alien.h"
#include "Entity/Vent/AVP_ClientEntity_Vent.h"
#include "Inventory/AVP_Inventory_HashIDs.h"
#include "Inventory/AVP_Inventory_Item.h"
#include "Inventory/AVP_Inventory_System.h"

// Game Axon
#include "Tactical/AVP_Tactical_System.h"

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

static const Asura_Vector_3 xUP(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 uALIEN_POUNCE_BLUEPRINT_HASH_MAXDISTANCE = 0x51B5E1D5; //Asura_GetHashID("PounceDistanceMax");

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

static const Asura_Hash_ID s_uFocusMeleeTargetPFX = 0x460656f3;	// Focus.MeleeTargetPFX
static const Asura_Hash_ID s_uFocusJumpTargetPFX = 0xc5409f0d;	// Focus.JumpTargetPFX

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


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


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

////////////////////////////////////////////////////////////////////////////////
// Function     : AVP_Alien_Focus::AVP_Alien_Focus
// Author       : Anthony Gregson
// Date         : 22/02/08
////////////////////////////////////////////////////////////////////////////////

AVP_Alien_Focus::AVP_Alien_Focus(AVP_ClientActor_Alien& xOwner)
: PARENT(xOwner)
, m_uMeleeTarget(ASURA_GUID_UNREGISTERED)
, m_uVentTarget( ASURA_GUID_UNREGISTERED )
, m_uMeleeIndicatorPFXID(ASURA_PFX_INVALID_EFFECT_ID)
, m_uVentIndicatorPFXID( ASURA_PFX_INVALID_EFFECT_ID )
, m_fMaxPounceDistance(15.0f)
{
}

/// End of function 'AVP_Alien_Focus::AVP_Alien_Focus'. ///



////////////////////////////////////////////////////////////////////////////////
// Function     : AVP_Alien_Focus::~AVP_Alien_Focus
// Author       : Anthony Gregson
// Date         : 22/02/08
////////////////////////////////////////////////////////////////////////////////

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

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


////////////////////////////////////////////////////////////////////////////////
// Function     : AVP_Alien_Focus::Update
// Author       : Anthony Gregson
// Date         : 22/02/08
////////////////////////////////////////////////////////////////////////////////

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

	// Are there any enemies or jump targets?
	// TODO - may not need to check every frame.
	UpdateTargets();

	if( m_uVentTarget != ASURA_GUID_UNREGISTERED )
	{
		AVP_Alien_HUDHandler::SetSurfaceInRange( true );
	}
}

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


////////////////////////////////////////////////////////////////////////////////
// Function     : AVP_Alien_Focus::Render
// Author       : Anthony Gregson
// Date         : 22/02/08
////////////////////////////////////////////////////////////////////////////////

void AVP_Alien_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(m_uMeleeTarget);
	if (pxEffect)
	{
		if (pxMeleeTarget)
		{
			if (pxEffect->GetState() != ASURA_PFX_EFFECT_RUNNING)
			{
				pxEffect->Start();
			}
			Asura_Vector_3 xPosition;
			pxMeleeTarget->GetChestPosition(xPosition);
			pxEffect->SetPosition(xPosition);
		}
		else if (pxEffect->GetState() == ASURA_PFX_EFFECT_RUNNING)
		{
			pxEffect->Stop(false);
		}
	}

	pxEffect = Asura_PFX_System::GetEntityEffect(m_uVentIndicatorPFXID);
	const AVP_ClientEntity_Vent* pxVentTarget = AVP_ClientEntity_Vent::FindEntity(m_uVentTarget);
	if (pxEffect)
	{
		if (pxVentTarget)
		{
			if (pxEffect->GetState() != ASURA_PFX_EFFECT_RUNNING)
			{
				pxEffect->Start();
			}
			Asura_Vector_3 xPosition;
			xPosition = pxVentTarget->GetCurrentPosition();
			pxEffect->SetPosition(xPosition);
		}
		else if (pxEffect->GetState() == ASURA_PFX_EFFECT_RUNNING)
		{
			pxEffect->Stop(false);
		}
	}
}

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


////////////////////////////////////////////////////////////////////////////////
// Function     : AVP_Alien_Focus::UpdateTargets
// Author       : Anthony Gregson
// Date         : 22/02/08
////////////////////////////////////////////////////////////////////////////////

void AVP_Alien_Focus::UpdateTargets()
{
	Asura_Guid uPreviousTarget = m_uMeleeTarget == ASURA_GUID_UNREGISTERED ? m_uVentTarget : m_uMeleeTarget;
	m_uMeleeTarget = m_uVentTarget = ASURA_GUID_UNREGISTERED;

	// Only search in focus mode.
	if (!GetActive()) { return; }

	Asura_Vector_3 xCameraPosition;
	Asura_Matrix_3x3 xCameraOrientation;

	// Always perform the LOS-test from the camera...
	Asura_Hash_ID uCameraHashID = AVP_Vertebrate_Description::GetMarkerPointHashID( AVP_Vertebrate_Description::MPID_EYES ); 
	
	if( m_xOwner.GetWorldSpaceMarkerPoint( uCameraHashID, &xCameraPosition, &xCameraOrientation) ) // Camera
	{
		const Asura_Vector_3 xCameraDirection(xCameraOrientation.e13, xCameraOrientation.e23, xCameraOrientation.e33);

		Asura_Vector_2 xBestScreenPos(1.0f, 1.0f); // Given we recentre 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;
		bool bIsVertebrate = true;
		const AVP_ClientActor_Vertebrate* pxVert = AVP_ClientActor_Vertebrate::FindEntity(uPreviousTarget);
		if ( pxVert == NULL )
		{
			bIsVertebrate = false;
			const AVP_ClientEntity_Vent* pxVent = AVP_ClientEntity_Vent::FindEntity(uPreviousTarget);
			if ( pxVent && !pxVent->IsDestroyed() && pxVent->IsEntityActive() )
			{
				Asura_Vector_2 xScreenPos;
				if (TestEntityAgainstScreenArea(*pxVent, xCameraPosition, xBestScreenPos, bIsVertebrate, xScreenPos))
				{
					m_uVentTarget = uPreviousTarget;
					xBestScreenPos = xScreenPos;
					bUsePrevious = true;
					bUsePreviousIfNoHighThreat = false;
				}
			}
		}
		else if ( !pxVert->IsDead() && !pxVert->IsDestroyed() && !pxVert->IsInvisible() )
		{
			Asura_Vector_2 xScreenPos;
			if (TestEntityAgainstScreenArea(*pxVert, xCameraPosition, xBestScreenPos, bIsVertebrate, xScreenPos))
			{
				m_uMeleeTarget = uPreviousTarget;
				xBestScreenPos = xScreenPos;

				const AVP_Inventory_System* pxInventory = pxVert->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;
			}
		}

		if (!bUsePrevious)
		{
			bool bHasHighThreatTarget = false;

			// The only real way to do this right now is to cycle through all actors.
			for (Asura_Hashed_List_ConstIt<AVP_ClientActor_Vertebrate> xIt(AVP_ClientActor_Vertebrate::GetList()); !xIt.Done(); xIt.Next())
			{
				const AVP_ClientActor_Vertebrate* pxVert = xIt.GetCurrent();
				if (!pxVert || pxVert->IsDead() || pxVert->IsDestroyed() || pxVert->IsInvisible() || (pxVert->GetGuid() == m_xOwner.GetGuid()))
				{
					continue;
				}
				if (AVP_Tactical_System::GetAllegiance(m_xOwner, *pxVert) == AVP_ALLEGIANCE_FRIEND)
				{
					continue;
				}

				const AVP_Inventory_System* pxInventory = pxVert->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;
					Asura_Vector_3 xWorldspacePos;
					if (!TestEntityAgainstScreenArea(*pxVert, xCameraPosition, (bNewHighThreatTarget ? Asura_Vector_2(1.0f, 1.0f) : xBestScreenPos), true, xScreenPos)) { continue; }

					// New best.
					xBestScreenPos = xScreenPos;
					m_uMeleeTarget = pxVert->GetGuid();
					bHasHighThreatTarget = bHighThreatTarget;
				}
			}

			if (!bUsePreviousIfNoHighThreat)
			{
				// The only real way to do this right now is to cycle through all vents.
				for (Asura_Hashed_List_ConstIt<AVP_ClientEntity_Vent> xIt(AVP_ClientEntity_Vent::GetList()); !xIt.Done(); xIt.Next())
				{
					const AVP_ClientEntity_Vent* pxVent = xIt.GetCurrent();
					if ( !pxVent || pxVent->IsDestroyed() || !pxVent->IsEntityActive() )
					{
						continue;
					}

					Asura_Vector_2 xScreenPos;
					Asura_Vector_3 xWorldspacePos;
					if (!TestEntityAgainstScreenArea(*pxVent, xCameraPosition, xBestScreenPos, false, xScreenPos )) { continue; }

					// New best.
					xBestScreenPos = xScreenPos;
					m_uVentTarget = pxVent->GetGuid();
				}
			}
		}
	}
	else
	{
		Asura_Assert(false, "Cannot find camera to use when checking for Alien jump and melee targets.");
	}
}

/// End of function 'AVP_Alien_Focus::UpdateTargets'. ///

DEBUGVAR float dbg_fScreenSpaceMeleeLockFromCentreLimit = 0.25f;
DEBUGVAR float dbg_fVentTargetLOSOffset = 0.3f;
DEBUGVAR float dbg_fVentTargetLOSLeeway = 1.0f;

bool AVP_Alien_Focus::TestEntityAgainstScreenArea(const Asura_Entity_Renderable& xEntity, const Asura_Vector_3& xCameraPosition, const Asura_Vector_2& xPreviousBestScreenPos, bool bIsVertebrate, Asura_Vector_2& xEntityScreenPos ) const
{
	Asura_Vector_3 xChestPosition;
	Asura_Vector_3 xHeadPosition;
	Asura_Vector_3 xChestCameraSpacePos(Asura_Maths::LargeFloat, Asura_Maths::LargeFloat, Asura_Maths::LargeFloat);
	Asura_Vector_3 xHeadCameraSpacePos(Asura_Maths::LargeFloat, Asura_Maths::LargeFloat, Asura_Maths::LargeFloat);
	if( bIsVertebrate )	
	{
		const AVP_ClientActor_Vertebrate* pxVert = AVP_ClientActor_Vertebrate::FindEntity( xEntity.GetGuid() );

		// First things first, see if there is an available pounce.
		if (!pxVert || !GetCanPounceToTarget(*pxVert)) { return false; }

		pxVert->GetChestPosition(xChestPosition);
		pxVert->GetHeadPosition(xHeadPosition);

		xChestCameraSpacePos = xChestPosition;
		xHeadCameraSpacePos = xHeadPosition;

		// TODO - make sure this is the correct camera.
		Asura_Camera::ProjectWorldCoordsToScreenSpace(xChestCameraSpacePos);
		Asura_Camera::ProjectWorldCoordsToScreenSpace(xHeadCameraSpacePos);
	}
	else
	{
		xChestPosition = xEntity.GetCurrentPosition();
		xHeadPosition = xChestPosition;
		xChestCameraSpacePos = xChestPosition;
		Asura_Camera::ProjectWorldCoordsToScreenSpace(xChestCameraSpacePos);
		xHeadCameraSpacePos = xChestCameraSpacePos;
	}

	if (xChestCameraSpacePos.z <= 0.0f && xHeadCameraSpacePos.z < 0.0f) { return false; } // Behind the camera
	if (xChestCameraSpacePos.z > m_fMaxPounceDistance && xHeadCameraSpacePos.z > m_fMaxPounceDistance) { 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;
	}

	Asura_Vector_3 xToEntity = xWorldspacePos - xCameraPosition;

	if (!bIsVertebrate)
	{
		// For vents, shift the LOS target backwards/forwards based on what side we're on.
		const Asura_Matrix_3x3& xOri = xEntity.GetCurrentOrientation();
		const Asura_Vector_3 xForward(xOri.e13, xOri.e23, xOri.e33);
		const bool bBehindVent = (xForward * xToEntity) > 0.0f;
		if (bBehindVent)
		{
			xToEntity -= xForward * dbg_fVentTargetLOSOffset;
		}
		else
		{
			// If the vent has no attractors we have to be behind it, so see if it has some.
			const AVP_ClientEntity_Vent* pxVent = AVP_ClientEntity_Vent::FindEntity(xEntity.GetGuid());
			if (!pxVent || pxVent->GetUseExitPointsOnly()) 
			{
				return false;
			}

			xToEntity += xForward * dbg_fVentTargetLOSOffset;
		}
	}

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

	const Asura_Guid uHitEntity = pxOutput->m_uHitEntity;
	const bool bHitEnv = pxOutput->m_bHitEnvironment;

	if ((uHitEntity == 0) && !bHitEnv)
	{
		return true;
	}

	if (uHitEntity == xEntity.GetGuid())
	{
		return true;
	}

	// For vents, see if we've hit another vent that's very close to the one we're aiming at.
	if (bIsVertebrate || bHitEnv)
	{
		return false;
	}
	else
	{
		const AVP_ClientEntity_Vent* pxVent = AVP_ClientEntity_Vent::FindEntity(uHitEntity);
		if (!pxVent)
		{
			return false;
		}

		return pxOutput->m_fDistanceToIntersection >= (fDist - dbg_fVentTargetLOSLeeway);
	}
}


void AVP_Alien_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(uALIEN_POUNCE_BLUEPRINT_HASH_MAXDISTANCE, xValue))
		{
			m_fMaxPounceDistance = 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(Asura_Maths::ZeroVector3, xFORWARD, xValue.GetHashIDValue(), NULL, usPFXFlags);
		}

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

	// Initialising some parameters requires another blueprint.
	const Asura_Blueprint* pxActorBlueprint = m_xOwner.GetBlueprint();
	if (pxActorBlueprint)
	{
		Asura_Value xValue;

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

/// End of file. ///
