Counter Strike : Global Offensive Source Code
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.

4055 lines
112 KiB

  1. //========= Copyright (c) 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/icommandline.h"
  18. #include "vengineserver_impl.h"
  19. #include "console.h"
  20. #include "dt_common_eng.h"
  21. #include "gl_model_private.h"
  22. #include "decal.h"
  23. #include "icliententitylist.h"
  24. #include "icliententity.h"
  25. #include "cl_demouipanel.h"
  26. #include "materialsystem/materialsystem_config.h"
  27. #include "tier2/tier2.h"
  28. #include "vgui_baseui_interface.h"
  29. #include "con_nprint.h"
  30. #include "networkstringtableclient.h"
  31. #include "host_cmd.h"
  32. #include "matchmaking/imatchframework.h"
  33. #include "tier0/perfstats.h"
  34. #include "GameEventManager.h"
  35. #include "tier1/bitbuf.h"
  36. #include "net_chan.h"
  37. #include "tier1/characterset.h"
  38. #include "cl_steamauth.h"
  39. #if !defined DEDICATED
  40. #include "sound.h"
  41. #endif
  42. #if IsPlatformWindowsPC()
  43. #define WIN32_LEAN_AND_MEAN
  44. #undef INVALID_HANDLE_VALUE
  45. #include <winsock2.h> // gethostname
  46. #elif !IsGameConsole()
  47. #include <sys/unistd.h> // gethostname
  48. #endif
  49. #ifdef DEDICATED
  50. #include "server.h"
  51. #endif
  52. // memdbgon must be the last include file in a .cpp file!!!
  53. #include "tier0/memdbgon.h"
  54. #ifdef _GAMECONSOLE
  55. // Disable demos on consoles by default, to avoid unwanted memory allocations, file I/O and computation
  56. #define ENABLE_DEMOS_BY_DEFAULT false
  57. #else
  58. #define ENABLE_DEMOS_BY_DEFAULT true
  59. #endif
  60. static ConVar demo_recordcommands( "demo_recordcommands", "1", FCVAR_CHEAT, "Record commands typed at console into .dem files." );
  61. static ConVar demo_quitafterplayback( "demo_quitafterplayback", "0",
  62. #if defined( ALLOW_TEXT_MODE )
  63. FCVAR_RELEASE,
  64. #else
  65. 0,
  66. #endif
  67. "Quits game after demo playback."
  68. );
  69. extern ConVar demo_debug;
  70. static ConVar demo_interpolateview( "demo_interpolateview", "1", 0, "Do view interpolation during dem playback." );
  71. static ConVar demo_pauseatservertick( "demo_pauseatservertick", "0", 0, "Pauses demo playback at server tick" );
  72. static ConVar demo_enabledemos( "demo_enabledemos", ENABLE_DEMOS_BY_DEFAULT ? "1" : "0", 0, "Enable recording demos (must be set true before loading a map)" );
  73. extern ConVar demo_strict_validation;
  74. // singeltons:
  75. static char g_pStatsFile[MAX_OSPATH] = { NULL };
  76. static bool s_bBenchframe = false;
  77. static CDemoRecorder s_ClientDemoRecorder;
  78. CDemoRecorder *g_pClientDemoRecorder = &s_ClientDemoRecorder;
  79. IDemoRecorder *demorecorder = g_pClientDemoRecorder;
  80. static CDemoPlayer s_ClientDemoPlayer;
  81. CDemoPlayer *g_pClientDemoPlayer = &s_ClientDemoPlayer;
  82. IDemoPlayer *demoplayer = g_pClientDemoPlayer;
  83. extern CNetworkStringTableContainer *networkStringTableContainerClient;
  84. CUtlVector<RegisteredDemoCustomDataCallbackPair_t> g_RegisteredDemoCustomDataCallbacks;
  85. // This is the number of units under which we are allowed to interpolate, otherwise pop.
  86. // This fixes problems with in-level transitions.
  87. static ConVar demo_interplimit( "demo_interplimit", "4000", 0, "How much origin velocity before it's considered to have 'teleported' causing interpolation to reset." );
  88. static ConVar demo_avellimit( "demo_avellimit", "2000", 0, "Angular velocity limit before eyes considered snapped for demo playback." );
  89. #define DEMO_HEADER_FILE "demoheader.tmp"
  90. // Fast forward convars
  91. static ConVar demo_fastforwardstartspeed( "demo_fastforwardstartspeed", "2", 0, "Go this fast when starting to hold FF button." );
  92. static ConVar demo_fastforwardfinalspeed( "demo_fastforwardfinalspeed", "20", 0, "Go this fast when starting to hold FF button." );
  93. static ConVar demo_fastforwardramptime( "demo_fastforwardramptime", "5", 0, "How many seconds it takes to get to full FF speed." );
  94. // highlight convars
  95. static ConVar demo_highlight_timebefore( "demo_highlight_timebefore", "6", 0, "How many seconds before highlight event to stop fast forwarding." );
  96. static ConVar demo_highlight_timeafter( "demo_highlight_timeafter", "4", 0, "How many seconds after highlight event to start fast forwarding." );
  97. static ConVar demo_highlight_fastforwardspeed( "demo_highlight_fastforwardspeed", "10", 0, "Speed to use when fast forwarding to highlights." );
  98. static ConVar demo_highlight_skipthreshold( "demo_highlight_skipthreshold", "10", 0, "Number of seconds between previous highlight event and round start that will fast forward instead of skipping." );
  99. float scr_demo_override_fov = 0.0f;
  100. // Defined in engine
  101. static ConVar cl_interpolate( "cl_interpolate", "1", FCVAR_RELEASE, "Enables or disables interpolation on listen servers or during demo playback" );
  102. void SetPlaybackParametersLockFirstPersonAccountID( uint32 nAccountID );
  103. void CL_ScanDemoDone( const char *pszMode );
  104. //-----------------------------------------------------------------------------
  105. // Purpose: Implements IDemo and handles demo file i/o
  106. // Demos are more or less driven off of network traffic, but there are a few
  107. // other kinds of data items that are also included in the demo file: specifically
  108. // commands that the client .dll itself issued to the engine are recorded, though they
  109. // probably were not the result of network traffic.
  110. // At the start of a connection to a map/server, all of the signon, etc. network packets
  111. // are buffered. This allows us to actually decide to start recording the demo at a later
  112. // time. Once we actually issue the recording command, we don't actually start recording
  113. // network traffic, but instead we ask the server for an "uncompressed" packet (otherwise
  114. // we wouldn't be able to deal with the incoming packets during playback because we'd be missing the
  115. // data to delta from ) and go into a waiting state. Once an uncompressed packet is received,
  116. // we unset the waiting state and start recording network packets from that point forward.
  117. // Demo's record the elapsed time based on the current client clock minus the time the demo was started
  118. // During playback, the elapsed time for playback ( based on the host_time, which is subject to the
  119. // host_frametime cvar ) is compared with the elapsed time on the message from the demo file.
  120. // If it's not quite time for the message yet, the demo input stream is rewound
  121. // The demo system sits at the point where the client is checking to see if any network messages
  122. // have arrived from the server. If the message isn't ready for processing, the demo system
  123. // just responds that there are no messages waiting and the client continues on
  124. // Once a true network message with entity data is read from the demo stream, a couple of other
  125. // actions occur. First, the timestamp in the demo file and the view origin/angles corresponding
  126. // to the message are cached off. Then, we search ahead (into the future) to find out the next true network message
  127. // we are going to read from the demo file. We store of it's elapsed time and view origin/angles
  128. // Every frame that the client is rendering, even if there is no data from the demo system,
  129. // the engine asks the demo system to compute an interpolated origin and view angles. This
  130. // is done by taking the current time on the host and figuring out how far that puts us between
  131. // the last read origin from the demo file and the time when we'll actually read out and use the next origin
  132. // We use Quaternions to avoid gimbel lock on interpolating the view angles
  133. // To make a movie recorded at a fixed frame rate you would simply set the host_framerate to the
  134. // desired playback fps ( e.g., 0.02 == 50 fps ), then issue the startmovie command, and then
  135. // play the demo. The demo system will think that the engine is running at 50 fps and will pull
  136. // messages accordingly, even though movie recording kills the actually framerate.
  137. // It will also render frames with render origin/angles interpolated in-between the previous and next origins
  138. // even if the recording framerate was not 50 fps or greater. The interpolation provides a smooth visual playback
  139. // of the demo information to the client without actually adding latency to the view position (because we are
  140. // looking into the future for the position, not buffering the past data ).
  141. //-----------------------------------------------------------------------------
  142. bool IsControlCommand( unsigned char cmd )
  143. {
  144. return ( (cmd == dem_signon) || (cmd == dem_stop) ||
  145. (cmd == dem_synctick) || (cmd == dem_datatables ) ||
  146. (cmd == dem_stringtables) );
  147. }
  148. // Puts a flashing overlay on the screen during demo recording/playback
  149. static ConVar cl_showdemooverlay( "cl_showdemooverlay", "0", 0, "How often to flash demo recording/playback overlay (0 - disable overlay, -1 - show always)" );
  150. class DemoOverlay
  151. {
  152. public:
  153. DemoOverlay();
  154. ~DemoOverlay();
  155. public:
  156. void Tick();
  157. void DrawOverlay( float fSetting );
  158. protected:
  159. float m_fLastTickTime;
  160. float m_fLastTickOverlay;
  161. enum Overlay { OVR_NONE = 0, OVR_REC = 1 << 1, OVR_PLAY = 1 << 2 };
  162. bool m_bTick;
  163. int m_maskDrawnOverlay;
  164. } g_DemoOverlay;
  165. DemoOverlay::DemoOverlay() :
  166. m_fLastTickTime( 0.f ), m_fLastTickOverlay( 0.f ), m_bTick( false ), m_maskDrawnOverlay( OVR_NONE )
  167. {
  168. }
  169. DemoOverlay::~DemoOverlay()
  170. {
  171. }
  172. void DemoOverlay::Tick()
  173. {
  174. if ( !m_bTick )
  175. {
  176. m_bTick = true;
  177. float const fRealTime = Sys_FloatTime();
  178. if ( m_fLastTickTime != fRealTime )
  179. {
  180. m_fLastTickTime = fRealTime;
  181. float const fDelta = m_fLastTickTime - m_fLastTickOverlay;
  182. float const fSettingDelta = cl_showdemooverlay.GetFloat();
  183. if ( fSettingDelta <= 0.f ||
  184. fDelta >= fSettingDelta )
  185. {
  186. m_fLastTickOverlay = m_fLastTickTime;
  187. DrawOverlay( fSettingDelta );
  188. }
  189. }
  190. m_bTick = false;
  191. }
  192. }
  193. void DemoOverlay::DrawOverlay( float fSetting )
  194. {
  195. int maskDrawnOverlay = OVR_NONE;
  196. if ( fSetting < 0.f )
  197. {
  198. // Keep drawing
  199. maskDrawnOverlay =
  200. ( demorecorder->IsRecording() ? OVR_REC : 0 ) |
  201. ( demoplayer->IsPlayingBack() ? OVR_PLAY : 0 );
  202. }
  203. else if ( fSetting == 0.f )
  204. {
  205. // None
  206. maskDrawnOverlay = OVR_NONE;
  207. }
  208. else
  209. {
  210. // Flash
  211. maskDrawnOverlay = ( !m_maskDrawnOverlay ) ? (
  212. ( demorecorder->IsRecording() ? OVR_REC : 0 ) |
  213. ( demoplayer->IsPlayingBack() ? OVR_PLAY : 0 )
  214. ) : OVR_NONE;
  215. }
  216. int const idx = 1;
  217. if ( OVR_NONE == maskDrawnOverlay &&
  218. OVR_NONE != m_maskDrawnOverlay )
  219. {
  220. con_nprint_s xprn;
  221. memset( &xprn, 0, sizeof( xprn ) );
  222. xprn.index = idx;
  223. xprn.time_to_live = -1;
  224. Con_NXPrintf( &xprn, "" );
  225. }
  226. if ( OVR_PLAY & maskDrawnOverlay )
  227. {
  228. con_nprint_s xprn;
  229. memset( &xprn, 0, sizeof( xprn ) );
  230. xprn.index = idx;
  231. xprn.color[0] = 0.f;
  232. xprn.color[1] = 1.f;
  233. xprn.color[2] = 0.f;
  234. xprn.fixed_width_font = true;
  235. xprn.time_to_live = ( fSetting > 0.f ) ? fSetting : 1.f;
  236. Con_NXPrintf( &xprn, " PLAY " );
  237. }
  238. if ( OVR_REC & maskDrawnOverlay )
  239. {
  240. con_nprint_s xprn;
  241. memset( &xprn, 0, sizeof( xprn ) );
  242. xprn.index = idx;
  243. xprn.color[0] = 1.f;
  244. xprn.color[1] = 0.f;
  245. xprn.color[2] = 0.f;
  246. xprn.fixed_width_font = true;
  247. xprn.time_to_live = ( fSetting > 0.f ) ? fSetting : 1.f;
  248. Con_NXPrintf( &xprn, " REC " );
  249. }
  250. m_maskDrawnOverlay = maskDrawnOverlay;
  251. }
  252. //-----------------------------------------------------------------------------
  253. // Purpose: Mark whether we are waiting for the first uncompressed update packet
  254. // Input : waiting -
  255. //-----------------------------------------------------------------------------
  256. void CDemoRecorder::SetSignonState(SIGNONSTATE state)
  257. {
  258. if ( demoplayer->IsPlayingBack() )
  259. return;
  260. if ( !demo_enabledemos.GetBool() )
  261. return;
  262. if ( state == SIGNONSTATE_NEW )
  263. {
  264. if ( m_DemoFile.IsOpen() )
  265. {
  266. // we are already recording a demo file
  267. CloseDemoFile();
  268. // prepare for recording next demo
  269. m_nDemoNumber++;
  270. }
  271. StartupDemoHeader();
  272. }
  273. else if ( state == SIGNONSTATE_SPAWN )
  274. {
  275. // close demo file header when this packet is finished
  276. m_bCloseDemoFile = true;
  277. }
  278. else if ( state == SIGNONSTATE_FULL )
  279. {
  280. if ( m_bRecording )
  281. {
  282. StartupDemoFile();
  283. }
  284. }
  285. }
  286. int CDemoRecorder::GetRecordingTick( void )
  287. {
  288. if ( GetBaseLocalClient().m_nMaxClients > 1 )
  289. {
  290. return TIME_TO_TICKS( net_time ) - m_nStartTick;
  291. }
  292. else
  293. {
  294. return GetBaseLocalClient().GetClientTickCount() - m_nStartTick;
  295. }
  296. }
  297. void CDemoRecorder::ResyncDemoClock()
  298. {
  299. if ( GetBaseLocalClient().m_nMaxClients > 1 )
  300. {
  301. m_nStartTick = TIME_TO_TICKS( net_time );
  302. }
  303. else
  304. {
  305. m_nStartTick = GetBaseLocalClient().GetClientTickCount();
  306. }
  307. }
  308. //-----------------------------------------------------------------------------
  309. // Purpose:
  310. // Input : info -
  311. //-----------------------------------------------------------------------------
  312. void CDemoRecorder::GetClientCmdInfo( democmdinfo_t& cmdInfo )
  313. {
  314. for ( int hh = 0; hh < host_state.max_splitscreen_players; ++hh )
  315. {
  316. ACTIVE_SPLITSCREEN_PLAYER_GUARD( hh );
  317. democmdinfo_t::Split_t &info = cmdInfo.u[ hh ];
  318. info.flags = FDEMO_NORMAL;
  319. if( m_bResetInterpolation )
  320. {
  321. info.flags |= FDEMO_NOINTERP;
  322. m_bResetInterpolation = false;
  323. }
  324. g_pClientSidePrediction->GetViewOrigin( info.viewOrigin );
  325. #ifndef DEDICATED
  326. info.viewAngles = GetLocalClient().viewangles;
  327. #endif
  328. g_pClientSidePrediction->GetLocalViewAngles( info.localViewAngles );
  329. // Nothing by default
  330. info.viewOrigin2.Init();
  331. info.viewAngles2.Init();
  332. info.localViewAngles2.Init();
  333. }
  334. m_bResetInterpolation = false;
  335. }
  336. void CDemoRecorder::WriteSplitScreenPlayers()
  337. {
  338. char data[NET_MAX_PAYLOAD];
  339. bf_write msg;
  340. msg.StartWriting( data, NET_MAX_PAYLOAD );
  341. msg.SetDebugName( "DemoFileWriteSplitScreenPlayers" );
  342. FOR_EACH_VALID_SPLITSCREEN_PLAYER( i )
  343. {
  344. if ( i == 0 )
  345. continue;
  346. CSVCMsg_SplitScreen_t ss;
  347. ss.set_type( MSG_SPLITSCREEN_ADDUSER );
  348. ss.set_slot( i );
  349. ss.set_player_index( splitscreen->GetSplitScreenPlayerEntity( i ) );
  350. ss.WriteToBuffer( msg );
  351. }
  352. WriteMessages( msg );
  353. }
  354. void CDemoRecorder::WriteBSPDecals()
  355. {
  356. decallist_t *decalList = (decallist_t*)malloc( sizeof(decallist_t) * Draw_DecalMax() );
  357. int decalcount = DecalListCreate( decalList );
  358. char data[NET_MAX_PAYLOAD];
  359. bf_write msg;
  360. msg.StartWriting( data, NET_MAX_PAYLOAD );
  361. msg.SetDebugName( "DemoFileWriteBSPDecals" );
  362. for ( int i = 0; i < decalcount; i++ )
  363. {
  364. decallist_t *entry = &decalList[ i ];
  365. CSVCMsg_BSPDecal_t decal;
  366. bool found = false;
  367. IClientEntity *clientEntity = entitylist->GetClientEntity( entry->entityIndex );
  368. if ( !clientEntity )
  369. continue;
  370. const model_t * pModel = clientEntity->GetModel();
  371. decal.mutable_pos()->set_x( entry->position.x );
  372. decal.mutable_pos()->set_y( entry->position.y );
  373. decal.mutable_pos()->set_z( entry->position.z );
  374. decal.set_entity_index( entry->entityIndex );
  375. decal.set_decal_texture_index( Draw_DecalIndexFromName( entry->name, &found ) );
  376. decal.set_model_index( pModel ? GetBaseLocalClient().LookupModelIndex( modelloader->GetName( pModel ) ) : 0 );
  377. decal.WriteToBuffer( msg );
  378. }
  379. WriteMessages( msg );
  380. free( decalList );
  381. }
  382. void CDemoRecorder::RecordServerClasses( ServerClass *pClasses )
  383. {
  384. MEM_ALLOC_CREDIT();
  385. if ( !m_DemoFile.IsOpen() )
  386. return;
  387. char *pBigBuffer;
  388. CUtlBuffer bigBuff;
  389. int buffSize = DEMO_RECORD_BUFFER_SIZE;
  390. // keep temp large allocations off of stack
  391. bigBuff.EnsureCapacity( buffSize );
  392. pBigBuffer = (char*)bigBuff.Base();
  393. bf_write buf( "CDemoRecorder::RecordServerClasses", pBigBuffer, buffSize );
  394. // Send SendTable info.
  395. DataTable_WriteSendTablesBuffer( pClasses, &buf );
  396. // Send class descriptions.
  397. DataTable_WriteClassInfosBuffer( pClasses, &buf );
  398. if ( buf.GetNumBitsLeft() <= 0 )
  399. {
  400. Sys_Error( "unable to record server classes\n" );
  401. }
  402. // Now write the buffer into the demo file
  403. m_DemoFile.WriteNetworkDataTables( &buf, GetRecordingTick() );
  404. }
  405. void CDemoRecorder::RecordStringTables()
  406. {
  407. MEM_ALLOC_CREDIT();
  408. if ( !m_DemoFile.IsOpen() )
  409. return;
  410. char *pBigBuffer;
  411. CUtlBuffer bigBuff;
  412. int buffSize = DEMO_RECORD_BUFFER_SIZE;
  413. // keep temp large allocations off of stack
  414. bigBuff.EnsureCapacity( buffSize );
  415. pBigBuffer = (char*)bigBuff.Base();
  416. bf_write buf( pBigBuffer, buffSize );
  417. networkStringTableContainerClient->WriteStringTables( buf );
  418. if ( buf.GetNumBitsLeft() <= 0 )
  419. {
  420. Sys_Error( "unable to record server classes\n" );
  421. }
  422. // Now write the buffer into the demo file
  423. m_DemoFile.WriteStringTables( &buf, GetRecordingTick() );
  424. }
  425. void CDemoRecorder::RecordCustomData( int iCallbackIndex, const void *pData, size_t iDataLength )
  426. {
  427. if ( !m_DemoFile.IsOpen() )
  428. return;
  429. m_DemoFile.WriteCustomData( iCallbackIndex, pData, iDataLength, GetRecordingTick() );
  430. }
  431. void CDemoRecorder::RecordUserInput( int cmdnumber )
  432. {
  433. if ( !m_DemoFile.IsOpen() )
  434. return;
  435. char buffer[256];
  436. bf_write msg( "CDemo::WriteUserCmd", buffer, sizeof(buffer) );
  437. ASSERT_LOCAL_PLAYER_RESOLVABLE();
  438. int nSlot = GET_ACTIVE_SPLITSCREEN_SLOT();
  439. g_ClientDLL->EncodeUserCmdToBuffer( nSlot, msg, cmdnumber );
  440. m_DemoFile.WriteUserCmd( cmdnumber, buffer, msg.GetNumBytesWritten(), GetRecordingTick(), nSlot );
  441. }
  442. void CDemoRecorder::ResetDemoInterpolation( void )
  443. {
  444. m_bResetInterpolation = true;
  445. }
  446. //-----------------------------------------------------------------------------
  447. // Purpose: saves all cvars falgged with FVAR_DEMO to demo file
  448. //-----------------------------------------------------------------------------
  449. void CDemoRecorder::WriteDemoCvars()
  450. {
  451. if ( !m_DemoFile.IsOpen() )
  452. return;
  453. ICvar::Iterator iter( g_pCVar );
  454. for ( iter.SetFirst() ; iter.IsValid() ; iter.Next() )
  455. {
  456. ConCommandBase *var = iter.Get();
  457. if ( var->IsCommand() )
  458. continue;
  459. const ConVar *cvar = ( const ConVar * )var;
  460. if ( !cvar->IsFlagSet( FCVAR_DEMO ) )
  461. continue;
  462. char cvarcmd[MAX_OSPATH];
  463. V_sprintf_safe( cvarcmd,"%s \"%s\"", cvar->GetName(), Host_CleanupConVarStringValue( cvar->GetString() ) );
  464. m_DemoFile.WriteConsoleCommand( cvarcmd, GetRecordingTick(), 0 );
  465. }
  466. }
  467. //-----------------------------------------------------------------------------
  468. // Purpose:
  469. // Input : *cmdname -
  470. //-----------------------------------------------------------------------------
  471. void CDemoRecorder::RecordCommand( const char *cmdstring )
  472. {
  473. if ( !IsRecording() )
  474. return;
  475. if ( !cmdstring || !cmdstring[0] )
  476. return;
  477. if ( !demo_recordcommands.GetInt() )
  478. return;
  479. ASSERT_LOCAL_PLAYER_RESOLVABLE();
  480. m_DemoFile.WriteConsoleCommand( cmdstring, GetRecordingTick(), GET_ACTIVE_SPLITSCREEN_SLOT() );
  481. }
  482. //-----------------------------------------------------------------------------
  483. // Purpose:
  484. //-----------------------------------------------------------------------------
  485. void CDemoRecorder::StartupDemoHeader( void )
  486. {
  487. CloseDemoFile(); // make sure it's closed
  488. // Note: this is replacing tmpfile()
  489. if ( !m_DemoFile.Open( DEMO_HEADER_FILE, false ) )
  490. {
  491. ConDMsg ("ERROR: couldn't open temporary header file.\n");
  492. return;
  493. }
  494. m_bIsDemoHeader = true;
  495. Assert( m_MessageData.GetBasePointer() == NULL );
  496. // setup writing data buffer
  497. m_MessageData.StartWriting( new unsigned char[NET_MAX_PAYLOAD], NET_MAX_PAYLOAD );
  498. m_MessageData.SetDebugName( "DemoHeaderWriteBuffer" );
  499. }
  500. //-----------------------------------------------------------------------------
  501. // Purpose:
  502. //-----------------------------------------------------------------------------
  503. void CDemoRecorder::StartupDemoFile( void )
  504. {
  505. if ( !m_bRecording )
  506. return;
  507. // Already recording!!!
  508. if ( m_DemoFile.IsOpen() )
  509. return;
  510. if ( !demo_enabledemos.GetBool() )
  511. {
  512. Warning( "DEMO: cannot start recording a demo (set 'demo_enabledemos' to 1 and restart the map to enable demos)\n" );
  513. return;
  514. }
  515. char demoFileName[MAX_OSPATH];
  516. if ( m_nDemoNumber <= 1 )
  517. {
  518. V_sprintf_safe( demoFileName, "%s.dem", m_szDemoBaseName );
  519. }
  520. else
  521. {
  522. V_sprintf_safe( demoFileName, "%s_%i.dem", m_szDemoBaseName, m_nDemoNumber );
  523. }
  524. if ( !m_DemoFile.Open( demoFileName, false ) )
  525. return;
  526. // open demo header file containing sigondata
  527. FileHandle_t hDemoHeader = g_pFileSystem->OpenEx( DEMO_HEADER_FILE, "rb", IsGameConsole() ? FSOPEN_NEVERINPACK : 0 );
  528. if ( hDemoHeader == FILESYSTEM_INVALID_HANDLE )
  529. {
  530. ConMsg ("StartupDemoFile: couldn't open demo file header.\n");
  531. return;
  532. }
  533. Assert( m_MessageData.GetBasePointer() == NULL );
  534. // setup writing data buffer
  535. m_MessageData.StartWriting( new unsigned char[NET_MAX_PAYLOAD], NET_MAX_PAYLOAD );
  536. m_MessageData.SetDebugName( "DemoFileWriteBuffer" );
  537. // fill demo header info
  538. demoheader_t *dh = &m_DemoFile.m_DemoHeader;
  539. V_memset(dh, 0, sizeof(demoheader_t));
  540. dh->demoprotocol = DEMO_PROTOCOL;
  541. dh->networkprotocol = GetHostVersion();
  542. V_strcpy_safe(dh->demofilestamp, DEMO_HEADER_ID );
  543. V_FileBase( modelloader->GetName( host_state.worldmodel ), dh->mapname, sizeof( dh->mapname ) );
  544. char szGameDir[MAX_OSPATH];
  545. V_strcpy_safe(szGameDir, com_gamedir );
  546. V_FileBase( szGameDir, dh->gamedirectory, sizeof( dh->gamedirectory ) );
  547. V_strcpy_safe( dh->servername, GetBaseLocalClient().m_Remote.Get( 0 ).m_szRetryAddress );
  548. V_strcpy_safe( dh->clientname, cl_name.GetString() );
  549. // goto end of demo header
  550. g_pFileSystem->Seek(hDemoHeader, 0, FILESYSTEM_SEEK_TAIL);
  551. // get size signon data size
  552. dh->signonlength = g_pFileSystem->Tell(hDemoHeader);
  553. // go back to start
  554. g_pFileSystem->Seek(hDemoHeader, 0, FILESYSTEM_SEEK_HEAD);
  555. // write demo file header info
  556. m_DemoFile.WriteDemoHeader();
  557. // copy signon data from header file to demo file
  558. m_DemoFile.WriteFileBytes( hDemoHeader, dh->signonlength );
  559. // close but keep header file, we might need it for a second record
  560. g_pFileSystem->Close( hDemoHeader );
  561. m_nFrameCount = 0;
  562. m_bIsDemoHeader = false;
  563. ResyncDemoClock(); // reset demo clock
  564. // tell client to sync demo clock too
  565. m_DemoFile.WriteCmdHeader( dem_synctick, 0, 0 );
  566. //write out the custom data callback table if we have any entries
  567. int iCustomDataCallbacks = g_RegisteredDemoCustomDataCallbacks.Count();
  568. if( iCustomDataCallbacks != 0 )
  569. {
  570. size_t iCombinedStringLength = 0;
  571. for( int i = 0; i != iCustomDataCallbacks; ++i )
  572. {
  573. iCombinedStringLength += V_strlen( STRING( g_RegisteredDemoCustomDataCallbacks[i].szSaveID ) ) + 1;
  574. }
  575. size_t iTotalDataSize = iCombinedStringLength + sizeof( int );
  576. uint8 *pWriteBuffer = (uint8 *)stackalloc( iTotalDataSize );
  577. uint8 *pWrite = pWriteBuffer;
  578. iCustomDataCallbacks = LittleDWord( iCustomDataCallbacks );
  579. *(int *)pWrite = iCustomDataCallbacks;
  580. pWrite += sizeof( int );
  581. for( int i = 0; i != iCustomDataCallbacks; ++i )
  582. {
  583. size_t iStringLength = V_strlen( STRING( g_RegisteredDemoCustomDataCallbacks[i].szSaveID ) ) + 1;
  584. memcpy( pWrite, STRING( g_RegisteredDemoCustomDataCallbacks[i].szSaveID ), iStringLength );
  585. pWrite += iStringLength;
  586. }
  587. Assert( pWrite == (pWriteBuffer + iTotalDataSize) );
  588. m_DemoFile.WriteCustomData( -1, pWriteBuffer, iTotalDataSize, 0 );
  589. }
  590. RecordStringTables();
  591. // Demo playback should read this as an incoming message.
  592. WriteDemoCvars(); // save all cvars marked with FCVAR_DEMO
  593. WriteBSPDecals();
  594. // Dump all accumulated avatar data messages into the starting portion of the demo file
  595. CNETMsg_PlayerAvatarData_t *pMsgMyOwnAvatarData = GetBaseLocalClient().AllocOwnPlayerAvatarData();
  596. FOR_EACH_MAP_FAST( GetBaseLocalClient().m_mapPlayerAvatarData, iData )
  597. {
  598. CNETMsg_PlayerAvatarData_t &msgPlayerAvatarData = *GetBaseLocalClient().m_mapPlayerAvatarData.Element( iData );
  599. // if the server authoritative data overrides local version of avatar data then don't write local version
  600. if ( pMsgMyOwnAvatarData && ( msgPlayerAvatarData.accountid() == pMsgMyOwnAvatarData->accountid() ) )
  601. {
  602. delete pMsgMyOwnAvatarData;
  603. pMsgMyOwnAvatarData = NULL;
  604. }
  605. byte buffer[ NET_MAX_PAYLOAD ];
  606. bf_write bfWrite( "CDemoRecorder::NETMsg_PlayerAvatarData", buffer, sizeof( buffer ) );
  607. msgPlayerAvatarData.WriteToBuffer( bfWrite );
  608. WriteMessages( bfWrite );
  609. }
  610. if ( pMsgMyOwnAvatarData )
  611. {
  612. byte buffer[ NET_MAX_PAYLOAD ];
  613. bf_write bfWrite( "CDemoRecorder::NETMsg_PlayerAvatarData", buffer, sizeof( buffer ) );
  614. pMsgMyOwnAvatarData->WriteToBuffer( bfWrite );
  615. WriteMessages( bfWrite );
  616. delete pMsgMyOwnAvatarData;
  617. pMsgMyOwnAvatarData = NULL;
  618. }
  619. g_ClientDLL->HudReset();
  620. if ( splitscreen->GetNumSplitScreenPlayers() > 1 )
  621. {
  622. WriteSplitScreenPlayers();
  623. }
  624. // tell server that we started recording a demo
  625. GetBaseLocalClient().SendStringCmd( "demorestart" );
  626. ConMsg ("Recording to %s...\n", demoFileName);
  627. }
  628. CDemoRecorder::CDemoRecorder()
  629. {
  630. }
  631. CDemoRecorder::~CDemoRecorder()
  632. {
  633. CloseDemoFile();
  634. }
  635. CDemoFile *CDemoRecorder::GetDemoFile()
  636. {
  637. return &m_DemoFile;
  638. }
  639. void CDemoRecorder::ResumeRecording()
  640. {
  641. }
  642. void CDemoRecorder::PauseRecording()
  643. {
  644. }
  645. void CDemoRecorder::CloseDemoFile()
  646. {
  647. if ( m_DemoFile.IsOpen())
  648. {
  649. if ( !m_bIsDemoHeader )
  650. {
  651. // Demo playback should read this as an incoming message.
  652. m_DemoFile.WriteCmdHeader( dem_stop, GetRecordingTick(), 0 );
  653. // update demo header infos
  654. m_DemoFile.m_DemoHeader.playback_ticks = GetRecordingTick();
  655. m_DemoFile.m_DemoHeader.playback_time = host_state.interval_per_tick * GetRecordingTick();
  656. m_DemoFile.m_DemoHeader.playback_frames = m_nFrameCount;
  657. // go back to header and write demoHeader with correct time and #frame again
  658. m_DemoFile.WriteDemoHeader();
  659. ConMsg ("Completed demo, recording time %.1f, game frames %i.\n",
  660. m_DemoFile.m_DemoHeader.playback_time, m_DemoFile.m_DemoHeader.playback_frames );
  661. }
  662. if ( demo_debug.GetInt() )
  663. {
  664. ConMsg ("Closed demo file, %i bytes.\n", m_DemoFile.GetSize() );
  665. }
  666. m_DemoFile.Close();
  667. }
  668. m_bCloseDemoFile = false;
  669. m_bIsDemoHeader = false;
  670. // clear writing data buffer
  671. if ( m_MessageData.GetBasePointer() )
  672. {
  673. delete [] m_MessageData.GetBasePointer();
  674. m_MessageData.StartWriting( NULL, 0 );
  675. }
  676. }
  677. void CDemoRecorder::RecordMessages(bf_read &data, int bits)
  678. {
  679. if ( m_MessageData.GetBasePointer() && (bits>0) )
  680. {
  681. m_MessageData.WriteBitsFromBuffer( &data, bits );
  682. Assert( !m_MessageData.IsOverflowed() );
  683. }
  684. }
  685. void CDemoRecorder::RecordPacket()
  686. {
  687. WriteMessages( m_MessageData );
  688. m_MessageData.Reset(); // clear message buffer
  689. if ( m_bCloseDemoFile )
  690. {
  691. CloseDemoFile();
  692. }
  693. }
  694. void CDemoRecorder::WriteMessages( bf_write &message )
  695. {
  696. if ( !m_DemoFile.IsOpen() )
  697. return;
  698. int len = message.GetNumBytesWritten();
  699. if (len <= 0)
  700. return;
  701. // fill last bits in last byte with NOP if necessary
  702. int nRemainingBits = message.GetNumBitsWritten() % 8;
  703. if ( nRemainingBits > 0 && nRemainingBits <= (8-NETMSG_TYPE_BITS) )
  704. {
  705. CNETMsg_NOP_t nop;
  706. nop.WriteToBuffer( message );
  707. }
  708. Assert( len < NET_MAX_MESSAGE );
  709. // if signondata read as fast as possible, no rewind
  710. // and wait for packet time
  711. unsigned char cmd = m_bIsDemoHeader ? dem_signon : dem_packet;
  712. if ( cmd == dem_packet )
  713. {
  714. m_nFrameCount++;
  715. }
  716. // write command & time
  717. m_DemoFile.WriteCmdHeader( cmd, GetRecordingTick(), 0 );
  718. democmdinfo_t info;
  719. // Snag current info
  720. GetClientCmdInfo( info );
  721. // Store it
  722. m_DemoFile.WriteCmdInfo( info );
  723. // write network channel sequencing infos
  724. int nOutSequenceNr, nInSequenceNr, nOutSequenceNrAck;
  725. GetBaseLocalClient().m_NetChannel->GetSequenceData( nOutSequenceNr, nInSequenceNr, nOutSequenceNrAck );
  726. m_DemoFile.WriteSequenceInfo( nInSequenceNr, nOutSequenceNrAck );
  727. // Output the messge buffer.
  728. m_DemoFile.WriteRawData( (char*) message.GetBasePointer(), len );
  729. if ( demo_debug.GetInt() >= 1 )
  730. {
  731. Msg( "Writing demo message %i bytes at file pos %i\n", len, m_DemoFile.GetCurPos( false ) );
  732. }
  733. }
  734. //-----------------------------------------------------------------------------
  735. // Purpose: stop recording a demo
  736. //-----------------------------------------------------------------------------
  737. void CDemoRecorder::StopRecording( const CGameInfo *pGameInfo )
  738. {
  739. if ( !IsRecording() )
  740. {
  741. return;
  742. }
  743. if ( m_MessageData.GetBasePointer() )
  744. {
  745. delete[] m_MessageData.GetBasePointer();
  746. m_MessageData.StartWriting( NULL, 0);
  747. }
  748. CloseDemoFile();
  749. m_bRecording = false;
  750. m_nDemoNumber = 0;
  751. g_DemoOverlay.Tick();
  752. }
  753. //-----------------------------------------------------------------------------
  754. // Purpose:
  755. // Input : *name -
  756. // track -
  757. //-----------------------------------------------------------------------------
  758. void CDemoRecorder::StartRecording( const char *name, bool bContinuously )
  759. {
  760. V_strcpy_safe( m_szDemoBaseName, name );
  761. m_bRecording = true;
  762. m_nDemoNumber = 1;
  763. m_bResetInterpolation = false;
  764. g_DemoOverlay.Tick();
  765. // request a full game update from server
  766. GetBaseLocalClient().ForceFullUpdate( "recording demo" );
  767. }
  768. //-----------------------------------------------------------------------------
  769. // Purpose:
  770. // Output : Returns true on success, false on failure.
  771. //-----------------------------------------------------------------------------
  772. bool CDemoRecorder::IsRecording( void )
  773. {
  774. g_DemoOverlay.Tick();
  775. return m_bRecording;
  776. }
  777. //
  778. // Demo highlight helpers
  779. //
  780. struct DemoUserInfo_t
  781. {
  782. DemoUserInfo_t()
  783. : userID( -1 )
  784. , name( NULL )
  785. {
  786. }
  787. int userID;
  788. char *name;
  789. CSteamID steamID;
  790. };
  791. CUtlVector< DemoUserInfo_t > s_demoUserInfo;
  792. void Callback_DemoScanUserInfoChanged( void *object, INetworkStringTable *stringTable, int stringNumber, const char *newString, const void *newData )
  793. {
  794. // stringnumber == player slot
  795. player_info_t *player = (player_info_t*)newData;
  796. if ( !player )
  797. {
  798. if ( s_demoUserInfo.Count() > stringNumber )
  799. {
  800. // clear out the entry in our user info array
  801. s_demoUserInfo[ stringNumber ].userID = -1;
  802. s_demoUserInfo[ stringNumber ].name = NULL;
  803. s_demoUserInfo[ stringNumber ].steamID.SetFromUint64( 0 );
  804. }
  805. return; // player left the game
  806. }
  807. CByteswap byteswap;
  808. byteswap.SetTargetBigEndian( true );
  809. byteswap.SwapFieldsToTargetEndian( player );
  810. if ( s_demoUserInfo.Count() <= stringNumber )
  811. {
  812. s_demoUserInfo.SetCountNonDestructively( stringNumber + 1 );
  813. }
  814. DemoUserInfo_t &entry = s_demoUserInfo.Element( stringNumber );
  815. entry.userID = player->userID;
  816. entry.name = player->name;
  817. entry.steamID.SetFromUint64( player->xuid );
  818. }
  819. void GetExtraDeathEventInfo( KeyValues *pKeys )
  820. {
  821. int nAttackerID = pKeys->GetInt( "attacker" );
  822. int nVictimID = pKeys->GetInt( "userid" );
  823. int nAssisterID = pKeys->GetInt( "assister" );
  824. FOR_EACH_VEC( s_demoUserInfo, i )
  825. {
  826. if ( nAttackerID == s_demoUserInfo[ i ].userID )
  827. {
  828. pKeys->SetString( "attacker_name", s_demoUserInfo[ i ].name );
  829. pKeys->SetUint64( "attacker_xuid", s_demoUserInfo[ i ].steamID.ConvertToUint64() );
  830. }
  831. else if ( nVictimID == s_demoUserInfo[ i ].userID )
  832. {
  833. pKeys->SetString( "victim_name", s_demoUserInfo[ i ].name );
  834. pKeys->SetUint64( "victim_xuid", s_demoUserInfo[ i ].steamID.ConvertToUint64() );
  835. }
  836. else if ( nAssisterID == s_demoUserInfo[ i ].userID )
  837. {
  838. pKeys->SetString( "assister_name", s_demoUserInfo[ i ].name );
  839. pKeys->SetUint64( "assister_xuid", s_demoUserInfo[ i ].steamID.ConvertToUint64() );
  840. }
  841. }
  842. }
  843. void GetExtraEventInfo( KeyValues *pKeys )
  844. {
  845. int nUserID = pKeys->GetInt( "userid" );
  846. FOR_EACH_VEC( s_demoUserInfo, i )
  847. {
  848. if ( nUserID == s_demoUserInfo[ i ].userID )
  849. {
  850. pKeys->SetString( "player_name", s_demoUserInfo[ i ].name );
  851. pKeys->SetUint64( "player_xuid", s_demoUserInfo[ i ].steamID.ConvertToUint64() );
  852. }
  853. }
  854. }
  855. static int s_nMaxViewers = 0;
  856. static int s_nMaxExternalTotal = 0;
  857. static int s_nMaxExternalLinked = 0;
  858. static int s_nMaxCombinedViewers = 0;
  859. void GetEventInfoForScan( const char *pszMode, KeyValues *pKeys )
  860. {
  861. if ( V_strcasecmp( pszMode, "hltv_status" ) == 0 )
  862. {
  863. int nClients = pKeys->GetInt( "clients", -1 );
  864. int nProxies = pKeys->GetInt( "proxies", -1 );
  865. int nExternalTotal = pKeys->GetInt( "externaltotal", -1 );
  866. int nExternalLinked = pKeys->GetInt( "externallinked", -1 );
  867. Msg( " GOTV Viewers: %d External Viewers: %d Linked: %d Combined Viewers: %d \n", nClients - nProxies, nExternalTotal, nExternalLinked, ( nClients - nProxies ) + nExternalTotal );
  868. if ( ( nClients - nProxies ) > s_nMaxViewers )
  869. {
  870. s_nMaxViewers = nClients - nProxies;
  871. }
  872. if ( nExternalTotal > s_nMaxExternalTotal )
  873. {
  874. s_nMaxExternalTotal = nExternalTotal;
  875. }
  876. if ( nExternalLinked > s_nMaxExternalLinked )
  877. {
  878. s_nMaxExternalLinked = nExternalLinked;
  879. }
  880. if ( ( ( nClients - nProxies ) + nExternalTotal ) > s_nMaxCombinedViewers )
  881. {
  882. s_nMaxCombinedViewers = ( nClients - nProxies ) + nExternalTotal;
  883. }
  884. }
  885. }
  886. bool CheckKeyXuid( const CSteamID &steamID, KeyValues *pKeys, const char* pKeyWithXuid )
  887. {
  888. CSteamID playerSteamID( pKeys->GetUint64( pKeyWithXuid ) );
  889. if ( steamID.GetAccountID() == playerSteamID.GetAccountID() )
  890. {
  891. return true;
  892. }
  893. return false;
  894. }
  895. int GetPlayerIndex( const CSteamID &steamID )
  896. {
  897. FOR_EACH_VEC( s_demoUserInfo, i )
  898. {
  899. if ( s_demoUserInfo[ i ].steamID.GetAccountID() == steamID.GetAccountID() )
  900. {
  901. return i + 1;
  902. }
  903. }
  904. return -1;
  905. }
  906. //-----------------------------------------------------------------------------
  907. // Purpose: Called when a demo file runs out, or the user starts a game
  908. // Output : void CDemo::StopPlayback
  909. //-----------------------------------------------------------------------------
  910. void CDemoPlayer::StopPlayback( void )
  911. {
  912. if ( !IsPlayingBack() )
  913. return;
  914. demoaction->StopPlaying();
  915. m_DemoFile.Close();
  916. m_bPlayingBack = false;
  917. m_bPlaybackPaused = false;
  918. m_flAutoResumeTime = 0.0f;
  919. if ( m_bTimeDemo )
  920. {
  921. g_EngineStats.EndRun();
  922. if ( !s_bBenchframe )
  923. {
  924. WriteTimeDemoResults();
  925. }
  926. else
  927. {
  928. mat_norendering.SetValue( 0 );
  929. }
  930. m_bTimeDemo = false;
  931. }
  932. else
  933. {
  934. int framecount = host_framecount - m_nTimeDemoStartFrame;
  935. float demotime = Sys_FloatTime() - m_flTimeDemoStartTime;
  936. if ( demotime > 0.0f )
  937. {
  938. DevMsg( "Demo playback finished ( %.1f seconds, %i render frames, %.2f fps).\n", demotime, framecount, framecount/demotime);
  939. }
  940. }
  941. m_flPlaybackRateModifier = 1.0f;
  942. delete[] m_DemoPacket.data;
  943. m_DemoPacket.data = NULL;
  944. scr_demo_override_fov = 0.0f;
  945. if ( demo_quitafterplayback.GetBool() )
  946. {
  947. Cbuf_AddText( Cbuf_GetCurrentPlayer(), "quit\n" );
  948. }
  949. g_ClientDLL->OnDemoPlaybackStop();
  950. FOR_EACH_VEC( m_ImportantTicks, i )
  951. {
  952. if ( m_ImportantTicks[ i ].pKeys )
  953. {
  954. m_ImportantTicks[ i ].pKeys->deleteThis();
  955. }
  956. }
  957. m_ImportantTicks.RemoveAll();
  958. m_ImportantGameEvents.RemoveAll();
  959. m_highlights.RemoveAll();
  960. m_nCurrentHighlight = -1;
  961. m_highlightSteamID.SetFromUint64( 0 );
  962. m_nHighlightPlayerIndex = -1;
  963. m_bScanMode = false;
  964. m_szScanMode[0] = 0;
  965. }
  966. #define SKIP_TO_TICK_FLAG uint32( uint32( 0x88 ) << 24 )
  967. bool CDemoPlayer::IsSkipping( void )const
  968. {
  969. return m_bPlayingBack && ( ( m_nSkipToTick != -1 ) || g_ClientDLL->ShouldSkipEvidencePlayback( m_pPlaybackParameters ) );
  970. }
  971. int CDemoPlayer::GetTotalTicks(void)
  972. {
  973. return m_DemoFile.m_DemoHeader.playback_ticks; // == m_DemoFile.GetTotalTicks();
  974. }
  975. void CDemoPlayer::SetPacketReadSuspended( bool bSuspendPacketReading )
  976. {
  977. if ( m_bPacketReadSuspended == bSuspendPacketReading )
  978. return; // same state
  979. m_bPacketReadSuspended = bSuspendPacketReading;
  980. if ( !m_bPacketReadSuspended )
  981. ResyncDemoClock(); // Make sure we resync demo clock when we resume packet reading
  982. }
  983. void CDemoPlayer::SkipToTick( int tick, bool bRelative, bool bPause )
  984. {
  985. if ( m_bPacketReadSuspended )
  986. return; // demo ticks and host ticks aren't resync'd when packet read is suspended
  987. if ( bRelative )
  988. {
  989. tick = GetPlaybackTick() + tick;
  990. }
  991. if ( tick < 0 )
  992. return;
  993. if ( tick < GetPlaybackTick() )
  994. {
  995. if ( m_pPlaybackParameters && m_pPlaybackParameters->m_bAnonymousPlayerIdentity )
  996. {
  997. Msg( "Going backwards not available in Overwatch!\n" );
  998. return;
  999. }
  1000. RestartPlayback();
  1001. #if 0 // old way
  1002. // we have to reload the whole demo file
  1003. // we need to create a temp copy of the filename
  1004. char fileName[MAX_OSPATH];
  1005. V_strcpy_safe( fileName, m_DemoFile.m_szFileName );
  1006. StopPlayback();
  1007. // disconnect before reloading demo, to avoid sometimes loading into game instead of demo
  1008. GetBaseLocalClient().Disconnect(false);
  1009. // reload current demo file
  1010. StartPlayback( fileName, m_bTimeDemo, NULL );
  1011. // Make sure the proper skipping occurs after reload
  1012. if ( tick > 0 )
  1013. tick |= SKIP_TO_TICK_FLAG;
  1014. #endif
  1015. }
  1016. if ( tick != GetPlaybackTick() )
  1017. {
  1018. m_nSkipToTick = tick;
  1019. }
  1020. if ( bPause || m_bPlaybackPaused )
  1021. {
  1022. int nTicksPerFrame = m_DemoFile.GetTicksPerFrame( );
  1023. m_nTickToPauseOn = tick + nTicksPerFrame;
  1024. m_bSavedInterpolateState = cl_interpolate.GetBool();
  1025. cl_interpolate.SetValue( 0 );
  1026. ResumePlayback();
  1027. }
  1028. }
  1029. void CDemoPlayer::SkipToImportantTick( const DemoImportantTick_t *pTick )
  1030. {
  1031. if ( m_bPacketReadSuspended )
  1032. return; // demo ticks and host ticks aren't resync'd when packet read is suspended
  1033. int nStartTick = GetPlaybackTick();
  1034. int nTargetTick = pTick->nPreviousTick;
  1035. int nTicksBeforeEvent = m_ImportantGameEvents[ pTick->nImportanGameEventIndex ].flSeekTimeBefore / host_state.interval_per_tick;
  1036. if ( nTicksBeforeEvent > 0 )
  1037. {
  1038. int nTargetTick = pTick->nTick - nTicksBeforeEvent;
  1039. // handle case where desired next tick is close to where we already are and going to 'ticks before event' would cause us to seek backwards.
  1040. // instead we won't skip at all
  1041. if ( pTick->nTick >= nStartTick && nTargetTick < nStartTick )
  1042. {
  1043. nTargetTick = nStartTick;
  1044. }
  1045. if ( nTargetTick < 0 )
  1046. {
  1047. nTargetTick = 0;
  1048. }
  1049. }
  1050. if ( nTargetTick < nStartTick )
  1051. {
  1052. if ( m_pPlaybackParameters && m_pPlaybackParameters->m_bAnonymousPlayerIdentity )
  1053. {
  1054. Msg( "Going backwards not available in Overwatch!\n" );
  1055. return;
  1056. }
  1057. RestartPlayback();
  1058. }
  1059. if ( nTargetTick != nStartTick )
  1060. {
  1061. m_nSkipToTick = nTargetTick;
  1062. }
  1063. if ( m_bPlaybackPaused )
  1064. {
  1065. // this code sets things up so that we will pause once we reach the tick.
  1066. int nTicksPerFrame = m_DemoFile.GetTicksPerFrame();
  1067. m_nTickToPauseOn = pTick->nTick + nTicksPerFrame;
  1068. // turn interpolation off for the time between seeking and playing until pause state
  1069. m_bSavedInterpolateState = cl_interpolate.GetBool();
  1070. cl_interpolate.SetValue( 0 );
  1071. ResumePlayback();
  1072. }
  1073. }
  1074. //-----------------------------------------------------------------------------
  1075. // Purpose: Read in next demo message and send to local client over network channel, if it's time.
  1076. // Output : bool
  1077. //-----------------------------------------------------------------------------
  1078. bool CDemoPlayer::ParseAheadForInterval( int curtick, int intervalticks )
  1079. {
  1080. int tick = 0;
  1081. int dummy;
  1082. byte cmd = dem_stop;
  1083. democmdinfo_t nextinfo;
  1084. long starting_position = m_DemoFile.GetCurPos( true );
  1085. // remove all entrys older than 32 ticks
  1086. while ( m_DestCmdInfo.Count() > 0 )
  1087. {
  1088. DemoCommandQueue& entry = m_DestCmdInfo[ 0 ];
  1089. if ( entry.tick >= (curtick - 32) )
  1090. break;
  1091. if ( entry.filepos >= starting_position )
  1092. break;
  1093. m_DestCmdInfo.Remove( 0 );
  1094. }
  1095. if ( m_bTimeDemo )
  1096. return false;
  1097. while ( true )
  1098. {
  1099. // skip forward to the next dem_packet or dem_signon
  1100. bool swallowmessages = true;
  1101. do
  1102. {
  1103. int nPlayerSlot = 0;
  1104. m_DemoFile.ReadCmdHeader( cmd, tick, nPlayerSlot );
  1105. // COMMAND HANDLERS
  1106. switch ( cmd )
  1107. {
  1108. case dem_synctick:
  1109. case dem_stop:
  1110. {
  1111. m_DemoFile.SeekTo( starting_position, true );
  1112. return false;
  1113. }
  1114. break;
  1115. case dem_consolecmd:
  1116. {
  1117. ACTIVE_SPLITSCREEN_PLAYER_GUARD( nPlayerSlot );
  1118. m_DemoFile.ReadConsoleCommand();
  1119. }
  1120. break;
  1121. case dem_datatables:
  1122. {
  1123. m_DemoFile.ReadNetworkDataTables( NULL );
  1124. }
  1125. break;
  1126. case dem_usercmd:
  1127. {
  1128. ACTIVE_SPLITSCREEN_PLAYER_GUARD( nPlayerSlot );
  1129. m_DemoFile.ReadUserCmd( NULL, dummy );
  1130. }
  1131. break;
  1132. case dem_customdata:
  1133. {
  1134. m_DemoFile.ReadCustomData( NULL, NULL );
  1135. }
  1136. break;
  1137. case dem_stringtables:
  1138. {
  1139. m_DemoFile.ReadStringTables( NULL );
  1140. }
  1141. break;
  1142. default:
  1143. {
  1144. swallowmessages = false;
  1145. }
  1146. break;
  1147. }
  1148. }
  1149. while ( swallowmessages );
  1150. int curpos = m_DemoFile.GetCurPos( true );
  1151. // we read now a dem_packet
  1152. m_DemoFile.ReadCmdInfo( nextinfo );
  1153. m_DemoFile.ReadSequenceInfo( dummy, dummy );
  1154. m_DemoFile.ReadRawData( NULL, 0 );
  1155. DemoCommandQueue entry;
  1156. entry.info = nextinfo;
  1157. entry.tick = tick;
  1158. entry.filepos = curpos;
  1159. int i = 0;
  1160. int c = m_DestCmdInfo.Count();
  1161. for ( ; i < c; ++i )
  1162. {
  1163. if ( m_DestCmdInfo[ i ].filepos == entry.filepos )
  1164. break; // cmdinfo is already in list
  1165. }
  1166. if ( i >= c )
  1167. {
  1168. // add cmdinfo to list
  1169. if ( c > 0 )
  1170. {
  1171. if ( m_DestCmdInfo[ c - 1 ].tick > tick )
  1172. {
  1173. m_DestCmdInfo.RemoveAll();
  1174. }
  1175. }
  1176. m_DestCmdInfo.AddToTail( entry );
  1177. }
  1178. if ( ( tick - curtick ) > intervalticks )
  1179. break;
  1180. }
  1181. m_DemoFile.SeekTo( starting_position, true );
  1182. return true;
  1183. }
  1184. //-----------------------------------------------------------------------------
  1185. // Purpose: Read in next demo message and send to local client over network channel, if it's time.
  1186. // Output : bool
  1187. //-----------------------------------------------------------------------------
  1188. netpacket_t *CDemoPlayer::ReadPacket( void )
  1189. {
  1190. int tick = 0;
  1191. byte cmd = dem_signon;
  1192. long curpos = 0;
  1193. if ( ! m_DemoFile.IsOpen() )
  1194. {
  1195. m_bPlayingBack = false;
  1196. Host_EndGame( true, "Tried to read a demo message with no demo file\n" );
  1197. return NULL;
  1198. }
  1199. // If game is still shutting down, then don't read any demo messages from file quite yet
  1200. if ( HostState_IsGameShuttingDown() )
  1201. {
  1202. return NULL;
  1203. }
  1204. Assert( IsPlayingBack() );
  1205. if ( m_nTimeDemoCurrentFrame >= 0 )
  1206. {
  1207. // don't scan when doing overwatch
  1208. if ( !m_pPlaybackParameters || !m_pPlaybackParameters->m_bAnonymousPlayerIdentity )
  1209. {
  1210. GetImportantGameEventIDs();
  1211. ScanForImportantTicks();
  1212. if ( m_bScanMode && m_ImportantTicks.Count() > 0 )
  1213. {
  1214. CL_ScanDemoDone( m_szScanMode );
  1215. return NULL;
  1216. }
  1217. if ( m_bDoHighlightScan )
  1218. {
  1219. BuildHighlightList();
  1220. }
  1221. }
  1222. }
  1223. // External editor has paused playback
  1224. if ( CheckPausedPlayback() )
  1225. return NULL;
  1226. // handle highlights
  1227. if ( m_nCurrentHighlight != -1 && !IsSkipping() )
  1228. {
  1229. int nCurrentTick = GetPlaybackTick();
  1230. if ( nCurrentTick >= m_highlights[ m_nCurrentHighlight ].nPlayToTick )
  1231. {
  1232. m_nCurrentHighlight++;
  1233. if ( m_nCurrentHighlight >= m_highlights.Count() )
  1234. {
  1235. m_nCurrentHighlight = -1;
  1236. SetPlaybackTimeScale( 1.0f );
  1237. g_ClientDLL->ShowHighlightSkippingMessage( false );
  1238. m_pPlaybackParameters = NULL;
  1239. return NULL;
  1240. }
  1241. }
  1242. const DemoHighlightEntry_t &highlight = m_highlights[ m_nCurrentHighlight ];
  1243. SetPlaybackParametersLockFirstPersonAccountID( highlight.unAccountID );
  1244. // deal with skipping and fast forwarding
  1245. if ( highlight.nSeekToTick != -1 && nCurrentTick < highlight.nSeekToTick )
  1246. {
  1247. g_ClientDLL->ShowHighlightSkippingMessage( true, nCurrentTick, highlight.nSeekToTick, highlight.nPlayToTick );
  1248. m_nSkipToTick = highlight.nSeekToTick;
  1249. }
  1250. else
  1251. {
  1252. if ( nCurrentTick < highlight.nFastForwardToTick )
  1253. {
  1254. g_ClientDLL->ShowHighlightSkippingMessage( true, nCurrentTick, highlight.nSeekToTick, highlight.nPlayToTick );
  1255. SetPlaybackTimeScale( demo_highlight_fastforwardspeed.GetFloat() );
  1256. }
  1257. else
  1258. {
  1259. SetPlaybackTimeScale( 1.0f );
  1260. g_ClientDLL->ShowHighlightSkippingMessage( false, nCurrentTick, highlight.nSeekToTick, highlight.nPlayToTick );
  1261. }
  1262. }
  1263. }
  1264. bool bStopReading = false;
  1265. while ( !bStopReading )
  1266. {
  1267. curpos = m_DemoFile.GetCurPos( true );
  1268. int nPlayerSlot = 0;
  1269. m_DemoFile.ReadCmdHeader( cmd, tick, nPlayerSlot );
  1270. m_nPacketTick = tick;
  1271. if ( m_nTickToPauseOn != -1 && tick >= m_nTickToPauseOn )
  1272. {
  1273. m_nTickToPauseOn = -1;
  1274. PausePlayback( -1 );
  1275. cl_interpolate.SetValue( m_bSavedInterpolateState ? 1 : 0 );
  1276. }
  1277. if ( m_nCurrentHighlight != -1 && IsSkipping() )
  1278. {
  1279. if ( m_highlights[ m_nCurrentHighlight ].nSeekToTick != -1 && tick >= m_highlights[ m_nCurrentHighlight ].nSeekToTick )
  1280. {
  1281. m_nSkipToTick = -1;
  1282. }
  1283. }
  1284. // always read control commands
  1285. if ( !IsControlCommand( cmd ) )
  1286. {
  1287. int playbacktick = GetPlaybackTick();
  1288. if ( !m_bTimeDemo )
  1289. {
  1290. // Time demo ignores clocks and tries to synchronize frames to what was recorded
  1291. // I.e., while frame is the same, read messages, otherwise, skip out.
  1292. // If we're still signing on, then just parse messages until fully connected no matter what
  1293. if ( GetBaseLocalClient().IsActive() &&
  1294. (tick > playbacktick) && !IsSkipping() )
  1295. {
  1296. // is not time yet
  1297. bStopReading = true;
  1298. }
  1299. }
  1300. else
  1301. {
  1302. if ( m_nTimeDemoCurrentFrame == host_framecount )
  1303. {
  1304. if ( !IsSkipping() )
  1305. {
  1306. // If we are playing back a timedemo, and we've already passed on a
  1307. // frame update for this host_frame tag, then we'll just skip this mess
  1308. bStopReading = true;
  1309. }
  1310. }
  1311. }
  1312. if ( bStopReading )
  1313. {
  1314. demoaction->Update( false, playbacktick, TICKS_TO_TIME( playbacktick ) );
  1315. m_DemoFile.SeekTo( curpos, true ); // go back to start of current demo command
  1316. return NULL; // Not time yet, dont return packet data.
  1317. }
  1318. }
  1319. // COMMAND HANDLERS
  1320. switch ( cmd )
  1321. {
  1322. case dem_synctick:
  1323. {
  1324. if ( demo_debug.GetBool() )
  1325. {
  1326. Msg( "%d dem_synctick\n", tick );
  1327. }
  1328. ResyncDemoClock();
  1329. m_nRestartFilePos = m_DemoFile.GetCurPos( true );
  1330. // Once demo clock got resync-ed we can go ahead and
  1331. // perform skipping logic normally
  1332. if ( ( m_nSkipToTick != -1 ) &&
  1333. ( ( m_nSkipToTick & SKIP_TO_TICK_FLAG ) == SKIP_TO_TICK_FLAG ) )
  1334. {
  1335. m_nSkipToTick &= ~SKIP_TO_TICK_FLAG;
  1336. }
  1337. }
  1338. break;
  1339. case dem_stop:
  1340. {
  1341. if ( demo_debug.GetBool() )
  1342. {
  1343. Msg( "%d dem_stop\n", tick );
  1344. }
  1345. if ( g_pMatchFramework )
  1346. {
  1347. g_pMatchFramework->GetEventsSubscription()->BroadcastEvent( new KeyValues( "OnDemoFileEndReached" ) );
  1348. }
  1349. FOR_EACH_VALID_SPLITSCREEN_PLAYER( hh )
  1350. {
  1351. ACTIVE_SPLITSCREEN_PLAYER_GUARD( hh );
  1352. GetBaseLocalClient().Disconnect(true);
  1353. }
  1354. return NULL;
  1355. }
  1356. break;
  1357. case dem_consolecmd:
  1358. {
  1359. ACTIVE_SPLITSCREEN_PLAYER_GUARD( nPlayerSlot );
  1360. const char * command = m_DemoFile.ReadConsoleCommand();
  1361. if ( demo_debug.GetBool() )
  1362. {
  1363. Msg( "%d dem_consolecmd [%s]\n", tick, command );
  1364. }
  1365. Cbuf_AddText( Cbuf_GetCurrentPlayer(), command, kCommandSrcDemoFile );
  1366. Cbuf_Execute();
  1367. }
  1368. break;
  1369. case dem_datatables:
  1370. {
  1371. if ( demo_debug.GetBool() )
  1372. {
  1373. Msg( "%d dem_datatables\n", tick );
  1374. }
  1375. void *data = malloc( DEMO_RECORD_BUFFER_SIZE ); // X360TBD: How much memory is really needed here?
  1376. bf_read buf( "dem_datatables", data, DEMO_RECORD_BUFFER_SIZE );
  1377. m_DemoFile.ReadNetworkDataTables( &buf );
  1378. buf.Seek( 0 ); // re-read data
  1379. // support for older engine demos
  1380. if ( !DataTable_LoadDataTablesFromBuffer( &buf, m_DemoFile.m_DemoHeader.demoprotocol ) )
  1381. {
  1382. Host_Error( "Error parsing network data tables during demo playback." );
  1383. }
  1384. free( data );
  1385. }
  1386. break;
  1387. case dem_stringtables:
  1388. {
  1389. void *data = malloc( DEMO_RECORD_BUFFER_SIZE ); // X360TBD: How much memory is really needed here?
  1390. bf_read buf( "dem_stringtables", data, DEMO_RECORD_BUFFER_SIZE );
  1391. m_DemoFile.ReadStringTables( &buf );
  1392. buf.Seek( 0 );
  1393. if ( !networkStringTableContainerClient->ReadStringTables( buf ) )
  1394. {
  1395. Host_Error( "Error parsing string tables during demo playback." );
  1396. }
  1397. free( data );
  1398. }
  1399. break;
  1400. case dem_usercmd:
  1401. {
  1402. ACTIVE_SPLITSCREEN_PLAYER_GUARD( nPlayerSlot );
  1403. if ( demo_debug.GetBool() )
  1404. {
  1405. Msg( "%d dem_usercmd\n", tick );
  1406. }
  1407. char buffer[256];
  1408. int length = sizeof(buffer);
  1409. int outgoing_sequence = m_DemoFile.ReadUserCmd( buffer, length );
  1410. // put it into a bitbuffer
  1411. bf_read msg( "CDemo::ReadUserCmd", buffer, length );
  1412. g_ClientDLL->DecodeUserCmdFromBuffer( nPlayerSlot, msg, outgoing_sequence );
  1413. // Note, we need to have the current outgoing sequence correct so we can do prediction
  1414. // correctly during playback
  1415. GetBaseLocalClient().lastoutgoingcommand = outgoing_sequence;
  1416. }
  1417. break;
  1418. case dem_customdata:
  1419. {
  1420. int iCallbackIndex;
  1421. uint8 *pData;
  1422. int iSize = m_DemoFile.ReadCustomData( &iCallbackIndex, &pData );
  1423. if( iCallbackIndex == -1 )
  1424. {
  1425. //special handler, this is the data that fills in the rest of the data
  1426. uint8 *pParse = pData;
  1427. int iTableEntries = *(int *)pParse;
  1428. pParse += sizeof( int );
  1429. iTableEntries = LittleDWord( iTableEntries );
  1430. m_CustomDataCallbackMap.SetSize( iTableEntries );
  1431. for( int i = 0; i != iTableEntries; ++i )
  1432. {
  1433. m_CustomDataCallbackMap[i].name = (char *)pParse;
  1434. pParse += V_strlen( (char *)pParse ) + 1;
  1435. //now that we have the name, map that to a registered callback function
  1436. int j;
  1437. for( j = 0; j != g_RegisteredDemoCustomDataCallbacks.Count(); ++j )
  1438. {
  1439. if( V_stricmp( m_CustomDataCallbackMap[i].name.Get(), STRING( g_RegisteredDemoCustomDataCallbacks[j].szSaveID ) ) == 0 )
  1440. {
  1441. //match
  1442. m_CustomDataCallbackMap[i].pCallback = g_RegisteredDemoCustomDataCallbacks[j].pCallback;
  1443. break;
  1444. }
  1445. }
  1446. Assert( j != g_RegisteredDemoCustomDataCallbacks.Count() ); //found a match
  1447. }
  1448. Assert( pParse == (pData + iSize) ); //properly parsed the table
  1449. }
  1450. else
  1451. {
  1452. //send it off
  1453. Assert( iCallbackIndex < m_CustomDataCallbackMap.Count() );
  1454. Assert( m_CustomDataCallbackMap[iCallbackIndex].pCallback != NULL );
  1455. if( m_CustomDataCallbackMap[iCallbackIndex].pCallback != NULL )
  1456. m_CustomDataCallbackMap[iCallbackIndex].pCallback( pData, iSize );
  1457. else
  1458. Warning( "Unable to decode custom demo data, callback \"%s\" not found.\n", m_CustomDataCallbackMap[iCallbackIndex].name.Get() );
  1459. }
  1460. }
  1461. break;
  1462. default:
  1463. {
  1464. bStopReading = true;
  1465. if ( IsSkipping() )
  1466. {
  1467. // adjust playback host_tickcount when skipping
  1468. m_nStartTick = host_tickcount - tick;
  1469. }
  1470. }
  1471. break;
  1472. }
  1473. }
  1474. if ( cmd == dem_packet )
  1475. {
  1476. // remember last frame we read a dem_packet update
  1477. m_nTimeDemoCurrentFrame = host_framecount;
  1478. }
  1479. int inseq, outseqack, outseq = 0;
  1480. m_DemoFile.ReadCmdInfo( m_LastCmdInfo );
  1481. m_DemoFile.ReadSequenceInfo( inseq, outseqack );
  1482. GetBaseLocalClient().m_NetChannel->SetSequenceData( outseq, inseq, outseqack );
  1483. int length = m_DemoFile.ReadRawData( (char*)m_DemoPacket.data, NET_MAX_PAYLOAD );
  1484. if ( demo_debug.GetBool() )
  1485. {
  1486. Msg( "%d network packet [%d]\n", tick, length );
  1487. }
  1488. if ( length > 0 )
  1489. {
  1490. // succsessfully read new demopacket
  1491. m_DemoPacket.received = realtime;
  1492. m_DemoPacket.size = length;
  1493. m_DemoPacket.message.StartReading( m_DemoPacket.data, m_DemoPacket.size );
  1494. if ( demo_debug.GetInt() >= 1 )
  1495. {
  1496. Msg( "Demo message, tick %i, %i bytes\n", GetPlaybackTick(), length );
  1497. }
  1498. }
  1499. // Try and jump ahead one frame
  1500. m_bInterpolateView = ParseAheadForInterval( tick, 8 );
  1501. // ConMsg( "Reading message for %i : %f skip %i\n", m_nFrameCount, fElapsedTime, forceskip ? 1 : 0 );
  1502. // Skip a few ticks before doing any timing
  1503. if ( (m_nTimeDemoStartFrame < 0) && GetPlaybackTick() > 100 )
  1504. {
  1505. m_nTimeDemoStartFrame = host_framecount;
  1506. m_flTimeDemoStartTime = Sys_FloatTime();
  1507. m_flTotalFPSVariability = 0.0f;
  1508. if ( m_bTimeDemo )
  1509. {
  1510. g_EngineStats.BeginRun();
  1511. g_PerfStats.Reset();
  1512. }
  1513. }
  1514. if ( m_nSnapshotTick > 0 && m_nSnapshotTick <= GetPlaybackTick() )
  1515. {
  1516. const char *filename = "benchframe";
  1517. if ( m_SnapshotFilename[0] )
  1518. filename = m_SnapshotFilename;
  1519. CL_TakeScreenshot( filename ); // take a screenshot
  1520. m_nSnapshotTick = 0;
  1521. if ( s_bBenchframe )
  1522. {
  1523. Cbuf_AddText( Cbuf_GetCurrentPlayer(), "stopdemo\n" );
  1524. }
  1525. }
  1526. return &m_DemoPacket;
  1527. }
  1528. void CDemoPlayer::InterpolateDemoCommand( int nSlot, int targettick, DemoCommandQueue& prev, DemoCommandQueue& next )
  1529. {
  1530. CUtlVector< DemoCommandQueue >& list = m_DestCmdInfo;
  1531. int c = list.Count();
  1532. prev.info.Reset();
  1533. next.info.Reset();
  1534. if ( c < 2 )
  1535. {
  1536. // we need at least two entries to interpolate
  1537. return;
  1538. }
  1539. int i = 0;
  1540. int savedI = -1;
  1541. DemoCommandQueue *entry1 = &list[ i ];
  1542. DemoCommandQueue *entry2 = &list[ i+1 ];
  1543. while ( true )
  1544. {
  1545. if ( (entry1->tick <= targettick) && (entry2->tick > targettick) )
  1546. {
  1547. // Means we hit a FDEMO_NOINTERP along the way to now
  1548. if ( savedI != -1 )
  1549. {
  1550. prev = list[ savedI ];
  1551. next = list[ savedI + 1 ];
  1552. }
  1553. else
  1554. {
  1555. prev = *entry1;
  1556. next = *entry2;
  1557. }
  1558. return;
  1559. }
  1560. // 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
  1561. // 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
  1562. /// the first FDEMO_NOINTERP we see
  1563. if ( savedI == -1 &&
  1564. entry2->tick > m_nPreviousTick &&
  1565. entry2->tick <= targettick &&
  1566. entry2->info.u[ nSlot ].flags & FDEMO_NOINTERP )
  1567. {
  1568. savedI = i;
  1569. }
  1570. if ( i+2 == c )
  1571. break;
  1572. i++;
  1573. entry1 = &list[ i ];
  1574. entry2 = &list[ i+1 ];
  1575. }
  1576. }
  1577. static ConVar demo_legacy_rollback( "demo_legacy_rollback", "1", 0, "Use legacy view interpolation rollback amount in demo playback." );
  1578. //-----------------------------------------------------------------------------
  1579. // Purpose:
  1580. //-----------------------------------------------------------------------------
  1581. void CDemoPlayer::InterpolateViewpoint( void )
  1582. {
  1583. if ( !IsPlayingBack() )
  1584. return;
  1585. democmdinfo_t outinfo;
  1586. outinfo.Reset();
  1587. FOR_EACH_VALID_SPLITSCREEN_PLAYER( hh )
  1588. {
  1589. ACTIVE_SPLITSCREEN_PLAYER_GUARD( hh );
  1590. bool bHasValidData =
  1591. m_LastCmdInfo.u[ hh ].viewOrigin != vec3_origin ||
  1592. m_LastCmdInfo.u[ hh ].viewAngles != vec3_angle ||
  1593. m_LastCmdInfo.u[ hh ].localViewAngles != vec3_angle ||
  1594. m_LastCmdInfo.u[ hh ].flags != 0;
  1595. int nTargetTick = GetPlaybackTick();
  1596. // Player view needs to be one tick interval in the past like the client DLL entities
  1597. if ( GetBaseLocalClient().m_nMaxClients == 1 )
  1598. {
  1599. if ( demo_legacy_rollback.GetBool() )
  1600. {
  1601. nTargetTick -= TIME_TO_TICKS( GetBaseLocalClient().GetClientInterpAmount() ) + 1;
  1602. }
  1603. else
  1604. {
  1605. nTargetTick -= 1;
  1606. }
  1607. }
  1608. float vel = 0.0f;
  1609. float angVel = 0.0f;
  1610. if ( m_bInterpolateView && demo_interpolateview.GetBool() && bHasValidData )
  1611. {
  1612. DemoCommandQueue prev, next;
  1613. float frac = 0.0f;
  1614. prev.info = m_LastCmdInfo;
  1615. prev.tick = -1;
  1616. next.info = m_LastCmdInfo;
  1617. next.tick = -1;
  1618. // Determine current time slice
  1619. InterpolateDemoCommand( hh, nTargetTick, prev, next );
  1620. float dt = TICKS_TO_TIME(next.tick-prev.tick);
  1621. frac = (TICKS_TO_TIME(nTargetTick-prev.tick)+GetBaseLocalClient().m_tickRemainder)/dt;
  1622. frac = clamp( frac, 0.0f, 1.0f );
  1623. // Now interpolate
  1624. Vector delta;
  1625. Vector startorigin = prev.info.u[ hh ].GetViewOrigin();
  1626. Vector destorigin = next.info.u[ hh ].GetViewOrigin();
  1627. // check for teleporting - since there can be multiple cmd packets between a game frame,
  1628. // we need to check from the last actually ran command to see if there was a teleport
  1629. VectorSubtract( destorigin, m_LastCmdInfo.u[ hh ].GetViewOrigin(), delta );
  1630. float distmoved = delta.Length();
  1631. if ( dt > 0.0f )
  1632. {
  1633. vel = distmoved / dt;
  1634. }
  1635. if ( dt > 0.0f )
  1636. {
  1637. QAngle startang = prev.info.u[ hh ].GetLocalViewAngles();
  1638. QAngle destang = next.info.u[ hh ].GetLocalViewAngles();
  1639. for ( int i = 0; i < 3; ++i )
  1640. {
  1641. float dAng = AngleNormalizePositive( destang[ i ] ) - AngleNormalizePositive( startang[ i ] );
  1642. dAng = AngleNormalize( dAng );
  1643. float aVel = fabs( dAng ) / dt;
  1644. if ( aVel > angVel )
  1645. {
  1646. angVel = aVel;
  1647. }
  1648. }
  1649. }
  1650. // FIXME: This should be velocity based maybe?
  1651. if ( (vel > demo_interplimit.GetFloat()) ||
  1652. (angVel > demo_avellimit.GetFloat() ) ||
  1653. m_bResetInterpolation )
  1654. {
  1655. m_bResetInterpolation = false;
  1656. // it's a teleport, just let it happen naturally next frame
  1657. // setting frac to 1.0 (like it was previously) would just mean that we
  1658. // are teleporting a frame ahead of when we should
  1659. outinfo.u[ hh ].viewOrigin = m_LastCmdInfo.u[ hh ].GetViewOrigin();
  1660. outinfo.u[ hh ].viewAngles = m_LastCmdInfo.u[ hh ].GetViewAngles();
  1661. outinfo.u[ hh ].localViewAngles = m_LastCmdInfo.u[ hh ].GetLocalViewAngles();
  1662. }
  1663. else
  1664. {
  1665. outinfo.u[ hh ].viewOrigin = startorigin + frac * ( destorigin - startorigin );
  1666. Quaternion src, dest;
  1667. Quaternion result;
  1668. AngleQuaternion( prev.info.u[ hh ].GetViewAngles(), src );
  1669. AngleQuaternion( next.info.u[ hh ].GetViewAngles(), dest );
  1670. QuaternionSlerp( src, dest, frac, result );
  1671. QuaternionAngles( result, outinfo.u[ hh ].viewAngles );
  1672. AngleQuaternion( prev.info.u[ hh ].GetLocalViewAngles(), src );
  1673. AngleQuaternion( next.info.u[ hh ].GetLocalViewAngles(), dest );
  1674. QuaternionSlerp( src, dest, frac, result );
  1675. QuaternionAngles( result, outinfo.u[ hh ].localViewAngles );
  1676. }
  1677. }
  1678. else if ( bHasValidData )
  1679. {
  1680. // don't interpolate, just copy values
  1681. outinfo.u[ hh ].viewOrigin = m_LastCmdInfo.u[ hh ].GetViewOrigin();
  1682. outinfo.u[ hh ].viewAngles = m_LastCmdInfo.u[ hh ].GetViewAngles();
  1683. outinfo.u[ hh ].localViewAngles = m_LastCmdInfo.u[ hh ].GetLocalViewAngles();
  1684. }
  1685. m_nPreviousTick = nTargetTick;
  1686. // let any demo system override view ( drive, editor, smoother etc)
  1687. bHasValidData |= OverrideView( outinfo );
  1688. if ( !bHasValidData )
  1689. continue; // no validate data & no override, exit
  1690. g_pClientSidePrediction->SetViewOrigin( outinfo.u[ hh ].viewOrigin );
  1691. g_pClientSidePrediction->SetViewAngles( outinfo.u[ hh ].viewAngles );
  1692. g_pClientSidePrediction->SetLocalViewAngles( outinfo.u[ hh ].localViewAngles );
  1693. #ifndef DEDICATED
  1694. VectorCopy( outinfo.u[ hh ].viewAngles, GetLocalClient().viewangles );
  1695. #endif
  1696. }
  1697. }
  1698. //-----------------------------------------------------------------------------
  1699. // Purpose:
  1700. // Output : Returns true on success, false on failure.
  1701. //-----------------------------------------------------------------------------
  1702. bool CDemoPlayer::IsPlayingTimeDemo( void )const
  1703. {
  1704. return m_bTimeDemo && m_bPlayingBack;
  1705. }
  1706. //-----------------------------------------------------------------------------
  1707. // Purpose:
  1708. // Output : Returns true on success, false on failure.
  1709. //-----------------------------------------------------------------------------
  1710. bool CDemoPlayer::IsPlayingBack( void )const
  1711. {
  1712. return m_bPlayingBack;
  1713. }
  1714. CDemoPlayer::CDemoPlayer()
  1715. {
  1716. m_flAutoResumeTime = 0.0f;
  1717. m_flPlaybackRateModifier = 1.0f;
  1718. m_bTimeDemo = false;
  1719. m_nTimeDemoStartFrame = -1;
  1720. m_flTimeDemoStartTime = 0.0f;
  1721. m_flTotalFPSVariability = 0.0f;
  1722. m_nTimeDemoCurrentFrame = -1;
  1723. m_nPacketTick = 0; // pulling together with broadcast
  1724. m_bPlayingBack = false;
  1725. m_bPlaybackPaused = false;
  1726. m_nSkipToTick = -1;
  1727. m_nSnapshotTick = 0;
  1728. m_SnapshotFilename[0] = 0;
  1729. m_bResetInterpolation = false;
  1730. m_nPreviousTick = 0;
  1731. m_pPlaybackParameters = NULL;
  1732. m_bPacketReadSuspended = false;
  1733. m_nRestartFilePos = -1;
  1734. m_pImportantEventData = NULL;
  1735. m_nTickToPauseOn = -1;
  1736. m_bSavedInterpolateState = true;
  1737. m_highlightSteamID.SetFromUint64( 0 );
  1738. m_nHighlightPlayerIndex = -1;
  1739. m_bDoHighlightScan = false;
  1740. m_nCurrentHighlight = -1;
  1741. m_bLowlightsMode = false;
  1742. m_bScanMode = false;
  1743. m_szScanMode[0] = 0;
  1744. }
  1745. CDemoPlayer::~CDemoPlayer()
  1746. {
  1747. StopPlayback();
  1748. if ( g_ClientDLL )
  1749. {
  1750. g_ClientDLL->OnDemoPlaybackStop();
  1751. }
  1752. }
  1753. CDemoPlaybackParameters_t const * CDemoPlayer::GetDemoPlaybackParameters()
  1754. {
  1755. return m_bPlayingBack ? m_pPlaybackParameters : NULL;
  1756. }
  1757. //-----------------------------------------------------------------------------
  1758. // Purpose: Start's demo playback
  1759. // Input : *name -
  1760. //-----------------------------------------------------------------------------
  1761. bool CDemoPlayer::StartPlayback( const char *filename, bool bAsTimeDemo, CDemoPlaybackParameters_t const *pPlaybackParameters, int nStartingTick )
  1762. {
  1763. m_pPlaybackParameters = pPlaybackParameters;
  1764. m_bPacketReadSuspended = false;
  1765. SCR_BeginLoadingPlaque();
  1766. // Disconnect from server or stop running one
  1767. int oldn = GetBaseLocalClient().demonum;
  1768. GetBaseLocalClient().demonum = -1;
  1769. Host_Disconnect( false );
  1770. GetBaseLocalClient().demonum = oldn;
  1771. m_bPlayingBack = true;
  1772. if ( !m_DemoFile.Open( filename, true ) )
  1773. {
  1774. m_bPlayingBack = false;
  1775. GetBaseLocalClient().demonum = -1; // stop demo loop
  1776. return false;
  1777. }
  1778. // Read in the m_DemoHeader
  1779. demoheader_t *dh = m_DemoFile.ReadDemoHeader( pPlaybackParameters );
  1780. if ( !dh )
  1781. {
  1782. m_bPlayingBack = false;
  1783. ConMsg( "Failed to read demo header.\n" );
  1784. m_DemoFile.Close();
  1785. GetBaseLocalClient().demonum = -1;
  1786. return false;
  1787. }
  1788. if ( dh->playback_frames == 0 && dh->playback_ticks == 0 && dh->playback_time == 0 )
  1789. {
  1790. ConMsg( "Demo %s is incomplete\n", filename );
  1791. }
  1792. else
  1793. {
  1794. ConMsg( "Playing demo from %s.\n", filename );
  1795. }
  1796. FOR_EACH_VEC( m_ImportantTicks, i )
  1797. {
  1798. if ( m_ImportantTicks[ i ].pKeys )
  1799. {
  1800. m_ImportantTicks[ i ].pKeys->deleteThis();
  1801. }
  1802. }
  1803. m_ImportantTicks.RemoveAll();
  1804. m_ImportantGameEvents.RemoveAll();
  1805. m_highlights.RemoveAll();
  1806. m_nCurrentHighlight = -1;
  1807. // Now read in the directory structure.
  1808. m_nSkipToTick = nStartingTick; // reset skip-to-tick, otherwise it remains stale from old skipping
  1809. if ( nStartingTick != -1 )
  1810. {
  1811. m_nSkipToTick |= SKIP_TO_TICK_FLAG;
  1812. }
  1813. GetBaseLocalClient().m_nSignonState= SIGNONSTATE_CONNECTED;
  1814. ResyncDemoClock();
  1815. // create a fake channel with a NULL address (no encryption keys in demos)
  1816. GetBaseLocalClient().m_NetChannel = NET_CreateNetChannel( NS_CLIENT, NULL, "DEMO", &GetBaseLocalClient(), NULL, false );
  1817. if ( !GetBaseLocalClient().m_NetChannel )
  1818. {
  1819. ConMsg ("CDemo::Play: failed to create demo net channel\n" );
  1820. m_DemoFile.Close();
  1821. GetBaseLocalClient().demonum = -1; // stop demo loop
  1822. Host_Disconnect(true);
  1823. return false;
  1824. }
  1825. GetBaseLocalClient().m_NetChannel->SetTimeout( -1.0f ); // never timeout
  1826. V_memset( &m_DemoPacket, 0, sizeof(m_DemoPacket) );
  1827. // setup demo packet data buffer
  1828. m_DemoPacket.data = new unsigned char[ NET_MAX_PAYLOAD ];
  1829. m_DemoPacket.from.SetAddrType( NSAT_NETADR );
  1830. m_DemoPacket.from.m_adr.SetType( NA_LOOPBACK );
  1831. GetBaseLocalClient().chokedcommands = 0;
  1832. GetBaseLocalClient().lastoutgoingcommand = -1;
  1833. GetBaseLocalClient().m_flNextCmdTime = net_time;
  1834. m_bTimeDemo = bAsTimeDemo;
  1835. m_nTimeDemoCurrentFrame = -1;
  1836. m_nTimeDemoStartFrame = -1;
  1837. m_nPacketTick = 0;
  1838. if ( m_bTimeDemo )
  1839. {
  1840. SeedRandomNumberGenerator( true );
  1841. }
  1842. demoaction->StartPlaying( filename );
  1843. // m_bFastForwarding = false;
  1844. m_flAutoResumeTime = 0.0f;
  1845. m_flPlaybackRateModifier = 1.0f;
  1846. demoplayer = this;
  1847. scr_demo_override_fov = 0.0f;
  1848. return true;
  1849. }
  1850. bool CDemoPlayer::ScanDemo( const char *filename, const char* pszMode )
  1851. {
  1852. m_bScanMode = true;
  1853. V_strcpy_safe( m_szScanMode, pszMode );
  1854. return StartPlayback( filename, false, NULL );
  1855. }
  1856. void CDemoPlayer::RestartPlayback( void )
  1857. {
  1858. if ( m_nRestartFilePos != -1 )
  1859. {
  1860. m_DemoFile.SeekTo( m_nRestartFilePos, true );
  1861. ResyncDemoClock();
  1862. GetBaseLocalClient().DeleteClientFrames( -1 );
  1863. GetBaseLocalClient().SetFrameTime( 0 );
  1864. GetBaseLocalClient().chokedcommands = 0;
  1865. GetBaseLocalClient().lastoutgoingcommand = -1;
  1866. GetBaseLocalClient().m_flNextCmdTime = net_time;
  1867. GetBaseLocalClient().events.RemoveAll();
  1868. #ifndef DEDICATED
  1869. S_StopAllSounds( true );
  1870. g_ClientDLL->OnDemoPlaybackRestart();
  1871. #endif
  1872. }
  1873. }
  1874. //-----------------------------------------------------------------------------
  1875. // Purpose:
  1876. // Input : flCurTime -
  1877. //-----------------------------------------------------------------------------
  1878. void CDemoPlayer::MarkFrame( float flFPSVariability )
  1879. {
  1880. m_flTotalFPSVariability += flFPSVariability;
  1881. }
  1882. void ComputeTimedemoResultsFilename( CFmtStr &fileName, CFmtStr &dateString )
  1883. {
  1884. // Write the results to a CSV file.
  1885. // If specified on the command-line, write to a specific benchmark path.
  1886. // Include the GPU name and host computer name for convenient sorting in Explorer.
  1887. // Compute a date+time string
  1888. struct tm time;
  1889. Plat_GetLocalTime( &time );
  1890. dateString.sprintf( "%04d_%02d_%02d__%02d_%02d_%02d", time.tm_year+1900, time.tm_mon+1, time.tm_mday, time.tm_hour, time.tm_min, time.tm_sec );
  1891. // Compute prettified GPU name
  1892. MaterialAdapterInfo_t info;
  1893. materials->GetDisplayAdapterInfo( materials->GetCurrentAdapter(), info );
  1894. CUtlString gpuName( info.m_pDriverName );
  1895. const char *pVendors[] = { "nvidia", "ati" };
  1896. for ( int i = 0; i < ARRAYSIZE( pVendors ); i++ )
  1897. {
  1898. const char *pVendor = V_stristr( gpuName.Get(), pVendors[i] );
  1899. if ( pVendor )
  1900. {
  1901. int nVendorLen = V_strlen( pVendors[i] );
  1902. if ( pVendor[nVendorLen] == ' ' ) nVendorLen++;
  1903. CUtlString tail = pVendor + nVendorLen;
  1904. gpuName.SetLength( pVendor - gpuName.Get() );
  1905. gpuName += tail;
  1906. break;
  1907. }
  1908. }
  1909. // Now replace any non-alphanumerics with an underscore
  1910. char *pGPUName = gpuName.Get();
  1911. for ( int i = 0; i < gpuName.Length(); i++ )
  1912. {
  1913. if ( !V_isalnum( pGPUName[i] ) )
  1914. pGPUName[i] = '_';
  1915. }
  1916. // Compute the local computer's name
  1917. char host[256] = "";
  1918. DWORD length = sizeof( host ) - 1;
  1919. #if !IsGameConsole()
  1920. if ( gethostname( host, length ) < 0 )
  1921. #endif
  1922. {
  1923. // Use dateString as a fallback, if we can't get the host name
  1924. V_strncpy( host, dateString.Access(), length );
  1925. }
  1926. host[ length ] = '\0';
  1927. // Get the destination path (default to the gamedir)
  1928. CUtlString benchmarkPath = CommandLine()->ParmValue( "-benchmark_path" );
  1929. if ( benchmarkPath.Length() && !IsOSX() ) // Don't bother on Mac, we can't write to an smb share trivially
  1930. {
  1931. benchmarkPath.StripTrailingSlash();
  1932. V_FixSlashes( benchmarkPath.Get() );
  1933. }
  1934. else
  1935. {
  1936. benchmarkPath = ".";
  1937. }
  1938. // Slap it all together
  1939. fileName.sprintf( "%s%sSourceBench_%s_%s.csv", benchmarkPath.Get(), CORRECT_PATH_SEPARATOR_S, gpuName.Get(), host );
  1940. }
  1941. void CDemoPlayer::WriteTimeDemoResults( void )
  1942. {
  1943. int frames = MAX( 1, ( (host_framecount - m_nTimeDemoStartFrame) - 1 ) );
  1944. float time = MAX( 1, ( Sys_FloatTime() - m_flTimeDemoStartTime ) );
  1945. float flVariability = m_flTotalFPSVariability / (float)frames;
  1946. 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 );
  1947. // Open the file (write the CSV to a benchmark path, if specified on the command-line, and name after the GPU and host computer)
  1948. CFmtStr fileName, dateString;
  1949. ComputeTimedemoResultsFilename( fileName, dateString );
  1950. bool bEmptyFile = !g_pFileSystem->FileExists( fileName.Access() );
  1951. FileHandle_t fileHandle = g_pFileSystem->Open( fileName.Access(), "a+" );
  1952. if ( fileHandle == FILESYSTEM_INVALID_HANDLE )
  1953. {
  1954. Warning( "DEMO: Failed to open %s!\n", fileName.Access() );
  1955. return;
  1956. }
  1957. bEmptyFile = !g_pFileSystem->Size( fileHandle );
  1958. // Write the header line when starting a new file
  1959. if( bEmptyFile )
  1960. {
  1961. g_pFileSystem->FPrintf( fileHandle, "Benchmark Results\n\n" );
  1962. g_pFileSystem->FPrintf( fileHandle, "demofile," );
  1963. g_pFileSystem->FPrintf( fileHandle, "frame data csv," );
  1964. g_pFileSystem->FPrintf( fileHandle, "fps," );
  1965. g_pFileSystem->FPrintf( fileHandle, "fps variability," );
  1966. g_pFileSystem->FPrintf( fileHandle, "total sec," );
  1967. g_pFileSystem->FPrintf( fileHandle, "avg ms," );
  1968. for ( int lp = 0; lp < PERF_STATS_SLOT_MAX; ++lp )
  1969. {
  1970. g_pFileSystem->FPrintf( fileHandle, "%s (avg ms),", g_PerfStats.m_Slots[lp].m_pszName );
  1971. }
  1972. g_pFileSystem->FPrintf( fileHandle, "width," );
  1973. g_pFileSystem->FPrintf( fileHandle, "height," );
  1974. g_pFileSystem->FPrintf( fileHandle, "msaa," );
  1975. g_pFileSystem->FPrintf( fileHandle, "aniso," );
  1976. g_pFileSystem->FPrintf( fileHandle, "picmip," );
  1977. g_pFileSystem->FPrintf( fileHandle, "numframes," );
  1978. g_pFileSystem->FPrintf( fileHandle, "dxlevel," );
  1979. g_pFileSystem->FPrintf( fileHandle, "backbuffer," );
  1980. g_pFileSystem->FPrintf( fileHandle, "cmdline," );
  1981. g_pFileSystem->FPrintf( fileHandle, "driver," );
  1982. g_pFileSystem->FPrintf( fileHandle, "vendor id," );
  1983. g_pFileSystem->FPrintf( fileHandle, "device id," );
  1984. //g_pFileSystem->FPrintf( fileHandle, "subsys id," );
  1985. //g_pFileSystem->FPrintf( fileHandle, "revision," );
  1986. //g_pFileSystem->FPrintf( fileHandle, "shaderdll," );
  1987. g_pFileSystem->FPrintf( fileHandle, "sound," );
  1988. g_pFileSystem->FPrintf( fileHandle, "vsync," );
  1989. g_pFileSystem->FPrintf( fileHandle, "gpu_level," );
  1990. g_pFileSystem->FPrintf( fileHandle, "cpu_level," );
  1991. g_pFileSystem->FPrintf( fileHandle, "date," );
  1992. g_pFileSystem->FPrintf( fileHandle, "csm enabled," );
  1993. g_pFileSystem->FPrintf( fileHandle, "csm quality," );
  1994. g_pFileSystem->FPrintf( fileHandle, "fxaa," );
  1995. g_pFileSystem->FPrintf( fileHandle, "motionblur," );
  1996. g_pFileSystem->FPrintf( fileHandle, "\n" );
  1997. }
  1998. // Append a new line of data
  1999. static ConVarRef gpu_level( "gpu_level" );
  2000. static ConVarRef cpu_level( "cpu_level" );
  2001. static ConVarRef mat_vsync( "mat_vsync" );
  2002. static ConVarRef mat_antialias( "mat_antialias" );
  2003. static ConVarRef mat_forceaniso( "mat_forceaniso" );
  2004. static ConVarRef mat_picmip( "mat_picmip" );
  2005. static ConVarRef cl_csm_enabled( "cl_csm_enabled" );
  2006. static ConVarRef csm_quality_level( "csm_quality_level" );
  2007. static ConVarRef mat_software_aa_strength( "mat_software_aa_strength" );
  2008. static ConVarRef mat_motion_blur_enabled( "mat_motion_blur_enabled" );
  2009. int width, height;
  2010. MaterialAdapterInfo_t info;
  2011. CMatRenderContextPtr pRenderContext( materials );
  2012. pRenderContext->GetWindowSize( width, height );
  2013. ImageFormat backBufferFormat = materials->GetBackBufferFormat();
  2014. materials->GetDisplayAdapterInfo( materials->GetCurrentAdapter(), info );
  2015. g_pFileSystem->Seek( fileHandle, 0, FILESYSTEM_SEEK_TAIL );
  2016. g_pFileSystem->FPrintf( fileHandle, "%s,", m_DemoFile.m_szFileName );
  2017. g_pFileSystem->FPrintf( fileHandle, "%s,", g_pStatsFile );
  2018. g_pFileSystem->FPrintf( fileHandle, "%5.1f,", frames/time );
  2019. g_pFileSystem->FPrintf( fileHandle, "%5.1f,", flVariability );
  2020. g_pFileSystem->FPrintf( fileHandle, "%5.1f,", time );
  2021. g_pFileSystem->FPrintf( fileHandle, "%5.3f,", 1000*time/frames );
  2022. for ( int lp = 0; lp < PERF_STATS_SLOT_MAX; ++lp )
  2023. {
  2024. g_pFileSystem->FPrintf( fileHandle, "%6.3f,", g_PerfStats.m_Slots[lp].m_AccTotalTime.GetMillisecondsF() / g_PerfStats.m_nFrames );
  2025. }
  2026. g_pFileSystem->FPrintf( fileHandle, "%i,", width );
  2027. g_pFileSystem->FPrintf( fileHandle, "%i,", height );
  2028. g_pFileSystem->FPrintf( fileHandle, "%i,", MAX( 1, mat_antialias.GetInt() ) );
  2029. g_pFileSystem->FPrintf( fileHandle, "%i,", mat_forceaniso.GetInt() );
  2030. g_pFileSystem->FPrintf( fileHandle, "%i,", mat_picmip.GetInt() );
  2031. g_pFileSystem->FPrintf( fileHandle, "%i,", frames );
  2032. g_pFileSystem->FPrintf( fileHandle, "%s,", COM_DXLevelToString( g_pMaterialSystemHardwareConfig->GetDXSupportLevel() ) );
  2033. g_pFileSystem->FPrintf( fileHandle, "%s,", ImageLoader::GetName( backBufferFormat ) );
  2034. g_pFileSystem->FPrintf( fileHandle, "%s,", CommandLine()->GetCmdLine() );
  2035. g_pFileSystem->FPrintf( fileHandle, "%s,", info.m_pDriverName );
  2036. g_pFileSystem->FPrintf( fileHandle, "0x%x,", info.m_VendorID );
  2037. g_pFileSystem->FPrintf( fileHandle, "0x%x,", info.m_DeviceID );
  2038. //g_pFileSystem->FPrintf( fileHandle, "0x%x,", info.m_SubSysID );
  2039. //g_pFileSystem->FPrintf( fileHandle, "0x%x,", info.m_Revision );
  2040. //g_pFileSystem->FPrintf( fileHandle, "%s,", g_pMaterialSystemHardwareConfig->GetShaderDLLName() );
  2041. g_pFileSystem->FPrintf( fileHandle, "%s,", CommandLine()->CheckParm( "-nosound" ) ? "disabled" : "enabled" );
  2042. g_pFileSystem->FPrintf( fileHandle, "%s,", CommandLine()->CheckParm( "-mat_vsync" ) || mat_vsync.GetBool() ? "enabled" : "disabled" );
  2043. g_pFileSystem->FPrintf( fileHandle, "%d,", gpu_level.GetInt() );
  2044. g_pFileSystem->FPrintf( fileHandle, "%d,", cpu_level.GetInt() );
  2045. g_pFileSystem->FPrintf( fileHandle, "%s,", dateString.Access() );
  2046. g_pFileSystem->FPrintf( fileHandle, "%i,", cl_csm_enabled.GetInt() );
  2047. g_pFileSystem->FPrintf( fileHandle, "%i,", csm_quality_level.GetInt() );
  2048. g_pFileSystem->FPrintf( fileHandle, "%i,", mat_software_aa_strength.GetInt() );
  2049. g_pFileSystem->FPrintf( fileHandle, "%i,", mat_motion_blur_enabled.GetInt() );
  2050. g_pFileSystem->FPrintf( fileHandle, "\n" );
  2051. g_pFileSystem->Close( fileHandle );
  2052. }
  2053. void CDemoPlayer::PausePlayback( float seconds )
  2054. {
  2055. m_bPlaybackPaused = true;
  2056. if ( seconds > 0.0f )
  2057. {
  2058. // Use true clock since everything else is frozen
  2059. m_flAutoResumeTime = Sys_FloatTime() + seconds;
  2060. }
  2061. else
  2062. {
  2063. m_flAutoResumeTime = 0.0f;
  2064. }
  2065. }
  2066. void CDemoPlayer::ResumePlayback()
  2067. {
  2068. m_bPlaybackPaused = false;
  2069. m_flAutoResumeTime = 0.0f;
  2070. }
  2071. bool CDemoPlayer::CheckPausedPlayback()
  2072. {
  2073. if ( m_bPacketReadSuspended )
  2074. return true; // When packet reading is suspended it trumps all other states
  2075. if ( demo_pauseatservertick.GetInt() > 0 )
  2076. {
  2077. if ( GetBaseLocalClient().GetServerTickCount() >= demo_pauseatservertick.GetInt() )
  2078. {
  2079. PausePlayback( -1 );
  2080. m_nSkipToTick = -1;
  2081. demo_pauseatservertick.SetValue( 0 );
  2082. Msg( "Demo paused at server tick %i\n", GetBaseLocalClient().GetServerTickCount() );
  2083. }
  2084. }
  2085. if ( IsSkipping() )
  2086. {
  2087. if ( ( m_nSkipToTick > GetPlaybackTick() ) ||
  2088. ( ( m_nSkipToTick & SKIP_TO_TICK_FLAG ) == SKIP_TO_TICK_FLAG ) )
  2089. {
  2090. // we are skipping
  2091. return false;
  2092. }
  2093. else
  2094. {
  2095. // we can't skip back (or finished skipping), so disable skipping
  2096. m_nSkipToTick = -1;
  2097. }
  2098. }
  2099. if ( !IsPlaybackPaused() )
  2100. return false;
  2101. if ( m_bPlaybackPaused )
  2102. {
  2103. if ( (m_flAutoResumeTime > 0.0f) &&
  2104. (Sys_FloatTime() >= m_flAutoResumeTime) )
  2105. {
  2106. // it's time to unpause replay
  2107. ResumePlayback();
  2108. }
  2109. }
  2110. return m_bPlaybackPaused;
  2111. }
  2112. bool CDemoPlayer::IsPlaybackPaused()const
  2113. {
  2114. if ( !IsPlayingBack() )
  2115. return false;
  2116. // never pause while reading signon data
  2117. if ( m_nTimeDemoCurrentFrame < 0 )
  2118. return false;
  2119. // If skipping then do not pretend paused
  2120. if ( IsSkipping() )
  2121. return false;
  2122. return m_bPlaybackPaused;
  2123. }
  2124. int CDemoPlayer::GetPlaybackStartTick( void )
  2125. {
  2126. return m_nStartTick;
  2127. }
  2128. int CDemoPlayer::GetPlaybackTick( void )
  2129. {
  2130. return host_tickcount - m_nStartTick;
  2131. }
  2132. int CDemoPlayer::GetPlaybackDeltaTick( void )
  2133. {
  2134. return host_tickcount - m_nStartTick;
  2135. }
  2136. int CDemoPlayer::GetPacketTick()
  2137. {
  2138. return m_nPacketTick;
  2139. }
  2140. void CDemoPlayer::ResyncDemoClock()
  2141. {
  2142. m_nStartTick = host_tickcount;
  2143. m_nPreviousTick = m_nStartTick;
  2144. }
  2145. float CDemoPlayer::GetPlaybackTimeScale()
  2146. {
  2147. return m_flPlaybackRateModifier;
  2148. }
  2149. void CDemoPlayer::SetPlaybackTimeScale(float timescale)
  2150. {
  2151. m_flPlaybackRateModifier = timescale;
  2152. }
  2153. void CDemoPlayer::SetBenchframe( int tick, const char *filename )
  2154. {
  2155. m_nSnapshotTick = tick;
  2156. if ( filename )
  2157. {
  2158. V_strcpy_safe( m_SnapshotFilename, filename );
  2159. }
  2160. }
  2161. void CDemoPlayer::SetImportantEventData( const KeyValues *pData )
  2162. {
  2163. m_pImportantEventData = pData->MakeCopy();
  2164. }
  2165. void CDemoPlayer::GetImportantGameEventIDs()
  2166. {
  2167. if ( m_ImportantGameEvents.Count() == 0 )
  2168. {
  2169. KeyValues *pCurrentEvent = m_pImportantEventData->GetFirstSubKey();
  2170. while( pCurrentEvent != NULL )
  2171. {
  2172. const char *pEventName = pCurrentEvent->GetName();
  2173. CGameEventDescriptor *descriptor = g_GameEventManager.GetEventDescriptor( pEventName );
  2174. if ( descriptor )
  2175. {
  2176. DemoImportantGameEvent_t event;
  2177. event.nEventID = descriptor->eventid;
  2178. event.pszEventName = pEventName;
  2179. event.pszUIName = pCurrentEvent->GetString( "uiname" );
  2180. event.flSeekTimeBefore = pCurrentEvent->GetFloat( "seek_time_before", 0.0f );
  2181. event.flSeekForwardOffset = pCurrentEvent->GetFloat( "seek_back_offset", 0.0f );
  2182. event.flSeekBackwardOffset = pCurrentEvent->GetFloat( "seek_forward_offset", 0.0f );
  2183. event.bScanOnly = pCurrentEvent->GetBool( "scanonly" );
  2184. m_ImportantGameEvents.AddToTail( event );
  2185. }
  2186. else
  2187. {
  2188. Msg( "GetImportantGameEventIDs: Couldn't get descriptor for %s\n", pEventName );
  2189. }
  2190. pCurrentEvent = pCurrentEvent->GetNextKey();
  2191. }
  2192. }
  2193. }
  2194. void CDemoPlayer::SetHighlightXuid( uint64 xuid, bool bLowlights )
  2195. {
  2196. m_highlightSteamID.SetFromUint64( xuid );
  2197. if ( xuid != 0 )
  2198. {
  2199. m_bDoHighlightScan = true;
  2200. m_bLowlightsMode = bLowlights;
  2201. }
  2202. g_ClientDLL->SetDemoPlaybackHighlightXuid( xuid, bLowlights );
  2203. }
  2204. void ParseEventKeys( CSVCMsg_GameEvent_t *msg, CGameEventDescriptor *pDescriptor, const char *pszEventName, KeyValues **ppKeys )
  2205. {
  2206. int nKeyCount = msg->keys().size();
  2207. if ( nKeyCount > 0 )
  2208. {
  2209. // build proper key values from descriptor plus message keys
  2210. *ppKeys = new KeyValues( pszEventName );
  2211. if ( *ppKeys )
  2212. {
  2213. KeyValues *pDescriptorKey = pDescriptor->keys->GetFirstSubKey();
  2214. for( int nKey = 0; nKey < nKeyCount; nKey++ )
  2215. {
  2216. const CSVCMsg_GameEvent::key_t& KeyValue = msg->keys( nKey );
  2217. if( KeyValue.has_val_string() )
  2218. {
  2219. (*ppKeys)->SetString( pDescriptorKey->GetName(), KeyValue.val_string().c_str() );
  2220. }
  2221. else if( KeyValue.has_val_float() )
  2222. {
  2223. (*ppKeys)->SetFloat( pDescriptorKey->GetName(), KeyValue.val_float() );
  2224. }
  2225. else if( KeyValue.has_val_long() )
  2226. {
  2227. (*ppKeys)->SetInt( pDescriptorKey->GetName(), KeyValue.val_long() );
  2228. }
  2229. else if( KeyValue.has_val_short() )
  2230. {
  2231. (*ppKeys)->SetInt( pDescriptorKey->GetName(), KeyValue.val_short() );
  2232. }
  2233. else if( KeyValue.has_val_byte() )
  2234. {
  2235. (*ppKeys)->SetInt( pDescriptorKey->GetName(), KeyValue.val_byte() );
  2236. }
  2237. else if( KeyValue.has_val_bool() )
  2238. {
  2239. (*ppKeys)->SetBool( pDescriptorKey->GetName(), KeyValue.val_bool() );
  2240. }
  2241. else if( KeyValue.has_val_uint64() )
  2242. {
  2243. (*ppKeys)->SetUint64( pDescriptorKey->GetName(), KeyValue.val_uint64() );
  2244. }
  2245. pDescriptorKey = pDescriptorKey->GetNextKey();
  2246. }
  2247. }
  2248. }
  2249. else
  2250. {
  2251. (*ppKeys) = NULL;
  2252. }
  2253. }
  2254. void CDemoPlayer::ScanForImportantTicks()
  2255. {
  2256. if ( m_ImportantTicks.Count() > 0 || m_ImportantGameEvents.Count() == 0 )
  2257. return;
  2258. s_demoUserInfo.RemoveAll();
  2259. // setup a string table for use while scanning
  2260. CNetworkStringTableContainer demoScanStringTables;
  2261. demoScanStringTables.AllowCreation( true );
  2262. int numTables = networkStringTableContainerClient->GetNumTables();
  2263. for ( int i =0; i<numTables; i++)
  2264. {
  2265. // iterate through server tables
  2266. CNetworkStringTable *serverTable =
  2267. (CNetworkStringTable*)networkStringTableContainerClient->GetTable( i );
  2268. if ( !serverTable )
  2269. continue;
  2270. // get matching client table
  2271. CNetworkStringTable *demoTable =
  2272. (CNetworkStringTable*)demoScanStringTables.CreateStringTable(
  2273. serverTable->GetTableName(),
  2274. serverTable->GetMaxStrings(),
  2275. serverTable->GetUserDataSize(),
  2276. serverTable->GetUserDataSizeBits(),
  2277. serverTable->IsUsingDictionary() ? NSF_DICTIONARY_ENABLED : NSF_NONE
  2278. );
  2279. if ( !demoTable )
  2280. {
  2281. DevMsg("CDemoPlayer::ScanForImportantTicks: failed to create table \"%s\".\n ", serverTable->GetTableName() );
  2282. continue;
  2283. }
  2284. if ( V_strcasecmp( demoTable->GetTableName(), USER_INFO_TABLENAME ) == 0 )
  2285. {
  2286. demoTable->SetStringChangedCallback( NULL, Callback_DemoScanUserInfoChanged );
  2287. }
  2288. // make demo scan table an exact copy of server table
  2289. demoTable->CopyStringTable( serverTable );
  2290. }
  2291. demoScanStringTables.AllowCreation( false );
  2292. m_nHighlightPlayerIndex = GetPlayerIndex( m_highlightSteamID );
  2293. democmdinfo_t info;
  2294. int dummy;
  2295. char buf[ NET_MAX_PAYLOAD ];
  2296. long starting_position = m_DemoFile.GetCurPos( true );
  2297. m_DemoFile.SeekTo( 0, true );
  2298. // Read in the m_DemoHeader
  2299. demoheader_t *dh = m_DemoFile.ReadDemoHeader( m_pPlaybackParameters );
  2300. if ( !dh )
  2301. {
  2302. ConMsg( "Failed to read demo header while scanning for important ticks.\n" );
  2303. m_DemoFile.SeekTo( starting_position, true );
  2304. return;
  2305. }
  2306. int previousTick = 0, nDemoPackets = 0;
  2307. bool demofinished = false;
  2308. while ( !demofinished )
  2309. {
  2310. int tick = 0;
  2311. byte cmd;
  2312. bool swallowmessages = true;
  2313. do
  2314. {
  2315. int nPlayerSlot = 0;
  2316. m_DemoFile.ReadCmdHeader( cmd, tick, nPlayerSlot );
  2317. // COMMAND HANDLERS
  2318. switch ( cmd )
  2319. {
  2320. case dem_synctick:
  2321. break;
  2322. case dem_stop:
  2323. {
  2324. swallowmessages = false;
  2325. demofinished = true;
  2326. }
  2327. break;
  2328. case dem_consolecmd:
  2329. {
  2330. ACTIVE_SPLITSCREEN_PLAYER_GUARD( nPlayerSlot );
  2331. m_DemoFile.ReadConsoleCommand();
  2332. }
  2333. break;
  2334. case dem_datatables:
  2335. {
  2336. m_DemoFile.ReadNetworkDataTables( NULL );
  2337. }
  2338. break;
  2339. case dem_stringtables:
  2340. {
  2341. void *data = malloc( DEMO_RECORD_BUFFER_SIZE );
  2342. bf_read buf( "dem_stringtables", data, DEMO_RECORD_BUFFER_SIZE );
  2343. m_DemoFile.ReadStringTables( &buf );
  2344. buf.Seek( 0 );
  2345. if ( !demoScanStringTables.ReadStringTables( buf ) )
  2346. {
  2347. Host_Error( "Error parsing string tables during demo scan." );
  2348. }
  2349. free( data );
  2350. }
  2351. break;
  2352. case dem_usercmd:
  2353. {
  2354. ACTIVE_SPLITSCREEN_PLAYER_GUARD( nPlayerSlot );
  2355. m_DemoFile.ReadUserCmd( NULL, dummy );
  2356. }
  2357. break;
  2358. case dem_packet:
  2359. nDemoPackets++;
  2360. // fall through
  2361. default:
  2362. {
  2363. swallowmessages = false;
  2364. }
  2365. break;
  2366. }
  2367. }
  2368. while ( swallowmessages );
  2369. if ( demofinished )
  2370. {
  2371. break;
  2372. }
  2373. m_DemoFile.ReadCmdInfo( info );
  2374. m_DemoFile.ReadSequenceInfo( dummy, dummy );
  2375. int length = m_DemoFile.ReadRawData( buf, NET_MAX_PAYLOAD );
  2376. if ( length > 0 )
  2377. {
  2378. bf_read message;
  2379. message.StartReading( buf, length );
  2380. CNetChan *pNetChannel = ( CNetChan * ) GetBaseLocalClient().m_NetChannel;
  2381. while ( true )
  2382. {
  2383. if ( message.IsOverflowed() )
  2384. {
  2385. Msg( "Packet message is overflowed while scanning for important ticks!\n" );
  2386. break;
  2387. }
  2388. // Are we at the end?
  2389. if ( message.GetNumBitsLeft() < 8 ) // Minimum bits for message header encoded using VarInt32
  2390. {
  2391. break;
  2392. }
  2393. // see if we have a registered message object for this type
  2394. unsigned char cmd = message.ReadVarInt32();
  2395. INetMessageBinder *pMsgBind = pNetChannel->FindMessageBinder( cmd, 0 );
  2396. if ( pMsgBind )
  2397. {
  2398. INetMessage *netmsg = pMsgBind->CreateFromBuffer( message );
  2399. if ( netmsg && netmsg->GetType() == svc_GameEvent ) // only care about game events
  2400. {
  2401. CSVCMsg_GameEvent_t *msg = static_cast< CSVCMsg_GameEvent_t * >( netmsg );
  2402. CGameEventDescriptor *pDescriptor = g_GameEventManager.GetEventDescriptor( msg->eventid() );
  2403. for ( int nEvent = 0; nEvent < m_ImportantGameEvents.Count(); nEvent++ )
  2404. {
  2405. if ( msg->eventid() == m_ImportantGameEvents[ nEvent ].nEventID )
  2406. {
  2407. DemoImportantTick_t importantTick;
  2408. importantTick.nTick = tick;
  2409. importantTick.nPreviousTick = previousTick;
  2410. importantTick.nImportanGameEventIndex = nEvent;
  2411. importantTick.bCanDirectSeek = false;
  2412. importantTick.pKeys = NULL;
  2413. bool bIgnore = false;
  2414. ParseEventKeys( msg, pDescriptor, m_ImportantGameEvents[ nEvent ].pszEventName, &importantTick.pKeys );
  2415. if ( !m_ImportantGameEvents[ nEvent ].bScanOnly )
  2416. {
  2417. if ( V_strcasecmp( m_ImportantGameEvents[ nEvent ].pszEventName, "player_death" ) == 0 )
  2418. {
  2419. GetExtraDeathEventInfo( importantTick.pKeys );
  2420. }
  2421. else if ( V_strcasecmp( m_ImportantGameEvents[ nEvent ].pszEventName, "bomb_defused" ) == 0 ||
  2422. V_strcasecmp( m_ImportantGameEvents[ nEvent ].pszEventName, "bomb_planted" ) == 0 )
  2423. {
  2424. if ( m_bLowlightsMode )
  2425. bIgnore = true;
  2426. else
  2427. GetExtraEventInfo( importantTick.pKeys );
  2428. }
  2429. if ( !bIgnore )
  2430. m_ImportantTicks.AddToTail( importantTick );
  2431. }
  2432. else
  2433. {
  2434. if ( m_bScanMode && V_strcasecmp( m_ImportantGameEvents[nEvent].pszEventName, m_szScanMode ) == 0 )
  2435. {
  2436. GetEventInfoForScan( m_szScanMode, importantTick.pKeys );
  2437. }
  2438. importantTick.pKeys->deleteThis(); // cleanup keys since we aren't saving this tick
  2439. }
  2440. break;
  2441. }
  2442. }
  2443. }
  2444. else if ( netmsg && netmsg->GetType() == svc_UpdateStringTable )
  2445. {
  2446. CSVCMsg_UpdateStringTable_t *msg = static_cast< CSVCMsg_UpdateStringTable_t * >( netmsg );
  2447. CNetworkStringTable *table = (CNetworkStringTable*)
  2448. demoScanStringTables.GetTable( msg->table_id() );
  2449. bf_read data( &msg->string_data()[0], msg->string_data().size() );
  2450. table->ParseUpdate( data, msg->num_changed_entries() );
  2451. }
  2452. delete netmsg;
  2453. }
  2454. }
  2455. }
  2456. previousTick = tick;
  2457. }
  2458. demoScanStringTables.RemoveAllTables();
  2459. m_DemoFile.SeekTo( starting_position, true );
  2460. if ( m_DemoFile.m_DemoHeader.playback_time == 0.0f && m_DemoFile.m_DemoHeader.playback_ticks == 0 && m_DemoFile.m_DemoHeader.playback_frames == 0 )
  2461. {
  2462. // this is a corrupt/incomplete file
  2463. if ( !demo_strict_validation.GetInt() && previousTick > 1 && nDemoPackets > 1 )
  2464. {
  2465. m_DemoFile.m_DemoHeader.playback_ticks = previousTick;
  2466. if ( m_nTickToPauseOn == -1 )
  2467. {
  2468. m_nTickToPauseOn = previousTick;
  2469. }
  2470. m_DemoFile.m_DemoHeader.playback_frames = nDemoPackets - 1;
  2471. m_DemoFile.m_DemoHeader.playback_time = previousTick * host_state.interval_per_tick;
  2472. // and we are allowed to "heal" it
  2473. Msg( "Attempting to heal incomplete demo file: assuming %d ticks, %d frames, %.2f seconds @%.0fHz\n", m_DemoFile.m_DemoHeader.playback_ticks, m_DemoFile.m_DemoHeader.playback_frames, m_DemoFile.m_DemoHeader.playback_time, 1.0f / host_state.interval_per_tick );
  2474. }
  2475. }
  2476. }
  2477. void CDemoPlayer::BuildHighlightList()
  2478. {
  2479. if ( m_bDoHighlightScan && m_highlightSteamID.ConvertToUint64() != 0 )
  2480. {
  2481. m_highlights.RemoveAll();
  2482. DemoHighlightEntry_t entry;
  2483. int nTicksAfterEvent = demo_highlight_timeafter.GetFloat() / host_state.interval_per_tick;
  2484. int nTicksBeforeEvent = demo_highlight_timebefore.GetFloat() / host_state.interval_per_tick;
  2485. const char *szKeyToCheck = m_bLowlightsMode ? "victim_xuid" : "attacker_xuid";
  2486. int nImportantTickIndex = FindNextImportantTickByXuid( 0, m_highlightSteamID );
  2487. while ( nImportantTickIndex != -1 )
  2488. {
  2489. if ( CheckKeyXuid( m_highlightSteamID, m_ImportantTicks[ nImportantTickIndex ].pKeys, szKeyToCheck ) ||
  2490. CheckKeyXuid( m_highlightSteamID, m_ImportantTicks[ nImportantTickIndex ].pKeys, "player_xuid" ) )
  2491. {
  2492. entry.nSeekToTick =
  2493. entry.nFastForwardToTick = Max( m_ImportantTicks[ nImportantTickIndex ].nPreviousTick - nTicksBeforeEvent, 0 ); // no need to play before the beginning of the stream
  2494. entry.nPlayToTick = m_ImportantTicks[ nImportantTickIndex ].nTick + nTicksAfterEvent;
  2495. entry.nActualFirstEventTick = entry.nActualLastEventTick = m_ImportantTicks[ nImportantTickIndex ].nTick;
  2496. entry.nNumEvents = 1;
  2497. if ( m_bLowlightsMode )
  2498. {
  2499. CSteamID attackerSteamID( m_ImportantTicks[ nImportantTickIndex ].pKeys->GetUint64( "attacker_xuid" ) );
  2500. entry.unAccountID = attackerSteamID.GetAccountID();
  2501. }
  2502. else
  2503. {
  2504. entry.unAccountID = m_highlightSteamID.GetAccountID();
  2505. }
  2506. // check if next event is close enough to this one to just include in this highlight entry
  2507. int nNextImportantTickIndex = FindNextImportantTickByXuid( m_ImportantTicks[ nImportantTickIndex ].nTick + 1, m_highlightSteamID );
  2508. while ( nNextImportantTickIndex != -1 )
  2509. {
  2510. if ( CheckKeyXuid( m_highlightSteamID, m_ImportantTicks[ nNextImportantTickIndex ].pKeys, szKeyToCheck ) ||
  2511. CheckKeyXuid( m_highlightSteamID, m_ImportantTicks[ nNextImportantTickIndex ].pKeys, "player_xuid" ) )
  2512. {
  2513. if ( m_ImportantTicks[ nNextImportantTickIndex ].nPreviousTick - nTicksBeforeEvent <= entry.nPlayToTick )
  2514. {
  2515. entry.nPlayToTick = m_ImportantTicks[ nNextImportantTickIndex ].nTick + nTicksAfterEvent;
  2516. entry.nActualLastEventTick = m_ImportantTicks[ nNextImportantTickIndex ].nTick;
  2517. entry.nNumEvents++;
  2518. nImportantTickIndex = nNextImportantTickIndex;
  2519. }
  2520. else
  2521. {
  2522. break;
  2523. }
  2524. }
  2525. nNextImportantTickIndex = FindNextImportantTickByXuid( m_ImportantTicks[ nNextImportantTickIndex ].nTick + 1, m_highlightSteamID );
  2526. }
  2527. m_highlights.AddToTail( entry );
  2528. }
  2529. nImportantTickIndex = FindNextImportantTickByXuid( m_ImportantTicks[ nImportantTickIndex ].nTick + 1, m_highlightSteamID );
  2530. }
  2531. // when we cross a round start between highlights, we include the round start in the highlight
  2532. // the playback will fast forward from the round start until nearby the highlight
  2533. if ( 0 )
  2534. for ( int i = 1; i < m_highlights.Count(); i++ )
  2535. {
  2536. int nTicksSkipToRoundThreshold = demo_highlight_skipthreshold.GetFloat() / host_state.interval_per_tick;
  2537. int nRoundTick = FindPreviousImportantTick( m_highlights[ i ].nFastForwardToTick, "round_start" );
  2538. if ( nRoundTick != -1 && ( m_ImportantTicks[ nRoundTick ].nTick - m_highlights[ i - 1 ].nPlayToTick ) > nTicksSkipToRoundThreshold )
  2539. {
  2540. m_highlights[ i ].nSeekToTick = m_ImportantTicks[ nRoundTick ].nPreviousTick;
  2541. }
  2542. }
  2543. if ( m_highlights.Count() > 0 )
  2544. {
  2545. // add on a seek to the end of the match and play from their to the end of the demo, this will show the scoreboard
  2546. int nEndMatchTickIndex = demoplayer->FindPreviousImportantTick( m_DemoFile.m_DemoHeader.playback_ticks, "announce_phase_end" );
  2547. if ( nEndMatchTickIndex != -1 )
  2548. {
  2549. int nTicksBeforeMatchEnd = 1.0f / host_state.interval_per_tick;
  2550. entry.nSeekToTick = m_ImportantTicks[ nEndMatchTickIndex ].nPreviousTick - nTicksBeforeMatchEnd;
  2551. entry.nFastForwardToTick = m_ImportantTicks[ nEndMatchTickIndex ].nPreviousTick - nTicksBeforeMatchEnd;
  2552. entry.nPlayToTick = m_ImportantTicks[ nEndMatchTickIndex ].nTick;
  2553. entry.nActualFirstEventTick = entry.nActualLastEventTick = m_ImportantTicks[ nEndMatchTickIndex ].nTick;
  2554. entry.nNumEvents = 0;
  2555. entry.unAccountID = m_highlightSteamID.GetAccountID();
  2556. m_highlights.AddToTail( entry );
  2557. }
  2558. m_nCurrentHighlight = 0;
  2559. }
  2560. else
  2561. {
  2562. // didn't find any highlights, so kill the parameters so the demo will play normally
  2563. m_pPlaybackParameters = NULL;
  2564. }
  2565. m_bDoHighlightScan = false;
  2566. }
  2567. }
  2568. int CDemoPlayer::FindNextImportantTick( int nCurrentTick, const char *pEventName /* = NULL */ )
  2569. {
  2570. FOR_EACH_VEC( m_ImportantTicks, i )
  2571. {
  2572. if ( m_ImportantTicks[ i ].nTick > nCurrentTick )
  2573. {
  2574. if ( pEventName == NULL || V_stricmp( m_ImportantGameEvents[ m_ImportantTicks[ i ].nImportanGameEventIndex ].pszEventName, pEventName ) == 0 )
  2575. {
  2576. return i;
  2577. }
  2578. }
  2579. }
  2580. return -1;
  2581. }
  2582. int CDemoPlayer::FindPreviousImportantTick( int nCurrentTick, const char *pEventName /* = NULL */ )
  2583. {
  2584. FOR_EACH_VEC_BACK( m_ImportantTicks, i )
  2585. {
  2586. if ( m_ImportantTicks[ i ].nTick < nCurrentTick )
  2587. {
  2588. if ( pEventName == NULL || V_stricmp( m_ImportantGameEvents[ m_ImportantTicks[ i ].nImportanGameEventIndex ].pszEventName, pEventName ) == 0 )
  2589. {
  2590. return i;
  2591. }
  2592. }
  2593. }
  2594. return -1;
  2595. }
  2596. int CDemoPlayer::FindNextImportantTickByXuidAndEvent( int nCurrentTick, const CSteamID &steamID, const char *pKeyWithXuid, const char *pEventName /* = NULL */ )
  2597. {
  2598. FOR_EACH_VEC( m_ImportantTicks, i )
  2599. {
  2600. if ( m_ImportantTicks[ i ].nTick > nCurrentTick )
  2601. {
  2602. CSteamID compareSteamID( m_ImportantTicks[ i ].pKeys->GetUint64( pKeyWithXuid ) );
  2603. if ( ( steamID.GetAccountID() == compareSteamID.GetAccountID() ) && ( pEventName == NULL || V_stricmp( m_ImportantGameEvents[ m_ImportantTicks[ i ].nImportanGameEventIndex ].pszEventName, pEventName ) == 0 ) )
  2604. {
  2605. return i;
  2606. }
  2607. }
  2608. }
  2609. return -1;
  2610. }
  2611. int CDemoPlayer::FindNextImportantTickByXuid( int nCurrentTick, const CSteamID &steamID )
  2612. {
  2613. FOR_EACH_VEC( m_ImportantTicks, i )
  2614. {
  2615. if ( m_ImportantTicks[ i ].nTick > nCurrentTick )
  2616. {
  2617. CSteamID playerSteamID( m_ImportantTicks[ i ].pKeys->GetUint64( "player_xuid" ) );
  2618. if ( steamID.GetAccountID() == playerSteamID.GetAccountID() )
  2619. {
  2620. return i;
  2621. }
  2622. CSteamID attackerSteamID( m_ImportantTicks[ i ].pKeys->GetUint64( "attacker_xuid" ) );
  2623. if ( steamID.GetAccountID() == attackerSteamID.GetAccountID() )
  2624. {
  2625. return i;
  2626. }
  2627. CSteamID victimSteamID( m_ImportantTicks[ i ].pKeys->GetUint64( "victim_xuid" ) );
  2628. if ( steamID.GetAccountID() == victimSteamID.GetAccountID() )
  2629. {
  2630. return i;
  2631. }
  2632. }
  2633. }
  2634. return -1;
  2635. }
  2636. int CDemoPlayer::FindPreviousImportantTickByXuidAndEvent( int nCurrentTick, const CSteamID &steamID, const char *pKeyWithXuid, const char *pEventName /* = NULL */ )
  2637. {
  2638. FOR_EACH_VEC_BACK( m_ImportantTicks, i )
  2639. {
  2640. if ( m_ImportantTicks[ i ].nTick < nCurrentTick )
  2641. {
  2642. CSteamID compareSteamID( m_ImportantTicks[ i ].pKeys->GetUint64( pKeyWithXuid ) );
  2643. if ( ( steamID.GetAccountID() == compareSteamID.GetAccountID() ) && ( pEventName == NULL || V_stricmp( m_ImportantGameEvents[ m_ImportantTicks[ i ].nImportanGameEventIndex ].pszEventName, pEventName ) == 0 ) )
  2644. {
  2645. return i;
  2646. }
  2647. }
  2648. }
  2649. return -1;
  2650. }
  2651. const DemoImportantTick_t *CDemoPlayer::GetImportantTick( int nIndex )
  2652. {
  2653. if ( m_ImportantTicks.IsValidIndex( nIndex ) )
  2654. {
  2655. return &m_ImportantTicks[ nIndex ];
  2656. }
  2657. return NULL;
  2658. }
  2659. const DemoImportantGameEvent_t *CDemoPlayer::GetImportantGameEvent( const char *pszEventName )
  2660. {
  2661. FOR_EACH_VEC( m_ImportantGameEvents, i )
  2662. {
  2663. if ( V_strcasecmp( m_ImportantGameEvents[ i ].pszEventName, pszEventName ) == 0 )
  2664. {
  2665. return &m_ImportantGameEvents[ i ];
  2666. }
  2667. }
  2668. return NULL;
  2669. }
  2670. void CDemoPlayer::ListImportantTicks()
  2671. {
  2672. Msg( "Important Ticks in demo:\n" );
  2673. FOR_EACH_VEC( m_ImportantTicks, i )
  2674. {
  2675. if ( V_strcasecmp( m_ImportantGameEvents[ m_ImportantTicks[ i ].nImportanGameEventIndex ].pszEventName, "player_death" ) == 0 )
  2676. {
  2677. if ( m_ImportantTicks[ i ].pKeys->GetBool( "headshot" ) )
  2678. {
  2679. Msg( "Tick: %d : %s killed %s with a headshot using a %s\n", m_ImportantTicks[ i ].nTick, m_ImportantTicks[ i ].pKeys->GetString( "attacker_name" ),
  2680. m_ImportantTicks[ i ].pKeys->GetString( "victim_name" ), m_ImportantTicks[ i ].pKeys->GetString( "weapon" ) );
  2681. }
  2682. else
  2683. {
  2684. Msg( "Tick: %d : %s killed %s using a %s\n", m_ImportantTicks[ i ].nTick, m_ImportantTicks[ i ].pKeys->GetString( "attacker_name" ),
  2685. m_ImportantTicks[ i ].pKeys->GetString( "victim_name" ), m_ImportantTicks[ i ].pKeys->GetString( "weapon" ) );
  2686. }
  2687. }
  2688. else
  2689. {
  2690. Msg( "Tick: %d Event: %s \n", m_ImportantTicks[ i ].nTick, m_ImportantGameEvents[ m_ImportantTicks[ i ].nImportanGameEventIndex ].pszEventName );
  2691. }
  2692. }
  2693. }
  2694. void CDemoPlayer::ListHighlightData()
  2695. {
  2696. if ( m_highlights.Count() > 0 )
  2697. {
  2698. Msg( "Highlights in demo:\n" );
  2699. FOR_EACH_VEC( m_highlights, i )
  2700. {
  2701. Msg( "highlight: %d >> %d -> %d (%d) (%d,%d)\n", m_highlights[ i ].nSeekToTick, m_highlights[ i ].nFastForwardToTick, m_highlights[ i ].nPlayToTick,
  2702. m_highlights[ i ].nNumEvents, m_highlights[ i ].nActualFirstEventTick, m_highlights[ i ].nActualLastEventTick );
  2703. }
  2704. }
  2705. else
  2706. {
  2707. Msg( "No Highlights.\n" );
  2708. }
  2709. }
  2710. static bool ComputeNextIncrementalDemoFilename( char *name, int namesize )
  2711. {
  2712. FileHandle_t test;
  2713. test = g_pFileSystem->Open( name, "rb" );
  2714. if ( FILESYSTEM_INVALID_HANDLE == test )
  2715. {
  2716. // file doesn't exist, so we can use that
  2717. return true;
  2718. }
  2719. g_pFileSystem->Close( test );
  2720. char basename[ MAX_OSPATH ];
  2721. V_StripExtension( name, basename, sizeof( basename ) );
  2722. // Start looking for a valid name
  2723. int i = 0;
  2724. for ( i = 0; i < 1000; i++ )
  2725. {
  2726. char newname[ MAX_OSPATH ];
  2727. V_sprintf_safe( newname, "%s%03i.dem", basename, i );
  2728. test = g_pFileSystem->Open( newname, "rb" );
  2729. if ( FILESYSTEM_INVALID_HANDLE == test )
  2730. {
  2731. V_strncpy( name, newname, namesize );
  2732. return true;
  2733. }
  2734. g_pFileSystem->Close( test );
  2735. }
  2736. ConMsg( "Unable to find a valid incremental demo filename for %s, try clearing the directory of %snnn.dem\n", name, basename );
  2737. return false;
  2738. }
  2739. //-----------------------------------------------------------------------------
  2740. // Purpose: List the contents of a demo file.
  2741. //-----------------------------------------------------------------------------
  2742. void CL_ListDemo_f( const CCommand &args )
  2743. {
  2744. // Find the file
  2745. char name[MAX_OSPATH];
  2746. V_sprintf_safe(name, "%s", args[1]);
  2747. V_DefaultExtension( name, ".dem", sizeof( name ) );
  2748. ConMsg ("Demo contents for %s:\n", name);
  2749. CDemoFile demofile;
  2750. if ( !demofile.Open( name, true ) )
  2751. {
  2752. ConMsg ("ERROR: couldn't open.\n");
  2753. return;
  2754. }
  2755. demofile.ReadDemoHeader( NULL );
  2756. demoheader_t *header = &demofile.m_DemoHeader;
  2757. if ( !header )
  2758. {
  2759. ConMsg( "Failed reading demo header.\n" );
  2760. demofile.Close();
  2761. return;
  2762. }
  2763. if ( V_strcmp( header->demofilestamp, DEMO_HEADER_ID ) )
  2764. {
  2765. ConMsg( "%s is not a valid demo file\n", name);
  2766. return;
  2767. }
  2768. ConMsg("Network protocol: %i\n", header->networkprotocol);
  2769. ConMsg("Demo version : %i\n", header->demoprotocol);
  2770. ConMsg("Server name : %s\n", header->servername);
  2771. ConMsg("Map name : %s\n", header->mapname);
  2772. ConMsg("Game : %s\n", header->gamedirectory);
  2773. ConMsg("Player name : %s\n", header->clientname);
  2774. ConMsg("Time : %.1f\n", header->playback_time);
  2775. ConMsg("Ticks : %i\n", header->playback_ticks);
  2776. ConMsg("Frames : %i\n", header->playback_frames);
  2777. ConMsg("Signon size : %i\n", header->signonlength);
  2778. }
  2779. //-----------------------------------------------------------------------------
  2780. // Purpose:
  2781. //-----------------------------------------------------------------------------
  2782. CON_COMMAND( stop, "Finish recording demo." )
  2783. {
  2784. if ( !demorecorder->IsRecording() )
  2785. {
  2786. ConDMsg ("Not recording a demo.\n");
  2787. return;
  2788. }
  2789. char name[ MAX_OSPATH ] = "Cannot stop recording now";
  2790. if ( !g_ClientDLL->CanStopRecordDemo( name, sizeof( name ) ) )
  2791. {
  2792. ConMsg( "%s\n", name ); // re-use name as the error string if the client prevents us from stopping the demo
  2793. return;
  2794. }
  2795. demorecorder->StopRecording();
  2796. // Notify the client
  2797. g_ClientDLL->OnDemoRecordStop();
  2798. }
  2799. static void DemoRecord( char const *pchDemoFileName, bool incremental )
  2800. {
  2801. if ( g_ClientDLL == NULL )
  2802. {
  2803. ConMsg( "Can't record on dedicated server.\n" );
  2804. return;
  2805. }
  2806. if ( demorecorder->IsRecording() )
  2807. {
  2808. ConMsg ("Already recording.\n");
  2809. return;
  2810. }
  2811. if ( demoplayer->IsPlayingBack() )
  2812. {
  2813. ConMsg ("Can't record during demo playback.\n");
  2814. return;
  2815. }
  2816. // check path first
  2817. if ( !COM_IsValidPath( pchDemoFileName ) )
  2818. {
  2819. ConMsg( "record %s: invalid path.\n", pchDemoFileName );
  2820. return;
  2821. }
  2822. char name[ MAX_OSPATH ] = "Cannot record now";
  2823. if ( !g_ClientDLL->CanRecordDemo( name, sizeof( name ) ) )
  2824. {
  2825. ConMsg( "%s\n", name ); // re-use name as the error string if the client prevents us from starting a demo
  2826. return;
  2827. }
  2828. // remove .dem extentsion if user added it
  2829. V_StripExtension( pchDemoFileName, name, sizeof( name ) );
  2830. if ( incremental )
  2831. {
  2832. // If file exists, construct a better name
  2833. if ( !ComputeNextIncrementalDemoFilename( name, sizeof( name ) ) )
  2834. {
  2835. return;
  2836. }
  2837. }
  2838. // Notify polisher of record
  2839. g_ClientDLL->OnDemoRecordStart( name );
  2840. // Record it
  2841. demorecorder->StartRecording( name, incremental );
  2842. }
  2843. //-----------------------------------------------------------------------------
  2844. // Purpose:
  2845. //-----------------------------------------------------------------------------
  2846. CON_COMMAND_F( record, "Record a demo.", FCVAR_DONTRECORD )
  2847. {
  2848. if ( args.ArgC() != 2 && args.ArgC() != 3 )
  2849. {
  2850. ConMsg ("record <demoname> [incremental]\n");
  2851. return;
  2852. }
  2853. bool incremental = false;
  2854. if ( args.ArgC() == 3 )
  2855. {
  2856. if ( !V_stricmp( args[2], "incremental" ) )
  2857. {
  2858. incremental = true;
  2859. }
  2860. }
  2861. DemoRecord( args[ 1 ], incremental );
  2862. }
  2863. //-----------------------------------------------------------------------------
  2864. // Purpose:
  2865. //-----------------------------------------------------------------------------
  2866. CON_COMMAND_F( _record, "Record a demo incrementally.", FCVAR_DONTRECORD )
  2867. {
  2868. if ( g_ClientDLL == NULL )
  2869. {
  2870. ConMsg ("Can't record on dedicated server.\n");
  2871. return;
  2872. }
  2873. if ( args.ArgC() != 2 )
  2874. {
  2875. ConMsg ("_record <demoname>\n");
  2876. return;
  2877. }
  2878. DemoRecord( args[ 1 ], true );
  2879. }
  2880. //-----------------------------------------------------------------------------
  2881. // Purpose:
  2882. //-----------------------------------------------------------------------------
  2883. static CDemoPlaybackParameters_t s_DemoPlaybackParams; // make sure these parameters are available throughout demo playback
  2884. void SetPlaybackParametersLockFirstPersonAccountID( uint32 nAccountID )
  2885. {
  2886. s_DemoPlaybackParams.m_uiLockFirstPersonAccountID = nAccountID;
  2887. }
  2888. void CL_PlayDemo_f( const CCommand &passed_args )
  2889. {
  2890. demoplayer->StopPlayback();
  2891. // disconnect before loading demo, to avoid sometimes loading into game instead of demo
  2892. GetBaseLocalClient().Disconnect( false );
  2893. // need to re-tokenize the args without the default breakset
  2894. // the default splits out and of {}(): into separate args along with splitting on spaces
  2895. // that makes it impossible to rebuild a file path that has a mixture of those characters in them
  2896. characterset_t breakset;
  2897. CharacterSetBuild( &breakset, "" );
  2898. CCommand args;
  2899. args.Tokenize( passed_args.GetCommandString(), passed_args.Source(), &breakset );
  2900. if ( args.ArgC() < 2 )
  2901. {
  2902. ConMsg ("playdemo <demoname> <steamid>: plays a demo file. If steamid is given, then play highlights of that player \n");
  2903. return;
  2904. }
  2905. if ( !net_time && !NET_IsMultiplayer() )
  2906. {
  2907. ConMsg( "Deferring playdemo command!\n" );
  2908. return;
  2909. }
  2910. int nEndNameArg = args.ArgC() - 1;
  2911. int nStartRound = 0;
  2912. const char *szCurArg = args[ nEndNameArg ];
  2913. const char *szStartRoundPrefix = "startround:";
  2914. if ( char* szStartRoundParam = V_strstr( szCurArg, szStartRoundPrefix ) )
  2915. {
  2916. szStartRoundParam += strlen( szStartRoundPrefix );
  2917. nStartRound = V_atoi( szStartRoundParam );
  2918. nEndNameArg--;
  2919. }
  2920. // first check if last arg is "lowlights" and set flag
  2921. bool bLowlights = false;
  2922. if ( V_stricmp( args[ nEndNameArg ], "lowlights" ) == 0 )
  2923. {
  2924. bLowlights = true;
  2925. nEndNameArg--;
  2926. }
  2927. // then check the last (or next to last if last was lowlights) to get the steam ID
  2928. // steam IDs are numbers only, we just check for digits in all of it
  2929. int nSteamIDArg = nEndNameArg;
  2930. bool bSteamID_self = !V_strcmp( args[ nSteamIDArg ], "self" );
  2931. if ( bSteamID_self )
  2932. {
  2933. nEndNameArg--;
  2934. }
  2935. else
  2936. {
  2937. for ( int i = 0; i < V_strlen( args[ nSteamIDArg ] ); i++ )
  2938. {
  2939. if ( !V_isdigit( args[ nSteamIDArg ][ 0 ] ) )
  2940. {
  2941. nSteamIDArg = -1;
  2942. break;
  2943. }
  2944. }
  2945. if ( nSteamIDArg != -1 )
  2946. {
  2947. nEndNameArg--;
  2948. }
  2949. else if ( bLowlights )
  2950. {
  2951. ConMsg( "Warning: lowlights argument given without valid steam id, ignoring.\n" );
  2952. bLowlights = false;
  2953. }
  2954. }
  2955. // now take all remaining args and build them back into a path (will be multiple args if it has spaces or contains a ':'
  2956. char name[ MAX_OSPATH ];
  2957. if ( nEndNameArg > 1 )
  2958. {
  2959. V_strcpy_safe( name, args[ 1 ] );
  2960. for ( int i = 2; i <= nEndNameArg; i++ )
  2961. {
  2962. V_strcat_safe( name, " " );
  2963. V_strcat_safe( name, args[ i ] );
  2964. }
  2965. }
  2966. else
  2967. {
  2968. V_strcpy_safe( name, args[ 1 ] );
  2969. }
  2970. // see if there is a starting tick attached to the filename (filename@####)
  2971. int nStartingTick = -1;
  2972. char *pTemp = V_strstr( name, "@" );
  2973. if ( pTemp != NULL )
  2974. {
  2975. Assert( nStartRound == 0 ); // Don't specify both of these, start round will stomp
  2976. nStartingTick = V_atoi(&pTemp[ 1 ]);
  2977. if ( nStartingTick <= 0 )
  2978. {
  2979. nStartingTick = -1;
  2980. }
  2981. pTemp[0] = 0;
  2982. }
  2983. // set current demo player to client demo player
  2984. demoplayer = g_pClientDemoPlayer;
  2985. if ( demoplayer->IsPlayingBack() )
  2986. {
  2987. demoplayer->StopPlayback();
  2988. }
  2989. // disconnect before loading demo, to avoid sometimes loading into game instead of demo
  2990. GetBaseLocalClient().Disconnect( false );
  2991. CDemoPlaybackParameters_t *pParams = NULL;
  2992. if ( nSteamIDArg != -1 )
  2993. {
  2994. CSteamID steamID;
  2995. if ( bSteamID_self )
  2996. {
  2997. if ( ISteamUser* pSteamUser = Steam3Client().SteamUser() )
  2998. {
  2999. steamID = pSteamUser->GetSteamID();
  3000. }
  3001. else
  3002. {
  3003. ConMsg( "Cannot obtain user id\n" );
  3004. return;
  3005. }
  3006. }
  3007. else
  3008. {
  3009. steamID = CSteamID( args[ nSteamIDArg ] );
  3010. }
  3011. demoplayer->SetHighlightXuid( steamID.ConvertToUint64(), bLowlights );
  3012. V_memset( &s_DemoPlaybackParams, 0, sizeof( s_DemoPlaybackParams ) );
  3013. s_DemoPlaybackParams.m_uiHeaderPrefixLength = 0;
  3014. s_DemoPlaybackParams.m_bAnonymousPlayerIdentity = false;
  3015. s_DemoPlaybackParams.m_uiLockFirstPersonAccountID = steamID.GetAccountID();
  3016. s_DemoPlaybackParams.m_numRoundSkip = 0;
  3017. s_DemoPlaybackParams.m_numRoundStop = 999;
  3018. s_DemoPlaybackParams.m_bSkipWarmup = false;
  3019. pParams = &s_DemoPlaybackParams;
  3020. }
  3021. else if ( nStartRound > 0 )
  3022. {
  3023. s_DemoPlaybackParams.m_uiHeaderPrefixLength = 0;
  3024. s_DemoPlaybackParams.m_bAnonymousPlayerIdentity = false;
  3025. s_DemoPlaybackParams.m_uiLockFirstPersonAccountID = 0;
  3026. s_DemoPlaybackParams.m_numRoundSkip = nStartRound - 1;
  3027. s_DemoPlaybackParams.m_numRoundStop = 999;
  3028. s_DemoPlaybackParams.m_bSkipWarmup = true;
  3029. pParams = &s_DemoPlaybackParams;
  3030. demoplayer->SetHighlightXuid( 0, false );
  3031. }
  3032. else
  3033. {
  3034. demoplayer->SetHighlightXuid( 0, false );
  3035. }
  3036. //
  3037. // open the demo file
  3038. //
  3039. V_DefaultExtension( name, ".dem", sizeof( name ) );
  3040. if ( demoplayer != g_pClientDemoPlayer )
  3041. {
  3042. demoplayer->StopPlayback();
  3043. demoplayer = g_pClientDemoPlayer;
  3044. }
  3045. if ( g_pClientDemoPlayer->StartPlayback( name, false, pParams, nStartingTick ) )
  3046. {
  3047. // Remove extension
  3048. char basename[ MAX_OSPATH ];
  3049. V_StripExtension( name, basename, sizeof( basename ) );
  3050. g_ClientDLL->OnDemoPlaybackStart( basename );
  3051. }
  3052. else
  3053. {
  3054. SCR_EndLoadingPlaque();
  3055. }
  3056. }
  3057. void CL_ScanDemo_f(const CCommand &args)
  3058. {
  3059. if (args.ArgC() < 2)
  3060. {
  3061. ConMsg("scandemo <demoname>: scans a demo file.\n");
  3062. return;
  3063. }
  3064. // set current demo player to client demo player
  3065. demoplayer = g_pClientDemoPlayer;
  3066. if (demoplayer->IsPlayingBack())
  3067. {
  3068. demoplayer->StopPlayback();
  3069. }
  3070. // disconnect before loading demo, to avoid sometimes loading into game instead of demo
  3071. GetBaseLocalClient().Disconnect(false);
  3072. demoplayer->SetHighlightXuid( 0, false );
  3073. //
  3074. // open the demo file
  3075. //
  3076. char name[MAX_OSPATH];
  3077. V_strcpy_safe( name, args[ 1 ] );
  3078. V_DefaultExtension( name, ".dem", sizeof( name ) );
  3079. s_nMaxViewers = 0;
  3080. s_nMaxExternalTotal = 0;
  3081. s_nMaxExternalLinked = 0;
  3082. s_nMaxCombinedViewers = 0;
  3083. if ( demoplayer->ScanDemo( name, "hltv_status" ) )
  3084. {
  3085. // Remove extension
  3086. char basename[ MAX_OSPATH ];
  3087. V_StripExtension( name, basename, sizeof( basename ) );
  3088. g_ClientDLL->OnDemoPlaybackStart( basename );
  3089. }
  3090. else
  3091. {
  3092. SCR_EndLoadingPlaque();
  3093. }
  3094. }
  3095. void CL_ScanDemoDone( const char *pszMode )
  3096. {
  3097. if ( V_strcasecmp( pszMode, "hltv_status" ) == 0 )
  3098. {
  3099. Msg( "Max GOTV Viewers: %d Max External Viewers: %d Max External Linked: %d Max Combined Viewers: %d \n", s_nMaxViewers, s_nMaxExternalTotal, s_nMaxExternalLinked, s_nMaxCombinedViewers );
  3100. }
  3101. demoplayer->StopPlayback();
  3102. GetBaseLocalClient().Disconnect( false );
  3103. }
  3104. void CL_PlayOverwatchEvidence_f( const CCommand &args )
  3105. {
  3106. if ( args.ArgC() != 3 )
  3107. {
  3108. DevMsg( "playoverwatchevidence syntax error.\n" );
  3109. return;
  3110. }
  3111. //
  3112. // Validate the header
  3113. //
  3114. char const *szCaseKey = args[1];
  3115. char name[ MAX_OSPATH ];
  3116. V_strcpy_safe( name, args[2] );
  3117. if ( !g_pFullFileSystem->FileExists( name ) )
  3118. {
  3119. DevMsg( "playoverwatchevidence no file.\n" );
  3120. return;
  3121. }
  3122. CUtlBuffer bufHeader;
  3123. if ( !g_pFullFileSystem->ReadFile( name, NULL, bufHeader, 128 ) )
  3124. {
  3125. DevMsg( "playoverwatchevidence read file error.\n" );
  3126. return;
  3127. }
  3128. if ( bufHeader.TellMaxPut() != 128 )
  3129. {
  3130. DevMsg( "playoverwatchevidence header of invalid size.\n" );
  3131. return;
  3132. }
  3133. static CDemoPlaybackParameters_t params; // make sure these parameters are available throughout demo playback
  3134. V_memset( &params, 0, sizeof( params ) );
  3135. params.m_uiHeaderPrefixLength = 128;
  3136. if ( !g_ClientDLL->ValidateSignedEvidenceHeader( szCaseKey, bufHeader.Base(), &params ) )
  3137. return;
  3138. // set current demo player to client demo player
  3139. demoplayer = g_pClientDemoPlayer;
  3140. //
  3141. // open the demo file
  3142. //
  3143. if ( g_pClientDemoPlayer->StartPlayback( name, false, &params ) )
  3144. {
  3145. // Remove extension
  3146. char basename[ MAX_OSPATH ];
  3147. V_StripExtension( name, basename, sizeof( basename ) );
  3148. g_ClientDLL->OnDemoPlaybackStart( basename );
  3149. }
  3150. else
  3151. {
  3152. SCR_EndLoadingPlaque();
  3153. }
  3154. }
  3155. void CL_TimeDemo_Helper( const char *pDemoName, const char *pStatsFileName, const char *pVProfStatsFileName )
  3156. {
  3157. V_strncpy( g_pStatsFile, pStatsFileName ? pStatsFileName : "UNKNOWN", sizeof( g_pStatsFile ) );
  3158. // set current demo player to client demo player
  3159. demoplayer = g_pClientDemoPlayer;
  3160. // open the demo file
  3161. char name[ MAX_OSPATH ];
  3162. V_strcpy_safe(name, pDemoName );
  3163. V_DefaultExtension( name, ".dem", sizeof( name ) );
  3164. if( pVProfStatsFileName )
  3165. {
  3166. g_EngineStats.EnableVProfStatsRecording( pVProfStatsFileName );
  3167. }
  3168. if ( !g_pClientDemoPlayer->StartPlayback( name, true, NULL ) )
  3169. {
  3170. SCR_EndLoadingPlaque();
  3171. }
  3172. }
  3173. //-----------------------------------------------------------------------------
  3174. // Purpose:
  3175. //-----------------------------------------------------------------------------
  3176. void CL_TimeDemo_f( const CCommand &args )
  3177. {
  3178. if ( args.ArgC() < 2 || args.ArgC() > 3 )
  3179. {
  3180. ConMsg ("timedemo <demoname> <optional stats.txt> : gets demo speeds, writing perf resutls to the optional stats.txt\n");
  3181. return;
  3182. }
  3183. CL_TimeDemo_Helper( args[1], ( args.ArgC() >= 3 ) ? args[2] : NULL, NULL );
  3184. }
  3185. void CL_TimeDemo_VProfRecord_f( const CCommand &args )
  3186. {
  3187. if ( args.ArgC() != 3 )
  3188. {
  3189. ConMsg ("timedemo_vprofrecord <demoname> <vprof stats filename> : gets demo speeds, recording perf data to a vprof stats file\n");
  3190. return;
  3191. }
  3192. CL_TimeDemo_Helper( args[1], NULL, args[2] );
  3193. }
  3194. void CL_TimeDemoQuit_f( const CCommand &args )
  3195. {
  3196. demo_quitafterplayback.SetValue( 1 );
  3197. CL_TimeDemo_f( args );
  3198. }
  3199. void CL_BenchFrame_f( const CCommand &args )
  3200. {
  3201. if ( args.ArgC() != 4 )
  3202. {
  3203. ConMsg ("benchframe <demoname> <frame> <tgafilename>: takes a snapshot of a particular frame in a demo\n");
  3204. return;
  3205. }
  3206. g_pClientDemoPlayer->SetBenchframe( MAX( 0, atoi( args[2] ) ), args[3] );
  3207. s_bBenchframe = true;
  3208. mat_norendering.SetValue( 1 );
  3209. // set current demo player to client demo player
  3210. demoplayer = g_pClientDemoPlayer;
  3211. // open the demo file
  3212. char name[ MAX_OSPATH ];
  3213. V_strcpy_safe(name, args[1] );
  3214. V_DefaultExtension( name, ".dem", sizeof( name ) );
  3215. if ( !g_pClientDemoPlayer->StartPlayback( name, true, NULL ) )
  3216. {
  3217. SCR_EndLoadingPlaque();
  3218. }
  3219. }
  3220. //-----------------------------------------------------------------------------
  3221. // Purpose:
  3222. //-----------------------------------------------------------------------------
  3223. CON_COMMAND( vtune, "Controls VTune's sampling." )
  3224. {
  3225. if ( args.ArgC() != 2 )
  3226. {
  3227. ConMsg ("vtune \"pause\" | \"resume\" : Suspend or resume VTune's sampling.\n");
  3228. return;
  3229. }
  3230. if( !V_strcasecmp( args[1], "pause" ) )
  3231. {
  3232. if(!vtune(false))
  3233. {
  3234. ConMsg("Failed to find \"VTPause()\" in \"vtuneapi.dll\".\n");
  3235. return;
  3236. }
  3237. ConMsg("VTune sampling paused.\n");
  3238. }
  3239. else if( !V_strcasecmp( args[1], "resume" ) )
  3240. {
  3241. if(!vtune(true))
  3242. {
  3243. ConMsg("Failed to find \"VTResume()\" in \"vtuneapi.dll\".\n");
  3244. return;
  3245. }
  3246. ConMsg("VTune sampling resumed.\n");
  3247. }
  3248. else
  3249. {
  3250. ConMsg("Unknown vtune option.\n");
  3251. }
  3252. }
  3253. CON_COMMAND_AUTOCOMPLETEFILE( playdemo, CL_PlayDemo_f, "Play a recorded demo file (.dem ).", NULL, dem );
  3254. CON_COMMAND_AUTOCOMPLETEFILE( scandemo, CL_ScanDemo_f, "Scan a recorded demo file (.dem ) for specific game events and dump data.", NULL, dem );
  3255. CON_COMMAND_EXTERN_F( playoverwatchevidence, CL_PlayOverwatchEvidence_f, "Play evidence for an overwatch case.", FCVAR_HIDDEN );
  3256. CON_COMMAND_AUTOCOMPLETEFILE( timedemo, CL_TimeDemo_f, "Play a demo and report performance info.", NULL, dem );
  3257. CON_COMMAND_AUTOCOMPLETEFILE( timedemoquit, CL_TimeDemoQuit_f, "Play a demo, report performance info, and then exit", NULL, dem );
  3258. CON_COMMAND_AUTOCOMPLETEFILE( listdemo, CL_ListDemo_f, "List demo file contents.", NULL, dem );
  3259. CON_COMMAND_AUTOCOMPLETEFILE( benchframe, CL_BenchFrame_f, "Takes a snapshot of a particular frame in a time demo.", NULL, dem );
  3260. CON_COMMAND_AUTOCOMPLETEFILE( timedemo_vprofrecord, CL_TimeDemo_VProfRecord_f, "Play a demo and report performance info. Also record vprof data for the span of the demo", NULL, dem );
  3261. CON_COMMAND( demo_pause, "Pauses demo playback." )
  3262. {
  3263. float seconds = -1.0;
  3264. if ( args.ArgC() == 2 )
  3265. {
  3266. seconds = atof( args[1] );
  3267. }
  3268. demoplayer->PausePlayback( seconds );
  3269. }
  3270. CON_COMMAND( demo_resume, "Resumes demo playback." )
  3271. {
  3272. demoplayer->ResumePlayback();
  3273. }
  3274. CON_COMMAND( demo_togglepause, "Toggles demo playback." )
  3275. {
  3276. if ( !demoplayer->IsPlayingBack() )
  3277. return;
  3278. if ( demoplayer->IsPlaybackPaused() )
  3279. {
  3280. demoplayer->ResumePlayback();
  3281. }
  3282. else
  3283. {
  3284. demoplayer->PausePlayback( -1 );
  3285. }
  3286. }
  3287. CON_COMMAND( demo_goto, "Skips to location in demo." )
  3288. {
  3289. bool bRelative = false;
  3290. bool bPause = false;
  3291. if ( args.ArgC() < 2 )
  3292. {
  3293. Msg( "Syntax: demo_goto <tick> [relative] [pause]\n" );
  3294. Msg( " eg: 'demo_gototick 6666' or 'demo_gototick 25%' or 'demo_gototick 42min'\n" );
  3295. if ( demoplayer && demoplayer->IsPlayingBack() )
  3296. {
  3297. IDemoStream *pDemoStream = demoplayer->GetDemoStream();
  3298. float flTotalTime = TICKS_TO_TIME( pDemoStream->GetTotalTicks() ) / 60.0f;
  3299. Msg( " Currently playing %d of %d ticks. Minutes:%.2f File:%s\n",
  3300. demoplayer->GetPlaybackTick(), pDemoStream->GetTotalTicks(),
  3301. flTotalTime, pDemoStream->GetUrl() );
  3302. }
  3303. return;
  3304. }
  3305. int iArg = 1;
  3306. const char *strTick = args[ iArg++ ];
  3307. int nTick = atoi( strTick );
  3308. // If they gave us "50%" or "50 %", then head to percentage of the file.
  3309. bool bIsPct = !!strchr( strTick, '%' );
  3310. bool bIsMinutes = !!strchr( strTick, 'm' );
  3311. if ( !bIsPct && ( args[ iArg ][ 0 ] == '%' ) )
  3312. {
  3313. iArg++;
  3314. bIsPct = true;
  3315. }
  3316. else if ( !bIsMinutes && ( args[ iArg ][ 0 ] == 'm' ) )
  3317. {
  3318. iArg++;
  3319. bIsMinutes = true;
  3320. }
  3321. if ( bIsPct )
  3322. {
  3323. nTick = Clamp( nTick, 0, 100 ) * demoplayer->GetDemoStream()->GetTotalTicks() / 100;
  3324. }
  3325. else if ( bIsMinutes )
  3326. {
  3327. nTick = Clamp( 60 * TIME_TO_TICKS( nTick ), 0, demoplayer->GetDemoStream()->GetTotalTicks() - 100 );
  3328. }
  3329. for ( ; iArg < args.ArgC(); iArg++ )
  3330. {
  3331. switch ( toupper( args[ iArg ][ 0 ] ) )
  3332. {
  3333. case 'R':
  3334. bRelative = true;
  3335. break;
  3336. case 'P':
  3337. bPause = true;
  3338. break;
  3339. }
  3340. }
  3341. demoplayer->SkipToTick( nTick, bRelative, bPause );
  3342. }
  3343. CON_COMMAND( demo_gototick, "Skips to a tick in demo." )
  3344. {
  3345. demo_goto( args );
  3346. }
  3347. CON_COMMAND( demo_info, "Print information about currently playing demo." )
  3348. {
  3349. if ( !demoplayer->IsPlayingBack() )
  3350. {
  3351. Msg( "Error - Not currently playing back a demo.\n" );
  3352. return;
  3353. }
  3354. ConMsg("Demo contents for %s:\n", demoplayer->GetDemoStream()->GetUrl());
  3355. }
  3356. CON_COMMAND( demo_timescale, "Sets demo replay speed." )
  3357. {
  3358. float fScale = 1.0f;
  3359. if ( args.ArgC() == 2 )
  3360. {
  3361. fScale = atof( args[1] );
  3362. fScale = clamp( fScale, 0.0f, 100.0f );
  3363. }
  3364. demoplayer->SetPlaybackTimeScale( fScale );
  3365. }
  3366. CON_COMMAND( demo_listimportantticks, "List all important ticks in the demo." )
  3367. {
  3368. demoplayer->ListImportantTicks();
  3369. }
  3370. CON_COMMAND( demo_listhighlights, "List all highlights data for the demo." )
  3371. {
  3372. demoplayer->ListHighlightData();
  3373. }
  3374. bool CDemoPlayer::OverrideView( democmdinfo_t& info )
  3375. {
  3376. #if !defined( LINUX )
  3377. if ( g_pDemoUI && g_pDemoUI->OverrideView( info, GetPlaybackTick() ) )
  3378. return true;
  3379. if ( demoaction && demoaction->OverrideView( info, GetPlaybackTick() ) )
  3380. return true;
  3381. #endif
  3382. return false;
  3383. }
  3384. void CDemoPlayer::ResetDemoInterpolation( void )
  3385. {
  3386. m_bResetInterpolation = true;
  3387. }