diff options
Diffstat (limited to 'serverbrowser')
44 files changed, 9466 insertions, 0 deletions
diff --git a/serverbrowser/BaseGamesPage.cpp b/serverbrowser/BaseGamesPage.cpp new file mode 100644 index 0000000..6cef882 --- /dev/null +++ b/serverbrowser/BaseGamesPage.cpp @@ -0,0 +1,2323 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include "pch_serverbrowser.h" + +#if defined( _X360 ) +#include "xbox/xbox_win32stubs.h" +#endif + +using namespace vgui; + +#define FILTER_ALLSERVERS 0 +#define FILTER_SECURESERVERSONLY 1 +#define FILTER_INSECURESERVERSONLY 2 + +#define UNIVERSE_OFFICIAL 0 +#define UNIVERSE_CUSTOMGAMES 1 +#define QUICKLIST_FILTER_MIN_PING 0 + +#define MAX_MAP_NAME 128 +const char *COM_GetModDirectory(); + +#undef wcscat + +ConVar sb_mod_suggested_maxplayers( "sb_mod_suggested_maxplayers", "0", FCVAR_HIDDEN ); +ConVar sb_filter_incompatible_versions( "sb_filter_incompatible_versions", + #ifdef STAGING_ONLY + "0", + #else + "1", + #endif + 0, "Hides servers running incompatible versions from the server browser. (Internet tab only.)" ); + +bool GameSupportsReplay() +{ + extern IEngineReplay *g_pEngineReplay; + return g_pEngineReplay && g_pEngineReplay->IsSupportedModAndPlatform(); +} + +#ifdef STAGING_ONLY + ConVar sb_fake_app_id( "sb_fake_app_id", "0", 0, "If nonzero, then server browser requests will use this App ID instead" ); +#endif + +//-------------------------------------------------------------------------------------------------------- +bool IsReplayServer( gameserveritem_t &server ) +{ + bool bReplay = false; + + if ( GameSupportsReplay() ) + { + if ( server.m_szGameTags && server.m_szGameTags[0] ) + { + CUtlVector<char*> TagList; + V_SplitString( server.m_szGameTags, ",", TagList ); + for ( int i = 0; i < TagList.Count(); i++ ) + { + if ( Q_stricmp( TagList[i], "replays" ) == 0 ) + { + bReplay = true; + } + } + } + } + + return bReplay; +} + +//-------------------------------------------------------------------------------------------------------- +inline char *CloneString( const char *str ) +{ + char *cloneStr = new char [ strlen(str)+1 ]; + strcpy( cloneStr, str ); + return cloneStr; +} + +const char *COM_GetModDirectory() +{ + static char modDir[MAX_PATH]; + if ( Q_strlen( modDir ) == 0 ) + { + const char *gamedir = CommandLine()->ParmValue("-game", CommandLine()->ParmValue( "-defaultgamedir", "hl2" ) ); + Q_strncpy( modDir, gamedir, sizeof(modDir) ); + if ( strchr( modDir, '/' ) || strchr( modDir, '\\' ) ) + { + Q_StripLastDir( modDir, sizeof(modDir) ); + int dirlen = Q_strlen( modDir ); + Q_strncpy( modDir, gamedir + dirlen, sizeof(modDir) - dirlen ); + } + } + + return modDir; +} + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +CGameListPanel::CGameListPanel( CBaseGamesPage *pOuter, const char *pName ) : + BaseClass( pOuter, pName ) +{ + m_pOuter = pOuter; +} + +//----------------------------------------------------------------------------- +// Purpose: Forward KEY_ENTER to the CBaseGamesPage. +//----------------------------------------------------------------------------- +void CGameListPanel::OnKeyCodePressed(vgui::KeyCode code) +{ + // Let the outer class handle it. + if ( code == KEY_ENTER && m_pOuter->OnGameListEnterPressed() ) + return; + + BaseClass::OnKeyCodePressed( code ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +CBaseGamesPage::CBaseGamesPage( vgui::Panel *parent, const char *name, EPageType eType, const char *pCustomResFilename) + : PropertyPage(parent, name), + m_CallbackFavoritesMsg( this, &CBaseGamesPage::OnFavoritesMsg ), + m_hRequest( NULL ), + m_pCustomResFilename( pCustomResFilename ) +{ + SetSize( 624, 278 ); + m_szGameFilter[0] = 0; + m_szMapFilter[0] = 0; + m_iMaxPlayerFilter = 0; + m_iPingFilter = 0; + m_iServerRefreshCount = 0; + m_bFilterNoFullServers = false; + m_bFilterNoEmptyServers = false; + m_bFilterNoPasswordedServers = false; + m_iSecureFilter = FILTER_ALLSERVERS; + m_hFont = NULL; + m_eMatchMakingType = eType; + m_bFilterReplayServers = false; + SetDefLessFunc( m_mapServers ); + SetDefLessFunc( m_mapServerIP ); + SetDefLessFunc( m_mapGamesFilterItem ); + + // Not always loaded + m_pWorkshopFilter = NULL; + + bool bRunningTF2 = GameSupportsReplay(); + + // get the 'all' text + wchar_t *all = g_pVGuiLocalize->Find("ServerBrowser_All"); + Q_UnicodeToUTF8(all, m_szComboAllText, sizeof(m_szComboAllText)); + + // Init UI + m_pConnect = new Button(this, "ConnectButton", "#ServerBrowser_Connect"); + m_pConnect->SetEnabled(false); + m_pRefreshAll = new Button(this, "RefreshButton", "#ServerBrowser_Refresh"); + m_pRefreshQuick = new Button(this, "RefreshQuickButton", "#ServerBrowser_RefreshQuick"); + m_pAddServer = new Button(this, "AddServerButton", "#ServerBrowser_AddServer"); + m_pAddCurrentServer = new Button(this, "AddCurrentServerButton", "#ServerBrowser_AddCurrentServer"); + m_pGameList = new CGameListPanel(this, "gamelist"); + m_pGameList->SetAllowUserModificationOfColumns(true); + + m_pQuickList = new PanelListPanel(this, "quicklist"); + m_pQuickList->SetFirstColumnWidth( 0 ); + + m_pAddToFavoritesButton = new vgui::Button( this, "AddToFavoritesButton", "" ); + m_pAddToFavoritesButton->SetEnabled( false ); + m_pAddToFavoritesButton->SetVisible( false ); + + // Increment this number if columns are added / removed or some other change is done that requires + // tossing out old saved user configs. + m_pGameList->m_nUserConfigFileVersion = 2; + + // Add the column headers + m_pGameList->AddColumnHeader( k_nColumn_Password, "Password", "#ServerBrowser_Password", 16, ListPanel::COLUMN_FIXEDSIZE | ListPanel::COLUMN_IMAGE); + m_pGameList->AddColumnHeader( k_nColumn_Secure, "Secure", "#ServerBrowser_Secure", 16, ListPanel::COLUMN_FIXEDSIZE | ListPanel::COLUMN_IMAGE); + + int nReplayWidth = 16; + if ( !bRunningTF2 ) + { + nReplayWidth = 0; + } + + m_pGameList->AddColumnHeader( k_nColumn_Replay, "Replay", "#ServerBrowser_Replay", nReplayWidth, ListPanel::COLUMN_FIXEDSIZE | ListPanel::COLUMN_IMAGE); + m_pGameList->AddColumnHeader( k_nColumn_Name, "Name", "#ServerBrowser_Servers", 50, ListPanel::COLUMN_RESIZEWITHWINDOW | ListPanel::COLUMN_UNHIDABLE); + m_pGameList->AddColumnHeader( k_nColumn_IPAddr, "IPAddr", "#ServerBrowser_IPAddress", 64, ListPanel::COLUMN_HIDDEN); + m_pGameList->AddColumnHeader( k_nColumn_GameDesc, "GameDesc", "#ServerBrowser_Game", 112, + 112, // minwidth + 300, // maxwidth + 0 // flags + ); + m_pGameList->AddColumnHeader( k_nColumn_Players, "Players", "#ServerBrowser_Players", 55, ListPanel::COLUMN_FIXEDSIZE); + m_pGameList->AddColumnHeader( k_nColumn_Bots, "Bots", "#ServerBrowser_Bots", 40, ListPanel::COLUMN_FIXEDSIZE); + m_pGameList->AddColumnHeader( k_nColumn_Map, "Map", "#ServerBrowser_Map", 90, + 90, // minwidth + 300, // maxwidth + 0 // flags + ); + m_pGameList->AddColumnHeader( k_nColumn_Ping, "Ping", "#ServerBrowser_Latency", 55, ListPanel::COLUMN_FIXEDSIZE); + + m_pGameList->SetColumnHeaderTooltip( k_nColumn_Password, "#ServerBrowser_PasswordColumn_Tooltip"); + m_pGameList->SetColumnHeaderTooltip( k_nColumn_Bots, "#ServerBrowser_BotColumn_Tooltip"); + m_pGameList->SetColumnHeaderTooltip( k_nColumn_Secure, "#ServerBrowser_SecureColumn_Tooltip"); + + if ( bRunningTF2 ) + { + m_pGameList->SetColumnHeaderTooltip( k_nColumn_Replay, "#ServerBrowser_ReplayColumn_Tooltip"); + } + + // setup fast sort functions + m_pGameList->SetSortFunc( k_nColumn_Password, PasswordCompare); + m_pGameList->SetSortFunc( k_nColumn_Bots, BotsCompare); + m_pGameList->SetSortFunc( k_nColumn_Secure, SecureCompare); + + if ( bRunningTF2 ) + { + m_pGameList->SetSortFunc( k_nColumn_Replay, ReplayCompare); + } + + m_pGameList->SetSortFunc( k_nColumn_Name, ServerNameCompare); + m_pGameList->SetSortFunc( k_nColumn_IPAddr, IPAddressCompare); + m_pGameList->SetSortFunc( k_nColumn_GameDesc, GameCompare); + m_pGameList->SetSortFunc( k_nColumn_Players, PlayersCompare); + m_pGameList->SetSortFunc( k_nColumn_Map, MapCompare); + m_pGameList->SetSortFunc( k_nColumn_Ping, PingCompare); + + // Sort by ping time by default + m_pGameList->SetSortColumn( k_nColumn_Ping ); + + CreateFilters(); + LoadFilterSettings(); + + m_bAutoSelectFirstItemInGameList = false; + + // In TF2, fill out the max player count so that we sort all >24 player servers below the rest. + if ( bRunningTF2 ) + { + sb_mod_suggested_maxplayers.SetValue( 24 ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +CBaseGamesPage::~CBaseGamesPage() +{ + if ( m_hRequest ) + { + steamapicontext->SteamMatchmakingServers()->ReleaseRequest( m_hRequest ); + m_hRequest = NULL; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CBaseGamesPage::GetInvalidServerListID() +{ + return m_pGameList->InvalidItemID(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseGamesPage::PerformLayout() +{ + BaseClass::PerformLayout(); + + if ( GetSelectedServerID() == -1 ) + { + m_pConnect->SetEnabled(false); + } + else + { + m_pConnect->SetEnabled(true); + } + + + if (SupportsItem(IGameList::GETNEWLIST)) + { + m_pRefreshQuick->SetVisible(true); + m_pRefreshAll->SetText("#ServerBrowser_RefreshAll"); + } + else + { + m_pRefreshQuick->SetVisible(false); + m_pRefreshAll->SetText("#ServerBrowser_Refresh"); + } + + if ( SupportsItem(IGameList::ADDSERVER) ) + { +// m_pFilterString->SetWide( 90 ); // shrink the filter label to fix the add current server button + m_pAddServer->SetVisible(true); + } + else + { + m_pAddServer->SetVisible(false); + } + + if ( SupportsItem(IGameList::ADDCURRENTSERVER) ) + { + m_pAddCurrentServer->SetVisible(true); + } + else + { + m_pAddCurrentServer->SetVisible(false); + } + + if ( IsRefreshing() ) + { + m_pRefreshAll->SetText( "#ServerBrowser_StopRefreshingList" ); + } + + if (m_pGameList->GetItemCount() > 0) + { + m_pRefreshQuick->SetEnabled(true); + } + else + { + m_pRefreshQuick->SetEnabled(false); + } + + if ( !steamapicontext->SteamMatchmakingServers() || !steamapicontext->SteamMatchmaking() ) + { + m_pAddCurrentServer->SetVisible( false ); + m_pRefreshQuick->SetEnabled( false ); + m_pAddServer->SetEnabled( false ); + m_pConnect->SetEnabled( false ); + m_pRefreshAll->SetEnabled( false ); + m_pAddToFavoritesButton->SetEnabled( false ); + m_pGameList->SetEmptyListText( "#ServerBrowser_SteamRunning" ); + } + + Repaint(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseGamesPage::ApplySchemeSettings(IScheme *pScheme) +{ + BaseClass::ApplySchemeSettings(pScheme); + + // load the password icon + ImageList *imageList = new ImageList(false); + m_nImageIndexPassword = imageList->AddImage(scheme()->GetImage("servers/icon_password", false)); + //imageList->AddImage(scheme()->GetImage("servers/icon_bots", false)); + m_nImageIndexSecure = imageList->AddImage(scheme()->GetImage("servers/icon_robotron", false)); + m_nImageIndexSecureVacBanned = imageList->AddImage(scheme()->GetImage("servers/icon_secure_deny", false)); + m_nImageIndexReplay = imageList->AddImage(scheme()->GetImage("servers/icon_replay", false)); + int passwordColumnImage = imageList->AddImage(scheme()->GetImage("servers/icon_password_column", false)); + //int botColumnImage = imageList->AddImage(scheme()->GetImage("servers/icon_bots_column", false)); + int secureColumnImage = imageList->AddImage(scheme()->GetImage("servers/icon_robotron_column", false)); + int replayColumnImage = imageList->AddImage(scheme()->GetImage("servers/icon_replay_column", false)); + + m_pGameList->SetImageList(imageList, true); + m_hFont = pScheme->GetFont( "ListSmall", IsProportional() ); + if ( !m_hFont ) + m_hFont = pScheme->GetFont( "DefaultSmall", IsProportional() ); + + m_pGameList->SetFont( m_hFont ); + m_pGameList->SetColumnHeaderImage( k_nColumn_Password, passwordColumnImage); + //m_pGameList->SetColumnHeaderImage( k_nColumn_Bots, botColumnImage); + m_pGameList->SetColumnHeaderImage( k_nColumn_Secure, secureColumnImage); + m_pGameList->SetColumnHeaderImage( k_nColumn_Replay, replayColumnImage); +} + +struct serverqualitysort_t +{ + int iIndex; + int iPing; + int iPlayerCount; + int iMaxPlayerCount; +}; + +int ServerQualitySort( const serverqualitysort_t *pSQ1, const serverqualitysort_t *pSQ2 ) +{ + int iMaxP = sb_mod_suggested_maxplayers.GetInt(); + if ( iMaxP && pSQ1->iMaxPlayerCount != pSQ2->iMaxPlayerCount ) + { + if ( pSQ1->iMaxPlayerCount > iMaxP ) + return 1; + if ( pSQ2->iMaxPlayerCount > iMaxP ) + return -1; + } + + if ( pSQ1->iPing <= 100 && pSQ2->iPing <= 100 && pSQ1->iPlayerCount != pSQ2->iPlayerCount ) + { + return pSQ2->iPlayerCount - pSQ1->iPlayerCount; + } + + return pSQ1->iPing - pSQ2->iPing; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseGamesPage::SelectQuickListServers( void ) +{ + int iIndex = m_pQuickList->FirstItem(); + + while ( iIndex != m_pQuickList->InvalidItemID() ) + { + CQuickListPanel *pQuickListPanel = dynamic_cast< CQuickListPanel *> ( m_pQuickList->GetItemPanel( iIndex ) ); + + if ( pQuickListPanel ) + { + CUtlVector< serverqualitysort_t > vecServerQuality; + + int iElement = m_quicklistserverlist.Find( pQuickListPanel->GetName() ); + + if ( iElement != m_quicklistserverlist.InvalidIndex() ) + { + CQuickListMapServerList *vecMapServers = &m_quicklistserverlist[iElement]; + + if ( vecMapServers ) + { + for ( int i =0; i < vecMapServers->Count(); i++ ) + { + int iListID = vecMapServers->Element( i ); + + serverqualitysort_t serverquality; + + serverquality.iIndex = iListID; + + KeyValues *kv = NULL; + if ( m_pGameList->IsValidItemID( iListID ) ) + { + kv = m_pGameList->GetItem( iListID ); + } + + if ( kv ) + { + serverquality.iPing = kv->GetInt( "ping", 0 ); + serverquality.iPlayerCount = kv->GetInt( "PlayerCount", 0 ); + serverquality.iMaxPlayerCount = kv->GetInt( "MaxPlayerCount", 0 ); + } + + vecServerQuality.AddToTail( serverquality ); + } + + vecServerQuality.Sort( ServerQualitySort ); + + serverqualitysort_t bestserver = vecServerQuality.Head(); + + if ( m_pGameList->IsValidItemID( bestserver.iIndex ) ) + { + pQuickListPanel->SetServerInfo( m_pGameList->GetItem( bestserver.iIndex ), bestserver.iIndex, vecServerQuality.Count() ); + } + } + } + } + + iIndex = m_pQuickList->NextItem( iIndex ); + } + + //Force the connect button to recalculate its state. + OnItemSelected(); +} + +int ServerMapnameSortFunc( const servermaps_t *p1, const servermaps_t *p2 ) +{ + //If they're both on disc OR both missing then sort them alphabetically + if ( (p1->bOnDisk && p2->bOnDisk) || (!p1->bOnDisk && !p2->bOnDisk ) ) + return Q_strcmp( p1->pFriendlyName, p2->pFriendlyName ); + + //Otherwise maps you have show up first + return p2->bOnDisk - p1->bOnDisk; +} + +//----------------------------------------------------------------------------- +// Purpose: prepares all the QuickListPanel map panels... +//----------------------------------------------------------------------------- +void CBaseGamesPage::PrepareQuickListMap( const char *pMapName, int iListID ) +{ + char szMapName[ 512 ]; + Q_snprintf( szMapName, sizeof( szMapName ), "%s", pMapName ); + + Q_strlower( szMapName ); + + char path[ 512 ]; + Q_snprintf( path, sizeof( path ), "maps/%s.bsp", szMapName ); + + int iIndex = m_quicklistserverlist.Find( szMapName ); + + if ( m_quicklistserverlist.IsValidIndex( iIndex ) == false ) + { + CQuickListMapServerList vecMapServers; + iIndex = m_quicklistserverlist.Insert( szMapName, vecMapServers ); + + char szFriendlyName[MAX_MAP_NAME]; + const char *pszFriendlyGameTypeName = ServerBrowser().GetMapFriendlyNameAndGameType( szMapName, szFriendlyName, sizeof(szFriendlyName) ); + + //Add the map to our list of panels. + if ( m_pQuickList ) + { + servermaps_t servermap; + + servermap.pFriendlyName = CloneString( szFriendlyName ); + servermap.pOriginalName = CloneString( szMapName ); + + char path[ 512 ]; + Q_snprintf( path, sizeof( path ), "maps/%s.bsp", szMapName ); + + servermap.bOnDisk = g_pFullFileSystem->FileExists( path, "MOD" ); + + CQuickListPanel *pQuickListPanel = new CQuickListPanel( m_pQuickList, "QuickListPanel"); + + if ( pQuickListPanel ) + { + pQuickListPanel->InvalidateLayout(); + pQuickListPanel->SetName( servermap.pOriginalName ); + pQuickListPanel->SetMapName( servermap.pFriendlyName ); + pQuickListPanel->SetImage( servermap.pOriginalName ); + pQuickListPanel->SetGameType( pszFriendlyGameTypeName ); + pQuickListPanel->SetVisible( true ); + pQuickListPanel->SetRefreshing(); + + servermap.iPanelIndex = m_pQuickList->AddItem( NULL, pQuickListPanel ); + } + + m_vecMapNamesFound.AddToTail( servermap ); + m_vecMapNamesFound.Sort( ServerMapnameSortFunc ); + } + + //Now make sure that list is sorted. + CUtlVector<int> *pPanelSort = m_pQuickList->GetSortedVector(); + + if ( pPanelSort ) + { + pPanelSort->RemoveAll(); + + for ( int i = 0; i < m_vecMapNamesFound.Count(); i++ ) + { + pPanelSort->AddToTail( m_vecMapNamesFound[i].iPanelIndex ); + } + } + } + + if ( iIndex != m_quicklistserverlist.InvalidIndex() ) + { + CQuickListMapServerList *vecMapServers = &m_quicklistserverlist[iIndex]; + + if ( vecMapServers ) + { + if ( vecMapServers->Find( iListID ) == vecMapServers->InvalidIndex() ) + { + vecMapServers->AddToTail( iListID ); + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: gets information about specified server +//----------------------------------------------------------------------------- +gameserveritem_t *CBaseGamesPage::GetServer( unsigned int serverID ) +{ + if ( !steamapicontext->SteamMatchmakingServers() ) + return NULL; + + // No point checking for >= 0 when serverID is unsigned. + //if ( serverID >= 0 ) + { + return steamapicontext->SteamMatchmakingServers()->GetServerDetails( m_hRequest, serverID ); + } + //else + //{ + // Assert( !"Unable to return a useful entry" ); + // return NULL; // bugbug Alfred: temp Favorites/History objects won't return a good value here... + //} +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CBaseGamesPage::TagsExclude( void ) +{ + if ( m_pTagsIncludeFilter == NULL ) + return false; + + return m_pTagsIncludeFilter->GetActiveItem(); +} + +//----------------------------------------------------------------------------- +// Purpose: What mode the workshop selection is in for pages that use it +//----------------------------------------------------------------------------- +CBaseGamesPage::eWorkshopMode CBaseGamesPage::WorkshopMode() +{ + if ( !m_pWorkshopFilter || !ServerBrowser().IsWorkshopEnabled() ) + { + return eWorkshop_None; + } + + return (eWorkshopMode)m_pWorkshopFilter->GetActiveItem(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseGamesPage::HideReplayFilter( void ) +{ + if ( m_pReplayFilterCheck && m_pReplayFilterCheck->IsVisible() ) + { + m_pReplayFilterCheck->SetVisible( false ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseGamesPage::CreateFilters() +{ + m_pFilter = new ToggleButton(this, "Filter", "#ServerBrowser_Filters"); + m_pFilterString = new Label(this, "FilterString", ""); + + if ( Q_stricmp( COM_GetModDirectory(), "cstrike" ) == 0 ) + { + m_pFilter->SetSelected( false ); + m_bFiltersVisible = false; + } + else + { + m_pFilter->SetSelected( true ); + m_bFiltersVisible = true; + } + + // filter controls + m_pGameFilter = new ComboBox(this, "GameFilter", 6, false); + + m_pLocationFilter = new ComboBox(this, "LocationFilter", 6, false); + m_pLocationFilter->AddItem("", NULL); + + m_pMapFilter = new TextEntry(this, "MapFilter"); + m_pMaxPlayerFilter = new TextEntry(this, "MaxPlayerFilter"); + m_pPingFilter = new ComboBox(this, "PingFilter", 6, false); + m_pPingFilter->AddItem("#ServerBrowser_All", NULL); + m_pPingFilter->AddItem("#ServerBrowser_LessThan50", NULL); + m_pPingFilter->AddItem("#ServerBrowser_LessThan100", NULL); + m_pPingFilter->AddItem("#ServerBrowser_LessThan150", NULL); + m_pPingFilter->AddItem("#ServerBrowser_LessThan250", NULL); + m_pPingFilter->AddItem("#ServerBrowser_LessThan350", NULL); + m_pPingFilter->AddItem("#ServerBrowser_LessThan600", NULL); + + m_pSecureFilter = new ComboBox(this, "SecureFilter", 3, false); + m_pSecureFilter->AddItem("#ServerBrowser_All", NULL); + m_pSecureFilter->AddItem("#ServerBrowser_SecureOnly", NULL); + m_pSecureFilter->AddItem("#ServerBrowser_InsecureOnly", NULL); + + m_pTagsIncludeFilter = new ComboBox(this, "TagsInclude", 2, false); + m_pTagsIncludeFilter->AddItem("#ServerBrowser_TagsInclude", NULL); + m_pTagsIncludeFilter->AddItem("#ServerBrowser_TagsDoNotInclude", NULL); + m_pTagsIncludeFilter->SetVisible( false ); + + if ( ServerBrowser().IsWorkshopEnabled() ) + { + m_pWorkshopFilter = new ComboBox(this, "WorkshopFilter", 3, false); + m_pWorkshopFilter->AddItem("#ServerBrowser_All", NULL); + m_pWorkshopFilter->AddItem("#ServerBrowser_WorkshopFilterWorkshopOnly", NULL); + m_pWorkshopFilter->AddItem("#ServerBrowser_WorkshopFilterSubscribed", NULL); + m_pWorkshopFilter->SetVisible( false ); + } + + m_pNoEmptyServersFilterCheck = new CheckButton(this, "ServerEmptyFilterCheck", ""); + m_pNoFullServersFilterCheck = new CheckButton(this, "ServerFullFilterCheck", ""); + m_pNoPasswordFilterCheck = new CheckButton(this, "NoPasswordFilterCheck", ""); + m_pQuickListCheckButton = new CCheckBoxWithStatus(this, "QuickListCheck", ""); + m_pReplayFilterCheck = new CheckButton(this, "ReplayFilterCheck", ""); + + KeyValues *pkv = new KeyValues("mod", "gamedir", "", "appid", NULL ); + m_pGameFilter->AddItem("#ServerBrowser_All", pkv); + + for (int i = 0; i < ModList().ModCount(); i++) + { + pkv->SetString("gamedir", ModList().GetModDir(i)); + pkv->SetUint64("appid", ModList().GetAppID(i).ToUint64() ); + int iItemID = m_pGameFilter->AddItem(ModList().GetModName(i), pkv); + m_mapGamesFilterItem.Insert( ModList().GetAppID(i).ToUint64(), iItemID ); + } + pkv->deleteThis(); + +} + + +//----------------------------------------------------------------------------- +// Purpose: loads filter settings from the keyvalues +//----------------------------------------------------------------------------- +void CBaseGamesPage::LoadFilterSettings() +{ + KeyValues *filter = ServerBrowserDialog().GetFilterSaveData(GetName()); + + if (ServerBrowserDialog().GetActiveModName()) + { + Q_strncpy(m_szGameFilter, ServerBrowserDialog().GetActiveModName(), sizeof(m_szGameFilter)); + m_iLimitToAppID = ServerBrowserDialog().GetActiveAppID(); + } + else + { + Q_strncpy(m_szGameFilter, filter->GetString("game"), sizeof(m_szGameFilter)); + m_iLimitToAppID = CGameID( filter->GetUint64( "appid", 0 ) ); + } + + Q_strncpy(m_szMapFilter, filter->GetString("map"), sizeof(m_szMapFilter)); + m_iMaxPlayerFilter = filter->GetInt("MaxPlayerCount"); + m_iPingFilter = filter->GetInt("ping"); + m_bFilterNoFullServers = filter->GetInt("NoFull"); + m_bFilterNoEmptyServers = filter->GetInt("NoEmpty"); + m_bFilterNoPasswordedServers = filter->GetInt("NoPassword"); + m_bFilterReplayServers = filter->GetInt("Replay"); + m_pQuickListCheckButton->SetSelected( filter->GetInt( "QuickList", 0 ) ); + + int secureFilter = filter->GetInt("Secure"); + m_pSecureFilter->ActivateItem(secureFilter); + + int tagsinclude = filter->GetInt("tagsinclude"); + m_pTagsIncludeFilter->ActivateItem( tagsinclude ); + + if ( m_pWorkshopFilter ) + { + int workshopFilter = filter->GetInt("workshopfilter"); + m_pWorkshopFilter->ActivateItem( workshopFilter ); + } + + // apply to the controls + UpdateGameFilter(); + m_pMapFilter->SetText(m_szMapFilter); + m_pLocationFilter->ActivateItem(filter->GetInt("location")); + + if (m_iMaxPlayerFilter) + { + char buf[32]; + Q_snprintf(buf, sizeof(buf), "%d", m_iMaxPlayerFilter); + m_pMaxPlayerFilter->SetText(buf); + } + + if (m_iPingFilter) + { + char buf[32]; + Q_snprintf(buf, sizeof(buf), "< %d", m_iPingFilter); + m_pPingFilter->SetText(buf); + } + + m_pNoFullServersFilterCheck->SetSelected(m_bFilterNoFullServers); + m_pNoEmptyServersFilterCheck->SetSelected(m_bFilterNoEmptyServers); + m_pNoPasswordFilterCheck->SetSelected(m_bFilterNoPasswordedServers); + m_pReplayFilterCheck->SetSelected(m_bFilterReplayServers); + + OnLoadFilter( filter ); + UpdateFilterSettings(); + + UpdateFilterAndQuickListVisibility(); +} + +//----------------------------------------------------------------------------- +// Purpose: Sets the game filter combo box to be the saved setting +//----------------------------------------------------------------------------- +void CBaseGamesPage::UpdateGameFilter() +{ + bool bFound = false; + for (int i = 0; i < m_pGameFilter->GetItemCount(); i++) + { + KeyValues *kv = m_pGameFilter->GetItemUserData(i); + CGameID gameID( kv->GetUint64( "appID", 0 ) ); + const char *pchGameDir = kv->GetString( "gamedir" ); + if ( ( gameID == m_iLimitToAppID || m_iLimitToAppID.AppID() == 0 ) && ( !m_szGameFilter[0] || + ( pchGameDir && pchGameDir[0] && !Q_strncmp( pchGameDir, m_szGameFilter, Q_strlen( pchGameDir ) ) ) ) ) + { + if ( i != m_pGameFilter->GetActiveItem() ) + { + m_pGameFilter->ActivateItem(i); + } + bFound = true; + break; + } + } + if (!bFound) + { + // default to empty + if ( 0 != m_pGameFilter->GetActiveItem() ) + { + m_pGameFilter->ActivateItem(0); + } + } + + // only one mod is allowed in the game + if ( ServerBrowserDialog().GetActiveModName() ) + { + m_pGameFilter->SetEnabled( false ); + m_pGameFilter->SetText( ServerBrowserDialog().GetActiveGameName() ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Handles incoming server refresh data +// updates the server browser with the refreshed information from the server itself +//----------------------------------------------------------------------------- +void CBaseGamesPage::ServerResponded( gameserveritem_t &server ) +{ + int nIndex = -1; // start at -1 and work backwards to find the next free slot for this adhoc query + while ( m_mapServers.Find( nIndex ) != m_mapServers.InvalidIndex() ) + nIndex--; + ServerResponded( nIndex, &server ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Callback for ISteamMatchmakingServerListResponse +//----------------------------------------------------------------------------- +void CBaseGamesPage::ServerResponded( HServerListRequest hReq, int iServer ) +{ + gameserveritem_t *pServerItem = steamapicontext->SteamMatchmakingServers()->GetServerDetails( hReq, iServer ); + if ( !pServerItem ) + { + Assert( !"Missing server response" ); + return; + } + + // FIXME(johns): This is a workaround for a steam bug, where it inproperly reads signed bytes out of the + // message. Once the upstream fix makes it into our SteamSDK, this block can be removed. + pServerItem->m_nPlayers = (uint8)(int8)pServerItem->m_nPlayers; + pServerItem->m_nBotPlayers = (uint8)(int8)pServerItem->m_nBotPlayers; + pServerItem->m_nMaxPlayers = (uint8)(int8)pServerItem->m_nMaxPlayers; + + ServerResponded( iServer, pServerItem ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Handles incoming server refresh data +// updates the server browser with the refreshed information from the server itself +//----------------------------------------------------------------------------- +void CBaseGamesPage::ServerResponded( int iServer, gameserveritem_t *pServerItem ) +{ + int iServerMap = m_mapServers.Find( iServer ); + if ( iServerMap == m_mapServers.InvalidIndex() ) + { + netadr_t netAdr( pServerItem->m_NetAdr.GetIP(), pServerItem->m_NetAdr.GetConnectionPort() ); + int iServerIP = m_mapServerIP.Find( netAdr ); + if ( iServerIP != m_mapServerIP.InvalidIndex() ) + { + // if we already had this entry under another index remove the old entry + int iServerMap = m_mapServers.Find( m_mapServerIP[ iServerIP ] ); + if ( iServerMap != m_mapServers.InvalidIndex() ) + { + serverdisplay_t &server = m_mapServers[ iServerMap ]; + if ( m_pGameList->IsValidItemID( server.m_iListID ) ) + m_pGameList->RemoveItem( server.m_iListID ); + m_mapServers.RemoveAt( iServerMap ); + } + m_mapServerIP.RemoveAt( iServerIP ); + } + + serverdisplay_t serverFind; + serverFind.m_iListID = -1; + serverFind.m_bDoNotRefresh = false; + iServerMap = m_mapServers.Insert( iServer, serverFind ); + m_mapServerIP.Insert( netAdr, iServer ); + } + + serverdisplay_t *pServer = &m_mapServers[ iServerMap ]; + pServer->m_iServerID = iServer; + Assert( pServerItem->m_NetAdr.GetIP() != 0 ); + + // check filters + bool removeItem = false; + if ( !CheckPrimaryFilters( *pServerItem ) ) + { + // server has been filtered at a primary level + // remove from lists + pServer->m_bDoNotRefresh = true; + + // remove from UI list + removeItem = true; + + if ( m_pGameList->IsValidItemID( pServer->m_iListID ) ) + { + m_pGameList->RemoveItem( pServer->m_iListID ); + pServer->m_iListID = GetInvalidServerListID(); + } + + return; + } + else if (!CheckSecondaryFilters( *pServerItem )) + { + // we still ping this server in the future; however it is removed from UI list + removeItem = true; + } + + // update UI + KeyValues *kv; + if ( m_pGameList->IsValidItemID( pServer->m_iListID ) ) + { + // we're updating an existing entry + kv = m_pGameList->GetItem( pServer->m_iListID ); + m_pGameList->SetUserData( pServer->m_iListID, pServer->m_iServerID ); + } + else + { + // new entry + kv = new KeyValues("Server"); + } + + kv->SetString("name", pServerItem->GetName()); + kv->SetString("map", pServerItem->m_szMap); + kv->SetString("GameDir", pServerItem->m_szGameDir); + kv->SetString("GameDesc", pServerItem->m_szGameDescription); + kv->SetInt("password", pServerItem->m_bPassword ? m_nImageIndexPassword : 0); + + if ( pServerItem->m_nBotPlayers > 0 ) + kv->SetInt("bots", pServerItem->m_nBotPlayers); + else + kv->SetString("bots", ""); + + if ( pServerItem->m_bSecure ) + { + // show the denied icon if banned from secure servers, the secure icon otherwise + kv->SetInt("secure", ServerBrowser().IsVACBannedFromGame( pServerItem->m_nAppID ) ? m_nImageIndexSecureVacBanned : m_nImageIndexSecure ); + } + else + { + kv->SetInt("secure", 0); + } + + kv->SetString( "IPAddr", pServerItem->m_NetAdr.GetConnectionAddressString() ); + + int nAdjustedForBotsPlayers = max( 0, pServerItem->m_nPlayers - pServerItem->m_nBotPlayers ); + + char buf[32]; + Q_snprintf(buf, sizeof(buf), "%d / %d", nAdjustedForBotsPlayers, pServerItem->m_nMaxPlayers ); + kv->SetString("Players", buf); + + kv->SetInt("PlayerCount", nAdjustedForBotsPlayers ); + kv->SetInt("MaxPlayerCount", pServerItem->m_nMaxPlayers ); + + kv->SetInt("Ping", pServerItem->m_nPing); + + kv->SetString("Tags", pServerItem->m_szGameTags ); + + kv->SetInt("Replay", IsReplayServer( *pServerItem ) ? m_nImageIndexReplay : 0); + + if ( pServerItem->m_ulTimeLastPlayed ) + { + // construct a time string for last played time + struct tm *now; + now = localtime( (time_t*)&pServerItem->m_ulTimeLastPlayed ); + + if ( now ) + { + char buf[64]; + strftime(buf, sizeof(buf), "%a %d %b %I:%M%p", now); + Q_strlower(buf + strlen(buf) - 4); + kv->SetString("LastPlayed", buf); + } + } + + if ( pServer->m_bDoNotRefresh ) + { + // clear out the vars + kv->SetString("Ping", ""); + kv->SetWString("GameDesc", g_pVGuiLocalize->Find("#ServerBrowser_NotResponding")); + kv->SetString("Players", ""); + kv->SetString("map", ""); + } + + if ( !m_pGameList->IsValidItemID( pServer->m_iListID ) ) + { + // new server, add to list + pServer->m_iListID = m_pGameList->AddItem(kv, pServer->m_iServerID, false, false); + if ( m_bAutoSelectFirstItemInGameList && m_pGameList->GetItemCount() == 1 ) + { + m_pGameList->AddSelectedItem( pServer->m_iListID ); + } + + m_pGameList->SetItemVisible( pServer->m_iListID, !removeItem ); + + kv->deleteThis(); + } + else + { + // tell the list that we've changed the data + m_pGameList->ApplyItemChanges( pServer->m_iListID ); + m_pGameList->SetItemVisible( pServer->m_iListID, !removeItem ); + } + + PrepareQuickListMap( pServerItem->m_szMap, pServer->m_iListID ); + UpdateStatus(); + m_iServerRefreshCount++; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseGamesPage::UpdateFilterAndQuickListVisibility() +{ + bool showQuickList = m_pQuickListCheckButton->IsSelected(); + bool showFilter = m_pFilter->IsSelected(); + + m_bFiltersVisible = !showQuickList && !m_pCustomResFilename && showFilter; + + int wide, tall; + GetSize( wide, tall ); + SetSize( 624, 278 ); + + UpdateDerivedLayouts(); + UpdateGameFilter(); + + if ( m_hFont ) + { + SETUP_PANEL( m_pGameList ); + m_pGameList->SetFont( m_hFont ); + } + + SetSize( wide, tall ); + + + m_pQuickList->SetVisible( showQuickList ); + m_pGameList->SetVisible( !showQuickList ); + m_pFilter->SetVisible( !showQuickList ); + m_pFilterString->SetVisible ( !showQuickList ); + + + InvalidateLayout(); + + UpdateFilterSettings(); + ApplyGameFilters(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseGamesPage::SetQuickListEnabled( bool bEnabled ) +{ + m_pQuickListCheckButton->SetSelected( bEnabled ); + + m_pQuickList->SetVisible( m_pQuickListCheckButton->IsSelected() ); + m_pGameList->SetVisible( !m_pQuickListCheckButton->IsSelected() ); + + m_pFilter->SetVisible( !m_pQuickListCheckButton->IsSelected() ); + m_pFilterString->SetVisible( !m_pQuickListCheckButton->IsSelected() ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseGamesPage::SetFiltersVisible( bool bVisible ) +{ + if ( bVisible == m_pFilter->IsSelected() ) + return; + + m_pFilter->SetSelected( bVisible ); + OnButtonToggled( m_pFilter, bVisible ); +} + +//----------------------------------------------------------------------------- +// Purpose: Handles filter dropdown being toggled +//----------------------------------------------------------------------------- +void CBaseGamesPage::OnButtonToggled( Panel *panel, int state ) +{ + UpdateFilterAndQuickListVisibility(); + + + if (panel == m_pNoFullServersFilterCheck || panel == m_pNoEmptyServersFilterCheck || panel == m_pNoPasswordFilterCheck || panel == m_pReplayFilterCheck) + { + // treat changing these buttons like any other filter has changed + OnTextChanged(panel, ""); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseGamesPage::UpdateDerivedLayouts( void ) +{ + char rgchControlSettings[MAX_PATH]; + if ( m_pCustomResFilename ) + { + Q_snprintf( rgchControlSettings, sizeof( rgchControlSettings ), "%s", m_pCustomResFilename ); + } + else + { + if ( m_pFilter->IsSelected() && !m_pQuickListCheckButton->IsSelected() ) + { + // drop down + V_strncpy( rgchControlSettings, "servers/InternetGamesPage_Filters.res", sizeof( rgchControlSettings ) ); + } + else + { + // hide filter area + V_strncpy( rgchControlSettings, "servers/InternetGamesPage.res", sizeof( rgchControlSettings ) ); + } + } + + const char *pPathID = "PLATFORM"; + + if ( g_pFullFileSystem->FileExists( rgchControlSettings, "MOD" ) ) + { + pPathID = "MOD"; + } + + LoadControlSettings( rgchControlSettings, pPathID ); + + if ( !GameSupportsReplay() ) + { + HideReplayFilter(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Called when the game dir combo box is changed +//----------------------------------------------------------------------------- +void CBaseGamesPage::OnTextChanged(Panel *panel, const char *text) +{ + if (!Q_stricmp(text, m_szComboAllText)) + { + ComboBox *box = dynamic_cast<ComboBox *>(panel); + if (box) + { + box->SetText(""); + text = ""; + } + } + + // get filter settings from controls + UpdateFilterSettings(); + + // apply settings + ApplyGameFilters(); + + if ( m_bFiltersVisible && ( panel == m_pGameFilter || panel == m_pLocationFilter ) && ServerBrowserDialog().IsVisible() ) + { + // if they changed games and/or region then cancel the refresh because the old list they are getting + // will be for the wrong game, so stop and start a refresh + StopRefresh(); + GetNewServerList(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: applies only the game filter to the current list +//----------------------------------------------------------------------------- +void CBaseGamesPage::ApplyGameFilters() +{ + if ( !steamapicontext->SteamMatchmakingServers() ) + return; + + m_iServersBlacklisted = 0; + + // loop through all the servers checking filters + FOR_EACH_MAP_FAST( m_mapServers, i ) + { + serverdisplay_t &server = m_mapServers[ i ]; + gameserveritem_t *pServer = steamapicontext->SteamMatchmakingServers()->GetServerDetails( m_hRequest, server.m_iServerID ); + if ( !pServer ) + continue; + + if (!CheckPrimaryFilters( *pServer ) || !CheckSecondaryFilters( *pServer )) + { + // server failed filtering, remove it + server.m_bDoNotRefresh = true; + if ( m_pGameList->IsValidItemID( server.m_iListID) ) + { + // don't remove the server from list, just hide since this is a lot faster + m_pGameList->SetItemVisible( server.m_iListID, false ); + } + } + else if ( BShowServer( server ) ) + { + // server passed filters, so it can be refreshed again + server.m_bDoNotRefresh = false; + + // re-add item to list + if ( !m_pGameList->IsValidItemID( server.m_iListID ) ) + { + KeyValues *kv = new KeyValues("Server"); + kv->SetString("name", pServer->GetName()); + kv->SetString("map", pServer->m_szMap); + kv->SetString("GameDir", pServer->m_szGameDir); + kv->SetString( "GameTags", pServer->m_szGameTags ); + if ( pServer->m_szGameDescription[0] ) + { + kv->SetString("GameDesc", pServer->m_szGameDescription ); + } + else + { + kv->SetWString("GameDesc", g_pVGuiLocalize->Find("#ServerBrowser_PendingPing")); + } + + int nAdjustedForBotsPlayers = max( 0, pServer->m_nPlayers - pServer->m_nBotPlayers ); + + char buf[256]; + Q_snprintf(buf, sizeof(buf), "%d / %d", nAdjustedForBotsPlayers, pServer->m_nMaxPlayers ); + kv->SetString( "Players", buf); + kv->SetInt( "Ping", pServer->m_nPing ); + kv->SetInt( "password", pServer->m_bPassword ? m_nImageIndexPassword : 0); + if ( pServer->m_nBotPlayers > 0 ) + kv->SetInt("bots", pServer->m_nBotPlayers); + else + kv->SetString("bots", ""); + + kv->SetInt("Replay", IsReplayServer( *pServer ) ? m_nImageIndexReplay : 0); + + server.m_iListID = m_pGameList->AddItem(kv, server.m_iServerID, false, false); + kv->deleteThis(); + } + + // make sure the server is visible + m_pGameList->SetItemVisible( server.m_iListID, true ); + } + } + + UpdateStatus(); + m_pGameList->SortList(); + InvalidateLayout(); + Repaint(); +} + +//----------------------------------------------------------------------------- +// Purpose: Resets UI server count +//----------------------------------------------------------------------------- +void CBaseGamesPage::UpdateStatus() +{ + if (m_pGameList->GetItemCount() > 1) + { + wchar_t header[256]; + wchar_t count[128]; + wchar_t blacklistcount[128]; + + _snwprintf( count, Q_ARRAYSIZE(count), L"%d", m_pGameList->GetItemCount() ); + _snwprintf( blacklistcount, Q_ARRAYSIZE(blacklistcount), L"%d", m_iServersBlacklisted ); + g_pVGuiLocalize->ConstructString( header, sizeof( header ), g_pVGuiLocalize->Find( "#ServerBrowser_ServersCountWithBlacklist"), 2, count, blacklistcount ); + m_pGameList->SetColumnHeaderText( k_nColumn_Name, header); + } + else + { + m_pGameList->SetColumnHeaderText( k_nColumn_Name, g_pVGuiLocalize->Find("#ServerBrowser_Servers")); + } +} + +//----------------------------------------------------------------------------- +// Purpose: gets filter settings from controls +//----------------------------------------------------------------------------- +void CBaseGamesPage::UpdateFilterSettings() +{ + // game + if ( ServerBrowserDialog().GetActiveModName() ) + { + // overriding the game filter + Q_strncpy(m_szGameFilter, ServerBrowserDialog().GetActiveModName(), sizeof(m_szGameFilter)); + m_iLimitToAppID = ServerBrowserDialog().GetActiveAppID(); + + + #ifdef STAGING_ONLY + if ( sb_fake_app_id.GetInt() != 0 ) + m_iLimitToAppID = CGameID( sb_fake_app_id.GetInt() ); + #endif + + + RecalculateFilterString(); + UpdateGameFilter(); + } + else + { + KeyValues *data = m_pGameFilter->GetActiveItemUserData(); + if (data && Q_strlen( data->GetString( "gamedir" ) ) > 0 ) + { + Q_strncpy( m_szGameFilter, data->GetString( "gamedir" ), sizeof( m_szGameFilter ) ); + if ( Q_strlen( m_szGameFilter ) > 0 ) // if there is a gamedir + { + m_iLimitToAppID = CGameID( data->GetUint64( "appid", 0 ) ); + } + else + { + m_iLimitToAppID.Reset(); + } + } + else + { + m_iLimitToAppID.Reset(); + m_szGameFilter[0] = 0; + } + m_pGameFilter->SetEnabled(true); + } + Q_strlower(m_szGameFilter); + + // map + m_pMapFilter->GetText(m_szMapFilter, sizeof(m_szMapFilter) - 1); + Q_strlower(m_szMapFilter); + + // max player + char buf[256]; + m_pMaxPlayerFilter->GetText(buf, sizeof(buf)); + if (buf[0]) + { + m_iMaxPlayerFilter = atoi(buf); + } + else + { + m_iMaxPlayerFilter = 0; + } + + // ping + m_pPingFilter->GetText(buf, sizeof(buf)); + if (buf[0]) + { + m_iPingFilter = atoi(buf + 2); + } + else + { + m_iPingFilter = 0; + } + + // players + m_bFilterNoFullServers = m_pNoFullServersFilterCheck->IsSelected(); + m_bFilterNoEmptyServers = m_pNoEmptyServersFilterCheck->IsSelected(); + m_bFilterNoPasswordedServers = m_pNoPasswordFilterCheck->IsSelected(); + m_iSecureFilter = m_pSecureFilter->GetActiveItem(); + + if ( GameSupportsReplay() ) + { + m_bFilterReplayServers = m_pReplayFilterCheck->IsSelected(); + } + else + { + m_bFilterReplayServers = false; + } + + m_vecServerFilters.RemoveAll(); + + bool bFilterNoEmpty = m_bFilterNoEmptyServers; + bool bFilterNoFull = m_bFilterNoFullServers; + bool bFilterNoPassword = m_bFilterNoPasswordedServers; + int iFilterSecure = m_iSecureFilter; + + if ( m_pQuickList->IsVisible() == true ) + { + bFilterNoEmpty = true; + bFilterNoFull = true; + bFilterNoPassword = true; + iFilterSecure = FILTER_SECURESERVERSONLY; + } + + extern IRunGameEngine *g_pRunGameEngine; + if ( sb_filter_incompatible_versions.GetBool() && g_pRunGameEngine != NULL ) + { + const char *pszVersion = g_pRunGameEngine->GetProductVersionString(); + const char k_VersionFromP4[] = "2000"; // magic version string we use when we're running from P4 + if ( pszVersion && *pszVersion && ( V_strcmp( pszVersion, k_VersionFromP4 ) != 0 ) ) + { + m_vecServerFilters.AddToTail( MatchMakingKeyValuePair_t( "version_match", pszVersion ) ); + } + } + + // update master filter string text + if (m_szGameFilter[0] && m_iLimitToAppID.AppID() != 1002 ) // HACKHACK: Alfred - don't use a dir filter for RDKF + { + m_vecServerFilters.AddToTail( MatchMakingKeyValuePair_t( "gamedir", m_szGameFilter ) ); + } + if (bFilterNoEmpty) + { + m_vecServerFilters.AddToTail( MatchMakingKeyValuePair_t( "empty", "1" ) ); + } + if (bFilterNoFull) + { + m_vecServerFilters.AddToTail( MatchMakingKeyValuePair_t( "full", "1" ) ); + } + if (iFilterSecure == FILTER_SECURESERVERSONLY) + { + m_vecServerFilters.AddToTail( MatchMakingKeyValuePair_t( "secure", "1" ) ); + } + int regCode = GetRegionCodeToFilter(); + if ( ( regCode >= 0 ) && ( regCode < 255 ) ) + { + char szRegCode[ 32 ]; + Q_snprintf( szRegCode, sizeof(szRegCode), "%i", regCode ); + m_vecServerFilters.AddToTail( MatchMakingKeyValuePair_t( "region", szRegCode ) ); + } + + // copy filter settings into filter file + KeyValues *filter = ServerBrowserDialog().GetFilterSaveData(GetName()); + + // only save the game filter if we're not overriding it + if (!ServerBrowserDialog().GetActiveModName()) + { + filter->SetString("game", m_szGameFilter); + filter->SetUint64( "appid", m_iLimitToAppID.ToUint64() ); + } + + filter->SetString("map", m_szMapFilter); + filter->SetInt("MaxPlayerCount", m_iMaxPlayerFilter); + filter->SetInt("ping", m_iPingFilter); + + if ( m_pLocationFilter->GetItemCount() > 1 ) + { + // only save this if there are options to choose from + filter->SetInt("location", m_pLocationFilter->GetActiveItem()); + } + + filter->SetInt("NoFull", m_bFilterNoFullServers); + filter->SetInt("NoEmpty", m_bFilterNoEmptyServers); + filter->SetInt("NoPassword", m_bFilterNoPasswordedServers); + filter->SetInt("Secure", m_iSecureFilter); + filter->SetInt("QuickList", m_pQuickListCheckButton->IsSelected() ); + filter->SetInt("tagsinclude", m_pTagsIncludeFilter->GetActiveItem() ); + if ( m_pWorkshopFilter ) + { + filter->SetInt("workshopfilter", m_pWorkshopFilter->GetActiveItem() ); + } + filter->SetInt("Replay", m_bFilterReplayServers); + + OnSaveFilter(filter); + + RecalculateFilterString(); +} + + +//----------------------------------------------------------------------------- +// Purpose: allow derived classes access to the saved filter string +//----------------------------------------------------------------------------- +void CBaseGamesPage::OnSaveFilter(KeyValues *filter) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: allow derived classes access to the saved filter string +//----------------------------------------------------------------------------- +void CBaseGamesPage::OnLoadFilter(KeyValues *filter) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: reconstructs the filter description string from the current filter settings +//----------------------------------------------------------------------------- +void CBaseGamesPage::RecalculateFilterString() +{ + wchar_t unicode[2048], tempUnicode[128], spacerUnicode[8]; + unicode[0] = 0; + int iTempUnicodeSize = sizeof( tempUnicode ); + + Q_UTF8ToUnicode( "; ", spacerUnicode, sizeof( spacerUnicode ) ); + + if (m_szGameFilter[0]) + { + Q_UTF8ToUnicode( ModList().GetModNameForModDir( m_iLimitToAppID ), tempUnicode, iTempUnicodeSize ); + wcscat( unicode, tempUnicode ); + wcscat( unicode, spacerUnicode ); + } + + if (m_iSecureFilter == FILTER_SECURESERVERSONLY) + { + wcscat( unicode, g_pVGuiLocalize->Find( "#ServerBrowser_FilterDescSecureOnly" ) ); + wcscat( unicode, spacerUnicode ); + } + else if (m_iSecureFilter == FILTER_INSECURESERVERSONLY) + { + wcscat( unicode, g_pVGuiLocalize->Find( "#ServerBrowser_FilterDescInsecureOnly" ) ); + wcscat( unicode, spacerUnicode ); + } + + if (m_pLocationFilter->GetActiveItem() > 0) + { + m_pLocationFilter->GetText(tempUnicode, sizeof(tempUnicode)); + wcscat( unicode, tempUnicode ); + wcscat( unicode, spacerUnicode ); + } + + if (m_iPingFilter) + { + char tmpBuf[16]; + _snprintf( tmpBuf, sizeof(tmpBuf), "%d", m_iPingFilter ); + + wcscat( unicode, g_pVGuiLocalize->Find( "#ServerBrowser_FilterDescLatency" ) ); + Q_UTF8ToUnicode( " < ", tempUnicode, iTempUnicodeSize ); + wcscat( unicode, tempUnicode ); + Q_UTF8ToUnicode(tmpBuf, tempUnicode, iTempUnicodeSize ); + wcscat( unicode, tempUnicode ); + wcscat( unicode, spacerUnicode ); + } + + if ( m_iMaxPlayerFilter ) + { + char tmpBuf[16]; + _snprintf( tmpBuf, sizeof(tmpBuf), "%d", m_iMaxPlayerFilter ); + + wcscat( unicode, g_pVGuiLocalize->Find( "#ServerBrowser_FilterDescMaxPlayers" ) ); + Q_UTF8ToUnicode( " <= ", tempUnicode, iTempUnicodeSize ); + wcscat( unicode, tempUnicode ); + Q_UTF8ToUnicode(tmpBuf, tempUnicode, iTempUnicodeSize ); + wcscat( unicode, tempUnicode ); + wcscat( unicode, spacerUnicode ); + } + + if (m_bFilterNoFullServers) + { + wcscat( unicode, g_pVGuiLocalize->Find( "#ServerBrowser_FilterDescNotFull" ) ); + wcscat( unicode, spacerUnicode ); + } + + if (m_bFilterNoEmptyServers) + { + wcscat( unicode, g_pVGuiLocalize->Find( "#ServerBrowser_FilterDescNotEmpty" ) ); + wcscat( unicode, spacerUnicode ); + } + + if (m_bFilterNoPasswordedServers) + { + wcscat( unicode, g_pVGuiLocalize->Find( "#ServerBrowser_FilterDescNoPassword" ) ); + wcscat( unicode, spacerUnicode ); + } + + if (m_bFilterReplayServers) + { + wcscat( unicode, g_pVGuiLocalize->Find( "#ServerBrowser_FilterDescReplays" ) ); + wcscat( unicode, spacerUnicode ); + } + + if (m_szMapFilter[0]) + { + Q_UTF8ToUnicode( m_szMapFilter, tempUnicode, iTempUnicodeSize ); + wcscat( unicode, tempUnicode ); + } + + m_pFilterString->SetText(unicode); +} + +//----------------------------------------------------------------------------- +// Purpose: Checks to see if the server passes the primary filters +// if the server fails the filters, it will not be refreshed again +//----------------------------------------------------------------------------- +bool CBaseGamesPage::CheckPrimaryFilters( gameserveritem_t &server ) +{ + if (m_szGameFilter[0] && ( server.m_szGameDir[0] || server.m_nPing ) && Q_stricmp(m_szGameFilter, server.m_szGameDir ) ) + { + return false; + } + + // If it's blacklisted, we ignore it too + if ( ServerBrowserDialog().IsServerBlacklisted( server ) ) + { + m_iServersBlacklisted++; + return false; + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Checks to see if a server passes the secondary set of filters +// server will be continued to be pinged if it fails the filter, since +// the relvent server data is dynamic +//----------------------------------------------------------------------------- +bool CBaseGamesPage::CheckSecondaryFilters( gameserveritem_t &server ) +{ + bool bFilterNoEmpty = m_bFilterNoEmptyServers; + bool bFilterNoFull = m_bFilterNoFullServers; + int iFilterPing = m_iPingFilter; + int iFilterMaxPlayerCount = m_iMaxPlayerFilter; + bool bFilterNoPassword = m_bFilterNoPasswordedServers; + int iFilterSecure = m_iSecureFilter; + + if ( m_pQuickList->IsVisible() == true ) + { + bFilterNoEmpty = true; + bFilterNoFull = true; + iFilterPing = QUICKLIST_FILTER_MIN_PING; + bFilterNoPassword = true; + iFilterSecure = FILTER_SECURESERVERSONLY; + iFilterMaxPlayerCount = sb_mod_suggested_maxplayers.GetInt(); + } + + if ( bFilterNoEmpty && (server.m_nPlayers - server.m_nBotPlayers) < 1 ) + { + return false; + } + + if ( bFilterNoFull && server.m_nPlayers >= server.m_nMaxPlayers ) + { + return false; + } + + if ( iFilterPing && server.m_nPing > iFilterPing ) + { + return false; + } + + if ( iFilterMaxPlayerCount && server.m_nMaxPlayers > iFilterMaxPlayerCount ) + { + return false; + } + + if ( bFilterNoPassword && server.m_bPassword ) + { + return false; + } + + if ( iFilterSecure == FILTER_SECURESERVERSONLY && !server.m_bSecure ) + { + return false; + } + + if ( iFilterSecure == FILTER_INSECURESERVERSONLY && server.m_bSecure ) + { + return false; + } + + if ( m_bFilterReplayServers && !IsReplayServer( server ) ) + { + return false; + } + + if ( m_pQuickList->IsVisible() == false ) + { + // compare the first few characters of the filter name + int count = Q_strlen( m_szMapFilter ); + if ( count && Q_strnicmp( server.m_szMap, m_szMapFilter, count ) ) + { + return false; + } + } + + return CheckTagFilter( server ) && CheckWorkshopFilter( server ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +uint32 CBaseGamesPage::GetServerFilters( MatchMakingKeyValuePair_t **pFilters ) +{ + *pFilters = m_vecServerFilters.Base(); + return m_vecServerFilters.Count(); +} + + +//----------------------------------------------------------------------------- +// Purpose: call to let the UI now whether the game list is currently refreshing +//----------------------------------------------------------------------------- +void CBaseGamesPage::SetRefreshing(bool state) +{ + if (state) + { + ServerBrowserDialog().UpdateStatusText("#ServerBrowser_RefreshingServerList"); + + // clear message in panel + m_pGameList->SetEmptyListText(""); + m_pRefreshAll->SetText("#ServerBrowser_StopRefreshingList"); + m_pRefreshAll->SetCommand("stoprefresh"); + m_pRefreshQuick->SetEnabled(false); + } + else + { + ServerBrowserDialog().UpdateStatusText(""); + if (SupportsItem(IGameList::GETNEWLIST)) + { + m_pRefreshAll->SetText("#ServerBrowser_RefreshAll"); + } + else + { + m_pRefreshAll->SetText("#ServerBrowser_Refresh"); + } + m_pRefreshAll->SetCommand("GetNewList"); + + // 'refresh quick' button is only enabled if there are servers in the list + if (m_pGameList->GetItemCount() > 0) + { + m_pRefreshQuick->SetEnabled(true); + } + else + { + m_pRefreshQuick->SetEnabled(false); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseGamesPage::OnCommand(const char *command) +{ + if (!Q_stricmp(command, "Connect")) + { + OnBeginConnect(); + } + else if (!Q_stricmp(command, "stoprefresh")) + { + // cancel the existing refresh + StopRefresh(); + } + else if ( !Q_stricmp(command, "refresh") ) + { + if ( steamapicontext->SteamMatchmakingServers() ) + steamapicontext->SteamMatchmakingServers()->RefreshQuery( m_hRequest ); + SetRefreshing( true ); + m_iServerRefreshCount = 0; + ClearQuickList(); + } + else if (!Q_stricmp(command, "GetNewList")) + { + GetNewServerList(); + } + else + { + BaseClass::OnCommand(command); + } +} + +//----------------------------------------------------------------------------- +// Purpose: called when a row gets selected in the list +//----------------------------------------------------------------------------- +void CBaseGamesPage::OnItemSelected() +{ + if ( GetSelectedServerID() == -1 ) + { + m_pConnect->SetEnabled(false); + } + else + { + m_pConnect->SetEnabled(true); + } +} + +//----------------------------------------------------------------------------- +// Purpose: refreshes server list on F5 +//----------------------------------------------------------------------------- +void CBaseGamesPage::OnKeyCodePressed(vgui::KeyCode code) +{ + if ( code == KEY_XBUTTON_A || code == STEAMCONTROLLER_A ) + { + m_pConnect->DoClick(); + } + else if ( code == KEY_F5 || code == KEY_XBUTTON_X || code == STEAMCONTROLLER_X ) + { + StartRefresh(); + } + else if ( m_pGameList->GetItemCount() > 0 && + ( code == KEY_XBUTTON_UP || code == KEY_XSTICK1_UP || code == KEY_XSTICK2_UP || code == STEAMCONTROLLER_DPAD_UP || + code == KEY_XBUTTON_DOWN || code == KEY_XSTICK1_DOWN || code == KEY_XSTICK2_DOWN || code == STEAMCONTROLLER_DPAD_DOWN ) ) + { + m_pGameList->RequestFocus(); + } + else + { + BaseClass::OnKeyCodePressed(code); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Handle enter pressed in the games list page. Return true +// to intercept the message instead of passing it on through vgui. +//----------------------------------------------------------------------------- +bool CBaseGamesPage::OnGameListEnterPressed() +{ + return false; +} + + +//----------------------------------------------------------------------------- +// Purpose: Get the # items selected in the game list. +//----------------------------------------------------------------------------- +int CBaseGamesPage::GetSelectedItemsCount() +{ + return m_pGameList->GetSelectedItemsCount(); +} + + +//----------------------------------------------------------------------------- +// Purpose: adds a server to the favorites +//----------------------------------------------------------------------------- +void CBaseGamesPage::OnAddToFavorites() +{ + if ( !steamapicontext->SteamMatchmakingServers() ) + return; + + // loop through all the selected favorites + for (int i = 0; i < m_pGameList->GetSelectedItemsCount(); i++) + { + int serverID = m_pGameList->GetItemUserData(m_pGameList->GetSelectedItem(i)); + + gameserveritem_t *pServer = steamapicontext->SteamMatchmakingServers()->GetServerDetails( m_hRequest, serverID ); + if ( pServer ) + { + // add to favorites list + ServerBrowserDialog().AddServerToFavorites(*pServer); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: adds a server to the blacklist +//----------------------------------------------------------------------------- +void CBaseGamesPage::OnAddToBlacklist() +{ + if ( !steamapicontext->SteamMatchmakingServers() ) + return; + + // loop through all the selected favorites + for (int i = 0; i < m_pGameList->GetSelectedItemsCount(); i++) + { + int serverID = m_pGameList->GetItemUserData(m_pGameList->GetSelectedItem(i)); + + gameserveritem_t *pServer = steamapicontext->SteamMatchmakingServers()->GetServerDetails( m_hRequest, serverID ); + if ( pServer ) + { + ServerBrowserDialog().AddServerToBlacklist(*pServer); + } + } + ServerBrowserDialog().BlacklistsChanged(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseGamesPage::ServerFailedToRespond( HServerListRequest hReq, int iServer ) +{ + ServerResponded( hReq, iServer ); +} + + +//----------------------------------------------------------------------------- +// Purpose: removes the server from the UI list +//----------------------------------------------------------------------------- +void CBaseGamesPage::RemoveServer( serverdisplay_t &server ) +{ + if ( m_pGameList->IsValidItemID( server.m_iListID ) ) + { + // don't remove the server from list, just hide since this is a lot faster + m_pGameList->SetItemVisible( server.m_iListID, false ); + + // find the row in the list and kill + // m_pGameList->RemoveItem(server.listEntryID); + // server.listEntryID = GetInvalidServerListID(); + } + + UpdateStatus(); +} + + +//----------------------------------------------------------------------------- +// Purpose: refreshes a single server +//----------------------------------------------------------------------------- +void CBaseGamesPage::OnRefreshServer( int serverID ) +{ + if ( !steamapicontext->SteamMatchmakingServers() ) + return; + + // walk the list of selected servers refreshing them + for (int i = 0; i < m_pGameList->GetSelectedItemsCount(); i++) + { + int serverID = m_pGameList->GetItemUserData(m_pGameList->GetSelectedItem(i)); + + // refresh this server + steamapicontext->SteamMatchmakingServers()->RefreshServer( m_hRequest, serverID ); + } + + SetRefreshing(IsRefreshing()); +} + + +//----------------------------------------------------------------------------- +// Purpose: starts the servers refreshing +//----------------------------------------------------------------------------- +void CBaseGamesPage::StartRefresh() +{ + if ( !steamapicontext->SteamMatchmakingServers() ) + return; + + ClearServerList(); + MatchMakingKeyValuePair_t *pFilters; + int nFilters = GetServerFilters( &pFilters ); + + if ( m_hRequest ) + { + steamapicontext->SteamMatchmakingServers()->ReleaseRequest( m_hRequest ); + m_hRequest = NULL; + } + switch ( m_eMatchMakingType ) + { + case eFavoritesServer: + m_hRequest = steamapicontext->SteamMatchmakingServers()->RequestFavoritesServerList( GetFilterAppID().AppID(), &pFilters, nFilters, this ); + break; + case eHistoryServer: + m_hRequest = steamapicontext->SteamMatchmakingServers()->RequestHistoryServerList( GetFilterAppID().AppID(), &pFilters, nFilters, this ); + break; + case eInternetServer: + m_hRequest = steamapicontext->SteamMatchmakingServers()->RequestInternetServerList( GetFilterAppID().AppID(), &pFilters, nFilters, this ); + break; + case eSpectatorServer: + m_hRequest = steamapicontext->SteamMatchmakingServers()->RequestSpectatorServerList( GetFilterAppID().AppID(), &pFilters, nFilters, this ); + break; + case eFriendsServer: + m_hRequest = steamapicontext->SteamMatchmakingServers()->RequestFriendsServerList( GetFilterAppID().AppID(), &pFilters, nFilters, this ); + break; + case eLANServer: + m_hRequest = steamapicontext->SteamMatchmakingServers()->RequestLANServerList( GetFilterAppID().AppID(), this ); + break; + default: + Assert( !"Unknown server type" ); + break; + } + + SetRefreshing( true ); + + m_iServerRefreshCount = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseGamesPage::ClearQuickList( void ) +{ + m_pQuickList->DeleteAllItems(); + m_vecMapNamesFound.RemoveAll(); + + int iIndex = m_quicklistserverlist.First(); + + while ( iIndex != m_quicklistserverlist.InvalidIndex() ) + { + CQuickListMapServerList *vecMapServers = &m_quicklistserverlist[iIndex]; + + vecMapServers->RemoveAll(); + + iIndex = m_quicklistserverlist.Next( iIndex ); + } + + m_quicklistserverlist.RemoveAll(); +} + +//----------------------------------------------------------------------------- +// Purpose: Remove all the servers we currently have +//----------------------------------------------------------------------------- +void CBaseGamesPage::ClearServerList() +{ + m_mapServers.RemoveAll(); + m_mapServerIP.RemoveAll(); + m_pGameList->RemoveAll(); + m_iServersBlacklisted = 0; + + ClearQuickList(); +} + + +//----------------------------------------------------------------------------- +// Purpose: get a new list of servers from the backend +//----------------------------------------------------------------------------- +void CBaseGamesPage::GetNewServerList() +{ + StartRefresh(); +} + + +//----------------------------------------------------------------------------- +// Purpose: stops current refresh/GetNewServerList() +//----------------------------------------------------------------------------- +void CBaseGamesPage::StopRefresh() +{ + // clear update states + m_iServerRefreshCount = 0; + + // Stop the server list refreshing + if ( steamapicontext->SteamMatchmakingServers() ) + steamapicontext->SteamMatchmakingServers()->CancelQuery( m_hRequest ); + + // update UI + RefreshComplete( m_hRequest, eServerResponded ); + + // apply settings + ApplyGameFilters(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseGamesPage::RefreshComplete( HServerListRequest hRequest, EMatchMakingServerResponse response ) +{ + SelectQuickListServers(); +} + +//----------------------------------------------------------------------------- +// Purpose: returns true if the list is currently refreshing servers +//----------------------------------------------------------------------------- +bool CBaseGamesPage::IsRefreshing() +{ + return steamapicontext->SteamMatchmakingServers() && steamapicontext->SteamMatchmakingServers()->IsRefreshing( m_hRequest ); +} + +//----------------------------------------------------------------------------- +// Purpose: Activates the page, starts refresh +//----------------------------------------------------------------------------- +void CBaseGamesPage::OnPageShow() +{ + StartRefresh(); +} + +//----------------------------------------------------------------------------- +// Purpose: Called on page hide, stops any refresh +//----------------------------------------------------------------------------- +void CBaseGamesPage::OnPageHide() +{ + StopRefresh(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +vgui::Panel *CBaseGamesPage::GetActiveList( void ) +{ + if ( m_pQuickList->IsVisible() ) + return m_pQuickList; + + return m_pGameList; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CBaseGamesPage::GetSelectedServerID( KeyValues **pKV ) +{ + int serverID = -1; + if ( pKV ) + { + *pKV = NULL; + } + + if ( m_pQuickList->IsVisible() == true ) + { + if ( IsRefreshing() == true ) + return -1; + + if ( m_pQuickList->GetSelectedPanel() ) + { + CQuickListPanel *pQuickPanel = dynamic_cast<CQuickListPanel*>( m_pQuickList->GetSelectedPanel() ); + + if ( pQuickPanel ) + { + serverID = m_pGameList->GetItemUserData( pQuickPanel->GetListID() ); + if ( pKV ) + { + *pKV = m_pGameList->GetItem( pQuickPanel->GetListID() ); + } + } + } + } + else + { + if (!m_pGameList->GetSelectedItemsCount()) + return -1; + + // get the server + serverID = m_pGameList->GetItemUserData( m_pGameList->GetSelectedItem(0) ); + + if ( pKV ) + { + *pKV = m_pGameList->GetItem( m_pGameList->GetSelectedItem(0) ); + } + } + + return serverID; +} + +//----------------------------------------------------------------------------- +// Purpose: Dialog which warns the user about the server they're joining +//----------------------------------------------------------------------------- +class CDialogServerWarning : public vgui::Frame +{ + DECLARE_CLASS_SIMPLE( CDialogServerWarning, vgui::Frame ); +public: + CDialogServerWarning(vgui::Panel *parent, IGameList *gameList, int serverID ); + + virtual void ApplySchemeSettings( vgui::IScheme *pScheme ); + virtual void OnCommand(const char *command); + + MESSAGE_FUNC_PTR_INT( OnButtonToggled, "ButtonToggled", panel, state ); + +private: + IGameList *m_pGameList; + int m_iServerID; + vgui::CheckButton *m_pDontShowThisAgainCheckButton; +}; + +//----------------------------------------------------------------------------- +// Purpose: Constructor +// Input : *gameList - game list to add specified server to +//----------------------------------------------------------------------------- +CDialogServerWarning::CDialogServerWarning(vgui::Panel *parent, IGameList *gameList, int serverID ) : Frame(parent, "DialogServerWarning") +{ + m_pGameList = gameList; + m_iServerID = serverID; + + m_pDontShowThisAgainCheckButton = new CheckButton(this, "DontShowThisAgainCheckbutton", ""); + + SetDeleteSelfOnClose(true); + SetSizeable( false ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CDialogServerWarning::ApplySchemeSettings( IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + LoadControlSettings("Servers/DialogServerWarning.res"); +} + +//----------------------------------------------------------------------------- +// Purpose: button command handler +//----------------------------------------------------------------------------- +void CDialogServerWarning::OnCommand(const char *command) +{ + if ( Q_stricmp(command, "OK") == 0 ) + { + // mark ourselves to be closed + PostMessage(this, new KeyValues("Close")); + + // join the game + ServerBrowserDialog().JoinGame( m_pGameList, m_iServerID ); + } + else + { + BaseClass::OnCommand(command); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Handles filter dropdown being toggled +//----------------------------------------------------------------------------- +void CDialogServerWarning::OnButtonToggled(Panel *panel, int state) +{ + ConVarRef sb_dontshow_maxplayer_warning( "sb_dontshow_maxplayer_warning", true ); + if ( sb_dontshow_maxplayer_warning.IsValid() ) + { + sb_dontshow_maxplayer_warning.SetValue( state ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: initiates server connection +//----------------------------------------------------------------------------- +void CBaseGamesPage::OnBeginConnect() +{ + KeyValues *pKV = NULL; + int serverID = GetSelectedServerID( &pKV ); + + if ( serverID == -1 ) + return; + + // Stop the current refresh + StopRefresh(); + + ConVarRef sb_dontshow_maxplayer_warning( "sb_dontshow_maxplayer_warning", true ); + if ( sb_dontshow_maxplayer_warning.IsValid() ) + { + // If the server is above the suggested maxplayers, warn the player + int iMaxP = sb_mod_suggested_maxplayers.GetInt(); + if ( iMaxP && pKV && !sb_dontshow_maxplayer_warning.GetBool() ) + { + int iMaxCount = pKV->GetInt( "MaxPlayerCount", 0 ); + if ( iMaxCount > iMaxP ) + { + CDialogServerWarning *dlg = vgui::SETUP_PANEL( new CDialogServerWarning( this, this, serverID ) ); + dlg->MoveToCenterOfScreen(); + dlg->DoModal(); + + wchar_t wszWarning[512]; + wchar_t wszServerMaxPlayers[12]; + wchar_t wszDesignedMaxPlayers[12]; + wchar_t wszGameName[256]; + _snwprintf( wszServerMaxPlayers, Q_ARRAYSIZE(wszServerMaxPlayers), L"%d", iMaxCount ); + _snwprintf( wszDesignedMaxPlayers, Q_ARRAYSIZE(wszDesignedMaxPlayers), L"%d", iMaxP ); + Q_UTF8ToUnicode( ModList().GetModNameForModDir( m_iLimitToAppID ), wszGameName, Q_ARRAYSIZE(wszGameName) ); + g_pVGuiLocalize->ConstructString( wszWarning, sizeof( wszWarning ), g_pVGuiLocalize->Find( "#ServerBrowser_ServerWarning_MaxPlayers"), 4, wszServerMaxPlayers, wszGameName, wszDesignedMaxPlayers, wszDesignedMaxPlayers ); + dlg->SetDialogVariable( "warning", wszWarning ); + + return; + } + } + } + + // join the game + ServerBrowserDialog().JoinGame(this, serverID); +} + +//----------------------------------------------------------------------------- +// Purpose: Displays the current game info without connecting +//----------------------------------------------------------------------------- +void CBaseGamesPage::OnViewGameInfo() +{ + int serverID = GetSelectedServerID(); + + if ( serverID == -1 ) + return; + + // Stop the current refresh + StopRefresh(); + + // join the game + ServerBrowserDialog().OpenGameInfoDialog(this, serverID); +} + +//----------------------------------------------------------------------------- +// Purpose: Return code to use for tracking how people are connecting to servers +//----------------------------------------------------------------------------- +const char *CBaseGamesPage::GetConnectCode() +{ + // Determine code to use, for the "connect" command. + // + // E.g.: "connect serverbrowser" (This command primarily exists so i can grep the code....) + + const char *pszConnectCode = "serverbrowser"; + switch ( m_eMatchMakingType ) + { + default: + AssertMsg1( false, "Unknown matchmaking type %d", m_eMatchMakingType ); + break; + + case eInternetServer: + pszConnectCode = "serverbrowser_internet"; + break; + case eLANServer: + pszConnectCode = "serverbrowser_lan"; + break; + case eFriendsServer: + pszConnectCode = "serverbrowser_friends"; + break; + case eFavoritesServer: + pszConnectCode = "serverbrowser_favorites"; + break; + case eHistoryServer: + pszConnectCode = "serverbrowser_history"; + break; + case eSpectatorServer: + pszConnectCode = "serverbrowser_spectator"; + break; + }; + + return pszConnectCode; +} + + +//----------------------------------------------------------------------------- +// Purpose: Refresh if our favorites list changed +//----------------------------------------------------------------------------- +void CBaseGamesPage::OnFavoritesMsg( FavoritesListChanged_t *pFavListChanged ) +{ + if ( !pFavListChanged->m_nIP ) // a zero for IP means the whole list was reloaded and we need to reload + { + switch ( m_eMatchMakingType ) + { + case eInternetServer: + case eLANServer: + case eSpectatorServer: + case eFriendsServer: + return; + case eFavoritesServer: + case eHistoryServer: + // check containing property sheet to see if the page is visible. + // if not, don't bother initiating a server list grab right now - + // it will happen when the dialog is activated later. + if ( reinterpret_cast< PropertySheet* >( GetParent() )->GetActivePage() == this && + GetParent()->IsVisible() && ServerBrowserDialog().IsVisible() ) + { + GetNewServerList(); + } + return; + default: + Assert( !"unknown matchmaking type" ); + } + return; + } + + switch ( m_eMatchMakingType ) + { + case eInternetServer: + case eLANServer: + case eSpectatorServer: + case eFriendsServer: + break; + case eFavoritesServer: + case eHistoryServer: + { + int iIPServer = m_mapServerIP.Find( netadr_t( pFavListChanged->m_nIP, pFavListChanged->m_nConnPort ) ); + if ( iIPServer == m_mapServerIP.InvalidIndex() ) + { + if ( pFavListChanged->m_bAdd ) + { + if ( steamapicontext->SteamMatchmakingServers() ) + steamapicontext->SteamMatchmakingServers()->PingServer( pFavListChanged->m_nIP, pFavListChanged->m_nQueryPort, this ); + } + // ignore deletes of fav's we didn't have + } + else + { + if ( pFavListChanged->m_bAdd ) + { + if ( m_mapServerIP[ iIPServer ] > 0 ) + ServerResponded( m_hRequest, m_mapServerIP[ iIPServer ] ); + } + else + { + int iServer = m_mapServers.Find( m_mapServerIP[ iIPServer ] ); + serverdisplay_t &server = m_mapServers[ iServer ]; + RemoveServer( server ); + } + } + } + break; + default: + Assert( !"unknown matchmaking type" ); + }; +} + +void CCheckBoxWithStatus::OnCursorEntered() +{ + ServerBrowserDialog().UpdateStatusText("#ServerBrowser_QuickListExplanation"); +} + +void CCheckBoxWithStatus::OnCursorExited() +{ + ServerBrowserDialog().UpdateStatusText(""); +} diff --git a/serverbrowser/BaseGamesPage.h b/serverbrowser/BaseGamesPage.h new file mode 100644 index 0000000..2d69b1d --- /dev/null +++ b/serverbrowser/BaseGamesPage.h @@ -0,0 +1,325 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef BASEGAMESPAGE_H +#define BASEGAMESPAGE_H +#ifdef _WIN32 +#pragma once +#endif + +#include "tier1/utldict.h" + +class CBaseGamesPage; + +//----------------------------------------------------------------------------- +// Purpose: Acts like a regular ListPanel but forwards enter key presses +// to its outer control. +//----------------------------------------------------------------------------- +class CGameListPanel : public vgui::ListPanel +{ +public: + DECLARE_CLASS_SIMPLE( CGameListPanel, vgui::ListPanel ); + + CGameListPanel( CBaseGamesPage *pOuter, const char *pName ); + + virtual void OnKeyCodePressed(vgui::KeyCode code); + +private: + CBaseGamesPage *m_pOuter; +}; + +class CQuickListMapServerList : public CUtlVector< int > +{ +public: + CQuickListMapServerList() : CUtlVector< int >( 1, 0 ) + { + } + + CQuickListMapServerList( const CQuickListMapServerList& src ) + { + CopyArray( src.Base(), src.Count() ); + } + + CQuickListMapServerList &operator=( const CQuickListMapServerList &src ) + { + CopyArray( src.Base(), src.Count() ); + return *this; + } +}; + + +class CCheckBoxWithStatus : public vgui::CheckButton +{ +public: + DECLARE_CLASS_SIMPLE( CCheckBoxWithStatus, vgui::CheckButton ); + + CCheckBoxWithStatus(Panel *parent, const char *panelName, const char *text) : vgui::CheckButton( parent, panelName, text ) + { + } + + virtual void OnCursorEntered(); + virtual void OnCursorExited(); +}; + +struct servermaps_t +{ + const char *pOriginalName; + const char *pFriendlyName; + int iPanelIndex; + bool bOnDisk; +}; + +struct gametypes_t +{ + const char *pPrefix; + const char *pGametypeName; +}; + +//----------------------------------------------------------------------------- +// Purpose: Base property page for all the games lists (internet/favorites/lan/etc.) +//----------------------------------------------------------------------------- +class CBaseGamesPage : public vgui::PropertyPage, public IGameList, public ISteamMatchmakingServerListResponse, public ISteamMatchmakingPingResponse +{ + DECLARE_CLASS_SIMPLE( CBaseGamesPage, vgui::PropertyPage ); + +public: + enum EPageType + { + eInternetServer, + eLANServer, + eFriendsServer, + eFavoritesServer, + eHistoryServer, + eSpectatorServer + }; + + // Column indices + enum + { + k_nColumn_Password = 0, + k_nColumn_Secure = 1, + k_nColumn_Replay = 2, + k_nColumn_Name = 3, + k_nColumn_IPAddr = 4, + k_nColumn_GameDesc = 5, + k_nColumn_Players = 6, + k_nColumn_Bots = 7, + k_nColumn_Map = 8, + k_nColumn_Ping = 9, + }; + + CBaseGamesPage( vgui::Panel *parent, const char *name, EPageType eType, const char *pCustomResFilename=NULL); + ~CBaseGamesPage(); + + virtual void PerformLayout(); + virtual void ApplySchemeSettings(vgui::IScheme *pScheme); + + // gets information about specified server + virtual gameserveritem_t *GetServer(unsigned int serverID); + virtual const char *GetConnectCode(); + + uint32 GetServerFilters( MatchMakingKeyValuePair_t **pFilters ); + + virtual void SetRefreshing(bool state); + + // loads filter settings from disk + virtual void LoadFilterSettings(); + + // Called by CGameList when the enter key is pressed. + // This is overridden in the add server dialog - since there is no Connect button, the message + // never gets handled, but we want to add a server when they dbl-click or press enter. + virtual bool OnGameListEnterPressed(); + + int GetSelectedItemsCount(); + + // adds a server to the favorites + MESSAGE_FUNC( OnAddToFavorites, "AddToFavorites" ); + MESSAGE_FUNC( OnAddToBlacklist, "AddToBlacklist" ); + + virtual void StartRefresh(); + + virtual void UpdateDerivedLayouts( void ); + + void PrepareQuickListMap( const char *pMapName, int iListID ); + void SelectQuickListServers( void ); + vgui::Panel *GetActiveList( void ); + virtual bool IsQuickListButtonChecked() + { + return m_pQuickListCheckButton ? m_pQuickListCheckButton->IsSelected() : false; + } + + STEAM_CALLBACK( CBaseGamesPage, OnFavoritesMsg, FavoritesListChanged_t, m_CallbackFavoritesMsg ); + + // applies games filters to current list + void ApplyGameFilters(); + + void OnLoadingStarted() + { + StopRefresh(); + } + +protected: + virtual void OnCommand(const char *command); + virtual void OnKeyCodePressed(vgui::KeyCode code); + virtual int GetRegionCodeToFilter() { return 255; } + + MESSAGE_FUNC( OnItemSelected, "ItemSelected" ); + + // updates server count UI + void UpdateStatus(); + + // ISteamMatchmakingServerListResponse callbacks + virtual void ServerResponded( HServerListRequest hReq, int iServer ); + virtual void ServerResponded( int iServer, gameserveritem_t *pServerItem ); + virtual void ServerFailedToRespond( HServerListRequest hReq, int iServer ); + virtual void RefreshComplete( HServerListRequest hReq, EMatchMakingServerResponse response ) = 0; + + // ISteamMatchmakingPingResponse callbacks + virtual void ServerResponded( gameserveritem_t &server ); + virtual void ServerFailedToRespond() {} + + // Removes server from list + void RemoveServer( serverdisplay_t &server ); + + virtual bool BShowServer( serverdisplay_t &server ) { return server.m_bDoNotRefresh; } + void ClearServerList(); + + // filtering methods + // returns true if filters passed; false if failed + virtual bool CheckPrimaryFilters( gameserveritem_t &server); + virtual bool CheckSecondaryFilters( gameserveritem_t &server ); + virtual bool CheckTagFilter( gameserveritem_t &server ) { return true; } + virtual bool CheckWorkshopFilter( gameserveritem_t &server ) { return true; } + virtual int GetInvalidServerListID(); + + virtual void OnSaveFilter(KeyValues *filter); + virtual void OnLoadFilter(KeyValues *filter); + virtual void UpdateFilterSettings(); + + // whether filter settings limit which master server to query + CGameID &GetFilterAppID() { return m_iLimitToAppID; } + + virtual void GetNewServerList(); + virtual void StopRefresh(); + virtual bool IsRefreshing(); + virtual void OnPageShow(); + virtual void OnPageHide(); + + // called when Connect button is pressed + MESSAGE_FUNC( OnBeginConnect, "ConnectToServer" ); + // called to look at game info + MESSAGE_FUNC( OnViewGameInfo, "ViewGameInfo" ); + // refreshes a single server + MESSAGE_FUNC_INT( OnRefreshServer, "RefreshServer", serverID ); + + // If true, then we automatically select the first item that comes into the games list. + bool m_bAutoSelectFirstItemInGameList; + + CGameListPanel *m_pGameList; + vgui::PanelListPanel *m_pQuickList; + + vgui::ComboBox *m_pLocationFilter; + + // command buttons + vgui::Button *m_pConnect; + vgui::Button *m_pRefreshAll; + vgui::Button *m_pRefreshQuick; + vgui::Button *m_pAddServer; + vgui::Button *m_pAddCurrentServer; + vgui::Button *m_pAddToFavoritesButton; + vgui::ToggleButton *m_pFilter; + + CUtlMap<uint64, int> m_mapGamesFilterItem; + CUtlMap<int, serverdisplay_t> m_mapServers; + CUtlMap<netadr_t, int> m_mapServerIP; + CUtlVector<MatchMakingKeyValuePair_t> m_vecServerFilters; + CUtlDict< CQuickListMapServerList, int > m_quicklistserverlist; + int m_iServerRefreshCount; + CUtlVector< servermaps_t > m_vecMapNamesFound; + + + EPageType m_eMatchMakingType; + HServerListRequest m_hRequest; + + int GetSelectedServerID( KeyValues **pKV = NULL ); + + void ClearQuickList( void ); + + bool TagsExclude( void ); + + enum eWorkshopMode { + // These correspond to the dropdown indices + eWorkshop_None = 0, + eWorkshop_WorkshopOnly = 1, + eWorkshop_SubscribedOnly = 2 + }; + eWorkshopMode WorkshopMode(); + + void HideReplayFilter( void ); + +protected: + virtual void CreateFilters(); + virtual void UpdateGameFilter(); + + MESSAGE_FUNC_PTR_CHARPTR( OnTextChanged, "TextChanged", panel, text ); + MESSAGE_FUNC_PTR_INT( OnButtonToggled, "ButtonToggled", panel, state ); + + void UpdateFilterAndQuickListVisibility(); + bool BFiltersVisible() { return m_bFiltersVisible; } + +private: + void RequestServersResponse( int iServer, EMatchMakingServerResponse response, bool bLastServer ); // callback for matchmaking interface + + void RecalculateFilterString(); + + void SetQuickListEnabled( bool bEnabled ); + void SetFiltersVisible( bool bVisible ); + + // If set, it uses the specified resfile name instead of its default one. + const char *m_pCustomResFilename; + + // filter controls + vgui::ComboBox *m_pGameFilter; + vgui::TextEntry *m_pMapFilter; + vgui::TextEntry *m_pMaxPlayerFilter; + vgui::ComboBox *m_pPingFilter; + vgui::ComboBox *m_pSecureFilter; + vgui::ComboBox *m_pTagsIncludeFilter; + vgui::ComboBox *m_pWorkshopFilter; + vgui::CheckButton *m_pNoFullServersFilterCheck; + vgui::CheckButton *m_pNoEmptyServersFilterCheck; + vgui::CheckButton *m_pNoPasswordFilterCheck; + CCheckBoxWithStatus *m_pQuickListCheckButton; + vgui::Label *m_pFilterString; + char m_szComboAllText[64]; + vgui::CheckButton *m_pReplayFilterCheck; + + KeyValues *m_pFilters; // base filter data + bool m_bFiltersVisible; // true if filter section is currently visible + vgui::HFont m_hFont; + + int m_nImageIndexPassword; + int m_nImageIndexSecure; + int m_nImageIndexSecureVacBanned; + int m_nImageIndexReplay; + + // filter data + char m_szGameFilter[32]; + char m_szMapFilter[32]; + int m_iMaxPlayerFilter; + int m_iPingFilter; + bool m_bFilterNoFullServers; + bool m_bFilterNoEmptyServers; + bool m_bFilterNoPasswordedServers; + int m_iSecureFilter; + int m_iServersBlacklisted; + bool m_bFilterReplayServers; + + CGameID m_iLimitToAppID; +}; + +#endif // BASEGAMESPAGE_H diff --git a/serverbrowser/BlacklistedServers.cpp b/serverbrowser/BlacklistedServers.cpp new file mode 100644 index 0000000..c310f39 --- /dev/null +++ b/serverbrowser/BlacklistedServers.cpp @@ -0,0 +1,500 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include "pch_serverbrowser.h" + +using namespace vgui; + +ConVar sb_showblacklists( "sb_showblacklists", "0", FCVAR_NONE, "If set to 1, blacklist rules will be printed to the console as they're applied." ); + +//----------------------------------------------------------------------------- +// Purpose: Server name comparison function +//----------------------------------------------------------------------------- +int __cdecl BlacklistedServerNameCompare(ListPanel *pPanel, const ListPanelItem &p1, const ListPanelItem &p2) +{ + blacklisted_server_t *pSvr1 = ServerBrowserDialog().GetBlacklistPage()->GetBlacklistedServer( p1.userData ); + blacklisted_server_t *pSvr2 = ServerBrowserDialog().GetBlacklistPage()->GetBlacklistedServer( p2.userData ); + + if ( !pSvr1 && pSvr2 ) + return -1; + if ( !pSvr2 && pSvr1 ) + return 1; + if ( !pSvr1 && !pSvr2 ) + return 0; + + return Q_stricmp( pSvr1->m_szServerName, pSvr2->m_szServerName ); +} + +//----------------------------------------------------------------------------- +// Purpose: list column sort function +//----------------------------------------------------------------------------- +int __cdecl BlacklistedIPAddressCompare(ListPanel *pPanel, const ListPanelItem &p1, const ListPanelItem &p2) +{ + blacklisted_server_t *pSvr1 = ServerBrowserDialog().GetBlacklistPage()->GetBlacklistedServer( p1.userData ); + blacklisted_server_t *pSvr2 = ServerBrowserDialog().GetBlacklistPage()->GetBlacklistedServer( p2.userData ); + + if ( !pSvr1 && pSvr2 ) + return -1; + if ( !pSvr2 && pSvr1 ) + return 1; + if ( !pSvr1 && !pSvr2 ) + return 0; + + if ( pSvr1->m_NetAdr < pSvr2->m_NetAdr ) + return -1; + else if ( pSvr2->m_NetAdr < pSvr1->m_NetAdr ) + return 1; + + return 0; +} + +//----------------------------------------------------------------------------- +// Purpose: Player number comparison function +//----------------------------------------------------------------------------- +int __cdecl BlacklistedAtCompare(ListPanel *pPanel, const ListPanelItem &p1, const ListPanelItem &p2) +{ + blacklisted_server_t *pSvr1 = ServerBrowserDialog().GetBlacklistPage()->GetBlacklistedServer( p1.userData ); + blacklisted_server_t *pSvr2 = ServerBrowserDialog().GetBlacklistPage()->GetBlacklistedServer( p2.userData ); + + if ( !pSvr1 && pSvr2 ) + return -1; + if ( !pSvr2 && pSvr1 ) + return 1; + if ( !pSvr1 && !pSvr2 ) + return 0; + + if ( pSvr1->m_ulTimeBlacklistedAt > pSvr2->m_ulTimeBlacklistedAt ) + return -1; + if ( pSvr1->m_ulTimeBlacklistedAt < pSvr2->m_ulTimeBlacklistedAt ) + return 1; + + return 0; +} + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +CBlacklistedServers::CBlacklistedServers( vgui::Panel *parent ) : + vgui::PropertyPage( parent, "BlacklistedGames" ) +{ + SetSize( 624, 278 ); + + m_pAddServer = new Button(this, "AddServerButton", "#ServerBrowser_AddServer"); + m_pAddCurrentServer = new Button(this, "AddCurrentServerButton", "#ServerBrowser_AddCurrentServer"); + m_pGameList = vgui::SETUP_PANEL( new vgui::ListPanel(this, "gamelist") ); + m_pGameList->SetAllowUserModificationOfColumns(true); + + // Add the column headers + m_pGameList->AddColumnHeader(0, "Name", "#ServerBrowser_BlacklistedServers", 50, ListPanel::COLUMN_RESIZEWITHWINDOW | ListPanel::COLUMN_UNHIDABLE); + m_pGameList->AddColumnHeader(1, "IPAddr", "#ServerBrowser_IPAddress", 64, ListPanel::COLUMN_HIDDEN); + m_pGameList->AddColumnHeader(2, "BlacklistedAt", "#ServerBrowser_BlacklistedDate", 100); + + //m_pGameList->SetColumnHeaderTooltip(0, "#ServerBrowser_PasswordColumn_Tooltip"); + + // setup fast sort functions + m_pGameList->SetSortFunc(0, BlacklistedServerNameCompare); + m_pGameList->SetSortFunc(1, BlacklistedIPAddressCompare); + m_pGameList->SetSortFunc(2, BlacklistedAtCompare); + + // Sort by name by default + m_pGameList->SetSortColumn(0); + + m_blackList.Reset(); + m_blackListTimestamp = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +CBlacklistedServers::~CBlacklistedServers() +{ + ClearServerList(); +} + +//----------------------------------------------------------------------------- +// Purpose: loads the initial blacklist from disk +//----------------------------------------------------------------------------- +void CBlacklistedServers::LoadBlacklistedList() +{ + m_pGameList->SetEmptyListText("#ServerBrowser_NoBlacklistedServers"); + + ClearServerList(); + m_blackList.Reset(); + + int count = m_blackList.LoadServersFromFile( BLACKLIST_DEFAULT_SAVE_FILE, false ); + + m_blackListTimestamp = g_pFullFileSystem->GetFileTime( BLACKLIST_DEFAULT_SAVE_FILE ); + + for( int i=0; i<count; ++i ) + { + UpdateBlacklistUI( m_blackList.GetServer(i) ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: adds all the servers inside the specified file to the blacklist +//----------------------------------------------------------------------------- +bool CBlacklistedServers::AddServersFromFile( const char *pszFilename, bool bResetTimes ) +{ + KeyValues *pKV = new KeyValues( "serverblacklist" ); + if ( !pKV->LoadFromFile( g_pFullFileSystem, pszFilename, "GAME" ) ) + return false; + + for ( KeyValues *pData = pKV->GetFirstSubKey(); pData != NULL; pData = pData->GetNextKey() ) + { + const char *pszName = pData->GetString( "name" ); + + uint32 ulDate = pData->GetInt( "date" ); + if ( bResetTimes ) + { + time_t today; + time( &today ); + ulDate = today; + } + + const char *pszNetAddr = pData->GetString( "addr" ); + + if ( pszNetAddr && pszNetAddr[0] && pszName && pszName[0] ) + { + blacklisted_server_t *blackServer = m_blackList.AddServer( pszName, pszNetAddr, ulDate ); + + UpdateBlacklistUI( blackServer ); + } + } + + // write out blacklist to preserve changes + SaveBlacklistedList(); + + pKV->deleteThis(); + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: save blacklist to disk +//----------------------------------------------------------------------------- +void CBlacklistedServers::SaveBlacklistedList() +{ + m_blackList.SaveToFile( BLACKLIST_DEFAULT_SAVE_FILE ); + m_blackListTimestamp = g_pFullFileSystem->GetFileTime( BLACKLIST_DEFAULT_SAVE_FILE ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBlacklistedServers::AddServer( gameserveritem_t &server ) +{ + blacklisted_server_t *blackServer = m_blackList.AddServer( server ); + + if ( !blackServer ) + { + return; + } + + SaveBlacklistedList(); + + UpdateBlacklistUI( blackServer ); + + if ( GameSupportsReplay() ) + { + // send command to propagate to the client so the client can send it on to the GC + char command[ 256 ]; + Q_snprintf( command, Q_ARRAYSIZE( command ), "rbgc %s\n", blackServer->m_NetAdr.ToString() ); + g_pRunGameEngine->AddTextCommand( command ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +blacklisted_server_t *CBlacklistedServers::GetBlacklistedServer( int iServerID ) +{ + return m_blackList.GetServer( iServerID ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CBlacklistedServers::IsServerBlacklisted( gameserveritem_t &server ) +{ + // if something has changed the blacklist file, reload it + if ( g_pFullFileSystem->GetFileTime( BLACKLIST_DEFAULT_SAVE_FILE ) != m_blackListTimestamp ) + { + LoadBlacklistedList(); + } + + return m_blackList.IsServerBlacklisted( server ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBlacklistedServers::UpdateBlacklistUI( blacklisted_server_t *blackServer ) +{ + if ( !blackServer ) + return; + + KeyValues *kv; + int iItemId = m_pGameList->GetItemIDFromUserData( blackServer->m_nServerID ); + if ( m_pGameList->IsValidItemID( iItemId ) ) + { + // we're updating an existing entry + kv = m_pGameList->GetItem( iItemId ); + m_pGameList->SetUserData( iItemId, blackServer->m_nServerID ); + } + else + { + // new entry + kv = new KeyValues("Server"); + } + + kv->SetString( "name", blackServer->m_szServerName ); + + // construct a time string for blacklisted time + struct tm *now; + now = localtime( (time_t*)&blackServer->m_ulTimeBlacklistedAt ); + if ( now ) + { + char buf[64]; + strftime(buf, sizeof(buf), "%a %d %b %I:%M%p", now); + Q_strlower(buf + strlen(buf) - 4); + kv->SetString("BlacklistedAt", buf); + } + + kv->SetString( "IPAddr", blackServer->m_NetAdr.ToString() ); + + if ( !m_pGameList->IsValidItemID( iItemId ) ) + { + // new server, add to list + iItemId = m_pGameList->AddItem(kv, blackServer->m_nServerID, false, false); + kv->deleteThis(); + } + else + { + // tell the list that we've changed the data + m_pGameList->ApplyItemChanges( iItemId ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBlacklistedServers::ApplySchemeSettings(vgui::IScheme *pScheme) +{ + BaseClass::ApplySchemeSettings(pScheme); + + const char *pPathID = "PLATFORM"; + const char *pszFileName = "servers/BlacklistedServersPage.res"; + if ( g_pFullFileSystem->FileExists( pszFileName, "MOD" ) ) + { + pPathID = "MOD"; + } + LoadControlSettings( pszFileName, pPathID ); + + vgui::HFont hFont = pScheme->GetFont( "ListSmall", IsProportional() ); + if ( !hFont ) + { + hFont = pScheme->GetFont( "DefaultSmall", IsProportional() ); + } + m_pGameList->SetFont( hFont ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBlacklistedServers::OnPageShow( void ) +{ + // reload list since client may have changed it + LoadBlacklistedList(); + + m_pGameList->SetEmptyListText("#ServerBrowser_NoBlacklistedServers"); + m_pGameList->SortList(); + + BaseClass::OnPageShow(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CBlacklistedServers::GetSelectedServerID( void ) +{ + int serverID = -1; + if ( m_pGameList->GetSelectedItemsCount() ) + { + serverID = m_pGameList->GetItemUserData( m_pGameList->GetSelectedItem(0) ); + } + + return serverID; +} + +//----------------------------------------------------------------------------- +// Purpose: opens context menu (user right clicked on a server) +//----------------------------------------------------------------------------- +void CBlacklistedServers::OnOpenContextMenu(int itemID) +{ + CServerContextMenu *menu = ServerBrowserDialog().GetContextMenu( m_pGameList ); + + // get the server + int serverID = GetSelectedServerID(); + + menu->ShowMenu( this,(uint32)-1, false, false, false, false ); + if ( serverID != -1 ) + { + menu->AddMenuItem("RemoveServer", "#ServerBrowser_RemoveServerFromBlacklist", new KeyValues("RemoveFromBlacklist"), this); + } + + menu->AddMenuItem("AddServerByName", "#ServerBrowser_AddServerByIP", new KeyValues("AddServerByName"), this); +} + + +//----------------------------------------------------------------------------- +// Purpose: Adds a server by IP address +//----------------------------------------------------------------------------- +void CBlacklistedServers::OnAddServerByName() +{ + // open the add server dialog + CDialogAddBlacklistedServer *dlg = new CDialogAddBlacklistedServer( &ServerBrowserDialog(), NULL ); + dlg->MoveToCenterOfScreen(); + dlg->DoModal(); +} + +//----------------------------------------------------------------------------- +// Purpose: removes a server from the blacklist +//----------------------------------------------------------------------------- +void CBlacklistedServers::OnRemoveFromBlacklist() +{ + // iterate the selection + for ( int iGame = (m_pGameList->GetSelectedItemsCount() - 1); iGame >= 0; iGame-- ) + { + int itemID = m_pGameList->GetSelectedItem( iGame ); + int serverID = m_pGameList->GetItemData( itemID )->userData; + + m_pGameList->RemoveItem( itemID ); + m_blackList.RemoveServer( serverID ); + } + + InvalidateLayout(); + Repaint(); + + ServerBrowserDialog().BlacklistsChanged(); + + SaveBlacklistedList(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBlacklistedServers::ClearServerList( void ) +{ + m_pGameList->RemoveAll(); + m_blackList.Reset(); + m_blackListTimestamp = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: Adds the currently connected server to the list +//----------------------------------------------------------------------------- +void CBlacklistedServers::OnAddCurrentServer() +{ + gameserveritem_t *pConnected = ServerBrowserDialog().GetCurrentConnectedServer(); + + if ( pConnected ) + { + blacklisted_server_t *blackServer = m_blackList.AddServer( *pConnected ); + + if ( !blackServer ) + { + return; + } + + UpdateBlacklistUI( blackServer ); + + ServerBrowserDialog().BlacklistsChanged(); + + SaveBlacklistedList(); + + if ( GameSupportsReplay() ) + { + // send command to propagate to the client so the client can send it on to the GC + char command[ 256 ]; + Q_snprintf( command, Q_ARRAYSIZE( command ), "rbgc %s\n", pConnected->m_NetAdr.GetConnectionAddressString() ); + g_pRunGameEngine->AddTextCommand( command ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBlacklistedServers::OnImportBlacklist() +{ + if ( m_hImportDialog.Get() ) + { + m_hImportDialog.Get()->MarkForDeletion(); + } + + m_hImportDialog = new FileOpenDialog( this, "#ServerBrowser_ImportBlacklistTitle", true ); + if ( m_hImportDialog.Get() ) + { + m_hImportDialog->SetStartDirectory( "cfg/" ); + m_hImportDialog->AddFilter( "*.txt", "#ServerBrowser_BlacklistFiles", true ); + m_hImportDialog->DoModal( false ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBlacklistedServers::OnFileSelected( char const *fullpath ) +{ + AddServersFromFile( fullpath, true ); + + if ( m_hImportDialog.Get() ) + { + m_hImportDialog.Get()->MarkForDeletion(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Parse posted messages +// +//----------------------------------------------------------------------------- +void CBlacklistedServers::OnCommand(const char *command) +{ + if (!Q_stricmp(command, "AddServerByName")) + { + OnAddServerByName(); + } + else if (!Q_stricmp(command, "AddCurrentServer" )) + { + OnAddCurrentServer(); + } + else if (!Q_stricmp(command, "ImportBlacklist" )) + { + OnImportBlacklist(); + } + else + { + BaseClass::OnCommand(command); + } +} + +//----------------------------------------------------------------------------- +// Purpose: enables adding server +//----------------------------------------------------------------------------- +void CBlacklistedServers::OnConnectToGame() +{ + m_pAddCurrentServer->SetEnabled( true ); +} + +//----------------------------------------------------------------------------- +// Purpose: disables adding current server +//----------------------------------------------------------------------------- +void CBlacklistedServers::OnDisconnectFromGame( void ) +{ + m_pAddCurrentServer->SetEnabled( false ); +} diff --git a/serverbrowser/BlacklistedServers.h b/serverbrowser/BlacklistedServers.h new file mode 100644 index 0000000..ce8b9b5 --- /dev/null +++ b/serverbrowser/BlacklistedServers.h @@ -0,0 +1,68 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef BLACKLISTEDSERVERS_H +#define BLACKLISTEDSERVERS_H +#ifdef _WIN32 +#pragma once +#endif + +#include "ServerBrowser/blacklisted_server_manager.h" + +//----------------------------------------------------------------------------- +// Purpose: Blacklisted servers list +//----------------------------------------------------------------------------- +class CBlacklistedServers : public vgui::PropertyPage +{ + DECLARE_CLASS_SIMPLE( CBlacklistedServers, vgui::PropertyPage ); + +public: + CBlacklistedServers(vgui::Panel *parent); + ~CBlacklistedServers(); + + // blacklist list, loads/saves from file + void LoadBlacklistedList(); + void SaveBlacklistedList(); + void AddServer(gameserveritem_t &server); + + virtual void ApplySchemeSettings(vgui::IScheme *pScheme); + + // passed from main server browser window instead of messages + void OnConnectToGame(); + void OnDisconnectFromGame( void ); + + blacklisted_server_t *GetBlacklistedServer( int iServerID ); + bool IsServerBlacklisted(gameserveritem_t &server); + +private: + // context menu message handlers + MESSAGE_FUNC( OnPageShow, "PageShow" ); + MESSAGE_FUNC_INT( OnOpenContextMenu, "OpenContextMenu", itemID ); + MESSAGE_FUNC( OnAddServerByName, "AddServerByName" ); + MESSAGE_FUNC( OnRemoveFromBlacklist, "RemoveFromBlacklist" ); + MESSAGE_FUNC_CHARPTR( OnFileSelected, "FileSelected", fullpath ); + + void ClearServerList( void ); + void OnAddCurrentServer( void ); + void OnImportBlacklist( void ); + void OnCommand(const char *command); + void UpdateBlacklistUI( blacklisted_server_t *blackServer ); + int GetSelectedServerID( void ); + bool AddServersFromFile( const char *pszFilename, bool bResetTimes ); + +private: + vgui::Button *m_pAddServer; + vgui::Button *m_pAddCurrentServer; + vgui::ListPanel *m_pGameList; + vgui::DHANDLE< vgui::FileOpenDialog > m_hImportDialog; + + CBlacklistedServerManager m_blackList; + long m_blackListTimestamp; +}; + + +#endif // BLACKLISTEDSERVERS_H diff --git a/serverbrowser/CustomGames.cpp b/serverbrowser/CustomGames.cpp new file mode 100644 index 0000000..26ebb55 --- /dev/null +++ b/serverbrowser/CustomGames.cpp @@ -0,0 +1,448 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#include "pch_serverbrowser.h" +#include <vgui_controls/HTML.h> +#include <vgui_controls/MessageDialog.h> + +using namespace vgui; + +#define NUM_COMMON_TAGS 20 + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +TagMenuButton::TagMenuButton(Panel *parent, const char *panelName, const char *text) : BaseClass(parent,panelName,text) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void TagMenuButton::OnShowMenu( vgui::Menu *menu ) +{ + PostActionSignal(new KeyValues("TagMenuButtonOpened")); + BaseClass::OnShowMenu(menu); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CCustomServerInfoURLQuery : public vgui::QueryBox +{ + DECLARE_CLASS_SIMPLE( CCustomServerInfoURLQuery, vgui::QueryBox ); +public: + CCustomServerInfoURLQuery(const char *title, const char *queryText,vgui::Panel *parent) : BaseClass( title, queryText, parent ) + { + SetOKButtonText( "#ServerBrowser_CustomServerURLButton" ); + } +}; + +DECLARE_BUILD_FACTORY( TagInfoLabel ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +TagInfoLabel::TagInfoLabel(Panel *parent, const char *panelName) : BaseClass(parent,panelName, (const char *)NULL, NULL) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +TagInfoLabel::TagInfoLabel(Panel *parent, const char *panelName, const char *text, const char *pszURL) : BaseClass(parent,panelName,text,pszURL) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: If we were left clicked on, launch the URL +//----------------------------------------------------------------------------- +void TagInfoLabel::OnMousePressed(MouseCode code) +{ + if (code == MOUSE_LEFT) + { + if ( GetURL() ) + { + // Pop up the dialog with the url in it + CCustomServerInfoURLQuery *qb = new CCustomServerInfoURLQuery( "#ServerBrowser_CustomServerURLWarning", "#ServerBrowser_CustomServerURLOpen", this ); + if (qb != NULL) + { + qb->SetOKCommand( new KeyValues("DoOpenCustomServerInfoURL") ); + qb->AddActionSignalTarget(this); + qb->MoveToFront(); + qb->DoModal(); + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void TagInfoLabel::DoOpenCustomServerInfoURL( void ) +{ + if ( GetURL() ) + { + system()->ShellExecute("open", GetURL() ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +CCustomGames::CCustomGames(vgui::Panel *parent) : + BaseClass(parent, "CustomGames", eInternetServer ) +{ + m_pGameList->AddColumnHeader(10, "Tags", "#ServerBrowser_Tags", 200); + m_pGameList->SetSortFunc(10, TagsCompare); + + if ( !IsSteamGameServerBrowsingEnabled() ) + { + m_pGameList->SetEmptyListText("#ServerBrowser_OfflineMode"); + m_pConnect->SetEnabled( false ); + m_pRefreshAll->SetEnabled( false ); + m_pRefreshQuick->SetEnabled( false ); + m_pAddServer->SetEnabled( false ); + m_pFilter->SetEnabled( false ); + } + + m_szTagFilter[0] = 0; + + m_pTagFilter = new TextEntry(this, "TagFilter"); + m_pTagFilter->SetEnabled( false ); + m_pTagFilter->SetMaximumCharCount( MAX_TAG_CHARACTERS ); + + m_pAddTagList = new TagMenuButton( this, "AddTagList", "#ServerBrowser_AddCommonTags" ); + m_pTagListMenu = new Menu( m_pAddTagList, "TagList" ); + m_pAddTagList->SetMenu( m_pTagListMenu ); + m_pAddTagList->SetOpenDirection( Menu::UP ); + m_pAddTagList->SetEnabled( false ); +} + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +CCustomGames::~CCustomGames() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCustomGames::UpdateDerivedLayouts( void ) +{ + const char *pPathID = "PLATFORM"; + + KeyValues *pConditions = NULL; + if ( ServerBrowser().IsWorkshopEnabled() ) + { + pConditions = new KeyValues( "conditions" ); + if ( pConditions ) + { + KeyValues *pNewKey = new KeyValues( "if_workshop_enabled" ); + if ( pNewKey ) + { + pConditions->AddSubKey( pNewKey ); + } + } + } + + if ( m_pFilter->IsSelected() ) + { + if ( g_pFullFileSystem->FileExists( "servers/CustomGamesPage_Filters.res", "MOD" ) ) + { + pPathID = "MOD"; + } + + LoadControlSettings( "servers/CustomGamesPage_Filters.res", pPathID, NULL, pConditions ); + } + else + { + if ( g_pFullFileSystem->FileExists( "servers/CustomGamesPage.res", "MOD" ) ) + { + pPathID = "MOD"; + } + + LoadControlSettings( "servers/CustomGamesPage.res", pPathID, NULL, pConditions ); + } + + if ( pConditions ) + { + pConditions->deleteThis(); + } + + if ( !GameSupportsReplay() ) + { + HideReplayFilter(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCustomGames::OnLoadFilter(KeyValues *filter) +{ + BaseClass::OnLoadFilter( filter ); + + Q_strncpy(m_szTagFilter, filter->GetString("gametype"), sizeof(m_szTagFilter)); + + if ( m_pTagFilter ) + { + m_pTagFilter->SetText(m_szTagFilter); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CCustomGames::CheckTagFilter( gameserveritem_t &server ) +{ + bool bRetVal = true; + + // Custom games substring matches tags with the server's tags + int count = Q_strlen( m_szTagFilter ); + if ( count ) + { + CUtlVector<char*> TagList; + V_SplitString( m_szTagFilter, ",", TagList ); + for ( int i = 0; i < TagList.Count(); i++ ) + { + if ( ( Q_strnistr( server.m_szGameTags, TagList[i], MAX_TAG_CHARACTERS ) > 0 ) == TagsExclude() ) + { + bRetVal = false; + break; + } + } + + TagList.PurgeAndDeleteElements(); + } + + return bRetVal; +} + +//----------------------------------------------------------------------------- +// Purpose: Checks the workshop filtering setting, taking into account workshop filtering might be disabled +//----------------------------------------------------------------------------- +bool CCustomGames::CheckWorkshopFilter( gameserveritem_t &server ) +{ + eWorkshopMode workshopMode = WorkshopMode(); + const char szWorkshopPrefix[] = "workshop/"; + if ( workshopMode == eWorkshop_WorkshopOnly ) + { + return V_strncasecmp( server.m_szMap, szWorkshopPrefix, sizeof( szWorkshopPrefix ) - 1 ) == 0; + } + else if ( workshopMode == eWorkshop_SubscribedOnly ) + { + return ServerBrowser().IsWorkshopSubscribedMap( server.m_szMap ); + } + + Assert( workshopMode == eWorkshop_None ); + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: gets filter settings from controls +//----------------------------------------------------------------------------- +void CCustomGames::OnSaveFilter(KeyValues *filter) +{ + BaseClass::OnSaveFilter( filter ); + + if ( m_pTagFilter ) + { + // tags + m_pTagFilter->GetText(m_szTagFilter, sizeof(m_szTagFilter) - 1); + } + + if ( m_szTagFilter[0] ) + { + Q_strlower(m_szTagFilter); + } + + if ( TagsExclude() ) + { + m_vecServerFilters.AddToTail( MatchMakingKeyValuePair_t( "gametype", "" ) ); + } + else + { + m_vecServerFilters.AddToTail( MatchMakingKeyValuePair_t( "gametype", m_szTagFilter ) ); + } + + filter->SetString("gametype", m_szTagFilter); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCustomGames::SetRefreshing(bool state) +{ + if ( state ) + { + m_pAddTagList->SetEnabled( false ); + } + + BaseClass::SetRefreshing( state ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCustomGames::ServerResponded( int iServer, gameserveritem_t *pServerItem ) +{ + CBaseGamesPage::ServerResponded( iServer, pServerItem ); + + // If we've found a server with some tags, enable the add tag button + if ( pServerItem->m_szGameTags[0] ) + { + m_pAddTagList->SetEnabled( true ); + } +} + +struct tagentry_t +{ + const char *pszTag; + int iCount; +}; +int __cdecl SortTagsInUse( const tagentry_t *pTag1, const tagentry_t *pTag2 ) +{ + return (pTag1->iCount < pTag2->iCount); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCustomGames::RecalculateCommonTags( void ) +{ + // Regenerate our tag list + m_pTagListMenu->DeleteAllItems(); + + // Loop through our servers, and build a list of all the tags + CUtlVector<tagentry_t> aTagsInUse; + + int iCount = m_pGameList->GetItemCount(); + for ( int i = 0; i < iCount; i++ ) + { + int serverID = m_pGameList->GetItemUserData( i ); + gameserveritem_t *pServer = GetServer( serverID ); + if ( pServer && pServer->m_szGameTags && pServer->m_szGameTags[0] ) + { + CUtlVector<char*> TagList; + V_SplitString( pServer->m_szGameTags, ",", TagList ); + + for ( int iTag = 0; iTag < TagList.Count(); iTag++ ) + { + // First make sure it's not already in our list + bool bFound = false; + for ( int iCheck = 0; iCheck < aTagsInUse.Count(); iCheck++ ) + { + if ( !Q_strnicmp(TagList[iTag], aTagsInUse[iCheck].pszTag, MAX_TAG_CHARACTERS ) ) + { + aTagsInUse[iCheck].iCount++; + bFound = true; + } + } + + if ( !bFound ) + { + int iIdx = aTagsInUse.AddToTail(); + aTagsInUse[iIdx].pszTag = TagList[iTag]; + aTagsInUse[iIdx].iCount = 0; + } + } + } + } + + aTagsInUse.Sort( SortTagsInUse ); + + int iTagsToAdd = min( aTagsInUse.Count(), NUM_COMMON_TAGS ); + for ( int i = 0; i < iTagsToAdd; i++ ) + { + const char *pszTag = aTagsInUse[i].pszTag; + m_pTagListMenu->AddMenuItem( pszTag, new KeyValues("AddTag", "tag", pszTag), this, new KeyValues( "data", "tag", pszTag ) ); + } + + m_pTagListMenu->SetFixedWidth( m_pAddTagList->GetWide() ); + m_pTagListMenu->InvalidateLayout( true, false ); + m_pTagListMenu->PositionRelativeToPanel( m_pAddTagList, Menu::UP ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCustomGames::OnTagMenuButtonOpened( void ) +{ + RecalculateCommonTags(); +} + +//----------------------------------------------------------------------------- +// Purpose: Sets the text from the message +//----------------------------------------------------------------------------- +void CCustomGames::OnAddTag(KeyValues *params) +{ + KeyValues *pkvText = params->FindKey("tag", false); + if (!pkvText) + return; + + AddTagToFilterList( pkvText->GetString() ); +} + + +int SortServerTags( char* const *p1, char* const *p2 ) +{ + return ( Q_strcmp( *p1, *p2 ) > 0 ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCustomGames::AddTagToFilterList( const char *pszTag ) +{ + char txt[ 128 ]; + m_pTagFilter->GetText( txt, sizeof( txt ) ); + + CUtlVector<char*> TagList; + V_SplitString( txt, ",", TagList ); + + if ( txt[0] ) + { + for ( int i = 0; i < TagList.Count(); i++ ) + { + // Already in the tag list? + if ( !Q_stricmp( TagList[i], pszTag ) ) + { + TagList.PurgeAndDeleteElements(); + return; + } + } + } + + char *pszNewTag = new char[64]; + Q_strncpy( pszNewTag, pszTag, 64 ); + TagList.AddToHead( pszNewTag ); + + TagList.Sort( SortServerTags ); + + // Append it + char tmptags[MAX_TAG_CHARACTERS]; + tmptags[0] = '\0'; + + for ( int i = 0; i < TagList.Count(); i++ ) + { + if ( i > 0 ) + { + Q_strncat( tmptags, ",", MAX_TAG_CHARACTERS ); + } + + Q_strncat( tmptags, TagList[i], MAX_TAG_CHARACTERS ); + } + + m_pTagFilter->SetText( tmptags ); + TagList.PurgeAndDeleteElements(); + + // Update & apply filters now that the tag list has changed + UpdateFilterSettings(); + ApplyGameFilters(); +} diff --git a/serverbrowser/CustomGames.h b/serverbrowser/CustomGames.h new file mode 100644 index 0000000..279b586 --- /dev/null +++ b/serverbrowser/CustomGames.h @@ -0,0 +1,70 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#ifndef CUSTOMGAMES_H +#define CUSTOMGAMES_H +#ifdef _WIN32 +#pragma once +#endif + +#define MAX_TAG_CHARACTERS 128 + +class TagInfoLabel : public vgui::URLLabel +{ + DECLARE_CLASS_SIMPLE( TagInfoLabel, vgui::URLLabel ); +public: + TagInfoLabel(Panel *parent, const char *panelName); + TagInfoLabel(Panel *parent, const char *panelName, const char *text, const char *pszURL); + + virtual void OnMousePressed(vgui::MouseCode code); + + MESSAGE_FUNC( DoOpenCustomServerInfoURL, "DoOpenCustomServerInfoURL" ); +}; + +class TagMenuButton : public vgui::MenuButton +{ + DECLARE_CLASS_SIMPLE( TagMenuButton, vgui::MenuButton ); +public: + TagMenuButton( Panel *parent, const char *panelName, const char *text); + + virtual void OnShowMenu(vgui::Menu *menu); +}; + +//----------------------------------------------------------------------------- +// Purpose: Internet games with tags +//----------------------------------------------------------------------------- + +class CCustomGames : public CInternetGames +{ + DECLARE_CLASS_SIMPLE( CCustomGames, CInternetGames ); +public: + CCustomGames(vgui::Panel *parent); + ~CCustomGames(); + + virtual void UpdateDerivedLayouts( void ) OVERRIDE; + virtual void OnLoadFilter(KeyValues *filter) OVERRIDE; + virtual void OnSaveFilter(KeyValues *filter) OVERRIDE; + bool CheckTagFilter( gameserveritem_t &server ) OVERRIDE; + bool CheckWorkshopFilter( gameserveritem_t &server ) OVERRIDE; + virtual void SetRefreshing(bool state) OVERRIDE; + virtual void ServerResponded( int iServer, gameserveritem_t *pServerItem ) OVERRIDE; + + MESSAGE_FUNC_PARAMS( OnAddTag, "AddTag", params ); + MESSAGE_FUNC( OnTagMenuButtonOpened, "TagMenuButtonOpened" ); + + void RecalculateCommonTags( void ); + void AddTagToFilterList( const char *pszTag ); + +private: + TagInfoLabel *m_pTagInfoURL; + TagMenuButton *m_pAddTagList; + vgui::Menu *m_pTagListMenu; + vgui::TextEntry *m_pTagFilter; + char m_szTagFilter[MAX_TAG_CHARACTERS]; +}; + + +#endif // CUSTOMGAMES_H diff --git a/serverbrowser/DialogAddServer.cpp b/serverbrowser/DialogAddServer.cpp new file mode 100644 index 0000000..cbba58e --- /dev/null +++ b/serverbrowser/DialogAddServer.cpp @@ -0,0 +1,392 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include "pch_serverbrowser.h" + +using namespace vgui; + +//----------------------------------------------------------------------------- +// Purpose: Constructor +// Input : *gameList - game list to add specified server to +//----------------------------------------------------------------------------- +CDialogAddServer::CDialogAddServer(vgui::Panel *parent, IGameList *gameList) : Frame(parent, "DialogAddServer") +{ + SetDeleteSelfOnClose(true); + + m_pGameList = gameList; + + SetTitle("#ServerBrowser_AddServersTitle", true); + SetSizeable( false ); + + m_pTabPanel = new PropertySheet(this, "GameTabs"); + m_pTabPanel->SetTabWidth(72); + + m_pDiscoveredGames = new ListPanel( this, "Servers" ); + + // Add the column headers + m_pDiscoveredGames->AddColumnHeader(0, "Password", "#ServerBrowser_Password", 16, ListPanel::COLUMN_FIXEDSIZE | ListPanel::COLUMN_IMAGE); + m_pDiscoveredGames->AddColumnHeader(1, "Bots", "#ServerBrowser_Bots", 16, ListPanel::COLUMN_FIXEDSIZE | ListPanel::COLUMN_IMAGE | ListPanel::COLUMN_HIDDEN); + m_pDiscoveredGames->AddColumnHeader(2, "Secure", "#ServerBrowser_Secure", 16, ListPanel::COLUMN_FIXEDSIZE | ListPanel::COLUMN_IMAGE); + + bool bGameSupportsReplay = GameSupportsReplay(); + + int nReplayWidth = 16; + if ( !bGameSupportsReplay ) + { + nReplayWidth = 0; + } + + m_pDiscoveredGames->AddColumnHeader(3, "Replay", "#ServerBrowser_Replay", nReplayWidth, ListPanel::COLUMN_FIXEDSIZE | ListPanel::COLUMN_IMAGE); + m_pDiscoveredGames->AddColumnHeader(4, "Name", "#ServerBrowser_Servers", 20, ListPanel::COLUMN_RESIZEWITHWINDOW | ListPanel::COLUMN_UNHIDABLE); + m_pDiscoveredGames->AddColumnHeader(5, "IPAddr", "#ServerBrowser_IPAddress", 60, ListPanel::COLUMN_HIDDEN); + m_pDiscoveredGames->AddColumnHeader(6, "GameDesc", "#ServerBrowser_Game", 150); + m_pDiscoveredGames->AddColumnHeader(7, "Players", "#ServerBrowser_Players", 60); + m_pDiscoveredGames->AddColumnHeader(8, "Map", "#ServerBrowser_Map", 80); + m_pDiscoveredGames->AddColumnHeader(9, "Ping", "#ServerBrowser_Latency", 60); + + m_pDiscoveredGames->SetColumnHeaderTooltip(0, "#ServerBrowser_PasswordColumn_Tooltip"); + m_pDiscoveredGames->SetColumnHeaderTooltip(1, "#ServerBrowser_BotColumn_Tooltip"); + m_pDiscoveredGames->SetColumnHeaderTooltip(2, "#ServerBrowser_SecureColumn_Tooltip"); + + if ( bGameSupportsReplay ) + { + m_pDiscoveredGames->SetColumnHeaderTooltip(3, "#ServerBrowser_ReplayColumn_Tooltip"); + } + + // setup fast sort functions + m_pDiscoveredGames->SetSortFunc(0, PasswordCompare); + m_pDiscoveredGames->SetSortFunc(1, BotsCompare); + m_pDiscoveredGames->SetSortFunc(2, SecureCompare); + + if ( bGameSupportsReplay ) + { + m_pDiscoveredGames->SetSortFunc(3, ReplayCompare); + } + + m_pDiscoveredGames->SetSortFunc(4, ServerNameCompare); + m_pDiscoveredGames->SetSortFunc(5, IPAddressCompare); + m_pDiscoveredGames->SetSortFunc(6, GameCompare); + m_pDiscoveredGames->SetSortFunc(7, PlayersCompare); + m_pDiscoveredGames->SetSortFunc(8, MapCompare); + m_pDiscoveredGames->SetSortFunc(9, PingCompare); + + m_pDiscoveredGames->SetSortColumn(9); // sort on ping + + m_pTextEntry = new vgui::TextEntry( this, "ServerNameText" ); + m_pTextEntry->AddActionSignalTarget( this ); + + m_pTestServersButton = new vgui::Button( this, "TestServersButton", "" ); + m_pAddServerButton = new vgui::Button( this, "OKButton", "" ); + m_pAddSelectedServerButton = new vgui::Button( this, "SelectedOKButton", "", this, "addselected" ); + + m_pTabPanel->AddPage( m_pDiscoveredGames, "#ServerBrowser_Servers" ); + + LoadControlSettings("Servers/DialogAddServer.res"); + + // Setup the buttons. We leave them disabled until there is text in the textbox. + m_pAddServerButton->SetEnabled( false ); + m_pTestServersButton->SetEnabled( false ); + m_pAddSelectedServerButton->SetEnabled( false ); + m_pAddSelectedServerButton->SetVisible( false ); + m_pTabPanel->SetVisible( false ); + + m_pTextEntry->RequestFocus(); + + // Initially, we aren't high enough to show the tab panel. + int x, y; + m_pTabPanel->GetPos( x, y ); + m_OriginalHeight = m_pTabPanel->GetTall() + y + 50; + SetTall( y ); +} + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +CDialogAddServer::~CDialogAddServer() +{ + FOR_EACH_VEC( m_Queries, i ) + { + if ( steamapicontext->SteamMatchmakingServers() ) + steamapicontext->SteamMatchmakingServers()->CancelServerQuery( m_Queries[ i ] ); + } +} + +//----------------------------------------------------------------------------- +// Lets us know when the text entry has changed. +//----------------------------------------------------------------------------- +void CDialogAddServer::OnTextChanged() +{ + bool bAnyText = (m_pTextEntry->GetTextLength() > 0); + m_pAddServerButton->SetEnabled( bAnyText ); + m_pTestServersButton->SetEnabled( bAnyText ); +} + +//----------------------------------------------------------------------------- +// Purpose: button command handler +//----------------------------------------------------------------------------- +void CDialogAddServer::OnCommand(const char *command) +{ + if ( Q_stricmp(command, "OK") == 0 ) + { + OnOK(); + } + else if ( Q_stricmp( command, "TestServers" ) == 0 ) + { + SetTall( m_OriginalHeight ); + m_pTabPanel->SetVisible( true ); + m_pAddSelectedServerButton->SetVisible( true ); + + TestServers(); + } + else if ( !Q_stricmp( command, "addselected" ) ) + { + if ( m_pDiscoveredGames->GetSelectedItemsCount() ) + { + // get the server + int serverID = m_pDiscoveredGames->GetItemUserData( m_pDiscoveredGames->GetSelectedItem(0) ); + FinishAddServer( m_Servers[ serverID ] ); + m_pDiscoveredGames->RemoveItem( m_pDiscoveredGames->GetSelectedItem(0) ); // as we add to favs remove from the list + m_pDiscoveredGames->SetEmptyListText( "" ); + } + } + else + { + BaseClass::OnCommand(command); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Handles the OK button being pressed; adds the server to the game list +//----------------------------------------------------------------------------- +void CDialogAddServer::OnOK() +{ + // try and parse out IP address + const char *address = GetControlString("ServerNameText", ""); + netadr_t netaddr; + netaddr.SetFromString( address, true ); + if ( !netaddr.GetPort() && !AllowInvalidIPs() ) + { + // use the default port since it was not entered + netaddr.SetPort( 27015 ); + } + + if ( AllowInvalidIPs() || netaddr.IsValid() ) + { + gameserveritem_t server; + memset(&server, 0, sizeof(server)); + server.SetName( address ); + + // We assume here that the query and connection ports are the same. This is why it's much + // better if they click "Servers" and choose a server in there. + server.m_NetAdr.Init( netaddr.GetIPHostByteOrder(), netaddr.GetPort(), netaddr.GetPort() ); + + server.m_nAppID = 0; + FinishAddServer( server ); + } + else + { + // could not parse the ip address, popup an error + MessageBox *dlg = new MessageBox("#ServerBrowser_AddServerErrorTitle", "#ServerBrowser_AddServerError"); + dlg->DoModal(); + } + + // mark ourselves to be closed + PostMessage(this, new KeyValues("Close")); +} + + +//----------------------------------------------------------------------------- +// Purpose: Ping a particular IP for server presence +//----------------------------------------------------------------------------- +void CDialogAddServer::TestServers() +{ + if ( !steamapicontext->SteamMatchmakingServers() ) + return; + + m_pDiscoveredGames->SetEmptyListText( "" ); + m_pDiscoveredGames->RemoveAll(); + + // If they specified a port, then send a query to that port. + const char *address = GetControlString("ServerNameText", ""); + netadr_t netaddr; + netaddr.SetFromString( address, true ); + + m_Servers.RemoveAll(); + CUtlVector<netadr_t> vecAdress; + + if ( netaddr.GetPort() == 0 ) + { + // No port specified. Go to town on the ports. + CUtlVector<uint16> portsToTry; + GetMostCommonQueryPorts( portsToTry ); + + for ( int i=0; i < portsToTry.Count(); i++ ) + { + netadr_t newAddr = netaddr; + newAddr.SetPort( portsToTry[i] ); + vecAdress.AddToTail( newAddr ); + } + } + else + { + vecAdress.AddToTail( netaddr ); + } + + // Change the text on the tab panel.. + m_pTabPanel->RemoveAllPages(); + + wchar_t wstr[512]; + if ( address[0] == 0 ) + { + Q_wcsncpy( wstr, g_pVGuiLocalize->Find( "#ServerBrowser_ServersRespondingLocal"), sizeof( wstr ) ); + } + else + { + wchar_t waddress[512]; + Q_UTF8ToUnicode( address, waddress, sizeof( waddress ) ); + g_pVGuiLocalize->ConstructString( wstr, sizeof( wstr ), g_pVGuiLocalize->Find( "#ServerBrowser_ServersResponding"), 1, waddress ); + } + + char str[512]; + Q_UnicodeToUTF8( wstr, str, sizeof( str ) ); + m_pTabPanel->AddPage( m_pDiscoveredGames, str ); + m_pTabPanel->InvalidateLayout(); + + FOR_EACH_VEC( vecAdress, iAddress ) + { + m_Queries.AddToTail( steamapicontext->SteamMatchmakingServers()->PingServer( vecAdress[ iAddress ].GetIPHostByteOrder(), vecAdress[ iAddress ].GetPort(), this ) ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: A server answered our ping +//----------------------------------------------------------------------------- +void CDialogAddServer::ServerResponded( gameserveritem_t &server ) +{ + KeyValues *kv = new KeyValues( "Server" ); + + kv->SetString( "name", server.GetName() ); + kv->SetString( "map", server.m_szMap ); + kv->SetString( "GameDir", server.m_szGameDir ); + kv->SetString( "GameDesc", server.m_szGameDescription ); + kv->SetString( "GameTags", server.m_szGameTags ); + kv->SetInt( "password", server.m_bPassword ? 1 : 0); + kv->SetInt( "bots", server.m_nBotPlayers ? 2 : 0); + kv->SetInt( "Replay", IsReplayServer( server ) ? 5 : 0 ); + + if ( server.m_bSecure ) + { + // show the denied icon if banned from secure servers, the secure icon otherwise + kv->SetInt("secure", ServerBrowser().IsVACBannedFromGame( server.m_nAppID ) ? 4 : 3); + } + else + { + kv->SetInt("secure", 0); + } + + netadr_t reportedIPAddr; + reportedIPAddr.SetIP( server.m_NetAdr.GetIP() ); + reportedIPAddr.SetPort( server.m_NetAdr.GetConnectionPort() ); + kv->SetString("IPAddr", reportedIPAddr.ToString() ); + + char buf[32]; + Q_snprintf(buf, sizeof(buf), "%d / %d", server.m_nPlayers, server.m_nMaxPlayers); + kv->SetString("Players", buf); + + kv->SetInt("Ping", server.m_nPing); + + // new server, add to list + int iServer = m_Servers.AddToTail( server ); + int iListID = m_pDiscoveredGames->AddItem(kv, iServer, false, false); + if ( m_pDiscoveredGames->GetItemCount() == 1 ) + { + m_pDiscoveredGames->AddSelectedItem( iListID ); + } + kv->deleteThis(); + + m_pDiscoveredGames->InvalidateLayout(); +} + +void CDialogAddServer::ServerFailedToRespond() +{ + m_pDiscoveredGames->SetEmptyListText( "#ServerBrowser_ServerNotResponding" ); +} + +void CDialogAddServer::ApplySchemeSettings( IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + ImageList *imageList = new ImageList(false); + imageList->AddImage(scheme()->GetImage("servers/icon_password", false)); + imageList->AddImage(scheme()->GetImage("servers/icon_bots", false)); + imageList->AddImage(scheme()->GetImage("servers/icon_robotron", false)); + imageList->AddImage(scheme()->GetImage("servers/icon_secure_deny", false)); + imageList->AddImage(scheme()->GetImage("servers/icon_replay", false)); + + int passwordColumnImage = imageList->AddImage(scheme()->GetImage("servers/icon_password_column", false)); + int botColumnImage = imageList->AddImage(scheme()->GetImage("servers/icon_bots_column", false)); + int secureColumnImage = imageList->AddImage(scheme()->GetImage("servers/icon_robotron_column", false)); + int replayColumnImage = imageList->AddImage(scheme()->GetImage("servers/icon_replay_column", false)); + + m_pDiscoveredGames->SetImageList(imageList, true); + vgui::HFont hFont = pScheme->GetFont( "ListSmall", IsProportional() ); + if ( !hFont ) + hFont = pScheme->GetFont( "DefaultSmall", IsProportional() ); + + m_pDiscoveredGames->SetFont( hFont ); + m_pDiscoveredGames->SetColumnHeaderImage(0, passwordColumnImage); + m_pDiscoveredGames->SetColumnHeaderImage(1, botColumnImage); + m_pDiscoveredGames->SetColumnHeaderImage(2, secureColumnImage); + m_pDiscoveredGames->SetColumnHeaderImage(3, replayColumnImage); +} + + +//----------------------------------------------------------------------------- +// Purpose: A server on the listed IP responded +//----------------------------------------------------------------------------- +void CDialogAddServer::OnItemSelected() +{ + int nSelectedItem = m_pDiscoveredGames->GetSelectedItem(0); + if( nSelectedItem != -1 ) + { + m_pAddSelectedServerButton->SetEnabled( true ); + } + else + { + m_pAddSelectedServerButton->SetEnabled( false ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CDialogAddServer::FinishAddServer( gameserveritem_t &pServer ) +{ + ServerBrowserDialog().AddServerToFavorites( pServer ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CDialogAddBlacklistedServer::FinishAddServer( gameserveritem_t &pServer ) +{ + ServerBrowserDialog().AddServerToBlacklist( pServer ); + ServerBrowserDialog().BlacklistsChanged(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CDialogAddBlacklistedServer::ApplySchemeSettings( vgui::IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + m_pAddServerButton->SetText( "#ServerBrowser_AddAddressToBlacklist" ); + m_pAddSelectedServerButton->SetText( "#ServerBrowser_AddSelectedToBlacklist" ); +} diff --git a/serverbrowser/DialogAddServer.h b/serverbrowser/DialogAddServer.h new file mode 100644 index 0000000..7abaee1 --- /dev/null +++ b/serverbrowser/DialogAddServer.h @@ -0,0 +1,75 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef DIALOGADDSERVER_H +#define DIALOGADDSERVER_H +#ifdef _WIN32 +#pragma once +#endif + +class CAddServerGameList; +class IGameList; + +//----------------------------------------------------------------------------- +// Purpose: Dialog which lets the user add a server by IP address +//----------------------------------------------------------------------------- +class CDialogAddServer : public vgui::Frame, public ISteamMatchmakingPingResponse +{ + DECLARE_CLASS_SIMPLE( CDialogAddServer, vgui::Frame ); + friend class CAddServerGameList; + +public: + CDialogAddServer(vgui::Panel *parent, IGameList *gameList); + ~CDialogAddServer(); + + void ServerResponded( gameserveritem_t &server ); + void ServerFailedToRespond(); + + void ApplySchemeSettings( vgui::IScheme *pScheme ); + + MESSAGE_FUNC( OnItemSelected, "ItemSelected" ); +private: + virtual void OnCommand(const char *command); + + void OnOK(); + + void TestServers(); + MESSAGE_FUNC( OnTextChanged, "TextChanged" ); + + virtual void FinishAddServer( gameserveritem_t &pServer ); + virtual bool AllowInvalidIPs( void ) { return false; } + +protected: + IGameList *m_pGameList; + + vgui::Button *m_pTestServersButton; + vgui::Button *m_pAddServerButton; + vgui::Button *m_pAddSelectedServerButton; + + vgui::PropertySheet *m_pTabPanel; + vgui::TextEntry *m_pTextEntry; + vgui::ListPanel *m_pDiscoveredGames; + int m_OriginalHeight; + CUtlVector<gameserveritem_t> m_Servers; + CUtlVector<HServerQuery> m_Queries; +}; + +class CDialogAddBlacklistedServer : public CDialogAddServer +{ + DECLARE_CLASS_SIMPLE( CDialogAddBlacklistedServer, CDialogAddServer ); +public: + CDialogAddBlacklistedServer( vgui::Panel *parent, IGameList *gameList) : + CDialogAddServer( parent, gameList ) + { + } + + virtual void FinishAddServer( gameserveritem_t &pServer ); + void ApplySchemeSettings( vgui::IScheme *pScheme ); + virtual bool AllowInvalidIPs( void ) { return true; } +}; + +#endif // DIALOGADDSERVER_H diff --git a/serverbrowser/DialogGameInfo.cpp b/serverbrowser/DialogGameInfo.cpp new file mode 100644 index 0000000..fc0ed36 --- /dev/null +++ b/serverbrowser/DialogGameInfo.cpp @@ -0,0 +1,802 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include "pch_serverbrowser.h" + +using namespace vgui; + +static const long RETRY_TIME = 10000; // refresh server every 10 seconds +static const long CHALLENGE_ENTRIES = 1024; + +extern "C" +{ + DLL_EXPORT bool JoiningSecureServerCall() + { + return true; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Comparison function used in query redblack tree +//----------------------------------------------------------------------------- +bool QueryLessFunc( const struct challenge_s &item1, const struct challenge_s &item2 ) +{ + // compare port then ip + if ( item1.addr.GetPort() < item2.addr.GetPort() ) + return true; + else if ( item1.addr.GetPort() > item2.addr.GetPort() ) + return false; + + int ip1 = item1.addr.GetIPNetworkByteOrder(); + int ip2 = item2.addr.GetIPNetworkByteOrder(); + + return ip1 < ip2; +} + + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +CDialogGameInfo::CDialogGameInfo( vgui::Panel *parent, int serverIP, int queryPort, unsigned short connectionPort, const char *pszConnectCode ) : + Frame(parent, "DialogGameInfo"), + m_CallbackPersonaStateChange( this, &CDialogGameInfo::OnPersonaStateChange ), + m_sConnectCode( pszConnectCode ) +{ + SetBounds(0, 0, 512, 512); + SetMinimumSize(416, 340); + SetDeleteSelfOnClose(true); + m_bConnecting = false; + m_bServerFull = false; + m_bShowAutoRetryToggle = false; + m_bServerNotResponding = false; + m_bShowingExtendedOptions = false; + m_SteamIDFriend = 0; + m_hPingQuery = HSERVERQUERY_INVALID; + m_hPlayersQuery = HSERVERQUERY_INVALID; + m_bPlayerListUpdatePending = false; + + m_szPassword[0] = 0; + + m_pConnectButton = new Button(this, "Connect", "#ServerBrowser_JoinGame"); + m_pCloseButton = new Button(this, "Close", "#ServerBrowser_Close"); + m_pRefreshButton = new Button(this, "Refresh", "#ServerBrowser_Refresh"); + m_pInfoLabel = new Label(this, "InfoLabel", ""); + m_pAutoRetry = new ToggleButton(this, "AutoRetry", "#ServerBrowser_AutoRetry"); + m_pAutoRetry->AddActionSignalTarget(this); + + m_pAutoRetryAlert = new RadioButton(this, "AutoRetryAlert", "#ServerBrowser_AlertMeWhenSlotOpens"); + m_pAutoRetryJoin = new RadioButton(this, "AutoRetryJoin", "#ServerBrowser_JoinWhenSlotOpens"); + m_pPlayerList = new ListPanel(this, "PlayerList"); + m_pPlayerList->AddColumnHeader(0, "PlayerName", "#ServerBrowser_PlayerName", 156); + m_pPlayerList->AddColumnHeader(1, "Score", "#ServerBrowser_Score", 64); + m_pPlayerList->AddColumnHeader(2, "Time", "#ServerBrowser_Time", 64); + + m_pPlayerList->SetSortFunc(2, &PlayerTimeColumnSortFunc); + + // set the defaults for sorting + // hack, need to make this more explicit functions in ListPanel + PostMessage(m_pPlayerList, new KeyValues("SetSortColumn", "column", 2)); + PostMessage(m_pPlayerList, new KeyValues("SetSortColumn", "column", 1)); + PostMessage(m_pPlayerList, new KeyValues("SetSortColumn", "column", 1)); + + m_pAutoRetryAlert->SetSelected(true); + + m_pConnectButton->SetCommand(new KeyValues("Connect")); + m_pCloseButton->SetCommand(new KeyValues("Close")); + m_pRefreshButton->SetCommand(new KeyValues("Refresh")); + + m_iRequestRetry = 0; + + // create a new server to watch + memset(&m_Server, 0, sizeof(m_Server) ); + m_Server.m_NetAdr.Init( serverIP, queryPort, connectionPort ); + + // refresh immediately + RequestInfo(); + + // let us be ticked every frame + ivgui()->AddTickSignal(this->GetVPanel()); + + LoadControlSettings("Servers/DialogGameInfo.res"); + RegisterControlSettingsFile( "Servers/DialogGameInfo_SinglePlayer.res" ); + RegisterControlSettingsFile( "Servers/DialogGameInfo_AutoRetry.res" ); + MoveToCenterOfScreen(); +} + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +CDialogGameInfo::~CDialogGameInfo() +{ + if ( !steamapicontext->SteamMatchmakingServers() ) + return; + + if ( m_hPingQuery != HSERVERQUERY_INVALID ) + steamapicontext->SteamMatchmakingServers()->CancelServerQuery( m_hPingQuery ); + if ( m_hPlayersQuery != HSERVERQUERY_INVALID ) + steamapicontext->SteamMatchmakingServers()->CancelServerQuery( m_hPlayersQuery ); +} + +//----------------------------------------------------------------------------- +// Purpose: send a player query to a server +//----------------------------------------------------------------------------- +void CDialogGameInfo::SendPlayerQuery( uint32 unIP, uint16 usQueryPort ) +{ + if ( !steamapicontext->SteamMatchmakingServers() ) + return; + + if ( m_hPlayersQuery != HSERVERQUERY_INVALID ) + steamapicontext->SteamMatchmakingServers()->CancelServerQuery( m_hPlayersQuery ); + m_hPlayersQuery = steamapicontext->SteamMatchmakingServers()->PlayerDetails( unIP, usQueryPort, this ); + m_bPlayerListUpdatePending = true; +} + + +//----------------------------------------------------------------------------- +// Purpose: Activates the dialog +//----------------------------------------------------------------------------- +void CDialogGameInfo::Run(const char *titleName) +{ + if ( titleName ) + { + SetTitle( "#ServerBrowser_GameInfoWithNameTitle", true ); + } + else + { + SetTitle( "#ServerBrowser_GameInfoWithNameTitle", true ); + } + SetDialogVariable( "game", titleName ); + + // get the info from the user + RequestInfo(); + Activate(); +} + +//----------------------------------------------------------------------------- +// Purpose: Changes which server to watch +//----------------------------------------------------------------------------- +void CDialogGameInfo::ChangeGame( int serverIP, int queryPort, unsigned short connectionPort ) +{ + memset( &m_Server, 0x0, sizeof(m_Server) ); + + m_Server.m_NetAdr.Init( serverIP, queryPort, connectionPort ); + + // remember the dialogs position so we can keep it the same + int x, y; + GetPos( x, y ); + + // see if we need to change dialog state + if ( !m_Server.m_NetAdr.GetIP() || !m_Server.m_NetAdr.GetQueryPort() ) + { + // not in a server, load the simple settings dialog + SetMinimumSize(0, 0); + SetSizeable( false ); + LoadControlSettings( "Servers/DialogGameInfo_SinglePlayer.res" ); + } + else + { + // moving from a single-player game -> multiplayer, reset dialog + SetMinimumSize(416, 340); + SetSizeable( true ); + LoadControlSettings( "Servers/DialogGameInfo.res" ); + } + SetPos( x, y ); + + // Start refresh immediately + m_iRequestRetry = 0; + RequestInfo(); + InvalidateLayout(); +} + + +//----------------------------------------------------------------------------- +// Purpose: updates the dialog if it's watching a friend who changes servers +//----------------------------------------------------------------------------- +void CDialogGameInfo::OnPersonaStateChange( PersonaStateChange_t *pPersonaStateChange ) +{ +#if 0 // TBD delete this func + if ( m_SteamIDFriend && m_SteamIDFriend == pPersonaStateChange->m_ulSteamID ) + { + // friend may have changed servers + uint64 nGameID; + uint32 unGameIP; + uint16 usGamePort; + uint16 usQueryPort; + + if ( SteamFriends()->GetFriendGamePlayed( m_SteamIDFriend, &nGameID, &unGameIP, &usGamePort, &usQueryPort ) ) + { + if ( pPersonaStateChange->m_nChangeFlags & k_EPersonaChangeGamePlayed ) + { + ChangeGame( unGameIP, usQueryPort, usGamePort ); + } + } + else + { + // bugbug johnc: change to not be in a game anymore + } + } +#endif +} + + +//----------------------------------------------------------------------------- +// Purpose: Associates a user with this dialog +//----------------------------------------------------------------------------- +void CDialogGameInfo::SetFriend( uint64 ulSteamIDFriend ) +{ + // set the title to include the friends name + SetTitle( "#ServerBrowser_GameInfoWithNameTitle", true ); + SetDialogVariable( "game", steamapicontext->SteamFriends()->GetFriendPersonaName( ulSteamIDFriend ) ); + SetDialogVariable( "friend", steamapicontext->SteamFriends()->GetFriendPersonaName( ulSteamIDFriend ) ); + + // store the friend we're associated with + m_SteamIDFriend = ulSteamIDFriend; + + FriendGameInfo_t friendGameInfo; + if ( steamapicontext->SteamFriends()->GetFriendGamePlayed( ulSteamIDFriend, &friendGameInfo ) ) + { + uint16 usConnPort = friendGameInfo.m_usGamePort; + if ( friendGameInfo.m_usQueryPort < QUERY_PORT_ERROR ) + usConnPort = friendGameInfo.m_usQueryPort; + ChangeGame( friendGameInfo.m_unGameIP, usConnPort, friendGameInfo.m_usGamePort ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: data access +//----------------------------------------------------------------------------- +uint64 CDialogGameInfo::GetAssociatedFriend() +{ + return m_SteamIDFriend; +} + + +//----------------------------------------------------------------------------- +// Purpose: lays out the data +//----------------------------------------------------------------------------- +void CDialogGameInfo::PerformLayout() +{ + BaseClass::PerformLayout(); + + SetControlString( "ServerText", m_Server.GetName() ); + SetControlString( "GameText", m_Server.m_szGameDescription ); + SetControlString( "MapText", m_Server.m_szMap ); + SetControlString( "GameTags", m_Server.m_szGameTags ); + + + if ( !m_Server.m_bHadSuccessfulResponse ) + { + SetControlString("SecureText", ""); + } + else if ( m_Server.m_bSecure ) + { + SetControlString("SecureText", "#ServerBrowser_Secure"); + } + else + { + SetControlString("SecureText", "#ServerBrowser_NotSecure"); + } + + char buf[128]; + if ( m_Server.m_nMaxPlayers > 0) + { + Q_snprintf(buf, sizeof(buf), "%d / %d", m_Server.m_nPlayers, m_Server.m_nMaxPlayers); + } + else + { + buf[0] = 0; + } + SetControlString("PlayersText", buf); + + if ( m_Server.m_NetAdr.GetIP() && m_Server.m_NetAdr.GetQueryPort() ) + { + SetControlString("ServerIPText", m_Server.m_NetAdr.GetConnectionAddressString() ); + m_pConnectButton->SetEnabled(true); + if ( m_pAutoRetry->IsSelected() ) + { + m_pAutoRetryAlert->SetVisible(true); + m_pAutoRetryJoin->SetVisible(true); + } + else + { + m_pAutoRetryAlert->SetVisible(false); + m_pAutoRetryJoin->SetVisible(false); + } + } + else + { + SetControlString("ServerIPText", ""); + m_pConnectButton->SetEnabled(false); + } + + if ( m_Server.m_bHadSuccessfulResponse ) + { + Q_snprintf(buf, sizeof(buf), "%d", m_Server.m_nPing ); + SetControlString("PingText", buf); + } + else + { + SetControlString("PingText", ""); + } + + // set the info text + if ( m_pAutoRetry->IsSelected() ) + { + if ( m_Server.m_nPlayers < m_Server.m_nMaxPlayers ) + { + m_pInfoLabel->SetText("#ServerBrowser_PressJoinToConnect"); + } + else if (m_pAutoRetryJoin->IsSelected()) + { + m_pInfoLabel->SetText("#ServerBrowser_JoinWhenSlotIsFree"); + } + else + { + m_pInfoLabel->SetText("#ServerBrowser_AlertWhenSlotIsFree"); + } + } + else if (m_bServerFull) + { + m_pInfoLabel->SetText("#ServerBrowser_CouldNotConnectServerFull"); + } + else if (m_bServerNotResponding) + { + m_pInfoLabel->SetText("#ServerBrowser_ServerNotResponding"); + } + else + { + // clear the status + m_pInfoLabel->SetText(""); + } + + if ( m_Server.m_bHadSuccessfulResponse && !(m_Server.m_nPlayers + m_Server.m_nBotPlayers) ) + { + m_pPlayerList->SetEmptyListText("#ServerBrowser_ServerHasNoPlayers"); + } + else + { + m_pPlayerList->SetEmptyListText("#ServerBrowser_ServerNotResponding"); + } + + // auto-retry layout + m_pAutoRetry->SetVisible(m_bShowAutoRetryToggle); + + Repaint(); +} + +void CDialogGameInfo::OnKeyCodePressed( vgui::KeyCode code ) +{ + if ( code == KEY_XBUTTON_B || code == KEY_XBUTTON_A || code == STEAMCONTROLLER_A || code == STEAMCONTROLLER_B ) + { + m_pCloseButton->DoClick(); + } + else + { + BaseClass::OnKeyCodePressed(code); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Forces the game info dialog to try and connect +//----------------------------------------------------------------------------- +void CDialogGameInfo::Connect() +{ + OnConnect(); +} + +//----------------------------------------------------------------------------- +// Purpose: Connects the user to this game +//----------------------------------------------------------------------------- +void CDialogGameInfo::OnConnect() +{ + // flag that we are attempting connection + m_bConnecting = true; + + // reset state + m_bServerFull = false; + m_bServerNotResponding = false; + + InvalidateLayout(); + + // need to refresh server before attempting to connect, to make sure there is enough room on the server + m_iRequestRetry = 0; + RequestInfo(); +} + +//----------------------------------------------------------------------------- +// Purpose: Cancel auto-retry if we connect to the game by other means +//----------------------------------------------------------------------------- +void CDialogGameInfo::OnConnectToGame( int ip, int port ) +{ + // if we just connected to the server we were looking at, close the dialog + // important so that we don't auto-retry a server that we are already on + if ( m_Server.m_NetAdr.GetIP() == (uint32)ip && m_Server.m_NetAdr.GetConnectionPort() == (uint16)port ) + { + // close this dialog + Close(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Handles Refresh button press, starts a re-ping of the server +//----------------------------------------------------------------------------- +void CDialogGameInfo::OnRefresh() +{ + m_iRequestRetry = 0; + // re-ask the server for the game info + RequestInfo(); +} + +//----------------------------------------------------------------------------- +// Purpose: Forces the whole dialog to redraw when the auto-retry button is toggled +//----------------------------------------------------------------------------- +void CDialogGameInfo::OnButtonToggled(Panel *panel) +{ + if (panel == m_pAutoRetry) + { + ShowAutoRetryOptions(m_pAutoRetry->IsSelected()); + } + + InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: Sets whether the extended auto-retry options are visible or not +//----------------------------------------------------------------------------- +void CDialogGameInfo::ShowAutoRetryOptions(bool state) +{ + // we need to extend the dialog + int growSize = 60; + if (!state) + { + growSize = -growSize; + } + + // alter the dialog size accordingly + int x, y, wide, tall; + GetBounds( x, y, wide, tall ); + + // load a new layout file depending on the state + SetMinimumSize(416, 340); + if ( state ) + LoadControlSettings( "Servers/DialogGameInfo_AutoRetry.res" ); + else + LoadControlSettings( "Servers/DialogGameInfo.res" ); + + // restore size and position as + // load control settings will override them + SetBounds( x, y, wide, tall + growSize ); + + // restore other properties of the dialog + PerformLayout(); + + m_pAutoRetryAlert->SetSelected( true ); + + InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: Requests the right info from the server +//----------------------------------------------------------------------------- +void CDialogGameInfo::RequestInfo() +{ + if ( !steamapicontext->SteamMatchmakingServers() ) + return; + + if ( m_iRequestRetry == 0 ) + { + // reset the time at which we auto-refresh + m_iRequestRetry = system()->GetTimeMillis() + RETRY_TIME; + if ( m_hPingQuery != HSERVERQUERY_INVALID ) + steamapicontext->SteamMatchmakingServers()->CancelServerQuery( m_hPingQuery ); + m_hPingQuery = steamapicontext->SteamMatchmakingServers()->PingServer( m_Server.m_NetAdr.GetIP(), m_Server.m_NetAdr.GetQueryPort(), this ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Called every frame, handles resending network messages +//----------------------------------------------------------------------------- +void CDialogGameInfo::OnTick() +{ + // check to see if we should perform an auto-refresh + if ( m_iRequestRetry && m_iRequestRetry < system()->GetTimeMillis() ) + { + m_iRequestRetry = 0; + RequestInfo(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: called when the server has successfully responded +//----------------------------------------------------------------------------- +void CDialogGameInfo::ServerResponded( gameserveritem_t &server ) +{ + if( m_Server.m_NetAdr.GetQueryPort() && + m_Server.m_NetAdr.GetQueryPort() != server.m_NetAdr.GetQueryPort() ) + { + return; // this is not the guy we talked about + } + + uint16 connectionPort = m_Server.m_NetAdr.GetConnectionPort(); + + // FIXME(johns): This is a workaround for a steam bug, where it inproperly reads signed bytes out of the + // message. Once the upstream fix makes it into our SteamSDK, this block can be removed. + server.m_nPlayers = (uint8)(int8)server.m_nPlayers; + server.m_nBotPlayers = (uint8)(int8)server.m_nBotPlayers; + server.m_nMaxPlayers = (uint8)(int8)server.m_nMaxPlayers; + + m_hPingQuery = HSERVERQUERY_INVALID; + m_Server = server; + + // Preserve our connection port, since we may be querying the sourceTV port but getting a response for the real + // server. This is a limitation of the steam Matchmaking API where it doesn't properly send us a sourcetv response + // but instead the main server's response (unless we're connecting to a proxy, THEN we get the sourcetv response!) + m_Server.m_NetAdr.SetConnectionPort( connectionPort ); + + if ( m_bConnecting ) + { + ConnectToServer(); + } + else if ( m_pAutoRetry->IsSelected() && server.m_nPlayers < server.m_nMaxPlayers ) + { + // there is a slot free, we can join + + // make the sound + surface()->PlaySound("Servers/game_ready.wav"); + + // flash this window + FlashWindow(); + + // if it's set, connect right away + if (m_pAutoRetryJoin->IsSelected()) + { + ConnectToServer(); + } + } + else + { + SendPlayerQuery( server.m_NetAdr.GetIP(), server.m_NetAdr.GetQueryPort() ); + } + + m_bServerNotResponding = false; + + InvalidateLayout(); + Repaint(); +} + + +//----------------------------------------------------------------------------- +// Purpose: called when a server response has timed out +//----------------------------------------------------------------------------- +void CDialogGameInfo::ServerFailedToRespond() +{ + // the server didn't respond, mark that in the UI + // only mark if we haven't ever received a response + if ( !m_Server.m_bHadSuccessfulResponse ) + { + m_bServerNotResponding = true; + } + + InvalidateLayout(); + Repaint(); +} + + + +//----------------------------------------------------------------------------- +// Purpose: Constructs a command to send a running game to connect to a server, +// based on the server type +// +// TODO it would be nice to push this logic into the IRunGameEngine interface; that +// way we could ask the engine itself to construct arguments in ways that fit. +// Might be worth the effort as we start to add more engines. +//----------------------------------------------------------------------------- +void CDialogGameInfo::ApplyConnectCommand( const gameserveritem_t &server ) +{ + char command[ 256 ]; + // set the server password, if any + if ( m_szPassword[0] ) + { + Q_snprintf( command, Q_ARRAYSIZE( command ), "password \"%s\"\n", m_szPassword ); + g_pRunGameEngine->AddTextCommand( command ); + } + // send engine command to change servers + Q_snprintf( command, Q_ARRAYSIZE( command ), "connect %s %s\n", server.m_NetAdr.GetConnectionAddressString(), m_sConnectCode.String() ); + g_pRunGameEngine->AddTextCommand( command ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Constructs game options to use when running a game to connect to a server +//----------------------------------------------------------------------------- +void CDialogGameInfo::ConstructConnectArgs( char *pchOptions, int cchOptions, const gameserveritem_t &server ) +{ + Q_snprintf( pchOptions, cchOptions, " +connect %s", server.m_NetAdr.GetConnectionAddressString() ); + if ( m_szPassword[0] ) + { + Q_strcat( pchOptions, " +password \"", cchOptions ); + Q_strcat( pchOptions, m_szPassword, cchOptions ); + Q_strcat( pchOptions, "\"", cchOptions ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Connects to the server +//----------------------------------------------------------------------------- +void CDialogGameInfo::ConnectToServer() +{ + m_bConnecting = false; + + // check VAC status + if ( m_Server.m_bSecure && ServerBrowser().IsVACBannedFromGame( m_Server.m_nAppID ) ) + { + // refuse the user + CVACBannedConnRefusedDialog *pDlg = new CVACBannedConnRefusedDialog( GetVParent(), "VACBannedConnRefusedDialog" ); + pDlg->Activate(); + Close(); + return; + } + + + // check to see if we need a password + if ( m_Server.m_bPassword && !m_szPassword[0] ) + { + CDialogServerPassword *box = new CDialogServerPassword(this); + box->AddActionSignalTarget(this); + box->Activate( m_Server.GetName(), 0 ); + return; + } + + // check the player count + if ( m_Server.m_nPlayers >= m_Server.m_nMaxPlayers ) + { + // mark why we cannot connect + m_bServerFull = true; + // give them access to auto-retry options + m_bShowAutoRetryToggle = true; + InvalidateLayout(); + return; + } + + // tell the engine to connect + const char *gameDir = m_Server.m_szGameDir; + if (g_pRunGameEngine->IsRunning()) + { + ApplyConnectCommand( m_Server ); + } + else + { + char connectArgs[256]; + ConstructConnectArgs( connectArgs, Q_ARRAYSIZE( connectArgs ), m_Server ); + + if ( ( m_Server.m_bSecure && JoiningSecureServerCall() )|| !m_Server.m_bSecure ) + { + switch ( g_pRunGameEngine->RunEngine( m_Server.m_nAppID, gameDir, connectArgs ) ) + { + case IRunGameEngine::k_ERunResultModNotInstalled: + { + MessageBox *dlg = new MessageBox( "#ServerBrowser_GameInfoTitle", "#ServerBrowser_ModNotInstalled" ); + dlg->DoModal(); + SetVisible(false); + return; + } + break; + case IRunGameEngine::k_ERunResultAppNotFound: + { + MessageBox *dlg = new MessageBox( "#ServerBrowser_GameInfoTitle", "#ServerBrowser_AppNotFound" ); + dlg->DoModal(); + SetVisible(false); + return; + } + break; + case IRunGameEngine::k_ERunResultNotInitialized: + { + MessageBox *dlg = new MessageBox( "#ServerBrowser_GameInfoTitle", "#ServerBrowser_NotInitialized" ); + dlg->DoModal(); + SetVisible(false); + return; + } + break; + case IRunGameEngine::k_ERunResultOkay: + default: + break; + }; + } + } + + // close this dialog + PostMessage(this, new KeyValues("Close")); +} + +//----------------------------------------------------------------------------- +// Purpose: called when the current refresh list is complete +//----------------------------------------------------------------------------- +void CDialogGameInfo::RefreshComplete( EMatchMakingServerResponse response ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: handles response from the get password dialog +//----------------------------------------------------------------------------- +void CDialogGameInfo::OnJoinServerWithPassword(const char *password) +{ + // copy out the password + Q_strncpy(m_szPassword, password, sizeof(m_szPassword)); + + // retry connecting to the server again + OnConnect(); +} + +//----------------------------------------------------------------------------- +// Purpose: player list received +//----------------------------------------------------------------------------- +void CDialogGameInfo::ClearPlayerList() +{ + m_pPlayerList->DeleteAllItems(); + Repaint(); +} + +//----------------------------------------------------------------------------- +// Purpose: on individual player added +//----------------------------------------------------------------------------- +void CDialogGameInfo::AddPlayerToList(const char *playerName, int score, float timePlayedSeconds) +{ + if ( m_bPlayerListUpdatePending ) + { + m_bPlayerListUpdatePending = false; + m_pPlayerList->RemoveAll(); + } + + KeyValues *player = new KeyValues("player"); + player->SetString("PlayerName", playerName); + player->SetInt("Score", score); + player->SetInt("TimeSec", (int)timePlayedSeconds); + + // construct a time string + int seconds = (int)timePlayedSeconds; + int minutes = seconds / 60; + int hours = minutes / 60; + seconds %= 60; + minutes %= 60; + char buf[64]; + buf[0] = 0; + if (hours) + { + Q_snprintf(buf, sizeof(buf), "%dh %dm %ds", hours, minutes, seconds); + } + else if (minutes) + { + Q_snprintf(buf, sizeof(buf), "%dm %ds", minutes, seconds); + } + else + { + Q_snprintf(buf, sizeof(buf), "%ds", seconds); + } + player->SetString("Time", buf); + + m_pPlayerList->AddItem(player, 0, false, true); + player->deleteThis(); +} + +//----------------------------------------------------------------------------- +// Purpose: Sorting function for time column +//----------------------------------------------------------------------------- +int CDialogGameInfo::PlayerTimeColumnSortFunc(ListPanel *pPanel, const ListPanelItem &p1, const ListPanelItem &p2) +{ + int p1time = p1.kv->GetInt("TimeSec"); + int p2time = p2.kv->GetInt("TimeSec"); + + if (p1time > p2time) + return -1; + if (p1time < p2time) + return 1; + + return 0; +} + diff --git a/serverbrowser/DialogGameInfo.h b/serverbrowser/DialogGameInfo.h new file mode 100644 index 0000000..586c1c4 --- /dev/null +++ b/serverbrowser/DialogGameInfo.h @@ -0,0 +1,126 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef DIALOGGAMEINFO_H +#define DIALOGGAMEINFO_H +#ifdef _WIN32 +#pragma once +#endif + +struct challenge_s +{ + netadr_t addr; + int challenge; +}; + +//----------------------------------------------------------------------------- +// Purpose: Dialog for displaying information about a game server +//----------------------------------------------------------------------------- +class CDialogGameInfo : public vgui::Frame, public ISteamMatchmakingPlayersResponse, public ISteamMatchmakingPingResponse +{ + DECLARE_CLASS_SIMPLE( CDialogGameInfo, vgui::Frame ); + +public: + CDialogGameInfo(vgui::Panel *parent, int serverIP, int queryPort, unsigned short connectionPort, const char *pszConnectCode ); + ~CDialogGameInfo(); + + void Run(const char *titleName); + void ChangeGame(int serverIP, int queryPort, unsigned short connectionPort); + void SetFriend( uint64 ulSteamIDFriend ); + uint64 GetAssociatedFriend(); + + // forces the dialog to attempt to connect to the server + void Connect(); + + // implementation of IServerRefreshResponse interface + // called when the server has successfully responded + virtual void ServerResponded( gameserveritem_t &server ); + + // called when a server response has timed out + virtual void ServerFailedToRespond(); + + // on individual player added + virtual void AddPlayerToList(const char *playerName, int score, float timePlayedSeconds); + virtual void PlayersFailedToRespond() {} + virtual void PlayersRefreshComplete() { m_hPlayersQuery = HSERVERQUERY_INVALID; } + + // called when the current refresh list is complete + virtual void RefreshComplete( EMatchMakingServerResponse response ); + + // player list received + virtual void ClearPlayerList(); + + //virtual void SendChallengeQuery( const netadr_t & to ); + virtual void SendPlayerQuery( uint32 unIP, uint16 usQueryPort ); + //virtual void InsertChallengeResponse( const netadr_t & to, int nChallenge ); + +protected: + // message handlers + MESSAGE_FUNC( OnConnect, "Connect" ); + MESSAGE_FUNC( OnRefresh, "Refresh" ); + MESSAGE_FUNC_PTR( OnButtonToggled, "ButtonToggled", panel ); + MESSAGE_FUNC_PTR( OnRadioButtonChecked, "RadioButtonChecked", panel ) + { + OnButtonToggled( panel ); + } + + // response from the get password dialog + MESSAGE_FUNC_CHARPTR( OnJoinServerWithPassword, "JoinServerWithPassword", password ); + + MESSAGE_FUNC_INT_INT( OnConnectToGame, "ConnectedToGame", ip, port ); + + // vgui overrides + virtual void OnTick(); + virtual void PerformLayout(); + + virtual void OnKeyCodePressed( vgui::KeyCode code ); + +private: + STEAM_CALLBACK( CDialogGameInfo, OnPersonaStateChange, PersonaStateChange_t, m_CallbackPersonaStateChange ); + + long m_iRequestRetry; // time at which to retry the request + static int PlayerTimeColumnSortFunc(vgui::ListPanel *pPanel, const vgui::ListPanelItem &p1, const vgui::ListPanelItem &p2); + + // methods + void RequestInfo(); + void ConnectToServer(); + void ShowAutoRetryOptions(bool state); + void ConstructConnectArgs( char *pchOptions, int cchOptions, const gameserveritem_t &server ); + void ApplyConnectCommand( const gameserveritem_t &server ); + + vgui::Button *m_pConnectButton; + vgui::Button *m_pCloseButton; + vgui::Button *m_pRefreshButton; + vgui::Label *m_pInfoLabel; + vgui::ToggleButton *m_pAutoRetry; + vgui::RadioButton *m_pAutoRetryAlert; + vgui::RadioButton *m_pAutoRetryJoin; + vgui::ListPanel *m_pPlayerList; + + enum { PING_TIMES_MAX = 4 }; + + // true if we should try connect to the server when it refreshes + bool m_bConnecting; + + // password, if entered + char m_szPassword[64]; + + // state + bool m_bServerNotResponding; + bool m_bServerFull; + bool m_bShowAutoRetryToggle; + bool m_bShowingExtendedOptions; + uint64 m_SteamIDFriend; + + CUtlString m_sConnectCode; + gameserveritem_t m_Server; + HServerQuery m_hPingQuery; + HServerQuery m_hPlayersQuery; + bool m_bPlayerListUpdatePending; +}; + +#endif // DIALOGGAMEINFO_H diff --git a/serverbrowser/DialogServerPassword.cpp b/serverbrowser/DialogServerPassword.cpp new file mode 100644 index 0000000..a5b2318 --- /dev/null +++ b/serverbrowser/DialogServerPassword.cpp @@ -0,0 +1,89 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include "pch_serverbrowser.h" + +using namespace vgui; + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +CDialogServerPassword::CDialogServerPassword(vgui::Panel *parent) : Frame(parent, "DialogServerPassword") +{ + m_iServerID = -1; + SetSize(320, 240); + SetDeleteSelfOnClose(true); + SetSizeable(false); + + m_pInfoLabel = new Label(this, "InfoLabel", "#ServerBrowser_ServerRequiresPassword"); + m_pGameLabel = new Label(this, "GameLabel", "<game label>"); + m_pPasswordEntry = new TextEntry(this, "PasswordEntry"); + m_pConnectButton = new Button(this, "ConnectButton", "#ServerBrowser_Connect"); + m_pPasswordEntry->SetTextHidden(true); + + LoadControlSettings("Servers/DialogServerPassword.res"); + + SetTitle("#ServerBrowser_ServerRequiresPasswordTitle", true); + + // set our initial position in the middle of the workspace + MoveToCenterOfScreen(); +} + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +CDialogServerPassword::~CDialogServerPassword() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: initializes the dialog and brings it to the foreground +//----------------------------------------------------------------------------- +void CDialogServerPassword::Activate(const char *serverName, unsigned int serverID) +{ + m_pGameLabel->SetText(serverName); + m_iServerID = serverID; + + m_pConnectButton->SetAsDefaultButton(true); + m_pPasswordEntry->RequestFocus(); + BaseClass::Activate(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *command - +//----------------------------------------------------------------------------- +void CDialogServerPassword::OnCommand(const char *command) +{ + bool bClose = false; + + if (!Q_stricmp(command, "Connect")) + { + KeyValues *msg = new KeyValues("JoinServerWithPassword"); + char buf[64]; + m_pPasswordEntry->GetText(buf, sizeof(buf)-1); + msg->SetString("password", buf); + msg->SetInt("serverID", m_iServerID); + PostActionSignal(msg); + + bClose = true; + } + else if (!Q_stricmp(command, "Close")) + { + bClose = true; + } + else + { + BaseClass::OnCommand(command); + } + + if (bClose) + { + PostMessage(this, new KeyValues("Close")); + } +} + diff --git a/serverbrowser/DialogServerPassword.h b/serverbrowser/DialogServerPassword.h new file mode 100644 index 0000000..eae8dd3 --- /dev/null +++ b/serverbrowser/DialogServerPassword.h @@ -0,0 +1,46 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef DIALOGSERVERPASSWORD_H +#define DIALOGSERVERPASSWORD_H +#ifdef _WIN32 +#pragma once +#endif + +//----------------------------------------------------------------------------- +// Purpose: Prompt for user to enter a password to be able to connect to the server +//----------------------------------------------------------------------------- +class CDialogServerPassword : public vgui::Frame +{ +public: + CDialogServerPassword(vgui::Panel *parent); + ~CDialogServerPassword(); + + // initializes the dialog and brings it to the foreground + void Activate(const char *serverName, unsigned int serverID); + + /* message returned: + "JoinServerWithPassword" + "serverID" + "password" + */ + +private: + virtual void OnCommand(const char *command); + + vgui::Label *m_pInfoLabel; + vgui::Label *m_pGameLabel; + vgui::TextEntry *m_pPasswordEntry; + vgui::Button *m_pConnectButton; + + typedef vgui::Frame BaseClass; + + int m_iServerID; +}; + + +#endif // DIALOGSERVERPASSWORD_H diff --git a/serverbrowser/FavoriteGames.cpp b/serverbrowser/FavoriteGames.cpp new file mode 100644 index 0000000..0149baf --- /dev/null +++ b/serverbrowser/FavoriteGames.cpp @@ -0,0 +1,214 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include "pch_serverbrowser.h" + +using namespace vgui; + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +CFavoriteGames::CFavoriteGames(vgui::Panel *parent) : + CBaseGamesPage(parent, "FavoriteGames", eFavoritesServer ) +{ + m_bRefreshOnListReload = false; +} + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +CFavoriteGames::~CFavoriteGames() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: loads favorites list from disk +//----------------------------------------------------------------------------- +void CFavoriteGames::LoadFavoritesList() +{ + if ( steamapicontext->SteamMatchmaking() && steamapicontext->SteamMatchmaking()->GetFavoriteGameCount() == 0 ) + { + // set empty message + m_pGameList->SetEmptyListText("#ServerBrowser_NoFavoriteServers"); + } + else + { + m_pGameList->SetEmptyListText("#ServerBrowser_NoInternetGamesResponded"); + + } + + if ( m_bRefreshOnListReload ) + { + m_bRefreshOnListReload = false; + StartRefresh(); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: returns true if the game list supports the specified ui elements +//----------------------------------------------------------------------------- +bool CFavoriteGames::SupportsItem(InterfaceItem_e item) +{ + switch (item) + { + case FILTERS: + case ADDSERVER: + return true; + + case ADDCURRENTSERVER: + return !IsSteam() && BFiltersVisible(); + + case GETNEWLIST: + default: + return false; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: called when the current refresh list is complete +//----------------------------------------------------------------------------- +void CFavoriteGames::RefreshComplete( HServerListRequest hReq, EMatchMakingServerResponse response ) +{ + SetRefreshing(false); + if ( steamapicontext->SteamMatchmaking() && steamapicontext->SteamMatchmaking()->GetFavoriteGameCount() == 0 ) + { + // set empty message + m_pGameList->SetEmptyListText("#ServerBrowser_NoFavoriteServers"); + } + else + { + m_pGameList->SetEmptyListText("#ServerBrowser_NoInternetGamesResponded"); + + } + m_pGameList->SortList(); + + BaseClass::RefreshComplete( hReq, response ); +} + +//----------------------------------------------------------------------------- +// Purpose: opens context menu (user right clicked on a server) +//----------------------------------------------------------------------------- +void CFavoriteGames::OnOpenContextMenu(int itemID) +{ + CServerContextMenu *menu = ServerBrowserDialog().GetContextMenu(GetActiveList()); + + // get the server + int serverID = GetSelectedServerID(); + + if ( serverID != -1 ) + { + // Activate context menu + menu->ShowMenu(this, serverID, true, true, true, false); + menu->AddMenuItem("RemoveServer", "#ServerBrowser_RemoveServerFromFavorites", new KeyValues("RemoveFromFavorites"), this); + } + else + { + // no selected rows, so don't display default stuff in menu + menu->ShowMenu( this,(uint32)-1, false, false, false, false ); + } + + menu->AddMenuItem("AddServerByName", "#ServerBrowser_AddServerByIP", new KeyValues("AddServerByName"), this); +} + + +//----------------------------------------------------------------------------- +// Purpose: removes a server from the favorites +//----------------------------------------------------------------------------- +void CFavoriteGames::OnRemoveFromFavorites() +{ + if ( !steamapicontext->SteamMatchmakingServers() || !steamapicontext->SteamMatchmaking() ) + return; + + // iterate the selection + for ( int iGame = 0; iGame < m_pGameList->GetSelectedItemsCount(); iGame++ ) + { + int itemID = m_pGameList->GetSelectedItem( iGame ); + int serverID = m_pGameList->GetItemData(itemID)->userData; + + gameserveritem_t *pServer = steamapicontext->SteamMatchmakingServers()->GetServerDetails( m_hRequest, serverID ); + + if ( pServer ) + { + steamapicontext->SteamMatchmaking()->RemoveFavoriteGame( pServer->m_nAppID, pServer->m_NetAdr.GetIP(), pServer->m_NetAdr.GetConnectionPort(), pServer->m_NetAdr.GetQueryPort(), k_unFavoriteFlagFavorite ); + } + } + + UpdateStatus(); + InvalidateLayout(); + Repaint(); +} + +//----------------------------------------------------------------------------- +// Purpose: Adds a server by IP address +//----------------------------------------------------------------------------- +void CFavoriteGames::OnAddServerByName() +{ + // open the add server dialog + CDialogAddServer *dlg = new CDialogAddServer( &ServerBrowserDialog(), this ); + dlg->MoveToCenterOfScreen(); + dlg->DoModal(); +} + +//----------------------------------------------------------------------------- +// Purpose: Adds the currently connected server to the list +//----------------------------------------------------------------------------- +void CFavoriteGames::OnAddCurrentServer() +{ + gameserveritem_t *pConnected = ServerBrowserDialog().GetCurrentConnectedServer(); + + if ( pConnected && steamapicontext->SteamMatchmaking() ) + { + steamapicontext->SteamMatchmaking()->AddFavoriteGame( pConnected->m_nAppID, pConnected->m_NetAdr.GetIP(), pConnected->m_NetAdr.GetConnectionPort(), pConnected->m_NetAdr.GetQueryPort(), k_unFavoriteFlagFavorite, time( NULL ) ); + m_bRefreshOnListReload = true; + + if ( GameSupportsReplay() ) + { + // send command to propagate to the client so the client can send it on to the GC + char command[ 256 ]; + Q_snprintf( command, Q_ARRAYSIZE( command ), "rfgc %s\n", pConnected->m_NetAdr.GetConnectionAddressString() ); + g_pRunGameEngine->AddTextCommand( command ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Parse posted messages +// +//----------------------------------------------------------------------------- +void CFavoriteGames::OnCommand(const char *command) +{ + if (!Q_stricmp(command, "AddServerByName")) + { + OnAddServerByName(); + } + else if (!Q_stricmp(command, "AddCurrentServer" )) + { + OnAddCurrentServer(); + } + else + { + BaseClass::OnCommand(command); + } +} + +//----------------------------------------------------------------------------- +// Purpose: enables adding server +//----------------------------------------------------------------------------- +void CFavoriteGames::OnConnectToGame() +{ + m_pAddCurrentServer->SetEnabled( true ); +} + +//----------------------------------------------------------------------------- +// Purpose: disables adding current server +//----------------------------------------------------------------------------- +void CFavoriteGames::OnDisconnectFromGame( void ) +{ + m_pAddCurrentServer->SetEnabled( false ); +} diff --git a/serverbrowser/FavoriteGames.h b/serverbrowser/FavoriteGames.h new file mode 100644 index 0000000..8355005 --- /dev/null +++ b/serverbrowser/FavoriteGames.h @@ -0,0 +1,55 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef FAVORITEGAMES_H +#define FAVORITEGAMES_H +#ifdef _WIN32 +#pragma once +#endif + +//----------------------------------------------------------------------------- +// Purpose: Favorite games list +//----------------------------------------------------------------------------- +class CFavoriteGames : public CBaseGamesPage +{ + DECLARE_CLASS_SIMPLE( CFavoriteGames, CBaseGamesPage ); + +public: + CFavoriteGames(vgui::Panel *parent); + ~CFavoriteGames(); + + // favorites list, loads/saves into keyvalues + void LoadFavoritesList(); + + // IGameList handlers + // returns true if the game list supports the specified ui elements + virtual bool SupportsItem(InterfaceItem_e item); + + // called when the current refresh list is complete + virtual void RefreshComplete( HServerListRequest hReq, EMatchMakingServerResponse response ); + + // passed from main server browser window instead of messages + void OnConnectToGame(); + void OnDisconnectFromGame( void ); + + void SetRefreshOnReload() { m_bRefreshOnListReload = true; } + +private: + // context menu message handlers + MESSAGE_FUNC_INT( OnOpenContextMenu, "OpenContextMenu", itemID ); + MESSAGE_FUNC( OnRemoveFromFavorites, "RemoveFromFavorites" ); + MESSAGE_FUNC( OnAddServerByName, "AddServerByName" ); + + void OnAddCurrentServer( void ); + + void OnCommand(const char *command); + + bool m_bRefreshOnListReload; +}; + + +#endif // FAVORITEGAMES_H diff --git a/serverbrowser/FriendsGames.cpp b/serverbrowser/FriendsGames.cpp new file mode 100644 index 0000000..daa14be --- /dev/null +++ b/serverbrowser/FriendsGames.cpp @@ -0,0 +1,88 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include "pch_serverbrowser.h" + +using namespace vgui; + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +CFriendsGames::CFriendsGames(vgui::Panel *parent) : + CBaseGamesPage(parent, "FriendsGames", eFriendsServer ) +{ + m_iServerRefreshCount = 0; + + if ( !IsSteamGameServerBrowsingEnabled() ) + { + m_pGameList->SetEmptyListText("#ServerBrowser_OfflineMode"); + m_pConnect->SetEnabled( false ); + m_pRefreshAll->SetEnabled( false ); + m_pRefreshQuick->SetEnabled( false ); + m_pAddServer->SetEnabled( false ); + m_pFilter->SetEnabled( false ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +CFriendsGames::~CFriendsGames() +{ +} + + +//----------------------------------------------------------------------------- +// Purpose: returns true if the game list supports the specified ui elements +//----------------------------------------------------------------------------- +bool CFriendsGames::SupportsItem(InterfaceItem_e item) +{ + switch (item) + { + case FILTERS: + return true; + + case GETNEWLIST: + default: + return false; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: called when the current refresh list is complete +//----------------------------------------------------------------------------- +void CFriendsGames::RefreshComplete( HServerListRequest hReq, EMatchMakingServerResponse response ) +{ + SetRefreshing(false); + m_pGameList->SortList(); + m_iServerRefreshCount = 0; + + if ( IsSteamGameServerBrowsingEnabled() ) + { + // set empty message + m_pGameList->SetEmptyListText("#ServerBrowser_NoFriendsServers"); + } + + BaseClass::RefreshComplete( hReq, response ); +} + +//----------------------------------------------------------------------------- +// Purpose: opens context menu (user right clicked on a server) +//----------------------------------------------------------------------------- +void CFriendsGames::OnOpenContextMenu(int itemID) +{ + // get the server + int serverID = GetSelectedServerID(); + + if ( serverID == -1 ) + return; + + // Activate context menu + CServerContextMenu *menu = ServerBrowserDialog().GetContextMenu(GetActiveList()); + menu->ShowMenu(this, serverID, true, true, true, true); +} diff --git a/serverbrowser/FriendsGames.h b/serverbrowser/FriendsGames.h new file mode 100644 index 0000000..84c383d --- /dev/null +++ b/serverbrowser/FriendsGames.h @@ -0,0 +1,39 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef FRIENDSGAMES_H +#define FRIENDSGAMES_H +#ifdef _WIN32 +#pragma once +#endif + +//----------------------------------------------------------------------------- +// Purpose: Favorite games list +//----------------------------------------------------------------------------- +class CFriendsGames : public CBaseGamesPage +{ + DECLARE_CLASS_SIMPLE( CFriendsGames, CBaseGamesPage ); + +public: + CFriendsGames(vgui::Panel *parent); + ~CFriendsGames(); + + // IGameList handlers + // returns true if the game list supports the specified ui elements + virtual bool SupportsItem(InterfaceItem_e item); + + // called when the current refresh list is complete + virtual void RefreshComplete( HServerListRequest hReq, EMatchMakingServerResponse response ); + +private: + // context menu message handlers + MESSAGE_FUNC_INT( OnOpenContextMenu, "OpenContextMenu", itemID ); + + int m_iServerRefreshCount; // number of servers refreshed +}; + +#endif // FRIENDSGAMES_H diff --git a/serverbrowser/HistoryGames.cpp b/serverbrowser/HistoryGames.cpp new file mode 100644 index 0000000..565c313 --- /dev/null +++ b/serverbrowser/HistoryGames.cpp @@ -0,0 +1,136 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#include "pch_serverbrowser.h" + +using namespace vgui; + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +CHistoryGames::CHistoryGames(vgui::Panel *parent) : + CBaseGamesPage(parent, "HistoryGames", eHistoryServer ) +{ + m_bRefreshOnListReload = false; + m_pGameList->AddColumnHeader(10, "LastPlayed", "#ServerBrowser_LastPlayed", 100); + m_pGameList->SetSortFunc(10, LastPlayedCompare); + m_pGameList->SetSortColumn(10); + + if ( !IsSteamGameServerBrowsingEnabled() ) + { + m_pGameList->SetEmptyListText("#ServerBrowser_OfflineMode"); + m_pConnect->SetEnabled( false ); + m_pRefreshAll->SetEnabled( false ); + m_pRefreshQuick->SetEnabled( false ); + m_pAddServer->SetEnabled( false ); + m_pFilter->SetEnabled( false ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +CHistoryGames::~CHistoryGames() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: loads favorites list from disk +//----------------------------------------------------------------------------- +void CHistoryGames::LoadHistoryList() +{ + if ( IsSteamGameServerBrowsingEnabled() ) + { + // set empty message + m_pGameList->SetEmptyListText("#ServerBrowser_NoServersPlayed"); + } + + if ( m_bRefreshOnListReload ) + { + m_bRefreshOnListReload = false; + StartRefresh(); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: returns true if the game list supports the specified ui elements +//----------------------------------------------------------------------------- +bool CHistoryGames::SupportsItem(InterfaceItem_e item) +{ + switch (item) + { + case FILTERS: + return true; + + case ADDSERVER: + case GETNEWLIST: + default: + return false; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: called when the current refresh list is complete +//----------------------------------------------------------------------------- +void CHistoryGames::RefreshComplete( HServerListRequest hReq, EMatchMakingServerResponse response ) +{ + SetRefreshing(false); + m_pGameList->SetEmptyListText("#ServerBrowser_NoServersPlayed"); + m_pGameList->SortList(); + + BaseClass::RefreshComplete( hReq, response ); +} + +//----------------------------------------------------------------------------- +// Purpose: opens context menu (user right clicked on a server) +//----------------------------------------------------------------------------- +void CHistoryGames::OnOpenContextMenu(int itemID) +{ + CServerContextMenu *menu = ServerBrowserDialog().GetContextMenu(GetActiveList()); + + // get the server + int serverID = GetSelectedServerID(); + + if( serverID != -1 ) + { + // Activate context menu + menu->ShowMenu(this, serverID, true, true, true, true); + menu->AddMenuItem("RemoveServer", "#ServerBrowser_RemoveServerFromHistory", new KeyValues("RemoveFromHistory"), this); + } + else + { + // no selected rows, so don't display default stuff in menu + menu->ShowMenu(this, (uint32)-1, false, false, false, false); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: removes a server from the favorites +//----------------------------------------------------------------------------- +void CHistoryGames::OnRemoveFromHistory() +{ + if ( !steamapicontext->SteamMatchmakingServers() || !steamapicontext->SteamMatchmaking() ) + return; + + // iterate the selection + for ( int i = m_pGameList->GetSelectedItemsCount() - 1; i >= 0; i-- ) + { + int itemID = m_pGameList->GetSelectedItem( i ); + int serverID = m_pGameList->GetItemData(itemID)->userData; + + gameserveritem_t *pServer = steamapicontext->SteamMatchmakingServers()->GetServerDetails( m_hRequest, serverID ); + if ( pServer ) + steamapicontext->SteamMatchmaking()->RemoveFavoriteGame( pServer->m_nAppID, pServer->m_NetAdr.GetIP(), pServer->m_NetAdr.GetConnectionPort(), pServer->m_NetAdr.GetQueryPort(), k_unFavoriteFlagHistory ); + } + + UpdateStatus(); + InvalidateLayout(); + Repaint(); +} + diff --git a/serverbrowser/HistoryGames.h b/serverbrowser/HistoryGames.h new file mode 100644 index 0000000..6dbbf44 --- /dev/null +++ b/serverbrowser/HistoryGames.h @@ -0,0 +1,46 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#ifndef HISTORYGAMES_H +#define HISTORYGAMES_H +#ifdef _WIN32 +#pragma once +#endif + +//----------------------------------------------------------------------------- +// Purpose: History of all the servers joined +//----------------------------------------------------------------------------- +class CHistoryGames : public CBaseGamesPage +{ + DECLARE_CLASS_SIMPLE( CHistoryGames, CBaseGamesPage ); + +public: + CHistoryGames(vgui::Panel *parent); + ~CHistoryGames(); + + // favorites list, loads/saves into keyvalues + void LoadHistoryList(); + + + // IGameList handlers + // returns true if the game list supports the specified ui elements + virtual bool SupportsItem(InterfaceItem_e item); + + // called when the current refresh list is complete + virtual void RefreshComplete( HServerListRequest hReq, EMatchMakingServerResponse response ); + + void SetRefreshOnReload() { m_bRefreshOnListReload = true; } + +private: + // context menu message handlers + MESSAGE_FUNC_INT( OnOpenContextMenu, "OpenContextMenu", itemID ); + MESSAGE_FUNC( OnRemoveFromHistory, "RemoveFromHistory" ); + + bool m_bRefreshOnListReload; +}; + + +#endif // HISTORYGAMES_H diff --git a/serverbrowser/InternetGames.cpp b/serverbrowser/InternetGames.cpp new file mode 100644 index 0000000..208bea4 --- /dev/null +++ b/serverbrowser/InternetGames.cpp @@ -0,0 +1,319 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include "pch_serverbrowser.h" + +using namespace vgui; + +// How often to re-sort the server list +const float MINIMUM_SORT_TIME = 1.5f; + +//----------------------------------------------------------------------------- +// Purpose: Constructor +// NOTE: m_Servers can not use more than 96 sockets, else it will +// cause internet explorer to Stop working under win98 SE! +//----------------------------------------------------------------------------- +CInternetGames::CInternetGames(vgui::Panel *parent, const char *panelName, EPageType eType ) : + CBaseGamesPage(parent, panelName, eType ) +{ + m_fLastSort = 0.0f; + m_bDirty = false; + m_bRequireUpdate = true; + m_bOfflineMode = !IsSteamGameServerBrowsingEnabled(); + + m_bAnyServersRetrievedFromMaster = false; + m_bNoServersListedOnMaster = false; + m_bAnyServersRespondedToQuery = false; + + m_pLocationFilter->DeleteAllItems(); + KeyValues *kv = new KeyValues("Regions"); + if (kv->LoadFromFile( g_pFullFileSystem, "servers/Regions.vdf", NULL)) + { + // iterate the list loading all the servers + for (KeyValues *srv = kv->GetFirstSubKey(); srv != NULL; srv = srv->GetNextKey()) + { + struct regions_s region; + + region.name = srv->GetString("text"); + region.code = srv->GetInt("code"); + KeyValues *regionKV = new KeyValues("region", "code", region.code); + m_pLocationFilter->AddItem( region.name.String(), regionKV ); + regionKV->deleteThis(); + m_Regions.AddToTail(region); + } + } + else + { + Assert(!("Could not load file servers/Regions.vdf; server browser will not function.")); + } + kv->deleteThis(); + + LoadFilterSettings(); + + ivgui()->AddTickSignal( GetVPanel(), 250 ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +CInternetGames::~CInternetGames() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CInternetGames::PerformLayout() +{ + if ( !m_bOfflineMode && m_bRequireUpdate && ServerBrowserDialog().IsVisible() ) + { + PostMessage( this, new KeyValues( "GetNewServerList" ), 0.1f ); + m_bRequireUpdate = false; + } + + if ( m_bOfflineMode ) + { + m_pGameList->SetEmptyListText("#ServerBrowser_OfflineMode"); + m_pConnect->SetEnabled( false ); + m_pRefreshAll->SetEnabled( false ); + m_pRefreshQuick->SetEnabled( false ); + m_pAddServer->SetEnabled( false ); + m_pFilter->SetEnabled( false ); + } + + BaseClass::PerformLayout(); + m_pLocationFilter->SetEnabled(true); +} + +//----------------------------------------------------------------------------- +// Purpose: Activates the page, starts refresh if needed +//----------------------------------------------------------------------------- +void CInternetGames::OnPageShow() +{ + if ( m_pGameList->GetItemCount() == 0 && ServerBrowserDialog().IsVisible() ) + BaseClass::OnPageShow(); + // the "internet games" tab (unlike the other browser tabs) + // does not automatically start a query when the user + // navigates to this tab unless they have no servers listed. +} + + +//----------------------------------------------------------------------------- +// Purpose: Called every frame, maintains sockets and runs refreshes +//----------------------------------------------------------------------------- +void CInternetGames::OnTick() +{ + if ( m_bOfflineMode ) + { + BaseClass::OnTick(); + return; + } + + BaseClass::OnTick(); + + CheckRedoSort(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Handles incoming server refresh data +// updates the server browser with the refreshed information from the server itself +//----------------------------------------------------------------------------- +void CInternetGames::ServerResponded( HServerListRequest hReq, int iServer ) +{ + m_bDirty = true; + BaseClass::ServerResponded( hReq, iServer ); + m_bAnyServersRespondedToQuery = true; + m_bAnyServersRetrievedFromMaster = true; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CInternetGames::ServerFailedToRespond( HServerListRequest hReq, int iServer ) +{ + m_bDirty = true; + gameserveritem_t *pServer = steamapicontext->SteamMatchmakingServers()->GetServerDetails( hReq, iServer ); + Assert( pServer ); + + if ( pServer->m_bHadSuccessfulResponse ) + { + // if it's had a successful response in the past, leave it on + ServerResponded( hReq, iServer ); + } + else + { + int iServerMap = m_mapServers.Find( iServer ); + if ( iServerMap != m_mapServers.InvalidIndex() ) + RemoveServer( m_mapServers[ iServerMap ] ); + // we've never had a good response from this server, remove it from the list + m_iServerRefreshCount++; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Called when server refresh has been completed +//----------------------------------------------------------------------------- +void CInternetGames::RefreshComplete( HServerListRequest hReq, EMatchMakingServerResponse response ) +{ + SetRefreshing(false); + UpdateFilterSettings(); + + if ( response != eServerFailedToRespond ) + { + if ( m_bAnyServersRespondedToQuery ) + { + m_pGameList->SetEmptyListText( GetStringNoUnfilteredServers() ); + } + else if ( response == eNoServersListedOnMasterServer ) + { + m_pGameList->SetEmptyListText( GetStringNoUnfilteredServersOnMaster() ); + } + else + { + m_pGameList->SetEmptyListText( GetStringNoServersResponded() ); + } + } + else + { + m_pGameList->SetEmptyListText("#ServerBrowser_MasterServerNotResponsive"); + } + + // perform last sort + m_bDirty = false; + m_fLastSort = Plat_FloatTime(); + if (IsVisible()) + { + m_pGameList->SortList(); + } + + UpdateStatus(); + + BaseClass::RefreshComplete( hReq, response ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CInternetGames::GetNewServerList() +{ + BaseClass::GetNewServerList(); + UpdateStatus(); + + m_bRequireUpdate = false; + m_bAnyServersRetrievedFromMaster = false; + m_bAnyServersRespondedToQuery = false; + + m_pGameList->DeleteAllItems(); +} + + +//----------------------------------------------------------------------------- +// Purpose: returns true if the game list supports the specified ui elements +//----------------------------------------------------------------------------- +bool CInternetGames::SupportsItem(IGameList::InterfaceItem_e item) +{ + switch (item) + { + case FILTERS: + case GETNEWLIST: + return true; + + default: + return false; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CInternetGames::CheckRedoSort( void ) +{ + float fCurTime; + + // No changes detected + if ( !m_bDirty ) + return; + + fCurTime = Plat_FloatTime(); + // Not time yet + if ( fCurTime - m_fLastSort < MINIMUM_SORT_TIME) + return; + + // postpone sort if mouse button is down + if ( input()->IsMouseDown(MOUSE_LEFT) || input()->IsMouseDown(MOUSE_RIGHT) ) + { + // don't sort for at least another second + m_fLastSort = fCurTime - MINIMUM_SORT_TIME + 1.0f; + return; + } + + // Reset timer + m_bDirty = false; + m_fLastSort = fCurTime; + + // Force sort to occur now! + m_pGameList->SortList(); +} + + +//----------------------------------------------------------------------------- +// Purpose: opens context menu (user right clicked on a server) +//----------------------------------------------------------------------------- +void CInternetGames::OnOpenContextMenu(int itemID) +{ + // get the server + int serverID = GetSelectedServerID(); + + if ( serverID == -1 ) + return; + + // Activate context menu + CServerContextMenu *menu = ServerBrowserDialog().GetContextMenu(GetActiveList()); + menu->ShowMenu(this, serverID, true, true, true, true); +} + +//----------------------------------------------------------------------------- +// Purpose: refreshes a single server +//----------------------------------------------------------------------------- +void CInternetGames::OnRefreshServer(int serverID) +{ + BaseClass::OnRefreshServer( serverID ); + + ServerBrowserDialog().UpdateStatusText("#ServerBrowser_GettingNewServerList"); +} + + +//----------------------------------------------------------------------------- +// Purpose: get the region code selected in the ui +// Output: returns the region code the user wants to filter by +//----------------------------------------------------------------------------- +int CInternetGames::GetRegionCodeToFilter() +{ + KeyValues *kv = m_pLocationFilter->GetActiveItemUserData(); + if ( kv ) + return kv->GetInt( "code" ); + else + return 255; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CInternetGames::CheckTagFilter( gameserveritem_t &server ) +{ + // Servers without tags go in the official games, servers with tags go in custom games + bool bOfficialServer = !( server.m_szGameTags && server.m_szGameTags[0] ); + if ( !bOfficialServer ) + return false; + + return true; +} diff --git a/serverbrowser/InternetGames.h b/serverbrowser/InternetGames.h new file mode 100644 index 0000000..8779d1f --- /dev/null +++ b/serverbrowser/InternetGames.h @@ -0,0 +1,83 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef INTERNETGAMES_H +#define INTERNETGAMES_H +#ifdef _WIN32 +#pragma once +#endif + +#include "BaseGamesPage.h" + +//----------------------------------------------------------------------------- +// Purpose: Internet games list +//----------------------------------------------------------------------------- +class CInternetGames : public CBaseGamesPage +{ + + DECLARE_CLASS_SIMPLE( CInternetGames, CBaseGamesPage ); + +public: + CInternetGames( vgui::Panel *parent, const char *panelName = "InternetGames", EPageType eType = eInternetServer ); + ~CInternetGames(); + + // property page handlers + virtual void OnPageShow(); + + // returns true if the game list supports the specified ui elements + virtual bool SupportsItem(IGameList::InterfaceItem_e item); + + // gets a new server list + MESSAGE_FUNC( GetNewServerList, "GetNewServerList" ); + + // serverlist refresh responses + virtual void ServerResponded( HServerListRequest hReq, int iServer ); + virtual void ServerFailedToRespond( HServerListRequest hReq, int iServer ); + virtual void RefreshComplete( HServerListRequest hReq, EMatchMakingServerResponse response ); + MESSAGE_FUNC_INT( OnRefreshServer, "RefreshServer", serverID ); + + virtual int GetRegionCodeToFilter(); + virtual bool CheckTagFilter( gameserveritem_t &server ); + +protected: + // vgui overrides + virtual void PerformLayout(); + virtual void OnTick(); + + virtual const char *GetStringNoUnfilteredServers() { return "#ServerBrowser_NoInternetGames"; } + virtual const char *GetStringNoUnfilteredServersOnMaster() { return "#ServerBrowser_MasterServerHasNoServersListed"; } + virtual const char *GetStringNoServersResponded() { return "#ServerBrowser_NoInternetGamesResponded"; } + +private: + // Called once per frame to see if sorting needs to occur again + void CheckRedoSort(); + // Called once per frame to check re-send request to master server + void CheckRetryRequest( ESteamServerType serverType ); + // opens context menu (user right clicked on a server) + MESSAGE_FUNC_INT( OnOpenContextMenu, "OpenContextMenu", itemID ); + + struct regions_s + { + CUtlSymbol name; + unsigned char code; + }; + + CUtlVector<struct regions_s> m_Regions; // list of the different regions you can query for + + float m_fLastSort; // Time of last re-sort + bool m_bDirty; // Has the list been modified, thereby needing re-sort + bool m_bRequireUpdate; // checks whether we need an update upon opening + + // error cases for if no servers are listed + bool m_bAnyServersRetrievedFromMaster; + bool m_bAnyServersRespondedToQuery; + bool m_bNoServersListedOnMaster; + + bool m_bOfflineMode; +}; + +#endif // INTERNETGAMES_H diff --git a/serverbrowser/LanGames.cpp b/serverbrowser/LanGames.cpp new file mode 100644 index 0000000..c375845 --- /dev/null +++ b/serverbrowser/LanGames.cpp @@ -0,0 +1,160 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include "pch_serverbrowser.h" + +using namespace vgui; + +const float BROADCAST_LIST_TIMEOUT = 0.4f; + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +CLanGames::CLanGames(vgui::Panel *parent, bool bAutoRefresh, const char *pCustomResFilename ) : + CBaseGamesPage(parent, "LanGames", eLANServer, pCustomResFilename) +{ + m_iServerRefreshCount = 0; + m_bRequesting = false; + m_bAutoRefresh = bAutoRefresh; +} + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +CLanGames::~CLanGames() +{ +} + + +//----------------------------------------------------------------------------- +// Purpose: Activates the page, starts refresh +//----------------------------------------------------------------------------- +void CLanGames::OnPageShow() +{ + if ( m_bAutoRefresh ) + StartRefresh(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Called every frame +//----------------------------------------------------------------------------- +void CLanGames::OnTick() +{ + BaseClass::OnTick(); + CheckRetryRequest(); +} + +//----------------------------------------------------------------------------- +// Purpose: returns true if the game list supports the specified ui elements +//----------------------------------------------------------------------------- +bool CLanGames::SupportsItem(InterfaceItem_e item) +{ + switch (item) + { + case FILTERS: + return true; + + case GETNEWLIST: + default: + return false; + } +} + +//----------------------------------------------------------------------------- +// Purpose: starts the servers refreshing +//----------------------------------------------------------------------------- +void CLanGames::StartRefresh() +{ + BaseClass::StartRefresh(); + m_fRequestTime = Plat_FloatTime(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Control which button are visible. +//----------------------------------------------------------------------------- +void CLanGames::ManualShowButtons( bool bShowConnect, bool bShowRefreshAll, bool bShowFilter ) +{ + m_pConnect->SetVisible( bShowConnect ); + m_pRefreshAll->SetVisible( bShowRefreshAll ); + m_pFilter->SetVisible( bShowFilter ); +} + + +//----------------------------------------------------------------------------- +// Purpose: stops current refresh/GetNewServerList() +//----------------------------------------------------------------------------- +void CLanGames::StopRefresh() +{ + BaseClass::StopRefresh(); + // clear update states + m_bRequesting = false; +} + +//----------------------------------------------------------------------------- +// Purpose: Check to see if we've finished looking for local servers +//----------------------------------------------------------------------------- +void CLanGames::CheckRetryRequest() +{ + if (!m_bRequesting) + return; + + double curtime = Plat_FloatTime(); + if (curtime - m_fRequestTime <= BROADCAST_LIST_TIMEOUT) + { + return; + } + + // time has elapsed, finish up + m_bRequesting = false; +} + +//----------------------------------------------------------------------------- +// Purpose: called when a server response has timed out, remove it +//----------------------------------------------------------------------------- +void CLanGames::ServerFailedToRespond( HServerListRequest hReq, int iServer ) +{ + int iServerMap = m_mapServers.Find( iServer ); + if ( iServerMap != m_mapServers.InvalidIndex() ) + RemoveServer( m_mapServers[ iServerMap ] ); +} + +//----------------------------------------------------------------------------- +// Purpose: called when the current refresh list is complete +//----------------------------------------------------------------------------- +void CLanGames::RefreshComplete( HServerListRequest hReq, EMatchMakingServerResponse response ) +{ + SetRefreshing( false ); + m_pGameList->SortList(); + m_iServerRefreshCount = 0; + m_pGameList->SetEmptyListText("#ServerBrowser_NoLanServers"); + SetEmptyListText(); + + BaseClass::RefreshComplete( hReq, response ); +} + +void CLanGames::SetEmptyListText() +{ + m_pGameList->SetEmptyListText("#ServerBrowser_NoLanServers"); +} + +//----------------------------------------------------------------------------- +// Purpose: opens context menu (user right clicked on a server) +//----------------------------------------------------------------------------- +void CLanGames::OnOpenContextMenu(int row) +{ + int serverID = GetSelectedServerID(); + + if ( serverID == -1 ) + return; + + // Activate context menu + CServerContextMenu *menu = ServerBrowserDialog().GetContextMenu(GetActiveList()); + menu->ShowMenu(this, serverID, true, true, true, false); +} + diff --git a/serverbrowser/LanGames.h b/serverbrowser/LanGames.h new file mode 100644 index 0000000..f1e3afa --- /dev/null +++ b/serverbrowser/LanGames.h @@ -0,0 +1,81 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef LANGAMES_H +#define LANGAMES_H +#ifdef _WIN32 +#pragma once +#endif + +class CLanBroadcastMsgHandler; + +//----------------------------------------------------------------------------- +// Purpose: Favorite games list +//----------------------------------------------------------------------------- +class CLanGames : public CBaseGamesPage +{ + DECLARE_CLASS_SIMPLE( CLanGames, CBaseGamesPage ); + +public: + CLanGames(vgui::Panel *parent, bool bAutoRefresh=true, const char *pCustomResFilename=NULL); + ~CLanGames(); + + // property page handlers + virtual void OnPageShow(); + + // IGameList handlers + // returns true if the game list supports the specified ui elements + virtual bool SupportsItem(InterfaceItem_e item); + + // Control which button are visible. + void ManualShowButtons( bool bShowConnect, bool bShowRefreshAll, bool bShowFilter ); + + // If you pass NULL for pSpecificAddresses, it will broadcast on certain points. + // If you pass a non-null value, then it will send info queries directly to those ports. + void InternalGetNewServerList( CUtlVector<netadr_t> *pSpecificAddresses ); + + virtual void StartRefresh(); + + // stops current refresh/GetNewServerList() + virtual void StopRefresh(); + + + // IServerRefreshResponse handlers + // called when a server response has timed out + virtual void ServerFailedToRespond( HServerListRequest hReq, int iServer ); + + // called when the current refresh list is complete + virtual void RefreshComplete( HServerListRequest hReq, EMatchMakingServerResponse response ); + + // Tell the game list what to put in there when there are no games found. + virtual void SetEmptyListText(); + +private: + // vgui message handlers + virtual void OnTick(); + + // lan timeout checking + virtual void CheckRetryRequest(); + + // context menu message handlers + MESSAGE_FUNC_INT( OnOpenContextMenu, "OpenContextMenu", itemID ); + + // number of servers refreshed + int m_iServerRefreshCount; + + // true if we're broadcasting for servers + bool m_bRequesting; + + // time at which we last broadcasted + double m_fRequestTime; + + bool m_bAutoRefresh; +}; + + + +#endif // LANGAMES_H diff --git a/serverbrowser/ModList.cpp b/serverbrowser/ModList.cpp new file mode 100644 index 0000000..b95b304 --- /dev/null +++ b/serverbrowser/ModList.cpp @@ -0,0 +1,125 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include "pch_serverbrowser.h" + +//----------------------------------------------------------------------------- +// Purpose: Singleton accessor +//----------------------------------------------------------------------------- +CModList &ModList() +{ + static CModList s_ModList; + return s_ModList; +} + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +CModList::CModList() +{ + ParseSteamMods(); +} + +//----------------------------------------------------------------------------- +// Purpose: returns number of mods +//----------------------------------------------------------------------------- +int CModList::ModCount() +{ + return m_ModList.Count(); +} + +//----------------------------------------------------------------------------- +// Purpose: data accessor +//----------------------------------------------------------------------------- +const char *CModList::GetModName(int index) +{ + return m_ModList[index].description; +} + +//----------------------------------------------------------------------------- +// Purpose: data accessor +//----------------------------------------------------------------------------- +const char *CModList::GetModDir(int index) +{ + return m_ModList[index].gamedir; +} + + +//----------------------------------------------------------------------------- +// Purpose: data accessor +//----------------------------------------------------------------------------- +const CGameID &CModList::GetAppID(int index) const +{ + return m_ModList[index].m_GameID; +} + + +//----------------------------------------------------------------------------- +// Purpose: get the modlist index for this app id +//----------------------------------------------------------------------------- +int CModList::GetIndex( const CGameID &iAppID ) const +{ + mod_t mod; + mod.m_GameID = iAppID; + return m_ModList.Find( mod ); +} + + +//----------------------------------------------------------------------------- +// Purpose: returns the mod name for the associated gamedir +//----------------------------------------------------------------------------- +const char *CModList::GetModNameForModDir( const CGameID &gameID ) +{ + int iApp = GetIndex( gameID ); + if ( iApp != m_ModList.InvalidIndex() ) + { + return m_ModList[iApp].description; + } + + if ( ServerBrowserDialog().GetActiveModName() ) + { + return ServerBrowserDialog().GetActiveGameName(); + } + return ""; +} + + +//----------------------------------------------------------------------------- +// Purpose: sort the mod list in alphabetical order +//----------------------------------------------------------------------------- +int CModList::ModNameCompare( const mod_t *pLeft, const mod_t *pRight ) +{ + return ( Q_stricmp( pLeft->description, pRight->description ) ); +} + + +//----------------------------------------------------------------------------- +// Purpose: gets list of steam games we can filter for +//----------------------------------------------------------------------------- +void CModList::ParseSteamMods() +{ + +} + + +//----------------------------------------------------------------------------- +// Purpose: load settings for an app +//----------------------------------------------------------------------------- +int CModList::LoadAppConfiguration( uint32 nAppID ) +{ + + return -1; +} + + +//----------------------------------------------------------------------------- +// Purpose: add a vgui panel to message when the app list changes +//----------------------------------------------------------------------------- +void CModList::AddVGUIListener( vgui::VPANEL panel ) +{ + m_VGUIListeners.AddToTail( panel ); +}
\ No newline at end of file diff --git a/serverbrowser/ModList.h b/serverbrowser/ModList.h new file mode 100644 index 0000000..b899ad9 --- /dev/null +++ b/serverbrowser/ModList.h @@ -0,0 +1,62 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef MODLIST_H +#define MODLIST_H +#ifdef _WIN32 +#pragma once +#endif + +//----------------------------------------------------------------------------- +// Purpose: Handles parsing of half-life directory for mod info +//----------------------------------------------------------------------------- +class CModList +{ +public: + CModList(); + + int GetIndex( const CGameID &iAppID ) const; + void AddVGUIListener( vgui::VPANEL panel ); + + // returns number of mods + int ModCount(); + + // returns the full name of the mod, index valid in range [0, ModCount) + const char *GetModName( int index ); + + // returns mod directory string + const char *GetModDir( int index ); + + const CGameID &GetAppID( int index ) const; + + // returns the mod name for the associated gamedir + const char *GetModNameForModDir( const CGameID &iAppID ); + +private: + struct mod_t + { + char description[64]; + char gamedir[64]; + CGameID m_GameID; + int m_InternalAppId; + bool operator==( const mod_t& rhs ) const { return rhs.m_GameID == m_GameID; } + }; + + static int ModNameCompare( const mod_t *pLeft, const mod_t *pRight ); + void ParseInstalledMods(); + void ParseSteamMods(); + int LoadAppConfiguration( uint32 nAppID ); + + CUtlVector<mod_t> m_ModList; + CUtlVector<vgui::VPANEL> m_VGUIListeners; +}; + +// singleton accessor +extern CModList &ModList(); + + +#endif // MODLIST_H diff --git a/serverbrowser/QuickListPanel.cpp b/serverbrowser/QuickListPanel.cpp new file mode 100644 index 0000000..0b08ca0 --- /dev/null +++ b/serverbrowser/QuickListPanel.cpp @@ -0,0 +1,317 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= +#include "pch_serverbrowser.h" + +using namespace vgui; + + +//----------------------------------------------------------------------------- +// Purpose: Invisible panel that forwards up mouse movement +//----------------------------------------------------------------------------- +class CMouseMessageForwardingPanel : public vgui::Panel +{ + DECLARE_CLASS_SIMPLE( CMouseMessageForwardingPanel, vgui::Panel ); +public: + CMouseMessageForwardingPanel( Panel *parent, const char *name ); + + virtual void PerformLayout( void ); + virtual void OnMousePressed( vgui::MouseCode code ); + virtual void OnMouseDoublePressed( vgui::MouseCode code ); + virtual void OnMouseWheeled(int delta); +}; + +CMouseMessageForwardingPanel::CMouseMessageForwardingPanel( Panel *parent, const char *name ) : BaseClass( parent, name ) +{ + // don't draw an + SetPaintEnabled(false); + SetPaintBackgroundEnabled(false); + SetPaintBorderEnabled(false); +} + +void CMouseMessageForwardingPanel::PerformLayout() +{ + // fill out the whole area + int w, t; + GetParent()->GetSize(w, t); + SetBounds(0, 0, w, t); +} + +void CMouseMessageForwardingPanel::OnMousePressed( vgui::MouseCode code ) +{ + if ( GetParent() ) + { + GetParent()->OnMousePressed( code ); + } +} + +void CMouseMessageForwardingPanel::OnMouseDoublePressed( vgui::MouseCode code ) +{ + if ( GetParent() ) + { + GetParent()->OnMouseDoublePressed( code ); + } +} + +void CMouseMessageForwardingPanel::OnMouseWheeled(int delta) +{ + if ( GetParent() ) + { + GetParent()->OnMouseWheeled( delta ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CQuickListPanel::CQuickListPanel( vgui::Panel* pParent, const char *pElementName ) : BaseClass( pParent, pElementName ) +{ + SetParent( pParent ); + + m_pListPanelParent = pParent; + + CMouseMessageForwardingPanel *panel = new CMouseMessageForwardingPanel(this, NULL); + panel->SetZPos(3); + + m_pLatencyImage = new ImagePanel( this, "latencyimage" ); + m_pPlayerCountLabel = new Label( this, "playercount", "" ); + m_pOtherServersLabel = new Label( this, "otherservercount", "" ); + m_pServerNameLabel = new Label( this, "servername", "" ); + m_pBGroundPanel = new Panel( this, "background" ); + m_pMapImage = new ImagePanel( this, "mapimage" ); + m_pGameTypeLabel = new Label( this, "gametype", "" ); + m_pMapNameLabel = new Label( this, "mapname", "" ); + m_pLatencyLabel = new Label( this, "latencytext", "" ); + m_pReplayImage = new ImagePanel( this, "replayimage" ); + + const char *pPathID = "PLATFORM"; + + if ( g_pFullFileSystem->FileExists( "servers/QuickListPanel.res", "MOD" ) ) + { + pPathID = "MOD"; + } + + LoadControlSettings( "servers/QuickListPanel.res", pPathID ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CQuickListPanel::ApplySchemeSettings(IScheme *pScheme) +{ + BaseClass::ApplySchemeSettings(pScheme); + + if ( pScheme && m_pBGroundPanel ) + { + m_pBGroundPanel->SetBgColor( pScheme->GetColor("QuickListBGDeselected", Color(255, 255, 255, 0 ) ) ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CQuickListPanel::SetRefreshing( void ) +{ + if ( m_pServerNameLabel ) + { + m_pServerNameLabel->SetText( g_pVGuiLocalize->Find("#ServerBrowser_QuickListRefreshing") ); + } + + if ( m_pPlayerCountLabel ) + { + m_pPlayerCountLabel->SetVisible( false ); + } + if ( m_pOtherServersLabel ) + { + m_pOtherServersLabel->SetVisible( false ); + } + + if ( m_pLatencyImage ) + { + m_pLatencyImage->SetVisible( false ); + } + + if ( m_pReplayImage ) + { + m_pReplayImage->SetVisible( false ); + } + + if ( m_pLatencyLabel ) + { + m_pLatencyLabel->SetVisible( false ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CQuickListPanel::SetMapName( const char *pMapName ) +{ + Q_strncpy( m_szMapName, pMapName, sizeof( m_szMapName ) ); + + if ( m_pMapNameLabel ) + { + m_pMapNameLabel->SetText( pMapName ); + m_pMapNameLabel->SizeToContents(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CQuickListPanel::SetGameType( const char *pGameType ) +{ + if ( strlen ( pGameType ) == 0 ) + { + m_pGameTypeLabel->SetVisible( false ); + return; + } + + char gametype[ 512 ]; + Q_snprintf( gametype, sizeof( gametype ), "(%s)", pGameType ); + + m_pGameTypeLabel->SetText( gametype ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CQuickListPanel::SetServerInfo ( KeyValues *pKV, int iListID, int iTotalServers ) +{ + if ( pKV == NULL ) + return; + + m_iListID = iListID; + + m_pServerNameLabel->SetText( pKV->GetString( "name", " " ) ); + + int iPing = pKV->GetInt( "ping", 0 ); + + if ( iPing <= 100 ) + { + m_pLatencyImage->SetImage( "../vgui/icon_con_high.vmt" ); + } + else if ( iPing <= 150 ) + { + m_pLatencyImage->SetImage( "../vgui/icon_con_medium.vmt" ); + } + else + { + m_pLatencyImage->SetImage( "../vgui/icon_con_low.vmt" ); + } + + m_pLatencyImage->SetVisible( false ); + + if ( GameSupportsReplay() ) + { + if ( pKV->GetInt( "Replay", 0 ) > 0 ) + { + m_pReplayImage->SetVisible( true ); + } + } + + char ping[ 512 ]; + Q_snprintf( ping, sizeof( ping ), "%d ms", iPing ); + + m_pLatencyLabel->SetText( ping ); + m_pLatencyLabel->SetVisible( true ); + + wchar_t players[ 512 ]; + wchar_t playercount[16]; + wchar_t *pwszPlayers = g_pVGuiLocalize->Find("#ServerBrowser_Players"); + + g_pVGuiLocalize->ConvertANSIToUnicode( pKV->GetString( "players", " " ), playercount, sizeof( playercount ) ); + + _snwprintf( players, ARRAYSIZE( players ), L"%ls %ls", playercount, pwszPlayers ); + + m_pPlayerCountLabel->SetText( players ); + m_pPlayerCountLabel->SetVisible( true ); + + + // Now setup the other server count + if ( iTotalServers == 2 ) + { + m_pOtherServersLabel->SetText( g_pVGuiLocalize->Find("#ServerBrowser_QuickListOtherServer") ); + m_pOtherServersLabel->SetVisible( true ); + } + else if ( iTotalServers > 2 ) + { + wchar_t *pwszServers = g_pVGuiLocalize->Find("#ServerBrowser_QuickListOtherServers"); + _snwprintf( playercount, Q_ARRAYSIZE(playercount), L"%d", (iTotalServers-1) ); + g_pVGuiLocalize->ConstructString( players, sizeof( players ), pwszServers, 1, playercount ); + m_pOtherServersLabel->SetText( players ); + m_pOtherServersLabel->SetVisible( true ); + } + else + { + m_pOtherServersLabel->SetVisible( false ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CQuickListPanel::SetImage( const char *pMapName ) +{ + char path[ 512 ]; + Q_snprintf( path, sizeof( path ), "materials/vgui/maps/menu_thumb_%s.vmt", pMapName ); + + char map[ 512 ]; + Q_snprintf( map, sizeof( map ), "maps/%s.bsp", pMapName ); + + if ( g_pFullFileSystem->FileExists( map, "MOD" ) == false ) + { + pMapName = "default_download"; + } + else + { + if ( g_pFullFileSystem->FileExists( path, "MOD" ) == false ) + { + pMapName = "default"; + } + } + + if ( m_pMapImage ) + { + char imagename[ 512 ]; + Q_snprintf( imagename, sizeof( imagename ), "..\\vgui\\maps\\menu_thumb_%s", pMapName ); + + m_pMapImage->SetImage ( imagename ); + m_pMapImage->SetMouseInputEnabled( false ); + } +} + +void CQuickListPanel::OnMousePressed( vgui::MouseCode code ) +{ + if ( m_pListPanelParent ) + { + vgui::PanelListPanel *pParent = dynamic_cast < vgui::PanelListPanel *> ( m_pListPanelParent ); + + if ( pParent ) + { + pParent->SetSelectedPanel( this ); + m_pListPanelParent->CallParentFunction( new KeyValues("ItemSelected", "itemID", -1 ) ); + } + + if ( code == MOUSE_RIGHT ) + { + m_pListPanelParent->CallParentFunction( new KeyValues("OpenContextMenu", "itemID", -1 ) ); + } + + } +} + +void CQuickListPanel::OnMouseDoublePressed( vgui::MouseCode code ) +{ + if ( code == MOUSE_RIGHT ) + return; + + // call the panel + OnMousePressed( code ); + + m_pListPanelParent->CallParentFunction( new KeyValues("ConnectToServer", "code", code) ); +} diff --git a/serverbrowser/QuickListPanel.h b/serverbrowser/QuickListPanel.h new file mode 100644 index 0000000..5672b2b --- /dev/null +++ b/serverbrowser/QuickListPanel.h @@ -0,0 +1,83 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef QUICKLISTPANEL +#define QUICKLISTPANEL +#ifdef _WIN32 +#pragma once +#endif + +//----------------------------------------------------------------------------- +// Purpose: Spectator games list +//----------------------------------------------------------------------------- +class CQuickListPanel : public vgui::EditablePanel +{ + DECLARE_CLASS_SIMPLE( CQuickListPanel, vgui::EditablePanel ); + +public: + CQuickListPanel( vgui::Panel *parent, const char *panelName ); + + virtual void ApplySchemeSettings(vgui::IScheme *pScheme); + void SetMapName( const char *pMapName ); + void SetImage( const char *pMapName ); + void SetGameType( const char *pGameType ); + const char *GetMapName( void ) { return m_szMapName; } + void SetRefreshing( void ); + + virtual void OnMousePressed( vgui::MouseCode code ); + virtual void OnMouseDoublePressed( vgui::MouseCode code ); + void SetServerInfo ( KeyValues *pKV, int iListID, int iTotalServers ); + int GetListID( void ) { return m_iListID; } + + + MESSAGE_FUNC_INT( OnPanelSelected, "PanelSelected", state ) + { + if ( state ) + { + vgui::IScheme *pScheme = vgui::scheme()->GetIScheme( GetScheme() ); + + if ( pScheme && m_pBGroundPanel ) + { + m_pBGroundPanel->SetBgColor( pScheme->GetColor("QuickListBGSelected", Color(255, 255, 255, 0 ) ) ); + } + } + else + { + vgui::IScheme *pScheme = vgui::scheme()->GetIScheme( GetScheme() ); + + if ( pScheme && m_pBGroundPanel ) + { + m_pBGroundPanel->SetBgColor( pScheme->GetColor("QuickListBGDeselected", Color(255, 255, 255, 0 ) ) ); + } + } + + PostMessage( GetParent()->GetVParent(), new KeyValues("PanelSelected") ); + } + +private: + + char m_szMapName[128]; + + vgui::ImagePanel *m_pLatencyImage; + vgui::Label *m_pLatencyLabel; + vgui::Label *m_pPlayerCountLabel; + vgui::Label *m_pOtherServersLabel; + vgui::Label *m_pServerNameLabel; + vgui::Panel *m_pBGroundPanel; + vgui::ImagePanel *m_pMapImage; + + vgui::Panel *m_pListPanelParent; + vgui::Label *m_pGameTypeLabel; + vgui::Label *m_pMapNameLabel; + + vgui::ImagePanel *m_pReplayImage; + + int m_iListID; +}; + + +#endif // QUICKLISTPANEL diff --git a/serverbrowser/ServerBrowser.cpp b/serverbrowser/ServerBrowser.cpp new file mode 100644 index 0000000..ff66bbb --- /dev/null +++ b/serverbrowser/ServerBrowser.cpp @@ -0,0 +1,489 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include "pch_serverbrowser.h" + +// expose the server browser interfaces +CServerBrowser g_ServerBrowserSingleton; +EXPOSE_SINGLE_INTERFACE_GLOBALVAR(CServerBrowser, IServerBrowser, SERVERBROWSER_INTERFACE_VERSION, g_ServerBrowserSingleton); +EXPOSE_SINGLE_INTERFACE_GLOBALVAR(CServerBrowser, IVGuiModule, "VGuiModuleServerBrowser001", g_ServerBrowserSingleton); // the interface loaded by PlatformMenu.vdf + +// singleton accessor +CServerBrowser &ServerBrowser() +{ + return g_ServerBrowserSingleton; +} + +IRunGameEngine *g_pRunGameEngine = NULL; + +static CSteamAPIContext g_SteamAPIContext; +CSteamAPIContext *steamapicontext = &g_SteamAPIContext; + +IEngineReplay *g_pEngineReplay = NULL; + +ConVar sb_firstopentime( "sb_firstopentime", "0", FCVAR_DEVELOPMENTONLY, "Indicates the time the server browser was first opened." ); +ConVar sb_numtimesopened( "sb_numtimesopened", "0", FCVAR_DEVELOPMENTONLY, "Indicates the number of times the server browser was opened this session." ); + +// the original author of this code felt strdup was not acceptible. +inline char *CloneString( const char *str ) +{ + char *cloneStr = new char [ strlen(str)+1 ]; + strcpy( cloneStr, str ); + return cloneStr; +} + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +CServerBrowser::CServerBrowser() +{ +} + + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +CServerBrowser::~CServerBrowser() +{ +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CServerBrowser::CreateDialog() +{ + if (!m_hInternetDlg.Get()) + { + m_hInternetDlg = new CServerBrowserDialog(NULL); // SetParent() call below fills this in + m_hInternetDlg->Initialize(); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: links to vgui and engine interfaces +//----------------------------------------------------------------------------- +bool CServerBrowser::Initialize(CreateInterfaceFn *factorylist, int factoryCount) +{ + ConnectTier1Libraries( factorylist, factoryCount ); + ConVar_Register(); + ConnectTier2Libraries( factorylist, factoryCount ); + ConnectTier3Libraries( factorylist, factoryCount ); + g_pRunGameEngine = NULL; + + for ( int i = 0; i < factoryCount; ++i ) + { + if ( !g_pEngineReplay ) + { + g_pEngineReplay = ( IEngineReplay * )factorylist[ i ]( ENGINE_REPLAY_INTERFACE_VERSION, NULL ); + } + } + + SteamAPI_InitSafe(); + SteamAPI_SetTryCatchCallbacks( false ); // We don't use exceptions, so tell steam not to use try/catch in callback handlers + steamapicontext->Init(); + + for (int i = 0; i < factoryCount; i++) + { + if (!g_pRunGameEngine) + { + g_pRunGameEngine = (IRunGameEngine *)(factorylist[i])(RUNGAMEENGINE_INTERFACE_VERSION, NULL); + } + } + + // load the vgui interfaces +#if defined( STEAM ) || defined( HL1 ) + if ( !vgui::VGuiControls_Init("ServerBrowser", factorylist, factoryCount) ) +#else + if ( !vgui::VGui_InitInterfacesList("ServerBrowser", factorylist, factoryCount) ) +#endif + return false; + + // load localization file + g_pVGuiLocalize->AddFile( "servers/serverbrowser_%language%.txt" ); + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: links to other modules interfaces (tracker) +//----------------------------------------------------------------------------- +bool CServerBrowser::PostInitialize(CreateInterfaceFn *modules, int factoryCount) +{ + // find the interfaces we need + for (int i = 0; i < factoryCount; i++) + { + if (!g_pRunGameEngine) + { + g_pRunGameEngine = (IRunGameEngine *)(modules[i])(RUNGAMEENGINE_INTERFACE_VERSION, NULL); + } + } + + CreateDialog(); + m_hInternetDlg->SetVisible(false); + + return g_pRunGameEngine; +} + + +//----------------------------------------------------------------------------- +// Purpose: true if the user can't play a game due to VAC banning +//----------------------------------------------------------------------------- +bool CServerBrowser::IsVACBannedFromGame( int nAppID ) +{ + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Marks that the tool/game loading us intends to feed us workshop information +//----------------------------------------------------------------------------- +void CServerBrowser::SetWorkshopEnabled( bool bManaged ) +{ + m_bWorkshopEnabled = bManaged; +} + +//----------------------------------------------------------------------------- +// Purpose: Add a mapname to our known user-subscribed workshop maps list +//----------------------------------------------------------------------------- +void CServerBrowser::AddWorkshopSubscribedMap( const char *pszMapName ) +{ + CUtlString strMap( pszMapName ); + if ( !IsWorkshopSubscribedMap( strMap ) ) + { + m_vecWorkshopSubscribedMaps.AddToTail( strMap ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: remove a mapname to our known user-subscribed workshop maps list +//----------------------------------------------------------------------------- +void CServerBrowser::RemoveWorkshopSubscribedMap( const char *pszMapName ) +{ + m_vecWorkshopSubscribedMaps.FindAndFastRemove( CUtlString( pszMapName ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: Well, is it? +//----------------------------------------------------------------------------- +bool CServerBrowser::IsWorkshopEnabled() +{ + return m_bWorkshopEnabled; +} + +//----------------------------------------------------------------------------- +// Purpose: Check if this map is in our subscribed list +//----------------------------------------------------------------------------- +bool CServerBrowser::IsWorkshopSubscribedMap( const char *pszMapName ) +{ + return m_vecWorkshopSubscribedMaps.HasElement( CUtlString( pszMapName ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CServerBrowser::IsValid() +{ + return ( g_pRunGameEngine ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CServerBrowser::Activate() +{ + static bool firstTimeOpening = true; + if ( firstTimeOpening ) + { + m_hInternetDlg->LoadUserData(); // reload the user data the first time the dialog is made visible, helps with the lag between module load and + // steamui getting Deactivate() call + firstTimeOpening = false; + } + + int numTimesOpened = sb_numtimesopened.GetInt() + 1; + sb_numtimesopened.SetValue( numTimesOpened ); + if ( numTimesOpened == 1 ) + { + time_t aclock; + time( &aclock ); + sb_firstopentime.SetValue( (int) aclock ); + } + + Open(); + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: called when the server browser gets used in the game +//----------------------------------------------------------------------------- +void CServerBrowser::Deactivate() +{ + if (m_hInternetDlg.Get()) + { + m_hInternetDlg->SaveUserData(); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: called when the server browser is no longer being used in the game +//----------------------------------------------------------------------------- +void CServerBrowser::Reactivate() +{ + if (m_hInternetDlg.Get()) + { + m_hInternetDlg->LoadUserData(); + if (m_hInternetDlg->IsVisible()) + { + m_hInternetDlg->RefreshCurrentPage(); + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CServerBrowser::Open() +{ + m_hInternetDlg->Open(); +} + + +//----------------------------------------------------------------------------- +// Purpose: returns direct handle to main server browser dialog +//----------------------------------------------------------------------------- +vgui::VPANEL CServerBrowser::GetPanel() +{ + return m_hInternetDlg.Get() ? m_hInternetDlg->GetVPanel() : NULL; +} + + +//----------------------------------------------------------------------------- +// Purpose: sets the parent panel of the main module panel +//----------------------------------------------------------------------------- +void CServerBrowser::SetParent(vgui::VPANEL parent) +{ + if (m_hInternetDlg.Get()) + { + m_hInternetDlg->SetParent(parent); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Closes down the server browser for good +//----------------------------------------------------------------------------- +void CServerBrowser::Shutdown() +{ + if (m_hInternetDlg.Get()) + { + m_hInternetDlg->Close(); + m_hInternetDlg->MarkForDeletion(); + } + +#if defined( STEAM ) + vgui::VGuiControls_Shutdown(); +#endif + + DisconnectTier3Libraries(); + DisconnectTier2Libraries(); + ConVar_Unregister(); + DisconnectTier1Libraries(); +} + + +//----------------------------------------------------------------------------- +// Purpose: opens a game info dialog to watch the specified server; associated with the friend 'userName' +//----------------------------------------------------------------------------- +bool CServerBrowser::OpenGameInfoDialog( uint64 ulSteamIDFriend, const char *pszConnectCode ) +{ +#if !defined( _X360 ) // X360TBD: SteamFriends() + if ( m_hInternetDlg.Get() ) + { + // activate an already-existing dialog + CDialogGameInfo *pDialogGameInfo = m_hInternetDlg->GetDialogGameInfoForFriend( ulSteamIDFriend ); + if ( pDialogGameInfo ) + { + pDialogGameInfo->Activate(); + return true; + } + + // none yet, create a new dialog + FriendGameInfo_t friendGameInfo; + if ( steamapicontext->SteamFriends()->GetFriendGamePlayed( ulSteamIDFriend, &friendGameInfo ) ) + { + uint16 usConnPort = friendGameInfo.m_usGamePort; + if ( friendGameInfo.m_usQueryPort < QUERY_PORT_ERROR ) + usConnPort = friendGameInfo.m_usQueryPort; + CDialogGameInfo *pDialogGameInfo = m_hInternetDlg->OpenGameInfoDialog( friendGameInfo.m_unGameIP, friendGameInfo.m_usGamePort, usConnPort, pszConnectCode ); + pDialogGameInfo->SetFriend( ulSteamIDFriend ); + return true; + } + } +#endif + return false; +} + + +//----------------------------------------------------------------------------- +// Purpose: joins a specified game - game info dialog will only be opened if the server is fully or passworded +//----------------------------------------------------------------------------- +bool CServerBrowser::JoinGame( uint64 ulSteamIDFriend, const char *pszConnectCode ) +{ + if ( OpenGameInfoDialog( ulSteamIDFriend, pszConnectCode ) ) + { + CDialogGameInfo *pDialogGameInfo = m_hInternetDlg->GetDialogGameInfoForFriend( ulSteamIDFriend ); + pDialogGameInfo->Connect(); + } + + return false; +} + + +//----------------------------------------------------------------------------- +// Purpose: joins a game by IP/Port +//----------------------------------------------------------------------------- +bool CServerBrowser::JoinGame( uint32 unGameIP, uint16 usGamePort, const char *pszConnectCode ) +{ + m_hInternetDlg->JoinGame( unGameIP, usGamePort, pszConnectCode ); + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: forces the game info dialog closed +//----------------------------------------------------------------------------- +void CServerBrowser::CloseGameInfoDialog( uint64 ulSteamIDFriend ) +{ + CDialogGameInfo *pDialogGameInfo = m_hInternetDlg->GetDialogGameInfoForFriend( ulSteamIDFriend ); + if ( pDialogGameInfo ) + { + pDialogGameInfo->Close(); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: closes all the game info dialogs +//----------------------------------------------------------------------------- +void CServerBrowser::CloseAllGameInfoDialogs() +{ + if ( m_hInternetDlg.Get() ) + { + m_hInternetDlg->CloseAllGameInfoDialogs(); + } +} + +CUtlVector< gametypes_t > g_GameTypes; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void LoadGameTypes( void ) +{ + if ( g_GameTypes.Count() > 0 ) + return; + + #define GAMETYPES_FILE "servers/ServerBrowserGameTypes.txt" + + KeyValues * kv = new KeyValues( GAMETYPES_FILE ); + + if ( !kv->LoadFromFile( g_pFullFileSystem, GAMETYPES_FILE, "MOD" ) ) + { + kv->deleteThis(); + return; + } + + g_GameTypes.RemoveAll(); + + for ( KeyValues *pData = kv->GetFirstSubKey(); pData != NULL; pData = pData->GetNextKey() ) + { + gametypes_t gametype; + + gametype.pPrefix = CloneString( pData->GetString( "prefix", "" ) ); + gametype.pGametypeName = CloneString( pData->GetString( "name", "" ) ); + g_GameTypes.AddToTail( gametype ); + } + + + kv->deleteThis(); +} + +const char *GetGameTypeName( const char *pMapName ) +{ + LoadGameTypes(); + for ( int i = 0; i < g_GameTypes.Count(); i++ ) + { + int iLength = strlen( g_GameTypes[i].pPrefix ); + + if ( !Q_strncmp( pMapName, g_GameTypes[i].pPrefix, iLength ) ) + { + return g_GameTypes[i].pGametypeName; + } + } + + return ""; +} + +//----------------------------------------------------------------------------- +// Purpose of comments like these: none +//----------------------------------------------------------------------------- +const char *CServerBrowser::GetMapFriendlyNameAndGameType( const char *pszMapName, char *szFriendlyMapName, int cchFriendlyName ) +{ + // Make sure game types are loaded + LoadGameTypes(); + + // Scan list + const char *pszFriendlyGameTypeName = ""; + for ( int i = 0; i < g_GameTypes.Count(); i++ ) + { + int iLength = strlen( g_GameTypes[i].pPrefix ); + + if ( !Q_strnicmp( pszMapName, g_GameTypes[i].pPrefix, iLength ) ) + { + pszMapName += iLength; + pszFriendlyGameTypeName = g_GameTypes[i].pGametypeName; + break; + } + } + + // See how many characters from the name to copy. + // Start by assuming we'll copy the whole thing. + // (After any prefix we just skipped) + int l = V_strlen( pszMapName ); + const char *pszFinal = Q_stristr( pszMapName, "_final" ); + if ( pszFinal ) + { + // truncate the _final (or _final1) part of the filename if it's at the end of the name + const char *pszNextChar = pszFinal + Q_strlen( "_final" ); + if ( ( *pszNextChar == '\0' ) || + ( ( *pszNextChar == '1' ) && ( *(pszNextChar+1) == '\0' ) ) ) + { + l = pszFinal - pszMapName; + } + } + + // Safety check against buffer size + if ( l >= cchFriendlyName ) + { + Assert( !"Map name too long for buffer!" ); + l = cchFriendlyName-1; + } + + // Copy friendly portion of name only + V_memcpy( szFriendlyMapName, pszMapName, l ); + + // It's like the Alamo. We never forget. + szFriendlyMapName[l] = '\0'; + + // Result should be the friendly game type name + return pszFriendlyGameTypeName; +} + diff --git a/serverbrowser/ServerBrowser.h b/serverbrowser/ServerBrowser.h new file mode 100644 index 0000000..7decbe7 --- /dev/null +++ b/serverbrowser/ServerBrowser.h @@ -0,0 +1,82 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef SERVERBROWSER_H +#define SERVERBROWSER_H +#ifdef _WIN32 +#pragma once +#endif + +class CServerBrowserDialog; + +//----------------------------------------------------------------------------- +// Purpose: Handles the UI and pinging of a half-life game server list +//----------------------------------------------------------------------------- +class CServerBrowser : public IServerBrowser, public IVGuiModule +{ +public: + CServerBrowser(); + ~CServerBrowser(); + + // IVGui module implementation + virtual bool Initialize(CreateInterfaceFn *factorylist, int numFactories); + virtual bool PostInitialize(CreateInterfaceFn *modules, int factoryCount); + virtual vgui::VPANEL GetPanel(); + virtual bool Activate(); + virtual bool IsValid(); + virtual void Shutdown(); + virtual void Deactivate(); + virtual void Reactivate(); + virtual void SetParent(vgui::VPANEL parent); + + // IServerBrowser implementation + // joins a specified game - game info dialog will only be opened if the server is fully or passworded + virtual bool JoinGame( uint32 unGameIP, uint16 usGamePort, const char *pszConnectCode ); + virtual bool JoinGame( uint64 ulSteamIDFriend, const char *pszConnectCode ); + + // opens a game info dialog to watch the specified server; associated with the friend 'userName' + virtual bool OpenGameInfoDialog( uint64 ulSteamIDFriend, const char *pszConnectCode ); + + // forces the game info dialog closed + virtual void CloseGameInfoDialog( uint64 ulSteamIDFriend ); + + // closes all the game info dialogs + virtual void CloseAllGameInfoDialogs(); + + virtual const char *GetMapFriendlyNameAndGameType( const char *pszMapName, char *szFriendlyMapName, int cchFriendlyName ) OVERRIDE; + + // methods + virtual void CreateDialog(); + virtual void Open(); + + // true if the user can't play a game + bool IsVACBannedFromGame( int nAppID ); + + // Enable filtering of workshop maps, requires the game/tool loading us to feed subscription data. This is a + // slightly ugly workaround to TF2 not yet having native workshop UI in quickplay, once that is in place this should + // either be stripped back out or expanded to be directly aware of the steam workshop without being managed. + virtual void SetWorkshopEnabled( bool bManaged ) OVERRIDE; + virtual void AddWorkshopSubscribedMap( const char *pszMapName ) OVERRIDE; + virtual void RemoveWorkshopSubscribedMap( const char *pszMapName ) OVERRIDE; + + bool IsWorkshopEnabled(); + bool IsWorkshopSubscribedMap( const char *pszMapName ); +private: + vgui::DHANDLE<CServerBrowserDialog> m_hInternetDlg; + + bool m_bWorkshopEnabled; + CUtlVector< CUtlString > m_vecWorkshopSubscribedMaps; +}; + +// singleton accessor +CServerBrowser &ServerBrowser(); + +class CSteamAPIContext; +extern CSteamAPIContext *steamapicontext; + + +#endif // SERVERBROWSER_H diff --git a/serverbrowser/ServerBrowser.vpc b/serverbrowser/ServerBrowser.vpc new file mode 100644 index 0000000..6bfedde --- /dev/null +++ b/serverbrowser/ServerBrowser.vpc @@ -0,0 +1,106 @@ +//----------------------------------------------------------------------------- +// SERVERBROWSER.VPC +// +// Project Script +//----------------------------------------------------------------------------- + +$Macro SRCDIR ".." +$Macro OUTBINDIR "$SRCDIR\..\game\bin" + +$Include "$SRCDIR\vpc_scripts\source_dll_base.vpc" + +$Configuration +{ + $Compiler + { + $PreprocessorDefinitions "$BASE;VERSION_SAFE_STEAM_API_INTERFACES;SERVERBROWSER_EXPORTS;GAME_SRC;_USE_32BIT_TIME_T" + } + + $Linker + { + $AdditionalDependencies "$BASE Advapi32.lib wsock32.lib Ws2_32.lib User32.lib" [$WIN32] + $AdditionalDependencies "$BASE Xonline.lib" [$X360] + $SystemLibraries "iconv" [$OSXALL] + } +} + +$Project "ServerBrowser" +{ + $Folder "Source Files" + { + $File "BaseGamesPage.cpp" + $File "BlacklistedServers.cpp" + $File "CustomGames.cpp" + $File "DialogAddServer.cpp" + $File "DialogGameInfo.cpp" + $File "DialogServerPassword.cpp" + $File "FavoriteGames.cpp" + $File "FriendsGames.cpp" + $File "HistoryGames.cpp" + $File "InternetGames.cpp" + $File "LanGames.cpp" + $File "ModList.cpp" + $File "ServerBrowser.cpp" + $File "ServerBrowserDialog.cpp" + $File "ServerContextMenu.cpp" + $File "ServerListCompare.cpp" + $File "SpectateGames.cpp" + $File "VACBannedConnRefusedDialog.cpp" + $File "VACBannedConnRefusedDialog.h" + + $File "QuickListPanel.cpp" + + $File "$SRCDIR\public\vgui_controls\vgui_controls.cpp" + { + $Configuration + { + $Compiler + { + } + } + } + + $File "$SRCDIR\common\ServerBrowser\blacklisted_server_manager.cpp" + } + + $Folder "Header Files" + { + $File "BaseGamesPage.h" + $File "BlacklistedServers.h" + $File "CustomGames.h" + $File "DialogAddServer.h" + $File "DialogGameInfo.h" + $File "DialogServerPassword.h" + $File "FavoriteGames.h" + $File "FriendsGames.h" + $File "HistoryGames.h" + $File "InternetGames.h" + $File "LanGames.h" + $File "ModList.h" + $File "ServerBrowser.h" + $File "ServerBrowserDialog.h" + $File "ServerContextMenu.h" + $File "ServerListCompare.h" + $File "SpectateGames.h" + $File "QuickListPanel.h" + + $File "$SRCDIR\common\ServerBrowser\blacklisted_server_manager.h" + } + + $Folder "Resource Files" + { + } + $Folder "Exposed interfaces" + { + $File "$SRCDIR\common\ServerBrowser\IServerBrowser.h" + } + + $Folder "Link Libraries" + { + $ImpLib steam_api + $Lib tier2 + $Lib tier3 + $Lib vgui_controls + $Lib mathlib + } +} diff --git a/serverbrowser/ServerBrowserDialog.cpp b/serverbrowser/ServerBrowserDialog.cpp new file mode 100644 index 0000000..0e2f3f4 --- /dev/null +++ b/serverbrowser/ServerBrowserDialog.cpp @@ -0,0 +1,816 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// The copyright to the contents herein is the property of Valve, L.L.C. +// The contents may be used and/or copied only with the written permission of +// Valve, L.L.C., or in accordance with the terms and conditions stipulated in +// the agreement/contract under which the contents have been supplied. +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//============================================================================= +#include "pch_serverbrowser.h" + +#if defined( _X360 ) +#include "xbox/xbox_win32stubs.h" +#endif + +#if defined( _WIN32 ) && !defined( _X360 ) +#define WIN32_LEAN_AND_MEAN +#include <winsock.h> +#endif +#ifdef LINUX +#include <arpa/inet.h> +#endif + +using namespace vgui; + +ConVar sb_quick_list_bit_field( "sb_quick_list_bit_field", "-1" ); + +static CServerBrowserDialog *s_InternetDlg = NULL; + +CServerBrowserDialog &ServerBrowserDialog() +{ + return *CServerBrowserDialog::GetInstance(); +} + + +// Returns a list of the ports that we hit when looking for +void GetMostCommonQueryPorts( CUtlVector<uint16> &ports ) +{ + for ( int i=0; i <= 5; i++ ) + { + ports.AddToTail( 27015 + i ); + ports.AddToTail( 26900 + i ); + } + + ports.AddToTail(4242); //RDKF + ports.AddToTail(27215); //Lost Planet +} + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +CServerBrowserDialog::CServerBrowserDialog(vgui::Panel *parent) : Frame(parent, "CServerBrowserDialog") +{ + s_InternetDlg = this; + + m_szGameName[0] = 0; + m_szModDir[0] = 0; + m_pSavedData = NULL; + m_pFilterData = NULL; + m_pFavorites = NULL; + m_pBlacklist = NULL; + m_pHistory = NULL; + + // Do this before LoadUserData() so it loads the blacklist file properly + m_pBlacklist = new CBlacklistedServers(this); + + LoadUserData(); + + m_pInternetGames = new CCustomGames(this); + m_pFavorites = new CFavoriteGames(this); + m_pHistory = new CHistoryGames(this); + m_pSpectateGames = new CSpectateGames(this); + m_pLanGames = new CLanGames(this); + m_pFriendsGames = new CFriendsGames(this); + + SetMinimumSize( 640, 384 ); + SetSize( 640, 384 ); + + m_pGameList = m_pInternetGames; + + m_pContextMenu = new CServerContextMenu(this);; + + // property sheet + m_pTabPanel = new PropertySheet(this, "GameTabs"); + m_pTabPanel->SetTabWidth(72); + m_pTabPanel->AddPage(m_pInternetGames, "#ServerBrowser_InternetTab"); + m_pTabPanel->AddPage(m_pFavorites, "#ServerBrowser_FavoritesTab"); + m_pTabPanel->AddPage(m_pHistory, "#ServerBrowser_HistoryTab"); + m_pTabPanel->AddPage(m_pSpectateGames, "#ServerBrowser_SpectateTab"); + m_pTabPanel->AddPage(m_pLanGames, "#ServerBrowser_LanTab"); + m_pTabPanel->AddPage(m_pFriendsGames, "#ServerBrowser_FriendsTab"); + if ( m_pBlacklist ) + { + m_pTabPanel->AddPage(m_pBlacklist, "#ServerBrowser_BlacklistTab"); + } + m_pTabPanel->AddActionSignalTarget(this); + + m_pStatusLabel = new Label(this, "StatusLabel", ""); + + LoadControlSettingsAndUserConfig("Servers/DialogServerBrowser.res"); + + m_pStatusLabel->SetText(""); + + // load current tab + const char *gameList = m_pSavedData->GetString("GameList"); + + if (!Q_stricmp(gameList, "spectate")) + { + m_pTabPanel->SetActivePage(m_pSpectateGames); + } + else if (!Q_stricmp(gameList, "favorites")) + { + m_pTabPanel->SetActivePage(m_pFavorites); + } + else if (!Q_stricmp(gameList, "history")) + { + m_pTabPanel->SetActivePage(m_pHistory); + } + else if (!Q_stricmp(gameList, "lan")) + { + m_pTabPanel->SetActivePage(m_pLanGames); + } + else if (!Q_stricmp(gameList, "friends")) + { + m_pTabPanel->SetActivePage(m_pFriendsGames); + } + else if (!Q_stricmp(gameList, "blacklist")) + { + m_pTabPanel->SetActivePage(m_pBlacklist); + } + else + { + m_pTabPanel->SetActivePage(m_pInternetGames); + } + + ivgui()->AddTickSignal( GetVPanel() ); +} + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +CServerBrowserDialog::~CServerBrowserDialog() +{ + delete m_pContextMenu; + + SaveUserData(); + + if (m_pSavedData) + { + m_pSavedData->deleteThis(); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Called once to set up +//----------------------------------------------------------------------------- +void CServerBrowserDialog::Initialize() +{ + SetTitle("#ServerBrowser_Servers", true); + SetVisible(false); +} + + +//----------------------------------------------------------------------------- +// Purpose: returns a server in the list +//----------------------------------------------------------------------------- +gameserveritem_t *CServerBrowserDialog::GetServer( unsigned int serverID ) +{ + if (m_pGameList) + return m_pGameList->GetServer( serverID ); + return NULL; +} + + +//----------------------------------------------------------------------------- +// Purpose: Activates and gives the tab focus +//----------------------------------------------------------------------------- +void CServerBrowserDialog::Open() +{ + BaseClass::Activate(); + m_pTabPanel->RequestFocus(); + + MoveToCenterOfScreen(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Called every frame, updates animations for this module +//----------------------------------------------------------------------------- +void CServerBrowserDialog::OnTick() +{ + BaseClass::OnTick(); + vgui::GetAnimationController()->UpdateAnimations( system()->GetFrameTime() ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Loads filter settings from disk +//----------------------------------------------------------------------------- +void CServerBrowserDialog::LoadUserData() +{ + // free any old filters + if (m_pSavedData) + { + m_pSavedData->deleteThis(); + } + + m_pSavedData = new KeyValues("Filters"); + if (!m_pSavedData->LoadFromFile( g_pFullFileSystem, "ServerBrowser.vdf", "CONFIG")) + { + // doesn't matter if the file is not found, defaults will work successfully and file will be created on exit + } + + KeyValues *filters = m_pSavedData->FindKey( "Filters", false ); + if ( filters ) + { + m_pFilterData = filters->MakeCopy(); + m_pSavedData->RemoveSubKey( filters ); + } + else + { + m_pFilterData = new KeyValues( "Filters" ); + } + + + // reload all the page settings if necessary + if (m_pHistory) + { + // history + m_pHistory->LoadHistoryList(); + if ( IsVisible() && m_pHistory->IsVisible() ) + m_pHistory->StartRefresh(); + } + + if (m_pFavorites) + { + // favorites + m_pFavorites->LoadFavoritesList(); + + // filters + ReloadFilterSettings(); + + if ( IsVisible() && m_pFavorites->IsVisible() ) + m_pFavorites->StartRefresh(); + } + + if ( m_pBlacklist ) + { + m_pBlacklist->LoadBlacklistedList(); + } + + InvalidateLayout(); + Repaint(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CServerBrowserDialog::SaveUserData() +{ + m_pSavedData->Clear(); + m_pSavedData->LoadFromFile( g_pFullFileSystem, "ServerBrowser.vdf", "CONFIG"); + + // set the current tab + if (m_pGameList == m_pSpectateGames) + { + m_pSavedData->SetString("GameList", "spectate"); + } + else if (m_pGameList == m_pFavorites) + { + m_pSavedData->SetString("GameList", "favorites"); + } + else if (m_pGameList == m_pLanGames) + { + m_pSavedData->SetString("GameList", "lan"); + } + else if (m_pGameList == m_pFriendsGames) + { + m_pSavedData->SetString("GameList", "friends"); + } + else if (m_pGameList == m_pHistory) + { + m_pSavedData->SetString("GameList", "history"); + } + else + { + m_pSavedData->SetString("GameList", "internet"); + } + + m_pSavedData->RemoveSubKey( m_pSavedData->FindKey( "Filters" ) ); // remove the saved subkey and add our subkey + m_pSavedData->AddSubKey( m_pFilterData->MakeCopy() ); + m_pSavedData->SaveToFile( g_pFullFileSystem, "ServerBrowser.vdf", "CONFIG"); + + if ( m_pBlacklist ) + { + m_pBlacklist->SaveBlacklistedList(); + } + + // save per-page config + SaveUserConfig(); +} + +//----------------------------------------------------------------------------- +// Purpose: refreshes the page currently visible +//----------------------------------------------------------------------------- +void CServerBrowserDialog::RefreshCurrentPage() +{ + if (m_pGameList) + { + m_pGameList->StartRefresh(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CServerBrowserDialog::BlacklistsChanged() +{ + m_pInternetGames->ApplyGameFilters(); +} + +//----------------------------------------------------------------------------- +// Purpose: Updates status test at bottom of window +//----------------------------------------------------------------------------- +void CServerBrowserDialog::UpdateStatusText(const char *fmt, ...) +{ + if ( !m_pStatusLabel ) + return; + + if ( fmt && strlen(fmt) > 0 ) + { + char str[ 1024 ]; + va_list argptr; + va_start( argptr, fmt ); + _vsnprintf( str, sizeof(str), fmt, argptr ); + va_end( argptr ); + + m_pStatusLabel->SetText( str ); + } + else + { + // clear + m_pStatusLabel->SetText( "" ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Updates status test at bottom of window +// Input : wchar_t* (unicode string) - +//----------------------------------------------------------------------------- +void CServerBrowserDialog::UpdateStatusText(wchar_t *unicode) +{ + if ( !m_pStatusLabel ) + return; + + if ( unicode && wcslen(unicode) > 0 ) + { + m_pStatusLabel->SetText( unicode ); + } + else + { + // clear + m_pStatusLabel->SetText( "" ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CServerBrowserDialog::OnGameListChanged() +{ + m_pGameList = dynamic_cast<IGameList *>(m_pTabPanel->GetActivePage()); + + UpdateStatusText(""); + + InvalidateLayout(); + Repaint(); +} + +//----------------------------------------------------------------------------- +// Purpose: returns a pointer to a static instance of this dialog +//----------------------------------------------------------------------------- +CServerBrowserDialog *CServerBrowserDialog::GetInstance() +{ + return s_InternetDlg; +} + +//----------------------------------------------------------------------------- +// Purpose: Adds a server to the list of favorites +//----------------------------------------------------------------------------- +void CServerBrowserDialog::AddServerToFavorites(gameserveritem_t &server) +{ + if ( steamapicontext->SteamMatchmaking() ) + { + steamapicontext->SteamMatchmaking()->AddFavoriteGame( + server.m_nAppID, + server.m_NetAdr.GetIP(), + server.m_NetAdr.GetConnectionPort(), + server.m_NetAdr.GetQueryPort(), + k_unFavoriteFlagFavorite, + time( NULL ) ); + + if ( GameSupportsReplay() ) + { + // send command to propagate to the client so the client can send it on to the GC + char command[ 256 ]; + Q_snprintf( command, Q_ARRAYSIZE( command ), "rfgc %s\n", server.m_NetAdr.GetConnectionAddressString() ); + g_pRunGameEngine->AddTextCommand( command ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Adds a server to our list of blacklisted servers +//----------------------------------------------------------------------------- +void CServerBrowserDialog::AddServerToBlacklist(gameserveritem_t &server) +{ + if ( m_pBlacklist ) + { + m_pBlacklist->AddServer( server ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CServerBrowserDialog::IsServerBlacklisted(gameserveritem_t &server) +{ + if ( m_pBlacklist ) + return m_pBlacklist->IsServerBlacklisted( server ); + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CServerContextMenu *CServerBrowserDialog::GetContextMenu(vgui::Panel *pPanel) +{ + // create a drop down for this object's states + if (m_pContextMenu) + delete m_pContextMenu; + m_pContextMenu = new CServerContextMenu(this); + m_pContextMenu->SetAutoDelete( false ); + + if (!pPanel) + { + m_pContextMenu->SetParent(this); + } + else + { + m_pContextMenu->SetParent(pPanel); + } + + m_pContextMenu->SetVisible(false); + return m_pContextMenu; +} + +//----------------------------------------------------------------------------- +// Purpose: begins the process of joining a server from a game list +// the game info dialog it opens will also update the game list +//----------------------------------------------------------------------------- +CDialogGameInfo *CServerBrowserDialog::JoinGame(IGameList *gameList, unsigned int serverIndex) +{ + // open the game info dialog, then mark it to attempt to connect right away + CDialogGameInfo *gameDialog = OpenGameInfoDialog(gameList, serverIndex); + + // set the dialog name to be the server name + gameDialog->Connect(); + + return gameDialog; +} + +//----------------------------------------------------------------------------- +// Purpose: joins a game by a specified IP, not attached to any game list +//----------------------------------------------------------------------------- +CDialogGameInfo *CServerBrowserDialog::JoinGame(int serverIP, int serverPort, const char *pszConnectCode) +{ + // open the game info dialog, then mark it to attempt to connect right away + CDialogGameInfo *gameDialog = OpenGameInfoDialog( serverIP, serverPort, serverPort, pszConnectCode ); + + // set the dialog name to be the server name + gameDialog->Connect(); + + return gameDialog; +} + +//----------------------------------------------------------------------------- +// Purpose: opens a game info dialog from a game list +//----------------------------------------------------------------------------- +CDialogGameInfo *CServerBrowserDialog::OpenGameInfoDialog( IGameList *gameList, unsigned int serverIndex ) +{ + gameserveritem_t *pServer = gameList->GetServer( serverIndex ); + if ( !pServer ) + return NULL; + + CDialogGameInfo *gameDialog = new CDialogGameInfo( NULL, pServer->m_NetAdr.GetIP(), pServer->m_NetAdr.GetQueryPort(), pServer->m_NetAdr.GetConnectionPort(), gameList->GetConnectCode() ); + gameDialog->SetParent(GetVParent()); + gameDialog->AddActionSignalTarget(this); + gameDialog->Run( pServer->GetName() ); + int i = m_GameInfoDialogs.AddToTail(); + m_GameInfoDialogs[i] = gameDialog; + return gameDialog; +} + +//----------------------------------------------------------------------------- +// Purpose: opens a game info dialog by a specified IP, not attached to any game list +//----------------------------------------------------------------------------- +CDialogGameInfo *CServerBrowserDialog::OpenGameInfoDialog( int serverIP, uint16 connPort, uint16 queryPort, const char *pszConnectCode ) +{ + CDialogGameInfo *gameDialog = new CDialogGameInfo(NULL, serverIP, queryPort, connPort, pszConnectCode); + gameDialog->AddActionSignalTarget(this); + gameDialog->SetParent(GetVParent()); + gameDialog->Run(""); + int i = m_GameInfoDialogs.AddToTail(); + m_GameInfoDialogs[i] = gameDialog; + return gameDialog; +} + +//----------------------------------------------------------------------------- +// Purpose: closes all the game info dialogs +//----------------------------------------------------------------------------- +void CServerBrowserDialog::CloseAllGameInfoDialogs() +{ + for (int i = 0; i < m_GameInfoDialogs.Count(); i++) + { + vgui::Panel *dlg = m_GameInfoDialogs[i]; + if (dlg) + { + vgui::ivgui()->PostMessage(dlg->GetVPanel(), new KeyValues("Close"), NULL); + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: finds a dialog +//----------------------------------------------------------------------------- +CDialogGameInfo *CServerBrowserDialog::GetDialogGameInfoForFriend( uint64 ulSteamIDFriend ) +{ + FOR_EACH_VEC( m_GameInfoDialogs, i ) + { + CDialogGameInfo *pDlg = m_GameInfoDialogs[i]; + if ( pDlg && pDlg->GetAssociatedFriend() == ulSteamIDFriend ) + { + return pDlg; + } + } + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: accessor to the filter save data +//----------------------------------------------------------------------------- +KeyValues *CServerBrowserDialog::GetFilterSaveData(const char *filterSet) +{ + return m_pFilterData->FindKey(filterSet, true); +} + +//----------------------------------------------------------------------------- +// Purpose: gets the name of the mod directory we're restricted to accessing, NULL if none +//----------------------------------------------------------------------------- +const char *CServerBrowserDialog::GetActiveModName() +{ + return m_szModDir[0] ? m_szModDir : NULL; +} + + +//----------------------------------------------------------------------------- +// Purpose: gets the name of the mod directory we're restricted to accessing, NULL if none +//----------------------------------------------------------------------------- +const char *CServerBrowserDialog::GetActiveGameName() +{ + return m_szGameName[0] ? m_szGameName : NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: return the app id to limit game queries to, set by Source/HL1 engines (NOT by filter settings, that is per page) +//----------------------------------------------------------------------------- +CGameID &CServerBrowserDialog::GetActiveAppID() +{ + // !TEST! Un comment this to force a particular AppID + //m_iLimitAppID = CGameID( 440 ); + return m_iLimitAppID; +} + + +//----------------------------------------------------------------------------- +// Purpose: receives a specified game is active, so no other game types can be displayed in server list +//----------------------------------------------------------------------------- +void CServerBrowserDialog::OnActiveGameName( KeyValues *pKV ) +{ + Q_strncpy(m_szModDir, pKV->GetString( "name" ), sizeof(m_szModDir)); + Q_strncpy(m_szGameName, pKV->GetString( "game" ), sizeof(m_szGameName)); + m_iLimitAppID = CGameID( pKV->GetUint64( "appid", 0 ) ); + // reload filter settings (since they are no forced to be game specific) + ReloadFilterSettings(); +} + +//----------------------------------------------------------------------------- +// Purpose: resets all pages filter settings +//----------------------------------------------------------------------------- +void CServerBrowserDialog::ReloadFilterSettings() +{ + m_pInternetGames->LoadFilterSettings(); + m_pSpectateGames->LoadFilterSettings(); + m_pFavorites->LoadFilterSettings(); + m_pLanGames->LoadFilterSettings(); + m_pFriendsGames->LoadFilterSettings(); + m_pHistory->LoadFilterSettings(); +} + +//----------------------------------------------------------------------------- +// Purpose: Adds server to the history, saves as currently connected server +//----------------------------------------------------------------------------- +void CServerBrowserDialog::OnConnectToGame( KeyValues *pMessageValues ) +{ + int ip = pMessageValues->GetInt( "ip" ); + int connectionPort = pMessageValues->GetInt( "connectionport" ); + int queryPort = pMessageValues->GetInt( "queryport" ); + + if ( !ip || !queryPort ) + return; + + uint32 unIP = htonl( ip ); + + memset( &m_CurrentConnection, 0, sizeof(gameserveritem_t) ); + m_CurrentConnection.m_NetAdr.SetIP( unIP ); + m_CurrentConnection.m_NetAdr.SetQueryPort( queryPort ); + m_CurrentConnection.m_NetAdr.SetConnectionPort( (unsigned short)connectionPort ); + + if (m_pHistory && steamapicontext->SteamMatchmaking() ) + { + steamapicontext->SteamMatchmaking()->AddFavoriteGame( 0, unIP, connectionPort, queryPort, k_unFavoriteFlagHistory, time( NULL ) ); + m_pHistory->SetRefreshOnReload(); + } + + // tell the game info dialogs, so they can cancel if we have connected + // to a server they were auto-retrying + for (int i = 0; i < m_GameInfoDialogs.Count(); i++) + { + vgui::Panel *dlg = m_GameInfoDialogs[i]; + if (dlg) + { + KeyValues *kv = new KeyValues("ConnectedToGame", "ip", unIP, "connectionport", connectionPort); + kv->SetInt( "queryport", queryPort ); + vgui::ivgui()->PostMessage(dlg->GetVPanel(), kv, NULL); + } + } + + // forward to favorites + m_pFavorites->OnConnectToGame(); + if ( m_pBlacklist ) + { + m_pBlacklist->OnConnectToGame(); + } + + m_bCurrentlyConnected = true; + + // Now we want to track which tabs have the quick list button checked + int iQuickListBitField = 0; + if ( m_pFriendsGames && m_pFriendsGames->IsQuickListButtonChecked() ) + { + iQuickListBitField |= ( 1 << 0 ); + } + if ( m_pLanGames && m_pLanGames->IsQuickListButtonChecked() ) + { + iQuickListBitField |= ( 1 << 1 ); + } + if ( m_pSpectateGames && m_pSpectateGames->IsQuickListButtonChecked() ) + { + iQuickListBitField |= ( 1 << 2 ); + } + if ( m_pHistory && m_pHistory->IsQuickListButtonChecked() ) + { + iQuickListBitField |= ( 1 << 3 ); + } + if ( m_pFavorites && m_pFavorites->IsQuickListButtonChecked() ) + { + iQuickListBitField |= ( 1 << 4 ); + } + if ( m_pInternetGames && m_pInternetGames->IsQuickListButtonChecked() ) + { + iQuickListBitField |= ( 1 << 5 ); + } + + // Set the value so that the client.dll can use it for gamestats + sb_quick_list_bit_field.SetValue( iQuickListBitField ); + + // TF2 wants to close this dialog when the player connects to a game + if ( GameSupportsReplay() ) + { + ConVarRef sb_close_browser_on_connect( "sb_close_browser_on_connect" ); + if ( sb_close_browser_on_connect.IsValid() ) + { + if ( sb_close_browser_on_connect.GetBool() == true ) + { + OnClose(); + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Clears currently connected server +//----------------------------------------------------------------------------- +void CServerBrowserDialog::OnDisconnectFromGame( void ) +{ + m_bCurrentlyConnected = false; + memset( &m_CurrentConnection, 0, sizeof(gameserveritem_t) ); + + // forward to favorites + m_pFavorites->OnDisconnectFromGame(); + if ( m_pBlacklist ) + { + m_pBlacklist->OnDisconnectFromGame(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Called when start start loading, so we can cease server browser activity +//----------------------------------------------------------------------------- +void CServerBrowserDialog::OnLoadingStarted( void ) +{ + m_pInternetGames->OnLoadingStarted(); + m_pSpectateGames->OnLoadingStarted(); + m_pFavorites->OnLoadingStarted(); + m_pLanGames->OnLoadingStarted(); + m_pFriendsGames->OnLoadingStarted(); + m_pHistory->OnLoadingStarted(); +} + +//----------------------------------------------------------------------------- +// Purpose: Passes build mode activation down into the pages +//----------------------------------------------------------------------------- +void CServerBrowserDialog::ActivateBuildMode() +{ + // no subpanel, no build mode + EditablePanel *panel = dynamic_cast<EditablePanel *>(m_pTabPanel->GetActivePage()); + if (!panel) + return; + + panel->ActivateBuildMode(); +} + +//----------------------------------------------------------------------------- +// Purpose: gets the default position and size on the screen to appear the first time +//----------------------------------------------------------------------------- +bool CServerBrowserDialog::GetDefaultScreenPosition(int &x, int &y, int &wide, int &tall) +{ + int wx, wy, ww, wt; + surface()->GetWorkspaceBounds( wx, wy, ww, wt ); + x = wx + (int)(ww * 0.05); + y = wy + (int)(wt * 0.4); + wide = (int)(ww * 0.5); + tall = (int)(wt * 0.55); + return true; +} + +void CServerBrowserDialog::OnKeyCodePressed( vgui::KeyCode code ) +{ + // Handle close here, CBasePanel parent doesn't support "DialogClosing" command + ButtonCode_t nButtonCode = GetBaseButtonCode( code ); + + if ( nButtonCode == KEY_XBUTTON_B || nButtonCode == STEAMCONTROLLER_B ) + { + OnCommand( "Close" ); + return; + } + else if ( nButtonCode == KEY_XBUTTON_A || nButtonCode == STEAMCONTROLLER_A ) + { + //OnOK( false ); + //return; + } + else if ( nButtonCode == KEY_XBUTTON_UP || + nButtonCode == KEY_XSTICK1_UP || + nButtonCode == KEY_XSTICK2_UP || + nButtonCode == STEAMCONTROLLER_DPAD_UP || + nButtonCode == KEY_UP || + nButtonCode == KEY_XBUTTON_DOWN || + nButtonCode == KEY_XSTICK1_DOWN || + nButtonCode == KEY_XSTICK2_DOWN || + nButtonCode == STEAMCONTROLLER_DPAD_DOWN || + nButtonCode == KEY_DOWN ) + { + CBaseGamesPage *pGamesPage = dynamic_cast< CBaseGamesPage* >( m_pTabPanel->GetActivePage() ); + if ( pGamesPage ) + { + ListPanel *pListPanel = dynamic_cast< ListPanel * >( pGamesPage->GetActiveList() ); + if ( pListPanel ) + { + if ( pListPanel->GetSelectedItem( 0 ) == -1 ) + { + pListPanel->SetSingleSelectedItem( pListPanel->GetItemIDFromRow( 0 ) ); + pListPanel->RequestFocus(); + return; + } + else if ( !pListPanel->HasFocus() ) + { + pListPanel->RequestFocus(); + return; + } + } + } + } + + BaseClass::OnKeyCodePressed( code ); +}
\ No newline at end of file diff --git a/serverbrowser/ServerBrowserDialog.h b/serverbrowser/ServerBrowserDialog.h new file mode 100644 index 0000000..2b25186 --- /dev/null +++ b/serverbrowser/ServerBrowserDialog.h @@ -0,0 +1,159 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef SERVERBROWSERDIALOG_H +#define SERVERBROWSERDIALOG_H +#ifdef _WIN32 +#pragma once +#endif + +extern class IRunGameEngine *g_pRunGameEngine; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CServerBrowserDialog : public vgui::Frame +{ + DECLARE_CLASS_SIMPLE( CServerBrowserDialog, vgui::Frame ); + +public: + // Construction/destruction + CServerBrowserDialog( vgui::Panel *parent ); + ~CServerBrowserDialog( void ); + + void Initialize( void ); + + // displays the dialog, moves it into focus, updates if it has to + void Open( void ); + + // gets server info + gameserveritem_t *GetServer(unsigned int serverID); + // called every frame + virtual void OnTick(); + + // updates status text at bottom of window + void UpdateStatusText(PRINTF_FORMAT_STRING const char *format, ...); + + // updates status text at bottom of window + void UpdateStatusText(wchar_t *unicode); + + // context menu access + CServerContextMenu *GetContextMenu(vgui::Panel *pParent); + + // returns a pointer to a static instance of this dialog + // valid for use only in sort functions + static CServerBrowserDialog *GetInstance(); + + // Adds a server to the list of favorites + void AddServerToFavorites(gameserveritem_t &server); + // Adds a server to our list of blacklisted servers + void AddServerToBlacklist(gameserveritem_t &server); + bool IsServerBlacklisted(gameserveritem_t &server); + + // begins the process of joining a server from a game list + // the game info dialog it opens will also update the game list + CDialogGameInfo *JoinGame(IGameList *gameList, unsigned int serverIndex); + + // joins a game by a specified IP, not attached to any game list + CDialogGameInfo *JoinGame(int serverIP, int serverPort, const char *pszConnectCode); + + // opens a game info dialog from a game list + CDialogGameInfo *OpenGameInfoDialog(IGameList *gameList, unsigned int serverIndex); + + // opens a game info dialog by a specified IP, not attached to any game list + CDialogGameInfo *OpenGameInfoDialog( int serverIP, uint16 connPort, uint16 queryPort, const char *pszConnectCode ); + + // closes all the game info dialogs + void CloseAllGameInfoDialogs(); + CDialogGameInfo *GetDialogGameInfoForFriend( uint64 ulSteamIDFriend ); + + // accessor to the filter save data + KeyValues *GetFilterSaveData(const char *filterSet); + + // gets the name of the mod directory we're restricted to accessing, NULL if none + const char *GetActiveModName(); + CGameID &GetActiveAppID(); + const char *GetActiveGameName(); + + // load/saves filter & favorites settings from disk + void LoadUserData(); + void SaveUserData(); + + // forces the currently active page to refresh + void RefreshCurrentPage(); + + virtual gameserveritem_t *GetCurrentConnectedServer() + { + return &m_CurrentConnection; + } + + void BlacklistsChanged(); + CBlacklistedServers *GetBlacklistPage( void ) { return m_pBlacklist; } + +private: + + // current game list change + MESSAGE_FUNC( OnGameListChanged, "PageChanged" ); + void ReloadFilterSettings(); + + // receives a specified game is active, so no other game types can be displayed in server list + MESSAGE_FUNC_PARAMS( OnActiveGameName, "ActiveGameName", name ); + + // notification that we connected / disconnected + MESSAGE_FUNC_PARAMS( OnConnectToGame, "ConnectedToGame", kv ); + MESSAGE_FUNC( OnDisconnectFromGame, "DisconnectedFromGame" ); + MESSAGE_FUNC( OnLoadingStarted, "LoadingStarted" ); + + virtual bool GetDefaultScreenPosition(int &x, int &y, int &wide, int &tall); + virtual void ActivateBuildMode(); + + void OnKeyCodePressed( vgui::KeyCode code ); + +private: + // list of all open game info dialogs + CUtlVector<vgui::DHANDLE<CDialogGameInfo> > m_GameInfoDialogs; + + // pointer to current game list + IGameList *m_pGameList; + + // Status text + vgui::Label *m_pStatusLabel; + + // property sheet + vgui::PropertySheet *m_pTabPanel; + CFavoriteGames *m_pFavorites; + CBlacklistedServers *m_pBlacklist; + CHistoryGames *m_pHistory; + CInternetGames *m_pInternetGames; + CSpectateGames *m_pSpectateGames; + CLanGames *m_pLanGames; + CFriendsGames *m_pFriendsGames; + + KeyValues *m_pSavedData; + KeyValues *m_pFilterData; + + // context menu + CServerContextMenu *m_pContextMenu; + + // active game + char m_szGameName[128]; + char m_szModDir[128]; + CGameID m_iLimitAppID; + + // currently connected game + bool m_bCurrentlyConnected; + gameserveritem_t m_CurrentConnection; +}; + +// singleton accessor +extern CServerBrowserDialog &ServerBrowserDialog(); + +// Used by the LAN tab and the add server dialog when trying to find servers without having +// been given any ports to look for servers on. +void GetMostCommonQueryPorts( CUtlVector<uint16> &ports ); + +#endif // SERVERBROWSERDIALOG_H diff --git a/serverbrowser/ServerContextMenu.cpp b/serverbrowser/ServerContextMenu.cpp new file mode 100644 index 0000000..0767d06 --- /dev/null +++ b/serverbrowser/ServerContextMenu.cpp @@ -0,0 +1,63 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include "pch_serverbrowser.h" + +using namespace vgui; + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +CServerContextMenu::CServerContextMenu(Panel *parent) : Menu(parent, "ServerContextMenu") +{ +} + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +CServerContextMenu::~CServerContextMenu() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: Activates the menu +//----------------------------------------------------------------------------- +void CServerContextMenu::ShowMenu( + Panel *target, + unsigned int serverID, + bool showConnect, + bool showViewGameInfo, + bool showRefresh, + bool showAddToFavorites ) +{ + if (showConnect) + { + AddMenuItem("ConnectToServer", "#ServerBrowser_ConnectToServer", new KeyValues("ConnectToServer", "serverID", serverID), target); + } + + if (showViewGameInfo) + { + AddMenuItem("ViewGameInfo", "#ServerBrowser_ViewServerInfo", new KeyValues("ViewGameInfo", "serverID", serverID), target); + } + + if (showRefresh) + { + AddMenuItem("RefreshServer", "#ServerBrowser_RefreshServer", new KeyValues("RefreshServer", "serverID", serverID), target); + } + + if (showAddToFavorites) + { + AddMenuItem("AddToFavorites", "#ServerBrowser_AddServerToFavorites", new KeyValues("AddToFavorites", "serverID", serverID), target); + AddMenuItem("AddToBlacklist", "#ServerBrowser_AddServerToBlacklist", new KeyValues("AddToBlacklist", "serverID", serverID), target); + } + + int x, y, gx, gy; + input()->GetCursorPos(x, y); + ipanel()->GetPos(surface()->GetEmbeddedPanel(), gx, gy); + SetPos(x - gx, y - gy); + SetVisible(true); +} diff --git a/serverbrowser/ServerContextMenu.h b/serverbrowser/ServerContextMenu.h new file mode 100644 index 0000000..1d8ff23 --- /dev/null +++ b/serverbrowser/ServerContextMenu.h @@ -0,0 +1,34 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef SERVERCONTEXTMENU_H +#define SERVERCONTEXTMENU_H +#ifdef _WIN32 +#pragma once +#endif + +//----------------------------------------------------------------------------- +// Purpose: Basic right-click context menu for servers +//----------------------------------------------------------------------------- +class CServerContextMenu : public vgui::Menu +{ +public: + CServerContextMenu(vgui::Panel *parent); + ~CServerContextMenu(); + + // call this to Activate the menu + void ShowMenu( + vgui::Panel *target, + unsigned int serverID, + bool showConnect, + bool showViewGameInfo, + bool showRefresh, + bool showAddToFavorites); +}; + + +#endif // SERVERCONTEXTMENU_H diff --git a/serverbrowser/ServerListCompare.cpp b/serverbrowser/ServerListCompare.cpp new file mode 100644 index 0000000..f17678b --- /dev/null +++ b/serverbrowser/ServerListCompare.cpp @@ -0,0 +1,289 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include "pch_serverbrowser.h" + +bool IsReplayServer( gameserveritem_t &server ); + +//----------------------------------------------------------------------------- +// Purpose: List password column sort function +//----------------------------------------------------------------------------- +int __cdecl PasswordCompare(ListPanel *pPanel, const ListPanelItem &p1, const ListPanelItem &p2) +{ + gameserveritem_t *s1 = ServerBrowserDialog().GetServer(p1.userData); + gameserveritem_t *s2 = ServerBrowserDialog().GetServer(p2.userData); + + if ( !s1 && s2 ) + return -1; + if ( !s2 && s1 ) + return 1; + if ( !s1 && !s2 ) + return 0; + + if ( s1->m_bPassword < s2->m_bPassword ) + return 1; + else if ( s1->m_bPassword > s2->m_bPassword ) + return -1; + + return 0; +} + +//----------------------------------------------------------------------------- +// Purpose: list column sort function +//----------------------------------------------------------------------------- +int __cdecl BotsCompare(ListPanel *pPanel, const ListPanelItem &p1, const ListPanelItem &p2) +{ + gameserveritem_t *s1 = ServerBrowserDialog().GetServer(p1.userData); + gameserveritem_t *s2 = ServerBrowserDialog().GetServer(p2.userData); + + if ( !s1 && s2 ) + return -1; + if ( !s2 && s1 ) + return 1; + if ( !s1 && !s2 ) + return 0; + + if ( s1->m_nBotPlayers < s2->m_nBotPlayers ) + return 1; + else if ( s1->m_nBotPlayers > s2->m_nBotPlayers ) + return -1; + + return 0; +} + +//----------------------------------------------------------------------------- +// Purpose: list column sort function +//----------------------------------------------------------------------------- +int __cdecl SecureCompare(ListPanel *pPanel, const ListPanelItem &p1, const ListPanelItem &p2) +{ + gameserveritem_t *s1 = ServerBrowserDialog().GetServer(p1.userData); + gameserveritem_t *s2 = ServerBrowserDialog().GetServer(p2.userData); + + if ( !s1 && s2 ) + return -1; + if ( !s2 && s1 ) + return 1; + if ( !s1 && !s2 ) + return 0; + + if ( s1->m_bSecure < s2->m_bSecure ) + return 1; + else if ( s1->m_bSecure > s2->m_bSecure ) + return -1; + + return 0; +} + +//----------------------------------------------------------------------------- +// Purpose: list column sort function +//----------------------------------------------------------------------------- +int __cdecl IPAddressCompare(ListPanel *pPanel, const ListPanelItem &p1, const ListPanelItem &p2) +{ + gameserveritem_t *s1 = ServerBrowserDialog().GetServer(p1.userData); + gameserveritem_t *s2 = ServerBrowserDialog().GetServer(p2.userData); + + if ( !s1 && s2 ) + return -1; + if ( !s2 && s1 ) + return 1; + if ( !s1 && !s2 ) + return 0; + + if ( s1->m_NetAdr < s2->m_NetAdr ) + return -1; + else if ( s2->m_NetAdr < s1->m_NetAdr ) + return 1; + + return 0; +} + +//----------------------------------------------------------------------------- +// Purpose: Ping comparison function +//----------------------------------------------------------------------------- +int __cdecl PingCompare(ListPanel *pPanel, const ListPanelItem &p1, const ListPanelItem &p2) +{ + gameserveritem_t *s1 = ServerBrowserDialog().GetServer(p1.userData); + gameserveritem_t *s2 = ServerBrowserDialog().GetServer(p2.userData); + + if ( !s1 && s2 ) + return -1; + if ( !s2 && s1 ) + return 1; + if ( !s1 && !s2 ) + return 0; + + int ping1 = s1->m_nPing; + int ping2 = s2->m_nPing; + + if ( ping1 < ping2 ) + return -1; + else if ( ping1 > ping2 ) + return 1; + + return 0; +} + +//----------------------------------------------------------------------------- +// Purpose: Map comparison function +//----------------------------------------------------------------------------- +int __cdecl MapCompare(ListPanel *pPanel, const ListPanelItem &p1, const ListPanelItem &p2) +{ + gameserveritem_t *s1 = ServerBrowserDialog().GetServer(p1.userData); + gameserveritem_t *s2 = ServerBrowserDialog().GetServer(p2.userData); + + if ( !s1 && s2 ) + return -1; + if ( !s2 && s1 ) + return 1; + if ( !s1 && !s2 ) + return 0; + + return Q_stricmp( s1->m_szMap, s2->m_szMap ); +} + +//----------------------------------------------------------------------------- +// Purpose: Game dir comparison function +//----------------------------------------------------------------------------- +int __cdecl GameCompare(ListPanel *pPanel, const ListPanelItem &p1, const ListPanelItem &p2) +{ + gameserveritem_t *s1 = ServerBrowserDialog().GetServer(p1.userData); + gameserveritem_t *s2 = ServerBrowserDialog().GetServer(p2.userData); + + if ( !s1 && s2 ) + return -1; + if ( !s2 && s1 ) + return 1; + if ( !s1 && !s2 ) + return 0; + + // make sure we haven't added the same server to the list twice + Assert( p1.userData != p2.userData ); + + return Q_stricmp( s1->m_szGameDescription, s2->m_szGameDescription ); +} + +//----------------------------------------------------------------------------- +// Purpose: Server name comparison function +//----------------------------------------------------------------------------- +int __cdecl ServerNameCompare(ListPanel *pPanel, const ListPanelItem &p1, const ListPanelItem &p2) +{ + gameserveritem_t *s1 = ServerBrowserDialog().GetServer(p1.userData); + gameserveritem_t *s2 = ServerBrowserDialog().GetServer(p2.userData); + + if ( !s1 && s2 ) + return -1; + if ( !s2 && s1 ) + return 1; + if ( !s1 && !s2 ) + return 0; + + return Q_stricmp( s1->GetName(), s2->GetName() ); +} + +//----------------------------------------------------------------------------- +// Purpose: Player number comparison function +//----------------------------------------------------------------------------- +int __cdecl PlayersCompare(ListPanel *pPanel, const ListPanelItem &p1, const ListPanelItem &p2) +{ + gameserveritem_t *s1 = ServerBrowserDialog().GetServer(p1.userData); + gameserveritem_t *s2 = ServerBrowserDialog().GetServer(p2.userData); + + if ( !s1 && s2 ) + return -1; + if ( !s2 && s1 ) + return 1; + if ( !s1 && !s2 ) + return 0; + + int s1p = max( 0, s1->m_nPlayers - s1->m_nBotPlayers ); + int s1m = max( 0, s1->m_nMaxPlayers - s1->m_nBotPlayers ); + int s2p = max( 0, s2->m_nPlayers - s2->m_nBotPlayers ); + int s2m = max( 0, s2->m_nMaxPlayers - s2->m_nBotPlayers ); + + // compare number of players + if ( s1p > s2p ) + return -1; + if ( s1p < s2p ) + return 1; + + // compare max players if number of current players is equal + if ( s1m > s2m ) + return -1; + if ( s1m < s2m ) + return 1; + + return 0; +} + +//----------------------------------------------------------------------------- +// Purpose: Player number comparison function +//----------------------------------------------------------------------------- +int __cdecl LastPlayedCompare(ListPanel *pPanel, const ListPanelItem &p1, const ListPanelItem &p2) +{ + gameserveritem_t *s1 = ServerBrowserDialog().GetServer( p1.userData ); + gameserveritem_t *s2 = ServerBrowserDialog().GetServer( p2.userData ); + + if ( !s1 && s2 ) + return -1; + if ( !s2 && s1 ) + return 1; + if ( !s1 && !s2 ) + return 0; + + // compare number of players + if ( s1->m_ulTimeLastPlayed > s2->m_ulTimeLastPlayed ) + return -1; + if ( s1->m_ulTimeLastPlayed < s2->m_ulTimeLastPlayed ) + return 1; + + return 0; +} + +//----------------------------------------------------------------------------- +// Purpose: Tag comparison function +//----------------------------------------------------------------------------- +int __cdecl TagsCompare(ListPanel *pPanel, const ListPanelItem &p1, const ListPanelItem &p2) +{ + gameserveritem_t *s1 = ServerBrowserDialog().GetServer(p1.userData); + gameserveritem_t *s2 = ServerBrowserDialog().GetServer(p2.userData); + + if ( !s1 && s2 ) + return -1; + if ( !s2 && s1 ) + return 1; + if ( !s1 && !s2 ) + return 0; + + return Q_stricmp( s1->m_szGameTags, s2->m_szGameTags ); +} + +//----------------------------------------------------------------------------- +// Purpose: Replay comparison function +//----------------------------------------------------------------------------- +int __cdecl ReplayCompare(ListPanel *pPanel, const ListPanelItem &p1, const ListPanelItem &p2) +{ + gameserveritem_t *s1 = ServerBrowserDialog().GetServer(p1.userData); + gameserveritem_t *s2 = ServerBrowserDialog().GetServer(p2.userData); + + if ( !s1 && s2 ) + return -1; + if ( !s2 && s1 ) + return 1; + if ( !s1 && !s2 ) + return 0; + + bool s1_is_replay = IsReplayServer( *s1 ); + bool s2_is_replay = IsReplayServer( *s2 ); + + if ( s1_is_replay < s2_is_replay ) + return 1; + else if ( s1_is_replay > s2_is_replay ) + return -1; + + return 0; +} + diff --git a/serverbrowser/ServerListCompare.h b/serverbrowser/ServerListCompare.h new file mode 100644 index 0000000..1e7a9ad --- /dev/null +++ b/serverbrowser/ServerListCompare.h @@ -0,0 +1,32 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef SERVERLISTCOMPARE_H +#define SERVERLISTCOMPARE_H +#ifdef _WIN32 +#pragma once +#endif + +using vgui::ListPanel; +using vgui::ListPanelItem; + + +// these functions are common to most server lists in sorts +int __cdecl PasswordCompare(ListPanel *pPanel, const ListPanelItem &p1, const ListPanelItem &p2); +int __cdecl BotsCompare(ListPanel *pPanel, const ListPanelItem &p1, const ListPanelItem &p2); +int __cdecl PingCompare(ListPanel *pPanel, const ListPanelItem &p1, const ListPanelItem &p2); +int __cdecl PlayersCompare(ListPanel *pPanel, const ListPanelItem &p1, const ListPanelItem &p2); +int __cdecl MapCompare(ListPanel *pPanel, const ListPanelItem &p1, const ListPanelItem &p2); +int __cdecl GameCompare(ListPanel *pPanel, const ListPanelItem &p1, const ListPanelItem &p2); +int __cdecl ServerNameCompare(ListPanel *pPanel, const ListPanelItem &p1, const ListPanelItem &p2); +int __cdecl SecureCompare(ListPanel *pPanel, const ListPanelItem &p1, const ListPanelItem &p2); +int __cdecl IPAddressCompare(ListPanel *pPanel, const ListPanelItem &p1, const ListPanelItem &p2); +int __cdecl LastPlayedCompare(ListPanel *pPanel, const ListPanelItem &p1, const ListPanelItem &p2); +int __cdecl TagsCompare(ListPanel *pPanel, const ListPanelItem &p1, const ListPanelItem &p2); +int __cdecl ReplayCompare(ListPanel *pPanel, const ListPanelItem &p1, const ListPanelItem &p2); + +#endif // SERVERLISTCOMPARE_H diff --git a/serverbrowser/SpectateGames.cpp b/serverbrowser/SpectateGames.cpp new file mode 100644 index 0000000..32b2999 --- /dev/null +++ b/serverbrowser/SpectateGames.cpp @@ -0,0 +1,33 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include "pch_serverbrowser.h" + + +CSpectateGames::CSpectateGames( vgui::Panel *parent ) + : CInternetGames( parent, "SpectateGames", eSpectatorServer ) +{ +} + +void CSpectateGames::GetNewServerList() +{ + m_vecServerFilters.AddToTail( MatchMakingKeyValuePair_t( "proxy", "1" ) ); + BaseClass::GetNewServerList(); +} + +void CSpectateGames::OnPageShow() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CSpectateGames::CheckTagFilter( gameserveritem_t &server ) +{ + return true; +} + diff --git a/serverbrowser/SpectateGames.h b/serverbrowser/SpectateGames.h new file mode 100644 index 0000000..49ab16e --- /dev/null +++ b/serverbrowser/SpectateGames.h @@ -0,0 +1,38 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef SPECTATEGAMES_H +#define SPECTATEGAMES_H +#ifdef _WIN32 +#pragma once +#endif + +#include "InternetGames.h" + +//----------------------------------------------------------------------------- +// Purpose: Spectator games list +//----------------------------------------------------------------------------- +class CSpectateGames : public CInternetGames +{ +public: + CSpectateGames(vgui::Panel *parent); + + // property page handlers + virtual void OnPageShow(); + + virtual bool CheckTagFilter( gameserveritem_t &server ); + +protected: + // filters by spectator games + virtual void GetNewServerList(); + +private: + typedef CInternetGames BaseClass; +}; + + +#endif // SPECTATEGAMES_H diff --git a/serverbrowser/VACBannedConnRefusedDialog.cpp b/serverbrowser/VACBannedConnRefusedDialog.cpp new file mode 100644 index 0000000..4e53105 --- /dev/null +++ b/serverbrowser/VACBannedConnRefusedDialog.cpp @@ -0,0 +1,22 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#include "pch_serverbrowser.h" + +using namespace vgui; + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +CVACBannedConnRefusedDialog::CVACBannedConnRefusedDialog( VPANEL hVParent, const char *name ) : BaseClass( NULL, name ) +{ + SetParent( hVParent ); + SetSize( 480, 220 ); + SetSizeable( false ); + + LoadControlSettings( "servers/VACBannedConnRefusedDialog.res" ); + MoveToCenterOfScreen(); +} diff --git a/serverbrowser/VACBannedConnRefusedDialog.h b/serverbrowser/VACBannedConnRefusedDialog.h new file mode 100644 index 0000000..16981b1 --- /dev/null +++ b/serverbrowser/VACBannedConnRefusedDialog.h @@ -0,0 +1,29 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#ifndef VACBANNEDCONNREFUSED_H +#define VACBANNEDCONNREFUSED_H +#ifdef _WIN32 +#pragma once +#endif + + +//----------------------------------------------------------------------------- +// Purpose: Displays information about new VAC bans +//----------------------------------------------------------------------------- +class CVACBannedConnRefusedDialog : public vgui::Frame +{ + DECLARE_CLASS_SIMPLE( CVACBannedConnRefusedDialog, vgui::Frame ); + +public: + CVACBannedConnRefusedDialog( vgui::VPANEL hVParent, const char *name ); + +}; + + + + +#endif // VACBANNEDCONNREFUSED_H
\ No newline at end of file diff --git a/serverbrowser/igamelist.h b/serverbrowser/igamelist.h new file mode 100644 index 0000000..3944c9b --- /dev/null +++ b/serverbrowser/igamelist.h @@ -0,0 +1,91 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef IGAMELIST_H +#define IGAMELIST_H +#ifdef _WIN32 +#pragma once +#endif + +class gameserveritem_t; +#if defined( STEAM ) +#include "steam2common.h" +#include "FindSteam2Servers.h" +#else +#include "steamcommon.h" +#include "FindSteamServers.h" +#endif +#include "netadr.h" + + +typedef enum +{ + SERVERVERSION_SAME_VERSION = 0, + SERVERVERSION_SERVER_OLD, + SERVERVERSION_SERVER_NEWER +} SERVERVERSION; + +struct serverdisplay_t +{ + serverdisplay_t() + { + m_iListID = -1; + m_iServerID = -1; + m_bDoNotRefresh = true; + } + int m_iListID; // the VGUI2 list panel index for displaying this server + int m_iServerID; // the matchmaking interface index for this server + bool m_bDoNotRefresh; + bool operator==( const serverdisplay_t &rhs ) const { return rhs.m_iServerID == m_iServerID; } + +}; + +//----------------------------------------------------------------------------- +// Purpose: Interface to accessing a game list +//----------------------------------------------------------------------------- +class IGameList +{ +public: + + enum InterfaceItem_e + { + FILTERS, + GETNEWLIST, + ADDSERVER, + ADDCURRENTSERVER, + }; + + // returns true if the game list supports the specified ui elements + virtual bool SupportsItem(InterfaceItem_e item) = 0; + + // starts the servers refreshing + virtual void StartRefresh() = 0; + + // gets a new server list + virtual void GetNewServerList() = 0; + + // stops current refresh/GetNewServerList() + virtual void StopRefresh() = 0; + + // returns true if the list is currently refreshing servers + virtual bool IsRefreshing() = 0; + + // gets information about specified server + virtual gameserveritem_t *GetServer(unsigned int serverID) = 0; + + // called when Connect button is pressed + virtual void OnBeginConnect() = 0; + + // invalid server index + virtual int GetInvalidServerListID() = 0; + + // Get code to use for tracking how people are connecting to servers + virtual const char *GetConnectCode() = 0; +}; + + +#endif // IGAMELIST_H diff --git a/serverbrowser/pch_serverbrowser.cpp b/serverbrowser/pch_serverbrowser.cpp new file mode 100644 index 0000000..42352b2 --- /dev/null +++ b/serverbrowser/pch_serverbrowser.cpp @@ -0,0 +1,7 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#include "pch_serverbrowser.h" diff --git a/serverbrowser/pch_serverbrowser.h b/serverbrowser/pch_serverbrowser.h new file mode 100644 index 0000000..575382e --- /dev/null +++ b/serverbrowser/pch_serverbrowser.h @@ -0,0 +1,86 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include <winlite.h> +#undef CreateDialog +#ifdef WIN32 +#include <direct.h> +#include <io.h> +#endif +#include <stdio.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <time.h> + +#include "vstdlib/pch_vstdlib.h" +#include "tier0/memdbgoff.h" +#include "vgui_controls/pch_vgui_controls.h" +#include "vgui_controls/Frame.h" +#include "tier0/memdbgon.h" + +#include "tier3/tier3.h" + +// steam3 API +//#include "steam/steam_querypackets.h" +#include "steam/steam_api.h" +#include "steam/isteamuser.h" +#include "steam/isteammatchmaking.h" +#include "steam/isteamfriends.h" + +#include "ServerBrowser/IServerBrowser.h" +#include "IVguiModule.h" +#include "vgui_controls/Controls.h" + +#include "netadr.h" +#include "filesystem.h" +#include "proto_oob.h" +#include "ModList.h" +#include "IRunGameEngine.h" + +#include "OfflineMode.h" + +// serverbrowser files + +#include "igamelist.h" +#include "ServerListCompare.h" +#include "ServerBrowser.h" +#include "VACBannedConnRefusedDialog.h" +#include "DialogGameInfo.h" +#include "ServerContextMenu.h" +#include "DialogServerPassword.h" +#include "DialogAddServer.h" + +// game list +#include "BaseGamesPage.h" +#include "BlacklistedServers.h" +#include "InternetGames.h" +#include "FavoriteGames.h" +#include "SpectateGames.h" +#include "LanGames.h" +#include "FriendsGames.h" +#include "HistoryGames.h" +#include "SpectateGames.h" +#include "CustomGames.h" +#include "ServerBrowserDialog.h" +#include "QuickListPanel.h" +#include "vgui_controls/PanelListPanel.h" + +#include "replay/ienginereplay.h" + +extern bool GameSupportsReplay(); +extern bool IsReplayServer( gameserveritem_t &server ); + +#pragma warning( disable: 4355 ) // warning C4355: 'this' : used in base member initializer list + +#if defined( STEAM ) +#define IsSteam() true +#else +#define IsSteam() false +#endif + +using namespace vgui; diff --git a/serverbrowser/resource.h b/serverbrowser/resource.h new file mode 100644 index 0000000..d568b57 --- /dev/null +++ b/serverbrowser/resource.h @@ -0,0 +1,15 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by ServerBrowser.rc + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 101 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/serverbrowser/xbox/xbox.def b/serverbrowser/xbox/xbox.def new file mode 100644 index 0000000..38daf14 --- /dev/null +++ b/serverbrowser/xbox/xbox.def @@ -0,0 +1,3 @@ +LIBRARY serverbrowser_360.dll +EXPORTS + CreateInterface @1 |