ServerScan.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640
  1. /*
  2. ===========================================================================
  3. Doom 3 GPL Source Code
  4. Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
  5. This file is part of the Doom 3 GPL Source Code (?Doom 3 Source Code?).
  6. Doom 3 Source Code is free software: you can redistribute it and/or modify
  7. it under the terms of the GNU General Public License as published by
  8. the Free Software Foundation, either version 3 of the License, or
  9. (at your option) any later version.
  10. Doom 3 Source Code is distributed in the hope that it will be useful,
  11. but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. GNU General Public License for more details.
  14. You should have received a copy of the GNU General Public License
  15. along with Doom 3 Source Code. If not, see <http://www.gnu.org/licenses/>.
  16. 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.
  17. 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.
  18. ===========================================================================
  19. */
  20. #include "../../idlib/precompiled.h"
  21. #pragma hdrstop
  22. idCVar gui_filter_password( "gui_filter_password", "0", CVAR_GUI | CVAR_INTEGER | CVAR_ARCHIVE, "Password filter" );
  23. idCVar gui_filter_players( "gui_filter_players", "0", CVAR_GUI | CVAR_INTEGER | CVAR_ARCHIVE, "Players filter" );
  24. idCVar gui_filter_gameType( "gui_filter_gameType", "0", CVAR_GUI | CVAR_INTEGER | CVAR_ARCHIVE, "Gametype filter" );
  25. idCVar gui_filter_idle( "gui_filter_idle", "0", CVAR_GUI | CVAR_INTEGER | CVAR_ARCHIVE, "Idle servers filter" );
  26. idCVar gui_filter_game( "gui_filter_game", "0", CVAR_GUI | CVAR_INTEGER | CVAR_ARCHIVE, "Game filter" );
  27. const char* l_gameTypes[] = {
  28. "Deathmatch",
  29. "Tourney",
  30. "Team DM",
  31. "Last Man",
  32. "CTF",
  33. NULL
  34. };
  35. static idServerScan *l_serverScan = NULL;
  36. /*
  37. ================
  38. idServerScan::idServerScan
  39. ================
  40. */
  41. idServerScan::idServerScan( ) {
  42. m_pGUI = NULL;
  43. m_sort = SORT_PING;
  44. m_sortAscending = true;
  45. challenge = 0;
  46. LocalClear();
  47. }
  48. /*
  49. ================
  50. idServerScan::LocalClear
  51. ================
  52. */
  53. void idServerScan::LocalClear( ) {
  54. scan_state = IDLE;
  55. incoming_net = false;
  56. lan_pingtime = -1;
  57. net_info.Clear();
  58. net_servers.Clear();
  59. cur_info = 0;
  60. if ( listGUI ) {
  61. listGUI->Clear();
  62. }
  63. incoming_useTimeout = false;
  64. m_sortedServers.Clear();
  65. }
  66. /*
  67. ================
  68. idServerScan::Clear
  69. ================
  70. */
  71. void idServerScan::Clear( ) {
  72. LocalClear();
  73. idList<networkServer_t>::Clear();
  74. }
  75. /*
  76. ================
  77. idServerScan::Shutdown
  78. ================
  79. */
  80. void idServerScan::Shutdown( ) {
  81. m_pGUI = NULL;
  82. if ( listGUI ) {
  83. listGUI->Config( NULL, NULL );
  84. uiManager->FreeListGUI( listGUI );
  85. listGUI = NULL;
  86. }
  87. screenshot.Clear();
  88. }
  89. /*
  90. ================
  91. idServerScan::SetupLANScan
  92. ================
  93. */
  94. void idServerScan::SetupLANScan( ) {
  95. Clear();
  96. GUIUpdateSelected();
  97. scan_state = LAN_SCAN;
  98. challenge++;
  99. lan_pingtime = Sys_Milliseconds();
  100. common->DPrintf( "SetupLANScan with challenge %d\n", challenge );
  101. }
  102. /*
  103. ================
  104. idServerScan::InfoResponse
  105. ================
  106. */
  107. int idServerScan::InfoResponse( networkServer_t &server ) {
  108. if ( scan_state == IDLE ) {
  109. return false;
  110. }
  111. idStr serv = Sys_NetAdrToString( server.adr );
  112. if ( server.challenge != challenge ) {
  113. common->DPrintf( "idServerScan::InfoResponse - ignoring response from %s, wrong challenge %d.", serv.c_str(), server.challenge );
  114. return false;
  115. }
  116. if ( scan_state == NET_SCAN ) {
  117. const idKeyValue *info = net_info.FindKey( serv.c_str() );
  118. if ( !info ) {
  119. common->DPrintf( "idServerScan::InfoResponse NET_SCAN: reply from unknown %s\n", serv.c_str() );
  120. return false;
  121. }
  122. int id = atoi( info->GetValue() );
  123. net_info.Delete( serv.c_str() );
  124. inServer_t iserv = net_servers[ id ];
  125. server.ping = Sys_Milliseconds() - iserv.time;
  126. server.id = iserv.id;
  127. } else {
  128. server.ping = Sys_Milliseconds() - lan_pingtime;
  129. server.id = 0;
  130. // check for duplicate servers
  131. for ( int i = 0; i < Num() ; i++ ) {
  132. if ( memcmp( &(*this)[ i ].adr, &server.adr, sizeof(netadr_t) ) == 0 ) {
  133. common->DPrintf( "idServerScan::InfoResponse LAN_SCAN: duplicate server %s\n", serv.c_str() );
  134. return true;
  135. }
  136. }
  137. }
  138. const char *si_map = server.serverInfo.GetString( "si_map" );
  139. const idDecl *mapDecl = declManager->FindType( DECL_MAPDEF, si_map, false );
  140. const idDeclEntityDef *mapDef = static_cast< const idDeclEntityDef * >( mapDecl );
  141. if ( mapDef ) {
  142. const char *mapName = common->GetLanguageDict()->GetString( mapDef->dict.GetString( "name", si_map ) );
  143. server.serverInfo.Set( "si_mapName", mapName );
  144. } else {
  145. server.serverInfo.Set( "si_mapName", si_map );
  146. }
  147. int index = Append( server );
  148. // for now, don't maintain sorting when adding new info response servers
  149. m_sortedServers.Append( Num()-1 );
  150. if ( listGUI->IsConfigured( ) && !IsFiltered( server ) ) {
  151. GUIAdd( Num()-1, server );
  152. }
  153. if ( listGUI->GetSelection( NULL, 0 ) == ( Num()-1 ) ) {
  154. GUIUpdateSelected();
  155. }
  156. return index;
  157. }
  158. /*
  159. ================
  160. idServerScan::AddServer
  161. ================
  162. */
  163. void idServerScan::AddServer( int id, const char *srv ) {
  164. inServer_t s;
  165. incoming_net = true;
  166. incoming_lastTime = Sys_Milliseconds() + INCOMING_TIMEOUT;
  167. s.id = id;
  168. // using IPs, not hosts
  169. if ( !Sys_StringToNetAdr( srv, &s.adr, false ) ) {
  170. common->DPrintf( "idServerScan::AddServer: failed to parse server %s\n", srv );
  171. return;
  172. }
  173. if ( !s.adr.port ) {
  174. s.adr.port = PORT_SERVER;
  175. }
  176. net_servers.Append( s );
  177. }
  178. /*
  179. ================
  180. idServerScan::EndServers
  181. ================
  182. */
  183. void idServerScan::EndServers( ) {
  184. incoming_net = false;
  185. l_serverScan = this;
  186. m_sortedServers.Sort( idServerScan::Cmp );
  187. ApplyFilter();
  188. }
  189. /*
  190. ================
  191. idServerScan::StartServers
  192. ================
  193. */
  194. void idServerScan::StartServers( bool timeout ) {
  195. incoming_net = true;
  196. incoming_useTimeout = timeout;
  197. incoming_lastTime = Sys_Milliseconds() + REFRESH_START;
  198. }
  199. /*
  200. ================
  201. idServerScan::EmitGetInfo
  202. ================
  203. */
  204. void idServerScan::EmitGetInfo( netadr_t &serv ) {
  205. idAsyncNetwork::client.GetServerInfo( serv );
  206. }
  207. /*
  208. ===============
  209. idServerScan::GetChallenge
  210. ===============
  211. */
  212. int idServerScan::GetChallenge( ) {
  213. return challenge;
  214. }
  215. /*
  216. ================
  217. idServerScan::NetScan
  218. ================
  219. */
  220. void idServerScan::NetScan( ) {
  221. if ( !idAsyncNetwork::client.IsPortInitialized() ) {
  222. // if the port isn't open, initialize it, but wait for a short
  223. // time to let the OS do whatever magic things it needs to do...
  224. idAsyncNetwork::client.InitPort();
  225. // start the scan one second from now...
  226. scan_state = WAIT_ON_INIT;
  227. endWaitTime = Sys_Milliseconds() + 1000;
  228. return;
  229. }
  230. // make sure the client port is open
  231. idAsyncNetwork::client.InitPort();
  232. scan_state = NET_SCAN;
  233. challenge++;
  234. idList<networkServer_t>::Clear();
  235. m_sortedServers.Clear();
  236. cur_info = 0;
  237. net_info.Clear();
  238. listGUI->Clear();
  239. GUIUpdateSelected();
  240. common->DPrintf( "NetScan with challenge %d\n", challenge );
  241. while ( cur_info < Min( net_servers.Num(), MAX_PINGREQUESTS ) ) {
  242. netadr_t serv = net_servers[ cur_info ].adr;
  243. EmitGetInfo( serv );
  244. net_servers[ cur_info ].time = Sys_Milliseconds();
  245. net_info.SetInt( Sys_NetAdrToString( serv ), cur_info );
  246. cur_info++;
  247. }
  248. }
  249. /*
  250. ===============
  251. idServerScan::ServerScanFrame
  252. ===============
  253. */
  254. void idServerScan::RunFrame( ) {
  255. if ( scan_state == IDLE ) {
  256. return;
  257. }
  258. if ( scan_state == WAIT_ON_INIT ) {
  259. if ( Sys_Milliseconds() >= endWaitTime ) {
  260. scan_state = IDLE;
  261. NetScan();
  262. }
  263. return;
  264. }
  265. int timeout_limit = Sys_Milliseconds() - REPLY_TIMEOUT;
  266. if ( scan_state == LAN_SCAN ) {
  267. if ( timeout_limit > lan_pingtime ) {
  268. common->Printf( "Scanned for servers on the LAN\n" );
  269. scan_state = IDLE;
  270. }
  271. return;
  272. }
  273. // if scan_state == NET_SCAN
  274. // check for timeouts
  275. int i = 0;
  276. while ( i < net_info.GetNumKeyVals() ) {
  277. if ( timeout_limit > net_servers[ atoi( net_info.GetKeyVal( i )->GetValue().c_str() ) ].time ) {
  278. common->DPrintf( "timeout %s\n", net_info.GetKeyVal( i )->GetKey().c_str() );
  279. net_info.Delete( net_info.GetKeyVal( i )->GetKey().c_str() );
  280. } else {
  281. i++;
  282. }
  283. }
  284. // possibly send more queries
  285. while ( cur_info < net_servers.Num() && net_info.GetNumKeyVals() < MAX_PINGREQUESTS ) {
  286. netadr_t serv = net_servers[ cur_info ].adr;
  287. EmitGetInfo( serv );
  288. net_servers[ cur_info ].time = Sys_Milliseconds();
  289. net_info.SetInt( Sys_NetAdrToString( serv ), cur_info );
  290. cur_info++;
  291. }
  292. // update state
  293. if ( ( !incoming_net || ( incoming_useTimeout && Sys_Milliseconds() > incoming_lastTime ) ) && net_info.GetNumKeyVals() == 0 ) {
  294. EndServers();
  295. // the list is complete, we are no longer waiting for any getInfo replies
  296. common->Printf( "Scanned %d servers.\n", cur_info );
  297. scan_state = IDLE;
  298. }
  299. }
  300. /*
  301. ===============
  302. idServerScan::GetBestPing
  303. ===============
  304. */
  305. bool idServerScan::GetBestPing( networkServer_t &serv ) {
  306. int i, ic;
  307. ic = Num();
  308. if ( !ic ) {
  309. return false;
  310. }
  311. serv = (*this)[ 0 ];
  312. for ( i = 0 ; i < ic ; i++ ) {
  313. if ( (*this)[ i ].ping < serv.ping ) {
  314. serv = (*this)[ i ];
  315. }
  316. }
  317. return true;
  318. }
  319. /*
  320. ================
  321. idServerScan::GUIConfig
  322. ================
  323. */
  324. void idServerScan::GUIConfig( idUserInterface *pGUI, const char *name ) {
  325. m_pGUI = pGUI;
  326. if ( listGUI == NULL ) {
  327. listGUI = uiManager->AllocListGUI();
  328. }
  329. listGUI->Config( pGUI, name );
  330. }
  331. /*
  332. ================
  333. idServerScan::GUIUpdateSelected
  334. ================
  335. */
  336. void idServerScan::GUIUpdateSelected( void ) {
  337. char screenshot[ MAX_STRING_CHARS ];
  338. if ( !m_pGUI ) {
  339. return;
  340. }
  341. int i = listGUI->GetSelection( NULL, 0 );
  342. if ( i == -1 || i >= Num() ) {
  343. m_pGUI->SetStateString( "server_name", "" );
  344. m_pGUI->SetStateString( "player1", "" );
  345. m_pGUI->SetStateString( "player2", "" );
  346. m_pGUI->SetStateString( "player3", "" );
  347. m_pGUI->SetStateString( "player4", "" );
  348. m_pGUI->SetStateString( "player5", "" );
  349. m_pGUI->SetStateString( "player6", "" );
  350. m_pGUI->SetStateString( "player7", "" );
  351. m_pGUI->SetStateString( "player8", "" );
  352. m_pGUI->SetStateString( "server_map", "" );
  353. m_pGUI->SetStateString( "browser_levelshot", "" );
  354. m_pGUI->SetStateString( "server_gameType", "" );
  355. m_pGUI->SetStateString( "server_IP", "" );
  356. m_pGUI->SetStateString( "server_passworded", "" );
  357. } else {
  358. m_pGUI->SetStateString( "server_name", (*this)[ i ].serverInfo.GetString( "si_name" ) );
  359. for ( int j = 0; j < 8; j++ ) {
  360. if ( (*this)[i].clients > j ) {
  361. m_pGUI->SetStateString( va( "player%i", j + 1 ) , (*this)[ i ].nickname[ j ] );
  362. } else {
  363. m_pGUI->SetStateString( va( "player%i", j + 1 ) , "" );
  364. }
  365. }
  366. m_pGUI->SetStateString( "server_map", (*this)[ i ].serverInfo.GetString( "si_mapName" ) );
  367. fileSystem->FindMapScreenshot( (*this)[ i ].serverInfo.GetString( "si_map" ), screenshot, MAX_STRING_CHARS );
  368. m_pGUI->SetStateString( "browser_levelshot", screenshot );
  369. m_pGUI->SetStateString( "server_gameType", (*this)[ i ].serverInfo.GetString( "si_gameType" ) );
  370. m_pGUI->SetStateString( "server_IP", Sys_NetAdrToString( (*this)[ i ].adr ) );
  371. if ( (*this)[ i ].serverInfo.GetBool( "si_usePass" ) ) {
  372. m_pGUI->SetStateString( "server_passworded", "PASSWORD REQUIRED" );
  373. } else {
  374. m_pGUI->SetStateString( "server_passworded", "" );
  375. }
  376. }
  377. }
  378. /*
  379. ================
  380. idServerScan::GUIAdd
  381. ================
  382. */
  383. void idServerScan::GUIAdd( int id, const networkServer_t server ) {
  384. idStr name = server.serverInfo.GetString( "si_name", GAME_NAME " Server" );
  385. bool d3xp = false;
  386. bool mod = false;
  387. if ( !idStr::Icmp( server.serverInfo.GetString( "fs_game" ), "d3xp" ) ||
  388. !idStr::Icmp( server.serverInfo.GetString( "fs_game_base" ), "d3xp" ) ) {
  389. d3xp = true;
  390. }
  391. if ( server.serverInfo.GetString( "fs_game" )[ 0 ] != '\0' ) {
  392. mod = true;
  393. }
  394. name += "\t";
  395. if ( server.serverInfo.GetString( "sv_punkbuster" )[ 0 ] == '1' ) {
  396. name += "mtr_PB";
  397. }
  398. name += "\t";
  399. if ( d3xp ) {
  400. // FIXME: even for a 'D3XP mod'
  401. // could have a specific icon for this case
  402. name += "mtr_doom3XPIcon";
  403. } else if ( mod ) {
  404. name += "mtr_doom3Mod";
  405. } else {
  406. name += "mtr_doom3Icon";
  407. }
  408. name += "\t";
  409. name += va( "%i/%i\t", server.clients, server.serverInfo.GetInt( "si_maxPlayers" ) );
  410. name += ( server.ping > -1 ) ? va( "%i\t", server.ping ) : "na\t";
  411. name += server.serverInfo.GetString( "si_gametype" );
  412. name += "\t";
  413. name += server.serverInfo.GetString( "si_mapName" );
  414. name += "\t";
  415. listGUI->Add( id, name );
  416. }
  417. /*
  418. ================
  419. idServerScan::ApplyFilter
  420. ================
  421. */
  422. void idServerScan::ApplyFilter( ) {
  423. int i;
  424. networkServer_t serv;
  425. idStr s;
  426. listGUI->SetStateChanges( false );
  427. listGUI->Clear();
  428. for ( i = m_sortAscending ? 0 : m_sortedServers.Num() - 1;
  429. m_sortAscending ? i < m_sortedServers.Num() : i >= 0;
  430. m_sortAscending ? i++ : i-- ) {
  431. serv = (*this)[ m_sortedServers[ i ] ];
  432. if ( !IsFiltered( serv ) ) {
  433. GUIAdd( m_sortedServers[ i ], serv );
  434. }
  435. }
  436. GUIUpdateSelected();
  437. listGUI->SetStateChanges( true );
  438. }
  439. /*
  440. ================
  441. idServerScan::IsFiltered
  442. ================
  443. */
  444. bool idServerScan::IsFiltered( const networkServer_t server ) {
  445. int i;
  446. const idKeyValue *keyval;
  447. // OS support filter
  448. #if 0
  449. // filter out pure servers that won't provide checksumed game code for client OS
  450. keyval = server.serverInfo.FindKey( "si_pure" );
  451. if ( keyval && !idStr::Cmp( keyval->GetValue(), "1" ) ) {
  452. if ( ( server.OSMask & ( 1 << BUILD_OS_ID ) ) == 0 ) {
  453. return true;
  454. }
  455. }
  456. #else
  457. if ( ( server.OSMask & ( 1 << BUILD_OS_ID ) ) == 0 ) {
  458. return true;
  459. }
  460. #endif
  461. // password filter
  462. keyval = server.serverInfo.FindKey( "si_usePass" );
  463. if ( keyval && gui_filter_password.GetInteger() == 1 ) {
  464. // show passworded only
  465. if ( keyval->GetValue()[ 0 ] == '0' ) {
  466. return true;
  467. }
  468. } else if ( keyval && gui_filter_password.GetInteger() == 2 ) {
  469. // show no password only
  470. if ( keyval->GetValue()[ 0 ] != '0' ) {
  471. return true;
  472. }
  473. }
  474. // players filter
  475. keyval = server.serverInfo.FindKey( "si_maxPlayers" );
  476. if ( keyval ) {
  477. if ( gui_filter_players.GetInteger() == 1 && server.clients == atoi( keyval->GetValue() ) ) {
  478. return true;
  479. } else if ( gui_filter_players.GetInteger() == 2 && ( !server.clients || server.clients == atoi( keyval->GetValue() ) ) ) {
  480. return true;
  481. }
  482. }
  483. // gametype filter
  484. keyval = server.serverInfo.FindKey( "si_gameType" );
  485. if ( keyval && gui_filter_gameType.GetInteger() ) {
  486. i = 0;
  487. while ( l_gameTypes[ i ] ) {
  488. if ( !keyval->GetValue().Icmp( l_gameTypes[ i ] ) ) {
  489. break;
  490. }
  491. i++;
  492. }
  493. if ( l_gameTypes[ i ] && i != gui_filter_gameType.GetInteger() -1 ) {
  494. return true;
  495. }
  496. }
  497. // idle server filter
  498. keyval = server.serverInfo.FindKey( "si_idleServer" );
  499. if ( keyval && !gui_filter_idle.GetInteger() ) {
  500. if ( !keyval->GetValue().Icmp( "1" ) ) {
  501. return true;
  502. }
  503. }
  504. // autofilter D3XP games if the user does not has the XP installed
  505. if(!fileSystem->HasD3XP() && !idStr::Icmp(server.serverInfo.GetString( "fs_game" ), "d3xp")) {
  506. return true;
  507. }
  508. // filter based on the game doom or XP
  509. if(gui_filter_game.GetInteger() == 1) { //Only Doom
  510. if(idStr::Icmp(server.serverInfo.GetString("fs_game"), "")) {
  511. return true;
  512. }
  513. } else if(gui_filter_game.GetInteger() == 2) { //Only D3XP
  514. if(idStr::Icmp(server.serverInfo.GetString("fs_game"), "d3xp")) {
  515. return true;
  516. }
  517. }
  518. return false;
  519. }
  520. /*
  521. ================
  522. idServerScan::Cmp
  523. ================
  524. */
  525. int idServerScan::Cmp( const int *a, const int *b ) {
  526. networkServer_t serv1, serv2;
  527. idStr s1, s2;
  528. int ret;
  529. serv1 = (*l_serverScan)[ *a ];
  530. serv2 = (*l_serverScan)[ *b ];
  531. switch ( l_serverScan->m_sort ) {
  532. case SORT_PING:
  533. ret = serv1.ping < serv2.ping ? -1 : ( serv1.ping > serv2.ping ? 1 : 0 );
  534. return ret;
  535. case SORT_SERVERNAME:
  536. serv1.serverInfo.GetString( "si_name", "", s1 );
  537. serv2.serverInfo.GetString( "si_name", "", s2 );
  538. return s1.IcmpNoColor( s2 );
  539. case SORT_PLAYERS:
  540. ret = serv1.clients < serv2.clients ? -1 : ( serv1.clients > serv2.clients ? 1 : 0 );
  541. return ret;
  542. case SORT_GAMETYPE:
  543. serv1.serverInfo.GetString( "si_gameType", "", s1 );
  544. serv2.serverInfo.GetString( "si_gameType", "", s2 );
  545. return s1.Icmp( s2 );
  546. case SORT_MAP:
  547. serv1.serverInfo.GetString( "si_mapName", "", s1 );
  548. serv2.serverInfo.GetString( "si_mapName", "", s2 );
  549. return s1.Icmp( s2 );
  550. case SORT_GAME:
  551. serv1.serverInfo.GetString( "fs_game", "", s1 );
  552. serv2.serverInfo.GetString( "fs_game", "", s2 );
  553. return s1.Icmp( s2 );
  554. }
  555. return 0;
  556. }
  557. /*
  558. ================
  559. idServerScan::SetSorting
  560. ================
  561. */
  562. void idServerScan::SetSorting( serverSort_t sort ) {
  563. l_serverScan = this;
  564. if ( sort == m_sort ) {
  565. m_sortAscending = !m_sortAscending;
  566. } else {
  567. m_sort = sort;
  568. m_sortAscending = true; // is the default for any new sort
  569. m_sortedServers.Sort( idServerScan::Cmp );
  570. }
  571. // trigger a redraw
  572. ApplyFilter();
  573. }