]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blobdiff - qcsrc/menu/xonotic/serverlist.c
Some cleanup
[xonotic/xonotic-data.pk3dir.git] / qcsrc / menu / xonotic / serverlist.c
index e0260e8d3866a09788196d4526013b9dc9e581ca..69f5d47374e32fde63deb1663901505c552c5b01 100644 (file)
@@ -49,7 +49,6 @@ 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)
 
@@ -57,14 +56,25 @@ CLASS(XonoticServerList) EXTENDS(XonoticListBox)
 
        ATTRIB(XonoticServerList, seenIPv4, float, 0)
        ATTRIB(XonoticServerList, seenIPv6, float, 0)
+       ATTRIB(XonoticServerList, categoriesHeight, float, 1.25)
+
+       METHOD(XonoticServerList, getTotalHeight, float(entity))
+       METHOD(XonoticServerList, getItemAtPos, float(entity, float))
+       METHOD(XonoticServerList, getItemStart, float(entity, float))
+       METHOD(XonoticServerList, getItemHeight, float(entity, float))
 ENDCLASS(XonoticServerList)
 entity makeXonoticServerList();
 
 #ifndef IMPLEMENTATION
-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";
+float autocvar_menu_slist_categories;
+float autocvar_menu_slist_categories_onlyifmultiple; 
+float autocvar_menu_slist_purethreshold;
+float autocvar_menu_slist_modimpurity;
+float autocvar_menu_slist_recommendations;
+float autocvar_menu_slist_recommendations_maxping;
+float autocvar_menu_slist_recommendations_minfreeslots; 
+float autocvar_menu_slist_recommendations_minhumans;
+float autocvar_menu_slist_recommendations_purethreshold; 
 
 // server cache fields
 #define SLIST_FIELDS \
@@ -95,12 +105,13 @@ const float REFRESHSERVERLIST_ASK = 2;       // ..., also suggest querying serve
 const float REFRESHSERVERLIST_RESET = 3;     // ..., also clear the list first
 
 // function declarations
-entity RetrieveCategoryEnt(float catnum);
-
 float IsServerInList(string list, string srv);
 #define IsFavorite(srv) IsServerInList(cvar_string("net_slist_favorites"), srv)
+#define IsPromoted(srv) IsServerInList(_Nex_ExtResponseSystem_PromotedServers, srv)
 #define IsRecommended(srv) IsServerInList(_Nex_ExtResponseSystem_RecommendedServers, srv)
 
+entity RetrieveCategoryEnt(float catnum);
+
 float CheckCategoryOverride(float cat);
 float CheckCategoryForEntry(float entry); 
 float m_gethostcachecategory(float entry) { return CheckCategoryOverride(CheckCategoryForEntry(entry)); }
@@ -270,26 +281,80 @@ float CheckCategoryOverride(float cat)
 float CheckCategoryForEntry(float entry)
 {
        string s, k, v, modtype = "";
-       float j, m, impure = 0;
+       float j, m, impure = 0, freeslots = 0, sflags = 0;
        s = gethostcachestring(SLIST_FIELD_QCSTATUS, entry);
        m = tokenizebyseparator(s, ":");
 
        for(j = 2; j < m; ++j)
        {
-               if(argv(j) == "")
-                       break;
+               if(argv(j) == "") { break; }
                k = substring(argv(j), 0, 1);
                v = substring(argv(j), 1, -1);
-               if(k == "P") { impure = stof(v); }
-               else if(k == "M") { modtype = strtolower(v); }
+               switch(k)
+               {
+                       case "P": { impure = stof(v); break; }
+                       case "S": { freeslots = stof(v); break; }
+                       case "F": { sflags = stof(v); break; }
+                       case "M": { modtype = strtolower(v); break; }
+               }
        }
 
-       if(impure > autocvar_menu_slist_purethreshold) { impure = TRUE; }
-       else { impure = FALSE; }
+       if(modtype != "xonotic") { impure += autocvar_menu_slist_modimpurity; }
 
+       // check if this server is favorited
        if(gethostcachenumber(SLIST_FIELD_ISFAVORITE, entry)) { return CAT_FAVORITED; }
-       if(IsRecommended(gethostcachestring(SLIST_FIELD_CNAME, entry))) { return CAT_RECOMMENDED; }
-       else if(modtype != "xonotic")
+
+       // now check if it's recommended
+       if(autocvar_menu_slist_recommendations)
+       {
+               string cname = gethostcachestring(SLIST_FIELD_CNAME, entry);
+               
+               if(IsPromoted(cname)) { return CAT_RECOMMENDED; }
+               else
+               {
+                       float recommended = 0;
+                       if(autocvar_menu_slist_recommendations & 1)
+                       {
+                               if(IsRecommended(cname)) { ++recommended; }
+                               else { --recommended; }
+                       }
+                       if(autocvar_menu_slist_recommendations & 2)
+                       {
+                               if(
+                                       ///// check for minimum free slots
+                                       (freeslots >= autocvar_menu_slist_recommendations_minfreeslots)
+                                       
+                                       && // check for purity requirement
+                                       (
+                                               (autocvar_menu_slist_recommendations_purethreshold < 0)
+                                               ||
+                                               (impure <= autocvar_menu_slist_recommendations_purethreshold)
+                                       )
+                                       
+                                       && // check for minimum amount of humans
+                                       (
+                                               gethostcachenumber(SLIST_FIELD_NUMHUMANS, entry)
+                                               >=
+                                               autocvar_menu_slist_recommendations_minhumans
+                                       )
+                                       
+                                       && // check for maximum latency
+                                       (
+                                               gethostcachenumber(SLIST_FIELD_PING, entry)
+                                               <=
+                                               autocvar_menu_slist_recommendations_maxping
+                                       )
+                               )
+                                       { ++recommended; }
+                               else
+                                       { --recommended; }
+                       }
+                       if(recommended > 0) { return CAT_RECOMMENDED; }
+               }
+       }
+
+       // if not favorited or recommended, check modname
+       if(modtype != "xonotic")
        {
                switch(modtype)
                {
@@ -309,29 +374,9 @@ float CheckCategoryForEntry(float entry)
                        default: { dprint(sprintf("Found strange mod type: %s\n", modtype)); return CAT_MODIFIED; }
                }
        }
-       else { return (impure ? CAT_MODIFIED : CAT_NORMAL); }
-
-       // should never hit this point
-       error(sprintf("CheckCategoryForEntry(%d): Function fell through without normal return!\n", entry));
-       return FALSE;
-}
-
-float CheckItemNumber(float num)
-{
-       float i, n;
-
-       if not(category_draw_count) { return num; } // there are no categories to process
-
-       for(i = 0, n = 1; n <= category_draw_count; ++i, ++n)
-       {
-               if(category_item[i] == (num - i)) { return -category_name[i]; }
-               else if(n == category_draw_count) { return (num - n); }
-               else if((num - i) <= category_item[n]) { return (num - n); }
-       }
 
-       // should never hit this point
-       error(sprintf("CheckItemNumber(%d): Function fell through without normal return!\n", num));
-       return FALSE;
+       // must be normal or impure server
+       return ((impure > autocvar_menu_slist_purethreshold) ? CAT_MODIFIED : CAT_NORMAL);
 }
 
 void XonoticServerList_toggleFavorite(entity me, string srv)
@@ -388,7 +433,7 @@ void ServerList_Update_favoriteButton(entity btn, entity me)
 {
        me.favoriteButton.setText(me.favoriteButton,
                (IsFavorite(me.ipAddressBox.text) ?
-                       _("Remove") : _("Bookmark")
+                       _("Remove") : _("Favorite")
                )
        );
 }
@@ -411,14 +456,10 @@ void XonoticServerList_configureXonoticServerList(entity me)
 
        // clear list
        me.nItems = 0;
-
-       // build categories
-       RegisterSLCategories();
 }
 void XonoticServerList_setSelected(entity me, float i)
 {
-       // todo: add logic to skip categories
-       float save, num;
+       float save;
        save = me.selectedItem;
        SUPER(XonoticServerList).setSelected(me, i);
        /*
@@ -427,58 +468,17 @@ void XonoticServerList_setSelected(entity me, float i)
        */
        if(me.nItems == 0)
                return;
+       if(gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT) != me.nItems)
+               return; // sorry, it would be wrong
 
-       //if(gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT) != CheckItemNumber(me.nItems))
-       //      { error("^1XonoticServerList_setSelected(); ERROR: ^7Host cache viewcount mismatches nItems!\n"); return; } // sorry, it would be wrong
-       // ^ 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 = CheckItemNumber(me.selectedItem);
+       if(me.selectedServer)
+               strunzone(me.selectedServer);
+       me.selectedServer = strzone(gethostcachestring(SLIST_FIELD_CNAME, me.selectedItem));
 
-       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 = CheckItemNumber(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 = CheckItemNumber(me.selectedItem);
-                               if(num >= 0)
-                               {
-                                       me.lastBumpSelectTime = time;
-                                       SET_SELECTED_SERVER(num);
-                               }
-                       }
-               }
-       }
+       me.ipAddressBox.setText(me.ipAddressBox, me.selectedServer);
+       me.ipAddressBox.cursorPos = strlen(me.selectedServer);
+       me.ipAddressBoxFocused = -1;
 }
-
 void XonoticServerList_refreshServerList(entity me, float mode)
 {
        //print("refresh of type ", ftos(mode), "\n");
@@ -573,7 +573,7 @@ void XonoticServerList_focusEnter(entity me)
 
 void XonoticServerList_draw(entity me)
 {
-       float i, found, owned, num;
+       float i, found, owned;
 
        if(_Nex_ExtResponseSystem_BannedServersNeedsRefresh)
        {
@@ -582,6 +582,13 @@ void XonoticServerList_draw(entity me)
                _Nex_ExtResponseSystem_BannedServersNeedsRefresh = 0;
        }
 
+       if(_Nex_ExtResponseSystem_PromotedServersNeedsRefresh)
+       {
+               if(!me.needsRefresh)
+                       me.needsRefresh = 3;
+               _Nex_ExtResponseSystem_PromotedServersNeedsRefresh = 0;
+       }
+
        if(_Nex_ExtResponseSystem_RecommendedServersNeedsRefresh)
        {
                if(!me.needsRefresh)
@@ -623,31 +630,74 @@ void XonoticServerList_draw(entity me)
                // ^ unfortunately no such optimization can be made-- we must process through the
                // entire list, otherwise there is no way to know which item is first in its category.
 
-               float cat, x;
-               for(i = 0; i < itemcount; ++i)
-               {
-                       cat = gethostcachenumber(SLIST_FIELD_CATEGORY, i);
-                       if(cat)
-                       {
-                               if(category_draw_count == 0)
-                               {
-                                       category_name[category_draw_count] = cat;
-                                       category_item[category_draw_count] = i;
+               // binary search method suggested by div
+               float x;
+               float begin = 0;
+               for(x = 1; x <= category_ent_count; ++x) {
+                       float first = begin;
+                       float last = (itemcount - 1);
+                       if (first > last) {
+                               // List is empty.
+                               break;
+                       }
+                       float catf = gethostcachenumber(SLIST_FIELD_CATEGORY, first);
+                       float catl = gethostcachenumber(SLIST_FIELD_CATEGORY, last);
+                       if (catf > x) {
+                               // The first one is already > x.
+                               // Therefore, category x does not exist.
+                               // Higher numbered categories do exist though.
+                       } else if (catl < x) {
+                               // The last one is < x.
+                               // Thus this category - and any following -
+                               // don't exist.
+                               break;
+                       } else if (catf == x) {
+                               // Starts at first. This breaks the loop
+                               // invariant in the binary search and thus has
+                               // to be handled separately.
+                               if(gethostcachenumber(SLIST_FIELD_CATEGORY, first) != x)
+                                       error("Category mismatch I");
+                               if(first > 0)
+                                       if(gethostcachenumber(SLIST_FIELD_CATEGORY, first - 1) == x)
+                                               error("Category mismatch II");
+                               category_name[category_draw_count] = x;
+                               category_item[category_draw_count] = first;
+                               ++category_draw_count;
+                               begin = first + 1;
+                       } else {
+                               // At this point, catf <= x < catl, thus
+                               // catf < catl, thus first < last.
+                               // INVARIANTS:
+                               // last - first >= 1
+                               // catf == gethostcachenumber(SLIST_FIELD_CATEGORY(first)
+                               // catl == gethostcachenumber(SLIST_FIELD_CATEGORY(last)
+                               // catf < x
+                               // catl >= x
+                               while (last - first > 1) {
+                                       float middle = floor((first + last) / 2);
+                                       // By loop condition, middle != first && middle != last.
+                                       float cat = gethostcachenumber(SLIST_FIELD_CATEGORY, middle);
+                                       if (cat >= x) {
+                                               last = middle;
+                                               catl = cat;
+                                       } else {
+                                               first = middle;
+                                               catf = cat;
+                                       }
+                               }
+                               if (catl == x) {
+                                       if(gethostcachenumber(SLIST_FIELD_CATEGORY, last) != x)
+                                               error("Category mismatch III");
+                                       if(last > 0)
+                                               if(gethostcachenumber(SLIST_FIELD_CATEGORY, last - 1) == x)
+                                                       error("Category mismatch IV");
+                                       category_name[category_draw_count] = x;
+                                       category_item[category_draw_count] = last;
                                        ++category_draw_count;
-                                       ++me.nItems;
+                                       begin = last + 1; // already scanned through these, skip 'em
                                }
                                else
-                               {
-                                       found = 0;
-                                       for(x = 0; x < category_draw_count; ++x) { if(cat == category_name[x]) { found = 1; } }
-                                       if not(found)
-                                       {
-                                               category_name[category_draw_count] = cat;
-                                               category_item[category_draw_count] = i;
-                                               ++category_draw_count;
-                                               ++me.nItems;
-                                       }
-                               }
+                                       begin = last; // already scanned through these, skip 'em
                        }
                }
                if(autocvar_menu_slist_categories_onlyifmultiple && (category_draw_count == 1))
@@ -669,19 +719,15 @@ void XonoticServerList_draw(entity me)
        {
                for(i = 0; i < me.nItems; ++i)
                {
-                       num = CheckItemNumber(i);
-                       if(num >= 0)
+                       if(gethostcachestring(SLIST_FIELD_CNAME, i) == me.selectedServer)
                        {
-                               if(gethostcachestring(SLIST_FIELD_CNAME, num) == me.selectedServer)
+                               if(i != me.selectedItem)
                                {
-                                       if(i != me.selectedItem)
-                                       {
-                                               me.lastClickedServer = -1;
-                                               me.selectedItem = i;
-                                       }
-                                       found = 1;
-                                       break;
+                                       me.lastClickedServer = -1;
+                                       me.selectedItem = i;
                                }
+                               found = 1;
+                               break;
                        }
                }
        }
@@ -689,11 +735,11 @@ void XonoticServerList_draw(entity me)
        {
                if(me.nItems > 0)
                {
-                       if(me.selectedItem >= me.nItems) { me.selectedItem = me.nItems - 1; }
-                       if(me.selectedServer) { strunzone(me.selectedServer); }
-
-                       num = CheckItemNumber(me.selectedItem);
-                       if(num >= 0) { me.selectedServer = strzone(gethostcachestring(SLIST_FIELD_CNAME, num)); }
+                       if(me.selectedItem >= me.nItems)
+                               me.selectedItem = me.nItems - 1;
+                       if(me.selectedServer)
+                               strunzone(me.selectedServer);
+                       me.selectedServer = strzone(gethostcachestring(SLIST_FIELD_CNAME, me.selectedItem));
                }
        }
        
@@ -795,13 +841,6 @@ void ServerList_Filter_Change(entity box, entity me)
 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, "");
@@ -915,23 +954,20 @@ void ServerList_Favorite_Click(entity btn, entity me)
 }
 void ServerList_Info_Click(entity btn, entity me)
 {
-       main.serverInfoDialog.loadServerInfo(main.serverInfoDialog, CheckItemNumber(me.selectedItem));
+       if (me.nItems != 0)
+               main.serverInfoDialog.loadServerInfo(main.serverInfoDialog, me.selectedItem);
        DialogOpenButton_Click(me, main.serverInfoDialog);
 }
 void XonoticServerList_clickListBoxItem(entity me, float i, vector where)
 {
-       float num = CheckItemNumber(i);
-       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;
-       }
+       if(i == me.lastClickedServer)
+               if(time < me.lastClickedTime + 0.3)
+               {
+                       // DOUBLE CLICK!
+                       ServerList_Connect_Click(NULL, me);
+               }
+       me.lastClickedServer = i;
+       me.lastClickedTime = time;
 }
 void XonoticServerList_drawListBoxItem(entity me, float i, vector absSize, float isSelected)
 {
@@ -943,32 +979,52 @@ void XonoticServerList_drawListBoxItem(entity me, float i, vector absSize, float
        float m, pure, freeslots, j, sflags;
        string s, typestr, versionstr, k, v, modname;
 
-       float item = CheckItemNumber(i);
        //print(sprintf("time: %f, i: %d, item: %d, nitems: %d\n", time, i, item, me.nItems));
-       
-       if(item < 0)
+
+       vector oldscale = draw_scale;
+       vector oldshift = draw_shift;
+#define SET_YRANGE(start,end) \
+       draw_scale = boxToGlobalSize(eX * 1 + eY * (end - start), oldscale); \
+       draw_shift = boxToGlobal(eY * start, oldshift, oldscale);
+
+       for (j = 0; j < category_draw_count; ++j) {
+               // Matches exactly the headings with increased height.
+               if (i == category_item[j])
+                       break;
+       }
+
+       if (j < category_draw_count)
        {
-               entity catent = RetrieveCategoryEnt(-item);
+               entity catent = RetrieveCategoryEnt(category_name[j]);
                if(catent)
                {
+                       SET_YRANGE(
+                               (me.categoriesHeight - 1) / (me.categoriesHeight + 1),
+                               me.categoriesHeight / (me.categoriesHeight + 1)
+                       );
                        draw_Text(
                                eY * me.realUpperMargin
                                +
+#if 0
                                eX * (me.columnNameOrigin + (me.columnNameSize - draw_TextWidth(catent.cat_string, 0, me.realFontSize)) * 0.5),
                                catent.cat_string,
+#else
+                               eX * (me.columnNameOrigin),
+                               strcat(catent.cat_string, ":"),
+#endif
                                me.realFontSize,
                                '1 1 1',
                                SKINALPHA_TEXT,
                                0
                        );
-                       return;
+                       SET_YRANGE(me.categoriesHeight / (me.categoriesHeight + 1), 1);
                }
        }
        
        if(isSelected)
                draw_Fill('0 0 0', '1 1 0', SKINCOLOR_LISTBOX_SELECTED, SKINALPHA_LISTBOX_SELECTED);
 
-       s = gethostcachestring(SLIST_FIELD_QCSTATUS, item);
+       s = gethostcachestring(SLIST_FIELD_QCSTATUS, i);
        m = tokenizebyseparator(s, ":");
        typestr = "";
        if(m >= 2)
@@ -1003,7 +1059,7 @@ void XonoticServerList_drawListBoxItem(entity me, float i, vector absSize, float
 
        /*
        SLIST_FIELD_MOD = gethostcacheindexforkey("mod");
-       s = gethostcachestring(SLIST_FIELD_MOD, item);
+       s = gethostcachestring(SLIST_FIELD_MOD, i);
        if(s != "data")
                if(modname == "Xonotic")
                        modname = s;
@@ -1017,16 +1073,16 @@ void XonoticServerList_drawListBoxItem(entity me, float i, vector absSize, float
        if(modname != "NewToys")
                pure = 0;
 
-       if(gethostcachenumber(SLIST_FIELD_FREESLOTS, item) <= 0)
+       if(gethostcachenumber(SLIST_FIELD_FREESLOTS, i) <= 0)
                theAlpha = SKINALPHA_SERVERLIST_FULL;
        else if(freeslots == 0)
                theAlpha = SKINALPHA_SERVERLIST_FULL; // g_maxplayers support
-       else if not(gethostcachenumber(SLIST_FIELD_NUMHUMANS, item))
+       else if not(gethostcachenumber(SLIST_FIELD_NUMHUMANS, i))
                theAlpha = SKINALPHA_SERVERLIST_EMPTY;
        else
                theAlpha = 1;
 
-       p = gethostcachenumber(SLIST_FIELD_PING, item);
+       p = gethostcachenumber(SLIST_FIELD_PING, i);
 #define PING_LOW 75
 #define PING_MED 200
 #define PING_HIGH 500
@@ -1045,13 +1101,13 @@ void XonoticServerList_drawListBoxItem(entity me, float i, vector absSize, float
                theAlpha *= SKINALPHA_SERVERLIST_HIGHPING;
        }
 
-       if(gethostcachenumber(SLIST_FIELD_ISFAVORITE, item))
+       if(gethostcachenumber(SLIST_FIELD_ISFAVORITE, i))
        {
                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, item);
+       s = gethostcachestring(SLIST_FIELD_CNAME, i);
 
        isv4 = isv6 = 0;
        if(substring(s, 0, 1) == "[")
@@ -1166,11 +1222,11 @@ void XonoticServerList_drawListBoxItem(entity me, float i, vector absSize, float
        draw_Text(me.realUpperMargin * eY + (me.columnPingOrigin + me.columnPingSize - draw_TextWidth(s, 0, me.realFontSize)) * eX, s, me.realFontSize, theColor, theAlpha, 0);
 
        // server name
-       s = draw_TextShortenToWidth(gethostcachestring(SLIST_FIELD_NAME, item), me.columnNameSize, 0, me.realFontSize);
+       s = draw_TextShortenToWidth(gethostcachestring(SLIST_FIELD_NAME, i), me.columnNameSize, 0, me.realFontSize);
        draw_Text(me.realUpperMargin * eY + me.columnNameOrigin * eX, s, me.realFontSize, theColor, theAlpha, 0);
 
        // server map
-       s = draw_TextShortenToWidth(gethostcachestring(SLIST_FIELD_MAP, item), me.columnMapSize, 0, me.realFontSize);
+       s = draw_TextShortenToWidth(gethostcachestring(SLIST_FIELD_MAP, i), 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
@@ -1178,20 +1234,17 @@ void XonoticServerList_drawListBoxItem(entity me, float i, vector absSize, float
        draw_Text(me.realUpperMargin * eY + (me.columnTypeOrigin + (me.columnTypeSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
 
        // server playercount
-       s = strcat(ftos(gethostcachenumber(SLIST_FIELD_NUMHUMANS, item)), "/", ftos(gethostcachenumber(SLIST_FIELD_MAXPLAYERS, item)));
+       s = strcat(ftos(gethostcachenumber(SLIST_FIELD_NUMHUMANS, i)), "/", ftos(gethostcachenumber(SLIST_FIELD_MAXPLAYERS, i)));
        draw_Text(me.realUpperMargin * eY + (me.columnPlayersOrigin + (me.columnPlayersSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
 }
 
 float XonoticServerList_keyDown(entity me, float scan, float ascii, float shift)
 {
-       float i = CheckItemNumber(me.selectedItem);
        vector org, sz;
 
        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);
@@ -1199,9 +1252,9 @@ float XonoticServerList_keyDown(entity me, float scan, float ascii, float shift)
        }
        else if(scan == K_MOUSE2 || scan == K_SPACE)
        {
-               if((me.nItems != 0) && (i >= 0))
+               if(me.nItems != 0)
                {
-                       main.serverInfoDialog.loadServerInfo(main.serverInfoDialog, i);
+                       main.serverInfoDialog.loadServerInfo(main.serverInfoDialog, me.selectedItem);
                        DialogOpenButton_Click_withCoords(me, main.serverInfoDialog, org, sz);
                        return 1;
                }
@@ -1209,7 +1262,7 @@ float XonoticServerList_keyDown(entity me, float scan, float ascii, float shift)
        }
        else if(scan == K_INS || scan == K_MOUSE3 || scan == K_KP_INS)
        {
-               if((me.nItems != 0) && (i >= 0))
+               if(me.nItems != 0)
                {
                        me.toggleFavorite(me, me.selectedServer);
                        me.ipAddressBoxFocused = -1;
@@ -1224,4 +1277,47 @@ float XonoticServerList_keyDown(entity me, float scan, float ascii, float shift)
        else
                return me.controlledTextbox.keyDown(me.controlledTextbox, scan, ascii, shift);
 }
+
+float XonoticServerList_getTotalHeight(entity me) {
+       float num_normal_rows = me.nItems;
+       float num_headers = category_draw_count;
+       return me.itemHeight * (num_normal_rows + me.categoriesHeight * num_headers);
+}
+float XonoticServerList_getItemAtPos(entity me, float pos) {
+       pos = pos / me.itemHeight;
+       float i;
+       for (i = category_draw_count - 1; i >= 0; --i) {
+               float itemidx = category_item[i];
+               float itempos = i * me.categoriesHeight + category_item[i];
+               if (pos >= itempos + me.categoriesHeight + 1)
+                       return itemidx + 1 + floor(pos - (itempos + me.categoriesHeight + 1));
+               if (pos >= itempos)
+                       return itemidx;
+       }
+       // No category matches? Note that category 0 is... 0. Therefore no headings exist at all.
+       return floor(pos);
+}
+float XonoticServerList_getItemStart(entity me, float item) {
+       float i;
+       for (i = category_draw_count - 1; i >= 0; --i) {
+               float itemidx = category_item[i];
+               float itempos = i * me.categoriesHeight + category_item[i];
+               if (item >= itemidx + 1)
+                       return (itempos + me.categoriesHeight + 1 + item - (itemidx + 1)) * me.itemHeight;
+               if (item >= itemidx)
+                       return itempos * me.itemHeight;
+       }
+       // No category matches? Note that category 0 is... 0. Therefore no headings exist at all.
+       return item * me.itemHeight;
+}
+float XonoticServerList_getItemHeight(entity me, float item) {
+       float i;
+       for (i = 0; i < category_draw_count; ++i) {
+               // Matches exactly the headings with increased height.
+               if (item == category_item[i])
+                       return me.itemHeight * (me.categoriesHeight + 1);
+       }
+       return me.itemHeight;
+}
+
 #endif