mp_serverlist.c

Go to the documentation of this file.
00001 
00006 /*
00007 Copyright (C) 2002-2010 UFO: Alien Invasion.
00008 
00009 This program is free software; you can redistribute it and/or
00010 modify it under the terms of the GNU General Public License
00011 as published by the Free Software Foundation; either version 2
00012 of the License, or (at your option) any later version.
00013 
00014 This program is distributed in the hope that it will be useful,
00015 but WITHOUT ANY WARRANTY; without even the implied warranty of
00016 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
00017 
00018 See the GNU General Public License for more details.
00019 
00020 You should have received a copy of the GNU General Public License
00021 along with this program; if not, write to the Free Software
00022 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
00023 
00024 */
00025 
00026 #include "../cl_shared.h"
00027 #include "../cl_game.h"
00028 #include "../ui/ui_main.h"
00029 #include "../ui/ui_popup.h"
00030 #include "../../shared/infostring.h"
00031 #include "../../shared/parse.h"
00032 #include "mp_serverlist.h"
00033 #include "mp_callbacks.h"
00034 #include <SDL_thread.h>
00035 
00036 #define MAX_SERVERLIST 128
00037 
00038 static serverList_t serverList[MAX_SERVERLIST];
00039 serverList_t *selectedServer;
00040 static char serverText[1024];
00041 static int serverListLength;
00042 static int serverListPos;
00043 static cvar_t *cl_serverlist;
00044 
00054 static qboolean CL_ProcessPingReply (serverList_t *server, const char *msg)
00055 {
00056     if (!msg)
00057         return qfalse;
00058 
00059     if (PROTOCOL_VERSION != atoi(Info_ValueForKey(msg, "sv_protocol"))) {
00060         Com_DPrintf(DEBUG_CLIENT, "CL_ProcessPingReply: Protocol mismatch\n");
00061         return qfalse;
00062     }
00063     if (strcmp(UFO_VERSION, Info_ValueForKey(msg, "sv_version"))) {
00064         Com_DPrintf(DEBUG_CLIENT, "CL_ProcessPingReply: Version mismatch\n");
00065     }
00066 
00067     if (server->pinged)
00068         return qfalse;
00069 
00070     server->pinged = qtrue;
00071     Q_strncpyz(server->sv_hostname, Info_ValueForKey(msg, "sv_hostname"),
00072         sizeof(server->sv_hostname));
00073     Q_strncpyz(server->version, Info_ValueForKey(msg, "sv_version"),
00074         sizeof(server->version));
00075     Q_strncpyz(server->mapname, Info_ValueForKey(msg, "sv_mapname"),
00076         sizeof(server->mapname));
00077     Q_strncpyz(server->gametype, Info_ValueForKey(msg, "sv_gametype"),
00078         sizeof(server->gametype));
00079     server->clients = atoi(Info_ValueForKey(msg, "clients"));
00080     server->sv_dedicated = atoi(Info_ValueForKey(msg, "sv_dedicated"));
00081     server->sv_maxclients = atoi(Info_ValueForKey(msg, "sv_maxclients"));
00082     return qtrue;
00083 }
00084 
00085 typedef enum {
00086     SERVERLIST_SHOWALL,
00087     SERVERLIST_HIDEFULL,
00088     SERVERLIST_HIDEEMPTY
00089 } serverListStatus_t;
00090 
00096 static inline qboolean CL_ShowServer (const serverList_t *server)
00097 {
00098     if (cl_serverlist->integer == SERVERLIST_SHOWALL)
00099         return qtrue;
00100     if (cl_serverlist->integer == SERVERLIST_HIDEFULL && server->clients < server->sv_maxclients)
00101         return qtrue;
00102     if (cl_serverlist->integer == SERVERLIST_HIDEEMPTY && server->clients > 0)
00103         return qtrue;
00104 
00105     return qfalse;
00106 }
00107 
00111 static void CL_PingServerCallback (struct net_stream *s)
00112 {
00113     struct dbuffer *buf = NET_ReadMsg(s);
00114     serverList_t *server = NET_StreamGetData(s);
00115     const int cmd = NET_ReadByte(buf);
00116     char str[512];
00117 
00118     NET_ReadStringLine(buf, str, sizeof(str));
00119 
00120     if (cmd == clc_oob && strncmp(str, "info", 4) == 0) {
00121         NET_ReadString(buf, str, sizeof(str));
00122         if (CL_ProcessPingReply(server, str)) {
00123             if (CL_ShowServer(server)) {
00124                 char string[MAX_INFO_STRING];
00125                 Com_sprintf(string, sizeof(string), "%s\t\t\t%s\t\t\t%s\t\t%i/%i\n",
00126                     server->sv_hostname,
00127                     server->mapname,
00128                     server->gametype,
00129                     server->clients,
00130                     server->sv_maxclients);
00131                 server->serverListPos = serverListPos;
00132                 serverListPos++;
00133                 Q_strcat(serverText, string, sizeof(serverText));
00134             }
00135         }
00136     }
00137     NET_StreamFree(s);
00138 }
00139 
00145 static void CL_PingServer (serverList_t *server)
00146 {
00147     struct net_stream *s = NET_Connect(server->node, server->service);
00148 
00149     if (s) {
00150         Com_DPrintf(DEBUG_CLIENT, "pinging [%s]:%s...\n", server->node, server->service);
00151         NET_OOB_Printf(s, "info %i", PROTOCOL_VERSION);
00152         NET_StreamSetData(s, server);
00153         NET_StreamSetCallback(s, &CL_PingServerCallback);
00154     } else {
00155         Com_Printf("pinging failed [%s]:%s...\n", server->node, server->service);
00156     }
00157 }
00158 
00162 void CL_PrintServerList_f (void)
00163 {
00164     int i;
00165 
00166     Com_Printf("%i servers on the list\n", serverListLength);
00167 
00168     for (i = 0; i < serverListLength; i++) {
00169         Com_Printf("%02i: [%s]:%s (pinged: %i)\n", i, serverList[i].node, serverList[i].service, serverList[i].pinged);
00170     }
00171 }
00172 
00178 static void CL_AddServerToList (const char *node, const char *service)
00179 {
00180     int i;
00181 
00182     if (serverListLength >= MAX_SERVERLIST)
00183         return;
00184 
00185     for (i = 0; i < serverListLength; i++)
00186         if (strcmp(serverList[i].node, node) == 0 && strcmp(serverList[i].service, service) == 0)
00187             return;
00188 
00189     memset(&(serverList[serverListLength]), 0, sizeof(serverList_t));
00190     serverList[serverListLength].node = Mem_PoolStrDup(node, cl_genericPool, 0);
00191     serverList[serverListLength].service = Mem_PoolStrDup(service, cl_genericPool, 0);
00192     CL_PingServer(&serverList[serverListLength]);
00193     serverListLength++;
00194 }
00195 
00204 void CL_ParseTeamInfoMessage (struct dbuffer *msg)
00205 {
00206     char str[4096];
00207     int cnt = 0;
00208     linkedList_t *userList = NULL;
00209     linkedList_t *userTeam = NULL;
00210 
00211     if (NET_ReadString(msg, str, sizeof(str)) == 0) {
00212         UI_ResetData(TEXT_MULTIPLAYER_USERLIST);
00213         UI_ResetData(TEXT_MULTIPLAYER_USERTEAM);
00214         UI_ExecuteConfunc("multiplayer_playerNumber 0");
00215         Com_DPrintf(DEBUG_CLIENT, "CL_ParseTeamInfoMessage: No teaminfo string\n");
00216         return;
00217     }
00218 
00219     memset(&teamData, 0, sizeof(teamData));
00220 
00221     teamData.maxteams = Info_IntegerForKey(str, "sv_maxteams");
00222     teamData.maxPlayersPerTeam = Info_IntegerForKey(str, "sv_maxplayersperteam");
00223 
00224     /* for each lines */
00225     while (NET_ReadString(msg, str, sizeof(str)) > 0) {
00226         const int team = Info_IntegerForKey(str, "cl_team");
00227         const int isReady = Info_IntegerForKey(str, "cl_ready");
00228         const char *user = Info_ValueForKey(str, "cl_name");
00229 
00230         if (team > 0 && team < MAX_TEAMS)
00231             teamData.teamCount[team]++;
00232 
00233         /* store data */
00234         LIST_AddString(&userList, user);
00235         if (team != TEAM_NO_ACTIVE)
00236             LIST_AddString(&userTeam, va(_("Team %d"), team));
00237         else
00238             LIST_AddString(&userTeam, _("No team"));
00239 
00240         UI_ExecuteConfunc("multiplayer_playerIsReady %i %i", cnt, isReady);
00241 
00242         cnt++;
00243     }
00244 
00245     UI_RegisterLinkedListText(TEXT_MULTIPLAYER_USERLIST, userList);
00246     UI_RegisterLinkedListText(TEXT_MULTIPLAYER_USERTEAM, userTeam);
00247     UI_ExecuteConfunc("multiplayer_playerNumber %i", cnt);
00248 
00249     /* no players are connected ATM */
00250     if (!cnt) {
00253         /* Q_strcat(teamData.teamInfoText, _("No player connected\n"), sizeof(teamData.teamInfoText)); */
00254     }
00255 
00256     Cvar_SetValue("mn_maxteams", teamData.maxteams);
00257     Cvar_SetValue("mn_maxplayersperteam", teamData.maxPlayersPerTeam);
00258 }
00259 
00260 static char serverInfoText[1024];
00261 static char userInfoText[256];
00271 void CL_ParseServerInfoMessage (struct dbuffer *msg, const char *hostname)
00272 {
00273     const char *value;
00274     const char *token;
00275     char str[MAX_INFO_STRING];
00276     char buf[256];
00277 
00278     NET_ReadString(msg, str, sizeof(str));
00279 
00280     /* check for server status response message */
00281     value = Info_ValueForKey(str, "sv_dedicated");
00282     if (*value) {
00283         /* server info cvars and users are seperated via newline */
00284         const char *users = strstr(str, "\n");
00285         if (!users) {
00286             Com_Printf(COLORED_GREEN "%s\n", str);
00287             return;
00288         }
00289         Com_DPrintf(DEBUG_CLIENT, "%s\n", str); /* status string */
00290 
00291         Cvar_Set("mn_mappic", "maps/shots/default.jpg");
00292         if (*Info_ValueForKey(str, "sv_needpass") == '1')
00293             Cvar_Set("mn_server_need_password", "1");
00294         else
00295             Cvar_Set("mn_server_need_password", "0");
00296 
00297         Com_sprintf(serverInfoText, sizeof(serverInfoText), _("IP\t%s\n\n"), hostname);
00298         Cvar_Set("mn_server_ip", hostname);
00299         value = Info_ValueForKey(str, "sv_mapname");
00300         assert(value);
00301         Cvar_Set("mn_svmapname", value);
00302         Q_strncpyz(buf, value, sizeof(buf));
00303         token = buf;
00304         /* skip random map char */
00305         if (token[0] == '+')
00306             token++;
00307 
00308         Com_sprintf(serverInfoText + strlen(serverInfoText), sizeof(serverInfoText) - strlen(serverInfoText), _("Map:\t%s\n"), value);
00309         if (FS_CheckFile("pics/maps/shots/%s.jpg", token) != -1) {
00310             /* store it relative to pics/ dir - not relative to game dir */
00311             Cvar_Set("mn_mappic", va("maps/shots/%s", token));
00312         }
00313         Com_sprintf(serverInfoText + strlen(serverInfoText), sizeof(serverInfoText) - strlen(serverInfoText), _("Servername:\t%s\n"), Info_ValueForKey(str, "sv_hostname"));
00314         Com_sprintf(serverInfoText + strlen(serverInfoText), sizeof(serverInfoText) - strlen(serverInfoText), _("Moralestates:\t%s\n"), _(Info_BoolForKey(str, "sv_enablemorale")));
00315         Com_sprintf(serverInfoText + strlen(serverInfoText), sizeof(serverInfoText) - strlen(serverInfoText), _("Gametype:\t%s\n"), Info_ValueForKey(str, "sv_gametype"));
00316         Com_sprintf(serverInfoText + strlen(serverInfoText), sizeof(serverInfoText) - strlen(serverInfoText), _("Gameversion:\t%s\n"), Info_ValueForKey(str, "ver"));
00317         Com_sprintf(serverInfoText + strlen(serverInfoText), sizeof(serverInfoText) - strlen(serverInfoText), _("Dedicated server:\t%s\n"), _(Info_BoolForKey(str, "sv_dedicated")));
00318         Com_sprintf(serverInfoText + strlen(serverInfoText), sizeof(serverInfoText) - strlen(serverInfoText), _("Operating system:\t%s\n"), Info_ValueForKey(str, "sys_os"));
00319         Com_sprintf(serverInfoText + strlen(serverInfoText), sizeof(serverInfoText) - strlen(serverInfoText), _("Network protocol:\t%s\n"), Info_ValueForKey(str, "sv_protocol"));
00320         Com_sprintf(serverInfoText + strlen(serverInfoText), sizeof(serverInfoText) - strlen(serverInfoText), _("Roundtime:\t%s\n"), Info_ValueForKey(str, "sv_roundtimelimit"));
00321         Com_sprintf(serverInfoText + strlen(serverInfoText), sizeof(serverInfoText) - strlen(serverInfoText), _("Teamplay:\t%s\n"), _(Info_BoolForKey(str, "sv_teamplay")));
00322         Com_sprintf(serverInfoText + strlen(serverInfoText), sizeof(serverInfoText) - strlen(serverInfoText), _("Max. players per team:\t%s\n"), Info_ValueForKey(str, "sv_maxplayersperteam"));
00323         Com_sprintf(serverInfoText + strlen(serverInfoText), sizeof(serverInfoText) - strlen(serverInfoText), _("Max. teams allowed in this map:\t%s\n"), Info_ValueForKey(str, "sv_maxteams"));
00324         Com_sprintf(serverInfoText + strlen(serverInfoText), sizeof(serverInfoText) - strlen(serverInfoText), _("Max. clients:\t%s\n"), Info_ValueForKey(str, "sv_maxclients"));
00325         Com_sprintf(serverInfoText + strlen(serverInfoText), sizeof(serverInfoText) - strlen(serverInfoText), _("Max. soldiers per player:\t%s\n"), Info_ValueForKey(str, "sv_maxsoldiersperplayer"));
00326         Com_sprintf(serverInfoText + strlen(serverInfoText), sizeof(serverInfoText) - strlen(serverInfoText), _("Max. soldiers per team:\t%s\n"), Info_ValueForKey(str, "sv_maxsoldiersperteam"));
00327         Com_sprintf(serverInfoText + strlen(serverInfoText), sizeof(serverInfoText) - strlen(serverInfoText), _("Password protected:\t%s\n"), _(Info_BoolForKey(str, "sv_needpass")));
00328         UI_RegisterText(TEXT_STANDARD, serverInfoText);
00329         userInfoText[0] = '\0';
00330         do {
00331             int team;
00332             token = Com_Parse(&users);
00333             if (!users)
00334                 break;
00335             team = atoi(token);
00336             token = Com_Parse(&users);
00337             if (!users)
00338                 break;
00339             Com_sprintf(userInfoText + strlen(userInfoText), sizeof(userInfoText) - strlen(userInfoText), "%s\t%i\n", token, team);
00340         } while (1);
00341         UI_RegisterText(TEXT_LIST, userInfoText);
00342         UI_PushWindow("serverinfo", NULL);
00343     } else
00344         Com_Printf(COLORED_GREEN "%s", str);
00345 }
00346 
00351 static void CL_ServerInfoCallback (struct net_stream *s)
00352 {
00353     struct dbuffer *buf = NET_ReadMsg(s);
00354     if (buf) {
00355         const int cmd = NET_ReadByte(buf);
00356         char str[8];
00357         NET_ReadStringLine(buf, str, sizeof(str));
00358 
00359         if (cmd == clc_oob && !strcmp(str, "print")) {
00360             char hostname[256];
00361             NET_StreamPeerToName(s, hostname, sizeof(hostname), qtrue);
00362             CL_ParseServerInfoMessage(buf, hostname);
00363         }
00364     }
00365     NET_StreamFree(s);
00366 }
00367 
00368 static SDL_Thread *masterServerQueryThread;
00369 
00370 static int CL_QueryMasterServerThread (void *data)
00371 {
00372     char *responseBuf;
00373     const char *serverList;
00374     const char *token;
00375     char node[MAX_VAR], service[MAX_VAR];
00376     int i, num;
00377 
00378     responseBuf = HTTP_GetURL(va("%s/ufo/masterserver.php?query", masterserver_url->string));
00379     if (!responseBuf) {
00380         Com_Printf("Could not query masterserver\n");
00381         return 1;
00382     }
00383 
00384     serverList = responseBuf;
00385 
00386     Com_DPrintf(DEBUG_CLIENT, "masterserver response: %s\n", serverList);
00387     token = Com_Parse(&serverList);
00388 
00389     num = atoi(token);
00390     if (num >= MAX_SERVERLIST) {
00391         Com_DPrintf(DEBUG_CLIENT, "Too many servers: %i\n", num);
00392         num = MAX_SERVERLIST;
00393     }
00394     for (i = 0; i < num; i++) {
00395         /* host */
00396         token = Com_Parse(&serverList);
00397         if (!*token || !serverList) {
00398             Com_Printf("Could not finish the masterserver response parsing\n");
00399             break;
00400         }
00401         Q_strncpyz(node, token, sizeof(node));
00402         /* port */
00403         token = Com_Parse(&serverList);
00404         if (!*token || !serverList) {
00405             Com_Printf("Could not finish the masterserver response parsing\n");
00406             break;
00407         }
00408         Q_strncpyz(service, token, sizeof(service));
00409         CL_AddServerToList(node, service);
00410     }
00411 
00412     Mem_Free(responseBuf);
00413 
00414     return 0;
00415 }
00416 
00420 static void CL_QueryMasterServer (void)
00421 {
00422     if (masterServerQueryThread != NULL)
00423         SDL_WaitThread(masterServerQueryThread, NULL);
00424 
00425     masterServerQueryThread = SDL_CreateThread(CL_QueryMasterServerThread, NULL);
00426 }
00427 
00431 static void CL_ServerListDiscoveryCallback (struct datagram_socket *s, const char *buf, int len, struct sockaddr *from)
00432 {
00433     const char match[] = "discovered";
00434     if (len == sizeof(match) && memcmp(buf, match, len) == 0) {
00435         char node[MAX_VAR];
00436         char service[MAX_VAR];
00437         NET_SockaddrToStrings(s, from, node, sizeof(node), service, sizeof(service));
00438         CL_AddServerToList(node, service);
00439     }
00440 }
00441 
00447 static void CL_BookmarkAdd_f (void)
00448 {
00449     int i;
00450     const char *newBookmark;
00451 
00452     if (Cmd_Argc() < 2) {
00453         newBookmark = Cvar_GetString("mn_server_ip");
00454         if (!newBookmark) {
00455             Com_Printf("Usage: %s <ip>\n", Cmd_Argv(0));
00456             return;
00457         }
00458     } else
00459         newBookmark = Cmd_Argv(1);
00460 
00461     for (i = 0; i < MAX_BOOKMARKS; i++) {
00462         const char *bookmark = Cvar_GetString(va("adr%i", i));
00463         if (bookmark[0] == '\0') {
00464             Cvar_Set(va("adr%i", i), newBookmark);
00465             return;
00466         }
00467     }
00468     /* bookmarks are full - overwrite the first entry */
00469     UI_Popup(_("Notice"), _("All bookmark slots are used - please removed unused entries and repeat this step"));
00470 }
00471 
00475 static void CL_ServerInfo_f (void)
00476 {
00477     struct net_stream *s;
00478     const char *host;
00479     const char *port;
00480 
00481     switch (Cmd_Argc()) {
00482     case 2:
00483         host = Cmd_Argv(1);
00484         port = DOUBLEQUOTE(PORT_SERVER);
00485         break;
00486     case 3:
00487         host = Cmd_Argv(1);
00488         port = Cmd_Argv(2);
00489         break;
00490     default:
00491         if (selectedServer) {
00492             host = selectedServer->node;
00493             port = selectedServer->service;
00494         } else {
00495             host = Cvar_GetString("mn_server_ip");
00496             port = DOUBLEQUOTE(PORT_SERVER);
00497         }
00498         break;
00499     }
00500     s = NET_Connect(host, port);
00501     if (s) {
00502         NET_OOB_Printf(s, "status %i", PROTOCOL_VERSION);
00503         NET_StreamSetCallback(s, &CL_ServerInfoCallback);
00504     } else
00505         Com_Printf("Could not connect to %s %s\n", host, port);
00506 }
00507 
00512 static void CL_ServerListClick_f (void)
00513 {
00514     int num;
00515 
00516     if (Cmd_Argc() < 2) {
00517         Com_Printf("Usage: %s <num>\n", Cmd_Argv(0));
00518         return;
00519     }
00520     num = atoi(Cmd_Argv(1));
00521 
00522     UI_RegisterText(TEXT_STANDARD, serverInfoText);
00523     if (num >= 0 && num < serverListLength) {
00524         int i;
00525         for (i = 0; i < serverListLength; i++)
00526             if (serverList[i].pinged && serverList[i].serverListPos == num) {
00527                 /* found the server - grab the infos for this server */
00528                 selectedServer = &serverList[i];
00529                 Cbuf_AddText(va("server_info %s %s;", serverList[i].node, serverList[i].service));
00530                 return;
00531             }
00532     }
00533 }
00534 
00536 static qboolean serversAlreadyQueried = qfalse;
00537 static int lastServerQuery = 0;
00539 #define SERVERQUERYTIMEOUT 40000
00540 
00546 void CL_PingServers_f (void)
00547 {
00548     selectedServer = NULL;
00549 
00550     /* refresh the list */
00551     if (Cmd_Argc() == 2) {
00552         int i;
00553         /* reset current list */
00554         serverText[0] = 0;
00555         serversAlreadyQueried = qfalse;
00556         for (i = 0; i < serverListLength; i++) {
00557             Mem_Free(serverList[i].node);
00558             Mem_Free(serverList[i].service);
00559         }
00560         serverListPos = 0;
00561         serverListLength = 0;
00562         memset(serverList, 0, sizeof(serverList));
00563     } else {
00564         UI_RegisterText(TEXT_LIST, serverText);
00565         return;
00566     }
00567 
00568     if (!cls.netDatagramSocket)
00569         cls.netDatagramSocket = NET_DatagramSocketNew(NULL, DOUBLEQUOTE(PORT_CLIENT), &CL_ServerListDiscoveryCallback);
00570 
00571     /* broadcast search for all the servers int the local network */
00572     if (cls.netDatagramSocket) {
00573         const char buf[] = "discover";
00574         NET_DatagramBroadcast(cls.netDatagramSocket, buf, sizeof(buf), PORT_SERVER);
00575     }
00576     UI_RegisterText(TEXT_LIST, serverText);
00577 
00578     /* don't query the masterservers with every call */
00579     if (serversAlreadyQueried) {
00580         if (lastServerQuery + SERVERQUERYTIMEOUT > CL_Milliseconds())
00581             return;
00582     } else
00583         serversAlreadyQueried = qtrue;
00584 
00585     lastServerQuery = CL_Milliseconds();
00586 
00587     /* query master server? */
00588     if (Cmd_Argc() == 2 && strcmp(Cmd_Argv(1), "local")) {
00589         Com_DPrintf(DEBUG_CLIENT, "Query masterserver\n");
00590         CL_QueryMasterServer();
00591     }
00592 }
00593 
00594 void MP_ServerListInit (void)
00595 {
00596     int i;
00597 
00598     /* register our variables */
00599     for (i = 0; i < MAX_BOOKMARKS; i++)
00600         Cvar_Get(va("adr%i", i), "", CVAR_ARCHIVE, "Bookmark for network ip");
00601     cl_serverlist = Cvar_Get("cl_serverlist", "0", CVAR_ARCHIVE, "0=show all, 1=hide full - servers on the serverlist");
00602 
00603     Cmd_AddCommand("bookmark_add", CL_BookmarkAdd_f, "Add a new bookmark - see adrX cvars");
00604     Cmd_AddCommand("server_info", CL_ServerInfo_f, NULL);
00605     Cmd_AddCommand("serverlist", CL_PrintServerList_f, NULL);
00606     /* text id is servers in menu_multiplayer.ufo */
00607     Cmd_AddCommand("servers_click", CL_ServerListClick_f, NULL);
00608 }
00609 
00610 void MP_ServerListShutdown (void)
00611 {
00612     Cmd_RemoveCommand("bookmark_add");
00613     Cmd_RemoveCommand("server_info");
00614     Cmd_RemoveCommand("serverlist");
00615     Cmd_RemoveCommand("servers_click");
00616 
00617     if (masterServerQueryThread)
00618         SDL_KillThread(masterServerQueryThread);
00619     masterServerQueryThread = NULL;
00620 }

Generated by  doxygen 1.6.2