]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blobdiff - qcsrc/menu/xonotic/serverlist.c
comments
[xonotic/xonotic-data.pk3dir.git] / qcsrc / menu / xonotic / serverlist.c
index 5db7a0267a29965d9d7364cd38f1270ba39d30e0..00473694509d3868b8c4af90fab298008d5e0f63 100644 (file)
@@ -7,6 +7,7 @@ CLASS(XonoticServerList) EXTENDS(XonoticListBox)
        METHOD(XonoticServerList, clickListBoxItem, void(entity, float, vector))
        METHOD(XonoticServerList, resizeNotify, void(entity, vector, vector, vector, vector))
        METHOD(XonoticServerList, keyDown, float(entity, float, float, float))
+       METHOD(XonoticServerList, toggleFavorite, void(entity, string))
 
        ATTRIB(XonoticServerList, iconsSizeFactor, float, 0.85)
 
@@ -35,7 +36,7 @@ CLASS(XonoticServerList) EXTENDS(XonoticListBox)
        ATTRIB(XonoticServerList, ipAddressBox, entity, NULL)
        ATTRIB(XonoticServerList, favoriteButton, entity, NULL)
        ATTRIB(XonoticServerList, nextRefreshTime, float, 0)
-       METHOD(XonoticServerList, refreshServerList, void(entity, float)) // refresh mode: 0 = just reparametrize, 1 = send new requests, 2 = clear
+       METHOD(XonoticServerList, refreshServerList, void(entity, float)) // refresh mode: REFRESHSERVERLIST_*
        ATTRIB(XonoticServerList, needsRefresh, float, 1)
        METHOD(XonoticServerList, focusEnter, void(entity))
        METHOD(XonoticServerList, positionSortButton, void(entity, entity, float, float, string, void(entity, entity)))
@@ -48,6 +49,7 @@ CLASS(XonoticServerList) EXTENDS(XonoticListBox)
        ATTRIB(XonoticServerList, infoButton, entity, NULL)
        ATTRIB(XonoticServerList, currentSortOrder, float, 0)
        ATTRIB(XonoticServerList, currentSortField, float, -1)
+       ATTRIB(XonoticServerList, lastBumpSelectTime, float, 0)
        ATTRIB(XonoticServerList, lastClickedServer, float, -1)
        ATTRIB(XonoticServerList, lastClickedTime, float, 0)
 
@@ -59,10 +61,15 @@ ENDCLASS(XonoticServerList)
 entity makeXonoticServerList();
 
 #ifndef IMPLEMENTATION
-var float autocvar_menu_serverlist_categories = TRUE;
-var float autocvar_menu_serverlist_categories_onlyifmultiple = TRUE; 
-var float autocvar_menu_serverlist_purethreshold = 10;
-var string autocvar_menu_serverlist_recommended = "76.124.107.5:26004";
+const float REFRESHSERVERLIST_RESORT = 0;    // sort the server list again to update for changes to e.g. favorite status, categories
+const float REFRESHSERVERLIST_REFILTER = 1;  // ..., also update filter and sort criteria
+const float REFRESHSERVERLIST_ASK = 2;       // ..., also suggest querying servers now
+const float REFRESHSERVERLIST_RESET = 3;     // ..., also clear the list first
+
+var float autocvar_menu_slist_categories = TRUE;
+var float autocvar_menu_slist_categories_onlyifmultiple = TRUE; 
+var float autocvar_menu_slist_purethreshold = 10;
+var string autocvar_menu_slist_recommended = "76.124.107.5:26004";
 
 // server cache fields
 #define SLIST_FIELDS \
@@ -97,9 +104,11 @@ entity Get_Cat_Ent(float catnum);
 
 float IsServerInList(string list, string srv);
 #define IsFavorite(srv) IsServerInList(cvar_string("net_slist_favorites"), srv)
-#define IsRecommended(srv) IsServerInList(cvar_string("menu_serverlist_recommended"), srv) // todo: use update notification instead of cvar
+#define IsRecommended(srv) IsServerInList(cvar_string("menu_slist_recommended"), srv) // todo: use update notification instead of cvar
 
 float CheckCategoryOverride(float cat);
+float CheckCategoryForEntry(float entry); 
+float m_getserverlistentrycategory(float entry) { return CheckCategoryOverride(CheckCategoryForEntry(entry)); }
 
 // fields for category entities
 #define MAX_CATEGORIES 9
@@ -108,8 +117,10 @@ entity categories[MAX_CATEGORIES];
 float category_ent_count;
 .string cat_name;
 .string cat_string;
-.string cat_override_string;
-.float cat_override;
+.string cat_enoverride_string;
+.string cat_dioverride_string;
+.float cat_enoverride;
+.float cat_dioverride;
 
 // fields for drawing categories
 float category_name[MAX_CATEGORIES];
@@ -128,8 +139,8 @@ float category_draw_count;
        SLIST_CATEGORY(CAT_DEFRAG,       "",            "CAT_SERVERS",  _("Defrag Mode"))
 
 // C is stupid, must use extra macro for concatenation
-#define SLIST_ADD_CAT_CVAR(name,default) var string autocvar_menu_serverlist_categories_##name##_override = default;
-#define SLIST_CATEGORY(name,enoverride,deoverride,string) \
+#define SLIST_ADD_CAT_CVAR(name,default) var string autocvar_menu_slist_categories_##name##_override = default;
+#define SLIST_CATEGORY(name,enoverride,dioverride,string) \
        SLIST_ADD_CAT_CVAR(name, enoverride) \
        float name; \
        void RegisterSLCategory_##name() \
@@ -140,10 +151,8 @@ float category_draw_count;
                categories[name - 1] = cat; \
                cat.classname = "slist_category"; \
                cat.cat_name = strzone(#name); \
-               cat.cat_override_string = strzone((autocvar_menu_serverlist_categories ? \
-                       autocvar_menu_serverlist_categories_##name##_override \
-                       : \
-                       deoverride)); \
+               cat.cat_enoverride_string = strzone(autocvar_menu_slist_categories_##name##_override); \
+               cat.cat_dioverride_string = strzone(dioverride); \
                cat.cat_string = strzone(string); \
        } \
        ACCUMULATE_FUNCTION(RegisterSLCategories, RegisterSLCategory_##name);
@@ -154,22 +163,28 @@ void RegisterSLCategories_Done()
 {
        float i, catnum;
        string s;
-       for(i = 0; i < category_ent_count; ++i)
-       {
-               s = categories[i].cat_override_string;
-               if((s != "") && (s != categories[i].cat_name))
-               {
-                       catnum = Get_Cat_Num_FromString(s);
-                       if(catnum)
-                       {
-                               strunzone(categories[i].cat_override_string);
-                               categories[i].cat_override = catnum;
-                               continue;
-                       }
+
+       #define PROCESS_OVERRIDE(override_string,override_field) \
+               for(i = 0; i < category_ent_count; ++i) \
+               { \
+                       s = categories[i].override_string; \
+                       if((s != "") && (s != categories[i].cat_name)) \
+                       { \
+                               catnum = Get_Cat_Num_FromString(s); \
+                               if(catnum) \
+                               { \
+                                       strunzone(categories[i].override_string); \
+                                       categories[i].override_field = catnum; \
+                                       continue; \
+                               } \
+                       } \
+                       strunzone(categories[i].override_string); \
+                       categories[i].override_field = 0; \
                }
-               strunzone(categories[i].cat_override_string);
-               categories[i].cat_override = 0;
-       }
+
+       PROCESS_OVERRIDE(cat_enoverride_string, cat_enoverride)
+       PROCESS_OVERRIDE(cat_dioverride_string, cat_dioverride)
+       #undef PROCESS_OVERRIDE
 }
 ACCUMULATE_FUNCTION(RegisterSLCategories, RegisterSLCategories_Done);
 
@@ -178,6 +193,7 @@ ACCUMULATE_FUNCTION(RegisterSLCategories, RegisterSLCategories_Done);
 #undef CATEGORIES
 
 void ServerList_Connect_Click(entity btn, entity me);
+void ServerList_Categories_Click(entity box, entity me);
 void ServerList_ShowEmpty_Click(entity box, entity me);
 void ServerList_ShowFull_Click(entity box, entity me);
 void ServerList_Filter_Change(entity box, entity me);
@@ -244,7 +260,8 @@ float CheckCategoryOverride(float cat)
        entity catent = Get_Cat_Ent(cat);
        if(catent)
        {
-               if(catent.cat_override) { return catent.cat_override; }
+               float override = (autocvar_menu_slist_categories ? catent.cat_enoverride : catent.cat_dioverride); 
+               if(override) { return override; }
                else { return cat; }
        }
        else
@@ -254,22 +271,13 @@ float CheckCategoryOverride(float cat)
        }
 }
 
-float m_getserverlistentrycategory(float entry)
+float CheckCategoryForEntry(float entry)
 {
        string s, k, v, modtype = "";
-       float j, m, impure;
+       float j, m, impure = 0;
        s = gethostcachestring(SLIST_FIELD_QCSTATUS, entry);
        m = tokenizebyseparator(s, ":");
-       //typestr = "";
-       //if(m >= 2)
-       //{
-       //      typestr = argv(0);
-       //      versionstr = argv(1);
-       //}
-       //freeslots = -1;
-       //sflags = -1;
-       //modname = "";
-       impure = 0;
+
        for(j = 2; j < m; ++j)
        {
                if(argv(j) == "")
@@ -282,37 +290,37 @@ float m_getserverlistentrycategory(float entry)
 
        //print(sprintf("modtype = %s\n", modtype)); 
 
-       if(impure > autocvar_menu_serverlist_purethreshold) { impure = TRUE; }
+       if(impure > autocvar_menu_slist_purethreshold) { impure = TRUE; }
        else { impure = FALSE; }
 
-       if(gethostcachenumber(SLIST_FIELD_ISFAVORITE, entry)) { return CheckCategoryOverride(CAT_FAVORITED); }
-       if(IsRecommended(gethostcachestring(SLIST_FIELD_CNAME, entry))) { return CheckCategoryOverride(CAT_RECOMMENDED); }
+       if(gethostcachenumber(SLIST_FIELD_ISFAVORITE, entry)) { return CAT_FAVORITED; }
+       if(IsRecommended(gethostcachestring(SLIST_FIELD_CNAME, entry))) { return CAT_RECOMMENDED; }
        else if(modtype != "xonotic")
        {
                switch(modtype)
                {
                        // old servers which don't report their mod name are considered modified now
-                       case "": { return CheckCategoryOverride(CAT_MODIFIED); }
+                       case "": { return CAT_MODIFIED; }
                        
-                       case "xpm": { return CheckCategoryOverride(CAT_XPM); } 
-                       case "minstagib": { return CheckCategoryOverride(CAT_MINSTAGIB); }
-                       case "overkill": { return CheckCategoryOverride(CAT_OVERKILL); }
+                       case "xpm": { return CAT_XPM; } 
+                       case "minstagib": { return CAT_MINSTAGIB; }
+                       case "overkill": { return CAT_OVERKILL; }
 
                        // "cts" is allowed as compat, xdf is replacement
                        case "cts": 
-                       case "xdf": { return CheckCategoryOverride(CAT_DEFRAG); }
+                       case "xdf": { return CAT_DEFRAG; }
                        
                        //if(modname != "CTS")
                        //if(modname != "NIX")
                        //if(modname != "NewToys")
                        
-                       default: { print(sprintf("Found strange mod type: %s\n", modtype)); return CheckCategoryOverride(CAT_MODIFIED); }
+                       default: { dprint(sprintf("Found strange mod type: %s\n", modtype)); return CAT_MODIFIED; }
                }
        }
-       else { return CheckCategoryOverride((impure ? CAT_MODIFIED : CAT_NORMAL)); }
+       else { return (impure ? CAT_MODIFIED : CAT_NORMAL); }
 
        // should never hit this point
-       error("wtf m_getserverlistentrycategory fail?");
+       error(sprintf("CheckCategoryForEntry(%d): Function fell through without normal return!\n", entry));
        return FALSE;
 }
 
@@ -335,7 +343,7 @@ float XonoticServerList_MapItems(float num)
        return FALSE;
 }
 
-void ToggleFavorite(string srv)
+void XonoticServerList_toggleFavorite(entity me, string srv)
 {
        string s, s0, s1, s2, srv_resolved, p;
        float i, n, f;
@@ -382,7 +390,7 @@ void ToggleFavorite(string srv)
                        cvar_set("net_slist_favorites", strcat(s, s1, srv));
        }
 
-       resorthostcache();
+       me.refreshServerList(me, REFRESHSERVERLIST_RESORT);
 }
 
 void ServerList_Update_favoriteButton(entity btn, entity me)
@@ -426,107 +434,137 @@ void XonoticServerList_setSelected(entity me, float i)
        if(me.nItems == 0)
                return;
 
-       //if(XonoticServerList_MapItems(gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT)) != me.nItems)
+       //if(gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT) != XonoticServerList_MapItems(me.nItems))
        //      { error("^1XonoticServerList_setSelected(); ERROR: ^7Host cache viewcount mismatches nItems!\n"); return; } // sorry, it would be wrong
-       // todo: make this work somehow? ^
+       // ^ todo: make this work somehow?
+
+       #define SET_SELECTED_SERVER(cachenum) \
+               if(me.selectedServer) { strunzone(me.selectedServer); } \
+               me.selectedServer = strzone(gethostcachestring(SLIST_FIELD_CNAME, cachenum)); \
+               me.ipAddressBox.setText(me.ipAddressBox, me.selectedServer); \
+               me.ipAddressBox.cursorPos = strlen(me.selectedServer); \
+               me.ipAddressBoxFocused = -1; \
+               return;
 
        num = XonoticServerList_MapItems(me.selectedItem);
-       if(num >= 0)
-       {
-               if(me.selectedServer)
-                       strunzone(me.selectedServer);
-               me.selectedServer = strzone(gethostcachestring(SLIST_FIELD_CNAME, num));
 
-               me.ipAddressBox.setText(me.ipAddressBox, me.selectedServer);
-               me.ipAddressBox.cursorPos = strlen(me.selectedServer);
-               me.ipAddressBoxFocused = -1;
+       if(num >= 0) { SET_SELECTED_SERVER(num); }
+       else if(save > me.selectedItem)
+       {
+               if(me.selectedItem == 0) { return; }
+               else
+               {
+                       if(me.lastClickedTime >= me.lastBumpSelectTime)
+                       {
+                               SUPER(XonoticServerList).setSelected(me, me.selectedItem - 1);
+                               num = XonoticServerList_MapItems(me.selectedItem);
+                               if(num >= 0)
+                               {
+                                       me.lastBumpSelectTime = time;
+                                       SET_SELECTED_SERVER(num);
+                               }
+                       }
+               }
+       }
+       else if(save < me.selectedItem)
+       {
+               if(me.selectedItem == me.nItems) { return; }
+               else
+               {
+                       if(me.lastClickedTime >= me.lastBumpSelectTime)
+                       {
+                               SUPER(XonoticServerList).setSelected(me, me.selectedItem + 1);
+                               num = XonoticServerList_MapItems(me.selectedItem);
+                               if(num >= 0)
+                               {
+                                       me.lastBumpSelectTime = time;
+                                       SET_SELECTED_SERVER(num);
+                               }
+                       }
+               }
        }
 }
+
 void XonoticServerList_refreshServerList(entity me, float mode)
 {
-       // 0: just reparametrize
-       // 1: also ask for new servers
-       // 2: clear
        //print("refresh of type ", ftos(mode), "\n");
-       /* if(mode == 2) // borken
-       {
-               // clear list
-               localcmd("net_slist\n");
-               me.needsRefresh = 1; // net_slist kills sort order, so we need to restore it later
-       }
-       else */
-       
-       float m, i, n;
-       float listflags = 0;
-       string s, typestr, modstr;
-       s = me.filterString;
 
-       m = strstrofs(s, ":", 0);
-       if(m >= 0)
+       if(mode >= REFRESHSERVERLIST_REFILTER)
        {
-               typestr = substring(s, 0, m);
-               s = substring(s, m + 1, strlen(s) - m - 1);
-               while(substring(s, 0, 1) == " ")
-                       s = substring(s, 1, strlen(s) - 1);
-       }
-       else
-               typestr = "";
+               float m, i, n;
+               float listflags = 0;
+               string s, typestr, modstr;
 
-       modstr = cvar_string("menu_slist_modfilter");
+               s = me.filterString;
 
-       m = SLIST_MASK_AND - 1;
-       resethostcachemasks();
+               m = strstrofs(s, ":", 0);
+               if(m >= 0)
+               {
+                       typestr = substring(s, 0, m);
+                       s = substring(s, m + 1, strlen(s) - m - 1);
+                       while(substring(s, 0, 1) == " ")
+                               s = substring(s, 1, strlen(s) - 1);
+               }
+               else
+                       typestr = "";
 
-       // ping: reject negative ping (no idea why this happens in the first place, engine bug)
-       sethostcachemasknumber(++m, SLIST_FIELD_PING, 0, SLIST_TEST_GREATEREQUAL);
+               modstr = cvar_string("menu_slist_modfilter");
 
-       // show full button
-       if(!me.filterShowFull)
-       {
-               sethostcachemasknumber(++m, SLIST_FIELD_FREESLOTS, 1, SLIST_TEST_GREATEREQUAL); // legacy
-               sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, ":S0:", SLIST_TEST_NOTCONTAIN); // g_maxplayers support
-       }
+               m = SLIST_MASK_AND - 1;
+               resethostcachemasks();
 
-       // show empty button
-       if(!me.filterShowEmpty)
-               sethostcachemasknumber(++m, SLIST_FIELD_NUMHUMANS, 1, SLIST_TEST_GREATEREQUAL);
+               // ping: reject negative ping (no idea why this happens in the first place, engine bug)
+               sethostcachemasknumber(++m, SLIST_FIELD_PING, 0, SLIST_TEST_GREATEREQUAL);
 
-       // gametype filtering
-       if(typestr != "")
-               sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, strcat(typestr, ":"), SLIST_TEST_STARTSWITH);
+               // show full button
+               if(!me.filterShowFull)
+               {
+                       sethostcachemasknumber(++m, SLIST_FIELD_FREESLOTS, 1, SLIST_TEST_GREATEREQUAL); // legacy
+                       sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, ":S0:", SLIST_TEST_NOTCONTAIN); // g_maxplayers support
+               }
 
-       // mod filtering
-       if(modstr != "")
-       {
-               if(substring(modstr, 0, 1) == "!")
-                       sethostcachemaskstring(++m, SLIST_FIELD_MOD, resolvemod(substring(modstr, 1, strlen(modstr) - 1)), SLIST_TEST_NOTEQUAL);
-               else
-                       sethostcachemaskstring(++m, SLIST_FIELD_MOD, resolvemod(modstr), SLIST_TEST_EQUAL);
-       }
+               // show empty button
+               if(!me.filterShowEmpty)
+                       sethostcachemasknumber(++m, SLIST_FIELD_NUMHUMANS, 1, SLIST_TEST_GREATEREQUAL);
 
-       // server banning
-       n = tokenizebyseparator(_Nex_ExtResponseSystem_BannedServers, " ");
-       for(i = 0; i < n; ++i)
-               if(argv(i) != "")
-                       sethostcachemaskstring(++m, SLIST_FIELD_CNAME, argv(i), SLIST_TEST_NOTSTARTSWITH);
+               // gametype filtering
+               if(typestr != "")
+                       sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, strcat(typestr, ":"), SLIST_TEST_STARTSWITH);
 
-       m = SLIST_MASK_OR - 1;
-       if(s != "")
-       {
-               sethostcachemaskstring(++m, SLIST_FIELD_NAME, s, SLIST_TEST_CONTAINS);
-               sethostcachemaskstring(++m, SLIST_FIELD_MAP, s, SLIST_TEST_CONTAINS);
-               sethostcachemaskstring(++m, SLIST_FIELD_PLAYERS, s, SLIST_TEST_CONTAINS);
-               sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, strcat(s, ":"), SLIST_TEST_STARTSWITH);
-       }
+               // mod filtering
+               if(modstr != "")
+               {
+                       if(substring(modstr, 0, 1) == "!")
+                               sethostcachemaskstring(++m, SLIST_FIELD_MOD, resolvemod(substring(modstr, 1, strlen(modstr) - 1)), SLIST_TEST_NOTEQUAL);
+                       else
+                               sethostcachemaskstring(++m, SLIST_FIELD_MOD, resolvemod(modstr), SLIST_TEST_EQUAL);
+               }
+
+               // server banning
+               n = tokenizebyseparator(_Nex_ExtResponseSystem_BannedServers, " ");
+               for(i = 0; i < n; ++i)
+                       if(argv(i) != "")
+                               sethostcachemaskstring(++m, SLIST_FIELD_CNAME, argv(i), SLIST_TEST_NOTSTARTSWITH);
 
-       // sorting flags
-       //listflags |= SLSF_FAVORITES;
-       listflags |= SLSF_CATEGORIES;
-       if(me.currentSortOrder < 0) { listflags |= SLSF_DESCENDING; }
-       sethostcachesort(me.currentSortField, listflags);
+               m = SLIST_MASK_OR - 1;
+               if(s != "")
+               {
+                       sethostcachemaskstring(++m, SLIST_FIELD_NAME, s, SLIST_TEST_CONTAINS);
+                       sethostcachemaskstring(++m, SLIST_FIELD_MAP, s, SLIST_TEST_CONTAINS);
+                       sethostcachemaskstring(++m, SLIST_FIELD_PLAYERS, s, SLIST_TEST_CONTAINS);
+                       sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, strcat(s, ":"), SLIST_TEST_STARTSWITH);
+               }
+
+               // sorting flags
+               //listflags |= SLSF_FAVORITES;
+               listflags |= SLSF_CATEGORIES;
+               if(me.currentSortOrder < 0) { listflags |= SLSF_DESCENDING; }
+               sethostcachesort(me.currentSortField, listflags);
+       }
        
        resorthostcache();
-       if(mode >= 1) { refreshhostcache(); }
+       if(mode >= REFRESHSERVERLIST_ASK)
+               refreshhostcache(mode >= REFRESHSERVERLIST_RESET);
 }
 void XonoticServerList_focusEnter(entity me)
 {
@@ -536,7 +574,7 @@ void XonoticServerList_focusEnter(entity me)
                return;
        }
        me.nextRefreshTime = time + 10;
-       me.refreshServerList(me, 1);
+       me.refreshServerList(me, REFRESHSERVERLIST_ASK);
 }
 
 void XonoticServerList_draw(entity me)
@@ -553,7 +591,7 @@ void XonoticServerList_draw(entity me)
        if(me.currentSortField == -1)
        {
                me.setSortOrder(me, SLIST_FIELD_PING, +1);
-               me.refreshServerList(me, 2);
+               me.refreshServerList(me, REFRESHSERVERLIST_RESET);
        }
        else if(me.needsRefresh == 1)
        {
@@ -562,7 +600,7 @@ void XonoticServerList_draw(entity me)
        else if(me.needsRefresh == 2)
        {
                me.needsRefresh = 0;
-               me.refreshServerList(me, 0);
+               me.refreshServerList(me, REFRESHSERVERLIST_REFILTER);
        }
 
        owned = ((me.selectedServer == me.ipAddressBox.text) && (me.ipAddressBox.text != ""));
@@ -570,7 +608,7 @@ void XonoticServerList_draw(entity me)
        for(i = 0; i < category_draw_count; ++i) { category_name[i] = -1; category_item[i] = -1; }
        category_draw_count = 0;
 
-       if(autocvar_menu_serverlist_categories >= 0) // if less than 0, don't even draw a category heading for favorites
+       if(autocvar_menu_slist_categories >= 0) // if less than 0, don't even draw a category heading for favorites
        {
                float itemcount = gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT);
                me.nItems = itemcount;
@@ -606,7 +644,7 @@ void XonoticServerList_draw(entity me)
                                }
                        }
                }
-               if(autocvar_menu_serverlist_categories_onlyifmultiple && (category_draw_count == 1))
+               if(autocvar_menu_slist_categories_onlyifmultiple && (category_draw_count == 1))
                {
                        category_name[0] = category_name[1] = -1;
                        category_item[0] = category_item[1] = -1;
@@ -614,6 +652,7 @@ void XonoticServerList_draw(entity me)
                        me.nItems = itemcount;
                }
        }
+       else { me.nItems = gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT); }
 
        me.connectButton.disabled = ((me.nItems == 0) && (me.ipAddressBox.text == ""));
        me.infoButton.disabled = ((me.nItems == 0) || !owned);
@@ -741,7 +780,23 @@ void ServerList_Filter_Change(entity box, entity me)
                me.filterString = strzone(box.text);
        else
                me.filterString = string_null;
-       me.refreshServerList(me, 0);
+       me.refreshServerList(me, REFRESHSERVERLIST_REFILTER);
+
+       me.ipAddressBox.setText(me.ipAddressBox, "");
+       me.ipAddressBox.cursorPos = 0;
+       me.ipAddressBoxFocused = -1;
+}
+void ServerList_Categories_Click(entity box, entity me)
+{
+       box.setChecked(box, autocvar_menu_slist_categories = !autocvar_menu_slist_categories);
+       ///refreshhostcache(TRUE);
+
+       //cvar_set("net_slist_pause", "0");
+       //Destroy_Category_Entities();
+       //CALL_ACCUMULATED_FUNCTION(RegisterSLCategories);
+       //me.refreshServerList(me, REFRESHSERVERLIST_REFILTER);
+
+       me.refreshServerList(me, REFRESHSERVERLIST_RESORT);
 
        me.ipAddressBox.setText(me.ipAddressBox, "");
        me.ipAddressBox.cursorPos = 0;
@@ -750,7 +805,7 @@ void ServerList_Filter_Change(entity box, entity me)
 void ServerList_ShowEmpty_Click(entity box, entity me)
 {
        box.setChecked(box, me.filterShowEmpty = !me.filterShowEmpty);
-       me.refreshServerList(me, 0);
+       me.refreshServerList(me, REFRESHSERVERLIST_REFILTER);
 
        me.ipAddressBox.setText(me.ipAddressBox, "");
        me.ipAddressBox.cursorPos = 0;
@@ -759,7 +814,7 @@ void ServerList_ShowEmpty_Click(entity box, entity me)
 void ServerList_ShowFull_Click(entity box, entity me)
 {
        box.setChecked(box, me.filterShowFull = !me.filterShowFull);
-       me.refreshServerList(me, 0);
+       me.refreshServerList(me, REFRESHSERVERLIST_REFILTER);
 
        me.ipAddressBox.setText(me.ipAddressBox, "");
        me.ipAddressBox.cursorPos = 0;
@@ -780,7 +835,7 @@ void XonoticServerList_setSortOrder(entity me, float fld, float direction)
        if(me.selectedServer)
                strunzone(me.selectedServer);
        me.selectedServer = string_null;
-       me.refreshServerList(me, 0);
+       me.refreshServerList(me, REFRESHSERVERLIST_REFILTER);
 }
 void XonoticServerList_positionSortButton(entity me, entity btn, float theOrigin, float theSize, string theTitle, void(entity, entity) theFunc)
 {
@@ -836,10 +891,11 @@ void XonoticServerList_resizeNotify(entity me, vector relOrigin, vector relSize,
 }
 void ServerList_Connect_Click(entity btn, entity me)
 {
-       if(me.ipAddressBox.text == "")
-               localcmd("connect ", me.selectedServer, "\n");
-       else
-               localcmd("connect ", me.ipAddressBox.text, "\n");
+       localcmd(sprintf("connect %s\n",
+               ((me.ipAddressBox.text != "") ?
+                       me.ipAddressBox.text : me.selectedServer
+               )
+       ));
 }
 void ServerList_Favorite_Click(entity btn, entity me)
 {
@@ -847,7 +903,7 @@ void ServerList_Favorite_Click(entity btn, entity me)
        ipstr = netaddress_resolve(me.ipAddressBox.text, 26000);
        if(ipstr != "")
        {
-               ToggleFavorite(me.ipAddressBox.text);
+               me.toggleFavorite(me, me.ipAddressBox.text);
                me.ipAddressBoxFocused = -1;
        }
 }
@@ -859,14 +915,17 @@ void ServerList_Info_Click(entity btn, entity me)
 void XonoticServerList_clickListBoxItem(entity me, float i, vector where)
 {
        float num = XonoticServerList_MapItems(i);
-       if(num == me.lastClickedServer)
-               if(time < me.lastClickedTime + 0.3)
-               {
-                       // DOUBLE CLICK!
-                       ServerList_Connect_Click(NULL, me);
-               }
-       me.lastClickedServer = num;
-       me.lastClickedTime = time;
+       if(num >= 0)
+       {
+               if(num == me.lastClickedServer)
+                       if(time < me.lastClickedTime + 0.3)
+                       {
+                               // DOUBLE CLICK!
+                               ServerList_Connect_Click(NULL, me);
+                       }
+               me.lastClickedServer = num;
+               me.lastClickedTime = time;
+       }
 }
 void XonoticServerList_drawListBoxItem(entity me, float i, vector absSize, float isSelected)
 {
@@ -878,19 +937,32 @@ void XonoticServerList_drawListBoxItem(entity me, float i, vector absSize, float
        float m, pure, freeslots, j, sflags;
        string s, typestr, versionstr, k, v, modname;
 
-       float cache = XonoticServerList_MapItems(i);
-       //print(sprintf("time: %f, i: %d, cache: %d, nitems: %d\n", time, i, cache, me.nItems));
+       float item = XonoticServerList_MapItems(i);
+       //print(sprintf("time: %f, i: %d, item: %d, nitems: %d\n", time, i, item, me.nItems));
        
-       if(cache < 0)
+       if(item < 0)
        {
-               entity catent = Get_Cat_Ent(-cache);
-               if(catent) { draw_Text(me.realUpperMargin * eY + (me.columnNameOrigin + (me.columnNameSize - draw_TextWidth(catent.cat_string, 0, me.realFontSize)) * 0.5) * eX, catent.cat_string, me.realFontSize, '1 1 1', SKINALPHA_TEXT, 0); return; }
+               entity catent = Get_Cat_Ent(-item);
+               if(catent)
+               {
+                       draw_Text(
+                               eY * me.realUpperMargin
+                               +
+                               eX * (me.columnNameOrigin + (me.columnNameSize - draw_TextWidth(catent.cat_string, 0, me.realFontSize)) * 0.5),
+                               catent.cat_string,
+                               me.realFontSize,
+                               '1 1 1',
+                               SKINALPHA_TEXT,
+                               0
+                       );
+                       return;
+               }
        }
        
        if(isSelected)
                draw_Fill('0 0 0', '1 1 0', SKINCOLOR_LISTBOX_SELECTED, SKINALPHA_LISTBOX_SELECTED);
 
-       s = gethostcachestring(SLIST_FIELD_QCSTATUS, cache);
+       s = gethostcachestring(SLIST_FIELD_QCSTATUS, item);
        m = tokenizebyseparator(s, ":");
        typestr = "";
        if(m >= 2)
@@ -925,7 +997,7 @@ void XonoticServerList_drawListBoxItem(entity me, float i, vector absSize, float
 
        /*
        SLIST_FIELD_MOD = gethostcacheindexforkey("mod");
-       s = gethostcachestring(SLIST_FIELD_MOD, cache);
+       s = gethostcachestring(SLIST_FIELD_MOD, item);
        if(s != "data")
                if(modname == "Xonotic")
                        modname = s;
@@ -939,16 +1011,16 @@ void XonoticServerList_drawListBoxItem(entity me, float i, vector absSize, float
        if(modname != "NewToys")
                pure = 0;
 
-       if(gethostcachenumber(SLIST_FIELD_FREESLOTS, cache) <= 0)
+       if(gethostcachenumber(SLIST_FIELD_FREESLOTS, item) <= 0)
                theAlpha = SKINALPHA_SERVERLIST_FULL;
        else if(freeslots == 0)
                theAlpha = SKINALPHA_SERVERLIST_FULL; // g_maxplayers support
-       else if not(gethostcachenumber(SLIST_FIELD_NUMHUMANS, cache))
+       else if not(gethostcachenumber(SLIST_FIELD_NUMHUMANS, item))
                theAlpha = SKINALPHA_SERVERLIST_EMPTY;
        else
                theAlpha = 1;
 
-       p = gethostcachenumber(SLIST_FIELD_PING, cache);
+       p = gethostcachenumber(SLIST_FIELD_PING, item);
 #define PING_LOW 75
 #define PING_MED 200
 #define PING_HIGH 500
@@ -967,13 +1039,13 @@ void XonoticServerList_drawListBoxItem(entity me, float i, vector absSize, float
                theAlpha *= SKINALPHA_SERVERLIST_HIGHPING;
        }
 
-       if(gethostcachenumber(SLIST_FIELD_ISFAVORITE, cache))
+       if(gethostcachenumber(SLIST_FIELD_ISFAVORITE, item))
        {
                theColor = theColor * (1 - SKINALPHA_SERVERLIST_FAVORITE) + SKINCOLOR_SERVERLIST_FAVORITE * SKINALPHA_SERVERLIST_FAVORITE;
                theAlpha = theAlpha * (1 - SKINALPHA_SERVERLIST_FAVORITE) + SKINALPHA_SERVERLIST_FAVORITE;
        }
 
-       s = gethostcachestring(SLIST_FIELD_CNAME, cache);
+       s = gethostcachestring(SLIST_FIELD_CNAME, item);
 
        isv4 = isv6 = 0;
        if(substring(s, 0, 1) == "[")
@@ -1016,77 +1088,91 @@ void XonoticServerList_drawListBoxItem(entity me, float i, vector absSize, float
        // 4: AES recommended and will be used
        // 5: AES required
 
-       {
-               vector iconSize = '0 0 0';
-               iconSize_y = me.realFontSize_y * me.iconsSizeFactor;
-               iconSize_x = me.realFontSize_x * me.iconsSizeFactor;
+       // --------------
+       //  RENDER ICONS
+       // --------------
+       vector iconSize = '0 0 0';
+       iconSize_y = me.realFontSize_y * me.iconsSizeFactor;
+       iconSize_x = me.realFontSize_x * me.iconsSizeFactor;
 
-               vector iconPos = '0 0 0';
-               iconPos_x = (me.columnIconsSize - 3 * iconSize_x) * 0.5;
-               iconPos_y = (1 - iconSize_y) * 0.5;
+       vector iconPos = '0 0 0';
+       iconPos_x = (me.columnIconsSize - 3 * iconSize_x) * 0.5;
+       iconPos_y = (1 - iconSize_y) * 0.5;
 
-               string n;
+       string n;
 
-               if not(me.seenIPv4 && me.seenIPv6)
-               {
-                       iconPos_x += iconSize_x * 0.5;
-               }
-               else if(me.seenIPv4 && me.seenIPv6)
-               {
-                       n = string_null;
-                       if(isv6)
-                               draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_ipv6"), 0); // PRECACHE_PIC_MIPMAP
-                       else if(isv4)
-                               draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_ipv4"), 0); // PRECACHE_PIC_MIPMAP
-                       if(n)
-                               draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
-                       iconPos_x += iconSize_x;
-               }
-
-               if(q > 0)
-               {
-                       draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_aeslevel", ftos(q)), 0); // PRECACHE_PIC_MIPMAP
+       if not(me.seenIPv4 && me.seenIPv6)
+       {
+               iconPos_x += iconSize_x * 0.5;
+       }
+       else if(me.seenIPv4 && me.seenIPv6)
+       {
+               n = string_null;
+               if(isv6)
+                       draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_ipv6"), 0); // PRECACHE_PIC_MIPMAP
+               else if(isv4)
+                       draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_ipv4"), 0); // PRECACHE_PIC_MIPMAP
+               if(n)
                        draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
-               }
                iconPos_x += iconSize_x;
+       }
 
-               if(modname == "Xonotic")
-               {
-                       if(pure == 0)
-                       {
-                               draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_pure1"), PRECACHE_PIC_MIPMAP);
-                               draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
-                       }
-               }
-               else
-               {
-                       draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_mod_", modname), PRECACHE_PIC_MIPMAP);
-                       if(draw_PictureSize(n) == '0 0 0')
-                               draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_mod_"), PRECACHE_PIC_MIPMAP);
-                       if(pure == 0)
-                               draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
-                       else
-                               draw_Picture(iconPos, n, iconSize, '1 1 1', SKINALPHA_SERVERLIST_ICON_NONPURE);
-               }
-               iconPos_x += iconSize_x;
+       if(q > 0)
+       {
+               draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_aeslevel", ftos(q)), 0); // PRECACHE_PIC_MIPMAP
+               draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
+       }
+       iconPos_x += iconSize_x;
 
-               if(sflags >= 0 && (sflags & SERVERFLAG_PLAYERSTATS))
+       if(modname == "Xonotic")
+       {
+               if(pure == 0)
                {
-                       draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_stats1"), 0); // PRECACHE_PIC_MIPMAP
+                       draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_pure1"), PRECACHE_PIC_MIPMAP);
                        draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
                }
-               iconPos_x += iconSize_x;
        }
+       else
+       {
+               draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_mod_", modname), PRECACHE_PIC_MIPMAP);
+               if(draw_PictureSize(n) == '0 0 0')
+                       draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_mod_"), PRECACHE_PIC_MIPMAP);
+               if(pure == 0)
+                       draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
+               else
+                       draw_Picture(iconPos, n, iconSize, '1 1 1', SKINALPHA_SERVERLIST_ICON_NONPURE);
+       }
+       iconPos_x += iconSize_x;
 
+       if(sflags >= 0 && (sflags & SERVERFLAG_PLAYERSTATS))
+       {
+               draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_stats1"), 0); // PRECACHE_PIC_MIPMAP
+               draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
+       }
+       iconPos_x += iconSize_x;
+       
+       // --------------
+       //  RENDER TEXT
+       // --------------
+       
+       // ping
        s = ftos(p);
        draw_Text(me.realUpperMargin * eY + (me.columnPingOrigin + me.columnPingSize - draw_TextWidth(s, 0, me.realFontSize)) * eX, s, me.realFontSize, theColor, theAlpha, 0);
-       s = draw_TextShortenToWidth(gethostcachestring(SLIST_FIELD_NAME, cache), me.columnNameSize, 0, me.realFontSize);
+
+       // server name
+       s = draw_TextShortenToWidth(gethostcachestring(SLIST_FIELD_NAME, item), me.columnNameSize, 0, me.realFontSize);
        draw_Text(me.realUpperMargin * eY + me.columnNameOrigin * eX, s, me.realFontSize, theColor, theAlpha, 0);
-       s = draw_TextShortenToWidth(gethostcachestring(SLIST_FIELD_MAP, cache), me.columnMapSize, 0, me.realFontSize);
+
+       // server map
+       s = draw_TextShortenToWidth(gethostcachestring(SLIST_FIELD_MAP, item), me.columnMapSize, 0, me.realFontSize);
        draw_Text(me.realUpperMargin * eY + (me.columnMapOrigin + (me.columnMapSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
+
+       // server gametype
        s = draw_TextShortenToWidth(typestr, me.columnTypeSize, 0, me.realFontSize);
        draw_Text(me.realUpperMargin * eY + (me.columnTypeOrigin + (me.columnTypeSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
-       s = strcat(ftos(gethostcachenumber(SLIST_FIELD_NUMHUMANS, cache)), "/", ftos(gethostcachenumber(SLIST_FIELD_MAXPLAYERS, cache)));
+
+       // server playercount
+       s = strcat(ftos(gethostcachenumber(SLIST_FIELD_NUMHUMANS, item)), "/", ftos(gethostcachenumber(SLIST_FIELD_MAXPLAYERS, item)));
        draw_Text(me.realUpperMargin * eY + (me.columnPlayersOrigin + (me.columnPlayersSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
 }
 
@@ -1098,6 +1184,8 @@ float XonoticServerList_keyDown(entity me, float scan, float ascii, float shift)
        org = boxToGlobal(eY * (me.selectedItem * me.itemHeight - me.scrollPos), me.origin, me.size);
        sz = boxToGlobalSize(eY * me.itemHeight + eX * (1 - me.controlWidth), me.size);
 
+       me.lastBumpSelectTime = 0;
+
        if(scan == K_ENTER || scan == K_KP_ENTER)
        {
                ServerList_Connect_Click(NULL, me);
@@ -1117,7 +1205,7 @@ float XonoticServerList_keyDown(entity me, float scan, float ascii, float shift)
        {
                if((me.nItems != 0) && (i >= 0))
                {
-                       ToggleFavorite(me.selectedServer);
+                       me.toggleFavorite(me, me.selectedServer);
                        me.ipAddressBoxFocused = -1;
                        return 1;
                }