//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
// $NoKeywords: $
//
//=============================================================================//
#include "cbase.h"

#if !defined( NO_ENTITY_PREDICTION )

#if defined( CLIENT_DLL )

#include "igamesystem.h"

#endif
#include <memory.h>
#include <stdarg.h>
#include "tier0/dbg.h"
#include "tier1/strtools.h"
#include "predictioncopy.h"
#include "engine/ivmodelinfo.h"
#include "tier1/fmtstr.h"

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

// --------------------------------------------------------------
//
// CSave
//
// --------------------------------------------------------------
static const char *g_FieldTypes[ FIELD_TYPECOUNT ] = 
{
	"FIELD_VOID",			// FIELD_VOID
	"FIELD_FLOAT",			// FIELD_FLOAT
	"FIELD_STRING",			// FIELD_STRING
	"FIELD_VECTOR",			// FIELD_VECTOR
	"FIELD_QUATERNION",		// FIELD_QUATERNION
	"FIELD_INTEGER",		// FIELD_INTEGER
	"FIELD_BOOLEAN",		// FIELD_BOOLEAN
	"FIELD_SHORT",			// FIELD_SHORT
	"FIELD_CHARACTER",		// FIELD_CHARACTER
	"FIELD_COLOR32",		// FIELD_COLOR32
	"FIELD_EMBEDDED",		// FIELD_EMBEDDED	(handled specially)
	"FIELD_CUSTOM",			// FIELD_CUSTOM		(handled specially)
	"FIELD_CLASSPTR",		// FIELD_CLASSPTR
	"FIELD_EHANDLE",		// FIELD_EHANDLE
	"FIELD_EDICT",			// FIELD_EDICT
	"FIELD_POSITION_VECTOR",// FIELD_POSITION_VECTOR
	"FIELD_TIME",			// FIELD_TIME
	"FIELD_TICK",			// FIELD_TICK
	"FIELD_MODELNAME",		// FIELD_MODELNAME
	"FIELD_SOUNDNAME",		// FIELD_SOUNDNAME
	"FIELD_INPUT",			// FIELD_INPUT		(uses custom type)
	"FIELD_FUNCTION",		// FIELD_FUNCTION
	"FIELD_VMATRIX",			
	"FIELD_VMATRIX_WORLDSPACE",
	"FIELD_MATRIX3X4_WORLDSPACE",
	"FIELD_INTERVAL"		// FIELD_INTERVAL
	"FIELD_MODELINDEX"		// FIELD_MODELINDEX
};

CPredictionCopy::CPredictionCopy( int type, void *dest, bool dest_packed, void const *src, bool src_packed, 
	bool counterrors /*= false*/, bool reporterrors /*= false*/, bool performcopy /*= true*/,
	bool describefields /*= false*/, FN_FIELD_COMPARE func /*= NULL*/ )
{
	m_nType				= type;
	m_pDest				= dest;
	m_pSrc				= src;
	m_nDestOffsetIndex	= dest_packed ? TD_OFFSET_PACKED : TD_OFFSET_NORMAL;
	m_nSrcOffsetIndex	= src_packed ? TD_OFFSET_PACKED : TD_OFFSET_NORMAL;
	m_bErrorCheck		= counterrors;
	m_bReportErrors		= reporterrors;
	m_bPerformCopy		= performcopy;
	m_bDescribeFields	= describefields;

	m_pCurrentField		= NULL;
	m_pCurrentMap		= NULL;
	m_pCurrentClassName = NULL;
	m_bShouldReport		= false;
	m_bShouldDescribe	= false;
	m_nErrorCount		= 0;

	m_FieldCompareFunc	= func;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *fmt - 
//			... - 
//-----------------------------------------------------------------------------
void CPredictionCopy::ReportFieldsDiffer( const char *fmt, ... )
{
	++m_nErrorCount;

	if ( !m_bShouldReport )
		return;

	if ( m_bDescribeFields && m_FieldCompareFunc )
		return;

	Assert( m_pCurrentMap );
	Assert( m_pCurrentClassName );

	const char *fieldname = "empty";
	int flags = 0;

	if ( m_pCurrentField )
	{
		flags		= m_pCurrentField->flags;
		fieldname	= m_pCurrentField->fieldName ? m_pCurrentField->fieldName : "NULL";
	}

	va_list argptr;
	char data[ 4096 ];
	int len;
	va_start(argptr, fmt);
	len = Q_vsnprintf(data, sizeof( data ), fmt, argptr);
	va_end(argptr);

	if ( m_nErrorCount == 1 )
	{
		Msg( "\n" );
	}

	Msg( "%03i %s::%s - %s",
		m_nErrorCount,
		m_pCurrentClassName,
		fieldname,
		data );

	m_bShouldReport = false;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *fmt - 
//			... - 
//-----------------------------------------------------------------------------
void CPredictionCopy::DescribeFields( difftype_t dt, const char *fmt, ... )
{
	if ( !m_bShouldDescribe )
		return;

	if ( !m_FieldCompareFunc )
		return;

	Assert( m_pCurrentMap );
	Assert( m_pCurrentClassName );

	const char *fieldname = "empty";
	int flags = 0;

	if ( m_pCurrentField )
	{
		flags		= m_pCurrentField->flags;
		fieldname	= m_pCurrentField->fieldName ? m_pCurrentField->fieldName : "NULL";
	}

	va_list argptr;
	char data[ 4096 ];
	int len;
	va_start(argptr, fmt);
	len = Q_vsnprintf(data, sizeof( data ), fmt, argptr);
	va_end(argptr);

	bool isnetworked = ( flags & FTYPEDESC_INSENDTABLE ) ? true : false;
	bool isnoterrorchecked = ( flags & FTYPEDESC_NOERRORCHECK ) ? true : false;

	( *m_FieldCompareFunc )( 
		m_pCurrentClassName,
		fieldname,
		g_FieldTypes[ m_pCurrentField->fieldType ],
		isnetworked,
		isnoterrorchecked,
		dt != IDENTICAL ? true : false,
		dt == WITHINTOLERANCE ? true : false,
		data 
	);

	m_bShouldDescribe = false;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CPredictionCopy::CanCheck( void )
{
	Assert( m_pCurrentField );

	if ( m_pCurrentField->flags & FTYPEDESC_NOERRORCHECK )
	{
		return false;
	}

	return true;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : size - 
//			*outdata - 
//			*indata - 
//-----------------------------------------------------------------------------
/*
void CPredictionCopy::CopyData( difftype_t dt, int size, char *outdata, const char *indata )
{
	if ( !m_bPerformCopy )
		return;

	if ( dt == IDENTICAL )
		return;

	memcpy( outdata, indata, size );
}
*/

CPredictionCopy::difftype_t CPredictionCopy::CompareData( int size, char *outdata, const char *indata )
{
	if ( !m_bErrorCheck )
		return DIFFERS;

	if ( CanCheck() )
	{
		if ( memcmp( outdata, indata, size ) )
		{
			return DIFFERS;
		}
		else
		{
			// No difference, so no need to copy
			return IDENTICAL;
		}
	}

	// Fields differ
	return IDENTICAL;
}

void CPredictionCopy::DescribeData( difftype_t dt, int size, char *outdata, const char *indata )
{
	if ( !m_bErrorCheck )
		return;

	if ( dt == DIFFERS )
	{
		ReportFieldsDiffer( "binary data differs (%i bytes)\n", size );
	}

	DescribeFields( dt, "binary (%i bytes)\n", size );
}

void CPredictionCopy::WatchData( difftype_t dt, int size, char *outdata, const char *indata )
{
	if ( m_pWatchField != m_pCurrentField )
		return;

	WatchMsg( "binary (%i bytes)", size );
}

void CPredictionCopy::DescribeShort( difftype_t dt, short *outvalue, const short *invalue, int count )
{
	if ( !m_bErrorCheck )
		return;

	if ( dt == DIFFERS )
	{
		int i = 0;
		ReportFieldsDiffer( "short differs (net %i pred %i) diff(%i)\n", (int)(invalue[i]), (int)(outvalue[i]), (int)(outvalue[i] - invalue[i]) );
	}

	DescribeFields( dt, "short (%i)\n", (int)(outvalue[0]) );
}

void CPredictionCopy::WatchShort( difftype_t dt, short *outvalue, const short *invalue, int count )
{
	if ( m_pWatchField != m_pCurrentField )
		return;

	WatchMsg( "short (%i)", (int)(outvalue[0]) );
}

#if defined( CLIENT_DLL )
#include "cdll_int.h"

#endif

void CPredictionCopy::DescribeInt( difftype_t dt, int *outvalue, const int *invalue, int count )
{
	if ( !m_bErrorCheck )
		return;

	if ( dt == DIFFERS )
	{
		int i = 0;
		ReportFieldsDiffer( "int differs (net %i pred %i) diff(%i)\n", invalue[i], outvalue[i], outvalue[i] - invalue[i] );
	}

#if defined( CLIENT_DLL )
	bool described = false;
	if ( m_pCurrentField->flags & FTYPEDESC_MODELINDEX )
	{
		int modelindex = outvalue[0];
		model_t const *m = modelinfo->GetModel( modelindex );
		if ( m )
		{
			described = true;
			char shortfile[ 512 ];
			shortfile[ 0 ] = 0;
			Q_FileBase( modelinfo->GetModelName( m ), shortfile, sizeof( shortfile ) );

			DescribeFields( dt, "integer (%i->%s)\n", outvalue[0], shortfile );
		}
	}

	if ( !described )
	{
		DescribeFields( dt, "integer (%i)\n", outvalue[0] );
	}
#else
	DescribeFields( dt, "integer (%i)\n", outvalue[0] );
#endif
}

void CPredictionCopy::WatchInt( difftype_t dt, int *outvalue, const int *invalue, int count )
{
	if ( m_pWatchField != m_pCurrentField )
		return;

#if defined( CLIENT_DLL )
	bool described = false;
	if ( m_pCurrentField->flags & FTYPEDESC_MODELINDEX )
	{
		int modelindex = outvalue[0];
		model_t const *m = modelinfo->GetModel( modelindex );
		if ( m )
		{
			described = true;
			char shortfile[ 512 ];
			shortfile[ 0 ] = 0;
			Q_FileBase( modelinfo->GetModelName( m ), shortfile, sizeof( shortfile ) );

			WatchMsg( "integer (%i->%s)", outvalue[0], shortfile );
		}
	}

	if ( !described )
	{
		WatchMsg( "integer (%i)", outvalue[0] );
	}
#else
	WatchMsg( "integer (%i)", outvalue[0] );
#endif
}

void CPredictionCopy::DescribeBool( difftype_t dt, bool *outvalue, const bool *invalue, int count )
{
	if ( !m_bErrorCheck )
		return;

	if ( dt == DIFFERS )
	{
		int i = 0;
		ReportFieldsDiffer( "bool differs (net %s pred %s)\n", (invalue[i]) ? "true" : "false", (outvalue[i]) ? "true" : "false" );
	}

	DescribeFields( dt, "bool (%s)\n", (outvalue[0]) ? "true" : "false" );
}


void CPredictionCopy::WatchBool( difftype_t dt, bool *outvalue, const bool *invalue, int count )
{
	if ( m_pWatchField != m_pCurrentField )
		return;

	WatchMsg( "bool (%s)", (outvalue[0]) ? "true" : "false" );
}

void CPredictionCopy::DescribeFloat( difftype_t dt, float *outvalue, const float *invalue, int count )
{
	if ( !m_bErrorCheck )
		return;

	if ( dt == DIFFERS )
	{
		int i = 0;
		ReportFieldsDiffer( "float differs (net %f pred %f) diff(%f)\n", invalue[ i ], outvalue[ i ], outvalue[ i ] - invalue[ i ] );
	}

	DescribeFields( dt, "float (%f)\n", outvalue[ 0 ] );
}

void CPredictionCopy::WatchFloat( difftype_t dt, float *outvalue, const float *invalue, int count )
{
	if ( m_pWatchField != m_pCurrentField )
		return;

	WatchMsg( "float (%f)", outvalue[ 0 ] );
}

void CPredictionCopy::DescribeString( difftype_t dt, char *outstring, const char *instring )
{
	if ( !m_bErrorCheck )
		return;

	if ( dt == DIFFERS )
	{
		ReportFieldsDiffer( "string differs (net %s pred %s)\n", instring, outstring );
	}

	DescribeFields( dt, "string (%s)\n", outstring );
}

void CPredictionCopy::WatchString( difftype_t dt, char *outstring, const char *instring )
{
	if ( m_pWatchField != m_pCurrentField )
		return;

	WatchMsg( "string (%s)", outstring );
}

void CPredictionCopy::DescribeVector( difftype_t dt, Vector& outValue, const Vector &inValue )
{
	if ( !m_bErrorCheck )
		return;

	if ( dt == DIFFERS )
	{
		Vector delta = outValue - inValue;

		ReportFieldsDiffer( "vec differs (net %f %f %f - pred %f %f %f) delta(%f %f %f)\n", 
			inValue.x, inValue.y, inValue.z,
			outValue.x, outValue.y, outValue.z,
			delta.x, delta.y, delta.z );
	}

	DescribeFields( dt, "vector (%f %f %f)\n", 
				outValue.x, outValue.y, outValue.z );
}

void CPredictionCopy::DescribeVector( difftype_t dt, Vector* outValue, const Vector *inValue, int count )
{
	if ( !m_bErrorCheck )
		return;

	if ( dt == DIFFERS )
	{
		int i = 0;
		Vector delta = outValue[ i ] - inValue[ i ];

		ReportFieldsDiffer( "vec[] differs (1st diff) (net %f %f %f - pred %f %f %f) delta(%f %f %f)\n", 
			inValue[i].x, inValue[i].y, inValue[i].z,
			outValue[i].x, outValue[i].y, outValue[i].z,
			delta.x, delta.y, delta.z );
	}

	DescribeFields( dt, "vector (%f %f %f)\n", 
					outValue[0].x, outValue[0].y, outValue[0].z );
}

void CPredictionCopy::WatchVector( difftype_t dt, Vector& outValue, const Vector &inValue )
{
	if ( m_pWatchField != m_pCurrentField )
		return;

	WatchMsg( "vector (%f %f %f)", outValue.x, outValue.y, outValue.z );
}

void CPredictionCopy::WatchVector( difftype_t dt, Vector* outValue, const Vector *inValue, int count )
{
	if ( m_pWatchField != m_pCurrentField )
		return;

	WatchMsg( "vector (%f %f %f)", outValue[0].x, outValue[0].y, outValue[0].z );
}



void CPredictionCopy::DescribeQuaternion( difftype_t dt, Quaternion& outValue, const Quaternion &inValue )
{
	if ( !m_bErrorCheck )
		return;

	if ( dt == DIFFERS )
	{
		Quaternion delta;
		
		for ( int i = 0; i < 4; i++ )
		{
			delta[i] = outValue[i] - inValue[i];
		}

		ReportFieldsDiffer( "quaternion differs (net %f %f %f %f - pred %f %f %f %f) delta(%f %f %f %f)\n", 
			inValue[0], inValue[1], inValue[2], inValue[3],
			outValue[0], outValue[1], outValue[2], outValue[3],
			delta[0], delta[1], delta[2], delta[3] );
	}

	DescribeFields( dt, "quaternion (%f %f %f %f)\n", 
				outValue[0], outValue[1], outValue[2], outValue[3] );
}

void CPredictionCopy::DescribeQuaternion( difftype_t dt, Quaternion* outValue, const Quaternion *inValue, int count )
{
	if ( !m_bErrorCheck )
		return;

	if ( dt == DIFFERS )
	{
		int i = 0;
		Quaternion delta;

		for ( int j = 0; j < 4; j++ )
		{
			delta[i] = outValue[i][j] - inValue[i][j];
		}

		ReportFieldsDiffer( "quaternion[] differs (1st diff) (net %f %f %f %f - pred %f %f %f %f) delta(%f %f %f %f)\n", 
			(float)inValue[i][0], (float)inValue[i][1], (float)inValue[i][2], (float)inValue[i][3],
			(float)outValue[i][0], (float)outValue[i][1], (float)outValue[i][2], (float)outValue[i][3],
			delta[0], delta[1], delta[2], delta[3] );
	}

	DescribeFields( dt, "quaternion (%f %f %f %f)\n", 
					(float)outValue[0][0], (float)outValue[0][1], (float)outValue[0][2], (float)outValue[0][3] );
}

void CPredictionCopy::WatchQuaternion( difftype_t dt, Quaternion& outValue, const Quaternion &inValue )
{
	if ( m_pWatchField != m_pCurrentField )
		return;

	WatchMsg( "quaternion (%f %f %f %f)", (float)outValue[0], (float)outValue[1], (float)outValue[2], (float)outValue[3] );
}

void CPredictionCopy::WatchQuaternion( difftype_t dt, Quaternion* outValue, const Quaternion *inValue, int count )
{
	if ( m_pWatchField != m_pCurrentField )
		return;

	WatchMsg( "quaternion (%f %f %f %f)", outValue[0][0], outValue[0][1], outValue[0][2], outValue[0][3] );
}



void CPredictionCopy::DescribeEHandle( difftype_t dt, EHANDLE *outvalue, EHANDLE const *invalue, int count )
{
	if ( !m_bErrorCheck )
		return;

	if ( dt == DIFFERS )
	{
		int i = 0;
		ReportFieldsDiffer( "EHandles differ (net) 0x%p (pred) 0x%p\n", (void const *)invalue[ i ].Get(), (void *)outvalue[ i ].Get() );
	}

#if defined( CLIENT_DLL )
	C_BaseEntity *ent = outvalue[0].Get();
	if ( ent )
	{
		const char *classname = ent->GetClassname();
		if ( !classname[0] )
		{
			classname = typeid( *ent ).name();
		}

		DescribeFields( dt, "EHandle (0x%p->%s)", (void *)outvalue[ 0 ], classname );
	}
	else
	{
		DescribeFields( dt, "EHandle (NULL)" );
	}

#else
	DescribeFields( dt, "EHandle (0x%p)", (void *)outvalue[ 0 ] );
#endif

}

void CPredictionCopy::WatchEHandle( difftype_t dt, EHANDLE *outvalue, EHANDLE const *invalue, int count )
{
	if ( m_pWatchField != m_pCurrentField )
		return;

#if defined( CLIENT_DLL )
	C_BaseEntity *ent = outvalue[0].Get();
	if ( ent )
	{
		const char *classname = ent->GetClassname();
		if ( !classname[0] )
		{
			classname = typeid( *ent ).name();
		}

		WatchMsg( "EHandle (0x%p->%s)", (void *)outvalue[ 0 ], classname );
	}
	else
	{
		WatchMsg( "EHandle (NULL)" );
	}

#else
	WatchMsg( "EHandle (0x%p)", (void *)outvalue[ 0 ] );
#endif

}

void CPredictionCopy::CopyShort( difftype_t dt, short *outvalue, const short *invalue, int count )
{
	if ( !m_bPerformCopy )
		return;

	if ( dt == IDENTICAL )
		return;

	CopyData( dt, sizeof(short) * count, (char *)outvalue, (const char *)invalue ); 
}

CPredictionCopy::difftype_t CPredictionCopy::CompareShort( short *outvalue, const short *invalue, int count )
{
	if ( !m_bErrorCheck )
		return DIFFERS;

	if ( CanCheck() )
	{
		for ( int i = 0; i < count; i++ )
		{
			if ( outvalue[ i ] == invalue[ i ] )
				continue;

			return DIFFERS;
		}
	}

	return IDENTICAL;
}


void CPredictionCopy::CopyInt( difftype_t dt, int *outvalue, const int *invalue, int count )
{
	if ( !m_bPerformCopy )
		return;

	if ( dt == IDENTICAL )
		return;

	CopyData( dt, sizeof(int) * count, (char *)outvalue, (const char *)invalue );
}

CPredictionCopy::difftype_t CPredictionCopy::CompareInt( int *outvalue, const int *invalue, int count )
{
	if ( !m_bErrorCheck )
		return DIFFERS;

	if ( CanCheck() )
	{
		for ( int i = 0; i < count; i++ )
		{
			if ( outvalue[ i ] == invalue[ i ] )
				continue;

			ReportFieldsDiffer( "int differs (net %i pred %i) diff(%i)\n", invalue[i], outvalue[i], outvalue[i] - invalue[i] );
			return DIFFERS;
		}
	}

	return IDENTICAL;
}

void CPredictionCopy::CopyBool( difftype_t dt, bool *outvalue, const bool *invalue, int count )
{
	if ( !m_bPerformCopy )
		return;

	if ( dt == IDENTICAL )
		return;

	CopyData( dt, sizeof( bool ) * count, (char *)outvalue, (const char *)invalue );
}

CPredictionCopy::difftype_t CPredictionCopy::CompareBool( bool *outvalue, const bool *invalue, int count )
{
	if ( !m_bErrorCheck )
		return DIFFERS;

	if ( CanCheck() )
	{
		for ( int i = 0; i < count; i++ )
		{
			if ( outvalue[ i ] == invalue[ i ] )
				continue;

			return DIFFERS;
		}
	}

	return IDENTICAL;
}

void CPredictionCopy::CopyFloat( difftype_t dt, float *outvalue, const float *invalue, int count )
{
	if ( !m_bPerformCopy )
		return;

	if ( dt == IDENTICAL )
		return;

	CopyData( dt, sizeof( float ) * count, (char *)outvalue, (const char *)invalue );
}

CPredictionCopy::difftype_t CPredictionCopy::CompareFloat( float *outvalue, const float *invalue, int count )
{
	if ( !m_bErrorCheck )
		return DIFFERS;

	difftype_t retval = IDENTICAL;

	if ( CanCheck() )
	{
		float tolerance = m_pCurrentField->fieldTolerance;
		Assert( tolerance >= 0.0f );
		bool usetolerance = tolerance > 0.0f;

		for ( int i = 0; i < count; i++ )
		{
			if ( outvalue[ i ] == invalue[ i ] )
				continue;

			if ( usetolerance &&
				( fabs( outvalue[ i ] - invalue[ i ] ) <= tolerance ) )
			{
				retval = WITHINTOLERANCE;
				continue;
			}

			return DIFFERS;
		}
	}
	
	return retval;
}

void CPredictionCopy::CopyString( difftype_t dt, char *outstring, const char *instring )
{
	if ( !m_bPerformCopy )
		return;

	if ( dt == IDENTICAL )
		return;

	CopyData( dt, Q_strlen( instring ) + 1, (char *)outstring, (const char *)instring );
}

CPredictionCopy::difftype_t CPredictionCopy::CompareString( char *outstring, const char *instring )
{
	if ( !m_bErrorCheck )
		return DIFFERS;

	if ( CanCheck() )
	{
		if ( Q_strcmp( outstring, instring ) )
		{
			return DIFFERS;
		}
	}

	return IDENTICAL;
}

void CPredictionCopy::CopyVector( difftype_t dt, Vector& outValue, const Vector &inValue )
{
	CopyVector( dt, &outValue, &inValue, 1 );
}

void CPredictionCopy::CopyQuaternion( difftype_t dt, Quaternion& outValue, const Quaternion &inValue )
{
	CopyQuaternion( dt, &outValue, &inValue, 1 );
}

CPredictionCopy::difftype_t CPredictionCopy::CompareVector( Vector& outValue, const Vector &inValue )
{
	if ( !m_bErrorCheck )
		return DIFFERS;

	if ( CanCheck() )
	{
		float tolerance = m_pCurrentField->fieldTolerance;
		Assert( tolerance >= 0.0f );

		if ( outValue != inValue && ( tolerance > 0.0f ) )
		{
			Vector delta = outValue - inValue;

			if ( fabs( delta.x ) <= tolerance &&
				 fabs( delta.y ) <= tolerance &&
				 fabs( delta.z ) <= tolerance )
			{
				return WITHINTOLERANCE;
			}
		}

		return DIFFERS;
	}

	return IDENTICAL;
}

static int QuaternionCompare (const Quaternion& q1, const Quaternion& q2 )
{
	for ( int i = 0; i < 4; i++ )
	{
		if ( q1[i] != q2[i] )
			return 0;
	}

	return 1;
}

CPredictionCopy::difftype_t CPredictionCopy::CompareQuaternion( Quaternion& outValue, const Quaternion &inValue )
{
	if ( !m_bErrorCheck )
		return DIFFERS;

	if ( CanCheck() )
	{
		float tolerance = m_pCurrentField->fieldTolerance;
		Assert( tolerance >= 0.0f );

		if ( QuaternionCompare( outValue, inValue ) == 0
			&& ( tolerance > 0.0f ) )
		{
			Quaternion delta;

			for ( int j = 0; j < 4; j++ )
			{
				delta[j] = outValue[j] - inValue[j];
			}

			if ( fabs( delta[0] ) <= tolerance &&
				 fabs( delta[1] ) <= tolerance &&
				 fabs( delta[2] ) <= tolerance &&
				 fabs( delta[3] ) <= tolerance )
			{
				return WITHINTOLERANCE;
			}
		}

		return DIFFERS;
	}

	return IDENTICAL;
}

void CPredictionCopy::CopyVector( difftype_t dt, Vector* outValue, const Vector *inValue, int count )
{
	if ( !m_bPerformCopy )
		return;

	if ( dt == IDENTICAL )
		return;

	CopyData( dt, sizeof( Vector ) * count, (char *)outValue, (const char *)inValue );
}

void CPredictionCopy::CopyQuaternion( difftype_t dt, Quaternion* outValue, const Quaternion *inValue, int count )
{
	if ( !m_bPerformCopy )
		return;

	if ( dt == IDENTICAL )
		return;

	CopyData( dt, sizeof( Quaternion ) * count, (char *)outValue, (const char *)inValue );
}


CPredictionCopy::difftype_t CPredictionCopy::CompareVector( Vector* outValue, const Vector *inValue, int count )
{
	if ( !m_bErrorCheck )
		return DIFFERS;

	difftype_t retval = IDENTICAL;

	if ( CanCheck() )
	{
		float tolerance = m_pCurrentField->fieldTolerance;
		Assert( tolerance >= 0.0f );

		for ( int i = 0; i < count; i++ )
		{
			if ( outValue[ i ] == inValue[ i ] )
				continue;

			Vector delta = outValue[ i ] - inValue[ i ];

			if ( tolerance > 0.0f )
			{
				if ( fabs( delta.x ) <= tolerance &&
					 fabs( delta.y ) <= tolerance &&
					 fabs( delta.z ) <= tolerance )
				{
					retval = WITHINTOLERANCE;
					continue;
				}
			}
			return DIFFERS;
		}
	}

	return retval;
}

CPredictionCopy::difftype_t CPredictionCopy::CompareQuaternion( Quaternion* outValue, const Quaternion *inValue, int count )
{
	if ( !m_bErrorCheck )
		return DIFFERS;

	difftype_t retval = IDENTICAL;

	if ( CanCheck() )
	{
		float tolerance = m_pCurrentField->fieldTolerance;
		Assert( tolerance >= 0.0f );

		for ( int i = 0; i < count; i++ )
		{
			if ( QuaternionCompare( outValue[ i ], inValue[ i ] ) )
				continue;

			Quaternion delta;

			for ( int j = 0; j < 4; j++ )
			{
				delta[i] = outValue[i][j] - inValue[i][j];
			}

			if ( tolerance > 0.0f )
			{
				if ( fabs( delta[0] ) <= tolerance &&
					 fabs( delta[1] ) <= tolerance &&
					 fabs( delta[2] ) <= tolerance &&
					 fabs( delta[3] ) <= tolerance )
				{
					retval = WITHINTOLERANCE;
					continue;
				}
			}
			return DIFFERS;
		}
	}

	return retval;
}

void CPredictionCopy::CopyEHandle( difftype_t dt, EHANDLE *outvalue, EHANDLE const *invalue, int count )
{
	if ( !m_bPerformCopy )
		return;

	if ( dt == IDENTICAL )
		return;

	for ( int i = 0; i < count; i++ )
	{
		outvalue[ i ] = invalue[ i ];
	}
}

CPredictionCopy::difftype_t CPredictionCopy::CompareEHandle( EHANDLE *outvalue, EHANDLE const *invalue, int count )
{
	if ( !m_bErrorCheck )
		return DIFFERS;

	int i;
	if ( CanCheck() )
	{
		for ( i = 0; i < count; i++ )
		{
			if ( outvalue[ i ].Get() == invalue[ i ].Get() )
				continue;

			return DIFFERS;
		}
	}

	return IDENTICAL;
}

void CPredictionCopy::CopyFields( int chain_count, datamap_t *pRootMap, typedescription_t *pFields, int fieldCount )
{
	int				i;
	int				flags;
	int				fieldOffsetSrc;
	int				fieldOffsetDest;
	int				fieldSize;

	m_pCurrentMap = pRootMap;
	if ( !m_pCurrentClassName )
	{
		m_pCurrentClassName = pRootMap->dataClassName;
	}

	for ( i = 0; i < fieldCount; i++ )
	{
		m_pCurrentField = &pFields[ i ];
		flags = m_pCurrentField->flags;

		// Mark any subchains first
		if ( m_pCurrentField->override_field != NULL )
		{
			m_pCurrentField->override_field->override_count = chain_count;
		}

		// Skip this field?
		if ( m_pCurrentField->override_count == chain_count )
		{
			continue;
		}

		// Always recurse into embeddeds
		if ( m_pCurrentField->fieldType != FIELD_EMBEDDED )
		{
			// Don't copy fields that are private to server or client
			if ( flags & FTYPEDESC_PRIVATE )
				continue;

			// For PC_NON_NETWORKED_ONLYs skip any fields that are present in the network send tables
			if ( m_nType == PC_NON_NETWORKED_ONLY && ( flags & FTYPEDESC_INSENDTABLE ) )
				continue;

			// For PC_NETWORKED_ONLYs skip any fields that are not present in the network send tables
			if ( m_nType == PC_NETWORKED_ONLY && !( flags & FTYPEDESC_INSENDTABLE ) )
				continue;
		}

		void *pOutputData;
		void const *pInputData;

		fieldOffsetDest = m_pCurrentField->fieldOffset[ m_nDestOffsetIndex ];
		fieldOffsetSrc	= m_pCurrentField->fieldOffset[ m_nSrcOffsetIndex ];
		fieldSize = m_pCurrentField->fieldSize;

		pOutputData = (void *)((char *)m_pDest + fieldOffsetDest );
		pInputData = (void const *)((char *)m_pSrc + fieldOffsetSrc );

		// Assume we can report
		m_bShouldReport = m_bReportErrors;
		m_bShouldDescribe = true;

		bool bShouldWatch = m_pWatchField == m_pCurrentField;

		difftype_t difftype;

		switch( m_pCurrentField->fieldType )
		{
		case FIELD_EMBEDDED:
			{
				typedescription_t *save = m_pCurrentField;
				void *saveDest = m_pDest;
				void const *saveSrc = m_pSrc;
				const char *saveName = m_pCurrentClassName;

				m_pCurrentClassName = m_pCurrentField->td->dataClassName;

				// FIXME: Should this be done outside the FIELD_EMBEDDED case??
				// Don't follow the pointer if we're reading from a compressed packet
				m_pSrc = pInputData;
				if ( ( flags & FTYPEDESC_PTR ) && (m_nSrcOffsetIndex == PC_DATA_NORMAL) )
				{
					m_pSrc = *((void**)m_pSrc);
				}

				m_pDest = pOutputData;
				if ( ( flags & FTYPEDESC_PTR ) && (m_nDestOffsetIndex == PC_DATA_NORMAL) )
				{
					m_pDest = *((void**)m_pDest);
				}

				CopyFields( chain_count, pRootMap, m_pCurrentField->td->dataDesc, m_pCurrentField->td->dataNumFields );

				m_pCurrentClassName = saveName;
				m_pCurrentField = save;
				m_pDest = saveDest;
				m_pSrc = saveSrc;
			}
			break;
		case FIELD_FLOAT:
			{
				difftype = CompareFloat( (float *)pOutputData, (float const *)pInputData, fieldSize );
				CopyFloat( difftype, (float *)pOutputData, (float const *)pInputData, fieldSize );
				if ( m_bErrorCheck && m_bShouldDescribe ) DescribeFloat( difftype, (float *)pOutputData, (float const *)pInputData, fieldSize );
				if ( bShouldWatch ) WatchFloat( difftype, (float *)pOutputData, (float const *)pInputData, fieldSize );
			}
			break;

		case FIELD_TIME:
		case FIELD_TICK:
			Assert( 0 );
			break;

		case FIELD_STRING:
			{
				difftype = CompareString( (char *)pOutputData, (char const*)pInputData );
				CopyString( difftype, (char *)pOutputData, (char const*)pInputData );
				if ( m_bErrorCheck && m_bShouldDescribe ) DescribeString( difftype,(char *)pOutputData, (char const*)pInputData );
				if ( bShouldWatch ) WatchString( difftype,(char *)pOutputData, (char const*)pInputData );
			}
			break;

		case FIELD_MODELINDEX:
			Assert( 0 );
			break;

		case FIELD_MODELNAME:
		case FIELD_SOUNDNAME:
			Assert( 0 );
			break;

		case FIELD_CUSTOM:
			Assert( 0 );
			break;

		case FIELD_CLASSPTR:
		case FIELD_EDICT:
			Assert( 0 );
			break;

		case FIELD_POSITION_VECTOR:
			Assert( 0 );
			break;

		case FIELD_VECTOR:
			{
				difftype = CompareVector( (Vector *)pOutputData, (Vector const *)pInputData, fieldSize );
				CopyVector( difftype, (Vector *)pOutputData, (Vector const *)pInputData, fieldSize );
				if ( m_bErrorCheck && m_bShouldDescribe ) DescribeVector( difftype, (Vector *)pOutputData, (Vector const *)pInputData, fieldSize );
				if ( bShouldWatch ) WatchVector( difftype, (Vector *)pOutputData, (Vector const *)pInputData, fieldSize );
			}
			break;

		case FIELD_QUATERNION:
			{
				difftype = CompareQuaternion( (Quaternion *)pOutputData, (Quaternion const *)pInputData, fieldSize );
				CopyQuaternion( difftype, (Quaternion *)pOutputData, (Quaternion const *)pInputData, fieldSize );
				if ( m_bErrorCheck && m_bShouldDescribe ) DescribeQuaternion( difftype, (Quaternion *)pOutputData, (Quaternion const *)pInputData, fieldSize );
				if ( bShouldWatch ) WatchQuaternion( difftype, (Quaternion *)pOutputData, (Quaternion const *)pInputData, fieldSize );
			}
			break;

		case FIELD_COLOR32:
			{
				difftype = CompareData( 4*fieldSize, (char *)pOutputData, (const char *)pInputData );
				CopyData( difftype, 4*fieldSize, (char *)pOutputData, (const char *)pInputData );
				if ( m_bErrorCheck && m_bShouldDescribe ) DescribeData( difftype, 4*fieldSize, (char *)pOutputData, (const char *)pInputData );
				if ( bShouldWatch ) WatchData( difftype, 4*fieldSize, (char *)pOutputData, (const char *)pInputData );
			}
			break;

		case FIELD_BOOLEAN:
			{
				difftype = CompareBool( (bool *)pOutputData, (bool const *)pInputData, fieldSize );
				CopyBool( difftype, (bool *)pOutputData, (bool const *)pInputData, fieldSize );
				if ( m_bErrorCheck && m_bShouldDescribe ) DescribeBool( difftype, (bool *)pOutputData, (bool const *)pInputData, fieldSize );
				if ( bShouldWatch ) WatchBool( difftype, (bool *)pOutputData, (bool const *)pInputData, fieldSize );
			}
			break;

		case FIELD_INTEGER:
			{
				difftype = CompareInt( (int *)pOutputData, (int const *)pInputData, fieldSize );
				CopyInt( difftype, (int *)pOutputData, (int const *)pInputData, fieldSize );
				if ( m_bErrorCheck && m_bShouldDescribe ) DescribeInt( difftype, (int *)pOutputData, (int const *)pInputData, fieldSize );
				if ( bShouldWatch ) WatchInt( difftype, (int *)pOutputData, (int const *)pInputData, fieldSize );
			}
			break;

		case FIELD_SHORT:
			{
				difftype = CompareShort( (short *)pOutputData, (short const *)pInputData, fieldSize );
				CopyShort( difftype, (short *)pOutputData, (short const *)pInputData, fieldSize );
				if ( m_bErrorCheck && m_bShouldDescribe ) DescribeShort( difftype, (short *)pOutputData, (short const *)pInputData, fieldSize );
				if ( bShouldWatch ) WatchShort( difftype, (short *)pOutputData, (short const *)pInputData, fieldSize );
			}
			break;

		case FIELD_CHARACTER:
			{
				difftype = CompareData( fieldSize, ((char *)pOutputData), (const char *)pInputData );
				CopyData( difftype, fieldSize, ((char *)pOutputData), (const char *)pInputData );
				
				int valOut = *((char *)pOutputData);
				int valIn  = *((const char *)pInputData);
				
				if ( m_bErrorCheck && m_bShouldDescribe ) DescribeInt( difftype, &valOut, &valIn, fieldSize );
				if ( bShouldWatch ) WatchData( difftype, fieldSize, ((char *)pOutputData), (const char *)pInputData );
			}
			break;
		case FIELD_EHANDLE:
			{
				difftype = CompareEHandle( (EHANDLE *)pOutputData, (EHANDLE const *)pInputData, fieldSize );
				CopyEHandle( difftype, (EHANDLE *)pOutputData, (EHANDLE const *)pInputData, fieldSize );
				if ( m_bErrorCheck && m_bShouldDescribe ) DescribeEHandle( difftype, (EHANDLE *)pOutputData, (EHANDLE const *)pInputData, fieldSize );
				if ( bShouldWatch ) WatchEHandle( difftype, (EHANDLE *)pOutputData, (EHANDLE const *)pInputData, fieldSize );
			}
			break;
		case FIELD_FUNCTION:
			{
			Assert( 0 );
			}
			break;
		case FIELD_VOID:
			{
				// Don't do anything, it's an empty data description
			}
			break;
		default:
			{
				Warning( "Bad field type\n" );
				Assert(0);
			}
			break;
		}
	}

	m_pCurrentClassName = NULL;
}

void CPredictionCopy::TransferData_R( int chaincount, datamap_t *dmap )
{
	// Copy from here first, then baseclasses
	CopyFields( chaincount, dmap, dmap->dataDesc, dmap->dataNumFields );

	if ( dmap->baseMap )
	{
		TransferData_R( chaincount, dmap->baseMap );
	}
}

static int g_nChainCount = 1;

static typedescription_t *FindFieldByName_R( const char *fieldname, datamap_t *dmap )
{
	int c = dmap->dataNumFields;
	for ( int i = 0; i < c; i++ )
	{
		typedescription_t *td = &dmap->dataDesc[ i ];
		if ( td->fieldType == FIELD_VOID )
			continue;

		if ( td->fieldType == FIELD_EMBEDDED )
		{
			// TODO:  this will only find the first subclass with the variable of the specified name
			//  At some point we might want to support multiple levels of overriding automatically
			typedescription_t *ret = FindFieldByName_R( fieldname, td->td );
			if ( ret )
			{
				return ret;
			}
		}

		if ( !V_stricmp( td->fieldName, fieldname ) )
		{
			return td;
		}
	}

	if ( dmap->baseMap )
	{
		return FindFieldByName_R( fieldname, dmap->baseMap );
	}
	return NULL;
}

void ValidateChains_R( datamap_t *dmap )
{
	dmap->chains_validated = true;

	int c = dmap->dataNumFields;
	for ( int i = 0; i < c; i++ )
	{
		typedescription_t *td = &dmap->dataDesc[ i ];
		if ( td->fieldType == FIELD_VOID )
			continue;

		if ( td->fieldType == FIELD_EMBEDDED )
		{
			ValidateChains_R( td->td );
			continue;
		}

		if ( !( td->flags & FTYPEDESC_OVERRIDE ) )
			continue;

		if ( dmap->baseMap )
		{
			typedescription_t *basefield = FindFieldByName_R( td->fieldName, dmap->baseMap );
			if ( basefield )
			{
				td->override_field = basefield;
			}
		}
	}

	if ( dmap->baseMap )
	{
		if ( !dmap->baseMap->chains_validated )
		{
			ValidateChains_R( dmap->baseMap );
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *fieldname - 
//			*dmap - 
// Output : typedescription_t
//-----------------------------------------------------------------------------
typedescription_t *FindFieldByName( const char *fieldname, datamap_t *dmap )
{
	return FindFieldByName_R( fieldname, dmap );
}

static ConVar pwatchent( "pwatchent", "-1", FCVAR_CHEAT, "Entity to watch for prediction system changes." );
static ConVar pwatchvar( "pwatchvar", "", FCVAR_CHEAT, "Entity variable to watch in prediction system for changes." );

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *fmt - 
//			... - 
//-----------------------------------------------------------------------------
void CPredictionCopy::WatchMsg( const char *fmt, ... )
{
	Assert( m_pCurrentField && (m_pCurrentField == m_pWatchField) );
	Assert( m_pOperation );

	va_list argptr;
	char data[ 4096 ];
	int len;
	va_start(argptr, fmt);
	len = Q_vsnprintf(data, sizeof( data ), fmt, argptr);
	va_end(argptr);

	Msg( "%i %s %s : %s\n", gpGlobals->tickcount, m_pOperation, m_pCurrentField->fieldName, data );
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *operation - 
//			entindex - 
//			*dmap - 
//-----------------------------------------------------------------------------
void CPredictionCopy::DetermineWatchField( const char *operation, int entindex,  datamap_t *dmap )
{
	m_pWatchField = NULL;
	m_pOperation = operation;
	if ( !m_pOperation || !m_pOperation[0] )
		return;

	int enttowatch = pwatchent.GetInt();
	if ( enttowatch < 0 )
		return;

	if ( entindex != enttowatch )
		return;

	// See if they specified a field
	if ( pwatchvar.GetString()[0] == 0 )
		return;

	m_pWatchField = FindFieldByName( pwatchvar.GetString(), dmap );
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *operation - 
//			entindex - 
//			*dmap - 
// Output : int
//-----------------------------------------------------------------------------
int CPredictionCopy::TransferData( const char *operation, int entindex, datamap_t *dmap )
{
	++g_nChainCount;

	if ( !dmap->chains_validated )
	{
		ValidateChains_R( dmap );
	}
	
	DetermineWatchField( operation, entindex, dmap );

	TransferData_R( g_nChainCount, dmap );

	return m_nErrorCount;
}

/*
//-----------------------------------------------------------------------------
// Purpose: Simply dumps all data fields in object
//-----------------------------------------------------------------------------
class CPredictionDescribeData
{
public:
	CPredictionDescribeData( void const *src );

	void	DescribeShort( const short *invalue, int count );
	void	DescribeInt( const int *invalue, int count );		
	void	DescribeBool( const bool *invalue, int count );	
	void	DescribeFloat( const float *invalue, int count );	
	void	DescribeData( int size, const char *indata );		
	void	DescribeString( const char *instring );			
	void	DescribeVector( const Vector &inValue );
	void	DescribeVector( const Vector *inValue, int count );
	void	DescribeEHandle( EHANDLE const *invalue, int count );

	void	DescribeFields( datamap_t *pMap, typedescription_t *pFields, int fieldCount );

private:

	void const		*m_pSrc;
	void			Describe( const char *fmt, ... );

	typedescription_t *m_pCurrentField;
	char const		*m_pCurrentClassName;
	datamap_t		*m_pCurrentMap;
};
*/

CPredictionDescribeData::CPredictionDescribeData( void const *src, bool src_packed, FN_FIELD_DESCRIPTION func /*= 0*/ )
{
	m_pSrc				= src;
	m_nSrcOffsetIndex	= src_packed ? TD_OFFSET_PACKED : TD_OFFSET_NORMAL;

	m_pCurrentField		= NULL;
	m_pCurrentMap		= NULL;
	m_pCurrentClassName = NULL;
	m_bShouldReport		= false;

	m_FieldDescFunc		= func;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *fmt - 
//			... - 
//-----------------------------------------------------------------------------
void CPredictionDescribeData::Describe( const char *fmt, ... )
{
//	if ( !m_bShouldReport )
//		return;

	Assert( m_pCurrentMap );
	Assert( m_pCurrentClassName );

	const char *fieldname = "empty";
	int flags = 0;

	if ( m_pCurrentField )
	{
		flags		= m_pCurrentField->flags;
		fieldname	= m_pCurrentField->fieldName ? m_pCurrentField->fieldName : "NULL";
	}

	va_list argptr;
	char data[ 4096 ];
	int len;
	va_start(argptr, fmt);
	len = Q_vsnprintf(data, sizeof( data ), fmt, argptr);
	va_end(argptr);

	bool isprivate = ( flags & FTYPEDESC_PRIVATE ) ? true : false;
	bool isnetworked = ( flags & FTYPEDESC_INSENDTABLE ) ? true : false;

	if ( m_FieldDescFunc )
	{
		(*m_FieldDescFunc)( 
			m_pCurrentClassName, 
			m_pCurrentField->fieldName, 
			g_FieldTypes[ m_pCurrentField->fieldType ],
			isnetworked,
			data );
	}
	else
	{
		char suffix[ 128 ];

		suffix[ 0 ] = 0;
		if ( isprivate )
		{
			Q_strncat( suffix, "private", sizeof( suffix ), COPY_ALL_CHARACTERS );
		}
		if ( isnetworked )
		{
			if ( suffix[ 0 ] )
			{
				Q_strncat( suffix, " - ", sizeof( suffix ), COPY_ALL_CHARACTERS );
			}
			Q_strncat( suffix, "net", sizeof( suffix ), COPY_ALL_CHARACTERS );
		}

		if ( suffix[ 0 ] )
		{
			Msg( "%s::%s(%s) - %s",
				m_pCurrentClassName,
				fieldname,
				suffix,
				data );
		}
		else
		{
			Msg( "%s::%s - %s",
				m_pCurrentClassName,
				fieldname,
				data );
		}
	}

	m_bShouldReport = false;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : size - 
//			*outdata - 
//			*indata - 
//-----------------------------------------------------------------------------
void CPredictionDescribeData::DescribeData( int size, const char *indata )
{
	if ( !indata )
		return;

	Describe( "binary (%i bytes)\n", size );
}


void CPredictionDescribeData::DescribeShort( const short *invalue, int count )
{
	Describe( "short (%i)\n", (int)(invalue[0]) );
}


void CPredictionDescribeData::DescribeInt( const int *invalue, int count )
{
	for ( int i = 0; i < count; ++i )
	{
		Describe( "[%i] integer (%i)\n", i, invalue[i] );
	}
}

void CPredictionDescribeData::DescribeBool( const bool *invalue, int count )
{
	Describe( "bool (%s)\n", (invalue[0]) ? "true" : "false" );
}

void CPredictionDescribeData::DescribeFloat( const float *invalue, int count )
{
	Describe( "float (%f)\n", invalue[ 0 ] );
}

void CPredictionDescribeData::DescribeString( const char *instring )
{
	Describe( "string (%s)\n", instring );
}

void CPredictionDescribeData::DescribeVector( const Vector &inValue )
{
	Describe( "vector (%f %f %f)\n", 
				inValue.x, inValue.y, inValue.z );
}


void CPredictionDescribeData::DescribeVector( const Vector *inValue, int count )
{
	Describe( "vector (%f %f %f)\n", 
					inValue[0].x, inValue[0].y, inValue[0].z );
}

void CPredictionDescribeData::DescribeQuaternion( const Quaternion &inValue )
{
	Describe( "quaternion (%f %f %f %f)\n", 
				inValue[0], inValue[1], inValue[2], inValue[3] );
}


void CPredictionDescribeData::DescribeQuaternion( const Quaternion *inValue, int count )
{
	Describe( "quaternion (%f %f %f %f)\n", 
				inValue[0][0], inValue[0][1], inValue[0][2], inValue[0][3] );
}

void CPredictionDescribeData::DescribeEHandle( EHANDLE const *invalue, int count )
{
	Describe( "EHandle (%p)\n", (void *)invalue[ 0 ] );
}

void CPredictionDescribeData::DescribeFields_R( int chain_count, datamap_t *pRootMap, typedescription_t *pFields, int fieldCount )
{
	int				i;
	int				flags;
	int				fieldOffsetSrc;
	int				fieldSize;

	m_pCurrentMap = pRootMap;
	if ( !m_pCurrentClassName )
	{
		m_pCurrentClassName = pRootMap->dataClassName;
	}

	for ( i = 0; i < fieldCount; i++ )
	{
		m_pCurrentField = &pFields[ i ];
		flags = m_pCurrentField->flags;
		
		// Mark any subchains first
		if ( m_pCurrentField->override_field != NULL )
		{
			m_pCurrentField->override_field->override_count = chain_count;
		}

		// Skip this field?
		if ( m_pCurrentField->override_count == chain_count )
		{
			continue;
		}

		void const *pInputData;
		
		fieldOffsetSrc = m_pCurrentField->fieldOffset[ m_nSrcOffsetIndex ];
		fieldSize = m_pCurrentField->fieldSize;
		
		pInputData = (void const *)((char *)m_pSrc + fieldOffsetSrc );
		
		// Assume we can report
		m_bShouldReport = true;
		
		switch( m_pCurrentField->fieldType )
		{
		case FIELD_EMBEDDED:
			{
				typedescription_t *save = m_pCurrentField;
				void const *saveSrc = m_pSrc;
				const char *saveName = m_pCurrentClassName;
				
				m_pCurrentClassName = m_pCurrentField->td->dataClassName;
				
				m_pSrc = pInputData;
				if ( ( flags & FTYPEDESC_PTR ) && (m_nSrcOffsetIndex == PC_DATA_NORMAL) )
				{
					m_pSrc = *((void**)m_pSrc);
				}

				DescribeFields_R( chain_count, pRootMap, m_pCurrentField->td->dataDesc, m_pCurrentField->td->dataNumFields );
				
				m_pCurrentClassName = saveName;
				m_pCurrentField = save;
				m_pSrc = saveSrc;
			}
			break;
		case FIELD_FLOAT:
			DescribeFloat( (float const *)pInputData, fieldSize );
			break;
		case FIELD_TIME:
		case FIELD_TICK:
			Assert( 0 );
			break;
		case FIELD_STRING:
			DescribeString( (char const*)pInputData );
			break;
			
		case FIELD_MODELNAME:
		case FIELD_SOUNDNAME:
			Assert( 0 );
			break;
			
		case FIELD_MODELINDEX:
			Assert( 0 );
			break;

		case FIELD_CUSTOM:
			Assert( 0 );
			break;
			
		case FIELD_CLASSPTR:
		case FIELD_EDICT:
			break;
		case FIELD_POSITION_VECTOR:
			Assert( 0 );
			break;
		case FIELD_VECTOR:
			DescribeVector( (const Vector *)pInputData, fieldSize );
			break;
		case FIELD_QUATERNION:
			DescribeQuaternion( ( const Quaternion * )pInputData, fieldSize );
			break;
			
		case FIELD_COLOR32:
			DescribeData( 4*fieldSize, (const char *)pInputData );
			break;
			
		case FIELD_BOOLEAN:
			DescribeBool( (bool const *)pInputData, fieldSize );
			break;
		case FIELD_INTEGER:
			DescribeInt( (int const *)pInputData, fieldSize );
			break;
			
		case FIELD_SHORT:
			DescribeShort( (short const *)pInputData, fieldSize );
			break;
			
		case FIELD_CHARACTER:
			DescribeData( fieldSize, (const char *)pInputData );
			break;
			
		case FIELD_EHANDLE:
			DescribeEHandle( (EHANDLE const *)pInputData, fieldSize );
			break;
		case FIELD_FUNCTION:
			Assert( 0 );
			break;
		case FIELD_VOID:
			Describe( "FIELD_VOID: empty field\n" );
			break;
		default:
			Warning( "Bad field type\n" );
			Assert(0);
			break;
		}
	}

	m_pCurrentClassName = NULL;
}

void CPredictionDescribeData::DumpDescription( datamap_t *pMap )
{
	++g_nChainCount;

	if ( !pMap->chains_validated )
	{
		ValidateChains_R( pMap );
	}

	while ( pMap )
	{
        DescribeFields_R( g_nChainCount, pMap, pMap->dataDesc, pMap->dataNumFields );
		pMap = pMap->baseMap;
	}
}

#if defined( CLIENT_DLL )
CValueChangeTracker::CValueChangeTracker() :
	m_bActive( false ),
	m_bTracking( false )
{
	Q_memset( m_OrigValueBuf, 0, sizeof( m_OrigValueBuf ) );
}

C_BaseEntity *CValueChangeTracker::GetEntity()
{
	return m_hEntityToTrack.Get();
}

void CValueChangeTracker::GetValue( char *buf, size_t bufsize )
{
	buf[ 0 ] = 0;

	Assert( IsActive() );

	if ( !m_hEntityToTrack.Get() )
		return;

	void const *pInputData = ( const void * )m_hEntityToTrack.Get();
	typedescription_t *td = NULL;
	for ( int i = 0; i < m_FieldStack.Count(); ++i )
	{
		td = m_FieldStack[ i ];
		Assert( ( i == ( m_FieldStack.Count() -1 ) ) || 
			( td->fieldType & FIELD_EMBEDDED ) );
		int fieldOffsetSrc = td->fieldOffset[ TD_OFFSET_NORMAL ];
		const void *pSaveSrc = (const void *)( (char *)pInputData + fieldOffsetSrc );
		if ( ( td->flags & FTYPEDESC_PTR ) && 
			 ( td->fieldType & FIELD_EMBEDDED ) )
		{
			pInputData = *(const void **)pSaveSrc;
		}
		else
		{
			pInputData = (void const *)((char *)pSaveSrc );
		}
	}

	if ( !td || !pInputData )
		return;

	int fieldType = td->fieldType;

	switch( fieldType )
	{
	default:
	case FIELD_EMBEDDED:
	case FIELD_MODELNAME:
	case FIELD_SOUNDNAME:
	case FIELD_CUSTOM:
	case FIELD_CLASSPTR:
	case FIELD_EDICT:
	case FIELD_POSITION_VECTOR:
	case FIELD_VOID:
	case FIELD_FUNCTION:
		{
			Assert( 0 );
		}
		break;
	case FIELD_FLOAT:
	case FIELD_TIME:
		Q_snprintf( buf, bufsize, "%f", *(float const *)pInputData );
		break;
	case FIELD_STRING:
		Q_snprintf( buf, bufsize, "%s", (char const*)pInputData );
		break;
	case FIELD_VECTOR:
		{
			const Vector *pVec = (const Vector *)pInputData;
			Q_snprintf( buf, bufsize, "%f %f %f", pVec->x, pVec->y, pVec->z );
		}
		break;
	case FIELD_QUATERNION:
		{
			const Quaternion *p = ( const Quaternion * )pInputData;
			Q_snprintf( buf, bufsize, "%f %f %f %f", p->x, p->y, p->z, p->w );
		}
		break;

	case FIELD_COLOR32:
		{
			const Color *color = ( const Color * )pInputData;
			Q_snprintf( buf, bufsize, "%d %d %d %d", color->r(), color->g(), color->b(), color->a() );
		}
		break;

	case FIELD_BOOLEAN:
		Q_snprintf( buf, bufsize, "%s", (*(const bool *)pInputData) ? "true" : "false" );
		break;
	case FIELD_INTEGER:
	case FIELD_TICK:
	case FIELD_MODELINDEX:
		Q_snprintf( buf, bufsize, "%i", *(const int*)pInputData );
		break;

	case FIELD_SHORT:
		Q_snprintf( buf, bufsize, "%i", (int)*(const short*)pInputData );
		break;

	case FIELD_CHARACTER:
		Q_snprintf( buf, bufsize, "%c", *(const char *)pInputData );
		break;

	case FIELD_EHANDLE:
		Q_snprintf( buf, bufsize, "eh 0x%p", (void const *)((const EHANDLE *)pInputData)->Get() );
		break;
	}
}

void CValueChangeTracker::StartTrack( char const *pchContext )
{
	if ( !IsActive() )
		return;

	m_strContext = pchContext;

	// Grab current value into scratch buffer
	GetValue( m_OrigValueBuf, sizeof( m_OrigValueBuf ) );

	m_bTracking = true;
}

void CValueChangeTracker::EndTrack()
{
	if ( !IsActive() )
		return;

	if ( !m_bTracking )
		return;
	m_bTracking = false;

	char final[ eChangeTrackerBufSize ];
	GetValue( final, sizeof( final ) );

	CUtlString *history = &m_History[ m_History.AddToTail() ];
	if ( Q_stricmp( final, m_OrigValueBuf ) )
	{
		history->Set( CFmtStr( "+++ %-20.20s:  %s (was %s)", m_strContext.String(), final, m_OrigValueBuf ) );
	}
	else
	{
		history->Set( CFmtStr( "    %-20.20s:  %s", m_strContext.String(), final ) );
	}

	Msg( ":%s\n", history->String() );
}

void CValueChangeTracker::ClearTracking()
{
	m_bActive = false;
	m_bTracking = false;
	m_hEntityToTrack = NULL;
	m_strFieldName = "";
	m_History.RemoveAll();
	m_FieldStack.RemoveAll();
}

static bool FindFieldStackByName_R( const char *fieldname, datamap_t *dmap, CUtlVector< typedescription_t * >& stack )
{
	int c = dmap->dataNumFields;
	for ( int i = 0; i < c; i++ )
	{
		typedescription_t *td = &dmap->dataDesc[ i ];

		if ( td->fieldType == FIELD_VOID )
			continue;

		stack.AddToTail( td );

		if ( td->fieldType == FIELD_EMBEDDED )
		{
			// TODO:  this will only find the first subclass with the variable of the specified name
			//  At some point we might want to support multiple levels of overriding automatically
			bool ret = FindFieldStackByName_R( fieldname, td->td, stack );
			if ( ret )
			{
				return ret;
			}
		}

		if ( !Q_stricmp( td->fieldName, fieldname ) )
		{
			return true;
		}

		stack.FindAndRemove( td );
	}

	if ( dmap->baseMap )
	{
		return FindFieldStackByName_R( fieldname, dmap->baseMap, stack );
	}
	return false;
}

void CValueChangeTracker::SetupTracking( C_BaseEntity *ent, char const *pchFieldName )
{
	ClearTracking();

	// Find the field
	datamap_t *dmap = ent->GetPredDescMap();
	if ( !dmap )
	{
		Msg( "No prediction datamap_t for entity %d/%s\n", ent->index, ent->GetClassname() );
		return;
	}

	bool bFound = FindFieldStackByName_R( pchFieldName, dmap, m_FieldStack );
	if ( !bFound || !m_FieldStack.Count() )
	{
		Msg( "No field '%s' in datamap_t for entity %d/%s\n", pchFieldName, ent->index, ent->GetClassname() );
		return;
	}

	m_hEntityToTrack = ent;
	m_strFieldName = pchFieldName;
	m_bActive = true;
}

void CValueChangeTracker::Reset()
{
	m_History.RemoveAll();
}

bool CValueChangeTracker::IsActive() const
{
	return m_bActive;
}

void CValueChangeTracker::Spew()
{
	if ( IsActive() )
	{
		for ( int i = 0 ; i < m_History.Count(); ++i )
		{
			Msg( "%s\n", m_History[ i ].String() );
		}
	}

	Reset();
}

static CValueChangeTracker g_ChangeTracker;
CValueChangeTracker *g_pChangeTracker = &g_ChangeTracker;

CON_COMMAND_F( cl_pred_track, "<entindex> <fieldname>:  Track changes to entity index entindex, for field fieldname.", 0 )
{
	g_pChangeTracker->ClearTracking();

	if ( args.ArgC() != 3 )
	{
		Msg( "cl_pred_track <entindex> <fieldname>\n" );
		return;
	}

	int iEntIndex = Q_atoi( args[1] );

	C_BaseEntity *ent = cl_entitylist->GetBaseEntity( iEntIndex );
	if ( !ent )
	{
		Msg( "cl_pred_track:  Unknown ent index %d\n", iEntIndex );
		return;
   	}

	g_pChangeTracker->SetupTracking( ent, args[2] );
}

#endif

#if defined( CLIENT_DLL ) && defined( COPY_CHECK_STRESSTEST )

class CPredictionCopyTester : public IGameSystem
{
public:

	// Init, shutdown
	virtual void Init()
	{
		RunTests();
		Remove( this );
	}

	virtual void Shutdown() {}

	// Level init, shutdown
	virtual void LevelInit() {}
	// The level is shutdown in two parts
	virtual void LevelShutdownPreEntity() {}
	// Entities are deleted / released here...
	virtual void LevelShutdownPostEntity() {}
	// end of level shutdown

	// Called before rendering
	virtual void PreRender ( ) {}

	// Called after rendering
	virtual void PostRender() {}

	// Gets called each frame
	virtual void Update( float frametime ) {}

private:

	void RunTests( void );
};

IGameSystem* GetPredictionCopyTester( void )
{
	static CPredictionCopyTester s_PredictionCopyTesterSystem;
	return &s_PredictionCopyTesterSystem;
}

class CCopyTesterData
{
public:

	CCopyTesterData()
	{
		m_CharValue = 'a';
		m_ShortValue = (short)100;
		m_IntValue = (int)100;
		m_FloatValue = 1.0f;
		Q_strncpy( m_szValue, "primarydata", sizeof( m_szValue ) );
		m_Vector = Vector( 100, 100, 100 );
		m_Bool = false;
		m_Clr.r = m_Clr.g = m_Clr.b = m_Clr.a = 255;

		m_Ptr = (void *)0xfedcba98;
	//	m_hEHandle = NULL;
	}

	void MakeDifferent( void )
	{
		m_CharValue = 'd';
		m_ShortValue = (short)400;
		m_IntValue = (int)400;
		m_FloatValue = 4.0f;
		Q_strncpy( m_szValue, "secondarydata", sizeof( m_szValue ) );
		m_Vector = Vector( 400, 400, 400 );
		m_Bool = true;
		m_Clr.r = m_Clr.g = m_Clr.b = m_Clr.a = 1;
		m_Ptr = (void *)0x00000001;
	//	m_hEHandle = (C_BaseEntity *)0x00000001;
	}

	DECLARE_PREDICTABLE();

	char	m_CharValue;
	short	m_ShortValue;
	int		m_IntValue;
	float	m_FloatValue;
	char	m_szValue[ 128 ];
	Vector	m_Vector;
	bool	m_Bool;
	color32	m_Clr;
	void	*m_Ptr;
//	EHANDLE	m_hEHandle;

};

BEGIN_PREDICTION_DATA_NO_BASE( CCopyTesterData )

	DEFINE_FIELD( CCopyTesterData, m_CharValue, FIELD_CHARACTER ),
	DEFINE_FIELD( CCopyTesterData, m_ShortValue, FIELD_SHORT ),
	DEFINE_FIELD( CCopyTesterData, m_IntValue, FIELD_INTEGER ),
	DEFINE_FIELD( CCopyTesterData, m_FloatValue, FIELD_FLOAT ),
	DEFINE_FIELD( CCopyTesterData, m_szValue, FIELD_STRING ),
	DEFINE_FIELD( CCopyTesterData, m_Vector, FIELD_VECTOR ),
	DEFINE_FIELD( CCopyTesterData, m_Bool, FIELD_BOOLEAN ),
	DEFINE_FIELD( CCopyTesterData, m_Clr, FIELD_COLOR32 ),
//	DEFINE_FIELD( CCopyTesterData, m_hEHandle, FIELD_EHANDLE ),

END_PREDICTION_DATA()

class CCopyTesterData2 : public C_BaseEntity
{
	DECLARE_CLASS( CCopyTesterData2, C_BaseEntity );

public:
	CCopyTesterData2()
	{
		CONSTRUCT_PREDICTABLE( CCopyTesterData2 );

		m_CharValue = 'b';
		m_ShortValue = (short)200;
		m_IntValue = (int)200;
		m_FloatValue = 2.0f;
	}

	void MakeDifferent( void )
	{
		m_CharValue = 'e';
		m_ShortValue = (short)500;
		m_IntValue = (int)500;
		m_FloatValue = 5.0f;
		m_FooData.MakeDifferent();
	}

	DECLARE_PREDICTABLE();

	char	m_CharValue;
	short	m_ShortValue;
	int		m_IntValue;
	float	m_FloatValue;

	CCopyTesterData	m_FooData;
};

BEGIN_PREDICTION_DATA_NO_BASE( CCopyTesterData2 )

	DEFINE_FIELD( CCopyTesterData2, m_CharValue, FIELD_CHARACTER ),
	DEFINE_FIELD( CCopyTesterData2, m_ShortValue, FIELD_SHORT ),
	DEFINE_PRED_TYPEDESCRIPTION( CCopyTesterData2, m_FooData, CCopyTesterData ),
	DEFINE_FIELD( CCopyTesterData2, m_IntValue, FIELD_INTEGER ),
	DEFINE_FIELD( CCopyTesterData2, m_FloatValue, FIELD_FLOAT ),

END_PREDICTION_DATA()

void CPredictionCopyTester::RunTests( void )
{
	CCopyTesterData2 *foo1, *foo2, *foo3;

	foo1 = new CCopyTesterData2;
	foo2 = new CCopyTesterData2;
	foo3 = new CCopyTesterData2;

	foo2->MakeDifferent();


	{
		Msg( "Comparing and copying == objects, should have zero diffcount\n" );

		CPredictionCopy tester( PC_NON_NETWORKED_ONLY, foo1, false, foo3, false, true );
		int diff_count = 0;
		diff_count = tester.TransferData( foo3->GetPredDescMap(), foo1->GetPredDescMap()->dataDesc, foo1->GetPredDescMap()->dataNumFields );
		
		Msg( "diff_count == %i\n", diff_count );
		Assert( !diff_count );
	}

	{
		Msg( "Simple compare of != objects, should spew and have non-zero diffcount\n" );

		CPredictionCopy tester( PC_NON_NETWORKED_ONLY, foo1, false, foo2, false, true, false );
		int diff_count = 0;
		diff_count = tester.TransferData( foo2->GetPredDescMap(), foo1->GetPredDescMap()->dataDesc, foo1->GetPredDescMap()->dataNumFields );
		
		Msg( "diff_count == %i (should be 12)\n", diff_count );
		Assert( diff_count == 12 );
	}

	{
		Msg( "Comparing and coyping same != objects, should spew and have non-zero diffcount\n" );

		CPredictionCopy tester( PC_NON_NETWORKED_ONLY, foo1, false, foo2, false, true );
		int diff_count = 0;
		diff_count = tester.TransferData( foo2->GetPredDescMap(), foo1->GetPredDescMap()->dataDesc, foo1->GetPredDescMap()->dataNumFields );
		
		Msg( "diff_count == %i (should be 12)\n", diff_count );
		Assert( diff_count == 12 );
	}

	{
		Msg( "Comparing and copying objects which were just made to coincide, should have zero diffcount\n" );
	

		CPredictionCopy tester( PC_NON_NETWORKED_ONLY, foo1, false, foo2, false, true );
		int diff_count = 0;
		diff_count = tester.TransferData( foo2->GetPredDescMap(), foo1->GetPredDescMap()->dataDesc, foo1->GetPredDescMap()->dataNumFields );

		Msg( "diff_count == %i\n", diff_count );
		Assert( !diff_count );
	}

	{
		CPredictionDescribeData describe( foo1, false );
		describe.DumpDescription( foo1->GetPredDescMap(), foo1->GetPredDescMap()->dataDesc, foo1->GetPredDescMap()->dataNumFields );
	}
		
	delete foo3;
	delete foo2;
	delete foo1;

}

#endif // CLIENT_DLL
#endif // !NO_ENTITY_PREDICTION )
