9 #define IsFavorite(srv) IsServerInList(cvar_string("net_slist_favorites"), srv) 10 #define IsPromoted(srv) IsServerInList(_Nex_ExtResponseSystem_PromotedServers, srv) 11 #define IsRecommended(srv) IsServerInList(_Nex_ExtResponseSystem_RecommendedServers, srv) 19 string s =
categories[i].override_field_string;
37 LOG_INFOF(
"RegisterSLCategories(): Improper override '%s' for category '%s'!", s,
categories[i].cat_name);
47 #define SLIST_CATEGORY(name,enoverride,dioverride,str) \ 48 SET_FIELD_COUNT(name, CATEGORY_FIRST, category_ent_count) \ 49 CHECK_MAX_COUNT(name, MAX_CATEGORIES, category_ent_count, "SLIST_CATEGORY") \ 50 cat = categories[name - 1] = new(slist_category); \ 51 cat.cat_name = #name; \ 52 cat.cat_enoverride_string = strzone(SLIST_CATEGORY_AUTOCVAR(name)); \ 53 cat.cat_dioverride_string = strzone(dioverride); \ 54 cat.cat_string = strzone(str); 71 error(sprintf(
"RetrieveCategoryEnt(%d): Improper category number!\n", catnum));
80 srv = netaddress_resolve(srv, 26000);
83 string p = crypto_getidfp(srv);
85 for(
int i = 0; i < n; ++i)
94 if(srv == netaddress_resolve(
argv(i), 26000))
107 if(
override) {
return override; }
112 error(sprintf(
"CategoryOverride(%d): Improper category number!\n", cat));
119 string s, k,
v, modtype =
"";
120 int j, m, impure = 0, freeslots = 0, sflags = 0;
124 for(j = 2; j < m; ++j)
126 if(
argv(j) ==
"") {
break; }
131 case "P": { impure =
stof(v);
break; }
132 case "S": { freeslots =
stof(v);
break; }
133 case "F": { sflags =
stof(v);
break; }
134 case "M": { modtype = strtolower(v);
break; }
148 if(
IsPromoted(cname)) {
return CAT_RECOMMENDED; }
151 float recommended = 0;
155 else { --recommended; }
188 if(recommended > 0) {
return CAT_RECOMMENDED; }
193 if(modtype !=
"xonotic")
198 case "": {
return CAT_MODIFIED; }
200 case "xpm": {
return CAT_XPM; }
202 case "instagib": {
return CAT_INSTAGIB; }
203 case "overkill": {
return CAT_OVERKILL; }
207 case "xdf": {
return CAT_DEFRAG; }
209 default: {
LOG_TRACEF(
"Found strange mod type: %s", modtype);
return CAT_MODIFIED; }
220 string srv_resolved = netaddress_resolve(srv, 26000);
221 string p = crypto_getidfp(srv_resolved);
230 match = (p && p ==
argv(i));
235 match = (srv_resolved == netaddress_resolve(
argv(i), 26000));
237 if (!match)
continue;
242 s =
strcat(before, (before !=
"" && after !=
"" ?
" " :
""), after);
251 ret =
strcat(s, (s !=
"" ?
" " :
""), p ? p : srv);
253 cvar_set(
"net_slist_favorites", ret);
259 entity e = me.favoriteButton;
262 e.setText(e,
ZCTX(_(
"SERVER^Remove favorite")));
267 e.setText(e,
ZCTX(_(
"SERVER^Favorite")));
276 me.configureXonoticServerList(me);
281 me.configureXonoticListBox(me);
284 #define SLIST_FIELD(suffix,name) SLIST_FIELD_##suffix = gethostcacheindexforkey(name); 293 me.lockedSelectedItem =
false;
307 me.ipAddressBox.setText(me.ipAddressBox, me.selectedServer);
308 me.ipAddressBox.cursorPos =
strlen(me.selectedServer);
309 me.ipAddressBoxFocused = -1;
317 string s = me.filterString;
331 string modstr =
cvar_string(
"menu_slist_modfilter");
340 if(!me.filterShowFull)
347 if(!me.filterShowEmpty)
369 for(
int i = 0; i < n; ++i)
382 int sorting_flags = 0;
396 if(
time < me.nextRefreshTime)
401 me.nextRefreshTime =
time + 10;
430 if(me.currentSortField == -1)
432 me.setSortOrder(me, SLIST_FIELD_PING, +1);
435 else if(me.needsRefresh == 1)
439 else if(me.needsRefresh == 2)
444 else if(me.needsRefresh == 3)
450 bool owned = ((me.selectedServer == me.ipAddressBox.text) && (me.ipAddressBox.text !=
""));
453 category_draw_count = 0;
459 int itemcount = me.nItems;
469 float last = (itemcount - 1);
480 }
else if (catl < j) {
485 }
else if (catf == j) {
490 error(
"Category mismatch I");
493 error(
"Category mismatch II");
507 while (last - first > 1) {
508 float middle =
floor((first + last) / 2);
521 error(
"Category mismatch III");
524 error(
"Category mismatch IV");
538 category_draw_count = 0;
539 me.nItems = itemcount;
543 me.connectButton.disabled = (me.ipAddressBox.text ==
"");
545 me.infoButton.disabled = !owned;
546 me.favoriteButton.disabled = (me.ipAddressBox.text ==
"");
549 if(me.lockedSelectedItem)
560 else if(me.selectedServer)
562 for(
int i = 0; i < me.nItems; ++i)
578 if(me.selectedItem >= me.nItems)
586 if(me.selectedServer != me.ipAddressBox.text)
588 me.ipAddressBox.setText(me.ipAddressBox, me.selectedServer);
589 me.ipAddressBox.cursorPos =
strlen(me.selectedServer);
590 me.ipAddressBoxFocused = -1;
594 if(me.ipAddressBoxFocused != me.ipAddressBox.focused)
596 if(me.ipAddressBox.focused || me.ipAddressBoxFocused < 0)
598 me.ipAddressBoxFocused = me.ipAddressBox.focused;
605 me.setSortOrder(me, SLIST_FIELD_PING, +1);
609 me.setSortOrder(me, SLIST_FIELD_NAME, -1);
613 me.setSortOrder(me, SLIST_FIELD_MAP, -1);
617 me.setSortOrder(me, SLIST_FIELD_NUMHUMANS, -1);
621 string s = me.filterString;
648 if(s !=
"") s =
strcat(s,
":");
651 me.controlledTextbox.setText(me.controlledTextbox, s);
652 me.controlledTextbox.keyDown(me.controlledTextbox,
K_END, 0, 0);
653 me.controlledTextbox.keyUp(me.controlledTextbox,
K_END, 0, 0);
660 me.filterString =
strzone(box.text);
663 me.ipAddressBox.setText(me.ipAddressBox,
"");
664 me.ipAddressBox.cursorPos = 0;
665 me.ipAddressBoxFocused = -1;
672 me.ipAddressBox.setText(me.ipAddressBox,
"");
673 me.ipAddressBox.cursorPos = 0;
674 me.ipAddressBoxFocused = -1;
678 box.setChecked(box, me.filterShowEmpty = !me.filterShowEmpty);
681 me.ipAddressBox.setText(me.ipAddressBox,
"");
682 me.ipAddressBox.cursorPos = 0;
683 me.ipAddressBoxFocused = -1;
687 box.setChecked(box, me.filterShowFull = !me.filterShowFull);
690 me.ipAddressBox.setText(me.ipAddressBox,
"");
691 me.ipAddressBox.cursorPos = 0;
692 me.ipAddressBoxFocused = -1;
696 box.setChecked(box, me.filterShowLaggy = !me.filterShowLaggy);
699 me.ipAddressBox.setText(me.ipAddressBox,
"");
700 me.ipAddressBox.cursorPos = 0;
701 me.ipAddressBoxFocused = -1;
705 if(me.currentSortField == fld)
706 direction = -me.currentSortOrder;
708 me.currentSortField = fld;
709 me.sortButton1.forcePressed = (fld == SLIST_FIELD_PING);
710 me.sortButton2.forcePressed = (fld == SLIST_FIELD_NAME);
711 me.sortButton3.forcePressed = (fld == SLIST_FIELD_MAP);
712 me.sortButton4.forcePressed = 0;
713 me.sortButton5.forcePressed = (fld == SLIST_FIELD_NUMHUMANS);
720 vector originInLBSpace =
eY * (-me.itemHeight);
721 vector sizeInLBSpace =
eY * me.itemHeight +
eX * (1 - me.controlWidth);
723 vector originInDialogSpace =
boxToGlobal(originInLBSpace, me.Container_origin, me.Container_size);
726 btn.Container_origin_x = originInDialogSpace.x + sizeInDialogSpace.x * theOrigin;
727 btn.Container_size_x = sizeInDialogSpace.x * theSize;
728 btn.setText(btn, theTitle);
729 btn.onClick = theFunc;
730 btn.onClickEntity = me;
737 me.realFontSize_y = me.fontSize / (absSize.y * me.itemHeight);
738 me.realFontSize_x = me.fontSize / (absSize.x * (1 - me.controlWidth));
739 me.realUpperMargin = 0.5 * (1 - me.realFontSize.y);
741 me.columnIconsOrigin = 0;
742 me.columnIconsSize = me.realFontSize.x * 4 * me.iconsSizeFactor;
743 me.columnPingSize = me.realFontSize.x * 3;
744 me.columnMapSize = me.realFontSize.x * 10;
745 me.columnTypeSize = me.realFontSize.x * 4;
746 me.columnPlayersSize = me.realFontSize.x * 5;
747 me.columnNameSize = 1 - me.columnPlayersSize - me.columnMapSize - me.columnPingSize - me.columnIconsSize - me.columnTypeSize - 5 * me.realFontSize.x;
748 me.columnPingOrigin = me.columnIconsOrigin + me.columnIconsSize + me.realFontSize.x;
749 me.columnNameOrigin = me.columnPingOrigin + me.columnPingSize + me.realFontSize.x;
750 me.columnMapOrigin = me.columnNameOrigin + me.columnNameSize + me.realFontSize.x;
751 me.columnTypeOrigin = me.columnMapOrigin + me.columnMapSize + me.realFontSize.x;
752 me.columnPlayersOrigin = me.columnTypeOrigin + me.columnTypeSize + me.realFontSize.x;
755 me.positionSortButton(me, me.sortButton2, me.columnNameOrigin, me.columnNameSize, _(
"Hostname"),
ServerList_NameSort_Click);
756 me.positionSortButton(me, me.sortButton3, me.columnMapOrigin, me.columnMapSize, _(
"Map"),
ServerList_MapSort_Click);
758 me.positionSortButton(me, me.sortButton5, me.columnPlayersOrigin, me.columnPlayersSize, _(
"Players"),
ServerList_PlayerSort_Click);
760 int f = me.currentSortField;
763 me.currentSortField = -1;
764 me.setSortOrder(me, f, me.currentSortOrder);
769 if (me.ipAddressBox.text !=
"")
770 localcmd(sprintf(
"connect %s\n", me.ipAddressBox.text));
774 string addr = this.ipAddressBox.text;
775 string ipstr = netaddress_resolve(addr, 26000);
776 if (ipstr ==
"")
return;
778 this.toggleFavorite(
this, addr);
779 this.ipAddressBoxFocused = -1;
784 main.serverInfoDialog.loadServerInfo(
main.serverInfoDialog, me.selectedItem);
786 float thisPos = me.getItemStart(me, me.selectedItem);
787 float thisHeight = me.getItemHeight(me, me.selectedItem);
800 #define SET_YRANGE(start,end) \ 801 draw_scale = boxToGlobalSize(eX + eY * ((end) - (start)), oldscale); \ 802 draw_shift = boxToGlobal(eY * (start), oldshift, oldscale); 811 if (c < category_draw_count)
817 (me.categoriesHeight - 1) / (me.categoriesHeight + 1),
818 me.categoriesHeight / (me.categoriesHeight + 1)
821 eY * me.realUpperMargin
824 eX * (me.columnNameOrigin + (me.columnNameSize -
draw_TextWidth(catent.cat_string, 0, me.realFontSize)) * 0.5),
827 eX * (me.columnNameOrigin),
828 strcat(catent.cat_string,
":"),
831 SKINCOLOR_SERVERLIST_CATEGORY,
832 SKINALPHA_SERVERLIST_CATEGORY,
835 SET_YRANGE(me.categoriesHeight / (me.categoriesHeight + 1), 1);
843 if(isSelected && !me.lockedSelectedItem)
844 draw_Fill(
'0 0 0',
'1 1 0', SKINCOLOR_LISTBOX_SELECTED, SKINALPHA_LISTBOX_SELECTED);
847 me.focusedItemAlpha =
getFadedAlpha(me.focusedItemAlpha, SKINALPHA_LISTBOX_FOCUSED, SKINFADEALPHA_LISTBOX_FOCUSED);
848 draw_Fill(
'0 0 0',
'1 1 0', SKINCOLOR_LISTBOX_FOCUSED, me.focusedItemAlpha);
853 string typestr =
"", versionstr =
"";
857 versionstr =
argv(1);
859 bool pure =
false, pure_available =
false;
860 int freeslots = -1, sflags = -1;
862 for(
int j = 2; j < m; ++j)
870 pure = (
stof(value) == 0);
871 pure_available =
true;
874 freeslots =
stof(value);
876 sflags =
stoi(value);
881 #ifdef COMPAT_NO_MOD_IS_XONOTIC 886 string original_modname =
modname;
887 modname = strtolower(modname);
898 if(modname !=
"xonotic")
899 if(modname !=
"instagib" || modname !=
"minstagib")
902 if(modname !=
"newtoys")
903 pure_available =
false;
907 theAlpha = SKINALPHA_SERVERLIST_FULL;
908 else if(freeslots == 0)
909 theAlpha = SKINALPHA_SERVERLIST_FULL;
911 theAlpha = SKINALPHA_SERVERLIST_EMPTY;
916 const int PING_LOW = 75;
917 const int PING_MED = 200;
918 const int PING_HIGH = 500;
921 theColor = SKINCOLOR_SERVERLIST_LOWPING + (SKINCOLOR_SERVERLIST_MEDPING - SKINCOLOR_SERVERLIST_LOWPING) * (ping / PING_LOW);
922 else if(ping < PING_MED)
923 theColor = SKINCOLOR_SERVERLIST_MEDPING + (SKINCOLOR_SERVERLIST_HIGHPING - SKINCOLOR_SERVERLIST_MEDPING) * ((ping - PING_LOW) / (PING_MED - PING_LOW));
924 else if(ping < PING_HIGH)
926 theColor = SKINCOLOR_SERVERLIST_HIGHPING;
927 theAlpha *= 1 + (SKINALPHA_SERVERLIST_HIGHPING - 1) * ((ping - PING_MED) / (PING_HIGH - PING_MED));
932 theAlpha *= SKINALPHA_SERVERLIST_HIGHPING;
937 theColor = theColor * (1 - SKINALPHA_SERVERLIST_FAVORITE) + SKINCOLOR_SERVERLIST_FAVORITE * SKINALPHA_SERVERLIST_FAVORITE;
938 theAlpha = theAlpha * (1 - SKINALPHA_SERVERLIST_FAVORITE) + SKINALPHA_SERVERLIST_FAVORITE;
943 bool isv4 =
false, isv6 =
false;
955 int crypto =
stof(
substring(crypto_getencryptlevel(s), 0, 1));
956 if((crypto <= 0 &&
cvar(
"crypto_aeslevel") >= 3) || (crypto >= 3 &&
cvar(
"crypto_aeslevel") <= 0))
958 theColor = SKINCOLOR_SERVERLIST_IMPOSSIBLE;
959 theAlpha = SKINALPHA_SERVERLIST_IMPOSSIBLE;
964 if(
cvar(
"crypto_aeslevel") >= 2)
969 if(
cvar(
"crypto_aeslevel") >= 1)
987 vector iconSize =
'0 0 0';
988 iconSize_y = me.realFontSize.y * me.iconsSizeFactor;
989 iconSize_x = me.realFontSize.x * me.iconsSizeFactor * me.serversHeight;
992 iconPos_x = (me.columnIconsSize - 3 * iconSize.x) * 0.5;
993 iconPos_y = (1 - iconSize.y) * 0.5;
996 if(me.seenIPv4 && me.seenIPv6)
999 draw_Picture(iconPos,
"icon_ipv6", iconSize,
'1 1 1', 1);
1001 draw_Picture(iconPos,
"icon_ipv4", iconSize,
'1 1 1', 1);
1004 iconPos.x += iconSize.x;
1010 iconPos.x += iconSize.x;
1013 if(modname ==
"xonotic")
1017 if(pure_available && pure)
1018 draw_Picture(iconPos,
"icon_pure1", iconSize,
'1 1 1', 1);
1022 string icon =
strcat(
"icon_mod_", modname);
1029 if(pure_available && !pure)
1030 draw_Picture(iconPos, icon, iconSize,
'1 1 1', SKINALPHA_SERVERLIST_ICON_NONPURE);
1035 iconPos.x += iconSize.x;
1041 draw_Picture(iconPos,
"icon_mod_", iconSize,
'1 1 1', 1);
1043 draw_Picture(iconPos,
"icon_stats1", iconSize,
'1 1 1', 1);
1046 if(isFocused && me.mouseOverIcons && !me.tooltip)
1049 if(me.seenIPv4 && me.seenIPv6)
1050 t =
strcat(t, (isv6) ?
"IPv6, " :
"IPv4, ");
1051 t =
strcat(t, _(
"encryption:"),
" ", (crypto ? sprintf(_(
"AES level %d"), crypto) :
ZCTX(_(
"ENC^none"))),
", ");
1052 t =
strcat(t, sprintf(_(
"mod: %s"), ((modname ==
"xonotic") ?
ZCTX(_(
"MOD^Default")) : original_modname)));
1054 t =
strcat(t, sprintf(
" (%s)", (pure) ? _(
"official settings") : _(
"modified settings")));
1056 t =
strcat(t, ((sflags >= 0 && (sflags & SERVERFLAG_PLAYERSTATS)) ? ((sflags &
SERVERFLAG_PLAYERSTATS_CUSTOM) ? _(
"custom stats server") : _(
"stats enabled")) : _(
"stats disabled")));
1065 0.5 - 0.5 / me.serversHeight,
1066 0.5 + 0.5 / me.serversHeight
1071 draw_Text(me.realUpperMargin *
eY + (me.columnPingOrigin + me.columnPingSize -
draw_TextWidth(s, 0, me.realFontSize)) *
eX, s, me.realFontSize, theColor, theAlpha, 0);
1075 draw_Text(me.realUpperMargin *
eY + me.columnNameOrigin *
eX, s, me.realFontSize, theColor, theAlpha, 0);
1079 draw_Text(me.realUpperMargin *
eY + (me.columnMapOrigin + (me.columnMapSize -
draw_TextWidth(s, 0, me.realFontSize)) * 0.5) *
eX, s, me.realFontSize, theColor, theAlpha, 0);
1083 draw_Text(me.realUpperMargin *
eY + (me.columnTypeOrigin + (me.columnTypeSize -
draw_TextWidth(s, 0, me.realFontSize)) * 0.5) *
eX, s, me.realFontSize, theColor, theAlpha, 0);
1087 draw_Text(me.realUpperMargin *
eY + (me.columnPlayersOrigin + (me.columnPlayersSize -
draw_TextWidth(s, 0, me.realFontSize)) * 0.5) *
eX, s, me.realFontSize, theColor, theAlpha, 0);
1099 me.mouseOverIcons =
false;
1104 me.mouseOverIcons = (pos_x <= me.columnIconsSize);
1105 if(!me.mouseOverIcons)
1121 float thisPos = me.getItemStart(me, me.selectedItem);
1122 float thisHeight = me.getItemHeight(me, me.selectedItem);
1126 main.serverInfoDialog.loadServerInfo(
main.serverInfoDialog, me.selectedItem);
1136 me.toggleFavorite(me, me.selectedServer);
1137 me.ipAddressBoxFocused = -1;
1144 else if(!me.controlledTextbox)
1147 return me.controlledTextbox.keyDown(me.controlledTextbox, scan, ascii, shift);
1152 float num_normal_rows = me.nItems;
1154 return me.itemHeight * (me.serversHeight * num_normal_rows + me.categoriesHeight * num_headers);
1158 pos = pos / me.itemHeight;
1161 float itempos = i * me.categoriesHeight + itemidx * me.serversHeight;
1162 if (pos >= itempos + me.categoriesHeight + me.serversHeight)
1163 return itemidx + 1 +
floor((pos - (itempos + me.categoriesHeight + me.serversHeight)) / me.serversHeight);
1168 return floor(pos / me.serversHeight);
1174 float itempos = i * me.categoriesHeight + itemidx * me.serversHeight;
1175 if (item >= itemidx + 1)
1176 return (itempos + me.categoriesHeight + (1 + item - (itemidx + 1)) * me.serversHeight) * me.itemHeight;
1177 if (item >= itemidx)
1178 return itempos * me.itemHeight;
1181 return item * me.serversHeight * me.itemHeight;
1189 return me.itemHeight * (me.categoriesHeight + me.serversHeight);
1191 return me.itemHeight * me.serversHeight;
string MapInfo_Type_ToString(Gametype t)
void ServerList_NameSort_Click(entity btn, entity me)
void ServerList_TypeSort_Click(entity btn, entity me)
void XonoticServerList_configureXonoticServerList(entity me)
void XonoticServerList_resizeNotify(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
bool IsServerInList(string list, string srv)
entity makeXonoticServerList()
void RegisterSLCategories()
float autocvar_menu_slist_recommendations_purethreshold
void XonoticServerList_positionSortButton(entity me, entity btn, float theOrigin, float theSize, string theTitle, void(entity, entity) theFunc)
void XonoticServerList_focusEnter(entity me)
void SL_ProcessCategoryOverrides(.string override_field_string,.float override_field)
float XonoticServerList_getItemHeight(entity me, int item)
#define SET_YRANGE(start, end)
void ServerList_Update_favoriteButton(entity btn, entity me)
void XonoticServerList_focusedItemChangeNotify(entity me)
#define REGISTRY_GET(id, i)
int category_item[MAX_CATEGORIES]
entity RetrieveCategoryEnt(int catnum)
float autocvar_menu_slist_recommendations_minfreeslots
void DialogOpenButton_Click_withCoords(entity button, entity tab, vector theOrigin, vector theSize)
const float REFRESHSERVERLIST_RESET
SLIST_FIELDS float autocvar_menu_slist_categories
#define IsRecommended(srv)
string cat_enoverride_string
const float REFRESHSERVERLIST_RESORT
#define METHOD(cname, name, prototype)
float autocvar_menu_slist_categories_onlyifmultiple
void ServerList_Categories_Click(entity box, entity me)
void ServerList_Info_Click(entity btn, entity me)
void ServerList_MapSort_Click(entity btn, entity me)
const int SERVERFLAG_PLAYERSTATS
float autocvar_menu_slist_modimpurity
const int SERVERFLAG_PLAYERSTATS_CUSTOM
float autocvar_menu_slist_purethreshold
const float REFRESHSERVERLIST_REFILTER
void ServerList_ShowEmpty_Click(entity box, entity me)
void XonoticServerList_setSortOrder(entity me, int fld, int direction)
void XonoticServerList_drawListBoxItem(entity me, int i, vector absSize, bool isSelected, bool isFocused)
float XonoticServerList_getTotalHeight(entity me)
float autocvar_menu_slist_recommendations
float autocvar_menu_slist_recommendations_minhumans
void ServerList_ShowLaggy_Click(entity box, entity me)
void ServerList_PlayerSort_Click(entity btn, entity me)
spree_cen s1 spree_cen s1 spree_cen s1 spree_cen s1 spree_cen s1 spree_cen s1 spree_cen s1 f1 s1 strcat(_("Level %s: "), "^BG%s\3\, _("^BGPress ^F2%s^BG to enter the game"))
float autocvar_menu_slist_maxping
float XonoticServerList_mouseMove(entity me, vector pos)
float m_gethostcachecategory(float entry)
void XonoticServerList_doubleClickListBoxItem(entity me, int i, vector where)
void ServerList_PingSort_Click(entity btn, entity me)
int CategoryForEntry(int entry)
void ServerList_Filter_Change(entity box, entity me)
vector(float skel, float bonenum) _skel_get_boneabs_hidden
void ServerList_ShowFull_Click(entity box, entity me)
void XonoticServerList_setSelected(entity me, int i)
int XonoticServerList_getItemAtPos(entity me, float pos)
string cat_dioverride_string
void XonoticServerList_draw(entity me)
#define tokenizebyseparator
entity categories[MAX_CATEGORIES]
const float REFRESHSERVERLIST_ASK
float XonoticServerList_getItemStart(entity me, int item)
void XonoticServerList_refreshServerList(entity me, int mode)
#define FOREACH(list, cond, body)
float autocvar_menu_slist_recommendations_maxping
void ServerList_Connect_Click(entity btn, entity me)
int category_name[MAX_CATEGORIES]
void ServerList_Favorite_Click(entity btn, entity this)
bool XonoticServerList_keyDown(entity me, int scan, bool ascii, bool shift)
int CategoryOverride(int cat)