/* =========================================================================== Doom 3 GPL Source Code Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company. This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code"). Doom 3 Source Code is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Doom 3 Source Code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Doom 3 Source Code. If not, see . In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below. If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. =========================================================================== */ #include "sys/platform.h" #include "idlib/LangDict.h" #include "framework/async/AsyncNetwork.h" #include "framework/Licensee.h" #include "framework/Common.h" #include "framework/CVarSystem.h" #include "framework/DeclManager.h" #include "framework/DeclEntityDef.h" #include "framework/async/ServerScan.h" idCVar gui_filter_password( "gui_filter_password", "0", CVAR_GUI | CVAR_INTEGER | CVAR_ARCHIVE, "Password filter" ); idCVar gui_filter_players( "gui_filter_players", "0", CVAR_GUI | CVAR_INTEGER | CVAR_ARCHIVE, "Players filter" ); idCVar gui_filter_gameType( "gui_filter_gameType", "0", CVAR_GUI | CVAR_INTEGER | CVAR_ARCHIVE, "Gametype filter" ); idCVar gui_filter_idle( "gui_filter_idle", "0", CVAR_GUI | CVAR_INTEGER | CVAR_ARCHIVE, "Idle servers filter" ); idCVar gui_filter_game( "gui_filter_game", "0", CVAR_GUI | CVAR_INTEGER | CVAR_ARCHIVE, "Game filter" ); const char* l_gameTypes[] = { "Deathmatch", "Tourney", "Team DM", "Last Man", "CTF", NULL }; static idServerScan *l_serverScan = NULL; /* ================ idServerScan::idServerScan ================ */ idServerScan::idServerScan( ) { m_pGUI = NULL; m_sort = SORT_PING; m_sortAscending = true; challenge = 0; LocalClear(); } /* ================ idServerScan::LocalClear ================ */ void idServerScan::LocalClear( ) { scan_state = IDLE; incoming_net = false; lan_pingtime = -1; net_info.Clear(); net_servers.Clear(); cur_info = 0; if ( listGUI ) { listGUI->Clear(); } incoming_useTimeout = false; m_sortedServers.Clear(); } /* ================ idServerScan::Clear ================ */ void idServerScan::Clear( ) { LocalClear(); idList::Clear(); } /* ================ idServerScan::Shutdown ================ */ void idServerScan::Shutdown( ) { m_pGUI = NULL; if ( listGUI ) { listGUI->Config( NULL, NULL ); uiManager->FreeListGUI( listGUI ); listGUI = NULL; } screenshot.Clear(); } /* ================ idServerScan::SetupLANScan ================ */ void idServerScan::SetupLANScan( ) { Clear(); GUIUpdateSelected(); scan_state = LAN_SCAN; challenge++; lan_pingtime = Sys_Milliseconds(); common->DPrintf( "SetupLANScan with challenge %d\n", challenge ); } /* ================ idServerScan::InfoResponse ================ */ int idServerScan::InfoResponse( networkServer_t &server ) { if ( scan_state == IDLE ) { return false; } idStr serv = Sys_NetAdrToString( server.adr ); if ( server.challenge != challenge ) { common->DPrintf( "idServerScan::InfoResponse - ignoring response from %s, wrong challenge %d.", serv.c_str(), server.challenge ); return false; } if ( scan_state == NET_SCAN ) { const idKeyValue *info = net_info.FindKey( serv.c_str() ); if ( !info ) { common->DPrintf( "idServerScan::InfoResponse NET_SCAN: reply from unknown %s\n", serv.c_str() ); return false; } int id = atoi( info->GetValue() ); net_info.Delete( serv.c_str() ); inServer_t iserv = net_servers[ id ]; server.ping = Sys_Milliseconds() - iserv.time; server.id = iserv.id; } else { server.ping = Sys_Milliseconds() - lan_pingtime; server.id = 0; // check for duplicate servers for ( int i = 0; i < Num() ; i++ ) { if ( memcmp( &(*this)[ i ].adr, &server.adr, sizeof(netadr_t) ) == 0 ) { common->DPrintf( "idServerScan::InfoResponse LAN_SCAN: duplicate server %s\n", serv.c_str() ); return true; } } } const char *si_map = server.serverInfo.GetString( "si_map" ); const idDecl *mapDecl = declManager->FindType( DECL_MAPDEF, si_map, false ); const idDeclEntityDef *mapDef = static_cast< const idDeclEntityDef * >( mapDecl ); if ( mapDef ) { const char *mapName = common->GetLanguageDict()->GetString( mapDef->dict.GetString( "name", si_map ) ); server.serverInfo.Set( "si_mapName", mapName ); } else { server.serverInfo.Set( "si_mapName", si_map ); } int index = Append( server ); // for now, don't maintain sorting when adding new info response servers m_sortedServers.Append( Num()-1 ); if ( listGUI->IsConfigured( ) && !IsFiltered( server ) ) { GUIAdd( Num()-1, server ); } if ( listGUI->GetSelection( NULL, 0 ) == ( Num()-1 ) ) { GUIUpdateSelected(); } return index; } /* ================ idServerScan::AddServer ================ */ void idServerScan::AddServer( int id, const char *srv ) { inServer_t s; incoming_net = true; incoming_lastTime = Sys_Milliseconds() + INCOMING_TIMEOUT; s.id = id; // using IPs, not hosts if ( !Sys_StringToNetAdr( srv, &s.adr, false ) ) { common->DPrintf( "idServerScan::AddServer: failed to parse server %s\n", srv ); return; } if ( !s.adr.port ) { s.adr.port = PORT_SERVER; } net_servers.Append( s ); } /* ================ idServerScan::EndServers ================ */ void idServerScan::EndServers( ) { incoming_net = false; l_serverScan = this; m_sortedServers.Sort( idServerScan::Cmp ); ApplyFilter(); } /* ================ idServerScan::StartServers ================ */ void idServerScan::StartServers( bool timeout ) { incoming_net = true; incoming_useTimeout = timeout; incoming_lastTime = Sys_Milliseconds() + REFRESH_START; } /* ================ idServerScan::EmitGetInfo ================ */ void idServerScan::EmitGetInfo( netadr_t &serv ) { idAsyncNetwork::client.GetServerInfo( serv ); } /* =============== idServerScan::GetChallenge =============== */ int idServerScan::GetChallenge( ) { return challenge; } /* ================ idServerScan::NetScan ================ */ void idServerScan::NetScan( ) { if ( !idAsyncNetwork::client.IsPortInitialized() ) { // if the port isn't open, initialize it, but wait for a short // time to let the OS do whatever magic things it needs to do... idAsyncNetwork::client.InitPort(); // start the scan one second from now... scan_state = WAIT_ON_INIT; endWaitTime = Sys_Milliseconds() + 1000; return; } // make sure the client port is open idAsyncNetwork::client.InitPort(); scan_state = NET_SCAN; challenge++; idList::Clear(); m_sortedServers.Clear(); cur_info = 0; net_info.Clear(); listGUI->Clear(); GUIUpdateSelected(); common->DPrintf( "NetScan with challenge %d\n", challenge ); while ( cur_info < Min( net_servers.Num(), MAX_PINGREQUESTS ) ) { netadr_t serv = net_servers[ cur_info ].adr; EmitGetInfo( serv ); net_servers[ cur_info ].time = Sys_Milliseconds(); net_info.SetInt( Sys_NetAdrToString( serv ), cur_info ); cur_info++; } } /* =============== idServerScan::ServerScanFrame =============== */ void idServerScan::RunFrame( ) { if ( scan_state == IDLE ) { return; } if ( scan_state == WAIT_ON_INIT ) { if ( Sys_Milliseconds() >= endWaitTime ) { scan_state = IDLE; NetScan(); } return; } int timeout_limit = Sys_Milliseconds() - REPLY_TIMEOUT; if ( scan_state == LAN_SCAN ) { if ( timeout_limit > lan_pingtime ) { common->Printf( "Scanned for servers on the LAN\n" ); scan_state = IDLE; } return; } // if scan_state == NET_SCAN // check for timeouts int i = 0; while ( i < net_info.GetNumKeyVals() ) { if ( timeout_limit > net_servers[ atoi( net_info.GetKeyVal( i )->GetValue().c_str() ) ].time ) { common->DPrintf( "timeout %s\n", net_info.GetKeyVal( i )->GetKey().c_str() ); net_info.Delete( net_info.GetKeyVal( i )->GetKey().c_str() ); } else { i++; } } // possibly send more queries while ( cur_info < net_servers.Num() && net_info.GetNumKeyVals() < MAX_PINGREQUESTS ) { netadr_t serv = net_servers[ cur_info ].adr; EmitGetInfo( serv ); net_servers[ cur_info ].time = Sys_Milliseconds(); net_info.SetInt( Sys_NetAdrToString( serv ), cur_info ); cur_info++; } // update state if ( ( !incoming_net || ( incoming_useTimeout && Sys_Milliseconds() > incoming_lastTime ) ) && net_info.GetNumKeyVals() == 0 ) { EndServers(); // the list is complete, we are no longer waiting for any getInfo replies common->Printf( "Scanned %d servers.\n", cur_info ); scan_state = IDLE; } } /* =============== idServerScan::GetBestPing =============== */ bool idServerScan::GetBestPing( networkServer_t &serv ) { int i, ic; ic = Num(); if ( !ic ) { return false; } serv = (*this)[ 0 ]; for ( i = 0 ; i < ic ; i++ ) { if ( (*this)[ i ].ping < serv.ping ) { serv = (*this)[ i ]; } } return true; } /* ================ idServerScan::GUIConfig ================ */ void idServerScan::GUIConfig( idUserInterface *pGUI, const char *name ) { m_pGUI = pGUI; if ( listGUI == NULL ) { listGUI = uiManager->AllocListGUI(); } listGUI->Config( pGUI, name ); } /* ================ idServerScan::GUIUpdateSelected ================ */ void idServerScan::GUIUpdateSelected( void ) { char screenshot[ MAX_STRING_CHARS ]; if ( !m_pGUI ) { return; } int i = listGUI->GetSelection( NULL, 0 ); if ( i == -1 || i >= Num() ) { m_pGUI->SetStateString( "server_name", "" ); m_pGUI->SetStateString( "player1", "" ); m_pGUI->SetStateString( "player2", "" ); m_pGUI->SetStateString( "player3", "" ); m_pGUI->SetStateString( "player4", "" ); m_pGUI->SetStateString( "player5", "" ); m_pGUI->SetStateString( "player6", "" ); m_pGUI->SetStateString( "player7", "" ); m_pGUI->SetStateString( "player8", "" ); m_pGUI->SetStateString( "server_map", "" ); m_pGUI->SetStateString( "browser_levelshot", "" ); m_pGUI->SetStateString( "server_gameType", "" ); m_pGUI->SetStateString( "server_IP", "" ); m_pGUI->SetStateString( "server_passworded", "" ); } else { m_pGUI->SetStateString( "server_name", (*this)[ i ].serverInfo.GetString( "si_name" ) ); for ( int j = 0; j < 8; j++ ) { if ( (*this)[i].clients > j ) { m_pGUI->SetStateString( va( "player%i", j + 1 ) , (*this)[ i ].nickname[ j ] ); } else { m_pGUI->SetStateString( va( "player%i", j + 1 ) , "" ); } } m_pGUI->SetStateString( "server_map", (*this)[ i ].serverInfo.GetString( "si_mapName" ) ); fileSystem->FindMapScreenshot( (*this)[ i ].serverInfo.GetString( "si_map" ), screenshot, MAX_STRING_CHARS ); m_pGUI->SetStateString( "browser_levelshot", screenshot ); m_pGUI->SetStateString( "server_gameType", (*this)[ i ].serverInfo.GetString( "si_gameType" ) ); m_pGUI->SetStateString( "server_IP", Sys_NetAdrToString( (*this)[ i ].adr ) ); if ( (*this)[ i ].serverInfo.GetBool( "si_usePass" ) ) { m_pGUI->SetStateString( "server_passworded", "PASSWORD REQUIRED" ); } else { m_pGUI->SetStateString( "server_passworded", "" ); } } } /* ================ idServerScan::GUIAdd ================ */ void idServerScan::GUIAdd( int id, const networkServer_t server ) { idStr name = server.serverInfo.GetString( "si_name", GAME_NAME " Server" ); bool d3xp = false; bool mod = false; if ( !idStr::Icmp( server.serverInfo.GetString( "fs_game" ), "d3xp" ) || !idStr::Icmp( server.serverInfo.GetString( "fs_game_base" ), "d3xp" ) ) { d3xp = true; } if ( server.serverInfo.GetString( "fs_game" )[ 0 ] != '\0' ) { mod = true; } name += "\t"; if ( server.serverInfo.GetString( "sv_punkbuster" )[ 0 ] == '1' ) { name += "mtr_PB"; } name += "\t"; if ( d3xp ) { // FIXME: even for a 'D3XP mod' // could have a specific icon for this case name += "mtr_doom3XPIcon"; } else if ( mod ) { name += "mtr_doom3Mod"; } else { name += "mtr_doom3Icon"; } name += "\t"; name += va( "%i/%i\t", server.clients, server.serverInfo.GetInt( "si_maxPlayers" ) ); name += ( server.ping > -1 ) ? va( "%i\t", server.ping ) : "na\t"; name += server.serverInfo.GetString( "si_gametype" ); name += "\t"; name += server.serverInfo.GetString( "si_mapName" ); name += "\t"; listGUI->Add( id, name ); } /* ================ idServerScan::ApplyFilter ================ */ void idServerScan::ApplyFilter( ) { int i; networkServer_t serv; idStr s; listGUI->SetStateChanges( false ); listGUI->Clear(); for ( i = m_sortAscending ? 0 : m_sortedServers.Num() - 1; m_sortAscending ? i < m_sortedServers.Num() : i >= 0; m_sortAscending ? i++ : i-- ) { serv = (*this)[ m_sortedServers[ i ] ]; if ( !IsFiltered( serv ) ) { GUIAdd( m_sortedServers[ i ], serv ); } } GUIUpdateSelected(); listGUI->SetStateChanges( true ); } /* ================ idServerScan::IsFiltered ================ */ bool idServerScan::IsFiltered( const networkServer_t server ) { int i; const idKeyValue *keyval; // password filter keyval = server.serverInfo.FindKey( "si_usePass" ); if ( keyval && gui_filter_password.GetInteger() == 1 ) { // show passworded only if ( keyval->GetValue()[ 0 ] == '0' ) { return true; } } else if ( keyval && gui_filter_password.GetInteger() == 2 ) { // show no password only if ( keyval->GetValue()[ 0 ] != '0' ) { return true; } } // players filter keyval = server.serverInfo.FindKey( "si_maxPlayers" ); if ( keyval ) { if ( gui_filter_players.GetInteger() == 1 && server.clients == atoi( keyval->GetValue() ) ) { return true; } else if ( gui_filter_players.GetInteger() == 2 && ( !server.clients || server.clients == atoi( keyval->GetValue() ) ) ) { return true; } } // gametype filter keyval = server.serverInfo.FindKey( "si_gameType" ); if ( keyval && gui_filter_gameType.GetInteger() ) { i = 0; while ( l_gameTypes[ i ] ) { if ( !keyval->GetValue().Icmp( l_gameTypes[ i ] ) ) { break; } i++; } if ( l_gameTypes[ i ] && i != gui_filter_gameType.GetInteger() -1 ) { return true; } } // idle server filter keyval = server.serverInfo.FindKey( "si_idleServer" ); if ( keyval && !gui_filter_idle.GetInteger() ) { if ( !keyval->GetValue().Icmp( "1" ) ) { return true; } } // autofilter D3XP games if the user does not has the XP installed if(!fileSystem->HasD3XP() && !idStr::Icmp(server.serverInfo.GetString( "fs_game" ), "d3xp")) { return true; } // filter based on the game doom or XP if(gui_filter_game.GetInteger() == 1) { //Only Doom if(idStr::Icmp(server.serverInfo.GetString("fs_game"), "")) { return true; } } else if(gui_filter_game.GetInteger() == 2) { //Only D3XP if(idStr::Icmp(server.serverInfo.GetString("fs_game"), "d3xp")) { return true; } } return false; } /* ================ idServerScan::Cmp ================ */ int idServerScan::Cmp( const int *a, const int *b ) { networkServer_t serv1, serv2; idStr s1, s2; int ret; serv1 = (*l_serverScan)[ *a ]; serv2 = (*l_serverScan)[ *b ]; switch ( l_serverScan->m_sort ) { case SORT_PING: ret = serv1.ping < serv2.ping ? -1 : ( serv1.ping > serv2.ping ? 1 : 0 ); return ret; case SORT_SERVERNAME: serv1.serverInfo.GetString( "si_name", "", s1 ); serv2.serverInfo.GetString( "si_name", "", s2 ); return s1.IcmpNoColor( s2 ); case SORT_PLAYERS: ret = serv1.clients < serv2.clients ? -1 : ( serv1.clients > serv2.clients ? 1 : 0 ); return ret; case SORT_GAMETYPE: serv1.serverInfo.GetString( "si_gameType", "", s1 ); serv2.serverInfo.GetString( "si_gameType", "", s2 ); return s1.Icmp( s2 ); case SORT_MAP: serv1.serverInfo.GetString( "si_mapName", "", s1 ); serv2.serverInfo.GetString( "si_mapName", "", s2 ); return s1.Icmp( s2 ); case SORT_GAME: serv1.serverInfo.GetString( "fs_game", "", s1 ); serv2.serverInfo.GetString( "fs_game", "", s2 ); return s1.Icmp( s2 ); } return 0; } /* ================ idServerScan::SetSorting ================ */ void idServerScan::SetSorting( serverSort_t sort ) { l_serverScan = this; if ( sort == m_sort ) { m_sortAscending = !m_sortAscending; } else { m_sort = sort; m_sortAscending = true; // is the default for any new sort m_sortedServers.Sort( idServerScan::Cmp ); } // trigger a redraw ApplyFilter(); }