]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/menu/xonotic/serverlist.c
Fix handling of overrides with categories
[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
11         ATTRIB(XonoticServerList, iconsSizeFactor, float, 0.85)
12
13         ATTRIB(XonoticServerList, realFontSize, vector, '0 0 0')
14         ATTRIB(XonoticServerList, realUpperMargin, float, 0)
15         ATTRIB(XonoticServerList, columnIconsOrigin, float, 0)
16         ATTRIB(XonoticServerList, columnIconsSize, float, 0)
17         ATTRIB(XonoticServerList, columnPingOrigin, float, 0)
18         ATTRIB(XonoticServerList, columnPingSize, float, 0)
19         ATTRIB(XonoticServerList, columnNameOrigin, float, 0)
20         ATTRIB(XonoticServerList, columnNameSize, float, 0)
21         ATTRIB(XonoticServerList, columnMapOrigin, float, 0)
22         ATTRIB(XonoticServerList, columnMapSize, float, 0)
23         ATTRIB(XonoticServerList, columnTypeOrigin, float, 0)
24         ATTRIB(XonoticServerList, columnTypeSize, float, 0)
25         ATTRIB(XonoticServerList, columnPlayersOrigin, float, 0)
26         ATTRIB(XonoticServerList, columnPlayersSize, float, 0)
27
28         ATTRIB(XonoticServerList, selectedServer, string, string_null) // to restore selected server when needed
29         METHOD(XonoticServerList, setSelected, void(entity, float))
30         METHOD(XonoticServerList, setSortOrder, void(entity, float, float))
31         ATTRIB(XonoticServerList, filterShowEmpty, float, 1)
32         ATTRIB(XonoticServerList, filterShowFull, float, 1)
33         ATTRIB(XonoticServerList, filterString, string, string_null)
34         ATTRIB(XonoticServerList, controlledTextbox, entity, NULL)
35         ATTRIB(XonoticServerList, ipAddressBox, entity, NULL)
36         ATTRIB(XonoticServerList, favoriteButton, entity, NULL)
37         ATTRIB(XonoticServerList, nextRefreshTime, float, 0)
38         METHOD(XonoticServerList, refreshServerList, void(entity, float)) // refresh mode: 0 = just reparametrize, 1 = send new requests, 2 = clear
39         ATTRIB(XonoticServerList, needsRefresh, float, 1)
40         METHOD(XonoticServerList, focusEnter, void(entity))
41         METHOD(XonoticServerList, positionSortButton, void(entity, entity, float, float, string, void(entity, entity)))
42         ATTRIB(XonoticServerList, sortButton1, entity, NULL)
43         ATTRIB(XonoticServerList, sortButton2, entity, NULL)
44         ATTRIB(XonoticServerList, sortButton3, entity, NULL)
45         ATTRIB(XonoticServerList, sortButton4, entity, NULL)
46         ATTRIB(XonoticServerList, sortButton5, entity, NULL)
47         ATTRIB(XonoticServerList, connectButton, entity, NULL)
48         ATTRIB(XonoticServerList, infoButton, entity, NULL)
49         ATTRIB(XonoticServerList, currentSortOrder, float, 0)
50         ATTRIB(XonoticServerList, currentSortField, float, -1)
51         ATTRIB(XonoticServerList, lastBumpSelectTime, float, 0)
52         ATTRIB(XonoticServerList, lastClickedServer, float, -1)
53         ATTRIB(XonoticServerList, lastClickedTime, float, 0)
54
55         ATTRIB(XonoticServerList, ipAddressBoxFocused, float, -1)
56
57         ATTRIB(XonoticServerList, seenIPv4, float, 0)
58         ATTRIB(XonoticServerList, seenIPv6, float, 0)
59 ENDCLASS(XonoticServerList)
60 entity makeXonoticServerList();
61
62 #ifndef IMPLEMENTATION
63 var float autocvar_menu_slist_categories = TRUE;
64 var float autocvar_menu_slist_categories_onlyifmultiple = TRUE; 
65 var float autocvar_menu_slist_purethreshold = 10;
66 var string autocvar_menu_slist_recommended = "76.124.107.5:26004";
67
68 // server cache fields
69 #define SLIST_FIELDS \
70         SLIST_FIELD(CNAME,       "cname") \
71         SLIST_FIELD(PING,        "ping") \
72         SLIST_FIELD(GAME,        "game") \
73         SLIST_FIELD(MOD,         "mod") \
74         SLIST_FIELD(MAP,         "map") \
75         SLIST_FIELD(NAME,        "name") \
76         SLIST_FIELD(MAXPLAYERS,  "maxplayers") \
77         SLIST_FIELD(NUMPLAYERS,  "numplayers") \
78         SLIST_FIELD(NUMHUMANS,   "numhumans") \
79         SLIST_FIELD(NUMBOTS,     "numbots") \
80         SLIST_FIELD(PROTOCOL,    "protocol") \
81         SLIST_FIELD(FREESLOTS,   "freeslots") \
82         SLIST_FIELD(PLAYERS,     "players") \
83         SLIST_FIELD(QCSTATUS,    "qcstatus") \
84         SLIST_FIELD(CATEGORY,    "category") \
85         SLIST_FIELD(ISFAVORITE,  "isfavorite")
86
87 #define SLIST_FIELD(suffix,name) float SLIST_FIELD_##suffix;
88 SLIST_FIELDS
89 #undef SLIST_FIELD
90
91 // sort flags
92 float SLSF_DESCENDING = 1;
93 float SLSF_FAVORITES = 2;
94 float SLSF_CATEGORIES = 4;
95
96 float Get_Cat_Num_FromString(string input);
97 entity Get_Cat_Ent(float catnum);
98
99 float IsServerInList(string list, string srv);
100 #define IsFavorite(srv) IsServerInList(cvar_string("net_slist_favorites"), srv)
101 #define IsRecommended(srv) IsServerInList(cvar_string("menu_slist_recommended"), srv) // todo: use update notification instead of cvar
102
103 float CheckCategoryOverride(float cat);
104 float CheckCategoryForEntry(float entry); 
105 float m_getserverlistentrycategory(float entry) { return CheckCategoryOverride(CheckCategoryForEntry(entry)); }
106
107 // fields for category entities
108 #define MAX_CATEGORIES 9
109 #define CATEGORY_FIRST 1
110 entity categories[MAX_CATEGORIES];
111 float category_ent_count;
112 .string cat_name;
113 .string cat_string;
114 .string cat_enoverride_string;
115 .string cat_dioverride_string;
116 .float cat_enoverride;
117 .float cat_dioverride;
118
119 // fields for drawing categories
120 float category_name[MAX_CATEGORIES];
121 float category_item[MAX_CATEGORIES];
122 float category_draw_count;
123
124 #define CATEGORIES \
125         SLIST_CATEGORY(CAT_FAVORITED,    "",            "",             _("Favorites")) \
126         SLIST_CATEGORY(CAT_RECOMMENDED,  "",            "CAT_SERVERS",  _("Recommended")) \
127         SLIST_CATEGORY(CAT_NORMAL,       "",            "CAT_SERVERS",  _("Normal Servers")) \
128         SLIST_CATEGORY(CAT_SERVERS,      "CAT_NORMAL",  "CAT_SERVERS",  _("Servers")) \
129         SLIST_CATEGORY(CAT_XPM,          "CAT_NORMAL",  "CAT_SERVERS",  _("Competitive Mode")) \
130         SLIST_CATEGORY(CAT_MODIFIED,     "",            "CAT_SERVERS",  _("Modified Servers")) \
131         SLIST_CATEGORY(CAT_OVERKILL,     "",            "CAT_SERVERS",  _("Overkill Mode")) \
132         SLIST_CATEGORY(CAT_MINSTAGIB,    "",            "CAT_SERVERS",  _("MinstaGib Mode")) \
133         SLIST_CATEGORY(CAT_DEFRAG,       "",            "CAT_SERVERS",  _("Defrag Mode"))
134
135 // C is stupid, must use extra macro for concatenation
136 #define SLIST_ADD_CAT_CVAR(name,default) var string autocvar_menu_slist_categories_##name##_override = default;
137 #define SLIST_CATEGORY(name,enoverride,dioverride,string) \
138         SLIST_ADD_CAT_CVAR(name, enoverride) \
139         float name; \
140         void RegisterSLCategory_##name() \
141         { \
142                 SET_FIELD_COUNT(name, CATEGORY_FIRST, category_ent_count) \
143                 CHECK_MAX_COUNT(name, MAX_CATEGORIES, category_ent_count, "SLIST_CATEGORY") \
144                 entity cat = spawn(); \
145                 categories[name - 1] = cat; \
146                 cat.classname = "slist_category"; \
147                 cat.cat_name = strzone(#name); \
148                 cat.cat_enoverride_string = strzone(autocvar_menu_slist_categories_##name##_override); \
149                 cat.cat_dioverride_string = strzone(dioverride); \
150                 cat.cat_string = strzone(string); \
151         } \
152         ACCUMULATE_FUNCTION(RegisterSLCategories, RegisterSLCategory_##name);
153
154 CATEGORIES
155
156 void RegisterSLCategories_Done()
157 {
158         float i, catnum;
159         string s;
160
161         #define PROCESS_OVERRIDE(override_string,override_field) \
162                 for(i = 0; i < category_ent_count; ++i) \
163                 { \
164                         s = categories[i].override_string; \
165                         if((s != "") && (s != categories[i].cat_name)) \
166                         { \
167                                 catnum = Get_Cat_Num_FromString(s); \
168                                 if(catnum) \
169                                 { \
170                                         strunzone(categories[i].override_string); \
171                                         categories[i].override_field = catnum; \
172                                         continue; \
173                                 } \
174                         } \
175                         strunzone(categories[i].override_string); \
176                         categories[i].override_field = 0; \
177                 }
178
179         PROCESS_OVERRIDE(cat_enoverride_string, cat_enoverride)
180         PROCESS_OVERRIDE(cat_dioverride_string, cat_dioverride)
181         #undef PROCESS_OVERRIDE
182 }
183 ACCUMULATE_FUNCTION(RegisterSLCategories, RegisterSLCategories_Done);
184
185 #undef SLIST_ADD_CAT_CVAR
186 #undef SLIST_CATEGORY
187 #undef CATEGORIES
188
189 void ServerList_Connect_Click(entity btn, entity me);
190 void ServerList_Categories_Click(entity box, entity me);
191 void ServerList_ShowEmpty_Click(entity box, entity me);
192 void ServerList_ShowFull_Click(entity box, entity me);
193 void ServerList_Filter_Change(entity box, entity me);
194 void ServerList_Favorite_Click(entity btn, entity me);
195 void ServerList_Info_Click(entity btn, entity me);
196 void ServerList_Update_favoriteButton(entity btn, entity me);
197
198 #endif
199 #endif
200 #ifdef IMPLEMENTATION
201
202 // Supporting Functions
203 float Get_Cat_Num_FromString(string input)
204 {
205         float i;
206         for(i = 0; i < category_ent_count; ++i) { if(categories[i].cat_name == input) { return (i + 1); } }
207         print(sprintf("Get_Cat_Num_FromString('%s'): Improper category name!\n", input));
208         return 0;
209 }
210 entity Get_Cat_Ent(float catnum)
211 {
212         if((catnum > 0) && (catnum <= category_ent_count))
213         {
214                 return categories[catnum - 1];
215         }
216         else
217         {
218                 error(sprintf("Get_Cat_Ent(%d): Improper category number!\n", catnum));
219                 return world;
220         }
221 }
222
223
224 float IsServerInList(string list, string srv)
225 {
226         string p;
227         float i, n;
228         if(srv == "")
229                 return FALSE;
230         srv = netaddress_resolve(srv, 26000);
231         if(srv == "")
232                 return FALSE;
233         p = crypto_getidfp(srv);
234         n = tokenize_console(list);
235         for(i = 0; i < n; ++i)
236         {
237                 if(substring(argv(i), 0, 1) != "[" && strlen(argv(i)) == 44 && strstrofs(argv(i), ".", 0) < 0)
238                 {
239                         if(p)
240                                 if(argv(i) == p)
241                                         return TRUE;
242                 }
243                 else
244                 {
245                         if(srv == netaddress_resolve(argv(i), 26000))
246                                 return TRUE;
247                 }
248         }
249         return FALSE;
250 }
251
252 float CheckCategoryOverride(float cat)
253 {
254         entity catent = Get_Cat_Ent(cat);
255         if(catent)
256         {
257                 float override = (autocvar_menu_slist_categories ? catent.cat_enoverride : catent.cat_dioverride); 
258                 if(override) { return override; }
259                 else { return cat; }
260         }
261         else
262         {
263                 error(sprintf("CheckCategoryOverride(%d): Improper category number!\n", cat));
264                 return cat;
265         }
266 }
267
268 float CheckCategoryForEntry(float entry)
269 {
270         string s, k, v, modtype = "";
271         float j, m, impure = 0;
272         s = gethostcachestring(SLIST_FIELD_QCSTATUS, entry);
273         m = tokenizebyseparator(s, ":");
274
275         for(j = 2; j < m; ++j)
276         {
277                 if(argv(j) == "")
278                         break;
279                 k = substring(argv(j), 0, 1);
280                 v = substring(argv(j), 1, -1);
281                 if(k == "P") { impure = stof(v); }
282                 else if(k == "M") { modtype = strtolower(v); }
283         }
284
285         //print(sprintf("modtype = %s\n", modtype)); 
286
287         if(impure > autocvar_menu_slist_purethreshold) { impure = TRUE; }
288         else { impure = FALSE; }
289
290         if(gethostcachenumber(SLIST_FIELD_ISFAVORITE, entry)) { return CAT_FAVORITED; }
291         if(IsRecommended(gethostcachestring(SLIST_FIELD_CNAME, entry))) { return CAT_RECOMMENDED; }
292         else if(modtype != "xonotic")
293         {
294                 switch(modtype)
295                 {
296                         // old servers which don't report their mod name are considered modified now
297                         case "": { return CAT_MODIFIED; }
298                         
299                         case "xpm": { return CAT_XPM; } 
300                         case "minstagib": { return CAT_MINSTAGIB; }
301                         case "overkill": { return CAT_OVERKILL; }
302
303                         // "cts" is allowed as compat, xdf is replacement
304                         case "cts": 
305                         case "xdf": { return CAT_DEFRAG; }
306                         
307                         //if(modname != "CTS")
308                         //if(modname != "NIX")
309                         //if(modname != "NewToys")
310                         
311                         default: { dprint(sprintf("Found strange mod type: %s\n", modtype)); return CAT_MODIFIED; }
312                 }
313         }
314         else { return (impure ? CAT_MODIFIED : CAT_NORMAL); }
315
316         // should never hit this point
317         error(sprintf("CheckCategoryForEntry(%d): Function fell through without normal return!\n", entry));
318         return FALSE;
319 }
320
321 float XonoticServerList_MapItems(float num)
322 {
323         float i, n;
324
325         if not(category_draw_count) { return num; } // there are no categories to process
326
327         for(i = 0, n = 1; n <= category_draw_count; ++i, ++n)
328         {
329                 //print(sprintf("num: %d, i: %d, category_draw_count: %d, category_item[i]: %d\n", num, i, category_draw_count, category_item[i])); 
330                 if(category_item[i] == (num - i)) { /*print("inserting cat... \\/\n");*/ return -category_name[i]; }
331                 else if(n == category_draw_count) { /*print("end item... \\/\n");*/ return (num - n); }
332                 else if((num - i) <= category_item[n]) { /*print("next item... \\/\n");*/ return (num - n); }
333         }
334
335         // should never hit this point
336         error("wtf XonoticServerList_MapItems fail?");
337         return FALSE;
338 }
339
340 void ToggleFavorite(string srv)
341 {
342         string s, s0, s1, s2, srv_resolved, p;
343         float i, n, f;
344         srv_resolved = netaddress_resolve(srv, 26000);
345         p = crypto_getidfp(srv_resolved);
346         s = cvar_string("net_slist_favorites");
347         n = tokenize_console(s);
348         f = 0;
349         for(i = 0; i < n; ++i)
350         {
351                 if(substring(argv(i), 0, 1) != "[" && strlen(argv(i)) == 44 && strstrofs(argv(i), ".", 0) < 0)
352                 {
353                         if(p)
354                                 if(argv(i) != p)
355                                         continue;
356                 }
357                 else
358                 {
359                         if(srv_resolved != netaddress_resolve(argv(i), 26000))
360                                 continue;
361                 }
362                 s0 = s1 = s2 = "";
363                 if(i > 0)
364                         s0 = substring(s, 0, argv_end_index(i - 1));
365                 if(i < n-1)
366                         s2 = substring(s, argv_start_index(i + 1), -1);
367                 if(s0 != "" && s2 != "")
368                         s1 = " ";
369                 cvar_set("net_slist_favorites", strcat(s0, s1, s2));
370                 s = cvar_string("net_slist_favorites");
371                 n = tokenize_console(s);
372                 f = 1;
373                 --i;
374         }
375         
376         if(!f)
377         {
378                 s1 = "";
379                 if(s != "")
380                         s1 = " ";
381                 if(p)
382                         cvar_set("net_slist_favorites", strcat(s, s1, p));
383                 else
384                         cvar_set("net_slist_favorites", strcat(s, s1, srv));
385         }
386
387         resorthostcache();
388 }
389
390 void ServerList_Update_favoriteButton(entity btn, entity me)
391 {
392         me.favoriteButton.setText(me.favoriteButton,
393                 (IsFavorite(me.ipAddressBox.text) ?
394                         _("Remove") : _("Bookmark")
395                 )
396         );
397 }
398
399 entity makeXonoticServerList()
400 {
401         entity me;
402         me = spawnXonoticServerList();
403         me.configureXonoticServerList(me);
404         return me;
405 }
406 void XonoticServerList_configureXonoticServerList(entity me)
407 {
408         me.configureXonoticListBox(me);
409
410         // update field ID's
411         #define SLIST_FIELD(suffix,name) SLIST_FIELD_##suffix = gethostcacheindexforkey(name);
412         SLIST_FIELDS
413         #undef SLIST_FIELD
414
415         // clear list
416         me.nItems = 0;
417 }
418 void XonoticServerList_setSelected(entity me, float i)
419 {
420         // todo: add logic to skip categories
421         float save, num;
422         save = me.selectedItem;
423         SUPER(XonoticServerList).setSelected(me, i);
424         /*
425         if(me.selectedItem == save)
426                 return;
427         */
428         if(me.nItems == 0)
429                 return;
430
431         //if(gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT) != XonoticServerList_MapItems(me.nItems))
432         //      { error("^1XonoticServerList_setSelected(); ERROR: ^7Host cache viewcount mismatches nItems!\n"); return; } // sorry, it would be wrong
433         // ^ todo: make this work somehow?
434
435         #define SET_SELECTED_SERVER(cachenum) \
436                 if(me.selectedServer) { strunzone(me.selectedServer); } \
437                 me.selectedServer = strzone(gethostcachestring(SLIST_FIELD_CNAME, cachenum)); \
438                 me.ipAddressBox.setText(me.ipAddressBox, me.selectedServer); \
439                 me.ipAddressBox.cursorPos = strlen(me.selectedServer); \
440                 me.ipAddressBoxFocused = -1; \
441                 return;
442
443         num = XonoticServerList_MapItems(me.selectedItem);
444
445         if(num >= 0) { SET_SELECTED_SERVER(num); }
446         else if(save > me.selectedItem)
447         {
448                 if(me.selectedItem == 0) { return; }
449                 else
450                 {
451                         if(me.lastClickedTime >= me.lastBumpSelectTime)
452                         {
453                                 SUPER(XonoticServerList).setSelected(me, me.selectedItem - 1);
454                                 num = XonoticServerList_MapItems(me.selectedItem);
455                                 if(num >= 0)
456                                 {
457                                         me.lastBumpSelectTime = time;
458                                         SET_SELECTED_SERVER(num);
459                                 }
460                         }
461                 }
462         }
463         else if(save < me.selectedItem)
464         {
465                 if(me.selectedItem == me.nItems) { return; }
466                 else
467                 {
468                         if(me.lastClickedTime >= me.lastBumpSelectTime)
469                         {
470                                 SUPER(XonoticServerList).setSelected(me, me.selectedItem + 1);
471                                 num = XonoticServerList_MapItems(me.selectedItem);
472                                 if(num >= 0)
473                                 {
474                                         me.lastBumpSelectTime = time;
475                                         SET_SELECTED_SERVER(num);
476                                 }
477                         }
478                 }
479         }
480 }
481 void XonoticServerList_refreshServerList(entity me, float mode)
482 {
483         // 0: just reparametrize
484         // 1: also ask for new servers
485         // 2: clear
486         //print("refresh of type ", ftos(mode), "\n");
487         /* if(mode == 2) // borken
488         {
489                 // clear list
490                 localcmd("net_slist\n");
491                 me.needsRefresh = 1; // net_slist kills sort order, so we need to restore it later
492         }
493         else */
494         
495         float m, i, n;
496         float listflags = 0;
497         string s, typestr, modstr;
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         resorthostcache();
565         if(mode >= 1) { refreshhostcache(FALSE); }
566 }
567 void XonoticServerList_focusEnter(entity me)
568 {
569         if(time < me.nextRefreshTime)
570         {
571                 //print("sorry, no refresh yet\n");
572                 return;
573         }
574         me.nextRefreshTime = time + 10;
575         me.refreshServerList(me, 1);
576 }
577
578 void XonoticServerList_draw(entity me)
579 {
580         float i, found, owned, num;
581
582         if(_Nex_ExtResponseSystem_BannedServersNeedsRefresh)
583         {
584                 if(!me.needsRefresh)
585                         me.needsRefresh = 2;
586                 _Nex_ExtResponseSystem_BannedServersNeedsRefresh = 0;
587         }
588
589         if(me.currentSortField == -1)
590         {
591                 me.setSortOrder(me, SLIST_FIELD_PING, +1);
592                 me.refreshServerList(me, 2);
593         }
594         else if(me.needsRefresh == 1)
595         {
596                 me.needsRefresh = 2; // delay by one frame to make sure "slist" has been executed
597         }
598         else if(me.needsRefresh == 2)
599         {
600                 me.needsRefresh = 0;
601                 me.refreshServerList(me, 0);
602         }
603
604         owned = ((me.selectedServer == me.ipAddressBox.text) && (me.ipAddressBox.text != ""));
605
606         for(i = 0; i < category_draw_count; ++i) { category_name[i] = -1; category_item[i] = -1; }
607         category_draw_count = 0;
608
609         if(autocvar_menu_slist_categories >= 0) // if less than 0, don't even draw a category heading for favorites
610         {
611                 float itemcount = gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT);
612                 me.nItems = itemcount;
613                 
614                 //float visible = floor(me.scrollPos / me.itemHeight);
615                 // ^ unfortunately no such optimization can be made-- we must process through the
616                 // entire list, otherwise there is no way to know which item is first in its category.
617
618                 float cat, x;
619                 for(i = 0; i < itemcount; ++i)
620                 {
621                         cat = gethostcachenumber(SLIST_FIELD_CATEGORY, i);
622                         if(cat)
623                         {
624                                 if(category_draw_count == 0)
625                                 {
626                                         category_name[category_draw_count] = cat;
627                                         category_item[category_draw_count] = i;
628                                         ++category_draw_count;
629                                         ++me.nItems;
630                                 }
631                                 else
632                                 {
633                                         found = 0;
634                                         for(x = 0; x < category_draw_count; ++x) { if(cat == category_name[x]) { found = 1; } }
635                                         if not(found)
636                                         {
637                                                 category_name[category_draw_count] = cat;
638                                                 category_item[category_draw_count] = i;
639                                                 ++category_draw_count;
640                                                 ++me.nItems;
641                                         }
642                                 }
643                         }
644                 }
645                 if(autocvar_menu_slist_categories_onlyifmultiple && (category_draw_count == 1))
646                 {
647                         category_name[0] = category_name[1] = -1;
648                         category_item[0] = category_item[1] = -1;
649                         category_draw_count = 0;
650                         me.nItems = itemcount;
651                 }
652         }
653         else { me.nItems = gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT); }
654
655         me.connectButton.disabled = ((me.nItems == 0) && (me.ipAddressBox.text == ""));
656         me.infoButton.disabled = ((me.nItems == 0) || !owned);
657         me.favoriteButton.disabled = ((me.nItems == 0) && (me.ipAddressBox.text == ""));
658
659         found = 0;
660         if(me.selectedServer)
661         {
662                 for(i = 0; i < me.nItems; ++i)
663                 {
664                         num = XonoticServerList_MapItems(i);
665                         if(num >= 0)
666                         {
667                                 if(gethostcachestring(SLIST_FIELD_CNAME, num) == me.selectedServer)
668                                 {
669                                         if(i != me.selectedItem)
670                                         {
671                                                 me.lastClickedServer = -1;
672                                                 me.selectedItem = i;
673                                         }
674                                         found = 1;
675                                         break;
676                                 }
677                         }
678                 }
679         }
680         if(!found)
681         {
682                 if(me.nItems > 0)
683                 {
684                         if(me.selectedItem >= me.nItems) { me.selectedItem = me.nItems - 1; }
685                         if(me.selectedServer) { strunzone(me.selectedServer); }
686
687                         num = XonoticServerList_MapItems(me.selectedItem);
688                         if(num >= 0) { me.selectedServer = strzone(gethostcachestring(SLIST_FIELD_CNAME, num)); }
689                 }
690         }
691         
692         if(owned)
693         {
694                 if(me.selectedServer != me.ipAddressBox.text)
695                 {
696                         me.ipAddressBox.setText(me.ipAddressBox, me.selectedServer);
697                         me.ipAddressBox.cursorPos = strlen(me.selectedServer);
698                         me.ipAddressBoxFocused = -1;
699                 }
700         }
701
702         if(me.ipAddressBoxFocused != me.ipAddressBox.focused)
703         {
704                 if(me.ipAddressBox.focused || me.ipAddressBoxFocused < 0)
705                         ServerList_Update_favoriteButton(NULL, me);
706                 me.ipAddressBoxFocused = me.ipAddressBox.focused;
707         }
708
709         SUPER(XonoticServerList).draw(me);
710 }
711 void ServerList_PingSort_Click(entity btn, entity me)
712 {
713         me.setSortOrder(me, SLIST_FIELD_PING, +1);
714 }
715 void ServerList_NameSort_Click(entity btn, entity me)
716 {
717         me.setSortOrder(me, SLIST_FIELD_NAME, -1); // why?
718 }
719 void ServerList_MapSort_Click(entity btn, entity me)
720 {
721         me.setSortOrder(me, SLIST_FIELD_MAP, -1); // why?
722 }
723 void ServerList_PlayerSort_Click(entity btn, entity me)
724 {
725         me.setSortOrder(me, SLIST_FIELD_NUMHUMANS, -1);
726 }
727 void ServerList_TypeSort_Click(entity btn, entity me)
728 {
729         string s, t;
730         float i, m;
731         s = me.filterString;
732         m = strstrofs(s, ":", 0);
733         if(m >= 0)
734         {
735                 s = substring(s, 0, m);
736                 while(substring(s, m+1, 1) == " ") // skip spaces
737                         ++m;
738         }
739         else
740                 s = "";
741
742         for(i = 1; ; i *= 2) // 20 modes ought to be enough for anyone
743         {
744                 t = MapInfo_Type_ToString(i);
745                 if(i > 1)
746                         if(t == "") // it repeats (default case)
747                         {
748                                 // no type was found
749                                 // choose the first one
750                                 s = MapInfo_Type_ToString(1);
751                                 break;
752                         }
753                 if(s == t)
754                 {
755                         // the type was found
756                         // choose the next one
757                         s = MapInfo_Type_ToString(i * 2);
758                         if(s == "")
759                                 s = MapInfo_Type_ToString(1);
760                         break;
761                 }
762         }
763
764         if(s != "")
765                 s = strcat(s, ":");
766         s = strcat(s, substring(me.filterString, m+1, strlen(me.filterString) - m - 1));
767
768         me.controlledTextbox.setText(me.controlledTextbox, s);
769         me.controlledTextbox.keyDown(me.controlledTextbox, K_END, 0, 0);
770         me.controlledTextbox.keyUp(me.controlledTextbox, K_END, 0, 0);
771         //ServerList_Filter_Change(me.controlledTextbox, me);
772 }
773 void ServerList_Filter_Change(entity box, entity me)
774 {
775         if(me.filterString)
776                 strunzone(me.filterString);
777         if(box.text != "")
778                 me.filterString = strzone(box.text);
779         else
780                 me.filterString = string_null;
781         me.refreshServerList(me, 0);
782
783         me.ipAddressBox.setText(me.ipAddressBox, "");
784         me.ipAddressBox.cursorPos = 0;
785         me.ipAddressBoxFocused = -1;
786 }
787 void ServerList_Categories_Click(entity box, entity me)
788 {
789         box.setChecked(box, autocvar_menu_slist_categories = !autocvar_menu_slist_categories);
790         ///refreshhostcache(TRUE);
791
792         //cvar_set("net_slist_pause", "0");
793         //Destroy_Category_Entities();
794         //CALL_ACCUMULATED_FUNCTION(RegisterSLCategories);
795         //me.refreshServerList(me, 0);
796
797         resorthostcache();
798
799         me.ipAddressBox.setText(me.ipAddressBox, "");
800         me.ipAddressBox.cursorPos = 0;
801         me.ipAddressBoxFocused = -1;
802 }
803 void ServerList_ShowEmpty_Click(entity box, entity me)
804 {
805         box.setChecked(box, me.filterShowEmpty = !me.filterShowEmpty);
806         me.refreshServerList(me, 0);
807
808         me.ipAddressBox.setText(me.ipAddressBox, "");
809         me.ipAddressBox.cursorPos = 0;
810         me.ipAddressBoxFocused = -1;
811 }
812 void ServerList_ShowFull_Click(entity box, entity me)
813 {
814         box.setChecked(box, me.filterShowFull = !me.filterShowFull);
815         me.refreshServerList(me, 0);
816
817         me.ipAddressBox.setText(me.ipAddressBox, "");
818         me.ipAddressBox.cursorPos = 0;
819         me.ipAddressBoxFocused = -1;
820 }
821 void XonoticServerList_setSortOrder(entity me, float fld, float direction)
822 {
823         if(me.currentSortField == fld)
824                 direction = -me.currentSortOrder;
825         me.currentSortOrder = direction;
826         me.currentSortField = fld;
827         me.sortButton1.forcePressed = (fld == SLIST_FIELD_PING);
828         me.sortButton2.forcePressed = (fld == SLIST_FIELD_NAME);
829         me.sortButton3.forcePressed = (fld == SLIST_FIELD_MAP);
830         me.sortButton4.forcePressed = 0;
831         me.sortButton5.forcePressed = (fld == SLIST_FIELD_NUMHUMANS);
832         me.selectedItem = 0;
833         if(me.selectedServer)
834                 strunzone(me.selectedServer);
835         me.selectedServer = string_null;
836         me.refreshServerList(me, 0);
837 }
838 void XonoticServerList_positionSortButton(entity me, entity btn, float theOrigin, float theSize, string theTitle, void(entity, entity) theFunc)
839 {
840         vector originInLBSpace, sizeInLBSpace;
841         originInLBSpace = eY * (-me.itemHeight);
842         sizeInLBSpace = eY * me.itemHeight + eX * (1 - me.controlWidth);
843
844         vector originInDialogSpace, sizeInDialogSpace;
845         originInDialogSpace = boxToGlobal(originInLBSpace, me.Container_origin, me.Container_size);
846         sizeInDialogSpace = boxToGlobalSize(sizeInLBSpace, me.Container_size);
847
848         btn.Container_origin_x = originInDialogSpace_x + sizeInDialogSpace_x * theOrigin;
849         btn.Container_size_x   =                         sizeInDialogSpace_x * theSize;
850         btn.setText(btn, theTitle);
851         btn.onClick = theFunc;
852         btn.onClickEntity = me;
853         btn.resized = 1;
854 }
855 void XonoticServerList_resizeNotify(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
856 {
857         SUPER(XonoticServerList).resizeNotify(me, relOrigin, relSize, absOrigin, absSize);
858
859         me.realFontSize_y = me.fontSize / (absSize_y * me.itemHeight);
860         me.realFontSize_x = me.fontSize / (absSize_x * (1 - me.controlWidth));
861         me.realUpperMargin = 0.5 * (1 - me.realFontSize_y);
862
863         me.columnIconsOrigin = 0;
864         me.columnIconsSize = me.realFontSize_x * 4 * me.iconsSizeFactor;
865         me.columnPingSize = me.realFontSize_x * 3;
866         me.columnMapSize = me.realFontSize_x * 10;
867         me.columnTypeSize = me.realFontSize_x * 4;
868         me.columnPlayersSize = me.realFontSize_x * 5;
869         me.columnNameSize = 1 - me.columnPlayersSize - me.columnMapSize - me.columnPingSize - me.columnIconsSize - me.columnTypeSize - 5 * me.realFontSize_x;
870         me.columnPingOrigin = me.columnIconsOrigin + me.columnIconsSize + me.realFontSize_x;
871         me.columnNameOrigin = me.columnPingOrigin + me.columnPingSize + me.realFontSize_x;
872         me.columnMapOrigin = me.columnNameOrigin + me.columnNameSize + me.realFontSize_x;
873         me.columnTypeOrigin = me.columnMapOrigin + me.columnMapSize + me.realFontSize_x;
874         me.columnPlayersOrigin = me.columnTypeOrigin + me.columnTypeSize + me.realFontSize_x;
875
876         me.positionSortButton(me, me.sortButton1, me.columnPingOrigin, me.columnPingSize, _("Ping"), ServerList_PingSort_Click);
877         me.positionSortButton(me, me.sortButton2, me.columnNameOrigin, me.columnNameSize, _("Host name"), ServerList_NameSort_Click);
878         me.positionSortButton(me, me.sortButton3, me.columnMapOrigin, me.columnMapSize, _("Map"), ServerList_MapSort_Click);
879         me.positionSortButton(me, me.sortButton4, me.columnTypeOrigin, me.columnTypeSize, _("Type"), ServerList_TypeSort_Click);
880         me.positionSortButton(me, me.sortButton5, me.columnPlayersOrigin, me.columnPlayersSize, _("Players"), ServerList_PlayerSort_Click);
881
882         float f;
883         f = me.currentSortField;
884         if(f >= 0)
885         {
886                 me.currentSortField = -1;
887                 me.setSortOrder(me, f, me.currentSortOrder); // force resetting the sort order
888         }
889 }
890 void ServerList_Connect_Click(entity btn, entity me)
891 {
892         localcmd(sprintf("connect %s\n",
893                 ((me.ipAddressBox.text != "") ?
894                         me.ipAddressBox.text : me.selectedServer
895                 )
896         ));
897 }
898 void ServerList_Favorite_Click(entity btn, entity me)
899 {
900         string ipstr;
901         ipstr = netaddress_resolve(me.ipAddressBox.text, 26000);
902         if(ipstr != "")
903         {
904                 ToggleFavorite(me.ipAddressBox.text);
905                 me.ipAddressBoxFocused = -1;
906         }
907 }
908 void ServerList_Info_Click(entity btn, entity me)
909 {
910         main.serverInfoDialog.loadServerInfo(main.serverInfoDialog, XonoticServerList_MapItems(me.selectedItem));
911         DialogOpenButton_Click(me, main.serverInfoDialog);
912 }
913 void XonoticServerList_clickListBoxItem(entity me, float i, vector where)
914 {
915         me.lastBumpSelectTime = 0; // must reset this for new clicks
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         if(scan != K_MOUSE1 && scan != K_MOUSE2) { 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                         ToggleFavorite(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