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

///////////////////////////////////////////////
// #includes
///////////////////////////////////////////////

#include "Core/Asura.h"

// This files header
#include "Actor/Vertebrates/Alien/AVP_Alien_MeleeCounter_System.h"

#include "Blueprint/Asura_Blueprint.h"
#include "Blueprint/Asura_Blueprint_System.h"
#include "Core/Asura_LOS.h"
#include "Entity/PhysicalObject/Moves/Asura_Movement_System.h"
#include "Physics/Asura_Physics_MovementQuery.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/AVP_Messages.h"
#include "Entity/Container/AVP_ServerEntity_Container.h"
#include "Entity/Damage/AVP_EntityDamage_Messages.h"
#include "Entity/PhysicalObject/Actor/AVP_ClientEntity_Actor.h"
#include "Entity/PhysicalObject/Actor/Vertebrates/Alien/AVP_ServerActor_Alien.h"
#include "Entity/PhysicalObject/Actor/Vertebrates/AVP_Vertebrate_Messages.h"
#include "Entity/PhysicalObject/Actor/Vertebrates/Human/AVP_ServerActor_Human.h"
#include "Inventory/AVP_Inventory_HashIDs.h"
#include "Inventory/AVP_Inventory_System.h"
#include "Inventory/Items/AVP_InventoryItem_Weapon.h"
#include "Mesh/AVP_PolygonFlags.h"
#include "Moves/Vertebrates/Alien/AVP_Alien_MovesMelee.h"
#include "Moves/Vertebrates/Alien/AVP_Alien_MovesTail.h"
#include "Moves/Vertebrates/Predator/AVP_Predator_MovesMelee.h"

///////////////////////////////////////////////
// #defines
///////////////////////////////////////////////


const char* AVP_Alien_MeleeCounter_System::s_szStandingLeftSlashAttackBaseParameterName = "StandingClawAttackLeft";
const char* AVP_Alien_MeleeCounter_System::s_szStandingRightSlashAttackBaseParameterName = "StandingClawAttackRight";

DEBUGVAR Asura_Vector_3 dbg_xJumpSlashLeftClawTargetOffset = Asura_Vector_3(1.5f, 0.0f, 0.0f);
DEBUGVAR Asura_Vector_3 dbg_xJumpSlashRightClawTargetOffset = Asura_Vector_3(-1.5f, 0.0f, 0.0f);
DEBUGVAR float          dbg_fNoTargetJumpSlashDistance = 3.2f;
DEBUGVAR Asura_AVector3 dbg_xJumpSlashTestStartOffset = Asura_AVector3( 0.0f, -0.75f, 0.0f );
DEBUGVAR Asura_AVector3 dbg_xJumpSlashLeftClawTestDisplacement = Asura_AVector3( 1.1f, 0.0f, 0.0f );
DEBUGVAR Asura_AVector3 dbg_xJumpSlashRightClawTestDisplacement = Asura_AVector3( -1.1f, 0.0f, 0.0f );

DEBUGVAR float dbg_fTailLashDefaultDamage = 30.0f;
DEBUGVAR float dbg_fTailLashDefaultImpulse = 20.0f;
DEBUGVAR float dbg_fTailStabDefaultDamage = 150.0f;
DEBUGVAR float dbg_fTailStabDefaultImpulse = 0.5f;
DEBUGVAR float dbg_fTailAttackDefaultRange = 4.5f;
DEBUGVAR float dbg_fHeadbiteRagdollImpulse = 1.5f;

DEBUGVAR float dbg_fJumpSlashTimeEstimate = 0.5f;
DEBUGVAR float dbg_fJumpSlashAdditionalTimeEstimate = 0.25f;
DEBUGVAR float dbg_fJumpSlashFrontalDelta = 0.35f;


/////////////////////////////////////////////////////////////////////
//
// Class :	AVP_Alien_MeleeCounter_System
//
//////////////////////////////////////////////////////////////////////

AVP_Alien_MeleeCounter_System::AVP_Alien_MeleeCounter_System(AVP_ServerActor_Alien& xOwner)
: AVP_Vertebrate_MeleeCounter_System(xOwner)
, m_fTailLashDamage(dbg_fTailLashDefaultDamage)
, m_fTailLashImpulse(dbg_fTailLashDefaultImpulse)
, m_fTailStabDamage(dbg_fTailStabDefaultDamage)
, m_fTailStabImpulse(dbg_fTailStabDefaultImpulse)
, m_fTailAttackRange(dbg_fTailAttackDefaultRange)
{
}


void AVP_Alien_MeleeCounter_System::InitialiseFromBlueprint(const Asura_Blueprint& xBlueprint)
{
	PARENT::InitialiseFromBlueprint(xBlueprint);

	Asura_Value	xValue;

	if (xBlueprint.GetParameterValueByHashID(uAVP_BLUEPRINT_PARAM_TAILLASH_DAMAGE, xValue))
	{
		m_fTailLashDamage = xValue.GetFloatValue();
	}
	if (xBlueprint.GetParameterValueByHashID(uAVP_BLUEPRINT_PARAM_TAILLASH_IMPULSE, xValue))
	{
		m_fTailLashImpulse = xValue.GetFloatValue();
	}
	if (xBlueprint.GetParameterValueByHashID(uAVP_BLUEPRINT_PARAM_TAILSTAB_DAMAGE, xValue))
	{
		m_fTailStabDamage = xValue.GetFloatValue();
	}
	if (xBlueprint.GetParameterValueByHashID(uAVP_BLUEPRINT_PARAM_TAILSTAB_IMPULSE, xValue))
	{
		m_fTailStabImpulse = xValue.GetFloatValue();
	}
	if (xBlueprint.GetParameterValueByHashID(uAVP_BLUEPRINT_PARAM_TAILATTACK_RANGE, xValue))
	{
		m_fTailAttackRange = xValue.GetFloatValue();
	}
}


bool AVP_Alien_MeleeCounter_System::StartMeleeMove(u_int uType)
{
	const AVP_ServerActor_Alien& xAlienOwner = static_cast<const AVP_ServerActor_Alien&>(m_xOwner);

	// The focus attacks only work if the target is far enough away, so we may adjust the type here.
	if (((uType == AVP_ALIEN_MELEEATTACK_TYPE_FOCUSATTACK) || (uType == AVP_MELEEATTACK_TYPE_GRAPPLEPOUNCE)) && GetHasAttackTarget() && xAlienOwner.IsStanding())
	{
		// If primary attack has been pressed and the enemy is close, we just want to attack them normally instead.
		// For the time being, we also don't adjust the type if we're crouching, so primary attack does something when focussed and crouched.
		if (uType == AVP_ALIEN_MELEEATTACK_TYPE_FOCUSATTACK)
		{
			uType = AVP_ALIEN_MELEEATTACK_TYPE_PRIMARYATTACK;
		}
		else
		{
			uType = AVP_MELEEATTACK_TYPE_NORMAL;
		}
	}
	else if (((uType == AVP_ALIEN_MELEEATTACK_TYPE_PRIMARYATTACK) || (uType == AVP_MELEEATTACK_TYPE_NORMAL)) && (m_uCurrentTarget != ASURA_GUID_UNREGISTERED) && !IsCurrentTargetInSuckRange() && GetCanUseShortestPounceVariation(m_uCurrentTarget))
	{
		if (uType == AVP_ALIEN_MELEEATTACK_TYPE_PRIMARYATTACK)
		{
			uType = AVP_ALIEN_MELEEATTACK_TYPE_FOCUSATTACK;
		}
		else
		{
			uType = AVP_MELEEATTACK_TYPE_GRAPPLEPOUNCE;
		}
	}

	// Awkwardly, if we want to be sure the character is off the ground we have to see what each of the client and the server think, as only one actually bothers to update it from anything other than ON_FLOOR.
	const AVP_ClientEntity_Actor* pxClient = AVP_ClientEntity_Actor::FindEntity(m_xOwner.GetGuid());
	bool bOffGround = m_xOwner.GetGroundType() == AVP_Actor_PhysicsObjectHandler::OFF_GROUND;
	bOffGround |= pxClient && (pxClient->GetGroundType() == AVP_Actor_PhysicsObjectHandler::OFF_GROUND);
	if (bOffGround)
	{
		// If off the ground, all jumping attacks are disabled.
		if (uType == AVP_ALIEN_MELEEATTACK_TYPE_FOCUSATTACK)
		{
			uType = AVP_ALIEN_MELEEATTACK_TYPE_PRIMARYATTACK;
		}
		else if (uType == AVP_MELEEATTACK_TYPE_GRAPPLEPOUNCE)
		{
			uType = AVP_MELEEATTACK_TYPE_NORMAL;
		}
		else if (uType == AVP_ALIEN_MELEEATTACK_TYPE_FOCUSPOUNCE)
		{
			return false;
		}
	}

	// Grabbing headbites
	if ((uType == AVP_MELEEATTACK_TYPE_NORMAL) && GetHasAttackTarget())
	{
		const AVP_ServerActor_Vertebrate* pxTarget = AVP_ServerActor_Vertebrate::FindEntity(m_uCurrentTarget);
		if (pxTarget)
		{
			switch (pxTarget->GetClassification())
			{
			default:
				// If not one of the following types, just continue as normal.
				break;

			case AVP_ENTITYCLASS_MARINE:
			case AVP_ENTITYCLASS_CIVILIAN:
			case AVP_ENTITYCLASS_PREDATOR:
				// These guys can be picked up.
				uType = AVP_ALIEN_MELEEATTACK_TYPE_PICKUPENEMY;
				break;
			}
		}
	}

	if (uType == AVP_ALIEN_MELEEATTACK_TYPE_JUMPSLASH || uType == AVP_ALIEN_MELEEATTACK_TYPE_AI_JUMPSLASHWALL)
	{
		Asura_Vector_3 xTarget;
		bool bRightClaw = false;

		const AVP_ServerEntity_Actor* pxTarget = AVP_ServerEntity_Actor::FindEntity(m_uCurrentTarget);
		bool bUseEntityTarget = false;
		if (pxTarget)
		{
			bUseEntityTarget = true;
			Asura_Vector_3 xFlatAim = m_xOwner.GetAimDirection();
			xFlatAim.y = 0.0f;


			xTarget = pxTarget->GetPosition() + pxTarget->GetVelocity() * dbg_fJumpSlashTimeEstimate;
			Asura_Vector_3 xDirToTarget = xTarget - m_xOwner.GetPosition() ;
			xDirToTarget.y = 0.0f;
			if (!xDirToTarget.ApproximatelyEquals(Asura_Maths::ZeroVector3))
			{
				xDirToTarget.Normalise();
				if (pxTarget->GetVelocity() * xDirToTarget > 0.0f) // the target move away from us then add a bit more to the estimate in that direction
				{
					xTarget += xDirToTarget * ( pxTarget->GetVelocity() * xDirToTarget ) * dbg_fJumpSlashAdditionalTimeEstimate;
				}
			}
			 
			Asura_Vector_3 xOffset = xTarget - m_xOwner.GetPosition();

			// Check we're not too close
			xOffset.y = 0.0f;
			Asura_Matrix_3x3 xOri;
			if (xOffset.ApproximatelyEquals(Asura_Maths::ZeroVector3))
			{
				Asura_Euler_3 xEuler;
				xEuler.FromMatrix(m_xOwner.GetOrientation());
				xEuler.x = xEuler.z = 0.0f;
				xEuler.ToMatrix(xOri);
				xOffset.x = xOri.e13;
				xOffset.y = xOri.e23;
				xOffset.z = xOri.e33;
			}
			else
			{
				xOffset.Normalise();
				Asura_Maths::OrientationFromDirection(xOri, xOffset);
			}
			if (xFlatAim.ApproximatelyEquals(Asura_Maths::ZeroVector3))
			{
				xFlatAim = xOffset;
			}
			else
			{
				xFlatAim.Normalise();
			}

			// With two flat directions, we can get which "side" one is relative to the other by using cross product and checking whether the resulting perpendicular points up or down.
			const Asura_Vector_3 xCross = xFlatAim ^ xOffset;
			bRightClaw = xCross.y > 0.0f;

			// Test to make sure we can pass the target on this side.
			const Asura_OBB xOBB( 0.15f );
			const Asura_AVector3 xTestDisplacement = ( bRightClaw ? dbg_xJumpSlashRightClawTestDisplacement : dbg_xJumpSlashLeftClawTestDisplacement ) * xOri;
			Asura_Physics_MovementQuery xMovementQuery( Asura_AVector3( xTarget ) + dbg_xJumpSlashTestStartOffset, // Assuming the target is upright.
				xTestDisplacement,
				xOBB,
				Asura_Physics_MovementQuery::ASURA_PMQCP_OBB,
				AsuraPolygonFlag_NoCollisions | AVP_PolygonFlag_IgnoreForAlienCollision | AVP_PolygonFlag_IgnoreForPhysicsObjectCollision | AVP_PolygonFlag_IgnoreForPlayerCollision,
				true, true );
			Asura_Physics_MovementQuery_Results xResults( xMovementQuery );
			if ( !xResults.GetAllowedDisplacement().ApproximatelyEquals( xTestDisplacement ) )
			{
				bRightClaw = !bRightClaw;
			}

			xTarget += (bRightClaw ? dbg_xJumpSlashRightClawTargetOffset : dbg_xJumpSlashLeftClawTargetOffset) * xOri;
			
		}

		if (!bUseEntityTarget)
		{
			// OK, the entity isn't valid, just jump forward by a fixed amount, and pick a side randomly.
			xTarget = m_xOwner.GetPosition();
			const Asura_Vector_3& xAimDir = m_xOwner.GetAimDirection();
			xTarget += xAimDir * dbg_fNoTargetJumpSlashDistance;

			bRightClaw = (Asura_Random::GetInt(0, 1) == 1);
		}

		// Send a special start message here.
		// Don't use GetMeleeMoveHash, as we don't want to bother calculating side again.
		Asura_Hash_ID uMoveHash;
		if ( uType == AVP_ALIEN_MELEEATTACK_TYPE_JUMPSLASH )
		{
			uMoveHash = bRightClaw ?
				AVP_Alien_Move_Melee_JumpSlash_RightClaw_Start::GetStaticHash() :
				AVP_Alien_Move_Melee_JumpSlash_LeftClaw_Start::GetStaticHash();
		}
		else // ( uType == AVP_ALIEN_MELEEATTACK_TYPE_AI_JUMPSLASHWALL )
		{
			uMoveHash = bRightClaw ?
				AVP_Alien_Move_Melee_JumpSlashWall_RightClaw_Start::GetStaticHash() :
				AVP_Alien_Move_Melee_JumpSlashWall_LeftClaw_Start::GetStaticHash();
		}

		m_uCurrentAttackType = uType;
		m_uNumHitEntities = 0;
		m_bCurrentAttackCountered = false;
		m_bCurrentAttackHasHitEnv = false;
		m_uCurrentGrappleBlueprint = ASURA_HASH_ID_UNSET;

		// Set the variation so we know what direction to test for damage in.
		m_uCurrentVariation = bRightClaw ? AVP_ALIEN_JUMPSLASH_VARIATION_RIGHT : AVP_ALIEN_JUMPSLASH_VARIATION_LEFT;

		Asura_Message* pxMessage = Asura_Network::CreateMessage(ASURA_MSG_MOVE_START, sizeof(AVP_Msg_MeleeWithTargetPosition_Move));

		if (pxMessage)
		{
			AVP_Msg_MeleeWithTargetPosition_Move* pxData = static_cast<AVP_Msg_MeleeWithTargetPosition_Move*>(pxMessage->GetData());

			pxData->ClearFlags();

			pxData->SetTargetPosition(xTarget);

			// Always tell the client to use variation 0.
			pxData->SetVariation(0);
			pxData->SetPendingAttackType(uType);
			pxData->SetTargetGuid(m_uCurrentTarget);
			pxData->SetMoveID(uMoveHash);
			pxData->SetIgnoredByOwner(false);

			pxMessage->SetToGuid(m_xOwner.GetGuid());
			pxMessage->SetFromGuid(m_xOwner.GetGuid());

			Asura_Network::Send(pxMessage);
		}

		m_bBlockActive = false;

		return true;
	}

	if (uType == AVP_ALIEN_MELEEATTACK_TYPE_SECONDARYRELEASE)
	{
		Asura_Hash_ID uMoveHash = GetMeleeMoveHash(uType);
		if (uMoveHash == ASURA_HASH_ID_UNSET) { return false; }

		m_uCurrentAttackType = uType;
		m_uNumHitEntities = 0;
		m_bCurrentAttackCountered = false;
		m_bCurrentAttackHasHitEnv = false;
		m_uCurrentGrappleBlueprint = ASURA_HASH_ID_UNSET;

		// This needs a melee with target position message.
		// If we have no specific target, or our target is out of range, calculate a target position now.
		Asura_Vector_3 xOrigin;
		m_xOwner.GetHeadPosition(xOrigin);
		const AVP_ServerActor_Vertebrate* pxTarget = AVP_ServerActor_Vertebrate::FindEntity(m_uCurrentTarget);

		Asura_Vector_3 xTargetOffset(m_fTailAttackRange, m_fTailAttackRange, m_fTailAttackRange); // Definitely greater magnitude than m_fTailAttackRange (unless range is 0).
		if (pxTarget)
		{
			Asura_Vector_3 xTarget;
			pxTarget->GetChestPosition(xTarget);

			xTargetOffset = xTarget - xOrigin;
		}
		else
		{
			const AVP_ServerEntity_PhysicalObject* pxPhysicalTarget = AVP_ServerEntity_PhysicalObject::FindEntity(m_uCurrentTarget);
			if (pxPhysicalTarget)
			{
				Asura_Vector_3 xTarget;
				pxPhysicalTarget->GetMeleeOrigin(xTarget);

				xTargetOffset = xTarget - xOrigin;
			}
			else
			{
				const Asura_ServerEntity_Container* pxContainerTarget = Asura_ServerEntity_Container::FindEntity(m_uCurrentTarget);
				if (pxContainerTarget)
				{
					xTargetOffset = m_xStoredTargetPosition - xOrigin;
				}
			}
		}

		if (xTargetOffset.MagnitudeSquared() >= (m_fTailAttackRange * m_fTailAttackRange))
		{
			// No target or target out of range. Calculate a postion.
			Asura_Vector_3 xOrigin;
			m_xOwner.GetHeadPosition(xOrigin);
			const Asura_Vector_3& xAimDir = m_xOwner.GetAimDirection();

			Asura_LOS_InputData xInput(xOrigin, xAimDir, m_xOwner.GetGuid(), m_fTailAttackRange, true);
			const Asura_LOS_OutputData* pxOutput = DoMeleeLOSTest(&xInput);

			// If we missed everything the distance to intersection matches the initial range.
			m_xStoredTargetPosition = xOrigin + (xAimDir * pxOutput->m_fDistanceToIntersection);
			m_uCurrentTarget = ASURA_GUID_UNREGISTERED;
		}

		Asura_Message* pxMessage = Asura_Network::CreateMessage(ASURA_MSG_MOVE_START, sizeof(AVP_Msg_MeleeWithTargetPosition_Move));

		if (pxMessage)
		{
			m_uCurrentVariation = PickMeleeMoveVariation(uMoveHash);

			AVP_Msg_MeleeWithTargetPosition_Move* pxData = static_cast<AVP_Msg_MeleeWithTargetPosition_Move*>(pxMessage->GetData());

			pxData->ClearFlags();

			pxData->SetTargetPosition(m_xStoredTargetPosition);
			pxData->SetVariation(m_uCurrentVariation);
			pxData->SetPendingAttackType(uType);
			pxData->SetTargetGuid(m_uCurrentTarget);
			pxData->SetMoveID(uMoveHash);
			pxData->SetIgnoredByOwner(false);

			pxMessage->SetToGuid(m_xOwner.GetGuid());
			pxMessage->SetFromGuid(m_xOwner.GetGuid());

			Asura_Network::Send(pxMessage);
		}

		return true;
	}


	if (uType == AVP_ALIEN_MELEEATTACK_TYPE_AI_INJURED || uType == AVP_ALIEN_MELEEATTACK_TYPE_AI_POUNCEGRAPPLE)
	{
		const AVP_ClientEntity_Actor* pxVictim = AVP_ClientEntity_Actor::FindEntity(m_uCurrentTarget);

		if (!pxVictim) 
		{
			return false; 
		}
		if (pxVictim->IsDead())
		{
			return false; 
		}
		if (pxVictim->GetMovementSystem() &&  
			pxVictim->GetMovementSystem()->GetCurrentMove() && 
			pxVictim->GetMovementSystem()->GetCurrentMove()->GetHash() == AVP_Move_Puppet::GetStaticHash())
		{
			return false; 
		}
	}

	if (PARENT::StartMeleeMove(uType))
	{
		if ((uType == AVP_ALIEN_MELEEATTACK_TYPE_PRIMARYATTACK) || (uType == AVP_MELEEATTACK_TYPE_NORMAL))
		{
			// Switch the side.
			SwitchQuickAttackSide();
		}

		return true;
	}
	else
	{
		return false;
	}
}

u_int AVP_Alien_MeleeCounter_System::PickMeleeMoveVariation(Asura_Hash_ID uMoveHash) const
{
	switch (m_uCurrentAttackType)
	{
	case AVP_ALIEN_MELEEATTACK_TYPE_PRIMARYATTACK:
	case AVP_MELEEATTACK_TYPE_NORMAL:
		if (uMoveHash == AVP_Alien_Move_Melee_StandingClawAttack_Start::GetStaticHash())
		{
			if (m_eNextQuickAttackSide == QUICK_ATTACK_LEFT)
			{
				return Asura_Random::GetIntU(0, m_uNumLeftQuickAttackVariations - 1);
			}
			else
			{
				Asura_Assert(m_eNextQuickAttackSide == QUICK_ATTACK_RIGHT, "Invalid quick attack side.");
				return Asura_Random::GetIntU(m_uNumLeftQuickAttackVariations, m_uNumLeftQuickAttackVariations + m_uNumRightQuickAttackVariations - 1);
			}
		}

		return PARENT::PickMeleeMoveVariation(uMoveHash);

	case AVP_ALIEN_MELEEATTACK_TYPE_FOCUSATTACK:
	case AVP_ALIEN_MELEEATTACK_TYPE_FOCUSPOUNCE:
	case AVP_ALIEN_MELEEATTACK_TYPE_NONFOCUSPOUNCE:
	case AVP_MELEEATTACK_TYPE_GRAPPLEPOUNCE:
		return GetPounceVariation();

	default:
		return PARENT::PickMeleeMoveVariation(uMoveHash);
	}
}

void AVP_Alien_MeleeCounter_System::CheckForAndDealMeleeDamage()
{
	if (m_uNumHitEntities >= MAX_HIT_MELEE_ENTITIES) { return; }

	switch (m_uCurrentAttackType)
	{
		case AVP_ALIEN_MELEEATTACK_TYPE_SECONDARYRELEASE:
		{
			// This does a LOS test.
			Asura_Vector_3 xOrigin;
			m_xOwner.GetHeadPosition(xOrigin);

			const AVP_ServerActor_Vertebrate* pxTarget = AVP_ServerActor_Vertebrate::FindEntity(m_uCurrentTarget);

			Asura_Vector_3 xAimDir;
			if (pxTarget)
			{
				Asura_Vector_3 xTarget;
				pxTarget->GetChestPosition(xTarget);

				xAimDir = xTarget - xOrigin;
				xAimDir.Normalise();
			}
			else
			{
				const AVP_ServerEntity_PhysicalObject* pxPhysicalTarget = AVP_ServerEntity_PhysicalObject::FindEntity(m_uCurrentTarget);
				if (pxPhysicalTarget)
				{
					Asura_Vector_3 xTarget;
					pxPhysicalTarget->GetMeleeOrigin(xTarget);

					xAimDir = xTarget - xOrigin;
					xAimDir.Normalise();
				}
				else
				{
					// Regardless of whether we're going to try to hit a container or not, at this point we use the stored target position.
					xAimDir = m_xStoredTargetPosition - xOrigin;
					xAimDir.Normalise();
				}
			}

			Asura_LOS_InputData xInput(xOrigin, xAimDir, m_xOwner.GetGuid(), m_fTailAttackRange, true);
			const Asura_LOS_OutputData* pxOutput = DoMeleeLOSTest(&xInput);

			// Just hit whoever we hit.
			if (pxOutput->m_bHitEnvironment)
			{
				if (!m_bCurrentAttackHasHitEnv)
				{
					m_bCurrentAttackHasHitEnv = true;

					// Send an environmental damage message.
					Asura_Message* pxMessage = Asura_Network::CreateMessage(AVP_MSGID_ENTITYDAMAGE_APPLY_TO_ENVIRONMENT, sizeof(AVP_Message_EntityDamage_ApplyToEnvironment));
					if (pxMessage)
					{

						const Asura_Vector_3 xPointOfImpact(xOrigin + (xAimDir * pxOutput->m_fDistanceToIntersection));

						const AVP_EntityDamage_ApplyToEnvironment xDamage(GetAttackDamageType(), m_xOwner.GetGuid(), xPointOfImpact, xAimDir, pxOutput->m_xNormal, pxOutput->m_uMaterialResponseHashID, pxOutput->m_usPolyFlags, pxOutput->m_uEnvironmentFaceIndex);

						AVP_Message_EntityDamage_ApplyToEnvironment* pxData = static_cast<AVP_Message_EntityDamage_ApplyToEnvironment*>(pxMessage->GetData());

						pxData->SetDamage(xDamage);

						pxMessage->SetFromGuid(m_xOwner.GetGuid());
						pxMessage->SetToGuid(ASURA_GUID_ALL_CLIENTMACHINES);
						Asura_Network::Send(pxMessage);
					}
				}
			}
			else if (pxOutput->m_uHitEntity && pxOutput->m_uHitEntity != ASURA_GUID_UNREGISTERED)
			{
				bool bAlreadyHit = false;
				for (u_int u=0; u<m_uNumHitEntities; ++u)
				{
					bAlreadyHit |= (m_auHitEntities[u] == pxOutput->m_uHitEntity);
				}
				if (!bAlreadyHit)
				{
					Asura_Message* pxMessage = Asura_Network::CreateMessage(AVP_MSGID_ENTITYDAMAGE_REQUEST_VICTIM, sizeof(AVP_Message_EntityDamage_RequestVictim));
					if(pxMessage)
					{
						AVP_Message_EntityDamage_RequestVictim*	pxData = static_cast<AVP_Message_EntityDamage_RequestVictim*>(pxMessage->GetData());
						const AVP_EntityDamage_RequestVictim	xDamage(GetAttackDamageType(),
																		m_xOwner.GetGuid(),
																		uMELEE_MARKER_POINT_HASH,
																		pxOutput->m_uHitEntity,
																		GetAttackDamage(),
																		xOrigin + (xAimDir * pxOutput->m_fDistanceToIntersection),
																		xAimDir,
																		Asura_Maths::ZeroVector3,
																		GetAttackImpulseStrength(),
																		pxOutput->m_iHitBoneID,
																		false,
																		m_xOwner.GetGuid(),
																		pxOutput->m_iHitAttachmentID);
						pxData->SetDamage(xDamage);

						pxMessage->SetFromGuid(m_xOwner.GetGuid());
						pxMessage->SetToGuid(pxOutput->m_uHitEntity);
						Asura_Network::Send(pxMessage);
					}

					m_auHitEntities[m_uNumHitEntities++] = pxOutput->m_uHitEntity;
				}
			}
			break;
		}

		case AVP_ALIEN_MELEEATTACK_TYPE_HEADBITE:
		{
			const AVP_ServerActor_Vertebrate* pxTarget = AVP_ServerActor_Vertebrate::FindEntity(m_uCurrentTarget);

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

					Asura_Vector_3 xPosition;
					if (!pxTarget->GetWorldSpaceMarkerPoint(AVP_Vertebrate_Description::GetMarkerPointHashID(AVP_Vertebrate_Description::MPID_TARGET_2), &xPosition, NULL))
					{
						xPosition = pxTarget->GetPosition();
					}
					Asura_Vector_3 xOwnerPos;
					if (!m_xOwner.GetWorldSpaceMarkerPoint(AVP_Vertebrate_Description::GetMarkerPointHashID(AVP_Vertebrate_Description::MPID_EYES), &xOwnerPos, NULL))
					{
						xOwnerPos = m_xOwner.GetPosition();
					}
					Asura_Vector_3 xDirection = xOwnerPos - xPosition;
					xDirection.Normalise();

					const float fDamage = GetGrappleDamage(); // Should be a fatal amount of damage.
					const float fImpulseStrength = dbg_fHeadbiteRagdollImpulse;

					const Asura_Vector_3 xNormal(Asura_Maths::ZeroVector3); // TODO - I find in somewhat annoying that the best thing to ensure the impulse is in the direction I want is to pass in a normal that isn't.

					{
						const AVP_EntityDamage_RequestVictim	xDamage(uAVP_BLUEPRINT_NAME_DAMAGE_BITE,
																		m_xOwner.GetGuid(),
																		uMELEE_MARKER_POINT_HASH,
																		m_uCurrentTarget,
																		fDamage,
																		xPosition,
																		xDirection,
																		xNormal,
																		fImpulseStrength,
																		pxTarget->GetAnyBoneIDFromElement(AVP_SET_HEAD),
																		false,
																		m_xOwner.GetGuid());

						pxData->SetDamage(xDamage);

						pxMessage->SetFromGuid(m_xOwner.GetGuid());
						pxMessage->SetToGuid(m_uCurrentTarget);
						Asura_Network::Send(pxMessage);
					}
				}

				if (pxTarget->GetClassification() != AVP_ENTITYCLASS_ANDROID)
				{
					m_xOwner.RegenHealthBlocks( 1 );
				}
			}

			break;
		}

		case AVP_ALIEN_MELEEATTACK_TYPE_JUMPSLASH:
		case AVP_ALIEN_MELEEATTACK_TYPE_AI_JUMPSLASHWALL:
		{
			// We need to test slightly further, and off to the appropriate side.
			// TODO - There a reasonable argument for this attack hitting multiple entities.
			if (!IsTargetInDamageRange()) { return; }

			// Standard check to make sure we haven't already hit them.
			for (u_int u=0; u<m_uNumHitEntities; ++u)
			{
				if (m_auHitEntities[u] == m_uCurrentTarget) { return; }
			}

			// See which side of us they are.
			const AVP_ServerEntity_Actor* pxTarget = AVP_ServerEntity_Actor::FindEntity(m_uCurrentTarget);
			if (!pxTarget) { return; }

			const Asura_Matrix_3x3& xOri = m_xOwner.GetOrientation();
			Asura_Vector_3 xFlatForward(xOri.e13, 0.0f, xOri.e33);
			if (xFlatForward.ApproximatelyEquals(Asura_Maths::ZeroVector3)) { return; }

			Asura_Vector_3 xOwnerOrigin;
			m_xOwner.GetMeleeOrigin(xOwnerOrigin);
			Asura_Vector_3 xToTarget;
			pxTarget->GetMeleeOrigin(xToTarget);
			xToTarget -= xOwnerOrigin;
			xToTarget.y = 0.0f;
			if (xToTarget.ApproximatelyEquals(Asura_Maths::ZeroVector3)) { return; }

			xFlatForward.Normalise();
			float fDistanceToTarget = xToTarget.Normalise();

			const Asura_Vector_3 xCross = xFlatForward ^ xToTarget;
			const bool bIsOnRight = xCross.y > 0.0f;
			const bool bShouldBeOnRight = m_uCurrentVariation == AVP_ALIEN_JUMPSLASH_VARIATION_RIGHT;

			xOwnerOrigin.y = 0;

			float fDistanceInFront = fDistanceToTarget * ( xFlatForward ^ xToTarget ).Magnitude();

			const bool bInFront = fDistanceInFront <  dbg_fJumpSlashFrontalDelta;

			if (bIsOnRight != bShouldBeOnRight && !bInFront) { return; }

			ApplyMeleeDamage(m_uCurrentTarget);
			break;
		}

		default:
			PARENT::CheckForAndDealMeleeDamage();
			break;
	}
}

DEBUGVAR float dbg_fTailStabUpperBodyImpulse = 3.0f;
DEBUGVAR float dbg_fTailStabLowerBodyImpulse = 0.2f;
DEBUGVAR float dbg_fTailStabRipDirectionRandomValue = 0.5f;

void AVP_Alien_MeleeCounter_System::ApplyGrappleDamage() const
{
	switch (m_uCurrentAttackType)
	{
	case AVP_ALIEN_MELEEATTACK_TYPE_SECONDARYATTACK:
		{
			// From the tail stab, the alien rips the enemy in two.
			const AVP_ServerEntity_PhysicalObject* pxTarget = AVP_ServerEntity_PhysicalObject::FindEntity(m_uCurrentTarget);

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

					Asura_Vector_3 xPosition;
					if (!pxTarget->GetWorldSpaceMarkerPoint(AVP_Vertebrate_Description::GetMarkerPointHashID(AVP_Vertebrate_Description::MPID_TARGET_1), &xPosition, NULL))
					{
						xPosition = pxTarget->GetPosition();
					}

					const Asura_Matrix_3x3& xOri = pxTarget->GetOrientation();
					Asura_Vector_3 xRightDirection(xOri.e11, xOri.e21, xOri.e31);
					// Add a little randomness to the direction so it doesn't look exactly the same every time.
					xRightDirection.x += Asura_Random::GetFloat(-dbg_fTailStabRipDirectionRandomValue, dbg_fTailStabRipDirectionRandomValue);
					xRightDirection.y += Asura_Random::GetFloat(-dbg_fTailStabRipDirectionRandomValue, dbg_fTailStabRipDirectionRandomValue);
					xRightDirection.z += Asura_Random::GetFloat(-dbg_fTailStabRipDirectionRandomValue, dbg_fTailStabRipDirectionRandomValue);
					xRightDirection.Normalise();

					pxData->SetElement(AVP_SET_BODY);
					pxData->SetDamagePosition(xPosition);
					pxData->SetRagdollDirection(xRightDirection);
					pxData->SetRagdollImpulse(dbg_fTailStabUpperBodyImpulse);
					pxData->SetGibDirection(-xRightDirection);
					pxData->SetGibImpulse(dbg_fTailStabLowerBodyImpulse);
					pxData->SetDamage(-1.0f);
					pxData->SetFromGuid(m_xOwner.GetGuid()); 

					pxMessage->SetFromGuid(m_xOwner.GetGuid());
					pxMessage->SetToGuid(m_uCurrentTarget);

					Asura_Network::Send(pxMessage);
				}
			}
		}
		break;

	case AVP_ALIEN_MELEEATTACK_TYPE_PICKUPENEMY_HEADBITE:
	case AVP_MELEEATTACK_TYPE_GRAPPLEPOUNCE:
		{
			// As the name implies, that head is getting bitten.
			const AVP_ServerActor_Vertebrate* pxTarget = AVP_ServerActor_Vertebrate::FindEntity(m_uCurrentTarget);

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

					Asura_Vector_3 xPosition;
					if (!pxTarget->GetWorldSpaceMarkerPoint(AVP_Vertebrate_Description::GetMarkerPointHashID(AVP_Vertebrate_Description::MPID_TARGET_2), &xPosition, NULL))
					{
						xPosition = pxTarget->GetPosition();
					}
					Asura_Vector_3 xOwnerPos;
					if (!m_xOwner.GetWorldSpaceMarkerPoint(AVP_Vertebrate_Description::GetMarkerPointHashID(AVP_Vertebrate_Description::MPID_EYES), &xOwnerPos, NULL))
					{
						xOwnerPos = m_xOwner.GetPosition();
					}
					Asura_Vector_3 xDirection = xOwnerPos - xPosition;
					xDirection.Normalise();

					const float fDamage = GetGrappleDamage();
					const float fImpulseStrength = 0.0f;

					const Asura_Vector_3 xNormal(Asura_Maths::ZeroVector3); // TODO - I find in somewhat annoying that the best thing to ensure the impulse is in the direction I want is to pass in a normal that isn't.

					{
						const AVP_EntityDamage_RequestVictim	xDamage(uAVP_BLUEPRINT_NAME_DAMAGE_BITE,
																		m_xOwner.GetGuid(),
																		uMELEE_MARKER_POINT_HASH,
																		m_uCurrentTarget,
																		fDamage,
																		xPosition,
																		xDirection,
																		xNormal,
																		fImpulseStrength,
																		pxTarget->GetAnyBoneIDFromElement(AVP_SET_HEAD),
																		false,
																		m_xOwner.GetGuid());

						pxData->SetDamage(xDamage);

						pxMessage->SetFromGuid(m_xOwner.GetGuid());
						pxMessage->SetToGuid(m_uCurrentTarget);
						Asura_Network::Send(pxMessage);
					}
				}
			}

			if (pxTarget->GetClassification() != AVP_ENTITYCLASS_ANDROID)
			{
				m_xOwner.RegenHealthBlocks( 2 );
			}
		}
		break;

	default:
		PARENT::ApplyGrappleDamage();
		break;
	}
}

DEBUGVAR float dbg_fUprightAlienDotProductLimit = 0.866f;

Asura_Hash_ID AVP_Alien_MeleeCounter_System::GetMeleeMoveHash(u_int uType) const
{
	switch (uType)
	{
	case AVP_ALIEN_MELEEATTACK_TYPE_PRIMARYATTACK:
	case AVP_MELEEATTACK_TYPE_NORMAL: // TODO - Probably wants to be its own attack.
		{
			// Check if our entity is standing, as our move changes based on that.
			const AVP_ServerActor_Alien& xAlien = static_cast<const AVP_ServerActor_Alien&>(m_xOwner);

			bool bStandingOrUprightAndClose = xAlien.IsStanding();
			if (!bStandingOrUprightAndClose && (m_uCurrentTarget != ASURA_GUID_UNREGISTERED) && IsCurrentTargetInSuckRange())
			{
				// As we're close enough to an enemy, see if we're almost upright anyway
				const Asura_Matrix_3x3& xOri = xAlien.GetOrientation();
				const Asura_Vector_3 xDownVec(xOri.e12, xOri.e22, xOri.e32);
				const float fDownDP = xDownVec * Asura_Vector_3(0.0f, 1.0f, 0.0f);
				bStandingOrUprightAndClose = fDownDP > dbg_fUprightAlienDotProductLimit;
			}

			return bStandingOrUprightAndClose ? AVP_Alien_Move_Melee_StandingClawAttack_Start::GetStaticHash() : AVP_Alien_Move_Melee_CrouchingClawAttack_Start::GetStaticHash();
		}

	case AVP_ALIEN_MELEEATTACK_TYPE_SECONDARYATTACK:
		return AVP_Alien_Move_Melee_TailCharge_Start::GetStaticHash();

	case AVP_ALIEN_MELEEATTACK_TYPE_SECONDARYRELEASE:
		// This has a special power-up attack if we're in the middle of the SECONDARYATTACK state.
		if (m_uCurrentAttackType == AVP_ALIEN_MELEEATTACK_TYPE_SECONDARYATTACK)
		{
			// We need to find out if we've finished charging up. That's defined by the current move being the charge loop.
			const AVP_ClientEntity_Actor* pxClient = AVP_ClientEntity_Actor::FindEntity(m_xOwner.GetGuid());
			if (!pxClient) { return ASURA_HASH_ID_UNSET; }
			if (!pxClient->GetMovementSystem()) { return ASURA_HASH_ID_UNSET; }
			if (!pxClient->GetMovementSystem()->GetCurrentMove()) { return ASURA_HASH_ID_UNSET; }

			const Asura_Hash_ID uMoveHash = pxClient->GetMovementSystem()->GetCurrentMove()->GetHash();
			if (uMoveHash == AVP_Alien_Move_Melee_TailCharge_Loop::GetStaticHash())
			{
				return AVP_Alien_Move_Melee_TailStab_Start::GetStaticHash();
			}
			else
			{
				return AVP_Alien_Move_Melee_TailLash_Start::GetStaticHash();
			}
		}
		// Otherwise, it'll always be the lash attack.
		else
		{
			return AVP_Alien_Move_Melee_TailLash_Start::GetStaticHash();
		}

	case AVP_ALIEN_MELEEATTACK_TYPE_SECONDARYCANCEL:
		return (m_uCurrentAttackType == AVP_ALIEN_MELEEATTACK_TYPE_SECONDARYATTACK) ? AVP_Alien_Move_Melee_TailCharge_End::GetStaticHash() : ASURA_HASH_ID_UNSET;

	case AVP_ALIEN_MELEEATTACK_TYPE_FOCUSATTACK:
	case AVP_ALIEN_MELEEATTACK_TYPE_FOCUSPOUNCE:
	case AVP_ALIEN_MELEEATTACK_TYPE_NONFOCUSPOUNCE:
	case AVP_MELEEATTACK_TYPE_GRAPPLEPOUNCE:
		return AVP_Move_Melee_Pounce_Attack_Start::GetStaticHash();

	case AVP_ALIEN_MELEEATTACK_TYPE_HEADBITE:
		return AVP_Alien_Move_Melee_Headbite::GetStaticHash();

	case AVP_ALIEN_MELEEATTACK_TYPE_AI_CRAWLTOSTANDSLASH_R:
		return AVP_Alien_Move_Melee_CrawlToStandAttackR_Start::GetStaticHash();

	case AVP_ALIEN_MELEEATTACK_TYPE_AI_CRAWLTOSTANDSLASH_L:
		return AVP_Alien_Move_Melee_CrawlToStandAttackL_Start::GetStaticHash();

	case AVP_ALIEN_MELEEATTACK_TYPE_AI_CRAWLTOSTANDSLASH_SHORT_R:
		return AVP_Alien_Move_Melee_CrawlToStandAttackShortR_Start::GetStaticHash();

	case AVP_ALIEN_MELEEATTACK_TYPE_AI_CRAWLTOSTANDSLASH_SHORT_L:
		return AVP_Alien_Move_Melee_CrawlToStandAttackShortL_Start::GetStaticHash();

	case AVP_ALIEN_MELEEATTACK_TYPE_AI_STANDSLASHLOOP_L:
		return AVP_Alien_Move_Melee_StandAttackLoopL_Start::GetStaticHash();

	case AVP_ALIEN_MELEEATTACK_TYPE_AI_WALKSLASHLOOP_L:
		return AVP_Alien_Move_Melee_WalkAttackLoopL_Start::GetStaticHash();

	case AVP_ALIEN_MELEEATTACK_TYPE_AI_WALKSLASHLOOP_R:
		return AVP_Alien_Move_Melee_WalkAttackLoopR_Start::GetStaticHash();

	case AVP_ALIEN_MELEEATTACK_TYPE_AI_STANDSLASHLOOP_R:
		return AVP_Alien_Move_Melee_StandAttackLoopR_Start::GetStaticHash();

	case AVP_ALIEN_MELEEATTACK_TYPE_PICKUPENEMY:
	case AVP_ALIEN_MELEEATTACK_TYPE_PICKUPENEMY_HEADBITE:
		return AVP_Alien_Move_Melee_PickupHuman_Start::GetStaticHash();

	case AVP_ALIEN_MELEEATTACK_TYPE_AI_INJURED:
		return AVP_Move_Melee_Grapple_Start::GetStaticHash(); // Everyone can use the grapple move

	case AVP_ALIEN_MELEEATTACK_TYPE_AI_POUNCEGRAPPLE:
		return AVP_Alien_Move_Melee_JumpToGrappleHuman_Start::GetStaticHash();
	default:
		return PARENT::GetMeleeMoveHash(uType);
	}
}

Asura_Hash_ID AVP_Alien_MeleeCounter_System::GetMeleeMoveGrappleSetBlueprintParam(u_int uType) const
{
	switch (uType)
	{
	case AVP_ALIEN_MELEEATTACK_TYPE_AI_INJURED:
		return uAVP_BLUEPRINT_PARAM_GRAPPLESET_ALIEN_INJURED;

	default:
		return PARENT::GetMeleeMoveGrappleSetBlueprintParam(uType);
	}
}

bool AVP_Alien_MeleeCounter_System::GetCanBlock() const
{
	// The alien can't block when crouched.
	const AVP_ServerActor_Alien& xAlienOwner = static_cast<const AVP_ServerActor_Alien&>(m_xOwner);
	if (!xAlienOwner.IsStanding()) { return false; }

	// However, it has no weapons, so don't do the parent check.
	return !IsBlockActive();
}

DEBUGVAR float dbg_fPredatorTailstabCounterMinCosAngle = 0.95f;

bool AVP_Alien_MeleeCounter_System::GetCanCounter_PredatorCounter(const AVP_ServerEntity_Actor& xEnemy, const u_int uQueuedCounterType, Asura_Hash_ID& uMoveHash, u_int& uNewAttackType, bool& bMakeEnemyIntoPuppet, Asura_Hash_ID& uGrappleSetBlueprintParam) const
{
	const AVP_Inventory_System* pxInventory = xEnemy.GetInventorySystem();
	if (!pxInventory)
	{
		uMoveHash = ASURA_HASH_ID_UNSET;
		return false;
	}

	// We can only do special counters when the wristblades are selected.
	const AVP_InventoryItem_Weapon* pxWeapon = static_cast<const AVP_InventoryItem_Weapon*>(pxInventory->GetCurrentItem(uAVP_INVENTORY_GROUPHASH_WEAPON));
	if (!pxWeapon)
	{
		uMoveHash = ASURA_HASH_ID_UNSET;
		return false;
	}

	if (pxWeapon->GetItemType() != AVP_INVENTORYITEMTYPE_WEAPON_MELEE)
	{
		// The only melee weapon is the wristblades, so if we're not wielding them we only have our default counters.
		return PARENT::GetCanCounter_PredatorCounter(xEnemy, uQueuedCounterType, uMoveHash, uNewAttackType, bMakeEnemyIntoPuppet, uGrappleSetBlueprintParam);
	}

	switch (m_uCurrentAttackType)
	{
	default:
		return PARENT::GetCanCounter_PredatorCounter(xEnemy, uQueuedCounterType, uMoveHash, uNewAttackType, bMakeEnemyIntoPuppet, uGrappleSetBlueprintParam);

	case AVP_ALIEN_MELEEATTACK_TYPE_PRIMARYATTACK:
	case AVP_MELEEATTACK_TYPE_NORMAL:
		// Special claw attack counter.
		{
			// Only use it if we're close enough though.
			const AVP_ServerActor_Vertebrate& xVertebrateEnemy = static_cast<const AVP_ServerActor_Vertebrate&>(xEnemy);

			if (!xVertebrateEnemy.GetMeleeSystem() || !xVertebrateEnemy.GetMeleeSystem()->GetHasAttackTarget())
			{
				return PARENT::GetCanCounter_PredatorCounter(xEnemy, uQueuedCounterType, uMoveHash, uNewAttackType, bMakeEnemyIntoPuppet, uGrappleSetBlueprintParam);
			}

			uMoveHash = AVP_Move_Melee_Grapple_Start::GetStaticHash();
			uNewAttackType = AVP_PREDATOR_MELEEATTACK_TYPE_COUNTERGRAPPLE_MINOR; // Needed so the Predator's melee system knows that the damage isn't fatal.
			bMakeEnemyIntoPuppet = true;
			uGrappleSetBlueprintParam = (m_uCurrentVariation < m_uNumLeftQuickAttackVariations) ? uAVP_BLUEPRINT_PARAM_GRAPPLESET_PREDATOR_COUNTER_LEFTCLAW : uAVP_BLUEPRINT_PARAM_GRAPPLESET_PREDATOR_COUNTER_RIGHTCLAW;
			return true;
		}

	case AVP_ALIEN_MELEEATTACK_TYPE_SECONDARYATTACK:
		// Special tail stab counter.
		{
			// Only use it if we're close enough and they're roughly in front.
			Asura_Vector_3 xToEnemy = xEnemy.GetPosition() - m_xOwner.GetPosition();
			const float fDist = xToEnemy.Normalise();
			if (fDist > m_fTailAttackRange) { return false; }

			const Asura_Matrix_3x3& xOri = m_xOwner.GetOrientation();
			const Asura_Vector_3 xForwardVector(xOri.e13, xOri.e23, xOri.e33);

			if ((xToEnemy * xForwardVector) < dbg_fPredatorTailstabCounterMinCosAngle) { return false; }

			uMoveHash = AVP_Predator_Move_Melee_CounterAlienStandingTailStab_Start::GetStaticHash();
			uNewAttackType = AVP_PREDATOR_MELEEATTACK_TYPE_COUNTERGRAPPLE_FATAL; // Needed so the Predator's melee system knows this counter requires input.
			bMakeEnemyIntoPuppet = true;
			return true;
		}

	case AVP_ALIEN_MELEEATTACK_TYPE_FOCUSATTACK:
	case AVP_ALIEN_MELEEATTACK_TYPE_FOCUSPOUNCE:
	case AVP_ALIEN_MELEEATTACK_TYPE_NONFOCUSPOUNCE:
	case AVP_MELEEATTACK_TYPE_GRAPPLEPOUNCE:
		uMoveHash = AVP_Predator_Move_Melee_CounterAlienPounceAttack::GetStaticHash();
		uNewAttackType = AVP_MELEEATTACK_TYPE_COUNTER;
		bMakeEnemyIntoPuppet = true;
		return true;

	case AVP_ALIEN_MELEEATTACK_TYPE_JUMPSLASH:
	case AVP_ALIEN_MELEEATTACK_TYPE_AI_JUMPSLASHWALL:
		if ((uQueuedCounterType == AVP_PREDATOR_MELEEATTACK_TYPE_PRIMARYRELEASE) || (uQueuedCounterType == AVP_PREDATOR_MELEEATTACK_TYPE_PRIMARYSLASH))
		{
			// AJG - 25/06/08 - For SPS, we'll be using the throw counter with quick attacks, and randomising the limb removal for the power attack. Leaving the old code here for now though, as I suspect we'll return to it after SPS.
			//uMoveHash = (m_uCurrentVariation == AVP_ALIEN_JUMPSLASH_VARIATION_RIGHT) ? AVP_Predator_Move_Melee_CounterAlienJumpAttack_Hit_Right::GetStaticHash() : AVP_Predator_Move_Melee_CounterAlienJumpAttack_Hit_Left::GetStaticHash();
			//// In order to calculate the new attack type, we need to know if the predator has charged up the attack. At the moment, the best way to do that is to check the predator's current move.
			//uNewAttackType = AVP_PREDATOR_MELEEATTACK_TYPE_COUNTERHIT_REMOVELIMB;
			//const AVP_ClientEntity_Actor* pxClientEnemy = AVP_ClientEntity_Actor::FindEntity(xEnemy.GetGuid());
			//if (pxClientEnemy)
			//{
			//	const Asura_Movement_System* pxMovementSystem = pxClientEnemy->GetMovementSystem();
			//	if (pxMovementSystem)
			//	{
			//		const Asura_Move* pxMove = pxMovementSystem->GetCurrentMove();
			//		if (pxMove && (pxMove->GetHash() == AVP_Predator_Move_MeleeCharge_Loop::GetStaticHash()))
			//		{
			//			uNewAttackType = AVP_PREDATOR_MELEEATTACK_TYPE_COUNTERHIT_FATAL;
			//		}
			//	}
			//}

			// In order to calculate the new attack type, we need to know if the predator has charged up the attack. At the moment, the best way to do that is to check the predator's current move.
			uNewAttackType = AVP_PREDATOR_MELEEATTACK_TYPE_COUNTERTHROW;
			if (m_xOwner.IsDestructible()) // Never do the fatal counter on an indestructible entity.
			{
				const AVP_ClientEntity_Actor* pxClientEnemy = AVP_ClientEntity_Actor::FindEntity(xEnemy.GetGuid());
				if (pxClientEnemy)
				{
					const Asura_Movement_System* pxMovementSystem = pxClientEnemy->GetMovementSystem();
					if (pxMovementSystem)
					{
						const Asura_Move* pxMove = pxMovementSystem->GetCurrentMove();
						if (pxMove && (pxMove->GetHash() == AVP_Predator_Move_MeleeCharge_Loop::GetStaticHash()))
						{
							uNewAttackType = AVP_PREDATOR_MELEEATTACK_TYPE_COUNTERHIT_FATAL;
						}
					}
				}
			}

			if (uNewAttackType == AVP_PREDATOR_MELEEATTACK_TYPE_COUNTERHIT_FATAL)
			{
				uMoveHash = (m_uCurrentVariation == AVP_ALIEN_JUMPSLASH_VARIATION_RIGHT) ? AVP_Predator_Move_Melee_CounterAlienJumpAttack_Hit_Right::GetStaticHash() : AVP_Predator_Move_Melee_CounterAlienJumpAttack_Hit_Left::GetStaticHash();
			}
			else
			{
				uMoveHash = (m_uCurrentVariation == AVP_ALIEN_JUMPSLASH_VARIATION_RIGHT) ? AVP_Predator_Move_Melee_CounterAlienJumpAttack_Throw_Right::GetStaticHash() : AVP_Predator_Move_Melee_CounterAlienJumpAttack_Throw_Left::GetStaticHash();
			}
		}
		else
		{
			uMoveHash = (m_uCurrentVariation == AVP_ALIEN_JUMPSLASH_VARIATION_RIGHT) ? AVP_Predator_Move_Melee_CounterAlienJumpAttack_Throw_Right::GetStaticHash() : AVP_Predator_Move_Melee_CounterAlienJumpAttack_Throw_Left::GetStaticHash();
			uNewAttackType = AVP_PREDATOR_MELEEATTACK_TYPE_COUNTERTHROW;
		}
		return true;

	case AVP_ALIEN_MELEEATTACK_TYPE_AI_CRAWLTOSTANDSLASH_L:
		case AVP_ALIEN_MELEEATTACK_TYPE_AI_CRAWLTOSTANDSLASH_SHORT_L:
	case AVP_ALIEN_MELEEATTACK_TYPE_AI_STANDSLASHLOOP_L:
	case AVP_ALIEN_MELEEATTACK_TYPE_AI_WALKSLASHLOOP_L:
		{
			// Only use it if we're close enough.
			const AVP_ServerActor_Vertebrate& xVertebrateEnemy = static_cast<const AVP_ServerActor_Vertebrate&>(xEnemy);

			if (!xVertebrateEnemy.GetMeleeSystem() || !xVertebrateEnemy.GetMeleeSystem()->GetHasAttackTarget())
			{
				return PARENT::GetCanCounter_PredatorCounter(xEnemy, uQueuedCounterType, uMoveHash, uNewAttackType, bMakeEnemyIntoPuppet, uGrappleSetBlueprintParam);
			}

			uMoveHash = AVP_Move_Melee_Grapple_Start::GetStaticHash();
			uNewAttackType = AVP_PREDATOR_MELEEATTACK_TYPE_COUNTERGRAPPLE_MINOR; // Needed so the Predator's melee system knows that the damage isn't fatal.
			bMakeEnemyIntoPuppet = true;
			uGrappleSetBlueprintParam = uAVP_BLUEPRINT_PARAM_GRAPPLESET_PREDATOR_COUNTER_LEFTCLAW;
			return true;
		}

	case AVP_ALIEN_MELEEATTACK_TYPE_AI_CRAWLTOSTANDSLASH_R:
	case AVP_ALIEN_MELEEATTACK_TYPE_AI_CRAWLTOSTANDSLASH_SHORT_R:
	case AVP_ALIEN_MELEEATTACK_TYPE_AI_STANDSLASHLOOP_R:
	case AVP_ALIEN_MELEEATTACK_TYPE_AI_WALKSLASHLOOP_R:
		{
			// Only use it if we're close enough.
			const AVP_ServerActor_Vertebrate& xVertebrateEnemy = static_cast<const AVP_ServerActor_Vertebrate&>(xEnemy);

			if (!xVertebrateEnemy.GetMeleeSystem() || !xVertebrateEnemy.GetMeleeSystem()->GetHasAttackTarget())
			{
				return PARENT::GetCanCounter_PredatorCounter(xEnemy, uQueuedCounterType, uMoveHash, uNewAttackType, bMakeEnemyIntoPuppet, uGrappleSetBlueprintParam);
			}

			uMoveHash = AVP_Move_Melee_Grapple_Start::GetStaticHash();
			uNewAttackType = AVP_PREDATOR_MELEEATTACK_TYPE_COUNTERGRAPPLE_MINOR; // Needed so the Predator's melee system knows that the damage isn't fatal.
			bMakeEnemyIntoPuppet = true;
			uGrappleSetBlueprintParam = uAVP_BLUEPRINT_PARAM_GRAPPLESET_PREDATOR_COUNTER_RIGHTCLAW;
			return true;
		}
	}
}

DEBUGVAR float dbg_fLongReachAttackMaxCounterDistance = 10.0f;

float AVP_Alien_MeleeCounter_System::GetCurrentMaxCounterDistance() const
{
	switch (m_uCurrentAttackType)
	{
	case AVP_ALIEN_MELEEATTACK_TYPE_FOCUSATTACK:
	case AVP_ALIEN_MELEEATTACK_TYPE_FOCUSPOUNCE:
	case AVP_ALIEN_MELEEATTACK_TYPE_NONFOCUSPOUNCE:
	case AVP_ALIEN_MELEEATTACK_TYPE_SECONDARYRELEASE:
	case AVP_MELEEATTACK_TYPE_GRAPPLEPOUNCE:
		return dbg_fLongReachAttackMaxCounterDistance;

	default:
		return PARENT::GetCurrentMaxCounterDistance();
	}
}

float AVP_Alien_MeleeCounter_System::GetAttackDamage() const
{
	switch (m_uCurrentAttackType)
	{
	case AVP_ALIEN_MELEEATTACK_TYPE_FOCUSATTACK:
	case AVP_ALIEN_MELEEATTACK_TYPE_FOCUSPOUNCE:
	case AVP_ALIEN_MELEEATTACK_TYPE_NONFOCUSPOUNCE:
		return m_pxPounceAttacks[m_uCurrentVariation].m_fDamage;

	case AVP_ALIEN_MELEEATTACK_TYPE_PICKUPENEMY:
	case AVP_ALIEN_MELEEATTACK_TYPE_PICKUPENEMY_HEADBITE:
	case AVP_MELEEATTACK_TYPE_GRAPPLEPOUNCE:
	case AVP_ALIEN_MELEEATTACK_TYPE_AI_POUNCEGRAPPLE:
		return 0.0f;

	case AVP_ALIEN_MELEEATTACK_TYPE_SECONDARYRELEASE:
		// This depends on what move we've ended up with.
		{
			const AVP_ClientEntity_Actor* pxClient = AVP_ClientEntity_Actor::FindEntity(m_xOwner.GetGuid());
			if (!pxClient) { return m_fTailLashDamage; }
			if (!pxClient->GetMovementSystem()) { return m_fTailLashDamage; }
			if (!pxClient->GetMovementSystem()->GetCurrentMove()) { return m_fTailLashDamage; }

			const Asura_Hash_ID uMoveHash = pxClient->GetMovementSystem()->GetCurrentMove()->GetHash();
			if (uMoveHash == AVP_Alien_Move_Melee_TailStab_Start::GetStaticHash())
			{
				return m_fTailStabDamage;
			}
			else
			{
				return m_fTailLashDamage;
			}
		}

	case AVP_ALIEN_MELEEATTACK_TYPE_JUMPSLASH:
		return m_fModerateDamageAmount;

	default:
		return PARENT::GetAttackDamage();
	}
}

Asura_Hash_ID AVP_Alien_MeleeCounter_System::GetAttackDamageType() const
{
	switch (m_uCurrentAttackType)
	{
	case AVP_ALIEN_MELEEATTACK_TYPE_SECONDARYRELEASE:
		return uAVP_BLUEPRINT_NAME_DAMAGE_TAIL;

	case AVP_ALIEN_MELEEATTACK_TYPE_HEADBITE:
		return uAVP_BLUEPRINT_NAME_DAMAGE_BITE;

	default:
		return uAVP_BLUEPRINT_NAME_DAMAGE_CLAW;
	}
}

float AVP_Alien_MeleeCounter_System::GetAttackImpulseStrength() const
{
	switch (m_uCurrentAttackType)
	{
	case AVP_ALIEN_MELEEATTACK_TYPE_FOCUSATTACK:
	case AVP_ALIEN_MELEEATTACK_TYPE_FOCUSPOUNCE:
	case AVP_ALIEN_MELEEATTACK_TYPE_NONFOCUSPOUNCE:
		return m_pxPounceAttacks[m_uCurrentVariation].m_fAttackImpulse;

	case AVP_ALIEN_MELEEATTACK_TYPE_SECONDARYRELEASE:
		// This depends on what move we've ended up with.
		{
			const AVP_ClientEntity_Actor* pxClient = AVP_ClientEntity_Actor::FindEntity(m_xOwner.GetGuid());
			if (!pxClient) { return m_fTailLashImpulse; }
			if (!pxClient->GetMovementSystem()) { return m_fTailLashImpulse; }
			if (!pxClient->GetMovementSystem()->GetCurrentMove()) { return m_fTailLashImpulse; }

			const Asura_Hash_ID uMoveHash = pxClient->GetMovementSystem()->GetCurrentMove()->GetHash();
			if (uMoveHash == AVP_Alien_Move_Melee_TailStab_Start::GetStaticHash())
			{
				return m_fTailStabImpulse;
			}
			else
			{
				return m_fTailLashImpulse;
			}
		}

	case AVP_ALIEN_MELEEATTACK_TYPE_PICKUPENEMY:
	case AVP_ALIEN_MELEEATTACK_TYPE_PICKUPENEMY_HEADBITE:
	case AVP_MELEEATTACK_TYPE_GRAPPLEPOUNCE:
	case AVP_ALIEN_MELEEATTACK_TYPE_AI_POUNCEGRAPPLE:
		return 0.0f;

	default:
		return PARENT::GetAttackImpulseStrength();
	}
}

static const Asura_Matrix_3x3 xSMALL_ROTATION_LEFT(0.866f, 0.0f, 0.500f, 0.0f, 1.0f, 0.0f, -0.500f, 0.0f, 0.866f);
static const Asura_Matrix_3x3 xSMALL_ROTATION_RIGHT(0.866f, 0.0f, -0.500f, 0.0f, 1.0f, 0.0f, 0.500f, 0.0f, 0.866f);

void AVP_Alien_MeleeCounter_System::GetAttackDirection(const AVP_ServerEntity_PhysicalObject* pxTarget, Asura_Vector_3& xDirection) const
{
	PARENT::GetAttackDirection(pxTarget, xDirection);

	// With left and right attacks, fudge the direction a bit.
	// However, since this comes from the variation for the alien, the current implementation is a little hacky and needs reworking once we have a better way of working out the direction of the attack.
	switch (m_uCurrentAttackType)
	{
	default:
		break;

	case AVP_ALIEN_MELEEATTACK_TYPE_PRIMARYATTACK:
	case AVP_MELEEATTACK_TYPE_NORMAL:
		{
			if (m_uCurrentVariation < m_uNumLeftQuickAttackVariations)
			{
				xDirection = xDirection * xSMALL_ROTATION_LEFT;
			}
			else
			{
				xDirection = xDirection * xSMALL_ROTATION_RIGHT;
			}
		}
		break;
	}
}

int AVP_Alien_MeleeCounter_System::GetAttackBoneHit(Asura_Guid uDamagedGuid) const
{
	switch (m_uCurrentAttackType)
	{
	case AVP_ALIEN_MELEEATTACK_TYPE_HEADBITE:
		{
			const AVP_ServerActor_Vertebrate* pxTarget = AVP_ServerActor_Vertebrate::FindEntity(uDamagedGuid);

			if (pxTarget)
			{
				return pxTarget->GetAnyBoneIDFromElement(AVP_SET_HEAD);
			}

			return PARENT::GetAttackBoneHit(uDamagedGuid);
		}

	default:
		return PARENT::GetAttackBoneHit(uDamagedGuid);
	}
}

bool AVP_Alien_MeleeCounter_System::IsAttackUnblockable() const
{
	switch (m_uCurrentAttackType)
	{
	case AVP_ALIEN_MELEEATTACK_TYPE_SECONDARYRELEASE:
	case AVP_ALIEN_MELEEATTACK_TYPE_FOCUSATTACK:
	case AVP_ALIEN_MELEEATTACK_TYPE_FOCUSPOUNCE:
	case AVP_ALIEN_MELEEATTACK_TYPE_NONFOCUSPOUNCE:
	case AVP_MELEEATTACK_TYPE_GRAPPLEPOUNCE:
	case AVP_ALIEN_MELEEATTACK_TYPE_PICKUPENEMY:
	case AVP_ALIEN_MELEEATTACK_TYPE_PICKUPENEMY_HEADBITE:
	case AVP_ALIEN_MELEEATTACK_TYPE_AI_INJURED:
	case AVP_ALIEN_MELEEATTACK_TYPE_AI_POUNCEGRAPPLE:
		return true;

	default:
		return PARENT::IsAttackUnblockable();
	}
}

bool AVP_Alien_MeleeCounter_System::GetAttackOnlyHitsCurrentTarget() const
{
	switch (m_uCurrentAttackType)
	{
	case AVP_ALIEN_MELEEATTACK_TYPE_PRIMARYATTACK:
	case AVP_ALIEN_MELEEATTACK_TYPE_SECONDARYRELEASE:
		return false;

	default:
		return PARENT::IsAttackUnblockable();
	}
}

Asura_Hash_ID AVP_Alien_MeleeCounter_System::GetAttackGrappleSetBlueprintParamID() const
{
	switch (m_uCurrentAttackType)
	{
	case AVP_ALIEN_MELEEATTACK_TYPE_AI_POUNCEGRAPPLE:
		return uAVP_BLUEPRINT_PARAM_GRAPPLESET_ALIEN_AIPOUNCE;

	default:
		return PARENT::GetAttackGrappleSetBlueprintParamID();
	}
}

bool AVP_Alien_MeleeCounter_System::GetSendIncomingMeleeMessage(u_int uAttackType) const
{
	switch (uAttackType)
	{
	case AVP_ALIEN_MELEEATTACK_TYPE_SECONDARYATTACK:
	case AVP_ALIEN_MELEEATTACK_TYPE_SECONDARYCANCEL:
		return false;

	default:
		return PARENT::GetSendIncomingMeleeMessage(uAttackType);
	}
}

void AVP_Alien_MeleeCounter_System::SendMeleeAttackHitMessage()
{
	if (m_uCurrentAttackType == AVP_ALIEN_MELEEATTACK_TYPE_JUMPSLASH)
	{
		// We need to always send variation 0 here, so send the message ourselves.
		Asura_Message* pxMessage = Asura_Network::CreateMessage(AVP_MSGID_MELEE_ATTACK_HIT, sizeof(AVP_Msg_Melee_Attack_Hit));

		if (pxMessage)
		{
			AVP_Msg_Melee_Attack_Hit* pxData = static_cast<AVP_Msg_Melee_Attack_Hit*>(pxMessage->GetData());
			if (pxData)
			{
				pxData->SetHitEntity(m_auHitEntities[0]);
				pxData->SetPendingVariation(0);
				pxData->SetPendingAttackType(m_uCurrentAttackType);
				pxData->SetDamageType(GetAttackDamageType());
				pxData->SetIsRightHanded(true);
			}

			pxMessage->SetToGuid(m_xOwner.GetGuid());
			pxMessage->SetFromGuid(m_xOwner.GetGuid());

			Asura_Network::Send(pxMessage);
		}
		return;
	}

	if ((m_uCurrentAttackType == AVP_ALIEN_MELEEATTACK_TYPE_PICKUPENEMY) || (m_uCurrentAttackType == AVP_ALIEN_MELEEATTACK_TYPE_PICKUPENEMY_HEADBITE))
	{
		// Make sure we're going to do the correct follow-up attack.
		const AVP_ServerEntity_Actor* pxTarget = AVP_ServerEntity_Actor::FindEntity(m_auHitEntities[0]);

		if (pxTarget)
		{
			// See if we're behind the enemy.
			const Asura_Matrix_3x3& xTargetOri = pxTarget->GetOrientation();
			const Asura_Vector_3 xTargetForward(xTargetOri.e13, xTargetOri.e23, xTargetOri.e33);
			const Asura_Matrix_3x3& xOwnerOri = m_xOwner.GetOrientation();
			const Asura_Vector_3 xOwnerForward(xOwnerOri.e13, xOwnerOri.e23, xOwnerOri.e33);
			const bool bBehind = (xTargetForward * xOwnerForward) > 0.0f;

			switch (pxTarget->GetClassification())
			{
			default:
				// By default, always do the normal attack.
				m_uCurrentAttackType = AVP_ALIEN_MELEEATTACK_TYPE_PICKUPENEMY;
				break;

			case AVP_ENTITYCLASS_ANDROID:
				// On androids, we headbite from the front and stealth kill from behind.
				if (bBehind)
				{
					m_uCurrentAttackType = AVP_ALIEN_MELEEATTACK_TYPE_PICKUPENEMY;
				}
				else
				{
					m_uCurrentAttackType = AVP_ALIEN_MELEEATTACK_TYPE_PICKUPENEMY_HEADBITE;
				}

			case AVP_ENTITYCLASS_MARINE:
			case AVP_ENTITYCLASS_CIVILIAN:
			case AVP_ENTITYCLASS_PREDATOR:
				// Non-android humans, plus predators, can be headbit if you need the health.
				if (m_xOwner.GetHealth() < m_xOwner.GetMaxHealth())
				{
					m_uCurrentAttackType = AVP_ALIEN_MELEEATTACK_TYPE_PICKUPENEMY_HEADBITE;
				}
				else
				{
					// From the front, we always headbite anyway as that's the only anim we have.
					if (bBehind)
					{
						m_uCurrentAttackType = AVP_ALIEN_MELEEATTACK_TYPE_PICKUPENEMY;
					}
					else
					{
						m_uCurrentAttackType = AVP_ALIEN_MELEEATTACK_TYPE_PICKUPENEMY_HEADBITE;
					}
				}
			}
		}
	}

	PARENT::SendMeleeAttackHitMessage();
}


const Asura_Blueprint* AVP_Alien_MeleeCounter_System::GetExtraMeleeDataBlueprint() const
{
	return m_xOwner.GetBlueprint();
}

bool AVP_Alien_MeleeCounter_System::GetQuickAttackBaseParameterNames(const char*& szLeftAttackName, const char*& szRightAttackName) const
{
	szLeftAttackName = s_szStandingLeftSlashAttackBaseParameterName;
	szRightAttackName = s_szStandingRightSlashAttackBaseParameterName;
	return true;
}

bool AVP_Alien_MeleeCounter_System::InsideTailAttackRange() const
{
	Asura_Vector_3 xOrigin;
	m_xOwner.GetHeadPosition(xOrigin);
	const AVP_ServerActor_Vertebrate* pxTarget = AVP_ServerActor_Vertebrate::FindEntity(m_uCurrentTarget);

	Asura_Vector_3 xTargetOffset(m_fTailAttackRange, m_fTailAttackRange, m_fTailAttackRange); // Definitely greater magnitude than m_fTailAttackRange (unless range is 0).
	if (pxTarget)
	{
		Asura_Vector_3 xTarget;
		pxTarget->GetChestPosition(xTarget);

		xTargetOffset = xTarget - xOrigin;
	}
	else
	{
		const AVP_ServerEntity_PhysicalObject* pxPhysicalTarget = AVP_ServerEntity_PhysicalObject::FindEntity(m_uCurrentTarget);
		if (pxPhysicalTarget)
		{
			Asura_Vector_3 xTarget;
			pxPhysicalTarget->GetMeleeOrigin(xTarget);

			xTargetOffset = xTarget - xOrigin;
		}
		else
		{
			const Asura_ServerEntity_Container* pxContainerTarget = Asura_ServerEntity_Container::FindEntity(m_uCurrentTarget);
			if (pxContainerTarget)
			{
				xTargetOffset = m_xStoredTargetPosition - xOrigin;
			}
		}
	}

	if (xTargetOffset.MagnitudeSquared() >= (m_fTailAttackRange * m_fTailAttackRange))
	{
		return false;
	}
	return true;
}

bool AVP_Alien_MeleeCounter_System::InsidePounceRange() const
{
	if( m_uCurrentTarget == ASURA_GUID_UNREGISTERED ){ return false; }

	Asura_Vector_3 xOrigin;
	m_xOwner.GetHeadPosition(xOrigin);
	const AVP_ServerActor_Vertebrate* pxTarget = AVP_ServerActor_Vertebrate::FindEntity(m_uCurrentTarget);
	if (pxTarget)
	{	 
		Asura_Vector_3 xOffset = pxTarget->GetPosition() - m_xOwner.GetPosition();

		if (xOffset.MagnitudeSquared() >= (m_fAttackDetectionRange * m_fAttackDetectionRange) && !IsCurrentTargetInSuckRange())
		{
			return true;
		}
	}
	return false;
}


Asura_Hash_ID AVP_Alien_MeleeCounter_System::GetPounceBlueprintType() const
{
	return uAVP_BLUEPRINT_TYPE_MOVESALIEN;
}

/// End of file. ///
