]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/menu/xonotic/serverlist.c
Fix some things
[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 XonoticServerList_MapItems(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                 //print(sprintf("num: %d, i: %d, category_draw_count: %d, category_item[i]: %d\n", num, i, category_draw_count, category_item[i])); 
333                 if(category_item[i] == (num - i)) { /*print("inserting cat... \\/\n");*/ return -category_name[i]; }
334                 else if(n == category_draw_count) { /*print("end item... \\/\n");*/ return (num - n); }
335                 else if((num - i) <= category_item[n]) { /*print("next item... \\/\n");*/ return (num - n); }
336         }
337
338         // should never hit this point
339         error("wtf XonoticServerList_MapItems fail?");
340         return FALSE;
341 }
342
343 void XonoticServerList_toggleFavorite(entity me, string srv)
344 {
345         string s, s0, s1, s2, srv_resolved, p;
346         float i, n, f;
347         srv_resolved = netaddress_resolve(srv, 26000);
348         p = crypto_getidfp(srv_resolved);
349         s = cvar_string("net_slist_favorites");
350         n = tokenize_console(s);
351         f = 0;
352         for(i = 0; i < n; ++i)
353         {
354                 if(substring(argv(i), 0, 1) != "[" && strlen(argv(i)) == 44 && strstrofs(argv(i), ".", 0) < 0)
355                 {
356                         if(p)
357                                 if(argv(i) != p)
358                                         continue;
359                 }
360                 else
361                 {
362                         if(srv_resolved != netaddress_resolve(argv(i), 26000))
363                                 continue;
364                 }
365                 s0 = s1 = s2 = "";
366                 if(i > 0)
367                         s0 = substring(s, 0, argv_end_index(i - 1));
368                 if(i < n-1)
369                         s2 = substring(s, argv_start_index(i + 1), -1);
370                 if(s0 != "" && s2 != "")
371                         s1 = " ";
372                 cvar_set("net_slist_favorites", strcat(s0, s1, s2));
373                 s = cvar_string("net_slist_favorites");
374                 n = tokenize_console(s);
375                 f = 1;
376                 --i;
377         }
378         
379         if(!f)
380         {
381                 s1 = "";
382                 if(s != "")
383                         s1 = " ";
384                 if(p)
385                         cvar_set("net_slist_favorites", strcat(s, s1, p));
386                 else
387                         cvar_set("net_slist_favorites", strcat(s, s1, srv));
388         }
389
390         me.refreshServerList(me, REFRESHSERVERLIST_RESORT);
391 }
392
393 void ServerList_Update_favoriteButton(entity btn, entity me)
394 {
395         me.favoriteButton.setText(me.favoriteButton,
396                 (IsFavorite(me.ipAddressBox.text) ?
397                         _("Remove") : _("Bookmark")
398                 )
399         );
400 }
401
402 entity makeXonoticServerList()
403 {
404         entity me;
405         me = spawnXonoticServerList();
406         me.configureXonoticServerList(me);
407         return me;
408 }
409 void XonoticServerList_configureXonoticServerList(entity me)
410 {
411         me.configureXonoticListBox(me);
412
413         // update field ID's
414         #define SLIST_FIELD(suffix,name) SLIST_FIELD_##suffix = gethostcacheindexforkey(name);
415         SLIST_FIELDS
416         #undef SLIST_FIELD
417
418         // clear list
419         me.nItems = 0;
420
421         // build categories
422         RegisterSLCategories();
423 }
424 void XonoticServerList_setSelected(entity me, float i)
425 {
426         // todo: add logic to skip categories
427         float save, num;
428         save = me.selectedItem;
429         SUPER(XonoticServerList).setSelected(me, i);
430         /*
431         if(me.selectedItem == save)
432                 return;
433         */
434         if(me.nItems == 0)
435                 return;
436
437         //if(gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT) != XonoticServerList_MapItems(me.nItems))
438         //      { error("^1XonoticServerList_setSelected(); ERROR: ^7Host cache viewcount mismatches nItems!\n"); return; } // sorry, it would be wrong
439         // ^ todo: make this work somehow?
440
441         #define SET_SELECTED_SERVER(cachenum) \
442                 if(me.selectedServer) { strunzone(me.selectedServer); } \
443                 me.selectedServer = strzone(gethostcachestring(SLIST_FIELD_CNAME, cachenum)); \
444                 me.ipAddressBox.setText(me.ipAddressBox, me.selectedServer); \
445                 me.ipAddressBox.cursorPos = strlen(me.selectedServer); \
446                 me.ipAddressBoxFocused = -1; \
447                 return;
448
449         num = XonoticServerList_MapItems(me.selectedItem);
450
451         if(num >= 0) { SET_SELECTED_SERVER(num); }
452         else if(save > me.selectedItem)
453         {
454                 if(me.selectedItem == 0) { return; }
455                 else
456                 {
457                         if(me.lastClickedTime >= me.lastBumpSelectTime)
458                         {
459                                 SUPER(XonoticServerList).setSelected(me, me.selectedItem - 1);
460                                 num = XonoticServerList_MapItems(me.selectedItem);
461                                 if(num >= 0)
462                                 {
463                                         me.lastBumpSelectTime = time;
464                                         SET_SELECTED_SERVER(num);
465                                 }
466                         }
467                 }
468         }
469         else if(save < me.selectedItem)
470         {
471                 if(me.selectedItem == me.nItems) { return; }
472                 else
473                 {
474                         if(me.lastClickedTime >= me.lastBumpSelectTime)
475                         {
476                                 SUPER(XonoticServerList).setSelected(me, me.selectedItem + 1);
477                                 num = XonoticServerList_MapItems(me.selectedItem);
478                                 if(num >= 0)
479                                 {
480                                         me.lastBumpSelectTime = time;
481                                         SET_SELECTED_SERVER(num);
482                                 }
483                         }
484                 }
485         }
486 }
487
488 void XonoticServerList_refreshServerList(entity me, float mode)
489 {
490         //print("refresh of type ", ftos(mode), "\n");
491
492         if(mode >= REFRESHSERVERLIST_REFILTER)
493         {
494                 float m, i, n;
495                 float listflags = 0;
496                 string s, typestr, modstr;
497
498                 s = me.filterString;
499
500                 m = strstrofs(s, ":", 0);
501                 if(m >= 0)
502                 {
503                         typestr = substring(s, 0, m);
504                         s = substring(s, m + 1, strlen(s) - m - 1);
505                         while(substring(s, 0, 1) == " ")
506                                 s = substring(s, 1, strlen(s) - 1);
507                 }
508                 else
509                         typestr = "";
510
511                 modstr = cvar_string("menu_slist_modfilter");
512
513                 m = SLIST_MASK_AND - 1;
514                 resethostcachemasks();
515
516                 // ping: reject negative ping (no idea why this happens in the first place, engine bug)
517                 sethostcachemasknumber(++m, SLIST_FIELD_PING, 0, SLIST_TEST_GREATEREQUAL);
518
519                 // show full button
520                 if(!me.filterShowFull)
521                 {
522                         sethostcachemasknumber(++m, SLIST_FIELD_FREESLOTS, 1, SLIST_TEST_GREATEREQUAL); // legacy
523                         sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, ":S0:", SLIST_TEST_NOTCONTAIN); // g_maxplayers support
524                 }
525
526                 // show empty button
527                 if(!me.filterShowEmpty)
528                         sethostcachemasknumber(++m, SLIST_FIELD_NUMHUMANS, 1, SLIST_TEST_GREATEREQUAL);
529
530                 // gametype filtering
531                 if(typestr != "")
532                         sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, strcat(typestr, ":"), SLIST_TEST_STARTSWITH);
533
534                 // mod filtering
535                 if(modstr != "")
536                 {
537                         if(substring(modstr, 0, 1) == "!")
538                                 sethostcachemaskstring(++m, SLIST_FIELD_MOD, resolvemod(substring(modstr, 1, strlen(modstr) - 1)), SLIST_TEST_NOTEQUAL);
539                         else
540                                 sethostcachemaskstring(++m, SLIST_FIELD_MOD, resolvemod(modstr), SLIST_TEST_EQUAL);
541                 }
542
543                 // server banning
544                 n = tokenizebyseparator(_Nex_ExtResponseSystem_BannedServers, " ");
545                 for(i = 0; i < n; ++i)
546                         if(argv(i) != "")
547                                 sethostcachemaskstring(++m, SLIST_FIELD_CNAME, argv(i), SLIST_TEST_NOTSTARTSWITH);
548
549                 m = SLIST_MASK_OR - 1;
550                 if(s != "")
551                 {
552                         sethostcachemaskstring(++m, SLIST_FIELD_NAME, s, SLIST_TEST_CONTAINS);
553                         sethostcachemaskstring(++m, SLIST_FIELD_MAP, s, SLIST_TEST_CONTAINS);
554                         sethostcachemaskstring(++m, SLIST_FIELD_PLAYERS, s, SLIST_TEST_CONTAINS);
555                         sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, strcat(s, ":"), SLIST_TEST_STARTSWITH);
556                 }
557
558                 // sorting flags
559                 //listflags |= SLSF_FAVORITES;
560                 listflags |= SLSF_CATEGORIES;
561                 if(me.currentSortOrder < 0) { listflags |= SLSF_DESCENDING; }
562                 sethostcachesort(me.currentSortField, listflags);
563         }
564         
565         resorthostcache();
566         if(mode >= REFRESHSERVERLIST_ASK)
567                 refreshhostcache(mode >= REFRESHSERVERLIST_RESET);
568 }
569 void XonoticServerList_focusEnter(entity me)
570 {
571         if(time < me.nextRefreshTime)
572         {
573                 //print("sorry, no refresh yet\n");
574                 return;
575         }
576         me.nextRefreshTime = time + 10;
577         me.refreshServerList(me, REFRESHSERVERLIST_ASK);
578 }
579
580 void XonoticServerList_draw(entity me)
581 {
582         float i, found, owned, num;
583
584         if(_Nex_ExtResponseSystem_BannedServersNeedsRefresh)
585         {
586                 if(!me.needsRefresh)
587                         me.needsRefresh = 2;
588                 _Nex_ExtResponseSystem_BannedServersNeedsRefresh = 0;
589         }
590
591         if(me.currentSortField == -1)
592         {
593                 me.setSortOrder(me, SLIST_FIELD_PING, +1);
594                 me.refreshServerList(me, REFRESHSERVERLIST_RESET);
595         }
596         else if(me.needsRefresh == 1)
597         {
598                 me.needsRefresh = 2; // delay by one frame to make sure "slist" has been executed
599         }
600         else if(me.needsRefresh == 2)
601         {
602                 me.needsRefresh = 0;
603                 me.refreshServerList(me, REFRESHSERVERLIST_REFILTER);
604         }
605
606         owned = ((me.selectedServer == me.ipAddressBox.text) && (me.ipAddressBox.text != ""));
607
608         for(i = 0; i < category_draw_count; ++i) { category_name[i] = -1; category_item[i] = -1; }
609         category_draw_count = 0;
610
611         if(autocvar_menu_slist_categories >= 0) // if less than 0, don't even draw a category heading for favorites
612         {
613                 float itemcount = gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT);
614                 me.nItems = itemcount;
615                 
616                 //float visible = floor(me.scrollPos / me.itemHeight);
617                 // ^ unfortunately no such optimization can be made-- we must process through the
618                 // entire list, otherwise there is no way to know which item is first in its category.
619
620                 float cat, x;
621                 for(i = 0; i < itemcount; ++i)
622                 {
623                         cat = gethostcachenumber(SLIST_FIELD_CATEGORY, i);
624                         if(cat)
625                         {
626                                 if(category_draw_count == 0)
627                                 {
628                                         category_name[category_draw_count] = cat;
629                                         category_item[category_draw_count] = i;
630                                         ++category_draw_count;
631                                         ++me.nItems;
632                                 }
633                                 else
634                                 {
635                                         found = 0;
636                                         for(x = 0; x < category_draw_count; ++x) { if(cat == category_name[x]) { found = 1; } }
637                                         if not(found)
638                                         {
639                                                 category_name[category_draw_count] = cat;
640                                                 category_item[category_draw_count] = i;
641                                                 ++category_draw_count;
642                                                 ++me.nItems;
643                                         }
644                                 }
645                         }
646                 }
647                 if(autocvar_menu_slist_categories_onlyifmultiple && (category_draw_count == 1))
648                 {
649                         category_name[0] = category_name[1] = -1;
650                         category_item[0] = category_item[1] = -1;
651                         category_draw_count = 0;
652                         me.nItems = itemcount;
653                 }
654         }
655         else { me.nItems = gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT); }
656
657         me.connectButton.disabled = ((me.nItems == 0) && (me.ipAddressBox.text == ""));
658         me.infoButton.disabled = ((me.nItems == 0) || !owned);
659         me.favoriteButton.disabled = ((me.nItems == 0) && (me.ipAddressBox.text == ""));
660
661         found = 0;
662         if(me.selectedServer)
663         {
664                 for(i = 0; i < me.nItems; ++i)
665                 {
666                         num = XonoticServerList_MapItems(i);
667                         if(num >= 0)
668                         {
669                                 if(gethostcachestring(SLIST_FIELD_CNAME, num) == me.selectedServer)
670                                 {
671                                         if(i != me.selectedItem)
672                                         {
673                                                 me.lastClickedServer = -1;
674                                                 me.selectedItem = i;
675                                         }
676                                         found = 1;
677                                         break;
678                                 }
679                         }
680                 }
681         }
682         if(!found)
683         {
684                 if(me.nItems > 0)
685                 {
686                         if(me.selectedItem >= me.nItems) { me.selectedItem = me.nItems - 1; }
687                         if(me.selectedServer) { strunzone(me.selectedServer); }
688
689                         num = XonoticServerList_MapItems(me.selectedItem);
690                         if(num >= 0) { me.selectedServer = strzone(gethostcachestring(SLIST_FIELD_CNAME, num)); }
691                 }
692         }
693         
694         if(owned)
695         {
696                 if(me.selectedServer != me.ipAddressBox.text)
697                 {
698                         me.ipAddressBox.setText(me.ipAddressBox, me.selectedServer);
699                         me.ipAddressBox.cursorPos = strlen(me.selectedServer);
700                         me.ipAddressBoxFocused = -1;
701                 }
702         }
703
704         if(me.ipAddressBoxFocused != me.ipAddressBox.focused)
705         {
706                 if(me.ipAddressBox.focused || me.ipAddressBoxFocused < 0)
707                         ServerList_Update_favoriteButton(NULL, me);
708                 me.ipAddressBoxFocused = me.ipAddressBox.focused;
709         }
710
711         SUPER(XonoticServerList).draw(me);
712 }
713 void ServerList_PingSort_Click(entity btn, entity me)
714 {
715         me.setSortOrder(me, SLIST_FIELD_PING, +1);
716 }
717 void ServerList_NameSort_Click(entity btn, entity me)
718 {
719         me.setSortOrder(me, SLIST_FIELD_NAME, -1); // why?
720 }
721 void ServerList_MapSort_Click(entity btn, entity me)
722 {
723         me.setSortOrder(me, SLIST_FIELD_MAP, -1); // why?
724 }
725 void ServerList_PlayerSort_Click(entity btn, entity me)
726 {
727         me.setSortOrder(me, SLIST_FIELD_NUMHUMANS, -1);
728 }
729 void ServerList_TypeSort_Click(entity btn, entity me)
730 {
731         string s, t;
732         float i, m;
733         s = me.filterString;
734         m = strstrofs(s, ":", 0);
735         if(m >= 0)
736         {
737                 s = substring(s, 0, m);
738                 while(substring(s, m+1, 1) == " ") // skip spaces
739                         ++m;
740         }
741         else
742                 s = "";
743
744         for(i = 1; ; i *= 2) // 20 modes ought to be enough for anyone
745         {
746                 t = MapInfo_Type_ToString(i);
747                 if(i > 1)
748                         if(t == "") // it repeats (default case)
749                         {
750                                 // no type was found
751                                 // choose the first one
752                                 s = MapInfo_Type_ToString(1);
753                                 break;
754                         }
755                 if(s == t)
756                 {
757                         // the type was found
758                         // choose the next one
759                         s = MapInfo_Type_ToString(i * 2);
760                         if(s == "")
761                                 s = MapInfo_Type_ToString(1);
762                         break;
763                 }
764         }
765
766         if(s != "")
767                 s = strcat(s, ":");
768         s = strcat(s, substring(me.filterString, m+1, strlen(me.filterString) - m - 1));
769
770         me.controlledTextbox.setText(me.controlledTextbox, s);
771         me.controlledTextbox.keyDown(me.controlledTextbox, K_END, 0, 0);
772         me.controlledTextbox.keyUp(me.controlledTextbox, K_END, 0, 0);
773         //ServerList_Filter_Change(me.controlledTextbox, me);
774 }
775 void ServerList_Filter_Change(entity box, entity me)
776 {
777         if(me.filterString)
778                 strunzone(me.filterString);
779         if(box.text != "")
780                 me.filterString = strzone(box.text);
781         else
782                 me.filterString = string_null;
783         me.refreshServerList(me, REFRESHSERVERLIST_REFILTER);
784
785         me.ipAddressBox.setText(me.ipAddressBox, "");
786         me.ipAddressBox.cursorPos = 0;
787         me.ipAddressBoxFocused = -1;
788 }
789 void ServerList_Categories_Click(entity box, entity me)
790 {
791         box.setChecked(box, autocvar_menu_slist_categories = !autocvar_menu_slist_categories);
792         ///refreshhostcache(TRUE);
793
794         //cvar_set("net_slist_pause", "0");
795         //Destroy_Category_Entities();
796         //CALL_ACCUMULATED_FUNCTION(RegisterSLCategories);
797         //me.refreshServerList(me, REFRESHSERVERLIST_REFILTER);
798
799         me.refreshServerList(me, REFRESHSERVERLIST_RESORT);
800
801         me.ipAddressBox.setText(me.ipAddressBox, "");
802         me.ipAddressBox.cursorPos = 0;
803         me.ipAddressBoxFocused = -1;
804 }
805 void ServerList_ShowEmpty_Click(entity box, entity me)
806 {
807         box.setChecked(box, me.filterShowEmpty = !me.filterShowEmpty);
808         me.refreshServerList(me, REFRESHSERVERLIST_REFILTER);
809
810         me.ipAddressBox.setText(me.ipAddressBox, "");
811         me.ipAddressBox.cursorPos = 0;
812         me.ipAddressBoxFocused = -1;
813 }
814 void ServerList_ShowFull_Click(entity box, entity me)
815 {
816         box.setChecked(box, me.filterShowFull = !me.filterShowFull);
817         me.refreshServerList(me, REFRESHSERVERLIST_REFILTER);
818
819         me.ipAddressBox.setText(me.ipAddressBox, "");
820         me.ipAddressBox.cursorPos = 0;
821         me.ipAddressBoxFocused = -1;
822 }
823 void XonoticServerList_setSortOrder(entity me, float fld, float direction)
824 {
825         if(me.currentSortField == fld)
826                 direction = -me.currentSortOrder;
827         me.currentSortOrder = direction;
828         me.currentSortField = fld;
829         me.sortButton1.forcePressed = (fld == SLIST_FIELD_PING);
830         me.sortButton2.forcePressed = (fld == SLIST_FIELD_NAME);
831         me.sortButton3.forcePressed = (fld == SLIST_FIELD_MAP);
832         me.sortButton4.forcePressed = 0;
833         me.sortButton5.forcePressed = (fld == SLIST_FIELD_NUMHUMANS);
834         me.selectedItem = 0;
835         if(me.selectedServer)
836                 strunzone(me.selectedServer);
837         me.selectedServer = string_null;
838         me.refreshServerList(me, REFRESHSERVERLIST_REFILTER);
839 }
840 void XonoticServerList_positionSortButton(entity me, entity btn, float theOrigin, float theSize, string theTitle, void(entity, entity) theFunc)
841 {
842         vector originInLBSpace, sizeInLBSpace;
843         originInLBSpace = eY * (-me.itemHeight);
844         sizeInLBSpace = eY * me.itemHeight + eX * (1 - me.controlWidth);
845
846         vector originInDialogSpace, sizeInDialogSpace;
847         originInDialogSpace = boxToGlobal(originInLBSpace, me.Container_origin, me.Container_size);
848         sizeInDialogSpace = boxToGlobalSize(sizeInLBSpace, me.Container_size);
849
850         btn.Container_origin_x = originInDialogSpace_x + sizeInDialogSpace_x * theOrigin;
851         btn.Container_size_x   =                         sizeInDialogSpace_x * theSize;
852         btn.setText(btn, theTitle);
853         btn.onClick = theFunc;
854         btn.onClickEntity = me;
855         btn.resized = 1;
856 }
857 void XonoticServerList_resizeNotify(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
858 {
859         SUPER(XonoticServerList).resizeNotify(me, relOrigin, relSize, absOrigin, absSize);
860
861         me.realFontSize_y = me.fontSize / (absSize_y * me.itemHeight);
862         me.realFontSize_x = me.fontSize / (absSize_x * (1 - me.controlWidth));
863         me.realUpperMargin = 0.5 * (1 - me.realFontSize_y);
864
865         me.columnIconsOrigin = 0;
866         me.columnIconsSize = me.realFontSize_x * 4 * me.iconsSizeFactor;
867         me.columnPingSize = me.realFontSize_x * 3;
868         me.columnMapSize = me.realFontSize_x * 10;
869         me.columnTypeSize = me.realFontSize_x * 4;
870         me.columnPlayersSize = me.realFontSize_x * 5;
871         me.columnNameSize = 1 - me.columnPlayersSize - me.columnMapSize - me.columnPingSize - me.columnIconsSize - me.columnTypeSize - 5 * me.realFontSize_x;
872         me.columnPingOrigin = me.columnIconsOrigin + me.columnIconsSize + me.realFontSize_x;
873         me.columnNameOrigin = me.columnPingOrigin + me.columnPingSize + me.realFontSize_x;
874         me.columnMapOrigin = me.columnNameOrigin + me.columnNameSize + me.realFontSize_x;
875         me.columnTypeOrigin = me.columnMapOrigin + me.columnMapSize + me.realFontSize_x;
876         me.columnPlayersOrigin = me.columnTypeOrigin + me.columnTypeSize + me.realFontSize_x;
877
878         me.positionSortButton(me, me.sortButton1, me.columnPingOrigin, me.columnPingSize, _("Ping"), ServerList_PingSort_Click);
879         me.positionSortButton(me, me.sortButton2, me.columnNameOrigin, me.columnNameSize, _("Host name"), ServerList_NameSort_Click);
880         me.positionSortButton(me, me.sortButton3, me.columnMapOrigin, me.columnMapSize, _("Map"), ServerList_MapSort_Click);
881         me.positionSortButton(me, me.sortButton4, me.columnTypeOrigin, me.columnTypeSize, _("Type"), ServerList_TypeSort_Click);
882         me.positionSortButton(me, me.sortButton5, me.columnPlayersOrigin, me.columnPlayersSize, _("Players"), ServerList_PlayerSort_Click);
883
884         float f;
885         f = me.currentSortField;
886         if(f >= 0)
887         {
888                 me.currentSortField = -1;
889                 me.setSortOrder(me, f, me.currentSortOrder); // force resetting the sort order
890         }
891 }
892 void ServerList_Connect_Click(entity btn, entity me)
893 {
894         localcmd(sprintf("connect %s\n",
895                 ((me.ipAddressBox.text != "") ?
896                         me.ipAddressBox.text : me.selectedServer
897                 )
898         ));
899 }
900 void ServerList_Favorite_Click(entity btn, entity me)
901 {
902         string ipstr;
903         ipstr = netaddress_resolve(me.ipAddressBox.text, 26000);
904         if(ipstr != "")
905         {
906                 me.toggleFavorite(me, me.ipAddressBox.text);
907                 me.ipAddressBoxFocused = -1;
908         }
909 }
910 void ServerList_Info_Click(entity btn, entity me)
911 {
912         main.serverInfoDialog.loadServerInfo(main.serverInfoDialog, XonoticServerList_MapItems(me.selectedItem));
913         DialogOpenButton_Click(me, main.serverInfoDialog);
914 }
915 void XonoticServerList_clickListBoxItem(entity me, float i, vector where)
916 {
917         float num = XonoticServerList_MapItems(i);
918         if(num >= 0)
919         {
920                 if(num == me.lastClickedServer)
921                         if(time < me.lastClickedTime + 0.3)
922                         {
923                                 // DOUBLE CLICK!
924                                 ServerList_Connect_Click(NULL, me);
925                         }
926                 me.lastClickedServer = num;
927                 me.lastClickedTime = time;
928         }
929 }
930 void XonoticServerList_drawListBoxItem(entity me, float i, vector absSize, float isSelected)
931 {
932         // layout: Ping, Server name, Map name, NP, TP, MP
933         float p, q;
934         float isv4, isv6;
935         vector theColor;
936         float theAlpha;
937         float m, pure, freeslots, j, sflags;
938         string s, typestr, versionstr, k, v, modname;
939
940         float item = XonoticServerList_MapItems(i);
941         //print(sprintf("time: %f, i: %d, item: %d, nitems: %d\n", time, i, item, me.nItems));
942         
943         if(item < 0)
944         {
945                 entity catent = Get_Cat_Ent(-item);
946                 if(catent)
947                 {
948                         draw_Text(
949                                 eY * me.realUpperMargin
950                                 +
951                                 eX * (me.columnNameOrigin + (me.columnNameSize - draw_TextWidth(catent.cat_string, 0, me.realFontSize)) * 0.5),
952                                 catent.cat_string,
953                                 me.realFontSize,
954                                 '1 1 1',
955                                 SKINALPHA_TEXT,
956                                 0
957                         );
958                         return;
959                 }
960         }
961         
962         if(isSelected)
963                 draw_Fill('0 0 0', '1 1 0', SKINCOLOR_LISTBOX_SELECTED, SKINALPHA_LISTBOX_SELECTED);
964
965         s = gethostcachestring(SLIST_FIELD_QCSTATUS, item);
966         m = tokenizebyseparator(s, ":");
967         typestr = "";
968         if(m >= 2)
969         {
970                 typestr = argv(0);
971                 versionstr = argv(1);
972         }
973         freeslots = -1;
974         sflags = -1;
975         modname = "";
976         pure = 0;
977         for(j = 2; j < m; ++j)
978         {
979                 if(argv(j) == "")
980                         break;
981                 k = substring(argv(j), 0, 1);
982                 v = substring(argv(j), 1, -1);
983                 if(k == "P")
984                         pure = stof(v);
985                 else if(k == "S")
986                         freeslots = stof(v);
987                 else if(k == "F")
988                         sflags = stof(v);
989                 else if(k == "M")
990                         modname = v;
991         }
992
993 #ifdef COMPAT_NO_MOD_IS_XONOTIC
994         if(modname == "")
995                 modname = "Xonotic";
996 #endif
997
998         /*
999         SLIST_FIELD_MOD = gethostcacheindexforkey("mod");
1000         s = gethostcachestring(SLIST_FIELD_MOD, item);
1001         if(s != "data")
1002                 if(modname == "Xonotic")
1003                         modname = s;
1004         */
1005
1006         // list the mods here on which the pure server check actually works
1007         if(modname != "Xonotic")
1008         if(modname != "MinstaGib")
1009         if(modname != "CTS")
1010         if(modname != "NIX")
1011         if(modname != "NewToys")
1012                 pure = 0;
1013
1014         if(gethostcachenumber(SLIST_FIELD_FREESLOTS, item) <= 0)
1015                 theAlpha = SKINALPHA_SERVERLIST_FULL;
1016         else if(freeslots == 0)
1017                 theAlpha = SKINALPHA_SERVERLIST_FULL; // g_maxplayers support
1018         else if not(gethostcachenumber(SLIST_FIELD_NUMHUMANS, item))
1019                 theAlpha = SKINALPHA_SERVERLIST_EMPTY;
1020         else
1021                 theAlpha = 1;
1022
1023         p = gethostcachenumber(SLIST_FIELD_PING, item);
1024 #define PING_LOW 75
1025 #define PING_MED 200
1026 #define PING_HIGH 500
1027         if(p < PING_LOW)
1028                 theColor = SKINCOLOR_SERVERLIST_LOWPING + (SKINCOLOR_SERVERLIST_MEDPING - SKINCOLOR_SERVERLIST_LOWPING) * (p / PING_LOW);
1029         else if(p < PING_MED)
1030                 theColor = SKINCOLOR_SERVERLIST_MEDPING + (SKINCOLOR_SERVERLIST_HIGHPING - SKINCOLOR_SERVERLIST_MEDPING) * ((p - PING_LOW) / (PING_MED - PING_LOW));
1031         else if(p < PING_HIGH)
1032         {
1033                 theColor = SKINCOLOR_SERVERLIST_HIGHPING;
1034                 theAlpha *= 1 + (SKINALPHA_SERVERLIST_HIGHPING - 1) * ((p - PING_MED) / (PING_HIGH - PING_MED));
1035         }
1036         else
1037         {
1038                 theColor = eX;
1039                 theAlpha *= SKINALPHA_SERVERLIST_HIGHPING;
1040         }
1041
1042         if(gethostcachenumber(SLIST_FIELD_ISFAVORITE, item))
1043         {
1044                 theColor = theColor * (1 - SKINALPHA_SERVERLIST_FAVORITE) + SKINCOLOR_SERVERLIST_FAVORITE * SKINALPHA_SERVERLIST_FAVORITE;
1045                 theAlpha = theAlpha * (1 - SKINALPHA_SERVERLIST_FAVORITE) + SKINALPHA_SERVERLIST_FAVORITE;
1046         }
1047
1048         s = gethostcachestring(SLIST_FIELD_CNAME, item);
1049
1050         isv4 = isv6 = 0;
1051         if(substring(s, 0, 1) == "[")
1052         {
1053                 isv6 = 1;
1054                 me.seenIPv6 += 1;
1055         }
1056         else if(strstrofs("0123456789", substring(s, 0, 1), 0) >= 0)
1057         {
1058                 isv4 = 1;
1059                 me.seenIPv4 += 1;
1060         }
1061
1062         q = stof(substring(crypto_getencryptlevel(s), 0, 1));
1063         if((q <= 0 && cvar("crypto_aeslevel") >= 3) || (q >= 3 && cvar("crypto_aeslevel") <= 0))
1064         {
1065                 theColor = SKINCOLOR_SERVERLIST_IMPOSSIBLE;
1066                 theAlpha = SKINALPHA_SERVERLIST_IMPOSSIBLE;
1067         }
1068
1069         if(q == 1)
1070         {
1071                 if(cvar("crypto_aeslevel") >= 2)
1072                         q |= 4;
1073         }
1074         if(q == 2)
1075         {
1076                 if(cvar("crypto_aeslevel") >= 1)
1077                         q |= 4;
1078         }
1079         if(q == 3)
1080                 q = 5;
1081         else if(q >= 3)
1082                 q -= 2;
1083         // possible status:
1084         // 0: crypto off
1085         // 1: AES possible
1086         // 2: AES recommended but not available
1087         // 3: AES possible and will be used
1088         // 4: AES recommended and will be used
1089         // 5: AES required
1090
1091         // --------------
1092         //  RENDER ICONS
1093         // --------------
1094         vector iconSize = '0 0 0';
1095         iconSize_y = me.realFontSize_y * me.iconsSizeFactor;
1096         iconSize_x = me.realFontSize_x * me.iconsSizeFactor;
1097
1098         vector iconPos = '0 0 0';
1099         iconPos_x = (me.columnIconsSize - 3 * iconSize_x) * 0.5;
1100         iconPos_y = (1 - iconSize_y) * 0.5;
1101
1102         string n;
1103
1104         if not(me.seenIPv4 && me.seenIPv6)
1105         {
1106                 iconPos_x += iconSize_x * 0.5;
1107         }
1108         else if(me.seenIPv4 && me.seenIPv6)
1109         {
1110                 n = string_null;
1111                 if(isv6)
1112                         draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_ipv6"), 0); // PRECACHE_PIC_MIPMAP
1113                 else if(isv4)
1114                         draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_ipv4"), 0); // PRECACHE_PIC_MIPMAP
1115                 if(n)
1116                         draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1117                 iconPos_x += iconSize_x;
1118         }
1119
1120         if(q > 0)
1121         {
1122                 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_aeslevel", ftos(q)), 0); // PRECACHE_PIC_MIPMAP
1123                 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1124         }
1125         iconPos_x += iconSize_x;
1126
1127         if(modname == "Xonotic")
1128         {
1129                 if(pure == 0)
1130                 {
1131                         draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_pure1"), PRECACHE_PIC_MIPMAP);
1132                         draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1133                 }
1134         }
1135         else
1136         {
1137                 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_mod_", modname), PRECACHE_PIC_MIPMAP);
1138                 if(draw_PictureSize(n) == '0 0 0')
1139                         draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_mod_"), PRECACHE_PIC_MIPMAP);
1140                 if(pure == 0)
1141                         draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1142                 else
1143                         draw_Picture(iconPos, n, iconSize, '1 1 1', SKINALPHA_SERVERLIST_ICON_NONPURE);
1144         }
1145         iconPos_x += iconSize_x;
1146
1147         if(sflags >= 0 && (sflags & SERVERFLAG_PLAYERSTATS))
1148         {
1149                 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_stats1"), 0); // PRECACHE_PIC_MIPMAP
1150                 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1151         }
1152         iconPos_x += iconSize_x;
1153         
1154         // --------------
1155         //  RENDER TEXT
1156         // --------------
1157         
1158         // ping
1159         s = ftos(p);
1160         draw_Text(me.realUpperMargin * eY + (me.columnPingOrigin + me.columnPingSize - draw_TextWidth(s, 0, me.realFontSize)) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1161
1162         // server name
1163         s = draw_TextShortenToWidth(gethostcachestring(SLIST_FIELD_NAME, item), me.columnNameSize, 0, me.realFontSize);
1164         draw_Text(me.realUpperMargin * eY + me.columnNameOrigin * eX, s, me.realFontSize, theColor, theAlpha, 0);
1165
1166         // server map
1167         s = draw_TextShortenToWidth(gethostcachestring(SLIST_FIELD_MAP, item), me.columnMapSize, 0, me.realFontSize);
1168         draw_Text(me.realUpperMargin * eY + (me.columnMapOrigin + (me.columnMapSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1169
1170         // server gametype
1171         s = draw_TextShortenToWidth(typestr, me.columnTypeSize, 0, me.realFontSize);
1172         draw_Text(me.realUpperMargin * eY + (me.columnTypeOrigin + (me.columnTypeSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1173
1174         // server playercount
1175         s = strcat(ftos(gethostcachenumber(SLIST_FIELD_NUMHUMANS, item)), "/", ftos(gethostcachenumber(SLIST_FIELD_MAXPLAYERS, item)));
1176         draw_Text(me.realUpperMargin * eY + (me.columnPlayersOrigin + (me.columnPlayersSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1177 }
1178
1179 float XonoticServerList_keyDown(entity me, float scan, float ascii, float shift)
1180 {
1181         float i = XonoticServerList_MapItems(me.selectedItem);
1182         vector org, sz;
1183
1184         org = boxToGlobal(eY * (me.selectedItem * me.itemHeight - me.scrollPos), me.origin, me.size);
1185         sz = boxToGlobalSize(eY * me.itemHeight + eX * (1 - me.controlWidth), me.size);
1186
1187         me.lastBumpSelectTime = 0;
1188
1189         if(scan == K_ENTER || scan == K_KP_ENTER)
1190         {
1191                 ServerList_Connect_Click(NULL, me);
1192                 return 1;
1193         }
1194         else if(scan == K_MOUSE2 || scan == K_SPACE)
1195         {
1196                 if((me.nItems != 0) && (i >= 0))
1197                 {
1198                         main.serverInfoDialog.loadServerInfo(main.serverInfoDialog, i);
1199                         DialogOpenButton_Click_withCoords(me, main.serverInfoDialog, org, sz);
1200                         return 1;
1201                 }
1202                 return 0;
1203         }
1204         else if(scan == K_INS || scan == K_MOUSE3 || scan == K_KP_INS)
1205         {
1206                 if((me.nItems != 0) && (i >= 0))
1207                 {
1208                         me.toggleFavorite(me, me.selectedServer);
1209                         me.ipAddressBoxFocused = -1;
1210                         return 1;
1211                 }
1212                 return 0;
1213         }
1214         else if(SUPER(XonoticServerList).keyDown(me, scan, ascii, shift))
1215                 return 1;
1216         else if(!me.controlledTextbox)
1217                 return 0;
1218         else
1219                 return me.controlledTextbox.keyDown(me.controlledTextbox, scan, ascii, shift);
1220 }
1221 #endif