diff options
Diffstat (limited to 'game/client/tf/vgui/tf_statsummary.cpp')
| -rw-r--r-- | game/client/tf/vgui/tf_statsummary.cpp | 1506 |
1 files changed, 1506 insertions, 0 deletions
diff --git a/game/client/tf/vgui/tf_statsummary.cpp b/game/client/tf/vgui/tf_statsummary.cpp new file mode 100644 index 0000000..8e96829 --- /dev/null +++ b/game/client/tf/vgui/tf_statsummary.cpp @@ -0,0 +1,1506 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include "cbase.h" + +#include <vgui_controls/Label.h> +#include <vgui_controls/Button.h> +#include <vgui_controls/ComboBox.h> +#include <vgui_controls/ImagePanel.h> +#include <vgui_controls/RichText.h> +#include <vgui_controls/Frame.h> +#include <vgui_controls/QueryBox.h> +#include <vgui/IScheme.h> +#include <vgui/ILocalize.h> +#include <vgui/ISurface.h> +#include "ienginevgui.h" +#include <game/client/iviewport.h> +#include "tf_tips.h" +#include "tf_mapinfo.h" +#include "vgui_avatarimage.h" +#include "VGuiMatSurface/IMatSystemSurface.h" + +#include "tf_statsummary.h" +#include <convar.h> +#include "fmtstr.h" +#include "tf_gamerules.h" +#include "tf_gc_client.h" + +using namespace vgui; + +#if defined( REPLAY_ENABLED ) +extern bool g_bIsReplayRewinding; +#else +bool g_bIsReplayRewinding = false; +#endif + +const char *g_pszTipsClassImages[] = +{ + "", // TF_CLASS_UNDEFINED = 0, + "class_portraits/scout", // TF_CLASS_SCOUT, + "class_portraits/sniper",// TF_CLASS_SNIPER, + "class_portraits/soldier", // TF_CLASS_SOLDIER, + "class_portraits/demoman", // TF_CLASS_DEMOMAN, + "class_portraits/medic", // TF_CLASS_MEDIC, + "class_portraits/heavy", // TF_CLASS_HEAVYWEAPONS, + "class_portraits/pyro", // TF_CLASS_PYRO, + "class_portraits/spy", // TF_CLASS_SPY, + "class_portraits/engineer", // TF_CLASS_ENGINEER, +}; + +ClassDetails_t g_PerClassStatDetails[15] = +{ + { TFSTAT_POINTSSCORED, ALL_CLASSES, "#TF_ClassRecord_MostPoints", "#TF_ClassRecord_Alt_MostPoints" }, + { TFSTAT_KILLS, ALL_CLASSES, "#TF_ClassRecord_MostKills", "#TF_ClassRecord_Alt_MostKills" }, + { TFSTAT_KILLASSISTS, ALL_CLASSES, "#TF_ClassRecord_MostAssists", "#TF_ClassRecord_Alt_MostAssists" }, + { TFSTAT_CAPTURES, ALL_CLASSES, "#TF_ClassRecord_MostCaptures", "#TF_ClassRecord_Alt_MostCaptures" }, + { TFSTAT_DEFENSES, ALL_CLASSES, "#TF_ClassRecord_MostDefenses", "#TF_ClassRecord_Alt_MostDefenses" }, + { TFSTAT_DAMAGE, ALL_CLASSES, "#TF_ClassRecord_MostDamage", "#TF_ClassRecord_Alt_MostDamage" }, + { TFSTAT_BUILDINGSDESTROYED, ALL_CLASSES, "#TF_ClassRecord_MostDestruction", "#TF_ClassRecord_Alt_MostDestruction" }, + { TFSTAT_DOMINATIONS, ALL_CLASSES, "#TF_ClassRecord_MostDominations", "#TF_ClassRecord_Alt_MostDominations" }, + { TFSTAT_PLAYTIME, ALL_CLASSES, "#TF_ClassRecord_LongestLife", "#TF_ClassRecord_Alt_LongestLife" }, + { TFSTAT_HEALING, MAKESTATFLAG(TF_CLASS_MEDIC) | MAKESTATFLAG(TF_CLASS_ENGINEER) | MAKESTATFLAG(TF_CLASS_HEAVYWEAPONS), "#TF_ClassRecord_MostHealing", "#TF_ClassRecord_Alt_MostHealing" }, + { TFSTAT_INVULNS, MAKESTATFLAG(TF_CLASS_MEDIC), "#TF_ClassRecord_MostInvulns", "#TF_ClassRecord_Alt_MostInvulns" }, + { TFSTAT_MAXSENTRYKILLS, MAKESTATFLAG(TF_CLASS_ENGINEER), "#TF_ClassRecord_MostSentryKills", "#TF_ClassRecord_Alt_MostSentryKills" }, + { TFSTAT_TELEPORTS, MAKESTATFLAG(TF_CLASS_ENGINEER), "#TF_ClassRecord_MostTeleports", "#TF_ClassRecord_Alt_MostTeleports" }, + { TFSTAT_HEADSHOTS, MAKESTATFLAG(TF_CLASS_SNIPER) | MAKESTATFLAG(TF_CLASS_SPY), "#TF_ClassRecord_MostHeadshots", "#TF_ClassRecord_Alt_MostHeadshots" }, + { TFSTAT_BACKSTABS, MAKESTATFLAG(TF_CLASS_SPY), "#TF_ClassRecord_MostBackstabs", "#TF_ClassRecord_Alt_MostBackstabs" }, +}; + +ClassDetails_t g_PerClassMVMStatDetails[12] = +{ + { TFSTAT_POINTSSCORED, ALL_CLASSES, "#TF_ClassRecord_MostPoints", "#TF_ClassRecord_Alt_MostPoints" }, + { TFSTAT_KILLS, ALL_CLASSES, "#TF_ClassRecord_MostKills", "#TF_ClassRecord_Alt_MostKills" }, + { TFSTAT_KILLASSISTS, ALL_CLASSES, "#TF_ClassRecord_MostAssists", "#TF_ClassRecord_Alt_MostAssists" }, + { TFSTAT_DEFENSES, ALL_CLASSES, "#TF_ClassRecord_MostDefenses", "#TF_ClassRecord_Alt_MostDefenses" }, + { TFSTAT_DAMAGE, ALL_CLASSES, "#TF_ClassRecord_MostDamage", "#TF_ClassRecord_Alt_MostDamage" }, + { TFSTAT_PLAYTIME, ALL_CLASSES, "#TF_ClassRecord_LongestLife", "#TF_ClassRecord_Alt_LongestLife" }, + { TFSTAT_HEALING, MAKESTATFLAG(TF_CLASS_MEDIC) | MAKESTATFLAG(TF_CLASS_ENGINEER) | MAKESTATFLAG(TF_CLASS_HEAVYWEAPONS), "#TF_ClassRecord_MostHealing", "#TF_ClassRecord_Alt_MostHealing" }, + { TFSTAT_INVULNS, MAKESTATFLAG(TF_CLASS_MEDIC), "#TF_ClassRecord_MostInvulns", "#TF_ClassRecord_Alt_MostInvulns" }, + { TFSTAT_MAXSENTRYKILLS, MAKESTATFLAG(TF_CLASS_ENGINEER), "#TF_ClassRecord_MostSentryKills", "#TF_ClassRecord_Alt_MostSentryKills" }, + { TFSTAT_TELEPORTS, MAKESTATFLAG(TF_CLASS_ENGINEER), "#TF_ClassRecord_MostTeleports", "#TF_ClassRecord_Alt_MostTeleports" }, + { TFSTAT_HEADSHOTS, MAKESTATFLAG(TF_CLASS_SNIPER) | MAKESTATFLAG(TF_CLASS_SPY), "#TF_ClassRecord_MostHeadshots", "#TF_ClassRecord_Alt_MostHeadshots" }, + { TFSTAT_BACKSTABS, MAKESTATFLAG(TF_CLASS_SPY), "#TF_ClassRecord_MostBackstabs", "#TF_ClassRecord_Alt_MostBackstabs" }, +}; + +CTFStatsSummaryPanel *g_pTFStatsSummaryPanel = NULL; + +CUtlVector<CTFStatsSummaryPanel *> g_vecStatPanels; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void UpdateStatSummaryPanels( CUtlVector<ClassStats_t> &vecClassStats ) +{ + for ( int i = 0; i < g_vecStatPanels.Count(); i++ ) + { + g_vecStatPanels[i]->SetStats( vecClassStats ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Returns the global stats summary panel +//----------------------------------------------------------------------------- +CTFStatsSummaryPanel *GStatsSummaryPanel() +{ + if ( NULL == g_pTFStatsSummaryPanel ) + { + g_pTFStatsSummaryPanel = new CTFStatsSummaryPanel(); + } + return g_pTFStatsSummaryPanel; +} + +//----------------------------------------------------------------------------- +// Purpose: Destroys the global stats summary panel +//----------------------------------------------------------------------------- +void DestroyStatsSummaryPanel() +{ + if ( NULL != g_pTFStatsSummaryPanel ) + { + g_pTFStatsSummaryPanel->MarkForDeletion(); + g_pTFStatsSummaryPanel = NULL; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +CTFStatsSummaryPanel::CTFStatsSummaryPanel() + : BaseClass( NULL, "TFStatsSummary", vgui::scheme()->LoadSchemeFromFile( "Resource/ClientScheme.res", "ClientScheme" ) ) + , m_bShowingLeaderboard( false ) + , m_bLoadingCommunityMap( false ) +{ + Init(); +} + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +void CTFStatsSummaryPanel::Init( void ) +{ + m_bControlsLoaded = false; + m_bInteractive = false; + m_bEmbedded = false; + m_xStartLHBar = 0; + m_xStartRHBar = 0; + m_iBarHeight = 1; + m_iBarMaxWidth = 1; + + m_pPlayerData = new vgui::EditablePanel( this, "statdata" ); + m_pInteractiveHeaders = new vgui::EditablePanel( m_pPlayerData, "InteractiveHeaders" ); + m_pNonInteractiveHeaders = new vgui::EditablePanel( m_pPlayerData, "NonInteractiveHeaders" ); + m_pBarChartComboBoxA = new vgui::ComboBox( m_pInteractiveHeaders, "BarChartComboA", 10, false ); + m_pBarChartComboBoxB = new vgui::ComboBox( m_pInteractiveHeaders, "BarChartComboB", 10, false ); + m_pClassComboBox = new vgui::ComboBox( m_pInteractiveHeaders, "ClassCombo", 10, false ); + m_pTipImage = new CTFImagePanel( this, "TipImage" ); + m_pTipText = new vgui::Label( this, "TipText", "" ); + m_pMapInfoPanel = NULL; + m_pMainBackground = NULL; + m_pLeaderboardTitle = NULL; + m_pContributedPanel = NULL; + +#ifdef _X360 + m_pFooter = new CTFFooter( this, "Footer" ); + m_bShowBackButton = false; +#else + m_pNextTipButton = new vgui::Button( this, "NextTipButton", "" ); + m_pResetStatsButton = new vgui::Button( this, "ResetStatsButton", "" ); + m_pCloseButton = new vgui::Button( this, "CloseButton", "" ); +#endif + + m_pBarChartComboBoxA->AddActionSignalTarget( this ); + m_pBarChartComboBoxB->AddActionSignalTarget( this ); + m_pClassComboBox->AddActionSignalTarget( this ); + + ListenForGameEvent( "server_spawn" ); + + Reset(); + + g_vecStatPanels.AddToTail( this ); +} + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +CTFStatsSummaryPanel::CTFStatsSummaryPanel( vgui::Panel *parent ) : BaseClass( parent, "TFStatsSummary", + vgui::scheme()->LoadSchemeFromFile( "Resource/ClientScheme.res", "ClientScheme" ) ) +{ + Init(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTFStatsSummaryPanel::~CTFStatsSummaryPanel() +{ + g_vecStatPanels.FindAndRemove( this ); +} + +//----------------------------------------------------------------------------- +// Purpose: Shows this dialog as a modal dialog +//----------------------------------------------------------------------------- +void CTFStatsSummaryPanel::ShowModal() +{ +#ifdef _X360 + m_bInteractive = false; + m_bShowBackButton = true; +#else + // we are in interactive mode, enable controls + m_bInteractive = true; +#endif + + SetParent( enginevgui->GetPanel( PANEL_GAMEUIDLL ) ); + UpdateDialog(); + SetVisible( true ); + MoveToFront(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStatsSummaryPanel::SetupForEmbedded( void ) +{ + m_bInteractive = true; + m_bEmbedded = true; + + UpdateDialog(); + + InvalidateLayout( true, true ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStatsSummaryPanel::PerformLayout() +{ + BaseClass::PerformLayout(); + +#ifndef _X360 + if ( m_pTipImage && m_pTipText ) + { + int iX,iY; + m_pTipImage->GetPos(iX,iY); + int iTX, iTY; + m_pTipText->GetPos(iTX, iTY); + m_pTipText->SetPos( iX + m_pTipImage->GetWide() + XRES(8), iTY ); + } + + if ( m_pNextTipButton ) + { + m_pNextTipButton->SizeToContents(); + } + + if ( m_pResetStatsButton ) + { + m_pResetStatsButton->SizeToContents(); + } +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStatsSummaryPanel::OnThink() +{ + BaseClass::OnThink(); + + if ( m_bShowingLeaderboard ) + { + UpdateLeaderboard(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Command handler +//----------------------------------------------------------------------------- +void CTFStatsSummaryPanel::OnCommand( const char *command ) +{ + if ( 0 == Q_stricmp( command, "vguicancel" ) ) + { + m_bInteractive = false; + UpdateDialog(); + SetVisible( false ); + SetParent( (VPANEL) NULL ); + +#ifdef _X360 + SetDefaultSelections(); + m_bShowBackButton = true; +#endif + } +#ifndef _X360 + else if ( 0 == Q_stricmp( command, "resetstatsbutton" ) ) + { + QueryBox *qb = new QueryBox( "#GameUI_Confirm", "#TF_ConfirmResetStats" ); + if (qb != NULL) + { + qb->SetOKCommand(new KeyValues("DoResetStats") ); + qb->AddActionSignalTarget(this); + qb->MoveToFront(); + qb->DoModal(); + } + } +#endif + else if ( 0 == Q_stricmp( command, "nexttip" ) ) + { + UpdateTip(); + } + + BaseClass::OnCommand( command ); +} + +//----------------------------------------------------------------------------- +// Purpose: Resets the dialog +//----------------------------------------------------------------------------- +void CTFStatsSummaryPanel::Reset() +{ + m_aClassStats.RemoveAll(); + + SetDefaultSelections(); +} + +//----------------------------------------------------------------------------- +// Purpose: Sets all user-controllable dialog settings to default values +//----------------------------------------------------------------------------- +void CTFStatsSummaryPanel::SetDefaultSelections() +{ + m_iSelectedClass = TF_CLASS_UNDEFINED; + m_statBarGraph[0] = TFSTAT_POINTSSCORED; + m_displayBarGraph[0]= SHOW_MAX; + m_statBarGraph[1] = TFSTAT_PLAYTIME; + m_displayBarGraph[1] = SHOW_TOTAL; + + m_pBarChartComboBoxA->ActivateItemByRow( 0 ); + m_pBarChartComboBoxB->ActivateItemByRow( 10 ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Set the background image based on the current mode +//----------------------------------------------------------------------------- +void CTFStatsSummaryPanel::UpdateMainBackground( void ) +{ + if ( IsPC() ) + { + m_pMainBackground = dynamic_cast<ImagePanel *>( FindChildByName( "MainBackground" ) ); + if ( m_pMainBackground ) + { + const IMatchGroupDescription* pMatchDesc = GetMatchGroupDescription( GTFGCClientSystem()->GetLiveMatchGroup() ); + + // determine if we're in widescreen or not and select the appropriate image + int screenWide, screenTall; + surface()->GetScreenSize( screenWide, screenTall ); + float aspectRatio = (float)screenWide/(float)screenTall; + bool bIsWidescreen = aspectRatio >= 1.5999f; + + if ( g_bIsReplayRewinding ) + { + m_pMainBackground->SetImage( bIsWidescreen ? "../console/rewind_background_widescreen" : "../console/rewind_background" ); + } + else if ( engine->IsLoadingDemo() || engine->IsPlayingDemo() || engine->IsSkippingPlayback() ) + { + m_pMainBackground->SetImage( bIsWidescreen ? "../console/replay_loading_widescreen" : "../console/replay_loading" ); + } + else if ( pMatchDesc && pMatchDesc->GetMapLoadBackgroundOverride( bIsWidescreen ) ) // Use match override if we have one + { + m_pMainBackground->SetImage( pMatchDesc->GetMapLoadBackgroundOverride( bIsWidescreen ) ); + } + else + { + m_pMainBackground->SetImage( bIsWidescreen ? "../console/background01_widescreen" : "../console/background01" ); + } + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Applies scheme settings +//----------------------------------------------------------------------------- +void CTFStatsSummaryPanel::ApplySchemeSettings(vgui::IScheme *pScheme) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + SetProportional( true ); + + if ( m_bEmbedded ) + { + LoadControlSettings( "Resource/UI/StatSummary_Embedded.res" ); + } + else + { + LoadControlSettings( "Resource/UI/StatSummary.res" ); + } + m_bControlsLoaded = true; + + // set the background image + UpdateMainBackground(); + + m_pMapInfoPanel = dynamic_cast< EditablePanel *>( FindChildByName( "MapInfo" ) ); + m_vecLeaderboardEntries.RemoveAll(); + if ( m_pMapInfoPanel ) + { + for ( int i = 0; i < 10; ++ i ) + { + vgui::EditablePanel *pEntryUI = new vgui::EditablePanel( m_pMapInfoPanel, "LeaderboardEntry" ); + pEntryUI->ApplySchemeSettings( pScheme ); + pEntryUI->LoadControlSettings( "Resource/UI/LeaderboardEntry.res" ); + m_vecLeaderboardEntries.AddToTail( pEntryUI ); + } + } + + // get the dimensions and position of a left-hand bar and a right-hand bar so we can do bar sizing later + Panel *pLHBar = m_pPlayerData->FindChildByName( "ClassBar1A" ); + Panel *pRHBar = m_pPlayerData->FindChildByName( "ClassBar1B" ); + if ( pLHBar && pRHBar ) + { + int y; + pLHBar->GetBounds( m_xStartLHBar, y, m_iBarMaxWidth, m_iBarHeight ); + pRHBar->GetBounds( m_xStartRHBar, y, m_iBarMaxWidth, m_iBarHeight ); + } + + // fill the combo box selections appropriately + InitBarChartComboBox( m_pBarChartComboBoxA ); + InitBarChartComboBox( m_pBarChartComboBoxB ); + + // fill the class names in the class combo box + HFont hFont = scheme()->GetIScheme( GetScheme() )->GetFont( "ScoreboardSmall", true ); + m_pClassComboBox->SetFont( hFont ); + m_pClassComboBox->RemoveAll(); + KeyValues *pKeyValues = new KeyValues( "data" ); + pKeyValues->SetInt( "class", TF_CLASS_UNDEFINED ); + m_pClassComboBox->AddItem( "#StatSummary_Label_AsAnyClass", pKeyValues ); + for ( int iClass = TF_FIRST_NORMAL_CLASS; iClass <= TF_LAST_NORMAL_CLASS; iClass++ ) + { + if ( iClass == TF_CLASS_CIVILIAN ) + continue; + pKeyValues = new KeyValues( "data" ); + pKeyValues->SetInt( "class", iClass ); + m_pClassComboBox->AddItem( g_aPlayerClassNames[iClass], pKeyValues ); + } + m_pClassComboBox->ActivateItemByRow( 0 ); + + if ( m_pMapInfoPanel ) + { + m_pContributedPanel = dynamic_cast< vgui::EditablePanel* >( m_pMapInfoPanel->FindChildByName( "ContributedLabel" ) ); + } + + SetDefaultSelections(); + UpdateDialog(); + + if ( !m_bEmbedded ) + { + SetVisible( false ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStatsSummaryPanel::OnKeyCodePressed( KeyCode code ) +{ + if ( IsX360() ) + { + if ( code == KEY_XBUTTON_A || code == STEAMCONTROLLER_A ) + { + OnCommand( "nexttip" ) ; + } + else if ( code == KEY_XBUTTON_B || code == STEAMCONTROLLER_B ) + { + OnCommand( "vguicancel" ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Sets stats to use +//----------------------------------------------------------------------------- +void CTFStatsSummaryPanel::SetStats( CUtlVector<ClassStats_t> &vecClassStats ) +{ + m_aClassStats = vecClassStats; + if ( m_bControlsLoaded ) + { + UpdateDialog(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Updates the dialog +//----------------------------------------------------------------------------- +void CTFStatsSummaryPanel::ClearMapLabel() +{ + SetDialogVariable( "maplabel", "" ); + SetDialogVariable( "maptype", "" ); + + vgui::Label *pLabel = dynamic_cast<Label *>( FindChildByName( "OnYourWayLabel" ) ); + if ( pLabel && pLabel->IsVisible() ) + { + pLabel->SetVisible( false ); + } + + pLabel = dynamic_cast<Label *>( FindChildByName( "MapType" ) ); + if ( pLabel && pLabel->IsVisible() ) + { + pLabel->SetVisible( false ); + } + + if ( m_pContributedPanel ) + { + m_pContributedPanel->SetVisible( false ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStatsSummaryPanel::ShowMapInfo( bool bShowMapInfo, bool bIsMVM /*= false*/, bool bBackgroundOverride /*= false*/ ) +{ + if ( m_pMainBackground ) + { + m_pMainBackground->SetVisible( !bShowMapInfo ); + } + m_pPlayerData->SetVisible( bIsMVM || !bShowMapInfo ); + m_pNextTipButton->SetVisible( m_bInteractive && !bShowMapInfo ); + m_pResetStatsButton->SetVisible( m_bInteractive && !bShowMapInfo ); + + if ( m_pMapInfoPanel ) + { + m_pMapInfoPanel->SetVisible( bShowMapInfo ); + vgui::Panel* pInfoBG = m_pMapInfoPanel->FindChildByName( "InfoBG" ); + if ( pInfoBG ) + { + pInfoBG->SetVisible( bShowMapInfo && !bIsMVM && !bBackgroundOverride ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFStatsSummaryPanel::OnMapLoad( const char *pMapName ) +{ + if ( g_bIsReplayRewinding || engine->IsLoadingDemo() || engine->IsPlayingDemo() || engine->IsSkippingPlayback() ) + return; + + bool bWidescreenBackground = false; + + bool bIsMVM = ( pMapName && !Q_strncmp( pMapName, "mvm_", 4 ) ); + const char *pszBackgroundOverride = NULL; + const IMatchGroupDescription* pMatchDesc = GetMatchGroupDescription( GTFGCClientSystem()->GetLiveMatchGroup() ); + if ( pMatchDesc ) + { + int screenWide, screenTall; + surface()->GetScreenSize( screenWide, screenTall ); + float aspectRatio = (float)screenWide/(float)screenTall; + bool bWideScreen = aspectRatio >= 1.5999f; + + // Check if there's a widescreen override + if( bWideScreen ) + { + pszBackgroundOverride = pMatchDesc->GetMapLoadBackgroundOverride( true ); + if ( pszBackgroundOverride ) + { + // Success! We're done + bWidescreenBackground = true; + } + } + + if ( !bWideScreen && !pszBackgroundOverride ) + { + pszBackgroundOverride = pMatchDesc->GetMapLoadBackgroundOverride( false ); + } + + } + else if ( bIsMVM ) + { + // this will preserve the current behavior for non-matchmaking servers + pszBackgroundOverride = "mvm_background_map"; + } + + bool bIsCommunityMap = false; + const char *pAuthors = NULL; + + const MapDef_t *pMapInfo = GetItemSchema()->GetMasterMapDefByName( pMapName ); + if ( pMapInfo ) + { + bIsCommunityMap = pMapInfo->IsCommunityMap(); + pAuthors = pMapInfo->pszAuthorsLocKey; + } + + ShowMapInfo( true, bIsMVM, ( pszBackgroundOverride != NULL ) ); + + m_xStartLeaderboard = 0; + m_yStartLeaderboard = 0; + + // If we're loading a background map, don't display anything + // HACK: Client doesn't get gpGlobals->eLoadType, so just do string compare for now. + if ( Q_stristr( pMapName, "background") ) + { + ClearMapLabel(); + } + else + { + // set the map name in the UI + wchar_t wzMapName[255]=L""; + g_pVGuiLocalize->ConvertANSIToUnicode( GetMapDisplayName( pMapName ), wzMapName, sizeof( wzMapName ) ); + + SetDialogVariable( "maplabel", wzMapName ); + SetDialogVariable( "maptype", g_pVGuiLocalize->Find( GetMapType( pMapName ) ) ); + + vgui::Label *pLabel = dynamic_cast<Label *>( FindChildByName( "OnYourWayLabel" ) ); + if ( pLabel && !pLabel->IsVisible() ) + { + pLabel->SetVisible( true ); + } + + pLabel = dynamic_cast<Label *>( FindChildByName( "MapType" ) ); + if ( pLabel && !pLabel->IsVisible() ) + { + pLabel->SetVisible( true ); + } + + ImagePanel *pMapImage = m_pMapInfoPanel ? dynamic_cast< ImagePanel *>( m_pMapInfoPanel->FindChildByName( "MapImage" ) ) : NULL; + if ( pMapImage ) + { + // load the map image (if it exists for the current map) + char szMapImage[ MAX_PATH ]; + Q_snprintf( szMapImage, sizeof( szMapImage ), "VGUI/maps/menu_photos_%s", pMapName ); + Q_strlower( szMapImage ); + + IMaterial *pMapMaterial = materials->FindMaterial( szMapImage, TEXTURE_GROUP_VGUI, false ); + if ( pMapMaterial && !IsErrorMaterial( pMapMaterial ) && !pszBackgroundOverride ) + { + // take off the vgui/ at the beginning when we set the image + Q_snprintf( szMapImage, sizeof( szMapImage ), "maps/menu_photos_%s", pMapName ); + Q_strlower( szMapImage ); + pMapImage->SetImage( szMapImage ); + pMapImage->SetVisible( true ); + } + else + { + pMapImage->SetVisible( false ); + } + } + + ImagePanel *pBackgroundImage = m_pMapInfoPanel ? dynamic_cast< ImagePanel *>( m_pMapInfoPanel->FindChildByName( "Background" ) ) : NULL; + if ( pBackgroundImage ) + { + const char* pszBackgroundImage = pszBackgroundOverride ? pszBackgroundOverride : "stamp_background_map"; + + pBackgroundImage->SetImage( pszBackgroundImage ); + + // Resize to accomodate the background image coming in + if ( bWidescreenBackground ) + { + pBackgroundImage->SetWide( GetWide() ); + } + else + { + pBackgroundImage->SetWide( GetTall() * ( 4.f / 3.f ) ); + } + + } + + if ( bIsMVM ) + { + UpdateClassDetails( true ); + m_pMapInfoPanel->SetDialogVariable( "map_leaderboard_title", "" ); + m_pMapInfoPanel->SetDialogVariable( "title", "" ); + m_pMapInfoPanel->SetDialogVariable( "authors", "" ); + + FOR_EACH_VEC( m_vecLeaderboardEntries, i ) + { + EditablePanel *pContainer = dynamic_cast< EditablePanel* >( m_vecLeaderboardEntries[i] ); + if ( pContainer ) + { + pContainer->SetVisible( false ); + } + } + } + else + { + m_pLeaderboardTitle = NULL; + // add authors + if ( m_pMapInfoPanel ) + { + if ( bIsCommunityMap ) + { + m_pMapInfoPanel->SetDialogVariable( "title", g_pVGuiLocalize->Find( "#TF_MapAuthors_Community_Title" ) ); + m_pMapInfoPanel->SetDialogVariable( "map_leaderboard_title", "" ); + m_pMapInfoPanel->SetDialogVariable( "authors", g_pVGuiLocalize->Find( pAuthors ) ); + m_pLeaderboardTitle = m_pMapInfoPanel->FindChildByName( "MapLeaderboardTitle" ); + } + else + { + m_pMapInfoPanel->SetDialogVariable( "title", g_pVGuiLocalize->Find( "#TF_DuelLeaderboard_Title" ) ); + m_pMapInfoPanel->SetDialogVariable( "map_leaderboard_title", "" ); + m_pMapInfoPanel->SetDialogVariable( "authors", "" ); + m_pLeaderboardTitle = m_pMapInfoPanel->FindChildByName( "Title" ); + } + } + if ( m_pLeaderboardTitle ) + { + m_pLeaderboardTitle->GetPos( m_xStartLeaderboard, m_yStartLeaderboard ); + m_yStartLeaderboard += m_pLeaderboardTitle->GetTall(); + } + + // request leaderboard data + m_bShowingLeaderboard = true; + if ( bIsCommunityMap ) + { + MapInfo_RefreshLeaderboard( pMapName ); + } + else + { + Leaderboards_Refresh(); + } + m_bLoadingCommunityMap = bIsCommunityMap; + + if ( m_pContributedPanel && steamapicontext && steamapicontext->SteamUser() && steamapicontext->SteamFriends() ) + { + int iDonationAmount = MapInfo_GetDonationAmount( steamapicontext->SteamUser()->GetSteamID().GetAccountID(), pMapName ); + m_pContributedPanel->SetVisible( iDonationAmount != 0 ); + if ( iDonationAmount != 0 ) + { + m_pContributedPanel->SetDialogVariable( "playername", steamapicontext->SteamFriends()->GetPersonaName() ); + } + } + + UpdateLeaderboard(); + } + } +} + +void CTFStatsSummaryPanel::UpdateLeaderboard() +{ + if ( m_pMapInfoPanel == NULL || steamapicontext == NULL || steamapicontext->SteamUserStats() == NULL || steamapicontext->SteamUser() == NULL ) + return; + + const int kMaxVisible_Supporters = 5; + const int kIdeallyNumVisible_Supporters = 3; + const int kMaxVisible_DuelWins = 10; + const int kIdeallyNumVisible_DuelWins = 5; + + // retrieve scores + CUtlVector< LeaderboardEntry_t* > scores; + bool bVisible = true; + int iNumLeaderboardEntries = 0; + if ( m_bLoadingCommunityMap ) + { + bVisible = MapInfo_GetLeaderboardInfo( engine->GetLevelName(), scores, iNumLeaderboardEntries, kIdeallyNumVisible_Supporters ); + wchar_t wzNumEntriesString[256]; + _snwprintf( wzNumEntriesString, ARRAYSIZE( wzNumEntriesString ), L"%i", iNumLeaderboardEntries ); + wchar_t wzTitle[256]; + g_pVGuiLocalize->ConstructString_safe( wzTitle, g_pVGuiLocalize->Find( "#TF_MapDonators_Title" ), 1, wzNumEntriesString ); + m_pMapInfoPanel->SetDialogVariable( "map_leaderboard_title", wzTitle ); + } + else + { + bVisible = Leaderboards_GetDuelWins( scores, false ); + if ( bVisible && scores.Count() < kIdeallyNumVisible_DuelWins ) + { + bVisible = Leaderboards_GetDuelWins( scores, true ) && scores.Count() > 0; + } + // show old stats + m_pPlayerData->SetVisible( bVisible == false ); + if ( m_pMapInfoPanel ) + { + vgui::Panel* pInfoBG = m_pMapInfoPanel->FindChildByName( "InfoBG" ); + if ( pInfoBG ) + { + pInfoBG->SetVisible( bVisible ); + } + } + } + + const int kMaxVisible = m_bLoadingCommunityMap ? kMaxVisible_Supporters : kMaxVisible_DuelWins; + + // try to show local player in relation to the people in the list + if ( bVisible && scores.Count() > 0 && steamapicontext && steamapicontext->SteamUser() && steamapicontext->SteamUserStats() ) + { + int iLocalPlayerIdx = -1; + CSteamID localSteamID = steamapicontext->SteamUser()->GetSteamID(); + FOR_EACH_VEC( scores, i ) + { + const LeaderboardEntry_t *leaderboardEntry = scores[i]; + if ( leaderboardEntry->m_steamIDUser == localSteamID ) + { + iLocalPlayerIdx = i; + break; + } + } + // local player is in the list, but is outside the visible range + // so we want to move them to the last spot + // and move the closest person above them as well + if ( iLocalPlayerIdx >= kMaxVisible ) + { + LeaderboardEntry_t *entryLocalPlayer = scores[iLocalPlayerIdx]; + LeaderboardEntry_t *closestPlayer = scores[iLocalPlayerIdx - 1]; + scores[kMaxVisible - 1] = entryLocalPlayer; + scores[kMaxVisible - 2] = closestPlayer; + } + } + + // set avatars and names + int x = m_xStartLeaderboard; + int y = m_yStartLeaderboard; + FOR_EACH_VEC( m_vecLeaderboardEntries, i ) + { + EditablePanel *pContainer = dynamic_cast< EditablePanel* >( m_vecLeaderboardEntries[i] ); + if ( pContainer ) + { + bool bIsEntryVisible = bVisible && i < scores.Count() && i < kMaxVisible; + pContainer->SetVisible( bIsEntryVisible ); + pContainer->SetPos( x, y ); + y += pContainer->GetTall(); + if ( bIsEntryVisible ) + { + const LeaderboardEntry_t *leaderboardEntry = scores[i]; + const CSteamID &steamID = leaderboardEntry->m_steamIDUser; + pContainer->SetDialogVariable( "username", CFmtStr( "%d. %s - %d", leaderboardEntry->m_nGlobalRank, InventoryManager()->PersonaName_Get( steamID.GetAccountID() ), leaderboardEntry->m_nScore ) ); + CAvatarImagePanel *pAvatar = dynamic_cast< CAvatarImagePanel* >( pContainer->FindChildByName( "AvatarImage" ) ); + if ( pAvatar ) + { + pAvatar->SetShouldDrawFriendIcon( false ); + pAvatar->SetPlayer( steamID, k_EAvatarSize32x32 ); + } + } + } + } + + if ( m_pLeaderboardTitle ) + { + bool bShowTitle = bVisible && scores.Count() > 0; + if ( m_pLeaderboardTitle->IsVisible() != bShowTitle ) + { + m_pLeaderboardTitle->SetVisible( bShowTitle ); + } + } + + m_bShowingLeaderboard = bVisible; +} + +//----------------------------------------------------------------------------- +// Purpose: Updates the dialog +//----------------------------------------------------------------------------- +void CTFStatsSummaryPanel::UpdateDialog() +{ + UpdateMainBackground(); + + if ( g_bIsReplayRewinding || engine->IsLoadingDemo() || engine->IsPlayingDemo() || engine->IsSkippingPlayback() ) + { + // hide all of the various panels for the other loadscreen modes + if ( IsPC() ) + { + ClearMapLabel(); + + m_pPlayerData->SetVisible( false ); + m_pNextTipButton->SetVisible( false ); + m_pResetStatsButton->SetVisible( false ); + m_pInteractiveHeaders->SetVisible( false ); + m_pNonInteractiveHeaders->SetVisible( false ); + m_pTipText->SetVisible( false ); + m_pTipImage->SetVisible( false ); + + if ( m_pMapInfoPanel ) + { + m_pMapInfoPanel->SetVisible( false ); + + vgui::Panel* pInfoBG = m_pMapInfoPanel->FindChildByName( "InfoBG" ); + if ( pInfoBG ) + { + pInfoBG->SetVisible( false ); + } + } + } + + return; + } + + RandomSeed( Plat_MSTime() ); + + m_iTotalSpawns = 0; + + // if we don't have stats for any class, add empty stat entries for them + for ( int iClass = TF_FIRST_NORMAL_CLASS; iClass <= TF_LAST_NORMAL_CLASS; iClass++ ) + { + if ( iClass == TF_CLASS_CIVILIAN ) + continue; // Ignore the civilian. + + int j; + for ( j = 0; j < m_aClassStats.Count(); j++ ) + { + if ( m_aClassStats[j].iPlayerClass == iClass ) + { + m_iTotalSpawns += m_aClassStats[j].iNumberOfRounds; + break; + } + } + if ( j == m_aClassStats.Count() ) + { + ClassStats_t stats; + stats.iPlayerClass = iClass; + m_aClassStats.AddToTail( stats ); + } + } + + ClearMapLabel(); + +#ifdef _X360 + if ( m_pFooter ) + { + m_pFooter->ShowButtonLabel( "nexttip", m_bShowBackButton ); + m_pFooter->ShowButtonLabel( "back", m_bShowBackButton ); + } +#endif + + // fill out bar charts + UpdateBarCharts(); + // fill out class details + UpdateClassDetails(); + // update the tip + UpdateTip(); + // show or hide controls depending on if we're interactive or not + UpdateControls(); +} + +//----------------------------------------------------------------------------- +// Purpose: Updates bar charts +//----------------------------------------------------------------------------- +void CTFStatsSummaryPanel::UpdateBarCharts() +{ + // sort the class stats by the selected stat for right-hand bar chart + m_aClassStats.Sort( &CTFStatsSummaryPanel::CompareClassStats ); + + // loop for left & right hand charts + for ( int iChart = 0; iChart < 2; iChart++ ) + { + float flMax = 0; + for ( int i = 0; i < m_aClassStats.Count(); i++ ) + { + // get max value of stat being charted so we know how to scale the graph + float flVal = GetDisplayValue( m_aClassStats[i], m_statBarGraph[iChart], m_displayBarGraph[iChart] ); + flMax = MAX( flVal, flMax ); + } + + // draw the bar chart value for each player class + // TODO: Fix up after the civilian becomes playable. + int iChartBar = 0; + for ( int i = 0; i < m_aClassStats.Count(); i++ ) + { + int iClass = m_aClassStats[i].iPlayerClass; + if ( iClass == TF_CLASS_CIVILIAN ) + { + continue; + } + if ( 0 == iChart ) + { + // if this is the first chart, set the class label for each class + m_pPlayerData->SetDialogVariable( CFmtStr( "class%d", iChartBar+1 ), g_pVGuiLocalize->Find( g_aPlayerClassNames[iClass] ) ); + } + // draw the bar for this class + DisplayBarValue( iChart, iChartBar++, m_aClassStats[i], m_statBarGraph[iChart], m_displayBarGraph[iChart], flMax ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Updates class details +//----------------------------------------------------------------------------- +void CTFStatsSummaryPanel::UpdateClassDetails( bool bIsMVM ) +{ + vgui::Label *pTitle = assert_cast< vgui::Label* >( FindChildByName( "RecordsLabel1", true ) ); + if ( pTitle ) + { + pTitle->SetText( bIsMVM ? "#StatSummary_Label_BestMVMMoments" : "#StatSummary_Label_BestMoments" ); + } + + const wchar_t *wzWithClassFmt = g_pVGuiLocalize->Find( "#StatSummary_ScoreAsClassFmt" ); + const wchar_t *wzWithoutClassFmt = L"%s1"; + + ClassDetails_t *pStatDetails = ( bIsMVM ? g_PerClassMVMStatDetails : g_PerClassStatDetails ); + int nArraySize = ( bIsMVM ? ARRAYSIZE( g_PerClassMVMStatDetails ) : ARRAYSIZE( g_PerClassStatDetails ) ); + + // display the record for each stat + int iRow = 0; + for ( int i = 0; i < nArraySize; i++ ) + { + TFStatType_t statType = pStatDetails[i].statType; + + int iClass = TF_CLASS_UNDEFINED; + int iMaxVal = 0; + + // if there is a selected class, and if this stat should not be shown for this class, skip this stat + if ( m_iSelectedClass != TF_CLASS_UNDEFINED && ( 0 == ( pStatDetails[i].iFlagsClass & MAKESTATFLAG( m_iSelectedClass ) ) ) ) + continue; + + if ( m_iSelectedClass == TF_CLASS_UNDEFINED ) + { + // if showing best from any class, look through all player classes to determine the max value of this stat + for ( int j = 0; j < m_aClassStats.Count(); j++ ) + { + RoundStats_t *pRoundStats = &( bIsMVM ? m_aClassStats[j].maxMVM : m_aClassStats[j].max ); + if ( pRoundStats->m_iStat[statType] > iMaxVal ) + { + // remember max value and class that has max value + iMaxVal = pRoundStats->m_iStat[statType]; + iClass = m_aClassStats[j].iPlayerClass; + } + } + } + else + { + // show best from selected class + iClass = m_iSelectedClass; + for ( int j = 0; j < m_aClassStats.Count(); j++ ) + { + if ( m_aClassStats[j].iPlayerClass == iClass ) + { + RoundStats_t *pRoundStats = &( bIsMVM ? m_aClassStats[j].maxMVM : m_aClassStats[j].max ); + iMaxVal = pRoundStats->m_iStat[statType]; + break; + } + } + } + + wchar_t wzStatNum[32]; + wchar_t wzStatVal[128]; + if ( TFSTAT_PLAYTIME == statType ) + { + // playtime gets displayed as a time string + g_pVGuiLocalize->ConvertANSIToUnicode( FormatSeconds( iMaxVal ), wzStatNum, sizeof( wzStatNum ) ); + } + else + { + // all other stats are just shown as a # + swprintf_s( wzStatNum, ARRAYSIZE( wzStatNum ), L"%d", iMaxVal ); + } + + if ( TF_CLASS_UNDEFINED == m_iSelectedClass && iMaxVal > 0 ) + { + // if we are doing a cross-class view (no single selected class) and the max value is non-zero, show "# (as <class>)" + wchar_t *wzLocalizedClassName = g_pVGuiLocalize->Find( g_aPlayerClassNames[iClass] ); + g_pVGuiLocalize->ConstructString_safe( wzStatVal, wzWithClassFmt, 2, wzStatNum, wzLocalizedClassName ); + } + else + { + // just show the value + g_pVGuiLocalize->ConstructString_safe( wzStatVal, wzWithoutClassFmt, 1, wzStatNum ); + } + + // set the label + m_pPlayerData->SetDialogVariable( CFmtStr( "classrecord%dlabel", iRow+1 ), g_pVGuiLocalize->Find( pStatDetails[i].szResourceName ) ); + // set the value + m_pPlayerData->SetDialogVariable( CFmtStr( "classrecord%dvalue", iRow+1 ), wzStatVal ); + + iRow++; + } + + // if there are any leftover rows for the selected class, fill out the remaining rows with blank labels and values + for ( ; iRow < 15; iRow ++ ) + { + m_pPlayerData->SetDialogVariable( CFmtStr( "classrecord%dlabel", iRow+1 ), "" ); + m_pPlayerData->SetDialogVariable( CFmtStr( "classrecord%dvalue", iRow+1 ), "" ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Updates the tip +//----------------------------------------------------------------------------- +void CTFStatsSummaryPanel::UpdateTip() +{ + int iTipClass = TF_CLASS_UNDEFINED; + + SetDialogVariable( "tiptext", g_TFTips.GetRandomTip( iTipClass ) ); + + if ( m_pTipImage ) + { + if ( iTipClass > TF_CLASS_UNDEFINED && iTipClass <= TF_CLASS_ENGINEER ) + { + m_pTipImage->SetVisible( true ); + m_pTipImage->SetImage( g_pszTipsClassImages[iTipClass] ); + } + else + { + m_pTipImage->SetVisible( false ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Shows or hides controls +//----------------------------------------------------------------------------- +void CTFStatsSummaryPanel::UpdateControls() +{ + // show or hide controls depending on what mode we're in +#ifndef _X360 + bool bShowPlayerData = ( m_bInteractive || m_iTotalSpawns > 0 ); +#else + bool bShowPlayerData = ( m_bInteractive || m_bShowBackButton || m_iTotalSpawns > 0 ); +#endif + m_pPlayerData->SetVisible( bShowPlayerData ); + m_pInteractiveHeaders->SetVisible( m_bInteractive ); + m_pNonInteractiveHeaders->SetVisible( !m_bInteractive ); + m_pTipText->SetVisible( bShowPlayerData ); + m_pTipImage->SetVisible( bShowPlayerData ); + + if ( !IsX360() ) + { + if ( !m_bInteractive ) + { + char szTemp[128]; + + // update our non-interactive headers to match the current combo box selections + Label *pLabel = dynamic_cast<Label *>( m_pNonInteractiveHeaders->FindChildByName( "BarChartLabelA" ) ); + if ( pLabel && m_pBarChartComboBoxA ) + { + m_pBarChartComboBoxA->GetItemText( m_pBarChartComboBoxA->GetActiveItem(), szTemp, sizeof( szTemp ) ); + pLabel->SetText( szTemp ); + } + + pLabel = dynamic_cast<Label *>( m_pNonInteractiveHeaders->FindChildByName( "BarChartLabelB" ) ); + if ( pLabel && m_pBarChartComboBoxB ) + { + m_pBarChartComboBoxB->GetItemText( m_pBarChartComboBoxB->GetActiveItem(), szTemp, sizeof( szTemp ) ); + pLabel->SetText( szTemp ); + } + + pLabel = dynamic_cast<Label *>( m_pNonInteractiveHeaders->FindChildByName( "OverallRecordLabel" ) ); + if ( pLabel && m_pClassComboBox ) + { + m_pClassComboBox->GetItemText( m_pClassComboBox->GetActiveItem(), szTemp, sizeof( szTemp ) ); + pLabel->SetText( szTemp ); + } + } + } + +#ifndef _X360 + m_pNextTipButton->SetVisible( m_bInteractive ); + m_pResetStatsButton->SetVisible( m_bInteractive ); + m_pCloseButton->SetVisible( m_bInteractive && !m_bEmbedded ); +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: Initializes a bar chart combo box +//----------------------------------------------------------------------------- +void CTFStatsSummaryPanel::InitBarChartComboBox( ComboBox *pComboBox ) +{ + struct BarChartComboInit_t + { + TFStatType_t statType; + StatDisplay_t statDisplay; + const char *szName; + }; + + BarChartComboInit_t initData[] = + { + { TFSTAT_POINTSSCORED, SHOW_MAX, "#StatSummary_StatTitle_MostPoints" }, + { TFSTAT_POINTSSCORED, SHOW_AVG, "#StatSummary_StatTitle_AvgPoints" }, + { TFSTAT_KILLS, SHOW_MAX, "#StatSummary_StatTitle_MostKills" }, + { TFSTAT_KILLS, SHOW_AVG, "#StatSummary_StatTitle_AvgKills" }, + { TFSTAT_CAPTURES, SHOW_MAX, "#StatSummary_StatTitle_MostCaptures" }, + { TFSTAT_CAPTURES, SHOW_AVG, "#StatSummary_StatTitle_AvgCaptures" }, + { TFSTAT_KILLASSISTS, SHOW_MAX, "#StatSummary_StatTitle_MostAssists" }, + { TFSTAT_KILLASSISTS, SHOW_AVG, "#StatSummary_StatTitle_AvgAssists" }, + { TFSTAT_DAMAGE, SHOW_MAX, "#StatSummary_StatTitle_MostDamage" }, + { TFSTAT_DAMAGE, SHOW_AVG, "#StatSummary_StatTitle_AvgDamage" }, + { TFSTAT_PLAYTIME, SHOW_TOTAL, "#StatSummary_StatTitle_TotalPlaytime" }, + { TFSTAT_PLAYTIME, SHOW_MAX, "#StatSummary_StatTitle_LongestLife" }, + }; + + // set the font + HFont hFont = scheme()->GetIScheme( GetScheme() )->GetFont( "ScoreboardVerySmall", true ); + pComboBox->SetFont( hFont ); + pComboBox->RemoveAll(); + // add all the options to the combo box + for ( int i=0; i < ARRAYSIZE( initData ); i++ ) + { + KeyValues *pKeyValues = new KeyValues( "data" ); + pKeyValues->SetInt( "stattype", initData[i].statType ); + pKeyValues->SetInt( "statdisplay", initData[i].statDisplay ); + pComboBox->AddItem( g_pVGuiLocalize->Find( initData[i].szName ), pKeyValues ); + } + pComboBox->SetNumberOfEditLines( ARRAYSIZE( initData ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: Helper function that sets the specified dialog variable to +// "<value> (as <localized class name>)" +//----------------------------------------------------------------------------- +void CTFStatsSummaryPanel::SetValueAsClass( const char *pDialogVariable, int iValue, int iPlayerClass ) +{ + if ( iValue > 0 ) + { + wchar_t *wzScoreAsClassFmt = g_pVGuiLocalize->Find( "#StatSummary_ScoreAsClassFmt" ); + wchar_t *wzLocalizedClassName = g_pVGuiLocalize->Find( g_aPlayerClassNames[iPlayerClass] ); + wchar_t wzVal[16]; + wchar_t wzMsg[128]; + swprintf( wzVal, ARRAYSIZE( wzVal ), L"%d", iValue ); + g_pVGuiLocalize->ConstructString_safe( wzMsg, wzScoreAsClassFmt, 2, wzVal, wzLocalizedClassName ); + m_pPlayerData->SetDialogVariable( pDialogVariable, wzMsg ); + } + else + { + m_pPlayerData->SetDialogVariable( pDialogVariable, "0" ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Sets the specified bar chart item to the specified value, in range 0->1 +//----------------------------------------------------------------------------- +void CTFStatsSummaryPanel::DisplayBarValue( int iChart, int iBar, ClassStats_t &stats, TFStatType_t statType, StatDisplay_t statDisplay, float flMaxValue ) +{ + const char *szControlSuffix = ( 0 == iChart ? "A" : "B" ); + Panel *pBar = m_pPlayerData->FindChildByName( CFmtStr( "ClassBar%d%s", iBar+1, szControlSuffix ) ); + Label *pLabel = dynamic_cast<Label*>( m_pPlayerData->FindChildByName( CFmtStr( "classbarlabel%d%s", iBar+1, szControlSuffix ) ) ); + if ( !pBar || !pLabel ) + return; + + // get the stat value + float flValue = GetDisplayValue( stats, statType, statDisplay ); + // calculate the bar size to draw, in the range of 0.0->1.0 + float flBarRange = SafeCalcFraction( flValue, flMaxValue ); + // calculate the # of pixels of bar width to draw + int iBarWidth = MAX( (int) ( flBarRange * (float) m_iBarMaxWidth ), 1 ); + + // Get the text label to draw for this bar. For values of 0, draw nothing, to minimize clutter + const char *szLabel = ( flValue > 0 ? RenderValue( flValue, statType, statDisplay ) : "" ); + // draw the label outside the bar if there's room + bool bLabelOutsideBar = true; + const int iLabelSpacing = 4; + HFont hFont = pLabel->GetFont(); + int iLabelWidth = UTIL_ComputeStringWidth( hFont, szLabel ); + if ( iBarWidth + iLabelWidth + iLabelSpacing > m_iBarMaxWidth ) + { + // if there's not room outside the bar for the label, draw it inside the bar + bLabelOutsideBar = false; + } + + int xBar,yBar,xLabel,yLabel; + pBar->GetPos( xBar,yBar ); + pLabel->GetPos( xLabel,yLabel ); + + m_pPlayerData->SetDialogVariable( CFmtStr( "classbarlabel%d%s", iBar+1, szControlSuffix ), szLabel ); + if ( 1 == iChart ) + { + // drawing code for RH bar chart + xBar = m_xStartRHBar; + pBar->SetBounds( xBar, yBar, iBarWidth, m_iBarHeight ); + if ( bLabelOutsideBar ) + { + pLabel->SetPos( xBar + iBarWidth + iLabelSpacing, yLabel ); + } + else + { + pLabel->SetPos( xBar + iBarWidth - ( iLabelWidth + iLabelSpacing ), yLabel ); + } + } + else + { + // drawing code for LH bar chart + xBar = m_xStartLHBar + m_iBarMaxWidth - iBarWidth; + pBar->SetBounds( xBar, yBar, iBarWidth, m_iBarHeight ); + if ( bLabelOutsideBar ) + { + pLabel->SetPos( xBar - ( iLabelWidth + iLabelSpacing ), yLabel ); + } + else + { + pLabel->SetPos( xBar + iLabelSpacing, yLabel ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Calculates a fraction and guards from divide by 0. (Returns 0 if +// denominator is 0.) +//----------------------------------------------------------------------------- +float CTFStatsSummaryPanel::SafeCalcFraction( float flNumerator, float flDemoninator ) +{ + if ( 0 == flDemoninator ) + return 0; + return flNumerator / flDemoninator; +} + +//----------------------------------------------------------------------------- +// Purpose: Formats # of seconds into a string +//----------------------------------------------------------------------------- +const char *FormatSeconds( int seconds ) +{ + static char string[64]; + + int hours = 0; + int minutes = seconds / 60; + + if ( minutes > 0 ) + { + seconds -= (minutes * 60); + hours = minutes / 60; + + if ( hours > 0 ) + { + minutes -= (hours * 60); + } + } + + if ( hours > 0 ) + { + Q_snprintf( string, sizeof(string), "%2i:%02i:%02i", hours, minutes, seconds ); + } + else + { + Q_snprintf( string, sizeof(string), "%02i:%02i", minutes, seconds ); + } + + return string; +} + +//----------------------------------------------------------------------------- +// Purpose: Static sort function that sorts in descending order by play time +//----------------------------------------------------------------------------- +int __cdecl CTFStatsSummaryPanel::CompareClassStats( const ClassStats_t *pStats0, const ClassStats_t *pStats1 ) +{ + // sort stats first by right-hand bar graph + TFStatType_t statTypePrimary = GStatsSummaryPanel()->m_statBarGraph[1]; + StatDisplay_t statDisplayPrimary = GStatsSummaryPanel()->m_displayBarGraph[1]; + // then by left-hand bar graph + TFStatType_t statTypeSecondary = GStatsSummaryPanel()->m_statBarGraph[0]; + StatDisplay_t statDisplaySecondary = GStatsSummaryPanel()->m_displayBarGraph[0]; + + float flValPrimary0 = GetDisplayValue( (ClassStats_t &) *pStats0, statTypePrimary, statDisplayPrimary ); + float flValPrimary1 = GetDisplayValue( (ClassStats_t &) *pStats1, statTypePrimary, statDisplayPrimary ); + float flValSecondary0 = GetDisplayValue( (ClassStats_t &) *pStats0, statTypeSecondary, statDisplaySecondary ); + float flValSecondary1 = GetDisplayValue( (ClassStats_t &) *pStats1, statTypeSecondary, statDisplaySecondary ); + + // sort in descending order by primary stat value + if ( flValPrimary1 > flValPrimary0 ) + return 1; + if ( flValPrimary1 < flValPrimary0 ) + return -1; + + // if primary stat values are equal, sort in descending order by secondary stat value + if ( flValSecondary1 > flValSecondary0 ) + return 1; + if ( flValSecondary1 < flValSecondary0 ) + return -1; + + // if primary & secondary stats are equal, sort by class for consistent sort order + return ( pStats1->iPlayerClass - pStats0->iPlayerClass ); +} + +//----------------------------------------------------------------------------- +// Purpose: Called when text changes in combo box +//----------------------------------------------------------------------------- +void CTFStatsSummaryPanel::OnTextChanged( KeyValues *data ) +{ + Panel *pPanel = reinterpret_cast<vgui::Panel *>( data->GetPtr("panel") ); + vgui::ComboBox *pComboBox = dynamic_cast<vgui::ComboBox *>( pPanel ); + + if ( m_pBarChartComboBoxA == pComboBox || m_pBarChartComboBoxB == pComboBox ) + { + // a bar chart combo box changed, update the bar charts + + KeyValues *pUserDataA = m_pBarChartComboBoxA->GetActiveItemUserData(); + KeyValues *pUserDataB = m_pBarChartComboBoxB->GetActiveItemUserData(); + if ( !pUserDataA || !pUserDataB ) + return; + m_statBarGraph[0] = (TFStatType_t) pUserDataA->GetInt( "stattype" ); + m_displayBarGraph[0] = (StatDisplay_t) pUserDataA->GetInt( "statdisplay" ); + m_statBarGraph[1] = (TFStatType_t) pUserDataB->GetInt( "stattype" ); + m_displayBarGraph[1] = (StatDisplay_t) pUserDataB->GetInt( "statdisplay" ); + UpdateBarCharts(); + } + else if ( m_pClassComboBox == pComboBox ) + { + // the class selection combo box changed, update class details + + KeyValues *pUserData = m_pClassComboBox->GetActiveItemUserData(); + if ( !pUserData ) + return; + + m_iSelectedClass = pUserData->GetInt( "class", TF_CLASS_UNDEFINED ); + + UpdateClassDetails(); + } + +} + +//----------------------------------------------------------------------------- +// Purpose: Command target handler called from reset stats confirmation query box +//----------------------------------------------------------------------------- +void CTFStatsSummaryPanel::DoResetStats() +{ +#ifndef _X360 + // reset the stats + engine->ClientCmd( "resetplayerstats" ); +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: Returns the stat value for specified display type +//----------------------------------------------------------------------------- +float CTFStatsSummaryPanel::GetDisplayValue( ClassStats_t &stats, TFStatType_t statType, StatDisplay_t statDisplay ) +{ + switch ( statDisplay ) + { + case SHOW_MAX: + return stats.max.m_iStat[statType]; + break; + case SHOW_TOTAL: + return stats.accumulated.m_iStat[statType]; + break; + case SHOW_AVG: + return SafeCalcFraction( stats.accumulated.m_iStat[statType], stats.iNumberOfRounds ); + break; + default: + AssertOnce( false ); + return 0; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Gets the text representation of this value +//----------------------------------------------------------------------------- +const char *CTFStatsSummaryPanel::RenderValue( float flValue, TFStatType_t statType, StatDisplay_t statDisplay ) +{ + static char szValue[64]; + if ( TFSTAT_PLAYTIME == statType ) + { + // the playtime stat is shown in seconds + return FormatSeconds( (int) flValue ); + } + else if ( SHOW_AVG == statDisplay ) + { + // if it's an average, render as a float w/2 decimal places + Q_snprintf( szValue, ARRAYSIZE( szValue ), "%.2f", flValue ); + } + else + { + // otherwise, render as an integer + Q_snprintf( szValue, ARRAYSIZE( szValue ), "%d", (int) flValue ); + } + + return szValue; +} + +//----------------------------------------------------------------------------- +// Purpose: Event handler +//----------------------------------------------------------------------------- +void CTFStatsSummaryPanel::FireGameEvent( IGameEvent *event ) +{ + const char *pEventName = event->GetName(); + + // when we are changing levels and + if ( 0 == Q_strcmp( pEventName, "server_spawn" ) ) + { + if ( !m_bInteractive ) + { + const char *pMapName = event->GetString( "mapname" ); + if ( pMapName ) + { + OnMapLoad( pMapName ); + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Called when we are activated during level load +//----------------------------------------------------------------------------- +void CTFStatsSummaryPanel::OnActivate() +{ + ClearMapLabel(); + + m_bShowingLeaderboard = false; + m_bLoadingCommunityMap = false; + ShowMapInfo( false ); + +#ifdef _X360 + m_bShowBackButton = false; +#endif + + UpdateDialog(); +} + +//----------------------------------------------------------------------------- +// Purpose: Called when we are deactivated at end of level load +//----------------------------------------------------------------------------- +void CTFStatsSummaryPanel::OnDeactivate() +{ + ClearMapLabel(); +} + +CON_COMMAND( showstatsdlg, "Shows the player stats dialog" ) +{ +#ifdef _DEBUG + GStatsSummaryPanel()->InvalidateLayout( false, true ); +#endif + GStatsSummaryPanel()->ShowModal(); +#ifdef _DEBUG + GStatsSummaryPanel()->OnMapLoad( "cp_coldfront" ); +#endif +} |