]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/menu/xonotic/serverlist.c
remove useless comment
[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         me.refreshServerList(me, REFRESHSERVERLIST_RESORT);
846
847         me.ipAddressBox.setText(me.ipAddressBox, "");
848         me.ipAddressBox.cursorPos = 0;
849         me.ipAddressBoxFocused = -1;
850 }
851 void ServerList_ShowEmpty_Click(entity box, entity me)
852 {
853         box.setChecked(box, me.filterShowEmpty = !me.filterShowEmpty);
854         me.refreshServerList(me, REFRESHSERVERLIST_REFILTER);
855
856         me.ipAddressBox.setText(me.ipAddressBox, "");
857         me.ipAddressBox.cursorPos = 0;
858         me.ipAddressBoxFocused = -1;
859 }
860 void ServerList_ShowFull_Click(entity box, entity me)
861 {
862         box.setChecked(box, me.filterShowFull = !me.filterShowFull);
863         me.refreshServerList(me, REFRESHSERVERLIST_REFILTER);
864
865         me.ipAddressBox.setText(me.ipAddressBox, "");
866         me.ipAddressBox.cursorPos = 0;
867         me.ipAddressBoxFocused = -1;
868 }
869 void XonoticServerList_setSortOrder(entity me, float fld, float direction)
870 {
871         if(me.currentSortField == fld)
872                 direction = -me.currentSortOrder;
873         me.currentSortOrder = direction;
874         me.currentSortField = fld;
875         me.sortButton1.forcePressed = (fld == SLIST_FIELD_PING);
876         me.sortButton2.forcePressed = (fld == SLIST_FIELD_NAME);
877         me.sortButton3.forcePressed = (fld == SLIST_FIELD_MAP);
878         me.sortButton4.forcePressed = 0;
879         me.sortButton5.forcePressed = (fld == SLIST_FIELD_NUMHUMANS);
880         me.selectedItem = 0;
881         if(me.selectedServer)
882                 strunzone(me.selectedServer);
883         me.selectedServer = string_null;
884         me.refreshServerList(me, REFRESHSERVERLIST_REFILTER);
885 }
886 void XonoticServerList_positionSortButton(entity me, entity btn, float theOrigin, float theSize, string theTitle, void(entity, entity) theFunc)
887 {
888         vector originInLBSpace, sizeInLBSpace;
889         originInLBSpace = eY * (-me.itemHeight);
890         sizeInLBSpace = eY * me.itemHeight + eX * (1 - me.controlWidth);
891
892         vector originInDialogSpace, sizeInDialogSpace;
893         originInDialogSpace = boxToGlobal(originInLBSpace, me.Container_origin, me.Container_size);
894         sizeInDialogSpace = boxToGlobalSize(sizeInLBSpace, me.Container_size);
895
896         btn.Container_origin_x = originInDialogSpace_x + sizeInDialogSpace_x * theOrigin;
897         btn.Container_size_x   =                         sizeInDialogSpace_x * theSize;
898         btn.setText(btn, theTitle);
899         btn.onClick = theFunc;
900         btn.onClickEntity = me;
901         btn.resized = 1;
902 }
903 void XonoticServerList_resizeNotify(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
904 {
905         SUPER(XonoticServerList).resizeNotify(me, relOrigin, relSize, absOrigin, absSize);
906
907         me.realFontSize_y = me.fontSize / (absSize_y * me.itemHeight);
908         me.realFontSize_x = me.fontSize / (absSize_x * (1 - me.controlWidth));
909         me.realUpperMargin = 0.5 * (1 - me.realFontSize_y);
910
911         me.columnIconsOrigin = 0;
912         me.columnIconsSize = me.realFontSize_x * 4 * me.iconsSizeFactor;
913         me.columnPingSize = me.realFontSize_x * 3;
914         me.columnMapSize = me.realFontSize_x * 10;
915         me.columnTypeSize = me.realFontSize_x * 4;
916         me.columnPlayersSize = me.realFontSize_x * 5;
917         me.columnNameSize = 1 - me.columnPlayersSize - me.columnMapSize - me.columnPingSize - me.columnIconsSize - me.columnTypeSize - 5 * me.realFontSize_x;
918         me.columnPingOrigin = me.columnIconsOrigin + me.columnIconsSize + me.realFontSize_x;
919         me.columnNameOrigin = me.columnPingOrigin + me.columnPingSize + me.realFontSize_x;
920         me.columnMapOrigin = me.columnNameOrigin + me.columnNameSize + me.realFontSize_x;
921         me.columnTypeOrigin = me.columnMapOrigin + me.columnMapSize + me.realFontSize_x;
922         me.columnPlayersOrigin = me.columnTypeOrigin + me.columnTypeSize + me.realFontSize_x;
923
924         me.positionSortButton(me, me.sortButton1, me.columnPingOrigin, me.columnPingSize, _("Ping"), ServerList_PingSort_Click);
925         me.positionSortButton(me, me.sortButton2, me.columnNameOrigin, me.columnNameSize, _("Host name"), ServerList_NameSort_Click);
926         me.positionSortButton(me, me.sortButton3, me.columnMapOrigin, me.columnMapSize, _("Map"), ServerList_MapSort_Click);
927         me.positionSortButton(me, me.sortButton4, me.columnTypeOrigin, me.columnTypeSize, _("Type"), ServerList_TypeSort_Click);
928         me.positionSortButton(me, me.sortButton5, me.columnPlayersOrigin, me.columnPlayersSize, _("Players"), ServerList_PlayerSort_Click);
929
930         float f;
931         f = me.currentSortField;
932         if(f >= 0)
933         {
934                 me.currentSortField = -1;
935                 me.setSortOrder(me, f, me.currentSortOrder); // force resetting the sort order
936         }
937 }
938 void ServerList_Connect_Click(entity btn, entity me)
939 {
940         localcmd(sprintf("connect %s\n",
941                 ((me.ipAddressBox.text != "") ?
942                         me.ipAddressBox.text : me.selectedServer
943                 )
944         ));
945 }
946 void ServerList_Favorite_Click(entity btn, entity me)
947 {
948         string ipstr;
949         ipstr = netaddress_resolve(me.ipAddressBox.text, 26000);
950         if(ipstr != "")
951         {
952                 me.toggleFavorite(me, me.ipAddressBox.text);
953                 me.ipAddressBoxFocused = -1;
954         }
955 }
956 void ServerList_Info_Click(entity btn, entity me)
957 {
958         main.serverInfoDialog.loadServerInfo(main.serverInfoDialog, CheckItemNumber(me.selectedItem));
959         DialogOpenButton_Click(me, main.serverInfoDialog);
960 }
961 void XonoticServerList_clickListBoxItem(entity me, float i, vector where)
962 {
963         float num = CheckItemNumber(i);
964         if(num >= 0)
965         {
966                 if(num == me.lastClickedServer)
967                         if(time < me.lastClickedTime + 0.3)
968                         {
969                                 // DOUBLE CLICK!
970                                 ServerList_Connect_Click(NULL, me);
971                         }
972                 me.lastClickedServer = num;
973                 me.lastClickedTime = time;
974         }
975 }
976 void XonoticServerList_drawListBoxItem(entity me, float i, vector absSize, float isSelected)
977 {
978         // layout: Ping, Server name, Map name, NP, TP, MP
979         float p, q;
980         float isv4, isv6;
981         vector theColor;
982         float theAlpha;
983         float m, pure, freeslots, j, sflags;
984         string s, typestr, versionstr, k, v, modname;
985
986         float item = CheckItemNumber(i);
987         //print(sprintf("time: %f, i: %d, item: %d, nitems: %d\n", time, i, item, me.nItems));
988         
989         if(item < 0)
990         {
991                 entity catent = RetrieveCategoryEnt(-item);
992                 if(catent)
993                 {
994                         draw_Text(
995                                 eY * me.realUpperMargin
996                                 +
997                                 eX * (me.columnNameOrigin + (me.columnNameSize - draw_TextWidth(catent.cat_string, 0, me.realFontSize)) * 0.5),
998                                 catent.cat_string,
999                                 me.realFontSize,
1000                                 '1 1 1',
1001                                 SKINALPHA_TEXT,
1002                                 0
1003                         );
1004                         return;
1005                 }
1006         }
1007         
1008         if(isSelected)
1009                 draw_Fill('0 0 0', '1 1 0', SKINCOLOR_LISTBOX_SELECTED, SKINALPHA_LISTBOX_SELECTED);
1010
1011         s = gethostcachestring(SLIST_FIELD_QCSTATUS, item);
1012         m = tokenizebyseparator(s, ":");
1013         typestr = "";
1014         if(m >= 2)
1015         {
1016                 typestr = argv(0);
1017                 versionstr = argv(1);
1018         }
1019         freeslots = -1;
1020         sflags = -1;
1021         modname = "";
1022         pure = 0;
1023         for(j = 2; j < m; ++j)
1024         {
1025                 if(argv(j) == "")
1026                         break;
1027                 k = substring(argv(j), 0, 1);
1028                 v = substring(argv(j), 1, -1);
1029                 if(k == "P")
1030                         pure = stof(v);
1031                 else if(k == "S")
1032                         freeslots = stof(v);
1033                 else if(k == "F")
1034                         sflags = stof(v);
1035                 else if(k == "M")
1036                         modname = v;
1037         }
1038
1039 #ifdef COMPAT_NO_MOD_IS_XONOTIC
1040         if(modname == "")
1041                 modname = "Xonotic";
1042 #endif
1043
1044         /*
1045         SLIST_FIELD_MOD = gethostcacheindexforkey("mod");
1046         s = gethostcachestring(SLIST_FIELD_MOD, item);
1047         if(s != "data")
1048                 if(modname == "Xonotic")
1049                         modname = s;
1050         */
1051
1052         // list the mods here on which the pure server check actually works
1053         if(modname != "Xonotic")
1054         if(modname != "MinstaGib")
1055         if(modname != "CTS")
1056         if(modname != "NIX")
1057         if(modname != "NewToys")
1058                 pure = 0;
1059
1060         if(gethostcachenumber(SLIST_FIELD_FREESLOTS, item) <= 0)
1061                 theAlpha = SKINALPHA_SERVERLIST_FULL;
1062         else if(freeslots == 0)
1063                 theAlpha = SKINALPHA_SERVERLIST_FULL; // g_maxplayers support
1064         else if not(gethostcachenumber(SLIST_FIELD_NUMHUMANS, item))
1065                 theAlpha = SKINALPHA_SERVERLIST_EMPTY;
1066         else
1067                 theAlpha = 1;
1068
1069         p = gethostcachenumber(SLIST_FIELD_PING, item);
1070 #define PING_LOW 75
1071 #define PING_MED 200
1072 #define PING_HIGH 500
1073         if(p < PING_LOW)
1074                 theColor = SKINCOLOR_SERVERLIST_LOWPING + (SKINCOLOR_SERVERLIST_MEDPING - SKINCOLOR_SERVERLIST_LOWPING) * (p / PING_LOW);
1075         else if(p < PING_MED)
1076                 theColor = SKINCOLOR_SERVERLIST_MEDPING + (SKINCOLOR_SERVERLIST_HIGHPING - SKINCOLOR_SERVERLIST_MEDPING) * ((p - PING_LOW) / (PING_MED - PING_LOW));
1077         else if(p < PING_HIGH)
1078         {
1079                 theColor = SKINCOLOR_SERVERLIST_HIGHPING;
1080                 theAlpha *= 1 + (SKINALPHA_SERVERLIST_HIGHPING - 1) * ((p - PING_MED) / (PING_HIGH - PING_MED));
1081         }
1082         else
1083         {
1084                 theColor = eX;
1085                 theAlpha *= SKINALPHA_SERVERLIST_HIGHPING;
1086         }
1087
1088         if(gethostcachenumber(SLIST_FIELD_ISFAVORITE, item))
1089         {
1090                 theColor = theColor * (1 - SKINALPHA_SERVERLIST_FAVORITE) + SKINCOLOR_SERVERLIST_FAVORITE * SKINALPHA_SERVERLIST_FAVORITE;
1091                 theAlpha = theAlpha * (1 - SKINALPHA_SERVERLIST_FAVORITE) + SKINALPHA_SERVERLIST_FAVORITE;
1092         }
1093
1094         s = gethostcachestring(SLIST_FIELD_CNAME, item);
1095
1096         isv4 = isv6 = 0;
1097         if(substring(s, 0, 1) == "[")
1098         {
1099                 isv6 = 1;
1100                 me.seenIPv6 += 1;
1101         }
1102         else if(strstrofs("0123456789", substring(s, 0, 1), 0) >= 0)
1103         {
1104                 isv4 = 1;
1105                 me.seenIPv4 += 1;
1106         }
1107
1108         q = stof(substring(crypto_getencryptlevel(s), 0, 1));
1109         if((q <= 0 && cvar("crypto_aeslevel") >= 3) || (q >= 3 && cvar("crypto_aeslevel") <= 0))
1110         {
1111                 theColor = SKINCOLOR_SERVERLIST_IMPOSSIBLE;
1112                 theAlpha = SKINALPHA_SERVERLIST_IMPOSSIBLE;
1113         }
1114
1115         if(q == 1)
1116         {
1117                 if(cvar("crypto_aeslevel") >= 2)
1118                         q |= 4;
1119         }
1120         if(q == 2)
1121         {
1122                 if(cvar("crypto_aeslevel") >= 1)
1123                         q |= 4;
1124         }
1125         if(q == 3)
1126                 q = 5;
1127         else if(q >= 3)
1128                 q -= 2;
1129         // possible status:
1130         // 0: crypto off
1131         // 1: AES possible
1132         // 2: AES recommended but not available
1133         // 3: AES possible and will be used
1134         // 4: AES recommended and will be used
1135         // 5: AES required
1136
1137         // --------------
1138         //  RENDER ICONS
1139         // --------------
1140         vector iconSize = '0 0 0';
1141         iconSize_y = me.realFontSize_y * me.iconsSizeFactor;
1142         iconSize_x = me.realFontSize_x * me.iconsSizeFactor;
1143
1144         vector iconPos = '0 0 0';
1145         iconPos_x = (me.columnIconsSize - 3 * iconSize_x) * 0.5;
1146         iconPos_y = (1 - iconSize_y) * 0.5;
1147
1148         string n;
1149
1150         if not(me.seenIPv4 && me.seenIPv6)
1151         {
1152                 iconPos_x += iconSize_x * 0.5;
1153         }
1154         else if(me.seenIPv4 && me.seenIPv6)
1155         {
1156                 n = string_null;
1157                 if(isv6)
1158                         draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_ipv6"), 0); // PRECACHE_PIC_MIPMAP
1159                 else if(isv4)
1160                         draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_ipv4"), 0); // PRECACHE_PIC_MIPMAP
1161                 if(n)
1162                         draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1163                 iconPos_x += iconSize_x;
1164         }
1165
1166         if(q > 0)
1167         {
1168                 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_aeslevel", ftos(q)), 0); // PRECACHE_PIC_MIPMAP
1169                 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1170         }
1171         iconPos_x += iconSize_x;
1172
1173         if(modname == "Xonotic")
1174         {
1175                 if(pure == 0)
1176                 {
1177                         draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_pure1"), PRECACHE_PIC_MIPMAP);
1178                         draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1179                 }
1180         }
1181         else
1182         {
1183                 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_mod_", modname), PRECACHE_PIC_MIPMAP);
1184                 if(draw_PictureSize(n) == '0 0 0')
1185                         draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_mod_"), PRECACHE_PIC_MIPMAP);
1186                 if(pure == 0)
1187                         draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1188                 else
1189                         draw_Picture(iconPos, n, iconSize, '1 1 1', SKINALPHA_SERVERLIST_ICON_NONPURE);
1190         }
1191         iconPos_x += iconSize_x;
1192
1193         if(sflags >= 0 && (sflags & SERVERFLAG_PLAYERSTATS))
1194         {
1195                 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_stats1"), 0); // PRECACHE_PIC_MIPMAP
1196                 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1197         }
1198         iconPos_x += iconSize_x;
1199         
1200         // --------------
1201         //  RENDER TEXT
1202         // --------------
1203         
1204         // ping
1205         s = ftos(p);
1206         draw_Text(me.realUpperMargin * eY + (me.columnPingOrigin + me.columnPingSize - draw_TextWidth(s, 0, me.realFontSize)) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1207
1208         // server name
1209         s = draw_TextShortenToWidth(gethostcachestring(SLIST_FIELD_NAME, item), me.columnNameSize, 0, me.realFontSize);
1210         draw_Text(me.realUpperMargin * eY + me.columnNameOrigin * eX, s, me.realFontSize, theColor, theAlpha, 0);
1211
1212         // server map
1213         s = draw_TextShortenToWidth(gethostcachestring(SLIST_FIELD_MAP, item), me.columnMapSize, 0, me.realFontSize);
1214         draw_Text(me.realUpperMargin * eY + (me.columnMapOrigin + (me.columnMapSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1215
1216         // server gametype
1217         s = draw_TextShortenToWidth(typestr, me.columnTypeSize, 0, me.realFontSize);
1218         draw_Text(me.realUpperMargin * eY + (me.columnTypeOrigin + (me.columnTypeSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1219
1220         // server playercount
1221         s = strcat(ftos(gethostcachenumber(SLIST_FIELD_NUMHUMANS, item)), "/", ftos(gethostcachenumber(SLIST_FIELD_MAXPLAYERS, item)));
1222         draw_Text(me.realUpperMargin * eY + (me.columnPlayersOrigin + (me.columnPlayersSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1223 }
1224
1225 float XonoticServerList_keyDown(entity me, float scan, float ascii, float shift)
1226 {
1227         float i = CheckItemNumber(me.selectedItem);
1228         vector org, sz;
1229
1230         org = boxToGlobal(eY * (me.selectedItem * me.itemHeight - me.scrollPos), me.origin, me.size);
1231         sz = boxToGlobalSize(eY * me.itemHeight + eX * (1 - me.controlWidth), me.size);
1232
1233         me.lastBumpSelectTime = 0;
1234
1235         if(scan == K_ENTER || scan == K_KP_ENTER)
1236         {
1237                 ServerList_Connect_Click(NULL, me);
1238                 return 1;
1239         }
1240         else if(scan == K_MOUSE2 || scan == K_SPACE)
1241         {
1242                 if((me.nItems != 0) && (i >= 0))
1243                 {
1244                         main.serverInfoDialog.loadServerInfo(main.serverInfoDialog, i);
1245                         DialogOpenButton_Click_withCoords(me, main.serverInfoDialog, org, sz);
1246                         return 1;
1247                 }
1248                 return 0;
1249         }
1250         else if(scan == K_INS || scan == K_MOUSE3 || scan == K_KP_INS)
1251         {
1252                 if((me.nItems != 0) && (i >= 0))
1253                 {
1254                         me.toggleFavorite(me, me.selectedServer);
1255                         me.ipAddressBoxFocused = -1;
1256                         return 1;
1257                 }
1258                 return 0;
1259         }
1260         else if(SUPER(XonoticServerList).keyDown(me, scan, ascii, shift))
1261                 return 1;
1262         else if(!me.controlledTextbox)
1263                 return 0;
1264         else
1265                 return me.controlledTextbox.keyDown(me.controlledTextbox, scan, ascii, shift);
1266 }
1267 #endif