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