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