
///////////////////////////////////////////////////////////////////////////////
//
// File   : AVP_Actor_AttachmentHandler.cpp
// Author : Steven Lovesey
// Date   : 6-Apr-2009
//
// Copyright (C) 2009 Rebellion, All Rights Reserved.
//
///////////////////////////////////////////////////////////////////////////////

///////////////////
// Include files //
///////////////////

#include "Core/Asura.h"

// This file's header.
#include "Actor/AVP_Actor_AttachmentHandler.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 "Blueprint/Asura_Blueprint_System.h"
#include "Network/Asura_Message.h"

// Game independent header-files.
#include "Actor/AVP_Actor_AnimationHandler.h"
#include "Blueprint/AVP_Blueprint_HashIDs.h"
#include "Entity/AVP_Messages.h"
#include "Entity/PhysicalObject/Actor/AVP_Actor_Messages.h"
#include "Entity/PhysicalObject/Actor/AVP_ClientEntity_Actor.h"
#include "Entity/PhysicalObject/PhysicsObject/AVP_ServerEntity_PhysicsObject.h"
#include "Entity/Damage/AVP_EntityDamage_Apply.h"
///////////////////////////
// Constant declarations //
///////////////////////////


////////////////////////////////////////
// Static class variable declarations //
////////////////////////////////////////

/////////////////////
// Class functions //
/////////////////////

//////////////////////
// Actor_Attachment //
//////////////////////

//////////////////////////////
// Construction/Destruction //
//////////////////////////////

AVP_Actor_Attachment::AVP_Actor_Attachment()
	: m_uSkinID( ASURA_HASH_ID_UNSET )
	, m_uAnimID( ASURA_HASH_ID_UNSET )
	, m_uMarkerID( ASURA_HASH_ID_UNSET )
	, m_uPhysicsBlueprint( ASURA_HASH_ID_UNSET )
	, m_fChanceOfDropOnDeath( 0.f )
	, m_fChanceOfDropOnHeadbit( 0.f )
	, m_bDropOnDecapitation( false )
	, m_bDeleteOnDeath( false )
	, m_bDeleteOnHeadbit( false )
	, m_bDeleteOnDecapitation( false )
	, m_bUpdateFirstFrame( true )
	, m_fAnimationStartTimeParametric( 0.f )
	, m_pxAnimation( NULL )
{
}

AVP_Actor_Attachment::~AVP_Actor_Attachment()
{
}

/////////////////////////////////
// AVP_Actor_AttachmentHandler //
/////////////////////////////////

//////////////////////////////
// Construction/Destruction //
//////////////////////////////

AVP_Actor_AttachmentHandler::AVP_Actor_AttachmentHandler(AVP_ClientEntity_Actor* pxOwner)
: m_uNumAttachments( 0 )
, m_uAttachmentStorage( 0 )
, m_pxAttachments( NULL ) 
, m_pxOwner(pxOwner)
, m_xLastDamageImpulse( Asura_Maths::ZeroVector3 )
{

}


AVP_Actor_AttachmentHandler::~AVP_Actor_AttachmentHandler()
{
	DestroyAllAttachments();
	Asura_Deallocate(m_pxAttachments);
}



///////////////////////////////
// Public Interface functions //
////////////////////////////////

bool AVP_Actor_AttachmentHandler::AddAttachment( const AVP_Actor_Attachment& xAttachment)
{
	DestroyAttachment(xAttachment.m_uSkinID);

	u_int uNewSize = m_uNumAttachments + 1;

	if (uNewSize > m_uAttachmentStorage)
	{
		AVP_Actor_Attachment* pxNewArray = static_cast<AVP_Actor_Attachment*> (Asura_Allocate(uNewSize * sizeof(AVP_Actor_Attachment)));
		if (!pxNewArray)
		{
			Asura_Assert(0, "Failed to allocate attachment, out of memory?");
			return false;
		}
		Asura_Mem::Copy(pxNewArray, m_pxAttachments, m_uNumAttachments * sizeof(AVP_Actor_Attachment));
		Asura_Deallocate(m_pxAttachments);
		m_pxAttachments = pxNewArray;
		m_uAttachmentStorage = uNewSize;
	}

	m_pxAttachments[m_uNumAttachments] = xAttachment;
	m_uNumAttachments++;

	return OnAttach(m_uNumAttachments-1);
}

bool AVP_Actor_AttachmentHandler::AddAttachment(const Asura_Blueprint* pxBlueprint)
{
	AVP_Actor_Attachment xAttachment;

	Asura_Value xTempValue;


	if(pxBlueprint->GetParameterValueByHashID(uAVP_BLUEPRINT_PARAM_MARKERPOINT, xTempValue))
	{
		xAttachment.m_uMarkerID = xTempValue.GetHashIDValue();
	} 
	else
	{
		Asura_Assertf(false, ("BLUEPRINT ERROR: Failed to find HashID 'MarkerPoint' in specified attachment-blueprint. %0X", pxBlueprint->GetHashID()));
		return(false);
	}

	if(pxBlueprint->GetParameterValueByHashID(uAVP_BLUEPRINT_PARAM_SKIN, xTempValue))
	{
		xAttachment.m_uSkinID = xTempValue.GetHashIDValue();
	}
	else
	{
		Asura_Assertf(false, ("BLUEPRINT ERROR: Failed to find HashID 'Skin' in specified attachment-blueprint. %0X", pxBlueprint->GetHashID()));
		return(false);
	}

	if(pxBlueprint->GetParameterValueByHashID(uAVP_BLUEPRINT_PARAM_DEFAULTANIM, xTempValue))
	{
		xAttachment.m_uAnimID = xTempValue.GetHashIDValue();
	}
	else
	{
		Asura_Assertf(false, ("BLUEPRINT ERROR: Failed to find HashID 'DefaultAnim' in specified attachment-blueprint. %0X", pxBlueprint->GetHashID()));
		return(false);
	}

	if(pxBlueprint->GetParameterValueByHashID(uAVP_BLUEPRINT_PARAM_ATTACHMENT_DELETEONDEATH, xTempValue))
	{
		if (xTempValue.GetBoolValue()) xAttachment.m_bDeleteOnDeath = true;
	}
	else
	{
		Asura_Assertf(false, ("BLUEPRINT WARNING: Failed to find bool 'DeleteOnDeath' in specified attachment-blueprint. %0X", pxBlueprint->GetHashID()));
	}

	if(pxBlueprint->GetParameterValueByHashID(uAVP_BLUEPRINT_PARAM_ATTACHMENT_DELETEONDECAPITATION, xTempValue))
	{
		if (xTempValue.GetBoolValue()) xAttachment.m_bDeleteOnDecapitation = true;
	}
	else
	{
		Asura_Assertf(false, ("BLUEPRINT WARNING: Failed to find bool 'DeleteOnDecapitation' in specified attachment-blueprint. %0X", pxBlueprint->GetHashID()));
	}

	if(pxBlueprint->GetParameterValueByHashID(uAVP_BLUEPRINT_PARAM_ATTACHMENT_DROPONDECAPITATION, xTempValue))
	{
		if (xTempValue.GetBoolValue()) xAttachment.m_bDropOnDecapitation = true;
	}
	else
	{
		Asura_Assertf(false, ("BLUEPRINT WARNING: Failed to find bool 'DropOnDecapitation' in specified attachment-blueprint. %0X", pxBlueprint->GetHashID()));
	}

	if(pxBlueprint->GetParameterValueByHashID(uAVP_BLUEPRINT_PARAM_ATTACHMENT_CHANCEOFDROPDEATH, xTempValue))
	{
		xAttachment.m_fChanceOfDropOnDeath = xTempValue.GetFloatValue();
	}
	else
	{
		Asura_Assertf(false, ("BLUEPRINT WARNING: Failed to find float 'ChanceOfDropDeath' in specified attachment-blueprint. %0X", pxBlueprint->GetHashID()));
	}

	if(pxBlueprint->GetParameterValueByHashID(uAVP_BLUEPRINT_PARAM_ATTACHMENT_CHANCEOFDROPHEADBIT, xTempValue))
	{
		xAttachment.m_fChanceOfDropOnHeadbit = xTempValue.GetFloatValue();
	}
	else
	{
		Asura_Assertf(false, ("BLUEPRINT WARNING: Failed to find float 'ChanceOfDropHeadbit' in specified attachment-blueprint. %0X", pxBlueprint->GetHashID()));
	}

	if(pxBlueprint->GetParameterValueByHashID(uAVP_BLUEPRINT_PARAM_ATTACHMENT_PHYSICSBLUEPRINT, xTempValue))
	{
		xAttachment.m_uPhysicsBlueprint = xTempValue.GetHashIDValue();
	} 
	else
	{
		if ( (xAttachment.m_bDropOnDecapitation) || xAttachment.m_fChanceOfDropOnDeath || xAttachment.m_fChanceOfDropOnHeadbit )
		{	
			Asura_Assertf(false, ("BLUEPRINT WARNING: Failed to find Blueprint 'Physics' in specified attachment-blueprint. %0X", pxBlueprint->GetHashID()));
		}
	}


	return AddAttachment(xAttachment);
}


bool AVP_Actor_AttachmentHandler::DetachAttachment(Asura_Hash_ID uSkin)
{
	for (u_int i=0 ; i<m_uNumAttachments ; i++)
	{
		if (m_pxAttachments[i].m_uSkinID == uSkin)
		{
			return DetachAttachmentByIndex(i);
		}
	}
	return false;
}

bool AVP_Actor_AttachmentHandler::DestroyAttachment(Asura_Hash_ID uSkin)
{
	for (u_int i=0 ; i<m_uNumAttachments ; i++)
	{
		if (m_pxAttachments[i].m_uSkinID == uSkin)
		{
			return DestroyAttachmentByIndex(i);

		}
	}
	return false;
}


void AVP_Actor_AttachmentHandler::OnDeath()
{
	for (u_int i=0 ; i< m_uNumAttachments ; i++)
	{
		if (m_pxAttachments[i].m_bDeleteOnDeath)
		{
			m_pxAttachments[i].m_bDeleteOnDeath = false;
			DestroyAttachmentByIndex(i);
			OnDeath();
			return;
		}

		if (m_pxAttachments[i].m_fChanceOfDropOnDeath)
		{
			if (Asura_Random::GetFloat(0.f, 1.f) < m_pxAttachments[i].m_fChanceOfDropOnDeath)
			{
				m_pxAttachments[i].m_fChanceOfDropOnDeath = 0.f;
				DetachAttachmentByIndex(i);
				OnDeath();
				return;
			}
		}
	}
}

void AVP_Actor_AttachmentHandler::OnDecapitation()
{
	for (u_int i=0 ; i< m_uNumAttachments ; i++)
	{
		if (m_pxAttachments[i].m_bDeleteOnDecapitation)
		{
			DestroyAttachmentByIndex(i);
			OnDecapitation();
			return;
		}

		if (m_pxAttachments[i].m_bDropOnDecapitation)
		{
			DetachAttachmentByIndex(i);
			OnDecapitation();
			return;
		}
	}
}

void AVP_Actor_AttachmentHandler::OnHeadBit()
{
	for (u_int i=0 ; i< m_uNumAttachments ; i++)
	{
		if (m_pxAttachments[i].m_bDeleteOnHeadbit)
		{
			DestroyAttachmentByIndex(i);
			OnHeadBit();
			return;
		}

		if (m_pxAttachments[i].m_fChanceOfDropOnHeadbit)
		{
			if (Asura_Random::GetFloat(0.f, 1.f) < m_pxAttachments[i].m_fChanceOfDropOnHeadbit)
			{
				m_pxAttachments[i].m_fChanceOfDropOnHeadbit = 0.f;
				DetachAttachmentByIndex(i);
				OnHeadBit();
				return;
			}
		}
	}
}

bool AVP_Actor_AttachmentHandler::HandleMessage(const Asura_Message* pxMessage)
{	
	const AVP_Message_Actor_Attachment* pxData = static_cast<const AVP_Message_Actor_Attachment*>(pxMessage->GetData());

	if (!pxData)
	{
		Asura_Assert(0, "AVP_Actor_AttachmentHandler::HandleMessage recieved null data");
		return false;
	}

	Asura_Blueprint* pxBlueprint = NULL;
	Asura_Value xTempValue;

	switch (pxMessage->GetMsgID())
	{
		case AVP_MSGID_CREATE_ATTACHMENT:
		{
			if (pxData->m_uBlueprintHash != ASURA_HASH_ID_UNSET)
			{
				pxBlueprint = Asura_Blueprint_System::GetBlueprintByHashID(uAVP_BLUEPRINT_TYPE_ATTACHMENTS, pxData->m_uBlueprintHash);
				if(pxBlueprint)
				{
					AddAttachment(pxBlueprint);
				}
				else
				{
					Asura_Assert(false, "AVP_Actor_AttachmentHandler::HandleMessage - AVP_MSGID_CREATE_ATTACHMENT - Blueprint not found");
				}	
			} 
			else
			{
				Asura_Assert(false, "AVP_Actor_AttachmentHandler::HandleMessage - AVP_MSGID_CREATE_ATTACHMENT - Unset blueprint hash");
			}
			return true;
		}

		case AVP_MSGID_DESTROY_ATTACHMENT:
		{
			if (pxData->m_uBlueprintHash != ASURA_HASH_ID_UNSET)
			{
				pxBlueprint = Asura_Blueprint_System::GetBlueprintByHashID(uAVP_BLUEPRINT_TYPE_ATTACHMENTS, pxData->m_uBlueprintHash);
				if (pxBlueprint)
				{
					if(pxBlueprint->GetParameterValueByHashID(uAVP_BLUEPRINT_PARAM_SKIN, xTempValue))
					{
						DestroyAttachment(xTempValue.GetHashIDValue());
					} 
					else
					{
						Asura_Assert(false, "AVP_Actor_AttachmentHandler::HandleMessage - AVP_MSGID_DESTROY_ATTACHMENT - 'DefaultSkin' not found in blueprint");
					}
				} 
				else
				{
					Asura_Assert(false, "AVP_Actor_AttachmentHandler::HandleMessage - AVP_MSGID_DESTROY_ATTACHMENT - Blueprint not found");
				}
			} 
			else
			{
				Asura_Assert(false, "AVP_Actor_AttachmentHandler::HandleMessage - AVP_MSGID_DESTROY_ATTACHMENT - Unset blueprint hash");
			}
		
			return true;
		}

		case AVP_MSGID_DETACH_ATTACHMENT:
		{
			if (pxData->m_uBlueprintHash != ASURA_HASH_ID_UNSET)
			{
				pxBlueprint = Asura_Blueprint_System::GetBlueprintByHashID(uAVP_BLUEPRINT_TYPE_ATTACHMENTS, pxData->m_uBlueprintHash);
				if (pxBlueprint)
				{
					if(pxBlueprint->GetParameterValueByHashID(uAVP_BLUEPRINT_PARAM_SKIN, xTempValue))
					{
						DetachAttachment(xTempValue.GetHashIDValue());
					} 
					else
					{
						Asura_Assert(false, "AVP_Actor_AttachmentHandler::HandleMessage - AVP_MSGID_DETACH_ATTACHMENT - 'DefaultSkin' not found in blueprint");
					}
				} 
				else
				{
					Asura_Assert(false, "AVP_Actor_AttachmentHandler::HandleMessage - AVP_MSGID_DETACH_ATTACHMENT - Blueprint not found");
				}
			} 
			else
			{
				Asura_Assert(false, "AVP_Actor_AttachmentHandler::HandleMessage - AVP_MSGID_DETACH_ATTACHMENT - Unset blueprint hash");
			}

			return true;
		}

		default:
		{
			return false;
		}
	}
}


void AVP_Actor_AttachmentHandler::OnDamage(const AVP_EntityDamage_ApplyToEntity& xDamage)
{
	xDamage.GetDirection(m_xLastDamageImpulse);
	m_xLastDamageImpulse *= xDamage.GetImpulseStrength() * Asura_Random::GetFloat(0.f,1.f);
}

void OnDestruction()
{

}

/////////////////////////
// Protected functions //
/////////////////////////

bool AVP_Actor_AttachmentHandler::DestroyAllAttachments()
{
	while (m_uNumAttachments)
	{
		DestroyAttachmentByIndex(m_uNumAttachments - 1);
	}
	return true;
}

bool AVP_Actor_AttachmentHandler::DestroyAttachmentByIndex(u_int uIndex)
{
	bool bSuccess = OnDetach(uIndex);
	if (uIndex != (m_uNumAttachments-1) )
	{
		m_pxAttachments[uIndex] = m_pxAttachments[m_uNumAttachments-1];
	}
	m_uNumAttachments --;
	return bSuccess;
}

bool AVP_Actor_AttachmentHandler::DetachAttachmentByIndex(u_int uIndex)
{
	// Creation of physics object triggered on server on non players only.
	// This is due to possible issues with creating objects near the camera that would look ugly (face mask pops into view as you die..)
	// Also with players they may be in first or third person mode, meaning the attachments might not even exist on the server
	if (!m_pxOwner->IsPlayer() && Asura_Network::IsServer())
	{
		CreateDroppedAttachment(uIndex);
	}

	return DestroyAttachmentByIndex(uIndex);
}

bool AVP_Actor_AttachmentHandler::OnAttach(u_int uIndex)
{
	if (!m_pxOwner)
	{
		Asura_Assert(0, "Attachment Handler has no owner");
		return false;
	}

	AVP_Actor_Attachment& xAttachment = m_pxAttachments[uIndex];
	
	xAttachment.m_pxAnimation = new Asura_Animation (xAttachment.m_uSkinID);
	if( xAttachment.m_pxAnimation == NULL )
	{
		Asura_Assert(false, "Failed to create animation for attachment.");
		return(false);
	}

	if( xAttachment.m_uAnimID != ASURA_HASH_ID_UNSET )
	{	
		u_int uInitialFlags = AAC_FLAG_ACTIVE | AAC_FLAG_ALLOW_DESTROY_FROM_MESSAGE;
		u_int uPriority = AVP_AAPC_ATTACHMENT;

		Asura_AnimationController_Animation* pxControllerAnimation = xAttachment.m_pxAnimation->CreateControllerAnimation(uInitialFlags , uPriority, xAttachment.m_uAnimID, NULL);
		Asura_Assert(pxControllerAnimation, "Failed to create Controller Animation in OnAttach");
		if (pxControllerAnimation)
		{
			pxControllerAnimation->SetParametricTimer(xAttachment.m_fAnimationStartTimeParametric);
			pxControllerAnimation->Play();
		}
	}

	Asura_Animation* pxParentAnimaton = m_pxOwner->GetAnimationHandler()->GetAnimation();
	if (!pxParentAnimaton)
	{
		Asura_Assert(pxParentAnimaton, "Failed to find our parents animation in OnAttach");
		return false;
	}

	if(pxParentAnimaton->LinkAnimation( xAttachment.m_pxAnimation , xAttachment.m_uMarkerID, true))
	{
		 xAttachment.m_pxAnimation->SetParent(pxParentAnimaton);
	} 
	else
	{
		Asura_Assert(0, "Failed to link Animation in OnAttach, check marker point exists.");
		return false;
	}

	// Update the animation to prevent any timing progression this frame. 
	Asura_Vector_3 xPos;
	Asura_Matrix_3x3 xOri;
	if ( (!xAttachment.m_bUpdateFirstFrame) && pxParentAnimaton->GetMarkerLocationFromHash( xAttachment.m_uMarkerID, &xPos, &xOri, true))
	{
		xAttachment.m_pxAnimation->Update(xPos,xOri,0.f, ASURA_HIERARCHY_RENDER_DONTRENDER | ASURA_HIERARCHY_RENDER_UPDATEBONESALWAYS);
	}

	return true;
}

bool AVP_Actor_AttachmentHandler::OnDetach(u_int uIndex)
{
	if (!m_pxOwner)
	{
		Asura_Assert(0, "Attachment Handler has no owner");
		return false;
	}

	AVP_Actor_Attachment& xAttachment = m_pxAttachments[uIndex];
	Asura_Animation* pxParentAnimaton = m_pxOwner->GetAnimationHandler()->GetAnimation();
	if (!pxParentAnimaton)
	{
		Asura_Assert(pxParentAnimaton, "Failed to find our parents animation in OnDetach");
		return false;
	}

	if (xAttachment.m_pxAnimation)
	{
		pxParentAnimaton->UnlinkAnimation(xAttachment.m_pxAnimation);
		delete xAttachment.m_pxAnimation;
		xAttachment.m_pxAnimation = NULL;
	}

	return true;
}

bool AVP_Actor_AttachmentHandler::CreateDroppedAttachment(u_int uIndex)
{
	Asura_Guid uGuid = Asura_Entity_System::GetCreatedByGameGuid();
	AVP_ServerEntity_PhysicsObject* pxP = AVP_ServerEntity_PhysicsObject::Create(uGuid);
	if (!pxP)
	{
		Asura_Assert(0,"CreateDroppedAttachment - Failed to create physics object.");
		return false;
	}

	AVP_Actor_Attachment& xAttachment = m_pxAttachments[uIndex];
	pxP->SetupPhysicsObject(ASURA_PHYSICS_OBJECT_DYNAMIC);

	Asura_Assert(pxP->GetPhysicsObject(), ("Could not create physics object in CreateDroppedAttachment."));

	Asura_Blueprint xBlueprint;
	Asura_Value xValue;

	Asura_Blueprint* pxPhysicsBlueprint = Asura_Blueprint_System::GetBlueprintByHashID(uAVP_BLUEPRINT_TYPE_PHYSICAL_OBJECTS, xAttachment.m_uPhysicsBlueprint);

	if (pxPhysicsBlueprint)
	{
		if(pxPhysicsBlueprint->GetParameterValueByHashID(Asura_GetHashID("GravityScale"), xValue))
		{
			pxP->SetGravityScale(xValue.GetFloatValue());
		}

		if(pxPhysicsBlueprint->GetParameterValueByHashID(Asura_GetHashID("Elasticity"), xValue))
		{
			pxP->SetElasticity(xValue.GetFloatValue());
		}

		if(pxPhysicsBlueprint->GetParameterValueByHashID(Asura_GetHashID("Friction"), xValue))
		{
			pxP->SetFriction(xValue.GetFloatValue());
		}

		if(pxPhysicsBlueprint->GetParameterValueByHashID(Asura_GetHashID("Drag"), xValue))
		{
			pxP->SetDrag(xValue.GetFloatValue());
		}

		if(pxPhysicsBlueprint->GetParameterValueByHashID(Asura_GetHashID("AngularDrag"), xValue))
		{
			pxP->SetAngularDrag(xValue.GetFloatValue());
		}

		if(pxPhysicsBlueprint->GetParameterValueByHashID(Asura_GetHashID("Mass"), xValue))
		{
			float fMass = xValue.GetFloatValue();
			if (fMass) pxP->SetInvMass(1.0f / fMass );
		}

		if(pxPhysicsBlueprint->GetParameterValueByHashID(Asura_GetHashID("MaterialType"), xValue))
		{
			pxP->SetMaterialType(xValue.GetHashIDValue());
		}
	}
	else
	{
		Asura_Assert(false, "Create dropped attachment, couldnt find specified Physics blueprint.");
	}


	// This calls reset collision object which creates the rigid body def, so need to call after physics creation.
	pxP->SetSkinAndAnimID(xAttachment.m_uSkinID, xAttachment.m_uAnimID);

	// Teleport it it to the correct location.
	Asura_Vector_3 xPosition = xAttachment.m_pxAnimation->GetPosition();
	const Asura_Quat& xQuat = xAttachment.m_pxAnimation->GetOrientQuat();
	Asura_Matrix_3x3 xOrientation;
	xQuat.ToMatrix(xOrientation);
	pxP->TeleportTo(xPosition, xOrientation, true);

	// Give it some impulse
	pxP->SetLinImpulse(m_xLastDamageImpulse);

	// Clear it up when the owner is no longer around.
	pxP->SetExitenceTiedToActor(m_pxOwner->GetGuid());

	return true;
}



/// End of file. ///


