2 CLASS(XonoticServerList) EXTENDS(XonoticListBox)
3 METHOD(XonoticServerList, configureXonoticServerList, void(entity))
4 ATTRIB(XonoticServerList, rowsPerItem, float, 1)
5 METHOD(XonoticServerList, draw, void(entity))
6 METHOD(XonoticServerList, drawListBoxItem, void(entity, float, vector, float))
7 METHOD(XonoticServerList, clickListBoxItem, void(entity, float, vector))
8 METHOD(XonoticServerList, resizeNotify, void(entity, vector, vector, vector, vector))
9 METHOD(XonoticServerList, keyDown, float(entity, float, float, float))
11 ATTRIB(XonoticServerList, iconsSizeFactor, float, 0.85)
13 ATTRIB(XonoticServerList, realFontSize, vector, '0 0 0')
14 ATTRIB(XonoticServerList, realUpperMargin, float, 0)
15 ATTRIB(XonoticServerList, columnIconsOrigin, float, 0)
16 ATTRIB(XonoticServerList, columnIconsSize, float, 0)
17 ATTRIB(XonoticServerList, columnPingOrigin, float, 0)
18 ATTRIB(XonoticServerList, columnPingSize, float, 0)
19 ATTRIB(XonoticServerList, columnNameOrigin, float, 0)
20 ATTRIB(XonoticServerList, columnNameSize, float, 0)
21 ATTRIB(XonoticServerList, columnMapOrigin, float, 0)
22 ATTRIB(XonoticServerList, columnMapSize, float, 0)
23 ATTRIB(XonoticServerList, columnTypeOrigin, float, 0)
24 ATTRIB(XonoticServerList, columnTypeSize, float, 0)
25 ATTRIB(XonoticServerList, columnPlayersOrigin, float, 0)
26 ATTRIB(XonoticServerList, columnPlayersSize, float, 0)
28 ATTRIB(XonoticServerList, selectedServer, string, string_null) // to restore selected server when needed
29 METHOD(XonoticServerList, setSelected, void(entity, float))
30 METHOD(XonoticServerList, setSortOrder, void(entity, float, float))
31 ATTRIB(XonoticServerList, filterShowEmpty, float, 1)
32 ATTRIB(XonoticServerList, filterShowFull, float, 1)
33 ATTRIB(XonoticServerList, filterString, string, string_null)
34 ATTRIB(XonoticServerList, controlledTextbox, entity, NULL)
35 ATTRIB(XonoticServerList, ipAddressBox, entity, NULL)
36 ATTRIB(XonoticServerList, favoriteButton, entity, NULL)
37 ATTRIB(XonoticServerList, nextRefreshTime, float, 0)
38 METHOD(XonoticServerList, refreshServerList, void(entity, float)) // refresh mode: 0 = just reparametrize, 1 = send new requests, 2 = clear
39 ATTRIB(XonoticServerList, needsRefresh, float, 1)
40 METHOD(XonoticServerList, focusEnter, void(entity))
41 METHOD(XonoticServerList, positionSortButton, void(entity, entity, float, float, string, void(entity, entity)))
42 ATTRIB(XonoticServerList, sortButton1, entity, NULL)
43 ATTRIB(XonoticServerList, sortButton2, entity, NULL)
44 ATTRIB(XonoticServerList, sortButton3, entity, NULL)
45 ATTRIB(XonoticServerList, sortButton4, entity, NULL)
46 ATTRIB(XonoticServerList, sortButton5, entity, NULL)
47 ATTRIB(XonoticServerList, connectButton, entity, NULL)
48 ATTRIB(XonoticServerList, infoButton, entity, NULL)
49 ATTRIB(XonoticServerList, currentSortOrder, float, 0)
50 ATTRIB(XonoticServerList, currentSortField, float, -1)
51 ATTRIB(XonoticServerList, lastClickedServer, float, -1)
52 ATTRIB(XonoticServerList, lastClickedTime, float, 0)
54 ATTRIB(XonoticServerList, ipAddressBoxFocused, float, -1)
56 ATTRIB(XonoticServerList, seenIPv4, float, 0)
57 ATTRIB(XonoticServerList, seenIPv6, float, 0)
58 ENDCLASS(XonoticServerList)
59 entity makeXonoticServerList();
61 #ifndef IMPLEMENTATION
62 var float autocvar_menu_serverlist_categories = TRUE;
63 var float autocvar_menu_serverlist_categories_onlyifmultiple = TRUE;
64 var float autocvar_menu_serverlist_purethreshold = 10;
65 var string autocvar_menu_serverlist_recommended = "76.124.107.5:26004";
67 // server cache fields
68 #define SLIST_FIELDS \
69 SLIST_FIELD(CNAME, "cname") \
70 SLIST_FIELD(PING, "ping") \
71 SLIST_FIELD(GAME, "game") \
72 SLIST_FIELD(MOD, "mod") \
73 SLIST_FIELD(MAP, "map") \
74 SLIST_FIELD(NAME, "name") \
75 SLIST_FIELD(MAXPLAYERS, "maxplayers") \
76 SLIST_FIELD(NUMPLAYERS, "numplayers") \
77 SLIST_FIELD(NUMHUMANS, "numhumans") \
78 SLIST_FIELD(NUMBOTS, "numbots") \
79 SLIST_FIELD(PROTOCOL, "protocol") \
80 SLIST_FIELD(FREESLOTS, "freeslots") \
81 SLIST_FIELD(PLAYERS, "players") \
82 SLIST_FIELD(QCSTATUS, "qcstatus") \
83 SLIST_FIELD(CATEGORY, "category") \
84 SLIST_FIELD(ISFAVORITE, "isfavorite")
86 #define SLIST_FIELD(suffix,name) float SLIST_FIELD_##suffix;
91 float SLSF_DESCENDING = 1;
92 float SLSF_FAVORITES = 2;
93 float SLSF_CATEGORIES = 4;
95 float Get_Cat_Num_FromString(string input);
96 entity Get_Cat_Ent(float catnum);
98 float IsServerInList(string list, string srv);
99 #define IsFavorite(srv) IsServerInList(cvar_string("net_slist_favorites"), srv)
100 #define IsRecommended(srv) IsServerInList(cvar_string("menu_serverlist_recommended"), srv) // todo: use update notification instead of cvar
102 float CheckCategoryOverride(float cat);
103 float CheckCategoryForEntry(float entry);
104 float m_getserverlistentrycategory(float entry) { return CheckCategoryOverride(CheckCategoryForEntry(entry)); }
106 // fields for category entities
107 #define MAX_CATEGORIES 9
108 #define CATEGORY_FIRST 1
109 entity categories[MAX_CATEGORIES];
110 float category_ent_count;
113 .string cat_override_string;
116 // fields for drawing categories
117 float category_name[MAX_CATEGORIES];
118 float category_item[MAX_CATEGORIES];
119 float category_draw_count;
122 SLIST_CATEGORY(CAT_FAVORITED, "", "", _("Favorites")) \
123 SLIST_CATEGORY(CAT_RECOMMENDED, "", "CAT_SERVERS", _("Recommended")) \
124 SLIST_CATEGORY(CAT_NORMAL, "", "CAT_SERVERS", _("Normal Servers")) \
125 SLIST_CATEGORY(CAT_SERVERS, "CAT_NORMAL", "CAT_SERVERS", _("Servers")) \
126 SLIST_CATEGORY(CAT_XPM, "CAT_NORMAL", "CAT_SERVERS", _("Competitive Mode")) \
127 SLIST_CATEGORY(CAT_MODIFIED, "", "CAT_SERVERS", _("Modified Servers")) \
128 SLIST_CATEGORY(CAT_OVERKILL, "", "CAT_SERVERS", _("Overkill Mode")) \
129 SLIST_CATEGORY(CAT_MINSTAGIB, "", "CAT_SERVERS", _("MinstaGib Mode")) \
130 SLIST_CATEGORY(CAT_DEFRAG, "", "CAT_SERVERS", _("Defrag Mode"))
132 // C is stupid, must use extra macro for concatenation
133 #define SLIST_ADD_CAT_CVAR(name,default) var string autocvar_menu_serverlist_categories_##name##_override = default;
134 #define SLIST_CATEGORY(name,enoverride,deoverride,string) \
135 SLIST_ADD_CAT_CVAR(name, enoverride) \
137 void RegisterSLCategory_##name() \
139 SET_FIELD_COUNT(name, CATEGORY_FIRST, category_ent_count) \
140 CHECK_MAX_COUNT(name, MAX_CATEGORIES, category_ent_count, "SLIST_CATEGORY") \
141 entity cat = spawn(); \
142 categories[name - 1] = cat; \
143 cat.classname = "slist_category"; \
144 cat.cat_name = strzone(#name); \
145 cat.cat_override_string = strzone((autocvar_menu_serverlist_categories ? \
146 autocvar_menu_serverlist_categories_##name##_override \
149 cat.cat_string = strzone(string); \
151 ACCUMULATE_FUNCTION(RegisterSLCategories, RegisterSLCategory_##name);
155 void RegisterSLCategories_Done()
159 for(i = 0; i < category_ent_count; ++i)
161 s = categories[i].cat_override_string;
162 if((s != "") && (s != categories[i].cat_name))
164 catnum = Get_Cat_Num_FromString(s);
167 strunzone(categories[i].cat_override_string);
168 categories[i].cat_override = catnum;
172 strunzone(categories[i].cat_override_string);
173 categories[i].cat_override = 0;
176 ACCUMULATE_FUNCTION(RegisterSLCategories, RegisterSLCategories_Done);
178 #undef SLIST_ADD_CAT_CVAR
179 #undef SLIST_CATEGORY
182 void ServerList_Connect_Click(entity btn, entity me);
183 void ServerList_ShowEmpty_Click(entity box, entity me);
184 void ServerList_ShowFull_Click(entity box, entity me);
185 void ServerList_Filter_Change(entity box, entity me);
186 void ServerList_Favorite_Click(entity btn, entity me);
187 void ServerList_Info_Click(entity btn, entity me);
188 void ServerList_Update_favoriteButton(entity btn, entity me);
192 #ifdef IMPLEMENTATION
194 // Supporting Functions
195 float Get_Cat_Num_FromString(string input)
198 for(i = 0; i < category_ent_count; ++i) { if(categories[i].cat_name == input) { return (i + 1); } }
199 print(sprintf("Get_Cat_Num_FromString('%s'): Improper category name!\n", input));
202 entity Get_Cat_Ent(float catnum)
204 if((catnum > 0) && (catnum <= category_ent_count))
206 return categories[catnum - 1];
210 error(sprintf("Get_Cat_Ent(%d): Improper category number!\n", catnum));
216 float IsServerInList(string list, string srv)
222 srv = netaddress_resolve(srv, 26000);
225 p = crypto_getidfp(srv);
226 n = tokenize_console(list);
227 for(i = 0; i < n; ++i)
229 if(substring(argv(i), 0, 1) != "[" && strlen(argv(i)) == 44 && strstrofs(argv(i), ".", 0) < 0)
237 if(srv == netaddress_resolve(argv(i), 26000))
244 float CheckCategoryOverride(float cat)
246 entity catent = Get_Cat_Ent(cat);
249 if(catent.cat_override) { return catent.cat_override; }
254 error(sprintf("CheckCategoryOverride(%d): Improper category number!\n", cat));
259 float CheckCategoryForEntry(float entry)
261 string s, k, v, modtype = "";
263 s = gethostcachestring(SLIST_FIELD_QCSTATUS, entry);
264 m = tokenizebyseparator(s, ":");
268 // typestr = argv(0);
269 // versionstr = argv(1);
275 for(j = 2; j < m; ++j)
279 k = substring(argv(j), 0, 1);
280 v = substring(argv(j), 1, -1);
281 if(k == "P") { impure = stof(v); }
282 else if(k == "M") { modtype = strtolower(v); }
285 //print(sprintf("modtype = %s\n", modtype));
287 if(impure > autocvar_menu_serverlist_purethreshold) { impure = TRUE; }
288 else { impure = FALSE; }
290 if(gethostcachenumber(SLIST_FIELD_ISFAVORITE, entry)) { return CAT_FAVORITED; }
291 if(IsRecommended(gethostcachestring(SLIST_FIELD_CNAME, entry))) { return CAT_RECOMMENDED; }
292 else if(modtype != "xonotic")
296 // old servers which don't report their mod name are considered modified now
297 case "": { return CAT_MODIFIED; }
299 case "xpm": { return CAT_XPM; }
300 case "minstagib": { return CAT_MINSTAGIB; }
301 case "overkill": { return CAT_OVERKILL; }
303 // "cts" is allowed as compat, xdf is replacement
305 case "xdf": { return CAT_DEFRAG; }
307 //if(modname != "CTS")
308 //if(modname != "NIX")
309 //if(modname != "NewToys")
311 default: { dprint(sprintf("Found strange mod type: %s\n", modtype)); return CAT_MODIFIED; }
314 else { return (impure ? CAT_MODIFIED : CAT_NORMAL); }
316 // should never hit this point
317 error(sprintf("CheckCategoryForEntry(%d): Function fell through without normal return!\n", entry));
321 float XonoticServerList_MapItems(float num)
325 if not(category_draw_count) { return num; } // there are no categories to process
327 for(i = 0, n = 1; n <= category_draw_count; ++i, ++n)
329 //print(sprintf("num: %d, i: %d, category_draw_count: %d, category_item[i]: %d\n", num, i, category_draw_count, category_item[i]));
330 if(category_item[i] == (num - i)) { /*print("inserting cat... \\/\n");*/ return -category_name[i]; }
331 else if(n == category_draw_count) { /*print("end item... \\/\n");*/ return (num - n); }
332 else if((num - i) <= category_item[n]) { /*print("next item... \\/\n");*/ return (num - n); }
335 // should never hit this point
336 error("wtf XonoticServerList_MapItems fail?");
340 void ToggleFavorite(string srv)
342 string s, s0, s1, s2, srv_resolved, p;
344 srv_resolved = netaddress_resolve(srv, 26000);
345 p = crypto_getidfp(srv_resolved);
346 s = cvar_string("net_slist_favorites");
347 n = tokenize_console(s);
349 for(i = 0; i < n; ++i)
351 if(substring(argv(i), 0, 1) != "[" && strlen(argv(i)) == 44 && strstrofs(argv(i), ".", 0) < 0)
359 if(srv_resolved != netaddress_resolve(argv(i), 26000))
364 s0 = substring(s, 0, argv_end_index(i - 1));
366 s2 = substring(s, argv_start_index(i + 1), -1);
367 if(s0 != "" && s2 != "")
369 cvar_set("net_slist_favorites", strcat(s0, s1, s2));
370 s = cvar_string("net_slist_favorites");
371 n = tokenize_console(s);
382 cvar_set("net_slist_favorites", strcat(s, s1, p));
384 cvar_set("net_slist_favorites", strcat(s, s1, srv));
390 void ServerList_Update_favoriteButton(entity btn, entity me)
392 me.favoriteButton.setText(me.favoriteButton,
393 (IsFavorite(me.ipAddressBox.text) ?
394 _("Remove") : _("Bookmark")
399 entity makeXonoticServerList()
402 me = spawnXonoticServerList();
403 me.configureXonoticServerList(me);
406 void XonoticServerList_configureXonoticServerList(entity me)
408 me.configureXonoticListBox(me);
411 #define SLIST_FIELD(suffix,name) SLIST_FIELD_##suffix = gethostcacheindexforkey(name);
418 void XonoticServerList_setSelected(entity me, float i)
420 // todo: add logic to skip categories
422 save = me.selectedItem;
423 SUPER(XonoticServerList).setSelected(me, i);
425 if(me.selectedItem == save)
431 if(gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT) != XonoticServerList_MapItems(me.nItems))
432 { error("^1XonoticServerList_setSelected(); ERROR: ^7Host cache viewcount mismatches nItems!\n"); return; } // sorry, it would be wrong
434 #define SET_SELECTED_SERVER(cachenum) \
435 if(me.selectedServer) { strunzone(me.selectedServer); } \
436 me.selectedServer = strzone(gethostcachestring(SLIST_FIELD_CNAME, cachenum)); \
437 me.ipAddressBox.setText(me.ipAddressBox, me.selectedServer); \
438 me.ipAddressBox.cursorPos = strlen(me.selectedServer); \
439 me.ipAddressBoxFocused = -1;
441 num = XonoticServerList_MapItems(me.selectedItem);
443 if(num >= 0) { SET_SELECTED_SERVER(num) return; }
444 else if(save > me.selectedItem)
446 if(me.selectedItem == 0) { return; }
449 SUPER(XonoticServerList).setSelected(me, me.selectedItem - 1);
450 num = XonoticServerList_MapItems(me.selectedItem);
451 if(num >= 0) { SET_SELECTED_SERVER(num); return; }
455 else if(save < me.selectedItem)
457 if(me.selectedItem == me.nItems) { return; }
460 SUPER(XonoticServerList).setSelected(me, me.selectedItem + 1);
461 num = XonoticServerList_MapItems(me.selectedItem);
462 if(num >= 0) { SET_SELECTED_SERVER(num); return; }
466 //else { error("how the fuck did this happen?\n"); }
468 void XonoticServerList_refreshServerList(entity me, float mode)
470 // 0: just reparametrize
471 // 1: also ask for new servers
473 //print("refresh of type ", ftos(mode), "\n");
474 /* if(mode == 2) // borken
477 localcmd("net_slist\n");
478 me.needsRefresh = 1; // net_slist kills sort order, so we need to restore it later
484 string s, typestr, modstr;
487 m = strstrofs(s, ":", 0);
490 typestr = substring(s, 0, m);
491 s = substring(s, m + 1, strlen(s) - m - 1);
492 while(substring(s, 0, 1) == " ")
493 s = substring(s, 1, strlen(s) - 1);
498 modstr = cvar_string("menu_slist_modfilter");
500 m = SLIST_MASK_AND - 1;
501 resethostcachemasks();
503 // ping: reject negative ping (no idea why this happens in the first place, engine bug)
504 sethostcachemasknumber(++m, SLIST_FIELD_PING, 0, SLIST_TEST_GREATEREQUAL);
507 if(!me.filterShowFull)
509 sethostcachemasknumber(++m, SLIST_FIELD_FREESLOTS, 1, SLIST_TEST_GREATEREQUAL); // legacy
510 sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, ":S0:", SLIST_TEST_NOTCONTAIN); // g_maxplayers support
514 if(!me.filterShowEmpty)
515 sethostcachemasknumber(++m, SLIST_FIELD_NUMHUMANS, 1, SLIST_TEST_GREATEREQUAL);
517 // gametype filtering
519 sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, strcat(typestr, ":"), SLIST_TEST_STARTSWITH);
524 if(substring(modstr, 0, 1) == "!")
525 sethostcachemaskstring(++m, SLIST_FIELD_MOD, resolvemod(substring(modstr, 1, strlen(modstr) - 1)), SLIST_TEST_NOTEQUAL);
527 sethostcachemaskstring(++m, SLIST_FIELD_MOD, resolvemod(modstr), SLIST_TEST_EQUAL);
531 n = tokenizebyseparator(_Nex_ExtResponseSystem_BannedServers, " ");
532 for(i = 0; i < n; ++i)
534 sethostcachemaskstring(++m, SLIST_FIELD_CNAME, argv(i), SLIST_TEST_NOTSTARTSWITH);
536 m = SLIST_MASK_OR - 1;
539 sethostcachemaskstring(++m, SLIST_FIELD_NAME, s, SLIST_TEST_CONTAINS);
540 sethostcachemaskstring(++m, SLIST_FIELD_MAP, s, SLIST_TEST_CONTAINS);
541 sethostcachemaskstring(++m, SLIST_FIELD_PLAYERS, s, SLIST_TEST_CONTAINS);
542 sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, strcat(s, ":"), SLIST_TEST_STARTSWITH);
546 //listflags |= SLSF_FAVORITES;
547 listflags |= SLSF_CATEGORIES;
548 if(me.currentSortOrder < 0) { listflags |= SLSF_DESCENDING; }
549 sethostcachesort(me.currentSortField, listflags);
552 if(mode >= 1) { refreshhostcache(); }
554 void XonoticServerList_focusEnter(entity me)
556 if(time < me.nextRefreshTime)
558 //print("sorry, no refresh yet\n");
561 me.nextRefreshTime = time + 10;
562 me.refreshServerList(me, 1);
565 void XonoticServerList_draw(entity me)
567 float i, found, owned, num;
569 if(_Nex_ExtResponseSystem_BannedServersNeedsRefresh)
573 _Nex_ExtResponseSystem_BannedServersNeedsRefresh = 0;
576 if(me.currentSortField == -1)
578 me.setSortOrder(me, SLIST_FIELD_PING, +1);
579 me.refreshServerList(me, 2);
581 else if(me.needsRefresh == 1)
583 me.needsRefresh = 2; // delay by one frame to make sure "slist" has been executed
585 else if(me.needsRefresh == 2)
588 me.refreshServerList(me, 0);
591 owned = ((me.selectedServer == me.ipAddressBox.text) && (me.ipAddressBox.text != ""));
593 for(i = 0; i < category_draw_count; ++i) { category_name[i] = -1; category_item[i] = -1; }
594 category_draw_count = 0;
596 if(autocvar_menu_serverlist_categories >= 0) // if less than 0, don't even draw a category heading for favorites
598 float itemcount = gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT);
599 me.nItems = itemcount;
601 //float visible = floor(me.scrollPos / me.itemHeight);
602 // ^ unfortunately no such optimization can be made-- we must process through the
603 // entire list, otherwise there is no way to know which item is first in its category.
606 for(i = 0; i < itemcount; ++i)
608 cat = gethostcachenumber(SLIST_FIELD_CATEGORY, i);
611 if(category_draw_count == 0)
613 category_name[category_draw_count] = cat;
614 category_item[category_draw_count] = i;
615 ++category_draw_count;
621 for(x = 0; x < category_draw_count; ++x) { if(cat == category_name[x]) { found = 1; } }
624 category_name[category_draw_count] = cat;
625 category_item[category_draw_count] = i;
626 ++category_draw_count;
632 if(autocvar_menu_serverlist_categories_onlyifmultiple && (category_draw_count == 1))
634 category_name[0] = category_name[1] = -1;
635 category_item[0] = category_item[1] = -1;
636 category_draw_count = 0;
637 me.nItems = itemcount;
640 else { me.nItems = gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT); }
642 me.connectButton.disabled = ((me.nItems == 0) && (me.ipAddressBox.text == ""));
643 me.infoButton.disabled = ((me.nItems == 0) || !owned);
644 me.favoriteButton.disabled = ((me.nItems == 0) && (me.ipAddressBox.text == ""));
647 if(me.selectedServer)
649 for(i = 0; i < me.nItems; ++i)
651 num = XonoticServerList_MapItems(i);
654 if(gethostcachestring(SLIST_FIELD_CNAME, num) == me.selectedServer)
656 if(i != me.selectedItem)
658 me.lastClickedServer = -1;
671 if(me.selectedItem >= me.nItems) { me.selectedItem = me.nItems - 1; }
672 if(me.selectedServer) { strunzone(me.selectedServer); }
674 num = XonoticServerList_MapItems(me.selectedItem);
675 if(num >= 0) { me.selectedServer = strzone(gethostcachestring(SLIST_FIELD_CNAME, num)); }
681 if(me.selectedServer != me.ipAddressBox.text)
683 me.ipAddressBox.setText(me.ipAddressBox, me.selectedServer);
684 me.ipAddressBox.cursorPos = strlen(me.selectedServer);
685 me.ipAddressBoxFocused = -1;
689 if(me.ipAddressBoxFocused != me.ipAddressBox.focused)
691 if(me.ipAddressBox.focused || me.ipAddressBoxFocused < 0)
692 ServerList_Update_favoriteButton(NULL, me);
693 me.ipAddressBoxFocused = me.ipAddressBox.focused;
696 SUPER(XonoticServerList).draw(me);
698 void ServerList_PingSort_Click(entity btn, entity me)
700 me.setSortOrder(me, SLIST_FIELD_PING, +1);
702 void ServerList_NameSort_Click(entity btn, entity me)
704 me.setSortOrder(me, SLIST_FIELD_NAME, -1); // why?
706 void ServerList_MapSort_Click(entity btn, entity me)
708 me.setSortOrder(me, SLIST_FIELD_MAP, -1); // why?
710 void ServerList_PlayerSort_Click(entity btn, entity me)
712 me.setSortOrder(me, SLIST_FIELD_NUMHUMANS, -1);
714 void ServerList_TypeSort_Click(entity btn, entity me)
719 m = strstrofs(s, ":", 0);
722 s = substring(s, 0, m);
723 while(substring(s, m+1, 1) == " ") // skip spaces
729 for(i = 1; ; i *= 2) // 20 modes ought to be enough for anyone
731 t = MapInfo_Type_ToString(i);
733 if(t == "") // it repeats (default case)
736 // choose the first one
737 s = MapInfo_Type_ToString(1);
742 // the type was found
743 // choose the next one
744 s = MapInfo_Type_ToString(i * 2);
746 s = MapInfo_Type_ToString(1);
753 s = strcat(s, substring(me.filterString, m+1, strlen(me.filterString) - m - 1));
755 me.controlledTextbox.setText(me.controlledTextbox, s);
756 me.controlledTextbox.keyDown(me.controlledTextbox, K_END, 0, 0);
757 me.controlledTextbox.keyUp(me.controlledTextbox, K_END, 0, 0);
758 //ServerList_Filter_Change(me.controlledTextbox, me);
760 void ServerList_Filter_Change(entity box, entity me)
763 strunzone(me.filterString);
765 me.filterString = strzone(box.text);
767 me.filterString = string_null;
768 me.refreshServerList(me, 0);
770 me.ipAddressBox.setText(me.ipAddressBox, "");
771 me.ipAddressBox.cursorPos = 0;
772 me.ipAddressBoxFocused = -1;
774 void ServerList_ShowEmpty_Click(entity box, entity me)
776 box.setChecked(box, me.filterShowEmpty = !me.filterShowEmpty);
777 me.refreshServerList(me, 0);
779 me.ipAddressBox.setText(me.ipAddressBox, "");
780 me.ipAddressBox.cursorPos = 0;
781 me.ipAddressBoxFocused = -1;
783 void ServerList_ShowFull_Click(entity box, entity me)
785 box.setChecked(box, me.filterShowFull = !me.filterShowFull);
786 me.refreshServerList(me, 0);
788 me.ipAddressBox.setText(me.ipAddressBox, "");
789 me.ipAddressBox.cursorPos = 0;
790 me.ipAddressBoxFocused = -1;
792 void XonoticServerList_setSortOrder(entity me, float fld, float direction)
794 if(me.currentSortField == fld)
795 direction = -me.currentSortOrder;
796 me.currentSortOrder = direction;
797 me.currentSortField = fld;
798 me.sortButton1.forcePressed = (fld == SLIST_FIELD_PING);
799 me.sortButton2.forcePressed = (fld == SLIST_FIELD_NAME);
800 me.sortButton3.forcePressed = (fld == SLIST_FIELD_MAP);
801 me.sortButton4.forcePressed = 0;
802 me.sortButton5.forcePressed = (fld == SLIST_FIELD_NUMHUMANS);
804 if(me.selectedServer)
805 strunzone(me.selectedServer);
806 me.selectedServer = string_null;
807 me.refreshServerList(me, 0);
809 void XonoticServerList_positionSortButton(entity me, entity btn, float theOrigin, float theSize, string theTitle, void(entity, entity) theFunc)
811 vector originInLBSpace, sizeInLBSpace;
812 originInLBSpace = eY * (-me.itemHeight);
813 sizeInLBSpace = eY * me.itemHeight + eX * (1 - me.controlWidth);
815 vector originInDialogSpace, sizeInDialogSpace;
816 originInDialogSpace = boxToGlobal(originInLBSpace, me.Container_origin, me.Container_size);
817 sizeInDialogSpace = boxToGlobalSize(sizeInLBSpace, me.Container_size);
819 btn.Container_origin_x = originInDialogSpace_x + sizeInDialogSpace_x * theOrigin;
820 btn.Container_size_x = sizeInDialogSpace_x * theSize;
821 btn.setText(btn, theTitle);
822 btn.onClick = theFunc;
823 btn.onClickEntity = me;
826 void XonoticServerList_resizeNotify(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
828 SUPER(XonoticServerList).resizeNotify(me, relOrigin, relSize, absOrigin, absSize);
830 me.realFontSize_y = me.fontSize / (absSize_y * me.itemHeight);
831 me.realFontSize_x = me.fontSize / (absSize_x * (1 - me.controlWidth));
832 me.realUpperMargin = 0.5 * (1 - me.realFontSize_y);
834 me.columnIconsOrigin = 0;
835 me.columnIconsSize = me.realFontSize_x * 4 * me.iconsSizeFactor;
836 me.columnPingSize = me.realFontSize_x * 3;
837 me.columnMapSize = me.realFontSize_x * 10;
838 me.columnTypeSize = me.realFontSize_x * 4;
839 me.columnPlayersSize = me.realFontSize_x * 5;
840 me.columnNameSize = 1 - me.columnPlayersSize - me.columnMapSize - me.columnPingSize - me.columnIconsSize - me.columnTypeSize - 5 * me.realFontSize_x;
841 me.columnPingOrigin = me.columnIconsOrigin + me.columnIconsSize + me.realFontSize_x;
842 me.columnNameOrigin = me.columnPingOrigin + me.columnPingSize + me.realFontSize_x;
843 me.columnMapOrigin = me.columnNameOrigin + me.columnNameSize + me.realFontSize_x;
844 me.columnTypeOrigin = me.columnMapOrigin + me.columnMapSize + me.realFontSize_x;
845 me.columnPlayersOrigin = me.columnTypeOrigin + me.columnTypeSize + me.realFontSize_x;
847 me.positionSortButton(me, me.sortButton1, me.columnPingOrigin, me.columnPingSize, _("Ping"), ServerList_PingSort_Click);
848 me.positionSortButton(me, me.sortButton2, me.columnNameOrigin, me.columnNameSize, _("Host name"), ServerList_NameSort_Click);
849 me.positionSortButton(me, me.sortButton3, me.columnMapOrigin, me.columnMapSize, _("Map"), ServerList_MapSort_Click);
850 me.positionSortButton(me, me.sortButton4, me.columnTypeOrigin, me.columnTypeSize, _("Type"), ServerList_TypeSort_Click);
851 me.positionSortButton(me, me.sortButton5, me.columnPlayersOrigin, me.columnPlayersSize, _("Players"), ServerList_PlayerSort_Click);
854 f = me.currentSortField;
857 me.currentSortField = -1;
858 me.setSortOrder(me, f, me.currentSortOrder); // force resetting the sort order
861 void ServerList_Connect_Click(entity btn, entity me)
863 localcmd(sprintf("connect %s\n",
864 ((me.ipAddressBox.text != "") ?
865 me.ipAddressBox.text : me.selectedServer
869 void ServerList_Favorite_Click(entity btn, entity me)
872 ipstr = netaddress_resolve(me.ipAddressBox.text, 26000);
875 ToggleFavorite(me.ipAddressBox.text);
876 me.ipAddressBoxFocused = -1;
879 void ServerList_Info_Click(entity btn, entity me)
881 main.serverInfoDialog.loadServerInfo(main.serverInfoDialog, XonoticServerList_MapItems(me.selectedItem));
882 DialogOpenButton_Click(me, main.serverInfoDialog);
884 void XonoticServerList_clickListBoxItem(entity me, float i, vector where)
886 float num = XonoticServerList_MapItems(i);
889 if(num == me.lastClickedServer)
890 if(time < me.lastClickedTime + 0.3)
893 ServerList_Connect_Click(NULL, me);
895 me.lastClickedServer = num;
896 me.lastClickedTime = time;
899 void XonoticServerList_drawListBoxItem(entity me, float i, vector absSize, float isSelected)
901 // layout: Ping, Server name, Map name, NP, TP, MP
906 float m, pure, freeslots, j, sflags;
907 string s, typestr, versionstr, k, v, modname;
909 float item = XonoticServerList_MapItems(i);
910 //print(sprintf("time: %f, i: %d, item: %d, nitems: %d\n", time, i, item, me.nItems));
914 entity catent = Get_Cat_Ent(-item);
918 eY * me.realUpperMargin
920 eX * (me.columnNameOrigin + (me.columnNameSize - draw_TextWidth(catent.cat_string, 0, me.realFontSize)) * 0.5),
932 draw_Fill('0 0 0', '1 1 0', SKINCOLOR_LISTBOX_SELECTED, SKINALPHA_LISTBOX_SELECTED);
934 s = gethostcachestring(SLIST_FIELD_QCSTATUS, item);
935 m = tokenizebyseparator(s, ":");
940 versionstr = argv(1);
946 for(j = 2; j < m; ++j)
950 k = substring(argv(j), 0, 1);
951 v = substring(argv(j), 1, -1);
962 #ifdef COMPAT_NO_MOD_IS_XONOTIC
968 SLIST_FIELD_MOD = gethostcacheindexforkey("mod");
969 s = gethostcachestring(SLIST_FIELD_MOD, item);
971 if(modname == "Xonotic")
975 // list the mods here on which the pure server check actually works
976 if(modname != "Xonotic")
977 if(modname != "MinstaGib")
980 if(modname != "NewToys")
983 if(gethostcachenumber(SLIST_FIELD_FREESLOTS, item) <= 0)
984 theAlpha = SKINALPHA_SERVERLIST_FULL;
985 else if(freeslots == 0)
986 theAlpha = SKINALPHA_SERVERLIST_FULL; // g_maxplayers support
987 else if not(gethostcachenumber(SLIST_FIELD_NUMHUMANS, item))
988 theAlpha = SKINALPHA_SERVERLIST_EMPTY;
992 p = gethostcachenumber(SLIST_FIELD_PING, item);
995 #define PING_HIGH 500
997 theColor = SKINCOLOR_SERVERLIST_LOWPING + (SKINCOLOR_SERVERLIST_MEDPING - SKINCOLOR_SERVERLIST_LOWPING) * (p / PING_LOW);
998 else if(p < PING_MED)
999 theColor = SKINCOLOR_SERVERLIST_MEDPING + (SKINCOLOR_SERVERLIST_HIGHPING - SKINCOLOR_SERVERLIST_MEDPING) * ((p - PING_LOW) / (PING_MED - PING_LOW));
1000 else if(p < PING_HIGH)
1002 theColor = SKINCOLOR_SERVERLIST_HIGHPING;
1003 theAlpha *= 1 + (SKINALPHA_SERVERLIST_HIGHPING - 1) * ((p - PING_MED) / (PING_HIGH - PING_MED));
1008 theAlpha *= SKINALPHA_SERVERLIST_HIGHPING;
1011 if(gethostcachenumber(SLIST_FIELD_ISFAVORITE, item))
1013 theColor = theColor * (1 - SKINALPHA_SERVERLIST_FAVORITE) + SKINCOLOR_SERVERLIST_FAVORITE * SKINALPHA_SERVERLIST_FAVORITE;
1014 theAlpha = theAlpha * (1 - SKINALPHA_SERVERLIST_FAVORITE) + SKINALPHA_SERVERLIST_FAVORITE;
1017 s = gethostcachestring(SLIST_FIELD_CNAME, item);
1020 if(substring(s, 0, 1) == "[")
1025 else if(strstrofs("0123456789", substring(s, 0, 1), 0) >= 0)
1031 q = stof(substring(crypto_getencryptlevel(s), 0, 1));
1032 if((q <= 0 && cvar("crypto_aeslevel") >= 3) || (q >= 3 && cvar("crypto_aeslevel") <= 0))
1034 theColor = SKINCOLOR_SERVERLIST_IMPOSSIBLE;
1035 theAlpha = SKINALPHA_SERVERLIST_IMPOSSIBLE;
1040 if(cvar("crypto_aeslevel") >= 2)
1045 if(cvar("crypto_aeslevel") >= 1)
1055 // 2: AES recommended but not available
1056 // 3: AES possible and will be used
1057 // 4: AES recommended and will be used
1063 vector iconSize = '0 0 0';
1064 iconSize_y = me.realFontSize_y * me.iconsSizeFactor;
1065 iconSize_x = me.realFontSize_x * me.iconsSizeFactor;
1067 vector iconPos = '0 0 0';
1068 iconPos_x = (me.columnIconsSize - 3 * iconSize_x) * 0.5;
1069 iconPos_y = (1 - iconSize_y) * 0.5;
1073 if not(me.seenIPv4 && me.seenIPv6)
1075 iconPos_x += iconSize_x * 0.5;
1077 else if(me.seenIPv4 && me.seenIPv6)
1081 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_ipv6"), 0); // PRECACHE_PIC_MIPMAP
1083 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_ipv4"), 0); // PRECACHE_PIC_MIPMAP
1085 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1086 iconPos_x += iconSize_x;
1091 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_aeslevel", ftos(q)), 0); // PRECACHE_PIC_MIPMAP
1092 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1094 iconPos_x += iconSize_x;
1096 if(modname == "Xonotic")
1100 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_pure1"), PRECACHE_PIC_MIPMAP);
1101 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1106 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_mod_", modname), PRECACHE_PIC_MIPMAP);
1107 if(draw_PictureSize(n) == '0 0 0')
1108 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_mod_"), PRECACHE_PIC_MIPMAP);
1110 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1112 draw_Picture(iconPos, n, iconSize, '1 1 1', SKINALPHA_SERVERLIST_ICON_NONPURE);
1114 iconPos_x += iconSize_x;
1116 if(sflags >= 0 && (sflags & SERVERFLAG_PLAYERSTATS))
1118 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_stats1"), 0); // PRECACHE_PIC_MIPMAP
1119 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1121 iconPos_x += iconSize_x;
1129 draw_Text(me.realUpperMargin * eY + (me.columnPingOrigin + me.columnPingSize - draw_TextWidth(s, 0, me.realFontSize)) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1132 s = draw_TextShortenToWidth(gethostcachestring(SLIST_FIELD_NAME, item), me.columnNameSize, 0, me.realFontSize);
1133 draw_Text(me.realUpperMargin * eY + me.columnNameOrigin * eX, s, me.realFontSize, theColor, theAlpha, 0);
1136 s = draw_TextShortenToWidth(gethostcachestring(SLIST_FIELD_MAP, item), me.columnMapSize, 0, me.realFontSize);
1137 draw_Text(me.realUpperMargin * eY + (me.columnMapOrigin + (me.columnMapSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1140 s = draw_TextShortenToWidth(typestr, me.columnTypeSize, 0, me.realFontSize);
1141 draw_Text(me.realUpperMargin * eY + (me.columnTypeOrigin + (me.columnTypeSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1143 // server playercount
1144 s = strcat(ftos(gethostcachenumber(SLIST_FIELD_NUMHUMANS, item)), "/", ftos(gethostcachenumber(SLIST_FIELD_MAXPLAYERS, item)));
1145 draw_Text(me.realUpperMargin * eY + (me.columnPlayersOrigin + (me.columnPlayersSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1148 float XonoticServerList_keyDown(entity me, float scan, float ascii, float shift)
1150 float i = XonoticServerList_MapItems(me.selectedItem);
1153 org = boxToGlobal(eY * (me.selectedItem * me.itemHeight - me.scrollPos), me.origin, me.size);
1154 sz = boxToGlobalSize(eY * me.itemHeight + eX * (1 - me.controlWidth), me.size);
1156 if(scan == K_ENTER || scan == K_KP_ENTER)
1158 ServerList_Connect_Click(NULL, me);
1161 else if(scan == K_MOUSE2 || scan == K_SPACE)
1163 if((me.nItems != 0) && (i >= 0))
1165 main.serverInfoDialog.loadServerInfo(main.serverInfoDialog, i);
1166 DialogOpenButton_Click_withCoords(me, main.serverInfoDialog, org, sz);
1171 else if(scan == K_INS || scan == K_MOUSE3 || scan == K_KP_INS)
1173 if((me.nItems != 0) && (i >= 0))
1175 ToggleFavorite(me.selectedServer);
1176 me.ipAddressBoxFocused = -1;
1181 else if(SUPER(XonoticServerList).keyDown(me, scan, ascii, shift))
1183 else if(!me.controlledTextbox)
1186 return me.controlledTextbox.keyDown(me.controlledTextbox, scan, ascii, shift);