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);
104 // fields for category entities
105 #define MAX_CATEGORIES 9
106 #define CATEGORY_FIRST 1
107 entity categories[MAX_CATEGORIES];
108 float category_ent_count;
111 .string cat_override_string;
114 // fields for drawing categories
115 float category_name[MAX_CATEGORIES];
116 float category_item[MAX_CATEGORIES];
117 float category_draw_count;
120 SLIST_CATEGORY(CAT_FAVORITED, "", "", _("Favorites")) \
121 SLIST_CATEGORY(CAT_RECOMMENDED, "", "CAT_SERVERS", _("Recommended")) \
122 SLIST_CATEGORY(CAT_NORMAL, "", "CAT_SERVERS", _("Normal Servers")) \
123 SLIST_CATEGORY(CAT_SERVERS, "CAT_NORMAL", "CAT_SERVERS", _("Servers")) \
124 SLIST_CATEGORY(CAT_XPM, "CAT_NORMAL", "CAT_SERVERS", _("Competitive Mode")) \
125 SLIST_CATEGORY(CAT_MODIFIED, "", "CAT_SERVERS", _("Modified Servers")) \
126 SLIST_CATEGORY(CAT_OVERKILL, "", "CAT_SERVERS", _("Overkill Mode")) \
127 SLIST_CATEGORY(CAT_MINSTAGIB, "", "CAT_SERVERS", _("MinstaGib Mode")) \
128 SLIST_CATEGORY(CAT_DEFRAG, "", "CAT_SERVERS", _("Defrag Mode"))
130 // C is stupid, must use extra macro for concatenation
131 #define SLIST_ADD_CAT_CVAR(name,default) var string autocvar_menu_serverlist_categories_##name##_override = default;
132 #define SLIST_CATEGORY(name,enoverride,deoverride,string) \
133 SLIST_ADD_CAT_CVAR(name, enoverride) \
135 void RegisterSLCategory_##name() \
137 SET_FIELD_COUNT(name, CATEGORY_FIRST, category_ent_count) \
138 CHECK_MAX_COUNT(name, MAX_CATEGORIES, category_ent_count, "SLIST_CATEGORY") \
139 entity cat = spawn(); \
140 categories[name - 1] = cat; \
141 cat.classname = "slist_category"; \
142 cat.cat_name = strzone(#name); \
143 cat.cat_override_string = strzone((autocvar_menu_serverlist_categories ? \
144 autocvar_menu_serverlist_categories_##name##_override \
147 cat.cat_string = strzone(string); \
149 ACCUMULATE_FUNCTION(RegisterSLCategories, RegisterSLCategory_##name);
153 void RegisterSLCategories_Done()
157 for(i = 0; i < category_ent_count; ++i)
159 s = categories[i].cat_override_string;
160 if((s != "") && (s != categories[i].cat_name))
162 catnum = Get_Cat_Num_FromString(s);
165 strunzone(categories[i].cat_override_string);
166 categories[i].cat_override = catnum;
170 strunzone(categories[i].cat_override_string);
171 categories[i].cat_override = 0;
174 ACCUMULATE_FUNCTION(RegisterSLCategories, RegisterSLCategories_Done);
176 #undef SLIST_ADD_CAT_CVAR
177 #undef SLIST_CATEGORY
180 void ServerList_Connect_Click(entity btn, entity me);
181 void ServerList_ShowEmpty_Click(entity box, entity me);
182 void ServerList_ShowFull_Click(entity box, entity me);
183 void ServerList_Filter_Change(entity box, entity me);
184 void ServerList_Favorite_Click(entity btn, entity me);
185 void ServerList_Info_Click(entity btn, entity me);
186 void ServerList_Update_favoriteButton(entity btn, entity me);
190 #ifdef IMPLEMENTATION
192 // Supporting Functions
193 float Get_Cat_Num_FromString(string input)
196 for(i = 0; i < category_ent_count; ++i) { if(categories[i].cat_name == input) { return (i + 1); } }
197 print(sprintf("Get_Cat_Num_FromString('%s'): Improper category name!\n", input));
200 entity Get_Cat_Ent(float catnum)
202 if((catnum > 0) && (catnum <= category_ent_count))
204 return categories[catnum - 1];
208 error(sprintf("Get_Cat_Ent(%d): Improper category number!\n", catnum));
214 float IsServerInList(string list, string srv)
220 srv = netaddress_resolve(srv, 26000);
223 p = crypto_getidfp(srv);
224 n = tokenize_console(list);
225 for(i = 0; i < n; ++i)
227 if(substring(argv(i), 0, 1) != "[" && strlen(argv(i)) == 44 && strstrofs(argv(i), ".", 0) < 0)
235 if(srv == netaddress_resolve(argv(i), 26000))
242 float CheckCategoryOverride(float cat)
244 entity catent = Get_Cat_Ent(cat);
247 if(catent.cat_override) { return catent.cat_override; }
252 error(sprintf("CheckCategoryOverride(%d): Improper category number!\n", cat));
257 float m_getserverlistentrycategory(float entry)
259 string s, k, v, modtype = "";
261 s = gethostcachestring(SLIST_FIELD_QCSTATUS, entry);
262 m = tokenizebyseparator(s, ":");
266 // typestr = argv(0);
267 // versionstr = argv(1);
273 for(j = 2; j < m; ++j)
277 k = substring(argv(j), 0, 1);
278 v = substring(argv(j), 1, -1);
279 if(k == "P") { impure = stof(v); }
280 else if(k == "M") { modtype = strtolower(v); }
283 //print(sprintf("modtype = %s\n", modtype));
285 if(impure > autocvar_menu_serverlist_purethreshold) { impure = TRUE; }
286 else { impure = FALSE; }
288 if(gethostcachenumber(SLIST_FIELD_ISFAVORITE, entry)) { return CheckCategoryOverride(CAT_FAVORITED); }
289 if(IsRecommended(gethostcachestring(SLIST_FIELD_CNAME, entry))) { return CheckCategoryOverride(CAT_RECOMMENDED); }
290 else if(modtype != "xonotic")
294 // old servers which don't report their mod name are considered modified now
295 case "": { return CheckCategoryOverride(CAT_MODIFIED); }
297 case "xpm": { return CheckCategoryOverride(CAT_XPM); }
298 case "minstagib": { return CheckCategoryOverride(CAT_MINSTAGIB); }
299 case "overkill": { return CheckCategoryOverride(CAT_OVERKILL); }
301 // "cts" is allowed as compat, xdf is replacement
303 case "xdf": { return CheckCategoryOverride(CAT_DEFRAG); }
305 //if(modname != "CTS")
306 //if(modname != "NIX")
307 //if(modname != "NewToys")
309 default: { print(sprintf("Found strange mod type: %s\n", modtype)); return CheckCategoryOverride(CAT_MODIFIED); }
312 else { return CheckCategoryOverride((impure ? CAT_MODIFIED : CAT_NORMAL)); }
314 // should never hit this point
315 error("wtf m_getserverlistentrycategory fail?");
319 float XonoticServerList_MapItems(float num)
323 if not(category_draw_count) { return num; } // there are no categories to process
325 for(i = 0, n = 1; n <= category_draw_count; ++i, ++n)
327 //print(sprintf("num: %d, i: %d, category_draw_count: %d, category_item[i]: %d\n", num, i, category_draw_count, category_item[i]));
328 if(category_item[i] == (num - i)) { /*print("inserting cat... \\/\n");*/ return -category_name[i]; }
329 else if(n == category_draw_count) { /*print("end item... \\/\n");*/ return (num - n); }
330 else if((num - i) <= category_item[n]) { /*print("next item... \\/\n");*/ return (num - n); }
333 // should never hit this point
334 error("wtf XonoticServerList_MapItems fail?");
338 void ToggleFavorite(string srv)
340 string s, s0, s1, s2, srv_resolved, p;
342 srv_resolved = netaddress_resolve(srv, 26000);
343 p = crypto_getidfp(srv_resolved);
344 s = cvar_string("net_slist_favorites");
345 n = tokenize_console(s);
347 for(i = 0; i < n; ++i)
349 if(substring(argv(i), 0, 1) != "[" && strlen(argv(i)) == 44 && strstrofs(argv(i), ".", 0) < 0)
357 if(srv_resolved != netaddress_resolve(argv(i), 26000))
362 s0 = substring(s, 0, argv_end_index(i - 1));
364 s2 = substring(s, argv_start_index(i + 1), -1);
365 if(s0 != "" && s2 != "")
367 cvar_set("net_slist_favorites", strcat(s0, s1, s2));
368 s = cvar_string("net_slist_favorites");
369 n = tokenize_console(s);
380 cvar_set("net_slist_favorites", strcat(s, s1, p));
382 cvar_set("net_slist_favorites", strcat(s, s1, srv));
388 void ServerList_Update_favoriteButton(entity btn, entity me)
390 me.favoriteButton.setText(me.favoriteButton,
391 (IsFavorite(me.ipAddressBox.text) ?
392 _("Remove") : _("Bookmark")
397 entity makeXonoticServerList()
400 me = spawnXonoticServerList();
401 me.configureXonoticServerList(me);
404 void XonoticServerList_configureXonoticServerList(entity me)
406 me.configureXonoticListBox(me);
409 #define SLIST_FIELD(suffix,name) SLIST_FIELD_##suffix = gethostcacheindexforkey(name);
416 void XonoticServerList_setSelected(entity me, float i)
418 // todo: add logic to skip categories
420 save = me.selectedItem;
421 SUPER(XonoticServerList).setSelected(me, i);
423 if(me.selectedItem == save)
429 //if(XonoticServerList_MapItems(gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT)) != me.nItems)
430 // { error("^1XonoticServerList_setSelected(); ERROR: ^7Host cache viewcount mismatches nItems!\n"); return; } // sorry, it would be wrong
431 // todo: make this work somehow? ^
433 num = XonoticServerList_MapItems(me.selectedItem);
436 if(me.selectedServer)
437 strunzone(me.selectedServer);
438 me.selectedServer = strzone(gethostcachestring(SLIST_FIELD_CNAME, num));
440 me.ipAddressBox.setText(me.ipAddressBox, me.selectedServer);
441 me.ipAddressBox.cursorPos = strlen(me.selectedServer);
442 me.ipAddressBoxFocused = -1;
445 void XonoticServerList_refreshServerList(entity me, float mode)
447 // 0: just reparametrize
448 // 1: also ask for new servers
450 //print("refresh of type ", ftos(mode), "\n");
451 /* if(mode == 2) // borken
454 localcmd("net_slist\n");
455 me.needsRefresh = 1; // net_slist kills sort order, so we need to restore it later
461 string s, typestr, modstr;
464 m = strstrofs(s, ":", 0);
467 typestr = substring(s, 0, m);
468 s = substring(s, m + 1, strlen(s) - m - 1);
469 while(substring(s, 0, 1) == " ")
470 s = substring(s, 1, strlen(s) - 1);
475 modstr = cvar_string("menu_slist_modfilter");
477 m = SLIST_MASK_AND - 1;
478 resethostcachemasks();
480 // ping: reject negative ping (no idea why this happens in the first place, engine bug)
481 sethostcachemasknumber(++m, SLIST_FIELD_PING, 0, SLIST_TEST_GREATEREQUAL);
484 if(!me.filterShowFull)
486 sethostcachemasknumber(++m, SLIST_FIELD_FREESLOTS, 1, SLIST_TEST_GREATEREQUAL); // legacy
487 sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, ":S0:", SLIST_TEST_NOTCONTAIN); // g_maxplayers support
491 if(!me.filterShowEmpty)
492 sethostcachemasknumber(++m, SLIST_FIELD_NUMHUMANS, 1, SLIST_TEST_GREATEREQUAL);
494 // gametype filtering
496 sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, strcat(typestr, ":"), SLIST_TEST_STARTSWITH);
501 if(substring(modstr, 0, 1) == "!")
502 sethostcachemaskstring(++m, SLIST_FIELD_MOD, resolvemod(substring(modstr, 1, strlen(modstr) - 1)), SLIST_TEST_NOTEQUAL);
504 sethostcachemaskstring(++m, SLIST_FIELD_MOD, resolvemod(modstr), SLIST_TEST_EQUAL);
508 n = tokenizebyseparator(_Nex_ExtResponseSystem_BannedServers, " ");
509 for(i = 0; i < n; ++i)
511 sethostcachemaskstring(++m, SLIST_FIELD_CNAME, argv(i), SLIST_TEST_NOTSTARTSWITH);
513 m = SLIST_MASK_OR - 1;
516 sethostcachemaskstring(++m, SLIST_FIELD_NAME, s, SLIST_TEST_CONTAINS);
517 sethostcachemaskstring(++m, SLIST_FIELD_MAP, s, SLIST_TEST_CONTAINS);
518 sethostcachemaskstring(++m, SLIST_FIELD_PLAYERS, s, SLIST_TEST_CONTAINS);
519 sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, strcat(s, ":"), SLIST_TEST_STARTSWITH);
523 //listflags |= SLSF_FAVORITES;
524 listflags |= SLSF_CATEGORIES;
525 if(me.currentSortOrder < 0) { listflags |= SLSF_DESCENDING; }
526 sethostcachesort(me.currentSortField, listflags);
529 if(mode >= 1) { refreshhostcache(); }
531 void XonoticServerList_focusEnter(entity me)
533 if(time < me.nextRefreshTime)
535 //print("sorry, no refresh yet\n");
538 me.nextRefreshTime = time + 10;
539 me.refreshServerList(me, 1);
542 void XonoticServerList_draw(entity me)
544 float i, found, owned, num;
546 if(_Nex_ExtResponseSystem_BannedServersNeedsRefresh)
550 _Nex_ExtResponseSystem_BannedServersNeedsRefresh = 0;
553 if(me.currentSortField == -1)
555 me.setSortOrder(me, SLIST_FIELD_PING, +1);
556 me.refreshServerList(me, 2);
558 else if(me.needsRefresh == 1)
560 me.needsRefresh = 2; // delay by one frame to make sure "slist" has been executed
562 else if(me.needsRefresh == 2)
565 me.refreshServerList(me, 0);
568 owned = ((me.selectedServer == me.ipAddressBox.text) && (me.ipAddressBox.text != ""));
570 for(i = 0; i < category_draw_count; ++i) { category_name[i] = -1; category_item[i] = -1; }
571 category_draw_count = 0;
573 if(autocvar_menu_serverlist_categories >= 0) // if less than 0, don't even draw a category heading for favorites
575 float itemcount = gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT);
576 me.nItems = itemcount;
578 //float visible = floor(me.scrollPos / me.itemHeight);
579 // ^ unfortunately no such optimization can be made-- we must process through the
580 // entire list, otherwise there is no way to know which item is first in its category.
583 for(i = 0; i < itemcount; ++i)
585 cat = gethostcachenumber(SLIST_FIELD_CATEGORY, i);
588 if(category_draw_count == 0)
590 category_name[category_draw_count] = cat;
591 category_item[category_draw_count] = i;
592 ++category_draw_count;
598 for(x = 0; x < category_draw_count; ++x) { if(cat == category_name[x]) { found = 1; } }
601 category_name[category_draw_count] = cat;
602 category_item[category_draw_count] = i;
603 ++category_draw_count;
609 if(autocvar_menu_serverlist_categories_onlyifmultiple && (category_draw_count == 1))
611 category_name[0] = category_name[1] = -1;
612 category_item[0] = category_item[1] = -1;
613 category_draw_count = 0;
614 me.nItems = itemcount;
618 me.connectButton.disabled = ((me.nItems == 0) && (me.ipAddressBox.text == ""));
619 me.infoButton.disabled = ((me.nItems == 0) || !owned);
620 me.favoriteButton.disabled = ((me.nItems == 0) && (me.ipAddressBox.text == ""));
623 if(me.selectedServer)
625 for(i = 0; i < me.nItems; ++i)
627 num = XonoticServerList_MapItems(i);
630 if(gethostcachestring(SLIST_FIELD_CNAME, num) == me.selectedServer)
632 if(i != me.selectedItem)
634 me.lastClickedServer = -1;
647 if(me.selectedItem >= me.nItems) { me.selectedItem = me.nItems - 1; }
648 if(me.selectedServer) { strunzone(me.selectedServer); }
650 num = XonoticServerList_MapItems(me.selectedItem);
651 if(num >= 0) { me.selectedServer = strzone(gethostcachestring(SLIST_FIELD_CNAME, num)); }
657 if(me.selectedServer != me.ipAddressBox.text)
659 me.ipAddressBox.setText(me.ipAddressBox, me.selectedServer);
660 me.ipAddressBox.cursorPos = strlen(me.selectedServer);
661 me.ipAddressBoxFocused = -1;
665 if(me.ipAddressBoxFocused != me.ipAddressBox.focused)
667 if(me.ipAddressBox.focused || me.ipAddressBoxFocused < 0)
668 ServerList_Update_favoriteButton(NULL, me);
669 me.ipAddressBoxFocused = me.ipAddressBox.focused;
672 SUPER(XonoticServerList).draw(me);
674 void ServerList_PingSort_Click(entity btn, entity me)
676 me.setSortOrder(me, SLIST_FIELD_PING, +1);
678 void ServerList_NameSort_Click(entity btn, entity me)
680 me.setSortOrder(me, SLIST_FIELD_NAME, -1); // why?
682 void ServerList_MapSort_Click(entity btn, entity me)
684 me.setSortOrder(me, SLIST_FIELD_MAP, -1); // why?
686 void ServerList_PlayerSort_Click(entity btn, entity me)
688 me.setSortOrder(me, SLIST_FIELD_NUMHUMANS, -1);
690 void ServerList_TypeSort_Click(entity btn, entity me)
695 m = strstrofs(s, ":", 0);
698 s = substring(s, 0, m);
699 while(substring(s, m+1, 1) == " ") // skip spaces
705 for(i = 1; ; i *= 2) // 20 modes ought to be enough for anyone
707 t = MapInfo_Type_ToString(i);
709 if(t == "") // it repeats (default case)
712 // choose the first one
713 s = MapInfo_Type_ToString(1);
718 // the type was found
719 // choose the next one
720 s = MapInfo_Type_ToString(i * 2);
722 s = MapInfo_Type_ToString(1);
729 s = strcat(s, substring(me.filterString, m+1, strlen(me.filterString) - m - 1));
731 me.controlledTextbox.setText(me.controlledTextbox, s);
732 me.controlledTextbox.keyDown(me.controlledTextbox, K_END, 0, 0);
733 me.controlledTextbox.keyUp(me.controlledTextbox, K_END, 0, 0);
734 //ServerList_Filter_Change(me.controlledTextbox, me);
736 void ServerList_Filter_Change(entity box, entity me)
739 strunzone(me.filterString);
741 me.filterString = strzone(box.text);
743 me.filterString = string_null;
744 me.refreshServerList(me, 0);
746 me.ipAddressBox.setText(me.ipAddressBox, "");
747 me.ipAddressBox.cursorPos = 0;
748 me.ipAddressBoxFocused = -1;
750 void ServerList_ShowEmpty_Click(entity box, entity me)
752 box.setChecked(box, me.filterShowEmpty = !me.filterShowEmpty);
753 me.refreshServerList(me, 0);
755 me.ipAddressBox.setText(me.ipAddressBox, "");
756 me.ipAddressBox.cursorPos = 0;
757 me.ipAddressBoxFocused = -1;
759 void ServerList_ShowFull_Click(entity box, entity me)
761 box.setChecked(box, me.filterShowFull = !me.filterShowFull);
762 me.refreshServerList(me, 0);
764 me.ipAddressBox.setText(me.ipAddressBox, "");
765 me.ipAddressBox.cursorPos = 0;
766 me.ipAddressBoxFocused = -1;
768 void XonoticServerList_setSortOrder(entity me, float fld, float direction)
770 if(me.currentSortField == fld)
771 direction = -me.currentSortOrder;
772 me.currentSortOrder = direction;
773 me.currentSortField = fld;
774 me.sortButton1.forcePressed = (fld == SLIST_FIELD_PING);
775 me.sortButton2.forcePressed = (fld == SLIST_FIELD_NAME);
776 me.sortButton3.forcePressed = (fld == SLIST_FIELD_MAP);
777 me.sortButton4.forcePressed = 0;
778 me.sortButton5.forcePressed = (fld == SLIST_FIELD_NUMHUMANS);
780 if(me.selectedServer)
781 strunzone(me.selectedServer);
782 me.selectedServer = string_null;
783 me.refreshServerList(me, 0);
785 void XonoticServerList_positionSortButton(entity me, entity btn, float theOrigin, float theSize, string theTitle, void(entity, entity) theFunc)
787 vector originInLBSpace, sizeInLBSpace;
788 originInLBSpace = eY * (-me.itemHeight);
789 sizeInLBSpace = eY * me.itemHeight + eX * (1 - me.controlWidth);
791 vector originInDialogSpace, sizeInDialogSpace;
792 originInDialogSpace = boxToGlobal(originInLBSpace, me.Container_origin, me.Container_size);
793 sizeInDialogSpace = boxToGlobalSize(sizeInLBSpace, me.Container_size);
795 btn.Container_origin_x = originInDialogSpace_x + sizeInDialogSpace_x * theOrigin;
796 btn.Container_size_x = sizeInDialogSpace_x * theSize;
797 btn.setText(btn, theTitle);
798 btn.onClick = theFunc;
799 btn.onClickEntity = me;
802 void XonoticServerList_resizeNotify(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
804 SUPER(XonoticServerList).resizeNotify(me, relOrigin, relSize, absOrigin, absSize);
806 me.realFontSize_y = me.fontSize / (absSize_y * me.itemHeight);
807 me.realFontSize_x = me.fontSize / (absSize_x * (1 - me.controlWidth));
808 me.realUpperMargin = 0.5 * (1 - me.realFontSize_y);
810 me.columnIconsOrigin = 0;
811 me.columnIconsSize = me.realFontSize_x * 4 * me.iconsSizeFactor;
812 me.columnPingSize = me.realFontSize_x * 3;
813 me.columnMapSize = me.realFontSize_x * 10;
814 me.columnTypeSize = me.realFontSize_x * 4;
815 me.columnPlayersSize = me.realFontSize_x * 5;
816 me.columnNameSize = 1 - me.columnPlayersSize - me.columnMapSize - me.columnPingSize - me.columnIconsSize - me.columnTypeSize - 5 * me.realFontSize_x;
817 me.columnPingOrigin = me.columnIconsOrigin + me.columnIconsSize + me.realFontSize_x;
818 me.columnNameOrigin = me.columnPingOrigin + me.columnPingSize + me.realFontSize_x;
819 me.columnMapOrigin = me.columnNameOrigin + me.columnNameSize + me.realFontSize_x;
820 me.columnTypeOrigin = me.columnMapOrigin + me.columnMapSize + me.realFontSize_x;
821 me.columnPlayersOrigin = me.columnTypeOrigin + me.columnTypeSize + me.realFontSize_x;
823 me.positionSortButton(me, me.sortButton1, me.columnPingOrigin, me.columnPingSize, _("Ping"), ServerList_PingSort_Click);
824 me.positionSortButton(me, me.sortButton2, me.columnNameOrigin, me.columnNameSize, _("Host name"), ServerList_NameSort_Click);
825 me.positionSortButton(me, me.sortButton3, me.columnMapOrigin, me.columnMapSize, _("Map"), ServerList_MapSort_Click);
826 me.positionSortButton(me, me.sortButton4, me.columnTypeOrigin, me.columnTypeSize, _("Type"), ServerList_TypeSort_Click);
827 me.positionSortButton(me, me.sortButton5, me.columnPlayersOrigin, me.columnPlayersSize, _("Players"), ServerList_PlayerSort_Click);
830 f = me.currentSortField;
833 me.currentSortField = -1;
834 me.setSortOrder(me, f, me.currentSortOrder); // force resetting the sort order
837 void ServerList_Connect_Click(entity btn, entity me)
839 if(me.ipAddressBox.text == "")
840 localcmd("connect ", me.selectedServer, "\n");
842 localcmd("connect ", me.ipAddressBox.text, "\n");
844 void ServerList_Favorite_Click(entity btn, entity me)
847 ipstr = netaddress_resolve(me.ipAddressBox.text, 26000);
850 ToggleFavorite(me.ipAddressBox.text);
851 me.ipAddressBoxFocused = -1;
854 void ServerList_Info_Click(entity btn, entity me)
856 main.serverInfoDialog.loadServerInfo(main.serverInfoDialog, XonoticServerList_MapItems(me.selectedItem));
857 DialogOpenButton_Click(me, main.serverInfoDialog);
859 void XonoticServerList_clickListBoxItem(entity me, float i, vector where)
861 float num = XonoticServerList_MapItems(i);
862 if(num == me.lastClickedServer)
863 if(time < me.lastClickedTime + 0.3)
866 ServerList_Connect_Click(NULL, me);
868 me.lastClickedServer = num;
869 me.lastClickedTime = time;
871 void XonoticServerList_drawListBoxItem(entity me, float i, vector absSize, float isSelected)
873 // layout: Ping, Server name, Map name, NP, TP, MP
878 float m, pure, freeslots, j, sflags;
879 string s, typestr, versionstr, k, v, modname;
881 float cache = XonoticServerList_MapItems(i);
882 //print(sprintf("time: %f, i: %d, cache: %d, nitems: %d\n", time, i, cache, me.nItems));
886 entity catent = Get_Cat_Ent(-cache);
887 if(catent) { draw_Text(me.realUpperMargin * eY + (me.columnNameOrigin + (me.columnNameSize - draw_TextWidth(catent.cat_string, 0, me.realFontSize)) * 0.5) * eX, catent.cat_string, me.realFontSize, '1 1 1', SKINALPHA_TEXT, 0); return; }
891 draw_Fill('0 0 0', '1 1 0', SKINCOLOR_LISTBOX_SELECTED, SKINALPHA_LISTBOX_SELECTED);
893 s = gethostcachestring(SLIST_FIELD_QCSTATUS, cache);
894 m = tokenizebyseparator(s, ":");
899 versionstr = argv(1);
905 for(j = 2; j < m; ++j)
909 k = substring(argv(j), 0, 1);
910 v = substring(argv(j), 1, -1);
921 #ifdef COMPAT_NO_MOD_IS_XONOTIC
927 SLIST_FIELD_MOD = gethostcacheindexforkey("mod");
928 s = gethostcachestring(SLIST_FIELD_MOD, cache);
930 if(modname == "Xonotic")
934 // list the mods here on which the pure server check actually works
935 if(modname != "Xonotic")
936 if(modname != "MinstaGib")
939 if(modname != "NewToys")
942 if(gethostcachenumber(SLIST_FIELD_FREESLOTS, cache) <= 0)
943 theAlpha = SKINALPHA_SERVERLIST_FULL;
944 else if(freeslots == 0)
945 theAlpha = SKINALPHA_SERVERLIST_FULL; // g_maxplayers support
946 else if not(gethostcachenumber(SLIST_FIELD_NUMHUMANS, cache))
947 theAlpha = SKINALPHA_SERVERLIST_EMPTY;
951 p = gethostcachenumber(SLIST_FIELD_PING, cache);
954 #define PING_HIGH 500
956 theColor = SKINCOLOR_SERVERLIST_LOWPING + (SKINCOLOR_SERVERLIST_MEDPING - SKINCOLOR_SERVERLIST_LOWPING) * (p / PING_LOW);
957 else if(p < PING_MED)
958 theColor = SKINCOLOR_SERVERLIST_MEDPING + (SKINCOLOR_SERVERLIST_HIGHPING - SKINCOLOR_SERVERLIST_MEDPING) * ((p - PING_LOW) / (PING_MED - PING_LOW));
959 else if(p < PING_HIGH)
961 theColor = SKINCOLOR_SERVERLIST_HIGHPING;
962 theAlpha *= 1 + (SKINALPHA_SERVERLIST_HIGHPING - 1) * ((p - PING_MED) / (PING_HIGH - PING_MED));
967 theAlpha *= SKINALPHA_SERVERLIST_HIGHPING;
970 if(gethostcachenumber(SLIST_FIELD_ISFAVORITE, cache))
972 theColor = theColor * (1 - SKINALPHA_SERVERLIST_FAVORITE) + SKINCOLOR_SERVERLIST_FAVORITE * SKINALPHA_SERVERLIST_FAVORITE;
973 theAlpha = theAlpha * (1 - SKINALPHA_SERVERLIST_FAVORITE) + SKINALPHA_SERVERLIST_FAVORITE;
976 s = gethostcachestring(SLIST_FIELD_CNAME, cache);
979 if(substring(s, 0, 1) == "[")
984 else if(strstrofs("0123456789", substring(s, 0, 1), 0) >= 0)
990 q = stof(substring(crypto_getencryptlevel(s), 0, 1));
991 if((q <= 0 && cvar("crypto_aeslevel") >= 3) || (q >= 3 && cvar("crypto_aeslevel") <= 0))
993 theColor = SKINCOLOR_SERVERLIST_IMPOSSIBLE;
994 theAlpha = SKINALPHA_SERVERLIST_IMPOSSIBLE;
999 if(cvar("crypto_aeslevel") >= 2)
1004 if(cvar("crypto_aeslevel") >= 1)
1014 // 2: AES recommended but not available
1015 // 3: AES possible and will be used
1016 // 4: AES recommended and will be used
1020 vector iconSize = '0 0 0';
1021 iconSize_y = me.realFontSize_y * me.iconsSizeFactor;
1022 iconSize_x = me.realFontSize_x * me.iconsSizeFactor;
1024 vector iconPos = '0 0 0';
1025 iconPos_x = (me.columnIconsSize - 3 * iconSize_x) * 0.5;
1026 iconPos_y = (1 - iconSize_y) * 0.5;
1030 if not(me.seenIPv4 && me.seenIPv6)
1032 iconPos_x += iconSize_x * 0.5;
1034 else if(me.seenIPv4 && me.seenIPv6)
1038 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_ipv6"), 0); // PRECACHE_PIC_MIPMAP
1040 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_ipv4"), 0); // PRECACHE_PIC_MIPMAP
1042 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1043 iconPos_x += iconSize_x;
1048 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_aeslevel", ftos(q)), 0); // PRECACHE_PIC_MIPMAP
1049 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1051 iconPos_x += iconSize_x;
1053 if(modname == "Xonotic")
1057 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_pure1"), PRECACHE_PIC_MIPMAP);
1058 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1063 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_mod_", modname), PRECACHE_PIC_MIPMAP);
1064 if(draw_PictureSize(n) == '0 0 0')
1065 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_mod_"), PRECACHE_PIC_MIPMAP);
1067 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1069 draw_Picture(iconPos, n, iconSize, '1 1 1', SKINALPHA_SERVERLIST_ICON_NONPURE);
1071 iconPos_x += iconSize_x;
1073 if(sflags >= 0 && (sflags & SERVERFLAG_PLAYERSTATS))
1075 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_stats1"), 0); // PRECACHE_PIC_MIPMAP
1076 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1078 iconPos_x += iconSize_x;
1082 draw_Text(me.realUpperMargin * eY + (me.columnPingOrigin + me.columnPingSize - draw_TextWidth(s, 0, me.realFontSize)) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1083 s = draw_TextShortenToWidth(gethostcachestring(SLIST_FIELD_NAME, cache), me.columnNameSize, 0, me.realFontSize);
1084 draw_Text(me.realUpperMargin * eY + me.columnNameOrigin * eX, s, me.realFontSize, theColor, theAlpha, 0);
1085 s = draw_TextShortenToWidth(gethostcachestring(SLIST_FIELD_MAP, cache), me.columnMapSize, 0, me.realFontSize);
1086 draw_Text(me.realUpperMargin * eY + (me.columnMapOrigin + (me.columnMapSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1087 s = draw_TextShortenToWidth(typestr, me.columnTypeSize, 0, me.realFontSize);
1088 draw_Text(me.realUpperMargin * eY + (me.columnTypeOrigin + (me.columnTypeSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1089 s = strcat(ftos(gethostcachenumber(SLIST_FIELD_NUMHUMANS, cache)), "/", ftos(gethostcachenumber(SLIST_FIELD_MAXPLAYERS, cache)));
1090 draw_Text(me.realUpperMargin * eY + (me.columnPlayersOrigin + (me.columnPlayersSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1093 float XonoticServerList_keyDown(entity me, float scan, float ascii, float shift)
1095 float i = XonoticServerList_MapItems(me.selectedItem);
1098 org = boxToGlobal(eY * (me.selectedItem * me.itemHeight - me.scrollPos), me.origin, me.size);
1099 sz = boxToGlobalSize(eY * me.itemHeight + eX * (1 - me.controlWidth), me.size);
1101 if(scan == K_ENTER || scan == K_KP_ENTER)
1103 ServerList_Connect_Click(NULL, me);
1106 else if(scan == K_MOUSE2 || scan == K_SPACE)
1108 if((me.nItems != 0) && (i >= 0))
1110 main.serverInfoDialog.loadServerInfo(main.serverInfoDialog, i);
1111 DialogOpenButton_Click_withCoords(me, main.serverInfoDialog, org, sz);
1116 else if(scan == K_INS || scan == K_MOUSE3 || scan == K_KP_INS)
1118 if((me.nItems != 0) && (i >= 0))
1120 ToggleFavorite(me.selectedServer);
1121 me.ipAddressBoxFocused = -1;
1126 else if(SUPER(XonoticServerList).keyDown(me, scan, ascii, shift))
1128 else if(!me.controlledTextbox)
1131 return me.controlledTextbox.keyDown(me.controlledTextbox, scan, ascii, shift);