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 ToggleFavorite(string srv)
339 string s, s0, s1, s2, srv_resolved, p;
341 srv_resolved = netaddress_resolve(srv, 26000);
342 p = crypto_getidfp(srv_resolved);
343 s = cvar_string("net_slist_favorites");
344 n = tokenize_console(s);
346 for(i = 0; i < n; ++i)
348 if(substring(argv(i), 0, 1) != "[" && strlen(argv(i)) == 44 && strstrofs(argv(i), ".", 0) < 0)
356 if(srv_resolved != netaddress_resolve(argv(i), 26000))
361 s0 = substring(s, 0, argv_end_index(i - 1));
363 s2 = substring(s, argv_start_index(i + 1), -1);
364 if(s0 != "" && s2 != "")
366 cvar_set("net_slist_favorites", strcat(s0, s1, s2));
367 s = cvar_string("net_slist_favorites");
368 n = tokenize_console(s);
379 cvar_set("net_slist_favorites", strcat(s, s1, p));
381 cvar_set("net_slist_favorites", strcat(s, s1, srv));
387 void ServerList_Update_favoriteButton(entity btn, entity me)
389 if(IsFavorite(me.ipAddressBox.text))
390 me.favoriteButton.setText(me.favoriteButton, _("Remove"));
392 me.favoriteButton.setText(me.favoriteButton, _("Bookmark"));
395 entity makeXonoticServerList()
398 me = spawnXonoticServerList();
399 me.configureXonoticServerList(me);
402 void XonoticServerList_configureXonoticServerList(entity me)
404 me.configureXonoticListBox(me);
407 #define SLIST_FIELD(suffix,name) SLIST_FIELD_##suffix = gethostcacheindexforkey(name);
414 void XonoticServerList_setSelected(entity me, float i)
417 save = me.selectedItem;
418 SUPER(XonoticServerList).setSelected(me, i);
420 if(me.selectedItem == save)
426 //if(XonoticServerList_MapItems(gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT)) != me.nItems)
427 // { error("^1XonoticServerList_setSelected(); ERROR: ^7Host cache viewcount mismatches nItems!\n"); return; } // sorry, it would be wrong
428 // todo: make this work somehow? ^
430 num = XonoticServerList_MapItems(me.selectedItem);
433 if(me.selectedServer)
434 strunzone(me.selectedServer);
435 me.selectedServer = strzone(gethostcachestring(SLIST_FIELD_CNAME, num));
437 me.ipAddressBox.setText(me.ipAddressBox, me.selectedServer);
438 me.ipAddressBox.cursorPos = strlen(me.selectedServer);
439 me.ipAddressBoxFocused = -1;
442 void XonoticServerList_refreshServerList(entity me, float mode)
444 // 0: just reparametrize
445 // 1: also ask for new servers
447 //print("refresh of type ", ftos(mode), "\n");
448 /* if(mode == 2) // borken
451 localcmd("net_slist\n");
452 me.needsRefresh = 1; // net_slist kills sort order, so we need to restore it later
458 string s, typestr, modstr;
461 m = strstrofs(s, ":", 0);
464 typestr = substring(s, 0, m);
465 s = substring(s, m + 1, strlen(s) - m - 1);
466 while(substring(s, 0, 1) == " ")
467 s = substring(s, 1, strlen(s) - 1);
472 modstr = cvar_string("menu_slist_modfilter");
474 m = SLIST_MASK_AND - 1;
475 resethostcachemasks();
477 // ping: reject negative ping (no idea why this happens in the first place, engine bug)
478 sethostcachemasknumber(++m, SLIST_FIELD_PING, 0, SLIST_TEST_GREATEREQUAL);
481 if(!me.filterShowFull)
483 sethostcachemasknumber(++m, SLIST_FIELD_FREESLOTS, 1, SLIST_TEST_GREATEREQUAL); // legacy
484 sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, ":S0:", SLIST_TEST_NOTCONTAIN); // g_maxplayers support
488 if(!me.filterShowEmpty)
489 sethostcachemasknumber(++m, SLIST_FIELD_NUMHUMANS, 1, SLIST_TEST_GREATEREQUAL);
491 // gametype filtering
493 sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, strcat(typestr, ":"), SLIST_TEST_STARTSWITH);
498 if(substring(modstr, 0, 1) == "!")
499 sethostcachemaskstring(++m, SLIST_FIELD_MOD, resolvemod(substring(modstr, 1, strlen(modstr) - 1)), SLIST_TEST_NOTEQUAL);
501 sethostcachemaskstring(++m, SLIST_FIELD_MOD, resolvemod(modstr), SLIST_TEST_EQUAL);
505 n = tokenizebyseparator(_Nex_ExtResponseSystem_BannedServers, " ");
506 for(i = 0; i < n; ++i)
508 sethostcachemaskstring(++m, SLIST_FIELD_CNAME, argv(i), SLIST_TEST_NOTSTARTSWITH);
510 m = SLIST_MASK_OR - 1;
513 sethostcachemaskstring(++m, SLIST_FIELD_NAME, s, SLIST_TEST_CONTAINS);
514 sethostcachemaskstring(++m, SLIST_FIELD_MAP, s, SLIST_TEST_CONTAINS);
515 sethostcachemaskstring(++m, SLIST_FIELD_PLAYERS, s, SLIST_TEST_CONTAINS);
516 sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, strcat(s, ":"), SLIST_TEST_STARTSWITH);
520 //listflags |= SLSF_FAVORITES;
521 listflags |= SLSF_CATEGORIES;
522 if(me.currentSortOrder < 0) { listflags |= SLSF_DESCENDING; }
523 sethostcachesort(me.currentSortField, listflags);
526 if(mode >= 1) { refreshhostcache(); }
528 void XonoticServerList_focusEnter(entity me)
530 if(time < me.nextRefreshTime)
532 //print("sorry, no refresh yet\n");
535 me.nextRefreshTime = time + 10;
536 me.refreshServerList(me, 1);
539 void XonoticServerList_draw(entity me)
541 float i, found, owned, num;
543 if(_Nex_ExtResponseSystem_BannedServersNeedsRefresh)
547 _Nex_ExtResponseSystem_BannedServersNeedsRefresh = 0;
550 if(me.currentSortField == -1)
552 me.setSortOrder(me, SLIST_FIELD_PING, +1);
553 me.refreshServerList(me, 2);
555 else if(me.needsRefresh == 1)
557 me.needsRefresh = 2; // delay by one frame to make sure "slist" has been executed
559 else if(me.needsRefresh == 2)
562 me.refreshServerList(me, 0);
565 owned = ((me.selectedServer == me.ipAddressBox.text) && (me.ipAddressBox.text != ""));
567 for(i = 0; i < totcat; ++i) { category_name[i] = -1; category_item[i] = -1; }
570 float itemcount = gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT);
571 //float visible = floor(me.scrollPos / me.itemHeight);
572 me.nItems = itemcount;
575 for(i = 0; i < itemcount; ++i)
577 cat = gethostcachenumber(SLIST_FIELD_CATEGORY, i);
582 category_name[totcat] = cat;
583 category_item[totcat] = i;
590 for(x = 0; x < totcat; ++x) { if(cat == category_name[x]) { found = 1; } }
593 category_name[totcat] = cat;
594 category_item[totcat] = i;
602 //print(sprintf("^1SERVERLIST_DRAW^7: servercount: %d, nitems: %d\n", itemcount, me.nItems));
604 me.connectButton.disabled = ((me.nItems == 0) && (me.ipAddressBox.text == ""));
605 me.infoButton.disabled = ((me.nItems == 0) || !owned);
606 me.favoriteButton.disabled = ((me.nItems == 0) && (me.ipAddressBox.text == ""));
609 if(me.selectedServer)
611 for(i = 0; i < me.nItems; ++i)
613 num = XonoticServerList_MapItems(i);
616 if(gethostcachestring(SLIST_FIELD_CNAME, num) == me.selectedServer)
618 if(i != me.selectedItem)
620 me.lastClickedServer = -1;
633 if(me.selectedItem >= me.nItems) { me.selectedItem = me.nItems - 1; }
634 if(me.selectedServer) { strunzone(me.selectedServer); }
636 num = XonoticServerList_MapItems(me.selectedItem);
637 if(num >= 0) { me.selectedServer = strzone(gethostcachestring(SLIST_FIELD_CNAME, num)); }
643 if(me.selectedServer != me.ipAddressBox.text)
645 me.ipAddressBox.setText(me.ipAddressBox, me.selectedServer);
646 me.ipAddressBox.cursorPos = strlen(me.selectedServer);
647 me.ipAddressBoxFocused = -1;
651 if(me.ipAddressBoxFocused != me.ipAddressBox.focused)
653 if(me.ipAddressBox.focused || me.ipAddressBoxFocused < 0)
654 ServerList_Update_favoriteButton(NULL, me);
655 me.ipAddressBoxFocused = me.ipAddressBox.focused;
658 SUPER(XonoticServerList).draw(me);
660 void ServerList_PingSort_Click(entity btn, entity me)
662 me.setSortOrder(me, SLIST_FIELD_PING, +1);
664 void ServerList_NameSort_Click(entity btn, entity me)
666 me.setSortOrder(me, SLIST_FIELD_NAME, -1); // why?
668 void ServerList_MapSort_Click(entity btn, entity me)
670 me.setSortOrder(me, SLIST_FIELD_MAP, -1); // why?
672 void ServerList_PlayerSort_Click(entity btn, entity me)
674 me.setSortOrder(me, SLIST_FIELD_NUMHUMANS, -1);
676 void ServerList_TypeSort_Click(entity btn, entity me)
681 m = strstrofs(s, ":", 0);
684 s = substring(s, 0, m);
685 while(substring(s, m+1, 1) == " ") // skip spaces
691 for(i = 1; ; i *= 2) // 20 modes ought to be enough for anyone
693 t = MapInfo_Type_ToString(i);
695 if(t == "") // it repeats (default case)
698 // choose the first one
699 s = MapInfo_Type_ToString(1);
704 // the type was found
705 // choose the next one
706 s = MapInfo_Type_ToString(i * 2);
708 s = MapInfo_Type_ToString(1);
715 s = strcat(s, substring(me.filterString, m+1, strlen(me.filterString) - m - 1));
717 me.controlledTextbox.setText(me.controlledTextbox, s);
718 me.controlledTextbox.keyDown(me.controlledTextbox, K_END, 0, 0);
719 me.controlledTextbox.keyUp(me.controlledTextbox, K_END, 0, 0);
720 //ServerList_Filter_Change(me.controlledTextbox, me);
722 void ServerList_Filter_Change(entity box, entity me)
725 strunzone(me.filterString);
727 me.filterString = strzone(box.text);
729 me.filterString = string_null;
730 me.refreshServerList(me, 0);
732 me.ipAddressBox.setText(me.ipAddressBox, "");
733 me.ipAddressBox.cursorPos = 0;
734 me.ipAddressBoxFocused = -1;
736 void ServerList_ShowEmpty_Click(entity box, entity me)
738 box.setChecked(box, me.filterShowEmpty = !me.filterShowEmpty);
739 me.refreshServerList(me, 0);
741 me.ipAddressBox.setText(me.ipAddressBox, "");
742 me.ipAddressBox.cursorPos = 0;
743 me.ipAddressBoxFocused = -1;
745 void ServerList_ShowFull_Click(entity box, entity me)
747 box.setChecked(box, me.filterShowFull = !me.filterShowFull);
748 me.refreshServerList(me, 0);
750 me.ipAddressBox.setText(me.ipAddressBox, "");
751 me.ipAddressBox.cursorPos = 0;
752 me.ipAddressBoxFocused = -1;
754 void XonoticServerList_setSortOrder(entity me, float fld, float direction)
756 if(me.currentSortField == fld)
757 direction = -me.currentSortOrder;
758 me.currentSortOrder = direction;
759 me.currentSortField = fld;
760 me.sortButton1.forcePressed = (fld == SLIST_FIELD_PING);
761 me.sortButton2.forcePressed = (fld == SLIST_FIELD_NAME);
762 me.sortButton3.forcePressed = (fld == SLIST_FIELD_MAP);
763 me.sortButton4.forcePressed = 0;
764 me.sortButton5.forcePressed = (fld == SLIST_FIELD_NUMHUMANS);
766 if(me.selectedServer)
767 strunzone(me.selectedServer);
768 me.selectedServer = string_null;
769 me.refreshServerList(me, 0);
771 void XonoticServerList_positionSortButton(entity me, entity btn, float theOrigin, float theSize, string theTitle, void(entity, entity) theFunc)
773 vector originInLBSpace, sizeInLBSpace;
774 originInLBSpace = eY * (-me.itemHeight);
775 sizeInLBSpace = eY * me.itemHeight + eX * (1 - me.controlWidth);
777 vector originInDialogSpace, sizeInDialogSpace;
778 originInDialogSpace = boxToGlobal(originInLBSpace, me.Container_origin, me.Container_size);
779 sizeInDialogSpace = boxToGlobalSize(sizeInLBSpace, me.Container_size);
781 btn.Container_origin_x = originInDialogSpace_x + sizeInDialogSpace_x * theOrigin;
782 btn.Container_size_x = sizeInDialogSpace_x * theSize;
783 btn.setText(btn, theTitle);
784 btn.onClick = theFunc;
785 btn.onClickEntity = me;
788 void XonoticServerList_resizeNotify(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
790 SUPER(XonoticServerList).resizeNotify(me, relOrigin, relSize, absOrigin, absSize);
792 me.realFontSize_y = me.fontSize / (absSize_y * me.itemHeight);
793 me.realFontSize_x = me.fontSize / (absSize_x * (1 - me.controlWidth));
794 me.realUpperMargin = 0.5 * (1 - me.realFontSize_y);
796 me.columnIconsOrigin = 0;
797 me.columnIconsSize = me.realFontSize_x * 4 * me.iconsSizeFactor;
798 me.columnPingSize = me.realFontSize_x * 3;
799 me.columnMapSize = me.realFontSize_x * 10;
800 me.columnTypeSize = me.realFontSize_x * 4;
801 me.columnPlayersSize = me.realFontSize_x * 5;
802 me.columnNameSize = 1 - me.columnPlayersSize - me.columnMapSize - me.columnPingSize - me.columnIconsSize - me.columnTypeSize - 5 * me.realFontSize_x;
803 me.columnPingOrigin = me.columnIconsOrigin + me.columnIconsSize + me.realFontSize_x;
804 me.columnNameOrigin = me.columnPingOrigin + me.columnPingSize + me.realFontSize_x;
805 me.columnMapOrigin = me.columnNameOrigin + me.columnNameSize + me.realFontSize_x;
806 me.columnTypeOrigin = me.columnMapOrigin + me.columnMapSize + me.realFontSize_x;
807 me.columnPlayersOrigin = me.columnTypeOrigin + me.columnTypeSize + me.realFontSize_x;
809 me.positionSortButton(me, me.sortButton1, me.columnPingOrigin, me.columnPingSize, _("Ping"), ServerList_PingSort_Click);
810 me.positionSortButton(me, me.sortButton2, me.columnNameOrigin, me.columnNameSize, _("Host name"), ServerList_NameSort_Click);
811 me.positionSortButton(me, me.sortButton3, me.columnMapOrigin, me.columnMapSize, _("Map"), ServerList_MapSort_Click);
812 me.positionSortButton(me, me.sortButton4, me.columnTypeOrigin, me.columnTypeSize, _("Type"), ServerList_TypeSort_Click);
813 me.positionSortButton(me, me.sortButton5, me.columnPlayersOrigin, me.columnPlayersSize, _("Players"), ServerList_PlayerSort_Click);
816 f = me.currentSortField;
819 me.currentSortField = -1;
820 me.setSortOrder(me, f, me.currentSortOrder); // force resetting the sort order
823 void ServerList_Connect_Click(entity btn, entity me)
825 if(me.ipAddressBox.text == "")
826 localcmd("connect ", me.selectedServer, "\n");
828 localcmd("connect ", me.ipAddressBox.text, "\n");
830 void ServerList_Favorite_Click(entity btn, entity me)
833 ipstr = netaddress_resolve(me.ipAddressBox.text, 26000);
836 ToggleFavorite(me.ipAddressBox.text);
837 me.ipAddressBoxFocused = -1;
840 void ServerList_Info_Click(entity btn, entity me)
842 main.serverInfoDialog.loadServerInfo(main.serverInfoDialog, XonoticServerList_MapItems(me.selectedItem));
843 DialogOpenButton_Click(me, main.serverInfoDialog);
845 void XonoticServerList_clickListBoxItem(entity me, float i, vector where)
847 float num = XonoticServerList_MapItems(i);
848 if(num == me.lastClickedServer)
849 if(time < me.lastClickedTime + 0.3)
852 ServerList_Connect_Click(NULL, me);
854 me.lastClickedServer = num;
855 me.lastClickedTime = time;
857 void XonoticServerList_drawListBoxItem(entity me, float i, vector absSize, float isSelected)
859 // layout: Ping, Server name, Map name, NP, TP, MP
864 float m, pure, freeslots, j, sflags;
865 string s, typestr, versionstr, k, v, modname;
867 float cache = XonoticServerList_MapItems(i);
868 //print(sprintf("time: %f, i: %d, cache: %d, nitems: %d\n", time, i, cache, me.nItems));
872 entity catent = Get_Cat_Ent(-cache);
873 if(catent) { draw_Text(me.realUpperMargin * eY + (me.columnNameOrigin + (me.columnNameSize - draw_TextWidth(catent.cat_string, 0, me.realFontSize)) * 0.5) * eX, catent.cat_string, me.realFontSize, '1 1 1', SKINALPHA_TEXT, 0); return; }
877 draw_Fill('0 0 0', '1 1 0', SKINCOLOR_LISTBOX_SELECTED, SKINALPHA_LISTBOX_SELECTED);
879 s = gethostcachestring(SLIST_FIELD_QCSTATUS, cache);
880 m = tokenizebyseparator(s, ":");
885 versionstr = argv(1);
891 for(j = 2; j < m; ++j)
895 k = substring(argv(j), 0, 1);
896 v = substring(argv(j), 1, -1);
907 #ifdef COMPAT_NO_MOD_IS_XONOTIC
913 SLIST_FIELD_MOD = gethostcacheindexforkey("mod");
914 s = gethostcachestring(SLIST_FIELD_MOD, cache);
916 if(modname == "Xonotic")
920 // list the mods here on which the pure server check actually works
921 if(modname != "Xonotic")
922 if(modname != "MinstaGib")
925 if(modname != "NewToys")
928 if(gethostcachenumber(SLIST_FIELD_FREESLOTS, cache) <= 0)
929 theAlpha = SKINALPHA_SERVERLIST_FULL;
930 else if(freeslots == 0)
931 theAlpha = SKINALPHA_SERVERLIST_FULL; // g_maxplayers support
932 else if not(gethostcachenumber(SLIST_FIELD_NUMHUMANS, cache))
933 theAlpha = SKINALPHA_SERVERLIST_EMPTY;
937 p = gethostcachenumber(SLIST_FIELD_PING, cache);
940 #define PING_HIGH 500
942 theColor = SKINCOLOR_SERVERLIST_LOWPING + (SKINCOLOR_SERVERLIST_MEDPING - SKINCOLOR_SERVERLIST_LOWPING) * (p / PING_LOW);
943 else if(p < PING_MED)
944 theColor = SKINCOLOR_SERVERLIST_MEDPING + (SKINCOLOR_SERVERLIST_HIGHPING - SKINCOLOR_SERVERLIST_MEDPING) * ((p - PING_LOW) / (PING_MED - PING_LOW));
945 else if(p < PING_HIGH)
947 theColor = SKINCOLOR_SERVERLIST_HIGHPING;
948 theAlpha *= 1 + (SKINALPHA_SERVERLIST_HIGHPING - 1) * ((p - PING_MED) / (PING_HIGH - PING_MED));
953 theAlpha *= SKINALPHA_SERVERLIST_HIGHPING;
956 if(gethostcachenumber(SLIST_FIELD_ISFAVORITE, cache))
958 theColor = theColor * (1 - SKINALPHA_SERVERLIST_FAVORITE) + SKINCOLOR_SERVERLIST_FAVORITE * SKINALPHA_SERVERLIST_FAVORITE;
959 theAlpha = theAlpha * (1 - SKINALPHA_SERVERLIST_FAVORITE) + SKINALPHA_SERVERLIST_FAVORITE;
962 s = gethostcachestring(SLIST_FIELD_CNAME, cache);
965 if(substring(s, 0, 1) == "[")
970 else if(strstrofs("0123456789", substring(s, 0, 1), 0) >= 0)
976 q = stof(substring(crypto_getencryptlevel(s), 0, 1));
977 if((q <= 0 && cvar("crypto_aeslevel") >= 3) || (q >= 3 && cvar("crypto_aeslevel") <= 0))
979 theColor = SKINCOLOR_SERVERLIST_IMPOSSIBLE;
980 theAlpha = SKINALPHA_SERVERLIST_IMPOSSIBLE;
985 if(cvar("crypto_aeslevel") >= 2)
990 if(cvar("crypto_aeslevel") >= 1)
1000 // 2: AES recommended but not available
1001 // 3: AES possible and will be used
1002 // 4: AES recommended and will be used
1006 vector iconSize = '0 0 0';
1007 iconSize_y = me.realFontSize_y * me.iconsSizeFactor;
1008 iconSize_x = me.realFontSize_x * me.iconsSizeFactor;
1010 vector iconPos = '0 0 0';
1011 iconPos_x = (me.columnIconsSize - 3 * iconSize_x) * 0.5;
1012 iconPos_y = (1 - iconSize_y) * 0.5;
1016 if not(me.seenIPv4 && me.seenIPv6)
1018 iconPos_x += iconSize_x * 0.5;
1020 else if(me.seenIPv4 && me.seenIPv6)
1024 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_ipv6"), 0); // PRECACHE_PIC_MIPMAP
1026 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_ipv4"), 0); // PRECACHE_PIC_MIPMAP
1028 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1029 iconPos_x += iconSize_x;
1034 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_aeslevel", ftos(q)), 0); // PRECACHE_PIC_MIPMAP
1035 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1037 iconPos_x += iconSize_x;
1039 if(modname == "Xonotic")
1043 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_pure1"), PRECACHE_PIC_MIPMAP);
1044 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1049 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_mod_", modname), PRECACHE_PIC_MIPMAP);
1050 if(draw_PictureSize(n) == '0 0 0')
1051 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_mod_"), PRECACHE_PIC_MIPMAP);
1053 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1055 draw_Picture(iconPos, n, iconSize, '1 1 1', SKINALPHA_SERVERLIST_ICON_NONPURE);
1057 iconPos_x += iconSize_x;
1059 if(sflags >= 0 && (sflags & SERVERFLAG_PLAYERSTATS))
1061 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_stats1"), 0); // PRECACHE_PIC_MIPMAP
1062 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1064 iconPos_x += iconSize_x;
1068 draw_Text(me.realUpperMargin * eY + (me.columnPingOrigin + me.columnPingSize - draw_TextWidth(s, 0, me.realFontSize)) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1069 s = draw_TextShortenToWidth(gethostcachestring(SLIST_FIELD_NAME, cache), me.columnNameSize, 0, me.realFontSize);
1070 draw_Text(me.realUpperMargin * eY + me.columnNameOrigin * eX, s, me.realFontSize, theColor, theAlpha, 0);
1071 s = draw_TextShortenToWidth(gethostcachestring(SLIST_FIELD_MAP, cache), me.columnMapSize, 0, me.realFontSize);
1072 draw_Text(me.realUpperMargin * eY + (me.columnMapOrigin + (me.columnMapSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1073 s = draw_TextShortenToWidth(typestr, me.columnTypeSize, 0, me.realFontSize);
1074 draw_Text(me.realUpperMargin * eY + (me.columnTypeOrigin + (me.columnTypeSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1075 s = strcat(ftos(gethostcachenumber(SLIST_FIELD_NUMHUMANS, cache)), "/", ftos(gethostcachenumber(SLIST_FIELD_MAXPLAYERS, cache)));
1076 draw_Text(me.realUpperMargin * eY + (me.columnPlayersOrigin + (me.columnPlayersSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1079 float XonoticServerList_keyDown(entity me, float scan, float ascii, float shift)
1081 float i = XonoticServerList_MapItems(me.selectedItem);
1084 org = boxToGlobal(eY * (me.selectedItem * me.itemHeight - me.scrollPos), me.origin, me.size);
1085 sz = boxToGlobalSize(eY * me.itemHeight + eX * (1 - me.controlWidth), me.size);
1087 if(scan == K_ENTER || scan == K_KP_ENTER)
1089 ServerList_Connect_Click(NULL, me);
1092 else if(scan == K_MOUSE2 || scan == K_SPACE)
1094 if((me.nItems != 0) && (i >= 0))
1096 main.serverInfoDialog.loadServerInfo(main.serverInfoDialog, i);
1097 DialogOpenButton_Click_withCoords(me, main.serverInfoDialog, org, sz);
1102 else if(scan == K_INS || scan == K_MOUSE3 || scan == K_KP_INS)
1104 if((me.nItems != 0) && (i >= 0))
1106 ToggleFavorite(me.selectedServer);
1107 me.ipAddressBoxFocused = -1;
1112 else if(SUPER(XonoticServerList).keyDown(me, scan, ascii, shift))
1114 else if(!me.controlledTextbox)
1117 return me.controlledTextbox.keyDown(me.controlledTextbox, scan, ascii, shift);