00001
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
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
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
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
00250 if (!cnt) {
00253
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
00281 value = Info_ValueForKey(str, "sv_dedicated");
00282 if (*value) {
00283
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);
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
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
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
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
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
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
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
00551 if (Cmd_Argc() == 2) {
00552 int i;
00553
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
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
00579 if (serversAlreadyQueried) {
00580 if (lastServerQuery + SERVERQUERYTIMEOUT > CL_Milliseconds())
00581 return;
00582 } else
00583 serversAlreadyQueried = qtrue;
00584
00585 lastServerQuery = CL_Milliseconds();
00586
00587
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
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
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 }