1 /**
2 * @file
3 * @brief Serverlist management for multiplayer
4 */
5
6 /*
7 Copyright (C) 2002-2013 UFO: Alien Invasion.
8
9 This program is free software; you can redistribute it and/or
10 modify it under the terms of the GNU General Public License
11 as published by the Free Software Foundation; either version 2
12 of the License, or (at your option) any later version.
13
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
17
18 See the GNU General Public License for more details.
19
20 You should have received a copy of the GNU General Public License
21 along with this program; if not, write to the Free Software
22 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23
24 */
25
26 #include "../../cl_shared.h"
27 #include "../cl_game.h"
28 #include "../../../shared/parse.h"
29 #include "../../ui/ui_data.h"
30 #include "../../../shared/infostring.h"
31 #include "mp_serverlist.h"
32 #include "mp_callbacks.h"
33
34 #define MAX_SERVERLIST 128
35
36 static const cgame_import_t* cgi;
37
38 static serverList_t serverList[MAX_SERVERLIST];
39 serverList_t* selectedServer;
40 static char serverText[1024];
41 static int serverListLength;
42 static int serverListPos;
43 static cvar_t* cl_serverlist;
44 static struct datagram_socket* netDatagramSocket;
45
46 /**
47 * @brief Parsed the server ping response.
48 * @param[out] server The server data to store the parsed information in
49 * @param[in] msg The ping response with the server information to parse
50 * @sa CL_PingServerCallback
51 * @sa SVC_Info
52 * @return @c true if the server is compatible, @c msg is not @c null and the server
53 * wasn't pinged already, @c false otherwise
54 */
GAME_MP_ProcessPingReply(serverList_t * server,const char * msg)55 static bool GAME_MP_ProcessPingReply (serverList_t* server, const char* msg)
56 {
57 if (!msg)
58 return false;
59
60 if (PROTOCOL_VERSION != Info_IntegerForKey(msg, "sv_protocol")) {
61 Com_DPrintf(DEBUG_CLIENT, "CL_ProcessPingReply: Protocol mismatch\n");
62 return false;
63 }
64 if (!Q_streq(UFO_VERSION, Info_ValueForKey(msg, "sv_version"))) {
65 Com_DPrintf(DEBUG_CLIENT, "CL_ProcessPingReply: Version mismatch\n");
66 }
67
68 if (server->pinged)
69 return false;
70
71 server->pinged = true;
72 Q_strncpyz(server->sv_hostname, Info_ValueForKey(msg, "sv_hostname"),
73 sizeof(server->sv_hostname));
74 Q_strncpyz(server->version, Info_ValueForKey(msg, "sv_version"),
75 sizeof(server->version));
76 Q_strncpyz(server->mapname, Info_ValueForKey(msg, "sv_mapname"),
77 sizeof(server->mapname));
78 Q_strncpyz(server->gametype, Info_ValueForKey(msg, "sv_gametype"),
79 sizeof(server->gametype));
80 server->clients = Info_IntegerForKey(msg, "clients");
81 server->sv_dedicated = Info_IntegerForKey(msg, "sv_dedicated");
82 server->sv_maxclients = Info_IntegerForKey(msg, "sv_maxclients");
83 return true;
84 }
85
86 typedef enum {
87 SERVERLIST_SHOWALL,
88 SERVERLIST_HIDEFULL,
89 SERVERLIST_HIDEEMPTY
90 } serverListStatus_t;
91
92 /**
93 * @brief Perform the server filtering
94 * @param[in] server The server data
95 * @return @c true if the server should be visible for the current filter settings, @c false otherwise
96 */
GAME_MP_ShowServer(const serverList_t * server)97 static inline bool GAME_MP_ShowServer (const serverList_t* server)
98 {
99 if (cl_serverlist->integer == SERVERLIST_SHOWALL)
100 return true;
101 if (cl_serverlist->integer == SERVERLIST_HIDEFULL && server->clients < server->sv_maxclients)
102 return true;
103 if (cl_serverlist->integer == SERVERLIST_HIDEEMPTY && server->clients > 0)
104 return true;
105
106 return false;
107 }
108
GAME_MP_PingServerCallback(struct net_stream * s)109 static void GAME_MP_PingServerCallback (struct net_stream *s)
110 {
111 AutoPtr<dbuffer> buf(cgi->NET_ReadMsg(s));
112 if (!buf) {
113 cgi->NET_StreamFree(s);
114 return;
115 }
116 serverList_t* server = (serverList_t*)cgi->NET_StreamGetData(s);
117 const int cmd = cgi->NET_ReadByte(buf);
118 if (cmd != svc_oob) {
119 cgi->NET_StreamFree(s);
120 return;
121 }
122
123 char str[512];
124 cgi->NET_ReadStringLine(buf, str, sizeof(str));
125
126 if (strncmp(str, "info", 4) == 0) {
127 cgi->NET_ReadString(buf, str, sizeof(str));
128 if (GAME_MP_ProcessPingReply(server, str)) {
129 if (GAME_MP_ShowServer(server)) {
130 server->serverListPos = serverListPos;
131 serverListPos++;
132 Q_strcat(serverText, sizeof(serverText), "%s\t\t\t%s\t\t\t%s\t\t%i/%i\n",
133 server->sv_hostname,
134 server->mapname,
135 server->gametype,
136 server->clients,
137 server->sv_maxclients);
138 }
139 }
140 } else if (strncmp(str, "print", 5) == 0) {
141 char paramBuf[2048];
142 cgi->NET_ReadString(buf, paramBuf, sizeof(paramBuf));
143 cgi->Com_DPrintf(DEBUG_CLIENT, "%s", paramBuf);
144 }
145 cgi->NET_StreamFree(s);
146 }
147
148 /**
149 * @brief Pings all servers in serverList
150 * @sa CL_AddServerToList
151 * @sa GAME_MP_ParseServerInfoMessage
152 */
GAME_MP_PingServer(serverList_t * server)153 static void GAME_MP_PingServer (serverList_t* server)
154 {
155 struct net_stream *s = cgi->NET_Connect(server->node, server->service, nullptr);
156 if (s == nullptr) {
157 cgi->Com_Printf("pinging failed [%s]:%s...\n", server->node, server->service);
158 return;
159 }
160 cgi->Com_DPrintf(DEBUG_CLIENT, "pinging [%s]:%s...\n", server->node, server->service);
161 cgi->NET_OOB_Printf(s, SV_CMD_INFO " %i", PROTOCOL_VERSION);
162 cgi->NET_StreamSetData(s, server);
163 cgi->NET_StreamSetCallback(s, &GAME_MP_PingServerCallback);
164 }
165
166 /**
167 * @brief Prints all the servers on the list to game console
168 */
GAME_MP_PrintServerList_f(void)169 static void GAME_MP_PrintServerList_f (void)
170 {
171 cgi->Com_Printf("%i servers on the list\n", serverListLength);
172
173 for (int i = 0; i < serverListLength; i++) {
174 const serverList_t* list = &serverList[i];
175 cgi->Com_Printf("%02i: [%s]:%s (pinged: %i)\n", i, list->node, list->service, list->pinged);
176 }
177 }
178
179 /**
180 * @brief Adds a server to the serverlist cache
181 * @return false if it is no valid address or server already exists
182 * @sa CL_ParseStatusMessage
183 */
GAME_MP_AddServerToList(const char * node,const char * service)184 static void GAME_MP_AddServerToList (const char* node, const char* service)
185 {
186 if (serverListLength >= MAX_SERVERLIST)
187 return;
188
189 for (int i = 0; i < serverListLength; i++)
190 if (Q_streq(serverList[i].node, node) && Q_streq(serverList[i].service, service))
191 return;
192
193 OBJZERO(serverList[serverListLength]);
194 serverList[serverListLength].node = cgi->GAME_StrDup(node);
195 serverList[serverListLength].service = cgi->GAME_StrDup(service);
196 GAME_MP_PingServer(&serverList[serverListLength]);
197 serverListLength++;
198 }
199
200 /**
201 * @brief Team selection text
202 *
203 * This function fills the multiplayer_selectteam menu with content
204 * @sa NET_OOB_Printf
205 * @sa SVC_TeamInfo
206 * @sa GAME_MP_SelectTeam_Init_f
207 */
GAME_MP_ParseTeamInfoMessage(dbuffer * msg)208 void GAME_MP_ParseTeamInfoMessage (dbuffer* msg)
209 {
210 char str[4096];
211 if (cgi->NET_ReadString(msg, str, sizeof(str)) == 0) {
212 cgi->UI_ResetData(TEXT_MULTIPLAYER_USERLIST);
213 cgi->UI_ResetData(TEXT_MULTIPLAYER_USERTEAM);
214 cgi->UI_ExecuteConfunc("multiplayer_playerNumber 0");
215 cgi->Com_DPrintf(DEBUG_CLIENT, "GAME_MP_ParseTeamInfoMessage: No teaminfo string\n");
216 return;
217 }
218
219 OBJZERO(teamData);
220
221 teamData.maxteams = Info_IntegerForKey(str, "sv_maxteams");
222 teamData.maxPlayersPerTeam = Info_IntegerForKey(str, "sv_maxplayersperteam");
223
224 int cnt = 0;
225 linkedList_t* userList = nullptr;
226 linkedList_t* userTeam = nullptr;
227
228 /* for each lines */
229 while (cgi->NET_ReadString(msg, str, sizeof(str)) > 0) {
230 const int team = Info_IntegerForKey(str, "cl_team");
231 const int isReady = Info_IntegerForKey(str, "cl_ready");
232 const char* user = Info_ValueForKey(str, "cl_name");
233
234 if (team > 0 && team < MAX_TEAMS)
235 teamData.teamCount[team]++;
236
237 /* store data */
238 cgi->LIST_AddString(&userList, user);
239 if (team != TEAM_NO_ACTIVE)
240 cgi->LIST_AddString(&userTeam, va(_("Team %d"), team));
241 else
242 cgi->LIST_AddString(&userTeam, _("No team"));
243
244 cgi->UI_ExecuteConfunc("multiplayer_playerIsReady %i %i", cnt, isReady);
245
246 cnt++;
247 }
248
249 cgi->UI_RegisterLinkedListText(TEXT_MULTIPLAYER_USERLIST, userList);
250 cgi->UI_RegisterLinkedListText(TEXT_MULTIPLAYER_USERTEAM, userTeam);
251 cgi->UI_ExecuteConfunc("multiplayer_playerNumber %i", cnt);
252
253 /* no players are connected ATM */
254 if (!cnt) {
255 /** @todo warning must not be into the player list.
256 * if we see this we are the first player that would be connected to the server */
257 /* Q_strcat(teamData.teamInfoText, sizeof(teamData.teamInfoText), _("No player connected\n")); */
258 }
259
260 cgi->Cvar_SetValue("mn_maxteams", teamData.maxteams);
261 cgi->Cvar_SetValue("mn_maxplayersperteam", teamData.maxPlayersPerTeam);
262 }
263
264 static char serverInfoText[1024];
265 static char userInfoText[256];
266 /**
267 * @brief Serverbrowser text
268 * @sa CL_PingServer
269 * @sa CL_PingServers_f
270 * @note This function fills the network browser server information with text
271 * @sa NET_OOB_Printf
272 * @sa CL_ServerInfoCallback
273 * @sa SVC_Info
274 */
GAME_MP_ParseServerInfoMessage(dbuffer * msg,const char * hostname)275 static void GAME_MP_ParseServerInfoMessage (dbuffer* msg, const char* hostname)
276 {
277 const char* value;
278 char str[MAX_INFO_STRING];
279
280 cgi->NET_ReadString(msg, str, sizeof(str));
281
282 /* check for server status response message */
283 value = Info_ValueForKey(str, "sv_dedicated");
284 if (Q_strnull(value)) {
285 cgi->Com_Printf(S_COLOR_GREEN "%s", str);
286 return;
287 }
288
289 /* server info cvars and users are seperated via newline */
290 const char* users = strstr(str, "\n");
291 if (users == nullptr) {
292 cgi->Com_Printf(S_COLOR_GREEN "%s\n", str);
293 return;
294 }
295 cgi->Com_DPrintf(DEBUG_CLIENT, "%s\n", str); /* status string */
296
297 cgi->Cvar_Set("mn_mappic", "maps/shots/default");
298 if (*Info_ValueForKey(str, "sv_needpass") == '1')
299 cgi->Cvar_Set("mn_server_need_password", "1");
300 else
301 cgi->Cvar_Set("mn_server_need_password", "0");
302
303 Com_sprintf(serverInfoText, sizeof(serverInfoText), _("IP\t%s\n\n"), hostname);
304 cgi->Cvar_Set("mn_server_ip", "%s", hostname);
305 value = Info_ValueForKey(str, "sv_mapname");
306 assert(value);
307 cgi->Cvar_Set("mn_svmapname", "%s", value);
308 char buf[256];
309 Q_strncpyz(buf, value, sizeof(buf));
310 const char* token = buf;
311 /* skip random map char. */
312 if (token[0] == '+')
313 token++;
314
315 Q_strcat(serverInfoText, sizeof(serverInfoText), _("Map:\t%s\n"), value);
316 if (!cgi->R_ImageExists("pics/maps/shots/%s", token)) {
317 /* store it relative to pics/ dir - not relative to game dir */
318 cgi->Cvar_Set("mn_mappic", "maps/shots/%s", token);
319 }
320 Q_strcat(serverInfoText, sizeof(serverInfoText), _("Servername:\t%s\n"), Info_ValueForKey(str, "sv_hostname"));
321 Q_strcat(serverInfoText, sizeof(serverInfoText), _("Moralestates:\t%s\n"), _(Info_BoolForKey(str, "sv_enablemorale")));
322 Q_strcat(serverInfoText, sizeof(serverInfoText), _("Gametype:\t%s\n"), Info_ValueForKey(str, "sv_gametype"));
323 Q_strcat(serverInfoText, sizeof(serverInfoText), _("Gameversion:\t%s\n"), Info_ValueForKey(str, "ver"));
324 Q_strcat(serverInfoText, sizeof(serverInfoText), _("Dedicated server:\t%s\n"), _(Info_BoolForKey(str, "sv_dedicated")));
325 Q_strcat(serverInfoText, sizeof(serverInfoText), _("Operating system:\t%s\n"), Info_ValueForKey(str, "sys_os"));
326 Q_strcat(serverInfoText, sizeof(serverInfoText), _("Network protocol:\t%s\n"), Info_ValueForKey(str, "sv_protocol"));
327 Q_strcat(serverInfoText, sizeof(serverInfoText), _("Roundtime:\t%s\n"), Info_ValueForKey(str, "sv_roundtimelimit"));
328 Q_strcat(serverInfoText, sizeof(serverInfoText), _("Teamplay:\t%s\n"), _(Info_BoolForKey(str, "sv_teamplay")));
329 Q_strcat(serverInfoText, sizeof(serverInfoText), _("Max. players per team:\t%s\n"), Info_ValueForKey(str, "sv_maxplayersperteam"));
330 Q_strcat(serverInfoText, sizeof(serverInfoText), _("Max. teams allowed in this map:\t%s\n"), Info_ValueForKey(str, "sv_maxteams"));
331 Q_strcat(serverInfoText, sizeof(serverInfoText), _("Max. clients:\t%s\n"), Info_ValueForKey(str, "sv_maxclients"));
332 Q_strcat(serverInfoText, sizeof(serverInfoText), _("Max. soldiers per player:\t%s\n"), Info_ValueForKey(str, "sv_maxsoldiersperplayer"));
333 Q_strcat(serverInfoText, sizeof(serverInfoText), _("Max. soldiers per team:\t%s\n"), Info_ValueForKey(str, "sv_maxsoldiersperteam"));
334 Q_strcat(serverInfoText, sizeof(serverInfoText), _("Password protected:\t%s\n"), _(Info_BoolForKey(str, "sv_needpass")));
335 cgi->UI_RegisterText(TEXT_STANDARD, serverInfoText);
336 userInfoText[0] = '\0';
337 for (;;) {
338 token = Com_Parse(&users);
339 if (users == nullptr)
340 break;
341 const int team = atoi(token);
342 token = Com_Parse(&users);
343 if (users == nullptr)
344 break;
345 Q_strcat(userInfoText, sizeof(userInfoText), "%s\t%i\n", token, team);
346 }
347 cgi->UI_RegisterText(TEXT_LIST, userInfoText);
348 cgi->UI_PushWindow("serverinfo");
349 }
350
351 /**
352 * @sa GAME_MP_ServerInfo_f
353 * @sa GAME_MP_ParseServerInfoMessage
354 */
GAME_MP_ServerInfoCallback(struct net_stream * s)355 static void GAME_MP_ServerInfoCallback (struct net_stream *s)
356 {
357 AutoPtr<dbuffer> buf(cgi->NET_ReadMsg(s));
358 if (!buf) {
359 cgi->NET_StreamFree(s);
360 return;
361 }
362 const int cmd = cgi->NET_ReadByte(buf);
363 if (cmd != svc_oob) {
364 cgi->NET_StreamFree(s);
365 return;
366 }
367 char str[8];
368 cgi->NET_ReadStringLine(buf, str, sizeof(str));
369 if (Q_streq(str, "print")) {
370 char hostname[256];
371 cgi->NET_StreamPeerToName(s, hostname, sizeof(hostname), true);
372 GAME_MP_ParseServerInfoMessage(buf, hostname);
373 }
374 cgi->NET_StreamFree(s);
375 }
376
GAME_MP_QueryMasterServerThread(const char * responseBuf,void * userdata)377 static void GAME_MP_QueryMasterServerThread (const char* responseBuf, void* userdata)
378 {
379 if (!responseBuf) {
380 cgi->Com_Printf("Could not query masterserver\n");
381 return;
382 }
383
384 const char* serverListBuf = responseBuf;
385
386 Com_DPrintf(DEBUG_CLIENT, "masterserver response: %s\n", serverListBuf);
387 const char* token = Com_Parse(&serverListBuf);
388
389 int num = atoi(token);
390 if (num >= MAX_SERVERLIST) {
391 cgi->Com_DPrintf(DEBUG_CLIENT, "Too many servers: %i\n", num);
392 num = MAX_SERVERLIST;
393 }
394 for (int i = 0; i < num; i++) {
395 /* host */
396 token = Com_Parse(&serverListBuf);
397 if (!*token || !serverListBuf) {
398 cgi->Com_Printf("Could not finish the masterserver response parsing\n");
399 break;
400 }
401 char node[MAX_VAR];
402 Q_strncpyz(node, token, sizeof(node));
403 /* port */
404 token = Com_Parse(&serverListBuf);
405 if (token[0] == '\0' || !serverListBuf) {
406 cgi->Com_Printf("Could not finish the masterserver response parsing\n");
407 break;
408 }
409 char service[MAX_VAR];
410 Q_strncpyz(service, token, sizeof(service));
411 GAME_MP_AddServerToList(node, service);
412 }
413 }
414
415 /**
416 * @sa SV_DiscoveryCallback
417 */
GAME_MP_ServerListDiscoveryCallback(struct datagram_socket * s,const char * buf,int len,struct sockaddr * from)418 static void GAME_MP_ServerListDiscoveryCallback (struct datagram_socket* s, const char* buf, int len, struct sockaddr* from)
419 {
420 const char match[] = "discovered";
421 if (len == sizeof(match) && memcmp(buf, match, len) == 0) {
422 char node[MAX_VAR];
423 char service[MAX_VAR];
424 cgi->NET_SockaddrToStrings(s, from, node, sizeof(node), service, sizeof(service));
425 GAME_MP_AddServerToList(node, service);
426 }
427 }
428
429 /**
430 * @brief Add a new bookmark
431 *
432 * bookmarks are saved in cvar adr[0-15]
433 */
GAME_MP_BookmarkAdd_f(void)434 static void GAME_MP_BookmarkAdd_f (void)
435 {
436 const char* newBookmark;
437
438 if (cgi->Cmd_Argc() < 2) {
439 newBookmark = cgi->Cvar_GetString("mn_server_ip");
440 if (!newBookmark) {
441 cgi->Com_Printf("Usage: %s <ip>\n", cgi->Cmd_Argv(0));
442 return;
443 }
444 } else {
445 newBookmark = cgi->Cmd_Argv(1);
446 }
447
448 for (int i = 0; i < MAX_BOOKMARKS; i++) {
449 const char* bookmark = cgi->Cvar_GetString(va("adr%i", i));
450 if (bookmark[0] == '\0') {
451 cgi->Cvar_Set(va("adr%i", i), "%s", newBookmark);
452 return;
453 }
454 }
455 /* bookmarks are full - overwrite the first entry */
456 cgi->UI_Popup(_("Notice"), "%s", _("All bookmark slots are used - please removed unused entries and repeat this step"));
457 }
458
459 /**
460 * @sa CL_ServerInfoCallback
461 */
GAME_MP_ServerInfo_f(void)462 static void GAME_MP_ServerInfo_f (void)
463 {
464 const char* host;
465 const char* port;
466
467 switch (cgi->Cmd_Argc()) {
468 case 2:
469 host = cgi->Cmd_Argv(1);
470 port = DOUBLEQUOTE(PORT_SERVER);
471 break;
472 case 3:
473 host = cgi->Cmd_Argv(1);
474 port = cgi->Cmd_Argv(2);
475 break;
476 default:
477 if (selectedServer) {
478 host = selectedServer->node;
479 port = selectedServer->service;
480 } else {
481 host = cgi->Cvar_GetString("mn_server_ip");
482 port = DOUBLEQUOTE(PORT_SERVER);
483 }
484 break;
485 }
486 struct net_stream *s = cgi->NET_Connect(host, port, nullptr);
487 if (s != nullptr) {
488 cgi->NET_OOB_Printf(s, SV_CMD_STATUS " %i", PROTOCOL_VERSION);
489 cgi->NET_StreamSetCallback(s, &GAME_MP_ServerInfoCallback);
490 } else {
491 cgi->Com_Printf("Could not connect to %s %s\n", host, port);
492 }
493 }
494
495 /**
496 * @brief Callback for bookmark nodes in multiplayer menu (mp_bookmarks)
497 * @sa GAME_MP_ParseServerInfoMessage
498 */
GAME_MP_ServerListClick_f(void)499 static void GAME_MP_ServerListClick_f (void)
500 {
501 if (cgi->Cmd_Argc() < 2) {
502 cgi->Com_Printf("Usage: %s <num>\n", cgi->Cmd_Argv(0));
503 return;
504 }
505 const int num = atoi(cgi->Cmd_Argv(1));
506
507 cgi->UI_RegisterText(TEXT_STANDARD, serverInfoText);
508 if (num < 0 || num >= serverListLength)
509 return;
510
511 for (int i = 0; i < serverListLength; i++) {
512 if (!serverList[i].pinged || serverList[i].serverListPos != num)
513 continue;
514 /* found the server - grab the infos for this server */
515 selectedServer = &serverList[i];
516 cgi->Cbuf_AddText("server_info %s %s\n", serverList[i].node, serverList[i].service);
517 return;
518 }
519 }
520
521 /** this is true if pingservers was already executed */
522 static bool serversAlreadyQueried = false;
523 static int lastServerQuery = 0;
524 /** ms until the server query timed out */
525 #define SERVERQUERYTIMEOUT 40000
526
527 /**
528 * @brief The first function called when entering the multiplayer menu, then CL_Frame takes over
529 * @sa GAME_MP_ParseServerInfoMessage
530 * @note Use a parameter for pingservers to update the current serverlist
531 */
GAME_MP_PingServers_f(void)532 void GAME_MP_PingServers_f (void)
533 {
534 selectedServer = nullptr;
535
536 /* refresh the list */
537 if (cgi->Cmd_Argc() == 2) {
538 int i;
539 /* reset current list */
540 serverText[0] = 0;
541 serversAlreadyQueried = false;
542 for (i = 0; i < serverListLength; i++) {
543 cgi->Free(serverList[i].node);
544 cgi->Free(serverList[i].service);
545 }
546 serverListPos = 0;
547 serverListLength = 0;
548 OBJZERO(serverList);
549 } else {
550 cgi->UI_RegisterText(TEXT_LIST, serverText);
551 return;
552 }
553
554 if (!netDatagramSocket)
555 netDatagramSocket = cgi->NET_DatagramSocketNew(nullptr, DOUBLEQUOTE(PORT_CLIENT), &GAME_MP_ServerListDiscoveryCallback);
556
557 /* broadcast search for all the servers int the local network */
558 if (netDatagramSocket) {
559 const char buf[] = "discover";
560 cgi->NET_DatagramBroadcast(netDatagramSocket, buf, sizeof(buf), PORT_SERVER);
561 }
562 cgi->UI_RegisterText(TEXT_LIST, serverText);
563
564 /* don't query the masterservers with every call */
565 if (serversAlreadyQueried) {
566 if (lastServerQuery + SERVERQUERYTIMEOUT > cgi->CL_Milliseconds())
567 return;
568 } else
569 serversAlreadyQueried = true;
570
571 lastServerQuery = cgi->CL_Milliseconds();
572
573 /* query master server? */
574 if (cgi->Cmd_Argc() == 2 && !Q_streq(cgi->Cmd_Argv(1), "local")) {
575 cgi->Com_DPrintf(DEBUG_CLIENT, "Query masterserver\n");
576 cgi->CL_QueryMasterServer("query", GAME_MP_QueryMasterServerThread);
577 }
578 }
579
GAME_MP_ServerListInit(const cgame_import_t * import)580 void GAME_MP_ServerListInit (const cgame_import_t* import)
581 {
582 cgi = import;
583 /* register our variables */
584 for (int i = 0; i < MAX_BOOKMARKS; i++)
585 cgi->Cvar_Get(va("adr%i", i), "", CVAR_ARCHIVE, "Bookmark for network ip");
586 cl_serverlist = cgi->Cvar_Get("cl_serverlist", "0", CVAR_ARCHIVE, "0=show all, 1=hide full - servers on the serverlist");
587
588 cgi->Cmd_AddCommand("bookmark_add", GAME_MP_BookmarkAdd_f, "Add a new bookmark - see adrX cvars");
589 cgi->Cmd_AddCommand("server_info", GAME_MP_ServerInfo_f, nullptr);
590 cgi->Cmd_AddCommand("serverlist", GAME_MP_PrintServerList_f, nullptr);
591 /* text id is servers in menu_multiplayer.ufo */
592 cgi->Cmd_AddCommand("servers_click", GAME_MP_ServerListClick_f, nullptr);
593 }
594
GAME_MP_ServerListShutdown(void)595 void GAME_MP_ServerListShutdown (void)
596 {
597 cgi->Cmd_RemoveCommand("bookmark_add");
598 cgi->Cmd_RemoveCommand("server_info");
599 cgi->Cmd_RemoveCommand("serverlist");
600 cgi->Cmd_RemoveCommand("servers_click");
601
602 cgi->NET_DatagramSocketClose(netDatagramSocket);
603 netDatagramSocket = nullptr;
604 }
605