]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/menu/xonotic/serverlist.c
Clean up/prepare header draw code
[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, lastClickedServer, float, -1)
52         ATTRIB(XonoticServerList, lastClickedTime, float, 0)
53
54         ATTRIB(XonoticServerList, ipAddressBoxFocused, float, -1)
55
56         ATTRIB(XonoticServerList, seenIPv4, float, 0)
57         ATTRIB(XonoticServerList, seenIPv6, float, 0)
58 ENDCLASS(XonoticServerList)
59 entity makeXonoticServerList();
60
61 #ifndef IMPLEMENTATION
62 var float autocvar_menu_serverlist_categories = TRUE;
63 var float autocvar_menu_serverlist_categories_onlyifmultiple = TRUE; 
64 var float autocvar_menu_serverlist_purethreshold = 10;
65 var string autocvar_menu_serverlist_recommended = "76.124.107.5:26004";
66
67 // server cache fields
68 #define SLIST_FIELDS \
69         SLIST_FIELD(CNAME,       "cname") \
70         SLIST_FIELD(PING,        "ping") \
71         SLIST_FIELD(GAME,        "game") \
72         SLIST_FIELD(MOD,         "mod") \
73         SLIST_FIELD(MAP,         "map") \
74         SLIST_FIELD(NAME,        "name") \
75         SLIST_FIELD(MAXPLAYERS,  "maxplayers") \
76         SLIST_FIELD(NUMPLAYERS,  "numplayers") \
77         SLIST_FIELD(NUMHUMANS,   "numhumans") \
78         SLIST_FIELD(NUMBOTS,     "numbots") \
79         SLIST_FIELD(PROTOCOL,    "protocol") \
80         SLIST_FIELD(FREESLOTS,   "freeslots") \
81         SLIST_FIELD(PLAYERS,     "players") \
82         SLIST_FIELD(QCSTATUS,    "qcstatus") \
83         SLIST_FIELD(CATEGORY,    "category") \
84         SLIST_FIELD(ISFAVORITE,  "isfavorite")
85
86 #define SLIST_FIELD(suffix,name) float SLIST_FIELD_##suffix;
87 SLIST_FIELDS
88 #undef SLIST_FIELD
89
90 // sort flags
91 float SLSF_DESCENDING = 1;
92 float SLSF_FAVORITES = 2;
93 float SLSF_CATEGORIES = 4;
94
95 float Get_Cat_Num_FromString(string input);
96 entity Get_Cat_Ent(float catnum);
97
98 float IsServerInList(string list, string srv);
99 #define IsFavorite(srv) IsServerInList(cvar_string("net_slist_favorites"), srv)
100 #define IsRecommended(srv) IsServerInList(cvar_string("menu_serverlist_recommended"), srv) // todo: use update notification instead of cvar
101
102 float CheckCategoryOverride(float cat);
103 float CheckCategoryForEntry(float entry); 
104 float m_getserverlistentrycategory(float entry) { return CheckCategoryOverride(CheckCategoryForEntry(entry)); }
105
106 // fields for category entities
107 #define MAX_CATEGORIES 9
108 #define CATEGORY_FIRST 1
109 entity categories[MAX_CATEGORIES];
110 float category_ent_count;
111 .string cat_name;
112 .string cat_string;
113 .string cat_override_string;
114 .float cat_override;
115
116 // fields for drawing categories
117 float category_name[MAX_CATEGORIES];
118 float category_item[MAX_CATEGORIES];
119 float category_draw_count;
120
121 #define CATEGORIES \
122         SLIST_CATEGORY(CAT_FAVORITED,    "",            "",             _("Favorites")) \
123         SLIST_CATEGORY(CAT_RECOMMENDED,  "",            "CAT_SERVERS",  _("Recommended")) \
124         SLIST_CATEGORY(CAT_NORMAL,       "",            "CAT_SERVERS",  _("Normal Servers")) \
125         SLIST_CATEGORY(CAT_SERVERS,      "CAT_NORMAL",  "CAT_SERVERS",  _("Servers")) \
126         SLIST_CATEGORY(CAT_XPM,          "CAT_NORMAL",  "CAT_SERVERS",  _("Competitive Mode")) \
127         SLIST_CATEGORY(CAT_MODIFIED,     "",            "CAT_SERVERS",  _("Modified Servers")) \
128         SLIST_CATEGORY(CAT_OVERKILL,     "",            "CAT_SERVERS",  _("Overkill Mode")) \
129         SLIST_CATEGORY(CAT_MINSTAGIB,    "",            "CAT_SERVERS",  _("MinstaGib Mode")) \
130         SLIST_CATEGORY(CAT_DEFRAG,       "",            "CAT_SERVERS",  _("Defrag Mode"))
131
132 // C is stupid, must use extra macro for concatenation
133 #define SLIST_ADD_CAT_CVAR(name,default) var string autocvar_menu_serverlist_categories_##name##_override = default;
134 #define SLIST_CATEGORY(name,enoverride,deoverride,string) \
135         SLIST_ADD_CAT_CVAR(name, enoverride) \
136         float name; \
137         void RegisterSLCategory_##name() \
138         { \
139                 SET_FIELD_COUNT(name, CATEGORY_FIRST, category_ent_count) \
140                 CHECK_MAX_COUNT(name, MAX_CATEGORIES, category_ent_count, "SLIST_CATEGORY") \
141                 entity cat = spawn(); \
142                 categories[name - 1] = cat; \
143                 cat.classname = "slist_category"; \
144                 cat.cat_name = strzone(#name); \
145                 cat.cat_override_string = strzone((autocvar_menu_serverlist_categories ? \
146                         autocvar_menu_serverlist_categories_##name##_override \
147                         : \
148                         deoverride)); \
149                 cat.cat_string = strzone(string); \
150         } \
151         ACCUMULATE_FUNCTION(RegisterSLCategories, RegisterSLCategory_##name);
152
153 CATEGORIES
154
155 void RegisterSLCategories_Done()
156 {
157         float i, catnum;
158         string s;
159         for(i = 0; i < category_ent_count; ++i)
160         {
161                 s = categories[i].cat_override_string;
162                 if((s != "") && (s != categories[i].cat_name))
163                 {
164                         catnum = Get_Cat_Num_FromString(s);
165                         if(catnum)
166                         {
167                                 strunzone(categories[i].cat_override_string);
168                                 categories[i].cat_override = catnum;
169                                 continue;
170                         }
171                 }
172                 strunzone(categories[i].cat_override_string);
173                 categories[i].cat_override = 0;
174         }
175 }
176 ACCUMULATE_FUNCTION(RegisterSLCategories, RegisterSLCategories_Done);
177
178 #undef SLIST_ADD_CAT_CVAR
179 #undef SLIST_CATEGORY
180 #undef CATEGORIES
181
182 void ServerList_Connect_Click(entity btn, entity me);
183 void ServerList_ShowEmpty_Click(entity box, entity me);
184 void ServerList_ShowFull_Click(entity box, entity me);
185 void ServerList_Filter_Change(entity box, entity me);
186 void ServerList_Favorite_Click(entity btn, entity me);
187 void ServerList_Info_Click(entity btn, entity me);
188 void ServerList_Update_favoriteButton(entity btn, entity me);
189
190 #endif
191 #endif
192 #ifdef IMPLEMENTATION
193
194 // Supporting Functions
195 float Get_Cat_Num_FromString(string input)
196 {
197         float i;
198         for(i = 0; i < category_ent_count; ++i) { if(categories[i].cat_name == input) { return (i + 1); } }
199         print(sprintf("Get_Cat_Num_FromString('%s'): Improper category name!\n", input));
200         return 0;
201 }
202 entity Get_Cat_Ent(float catnum)
203 {
204         if((catnum > 0) && (catnum <= category_ent_count))
205         {
206                 return categories[catnum - 1];
207         }
208         else
209         {
210                 error(sprintf("Get_Cat_Ent(%d): Improper category number!\n", catnum));
211                 return world;
212         }
213 }
214
215
216 float IsServerInList(string list, string srv)
217 {
218         string p;
219         float i, n;
220         if(srv == "")
221                 return FALSE;
222         srv = netaddress_resolve(srv, 26000);
223         if(srv == "")
224                 return FALSE;
225         p = crypto_getidfp(srv);
226         n = tokenize_console(list);
227         for(i = 0; i < n; ++i)
228         {
229                 if(substring(argv(i), 0, 1) != "[" && strlen(argv(i)) == 44 && strstrofs(argv(i), ".", 0) < 0)
230                 {
231                         if(p)
232                                 if(argv(i) == p)
233                                         return TRUE;
234                 }
235                 else
236                 {
237                         if(srv == netaddress_resolve(argv(i), 26000))
238                                 return TRUE;
239                 }
240         }
241         return FALSE;
242 }
243
244 float CheckCategoryOverride(float cat)
245 {
246         entity catent = Get_Cat_Ent(cat);
247         if(catent)
248         {
249                 if(catent.cat_override) { return catent.cat_override; }
250                 else { return cat; }
251         }
252         else
253         {
254                 error(sprintf("CheckCategoryOverride(%d): Improper category number!\n", cat));
255                 return cat;
256         }
257 }
258
259 float CheckCategoryForEntry(float entry)
260 {
261         string s, k, v, modtype = "";
262         float j, m, impure;
263         s = gethostcachestring(SLIST_FIELD_QCSTATUS, entry);
264         m = tokenizebyseparator(s, ":");
265         //typestr = "";
266         //if(m >= 2)
267         //{
268         //      typestr = argv(0);
269         //      versionstr = argv(1);
270         //}
271         //freeslots = -1;
272         //sflags = -1;
273         //modname = "";
274         impure = 0;
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_serverlist_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(XonoticServerList_MapItems(gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT)) != 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         num = XonoticServerList_MapItems(me.selectedItem);
436         if(num >= 0)
437         {
438                 if(me.selectedServer)
439                         strunzone(me.selectedServer);
440                 me.selectedServer = strzone(gethostcachestring(SLIST_FIELD_CNAME, num));
441
442                 me.ipAddressBox.setText(me.ipAddressBox, me.selectedServer);
443                 me.ipAddressBox.cursorPos = strlen(me.selectedServer);
444                 me.ipAddressBoxFocused = -1;
445         }
446 }
447 void XonoticServerList_refreshServerList(entity me, float mode)
448 {
449         // 0: just reparametrize
450         // 1: also ask for new servers
451         // 2: clear
452         //print("refresh of type ", ftos(mode), "\n");
453         /* if(mode == 2) // borken
454         {
455                 // clear list
456                 localcmd("net_slist\n");
457                 me.needsRefresh = 1; // net_slist kills sort order, so we need to restore it later
458         }
459         else */
460         
461         float m, i, n;
462         float listflags = 0;
463         string s, typestr, modstr;
464         s = me.filterString;
465
466         m = strstrofs(s, ":", 0);
467         if(m >= 0)
468         {
469                 typestr = substring(s, 0, m);
470                 s = substring(s, m + 1, strlen(s) - m - 1);
471                 while(substring(s, 0, 1) == " ")
472                         s = substring(s, 1, strlen(s) - 1);
473         }
474         else
475                 typestr = "";
476
477         modstr = cvar_string("menu_slist_modfilter");
478
479         m = SLIST_MASK_AND - 1;
480         resethostcachemasks();
481
482         // ping: reject negative ping (no idea why this happens in the first place, engine bug)
483         sethostcachemasknumber(++m, SLIST_FIELD_PING, 0, SLIST_TEST_GREATEREQUAL);
484
485         // show full button
486         if(!me.filterShowFull)
487         {
488                 sethostcachemasknumber(++m, SLIST_FIELD_FREESLOTS, 1, SLIST_TEST_GREATEREQUAL); // legacy
489                 sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, ":S0:", SLIST_TEST_NOTCONTAIN); // g_maxplayers support
490         }
491
492         // show empty button
493         if(!me.filterShowEmpty)
494                 sethostcachemasknumber(++m, SLIST_FIELD_NUMHUMANS, 1, SLIST_TEST_GREATEREQUAL);
495
496         // gametype filtering
497         if(typestr != "")
498                 sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, strcat(typestr, ":"), SLIST_TEST_STARTSWITH);
499
500         // mod filtering
501         if(modstr != "")
502         {
503                 if(substring(modstr, 0, 1) == "!")
504                         sethostcachemaskstring(++m, SLIST_FIELD_MOD, resolvemod(substring(modstr, 1, strlen(modstr) - 1)), SLIST_TEST_NOTEQUAL);
505                 else
506                         sethostcachemaskstring(++m, SLIST_FIELD_MOD, resolvemod(modstr), SLIST_TEST_EQUAL);
507         }
508
509         // server banning
510         n = tokenizebyseparator(_Nex_ExtResponseSystem_BannedServers, " ");
511         for(i = 0; i < n; ++i)
512                 if(argv(i) != "")
513                         sethostcachemaskstring(++m, SLIST_FIELD_CNAME, argv(i), SLIST_TEST_NOTSTARTSWITH);
514
515         m = SLIST_MASK_OR - 1;
516         if(s != "")
517         {
518                 sethostcachemaskstring(++m, SLIST_FIELD_NAME, s, SLIST_TEST_CONTAINS);
519                 sethostcachemaskstring(++m, SLIST_FIELD_MAP, s, SLIST_TEST_CONTAINS);
520                 sethostcachemaskstring(++m, SLIST_FIELD_PLAYERS, s, SLIST_TEST_CONTAINS);
521                 sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, strcat(s, ":"), SLIST_TEST_STARTSWITH);
522         }
523
524         // sorting flags
525         //listflags |= SLSF_FAVORITES;
526         listflags |= SLSF_CATEGORIES;
527         if(me.currentSortOrder < 0) { listflags |= SLSF_DESCENDING; }
528         sethostcachesort(me.currentSortField, listflags);
529         
530         resorthostcache();
531         if(mode >= 1) { refreshhostcache(); }
532 }
533 void XonoticServerList_focusEnter(entity me)
534 {
535         if(time < me.nextRefreshTime)
536         {
537                 //print("sorry, no refresh yet\n");
538                 return;
539         }
540         me.nextRefreshTime = time + 10;
541         me.refreshServerList(me, 1);
542 }
543
544 void XonoticServerList_draw(entity me)
545 {
546         float i, found, owned, num;
547
548         if(_Nex_ExtResponseSystem_BannedServersNeedsRefresh)
549         {
550                 if(!me.needsRefresh)
551                         me.needsRefresh = 2;
552                 _Nex_ExtResponseSystem_BannedServersNeedsRefresh = 0;
553         }
554
555         if(me.currentSortField == -1)
556         {
557                 me.setSortOrder(me, SLIST_FIELD_PING, +1);
558                 me.refreshServerList(me, 2);
559         }
560         else if(me.needsRefresh == 1)
561         {
562                 me.needsRefresh = 2; // delay by one frame to make sure "slist" has been executed
563         }
564         else if(me.needsRefresh == 2)
565         {
566                 me.needsRefresh = 0;
567                 me.refreshServerList(me, 0);
568         }
569
570         owned = ((me.selectedServer == me.ipAddressBox.text) && (me.ipAddressBox.text != ""));
571
572         for(i = 0; i < category_draw_count; ++i) { category_name[i] = -1; category_item[i] = -1; }
573         category_draw_count = 0;
574
575         if(autocvar_menu_serverlist_categories >= 0) // if less than 0, don't even draw a category heading for favorites
576         {
577                 float itemcount = gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT);
578                 me.nItems = itemcount;
579                 
580                 //float visible = floor(me.scrollPos / me.itemHeight);
581                 // ^ unfortunately no such optimization can be made-- we must process through the
582                 // entire list, otherwise there is no way to know which item is first in its category.
583
584                 float cat, x;
585                 for(i = 0; i < itemcount; ++i)
586                 {
587                         cat = gethostcachenumber(SLIST_FIELD_CATEGORY, i);
588                         if(cat)
589                         {
590                                 if(category_draw_count == 0)
591                                 {
592                                         category_name[category_draw_count] = cat;
593                                         category_item[category_draw_count] = i;
594                                         ++category_draw_count;
595                                         ++me.nItems;
596                                 }
597                                 else
598                                 {
599                                         found = 0;
600                                         for(x = 0; x < category_draw_count; ++x) { if(cat == category_name[x]) { found = 1; } }
601                                         if not(found)
602                                         {
603                                                 category_name[category_draw_count] = cat;
604                                                 category_item[category_draw_count] = i;
605                                                 ++category_draw_count;
606                                                 ++me.nItems;
607                                         }
608                                 }
609                         }
610                 }
611                 if(autocvar_menu_serverlist_categories_onlyifmultiple && (category_draw_count == 1))
612                 {
613                         category_name[0] = category_name[1] = -1;
614                         category_item[0] = category_item[1] = -1;
615                         category_draw_count = 0;
616                         me.nItems = itemcount;
617                 }
618         }
619         else { me.nItems = gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT); }
620
621         me.connectButton.disabled = ((me.nItems == 0) && (me.ipAddressBox.text == ""));
622         me.infoButton.disabled = ((me.nItems == 0) || !owned);
623         me.favoriteButton.disabled = ((me.nItems == 0) && (me.ipAddressBox.text == ""));
624
625         found = 0;
626         if(me.selectedServer)
627         {
628                 for(i = 0; i < me.nItems; ++i)
629                 {
630                         num = XonoticServerList_MapItems(i);
631                         if(num >= 0)
632                         {
633                                 if(gethostcachestring(SLIST_FIELD_CNAME, num) == me.selectedServer)
634                                 {
635                                         if(i != me.selectedItem)
636                                         {
637                                                 me.lastClickedServer = -1;
638                                                 me.selectedItem = i;
639                                         }
640                                         found = 1;
641                                         break;
642                                 }
643                         }
644                 }
645         }
646         if(!found)
647         {
648                 if(me.nItems > 0)
649                 {
650                         if(me.selectedItem >= me.nItems) { me.selectedItem = me.nItems - 1; }
651                         if(me.selectedServer) { strunzone(me.selectedServer); }
652
653                         num = XonoticServerList_MapItems(me.selectedItem);
654                         if(num >= 0) { me.selectedServer = strzone(gethostcachestring(SLIST_FIELD_CNAME, num)); }
655                 }
656         }
657         
658         if(owned)
659         {
660                 if(me.selectedServer != me.ipAddressBox.text)
661                 {
662                         me.ipAddressBox.setText(me.ipAddressBox, me.selectedServer);
663                         me.ipAddressBox.cursorPos = strlen(me.selectedServer);
664                         me.ipAddressBoxFocused = -1;
665                 }
666         }
667
668         if(me.ipAddressBoxFocused != me.ipAddressBox.focused)
669         {
670                 if(me.ipAddressBox.focused || me.ipAddressBoxFocused < 0)
671                         ServerList_Update_favoriteButton(NULL, me);
672                 me.ipAddressBoxFocused = me.ipAddressBox.focused;
673         }
674
675         SUPER(XonoticServerList).draw(me);
676 }
677 void ServerList_PingSort_Click(entity btn, entity me)
678 {
679         me.setSortOrder(me, SLIST_FIELD_PING, +1);
680 }
681 void ServerList_NameSort_Click(entity btn, entity me)
682 {
683         me.setSortOrder(me, SLIST_FIELD_NAME, -1); // why?
684 }
685 void ServerList_MapSort_Click(entity btn, entity me)
686 {
687         me.setSortOrder(me, SLIST_FIELD_MAP, -1); // why?
688 }
689 void ServerList_PlayerSort_Click(entity btn, entity me)
690 {
691         me.setSortOrder(me, SLIST_FIELD_NUMHUMANS, -1);
692 }
693 void ServerList_TypeSort_Click(entity btn, entity me)
694 {
695         string s, t;
696         float i, m;
697         s = me.filterString;
698         m = strstrofs(s, ":", 0);
699         if(m >= 0)
700         {
701                 s = substring(s, 0, m);
702                 while(substring(s, m+1, 1) == " ") // skip spaces
703                         ++m;
704         }
705         else
706                 s = "";
707
708         for(i = 1; ; i *= 2) // 20 modes ought to be enough for anyone
709         {
710                 t = MapInfo_Type_ToString(i);
711                 if(i > 1)
712                         if(t == "") // it repeats (default case)
713                         {
714                                 // no type was found
715                                 // choose the first one
716                                 s = MapInfo_Type_ToString(1);
717                                 break;
718                         }
719                 if(s == t)
720                 {
721                         // the type was found
722                         // choose the next one
723                         s = MapInfo_Type_ToString(i * 2);
724                         if(s == "")
725                                 s = MapInfo_Type_ToString(1);
726                         break;
727                 }
728         }
729
730         if(s != "")
731                 s = strcat(s, ":");
732         s = strcat(s, substring(me.filterString, m+1, strlen(me.filterString) - m - 1));
733
734         me.controlledTextbox.setText(me.controlledTextbox, s);
735         me.controlledTextbox.keyDown(me.controlledTextbox, K_END, 0, 0);
736         me.controlledTextbox.keyUp(me.controlledTextbox, K_END, 0, 0);
737         //ServerList_Filter_Change(me.controlledTextbox, me);
738 }
739 void ServerList_Filter_Change(entity box, entity me)
740 {
741         if(me.filterString)
742                 strunzone(me.filterString);
743         if(box.text != "")
744                 me.filterString = strzone(box.text);
745         else
746                 me.filterString = string_null;
747         me.refreshServerList(me, 0);
748
749         me.ipAddressBox.setText(me.ipAddressBox, "");
750         me.ipAddressBox.cursorPos = 0;
751         me.ipAddressBoxFocused = -1;
752 }
753 void ServerList_ShowEmpty_Click(entity box, entity me)
754 {
755         box.setChecked(box, me.filterShowEmpty = !me.filterShowEmpty);
756         me.refreshServerList(me, 0);
757
758         me.ipAddressBox.setText(me.ipAddressBox, "");
759         me.ipAddressBox.cursorPos = 0;
760         me.ipAddressBoxFocused = -1;
761 }
762 void ServerList_ShowFull_Click(entity box, entity me)
763 {
764         box.setChecked(box, me.filterShowFull = !me.filterShowFull);
765         me.refreshServerList(me, 0);
766
767         me.ipAddressBox.setText(me.ipAddressBox, "");
768         me.ipAddressBox.cursorPos = 0;
769         me.ipAddressBoxFocused = -1;
770 }
771 void XonoticServerList_setSortOrder(entity me, float fld, float direction)
772 {
773         if(me.currentSortField == fld)
774                 direction = -me.currentSortOrder;
775         me.currentSortOrder = direction;
776         me.currentSortField = fld;
777         me.sortButton1.forcePressed = (fld == SLIST_FIELD_PING);
778         me.sortButton2.forcePressed = (fld == SLIST_FIELD_NAME);
779         me.sortButton3.forcePressed = (fld == SLIST_FIELD_MAP);
780         me.sortButton4.forcePressed = 0;
781         me.sortButton5.forcePressed = (fld == SLIST_FIELD_NUMHUMANS);
782         me.selectedItem = 0;
783         if(me.selectedServer)
784                 strunzone(me.selectedServer);
785         me.selectedServer = string_null;
786         me.refreshServerList(me, 0);
787 }
788 void XonoticServerList_positionSortButton(entity me, entity btn, float theOrigin, float theSize, string theTitle, void(entity, entity) theFunc)
789 {
790         vector originInLBSpace, sizeInLBSpace;
791         originInLBSpace = eY * (-me.itemHeight);
792         sizeInLBSpace = eY * me.itemHeight + eX * (1 - me.controlWidth);
793
794         vector originInDialogSpace, sizeInDialogSpace;
795         originInDialogSpace = boxToGlobal(originInLBSpace, me.Container_origin, me.Container_size);
796         sizeInDialogSpace = boxToGlobalSize(sizeInLBSpace, me.Container_size);
797
798         btn.Container_origin_x = originInDialogSpace_x + sizeInDialogSpace_x * theOrigin;
799         btn.Container_size_x   =                         sizeInDialogSpace_x * theSize;
800         btn.setText(btn, theTitle);
801         btn.onClick = theFunc;
802         btn.onClickEntity = me;
803         btn.resized = 1;
804 }
805 void XonoticServerList_resizeNotify(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
806 {
807         SUPER(XonoticServerList).resizeNotify(me, relOrigin, relSize, absOrigin, absSize);
808
809         me.realFontSize_y = me.fontSize / (absSize_y * me.itemHeight);
810         me.realFontSize_x = me.fontSize / (absSize_x * (1 - me.controlWidth));
811         me.realUpperMargin = 0.5 * (1 - me.realFontSize_y);
812
813         me.columnIconsOrigin = 0;
814         me.columnIconsSize = me.realFontSize_x * 4 * me.iconsSizeFactor;
815         me.columnPingSize = me.realFontSize_x * 3;
816         me.columnMapSize = me.realFontSize_x * 10;
817         me.columnTypeSize = me.realFontSize_x * 4;
818         me.columnPlayersSize = me.realFontSize_x * 5;
819         me.columnNameSize = 1 - me.columnPlayersSize - me.columnMapSize - me.columnPingSize - me.columnIconsSize - me.columnTypeSize - 5 * me.realFontSize_x;
820         me.columnPingOrigin = me.columnIconsOrigin + me.columnIconsSize + me.realFontSize_x;
821         me.columnNameOrigin = me.columnPingOrigin + me.columnPingSize + me.realFontSize_x;
822         me.columnMapOrigin = me.columnNameOrigin + me.columnNameSize + me.realFontSize_x;
823         me.columnTypeOrigin = me.columnMapOrigin + me.columnMapSize + me.realFontSize_x;
824         me.columnPlayersOrigin = me.columnTypeOrigin + me.columnTypeSize + me.realFontSize_x;
825
826         me.positionSortButton(me, me.sortButton1, me.columnPingOrigin, me.columnPingSize, _("Ping"), ServerList_PingSort_Click);
827         me.positionSortButton(me, me.sortButton2, me.columnNameOrigin, me.columnNameSize, _("Host name"), ServerList_NameSort_Click);
828         me.positionSortButton(me, me.sortButton3, me.columnMapOrigin, me.columnMapSize, _("Map"), ServerList_MapSort_Click);
829         me.positionSortButton(me, me.sortButton4, me.columnTypeOrigin, me.columnTypeSize, _("Type"), ServerList_TypeSort_Click);
830         me.positionSortButton(me, me.sortButton5, me.columnPlayersOrigin, me.columnPlayersSize, _("Players"), ServerList_PlayerSort_Click);
831
832         float f;
833         f = me.currentSortField;
834         if(f >= 0)
835         {
836                 me.currentSortField = -1;
837                 me.setSortOrder(me, f, me.currentSortOrder); // force resetting the sort order
838         }
839 }
840 void ServerList_Connect_Click(entity btn, entity me)
841 {
842         localcmd(sprintf("connect %s\n",
843                 ((me.ipAddressBox.text != "") ?
844                         me.ipAddressBox.text : me.selectedServer
845                 )
846         ));
847 }
848 void ServerList_Favorite_Click(entity btn, entity me)
849 {
850         string ipstr;
851         ipstr = netaddress_resolve(me.ipAddressBox.text, 26000);
852         if(ipstr != "")
853         {
854                 ToggleFavorite(me.ipAddressBox.text);
855                 me.ipAddressBoxFocused = -1;
856         }
857 }
858 void ServerList_Info_Click(entity btn, entity me)
859 {
860         main.serverInfoDialog.loadServerInfo(main.serverInfoDialog, XonoticServerList_MapItems(me.selectedItem));
861         DialogOpenButton_Click(me, main.serverInfoDialog);
862 }
863 void XonoticServerList_clickListBoxItem(entity me, float i, vector where)
864 {
865         float num = XonoticServerList_MapItems(i);
866         if(num == me.lastClickedServer)
867                 if(time < me.lastClickedTime + 0.3)
868                 {
869                         // DOUBLE CLICK!
870                         ServerList_Connect_Click(NULL, me);
871                 }
872         me.lastClickedServer = num;
873         me.lastClickedTime = time;
874 }
875 void XonoticServerList_drawListBoxItem(entity me, float i, vector absSize, float isSelected)
876 {
877         // layout: Ping, Server name, Map name, NP, TP, MP
878         float p, q;
879         float isv4, isv6;
880         vector theColor;
881         float theAlpha;
882         float m, pure, freeslots, j, sflags;
883         string s, typestr, versionstr, k, v, modname;
884
885         float item = XonoticServerList_MapItems(i);
886         //print(sprintf("time: %f, i: %d, item: %d, nitems: %d\n", time, i, item, me.nItems));
887         
888         if(item < 0)
889         {
890                 entity catent = Get_Cat_Ent(-item);
891                 if(catent)
892                 {
893                         draw_Text(
894                                 eY * me.realUpperMargin
895                                 +
896                                 eX * (me.columnNameOrigin + (me.columnNameSize - draw_TextWidth(catent.cat_string, 0, me.realFontSize)) * 0.5),
897                                 catent.cat_string,
898                                 me.realFontSize,
899                                 '1 1 1',
900                                 SKINALPHA_TEXT,
901                                 0
902                         );
903                         return;
904                 }
905         }
906         
907         if(isSelected)
908                 draw_Fill('0 0 0', '1 1 0', SKINCOLOR_LISTBOX_SELECTED, SKINALPHA_LISTBOX_SELECTED);
909
910         s = gethostcachestring(SLIST_FIELD_QCSTATUS, item);
911         m = tokenizebyseparator(s, ":");
912         typestr = "";
913         if(m >= 2)
914         {
915                 typestr = argv(0);
916                 versionstr = argv(1);
917         }
918         freeslots = -1;
919         sflags = -1;
920         modname = "";
921         pure = 0;
922         for(j = 2; j < m; ++j)
923         {
924                 if(argv(j) == "")
925                         break;
926                 k = substring(argv(j), 0, 1);
927                 v = substring(argv(j), 1, -1);
928                 if(k == "P")
929                         pure = stof(v);
930                 else if(k == "S")
931                         freeslots = stof(v);
932                 else if(k == "F")
933                         sflags = stof(v);
934                 else if(k == "M")
935                         modname = v;
936         }
937
938 #ifdef COMPAT_NO_MOD_IS_XONOTIC
939         if(modname == "")
940                 modname = "Xonotic";
941 #endif
942
943         /*
944         SLIST_FIELD_MOD = gethostcacheindexforkey("mod");
945         s = gethostcachestring(SLIST_FIELD_MOD, item);
946         if(s != "data")
947                 if(modname == "Xonotic")
948                         modname = s;
949         */
950
951         // list the mods here on which the pure server check actually works
952         if(modname != "Xonotic")
953         if(modname != "MinstaGib")
954         if(modname != "CTS")
955         if(modname != "NIX")
956         if(modname != "NewToys")
957                 pure = 0;
958
959         if(gethostcachenumber(SLIST_FIELD_FREESLOTS, item) <= 0)
960                 theAlpha = SKINALPHA_SERVERLIST_FULL;
961         else if(freeslots == 0)
962                 theAlpha = SKINALPHA_SERVERLIST_FULL; // g_maxplayers support
963         else if not(gethostcachenumber(SLIST_FIELD_NUMHUMANS, item))
964                 theAlpha = SKINALPHA_SERVERLIST_EMPTY;
965         else
966                 theAlpha = 1;
967
968         p = gethostcachenumber(SLIST_FIELD_PING, item);
969 #define PING_LOW 75
970 #define PING_MED 200
971 #define PING_HIGH 500
972         if(p < PING_LOW)
973                 theColor = SKINCOLOR_SERVERLIST_LOWPING + (SKINCOLOR_SERVERLIST_MEDPING - SKINCOLOR_SERVERLIST_LOWPING) * (p / PING_LOW);
974         else if(p < PING_MED)
975                 theColor = SKINCOLOR_SERVERLIST_MEDPING + (SKINCOLOR_SERVERLIST_HIGHPING - SKINCOLOR_SERVERLIST_MEDPING) * ((p - PING_LOW) / (PING_MED - PING_LOW));
976         else if(p < PING_HIGH)
977         {
978                 theColor = SKINCOLOR_SERVERLIST_HIGHPING;
979                 theAlpha *= 1 + (SKINALPHA_SERVERLIST_HIGHPING - 1) * ((p - PING_MED) / (PING_HIGH - PING_MED));
980         }
981         else
982         {
983                 theColor = eX;
984                 theAlpha *= SKINALPHA_SERVERLIST_HIGHPING;
985         }
986
987         if(gethostcachenumber(SLIST_FIELD_ISFAVORITE, item))
988         {
989                 theColor = theColor * (1 - SKINALPHA_SERVERLIST_FAVORITE) + SKINCOLOR_SERVERLIST_FAVORITE * SKINALPHA_SERVERLIST_FAVORITE;
990                 theAlpha = theAlpha * (1 - SKINALPHA_SERVERLIST_FAVORITE) + SKINALPHA_SERVERLIST_FAVORITE;
991         }
992
993         s = gethostcachestring(SLIST_FIELD_CNAME, item);
994
995         isv4 = isv6 = 0;
996         if(substring(s, 0, 1) == "[")
997         {
998                 isv6 = 1;
999                 me.seenIPv6 += 1;
1000         }
1001         else if(strstrofs("0123456789", substring(s, 0, 1), 0) >= 0)
1002         {
1003                 isv4 = 1;
1004                 me.seenIPv4 += 1;
1005         }
1006
1007         q = stof(substring(crypto_getencryptlevel(s), 0, 1));
1008         if((q <= 0 && cvar("crypto_aeslevel") >= 3) || (q >= 3 && cvar("crypto_aeslevel") <= 0))
1009         {
1010                 theColor = SKINCOLOR_SERVERLIST_IMPOSSIBLE;
1011                 theAlpha = SKINALPHA_SERVERLIST_IMPOSSIBLE;
1012         }
1013
1014         if(q == 1)
1015         {
1016                 if(cvar("crypto_aeslevel") >= 2)
1017                         q |= 4;
1018         }
1019         if(q == 2)
1020         {
1021                 if(cvar("crypto_aeslevel") >= 1)
1022                         q |= 4;
1023         }
1024         if(q == 3)
1025                 q = 5;
1026         else if(q >= 3)
1027                 q -= 2;
1028         // possible status:
1029         // 0: crypto off
1030         // 1: AES possible
1031         // 2: AES recommended but not available
1032         // 3: AES possible and will be used
1033         // 4: AES recommended and will be used
1034         // 5: AES required
1035
1036         // --------------
1037         //  RENDER ICONS
1038         // --------------
1039         vector iconSize = '0 0 0';
1040         iconSize_y = me.realFontSize_y * me.iconsSizeFactor;
1041         iconSize_x = me.realFontSize_x * me.iconsSizeFactor;
1042
1043         vector iconPos = '0 0 0';
1044         iconPos_x = (me.columnIconsSize - 3 * iconSize_x) * 0.5;
1045         iconPos_y = (1 - iconSize_y) * 0.5;
1046
1047         string n;
1048
1049         if not(me.seenIPv4 && me.seenIPv6)
1050         {
1051                 iconPos_x += iconSize_x * 0.5;
1052         }
1053         else if(me.seenIPv4 && me.seenIPv6)
1054         {
1055                 n = string_null;
1056                 if(isv6)
1057                         draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_ipv6"), 0); // PRECACHE_PIC_MIPMAP
1058                 else if(isv4)
1059                         draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_ipv4"), 0); // PRECACHE_PIC_MIPMAP
1060                 if(n)
1061                         draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1062                 iconPos_x += iconSize_x;
1063         }
1064
1065         if(q > 0)
1066         {
1067                 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_aeslevel", ftos(q)), 0); // PRECACHE_PIC_MIPMAP
1068                 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1069         }
1070         iconPos_x += iconSize_x;
1071
1072         if(modname == "Xonotic")
1073         {
1074                 if(pure == 0)
1075                 {
1076                         draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_pure1"), PRECACHE_PIC_MIPMAP);
1077                         draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1078                 }
1079         }
1080         else
1081         {
1082                 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_mod_", modname), PRECACHE_PIC_MIPMAP);
1083                 if(draw_PictureSize(n) == '0 0 0')
1084                         draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_mod_"), PRECACHE_PIC_MIPMAP);
1085                 if(pure == 0)
1086                         draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1087                 else
1088                         draw_Picture(iconPos, n, iconSize, '1 1 1', SKINALPHA_SERVERLIST_ICON_NONPURE);
1089         }
1090         iconPos_x += iconSize_x;
1091
1092         if(sflags >= 0 && (sflags & SERVERFLAG_PLAYERSTATS))
1093         {
1094                 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_stats1"), 0); // PRECACHE_PIC_MIPMAP
1095                 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1096         }
1097         iconPos_x += iconSize_x;
1098         
1099         // --------------
1100         //  RENDER TEXT
1101         // --------------
1102         
1103         // ping
1104         s = ftos(p);
1105         draw_Text(me.realUpperMargin * eY + (me.columnPingOrigin + me.columnPingSize - draw_TextWidth(s, 0, me.realFontSize)) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1106
1107         // server name
1108         s = draw_TextShortenToWidth(gethostcachestring(SLIST_FIELD_NAME, item), me.columnNameSize, 0, me.realFontSize);
1109         draw_Text(me.realUpperMargin * eY + me.columnNameOrigin * eX, s, me.realFontSize, theColor, theAlpha, 0);
1110
1111         // server map
1112         s = draw_TextShortenToWidth(gethostcachestring(SLIST_FIELD_MAP, item), me.columnMapSize, 0, me.realFontSize);
1113         draw_Text(me.realUpperMargin * eY + (me.columnMapOrigin + (me.columnMapSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1114
1115         // server gametype
1116         s = draw_TextShortenToWidth(typestr, me.columnTypeSize, 0, me.realFontSize);
1117         draw_Text(me.realUpperMargin * eY + (me.columnTypeOrigin + (me.columnTypeSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1118
1119         // server playercount
1120         s = strcat(ftos(gethostcachenumber(SLIST_FIELD_NUMHUMANS, item)), "/", ftos(gethostcachenumber(SLIST_FIELD_MAXPLAYERS, item)));
1121         draw_Text(me.realUpperMargin * eY + (me.columnPlayersOrigin + (me.columnPlayersSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1122 }
1123
1124 float XonoticServerList_keyDown(entity me, float scan, float ascii, float shift)
1125 {
1126         float i = XonoticServerList_MapItems(me.selectedItem);
1127         vector org, sz;
1128
1129         org = boxToGlobal(eY * (me.selectedItem * me.itemHeight - me.scrollPos), me.origin, me.size);
1130         sz = boxToGlobalSize(eY * me.itemHeight + eX * (1 - me.controlWidth), me.size);
1131
1132         if(scan == K_ENTER || scan == K_KP_ENTER)
1133         {
1134                 ServerList_Connect_Click(NULL, me);
1135                 return 1;
1136         }
1137         else if(scan == K_MOUSE2 || scan == K_SPACE)
1138         {
1139                 if((me.nItems != 0) && (i >= 0))
1140                 {
1141                         main.serverInfoDialog.loadServerInfo(main.serverInfoDialog, i);
1142                         DialogOpenButton_Click_withCoords(me, main.serverInfoDialog, org, sz);
1143                         return 1;
1144                 }
1145                 return 0;
1146         }
1147         else if(scan == K_INS || scan == K_MOUSE3 || scan == K_KP_INS)
1148         {
1149                 if((me.nItems != 0) && (i >= 0))
1150                 {
1151                         ToggleFavorite(me.selectedServer);
1152                         me.ipAddressBoxFocused = -1;
1153                         return 1;
1154                 }
1155                 return 0;
1156         }
1157         else if(SUPER(XonoticServerList).keyDown(me, scan, ascii, shift))
1158                 return 1;
1159         else if(!me.controlledTextbox)
1160                 return 0;
1161         else
1162                 return me.controlledTextbox.keyDown(me.controlledTextbox, scan, ascii, shift);
1163 }
1164 #endif