/*
===========================================================================
IceTech GPL Source Code
Copyright (C) 2026 Justin Marshall
This file is part of the IceTech GPL Source Code (?IceTech Source Code?).
IceTech 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.
IceTech 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 IceTech Source Code. If not, see .
In addition, the IceTech 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 IceTech 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 Justin Marshall, justinmarshall20@gmail.com
===========================================================================
*/
#include "precompiled.h"
#pragma hdrstop
#ifdef QUAKE4
#include "NetworkSystem.h"
#endif
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" );
#ifdef QUAKE4
idCVar g_friendsList( "g_friendsList", "", CVAR_GUI | CVAR_ARCHIVE, "Quake 4 style friends list" );
idCVar g_favoritesList( "g_favoritesList", "", CVAR_GUI | CVAR_ARCHIVE, "Quake 4 style favorites list" );
#endif
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;
// OS support filter
#if 0
// filter out pure servers that won't provide checksumed game code for client OS
keyval = server.serverInfo.FindKey( "si_pure" );
if ( keyval && !idStr::Cmp( keyval->GetValue(), "1" ) ) {
if ( ( server.OSMask & ( 1 << BUILD_OS_ID ) ) == 0 ) {
return true;
}
}
#else
if ( ( server.OSMask & ( 1 << BUILD_OS_ID ) ) == 0 ) {
return true;
}
#endif
// 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();
}
#ifdef QUAKE4
/*
================
Friend list helpers
================
*/
static bool Q4_ParseFriendEntry( const char *start, friend_t &outFriend ) {
memset( &outFriend, 0, sizeof( outFriend ) );
const char *p = strstr( start, "{[" );
if ( !p ) {
return false;
}
p += 2;
const char *endGuid = strstr( p, "][" );
if ( !endGuid ) {
return false;
}
int guidLen = Min( (int)( endGuid - p ), (int)sizeof( outFriend.guid ) - 1 );
idStr::Copynz( outFriend.guid, p, guidLen + 1 );
p = endGuid + 2;
const char *endName = strstr( p, "][" );
if ( !endName ) {
return false;
}
int nameLen = Min( (int)( endName - p ), (int)sizeof( outFriend.name ) - 1 );
idStr::Copynz( outFriend.name, p, nameLen + 1 );
p = endName + 2;
const char *endClan = strstr( p, "]}" );
if ( !endClan ) {
return false;
}
int clanLen = Min( (int)( endClan - p ), (int)sizeof( outFriend.clan ) - 1 );
idStr::Copynz( outFriend.clan, p, clanLen + 1 );
return true;
}
int idServerScan::GetNumFriendsInFriendsList( void ) {
const char *list = g_friendsList.GetString();
int count = 0;
for ( const char *p = list; ( p = strstr( p, "{[" ) ) != NULL; p += 2 ) {
count++;
}
return count;
}
void idServerScan::GetFriendFromFriendsList( friend_t *newFriend, int num ) {
if ( !newFriend ) {
return;
}
memset( newFriend, 0, sizeof( *newFriend ) );
const char *p = g_friendsList.GetString();
for ( int i = 0; ( p = strstr( p, "{[" ) ) != NULL; i++, p += 2 ) {
if ( i == num ) {
Q4_ParseFriendEntry( p, *newFriend );
return;
}
}
}
bool idServerScan::GetFriendGUIDFromFriendsList( int num, char *guid ) {
friend_t f;
GetFriendFromFriendsList( &f, num );
if ( f.guid[0] == '\0' ) {
if ( guid ) {
guid[0] = '\0';
}
return false;
}
if ( guid ) {
idStr::Copynz( guid, f.guid, 12 );
}
return true;
}
void idServerScan::AddToFriendsList( int clientNum ) {
if ( clientNum < 0 || clientNum >= MAX_ASYNC_CLIENTS ) {
common->Warning( "idServerScan::AddToFriendsList() - Invalid client '%d' specified\n", clientNum );
return;
}
const char *guid = networkSystem->GetClientGUID( clientNum );
if ( !guid || !guid[0] ) {
common->Warning( "idServerScan::AddToFriendsList() - Bad guid for client %d\n", clientNum );
return;
}
if ( strstr( g_friendsList.GetString(), guid ) ) {
common->DPrintf( "idServerScan::AddToFriendsList() - Not adding duplicate friend '%s'\n", guid );
return;
}
const idDict *info = game->GetUserInfo( clientNum );
const char *name = info ? info->GetString( "ui_name", "" ) : "";
const char *clan = info ? info->GetString( "ui_clan", "" ) : "";
idStr newList = g_friendsList.GetString();
newList += va( "{[%s][%s][%s]}", guid, name, clan );
cvarSystem->SetCVarString( "g_friendsList", newList.c_str() );
game->SetFriend( clientNum, true );
}
void idServerScan::RemoveFromFriendsList( int clientNum ) {
const char *guid = networkSystem->GetClientGUID( clientNum );
if ( !guid || !guid[0] ) {
return;
}
idStr list = g_friendsList.GetString();
char token[64];
idStr::snPrintf( token, sizeof( token ), "{[%s]", guid );
const char *start = strstr( list.c_str(), token );
if ( !start ) {
return;
}
const char *end = strstr( start, "]}" );
if ( !end ) {
return;
}
end += 2;
idStr newList;
newList.Append( list.c_str(), start - list.c_str() );
newList += end;
cvarSystem->SetCVarString( "g_friendsList", newList.c_str() );
game->SetFriend( clientNum, false );
}
bool idServerScan::IsAFriend( int clientNum ) {
const char *guid = networkSystem->GetClientGUID( clientNum );
return ( guid && guid[0] && strstr( g_friendsList.GetString(), guid ) != NULL );
}
void idServerScan::AddFriend( const char *guid, const char *name, const char *clan, const char *server ) {
networkServer_t netServer;
memset( &netServer, 0, sizeof( netServer ) );
if ( !server || !Sys_StringToNetAdr( server, &netServer.adr, false ) ) {
return;
}
if ( !netServer.adr.port ) {
netServer.adr.port = PORT_SERVER;
}
netServer.online = true;
netServer.serverInfo.Set( "si_name", va( "%s%s%s", clan && clan[0] ? clan : "", clan && clan[0] ? " " : "", name ? name : "Friend" ) );
netServer.clients = 1;
idStr::Copynz( netServer.nickname[0], name ? name : "Friend", MAX_NICKLEN );
InfoResponse( netServer );
}
void idServerScan::AddTestServers( int count ) {
for ( int i = 0; i < count; i++ ) {
networkServer_t server;
memset( &server, 0, sizeof( server ) );
Sys_StringToNetAdr( va( "127.0.0.1:%d", PORT_SERVER + i ), &server.adr, false );
server.ping = 1 + i;
server.serverInfo.Set( "si_name", va( "Quake4 Test Server %d", i ) );
server.serverInfo.Set( "si_map", "mp/q4dm1" );
server.serverInfo.Set( "si_mapName", "Q4 Test Map" );
server.serverInfo.Set( "si_gameType", "DM" );
server.clients = 0;
InfoResponse( server );
}
}
void idServerScan::AddTestFriends( int count ) {
for ( int i = 0; i < count; i++ ) {
AddFriend( va( "TESTGUID%03d", i ), va( "Friend%d", i ), "", va( "127.0.0.1:%d", PORT_SERVER + i ) );
}
}
#endif