]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/menu/xonotic/serverlist.c
More cleanup, add todo for sanity check
[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 ToggleFavorite(string srv)
338 {
339         string s, s0, s1, s2, srv_resolved, p;
340         float i, n, f;
341         srv_resolved = netaddress_resolve(srv, 26000);
342         p = crypto_getidfp(srv_resolved);
343         s = cvar_string("net_slist_favorites");
344         n = tokenize_console(s);
345         f = 0;
346         for(i = 0; i < n; ++i)
347         {
348                 if(substring(argv(i), 0, 1) != "[" && strlen(argv(i)) == 44 && strstrofs(argv(i), ".", 0) < 0)
349                 {
350                         if(p)
351                                 if(argv(i) != p)
352                                         continue;
353                 }
354                 else
355                 {
356                         if(srv_resolved != netaddress_resolve(argv(i), 26000))
357                                 continue;
358                 }
359                 s0 = s1 = s2 = "";
360                 if(i > 0)
361                         s0 = substring(s, 0, argv_end_index(i - 1));
362                 if(i < n-1)
363                         s2 = substring(s, argv_start_index(i + 1), -1);
364                 if(s0 != "" && s2 != "")
365                         s1 = " ";
366                 cvar_set("net_slist_favorites", strcat(s0, s1, s2));
367                 s = cvar_string("net_slist_favorites");
368                 n = tokenize_console(s);
369                 f = 1;
370                 --i;
371         }
372         
373         if(!f)
374         {
375                 s1 = "";
376                 if(s != "")
377                         s1 = " ";
378                 if(p)
379                         cvar_set("net_slist_favorites", strcat(s, s1, p));
380                 else
381                         cvar_set("net_slist_favorites", strcat(s, s1, srv));
382         }
383
384         resorthostcache();
385 }
386
387 void ServerList_Update_favoriteButton(entity btn, entity me)
388 {
389         if(IsFavorite(me.ipAddressBox.text))
390                 me.favoriteButton.setText(me.favoriteButton, _("Remove"));
391         else
392                 me.favoriteButton.setText(me.favoriteButton, _("Bookmark"));
393 }
394
395 entity makeXonoticServerList()
396 {
397         entity me;
398         me = spawnXonoticServerList();
399         me.configureXonoticServerList(me);
400         return me;
401 }
402 void XonoticServerList_configureXonoticServerList(entity me)
403 {
404         me.configureXonoticListBox(me);
405
406         // update field ID's
407         #define SLIST_FIELD(suffix,name) SLIST_FIELD_##suffix = gethostcacheindexforkey(name);
408         SLIST_FIELDS
409         #undef SLIST_FIELD
410
411         // clear list
412         me.nItems = 0;
413 }
414 void XonoticServerList_setSelected(entity me, float i)
415 {
416         float save, num;
417         save = me.selectedItem;
418         SUPER(XonoticServerList).setSelected(me, i);
419         /*
420         if(me.selectedItem == save)
421                 return;
422         */
423         if(me.nItems == 0)
424                 return;
425
426         //if(XonoticServerList_MapItems(gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT)) != me.nItems)
427         //      { error("^1XonoticServerList_setSelected(); ERROR: ^7Host cache viewcount mismatches nItems!\n"); return; } // sorry, it would be wrong
428         // todo: make this work somehow? ^
429
430         num = XonoticServerList_MapItems(me.selectedItem);
431         if(num >= 0)
432         {
433                 if(me.selectedServer)
434                         strunzone(me.selectedServer);
435                 me.selectedServer = strzone(gethostcachestring(SLIST_FIELD_CNAME, num));
436
437                 me.ipAddressBox.setText(me.ipAddressBox, me.selectedServer);
438                 me.ipAddressBox.cursorPos = strlen(me.selectedServer);
439                 me.ipAddressBoxFocused = -1;
440         }
441 }
442 void XonoticServerList_refreshServerList(entity me, float mode)
443 {
444         // 0: just reparametrize
445         // 1: also ask for new servers
446         // 2: clear
447         //print("refresh of type ", ftos(mode), "\n");
448         /* if(mode == 2) // borken
449         {
450                 // clear list
451                 localcmd("net_slist\n");
452                 me.needsRefresh = 1; // net_slist kills sort order, so we need to restore it later
453         }
454         else */
455         
456         float m, i, n;
457         float listflags = 0;
458         string s, typestr, modstr;
459         s = me.filterString;
460
461         m = strstrofs(s, ":", 0);
462         if(m >= 0)
463         {
464                 typestr = substring(s, 0, m);
465                 s = substring(s, m + 1, strlen(s) - m - 1);
466                 while(substring(s, 0, 1) == " ")
467                         s = substring(s, 1, strlen(s) - 1);
468         }
469         else
470                 typestr = "";
471
472         modstr = cvar_string("menu_slist_modfilter");
473
474         m = SLIST_MASK_AND - 1;
475         resethostcachemasks();
476
477         // ping: reject negative ping (no idea why this happens in the first place, engine bug)
478         sethostcachemasknumber(++m, SLIST_FIELD_PING, 0, SLIST_TEST_GREATEREQUAL);
479
480         // show full button
481         if(!me.filterShowFull)
482         {
483                 sethostcachemasknumber(++m, SLIST_FIELD_FREESLOTS, 1, SLIST_TEST_GREATEREQUAL); // legacy
484                 sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, ":S0:", SLIST_TEST_NOTCONTAIN); // g_maxplayers support
485         }
486
487         // show empty button
488         if(!me.filterShowEmpty)
489                 sethostcachemasknumber(++m, SLIST_FIELD_NUMHUMANS, 1, SLIST_TEST_GREATEREQUAL);
490
491         // gametype filtering
492         if(typestr != "")
493                 sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, strcat(typestr, ":"), SLIST_TEST_STARTSWITH);
494
495         // mod filtering
496         if(modstr != "")
497         {
498                 if(substring(modstr, 0, 1) == "!")
499                         sethostcachemaskstring(++m, SLIST_FIELD_MOD, resolvemod(substring(modstr, 1, strlen(modstr) - 1)), SLIST_TEST_NOTEQUAL);
500                 else
501                         sethostcachemaskstring(++m, SLIST_FIELD_MOD, resolvemod(modstr), SLIST_TEST_EQUAL);
502         }
503
504         // server banning
505         n = tokenizebyseparator(_Nex_ExtResponseSystem_BannedServers, " ");
506         for(i = 0; i < n; ++i)
507                 if(argv(i) != "")
508                         sethostcachemaskstring(++m, SLIST_FIELD_CNAME, argv(i), SLIST_TEST_NOTSTARTSWITH);
509
510         m = SLIST_MASK_OR - 1;
511         if(s != "")
512         {
513                 sethostcachemaskstring(++m, SLIST_FIELD_NAME, s, SLIST_TEST_CONTAINS);
514                 sethostcachemaskstring(++m, SLIST_FIELD_MAP, s, SLIST_TEST_CONTAINS);
515                 sethostcachemaskstring(++m, SLIST_FIELD_PLAYERS, s, SLIST_TEST_CONTAINS);
516                 sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, strcat(s, ":"), SLIST_TEST_STARTSWITH);
517         }
518
519         // sorting flags
520         //listflags |= SLSF_FAVORITES;
521         listflags |= SLSF_CATEGORIES;
522         if(me.currentSortOrder < 0) { listflags |= SLSF_DESCENDING; }
523         sethostcachesort(me.currentSortField, listflags);
524         
525         resorthostcache();
526         if(mode >= 1) { refreshhostcache(); }
527 }
528 void XonoticServerList_focusEnter(entity me)
529 {
530         if(time < me.nextRefreshTime)
531         {
532                 //print("sorry, no refresh yet\n");
533                 return;
534         }
535         me.nextRefreshTime = time + 10;
536         me.refreshServerList(me, 1);
537 }
538
539 void XonoticServerList_draw(entity me)
540 {
541         float i, found, owned, num;
542
543         if(_Nex_ExtResponseSystem_BannedServersNeedsRefresh)
544         {
545                 if(!me.needsRefresh)
546                         me.needsRefresh = 2;
547                 _Nex_ExtResponseSystem_BannedServersNeedsRefresh = 0;
548         }
549
550         if(me.currentSortField == -1)
551         {
552                 me.setSortOrder(me, SLIST_FIELD_PING, +1);
553                 me.refreshServerList(me, 2);
554         }
555         else if(me.needsRefresh == 1)
556         {
557                 me.needsRefresh = 2; // delay by one frame to make sure "slist" has been executed
558         }
559         else if(me.needsRefresh == 2)
560         {
561                 me.needsRefresh = 0;
562                 me.refreshServerList(me, 0);
563         }
564
565         owned = ((me.selectedServer == me.ipAddressBox.text) && (me.ipAddressBox.text != ""));
566
567         for(i = 0; i < totcat; ++i) { category_name[i] = -1; category_item[i] = -1; }
568         totcat = 0;
569
570         float itemcount = gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT);
571         //float visible = floor(me.scrollPos / me.itemHeight);
572         me.nItems = itemcount;
573
574         float cat, x;
575         for(i = 0; i < itemcount; ++i)
576         {
577                 cat = gethostcachenumber(SLIST_FIELD_CATEGORY, i);
578                 if(cat)
579                 {
580                         if(totcat == 0)
581                         {
582                                 category_name[totcat] = cat;
583                                 category_item[totcat] = i;
584                                 ++totcat;
585                                 ++me.nItems;
586                         }
587                         else
588                         {
589                                 found = 0;
590                                 for(x = 0; x < totcat; ++x) { if(cat == category_name[x]) { found = 1; } }
591                                 if not(found)
592                                 {
593                                         category_name[totcat] = cat;
594                                         category_item[totcat] = i;
595                                         ++totcat;
596                                         ++me.nItems;
597                                 }
598                         }
599                 }
600         }
601
602         //print(sprintf("^1SERVERLIST_DRAW^7: servercount: %d, nitems: %d\n", itemcount, me.nItems));
603
604         me.connectButton.disabled = ((me.nItems == 0) && (me.ipAddressBox.text == ""));
605         me.infoButton.disabled = ((me.nItems == 0) || !owned);
606         me.favoriteButton.disabled = ((me.nItems == 0) && (me.ipAddressBox.text == ""));
607
608         found = 0;
609         if(me.selectedServer)
610         {
611                 for(i = 0; i < me.nItems; ++i)
612                 {
613                         num = XonoticServerList_MapItems(i);
614                         if(num >= 0)
615                         {
616                                 if(gethostcachestring(SLIST_FIELD_CNAME, num) == me.selectedServer)
617                                 {
618                                         if(i != me.selectedItem)
619                                         {
620                                                 me.lastClickedServer = -1;
621                                                 me.selectedItem = i;
622                                         }
623                                         found = 1;
624                                         break;
625                                 }
626                         }
627                 }
628         }
629         if(!found)
630         {
631                 if(me.nItems > 0)
632                 {
633                         if(me.selectedItem >= me.nItems) { me.selectedItem = me.nItems - 1; }
634                         if(me.selectedServer) { strunzone(me.selectedServer); }
635
636                         num = XonoticServerList_MapItems(me.selectedItem);
637                         if(num >= 0) { me.selectedServer = strzone(gethostcachestring(SLIST_FIELD_CNAME, num)); }
638                 }
639         }
640         
641         if(owned)
642         {
643                 if(me.selectedServer != me.ipAddressBox.text)
644                 {
645                         me.ipAddressBox.setText(me.ipAddressBox, me.selectedServer);
646                         me.ipAddressBox.cursorPos = strlen(me.selectedServer);
647                         me.ipAddressBoxFocused = -1;
648                 }
649         }
650
651         if(me.ipAddressBoxFocused != me.ipAddressBox.focused)
652         {
653                 if(me.ipAddressBox.focused || me.ipAddressBoxFocused < 0)
654                         ServerList_Update_favoriteButton(NULL, me);
655                 me.ipAddressBoxFocused = me.ipAddressBox.focused;
656         }
657
658         SUPER(XonoticServerList).draw(me);
659 }
660 void ServerList_PingSort_Click(entity btn, entity me)
661 {
662         me.setSortOrder(me, SLIST_FIELD_PING, +1);
663 }
664 void ServerList_NameSort_Click(entity btn, entity me)
665 {
666         me.setSortOrder(me, SLIST_FIELD_NAME, -1); // why?
667 }
668 void ServerList_MapSort_Click(entity btn, entity me)
669 {
670         me.setSortOrder(me, SLIST_FIELD_MAP, -1); // why?
671 }
672 void ServerList_PlayerSort_Click(entity btn, entity me)
673 {
674         me.setSortOrder(me, SLIST_FIELD_NUMHUMANS, -1);
675 }
676 void ServerList_TypeSort_Click(entity btn, entity me)
677 {
678         string s, t;
679         float i, m;
680         s = me.filterString;
681         m = strstrofs(s, ":", 0);
682         if(m >= 0)
683         {
684                 s = substring(s, 0, m);
685                 while(substring(s, m+1, 1) == " ") // skip spaces
686                         ++m;
687         }
688         else
689                 s = "";
690
691         for(i = 1; ; i *= 2) // 20 modes ought to be enough for anyone
692         {
693                 t = MapInfo_Type_ToString(i);
694                 if(i > 1)
695                         if(t == "") // it repeats (default case)
696                         {
697                                 // no type was found
698                                 // choose the first one
699                                 s = MapInfo_Type_ToString(1);
700                                 break;
701                         }
702                 if(s == t)
703                 {
704                         // the type was found
705                         // choose the next one
706                         s = MapInfo_Type_ToString(i * 2);
707                         if(s == "")
708                                 s = MapInfo_Type_ToString(1);
709                         break;
710                 }
711         }
712
713         if(s != "")
714                 s = strcat(s, ":");
715         s = strcat(s, substring(me.filterString, m+1, strlen(me.filterString) - m - 1));
716
717         me.controlledTextbox.setText(me.controlledTextbox, s);
718         me.controlledTextbox.keyDown(me.controlledTextbox, K_END, 0, 0);
719         me.controlledTextbox.keyUp(me.controlledTextbox, K_END, 0, 0);
720         //ServerList_Filter_Change(me.controlledTextbox, me);
721 }
722 void ServerList_Filter_Change(entity box, entity me)
723 {
724         if(me.filterString)
725                 strunzone(me.filterString);
726         if(box.text != "")
727                 me.filterString = strzone(box.text);
728         else
729                 me.filterString = string_null;
730         me.refreshServerList(me, 0);
731
732         me.ipAddressBox.setText(me.ipAddressBox, "");
733         me.ipAddressBox.cursorPos = 0;
734         me.ipAddressBoxFocused = -1;
735 }
736 void ServerList_ShowEmpty_Click(entity box, entity me)
737 {
738         box.setChecked(box, me.filterShowEmpty = !me.filterShowEmpty);
739         me.refreshServerList(me, 0);
740
741         me.ipAddressBox.setText(me.ipAddressBox, "");
742         me.ipAddressBox.cursorPos = 0;
743         me.ipAddressBoxFocused = -1;
744 }
745 void ServerList_ShowFull_Click(entity box, entity me)
746 {
747         box.setChecked(box, me.filterShowFull = !me.filterShowFull);
748         me.refreshServerList(me, 0);
749
750         me.ipAddressBox.setText(me.ipAddressBox, "");
751         me.ipAddressBox.cursorPos = 0;
752         me.ipAddressBoxFocused = -1;
753 }
754 void XonoticServerList_setSortOrder(entity me, float fld, float direction)
755 {
756         if(me.currentSortField == fld)
757                 direction = -me.currentSortOrder;
758         me.currentSortOrder = direction;
759         me.currentSortField = fld;
760         me.sortButton1.forcePressed = (fld == SLIST_FIELD_PING);
761         me.sortButton2.forcePressed = (fld == SLIST_FIELD_NAME);
762         me.sortButton3.forcePressed = (fld == SLIST_FIELD_MAP);
763         me.sortButton4.forcePressed = 0;
764         me.sortButton5.forcePressed = (fld == SLIST_FIELD_NUMHUMANS);
765         me.selectedItem = 0;
766         if(me.selectedServer)
767                 strunzone(me.selectedServer);
768         me.selectedServer = string_null;
769         me.refreshServerList(me, 0);
770 }
771 void XonoticServerList_positionSortButton(entity me, entity btn, float theOrigin, float theSize, string theTitle, void(entity, entity) theFunc)
772 {
773         vector originInLBSpace, sizeInLBSpace;
774         originInLBSpace = eY * (-me.itemHeight);
775         sizeInLBSpace = eY * me.itemHeight + eX * (1 - me.controlWidth);
776
777         vector originInDialogSpace, sizeInDialogSpace;
778         originInDialogSpace = boxToGlobal(originInLBSpace, me.Container_origin, me.Container_size);
779         sizeInDialogSpace = boxToGlobalSize(sizeInLBSpace, me.Container_size);
780
781         btn.Container_origin_x = originInDialogSpace_x + sizeInDialogSpace_x * theOrigin;
782         btn.Container_size_x   =                         sizeInDialogSpace_x * theSize;
783         btn.setText(btn, theTitle);
784         btn.onClick = theFunc;
785         btn.onClickEntity = me;
786         btn.resized = 1;
787 }
788 void XonoticServerList_resizeNotify(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
789 {
790         SUPER(XonoticServerList).resizeNotify(me, relOrigin, relSize, absOrigin, absSize);
791
792         me.realFontSize_y = me.fontSize / (absSize_y * me.itemHeight);
793         me.realFontSize_x = me.fontSize / (absSize_x * (1 - me.controlWidth));
794         me.realUpperMargin = 0.5 * (1 - me.realFontSize_y);
795
796         me.columnIconsOrigin = 0;
797         me.columnIconsSize = me.realFontSize_x * 4 * me.iconsSizeFactor;
798         me.columnPingSize = me.realFontSize_x * 3;
799         me.columnMapSize = me.realFontSize_x * 10;
800         me.columnTypeSize = me.realFontSize_x * 4;
801         me.columnPlayersSize = me.realFontSize_x * 5;
802         me.columnNameSize = 1 - me.columnPlayersSize - me.columnMapSize - me.columnPingSize - me.columnIconsSize - me.columnTypeSize - 5 * me.realFontSize_x;
803         me.columnPingOrigin = me.columnIconsOrigin + me.columnIconsSize + me.realFontSize_x;
804         me.columnNameOrigin = me.columnPingOrigin + me.columnPingSize + me.realFontSize_x;
805         me.columnMapOrigin = me.columnNameOrigin + me.columnNameSize + me.realFontSize_x;
806         me.columnTypeOrigin = me.columnMapOrigin + me.columnMapSize + me.realFontSize_x;
807         me.columnPlayersOrigin = me.columnTypeOrigin + me.columnTypeSize + me.realFontSize_x;
808
809         me.positionSortButton(me, me.sortButton1, me.columnPingOrigin, me.columnPingSize, _("Ping"), ServerList_PingSort_Click);
810         me.positionSortButton(me, me.sortButton2, me.columnNameOrigin, me.columnNameSize, _("Host name"), ServerList_NameSort_Click);
811         me.positionSortButton(me, me.sortButton3, me.columnMapOrigin, me.columnMapSize, _("Map"), ServerList_MapSort_Click);
812         me.positionSortButton(me, me.sortButton4, me.columnTypeOrigin, me.columnTypeSize, _("Type"), ServerList_TypeSort_Click);
813         me.positionSortButton(me, me.sortButton5, me.columnPlayersOrigin, me.columnPlayersSize, _("Players"), ServerList_PlayerSort_Click);
814
815         float f;
816         f = me.currentSortField;
817         if(f >= 0)
818         {
819                 me.currentSortField = -1;
820                 me.setSortOrder(me, f, me.currentSortOrder); // force resetting the sort order
821         }
822 }
823 void ServerList_Connect_Click(entity btn, entity me)
824 {
825         if(me.ipAddressBox.text == "")
826                 localcmd("connect ", me.selectedServer, "\n");
827         else
828                 localcmd("connect ", me.ipAddressBox.text, "\n");
829 }
830 void ServerList_Favorite_Click(entity btn, entity me)
831 {
832         string ipstr;
833         ipstr = netaddress_resolve(me.ipAddressBox.text, 26000);
834         if(ipstr != "")
835         {
836                 ToggleFavorite(me.ipAddressBox.text);
837                 me.ipAddressBoxFocused = -1;
838         }
839 }
840 void ServerList_Info_Click(entity btn, entity me)
841 {
842         main.serverInfoDialog.loadServerInfo(main.serverInfoDialog, XonoticServerList_MapItems(me.selectedItem));
843         DialogOpenButton_Click(me, main.serverInfoDialog);
844 }
845 void XonoticServerList_clickListBoxItem(entity me, float i, vector where)
846 {
847         float num = XonoticServerList_MapItems(i);
848         if(num == me.lastClickedServer)
849                 if(time < me.lastClickedTime + 0.3)
850                 {
851                         // DOUBLE CLICK!
852                         ServerList_Connect_Click(NULL, me);
853                 }
854         me.lastClickedServer = num;
855         me.lastClickedTime = time;
856 }
857 void XonoticServerList_drawListBoxItem(entity me, float i, vector absSize, float isSelected)
858 {
859         // layout: Ping, Server name, Map name, NP, TP, MP
860         float p, q;
861         float isv4, isv6;
862         vector theColor;
863         float theAlpha;
864         float m, pure, freeslots, j, sflags;
865         string s, typestr, versionstr, k, v, modname;
866
867         float cache = XonoticServerList_MapItems(i);
868         //print(sprintf("time: %f, i: %d, cache: %d, nitems: %d\n", time, i, cache, me.nItems));
869         
870         if(cache < 0)
871         {
872                 entity catent = Get_Cat_Ent(-cache);
873                 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; }
874         }
875         
876         if(isSelected)
877                 draw_Fill('0 0 0', '1 1 0', SKINCOLOR_LISTBOX_SELECTED, SKINALPHA_LISTBOX_SELECTED);
878
879         s = gethostcachestring(SLIST_FIELD_QCSTATUS, cache);
880         m = tokenizebyseparator(s, ":");
881         typestr = "";
882         if(m >= 2)
883         {
884                 typestr = argv(0);
885                 versionstr = argv(1);
886         }
887         freeslots = -1;
888         sflags = -1;
889         modname = "";
890         pure = 0;
891         for(j = 2; j < m; ++j)
892         {
893                 if(argv(j) == "")
894                         break;
895                 k = substring(argv(j), 0, 1);
896                 v = substring(argv(j), 1, -1);
897                 if(k == "P")
898                         pure = stof(v);
899                 else if(k == "S")
900                         freeslots = stof(v);
901                 else if(k == "F")
902                         sflags = stof(v);
903                 else if(k == "M")
904                         modname = v;
905         }
906
907 #ifdef COMPAT_NO_MOD_IS_XONOTIC
908         if(modname == "")
909                 modname = "Xonotic";
910 #endif
911
912         /*
913         SLIST_FIELD_MOD = gethostcacheindexforkey("mod");
914         s = gethostcachestring(SLIST_FIELD_MOD, cache);
915         if(s != "data")
916                 if(modname == "Xonotic")
917                         modname = s;
918         */
919
920         // list the mods here on which the pure server check actually works
921         if(modname != "Xonotic")
922         if(modname != "MinstaGib")
923         if(modname != "CTS")
924         if(modname != "NIX")
925         if(modname != "NewToys")
926                 pure = 0;
927
928         if(gethostcachenumber(SLIST_FIELD_FREESLOTS, cache) <= 0)
929                 theAlpha = SKINALPHA_SERVERLIST_FULL;
930         else if(freeslots == 0)
931                 theAlpha = SKINALPHA_SERVERLIST_FULL; // g_maxplayers support
932         else if not(gethostcachenumber(SLIST_FIELD_NUMHUMANS, cache))
933                 theAlpha = SKINALPHA_SERVERLIST_EMPTY;
934         else
935                 theAlpha = 1;
936
937         p = gethostcachenumber(SLIST_FIELD_PING, cache);
938 #define PING_LOW 75
939 #define PING_MED 200
940 #define PING_HIGH 500
941         if(p < PING_LOW)
942                 theColor = SKINCOLOR_SERVERLIST_LOWPING + (SKINCOLOR_SERVERLIST_MEDPING - SKINCOLOR_SERVERLIST_LOWPING) * (p / PING_LOW);
943         else if(p < PING_MED)
944                 theColor = SKINCOLOR_SERVERLIST_MEDPING + (SKINCOLOR_SERVERLIST_HIGHPING - SKINCOLOR_SERVERLIST_MEDPING) * ((p - PING_LOW) / (PING_MED - PING_LOW));
945         else if(p < PING_HIGH)
946         {
947                 theColor = SKINCOLOR_SERVERLIST_HIGHPING;
948                 theAlpha *= 1 + (SKINALPHA_SERVERLIST_HIGHPING - 1) * ((p - PING_MED) / (PING_HIGH - PING_MED));
949         }
950         else
951         {
952                 theColor = eX;
953                 theAlpha *= SKINALPHA_SERVERLIST_HIGHPING;
954         }
955
956         if(gethostcachenumber(SLIST_FIELD_ISFAVORITE, cache))
957         {
958                 theColor = theColor * (1 - SKINALPHA_SERVERLIST_FAVORITE) + SKINCOLOR_SERVERLIST_FAVORITE * SKINALPHA_SERVERLIST_FAVORITE;
959                 theAlpha = theAlpha * (1 - SKINALPHA_SERVERLIST_FAVORITE) + SKINALPHA_SERVERLIST_FAVORITE;
960         }
961
962         s = gethostcachestring(SLIST_FIELD_CNAME, cache);
963
964         isv4 = isv6 = 0;
965         if(substring(s, 0, 1) == "[")
966         {
967                 isv6 = 1;
968                 me.seenIPv6 += 1;
969         }
970         else if(strstrofs("0123456789", substring(s, 0, 1), 0) >= 0)
971         {
972                 isv4 = 1;
973                 me.seenIPv4 += 1;
974         }
975
976         q = stof(substring(crypto_getencryptlevel(s), 0, 1));
977         if((q <= 0 && cvar("crypto_aeslevel") >= 3) || (q >= 3 && cvar("crypto_aeslevel") <= 0))
978         {
979                 theColor = SKINCOLOR_SERVERLIST_IMPOSSIBLE;
980                 theAlpha = SKINALPHA_SERVERLIST_IMPOSSIBLE;
981         }
982
983         if(q == 1)
984         {
985                 if(cvar("crypto_aeslevel") >= 2)
986                         q |= 4;
987         }
988         if(q == 2)
989         {
990                 if(cvar("crypto_aeslevel") >= 1)
991                         q |= 4;
992         }
993         if(q == 3)
994                 q = 5;
995         else if(q >= 3)
996                 q -= 2;
997         // possible status:
998         // 0: crypto off
999         // 1: AES possible
1000         // 2: AES recommended but not available
1001         // 3: AES possible and will be used
1002         // 4: AES recommended and will be used
1003         // 5: AES required
1004
1005         {
1006                 vector iconSize = '0 0 0';
1007                 iconSize_y = me.realFontSize_y * me.iconsSizeFactor;
1008                 iconSize_x = me.realFontSize_x * me.iconsSizeFactor;
1009
1010                 vector iconPos = '0 0 0';
1011                 iconPos_x = (me.columnIconsSize - 3 * iconSize_x) * 0.5;
1012                 iconPos_y = (1 - iconSize_y) * 0.5;
1013
1014                 string n;
1015
1016                 if not(me.seenIPv4 && me.seenIPv6)
1017                 {
1018                         iconPos_x += iconSize_x * 0.5;
1019                 }
1020                 else if(me.seenIPv4 && me.seenIPv6)
1021                 {
1022                         n = string_null;
1023                         if(isv6)
1024                                 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_ipv6"), 0); // PRECACHE_PIC_MIPMAP
1025                         else if(isv4)
1026                                 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_ipv4"), 0); // PRECACHE_PIC_MIPMAP
1027                         if(n)
1028                                 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1029                         iconPos_x += iconSize_x;
1030                 }
1031
1032                 if(q > 0)
1033                 {
1034                         draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_aeslevel", ftos(q)), 0); // PRECACHE_PIC_MIPMAP
1035                         draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1036                 }
1037                 iconPos_x += iconSize_x;
1038
1039                 if(modname == "Xonotic")
1040                 {
1041                         if(pure == 0)
1042                         {
1043                                 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_pure1"), PRECACHE_PIC_MIPMAP);
1044                                 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1045                         }
1046                 }
1047                 else
1048                 {
1049                         draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_mod_", modname), PRECACHE_PIC_MIPMAP);
1050                         if(draw_PictureSize(n) == '0 0 0')
1051                                 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_mod_"), PRECACHE_PIC_MIPMAP);
1052                         if(pure == 0)
1053                                 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1054                         else
1055                                 draw_Picture(iconPos, n, iconSize, '1 1 1', SKINALPHA_SERVERLIST_ICON_NONPURE);
1056                 }
1057                 iconPos_x += iconSize_x;
1058
1059                 if(sflags >= 0 && (sflags & SERVERFLAG_PLAYERSTATS))
1060                 {
1061                         draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_stats1"), 0); // PRECACHE_PIC_MIPMAP
1062                         draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1063                 }
1064                 iconPos_x += iconSize_x;
1065         }
1066
1067         s = ftos(p);
1068         draw_Text(me.realUpperMargin * eY + (me.columnPingOrigin + me.columnPingSize - draw_TextWidth(s, 0, me.realFontSize)) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1069         s = draw_TextShortenToWidth(gethostcachestring(SLIST_FIELD_NAME, cache), me.columnNameSize, 0, me.realFontSize);
1070         draw_Text(me.realUpperMargin * eY + me.columnNameOrigin * eX, s, me.realFontSize, theColor, theAlpha, 0);
1071         s = draw_TextShortenToWidth(gethostcachestring(SLIST_FIELD_MAP, cache), me.columnMapSize, 0, me.realFontSize);
1072         draw_Text(me.realUpperMargin * eY + (me.columnMapOrigin + (me.columnMapSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1073         s = draw_TextShortenToWidth(typestr, me.columnTypeSize, 0, me.realFontSize);
1074         draw_Text(me.realUpperMargin * eY + (me.columnTypeOrigin + (me.columnTypeSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1075         s = strcat(ftos(gethostcachenumber(SLIST_FIELD_NUMHUMANS, cache)), "/", ftos(gethostcachenumber(SLIST_FIELD_MAXPLAYERS, cache)));
1076         draw_Text(me.realUpperMargin * eY + (me.columnPlayersOrigin + (me.columnPlayersSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1077 }
1078
1079 float XonoticServerList_keyDown(entity me, float scan, float ascii, float shift)
1080 {
1081         float i = XonoticServerList_MapItems(me.selectedItem);
1082         vector org, sz;
1083
1084         org = boxToGlobal(eY * (me.selectedItem * me.itemHeight - me.scrollPos), me.origin, me.size);
1085         sz = boxToGlobalSize(eY * me.itemHeight + eX * (1 - me.controlWidth), me.size);
1086
1087         if(scan == K_ENTER || scan == K_KP_ENTER)
1088         {
1089                 ServerList_Connect_Click(NULL, me);
1090                 return 1;
1091         }
1092         else if(scan == K_MOUSE2 || scan == K_SPACE)
1093         {
1094                 if((me.nItems != 0) && (i >= 0))
1095                 {
1096                         main.serverInfoDialog.loadServerInfo(main.serverInfoDialog, i);
1097                         DialogOpenButton_Click_withCoords(me, main.serverInfoDialog, org, sz);
1098                         return 1;
1099                 }
1100                 return 0;
1101         }
1102         else if(scan == K_INS || scan == K_MOUSE3 || scan == K_KP_INS)
1103         {
1104                 if((me.nItems != 0) && (i >= 0))
1105                 {
1106                         ToggleFavorite(me.selectedServer);
1107                         me.ipAddressBoxFocused = -1;
1108                         return 1;
1109                 }
1110                 return 0;
1111         }
1112         else if(SUPER(XonoticServerList).keyDown(me, scan, ascii, shift))
1113                 return 1;
1114         else if(!me.controlledTextbox)
1115                 return 0;
1116         else
1117                 return me.controlledTextbox.keyDown(me.controlledTextbox, scan, ascii, shift);
1118 }
1119 #endif