/* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code 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. Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // /* ======================================================================= MULTIPLAYER MENU (SERVER BROWSER) ======================================================================= */ #include "ui_local.h" #define MAX_GLOBALSERVERS 128 #define MAX_PINGREQUESTS 32 #define MAX_ADDRESSLENGTH 64 #define MAX_HOSTNAMELENGTH 22 #define MAX_MAPNAMELENGTH 16 #define MAX_LISTBOXITEMS 128 #define MAX_LOCALSERVERS 128 #define MAX_STATUSLENGTH 64 #define MAX_LEAGUELENGTH 28 #define MAX_LISTBOXWIDTH 68 #define ART_BACK0 "menu/art/back_0" #define ART_BACK1 "menu/art/back_1" #define ART_CREATE0 "menu/art/create_0" #define ART_CREATE1 "menu/art/create_1" #define ART_SPECIFY0 "menu/art/specify_0" #define ART_SPECIFY1 "menu/art/specify_1" #define ART_REFRESH0 "menu/art/refresh_0" #define ART_REFRESH1 "menu/art/refresh_1" #define ART_CONNECT0 "menu/art/fight_0" #define ART_CONNECT1 "menu/art/fight_1" #define ART_ARROWS0 "menu/art/arrows_vert_0" #define ART_ARROWS_UP "menu/art/arrows_vert_top" #define ART_ARROWS_DOWN "menu/art/arrows_vert_bot" #define ART_UNKNOWNMAP "menu/art/unknownmap" #define ART_REMOVE0 "menu/art/delete_0" #define ART_REMOVE1 "menu/art/delete_1" #define ART_PUNKBUSTER "menu/art/pblogo" #define ID_MASTER 10 #define ID_GAMETYPE 11 #define ID_SORTKEY 12 #define ID_SHOW_FULL 13 #define ID_SHOW_EMPTY 14 #define ID_LIST 15 #define ID_SCROLL_UP 16 #define ID_SCROLL_DOWN 17 #define ID_BACK 18 #define ID_REFRESH 19 #define ID_SPECIFY 20 #define ID_CREATE 21 #define ID_CONNECT 22 #define ID_REMOVE 23 #define ID_PUNKBUSTER 24 #define GR_LOGO 30 #define GR_LETTERS 31 #define UIAS_LOCAL 0 #define UIAS_GLOBAL1 1 #define UIAS_GLOBAL2 2 #define UIAS_GLOBAL3 3 #define UIAS_GLOBAL4 4 #define UIAS_GLOBAL5 5 #define UIAS_FAVORITES 6 #define UI_MAX_MASTER_SERVERS 5 #define SORT_HOST 0 #define SORT_MAP 1 #define SORT_CLIENTS 2 #define SORT_GAME 3 #define SORT_PING 4 #define GAMES_ALL 0 #define GAMES_FFA 1 #define GAMES_TEAMPLAY 2 #define GAMES_TOURNEY 3 #define GAMES_CTF 4 static const char *master_items[] = { "Local", "Internet1", "Internet2", "Internet3", "Internet4", "Internet5", "Favorites", NULL }; static const char *servertype_items[] = { "All", "Free For All", "Team Deathmatch", "Tournament", "Capture the Flag", NULL }; static const char *sortkey_items[] = { "Server Name", "Map Name", "Open Player Spots", "Game Type", "Ping Time", NULL }; static char* gamenames[] = { "DM ", // deathmatch "1v1", // tournament "SP ", // single player "Team DM", // team deathmatch "CTF", // capture the flag "One Flag CTF", // one flag ctf "OverLoad", // Overload "Harvester", // Harvester "Rocket Arena 3", // Rocket Arena 3 "Q3F", // Q3F "Urban Terror", // Urban Terror "OSP", // Orange Smoothie Productions "???", // unknown NULL }; static char* netnames[] = { "??? ", "UDP ", "UDP6", NULL }; static char quake3worldMessage[] = "Visit www.quake3world.com - News, Community, Events, Files"; const char* punkbuster_items[] = { "Disabled", "Enabled", NULL }; const char* punkbuster_msg[] = { "PunkBuster will be", "disabled the next time", "Quake III Arena", "is started.", NULL }; typedef struct { char adrstr[MAX_ADDRESSLENGTH]; int start; } pinglist_t; typedef struct servernode_s { char adrstr[MAX_ADDRESSLENGTH]; char hostname[MAX_HOSTNAMELENGTH+3]; char mapname[MAX_MAPNAMELENGTH]; int numclients; int maxclients; int pingtime; int gametype; char gamename[12]; int nettype; int minPing; int maxPing; qboolean bPB; } servernode_t; typedef struct { char buff[MAX_LISTBOXWIDTH]; servernode_t* servernode; } table_t; typedef struct { menuframework_s menu; menutext_s banner; menulist_s master; menulist_s gametype; menulist_s sortkey; menuradiobutton_s showfull; menuradiobutton_s showempty; menulist_s list; menubitmap_s mappic; menubitmap_s arrows; menubitmap_s up; menubitmap_s down; menutext_s status; menutext_s statusbar; menubitmap_s remove; menubitmap_s back; menubitmap_s refresh; menubitmap_s specify; menubitmap_s create; menubitmap_s go; pinglist_t pinglist[MAX_PINGREQUESTS]; table_t table[MAX_LISTBOXITEMS]; char* items[MAX_LISTBOXITEMS]; int numqueriedservers; int *numservers; servernode_t *serverlist; int currentping; qboolean refreshservers; int nextpingtime; int maxservers; int refreshtime; char favoriteaddresses[MAX_FAVORITESERVERS][MAX_ADDRESSLENGTH]; int numfavoriteaddresses; menulist_s punkbuster; menubitmap_s pblogo; } arenaservers_t; static arenaservers_t g_arenaservers; static servernode_t g_globalserverlist[UI_MAX_MASTER_SERVERS][MAX_GLOBALSERVERS]; static int g_numglobalservers[UI_MAX_MASTER_SERVERS]; static servernode_t g_localserverlist[MAX_LOCALSERVERS]; static int g_numlocalservers; static servernode_t g_favoriteserverlist[MAX_FAVORITESERVERS]; static int g_numfavoriteservers; static int g_servertype; static int g_gametype; static int g_sortkey; static int g_emptyservers; static int g_fullservers; /* ================= ArenaServers_MaxPing ================= */ static int ArenaServers_MaxPing( void ) { int maxPing; maxPing = (int)trap_Cvar_VariableValue( "cl_maxPing" ); if( maxPing < 100 ) { maxPing = 100; } return maxPing; } /* ================= ArenaServers_Compare ================= */ static int QDECL ArenaServers_Compare( const void *arg1, const void *arg2 ) { float f1; float f2; servernode_t* t1; servernode_t* t2; t1 = (servernode_t *)arg1; t2 = (servernode_t *)arg2; switch( g_sortkey ) { case SORT_HOST: return Q_stricmp( t1->hostname, t2->hostname ); case SORT_MAP: return Q_stricmp( t1->mapname, t2->mapname ); case SORT_CLIENTS: f1 = t1->maxclients - t1->numclients; if( f1 < 0 ) { f1 = 0; } f2 = t2->maxclients - t2->numclients; if( f2 < 0 ) { f2 = 0; } if( f1 < f2 ) { return 1; } if( f1 == f2 ) { return 0; } return -1; case SORT_GAME: if( t1->gametype < t2->gametype ) { return -1; } if( t1->gametype == t2->gametype ) { return 0; } return 1; case SORT_PING: if( t1->pingtime < t2->pingtime ) { return -1; } if( t1->pingtime > t2->pingtime ) { return 1; } return Q_stricmp( t1->hostname, t2->hostname ); } return 0; } /* ================= ArenaServers_SourceForLAN Convert ui's g_servertype to AS_* used by trap calls. ================= */ int ArenaServers_SourceForLAN(void) { switch( g_servertype ) { default: case UIAS_LOCAL: return AS_LOCAL; case UIAS_GLOBAL1: case UIAS_GLOBAL2: case UIAS_GLOBAL3: case UIAS_GLOBAL4: case UIAS_GLOBAL5: return AS_GLOBAL; case UIAS_FAVORITES: return AS_FAVORITES; } } /* ================= ArenaServers_Go ================= */ static void ArenaServers_Go( void ) { servernode_t* servernode; servernode = g_arenaservers.table[g_arenaservers.list.curvalue].servernode; if( servernode ) { trap_Cmd_ExecuteText( EXEC_APPEND, va( "connect %s\n", servernode->adrstr ) ); } } /* ================= ArenaServers_UpdatePicture ================= */ static void ArenaServers_UpdatePicture( void ) { static char picname[64]; servernode_t* servernodeptr; if( !g_arenaservers.list.numitems ) { g_arenaservers.mappic.generic.name = NULL; } else { servernodeptr = g_arenaservers.table[g_arenaservers.list.curvalue].servernode; Com_sprintf( picname, sizeof(picname), "levelshots/%s.tga", servernodeptr->mapname ); g_arenaservers.mappic.generic.name = picname; } // force shader update during draw g_arenaservers.mappic.shader = 0; } /* ================= ArenaServers_UpdateMenu ================= */ static void ArenaServers_UpdateMenu( void ) { int i; int j; int count; char* buff; servernode_t* servernodeptr; table_t* tableptr; char *pingColor; if( g_arenaservers.numqueriedservers > 0 ) { // servers found if( g_arenaservers.refreshservers && ( g_arenaservers.currentping <= g_arenaservers.numqueriedservers ) ) { // show progress Com_sprintf( g_arenaservers.status.string, MAX_STATUSLENGTH, "%d of %d Arena Servers.", g_arenaservers.currentping, g_arenaservers.numqueriedservers); g_arenaservers.statusbar.string = "Press SPACE to stop"; qsort( g_arenaservers.serverlist, *g_arenaservers.numservers, sizeof( servernode_t ), ArenaServers_Compare); } else { // all servers pinged - enable controls g_arenaservers.master.generic.flags &= ~QMF_GRAYED; g_arenaservers.gametype.generic.flags &= ~QMF_GRAYED; g_arenaservers.sortkey.generic.flags &= ~QMF_GRAYED; g_arenaservers.showempty.generic.flags &= ~QMF_GRAYED; g_arenaservers.showfull.generic.flags &= ~QMF_GRAYED; g_arenaservers.list.generic.flags &= ~QMF_GRAYED; g_arenaservers.refresh.generic.flags &= ~QMF_GRAYED; g_arenaservers.go.generic.flags &= ~QMF_GRAYED; g_arenaservers.punkbuster.generic.flags &= ~QMF_GRAYED; // update status bar if( g_servertype >= UIAS_GLOBAL1 && g_servertype <= UIAS_GLOBAL5 ) { g_arenaservers.statusbar.string = quake3worldMessage; } else { g_arenaservers.statusbar.string = ""; } } } else { // no servers found if( g_arenaservers.refreshservers ) { strcpy( g_arenaservers.status.string,"Scanning For Servers." ); g_arenaservers.statusbar.string = "Press SPACE to stop"; // disable controls during refresh g_arenaservers.master.generic.flags |= QMF_GRAYED; g_arenaservers.gametype.generic.flags |= QMF_GRAYED; g_arenaservers.sortkey.generic.flags |= QMF_GRAYED; g_arenaservers.showempty.generic.flags |= QMF_GRAYED; g_arenaservers.showfull.generic.flags |= QMF_GRAYED; g_arenaservers.list.generic.flags |= QMF_GRAYED; g_arenaservers.refresh.generic.flags |= QMF_GRAYED; g_arenaservers.go.generic.flags |= QMF_GRAYED; g_arenaservers.punkbuster.generic.flags |= QMF_GRAYED; } else { if( g_arenaservers.numqueriedservers < 0 ) { strcpy(g_arenaservers.status.string,"No Response From Master Server." ); } else { strcpy(g_arenaservers.status.string,"No Servers Found." ); } // update status bar if( g_servertype >= UIAS_GLOBAL1 && g_servertype <= UIAS_GLOBAL5 ) { g_arenaservers.statusbar.string = quake3worldMessage; } else { g_arenaservers.statusbar.string = ""; } // end of refresh - set control state g_arenaservers.master.generic.flags &= ~QMF_GRAYED; g_arenaservers.gametype.generic.flags &= ~QMF_GRAYED; g_arenaservers.sortkey.generic.flags &= ~QMF_GRAYED; g_arenaservers.showempty.generic.flags &= ~QMF_GRAYED; g_arenaservers.showfull.generic.flags &= ~QMF_GRAYED; g_arenaservers.list.generic.flags |= QMF_GRAYED; g_arenaservers.refresh.generic.flags &= ~QMF_GRAYED; g_arenaservers.go.generic.flags |= QMF_GRAYED; g_arenaservers.punkbuster.generic.flags &= ~QMF_GRAYED; } // zero out list box g_arenaservers.list.numitems = 0; g_arenaservers.list.curvalue = 0; g_arenaservers.list.top = 0; // update picture ArenaServers_UpdatePicture(); return; } // build list box strings - apply culling filters servernodeptr = g_arenaservers.serverlist; count = *g_arenaservers.numservers; for( i = 0, j = 0; i < count; i++, servernodeptr++ ) { tableptr = &g_arenaservers.table[j]; tableptr->servernode = servernodeptr; buff = tableptr->buff; // can only cull valid results if( !g_emptyservers && !servernodeptr->numclients ) { continue; } if( !g_fullservers && ( servernodeptr->numclients == servernodeptr->maxclients ) ) { continue; } switch( g_gametype ) { case GAMES_ALL: break; case GAMES_FFA: if( servernodeptr->gametype != GT_FFA ) { continue; } break; case GAMES_TEAMPLAY: if( servernodeptr->gametype != GT_TEAM ) { continue; } break; case GAMES_TOURNEY: if( servernodeptr->gametype != GT_TOURNAMENT ) { continue; } break; case GAMES_CTF: if( servernodeptr->gametype != GT_CTF ) { continue; } break; } if( servernodeptr->pingtime < servernodeptr->minPing ) { pingColor = S_COLOR_BLUE; } else if( servernodeptr->maxPing && servernodeptr->pingtime > servernodeptr->maxPing ) { pingColor = S_COLOR_BLUE; } else if( servernodeptr->pingtime < 200 ) { pingColor = S_COLOR_GREEN; } else if( servernodeptr->pingtime < 400 ) { pingColor = S_COLOR_YELLOW; } else { pingColor = S_COLOR_RED; } Com_sprintf( buff, MAX_LISTBOXWIDTH, "%-20.20s %-12.12s %2d/%2d %-8.8s %4s%s%3d " S_COLOR_YELLOW "%s", servernodeptr->hostname, servernodeptr->mapname, servernodeptr->numclients, servernodeptr->maxclients, servernodeptr->gamename, netnames[servernodeptr->nettype], pingColor, servernodeptr->pingtime, servernodeptr->bPB ? "Yes" : "No" ); j++; } g_arenaservers.list.numitems = j; g_arenaservers.list.curvalue = 0; g_arenaservers.list.top = 0; // update picture ArenaServers_UpdatePicture(); } /* ================= ArenaServers_Remove ================= */ static void ArenaServers_Remove( void ) { int i; servernode_t* servernodeptr; table_t* tableptr; if (!g_arenaservers.list.numitems) return; // remove selected item from display list // items are in scattered order due to sort and cull // perform delete on list box contents, resync all lists tableptr = &g_arenaservers.table[g_arenaservers.list.curvalue]; servernodeptr = tableptr->servernode; // find address in master list for (i=0; iadrstr)) { // delete address from master list if (i < g_arenaservers.numfavoriteaddresses-1) { // shift items up memcpy( &g_arenaservers.favoriteaddresses[i], &g_arenaservers.favoriteaddresses[i+1], (g_arenaservers.numfavoriteaddresses - i - 1)* MAX_ADDRESSLENGTH ); } g_arenaservers.numfavoriteaddresses--; memset( &g_arenaservers.favoriteaddresses[g_arenaservers.numfavoriteaddresses], 0, MAX_ADDRESSLENGTH ); break; } } // find address in server list for (i=0; i= ArenaServers_MaxPing()) && (g_servertype != UIAS_FAVORITES)) { // slow global or local servers do not get entered return; } if (*g_arenaservers.numservers >= g_arenaservers.maxservers) { // list full; servernodeptr = g_arenaservers.serverlist+(*g_arenaservers.numservers)-1; } else { // next slot servernodeptr = g_arenaservers.serverlist+(*g_arenaservers.numservers); (*g_arenaservers.numservers)++; } Q_strncpyz( servernodeptr->adrstr, adrstr, MAX_ADDRESSLENGTH ); Q_strncpyz( servernodeptr->hostname, Info_ValueForKey( info, "hostname"), MAX_HOSTNAMELENGTH ); Q_CleanStr( servernodeptr->hostname ); Q_strupr( servernodeptr->hostname ); Q_strncpyz( servernodeptr->mapname, Info_ValueForKey( info, "mapname"), MAX_MAPNAMELENGTH ); Q_CleanStr( servernodeptr->mapname ); Q_strupr( servernodeptr->mapname ); servernodeptr->numclients = atoi( Info_ValueForKey( info, "clients") ); servernodeptr->maxclients = atoi( Info_ValueForKey( info, "sv_maxclients") ); servernodeptr->pingtime = pingtime; servernodeptr->minPing = atoi( Info_ValueForKey( info, "minPing") ); servernodeptr->maxPing = atoi( Info_ValueForKey( info, "maxPing") ); servernodeptr->bPB = atoi( Info_ValueForKey( info, "punkbuster") ); /* s = Info_ValueForKey( info, "nettype" ); for (i=0; ;i++) { if (!netnames[i]) { servernodeptr->nettype = 0; break; } else if (!Q_stricmp( netnames[i], s )) { servernodeptr->nettype = i; break; } } */ servernodeptr->nettype = atoi(Info_ValueForKey(info, "nettype")); if (servernodeptr->nettype < 0 || servernodeptr->nettype >= ARRAY_LEN(netnames) - 1) { servernodeptr->nettype = 0; } s = Info_ValueForKey( info, "game"); i = atoi( Info_ValueForKey( info, "gametype") ); if( i < 0 ) { i = 0; } else if( i > 11 ) { i = 12; } if( *s ) { servernodeptr->gametype = i;//-1; Q_strncpyz( servernodeptr->gamename, s, sizeof(servernodeptr->gamename) ); } else { servernodeptr->gametype = i; Q_strncpyz( servernodeptr->gamename, gamenames[i], sizeof(servernodeptr->gamename) ); } } /* ================= ArenaServers_InsertFavorites Insert nonresponsive address book entries into display lists. ================= */ void ArenaServers_InsertFavorites( void ) { int i; int j; char info[MAX_INFO_STRING]; // resync existing results with new or deleted cvars info[0] = '\0'; Info_SetValueForKey( info, "hostname", "No Response" ); for (i=0; i= g_numfavoriteservers) { // not in list, add it ArenaServers_Insert( g_arenaservers.favoriteaddresses[i], info, ArenaServers_MaxPing() ); } } } /* ================= ArenaServers_LoadFavorites Load cvar address book entries into local lists. ================= */ void ArenaServers_LoadFavorites( void ) { int i; int j; int numtempitems; char adrstr[MAX_ADDRESSLENGTH]; servernode_t templist[MAX_FAVORITESERVERS]; qboolean found; found = qfalse; // copy the old memcpy( templist, g_favoriteserverlist, sizeof(servernode_t)*MAX_FAVORITESERVERS ); numtempitems = g_numfavoriteservers; // clear the current for sync memset( g_favoriteserverlist, 0, sizeof(servernode_t)*MAX_FAVORITESERVERS ); g_numfavoriteservers = 0; // resync existing results with new or deleted cvars for (i=0; i= 0) { g_arenaservers.currentping = *g_arenaservers.numservers; g_arenaservers.numqueriedservers = *g_arenaservers.numservers; } // sort qsort( g_arenaservers.serverlist, *g_arenaservers.numservers, sizeof( servernode_t ), ArenaServers_Compare); ArenaServers_UpdateMenu(); } /* ================= ArenaServers_DoRefresh ================= */ static void ArenaServers_DoRefresh( void ) { int i; int j; int time; int maxPing; char adrstr[MAX_ADDRESSLENGTH]; char info[MAX_INFO_STRING]; if (uis.realtime < g_arenaservers.refreshtime) { if (g_servertype != UIAS_FAVORITES) { if (g_servertype == UIAS_LOCAL) { if (!trap_LAN_GetServerCount(AS_LOCAL)) { return; } } if (trap_LAN_GetServerCount(ArenaServers_SourceForLAN()) < 0) { // still waiting for response return; } } } if (uis.realtime < g_arenaservers.nextpingtime) { // wait for time trigger return; } // trigger at 10Hz intervals g_arenaservers.nextpingtime = uis.realtime + 10; // process ping results maxPing = ArenaServers_MaxPing(); for (i=0; i maxPing) { // stale it out info[0] = '\0'; time = maxPing; } else { trap_LAN_GetPingInfo( i, info, MAX_INFO_STRING ); } // insert ping results ArenaServers_Insert( adrstr, info, time ); // clear this query from internal list g_arenaservers.pinglist[j].adrstr[0] = '\0'; } // clear this query from external list trap_LAN_ClearPing( i ); } // get results of servers query // counts can increase as servers respond if (g_servertype == UIAS_FAVORITES) { g_arenaservers.numqueriedservers = g_arenaservers.numfavoriteaddresses; } else { g_arenaservers.numqueriedservers = trap_LAN_GetServerCount(ArenaServers_SourceForLAN()); } // if (g_arenaservers.numqueriedservers > g_arenaservers.maxservers) // g_arenaservers.numqueriedservers = g_arenaservers.maxservers; // send ping requests in reasonable bursts // iterate ping through all found servers for (i=0; i= MAX_PINGREQUESTS) { // ping queue is full break; } // find empty slot for (j=0; j= MAX_PINGREQUESTS) // no empty slots available yet - wait for timeout break; // get an address to ping if (g_servertype == UIAS_FAVORITES) { strcpy( adrstr, g_arenaservers.favoriteaddresses[g_arenaservers.currentping] ); } else { trap_LAN_GetServerAddressString(ArenaServers_SourceForLAN(), g_arenaservers.currentping, adrstr, MAX_ADDRESSLENGTH ); } strcpy( g_arenaservers.pinglist[j].adrstr, adrstr ); g_arenaservers.pinglist[j].start = uis.realtime; trap_Cmd_ExecuteText( EXEC_NOW, va( "ping %s\n", adrstr ) ); // advance to next server g_arenaservers.currentping++; } if (!trap_LAN_GetPingQueueCount()) { // all pings completed ArenaServers_StopRefresh(); return; } // update the user interface with ping status ArenaServers_UpdateMenu(); } /* ================= ArenaServers_StartRefresh ================= */ static void ArenaServers_StartRefresh( void ) { int i; char myargs[32], protocol[32]; memset( g_arenaservers.serverlist, 0, g_arenaservers.maxservers*sizeof(table_t) ); for (i=0; i= UIAS_GLOBAL1 && g_servertype <= UIAS_GLOBAL5 ) { switch( g_arenaservers.gametype.curvalue ) { default: case GAMES_ALL: myargs[0] = 0; break; case GAMES_FFA: strcpy( myargs, " ffa" ); break; case GAMES_TEAMPLAY: strcpy( myargs, " team" ); break; case GAMES_TOURNEY: strcpy( myargs, " tourney" ); break; case GAMES_CTF: strcpy( myargs, " ctf" ); break; } if (g_emptyservers) { strcat(myargs, " empty"); } if (g_fullservers) { strcat(myargs, " full"); } protocol[0] = '\0'; trap_Cvar_VariableStringBuffer( "debug_protocol", protocol, sizeof(protocol) ); if (strlen(protocol)) { trap_Cmd_ExecuteText( EXEC_APPEND, va( "globalservers %d %s%s\n", g_servertype - UIAS_GLOBAL1, protocol, myargs )); } else { trap_Cmd_ExecuteText( EXEC_APPEND, va( "globalservers %d %d%s\n", g_servertype - UIAS_GLOBAL1, (int)trap_Cvar_VariableValue( "protocol" ), myargs ) ); } } } /* ================= ArenaServers_SaveChanges ================= */ void ArenaServers_SaveChanges( void ) { int i; for (i=0; i= UIAS_GLOBAL1 && type <= UIAS_GLOBAL5) { char masterstr[2], cvarname[sizeof("sv_master1")]; while(type <= UIAS_GLOBAL5) { Com_sprintf(cvarname, sizeof(cvarname), "sv_master%d", type); trap_Cvar_VariableStringBuffer(cvarname, masterstr, sizeof(masterstr)); if(*masterstr) break; type++; } } g_servertype = type; switch( type ) { default: case UIAS_LOCAL: g_arenaservers.remove.generic.flags |= (QMF_INACTIVE|QMF_HIDDEN); g_arenaservers.serverlist = g_localserverlist; g_arenaservers.numservers = &g_numlocalservers; g_arenaservers.maxservers = MAX_LOCALSERVERS; break; case UIAS_GLOBAL1: case UIAS_GLOBAL2: case UIAS_GLOBAL3: case UIAS_GLOBAL4: case UIAS_GLOBAL5: g_arenaservers.remove.generic.flags |= (QMF_INACTIVE|QMF_HIDDEN); g_arenaservers.serverlist = g_globalserverlist[type-UIAS_GLOBAL1]; g_arenaservers.numservers = &g_numglobalservers[type-UIAS_GLOBAL1]; g_arenaservers.maxservers = MAX_GLOBALSERVERS; break; case UIAS_FAVORITES: g_arenaservers.remove.generic.flags &= ~(QMF_INACTIVE|QMF_HIDDEN); g_arenaservers.serverlist = g_favoriteserverlist; g_arenaservers.numservers = &g_numfavoriteservers; g_arenaservers.maxservers = MAX_FAVORITESERVERS; break; } if( !*g_arenaservers.numservers ) { ArenaServers_StartRefresh(); } else { // avoid slow operation, use existing results g_arenaservers.currentping = *g_arenaservers.numservers; g_arenaservers.numqueriedservers = *g_arenaservers.numservers; ArenaServers_UpdateMenu(); strcpy(g_arenaservers.status.string,"hit refresh to update"); } return type; } /* ================= PunkBuster_Confirm ================= */ static void Punkbuster_ConfirmEnable( qboolean result ) { if (result) { trap_SetPbClStatus(1); } g_arenaservers.punkbuster.curvalue = Com_Clamp( 0, 1, trap_Cvar_VariableValue( "cl_punkbuster" ) ); } static void Punkbuster_ConfirmDisable( qboolean result ) { if (result) { trap_SetPbClStatus(0); UI_Message( punkbuster_msg ); } g_arenaservers.punkbuster.curvalue = Com_Clamp( 0, 1, trap_Cvar_VariableValue( "cl_punkbuster" ) ); } /* ================= ArenaServers_Event ================= */ static void ArenaServers_Event( void* ptr, int event ) { int id; id = ((menucommon_s*)ptr)->id; if( event != QM_ACTIVATED && id != ID_LIST ) { return; } switch( id ) { case ID_MASTER: g_arenaservers.master.curvalue = ArenaServers_SetType(g_arenaservers.master.curvalue); trap_Cvar_SetValue( "ui_browserMaster", g_arenaservers.master.curvalue); break; case ID_GAMETYPE: trap_Cvar_SetValue( "ui_browserGameType", g_arenaservers.gametype.curvalue ); g_gametype = g_arenaservers.gametype.curvalue; ArenaServers_UpdateMenu(); break; case ID_SORTKEY: trap_Cvar_SetValue( "ui_browserSortKey", g_arenaservers.sortkey.curvalue ); ArenaServers_Sort( g_arenaservers.sortkey.curvalue ); ArenaServers_UpdateMenu(); break; case ID_SHOW_FULL: trap_Cvar_SetValue( "ui_browserShowFull", g_arenaservers.showfull.curvalue ); g_fullservers = g_arenaservers.showfull.curvalue; ArenaServers_UpdateMenu(); break; case ID_SHOW_EMPTY: trap_Cvar_SetValue( "ui_browserShowEmpty", g_arenaservers.showempty.curvalue ); g_emptyservers = g_arenaservers.showempty.curvalue; ArenaServers_UpdateMenu(); break; case ID_LIST: if( event == QM_GOTFOCUS ) { ArenaServers_UpdatePicture(); } break; case ID_SCROLL_UP: ScrollList_Key( &g_arenaservers.list, K_UPARROW ); break; case ID_SCROLL_DOWN: ScrollList_Key( &g_arenaservers.list, K_DOWNARROW ); break; case ID_BACK: ArenaServers_StopRefresh(); ArenaServers_SaveChanges(); UI_PopMenu(); break; case ID_REFRESH: ArenaServers_StartRefresh(); break; case ID_SPECIFY: UI_SpecifyServerMenu(); break; case ID_CREATE: UI_StartServerMenu( qtrue ); break; case ID_CONNECT: ArenaServers_Go(); break; case ID_REMOVE: ArenaServers_Remove(); ArenaServers_UpdateMenu(); break; case ID_PUNKBUSTER: if (g_arenaservers.punkbuster.curvalue) { UI_ConfirmMenu_Style( "Enable Punkbuster?", UI_CENTER|UI_INVERSE|UI_SMALLFONT, 0, Punkbuster_ConfirmEnable ); } else { UI_ConfirmMenu_Style( "Disable Punkbuster?", UI_CENTER|UI_INVERSE|UI_SMALLFONT, 0, Punkbuster_ConfirmDisable ); } break; } } /* ================= ArenaServers_MenuDraw ================= */ static void ArenaServers_MenuDraw( void ) { if (g_arenaservers.refreshservers) ArenaServers_DoRefresh(); Menu_Draw( &g_arenaservers.menu ); } /* ================= ArenaServers_MenuKey ================= */ static sfxHandle_t ArenaServers_MenuKey( int key ) { if( key == K_SPACE && g_arenaservers.refreshservers ) { ArenaServers_StopRefresh(); return menu_move_sound; } if( ( key == K_DEL || key == K_KP_DEL ) && ( g_servertype == UIAS_FAVORITES ) && ( Menu_ItemAtCursor( &g_arenaservers.menu) == &g_arenaservers.list ) ) { ArenaServers_Remove(); ArenaServers_UpdateMenu(); return menu_move_sound; } if( key == K_MOUSE2 || key == K_ESCAPE ) { ArenaServers_StopRefresh(); ArenaServers_SaveChanges(); } return Menu_DefaultKey( &g_arenaservers.menu, key ); } /* ================= ArenaServers_MenuInit ================= */ static void ArenaServers_MenuInit( void ) { int i; int y; static char statusbuffer[MAX_STATUSLENGTH]; // zero set all our globals memset( &g_arenaservers, 0 ,sizeof(arenaservers_t) ); ArenaServers_Cache(); g_arenaservers.menu.fullscreen = qtrue; g_arenaservers.menu.wrapAround = qtrue; g_arenaservers.menu.draw = ArenaServers_MenuDraw; g_arenaservers.menu.key = ArenaServers_MenuKey; g_arenaservers.banner.generic.type = MTYPE_BTEXT; g_arenaservers.banner.generic.flags = QMF_CENTER_JUSTIFY; g_arenaservers.banner.generic.x = 320; g_arenaservers.banner.generic.y = 16; g_arenaservers.banner.string = "ARENA SERVERS"; g_arenaservers.banner.style = UI_CENTER; g_arenaservers.banner.color = color_white; y = 80; g_arenaservers.master.generic.type = MTYPE_SPINCONTROL; g_arenaservers.master.generic.name = "Servers:"; g_arenaservers.master.generic.flags = QMF_PULSEIFFOCUS|QMF_SMALLFONT; g_arenaservers.master.generic.callback = ArenaServers_Event; g_arenaservers.master.generic.id = ID_MASTER; g_arenaservers.master.generic.x = 320; g_arenaservers.master.generic.y = y; g_arenaservers.master.itemnames = master_items; y += SMALLCHAR_HEIGHT; g_arenaservers.gametype.generic.type = MTYPE_SPINCONTROL; g_arenaservers.gametype.generic.name = "Game Type:"; g_arenaservers.gametype.generic.flags = QMF_PULSEIFFOCUS|QMF_SMALLFONT; g_arenaservers.gametype.generic.callback = ArenaServers_Event; g_arenaservers.gametype.generic.id = ID_GAMETYPE; g_arenaservers.gametype.generic.x = 320; g_arenaservers.gametype.generic.y = y; g_arenaservers.gametype.itemnames = servertype_items; y += SMALLCHAR_HEIGHT; g_arenaservers.sortkey.generic.type = MTYPE_SPINCONTROL; g_arenaservers.sortkey.generic.name = "Sort By:"; g_arenaservers.sortkey.generic.flags = QMF_PULSEIFFOCUS|QMF_SMALLFONT; g_arenaservers.sortkey.generic.callback = ArenaServers_Event; g_arenaservers.sortkey.generic.id = ID_SORTKEY; g_arenaservers.sortkey.generic.x = 320; g_arenaservers.sortkey.generic.y = y; g_arenaservers.sortkey.itemnames = sortkey_items; y += SMALLCHAR_HEIGHT; g_arenaservers.showfull.generic.type = MTYPE_RADIOBUTTON; g_arenaservers.showfull.generic.name = "Show Full:"; g_arenaservers.showfull.generic.flags = QMF_PULSEIFFOCUS|QMF_SMALLFONT; g_arenaservers.showfull.generic.callback = ArenaServers_Event; g_arenaservers.showfull.generic.id = ID_SHOW_FULL; g_arenaservers.showfull.generic.x = 320; g_arenaservers.showfull.generic.y = y; y += SMALLCHAR_HEIGHT; g_arenaservers.showempty.generic.type = MTYPE_RADIOBUTTON; g_arenaservers.showempty.generic.name = "Show Empty:"; g_arenaservers.showempty.generic.flags = QMF_PULSEIFFOCUS|QMF_SMALLFONT; g_arenaservers.showempty.generic.callback = ArenaServers_Event; g_arenaservers.showempty.generic.id = ID_SHOW_EMPTY; g_arenaservers.showempty.generic.x = 320; g_arenaservers.showempty.generic.y = y; y += 3 * SMALLCHAR_HEIGHT; g_arenaservers.list.generic.type = MTYPE_SCROLLLIST; g_arenaservers.list.generic.flags = QMF_HIGHLIGHT_IF_FOCUS; g_arenaservers.list.generic.id = ID_LIST; g_arenaservers.list.generic.callback = ArenaServers_Event; g_arenaservers.list.generic.x = 72; g_arenaservers.list.generic.y = y; g_arenaservers.list.width = MAX_LISTBOXWIDTH; g_arenaservers.list.height = 11; g_arenaservers.list.itemnames = (const char **)g_arenaservers.items; for( i = 0; i < MAX_LISTBOXITEMS; i++ ) { g_arenaservers.items[i] = g_arenaservers.table[i].buff; } g_arenaservers.mappic.generic.type = MTYPE_BITMAP; g_arenaservers.mappic.generic.flags = QMF_LEFT_JUSTIFY|QMF_INACTIVE; g_arenaservers.mappic.generic.x = 72; g_arenaservers.mappic.generic.y = 80; g_arenaservers.mappic.width = 128; g_arenaservers.mappic.height = 96; g_arenaservers.mappic.errorpic = ART_UNKNOWNMAP; g_arenaservers.arrows.generic.type = MTYPE_BITMAP; g_arenaservers.arrows.generic.name = ART_ARROWS0; g_arenaservers.arrows.generic.flags = QMF_LEFT_JUSTIFY|QMF_INACTIVE; g_arenaservers.arrows.generic.callback = ArenaServers_Event; g_arenaservers.arrows.generic.x = 512+48; g_arenaservers.arrows.generic.y = 240-64+16; g_arenaservers.arrows.width = 64; g_arenaservers.arrows.height = 128; g_arenaservers.up.generic.type = MTYPE_BITMAP; g_arenaservers.up.generic.flags = QMF_LEFT_JUSTIFY|QMF_PULSEIFFOCUS|QMF_MOUSEONLY; g_arenaservers.up.generic.callback = ArenaServers_Event; g_arenaservers.up.generic.id = ID_SCROLL_UP; g_arenaservers.up.generic.x = 512+48; g_arenaservers.up.generic.y = 240-64+16; g_arenaservers.up.width = 64; g_arenaservers.up.height = 64; g_arenaservers.up.focuspic = ART_ARROWS_UP; g_arenaservers.down.generic.type = MTYPE_BITMAP; g_arenaservers.down.generic.flags = QMF_LEFT_JUSTIFY|QMF_PULSEIFFOCUS|QMF_MOUSEONLY; g_arenaservers.down.generic.callback = ArenaServers_Event; g_arenaservers.down.generic.id = ID_SCROLL_DOWN; g_arenaservers.down.generic.x = 512+48; g_arenaservers.down.generic.y = 240+16; g_arenaservers.down.width = 64; g_arenaservers.down.height = 64; g_arenaservers.down.focuspic = ART_ARROWS_DOWN; y = 376; g_arenaservers.status.generic.type = MTYPE_TEXT; g_arenaservers.status.generic.x = 320; g_arenaservers.status.generic.y = y; g_arenaservers.status.string = statusbuffer; g_arenaservers.status.style = UI_CENTER|UI_SMALLFONT; g_arenaservers.status.color = menu_text_color; y += SMALLCHAR_HEIGHT; g_arenaservers.statusbar.generic.type = MTYPE_TEXT; g_arenaservers.statusbar.generic.x = 320; g_arenaservers.statusbar.generic.y = y; g_arenaservers.statusbar.string = ""; g_arenaservers.statusbar.style = UI_CENTER|UI_SMALLFONT; g_arenaservers.statusbar.color = text_color_normal; g_arenaservers.remove.generic.type = MTYPE_BITMAP; g_arenaservers.remove.generic.name = ART_REMOVE0; g_arenaservers.remove.generic.flags = QMF_LEFT_JUSTIFY|QMF_PULSEIFFOCUS; g_arenaservers.remove.generic.callback = ArenaServers_Event; g_arenaservers.remove.generic.id = ID_REMOVE; g_arenaservers.remove.generic.x = 450; g_arenaservers.remove.generic.y = 86; g_arenaservers.remove.width = 96; g_arenaservers.remove.height = 48; g_arenaservers.remove.focuspic = ART_REMOVE1; g_arenaservers.back.generic.type = MTYPE_BITMAP; g_arenaservers.back.generic.name = ART_BACK0; g_arenaservers.back.generic.flags = QMF_LEFT_JUSTIFY|QMF_PULSEIFFOCUS; g_arenaservers.back.generic.callback = ArenaServers_Event; g_arenaservers.back.generic.id = ID_BACK; g_arenaservers.back.generic.x = 0; g_arenaservers.back.generic.y = 480-64; g_arenaservers.back.width = 128; g_arenaservers.back.height = 64; g_arenaservers.back.focuspic = ART_BACK1; g_arenaservers.specify.generic.type = MTYPE_BITMAP; g_arenaservers.specify.generic.name = ART_SPECIFY0; g_arenaservers.specify.generic.flags = QMF_LEFT_JUSTIFY|QMF_PULSEIFFOCUS; g_arenaservers.specify.generic.callback = ArenaServers_Event; g_arenaservers.specify.generic.id = ID_SPECIFY; g_arenaservers.specify.generic.x = 128; g_arenaservers.specify.generic.y = 480-64; g_arenaservers.specify.width = 128; g_arenaservers.specify.height = 64; g_arenaservers.specify.focuspic = ART_SPECIFY1; g_arenaservers.refresh.generic.type = MTYPE_BITMAP; g_arenaservers.refresh.generic.name = ART_REFRESH0; g_arenaservers.refresh.generic.flags = QMF_LEFT_JUSTIFY|QMF_PULSEIFFOCUS; g_arenaservers.refresh.generic.callback = ArenaServers_Event; g_arenaservers.refresh.generic.id = ID_REFRESH; g_arenaservers.refresh.generic.x = 256; g_arenaservers.refresh.generic.y = 480-64; g_arenaservers.refresh.width = 128; g_arenaservers.refresh.height = 64; g_arenaservers.refresh.focuspic = ART_REFRESH1; g_arenaservers.create.generic.type = MTYPE_BITMAP; g_arenaservers.create.generic.name = ART_CREATE0; g_arenaservers.create.generic.flags = QMF_LEFT_JUSTIFY|QMF_PULSEIFFOCUS; g_arenaservers.create.generic.callback = ArenaServers_Event; g_arenaservers.create.generic.id = ID_CREATE; g_arenaservers.create.generic.x = 384; g_arenaservers.create.generic.y = 480-64; g_arenaservers.create.width = 128; g_arenaservers.create.height = 64; g_arenaservers.create.focuspic = ART_CREATE1; g_arenaservers.go.generic.type = MTYPE_BITMAP; g_arenaservers.go.generic.name = ART_CONNECT0; g_arenaservers.go.generic.flags = QMF_RIGHT_JUSTIFY|QMF_PULSEIFFOCUS; g_arenaservers.go.generic.callback = ArenaServers_Event; g_arenaservers.go.generic.id = ID_CONNECT; g_arenaservers.go.generic.x = 640; g_arenaservers.go.generic.y = 480-64; g_arenaservers.go.width = 128; g_arenaservers.go.height = 64; g_arenaservers.go.focuspic = ART_CONNECT1; g_arenaservers.punkbuster.generic.type = MTYPE_SPINCONTROL; g_arenaservers.punkbuster.generic.name = "Punkbuster:"; g_arenaservers.punkbuster.generic.flags = QMF_PULSEIFFOCUS|QMF_SMALLFONT; g_arenaservers.punkbuster.generic.callback = ArenaServers_Event; g_arenaservers.punkbuster.generic.id = ID_PUNKBUSTER; g_arenaservers.punkbuster.generic.x = 480+32; g_arenaservers.punkbuster.generic.y = 144; g_arenaservers.punkbuster.itemnames = punkbuster_items; g_arenaservers.pblogo.generic.type = MTYPE_BITMAP; g_arenaservers.pblogo.generic.name = ART_PUNKBUSTER; g_arenaservers.pblogo.generic.flags = QMF_LEFT_JUSTIFY|QMF_INACTIVE; g_arenaservers.pblogo.generic.x = 526; g_arenaservers.pblogo.generic.y = 176; g_arenaservers.pblogo.width = 32; g_arenaservers.pblogo.height = 16; g_arenaservers.pblogo.errorpic = ART_UNKNOWNMAP; Menu_AddItem( &g_arenaservers.menu, (void*) &g_arenaservers.banner ); Menu_AddItem( &g_arenaservers.menu, (void*) &g_arenaservers.master ); Menu_AddItem( &g_arenaservers.menu, (void*) &g_arenaservers.gametype ); Menu_AddItem( &g_arenaservers.menu, (void*) &g_arenaservers.sortkey ); Menu_AddItem( &g_arenaservers.menu, (void*) &g_arenaservers.showfull); Menu_AddItem( &g_arenaservers.menu, (void*) &g_arenaservers.showempty ); Menu_AddItem( &g_arenaservers.menu, (void*) &g_arenaservers.mappic ); Menu_AddItem( &g_arenaservers.menu, (void*) &g_arenaservers.status ); Menu_AddItem( &g_arenaservers.menu, (void*) &g_arenaservers.statusbar ); Menu_AddItem( &g_arenaservers.menu, (void*) &g_arenaservers.arrows ); Menu_AddItem( &g_arenaservers.menu, (void*) &g_arenaservers.up ); Menu_AddItem( &g_arenaservers.menu, (void*) &g_arenaservers.down ); Menu_AddItem( &g_arenaservers.menu, (void*) &g_arenaservers.list ); Menu_AddItem( &g_arenaservers.menu, (void*) &g_arenaservers.remove ); Menu_AddItem( &g_arenaservers.menu, (void*) &g_arenaservers.back ); Menu_AddItem( &g_arenaservers.menu, (void*) &g_arenaservers.specify ); Menu_AddItem( &g_arenaservers.menu, (void*) &g_arenaservers.refresh ); Menu_AddItem( &g_arenaservers.menu, (void*) &g_arenaservers.create ); Menu_AddItem( &g_arenaservers.menu, (void*) &g_arenaservers.go ); Menu_AddItem( &g_arenaservers.menu, (void*) &g_arenaservers.punkbuster ); Menu_AddItem( &g_arenaservers.menu, (void*) &g_arenaservers.pblogo ); ArenaServers_LoadFavorites(); g_arenaservers.master.curvalue = g_servertype = Com_Clamp( 0, 6, ui_browserMaster.integer ); g_gametype = Com_Clamp( 0, 4, ui_browserGameType.integer ); g_arenaservers.gametype.curvalue = g_gametype; g_sortkey = Com_Clamp( 0, 4, ui_browserSortKey.integer ); g_arenaservers.sortkey.curvalue = g_sortkey; g_fullservers = Com_Clamp( 0, 1, ui_browserShowFull.integer ); g_arenaservers.showfull.curvalue = g_fullservers; g_emptyservers = Com_Clamp( 0, 1, ui_browserShowEmpty.integer ); g_arenaservers.showempty.curvalue = g_emptyservers; g_arenaservers.punkbuster.curvalue = Com_Clamp( 0, 1, trap_Cvar_VariableValue( "cl_punkbuster" ) ); // force to initial state and refresh g_arenaservers.master.curvalue = g_servertype = ArenaServers_SetType(g_servertype); trap_Cvar_Register(NULL, "debug_protocol", "", 0 ); } /* ================= ArenaServers_Cache ================= */ void ArenaServers_Cache( void ) { trap_R_RegisterShaderNoMip( ART_BACK0 ); trap_R_RegisterShaderNoMip( ART_BACK1 ); trap_R_RegisterShaderNoMip( ART_CREATE0 ); trap_R_RegisterShaderNoMip( ART_CREATE1 ); trap_R_RegisterShaderNoMip( ART_SPECIFY0 ); trap_R_RegisterShaderNoMip( ART_SPECIFY1 ); trap_R_RegisterShaderNoMip( ART_REFRESH0 ); trap_R_RegisterShaderNoMip( ART_REFRESH1 ); trap_R_RegisterShaderNoMip( ART_CONNECT0 ); trap_R_RegisterShaderNoMip( ART_CONNECT1 ); trap_R_RegisterShaderNoMip( ART_ARROWS0 ); trap_R_RegisterShaderNoMip( ART_ARROWS_UP ); trap_R_RegisterShaderNoMip( ART_ARROWS_DOWN ); trap_R_RegisterShaderNoMip( ART_UNKNOWNMAP ); trap_R_RegisterShaderNoMip( ART_PUNKBUSTER ); } /* ================= UI_ArenaServersMenu ================= */ void UI_ArenaServersMenu( void ) { ArenaServers_MenuInit(); UI_PushMenu( &g_arenaservers.menu ); }