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

720 lines
19 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose: Generic in-game abuse reporting
  4. //
  5. // $NoKeywords: $
  6. //=============================================================================//
  7. #include "cbase.h"
  8. #include "abuse_report.h"
  9. #include "abuse_report_ui.h"
  10. #include "filesystem.h"
  11. #include "imageutils.h"
  12. #include "econ/confirm_dialog.h"
  13. #include "econ/econ_notifications.h"
  14. inline bool IsLoggedOnToSteam()
  15. {
  16. return steamapicontext != NULL && steamapicontext->SteamUser() != NULL && steamapicontext->SteamUser()->BLoggedOn();
  17. }
  18. const char CAbuseReportManager::k_rchScreenShotFilenameBase[] = "abuse_report";
  19. const char CAbuseReportManager::k_rchScreenShotFilename[] = "screenshots\\abuse_report.jpg";
  20. //-----------------------------------------------------------------------------
  21. class CEconNotification_AbuseReportReady : public CEconNotification
  22. {
  23. public:
  24. CEconNotification_AbuseReportReady() : CEconNotification()
  25. {
  26. m_bHasTriggered = false;
  27. m_bShowInGame = false;
  28. }
  29. ~CEconNotification_AbuseReportReady()
  30. {
  31. //if ( !m_bHasTriggered )
  32. //{
  33. // ReallyTrigger();
  34. //}
  35. }
  36. virtual void MarkForDeletion()
  37. {
  38. m_bHasTriggered = true;
  39. CEconNotification::MarkForDeletion();
  40. }
  41. virtual bool BShowInGameElements() const { return m_bShowInGame; }
  42. virtual EType NotificationType() { return eType_Trigger; }
  43. virtual void Trigger()
  44. {
  45. ReallyTrigger();
  46. MarkForDeletion();
  47. }
  48. virtual const char *GetUnlocalizedHelpText()
  49. {
  50. return "#AbuseReport_Notification_Help";
  51. }
  52. static bool IsNotificationType( CEconNotification *pNotification ) { return dynamic_cast< CEconNotification_AbuseReportReady *>( pNotification ) != NULL; }
  53. static bool IsInGameNotificationType( CEconNotification *pNotification )
  54. {
  55. CEconNotification_AbuseReportReady *n = dynamic_cast< CEconNotification_AbuseReportReady *>( pNotification );
  56. return n != NULL && n->BShowInGameElements();
  57. }
  58. bool m_bShowInGame;
  59. private:
  60. void ReallyTrigger()
  61. {
  62. Assert( !m_bHasTriggered );
  63. m_bHasTriggered = true;
  64. engine->ClientCmd_Unrestricted( "abuse_report_submit" );
  65. }
  66. bool m_bHasTriggered;
  67. };
  68. AbuseIncidentData_t::AbuseIncidentData_t()
  69. {
  70. m_nScreenShotWaitFrames = 5;
  71. }
  72. AbuseIncidentData_t::~AbuseIncidentData_t()
  73. {
  74. }
  75. bool AbuseIncidentData_t::Poll()
  76. {
  77. bool bReady = true;
  78. // Poll player data
  79. for ( int i = 0 ; i < m_vecPlayers.Count() ; ++i )
  80. {
  81. // Make sure sure Steam knows we want the Avatar
  82. PlayerData_t *p = &m_vecPlayers[i];
  83. if ( p->m_iSteamAvatarIndex < 0 )
  84. {
  85. if ( steamapicontext && steamapicontext->SteamUser() )
  86. {
  87. p->m_iSteamAvatarIndex = steamapicontext->SteamFriends()->GetLargeFriendAvatar( p->m_steamID );
  88. if ( p->m_iSteamAvatarIndex < 0 )
  89. {
  90. bReady = false;
  91. }
  92. }
  93. else
  94. {
  95. p->m_iSteamAvatarIndex = 0;
  96. }
  97. }
  98. }
  99. // Screenshot ready?
  100. if ( !m_bitmapScreenshot.IsValid() && m_nScreenShotWaitFrames > 0 )
  101. {
  102. --m_nScreenShotWaitFrames;
  103. // Just load the whole file into a memory buffer
  104. char szFullPath[ MAX_PATH ] = "";
  105. if ( !g_pFullFileSystem->RelativePathToFullPath( CAbuseReportManager::k_rchScreenShotFilename, NULL, szFullPath, ARRAYSIZE(szFullPath) ) )
  106. {
  107. Assert( false ); // ???
  108. }
  109. // Load it
  110. if ( g_pFullFileSystem->FileExists( szFullPath ) )
  111. {
  112. // Load the screenshot into a local buffer
  113. if ( !g_pFullFileSystem->ReadFile( CAbuseReportManager::k_rchScreenShotFilename, NULL, m_bufScreenshotFileData ) )
  114. {
  115. Warning( "Failed to read back %s\n", CAbuseReportManager::k_rchScreenShotFilename );
  116. m_nScreenShotWaitFrames = 0;
  117. }
  118. else
  119. {
  120. ConversionErrorType nErrorCode = ImgUtl_LoadBitmap( szFullPath, m_bitmapScreenshot );
  121. if ( nErrorCode != CE_SUCCESS )
  122. {
  123. Warning( "Abuse report screenshot %s failed to load with error code %d\n", CAbuseReportManager::k_rchScreenShotFilename, nErrorCode );
  124. Assert( nErrorCode == CE_SUCCESS );
  125. m_nScreenShotWaitFrames = 0;
  126. }
  127. else
  128. {
  129. // !KLUDGE! Resize to power of two dimensions, since VGUI doesn't like odd sizes
  130. ImgUtl_ResizeBitmap( m_bitmapScreenshot, 1024, 1024, &m_bitmapScreenshot );
  131. }
  132. }
  133. g_pFullFileSystem->RemoveFile( CAbuseReportManager::k_rchScreenShotFilename );
  134. }
  135. }
  136. return bReady;
  137. }
  138. CAbuseReportManager *g_AbuseReportMgr;
  139. CAbuseReportManager::CAbuseReportManager()
  140. {
  141. m_pIncidentData = NULL;
  142. m_bTestReport = false;
  143. m_eIncidentDataStatus = k_EIncidentDataStatus_None;
  144. m_bReportUIPending = false;
  145. // We're the singleton --- set global pointer
  146. Assert( g_AbuseReportMgr == NULL );
  147. g_AbuseReportMgr = this;
  148. m_timeLastReportReadyNotification = 0.0;
  149. m_adrCurrentServer.Clear();
  150. }
  151. CAbuseReportManager::~CAbuseReportManager()
  152. {
  153. Assert( m_pIncidentData == NULL );
  154. }
  155. char const *CAbuseReportManager::Name()
  156. {
  157. return "AbuseRepotManager";
  158. }
  159. bool CAbuseReportManager::Init()
  160. {
  161. // Clean out any temporary files
  162. Assert( m_pIncidentData == NULL );
  163. DestroyIncidentData();
  164. ListenForGameEvent( "teamplay_round_win" );
  165. ListenForGameEvent( "tf_game_over" );
  166. ListenForGameEvent( "player_death" );
  167. ListenForGameEvent( "server_spawn" );
  168. return true;
  169. }
  170. void CAbuseReportManager::LevelShutdownPreEntity()
  171. {
  172. // Don't keep the dialog open across a level transition. Don't discard their
  173. // report data, but let's kill the dialog
  174. if ( g_AbuseReportDlg.Get() != NULL )
  175. {
  176. Warning( "Abuse report dialog open during level shutdown. Closing it.\n" );
  177. g_AbuseReportDlg.Get()->Close();
  178. }
  179. // And clear the 'pending' flag
  180. m_bReportUIPending = false;
  181. }
  182. void CAbuseReportManager::FireGameEvent( IGameEvent *event )
  183. {
  184. //C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer();
  185. const char *eventname = event->GetName();
  186. if ( !eventname || !eventname[0] )
  187. return;
  188. if (
  189. !Q_strcmp( "teamplay_round_win", eventname )
  190. || !Q_strcmp( "tf_game_over", eventname )
  191. ) {
  192. // Periodically remind them that they have a report ready to file
  193. CheckCreateReportReadyNotification( 60.0 * 5.0, true, 10.0f );
  194. }
  195. else if ( !Q_strcmp( "player_death", eventname ) )
  196. {
  197. // In some maps, the round just never ends.
  198. // So make sure we do remind them every now and then about this.
  199. // Just not too often
  200. CheckCreateReportReadyNotification( 60.0 * 20.0, true, 5.0f );
  201. }
  202. else if ( !Q_strcmp( "server_spawn", eventname ) )
  203. {
  204. m_adrCurrentServer.Clear();
  205. m_adrCurrentServer.SetFromString( event->GetString( "address", "" ), false );
  206. m_adrCurrentServer.SetPort( event->GetInt( "port", 0 ) );
  207. m_steamIDCurrentServer = CSteamID();
  208. if ( steamapicontext && steamapicontext->SteamUser() && GetUniverse() != k_EUniverseInvalid )
  209. {
  210. m_steamIDCurrentServer.SetFromString( event->GetString( "steamid", "" ), GetUniverse() );
  211. }
  212. }
  213. }
  214. void CAbuseReportManager::Shutdown()
  215. {
  216. // Close the dialog, if any
  217. LevelShutdownPreEntity();
  218. DestroyIncidentData();
  219. // Clear global pointer
  220. Assert( g_AbuseReportMgr == this );
  221. if ( g_AbuseReportMgr == this )
  222. {
  223. g_AbuseReportMgr = NULL;
  224. }
  225. }
  226. void CAbuseReportManager::Update( float frametime )
  227. {
  228. // if a dialog is already displayed, make sure we don't try to activate another
  229. if ( g_AbuseReportDlg.Get() != NULL )
  230. {
  231. m_bReportUIPending = false;
  232. }
  233. // Poll report data, if any
  234. if ( m_pIncidentData != NULL )
  235. {
  236. if ( m_eIncidentDataStatus == k_EIncidentDataStatus_Preparing )
  237. {
  238. if ( m_pIncidentData->Poll() )
  239. {
  240. m_eIncidentDataStatus = k_EIncidentDataStatus_Ready;
  241. CheckCreateReportReadyNotification( 1.0f, true, 7.0f );
  242. }
  243. }
  244. else
  245. {
  246. Assert( m_eIncidentDataStatus == k_EIncidentDataStatus_Ready );
  247. }
  248. if ( m_eIncidentDataStatus == k_EIncidentDataStatus_Ready && m_bReportUIPending )
  249. {
  250. m_bReportUIPending = false;
  251. ActivateSubmitReportUI();
  252. }
  253. }
  254. else
  255. {
  256. m_bReportUIPending = false;
  257. }
  258. // Re-create notification constantly in the menu.
  259. // While in game, we will only popup notifications
  260. // periodically at round end or player death
  261. CheckCreateReportReadyNotification( 10.0, false, 999.0f );
  262. }
  263. void CAbuseReportManager::SubmitReportUIRequested()
  264. {
  265. if ( g_AbuseReportDlg.Get() != NULL )
  266. {
  267. Assert( g_AbuseReportDlg.Get() == NULL );
  268. return;
  269. }
  270. // If no report data already, then create some
  271. if ( m_pIncidentData == NULL )
  272. {
  273. QueueReport();
  274. if ( m_pIncidentData == NULL )
  275. {
  276. // Failed
  277. return;
  278. }
  279. }
  280. // Set flag to bring up the reporting UI at earliest opportunity,
  281. // once all data has been fetched asynchronously
  282. m_bReportUIPending = true;
  283. }
  284. bool CAbuseReportManager::CreateAndPopulateIncident()
  285. {
  286. Assert( m_pIncidentData == NULL );
  287. // by default, just create the base class version
  288. m_pIncidentData = new AbuseIncidentData_t;
  289. // And populate it
  290. return PopulateIncident();
  291. }
  292. bool CAbuseReportManager::PopulateIncident()
  293. {
  294. if ( m_pIncidentData == NULL )
  295. {
  296. Assert( m_pIncidentData );
  297. return false;
  298. }
  299. // Queue a screenshot
  300. CUtlString cmd;
  301. cmd.Format( "__screenshot_internal \"%s\"", k_rchScreenShotFilenameBase );
  302. engine->ClientCmd_Unrestricted( cmd );
  303. // Set status as preparing
  304. m_eIncidentDataStatus = k_EIncidentDataStatus_Preparing;
  305. m_pIncidentData->m_bCanReportGameServer = false;
  306. m_pIncidentData->m_adrGameServer.Clear();
  307. if (
  308. m_adrCurrentServer.IsValid()
  309. && !m_adrCurrentServer.IsLocalhost()
  310. && m_steamIDCurrentServer.IsValid()
  311. && ( !m_adrCurrentServer.IsReservedAdr() || m_steamIDCurrentServer.GetEUniverse() != k_EUniversePublic )
  312. )
  313. {
  314. m_pIncidentData->m_adrGameServer = m_adrCurrentServer;
  315. m_pIncidentData->m_steamIDGameServer = m_steamIDCurrentServer;
  316. m_pIncidentData->m_bCanReportGameServer = true;
  317. }
  318. m_pIncidentData->m_matWorldToClip = engine->WorldToScreenMatrix();
  319. // Add in players
  320. for (int i = 1 ; i <= gpGlobals->maxClients ; ++i )
  321. {
  322. CBasePlayer *player = UTIL_PlayerByIndex( i );
  323. #ifndef _DEBUG
  324. // Skip local players
  325. if ( player != NULL && player->IsLocalPlayer() )
  326. {
  327. continue;
  328. }
  329. #endif
  330. // Get player info from the engine. This works even if they haven't spawned yet.
  331. player_info_t pi;
  332. if ( !engine->GetPlayerInfo( i, &pi ) )
  333. {
  334. continue;
  335. }
  336. if ( pi.fakeplayer )
  337. {
  338. continue;
  339. }
  340. if ( pi.friendsID == 0 )
  341. {
  342. continue;
  343. }
  344. CSteamID steamID( pi.friendsID, 1, GetUniverse(), k_EAccountTypeIndividual );
  345. if ( !steamID.IsValid() )
  346. {
  347. Assert( steamID.IsValid() );
  348. continue;
  349. }
  350. int arrayIndex = m_pIncidentData->m_vecPlayers.AddToTail();
  351. AbuseIncidentData_t::PlayerData_t *p = &m_pIncidentData->m_vecPlayers[ arrayIndex ];
  352. p->m_iClientIndex = i;
  353. p->m_steamID = steamID;
  354. p->m_sPersona = pi.name;
  355. p->m_bHasEntity = false;
  356. p->m_bRenderBoundsValid = false;
  357. p->m_screenBoundsMin.x = p->m_screenBoundsMin.y = 1.0f;
  358. p->m_screenBoundsMax.x = p->m_screenBoundsMax.y = 0.0f;
  359. if ( player==NULL )
  360. {
  361. continue;
  362. }
  363. p->m_bHasEntity = true;
  364. player->GetRenderBounds( p->m_vecRenderBoundsMin, p->m_vecRenderBoundsMax );
  365. p->m_matModelToWorld.CopyFrom3x4( player->RenderableToWorldTransform() );
  366. MatrixMultiply( m_pIncidentData->m_matWorldToClip, p->m_matModelToWorld, p->m_matModelToClip );
  367. // Gather up screen extents
  368. p->m_bRenderBoundsValid = false;
  369. for ( int j = 0 ; j < 8 ; ++j )
  370. {
  371. // Get corner point in model space
  372. Vector4D modelCorner(
  373. ( j & 1 ) ? p->m_vecRenderBoundsMax.x : p->m_vecRenderBoundsMin.x,
  374. ( j & 2 ) ? p->m_vecRenderBoundsMax.y : p->m_vecRenderBoundsMin.y,
  375. ( j & 4 ) ? p->m_vecRenderBoundsMax.z : p->m_vecRenderBoundsMin.z,
  376. 1.0f
  377. );
  378. // Transform to clip space
  379. Vector4D clipCorner;
  380. Vector4DMultiply( p->m_matModelToClip, modelCorner, clipCorner );
  381. //Msg( "%6.3f, %6.3f, %6.3f, %6.3f\n", clipCorner[0], clipCorner[1], clipCorner[2], clipCorner[3] );
  382. // If all points behind near clip plane, don't try to
  383. // figure out screen space bounds
  384. if ( clipCorner[3] > .1f )
  385. {
  386. p->m_bRenderBoundsValid = true;
  387. }
  388. // Push w forward to "near clip plane"
  389. float w = MAX( clipCorner[3], .1f );
  390. // Divide by w to project, and convert normalized device coordinates
  391. // where the view volume is (-1...1), to normalized screen coords, where
  392. // they are from 0...1
  393. float x = ( clipCorner[0] / w + 1.0f ) / 2.0f;
  394. float y = ( -clipCorner[1] / w + 1.0f ) / 2.0f;
  395. p->m_screenBoundsMin.x = MIN( p->m_screenBoundsMin.x, x );
  396. p->m_screenBoundsMax.x = MAX( p->m_screenBoundsMax.x, x );
  397. p->m_screenBoundsMin.y = MIN( p->m_screenBoundsMin.y, y );
  398. p->m_screenBoundsMax.y = MAX( p->m_screenBoundsMax.y, y );
  399. }
  400. // Clip projected rect to the screen
  401. if ( p->m_bRenderBoundsValid )
  402. {
  403. p->m_screenBoundsMin.x = MAX( p->m_screenBoundsMin.x, 0.0f );
  404. p->m_screenBoundsMax.x = MIN( p->m_screenBoundsMax.x, 1.0f );
  405. p->m_screenBoundsMin.y = MAX( p->m_screenBoundsMin.y, 0.0f );
  406. p->m_screenBoundsMax.y = MIN( p->m_screenBoundsMax.y, 1.0f );
  407. p->m_bRenderBoundsValid =
  408. p->m_screenBoundsMin.x + .01f < p->m_screenBoundsMax.x
  409. && p->m_screenBoundsMin.y + .01f < p->m_screenBoundsMax.y;
  410. }
  411. // Sanity check that we agree on what their steam ID is!
  412. if ( player->GetSteamID( &steamID ) )
  413. {
  414. Assert( p->m_steamID == steamID );
  415. }
  416. }
  417. // Test harness: add in a handful of fake players
  418. #ifdef _DEBUG
  419. if ( m_bTestReport )
  420. {
  421. int arrayIndex = m_pIncidentData->m_vecPlayers.AddToTail();
  422. AbuseIncidentData_t::PlayerData_t *p = &m_pIncidentData->m_vecPlayers[ arrayIndex ];
  423. p->m_iClientIndex = -1;
  424. p->m_sPersona = "Lippencott";
  425. p->m_steamID.SetFromUint64( 148618791998333672 );
  426. arrayIndex = m_pIncidentData->m_vecPlayers.AddToTail();
  427. p = &m_pIncidentData->m_vecPlayers[ arrayIndex ];
  428. p->m_iClientIndex = -1;
  429. p->m_sPersona = "EricS";
  430. p->m_steamID.SetFromUint64( 148618791998195668 );
  431. arrayIndex = m_pIncidentData->m_vecPlayers.AddToTail();
  432. p = &m_pIncidentData->m_vecPlayers[ arrayIndex ];
  433. p->m_iClientIndex = -1;
  434. p->m_sPersona = "Sarenya";
  435. p->m_steamID.SetFromUint64( 148618791998429832 );
  436. arrayIndex = m_pIncidentData->m_vecPlayers.AddToTail();
  437. p = &m_pIncidentData->m_vecPlayers[ arrayIndex ];
  438. p->m_iClientIndex = -1;
  439. p->m_sPersona = "fletch";
  440. p->m_steamID.SetFromUint64( 148618791998436114 );
  441. {
  442. AbuseIncidentData_t::PlayerImage_t img;
  443. img.m_eType = AbuseIncidentData_t::k_PlayerImageType_UGC;
  444. img.m_hUGCHandle = 6978249415967519;
  445. p->m_vecImages.AddToTail( img );
  446. }
  447. if ( !m_pIncidentData->m_bCanReportGameServer)
  448. {
  449. m_pIncidentData->m_adrGameServer.SetFromString( "123.45.67.89:27015", false );
  450. m_pIncidentData->m_steamIDGameServer = CSteamID( 12345, 0, GetUniverse(), k_EAccountTypeAnonGameServer );
  451. m_pIncidentData->m_bCanReportGameServer = true;
  452. }
  453. }
  454. #endif
  455. // Make sure there is at least one other person we could file a report against!
  456. if ( m_pIncidentData->m_vecPlayers.Count() < 1 )
  457. {
  458. Warning( "No players to accuse of abuse, cannot file report\n" );
  459. return false;
  460. }
  461. return true;
  462. }
  463. void CAbuseReportManager::DestroyIncidentData()
  464. {
  465. if ( m_pIncidentData != NULL )
  466. {
  467. delete m_pIncidentData;
  468. m_pIncidentData = NULL;
  469. }
  470. m_eIncidentDataStatus = k_EIncidentDataStatus_None;
  471. // Get rid of any existing screenshot file, both locally
  472. // and in the cloud. We don't want this to count against
  473. // our quota
  474. if ( steamapicontext && steamapicontext->SteamRemoteStorage() && steamapicontext->SteamRemoteStorage()->FileExists( k_rchScreenShotFilename ) )
  475. {
  476. steamapicontext->SteamRemoteStorage()->FileDelete( k_rchScreenShotFilename );
  477. }
  478. if ( g_pFullFileSystem->FileExists( k_rchScreenShotFilename ) ) // !KLUDGE! To prevent warning if the file doesn't exist!
  479. {
  480. g_pFullFileSystem->RemoveFile( k_rchScreenShotFilename );
  481. }
  482. m_timeLastReportReadyNotification = 0.0;
  483. // Make sure we don't have any notifications queued
  484. NotificationQueue_Remove( &CEconNotification_AbuseReportReady::IsNotificationType );
  485. }
  486. void CAbuseReportManager::QueueReport()
  487. {
  488. // Dialog is already active?
  489. if ( g_AbuseReportDlg.Get() != NULL )
  490. {
  491. Warning( "Cannot capture another incident report. Submission dialog is active.\n" );
  492. return;
  493. }
  494. // Destroy any existing data
  495. DestroyIncidentData();
  496. // Make sure we're logged on to Steam
  497. if ( !IsLoggedOnToSteam() )
  498. {
  499. g_AbuseReportMgr->ShowNoSteamErrorMessage();
  500. return;
  501. }
  502. if ( CreateAndPopulateIncident() )
  503. {
  504. Msg( "Captured data for abuse report.\n");
  505. }
  506. else
  507. {
  508. Warning( "Failed to captured data for abuse report.\n");
  509. DestroyIncidentData();
  510. }
  511. }
  512. void CAbuseReportManager::ShowNoSteamErrorMessage()
  513. {
  514. ShowMessageBox( "#AbuseReport_NoSteamTitle", "#AbuseReport_NoSteamMessage", "#GameUI_OK" );
  515. }
  516. void CAbuseReportManager::CheckCreateReportReadyNotification( float flMinSecondsSinceLastNotification, bool bInGame, float flLifetime )
  517. {
  518. // We have to have some data ready
  519. if ( m_pIncidentData == NULL || m_eIncidentDataStatus != k_EIncidentDataStatus_Ready )
  520. {
  521. return;
  522. }
  523. // Don't pester them if they are already trying to do something about it
  524. if ( g_AbuseReportDlg.Get() != NULL || m_bReportUIPending )
  525. {
  526. return;
  527. }
  528. // Already notified them too recently?
  529. if ( m_timeLastReportReadyNotification != 0.0 && Plat_FloatTime() < m_timeLastReportReadyNotification + flMinSecondsSinceLastNotification )
  530. {
  531. return;
  532. }
  533. // Already a notification in the queue?
  534. if ( bInGame )
  535. {
  536. if ( NotificationQueue_Count( &CEconNotification_AbuseReportReady::IsInGameNotificationType ) > 0 )
  537. {
  538. return;
  539. }
  540. }
  541. else
  542. {
  543. if ( NotificationQueue_Count( &CEconNotification_AbuseReportReady::IsNotificationType ) > 0 )
  544. {
  545. return;
  546. }
  547. }
  548. CreateReportReadyNotification( bInGame, flLifetime );
  549. }
  550. void CAbuseReportManager::CreateReportReadyNotification( bool bInGame, float flLifetime )
  551. {
  552. NotificationQueue_Remove( &CEconNotification_AbuseReportReady::IsNotificationType );
  553. CEconNotification_AbuseReportReady *pNotification = new CEconNotification_AbuseReportReady();
  554. pNotification->SetText( "AbuseReport_Notification" );
  555. pNotification->SetLifetime( flLifetime );
  556. pNotification->m_bShowInGame = bInGame;
  557. NotificationQueue_Add( pNotification );
  558. m_timeLastReportReadyNotification = Plat_FloatTime();
  559. }
  560. CON_COMMAND_F( abuse_report_queue, "Capture data for abuse report and queue for submission. Use abose_report_submit to activate UI to submit the report", FCVAR_DONTRECORD )
  561. {
  562. if ( !g_AbuseReportMgr )
  563. {
  564. Warning( "abuse_report_queue: No abuse report manager, cannot create report.\n" );
  565. return;
  566. }
  567. g_AbuseReportMgr->QueueReport();
  568. }
  569. CON_COMMAND_F( abuse_report_submit, "Activate UI to submit queued report. Use abuse_report_queue to capture data for the report the report", FCVAR_DONTRECORD )
  570. {
  571. if ( !g_AbuseReportMgr )
  572. {
  573. Warning( "abuse_report_submit: No abuse report manager, cannot submit report.\n" );
  574. return;
  575. }
  576. // Make sure we're logged on to Steam
  577. if ( !IsLoggedOnToSteam() )
  578. {
  579. g_AbuseReportMgr->ShowNoSteamErrorMessage();
  580. return;
  581. }
  582. if ( g_AbuseReportDlg.Get() != NULL )
  583. {
  584. // Dialog is already active
  585. return;
  586. }
  587. g_AbuseReportMgr->SubmitReportUIRequested();
  588. }
  589. // Test harness
  590. #ifdef _DEBUG
  591. CON_COMMAND_F( abuse_report_test, "Make a test abuse incident and activate UI", FCVAR_DONTRECORD )
  592. {
  593. if ( !g_AbuseReportMgr )
  594. {
  595. Assert( g_AbuseReportMgr );
  596. return;
  597. }
  598. g_AbuseReportMgr->m_bTestReport = true;
  599. g_AbuseReportMgr->QueueReport();
  600. g_AbuseReportMgr->m_bTestReport = false;
  601. engine->ClientCmd_Unrestricted( "abuse_report_submit" );
  602. }
  603. #endif