//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
// $NoKeywords: $
//=============================================================================//

#include "client_pch.h"
#include "enginestats.h"
#include "iprediction.h"
#include "cl_demo.h"
#include "cl_demoactionmanager.h"
#include "cl_pred.h"

#include "baseautocompletefilelist.h"
#include "demofile/demoformat.h"
#include "gl_matsysiface.h"
#include "materialsystem/imaterialsystemhardwareconfig.h"
#include "tier0/etwprof.h"
#include "tier0/icommandline.h"
#include "vengineserver_impl.h"
#include "console.h"
#include "dt_common_eng.h"
#include "net_chan.h"
#include "gl_model_private.h"
#include "decal.h"
#include "icliententitylist.h"
#include "icliententity.h"
#include "cl_demouipanel.h"
#include "materialsystem/materialsystem_config.h"
#include "tier2/tier2.h"
#include "vgui_baseui_interface.h"
#include "con_nprint.h"
#include "networkstringtableclient.h"

#ifdef SWDS
#include "server.h"
#endif

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

static ConVar demo_recordcommands( "demo_recordcommands", "1", FCVAR_CHEAT, "Record commands typed at console into .dem files." );
static ConVar demo_quitafterplayback( "demo_quitafterplayback", "0", 0, "Quits game after demo playback." );
static ConVar demo_debug( "demo_debug", "0", 0, "Demo debug info." );
static ConVar demo_interpolateview( "demo_interpolateview", "1", 0, "Do view interpolation during dem playback." );
static ConVar demo_pauseatservertick( "demo_pauseatservertick", "0", 0, "Pauses demo playback at server tick" );
static ConVar timedemo_runcount( "timedemo_runcount", "0", 0, "Runs time demo X number of times." );

// singeltons:
static char g_pStatsFile[MAX_OSPATH] = { 0 };
static bool s_bBenchframe = false;

static CDemoRecorder s_ClientDemoRecorder;
CDemoRecorder *g_pClientDemoRecorder = &s_ClientDemoRecorder;
IDemoRecorder *demorecorder = g_pClientDemoRecorder;

static CDemoPlayer s_ClientDemoPlayer;
CDemoPlayer *g_pClientDemoPlayer = &s_ClientDemoPlayer;
IDemoPlayer *demoplayer = g_pClientDemoPlayer;

extern CNetworkStringTableContainer *networkStringTableContainerClient;

// This is the number of units under which we are allowed to interpolate, otherwise pop.
// This fixes problems with in-level transitions.
static ConVar demo_interplimit( "demo_interplimit", "4000", 0, "How much origin velocity before it's considered to have 'teleported' causing interpolation to reset." );
static ConVar demo_avellimit( "demo_avellimit", "2000", 0, "Angular velocity limit before eyes considered snapped for demo playback." );

#define DEMO_HEADER_FILE	"demoheader.tmp"

// Fast forward convars
static ConVar demo_fastforwardstartspeed( "demo_fastforwardstartspeed", "2", 0, "Go this fast when starting to hold FF button." );
static ConVar demo_fastforwardfinalspeed( "demo_fastforwardfinalspeed", "20", 0, "Go this fast when starting to hold FF button." );
static ConVar demo_fastforwardramptime( "demo_fastforwardramptime", "5", 0, "How many seconds it takes to get to full FF speed." );

float scr_demo_override_fov = 0.0f;

//-----------------------------------------------------------------------------
// Purpose: Implements IDemo and handles demo file i/o
// Demos are more or less driven off of network traffic, but there are a few
//  other kinds of data items that are also included in the demo file:  specifically
//  commands that the client .dll itself issued to the engine are recorded, though they
//  probably were not the result of network traffic.
// At the start of a connection to a map/server, all of the signon, etc. network packets
//  are buffered.  This allows us to actually decide to start recording the demo at a later
//  time.  Once we actually issue the recording command, we don't actually start recording 
//  network traffic, but instead we ask the server for an "uncompressed" packet (otherwise
//  we wouldn't be able to deal with the incoming packets during playback because we'd be missing the
//  data to delta from ) and go into a waiting state.  Once an uncompressed packet is received, 
//  we unset the waiting state and start recording network packets from that point forward.
// Demo's record the elapsed time based on the current client clock minus the time the demo was started
// During playback, the elapsed time for playback ( based on the host_time, which is subject to the
//  host_frametime cvar ) is compared with the elapsed time on the message from the demo file.  
// If it's not quite time for the message yet, the demo input stream is rewound
// The demo system sits at the point where the client is checking to see if any network messages
//  have arrived from the server.  If the message isn't ready for processing, the demo system
//  just responds that there are no messages waiting and the client continues on
// Once a true network message with entity data is read from the demo stream, a couple of other
//  actions occur.  First, the timestamp in the demo file and the view origin/angles corresponding
//  to the message are cached off.  Then, we search ahead (into the future) to find out the next true network message
//  we are going to read from the demo file.  We store of it's elapsed time and view origin/angles
// Every frame that the client is rendering, even if there is no data from the demo system,
//  the engine asks the demo system to compute an interpolated origin and view angles.  This
//  is done by taking the current time on the host and figuring out how far that puts us between
//  the last read origin from the demo file and the time when we'll actually read out and use the next origin
// We use Quaternions to avoid gimbel lock on interpolating the view angles
// To make a movie recorded at a fixed frame rate you would simply set the host_framerate to the
//  desired playback fps ( e.g., 0.02 == 50 fps ), then issue the startmovie command, and then
//  play the demo.  The demo system will think that the engine is running at 50 fps and will pull
//  messages accordingly, even though movie recording kills the actually framerate.
// It will also render frames with render origin/angles interpolated in-between the previous and next origins
//  even if the recording framerate was not 50 fps or greater.  The interpolation provides a smooth visual playback 
//  of the demo information to the client without actually adding latency to the view position (because we are
//  looking into the future for the position, not buffering the past data ).
//-----------------------------------------------------------------------------

static bool IsControlCommand( unsigned char cmd )
{
	return ( (cmd == dem_signon) || (cmd == dem_stop) ||
		     (cmd == dem_synctick) || (cmd == dem_datatables ) ||
			 (cmd == dem_stringtables) );
}


// Puts a flashing overlay on the screen during demo recording/playback
static ConVar cl_showdemooverlay( "cl_showdemooverlay", "0", 0, "How often to flash demo recording/playback overlay (0 - disable overlay, -1 - show always)" );

class DemoOverlay
{
public:
	DemoOverlay();
	~DemoOverlay();

public:
	void Tick();
	void DrawOverlay( float fSetting );

protected:
	float m_fLastTickTime;
	float m_fLastTickOverlay;
	enum Overlay { OVR_NONE = 0, OVR_REC = 1 << 1, OVR_PLAY = 1 << 2 };
	bool m_bTick;
	int m_maskDrawnOverlay;
} g_DemoOverlay;

DemoOverlay::DemoOverlay() :
	m_fLastTickTime( 0.f ), m_fLastTickOverlay( 0.f ), m_bTick( false ), m_maskDrawnOverlay( OVR_NONE )
{
}

DemoOverlay::~DemoOverlay()
{
}

void DemoOverlay::Tick()
{
	if ( !m_bTick )
	{
		m_bTick = true;

		float const fRealTime = Sys_FloatTime();
		if ( m_fLastTickTime != fRealTime )
		{
			m_fLastTickTime = fRealTime;

			float const fDelta = m_fLastTickTime - m_fLastTickOverlay;
			float const fSettingDelta = cl_showdemooverlay.GetFloat();

			if ( fSettingDelta <= 0.f ||
				fDelta >= fSettingDelta )
			{
				m_fLastTickOverlay = m_fLastTickTime;
				DrawOverlay( fSettingDelta );
			}
		}

		m_bTick = false;
	}
}

void DemoOverlay::DrawOverlay( float fSetting )
{
	int maskDrawnOverlay = OVR_NONE;

	if ( fSetting < 0.f )
	{
		// Keep drawing
		maskDrawnOverlay =
			( demorecorder->IsRecording() ? OVR_REC : 0 ) |
			( demoplayer->IsPlayingBack() ? OVR_PLAY : 0 );
	}
	else if ( fSetting == 0.f )
	{
		// None
		maskDrawnOverlay = OVR_NONE;
	}
	else
	{
		// Flash
		maskDrawnOverlay = ( !m_maskDrawnOverlay ) ? (
			( demorecorder->IsRecording() ? OVR_REC : 0 ) |
			( demoplayer->IsPlayingBack() ? OVR_PLAY : 0 )
			) : OVR_NONE;
	}

	int const idx = 1;

	if ( OVR_NONE == maskDrawnOverlay &&
		 OVR_NONE != m_maskDrawnOverlay )
	{
		con_nprint_s xprn;
		memset( &xprn, 0, sizeof( xprn ) );
		xprn.index = idx;
		xprn.time_to_live = -1;
		Con_NXPrintf( &xprn, "" );
	}
	
	if ( OVR_PLAY & maskDrawnOverlay )
	{
		con_nprint_s xprn;
		memset( &xprn, 0, sizeof( xprn ) );
		xprn.index = idx;
		xprn.color[0] = 0.f;
		xprn.color[1] = 1.f;
		xprn.color[2] = 0.f;
		xprn.fixed_width_font = true;
		xprn.time_to_live = ( fSetting > 0.f ) ? fSetting : 1.f;
		Con_NXPrintf( &xprn, "  PLAY   " );
	}
	
	if ( OVR_REC & maskDrawnOverlay )
	{
		con_nprint_s xprn;
		memset( &xprn, 0, sizeof( xprn ) );
		xprn.index = idx;
		xprn.color[0] = 1.f;
		xprn.color[1] = 0.f;
		xprn.color[2] = 0.f;
		xprn.fixed_width_font = true;
		xprn.time_to_live = ( fSetting > 0.f ) ? fSetting : 1.f;
		Con_NXPrintf( &xprn, "   REC   " );
	}

	m_maskDrawnOverlay = maskDrawnOverlay;
}
			

//-----------------------------------------------------------------------------
// Purpose: Mark whether we are waiting for the first uncompressed update packet
// Input  : waiting - 
//-----------------------------------------------------------------------------
void CDemoRecorder::SetSignonState(int state)
{
	if ( demoplayer->IsPlayingBack() )
		return;

	if ( state == SIGNONSTATE_NEW )
	{
		if ( m_DemoFile.IsOpen() )
		{
			// we are already recording a demo file
			CloseDemoFile();

			// prepare for recording next demo
			m_nDemoNumber++; 
		}

		StartupDemoHeader();
	}
	else if ( state == SIGNONSTATE_SPAWN )
	{
		// close demo file header when this packet is finished
		m_bCloseDemoFile = true;
	}
	else if ( state == SIGNONSTATE_FULL )
	{
		if ( m_bRecording )
		{
			StartupDemoFile();
		}
	}
}

int CDemoRecorder::GetRecordingTick( void )
{
	if ( cl.m_nMaxClients > 1 )
	{
		return TIME_TO_TICKS( net_time ) - m_nStartTick;
	}
	else
	{
		return cl.GetClientTickCount() - m_nStartTick;
	}
}

void CDemoRecorder::ResyncDemoClock()
{
	if ( cl.m_nMaxClients > 1 )
	{
		m_nStartTick = TIME_TO_TICKS( net_time );
	}
	else
	{
		m_nStartTick = cl.GetClientTickCount();
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : info - 
//-----------------------------------------------------------------------------
void CDemoRecorder::GetClientCmdInfo( democmdinfo_t& info )
{
	info.flags		= FDEMO_NORMAL;

	if( m_bResetInterpolation )
	{
		info.flags |= FDEMO_NOINTERP;
		m_bResetInterpolation = false;
	}

	g_pClientSidePrediction->GetViewOrigin( info.viewOrigin );
#ifndef SWDS
	info.viewAngles = cl.viewangles;
#endif
	g_pClientSidePrediction->GetLocalViewAngles( info.localViewAngles );

	// Nothing by default
	info.viewOrigin2.Init();
	info.viewAngles2.Init();
	info.localViewAngles2.Init();
}

void CDemoRecorder::WriteBSPDecals()
{
	decallist_t	*decalList = (decallist_t*)malloc( sizeof(decallist_t) * Draw_DecalMax() );
	
	int decalcount = DecalListCreate( decalList );

	char		data[NET_MAX_PAYLOAD];
	bf_write	msg;

	msg.StartWriting( data, NET_MAX_PAYLOAD );
	msg.SetDebugName( "DemoFileWriteBSPDecals" );

	for ( int i = 0; i < decalcount; i++ )
	{
		decallist_t *entry = &decalList[ i ];

		SVC_BSPDecal decal;

		bool found = false;

		IClientEntity *clientEntity = entitylist->GetClientEntity( entry->entityIndex );

		if ( !clientEntity )
			continue;

		
		const model_t * pModel = clientEntity->GetModel();

		decal.m_Pos = entry->position;
		decal.m_nEntityIndex = entry->entityIndex;
		decal.m_nDecalTextureIndex = Draw_DecalIndexFromName( entry->name, &found );
		decal.m_nModelIndex = 0;

		if ( pModel )
		{
			decal.m_nModelIndex = cl.LookupModelIndex( modelloader->GetName( pModel ) );
		}

		decal.WriteToBuffer( msg );
	}

	WriteMessages( msg );
	
	free( decalList );
}

void CDemoRecorder::RecordServerClasses( ServerClass *pClasses )
{
	MEM_ALLOC_CREDIT();

	char *pBigBuffer;
	CUtlBuffer bigBuff;

	int buffSize = 256*1024;
	if ( !IsX360() )
	{
		pBigBuffer = (char*)stackalloc( buffSize );
	}
	else
	{
		// keep temp large allocations off of stack
		bigBuff.EnsureCapacity( buffSize );
		pBigBuffer = (char*)bigBuff.Base();
	}

	bf_write buf( pBigBuffer, buffSize );

	// Send SendTable info.
	DataTable_WriteSendTablesBuffer( pClasses, &buf );

	// Send class descriptions.
	DataTable_WriteClassInfosBuffer( pClasses, &buf );

	// Now write the buffer into the demo file
	m_DemoFile.WriteNetworkDataTables( &buf, GetRecordingTick() );
}

void CDemoRecorder::RecordStringTables()
{
	MEM_ALLOC_CREDIT();

	// !KLUDGE! It would be nice if the bit buffer could write into a stream
	// with the power to grow itself.  But it can't.  Hence this really bad
	// kludge
	void *data = NULL;
	int dataLen = 512 * 1024;
	while ( dataLen <= DEMO_FILE_MAX_STRINGTABLE_SIZE )
	{
		data = realloc( data, dataLen );
		bf_write buf( data, dataLen );
		buf.SetDebugName("CDemoRecorder::RecordStringTables");
		buf.SetAssertOnOverflow( false ); // Doesn't turn off all the spew / asserts, but turns off one
		networkStringTableContainerClient->WriteStringTables( buf );

		// Did we fit?
		if ( !buf.IsOverflowed() )
		{
			// Now write the buffer into the demo file
			m_DemoFile.WriteStringTables( &buf, GetRecordingTick() );
			break;
		}

		// Didn't fit.  Try doubling the size of the buffer
		dataLen *= 2;
	}

	if ( dataLen > DEMO_FILE_MAX_STRINGTABLE_SIZE )
	{
		Warning( "Failed to RecordStringTables. Trying to record string table that's bigger than max string table size\n" );
	}

	free(data);
}

void CDemoRecorder::RecordUserInput( int cmdnumber )
{
	char buffer[256];
	bf_write msg( "CDemo::WriteUserCmd", buffer, sizeof(buffer) );

	g_ClientDLL->EncodeUserCmdToBuffer( msg, cmdnumber );

	m_DemoFile.WriteUserCmd( cmdnumber, buffer, msg.GetNumBytesWritten(), GetRecordingTick() );
}

void CDemoRecorder::ResetDemoInterpolation( void )
{
	m_bResetInterpolation = true;
}


//-----------------------------------------------------------------------------
// Purpose: saves all cvars falgged with FVAR_DEMO to demo file
//-----------------------------------------------------------------------------
void CDemoRecorder::WriteDemoCvars()
{
	const ConCommandBase *var;
	
	for ( var= g_pCVar->GetCommands() ; var ; var=var->GetNext() )
	{
		if ( var->IsCommand() )
			continue;

		const ConVar *pCvar = ( const ConVar * )var;

		if ( !pCvar->IsFlagSet( FCVAR_DEMO ) )
			continue;

		char cvarcmd[MAX_OSPATH];

		Q_snprintf( cvarcmd, sizeof(cvarcmd),"%s \"%s\"",
			pCvar->GetName(), Host_CleanupConVarStringValue( pCvar->GetString() ) );

		m_DemoFile.WriteConsoleCommand( cvarcmd, GetRecordingTick() );
	}
}



//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *cmdname - 
//-----------------------------------------------------------------------------
void CDemoRecorder::RecordCommand( const char *cmdstring )
{
	if ( !IsRecording() )
		return;

	if ( !cmdstring || !cmdstring[0] )
		return;

	if ( !demo_recordcommands.GetInt() )
		return;

	m_DemoFile.WriteConsoleCommand( cmdstring, GetRecordingTick() );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CDemoRecorder::StartupDemoHeader( void )
{
	CloseDemoFile();	// make sure it's closed

	// Note: this is replacing tmpfile()
	if ( !m_DemoFile.Open( DEMO_HEADER_FILE, false ) )
	{
		ConDMsg ("ERROR: couldn't open temporary header file.\n");
		return;
	}

	m_bIsDemoHeader = true;

	Assert( m_MessageData.GetBasePointer() == NULL );

	// setup writing data buffer
	m_MessageData.StartWriting( new unsigned char[NET_MAX_PAYLOAD], NET_MAX_PAYLOAD );
	m_MessageData.SetDebugName( "DemoHeaderWriteBuffer" );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CDemoRecorder::StartupDemoFile( void )
{
	if ( !m_bRecording )
		return;

	// Already recording!!!
	if ( m_DemoFile.IsOpen() )
		return;

	char demoFileName[MAX_OSPATH];

	if ( m_nDemoNumber <= 1 )
	{
		V_sprintf_safe( demoFileName, "%s.dem", m_szDemoBaseName );
	}
	else
	{
		V_sprintf_safe( demoFileName, "%s_%i.dem", m_szDemoBaseName, m_nDemoNumber );
	}

	// strip any trailing whitespace
	Q_StripPrecedingAndTrailingWhitespace( demoFileName );

	// make sure the .dem extension is still present
	char ext[10];
	Q_ExtractFileExtension( demoFileName, ext, sizeof( ext ) );
	if ( Q_strcasecmp( ext, "dem" ) )
	{
		ConMsg( "StartupDemoFile: invalid filename.\n" );
		return;
	}

	if ( !m_DemoFile.Open( demoFileName, false ) )
		return;

	// open demo header file containing sigondata
	FileHandle_t hDemoHeader = g_pFileSystem->Open( DEMO_HEADER_FILE, "rb"	);
	if ( hDemoHeader == FILESYSTEM_INVALID_HANDLE )
	{
		ConMsg ("StartupDemoFile: couldn't open demo file header.\n");
		return;
	}

	Assert( m_MessageData.GetBasePointer() == NULL );

	// setup writing data buffer
	m_MessageData.StartWriting( new unsigned char[NET_MAX_PAYLOAD], NET_MAX_PAYLOAD );
	m_MessageData.SetDebugName( "DemoFileWriteBuffer" );

	// fill demo header info
	demoheader_t *dh = &m_DemoFile.m_DemoHeader;
	Q_memset(dh, 0, sizeof(demoheader_t));

	dh->demoprotocol = DEMO_PROTOCOL;
	dh->networkprotocol = PROTOCOL_VERSION;
	Q_strncpy(dh->demofilestamp, DEMO_HEADER_ID, sizeof(dh->demofilestamp) );

	Q_FileBase( modelloader->GetName( host_state.worldmodel ), dh->mapname, sizeof( dh->mapname ) );

	char szGameDir[MAX_OSPATH];
	Q_strncpy(szGameDir, com_gamedir, sizeof( szGameDir ) );
	Q_FileBase ( szGameDir, dh->gamedirectory, sizeof( dh->gamedirectory ) );

	Q_strncpy( dh->servername, cl.m_szRetryAddress, sizeof( dh->servername ) );
	Q_strncpy( dh->clientname, cl_name.GetString(), sizeof( dh->clientname ) );

	
	// get size	signon data size
	dh->signonlength = g_pFileSystem->Size(hDemoHeader);
	
	// write demo file header info
	m_DemoFile.WriteDemoHeader();
	
	// copy signon data from header file to demo file
	m_DemoFile.WriteFileBytes( hDemoHeader, dh->signonlength );

	// close but keep header file, we might need it for a second record
	g_pFileSystem->Close( hDemoHeader );
	
	m_nFrameCount = 0;
	m_bIsDemoHeader = false;
		
	ResyncDemoClock(); // reset demo clock
		
	// tell client to sync demo clock too 
	m_DemoFile.WriteCmdHeader( dem_synctick, 0 );
	
	RecordStringTables();

	// Demo playback should read this as an incoming message.
	WriteDemoCvars(); // save all cvars marked with FCVAR_DEMO

	WriteBSPDecals();

	g_ClientDLL->HudReset();

	//  tell server that we started recording a demo
	cl.SendStringCmd( "demorestart" );

	ConMsg ("Recording to %s...\n", demoFileName);

	g_ClientDLL->OnDemoRecordStart( m_szDemoBaseName );
}

CDemoRecorder::~CDemoRecorder()
{
	CloseDemoFile();	
}

CDemoFile *CDemoRecorder::GetDemoFile()
{
	return &m_DemoFile;
}

void CDemoRecorder::ResumeRecording()
{

}

void CDemoRecorder::PauseRecording()
{

}


void CDemoRecorder::CloseDemoFile()
{
	if ( m_DemoFile.IsOpen())
	{
		if ( !m_bIsDemoHeader )
		{
			// Demo playback should read this as an incoming message.
			m_DemoFile.WriteCmdHeader( dem_stop, GetRecordingTick() );

			// update demo header infos
			m_DemoFile.m_DemoHeader.playback_ticks	= GetRecordingTick();
			m_DemoFile.m_DemoHeader.playback_time	= host_state.interval_per_tick * GetRecordingTick();
			m_DemoFile.m_DemoHeader.playback_frames = m_nFrameCount;

			// go back to header and write demoHeader with correct time and #frame again
			m_DemoFile.WriteDemoHeader();

			ConMsg ("Completed demo, recording time %.1f, game frames %i.\n", 
				m_DemoFile.m_DemoHeader.playback_time, m_DemoFile.m_DemoHeader.playback_frames );
		}

		if ( demo_debug.GetInt() )
		{
			ConMsg ("Closed demo file, %i bytes.\n", m_DemoFile.GetSize() );
		}

		m_DemoFile.Close();

		if( g_ClientDLL ) g_ClientDLL->OnDemoRecordStop();
	}

	m_bCloseDemoFile = false;
	m_bIsDemoHeader = false;

	// clear writing data buffer
	if ( m_MessageData.GetBasePointer() )
	{
		delete [] m_MessageData.GetBasePointer();
		m_MessageData.StartWriting( NULL, 0 );
	}
}

void CDemoRecorder::RecordMessages(bf_read &data, int bits)
{
	if ( m_MessageData.GetBasePointer() && (bits>0) )
	{
		m_MessageData.WriteBitsFromBuffer( &data, bits );

		Assert( !m_MessageData.IsOverflowed() );
	}
}

void CDemoRecorder::RecordPacket()
{
	WriteMessages( m_MessageData );

	m_MessageData.Reset(); // clear message buffer
	
	if ( m_bCloseDemoFile )
	{
		CloseDemoFile();
	}
}

void CDemoRecorder::WriteMessages( bf_write &message )
{
	int len = message.GetNumBytesWritten();

	if (len <= 0)
		return;

	// fill last bits in last byte with NOP if necessary
	int nRemainingBits = message.GetNumBitsWritten() % 8;
	if ( nRemainingBits > 0 &&  nRemainingBits <= (8-NETMSG_TYPE_BITS) )
	{
		message.WriteUBitLong( net_NOP, NETMSG_TYPE_BITS );
	}

	Assert( len < NET_MAX_MESSAGE );

	// if signondata read as fast as possible, no rewind
	// and wait for packet time
	unsigned char cmd = m_bIsDemoHeader ? dem_signon : dem_packet;

	if ( cmd == dem_packet )
	{
		m_nFrameCount++;
	}

	// write command & time
	m_DemoFile.WriteCmdHeader( cmd, GetRecordingTick() ); 
	
	democmdinfo_t info;
	// Snag current info
	GetClientCmdInfo( info );
		
	// Store it
	m_DemoFile.WriteCmdInfo( info );
		
	// write network channel sequencing infos
	int nOutSequenceNr, nInSequenceNr, nOutSequenceNrAck;
	cl.m_NetChannel->GetSequenceData( nOutSequenceNr, nInSequenceNr, nOutSequenceNrAck );
	m_DemoFile.WriteSequenceInfo( nInSequenceNr, nOutSequenceNrAck );
	
	// Output the messge buffer.
	m_DemoFile.WriteRawData( (char*) message.GetBasePointer(), len );

	if ( demo_debug.GetInt() >= 1 )
	{
		Msg( "Writing demo message %i bytes at file pos %i\n", len, m_DemoFile.GetCurPos( false ) );
	}
}

//-----------------------------------------------------------------------------
// Purpose: stop recording a demo
//-----------------------------------------------------------------------------
void CDemoRecorder::StopRecording( void )
{
	if ( !IsRecording() )
	{
		return;
	}

	if ( m_MessageData.GetBasePointer() )
	{
		delete[] m_MessageData.GetBasePointer();
		m_MessageData.StartWriting( NULL, 0);
	}

	CloseDemoFile();
	
	m_bRecording = false;
	m_nDemoNumber = 0;

	g_DemoOverlay.Tick();
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *name - 
//			track - 
//-----------------------------------------------------------------------------
void CDemoRecorder::StartRecording( const char *name, bool bContinuously )
{
	Q_strncpy( m_szDemoBaseName, name, sizeof(m_szDemoBaseName));
	
	m_bRecording		 = true;
	m_nDemoNumber		 = 1;
	m_bResetInterpolation = false;


	g_DemoOverlay.Tick();

	// request a full game update from server 
	cl.ForceFullUpdate();
}

//-----------------------------------------------------------------------------
// Purpose: 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CDemoRecorder::IsRecording( void )
{
	g_DemoOverlay.Tick();

	return m_bRecording;
}

//-----------------------------------------------------------------------------
// Purpose: Called when a demo file runs out, or the user starts a game
// Output : void CDemo::StopPlayback
//-----------------------------------------------------------------------------
void CDemoPlayer::StopPlayback( void )
{
	if ( !IsPlayingBack() )
		return;

	demoaction->StopPlaying();

	m_DemoFile.Close();
	m_bPlayingBack = false;
	m_bLoading = false;
	m_bPlaybackPaused = false;
	m_flAutoResumeTime = 0.0f;
	m_nEndTick = 0;

	if ( m_bTimeDemo )
	{
		g_EngineStats.EndRun();

		if ( !s_bBenchframe )
		{
			WriteTimeDemoResults();
		}
		else
		{
			mat_norendering.SetValue( 0 );
		}

		m_bTimeDemo = false;
	}
	else
	{
		int framecount = host_framecount - m_nTimeDemoStartFrame;
		float demotime = Sys_FloatTime() - m_flTimeDemoStartTime;

		if ( demotime > 0.0f )
		{
			DevMsg( "Demo playback finished ( %.1f seconds, %i render frames, %.2f fps).\n", demotime, framecount, framecount/demotime);
		}

	}
	
	m_flPlaybackRateModifier = 1.0f;

	delete[] m_DemoPacket.data;
	m_DemoPacket.data = NULL;

	scr_demo_override_fov = 0.0f;

	if ( timedemo_runcount.GetInt() > 1 )
	{
		timedemo_runcount.SetValue( timedemo_runcount.GetInt() - 1 );

		Cbuf_AddText( va( "timedemo %s", m_DemoFile.m_szFileName ) );
	}
	else if ( demo_quitafterplayback.GetBool() )
	{
		Cbuf_AddText( "quit\n" );
	}

	g_ClientDLL->OnDemoPlaybackStop();
}

CDemoFile *CDemoPlayer::GetDemoFile( void )
{
	return &m_DemoFile;
}

#define SKIP_TO_TICK_FLAG uint32( uint32( 0x88 ) << 24 )

bool CDemoPlayer::IsSkipping( void )
{
	return m_bPlayingBack && ( m_nSkipToTick != -1 );
}

bool CDemoPlayer::IsLoading( void )
{
	return m_bLoading;
}

int CDemoPlayer::GetTotalTicks(void)
{
	return m_DemoFile.m_DemoHeader.playback_ticks;	
}

void CDemoPlayer::SkipToTick( int tick, bool bRelative, bool bPause )
{
	if ( bRelative )
	{
		tick = GetPlaybackTick() + tick;
	}

	if ( tick < 0 )
		return;

	if ( tick < GetPlaybackTick() )
	{
		// we have to reload the whole demo file
		// we need to create a temp copy of the filename
		char fileName[MAX_OSPATH];
		Q_strncpy( fileName, m_DemoFile.m_szFileName, sizeof(fileName) );

		// reload current demo file
		ETWMarkPrintf( "DemoPlayer: Reloading demo file '%s'", fileName );
		StartPlayback( fileName, m_bTimeDemo );

		// Make sure the proper skipping occurs after reload
		if ( tick > 0 )
			tick |= SKIP_TO_TICK_FLAG;
	}

	m_nSkipToTick = tick;
	ETWMark1I( "DemoPlayer: SkipToTick", tick );

	if ( bPause )
		PausePlayback( -1 );
}

void CDemoPlayer::SetEndTick( int tick )
{
	if ( tick < 0 )
		return;

	m_nEndTick = tick;
}

//-----------------------------------------------------------------------------
// Purpose: Read in next demo message and send to local client over network channel, if it's time.
// Output : bool 
//-----------------------------------------------------------------------------
bool CDemoPlayer::ParseAheadForInterval( int curtick, int intervalticks )
{
	int			tick = 0;
	int			dummy;
	byte		cmd = dem_stop;

	democmdinfo_t	nextinfo;

	long		starting_position = m_DemoFile.GetCurPos( true );

	// remove all entrys older than 32 ticks
	while ( m_DestCmdInfo.Count() > 0 )
	{
		DemoCommandQueue& entry = m_DestCmdInfo[ 0 ];

		if ( entry.tick >= (curtick - 32)  )
			break;

		if ( entry.filepos >= starting_position )
			break;

		 m_DestCmdInfo.Remove( 0 );
	}

	if ( m_bTimeDemo ) 
		return false;

	while ( true )
	{
		// skip forward to the next dem_packet or dem_signon
		bool swallowmessages = true;
		do
		{
			m_DemoFile.ReadCmdHeader( cmd, tick );

			// COMMAND HANDLERS
			switch ( cmd )
			{
			case dem_synctick:
			case dem_stop:
				{
					m_DemoFile.SeekTo( starting_position, true );
					return false;
				}
				break;
			case dem_consolecmd:
				{
					m_DemoFile.ReadConsoleCommand();
				}
				break;
			case dem_datatables:
				{
					m_DemoFile.ReadNetworkDataTables( NULL );
				}
				break;
			case dem_usercmd:
				{
					m_DemoFile.ReadUserCmd( NULL, dummy );
				}
				break;
			case dem_stringtables:
				{
					m_DemoFile.ReadStringTables( NULL );
				}
				break;
			default:
				{
					swallowmessages = false;
				}
				break;
			}
		}
		while ( swallowmessages );

		int curpos = m_DemoFile.GetCurPos( true );

		// we read now a dem_packet
		m_DemoFile.ReadCmdInfo( nextinfo );
		m_DemoFile.ReadSequenceInfo( dummy, dummy ); 
		m_DemoFile.ReadRawData( NULL, 0 );

		DemoCommandQueue entry;
		entry.info = nextinfo;
		entry.tick = tick;
		entry.filepos = curpos;

		int i = 0;
		int c =  m_DestCmdInfo.Count();
		for ( ; i < c; ++i )
		{
			if (  m_DestCmdInfo[ i ].filepos == entry.filepos )
				break; // cmdinfo is already in list
		}

		if ( i >= c )
		{
			// add cmdinfo to list
			if ( c > 0 )
			{
				if (  m_DestCmdInfo[ c - 1 ].tick > tick )
				{
					 m_DestCmdInfo.RemoveAll();
				}
			}

			m_DestCmdInfo.AddToTail( entry );
		}

		if ( ( tick - curtick ) > intervalticks )
			break;
	}

	m_DemoFile.SeekTo( starting_position, true );

	return true;
}

//-----------------------------------------------------------------------------
// Purpose: Read in next demo message and send to local client over network channel, if it's time.
// Output : netpacket_t* -- NULL if there is no packet available at this time.
//-----------------------------------------------------------------------------
netpacket_t *CDemoPlayer::ReadPacket( void )
{
	int			tick = 0;
	byte		cmd = dem_signon;
	long		curpos = 0;

	if ( ! m_DemoFile.IsOpen() )
	{
		m_bPlayingBack = false;
		Host_EndGame( true, "Tried to read a demo message with no demo file\n" );
		return NULL;
	}

	// If game is still shutting down, then don't read any demo messages from file quite yet
	if ( HostState_IsGameShuttingDown() )
	{
		return NULL;
	}

	Assert( IsPlayingBack() );

	if ( IsSkipping() )
	{
		// Every nMaxConsecutiveSkipPackets frames return NULL so that we don't build up an
		// endless supply of unprocessed packets. This avoids causing overflows and excessive
		// "highwater marks" in various Dota subsystems.
		++m_nSkipPacketsPlayed;
		if ( m_nSkipPacketsPlayed >= nMaxConsecutiveSkipPackets )
		{
			m_nSkipPacketsPlayed = 0;
			return NULL;
		}
	}
	else
	{
		m_nSkipPacketsPlayed = 0;
	}

	// External editor has paused playback
	if ( CheckPausedPlayback() )
		return NULL;

	bool bStopReading = false;
	
	while ( !bStopReading )
	{
		curpos = m_DemoFile.GetCurPos( true );

		m_DemoFile.ReadCmdHeader( cmd, tick );

		// always read control commands 
		if ( !IsControlCommand( cmd ) )
		{
			int playbacktick = GetPlaybackTick();

#if defined( RAD_TELEMETRY_ENABLED )
			g_Telemetry.playbacktick = playbacktick;
#endif

			// If the end tick is set, check to see if we should bail
			if ( m_nEndTick > 0 && playbacktick >= m_nEndTick )
			{
				m_nEndTick = 0;
				return NULL;
			}

			if ( !m_bTimeDemo )
			{
				// Time demo ignores clocks and tries to synchronize frames to what was recorded
				//  I.e., while frame is the same, read messages, otherwise, skip out.
				// If we're still signing on, then just parse messages until fully connected no matter what
				if ( cl.IsActive() &&
					(tick > playbacktick) && !IsSkipping() )
				{
					// is not time yet
					bStopReading = true;
				}
			}
			else
			{
				if ( m_nTimeDemoCurrentFrame == host_framecount )
				{
					// If we are playing back a timedemo, and we've already passed on a 
					//  frame update for this host_frame tag, then we'll just skip this mess
					bStopReading = true;
				}
			}

			if ( bStopReading )
			{
				demoaction->Update( false, playbacktick, TICKS_TO_TIME( playbacktick )  );
				m_DemoFile.SeekTo( curpos, true ); // go back to start of current demo command
				return NULL;   // Not time yet, dont return packet data.
			}
		}

		// COMMAND HANDLERS
		switch ( cmd )
		{
		case dem_synctick:
			{
				if ( demo_debug.GetBool() )
				{
					Msg( "%d dem_synctick\n", tick );
				}

				ResyncDemoClock();

				// Once demo clock got resync-ed we can go ahead and
				// perform skipping logic normally
				if ( ( m_nSkipToTick != -1 ) &&
					 ( ( m_nSkipToTick & SKIP_TO_TICK_FLAG ) == SKIP_TO_TICK_FLAG ) )
				{
					m_nSkipToTick &= ~SKIP_TO_TICK_FLAG;
				}
			}
			break;
		case dem_stop:
			{
				if ( demo_debug.GetBool() )
				{
					Msg( "%d dem_stop\n", tick );
				}

				OnStopCommand();

				return NULL;
			}
			break;
		case dem_consolecmd:
			{
				const char * command = m_DemoFile.ReadConsoleCommand();

				if ( demo_debug.GetBool() )
				{
					Msg( "%d dem_consolecmd [%s]\n", tick, command );
				}

				Cbuf_AddText( command );
				Cbuf_Execute();
			}
			break;
		case dem_datatables:
			{
				if ( demo_debug.GetBool() )
				{
					Msg( "%d dem_datatables\n", tick );
				}

				void *data = malloc( 256*1024 ); // X360TBD: How much memory is really needed here?
				bf_read buf( "dem_datatables", data, 256*1024 );
				m_DemoFile.ReadNetworkDataTables( &buf );
				buf.Seek( 0 );								// re-read data

				// support for older engine demos
				if ( !DataTable_LoadDataTablesFromBuffer( &buf, m_DemoFile.m_DemoHeader.demoprotocol ) )
				{
					Host_Error( "Error parsing network data tables during demo playback." );
				}
				free( data );
			}
			break;
		case dem_stringtables:
			{
				void *data = NULL;
				int dataLen = 512 * 1024;
				while ( dataLen <= DEMO_FILE_MAX_STRINGTABLE_SIZE )
				{
					data = realloc( data, dataLen );
					bf_read buf( "dem_stringtables", data, dataLen );
					// did we successfully read
					if ( m_DemoFile.ReadStringTables( &buf ) > 0 )
					{
						buf.Seek( 0 );
						if ( !networkStringTableContainerClient->ReadStringTables( buf ) )
						{
							Host_Error( "Error parsing string tables during demo playback." );
						}
						break;
					}

					// Didn't fit.  Try doubling the size of the buffer
					dataLen *= 2;
				}

				if ( dataLen > DEMO_FILE_MAX_STRINGTABLE_SIZE )
				{
					Warning( "ReadPacket failed to read string tables. Trying to read string tables that's bigger than max string table size\n" );
				}

				free( data );
			}
			break;
		case dem_usercmd:
			{

				if ( demo_debug.GetBool() )
				{
					Msg( "%d dem_usercmd\n", tick );
				}

				char buffer[256];
				int  length = sizeof(buffer);
				int outgoing_sequence = m_DemoFile.ReadUserCmd( buffer, length );

				// put it into a bitbuffer 
				bf_read msg( "CDemo::ReadUserCmd", buffer, length );

				g_ClientDLL->DecodeUserCmdFromBuffer( msg, outgoing_sequence );

				// Note, we need to have the current outgoing sequence correct so we can do prediction
				//  correctly during playback
				cl.lastoutgoingcommand = outgoing_sequence;
				
			}
			break;
		default:
			{
				bStopReading = true;

				if ( IsSkipping() )
				{
					// adjust playback host_tickcount when skipping
					m_nStartTick = host_tickcount - tick;
				}
			}
			break;
		}
	}

	if ( cmd == dem_packet )
	{
		// remember last frame we read a dem_packet update
		m_nTimeDemoCurrentFrame = host_framecount;
	}
	
	int inseq, outseqack, outseq = 0;

	m_DemoFile.ReadCmdInfo( m_LastCmdInfo );

	m_DemoFile.ReadSequenceInfo( inseq, outseqack );
	cl.m_NetChannel->SetSequenceData( outseq, inseq, outseqack );

	int length = m_DemoFile.ReadRawData( (char*)m_DemoPacket.data,  NET_MAX_PAYLOAD );

	if ( demo_debug.GetBool() )
	{
		Msg( "%d network packet [%d]\n", tick, length );
	}

	if ( length > 0 )
	{
		// succsessfully read new demopacket
		m_DemoPacket.received = realtime;
		m_DemoPacket.size = length;
		m_DemoPacket.message.StartReading( m_DemoPacket.data,  m_DemoPacket.size );
	
		if ( demo_debug.GetInt() >= 1 )
		{
			Msg( "Demo message, tick %i, %i bytes\n", GetPlaybackTick(), length );
		}
	}

	// Try and jump ahead one frame
	m_bInterpolateView = ParseAheadForInterval( tick, 8 );

	// ConMsg( "Reading message for %i : %f skip %i\n", m_nFrameCount, fElapsedTime, forceskip ? 1 : 0 );

	// Skip a few ticks before doing any timing
	if ( (m_nTimeDemoStartFrame < 0) && GetPlaybackTick() > 100 )
	{
		m_nTimeDemoStartFrame = host_framecount;
		m_flTimeDemoStartTime = Sys_FloatTime();
		m_flTotalFPSVariability = 0.0f;

		if ( m_bTimeDemo )
		{
			g_EngineStats.BeginRun();
		}
	}

	if ( m_nSnapshotTick > 0 && m_nSnapshotTick <= GetPlaybackTick() )
	{
		const char *filename = "benchframe";

		if ( m_SnapshotFilename[0] )
			filename = m_SnapshotFilename;

		CL_TakeScreenshot( filename ); // take a screenshot
		m_nSnapshotTick = 0;

		if ( s_bBenchframe )
		{
			Cbuf_AddText( "stopdemo\n" );
		}
	}

	return &m_DemoPacket;
}

void CDemoPlayer::InterpolateDemoCommand( int targettick, DemoCommandQueue& prev, DemoCommandQueue& next )
{
	CUtlVector< DemoCommandQueue >& list = m_DestCmdInfo;
	int c = list.Count();

	prev.info.Reset();
	next.info.Reset();

	if ( c < 2 )
	{
		// we need at least two entries to interpolate
		return; 
	}

	int i = 0;
	int savedI = -1;

	DemoCommandQueue *entry1 = &list[ i ];
	DemoCommandQueue *entry2 = &list[ i+1 ];

	while ( true )
	{
		if ( (entry1->tick <= targettick) && (entry2->tick > targettick) )
		{
			// Means we hit a FDEMO_NOINTERP along the way to now
			if ( savedI != -1 )
			{
				prev = list[ savedI ];
				next = list[ savedI + 1 ];
			}
			else
			{
				prev = *entry1;
				next = *entry2;
			}
			return;
		}

		// If any command between the previous target and now has the FDEMO_NOINTERP, we need to stop at the command just before that (entry), so we save off the I
		// We can't just return since we need to see if we actually get to a spanning pair (though we always should).  Also, we only latch this final interp spot on
		///  the first FDEMO_NOINTERP we see
		if ( savedI == -1 &&
			 entry2->tick > m_nPreviousTick &&
			 entry2->tick <= targettick &&
			 entry2->info.flags & FDEMO_NOINTERP )
		{
			savedI = i;
		}

		if ( i+2 == c )
			break;

		i++;
		entry1 = &list[ i ];
		entry2 = &list[ i+1 ];
	}	

	Assert( 0 );
}

static ConVar demo_legacy_rollback( "demo_legacy_rollback", "1", 0, "Use legacy view interpolation rollback amount in demo playback." );

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CDemoPlayer::InterpolateViewpoint( void )
{
	if ( !IsPlayingBack() )
		return;

	democmdinfo_t outinfo;
	outinfo.Reset();

	bool bHasValidData =
		 m_LastCmdInfo.viewOrigin != vec3_origin ||
		 m_LastCmdInfo.viewAngles != vec3_angle ||
		 m_LastCmdInfo.localViewAngles != vec3_angle ||
		 m_LastCmdInfo.flags != 0;

	int nTargetTick = GetPlaybackTick();

	// Player view needs to be one tick interval in the past like the client DLL entities
	if ( cl.m_nMaxClients == 1 )
	{
		if ( demo_legacy_rollback.GetBool() )
		{
			nTargetTick -= TIME_TO_TICKS( cl.GetClientInterpAmount() ) + 1;
		}
		else
		{
			nTargetTick -= 1;
		}
	}

	float vel = 0.0f;
	float angVel = 0.0f; 
	if ( m_bInterpolateView && demo_interpolateview.GetBool() && bHasValidData )
	{
		DemoCommandQueue prev, next;
		float frac = 0.0f;

		prev.info = m_LastCmdInfo;
		prev.tick = -1;
		next.info = m_LastCmdInfo;
		next.tick = -1;

		// Determine current time slice
		
		InterpolateDemoCommand( nTargetTick, prev, next );

		float dt = TICKS_TO_TIME(next.tick-prev.tick);

		frac = (TICKS_TO_TIME(nTargetTick-prev.tick)+cl.m_tickRemainder)/dt;

		frac = clamp( frac, 0.0f, 1.0f );

		// Now interpolate
		Vector delta;

		Vector startorigin = prev.info.GetViewOrigin();
		Vector destorigin = next.info.GetViewOrigin();

		// check for teleporting - since there can be multiple cmd packets between a game frame,
		// we need to check from the last actually ran command to see if there was a teleport
		VectorSubtract( destorigin, m_LastCmdInfo.GetViewOrigin(), delta );
		float distmoved = delta.Length();
		
		if ( dt > 0.0f )
		{
			vel = distmoved / dt;
		}

		if ( dt > 0.0f )
		{
			QAngle startang = prev.info.GetLocalViewAngles();
			QAngle destang = next.info.GetLocalViewAngles();
	
			for ( int i = 0; i < 3; ++i )
			{
				float dAng = AngleNormalizePositive( destang[ i ] ) - AngleNormalizePositive( startang[ i ] );
				dAng = AngleNormalize( dAng );
				float aVel = fabs( dAng ) / dt;
				if ( aVel > angVel )
				{
					angVel = aVel;
				}
			}
		}

		// FIXME: This should be velocity based maybe?
		if ( (vel > demo_interplimit.GetFloat()) || 
			 (angVel > demo_avellimit.GetFloat() ) ||
			m_bResetInterpolation )
		{
			m_bResetInterpolation = false;

			// it's a teleport, just let it happen naturally next frame
			// setting frac to 1.0 (like it was previously) would just mean that we
			// are teleporting a frame ahead of when we should
			outinfo.viewOrigin = m_LastCmdInfo.GetViewOrigin();
			outinfo.viewAngles = m_LastCmdInfo.GetViewAngles();
			outinfo.localViewAngles = m_LastCmdInfo.GetLocalViewAngles();
		}
		else
		{
			outinfo.viewOrigin = startorigin + frac * ( destorigin - startorigin );

			Quaternion src, dest;
			Quaternion result;

			AngleQuaternion( prev.info.GetViewAngles(), src );
			AngleQuaternion( next.info.GetViewAngles(), dest );
			QuaternionSlerp( src, dest, frac, result );

			QuaternionAngles( result, outinfo.viewAngles );

			AngleQuaternion( prev.info.GetLocalViewAngles(), src );
			AngleQuaternion( next.info.GetLocalViewAngles(), dest );
			QuaternionSlerp( src, dest, frac, result );

			QuaternionAngles( result, outinfo.localViewAngles );
		}
	}
	else if ( bHasValidData )
	{
		// don't interpolate, just copy values
		outinfo.viewOrigin = m_LastCmdInfo.GetViewOrigin();
		outinfo.viewAngles = m_LastCmdInfo.GetViewAngles();
		outinfo.localViewAngles = m_LastCmdInfo.GetLocalViewAngles();
	}

	m_nPreviousTick = nTargetTick;

	// let any demo system override view ( drive, editor, smoother etc)
	bHasValidData |= OverrideView( outinfo );

	if ( !bHasValidData )
		return; // no validate data & no override, exit

	g_pClientSidePrediction->SetViewOrigin( outinfo.viewOrigin );
	g_pClientSidePrediction->SetViewAngles( outinfo.viewAngles );
	g_pClientSidePrediction->SetLocalViewAngles( outinfo.localViewAngles );
#ifndef SWDS
	VectorCopy( outinfo.viewAngles, cl.viewangles );
#endif

	
}

//-----------------------------------------------------------------------------
// Purpose: 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CDemoPlayer::IsPlayingTimeDemo( void )
{
	return m_bTimeDemo && m_bPlayingBack;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CDemoPlayer::IsPlayingBack( void )
{
	return m_bPlayingBack;
}

CDemoPlayer::CDemoPlayer()
{
	m_flAutoResumeTime = 0.0f;
	m_flPlaybackRateModifier = 1.0f;
	m_bTimeDemo = false;	
	m_nTimeDemoStartFrame = -1;	
	m_flTimeDemoStartTime = 0.0f;	
	m_flTotalFPSVariability = 0.0f;
	m_nTimeDemoCurrentFrame = -1; 
	m_bPlayingBack = false;
	m_bLoading = false;
	m_bPlaybackPaused = false;
	m_nSkipToTick = -1;
	m_nSkipPacketsPlayed = 0;
	m_nSnapshotTick = 0;
	m_SnapshotFilename[0] = 0;
	m_bResetInterpolation = false;
	m_nPreviousTick = 0;
	m_nEndTick = 0;
}

CDemoPlayer::~CDemoPlayer()
{
	StopPlayback();
	if ( g_ClientDLL )
	{
		g_ClientDLL->OnDemoPlaybackStop();
	}
}

//-----------------------------------------------------------------------------
// Purpose: Start's demo playback
// Input  : *name - 
//-----------------------------------------------------------------------------
bool CDemoPlayer::StartPlayback( const char *filename, bool bAsTimeDemo )
{
	m_bLoading = true;

	SCR_BeginLoadingPlaque();

	// Disconnect from server or stop running one
	int oldn = cl.demonum;
	cl.demonum = -1;
	Host_Disconnect(false);
	cl.demonum = oldn;

	if ( !m_DemoFile.Open( filename, true )  )
	{
		cl.demonum = -1;		// stop demo loop
		return false;
	}

	// Read in the m_DemoHeader
	demoheader_t *dh = m_DemoFile.ReadDemoHeader();

	if ( !dh )
	{
		ConMsg( "Failed to read demo header.\n" );
		m_DemoFile.Close();
		cl.demonum = -1;
		return false;
	}
	
	ConMsg ("Playing demo from %s.\n", filename);

	// Now read in the directory structure.
	m_bPlayingBack = true;
	cl.m_nSignonState= SIGNONSTATE_CONNECTED;

	ResyncDemoClock(); 

	// create a fake channel with a NULL address
	cl.m_NetChannel = NET_CreateNetChannel( NS_CLIENT, NULL, "DEMO", &cl, false, dh->networkprotocol );

	if ( !cl.m_NetChannel )
	{
		ConMsg ("CDemo::Play: failed to create demo net channel\n" );
		m_DemoFile.Close();
		cl.demonum = -1;		// stop demo loop
		Host_Disconnect(true);
	}
	
	cl.m_NetChannel->SetTimeout( -1.0f );	// never timeout
	
	Q_memset( &m_DemoPacket, 0, sizeof(m_DemoPacket) );

	// setup demo packet data buffer
	m_DemoPacket.data = new unsigned char[NET_MAX_PAYLOAD];
	m_DemoPacket.from.SetType( NA_LOOPBACK);
		
	cl.chokedcommands = 0;
	cl.lastoutgoingcommand = -1;
 	cl.m_flNextCmdTime = net_time;

	m_bTimeDemo = bAsTimeDemo;
	m_nTimeDemoCurrentFrame = -1;
	m_nTimeDemoStartFrame = -1;

	if ( m_bTimeDemo )
	{
		SeedRandomNumberGenerator( true );
	}

	demoaction->StartPlaying( filename );

	// m_bFastForwarding = false;
	m_flAutoResumeTime = 0.0f;
	m_flPlaybackRateModifier = 1.0f;

	scr_demo_override_fov = 0.0f;

	m_bLoading = false;

	return true;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : flCurTime - 
//-----------------------------------------------------------------------------
void CDemoPlayer::MarkFrame( float flFPSVariability )
{
	m_flTotalFPSVariability += flFPSVariability;
}

void CDemoPlayer::WriteTimeDemoResults( void )
{
	int		frames;
	float	time;
	frames = (host_framecount - m_nTimeDemoStartFrame) - 1;
	time = Sys_FloatTime() - m_flTimeDemoStartTime;
	if (!time)
	{
		time = 1;
	}
	float flVariability = (m_flTotalFPSVariability / (float)frames);
	ConMsg ("%i frames %5.3f seconds %5.2f fps (%5.2f ms/f) %5.3f fps variability\n", frames, time, frames/time, 1000*time/frames, flVariability );
	bool bFileExists = g_pFileSystem->FileExists( "SourceBench.csv" );
	FileHandle_t fileHandle = g_pFileSystem->Open( "SourceBench.csv", "a+" );
	int width, height;
	CMatRenderContextPtr pRenderContext( materials );
	pRenderContext->GetWindowSize( width, height );

	const MaterialSystem_Config_t &config = materials->GetCurrentConfigForVideoCard();

	if( !bFileExists )
	{
		g_pFileSystem->FPrintf( fileHandle, "demofile," );
		g_pFileSystem->FPrintf( fileHandle, "fps," );
		g_pFileSystem->FPrintf( fileHandle, "framerate variability," );
		g_pFileSystem->FPrintf( fileHandle, "totaltime," );
		g_pFileSystem->FPrintf( fileHandle, "numframes," );
		g_pFileSystem->FPrintf( fileHandle, "width," );
		g_pFileSystem->FPrintf( fileHandle, "height," );
		g_pFileSystem->FPrintf( fileHandle, "windowed," );
		g_pFileSystem->FPrintf( fileHandle, "vsync," );
		g_pFileSystem->FPrintf( fileHandle, "MSAA," );
		g_pFileSystem->FPrintf( fileHandle, "Aniso," );
		g_pFileSystem->FPrintf( fileHandle, "dxlevel," );
		g_pFileSystem->FPrintf( fileHandle, "cmdline," );
		g_pFileSystem->FPrintf( fileHandle, "driver name," );
		g_pFileSystem->FPrintf( fileHandle, "vendor id," );
		g_pFileSystem->FPrintf( fileHandle, "device id," );

//		g_pFileSystem->FPrintf( fileHandle, "sound," );
		g_pFileSystem->FPrintf( fileHandle, "Reduce fillrate," );
		g_pFileSystem->FPrintf( fileHandle, "reflect entities," );
		g_pFileSystem->FPrintf( fileHandle, "motion blur," );
		g_pFileSystem->FPrintf( fileHandle, "flashlight shadows," );
		g_pFileSystem->FPrintf( fileHandle, "mat_reduceparticles," );
		g_pFileSystem->FPrintf( fileHandle, "r_dopixelvisibility," );
		g_pFileSystem->FPrintf( fileHandle, "nulldevice," );
		g_pFileSystem->FPrintf( fileHandle, "timedemo_comment," );
		g_pFileSystem->FPrintf( fileHandle, "\n" );
	}

	ConVarRef mat_vsync( "mat_vsync" );
	ConVarRef mat_antialias( "mat_antialias" );
	ConVarRef mat_forceaniso( "mat_forceaniso" );
	ConVarRef r_waterforcereflectentities( "r_waterforcereflectentities" );
	ConVarRef mat_motion_blur_enabled( "mat_motion_blur_enabled" );
	ConVarRef r_flashlightdepthtexture( "r_flashlightdepthtexture" );
	ConVarRef mat_reducefillrate( "mat_reducefillrate" );
	ConVarRef mat_reduceparticles( "mat_reduceparticles" );
	ConVarRef r_dopixelvisibility( "r_dopixelvisibility" );

	g_pFileSystem->Seek( fileHandle, 0, FILESYSTEM_SEEK_TAIL );
	MaterialAdapterInfo_t info;
	materials->GetDisplayAdapterInfo( materials->GetCurrentAdapter(), info );
	g_pFileSystem->FPrintf( fileHandle, "%s,", m_DemoFile.m_szFileName );
	g_pFileSystem->FPrintf( fileHandle, "%5.1f,", frames/time );
	g_pFileSystem->FPrintf( fileHandle, "%5.1f,", flVariability );
	g_pFileSystem->FPrintf( fileHandle, "%5.1f,", time );
	g_pFileSystem->FPrintf( fileHandle, "%i,", frames );
	g_pFileSystem->FPrintf( fileHandle, "%i,", width );
	g_pFileSystem->FPrintf( fileHandle, "%i,", height );
	g_pFileSystem->FPrintf( fileHandle, "%s,", config.Windowed() ? "windowed" : "fullscreen");
	g_pFileSystem->FPrintf( fileHandle, "%s,", mat_vsync.GetBool() ? "on" : "off" );
	g_pFileSystem->FPrintf( fileHandle, "%d,", mat_antialias.GetInt() );
	g_pFileSystem->FPrintf( fileHandle, "%d,", mat_forceaniso.GetInt() );
	g_pFileSystem->FPrintf( fileHandle, "%s,", COM_DXLevelToString( g_pMaterialSystemHardwareConfig->GetDXSupportLevel() ) );
	g_pFileSystem->FPrintf( fileHandle, "%s,", CommandLine()->GetCmdLine() );
	g_pFileSystem->FPrintf( fileHandle, "%s,", info.m_pDriverName );
	g_pFileSystem->FPrintf( fileHandle, "0x%x,", info.m_VendorID );
	g_pFileSystem->FPrintf( fileHandle, "0x%x,", info.m_DeviceID );

//	g_pFileSystem->FPrintf( fileHandle, "%s,", CommandLine()->CheckParm( "-nosound" ) ? "off" : "on" );
	g_pFileSystem->FPrintf( fileHandle, "%s,", mat_reducefillrate.GetBool() ? "on" : "off" );
	g_pFileSystem->FPrintf( fileHandle, "%s,", r_waterforcereflectentities.GetBool() ? "on" : "off" );
	g_pFileSystem->FPrintf( fileHandle, "%s,", mat_motion_blur_enabled.GetBool() ? "on" : "off" );
	g_pFileSystem->FPrintf( fileHandle, "%s,", r_flashlightdepthtexture.GetBool() ? "on" : "off" );
	g_pFileSystem->FPrintf( fileHandle, "%s,", mat_reduceparticles.GetBool() ? "on" : "off" );
	g_pFileSystem->FPrintf( fileHandle, "%s,", r_dopixelvisibility.GetBool() ? "on" : "off" );
	g_pFileSystem->FPrintf( fileHandle, "%s,", CommandLine()->CheckParm( "-nulldevice" ) ? "yes" : "no" );

	int itimedemo_comment = CommandLine()->FindParm( "-timedemo_comment" );
	const char *timedemo_comment = itimedemo_comment ? CommandLine()->GetParm( itimedemo_comment + 1 ) : "";
	g_pFileSystem->FPrintf( fileHandle, "%s,", timedemo_comment );
	g_pFileSystem->FPrintf( fileHandle, "\n" );
	g_pFileSystem->Close( fileHandle );
}


void CDemoPlayer::PausePlayback( float seconds  )
{
	m_bPlaybackPaused = true;
	
	if ( seconds > 0.0f )
	{
		// Use true clock since everything else is frozen
		m_flAutoResumeTime = Sys_FloatTime() + seconds;
	}
	else
	{
		m_flAutoResumeTime = 0.0f;
	}
}

void CDemoPlayer::ResumePlayback()
{
	m_bPlaybackPaused = false;
	m_flAutoResumeTime = 0.0f;
}

bool CDemoPlayer::CheckPausedPlayback()
{
	if ( demo_pauseatservertick.GetInt() > 0 )
	{
		if ( cl.GetServerTickCount() >= demo_pauseatservertick.GetInt() )
		{
			PausePlayback( -1 );
			ETWMark1I( "DemoPlayer: Reached pause tick", cl.GetServerTickCount() );
			m_nSkipToTick = -1;
			demo_pauseatservertick.SetValue( 0 );
			Msg( "Demo paused at server tick %i\n", cl.GetServerTickCount() );
		}
	}
	
	if ( IsSkipping() )
	{
		if ( ( m_nSkipToTick > GetPlaybackTick() ) ||
			 ( ( m_nSkipToTick & SKIP_TO_TICK_FLAG ) == SKIP_TO_TICK_FLAG ) )
		{
			// we are skipping
			return false;
		}
		else
		{
			// we can't skip back (or finished skipping), so disable skipping
			ETWMark1I( "DemoPlayer: SkipToTick done", GetPlaybackTick() );
			m_nSkipToTick = -1;
		}
	}

	if ( !IsPlaybackPaused() )
		return false;

	if ( m_bPlaybackPaused )
	{
		if ( (m_flAutoResumeTime > 0.0f) &&
			 (Sys_FloatTime() >= m_flAutoResumeTime) )
		{
			// it's time to unpause replay
			ResumePlayback();
		}
	}

	return m_bPlaybackPaused;
}

bool CDemoPlayer::IsPlaybackPaused()
{
	if ( !IsPlayingBack() )
		return false;

	// never pause while reading signon data
	if ( m_nTimeDemoCurrentFrame < 0 )
		return false;

	// If skipping then do not pretend paused
	if ( IsSkipping() )
		return false;
	
	return m_bPlaybackPaused;
}

int CDemoPlayer::GetPlaybackStartTick( void )
{
	return m_nStartTick;
}

int CDemoPlayer::GetPlaybackTick( void )
{
	return host_tickcount - m_nStartTick;
}

void CDemoPlayer::ResyncDemoClock()
{
	m_nStartTick = host_tickcount;
	m_nPreviousTick = m_nStartTick;
}

float CDemoPlayer::GetPlaybackTimeScale()
{
	return m_flPlaybackRateModifier;
}

void CDemoPlayer::SetPlaybackTimeScale(float timescale)
{
	m_flPlaybackRateModifier = timescale;
}

void CDemoPlayer::SetBenchframe( int tick, const char *filename )
{
	m_nSnapshotTick = tick;

	if ( filename )
	{
		Q_strncpy( m_SnapshotFilename, filename, sizeof(m_SnapshotFilename) );
	}
}

static bool ComputeNextIncrementalDemoFilename( char *name, int namesize )
{
	FileHandle_t test;
	
	test = g_pFileSystem->Open( name, "rb" );
	if ( FILESYSTEM_INVALID_HANDLE == test )
	{
		// file doesn't exist, so we can use that 
		return true;
	}
	g_pFileSystem->Close( test );

	char basename[ MAX_OSPATH ];

	Q_StripExtension( name, basename, sizeof( basename ) );

	// Start looking for a valid name
	int i = 0;
	for ( i = 0; i < 1000; i++ )
	{
		char newname[ MAX_OSPATH ];
		Q_snprintf( newname, sizeof( newname ), "%s%03i.dem", basename, i );

		test = g_pFileSystem->Open( newname, "rb" );
		if ( FILESYSTEM_INVALID_HANDLE == test )
		{
			Q_strncpy( name, newname, namesize );
			return true;
		}
		g_pFileSystem->Close( test );
	}

	ConMsg( "Unable to find a valid incremental demo filename for %s, try clearing the directory of %snnn.dem\n", name, basename );
	return false;
}

//-----------------------------------------------------------------------------
// Purpose: List the contents of a demo file.
//-----------------------------------------------------------------------------
void CL_ListDemo_f( const CCommand &args )
{
	if ( cmd_source != src_command )
		return;

	// Find the file
	char name[MAX_OSPATH];

	Q_snprintf (name, sizeof(name), "%s", args[1]);
	
	Q_DefaultExtension( name, ".dem", sizeof( name ) );

	ConMsg ("Demo contents for %s:\n", name);

	CDemoFile demofile;

	if ( !demofile.Open( name, true ) )
	{
		ConMsg ("ERROR: couldn't open.\n");
		return;
	}

	demofile.ReadDemoHeader();

	demoheader_t *header = &demofile.m_DemoHeader;

	if ( !header )
	{
		ConMsg( "Failed reading demo header.\n" );
		demofile.Close();
		return;
	}
	
	if ( Q_strcmp ( header->demofilestamp, DEMO_HEADER_ID ) )
	{
		ConMsg( "%s is not a valid demo file\n", name);
		return;
	}

	ConMsg("Network protocol: %i\n", header->networkprotocol);
	ConMsg("Demo version    : %i\n", header->demoprotocol);
	ConMsg("Server name     : %s\n", header->servername);
	ConMsg("Map name        : %s\n", header->mapname);
	ConMsg("Game            : %s\n", header->gamedirectory);
	ConMsg("Player name     : %s\n", header->clientname);
	ConMsg("Time            : %.1f\n", header->playback_time);
	ConMsg("Ticks           : %i\n", header->playback_ticks);
	ConMsg("Frames          : %i\n", header->playback_frames);
	ConMsg("Signon size     : %i\n", header->signonlength);
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CON_COMMAND( stop, "Finish recording demo." )
{
	if ( cmd_source != src_command )
		return;

	if ( !demorecorder->IsRecording() )
	{
		ConDMsg ("Not recording a demo.\n");
		return;
	}

	demorecorder->StopRecording();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CON_COMMAND_F( record, "Record a demo.", FCVAR_DONTRECORD )
{
	if ( g_ClientDLL == NULL )
	{
		ConMsg ("Can't record on dedicated server.\n");
		return;
	}	

	if ( args.ArgC() != 2 && args.ArgC() != 3 )
	{
		ConMsg ("record <demoname> [incremental]\n");
		return;
	}

	bool incremental = false;
	if ( args.ArgC() == 3 )
	{
		if ( !Q_stricmp( args[2], "incremental" ) )
		{
			incremental = true;
		}
	}
	
	if ( demorecorder->IsRecording() )
	{
		ConMsg ("Already recording.\n");
		return;
	}

	if ( demoplayer->IsPlayingBack() )
	{
		ConMsg ("Can't record during demo playback.\n");
		return;
	}

	// check path first
	if ( !COM_IsValidPath( args[1] ) )
	{
		ConMsg( "record %s: invalid path.\n", args[1] );
		return;
	}

	char	name[ MAX_OSPATH ];

	if ( !g_ClientDLL->CanRecordDemo( name, sizeof( name ) ) )
	{
		ConMsg( "%s\n", name );	// re-use name as the error string if the client prevents us from starting a demo
		return;
	}

	// remove .dem extension if user added it
	Q_StripExtension( args[1], name, sizeof( name ) );
	
	if ( incremental )
	{
		// If file exists, construct a better name
		if ( !ComputeNextIncrementalDemoFilename( name, sizeof( name ) ) )
		{
			return;
		}
	}
	// Record it
	demorecorder->StartRecording( name, incremental );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CL_PlayDemo_f( const CCommand &args )
{
	if ( cmd_source != src_command )
		return;

	if ( args.ArgC() != 2 )
	{
		ConMsg ("playdemo <demoname> : plays a demo file\n");
		return;
	}

	// Get the demo filename
	char name[ MAX_OSPATH ];
	Q_strncpy( name, args[1], sizeof( name ) );
	Q_DefaultExtension( name, ".dem", sizeof( name ) );

	// set current demo player to replay demo player?
	demoplayer = g_pClientDemoPlayer;

	//
	// open the demo file
	//
	if ( demoplayer->StartPlayback( name, false ) )
	{
		// Remove extension
		char basename[ MAX_OSPATH ];
		V_StripExtension( name, basename, sizeof( basename ) );
		g_ClientDLL->OnDemoPlaybackStart( basename );
	}
	else
	{
		SCR_EndLoadingPlaque();
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CL_TimeDemo_f( const CCommand &args )
{
	if ( cmd_source != src_command )
		return;

	if ( args.ArgC() < 2 || args.ArgC() > 4 )
	{
		ConMsg ("timedemo <demoname> <optional stats.txt> : gets demo speeds, starting from optional frame\n");
		return;
	}

	if( args.ArgC() >= 3 )
	{
		Q_strncpy( g_pStatsFile, args[ 2 ], sizeof( g_pStatsFile ) );
	}
	else
	{
		Q_strncpy( g_pStatsFile, "UNKNOWN", sizeof( g_pStatsFile ) );
	}

	// set current demo player to client demo player
	demoplayer = g_pClientDemoPlayer;
	
	// open the demo file
	char name[ MAX_OSPATH ];
	Q_strncpy (name, args[1], sizeof( name ) );
	Q_DefaultExtension( name, ".dem", sizeof( name ) );

	if ( !demoplayer->StartPlayback( name, true ) )
	{
		SCR_EndLoadingPlaque();
	}
}

void CL_TimeDemoQuit_f( const CCommand &args )
{
	demo_quitafterplayback.SetValue( 1 );
	CL_TimeDemo_f( args );
}

void CL_BenchFrame_f( const CCommand &args )
{
	if ( cmd_source != src_command )
		return;

	if ( args.ArgC() != 4 )
	{
		ConMsg ("benchframe <demoname> <frame> <tgafilename>: takes a snapshot of a particular frame in a demo\n");
		return;
	}

	g_pClientDemoPlayer->SetBenchframe( max( 0, atoi( args[2] ) ), args[3] );

	s_bBenchframe = true;

	mat_norendering.SetValue( 1 );
	
	// set current demo player to client demo player
	demoplayer = g_pClientDemoPlayer;
	
	// open the demo file
	char name[ MAX_OSPATH ];
	Q_strncpy (name, args[1], sizeof( name ) );
	Q_DefaultExtension( name, ".dem", sizeof( name ) );

	if ( !demoplayer->StartPlayback( name, true ) )
	{
		SCR_EndLoadingPlaque();
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CON_COMMAND( vtune, "Controls VTune's sampling." )
{
 	if ( args.ArgC() != 2 )
	{
		ConMsg ("vtune \"pause\" | \"resume\" : Suspend or resume VTune's sampling.\n");
		return;
	}
	
	if( !Q_strcasecmp( args[1], "pause" ) )
	{
		if(!vtune(false))
		{
			ConMsg("Failed to find \"VTPause()\" in \"vtuneapi.dll\".\n");
			return;
		}

		ConMsg("VTune sampling paused.\n");
	}

	else if( !Q_strcasecmp( args[1], "resume" ) )
	{
		if(!vtune(true))
		{
			ConMsg("Failed to find \"VTResume()\" in \"vtuneapi.dll\".\n");
			return;
		}
		
		ConMsg("VTune sampling resumed.\n");
	}

	else
	{
		ConMsg("Unknown vtune option.\n");
	}

}



CON_COMMAND_AUTOCOMPLETEFILE( playdemo, CL_PlayDemo_f, "Play a recorded demo file (.dem ).", NULL, dem );
CON_COMMAND_AUTOCOMPLETEFILE( timedemo, CL_TimeDemo_f, "Play a demo and report performance info.", NULL, dem );
CON_COMMAND_AUTOCOMPLETEFILE( timedemoquit, CL_TimeDemoQuit_f, "Play a demo, report performance info, and then exit", NULL, dem );
CON_COMMAND_AUTOCOMPLETEFILE( listdemo, CL_ListDemo_f, "List demo file contents.", NULL, dem );
CON_COMMAND_AUTOCOMPLETEFILE( benchframe, CL_BenchFrame_f, "Takes a snapshot of a particular frame in a time demo.", NULL, dem );

CON_COMMAND( demo_pause, "Pauses demo playback." )
{
	float seconds = -1.0;

	if ( args.ArgC() == 2 )
	{
		seconds = atof( args[1] );
	}

	demoplayer->PausePlayback( seconds );
}

CON_COMMAND( demo_resume, "Resumes demo playback." )
{
	demoplayer->ResumePlayback();
}

CON_COMMAND( demo_togglepause, "Toggles demo playback." )
{
	if ( !demoplayer->IsPlayingBack() )
		return;
	
	if ( demoplayer->IsPlaybackPaused() )
	{
		demoplayer->ResumePlayback();
	}
	else
	{
		demoplayer->PausePlayback( -1 );
	}
}

CON_COMMAND( demo_gototick, "Skips to a tick in demo." )
{
	bool bRelative = false;
	bool bPause = false;

	if ( args.ArgC() < 2 )
	{
		Msg("Syntax: demo_gototick <tick> [relative] [pause]\n");
		return;
	}
	
	int nTick = atoi( args[1] );
	
	if ( args.ArgC() >= 3 )
	{
		bRelative = Q_atoi( args[2] ) != 0;
	}

	if ( args.ArgC() >= 4 )
	{
		bPause = Q_atoi( args[3] ) != 0;
	}

	demoplayer->SkipToTick( nTick, bRelative, bPause );
}

CON_COMMAND( demo_setendtick, "Sets end demo playback tick. Set to 0 to disable." )
{
	if ( args.ArgC() != 2 )
	{
		Msg( "Syntax: demo_setendtick <tick>\n" );
		return;
	}

	int nTick = atoi( args[1] );

	demoplayer->SetEndTick( nTick );
}

CON_COMMAND( demo_timescale, "Sets demo replay speed." )
{
	float fScale = 1.0f;

	if ( args.ArgC() == 2 )
	{
		fScale = atof( args[1] );
		fScale = clamp( fScale, 0.0f, 100.0f );
	}

	demoplayer->SetPlaybackTimeScale( fScale );
}

bool CDemoPlayer::OverrideView( democmdinfo_t& info )
{
	if ( g_pDemoUI && g_pDemoUI->OverrideView( info, GetPlaybackTick() ) )
		return true;

	if ( g_pDemoUI2 && g_pDemoUI2->OverrideView( info, GetPlaybackTick() ) )
		return true;

	if ( demoaction && demoaction->OverrideView( info, GetPlaybackTick() ) )
		return true;

	return false;
}

void CDemoPlayer::OnStopCommand()
{
	cl.Disconnect( "Demo stopped", true);
}

void CDemoPlayer::ResetDemoInterpolation( void )
{
	m_bResetInterpolation = true;
}

int CDemoPlayer::GetProtocolVersion()
{
	Assert( IsPlayingBack() );
	if ( !IsPlayingBack() )
		return PROTOCOL_VERSION;

	return m_DemoFile.GetProtocolVersion();
}
