diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /engine/console.cpp | |
| download | archived-source-engine-2018-hl2-src-3bf9df6b2785fa6d951086978a3e66f49427166a.tar.xz archived-source-engine-2018-hl2-src-3bf9df6b2785fa6d951086978a3e66f49427166a.zip | |
Diffstat (limited to 'engine/console.cpp')
| -rw-r--r-- | engine/console.cpp | 1330 |
1 files changed, 1330 insertions, 0 deletions
diff --git a/engine/console.cpp b/engine/console.cpp new file mode 100644 index 0000000..f2a2ba7 --- /dev/null +++ b/engine/console.cpp @@ -0,0 +1,1330 @@ +//========= 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 |