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