
///////////////////////////////////////////////////////////////////////////////
//
// File   : AVP_ClienmEntity_Gib.cpp
// Author : Paul Humphries
// Date   : 06-Feb-2007
//
// Copyright (C) 2007 Rebellion, All Rights Reserved.
//
///////////////////////////////////////////////////////////////////////////////

// Core Asura
#include "Core/Asura.h"

// This file's header
#include "Entity/PhysicalObject/Gib/AVP_ClientEntity_Gib.h"

// Asura files (Engine-Independent)
#include "Animation/Asura_AnimationAttachment.h"
#include "Blueprint/Asura_Blueprint.h"
#include "Entity/Asura_PosAndOriInterpolator.h"
#include "Entity/Ragdoll/Asura_Ragdoll_Wrapper.h"
#include "Physics/Asura_Physics_Ragdoll.h"
#include "Rendering/Asura_Project_Skin_Elements.h"
#include "SpecialFX/Asura_Decal_Manager.h"
#include "SpecialFX/PFX/Asura_PFX_Entity_Effect.h"

// Project files (Engine-Independent)
#include "Blueprint/AVP_Blueprint_HashIDs.h"
#include "Core/AVP_Settings.h"
#include "Entity/AVP_Messages.h"
#include "Entity/AVP_Entity_Classification.h"
#include "Entity/Damage/AVP_EntityDamage_Messages.h"
#include "Entity/PhysicalObject/Actor/AVP_Actor_Messages.h"
#include "Entity/PhysicalObject/Actor/AVP_ClientEntity_Actor.h"
#include "Entity/PhysicalObject/AVP_ClientEntity_PhysicalObject.h"
#include "Entity/Asura_PosAndOriInterpolator.h"

// Constants

const float g_fFadeDelay    = 20.f;		// delay before a gib begins to fade away (alpha value linearly decreased)
const float g_fFadeDuration = 10.f;		// duration of fade before gib is transparent and then destroyed
const float g_fGibInvMass   = 1.0f;     // inverse mass of gibs

// Static variables
Asura_Hashed_List<AVP_ClientEntity_Gib> AVP_ClientEntity_Gib::s_xList;

///////////////////////////////////////////////////////
// Class: AVP_ClientEntity_Gib
///////////////////////////////////////////////////////

// Default constructor (protected)
AVP_ClientEntity_Gib::AVP_ClientEntity_Gib()
: m_fAge( 0.0f )
, m_pxCollisionBox( NULL )
, m_uBloodHash(ASURA_HASH_ID_UNSET)
, m_uBloodHitPFXHash(ASURA_HASH_ID_UNSET)
, m_uSkinHash(ASURA_HASH_ID_UNSET)
, m_iRagdollHandle(ASURA_INVALID_HANDLE)
{

	// don't forget to set the classification
	Classification = AVP_ENTITYCLASS_GIB;
	
	m_pxDynamicObject = NULL;

#if !defined(ASURA_NO_NETWORK_LAG_COMPENSATION)
	// Remove the interpolator - its not appropriate for gibs...
	delete(m_pxPosAndOriInterpolator);
	m_pxPosAndOriInterpolator	= NULL;
#endif	// !defined(ASURA_NO_NETWORK_LAG_COMPENSATION)

	// and add it to the object list
	s_xList.AddToList(this);

	m_bIgnoreForSurfaceReorientation = true;

	// and the updateable client list
	Asura_Entity_Client::AddToUpdateableClientList(this);

	SetInvisibleFlag( false );
}

// Virtual destructor (protected)
AVP_ClientEntity_Gib::~AVP_ClientEntity_Gib()
{
	if ( IsRagdoll() )
	{
		Asura_Ragdoll_Wrapper::Destroy( m_iRagdollHandle, false );
		m_iRagdollHandle = ASURA_INVALID_HANDLE;
	}

	// Destroy the physics-object (not forgetting to NULL the pointer to prevent
	// parent classes from also attempting to destroy it)...
	Asura_Physics_Object::Destroy(m_pxDynamicObject);
	m_pxDynamicObject	= NULL;

	delete m_pxCollisionBox;

	s_xList.RemoveFromList(this);
	Asura_Entity_Client::RemoveFromUpdateableClientList(this);
}

///////////////////////////////////////////////////////
// Function:	AVP_ClientEntity_Gib::Create
// Description:	Static method to create a Gib
///////////////////////////////////////////////////////

AVP_ClientEntity_Gib* AVP_ClientEntity_Gib::Create(Asura_Guid uOwnerGuid, const Asura_Vector_3& xPosition, const Asura_Matrix_3x3& xOrientation, const Asura_Vector_3& xVelocity, const Asura_Hash_ID uSkinHash, const Asura_Vector_3& xAttackImpulse, const Asura_Hash_ID uHitPFXHash, const Asura_Hash_ID uHitDecalHash, const PFXAttachData* pxPFXAttachData, const u_int uNumPFXToAttach)
{
	if (uSkinHash == ASURA_HASH_ID_UNSET) { return NULL; }

	// AJG - 14/12/07 - For now, only do this in single player. Leave the client guid as unregistered in multiplayer since remote machines can otherwise end up reusing guids that exist already.
	if (AVP_Settings::IsSinglePlayer())
	{
		Asura_Entity_System::SetNextClientGuid(Asura_Entity_System::GetCreatedByGameGuid());
	}
	AVP_ClientEntity_Gib* pxClient = new AVP_ClientEntity_Gib();
	if (!pxClient) { return NULL; }

	pxClient->m_uSkinHash = uSkinHash;
	pxClient->m_uBloodHash = uHitDecalHash;
	pxClient->m_uBloodHitPFXHash = uHitPFXHash;

	pxClient->SetClientControlsShapeSkinAndAnim( true );
	pxClient->SetSkinAndAnimID(uSkinHash, ASURA_HASH_ID_UNSET, 0, 0.0f, 0.0f);

	Asura_Animation* pxAnimation = pxClient->GetShapeInstance().GetAnimation();
	const Asura_Physics_Ragdoll_Description* pxRagdollDescription = Asura_Physics_Ragdoll_System::GetRagdollDescription(uSkinHash);

	if (pxRagdollDescription != NULL && pxAnimation != NULL)
	{
		pxClient->m_iRagdollHandle = Asura_Ragdoll_Wrapper::Create(pxClient->GetGuid(), uOwnerGuid, 1, *pxRagdollDescription, pxAnimation, xPosition, xOrientation, xVelocity, xAttackImpulse * g_fGibInvMass, xPosition);
	}
	Asura_Ragdoll_Wrapper* pxRagdollWrapper = Asura_Ragdoll_Wrapper::GetRagdollWrapper(pxClient->m_iRagdollHandle);
	if (pxRagdollWrapper)
	{
		pxRagdollWrapper->SetShouldDestroyClientEntity();
		pxRagdollWrapper->SetFadeDelay(g_fFadeDelay);
		pxRagdollWrapper->SetFadeDuration(g_fFadeDuration);
		pxClient->GetShapeInstance().AddFlags(ASURA_SHAPE_INSTANCE_BONES_IN_WORLDSPACE);
		pxClient->GetShapeInstance().SetNewPosAndOri(xPosition, xOrientation);
	}
	else 
	{
		pxClient->m_pxDynamicObject = Asura_Physics_Object::Create(ASURA_PHYSICS_OBJECT_DYNAMIC);
		if (pxClient->m_pxDynamicObject)
		{
			Asura_Hierarchy_Skin* pxSkin = Asura_Animation_System::GetSkinByHash(pxClient->GetSkinHash());
			Asura_Assert( pxSkin, "No skin found for Gib" );

			if ((pxSkin)
// Collision box not always valid in tools mode. Delete the following if it has been fixed already! (AN 03/12/07)
#ifdef ASURA_TOOLS
				&& (pxSkin->GetCollisionBB().CalculateRadius() > 0.001f))
#else
			)
#endif
			{
				pxClient->m_pxCollisionBox = new Asura_Collision_Box(pxSkin->GetCollisionBB());
			}
			else
			{
				pxClient->m_pxCollisionBox = new Asura_Collision_Box(-0.02f, 0.02f, -0.02f, 0.02f, -0.02f, 0.02f);
			}

			pxClient->TeleportTo(xPosition, xOrientation);
			pxClient->GetShapeInstance().SetNewPosAndOri(xPosition, xOrientation);
			pxClient->m_pxDynamicObject->SetCollisionObject(pxClient->m_pxCollisionBox);
			pxClient->m_pxDynamicObject->SetCollisionProxy(Asura_Physics_Object::COLLISION_PROXY_OBB);
			pxClient->m_pxDynamicObject->SetAxisConstraint(Asura_Physics_Object::AXIS_CONSTRAINT_NONE);
			pxClient->m_pxDynamicObject->SetFeelsGravity(true);
			pxClient->m_pxDynamicObject->SetElasticity(0.1f);
			pxClient->m_pxDynamicObject->SetInvMass(g_fGibInvMass);
			pxClient->m_pxDynamicObject->SetAssociatedEntityGuid(pxClient->GetGuid());
			pxClient->m_pxDynamicObject->SetIgnoreDynamicObjects(true);

			// Apply the damage impulse
			pxClient->m_pxDynamicObject->WakeUp();
			pxClient->m_pxDynamicObject->ApplyImpulse(xAttackImpulse, xPosition, false);
		}
		else
		{
			Asura_Assert(false, "Couldn't make either a ragdoll or a physics object for this gib, it's not gonna do much.");
		}
	}

	// Attach some pfx to the gib. This will probably be blood of some description.
	Asura_Animation* pxAnim = pxClient->GetShapeInstance().GetAnimation();
	if (pxAnim)
	{
		const u_short usPFXFlags = ASURA_PFX_EFFECT_CAMERA_FLAGS;

		for (u_int u=0; u<uNumPFXToAttach; ++u)
		{
			const Asura_Hash_ID uPFXHash = pxPFXAttachData[u].m_uPFXID;
			const Asura_Hash_ID uMarkerHash = pxPFXAttachData[u].m_uMarkerID;
			if ((uPFXHash != ASURA_HASH_ID_UNSET) && (uMarkerHash != ASURA_HASH_ID_UNSET))
			{
				Asura_AnimationAttachment* pxAttachment = new Asura_AnimationAttachment_PFX(uPFXHash, usPFXFlags);
				if (pxAttachment)
				{
					pxAttachment->LinkAttachment(pxAnim, uMarkerHash, true);
				}
			}
		}
	}

	return pxClient;
}

///////////////////////////////////////////////////////
// Function:	AVP_ClientEntity_Gib::Update
// Description:	Updates the client Gib
///////////////////////////////////////////////////////

void AVP_ClientEntity_Gib::Update()
{
	Asura_Ragdoll_Wrapper* pxRagdollWrapper = Asura_Ragdoll_Wrapper::GetRagdollWrapper( m_iRagdollHandle );
	if ( pxRagdollWrapper )
	{
		pxRagdollWrapper->Update( GetAnimation() );
	}
	else
	{
		if (m_fAge < g_fFadeDelay && m_pxDynamicObject && !m_pxDynamicObject->IsAsleep())
		{
			m_fAge = 0.0f;
		}

		m_fAge += Asura_Timers::GetGameTimeSlice();

		Asura_Shape_Instance& xShape = GetShapeInstance();

		if (m_fAge > g_fFadeDelay)
		{
			float fAlpha = 1.0f - ( ( m_fAge - g_fFadeDelay ) / g_fFadeDuration );
			if (fAlpha > 0.0f)
			{
				xShape.SetAlpha(fAlpha);
			}
			else
			{
				SetAsDestroyed();
			}
		}
	}
	PARENT::Update();
}

bool AVP_ClientEntity_Gib::IsRagdoll() const
{
	Asura_Ragdoll_Wrapper* pxRagdollWrapper = Asura_Ragdoll_Wrapper::GetRagdollWrapper( m_iRagdollHandle );
	Asura_Physics_Ragdoll* pxRagdoll = NULL;
	if ( pxRagdollWrapper )
	{
		pxRagdoll = pxRagdollWrapper->GetPhysicsRagdollPointer();
	}
	return ( pxRagdoll != NULL );
}

////////////////////////////////////////////////////////////////////////////////
// Function     : AVP_ClientEntity_Gib::Render
////////////////////////////////////////////////////////////////////////////////

void AVP_ClientEntity_Gib::Render()
{
	Asura_Ragdoll_Wrapper* pxRagdollWrapper = Asura_Ragdoll_Wrapper::GetRagdollWrapper( m_iRagdollHandle );
	Asura_Physics_Ragdoll* pxRagdoll = NULL;
	if ( pxRagdollWrapper )
	{
		pxRagdoll = pxRagdollWrapper->GetPhysicsRagdollPointer();
	}

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

		if ( pxAttachment )
		{
			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();
	}
	PARENT::Render();
}

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


////////////////////////////////////////////////////////////////////////////////
// Function     : AVP_ClientEntity_Gib::HandleMessage
////////////////////////////////////////////////////////////////////////////////

 bool AVP_ClientEntity_Gib::HandleMessage(const Asura_Message* pxMessage)
{	
	if (pxMessage)
	{
		switch (pxMessage->GetMsgID())
		{
			case AVP_MSGID_ENTITYDAMAGE_APPLY_TO_ENTITY:
			{
				const AVP_Message_EntityDamage_ApplyToEntity*	pxData	= static_cast<const AVP_Message_EntityDamage_ApplyToEntity*>(pxMessage->GetData());
				AVP_EntityDamage_ApplyToEntity					xDamage;
					
				pxData->GetDamage(xDamage);
				OnApplyDamage(xDamage);					
				return(true);
			}

// 			case ASURA_MSG_CLIENTUPDATE:
// 			{
// 				const AVP_Message_Gib_ClientUpdate* pxData = static_cast< const AVP_Message_Gib_ClientUpdate* >( pxMessage->GetData() );
// 
// 				// If we are inactive and now receiving a client update this means we are becoming active.
// 				// We should go visible and teleport to this first pos/ori so we don't zip across the level.
// 				if(!IsActive())
// 				{
// 					Asura_Matrix_3x3 xOrientMatrix;
// 					pxData->GetOrientation().ToMatrix( xOrientMatrix );
// 					TeleportTo( pxData->GetPosition(), xOrientMatrix );
// 				}
// 
// 				ProcessClientUpdate( pxData, pxMessage->GetSentTime() );
// 				return true;
// 			}
			default:
			{
				return(PARENT::HandleMessage(pxMessage));
			}
		}
	}

	return(false);
}


void AVP_ClientEntity_Gib::OnApplyDamage(const AVP_EntityDamage_ApplyToEntity& xDamage)
{
	PARENT::OnApplyDamage( xDamage );
	
	Asura_Vector_3	xPosition;
	Asura_Vector_3	xNormal;
	xDamage.GetPointOfImpact(xPosition);
	xDamage.GetNormal(xNormal);

	float fImpulseStrength = xDamage.GetImpulseStrength();

	if(fImpulseStrength != 0.0f)
	{
		Asura_Vector_3	xImpulse;

		xDamage.GetDirection(xImpulse);

		xImpulse	-= xNormal;
		xImpulse.Normalise();
		xImpulse	*= fImpulseStrength / xImpulse.Magnitude();

		// TODO, Should be one over the mass?
		Asura_Ragdoll_Wrapper* pxRagdollWrapper = Asura_Ragdoll_Wrapper::GetRagdollWrapper( m_iRagdollHandle );
		Asura_Physics_Ragdoll* pxRagdoll = NULL;
		if ( pxRagdollWrapper )
		{
			pxRagdoll = pxRagdollWrapper->GetPhysicsRagdollPointer();
		}

		if ( pxRagdoll )
		{
			pxRagdoll->ApplyImpulseToNearbyNodes( xPosition, xImpulse );
		}
		else if ( m_pxDynamicObject )
		{
			// Apply the damage impulse
			m_pxDynamicObject->WakeUp();
			m_pxDynamicObject->ApplyImpulse( xImpulse, xPosition, false );
		}
	}
	
	//Blood decal
	if ( xDamage.GetAmount() > 0.0f )
	{
		Asura_Vector_3 xPosition2;

		Asura_Vector_3 xDirectionDown(0.0f,1.0f,0.0f);
		float fDistance = 5.0f;

		const Asura_LOS_InputData	xLOSInput(xPosition, xDirectionDown, GetGuid(), fDistance);
		const Asura_LOS_OutputData*	pxLOSOutput	= Asura_LOS::TestAgainstEnvironment(&xLOSInput);

		if(pxLOSOutput->m_fDistanceToIntersection < fDistance)
		{
			xNormal		= pxLOSOutput->m_xNormal;
			xPosition2	= xDirectionDown*pxLOSOutput->m_fDistanceToIntersection + xPosition;

			Asura_Decal_Manager::AddDecal( xPosition2, xNormal, m_uBloodHash );
		}

		Asura_PFX_Entity_Effect::Create(xPosition, xNormal, m_uBloodHitPFXHash);
	}
}


void AVP_ClientEntity_Gib::FillPFXDetailsFromBlueprint(const Asura_Blueprint* pxBlueprint, AVP_ClientEntity_Gib::PFXAttachData* pxPFXDataStorage, u_int& uNumEntries)
{
	if (!pxBlueprint)
	{
		uNumEntries = 0;
		return;
	}

	u_int uTempNumEntries;
	AVP_Blueprint_ParameterEnumerator_EntityGibPFX xEnumerator(pxPFXDataStorage, uTempNumEntries, false);

	// First, the pfx effects.
	pxBlueprint->EnumerateParameters(szAVP_BLUEPRINT_ENUMERATION_PARAM_SKINELEMENT_GIBPFXEFFECT, 2, xEnumerator);
	const u_int uNumEffects = uTempNumEntries;

	// Then the marker points.
	xEnumerator.SetStoringMarkerPoints(true);
	pxBlueprint->EnumerateParameters(szAVP_BLUEPRINT_ENUMERATION_PARAM_SKINELEMENT_GIBPFXMARKER, 2, xEnumerator);
	const u_int uNumMarkers = uTempNumEntries;

	Asura_Assert(uNumEffects == uNumMarkers, "Number of gib pfx effects doesn't match the number of markers to attach them to.");
	uNumEntries = Asura_Maths::Min(uNumEffects, uNumMarkers);
}


// Enumerator to get any PFX to attach.
AVP_Blueprint_ParameterEnumerator_EntityGibPFX::AVP_Blueprint_ParameterEnumerator_EntityGibPFX(AVP_ClientEntity_Gib::PFXAttachData* pxPFXDataStorage, u_int& uNumEntries, bool bStoringMarkerPoints)
: m_pxPFXDataStorage(pxPFXDataStorage)
, m_uNumEntries(uNumEntries)
, m_bStoringMarkerPoints(bStoringMarkerPoints)
, m_bInEnumeration(false)
{
}

void AVP_Blueprint_ParameterEnumerator_EntityGibPFX::BeginEnumeration()
{
	m_uNumEntries = 0;
	m_bInEnumeration = true;
}

bool AVP_Blueprint_ParameterEnumerator_EntityGibPFX::Enumerate(const u_int uIndex, char const* const szName, const Asura_Value& xValue)
{
	if (m_uNumEntries >= uAVP_VERTEBRATE_GIB_MAX_PFX_ATTACHMENTS)
	{
		Asura_Assert(false, "Too many pfx to attach to this gib. Ignoring the rest.");
		return false;
	}

	const Asura_Hash_ID uValue = xValue.GetHashIDValue();;

	if (m_bStoringMarkerPoints)
	{
		m_pxPFXDataStorage[m_uNumEntries].m_uMarkerID = uValue;
	}
	else
	{
		m_pxPFXDataStorage[m_uNumEntries].m_uPFXID = uValue;
	}

	++m_uNumEntries;

	return true;
}

void AVP_Blueprint_ParameterEnumerator_EntityGibPFX::EndEnumeration(const bool bSuccess)
{
	// No need to do anything special on failure, as it just means there were too many entries.
	m_bInEnumeration = false;
}

void AVP_Blueprint_ParameterEnumerator_EntityGibPFX::SetStoringMarkerPoints(bool bStoringMarkers)
{
	Asura_Assert(!m_bInEnumeration, "Cannot change type of data being stored while the enumeration is in progress.");
	if (m_bInEnumeration) { return; }

	m_bStoringMarkerPoints = bStoringMarkers;
}

/// End of file. ///
