]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/menu/xonotic/serverlist.c
Whoops, nItems still needs to be set even when categories are disabled
[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         if(me.ipAddressBox.text == "")
843                 localcmd("connect ", me.selectedServer, "\n");
844         else
845                 localcmd("connect ", me.ipAddressBox.text, "\n");
846 }
847 void ServerList_Favorite_Click(entity btn, entity me)
848 {
849         string ipstr;
850         ipstr = netaddress_resolve(me.ipAddressBox.text, 26000);
851         if(ipstr != "")
852         {
853                 ToggleFavorite(me.ipAddressBox.text);
854                 me.ipAddressBoxFocused = -1;
855         }
856 }
857 void ServerList_Info_Click(entity btn, entity me)
858 {
859         main.serverInfoDialog.loadServerInfo(main.serverInfoDialog, XonoticServerList_MapItems(me.selectedItem));
860         DialogOpenButton_Click(me, main.serverInfoDialog);
861 }
862 void XonoticServerList_clickListBoxItem(entity me, float i, vector where)
863 {
864         float num = XonoticServerList_MapItems(i);
865         if(num == me.lastClickedServer)
866                 if(time < me.lastClickedTime + 0.3)
867                 {
868                         // DOUBLE CLICK!
869                         ServerList_Connect_Click(NULL, me);
870                 }
871         me.lastClickedServer = num;
872         me.lastClickedTime = time;
873 }
874 void XonoticServerList_drawListBoxItem(entity me, float i, vector absSize, float isSelected)
875 {
876         // layout: Ping, Server name, Map name, NP, TP, MP
877         float p, q;
878         float isv4, isv6;
879         vector theColor;
880         float theAlpha;
881         float m, pure, freeslots, j, sflags;
882         string s, typestr, versionstr, k, v, modname;
883
884         float cache = XonoticServerList_MapItems(i);
885         //print(sprintf("time: %f, i: %d, cache: %d, nitems: %d\n", time, i, cache, me.nItems));
886         
887         if(cache < 0)
888         {
889                 entity catent = Get_Cat_Ent(-cache);
890                 if(catent) { draw_Text(me.realUpperMargin * eY + (me.columnNameOrigin + (me.columnNameSize - draw_TextWidth(catent.cat_string, 0, me.realFontSize)) * 0.5) * eX, catent.cat_string, me.realFontSize, '1 1 1', SKINALPHA_TEXT, 0); return; }
891         }
892         
893         if(isSelected)
894                 draw_Fill('0 0 0', '1 1 0', SKINCOLOR_LISTBOX_SELECTED, SKINALPHA_LISTBOX_SELECTED);
895
896         s = gethostcachestring(SLIST_FIELD_QCSTATUS, cache);
897         m = tokenizebyseparator(s, ":");
898         typestr = "";
899         if(m >= 2)
900         {
901                 typestr = argv(0);
902                 versionstr = argv(1);
903         }
904         freeslots = -1;
905         sflags = -1;
906         modname = "";
907         pure = 0;
908         for(j = 2; j < m; ++j)
909         {
910                 if(argv(j) == "")
911                         break;
912                 k = substring(argv(j), 0, 1);
913                 v = substring(argv(j), 1, -1);
914                 if(k == "P")
915                         pure = stof(v);
916                 else if(k == "S")
917                         freeslots = stof(v);
918                 else if(k == "F")
919                         sflags = stof(v);
920                 else if(k == "M")
921                         modname = v;
922         }
923
924 #ifdef COMPAT_NO_MOD_IS_XONOTIC
925         if(modname == "")
926                 modname = "Xonotic";
927 #endif
928
929         /*
930         SLIST_FIELD_MOD = gethostcacheindexforkey("mod");
931         s = gethostcachestring(SLIST_FIELD_MOD, cache);
932         if(s != "data")
933                 if(modname == "Xonotic")
934                         modname = s;
935         */
936
937         // list the mods here on which the pure server check actually works
938         if(modname != "Xonotic")
939         if(modname != "MinstaGib")
940         if(modname != "CTS")
941         if(modname != "NIX")
942         if(modname != "NewToys")
943                 pure = 0;
944
945         if(gethostcachenumber(SLIST_FIELD_FREESLOTS, cache) <= 0)
946                 theAlpha = SKINALPHA_SERVERLIST_FULL;
947         else if(freeslots == 0)
948                 theAlpha = SKINALPHA_SERVERLIST_FULL; // g_maxplayers support
949         else if not(gethostcachenumber(SLIST_FIELD_NUMHUMANS, cache))
950                 theAlpha = SKINALPHA_SERVERLIST_EMPTY;
951         else
952                 theAlpha = 1;
953
954         p = gethostcachenumber(SLIST_FIELD_PING, cache);
955 #define PING_LOW 75
956 #define PING_MED 200
957 #define PING_HIGH 500
958         if(p < PING_LOW)
959                 theColor = SKINCOLOR_SERVERLIST_LOWPING + (SKINCOLOR_SERVERLIST_MEDPING - SKINCOLOR_SERVERLIST_LOWPING) * (p / PING_LOW);
960         else if(p < PING_MED)
961                 theColor = SKINCOLOR_SERVERLIST_MEDPING + (SKINCOLOR_SERVERLIST_HIGHPING - SKINCOLOR_SERVERLIST_MEDPING) * ((p - PING_LOW) / (PING_MED - PING_LOW));
962         else if(p < PING_HIGH)
963         {
964                 theColor = SKINCOLOR_SERVERLIST_HIGHPING;
965                 theAlpha *= 1 + (SKINALPHA_SERVERLIST_HIGHPING - 1) * ((p - PING_MED) / (PING_HIGH - PING_MED));
966         }
967         else
968         {
969                 theColor = eX;
970                 theAlpha *= SKINALPHA_SERVERLIST_HIGHPING;
971         }
972
973         if(gethostcachenumber(SLIST_FIELD_ISFAVORITE, cache))
974         {
975                 theColor = theColor * (1 - SKINALPHA_SERVERLIST_FAVORITE) + SKINCOLOR_SERVERLIST_FAVORITE * SKINALPHA_SERVERLIST_FAVORITE;
976                 theAlpha = theAlpha * (1 - SKINALPHA_SERVERLIST_FAVORITE) + SKINALPHA_SERVERLIST_FAVORITE;
977         }
978
979         s = gethostcachestring(SLIST_FIELD_CNAME, cache);
980
981         isv4 = isv6 = 0;
982         if(substring(s, 0, 1) == "[")
983         {
984                 isv6 = 1;
985                 me.seenIPv6 += 1;
986         }
987         else if(strstrofs("0123456789", substring(s, 0, 1), 0) >= 0)
988         {
989                 isv4 = 1;
990                 me.seenIPv4 += 1;
991         }
992
993         q = stof(substring(crypto_getencryptlevel(s), 0, 1));
994         if((q <= 0 && cvar("crypto_aeslevel") >= 3) || (q >= 3 && cvar("crypto_aeslevel") <= 0))
995         {
996                 theColor = SKINCOLOR_SERVERLIST_IMPOSSIBLE;
997                 theAlpha = SKINALPHA_SERVERLIST_IMPOSSIBLE;
998         }
999
1000         if(q == 1)
1001         {
1002                 if(cvar("crypto_aeslevel") >= 2)
1003                         q |= 4;
1004         }
1005         if(q == 2)
1006         {
1007                 if(cvar("crypto_aeslevel") >= 1)
1008                         q |= 4;
1009         }
1010         if(q == 3)
1011                 q = 5;
1012         else if(q >= 3)
1013                 q -= 2;
1014         // possible status:
1015         // 0: crypto off
1016         // 1: AES possible
1017         // 2: AES recommended but not available
1018         // 3: AES possible and will be used
1019         // 4: AES recommended and will be used
1020         // 5: AES required
1021
1022         {
1023                 vector iconSize = '0 0 0';
1024                 iconSize_y = me.realFontSize_y * me.iconsSizeFactor;
1025                 iconSize_x = me.realFontSize_x * me.iconsSizeFactor;
1026
1027                 vector iconPos = '0 0 0';
1028                 iconPos_x = (me.columnIconsSize - 3 * iconSize_x) * 0.5;
1029                 iconPos_y = (1 - iconSize_y) * 0.5;
1030
1031                 string n;
1032
1033                 if not(me.seenIPv4 && me.seenIPv6)
1034                 {
1035                         iconPos_x += iconSize_x * 0.5;
1036                 }
1037                 else if(me.seenIPv4 && me.seenIPv6)
1038                 {
1039                         n = string_null;
1040                         if(isv6)
1041                                 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_ipv6"), 0); // PRECACHE_PIC_MIPMAP
1042                         else if(isv4)
1043                                 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_ipv4"), 0); // PRECACHE_PIC_MIPMAP
1044                         if(n)
1045                                 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1046                         iconPos_x += iconSize_x;
1047                 }
1048
1049                 if(q > 0)
1050                 {
1051                         draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_aeslevel", ftos(q)), 0); // PRECACHE_PIC_MIPMAP
1052                         draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1053                 }
1054                 iconPos_x += iconSize_x;
1055
1056                 if(modname == "Xonotic")
1057                 {
1058                         if(pure == 0)
1059                         {
1060                                 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_pure1"), PRECACHE_PIC_MIPMAP);
1061                                 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1062                         }
1063                 }
1064                 else
1065                 {
1066                         draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_mod_", modname), PRECACHE_PIC_MIPMAP);
1067                         if(draw_PictureSize(n) == '0 0 0')
1068                                 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_mod_"), PRECACHE_PIC_MIPMAP);
1069                         if(pure == 0)
1070                                 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1071                         else
1072                                 draw_Picture(iconPos, n, iconSize, '1 1 1', SKINALPHA_SERVERLIST_ICON_NONPURE);
1073                 }
1074                 iconPos_x += iconSize_x;
1075
1076                 if(sflags >= 0 && (sflags & SERVERFLAG_PLAYERSTATS))
1077                 {
1078                         draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_stats1"), 0); // PRECACHE_PIC_MIPMAP
1079                         draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1080                 }
1081                 iconPos_x += iconSize_x;
1082         }
1083
1084         s = ftos(p);
1085         draw_Text(me.realUpperMargin * eY + (me.columnPingOrigin + me.columnPingSize - draw_TextWidth(s, 0, me.realFontSize)) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1086         s = draw_TextShortenToWidth(gethostcachestring(SLIST_FIELD_NAME, cache), me.columnNameSize, 0, me.realFontSize);
1087         draw_Text(me.realUpperMargin * eY + me.columnNameOrigin * eX, s, me.realFontSize, theColor, theAlpha, 0);
1088         s = draw_TextShortenToWidth(gethostcachestring(SLIST_FIELD_MAP, cache), me.columnMapSize, 0, me.realFontSize);
1089         draw_Text(me.realUpperMargin * eY + (me.columnMapOrigin + (me.columnMapSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1090         s = draw_TextShortenToWidth(typestr, me.columnTypeSize, 0, me.realFontSize);
1091         draw_Text(me.realUpperMargin * eY + (me.columnTypeOrigin + (me.columnTypeSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1092         s = strcat(ftos(gethostcachenumber(SLIST_FIELD_NUMHUMANS, cache)), "/", ftos(gethostcachenumber(SLIST_FIELD_MAXPLAYERS, cache)));
1093         draw_Text(me.realUpperMargin * eY + (me.columnPlayersOrigin + (me.columnPlayersSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1094 }
1095
1096 float XonoticServerList_keyDown(entity me, float scan, float ascii, float shift)
1097 {
1098         float i = XonoticServerList_MapItems(me.selectedItem);
1099         vector org, sz;
1100
1101         org = boxToGlobal(eY * (me.selectedItem * me.itemHeight - me.scrollPos), me.origin, me.size);
1102         sz = boxToGlobalSize(eY * me.itemHeight + eX * (1 - me.controlWidth), me.size);
1103
1104         if(scan == K_ENTER || scan == K_KP_ENTER)
1105         {
1106                 ServerList_Connect_Click(NULL, me);
1107                 return 1;
1108         }
1109         else if(scan == K_MOUSE2 || scan == K_SPACE)
1110         {
1111                 if((me.nItems != 0) && (i >= 0))
1112                 {
1113                         main.serverInfoDialog.loadServerInfo(main.serverInfoDialog, i);
1114                         DialogOpenButton_Click_withCoords(me, main.serverInfoDialog, org, sz);
1115                         return 1;
1116                 }
1117                 return 0;
1118         }
1119         else if(scan == K_INS || scan == K_MOUSE3 || scan == K_KP_INS)
1120         {
1121                 if((me.nItems != 0) && (i >= 0))
1122                 {
1123                         ToggleFavorite(me.selectedServer);
1124                         me.ipAddressBoxFocused = -1;
1125                         return 1;
1126                 }
1127                 return 0;
1128         }
1129         else if(SUPER(XonoticServerList).keyDown(me, scan, ascii, shift))
1130                 return 1;
1131         else if(!me.controlledTextbox)
1132                 return 0;
1133         else
1134                 return me.controlledTextbox.keyDown(me.controlledTextbox, scan, ascii, shift);
1135 }
1136 #endif