UFO: Alien Invasion
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
mp_serverlist.cpp
Go to the documentation of this file.
1 
6 /*
7 Copyright (C) 2002-2020 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 
40 static char serverText[1024];
41 static int serverListLength;
42 static int serverListPos;
45 
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 {
91 
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 
109 static void GAME_MP_PingServerCallback (struct net_stream* s)
110 {
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 
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 
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 
184 static void GAME_MP_AddServerToList (const char* node, const char* service)
185 {
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 
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) {
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];
275 static void GAME_MP_ParseServerInfoMessage (dbuffer* msg, const char* hostname)
276 {
277  char str[MAX_INFO_STRING];
278 
279  cgi->NET_ReadString(msg, str, sizeof(str));
280 
281  /* check for server status response message */
282  const char* value = Info_ValueForKey(str, "sv_dedicated");
283  if (Q_strnull(value)) {
284  cgi->Com_Printf(S_COLOR_GREEN "%s", str);
285  return;
286  }
287 
288  /* server info cvars and users are seperated via newline */
289  const char* users = strstr(str, "\n");
290  if (users == nullptr) {
291  cgi->Com_Printf(S_COLOR_GREEN "%s\n", str);
292  return;
293  }
294  cgi->Com_DPrintf(DEBUG_CLIENT, "%s\n", str); /* status string */
295 
296  cgi->Cvar_Set("mn_mappic", "maps/shots/default");
297  if (*Info_ValueForKey(str, "sv_needpass") == '1')
298  cgi->Cvar_Set("mn_server_need_password", "1");
299  else
300  cgi->Cvar_Set("mn_server_need_password", "0");
301 
302  Com_sprintf(serverInfoText, sizeof(serverInfoText), _("IP\t%s\n\n"), hostname);
303  cgi->Cvar_Set("mn_server_ip", "%s", hostname);
304  value = Info_ValueForKey(str, "sv_mapname");
305  assert(value);
306  cgi->Cvar_Set("mn_svmapname", "%s", value);
307  char buf[256];
308  Q_strncpyz(buf, value, sizeof(buf));
309  const char* token = buf;
310  /* skip random map char. */
311  if (token[0] == '+')
312  token++;
313 
314  Q_strcat(serverInfoText, sizeof(serverInfoText), _("Map:\t%s\n"), value);
315  if (!cgi->R_ImageExists("pics/maps/shots/%s", token)) {
316  /* store it relative to pics/ dir - not relative to game dir */
317  cgi->Cvar_Set("mn_mappic", "maps/shots/%s", token);
318  }
319  Q_strcat(serverInfoText, sizeof(serverInfoText), _("Servername:\t%s\n"), Info_ValueForKey(str, "sv_hostname"));
320  Q_strcat(serverInfoText, sizeof(serverInfoText), _("Moralestates:\t%s\n"), _(Info_BoolForKey(str, "sv_enablemorale")));
321  Q_strcat(serverInfoText, sizeof(serverInfoText), _("Gametype:\t%s\n"), Info_ValueForKey(str, "sv_gametype"));
322  Q_strcat(serverInfoText, sizeof(serverInfoText), _("Gameversion:\t%s\n"), Info_ValueForKey(str, "ver"));
323  Q_strcat(serverInfoText, sizeof(serverInfoText), _("Dedicated server:\t%s\n"), _(Info_BoolForKey(str, "sv_dedicated")));
324  Q_strcat(serverInfoText, sizeof(serverInfoText), _("Operating system:\t%s\n"), Info_ValueForKey(str, "sys_os"));
325  Q_strcat(serverInfoText, sizeof(serverInfoText), _("Network protocol:\t%s\n"), Info_ValueForKey(str, "sv_protocol"));
326  Q_strcat(serverInfoText, sizeof(serverInfoText), _("Roundtime:\t%s\n"), Info_ValueForKey(str, "sv_roundtimelimit"));
327  Q_strcat(serverInfoText, sizeof(serverInfoText), _("Teamplay:\t%s\n"), _(Info_BoolForKey(str, "sv_teamplay")));
328  Q_strcat(serverInfoText, sizeof(serverInfoText), _("Max. players per team:\t%s\n"), Info_ValueForKey(str, "sv_maxplayersperteam"));
329  Q_strcat(serverInfoText, sizeof(serverInfoText), _("Max. teams allowed in this map:\t%s\n"), Info_ValueForKey(str, "sv_maxteams"));
330  Q_strcat(serverInfoText, sizeof(serverInfoText), _("Max. clients:\t%s\n"), Info_ValueForKey(str, "sv_maxclients"));
331  Q_strcat(serverInfoText, sizeof(serverInfoText), _("Max. soldiers per player:\t%s\n"), Info_ValueForKey(str, "sv_maxsoldiersperplayer"));
332  Q_strcat(serverInfoText, sizeof(serverInfoText), _("Max. soldiers per team:\t%s\n"), Info_ValueForKey(str, "sv_maxsoldiersperteam"));
333  Q_strcat(serverInfoText, sizeof(serverInfoText), _("Password protected:\t%s\n"), _(Info_BoolForKey(str, "sv_needpass")));
334  cgi->UI_RegisterText(TEXT_STANDARD, serverInfoText);
335  userInfoText[0] = '\0';
336  for (;;) {
337  token = Com_Parse(&users);
338  if (users == nullptr)
339  break;
340  const int team = atoi(token);
341  token = Com_Parse(&users);
342  if (users == nullptr)
343  break;
344  Q_strcat(userInfoText, sizeof(userInfoText), "%s\t%i\n", token, team);
345  }
346  cgi->UI_RegisterText(TEXT_LIST, userInfoText);
347  cgi->UI_PushWindow("serverinfo");
348 }
349 
354 static void GAME_MP_ServerInfoCallback (struct net_stream* s)
355 {
357  if (!buf) {
358  cgi->NET_StreamFree(s);
359  return;
360  }
361  const int cmd = cgi->NET_ReadByte(buf);
362  if (cmd != svc_oob) {
363  cgi->NET_StreamFree(s);
364  return;
365  }
366  char str[8];
367  cgi->NET_ReadStringLine(buf, str, sizeof(str));
368  if (Q_streq(str, "print")) {
369  char hostname[256];
370  cgi->NET_StreamPeerToName(s, hostname, sizeof(hostname), true);
372  }
373  cgi->NET_StreamFree(s);
374 }
375 
376 static void GAME_MP_QueryMasterServerThread (const char* responseBuf, void* userdata)
377 {
378  if (!responseBuf) {
379  cgi->Com_Printf("Could not query masterserver\n");
380  return;
381  }
382 
383  const char* serverListBuf = responseBuf;
384 
385  Com_DPrintf(DEBUG_CLIENT, "masterserver response: %s\n", serverListBuf);
386  const char* token = Com_Parse(&serverListBuf);
387 
388  int num = atoi(token);
389  if (num >= MAX_SERVERLIST) {
390  cgi->Com_DPrintf(DEBUG_CLIENT, "Too many servers: %i\n", num);
391  num = MAX_SERVERLIST;
392  }
393  for (int i = 0; i < num; i++) {
394  /* host */
395  token = Com_Parse(&serverListBuf);
396  if (!*token || !serverListBuf) {
397  cgi->Com_Printf("Could not finish the masterserver response parsing\n");
398  break;
399  }
400  char node[MAX_VAR];
401  Q_strncpyz(node, token, sizeof(node));
402  /* port */
403  token = Com_Parse(&serverListBuf);
404  if (token[0] == '\0' || !serverListBuf) {
405  cgi->Com_Printf("Could not finish the masterserver response parsing\n");
406  break;
407  }
408  char service[MAX_VAR];
409  Q_strncpyz(service, token, sizeof(service));
410  GAME_MP_AddServerToList(node, service);
411  }
412 }
413 
417 static void GAME_MP_ServerListDiscoveryCallback (struct datagram_socket* s, const char* buf, int len, struct sockaddr* from)
418 {
419  const char match[] = "discovered";
420  if (len == sizeof(match) && memcmp(buf, match, len) == 0) {
421  char node[MAX_VAR];
422  char service[MAX_VAR];
423  cgi->NET_SockaddrToStrings(s, from, node, sizeof(node), service, sizeof(service));
424  GAME_MP_AddServerToList(node, service);
425  }
426 }
427 
433 static void GAME_MP_BookmarkAdd_f (void)
434 {
435  const char* newBookmark;
436 
437  if (cgi->Cmd_Argc() < 2) {
438  newBookmark = cgi->Cvar_GetString("mn_server_ip");
439  if (!newBookmark) {
440  cgi->Com_Printf("Usage: %s <ip>\n", cgi->Cmd_Argv(0));
441  return;
442  }
443  } else {
444  newBookmark = cgi->Cmd_Argv(1);
445  }
446 
447  for (int i = 0; i < MAX_BOOKMARKS; i++) {
448  const char* bookmark = cgi->Cvar_GetString(va("adr%i", i));
449  if (bookmark[0] == '\0') {
450  cgi->Cvar_Set(va("adr%i", i), "%s", newBookmark);
451  return;
452  }
453  }
454  /* bookmarks are full */
455  cgi->UI_Popup(_("Notice"), "%s", _("All bookmark slots are used - please removed unused entries and repeat this step"));
456 }
457 
461 static void GAME_MP_ServerInfo_f (void)
462 {
463  const char* host;
464  const char* port;
465 
466  switch (cgi->Cmd_Argc()) {
467  case 2:
468  host = cgi->Cmd_Argv(1);
469  port = DOUBLEQUOTE(PORT_SERVER);
470  break;
471  case 3:
472  host = cgi->Cmd_Argv(1);
473  port = cgi->Cmd_Argv(2);
474  break;
475  default:
476  if (selectedServer) {
477  host = selectedServer->node;
478  port = selectedServer->service;
479  } else {
480  host = cgi->Cvar_GetString("mn_server_ip");
481  port = DOUBLEQUOTE(PORT_SERVER);
482  }
483  break;
484  }
485  struct net_stream* s = cgi->NET_Connect(host, port, nullptr);
486  if (s != nullptr) {
487  cgi->NET_OOB_Printf(s, SV_CMD_STATUS " %i", PROTOCOL_VERSION);
488  cgi->NET_StreamSetCallback(s, &GAME_MP_ServerInfoCallback);
489  } else {
490  cgi->Com_Printf("Could not connect to %s %s\n", host, port);
491  }
492 }
493 
498 static void GAME_MP_ServerListClick_f (void)
499 {
500  if (cgi->Cmd_Argc() < 2) {
501  cgi->Com_Printf("Usage: %s <num>\n", cgi->Cmd_Argv(0));
502  return;
503  }
504  const int num = atoi(cgi->Cmd_Argv(1));
505 
506  if (num < 0 || num >= serverListLength)
507  return;
508 
509  cgi->UI_RegisterText(TEXT_STANDARD, serverInfoText);
510  for (int i = 0; i < serverListLength; i++) {
511  if (!serverList[i].pinged || serverList[i].serverListPos != num)
512  continue;
513  /* found the server - grab the infos for this server */
514  selectedServer = &serverList[i];
515  cgi->Cbuf_AddText("server_info %s %s\n", serverList[i].node, serverList[i].service);
516  return;
517  }
518 }
519 
521 static bool serversAlreadyQueried = false;
522 static int lastServerQuery = 0;
524 #define SERVERQUERYTIMEOUT 40000
525 
532 {
533  selectedServer = nullptr;
534 
535  /* refresh the list */
536  if (cgi->Cmd_Argc() == 2) {
537  /* reset current list */
538  serverText[0] = 0;
539  serversAlreadyQueried = false;
540  for (int i = 0; i < serverListLength; i++) {
541  cgi->Free(serverList[i].node);
542  cgi->Free(serverList[i].service);
543  }
544  serverListPos = 0;
545  serverListLength = 0;
546  OBJZERO(serverList);
547  } else {
548  cgi->UI_RegisterText(TEXT_LIST, serverText);
549  return;
550  }
551 
552  if (!netDatagramSocket)
554 
555  /* broadcast search for all the servers int the local network */
556  if (netDatagramSocket) {
557  const char buf[] = "discover";
558  cgi->NET_DatagramBroadcast(netDatagramSocket, buf, sizeof(buf), PORT_SERVER);
559  }
560  cgi->UI_RegisterText(TEXT_LIST, serverText);
561 
562  /* don't query the masterservers with every call */
563  if (serversAlreadyQueried) {
564  if (lastServerQuery + SERVERQUERYTIMEOUT > cgi->CL_Milliseconds())
565  return;
566  } else
567  serversAlreadyQueried = true;
568 
569  lastServerQuery = cgi->CL_Milliseconds();
570 
571  /* query master server? */
572  if (cgi->Cmd_Argc() == 2 && !Q_streq(cgi->Cmd_Argv(1), "local")) {
573  cgi->Com_DPrintf(DEBUG_CLIENT, "Query masterserver\n");
574  cgi->CL_QueryMasterServer("query", GAME_MP_QueryMasterServerThread);
575  }
576 }
577 
578 static const cmdList_t serverListCmds[] = {
579  {"bookmark_add", GAME_MP_BookmarkAdd_f, "Add a new bookmark - see adrX cvars"},
580  {"server_info", GAME_MP_ServerInfo_f, nullptr},
581  {"serverlist", GAME_MP_PrintServerList_f, nullptr},
582  /* text id is servers in menu_multiplayer.ufo */
583  {"servers_click", GAME_MP_ServerListClick_f, nullptr},
584  {nullptr, nullptr, nullptr}
585 };
587 {
588  cgi = import;
589  /* register our variables */
590  for (int i = 0; i < MAX_BOOKMARKS; i++)
591  cgi->Cvar_Get(va("adr%i", i), "", CVAR_ARCHIVE, "Bookmark for network ip");
592  cl_serverlist = cgi->Cvar_Get("cl_serverlist", "0", CVAR_ARCHIVE, "0=show all, 1=hide full - servers on the serverlist");
593 
594  cgi->Cmd_TableAddList(serverListCmds);
595 }
596 
598 {
599  cgi->Cmd_TableRemoveList(serverListCmds);
600 
601  cgi->NET_DatagramSocketClose(netDatagramSocket);
602  netDatagramSocket = nullptr;
603 }
bool Q_strnull(const char *string)
Definition: shared.h:138
serverList_t * selectedServer
static void GAME_MP_PrintServerList_f(void)
Prints all the servers on the list to game console.
static const cgame_import_t * cgi
#define MAX_BOOKMARKS
Definition: mp_serverlist.h:28
#define MAX_TEAMS
Definition: defines.h:98
const char * va(const char *format,...)
does a varargs printf into a temp buffer, so I don't need to have varargs versions of all text functi...
Definition: shared.cpp:410
static void GAME_MP_ServerListClick_f(void)
Callback for bookmark nodes in multiplayer menu (mp_bookmarks)
static int serverListLength
This is a cvar definition. Cvars can be user modified and used in our menus e.g.
Definition: cvar.h:71
#define _(String)
Definition: cl_shared.h:43
char mapname[16]
Definition: mp_serverlist.h:35
bool Com_sprintf(char *dest, size_t size, const char *fmt,...)
copies formatted string with buffer-size checking
Definition: shared.cpp:494
int maxPlayersPerTeam
Definition: mp_callbacks.h:33
Serverlist menu callbacks headers for multiplayer.
char * service
Definition: mp_serverlist.h:32
static void GAME_MP_ServerInfo_f(void)
static const char * user
Definition: test_webapi.cpp:35
static bool GAME_MP_ShowServer(const serverList_t *server)
Perform the server filtering.
static void GAME_MP_PingServerCallback(struct net_stream *s)
Definition: autoptr.h:3
char *IMPORT * GAME_StrDup(const char *string)
static void GAME_MP_ServerInfoCallback(struct net_stream *s)
static void GAME_MP_QueryMasterServerThread(const char *responseBuf, void *userdata)
static char userInfoText[256]
char version[8]
Definition: mp_serverlist.h:36
int integer
Definition: cvar.h:81
static void GAME_MP_ServerListDiscoveryCallback(struct datagram_socket *s, const char *buf, int len, struct sockaddr *from)
static void GAME_MP_PingServer(serverList_t *server)
Pings all servers in serverList.
voidpf void * buf
Definition: ioapi.h:42
#define CVAR_ARCHIVE
Definition: cvar.h:40
static void GAME_MP_BookmarkAdd_f(void)
Add a new bookmark.
static cvar_t * cl_serverlist
const char *IMPORT * Cvar_GetString(const char *varName)
static serverList_t serverList[MAX_SERVERLIST]
const char * Info_ValueForKey(const char *s, const char *key)
Searches the string for the given key and returns the associated value, or an empty string...
Definition: infostring.cpp:39
void Q_strncpyz(char *dest, const char *src, size_t destsize)
Safe strncpy that ensures a trailing zero.
Definition: shared.cpp:457
cvar_t *IMPORT * Cvar_Set(const char *varName, const char *value,...) __attribute__((format(__printf__
#define UFO_VERSION
Definition: common.h:36
#define MAX_INFO_STRING
Definition: infostring.h:36
#define DEBUG_CLIENT
Definition: defines.h:59
#define OBJZERO(obj)
Definition: shared.h:178
#define MAX_VAR
Definition: shared.h:36
dbuffer *IMPORT * NET_ReadMsg(struct net_stream *s)
Serverlist management headers for multiplayer.
#define MAX_SERVERLIST
char sv_hostname[MAX_OSPATH]
Definition: mp_serverlist.h:34
Definition: cmd.h:86
#define S_COLOR_GREEN
Definition: common.h:219
void Com_DPrintf(int level, const char *fmt,...)
A Com_Printf that only shows up if the "developer" cvar is set.
Definition: common.cpp:398
cvar_t *IMPORT * Cvar_Get(const char *varName, const char *value, int flags, const char *desc)
const char *IMPORT * NET_StreamPeerToName(struct net_stream *s, char *dst, int len, bool appendPort)
struct net_stream *IMPORT * NET_Connect(const char *node, const char *service, stream_onclose_func *onclose)
#define SERVERQUERYTIMEOUT
void *IMPORT * NET_StreamGetData(struct net_stream *s)
void GAME_MP_ParseTeamInfoMessage(dbuffer *msg)
Team selection text.
const char * Com_Parse(const char *data_p[], char *target, size_t size, bool replaceWhitespaces)
Parse a token out of a string.
Definition: parse.cpp:107
#define SV_CMD_STATUS
Definition: q_shared.h:590
static char serverInfoText[1024]
void GAME_MP_ServerListShutdown(void)
QGL_EXTERN GLint i
Definition: r_gl.h:113
QGL_EXTERN GLuint GLchar GLuint * len
Definition: r_gl.h:99
static void GAME_MP_AddServerToList(const char *node, const char *service)
Adds a server to the serverlist cache.
static void GAME_MP_ParseServerInfoMessage(dbuffer *msg, const char *hostname)
Serverbrowser text.
teamData_t teamData
struct datagram_socket *IMPORT * NET_DatagramSocketNew(const char *node, const char *service, datagram_callback_func *func)
void GAME_MP_PingServers_f(void)
The first function called when entering the multiplayer menu, then CL_Frame takes over...
static int serverListPos
static bool GAME_MP_ProcessPingReply(serverList_t *server, const char *msg)
Parsed the server ping response.
cvar_t * port
Definition: common.cpp:58
void Q_strcat(char *dest, size_t destsize, const char *format,...)
Safely (without overflowing the destination buffer) concatenates two strings.
Definition: shared.cpp:475
static bool serversAlreadyQueried
static char serverText[1024]
static int lastServerQuery
serverListStatus_t
void GAME_MP_ServerListInit(const cgame_import_t *import)
static struct datagram_socket * netDatagramSocket
static const cmdList_t serverListCmds[]
#define Q_streq(a, b)
Definition: shared.h:136
char gametype[8]
Definition: mp_serverlist.h:37
#define TEAM_NO_ACTIVE
Definition: q_shared.h:60
#define PROTOCOL_VERSION
Definition: common.h:134
#define SV_CMD_INFO
Definition: q_shared.h:592
const char *IMPORT * Cmd_Argv(int n)
#define DOUBLEQUOTE(x)
Definition: shared.h:90
const char * Info_BoolForKey(const char *s, const char *key)
Definition: infostring.cpp:76
#define PORT_CLIENT
Definition: common.h:136
int Info_IntegerForKey(const char *s, const char *key)
Definition: infostring.cpp:84
#define PORT_SERVER
Definition: common.h:137
int teamCount[MAX_TEAMS]
Definition: mp_callbacks.h:31