]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/menu/xonotic/serverlist.c
Revert "another superslow loop. FIXME added." as this is just once per
[xonotic/xonotic-data.pk3dir.git] / qcsrc / menu / xonotic / serverlist.c
1 #ifdef INTERFACE
2 CLASS(XonoticServerList) EXTENDS(XonoticListBox)
3         METHOD(XonoticServerList, configureXonoticServerList, void(entity))
4         ATTRIB(XonoticServerList, rowsPerItem, float, 1)
5         METHOD(XonoticServerList, draw, void(entity))
6         METHOD(XonoticServerList, drawListBoxItem, void(entity, float, vector, float))
7         METHOD(XonoticServerList, clickListBoxItem, void(entity, float, vector))
8         METHOD(XonoticServerList, resizeNotify, void(entity, vector, vector, vector, vector))
9         METHOD(XonoticServerList, keyDown, float(entity, float, float, float))
10         METHOD(XonoticServerList, toggleFavorite, void(entity, string))
11
12         ATTRIB(XonoticServerList, iconsSizeFactor, float, 0.85)
13
14         ATTRIB(XonoticServerList, realFontSize, vector, '0 0 0')
15         ATTRIB(XonoticServerList, realUpperMargin, float, 0)
16         ATTRIB(XonoticServerList, columnIconsOrigin, float, 0)
17         ATTRIB(XonoticServerList, columnIconsSize, float, 0)
18         ATTRIB(XonoticServerList, columnPingOrigin, float, 0)
19         ATTRIB(XonoticServerList, columnPingSize, float, 0)
20         ATTRIB(XonoticServerList, columnNameOrigin, float, 0)
21         ATTRIB(XonoticServerList, columnNameSize, float, 0)
22         ATTRIB(XonoticServerList, columnMapOrigin, float, 0)
23         ATTRIB(XonoticServerList, columnMapSize, float, 0)
24         ATTRIB(XonoticServerList, columnTypeOrigin, float, 0)
25         ATTRIB(XonoticServerList, columnTypeSize, float, 0)
26         ATTRIB(XonoticServerList, columnPlayersOrigin, float, 0)
27         ATTRIB(XonoticServerList, columnPlayersSize, float, 0)
28
29         ATTRIB(XonoticServerList, selectedServer, string, string_null) // to restore selected server when needed
30         METHOD(XonoticServerList, setSelected, void(entity, float))
31         METHOD(XonoticServerList, setSortOrder, void(entity, float, float))
32         ATTRIB(XonoticServerList, filterShowEmpty, float, 1)
33         ATTRIB(XonoticServerList, filterShowFull, float, 1)
34         ATTRIB(XonoticServerList, filterString, string, string_null)
35         ATTRIB(XonoticServerList, controlledTextbox, entity, NULL)
36         ATTRIB(XonoticServerList, ipAddressBox, entity, NULL)
37         ATTRIB(XonoticServerList, favoriteButton, entity, NULL)
38         ATTRIB(XonoticServerList, nextRefreshTime, float, 0)
39         METHOD(XonoticServerList, refreshServerList, void(entity, float)) // refresh mode: REFRESHSERVERLIST_*
40         ATTRIB(XonoticServerList, needsRefresh, float, 1)
41         METHOD(XonoticServerList, focusEnter, void(entity))
42         METHOD(XonoticServerList, positionSortButton, void(entity, entity, float, float, string, void(entity, entity)))
43         ATTRIB(XonoticServerList, sortButton1, entity, NULL)
44         ATTRIB(XonoticServerList, sortButton2, entity, NULL)
45         ATTRIB(XonoticServerList, sortButton3, entity, NULL)
46         ATTRIB(XonoticServerList, sortButton4, entity, NULL)
47         ATTRIB(XonoticServerList, sortButton5, entity, NULL)
48         ATTRIB(XonoticServerList, connectButton, entity, NULL)
49         ATTRIB(XonoticServerList, infoButton, entity, NULL)
50         ATTRIB(XonoticServerList, currentSortOrder, float, 0)
51         ATTRIB(XonoticServerList, currentSortField, float, -1)
52         ATTRIB(XonoticServerList, lastBumpSelectTime, float, 0)
53         ATTRIB(XonoticServerList, lastClickedServer, float, -1)
54         ATTRIB(XonoticServerList, lastClickedTime, float, 0)
55
56         ATTRIB(XonoticServerList, ipAddressBoxFocused, float, -1)
57
58         ATTRIB(XonoticServerList, seenIPv4, float, 0)
59         ATTRIB(XonoticServerList, seenIPv6, float, 0)
60 ENDCLASS(XonoticServerList)
61 entity makeXonoticServerList();
62
63 #ifndef IMPLEMENTATION
64 var float autocvar_menu_slist_categories = TRUE;
65 var float autocvar_menu_slist_categories_onlyifmultiple = TRUE; 
66 var float autocvar_menu_slist_purethreshold = 10;
67 //var string autocvar_menu_slist_recommended = "76.124.107.5:26004";
68
69 // server cache fields
70 #define SLIST_FIELDS \
71         SLIST_FIELD(CNAME,       "cname") \
72         SLIST_FIELD(PING,        "ping") \
73         SLIST_FIELD(GAME,        "game") \
74         SLIST_FIELD(MOD,         "mod") \
75         SLIST_FIELD(MAP,         "map") \
76         SLIST_FIELD(NAME,        "name") \
77         SLIST_FIELD(MAXPLAYERS,  "maxplayers") \
78         SLIST_FIELD(NUMPLAYERS,  "numplayers") \
79         SLIST_FIELD(NUMHUMANS,   "numhumans") \
80         SLIST_FIELD(NUMBOTS,     "numbots") \
81         SLIST_FIELD(PROTOCOL,    "protocol") \
82         SLIST_FIELD(FREESLOTS,   "freeslots") \
83         SLIST_FIELD(PLAYERS,     "players") \
84         SLIST_FIELD(QCSTATUS,    "qcstatus") \
85         SLIST_FIELD(CATEGORY,    "category") \
86         SLIST_FIELD(ISFAVORITE,  "isfavorite")
87
88 #define SLIST_FIELD(suffix,name) float SLIST_FIELD_##suffix;
89 SLIST_FIELDS
90 #undef SLIST_FIELD
91
92 const float REFRESHSERVERLIST_RESORT = 0;    // sort the server list again to update for changes to e.g. favorite status, categories
93 const float REFRESHSERVERLIST_REFILTER = 1;  // ..., also update filter and sort criteria
94 const float REFRESHSERVERLIST_ASK = 2;       // ..., also suggest querying servers now
95 const float REFRESHSERVERLIST_RESET = 3;     // ..., also clear the list first
96
97 // function declarations
98 entity RetrieveCategoryEnt(float catnum);
99
100 float IsServerInList(string list, string srv);
101 #define IsFavorite(srv) IsServerInList(cvar_string("net_slist_favorites"), srv)
102 #define IsRecommended(srv) IsServerInList(_Nex_ExtResponseSystem_RecommendedServers, srv)
103
104 float CheckCategoryOverride(float cat);
105 float CheckCategoryForEntry(float entry); 
106 float m_gethostcachecategory(float entry) { return CheckCategoryOverride(CheckCategoryForEntry(entry)); }
107
108 void RegisterSLCategories();
109
110 void ServerList_Connect_Click(entity btn, entity me);
111 void ServerList_Categories_Click(entity box, entity me);
112 void ServerList_ShowEmpty_Click(entity box, entity me);
113 void ServerList_ShowFull_Click(entity box, entity me);
114 void ServerList_Filter_Change(entity box, entity me);
115 void ServerList_Favorite_Click(entity btn, entity me);
116 void ServerList_Info_Click(entity btn, entity me);
117 void ServerList_Update_favoriteButton(entity btn, entity me);
118
119 // fields for category entities
120 #define MAX_CATEGORIES 9
121 #define CATEGORY_FIRST 1
122 entity categories[MAX_CATEGORIES];
123 float category_ent_count;
124 .string cat_name;
125 .string cat_string;
126 .string cat_enoverride_string;
127 .string cat_dioverride_string;
128 .float cat_enoverride;
129 .float cat_dioverride;
130
131 // fields for drawing categories
132 float category_name[MAX_CATEGORIES];
133 float category_item[MAX_CATEGORIES];
134 float category_draw_count;
135
136 #define SLIST_CATEGORIES \
137         SLIST_CATEGORY(CAT_FAVORITED,    "",            "",             ZCTX(_("SLCAT^Favorites"))) \
138         SLIST_CATEGORY(CAT_RECOMMENDED,  "",            "CAT_SERVERS",  ZCTX(_("SLCAT^Recommended"))) \
139         SLIST_CATEGORY(CAT_NORMAL,       "",            "CAT_SERVERS",  ZCTX(_("SLCAT^Normal Servers"))) \
140         SLIST_CATEGORY(CAT_SERVERS,      "CAT_NORMAL",  "CAT_SERVERS",  ZCTX(_("SLCAT^Servers"))) \
141         SLIST_CATEGORY(CAT_XPM,          "CAT_NORMAL",  "CAT_SERVERS",  ZCTX(_("SLCAT^Competitive Mode"))) \
142         SLIST_CATEGORY(CAT_MODIFIED,     "",            "CAT_SERVERS",  ZCTX(_("SLCAT^Modified Servers"))) \
143         SLIST_CATEGORY(CAT_OVERKILL,     "",            "CAT_SERVERS",  ZCTX(_("SLCAT^Overkill Mode"))) \
144         SLIST_CATEGORY(CAT_MINSTAGIB,    "",            "CAT_SERVERS",  ZCTX(_("SLCAT^MinstaGib Mode"))) \
145         SLIST_CATEGORY(CAT_DEFRAG,       "",            "CAT_SERVERS",  ZCTX(_("SLCAT^Defrag Mode")))
146
147 #define SLIST_CATEGORY_AUTOCVAR(name) autocvar_menu_slist_categories_##name##_override
148 #define SLIST_CATEGORY(name,enoverride,dioverride,str) \
149         float name; \
150         var string SLIST_CATEGORY_AUTOCVAR(name) = enoverride;
151 SLIST_CATEGORIES
152 #undef SLIST_CATEGORY
153
154 #endif
155 #endif
156 #ifdef IMPLEMENTATION
157
158 void RegisterSLCategories()
159 {
160         entity cat;
161         #define SLIST_CATEGORY(name,enoverride,dioverride,str) \
162                 SET_FIELD_COUNT(name, CATEGORY_FIRST, category_ent_count) \
163                 CHECK_MAX_COUNT(name, MAX_CATEGORIES, category_ent_count, "SLIST_CATEGORY") \
164                 cat = spawn(); \
165                 categories[name - 1] = cat; \
166                 cat.classname = "slist_category"; \
167                 cat.cat_name = strzone(#name); \
168                 cat.cat_enoverride_string = strzone(SLIST_CATEGORY_AUTOCVAR(name)); \
169                 cat.cat_dioverride_string = strzone(dioverride); \
170                 cat.cat_string = strzone(str);
171         SLIST_CATEGORIES
172         #undef SLIST_CATEGORY
173
174         float i, x, catnum;
175         string s;
176
177         #define PROCESS_OVERRIDE(override_string,override_field) \
178                 for(i = 0; i < category_ent_count; ++i) \
179                 { \
180                         s = categories[i].override_string; \
181                         if((s != "") && (s != categories[i].cat_name)) \
182                         { \
183                                 catnum = 0; \
184                                 for(x = 0; x < category_ent_count; ++x) \
185                                 { if(categories[x].cat_name == s) { \
186                                         catnum = (x+1); \
187                                         break; \
188                                 } } \
189                                 if(catnum) \
190                                 { \
191                                         strunzone(categories[i].override_string); \
192                                         categories[i].override_field = catnum; \
193                                         continue; \
194                                 } \
195                                 else \
196                                 { \
197                                         print(sprintf( \
198                                                 "RegisterSLCategories(): Improper override '%s' for category '%s'!\n", \
199                                                 s, \
200                                                 categories[i].cat_name \
201                                         )); \
202                                 } \
203                         } \
204                         strunzone(categories[i].override_string); \
205                         categories[i].override_field = 0; \
206                 }
207         PROCESS_OVERRIDE(cat_enoverride_string, cat_enoverride)
208         PROCESS_OVERRIDE(cat_dioverride_string, cat_dioverride)
209         #undef PROCESS_OVERRIDE
210 }
211
212 // Supporting Functions
213 entity RetrieveCategoryEnt(float catnum)
214 {
215         if((catnum > 0) && (catnum <= category_ent_count))
216         {
217                 return categories[catnum - 1];
218         }
219         else
220         {
221                 error(sprintf("RetrieveCategoryEnt(%d): Improper category number!\n", catnum));
222                 return world;
223         }
224 }
225
226 float IsServerInList(string list, string srv)
227 {
228         string p;
229         float i, n;
230         if(srv == "")
231                 return FALSE;
232         srv = netaddress_resolve(srv, 26000);
233         if(srv == "")
234                 return FALSE;
235         p = crypto_getidfp(srv);
236         n = tokenize_console(list);
237         for(i = 0; i < n; ++i)
238         {
239                 if(substring(argv(i), 0, 1) != "[" && strlen(argv(i)) == 44 && strstrofs(argv(i), ".", 0) < 0)
240                 {
241                         if(p)
242                                 if(argv(i) == p)
243                                         return TRUE;
244                 }
245                 else
246                 {
247                         if(srv == netaddress_resolve(argv(i), 26000))
248                                 return TRUE;
249                 }
250         }
251         return FALSE;
252 }
253
254 float CheckCategoryOverride(float cat)
255 {
256         entity catent = RetrieveCategoryEnt(cat);
257         if(catent)
258         {
259                 float override = (autocvar_menu_slist_categories ? catent.cat_enoverride : catent.cat_dioverride); 
260                 if(override) { return override; }
261                 else { return cat; }
262         }
263         else
264         {
265                 error(sprintf("CheckCategoryOverride(%d): Improper category number!\n", cat));
266                 return cat;
267         }
268 }
269
270 float CheckCategoryForEntry(float entry)
271 {
272         string s, k, v, modtype = "";
273         float j, m, impure = 0;
274         s = gethostcachestring(SLIST_FIELD_QCSTATUS, entry);
275         m = tokenizebyseparator(s, ":");
276
277         for(j = 2; j < m; ++j)
278         {
279                 if(argv(j) == "")
280                         break;
281                 k = substring(argv(j), 0, 1);
282                 v = substring(argv(j), 1, -1);
283                 if(k == "P") { impure = stof(v); }
284                 else if(k == "M") { modtype = strtolower(v); }
285         }
286
287         if(impure > autocvar_menu_slist_purethreshold) { impure = TRUE; }
288         else { impure = FALSE; }
289
290         if(gethostcachenumber(SLIST_FIELD_ISFAVORITE, entry)) { return CAT_FAVORITED; }
291         if(IsRecommended(gethostcachestring(SLIST_FIELD_CNAME, entry))) { return CAT_RECOMMENDED; }
292         else if(modtype != "xonotic")
293         {
294                 switch(modtype)
295                 {
296                         // old servers which don't report their mod name are considered modified now
297                         case "": { return CAT_MODIFIED; }
298                         
299                         case "xpm": { return CAT_XPM; } 
300                         case "minstagib": { return CAT_MINSTAGIB; }
301                         case "overkill": { return CAT_OVERKILL; }
302                         //case "nix": { return CAT_NIX; }
303                         //case "newtoys": { return CAT_NEWTOYS; }
304
305                         // "cts" is allowed as compat, xdf is replacement
306                         case "cts": 
307                         case "xdf": { return CAT_DEFRAG; }
308                         
309                         default: { dprint(sprintf("Found strange mod type: %s\n", modtype)); return CAT_MODIFIED; }
310                 }
311         }
312         else { return (impure ? CAT_MODIFIED : CAT_NORMAL); }
313
314         // should never hit this point
315         error(sprintf("CheckCategoryForEntry(%d): Function fell through without normal return!\n", entry));
316         return FALSE;
317 }
318
319 float CheckItemNumber(float num)
320 {
321         float i, n;
322
323         if not(category_draw_count) { return num; } // there are no categories to process
324
325         for(i = 0, n = 1; n <= category_draw_count; ++i, ++n)
326         {
327                 if(category_item[i] == (num - i)) { return -category_name[i]; }
328                 else if(n == category_draw_count) { return (num - n); }
329                 else if((num - i) <= category_item[n]) { return (num - n); }
330         }
331
332         // should never hit this point
333         error(sprintf("CheckItemNumber(%d): Function fell through without normal return!\n", num));
334         return FALSE;
335 }
336
337 void XonoticServerList_toggleFavorite(entity me, string srv)
338 {
339         string s, s0, s1, s2, srv_resolved, p;
340         float i, n, f;
341         srv_resolved = netaddress_resolve(srv, 26000);
342         p = crypto_getidfp(srv_resolved);
343         s = cvar_string("net_slist_favorites");
344         n = tokenize_console(s);
345         f = 0;
346         for(i = 0; i < n; ++i)
347         {
348                 if(substring(argv(i), 0, 1) != "[" && strlen(argv(i)) == 44 && strstrofs(argv(i), ".", 0) < 0)
349                 {
350                         if(p)
351                                 if(argv(i) != p)
352                                         continue;
353                 }
354                 else
355                 {
356                         if(srv_resolved != netaddress_resolve(argv(i), 26000))
357                                 continue;
358                 }
359                 s0 = s1 = s2 = "";
360                 if(i > 0)
361                         s0 = substring(s, 0, argv_end_index(i - 1));
362                 if(i < n-1)
363                         s2 = substring(s, argv_start_index(i + 1), -1);
364                 if(s0 != "" && s2 != "")
365                         s1 = " ";
366                 cvar_set("net_slist_favorites", strcat(s0, s1, s2));
367                 s = cvar_string("net_slist_favorites");
368                 n = tokenize_console(s);
369                 f = 1;
370                 --i;
371         }
372         
373         if(!f)
374         {
375                 s1 = "";
376                 if(s != "")
377                         s1 = " ";
378                 if(p)
379                         cvar_set("net_slist_favorites", strcat(s, s1, p));
380                 else
381                         cvar_set("net_slist_favorites", strcat(s, s1, srv));
382         }
383
384         me.refreshServerList(me, REFRESHSERVERLIST_RESORT);
385 }
386
387 void ServerList_Update_favoriteButton(entity btn, entity me)
388 {
389         me.favoriteButton.setText(me.favoriteButton,
390                 (IsFavorite(me.ipAddressBox.text) ?
391                         _("Remove") : _("Bookmark")
392                 )
393         );
394 }
395
396 entity makeXonoticServerList()
397 {
398         entity me;
399         me = spawnXonoticServerList();
400         me.configureXonoticServerList(me);
401         return me;
402 }
403 void XonoticServerList_configureXonoticServerList(entity me)
404 {
405         me.configureXonoticListBox(me);
406
407         // update field ID's
408         #define SLIST_FIELD(suffix,name) SLIST_FIELD_##suffix = gethostcacheindexforkey(name);
409         SLIST_FIELDS
410         #undef SLIST_FIELD
411
412         // clear list
413         me.nItems = 0;
414
415         // build categories
416         RegisterSLCategories();
417 }
418 void XonoticServerList_setSelected(entity me, float i)
419 {
420         // todo: add logic to skip categories
421         float save, num;
422         save = me.selectedItem;
423         SUPER(XonoticServerList).setSelected(me, i);
424         /*
425         if(me.selectedItem == save)
426                 return;
427         */
428         if(me.nItems == 0)
429                 return;
430
431         //if(gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT) != CheckItemNumber(me.nItems))
432         //      { error("^1XonoticServerList_setSelected(); ERROR: ^7Host cache viewcount mismatches nItems!\n"); return; } // sorry, it would be wrong
433         // ^ todo: make this work somehow?
434
435         #define SET_SELECTED_SERVER(cachenum) \
436                 if(me.selectedServer) { strunzone(me.selectedServer); } \
437                 me.selectedServer = strzone(gethostcachestring(SLIST_FIELD_CNAME, cachenum)); \
438                 me.ipAddressBox.setText(me.ipAddressBox, me.selectedServer); \
439                 me.ipAddressBox.cursorPos = strlen(me.selectedServer); \
440                 me.ipAddressBoxFocused = -1; \
441                 return;
442
443         num = CheckItemNumber(me.selectedItem);
444
445         if(num >= 0) { SET_SELECTED_SERVER(num); }
446         else if(save > me.selectedItem)
447         {
448                 if(me.selectedItem == 0) { return; }
449                 else
450                 {
451                         if(me.lastClickedTime >= me.lastBumpSelectTime)
452                         {
453                                 SUPER(XonoticServerList).setSelected(me, me.selectedItem - 1);
454                                 num = CheckItemNumber(me.selectedItem);
455                                 if(num >= 0)
456                                 {
457                                         me.lastBumpSelectTime = time;
458                                         SET_SELECTED_SERVER(num);
459                                 }
460                         }
461                 }
462         }
463         else if(save < me.selectedItem)
464         {
465                 if(me.selectedItem == me.nItems) { return; }
466                 else
467                 {
468                         if(me.lastClickedTime >= me.lastBumpSelectTime)
469                         {
470                                 SUPER(XonoticServerList).setSelected(me, me.selectedItem + 1);
471                                 num = CheckItemNumber(me.selectedItem);
472                                 if(num >= 0)
473                                 {
474                                         me.lastBumpSelectTime = time;
475                                         SET_SELECTED_SERVER(num);
476                                 }
477                         }
478                 }
479         }
480 }
481
482 void XonoticServerList_refreshServerList(entity me, float mode)
483 {
484         //print("refresh of type ", ftos(mode), "\n");
485
486         if(mode >= REFRESHSERVERLIST_REFILTER)
487         {
488                 float m, i, n;
489                 float listflags = 0;
490                 string s, typestr, modstr;
491
492                 s = me.filterString;
493
494                 m = strstrofs(s, ":", 0);
495                 if(m >= 0)
496                 {
497                         typestr = substring(s, 0, m);
498                         s = substring(s, m + 1, strlen(s) - m - 1);
499                         while(substring(s, 0, 1) == " ")
500                                 s = substring(s, 1, strlen(s) - 1);
501                 }
502                 else
503                         typestr = "";
504
505                 modstr = cvar_string("menu_slist_modfilter");
506
507                 m = SLIST_MASK_AND - 1;
508                 resethostcachemasks();
509
510                 // ping: reject negative ping (no idea why this happens in the first place, engine bug)
511                 sethostcachemasknumber(++m, SLIST_FIELD_PING, 0, SLIST_TEST_GREATEREQUAL);
512
513                 // show full button
514                 if(!me.filterShowFull)
515                 {
516                         sethostcachemasknumber(++m, SLIST_FIELD_FREESLOTS, 1, SLIST_TEST_GREATEREQUAL); // legacy
517                         sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, ":S0:", SLIST_TEST_NOTCONTAIN); // g_maxplayers support
518                 }
519
520                 // show empty button
521                 if(!me.filterShowEmpty)
522                         sethostcachemasknumber(++m, SLIST_FIELD_NUMHUMANS, 1, SLIST_TEST_GREATEREQUAL);
523
524                 // gametype filtering
525                 if(typestr != "")
526                         sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, strcat(typestr, ":"), SLIST_TEST_STARTSWITH);
527
528                 // mod filtering
529                 if(modstr != "")
530                 {
531                         if(substring(modstr, 0, 1) == "!")
532                                 sethostcachemaskstring(++m, SLIST_FIELD_MOD, resolvemod(substring(modstr, 1, strlen(modstr) - 1)), SLIST_TEST_NOTEQUAL);
533                         else
534                                 sethostcachemaskstring(++m, SLIST_FIELD_MOD, resolvemod(modstr), SLIST_TEST_EQUAL);
535                 }
536
537                 // server banning
538                 n = tokenizebyseparator(_Nex_ExtResponseSystem_BannedServers, " ");
539                 for(i = 0; i < n; ++i)
540                         if(argv(i) != "")
541                                 sethostcachemaskstring(++m, SLIST_FIELD_CNAME, argv(i), SLIST_TEST_NOTSTARTSWITH);
542
543                 m = SLIST_MASK_OR - 1;
544                 if(s != "")
545                 {
546                         sethostcachemaskstring(++m, SLIST_FIELD_NAME, s, SLIST_TEST_CONTAINS);
547                         sethostcachemaskstring(++m, SLIST_FIELD_MAP, s, SLIST_TEST_CONTAINS);
548                         sethostcachemaskstring(++m, SLIST_FIELD_PLAYERS, s, SLIST_TEST_CONTAINS);
549                         sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, strcat(s, ":"), SLIST_TEST_STARTSWITH);
550                 }
551
552                 // sorting flags
553                 //listflags |= SLSF_FAVORITES;
554                 listflags |= SLSF_CATEGORIES;
555                 if(me.currentSortOrder < 0) { listflags |= SLSF_DESCENDING; }
556                 sethostcachesort(me.currentSortField, listflags);
557         }
558         
559         resorthostcache();
560         if(mode >= REFRESHSERVERLIST_ASK)
561                 refreshhostcache(mode >= REFRESHSERVERLIST_RESET);
562 }
563 void XonoticServerList_focusEnter(entity me)
564 {
565         if(time < me.nextRefreshTime)
566         {
567                 //print("sorry, no refresh yet\n");
568                 return;
569         }
570         me.nextRefreshTime = time + 10;
571         me.refreshServerList(me, REFRESHSERVERLIST_ASK);
572 }
573
574 void XonoticServerList_draw(entity me)
575 {
576         float i, found, owned, num;
577
578         if(_Nex_ExtResponseSystem_BannedServersNeedsRefresh)
579         {
580                 if(!me.needsRefresh)
581                         me.needsRefresh = 2;
582                 _Nex_ExtResponseSystem_BannedServersNeedsRefresh = 0;
583         }
584
585         if(_Nex_ExtResponseSystem_RecommendedServersNeedsRefresh)
586         {
587                 if(!me.needsRefresh)
588                         me.needsRefresh = 3;
589                 _Nex_ExtResponseSystem_RecommendedServersNeedsRefresh = 0;
590         }
591
592         if(me.currentSortField == -1)
593         {
594                 me.setSortOrder(me, SLIST_FIELD_PING, +1);
595                 me.refreshServerList(me, REFRESHSERVERLIST_RESET);
596         }
597         else if(me.needsRefresh == 1)
598         {
599                 me.needsRefresh = 2; // delay by one frame to make sure "slist" has been executed
600         }
601         else if(me.needsRefresh == 2)
602         {
603                 me.needsRefresh = 0;
604                 me.refreshServerList(me, REFRESHSERVERLIST_REFILTER);
605         }
606         else if(me.needsRefresh == 3)
607         {
608                 me.needsRefresh = 0;
609                 me.refreshServerList(me, REFRESHSERVERLIST_RESORT);
610         }
611
612         owned = ((me.selectedServer == me.ipAddressBox.text) && (me.ipAddressBox.text != ""));
613
614         for(i = 0; i < category_draw_count; ++i) { category_name[i] = -1; category_item[i] = -1; }
615         category_draw_count = 0;
616
617         if(autocvar_menu_slist_categories >= 0) // if less than 0, don't even draw a category heading for favorites
618         {
619                 float itemcount = gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT);
620                 me.nItems = itemcount;
621                 
622                 //float visible = floor(me.scrollPos / me.itemHeight);
623                 // ^ unfortunately no such optimization can be made-- we must process through the
624                 // entire list, otherwise there is no way to know which item is first in its category.
625
626                 float cat, x;
627                 for(i = 0; i < itemcount; ++i) // FIXME this loop is TOTALLY unacceptable (O(servers)). Make it O(categories * log(servers)). Yes, that is possible.
628                 {
629                         cat = gethostcachenumber(SLIST_FIELD_CATEGORY, i);
630                         if(cat)
631                         {
632                                 if(category_draw_count == 0)
633                                 {
634                                         category_name[category_draw_count] = cat;
635                                         category_item[category_draw_count] = i;
636                                         ++category_draw_count;
637                                         ++me.nItems;
638                                 }
639                                 else
640                                 {
641                                         found = 0;
642                                         for(x = 0; x < category_draw_count; ++x) { if(cat == category_name[x]) { found = 1; } }
643                                         if not(found)
644                                         {
645                                                 category_name[category_draw_count] = cat;
646                                                 category_item[category_draw_count] = i;
647                                                 ++category_draw_count;
648                                                 ++me.nItems;
649                                         }
650                                 }
651                         }
652                 }
653                 if(autocvar_menu_slist_categories_onlyifmultiple && (category_draw_count == 1))
654                 {
655                         category_name[0] = -1;
656                         category_item[0] = -1;
657                         category_draw_count = 0;
658                         me.nItems = itemcount;
659                 }
660         }
661         else { me.nItems = gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT); }
662
663         me.connectButton.disabled = ((me.nItems == 0) && (me.ipAddressBox.text == ""));
664         me.infoButton.disabled = ((me.nItems == 0) || !owned);
665         me.favoriteButton.disabled = ((me.nItems == 0) && (me.ipAddressBox.text == ""));
666
667         found = 0;
668         if(me.selectedServer)
669         {
670                 for(i = 0; i < me.nItems; ++i)
671                 {
672                         num = CheckItemNumber(i);
673                         if(num >= 0)
674                         {
675                                 if(gethostcachestring(SLIST_FIELD_CNAME, num) == me.selectedServer)
676                                 {
677                                         if(i != me.selectedItem)
678                                         {
679                                                 me.lastClickedServer = -1;
680                                                 me.selectedItem = i;
681                                         }
682                                         found = 1;
683                                         break;
684                                 }
685                         }
686                 }
687         }
688         if(!found)
689         {
690                 if(me.nItems > 0)
691                 {
692                         if(me.selectedItem >= me.nItems) { me.selectedItem = me.nItems - 1; }
693                         if(me.selectedServer) { strunzone(me.selectedServer); }
694
695                         num = CheckItemNumber(me.selectedItem);
696                         if(num >= 0) { me.selectedServer = strzone(gethostcachestring(SLIST_FIELD_CNAME, num)); }
697                 }
698         }
699         
700         if(owned)
701         {
702                 if(me.selectedServer != me.ipAddressBox.text)
703                 {
704                         me.ipAddressBox.setText(me.ipAddressBox, me.selectedServer);
705                         me.ipAddressBox.cursorPos = strlen(me.selectedServer);
706                         me.ipAddressBoxFocused = -1;
707                 }
708         }
709
710         if(me.ipAddressBoxFocused != me.ipAddressBox.focused)
711         {
712                 if(me.ipAddressBox.focused || me.ipAddressBoxFocused < 0)
713                         ServerList_Update_favoriteButton(NULL, me);
714                 me.ipAddressBoxFocused = me.ipAddressBox.focused;
715         }
716
717         SUPER(XonoticServerList).draw(me);
718 }
719 void ServerList_PingSort_Click(entity btn, entity me)
720 {
721         me.setSortOrder(me, SLIST_FIELD_PING, +1);
722 }
723 void ServerList_NameSort_Click(entity btn, entity me)
724 {
725         me.setSortOrder(me, SLIST_FIELD_NAME, -1); // why?
726 }
727 void ServerList_MapSort_Click(entity btn, entity me)
728 {
729         me.setSortOrder(me, SLIST_FIELD_MAP, -1); // why?
730 }
731 void ServerList_PlayerSort_Click(entity btn, entity me)
732 {
733         me.setSortOrder(me, SLIST_FIELD_NUMHUMANS, -1);
734 }
735 void ServerList_TypeSort_Click(entity btn, entity me)
736 {
737         string s, t;
738         float i, m;
739         s = me.filterString;
740         m = strstrofs(s, ":", 0);
741         if(m >= 0)
742         {
743                 s = substring(s, 0, m);
744                 while(substring(s, m+1, 1) == " ") // skip spaces
745                         ++m;
746         }
747         else
748                 s = "";
749
750         for(i = 1; ; i *= 2) // 20 modes ought to be enough for anyone
751         {
752                 t = MapInfo_Type_ToString(i);
753                 if(i > 1)
754                         if(t == "") // it repeats (default case)
755                         {
756                                 // no type was found
757                                 // choose the first one
758                                 s = MapInfo_Type_ToString(1);
759                                 break;
760                         }
761                 if(s == t)
762                 {
763                         // the type was found
764                         // choose the next one
765                         s = MapInfo_Type_ToString(i * 2);
766                         if(s == "")
767                                 s = MapInfo_Type_ToString(1);
768                         break;
769                 }
770         }
771
772         if(s != "")
773                 s = strcat(s, ":");
774         s = strcat(s, substring(me.filterString, m+1, strlen(me.filterString) - m - 1));
775
776         me.controlledTextbox.setText(me.controlledTextbox, s);
777         me.controlledTextbox.keyDown(me.controlledTextbox, K_END, 0, 0);
778         me.controlledTextbox.keyUp(me.controlledTextbox, K_END, 0, 0);
779         //ServerList_Filter_Change(me.controlledTextbox, me);
780 }
781 void ServerList_Filter_Change(entity box, entity me)
782 {
783         if(me.filterString)
784                 strunzone(me.filterString);
785         if(box.text != "")
786                 me.filterString = strzone(box.text);
787         else
788                 me.filterString = string_null;
789         me.refreshServerList(me, REFRESHSERVERLIST_REFILTER);
790
791         me.ipAddressBox.setText(me.ipAddressBox, "");
792         me.ipAddressBox.cursorPos = 0;
793         me.ipAddressBoxFocused = -1;
794 }
795 void ServerList_Categories_Click(entity box, entity me)
796 {
797         box.setChecked(box, autocvar_menu_slist_categories = !autocvar_menu_slist_categories);
798         ///refreshhostcache(TRUE);
799
800         //cvar_set("net_slist_pause", "0");
801         //Destroy_Category_Entities();
802         //CALL_ACCUMULATED_FUNCTION(RegisterSLCategories);
803         //me.refreshServerList(me, REFRESHSERVERLIST_REFILTER);
804
805         me.refreshServerList(me, REFRESHSERVERLIST_RESORT);
806
807         me.ipAddressBox.setText(me.ipAddressBox, "");
808         me.ipAddressBox.cursorPos = 0;
809         me.ipAddressBoxFocused = -1;
810 }
811 void ServerList_ShowEmpty_Click(entity box, entity me)
812 {
813         box.setChecked(box, me.filterShowEmpty = !me.filterShowEmpty);
814         me.refreshServerList(me, REFRESHSERVERLIST_REFILTER);
815
816         me.ipAddressBox.setText(me.ipAddressBox, "");
817         me.ipAddressBox.cursorPos = 0;
818         me.ipAddressBoxFocused = -1;
819 }
820 void ServerList_ShowFull_Click(entity box, entity me)
821 {
822         box.setChecked(box, me.filterShowFull = !me.filterShowFull);
823         me.refreshServerList(me, REFRESHSERVERLIST_REFILTER);
824
825         me.ipAddressBox.setText(me.ipAddressBox, "");
826         me.ipAddressBox.cursorPos = 0;
827         me.ipAddressBoxFocused = -1;
828 }
829 void XonoticServerList_setSortOrder(entity me, float fld, float direction)
830 {
831         if(me.currentSortField == fld)
832                 direction = -me.currentSortOrder;
833         me.currentSortOrder = direction;
834         me.currentSortField = fld;
835         me.sortButton1.forcePressed = (fld == SLIST_FIELD_PING);
836         me.sortButton2.forcePressed = (fld == SLIST_FIELD_NAME);
837         me.sortButton3.forcePressed = (fld == SLIST_FIELD_MAP);
838         me.sortButton4.forcePressed = 0;
839         me.sortButton5.forcePressed = (fld == SLIST_FIELD_NUMHUMANS);
840         me.selectedItem = 0;
841         if(me.selectedServer)
842                 strunzone(me.selectedServer);
843         me.selectedServer = string_null;
844         me.refreshServerList(me, REFRESHSERVERLIST_REFILTER);
845 }
846 void XonoticServerList_positionSortButton(entity me, entity btn, float theOrigin, float theSize, string theTitle, void(entity, entity) theFunc)
847 {
848         vector originInLBSpace, sizeInLBSpace;
849         originInLBSpace = eY * (-me.itemHeight);
850         sizeInLBSpace = eY * me.itemHeight + eX * (1 - me.controlWidth);
851
852         vector originInDialogSpace, sizeInDialogSpace;
853         originInDialogSpace = boxToGlobal(originInLBSpace, me.Container_origin, me.Container_size);
854         sizeInDialogSpace = boxToGlobalSize(sizeInLBSpace, me.Container_size);
855
856         btn.Container_origin_x = originInDialogSpace_x + sizeInDialogSpace_x * theOrigin;
857         btn.Container_size_x   =                         sizeInDialogSpace_x * theSize;
858         btn.setText(btn, theTitle);
859         btn.onClick = theFunc;
860         btn.onClickEntity = me;
861         btn.resized = 1;
862 }
863 void XonoticServerList_resizeNotify(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
864 {
865         SUPER(XonoticServerList).resizeNotify(me, relOrigin, relSize, absOrigin, absSize);
866
867         me.realFontSize_y = me.fontSize / (absSize_y * me.itemHeight);
868         me.realFontSize_x = me.fontSize / (absSize_x * (1 - me.controlWidth));
869         me.realUpperMargin = 0.5 * (1 - me.realFontSize_y);
870
871         me.columnIconsOrigin = 0;
872         me.columnIconsSize = me.realFontSize_x * 4 * me.iconsSizeFactor;
873         me.columnPingSize = me.realFontSize_x * 3;
874         me.columnMapSize = me.realFontSize_x * 10;
875         me.columnTypeSize = me.realFontSize_x * 4;
876         me.columnPlayersSize = me.realFontSize_x * 5;
877         me.columnNameSize = 1 - me.columnPlayersSize - me.columnMapSize - me.columnPingSize - me.columnIconsSize - me.columnTypeSize - 5 * me.realFontSize_x;
878         me.columnPingOrigin = me.columnIconsOrigin + me.columnIconsSize + me.realFontSize_x;
879         me.columnNameOrigin = me.columnPingOrigin + me.columnPingSize + me.realFontSize_x;
880         me.columnMapOrigin = me.columnNameOrigin + me.columnNameSize + me.realFontSize_x;
881         me.columnTypeOrigin = me.columnMapOrigin + me.columnMapSize + me.realFontSize_x;
882         me.columnPlayersOrigin = me.columnTypeOrigin + me.columnTypeSize + me.realFontSize_x;
883
884         me.positionSortButton(me, me.sortButton1, me.columnPingOrigin, me.columnPingSize, _("Ping"), ServerList_PingSort_Click);
885         me.positionSortButton(me, me.sortButton2, me.columnNameOrigin, me.columnNameSize, _("Host name"), ServerList_NameSort_Click);
886         me.positionSortButton(me, me.sortButton3, me.columnMapOrigin, me.columnMapSize, _("Map"), ServerList_MapSort_Click);
887         me.positionSortButton(me, me.sortButton4, me.columnTypeOrigin, me.columnTypeSize, _("Type"), ServerList_TypeSort_Click);
888         me.positionSortButton(me, me.sortButton5, me.columnPlayersOrigin, me.columnPlayersSize, _("Players"), ServerList_PlayerSort_Click);
889
890         float f;
891         f = me.currentSortField;
892         if(f >= 0)
893         {
894                 me.currentSortField = -1;
895                 me.setSortOrder(me, f, me.currentSortOrder); // force resetting the sort order
896         }
897 }
898 void ServerList_Connect_Click(entity btn, entity me)
899 {
900         localcmd(sprintf("connect %s\n",
901                 ((me.ipAddressBox.text != "") ?
902                         me.ipAddressBox.text : me.selectedServer
903                 )
904         ));
905 }
906 void ServerList_Favorite_Click(entity btn, entity me)
907 {
908         string ipstr;
909         ipstr = netaddress_resolve(me.ipAddressBox.text, 26000);
910         if(ipstr != "")
911         {
912                 me.toggleFavorite(me, me.ipAddressBox.text);
913                 me.ipAddressBoxFocused = -1;
914         }
915 }
916 void ServerList_Info_Click(entity btn, entity me)
917 {
918         main.serverInfoDialog.loadServerInfo(main.serverInfoDialog, CheckItemNumber(me.selectedItem));
919         DialogOpenButton_Click(me, main.serverInfoDialog);
920 }
921 void XonoticServerList_clickListBoxItem(entity me, float i, vector where)
922 {
923         float num = CheckItemNumber(i);
924         if(num >= 0)
925         {
926                 if(num == me.lastClickedServer)
927                         if(time < me.lastClickedTime + 0.3)
928                         {
929                                 // DOUBLE CLICK!
930                                 ServerList_Connect_Click(NULL, me);
931                         }
932                 me.lastClickedServer = num;
933                 me.lastClickedTime = time;
934         }
935 }
936 void XonoticServerList_drawListBoxItem(entity me, float i, vector absSize, float isSelected)
937 {
938         // layout: Ping, Server name, Map name, NP, TP, MP
939         float p, q;
940         float isv4, isv6;
941         vector theColor;
942         float theAlpha;
943         float m, pure, freeslots, j, sflags;
944         string s, typestr, versionstr, k, v, modname;
945
946         float item = CheckItemNumber(i);
947         //print(sprintf("time: %f, i: %d, item: %d, nitems: %d\n", time, i, item, me.nItems));
948         
949         if(item < 0)
950         {
951                 entity catent = RetrieveCategoryEnt(-item);
952                 if(catent)
953                 {
954                         draw_Text(
955                                 eY * me.realUpperMargin
956                                 +
957                                 eX * (me.columnNameOrigin + (me.columnNameSize - draw_TextWidth(catent.cat_string, 0, me.realFontSize)) * 0.5),
958                                 catent.cat_string,
959                                 me.realFontSize,
960                                 '1 1 1',
961                                 SKINALPHA_TEXT,
962                                 0
963                         );
964                         return;
965                 }
966         }
967         
968         if(isSelected)
969                 draw_Fill('0 0 0', '1 1 0', SKINCOLOR_LISTBOX_SELECTED, SKINALPHA_LISTBOX_SELECTED);
970
971         s = gethostcachestring(SLIST_FIELD_QCSTATUS, item);
972         m = tokenizebyseparator(s, ":");
973         typestr = "";
974         if(m >= 2)
975         {
976                 typestr = argv(0);
977                 versionstr = argv(1);
978         }
979         freeslots = -1;
980         sflags = -1;
981         modname = "";
982         pure = 0;
983         for(j = 2; j < m; ++j)
984         {
985                 if(argv(j) == "")
986                         break;
987                 k = substring(argv(j), 0, 1);
988                 v = substring(argv(j), 1, -1);
989                 if(k == "P")
990                         pure = stof(v);
991                 else if(k == "S")
992                         freeslots = stof(v);
993                 else if(k == "F")
994                         sflags = stof(v);
995                 else if(k == "M")
996                         modname = v;
997         }
998
999 #ifdef COMPAT_NO_MOD_IS_XONOTIC
1000         if(modname == "")
1001                 modname = "Xonotic";
1002 #endif
1003
1004         /*
1005         SLIST_FIELD_MOD = gethostcacheindexforkey("mod");
1006         s = gethostcachestring(SLIST_FIELD_MOD, item);
1007         if(s != "data")
1008                 if(modname == "Xonotic")
1009                         modname = s;
1010         */
1011
1012         // list the mods here on which the pure server check actually works
1013         if(modname != "Xonotic")
1014         if(modname != "MinstaGib")
1015         if(modname != "CTS")
1016         if(modname != "NIX")
1017         if(modname != "NewToys")
1018                 pure = 0;
1019
1020         if(gethostcachenumber(SLIST_FIELD_FREESLOTS, item) <= 0)
1021                 theAlpha = SKINALPHA_SERVERLIST_FULL;
1022         else if(freeslots == 0)
1023                 theAlpha = SKINALPHA_SERVERLIST_FULL; // g_maxplayers support
1024         else if not(gethostcachenumber(SLIST_FIELD_NUMHUMANS, item))
1025                 theAlpha = SKINALPHA_SERVERLIST_EMPTY;
1026         else
1027                 theAlpha = 1;
1028
1029         p = gethostcachenumber(SLIST_FIELD_PING, item);
1030 #define PING_LOW 75
1031 #define PING_MED 200
1032 #define PING_HIGH 500
1033         if(p < PING_LOW)
1034                 theColor = SKINCOLOR_SERVERLIST_LOWPING + (SKINCOLOR_SERVERLIST_MEDPING - SKINCOLOR_SERVERLIST_LOWPING) * (p / PING_LOW);
1035         else if(p < PING_MED)
1036                 theColor = SKINCOLOR_SERVERLIST_MEDPING + (SKINCOLOR_SERVERLIST_HIGHPING - SKINCOLOR_SERVERLIST_MEDPING) * ((p - PING_LOW) / (PING_MED - PING_LOW));
1037         else if(p < PING_HIGH)
1038         {
1039                 theColor = SKINCOLOR_SERVERLIST_HIGHPING;
1040                 theAlpha *= 1 + (SKINALPHA_SERVERLIST_HIGHPING - 1) * ((p - PING_MED) / (PING_HIGH - PING_MED));
1041         }
1042         else
1043         {
1044                 theColor = eX;
1045                 theAlpha *= SKINALPHA_SERVERLIST_HIGHPING;
1046         }
1047
1048         if(gethostcachenumber(SLIST_FIELD_ISFAVORITE, item))
1049         {
1050                 theColor = theColor * (1 - SKINALPHA_SERVERLIST_FAVORITE) + SKINCOLOR_SERVERLIST_FAVORITE * SKINALPHA_SERVERLIST_FAVORITE;
1051                 theAlpha = theAlpha * (1 - SKINALPHA_SERVERLIST_FAVORITE) + SKINALPHA_SERVERLIST_FAVORITE;
1052         }
1053
1054         s = gethostcachestring(SLIST_FIELD_CNAME, item);
1055
1056         isv4 = isv6 = 0;
1057         if(substring(s, 0, 1) == "[")
1058         {
1059                 isv6 = 1;
1060                 me.seenIPv6 += 1;
1061         }
1062         else if(strstrofs("0123456789", substring(s, 0, 1), 0) >= 0)
1063         {
1064                 isv4 = 1;
1065                 me.seenIPv4 += 1;
1066         }
1067
1068         q = stof(substring(crypto_getencryptlevel(s), 0, 1));
1069         if((q <= 0 && cvar("crypto_aeslevel") >= 3) || (q >= 3 && cvar("crypto_aeslevel") <= 0))
1070         {
1071                 theColor = SKINCOLOR_SERVERLIST_IMPOSSIBLE;
1072                 theAlpha = SKINALPHA_SERVERLIST_IMPOSSIBLE;
1073         }
1074
1075         if(q == 1)
1076         {
1077                 if(cvar("crypto_aeslevel") >= 2)
1078                         q |= 4;
1079         }
1080         if(q == 2)
1081         {
1082                 if(cvar("crypto_aeslevel") >= 1)
1083                         q |= 4;
1084         }
1085         if(q == 3)
1086                 q = 5;
1087         else if(q >= 3)
1088                 q -= 2;
1089         // possible status:
1090         // 0: crypto off
1091         // 1: AES possible
1092         // 2: AES recommended but not available
1093         // 3: AES possible and will be used
1094         // 4: AES recommended and will be used
1095         // 5: AES required
1096
1097         // --------------
1098         //  RENDER ICONS
1099         // --------------
1100         vector iconSize = '0 0 0';
1101         iconSize_y = me.realFontSize_y * me.iconsSizeFactor;
1102         iconSize_x = me.realFontSize_x * me.iconsSizeFactor;
1103
1104         vector iconPos = '0 0 0';
1105         iconPos_x = (me.columnIconsSize - 3 * iconSize_x) * 0.5;
1106         iconPos_y = (1 - iconSize_y) * 0.5;
1107
1108         string n;
1109
1110         if not(me.seenIPv4 && me.seenIPv6)
1111         {
1112                 iconPos_x += iconSize_x * 0.5;
1113         }
1114         else if(me.seenIPv4 && me.seenIPv6)
1115         {
1116                 n = string_null;
1117                 if(isv6)
1118                         draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_ipv6"), 0); // PRECACHE_PIC_MIPMAP
1119                 else if(isv4)
1120                         draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_ipv4"), 0); // PRECACHE_PIC_MIPMAP
1121                 if(n)
1122                         draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1123                 iconPos_x += iconSize_x;
1124         }
1125
1126         if(q > 0)
1127         {
1128                 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_aeslevel", ftos(q)), 0); // PRECACHE_PIC_MIPMAP
1129                 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1130         }
1131         iconPos_x += iconSize_x;
1132
1133         if(modname == "Xonotic")
1134         {
1135                 if(pure == 0)
1136                 {
1137                         draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_pure1"), PRECACHE_PIC_MIPMAP);
1138                         draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1139                 }
1140         }
1141         else
1142         {
1143                 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_mod_", modname), PRECACHE_PIC_MIPMAP);
1144                 if(draw_PictureSize(n) == '0 0 0')
1145                         draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_mod_"), PRECACHE_PIC_MIPMAP);
1146                 if(pure == 0)
1147                         draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1148                 else
1149                         draw_Picture(iconPos, n, iconSize, '1 1 1', SKINALPHA_SERVERLIST_ICON_NONPURE);
1150         }
1151         iconPos_x += iconSize_x;
1152
1153         if(sflags >= 0 && (sflags & SERVERFLAG_PLAYERSTATS))
1154         {
1155                 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_stats1"), 0); // PRECACHE_PIC_MIPMAP
1156                 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1157         }
1158         iconPos_x += iconSize_x;
1159         
1160         // --------------
1161         //  RENDER TEXT
1162         // --------------
1163         
1164         // ping
1165         s = ftos(p);
1166         draw_Text(me.realUpperMargin * eY + (me.columnPingOrigin + me.columnPingSize - draw_TextWidth(s, 0, me.realFontSize)) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1167
1168         // server name
1169         s = draw_TextShortenToWidth(gethostcachestring(SLIST_FIELD_NAME, item), me.columnNameSize, 0, me.realFontSize);
1170         draw_Text(me.realUpperMargin * eY + me.columnNameOrigin * eX, s, me.realFontSize, theColor, theAlpha, 0);
1171
1172         // server map
1173         s = draw_TextShortenToWidth(gethostcachestring(SLIST_FIELD_MAP, item), me.columnMapSize, 0, me.realFontSize);
1174         draw_Text(me.realUpperMargin * eY + (me.columnMapOrigin + (me.columnMapSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1175
1176         // server gametype
1177         s = draw_TextShortenToWidth(typestr, me.columnTypeSize, 0, me.realFontSize);
1178         draw_Text(me.realUpperMargin * eY + (me.columnTypeOrigin + (me.columnTypeSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1179
1180         // server playercount
1181         s = strcat(ftos(gethostcachenumber(SLIST_FIELD_NUMHUMANS, item)), "/", ftos(gethostcachenumber(SLIST_FIELD_MAXPLAYERS, item)));
1182         draw_Text(me.realUpperMargin * eY + (me.columnPlayersOrigin + (me.columnPlayersSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1183 }
1184
1185 float XonoticServerList_keyDown(entity me, float scan, float ascii, float shift)
1186 {
1187         float i = CheckItemNumber(me.selectedItem);
1188         vector org, sz;
1189
1190         org = boxToGlobal(eY * (me.selectedItem * me.itemHeight - me.scrollPos), me.origin, me.size);
1191         sz = boxToGlobalSize(eY * me.itemHeight + eX * (1 - me.controlWidth), me.size);
1192
1193         me.lastBumpSelectTime = 0;
1194
1195         if(scan == K_ENTER || scan == K_KP_ENTER)
1196         {
1197                 ServerList_Connect_Click(NULL, me);
1198                 return 1;
1199         }
1200         else if(scan == K_MOUSE2 || scan == K_SPACE)
1201         {
1202                 if((me.nItems != 0) && (i >= 0))
1203                 {
1204                         main.serverInfoDialog.loadServerInfo(main.serverInfoDialog, i);
1205                         DialogOpenButton_Click_withCoords(me, main.serverInfoDialog, org, sz);
1206                         return 1;
1207                 }
1208                 return 0;
1209         }
1210         else if(scan == K_INS || scan == K_MOUSE3 || scan == K_KP_INS)
1211         {
1212                 if((me.nItems != 0) && (i >= 0))
1213                 {
1214                         me.toggleFavorite(me, me.selectedServer);
1215                         me.ipAddressBoxFocused = -1;
1216                         return 1;
1217                 }
1218                 return 0;
1219         }
1220         else if(SUPER(XonoticServerList).keyDown(me, scan, ascii, shift))
1221                 return 1;
1222         else if(!me.controlledTextbox)
1223                 return 0;
1224         else
1225                 return me.controlledTextbox.keyDown(me.controlledTextbox, scan, ascii, shift);
1226 }
1227 #endif