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