
///////////////////////////////////////////////////////////////////////////////
//
// File   : AVP_Vertebrate_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/AVP_Vertebrate_AnimationHandler.h"

// Asura independent header-files...
#include "Animation/Asura_Animation.h"
#include "Animation/Asura_AnimationAttachment.h"
#include "Animation/Asura_AnimationBlend.h"
#include "Animation/Asura_AnimationController_Animation.h"
#include "Core/Asura_Timers.h"
#include "Core/Memory/Asura_Mem.h"
#include "Entity/Ragdoll/Asura_Ragdoll_Wrapper.h"
#include "Interface/Asura_CommandConsole.h"
#include "Network/Asura_Network.h"
#include "Network/Asura_Message.h"
#include "Physics/Asura_Physics_Ragdoll.h"
#include "Shapes/Asura_Shapes.h"

// Game independent header-files...
#include "Actor/AVP_Actor_FidgetAnimHandler.h"
#include "Actor/AVP_Actor_AnimStateData.h"
#include "Actor/Vertebrates/Human/AVP_Human_Description.h"
#include "Actor/Vertebrates/Alien/AVP_Alien_Description.h"
#include "Actor/Vertebrates/FaceHugger/AVP_FaceHugger_Description.h"
#include "Actor/Vertebrates/Predator/AVP_Predator_Description.h"
#include "Blueprint/AVP_Blueprint_HashIDs.h"
#include "Blueprint/ParameterEnumerators/AVP_Blueprint_ParameterEnumerator_SkinAttachments.h"
#include "Core/AVP_Game_Core.h"
#include "Core/AVP_Maths.h"
#include "Core/AVP_Settings.h"
#include "Entity/AVP_Messages.h"
#include "Entity/PhysicalObject/AVP_ServerEntity_PhysicalObject.h"
#include "Entity/PhysicalObject/Actor/Vertebrates/AVP_ClientActor_Vertebrate.h"
#include "Inventory/AVP_Inventory_HashIDs.h"
#include "Inventory/AVP_Inventory_System.h"
#include "Inventory/Items/Weapons/AVP_InventoryWeapon_RangedWeapon.h"
#include "Moves/AVP_Moves.h"

// axon
#include "AnimTemplates/Axon_Gamescene_AnimTemplates.h"
#include "AnimTemplates/Axon_Gamescene_AnimTemplate_System.h"

// Tools header-files...

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

#define INVALID_BLEND_REGION 0xFFFFFFFF
#define INVALID_ANIM_INDEX 0xFFFFFFFF

DEBUGVAR float	dbg_fIKBlendOutTime = 0.2f;

DEBUGVAR float	dbg_fRagdollDampingScale	= 0.990f;

DEBUGVAR float	dbg_fWeaponGroupTweenTime	= 0.2f;

DEBUGVAR float	dbg_fDefaultDirectionBlendTweenTime = 0.2f;

DVSVAR float dvs_fUBTweenTime = 0.3f;

enum DirItr
{
	ACW,
	CW
};

enum SpeedItr
{
	SLOW,
	FAST
};

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

void AVP_Vertebrate_AnimationHandler::CreateConsoleVars()
{
#if defined(ASURA_DEBUG_VARIABLE_SYSTEM)
	Asura_CommandConsole::AddVar( L"AVP.BodyRig.UpperBodyAnimTweenTime", &dvs_fUBTweenTime, Asura_CommandConsole::REMOVE_AT_SHUTDOWN );
#endif
}

float AVP_Vertebrate_AnimationHandler::GetUpperBodyAnimTweenTime()
{
	return dvs_fUBTweenTime;
}

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




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

bool AVP_Vertebrate_AnimationHandler::MakeRagdoll( const Asura_Vector_3& xPosition, const Asura_Matrix_3x3& xOrientation )
{
	Asura_Hash_ID uRagdollHash = ASURA_HASH_ID_UNSET;
	Asura_Hash_ID uRagdollParamHash = ASURA_HASH_ID_UNSET;

	AVP_ClientActor_Vertebrate* pxVert = AVP_ClientActor_Vertebrate::FindEntity( m_uOwnerGuid );
	if ( pxVert )
	{
		if( pxVert->IsUsingFirstPersonSkin() )
		{
			uRagdollParamHash = 0xc75123ec; //Asura_GetHashID("Ragdoll.HUD");
		}
		else
		{
			uRagdollParamHash = 0x2fb84ce7; //Asura_GetHashID("Ragdoll.TP");
		}

		Asura_Blueprint* pxBP = pxVert->GetBlueprint();
		if ( pxBP )
		{
			Asura_Value xValue;
			if ( pxBP->GetParameterValueByHashID( uRagdollParamHash, xValue ) )
			{
				uRagdollHash = xValue.GetHashIDValue();
			}
		}
	}

	DisableIKSolver();

	const Asura_Physics_Ragdoll_Description* pxRagdollDescription = Asura_Physics_Ragdoll_System::GetRagdollDescription( uRagdollHash );
	// Did we find our description?
	if (pxRagdollDescription)
	{
		return(CreateRagdoll(*pxRagdollDescription, xPosition, xOrientation));
	}
	else
	{
		return false; 
	}
}

////////////////////////////////////////////////////////////////////////////////
// Function     : AVP_Vertebrate_AnimationHandler::CreateRagdoll
// Author       : Andy Tate
// Date         : 9-Nov-2006
////////////////////////////////////////////////////////////////////////////////

DEBUGVAR float dbg_fVelocityMultiplier = 0.3f; // hackish way to translate velocity to impulse ?

bool AVP_Vertebrate_AnimationHandler::CreateRagdoll (const Asura_Physics_Ragdoll_Description &xDescription,
													 const Asura_Vector_3 &xPosition,
													 const Asura_Matrix_3x3 &xOrientation)
{
	if (!m_pxAnimation || !m_pxAnimation->GetSkin()) { return false; }

	AVP_ClientEntity_Actor*		pxClient		= AVP_ClientEntity_Actor::FindEntity( m_uOwnerGuid );

	if (!pxClient) { return false; }

	pxClient->FlagMovementSystemForDestruction();

	Asura_AnimationAttachment*	pxParentAttachment	= pxClient->GetParentAttachment();
	Asura_Shape_Instance&		xShapeInstance	= pxClient->GetShapeInstance();

	// Remove any existing ragdoll...
	DestroyRagdoll();

	// Create the ragdoll...
	Asura_Vector_3 xVelocity;
	pxClient->GetCurrentVelocity(xVelocity);

	int iRagDollPriority = (pxClient->IsADeadBody()) ? AVP_ClientEntity_Actor::START_DEAD_ACTOR_RAGDOLL_PRIORITY : GetRagdollPriority();

	m_iRagdollHandle = Asura_Ragdoll_Wrapper::Create( m_uOwnerGuid, m_uOwnerGuid, iRagDollPriority, xDescription, m_pxAnimation, xPosition, xOrientation, xVelocity * dbg_fVelocityMultiplier );
	Asura_Ragdoll_Wrapper* pxRagdollWrapper = Asura_Ragdoll_Wrapper::GetRagdollWrapper( m_iRagdollHandle );
	Asura_Physics_Ragdoll* pxRagdoll = NULL;
	if ( pxRagdollWrapper )
	{
		pxRagdollWrapper->SetShouldFadeOut( false );
		pxRagdoll = pxRagdollWrapper->GetPhysicsRagdollPointer();
	}

	if ( pxRagdoll )
	{
		pxRagdoll->SetAssociatedEntityGuid(pxClient->GetGuid());
		pxRagdoll->SetReportEnvironmentCollisionFX(true);
		pxRagdoll->SetFlagsOfPolysToIgnore( pxClient->GetFlagsToIgnore() | pxRagdoll->GetFlagsOfPolysToIgnore() ); // Make sure the defaults are included too.
		pxRagdollWrapper->SetShouldDestroyServerEntity();
		
		if ( !pxClient->IsADeadBody() )
		{
			pxRagdollWrapper->SetFadeDelay( GetRagdollStartFadingOutTime() );
		}

		pxRagdoll->SetDampingScale( dbg_fRagdollDampingScale );
		// Update the render flags...
		xShapeInstance.AddFlags( ASURA_SHAPE_INSTANCE_BONES_IN_WORLDSPACE );
		if ( pxParentAttachment != NULL )
		{
			pxParentAttachment->SetRenderFlagsToAdd( ASURA_HIERARCHY_RENDER_BONES_IN_WORLDSPACE );
		}

		return true;
	}
	else
	{
		return false;
	}
}

/// End of function 'AVP_Vertebrate_AnimationHandler::CreateRagdoll'. ///



////////////////////////////////////////////////////////////////////////////////
// Function     : AVP_Vertebrate_AnimationHandler::DestroyRagdoll
// Author       : Andy Tate
// Date         : 10-Nov-2006
////////////////////////////////////////////////////////////////////////////////

bool AVP_Vertebrate_AnimationHandler::DestroyRagdoll (bool bDestroyEntity)
{
	if ( !IsRagdoll() )
	{
		return false;
	}
	else
	{
		AVP_ClientEntity_Actor*		pxClient		= AVP_ClientEntity_Actor::FindEntity(m_uOwnerGuid);
		if (pxClient)
		{
			Asura_AnimationAttachment*	pxAttachment	= pxClient->GetParentAttachment();
			Asura_Shape_Instance&		xShapeInstance	= pxClient->GetShapeInstance();

			// Destroy the render flags...
			xShapeInstance.RemoveFlags( ASURA_SHAPE_INSTANCE_BONES_IN_WORLDSPACE );
			if( pxAttachment != NULL )
			{
				pxAttachment->RemoveRenderFlags( ASURA_HIERARCHY_RENDER_BONES_IN_WORLDSPACE );
			}
		}

		Asura_Ragdoll_Wrapper::Destroy( m_iRagdollHandle, bDestroyEntity );
		m_iRagdollHandle = ASURA_INVALID_HANDLE;

		return(true);
	}
}

/// End of function 'AVP_Vertebrate_AnimationHandler::DestroyRagdoll'. ///



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

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

AVP_Vertebrate_AnimationHandler::AVP_Vertebrate_AnimationHandler(AVP_ClientActor_Vertebrate& xOwner)
: PARENT(xOwner)
, m_xOwnerVertebrate(xOwner)
, m_iRagdollHandle(ASURA_INVALID_HANDLE)
, m_pxCurrentLBSlowAnimData( NULL )
, m_pxCurrentLBFastAnimData( NULL )
, m_pxCurrentUBSlowAnimData( NULL )
, m_pxCurrentUBFastAnimData( NULL )
, m_uLowerBodyGroupHash( ASURA_HASH_ID_UNSET )
, m_uUpperBodyGroupHash( ASURA_HASH_ID_UNSET )
, m_uFootstepsAACAHash( ASURA_HASH_ID_UNSET )
, m_uSecondaryTimerAACAHash( ASURA_HASH_ID_UNSET )
, m_fCurrentLBBlendSpeed( 0.0f )
, m_fCurrentUBBlendSpeed( 0.0f )
, m_fLastFadeParametric( 1.0f )
, m_fBoneDataUpdateTweenTimer( 0.0f )
{
	for ( u_int uSpeedItr = 0; uSpeedItr < 2; ++uSpeedItr )
	{
		m_auLBSpeedAACGHash[ uSpeedItr ] = ASURA_HASH_ID_UNSET;
		m_afCurrentLBBlendDir[ uSpeedItr ] = 0.0f;
		m_auCurrentLBAnimBlendRegion[ uSpeedItr ] = INVALID_BLEND_REGION;
		m_auCurrentLBACWAnimIndex[ uSpeedItr ] = INVALID_ANIM_INDEX;
		m_auLastLBAnimBlendRegion[ uSpeedItr ] = INVALID_BLEND_REGION;

		m_auUBSpeedAACGHash[ uSpeedItr ] = ASURA_HASH_ID_UNSET;
		m_afCurrentUBBlendDir[ uSpeedItr ] = 0.0f;
		m_auCurrentUBAnimBlendRegion[ uSpeedItr ] = INVALID_BLEND_REGION;
		m_auCurrentUBACWAnimIndex[ uSpeedItr ] = INVALID_ANIM_INDEX;
		m_auLastUBAnimBlendRegion[ uSpeedItr ] = INVALID_BLEND_REGION;
		for ( u_int uDirItr = 0; uDirItr < 2; ++uDirItr )
		{
			m_aauLBAACAHash[ uSpeedItr ][ uDirItr ] = ASURA_HASH_ID_UNSET;
			m_aauUBAACAHash[ uSpeedItr ][ uDirItr ] = ASURA_HASH_ID_UNSET;
		}
	}

	for (u_int u=0; u<ASURA_ANIMATION_SKIN_COUNT; ++u)
	{
		m_auSkinHashes[u] = ASURA_HASH_ID_UNSET;
	}
}
/// End of function 'AVP_Actor_AnimationHandler::AVP_Actor_AnimationHandler'. ///



////////////////////////////////////////////////////////////////////////////////
// Function     : AVP_Actor_AnimationHandler::~AVP_Actor_AnimationHandler
// Author       : Andy Tate
// Date         : 9-Nov-2006
////////////////////////////////////////////////////////////////////////////////

AVP_Vertebrate_AnimationHandler::~AVP_Vertebrate_AnimationHandler ()
{
	Asura_Assert( m_pxBodyRig == NULL, "Still have a body rig, maybe AnimationHandler wasn't Shutdown before destroying?" );
	Asura_Assert( m_pxFidgetAnimHandler == NULL, "Still have a fidget animation handler, maybe AnimationHandler wasn't Shutdown before destroying?" );
}

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

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

	Asura_Assert( m_pxBodyRig == NULL, "Already have a body rig, maybe AnimationHandler wasn't Shutdown before re-initialising?" );
	m_pxBodyRig = new AVP_Animation_BodyRig( m_xOwnerVertebrate );

	Asura_Assert( m_pxFidgetAnimHandler == NULL, "Already have a fidget animation engine, maybe AnimationHandler wasn't Shutdown before re-initialising?" );
	m_pxFidgetAnimHandler = new AVP_Actor_FidgetAnimHandler( m_xOwnerVertebrate );

	Asura_AnimationController_Group* pxRootStatesGroup = m_pxAnimation->GetControllerGroupFromHash( m_uRootStatesGroupHash );
	if ( pxRootStatesGroup )
	{
		u_int uGroupFlags = AAC_FLAG_ACTIVE | AAC_FLAG_ENSURE_UNIQUE_HASH;
		Asura_AnimationBlend* pxLBGroupBlend = new Asura_AnimationBlend( 1.0f );
#ifdef _DEBUG
		Asura_AnimationController_Group* pxLBGroup = m_pxAnimation->CreateControllerGroup( "Lower Body Group", uGroupFlags, AVP_AAPC_LOWER_BODY_GROUP, pxLBGroupBlend, pxRootStatesGroup );
#else
		Asura_AnimationController_Group* pxLBGroup = m_pxAnimation->CreateControllerGroup( 0xe035f500, uGroupFlags, AVP_AAPC_LOWER_BODY_GROUP, pxLBGroupBlend, pxRootStatesGroup );
#endif
		m_uLowerBodyGroupHash = pxLBGroup ? pxLBGroup->GetControllerHash() : ASURA_HASH_ID_UNSET;

		Asura_AnimationBlend* pxUBGroupBlend = new Asura_AnimationBlend( 1.0f );
#ifdef _DEBUG
		Asura_AnimationController_Group* pxUBGroup = m_pxAnimation->CreateControllerGroup( "Upper Body Group", uGroupFlags, AVP_AAPC_UPPER_BODY_GROUP, pxUBGroupBlend, pxRootStatesGroup );
#else
		Asura_AnimationController_Group* pxUBGroup = m_pxAnimation->CreateControllerGroup( 0xfb21a8bf, uGroupFlags, AVP_AAPC_UPPER_BODY_GROUP, pxUBGroupBlend, pxRootStatesGroup );
#endif
		m_uUpperBodyGroupHash = pxUBGroup ? pxUBGroup->GetControllerHash() : ASURA_HASH_ID_UNSET;
	}
}

void AVP_Vertebrate_AnimationHandler::Shutdown( void )
{
	// clear default weapon group
	ShutdownIKSolver();

	if ( IsRagdoll() )
	{
		Asura_Ragdoll_Wrapper::Destroy( m_iRagdollHandle , false );
		m_iRagdollHandle = ASURA_INVALID_HANDLE;
	}

	delete m_pxBodyRig;
	m_pxBodyRig = NULL;

	PARENT::Shutdown();
}

void AVP_Vertebrate_AnimationHandler::UpdateSkin(void)
{
	const Asura_Blueprint*	pxBlueprint	= m_xOwnerActor.GetBlueprint();

	//////////////////////////////////////////////////////////////////////////
	// NOTE: We don't call the parent function from here, since we do all of
	// the work done their and more (because of head-swapping, etc...)
	//////////////////////////////////////////////////////////////////////////
	if(pxBlueprint != NULL)
	{
		Asura_Hash_ID			auSkinHashes[ASURA_ANIMATION_SKIN_COUNT];
		const Asura_Hash_ID*	puBlueprintParamHashes;
		Asura_Value				xValue;

		u_int					uNumSkins	= 0;

		for (u_int u=0; u<ASURA_ANIMATION_SKIN_COUNT; ++u)
		{
			auSkinHashes[u] = ASURA_HASH_ID_UNSET;
		}

		// Decide on which blueprint parameters to check for skins to use...
		if(IsFirstPerson())
		{
			static const Asura_Hash_ID auBlueprintParamHashes[ASURA_ANIMATION_SKIN_COUNT] =
			{
				uAVP_BLUEPRINT_PARAM_FIRSTPERSON_SKIN_HEAD,
				uAVP_BLUEPRINT_PARAM_FIRSTPERSON_SKIN_ARMS,
				uAVP_BLUEPRINT_PARAM_FIRSTPERSON_SKIN_MAIN,
			};

			puBlueprintParamHashes	= auBlueprintParamHashes;
		}
		else 
		{
			if(IsDamaged())
			{
				static const Asura_Hash_ID auBlueprintParamHashes[ASURA_ANIMATION_SKIN_COUNT] =
				{
					uAVP_BLUEPRINT_PARAM_THIRDPERSON_SKIN_DAMAGED_HEAD,
					uAVP_BLUEPRINT_PARAM_THIRDPERSON_SKIN_DAMAGED_ARMS,
					uAVP_BLUEPRINT_PARAM_THIRDPERSON_SKIN_DAMAGED_MAIN,
				};

				puBlueprintParamHashes	= auBlueprintParamHashes;
			}
			else
			{
				static const Asura_Hash_ID auBlueprintParamHashes[ASURA_ANIMATION_SKIN_COUNT] =
				{
					uAVP_BLUEPRINT_PARAM_THIRDPERSON_SKIN_HEAD,
					uAVP_BLUEPRINT_PARAM_THIRDPERSON_SKIN_ARMS,
					uAVP_BLUEPRINT_PARAM_THIRDPERSON_SKIN_MAIN,
				};

				puBlueprintParamHashes	= auBlueprintParamHashes;
			}
		}

		// Update the initial skin (making sure we do actually need to make any changes)
		bool bRequiresSkinUpdate = false;
		for(u_int u = 0; u < ASURA_ANIMATION_SKIN_COUNT; u++)
		{
			const Asura_Hash_ID	uBlueprintParamHash	= puBlueprintParamHashes[u];

			if( (uBlueprintParamHash != ASURA_HASH_ID_UNSET) &&
				pxBlueprint->GetParameterValueByHashID(uBlueprintParamHash, xValue) )
			{
				const Asura_Hash_ID	uSkinHash	= xValue.GetHashIDValue();

				if(uSkinHash != ASURA_HASH_ID_UNSET)
				{
					auSkinHashes[uNumSkins]	= uSkinHash;
					bRequiresSkinUpdate |= (m_auSkinHashes[uNumSkins] != auSkinHashes[uNumSkins]);
					++uNumSkins;
				}
			}
		}

		if (bRequiresSkinUpdate)
		{
			m_uSkinHash	= auSkinHashes[0];

			// Do we have an "Asura_Animation" to set the skins in?
			bool bNewAnim = false;
			if(m_pxAnimation == NULL)
			{
				m_pxAnimation	= new Asura_Animation(m_uSkinHash);
				bNewAnim = true;
			}

			// Change to this skin (if appropriate)...
			if(m_pxAnimation != NULL)
			{
				const Asura_Hierarchy_Skin*							pxFirstSkin		= m_pxAnimation->GetSkin(0);

				// Set each skin in-turn...
				for(u_int u = 0; u < ASURA_ANIMATION_SKIN_COUNT; u++)
				{
					const Asura_Hash_ID	uSkinHash	= auSkinHashes[u];
					m_auSkinHashes[u] = uSkinHash;

					if(u < uNumSkins)
					{
						const Asura_Hierarchy_Skin*	pxNewSkin	= Asura_Animation_System::GetSkinByHash(auSkinHashes[u]);

						if(pxNewSkin == NULL)
						{
							Asura_Assert(false, "New skin is invalid" );
						}
						else
						{
							if((pxFirstSkin == NULL) || (pxFirstSkin->NumberOfBones == pxNewSkin->NumberOfBones))
							{
								m_pxAnimation->SetSkin(uSkinHash, false, u);
							}
						}
					}
					else
					{
						m_pxAnimation->SetSkin(ASURA_HASH_ID_UNSET, false, u);
					}
				}

				// Handle the enumeration of attachments. We only need to do this the first time, updating skins automatically update attachments.
				if (bNewAnim)
				{
					AVP_Blueprint_ParameterEnumerator_SkinAttachments	xSkinAttachmentsEnumerator(*m_pxAnimation, m_xOwnerActor);
					const char*											szParameterText	= (IsFirstPerson()) ? "FirstPerson.SkinAttachment" : "ThirdPerson.SkinAttachment";

					pxBlueprint->EnumerateParameters(szParameterText, 2, xSkinAttachmentsEnumerator);
				}
			}
		}
	}
}

DEBUGVAR float dbg_fRagdollFadeMoveDownDist = 1.0f; // move ragdolls down by 1 metre over the course of fading them out.

void AVP_Vertebrate_AnimationHandler::Update()
{
	if ( m_xOwnerActor.GetGroundType() == AVP_Actor_PhysicsObjectHandler::OFF_GROUND || m_xOwnerActor.IsDead() )
	{
		m_xIKSolver.DisableRootBoneMovement( dbg_fIKBlendOutTime );
	}
	else
	{
		m_xIKSolver.EnableRootBoneMovement( dbg_fIKBlendOutTime );
	}

	// See if we need to create a ragdoll
	AVP_ClientActor_Vertebrate* pxOwner = AVP_ClientActor_Vertebrate::FindEntity(m_uOwnerGuid);
	if (pxOwner && pxOwner->IsDead() && (m_iRagdollHandle == ASURA_INVALID_HANDLE))
	{
		if (m_uDeathAnimController != ASURA_HASH_ID_UNSET)
		{
			// If we have a death anim, see if it's finished.
			const Asura_AnimationController_Animation* pxController = m_pxAnimation->GetControllerAnimationFromHash(m_uDeathAnimController);
			if (!pxController || pxController->IsFinished())
			{
				AVP_ClientActor_Vertebrate* pxOwner = AVP_ClientActor_Vertebrate::FindEntity(m_uOwnerGuid);
				if (pxOwner)
				{
					pxOwner->MakeRagdoll();
				}
			}
		}
		else
		{
			// Without a death anim, see if we currently have a move controlling death.
			const Asura_Movement_System* pxMovementSystem = pxOwner->GetMovementSystem();
			if (!pxMovementSystem)
			{
				pxOwner->MakeRagdoll();
			}
			else
			{
				const AVP_Move* pxCurrentMove = static_cast<const AVP_Move*>(pxMovementSystem->GetCurrentMove());
				if (!pxCurrentMove || !pxCurrentMove->GetControlDeathInternally())
				{
					pxOwner->MakeRagdoll();
				}
			}
		}
	}

	if ( IsRagdoll() )
	{
		Asura_Ragdoll_Wrapper* pxRagdollrapper = Asura_Ragdoll_Wrapper::GetRagdollWrapper( m_iRagdollHandle );
		if ( m_pxAnimation && pxRagdollrapper && pxRagdollrapper->IsFadingOut() )
		{
			UpdateRagdollFading( pxRagdollrapper );
		}
	}

	PARENT::Update();

	if ( m_fBoneDataUpdateTweenTimer > 0.0f )
	{
		m_fBoneDataUpdateTweenTimer -= Asura_Timers::GetGameTimeSlice();
		if ( m_fBoneDataUpdateTweenTimer <= 0.0f )
		{
			m_xOwnerActor.ForceBoneUpdate();
		}
	}
}

void AVP_Vertebrate_AnimationHandler::UpdateRagdollFading( Asura_Ragdoll_Wrapper* pxRagdollrapper )
{
	Asura_Physics_Ragdoll* pxRagdoll = pxRagdollrapper->GetPhysicsRagdollPointer();
	if ( pxRagdoll )
	{
		pxRagdoll->Sleep();
	}

	float fFadePara = pxRagdollrapper->GetFadeParametric();
	Asura_Vector_3 xDeltaPos( 0.0f, dbg_fRagdollFadeMoveDownDist * ( m_fLastFadeParametric - fFadePara ), 0.0f );
	m_fLastFadeParametric = fFadePara;

	m_pxAnimation->UpdateTimers();
	for ( u_int uBone = 0; uBone < static_cast< u_int >( m_pxAnimation->GetSkin()->NumberOfBones ); ++uBone )
	{
		Asura_Hierarchy_TweenStore_BoneData& xBoneData = m_pxAnimation->GetTweenStore()->GetBoneData( uBone );
		Asura_Vector_3 xBonePos;
		xBoneData.GetPosition( xBonePos );
		xBoneData.SetPosition( xBonePos + xDeltaPos );
	}
	m_pxAnimation->GetTweenStore()->SetPositionDataValid(true);
	m_pxAnimation->IncrementNumTimerUpdates();
}

////////////////////////////////////////////////////////////////////////////////
// Function     : AVP_Vertebrate_AnimationHandler::Render
// Author       : Andy Tate
// Date         : 9-Nov-2006
////////////////////////////////////////////////////////////////////////////////

void AVP_Vertebrate_AnimationHandler::Render ( void ) const
{
	Asura_Ragdoll_Wrapper* pxRagdollWrapper = Asura_Ragdoll_Wrapper::GetRagdollWrapper( m_iRagdollHandle );
	Asura_Physics_Ragdoll* pxRagdoll = NULL;
	if ( pxRagdollWrapper )
	{
		if ( !pxRagdollWrapper->IsFadingOut() )
		{
			pxRagdoll = pxRagdollWrapper->GetPhysicsRagdollPointer();

			if ( pxRagdoll )
			{
				const Asura_AnimationAttachment_Sticky*	pxAttachment	= static_cast<const Asura_AnimationAttachment_Sticky*>( m_xOwnerVertebrate.GetParentAttachment() );

				if ( pxAttachment != NULL )
				{
					const int				iMarkerPointIndex	= pxAttachment->GetMarkerPointIndex();
					const int				iMarkerPointSkinIndex	= pxAttachment->GetMarkerPointSkinIndex();
					const Asura_Animation*	pxParentAnimation	= pxAttachment->GetParentAnimation();

					Asura_Vector_3			xPosition;

					if(pxParentAnimation->GetMarkerLocation(iMarkerPointIndex, iMarkerPointSkinIndex, &xPosition, NULL, true))
					{
						const u_int	uBoneID	= pxAttachment->GetStickyBoneID();

						pxRagdoll->SetBonePosition(uBoneID, xPosition);
					}
				}
				pxRagdoll->UpdateAnimation();
			}
		}
		pxRagdollWrapper->Update( m_pxAnimation );
	}
	else
	{
		PARENT::Render();
	}
}

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

bool AVP_Vertebrate_AnimationHandler::IsRagdoll() const
{
	return ( Asura_Ragdoll_Wrapper::GetRagdollWrapper( m_iRagdollHandle ) != NULL );
}

bool AVP_Vertebrate_AnimationHandler::IsRagdollFadingOut() const
{
	const Asura_Ragdoll_Wrapper* pxRW = Asura_Ragdoll_Wrapper::GetRagdollWrapper( m_iRagdollHandle );
	return ( pxRW ? pxRW->IsFadingOut() : false );
}

void AVP_Vertebrate_AnimationHandler::EnableIKSolver()
{
	if ( m_pxAnimation )
	{
		m_pxAnimation->SetIKSolver( &m_xIKSolver );
		m_xIKSolver.Enable();
	}
}

void AVP_Vertebrate_AnimationHandler::DisableIKSolver()
{
	m_pxAnimation->SetIKSolver( NULL );
	m_xIKSolver.Disable();
}

void AVP_Vertebrate_AnimationHandler::UpdateAnimStateData( const AVP_Actor_AnimStateData* pxAnimDataLBSlow, const AVP_Actor_AnimStateData* pxAnimDataLBFast, const AVP_Actor_AnimStateData* pxAnimDataUBSlow, const AVP_Actor_AnimStateData* pxAnimDataUBFast, const bool bLoop, const float fBlendLBSpeed, const float fBlendUBSpeed, const float fOSMoveDirAngle, const float fTweenTime, const bool bSynch )
{
	m_fCurrentLBBlendSpeed = fBlendLBSpeed;
	m_fCurrentUBBlendSpeed = fBlendUBSpeed;
	u_int uGroupFlags = AAC_FLAG_ACTIVE | AAC_FLAG_ENSURE_UNIQUE_HASH | AAC_FLAG_DESTROY_WHEN_FINISHED;
	u_int uAnimFlags = AAC_FLAG_ACTIVE | AAC_FLAG_ENSURE_UNIQUE_HASH | AAC_FLAG_ALLOW_DESTROY_FROM_MESSAGE;

	if ( bLoop )
	{
		uAnimFlags |= AACA_FLAG_LOOPED;
	}
	u_int uAnimFlagsWithVel = uAnimFlags | AACA_FLAG_CALCULATE_VELOCITY | AACA_FLAG_CALCULATE_ANGULAR_VELOCITY | AACA_FLAG_CALCULATE_MOVEMENT_OFFSET;

	if ( pxAnimDataLBSlow != m_pxCurrentLBSlowAnimData || pxAnimDataLBFast != m_pxCurrentLBFastAnimData || !bSynch )
	{
		m_fBoneDataUpdateTweenTimer = fTweenTime;

		m_pxCurrentLBSlowAnimData = pxAnimDataLBSlow;
		m_pxCurrentLBFastAnimData = pxAnimDataLBFast;
		m_auCurrentLBACWAnimIndex[ 0 ] = INVALID_ANIM_INDEX;
		m_auCurrentLBACWAnimIndex[ 1 ] = INVALID_ANIM_INDEX;
		m_auLastLBAnimBlendRegion[ 0 ] = INVALID_BLEND_REGION;
		m_auLastLBAnimBlendRegion[ 1 ] = INVALID_BLEND_REGION;

		if ( ( pxAnimDataLBSlow != NULL && ( pxAnimDataLBSlow->GetAnimHash() != ASURA_HASH_ID_UNSET || pxAnimDataLBSlow->GetNumBlendRegions() > 0 ) ) ||
			 ( pxAnimDataLBFast != NULL && ( pxAnimDataLBFast->GetAnimHash() != ASURA_HASH_ID_UNSET || pxAnimDataLBFast->GetNumBlendRegions() > 0 ) ) )
		{
			Asura_AnimationController_Group*	pxLBGroup	= m_pxAnimation->GetControllerGroupFromHash( m_uLowerBodyGroupHash );
			Asura_Assert( pxLBGroup != NULL, "Unable to get lower body group" );
			if ( pxLBGroup != NULL )
			{
				float fParametric = 0.0f;
				if ( bSynch )
				{
					Asura_AnimationController_Animation* pxAACA = m_pxAnimation->GetControllerAnimationFromHash( m_uFootstepsAACAHash );
					if ( pxAACA != NULL )
					{
						fParametric = pxAACA->GetParametricTimer();
					}
				}

				Asura_AnimationBlend* pxTweenGroupBlend = new Asura_AnimationBlend_AnimatedTween( fTweenTime, false );
				pxTweenGroupBlend->SetDeltasUseBlends( true );
#ifdef _DEBUG
				Asura_AnimationController_Group* pxTweenGroup = m_pxAnimation->CreateControllerGroup( "Tween Group", uGroupFlags, AVP_AAPC_STATES_GROUP, pxTweenGroupBlend, pxLBGroup );
#else
				Asura_AnimationController_Group* pxTweenGroup = m_pxAnimation->CreateControllerGroup( 0x7804948a, uGroupFlags, AVP_AAPC_STATES_GROUP, pxTweenGroupBlend, pxLBGroup );
#endif
				if ( pxTweenGroup != NULL )
				{
					for ( u_int uSpeedItr = 0; uSpeedItr < 2; ++ uSpeedItr )
					{
						m_auLBSpeedAACGHash[ uSpeedItr ] = ASURA_HASH_ID_UNSET;
						m_aauLBAACAHash[ uSpeedItr ][ 0 ] = ASURA_HASH_ID_UNSET;
						m_aauLBAACAHash[ uSpeedItr ][ 1 ] = ASURA_HASH_ID_UNSET;
						const AVP_Actor_AnimStateData* pxSpeedData = uSpeedItr == 0 ? pxAnimDataLBSlow : pxAnimDataLBFast;
						if ( pxSpeedData )
						{
							Asura_AnimationBlend* pxSpeedGroupBlend = new Asura_AnimationBlend( 1.0f );
#ifdef _DEBUG
							Asura_AnimationController_Group* pxSpeedGroup = m_pxAnimation->CreateControllerGroup( "Speed Group", uGroupFlags, AVP_AAPC_STATES_GROUP, pxSpeedGroupBlend, pxTweenGroup );
#else
							Asura_AnimationController_Group* pxSpeedGroup   = m_pxAnimation->CreateControllerGroup( 0x241f7966, uGroupFlags, AVP_AAPC_STATES_GROUP, pxSpeedGroupBlend, pxTweenGroup );
#endif
							if ( pxSpeedGroup != NULL )
							{
								m_auLBSpeedAACGHash[ uSpeedItr ] = pxSpeedGroup->GetControllerHash();

								if ( pxSpeedData->GetAnimHash() != ASURA_HASH_ID_UNSET ) // the directional blend anims will be set up in the blend groups in the UpdateAnimBlends function
								{
									Asura_AnimationBlend* pxLowerBlend = new Asura_AnimationBlend( 1.0f );
									Asura_Hash_ID uAACAHash = PlayAnimation( uAnimFlagsWithVel, AVP_AAPC_STATES_GROUP, pxSpeedData->GetAnimHash(), pxLowerBlend, pxSpeedGroup );

									if ( bSynch )
									{
										Asura_AnimationController_Animation* pxAACA = m_pxAnimation->GetControllerAnimationFromHash( uAACAHash );
										if ( pxAACA )
										{
											pxAACA->SetParametricTimer( fParametric, false );
										}
									}
									m_aauLBAACAHash[ uSpeedItr ][ 0 ] = uAACAHash;
								} // has anim (no blend)
							} // speed group
						} // if speed data
					} // speed loop
				} // tween group
			} // LB group
		} // valid data
	} // LB data changed

	if ( pxAnimDataUBSlow != m_pxCurrentUBSlowAnimData || pxAnimDataUBFast != m_pxCurrentUBFastAnimData || !bSynch )
	{
		m_fBoneDataUpdateTweenTimer = fTweenTime;

		m_pxCurrentUBSlowAnimData = pxAnimDataUBSlow;
		m_pxCurrentUBFastAnimData = pxAnimDataUBFast;
		m_auCurrentUBACWAnimIndex[ 0 ] = INVALID_ANIM_INDEX;
		m_auCurrentUBACWAnimIndex[ 1 ] = INVALID_ANIM_INDEX;
		m_auLastUBAnimBlendRegion[ 0 ] = INVALID_BLEND_REGION;
		m_auLastUBAnimBlendRegion[ 1 ] = INVALID_BLEND_REGION;

		if ( ( pxAnimDataUBSlow != NULL && ( pxAnimDataUBSlow->GetAnimHash() != ASURA_HASH_ID_UNSET || pxAnimDataUBSlow->GetNumBlendRegions() > 0 ) ) ||
			( pxAnimDataUBFast != NULL && ( pxAnimDataUBFast->GetAnimHash() != ASURA_HASH_ID_UNSET || pxAnimDataUBFast->GetNumBlendRegions() > 0 ) ) )
		{
			Asura_AnimationController_Group*	pxUBGroup	= m_pxAnimation->GetControllerGroupFromHash( m_uUpperBodyGroupHash );
			Asura_Assert( pxUBGroup != NULL, "Unable to get upper body group" );
			if ( pxUBGroup != NULL )
			{
				float fSecondaryParametric = 0.0f;
				if ( bSynch )
				{
					Asura_AnimationController_Animation* pxAACA = m_pxAnimation->GetControllerAnimationFromHash( m_uSecondaryTimerAACAHash );
					if ( pxAACA != NULL )
					{
						fSecondaryParametric = pxAACA->GetParametricTimer();
					}
				}

				Asura_AnimationBlend* pxTweenGroupBlend = new Asura_AnimationBlend_AnimatedTween( fTweenTime, false );
				pxTweenGroupBlend->SetDeltasUseBlends( true );
#ifdef _DEBUG
				Asura_AnimationController_Group* pxTweenGroup = m_pxAnimation->CreateControllerGroup( "Tween Group", uGroupFlags, AVP_AAPC_STATES_GROUP, pxTweenGroupBlend, pxUBGroup );
#else
				Asura_AnimationController_Group* pxTweenGroup = m_pxAnimation->CreateControllerGroup( 0x7804948a, uGroupFlags, AVP_AAPC_STATES_GROUP, pxTweenGroupBlend, pxUBGroup );
#endif
				if ( pxTweenGroup != NULL )
				{
					for ( u_int uSpeedItr = 0; uSpeedItr < 2; ++ uSpeedItr )
					{
						m_auUBSpeedAACGHash[ uSpeedItr ] = ASURA_HASH_ID_UNSET;
						m_aauUBAACAHash[ uSpeedItr ][ 0 ] = ASURA_HASH_ID_UNSET;
						m_aauUBAACAHash[ uSpeedItr ][ 1 ] = ASURA_HASH_ID_UNSET;
						const AVP_Actor_AnimStateData* pxSpeedData = uSpeedItr == 0 ? pxAnimDataUBSlow : pxAnimDataUBFast;
						if ( pxSpeedData )
						{
							Asura_AnimationBlend* pxSpeedGroupBlend = new Asura_AnimationBlend( 1.0f );
#ifdef _DEBUG
							Asura_AnimationController_Group* pxSpeedGroup = m_pxAnimation->CreateControllerGroup( "Speed Group", uGroupFlags, AVP_AAPC_STATES_GROUP, pxSpeedGroupBlend, pxTweenGroup );
#else
							Asura_AnimationController_Group* pxSpeedGroup   = m_pxAnimation->CreateControllerGroup( 0x241f7966, uGroupFlags, AVP_AAPC_STATES_GROUP, pxSpeedGroupBlend, pxTweenGroup );
#endif
							if ( pxSpeedGroup != NULL )
							{
								m_auUBSpeedAACGHash[ uSpeedItr ] = pxSpeedGroup->GetControllerHash();

								Asura_Hash_ID uAACAHash = ASURA_HASH_ID_UNSET;
								if ( pxSpeedData->GetSecondaryAnimHash() != ASURA_HASH_ID_UNSET && pxSpeedData->GetNumBlendRegions() == 0 )
								{
									Asura_AnimationBlend* pxUpperBlend = new Asura_AnimationBlend( 1.0f );
									uAACAHash = PlayAnimation( uAnimFlags, AVP_AAPC_STATES_GROUP, pxSpeedData->GetSecondaryAnimHash(), pxUpperBlend, pxSpeedGroup );
									if ( bSynch )
									{
										Asura_AnimationController_Animation* pxAACA = m_pxAnimation->GetControllerAnimationFromHash( uAACAHash );
										if ( pxAACA )
										{
											pxAACA->SetParametricTimer( fSecondaryParametric, false );
										}
									}

									m_aauUBAACAHash[ uSpeedItr ][ 0 ] = uAACAHash;
									if ( ( fBlendUBSpeed < 0.5 ? 0u : 1u ) == uSpeedItr )
									{
										m_uSecondaryTimerAACAHash = uAACAHash;
									}					
								} // has anim (& no blend)
							} // speed group
						} // if speed data
					} // speed loop
				} // tween group
			} // UB group
		} // if state data not NULL
	} // if new state data

	UpdateAnimBlends( bLoop, fOSMoveDirAngle, fTweenTime, bSynch );

	if ( m_pxBodyRig )
	{
		//Look/turn anims
		const AVP_Actor_AnimStateData* pxCurrentStateData = GetCurrentAnimStateData();
		if ( pxCurrentStateData )
		{
			m_pxBodyRig->SetAimAnimations( pxCurrentStateData->GetAimDownUpAnimHash(), pxCurrentStateData->GetAimDownUpDragAnimHash(), pxCurrentStateData->GetAimRightLeftAnimHash(), pxCurrentStateData->GetAimRightLeftDragAnimHash(), pxCurrentStateData->GetStrafeDragAnimHash(), fTweenTime );
			m_pxBodyRig->SetTurnAnimations( pxCurrentStateData->GetTurn45LeftAnimHash(), pxCurrentStateData->GetTurn45RightAnimHash(), pxCurrentStateData->GetTurn90LeftAnimHash(), pxCurrentStateData->GetTurn90RightAnimHash(), pxCurrentStateData->GetTurn180LeftAnimHash(), pxCurrentStateData->GetTurn180RightAnimHash() );
		}
		else
		{
			m_pxBodyRig->SetAimAnimations( ASURA_HASH_ID_UNSET, ASURA_HASH_ID_UNSET, ASURA_HASH_ID_UNSET, ASURA_HASH_ID_UNSET, ASURA_HASH_ID_UNSET, 0.0f );
			m_pxBodyRig->SetTurnAnimations( ASURA_HASH_ID_UNSET, ASURA_HASH_ID_UNSET, ASURA_HASH_ID_UNSET, ASURA_HASH_ID_UNSET, ASURA_HASH_ID_UNSET, ASURA_HASH_ID_UNSET );
		}
	}
}

bool AVP_Vertebrate_AnimationHandler::IsCurrentAnimStateFinished() const
{
	Asura_AnimationController_Animation* pxAACA = m_pxAnimation->GetControllerAnimationFromHash( m_uFootstepsAACAHash );
	return pxAACA == NULL || pxAACA->IsFinished();
}

void AVP_Vertebrate_AnimationHandler::PauseCurrentAnimState()
{
	for ( u_int uSpeedItr = 0; uSpeedItr < 2; ++uSpeedItr )
	{
		for ( u_int uDirItr = 0; uDirItr < 2; ++uDirItr )
		{
			if ( m_pxAnimation )
			{
				Asura_AnimationController_Animation* pxAACA = m_pxAnimation->GetControllerAnimationFromHash( m_aauLBAACAHash[ uSpeedItr ][ uDirItr ] );
			
				if ( pxAACA != NULL && pxAACA->IsPlaying() )
				{
					pxAACA->Pause();
				}
			}
		}
	}
}

void AVP_Vertebrate_AnimationHandler::PlayCurrentAnimState()
{
	for ( u_int uSpeedItr = 0; uSpeedItr < 2; ++uSpeedItr )
	{
		for ( u_int uDirItr = 0; uDirItr < 2; ++uDirItr )
		{
			if ( m_pxAnimation )
			{
				Asura_AnimationController_Animation* pxAACA = m_pxAnimation->GetControllerAnimationFromHash( m_aauLBAACAHash[ uSpeedItr ][ uDirItr ] );

				if ( pxAACA != NULL && !pxAACA->IsPlaying() )
				{
					pxAACA->Play();
				}
			}
		}
	}
}

void AVP_Vertebrate_AnimationHandler::SetCurrentAnimStateLooped( const bool bSet )
{
	for ( u_int uSpeedItr = 0; uSpeedItr < 2; ++uSpeedItr )
	{
		for ( u_int uDirItr = 0; uDirItr < 2; ++uDirItr )
		{
			if ( m_pxAnimation )
			{
				Asura_AnimationController_Animation* pxAACA = m_pxAnimation->GetControllerAnimationFromHash( m_aauLBAACAHash[ uSpeedItr ][ uDirItr ] );

				if ( pxAACA != NULL )
				{
					bool bIsStateAnim = false;
					const Asura_AnimationController_Group* pxParent = m_pxAnimation->GetControllerGroupFromHash( pxAACA->GetParentControllerHash() );
					while ( pxParent && !bIsStateAnim )
					{
						if ( pxParent->GetControllerHash() == m_uRootStatesGroupHash )
						{
							bIsStateAnim = true;
						}
						pxParent = m_pxAnimation->GetControllerGroupFromHash( pxParent->GetParentControllerHash() );
					}
					if ( bIsStateAnim )
					{
						if ( bSet )
						{
							pxAACA->SetControllerFlags( pxAACA->GetControllerFlags() | AACA_FLAG_LOOPED );
						}
						else
						{
							pxAACA->SetControllerFlags( pxAACA->GetControllerFlags() & ~AACA_FLAG_LOOPED );
						}
					}
				}
			}
		}
	}
}

bool AVP_Vertebrate_AnimationHandler::AttachWeaponToMarkerPoint( u_int uWeaponGuid, Asura_Hash_ID uMarkerPointHashID )
{
	if (!GetHandleWeaponAttachmentLocally()) { return true; }

	Asura_Message*	pxMessage	= Asura_Network::CreateMessage(ASURA_MSG_LINK_TO_PARENT, sizeof(Asura_Message_PhysicalObject_LinkToParent));

	if(pxMessage == NULL)
	{
		return(false);
	}
	else
	{
		Asura_Message_PhysicalObject_LinkToParent*	pxData	= static_cast<Asura_Message_PhysicalObject_LinkToParent*>( pxMessage->GetData() );

		// Create and send the message...
		pxData->SetParentGuid( m_uOwnerGuid);

		pxData->SetMarkerHash( uMarkerPointHashID );

		pxMessage->SetFromGuid( m_uOwnerGuid );
		pxMessage->SetToGuid( uWeaponGuid );

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

bool AVP_Vertebrate_AnimationHandler::DetachWeaponFromHand( u_int uWeaponGuid )
{
	if (!GetHandleWeaponAttachmentLocally()) { return true; }

	const AVP_ClientEntity_PhysicalObject*	pxClientWeapon	= AVP_ClientEntity_PhysicalObject::FindEntity( uWeaponGuid );
	const Asura_Guid						uParentGuid		= ( pxClientWeapon == NULL ) ? ASURA_GUID_UNREGISTERED : pxClientWeapon->GetParentGuid();

	if(uParentGuid == ASURA_GUID_UNREGISTERED)
	{
		return(false);
	}
	else
	{
		Asura_Message*	pxMessage	= Asura_Network::CreateMessage( ASURA_MSG_UNLINK_FROM_PARENT );

		if(pxMessage == NULL)
		{
			return(false);
		}
		else
		{
			// Send the message...
			pxMessage->SetFromGuid( m_uOwnerGuid );
			pxMessage->SetToGuid( uWeaponGuid );

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

bool AVP_Vertebrate_AnimationHandler::GetHandleWeaponAttachmentLocally() const
{
	const AVP_ClientEntity_Actor* pxOwner = AVP_ClientEntity_Actor::FindEntity(m_uOwnerGuid);

	if (!pxOwner) { return false; }

	// AIs only send from the server
	if (pxOwner->IsAIActor()) { return AVP_Settings::GetIsServer(); }

	// Players only send if they're local
	return AVP_Game_Core::IsLocalPlayer(m_uOwnerGuid);
}

u_int AVP_Vertebrate_AnimationHandler::GetFootStepFlags() const
{
	const Asura_AnimationController_Animation* pxAACA = m_pxAnimation->GetControllerAnimationFromHash( m_uFootstepsAACAHash );
	if ( pxAACA != NULL )
	{
		return pxAACA->GetFlagsForLastFrame();
	}
	else
	{
		Asura_Assert( false, "Unable to get anim for footstep flags" );
		return 0;
	}
}

void AVP_Vertebrate_AnimationHandler::UpdateAnimBlends( const bool bLoop, const float fOSMoveDirAngle, const float fNewStateTweenTime, const bool bNewStateSync )
{
	// Set speed blends
	Asura_AnimationController_Group* pxLBSlowSpeedAACG = m_pxAnimation->GetControllerGroupFromHash( m_auLBSpeedAACGHash[ 0 ] );
	if ( pxLBSlowSpeedAACG != NULL )
	{
		Asura_AnimationBlend* pxBlend = pxLBSlowSpeedAACG->GetBlendToNextController();
		if ( pxBlend != NULL )
		{
			pxBlend->SetWeight( m_fCurrentLBBlendSpeed );
		}
	}
	Asura_AnimationController_Group* pxUBSlowSpeedAACG = m_pxAnimation->GetControllerGroupFromHash( m_auUBSpeedAACGHash[ 0 ] );
	if ( pxUBSlowSpeedAACG != NULL )
	{
		Asura_AnimationBlend* pxBlend = pxUBSlowSpeedAACG->GetBlendToNextController();
		if ( pxBlend != NULL )
		{
			pxBlend->SetWeight( m_fCurrentUBBlendSpeed );
		}
	}

	u_int uBestSpeed = m_fCurrentLBBlendSpeed > 0.5f ? 1 : 0;
	u_int uBestDir = 0;
	for ( u_int uSpeedItr = 0; uSpeedItr < 2; ++ uSpeedItr )
	{
		const AVP_Actor_AnimStateData* pxSpeedData = uSpeedItr == 0 ? m_pxCurrentLBSlowAnimData : m_pxCurrentLBFastAnimData;
		if ( pxSpeedData )
		{
			if ( pxSpeedData->GetNumBlendRegions() > 0 )
			{
				u_int uNumInRegions = 0;
				int iCurrentRegionIndex = -1;
				int iLastRegionIndex = -1;
				int aiInRegion[ 2 ];
				int aiInRegionACWAnimIndex[ 2 ];
				float afInRegionParametric[ 2 ];
				bool bInTweenRegion[ 2 ];
		
				for ( u_int uRegionIndexItr = 0; uRegionIndexItr < 2; ++uRegionIndexItr )
				{
					aiInRegion[ uRegionIndexItr ] = INVALID_BLEND_REGION;
					aiInRegionACWAnimIndex[ uRegionIndexItr ] = INVALID_ANIM_INDEX;
					afInRegionParametric[ uRegionIndexItr ] = 0.0f;
					bInTweenRegion[ uRegionIndexItr ] = false;
				}

				for ( u_int uRegionItr = 0; uRegionItr < pxSpeedData->GetNumBlendRegions() && uNumInRegions < 2; ++uRegionItr )
				{
					const AVP_Actor_AnimStateData_Directional_Region* pxRegion = pxSpeedData->GetBlendRegion( uRegionItr );
					if ( pxRegion && pxRegion->GetNumAnims() > 1 && AVP_Maths::IsAngleInRange( fOSMoveDirAngle, pxRegion->GetAngle( 0 ), pxRegion->GetAngle( pxRegion->GetNumAnims() - 1 ) ) )
					{
						bInTweenRegion[ uRegionItr ] = AVP_Maths::IsAngleInRange( fOSMoveDirAngle, pxRegion->GetTweenAngleACW(), pxRegion->GetTweenAngleCW() );
						if ( m_auCurrentLBAnimBlendRegion[ uSpeedItr ] == uRegionItr )
						{
							iCurrentRegionIndex = uNumInRegions;
						}
						if ( m_auLastLBAnimBlendRegion[ uSpeedItr ] == uRegionItr )
						{
							iLastRegionIndex = uNumInRegions;
						}
						aiInRegion[ uNumInRegions ] = uRegionItr;
						for ( u_int uAnimItr = 1; uAnimItr < pxRegion->GetNumAnims(); ++uAnimItr )
						{
							float fParametric;
							if ( AVP_Maths::IsAngleInRange( fOSMoveDirAngle, pxRegion->GetAngle( uAnimItr - 1 ), pxRegion->GetAngle( uAnimItr ), &fParametric ) )
							{
								aiInRegionACWAnimIndex[ uNumInRegions ] = uAnimItr - 1;
								afInRegionParametric[ uNumInRegions ] = fParametric;
							}
						}
						++uNumInRegions;
					}
				}
	 
				Asura_Assert( !( bInTweenRegion[ 0 ] && bInTweenRegion[ 1 ] ), "Can't be in two tween regions at one, error in movement template blueprint" );
				Asura_Assertf( uNumInRegions == 1 || uNumInRegions == 2, ( "Blended Anim Movement Blueprint not set up correctly for entity %d we have %d valid regions at angle %.0f", m_uOwnerGuid, uNumInRegions, Asura_Maths::RadToDeg( fOSMoveDirAngle ) ) );
				bool bTweenFlagPresent = true; // TODO
				u_int uNextRegionIndex = 0;
				bool bTweenNow = false;
				float fTweenTime = dbg_fDefaultDirectionBlendTweenTime;
				if ( iCurrentRegionIndex < 0 ) // if we're no longer in our current region then we must tween over to the new one
				{
					bTweenNow = true;
					if ( uNumInRegions == 2 && ( iLastRegionIndex == 0 || bInTweenRegion[ 1 ] ) ) // if we were previously in region 0 or we're in the tween region of region 1 then pick region 1.
					{
						uNextRegionIndex = 1;
					}
				}
				else if ( uNumInRegions == 2 && bInTweenRegion[ 1 - iCurrentRegionIndex ] && bTweenFlagPresent ) // else if we're in two regions and in the tween region of the non-current region and the tween flag is present.
				{
					bTweenNow = true;
					uNextRegionIndex = 1 - iCurrentRegionIndex;
				}
				else if ( m_auCurrentLBACWAnimIndex[ uSpeedItr ] != static_cast< u_int >( aiInRegionACWAnimIndex[ iCurrentRegionIndex ] ) ) // else if we're not playing the correct anims for the current region.
				{
					bTweenNow = true;
					uNextRegionIndex = iCurrentRegionIndex;
					fTweenTime = 0.0f;
				}

				if ( m_auCurrentLBACWAnimIndex[ uSpeedItr ] == INVALID_ANIM_INDEX ) // we're starting a new state
				{
					fTweenTime = fNewStateTweenTime;
					if ( !bNewStateSync )
					{
						m_uFootstepsAACAHash = ASURA_HASH_ID_UNSET; // NULL this down so that we don't sync to the old anims
					}
				}

				if ( bTweenNow )
				{
					const AVP_Actor_AnimStateData_Directional_Region* pxRegion = pxSpeedData->GetBlendRegion( aiInRegion[ uNextRegionIndex ] );
					const AVP_Actor_AnimStateData* pxAnimDataACW = pxRegion != NULL ? pxRegion->GetAnimData( Asura_Maths::Max( aiInRegionACWAnimIndex[ uNextRegionIndex ], 0 ) ) : NULL; // if the region only has 1 anim then aiInRegionACWAnimIndex[ uNextRegionIndex ] will be -1 so we'll use anim 0 for both anims here
					const AVP_Actor_AnimStateData* pxAnimDataCW = pxRegion != NULL ? pxRegion->GetAnimData( aiInRegionACWAnimIndex[ uNextRegionIndex ] + 1 ) : NULL;

					m_afCurrentLBBlendDir[ uSpeedItr ] = afInRegionParametric[ uNextRegionIndex ];
					m_auLastLBAnimBlendRegion[ uSpeedItr ] = m_auCurrentLBAnimBlendRegion[ uSpeedItr ];
					m_auCurrentLBAnimBlendRegion[ uSpeedItr ] = aiInRegion[ uNextRegionIndex ];
					m_auCurrentLBACWAnimIndex[ uSpeedItr ] = aiInRegionACWAnimIndex[ uNextRegionIndex ];
					TweenToNewLBDirectionalAnimGroup( pxAnimDataACW, pxAnimDataCW, uSpeedItr, afInRegionParametric[ uNextRegionIndex ], bLoop, fTweenTime );
					if ( uSpeedItr == uBestSpeed )
					{
						uBestDir = afInRegionParametric[ uNextRegionIndex ] > 0.5f ? 1 : 0;
						m_uSecondaryTimerAACAHash = m_aauUBAACAHash[ uSpeedItr ][ uBestDir ];
					}
				}
				else
				{
					Asura_AnimationController_Animation* pxLBAACA = m_pxAnimation->GetControllerAnimationFromHash( m_aauLBAACAHash[ uSpeedItr ][ 0 ] );
					if ( pxLBAACA != NULL )
					{
						Asura_AnimationBlend* pxBlend = pxLBAACA->GetBlendToNextController();
						if ( pxBlend != NULL )
						{
							pxBlend->SetWeight( afInRegionParametric[ iCurrentRegionIndex ] );
							m_afCurrentLBBlendDir[ uSpeedItr ] = afInRegionParametric[ iCurrentRegionIndex ];
						}
					}
					if ( uSpeedItr == uBestSpeed )
					{
						uBestDir = afInRegionParametric[ iCurrentRegionIndex ] > 0.5f ? 1 : 0;
					}
					
					// Maybe update the blend on the old (tweening out) state anims? See how we go.
				}
			} // if speed data has blend regions
		}
		else // no speed data
		{
			for ( u_int uDirItr = 0; uDirItr < 2; ++uDirItr )
			{
				m_aauLBAACAHash[ uSpeedItr ][ uDirItr ] = ASURA_HASH_ID_UNSET;
			}
		}

		pxSpeedData = uSpeedItr == 0 ? m_pxCurrentUBSlowAnimData : m_pxCurrentUBFastAnimData;
		if ( pxSpeedData )
		{
			if ( pxSpeedData->GetNumBlendRegions() > 0 )
			{
				u_int uNumInRegions = 0;
				int iCurrentRegionIndex = -1;
				int iLastRegionIndex = -1;
				int aiInRegion[ 2 ];
				int aiInRegionACWAnimIndex[ 2 ];
				float afInRegionParametric[ 2 ];
				bool bInTweenRegion[ 2 ];

				for ( u_int uRegionIndexItr = 0; uRegionIndexItr < 2; ++uRegionIndexItr )
				{
					aiInRegion[ uRegionIndexItr ] = INVALID_BLEND_REGION;
					aiInRegionACWAnimIndex[ uRegionIndexItr ] = INVALID_ANIM_INDEX;
					afInRegionParametric[ uRegionIndexItr ] = 0.0f;
					bInTweenRegion[ uRegionIndexItr ] = false;
				}

				for ( u_int uRegionItr = 0; uRegionItr < pxSpeedData->GetNumBlendRegions() && uNumInRegions < 2; ++uRegionItr )
				{
					const AVP_Actor_AnimStateData_Directional_Region* pxRegion = pxSpeedData->GetBlendRegion( uRegionItr );
					if ( pxRegion && pxRegion->GetNumAnims() > 1 && AVP_Maths::IsAngleInRange( fOSMoveDirAngle, pxRegion->GetAngle( 0 ), pxRegion->GetAngle( pxRegion->GetNumAnims() - 1 ) ) )
					{
						bInTweenRegion[ uRegionItr ] = AVP_Maths::IsAngleInRange( fOSMoveDirAngle, pxRegion->GetTweenAngleACW(), pxRegion->GetTweenAngleCW() );
						if ( m_auCurrentUBAnimBlendRegion[ uSpeedItr ] == uRegionItr )
						{
							iCurrentRegionIndex = uNumInRegions;
						}
						if ( m_auLastUBAnimBlendRegion[ uSpeedItr ] == uRegionItr )
						{
							iLastRegionIndex = uNumInRegions;
						}
						aiInRegion[ uNumInRegions ] = uRegionItr;
						for ( u_int uAnimItr = 1; uAnimItr < pxRegion->GetNumAnims(); ++uAnimItr )
						{
							float fParametric;
							if ( AVP_Maths::IsAngleInRange( fOSMoveDirAngle, pxRegion->GetAngle( uAnimItr - 1 ), pxRegion->GetAngle( uAnimItr ), &fParametric ) )
							{
								aiInRegionACWAnimIndex[ uNumInRegions ] = uAnimItr - 1;
								afInRegionParametric[ uNumInRegions ] = fParametric;
							}
						}
						++uNumInRegions;
					}
				}

				Asura_Assert( !( bInTweenRegion[ 0 ] && bInTweenRegion[ 1 ] ), "Can't be in two tween regions at one, error in movement template blueprint" );
				Asura_Assertf( uNumInRegions == 1 || uNumInRegions == 2, ( "Blended Anim Movement Blueprint not set up correctly for entity %d we have %d valid regions at angle %.0f", m_uOwnerGuid, uNumInRegions, Asura_Maths::RadToDeg( fOSMoveDirAngle ) ) );
				bool bTweenFlagPresent = true; // TODO
				u_int uNextRegionIndex = 0;
				bool bTweenNow = false;
				float fTweenTime = dbg_fDefaultDirectionBlendTweenTime;
				if ( iCurrentRegionIndex < 0 ) // if we're no longer in our current region then we must tween over to the new one
				{
					bTweenNow = true;
					if ( uNumInRegions == 2 && ( iLastRegionIndex == 0 || bInTweenRegion[ 1 ] ) ) // if we were previously in region 0 or we're in the tween region of region 1 then pick region 1.
					{
						uNextRegionIndex = 1;
					}
				}
				else if ( uNumInRegions == 2 && bInTweenRegion[ 1 - iCurrentRegionIndex ] && bTweenFlagPresent ) // else if we're in two regions and in the tween region of the non-current region and the tween flag is present.
				{
					bTweenNow = true;
					uNextRegionIndex = 1 - iCurrentRegionIndex;
				}
				else if ( m_auCurrentUBACWAnimIndex[ uSpeedItr ] != static_cast< u_int >( aiInRegionACWAnimIndex[ iCurrentRegionIndex ] ) ) // else if we're not playing the correct anims for the current region.
				{
					bTweenNow = true;
					uNextRegionIndex = iCurrentRegionIndex;
					fTweenTime = 0.0f;
				}

				if ( m_auCurrentUBACWAnimIndex[ uSpeedItr ] == INVALID_ANIM_INDEX ) // we're starting a new state
				{
					fTweenTime = fNewStateTweenTime;
					if ( !bNewStateSync )
					{
						m_uFootstepsAACAHash = ASURA_HASH_ID_UNSET; // NULL this down so that we don't sync to the old anims
					}
				}

				if ( bTweenNow )
				{
					const AVP_Actor_AnimStateData_Directional_Region* pxRegion = pxSpeedData->GetBlendRegion( aiInRegion[ uNextRegionIndex ] );
					const AVP_Actor_AnimStateData* pxAnimDataACW = pxRegion != NULL ? pxRegion->GetAnimData( Asura_Maths::Max( aiInRegionACWAnimIndex[ uNextRegionIndex ], 0 ) ) : NULL; // if the region only has 1 anim then aiInRegionACWAnimIndex[ uNextRegionIndex ] will be -1 so we'll use anim 0 for both anims here
					const AVP_Actor_AnimStateData* pxAnimDataCW = pxRegion != NULL ? pxRegion->GetAnimData( aiInRegionACWAnimIndex[ uNextRegionIndex ] + 1 ) : NULL;

					m_afCurrentUBBlendDir[ uSpeedItr ] = afInRegionParametric[ uNextRegionIndex ];
					m_auLastUBAnimBlendRegion[ uSpeedItr ] = m_auCurrentUBAnimBlendRegion[ uSpeedItr ];
					m_auCurrentUBAnimBlendRegion[ uSpeedItr ] = aiInRegion[ uNextRegionIndex ];
					m_auCurrentUBACWAnimIndex[ uSpeedItr ] = aiInRegionACWAnimIndex[ uNextRegionIndex ];
					TweenToNewUBDirectionalAnimGroup( pxAnimDataACW, pxAnimDataCW, uSpeedItr, afInRegionParametric[ uNextRegionIndex ], bLoop, fTweenTime );
					if ( uSpeedItr == uBestSpeed )
					{
						uBestDir = afInRegionParametric[ uNextRegionIndex ] > 0.5f ? 1 : 0;
						m_uSecondaryTimerAACAHash = m_aauUBAACAHash[ uSpeedItr ][ uBestDir ];
					}
				}
				else
				{
					Asura_AnimationController_Animation* pxUBAACA = m_pxAnimation->GetControllerAnimationFromHash( m_aauUBAACAHash[ uSpeedItr ][ 0 ] );
					if ( pxUBAACA != NULL )
					{
						Asura_AnimationBlend* pxBlend = pxUBAACA->GetBlendToNextController();
						if ( pxBlend != NULL )
						{
							pxBlend->SetWeight( afInRegionParametric[ iCurrentRegionIndex ] );
							m_afCurrentUBBlendDir[ uSpeedItr ] = afInRegionParametric[ iCurrentRegionIndex ];
						}
					}
					// Maybe update the blend on the old (tweening out) state anims? See how we go.
				}
			} // if speed data has blend regions
		}
		else // no speed data
		{
			for ( u_int uDirItr = 0; uDirItr < 2; ++uDirItr )
			{
				m_aauUBAACAHash[ uSpeedItr ][ uDirItr ] = ASURA_HASH_ID_UNSET;
			}
		}
	} // for speed

	m_uFootstepsAACAHash = m_aauLBAACAHash[ uBestSpeed ][ uBestDir ];

	// mess with the durations
	float fSlowDuration = -1.0f;
	if ( m_pxCurrentLBSlowAnimData != NULL )
	{
		if ( m_pxCurrentLBSlowAnimData->GetAnimHash() != ASURA_HASH_ID_UNSET )
		{
			Asura_Hierarchy_Anim* pxSlowHA = Asura_Animation_System::GetAnimByHash( m_pxCurrentLBSlowAnimData->GetAnimHash() );
			fSlowDuration = pxSlowHA != NULL ? pxSlowHA->GetDuration() : -1.0f;
		}
		else
		{
			const Asura_AnimationController_Animation* pxSlowACWAACA = m_pxAnimation->GetControllerAnimationFromHash( m_aauLBAACAHash[ 0 ][ 0 ] );
			const Asura_Hierarchy_Anim* pxSlowACWAnim = pxSlowACWAACA ? pxSlowACWAACA->GetAnimation() : NULL;
			float fSlowACWDuration = pxSlowACWAnim != NULL ? pxSlowACWAnim->GetDuration() : -1.0f;

			const Asura_AnimationController_Animation* pxSlowCWAACA = m_pxAnimation->GetControllerAnimationFromHash( m_aauLBAACAHash[ 0 ][ 1 ] );
			const Asura_Hierarchy_Anim* pxSlowCWAnim = pxSlowCWAACA ? pxSlowCWAACA->GetAnimation() : NULL;
			float fSlowCWDuration = pxSlowCWAnim != NULL ? pxSlowCWAnim->GetDuration() : -1.0f;

			fSlowDuration = fSlowACWDuration < 0.0f ? fSlowCWDuration : ( fSlowCWDuration < 0.0f ? fSlowACWDuration : Asura_Maths::Lerp( fSlowACWDuration, fSlowCWDuration, m_afCurrentLBBlendDir[ 0 ] ) );
		}
	}

	float fFastDuration = -1.0f;
	if ( m_pxCurrentLBFastAnimData != NULL )
	{
		if ( m_pxCurrentLBFastAnimData->GetAnimHash() != ASURA_HASH_ID_UNSET )
		{
			Asura_Hierarchy_Anim* pxFastHA = Asura_Animation_System::GetAnimByHash( m_pxCurrentLBFastAnimData->GetAnimHash() );
			fFastDuration = pxFastHA != NULL ? pxFastHA->GetDuration() : -1.0f;
		}
		else
		{
			const Asura_AnimationController_Animation* pxFastACWAACA = m_pxAnimation->GetControllerAnimationFromHash( m_aauLBAACAHash[ 1 ][ 0 ] );
			const Asura_Hierarchy_Anim* pxFastACWAnim = pxFastACWAACA ? pxFastACWAACA->GetAnimation() : NULL;
			float fFastACWDuration = pxFastACWAnim != NULL ? pxFastACWAnim->GetDuration() : -1.0f;

			const Asura_AnimationController_Animation* pxFastCWAACA = m_pxAnimation->GetControllerAnimationFromHash( m_aauLBAACAHash[ 1 ][ 1 ] );
			const Asura_Hierarchy_Anim* pxFastCWAnim = pxFastCWAACA ? pxFastCWAACA->GetAnimation() : NULL;
			float fFastCWDuration = pxFastCWAnim != NULL ? pxFastCWAnim->GetDuration() : -1.0f;

			fFastDuration = fFastACWDuration < 0.0f ? fFastCWDuration : ( fFastCWDuration < 0.0f ? fFastACWDuration : Asura_Maths::Lerp( fFastACWDuration, fFastCWDuration, m_afCurrentLBBlendDir[ 1 ] ) );
		}
	}

	float fDuration = fSlowDuration < 0.0f ? fFastDuration : ( fFastDuration < 0.0f ? fSlowDuration : Asura_Maths::Lerp( fSlowDuration, fFastDuration, m_fCurrentLBBlendSpeed ) );
	Asura_Assert( fDuration >= 0.0f, "Couldn't get a good duration" );

	fDuration *= m_xOwnerActor.GetAnimDurationModifier();

	for ( u_int uSpeedItr = 0; uSpeedItr < 2; ++uSpeedItr )
	{
		for ( u_int uDirItr = 0; uDirItr < 2; ++uDirItr )
		{
			Asura_AnimationController_Animation* pxAACA = m_pxAnimation->GetControllerAnimationFromHash( m_aauUBAACAHash[ uSpeedItr ][ uDirItr ] );
			if ( pxAACA != NULL )
			{
				if ( fDuration >= 0.0f )
				{
					pxAACA->SetDuration( fDuration );
				}
			}
			pxAACA = m_pxAnimation->GetControllerAnimationFromHash( m_aauLBAACAHash[ uSpeedItr ][ uDirItr ] );
			if ( pxAACA != NULL )
			{
				if ( fDuration >= 0.0f )
				{
					pxAACA->SetDuration( fDuration );
				}
			}
		}
	}
}

void AVP_Vertebrate_AnimationHandler::TweenToNewLBDirectionalAnimGroup( const AVP_Actor_AnimStateData* pxAnimDataACW, const AVP_Actor_AnimStateData* pxAnimDataCW, const u_int uSpeedItr, const float fDirBlendWeight, const bool bLoop, const float fTweenTime )
{
	u_int uGroupFlags = AAC_FLAG_ACTIVE | AAC_FLAG_ENSURE_UNIQUE_HASH | AAC_FLAG_DESTROY_WHEN_FINISHED;
	u_int uAnimFlags = AAC_FLAG_ACTIVE | AAC_FLAG_ENSURE_UNIQUE_HASH | AAC_FLAG_ALLOW_DESTROY_FROM_MESSAGE;
	if ( bLoop )
	{
		uAnimFlags |= AACA_FLAG_LOOPED;
	}
	u_int uAnimFlagsWithVel = uAnimFlags | AACA_FLAG_CALCULATE_VELOCITY | AACA_FLAG_CALCULATE_ANGULAR_VELOCITY | AACA_FLAG_CALCULATE_MOVEMENT_OFFSET;

	float fParametric = 0.0f;
	Asura_AnimationController_Animation* pxAACA = m_pxAnimation->GetControllerAnimationFromHash( m_uFootstepsAACAHash );
	if ( pxAACA != NULL )
	{
		fParametric = pxAACA->GetParametricTimer();
	}

	for ( u_int uDirItr = 0; uDirItr < 2; ++uDirItr )
	{
		m_aauLBAACAHash[ uSpeedItr ][ uDirItr ] = ASURA_HASH_ID_UNSET;
	}

	Asura_AnimationController_Group* pxLBSpeedAACG = m_pxAnimation->GetControllerGroupFromHash( m_auLBSpeedAACGHash[ uSpeedItr ] );
	if ( pxLBSpeedAACG )
	{
		Asura_AnimationBlend_AnimatedTween* pxDirGroupBlend = new Asura_AnimationBlend_AnimatedTween( fTweenTime, false );
#ifdef _DEBUG
		Asura_AnimationController_Group* pxDirGroup = m_pxAnimation->CreateControllerGroup( "Direction Group", uGroupFlags, AVP_AAPC_STATES_GROUP, pxDirGroupBlend, pxLBSpeedAACG );
#else
		Asura_AnimationController_Group* pxDirGroup = m_pxAnimation->CreateControllerGroup( 0x14f2369e, uGroupFlags, AVP_AAPC_STATES_GROUP, pxDirGroupBlend, pxLBSpeedAACG );
#endif
		if ( pxDirGroup != NULL )
		{
			for ( u_int uDirItr = 0; uDirItr < 2; ++ uDirItr )
			{
				Asura_AnimationBlend* pxDirBlend = new Asura_AnimationBlend( uDirItr == 0 ? 1.0f : fDirBlendWeight );
				if ( uDirItr == 1 )
				{
					pxDirBlend->SetSlerpVelocity( true );
				}

				Asura_Hash_ID uAnimHash = ASURA_HASH_ID_UNSET;
				if ( uDirItr == 0 )
				{
					if ( pxAnimDataACW != NULL )
					{
						uAnimHash = pxAnimDataACW->GetAnimHash();
					}
				}
				else
				{
					if ( pxAnimDataCW != NULL )
					{
						uAnimHash = pxAnimDataCW->GetAnimHash();
					}
				}
				Asura_Hash_ID uAACAHash = PlayAnimation( uAnimFlagsWithVel, AVP_AAPC_STATES_GROUP, uAnimHash, pxDirBlend, pxDirGroup );

				Asura_AnimationController_Animation* pxAACA = m_pxAnimation->GetControllerAnimationFromHash( uAACAHash );
				if ( pxAACA )
				{
					pxAACA->SetParametricTimer( fParametric, false );
				}
				m_aauLBAACAHash[ uSpeedItr ][ uDirItr ] = uAACAHash;
			} // dir loop
		} // dir group
	} // LB speed group
}

void AVP_Vertebrate_AnimationHandler::TweenToNewUBDirectionalAnimGroup( const AVP_Actor_AnimStateData* pxAnimDataACW, const AVP_Actor_AnimStateData* pxAnimDataCW, const u_int uSpeedItr, const float fDirBlendWeight, const bool bLoop, const float fTweenTime )
{
	u_int uGroupFlags = AAC_FLAG_ACTIVE | AAC_FLAG_ENSURE_UNIQUE_HASH | AAC_FLAG_DESTROY_WHEN_FINISHED;
	u_int uAnimFlags = AAC_FLAG_ACTIVE | AAC_FLAG_ENSURE_UNIQUE_HASH | AAC_FLAG_ALLOW_DESTROY_FROM_MESSAGE;
	if ( bLoop )
	{
		uAnimFlags |= AACA_FLAG_LOOPED;
	}

	float fSecondaryParametric = 0.0f;
	Asura_AnimationController_Animation* pxAACA = m_pxAnimation->GetControllerAnimationFromHash( m_uSecondaryTimerAACAHash );
	if ( pxAACA != NULL )
	{
		fSecondaryParametric = pxAACA->GetParametricTimer();
	}

	for ( u_int uDirItr = 0; uDirItr < 2; ++uDirItr )
	{
		m_aauUBAACAHash[ uSpeedItr ][ uDirItr ] = ASURA_HASH_ID_UNSET;
	}

	Asura_AnimationController_Group* pxUBSpeedAACG = m_pxAnimation->GetControllerGroupFromHash( m_auUBSpeedAACGHash[ uSpeedItr ] );
	if ( pxUBSpeedAACG )
	{
		Asura_AnimationBlend_AnimatedTween* pxDirGroupBlend = new Asura_AnimationBlend_AnimatedTween( fTweenTime, false );
#ifdef _DEBUG
		Asura_AnimationController_Group* pxDirGroup = m_pxAnimation->CreateControllerGroup( "Direction Group", uGroupFlags, AVP_AAPC_STATES_GROUP, pxDirGroupBlend, pxUBSpeedAACG );
#else
		Asura_AnimationController_Group* pxDirGroup = m_pxAnimation->CreateControllerGroup( 0x14f2369e, uGroupFlags, AVP_AAPC_STATES_GROUP, pxDirGroupBlend, pxUBSpeedAACG );
#endif
		if ( pxDirGroup != NULL )
		{
			for ( u_int uDirItr = 0; uDirItr < 2; ++ uDirItr )
			{
				Asura_AnimationBlend* pxDirBlend = new Asura_AnimationBlend( uDirItr == 0 ? 1.0f : fDirBlendWeight );

				Asura_Hash_ID uAnimHash = ASURA_HASH_ID_UNSET;
				if ( uDirItr == 0 )
				{
					if ( pxAnimDataACW != NULL )
					{
						uAnimHash = pxAnimDataACW->GetSecondaryAnimHash();
					}
				}
				else
				{
					if ( pxAnimDataCW != NULL )
					{
						uAnimHash = pxAnimDataCW->GetSecondaryAnimHash();
					}
				}
				Asura_Hash_ID uSecondaryAACAHash = PlayAnimation( uAnimFlags, AVP_AAPC_STATES_GROUP, uAnimHash, pxDirBlend, pxDirGroup );

				Asura_AnimationController_Animation* pxAACA = m_pxAnimation->GetControllerAnimationFromHash( uSecondaryAACAHash );
				if ( pxAACA )
				{
					pxAACA->SetParametricTimer( fSecondaryParametric, false );
				}
				m_aauUBAACAHash[ uSpeedItr ][ uDirItr ] = uSecondaryAACAHash;
			} // dir loop
		} // dir group
	} // UB speed group
}

const AVP_Actor_AnimStateData* AVP_Vertebrate_AnimationHandler::GetCurrentAnimStateData() const
{
	const AVP_Actor_AnimStateData* pxBestData = m_fCurrentLBBlendSpeed < 0.5f ? m_pxCurrentLBSlowAnimData : m_pxCurrentLBFastAnimData;
	if ( pxBestData && pxBestData->GetNumBlendRegions() > 0 )
	{
		const u_int uBlendRegionIndex = m_auCurrentLBAnimBlendRegion[ m_fCurrentLBBlendSpeed < 0.5f ? 0 : 1 ];
		if ( uBlendRegionIndex < AVP_ACTOR_ANIMSTATEDATA_MAX_BLEND_REGIONS )
		{
			const AVP_Actor_AnimStateData_Directional_Region* pxBestRegion = pxBestData->GetBlendRegion( uBlendRegionIndex );
			if ( pxBestRegion )
			{
				const u_int uAnimIndex = m_auCurrentLBACWAnimIndex[ m_fCurrentLBBlendSpeed < 0.5f ? 0 : 1 ];
				if ( uAnimIndex < AVP_ACTOR_ANIMSTATEDATA_MAX_ANIMS_PER_REGION )
				{
					pxBestData = pxBestRegion->GetAnimData( uAnimIndex + ( m_afCurrentLBBlendDir[ m_fCurrentLBBlendSpeed < 0.5f ? 0 : 1 ] < 0.5f ? 0 : 1 ) );
				}
			}
		}
	}

	return pxBestData;
}

void AVP_Vertebrate_AnimationHandler::ShutdownIKSolver()
{
	DisableIKSolver();
	m_xIKSolver.Shutdown();
}

// AJK 17-12-08
void AVP_Vertebrate_AnimationHandler::SetRagDollAsleep()  const
{
	Asura_Ragdoll_Wrapper* pxRagdollWrapper = Asura_Ragdoll_Wrapper::GetRagdollWrapper( m_iRagdollHandle );
	Asura_Physics_Ragdoll* pxRagdoll = NULL;
	if ( pxRagdollWrapper )
	{
		pxRagdoll = pxRagdollWrapper->GetPhysicsRagdollPointer();
		if(pxRagdoll)
		{
			pxRagdoll->Sleep();
		}
	}
}

void AVP_Vertebrate_AnimationHandler::WakeUpRagDoll()  const
{
	Asura_Ragdoll_Wrapper* pxRagdollWrapper = Asura_Ragdoll_Wrapper::GetRagdollWrapper( m_iRagdollHandle );
	Asura_Physics_Ragdoll* pxRagdoll = NULL;
	if ( pxRagdollWrapper )
	{
		pxRagdoll = pxRagdollWrapper->GetPhysicsRagdollPointer();
		if(pxRagdoll)
		{
			pxRagdoll->WakeUp();
		}
	}
}

bool AVP_Vertebrate_AnimationHandler::IsRagDollAsleep()  const
{
	Asura_Ragdoll_Wrapper* pxRagdollWrapper = Asura_Ragdoll_Wrapper::GetRagdollWrapper( m_iRagdollHandle );
	Asura_Physics_Ragdoll* pxRagdoll = NULL;
	if ( pxRagdollWrapper )
	{
		pxRagdoll = pxRagdollWrapper->GetPhysicsRagdollPointer();
		if(pxRagdoll)
		{
			return pxRagdoll->IsAsleep();
		}
	}
	return false;
}

/// End of file. ///
