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_purethreshold = 10;
64 var string autocvar_menu_serverlist_recommended = "76.124.107.5:26004";
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")
85 #define SLIST_FIELD(suffix,name) float SLIST_FIELD_##suffix;
90 float SLSF_DESCENDING = 1;
91 float SLSF_FAVORITES = 2;
92 float SLSF_CATEGORIES = 4;
94 float Get_Cat_Num_FromString(string input);
95 entity Get_Cat_Ent(float catnum);
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
101 float CheckCategoryOverride(float cat);
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;
110 .string cat_override_string;
113 // fields for drawing categories
114 float category_name[MAX_CATEGORIES];
115 float category_item[MAX_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"))
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) \
134 void RegisterSLCategory_##name() \
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 \
146 cat.cat_string = strzone(string); \
148 ACCUMULATE_FUNCTION(RegisterSLCategories, RegisterSLCategory_##name);
152 void RegisterSLCategories_Done()
156 for(i = 0; i < category_ent_count; ++i)
158 s = categories[i].cat_override_string;
159 if((s != "") && (s != categories[i].cat_name))
161 catnum = Get_Cat_Num_FromString(s);
164 strunzone(categories[i].cat_override_string);
165 categories[i].cat_override = catnum;
169 strunzone(categories[i].cat_override_string);
170 categories[i].cat_override = 0;
173 ACCUMULATE_FUNCTION(RegisterSLCategories, RegisterSLCategories_Done);
175 #undef SLIST_ADD_CAT_CVAR
176 #undef SLIST_CATEGORY
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);
189 #ifdef IMPLEMENTATION
191 // Supporting Functions
192 float Get_Cat_Num_FromString(string input)
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));
199 entity Get_Cat_Ent(float catnum)
201 if((catnum > 0) && (catnum <= category_ent_count))
203 return categories[catnum - 1];
207 error(sprintf("Get_Cat_Ent(%d): Improper category number!\n", catnum));
213 float IsServerInList(string list, string srv)
219 srv = netaddress_resolve(srv, 26000);
222 p = crypto_getidfp(srv);
223 n = tokenize_console(list);
224 for(i = 0; i < n; ++i)
226 if(substring(argv(i), 0, 1) != "[" && strlen(argv(i)) == 44 && strstrofs(argv(i), ".", 0) < 0)
234 if(srv == netaddress_resolve(argv(i), 26000))
241 float CheckCategoryOverride(float cat)
243 entity catent = Get_Cat_Ent(cat);
246 if(catent.cat_override) { return catent.cat_override; }
251 error(sprintf("CheckCategoryOverride(%d): Improper category number!\n", cat));
256 float m_getserverlistentrycategory(float entry)
258 string s, k, v, modtype = "";
260 s = gethostcachestring(SLIST_FIELD_QCSTATUS, entry);
261 m = tokenizebyseparator(s, ":");
265 // typestr = argv(0);
266 // versionstr = argv(1);
272 for(j = 2; j < m; ++j)
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); }
282 //print(sprintf("modtype = %s\n", modtype));
284 if(impure > autocvar_menu_serverlist_purethreshold) { impure = TRUE; }
285 else { impure = FALSE; }
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")
293 // old servers which don't report their mod name are considered modified now
294 case "": { return CheckCategoryOverride(CAT_MODIFIED); }
296 case "xpm": { return CheckCategoryOverride(CAT_XPM); }
297 case "minstagib": { return CheckCategoryOverride(CAT_MINSTAGIB); }
298 case "overkill": { return CheckCategoryOverride(CAT_OVERKILL); }
300 // "cts" is allowed as compat, xdf is replacement
302 case "xdf": { return CheckCategoryOverride(CAT_DEFRAG); }
304 //if(modname != "CTS")
305 //if(modname != "NIX")
306 //if(modname != "NewToys")
308 default: { print(sprintf("Found strange mod type: %s\n", modtype)); return CheckCategoryOverride(CAT_MODIFIED); }
311 else { return CheckCategoryOverride((impure ? CAT_MODIFIED : CAT_NORMAL)); }
313 // should never hit this point
314 error("wtf m_getserverlistentrycategory fail?");
318 float XonoticServerList_MapItems(float num)
322 if not(totcat) { return num; } // there are no categories to process
324 for(i = 0, n = 1; n <= totcat; ++i, ++n)
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); }
332 // should never hit this point
333 error("wtf XonoticServerList_MapItems fail?");
337 void ServerList_UpdateFieldIDs()
339 #define SLIST_FIELD(suffix,name) SLIST_FIELD_##suffix = gethostcacheindexforkey(name);
344 void ToggleFavorite(string srv)
346 string s, s0, s1, s2, srv_resolved, p;
348 srv_resolved = netaddress_resolve(srv, 26000);
349 p = crypto_getidfp(srv_resolved);
350 s = cvar_string("net_slist_favorites");
351 n = tokenize_console(s);
353 for(i = 0; i < n; ++i)
355 if(substring(argv(i), 0, 1) != "[" && strlen(argv(i)) == 44 && strstrofs(argv(i), ".", 0) < 0)
363 if(srv_resolved != netaddress_resolve(argv(i), 26000))
368 s0 = substring(s, 0, argv_end_index(i - 1));
370 s2 = substring(s, argv_start_index(i + 1), -1);
371 if(s0 != "" && s2 != "")
373 cvar_set("net_slist_favorites", strcat(s0, s1, s2));
374 s = cvar_string("net_slist_favorites");
375 n = tokenize_console(s);
386 cvar_set("net_slist_favorites", strcat(s, s1, p));
388 cvar_set("net_slist_favorites", strcat(s, s1, srv));
394 void ServerList_Update_favoriteButton(entity btn, entity me)
396 if(IsFavorite(me.ipAddressBox.text))
397 me.favoriteButton.setText(me.favoriteButton, _("Remove"));
399 me.favoriteButton.setText(me.favoriteButton, _("Bookmark"));
402 entity makeXonoticServerList()
405 me = spawnXonoticServerList();
406 me.configureXonoticServerList(me);
409 void XonoticServerList_configureXonoticServerList(entity me)
411 me.configureXonoticListBox(me);
413 ServerList_UpdateFieldIDs();
417 void XonoticServerList_setSelected(entity me, float i)
420 save = me.selectedItem;
421 SUPER(XonoticServerList).setSelected(me, i);
423 if(me.selectedItem == save)
428 //if(XonoticServerList_MapItems(gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT)) != me.nItems)
429 // { error("^1XonoticServerList_setSelected(); ERROR: ^7Host cache viewcount mismatches nItems!\n"); return; } // sorry, it would be wrong
431 num = XonoticServerList_MapItems(me.selectedItem);
434 if(me.selectedServer)
435 strunzone(me.selectedServer);
436 me.selectedServer = strzone(gethostcachestring(SLIST_FIELD_CNAME, num));
438 me.ipAddressBox.setText(me.ipAddressBox, me.selectedServer);
439 me.ipAddressBox.cursorPos = strlen(me.selectedServer);
440 me.ipAddressBoxFocused = -1;
443 void XonoticServerList_refreshServerList(entity me, float mode)
445 // 0: just reparametrize
446 // 1: also ask for new servers
448 //print("refresh of type ", ftos(mode), "\n");
449 /* if(mode == 2) // borken
452 localcmd("net_slist\n");
453 me.needsRefresh = 1; // net_slist kills sort order, so we need to restore it later
459 string s, typestr, modstr;
462 m = strstrofs(s, ":", 0);
465 typestr = substring(s, 0, m);
466 s = substring(s, m + 1, strlen(s) - m - 1);
467 while(substring(s, 0, 1) == " ")
468 s = substring(s, 1, strlen(s) - 1);
473 modstr = cvar_string("menu_slist_modfilter");
475 m = SLIST_MASK_AND - 1;
476 resethostcachemasks();
478 // ping: reject negative ping (no idea why this happens in the first place, engine bug)
479 sethostcachemasknumber(++m, SLIST_FIELD_PING, 0, SLIST_TEST_GREATEREQUAL);
482 if(!me.filterShowFull)
484 sethostcachemasknumber(++m, SLIST_FIELD_FREESLOTS, 1, SLIST_TEST_GREATEREQUAL); // legacy
485 sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, ":S0:", SLIST_TEST_NOTCONTAIN); // g_maxplayers support
489 if(!me.filterShowEmpty)
490 sethostcachemasknumber(++m, SLIST_FIELD_NUMHUMANS, 1, SLIST_TEST_GREATEREQUAL);
492 // gametype filtering
494 sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, strcat(typestr, ":"), SLIST_TEST_STARTSWITH);
499 if(substring(modstr, 0, 1) == "!")
500 sethostcachemaskstring(++m, SLIST_FIELD_MOD, resolvemod(substring(modstr, 1, strlen(modstr) - 1)), SLIST_TEST_NOTEQUAL);
502 sethostcachemaskstring(++m, SLIST_FIELD_MOD, resolvemod(modstr), SLIST_TEST_EQUAL);
506 n = tokenizebyseparator(_Nex_ExtResponseSystem_BannedServers, " ");
507 for(i = 0; i < n; ++i)
509 sethostcachemaskstring(++m, SLIST_FIELD_CNAME, argv(i), SLIST_TEST_NOTSTARTSWITH);
511 m = SLIST_MASK_OR - 1;
514 sethostcachemaskstring(++m, SLIST_FIELD_NAME, s, SLIST_TEST_CONTAINS);
515 sethostcachemaskstring(++m, SLIST_FIELD_MAP, s, SLIST_TEST_CONTAINS);
516 sethostcachemaskstring(++m, SLIST_FIELD_PLAYERS, s, SLIST_TEST_CONTAINS);
517 sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, strcat(s, ":"), SLIST_TEST_STARTSWITH);
521 //listflags |= SLSF_FAVORITES;
522 listflags |= SLSF_CATEGORIES;
523 if(me.currentSortOrder < 0) { listflags |= SLSF_DESCENDING; }
524 sethostcachesort(me.currentSortField, listflags);
527 if(mode >= 1) { refreshhostcache(); }
529 void XonoticServerList_focusEnter(entity me)
531 if(time < me.nextRefreshTime)
533 //print("sorry, no refresh yet\n");
536 me.nextRefreshTime = time + 10;
537 me.refreshServerList(me, 1);
540 void XonoticServerList_draw(entity me)
542 float i, found, owned, num;
544 if(_Nex_ExtResponseSystem_BannedServersNeedsRefresh)
548 _Nex_ExtResponseSystem_BannedServersNeedsRefresh = 0;
551 if(me.currentSortField == -1)
553 me.setSortOrder(me, SLIST_FIELD_PING, +1);
554 me.refreshServerList(me, 2);
556 else if(me.needsRefresh == 1)
558 me.needsRefresh = 2; // delay by one frame to make sure "slist" has been executed
560 else if(me.needsRefresh == 2)
563 me.refreshServerList(me, 0);
566 owned = ((me.selectedServer == me.ipAddressBox.text) && (me.ipAddressBox.text != ""));
568 for(i = 0; i < totcat; ++i) { category_name[i] = -1; category_item[i] = -1; }
571 float itemcount = gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT);
572 //float visible = floor(me.scrollPos / me.itemHeight);
573 me.nItems = itemcount;
576 for(i = 0; i < itemcount; ++i)
578 cat = gethostcachenumber(SLIST_FIELD_CATEGORY, i);
583 category_name[totcat] = cat;
584 category_item[totcat] = i;
591 for(x = 0; x < totcat; ++x) { if(cat == category_name[x]) { found = 1; } }
594 category_name[totcat] = cat;
595 category_item[totcat] = i;
603 //print(sprintf("^1SERVERLIST_DRAW^7: servercount: %d, nitems: %d\n", itemcount, me.nItems));
605 me.connectButton.disabled = ((me.nItems == 0) && (me.ipAddressBox.text == ""));
606 me.infoButton.disabled = ((me.nItems == 0) || !owned);
607 me.favoriteButton.disabled = ((me.nItems == 0) && (me.ipAddressBox.text == ""));
610 if(me.selectedServer)
612 for(i = 0; i < me.nItems; ++i)
614 num = XonoticServerList_MapItems(i);
617 if(gethostcachestring(SLIST_FIELD_CNAME, num) == me.selectedServer)
619 if(i != me.selectedItem)
621 me.lastClickedServer = -1;
634 if(me.selectedItem >= me.nItems) { me.selectedItem = me.nItems - 1; }
635 if(me.selectedServer) { strunzone(me.selectedServer); }
637 num = XonoticServerList_MapItems(me.selectedItem);
638 if(num >= 0) { me.selectedServer = strzone(gethostcachestring(SLIST_FIELD_CNAME, num)); }
644 if(me.selectedServer != me.ipAddressBox.text)
646 me.ipAddressBox.setText(me.ipAddressBox, me.selectedServer);
647 me.ipAddressBox.cursorPos = strlen(me.selectedServer);
648 me.ipAddressBoxFocused = -1;
652 if(me.ipAddressBoxFocused != me.ipAddressBox.focused)
654 if(me.ipAddressBox.focused || me.ipAddressBoxFocused < 0)
655 ServerList_Update_favoriteButton(NULL, me);
656 me.ipAddressBoxFocused = me.ipAddressBox.focused;
659 SUPER(XonoticServerList).draw(me);
661 void ServerList_PingSort_Click(entity btn, entity me)
663 me.setSortOrder(me, SLIST_FIELD_PING, +1);
665 void ServerList_NameSort_Click(entity btn, entity me)
667 me.setSortOrder(me, SLIST_FIELD_NAME, -1); // why?
669 void ServerList_MapSort_Click(entity btn, entity me)
671 me.setSortOrder(me, SLIST_FIELD_MAP, -1); // why?
673 void ServerList_PlayerSort_Click(entity btn, entity me)
675 me.setSortOrder(me, SLIST_FIELD_NUMHUMANS, -1);
677 void ServerList_TypeSort_Click(entity btn, entity me)
682 m = strstrofs(s, ":", 0);
685 s = substring(s, 0, m);
686 while(substring(s, m+1, 1) == " ") // skip spaces
692 for(i = 1; ; i *= 2) // 20 modes ought to be enough for anyone
694 t = MapInfo_Type_ToString(i);
696 if(t == "") // it repeats (default case)
699 // choose the first one
700 s = MapInfo_Type_ToString(1);
705 // the type was found
706 // choose the next one
707 s = MapInfo_Type_ToString(i * 2);
709 s = MapInfo_Type_ToString(1);
716 s = strcat(s, substring(me.filterString, m+1, strlen(me.filterString) - m - 1));
718 me.controlledTextbox.setText(me.controlledTextbox, s);
719 me.controlledTextbox.keyDown(me.controlledTextbox, K_END, 0, 0);
720 me.controlledTextbox.keyUp(me.controlledTextbox, K_END, 0, 0);
721 //ServerList_Filter_Change(me.controlledTextbox, me);
723 void ServerList_Filter_Change(entity box, entity me)
726 strunzone(me.filterString);
728 me.filterString = strzone(box.text);
730 me.filterString = string_null;
731 me.refreshServerList(me, 0);
733 me.ipAddressBox.setText(me.ipAddressBox, "");
734 me.ipAddressBox.cursorPos = 0;
735 me.ipAddressBoxFocused = -1;
737 void ServerList_ShowEmpty_Click(entity box, entity me)
739 box.setChecked(box, me.filterShowEmpty = !me.filterShowEmpty);
740 me.refreshServerList(me, 0);
742 me.ipAddressBox.setText(me.ipAddressBox, "");
743 me.ipAddressBox.cursorPos = 0;
744 me.ipAddressBoxFocused = -1;
746 void ServerList_ShowFull_Click(entity box, entity me)
748 box.setChecked(box, me.filterShowFull = !me.filterShowFull);
749 me.refreshServerList(me, 0);
751 me.ipAddressBox.setText(me.ipAddressBox, "");
752 me.ipAddressBox.cursorPos = 0;
753 me.ipAddressBoxFocused = -1;
755 void XonoticServerList_setSortOrder(entity me, float fld, float direction)
757 if(me.currentSortField == fld)
758 direction = -me.currentSortOrder;
759 me.currentSortOrder = direction;
760 me.currentSortField = fld;
761 me.sortButton1.forcePressed = (fld == SLIST_FIELD_PING);
762 me.sortButton2.forcePressed = (fld == SLIST_FIELD_NAME);
763 me.sortButton3.forcePressed = (fld == SLIST_FIELD_MAP);
764 me.sortButton4.forcePressed = 0;
765 me.sortButton5.forcePressed = (fld == SLIST_FIELD_NUMHUMANS);
767 if(me.selectedServer)
768 strunzone(me.selectedServer);
769 me.selectedServer = string_null;
770 me.refreshServerList(me, 0);
772 void XonoticServerList_positionSortButton(entity me, entity btn, float theOrigin, float theSize, string theTitle, void(entity, entity) theFunc)
774 vector originInLBSpace, sizeInLBSpace;
775 originInLBSpace = eY * (-me.itemHeight);
776 sizeInLBSpace = eY * me.itemHeight + eX * (1 - me.controlWidth);
778 vector originInDialogSpace, sizeInDialogSpace;
779 originInDialogSpace = boxToGlobal(originInLBSpace, me.Container_origin, me.Container_size);
780 sizeInDialogSpace = boxToGlobalSize(sizeInLBSpace, me.Container_size);
782 btn.Container_origin_x = originInDialogSpace_x + sizeInDialogSpace_x * theOrigin;
783 btn.Container_size_x = sizeInDialogSpace_x * theSize;
784 btn.setText(btn, theTitle);
785 btn.onClick = theFunc;
786 btn.onClickEntity = me;
789 void XonoticServerList_resizeNotify(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
791 SUPER(XonoticServerList).resizeNotify(me, relOrigin, relSize, absOrigin, absSize);
793 me.realFontSize_y = me.fontSize / (absSize_y * me.itemHeight);
794 me.realFontSize_x = me.fontSize / (absSize_x * (1 - me.controlWidth));
795 me.realUpperMargin = 0.5 * (1 - me.realFontSize_y);
797 me.columnIconsOrigin = 0;
798 me.columnIconsSize = me.realFontSize_x * 4 * me.iconsSizeFactor;
799 me.columnPingSize = me.realFontSize_x * 3;
800 me.columnMapSize = me.realFontSize_x * 10;
801 me.columnTypeSize = me.realFontSize_x * 4;
802 me.columnPlayersSize = me.realFontSize_x * 5;
803 me.columnNameSize = 1 - me.columnPlayersSize - me.columnMapSize - me.columnPingSize - me.columnIconsSize - me.columnTypeSize - 5 * me.realFontSize_x;
804 me.columnPingOrigin = me.columnIconsOrigin + me.columnIconsSize + me.realFontSize_x;
805 me.columnNameOrigin = me.columnPingOrigin + me.columnPingSize + me.realFontSize_x;
806 me.columnMapOrigin = me.columnNameOrigin + me.columnNameSize + me.realFontSize_x;
807 me.columnTypeOrigin = me.columnMapOrigin + me.columnMapSize + me.realFontSize_x;
808 me.columnPlayersOrigin = me.columnTypeOrigin + me.columnTypeSize + me.realFontSize_x;
810 me.positionSortButton(me, me.sortButton1, me.columnPingOrigin, me.columnPingSize, _("Ping"), ServerList_PingSort_Click);
811 me.positionSortButton(me, me.sortButton2, me.columnNameOrigin, me.columnNameSize, _("Host name"), ServerList_NameSort_Click);
812 me.positionSortButton(me, me.sortButton3, me.columnMapOrigin, me.columnMapSize, _("Map"), ServerList_MapSort_Click);
813 me.positionSortButton(me, me.sortButton4, me.columnTypeOrigin, me.columnTypeSize, _("Type"), ServerList_TypeSort_Click);
814 me.positionSortButton(me, me.sortButton5, me.columnPlayersOrigin, me.columnPlayersSize, _("Players"), ServerList_PlayerSort_Click);
817 f = me.currentSortField;
820 me.currentSortField = -1;
821 me.setSortOrder(me, f, me.currentSortOrder); // force resetting the sort order
824 void ServerList_Connect_Click(entity btn, entity me)
826 if(me.ipAddressBox.text == "")
827 localcmd("connect ", me.selectedServer, "\n");
829 localcmd("connect ", me.ipAddressBox.text, "\n");
831 void ServerList_Favorite_Click(entity btn, entity me)
834 ipstr = netaddress_resolve(me.ipAddressBox.text, 26000);
837 ToggleFavorite(me.ipAddressBox.text);
838 me.ipAddressBoxFocused = -1;
841 void ServerList_Info_Click(entity btn, entity me)
843 main.serverInfoDialog.loadServerInfo(main.serverInfoDialog, XonoticServerList_MapItems(me.selectedItem));
844 DialogOpenButton_Click(me, main.serverInfoDialog);
846 void XonoticServerList_clickListBoxItem(entity me, float i, vector where)
848 float num = XonoticServerList_MapItems(i);
849 if(num == me.lastClickedServer)
850 if(time < me.lastClickedTime + 0.3)
853 ServerList_Connect_Click(NULL, me);
855 me.lastClickedServer = num;
856 me.lastClickedTime = time;
858 void XonoticServerList_drawListBoxItem(entity me, float i, vector absSize, float isSelected)
860 // layout: Ping, Server name, Map name, NP, TP, MP
865 float m, pure, freeslots, j, sflags;
866 string s, typestr, versionstr, k, v, modname;
868 float cache = XonoticServerList_MapItems(i);
869 //print(sprintf("time: %f, i: %d, cache: %d, nitems: %d\n", time, i, cache, me.nItems));
873 entity catent = Get_Cat_Ent(-cache);
874 if(catent) { draw_Text(me.realUpperMargin * eY + (me.columnNameOrigin + (me.columnNameSize - draw_TextWidth(catent.cat_string, 0, me.realFontSize)) * 0.5) * eX, catent.cat_string, me.realFontSize, '1 1 1', SKINALPHA_TEXT, 0); return; }
878 draw_Fill('0 0 0', '1 1 0', SKINCOLOR_LISTBOX_SELECTED, SKINALPHA_LISTBOX_SELECTED);
880 s = gethostcachestring(SLIST_FIELD_QCSTATUS, cache);
881 m = tokenizebyseparator(s, ":");
886 versionstr = argv(1);
892 for(j = 2; j < m; ++j)
896 k = substring(argv(j), 0, 1);
897 v = substring(argv(j), 1, -1);
908 #ifdef COMPAT_NO_MOD_IS_XONOTIC
914 SLIST_FIELD_MOD = gethostcacheindexforkey("mod");
915 s = gethostcachestring(SLIST_FIELD_MOD, cache);
917 if(modname == "Xonotic")
921 // list the mods here on which the pure server check actually works
922 if(modname != "Xonotic")
923 if(modname != "MinstaGib")
926 if(modname != "NewToys")
929 if(gethostcachenumber(SLIST_FIELD_FREESLOTS, cache) <= 0)
930 theAlpha = SKINALPHA_SERVERLIST_FULL;
931 else if(freeslots == 0)
932 theAlpha = SKINALPHA_SERVERLIST_FULL; // g_maxplayers support
933 else if not(gethostcachenumber(SLIST_FIELD_NUMHUMANS, cache))
934 theAlpha = SKINALPHA_SERVERLIST_EMPTY;
938 p = gethostcachenumber(SLIST_FIELD_PING, cache);
941 #define PING_HIGH 500
943 theColor = SKINCOLOR_SERVERLIST_LOWPING + (SKINCOLOR_SERVERLIST_MEDPING - SKINCOLOR_SERVERLIST_LOWPING) * (p / PING_LOW);
944 else if(p < PING_MED)
945 theColor = SKINCOLOR_SERVERLIST_MEDPING + (SKINCOLOR_SERVERLIST_HIGHPING - SKINCOLOR_SERVERLIST_MEDPING) * ((p - PING_LOW) / (PING_MED - PING_LOW));
946 else if(p < PING_HIGH)
948 theColor = SKINCOLOR_SERVERLIST_HIGHPING;
949 theAlpha *= 1 + (SKINALPHA_SERVERLIST_HIGHPING - 1) * ((p - PING_MED) / (PING_HIGH - PING_MED));
954 theAlpha *= SKINALPHA_SERVERLIST_HIGHPING;
957 if(gethostcachenumber(SLIST_FIELD_ISFAVORITE, cache))
959 theColor = theColor * (1 - SKINALPHA_SERVERLIST_FAVORITE) + SKINCOLOR_SERVERLIST_FAVORITE * SKINALPHA_SERVERLIST_FAVORITE;
960 theAlpha = theAlpha * (1 - SKINALPHA_SERVERLIST_FAVORITE) + SKINALPHA_SERVERLIST_FAVORITE;
963 s = gethostcachestring(SLIST_FIELD_CNAME, cache);
966 if(substring(s, 0, 1) == "[")
971 else if(strstrofs("0123456789", substring(s, 0, 1), 0) >= 0)
977 q = stof(substring(crypto_getencryptlevel(s), 0, 1));
978 if((q <= 0 && cvar("crypto_aeslevel") >= 3) || (q >= 3 && cvar("crypto_aeslevel") <= 0))
980 theColor = SKINCOLOR_SERVERLIST_IMPOSSIBLE;
981 theAlpha = SKINALPHA_SERVERLIST_IMPOSSIBLE;
986 if(cvar("crypto_aeslevel") >= 2)
991 if(cvar("crypto_aeslevel") >= 1)
1001 // 2: AES recommended but not available
1002 // 3: AES possible and will be used
1003 // 4: AES recommended and will be used
1007 vector iconSize = '0 0 0';
1008 iconSize_y = me.realFontSize_y * me.iconsSizeFactor;
1009 iconSize_x = me.realFontSize_x * me.iconsSizeFactor;
1011 vector iconPos = '0 0 0';
1012 iconPos_x = (me.columnIconsSize - 3 * iconSize_x) * 0.5;
1013 iconPos_y = (1 - iconSize_y) * 0.5;
1017 if not(me.seenIPv4 && me.seenIPv6)
1019 iconPos_x += iconSize_x * 0.5;
1021 else if(me.seenIPv4 && me.seenIPv6)
1025 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_ipv6"), 0); // PRECACHE_PIC_MIPMAP
1027 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_ipv4"), 0); // PRECACHE_PIC_MIPMAP
1029 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1030 iconPos_x += iconSize_x;
1035 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_aeslevel", ftos(q)), 0); // PRECACHE_PIC_MIPMAP
1036 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1038 iconPos_x += iconSize_x;
1040 if(modname == "Xonotic")
1044 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_pure1"), PRECACHE_PIC_MIPMAP);
1045 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1050 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_mod_", modname), PRECACHE_PIC_MIPMAP);
1051 if(draw_PictureSize(n) == '0 0 0')
1052 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_mod_"), PRECACHE_PIC_MIPMAP);
1054 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1056 draw_Picture(iconPos, n, iconSize, '1 1 1', SKINALPHA_SERVERLIST_ICON_NONPURE);
1058 iconPos_x += iconSize_x;
1060 if(sflags >= 0 && (sflags & SERVERFLAG_PLAYERSTATS))
1062 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_stats1"), 0); // PRECACHE_PIC_MIPMAP
1063 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1065 iconPos_x += iconSize_x;
1069 draw_Text(me.realUpperMargin * eY + (me.columnPingOrigin + me.columnPingSize - draw_TextWidth(s, 0, me.realFontSize)) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1070 s = draw_TextShortenToWidth(gethostcachestring(SLIST_FIELD_NAME, cache), me.columnNameSize, 0, me.realFontSize);
1071 draw_Text(me.realUpperMargin * eY + me.columnNameOrigin * eX, s, me.realFontSize, theColor, theAlpha, 0);
1072 s = draw_TextShortenToWidth(gethostcachestring(SLIST_FIELD_MAP, cache), me.columnMapSize, 0, me.realFontSize);
1073 draw_Text(me.realUpperMargin * eY + (me.columnMapOrigin + (me.columnMapSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1074 s = draw_TextShortenToWidth(typestr, me.columnTypeSize, 0, me.realFontSize);
1075 draw_Text(me.realUpperMargin * eY + (me.columnTypeOrigin + (me.columnTypeSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1076 s = strcat(ftos(gethostcachenumber(SLIST_FIELD_NUMHUMANS, cache)), "/", ftos(gethostcachenumber(SLIST_FIELD_MAXPLAYERS, cache)));
1077 draw_Text(me.realUpperMargin * eY + (me.columnPlayersOrigin + (me.columnPlayersSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1080 float XonoticServerList_keyDown(entity me, float scan, float ascii, float shift)
1082 float i = XonoticServerList_MapItems(me.selectedItem);
1085 org = boxToGlobal(eY * (me.selectedItem * me.itemHeight - me.scrollPos), me.origin, me.size);
1086 sz = boxToGlobalSize(eY * me.itemHeight + eX * (1 - me.controlWidth), me.size);
1088 if(scan == K_ENTER || scan == K_KP_ENTER)
1090 ServerList_Connect_Click(NULL, me);
1093 else if(scan == K_MOUSE2 || scan == K_SPACE)
1095 if((me.nItems != 0) && (i >= 0))
1097 main.serverInfoDialog.loadServerInfo(main.serverInfoDialog, i);
1098 DialogOpenButton_Click_withCoords(me, main.serverInfoDialog, org, sz);
1103 else if(scan == K_INS || scan == K_MOUSE3 || scan == K_KP_INS)
1105 if((me.nItems != 0) && (i >= 0))
1107 ToggleFavorite(me.selectedServer);
1108 me.ipAddressBoxFocused = -1;
1113 else if(SUPER(XonoticServerList).keyDown(me, scan, ascii, shift))
1115 else if(!me.controlledTextbox)
1118 return me.controlledTextbox.keyDown(me.controlledTextbox, scan, ascii, shift);