]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/menu/xonotic/serverlist.c
More cleanup
[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
620         me.connectButton.disabled = ((me.nItems == 0) && (me.ipAddressBox.text == ""));
621         me.infoButton.disabled = ((me.nItems == 0) || !owned);
622         me.favoriteButton.disabled = ((me.nItems == 0) && (me.ipAddressBox.text == ""));
623
624         found = 0;
625         if(me.selectedServer)
626         {
627                 for(i = 0; i < me.nItems; ++i)
628                 {
629                         num = XonoticServerList_MapItems(i);
630                         if(num >= 0)
631                         {
632                                 if(gethostcachestring(SLIST_FIELD_CNAME, num) == me.selectedServer)
633                                 {
634                                         if(i != me.selectedItem)
635                                         {
636                                                 me.lastClickedServer = -1;
637                                                 me.selectedItem = i;
638                                         }
639                                         found = 1;
640                                         break;
641                                 }
642                         }
643                 }
644         }
645         if(!found)
646         {
647                 if(me.nItems > 0)
648                 {
649                         if(me.selectedItem >= me.nItems) { me.selectedItem = me.nItems - 1; }
650                         if(me.selectedServer) { strunzone(me.selectedServer); }
651
652                         num = XonoticServerList_MapItems(me.selectedItem);
653                         if(num >= 0) { me.selectedServer = strzone(gethostcachestring(SLIST_FIELD_CNAME, num)); }
654                 }
655         }
656         
657         if(owned)
658         {
659                 if(me.selectedServer != me.ipAddressBox.text)
660                 {
661                         me.ipAddressBox.setText(me.ipAddressBox, me.selectedServer);
662                         me.ipAddressBox.cursorPos = strlen(me.selectedServer);
663                         me.ipAddressBoxFocused = -1;
664                 }
665         }
666
667         if(me.ipAddressBoxFocused != me.ipAddressBox.focused)
668         {
669                 if(me.ipAddressBox.focused || me.ipAddressBoxFocused < 0)
670                         ServerList_Update_favoriteButton(NULL, me);
671                 me.ipAddressBoxFocused = me.ipAddressBox.focused;
672         }
673
674         SUPER(XonoticServerList).draw(me);
675 }
676 void ServerList_PingSort_Click(entity btn, entity me)
677 {
678         me.setSortOrder(me, SLIST_FIELD_PING, +1);
679 }
680 void ServerList_NameSort_Click(entity btn, entity me)
681 {
682         me.setSortOrder(me, SLIST_FIELD_NAME, -1); // why?
683 }
684 void ServerList_MapSort_Click(entity btn, entity me)
685 {
686         me.setSortOrder(me, SLIST_FIELD_MAP, -1); // why?
687 }
688 void ServerList_PlayerSort_Click(entity btn, entity me)
689 {
690         me.setSortOrder(me, SLIST_FIELD_NUMHUMANS, -1);
691 }
692 void ServerList_TypeSort_Click(entity btn, entity me)
693 {
694         string s, t;
695         float i, m;
696         s = me.filterString;
697         m = strstrofs(s, ":", 0);
698         if(m >= 0)
699         {
700                 s = substring(s, 0, m);
701                 while(substring(s, m+1, 1) == " ") // skip spaces
702                         ++m;
703         }
704         else
705                 s = "";
706
707         for(i = 1; ; i *= 2) // 20 modes ought to be enough for anyone
708         {
709                 t = MapInfo_Type_ToString(i);
710                 if(i > 1)
711                         if(t == "") // it repeats (default case)
712                         {
713                                 // no type was found
714                                 // choose the first one
715                                 s = MapInfo_Type_ToString(1);
716                                 break;
717                         }
718                 if(s == t)
719                 {
720                         // the type was found
721                         // choose the next one
722                         s = MapInfo_Type_ToString(i * 2);
723                         if(s == "")
724                                 s = MapInfo_Type_ToString(1);
725                         break;
726                 }
727         }
728
729         if(s != "")
730                 s = strcat(s, ":");
731         s = strcat(s, substring(me.filterString, m+1, strlen(me.filterString) - m - 1));
732
733         me.controlledTextbox.setText(me.controlledTextbox, s);
734         me.controlledTextbox.keyDown(me.controlledTextbox, K_END, 0, 0);
735         me.controlledTextbox.keyUp(me.controlledTextbox, K_END, 0, 0);
736         //ServerList_Filter_Change(me.controlledTextbox, me);
737 }
738 void ServerList_Filter_Change(entity box, entity me)
739 {
740         if(me.filterString)
741                 strunzone(me.filterString);
742         if(box.text != "")
743                 me.filterString = strzone(box.text);
744         else
745                 me.filterString = string_null;
746         me.refreshServerList(me, 0);
747
748         me.ipAddressBox.setText(me.ipAddressBox, "");
749         me.ipAddressBox.cursorPos = 0;
750         me.ipAddressBoxFocused = -1;
751 }
752 void ServerList_ShowEmpty_Click(entity box, entity me)
753 {
754         box.setChecked(box, me.filterShowEmpty = !me.filterShowEmpty);
755         me.refreshServerList(me, 0);
756
757         me.ipAddressBox.setText(me.ipAddressBox, "");
758         me.ipAddressBox.cursorPos = 0;
759         me.ipAddressBoxFocused = -1;
760 }
761 void ServerList_ShowFull_Click(entity box, entity me)
762 {
763         box.setChecked(box, me.filterShowFull = !me.filterShowFull);
764         me.refreshServerList(me, 0);
765
766         me.ipAddressBox.setText(me.ipAddressBox, "");
767         me.ipAddressBox.cursorPos = 0;
768         me.ipAddressBoxFocused = -1;
769 }
770 void XonoticServerList_setSortOrder(entity me, float fld, float direction)
771 {
772         if(me.currentSortField == fld)
773                 direction = -me.currentSortOrder;
774         me.currentSortOrder = direction;
775         me.currentSortField = fld;
776         me.sortButton1.forcePressed = (fld == SLIST_FIELD_PING);
777         me.sortButton2.forcePressed = (fld == SLIST_FIELD_NAME);
778         me.sortButton3.forcePressed = (fld == SLIST_FIELD_MAP);
779         me.sortButton4.forcePressed = 0;
780         me.sortButton5.forcePressed = (fld == SLIST_FIELD_NUMHUMANS);
781         me.selectedItem = 0;
782         if(me.selectedServer)
783                 strunzone(me.selectedServer);
784         me.selectedServer = string_null;
785         me.refreshServerList(me, 0);
786 }
787 void XonoticServerList_positionSortButton(entity me, entity btn, float theOrigin, float theSize, string theTitle, void(entity, entity) theFunc)
788 {
789         vector originInLBSpace, sizeInLBSpace;
790         originInLBSpace = eY * (-me.itemHeight);
791         sizeInLBSpace = eY * me.itemHeight + eX * (1 - me.controlWidth);
792
793         vector originInDialogSpace, sizeInDialogSpace;
794         originInDialogSpace = boxToGlobal(originInLBSpace, me.Container_origin, me.Container_size);
795         sizeInDialogSpace = boxToGlobalSize(sizeInLBSpace, me.Container_size);
796
797         btn.Container_origin_x = originInDialogSpace_x + sizeInDialogSpace_x * theOrigin;
798         btn.Container_size_x   =                         sizeInDialogSpace_x * theSize;
799         btn.setText(btn, theTitle);
800         btn.onClick = theFunc;
801         btn.onClickEntity = me;
802         btn.resized = 1;
803 }
804 void XonoticServerList_resizeNotify(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
805 {
806         SUPER(XonoticServerList).resizeNotify(me, relOrigin, relSize, absOrigin, absSize);
807
808         me.realFontSize_y = me.fontSize / (absSize_y * me.itemHeight);
809         me.realFontSize_x = me.fontSize / (absSize_x * (1 - me.controlWidth));
810         me.realUpperMargin = 0.5 * (1 - me.realFontSize_y);
811
812         me.columnIconsOrigin = 0;
813         me.columnIconsSize = me.realFontSize_x * 4 * me.iconsSizeFactor;
814         me.columnPingSize = me.realFontSize_x * 3;
815         me.columnMapSize = me.realFontSize_x * 10;
816         me.columnTypeSize = me.realFontSize_x * 4;
817         me.columnPlayersSize = me.realFontSize_x * 5;
818         me.columnNameSize = 1 - me.columnPlayersSize - me.columnMapSize - me.columnPingSize - me.columnIconsSize - me.columnTypeSize - 5 * me.realFontSize_x;
819         me.columnPingOrigin = me.columnIconsOrigin + me.columnIconsSize + me.realFontSize_x;
820         me.columnNameOrigin = me.columnPingOrigin + me.columnPingSize + me.realFontSize_x;
821         me.columnMapOrigin = me.columnNameOrigin + me.columnNameSize + me.realFontSize_x;
822         me.columnTypeOrigin = me.columnMapOrigin + me.columnMapSize + me.realFontSize_x;
823         me.columnPlayersOrigin = me.columnTypeOrigin + me.columnTypeSize + me.realFontSize_x;
824
825         me.positionSortButton(me, me.sortButton1, me.columnPingOrigin, me.columnPingSize, _("Ping"), ServerList_PingSort_Click);
826         me.positionSortButton(me, me.sortButton2, me.columnNameOrigin, me.columnNameSize, _("Host name"), ServerList_NameSort_Click);
827         me.positionSortButton(me, me.sortButton3, me.columnMapOrigin, me.columnMapSize, _("Map"), ServerList_MapSort_Click);
828         me.positionSortButton(me, me.sortButton4, me.columnTypeOrigin, me.columnTypeSize, _("Type"), ServerList_TypeSort_Click);
829         me.positionSortButton(me, me.sortButton5, me.columnPlayersOrigin, me.columnPlayersSize, _("Players"), ServerList_PlayerSort_Click);
830
831         float f;
832         f = me.currentSortField;
833         if(f >= 0)
834         {
835                 me.currentSortField = -1;
836                 me.setSortOrder(me, f, me.currentSortOrder); // force resetting the sort order
837         }
838 }
839 void ServerList_Connect_Click(entity btn, entity me)
840 {
841         if(me.ipAddressBox.text == "")
842                 localcmd("connect ", me.selectedServer, "\n");
843         else
844                 localcmd("connect ", me.ipAddressBox.text, "\n");
845 }
846 void ServerList_Favorite_Click(entity btn, entity me)
847 {
848         string ipstr;
849         ipstr = netaddress_resolve(me.ipAddressBox.text, 26000);
850         if(ipstr != "")
851         {
852                 ToggleFavorite(me.ipAddressBox.text);
853                 me.ipAddressBoxFocused = -1;
854         }
855 }
856 void ServerList_Info_Click(entity btn, entity me)
857 {
858         main.serverInfoDialog.loadServerInfo(main.serverInfoDialog, XonoticServerList_MapItems(me.selectedItem));
859         DialogOpenButton_Click(me, main.serverInfoDialog);
860 }
861 void XonoticServerList_clickListBoxItem(entity me, float i, vector where)
862 {
863         float num = XonoticServerList_MapItems(i);
864         if(num == me.lastClickedServer)
865                 if(time < me.lastClickedTime + 0.3)
866                 {
867                         // DOUBLE CLICK!
868                         ServerList_Connect_Click(NULL, me);
869                 }
870         me.lastClickedServer = num;
871         me.lastClickedTime = time;
872 }
873 void XonoticServerList_drawListBoxItem(entity me, float i, vector absSize, float isSelected)
874 {
875         // layout: Ping, Server name, Map name, NP, TP, MP
876         float p, q;
877         float isv4, isv6;
878         vector theColor;
879         float theAlpha;
880         float m, pure, freeslots, j, sflags;
881         string s, typestr, versionstr, k, v, modname;
882
883         float cache = XonoticServerList_MapItems(i);
884         //print(sprintf("time: %f, i: %d, cache: %d, nitems: %d\n", time, i, cache, me.nItems));
885         
886         if(cache < 0)
887         {
888                 entity catent = Get_Cat_Ent(-cache);
889                 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; }
890         }
891         
892         if(isSelected)
893                 draw_Fill('0 0 0', '1 1 0', SKINCOLOR_LISTBOX_SELECTED, SKINALPHA_LISTBOX_SELECTED);
894
895         s = gethostcachestring(SLIST_FIELD_QCSTATUS, cache);
896         m = tokenizebyseparator(s, ":");
897         typestr = "";
898         if(m >= 2)
899         {
900                 typestr = argv(0);
901                 versionstr = argv(1);
902         }
903         freeslots = -1;
904         sflags = -1;
905         modname = "";
906         pure = 0;
907         for(j = 2; j < m; ++j)
908         {
909                 if(argv(j) == "")
910                         break;
911                 k = substring(argv(j), 0, 1);
912                 v = substring(argv(j), 1, -1);
913                 if(k == "P")
914                         pure = stof(v);
915                 else if(k == "S")
916                         freeslots = stof(v);
917                 else if(k == "F")
918                         sflags = stof(v);
919                 else if(k == "M")
920                         modname = v;
921         }
922
923 #ifdef COMPAT_NO_MOD_IS_XONOTIC
924         if(modname == "")
925                 modname = "Xonotic";
926 #endif
927
928         /*
929         SLIST_FIELD_MOD = gethostcacheindexforkey("mod");
930         s = gethostcachestring(SLIST_FIELD_MOD, cache);
931         if(s != "data")
932                 if(modname == "Xonotic")
933                         modname = s;
934         */
935
936         // list the mods here on which the pure server check actually works
937         if(modname != "Xonotic")
938         if(modname != "MinstaGib")
939         if(modname != "CTS")
940         if(modname != "NIX")
941         if(modname != "NewToys")
942                 pure = 0;
943
944         if(gethostcachenumber(SLIST_FIELD_FREESLOTS, cache) <= 0)
945                 theAlpha = SKINALPHA_SERVERLIST_FULL;
946         else if(freeslots == 0)
947                 theAlpha = SKINALPHA_SERVERLIST_FULL; // g_maxplayers support
948         else if not(gethostcachenumber(SLIST_FIELD_NUMHUMANS, cache))
949                 theAlpha = SKINALPHA_SERVERLIST_EMPTY;
950         else
951                 theAlpha = 1;
952
953         p = gethostcachenumber(SLIST_FIELD_PING, cache);
954 #define PING_LOW 75
955 #define PING_MED 200
956 #define PING_HIGH 500
957         if(p < PING_LOW)
958                 theColor = SKINCOLOR_SERVERLIST_LOWPING + (SKINCOLOR_SERVERLIST_MEDPING - SKINCOLOR_SERVERLIST_LOWPING) * (p / PING_LOW);
959         else if(p < PING_MED)
960                 theColor = SKINCOLOR_SERVERLIST_MEDPING + (SKINCOLOR_SERVERLIST_HIGHPING - SKINCOLOR_SERVERLIST_MEDPING) * ((p - PING_LOW) / (PING_MED - PING_LOW));
961         else if(p < PING_HIGH)
962         {
963                 theColor = SKINCOLOR_SERVERLIST_HIGHPING;
964                 theAlpha *= 1 + (SKINALPHA_SERVERLIST_HIGHPING - 1) * ((p - PING_MED) / (PING_HIGH - PING_MED));
965         }
966         else
967         {
968                 theColor = eX;
969                 theAlpha *= SKINALPHA_SERVERLIST_HIGHPING;
970         }
971
972         if(gethostcachenumber(SLIST_FIELD_ISFAVORITE, cache))
973         {
974                 theColor = theColor * (1 - SKINALPHA_SERVERLIST_FAVORITE) + SKINCOLOR_SERVERLIST_FAVORITE * SKINALPHA_SERVERLIST_FAVORITE;
975                 theAlpha = theAlpha * (1 - SKINALPHA_SERVERLIST_FAVORITE) + SKINALPHA_SERVERLIST_FAVORITE;
976         }
977
978         s = gethostcachestring(SLIST_FIELD_CNAME, cache);
979
980         isv4 = isv6 = 0;
981         if(substring(s, 0, 1) == "[")
982         {
983                 isv6 = 1;
984                 me.seenIPv6 += 1;
985         }
986         else if(strstrofs("0123456789", substring(s, 0, 1), 0) >= 0)
987         {
988                 isv4 = 1;
989                 me.seenIPv4 += 1;
990         }
991
992         q = stof(substring(crypto_getencryptlevel(s), 0, 1));
993         if((q <= 0 && cvar("crypto_aeslevel") >= 3) || (q >= 3 && cvar("crypto_aeslevel") <= 0))
994         {
995                 theColor = SKINCOLOR_SERVERLIST_IMPOSSIBLE;
996                 theAlpha = SKINALPHA_SERVERLIST_IMPOSSIBLE;
997         }
998
999         if(q == 1)
1000         {
1001                 if(cvar("crypto_aeslevel") >= 2)
1002                         q |= 4;
1003         }
1004         if(q == 2)
1005         {
1006                 if(cvar("crypto_aeslevel") >= 1)
1007                         q |= 4;
1008         }
1009         if(q == 3)
1010                 q = 5;
1011         else if(q >= 3)
1012                 q -= 2;
1013         // possible status:
1014         // 0: crypto off
1015         // 1: AES possible
1016         // 2: AES recommended but not available
1017         // 3: AES possible and will be used
1018         // 4: AES recommended and will be used
1019         // 5: AES required
1020
1021         {
1022                 vector iconSize = '0 0 0';
1023                 iconSize_y = me.realFontSize_y * me.iconsSizeFactor;
1024                 iconSize_x = me.realFontSize_x * me.iconsSizeFactor;
1025
1026                 vector iconPos = '0 0 0';
1027                 iconPos_x = (me.columnIconsSize - 3 * iconSize_x) * 0.5;
1028                 iconPos_y = (1 - iconSize_y) * 0.5;
1029
1030                 string n;
1031
1032                 if not(me.seenIPv4 && me.seenIPv6)
1033                 {
1034                         iconPos_x += iconSize_x * 0.5;
1035                 }
1036                 else if(me.seenIPv4 && me.seenIPv6)
1037                 {
1038                         n = string_null;
1039                         if(isv6)
1040                                 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_ipv6"), 0); // PRECACHE_PIC_MIPMAP
1041                         else if(isv4)
1042                                 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_ipv4"), 0); // PRECACHE_PIC_MIPMAP
1043                         if(n)
1044                                 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1045                         iconPos_x += iconSize_x;
1046                 }
1047
1048                 if(q > 0)
1049                 {
1050                         draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_aeslevel", ftos(q)), 0); // PRECACHE_PIC_MIPMAP
1051                         draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1052                 }
1053                 iconPos_x += iconSize_x;
1054
1055                 if(modname == "Xonotic")
1056                 {
1057                         if(pure == 0)
1058                         {
1059                                 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_pure1"), PRECACHE_PIC_MIPMAP);
1060                                 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1061                         }
1062                 }
1063                 else
1064                 {
1065                         draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_mod_", modname), PRECACHE_PIC_MIPMAP);
1066                         if(draw_PictureSize(n) == '0 0 0')
1067                                 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_mod_"), PRECACHE_PIC_MIPMAP);
1068                         if(pure == 0)
1069                                 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1070                         else
1071                                 draw_Picture(iconPos, n, iconSize, '1 1 1', SKINALPHA_SERVERLIST_ICON_NONPURE);
1072                 }
1073                 iconPos_x += iconSize_x;
1074
1075                 if(sflags >= 0 && (sflags & SERVERFLAG_PLAYERSTATS))
1076                 {
1077                         draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_stats1"), 0); // PRECACHE_PIC_MIPMAP
1078                         draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1079                 }
1080                 iconPos_x += iconSize_x;
1081         }
1082
1083         s = ftos(p);
1084         draw_Text(me.realUpperMargin * eY + (me.columnPingOrigin + me.columnPingSize - draw_TextWidth(s, 0, me.realFontSize)) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1085         s = draw_TextShortenToWidth(gethostcachestring(SLIST_FIELD_NAME, cache), me.columnNameSize, 0, me.realFontSize);
1086         draw_Text(me.realUpperMargin * eY + me.columnNameOrigin * eX, s, me.realFontSize, theColor, theAlpha, 0);
1087         s = draw_TextShortenToWidth(gethostcachestring(SLIST_FIELD_MAP, cache), me.columnMapSize, 0, me.realFontSize);
1088         draw_Text(me.realUpperMargin * eY + (me.columnMapOrigin + (me.columnMapSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1089         s = draw_TextShortenToWidth(typestr, me.columnTypeSize, 0, me.realFontSize);
1090         draw_Text(me.realUpperMargin * eY + (me.columnTypeOrigin + (me.columnTypeSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1091         s = strcat(ftos(gethostcachenumber(SLIST_FIELD_NUMHUMANS, cache)), "/", ftos(gethostcachenumber(SLIST_FIELD_MAXPLAYERS, cache)));
1092         draw_Text(me.realUpperMargin * eY + (me.columnPlayersOrigin + (me.columnPlayersSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1093 }
1094
1095 float XonoticServerList_keyDown(entity me, float scan, float ascii, float shift)
1096 {
1097         float i = XonoticServerList_MapItems(me.selectedItem);
1098         vector org, sz;
1099
1100         org = boxToGlobal(eY * (me.selectedItem * me.itemHeight - me.scrollPos), me.origin, me.size);
1101         sz = boxToGlobalSize(eY * me.itemHeight + eX * (1 - me.controlWidth), me.size);
1102
1103         if(scan == K_ENTER || scan == K_KP_ENTER)
1104         {
1105                 ServerList_Connect_Click(NULL, me);
1106                 return 1;
1107         }
1108         else if(scan == K_MOUSE2 || scan == K_SPACE)
1109         {
1110                 if((me.nItems != 0) && (i >= 0))
1111                 {
1112                         main.serverInfoDialog.loadServerInfo(main.serverInfoDialog, i);
1113                         DialogOpenButton_Click_withCoords(me, main.serverInfoDialog, org, sz);
1114                         return 1;
1115                 }
1116                 return 0;
1117         }
1118         else if(scan == K_INS || scan == K_MOUSE3 || scan == K_KP_INS)
1119         {
1120                 if((me.nItems != 0) && (i >= 0))
1121                 {
1122                         ToggleFavorite(me.selectedServer);
1123                         me.ipAddressBoxFocused = -1;
1124                         return 1;
1125                 }
1126                 return 0;
1127         }
1128         else if(SUPER(XonoticServerList).keyDown(me, scan, ascii, shift))
1129                 return 1;
1130         else if(!me.controlledTextbox)
1131                 return 0;
1132         else
1133                 return me.controlledTextbox.keyDown(me.controlledTextbox, scan, ascii, shift);
1134 }
1135 #endif