]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/menu/xonotic/serverlist.c
Refactor refreshServerList a bit.
[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 const float REFRESHSERVERLIST_RESORT = 0;
65 const float REFRESHSERVERLIST_REFILTER = 1;
66 const float REFRESHSERVERLIST_ASK = 2;
67 const float REFRESHSERVERLIST_RESET = 3;
68
69 var float autocvar_menu_slist_categories = TRUE;
70 var float autocvar_menu_slist_categories_onlyifmultiple = TRUE; 
71 var float autocvar_menu_slist_purethreshold = 10;
72 var string autocvar_menu_slist_recommended = "76.124.107.5:26004";
73
74 // server cache fields
75 #define SLIST_FIELDS \
76         SLIST_FIELD(CNAME,       "cname") \
77         SLIST_FIELD(PING,        "ping") \
78         SLIST_FIELD(GAME,        "game") \
79         SLIST_FIELD(MOD,         "mod") \
80         SLIST_FIELD(MAP,         "map") \
81         SLIST_FIELD(NAME,        "name") \
82         SLIST_FIELD(MAXPLAYERS,  "maxplayers") \
83         SLIST_FIELD(NUMPLAYERS,  "numplayers") \
84         SLIST_FIELD(NUMHUMANS,   "numhumans") \
85         SLIST_FIELD(NUMBOTS,     "numbots") \
86         SLIST_FIELD(PROTOCOL,    "protocol") \
87         SLIST_FIELD(FREESLOTS,   "freeslots") \
88         SLIST_FIELD(PLAYERS,     "players") \
89         SLIST_FIELD(QCSTATUS,    "qcstatus") \
90         SLIST_FIELD(CATEGORY,    "category") \
91         SLIST_FIELD(ISFAVORITE,  "isfavorite")
92
93 #define SLIST_FIELD(suffix,name) float SLIST_FIELD_##suffix;
94 SLIST_FIELDS
95 #undef SLIST_FIELD
96
97 // sort flags
98 float SLSF_DESCENDING = 1;
99 float SLSF_FAVORITES = 2;
100 float SLSF_CATEGORIES = 4;
101
102 float Get_Cat_Num_FromString(string input);
103 entity Get_Cat_Ent(float catnum);
104
105 float IsServerInList(string list, string srv);
106 #define IsFavorite(srv) IsServerInList(cvar_string("net_slist_favorites"), srv)
107 #define IsRecommended(srv) IsServerInList(cvar_string("menu_slist_recommended"), srv) // todo: use update notification instead of cvar
108
109 float CheckCategoryOverride(float cat);
110 float CheckCategoryForEntry(float entry); 
111 float m_getserverlistentrycategory(float entry) { return CheckCategoryOverride(CheckCategoryForEntry(entry)); }
112
113 // fields for category entities
114 #define MAX_CATEGORIES 9
115 #define CATEGORY_FIRST 1
116 entity categories[MAX_CATEGORIES];
117 float category_ent_count;
118 .string cat_name;
119 .string cat_string;
120 .string cat_enoverride_string;
121 .string cat_dioverride_string;
122 .float cat_enoverride;
123 .float cat_dioverride;
124
125 // fields for drawing categories
126 float category_name[MAX_CATEGORIES];
127 float category_item[MAX_CATEGORIES];
128 float category_draw_count;
129
130 #define CATEGORIES \
131         SLIST_CATEGORY(CAT_FAVORITED,    "",            "",             _("Favorites")) \
132         SLIST_CATEGORY(CAT_RECOMMENDED,  "",            "CAT_SERVERS",  _("Recommended")) \
133         SLIST_CATEGORY(CAT_NORMAL,       "",            "CAT_SERVERS",  _("Normal Servers")) \
134         SLIST_CATEGORY(CAT_SERVERS,      "CAT_NORMAL",  "CAT_SERVERS",  _("Servers")) \
135         SLIST_CATEGORY(CAT_XPM,          "CAT_NORMAL",  "CAT_SERVERS",  _("Competitive Mode")) \
136         SLIST_CATEGORY(CAT_MODIFIED,     "",            "CAT_SERVERS",  _("Modified Servers")) \
137         SLIST_CATEGORY(CAT_OVERKILL,     "",            "CAT_SERVERS",  _("Overkill Mode")) \
138         SLIST_CATEGORY(CAT_MINSTAGIB,    "",            "CAT_SERVERS",  _("MinstaGib Mode")) \
139         SLIST_CATEGORY(CAT_DEFRAG,       "",            "CAT_SERVERS",  _("Defrag Mode"))
140
141 // C is stupid, must use extra macro for concatenation
142 #define SLIST_ADD_CAT_CVAR(name,default) var string autocvar_menu_slist_categories_##name##_override = default;
143 #define SLIST_CATEGORY(name,enoverride,dioverride,string) \
144         SLIST_ADD_CAT_CVAR(name, enoverride) \
145         float name; \
146         void RegisterSLCategory_##name() \
147         { \
148                 SET_FIELD_COUNT(name, CATEGORY_FIRST, category_ent_count) \
149                 CHECK_MAX_COUNT(name, MAX_CATEGORIES, category_ent_count, "SLIST_CATEGORY") \
150                 entity cat = spawn(); \
151                 categories[name - 1] = cat; \
152                 cat.classname = "slist_category"; \
153                 cat.cat_name = strzone(#name); \
154                 cat.cat_enoverride_string = strzone(autocvar_menu_slist_categories_##name##_override); \
155                 cat.cat_dioverride_string = strzone(dioverride); \
156                 cat.cat_string = strzone(string); \
157         } \
158         ACCUMULATE_FUNCTION(RegisterSLCategories, RegisterSLCategory_##name);
159
160 CATEGORIES
161
162 void RegisterSLCategories_Done()
163 {
164         float i, catnum;
165         string s;
166
167         #define PROCESS_OVERRIDE(override_string,override_field) \
168                 for(i = 0; i < category_ent_count; ++i) \
169                 { \
170                         s = categories[i].override_string; \
171                         if((s != "") && (s != categories[i].cat_name)) \
172                         { \
173                                 catnum = Get_Cat_Num_FromString(s); \
174                                 if(catnum) \
175                                 { \
176                                         strunzone(categories[i].override_string); \
177                                         categories[i].override_field = catnum; \
178                                         continue; \
179                                 } \
180                         } \
181                         strunzone(categories[i].override_string); \
182                         categories[i].override_field = 0; \
183                 }
184
185         PROCESS_OVERRIDE(cat_enoverride_string, cat_enoverride)
186         PROCESS_OVERRIDE(cat_dioverride_string, cat_dioverride)
187         #undef PROCESS_OVERRIDE
188 }
189 ACCUMULATE_FUNCTION(RegisterSLCategories, RegisterSLCategories_Done);
190
191 #undef SLIST_ADD_CAT_CVAR
192 #undef SLIST_CATEGORY
193 #undef CATEGORIES
194
195 void ServerList_Connect_Click(entity btn, entity me);
196 void ServerList_Categories_Click(entity box, entity me);
197 void ServerList_ShowEmpty_Click(entity box, entity me);
198 void ServerList_ShowFull_Click(entity box, entity me);
199 void ServerList_Filter_Change(entity box, entity me);
200 void ServerList_Favorite_Click(entity btn, entity me);
201 void ServerList_Info_Click(entity btn, entity me);
202 void ServerList_Update_favoriteButton(entity btn, entity me);
203
204 #endif
205 #endif
206 #ifdef IMPLEMENTATION
207
208 // Supporting Functions
209 float Get_Cat_Num_FromString(string input)
210 {
211         float i;
212         for(i = 0; i < category_ent_count; ++i) { if(categories[i].cat_name == input) { return (i + 1); } }
213         print(sprintf("Get_Cat_Num_FromString('%s'): Improper category name!\n", input));
214         return 0;
215 }
216 entity Get_Cat_Ent(float catnum)
217 {
218         if((catnum > 0) && (catnum <= category_ent_count))
219         {
220                 return categories[catnum - 1];
221         }
222         else
223         {
224                 error(sprintf("Get_Cat_Ent(%d): Improper category number!\n", catnum));
225                 return world;
226         }
227 }
228
229
230 float IsServerInList(string list, string srv)
231 {
232         string p;
233         float i, n;
234         if(srv == "")
235                 return FALSE;
236         srv = netaddress_resolve(srv, 26000);
237         if(srv == "")
238                 return FALSE;
239         p = crypto_getidfp(srv);
240         n = tokenize_console(list);
241         for(i = 0; i < n; ++i)
242         {
243                 if(substring(argv(i), 0, 1) != "[" && strlen(argv(i)) == 44 && strstrofs(argv(i), ".", 0) < 0)
244                 {
245                         if(p)
246                                 if(argv(i) == p)
247                                         return TRUE;
248                 }
249                 else
250                 {
251                         if(srv == netaddress_resolve(argv(i), 26000))
252                                 return TRUE;
253                 }
254         }
255         return FALSE;
256 }
257
258 float CheckCategoryOverride(float cat)
259 {
260         entity catent = Get_Cat_Ent(cat);
261         if(catent)
262         {
263                 float override = (autocvar_menu_slist_categories ? catent.cat_enoverride : catent.cat_dioverride); 
264                 if(override) { return override; }
265                 else { return cat; }
266         }
267         else
268         {
269                 error(sprintf("CheckCategoryOverride(%d): Improper category number!\n", cat));
270                 return cat;
271         }
272 }
273
274 float CheckCategoryForEntry(float entry)
275 {
276         string s, k, v, modtype = "";
277         float j, m, impure = 0;
278         s = gethostcachestring(SLIST_FIELD_QCSTATUS, entry);
279         m = tokenizebyseparator(s, ":");
280
281         for(j = 2; j < m; ++j)
282         {
283                 if(argv(j) == "")
284                         break;
285                 k = substring(argv(j), 0, 1);
286                 v = substring(argv(j), 1, -1);
287                 if(k == "P") { impure = stof(v); }
288                 else if(k == "M") { modtype = strtolower(v); }
289         }
290
291         //print(sprintf("modtype = %s\n", modtype)); 
292
293         if(impure > autocvar_menu_slist_purethreshold) { impure = TRUE; }
294         else { impure = FALSE; }
295
296         if(gethostcachenumber(SLIST_FIELD_ISFAVORITE, entry)) { return CAT_FAVORITED; }
297         if(IsRecommended(gethostcachestring(SLIST_FIELD_CNAME, entry))) { return CAT_RECOMMENDED; }
298         else if(modtype != "xonotic")
299         {
300                 switch(modtype)
301                 {
302                         // old servers which don't report their mod name are considered modified now
303                         case "": { return CAT_MODIFIED; }
304                         
305                         case "xpm": { return CAT_XPM; } 
306                         case "minstagib": { return CAT_MINSTAGIB; }
307                         case "overkill": { return CAT_OVERKILL; }
308
309                         // "cts" is allowed as compat, xdf is replacement
310                         case "cts": 
311                         case "xdf": { return CAT_DEFRAG; }
312                         
313                         //if(modname != "CTS")
314                         //if(modname != "NIX")
315                         //if(modname != "NewToys")
316                         
317                         default: { dprint(sprintf("Found strange mod type: %s\n", modtype)); return CAT_MODIFIED; }
318                 }
319         }
320         else { return (impure ? CAT_MODIFIED : CAT_NORMAL); }
321
322         // should never hit this point
323         error(sprintf("CheckCategoryForEntry(%d): Function fell through without normal return!\n", entry));
324         return FALSE;
325 }
326
327 float XonoticServerList_MapItems(float num)
328 {
329         float i, n;
330
331         if not(category_draw_count) { return num; } // there are no categories to process
332
333         for(i = 0, n = 1; n <= category_draw_count; ++i, ++n)
334         {
335                 //print(sprintf("num: %d, i: %d, category_draw_count: %d, category_item[i]: %d\n", num, i, category_draw_count, category_item[i])); 
336                 if(category_item[i] == (num - i)) { /*print("inserting cat... \\/\n");*/ return -category_name[i]; }
337                 else if(n == category_draw_count) { /*print("end item... \\/\n");*/ return (num - n); }
338                 else if((num - i) <= category_item[n]) { /*print("next item... \\/\n");*/ return (num - n); }
339         }
340
341         // should never hit this point
342         error("wtf XonoticServerList_MapItems fail?");
343         return FALSE;
344 }
345
346 void XonoticServerList_toggleFavorite(entity me, string srv)
347 {
348         string s, s0, s1, s2, srv_resolved, p;
349         float i, n, f;
350         srv_resolved = netaddress_resolve(srv, 26000);
351         p = crypto_getidfp(srv_resolved);
352         s = cvar_string("net_slist_favorites");
353         n = tokenize_console(s);
354         f = 0;
355         for(i = 0; i < n; ++i)
356         {
357                 if(substring(argv(i), 0, 1) != "[" && strlen(argv(i)) == 44 && strstrofs(argv(i), ".", 0) < 0)
358                 {
359                         if(p)
360                                 if(argv(i) != p)
361                                         continue;
362                 }
363                 else
364                 {
365                         if(srv_resolved != netaddress_resolve(argv(i), 26000))
366                                 continue;
367                 }
368                 s0 = s1 = s2 = "";
369                 if(i > 0)
370                         s0 = substring(s, 0, argv_end_index(i - 1));
371                 if(i < n-1)
372                         s2 = substring(s, argv_start_index(i + 1), -1);
373                 if(s0 != "" && s2 != "")
374                         s1 = " ";
375                 cvar_set("net_slist_favorites", strcat(s0, s1, s2));
376                 s = cvar_string("net_slist_favorites");
377                 n = tokenize_console(s);
378                 f = 1;
379                 --i;
380         }
381         
382         if(!f)
383         {
384                 s1 = "";
385                 if(s != "")
386                         s1 = " ";
387                 if(p)
388                         cvar_set("net_slist_favorites", strcat(s, s1, p));
389                 else
390                         cvar_set("net_slist_favorites", strcat(s, s1, srv));
391         }
392
393         me.refreshServerList(me, REFRESHSERVERLIST_RESORT);
394 }
395
396 void ServerList_Update_favoriteButton(entity btn, entity me)
397 {
398         me.favoriteButton.setText(me.favoriteButton,
399                 (IsFavorite(me.ipAddressBox.text) ?
400                         _("Remove") : _("Bookmark")
401                 )
402         );
403 }
404
405 entity makeXonoticServerList()
406 {
407         entity me;
408         me = spawnXonoticServerList();
409         me.configureXonoticServerList(me);
410         return me;
411 }
412 void XonoticServerList_configureXonoticServerList(entity me)
413 {
414         me.configureXonoticListBox(me);
415
416         // update field ID's
417         #define SLIST_FIELD(suffix,name) SLIST_FIELD_##suffix = gethostcacheindexforkey(name);
418         SLIST_FIELDS
419         #undef SLIST_FIELD
420
421         // clear list
422         me.nItems = 0;
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