diff options
Diffstat (limited to 'game/client/game_controls/ClientScoreBoardDialog.cpp')
| -rw-r--r-- | game/client/game_controls/ClientScoreBoardDialog.cpp | 580 |
1 files changed, 580 insertions, 0 deletions
diff --git a/game/client/game_controls/ClientScoreBoardDialog.cpp b/game/client/game_controls/ClientScoreBoardDialog.cpp new file mode 100644 index 0000000..29865b0 --- /dev/null +++ b/game/client/game_controls/ClientScoreBoardDialog.cpp @@ -0,0 +1,580 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//===========================================================================// + +#include "cbase.h" +#include <stdio.h> + +#include <cdll_client_int.h> +#include <cdll_util.h> +#include <globalvars_base.h> +#include <igameresources.h> +#include "IGameUIFuncs.h" // for key bindings +#include "inputsystem/iinputsystem.h" +#include "clientscoreboarddialog.h" +#include <voice_status.h> + +#include <vgui/IScheme.h> +#include <vgui/ILocalize.h> +#include <vgui/ISurface.h> +#include <vgui/IVGui.h> +#include <vstdlib/IKeyValuesSystem.h> + +#include <KeyValues.h> +#include <vgui_controls/ImageList.h> +#include <vgui_controls/Label.h> +#include <vgui_controls/SectionedListPanel.h> + +#include <game/client/iviewport.h> +#include <igameresources.h> + +#include "vgui_avatarimage.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +using namespace vgui; + +bool AvatarIndexLessFunc( const int &lhs, const int &rhs ) +{ + return lhs < rhs; +} + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +CClientScoreBoardDialog::CClientScoreBoardDialog(IViewPort *pViewPort) : EditablePanel( NULL, PANEL_SCOREBOARD ) +{ + m_iPlayerIndexSymbol = KeyValuesSystem()->GetSymbolForString("playerIndex"); + m_nCloseKey = BUTTON_CODE_INVALID; + + //memset(s_VoiceImage, 0x0, sizeof( s_VoiceImage )); + TrackerImage = 0; + m_pViewPort = pViewPort; + + // initialize dialog + SetProportional(true); + SetKeyBoardInputEnabled(false); + SetMouseInputEnabled(false); + + // set the scheme before any child control is created + SetScheme("ClientScheme"); + + m_pPlayerList = new SectionedListPanel(this, "PlayerList"); + m_pPlayerList->SetVerticalScrollbar(false); + + LoadControlSettings("Resource/UI/ScoreBoard.res"); + m_iDesiredHeight = GetTall(); + m_pPlayerList->SetVisible( false ); // hide this until we load the images in applyschemesettings + + m_HLTVSpectators = 0; + m_ReplaySpectators = 0; + + // update scoreboard instantly if on of these events occure + ListenForGameEvent( "hltv_status" ); + ListenForGameEvent( "server_spawn" ); + + m_pImageList = NULL; + + m_mapAvatarsToImageList.SetLessFunc( DefLessFunc( CSteamID ) ); + m_mapAvatarsToImageList.RemoveAll(); +} + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +CClientScoreBoardDialog::~CClientScoreBoardDialog() +{ + if ( NULL != m_pImageList ) + { + delete m_pImageList; + m_pImageList = NULL; + } +} + +//----------------------------------------------------------------------------- +// Call every frame +//----------------------------------------------------------------------------- +void CClientScoreBoardDialog::OnThink() +{ + BaseClass::OnThink(); + + // NOTE: this is necessary because of the way input works. + // If a key down message is sent to vgui, then it will get the key up message + // Sometimes the scoreboard is activated by other vgui menus, + // sometimes by console commands. In the case where it's activated by + // other vgui menus, we lose the key up message because this panel + // doesn't accept keyboard input. It *can't* accept keyboard input + // because another feature of the dialog is that if it's triggered + // from within the game, you should be able to still run around while + // the scoreboard is up. That feature is impossible if this panel accepts input. + // because if a vgui panel is up that accepts input, it prevents the engine from + // receiving that input. So, I'm stuck with a polling solution. + // + // Close key is set to non-invalid when something other than a keybind + // brings the scoreboard up, and it's set to invalid as soon as the + // dialog becomes hidden. + if ( m_nCloseKey != BUTTON_CODE_INVALID ) + { + if ( !g_pInputSystem->IsButtonDown( m_nCloseKey ) ) + { + m_nCloseKey = BUTTON_CODE_INVALID; + gViewPortInterface->ShowPanel( PANEL_SCOREBOARD, false ); + GetClientVoiceMgr()->StopSquelchMode(); + } + } +} + +//----------------------------------------------------------------------------- +// Called by vgui panels that activate the client scoreboard +//----------------------------------------------------------------------------- +void CClientScoreBoardDialog::OnPollHideCode( int code ) +{ + m_nCloseKey = (ButtonCode_t)code; +} + +//----------------------------------------------------------------------------- +// Purpose: clears everything in the scoreboard and all it's state +//----------------------------------------------------------------------------- +void CClientScoreBoardDialog::Reset() +{ + // clear + m_pPlayerList->DeleteAllItems(); + m_pPlayerList->RemoveAllSections(); + + m_iSectionId = 0; + m_fNextUpdateTime = 0; + // add all the sections + InitScoreboardSections(); +} + +//----------------------------------------------------------------------------- +// Purpose: adds all the team sections to the scoreboard +//----------------------------------------------------------------------------- +void CClientScoreBoardDialog::InitScoreboardSections() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: sets up screen +//----------------------------------------------------------------------------- +void CClientScoreBoardDialog::ApplySchemeSettings( IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + if ( m_pImageList ) + delete m_pImageList; + m_pImageList = new ImageList( false ); + + m_mapAvatarsToImageList.RemoveAll(); + + PostApplySchemeSettings( pScheme ); +} + +//----------------------------------------------------------------------------- +// Purpose: Does dialog-specific customization after applying scheme settings. +//----------------------------------------------------------------------------- +void CClientScoreBoardDialog::PostApplySchemeSettings( vgui::IScheme *pScheme ) +{ + // resize the images to our resolution + for (int i = 0; i < m_pImageList->GetImageCount(); i++ ) + { + int wide, tall; + m_pImageList->GetImage(i)->GetSize(wide, tall); + m_pImageList->GetImage(i)->SetSize(scheme()->GetProportionalScaledValueEx( GetScheme(),wide), scheme()->GetProportionalScaledValueEx( GetScheme(),tall)); + } + + m_pPlayerList->SetImageList( m_pImageList, false ); + m_pPlayerList->SetVisible( true ); + + // light up scoreboard a bit + SetBgColor( Color( 0,0,0,0) ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CClientScoreBoardDialog::ShowPanel(bool bShow) +{ + // Catch the case where we call ShowPanel before ApplySchemeSettings, eg when + // going from windowed <-> fullscreen + if ( m_pImageList == NULL ) + { + InvalidateLayout( true, true ); + } + + if ( !bShow ) + { + m_nCloseKey = BUTTON_CODE_INVALID; + } + + if ( BaseClass::IsVisible() == bShow ) + return; + + if ( bShow ) + { + Reset(); + Update(); + SetVisible( true ); + MoveToFront(); + } + else + { + BaseClass::SetVisible( false ); + SetMouseInputEnabled( false ); + SetKeyBoardInputEnabled( false ); + } +} + +void CClientScoreBoardDialog::FireGameEvent( IGameEvent *event ) +{ + const char * type = event->GetName(); + + if ( Q_strcmp(type, "hltv_status") == 0 ) + { + // spectators = clients - proxies + m_HLTVSpectators = event->GetInt( "clients" ); + m_HLTVSpectators -= event->GetInt( "proxies" ); + } + else if ( Q_strcmp(type, "server_spawn") == 0 ) + { + // We'll post the message ourselves instead of using SetControlString() + // so we don't try to translate the hostname. + const char *hostname = event->GetString( "hostname" ); + Panel *control = FindChildByName( "ServerName" ); + if ( control ) + { + PostMessage( control, new KeyValues( "SetText", "text", hostname ) ); + control->MoveToFront(); + } + } + + if( IsVisible() ) + Update(); + +} + +bool CClientScoreBoardDialog::NeedsUpdate( void ) +{ + return (m_fNextUpdateTime < gpGlobals->curtime); +} + +//----------------------------------------------------------------------------- +// Purpose: Recalculate the internal scoreboard data +//----------------------------------------------------------------------------- +void CClientScoreBoardDialog::Update( void ) +{ + // Set the title + + // Reset(); + m_pPlayerList->DeleteAllItems(); + + FillScoreBoard(); + + // grow the scoreboard to fit all the players + int wide, tall; + m_pPlayerList->GetContentSize(wide, tall); + tall += GetAdditionalHeight(); + wide = GetWide(); + if (m_iDesiredHeight < tall) + { + SetSize(wide, tall); + m_pPlayerList->SetSize(wide, tall); + } + else + { + SetSize(wide, m_iDesiredHeight); + m_pPlayerList->SetSize(wide, m_iDesiredHeight); + } + + MoveToCenterOfScreen(); + + // update every second + m_fNextUpdateTime = gpGlobals->curtime + 1.0f; +} + +//----------------------------------------------------------------------------- +// Purpose: Sort all the teams +//----------------------------------------------------------------------------- +void CClientScoreBoardDialog::UpdateTeamInfo() +{ +// TODO: work out a sorting algorithm for team display for TF2 +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CClientScoreBoardDialog::UpdatePlayerInfo() +{ + m_iSectionId = 0; // 0'th row is a header + int selectedRow = -1; + + // walk all the players and make sure they're in the scoreboard + for ( int i = 1; i <= gpGlobals->maxClients; ++i ) + { + IGameResources *gr = GameResources(); + + if ( gr && gr->IsConnected( i ) ) + { + // add the player to the list + KeyValues *playerData = new KeyValues("data"); + GetPlayerScoreInfo( i, playerData ); + UpdatePlayerAvatar( i, playerData ); + + const char *oldName = playerData->GetString("name",""); + char newName[MAX_PLAYER_NAME_LENGTH]; + + UTIL_MakeSafeName( oldName, newName, MAX_PLAYER_NAME_LENGTH ); + + playerData->SetString("name", newName); + + int itemID = FindItemIDForPlayerIndex( i ); + int sectionID = gr->GetTeam( i ); + + if ( gr->IsLocalPlayer( i ) ) + { + selectedRow = itemID; + } + if (itemID == -1) + { + // add a new row + itemID = m_pPlayerList->AddItem( sectionID, playerData ); + } + else + { + // modify the current row + m_pPlayerList->ModifyItem( itemID, sectionID, playerData ); + } + + // set the row color based on the players team + m_pPlayerList->SetItemFgColor( itemID, gr->GetTeamColor( sectionID ) ); + + playerData->deleteThis(); + } + else + { + // remove the player + int itemID = FindItemIDForPlayerIndex( i ); + if (itemID != -1) + { + m_pPlayerList->RemoveItem(itemID); + } + } + } + + if ( selectedRow != -1 ) + { + m_pPlayerList->SetSelectedItem(selectedRow); + } +} + +//----------------------------------------------------------------------------- +// Purpose: adds the top header of the scoreboars +//----------------------------------------------------------------------------- +void CClientScoreBoardDialog::AddHeader() +{ + // add the top header + m_pPlayerList->AddSection(m_iSectionId, ""); + m_pPlayerList->SetSectionAlwaysVisible(m_iSectionId); + m_pPlayerList->AddColumnToSection(m_iSectionId, "name", "#PlayerName", 0, scheme()->GetProportionalScaledValueEx( GetScheme(),NAME_WIDTH) ); + m_pPlayerList->AddColumnToSection(m_iSectionId, "frags", "#PlayerScore", 0, scheme()->GetProportionalScaledValueEx( GetScheme(),SCORE_WIDTH) ); + m_pPlayerList->AddColumnToSection(m_iSectionId, "deaths", "#PlayerDeath", 0, scheme()->GetProportionalScaledValueEx( GetScheme(),DEATH_WIDTH) ); + m_pPlayerList->AddColumnToSection(m_iSectionId, "ping", "#PlayerPing", 0, scheme()->GetProportionalScaledValueEx( GetScheme(),PING_WIDTH) ); +} + +//----------------------------------------------------------------------------- +// Purpose: Adds a new section to the scoreboard (i.e the team header) +//----------------------------------------------------------------------------- +void CClientScoreBoardDialog::AddSection(int teamType, int teamNumber) +{ + if ( teamType == TYPE_TEAM ) + { + IGameResources *gr = GameResources(); + + if ( !gr ) + return; + + // setup the team name + wchar_t *teamName = g_pVGuiLocalize->Find( gr->GetTeamName(teamNumber) ); + wchar_t name[64]; + wchar_t string1[1024]; + + if (!teamName) + { + g_pVGuiLocalize->ConvertANSIToUnicode(gr->GetTeamName(teamNumber), name, sizeof(name)); + teamName = name; + } + + g_pVGuiLocalize->ConstructString_safe( string1, g_pVGuiLocalize->Find("#Player"), 2, teamName ); + + m_pPlayerList->AddSection(m_iSectionId, "", StaticPlayerSortFunc); + + // Avatars are always displayed at 32x32 regardless of resolution + if ( ShowAvatars() ) + { + m_pPlayerList->AddColumnToSection( m_iSectionId, "avatar", "", SectionedListPanel::COLUMN_IMAGE | SectionedListPanel::COLUMN_RIGHT, m_iAvatarWidth ); + } + + m_pPlayerList->AddColumnToSection(m_iSectionId, "name", string1, 0, scheme()->GetProportionalScaledValueEx( GetScheme(),NAME_WIDTH) - m_iAvatarWidth ); + m_pPlayerList->AddColumnToSection(m_iSectionId, "frags", "", 0, scheme()->GetProportionalScaledValueEx( GetScheme(),SCORE_WIDTH) ); + m_pPlayerList->AddColumnToSection(m_iSectionId, "deaths", "", 0, scheme()->GetProportionalScaledValueEx( GetScheme(),DEATH_WIDTH) ); + m_pPlayerList->AddColumnToSection(m_iSectionId, "ping", "", 0, scheme()->GetProportionalScaledValueEx( GetScheme(),PING_WIDTH) ); + } + else if ( teamType == TYPE_SPECTATORS ) + { + m_pPlayerList->AddSection(m_iSectionId, ""); + + // Avatars are always displayed at 32x32 regardless of resolution + if ( ShowAvatars() ) + { + m_pPlayerList->AddColumnToSection( m_iSectionId, "avatar", "", SectionedListPanel::COLUMN_IMAGE | SectionedListPanel::COLUMN_RIGHT, m_iAvatarWidth ); + } + m_pPlayerList->AddColumnToSection(m_iSectionId, "name", "#Spectators", 0, scheme()->GetProportionalScaledValueEx( GetScheme(),NAME_WIDTH) - m_iAvatarWidth ); + m_pPlayerList->AddColumnToSection(m_iSectionId, "frags", "", 0, scheme()->GetProportionalScaledValueEx( GetScheme(),SCORE_WIDTH) ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Used for sorting players +//----------------------------------------------------------------------------- +bool CClientScoreBoardDialog::StaticPlayerSortFunc(vgui::SectionedListPanel *list, int itemID1, int itemID2) +{ + KeyValues *it1 = list->GetItemData(itemID1); + KeyValues *it2 = list->GetItemData(itemID2); + Assert(it1 && it2); + + // first compare frags + int v1 = it1->GetInt("frags"); + int v2 = it2->GetInt("frags"); + if (v1 > v2) + return true; + else if (v1 < v2) + return false; + + // next compare deaths + v1 = it1->GetInt("deaths"); + v2 = it2->GetInt("deaths"); + if (v1 > v2) + return false; + else if (v1 < v2) + return true; + + // the same, so compare itemID's (as a sentinel value to get deterministic sorts) + return itemID1 < itemID2; +} + +//----------------------------------------------------------------------------- +// Purpose: Adds a new row to the scoreboard, from the playerinfo structure +//----------------------------------------------------------------------------- +bool CClientScoreBoardDialog::GetPlayerScoreInfo(int playerIndex, KeyValues *kv) +{ + IGameResources *gr = GameResources(); + + if (!gr ) + return false; + + kv->SetInt("deaths", gr->GetDeaths( playerIndex ) ); + kv->SetInt("frags", gr->GetFrags( playerIndex ) ); + kv->SetInt("ping", gr->GetPing( playerIndex ) ) ; + kv->SetString("name", gr->GetPlayerName( playerIndex ) ); + kv->SetInt("playerIndex", playerIndex); + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CClientScoreBoardDialog::UpdatePlayerAvatar( int playerIndex, KeyValues *kv ) +{ + // Update their avatar + if ( kv && ShowAvatars() && 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: reload the player list on the scoreboard +//----------------------------------------------------------------------------- +void CClientScoreBoardDialog::FillScoreBoard() +{ + // update totals information + UpdateTeamInfo(); + + // update player info + UpdatePlayerInfo(); +} + +//----------------------------------------------------------------------------- +// Purpose: searches for the player in the scoreboard +//----------------------------------------------------------------------------- +int CClientScoreBoardDialog::FindItemIDForPlayerIndex(int playerIndex) +{ + for (int i = 0; i <= m_pPlayerList->GetHighestItemID(); i++) + { + if (m_pPlayerList->IsItemIDValid(i)) + { + KeyValues *kv = m_pPlayerList->GetItemData(i); + kv = kv->FindKey(m_iPlayerIndexSymbol); + if (kv && kv->GetInt() == playerIndex) + return i; + } + } + return -1; +} + +//----------------------------------------------------------------------------- +// Purpose: Sets the text of a control by name +//----------------------------------------------------------------------------- +void CClientScoreBoardDialog::MoveLabelToFront(const char *textEntryName) +{ + Label *entry = dynamic_cast<Label *>(FindChildByName(textEntryName)); + if (entry) + { + entry->MoveToFront(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Center the dialog on the screen. (vgui has this method on +// Frame, but we're an EditablePanel, need to roll our own.) +//----------------------------------------------------------------------------- +void CClientScoreBoardDialog::MoveToCenterOfScreen() +{ + int wx, wy, ww, wt; + surface()->GetWorkspaceBounds(wx, wy, ww, wt); + SetPos((ww - GetWide()) / 2, (wt - GetTall()) / 2); +} |