//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // //=============================================================================// #include "cbase.h" #include "hudelement.h" #include "iclientmode.h" #include #include #include #include #include "vgui_avatarimage.h" #include "tf_hud_match_status.h" #include "tf_gamerules.h" #include "c_tf_team.h" #include "vgui_controls/ScalableImagePanel.h" #include "tf_time_panel.h" #include "c_team_objectiveresource.h" #include "game_controls/spectatorgui.h" #include "c_tf_playerresource.h" #include "tf_gc_client.h" #include "tf_match_description.h" #include "tf_hud_tournament.h" #include "tf_classmenu.h" extern ConVar mp_winlimit; extern ConVar mp_tournament_stopwatch; using namespace vgui; void AddSubKeyNamed( KeyValues *pKeys, const char *pszName ); //----------------------------------------------------------------------------- // Purpose: Use the new match HUD or the old? Right now, Comp is the key //----------------------------------------------------------------------------- bool ShouldUseMatchHUD() { const IMatchGroupDescription* pMatchDesc = NULL; if ( GTFGCClientSystem()->BHaveLiveMatch() ) { pMatchDesc = GetMatchGroupDescription( GTFGCClientSystem()->GetLiveMatchGroup() ); } else if ( TFGameRules() ) { pMatchDesc = GetMatchGroupDescription( TFGameRules()->GetCurrentMatchGroup() ); } if ( pMatchDesc ) { return pMatchDesc->m_params.m_bUseMatchHud; } return false; } const int g_nMaxSupportedRounds = 5; //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CRoundCounterPanel::CRoundCounterPanel( Panel *parent, const char *panelName ) : BaseClass( parent, panelName ) , m_pRoundIndicatorKVs( NULL ) , m_pRoundWinIndicatorRedKV( NULL ) , m_pRoundWinIndicatorBlueKV( NULL ) , m_bCountDirty( false ) { ListenForGameEvent( "winlimit_changed" ); ListenForGameEvent( "winpanel_show_scores" ); ListenForGameEvent( "stop_watch_changed" ); ListenForGameEvent( "teamplay_round_start" ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CRoundCounterPanel::~CRoundCounterPanel() { if ( m_pRoundIndicatorKVs ) m_pRoundIndicatorKVs->deleteThis(); if ( m_pRoundWinIndicatorRedKV ) m_pRoundWinIndicatorRedKV->deleteThis(); if ( m_pRoundWinIndicatorBlueKV ) m_pRoundWinIndicatorBlueKV->deleteThis(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CRoundCounterPanel::ApplySchemeSettings(IScheme *pScheme) { BaseClass::ApplySchemeSettings( pScheme ); LoadControlSettings( "resource/UI/HudRoundCounter.res" ); } //----------------------------------------------------------------------------- // Purpose: Put a copy of the specified keys in block pszKeyName from pKVIn // into pKV //----------------------------------------------------------------------------- void LoadKeyValues( KeyValues** pKV, KeyValues* pKVIn, const char* pszKeyName ) { if ( (*pKV) ) (*pKV)->deleteThis(); (*pKV) = pKVIn->FindKey( pszKeyName ); if ((*pKV)) { (*pKV) = (*pKV)->MakeCopy(); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CRoundCounterPanel::ApplySettings( KeyValues *inResourceData ) { BaseClass::ApplySettings( inResourceData ); LoadKeyValues( &m_pRoundIndicatorKVs, inResourceData, "RoundIndicatorPanel_kv" ); LoadKeyValues( &m_pRoundWinIndicatorRedKV, inResourceData, "RoundWinPanelRed_kv" ); LoadKeyValues( &m_pRoundWinIndicatorBlueKV, inResourceData, "RoundWinPanelBlue_kv" ); CreateRoundPanels( m_vecBlueRoundIndicators, "RoundIndicator", m_pRoundIndicatorKVs ); CreateRoundPanels( m_vecRedRoundIndicators, "RoundIndicator", m_pRoundIndicatorKVs ); CreateRoundPanels( m_vecBlueWinIndicators, "WinIndicatorBlue", m_pRoundWinIndicatorBlueKV ); CreateRoundPanels( m_vecRedWinIndicators, "WinIndicatorRed", m_pRoundWinIndicatorRedKV ); } //----------------------------------------------------------------------------- // Purpose: Ensure there are the correct number of image panels. If not, create // them and apply the passed-in settings //----------------------------------------------------------------------------- void CRoundCounterPanel::CreateRoundPanels( ImageVector& vecImages, const char* pszName, KeyValues* pKVSettings ) { int nMaxRounds = g_nMaxSupportedRounds; if ( vecImages.Count() != nMaxRounds ) { FOR_EACH_VEC( vecImages, i ) { vecImages[ i ]->MarkForDeletion(); } vecImages.Purge(); if ( nMaxRounds > 0 ) { while ( nMaxRounds-- ) { vecImages.AddToTail(new ImagePanel(this, pszName)); } } } if ( pKVSettings ) { FOR_EACH_VEC(vecImages, i) { vecImages[i]->ApplySettings( pKVSettings ); } } } extern ConVar tf_attack_defend_map; //----------------------------------------------------------------------------- // Purpose: Loop through and conditionally set visible some panels //----------------------------------------------------------------------------- void VisibleCondition( CRoundCounterPanel::ImageVector& vecImages, int iMax ) { bool bInStopWatch = tf_attack_defend_map.GetBool(); FOR_EACH_VEC( vecImages, i ) { vecImages[i]->SetVisible( i < iMax && !bInStopWatch ); } } //----------------------------------------------------------------------------- // Purpose: Position all of the round panels and resize the background blue/red //----------------------------------------------------------------------------- void CRoundCounterPanel::PerformLayout() { BaseClass::PerformLayout(); if ( !TFGameRules() || !ShouldUseMatchHUD() ) return; C_TFTeam* pTeams[ TF_TEAM_COUNT ]; pTeams[ TF_TEAM_RED ] = GetGlobalTFTeam( TF_TEAM_RED ); pTeams[ TF_TEAM_BLUE ] = GetGlobalTFTeam( TF_TEAM_BLUE ); if ( !pTeams[ TF_TEAM_RED ] || !pTeams[ TF_TEAM_BLUE ] ) return; // Layout the round indicators LayoutPanels( m_vecBlueRoundIndicators, EAlignment::ALIGN_WEST, (GetWide() / 2) - m_nIndicatorStartOffset, m_nIndicatorPanelStep ); VisibleCondition( m_vecBlueRoundIndicators, mp_winlimit.GetInt() ); LayoutPanels( m_vecRedRoundIndicators, EAlignment::ALIGN_EAST, (GetWide() / 2) + m_nIndicatorStartOffset, m_nIndicatorPanelStep ); VisibleCondition( m_vecRedRoundIndicators, mp_winlimit.GetInt() ); // Layout the win indicators LayoutPanels( m_vecBlueWinIndicators, EAlignment::ALIGN_WEST, (GetWide() / 2) - m_nIndicatorStartOffset, m_nIndicatorPanelStep ); VisibleCondition( m_vecBlueWinIndicators, Min( mp_winlimit.GetInt(), pTeams[ TF_TEAM_BLUE ]->m_iScore ) ); LayoutPanels( m_vecRedWinIndicators, EAlignment::ALIGN_EAST, (GetWide() / 2) + m_nIndicatorStartOffset, m_nIndicatorPanelStep ); VisibleCondition( m_vecRedWinIndicators, Min( mp_winlimit.GetInt(), pTeams[ TF_TEAM_RED ]->m_iScore ) ); } void CRoundCounterPanel::OnThink() { if ( m_bCountDirty ) { int nNumVisible = 0; FOR_EACH_VEC( m_vecBlueRoundIndicators, i ) { if ( m_vecBlueRoundIndicators[i]->IsVisible() ) ++nNumVisible; } if ( nNumVisible != mp_winlimit.GetInt() ) { InvalidateLayout(); m_bCountDirty = false; } } } void CRoundCounterPanel::FireGameEvent(IGameEvent * event ) { if ( FStrEq( event->GetName(), "winlimit_changed" ) ) // Resize if the win limit changes { m_bCountDirty = true; } else if ( FStrEq( event->GetName(), "winpanel_show_scores" ) // Conditionally hide the win markers || FStrEq( event->GetName(), "stop_watch_changed" ) // Match the timing of the win panel "Ding!" when the scores update || FStrEq( event->GetName(), "teamplay_round_start" ) ) // Make sure we're accurate when the round starts in case the hud event didnt happen { InvalidateLayout( true ); } } //----------------------------------------------------------------------------- // Purpose: Layout the round panels //----------------------------------------------------------------------------- void CRoundCounterPanel::LayoutPanels( ImageVector& vecImages, EAlignment eAlignment, int nStartPos, int nMaxWide ) { if ( !mp_winlimit.GetInt() ) return; FOR_EACH_VEC( vecImages, i ) { Panel* pPanel = vecImages[ i ]; const int nXStartPos = eAlignment == ALIGN_EAST ? nStartPos : nStartPos; const int nStep = ( nMaxWide / mp_winlimit.GetInt() ); const int nXOffset = nStep * i; // Step out the panels by the steph width int nXPos = eAlignment == ALIGN_EAST ? nXStartPos + nXOffset - ( pPanel->GetWide() / 2 ) + ( nStep / 2 ) : nXStartPos - nXOffset - ( pPanel->GetWide() / 2 ) - ( nStep / 2 ); pPanel->SetPos( nXPos, pPanel->GetYPos() ); } } DECLARE_HUDELEMENT( CTFHudMatchStatus ); //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CTFHudMatchStatus::CTFHudMatchStatus(const char *pElementName) : CHudElement(pElementName) , BaseClass(NULL, "HudMatchStatus") , m_pTimePanel( NULL ) , m_bUseMatchHUD( false ) , m_eMatchGroupSettings( k_nMatchGroup_Invalid ) { Panel *pParent = g_pClientMode->GetViewport(); SetParent(pParent); SetHiddenBits( HIDEHUD_MISCSTATUS ); m_pMatchStartModelPanel = new CModelPanel( this, "MatchDoors" ); m_pRoundCounter = new CRoundCounterPanel( this, "RoundCounter" ); m_pTimePanel = new CTFHudTimeStatus( this, "ObjectiveStatusTimePanel" ); m_pRoundSignModel = new CModelPanel( this, "RoundSignModel" ); m_pTeamStatus = new CTFTeamStatus( this, "TeamStatus" ); m_pBlueTeamPanel = new vgui::EditablePanel( this, "BlueTeamPanel" ); m_pPlayerListBlue = new vgui::SectionedListPanel( m_pBlueTeamPanel, "BluePlayerList" ); m_pBlueLeaderAvatarImage = new CAvatarImagePanel( m_pBlueTeamPanel, "BlueLeaderAvatar" ); m_pBlueLeaderAvatarBG = new EditablePanel( m_pBlueTeamPanel, "BlueLeaderAvatarBG" ); m_pBlueTeamImage = new ImagePanel( m_pBlueTeamPanel, "BlueTeamImage" ); m_pBlueTeamName = new CExLabel( m_pBlueTeamPanel, "BlueTeamLabel", "" ); m_pRedTeamPanel = new vgui::EditablePanel( this, "RedTeamPanel" ); m_pPlayerListRed = new vgui::SectionedListPanel( m_pRedTeamPanel, "RedPlayerList" ); m_pRedLeaderAvatarImage = new CAvatarImagePanel( m_pRedTeamPanel, "RedLeaderAvatar" ); m_pRedLeaderAvatarBG = new EditablePanel( m_pRedTeamPanel, "RedLeaderAvatarBG" ); m_pRedTeamImage = new ImagePanel( m_pRedTeamPanel, "RedTeamImage" ); m_pRedTeamName = new CExLabel( m_pRedTeamPanel, "RedTeamLabel", "" ); m_mapAvatarsToImageList.SetLessFunc( DefLessFunc( CSteamID ) ); m_mapAvatarsToImageList.RemoveAll(); ListenForGameEvent( "teamplay_round_start" ); ListenForGameEvent( "restart_timer_time" ); ListenForGameEvent( "show_match_summary" ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CTFHudMatchStatus::~CTFHudMatchStatus() { if ( NULL != m_pImageList ) { delete m_pImageList; m_pImageList = NULL; } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFHudMatchStatus::Reset() { SetPanelsVisible(); if ( m_pTimePanel ) { m_pTimePanel->Reset(); } if ( m_pTeamStatus ) { m_pTeamStatus->Reset(); } CHudElement::Reset(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFHudMatchStatus::SetPanelsVisible() { m_pRoundCounter->SetVisible( ShouldUseMatchHUD() ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFHudMatchStatus::ApplySchemeSettings(IScheme *pScheme) { BaseClass::ApplySchemeSettings(pScheme); KeyValues *pConditions = NULL; if ( ShouldUseMatchHUD() ) { pConditions = new KeyValues( "conditions" ); AddSubKeyNamed( pConditions, "if_match" ); const IMatchGroupDescription* pMatchDesc = GetMatchGroupDescription( GTFGCClientSystem()->GetLiveMatchGroup() ); if ( pMatchDesc ) { if ( pMatchDesc->m_params.m_pmm_match_group_size->GetInt() > 12 ) { AddSubKeyNamed( pConditions, "if_large" ); } } } // load control settings... LoadControlSettings( "resource/UI/HudMatchStatus.res", NULL, NULL, pConditions ); if ( pConditions ) { pConditions->deleteThis(); } if ( m_pImageList ) delete m_pImageList; m_pImageList = new ImageList( false ); m_mapAvatarsToImageList.RemoveAll(); m_pPlayerListBlue->SetImageList( m_pImageList, false ); m_pPlayerListRed->SetImageList( m_pImageList, false ); InitPlayerList( m_pPlayerListBlue, TF_TEAM_BLUE ); InitPlayerList( m_pPlayerListRed, TF_TEAM_RED ); m_hPlayerListFont = pScheme->GetFont( "Default", true ); UpdatePlayerList(); UpdateTeamInfo(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFHudMatchStatus::PerformLayout() { BaseClass::PerformLayout(); SetPanelsVisible(); } bool CTFHudMatchStatus::ShouldDraw( void ) { // Force to draw during match summary so the doors show up. This panel // will try to hide itself if you're dead, but we want to ignore that // behavior and force us to draw. if ( TFGameRules() && TFGameRules()->ShowMatchSummary() ) return true; if ( gViewPortInterface->GetActivePanel() ) return false; return CHudElement::ShouldDraw(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFHudMatchStatus::OnThink() { if ( !TFGameRules() ) return; bool bReload = false; bool bUseMatchHUD = ShouldUseMatchHUD(); if ( bUseMatchHUD != m_bUseMatchHUD ) { m_bUseMatchHUD = bUseMatchHUD; bReload = true; } EMatchGroup eCurrentGroup = TFGameRules()->GetCurrentMatchGroup(); if ( eCurrentGroup != m_eMatchGroupSettings ) { m_eMatchGroupSettings = eCurrentGroup; bReload = true; } if ( bReload ) { InvalidateLayout( false, true ); // The KOTH timers are their own hud element CTFHudKothTimeStatus *pKothHUD = GET_HUDELEMENT( CTFHudKothTimeStatus ); if ( pKothHUD ) { pKothHUD->InvalidateLayout( false, true ); } } // check for an active timer and turn the time panel on or off if we need to if ( m_pTimePanel ) { // Don't draw in freezecam, or when the game's not running C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer(); bool bDisplayTimer = !( pPlayer && pPlayer->GetObserverMode() == OBS_MODE_FREEZECAM ); if ( TeamplayRoundBasedRules()->IsInTournamentMode() && TeamplayRoundBasedRules()->IsInWaitingForPlayers() ) { bDisplayTimer = false; } if ( bDisplayTimer ) { // is the time panel still pointing at an active timer? int iCurrentTimer = m_pTimePanel->GetTimerIndex(); CTeamRoundTimer *pTimer = dynamic_cast< CTeamRoundTimer* >( ClientEntityList().GetEnt( iCurrentTimer ) ); if ( pTimer && !pTimer->IsDormant() && !pTimer->IsDisabled() && pTimer->ShowInHud() ) { // the current timer is fine, make sure the panel is visible bDisplayTimer = true; } else if ( ObjectiveResource() ) { // check for a different timer int iActiveTimer = ObjectiveResource()->GetTimerToShowInHUD(); pTimer = dynamic_cast< CTeamRoundTimer* >( ClientEntityList().GetEnt( iActiveTimer ) ); bDisplayTimer = ( iActiveTimer != 0 && pTimer && !pTimer->IsDormant() ); m_pTimePanel->SetTimerIndex( iActiveTimer ); } } if ( bDisplayTimer && !TFGameRules()->ShowMatchSummary() ) { if ( !TFGameRules()->IsInKothMode() ) { if ( !m_pTimePanel->IsVisible() ) { m_pTimePanel->SetVisible( true ); // If our spectator GUI is visible, invalidate its layout so that it moves the reinforcement label if ( g_pSpectatorGUI ) { g_pSpectatorGUI->InvalidateLayout(); } } } else { bool bVisible = TeamplayRoundBasedRules()->IsInWaitingForPlayers(); if ( m_pTimePanel->IsVisible() != bVisible ) { m_pTimePanel->SetVisible( bVisible ); // If our spectator GUI is visible, invalidate its layout so that it moves the reinforcement label if ( g_pSpectatorGUI ) { g_pSpectatorGUI->InvalidateLayout(); } } } } else { if ( m_pTimePanel->IsVisible() ) { m_pTimePanel->SetVisible( false ); } } } BaseClass::OnThink(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFHudMatchStatus::FireGameEvent( IGameEvent * event ) { if ( !ShouldUseMatchHUD() ) return; if ( FStrEq("teamplay_round_start", event->GetName() ) ) { // Drop the round sign right when the match starts on rounds > 1 if ( TFGameRules()->GetRoundsPlayed() > 0 ) { ShowRoundSign( TFGameRules()->GetRoundsPlayed() ); } } else if ( FStrEq( "restart_timer_time", event->GetName() ) ) { HandleCountdown( event->GetInt( "time" ) ); } else if ( FStrEq( "show_match_summary", event->GetName() ) ) { if ( m_pBlueTeamPanel ) { m_pBlueTeamPanel->SetVisible( false ); } if ( m_pRedTeamPanel ) { m_pRedTeamPanel->SetVisible( false ); } const IMatchGroupDescription* pMatchDesc = GetMatchGroupDescription( TFGameRules()->GetCurrentMatchGroup() ); bool bForceDoors = false; #ifdef STAGING_ONLY bForceDoors = tf_test_match_summary.GetBool(); #endif if ( bForceDoors || ( pMatchDesc && pMatchDesc->m_params.m_bShowPostRoundDoors ) ) { if ( TFGameRules() && TFGameRules()->MapHasMatchSummaryStage() && ( bForceDoors || pMatchDesc->m_params.m_bUseMatchSummaryStage ) ) { g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( this, "HudMatchStatus_ShowMatchWinDoors", false ); } else { g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( this, "HudMatchStatus_ShowMatchWinDoors_NoOpen", false ); } } } } void CTFHudMatchStatus::HandleCountdown( int nTime ) { // Update the timer SetDialogVariable( "countdown", nTime ); switch ( nTime ) { case 2: // Drop the round sign with 2 seconds to go on the 1st round if ( TFGameRules()->GetRoundsPlayed() == 0 ) { ShowRoundSign( TFGameRules()->GetRoundsPlayed() ); } break; case 10: if ( TFGameRules()->GetRoundsPlayed() == 0 ) { ShowMatchStartDoors(); } else { g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( this, "HudMatchStatus_ShowCountdown", false ); } break; } } #ifdef STAGING_ONLY ConVar tf_comp_door_skin_override( "tf_comp_door_skin_override", "-1", 0, "Skin override for the competitive doors. Set to -1 to not override calculated skin" ); ConVar tf_comp_door_bodygroup_override( "tf_comp_door_bodygroup_override", "-1", 0, "Bodygroup override for the competitive doors. Set to -1 to not override calculated skin" ); #endif //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFHudMatchStatus::ShowMatchStartDoors() { if ( TFGameRules()->GetCurrentMatchGroup() == k_nMatchGroup_Invalid ) return; const IMatchGroupDescription* pMatchDesc = GetMatchGroupDescription( TFGameRules()->GetCurrentMatchGroup() ); int nSkin = 0; int nSubModel = 0; if ( pMatchDesc->BGetRoundDoorParameters( nSkin, nSubModel ) ) { #ifdef STAGING_ONLY if ( tf_comp_door_skin_override.GetInt() != -1 ) { nSkin = tf_comp_door_skin_override.GetInt(); } if ( tf_comp_door_bodygroup_override.GetInt() != -1 ) { nSubModel = tf_comp_door_bodygroup_override.GetInt(); } #endif UpdatePlayerList(); UpdateTeamInfo(); if ( m_pMatchStartModelPanel->m_hModel == NULL ) { m_pMatchStartModelPanel->UpdateModel(); } m_pMatchStartModelPanel->SetBodyGroup( "logos", nSubModel ); m_pMatchStartModelPanel->UpdateModel(); m_pMatchStartModelPanel->SetSkin( nSkin ); g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( this, "HudMatchStatus_ShowMatchStartDoors", false ); // Hide the class selection panel. It sorts weird with the doors, and we dont have time to figure out why. gViewPortInterface->ShowPanel( PANEL_CLASS_RED, false ); gViewPortInterface->ShowPanel( PANEL_CLASS_BLUE, false ); C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer(); if ( pLocalPlayer ) { pLocalPlayer->EmitSound( pMatchDesc->m_params.m_pszMatchStartSound ); } } } //----------------------------------------------------------------------------- // Purpose: Show the round sign with the specified round number //----------------------------------------------------------------------------- void CTFHudMatchStatus::ShowRoundSign( int nRoundNumber ) { if ( TFGameRules()->GetCurrentMatchGroup() == k_nMatchGroup_Invalid ) return; if ( !m_pRoundSignModel || !m_pRoundSignModel->m_pModelInfo ) return; Assert( TFGameRules()->GetRoundsPlayed() >= 0 && TFGameRules()->GetRoundsPlayed() <= 6 ); int nSkin = 0; int nBodyGroup = 0; if ( GetMatchGroupDescription( TFGameRules()->GetCurrentMatchGroup() )->BGetRoundStartBannerParameters( nSkin, nBodyGroup ) ) { if ( m_pRoundSignModel->m_hModel == NULL ) { m_pRoundSignModel->UpdateModel(); } // Change the skin and bodygroup to be correct for the mode and round m_pRoundSignModel->SetBodyGroup( "logos", nBodyGroup ); m_pRoundSignModel->m_pModelInfo->m_nSkin = nSkin; // Make the model actually update with the new look m_pRoundSignModel->SetPanelDirty(); m_pRoundSignModel->UpdateModel(); // Play the sign drop anim g_pClientMode->GetViewportAnimationController()->StartAnimationSequence(this, "HudTournament_ShowRoundSign", false); } } //----------------------------------------------------------------------------- // Purpose: Used for sorting players //----------------------------------------------------------------------------- bool TFPlayerSortFunc( vgui::SectionedListPanel *list, int itemID1, int itemID2 ) { KeyValues *it1 = list->GetItemData( itemID1 ); KeyValues *it2 = list->GetItemData( itemID2 ); Assert( it1 && it2 ); // first compare score int v1 = it1->GetInt( "score" ); int v2 = it2->GetInt( "score" ); if ( v1 > v2 ) return true; else if ( v1 < v2 ) return false; // if score is the same, use player index to get deterministic sort int iPlayerIndex1 = it1->GetInt( "playerIndex" ); int iPlayerIndex2 = it2->GetInt( "playerIndex" ); return ( iPlayerIndex1 > iPlayerIndex2 ); } //----------------------------------------------------------------------------- // Purpose: Inits the player list in a list panel //----------------------------------------------------------------------------- void CTFHudMatchStatus::InitPlayerList( SectionedListPanel *pPlayerList, int nTeam ) { pPlayerList->SetVerticalScrollbar( false ); pPlayerList->RemoveAll(); pPlayerList->RemoveAllSections(); pPlayerList->AddSection( 0, "Players", TFPlayerSortFunc ); pPlayerList->SetSectionAlwaysVisible( 0, true ); pPlayerList->SetSectionDrawDividerBar( 0, false ); pPlayerList->SetBorder( NULL ); pPlayerList->SetMouseInputEnabled( false ); pPlayerList->SetClickable( false ); pPlayerList->AddColumnToSection( 0, "avatar", "", SectionedListPanel::COLUMN_IMAGE | SectionedListPanel::COLUMN_RIGHT, m_iAvatarWidth ); pPlayerList->AddColumnToSection( 0, "spacer", "", 0, m_iSpacerWidth ); // the player avatar is always a fixed size, so as we change resolutions we need to vary the size of the name column to adjust the total width of all the columns int nExtraSpace = pPlayerList->GetWide() - m_iAvatarWidth - m_iSpacerWidth - m_iNameWidth - ( 2 * SectionedListPanel::COLUMN_DATA_INDENT ); // the SectionedListPanel will indent the columns on either end by SectionedListPanel::COLUMN_DATA_INDENT pPlayerList->AddColumnToSection( 0, "name", "", 0, m_iNameWidth + nExtraSpace ); } //----------------------------------------------------------------------------- // Purpose: Updates the player list //----------------------------------------------------------------------------- void CTFHudMatchStatus::UpdatePlayerList() { m_pPlayerListRed->RemoveAll(); m_pPlayerListRed->ClearAllColorOverrideForCell(); m_pPlayerListBlue->RemoveAll(); m_pPlayerListBlue->ClearAllColorOverrideForCell(); if ( !g_TF_PR ) return; for ( int playerIndex = 1; playerIndex <= MAX_PLAYERS; playerIndex++ ) { if ( g_PR->IsConnected( playerIndex ) ) { SectionedListPanel *pPlayerList = NULL; int nTeam = g_PR->GetTeam( playerIndex ); switch ( nTeam ) { case TF_TEAM_BLUE: pPlayerList = m_pPlayerListBlue; break; case TF_TEAM_RED: pPlayerList = m_pPlayerListRed; break; } if ( null == pPlayerList ) continue; KeyValues *pKeyValues = new KeyValues( "data" ); pKeyValues->SetInt( "playerIndex", playerIndex ); pKeyValues->SetString( "name", g_TF_PR->GetPlayerName( playerIndex ) ); UpdatePlayerAvatar( playerIndex, pKeyValues ); int itemID = pPlayerList->AddItem( 0, pKeyValues ); pPlayerList->SetItemFgColor( itemID, g_PR->GetTeamColor( nTeam ) ); pPlayerList->SetItemBgColor( itemID, Color( 120, 120, 120, 80 ) ); pPlayerList->SetItemBgHorizFillInset( itemID, m_iHorizFillInset ); pPlayerList->SetItemFont( itemID, m_hPlayerListFont ); pKeyValues->deleteThis(); } } m_pPlayerListRed->SetSectionFgColor( 0, g_PR->GetTeamColor( TF_TEAM_RED ) ); m_pPlayerListBlue->SetSectionFgColor( 0, g_PR->GetTeamColor( TF_TEAM_BLUE ) ); } //----------------------------------------------------------------------------- // Purpose: Updates the player list //----------------------------------------------------------------------------- void CTFHudMatchStatus::UpdatePlayerAvatar( int playerIndex, KeyValues *kv ) { // Update their avatar if ( kv && steamapicontext->SteamFriends() && steamapicontext->SteamUtils() ) { player_info_t pi; if ( engine->GetPlayerInfo( playerIndex, &pi ) ) { if ( pi.friendsID ) { CSteamID steamIDForPlayer( pi.friendsID, 1, GetUniverse(), k_EAccountTypeIndividual ); // See if we already have that avatar in our list int iMapIndex = m_mapAvatarsToImageList.Find( steamIDForPlayer ); int iImageIndex; if ( iMapIndex == m_mapAvatarsToImageList.InvalidIndex() ) { CAvatarImage *pImage = new CAvatarImage(); pImage->SetAvatarSteamID( steamIDForPlayer ); pImage->SetAvatarSize( 32, 32 ); // Deliberately non scaling iImageIndex = m_pImageList->AddImage( pImage ); m_mapAvatarsToImageList.Insert( steamIDForPlayer, iImageIndex ); } else { iImageIndex = m_mapAvatarsToImageList[iMapIndex]; } kv->SetInt( "avatar", iImageIndex ); CAvatarImage *pAvIm = (CAvatarImage *)m_pImageList->GetImage( iImageIndex ); pAvIm->UpdateFriendStatus(); } } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFHudMatchStatus::UpdateTeamInfo() { for ( int teamIndex = TF_TEAM_RED; teamIndex <= TF_TEAM_BLUE; teamIndex++ ) { C_TFTeam *team = GetGlobalTFTeam( teamIndex ); if ( team ) { // choose dialog variables to set depending on team const char *pDialogVarTeamName = ""; vgui::EditablePanel *pPanel = NULL; switch ( teamIndex ) { case TF_TEAM_RED: pDialogVarTeamName = "redteamname"; pPanel = m_pRedTeamPanel; break; case TF_TEAM_BLUE: pDialogVarTeamName = "blueteamname"; pPanel = m_pBlueTeamPanel; break; default: Assert( false ); break; } // set the team name if ( pPanel ) { pPanel->SetDialogVariable( pDialogVarTeamName, team->Get_Localized_Name() ); } } } bool bShowAvatars = g_TF_PR && g_TF_PR->HasPremadeParties(); if ( bShowAvatars ) { m_pRedLeaderAvatarImage->SetPlayer( GetSteamIDForPlayerIndex( g_TF_PR->GetPartyLeaderRedTeamIndex() ), k_EAvatarSize64x64 ); m_pRedLeaderAvatarImage->SetShouldDrawFriendIcon( false ); m_pBlueLeaderAvatarImage->SetPlayer( GetSteamIDForPlayerIndex( g_TF_PR->GetPartyLeaderBlueTeamIndex() ), k_EAvatarSize64x64 ); m_pBlueLeaderAvatarImage->SetShouldDrawFriendIcon( false ); } m_pRedLeaderAvatarImage->SetVisible( bShowAvatars ); m_pRedLeaderAvatarBG->SetVisible( bShowAvatars ); m_pRedTeamName->SetVisible( bShowAvatars ); m_pRedTeamImage->SetVisible( !bShowAvatars ); m_pBlueLeaderAvatarImage->SetVisible( bShowAvatars ); m_pBlueLeaderAvatarBG->SetVisible( bShowAvatars ); m_pBlueTeamName->SetVisible( bShowAvatars ); m_pBlueTeamImage->SetVisible( !bShowAvatars ); }