608 lines
16 KiB
C++
608 lines
16 KiB
C++
//========= Copyright Valve Corporation, All rights reserved. ============//
|
|
//
|
|
// Purpose:
|
|
//
|
|
// $NoKeywords: $
|
|
//=============================================================================//
|
|
#include "client_pch.h"
|
|
#include "ivideomode.h"
|
|
|
|
// memdbgon must be the last include file in a .cpp file!!!
|
|
#include "tier0/memdbgon.h"
|
|
|
|
ConVar cl_entityreport( "cl_entityreport", "0", FCVAR_CHEAT, "For debugging, draw entity states to console" );
|
|
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" );
|
|
|
|
enum
|
|
{
|
|
ENTITYSORT_NONE = 0,
|
|
ENTITYSORT_AVG = 1,
|
|
ENTITYSORT_CURRENT = 2,
|
|
ENTITYSORT_PEAK = 3,
|
|
};
|
|
|
|
// How quickly to move rolling average for entityreport
|
|
#define BITCOUNT_AVERAGE 0.95f
|
|
// How long to flush item when something important happens
|
|
#define EFFECT_TIME 1.5f
|
|
// How long to latch peak bit count for item
|
|
#define PEAK_LATCH_TIME 2.0f;
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Entity report event types
|
|
//-----------------------------------------------------------------------------
|
|
enum
|
|
{
|
|
FENTITYBITS_ZERO = 0,
|
|
FENTITYBITS_ADD = 0x01,
|
|
FENTITYBITS_LEAVEPVS = 0x02,
|
|
FENTITYBITS_DELETE = 0x04,
|
|
};
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Data about an entity
|
|
//-----------------------------------------------------------------------------
|
|
typedef struct
|
|
{
|
|
// Bits used for last message
|
|
int bits;
|
|
// Rolling average of bits used
|
|
float average;
|
|
// Last bit peak
|
|
int peak;
|
|
// Time at which peak was last reset
|
|
float peaktime;
|
|
// Event info
|
|
int flags;
|
|
// If doing effect, when it will finish
|
|
float effectfinishtime;
|
|
// If event was deletion, remember client class for a little bit
|
|
ClientClass *deletedclientclass;
|
|
} ENTITYBITS;
|
|
|
|
// List of entities we are keeping data bout
|
|
static ENTITYBITS s_EntityBits[ MAX_EDICTS ];
|
|
|
|
// Used to sort by average
|
|
int CompareEntityBits(const void* pIndexA, const void* pIndexB )
|
|
{
|
|
int indexA = *(int*)pIndexA;
|
|
int indexB = *(int*)pIndexB;
|
|
|
|
ENTITYBITS *pEntryA = &s_EntityBits[indexA];
|
|
ENTITYBITS *pEntryB = &s_EntityBits[indexB];
|
|
|
|
/*
|
|
if ( pEntryA->flags == FENTITYBITS_ZERO )
|
|
{
|
|
if ( pEntryB->flags == FENTITYBITS_ZERO )
|
|
{
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
else if ( pEntryB->flags == FENTITYBITS_ZERO )
|
|
{
|
|
return -1;
|
|
}
|
|
*/
|
|
|
|
// sort dormant, out-of-pvs to the end
|
|
IClientNetworkable *pNetA = entitylist->GetClientNetworkable( indexA );
|
|
IClientNetworkable *pNetB = entitylist->GetClientNetworkable( indexB );
|
|
|
|
bool bDormantA = pNetA == NULL || pNetA->IsDormant();
|
|
bool bDormantB = pNetB == NULL || pNetB->IsDormant();
|
|
|
|
if ( bDormantA != bDormantB )
|
|
{
|
|
return bDormantA ? 1 : -1;
|
|
}
|
|
|
|
switch ( cl_entityreport_sorted.GetInt() )
|
|
{
|
|
case ENTITYSORT_AVG:
|
|
if ( pEntryA->average > pEntryB->average )
|
|
{
|
|
return -1;
|
|
}
|
|
if ( pEntryA->average < pEntryB->average )
|
|
{
|
|
return 1;
|
|
}
|
|
break;
|
|
case ENTITYSORT_CURRENT:
|
|
if ( pEntryA->bits > pEntryB->bits )
|
|
{
|
|
return -1;
|
|
}
|
|
if ( pEntryA->bits < pEntryB->bits )
|
|
{
|
|
return 1;
|
|
}
|
|
break;
|
|
case ENTITYSORT_PEAK:
|
|
default:
|
|
if ( pEntryA->peak > pEntryB->peak )
|
|
{
|
|
return -1;
|
|
}
|
|
if ( pEntryA->peak < pEntryB->peak )
|
|
{
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Zero out structure ( level transition/startup )
|
|
//-----------------------------------------------------------------------------
|
|
void CL_ResetEntityBits( void )
|
|
{
|
|
memset( s_EntityBits, 0, sizeof( s_EntityBits ) );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Record activity
|
|
// Input : entnum -
|
|
// bitcount -
|
|
//-----------------------------------------------------------------------------
|
|
void CL_RecordEntityBits( int entnum, int bitcount )
|
|
{
|
|
if ( entnum < 0 || entnum >= MAX_EDICTS )
|
|
{
|
|
return;
|
|
}
|
|
|
|
ENTITYBITS *slot = &s_EntityBits[ entnum ];
|
|
|
|
slot->bits = bitcount;
|
|
// Update average
|
|
slot->average = ( BITCOUNT_AVERAGE ) * slot->average + ( 1.f - BITCOUNT_AVERAGE ) * bitcount;
|
|
|
|
// Recompute peak
|
|
if ( realtime >= slot->peaktime )
|
|
{
|
|
slot->peak = 0.0f;
|
|
slot->peaktime = realtime + PEAK_LATCH_TIME;
|
|
}
|
|
|
|
// Store off peak
|
|
if ( bitcount > slot->peak )
|
|
{
|
|
slot->peak = bitcount;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Record entity add event
|
|
// Input : entnum -
|
|
//-----------------------------------------------------------------------------
|
|
void CL_RecordAddEntity( int entnum )
|
|
{
|
|
if ( !cl_entityreport.GetBool() || entnum < 0 || entnum >= MAX_EDICTS )
|
|
{
|
|
return;
|
|
}
|
|
|
|
ENTITYBITS *slot = &s_EntityBits[ entnum ];
|
|
slot->flags = FENTITYBITS_ADD;
|
|
slot->effectfinishtime = realtime + EFFECT_TIME;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: record entity leave event
|
|
// Input : entnum -
|
|
//-----------------------------------------------------------------------------
|
|
void CL_RecordLeavePVS( int entnum )
|
|
{
|
|
if ( !cl_entityreport.GetBool() || entnum < 0 || entnum >= MAX_EDICTS )
|
|
{
|
|
return;
|
|
}
|
|
|
|
ENTITYBITS *slot = &s_EntityBits[ entnum ];
|
|
slot->flags = FENTITYBITS_LEAVEPVS;
|
|
slot->effectfinishtime = realtime + EFFECT_TIME;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: record entity deletion event
|
|
// Input : entnum -
|
|
// *pclass -
|
|
//-----------------------------------------------------------------------------
|
|
void CL_RecordDeleteEntity( int entnum, ClientClass *pclass )
|
|
{
|
|
if ( !cl_entityreport.GetBool() || entnum < 0 || entnum >= MAX_EDICTS )
|
|
{
|
|
return;
|
|
}
|
|
|
|
ENTITYBITS *slot = &s_EntityBits[ entnum ];
|
|
slot->flags = FENTITYBITS_DELETE;
|
|
slot->effectfinishtime = realtime + EFFECT_TIME;
|
|
slot->deletedclientclass = pclass;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Shows entity status report if cl_entityreport cvar is set
|
|
//-----------------------------------------------------------------------------
|
|
class CEntityReportPanel : public CBasePanel
|
|
{
|
|
typedef CBasePanel BaseClass;
|
|
public:
|
|
// Construction
|
|
CEntityReportPanel( vgui::Panel *parent );
|
|
virtual ~CEntityReportPanel( void );
|
|
|
|
// Refresh
|
|
virtual void Paint();
|
|
virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
|
|
virtual bool ShouldDraw( void );
|
|
|
|
// Helpers
|
|
void ApplyEffect( ENTITYBITS *entry, int& r, int& g, int& b );
|
|
bool DrawEntry( int row, int col, int rowheight, int colwidth, int entityIdx );
|
|
|
|
private:
|
|
// Font to use for drawing
|
|
vgui::HFont m_hFont;
|
|
};
|
|
|
|
static CEntityReportPanel *g_pEntityReportPanel = NULL;
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Creates the CEntityReportPanel VGUI panel
|
|
// Input : *parent -
|
|
//-----------------------------------------------------------------------------
|
|
void CL_CreateEntityReportPanel( vgui::Panel *parent )
|
|
{
|
|
g_pEntityReportPanel = new CEntityReportPanel( parent );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Instances the entity report panel
|
|
// Input : *parent -
|
|
//-----------------------------------------------------------------------------
|
|
CEntityReportPanel::CEntityReportPanel( vgui::Panel *parent ) :
|
|
CBasePanel( parent, "CEntityReportPanel" )
|
|
{
|
|
// Need parent here, before loading up textures, so getSurfaceBase
|
|
// will work on this panel ( it's null otherwise )
|
|
SetSize( videomode->GetModeStereoWidth(), videomode->GetModeStereoHeight() );
|
|
SetPos( 0, 0 );
|
|
SetVisible( true );
|
|
SetCursor( null );
|
|
|
|
m_hFont = vgui::INVALID_FONT;
|
|
|
|
SetFgColor( Color( 0, 0, 0, 255 ) );
|
|
SetPaintBackgroundEnabled( false );
|
|
SetPaintBorderEnabled(false);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
CEntityReportPanel::~CEntityReportPanel( void )
|
|
{
|
|
}
|
|
|
|
void CEntityReportPanel::ApplySchemeSettings( vgui::IScheme *pScheme )
|
|
{
|
|
BaseClass::ApplySchemeSettings( pScheme );
|
|
|
|
// If you change this font, be sure to mark it with
|
|
// $use_in_fillrate_mode in its .vmt file
|
|
m_hFont = pScheme->GetFont( "DefaultVerySmall", false );
|
|
Assert( m_hFont );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Output : Returns true on success, false on failure.
|
|
//-----------------------------------------------------------------------------
|
|
bool CEntityReportPanel::ShouldDraw( void )
|
|
{
|
|
if ( !cl_entityreport.GetInt() )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Helper to flash colors
|
|
// Input : cycle -
|
|
// value -
|
|
// Output : static int
|
|
//-----------------------------------------------------------------------------
|
|
static int MungeColorValue( float cycle, int& value )
|
|
{
|
|
int midpoint;
|
|
int remaining;
|
|
bool invert = false;
|
|
|
|
if ( value < 128 )
|
|
{
|
|
invert = true;
|
|
value = 255 - value;
|
|
}
|
|
|
|
midpoint = value / 2;
|
|
|
|
remaining = value - midpoint;
|
|
midpoint = midpoint + remaining / 2;
|
|
|
|
value = midpoint + ( remaining / 2 ) * cycle;
|
|
if ( invert )
|
|
{
|
|
value = 255 - value;
|
|
}
|
|
|
|
value = max( 0, value );
|
|
value = min( 255, value );
|
|
return value;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : frac -
|
|
// r -
|
|
// g -
|
|
// b -
|
|
//-----------------------------------------------------------------------------
|
|
void CEntityReportPanel::ApplyEffect( ENTITYBITS *entry, int& r, int& g, int& b )
|
|
{
|
|
bool effectactive = ( realtime <= entry->effectfinishtime ) ? true : false;
|
|
if ( !effectactive )
|
|
return;
|
|
|
|
float frequency = 3.0f;
|
|
|
|
float frac = ( EFFECT_TIME - ( entry->effectfinishtime - realtime ) ) / EFFECT_TIME;
|
|
frac = min( 1.f, frac );
|
|
frac = max( 0.f, frac );
|
|
|
|
frac *= 2.0 * M_PI;
|
|
frac = sin( frequency * frac );
|
|
|
|
if ( entry->flags & FENTITYBITS_LEAVEPVS )
|
|
{
|
|
r = MungeColorValue( frac, r );
|
|
}
|
|
else if ( entry->flags & FENTITYBITS_ADD )
|
|
{
|
|
g = MungeColorValue( frac, g );
|
|
}
|
|
else if ( entry->flags & FENTITYBITS_DELETE )
|
|
{
|
|
r = MungeColorValue( frac, r );
|
|
g = MungeColorValue( frac, g );
|
|
b = MungeColorValue( frac, b );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
bool CEntityReportPanel::DrawEntry( int row, int col, int rowheight, int colwidth, int entityIdx )
|
|
{
|
|
IClientNetworkable *pNet;
|
|
ClientClass *pClientClass;
|
|
bool inpvs;
|
|
int r, g, b, a;
|
|
bool effectactive;
|
|
ENTITYBITS *entry;
|
|
|
|
int top = 5;
|
|
int left = 5;
|
|
|
|
pNet = entitylist->GetClientNetworkable( entityIdx );
|
|
|
|
entry = &s_EntityBits[ entityIdx ];
|
|
|
|
effectactive = ( realtime <= entry->effectfinishtime ) ? true : false;
|
|
|
|
if ( pNet && ((pClientClass = pNet->GetClientClass())) != NULL )
|
|
{
|
|
inpvs = !pNet->IsDormant();
|
|
if ( inpvs )
|
|
{
|
|
if ( entry->average >= 5 )
|
|
{
|
|
r = 200; g = 200; b = 250;
|
|
a = 255;
|
|
}
|
|
else
|
|
{
|
|
r = 200; g = 255; b = 100;
|
|
a = 255;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
r = 255; g = 150; b = 100;
|
|
a = 255;
|
|
}
|
|
|
|
ApplyEffect( entry, r, g, b );
|
|
|
|
char text[256];
|
|
wchar_t unicode[ 256 ];
|
|
|
|
Q_snprintf( text, sizeof(text), "(%i) %s", entityIdx, pClientClass->m_pNetworkName );
|
|
|
|
g_pVGuiLocalize->ConvertANSIToUnicode( text, unicode, sizeof( unicode ) );
|
|
|
|
DrawColoredText( m_hFont, left + col * colwidth, top + row * rowheight, r, g, b, a, unicode );
|
|
|
|
if ( inpvs )
|
|
{
|
|
float fracs[ 3 ];
|
|
fracs[ 0 ] = (float)( entry->bits >> 3 ) / 100.0f;
|
|
fracs[ 1 ] = (float)( entry->peak >> 3 ) / 100.0f;
|
|
fracs[ 2 ] = (float)( (int)entry->average >> 3 ) / 100.0f;
|
|
|
|
for ( int j = 0; j < 3; j++ )
|
|
{
|
|
fracs[ j ] = max( 0.0f, fracs[ j ] );
|
|
fracs[ j ] = min( 1.0f, fracs[ j ] );
|
|
}
|
|
|
|
int rcright = left + col * colwidth + colwidth-2;
|
|
int wide = colwidth / 3;
|
|
int rcleft = rcright - wide;
|
|
int rctop = top + row * rowheight;
|
|
int rcbottom = rctop + rowheight - 1;
|
|
|
|
vgui::surface()->DrawSetColor( 63, 63, 63, 127 );
|
|
vgui::surface()->DrawFilledRect( rcleft, rctop, rcright, rcbottom );
|
|
|
|
// draw a box around it
|
|
vgui::surface()->DrawSetColor( 200, 200, 200, 127 );
|
|
vgui::surface()->DrawOutlinedRect( rcleft, rctop, rcright, rcbottom );
|
|
|
|
// Draw current as a filled rect
|
|
vgui::surface()->DrawSetColor( 200, 255, 100, 192 );
|
|
vgui::surface()->DrawFilledRect( rcleft, rctop + rowheight / 2, rcleft + wide * fracs[ 0 ], rcbottom - 1 );
|
|
|
|
// Draw average a vertical bar
|
|
vgui::surface()->DrawSetColor( 192, 192, 192, 255 );
|
|
vgui::surface()->DrawFilledRect( rcleft + wide * fracs[ 2 ], rctop + rowheight / 2, rcleft + wide * fracs[ 2 ] + 1, rcbottom - 1 );
|
|
|
|
// Draw peak as a vertical red tick
|
|
vgui::surface()->DrawSetColor( 192, 0, 0, 255 );
|
|
vgui::surface()->DrawFilledRect( rcleft + wide * fracs[ 1 ], rctop + 1, rcleft + wide * fracs[ 1 ] + 1, rctop + rowheight / 2 );
|
|
}
|
|
|
|
// drew something...
|
|
return true;
|
|
}
|
|
/*else
|
|
{
|
|
r = 63; g = 63; b = 63;
|
|
a = 220;
|
|
|
|
ApplyEffect( entry, r, g, b );
|
|
|
|
wchar_t unicode[ 256 ];
|
|
g_pVGuiLocalize->ConvertANSIToUnicode( ( effectactive && entry->deletedclientclass ) ?
|
|
entry->deletedclientclass->m_pNetworkName : "unused", unicode, sizeof( unicode ) );
|
|
|
|
DrawColoredText( m_hFont, left + col * colwidth, top + row * rowheight, r, g, b, a,
|
|
L"(%i) %s", i, unicode );
|
|
}*/
|
|
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CEntityReportPanel::Paint()
|
|
{
|
|
VPROF( "CEntityReportPanel::Paint" );
|
|
|
|
if ( !m_hFont )
|
|
return;
|
|
|
|
if ( !cl.IsActive() )
|
|
return;
|
|
|
|
if ( !entitylist )
|
|
return;
|
|
|
|
int top = 5;
|
|
int left = 5;
|
|
int row = 0;
|
|
int col = 0;
|
|
int colwidth = 160;
|
|
int rowheight = vgui::surface()->GetFontTall( m_hFont );
|
|
|
|
IClientNetworkable *pNet;
|
|
bool effectactive;
|
|
ENTITYBITS *entry;
|
|
|
|
int lastused = entitylist->GetMaxEntities()-1;
|
|
|
|
while ( lastused > 0 )
|
|
{
|
|
pNet = entitylist->GetClientNetworkable( lastused );
|
|
|
|
entry = &s_EntityBits[ lastused ];
|
|
|
|
effectactive = ( realtime <= entry->effectfinishtime ) ? true : false;
|
|
|
|
if ( pNet && pNet->GetClientClass() )
|
|
{
|
|
break;
|
|
}
|
|
|
|
if ( effectactive )
|
|
break;
|
|
|
|
lastused--;
|
|
}
|
|
|
|
int start = 0;
|
|
if ( cl_entityreport.GetInt() > 1 )
|
|
{
|
|
start = cl_entityreport.GetInt();
|
|
}
|
|
|
|
// draw sorted
|
|
if ( cl_entityreport_sorted.GetInt() != ENTITYSORT_NONE )
|
|
{
|
|
// copy and sort
|
|
int entityIndices[MAX_EDICTS];
|
|
int count = lastused - start + 1;
|
|
for ( int i = 0, entityIdx = start; entityIdx <= lastused; ++i, ++entityIdx )
|
|
{
|
|
entityIndices[i] = entityIdx;
|
|
}
|
|
qsort( entityIndices, count, sizeof(int), CompareEntityBits );
|
|
|
|
// now draw
|
|
for ( int i = 0; i < count; ++i )
|
|
{
|
|
int entityIdx = entityIndices[i];
|
|
|
|
if ( DrawEntry( row, col, rowheight, colwidth, entityIdx ) )
|
|
{
|
|
row++;
|
|
if ( top + row * rowheight > videomode->GetModeStereoHeight() - rowheight )
|
|
{
|
|
row = 0;
|
|
col++;
|
|
// No more space anyway, give up
|
|
if ( left + ( col + 1 ) * 200 > videomode->GetModeStereoWidth() )
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// not sorted, old method with items scattered across the screen
|
|
else
|
|
{
|
|
for ( int i = start; i <= lastused; i++ )
|
|
{
|
|
DrawEntry( row, col, rowheight, colwidth, i );
|
|
|
|
row++;
|
|
if ( top + row * rowheight > videomode->GetModeStereoHeight() - rowheight )
|
|
{
|
|
row = 0;
|
|
col++;
|
|
// No more space anyway, give up
|
|
if ( left + ( col + 1 ) * 200 > videomode->GetModeStereoWidth() )
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|