
///////////////////////////////////////////////////////////////////////////////
//
// File   : AVP_Alien_AnimationHandler.cpp
// Author : Andy Tate
// Date   : 18-Oct-2006
//
// Copyright (C) 2006 Rebellion, All Rights Reserved.
//
///////////////////////////////////////////////////////////////////////////////

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

#include "Core/Asura.h"

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

// Asura independent header-files...
#include "Animation/Asura_Animation.h"
#include "Animation/Asura_AnimationBlend.h"
#include "Animation/Asura_AnimationController_Animation.h"
#include "Blueprint/Asura_Blueprint.h"
#include "Blueprint/Asura_Blueprint_System.h"
#include "Entity/PhysicalObject/Moves/Asura_Movement_System.h"
#include "Entity/PhysicalObject/Moves/Asura_Moves.h"
#include "Physics/Asura_Physics_Ragdoll.h"
#include "SpecialFX/pfx/Asura_PFX_Entity_Effect.h"

// Asura platform header-files...

// Game independent header-files...
#include "Actor/Vertebrates/Alien/AVP_Alien_Description.h"
#include "Actor/AVP_Actor_InteractionHandler.h"
#include "Animation/AVP_AnimFlags.h"
#include "Animation/AVP_Animation_BodyRig.h"
#include "Blueprint/AVP_Blueprint_HashIDs.h"
#include "Core/AVP_Game_Core.h"
#include "Entity/PhysicalObject/Actor/Vertebrates/Alien/AVP_ClientActor_Alien.h"
#include "Entity/PhysicalObject/Actor/AVP_Actor_Messages.h"
#include "Moves/Vertebrates/Alien/AVP_Alien_MovesMelee.h"

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

static const Asura_AVector3 g_xDownVector( 0.0f, 1.0f, 0.0f );
static const Asura_Vector_3 g_xTailStabPos( 0.4f, -0.8f, 2.8f ); // This is the object space position of the last bone in the tail at the end of the tail stab anim

const Asura_Hash_ID uAVP_ALIEN_MOUTH_START_PARAMETER_HASH = Asura_GetHashID("MouthStart");
const Asura_Hash_ID uAVP_ALIEN_MOUTH_IDLE_PARAMETER_HASH = Asura_GetHashID("MouthIdle");
const Asura_Hash_ID uAVP_ALIEN_MOUTH_FINISH_PARAMETER_HASH = Asura_GetHashID("MouthFinish");

DEBUGVAR float dbg_fRagdollFadePFXTime = 0.5f;

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

////////////////////////////////////////////////////////////////////////////////
// Function     : AVP_Alien_AnimationHandler::AVP_Alien_AnimationHandler
// Author       : Andy Tate
// Date         : 18-Oct-2006
////////////////////////////////////////////////////////////////////////////////

AVP_Alien_AnimationHandler::AVP_Alien_AnimationHandler(AVP_ClientActor_Alien& xOwner)
: PARENT(xOwner)
, m_xOwnerAlien(xOwner)
, m_uMouthControllerHash(ASURA_HASH_ID_UNSET)
, m_uMouthStartAnimID(ASURA_HASH_ID_UNSET)
, m_uMouthIdleAnimID(ASURA_HASH_ID_UNSET)
, m_uMouthFinishAnimID(ASURA_HASH_ID_UNSET)
, m_pxTailStabBoneModifier(NULL)
, m_xTailStabTargetPos(g_xTailStabPos)
, m_uTailStabEntityTargetGuid(ASURA_GUID_UNREGISTERED)
, m_uTailStabEntityTargetMarkerPoint(ASURA_HASH_ID_UNSET)
, m_uRagdollFadingPFXHash(ASURA_HASH_ID_UNSET)
, m_bTailStabbing(false)
, m_bTailStabUsingWorldspaceTarget(false)
, m_bTailStabUsingEntityTarget(false)
, m_fPFXTimer(dbg_fRagdollFadePFXTime)
{
	const Asura_Blueprint* pxBP = m_xOwnerAlien.GetBlueprint();
	Asura_Value xValue;
	if ( pxBP && pxBP->GetParameterValueByHashID( 0xB90B66C0, xValue ) ) // AcidDamage.PFX
	{
		m_uRagdollFadingPFXHash	= xValue.GetHashIDValue();
	}
}

/// End of function 'AVP_Alien_AnimationHandler::AVP_Alien_AnimationHandler'. ///

AVP_Alien_AnimationHandler::~AVP_Alien_AnimationHandler()
{
}

void AVP_Alien_AnimationHandler::Initialise( void )
{
	PARENT::Initialise();

	if ( m_pxBodyRig )
	{
		m_pxBodyRig->SetMaxAimRightLeft( Asura_Maths::Deg90 );
	}

	InitialiseIKSolver();

	m_pxTailStabBoneModifier = new AVP_BoneDataModifier_TargetRelative( m_pxAnimation, g_xTailStabPos, uAVP_ALIEN_NUM_IK_BONES_PER_TAIL, IsFirstPerson() ? AVP_Alien_Description::GetFirstPersonChainsArray( AVP_Alien_Description::TAIL ) : AVP_Alien_Description::GetThirdPersonChainsArray( AVP_Alien_Description::TAIL ), true );

	// Store the animation hashes for the mouth anims.
	const Asura_Hash_ID		uBlueprintHash	= m_xOwnerAlien.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(uAVP_ALIEN_MOUTH_START_PARAMETER_HASH, xValue))
		{
			m_uMouthStartAnimID = xValue.GetHashIDValue();
		}
		if (pxBlueprint->GetParameterValueByHashID(uAVP_ALIEN_MOUTH_IDLE_PARAMETER_HASH, xValue))
		{
			m_uMouthIdleAnimID = xValue.GetHashIDValue();
		}
		if (pxBlueprint->GetParameterValueByHashID(uAVP_ALIEN_MOUTH_FINISH_PARAMETER_HASH, xValue))
		{
			m_uMouthFinishAnimID = xValue.GetHashIDValue();
		}
	}
}

void AVP_Alien_AnimationHandler::Shutdown( void )
{
	delete m_pxTailStabBoneModifier;
	PARENT::Shutdown();
}

void AVP_Alien_AnimationHandler::HandleAnimationRequest( const AVP_Message_Actor_RequestAnimation& xAnimRequestData )
{
#if 0
    if ( m_xCurrentAnimSequenceData.IsValid() || m_xPendingAnimSequenceData.IsValid() )
    {
        //Asura_Assert( false, "AVP_Alien_AnimationHandler::HandleAnimationRequest - shouldn't request an anim whilst playing a gamescene anim template" );
        return;
    }
  
	u_int uAnimFlags = AAC_FLAG_ACTIVE | AAC_FLAG_ALLOW_DESTROY_FROM_MESSAGE | AAC_FLAG_ENSURE_UNIQUE_HASH | AACA_FLAG_CALCULATE_VELOCITY;
    // Work out if we should add the loop flag.
    if ( xAnimRequestData.m_eLoopStatus == AVP_Message_Actor_RequestAnimation::LOOP_ON )
    {
        uAnimFlags |= AACA_FLAG_LOOPED;
    }
    else if ( xAnimRequestData.m_eLoopStatus == AVP_Message_Actor_RequestAnimation::LOOP_STATUS_UNSET )
    {
        const bool bAnimLoopsByDefault =
            ( xAnimRequestData.m_uAnimation == AVP_Alien_Description::ALIEN_ANIM_IDLE		||
              xAnimRequestData.m_uAnimation == AVP_Alien_Description::ALIEN_ANIM_SNEAK		||
              xAnimRequestData.m_uAnimation == AVP_Alien_Description::ALIEN_ANIM_SNEAK_REST	||
              xAnimRequestData.m_uAnimation == AVP_Alien_Description::ALIEN_ANIM_WALK		||
              xAnimRequestData.m_uAnimation == AVP_Alien_Description::ALIEN_ANIM_JOG		||
              xAnimRequestData.m_uAnimation == AVP_Alien_Description::ALIEN_ANIM_RUN		||
              xAnimRequestData.m_uAnimation == AVP_Alien_Description::ALIEN_ANIM_SPRINT		||
              xAnimRequestData.m_uAnimation == AVP_Alien_Description::ALIEN_ANIM_PIN_LOOP	||
              xAnimRequestData.m_uAnimation == AVP_Alien_Description::ALIEN_ANIM_CHARGE_ATTACK_RUN	||
              xAnimRequestData.m_uAnimation == AVP_Alien_Description::ALIEN_ANIM_CROUCH_ATTACK_REST ||
              xAnimRequestData.m_uAnimation == AVP_Alien_Description::ALIEN_ANIM_STAND_ATTACK_REST  ||
			  xAnimRequestData.m_uAnimation == AVP_Alien_Description::ALIEN_ANIM_CRAWL ||
              xAnimRequestData.m_uAnimation == AVP_Alien_Description::ALIEN_ANIM_WALL_CRAWL ||
              xAnimRequestData.m_uAnimation == AVP_Alien_Description::ALIEN_ANIM_WALL_IDLE );
        uAnimFlags |= bAnimLoopsByDefault ? AACA_FLAG_LOOPED : 0;
    }
    // Work out if we should calculate angular velocity flag	
	if ( xAnimRequestData.m_uAnimation == AVP_Alien_Description::ALIEN_ANIM_SNEAK_TURN_90LEFT  || 
		 xAnimRequestData.m_uAnimation == AVP_Alien_Description::ALIEN_ANIM_SNEAK_TURN_90RIGHT ||
         xAnimRequestData.m_uAnimation == AVP_Alien_Description::ALIEN_ANIM_SNEAK_TURN_180	   ||
		 xAnimRequestData.m_uAnimation == AVP_Alien_Description::ALIEN_ANIM_STAND_ATTACK_TURN_LEFT ||
		 xAnimRequestData.m_uAnimation == AVP_Alien_Description::ALIEN_ANIM_STAND_ATTACK_TURN_RIGHT )
    {
       uAnimFlags |= AACA_FLAG_CALCULATE_ANGULAR_VELOCITY;
    }
    
    // use animations offset during this anim
    if ( xAnimRequestData.m_uAnimation == AVP_Alien_Description::ALIEN_ANIM_TAIL_ATTACK_SPUN )
    {
		uAnimFlags |= AACA_FLAG_CALCULATE_MOVEMENT_OFFSET;
		uAnimFlags |= AACA_FLAG_CALCULATE_VELOCITY;
		uAnimFlags |= AACA_FLAG_CALCULATE_ANGULAR_VELOCITY;
	}

	u_int uHashId = AVP_Alien_Description::GetAnimationHashID(
		static_cast<AVP_Alien_Description::AnimationType>(
			xAnimRequestData.m_uAnimation));

	Asura_AnimationBlend* pxAnimBlend = new Asura_AnimationBlend_AnimatedTween( 0.2f, 0.0f, false );
	m_uCurrentRequestedAnimControllerHash = PlayAnimation( uAnimFlags, AVP_AAPC_REQUEST_ANIM, uHashId, pxAnimBlend );
#endif
}

void AVP_Alien_AnimationHandler::Update()
{	
    PARENT::Update();

	const u_int uAnimFlags = GetAnimation()->GetFlagsForAllAnimsLastFrame();

	u_int u = 0;
	for ( ; u != AVP_Alien_Description::LEFT_ARM; ++u )
	{
		m_axIKLOSChains[ u ].SetAnimVeto( ( uAnimFlags & AVP_ANIMFLAG_NOT_IK_FEET ) != 0 );
	}

	for ( ; u != AVP_Alien_Description::TAIL; ++u )
	{
		m_axIKLOSChains[ u ].SetAnimVeto( ( uAnimFlags & AVP_ANIMFLAG_NOT_IK_HANDS_AND_TAIL ) != 0 );
	}

	m_xTailIKChain.SetAnimVeto( ( uAnimFlags & AVP_ANIMFLAG_NOT_IK_HANDS_AND_TAIL ) != 0 );

	if ( m_pxTailStabBoneModifier )
	{
		if ( m_bTailStabbing )
		{
			m_xTailIKChain.SetAnimVeto( true );
			m_pxTailStabBoneModifier->TweenIn( DEFAULT_TWEEN_TIME );
			Asura_Vector_3 xOSTargetPos;
			if (CalculateTailStabTargetPosition(xOSTargetPos))
			{
				m_pxTailStabBoneModifier->SetOSTargetPosition(xOSTargetPos);
			}
		}
		else
		{
			m_pxTailStabBoneModifier->TweenOut( 0.5f );
		}
	}

	UpdateMouthAnim();
}


const Asura_Animation_IK_Chain_LOS* AVP_Alien_AnimationHandler::GetLimbIKChainLOS( u_int uLimbItr ) const
{
	if ( uLimbItr < AVP_Alien_Description::LEFT_FINGER0 )
	{
		return &m_axIKLOSChains[ uLimbItr ];
	}
	else
	{
		return NULL;
	}
}

void AVP_Alien_AnimationHandler::InitialiseIKSolver()
{
	Asura_Assert( m_xIKSolver.GetFirstChain() == NULL, "IK Solver already initialised, clearing it..." );
	if ( m_xIKSolver.GetFirstChain() )
	{
		m_xIKSolver.Clear();
	}

	static const int NUM_EXCEPTIONS = 7;
	static const u_short s_ausClassificationExceptionArray[NUM_EXCEPTIONS] = { 
		AVP_ENTITYCLASS_DISC, 
		AVP_ENTITYCLASS_DROPSHIP_MISSILE, 
		AVP_ENTITYCLASS_GENERICPROJECTILE, 
		AVP_ENTITYCLASS_HUMANFLARE, 
		AVP_ENTITYCLASS_PROJECTILE_FLAMETHROWER,
		AVP_ENTITYCLASS_MINE,
		AVP_ENTITYCLASS_PROJECTILE_ACIDSPIT
	};



	if ( IsFirstPerson() )
	{
		for ( u_int u = 0; u != AVP_Alien_Description::NUM_IK_CHAINS; ++u )
		{
			if ( u < AVP_Alien_Description::LEFT_FINGER0 )
			{
				m_axIKLOSChains[ u ].SetBonesArrays( AVP_Alien_Description::GetFirstPersonChainsArray(u), AVP_Alien_Description::GetFirstPersonTwistChainsArray(u) );
				if ( m_pxAnimation->GetSkin() != NULL )
				{
					m_axIKLOSChains[ u ].SetAxesFromRestPose( m_pxAnimation->GetSkin()->BoneRestPose );
				}
				m_axIKLOSChains[ u ].SetIsRootBoneMover();
				m_axIKLOSChains[ u ].SetLocalLOSDirection( g_xDownVector );
				m_axIKLOSChains[ u ].SetExclusionLists( NUM_EXCEPTIONS, s_ausClassificationExceptionArray );
				m_axIKLOSChains[ u ].SetTestAgainstEverything( m_uOwnerGuid );
				m_axIKLOSChains[ u ].SetExtendLimit( 0.01f );
				m_xIKSolver.AddChain( m_axIKLOSChains[ u ] );
			}
			else if ( u < AVP_Alien_Description::RIGHT_FINGER0 )
			{
				m_axIKLOSChains[ u ].SetBonesArrays( AVP_Alien_Description::GetFirstPersonChainsArray(u), AVP_Alien_Description::GetFirstPersonTwistChainsArray(u) );
				if ( m_pxAnimation->GetSkin() != NULL )
				{
					m_axIKLOSChains[ u ].SetAxesFromRestPose( m_pxAnimation->GetSkin()->BoneRestPose );
				}
				m_axIKLOSChains[ u ].SetLocalLOSDirection( g_xDownVector );
				m_axIKLOSChains[ u ].SetTestAgainstEverything( m_uOwnerGuid );
				m_axIKLOSChains[ u ].SetExtendLimit( 0.0f );
				m_axIKLOSChains[ AVP_Alien_Description::LEFT_ARM ].AddChildChain( m_axIKLOSChains[ u ] );
			}
			else if ( u < AVP_Alien_Description::TAIL )
			{
				m_axIKLOSChains[ u ].SetBonesArrays( AVP_Alien_Description::GetFirstPersonChainsArray(u), AVP_Alien_Description::GetFirstPersonTwistChainsArray(u) );
				if ( m_pxAnimation->GetSkin() != NULL )
				{
					m_axIKLOSChains[ u ].SetAxesFromRestPose( m_pxAnimation->GetSkin()->BoneRestPose );
				}
				m_axIKLOSChains[ u ].SetLocalLOSDirection( g_xDownVector );
				m_axIKLOSChains[ u ].SetTestAgainstEverything( m_uOwnerGuid );
				m_axIKLOSChains[ u ].SetExtendLimit( 0.0f );
				m_axIKLOSChains[ AVP_Alien_Description::RIGHT_ARM ].AddChildChain( m_axIKLOSChains[ u ] );
			}
			else
			{
				m_xTailIKChain.SetBonesArrays( AVP_Alien_Description::GetFirstPersonChainsArray(u), AVP_Alien_Description::GetFirstPersonTwistChainsArray(u) );
				m_xTailIKChain.SetDoJointConstraints();
				m_xTailIKChain.SetDoCollisionConstraints();
				m_xTailIKChain.SetBoneCollisionRadius( 0.1f );
				m_xIKSolver.AddChain( m_xTailIKChain );
			}
		}
	}
	else
	{
		for ( u_int u = 0; u != AVP_Alien_Description::NUM_IK_CHAINS; ++u )
		{
			if ( u < AVP_Alien_Description::LEFT_FINGER0 )
			{
				m_axIKLOSChains[ u ].SetBonesArrays( AVP_Alien_Description::GetThirdPersonChainsArray(u), AVP_Alien_Description::GetThirdPersonTwistChainsArray(u) );
				if ( m_pxAnimation->GetSkin() != NULL )
				{
					m_axIKLOSChains[ u ].SetAxesFromRestPose( m_pxAnimation->GetSkin()->BoneRestPose );
				}
				m_axIKLOSChains[ u ].SetIsRootBoneMover();
				m_axIKLOSChains[ u ].SetLocalLOSDirection( g_xDownVector );
				m_axIKLOSChains[ u ].SetExclusionLists( NUM_EXCEPTIONS, s_ausClassificationExceptionArray );
				m_axIKLOSChains[ u ].SetTestAgainstEverything( m_uOwnerGuid );
				m_axIKLOSChains[ u ].SetExtendLimit( 0.01f );
				m_axIKLOSChains[ u ].SetDistanceFadeRange( 20.0f, 30.0f );
				m_xIKSolver.AddChain( m_axIKLOSChains[ u ] );
			}
			else if ( u == AVP_Alien_Description::TAIL )
			{
				m_xTailIKChain.SetBonesArrays( AVP_Alien_Description::GetThirdPersonChainsArray(u), AVP_Alien_Description::GetThirdPersonTwistChainsArray(u) );
				m_xTailIKChain.SetDoJointConstraints();
				m_xTailIKChain.SetDoCollisionConstraints();
				m_xTailIKChain.SetBoneCollisionRadius( 0.1f );
				m_xTailIKChain.SetDistanceFadeRange( 50.0f, 70.0f );
				m_xIKSolver.AddChain( m_xTailIKChain );
			}
		}
	}

	m_xIKSolver.SetRootRootBoneMaxMovement( 0.3f ); // max 30cm movement.
	EnableIKSolver();
}

void AVP_Alien_AnimationHandler::EnableAllIKButTail()
{
	for ( u_int u = 0; u != AVP_Alien_Description::LEFT_FINGER0; ++u )
	{
		m_xIKSolver.AddChain( m_axIKLOSChains[ u ] );
	}
}

void AVP_Alien_AnimationHandler::DisableAllIKButTail()
{
	for ( u_int u = 0; u != AVP_Alien_Description::LEFT_FINGER0; ++u )
	{
		m_xIKSolver.RemoveChain( m_axIKLOSChains[ u ] );
	}
}

void AVP_Alien_AnimationHandler::EnableArmsIK()
{
	m_xIKSolver.AddChain( m_axIKLOSChains[ AVP_Alien_Description::LEFT_ARM ] );
	m_xIKSolver.AddChain( m_axIKLOSChains[ AVP_Alien_Description::RIGHT_ARM ] );
}

void AVP_Alien_AnimationHandler::DisableArmsIK()
{
	m_xIKSolver.RemoveChain( m_axIKLOSChains[ AVP_Alien_Description::LEFT_ARM ] );
	m_xIKSolver.RemoveChain( m_axIKLOSChains[ AVP_Alien_Description::RIGHT_ARM ] );
}

DEBUGVAR float dbg_fMouthTweenTime = 0.1f;

void AVP_Alien_AnimationHandler::UpdateMouthAnim()
{
	if (!m_pxAnimation) { return; }

	// Currently, local player is the best way we have to check for the use of first-person skins.
	if (!AVP_Game_Core::IsLocalPlayer(m_uOwnerGuid)) { return; }

	const AVP_ClientActor_Alien* pxOwner = AVP_ClientActor_Alien::FindEntity(m_uOwnerGuid);
	if (!pxOwner) { return; }

	const AVP_Actor_InteractionHandler* pxInteraction = pxOwner->GetInteractionHandler();
	if (!pxInteraction) { return; }

	const bool bHasHeadbiteTarget = pxInteraction->GetGuidForInteraction(AVP_INTERACTION_TYPE_HEADBITE) != ASURA_GUID_UNREGISTERED;

	const bool bInMove = pxOwner->GetMovementSystem() && pxOwner->GetMovementSystem()->GetCurrentMove();
	const bool bInHeadbiteMove = bInMove && (pxOwner->GetMovementSystem()->GetCurrentMove()->GetHash() == AVP_Alien_Move_Melee_Headbite::GetStaticHash());

	if ((bHasHeadbiteTarget && !bInMove) || bInHeadbiteMove)
	{
		// This needs us to have the mouth coming in and staying in.
		if (m_uMouthControllerHash == ASURA_HASH_ID_UNSET)
		{
			// This means the mouth hasn't come in yet, start the initial anim.
			u_int uAnimHash = (m_uMouthStartAnimID != ASURA_HASH_ID_UNSET) ? m_uMouthStartAnimID : m_uMouthIdleAnimID;
			if (uAnimHash != ASURA_HASH_ID_UNSET)
			{
				u_int uFlags = AAC_FLAG_ACTIVE | AAC_FLAG_ALLOW_DESTROY_FROM_MESSAGE | AAC_FLAG_ENSURE_UNIQUE_HASH;
				Asura_AnimationBlend_AnimatedTween* pxBlend = new Asura_AnimationBlend_AnimatedTween(dbg_fMouthTweenTime, false);
				m_uMouthControllerHash = PlayAnimation(uFlags, AVP_AAPC_READYBITE_ANIM, uAnimHash, pxBlend);
			}
		}
		else
		{
			// This means the mouth is coming in or idling. We need to start the idle when the start anim finishes.
			// Since the idle anim will never finish, as it loops, checking if the current anim is finished is sufficient.
			const Asura_AnimationController_Animation* pxController = m_pxAnimation->GetControllerAnimationFromHash(m_uMouthControllerHash);
			if (!pxController)
			{
				// Must have got destroyed by something.
				Asura_Assert(false, "Lost the mouth animation, restarting sequence.");
				m_uMouthControllerHash = ASURA_HASH_ID_UNSET;
			}
			else if (pxController->IsFinished())
			{
				// The start anim has finished, play the idle anim.
				if (m_uMouthIdleAnimID != ASURA_HASH_ID_UNSET)
				{
					u_int uFlags = AAC_FLAG_ACTIVE | AAC_FLAG_ALLOW_DESTROY_FROM_MESSAGE | AAC_FLAG_ENSURE_UNIQUE_HASH | AACA_FLAG_LOOPED;
					Asura_AnimationBlend_AnimatedTween* pxBlend = new Asura_AnimationBlend_AnimatedTween(dbg_fMouthTweenTime, false);
					m_uMouthControllerHash = PlayAnimation(uFlags, AVP_AAPC_READYBITE_ANIM, m_uMouthIdleAnimID, pxBlend); // We can forget the earlier anim, as it'll be destroyed automatically by this one tweening in.
				}
			}
		}
	}
	else
	{
		// This needs to have the mouth going out and staying out.
		if (m_uMouthControllerHash != ASURA_HASH_ID_UNSET)
		{
			// We must have a start or idle anim around still, play the finish anim.
			if (m_uMouthFinishAnimID != ASURA_HASH_ID_UNSET)
			{
				u_int uFlags = AAC_FLAG_ACTIVE | AAC_FLAG_DESTROY_WHEN_FINISHED | AAC_FLAG_ENSURE_UNIQUE_HASH; // No need to tween out, as it'll be off-screen. Instead, just destroy.
				Asura_AnimationBlend_AnimatedTween* pxBlend = new Asura_AnimationBlend_AnimatedTween(dbg_fMouthTweenTime, false);
				u_int uTempControllerHash = PlayAnimation(uFlags, AVP_AAPC_READYBITE_ANIM, m_uMouthFinishAnimID, pxBlend);
				if (uTempControllerHash == ASURA_HASH_ID_UNSET)
				{
					// Anim failed to start, make sure we lose the old one anyway
					m_pxAnimation->DestroyController(m_uMouthControllerHash);
				}
				// We can forget the earlier anim, as it'll be destroyed automatically by this one tweening in (or we just destroyed it in the block above).
				m_uMouthControllerHash = ASURA_HASH_ID_UNSET;
			}
			else
			{
				// Just destroy the animation in this case.
				// TODO - Should possibly tween out the anim, but that may never be needed.
				m_pxAnimation->DestroyController(m_uMouthControllerHash);
				m_uMouthControllerHash = ASURA_HASH_ID_UNSET;
			}
		}
	}
}

bool AVP_Alien_AnimationHandler::CalculateTailStabTargetPosition(Asura_Vector_3& xOSTargetPos) const
{
	Asura_Assert(m_bTailStabbing, "Shouldn't be trying to calculate a tail stab target position unless we're tail-stabbing.");

	if (m_bTailStabUsingEntityTarget)
	{
		// Entity target, get a worldspace position from them then convert that to object space.
		const AVP_ClientEntity_PhysicalObject* pxTarget = AVP_ClientEntity_PhysicalObject::FindEntity(m_uTailStabEntityTargetGuid);
		if (!pxTarget) { return false; }

		if (!pxTarget->GetMarkerLocationFromHash(m_uTailStabEntityTargetMarkerPoint, &xOSTargetPos, NULL))
		{
			xOSTargetPos = pxTarget->GetCurrentPosition();
		}

		const Asura_Vector_3& xOwnerPos = m_xOwnerActor.GetCurrentPosition();
		Asura_Quat xInverseOwnerOri(m_xOwnerActor.GetCurrentOrientation());
		xInverseOwnerOri.Invert();

		xOSTargetPos = xInverseOwnerOri.RotateVector(xOSTargetPos - xOwnerPos);
		return true;
	}
	else if (m_bTailStabUsingWorldspaceTarget)
	{
		// Worldspace stored target position, convert to object space.
		const Asura_Vector_3& xOwnerPos = m_xOwnerActor.GetCurrentPosition();
		Asura_Quat xInverseOwnerOri(m_xOwnerActor.GetCurrentOrientation());
		xInverseOwnerOri.Invert();

		xOSTargetPos = xInverseOwnerOri.RotateVector(m_xTailStabTargetPos - xOwnerPos);
		return true;
	}
	else
	{
		// Object space stored target position
		xOSTargetPos = m_xTailStabTargetPos;
		return true;
	}
}

void AVP_Alien_AnimationHandler::UpdateRagdollFading( Asura_Ragdoll_Wrapper* pxRagdollrapper )
{
	PARENT::UpdateRagdollFading( pxRagdollrapper );

	// maybe do some alien acid PFX or something here?
	m_fPFXTimer -= Asura_Timers::GetGameTimeSlice();
	if ( m_fPFXTimer < 0.0f )
	{
		m_fPFXTimer = dbg_fRagdollFadePFXTime;
		int iBoneIdx = Asura_Random::GetInt( 0, m_pxAnimation->GetSkin()->NumberOfBones - 1 );
		Asura_Vector_3 xBonePos;
		Asura_Matrix_3x3 xBoneOri;
		m_pxAnimation->GetBoneLocation( iBoneIdx, &xBonePos, &xBoneOri, true );
		Asura_PFX_Entity_Effect::Create( xBonePos, xBoneOri, m_uRagdollFadingPFXHash, 0, ASURA_PFX_EFFECT_CAMERA_FLAGS );
	}
}

/// End of file. ///
