#include "quakedef.h" #ifdef CL_MASTER #include "cl_master.h" //filtering cvar_t sb_hideempty = SCVARF("sb_hideempty", "0", CVAR_ARCHIVE); cvar_t sb_hidenotempty = SCVARF("sb_hidenotempty", "0", CVAR_ARCHIVE); cvar_t sb_hidefull = SCVARF("sb_hidefull", "0", CVAR_ARCHIVE); cvar_t sb_hidedead = SCVARF("sb_hidedead", "1", CVAR_ARCHIVE); cvar_t sb_hidequake2 = SCVARF("sb_hidequake2", "1", CVAR_ARCHIVE); cvar_t sb_hidequake3 = SCVARF("sb_hidequake3", "1", CVAR_ARCHIVE); cvar_t sb_hidenetquake = SCVARF("sb_hidenetquake", "1", CVAR_ARCHIVE); cvar_t sb_hidequakeworld = SCVARF("sb_hidequakeworld","0", CVAR_ARCHIVE); cvar_t sb_maxping = SCVARF("sb_maxping", "0", CVAR_ARCHIVE); cvar_t sb_gamedir = SCVARF("sb_gamedir", "", CVAR_ARCHIVE); cvar_t sb_mapname = SCVARF("sb_mapname", "", CVAR_ARCHIVE); cvar_t sb_showping = SCVARF("sb_showping", "1", CVAR_ARCHIVE); cvar_t sb_showaddress = SCVARF("sb_showaddress", "0", CVAR_ARCHIVE); cvar_t sb_showmap = SCVARF("sb_showmap", "0", CVAR_ARCHIVE); cvar_t sb_showgamedir = SCVARF("sb_showgamedir", "0", CVAR_ARCHIVE); cvar_t sb_showplayers = SCVARF("sb_showplayers", "1", CVAR_ARCHIVE); cvar_t sb_showfraglimit = SCVARF("sb_showfraglimit","0", CVAR_ARCHIVE); cvar_t sb_showtimelimit = SCVARF("sb_showtimelimit","0", CVAR_ARCHIVE); cvar_t sb_filterkey = SCVARF("sb_filterkey", "hostname", CVAR_ARCHIVE); cvar_t sb_filtervalue = SCVARF("sb_filtervalue", "", CVAR_ARCHIVE); extern cvar_t slist_writeserverstxt; extern cvar_t slist_cacheinfo; void M_Serverlist_Init(void) { char *grp = "Server Browser Vars"; Cvar_Register(&sb_hideempty, grp); Cvar_Register(&sb_hidenotempty, grp); Cvar_Register(&sb_hidefull, grp); Cvar_Register(&sb_hidedead, grp); Cvar_Register(&sb_hidequake2, grp); Cvar_Register(&sb_hidequake3, grp); Cvar_Register(&sb_hidenetquake, grp); Cvar_Register(&sb_hidequakeworld, grp); Cvar_Register(&sb_maxping, grp); Cvar_Register(&sb_gamedir, grp); Cvar_Register(&sb_mapname, grp); Cvar_Register(&sb_showping, grp); Cvar_Register(&sb_showaddress, grp); Cvar_Register(&sb_showmap, grp); Cvar_Register(&sb_showgamedir, grp); Cvar_Register(&sb_showplayers, grp); Cvar_Register(&sb_showfraglimit, grp); Cvar_Register(&sb_showtimelimit, grp); Cvar_Register(&slist_writeserverstxt, grp); Cvar_Register(&slist_cacheinfo, grp); } enum { SLISTTYPE_SERVERS, SLISTTYPE_FAVORITES, SLISTTYPE_SOURCES, SLISTTYPE_OPTIONS //must be last } slist_option; int slist_numoptions; int slist_firstoption; int slist_type; static void NM_Print (int cx, int cy, qbyte *str) { Draw_AltFunString(cx, cy, str); } static void NM_PrintWhite (int cx, int cy, qbyte *str) { Draw_FunString(cx, cy, str); } static void NM_PrintColoured (int cx, int cy, int colour, qbyte *str) { #ifdef _MSC_VER #pragma message("NM_PrintColoured: needs reimplementing") #endif /* while (*str) { NM_DrawColouredCharacter (cx, cy, (*str) | (colour<special & SS_FAVORITE)) return true; #ifdef Q2CLIENT if (sb_hidequake2.value) #endif if (server->special & SS_QUAKE2) return true; #ifdef Q2CLIENT if (sb_hidequake3.value) #endif if (server->special & SS_QUAKE3) return true; #ifdef NQPROT if (sb_hidenetquake.value) #endif if (server->special & (SS_NETQUAKE|SS_DARKPLACES)) return true; if (sb_hidequakeworld.value) if (!(server->special & (SS_QUAKE2|SS_QUAKE3|SS_NETQUAKE|SS_DARKPLACES))) return true; if (sb_hideempty.value) if (!server->players) return true; if (sb_hidenotempty.value) if (server->players) return true; if (sb_hidefull.value) if (server->players == server->maxplayers) return true; if (sb_hidedead.value) if (server->maxplayers == 0) return true; if (sb_maxping.value) if (server->ping > sb_maxping.value) return true; if (*sb_gamedir.string) if (strcmp(server->gamedir, sb_gamedir.string)) return true; if (*sb_mapname.string) if (!strstr(server->map, sb_mapname.string)) return true; return false; } qboolean M_MasterIsFiltered(master_t *mast) { #ifndef Q2CLIENT if (mast->type == MT_BCASTQ2 || mast->type == MT_SINGLEQ2 || mast->type == MT_MASTERQ2) return true; #endif #ifndef NQPROT if (mast->type == MT_BCASTNQ || mast->type == MT_SINGLENQ) return true; #endif return false; } void M_DrawOneServer (int inity) { char key[512]; char value[512]; char *o; int l, i; char *s; int miny=8*5; int y=8*(5-selectedserver.linenum); miny += inity; y += inity; if (!selectedserver.detail) { NM_Print (0, y, "No details\n"); return; } s = selectedserver.detail->info; if (*s == '\\') s++; while (*s) { o = key; while (*s && *s != '\\') *o++ = *s++; l = o - key; // if (l < 20) // { // memset (o, ' ', 20-l); // key[20] = 0; // } // else *o = 0; if (y>=miny) NM_Print (0, y, va("%19s", key)); if (!*s) { if (y>=miny) NM_Print (0, y, "MISSING VALUE\n"); return; } o = value; s++; while (*s && *s != '\\') *o++ = *s++; *o = 0; if (*s) s++; if (y>=miny) NM_Print (320/2, y, va("%s\n", value)); y+=8; } for ( i = 0; i < selectedserver.detail->numplayers; i++) { if (y>=miny) { R2D_ImagePaletteColour(Sbar_ColorForMap(selectedserver.detail->players[i].topc), 1.0); R2D_FillBlock (12, y, 28, 4); R2D_ImagePaletteColour(Sbar_ColorForMap(selectedserver.detail->players[i].botc), 1.0); R2D_FillBlock (12, y+4, 28, 4); R2D_ImageColours(1.0, 1.0, 1.0, 1.0); NM_PrintWhite (12, y, va("%3i", selectedserver.detail->players[i].frags)); NM_Print (12+8*4, y, selectedserver.detail->players[i].name); } y+=8; } if (y<=miny) //whoops, there was a hole at the end, try scrolling up. selectedserver.linenum--; } int M_AddColumn (int right, int y, char *text, int maxchars, int colour, int highlight) { int left; left = right - maxchars*8; if (left < 0) return right; right = left; #ifdef _MSC_VER #pragma message("M_AddColumn: needs reimplementing") #endif /* if (highlight >= 0) { while (*text && maxchars>0) { NM_DrawColouredCharacter (right, y, (*(unsigned char *)text) | (colour<0) { NM_DrawColouredCharacter (right, y, (*(unsigned char *)text) | (colour<next) if (M_IsFiltered(server)) filtered++; else slist_numoptions++; if (!slist_numoptions) { char *text, *text2="", *text3=""; if (filtered) { if (slist_type == SLISTTYPE_FAVORITES) { text = "Highlight a server"; text2 = "and press \'f\'"; text3 = "to add it to this list"; } else text = "All servers were filtered out"; } else text = "No servers found"; NM_PrintColoured((vid.width-strlen(text)*8)/2, 8*5, COLOR_WHITE, text); NM_PrintColoured((vid.width-strlen(text2)*8)/2, 8*5+8, COLOR_WHITE, text2); NM_PrintColoured((vid.width-strlen(text3)*8)/2, 8*5+16, COLOR_WHITE, text3); return; } if (slist_option >= slist_numoptions) slist_option = slist_numoptions-1; op = vid.height/2/8; op/=2; op=slist_option-op; snum = op; if (selectedserver.inuse == true) { M_DrawOneServer(8*5); return; } if (op < 0) op = 0; if (snum < 0) snum = 0; //find the server that we want for (server = firstserver; op>0; server=server->next) { if (M_IsFiltered(server)) continue; op--; } y = 8*2; x = vid.width; if (sb_showtimelimit.value) x = M_AddColumn(x, y, "tl", 3, COLOR_RED, -1); if (sb_showfraglimit.value) x = M_AddColumn(x, y, "fl", 3, COLOR_RED, -1); if (sb_showplayers.value) x = M_AddColumn(x, y, "plyrs", 6, COLOR_RED, -1); if (sb_showmap.value) x = M_AddColumn(x, y, "map", 9, COLOR_RED, -1); if (sb_showgamedir.value) x = M_AddColumn(x, y, "gamedir", 9, COLOR_RED, -1); if (sb_showping.value) x = M_AddColumn(x, y, "png", 4, COLOR_RED, -1); if (sb_showaddress.value) x = M_AddColumn(x, y, "address", 21, COLOR_RED, -1); x = M_AddColumn(x, y, "name", x/8-1, COLOR_RED, -1); y = 8*3; while(server) { if (M_IsFiltered(server)) { server = server->next; continue; //doesn't count } if (y > vid.height/2) break; if (slist_option == snum) highlight = COLOR_DARKBLUE; else highlight = -1; if (*server->name) { if (server->special & SS_FAVORITE) colour = COLOR_GREEN; else if (server->special & SS_FTESERVER) colour = COLOR_RED; else if (server->special & SS_QUAKE2) colour = COLOR_YELLOW; else if (server->special & SS_QUAKE3) colour = COLOR_BLUE; else if (server->special & SS_NETQUAKE) colour = COLOR_GREY; else if (server->special & SS_PROXY) colour = COLOR_MAGENTA; else colour = COLOR_WHITE; x = vid.width; // make sure we have a highlighted background if (highlight >= 0) { R2D_ImageColours(consolecolours[highlight].fr, consolecolours[highlight].fg, consolecolours[highlight].fb, 1.0); R2D_FillBlock(8, y, vid.width-16, 8); } if (sb_showtimelimit.value) x = M_AddColumn(x, y, va("%i", server->tl), 3, colour, highlight); //time limit if (sb_showfraglimit.value) x = M_AddColumn(x, y, va("%i", server->fl), 3, colour, highlight); //frag limit if (sb_showplayers.value) x = M_AddColumn(x, y, va("%i/%i", server->players, server->maxplayers), 6, colour, highlight); if (sb_showmap.value) x = M_AddColumn(x, y, server->map, 9, colour, highlight); if (sb_showgamedir.value) x = M_AddColumn(x, y, server->gamedir, 9, colour, highlight); if (sb_showping.value) x = M_AddColumn(x, y, va("%i", server->ping), 4, colour, highlight); //frag limit if (sb_showaddress.value) x = M_AddColumn(x, y, NET_AdrToString(adr, sizeof(adr), server->adr), 21, colour, highlight); x = M_AddColumn(x, y, server->name, x/8-1, colour, highlight); } if (*server->name) y+=8; server = server->next; snum++; } selectedserver.inuse=2; M_DrawOneServer(vid.height/2-4*8); } void M_DrawSources (void) { int snum=0; int op; int y = 3*8; master_t *mast; int clr; slist_numoptions = 0; //find total sources. for (mast = master; mast; mast = mast->next) slist_numoptions++; if (!slist_numoptions) { char *text; if (0)//filtered) text = "All servers were filtered out\n"; else text = "No sources were found\n"; NM_PrintColoured((vid.width-strlen(text)*8)/2, 8*5, COLOR_WHITE, text); return; } if (slist_option >= slist_numoptions) slist_option = slist_numoptions-1; op=slist_option-vid.height/2/8; snum = op; if (op < 0) op = 0; if (snum < 0) snum = 0; //find the server that we want for (mast = master; op>0; mast=mast->next) { if (M_MasterIsFiltered(mast)) continue; op--; } for (; mast; mast = mast->next) { if (M_MasterIsFiltered(mast)) continue; switch (mast->type) { case MT_MASTERHTTPNQ: case MT_MASTERHTTPQW: clr = COLOR_YELLOW; break; case MT_MASTERQW: case MT_MASTERQ2: clr = COLOR_WHITE; break; case MT_SINGLENQ: case MT_SINGLEQW: case MT_SINGLEQ2: clr = COLOR_GREEN; break; default: clr = COLOR_RED; } if (slist_option == snum) // highlight it if selected NM_PrintHighlighted(46, y, clr, COLOR_DARKBLUE, va("%s", mast->name)); else NM_PrintColoured(46, y, clr, va("%s", mast->name)); y+=8; snum++; } } #define NUMSLISTOPTIONS (8+7+4) struct { char *title; cvar_t *cvar; int type; } options[NUMSLISTOPTIONS] = { {"Hide Empty", &sb_hideempty}, {"Hide Not Empty", &sb_hidenotempty}, {"Hide Full", &sb_hidefull}, {"Hide Dead", &sb_hidedead}, {"Hide Quake 2", &sb_hidequake2}, {"Hide Quake 3", &sb_hidequake3}, {"Hide Quake 1", &sb_hidenetquake}, {"Hide QuakeWorld", &sb_hidequakeworld}, {"Show pings", &sb_showping}, {"Show Addresses", &sb_showaddress}, {"Show map", &sb_showmap}, {"Show Game Dir", &sb_showgamedir}, {"Show Players", &sb_showplayers}, {"Show Fraglimit", &sb_showfraglimit}, {"Show Timelimit", &sb_showtimelimit}, {"Max ping", &sb_maxping, 1}, {"GameDir", &sb_gamedir, 2}, {"Using map", &sb_mapname, 2}, {"Game name", &com_gamename, 2} }; void M_DrawSListOptions (void) { int c; int op; char *s; slist_numoptions = NUMSLISTOPTIONS; for (op = 0; op < NUMSLISTOPTIONS; op++) { if (options[op].cvar->value>0 || (*options[op].cvar->string && *options[op].cvar->string != '0')) c = COLOR_RED; else c = COLOR_WHITE; switch(options[op].type) { default: s = options[op].title; break; case 1: if (!options[op].cvar->value) { s = va("%s ", options[op].title); break; } case 2: s = va("%s %s", options[op].title, options[op].cvar->string); break; } if (slist_option == op) // selected NM_PrintHighlighted(46, op*8+8*3, c, COLOR_DARKBLUE, s); else NM_PrintColoured(46, op*8+8*3, c, s); } } void M_SListOptions_Key (int key) { if (key == K_UPARROW) { slist_option--; if (slist_option<0) slist_option=0; return; } else if (key == K_DOWNARROW) { slist_option++; if (slist_option >= slist_numoptions) slist_option = slist_numoptions-1; return; } switch(options[slist_option].type) { default: if (key == K_ENTER) { if (options[slist_option].cvar->value) Cvar_Set(options[slist_option].cvar, "0"); else Cvar_Set(options[slist_option].cvar, "1"); } break; case 1: if (key >= '0' && key <= '9') Cvar_SetValue(options[slist_option].cvar, options[slist_option].cvar->value*10+key-'0'); else if (key == K_DEL) Cvar_SetValue(options[slist_option].cvar, 0); else if (key == K_BACKSPACE) Cvar_SetValue(options[slist_option].cvar, (int)options[slist_option].cvar->value/10); break; case 2: if ((key >= '0' && key <= '9') || (key >= 'a' && key <= 'z') || (key >= 'A' && key <= 'Z')|| key == '_') Cvar_Set(options[slist_option].cvar, va("%s%c", options[slist_option].cvar->string, key)); else if (key == K_DEL) Cvar_Set(options[slist_option].cvar, ""); else if (key == K_BACKSPACE) //FIXME Cvar_Set(options[slist_option].cvar, ""); break; } } void M_DrawServers(void) { #define NUMSLISTHEADERS (SLISTTYPE_OPTIONS+1) char *titles[NUMSLISTHEADERS] = { "Servers", "Favorites", "Sources", // "Players", "Options" }; int snum=0; int width, lofs; NET_CheckPollSockets(); //see if we were told something important. width = vid.width / NUMSLISTHEADERS; lofs = width/2 - 7*4; for (snum = 0; snum < NUMSLISTHEADERS; snum++) { if (slist_type == snum) NM_PrintHighlighted(width*snum+width/2 - strlen(titles[snum])*4, 0, COLOR_WHITE, COLOR_DARKBLUE, titles[snum]); else NM_PrintColoured(width*snum+width/2 - strlen(titles[snum])*4, 0, COLOR_WHITE, titles[snum]); } NM_PrintColoured(8, 8, COLOR_WHITE, "\35"); for (snum = 16; snum < vid.width-16; snum+=8) NM_PrintColoured(snum, 8, COLOR_WHITE, "\36"); NM_PrintColoured(snum, 8, COLOR_WHITE, "\37"); switch(slist_type) { case SLISTTYPE_SERVERS: case SLISTTYPE_FAVORITES: M_DrawServerList(); break; case SLISTTYPE_SOURCES: M_DrawSources (); break; case SLISTTYPE_OPTIONS: M_DrawSListOptions (); break; } } serverinfo_t *M_FindCurrentServer(void) { serverinfo_t *server; int op = slist_option; for (server = firstserver; server; server = server->next) { if (M_IsFiltered(server)) continue; //doesn't count if (!op--) return server; } return NULL; } master_t *M_FindCurrentMaster(void) { master_t *mast; int op = slist_option; for (mast = master; mast; mast = mast->next) { if (M_MasterIsFiltered(mast)) continue; if (!op--) return mast; } return NULL; } void M_SListKey(int key) { char adr[MAX_ADR_SIZE]; if (key == K_ESCAPE) { // if (selectedserver.inuse) // selectedserver.inuse = false; // else M_Menu_MultiPlayer_f(); return; } else if (key == K_LEFTARROW) { slist_type--; if (slist_type<0) slist_type=0; selectedserver.linenum--; if (selectedserver.linenum<0) selectedserver.linenum=0; slist_numoptions=0; return; } else if (key == K_RIGHTARROW) { slist_type++; if (slist_type>NUMSLISTHEADERS-1) slist_type=NUMSLISTHEADERS-1; selectedserver.linenum++; slist_numoptions = 0; return; } else if (key == 'q') selectedserver.linenum--; else if (key == 'a') selectedserver.linenum++; if (!slist_numoptions) return; if (slist_type == SLISTTYPE_OPTIONS) { M_SListOptions_Key(key); return; } if (key == K_UPARROW) { slist_option--; if (slist_option<0) slist_option=0; if (slist_type == SLISTTYPE_SERVERS || slist_type == SLISTTYPE_FAVORITES) SListOptionChanged(M_FindCurrentServer()); //go for these early. } else if (key == K_DOWNARROW) { slist_option++; if (slist_option >= slist_numoptions) slist_option = slist_numoptions-1; if (slist_type == SLISTTYPE_SERVERS || slist_type == SLISTTYPE_FAVORITES) SListOptionChanged(M_FindCurrentServer()); //go for these early. } else if (key == K_PGDN) { slist_option+=10; if (slist_option >= slist_numoptions) slist_option = slist_numoptions-1; if (slist_type == SLISTTYPE_SERVERS || slist_type == SLISTTYPE_FAVORITES) SListOptionChanged(M_FindCurrentServer()); //go for these early. } else if (key == K_PGUP) { slist_option-=10; if (slist_option<0) slist_option=0; if (slist_type == SLISTTYPE_SERVERS || slist_type == SLISTTYPE_FAVORITES) SListOptionChanged(M_FindCurrentServer()); //go for these early. } else if (key == 'r') MasterInfo_Refresh(); else if (key == K_SPACE) { if (slist_type == SLISTTYPE_SERVERS || slist_type == SLISTTYPE_FAVORITES) { selectedserver.inuse = !selectedserver.inuse; if (selectedserver.inuse) SListOptionChanged(M_FindCurrentServer()); } } else if (key == 'c') { Sys_SaveClipboard(NET_AdrToString(adr, sizeof(adr), selectedserver.adr)); } else if (key == 'f') { serverinfo_t *server; if (slist_type == SLISTTYPE_SERVERS) //add to favorites { server = M_FindCurrentServer(); if (server) { server->special |= SS_FAVORITE; MasterInfo_WriteServers(); } } if (slist_type == SLISTTYPE_FAVORITES) //remove from favorites { server = M_FindCurrentServer(); if (server) { server->special &= ~SS_FAVORITE; MasterInfo_WriteServers(); } } } else if (key==K_ENTER || key == 's' || key == 'j') { serverinfo_t *server; if (slist_type == SLISTTYPE_SERVERS || slist_type == SLISTTYPE_FAVORITES) { if (!selectedserver.inuse) { selectedserver.inuse = true; SListOptionChanged(M_FindCurrentServer()); return; } server = M_FindCurrentServer(); if (!server) return; //ah. off the end. if (key == 's') Cbuf_AddText("spectator 1\n", RESTRICT_LOCAL); else if (key == 'j') Cbuf_AddText("spectator 0\n", RESTRICT_LOCAL); if (server->special & SS_NETQUAKE) Cbuf_AddText(va("nqconnect %s\n", NET_AdrToString(adr, sizeof(adr), server->adr)), RESTRICT_LOCAL); else Cbuf_AddText(va("connect %s\n", NET_AdrToString(adr, sizeof(adr), server->adr)), RESTRICT_LOCAL); M_ToggleMenu_f(); M_ToggleMenu_f(); } else if (slist_type == SLISTTYPE_SOURCES) { MasterInfo_Request(M_FindCurrentMaster(), true); } return; } } typedef struct { int visibleslots; int scrollpos; int selectedpos; int numslots; qboolean stillpolling; qbyte filter[8]; char refreshtext[64]; qboolean sliderpressed; menupicture_t *mappic; } serverlist_t; void SL_DrawColumnTitle (int *x, int y, int xlen, int mx, char *str, qboolean recolor, qbyte clr, qboolean *filldraw) { int xmin; if (x == NULL) xmin = 0; else xmin = (*x - xlen); if (recolor) str = va("^&%c-%s", clr, str); if (mx >= xmin && !(*filldraw)) { *filldraw = true; R2D_ImageColours((sin(realtime*4.4)*0.25)+0.5, (sin(realtime*4.4)*0.25)+0.5, 0.08, 1.0); R2D_FillBlock(xmin, y, xlen, 8); } Draw_FunStringWidth(xmin, y, str, xlen); if (x != NULL) *x -= xlen + 8; } void SL_TitlesDraw (int x, int y, menucustom_t *ths, menu_t *menu) { int sf = Master_GetSortField(); extern int mousecursor_x, mousecursor_y; int mx = mousecursor_x; qboolean filldraw = false; qbyte clr; if (Master_GetSortDescending()) clr = 'D'; else clr = 'B'; x = ths->common.width; if (mx > x || mousecursor_y < y || mousecursor_y >= y+8) filldraw = true; if (sb_showtimelimit.value) {SL_DrawColumnTitle(&x, y, 3*8, mx, "tl", (sf==SLKEY_TIMELIMIT), clr, &filldraw);} if (sb_showfraglimit.value) {SL_DrawColumnTitle(&x, y, 3*8, mx, "fl", (sf==SLKEY_FRAGLIMIT), clr, &filldraw);} if (sb_showplayers.value) {SL_DrawColumnTitle(&x, y, 5*8, mx, "plyrs", (sf==SLKEY_NUMPLAYERS), clr, &filldraw);} if (sb_showmap.value) {SL_DrawColumnTitle(&x, y, 8*8, mx, "map", (sf==SLKEY_MAP), clr, &filldraw);} if (sb_showgamedir.value) {SL_DrawColumnTitle(&x, y, 8*8, mx, "gamedir", (sf==SLKEY_GAMEDIR), clr, &filldraw);} if (sb_showping.value) {SL_DrawColumnTitle(&x, y, 3*8, mx, "png", (sf==SLKEY_PING), clr, &filldraw);} if (sb_showaddress.value) {SL_DrawColumnTitle(&x, y, 21*8, mx, "address", (sf==SLKEY_ADDRESS), clr, &filldraw);} SL_DrawColumnTitle(NULL, y, x, mx, "hostname ", (sf==SLKEY_NAME), clr, &filldraw); } qboolean SL_TitlesKey (menucustom_t *ths, menu_t *menu, int key) { int x; extern int mousecursor_x, mousecursor_y; int mx = mousecursor_x/8; int sortkey; if (key != K_MOUSE1) return false; do { x = ths->common.width/8; if (mx > x) return false; //out of bounds if (sb_showtimelimit.value) {x-=4;if (mx > x) {sortkey = SLKEY_TIMELIMIT; break;}} if (sb_showfraglimit.value) {x-=4;if (mx > x) {sortkey = SLKEY_FRAGLIMIT; break;}} if (sb_showplayers.value) {x-=6;if (mx > x) {sortkey = SLKEY_NUMPLAYERS; break;}} if (sb_showmap.value) {x-=9;if (mx > x) {sortkey = SLKEY_MAP; break;}} if (sb_showgamedir.value) {x-=9;if (mx > x) {sortkey = SLKEY_GAMEDIR; break;}} if (sb_showping.value) {x-=4;if (mx > x) {sortkey = SLKEY_PING; break;}} if (sb_showaddress.value) {x-=22;if (mx > x) {sortkey = SLKEY_ADDRESS; break;}} sortkey = SLKEY_NAME;break; } while (0); if (sortkey == SLKEY_ADDRESS) return true; Master_SetSortField(sortkey, Master_GetSortField()!=sortkey||!Master_GetSortDescending()); return true; } typedef enum { ST_NORMALQW, ST_FTESERVER, ST_QUAKE2, ST_QUAKE3, ST_NETQUAKE, ST_QTV, ST_PROXY, ST_FAVORITE, MAX_SERVERTYPES } servertypes_t; float serverbackcolor[MAX_SERVERTYPES * 2][3] = { {0.08, 0.08, 0.08}, // default {0.16, 0.16, 0.16}, {0.14, 0.07, 0.07}, // FTE server {0.28, 0.14, 0.14}, {0.04, 0.09, 0.04}, // Quake 2 {0.08, 0.18, 0.08}, {0.05, 0.05, 0.12}, // Quake 3 {0.10, 0.10, 0.24}, {0.12, 0.08, 0.02}, // NetQuake {0.24, 0.16, 0.04}, {0.10, 0.05, 0.10}, // FTEQTV {0.20, 0.10, 0.20}, {0.10, 0.05, 0.10}, // qizmo {0.20, 0.10, 0.20}, {0.01, 0.13, 0.13}, // Favorite {0.02, 0.26, 0.26} }; float serverhighlight[MAX_SERVERTYPES][3] = { {0.35, 0.35, 0.45}, // Default {0.60, 0.30, 0.30}, // FTE Server {0.25, 0.45, 0.25}, // Quake 2 {0.20, 0.20, 0.60}, // Quake 3 {0.40, 0.40, 0.25}, // NetQuake {0.45, 0.20, 0.45}, // FTEQTV {0.45, 0.20, 0.45}, // qizmo {0.10, 0.60, 0.60} // Favorite }; servertypes_t flagstoservertype(int flags) { if (flags & SS_FAVORITE) return ST_FAVORITE; if (flags & SS_PROXY) { if (flags & SS_FTESERVER) return ST_QTV; else return ST_PROXY; } if (flags & SS_FTESERVER) return ST_FTESERVER; if ((flags & SS_NETQUAKE) || (flags & SS_DARKPLACES)) return ST_NETQUAKE; if (flags & SS_QUAKE2) return ST_QUAKE2; if (flags & SS_QUAKE3) return ST_QUAKE3; return ST_NORMALQW; } void SL_ServerDraw (int x, int y, menucustom_t *ths, menu_t *menu) { extern int mousecursor_x, mousecursor_y; serverlist_t *info = (serverlist_t*)(menu + 1); serverinfo_t *si; int thisone = (int)ths->data + info->scrollpos; servertypes_t stype; char adr[MAX_ADR_SIZE]; si = Master_SortedServer(thisone); if (si) { x = ths->common.width; stype = flagstoservertype(si->special); if (thisone == info->selectedpos) { R2D_ImageColours( serverhighlight[(int)stype][0], serverhighlight[(int)stype][1], serverhighlight[(int)stype][2], 1.0); } else if (thisone == info->scrollpos + (mousecursor_y-16)/8 && mousecursor_x < x) R2D_ImageColours((sin(realtime*4.4)*0.25)+0.5, (sin(realtime*4.4)*0.25)+0.5, 0.08, 1.0); else if (selectedserver.inuse && NET_CompareAdr(si->adr, selectedserver.adr)) R2D_ImageColours(((sin(realtime*4.4)*0.25)+0.5) * 0.5, ((sin(realtime*4.4)*0.25)+0.5)*0.5, 0.08*0.5, 1.0); else { R2D_ImageColours( serverbackcolor[(int)stype * 2 + (thisone & 1)][0], serverbackcolor[(int)stype * 2 + (thisone & 1)][1], serverbackcolor[(int)stype * 2 + (thisone & 1)][2], 1.0); } R2D_FillBlock(0, y, ths->common.width, 8); if (sb_showtimelimit.value) {Draw_FunStringWidth((x-3*8), y, va("%i", si->tl), 3*8); x-=4*8;} if (sb_showfraglimit.value) {Draw_FunStringWidth((x-3*8), y, va("%i", si->fl), 3*8); x-=4*8;} if (sb_showplayers.value) {Draw_FunStringWidth((x-5*8), y, va("%2i/%2i", si->players, si->maxplayers), 5*8); x-=6*8;} if (sb_showmap.value) {Draw_FunStringWidth((x-8*8), y, si->map, 8*8); x-=9*8;} if (sb_showgamedir.value) {Draw_FunStringWidth((x-8*8), y, si->gamedir, 8*8); x-=9*8;} if (sb_showping.value) {Draw_FunStringWidth((x-3*8), y, va("%i", si->ping), 3*8); x-=4*8;} if (sb_showaddress.value) {Draw_FunStringWidth((x-21*8), y, NET_AdrToString(adr, sizeof(adr), si->adr), 21*8); x-=22*8;} Draw_FunStringWidth(0, y, si->name, x); } } qboolean SL_ServerKey (menucustom_t *ths, menu_t *menu, int key) { static int lastclick; int curtime; int oldselection; extern int mousecursor_x, mousecursor_y; serverlist_t *info = (serverlist_t*)(menu + 1); serverinfo_t *server; char adr[MAX_ADR_SIZE]; if (key == K_MOUSE1) { oldselection = info->selectedpos; info->selectedpos = info->scrollpos + (mousecursor_y-16)/8; server = Master_SortedServer(info->selectedpos); selectedserver.inuse = true; SListOptionChanged(server); if (server) { snprintf(info->mappic->picturename, 32, "levelshots/%s", server->map); if (!R2D_SafeCachePic(info->mappic->picturename)) snprintf(info->mappic->picturename, 32, "levelshots/nomap"); } else { snprintf(info->mappic->picturename, 32, "levelshots/nomap"); return true; } curtime = Sys_Milliseconds(); if (lastclick > curtime || lastclick < curtime-250) { //shouldn't happen, or too old a click lastclick = curtime; return true; } if (oldselection == info->selectedpos) goto joinserver; return true; } if (key == 'f') { server = Master_SortedServer(info->selectedpos); if (server) { server->special ^= SS_FAVORITE; } } if (key == K_ENTER || key == 's' || key == 'j' || key == K_SPACE) { server = Master_SortedServer(info->selectedpos); if (server) { if (key == 's' || key == K_SPACE) Cbuf_AddText("spectator 1\n", RESTRICT_LOCAL); else if (key == 'j') { joinserver: Cbuf_AddText("spectator 0\n", RESTRICT_LOCAL); } if (server->special & SS_NETQUAKE) Cbuf_AddText(va("nqconnect %s\n", NET_AdrToString(adr, sizeof(adr), server->adr)), RESTRICT_LOCAL); else Cbuf_AddText(va("connect %s\n", NET_AdrToString(adr, sizeof(adr), server->adr)), RESTRICT_LOCAL); M_RemoveAllMenus(); } return true; } return false; } void SL_PreDraw (menu_t *menu) { serverlist_t *info = (serverlist_t*)(menu + 1); NET_CheckPollSockets(); CL_QueryServers(); snprintf(info->refreshtext, sizeof(info->refreshtext), "Refresh - %u of %u\n", Master_NumPolled(), Master_TotalCount()); info->numslots = Master_NumSorted(); } qboolean SL_Key (int key, menu_t *menu) { serverlist_t *info = (serverlist_t*)(menu + 1); if (key == K_HOME) { info->scrollpos = 0; info->selectedpos = 0; return true; } if (key == K_END) { info->selectedpos = info->numslots-1; info->scrollpos = info->selectedpos - (vid.height-16-7)/8; return true; } if (key == K_PGDN) info->selectedpos += 10; else if (key == K_PGUP) info->selectedpos -= 10; else if (key == K_DOWNARROW) info->selectedpos += 1; else if (key == K_UPARROW) info->selectedpos -= 1; else if (key == K_MWHEELUP) info->selectedpos -= 3; else if (key == K_MWHEELDOWN) info->selectedpos += 3; else return false; { serverinfo_t *server; server = Master_SortedServer(info->selectedpos); // selectedserver.inuse = true; // SListOptionChanged(server); if (server) { snprintf(info->mappic->picturename, 32, "levelshots/%s", server->map); if (!R2D_SafeCachePic(info->mappic->picturename)) snprintf(info->mappic->picturename, 32, "levelshots/nomap"); } else { snprintf(info->mappic->picturename, 32, "levelshots/nomap"); } } if (info->selectedpos < 0) info->selectedpos = 0; if (info->selectedpos > info->numslots-1) info->selectedpos = info->numslots-1; if (info->scrollpos < info->selectedpos - info->visibleslots) info->scrollpos = info->selectedpos - info->visibleslots; if (info->selectedpos < info->scrollpos) info->scrollpos = info->selectedpos; return true; } void SL_ServerPlayer (int x, int y, menucustom_t *ths, menu_t *menu) { if (selectedserver.inuse) { if (selectedserver.detail) if ((int)ths->data < selectedserver.detail->numplayers) { int i = (int)ths->data; R2D_ImagePaletteColour (Sbar_ColorForMap(selectedserver.detail->players[i].topc), 1.0); R2D_FillBlock (x, y, 28, 4); R2D_ImagePaletteColour (Sbar_ColorForMap(selectedserver.detail->players[i].botc), 1.0); R2D_FillBlock (x, y+4, 28, 4); NM_PrintWhite (x, y, va("%3i", selectedserver.detail->players[i].frags)); Draw_FunStringWidth (x+28, y, selectedserver.detail->players[i].name, 12*8); } } } void SL_SliderDraw (int x, int y, menucustom_t *ths, menu_t *menu) { serverlist_t *info = (serverlist_t*)(menu + 1); mpic_t *pic; pic = R2D_SafeCachePic("scrollbars/slidebg.png"); if (pic) { R2D_ScalePic(x + ths->common.width - 8, y+8, 8, ths->common.height-16, pic); pic = R2D_SafeCachePic("scrollbars/arrow_up.png"); R2D_ScalePic(x + ths->common.width - 8, y, 8, 8, pic); pic = R2D_SafeCachePic("scrollbars/arrow_down.png"); R2D_ScalePic(x + ths->common.width - 8, y + ths->common.height - 8, 8, 8, pic); y += ((info->scrollpos) / ((float)info->numslots - info->visibleslots)) * (float)(ths->common.height-(64+16-1)); y += 8; pic = R2D_SafeCachePic("scrollbars/slider.png"); R2D_ScalePic(x + ths->common.width - 8, y, 8, 64, pic); } else { R2D_ImageColours(0.1, 0.1, 0.2, 1.0); R2D_FillBlock(x, y, ths->common.width, ths->common.height); y += ((info->scrollpos) / ((float)info->numslots - info->visibleslots)) * (ths->common.height-8); R2D_ImageColours(0.35, 0.35, 0.55, 1.0); R2D_FillBlock(x, y, 8, 8); } if (info->sliderpressed) { extern qboolean keydown[K_MAX]; if (keydown[K_MOUSE1]) { extern int mousecursor_x, mousecursor_y; float my; serverlist_t *info = (serverlist_t*)(menu + 1); my = mousecursor_y; my -= ths->common.posy; if (R2D_SafeCachePic("scrollbars/slidebg.png")) { my -= 32+8; my /= ths->common.height - (64+16); } else my /= ths->common.height; my *= (info->numslots-info->visibleslots); if (my > info->numslots-info->visibleslots-1) my = info->numslots-info->visibleslots-1; if (my < 0) my = 0; info->scrollpos = my; } else info->sliderpressed = false; } } qboolean SL_SliderKey (menucustom_t *ths, menu_t *menu, int key) { if (key == K_MOUSE1) { extern int mousecursor_x, mousecursor_y; float my; serverlist_t *info = (serverlist_t*)(menu + 1); my = mousecursor_y; my -= ths->common.posy; if (R2D_SafeCachePic("scrollbars/slidebg.png")) { my -= 32+8; my /= ths->common.height - (64+16); } else my /= ths->common.height; my *= (info->numslots-info->visibleslots); if (my > info->numslots-info->visibleslots-1) my = info->numslots-info->visibleslots-1; if (my < 0) my = 0; info->scrollpos = my; info->sliderpressed = true; return true; } return false; } void CalcFilters(menu_t *menu) { serverlist_t *info = (serverlist_t*)(menu + 1); Master_ClearMasks(); Master_SetMaskInteger(false, SLKEY_PING, 0, SLIST_TEST_LESS); if (info->filter[1]) Master_SetMaskInteger(true, SLKEY_BASEGAME, SS_NETQUAKE|SS_DARKPLACES, SLIST_TEST_CONTAINS); if (info->filter[2]) Master_SetMaskInteger(true, SLKEY_BASEGAME, SS_NETQUAKE|SS_DARKPLACES|SS_QUAKE2|SS_QUAKE3, SLIST_TEST_NOTCONTAIN); if (info->filter[3]) Master_SetMaskInteger(true, SLKEY_BASEGAME, SS_QUAKE2, SLIST_TEST_CONTAINS); if (info->filter[4]) Master_SetMaskInteger(true, SLKEY_BASEGAME, SS_QUAKE3, SLIST_TEST_CONTAINS); if (info->filter[5]) Master_SetMaskInteger(false, SLKEY_BASEGAME, SS_FAVORITE, SLIST_TEST_CONTAINS); if (info->filter[6]) Master_SetMaskInteger(false, SLKEY_NUMPLAYERS, 0, SLIST_TEST_NOTEQUAL); if (info->filter[7]) Master_SetMaskInteger(false, SLKEY_FREEPLAYERS, 0, SLIST_TEST_NOTEQUAL); } qboolean SL_ReFilter (menucheck_t *option, menu_t *menu, chk_set_t set) { serverlist_t *info = (serverlist_t*)(menu + 1); switch(set) { case CHK_CHECKED: return info->filter[option->bits]; case CHK_TOGGLE: if (option->bits>0) { info->filter[option->bits] ^= 1; Cvar_Set(&sb_hidenetquake, info->filter[1]?"0":"1"); Cvar_Set(&sb_hidequakeworld, info->filter[2]?"0":"1"); Cvar_Set(&sb_hidequake2, info->filter[3]?"0":"1"); Cvar_Set(&sb_hidequake3, info->filter[4]?"0":"1"); Cvar_Set(&sb_hideempty, info->filter[6]?"1":"0"); Cvar_Set(&sb_hidefull, info->filter[7]?"1":"0"); } CalcFilters(menu); return true; } return true; } void SL_Remove (menu_t *menu) { serverlist_t *info = (serverlist_t*)(menu + 1); Cvar_Set(&sb_hidenetquake, info->filter[1]?"0":"1"); Cvar_Set(&sb_hidequakeworld, info->filter[2]?"0":"1"); Cvar_Set(&sb_hidequake2, info->filter[3]?"0":"1"); Cvar_Set(&sb_hidequake3, info->filter[4]?"0":"1"); Cvar_Set(&sb_hideempty, info->filter[6]?"1":"0"); Cvar_Set(&sb_hidefull, info->filter[7]?"1":"0"); } qboolean SL_DoRefresh (menuoption_t *opt, menu_t *menu, int key) { MasterInfo_Refresh(); return true; } void M_Menu_ServerList2_f(void) { int i, y, x; menu_t *menu; menucustom_t *cust; serverlist_t *info; if (!qrenderer) { Cbuf_AddText("wait; menu_servers\n", Cmd_ExecLevel); return; } key_dest = key_menu; m_state = m_complex; menu = M_CreateMenu(sizeof(serverlist_t)); menu->event = SL_PreDraw; menu->key = SL_Key; menu->remove = SL_Remove; info = (serverlist_t*)(menu + 1); y = 8; cust = MC_AddCustom(menu, 0, y, 0); cust->draw = SL_TitlesDraw; cust->key = SL_TitlesKey; cust->common.height = 8; cust->common.width = vid.width-8; info->visibleslots = (vid.height-16 - 64); cust = MC_AddCustom(menu, vid.width-8, 16, NULL); cust->draw = SL_SliderDraw; cust->key = SL_SliderKey; cust->common.height = info->visibleslots; cust->common.width = 8; info->visibleslots = (info->visibleslots-7)/8; for (i = 0, y = 16; i <= info->visibleslots; y +=8, i++) { cust = MC_AddCustom(menu, 0, y, (void*)i); cust->draw = SL_ServerDraw; cust->key = SL_ServerKey; cust->common.height = 8; cust->common.width = vid.width-8; cust->common.noselectionsound = true; } menu->dontexpand = true; i = 0; for (x = 256; x < vid.width-64; x += 128) { for (y = vid.height-64+8; y < vid.height; y += 8, i++) { cust = MC_AddCustom(menu, x+16, y, (void*)i); cust->draw = SL_ServerPlayer; cust->key = NULL; cust->common.height = 8; cust->common.width = 0; } } strcpy(info->refreshtext, "Refresh"); MC_AddCheckBox(menu, 0, vid.height - 64+8*1, "Ping ", &sb_showping, 1); MC_AddCheckBox(menu, 0, vid.height - 64+8*2, "Address ", &sb_showaddress, 1); MC_AddCheckBox(menu, 0, vid.height - 64+8*3, "Map ", &sb_showmap, 1); MC_AddCheckBox(menu, 0, vid.height - 64+8*4, "Gamedir ", &sb_showgamedir, 1); MC_AddCheckBox(menu, 0, vid.height - 64+8*5, "Players ", &sb_showplayers, 1); MC_AddCheckBox(menu, 0, vid.height - 64+8*6, "Fraglimit", &sb_showfraglimit, 1); MC_AddCheckBox(menu, 0, vid.height - 64+8*7, "Timelimit", &sb_showtimelimit, 1); MC_AddCheckBoxFunc(menu, 128, vid.height - 64+8*1, "List NQ ", SL_ReFilter, 1); MC_AddCheckBoxFunc(menu, 128, vid.height - 64+8*2, "List QW ", SL_ReFilter, 2); MC_AddCheckBoxFunc(menu, 128, vid.height - 64+8*3, "List Q2 ", SL_ReFilter, 3); MC_AddCheckBoxFunc(menu, 128, vid.height - 64+8*4, "List Q3 ", SL_ReFilter, 4); MC_AddCheckBoxFunc(menu, 128, vid.height - 64+8*5, "Only Favs ", SL_ReFilter, 5); MC_AddCheckBoxFunc(menu, 128, vid.height - 64+8*6, "Hide Empty", SL_ReFilter, 6); MC_AddCheckBoxFunc(menu, 128, vid.height - 64+8*7, "Hide Full ", SL_ReFilter, 7); MC_AddCommand(menu, 64, 0, info->refreshtext, SL_DoRefresh); info->filter[1] = !sb_hidenetquake.value; info->filter[2] = !sb_hidequakeworld.value; info->filter[3] = !sb_hidequake2.value; info->filter[4] = !sb_hidequake3.value; info->filter[6] = !!sb_hideempty.value; info->filter[7] = !!sb_hidefull.value; info->mappic = (menupicture_t *)MC_AddPicture(menu, vid.width - 64, vid.height - 64, 64, 64, "012345678901234567890123456789012"); CalcFilters(menu); Master_SetSortField(SLKEY_PING, true); MasterInfo_Refresh(); } float quickconnecttimeout; void M_QuickConnect_PreDraw(menu_t *menu) { serverinfo_t *best = NULL; serverinfo_t *s; char adr[MAX_ADR_SIZE]; NET_CheckPollSockets(); //see if we were told something important. CL_QueryServers(); if (Sys_DoubleTime() > quickconnecttimeout) { for (s = firstserver; s; s = s->next) { if (!s->maxplayers) //no response? continue; if (s->players == s->maxplayers) continue; //server is full already if (s->special & SS_PROXY) continue; //don't quickconnect to a proxy. their player counts are often wrong (especially with qtv) if (s->ping < 50) //don't like servers with too high a ping { if (s->players > 0) { if (best) if (best->players > s->players) continue; //go for the one with most players best = s; } } } if (best) { Con_Printf("Quick connect found %s (gamedir %s, players %i/%i, ping %ims)\n", best->name, best->gamedir, best->players, best->maxplayers, best->ping); if (best->special & SS_NETQUAKE) Cbuf_AddText(va("nqconnect %s\n", NET_AdrToString(adr, sizeof(adr), best->adr)), RESTRICT_LOCAL); else Cbuf_AddText(va("join %s\n", NET_AdrToString(adr, sizeof(adr), best->adr)), RESTRICT_LOCAL); M_ToggleMenu_f(); return; } //retry MasterInfo_Refresh(); quickconnecttimeout = Sys_DoubleTime() + 5; } } qboolean M_QuickConnect_Key (int key, menu_t *menu) { return false; } void M_QuickConnect_Remove (menu_t *menu) { } qboolean M_QuickConnect_Cancel (menuoption_t *opt, menu_t *menu, int key) { return false; } void M_QuickConnect_DrawStatus (int x, int y, menucustom_t *ths, menu_t *menu) { Draw_FunString(x, y, va("Polling, %i secs\n", (int)(quickconnecttimeout - Sys_DoubleTime() + 0.9))); } void M_QuickConnect_f(void) { menucustom_t *cust; menu_t *menu; key_dest = key_menu; m_state = m_complex; MasterInfo_Refresh(); quickconnecttimeout = Sys_DoubleTime() + 5; menu = M_CreateMenu(sizeof(serverlist_t)); menu->event = M_QuickConnect_PreDraw; menu->key = M_QuickConnect_Key; menu->remove = M_QuickConnect_Remove; cust = MC_AddCustom(menu, 64, 64, NULL); cust->draw = M_QuickConnect_DrawStatus; cust->common.height = 8; cust->common.width = vid.width-8; MC_AddCommand(menu, 64, 128, "Refresh", SL_DoRefresh); MC_AddCommand(menu, 64, 136, "Cancel", M_QuickConnect_Cancel); } #endif