//========= Copyright Valve Corporation, All rights reserved. ============//
//
// TODO:
//    - Use a mempool
//    - Need to be able to gracefully turn replay ragdolls on/off
//
//----------------------------------------------------------------------------------------

#include "cbase.h"

#if defined( REPLAY_ENABLED )

#include "replay_ragdoll.h"
#include "tier1/mempool.h"
#include "debugoverlay_shared.h"
#include "filesystem.h"

//--------------------------------------------------------------------------------

static matrix3x4_t gs_BoneCache[ MAXSTUDIOBONES ];
static int gs_nBytesAllocated = 0;

//--------------------------------------------------------------------------------

void OnReplayCacheClientRagdollsCvarChange( IConVar *pVar, const char *pOldValue, float flOldValue )
{
	// TODO: need to be able to gracefully turn replay ragdolls on/off
}

//--------------------------------------------------------------------------------

static ConVar replay_ragdoll_dbg( "replay_ragdoll_dbg", "0", FCVAR_CLIENTDLL, "Display replay ragdoll debugging information." );
static ConVar replay_cache_client_ragdolls( "replay_cache_client_ragdolls", "0", FCVAR_CLIENTDLL, "Record ragdolls on the client during.", OnReplayCacheClientRagdollsCvarChange );

//--------------------------------------------------------------------------------

void DrawBones( matrix3x4_t const* pBones, int nNumBones, ragdoll_t const* pRagdoll,
			    int nRed, int nGreen, int nBlue, C_BaseAnimating* pBaseAnimating )
{
	Assert( pBones );
	Assert( pRagdoll );
	Assert( pBaseAnimating );

	Vector from, to;
	for ( int i = 0; i < nNumBones; ++i )
	{
//		debugoverlay->AddCoordFrameOverlay( pBones[ i ], 3.0f );

		int const iRagdollParentIndex = pRagdoll->list[ i ].parentIndex;
		if ( iRagdollParentIndex < 0 )
			continue;

		int iBoneIndex = pRagdoll->boneIndex[ i ];
		int iParentIndex = pRagdoll->boneIndex[ iRagdollParentIndex ];

		MatrixPosition( pBones[ iParentIndex ], from );
		MatrixPosition( pBones[ iBoneIndex   ], to );

		debugoverlay->AddLineOverlay( from, to, nRed, nGreen, nBlue, true, 0.0f );
	}
}

//--------------------------------------------------------------------------------

inline int GetServerTickCount()
{
	int nTick = TIME_TO_TICKS( engine->GetLastTimeStamp() );
	return nTick;
}

//--------------------------------------------------------------------------------

/*static*/ RagdollSimulationFrame_t* RagdollSimulationFrame_t::Alloc( int nNumBones )
{
	// TODO: use a mempool
	RagdollSimulationFrame_t* pNew = new RagdollSimulationFrame_t();
	pNew->pPositions = new Vector[ nNumBones ];
	pNew->pAngles = new QAngle[ nNumBones ];
	gs_nBytesAllocated += sizeof( pNew ) + nNumBones * ( sizeof( Vector ) + sizeof( QAngle ) );
	return pNew;
}

//--------------------------------------------------------------------------------

RagdollSimulationData_t::RagdollSimulationData_t( C_BaseAnimating* pEntity, int nStartTick, int nNumBones )
:	m_pEntity( pEntity ),
	m_nEntityIndex( -1 ),
	m_nStartTick( nStartTick ),
	m_nNumBones( nNumBones ),
	m_nDuration( -1 )
{
	if ( pEntity )
	{
		m_nEntityIndex = pEntity->entindex();
	}

	Assert( nNumBones >= 0 && nNumBones < MAXSTUDIOBONES );
}

bool _ComputeRagdollBones( const ragdoll_t *pRagdoll, matrix3x4_t &parentTransform, matrix3x4_t *pBones, Vector *pPositions, QAngle *pAngles )
{
	matrix3x4_t inverted, output;

#ifdef _DEBUG
	CBitVec<MAXSTUDIOBONES> vBonesComputed;
	vBonesComputed.ClearAll();
#endif

	for ( int i = 0; i < pRagdoll->listCount; ++i )
	{
		const ragdollelement_t& element = pRagdoll->list[ i ];

		// during restore if a model has changed since the file was saved, this could be NULL
		if ( !element.pObject )
			return false;

		int const boneIndex = pRagdoll->boneIndex[ i ];
		if ( boneIndex < 0 )
		{
			AssertMsg( 0, "Replay: No mapping for ragdoll bone\n" );
			return false;
		}

		// Get global transform and put it into the bone cache
		element.pObject->GetPositionMatrix( &pBones[ boneIndex ] );

		// Ensure a fixed translation from the parent (no stretching)
		if ( element.parentIndex >= 0 && !pRagdoll->allowStretch )
		{
			int parentIndex = pRagdoll->boneIndex[ element.parentIndex ];

#ifdef _DEBUG
			// Make sure we computed the parent already
			Assert( vBonesComputed.IsBitSet(parentIndex) );
#endif

			// overwrite the position from physics to force rigid attachment
			// NOTE: On the client we actually override this with the proper parent bone in each LOD
			Vector out;
			VectorTransform( element.originParentSpace, pBones[ parentIndex ], out );
			MatrixSetColumn( out, 3, pBones[ boneIndex ] );

			MatrixInvert( pBones[ parentIndex ], inverted );
		}
		else if ( element.parentIndex == - 1 )
		{
			// Decompose into parent space
			MatrixInvert( parentTransform, inverted );
		}

#ifdef _DEBUG
		vBonesComputed.Set( boneIndex, true );
#endif

		// Compute local transform and put into 'output'
 		ConcatTransforms( inverted, pBones[ boneIndex ], output );

		// Cache as Euler/position
 		MatrixAngles( output, pAngles[ i ], pPositions[ i ] );
	}
	return true;
}

void RagdollSimulationData_t::Record()
{
	Assert( m_pEntity->m_pRagdoll );

	// Allocate a frame
	RagdollSimulationFrame_t* pNewFrame = RagdollSimulationFrame_t::Alloc( m_nNumBones );
	if ( !pNewFrame )
		return;

	// Set the current tick
	pNewFrame->nTick = GetServerTickCount();

	// Add new frame to list of frames
	m_lstFrames.AddToTail( pNewFrame );

	// Compute parent transform
	matrix3x4_t parentTransform;
	Vector vRootPosition = m_pEntity->GetRenderOrigin();
	QAngle angRootAngles = m_pEntity->GetRenderAngles();
	AngleMatrix( angRootAngles, vRootPosition, parentTransform );

//	debugoverlay->AddCoordFrameOverlay( parentTransform, 100 );

	// Cache off root position/orientation
	pNewFrame->vRootPosition = vRootPosition;
	pNewFrame->angRootAngles = angRootAngles;

	// Compute actual ragdoll bones
	matrix3x4_t* pBones = gs_BoneCache;
	_ComputeRagdollBones( m_pEntity->m_pRagdoll->GetRagdoll(), parentTransform, pBones, pNewFrame->pPositions, pNewFrame->pAngles );

	// Draw bones
	if ( replay_ragdoll_dbg.GetBool() )
	{
		DrawBones( pBones, m_pEntity->m_pRagdoll->RagdollBoneCount(), m_pEntity->m_pRagdoll->GetRagdoll(), 255, 0, 0, m_pEntity );
	}
}

//--------------------------------------------------------------------------------

CReplayRagdollRecorder::CReplayRagdollRecorder()
:	m_bIsRecording(false)
{
}

CReplayRagdollRecorder::~CReplayRagdollRecorder()
{
}

/*static*/ CReplayRagdollRecorder& CReplayRagdollRecorder::Instance()
{
	static CReplayRagdollRecorder s_instance;
	return s_instance;
}

void CReplayRagdollRecorder::Init()
{
	Assert( !m_bIsRecording );
	m_bIsRecording = true;
	gs_nBytesAllocated = 0;
}

void CReplayRagdollRecorder::Shutdown()
{
	if ( !m_bIsRecording )
		return;

	m_lstRagdolls.PurgeAndDeleteElements();
	gs_nBytesAllocated = 0;

	// RemoveAll() purges, and there is no UnlinkAll() - is there an easier way to do this?
	Iterator_t i = m_lstRagdollsToRecord.Head();
	while ( i != m_lstRagdollsToRecord.InvalidIndex() )
	{
		m_lstRagdollsToRecord.Unlink( i );
		i = m_lstRagdollsToRecord.Head();
	}

	Assert( m_bIsRecording );
	m_bIsRecording = false;
}

void CReplayRagdollRecorder::AddEntry( C_BaseAnimating* pEntity, int nStartTick, int nNumBones )
{
	DevMsg( "Replay: Processing Ragdoll at time %d\n", nStartTick );

	Assert( pEntity );
	RagdollSimulationData_t* pNewEntry = new RagdollSimulationData_t( pEntity, nStartTick, nNumBones );
	gs_nBytesAllocated += sizeof( RagdollSimulationData_t );
	m_lstRagdolls.AddToTail( pNewEntry );

	// Also add to list of ragdolls to record
	m_lstRagdollsToRecord.AddToTail( pNewEntry );
}

void CReplayRagdollRecorder::StopRecordingRagdoll( C_BaseAnimating* pEntity )
{
	Assert( pEntity );

	// Find the entry in the recording list
	Iterator_t nIndex;
	if ( !FindEntryInRecordingList( pEntity, nIndex ) )
		return;

	StopRecordingRagdollAtIndex( nIndex );
}

void CReplayRagdollRecorder::StopRecordingRagdollAtIndex( Iterator_t nIndex )
{
	// No longer recording - compute duration
	RagdollSimulationData_t* pData = m_lstRagdollsToRecord[ nIndex ];

	// Does duration need to be set?
	if ( pData->m_nDuration < 0 )
	{
		pData->m_nDuration = GetServerTickCount() - pData->m_nStartTick;		Assert( pData->m_nDuration > 0 );
	}

	// Remove it from the recording list
	m_lstRagdollsToRecord.Unlink( nIndex );
}

void CReplayRagdollRecorder::StopRecordingSleepingRagdolls()
{
	Iterator_t i = m_lstRagdollsToRecord.Head();
	while ( i != m_lstRagdollsToRecord.InvalidIndex() )
	{
		if ( RagdollIsAsleep( *m_lstRagdollsToRecord[ i ]->m_pEntity->m_pRagdoll->GetRagdoll() ) )
		{
			DevMsg( "entity %d: Removing sleeping ragdoll\n", m_lstRagdollsToRecord[ i ]->m_nEntityIndex );

			StopRecordingRagdollAtIndex( i );
			i = m_lstRagdollsToRecord.Head();
		}
		else
		{
			i = m_lstRagdollsToRecord.Next( i );
		}
	}
}

bool CReplayRagdollRecorder::FindEntryInRecordingList( C_BaseAnimating* pEntity,
													   CReplayRagdollRecorder::Iterator_t& nOutIndex )
{
	// Find the entry
	FOR_EACH_LL( m_lstRagdollsToRecord, i )
	{
		if ( m_lstRagdollsToRecord[ i ]->m_pEntity == pEntity )
		{
			nOutIndex = i;
			return true;
		}
	}

	nOutIndex = m_lstRagdollsToRecord.InvalidIndex();
	return false;
}

void CReplayRagdollRecorder::Record()
{
	static ConVar* pReplayEnable = NULL;
	static bool bLookedForConvar = false;
	if ( bLookedForConvar )
	{
		pReplayEnable =  (ConVar*)cvar->FindVar( "replay_enable" );
		bLookedForConvar = true;
	}
	if ( !pReplayEnable || !pReplayEnable->GetInt() )
		return;

	if ( !replay_cache_client_ragdolls.GetInt() )
		return;

	FOR_EACH_LL( m_lstRagdollsToRecord, i )
	{
		Assert( m_lstRagdollsToRecord[ i ]->m_pEntity->IsRagdoll() );
		m_lstRagdollsToRecord[ i ]->Record();
	}
}

void CReplayRagdollRecorder::Think()
{
	if ( !IsRecording() )
		return;

	StopRecordingSleepingRagdolls();
	Record();

	PrintDebug();
}

void CReplayRagdollRecorder::PrintDebug()
{
	if ( !replay_ragdoll_dbg.GetInt() )
		return;

	int nLine = 0;

	// Print memory usage
	engine->Con_NPrintf( nLine++, "ragdolls: %.2f MB", gs_nBytesAllocated / 1048576.0f );

	// Print server time
	engine->Con_NPrintf( nLine++, "server time: %d", GetServerTickCount() );

	++nLine;  // Blank line

	// Print info about each ragdoll
	FOR_EACH_LL( m_lstRagdolls, i )
	{
		engine->Con_NPrintf( nLine++, "entity %d: start time=%d  duration=%d  num bones=%d", m_lstRagdolls[i]->m_nEntityIndex, m_lstRagdolls[i]->m_nStartTick, m_lstRagdolls[i]->m_nDuration, m_lstRagdolls[i]->m_nNumBones );
	}
}

void CReplayRagdollRecorder::CleanupStartupTicksAndDurations( int nStartTick )
{
	FOR_EACH_LL( m_lstRagdolls, i )
	{
		RagdollSimulationData_t* pRagdollData = m_lstRagdolls[ i ];

		// Offset start tick with start tick, sent over from server
		pRagdollData->m_nStartTick -= nStartTick;			Assert( pRagdollData->m_nStartTick >= 0 );

		// Setup duration
		pRagdollData->m_nDuration = GetServerTickCount() - nStartTick;		Assert( pRagdollData->m_nDuration > 0 );

		// Go through all frames and subtract the start tick
		FOR_EACH_LL( pRagdollData->m_lstFrames, j )
		{
			pRagdollData->m_lstFrames[ j ]->nTick -= nStartTick;
		}
	}
}

BEGIN_DMXELEMENT_UNPACK( RagdollSimulationData_t )
	DMXELEMENT_UNPACK_FIELD( "nEntityIndex", "0", int, m_nEntityIndex )
	DMXELEMENT_UNPACK_FIELD( "nStartTick", "0", int, m_nStartTick )
	DMXELEMENT_UNPACK_FIELD( "nDuration", "0", int, m_nDuration )
	DMXELEMENT_UNPACK_FIELD( "nNumBones", "0", int, m_nNumBones )
END_DMXELEMENT_UNPACK( RagdollSimulationData_t, s_RagdollSimulationDataUnpack )

bool CReplayRagdollRecorder::DumpRagdollsToDisk( char const* pFilename ) const
{
	MEM_ALLOC_CREDIT();
	DECLARE_DMX_CONTEXT();

	CDmxElement* pSimulations = CreateDmxElement( "Simulations" );
	CDmxElementModifyScope modify( pSimulations );

		int const nNumRagdolls = m_lstRagdolls.Count();

		pSimulations->SetValue( "iNumRagdolls", nNumRagdolls );

		CDmxAttribute* pRagdolls = pSimulations->AddAttribute( "ragdolls" );
		CUtlVector< CDmxElement* >& ragdolls = pRagdolls->GetArrayForEdit< CDmxElement* >();

	modify.Release();

	char name[32];

	FOR_EACH_LL( m_lstRagdolls, i )
	{
		RagdollSimulationData_t const* pData = m_lstRagdolls[ i ];

		// Make sure we've setup all durations properly
		Assert( pData->m_nDuration >= 0 );

		CDmxElement* pRagdoll = CreateDmxElement( "ragdoll" );
		ragdolls.AddToTail( pRagdoll );

		V_snprintf( name, sizeof(name), "ragdoll %d", i );
		pRagdoll->SetValue( "name", name );

		CDmxElementModifyScope modifyClass( pRagdoll );

		pRagdoll->AddAttributesFromStructure( pData, s_RagdollSimulationDataUnpack );

		CDmxAttribute* pFrames = pRagdoll->AddAttribute( "frames" );
		CUtlVector< CDmxElement* >& frames = pFrames->GetArrayForEdit< CDmxElement* >();

		FOR_EACH_LL( pData->m_lstFrames, j )
		{
			CDmxElement* pFrame = CreateDmxElement( "frame" );
			frames.AddToTail( pFrame );

			V_snprintf( name, sizeof(name), "frame %d", j );
			pFrame->SetValue( "name", name );

			// Store tick
			pFrame->SetValue( "tick", pData->m_lstFrames[ j ]->nTick );

			// Store root pos/orientation
			pFrame->SetValue( "root_pos"   , pData->m_lstFrames[ j ]->vRootPosition );
			pFrame->SetValue( "root_angles", pData->m_lstFrames[ j ]->angRootAngles );

			for ( int k = 0; k < pData->m_nNumBones; ++k )
			{
				CDmxAttribute* pPositions = pFrame->AddAttribute( "positions" );
				CUtlVector< Vector >& positions = pPositions->GetArrayForEdit< Vector >();

				CDmxAttribute* pAngles = pFrame->AddAttribute( "angles" );
				CUtlVector< QAngle >& angles = pAngles->GetArrayForEdit< QAngle >();

				positions.AddToTail( pData->m_lstFrames[ j ]->pPositions[ k ] );
				angles.AddToTail( pData->m_lstFrames[ j ]->pAngles[ k ] );
			}
		}
	}

	{
		MEM_ALLOC_CREDIT();
		CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER );
		if ( !SerializeDMX( buf, pSimulations, pFilename ) )
		{
			Warning( "Replay: Failed to write ragdoll cache, %s.\n", pFilename );
			return false;
		}

		// Write the file
		filesystem->WriteFile( pFilename, "MOD", buf );
	}

	CleanupDMX( pSimulations );

	Msg( "Replay: Cached ragdoll data.\n" );

	return true;
}

//--------------------------------------------------------------------------------

CReplayRagdollCache::CReplayRagdollCache()
:	m_bInit( false )
{
}

/*static*/ CReplayRagdollCache& CReplayRagdollCache::Instance()
{
	static CReplayRagdollCache s_instance;
	return s_instance;
}

bool CReplayRagdollCache::Init( char const* pFilename )
{
	Assert( !m_bInit );

	// Make sure valid filename
	if ( !pFilename || pFilename[0] == 0 )
		return false;

	DECLARE_DMX_CONTEXT();

	// Attempt to read from disk
	CDmxElement* pRagdolls = NULL;
	if ( !UnserializeDMX( pFilename, "MOD", true, &pRagdolls ) )
//	if ( !UnserializeDMX( pFilename, "GAME", false, &pRagdolls ) )
		return false;

	CUtlVector< CDmxElement* > const& ragdolls = pRagdolls->GetArray< CDmxElement* >( "ragdolls" );
	for ( int i = 0; i < ragdolls.Count(); ++i )
	{
		CDmxElement* pCurRagdollInput = ragdolls[ i ];

		// Create a new ragdoll entry and add to list
		RagdollSimulationData_t* pNewSimData = new RagdollSimulationData_t();
		m_lstRagdolls.AddToTail( pNewSimData );

		// Read
		pCurRagdollInput->UnpackIntoStructure( pNewSimData, sizeof( *pNewSimData ), s_RagdollSimulationDataUnpack );

		// NOTE: Entity ptr doesn't get linked up here because it doesn't necessarily exist at this point

		// Read frames
		CUtlVector< CDmxElement* > const& frames = pCurRagdollInput->GetArray< CDmxElement* >( "frames" );
		for ( int j = 0; j < frames.Count(); ++j )
		{
			CDmxElement* pCurFrameInput = frames[ j ];

			// Create a new frame and add it to list of frames
			RagdollSimulationFrame_t* pNewFrame = RagdollSimulationFrame_t::Alloc( pNewSimData->m_nNumBones );
			pNewSimData->m_lstFrames.AddToTail( pNewFrame );

			// Read tick
			pNewFrame->nTick = pCurFrameInput->GetValue( "tick", -1 );			Assert( pNewFrame->nTick != -1 );

			// Read root pos/orientation
			pNewFrame->vRootPosition = pCurFrameInput->GetValue( "root_pos"   , vec3_origin );
			pNewFrame->angRootAngles = pCurFrameInput->GetValue( "root_angles", vec3_angle );

			CUtlVector< Vector > const& positions = pCurFrameInput->GetArray< Vector >( "positions" );
			CUtlVector< QAngle > const& angles = pCurFrameInput->GetArray< QAngle >( "angles" );

			for ( int k = 0; k < pNewSimData->m_nNumBones; ++k )
			{
				pNewFrame->pPositions[ k ] = positions[ k ];
				pNewFrame->pAngles[ k ]    = angles[ k ];
			}
		}
	}


	// Cleanup
	CleanupDMX( pRagdolls );

	m_bInit = true;

	return true;
}

void CReplayRagdollCache::Shutdown()
{
	if ( !m_bInit )
		return;

	m_lstRagdolls.PurgeAndDeleteElements();
	m_bInit = false;
}

ConVar replay_ragdoll_blending( "replay_ragdoll_blending", "1", FCVAR_DEVELOPMENTONLY );
ConVar replay_ragdoll_tickoffset( "replay_ragdoll_tickoffset", "0", FCVAR_DEVELOPMENTONLY );

bool CReplayRagdollCache::GetFrame( C_BaseAnimating* pEntity, int nTick, bool* pBoneSimulated, CBoneAccessor* pBoneAccessor ) const
{
	nTick += replay_ragdoll_tickoffset.GetInt();

	Assert( pEntity );
	Assert( pBoneSimulated );
	Assert( pEntity->m_pRagdoll );

	// Find ragdoll for the given entity - will return NULL if nTick is out of the entry's time window
	const RagdollSimulationData_t* pRagdollEntry = FindRagdollEntry( pEntity, nTick );
	if ( !pRagdollEntry )
		return false;

	// Find frame for the given tick
	RagdollSimulationFrame_t* pFrame;
	RagdollSimulationFrame_t* pNextFrame;
	if ( !FindFrame( pFrame, pNextFrame, pRagdollEntry, nTick ) )
		return false;

	// Compute root transform
	matrix3x4_t rootTransform;
	float flInterpAmount = gpGlobals->interpolation_amount;
	if ( pNextFrame )
	{
		AngleMatrix(
			(const QAngle &)Lerp( flInterpAmount, pFrame->angRootAngles, pNextFrame->angRootAngles ),	// Actually does a slerp
			Lerp( flInterpAmount, pFrame->vRootPosition, pNextFrame->vRootPosition ),
			rootTransform
		);
	}
	else
	{
		AngleMatrix( pFrame->angRootAngles, pFrame->vRootPosition, rootTransform );
	}

	// Compute each bone
	ragdoll_t* pRagdoll = pEntity->m_pRagdoll->GetRagdoll();		Assert( pRagdoll );
	for ( int k = 0; k < pRagdoll->listCount; ++k )
	{
		int objectIndex = k;
		const ragdollelement_t& element = pRagdoll->list[ objectIndex ];

		int const boneIndex = pRagdoll->boneIndex[ objectIndex ];			Assert( boneIndex >= 0 );

		// Compute blended transform if possible
		matrix3x4_t localTransform;
		if ( pNextFrame && replay_ragdoll_blending.GetInt() )
		{
			// Get blended Eular angles - NOTE: The Lerp() here actually calls Lerp<QAngle>() which converts to quats and back
			float flInterpAmount = gpGlobals->interpolation_amount;		Assert( flInterpAmount >= 0.0f && flInterpAmount <= 1.0f );
			AngleMatrix(
				(const QAngle &)Lerp( flInterpAmount, pFrame->pAngles   [ objectIndex ], pNextFrame->pAngles   [ objectIndex ] ), 
				Lerp( flInterpAmount, pFrame->pPositions[ objectIndex ], pNextFrame->pPositions[ objectIndex ] ),
				localTransform
			);
		}
		else
		{
			// Last frame
			AngleMatrix( pFrame->pAngles[ objectIndex ], pFrame->pPositions[ objectIndex ], localTransform );
		}

		matrix3x4_t& boneMatrix = pBoneAccessor->GetBoneForWrite( boneIndex );

		if ( element.parentIndex < 0 )
		{
			ConcatTransforms( rootTransform, localTransform, boneMatrix );
		}
		else
		{
			int parentBoneIndex = pRagdoll->boneIndex[ element.parentIndex ];		Assert( parentBoneIndex >= 0 );
			Assert( pBoneSimulated[ parentBoneIndex ] );
			matrix3x4_t const& parentMatrix = pBoneAccessor->GetBone( parentBoneIndex );
			ConcatTransforms( parentMatrix, localTransform, boneMatrix );
		}

		// Simulated this bone
		pBoneSimulated[ boneIndex ] = true;
	}

	if ( replay_ragdoll_dbg.GetBool() )
	{
		DrawBones( pBoneAccessor->GetBoneArrayForWrite(), pRagdollEntry->m_nNumBones, pRagdoll, 0, 0, 255, pEntity );
	}

	return true;
}

RagdollSimulationData_t* CReplayRagdollCache::FindRagdollEntry( C_BaseAnimating* pEntity, int nTick )
{
	Assert( pEntity );

	int const nEntIndex = pEntity->entindex();

	FOR_EACH_LL( m_lstRagdolls, i )
	{
		RagdollSimulationData_t* pRagdollData = m_lstRagdolls[ i ];

		// If not the right entity or the tick is out range, continue.
		if ( pRagdollData->m_nEntityIndex != nEntIndex )
			continue;

		// We've got the ragdoll, but only return it if nTick is in the window
		if ( nTick < pRagdollData->m_nStartTick ||
			 nTick > pRagdollData->m_nStartTick + pRagdollData->m_nDuration )
			return NULL;

		return pRagdollData;
	}

	return NULL;
}

bool CReplayRagdollCache::FindFrame( RagdollSimulationFrame_t*& pFrameOut, RagdollSimulationFrame_t*& pNextFrameOut,
									 const RagdollSimulationData_t* pRagdollEntry, int nTick )
{
	// Look for the appropriate frame
	FOR_EACH_LL( pRagdollEntry->m_lstFrames, j )
	{
		RagdollSimulationFrame_t* pFrame = pRagdollEntry->m_lstFrames[ j ];

		// Get next frame if possible
		int const nNext = pRagdollEntry->m_lstFrames.Next( j );
		RagdollSimulationFrame_t* pNextFrame =
			nNext == pRagdollEntry->m_lstFrames.InvalidIndex() ? NULL : pRagdollEntry->m_lstFrames[ nNext ];

		// Use this frame?
		if ( nTick >= pFrame->nTick &&
			( (pNextFrame && nTick <= pNextFrame->nTick) || !pNextFrame ) )		// Use the last frame if the tick is past the range of frames -
		{																		// this is the "sleeping" ragdoll frame
			pFrameOut     = pFrame;
			pNextFrameOut = pNextFrame;

			return true;
		}
	}

	pFrameOut     = NULL;
	pNextFrameOut = NULL;

	return false;
}

void CReplayRagdollCache::Think()
{
	// TODO: Add IsPlayingReplayDemo() to engine interface
	/*
	engine->Con_NPrintf( 8, "time: %d", engine->GetDemoPlaybackTick() );
	FOR_EACH_LL( m_lstRagdolls, i )
	{
		engine->Con_NPrintf( 10 + i, "entity %d: start time=%d  duration=%d  num bones=%d", m_lstRagdolls[i]->m_nEntityIndex, m_lstRagdolls[i]->m_nStartTick, m_lstRagdolls[i]->m_nDuration, m_lstRagdolls[i]->m_nNumBones );
	}
	*/
}

//--------------------------------------------------------------------------------

bool Replay_CacheRagdolls( const char* pFilename, int nStartTick )
{
	CReplayRagdollRecorder::Instance().CleanupStartupTicksAndDurations( nStartTick );
	return CReplayRagdollRecorder::Instance().DumpRagdollsToDisk( pFilename );
}

#endif