/* Copyright (C) 1997-2001 Id Software, Inc. This program 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 2 of the License, or (at your option) any later version. This program 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 this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // // m_mp_join.c // #include "m_local.h" /* ============================================================================= JOIN SERVER MENU ============================================================================= */ typedef struct m_joinServerMenu_s { // Menu items uiFrameWork_t frameWork; uiImage_t banner; uiAction_t addressBookAction; uiAction_t bookServersAction; uiAction_t localServersAction; uiAction_t nameSortAction; uiAction_t gameSortAction; uiAction_t mapSortAction; uiAction_t playerSortAction; uiAction_t pingSortAction; uiAction_t hostNames[MAX_LOCAL_SERVERS]; uiAction_t gameNames[MAX_LOCAL_SERVERS]; uiAction_t serverMap[MAX_LOCAL_SERVERS]; uiAction_t serverPlayers[MAX_LOCAL_SERVERS]; uiAction_t serverPing[MAX_LOCAL_SERVERS]; uiAction_t backAction; uiAction_t refreshAction; uiAction_t playAction; } m_joinServerMenu_t; static m_joinServerMenu_t m_joinServerMenu; static void JoinServerMenu_Init (qBool sort); // ========================================================================== typedef struct serverItem_s { char *mapName; char *hostName, *shortName; char *gameName; char *netAddress; char *playersStr; int numPlayers; int maxPlayers; char *pingString; int ping; qBool statusPacket; } serverItem_t; static int totalServers; static serverItem_t sortedServers[MAX_LOCAL_SERVERS]; static int pingTime; #define MAX_HOSTNAME_LEN 32 #define MAX_GAMENAME_LEN 10 #define MAX_MAPNAME_LEN 16 #define MAX_PING_LEN 10 enum { JS_SORT_HOSTNAME, JS_SORT_GAMENAME, JS_SORT_MAPNAME, JS_SORT_PLAYERCNT, JS_SORT_PINGCNT }; enum { JS_PAGE_ADDRBOOK, JS_PAGE_LAN }; /* ============= UI_FreeServer ============= */ static void UI_FreeServer (serverItem_t *server) { if (server->mapName) CG_MemFree (server->mapName); if (server->hostName) CG_MemFree (server->hostName); if (server->shortName) CG_MemFree (server->shortName); if (server->gameName) CG_MemFree (server->gameName); if (server->netAddress) CG_MemFree (server->netAddress); if (server->playersStr) CG_MemFree (server->playersStr); if (server->pingString) CG_MemFree (server->pingString); memset (server, 0, sizeof (serverItem_t)); } /* ============= UI_DupeCheckServerList Checks for duplicates and returns true if there is one... Since status has higher priority than info, if there is already an instance and it's not status, and the current one is status, the old one is removed. ============= */ static qBool UI_DupeCheckServerList (char *adr, qBool status) { int i; for (i=0 ; i= MAX_LOCAL_SERVERS) return qTrue; if (UI_DupeCheckServerList (adr, qFalse)) return qTrue; server = &sortedServers[totalServers]; UI_FreeServer (server); totalServers++; // add net address server->netAddress = CG_TagStrDup (adr, CGTAG_MENU); // start at end of string token = info + strlen (info); // find max players while (*token != '/') token--; if (token < info) { // not found token = info + strlen (info); server->playersStr = CG_TagStrDup ("?/?", CGTAG_MENU); server->mapName = CG_TagStrDup ("?", CGTAG_MENU); server->maxPlayers = -1; server->numPlayers = -1; } else { // found server->maxPlayers = atoi (token+1); // find current number of players *token = 0; token--; while (token > info && *token >= '0' && *token <= '9') token--; server->numPlayers = atoi (token+1); // set the player string server->playersStr = CG_TagStrDup (Q_VarArgs ("%i/%i", server->numPlayers, server->maxPlayers), CGTAG_MENU); // find map name while ((token > info) && (*token == ' ')) // clear end whitespace token--; *(token+1) = 0; // go to the beginning of the single word while ((token > info) && (*token != ' ')) token--; server->mapName = CG_TagStrDup (token+1, CGTAG_MENU); } // host name is what's left over *token = 0; if (strlen (info) > MAX_HOSTNAME_LEN-1) { token = info + MAX_HOSTNAME_LEN-4; while ((token > info) && (*token == ' ')) token--; *token++ = '.'; *token++ = '.'; *token++ = '.'; } else token = info + strlen (info); *token = 0; Com_StripPadding (info, name); server->hostName = CG_TagStrDup (name, CGTAG_MENU); server->shortName = CG_TagStrDup (name, CGTAG_MENU); // add the ping server->ping = cgi.Sys_Milliseconds () - pingTime; server->pingString = CG_TagStrDup (Q_VarArgs ("%ims", server->ping), CGTAG_MENU); server->statusPacket = qFalse; // print information Com_Printf (0, "%s %s ", server->hostName, server->mapName); Com_Printf (0, "%i/%i %ims\n", server->numPlayers, server->maxPlayers, server->ping); // refresh menu // do after printing so that sorting doesn't throw the pointers off JoinServerMenu_Init (qTrue); return qTrue; } /* ============= UI_ParseServerStatus Parses a status packet from a server FIXME: check against a list of sent status requests so it's not attempting to parse things it shouldn't ============= */ #define TOKDELIMS "\\" qBool UI_ParseServerStatus (char *adr, char *info) { serverItem_t *server; char *token; char shortName[MAX_HOSTNAME_LEN]; if (!cg.menuOpen || !m_joinServerMenu.frameWork.initialized) return qFalse; if (!info || !info[0]) return qFalse; if (!adr || !adr[0]) return qFalse; if (!strchr (info, '\\')) return qFalse; if (totalServers >= MAX_LOCAL_SERVERS) return qTrue; if (UI_DupeCheckServerList (adr, qTrue)) return qTrue; server = &sortedServers[totalServers]; UI_FreeServer (server); totalServers++; // Add net address server->netAddress = CG_TagStrDup (adr, CGTAG_MENU); server->mapName = CG_TagStrDup (Info_ValueForKey (info, "mapname"), CGTAG_MENU); server->maxPlayers = atoi (Info_ValueForKey (info, "maxclients")); server->gameName = CG_TagStrDup (Info_ValueForKey (info, "gamename"), CGTAG_MENU); server->hostName = CG_TagStrDup (Info_ValueForKey (info, "hostname"), CGTAG_MENU); if (server->hostName) { Q_strncpyz (shortName, server->hostName, sizeof (shortName)); server->shortName = CG_TagStrDup (shortName, CGTAG_MENU); } // Check the player count server->numPlayers = atoi (Info_ValueForKey (info, "curplayers")); if (server->numPlayers <= 0) { server->numPlayers = 0; token = strtok (info, "\n"); if (token) { token = strtok (NULL, "\n"); while (token) { server->numPlayers++; token = strtok (NULL, "\n"); } } } // Check if it's valid if (!server->mapName[0] && !server->maxPlayers && !server->gameName[0] && !server->hostName[0]) { UI_FreeServer (server); return qFalse; } server->playersStr = CG_TagStrDup (Q_VarArgs ("%i/%i", server->numPlayers, server->maxPlayers), CGTAG_MENU); // Add the ping server->ping = cgi.Sys_Milliseconds () - pingTime; server->pingString = CG_TagStrDup (Q_VarArgs ("%ims", server->ping), CGTAG_MENU); server->statusPacket = qTrue; // Print information Com_Printf (0, "%s %s ", server->hostName, server->mapName); Com_Printf (0, "%i/%i %ims\n", server->numPlayers, server->maxPlayers, server->ping); // Refresh menu // Do after printing so that sorting doesn't throw the pointers off JoinServerMenu_Init (qTrue); return qTrue; } // ========================================================================== static void JoinServerFunc (void *used) { char buffer[128]; int index; if (uiState.selectedItem) index = (uiAction_t *)uiState.selectedItem - m_joinServerMenu.hostNames; else index = (uiAction_t *)used - m_joinServerMenu.hostNames; if (index >= totalServers) return; if (!sortedServers[index].netAddress) return; Q_snprintfz (buffer, sizeof (buffer), "connect %s\n", sortedServers[index].netAddress); cgi.Cbuf_AddText (buffer); M_ForceMenuOff (); } static void ADDRBOOK_MenuFunc (void *unused) { UI_AddressBookMenu_f (); } static void JS_Menu_PopFunc (void *unused) { UI_FreeServerList (); M_PopMenu (); } void JoinMenu_StartSStatus (void) { pingTime = cgi.Sys_Milliseconds (); } static void SearchLocalGamesFunc (void *item) { float midrow = (cg.refConfig.vidWidth*0.5) - (18*UIFT_SIZEMED); float midcol = (cg.refConfig.vidHeight*0.5) - (3*UIFT_SIZEMED); int i; char *adrString; char name[32]; UI_FreeServerList (); JoinServerMenu_Init (qTrue); UI_DrawTextBox (midrow, midcol, UIFT_SCALEMED, 36, 4); cgi.R_DrawString (NULL, midrow + (UIFT_SIZEMED*2), midcol + UIFT_SIZEMED, UIFT_SCALEMED, UIFT_SCALEMED, 0, " --- PLEASE WAIT! --- ", Q_colorGreen); cgi.R_DrawString (NULL, midrow + (UIFT_SIZEMED*2), midcol + (UIFT_SIZEMED*2), UIFT_SCALEMED, UIFT_SCALEMED, 0, "Searching for local servers, this", Q_colorGreen); cgi.R_DrawString (NULL, midrow + (UIFT_SIZEMED*2), midcol + (UIFT_SIZEMED*3), UIFT_SCALEMED, UIFT_SCALEMED, 0, "could take up to a minute, please", Q_colorGreen); cgi.R_DrawString (NULL, midrow + (UIFT_SIZEMED*2), midcol + (UIFT_SIZEMED*4), UIFT_SCALEMED, UIFT_SCALEMED, 0, "please be patient.", Q_colorGreen); cgi.R_EndFrame (); // the text box won't show up unless we do a buffer swap if (item == &m_joinServerMenu.bookServersAction) cgi.Cvar_VariableSetValue (ui_jsMenuPage, JS_PAGE_ADDRBOOK, qTrue); else if (item == &m_joinServerMenu.localServersAction) cgi.Cvar_VariableSetValue (ui_jsMenuPage, JS_PAGE_LAN, qTrue); switch (ui_jsMenuPage->intVal) { case JS_PAGE_LAN: m_joinServerMenu.localServersAction.generic.flags |= UIF_FORCESELBAR; m_joinServerMenu.bookServersAction.generic.flags &= ~UIF_FORCESELBAR; // cgi.Cbuf_AddText ("pinglocal\n"); // send out info packet request cgi.Cbuf_AddText ("statuslocal\n"); // send out status packet request break; case JS_PAGE_ADDRBOOK: m_joinServerMenu.localServersAction.generic.flags &= ~UIF_FORCESELBAR; m_joinServerMenu.bookServersAction.generic.flags |= UIF_FORCESELBAR; cgi.Cbuf_AddText ("ui_startSStatus\n"); for (i=0 ; iintVal) { case JS_SORT_HOSTNAME: m_joinServerMenu.nameSortAction.generic.flags |= UIF_FORCESELBAR; break; case JS_SORT_GAMENAME: m_joinServerMenu.gameSortAction.generic.flags |= UIF_FORCESELBAR; break; case JS_SORT_MAPNAME: m_joinServerMenu.mapSortAction.generic.flags |= UIF_FORCESELBAR; break; case JS_SORT_PLAYERCNT: m_joinServerMenu.playerSortAction.generic.flags |= UIF_FORCESELBAR; break; case JS_SORT_PINGCNT: m_joinServerMenu.pingSortAction.generic.flags |= UIF_FORCESELBAR; break; default: Com_Printf (PRNT_ERROR, "Invalid ui_jsSortItem value\n"); break; } } /* ============= Sort_HostNameFunc ============= */ static int hostNameSortCmp (const void *_a, const void *_b) { const serverItem_t *a = (const serverItem_t *) _a; const serverItem_t *b = (const serverItem_t *) _b; if (a && a->hostName && b && b->hostName) return strcmp (a->hostName, b->hostName); return 0; } static int hostNameInvSortCmp (const void *_a, const void *_b) { const serverItem_t *a = (const serverItem_t *) _a; const serverItem_t *b = (const serverItem_t *) _b; if (a && a->hostName && b && b->hostName) return -strcmp (a->hostName, b->hostName); return 0; } static void Sort_HostNameFunc (void *item) { cgi.Cvar_VariableSetValue (ui_jsSortItem, 0, qTrue); // sort if (item) cgi.Cvar_VariableSetValue (ui_jsSortMethod, ui_jsSortMethod->intVal ? 0 : 1, qTrue); switch (ui_jsSortMethod->intVal) { case 0: qsort (sortedServers, totalServers, sizeof (serverItem_t), hostNameSortCmp); break; default: qsort (sortedServers, totalServers, sizeof (serverItem_t), hostNameInvSortCmp); break; } if (item) JoinServerMenu_Init (qFalse); } /* ============= Sort_GameNameFunc ============= */ static int gameNameSortCmp (const void *_a, const void *_b) { const serverItem_t *a = (const serverItem_t *) _a; const serverItem_t *b = (const serverItem_t *) _b; if (a && a->gameName && b && b->gameName) return strcmp (a->gameName, b->gameName); return 0; } static int gameNameInvSortCmp (const void *_a, const void *_b) { const serverItem_t *a = (const serverItem_t *) _a; const serverItem_t *b = (const serverItem_t *) _b; if (a && a->gameName && b && b->gameName) return -strcmp (a->gameName, b->gameName); return 0; } static void Sort_GameNameFunc (void *item) { cgi.Cvar_VariableSetValue (ui_jsSortItem, 1, qTrue); // sort if (item) cgi.Cvar_VariableSetValue (ui_jsSortMethod, ui_jsSortMethod->intVal ? 0 : 1, qTrue); switch (ui_jsSortMethod->intVal) { case 0: qsort (sortedServers, totalServers, sizeof (serverItem_t), gameNameSortCmp); break; default: qsort (sortedServers, totalServers, sizeof (serverItem_t), gameNameInvSortCmp); break; } if (item) JoinServerMenu_Init (qFalse); } /* ============= Sort_MapNameFunc ============= */ static int mapNameSortCmp (const void *_a, const void *_b) { const serverItem_t *a = (const serverItem_t *) _a; const serverItem_t *b = (const serverItem_t *) _b; if (a && a->mapName && b && b->mapName) return strcmp (a->mapName, b->mapName); return 1; } static int mapNameInvSortCmp (const void *_a, const void *_b) { const serverItem_t *a = (const serverItem_t *) _a; const serverItem_t *b = (const serverItem_t *) _b; if (a && a->mapName && b && b->mapName) return -strcmp (a->mapName, b->mapName); return 1; } static void Sort_MapNameFunc (void *item) { cgi.Cvar_VariableSetValue (ui_jsSortItem, 2, qTrue); // sort if (item) cgi.Cvar_VariableSetValue (ui_jsSortMethod, ui_jsSortMethod->intVal ? 0 : 1, qTrue); switch (ui_jsSortMethod->intVal) { case 0: qsort (sortedServers, totalServers, sizeof (serverItem_t), mapNameSortCmp); break; default: qsort (sortedServers, totalServers, sizeof (serverItem_t), mapNameInvSortCmp); break; } if (item) JoinServerMenu_Init (qFalse); } /* ============= Sort_PlayerCntFunc ============= */ static int playerSortCmp (const void *_a, const void *_b) { const serverItem_t *a = (const serverItem_t *) _a; const serverItem_t *b = (const serverItem_t *) _b; if (a && b) { if (a->numPlayers > b->numPlayers) return 1; else return -1; } return 1; } static int playerInvSortCmp (const void *_a, const void *_b) { const serverItem_t *a = (const serverItem_t *) _a; const serverItem_t *b = (const serverItem_t *) _b; if (a && b) { if (a->numPlayers > b->numPlayers) return -1; else return 1; } return -1; } static void Sort_PlayerCntFunc (void *item) { cgi.Cvar_VariableSetValue (ui_jsSortItem, 3, qTrue); // sort if (item) cgi.Cvar_VariableSetValue (ui_jsSortMethod, ui_jsSortMethod->intVal ? 0 : 1, qTrue); switch (ui_jsSortMethod->intVal) { case 0: qsort (sortedServers, totalServers, sizeof (serverItem_t), playerSortCmp); break; default: qsort (sortedServers, totalServers, sizeof (serverItem_t), playerInvSortCmp); break; } if (item) JoinServerMenu_Init (qFalse); } /* ============= Sort_PingFunc ============= */ static int pingSortCmp (const void *_a, const void *_b) { const serverItem_t *a = (const serverItem_t *) _a; const serverItem_t *b = (const serverItem_t *) _b; if (a && b) { if (a->ping > b->ping) return 1; else return -1; } return 1; } static int pingInvSortCmp (const void *_a, const void *_b) { const serverItem_t *a = (const serverItem_t *) _a; const serverItem_t *b = (const serverItem_t *) _b; if (a && b) { if (a->ping > b->ping) return -1; else return 1; } return -1; } static void Sort_PingFunc (void *item) { cgi.Cvar_VariableSetValue (ui_jsSortItem, 4, qTrue); // sort if (item) cgi.Cvar_VariableSetValue (ui_jsSortMethod, ui_jsSortMethod->intVal ? 0 : 1, qTrue); switch (ui_jsSortMethod->intVal) { case 0: qsort (sortedServers, totalServers, sizeof (serverItem_t), pingSortCmp); break; default: qsort (sortedServers, totalServers, sizeof (serverItem_t), pingInvSortCmp); break; } if (item) JoinServerMenu_Init (qFalse); } // ========================================================================== /* ============= JoinServerMenu_Init ============= */ static void JoinServerMenu_Init (qBool sort) { int i; if (sort) { switch (ui_jsSortItem->intVal) { case JS_SORT_HOSTNAME: Sort_HostNameFunc (NULL); break; case JS_SORT_GAMENAME: Sort_GameNameFunc (NULL); break; case JS_SORT_MAPNAME: Sort_MapNameFunc (NULL); break; case JS_SORT_PLAYERCNT: Sort_PlayerCntFunc (NULL); break; case JS_SORT_PINGCNT: Sort_PingFunc (NULL); break; default: Com_Printf (PRNT_ERROR, "Invalid ui_jsSortItem value\n"); break; } } UI_StartFramework (&m_joinServerMenu.frameWork, FWF_CENTERHEIGHT); m_joinServerMenu.banner.generic.type = UITYPE_IMAGE; m_joinServerMenu.banner.generic.flags = UIF_NOSELECT|UIF_CENTERED; m_joinServerMenu.banner.generic.name = NULL; m_joinServerMenu.banner.shader = uiMedia.banners.joinServer; m_joinServerMenu.addressBookAction.generic.type = UITYPE_ACTION; m_joinServerMenu.addressBookAction.generic.name = "Edit Addresses"; m_joinServerMenu.addressBookAction.generic.flags = UIF_LEFT_JUSTIFY|UIF_SHADOW; m_joinServerMenu.addressBookAction.generic.callBack = ADDRBOOK_MenuFunc; m_joinServerMenu.addressBookAction.generic.statusBar = "Edit address book entries"; m_joinServerMenu.bookServersAction.generic.type = UITYPE_ACTION; m_joinServerMenu.bookServersAction.generic.name = S_COLOR_YELLOW"Address Book"; m_joinServerMenu.bookServersAction.generic.flags |= UIF_LEFT_JUSTIFY|UIF_SHADOW; m_joinServerMenu.bookServersAction.generic.callBack = SearchLocalGamesFunc; m_joinServerMenu.bookServersAction.generic.cursorDraw = Cursor_NullFunc; m_joinServerMenu.localServersAction.generic.type = UITYPE_ACTION; m_joinServerMenu.localServersAction.generic.name = S_COLOR_YELLOW"LAN"; m_joinServerMenu.localServersAction.generic.flags |= UIF_LEFT_JUSTIFY|UIF_SHADOW; m_joinServerMenu.localServersAction.generic.callBack = SearchLocalGamesFunc; m_joinServerMenu.localServersAction.generic.cursorDraw = Cursor_NullFunc; UI_AddItem (&m_joinServerMenu.frameWork, &m_joinServerMenu.banner); UI_AddItem (&m_joinServerMenu.frameWork, &m_joinServerMenu.addressBookAction); UI_AddItem (&m_joinServerMenu.frameWork, &m_joinServerMenu.bookServersAction); UI_AddItem (&m_joinServerMenu.frameWork, &m_joinServerMenu.localServersAction); m_joinServerMenu.nameSortAction.generic.type = UITYPE_ACTION; m_joinServerMenu.nameSortAction.generic.name = S_COLOR_GREEN"Server name"; m_joinServerMenu.nameSortAction.generic.flags = UIF_LEFT_JUSTIFY|UIF_SHADOW; m_joinServerMenu.nameSortAction.generic.callBack = Sort_HostNameFunc; m_joinServerMenu.nameSortAction.generic.statusBar = "Sort by server name"; m_joinServerMenu.nameSortAction.generic.cursorDraw = Cursor_NullFunc; m_joinServerMenu.gameSortAction.generic.type = UITYPE_ACTION; m_joinServerMenu.gameSortAction.generic.name = S_COLOR_GREEN"Game"; m_joinServerMenu.gameSortAction.generic.flags = UIF_LEFT_JUSTIFY|UIF_SHADOW; m_joinServerMenu.gameSortAction.generic.callBack = Sort_GameNameFunc; m_joinServerMenu.gameSortAction.generic.statusBar = "Sort by game name"; m_joinServerMenu.gameSortAction.generic.cursorDraw = Cursor_NullFunc; m_joinServerMenu.mapSortAction.generic.type = UITYPE_ACTION; m_joinServerMenu.mapSortAction.generic.name = S_COLOR_GREEN"Map"; m_joinServerMenu.mapSortAction.generic.flags = UIF_LEFT_JUSTIFY|UIF_SHADOW; m_joinServerMenu.mapSortAction.generic.callBack = Sort_MapNameFunc; m_joinServerMenu.mapSortAction.generic.statusBar = "Sort by mapname"; m_joinServerMenu.mapSortAction.generic.cursorDraw = Cursor_NullFunc; m_joinServerMenu.playerSortAction.generic.type = UITYPE_ACTION; m_joinServerMenu.playerSortAction.generic.name = S_COLOR_GREEN"Players"; m_joinServerMenu.playerSortAction.generic.flags = UIF_LEFT_JUSTIFY|UIF_SHADOW; m_joinServerMenu.playerSortAction.generic.callBack = Sort_PlayerCntFunc; m_joinServerMenu.playerSortAction.generic.statusBar = "Sort by player count"; m_joinServerMenu.playerSortAction.generic.cursorDraw= Cursor_NullFunc; m_joinServerMenu.pingSortAction.generic.type = UITYPE_ACTION; m_joinServerMenu.pingSortAction.generic.name = S_COLOR_GREEN"Ping"; m_joinServerMenu.pingSortAction.generic.flags = UIF_LEFT_JUSTIFY|UIF_SHADOW; m_joinServerMenu.pingSortAction.generic.callBack = Sort_PingFunc; m_joinServerMenu.pingSortAction.generic.statusBar = "Sort by player count"; m_joinServerMenu.pingSortAction.generic.cursorDraw = Cursor_NullFunc; Sort_SetHighlight (); UI_AddItem (&m_joinServerMenu.frameWork, &m_joinServerMenu.nameSortAction); UI_AddItem (&m_joinServerMenu.frameWork, &m_joinServerMenu.gameSortAction); UI_AddItem (&m_joinServerMenu.frameWork, &m_joinServerMenu.mapSortAction); UI_AddItem (&m_joinServerMenu.frameWork, &m_joinServerMenu.playerSortAction); UI_AddItem (&m_joinServerMenu.frameWork, &m_joinServerMenu.pingSortAction); for (i=0 ; i