/* =========================================================================== 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 Foobar; 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 AS_LOCAL 0 #define AS_MPLAYER 1 #define AS_GLOBAL 2 #define AS_FAVORITES 3 #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", "Internet", "Favorites", 0 }; static const char *servertype_items[] = { "All", "Free For All", "Team Deathmatch", "Tournament", "Capture the Flag", 0 }; static const char *sortkey_items[] = { "Server Name", "Map Name", "Open Player Spots", "Game Type", "Ping Time", 0 }; 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 0 }; static char* netnames[] = { "???", "UDP", "IPX", 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[MAX_GLOBALSERVERS]; static int g_numglobalservers; static servernode_t g_localserverlist[MAX_LOCALSERVERS]; static int g_numlocalservers; static servernode_t g_favoriteserverlist[MAX_FAVORITESERVERS]; static int g_numfavoriteservers; static servernode_t g_mplayerserverlist[MAX_GLOBALSERVERS]; static int g_nummplayerservers; 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_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 == AS_GLOBAL || g_servertype == AS_MPLAYER ) { 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 == AS_GLOBAL || g_servertype == AS_MPLAYER ) { 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 %3s %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)) break; // delete address from master list if (i <= g_arenaservers.numfavoriteaddresses-1) { if (i < g_arenaservers.numfavoriteaddresses-1) { // shift items up memcpy( &g_arenaservers.favoriteaddresses[i], &g_arenaservers.favoriteaddresses[i+1], (g_arenaservers.numfavoriteaddresses - i - 1)*sizeof(MAX_ADDRESSLENGTH)); } g_arenaservers.numfavoriteaddresses--; } // find address in server list for (i=0; i= ArenaServers_MaxPing()) && (g_servertype != AS_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")); 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 emptyinfo[MAX_INFO_STRING]; char adrstr[MAX_ADDRESSLENGTH]; servernode_t templist[MAX_FAVORITESERVERS]; qboolean found; found = qfalse; emptyinfo[0] = '\0'; // 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 '9') continue; // favorite server addresses must be maintained outside refresh list // this mimics local and global netadr's stored in client // these can be fetched to fill ping list strcpy( g_arenaservers.favoriteaddresses[g_numfavoriteservers], adrstr ); // find this server in the old list for (j=0; j= 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 != AS_FAVORITES) { if (g_servertype == AS_LOCAL) { if (!trap_LAN_GetServerCount(g_servertype)) { return; } } if (trap_LAN_GetServerCount(g_servertype) < 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 == AS_FAVORITES) { g_arenaservers.numqueriedservers = g_arenaservers.numfavoriteaddresses; } else { g_arenaservers.numqueriedservers = trap_LAN_GetServerCount(g_servertype); } // 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 == AS_FAVORITES) { strcpy( adrstr, g_arenaservers.favoriteaddresses[g_arenaservers.currentping] ); } else { trap_LAN_GetServerAddressString(g_servertype, 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; iid; if( event != QM_ACTIVATED && id != ID_LIST ) { return; } switch( id ) { case ID_MASTER: value = g_arenaservers.master.curvalue; if (value >= 1) { value++; } trap_Cvar_SetValue( "ui_browserMaster", value ); ArenaServers_SetType( value ); 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, (voidfunc_f)NULL, Punkbuster_ConfirmEnable ); } else { UI_ConfirmMenu_Style( "Disable Punkbuster?", UI_CENTER|UI_INVERSE|UI_SMALLFONT, (voidfunc_f)NULL, 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 == AS_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 type; int y; int value; 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.list ); 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.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_servertype = Com_Clamp( 0, 3, ui_browserMaster.integer ); // hack to get rid of MPlayer stuff value = g_servertype; if (value >= 1) value--; g_arenaservers.master.curvalue = value; 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 type = g_servertype; g_servertype = -1; ArenaServers_SetType( type ); 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 ); }