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