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.

607 lines
16 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //=============================================================================//
  7. #include "client_pch.h"
  8. #include "ivideomode.h"
  9. // memdbgon must be the last include file in a .cpp file!!!
  10. #include "tier0/memdbgon.h"
  11. ConVar cl_entityreport( "cl_entityreport", "0", FCVAR_CHEAT, "For debugging, draw entity states to console" );
  12. ConVar cl_entityreport_sorted( "cl_entityreport_sorted", "0", FCVAR_CHEAT, "For debugging, draw entity states to console in sorted order. [0 = disabled, 1 = average, 2 = current, 3 = peak" );
  13. enum
  14. {
  15. ENTITYSORT_NONE = 0,
  16. ENTITYSORT_AVG = 1,
  17. ENTITYSORT_CURRENT = 2,
  18. ENTITYSORT_PEAK = 3,
  19. };
  20. // How quickly to move rolling average for entityreport
  21. #define BITCOUNT_AVERAGE 0.95f
  22. // How long to flush item when something important happens
  23. #define EFFECT_TIME 1.5f
  24. // How long to latch peak bit count for item
  25. #define PEAK_LATCH_TIME 2.0f;
  26. //-----------------------------------------------------------------------------
  27. // Purpose: Entity report event types
  28. //-----------------------------------------------------------------------------
  29. enum
  30. {
  31. FENTITYBITS_ZERO = 0,
  32. FENTITYBITS_ADD = 0x01,
  33. FENTITYBITS_LEAVEPVS = 0x02,
  34. FENTITYBITS_DELETE = 0x04,
  35. };
  36. //-----------------------------------------------------------------------------
  37. // Purpose: Data about an entity
  38. //-----------------------------------------------------------------------------
  39. typedef struct
  40. {
  41. // Bits used for last message
  42. int bits;
  43. // Rolling average of bits used
  44. float average;
  45. // Last bit peak
  46. int peak;
  47. // Time at which peak was last reset
  48. float peaktime;
  49. // Event info
  50. int flags;
  51. // If doing effect, when it will finish
  52. float effectfinishtime;
  53. // If event was deletion, remember client class for a little bit
  54. ClientClass *deletedclientclass;
  55. } ENTITYBITS;
  56. // List of entities we are keeping data bout
  57. static ENTITYBITS s_EntityBits[ MAX_EDICTS ];
  58. // Used to sort by average
  59. int CompareEntityBits(const void* pIndexA, const void* pIndexB )
  60. {
  61. int indexA = *(int*)pIndexA;
  62. int indexB = *(int*)pIndexB;
  63. ENTITYBITS *pEntryA = &s_EntityBits[indexA];
  64. ENTITYBITS *pEntryB = &s_EntityBits[indexB];
  65. /*
  66. if ( pEntryA->flags == FENTITYBITS_ZERO )
  67. {
  68. if ( pEntryB->flags == FENTITYBITS_ZERO )
  69. {
  70. return 0;
  71. }
  72. return 1;
  73. }
  74. else if ( pEntryB->flags == FENTITYBITS_ZERO )
  75. {
  76. return -1;
  77. }
  78. */
  79. // sort dormant, out-of-pvs to the end
  80. IClientNetworkable *pNetA = entitylist->GetClientNetworkable( indexA );
  81. IClientNetworkable *pNetB = entitylist->GetClientNetworkable( indexB );
  82. bool bDormantA = pNetA == NULL || pNetA->IsDormant();
  83. bool bDormantB = pNetB == NULL || pNetB->IsDormant();
  84. if ( bDormantA != bDormantB )
  85. {
  86. return bDormantA ? 1 : -1;
  87. }
  88. switch ( cl_entityreport_sorted.GetInt() )
  89. {
  90. case ENTITYSORT_AVG:
  91. if ( pEntryA->average > pEntryB->average )
  92. {
  93. return -1;
  94. }
  95. if ( pEntryA->average < pEntryB->average )
  96. {
  97. return 1;
  98. }
  99. break;
  100. case ENTITYSORT_CURRENT:
  101. if ( pEntryA->bits > pEntryB->bits )
  102. {
  103. return -1;
  104. }
  105. if ( pEntryA->bits < pEntryB->bits )
  106. {
  107. return 1;
  108. }
  109. break;
  110. case ENTITYSORT_PEAK:
  111. default:
  112. if ( pEntryA->peak > pEntryB->peak )
  113. {
  114. return -1;
  115. }
  116. if ( pEntryA->peak < pEntryB->peak )
  117. {
  118. return 1;
  119. }
  120. }
  121. return 0;
  122. }
  123. //-----------------------------------------------------------------------------
  124. // Purpose: Zero out structure ( level transition/startup )
  125. //-----------------------------------------------------------------------------
  126. void CL_ResetEntityBits( void )
  127. {
  128. memset( s_EntityBits, 0, sizeof( s_EntityBits ) );
  129. }
  130. //-----------------------------------------------------------------------------
  131. // Purpose: Record activity
  132. // Input : entnum -
  133. // bitcount -
  134. //-----------------------------------------------------------------------------
  135. void CL_RecordEntityBits( int entnum, int bitcount )
  136. {
  137. if ( entnum < 0 || entnum >= MAX_EDICTS )
  138. {
  139. return;
  140. }
  141. ENTITYBITS *slot = &s_EntityBits[ entnum ];
  142. slot->bits = bitcount;
  143. // Update average
  144. slot->average = ( BITCOUNT_AVERAGE ) * slot->average + ( 1.f - BITCOUNT_AVERAGE ) * bitcount;
  145. // Recompute peak
  146. if ( realtime >= slot->peaktime )
  147. {
  148. slot->peak = 0.0f;
  149. slot->peaktime = realtime + PEAK_LATCH_TIME;
  150. }
  151. // Store off peak
  152. if ( bitcount > slot->peak )
  153. {
  154. slot->peak = bitcount;
  155. }
  156. }
  157. //-----------------------------------------------------------------------------
  158. // Purpose: Record entity add event
  159. // Input : entnum -
  160. //-----------------------------------------------------------------------------
  161. void CL_RecordAddEntity( int entnum )
  162. {
  163. if ( !cl_entityreport.GetBool() || entnum < 0 || entnum >= MAX_EDICTS )
  164. {
  165. return;
  166. }
  167. ENTITYBITS *slot = &s_EntityBits[ entnum ];
  168. slot->flags = FENTITYBITS_ADD;
  169. slot->effectfinishtime = realtime + EFFECT_TIME;
  170. }
  171. //-----------------------------------------------------------------------------
  172. // Purpose: record entity leave event
  173. // Input : entnum -
  174. //-----------------------------------------------------------------------------
  175. void CL_RecordLeavePVS( int entnum )
  176. {
  177. if ( !cl_entityreport.GetBool() || entnum < 0 || entnum >= MAX_EDICTS )
  178. {
  179. return;
  180. }
  181. ENTITYBITS *slot = &s_EntityBits[ entnum ];
  182. slot->flags = FENTITYBITS_LEAVEPVS;
  183. slot->effectfinishtime = realtime + EFFECT_TIME;
  184. }
  185. //-----------------------------------------------------------------------------
  186. // Purpose: record entity deletion event
  187. // Input : entnum -
  188. // *pclass -
  189. //-----------------------------------------------------------------------------
  190. void CL_RecordDeleteEntity( int entnum, ClientClass *pclass )
  191. {
  192. if ( !cl_entityreport.GetBool() || entnum < 0 || entnum >= MAX_EDICTS )
  193. {
  194. return;
  195. }
  196. ENTITYBITS *slot = &s_EntityBits[ entnum ];
  197. slot->flags = FENTITYBITS_DELETE;
  198. slot->effectfinishtime = realtime + EFFECT_TIME;
  199. slot->deletedclientclass = pclass;
  200. }
  201. //-----------------------------------------------------------------------------
  202. // Purpose: Shows entity status report if cl_entityreport cvar is set
  203. //-----------------------------------------------------------------------------
  204. class CEntityReportPanel : public CBasePanel
  205. {
  206. typedef CBasePanel BaseClass;
  207. public:
  208. // Construction
  209. CEntityReportPanel( vgui::Panel *parent );
  210. virtual ~CEntityReportPanel( void );
  211. // Refresh
  212. virtual void Paint();
  213. virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
  214. virtual bool ShouldDraw( void );
  215. // Helpers
  216. void ApplyEffect( ENTITYBITS *entry, int& r, int& g, int& b );
  217. bool DrawEntry( int row, int col, int rowheight, int colwidth, int entityIdx );
  218. private:
  219. // Font to use for drawing
  220. vgui::HFont m_hFont;
  221. };
  222. static CEntityReportPanel *g_pEntityReportPanel = NULL;
  223. //-----------------------------------------------------------------------------
  224. // Purpose: Creates the CEntityReportPanel VGUI panel
  225. // Input : *parent -
  226. //-----------------------------------------------------------------------------
  227. void CL_CreateEntityReportPanel( vgui::Panel *parent )
  228. {
  229. g_pEntityReportPanel = new CEntityReportPanel( parent );
  230. }
  231. //-----------------------------------------------------------------------------
  232. // Purpose: Instances the entity report panel
  233. // Input : *parent -
  234. //-----------------------------------------------------------------------------
  235. CEntityReportPanel::CEntityReportPanel( vgui::Panel *parent ) :
  236. CBasePanel( parent, "CEntityReportPanel" )
  237. {
  238. // Need parent here, before loading up textures, so getSurfaceBase
  239. // will work on this panel ( it's null otherwise )
  240. SetSize( videomode->GetModeStereoWidth(), videomode->GetModeStereoHeight() );
  241. SetPos( 0, 0 );
  242. SetVisible( true );
  243. SetCursor( null );
  244. m_hFont = vgui::INVALID_FONT;
  245. SetFgColor( Color( 0, 0, 0, 255 ) );
  246. SetPaintBackgroundEnabled( false );
  247. SetPaintBorderEnabled(false);
  248. }
  249. //-----------------------------------------------------------------------------
  250. // Purpose:
  251. //-----------------------------------------------------------------------------
  252. CEntityReportPanel::~CEntityReportPanel( void )
  253. {
  254. }
  255. void CEntityReportPanel::ApplySchemeSettings( vgui::IScheme *pScheme )
  256. {
  257. BaseClass::ApplySchemeSettings( pScheme );
  258. // If you change this font, be sure to mark it with
  259. // $use_in_fillrate_mode in its .vmt file
  260. m_hFont = pScheme->GetFont( "DefaultVerySmall", false );
  261. Assert( m_hFont );
  262. }
  263. //-----------------------------------------------------------------------------
  264. // Purpose:
  265. // Output : Returns true on success, false on failure.
  266. //-----------------------------------------------------------------------------
  267. bool CEntityReportPanel::ShouldDraw( void )
  268. {
  269. if ( !cl_entityreport.GetInt() )
  270. {
  271. return false;
  272. }
  273. return true;
  274. }
  275. //-----------------------------------------------------------------------------
  276. // Purpose: Helper to flash colors
  277. // Input : cycle -
  278. // value -
  279. // Output : static int
  280. //-----------------------------------------------------------------------------
  281. static int MungeColorValue( float cycle, int& value )
  282. {
  283. int midpoint;
  284. int remaining;
  285. bool invert = false;
  286. if ( value < 128 )
  287. {
  288. invert = true;
  289. value = 255 - value;
  290. }
  291. midpoint = value / 2;
  292. remaining = value - midpoint;
  293. midpoint = midpoint + remaining / 2;
  294. value = midpoint + ( remaining / 2 ) * cycle;
  295. if ( invert )
  296. {
  297. value = 255 - value;
  298. }
  299. value = max( 0, value );
  300. value = min( 255, value );
  301. return value;
  302. }
  303. //-----------------------------------------------------------------------------
  304. // Purpose:
  305. // Input : frac -
  306. // r -
  307. // g -
  308. // b -
  309. //-----------------------------------------------------------------------------
  310. void CEntityReportPanel::ApplyEffect( ENTITYBITS *entry, int& r, int& g, int& b )
  311. {
  312. bool effectactive = ( realtime <= entry->effectfinishtime ) ? true : false;
  313. if ( !effectactive )
  314. return;
  315. float frequency = 3.0f;
  316. float frac = ( EFFECT_TIME - ( entry->effectfinishtime - realtime ) ) / EFFECT_TIME;
  317. frac = min( 1.f, frac );
  318. frac = max( 0.f, frac );
  319. frac *= 2.0 * M_PI;
  320. frac = sin( frequency * frac );
  321. if ( entry->flags & FENTITYBITS_LEAVEPVS )
  322. {
  323. r = MungeColorValue( frac, r );
  324. }
  325. else if ( entry->flags & FENTITYBITS_ADD )
  326. {
  327. g = MungeColorValue( frac, g );
  328. }
  329. else if ( entry->flags & FENTITYBITS_DELETE )
  330. {
  331. r = MungeColorValue( frac, r );
  332. g = MungeColorValue( frac, g );
  333. b = MungeColorValue( frac, b );
  334. }
  335. }
  336. //-----------------------------------------------------------------------------
  337. // Purpose:
  338. //-----------------------------------------------------------------------------
  339. bool CEntityReportPanel::DrawEntry( int row, int col, int rowheight, int colwidth, int entityIdx )
  340. {
  341. IClientNetworkable *pNet;
  342. ClientClass *pClientClass;
  343. bool inpvs;
  344. int r, g, b, a;
  345. bool effectactive;
  346. ENTITYBITS *entry;
  347. int top = 5;
  348. int left = 5;
  349. pNet = entitylist->GetClientNetworkable( entityIdx );
  350. entry = &s_EntityBits[ entityIdx ];
  351. effectactive = ( realtime <= entry->effectfinishtime ) ? true : false;
  352. if ( pNet && ((pClientClass = pNet->GetClientClass())) != NULL )
  353. {
  354. inpvs = !pNet->IsDormant();
  355. if ( inpvs )
  356. {
  357. if ( entry->average >= 5 )
  358. {
  359. r = 200; g = 200; b = 250;
  360. a = 255;
  361. }
  362. else
  363. {
  364. r = 200; g = 255; b = 100;
  365. a = 255;
  366. }
  367. }
  368. else
  369. {
  370. r = 255; g = 150; b = 100;
  371. a = 255;
  372. }
  373. ApplyEffect( entry, r, g, b );
  374. char text[256];
  375. wchar_t unicode[ 256 ];
  376. Q_snprintf( text, sizeof(text), "(%i) %s", entityIdx, pClientClass->m_pNetworkName );
  377. g_pVGuiLocalize->ConvertANSIToUnicode( text, unicode, sizeof( unicode ) );
  378. DrawColoredText( m_hFont, left + col * colwidth, top + row * rowheight, r, g, b, a, unicode );
  379. if ( inpvs )
  380. {
  381. float fracs[ 3 ];
  382. fracs[ 0 ] = (float)( entry->bits >> 3 ) / 100.0f;
  383. fracs[ 1 ] = (float)( entry->peak >> 3 ) / 100.0f;
  384. fracs[ 2 ] = (float)( (int)entry->average >> 3 ) / 100.0f;
  385. for ( int j = 0; j < 3; j++ )
  386. {
  387. fracs[ j ] = max( 0.0f, fracs[ j ] );
  388. fracs[ j ] = min( 1.0f, fracs[ j ] );
  389. }
  390. int rcright = left + col * colwidth + colwidth-2;
  391. int wide = colwidth / 3;
  392. int rcleft = rcright - wide;
  393. int rctop = top + row * rowheight;
  394. int rcbottom = rctop + rowheight - 1;
  395. vgui::surface()->DrawSetColor( 63, 63, 63, 127 );
  396. vgui::surface()->DrawFilledRect( rcleft, rctop, rcright, rcbottom );
  397. // draw a box around it
  398. vgui::surface()->DrawSetColor( 200, 200, 200, 127 );
  399. vgui::surface()->DrawOutlinedRect( rcleft, rctop, rcright, rcbottom );
  400. // Draw current as a filled rect
  401. vgui::surface()->DrawSetColor( 200, 255, 100, 192 );
  402. vgui::surface()->DrawFilledRect( rcleft, rctop + rowheight / 2, rcleft + wide * fracs[ 0 ], rcbottom - 1 );
  403. // Draw average a vertical bar
  404. vgui::surface()->DrawSetColor( 192, 192, 192, 255 );
  405. vgui::surface()->DrawFilledRect( rcleft + wide * fracs[ 2 ], rctop + rowheight / 2, rcleft + wide * fracs[ 2 ] + 1, rcbottom - 1 );
  406. // Draw peak as a vertical red tick
  407. vgui::surface()->DrawSetColor( 192, 0, 0, 255 );
  408. vgui::surface()->DrawFilledRect( rcleft + wide * fracs[ 1 ], rctop + 1, rcleft + wide * fracs[ 1 ] + 1, rctop + rowheight / 2 );
  409. }
  410. // drew something...
  411. return true;
  412. }
  413. /*else
  414. {
  415. r = 63; g = 63; b = 63;
  416. a = 220;
  417. ApplyEffect( entry, r, g, b );
  418. wchar_t unicode[ 256 ];
  419. g_pVGuiLocalize->ConvertANSIToUnicode( ( effectactive && entry->deletedclientclass ) ?
  420. entry->deletedclientclass->m_pNetworkName : "unused", unicode, sizeof( unicode ) );
  421. DrawColoredText( m_hFont, left + col * colwidth, top + row * rowheight, r, g, b, a,
  422. L"(%i) %s", i, unicode );
  423. }*/
  424. return false;
  425. }
  426. //-----------------------------------------------------------------------------
  427. // Purpose:
  428. //-----------------------------------------------------------------------------
  429. void CEntityReportPanel::Paint()
  430. {
  431. VPROF( "CEntityReportPanel::Paint" );
  432. if ( !m_hFont )
  433. return;
  434. if ( !cl.IsActive() )
  435. return;
  436. if ( !entitylist )
  437. return;
  438. int top = 5;
  439. int left = 5;
  440. int row = 0;
  441. int col = 0;
  442. int colwidth = 160;
  443. int rowheight = vgui::surface()->GetFontTall( m_hFont );
  444. IClientNetworkable *pNet;
  445. bool effectactive;
  446. ENTITYBITS *entry;
  447. int lastused = entitylist->GetMaxEntities()-1;
  448. while ( lastused > 0 )
  449. {
  450. pNet = entitylist->GetClientNetworkable( lastused );
  451. entry = &s_EntityBits[ lastused ];
  452. effectactive = ( realtime <= entry->effectfinishtime ) ? true : false;
  453. if ( pNet && pNet->GetClientClass() )
  454. {
  455. break;
  456. }
  457. if ( effectactive )
  458. break;
  459. lastused--;
  460. }
  461. int start = 0;
  462. if ( cl_entityreport.GetInt() > 1 )
  463. {
  464. start = cl_entityreport.GetInt();
  465. }
  466. // draw sorted
  467. if ( cl_entityreport_sorted.GetInt() != ENTITYSORT_NONE )
  468. {
  469. // copy and sort
  470. int entityIndices[MAX_EDICTS];
  471. int count = lastused - start + 1;
  472. for ( int i = 0, entityIdx = start; entityIdx <= lastused; ++i, ++entityIdx )
  473. {
  474. entityIndices[i] = entityIdx;
  475. }
  476. qsort( entityIndices, count, sizeof(int), CompareEntityBits );
  477. // now draw
  478. for ( int i = 0; i < count; ++i )
  479. {
  480. int entityIdx = entityIndices[i];
  481. if ( DrawEntry( row, col, rowheight, colwidth, entityIdx ) )
  482. {
  483. row++;
  484. if ( top + row * rowheight > videomode->GetModeStereoHeight() - rowheight )
  485. {
  486. row = 0;
  487. col++;
  488. // No more space anyway, give up
  489. if ( left + ( col + 1 ) * 200 > videomode->GetModeStereoWidth() )
  490. return;
  491. }
  492. }
  493. }
  494. }
  495. // not sorted, old method with items scattered across the screen
  496. else
  497. {
  498. for ( int i = start; i <= lastused; i++ )
  499. {
  500. DrawEntry( row, col, rowheight, colwidth, i );
  501. row++;
  502. if ( top + row * rowheight > videomode->GetModeStereoHeight() - rowheight )
  503. {
  504. row = 0;
  505. col++;
  506. // No more space anyway, give up
  507. if ( left + ( col + 1 ) * 200 > videomode->GetModeStereoWidth() )
  508. return;
  509. }
  510. }
  511. }
  512. }