|
|
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=====================================================================================//
#include "client_pch.h"
#include <time.h>
#include "console.h"
#include "ivideomode.h"
#include "zone.h"
#include "sv_main.h"
#include "server.h"
#include "MapReslistGenerator.h"
#include "tier0/vcrmode.h"
#if defined( _X360 )
#include "xbox/xbox_console.h"
#endif
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
#if !defined( _X360 )
#define MAXPRINTMSG 4096
#else
#define MAXPRINTMSG 1024
#endif
bool con_debuglog = false; bool con_initialized = false; bool con_debuglogmapprefixed = false;
CThreadFastMutex g_AsyncNotifyTextMutex;
static ConVar con_timestamp( "con_timestamp", "0", 0, "Prefix console.log entries with timestamps" );
// In order to avoid excessive opening and closing of the console log file
// we wrap it in an object and keep the handle open. This is necessary
// because of the sometimes considerable cost of opening and closing files
// on Windows. Opening and closing files on Windows is always moderately
// expensive, but profiling may dramatically underestimate the true cost
// because some anti-virus software can make closing a file handle take
// 20-90 ms!
class ConsoleLogManager { public: ConsoleLogManager(); ~ConsoleLogManager();
void RemoveConsoleLogFile(); bool ReadConsoleLogFile( CUtlBuffer& buf ); FileHandle_t GetConsoleLogFileHandleForAppend(); void CloseFileIfOpen();
private: FileHandle_t m_fh;
const char *GetConsoleLogFilename() const; };
// Wrap the ConsoleLogManager in a function to ensure that the object is always
// constructed before it is used.
ConsoleLogManager& GetConsoleLogManager() { static ConsoleLogManager object; return object; }
void ConsoleLogFileCallback( IConVar *var, const char *pOldValue, float flOldValue ) { ConVarRef con_logfile( var->GetName() ); const char *logFile = con_logfile.GetString(); // close any existing file, because we have changed the name
GetConsoleLogManager().CloseFileIfOpen();
// validate the path and the .log/.txt extensions
if ( !COM_IsValidPath( logFile ) || !COM_IsValidLogFilename( logFile ) ) { ConMsg( "invalid log filename\n" ); con_logfile.SetValue( "console.log" ); return; } else { const char *extension = Q_GetFileExtension( logFile ); if ( !extension || ( Q_strcasecmp( extension, "log" ) && Q_strcasecmp( extension, "txt" ) ) ) { char szTemp[MAX_PATH]; V_sprintf_safe( szTemp, "%s.log", logFile ); con_logfile.SetValue( szTemp ); return; } } if ( !COM_IsValidPath( logFile ) ) { con_debuglog = CommandLine()->FindParm( "-condebug" ) != 0; } else { con_debuglog = true; } }
ConVar con_logfile( "con_logfile", "", 0, "Console output gets written to this file", false, 0.0f, false, 0.0f, ConsoleLogFileCallback );
static const char *GetTimestampString( void ) { static char string[128]; tm today; VCRHook_LocalTime( &today ); Q_snprintf( string, sizeof( string ), "%02i/%02i/%04i - %02i:%02i:%02i", today.tm_mon+1, today.tm_mday, 1900 + today.tm_year, today.tm_hour, today.tm_min, today.tm_sec ); return string; }
#ifndef SWDS
static ConVar con_trace( "con_trace", "0", FCVAR_MATERIAL_SYSTEM_THREAD, "Print console text to low level printout." ); static ConVar con_notifytime( "con_notifytime","8", FCVAR_MATERIAL_SYSTEM_THREAD, "How long to display recent console text to the upper part of the game window" ); static ConVar con_times("contimes", "8", FCVAR_MATERIAL_SYSTEM_THREAD, "Number of console lines to overlay for debugging." ); static ConVar con_drawnotify( "con_drawnotify", "1", 0, "Disables drawing of notification area (for taking screenshots)." ); static ConVar con_enable("con_enable", "0", FCVAR_ARCHIVE, "Allows the console to be activated."); static ConVar con_filter_enable ( "con_filter_enable","0", FCVAR_MATERIAL_SYSTEM_THREAD, "Filters console output based on the setting of con_filter_text. 1 filters completely, 2 displays filtered text brighter than other text." ); static ConVar con_filter_text ( "con_filter_text","", FCVAR_MATERIAL_SYSTEM_THREAD, "Text with which to filter console spew. Set con_filter_enable 1 or 2 to activate." ); static ConVar con_filter_text_out ( "con_filter_text_out","", FCVAR_MATERIAL_SYSTEM_THREAD, "Text with which to filter OUT of console spew. Set con_filter_enable 1 or 2 to activate." );
//-----------------------------------------------------------------------------
// Purpose: Implements the console using VGUI
//-----------------------------------------------------------------------------
class CConPanel : public CBasePanel { typedef CBasePanel BaseClass;
public: enum { MAX_NOTIFY_TEXT_LINE = 256 };
CConPanel( vgui::Panel *parent ); virtual ~CConPanel( void );
virtual void ApplySchemeSettings( vgui::IScheme *pScheme );
// Draws the text
virtual void Paint(); // Draws the background image
virtual void PaintBackground();
// Draw notify area
virtual void DrawNotify( void ); // Draws debug ( Con_NXPrintf ) areas
virtual void DrawDebugAreas( void ); int ProcessNotifyLines( int &left, int &top, int &right, int &bottom, bool bDraw );
// Draw helpers
virtual int DrawText( vgui::HFont font, int x, int y, wchar_t *data );
virtual bool ShouldDraw( void );
void Con_NPrintf( int idx, const char *msg ); void Con_NXPrintf( const struct con_nprint_s *info, const char *msg );
void AddToNotify( const Color& clr, char const *msg ); void ClearNotify();
private: // Console font
vgui::HFont m_hFont; vgui::HFont m_hFontFixed;
struct CNotifyText { Color clr; float liferemaining; wchar_t text[MAX_NOTIFY_TEXT_LINE]; };
CCopyableUtlVector< CNotifyText > m_NotifyText;
enum { MAX_DBG_NOTIFY = 128, DBG_NOTIFY_TIMEOUT = 4, };
float da_default_color[3];
typedef struct { wchar_t szNotify[MAX_NOTIFY_TEXT_LINE]; float expire; float color[3]; bool fixed_width_font; } da_notify_t;
da_notify_t da_notify[MAX_DBG_NOTIFY]; bool m_bDrawDebugAreas; };
static CConPanel *g_pConPanel = NULL;
/*
================ Con_HideConsole_f
================ */ void Con_HideConsole_f( void ) { if ( IsX360() ) return;
if ( EngineVGui()->IsConsoleVisible() ) { // hide the console
EngineVGui()->HideConsole(); } }
/*
================ Con_ShowConsole_f ================ */ void Con_ShowConsole_f( void ) { if ( IsX360() ) return;
if ( vgui::input()->GetAppModalSurface() ) { // If a dialog has modal, it probably has grabbed keyboard focus, so showing
// the console would be a bad idea.
return; }
if ( !g_ClientDLL->ShouldAllowConsole() ) return;
// make sure we're allowed to see the console
if ( con_enable.GetBool() || developer.GetInt() || CommandLine()->CheckParm("-console") || CommandLine()->CheckParm("-rpt") ) { // show the console
EngineVGui()->ShowConsole();
// remove any loading screen
SCR_EndLoadingPlaque(); } }
//-----------------------------------------------------------------------------
// Purpose: toggles the console
//-----------------------------------------------------------------------------
void Con_ToggleConsole_f( void ) { if ( IsX360() ) return;
if (EngineVGui()->IsConsoleVisible()) { Con_HideConsole_f();
// If we hide the console, we also hide the game UI
EngineVGui()->HideGameUI(); } else { Con_ShowConsole_f(); } }
//-----------------------------------------------------------------------------
// Purpose: Clears the console
//-----------------------------------------------------------------------------
void Con_Clear_f( void ) { if ( IsX360() ) return;
EngineVGui()->ClearConsole(); Con_ClearNotify(); } /*
================ Con_ClearNotify ================ */ void Con_ClearNotify (void) { if ( g_pConPanel ) { g_pConPanel->ClearNotify(); } }
#endif // SWDS
ConsoleLogManager::ConsoleLogManager() { m_fh = FILESYSTEM_INVALID_HANDLE; }
ConsoleLogManager::~ConsoleLogManager() { // This fails because of destructor order problems. The file
// system has already been shut down by the time this runs.
// We'll have to count on the OS to close the file for us.
//CloseFileIfOpen();
}
void ConsoleLogManager::RemoveConsoleLogFile() { // Make sure the log file is closed before we try deleting it.
CloseFileIfOpen(); g_pFileSystem->RemoveFile( GetConsoleLogFilename(), "GAME" ); }
bool ConsoleLogManager::ReadConsoleLogFile( CUtlBuffer& buf ) { // Make sure the log file is closed before we try reading it.
CloseFileIfOpen(); const char *pLogFile = GetConsoleLogFilename(); if ( g_pFullFileSystem->ReadFile( pLogFile, "GAME", buf ) ) return true;
return false; }
FileHandle_t ConsoleLogManager::GetConsoleLogFileHandleForAppend() { if ( m_fh == FILESYSTEM_INVALID_HANDLE ) { const char* file = GetConsoleLogFilename(); m_fh = g_pFileSystem->Open( file, "a" ); }
return m_fh; }
void ConsoleLogManager::CloseFileIfOpen() { if ( m_fh != FILESYSTEM_INVALID_HANDLE ) { g_pFileSystem->Close( m_fh ); m_fh = FILESYSTEM_INVALID_HANDLE; } }
const char *ConsoleLogManager::GetConsoleLogFilename() const { const char *logFile = con_logfile.GetString(); if ( !COM_IsValidPath( logFile ) || !COM_IsValidLogFilename( logFile ) ) { return "console.log"; } return logFile; }
/*
================ Con_Init ================ */ void Con_Init (void) { #ifdef DEDICATED
con_debuglog = false; // the dedicated server's console will handle this
con_debuglogmapprefixed = false;
// Check -consolelog arg and set con_logfile if it's present. This gets some messages logged
// that we would otherwise miss due to con_logfile being set in the .cfg file.
const char *filename = NULL; if ( CommandLine()->CheckParm( "-consolelog", &filename ) && filename && filename[ 0 ] ) { con_logfile.SetValue( filename ); } #else
bool bRPTClient = ( CommandLine()->FindParm( "-rpt" ) != 0 ); con_debuglog = bRPTClient || ( CommandLine()->FindParm( "-condebug" ) != 0 ); con_debuglogmapprefixed = CommandLine()->FindParm( "-makereslists" ) != 0; if ( con_debuglog ) { con_logfile.SetValue( "console.log" ); if ( bRPTClient || ( CommandLine()->FindParm( "-conclearlog" ) ) ) { GetConsoleLogManager().RemoveConsoleLogFile(); } } #endif // !DEDICATED
con_initialized = true; }
/*
================ Con_Shutdown ================ */ void Con_Shutdown (void) { con_initialized = false; }
/*
================ Read the console log from disk and return it in 'buf'. Buf should come in as an empty TEXT_BUFFER CUtlBuffer. Returns true if the log file is successfully read. ================ */ bool GetConsoleLogFileData( CUtlBuffer& buf ) { return GetConsoleLogManager().ReadConsoleLogFile( buf ); }
/*
================ Con_DebugLog ================ */ void Con_DebugLog( const char *fmt, ...) { va_list argptr; char data[MAXPRINTMSG]; va_start(argptr, fmt); Q_vsnprintf(data, sizeof(data), fmt, argptr); va_end(argptr);
FileHandle_t fh = GetConsoleLogManager().GetConsoleLogFileHandleForAppend(); if (fh != FILESYSTEM_INVALID_HANDLE ) { if ( con_debuglogmapprefixed ) { char const *prefix = MapReslistGenerator().LogPrefix(); if ( prefix ) { g_pFileSystem->Write( prefix, strlen(prefix), fh ); } }
if ( con_timestamp.GetBool() ) { static bool needTimestamp = true; // Start the first line with a timestamp
if ( needTimestamp ) { const char *timestamp = GetTimestampString(); g_pFileSystem->Write( timestamp, strlen( timestamp ), fh ); g_pFileSystem->Write( ": ", 2, fh ); } needTimestamp = V_stristr( data, "\n" ) != 0; }
g_pFileSystem->Write( data, strlen(data), fh ); // Now that we don't close the file we need to flush it in order
// to make sure that the data makes it to the file system.
g_pFileSystem->Flush( fh ); } }
static bool g_fIsDebugPrint = false;
#ifndef SWDS
/*
================ Con_Printf
Handles cursor positioning, line wrapping, etc ================ */ static bool g_fColorPrintf = false; static bool g_bInColorPrint = false; extern CThreadLocalInt<> g_bInSpew;
void Con_Printf( const char *fmt, ... );
extern ConVar spew_consolelog_to_debugstring;
void Con_ColorPrint( const Color& clr, char const *msg ) { if ( IsPC() ) { if ( g_bInColorPrint ) return;
int nCon_Filter_Enable = con_filter_enable.GetInt(); if ( nCon_Filter_Enable > 0 ) { const char *pszText = con_filter_text.GetString(); const char *pszIgnoreText = con_filter_text_out.GetString();
switch( nCon_Filter_Enable ) { case 1: // if line does not contain keyword do not print the line
if ( pszText && ( *pszText != '\0' ) && ( Q_stristr( msg, pszText ) == NULL )) return; if ( pszIgnoreText && *pszIgnoreText && ( Q_stristr( msg, pszIgnoreText ) != NULL ) ) return; break;
case 2: if ( pszIgnoreText && *pszIgnoreText && ( Q_stristr( msg, pszIgnoreText ) != NULL ) ) return; // if line does not contain keyword print it in a darker color
if ( pszText && ( *pszText != '\0' ) && ( Q_stristr( msg, pszText ) == NULL )) { Color mycolor(200, 200, 200, 150 ); g_pCVar->ConsoleColorPrintf( mycolor, "%s", msg ); return; } break;
default: // by default do no filtering
break; } }
g_bInColorPrint = true;
// also echo to debugging console
if ( Plat_IsInDebugSession() && !con_trace.GetInt() && !spew_consolelog_to_debugstring.GetBool() ) { Sys_OutputDebugString(msg); } if ( sv.IsDedicated() ) { g_bInColorPrint = false; return; // no graphics mode
}
bool convisible = Con_IsVisible(); bool indeveloper = ( developer.GetInt() > 0 ); bool debugprint = g_fIsDebugPrint;
if ( g_fColorPrintf ) { g_pCVar->ConsoleColorPrintf( clr, "%s", msg ); } else { // write it out to the vgui console no matter what
if ( g_fIsDebugPrint ) { // Don't spew debug stuff to actual console once in game, unless console isn't up
if ( !cl.IsActive() || !convisible ) { g_pCVar->ConsoleDPrintf( "%s", msg ); } } else { g_pCVar->ConsolePrintf( "%s", msg ); } }
// Make sure we "spew" if this wan't generated from the spew system
if ( !g_bInSpew ) { Msg( "%s", msg ); }
// Only write to notify if it's non-debug or we are running with developer set > 0
// Buf it it's debug then make sure we don't have the console down
if ( ( !debugprint || indeveloper ) && !( debugprint && convisible ) ) { if ( g_pConPanel ) { g_pConPanel->AddToNotify( clr, msg ); } } g_bInColorPrint = false; }
#if defined( _X360 )
int r,g,b,a; char buffer[MAXPRINTMSG]; const char *pFrom; char *pTo;
clr.GetColor(r, g, b, a);
// fixup percent printers
pFrom = msg; pTo = buffer; while ( *pFrom && pTo < buffer+sizeof(buffer)-1 ) { *pTo = *pFrom++; if ( *pTo++ == '%' ) *pTo++ = '%'; } *pTo = '\0';
XBX_DebugString( XMAKECOLOR(r,g,b), buffer ); #endif
} #endif
// returns false if the print function shouldn't continue
bool HandleRedirectAndDebugLog( const char *msg ) { // Add to redirected message
if ( SV_RedirectActive() ) { SV_RedirectAddText( msg ); return false; }
// log all messages to file
if ( con_debuglog ) Con_DebugLog( "%s", msg );
if (!con_initialized) { return false; } return true; }
void Con_Print( const char *msg ) { if ( !msg || !msg[0] ) return;
if ( !HandleRedirectAndDebugLog( msg ) ) { return; }
#ifdef SWDS
Msg( "%s", msg ); #else
if ( sv.IsDedicated() ) { Msg( "%s", msg ); } else { #if !defined( _X360 )
Color clr( 255, 255, 255, 255 ); #else
Color clr( 0, 0, 0, 255 ); #endif
Con_ColorPrint( clr, msg ); } #endif
}
void Con_Printf( const char *fmt, ... ) { va_list argptr; char msg[MAXPRINTMSG]; static bool inupdate; va_start( argptr, fmt ); Q_vsnprintf( msg, sizeof( msg ), fmt, argptr ); va_end( argptr );
#ifndef NO_VCR
// Normally, we shouldn't need to write this data to the file, but it can help catch
// out-of-sync errors earlier.
if ( vcr_verbose.GetInt() ) { VCRGenericString( "Con_Printf", msg ); } #endif
if ( !HandleRedirectAndDebugLog( msg ) ) { return; }
#ifdef SWDS
Msg( "%s", msg ); #else
if ( sv.IsDedicated() ) { Msg( "%s", msg ); } else { #if !defined( _X360 )
Color clr( 255, 255, 255, 255 ); #else
Color clr( 0, 0, 0, 255 ); #endif
Con_ColorPrint( clr, msg ); } #endif
}
#ifndef SWDS
//-----------------------------------------------------------------------------
// Purpose:
// Input : clr -
// *fmt -
// ... -
//-----------------------------------------------------------------------------
void Con_ColorPrintf( const Color& clr, const char *fmt, ... ) { va_list argptr; char msg[MAXPRINTMSG];
va_start (argptr,fmt); Q_vsnprintf (msg,sizeof( msg ), fmt,argptr); va_end (argptr);
AUTO_LOCK( g_AsyncNotifyTextMutex ); if ( !HandleRedirectAndDebugLog( msg ) ) { return; }
g_fColorPrintf = true; Con_ColorPrint( clr, msg ); g_fColorPrintf = false; } #endif
/*
================ Con_DPrintf
A Con_Printf that only shows up if the "developer" cvar is set ================ */ void Con_DPrintf (const char *fmt, ...) { va_list argptr; char msg[MAXPRINTMSG];
va_start (argptr,fmt); Q_vsnprintf(msg,sizeof( msg ), fmt,argptr); va_end (argptr); g_fIsDebugPrint = true;
#ifdef SWDS
DevMsg( "%s", msg ); #else
if ( sv.IsDedicated() ) { DevMsg( "%s", msg ); } else { Color clr( 196, 181, 80, 255 ); Con_ColorPrint ( clr, msg ); } #endif
g_fIsDebugPrint = false; }
/*
================== Con_SafePrintf
Okay to call even when the screen can't be updated ================== */ void Con_SafePrintf (const char *fmt, ...) { va_list argptr; char msg[MAXPRINTMSG]; va_start (argptr,fmt); Q_vsnprintf(msg,sizeof( msg ), fmt,argptr); va_end (argptr);
#ifndef SWDS
bool temp; temp = scr_disabled_for_loading; scr_disabled_for_loading = true; #endif
g_fIsDebugPrint = true; Con_Printf ("%s", msg); g_fIsDebugPrint = false; #ifndef SWDS
scr_disabled_for_loading = temp; #endif
}
#ifndef SWDS
bool Con_IsVisible() { return (EngineVGui()->IsConsoleVisible()); }
void Con_NPrintf( int idx, const char *fmt, ... ) { va_list argptr; char outtext[MAXPRINTMSG];
va_start(argptr, fmt); Q_vsnprintf( outtext, sizeof( outtext ), fmt, argptr); va_end(argptr);
if ( IsPC() ) { g_pConPanel->Con_NPrintf( idx, outtext ); } else { Con_Printf( outtext ); } }
void Con_NXPrintf( const struct con_nprint_s *info, const char *fmt, ... ) { va_list argptr; char outtext[MAXPRINTMSG];
va_start(argptr, fmt); Q_vsnprintf( outtext, sizeof( outtext ), fmt, argptr); va_end(argptr);
if ( IsPC() ) { g_pConPanel->Con_NXPrintf( info, outtext ); } else { Con_Printf( outtext ); } }
//-----------------------------------------------------------------------------
// Purpose: Creates the console panel
// Input : *parent -
//-----------------------------------------------------------------------------
CConPanel::CConPanel( vgui::Panel *parent ) : CBasePanel( parent, "CConPanel" ) { // Full screen assumed
SetSize( videomode->GetModeStereoWidth(), videomode->GetModeStereoHeight() ); SetPos( 0, 0 ); SetVisible( true ); SetMouseInputEnabled( false ); SetKeyBoardInputEnabled( false );
da_default_color[0] = 1.0; da_default_color[1] = 1.0; da_default_color[2] = 1.0;
m_bDrawDebugAreas = false;
g_pConPanel = this; memset( da_notify, 0, sizeof(da_notify) ); }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CConPanel::~CConPanel( void ) { }
void CConPanel::Con_NPrintf( int idx, const char *msg ) { if ( idx < 0 || idx >= MAX_DBG_NOTIFY ) return;
#ifdef WIN32
_snwprintf( da_notify[idx].szNotify, sizeof( da_notify[idx].szNotify ) / sizeof( wchar_t ) - 1, L"%S", msg ); #else
_snwprintf( da_notify[idx].szNotify, sizeof( da_notify[idx].szNotify ) / sizeof( wchar_t ) - 1, L"%s", msg ); #endif
da_notify[idx].szNotify[ sizeof( da_notify[idx].szNotify ) / sizeof( wchar_t ) - 1 ] = L'\0';
// Reset values
da_notify[idx].expire = realtime + DBG_NOTIFY_TIMEOUT; VectorCopy( da_default_color, da_notify[idx].color ); da_notify[idx].fixed_width_font = false; m_bDrawDebugAreas = true; }
void CConPanel::Con_NXPrintf( const struct con_nprint_s *info, const char *msg ) { if ( !info ) return;
if ( info->index < 0 || info->index >= MAX_DBG_NOTIFY ) return;
#ifdef WIN32
_snwprintf( da_notify[info->index].szNotify, sizeof( da_notify[info->index].szNotify ) / sizeof( wchar_t ) - 1, L"%S", msg ); #else
_snwprintf( da_notify[info->index].szNotify, sizeof( da_notify[info->index].szNotify ) / sizeof( wchar_t ) - 1, L"%s", msg ); #endif
da_notify[info->index].szNotify[ sizeof( da_notify[info->index].szNotify ) / sizeof( wchar_t ) - 1 ] = L'\0';
// Reset values
if ( info->time_to_live == -1 ) da_notify[ info->index ].expire = -1; // special marker means to just draw it once
else da_notify[ info->index ].expire = realtime + info->time_to_live; VectorCopy( info->color, da_notify[ info->index ].color ); da_notify[ info->index ].fixed_width_font = info->fixed_width_font; m_bDrawDebugAreas = true; }
static void safestrncat( wchar_t *text, int maxCharactersWithNullTerminator, wchar_t const *add, int addchars ) { int maxCharactersWithoutTerminator = maxCharactersWithNullTerminator - 1;
int curlen = wcslen( text ); if ( curlen >= maxCharactersWithoutTerminator ) return;
wchar_t *p = text + curlen; while ( curlen++ < maxCharactersWithoutTerminator && --addchars >= 0 ) { *p++ = *add++; } *p = 0; }
void CConPanel::AddToNotify( const Color& clr, char const *msg ) { if ( !host_initialized ) return;
// notify area only ever draws in developer mode - it should never be used for game messages
if ( !developer.GetBool() ) return;
// skip any special characters
if ( msg[0] == 1 || msg[0] == 2 ) { msg++; }
// Nothing left
if ( !msg[0] ) return;
// Protect against background modifications to m_NotifyText.
AUTO_LOCK( g_AsyncNotifyTextMutex );
CNotifyText *current = NULL;
int slot = m_NotifyText.Count() - 1; if ( slot < 0 ) { slot = m_NotifyText.AddToTail(); current = &m_NotifyText[ slot ]; current->clr = clr; current->text[ 0 ] = 0; current->liferemaining = con_notifytime.GetFloat();; } else { current = &m_NotifyText[ slot ]; current->clr = clr; }
Assert( current );
wchar_t unicode[ 1024 ]; g_pVGuiLocalize->ConvertANSIToUnicode( msg, unicode, sizeof( unicode ) );
wchar_t const *p = unicode; while ( *p ) { const wchar_t *nextreturn = wcsstr( p, L"\n" ); if ( nextreturn != NULL ) { int copysize = nextreturn - p + 1; safestrncat( current->text, MAX_NOTIFY_TEXT_LINE, p, copysize );
// Add a new notify, but don't add a new one if the previous one was empty...
if ( current->text[0] && current->text[0] != L'\n' ) { slot = m_NotifyText.AddToTail(); current = &m_NotifyText[ slot ]; } // Clear it
current->clr = clr; current->text[ 0 ] = 0; current->liferemaining = con_notifytime.GetFloat(); // Skip return character
p += copysize; continue; }
// Append it
safestrncat( current->text, MAX_NOTIFY_TEXT_LINE, p, wcslen( p ) ); current->clr = clr; current->liferemaining = con_notifytime.GetFloat(); break; }
while ( m_NotifyText.Count() > 0 && ( m_NotifyText.Count() >= con_times.GetInt() ) ) { m_NotifyText.Remove( 0 ); } }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CConPanel::ClearNotify() { // Protect against background modifications to m_NotifyText.
AUTO_LOCK( g_AsyncNotifyTextMutex );
m_NotifyText.RemoveAll(); }
void CConPanel::ApplySchemeSettings( vgui::IScheme *pScheme ) { BaseClass::ApplySchemeSettings( pScheme );
// Console font
m_hFont = pScheme->GetFont( "DefaultSmallDropShadow", false ); m_hFontFixed = pScheme->GetFont( "DefaultFixedDropShadow", false ); }
int CConPanel::DrawText( vgui::HFont font, int x, int y, wchar_t *data ) { int len = DrawColoredText( font, x, y, 255, 255, 255, 255, data );
return len; }
//-----------------------------------------------------------------------------
// called when we're ticked...
//-----------------------------------------------------------------------------
bool CConPanel::ShouldDraw() { bool bVisible = false;
if ( m_bDrawDebugAreas ) { bVisible = true; }
// Should be invisible if there's no notifys and the console is up.
// and if the launcher isn't active
if ( !Con_IsVisible() ) { // Protect against background modifications to m_NotifyText.
AUTO_LOCK( g_AsyncNotifyTextMutex );
int i; int c = m_NotifyText.Count(); for ( i = c - 1; i >= 0; i-- ) { CNotifyText *notify = &m_NotifyText[ i ];
notify->liferemaining -= host_frametime;
if ( notify->liferemaining <= 0.0f ) { m_NotifyText.Remove( i ); continue; } bVisible = true; } } else { bVisible = true; }
return bVisible; }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CConPanel::DrawNotify( void ) { int x = 8; int y = 5;
if ( !m_hFontFixed ) return;
// notify area only draws in developer mode
if ( !developer.GetBool() ) return;
// don't render notify area into movies, either
if ( cl_movieinfo.IsRecording( ) ) { return; }
vgui::surface()->DrawSetTextFont( m_hFontFixed );
int fontTall = vgui::surface()->GetFontTall( m_hFontFixed ) + 1;
// Protect against background modifications to m_NotifyText.
// DEADLOCK WARNING: Cannot call DrawColoredText while holding g_AsyncNotifyTextMutex or
// deadlock can occur while MatQueue0 holds material system lock and attempts to add text
// to m_NotifyText.
CUtlVector< CNotifyText > textToDraw; { AUTO_LOCK( g_AsyncNotifyTextMutex ); textToDraw = m_NotifyText; }
int c = textToDraw.Count(); for ( int i = 0; i < c; i++ ) { CNotifyText *notify = &textToDraw[ i ];
float timeleft = notify->liferemaining; Color clr = notify->clr;
if ( timeleft < .5f ) { float f = clamp( timeleft, 0.0f, .5f ) / .5f;
clr[3] = (int)( f * 255.0f );
if ( i == 0 && f < 0.2f ) { y -= fontTall * ( 1.0f - f / 0.2f ); } } else { clr[3] = 255; }
DrawColoredText( m_hFontFixed, x, y, clr[0], clr[1], clr[2], clr[3], notify->text );
y += fontTall; } }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
ConVar con_nprint_bgalpha( "con_nprint_bgalpha", "50", 0, "Con_NPrint background alpha." ); ConVar con_nprint_bgborder( "con_nprint_bgborder", "5", 0, "Con_NPrint border size." );
void CConPanel::DrawDebugAreas( void ) { if ( !m_bDrawDebugAreas ) return;
// Find the top and bottom of all the nprint text so we can draw a box behind it.
int left=99999, top=99999, right=-99999, bottom=-99999; if ( con_nprint_bgalpha.GetInt() ) { // First, figure out the bounds of all the con_nprint text.
if ( ProcessNotifyLines( left, top, right, bottom, false ) ) { int b = con_nprint_bgborder.GetInt();
// Now draw a box behind it.
vgui::surface()->DrawSetColor( 0, 0, 0, con_nprint_bgalpha.GetInt() ); vgui::surface()->DrawFilledRect( left-b, top-b, right+b, bottom+b ); } } // Now draw the text.
if ( ProcessNotifyLines( left, top, right, bottom, true ) == 0 ) { // Have all notifies expired?
m_bDrawDebugAreas = false; } }
int CConPanel::ProcessNotifyLines( int &left, int &top, int &right, int &bottom, bool bDraw ) { int count = 0; int y = 20;
for ( int i = 0; i < MAX_DBG_NOTIFY; i++ ) { if ( realtime < da_notify[i].expire || da_notify[i].expire == -1 ) { // If it's marked this way, only draw it once.
if ( da_notify[i].expire == -1 && bDraw ) { da_notify[i].expire = realtime - 1; } int len; int x;
vgui::HFont font = da_notify[i].fixed_width_font ? m_hFontFixed : m_hFont ;
int fontTall = vgui::surface()->GetFontTall( m_hFontFixed ) + 1;
len = DrawTextLen( font, da_notify[i].szNotify ); x = videomode->GetModeStereoWidth() - 10 - len;
if ( y + fontTall > videomode->GetModeStereoHeight() - 20 ) return count;
count++; y = 20 + 10 * i;
if ( bDraw ) { DrawColoredText( font, x, y, da_notify[i].color[0] * 255, da_notify[i].color[1] * 255, da_notify[i].color[2] * 255, 255, da_notify[i].szNotify ); }
if ( da_notify[i].szNotify[0] ) { // Extend the bounds.
left = min( left, x ); top = min( top, y ); right = max( right, x+len ); bottom = max( bottom, y+fontTall ); }
y += fontTall; } }
return count; }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CConPanel::Paint() { VPROF( "CConPanel::Paint" );
#if !defined( SWDS ) && !defined( DEDICATED )
if ( IsPC() && !g_ClientDLL->ShouldDrawDropdownConsole() ) return; #endif
DrawDebugAreas();
DrawNotify(); // only draw notify in game
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CConPanel::PaintBackground() { if ( !Con_IsVisible() ) return;
int wide = GetWide(); char ver[ 100 ]; Q_snprintf(ver, sizeof( ver ), "Source Engine %i (build %d)", PROTOCOL_VERSION, build_number() ); wchar_t unicode[ 200 ]; g_pVGuiLocalize->ConvertANSIToUnicode( ver, unicode, sizeof( unicode ) );
vgui::surface()->DrawSetTextColor( Color( 255, 255, 255, 255 ) ); int x = wide - DrawTextLen( m_hFont, unicode ) - 2; DrawText( m_hFont, x, 0, unicode );
if ( cl.IsActive() ) { if ( cl.m_NetChannel->IsLoopback() ) { Q_snprintf(ver, sizeof( ver ), "Map '%s'", cl.m_szLevelBaseName ); } else { Q_snprintf(ver, sizeof( ver ), "Server '%s' Map '%s'", cl.m_NetChannel->GetRemoteAddress().ToString(), cl.m_szLevelBaseName ); } wchar_t wUnicode[ 200 ]; g_pVGuiLocalize->ConvertANSIToUnicode( ver, wUnicode, sizeof( wUnicode ) );
int tall = vgui::surface()->GetFontTall( m_hFont );
x = wide - DrawTextLen( m_hFont, wUnicode ) - 2; DrawText( m_hFont, x, tall + 1, wUnicode ); } }
//-----------------------------------------------------------------------------
// Purpose: Creates the Console VGUI object
//-----------------------------------------------------------------------------
static CConPanel *conPanel = NULL;
void Con_CreateConsolePanel( vgui::Panel *parent ) { conPanel = new CConPanel( parent ); if (conPanel) { conPanel->SetVisible(false); } }
vgui::Panel* Con_GetConsolePanel() { return conPanel; }
static ConCommand toggleconsole("toggleconsole", Con_ToggleConsole_f, "Show/hide the console.", FCVAR_DONTRECORD ); static ConCommand hideconsole("hideconsole", Con_HideConsole_f, "Hide the console.", FCVAR_DONTRECORD ); static ConCommand showconsole("showconsole", Con_ShowConsole_f, "Show the console.", FCVAR_DONTRECORD ); static ConCommand clear("clear", Con_Clear_f, "Clear all console output.", FCVAR_DONTRECORD );
#endif // SWDS
|