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))
11 ATTRIB(XonoticServerList, iconsSizeFactor, float, 0.85)
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)
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)
54 ATTRIB(XonoticServerList, ipAddressBoxFocused, float, -1)
56 ATTRIB(XonoticServerList, seenIPv4, float, 0)
57 ATTRIB(XonoticServerList, seenIPv6, float, 0)
58 ENDCLASS(XonoticServerList)
59 entity makeXonoticServerList();
61 #ifndef IMPLEMENTATION
62 var float autocvar_menu_serverlist_categories = TRUE;
63 var float autocvar_menu_serverlist_categories_onlyifmultiple = TRUE;
64 var float autocvar_menu_serverlist_purethreshold = 10;
65 var string autocvar_menu_serverlist_recommended = "76.124.107.5:26004";
67 // server cache fields
68 #define SLIST_FIELDS \
69 SLIST_FIELD(CNAME, "cname") \
70 SLIST_FIELD(PING, "ping") \
71 SLIST_FIELD(GAME, "game") \
72 SLIST_FIELD(MOD, "mod") \
73 SLIST_FIELD(MAP, "map") \
74 SLIST_FIELD(NAME, "name") \
75 SLIST_FIELD(MAXPLAYERS, "maxplayers") \
76 SLIST_FIELD(NUMPLAYERS, "numplayers") \
77 SLIST_FIELD(NUMHUMANS, "numhumans") \
78 SLIST_FIELD(NUMBOTS, "numbots") \
79 SLIST_FIELD(PROTOCOL, "protocol") \
80 SLIST_FIELD(FREESLOTS, "freeslots") \
81 SLIST_FIELD(PLAYERS, "players") \
82 SLIST_FIELD(QCSTATUS, "qcstatus") \
83 SLIST_FIELD(CATEGORY, "category") \
84 SLIST_FIELD(ISFAVORITE, "isfavorite")
86 #define SLIST_FIELD(suffix,name) float SLIST_FIELD_##suffix;
91 float SLSF_DESCENDING = 1;
92 float SLSF_FAVORITES = 2;
93 float SLSF_CATEGORIES = 4;
95 float Get_Cat_Num_FromString(string input);
96 entity Get_Cat_Ent(float catnum);
98 float IsServerInList(string list, string srv);
99 #define IsFavorite(srv) IsServerInList(cvar_string("net_slist_favorites"), srv)
100 #define IsRecommended(srv) IsServerInList(cvar_string("menu_serverlist_recommended"), srv) // todo: use update notification instead of cvar
102 float CheckCategoryOverride(float cat);
103 float CheckCategoryForEntry(float entry);
104 float m_getserverlistentrycategory(float entry) { return CheckCategoryOverride(CheckCategoryForEntry(entry)); }
106 // fields for category entities
107 #define MAX_CATEGORIES 9
108 #define CATEGORY_FIRST 1
109 entity categories[MAX_CATEGORIES];
110 float category_ent_count;
113 .string cat_override_string;
116 // fields for drawing categories
117 float category_name[MAX_CATEGORIES];
118 float category_item[MAX_CATEGORIES];
119 float category_draw_count;
122 SLIST_CATEGORY(CAT_FAVORITED, "", "", _("Favorites")) \
123 SLIST_CATEGORY(CAT_RECOMMENDED, "", "CAT_SERVERS", _("Recommended")) \
124 SLIST_CATEGORY(CAT_NORMAL, "", "CAT_SERVERS", _("Normal Servers")) \
125 SLIST_CATEGORY(CAT_SERVERS, "CAT_NORMAL", "CAT_SERVERS", _("Servers")) \
126 SLIST_CATEGORY(CAT_XPM, "CAT_NORMAL", "CAT_SERVERS", _("Competitive Mode")) \
127 SLIST_CATEGORY(CAT_MODIFIED, "", "CAT_SERVERS", _("Modified Servers")) \
128 SLIST_CATEGORY(CAT_OVERKILL, "", "CAT_SERVERS", _("Overkill Mode")) \
129 SLIST_CATEGORY(CAT_MINSTAGIB, "", "CAT_SERVERS", _("MinstaGib Mode")) \
130 SLIST_CATEGORY(CAT_DEFRAG, "", "CAT_SERVERS", _("Defrag Mode"))
132 // C is stupid, must use extra macro for concatenation
133 #define SLIST_ADD_CAT_CVAR(name,default) var string autocvar_menu_serverlist_categories_##name##_override = default;
134 #define SLIST_CATEGORY(name,enoverride,deoverride,string) \
135 SLIST_ADD_CAT_CVAR(name, enoverride) \
137 void RegisterSLCategory_##name() \
139 SET_FIELD_COUNT(name, CATEGORY_FIRST, category_ent_count) \
140 CHECK_MAX_COUNT(name, MAX_CATEGORIES, category_ent_count, "SLIST_CATEGORY") \
141 entity cat = spawn(); \
142 categories[name - 1] = cat; \
143 cat.classname = "slist_category"; \
144 cat.cat_name = strzone(#name); \
145 cat.cat_override_string = strzone((autocvar_menu_serverlist_categories ? \
146 autocvar_menu_serverlist_categories_##name##_override \
149 cat.cat_string = strzone(string); \
151 ACCUMULATE_FUNCTION(RegisterSLCategories, RegisterSLCategory_##name);
155 void RegisterSLCategories_Done()
159 for(i = 0; i < category_ent_count; ++i)
161 s = categories[i].cat_override_string;
162 if((s != "") && (s != categories[i].cat_name))
164 catnum = Get_Cat_Num_FromString(s);
167 strunzone(categories[i].cat_override_string);
168 categories[i].cat_override = catnum;
172 strunzone(categories[i].cat_override_string);
173 categories[i].cat_override = 0;
176 ACCUMULATE_FUNCTION(RegisterSLCategories, RegisterSLCategories_Done);
178 #undef SLIST_ADD_CAT_CVAR
179 #undef SLIST_CATEGORY
182 void ServerList_Connect_Click(entity btn, entity me);
183 void ServerList_ShowEmpty_Click(entity box, entity me);
184 void ServerList_ShowFull_Click(entity box, entity me);
185 void ServerList_Filter_Change(entity box, entity me);
186 void ServerList_Favorite_Click(entity btn, entity me);
187 void ServerList_Info_Click(entity btn, entity me);
188 void ServerList_Update_favoriteButton(entity btn, entity me);
192 #ifdef IMPLEMENTATION
194 // Supporting Functions
195 float Get_Cat_Num_FromString(string input)
198 for(i = 0; i < category_ent_count; ++i) { if(categories[i].cat_name == input) { return (i + 1); } }
199 print(sprintf("Get_Cat_Num_FromString('%s'): Improper category name!\n", input));
202 entity Get_Cat_Ent(float catnum)
204 if((catnum > 0) && (catnum <= category_ent_count))
206 return categories[catnum - 1];
210 error(sprintf("Get_Cat_Ent(%d): Improper category number!\n", catnum));
216 float IsServerInList(string list, string srv)
222 srv = netaddress_resolve(srv, 26000);
225 p = crypto_getidfp(srv);
226 n = tokenize_console(list);
227 for(i = 0; i < n; ++i)
229 if(substring(argv(i), 0, 1) != "[" && strlen(argv(i)) == 44 && strstrofs(argv(i), ".", 0) < 0)
237 if(srv == netaddress_resolve(argv(i), 26000))
244 float CheckCategoryOverride(float cat)
246 entity catent = Get_Cat_Ent(cat);
249 if(catent.cat_override) { return catent.cat_override; }
254 error(sprintf("CheckCategoryOverride(%d): Improper category number!\n", cat));
259 float CheckCategoryForEntry(float entry)
261 string s, k, v, modtype = "";
263 s = gethostcachestring(SLIST_FIELD_QCSTATUS, entry);
264 m = tokenizebyseparator(s, ":");
268 // typestr = argv(0);
269 // versionstr = argv(1);
275 for(j = 2; j < m; ++j)
279 k = substring(argv(j), 0, 1);
280 v = substring(argv(j), 1, -1);
281 if(k == "P") { impure = stof(v); }
282 else if(k == "M") { modtype = strtolower(v); }
285 //print(sprintf("modtype = %s\n", modtype));
287 if(impure > autocvar_menu_serverlist_purethreshold) { impure = TRUE; }
288 else { impure = FALSE; }
290 if(gethostcachenumber(SLIST_FIELD_ISFAVORITE, entry)) { return CAT_FAVORITED; }
291 if(IsRecommended(gethostcachestring(SLIST_FIELD_CNAME, entry))) { return CAT_RECOMMENDED; }
292 else if(modtype != "xonotic")
296 // old servers which don't report their mod name are considered modified now
297 case "": { return CAT_MODIFIED; }
299 case "xpm": { return CAT_XPM; }
300 case "minstagib": { return CAT_MINSTAGIB; }
301 case "overkill": { return CAT_OVERKILL; }
303 // "cts" is allowed as compat, xdf is replacement
305 case "xdf": { return CAT_DEFRAG; }
307 //if(modname != "CTS")
308 //if(modname != "NIX")
309 //if(modname != "NewToys")
311 default: { dprint(sprintf("Found strange mod type: %s\n", modtype)); return CAT_MODIFIED; }
314 else { return (impure ? CAT_MODIFIED : CAT_NORMAL); }
316 // should never hit this point
317 error(sprintf("CheckCategoryForEntry(%d): Function fell through without normal return!\n", entry));
321 float XonoticServerList_MapItems(float num)
325 if not(category_draw_count) { return num; } // there are no categories to process
327 for(i = 0, n = 1; n <= category_draw_count; ++i, ++n)
329 //print(sprintf("num: %d, i: %d, category_draw_count: %d, category_item[i]: %d\n", num, i, category_draw_count, category_item[i]));
330 if(category_item[i] == (num - i)) { /*print("inserting cat... \\/\n");*/ return -category_name[i]; }
331 else if(n == category_draw_count) { /*print("end item... \\/\n");*/ return (num - n); }
332 else if((num - i) <= category_item[n]) { /*print("next item... \\/\n");*/ return (num - n); }
335 // should never hit this point
336 error("wtf XonoticServerList_MapItems fail?");
340 void ToggleFavorite(string srv)
342 string s, s0, s1, s2, srv_resolved, p;
344 srv_resolved = netaddress_resolve(srv, 26000);
345 p = crypto_getidfp(srv_resolved);
346 s = cvar_string("net_slist_favorites");
347 n = tokenize_console(s);
349 for(i = 0; i < n; ++i)
351 if(substring(argv(i), 0, 1) != "[" && strlen(argv(i)) == 44 && strstrofs(argv(i), ".", 0) < 0)
359 if(srv_resolved != netaddress_resolve(argv(i), 26000))
364 s0 = substring(s, 0, argv_end_index(i - 1));
366 s2 = substring(s, argv_start_index(i + 1), -1);
367 if(s0 != "" && s2 != "")
369 cvar_set("net_slist_favorites", strcat(s0, s1, s2));
370 s = cvar_string("net_slist_favorites");
371 n = tokenize_console(s);
382 cvar_set("net_slist_favorites", strcat(s, s1, p));
384 cvar_set("net_slist_favorites", strcat(s, s1, srv));
390 void ServerList_Update_favoriteButton(entity btn, entity me)
392 me.favoriteButton.setText(me.favoriteButton,
393 (IsFavorite(me.ipAddressBox.text) ?
394 _("Remove") : _("Bookmark")
399 entity makeXonoticServerList()
402 me = spawnXonoticServerList();
403 me.configureXonoticServerList(me);
406 void XonoticServerList_configureXonoticServerList(entity me)
408 me.configureXonoticListBox(me);
411 #define SLIST_FIELD(suffix,name) SLIST_FIELD_##suffix = gethostcacheindexforkey(name);
418 void XonoticServerList_setSelected(entity me, float i)
420 // todo: add logic to skip categories
422 save = me.selectedItem;
423 SUPER(XonoticServerList).setSelected(me, i);
425 if(me.selectedItem == save)
431 //if(XonoticServerList_MapItems(gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT)) != me.nItems)
432 // { error("^1XonoticServerList_setSelected(); ERROR: ^7Host cache viewcount mismatches nItems!\n"); return; } // sorry, it would be wrong
433 // todo: make this work somehow? ^
435 num = XonoticServerList_MapItems(me.selectedItem);
438 if(me.selectedServer)
439 strunzone(me.selectedServer);
440 me.selectedServer = strzone(gethostcachestring(SLIST_FIELD_CNAME, num));
442 me.ipAddressBox.setText(me.ipAddressBox, me.selectedServer);
443 me.ipAddressBox.cursorPos = strlen(me.selectedServer);
444 me.ipAddressBoxFocused = -1;
447 void XonoticServerList_refreshServerList(entity me, float mode)
449 // 0: just reparametrize
450 // 1: also ask for new servers
452 //print("refresh of type ", ftos(mode), "\n");
453 /* if(mode == 2) // borken
456 localcmd("net_slist\n");
457 me.needsRefresh = 1; // net_slist kills sort order, so we need to restore it later
463 string s, typestr, modstr;
466 m = strstrofs(s, ":", 0);
469 typestr = substring(s, 0, m);
470 s = substring(s, m + 1, strlen(s) - m - 1);
471 while(substring(s, 0, 1) == " ")
472 s = substring(s, 1, strlen(s) - 1);
477 modstr = cvar_string("menu_slist_modfilter");
479 m = SLIST_MASK_AND - 1;
480 resethostcachemasks();
482 // ping: reject negative ping (no idea why this happens in the first place, engine bug)
483 sethostcachemasknumber(++m, SLIST_FIELD_PING, 0, SLIST_TEST_GREATEREQUAL);
486 if(!me.filterShowFull)
488 sethostcachemasknumber(++m, SLIST_FIELD_FREESLOTS, 1, SLIST_TEST_GREATEREQUAL); // legacy
489 sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, ":S0:", SLIST_TEST_NOTCONTAIN); // g_maxplayers support
493 if(!me.filterShowEmpty)
494 sethostcachemasknumber(++m, SLIST_FIELD_NUMHUMANS, 1, SLIST_TEST_GREATEREQUAL);
496 // gametype filtering
498 sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, strcat(typestr, ":"), SLIST_TEST_STARTSWITH);
503 if(substring(modstr, 0, 1) == "!")
504 sethostcachemaskstring(++m, SLIST_FIELD_MOD, resolvemod(substring(modstr, 1, strlen(modstr) - 1)), SLIST_TEST_NOTEQUAL);
506 sethostcachemaskstring(++m, SLIST_FIELD_MOD, resolvemod(modstr), SLIST_TEST_EQUAL);
510 n = tokenizebyseparator(_Nex_ExtResponseSystem_BannedServers, " ");
511 for(i = 0; i < n; ++i)
513 sethostcachemaskstring(++m, SLIST_FIELD_CNAME, argv(i), SLIST_TEST_NOTSTARTSWITH);
515 m = SLIST_MASK_OR - 1;
518 sethostcachemaskstring(++m, SLIST_FIELD_NAME, s, SLIST_TEST_CONTAINS);
519 sethostcachemaskstring(++m, SLIST_FIELD_MAP, s, SLIST_TEST_CONTAINS);
520 sethostcachemaskstring(++m, SLIST_FIELD_PLAYERS, s, SLIST_TEST_CONTAINS);
521 sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, strcat(s, ":"), SLIST_TEST_STARTSWITH);
525 //listflags |= SLSF_FAVORITES;
526 listflags |= SLSF_CATEGORIES;
527 if(me.currentSortOrder < 0) { listflags |= SLSF_DESCENDING; }
528 sethostcachesort(me.currentSortField, listflags);
531 if(mode >= 1) { refreshhostcache(); }
533 void XonoticServerList_focusEnter(entity me)
535 if(time < me.nextRefreshTime)
537 //print("sorry, no refresh yet\n");
540 me.nextRefreshTime = time + 10;
541 me.refreshServerList(me, 1);
544 void XonoticServerList_draw(entity me)
546 float i, found, owned, num;
548 if(_Nex_ExtResponseSystem_BannedServersNeedsRefresh)
552 _Nex_ExtResponseSystem_BannedServersNeedsRefresh = 0;
555 if(me.currentSortField == -1)
557 me.setSortOrder(me, SLIST_FIELD_PING, +1);
558 me.refreshServerList(me, 2);
560 else if(me.needsRefresh == 1)
562 me.needsRefresh = 2; // delay by one frame to make sure "slist" has been executed
564 else if(me.needsRefresh == 2)
567 me.refreshServerList(me, 0);
570 owned = ((me.selectedServer == me.ipAddressBox.text) && (me.ipAddressBox.text != ""));
572 for(i = 0; i < category_draw_count; ++i) { category_name[i] = -1; category_item[i] = -1; }
573 category_draw_count = 0;
575 if(autocvar_menu_serverlist_categories >= 0) // if less than 0, don't even draw a category heading for favorites
577 float itemcount = gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT);
578 me.nItems = itemcount;
580 //float visible = floor(me.scrollPos / me.itemHeight);
581 // ^ unfortunately no such optimization can be made-- we must process through the
582 // entire list, otherwise there is no way to know which item is first in its category.
585 for(i = 0; i < itemcount; ++i)
587 cat = gethostcachenumber(SLIST_FIELD_CATEGORY, i);
590 if(category_draw_count == 0)
592 category_name[category_draw_count] = cat;
593 category_item[category_draw_count] = i;
594 ++category_draw_count;
600 for(x = 0; x < category_draw_count; ++x) { if(cat == category_name[x]) { found = 1; } }
603 category_name[category_draw_count] = cat;
604 category_item[category_draw_count] = i;
605 ++category_draw_count;
611 if(autocvar_menu_serverlist_categories_onlyifmultiple && (category_draw_count == 1))
613 category_name[0] = category_name[1] = -1;
614 category_item[0] = category_item[1] = -1;
615 category_draw_count = 0;
616 me.nItems = itemcount;
620 me.connectButton.disabled = ((me.nItems == 0) && (me.ipAddressBox.text == ""));
621 me.infoButton.disabled = ((me.nItems == 0) || !owned);
622 me.favoriteButton.disabled = ((me.nItems == 0) && (me.ipAddressBox.text == ""));
625 if(me.selectedServer)
627 for(i = 0; i < me.nItems; ++i)
629 num = XonoticServerList_MapItems(i);
632 if(gethostcachestring(SLIST_FIELD_CNAME, num) == me.selectedServer)
634 if(i != me.selectedItem)
636 me.lastClickedServer = -1;
649 if(me.selectedItem >= me.nItems) { me.selectedItem = me.nItems - 1; }
650 if(me.selectedServer) { strunzone(me.selectedServer); }
652 num = XonoticServerList_MapItems(me.selectedItem);
653 if(num >= 0) { me.selectedServer = strzone(gethostcachestring(SLIST_FIELD_CNAME, num)); }
659 if(me.selectedServer != me.ipAddressBox.text)
661 me.ipAddressBox.setText(me.ipAddressBox, me.selectedServer);
662 me.ipAddressBox.cursorPos = strlen(me.selectedServer);
663 me.ipAddressBoxFocused = -1;
667 if(me.ipAddressBoxFocused != me.ipAddressBox.focused)
669 if(me.ipAddressBox.focused || me.ipAddressBoxFocused < 0)
670 ServerList_Update_favoriteButton(NULL, me);
671 me.ipAddressBoxFocused = me.ipAddressBox.focused;
674 SUPER(XonoticServerList).draw(me);
676 void ServerList_PingSort_Click(entity btn, entity me)
678 me.setSortOrder(me, SLIST_FIELD_PING, +1);
680 void ServerList_NameSort_Click(entity btn, entity me)
682 me.setSortOrder(me, SLIST_FIELD_NAME, -1); // why?
684 void ServerList_MapSort_Click(entity btn, entity me)
686 me.setSortOrder(me, SLIST_FIELD_MAP, -1); // why?
688 void ServerList_PlayerSort_Click(entity btn, entity me)
690 me.setSortOrder(me, SLIST_FIELD_NUMHUMANS, -1);
692 void ServerList_TypeSort_Click(entity btn, entity me)
697 m = strstrofs(s, ":", 0);
700 s = substring(s, 0, m);
701 while(substring(s, m+1, 1) == " ") // skip spaces
707 for(i = 1; ; i *= 2) // 20 modes ought to be enough for anyone
709 t = MapInfo_Type_ToString(i);
711 if(t == "") // it repeats (default case)
714 // choose the first one
715 s = MapInfo_Type_ToString(1);
720 // the type was found
721 // choose the next one
722 s = MapInfo_Type_ToString(i * 2);
724 s = MapInfo_Type_ToString(1);
731 s = strcat(s, substring(me.filterString, m+1, strlen(me.filterString) - m - 1));
733 me.controlledTextbox.setText(me.controlledTextbox, s);
734 me.controlledTextbox.keyDown(me.controlledTextbox, K_END, 0, 0);
735 me.controlledTextbox.keyUp(me.controlledTextbox, K_END, 0, 0);
736 //ServerList_Filter_Change(me.controlledTextbox, me);
738 void ServerList_Filter_Change(entity box, entity me)
741 strunzone(me.filterString);
743 me.filterString = strzone(box.text);
745 me.filterString = string_null;
746 me.refreshServerList(me, 0);
748 me.ipAddressBox.setText(me.ipAddressBox, "");
749 me.ipAddressBox.cursorPos = 0;
750 me.ipAddressBoxFocused = -1;
752 void ServerList_ShowEmpty_Click(entity box, entity me)
754 box.setChecked(box, me.filterShowEmpty = !me.filterShowEmpty);
755 me.refreshServerList(me, 0);
757 me.ipAddressBox.setText(me.ipAddressBox, "");
758 me.ipAddressBox.cursorPos = 0;
759 me.ipAddressBoxFocused = -1;
761 void ServerList_ShowFull_Click(entity box, entity me)
763 box.setChecked(box, me.filterShowFull = !me.filterShowFull);
764 me.refreshServerList(me, 0);
766 me.ipAddressBox.setText(me.ipAddressBox, "");
767 me.ipAddressBox.cursorPos = 0;
768 me.ipAddressBoxFocused = -1;
770 void XonoticServerList_setSortOrder(entity me, float fld, float direction)
772 if(me.currentSortField == fld)
773 direction = -me.currentSortOrder;
774 me.currentSortOrder = direction;
775 me.currentSortField = fld;
776 me.sortButton1.forcePressed = (fld == SLIST_FIELD_PING);
777 me.sortButton2.forcePressed = (fld == SLIST_FIELD_NAME);
778 me.sortButton3.forcePressed = (fld == SLIST_FIELD_MAP);
779 me.sortButton4.forcePressed = 0;
780 me.sortButton5.forcePressed = (fld == SLIST_FIELD_NUMHUMANS);
782 if(me.selectedServer)
783 strunzone(me.selectedServer);
784 me.selectedServer = string_null;
785 me.refreshServerList(me, 0);
787 void XonoticServerList_positionSortButton(entity me, entity btn, float theOrigin, float theSize, string theTitle, void(entity, entity) theFunc)
789 vector originInLBSpace, sizeInLBSpace;
790 originInLBSpace = eY * (-me.itemHeight);
791 sizeInLBSpace = eY * me.itemHeight + eX * (1 - me.controlWidth);
793 vector originInDialogSpace, sizeInDialogSpace;
794 originInDialogSpace = boxToGlobal(originInLBSpace, me.Container_origin, me.Container_size);
795 sizeInDialogSpace = boxToGlobalSize(sizeInLBSpace, me.Container_size);
797 btn.Container_origin_x = originInDialogSpace_x + sizeInDialogSpace_x * theOrigin;
798 btn.Container_size_x = sizeInDialogSpace_x * theSize;
799 btn.setText(btn, theTitle);
800 btn.onClick = theFunc;
801 btn.onClickEntity = me;
804 void XonoticServerList_resizeNotify(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
806 SUPER(XonoticServerList).resizeNotify(me, relOrigin, relSize, absOrigin, absSize);
808 me.realFontSize_y = me.fontSize / (absSize_y * me.itemHeight);
809 me.realFontSize_x = me.fontSize / (absSize_x * (1 - me.controlWidth));
810 me.realUpperMargin = 0.5 * (1 - me.realFontSize_y);
812 me.columnIconsOrigin = 0;
813 me.columnIconsSize = me.realFontSize_x * 4 * me.iconsSizeFactor;
814 me.columnPingSize = me.realFontSize_x * 3;
815 me.columnMapSize = me.realFontSize_x * 10;
816 me.columnTypeSize = me.realFontSize_x * 4;
817 me.columnPlayersSize = me.realFontSize_x * 5;
818 me.columnNameSize = 1 - me.columnPlayersSize - me.columnMapSize - me.columnPingSize - me.columnIconsSize - me.columnTypeSize - 5 * me.realFontSize_x;
819 me.columnPingOrigin = me.columnIconsOrigin + me.columnIconsSize + me.realFontSize_x;
820 me.columnNameOrigin = me.columnPingOrigin + me.columnPingSize + me.realFontSize_x;
821 me.columnMapOrigin = me.columnNameOrigin + me.columnNameSize + me.realFontSize_x;
822 me.columnTypeOrigin = me.columnMapOrigin + me.columnMapSize + me.realFontSize_x;
823 me.columnPlayersOrigin = me.columnTypeOrigin + me.columnTypeSize + me.realFontSize_x;
825 me.positionSortButton(me, me.sortButton1, me.columnPingOrigin, me.columnPingSize, _("Ping"), ServerList_PingSort_Click);
826 me.positionSortButton(me, me.sortButton2, me.columnNameOrigin, me.columnNameSize, _("Host name"), ServerList_NameSort_Click);
827 me.positionSortButton(me, me.sortButton3, me.columnMapOrigin, me.columnMapSize, _("Map"), ServerList_MapSort_Click);
828 me.positionSortButton(me, me.sortButton4, me.columnTypeOrigin, me.columnTypeSize, _("Type"), ServerList_TypeSort_Click);
829 me.positionSortButton(me, me.sortButton5, me.columnPlayersOrigin, me.columnPlayersSize, _("Players"), ServerList_PlayerSort_Click);
832 f = me.currentSortField;
835 me.currentSortField = -1;
836 me.setSortOrder(me, f, me.currentSortOrder); // force resetting the sort order
839 void ServerList_Connect_Click(entity btn, entity me)
841 if(me.ipAddressBox.text == "")
842 localcmd("connect ", me.selectedServer, "\n");
844 localcmd("connect ", me.ipAddressBox.text, "\n");
846 void ServerList_Favorite_Click(entity btn, entity me)
849 ipstr = netaddress_resolve(me.ipAddressBox.text, 26000);
852 ToggleFavorite(me.ipAddressBox.text);
853 me.ipAddressBoxFocused = -1;
856 void ServerList_Info_Click(entity btn, entity me)
858 main.serverInfoDialog.loadServerInfo(main.serverInfoDialog, XonoticServerList_MapItems(me.selectedItem));
859 DialogOpenButton_Click(me, main.serverInfoDialog);
861 void XonoticServerList_clickListBoxItem(entity me, float i, vector where)
863 float num = XonoticServerList_MapItems(i);
864 if(num == me.lastClickedServer)
865 if(time < me.lastClickedTime + 0.3)
868 ServerList_Connect_Click(NULL, me);
870 me.lastClickedServer = num;
871 me.lastClickedTime = time;
873 void XonoticServerList_drawListBoxItem(entity me, float i, vector absSize, float isSelected)
875 // layout: Ping, Server name, Map name, NP, TP, MP
880 float m, pure, freeslots, j, sflags;
881 string s, typestr, versionstr, k, v, modname;
883 float cache = XonoticServerList_MapItems(i);
884 //print(sprintf("time: %f, i: %d, cache: %d, nitems: %d\n", time, i, cache, me.nItems));
888 entity catent = Get_Cat_Ent(-cache);
889 if(catent) { draw_Text(me.realUpperMargin * eY + (me.columnNameOrigin + (me.columnNameSize - draw_TextWidth(catent.cat_string, 0, me.realFontSize)) * 0.5) * eX, catent.cat_string, me.realFontSize, '1 1 1', SKINALPHA_TEXT, 0); return; }
893 draw_Fill('0 0 0', '1 1 0', SKINCOLOR_LISTBOX_SELECTED, SKINALPHA_LISTBOX_SELECTED);
895 s = gethostcachestring(SLIST_FIELD_QCSTATUS, cache);
896 m = tokenizebyseparator(s, ":");
901 versionstr = argv(1);
907 for(j = 2; j < m; ++j)
911 k = substring(argv(j), 0, 1);
912 v = substring(argv(j), 1, -1);
923 #ifdef COMPAT_NO_MOD_IS_XONOTIC
929 SLIST_FIELD_MOD = gethostcacheindexforkey("mod");
930 s = gethostcachestring(SLIST_FIELD_MOD, cache);
932 if(modname == "Xonotic")
936 // list the mods here on which the pure server check actually works
937 if(modname != "Xonotic")
938 if(modname != "MinstaGib")
941 if(modname != "NewToys")
944 if(gethostcachenumber(SLIST_FIELD_FREESLOTS, cache) <= 0)
945 theAlpha = SKINALPHA_SERVERLIST_FULL;
946 else if(freeslots == 0)
947 theAlpha = SKINALPHA_SERVERLIST_FULL; // g_maxplayers support
948 else if not(gethostcachenumber(SLIST_FIELD_NUMHUMANS, cache))
949 theAlpha = SKINALPHA_SERVERLIST_EMPTY;
953 p = gethostcachenumber(SLIST_FIELD_PING, cache);
956 #define PING_HIGH 500
958 theColor = SKINCOLOR_SERVERLIST_LOWPING + (SKINCOLOR_SERVERLIST_MEDPING - SKINCOLOR_SERVERLIST_LOWPING) * (p / PING_LOW);
959 else if(p < PING_MED)
960 theColor = SKINCOLOR_SERVERLIST_MEDPING + (SKINCOLOR_SERVERLIST_HIGHPING - SKINCOLOR_SERVERLIST_MEDPING) * ((p - PING_LOW) / (PING_MED - PING_LOW));
961 else if(p < PING_HIGH)
963 theColor = SKINCOLOR_SERVERLIST_HIGHPING;
964 theAlpha *= 1 + (SKINALPHA_SERVERLIST_HIGHPING - 1) * ((p - PING_MED) / (PING_HIGH - PING_MED));
969 theAlpha *= SKINALPHA_SERVERLIST_HIGHPING;
972 if(gethostcachenumber(SLIST_FIELD_ISFAVORITE, cache))
974 theColor = theColor * (1 - SKINALPHA_SERVERLIST_FAVORITE) + SKINCOLOR_SERVERLIST_FAVORITE * SKINALPHA_SERVERLIST_FAVORITE;
975 theAlpha = theAlpha * (1 - SKINALPHA_SERVERLIST_FAVORITE) + SKINALPHA_SERVERLIST_FAVORITE;
978 s = gethostcachestring(SLIST_FIELD_CNAME, cache);
981 if(substring(s, 0, 1) == "[")
986 else if(strstrofs("0123456789", substring(s, 0, 1), 0) >= 0)
992 q = stof(substring(crypto_getencryptlevel(s), 0, 1));
993 if((q <= 0 && cvar("crypto_aeslevel") >= 3) || (q >= 3 && cvar("crypto_aeslevel") <= 0))
995 theColor = SKINCOLOR_SERVERLIST_IMPOSSIBLE;
996 theAlpha = SKINALPHA_SERVERLIST_IMPOSSIBLE;
1001 if(cvar("crypto_aeslevel") >= 2)
1006 if(cvar("crypto_aeslevel") >= 1)
1016 // 2: AES recommended but not available
1017 // 3: AES possible and will be used
1018 // 4: AES recommended and will be used
1022 vector iconSize = '0 0 0';
1023 iconSize_y = me.realFontSize_y * me.iconsSizeFactor;
1024 iconSize_x = me.realFontSize_x * me.iconsSizeFactor;
1026 vector iconPos = '0 0 0';
1027 iconPos_x = (me.columnIconsSize - 3 * iconSize_x) * 0.5;
1028 iconPos_y = (1 - iconSize_y) * 0.5;
1032 if not(me.seenIPv4 && me.seenIPv6)
1034 iconPos_x += iconSize_x * 0.5;
1036 else if(me.seenIPv4 && me.seenIPv6)
1040 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_ipv6"), 0); // PRECACHE_PIC_MIPMAP
1042 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_ipv4"), 0); // PRECACHE_PIC_MIPMAP
1044 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1045 iconPos_x += iconSize_x;
1050 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_aeslevel", ftos(q)), 0); // PRECACHE_PIC_MIPMAP
1051 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1053 iconPos_x += iconSize_x;
1055 if(modname == "Xonotic")
1059 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_pure1"), PRECACHE_PIC_MIPMAP);
1060 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1065 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_mod_", modname), PRECACHE_PIC_MIPMAP);
1066 if(draw_PictureSize(n) == '0 0 0')
1067 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_mod_"), PRECACHE_PIC_MIPMAP);
1069 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1071 draw_Picture(iconPos, n, iconSize, '1 1 1', SKINALPHA_SERVERLIST_ICON_NONPURE);
1073 iconPos_x += iconSize_x;
1075 if(sflags >= 0 && (sflags & SERVERFLAG_PLAYERSTATS))
1077 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_stats1"), 0); // PRECACHE_PIC_MIPMAP
1078 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1080 iconPos_x += iconSize_x;
1084 draw_Text(me.realUpperMargin * eY + (me.columnPingOrigin + me.columnPingSize - draw_TextWidth(s, 0, me.realFontSize)) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1085 s = draw_TextShortenToWidth(gethostcachestring(SLIST_FIELD_NAME, cache), me.columnNameSize, 0, me.realFontSize);
1086 draw_Text(me.realUpperMargin * eY + me.columnNameOrigin * eX, s, me.realFontSize, theColor, theAlpha, 0);
1087 s = draw_TextShortenToWidth(gethostcachestring(SLIST_FIELD_MAP, cache), me.columnMapSize, 0, me.realFontSize);
1088 draw_Text(me.realUpperMargin * eY + (me.columnMapOrigin + (me.columnMapSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1089 s = draw_TextShortenToWidth(typestr, me.columnTypeSize, 0, me.realFontSize);
1090 draw_Text(me.realUpperMargin * eY + (me.columnTypeOrigin + (me.columnTypeSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1091 s = strcat(ftos(gethostcachenumber(SLIST_FIELD_NUMHUMANS, cache)), "/", ftos(gethostcachenumber(SLIST_FIELD_MAXPLAYERS, cache)));
1092 draw_Text(me.realUpperMargin * eY + (me.columnPlayersOrigin + (me.columnPlayersSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1095 float XonoticServerList_keyDown(entity me, float scan, float ascii, float shift)
1097 float i = XonoticServerList_MapItems(me.selectedItem);
1100 org = boxToGlobal(eY * (me.selectedItem * me.itemHeight - me.scrollPos), me.origin, me.size);
1101 sz = boxToGlobalSize(eY * me.itemHeight + eX * (1 - me.controlWidth), me.size);
1103 if(scan == K_ENTER || scan == K_KP_ENTER)
1105 ServerList_Connect_Click(NULL, me);
1108 else if(scan == K_MOUSE2 || scan == K_SPACE)
1110 if((me.nItems != 0) && (i >= 0))
1112 main.serverInfoDialog.loadServerInfo(main.serverInfoDialog, i);
1113 DialogOpenButton_Click_withCoords(me, main.serverInfoDialog, org, sz);
1118 else if(scan == K_INS || scan == K_MOUSE3 || scan == K_KP_INS)
1120 if((me.nItems != 0) && (i >= 0))
1122 ToggleFavorite(me.selectedServer);
1123 me.ipAddressBoxFocused = -1;
1128 else if(SUPER(XonoticServerList).keyDown(me, scan, ascii, shift))
1130 else if(!me.controlledTextbox)
1133 return me.controlledTextbox.keyDown(me.controlledTextbox, scan, ascii, shift);