|
|
//========= Copyright � 1996-2009, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=====================================================================================//
#ifndef _X360
#include "xbox/xboxstubs.h"
#endif
#include "mm_framework.h"
#if !defined( _X360 ) && !defined( NO_STEAM ) && !defined( SWDS )
#include "steam/matchmakingtypes.h"
#endif
#include "proto_oob.h"
#include "fmtstr.h"
// NOTE: This has to be the last file included!
#include "tier0/memdbgon.h"
#pragma warning (disable : 4355 )
size_t const mm_filter_max_size = 128; // Maximum size of a filter key, it's key+value+3
static ConVar mm_server_search_update_interval( "mm_server_search_update_interval", "60", FCVAR_DEVELOPMENTONLY, "Interval between servers updates." );
static ConVar mm_server_search_inet_ping_interval( "mm_server_search_inet_ping_interval", "1.0", FCVAR_DEVELOPMENTONLY, "How long to wait between pinging internet server details." ); static ConVar mm_server_search_inet_ping_timeout( "mm_server_search_inet_ping_timeout", "3.0", FCVAR_DEVELOPMENTONLY, "How long to wait for internet server details." ); static ConVar mm_server_search_inet_ping_window( "mm_server_search_inet_ping_window", "10", FCVAR_DEVELOPMENTONLY, "How many servers can be pinged for server details in a batch." ); static ConVar mm_server_search_inet_ping_refresh( "mm_server_search_inet_ping_refresh", "15", FCVAR_DEVELOPMENTONLY, "How often to refresh a listed server." ); static ConVar mm_server_search_server_lifetime( "mm_server_search_server_lifetime", "180", FCVAR_DEVELOPMENTONLY, "How long until a server is no longer returned by the master till we remove it." );
static ConVar mm_server_search_lan_ping_interval( "mm_server_search_lan_ping_interval", "0.4", FCVAR_DEVELOPMENTONLY, "Interval between LAN discovery pings." ); static ConVar mm_server_search_lan_ping_duration( "mm_server_search_lan_ping_duration", "1.0", FCVAR_DEVELOPMENTONLY, "Duration of LAN discovery ping phase." );
static ConVar mm_server_search_lan_ports( "mm_server_search_lan_ports", "27015,27016,27017,27018,27019,27020", FCVAR_RELEASE | FCVAR_ARCHIVE, "Ports to scan during LAN games discovery. Also used to discover and correctly connect to dedicated LAN servers behind NATs." );
//
// Server implementation
//
class CServerPinging : public CServer { public: CServerPinging() : m_flTimeout( 0 ) {} public: #if !defined( _X360 ) && !defined( NO_STEAM ) && !defined( SWDS )
gameserveritem_t m_gsi; #endif
float m_flTimeout; };
CServer::CServer() : m_flLastRefresh( Plat_FloatTime() ), m_xuid( 0ull ), m_pGameDetails( NULL ) { }
CServer::~CServer() { if ( m_pGameDetails ) m_pGameDetails->deleteThis(); m_pGameDetails = NULL; }
XUID CServer::GetOnlineId() { return m_xuid; }
KeyValues * CServer::GetGameDetails() { return m_pGameDetails; }
bool CServer::IsJoinable() { return m_pGameDetails != NULL; }
void CServer::Join() { char const *szConnectString = m_pGameDetails->GetString( "server/connectstring", NULL ); if ( !szConnectString || !*szConnectString ) return;
g_pMatchExtensions->GetIVEngineClient()->ClientCmd( CFmtStr( "connect %s\n", szConnectString ) ); }
//
// Server manager implementation
//
CServerManager::CServerManager() { #if !defined( _X360 ) && !defined( NO_STEAM ) && !defined( SWDS )
m_hRequest = NULL; #endif
m_bUpdateEnabled = false; m_flNextUpdateTime = 0.0f; m_flNextServerUpdateTime = 0.0f; m_eState = STATE_IDLE; }
CServerManager::~CServerManager() { m_Servers.PurgeAndDeleteElements(); m_ServersPinging.PurgeAndDeleteElements();
#if !defined( _X360 ) && !defined( NO_STEAM ) && !defined( SWDS )
if ( m_hRequest ) steamapicontext->SteamMatchmakingServers()->ReleaseRequest( m_hRequest ); m_hRequest = NULL; #endif
}
static CServerManager g_ServerManager; CServerManager *g_pServerManager = &g_ServerManager;
void CServerManager::EnableServersUpdate( bool bEnable ) { if ( bEnable && ( g_pMatchFramework->GetMatchTitle()->GetTitleSettingsFlags() & MATCHTITLE_SERVERMGR_DISABLED ) ) bEnable = false;
m_bUpdateEnabled = bEnable; m_flNextUpdateTime = 0.0f;
// If enabled the search, we'll pick it up next update
if ( bEnable ) return;
// Otherwise search is being disabled
m_Servers.PurgeAndDeleteElements(); m_ServersPinging.PurgeAndDeleteElements();
#if !defined( _X360 ) && !defined( NO_STEAM ) && !defined( SWDS )
if ( m_eState == STATE_FETCHING_SERVERS && m_hRequest ) { steamapicontext->SteamMatchmakingServers()->ReleaseRequest( m_hRequest ); m_hRequest = NULL; } #endif
// Will clean up all servers and go IDLE
OnAllDataFetched(); }
int CServerManager::GetNumServers() { return m_Servers.Count(); }
IMatchServer * CServerManager::GetServerByIndex( int iServerIdx ) { return m_Servers.IsValidIndex( iServerIdx ) ? m_Servers[ iServerIdx ] : NULL; }
IMatchServer * CServerManager::GetServerByOnlineId( XUID xuidServerOnline ) { return GetServerRecordByOnlineId( m_Servers, xuidServerOnline ); }
CServer * CServerManager::GetServerRecordByOnlineId( CUtlVector< CServer * > &arr, XUID xuidServerOnline ) { for ( int k = 0; k < arr.Count(); ++ k ) { CServer *pServer = arr[ k ]; if ( pServer && pServer->GetOnlineId() == xuidServerOnline ) return pServer; } return NULL; }
void CServerManager::OnEvent( KeyValues *pEvent ) { if ( IsX360() ) return;
char const *szName = pEvent->GetName();
if ( !Q_stricmp( szName, "OnNetLanConnectionlessPacket" ) ) { char const * arrServerProbeKeys[] = { "LanSearchServerPing", "ConnectServerDetailsRequest", "InetSearchServerDetails" };
for ( int k = 0; k < ARRAYSIZE( arrServerProbeKeys ); ++ k ) { KeyValues *pProbeKey = pEvent->FindKey( arrServerProbeKeys[k] ); if ( !pProbeKey ) continue;
KeyValues *pDetails = g_pMatchFramework->GetMatchNetworkMsgController()->GetActiveServerGameDetails( pEvent ); KeyValues::AutoDelete autodelete_pDetails( pDetails ); if ( !pDetails ) return;
if ( !pDetails->FindKey( "server" ) ) return;
pDetails->FindKey( arrServerProbeKeys[k], true )->MergeFrom( pProbeKey, KeyValues::MERGE_KV_UPDATE );
g_pConnectionlessLanMgr->SendPacket( pDetails, pEvent->GetString( "from" ), INetSupport::NS_SOCK_SERVER ); return; }
if ( KeyValues *pGameDetailsServer = pEvent->FindKey( "GameDetailsServer" ) ) { // Incoming data:
//
// System
// Game
// Server
// Members
// LanSearchServerPing
// timestamp = holds the time packet was sent for ping
// Server ping
int nPing = 0; if ( float flTimeSent = pGameDetailsServer->GetFloat( "LanSearchServerPing/timestamp" ) ) { float flSeconds = Plat_FloatTime() - flTimeSent; nPing = flSeconds * 1000; if ( nPing < 0 ) nPing = 0; if ( nPing >= 1000 ) nPing = 999; } else if ( uint64 xuidInetPing = pGameDetailsServer->GetUint64( "InetSearchServerDetails/pingxuid" ) ) { CServerPinging *pServerPinging = ( CServerPinging * ) GetServerRecordByOnlineId( m_ServersPinging, xuidInetPing ); if ( !pServerPinging ) return; if ( float flTimeSent = pGameDetailsServer->GetFloat( "InetSearchServerDetails/timestamp" ) ) { float flSeconds = Plat_FloatTime() - flTimeSent; nPing = flSeconds * 1000; if ( nPing < 0 ) nPing = 0; if ( nPing >= 1000 ) nPing = 999; } #if !defined( _X360 ) && !defined( NO_STEAM ) && !defined( SWDS )
else { nPing = pServerPinging->m_gsi.m_nPing; } #endif
// Remove the server from outstanding pings list
m_ServersPinging.FindAndFastRemove( pServerPinging ); delete pServerPinging; } else if ( char const *szDetailsAdr = pGameDetailsServer->GetString( "ConnectServerDetailsRequest/server" ) ) { g_pMatchExtensions->GetINetSupport()->OnMatchEvent( pEvent ); return; } else return;
// Server address
char const *szAddr = pGameDetailsServer->GetString( "server/adronline", NULL ); if ( !szAddr || !*szAddr ) return;
// Determine server network address
netadr_t netAddress; netAddress.SetType( NA_IP ); netAddress.SetPort( PORT_SERVER ); netAddress.SetFromString( szAddr );
// Check if this is not our local server
INetSupport::ServerInfo_t si; g_pMatchExtensions->GetINetSupport()->GetServerInfo( &si ); if ( si.m_bActive && ( si.m_netAdr.CompareAdr( netAddress ) || si.m_netAdrOnline.CompareAdr( netAddress ) ) ) return;
// Coalesce it into its XUID online id
XUID xuidOnline = uint64( netAddress.GetIPNetworkByteOrder() ) | ( uint64( netAddress.GetPort() ) << 32ull );
// Prepare the settings
KeyValues *pSettings = pGameDetailsServer->MakeCopy(); pSettings->SetName( "settings" ); if ( KeyValues *kvLanSearch = pSettings->FindKey( "LanSearch" ) ) { pSettings->RemoveSubKey( kvLanSearch ); kvLanSearch->deleteThis(); } pSettings->SetInt( "server/ping", nPing ); if ( char const *szPacketFrom = pEvent->GetString( "from", NULL ) ) pSettings->SetString( "server/connectstring", szPacketFrom );
//
// Find the server or create a new one
//
IMatchServer *pExistingServer = GetServerByOnlineId( xuidOnline ); CServer *pServer = ( CServer * ) pExistingServer; if ( !pServer ) { pServer = new CServer(); pServer->m_xuid = xuidOnline; #if !defined( NO_STEAM ) && !defined( SWDS )
servernetadr_t serverNetAddr; serverNetAddr.Init( netAddress.GetIPHostByteOrder(), netAddress.GetPort(), netAddress.GetPort() ); pServer->m_netAdr = serverNetAddr; #endif
m_Servers.AddToTail( pServer ); }
if ( pServer->m_pGameDetails ) { // Average out the ping value
int nKnownPing = pServer->m_pGameDetails->GetInt( "server/ping", 0 ); if ( nKnownPing < nPing ) { // we got a high ping, try to display the ping as low as possible
nPing = ( nKnownPing * 9 + nPing * 1 ) / 10; pSettings->SetInt( "server/ping", nPing ); }
pServer->m_pGameDetails->deleteThis(); }
pServer->m_pGameDetails = pSettings; pServer->m_flLastRefresh = Plat_FloatTime();
// Signal that we have updated a server
KeyValues *kvEvent = new KeyValues( "OnMatchServerMgrUpdate", "update", "server" ); kvEvent->SetUint64( "xuid", xuidOnline ); g_pMatchFramework->GetEventsSubscription()->BroadcastEvent( kvEvent ); } } else if ( !Q_stricmp( "Client::ResendGameDetailsRequest", szName ) ) { KeyValues *kv = new KeyValues( "ConnectServerDetailsRequest" ); KeyValues::AutoDelete autodelete( kv ); kv->SetString( "server", pEvent->GetString( "to", "" ) );
g_pConnectionlessLanMgr->SendPacket( kv, pEvent->GetString( "to", "" ) ); } }
#if !defined( _X360 ) && !defined( NO_STEAM ) && !defined( SWDS )
void CServerManager::ServerResponded( HServerListRequest hReq, int iServer ) { gameserveritem_t *gsi = steamapicontext->SteamMatchmakingServers()->GetServerDetails( hReq, iServer ); if ( !gsi ) return;
// Every time a server responds during groups query, bump this groups last request timeout
m_lanSearchData.m_flLastBroadcastTime = Plat_FloatTime();
// Determine server network address
netadr_t netAddress; netAddress.SetType( NA_IP ); netAddress.SetPort( PORT_SERVER ); netAddress.SetFromString( gsi->m_NetAdr.GetConnectionAddressString() );
// Coalesce it into its XUID online id
XUID xuidOnline = uint64( netAddress.GetIPNetworkByteOrder() ) | ( uint64( netAddress.GetPort() ) << 32ull );
// Check if we have a pinging record for this server
CServerPinging *pServer = ( CServerPinging * ) GetServerRecordByOnlineId( m_ServersPinging, xuidOnline ); if ( pServer ) { // We already have a pinging record for this server, just update its info
pServer->m_gsi = *gsi; pServer->m_netAdr = gsi->m_NetAdr; return; }
// Create a new pinging record
pServer = new CServerPinging(); pServer->m_xuid = xuidOnline; pServer->m_gsi = *gsi; pServer->m_netAdr = gsi->m_NetAdr; m_ServersPinging.AddToTail( pServer ); }
void CServerManager::RefreshComplete( HServerListRequest hReq, EMatchMakingServerResponse response ) { if ( m_eState == STATE_FETCHING_SERVERS ) m_eState = STATE_GROUP_FETCHED;
steamapicontext->SteamMatchmakingServers()->ReleaseRequest( m_hRequest ); m_hRequest = NULL; } #endif
void CServerManager::Update() { #if !( !defined( _X360 ) && !defined( NO_STEAM ) && !defined( SWDS ) )
return; #endif
float now = Plat_FloatTime();
switch ( m_eState ) { case STATE_IDLE: if ( m_bUpdateEnabled && !IsLocalClientConnectedToServer() ) { if ( now > m_flNextUpdateTime ) { MarkOldServersAndBeginSearch(); } else if ( now > m_flNextServerUpdateTime ) { float nextUpdatePeriod = mm_server_search_inet_ping_refresh.GetFloat();
// any servers not in the pinging list but haven't been refreshed in a while should be refreshed
for ( int i = 0; i < m_Servers.Count(); ++i ) { float timePassed = now - m_Servers[i]->m_flLastRefresh; if ( timePassed > mm_server_search_inet_ping_refresh.GetFloat() ) { CServerPinging *pServerPinging = ( CServerPinging * ) GetServerRecordByOnlineId( m_ServersPinging, m_Servers[i]->GetOnlineId() ); if ( !pServerPinging ) { pServerPinging = new CServerPinging(); pServerPinging->m_xuid = m_Servers[i]->GetOnlineId(); #if !defined( NO_STEAM ) && !defined( SWDS )
pServerPinging->m_netAdr = m_Servers[i]->m_netAdr; #endif
m_ServersPinging.AddToTail( pServerPinging ); } } else { nextUpdatePeriod = MIN( nextUpdatePeriod, mm_server_search_inet_ping_refresh.GetFloat() - timePassed ); } }
m_flNextServerUpdateTime = now + nextUpdatePeriod;
m_eState = STATE_REQUESTING_DETAILS; break; } } break;
case STATE_LAN_SEARCH: UpdateLanSearch(); break;
case STATE_GROUP_SEARCH: if ( StartFetchingGroupServersData() ) break; // else -> // fall through
case STATE_GROUP_FETCHED: OnGroupFetched(); break;
#if !defined( _X360 ) && !defined( NO_STEAM ) && !defined( SWDS )
case STATE_FETCHING_SERVERS: if ( Plat_FloatTime() > m_lanSearchData.m_flLastBroadcastTime + mm_server_search_inet_ping_timeout.GetFloat() ) { steamapicontext->SteamMatchmakingServers()->ReleaseRequest( m_hRequest ); m_hRequest = NULL; m_eState = STATE_GROUP_FETCHED; } break; #endif
case STATE_REQUESTING_DETAILS: UpdateRequestingDetails(); break; } }
void CServerManager::MarkOldServersAndBeginSearch() { DevMsg( 2, "Server manager refreshing...\n" );
// Signal that we are starting a search
g_pMatchFramework->GetEventsSubscription()->BroadcastEvent( new KeyValues( "OnMatchServerMgrUpdate", "update", "searchstarted" ) );
m_ServersPinging.PurgeAndDeleteElements();
m_groupSearchData.Reset(); m_lanSearchData = SLanSearchData_t(); m_eState = STATE_LAN_SEARCH;
// If broadcasts are disallowed, then go straight to group search state
extern ConVar net_allow_multicast; if ( !net_allow_multicast.GetBool() ) m_eState = STATE_GROUP_SEARCH; }
void CServerManager::UpdateLanSearch() { if ( m_lanSearchData.m_flStartTime && m_lanSearchData.m_flLastBroadcastTime ) { if ( Plat_FloatTime() > m_lanSearchData.m_flStartTime + mm_server_search_lan_ping_duration.GetFloat() ) { m_eState = STATE_GROUP_SEARCH; return; }
if ( Plat_FloatTime() < m_lanSearchData.m_flLastBroadcastTime + mm_server_search_lan_ping_interval.GetFloat() ) { // waiting out interval between pings
return; } } else { // Initialize the start time of the lan broadcast
m_lanSearchData.m_flStartTime = Plat_FloatTime(); }
//
// Send the packet
//
m_lanSearchData.m_flLastBroadcastTime = Plat_FloatTime();
KeyValues *kv = new KeyValues( "LanSearchServerPing" ); KeyValues::AutoDelete autodelete( kv ); kv->SetFloat( "timestamp", Plat_FloatTime() );
if ( mm_server_search_lan_ports.GetString()[0] ) { // Build the list of ports to scan
CSplitString arrPorts( mm_server_search_lan_ports.GetString(), "," );
for ( int i = 0; i < arrPorts.Count(); i++ ) { // Port number
int nPort = Q_atoi( arrPorts[i] ); if ( nPort <= 0 ) continue;
g_pConnectionlessLanMgr->SendPacket( kv, CFmtStr( "*:%d", nPort ) ); } } }
void CServerManager::RemoveOldServers() { float now = Plat_FloatTime();
for ( int k = 0; k < m_Servers.Count(); ++ k ) { CServer *pServer = m_Servers[ k ]; if ( pServer && now - pServer->m_flLastRefresh < mm_server_search_server_lifetime.GetFloat() ) continue;
m_Servers.Remove( k -- ); } }
bool CServerManager::StartFetchingGroupServersData() { #if !defined( _X360 ) && !defined( NO_STEAM ) && !defined( SWDS )
ISteamUser *pUser = steamapicontext->SteamUser(); ISteamFriends *pFriends = steamapicontext->SteamFriends(); if ( !pUser || !pFriends ) return false;
int iGroupCount = pFriends->GetClanCount(); if ( !iGroupCount ) return false;
m_groupSearchData.m_UserGroupAccountIDs.SetCount( iGroupCount ); for ( int k = 0; k < iGroupCount; ++ k ) { m_groupSearchData.m_UserGroupAccountIDs[ k ] = pFriends->GetClanByIndex( k ).GetAccountID(); }
return FetchGroupServers(); #else
return false; #endif
}
bool CServerManager::FetchGroupServers() { #if !defined( _X360 ) && !defined( NO_STEAM ) && !defined( SWDS )
static const char gamedataFilterType[] = "gamedataor";
char gamedataFitler[ mm_filter_max_size ];
*gamedataFitler = 0;
size_t roomLeft = mm_filter_max_size - strlen( gamedataFilterType ) - 3;
// Add as many groups as will fit
while ( m_groupSearchData.m_idxSearchGroupId < m_groupSearchData.m_UserGroupAccountIDs.Count() ) { const char *tag = CFmtStr( *gamedataFitler ? ",grp:%ui" : "grp:%ui", m_groupSearchData.m_UserGroupAccountIDs[ m_groupSearchData.m_idxSearchGroupId ] ); if ( roomLeft < strlen( tag ) ) { break; }
Q_strncat( gamedataFitler, tag, mm_filter_max_size ); roomLeft -= strlen( tag );
++m_groupSearchData.m_idxSearchGroupId; }
MatchMakingKeyValuePair_t filters[ 2 ] = { // filter by game
MatchMakingKeyValuePair_t( "gamedir", COM_GetModDirectory() ), // look for group servers
MatchMakingKeyValuePair_t( gamedataFilterType, gamedataFitler ) };
// request the server list. We will get called back at ServerResponded, ServerFailedToRespond, and RefreshComplete
m_eState = STATE_FETCHING_SERVERS; m_lanSearchData.m_flLastBroadcastTime = Plat_FloatTime();
MatchMakingKeyValuePair_t *pFilter = filters; DevMsg( 2, "Requesting group server list for groups %s...\n", gamedataFitler ); if ( m_hRequest ) steamapicontext->SteamMatchmakingServers()->ReleaseRequest( m_hRequest ); m_hRequest = steamapicontext->SteamMatchmakingServers()->RequestInternetServerList( ( AppId_t ) g_pMatchFramework->GetMatchTitle()->GetTitleID(), &pFilter, ARRAYSIZE( filters ), this );
return true;
#else
return false; #endif
}
void CServerManager::OnGroupFetched() { if ( m_bUpdateEnabled && m_groupSearchData.m_UserGroupAccountIDs.IsValidIndex( m_groupSearchData.m_idxSearchGroupId ) && FetchGroupServers() ) return;
OnAllGroupsFetched(); }
void CServerManager::OnAllGroupsFetched() { m_flNextUpdateTime = Plat_FloatTime() + mm_server_search_update_interval.GetInt();
if ( !m_ServersPinging.Count() ) { OnAllDataFetched(); return; }
m_eState = STATE_REQUESTING_DETAILS; m_lanSearchData.m_flStartTime = Plat_FloatTime();
RequestPingingDetails(); }
void CServerManager::RequestPingingDetails() { // Ping every server that we deferred to ping
KeyValues *kv = new KeyValues( "InetSearchServerDetails" ); KeyValues::AutoDelete autodelete( kv ); for ( int k = 0; k < m_ServersPinging.Count() && k < mm_server_search_inet_ping_window.GetInt(); ++ k ) { CServerPinging *pServerPinging = ( CServerPinging * ) m_ServersPinging[k]; if ( pServerPinging->m_flTimeout && Plat_FloatTime() > pServerPinging->m_flTimeout ) { m_ServersPinging.FastRemove( k -- ); // server timed out
delete pServerPinging; continue; }
kv->SetFloat( "timestamp", Plat_FloatTime() ); kv->SetUint64( "pingxuid", pServerPinging->m_xuid ); #if !defined( _X360 ) && !defined( NO_STEAM ) && !defined( SWDS )
g_pConnectionlessLanMgr->SendPacket( kv, pServerPinging->m_netAdr.GetConnectionAddressString() ); #else
DevWarning( "Cannot request internet pinging server details.\n" ); #endif
if ( !pServerPinging->m_flTimeout ) pServerPinging->m_flTimeout = Plat_FloatTime() + mm_server_search_inet_ping_timeout.GetFloat(); }
m_lanSearchData.m_flLastBroadcastTime = Plat_FloatTime();
DevMsg( 2, "Server manager waiting for game details from %d servers...\n", m_ServersPinging.Count() ); }
void CServerManager::UpdateRequestingDetails() { if ( !m_ServersPinging.Count() ) { // We have no more servers to ping
m_ServersPinging.PurgeAndDeleteElements(); OnAllDataFetched(); return; }
if ( m_lanSearchData.m_flLastBroadcastTime + mm_server_search_inet_ping_interval.GetFloat() < Plat_FloatTime() ) { RequestPingingDetails(); } }
void CServerManager::OnAllDataFetched() { DevMsg( 2, "Server manager refresh completed.\n" );
m_eState = STATE_IDLE;
if ( !m_bUpdateEnabled ) { m_Servers.PurgeAndDeleteElements(); m_ServersPinging.PurgeAndDeleteElements(); } else { RemoveOldServers(); }
if ( g_pMatchFramework ) { g_pMatchFramework->GetEventsSubscription()->BroadcastEvent( new KeyValues( "OnMatchServerMgrUpdate", "update", "searchfinished" ) ); } }
|