You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
580 lines
18 KiB
580 lines
18 KiB
//========= 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); |
|
}
|
|
|