Team Fortress 2 Source Code as on 22/4/2020
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2406 lines
62 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //=============================================================================//
  7. #include "client_pch.h"
  8. #include "enginestats.h"
  9. #include "iprediction.h"
  10. #include "cl_demo.h"
  11. #include "cl_demoactionmanager.h"
  12. #include "cl_pred.h"
  13. #include "baseautocompletefilelist.h"
  14. #include "demofile/demoformat.h"
  15. #include "gl_matsysiface.h"
  16. #include "materialsystem/imaterialsystemhardwareconfig.h"
  17. #include "tier0/etwprof.h"
  18. #include "tier0/icommandline.h"
  19. #include "vengineserver_impl.h"
  20. #include "console.h"
  21. #include "dt_common_eng.h"
  22. #include "net_chan.h"
  23. #include "gl_model_private.h"
  24. #include "decal.h"
  25. #include "icliententitylist.h"
  26. #include "icliententity.h"
  27. #include "cl_demouipanel.h"
  28. #include "materialsystem/materialsystem_config.h"
  29. #include "tier2/tier2.h"
  30. #include "vgui_baseui_interface.h"
  31. #include "con_nprint.h"
  32. #include "networkstringtableclient.h"
  33. #ifdef SWDS
  34. #include "server.h"
  35. #endif
  36. // memdbgon must be the last include file in a .cpp file!!!
  37. #include "tier0/memdbgon.h"
  38. static ConVar demo_recordcommands( "demo_recordcommands", "1", FCVAR_CHEAT, "Record commands typed at console into .dem files." );
  39. static ConVar demo_quitafterplayback( "demo_quitafterplayback", "0", 0, "Quits game after demo playback." );
  40. static ConVar demo_debug( "demo_debug", "0", 0, "Demo debug info." );
  41. static ConVar demo_interpolateview( "demo_interpolateview", "1", 0, "Do view interpolation during dem playback." );
  42. static ConVar demo_pauseatservertick( "demo_pauseatservertick", "0", 0, "Pauses demo playback at server tick" );
  43. static ConVar timedemo_runcount( "timedemo_runcount", "0", 0, "Runs time demo X number of times." );
  44. // singeltons:
  45. static char g_pStatsFile[MAX_OSPATH] = { 0 };
  46. static bool s_bBenchframe = false;
  47. static CDemoRecorder s_ClientDemoRecorder;
  48. CDemoRecorder *g_pClientDemoRecorder = &s_ClientDemoRecorder;
  49. IDemoRecorder *demorecorder = g_pClientDemoRecorder;
  50. static CDemoPlayer s_ClientDemoPlayer;
  51. CDemoPlayer *g_pClientDemoPlayer = &s_ClientDemoPlayer;
  52. IDemoPlayer *demoplayer = g_pClientDemoPlayer;
  53. extern CNetworkStringTableContainer *networkStringTableContainerClient;
  54. // This is the number of units under which we are allowed to interpolate, otherwise pop.
  55. // This fixes problems with in-level transitions.
  56. static ConVar demo_interplimit( "demo_interplimit", "4000", 0, "How much origin velocity before it's considered to have 'teleported' causing interpolation to reset." );
  57. static ConVar demo_avellimit( "demo_avellimit", "2000", 0, "Angular velocity limit before eyes considered snapped for demo playback." );
  58. #define DEMO_HEADER_FILE "demoheader.tmp"
  59. // Fast forward convars
  60. static ConVar demo_fastforwardstartspeed( "demo_fastforwardstartspeed", "2", 0, "Go this fast when starting to hold FF button." );
  61. static ConVar demo_fastforwardfinalspeed( "demo_fastforwardfinalspeed", "20", 0, "Go this fast when starting to hold FF button." );
  62. static ConVar demo_fastforwardramptime( "demo_fastforwardramptime", "5", 0, "How many seconds it takes to get to full FF speed." );
  63. float scr_demo_override_fov = 0.0f;
  64. //-----------------------------------------------------------------------------
  65. // Purpose: Implements IDemo and handles demo file i/o
  66. // Demos are more or less driven off of network traffic, but there are a few
  67. // other kinds of data items that are also included in the demo file: specifically
  68. // commands that the client .dll itself issued to the engine are recorded, though they
  69. // probably were not the result of network traffic.
  70. // At the start of a connection to a map/server, all of the signon, etc. network packets
  71. // are buffered. This allows us to actually decide to start recording the demo at a later
  72. // time. Once we actually issue the recording command, we don't actually start recording
  73. // network traffic, but instead we ask the server for an "uncompressed" packet (otherwise
  74. // we wouldn't be able to deal with the incoming packets during playback because we'd be missing the
  75. // data to delta from ) and go into a waiting state. Once an uncompressed packet is received,
  76. // we unset the waiting state and start recording network packets from that point forward.
  77. // Demo's record the elapsed time based on the current client clock minus the time the demo was started
  78. // During playback, the elapsed time for playback ( based on the host_time, which is subject to the
  79. // host_frametime cvar ) is compared with the elapsed time on the message from the demo file.
  80. // If it's not quite time for the message yet, the demo input stream is rewound
  81. // The demo system sits at the point where the client is checking to see if any network messages
  82. // have arrived from the server. If the message isn't ready for processing, the demo system
  83. // just responds that there are no messages waiting and the client continues on
  84. // Once a true network message with entity data is read from the demo stream, a couple of other
  85. // actions occur. First, the timestamp in the demo file and the view origin/angles corresponding
  86. // to the message are cached off. Then, we search ahead (into the future) to find out the next true network message
  87. // we are going to read from the demo file. We store of it's elapsed time and view origin/angles
  88. // Every frame that the client is rendering, even if there is no data from the demo system,
  89. // the engine asks the demo system to compute an interpolated origin and view angles. This
  90. // is done by taking the current time on the host and figuring out how far that puts us between
  91. // the last read origin from the demo file and the time when we'll actually read out and use the next origin
  92. // We use Quaternions to avoid gimbel lock on interpolating the view angles
  93. // To make a movie recorded at a fixed frame rate you would simply set the host_framerate to the
  94. // desired playback fps ( e.g., 0.02 == 50 fps ), then issue the startmovie command, and then
  95. // play the demo. The demo system will think that the engine is running at 50 fps and will pull
  96. // messages accordingly, even though movie recording kills the actually framerate.
  97. // It will also render frames with render origin/angles interpolated in-between the previous and next origins
  98. // even if the recording framerate was not 50 fps or greater. The interpolation provides a smooth visual playback
  99. // of the demo information to the client without actually adding latency to the view position (because we are
  100. // looking into the future for the position, not buffering the past data ).
  101. //-----------------------------------------------------------------------------
  102. static bool IsControlCommand( unsigned char cmd )
  103. {
  104. return ( (cmd == dem_signon) || (cmd == dem_stop) ||
  105. (cmd == dem_synctick) || (cmd == dem_datatables ) ||
  106. (cmd == dem_stringtables) );
  107. }
  108. // Puts a flashing overlay on the screen during demo recording/playback
  109. static ConVar cl_showdemooverlay( "cl_showdemooverlay", "0", 0, "How often to flash demo recording/playback overlay (0 - disable overlay, -1 - show always)" );
  110. class DemoOverlay
  111. {
  112. public:
  113. DemoOverlay();
  114. ~DemoOverlay();
  115. public:
  116. void Tick();
  117. void DrawOverlay( float fSetting );
  118. protected:
  119. float m_fLastTickTime;
  120. float m_fLastTickOverlay;
  121. enum Overlay { OVR_NONE = 0, OVR_REC = 1 << 1, OVR_PLAY = 1 << 2 };
  122. bool m_bTick;
  123. int m_maskDrawnOverlay;
  124. } g_DemoOverlay;
  125. DemoOverlay::DemoOverlay() :
  126. m_fLastTickTime( 0.f ), m_fLastTickOverlay( 0.f ), m_bTick( false ), m_maskDrawnOverlay( OVR_NONE )
  127. {
  128. }
  129. DemoOverlay::~DemoOverlay()
  130. {
  131. }
  132. void DemoOverlay::Tick()
  133. {
  134. if ( !m_bTick )
  135. {
  136. m_bTick = true;
  137. float const fRealTime = Sys_FloatTime();
  138. if ( m_fLastTickTime != fRealTime )
  139. {
  140. m_fLastTickTime = fRealTime;
  141. float const fDelta = m_fLastTickTime - m_fLastTickOverlay;
  142. float const fSettingDelta = cl_showdemooverlay.GetFloat();
  143. if ( fSettingDelta <= 0.f ||
  144. fDelta >= fSettingDelta )
  145. {
  146. m_fLastTickOverlay = m_fLastTickTime;
  147. DrawOverlay( fSettingDelta );
  148. }
  149. }
  150. m_bTick = false;
  151. }
  152. }
  153. void DemoOverlay::DrawOverlay( float fSetting )
  154. {
  155. int maskDrawnOverlay = OVR_NONE;
  156. if ( fSetting < 0.f )
  157. {
  158. // Keep drawing
  159. maskDrawnOverlay =
  160. ( demorecorder->IsRecording() ? OVR_REC : 0 ) |
  161. ( demoplayer->IsPlayingBack() ? OVR_PLAY : 0 );
  162. }
  163. else if ( fSetting == 0.f )
  164. {
  165. // None
  166. maskDrawnOverlay = OVR_NONE;
  167. }
  168. else
  169. {
  170. // Flash
  171. maskDrawnOverlay = ( !m_maskDrawnOverlay ) ? (
  172. ( demorecorder->IsRecording() ? OVR_REC : 0 ) |
  173. ( demoplayer->IsPlayingBack() ? OVR_PLAY : 0 )
  174. ) : OVR_NONE;
  175. }
  176. int const idx = 1;
  177. if ( OVR_NONE == maskDrawnOverlay &&
  178. OVR_NONE != m_maskDrawnOverlay )
  179. {
  180. con_nprint_s xprn;
  181. memset( &xprn, 0, sizeof( xprn ) );
  182. xprn.index = idx;
  183. xprn.time_to_live = -1;
  184. Con_NXPrintf( &xprn, "" );
  185. }
  186. if ( OVR_PLAY & maskDrawnOverlay )
  187. {
  188. con_nprint_s xprn;
  189. memset( &xprn, 0, sizeof( xprn ) );
  190. xprn.index = idx;
  191. xprn.color[0] = 0.f;
  192. xprn.color[1] = 1.f;
  193. xprn.color[2] = 0.f;
  194. xprn.fixed_width_font = true;
  195. xprn.time_to_live = ( fSetting > 0.f ) ? fSetting : 1.f;
  196. Con_NXPrintf( &xprn, " PLAY " );
  197. }
  198. if ( OVR_REC & maskDrawnOverlay )
  199. {
  200. con_nprint_s xprn;
  201. memset( &xprn, 0, sizeof( xprn ) );
  202. xprn.index = idx;
  203. xprn.color[0] = 1.f;
  204. xprn.color[1] = 0.f;
  205. xprn.color[2] = 0.f;
  206. xprn.fixed_width_font = true;
  207. xprn.time_to_live = ( fSetting > 0.f ) ? fSetting : 1.f;
  208. Con_NXPrintf( &xprn, " REC " );
  209. }
  210. m_maskDrawnOverlay = maskDrawnOverlay;
  211. }
  212. //-----------------------------------------------------------------------------
  213. // Purpose: Mark whether we are waiting for the first uncompressed update packet
  214. // Input : waiting -
  215. //-----------------------------------------------------------------------------
  216. void CDemoRecorder::SetSignonState(int state)
  217. {
  218. if ( demoplayer->IsPlayingBack() )
  219. return;
  220. if ( state == SIGNONSTATE_NEW )
  221. {
  222. if ( m_DemoFile.IsOpen() )
  223. {
  224. // we are already recording a demo file
  225. CloseDemoFile();
  226. // prepare for recording next demo
  227. m_nDemoNumber++;
  228. }
  229. StartupDemoHeader();
  230. }
  231. else if ( state == SIGNONSTATE_SPAWN )
  232. {
  233. // close demo file header when this packet is finished
  234. m_bCloseDemoFile = true;
  235. }
  236. else if ( state == SIGNONSTATE_FULL )
  237. {
  238. if ( m_bRecording )
  239. {
  240. StartupDemoFile();
  241. }
  242. }
  243. }
  244. int CDemoRecorder::GetRecordingTick( void )
  245. {
  246. if ( cl.m_nMaxClients > 1 )
  247. {
  248. return TIME_TO_TICKS( net_time ) - m_nStartTick;
  249. }
  250. else
  251. {
  252. return cl.GetClientTickCount() - m_nStartTick;
  253. }
  254. }
  255. void CDemoRecorder::ResyncDemoClock()
  256. {
  257. if ( cl.m_nMaxClients > 1 )
  258. {
  259. m_nStartTick = TIME_TO_TICKS( net_time );
  260. }
  261. else
  262. {
  263. m_nStartTick = cl.GetClientTickCount();
  264. }
  265. }
  266. //-----------------------------------------------------------------------------
  267. // Purpose:
  268. // Input : info -
  269. //-----------------------------------------------------------------------------
  270. void CDemoRecorder::GetClientCmdInfo( democmdinfo_t& info )
  271. {
  272. info.flags = FDEMO_NORMAL;
  273. if( m_bResetInterpolation )
  274. {
  275. info.flags |= FDEMO_NOINTERP;
  276. m_bResetInterpolation = false;
  277. }
  278. g_pClientSidePrediction->GetViewOrigin( info.viewOrigin );
  279. #ifndef SWDS
  280. info.viewAngles = cl.viewangles;
  281. #endif
  282. g_pClientSidePrediction->GetLocalViewAngles( info.localViewAngles );
  283. // Nothing by default
  284. info.viewOrigin2.Init();
  285. info.viewAngles2.Init();
  286. info.localViewAngles2.Init();
  287. }
  288. void CDemoRecorder::WriteBSPDecals()
  289. {
  290. decallist_t *decalList = (decallist_t*)malloc( sizeof(decallist_t) * Draw_DecalMax() );
  291. int decalcount = DecalListCreate( decalList );
  292. char data[NET_MAX_PAYLOAD];
  293. bf_write msg;
  294. msg.StartWriting( data, NET_MAX_PAYLOAD );
  295. msg.SetDebugName( "DemoFileWriteBSPDecals" );
  296. for ( int i = 0; i < decalcount; i++ )
  297. {
  298. decallist_t *entry = &decalList[ i ];
  299. SVC_BSPDecal decal;
  300. bool found = false;
  301. IClientEntity *clientEntity = entitylist->GetClientEntity( entry->entityIndex );
  302. if ( !clientEntity )
  303. continue;
  304. const model_t * pModel = clientEntity->GetModel();
  305. decal.m_Pos = entry->position;
  306. decal.m_nEntityIndex = entry->entityIndex;
  307. decal.m_nDecalTextureIndex = Draw_DecalIndexFromName( entry->name, &found );
  308. decal.m_nModelIndex = 0;
  309. if ( pModel )
  310. {
  311. decal.m_nModelIndex = cl.LookupModelIndex( modelloader->GetName( pModel ) );
  312. }
  313. decal.WriteToBuffer( msg );
  314. }
  315. WriteMessages( msg );
  316. free( decalList );
  317. }
  318. void CDemoRecorder::RecordServerClasses( ServerClass *pClasses )
  319. {
  320. MEM_ALLOC_CREDIT();
  321. char *pBigBuffer;
  322. CUtlBuffer bigBuff;
  323. int buffSize = 256*1024;
  324. if ( !IsX360() )
  325. {
  326. pBigBuffer = (char*)stackalloc( buffSize );
  327. }
  328. else
  329. {
  330. // keep temp large allocations off of stack
  331. bigBuff.EnsureCapacity( buffSize );
  332. pBigBuffer = (char*)bigBuff.Base();
  333. }
  334. bf_write buf( pBigBuffer, buffSize );
  335. // Send SendTable info.
  336. DataTable_WriteSendTablesBuffer( pClasses, &buf );
  337. // Send class descriptions.
  338. DataTable_WriteClassInfosBuffer( pClasses, &buf );
  339. // Now write the buffer into the demo file
  340. m_DemoFile.WriteNetworkDataTables( &buf, GetRecordingTick() );
  341. }
  342. void CDemoRecorder::RecordStringTables()
  343. {
  344. MEM_ALLOC_CREDIT();
  345. // !KLUDGE! It would be nice if the bit buffer could write into a stream
  346. // with the power to grow itself. But it can't. Hence this really bad
  347. // kludge
  348. void *data = NULL;
  349. int dataLen = 512 * 1024;
  350. while ( dataLen <= DEMO_FILE_MAX_STRINGTABLE_SIZE )
  351. {
  352. data = realloc( data, dataLen );
  353. bf_write buf( data, dataLen );
  354. buf.SetDebugName("CDemoRecorder::RecordStringTables");
  355. buf.SetAssertOnOverflow( false ); // Doesn't turn off all the spew / asserts, but turns off one
  356. networkStringTableContainerClient->WriteStringTables( buf );
  357. // Did we fit?
  358. if ( !buf.IsOverflowed() )
  359. {
  360. // Now write the buffer into the demo file
  361. m_DemoFile.WriteStringTables( &buf, GetRecordingTick() );
  362. break;
  363. }
  364. // Didn't fit. Try doubling the size of the buffer
  365. dataLen *= 2;
  366. }
  367. if ( dataLen > DEMO_FILE_MAX_STRINGTABLE_SIZE )
  368. {
  369. Warning( "Failed to RecordStringTables. Trying to record string table that's bigger than max string table size\n" );
  370. }
  371. free(data);
  372. }
  373. void CDemoRecorder::RecordUserInput( int cmdnumber )
  374. {
  375. char buffer[256];
  376. bf_write msg( "CDemo::WriteUserCmd", buffer, sizeof(buffer) );
  377. g_ClientDLL->EncodeUserCmdToBuffer( msg, cmdnumber );
  378. m_DemoFile.WriteUserCmd( cmdnumber, buffer, msg.GetNumBytesWritten(), GetRecordingTick() );
  379. }
  380. void CDemoRecorder::ResetDemoInterpolation( void )
  381. {
  382. m_bResetInterpolation = true;
  383. }
  384. //-----------------------------------------------------------------------------
  385. // Purpose: saves all cvars falgged with FVAR_DEMO to demo file
  386. //-----------------------------------------------------------------------------
  387. void CDemoRecorder::WriteDemoCvars()
  388. {
  389. const ConCommandBase *var;
  390. for ( var= g_pCVar->GetCommands() ; var ; var=var->GetNext() )
  391. {
  392. if ( var->IsCommand() )
  393. continue;
  394. const ConVar *pCvar = ( const ConVar * )var;
  395. if ( !pCvar->IsFlagSet( FCVAR_DEMO ) )
  396. continue;
  397. char cvarcmd[MAX_OSPATH];
  398. Q_snprintf( cvarcmd, sizeof(cvarcmd),"%s \"%s\"",
  399. pCvar->GetName(), Host_CleanupConVarStringValue( pCvar->GetString() ) );
  400. m_DemoFile.WriteConsoleCommand( cvarcmd, GetRecordingTick() );
  401. }
  402. }
  403. //-----------------------------------------------------------------------------
  404. // Purpose:
  405. // Input : *cmdname -
  406. //-----------------------------------------------------------------------------
  407. void CDemoRecorder::RecordCommand( const char *cmdstring )
  408. {
  409. if ( !IsRecording() )
  410. return;
  411. if ( !cmdstring || !cmdstring[0] )
  412. return;
  413. if ( !demo_recordcommands.GetInt() )
  414. return;
  415. m_DemoFile.WriteConsoleCommand( cmdstring, GetRecordingTick() );
  416. }
  417. //-----------------------------------------------------------------------------
  418. // Purpose:
  419. //-----------------------------------------------------------------------------
  420. void CDemoRecorder::StartupDemoHeader( void )
  421. {
  422. CloseDemoFile(); // make sure it's closed
  423. // Note: this is replacing tmpfile()
  424. if ( !m_DemoFile.Open( DEMO_HEADER_FILE, false ) )
  425. {
  426. ConDMsg ("ERROR: couldn't open temporary header file.\n");
  427. return;
  428. }
  429. m_bIsDemoHeader = true;
  430. Assert( m_MessageData.GetBasePointer() == NULL );
  431. // setup writing data buffer
  432. m_MessageData.StartWriting( new unsigned char[NET_MAX_PAYLOAD], NET_MAX_PAYLOAD );
  433. m_MessageData.SetDebugName( "DemoHeaderWriteBuffer" );
  434. }
  435. //-----------------------------------------------------------------------------
  436. // Purpose:
  437. //-----------------------------------------------------------------------------
  438. void CDemoRecorder::StartupDemoFile( void )
  439. {
  440. if ( !m_bRecording )
  441. return;
  442. // Already recording!!!
  443. if ( m_DemoFile.IsOpen() )
  444. return;
  445. char demoFileName[MAX_OSPATH];
  446. if ( m_nDemoNumber <= 1 )
  447. {
  448. V_sprintf_safe( demoFileName, "%s.dem", m_szDemoBaseName );
  449. }
  450. else
  451. {
  452. V_sprintf_safe( demoFileName, "%s_%i.dem", m_szDemoBaseName, m_nDemoNumber );
  453. }
  454. // strip any trailing whitespace
  455. Q_StripPrecedingAndTrailingWhitespace( demoFileName );
  456. // make sure the .dem extension is still present
  457. char ext[10];
  458. Q_ExtractFileExtension( demoFileName, ext, sizeof( ext ) );
  459. if ( Q_strcasecmp( ext, "dem" ) )
  460. {
  461. ConMsg( "StartupDemoFile: invalid filename.\n" );
  462. return;
  463. }
  464. if ( !m_DemoFile.Open( demoFileName, false ) )
  465. return;
  466. // open demo header file containing sigondata
  467. FileHandle_t hDemoHeader = g_pFileSystem->Open( DEMO_HEADER_FILE, "rb" );
  468. if ( hDemoHeader == FILESYSTEM_INVALID_HANDLE )
  469. {
  470. ConMsg ("StartupDemoFile: couldn't open demo file header.\n");
  471. return;
  472. }
  473. Assert( m_MessageData.GetBasePointer() == NULL );
  474. // setup writing data buffer
  475. m_MessageData.StartWriting( new unsigned char[NET_MAX_PAYLOAD], NET_MAX_PAYLOAD );
  476. m_MessageData.SetDebugName( "DemoFileWriteBuffer" );
  477. // fill demo header info
  478. demoheader_t *dh = &m_DemoFile.m_DemoHeader;
  479. Q_memset(dh, 0, sizeof(demoheader_t));
  480. dh->demoprotocol = DEMO_PROTOCOL;
  481. dh->networkprotocol = PROTOCOL_VERSION;
  482. Q_strncpy(dh->demofilestamp, DEMO_HEADER_ID, sizeof(dh->demofilestamp) );
  483. Q_FileBase( modelloader->GetName( host_state.worldmodel ), dh->mapname, sizeof( dh->mapname ) );
  484. char szGameDir[MAX_OSPATH];
  485. Q_strncpy(szGameDir, com_gamedir, sizeof( szGameDir ) );
  486. Q_FileBase ( szGameDir, dh->gamedirectory, sizeof( dh->gamedirectory ) );
  487. Q_strncpy( dh->servername, cl.m_szRetryAddress, sizeof( dh->servername ) );
  488. Q_strncpy( dh->clientname, cl_name.GetString(), sizeof( dh->clientname ) );
  489. // get size signon data size
  490. dh->signonlength = g_pFileSystem->Size(hDemoHeader);
  491. // write demo file header info
  492. m_DemoFile.WriteDemoHeader();
  493. // copy signon data from header file to demo file
  494. m_DemoFile.WriteFileBytes( hDemoHeader, dh->signonlength );
  495. // close but keep header file, we might need it for a second record
  496. g_pFileSystem->Close( hDemoHeader );
  497. m_nFrameCount = 0;
  498. m_bIsDemoHeader = false;
  499. ResyncDemoClock(); // reset demo clock
  500. // tell client to sync demo clock too
  501. m_DemoFile.WriteCmdHeader( dem_synctick, 0 );
  502. RecordStringTables();
  503. // Demo playback should read this as an incoming message.
  504. WriteDemoCvars(); // save all cvars marked with FCVAR_DEMO
  505. WriteBSPDecals();
  506. g_ClientDLL->HudReset();
  507. // tell server that we started recording a demo
  508. cl.SendStringCmd( "demorestart" );
  509. ConMsg ("Recording to %s...\n", demoFileName);
  510. g_ClientDLL->OnDemoRecordStart( m_szDemoBaseName );
  511. }
  512. CDemoRecorder::CDemoRecorder()
  513. {
  514. }
  515. CDemoRecorder::~CDemoRecorder()
  516. {
  517. CloseDemoFile();
  518. }
  519. CDemoFile *CDemoRecorder::GetDemoFile()
  520. {
  521. return &m_DemoFile;
  522. }
  523. void CDemoRecorder::ResumeRecording()
  524. {
  525. }
  526. void CDemoRecorder::PauseRecording()
  527. {
  528. }
  529. void CDemoRecorder::CloseDemoFile()
  530. {
  531. if ( m_DemoFile.IsOpen())
  532. {
  533. if ( !m_bIsDemoHeader )
  534. {
  535. // Demo playback should read this as an incoming message.
  536. m_DemoFile.WriteCmdHeader( dem_stop, GetRecordingTick() );
  537. // update demo header infos
  538. m_DemoFile.m_DemoHeader.playback_ticks = GetRecordingTick();
  539. m_DemoFile.m_DemoHeader.playback_time = host_state.interval_per_tick * GetRecordingTick();
  540. m_DemoFile.m_DemoHeader.playback_frames = m_nFrameCount;
  541. // go back to header and write demoHeader with correct time and #frame again
  542. m_DemoFile.WriteDemoHeader();
  543. ConMsg ("Completed demo, recording time %.1f, game frames %i.\n",
  544. m_DemoFile.m_DemoHeader.playback_time, m_DemoFile.m_DemoHeader.playback_frames );
  545. }
  546. if ( demo_debug.GetInt() )
  547. {
  548. ConMsg ("Closed demo file, %i bytes.\n", m_DemoFile.GetSize() );
  549. }
  550. m_DemoFile.Close();
  551. g_ClientDLL->OnDemoRecordStop();
  552. }
  553. m_bCloseDemoFile = false;
  554. m_bIsDemoHeader = false;
  555. // clear writing data buffer
  556. if ( m_MessageData.GetBasePointer() )
  557. {
  558. delete [] m_MessageData.GetBasePointer();
  559. m_MessageData.StartWriting( NULL, 0 );
  560. }
  561. }
  562. void CDemoRecorder::RecordMessages(bf_read &data, int bits)
  563. {
  564. if ( m_MessageData.GetBasePointer() && (bits>0) )
  565. {
  566. m_MessageData.WriteBitsFromBuffer( &data, bits );
  567. Assert( !m_MessageData.IsOverflowed() );
  568. }
  569. }
  570. void CDemoRecorder::RecordPacket()
  571. {
  572. WriteMessages( m_MessageData );
  573. m_MessageData.Reset(); // clear message buffer
  574. if ( m_bCloseDemoFile )
  575. {
  576. CloseDemoFile();
  577. }
  578. }
  579. void CDemoRecorder::WriteMessages( bf_write &message )
  580. {
  581. int len = message.GetNumBytesWritten();
  582. if (len <= 0)
  583. return;
  584. // fill last bits in last byte with NOP if necessary
  585. int nRemainingBits = message.GetNumBitsWritten() % 8;
  586. if ( nRemainingBits > 0 && nRemainingBits <= (8-NETMSG_TYPE_BITS) )
  587. {
  588. message.WriteUBitLong( net_NOP, NETMSG_TYPE_BITS );
  589. }
  590. Assert( len < NET_MAX_MESSAGE );
  591. // if signondata read as fast as possible, no rewind
  592. // and wait for packet time
  593. unsigned char cmd = m_bIsDemoHeader ? dem_signon : dem_packet;
  594. if ( cmd == dem_packet )
  595. {
  596. m_nFrameCount++;
  597. }
  598. // write command & time
  599. m_DemoFile.WriteCmdHeader( cmd, GetRecordingTick() );
  600. democmdinfo_t info;
  601. // Snag current info
  602. GetClientCmdInfo( info );
  603. // Store it
  604. m_DemoFile.WriteCmdInfo( info );
  605. // write network channel sequencing infos
  606. int nOutSequenceNr, nInSequenceNr, nOutSequenceNrAck;
  607. cl.m_NetChannel->GetSequenceData( nOutSequenceNr, nInSequenceNr, nOutSequenceNrAck );
  608. m_DemoFile.WriteSequenceInfo( nInSequenceNr, nOutSequenceNrAck );
  609. // Output the messge buffer.
  610. m_DemoFile.WriteRawData( (char*) message.GetBasePointer(), len );
  611. if ( demo_debug.GetInt() >= 1 )
  612. {
  613. Msg( "Writing demo message %i bytes at file pos %i\n", len, m_DemoFile.GetCurPos( false ) );
  614. }
  615. }
  616. //-----------------------------------------------------------------------------
  617. // Purpose: stop recording a demo
  618. //-----------------------------------------------------------------------------
  619. void CDemoRecorder::StopRecording( void )
  620. {
  621. if ( !IsRecording() )
  622. {
  623. return;
  624. }
  625. if ( m_MessageData.GetBasePointer() )
  626. {
  627. delete[] m_MessageData.GetBasePointer();
  628. m_MessageData.StartWriting( NULL, 0);
  629. }
  630. CloseDemoFile();
  631. m_bRecording = false;
  632. m_nDemoNumber = 0;
  633. g_DemoOverlay.Tick();
  634. }
  635. //-----------------------------------------------------------------------------
  636. // Purpose:
  637. // Input : *name -
  638. // track -
  639. //-----------------------------------------------------------------------------
  640. void CDemoRecorder::StartRecording( const char *name, bool bContinuously )
  641. {
  642. Q_strncpy( m_szDemoBaseName, name, sizeof(m_szDemoBaseName));
  643. m_bRecording = true;
  644. m_nDemoNumber = 1;
  645. m_bResetInterpolation = false;
  646. g_DemoOverlay.Tick();
  647. // request a full game update from server
  648. cl.ForceFullUpdate();
  649. }
  650. //-----------------------------------------------------------------------------
  651. // Purpose:
  652. // Output : Returns true on success, false on failure.
  653. //-----------------------------------------------------------------------------
  654. bool CDemoRecorder::IsRecording( void )
  655. {
  656. g_DemoOverlay.Tick();
  657. return m_bRecording;
  658. }
  659. //-----------------------------------------------------------------------------
  660. // Purpose: Called when a demo file runs out, or the user starts a game
  661. // Output : void CDemo::StopPlayback
  662. //-----------------------------------------------------------------------------
  663. void CDemoPlayer::StopPlayback( void )
  664. {
  665. if ( !IsPlayingBack() )
  666. return;
  667. demoaction->StopPlaying();
  668. m_DemoFile.Close();
  669. m_bPlayingBack = false;
  670. m_bLoading = false;
  671. m_bPlaybackPaused = false;
  672. m_flAutoResumeTime = 0.0f;
  673. m_nEndTick = 0;
  674. if ( m_bTimeDemo )
  675. {
  676. g_EngineStats.EndRun();
  677. if ( !s_bBenchframe )
  678. {
  679. WriteTimeDemoResults();
  680. }
  681. else
  682. {
  683. mat_norendering.SetValue( 0 );
  684. }
  685. m_bTimeDemo = false;
  686. }
  687. else
  688. {
  689. int framecount = host_framecount - m_nTimeDemoStartFrame;
  690. float demotime = Sys_FloatTime() - m_flTimeDemoStartTime;
  691. if ( demotime > 0.0f )
  692. {
  693. DevMsg( "Demo playback finished ( %.1f seconds, %i render frames, %.2f fps).\n", demotime, framecount, framecount/demotime);
  694. }
  695. }
  696. m_flPlaybackRateModifier = 1.0f;
  697. delete[] m_DemoPacket.data;
  698. m_DemoPacket.data = NULL;
  699. scr_demo_override_fov = 0.0f;
  700. if ( timedemo_runcount.GetInt() > 1 )
  701. {
  702. timedemo_runcount.SetValue( timedemo_runcount.GetInt() - 1 );
  703. Cbuf_AddText( va( "timedemo %s", m_DemoFile.m_szFileName ) );
  704. }
  705. else if ( demo_quitafterplayback.GetBool() )
  706. {
  707. Cbuf_AddText( "quit\n" );
  708. }
  709. g_ClientDLL->OnDemoPlaybackStop();
  710. }
  711. CDemoFile *CDemoPlayer::GetDemoFile( void )
  712. {
  713. return &m_DemoFile;
  714. }
  715. #define SKIP_TO_TICK_FLAG uint32( uint32( 0x88 ) << 24 )
  716. bool CDemoPlayer::IsSkipping( void )
  717. {
  718. return m_bPlayingBack && ( m_nSkipToTick != -1 );
  719. }
  720. bool CDemoPlayer::IsLoading( void )
  721. {
  722. return m_bLoading;
  723. }
  724. int CDemoPlayer::GetTotalTicks(void)
  725. {
  726. return m_DemoFile.m_DemoHeader.playback_ticks;
  727. }
  728. void CDemoPlayer::SkipToTick( int tick, bool bRelative, bool bPause )
  729. {
  730. if ( bRelative )
  731. {
  732. tick = GetPlaybackTick() + tick;
  733. }
  734. if ( tick < 0 )
  735. return;
  736. if ( tick < GetPlaybackTick() )
  737. {
  738. // we have to reload the whole demo file
  739. // we need to create a temp copy of the filename
  740. char fileName[MAX_OSPATH];
  741. Q_strncpy( fileName, m_DemoFile.m_szFileName, sizeof(fileName) );
  742. // reload current demo file
  743. ETWMarkPrintf( "DemoPlayer: Reloading demo file '%s'", fileName );
  744. StartPlayback( fileName, m_bTimeDemo );
  745. // Make sure the proper skipping occurs after reload
  746. if ( tick > 0 )
  747. tick |= SKIP_TO_TICK_FLAG;
  748. }
  749. m_nSkipToTick = tick;
  750. ETWMark1I( "DemoPlayer: SkipToTick", tick );
  751. if ( bPause )
  752. PausePlayback( -1 );
  753. }
  754. void CDemoPlayer::SetEndTick( int tick )
  755. {
  756. if ( tick < 0 )
  757. return;
  758. m_nEndTick = tick;
  759. }
  760. //-----------------------------------------------------------------------------
  761. // Purpose: Read in next demo message and send to local client over network channel, if it's time.
  762. // Output : bool
  763. //-----------------------------------------------------------------------------
  764. bool CDemoPlayer::ParseAheadForInterval( int curtick, int intervalticks )
  765. {
  766. int tick = 0;
  767. int dummy;
  768. byte cmd = dem_stop;
  769. democmdinfo_t nextinfo;
  770. long starting_position = m_DemoFile.GetCurPos( true );
  771. // remove all entrys older than 32 ticks
  772. while ( m_DestCmdInfo.Count() > 0 )
  773. {
  774. DemoCommandQueue& entry = m_DestCmdInfo[ 0 ];
  775. if ( entry.tick >= (curtick - 32) )
  776. break;
  777. if ( entry.filepos >= starting_position )
  778. break;
  779. m_DestCmdInfo.Remove( 0 );
  780. }
  781. if ( m_bTimeDemo )
  782. return false;
  783. while ( true )
  784. {
  785. // skip forward to the next dem_packet or dem_signon
  786. bool swallowmessages = true;
  787. do
  788. {
  789. m_DemoFile.ReadCmdHeader( cmd, tick );
  790. // COMMAND HANDLERS
  791. switch ( cmd )
  792. {
  793. case dem_synctick:
  794. case dem_stop:
  795. {
  796. m_DemoFile.SeekTo( starting_position, true );
  797. return false;
  798. }
  799. break;
  800. case dem_consolecmd:
  801. {
  802. m_DemoFile.ReadConsoleCommand();
  803. }
  804. break;
  805. case dem_datatables:
  806. {
  807. m_DemoFile.ReadNetworkDataTables( NULL );
  808. }
  809. break;
  810. case dem_usercmd:
  811. {
  812. m_DemoFile.ReadUserCmd( NULL, dummy );
  813. }
  814. break;
  815. case dem_stringtables:
  816. {
  817. m_DemoFile.ReadStringTables( NULL );
  818. }
  819. break;
  820. default:
  821. {
  822. swallowmessages = false;
  823. }
  824. break;
  825. }
  826. }
  827. while ( swallowmessages );
  828. int curpos = m_DemoFile.GetCurPos( true );
  829. // we read now a dem_packet
  830. m_DemoFile.ReadCmdInfo( nextinfo );
  831. m_DemoFile.ReadSequenceInfo( dummy, dummy );
  832. m_DemoFile.ReadRawData( NULL, 0 );
  833. DemoCommandQueue entry;
  834. entry.info = nextinfo;
  835. entry.tick = tick;
  836. entry.filepos = curpos;
  837. int i = 0;
  838. int c = m_DestCmdInfo.Count();
  839. for ( ; i < c; ++i )
  840. {
  841. if ( m_DestCmdInfo[ i ].filepos == entry.filepos )
  842. break; // cmdinfo is already in list
  843. }
  844. if ( i >= c )
  845. {
  846. // add cmdinfo to list
  847. if ( c > 0 )
  848. {
  849. if ( m_DestCmdInfo[ c - 1 ].tick > tick )
  850. {
  851. m_DestCmdInfo.RemoveAll();
  852. }
  853. }
  854. m_DestCmdInfo.AddToTail( entry );
  855. }
  856. if ( ( tick - curtick ) > intervalticks )
  857. break;
  858. }
  859. m_DemoFile.SeekTo( starting_position, true );
  860. return true;
  861. }
  862. //-----------------------------------------------------------------------------
  863. // Purpose: Read in next demo message and send to local client over network channel, if it's time.
  864. // Output : netpacket_t* -- NULL if there is no packet available at this time.
  865. //-----------------------------------------------------------------------------
  866. netpacket_t *CDemoPlayer::ReadPacket( void )
  867. {
  868. int tick = 0;
  869. byte cmd = dem_signon;
  870. long curpos = 0;
  871. if ( ! m_DemoFile.IsOpen() )
  872. {
  873. m_bPlayingBack = false;
  874. Host_EndGame( true, "Tried to read a demo message with no demo file\n" );
  875. return NULL;
  876. }
  877. // If game is still shutting down, then don't read any demo messages from file quite yet
  878. if ( HostState_IsGameShuttingDown() )
  879. {
  880. return NULL;
  881. }
  882. Assert( IsPlayingBack() );
  883. if ( IsSkipping() )
  884. {
  885. // Every nMaxConsecutiveSkipPackets frames return NULL so that we don't build up an
  886. // endless supply of unprocessed packets. This avoids causing overflows and excessive
  887. // "highwater marks" in various Dota subsystems.
  888. ++m_nSkipPacketsPlayed;
  889. if ( m_nSkipPacketsPlayed >= nMaxConsecutiveSkipPackets )
  890. {
  891. m_nSkipPacketsPlayed = 0;
  892. return NULL;
  893. }
  894. }
  895. else
  896. {
  897. m_nSkipPacketsPlayed = 0;
  898. }
  899. // External editor has paused playback
  900. if ( CheckPausedPlayback() )
  901. return NULL;
  902. bool bStopReading = false;
  903. while ( !bStopReading )
  904. {
  905. curpos = m_DemoFile.GetCurPos( true );
  906. m_DemoFile.ReadCmdHeader( cmd, tick );
  907. // always read control commands
  908. if ( !IsControlCommand( cmd ) )
  909. {
  910. int playbacktick = GetPlaybackTick();
  911. #if defined( RAD_TELEMETRY_ENABLED )
  912. g_Telemetry.playbacktick = playbacktick;
  913. #endif
  914. // If the end tick is set, check to see if we should bail
  915. if ( m_nEndTick > 0 && playbacktick >= m_nEndTick )
  916. {
  917. m_nEndTick = 0;
  918. return NULL;
  919. }
  920. if ( !m_bTimeDemo )
  921. {
  922. // Time demo ignores clocks and tries to synchronize frames to what was recorded
  923. // I.e., while frame is the same, read messages, otherwise, skip out.
  924. // If we're still signing on, then just parse messages until fully connected no matter what
  925. if ( cl.IsActive() &&
  926. (tick > playbacktick) && !IsSkipping() )
  927. {
  928. // is not time yet
  929. bStopReading = true;
  930. }
  931. }
  932. else
  933. {
  934. if ( m_nTimeDemoCurrentFrame == host_framecount )
  935. {
  936. // If we are playing back a timedemo, and we've already passed on a
  937. // frame update for this host_frame tag, then we'll just skip this mess
  938. bStopReading = true;
  939. }
  940. }
  941. if ( bStopReading )
  942. {
  943. demoaction->Update( false, playbacktick, TICKS_TO_TIME( playbacktick ) );
  944. m_DemoFile.SeekTo( curpos, true ); // go back to start of current demo command
  945. return NULL; // Not time yet, dont return packet data.
  946. }
  947. }
  948. // COMMAND HANDLERS
  949. switch ( cmd )
  950. {
  951. case dem_synctick:
  952. {
  953. if ( demo_debug.GetBool() )
  954. {
  955. Msg( "%d dem_synctick\n", tick );
  956. }
  957. ResyncDemoClock();
  958. // Once demo clock got resync-ed we can go ahead and
  959. // perform skipping logic normally
  960. if ( ( m_nSkipToTick != -1 ) &&
  961. ( ( m_nSkipToTick & SKIP_TO_TICK_FLAG ) == SKIP_TO_TICK_FLAG ) )
  962. {
  963. m_nSkipToTick &= ~SKIP_TO_TICK_FLAG;
  964. }
  965. }
  966. break;
  967. case dem_stop:
  968. {
  969. if ( demo_debug.GetBool() )
  970. {
  971. Msg( "%d dem_stop\n", tick );
  972. }
  973. OnStopCommand();
  974. return NULL;
  975. }
  976. break;
  977. case dem_consolecmd:
  978. {
  979. const char * command = m_DemoFile.ReadConsoleCommand();
  980. if ( demo_debug.GetBool() )
  981. {
  982. Msg( "%d dem_consolecmd [%s]\n", tick, command );
  983. }
  984. Cbuf_AddText( command );
  985. Cbuf_Execute();
  986. }
  987. break;
  988. case dem_datatables:
  989. {
  990. if ( demo_debug.GetBool() )
  991. {
  992. Msg( "%d dem_datatables\n", tick );
  993. }
  994. void *data = malloc( 256*1024 ); // X360TBD: How much memory is really needed here?
  995. bf_read buf( "dem_datatables", data, 256*1024 );
  996. m_DemoFile.ReadNetworkDataTables( &buf );
  997. buf.Seek( 0 ); // re-read data
  998. // support for older engine demos
  999. if ( !DataTable_LoadDataTablesFromBuffer( &buf, m_DemoFile.m_DemoHeader.demoprotocol ) )
  1000. {
  1001. Host_Error( "Error parsing network data tables during demo playback." );
  1002. }
  1003. free( data );
  1004. }
  1005. break;
  1006. case dem_stringtables:
  1007. {
  1008. void *data = NULL;
  1009. int dataLen = 512 * 1024;
  1010. while ( dataLen <= DEMO_FILE_MAX_STRINGTABLE_SIZE )
  1011. {
  1012. data = realloc( data, dataLen );
  1013. bf_read buf( "dem_stringtables", data, dataLen );
  1014. // did we successfully read
  1015. if ( m_DemoFile.ReadStringTables( &buf ) > 0 )
  1016. {
  1017. buf.Seek( 0 );
  1018. if ( !networkStringTableContainerClient->ReadStringTables( buf ) )
  1019. {
  1020. Host_Error( "Error parsing string tables during demo playback." );
  1021. }
  1022. break;
  1023. }
  1024. // Didn't fit. Try doubling the size of the buffer
  1025. dataLen *= 2;
  1026. }
  1027. if ( dataLen > DEMO_FILE_MAX_STRINGTABLE_SIZE )
  1028. {
  1029. Warning( "ReadPacket failed to read string tables. Trying to read string tables that's bigger than max string table size\n" );
  1030. }
  1031. free( data );
  1032. }
  1033. break;
  1034. case dem_usercmd:
  1035. {
  1036. if ( demo_debug.GetBool() )
  1037. {
  1038. Msg( "%d dem_usercmd\n", tick );
  1039. }
  1040. char buffer[256];
  1041. int length = sizeof(buffer);
  1042. int outgoing_sequence = m_DemoFile.ReadUserCmd( buffer, length );
  1043. // put it into a bitbuffer
  1044. bf_read msg( "CDemo::ReadUserCmd", buffer, length );
  1045. g_ClientDLL->DecodeUserCmdFromBuffer( msg, outgoing_sequence );
  1046. // Note, we need to have the current outgoing sequence correct so we can do prediction
  1047. // correctly during playback
  1048. cl.lastoutgoingcommand = outgoing_sequence;
  1049. }
  1050. break;
  1051. default:
  1052. {
  1053. bStopReading = true;
  1054. if ( IsSkipping() )
  1055. {
  1056. // adjust playback host_tickcount when skipping
  1057. m_nStartTick = host_tickcount - tick;
  1058. }
  1059. }
  1060. break;
  1061. }
  1062. }
  1063. if ( cmd == dem_packet )
  1064. {
  1065. // remember last frame we read a dem_packet update
  1066. m_nTimeDemoCurrentFrame = host_framecount;
  1067. }
  1068. int inseq, outseqack, outseq = 0;
  1069. m_DemoFile.ReadCmdInfo( m_LastCmdInfo );
  1070. m_DemoFile.ReadSequenceInfo( inseq, outseqack );
  1071. cl.m_NetChannel->SetSequenceData( outseq, inseq, outseqack );
  1072. int length = m_DemoFile.ReadRawData( (char*)m_DemoPacket.data, NET_MAX_PAYLOAD );
  1073. if ( demo_debug.GetBool() )
  1074. {
  1075. Msg( "%d network packet [%d]\n", tick, length );
  1076. }
  1077. if ( length > 0 )
  1078. {
  1079. // succsessfully read new demopacket
  1080. m_DemoPacket.received = realtime;
  1081. m_DemoPacket.size = length;
  1082. m_DemoPacket.message.StartReading( m_DemoPacket.data, m_DemoPacket.size );
  1083. if ( demo_debug.GetInt() >= 1 )
  1084. {
  1085. Msg( "Demo message, tick %i, %i bytes\n", GetPlaybackTick(), length );
  1086. }
  1087. }
  1088. // Try and jump ahead one frame
  1089. m_bInterpolateView = ParseAheadForInterval( tick, 8 );
  1090. // ConMsg( "Reading message for %i : %f skip %i\n", m_nFrameCount, fElapsedTime, forceskip ? 1 : 0 );
  1091. // Skip a few ticks before doing any timing
  1092. if ( (m_nTimeDemoStartFrame < 0) && GetPlaybackTick() > 100 )
  1093. {
  1094. m_nTimeDemoStartFrame = host_framecount;
  1095. m_flTimeDemoStartTime = Sys_FloatTime();
  1096. m_flTotalFPSVariability = 0.0f;
  1097. if ( m_bTimeDemo )
  1098. {
  1099. g_EngineStats.BeginRun();
  1100. }
  1101. }
  1102. if ( m_nSnapshotTick > 0 && m_nSnapshotTick <= GetPlaybackTick() )
  1103. {
  1104. const char *filename = "benchframe";
  1105. if ( m_SnapshotFilename[0] )
  1106. filename = m_SnapshotFilename;
  1107. CL_TakeScreenshot( filename ); // take a screenshot
  1108. m_nSnapshotTick = 0;
  1109. if ( s_bBenchframe )
  1110. {
  1111. Cbuf_AddText( "stopdemo\n" );
  1112. }
  1113. }
  1114. return &m_DemoPacket;
  1115. }
  1116. void CDemoPlayer::InterpolateDemoCommand( int targettick, DemoCommandQueue& prev, DemoCommandQueue& next )
  1117. {
  1118. CUtlVector< DemoCommandQueue >& list = m_DestCmdInfo;
  1119. int c = list.Count();
  1120. prev.info.Reset();
  1121. next.info.Reset();
  1122. if ( c < 2 )
  1123. {
  1124. // we need at least two entries to interpolate
  1125. return;
  1126. }
  1127. int i = 0;
  1128. int savedI = -1;
  1129. DemoCommandQueue *entry1 = &list[ i ];
  1130. DemoCommandQueue *entry2 = &list[ i+1 ];
  1131. while ( true )
  1132. {
  1133. if ( (entry1->tick <= targettick) && (entry2->tick > targettick) )
  1134. {
  1135. // Means we hit a FDEMO_NOINTERP along the way to now
  1136. if ( savedI != -1 )
  1137. {
  1138. prev = list[ savedI ];
  1139. next = list[ savedI + 1 ];
  1140. }
  1141. else
  1142. {
  1143. prev = *entry1;
  1144. next = *entry2;
  1145. }
  1146. return;
  1147. }
  1148. // 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
  1149. // 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
  1150. /// the first FDEMO_NOINTERP we see
  1151. if ( savedI == -1 &&
  1152. entry2->tick > m_nPreviousTick &&
  1153. entry2->tick <= targettick &&
  1154. entry2->info.flags & FDEMO_NOINTERP )
  1155. {
  1156. savedI = i;
  1157. }
  1158. if ( i+2 == c )
  1159. break;
  1160. i++;
  1161. entry1 = &list[ i ];
  1162. entry2 = &list[ i+1 ];
  1163. }
  1164. Assert( 0 );
  1165. }
  1166. static ConVar demo_legacy_rollback( "demo_legacy_rollback", "1", 0, "Use legacy view interpolation rollback amount in demo playback." );
  1167. //-----------------------------------------------------------------------------
  1168. // Purpose:
  1169. //-----------------------------------------------------------------------------
  1170. void CDemoPlayer::InterpolateViewpoint( void )
  1171. {
  1172. if ( !IsPlayingBack() )
  1173. return;
  1174. democmdinfo_t outinfo;
  1175. outinfo.Reset();
  1176. bool bHasValidData =
  1177. m_LastCmdInfo.viewOrigin != vec3_origin ||
  1178. m_LastCmdInfo.viewAngles != vec3_angle ||
  1179. m_LastCmdInfo.localViewAngles != vec3_angle ||
  1180. m_LastCmdInfo.flags != 0;
  1181. int nTargetTick = GetPlaybackTick();
  1182. // Player view needs to be one tick interval in the past like the client DLL entities
  1183. if ( cl.m_nMaxClients == 1 )
  1184. {
  1185. if ( demo_legacy_rollback.GetBool() )
  1186. {
  1187. nTargetTick -= TIME_TO_TICKS( cl.GetClientInterpAmount() ) + 1;
  1188. }
  1189. else
  1190. {
  1191. nTargetTick -= 1;
  1192. }
  1193. }
  1194. float vel = 0.0f;
  1195. float angVel = 0.0f;
  1196. if ( m_bInterpolateView && demo_interpolateview.GetBool() && bHasValidData )
  1197. {
  1198. DemoCommandQueue prev, next;
  1199. float frac = 0.0f;
  1200. prev.info = m_LastCmdInfo;
  1201. prev.tick = -1;
  1202. next.info = m_LastCmdInfo;
  1203. next.tick = -1;
  1204. // Determine current time slice
  1205. InterpolateDemoCommand( nTargetTick, prev, next );
  1206. float dt = TICKS_TO_TIME(next.tick-prev.tick);
  1207. frac = (TICKS_TO_TIME(nTargetTick-prev.tick)+cl.m_tickRemainder)/dt;
  1208. frac = clamp( frac, 0.0f, 1.0f );
  1209. // Now interpolate
  1210. Vector delta;
  1211. Vector startorigin = prev.info.GetViewOrigin();
  1212. Vector destorigin = next.info.GetViewOrigin();
  1213. // check for teleporting - since there can be multiple cmd packets between a game frame,
  1214. // we need to check from the last actually ran command to see if there was a teleport
  1215. VectorSubtract( destorigin, m_LastCmdInfo.GetViewOrigin(), delta );
  1216. float distmoved = delta.Length();
  1217. if ( dt > 0.0f )
  1218. {
  1219. vel = distmoved / dt;
  1220. }
  1221. if ( dt > 0.0f )
  1222. {
  1223. QAngle startang = prev.info.GetLocalViewAngles();
  1224. QAngle destang = next.info.GetLocalViewAngles();
  1225. for ( int i = 0; i < 3; ++i )
  1226. {
  1227. float dAng = AngleNormalizePositive( destang[ i ] ) - AngleNormalizePositive( startang[ i ] );
  1228. dAng = AngleNormalize( dAng );
  1229. float aVel = fabs( dAng ) / dt;
  1230. if ( aVel > angVel )
  1231. {
  1232. angVel = aVel;
  1233. }
  1234. }
  1235. }
  1236. // FIXME: This should be velocity based maybe?
  1237. if ( (vel > demo_interplimit.GetFloat()) ||
  1238. (angVel > demo_avellimit.GetFloat() ) ||
  1239. m_bResetInterpolation )
  1240. {
  1241. m_bResetInterpolation = false;
  1242. // it's a teleport, just let it happen naturally next frame
  1243. // setting frac to 1.0 (like it was previously) would just mean that we
  1244. // are teleporting a frame ahead of when we should
  1245. outinfo.viewOrigin = m_LastCmdInfo.GetViewOrigin();
  1246. outinfo.viewAngles = m_LastCmdInfo.GetViewAngles();
  1247. outinfo.localViewAngles = m_LastCmdInfo.GetLocalViewAngles();
  1248. }
  1249. else
  1250. {
  1251. outinfo.viewOrigin = startorigin + frac * ( destorigin - startorigin );
  1252. Quaternion src, dest;
  1253. Quaternion result;
  1254. AngleQuaternion( prev.info.GetViewAngles(), src );
  1255. AngleQuaternion( next.info.GetViewAngles(), dest );
  1256. QuaternionSlerp( src, dest, frac, result );
  1257. QuaternionAngles( result, outinfo.viewAngles );
  1258. AngleQuaternion( prev.info.GetLocalViewAngles(), src );
  1259. AngleQuaternion( next.info.GetLocalViewAngles(), dest );
  1260. QuaternionSlerp( src, dest, frac, result );
  1261. QuaternionAngles( result, outinfo.localViewAngles );
  1262. }
  1263. }
  1264. else if ( bHasValidData )
  1265. {
  1266. // don't interpolate, just copy values
  1267. outinfo.viewOrigin = m_LastCmdInfo.GetViewOrigin();
  1268. outinfo.viewAngles = m_LastCmdInfo.GetViewAngles();
  1269. outinfo.localViewAngles = m_LastCmdInfo.GetLocalViewAngles();
  1270. }
  1271. m_nPreviousTick = nTargetTick;
  1272. // let any demo system override view ( drive, editor, smoother etc)
  1273. bHasValidData |= OverrideView( outinfo );
  1274. if ( !bHasValidData )
  1275. return; // no validate data & no override, exit
  1276. g_pClientSidePrediction->SetViewOrigin( outinfo.viewOrigin );
  1277. g_pClientSidePrediction->SetViewAngles( outinfo.viewAngles );
  1278. g_pClientSidePrediction->SetLocalViewAngles( outinfo.localViewAngles );
  1279. #ifndef SWDS
  1280. VectorCopy( outinfo.viewAngles, cl.viewangles );
  1281. #endif
  1282. }
  1283. //-----------------------------------------------------------------------------
  1284. // Purpose:
  1285. // Output : Returns true on success, false on failure.
  1286. //-----------------------------------------------------------------------------
  1287. bool CDemoPlayer::IsPlayingTimeDemo( void )
  1288. {
  1289. return m_bTimeDemo && m_bPlayingBack;
  1290. }
  1291. //-----------------------------------------------------------------------------
  1292. // Purpose:
  1293. // Output : Returns true on success, false on failure.
  1294. //-----------------------------------------------------------------------------
  1295. bool CDemoPlayer::IsPlayingBack( void )
  1296. {
  1297. return m_bPlayingBack;
  1298. }
  1299. CDemoPlayer::CDemoPlayer()
  1300. {
  1301. m_flAutoResumeTime = 0.0f;
  1302. m_flPlaybackRateModifier = 1.0f;
  1303. m_bTimeDemo = false;
  1304. m_nTimeDemoStartFrame = -1;
  1305. m_flTimeDemoStartTime = 0.0f;
  1306. m_flTotalFPSVariability = 0.0f;
  1307. m_nTimeDemoCurrentFrame = -1;
  1308. m_bPlayingBack = false;
  1309. m_bLoading = false;
  1310. m_bPlaybackPaused = false;
  1311. m_nSkipToTick = -1;
  1312. m_nSkipPacketsPlayed = 0;
  1313. m_nSnapshotTick = 0;
  1314. m_SnapshotFilename[0] = 0;
  1315. m_bResetInterpolation = false;
  1316. m_nPreviousTick = 0;
  1317. m_nEndTick = 0;
  1318. }
  1319. CDemoPlayer::~CDemoPlayer()
  1320. {
  1321. StopPlayback();
  1322. if ( g_ClientDLL )
  1323. {
  1324. g_ClientDLL->OnDemoPlaybackStop();
  1325. }
  1326. }
  1327. //-----------------------------------------------------------------------------
  1328. // Purpose: Start's demo playback
  1329. // Input : *name -
  1330. //-----------------------------------------------------------------------------
  1331. bool CDemoPlayer::StartPlayback( const char *filename, bool bAsTimeDemo )
  1332. {
  1333. m_bLoading = true;
  1334. SCR_BeginLoadingPlaque();
  1335. // Disconnect from server or stop running one
  1336. int oldn = cl.demonum;
  1337. cl.demonum = -1;
  1338. Host_Disconnect(false);
  1339. cl.demonum = oldn;
  1340. if ( !m_DemoFile.Open( filename, true ) )
  1341. {
  1342. cl.demonum = -1; // stop demo loop
  1343. return false;
  1344. }
  1345. // Read in the m_DemoHeader
  1346. demoheader_t *dh = m_DemoFile.ReadDemoHeader();
  1347. if ( !dh )
  1348. {
  1349. ConMsg( "Failed to read demo header.\n" );
  1350. m_DemoFile.Close();
  1351. cl.demonum = -1;
  1352. return false;
  1353. }
  1354. ConMsg ("Playing demo from %s.\n", filename);
  1355. // Now read in the directory structure.
  1356. m_bPlayingBack = true;
  1357. cl.m_nSignonState= SIGNONSTATE_CONNECTED;
  1358. ResyncDemoClock();
  1359. // create a fake channel with a NULL address
  1360. cl.m_NetChannel = NET_CreateNetChannel( NS_CLIENT, NULL, "DEMO", &cl, false, dh->networkprotocol );
  1361. if ( !cl.m_NetChannel )
  1362. {
  1363. ConMsg ("CDemo::Play: failed to create demo net channel\n" );
  1364. m_DemoFile.Close();
  1365. cl.demonum = -1; // stop demo loop
  1366. Host_Disconnect(true);
  1367. }
  1368. cl.m_NetChannel->SetTimeout( -1.0f ); // never timeout
  1369. Q_memset( &m_DemoPacket, 0, sizeof(m_DemoPacket) );
  1370. // setup demo packet data buffer
  1371. m_DemoPacket.data = new unsigned char[NET_MAX_PAYLOAD];
  1372. m_DemoPacket.from.SetType( NA_LOOPBACK);
  1373. cl.chokedcommands = 0;
  1374. cl.lastoutgoingcommand = -1;
  1375. cl.m_flNextCmdTime = net_time;
  1376. m_bTimeDemo = bAsTimeDemo;
  1377. m_nTimeDemoCurrentFrame = -1;
  1378. m_nTimeDemoStartFrame = -1;
  1379. if ( m_bTimeDemo )
  1380. {
  1381. SeedRandomNumberGenerator( true );
  1382. }
  1383. demoaction->StartPlaying( filename );
  1384. // m_bFastForwarding = false;
  1385. m_flAutoResumeTime = 0.0f;
  1386. m_flPlaybackRateModifier = 1.0f;
  1387. scr_demo_override_fov = 0.0f;
  1388. m_bLoading = false;
  1389. return true;
  1390. }
  1391. //-----------------------------------------------------------------------------
  1392. // Purpose:
  1393. // Input : flCurTime -
  1394. //-----------------------------------------------------------------------------
  1395. void CDemoPlayer::MarkFrame( float flFPSVariability )
  1396. {
  1397. m_flTotalFPSVariability += flFPSVariability;
  1398. }
  1399. void CDemoPlayer::WriteTimeDemoResults( void )
  1400. {
  1401. int frames;
  1402. float time;
  1403. frames = (host_framecount - m_nTimeDemoStartFrame) - 1;
  1404. time = Sys_FloatTime() - m_flTimeDemoStartTime;
  1405. if (!time)
  1406. {
  1407. time = 1;
  1408. }
  1409. float flVariability = (m_flTotalFPSVariability / (float)frames);
  1410. 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 );
  1411. bool bFileExists = g_pFileSystem->FileExists( "SourceBench.csv" );
  1412. FileHandle_t fileHandle = g_pFileSystem->Open( "SourceBench.csv", "a+" );
  1413. int width, height;
  1414. CMatRenderContextPtr pRenderContext( materials );
  1415. pRenderContext->GetWindowSize( width, height );
  1416. const MaterialSystem_Config_t &config = materials->GetCurrentConfigForVideoCard();
  1417. if( !bFileExists )
  1418. {
  1419. g_pFileSystem->FPrintf( fileHandle, "demofile," );
  1420. g_pFileSystem->FPrintf( fileHandle, "fps," );
  1421. g_pFileSystem->FPrintf( fileHandle, "framerate variability," );
  1422. g_pFileSystem->FPrintf( fileHandle, "totaltime," );
  1423. g_pFileSystem->FPrintf( fileHandle, "numframes," );
  1424. g_pFileSystem->FPrintf( fileHandle, "width," );
  1425. g_pFileSystem->FPrintf( fileHandle, "height," );
  1426. g_pFileSystem->FPrintf( fileHandle, "windowed," );
  1427. g_pFileSystem->FPrintf( fileHandle, "vsync," );
  1428. g_pFileSystem->FPrintf( fileHandle, "MSAA," );
  1429. g_pFileSystem->FPrintf( fileHandle, "Aniso," );
  1430. g_pFileSystem->FPrintf( fileHandle, "dxlevel," );
  1431. g_pFileSystem->FPrintf( fileHandle, "cmdline," );
  1432. g_pFileSystem->FPrintf( fileHandle, "driver name," );
  1433. g_pFileSystem->FPrintf( fileHandle, "vendor id," );
  1434. g_pFileSystem->FPrintf( fileHandle, "device id," );
  1435. // g_pFileSystem->FPrintf( fileHandle, "sound," );
  1436. g_pFileSystem->FPrintf( fileHandle, "Reduce fillrate," );
  1437. g_pFileSystem->FPrintf( fileHandle, "reflect entities," );
  1438. g_pFileSystem->FPrintf( fileHandle, "motion blur," );
  1439. g_pFileSystem->FPrintf( fileHandle, "flashlight shadows," );
  1440. g_pFileSystem->FPrintf( fileHandle, "mat_reduceparticles," );
  1441. g_pFileSystem->FPrintf( fileHandle, "r_dopixelvisibility," );
  1442. g_pFileSystem->FPrintf( fileHandle, "nulldevice," );
  1443. g_pFileSystem->FPrintf( fileHandle, "timedemo_comment," );
  1444. g_pFileSystem->FPrintf( fileHandle, "\n" );
  1445. }
  1446. ConVarRef mat_vsync( "mat_vsync" );
  1447. ConVarRef mat_antialias( "mat_antialias" );
  1448. ConVarRef mat_forceaniso( "mat_forceaniso" );
  1449. ConVarRef r_waterforcereflectentities( "r_waterforcereflectentities" );
  1450. ConVarRef mat_motion_blur_enabled( "mat_motion_blur_enabled" );
  1451. ConVarRef r_flashlightdepthtexture( "r_flashlightdepthtexture" );
  1452. ConVarRef mat_reducefillrate( "mat_reducefillrate" );
  1453. ConVarRef mat_reduceparticles( "mat_reduceparticles" );
  1454. ConVarRef r_dopixelvisibility( "r_dopixelvisibility" );
  1455. g_pFileSystem->Seek( fileHandle, 0, FILESYSTEM_SEEK_TAIL );
  1456. MaterialAdapterInfo_t info;
  1457. materials->GetDisplayAdapterInfo( materials->GetCurrentAdapter(), info );
  1458. g_pFileSystem->FPrintf( fileHandle, "%s,", m_DemoFile.m_szFileName );
  1459. g_pFileSystem->FPrintf( fileHandle, "%5.1f,", frames/time );
  1460. g_pFileSystem->FPrintf( fileHandle, "%5.1f,", flVariability );
  1461. g_pFileSystem->FPrintf( fileHandle, "%5.1f,", time );
  1462. g_pFileSystem->FPrintf( fileHandle, "%i,", frames );
  1463. g_pFileSystem->FPrintf( fileHandle, "%i,", width );
  1464. g_pFileSystem->FPrintf( fileHandle, "%i,", height );
  1465. g_pFileSystem->FPrintf( fileHandle, "%s,", config.Windowed() ? "windowed" : "fullscreen");
  1466. g_pFileSystem->FPrintf( fileHandle, "%s,", mat_vsync.GetBool() ? "on" : "off" );
  1467. g_pFileSystem->FPrintf( fileHandle, "%d,", mat_antialias.GetInt() );
  1468. g_pFileSystem->FPrintf( fileHandle, "%d,", mat_forceaniso.GetInt() );
  1469. g_pFileSystem->FPrintf( fileHandle, "%s,", COM_DXLevelToString( g_pMaterialSystemHardwareConfig->GetDXSupportLevel() ) );
  1470. g_pFileSystem->FPrintf( fileHandle, "%s,", CommandLine()->GetCmdLine() );
  1471. g_pFileSystem->FPrintf( fileHandle, "%s,", info.m_pDriverName );
  1472. g_pFileSystem->FPrintf( fileHandle, "0x%x,", info.m_VendorID );
  1473. g_pFileSystem->FPrintf( fileHandle, "0x%x,", info.m_DeviceID );
  1474. // g_pFileSystem->FPrintf( fileHandle, "%s,", CommandLine()->CheckParm( "-nosound" ) ? "off" : "on" );
  1475. g_pFileSystem->FPrintf( fileHandle, "%s,", mat_reducefillrate.GetBool() ? "on" : "off" );
  1476. g_pFileSystem->FPrintf( fileHandle, "%s,", r_waterforcereflectentities.GetBool() ? "on" : "off" );
  1477. g_pFileSystem->FPrintf( fileHandle, "%s,", mat_motion_blur_enabled.GetBool() ? "on" : "off" );
  1478. g_pFileSystem->FPrintf( fileHandle, "%s,", r_flashlightdepthtexture.GetBool() ? "on" : "off" );
  1479. g_pFileSystem->FPrintf( fileHandle, "%s,", mat_reduceparticles.GetBool() ? "on" : "off" );
  1480. g_pFileSystem->FPrintf( fileHandle, "%s,", r_dopixelvisibility.GetBool() ? "on" : "off" );
  1481. g_pFileSystem->FPrintf( fileHandle, "%s,", CommandLine()->CheckParm( "-nulldevice" ) ? "yes" : "no" );
  1482. int itimedemo_comment = CommandLine()->FindParm( "-timedemo_comment" );
  1483. const char *timedemo_comment = itimedemo_comment ? CommandLine()->GetParm( itimedemo_comment + 1 ) : "";
  1484. g_pFileSystem->FPrintf( fileHandle, "%s,", timedemo_comment );
  1485. g_pFileSystem->FPrintf( fileHandle, "\n" );
  1486. g_pFileSystem->Close( fileHandle );
  1487. }
  1488. void CDemoPlayer::PausePlayback( float seconds )
  1489. {
  1490. m_bPlaybackPaused = true;
  1491. if ( seconds > 0.0f )
  1492. {
  1493. // Use true clock since everything else is frozen
  1494. m_flAutoResumeTime = Sys_FloatTime() + seconds;
  1495. }
  1496. else
  1497. {
  1498. m_flAutoResumeTime = 0.0f;
  1499. }
  1500. }
  1501. void CDemoPlayer::ResumePlayback()
  1502. {
  1503. m_bPlaybackPaused = false;
  1504. m_flAutoResumeTime = 0.0f;
  1505. }
  1506. bool CDemoPlayer::CheckPausedPlayback()
  1507. {
  1508. if ( demo_pauseatservertick.GetInt() > 0 )
  1509. {
  1510. if ( cl.GetServerTickCount() >= demo_pauseatservertick.GetInt() )
  1511. {
  1512. PausePlayback( -1 );
  1513. ETWMark1I( "DemoPlayer: Reached pause tick", cl.GetServerTickCount() );
  1514. m_nSkipToTick = -1;
  1515. demo_pauseatservertick.SetValue( 0 );
  1516. Msg( "Demo paused at server tick %i\n", cl.GetServerTickCount() );
  1517. }
  1518. }
  1519. if ( IsSkipping() )
  1520. {
  1521. if ( ( m_nSkipToTick > GetPlaybackTick() ) ||
  1522. ( ( m_nSkipToTick & SKIP_TO_TICK_FLAG ) == SKIP_TO_TICK_FLAG ) )
  1523. {
  1524. // we are skipping
  1525. return false;
  1526. }
  1527. else
  1528. {
  1529. // we can't skip back (or finished skipping), so disable skipping
  1530. ETWMark1I( "DemoPlayer: SkipToTick done", GetPlaybackTick() );
  1531. m_nSkipToTick = -1;
  1532. }
  1533. }
  1534. if ( !IsPlaybackPaused() )
  1535. return false;
  1536. if ( m_bPlaybackPaused )
  1537. {
  1538. if ( (m_flAutoResumeTime > 0.0f) &&
  1539. (Sys_FloatTime() >= m_flAutoResumeTime) )
  1540. {
  1541. // it's time to unpause replay
  1542. ResumePlayback();
  1543. }
  1544. }
  1545. return m_bPlaybackPaused;
  1546. }
  1547. bool CDemoPlayer::IsPlaybackPaused()
  1548. {
  1549. if ( !IsPlayingBack() )
  1550. return false;
  1551. // never pause while reading signon data
  1552. if ( m_nTimeDemoCurrentFrame < 0 )
  1553. return false;
  1554. // If skipping then do not pretend paused
  1555. if ( IsSkipping() )
  1556. return false;
  1557. return m_bPlaybackPaused;
  1558. }
  1559. int CDemoPlayer::GetPlaybackStartTick( void )
  1560. {
  1561. return m_nStartTick;
  1562. }
  1563. int CDemoPlayer::GetPlaybackTick( void )
  1564. {
  1565. return host_tickcount - m_nStartTick;
  1566. }
  1567. void CDemoPlayer::ResyncDemoClock()
  1568. {
  1569. m_nStartTick = host_tickcount;
  1570. m_nPreviousTick = m_nStartTick;
  1571. }
  1572. float CDemoPlayer::GetPlaybackTimeScale()
  1573. {
  1574. return m_flPlaybackRateModifier;
  1575. }
  1576. void CDemoPlayer::SetPlaybackTimeScale(float timescale)
  1577. {
  1578. m_flPlaybackRateModifier = timescale;
  1579. }
  1580. void CDemoPlayer::SetBenchframe( int tick, const char *filename )
  1581. {
  1582. m_nSnapshotTick = tick;
  1583. if ( filename )
  1584. {
  1585. Q_strncpy( m_SnapshotFilename, filename, sizeof(m_SnapshotFilename) );
  1586. }
  1587. }
  1588. static bool ComputeNextIncrementalDemoFilename( char *name, int namesize )
  1589. {
  1590. FileHandle_t test;
  1591. test = g_pFileSystem->Open( name, "rb" );
  1592. if ( FILESYSTEM_INVALID_HANDLE == test )
  1593. {
  1594. // file doesn't exist, so we can use that
  1595. return true;
  1596. }
  1597. g_pFileSystem->Close( test );
  1598. char basename[ MAX_OSPATH ];
  1599. Q_StripExtension( name, basename, sizeof( basename ) );
  1600. // Start looking for a valid name
  1601. int i = 0;
  1602. for ( i = 0; i < 1000; i++ )
  1603. {
  1604. char newname[ MAX_OSPATH ];
  1605. Q_snprintf( newname, sizeof( newname ), "%s%03i.dem", basename, i );
  1606. test = g_pFileSystem->Open( newname, "rb" );
  1607. if ( FILESYSTEM_INVALID_HANDLE == test )
  1608. {
  1609. Q_strncpy( name, newname, namesize );
  1610. return true;
  1611. }
  1612. g_pFileSystem->Close( test );
  1613. }
  1614. ConMsg( "Unable to find a valid incremental demo filename for %s, try clearing the directory of %snnn.dem\n", name, basename );
  1615. return false;
  1616. }
  1617. //-----------------------------------------------------------------------------
  1618. // Purpose: List the contents of a demo file.
  1619. //-----------------------------------------------------------------------------
  1620. void CL_ListDemo_f( const CCommand &args )
  1621. {
  1622. if ( cmd_source != src_command )
  1623. return;
  1624. // Find the file
  1625. char name[MAX_OSPATH];
  1626. Q_snprintf (name, sizeof(name), "%s", args[1]);
  1627. Q_DefaultExtension( name, ".dem", sizeof( name ) );
  1628. ConMsg ("Demo contents for %s:\n", name);
  1629. CDemoFile demofile;
  1630. if ( !demofile.Open( name, true ) )
  1631. {
  1632. ConMsg ("ERROR: couldn't open.\n");
  1633. return;
  1634. }
  1635. demofile.ReadDemoHeader();
  1636. demoheader_t *header = &demofile.m_DemoHeader;
  1637. if ( !header )
  1638. {
  1639. ConMsg( "Failed reading demo header.\n" );
  1640. demofile.Close();
  1641. return;
  1642. }
  1643. if ( Q_strcmp ( header->demofilestamp, DEMO_HEADER_ID ) )
  1644. {
  1645. ConMsg( "%s is not a valid demo file\n", name);
  1646. return;
  1647. }
  1648. ConMsg("Network protocol: %i\n", header->networkprotocol);
  1649. ConMsg("Demo version : %i\n", header->demoprotocol);
  1650. ConMsg("Server name : %s\n", header->servername);
  1651. ConMsg("Map name : %s\n", header->mapname);
  1652. ConMsg("Game : %s\n", header->gamedirectory);
  1653. ConMsg("Player name : %s\n", header->clientname);
  1654. ConMsg("Time : %.1f\n", header->playback_time);
  1655. ConMsg("Ticks : %i\n", header->playback_ticks);
  1656. ConMsg("Frames : %i\n", header->playback_frames);
  1657. ConMsg("Signon size : %i\n", header->signonlength);
  1658. }
  1659. //-----------------------------------------------------------------------------
  1660. // Purpose:
  1661. //-----------------------------------------------------------------------------
  1662. CON_COMMAND( stop, "Finish recording demo." )
  1663. {
  1664. if ( cmd_source != src_command )
  1665. return;
  1666. if ( !demorecorder->IsRecording() )
  1667. {
  1668. ConDMsg ("Not recording a demo.\n");
  1669. return;
  1670. }
  1671. demorecorder->StopRecording();
  1672. }
  1673. //-----------------------------------------------------------------------------
  1674. // Purpose:
  1675. //-----------------------------------------------------------------------------
  1676. CON_COMMAND_F( record, "Record a demo.", FCVAR_DONTRECORD )
  1677. {
  1678. if ( g_ClientDLL == NULL )
  1679. {
  1680. ConMsg ("Can't record on dedicated server.\n");
  1681. return;
  1682. }
  1683. if ( args.ArgC() != 2 && args.ArgC() != 3 )
  1684. {
  1685. ConMsg ("record <demoname> [incremental]\n");
  1686. return;
  1687. }
  1688. bool incremental = false;
  1689. if ( args.ArgC() == 3 )
  1690. {
  1691. if ( !Q_stricmp( args[2], "incremental" ) )
  1692. {
  1693. incremental = true;
  1694. }
  1695. }
  1696. if ( demorecorder->IsRecording() )
  1697. {
  1698. ConMsg ("Already recording.\n");
  1699. return;
  1700. }
  1701. if ( demoplayer->IsPlayingBack() )
  1702. {
  1703. ConMsg ("Can't record during demo playback.\n");
  1704. return;
  1705. }
  1706. // check path first
  1707. if ( !COM_IsValidPath( args[1] ) )
  1708. {
  1709. ConMsg( "record %s: invalid path.\n", args[1] );
  1710. return;
  1711. }
  1712. char name[ MAX_OSPATH ];
  1713. if ( !g_ClientDLL->CanRecordDemo( name, sizeof( name ) ) )
  1714. {
  1715. ConMsg( "%s\n", name ); // re-use name as the error string if the client prevents us from starting a demo
  1716. return;
  1717. }
  1718. // remove .dem extension if user added it
  1719. Q_StripExtension( args[1], name, sizeof( name ) );
  1720. if ( incremental )
  1721. {
  1722. // If file exists, construct a better name
  1723. if ( !ComputeNextIncrementalDemoFilename( name, sizeof( name ) ) )
  1724. {
  1725. return;
  1726. }
  1727. }
  1728. // Record it
  1729. demorecorder->StartRecording( name, incremental );
  1730. }
  1731. //-----------------------------------------------------------------------------
  1732. // Purpose:
  1733. //-----------------------------------------------------------------------------
  1734. void CL_PlayDemo_f( const CCommand &args )
  1735. {
  1736. if ( cmd_source != src_command )
  1737. return;
  1738. if ( args.ArgC() != 2 )
  1739. {
  1740. ConMsg ("playdemo <demoname> : plays a demo file\n");
  1741. return;
  1742. }
  1743. // Get the demo filename
  1744. char name[ MAX_OSPATH ];
  1745. Q_strncpy( name, args[1], sizeof( name ) );
  1746. Q_DefaultExtension( name, ".dem", sizeof( name ) );
  1747. // set current demo player to replay demo player?
  1748. demoplayer = g_pClientDemoPlayer;
  1749. //
  1750. // open the demo file
  1751. //
  1752. if ( demoplayer->StartPlayback( name, false ) )
  1753. {
  1754. // Remove extension
  1755. char basename[ MAX_OSPATH ];
  1756. V_StripExtension( name, basename, sizeof( basename ) );
  1757. g_ClientDLL->OnDemoPlaybackStart( basename );
  1758. }
  1759. else
  1760. {
  1761. SCR_EndLoadingPlaque();
  1762. }
  1763. }
  1764. //-----------------------------------------------------------------------------
  1765. // Purpose:
  1766. //-----------------------------------------------------------------------------
  1767. void CL_TimeDemo_f( const CCommand &args )
  1768. {
  1769. if ( cmd_source != src_command )
  1770. return;
  1771. if ( args.ArgC() < 2 || args.ArgC() > 4 )
  1772. {
  1773. ConMsg ("timedemo <demoname> <optional stats.txt> : gets demo speeds, starting from optional frame\n");
  1774. return;
  1775. }
  1776. if( args.ArgC() >= 3 )
  1777. {
  1778. Q_strncpy( g_pStatsFile, args[ 2 ], sizeof( g_pStatsFile ) );
  1779. }
  1780. else
  1781. {
  1782. Q_strncpy( g_pStatsFile, "UNKNOWN", sizeof( g_pStatsFile ) );
  1783. }
  1784. // set current demo player to client demo player
  1785. demoplayer = g_pClientDemoPlayer;
  1786. // open the demo file
  1787. char name[ MAX_OSPATH ];
  1788. Q_strncpy (name, args[1], sizeof( name ) );
  1789. Q_DefaultExtension( name, ".dem", sizeof( name ) );
  1790. if ( !demoplayer->StartPlayback( name, true ) )
  1791. {
  1792. SCR_EndLoadingPlaque();
  1793. }
  1794. }
  1795. void CL_TimeDemoQuit_f( const CCommand &args )
  1796. {
  1797. demo_quitafterplayback.SetValue( 1 );
  1798. CL_TimeDemo_f( args );
  1799. }
  1800. void CL_BenchFrame_f( const CCommand &args )
  1801. {
  1802. if ( cmd_source != src_command )
  1803. return;
  1804. if ( args.ArgC() != 4 )
  1805. {
  1806. ConMsg ("benchframe <demoname> <frame> <tgafilename>: takes a snapshot of a particular frame in a demo\n");
  1807. return;
  1808. }
  1809. g_pClientDemoPlayer->SetBenchframe( max( 0, atoi( args[2] ) ), args[3] );
  1810. s_bBenchframe = true;
  1811. mat_norendering.SetValue( 1 );
  1812. // set current demo player to client demo player
  1813. demoplayer = g_pClientDemoPlayer;
  1814. // open the demo file
  1815. char name[ MAX_OSPATH ];
  1816. Q_strncpy (name, args[1], sizeof( name ) );
  1817. Q_DefaultExtension( name, ".dem", sizeof( name ) );
  1818. if ( !demoplayer->StartPlayback( name, true ) )
  1819. {
  1820. SCR_EndLoadingPlaque();
  1821. }
  1822. }
  1823. //-----------------------------------------------------------------------------
  1824. // Purpose:
  1825. //-----------------------------------------------------------------------------
  1826. CON_COMMAND( vtune, "Controls VTune's sampling." )
  1827. {
  1828. if ( args.ArgC() != 2 )
  1829. {
  1830. ConMsg ("vtune \"pause\" | \"resume\" : Suspend or resume VTune's sampling.\n");
  1831. return;
  1832. }
  1833. if( !Q_strcasecmp( args[1], "pause" ) )
  1834. {
  1835. if(!vtune(false))
  1836. {
  1837. ConMsg("Failed to find \"VTPause()\" in \"vtuneapi.dll\".\n");
  1838. return;
  1839. }
  1840. ConMsg("VTune sampling paused.\n");
  1841. }
  1842. else if( !Q_strcasecmp( args[1], "resume" ) )
  1843. {
  1844. if(!vtune(true))
  1845. {
  1846. ConMsg("Failed to find \"VTResume()\" in \"vtuneapi.dll\".\n");
  1847. return;
  1848. }
  1849. ConMsg("VTune sampling resumed.\n");
  1850. }
  1851. else
  1852. {
  1853. ConMsg("Unknown vtune option.\n");
  1854. }
  1855. }
  1856. CON_COMMAND_AUTOCOMPLETEFILE( playdemo, CL_PlayDemo_f, "Play a recorded demo file (.dem ).", NULL, dem );
  1857. CON_COMMAND_AUTOCOMPLETEFILE( timedemo, CL_TimeDemo_f, "Play a demo and report performance info.", NULL, dem );
  1858. CON_COMMAND_AUTOCOMPLETEFILE( timedemoquit, CL_TimeDemoQuit_f, "Play a demo, report performance info, and then exit", NULL, dem );
  1859. CON_COMMAND_AUTOCOMPLETEFILE( listdemo, CL_ListDemo_f, "List demo file contents.", NULL, dem );
  1860. CON_COMMAND_AUTOCOMPLETEFILE( benchframe, CL_BenchFrame_f, "Takes a snapshot of a particular frame in a time demo.", NULL, dem );
  1861. CON_COMMAND( demo_pause, "Pauses demo playback." )
  1862. {
  1863. float seconds = -1.0;
  1864. if ( args.ArgC() == 2 )
  1865. {
  1866. seconds = atof( args[1] );
  1867. }
  1868. demoplayer->PausePlayback( seconds );
  1869. }
  1870. CON_COMMAND( demo_resume, "Resumes demo playback." )
  1871. {
  1872. demoplayer->ResumePlayback();
  1873. }
  1874. CON_COMMAND( demo_togglepause, "Toggles demo playback." )
  1875. {
  1876. if ( !demoplayer->IsPlayingBack() )
  1877. return;
  1878. if ( demoplayer->IsPlaybackPaused() )
  1879. {
  1880. demoplayer->ResumePlayback();
  1881. }
  1882. else
  1883. {
  1884. demoplayer->PausePlayback( -1 );
  1885. }
  1886. }
  1887. CON_COMMAND( demo_gototick, "Skips to a tick in demo." )
  1888. {
  1889. bool bRelative = false;
  1890. bool bPause = false;
  1891. if ( args.ArgC() < 2 )
  1892. {
  1893. Msg("Syntax: demo_gototick <tick> [relative] [pause]\n");
  1894. return;
  1895. }
  1896. int nTick = atoi( args[1] );
  1897. if ( args.ArgC() >= 3 )
  1898. {
  1899. bRelative = Q_atoi( args[2] ) != 0;
  1900. }
  1901. if ( args.ArgC() >= 4 )
  1902. {
  1903. bPause = Q_atoi( args[3] ) != 0;
  1904. }
  1905. demoplayer->SkipToTick( nTick, bRelative, bPause );
  1906. }
  1907. CON_COMMAND( demo_setendtick, "Sets end demo playback tick. Set to 0 to disable." )
  1908. {
  1909. if ( args.ArgC() != 2 )
  1910. {
  1911. Msg( "Syntax: demo_setendtick <tick>\n" );
  1912. return;
  1913. }
  1914. int nTick = atoi( args[1] );
  1915. demoplayer->SetEndTick( nTick );
  1916. }
  1917. CON_COMMAND( demo_timescale, "Sets demo replay speed." )
  1918. {
  1919. float fScale = 1.0f;
  1920. if ( args.ArgC() == 2 )
  1921. {
  1922. fScale = atof( args[1] );
  1923. fScale = clamp( fScale, 0.0f, 100.0f );
  1924. }
  1925. demoplayer->SetPlaybackTimeScale( fScale );
  1926. }
  1927. bool CDemoPlayer::OverrideView( democmdinfo_t& info )
  1928. {
  1929. if ( g_pDemoUI && g_pDemoUI->OverrideView( info, GetPlaybackTick() ) )
  1930. return true;
  1931. if ( g_pDemoUI2 && g_pDemoUI2->OverrideView( info, GetPlaybackTick() ) )
  1932. return true;
  1933. if ( demoaction && demoaction->OverrideView( info, GetPlaybackTick() ) )
  1934. return true;
  1935. return false;
  1936. }
  1937. void CDemoPlayer::OnStopCommand()
  1938. {
  1939. cl.Disconnect( "Demo stopped", true);
  1940. }
  1941. void CDemoPlayer::ResetDemoInterpolation( void )
  1942. {
  1943. m_bResetInterpolation = true;
  1944. }
  1945. int CDemoPlayer::GetProtocolVersion()
  1946. {
  1947. Assert( IsPlayingBack() );
  1948. if ( !IsPlayingBack() )
  1949. return PROTOCOL_VERSION;
  1950. return m_DemoFile.GetProtocolVersion();
  1951. }