//========= 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 }