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.

538 lines
16 KiB

  1. //=========== Copyright Valve Corporation, All rights reserved. ==============//
  2. #include "cbase.h"
  3. #include "fatdemo.h"
  4. #include "baseplayer_shared.h"
  5. #include "cs_gamerules.h"
  6. #include "gametypes/igametypes.h"
  7. #ifdef CLIENT_DLL
  8. #include "c_team.h"
  9. #include "c_playerresource.h"
  10. #include "c_cs_player.h"
  11. #include "c_cs_playerresource.h"
  12. #else
  13. #include "team.h"
  14. #include "cs_player.h"
  15. #include "cs_player_resource.h"
  16. #endif
  17. #include "weapon_csbase.h"
  18. #include "cs_weapon_parse.h"
  19. #include "proto_oob.h" // For MAKE_4BYTES
  20. // memdbgon must be the last include file in a .cpp file!!!
  21. #include "tier0/memdbgon.h"
  22. #if !defined( CSTRIKE_REL_BUILD )
  23. // Globals
  24. CCSFatDemoRecorder g_fatDemoRecorder;
  25. CCSFatDemoRecorder *g_pFatDemoRecorder = &g_fatDemoRecorder;
  26. ConVar csgo_fatdemo_enable( "csgo_fatdemo_enable", "0", FCVAR_RELEASE );
  27. ConVar csgo_fatdemo_output( "csgo_fatdemo_output", "test.fatdem", FCVAR_RELEASE );
  28. // The file structure is thus:
  29. // FatDemoHeader
  30. // for each protobuf message:
  31. // size of message
  32. // protobuf message
  33. struct FatDemoHeader
  34. {
  35. uint32 m_magic; // Must be characters "GOML",
  36. uint32 m_version; // Which version of the header. Protobuf mechanisms are used for the actual payloads.
  37. };
  38. // ------------------------------------------------------------------------------------------------
  39. // ------------------------------------------------------------------------------------------------
  40. // ------------------------------------------------------------------------------------------------
  41. void CaptureGameState( MLGameState* pOutState );
  42. void CaptureMatchState( MLMatchState* pOutState );
  43. void CaptureRoundState( MLRoundState* pOutState );
  44. void CapturePlayerState( MLPlayerState* pOutState, CCSPlayer* pCsPlayer );
  45. void CaptureWeaponState( MLWeaponState* pOutState, CWeaponCSBase* pCsWeapon, int index, CCSPlayer* pCsPlayer );
  46. // ------------------------------------------------------------------------------------------------
  47. // ------------------------------------------------------------------------------------------------
  48. // ------------------------------------------------------------------------------------------------
  49. class CCSFatDemoEventVisitor : public IGameEventVisitor2
  50. {
  51. public:
  52. CCSFatDemoEventVisitor( MLEvent* pEvent )
  53. : m_pEvent( pEvent )
  54. {}
  55. // IGameEventVisitor2
  56. virtual bool VisitString( const char* name, const char* value ) OVERRIDE
  57. {
  58. MLDict* pEntry = m_pEvent->add_data();
  59. pEntry->set_key( name );
  60. pEntry->set_val_string( value );
  61. return true;
  62. }
  63. virtual bool VisitFloat( const char* name, float value ) OVERRIDE
  64. {
  65. MLDict* pEntry = m_pEvent->add_data();
  66. pEntry->set_key( name );
  67. pEntry->set_val_float( value );
  68. return true;
  69. }
  70. virtual bool VisitInt( const char* name, int value ) OVERRIDE
  71. {
  72. MLDict* pEntry = m_pEvent->add_data();
  73. pEntry->set_key( name );
  74. pEntry->set_val_int( value );
  75. return true;
  76. }
  77. virtual bool VisitBool( const char*name, bool value ) OVERRIDE
  78. {
  79. MLDict* pEntry = m_pEvent->add_data();
  80. pEntry->set_key( name );
  81. pEntry->set_val_int( value ? 1 : 0 );
  82. return true;
  83. }
  84. private:
  85. MLEvent* m_pEvent;
  86. };
  87. // ------------------------------------------------------------------------------------------------
  88. // ------------------------------------------------------------------------------------------------
  89. // ------------------------------------------------------------------------------------------------
  90. CCSFatDemoRecorder::CCSFatDemoRecorder()
  91. : m_tickcount( -1 )
  92. , m_bInLevel( false )
  93. , m_pCurrentTick( NULL )
  94. {
  95. }
  96. // ------------------------------------------------------------------------------------------------
  97. CCSFatDemoRecorder::~CCSFatDemoRecorder()
  98. {
  99. }
  100. // ------------------------------------------------------------------------------------------------
  101. void CCSFatDemoRecorder::Reset()
  102. {
  103. // Sync up the state of our trackers with the current state of the game.
  104. }
  105. // ------------------------------------------------------------------------------------------------
  106. void CCSFatDemoRecorder::FireGameEvent( IGameEvent *pEvent )
  107. {
  108. if ( !csgo_fatdemo_enable.GetBool() )
  109. return;
  110. if ( !m_pCurrentTick )
  111. return;
  112. MLEvent* pOutEvent = m_pCurrentTick->add_events();
  113. pOutEvent->set_event_name( pEvent->GetName() );
  114. CCSFatDemoEventVisitor visitor( pOutEvent );
  115. pEvent->ForEventData( &visitor );
  116. }
  117. // ------------------------------------------------------------------------------------------------
  118. void CCSFatDemoRecorder::PostInit()
  119. {
  120. ListenForAllGameEvents();
  121. }
  122. // ------------------------------------------------------------------------------------------------
  123. void CCSFatDemoRecorder::LevelInitPreEntity()
  124. {
  125. BeginFile();
  126. m_bInLevel = true;
  127. m_tickcount = -1;
  128. }
  129. // ------------------------------------------------------------------------------------------------
  130. void CCSFatDemoRecorder::LevelShutdownPostEntity()
  131. {
  132. #ifdef _LINUX
  133. bool bWasInLevel = m_bInLevel;
  134. #endif
  135. m_bInLevel = false;
  136. FinalizeFile();
  137. // Clean up our temp memory.
  138. m_tempPacketStorage.Purge();
  139. if ( m_pCurrentTick )
  140. {
  141. delete m_pCurrentTick;
  142. m_pCurrentTick = NULL;
  143. }
  144. // There's an ugly crash in the bowels of scaleform that makes it hard for us to tell whether
  145. // CSGO was actually successful or not. However, at this point we have been successful, so we
  146. // should go ahead and exit with a success code if we're in demo_quitafterplayback mode (which
  147. // is the usual case for autonomous capture.
  148. #ifdef _LINUX
  149. static ConVarRef demo_quitafterplayback( "demo_quitafterplayback" );
  150. if ( bWasInLevel && demo_quitafterplayback.GetBool() )
  151. {
  152. _exit( 0 );
  153. }
  154. #endif
  155. }
  156. // ------------------------------------------------------------------------------------------------
  157. void CCSFatDemoRecorder::OnTickPre( int tickcount )
  158. {
  159. if ( !csgo_fatdemo_enable.GetBool() )
  160. return;
  161. // Guard against multiple updates in the client if we're running a demo that isn't a timedemo.
  162. if ( m_tickcount == tickcount )
  163. return;
  164. if ( !m_bInLevel )
  165. return;
  166. if ( !m_outFile )
  167. return;
  168. Assert( CSGameRules() );
  169. if ( m_pCurrentTick )
  170. {
  171. m_pCurrentTick->set_tick_count( tickcount );
  172. CaptureGameState( m_pCurrentTick->mutable_state() );
  173. OutputProtobuf( m_pCurrentTick );
  174. // TODO: This should be serialized or written out to a queue or something.
  175. delete m_pCurrentTick;
  176. m_pCurrentTick = NULL;
  177. }
  178. // Set up the current tick for next tick. We do this here so that any events captured from now
  179. // until then affect the next tick (since we're done with this tick).
  180. m_pCurrentTick = new MLTick;
  181. // We've updated for this tick now.
  182. m_tickcount = tickcount;
  183. }
  184. // ------------------------------------------------------------------------------------------------
  185. void CCSFatDemoRecorder::OutputProtobuf( ::google::protobuf::Message* pProto )
  186. {
  187. Assert( pProto );
  188. int32 size = pProto->ByteSize();
  189. int32 totalSize = size + sizeof( int32 );
  190. m_tempPacketStorage.EnsureCapacity( totalSize );
  191. *( ( int32* ) m_tempPacketStorage.Base() ) = size;
  192. if ( !pProto->SerializeToArray( ( ( byte* ) m_tempPacketStorage.Base() ) + sizeof( int ), size ) )
  193. {
  194. Assert( !"Serialization failed for... reasons." );
  195. return;
  196. }
  197. g_pFullFileSystem->Write( m_tempPacketStorage.Base(), totalSize, m_outFile );
  198. }
  199. // ------------------------------------------------------------------------------------------------
  200. void CCSFatDemoRecorder::BeginFile()
  201. {
  202. char buffer[MAX_PATH];
  203. V_strcpy_safe( buffer, csgo_fatdemo_output.GetString() );
  204. V_DefaultExtension( buffer, ".fatdem", sizeof( buffer ) );
  205. m_outFile = g_pFullFileSystem->OpenEx( buffer, "wb" );
  206. if ( !m_outFile )
  207. return;
  208. FatDemoHeader header;
  209. header.m_magic = MAKE_4BYTES( 'G', 'O', 'M', 'L' );
  210. header.m_version = 1;
  211. g_pFullFileSystem->Write( &header, sizeof( header ), m_outFile );
  212. // Now the protobuf header,
  213. MLDemoHeader protoHeader;
  214. #ifdef CLIENT_DLL
  215. protoHeader.set_map_name( engine->GetLevelNameShort() );
  216. #else
  217. protoHeader.set_map_name( gpGlobals->mapname.ToCStr() );
  218. #endif
  219. if ( gpGlobals->interval_per_tick != 0.0f )
  220. protoHeader.set_tick_rate(1 / gpGlobals->interval_per_tick );
  221. #ifdef CLIENT_DLL
  222. protoHeader.set_version( engine->GetClientVersion() );
  223. #else
  224. protoHeader.set_version( engine->GetServerVersion() );
  225. #endif
  226. #ifndef NO_STEAM
  227. EUniverse eUniverse = steamapicontext && steamapicontext->SteamUtils()
  228. ? steamapicontext->SteamUtils()->GetConnectedUniverse()
  229. : k_EUniverseInvalid;
  230. protoHeader.set_steam_universe( ( int ) eUniverse );
  231. #else
  232. // Pretty sure this doesn't actually work anymore.
  233. protoHeader.set_steam_universe( -1 );
  234. #endif
  235. OutputProtobuf( &protoHeader );
  236. }
  237. // ------------------------------------------------------------------------------------------------
  238. void CCSFatDemoRecorder::FinalizeFile()
  239. {
  240. if ( m_outFile )
  241. g_pFullFileSystem->Close( m_outFile );
  242. m_outFile = 0;
  243. }
  244. // ------------------------------------------------------------------------------------------------
  245. // ------------------------------------------------------------------------------------------------
  246. // ------------------------------------------------------------------------------------------------
  247. void CaptureGameState( MLGameState* pOutState )
  248. {
  249. CaptureMatchState( pOutState->mutable_match() );
  250. CaptureRoundState( pOutState->mutable_round() );
  251. for ( int i = 1; i < MAX_PLAYERS; ++i )
  252. {
  253. CCSPlayer* pPlayer = dynamic_cast< CCSPlayer* >( UTIL_PlayerByIndex( i ) );
  254. if ( !pPlayer )
  255. continue;
  256. CapturePlayerState( pOutState->add_players(), pPlayer );
  257. }
  258. }
  259. // ------------------------------------------------------------------------------------------------
  260. void CaptureMatchState( MLMatchState* pOutState )
  261. {
  262. char const *szGameMode = g_pGameTypes->GetGameModeFromInt( g_pGameTypes->GetCurrentGameType(), g_pGameTypes->GetCurrentGameMode() );
  263. if ( !szGameMode || !*szGameMode )
  264. szGameMode = "custom";
  265. pOutState->set_game_mode( szGameMode );
  266. char const *szPhase = "warmup";
  267. bool bActivePhase = false;
  268. if ( !CSGameRules()->IsWarmupPeriod() )
  269. {
  270. bActivePhase = true;
  271. switch ( CSGameRules()->GetGamePhase() )
  272. {
  273. case GAMEPHASE_HALFTIME:
  274. szPhase = "intermission";
  275. break;
  276. case GAMEPHASE_MATCH_ENDED:
  277. szPhase = "gameover";
  278. break;
  279. default:
  280. szPhase = "live";
  281. break;
  282. }
  283. }
  284. pOutState->set_phase( szPhase );
  285. if ( bActivePhase )
  286. pOutState->set_round( CSGameRules()->GetTotalRoundsPlayed() );
  287. int nTeams[2] = { TEAM_CT, TEAM_TERRORIST };
  288. int nScores[ 2 ] = { 0, 0 };
  289. for ( int j = 0; j < 2; ++j )
  290. {
  291. auto *pTeam = GetGlobalTeam( nTeams[ j ] );
  292. if ( !pTeam )
  293. continue;
  294. #ifdef CLIENT_DLL
  295. nScores[ j ] = pTeam->Get_Score();
  296. #else
  297. nScores[ j ] = pTeam->GetScore();
  298. #endif
  299. }
  300. pOutState->set_score_ct( nScores[ 0 ] );
  301. pOutState->set_score_t( nScores[ 1 ] );
  302. }
  303. // ------------------------------------------------------------------------------------------------
  304. void CaptureRoundState( MLRoundState* pOutState )
  305. {
  306. char const *szPhase = "freezetime";
  307. if ( !CSGameRules()->IsFreezePeriod() )
  308. {
  309. if ( CSGameRules()->IsRoundOver() )
  310. szPhase = "over";
  311. else
  312. szPhase = "live";
  313. }
  314. pOutState->set_phase( szPhase );
  315. switch ( CSGameRules()->m_iRoundWinStatus )
  316. {
  317. case WINNER_CT:
  318. pOutState->set_win_team( ET_CT );
  319. break;
  320. case WINNER_TER:
  321. pOutState->set_win_team( ET_Terrorist );
  322. break;
  323. }
  324. if ( CSGameRules()->IsBombDefuseMap() )
  325. {
  326. char const *szBombState = "";
  327. if ( CSGameRules()->m_bBombPlanted && !CSGameRules()->IsRoundOver() )
  328. szBombState = "planted";
  329. if ( CSGameRules()->IsRoundOver() )
  330. {
  331. // Check if the bomb exploded or got defused?
  332. switch ( CSGameRules()->m_eRoundWinReason )
  333. {
  334. case Target_Bombed:
  335. szBombState = "exploded";
  336. break;
  337. case Bomb_Defused:
  338. szBombState = "defused";
  339. break;
  340. }
  341. }
  342. if ( *szBombState )
  343. pOutState->set_bomb_state( szBombState );
  344. }
  345. }
  346. // ------------------------------------------------------------------------------------------------
  347. static void DemoSetVector( CMsgVector* pOutVec, const Vector& inVec )
  348. {
  349. Assert( pOutVec );
  350. pOutVec->set_x( inVec.x );
  351. pOutVec->set_y( inVec.y );
  352. pOutVec->set_z( inVec.z );
  353. }
  354. // ------------------------------------------------------------------------------------------------
  355. static void DemoSetQAngle( CMsgQAngle* pOutAng, const QAngle& inAng )
  356. {
  357. Assert( pOutAng );
  358. pOutAng->set_x( inAng.x );
  359. pOutAng->set_y( inAng.y );
  360. pOutAng->set_z( inAng.z );
  361. }
  362. // ------------------------------------------------------------------------------------------------
  363. static void DemoSetQAngleAndForward( CMsgQAngle* pOutAng, CMsgVector* pOutVec, const QAngle& inAng )
  364. {
  365. DemoSetQAngle( pOutAng, inAng );
  366. Vector fwd;
  367. AngleVectors( inAng, &fwd );
  368. DemoSetVector( pOutVec, fwd );
  369. }
  370. // ------------------------------------------------------------------------------------------------
  371. void CapturePlayerState( MLPlayerState* pOutState, CCSPlayer* pCsPlayer )
  372. {
  373. CSteamID steamID;
  374. if ( pCsPlayer->GetSteamID( &steamID ) )
  375. pOutState->set_account_id( steamID.GetAccountID() );
  376. pOutState->set_entindex( pCsPlayer->entindex() );
  377. pOutState->set_name( pCsPlayer->GetPlayerName() );
  378. // pOutState->set_clan( );
  379. pOutState->set_team( ( ETeam )( pCsPlayer->GetTeamNumber() ) );
  380. pOutState->set_user_id( pCsPlayer->GetUserID() );
  381. DemoSetVector( pOutState->mutable_abspos(), pCsPlayer->GetAbsOrigin() );
  382. DemoSetQAngleAndForward( pOutState->mutable_eyeangle(), pOutState->mutable_eyeangle_fwd(), pCsPlayer->EyeAngles() );
  383. pOutState->set_health( pCsPlayer->GetHealth() );
  384. pOutState->set_armor( pCsPlayer->ArmorValue() );
  385. #ifdef CLIENT_DLL
  386. pOutState->set_flashed( clamp( pCsPlayer->m_flFlashOverlayAlpha, 0.0f, 1.0f ) );
  387. pOutState->set_smoked( clamp( pCsPlayer->GetLastSmokeOverlayAlpha(), 0.0f, 1.0f ) );
  388. pOutState->set_money( pCsPlayer->GetAccount() );
  389. pOutState->set_helmet( pCsPlayer->HasHelmet() );
  390. #else
  391. // TODO pOutState->set_flashed( clamp( pCsPlayer->m_flFlashOverlayAlpha, 0.0f, 1.0f ) );
  392. // TODO pOutState->set_smoked( clamp( pCsPlayer->GetLastSmokeOverlayAlpha(), 0.0f, 1.0f ) );
  393. pOutState->set_money( pCsPlayer->m_iAccount );
  394. pOutState->set_helmet( pCsPlayer->m_bHasHelmet );
  395. #endif
  396. pOutState->set_round_kills( pCsPlayer->m_iNumRoundKills );
  397. pOutState->set_round_killhs( pCsPlayer->m_iNumRoundKillsHeadshots );
  398. pOutState->set_defuse_kit( pCsPlayer->HasDefuser() );
  399. float flOnFireAmount = 0.0f;
  400. if ( ( pCsPlayer->m_fMolotovDamageTime > 0.0f ) && ( gpGlobals->curtime - pCsPlayer->m_fMolotovDamageTime < 2 ) ) // took burn damage in last two seconds
  401. {
  402. flOnFireAmount = ( gpGlobals->curtime - pCsPlayer->m_fMolotovDamageTime <= 1.0f )
  403. ? 1.0f
  404. : ( 2.0f - gpGlobals->curtime + pCsPlayer->m_fMolotovDamageTime );
  405. }
  406. pOutState->set_burning( clamp( flOnFireAmount, 0.0f, 1.0f ) );
  407. int numWeapons = 0;
  408. for ( int i = 0; i < pCsPlayer->WeaponCount(); ++i )
  409. {
  410. CWeaponCSBase* pCsWeapon = dynamic_cast< CWeaponCSBase * >( pCsPlayer->GetWeapon( i ) );
  411. if ( !pCsWeapon )
  412. continue;
  413. CaptureWeaponState( pOutState->add_weapons(), pCsWeapon, numWeapons, pCsPlayer );
  414. ++numWeapons;
  415. }
  416. }
  417. // ------------------------------------------------------------------------------------------------
  418. void CaptureWeaponState( MLWeaponState* pOutState, CWeaponCSBase* pCsWeapon, int index, CCSPlayer* pCsPlayer )
  419. {
  420. pOutState->set_index( index );
  421. pOutState->set_name( pCsWeapon->GetName() );
  422. pOutState->set_type( ( EWeaponType ) pCsWeapon->GetWeaponType() );
  423. pOutState->set_recoil_index( pCsWeapon->m_flRecoilIndex );
  424. if ( pCsWeapon->m_iClip1 >= 0 )
  425. pOutState->set_ammo_clip( pCsWeapon->m_iClip1 );
  426. int iMaxClip1 = pCsWeapon->GetMaxClip1();
  427. if ( iMaxClip1 > 0 )
  428. pOutState->set_ammo_clip_max( iMaxClip1 );
  429. if ( pCsWeapon->GetPrimaryAmmoType() >= 0 )
  430. pOutState->set_ammo_reserve( pCsWeapon->GetReserveAmmoCount( AMMO_POSITION_PRIMARY ) );
  431. char const *szState = "holstered";
  432. if ( pCsPlayer->GetActiveCSWeapon() == pCsWeapon )
  433. {
  434. szState = "active";
  435. if ( pCsWeapon->m_bInReload )
  436. szState = "reloading";
  437. }
  438. pOutState->set_state( szState );
  439. }
  440. #endif