diff options
Diffstat (limited to 'game/client/cstrike/VGUI/cstrikeclientscoreboard.cpp')
| -rw-r--r-- | game/client/cstrike/VGUI/cstrikeclientscoreboard.cpp | 1596 |
1 files changed, 1596 insertions, 0 deletions
diff --git a/game/client/cstrike/VGUI/cstrikeclientscoreboard.cpp b/game/client/cstrike/VGUI/cstrikeclientscoreboard.cpp new file mode 100644 index 0000000..eb2a652 --- /dev/null +++ b/game/client/cstrike/VGUI/cstrikeclientscoreboard.cpp @@ -0,0 +1,1596 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "hud.h" +#include "cstrikeclientscoreboard.h" +#include "c_team.h" +#include "c_cs_playerresource.h" +#include "c_cs_player.h" +#include "cs_gamerules.h" +#include "backgroundpanel.h" +#include "clientmode.h" + +#include <KeyValues.h> + +#include <vgui/IScheme.h> +#include <vgui/ILocalize.h> +#include <vgui/ISurface.h> +#include <vgui/IVGui.h> +#include <vgui_controls/SectionedListPanel.h> +#include <vgui_controls/ScalableImagePanel.h> +#include "VGuiMatSurface/IMatSystemSurface.h" + +#include "voice_status.h" +#include "vgui_avatarimage.h" + +#include "achievementmgr.h" +#include "engine/imatchmaking.h" + +using namespace vgui; + +const int kInvalidImageID = -1; +const int kMaxMVPCount = 9999; +const float kScaleMVP = 0.75f; // scale of the MVP star relative to the player name height +const float kUpdateInterval = 1.0f; // how often the scoreboard refreshes +const float kTeamScoreMargin = 0.15f; // margin as a ratio of avatar height +const float kTeamScoreLineLeadingRatio = 0.25f; // padding as a ratio of avatar height + +// CT player data colors +ConVar cl_scoreboard_ct_color_red( "cl_scoreboard_ct_color_red", "150", FCVAR_CLIENTDLL | FCVAR_ARCHIVE, "Scoreboard CT player data red channel", true, 0.0f, true, 255.0f ); +ConVar cl_scoreboard_ct_color_green( "cl_scoreboard_ct_color_green", "200", FCVAR_CLIENTDLL | FCVAR_ARCHIVE, "Scoreboard CT player data green channel", true, 0.0f, true, 255.0f ); +ConVar cl_scoreboard_ct_color_blue( "cl_scoreboard_ct_color_blue", "255", FCVAR_CLIENTDLL | FCVAR_ARCHIVE, "Scoreboard CT player data blue channel", true, 0.0f, true, 255.0f ); + +// T player data colors +ConVar cl_scoreboard_t_color_red( "cl_scoreboard_t_color_red", "240", FCVAR_CLIENTDLL | FCVAR_ARCHIVE, "Scoreboard T player data red channel", true, 0.0f, true, 255.0f ); +ConVar cl_scoreboard_t_color_green( "cl_scoreboard_t_color_green", "90", FCVAR_CLIENTDLL | FCVAR_ARCHIVE, "Scoreboard T player data green channel", true, 0.0f, true, 255.0f ); +ConVar cl_scoreboard_t_color_blue( "cl_scoreboard_t_color_blue", "90", FCVAR_CLIENTDLL | FCVAR_ARCHIVE, "Scoreboard T player data blue channel", true, 0.0f, true, 255.0f ); + +// Dead player data colors +ConVar cl_scoreboard_dead_color_red( "cl_scoreboard_dead_color_red", "125", FCVAR_CLIENTDLL | FCVAR_ARCHIVE, "Scoreboard dead player data red channel", true, 0.0f, true, 255.0f ); +ConVar cl_scoreboard_dead_color_green( "cl_scoreboard_dead_color_green", "125", FCVAR_CLIENTDLL | FCVAR_ARCHIVE, "Scoreboard dead player data green channel", true, 0.0f, true, 255.0f ); +ConVar cl_scoreboard_dead_color_blue( "cl_scoreboard_dead_color_blue", "125", FCVAR_CLIENTDLL | FCVAR_ARCHIVE, "Scoreboard dead player data blue channel", true, 0.0f, true, 255.0f ); + +// Clan colors +ConVar cl_scoreboard_clan_ct_color_red( "cl_scoreboard_clan_ct_color_red", "150", FCVAR_CLIENTDLL | FCVAR_ARCHIVE, "Scoreboard CT player clan tag red channel", true, 0.0f, true, 255.0f ); +ConVar cl_scoreboard_clan_ct_color_green( "cl_scoreboard_clan_ct_color_green", "200", FCVAR_CLIENTDLL | FCVAR_ARCHIVE, "Scoreboard CT player clan tag green channel", true, 0.0f, true, 255.0f ); +ConVar cl_scoreboard_clan_ct_color_blue( "cl_scoreboard_clan_ct_color_blue", "255", FCVAR_CLIENTDLL | FCVAR_ARCHIVE, "Scoreboard CT player clan tag blue channel", true, 0.0f, true, 255.0f ); + +ConVar cl_scoreboard_clan_t_color_red( "cl_scoreboard_clan_t_color_red", "240", FCVAR_CLIENTDLL | FCVAR_ARCHIVE, "Scoreboard T player clan tag red channel", true, 0.0f, true, 255.0f ); +ConVar cl_scoreboard_clan_t_color_green( "cl_scoreboard_clan_t_color_green", "90", FCVAR_CLIENTDLL | FCVAR_ARCHIVE, "Scoreboard T player clan tag green channel", true, 0.0f, true, 255.0f ); +ConVar cl_scoreboard_clan_t_color_blue( "cl_scoreboard_clan_t_color_blue", "90", FCVAR_CLIENTDLL | FCVAR_ARCHIVE, "Scoreboard T player clan tag blue channel", true, 0.0f, true, 255.0f ); + +ConVar cl_scoreboard_dead_clan_color_red( "cl_scoreboard_dead_clan_color_red", "125", FCVAR_CLIENTDLL | FCVAR_ARCHIVE, "Scoreboard dead player clan tag red channel", true, 0.0f, true, 255.0f ); +ConVar cl_scoreboard_dead_clan_color_green( "cl_scoreboard_dead_clan_color_green", "125", FCVAR_CLIENTDLL | FCVAR_ARCHIVE, "Scoreboard dead player clan tag green channel", true, 0.0f, true, 255.0f ); +ConVar cl_scoreboard_dead_clan_color_blue( "cl_scoreboard_dead_clan_color_blue", "125", FCVAR_CLIENTDLL | FCVAR_ARCHIVE, "Scoreboard dead player clan tag blue channel", true, 0.0f, true, 255.0f ); + + +// [tj] These ConVars are defined at various places in the global scope. Just declaring them here so we can use them +extern ConVar mp_winlimit; +extern ConVar mp_maxrounds; +extern ConVar mp_timelimit; + + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +CCSClientScoreBoardDialog::CCSClientScoreBoardDialog( IViewPort *pViewPort ) : CClientScoreBoardDialog( pViewPort ), + m_DeadPlayerDataColor( 125, 125, 125, 125 ), + m_PlayerDataBgColor( 0, 0, 0, 0 ), + m_DeadPlayerClanColor( 125, 125, 125, 125 ) +{ + m_teamDisplayT.playerDataColor = Color( 240, 90, 90, 255 ); + m_teamDisplayT.playerClanColor = Color( 240, 90, 90, 255 ); + + m_teamDisplayCT.playerDataColor = Color( 150, 200, 255, 255 ); + m_teamDisplayCT.playerClanColor = Color( 150, 200, 255, 255 ); + + m_iImageDead = kInvalidImageID; + m_iImageDominated = kInvalidImageID; + m_iImageNemesis = kInvalidImageID; + m_iImageBomb = kInvalidImageID; + m_iImageVIP = kInvalidImageID; + m_iImageFriend = kInvalidImageID; + m_iImageNemesisDead = kInvalidImageID; + m_iImageDominationDead = kInvalidImageID; + + m_pWinConditionLabel = new Label( this, "WinConditionLabel", "" ); + m_pClockLabel = new Label( this, "Icon_Clock", "" ); + + m_pLabelMapName = new Label( this, "MapName", "" ); + m_pServerLabel = new Label( this, "ServerNameLabel", "" ); + + ListenForGameEvent( "server_spawn" ); + ListenForGameEvent( "game_newmap" ); + ListenForGameEvent( "match_end_conditions" ); + ListenForGameEvent( "cs_win_panel_match" ); + + SetDialogVariable( "server", "" ); + SetVisible( false ); + SetProportional(true); + SetPaintBorderEnabled(false); + SetScheme( "ClientScheme" ); + + // [pfreese] Make the scoreboard a popup so it renders over the chat interface (which is also a popup). Hacky. + MakePopup(); + + m_listItemFont = NULL; + + m_LocalPlayerItemID = -1; + m_iImageMVP = -1; + + m_gameOver = false; + + if ( g_pClientMode && + g_pClientMode->GetMapName() ) + { + V_wcsncpy( m_pMapName, g_pClientMode->GetMapName(), sizeof( m_pMapName ) ); + SetDialogVariable( "mapname", m_pMapName ); + m_pLabelMapName->SetVisible( true ); + } + + SetKeyBoardInputEnabled( true ); + + for ( int i = 0; i < cMaxScoreLines; ++i ) + { + PlayerDisplay* pPlayerDisplay; + + pPlayerDisplay = &m_teamDisplayCT.playerDisplay[i]; + pPlayerDisplay->pClanLabel = NULL; + pPlayerDisplay->pNameLabel = NULL; + pPlayerDisplay->pScoreLabel = NULL; + pPlayerDisplay->pDeathsLabel = NULL; + pPlayerDisplay->pPingLabel = NULL; + pPlayerDisplay->pMVPCountLabel = NULL; + pPlayerDisplay->pAvatar = NULL; + pPlayerDisplay->pStatusImage = NULL; + pPlayerDisplay->pMVPImage = NULL; + pPlayerDisplay->pSelect = NULL; + + pPlayerDisplay = &m_teamDisplayT.playerDisplay[i]; + pPlayerDisplay->pClanLabel = NULL; + pPlayerDisplay->pNameLabel = NULL; + pPlayerDisplay->pScoreLabel = NULL; + pPlayerDisplay->pDeathsLabel = NULL; + pPlayerDisplay->pPingLabel = NULL; + pPlayerDisplay->pMVPCountLabel = NULL; + pPlayerDisplay->pAvatar = NULL; + pPlayerDisplay->pStatusImage = NULL; + pPlayerDisplay->pMVPImage = NULL; + pPlayerDisplay->pSelect = NULL; + } + + m_pServerName[0] = L'\0'; + m_pStatsEnabled[0] = L'\0'; + m_pStatsDisabled[0] = L'\0'; + + m_MVPXOffset = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +CCSClientScoreBoardDialog::~CCSClientScoreBoardDialog() +{ + for (int i = 0; i < cMaxScoreLines; ++i) + { + PlayerDisplay* pPlayerDisplay; + + pPlayerDisplay = &m_teamDisplayCT.playerDisplay[i]; + delete pPlayerDisplay->pClanLabel; + delete pPlayerDisplay->pNameLabel; + delete pPlayerDisplay->pScoreLabel; + delete pPlayerDisplay->pDeathsLabel; + delete pPlayerDisplay->pPingLabel; + delete pPlayerDisplay->pMVPCountLabel; + delete pPlayerDisplay->pAvatar; + delete pPlayerDisplay->pStatusImage; + delete pPlayerDisplay->pMVPImage; + delete pPlayerDisplay->pSelect; + + pPlayerDisplay = &m_teamDisplayT.playerDisplay[i]; + delete pPlayerDisplay->pClanLabel; + delete pPlayerDisplay->pNameLabel; + delete pPlayerDisplay->pScoreLabel; + delete pPlayerDisplay->pDeathsLabel; + delete pPlayerDisplay->pPingLabel; + delete pPlayerDisplay->pMVPCountLabel; + delete pPlayerDisplay->pAvatar; + delete pPlayerDisplay->pStatusImage; + delete pPlayerDisplay->pMVPImage; + delete pPlayerDisplay->pSelect; + } +} + +const wchar_t *LocalizeFindSafe( const char *pTokenName ) +{ + const wchar_t *pStr = g_pVGuiLocalize->Find( pTokenName ); + return pStr ? pStr : L"\0"; +} + +//----------------------------------------------------------------------------- +// Purpose: Apply scheme settings +//----------------------------------------------------------------------------- +void CCSClientScoreBoardDialog::ApplySchemeSettings( vgui::IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + // + // [smessick] Note: ApplySchemeSettings is called multiple times for the scoreboard. + // Therefore, we must make sure to delete previously allocated items. + // + + LoadControlSettings( "Resource/UI/scoreboard.res" ); + + //Just used for a background alpha value. 50% opacity + m_listItemFont = pScheme->GetFont( "ScoreboardBody_1", IsProportional() ); + m_listItemFontSmaller = pScheme->GetFont( "ScoreboardBody_2", IsProportional() ); + m_listItemFontSmallest = pScheme->GetFont( "ScoreboardBody_3", IsProportional() ); + m_MVPFont = pScheme->GetFont( "ScoreboardMVP", IsProportional() ); + + SetBgColor( Color( 0, 0, 0, 0 ) ); + SetBorder( pScheme->GetBorder( "BaseBorder" ) ); + + // turn off the default player list since we have our own + if ( m_pPlayerList ) + { + m_pPlayerList->SetVisible( false ); + } + + // Set the player colors from the convars. + UpdatePlayerColors(); + + m_MVPXOffset = scheme()->GetProportionalScaledValueEx( GetScheme(), 2 ); + + SetupTeamDisplay( m_teamDisplayCT, "CT" ); + SetupTeamDisplay( m_teamDisplayT, "T" ); + + // Set the server name (in the case of a resolution change). + if ( m_pServerName[0] == L'\0' && + g_pClientMode->GetServerName() != NULL ) + { + V_wcsncpy( m_pServerName, g_pClientMode->GetServerName(), sizeof( m_pServerName ) ); + } + + // Cache the stats enabled string. + if ( m_pStatsEnabled[0] == L'\0' ) + { + V_wcsncpy( m_pStatsEnabled, LocalizeFindSafe( "#Cstrike_Scoreboard_StatsEnabled" ), sizeof( m_pStatsEnabled ) ); + } + + // Cache the stats disabled string. + if ( m_pStatsDisabled[0] == L'\0' ) + { + V_wcsncpy( m_pStatsDisabled, LocalizeFindSafe( "#Cstrike_Scoreboard_StatsDisabled" ), sizeof( m_pStatsDisabled ) ); + } + + SetVisible( false ); +} + + +void CCSClientScoreBoardDialog::SetupTeamDisplay( TeamDisplayInfo& teamDisplay, const char* szTeamPrefix ) +{ + const int selectMargin = scheme()->GetProportionalScaledValueEx( GetScheme(), 1 ); + const int mvpLabelWidth = scheme()->GetProportionalScaledValueEx( GetScheme(), 20 ); + const int mvpLabelYOffset = scheme()->GetProportionalScaledValueEx( GetScheme(), 2 ); + + char tmpName[32]; + + V_snprintf( tmpName, sizeof(tmpName), "%sPlayerArea", szTeamPrefix ); + Panel *pPlayerArea = FindChildByName( tmpName ); + + V_snprintf( tmpName, sizeof(tmpName), "%sPlayerAvatar0", szTeamPrefix ); + Panel *pPlayerAvatar0 = FindChildByName( tmpName ); + + V_snprintf( tmpName, sizeof(tmpName), "%sPlayerClan0", szTeamPrefix ); + Panel *pPlayerClan0 = FindChildByName( tmpName ); + + V_snprintf( tmpName, sizeof(tmpName), "%sPlayerName0", szTeamPrefix ); + Panel *pPlayerName0 = FindChildByName( tmpName ); + + V_snprintf( tmpName, sizeof(tmpName), "%sPlayerStatus0", szTeamPrefix ); + Panel *pPlayerStatus0 = FindChildByName( tmpName ); + + V_snprintf( tmpName, sizeof(tmpName), "%sPlayerScore0", szTeamPrefix ); + Panel *pPlayerScore0 = FindChildByName( tmpName ); + + V_snprintf( tmpName, sizeof(tmpName), "%sPlayerDeaths0", szTeamPrefix ); + Panel *pPlayerDeaths0 = FindChildByName( tmpName ); + + V_snprintf( tmpName, sizeof(tmpName), "%sPlayerLatency0", szTeamPrefix ); + Panel *pPlayerLatency0 = FindChildByName( tmpName ); + + // Get the bounds of the player area. + int playerX = 0; + int playerY = 0; + int playerWide = 0; + int playerTall = 0; + if ( pPlayerArea ) + pPlayerArea->GetBounds( playerX, playerY, playerWide, playerTall ); + + // determine the line height needed + int x, y, wide, tall; + teamDisplay.scoreAreaLineHeight = 0; + teamDisplay.scoreAreaMinX = INT_MAX; + teamDisplay.scoreAreaMaxX = INT_MIN; + + if ( pPlayerAvatar0 != NULL ) + { + pPlayerAvatar0->GetBounds( x, y, wide, tall ); + teamDisplay.scoreAreaLineHeight = MAX( tall, teamDisplay.scoreAreaLineHeight ); + teamDisplay.scoreAreaMinX = MIN( teamDisplay.scoreAreaMinX, x ); + teamDisplay.scoreAreaMaxX = MAX( teamDisplay.scoreAreaMaxX, x + wide ); + } + if ( pPlayerClan0 != NULL ) + { + pPlayerClan0->GetBounds( x, y, wide, tall ); + teamDisplay.scoreAreaLineHeight = MAX( tall, teamDisplay.scoreAreaLineHeight ); + teamDisplay.scoreAreaMinX = MIN( teamDisplay.scoreAreaMinX, x ); + teamDisplay.scoreAreaMaxX = MAX( teamDisplay.scoreAreaMaxX, x + wide ); + } + if ( pPlayerName0 != NULL ) + { + pPlayerName0->GetBounds( x, y, wide, tall ); + teamDisplay.scoreAreaLineHeight = MAX( tall, teamDisplay.scoreAreaLineHeight ); + teamDisplay.scoreAreaMinX = MIN( teamDisplay.scoreAreaMinX, x ); + teamDisplay.scoreAreaMaxX = MAX( teamDisplay.scoreAreaMaxX, x + wide ); + } + if ( pPlayerScore0 != NULL ) + { + pPlayerScore0->GetBounds( x, y, wide, tall ); + teamDisplay.scoreAreaLineHeight = MAX( tall, teamDisplay.scoreAreaLineHeight ); + teamDisplay.scoreAreaMinX = MIN( teamDisplay.scoreAreaMinX, x ); + teamDisplay.scoreAreaMaxX = MAX( teamDisplay.scoreAreaMaxX, x + wide ); + } + if ( pPlayerDeaths0 != NULL ) + { + pPlayerDeaths0->GetBounds( x, y, wide, tall ); + teamDisplay.scoreAreaLineHeight = MAX( tall, teamDisplay.scoreAreaLineHeight ); + teamDisplay.scoreAreaMinX = MIN( teamDisplay.scoreAreaMinX, x ); + teamDisplay.scoreAreaMaxX = MAX( teamDisplay.scoreAreaMaxX, x + wide ); + } + if ( pPlayerLatency0 != NULL ) + { + pPlayerLatency0->GetBounds( x, y, wide, tall ); + teamDisplay.scoreAreaLineHeight = MAX( tall, teamDisplay.scoreAreaLineHeight ); + teamDisplay.scoreAreaMinX = MIN( teamDisplay.scoreAreaMinX, x ); + teamDisplay.scoreAreaMaxX = MAX( teamDisplay.scoreAreaMaxX, x + wide ); + } + if ( pPlayerStatus0 != NULL ) + { + pPlayerStatus0->GetBounds( x, y, wide, tall ); + teamDisplay.scoreAreaLineHeight = MAX( tall, teamDisplay.scoreAreaLineHeight ); + teamDisplay.scoreAreaMinX = MIN( teamDisplay.scoreAreaMinX, x ); + teamDisplay.scoreAreaMaxX = MAX( teamDisplay.scoreAreaMaxX, x + wide ); + } + + int marginY = RoundFloatToInt(teamDisplay.scoreAreaLineHeight * kTeamScoreMargin); + + teamDisplay.scoreAreaInnerHeight = playerTall - 2 * marginY; + teamDisplay.scoreAreaLinePreferredLeading = RoundFloatToInt(teamDisplay.scoreAreaLineHeight * kTeamScoreLineLeadingRatio); + teamDisplay.scoreAreaStartY = playerY + marginY; + teamDisplay.maxPlayersVisible = MIN(cMaxScoreLines, teamDisplay.scoreAreaInnerHeight / teamDisplay.scoreAreaLineHeight); + + // Calculate the starting point for player data. + int startY = teamDisplay.scoreAreaStartY; + int spacingY = teamDisplay.scoreAreaLineHeight + teamDisplay.scoreAreaLinePreferredLeading; + + for ( int i = 0; i < cMaxScoreLines; ++i, startY += spacingY ) + { + PlayerDisplay& playerDisplay = teamDisplay.playerDisplay[i]; + + int wide = 0; + int tall = 0; + int x = 0; + int y = 0; + + // avatar + if ( pPlayerAvatar0 != NULL ) + { + pPlayerAvatar0->GetBounds( x, y, wide, tall ); + V_snprintf( tmpName, 32, "_%s_playeravatar%d", szTeamPrefix, i ); + delete playerDisplay.pAvatar; + playerDisplay.pAvatar = (CAvatarImagePanel*)SETUP_PANEL( new CAvatarImagePanel( this, tmpName ) ); + playerDisplay.pAvatar->SetBounds( x, startY, wide, tall ); + playerDisplay.pAvatar->SetDefaultAvatar( scheme()->GetImage( &teamDisplay == &m_teamDisplayCT ? CSTRIKE_DEFAULT_CT_AVATAR : CSTRIKE_DEFAULT_T_AVATAR, true ) ); + playerDisplay.pAvatar->SetShouldDrawFriendIcon( false ); + playerDisplay.pAvatar->SetShouldScaleImage( true ); + playerDisplay.pAvatar->SetVisible( false ); + } + + // clan + if ( pPlayerClan0 != NULL ) + { + pPlayerClan0->GetBounds( x, y, wide, tall ); + V_snprintf( tmpName, 32, "_%s_playerclan%d", szTeamPrefix, i ); + delete playerDisplay.pClanLabel; + playerDisplay.pClanLabel = (Label*)SETUP_PANEL( new Label( this, tmpName, tmpName ) ); + playerDisplay.pClanLabel->SetFont( m_listItemFont ); + playerDisplay.pClanLabel->SetBounds( x, startY, wide, tall ); + playerDisplay.pClanLabel->SetBgColor( m_PlayerDataBgColor ); + playerDisplay.pClanLabel->SetFgColor( m_teamDisplayCT.playerClanColor ); + playerDisplay.pClanLabel->SetContentAlignment( Label::a_east ); + playerDisplay.pClanLabel->SetVisible( false ); + } + + // name + if ( pPlayerName0 != NULL ) + { + pPlayerName0->GetBounds( x, y, wide, tall ); + V_snprintf( tmpName, 32, "_%s_playername%d", szTeamPrefix, i ); + delete playerDisplay.pNameLabel; + playerDisplay.pNameLabel = (Label*)SETUP_PANEL( new Label( this, tmpName, tmpName ) ); + playerDisplay.pNameLabel->SetFont( m_listItemFont ); + playerDisplay.pNameLabel->SetBounds( x, startY, wide, tall); + playerDisplay.pNameLabel->SetBgColor( m_PlayerDataBgColor ); + playerDisplay.pNameLabel->SetFgColor( m_teamDisplayCT.playerDataColor ); + playerDisplay.pNameLabel->SetContentAlignment( Label::a_west ); + playerDisplay.pNameLabel->SetVisible( false ); + + // mvp + int wideMVP = RoundFloatToInt(teamDisplay.scoreAreaLineHeight * kScaleMVP); + int tallMVP = RoundFloatToInt(teamDisplay.scoreAreaLineHeight * kScaleMVP); + int yMVP = ( teamDisplay.scoreAreaLineHeight - tallMVP ) / 2; + + V_snprintf( tmpName, 32, "_%s_mvp%d", szTeamPrefix, i ); + delete playerDisplay.pMVPImage; + playerDisplay.pMVPImage = (ImagePanel*)SETUP_PANEL( new ImagePanel( this, tmpName ) ); + playerDisplay.pMVPImage->SetBounds( x, startY + yMVP, wideMVP, tallMVP ); + playerDisplay.pMVPImage->SetImage( "../hud/scoreboard_mvp" ); + playerDisplay.pMVPImage->SetShouldScaleImage( true ); + playerDisplay.pMVPImage->SetVisible( false ); + + // mvp count + V_snprintf( tmpName, 32, "_%s_mvpcount%d", szTeamPrefix, i ); + delete playerDisplay.pMVPCountLabel; + playerDisplay.pMVPCountLabel = (Label*)SETUP_PANEL( new Label( this, tmpName, "" ) ); + playerDisplay.pMVPCountLabel->SetFont( m_MVPFont ); + playerDisplay.pMVPCountLabel->SetBounds( 0, -mvpLabelYOffset, mvpLabelWidth, tallMVP ); + playerDisplay.pMVPCountLabel->SetVisible( false ); + + // Pin to the mvp image. + V_snprintf( tmpName, 32, "_%s_mvp%d", szTeamPrefix, i ); + playerDisplay.pMVPCountLabel->PinToSibling( tmpName, PIN_CENTER_LEFT, PIN_CENTER_RIGHT ); + } + + // score + if ( pPlayerScore0 != NULL ) + { + pPlayerScore0->GetBounds( x, y, wide, tall ); + V_snprintf( tmpName, 32, "_%s_playerscore%d", szTeamPrefix, i ); + delete playerDisplay.pScoreLabel; + playerDisplay.pScoreLabel = (Label*)SETUP_PANEL( new Label( this, tmpName, "" ) ); + playerDisplay.pScoreLabel->SetFont( m_listItemFont ); + playerDisplay.pScoreLabel->SetBounds( x, startY, wide, tall ); + playerDisplay.pScoreLabel->SetBgColor( m_PlayerDataBgColor ); + playerDisplay.pScoreLabel->SetFgColor( m_teamDisplayCT.playerDataColor ); + playerDisplay.pScoreLabel->SetContentAlignment( Label::a_center ); + playerDisplay.pScoreLabel->SetVisible( false ); + } + + // deaths + if ( pPlayerDeaths0 != NULL ) + { + pPlayerDeaths0->GetBounds( x, y, wide, tall ); + V_snprintf( tmpName, 32, "_%s_playerdeaths%d", szTeamPrefix, i ); + delete playerDisplay.pDeathsLabel; + playerDisplay.pDeathsLabel = (Label*)SETUP_PANEL( new Label( this, tmpName, "" ) ); + playerDisplay.pDeathsLabel->SetFont( m_listItemFont ); + playerDisplay.pDeathsLabel->SetBounds( x, startY, wide, tall ); + playerDisplay.pDeathsLabel->SetBgColor( m_PlayerDataBgColor ); + playerDisplay.pDeathsLabel->SetFgColor( m_teamDisplayCT.playerDataColor ); + playerDisplay.pDeathsLabel->SetContentAlignment( Label::a_center ); + playerDisplay.pDeathsLabel->SetVisible( false ); + } + + // latency + if ( pPlayerLatency0 != NULL ) + { + pPlayerLatency0->GetBounds( x, y, wide, tall ); + V_snprintf( tmpName, 32, "_%s_playerping%d", szTeamPrefix, i ); + delete playerDisplay.pPingLabel; + playerDisplay.pPingLabel = (Label*)SETUP_PANEL( new Label( this, tmpName, "" ) ); + playerDisplay.pPingLabel->SetFont( m_listItemFont ); + playerDisplay.pPingLabel->SetBounds( x, startY, wide, tall ); + playerDisplay.pPingLabel->SetBgColor( m_PlayerDataBgColor ); + playerDisplay.pPingLabel->SetFgColor( m_teamDisplayCT.playerDataColor ); + playerDisplay.pPingLabel->SetContentAlignment( Label::a_center ); + playerDisplay.pPingLabel->SetVisible( false ); + } + + // status + if ( pPlayerStatus0 != NULL ) + { + pPlayerStatus0->GetBounds( x, y, wide, tall ); + V_snprintf( tmpName, 32, "_%s_playerstatus%d", szTeamPrefix, i ); + delete playerDisplay.pStatusImage; + playerDisplay.pStatusImage = (ImagePanel*)SETUP_PANEL( new ImagePanel( this, tmpName ) ); + playerDisplay.pStatusImage->SetBounds( x, startY, wide, tall ); + playerDisplay.pStatusImage->SetImage( "../hud/scoreboard_dead" ); + playerDisplay.pStatusImage->SetShouldScaleImage( true ); + playerDisplay.pStatusImage->SetVisible( false ); + } + + // select + { + int x1 = teamDisplay.scoreAreaMinX - selectMargin; + int y1 = startY - selectMargin; + int x2 = teamDisplay.scoreAreaMaxX + selectMargin; + int y2 = startY + teamDisplay.scoreAreaLineHeight + selectMargin; + + V_snprintf( tmpName, 32, "_%s_playerselect%d", szTeamPrefix, i ); + delete playerDisplay.pSelect; + playerDisplay.pSelect = (ImagePanel*)SETUP_PANEL( new ImagePanel( this, tmpName ) ); + playerDisplay.pSelect->SetBounds( x1, y1, x2 - x1, y2 - y1 ); + playerDisplay.pSelect->SetImage( "../vgui/scoreboard/scoreboard-select" ); + playerDisplay.pSelect->SetShouldScaleImage( true ); + playerDisplay.pSelect->SetVisible( false ); + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Used for sorting players +//----------------------------------------------------------------------------- +int CCSClientScoreBoardDialog::PlayerSortFunction( PlayerScoreInfo* const* pLeft, PlayerScoreInfo* const* pRight ) +{ + // a return value < 0 puts pLeft earlier in the list, > 0 puts pRight earlier + const PlayerScoreInfo* pPlayer1 = *pLeft; + const PlayerScoreInfo* pPlayer2 = *pRight; + Assert( pPlayer1 && pPlayer2 ); + + // bail out early if either player is an empty slot, i.e. has a player index of -1 + if( pPlayer1->playerIndex == -1 ) + return 1; + if( pPlayer2->playerIndex == -1 ) + return -1; + + // first compare scores + if ( pPlayer1->frags > pPlayer2->frags ) + return -1; + if ( pPlayer1->frags < pPlayer2->frags ) + return 1; + + // second compare deaths + if ( pPlayer1->deaths > pPlayer2->deaths ) + return 1; + if ( pPlayer1->deaths < pPlayer2->deaths ) + return -1; + + // if score and deaths are the same, use player index to get deterministic sort + if ( pPlayer1->playerIndex < pPlayer2->playerIndex ) + return -1; + else + return 1; +} + + +//----------------------------------------------------------------------------- +// Purpose: Updates the dialog +//----------------------------------------------------------------------------- +void CCSClientScoreBoardDialog::Update() +{ + if ( m_pServerLabel ) + { + m_pServerLabel->SetText( m_pServerName ); + } + + // Update the stats status. + CAchievementMgr *pAchievementMgr = dynamic_cast<CAchievementMgr*>( engine->GetAchievementMgr() ); + if ( pAchievementMgr != NULL && + pAchievementMgr->CheckAchievementsEnabled() ) + { + SetDialogVariable( "statsstatus", m_pStatsEnabled ); + } + else + { + SetDialogVariable( "statsstatus", m_pStatsDisabled ); + } + + UpdateTeamInfo(); + UpdatePlayerList(); + UpdateSpectatorList(); + UpdateHLTVList(); + UpdateMatchEndText(); + UpdateMvpElements(); + + // update every second + m_fNextUpdateTime = gpGlobals->curtime + kUpdateInterval; + + // Catch the case where we call ShowPanel before ApplySchemeSettings, eg when going from windowed <-> fullscreen + if ( m_pImageList == NULL ) + { + InvalidateLayout( true, true ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Updates information about teams +//----------------------------------------------------------------------------- +void CCSClientScoreBoardDialog::UpdateTeamInfo() +{ + if ( g_PR == NULL ) + { + return; + } + + // update the team sections in the scoreboard + for ( int teamIndex = TEAM_TERRORIST; teamIndex <= TEAM_CT; teamIndex++ ) + { + wchar_t *teamName = NULL; + C_Team *team = GetGlobalTeam( teamIndex ); + if ( team ) + { + // choose dialog variables to set depending on team + const char *pDialogVarTeamName = NULL; + const char *pDialogVarAliveCount = NULL; + const char *pDialogVarTeamScore = NULL; + switch ( teamIndex ) + { + case TEAM_TERRORIST: + teamName = g_pVGuiLocalize->Find( "#Cstrike_Team_T" ); + pDialogVarTeamName = "t_teamname"; + pDialogVarAliveCount = "t_alivecount"; + pDialogVarTeamScore = "t_totalteamscore"; + break; + case TEAM_CT: + teamName = g_pVGuiLocalize->Find( "#Cstrike_Team_CT" ); + pDialogVarTeamName = "ct_teamname"; + pDialogVarAliveCount = "ct_alivecount"; + pDialogVarTeamScore = "ct_totalteamscore"; + break; + default: + Assert( false ); + break; + } + + // Set the team name if it hasn't been set. + wchar_t name[64]; + if ( !teamName && team && team->Get_Name() != NULL ) + { + g_pVGuiLocalize->ConvertANSIToUnicode( team->Get_Name(), name, sizeof( name ) ); + teamName = name; + } + + // Count the players on the team. + int numPlayers = 0; + int numAlive = 0; + for ( int playerIndex = 1 ; playerIndex <= MAX_PLAYERS; playerIndex++ ) + { + if ( g_PR->IsConnected( playerIndex ) && g_PR->GetTeam( playerIndex ) == teamIndex ) + { + numPlayers++; + if ( g_PR->IsAlive( playerIndex ) ) + { + ++numAlive; + } + } + } + + SetDialogVariable( pDialogVarTeamName, teamName ); + + // Team score + wchar_t wNumScore[16]; + V_snwprintf( wNumScore, ARRAYSIZE( wNumScore ), L"%i", team->Get_Score() ); + SetDialogVariable( pDialogVarTeamScore, wNumScore ); + + // Number of alive players + wchar_t numAliveString[32]; + V_snwprintf( numAliveString, ARRAYSIZE( numAliveString ), L"%i / %i", numAlive, numPlayers); + SetDialogVariable( pDialogVarAliveCount, numAliveString ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Helper to shrink font size when a string gets too long for its label +//----------------------------------------------------------------------------- +void CCSClientScoreBoardDialog::AdjustFontToFit( const char *pString, vgui::Label *pLabel ) +{ + if ( !pString || !pLabel ) + return; + + int len = Q_strlen( pString ); + if ( !len ) + return; + + int arraySize = len + 1; + wchar_t *pWideString = new wchar_t[arraySize]; + V_UTF8ToUnicode( pString, pWideString, (arraySize * sizeof(wchar_t)) ); + + int stringWidth, stringHeight; + g_pMatSystemSurface->GetTextSize( m_listItemFont, pWideString, stringWidth, stringHeight ); + + int labelWidth = pLabel->GetWide(); + if ( stringWidth <= labelWidth ) + { + pLabel->SetFont( m_listItemFont ); + } + else + { + g_pMatSystemSurface->GetTextSize( m_listItemFontSmaller, pWideString, stringWidth, stringHeight ); + if ( stringWidth <= labelWidth ) + { + pLabel->SetFont( m_listItemFontSmaller ); + } + else + { + pLabel->SetFont(m_listItemFontSmallest); + } + } + delete [] pWideString; +} + +//----------------------------------------------------------------------------- +// Purpose: Updates the player list +//----------------------------------------------------------------------------- +void CCSClientScoreBoardDialog::UpdatePlayerList() +{ + m_teamDisplayT.playerScores.PurgeAndDeleteElements(); + m_teamDisplayCT.playerScores.PurgeAndDeleteElements(); + + C_CSPlayer *pLocalPlayer = C_CSPlayer::GetLocalCSPlayer(); + if ( !pLocalPlayer ) + return; + + // Set the player colors from the convars. + UpdatePlayerColors(); + + for( int playerIndex = 1 ; playerIndex <= MAX_PLAYERS; playerIndex++ ) + { + if( g_PR->IsConnected( playerIndex ) ) + { + PlayerScoreInfo* playerScoreInfo = new PlayerScoreInfo; + if ( !GetPlayerScoreInfo( playerIndex, *playerScoreInfo ) ) + { + delete playerScoreInfo; + continue; + } + + if ( g_PR->GetTeam( playerIndex ) == TEAM_TERRORIST ) + { + m_teamDisplayT.playerScores.AddToTail(playerScoreInfo); + } + else if ( g_PR->GetTeam( playerIndex ) == TEAM_CT ) + { + m_teamDisplayCT.playerScores.AddToTail(playerScoreInfo); + } + else + { + // [mhansen] make sure we don't leak here + delete playerScoreInfo; + } + } + } + + // Sort the lists of players + m_teamDisplayT.playerScores.Sort(PlayerSortFunction); + m_teamDisplayCT.playerScores.Sort(PlayerSortFunction); + + // Force the local player to be visible when he is below the visible portion of the sorted list + if ( pLocalPlayer->GetTeamNumber() == TEAM_TERRORIST ) + ForceLocalPlayerVisible(m_teamDisplayT); + else if ( pLocalPlayer->GetTeamNumber() == TEAM_CT ) + ForceLocalPlayerVisible(m_teamDisplayCT); + + UpdateTeamPlayerDisplay(m_teamDisplayT); + UpdateTeamPlayerDisplay(m_teamDisplayCT); +} + +void CCSClientScoreBoardDialog::UpdateTeamPlayerDisplay( TeamDisplayInfo& teamDisplay ) +{ + const int selectMargin = scheme()->GetProportionalScaledValueEx( GetScheme(), 1 ); + + C_CS_PlayerResource *cs_PR = dynamic_cast<C_CS_PlayerResource *>( g_PR ); + if ( !cs_PR ) + return; + int iLocalPlayerIndex = GetLocalPlayerIndex(); + + int maxTeamSize = MAX(m_teamDisplayT.playerScores.Count(), m_teamDisplayCT.playerScores.Count()); + + // adjust spacing + int leadingAvailable = teamDisplay.scoreAreaInnerHeight - maxTeamSize * teamDisplay.scoreAreaLineHeight; + int leading = 0; + if ( maxTeamSize > 1 ) // only makes sense if we have more than one player + { + leading = clamp(leadingAvailable / (maxTeamSize - 1), 0, teamDisplay.scoreAreaLinePreferredLeading); + } + int spacingY = teamDisplay.scoreAreaLineHeight + leading; + + // temp values for updating just the y position of elements + int xPos, yPos; + + int i = 0; + int startY = teamDisplay.scoreAreaStartY; + for ( i = 0; i < MIN( cMaxScoreLines, teamDisplay.playerScores.Count() ); ++i, startY += spacingY ) + { + if ( startY + teamDisplay.scoreAreaLineHeight > teamDisplay.scoreAreaStartY + teamDisplay.scoreAreaInnerHeight ) + break; + + PlayerDisplay& playerDisplay = teamDisplay.playerDisplay[i]; + + const PlayerScoreInfo* pPlayerScore = teamDisplay.playerScores[i]; + if ( pPlayerScore ) + { + int playerIndex = pPlayerScore->playerIndex; + + const char* pUTF8Name = pPlayerScore->szName; + +// int bufsize; +// if ( g_PR->IsFakePlayer( playerIndex ) ) +// bufsize = strlen( oldName ) * 2 + 14 + 1; +// else +// bufsize = strlen( oldName ) * 2 + 1; +// +// char *newName = (char *)_alloca( bufsize ); +// UTIL_MakeSafeName( oldName, newName, bufsize ); + + if ( pUTF8Name != NULL && V_strlen( pUTF8Name ) > 0 ) + { + bool isAlive = cs_PR->IsAlive( playerIndex ); + Color fgColor = ( isAlive ? teamDisplay.playerDataColor : m_DeadPlayerDataColor ); + Color fgClanColor = ( isAlive ? teamDisplay.playerClanColor : m_DeadPlayerClanColor ); + + if ( playerDisplay.pNameLabel != NULL ) + { + AdjustFontToFit( pUTF8Name, playerDisplay.pNameLabel ); + + playerDisplay.pNameLabel->SetVisible( true ); + playerDisplay.pNameLabel->SetText( pUTF8Name ); + playerDisplay.pNameLabel->SetBgColor( m_PlayerDataBgColor ); + playerDisplay.pNameLabel->SetFgColor( fgColor ); + playerDisplay.pNameLabel->GetPos(xPos, yPos); + playerDisplay.pNameLabel->SetPos(xPos, startY); + + int tallMVP = RoundFloatToInt(teamDisplay.scoreAreaLineHeight * kScaleMVP); + int yMVP = ( teamDisplay.scoreAreaLineHeight - tallMVP ) / 2; + playerDisplay.pMVPImage->SetPos( xPos, startY + yMVP); + } + + if ( playerDisplay.pClanLabel != NULL ) + { + const char* pUTF8Clan = pPlayerScore->szClanTag; + AdjustFontToFit( pUTF8Clan, playerDisplay.pClanLabel ); + + playerDisplay.pClanLabel->SetVisible( true ); + playerDisplay.pClanLabel->SetText( pUTF8Clan ); + playerDisplay.pClanLabel->SetBgColor( m_PlayerDataBgColor ); + playerDisplay.pClanLabel->SetFgColor( fgClanColor ); + playerDisplay.pClanLabel->GetPos(xPos, yPos); + playerDisplay.pClanLabel->SetPos(xPos, startY); + } + + char tmpbuf[16]; + + if ( playerDisplay.pScoreLabel != NULL ) + { + Q_snprintf( tmpbuf, sizeof( tmpbuf ), "%d", pPlayerScore->frags); + playerDisplay.pScoreLabel->SetVisible( true ); + playerDisplay.pScoreLabel->SetText( tmpbuf ); + playerDisplay.pScoreLabel->SetBgColor( m_PlayerDataBgColor ); + playerDisplay.pScoreLabel->SetFgColor( fgColor ); + playerDisplay.pScoreLabel->GetPos(xPos, yPos); + playerDisplay.pScoreLabel->SetPos(xPos, startY); + } + + if ( playerDisplay.pDeathsLabel != NULL ) + { + Q_snprintf( tmpbuf, sizeof( tmpbuf ), "%d", pPlayerScore->deaths); + playerDisplay.pDeathsLabel->SetVisible( true ); + playerDisplay.pDeathsLabel->SetText( tmpbuf ); + playerDisplay.pDeathsLabel->SetBgColor( m_PlayerDataBgColor ); + playerDisplay.pDeathsLabel->SetFgColor( fgColor ); + playerDisplay.pDeathsLabel->GetPos(xPos, yPos); + playerDisplay.pDeathsLabel->SetPos(xPos, startY); + + } + + if ( playerDisplay.pPingLabel != NULL ) + { + if ( pPlayerScore->ping >= 0 ) + Q_snprintf( tmpbuf, sizeof( tmpbuf ), "%d", pPlayerScore->ping); + else + Q_strcpy( tmpbuf, "BOT"); + + playerDisplay.pPingLabel->SetVisible( true ); + playerDisplay.pPingLabel->SetText( tmpbuf ); + playerDisplay.pPingLabel->SetBgColor( m_PlayerDataBgColor ); + playerDisplay.pPingLabel->SetFgColor( fgColor ); + playerDisplay.pPingLabel->GetPos(xPos, yPos); + playerDisplay.pPingLabel->SetPos(xPos, startY); + + } + } + + if ( playerDisplay.pStatusImage != NULL ) + { + if ( pPlayerScore->szStatus == NULL ) + { + playerDisplay.pStatusImage->SetVisible( false ); + } + else + { + playerDisplay.pStatusImage->SetVisible( true ); + playerDisplay.pStatusImage->SetImage( pPlayerScore->szStatus ); + playerDisplay.pStatusImage->GetPos(xPos, yPos); + playerDisplay.pStatusImage->SetPos(xPos, startY); + playerDisplay.pStatusImage->SetDrawColor(pPlayerScore->bStatusPlayerColor ? teamDisplay.playerDataColor : COLOR_WHITE); + } + } + + if ( playerDisplay.pAvatar != NULL ) + { + playerDisplay.pAvatar->SetVisible( true ); + playerDisplay.pAvatar->SetPlayer( playerIndex, k_EAvatarSize32x32 ); + playerDisplay.pAvatar->GetPos(xPos, yPos); + playerDisplay.pAvatar->SetPos(xPos, startY); + } + + if ( playerDisplay.pSelect != NULL ) + { + playerDisplay.pSelect->SetVisible( pPlayerScore->playerIndex == iLocalPlayerIndex ); + playerDisplay.pSelect->SetPos(teamDisplay.scoreAreaMinX - selectMargin, startY - selectMargin); + } + } + } + + // set any remaining entries to non-visible + for ( ; i < cMaxScoreLines; ++i ) + { + PlayerDisplay& playerDisplay = teamDisplay.playerDisplay[i]; + + if ( playerDisplay.pClanLabel != NULL ) + playerDisplay.pClanLabel->SetVisible(false); + + if ( playerDisplay.pNameLabel != NULL ) + playerDisplay.pNameLabel->SetVisible(false); + + if ( playerDisplay.pScoreLabel != NULL ) + playerDisplay.pScoreLabel->SetVisible(false); + + if ( playerDisplay.pDeathsLabel != NULL ) + playerDisplay.pDeathsLabel->SetVisible(false); + + if ( playerDisplay.pPingLabel != NULL ) + playerDisplay.pPingLabel->SetVisible(false); + + if ( playerDisplay.pStatusImage != NULL ) + playerDisplay.pStatusImage->SetVisible(false); + + if ( playerDisplay.pAvatar != NULL ) + playerDisplay.pAvatar->SetVisible( false ); + + if ( playerDisplay.pMVPImage != NULL ) + playerDisplay.pMVPImage->SetVisible( false ); + + if ( playerDisplay.pMVPCountLabel != NULL ) + playerDisplay.pMVPCountLabel->SetVisible( false ); + + if ( playerDisplay.pSelect != NULL ) + playerDisplay.pSelect->SetVisible( false ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Updates the spectator list +//----------------------------------------------------------------------------- +void CCSClientScoreBoardDialog::UpdateSpectatorList() +{ + if ( g_PR == NULL ) + { + return; + } + + const int listTextLen = 100; + wchar_t listText[listTextLen]; + listText[0] = L'\0'; + + const wchar_t *delimText = L", "; + const int delimTextLen = V_wcslen( delimText ); + + // Count the number of spectators and build up a list. + int nSpectators = 0; + for ( int playerIndex = 1 ; playerIndex <= MAX_PLAYERS; ++playerIndex ) + { + if ( ShouldShowAsSpectator( playerIndex ) ) + { + const char *playerName = g_PR->GetPlayerName( playerIndex ); + if ( playerName != NULL ) + { + // Convert the name to wide char. + wchar_t playerBuf[MAX_PLAYER_NAME_LENGTH]; + playerBuf[0] = L'\0'; + V_UTF8ToUnicode( playerName, playerBuf, sizeof( playerBuf ) ); + + // + // Check to see if there is space for the player name and delimiter. + // + + bool addDelim = ( nSpectators > 0 ); + + int playerNameLen = V_wcslen( playerBuf ); + if ( addDelim ) + { + playerNameLen += delimTextLen; + } + + int currentLen = V_wcslen( listText ); + int remainingLen = listTextLen - currentLen - playerNameLen - 1; + if ( remainingLen >= 0 ) + { + // Append the delimiter. + if ( addDelim ) + { + V_wcscat_safe( listText, delimText ); + } + + // Append the player name. + V_wcscat_safe( listText, playerBuf ); + } + } + + ++nSpectators; + } + } + + wchar_t labelText[512]; + labelText[0] = L'\0'; + + if ( nSpectators == 0 ) + { + // No spectators. + wchar_t *noSpectators = g_pVGuiLocalize->Find( "#Cstrike_Scoreboard_NoSpectators" ); + if ( noSpectators != NULL ) + { + V_wcsncpy( labelText, noSpectators, sizeof( labelText ) ); + } + } + else + { + // Build the text for the number of spectators. + const int countTextLen = 16; + wchar_t countText[countTextLen]; + countText[0] = L'\0'; + V_snwprintf( countText, countTextLen, L"%i", nSpectators ); + countText[countTextLen - 1] = L'\0'; + + // Build the combined count and spectator list text. + wchar_t *formatLabel = g_pVGuiLocalize->Find( ( nSpectators == 1 ) ? "#Cstrike_Scoreboard_Spectator" : "#Cstrike_Scoreboard_Spectators" ); + if ( formatLabel != NULL ) + { + g_pVGuiLocalize->ConstructString( labelText, sizeof( labelText ), formatLabel, 2, countText, listText ); + } + } + + SetDialogVariable( "spectators", labelText ); +} + +//----------------------------------------------------------------------------- +// Purpose: Display the number of HLTV viewers +//----------------------------------------------------------------------------- +void CCSClientScoreBoardDialog::UpdateHLTVList( void ) +{ + // Build the text for the number of viewers. + const int countTextLen = 16; + wchar_t countText[countTextLen]; + countText[0] = L'\0'; + V_snwprintf( countText, countTextLen, L"%i", m_HLTVSpectators ); + countText[countTextLen - 1] = L'\0'; + + // Build the combined text. + wchar_t labelText[512]; + labelText[0] = L'\0'; + wchar_t *formatLabel = g_pVGuiLocalize->Find( "#Cstrike_Scoreboard_HLTV" ); + if ( formatLabel != NULL ) + { + g_pVGuiLocalize->ConstructString( labelText, sizeof( labelText ), formatLabel, 1, countText ); + } + + SetDialogVariable( "sourcetv", labelText ); +} + +/** +* Special processing for MVP UI elements +*/ +void CCSClientScoreBoardDialog::UpdateMvpElements() +{ + C_CS_PlayerResource *cs_PR = dynamic_cast<C_CS_PlayerResource *>( g_PR ); + if ( cs_PR == NULL ) + { + return; + } + + for ( int teamIndex = TEAM_TERRORIST; teamIndex <= TEAM_CT; ++teamIndex ) + { + TeamDisplayInfo& teamDisplay = ( teamIndex == TEAM_TERRORIST ) ? m_teamDisplayT : m_teamDisplayCT; + + for ( int i = 0; i < cMaxScoreLines && i < teamDisplay.playerScores.Count(); ++i ) + { + PlayerDisplay& playerDisplay = teamDisplay.playerDisplay[i]; + + // Get the player index. + int playerIndex = -1; + if ( teamDisplay.playerScores[i] != NULL ) + { + playerIndex = teamDisplay.playerScores[i]->playerIndex; + } + + if ( playerIndex == -1 ) + continue; + + // early out fail conditions + if ( playerDisplay.pNameLabel == NULL || playerDisplay.pMVPImage == NULL || playerDisplay.pMVPCountLabel == NULL ) + continue; + + // Get the number of MVPs and cap it. + int numMVPs = MIN( kMaxMVPCount, cs_PR->GetNumMVPs( playerIndex ) ); + + // No MVPs. + if ( numMVPs == 0 ) + { + playerDisplay.pMVPImage->SetVisible( false ); + playerDisplay.pMVPCountLabel->SetVisible( false ); + continue; + } + + // Get the dimensions of the player label. + int xName = 0; + int yName = 0; + int wideName = 0; + int tallName = 0; + playerDisplay.pNameLabel->GetBounds( xName, yName, wideName, tallName ); + + wchar_t playerName[64]; + playerDisplay.pNameLabel->GetText( playerName, sizeof(playerName) ); + + // Find the actual width of the rendered player name. + int actualWideName = 0; + int actualTallName = 0; + g_pMatSystemSurface->GetTextSize( playerDisplay.pNameLabel->GetFont(), playerName, actualWideName, actualTallName ); + if ( actualWideName > wideName ) + { + actualWideName = wideName; + } + + // Get the dimensions of the mvp image. + int xMVPImage = 0; + int yMVPImage = 0; + int wideMVPImage = 0; + int tallMVPImage = 0; + playerDisplay.pMVPImage->GetBounds( xMVPImage, yMVPImage, wideMVPImage, tallMVPImage ); + + // The MVP label is hidden for only one star. + bool showMVPNumber = ( numMVPs > 1 ); + + // Set the MVP label text and get the actual size. + int actualWideMVPLabel = 0; + if ( showMVPNumber ) + { + const int mvpTextSize = 8; + wchar_t mvpText[mvpTextSize]; + V_snwprintf( mvpText, ARRAYSIZE( mvpText ), L"%i", numMVPs ); + playerDisplay.pMVPCountLabel->SetText( mvpText ); + playerDisplay.pMVPCountLabel->SetVisible( true ); + + int actualTallMVPLabel = 0; + g_pMatSystemSurface->GetTextSize( playerDisplay.pMVPCountLabel->GetFont(), mvpText, actualWideMVPLabel, actualTallMVPLabel ); + } + + // Calculate the total width of the mvp stuff. + int wideMVPTotal = wideMVPImage; + if ( showMVPNumber ) + { + wideMVPTotal += actualWideMVPLabel; + } + + // Calculate the optimal place for the mvp. + int x = xName + actualWideName + m_MVPXOffset; + + // Get the position of the status image. + if ( playerDisplay.pStatusImage ) + { + int xStatus = 0; + int yStatus = 0; + playerDisplay.pStatusImage->GetPos( xStatus, yStatus ); + + if ( x + wideMVPTotal > xStatus ) + { + // Don't run over the status image. Back up. + x = xStatus - wideMVPTotal; + } + } + + playerDisplay.pMVPImage->SetPos( x, yMVPImage ); + playerDisplay.pMVPImage->SetVisible( true ); + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Returns whether the specified player index is a spectator +//----------------------------------------------------------------------------- +bool CCSClientScoreBoardDialog::ShouldShowAsSpectator( int iPlayerIndex ) +{ + C_CS_PlayerResource *cs_PR = dynamic_cast<C_CS_PlayerResource *>( g_PR ); + if ( !cs_PR ) + return false; + + // see if player is connected + if ( cs_PR->IsConnected( iPlayerIndex ) ) + { + // either spectator or unassigned team should show in spectator list + int iTeam = cs_PR->GetTeam( iPlayerIndex ); + if ( TEAM_SPECTATOR == iTeam || TEAM_UNASSIGNED == iTeam ) + return true; + } + return false; +} + + +void CCSClientScoreBoardDialog::FireGameEvent( IGameEvent *event ) +{ + if ( event == NULL ) + return; + + const char *pEventName = event->GetName(); + if ( pEventName == NULL ) + return; + + if ( Q_strcmp( pEventName, "server_spawn" ) == 0 ) + { + // set server name in scoreboard + const char *hostname = event->GetString( "hostname" ); + if ( hostname != NULL ) + { + wchar_t wzHostName[256]; + g_pVGuiLocalize->ConvertANSIToUnicode( hostname, wzHostName, sizeof( wzHostName ) ); + g_pVGuiLocalize->ConstructString( m_pServerName, sizeof(m_pServerName), g_pVGuiLocalize->Find( "#Cstrike_SB_Server" ), 1, wzHostName ); + + if ( m_pServerLabel ) + { + m_pServerLabel->SetText( m_pServerName ); + } + + if ( m_gameOver ) + { + ResetFromGameOverState(); + } + + // Save the server name for use after this panel is reconstructed + if ( g_pClientMode ) + { + g_pClientMode->SetServerName( m_pServerName ); + } + } + } + else if ( Q_strcmp( pEventName, "game_newmap" ) == 0 ) + { + const char *mapName = event->GetString( "mapname" ); + if ( mapName != NULL ) + { + g_pVGuiLocalize->ConvertANSIToUnicode( mapName, m_pMapName, sizeof( m_pMapName ) ); + SetDialogVariable( "mapname", m_pMapName ); + + if ( m_gameOver ) + { + ResetFromGameOverState(); + } + + // Save the map name for use after this panel is reconstructed + if ( g_pClientMode ) + { + g_pClientMode->SetMapName( m_pMapName ); + } + } + } + else if ( Q_strcmp( pEventName, "match_end_conditions" ) == 0 ) + { + UpdateMatchEndText(); + } + else if ( Q_strcmp( pEventName, "cs_win_panel_match" ) == 0 ) + { + m_gameOver = true; + UpdateMatchEndText(); + } + + BaseClass::FireGameEvent( event ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Adds a new row to the scoreboard, from the playerinfo structure +//----------------------------------------------------------------------------- +bool CCSClientScoreBoardDialog::GetPlayerScoreInfo( int playerIndex, PlayerScoreInfo& playerScoreInfo ) +{ + if ( g_PR == NULL ) + return false; + + // Clean up the player name + const char *oldName = g_PR->GetPlayerName( playerIndex ); + if ( oldName == NULL ) + return false; + + playerScoreInfo.szName = g_PR->GetPlayerName( playerIndex ); + + playerScoreInfo.playerIndex = playerIndex; + playerScoreInfo.frags = g_PR->GetPlayerScore( playerIndex ); + playerScoreInfo.deaths = g_PR->GetDeaths( playerIndex ); + + if ( g_PR->GetPing( playerIndex ) < 1 ) + { + if ( g_PR->IsFakePlayer( playerIndex ) ) + { + playerScoreInfo.ping = -1; + } + else + { + playerScoreInfo.ping = 0; + } + } + else + { + playerScoreInfo.ping = g_PR->GetPing( playerIndex ); + } + + // get CS specific infos + C_CS_PlayerResource *cs_PR = dynamic_cast<C_CS_PlayerResource *>( g_PR ); + + C_CSPlayer *pLocalPlayer = C_CSPlayer::GetLocalCSPlayer(); + + if ( !cs_PR || !pLocalPlayer ) + { + return false; + } + + // Get the clan tag + playerScoreInfo.szClanTag = cs_PR->GetClanTag( playerIndex ); + + bool bShowExtraInfo = + ( pLocalPlayer->GetTeamNumber() == TEAM_UNASSIGNED ) || // we're not spawned yet + ( pLocalPlayer->GetTeamNumber() == TEAM_SPECTATOR ) || // we are a spectator + ( pLocalPlayer->IsPlayerDead() && mp_forcecamera.GetInt() == OBS_ALLOW_ALL ) || // we are dead and allowed to spectate opponents + ( pLocalPlayer->GetTeamNumber() == g_PR->GetTeam( playerIndex ) ); // we're on the same team + + playerScoreInfo.szStatus = NULL; + playerScoreInfo.bStatusPlayerColor = false; + + // set the status icon; lowest priority icons are tested first, and highest last + + if ( cs_PR->IsVIP( playerIndex ) && bShowExtraInfo ) + { + playerScoreInfo.szStatus = "../hud/scoreboard_clock"; + playerScoreInfo.bStatusPlayerColor = true; + } + + if ( !g_PR->IsAlive( playerIndex ) && g_PR->GetTeam( playerIndex ) > TEAM_SPECTATOR ) + { + playerScoreInfo.szStatus = "../hud/scoreboard_dead"; + } + + if ( g_PR->IsHLTV( playerIndex ) ) + { +// // show #spectators in class field, it's transmitted as player's score +// char numspecs[32]; +// Q_snprintf( numspecs, sizeof( numspecs ), "%i Spectators", m_HLTVSpectators ); +// kv->SetString( "class", numspecs ); + } + + // Set the dominated icon + if ( pLocalPlayer->IsPlayerDominatingMe( playerIndex ) ) + { + if ( g_PR->IsAlive( playerIndex ) ) + { + playerScoreInfo.szStatus = "../hud/scoreboard_nemesis"; + } + else + { + playerScoreInfo.szStatus = "../hud/scoreboard_nemesis-dead"; + } + } + + if ( pLocalPlayer->IsPlayerDominated(playerIndex) ) + { + if ( g_PR->IsAlive( playerIndex ) ) + { + playerScoreInfo.szStatus = "../hud/scoreboard_dominated"; + } + else + { + playerScoreInfo.szStatus = "../hud/scoreboard_domination-dead"; + } + } + + if ( cs_PR->HasC4( playerIndex ) && bShowExtraInfo ) + { + playerScoreInfo.szStatus = "../hud/scoreboard_bomb"; + playerScoreInfo.bStatusPlayerColor = true; + } + + if ( cs_PR->HasDefuser( playerIndex ) && bShowExtraInfo ) + { + playerScoreInfo.szStatus = "../hud/scoreboard_defuser"; + playerScoreInfo.bStatusPlayerColor = true; + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Updates the time/round remaining display and server and map name +//----------------------------------------------------------------------------- +void CCSClientScoreBoardDialog::UpdateMatchEndText() +{ + // Hide the win condition. + if ( m_pWinConditionLabel ) + { + m_pWinConditionLabel->SetVisible( false ); + } + + // Hide the clock. + if ( m_pClockLabel ) + { + m_pClockLabel->SetVisible( false ); + } + + if ( !m_gameOver ) + { + SetDialogVariable( "mapname", m_pMapName ); + + wchar_t wzMatchEndCausesLabel[128], wzMatchEndCause[32]; + + // Time limit + if ( mp_timelimit.GetInt() != 0 ) + { + int timeTillEndOfMatch = CSGameRules()->GetMapRemainingTime(); + bool showTime = ( timeTillEndOfMatch != -1 ); + if ( showTime ) + { + if ( m_pWinConditionLabel ) + { + V_snwprintf( wzMatchEndCause, ARRAYSIZE( wzMatchEndCause ), L"%.2i:%.2i", timeTillEndOfMatch / 60, timeTillEndOfMatch % 60 ); + g_pVGuiLocalize->ConstructString( wzMatchEndCausesLabel, sizeof( wzMatchEndCausesLabel ), + g_pVGuiLocalize->Find( "#Cstrike_Time_LeftVariable" ), 1, wzMatchEndCause ); + m_pWinConditionLabel->SetText( wzMatchEndCausesLabel ); + m_pWinConditionLabel->SetVisible( true ); + } + + if ( m_pClockLabel ) + { + m_pClockLabel->SetVisible( true ); + } + } + } + // Round limit + else if ( mp_maxrounds.GetInt() != 0 ) + { + if ( m_pWinConditionLabel ) + { + // Get the number of rounds played. + int roundsPlayed = 0; + for ( int teamIndex = TEAM_TERRORIST; teamIndex <= TEAM_CT; teamIndex++ ) + { + C_Team *team = GetGlobalTeam( teamIndex ); + if ( team ) + { + roundsPlayed += team->Get_Score(); + } + } + + V_snwprintf( wzMatchEndCause, ARRAYSIZE( wzMatchEndCause ), L"%d", mp_maxrounds.GetInt() - roundsPlayed ); + g_pVGuiLocalize->ConstructString( wzMatchEndCausesLabel, sizeof( wzMatchEndCausesLabel ), + g_pVGuiLocalize->Find( "#Cstrike_Rounds_LeftVariable" ), 1, wzMatchEndCause ); + m_pWinConditionLabel->SetText( wzMatchEndCausesLabel ); + m_pWinConditionLabel->SetVisible( true ); + } + } + // Win limit + else if ( mp_winlimit.GetInt() != 0 ) + { + if ( m_pWinConditionLabel ) + { + V_snwprintf( wzMatchEndCause, ARRAYSIZE( wzMatchEndCause ), L"%d", mp_winlimit.GetInt() ); + g_pVGuiLocalize->ConstructString( wzMatchEndCausesLabel, sizeof( wzMatchEndCausesLabel ), + g_pVGuiLocalize->Find( "#Cstrike_Wins_NeededVariable" ), 1, wzMatchEndCause ); + m_pWinConditionLabel->SetText( wzMatchEndCausesLabel ); + m_pWinConditionLabel->SetVisible( true ); + } + } + } +} + +// Resets all changes made by the scoreboard's state at the match end +void CCSClientScoreBoardDialog::ResetFromGameOverState() +{ + m_gameOver = false; + + if ( m_pLabelMapName ) + { + m_pLabelMapName->SetVisible( true ); + } + + UpdateMatchEndText(); +} + +// [tj] We hook into the show command so we can lock or unlock all the elements that need to be hidden +// +// [pfreese] This used to enable/disable keyboard input, but since the scoreboard is now a popup, we have +// to leave the keyboard disabled +void CCSClientScoreBoardDialog::ShowPanel( bool state ) +{ + BaseClass::ShowPanel(state); + + int iRenderGroup = gHUD.LookupRenderGroupIndexByName( "hide_for_scoreboard" ); + + if ( state ) + { + gHUD.LockRenderGroup( iRenderGroup ); + } + else + { + gHUD.UnlockRenderGroup( iRenderGroup ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Grabs the player data colors from the convars. +//----------------------------------------------------------------------------- +void CCSClientScoreBoardDialog::UpdatePlayerColors( void ) +{ + m_teamDisplayT.playerDataColor.SetColor( cl_scoreboard_t_color_red.GetInt(), cl_scoreboard_t_color_green.GetInt(), cl_scoreboard_t_color_blue.GetInt(), 255 ); + m_teamDisplayCT.playerDataColor.SetColor( cl_scoreboard_ct_color_red.GetInt(), cl_scoreboard_ct_color_green.GetInt(), cl_scoreboard_ct_color_blue.GetInt(), 255 ); + m_DeadPlayerDataColor.SetColor( cl_scoreboard_dead_color_red.GetInt(), cl_scoreboard_dead_color_green.GetInt(), cl_scoreboard_dead_color_blue.GetInt(), 255 ); + m_teamDisplayT.playerClanColor.SetColor( cl_scoreboard_clan_t_color_red.GetInt(), cl_scoreboard_clan_t_color_green.GetInt(), cl_scoreboard_clan_t_color_blue.GetInt(), 255 ); + m_teamDisplayCT.playerClanColor.SetColor( cl_scoreboard_clan_ct_color_red.GetInt(), cl_scoreboard_clan_ct_color_green.GetInt(), cl_scoreboard_clan_ct_color_blue.GetInt(), 255 ); + m_DeadPlayerClanColor.SetColor( cl_scoreboard_dead_clan_color_red.GetInt(), cl_scoreboard_dead_clan_color_green.GetInt(), cl_scoreboard_dead_clan_color_blue.GetInt(), 255 ); +} + +// [tj] Disabling joystick input if you are dead. +void CCSClientScoreBoardDialog::OnThink() +{ + BaseClass::OnThink(); + +#ifdef _XBOX + C_CSPlayer *pLocalPlayer = C_CSPlayer::GetLocalCSPlayer(); + if ( pLocalPlayer ) + { + bool mouseEnabled = IsMouseInputEnabled(); + if (pLocalPlayer->IsAlive() == mouseEnabled) + { + SetMouseInputEnabled( !mouseEnabled ); + } + } +#endif +} + +bool CCSClientScoreBoardDialog::ForceLocalPlayerVisible( TeamDisplayInfo& teamDisplay ) +{ + int iLocalPlayerIndex = GetLocalPlayerIndex(); + + // Look for the local player in the non-visible portion of the member list + for (int i = teamDisplay.maxPlayersVisible; i < teamDisplay.playerScores.Count(); ++i) + { + PlayerScoreInfo* pPlayerScore = teamDisplay.playerScores[i]; + Assert(pPlayerScore != NULL); + + // Determine if this is the local player's entry + if ( pPlayerScore->playerIndex == iLocalPlayerIndex ) + { + // Remove the local player entry from the current position + teamDisplay.playerScores.Remove(i); + + // Re-insert the local player entry at the end of the visible list + teamDisplay.playerScores.InsertBefore( teamDisplay.maxPlayersVisible - 1, pPlayerScore ); + + return true; + } + } + return false; +} |