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, lastBumpSelectTime, float, 0)
52 ATTRIB(XonoticServerList, lastClickedServer, float, -1)
53 ATTRIB(XonoticServerList, lastClickedTime, float, 0)
55 ATTRIB(XonoticServerList, ipAddressBoxFocused, float, -1)
57 ATTRIB(XonoticServerList, seenIPv4, float, 0)
58 ATTRIB(XonoticServerList, seenIPv6, float, 0)
59 ENDCLASS(XonoticServerList)
60 entity makeXonoticServerList();
62 #ifndef IMPLEMENTATION
63 var float autocvar_menu_serverlist_categories = TRUE;
64 var float autocvar_menu_serverlist_categories_onlyifmultiple = TRUE;
65 var float autocvar_menu_serverlist_purethreshold = 10;
66 var string autocvar_menu_serverlist_recommended = "76.124.107.5:26004";
68 // server cache fields
69 #define SLIST_FIELDS \
70 SLIST_FIELD(CNAME, "cname") \
71 SLIST_FIELD(PING, "ping") \
72 SLIST_FIELD(GAME, "game") \
73 SLIST_FIELD(MOD, "mod") \
74 SLIST_FIELD(MAP, "map") \
75 SLIST_FIELD(NAME, "name") \
76 SLIST_FIELD(MAXPLAYERS, "maxplayers") \
77 SLIST_FIELD(NUMPLAYERS, "numplayers") \
78 SLIST_FIELD(NUMHUMANS, "numhumans") \
79 SLIST_FIELD(NUMBOTS, "numbots") \
80 SLIST_FIELD(PROTOCOL, "protocol") \
81 SLIST_FIELD(FREESLOTS, "freeslots") \
82 SLIST_FIELD(PLAYERS, "players") \
83 SLIST_FIELD(QCSTATUS, "qcstatus") \
84 SLIST_FIELD(CATEGORY, "category") \
85 SLIST_FIELD(ISFAVORITE, "isfavorite")
87 #define SLIST_FIELD(suffix,name) float SLIST_FIELD_##suffix;
92 float SLSF_DESCENDING = 1;
93 float SLSF_FAVORITES = 2;
94 float SLSF_CATEGORIES = 4;
96 float Get_Cat_Num_FromString(string input);
97 entity Get_Cat_Ent(float catnum);
99 float IsServerInList(string list, string srv);
100 #define IsFavorite(srv) IsServerInList(cvar_string("net_slist_favorites"), srv)
101 #define IsRecommended(srv) IsServerInList(cvar_string("menu_serverlist_recommended"), srv) // todo: use update notification instead of cvar
103 float CheckCategoryOverride(float cat);
104 float CheckCategoryForEntry(float entry);
105 float m_getserverlistentrycategory(float entry) { return CheckCategoryOverride(CheckCategoryForEntry(entry)); }
107 // fields for category entities
108 #define MAX_CATEGORIES 9
109 #define CATEGORY_FIRST 1
110 entity categories[MAX_CATEGORIES];
111 float category_ent_count;
114 .string cat_override_string;
117 // fields for drawing categories
118 float category_name[MAX_CATEGORIES];
119 float category_item[MAX_CATEGORIES];
120 float category_draw_count;
123 SLIST_CATEGORY(CAT_FAVORITED, "", "", _("Favorites")) \
124 SLIST_CATEGORY(CAT_RECOMMENDED, "", "CAT_SERVERS", _("Recommended")) \
125 SLIST_CATEGORY(CAT_NORMAL, "", "CAT_SERVERS", _("Normal Servers")) \
126 SLIST_CATEGORY(CAT_SERVERS, "CAT_NORMAL", "CAT_SERVERS", _("Servers")) \
127 SLIST_CATEGORY(CAT_XPM, "CAT_NORMAL", "CAT_SERVERS", _("Competitive Mode")) \
128 SLIST_CATEGORY(CAT_MODIFIED, "", "CAT_SERVERS", _("Modified Servers")) \
129 SLIST_CATEGORY(CAT_OVERKILL, "", "CAT_SERVERS", _("Overkill Mode")) \
130 SLIST_CATEGORY(CAT_MINSTAGIB, "", "CAT_SERVERS", _("MinstaGib Mode")) \
131 SLIST_CATEGORY(CAT_DEFRAG, "", "CAT_SERVERS", _("Defrag Mode"))
133 // C is stupid, must use extra macro for concatenation
134 #define SLIST_ADD_CAT_CVAR(name,default) var string autocvar_menu_serverlist_categories_##name##_override = default;
135 #define SLIST_CATEGORY(name,enoverride,deoverride,string) \
136 SLIST_ADD_CAT_CVAR(name, enoverride) \
138 void RegisterSLCategory_##name() \
140 SET_FIELD_COUNT(name, CATEGORY_FIRST, category_ent_count) \
141 CHECK_MAX_COUNT(name, MAX_CATEGORIES, category_ent_count, "SLIST_CATEGORY") \
142 entity cat = spawn(); \
143 categories[name - 1] = cat; \
144 cat.classname = "slist_category"; \
145 cat.cat_name = strzone(#name); \
146 cat.cat_override_string = strzone((autocvar_menu_serverlist_categories ? \
147 autocvar_menu_serverlist_categories_##name##_override \
150 cat.cat_string = strzone(string); \
152 ACCUMULATE_FUNCTION(RegisterSLCategories, RegisterSLCategory_##name);
156 void RegisterSLCategories_Done()
160 for(i = 0; i < category_ent_count; ++i)
162 s = categories[i].cat_override_string;
163 if((s != "") && (s != categories[i].cat_name))
165 catnum = Get_Cat_Num_FromString(s);
168 strunzone(categories[i].cat_override_string);
169 categories[i].cat_override = catnum;
173 strunzone(categories[i].cat_override_string);
174 categories[i].cat_override = 0;
177 ACCUMULATE_FUNCTION(RegisterSLCategories, RegisterSLCategories_Done);
179 #undef SLIST_ADD_CAT_CVAR
180 #undef SLIST_CATEGORY
183 void ServerList_Connect_Click(entity btn, entity me);
184 void ServerList_ShowEmpty_Click(entity box, entity me);
185 void ServerList_ShowFull_Click(entity box, entity me);
186 void ServerList_Filter_Change(entity box, entity me);
187 void ServerList_Favorite_Click(entity btn, entity me);
188 void ServerList_Info_Click(entity btn, entity me);
189 void ServerList_Update_favoriteButton(entity btn, entity me);
193 #ifdef IMPLEMENTATION
195 // Supporting Functions
196 float Get_Cat_Num_FromString(string input)
199 for(i = 0; i < category_ent_count; ++i) { if(categories[i].cat_name == input) { return (i + 1); } }
200 print(sprintf("Get_Cat_Num_FromString('%s'): Improper category name!\n", input));
203 entity Get_Cat_Ent(float catnum)
205 if((catnum > 0) && (catnum <= category_ent_count))
207 return categories[catnum - 1];
211 error(sprintf("Get_Cat_Ent(%d): Improper category number!\n", catnum));
217 float IsServerInList(string list, string srv)
223 srv = netaddress_resolve(srv, 26000);
226 p = crypto_getidfp(srv);
227 n = tokenize_console(list);
228 for(i = 0; i < n; ++i)
230 if(substring(argv(i), 0, 1) != "[" && strlen(argv(i)) == 44 && strstrofs(argv(i), ".", 0) < 0)
238 if(srv == netaddress_resolve(argv(i), 26000))
245 float CheckCategoryOverride(float cat)
247 entity catent = Get_Cat_Ent(cat);
250 if(catent.cat_override) { return catent.cat_override; }
255 error(sprintf("CheckCategoryOverride(%d): Improper category number!\n", cat));
260 float CheckCategoryForEntry(float entry)
262 string s, k, v, modtype = "";
263 float j, m, impure = 0;
264 s = gethostcachestring(SLIST_FIELD_QCSTATUS, entry);
265 m = tokenizebyseparator(s, ":");
267 for(j = 2; j < m; ++j)
271 k = substring(argv(j), 0, 1);
272 v = substring(argv(j), 1, -1);
273 if(k == "P") { impure = stof(v); }
274 else if(k == "M") { modtype = strtolower(v); }
277 //print(sprintf("modtype = %s\n", modtype));
279 if(impure > autocvar_menu_serverlist_purethreshold) { impure = TRUE; }
280 else { impure = FALSE; }
282 if(gethostcachenumber(SLIST_FIELD_ISFAVORITE, entry)) { return CAT_FAVORITED; }
283 if(IsRecommended(gethostcachestring(SLIST_FIELD_CNAME, entry))) { return CAT_RECOMMENDED; }
284 else if(modtype != "xonotic")
288 // old servers which don't report their mod name are considered modified now
289 case "": { return CAT_MODIFIED; }
291 case "xpm": { return CAT_XPM; }
292 case "minstagib": { return CAT_MINSTAGIB; }
293 case "overkill": { return CAT_OVERKILL; }
295 // "cts" is allowed as compat, xdf is replacement
297 case "xdf": { return CAT_DEFRAG; }
299 //if(modname != "CTS")
300 //if(modname != "NIX")
301 //if(modname != "NewToys")
303 default: { dprint(sprintf("Found strange mod type: %s\n", modtype)); return CAT_MODIFIED; }
306 else { return (impure ? CAT_MODIFIED : CAT_NORMAL); }
308 // should never hit this point
309 error(sprintf("CheckCategoryForEntry(%d): Function fell through without normal return!\n", entry));
313 float XonoticServerList_MapItems(float num)
317 if not(category_draw_count) { return num; } // there are no categories to process
319 for(i = 0, n = 1; n <= category_draw_count; ++i, ++n)
321 //print(sprintf("num: %d, i: %d, category_draw_count: %d, category_item[i]: %d\n", num, i, category_draw_count, category_item[i]));
322 if(category_item[i] == (num - i)) { /*print("inserting cat... \\/\n");*/ return -category_name[i]; }
323 else if(n == category_draw_count) { /*print("end item... \\/\n");*/ return (num - n); }
324 else if((num - i) <= category_item[n]) { /*print("next item... \\/\n");*/ return (num - n); }
327 // should never hit this point
328 error("wtf XonoticServerList_MapItems fail?");
332 void ToggleFavorite(string srv)
334 string s, s0, s1, s2, srv_resolved, p;
336 srv_resolved = netaddress_resolve(srv, 26000);
337 p = crypto_getidfp(srv_resolved);
338 s = cvar_string("net_slist_favorites");
339 n = tokenize_console(s);
341 for(i = 0; i < n; ++i)
343 if(substring(argv(i), 0, 1) != "[" && strlen(argv(i)) == 44 && strstrofs(argv(i), ".", 0) < 0)
351 if(srv_resolved != netaddress_resolve(argv(i), 26000))
356 s0 = substring(s, 0, argv_end_index(i - 1));
358 s2 = substring(s, argv_start_index(i + 1), -1);
359 if(s0 != "" && s2 != "")
361 cvar_set("net_slist_favorites", strcat(s0, s1, s2));
362 s = cvar_string("net_slist_favorites");
363 n = tokenize_console(s);
374 cvar_set("net_slist_favorites", strcat(s, s1, p));
376 cvar_set("net_slist_favorites", strcat(s, s1, srv));
382 void ServerList_Update_favoriteButton(entity btn, entity me)
384 me.favoriteButton.setText(me.favoriteButton,
385 (IsFavorite(me.ipAddressBox.text) ?
386 _("Remove") : _("Bookmark")
391 entity makeXonoticServerList()
394 me = spawnXonoticServerList();
395 me.configureXonoticServerList(me);
398 void XonoticServerList_configureXonoticServerList(entity me)
400 me.configureXonoticListBox(me);
403 #define SLIST_FIELD(suffix,name) SLIST_FIELD_##suffix = gethostcacheindexforkey(name);
410 void XonoticServerList_setSelected(entity me, float i)
412 // todo: add logic to skip categories
414 save = me.selectedItem;
415 SUPER(XonoticServerList).setSelected(me, i);
417 if(me.selectedItem == save)
423 //if(gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT) != XonoticServerList_MapItems(me.nItems))
424 // { error("^1XonoticServerList_setSelected(); ERROR: ^7Host cache viewcount mismatches nItems!\n"); return; } // sorry, it would be wrong
425 // ^ todo: make this work somehow?
427 #define SET_SELECTED_SERVER(cachenum) \
428 if(me.selectedServer) { strunzone(me.selectedServer); } \
429 me.selectedServer = strzone(gethostcachestring(SLIST_FIELD_CNAME, cachenum)); \
430 me.ipAddressBox.setText(me.ipAddressBox, me.selectedServer); \
431 me.ipAddressBox.cursorPos = strlen(me.selectedServer); \
432 me.ipAddressBoxFocused = -1; \
435 num = XonoticServerList_MapItems(me.selectedItem);
437 if(num >= 0) { SET_SELECTED_SERVER(num); }
438 else if(save > me.selectedItem)
440 if(me.selectedItem == 0) { return; }
443 if(me.lastClickedTime >= me.lastBumpSelectTime)
445 SUPER(XonoticServerList).setSelected(me, me.selectedItem - 1);
446 num = XonoticServerList_MapItems(me.selectedItem);
449 me.lastBumpSelectTime = time;
450 SET_SELECTED_SERVER(num);
455 else if(save < me.selectedItem)
457 if(me.selectedItem == me.nItems) { return; }
460 if(me.lastClickedTime >= me.lastBumpSelectTime)
462 SUPER(XonoticServerList).setSelected(me, me.selectedItem + 1);
463 num = XonoticServerList_MapItems(me.selectedItem);
466 me.lastBumpSelectTime = time;
467 SET_SELECTED_SERVER(num);
473 void XonoticServerList_refreshServerList(entity me, float mode)
475 // 0: just reparametrize
476 // 1: also ask for new servers
478 //print("refresh of type ", ftos(mode), "\n");
479 /* if(mode == 2) // borken
482 localcmd("net_slist\n");
483 me.needsRefresh = 1; // net_slist kills sort order, so we need to restore it later
489 string s, typestr, modstr;
492 m = strstrofs(s, ":", 0);
495 typestr = substring(s, 0, m);
496 s = substring(s, m + 1, strlen(s) - m - 1);
497 while(substring(s, 0, 1) == " ")
498 s = substring(s, 1, strlen(s) - 1);
503 modstr = cvar_string("menu_slist_modfilter");
505 m = SLIST_MASK_AND - 1;
506 resethostcachemasks();
508 // ping: reject negative ping (no idea why this happens in the first place, engine bug)
509 sethostcachemasknumber(++m, SLIST_FIELD_PING, 0, SLIST_TEST_GREATEREQUAL);
512 if(!me.filterShowFull)
514 sethostcachemasknumber(++m, SLIST_FIELD_FREESLOTS, 1, SLIST_TEST_GREATEREQUAL); // legacy
515 sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, ":S0:", SLIST_TEST_NOTCONTAIN); // g_maxplayers support
519 if(!me.filterShowEmpty)
520 sethostcachemasknumber(++m, SLIST_FIELD_NUMHUMANS, 1, SLIST_TEST_GREATEREQUAL);
522 // gametype filtering
524 sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, strcat(typestr, ":"), SLIST_TEST_STARTSWITH);
529 if(substring(modstr, 0, 1) == "!")
530 sethostcachemaskstring(++m, SLIST_FIELD_MOD, resolvemod(substring(modstr, 1, strlen(modstr) - 1)), SLIST_TEST_NOTEQUAL);
532 sethostcachemaskstring(++m, SLIST_FIELD_MOD, resolvemod(modstr), SLIST_TEST_EQUAL);
536 n = tokenizebyseparator(_Nex_ExtResponseSystem_BannedServers, " ");
537 for(i = 0; i < n; ++i)
539 sethostcachemaskstring(++m, SLIST_FIELD_CNAME, argv(i), SLIST_TEST_NOTSTARTSWITH);
541 m = SLIST_MASK_OR - 1;
544 sethostcachemaskstring(++m, SLIST_FIELD_NAME, s, SLIST_TEST_CONTAINS);
545 sethostcachemaskstring(++m, SLIST_FIELD_MAP, s, SLIST_TEST_CONTAINS);
546 sethostcachemaskstring(++m, SLIST_FIELD_PLAYERS, s, SLIST_TEST_CONTAINS);
547 sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, strcat(s, ":"), SLIST_TEST_STARTSWITH);
551 //listflags |= SLSF_FAVORITES;
552 listflags |= SLSF_CATEGORIES;
553 if(me.currentSortOrder < 0) { listflags |= SLSF_DESCENDING; }
554 sethostcachesort(me.currentSortField, listflags);
557 if(mode >= 1) { refreshhostcache(); }
559 void XonoticServerList_focusEnter(entity me)
561 if(time < me.nextRefreshTime)
563 //print("sorry, no refresh yet\n");
566 me.nextRefreshTime = time + 10;
567 me.refreshServerList(me, 1);
570 void XonoticServerList_draw(entity me)
572 float i, found, owned, num;
574 if(_Nex_ExtResponseSystem_BannedServersNeedsRefresh)
578 _Nex_ExtResponseSystem_BannedServersNeedsRefresh = 0;
581 if(me.currentSortField == -1)
583 me.setSortOrder(me, SLIST_FIELD_PING, +1);
584 me.refreshServerList(me, 2);
586 else if(me.needsRefresh == 1)
588 me.needsRefresh = 2; // delay by one frame to make sure "slist" has been executed
590 else if(me.needsRefresh == 2)
593 me.refreshServerList(me, 0);
596 owned = ((me.selectedServer == me.ipAddressBox.text) && (me.ipAddressBox.text != ""));
598 for(i = 0; i < category_draw_count; ++i) { category_name[i] = -1; category_item[i] = -1; }
599 category_draw_count = 0;
601 if(autocvar_menu_serverlist_categories >= 0) // if less than 0, don't even draw a category heading for favorites
603 float itemcount = gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT);
604 me.nItems = itemcount;
606 //float visible = floor(me.scrollPos / me.itemHeight);
607 // ^ unfortunately no such optimization can be made-- we must process through the
608 // entire list, otherwise there is no way to know which item is first in its category.
611 for(i = 0; i < itemcount; ++i)
613 cat = gethostcachenumber(SLIST_FIELD_CATEGORY, i);
616 if(category_draw_count == 0)
618 category_name[category_draw_count] = cat;
619 category_item[category_draw_count] = i;
620 ++category_draw_count;
626 for(x = 0; x < category_draw_count; ++x) { if(cat == category_name[x]) { found = 1; } }
629 category_name[category_draw_count] = cat;
630 category_item[category_draw_count] = i;
631 ++category_draw_count;
637 if(autocvar_menu_serverlist_categories_onlyifmultiple && (category_draw_count == 1))
639 category_name[0] = category_name[1] = -1;
640 category_item[0] = category_item[1] = -1;
641 category_draw_count = 0;
642 me.nItems = itemcount;
645 else { me.nItems = gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT); }
647 me.connectButton.disabled = ((me.nItems == 0) && (me.ipAddressBox.text == ""));
648 me.infoButton.disabled = ((me.nItems == 0) || !owned);
649 me.favoriteButton.disabled = ((me.nItems == 0) && (me.ipAddressBox.text == ""));
652 if(me.selectedServer)
654 for(i = 0; i < me.nItems; ++i)
656 num = XonoticServerList_MapItems(i);
659 if(gethostcachestring(SLIST_FIELD_CNAME, num) == me.selectedServer)
661 if(i != me.selectedItem)
663 me.lastClickedServer = -1;
676 if(me.selectedItem >= me.nItems) { me.selectedItem = me.nItems - 1; }
677 if(me.selectedServer) { strunzone(me.selectedServer); }
679 num = XonoticServerList_MapItems(me.selectedItem);
680 if(num >= 0) { me.selectedServer = strzone(gethostcachestring(SLIST_FIELD_CNAME, num)); }
686 if(me.selectedServer != me.ipAddressBox.text)
688 me.ipAddressBox.setText(me.ipAddressBox, me.selectedServer);
689 me.ipAddressBox.cursorPos = strlen(me.selectedServer);
690 me.ipAddressBoxFocused = -1;
694 if(me.ipAddressBoxFocused != me.ipAddressBox.focused)
696 if(me.ipAddressBox.focused || me.ipAddressBoxFocused < 0)
697 ServerList_Update_favoriteButton(NULL, me);
698 me.ipAddressBoxFocused = me.ipAddressBox.focused;
701 SUPER(XonoticServerList).draw(me);
703 void ServerList_PingSort_Click(entity btn, entity me)
705 me.setSortOrder(me, SLIST_FIELD_PING, +1);
707 void ServerList_NameSort_Click(entity btn, entity me)
709 me.setSortOrder(me, SLIST_FIELD_NAME, -1); // why?
711 void ServerList_MapSort_Click(entity btn, entity me)
713 me.setSortOrder(me, SLIST_FIELD_MAP, -1); // why?
715 void ServerList_PlayerSort_Click(entity btn, entity me)
717 me.setSortOrder(me, SLIST_FIELD_NUMHUMANS, -1);
719 void ServerList_TypeSort_Click(entity btn, entity me)
724 m = strstrofs(s, ":", 0);
727 s = substring(s, 0, m);
728 while(substring(s, m+1, 1) == " ") // skip spaces
734 for(i = 1; ; i *= 2) // 20 modes ought to be enough for anyone
736 t = MapInfo_Type_ToString(i);
738 if(t == "") // it repeats (default case)
741 // choose the first one
742 s = MapInfo_Type_ToString(1);
747 // the type was found
748 // choose the next one
749 s = MapInfo_Type_ToString(i * 2);
751 s = MapInfo_Type_ToString(1);
758 s = strcat(s, substring(me.filterString, m+1, strlen(me.filterString) - m - 1));
760 me.controlledTextbox.setText(me.controlledTextbox, s);
761 me.controlledTextbox.keyDown(me.controlledTextbox, K_END, 0, 0);
762 me.controlledTextbox.keyUp(me.controlledTextbox, K_END, 0, 0);
763 //ServerList_Filter_Change(me.controlledTextbox, me);
765 void ServerList_Filter_Change(entity box, entity me)
768 strunzone(me.filterString);
770 me.filterString = strzone(box.text);
772 me.filterString = string_null;
773 me.refreshServerList(me, 0);
775 me.ipAddressBox.setText(me.ipAddressBox, "");
776 me.ipAddressBox.cursorPos = 0;
777 me.ipAddressBoxFocused = -1;
779 void ServerList_ShowEmpty_Click(entity box, entity me)
781 box.setChecked(box, me.filterShowEmpty = !me.filterShowEmpty);
782 me.refreshServerList(me, 0);
784 me.ipAddressBox.setText(me.ipAddressBox, "");
785 me.ipAddressBox.cursorPos = 0;
786 me.ipAddressBoxFocused = -1;
788 void ServerList_ShowFull_Click(entity box, entity me)
790 box.setChecked(box, me.filterShowFull = !me.filterShowFull);
791 me.refreshServerList(me, 0);
793 me.ipAddressBox.setText(me.ipAddressBox, "");
794 me.ipAddressBox.cursorPos = 0;
795 me.ipAddressBoxFocused = -1;
797 void XonoticServerList_setSortOrder(entity me, float fld, float direction)
799 if(me.currentSortField == fld)
800 direction = -me.currentSortOrder;
801 me.currentSortOrder = direction;
802 me.currentSortField = fld;
803 me.sortButton1.forcePressed = (fld == SLIST_FIELD_PING);
804 me.sortButton2.forcePressed = (fld == SLIST_FIELD_NAME);
805 me.sortButton3.forcePressed = (fld == SLIST_FIELD_MAP);
806 me.sortButton4.forcePressed = 0;
807 me.sortButton5.forcePressed = (fld == SLIST_FIELD_NUMHUMANS);
809 if(me.selectedServer)
810 strunzone(me.selectedServer);
811 me.selectedServer = string_null;
812 me.refreshServerList(me, 0);
814 void XonoticServerList_positionSortButton(entity me, entity btn, float theOrigin, float theSize, string theTitle, void(entity, entity) theFunc)
816 vector originInLBSpace, sizeInLBSpace;
817 originInLBSpace = eY * (-me.itemHeight);
818 sizeInLBSpace = eY * me.itemHeight + eX * (1 - me.controlWidth);
820 vector originInDialogSpace, sizeInDialogSpace;
821 originInDialogSpace = boxToGlobal(originInLBSpace, me.Container_origin, me.Container_size);
822 sizeInDialogSpace = boxToGlobalSize(sizeInLBSpace, me.Container_size);
824 btn.Container_origin_x = originInDialogSpace_x + sizeInDialogSpace_x * theOrigin;
825 btn.Container_size_x = sizeInDialogSpace_x * theSize;
826 btn.setText(btn, theTitle);
827 btn.onClick = theFunc;
828 btn.onClickEntity = me;
831 void XonoticServerList_resizeNotify(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
833 SUPER(XonoticServerList).resizeNotify(me, relOrigin, relSize, absOrigin, absSize);
835 me.realFontSize_y = me.fontSize / (absSize_y * me.itemHeight);
836 me.realFontSize_x = me.fontSize / (absSize_x * (1 - me.controlWidth));
837 me.realUpperMargin = 0.5 * (1 - me.realFontSize_y);
839 me.columnIconsOrigin = 0;
840 me.columnIconsSize = me.realFontSize_x * 4 * me.iconsSizeFactor;
841 me.columnPingSize = me.realFontSize_x * 3;
842 me.columnMapSize = me.realFontSize_x * 10;
843 me.columnTypeSize = me.realFontSize_x * 4;
844 me.columnPlayersSize = me.realFontSize_x * 5;
845 me.columnNameSize = 1 - me.columnPlayersSize - me.columnMapSize - me.columnPingSize - me.columnIconsSize - me.columnTypeSize - 5 * me.realFontSize_x;
846 me.columnPingOrigin = me.columnIconsOrigin + me.columnIconsSize + me.realFontSize_x;
847 me.columnNameOrigin = me.columnPingOrigin + me.columnPingSize + me.realFontSize_x;
848 me.columnMapOrigin = me.columnNameOrigin + me.columnNameSize + me.realFontSize_x;
849 me.columnTypeOrigin = me.columnMapOrigin + me.columnMapSize + me.realFontSize_x;
850 me.columnPlayersOrigin = me.columnTypeOrigin + me.columnTypeSize + me.realFontSize_x;
852 me.positionSortButton(me, me.sortButton1, me.columnPingOrigin, me.columnPingSize, _("Ping"), ServerList_PingSort_Click);
853 me.positionSortButton(me, me.sortButton2, me.columnNameOrigin, me.columnNameSize, _("Host name"), ServerList_NameSort_Click);
854 me.positionSortButton(me, me.sortButton3, me.columnMapOrigin, me.columnMapSize, _("Map"), ServerList_MapSort_Click);
855 me.positionSortButton(me, me.sortButton4, me.columnTypeOrigin, me.columnTypeSize, _("Type"), ServerList_TypeSort_Click);
856 me.positionSortButton(me, me.sortButton5, me.columnPlayersOrigin, me.columnPlayersSize, _("Players"), ServerList_PlayerSort_Click);
859 f = me.currentSortField;
862 me.currentSortField = -1;
863 me.setSortOrder(me, f, me.currentSortOrder); // force resetting the sort order
866 void ServerList_Connect_Click(entity btn, entity me)
868 localcmd(sprintf("connect %s\n",
869 ((me.ipAddressBox.text != "") ?
870 me.ipAddressBox.text : me.selectedServer
874 void ServerList_Favorite_Click(entity btn, entity me)
877 ipstr = netaddress_resolve(me.ipAddressBox.text, 26000);
880 ToggleFavorite(me.ipAddressBox.text);
881 me.ipAddressBoxFocused = -1;
884 void ServerList_Info_Click(entity btn, entity me)
886 main.serverInfoDialog.loadServerInfo(main.serverInfoDialog, XonoticServerList_MapItems(me.selectedItem));
887 DialogOpenButton_Click(me, main.serverInfoDialog);
889 void XonoticServerList_clickListBoxItem(entity me, float i, vector where)
891 me.lastBumpSelectTime = 0; // must reset this for new clicks
893 float num = XonoticServerList_MapItems(i);
896 if(num == me.lastClickedServer)
897 if(time < me.lastClickedTime + 0.3)
900 ServerList_Connect_Click(NULL, me);
902 me.lastClickedServer = num;
903 me.lastClickedTime = time;
906 void XonoticServerList_drawListBoxItem(entity me, float i, vector absSize, float isSelected)
908 // layout: Ping, Server name, Map name, NP, TP, MP
913 float m, pure, freeslots, j, sflags;
914 string s, typestr, versionstr, k, v, modname;
916 float item = XonoticServerList_MapItems(i);
917 //print(sprintf("time: %f, i: %d, item: %d, nitems: %d\n", time, i, item, me.nItems));
921 entity catent = Get_Cat_Ent(-item);
925 eY * me.realUpperMargin
927 eX * (me.columnNameOrigin + (me.columnNameSize - draw_TextWidth(catent.cat_string, 0, me.realFontSize)) * 0.5),
939 draw_Fill('0 0 0', '1 1 0', SKINCOLOR_LISTBOX_SELECTED, SKINALPHA_LISTBOX_SELECTED);
941 s = gethostcachestring(SLIST_FIELD_QCSTATUS, item);
942 m = tokenizebyseparator(s, ":");
947 versionstr = argv(1);
953 for(j = 2; j < m; ++j)
957 k = substring(argv(j), 0, 1);
958 v = substring(argv(j), 1, -1);
969 #ifdef COMPAT_NO_MOD_IS_XONOTIC
975 SLIST_FIELD_MOD = gethostcacheindexforkey("mod");
976 s = gethostcachestring(SLIST_FIELD_MOD, item);
978 if(modname == "Xonotic")
982 // list the mods here on which the pure server check actually works
983 if(modname != "Xonotic")
984 if(modname != "MinstaGib")
987 if(modname != "NewToys")
990 if(gethostcachenumber(SLIST_FIELD_FREESLOTS, item) <= 0)
991 theAlpha = SKINALPHA_SERVERLIST_FULL;
992 else if(freeslots == 0)
993 theAlpha = SKINALPHA_SERVERLIST_FULL; // g_maxplayers support
994 else if not(gethostcachenumber(SLIST_FIELD_NUMHUMANS, item))
995 theAlpha = SKINALPHA_SERVERLIST_EMPTY;
999 p = gethostcachenumber(SLIST_FIELD_PING, item);
1001 #define PING_MED 200
1002 #define PING_HIGH 500
1004 theColor = SKINCOLOR_SERVERLIST_LOWPING + (SKINCOLOR_SERVERLIST_MEDPING - SKINCOLOR_SERVERLIST_LOWPING) * (p / PING_LOW);
1005 else if(p < PING_MED)
1006 theColor = SKINCOLOR_SERVERLIST_MEDPING + (SKINCOLOR_SERVERLIST_HIGHPING - SKINCOLOR_SERVERLIST_MEDPING) * ((p - PING_LOW) / (PING_MED - PING_LOW));
1007 else if(p < PING_HIGH)
1009 theColor = SKINCOLOR_SERVERLIST_HIGHPING;
1010 theAlpha *= 1 + (SKINALPHA_SERVERLIST_HIGHPING - 1) * ((p - PING_MED) / (PING_HIGH - PING_MED));
1015 theAlpha *= SKINALPHA_SERVERLIST_HIGHPING;
1018 if(gethostcachenumber(SLIST_FIELD_ISFAVORITE, item))
1020 theColor = theColor * (1 - SKINALPHA_SERVERLIST_FAVORITE) + SKINCOLOR_SERVERLIST_FAVORITE * SKINALPHA_SERVERLIST_FAVORITE;
1021 theAlpha = theAlpha * (1 - SKINALPHA_SERVERLIST_FAVORITE) + SKINALPHA_SERVERLIST_FAVORITE;
1024 s = gethostcachestring(SLIST_FIELD_CNAME, item);
1027 if(substring(s, 0, 1) == "[")
1032 else if(strstrofs("0123456789", substring(s, 0, 1), 0) >= 0)
1038 q = stof(substring(crypto_getencryptlevel(s), 0, 1));
1039 if((q <= 0 && cvar("crypto_aeslevel") >= 3) || (q >= 3 && cvar("crypto_aeslevel") <= 0))
1041 theColor = SKINCOLOR_SERVERLIST_IMPOSSIBLE;
1042 theAlpha = SKINALPHA_SERVERLIST_IMPOSSIBLE;
1047 if(cvar("crypto_aeslevel") >= 2)
1052 if(cvar("crypto_aeslevel") >= 1)
1062 // 2: AES recommended but not available
1063 // 3: AES possible and will be used
1064 // 4: AES recommended and will be used
1070 vector iconSize = '0 0 0';
1071 iconSize_y = me.realFontSize_y * me.iconsSizeFactor;
1072 iconSize_x = me.realFontSize_x * me.iconsSizeFactor;
1074 vector iconPos = '0 0 0';
1075 iconPos_x = (me.columnIconsSize - 3 * iconSize_x) * 0.5;
1076 iconPos_y = (1 - iconSize_y) * 0.5;
1080 if not(me.seenIPv4 && me.seenIPv6)
1082 iconPos_x += iconSize_x * 0.5;
1084 else if(me.seenIPv4 && me.seenIPv6)
1088 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_ipv6"), 0); // PRECACHE_PIC_MIPMAP
1090 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_ipv4"), 0); // PRECACHE_PIC_MIPMAP
1092 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1093 iconPos_x += iconSize_x;
1098 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_aeslevel", ftos(q)), 0); // PRECACHE_PIC_MIPMAP
1099 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1101 iconPos_x += iconSize_x;
1103 if(modname == "Xonotic")
1107 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_pure1"), PRECACHE_PIC_MIPMAP);
1108 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1113 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_mod_", modname), PRECACHE_PIC_MIPMAP);
1114 if(draw_PictureSize(n) == '0 0 0')
1115 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_mod_"), PRECACHE_PIC_MIPMAP);
1117 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1119 draw_Picture(iconPos, n, iconSize, '1 1 1', SKINALPHA_SERVERLIST_ICON_NONPURE);
1121 iconPos_x += iconSize_x;
1123 if(sflags >= 0 && (sflags & SERVERFLAG_PLAYERSTATS))
1125 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_stats1"), 0); // PRECACHE_PIC_MIPMAP
1126 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1128 iconPos_x += iconSize_x;
1136 draw_Text(me.realUpperMargin * eY + (me.columnPingOrigin + me.columnPingSize - draw_TextWidth(s, 0, me.realFontSize)) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1139 s = draw_TextShortenToWidth(gethostcachestring(SLIST_FIELD_NAME, item), me.columnNameSize, 0, me.realFontSize);
1140 draw_Text(me.realUpperMargin * eY + me.columnNameOrigin * eX, s, me.realFontSize, theColor, theAlpha, 0);
1143 s = draw_TextShortenToWidth(gethostcachestring(SLIST_FIELD_MAP, item), me.columnMapSize, 0, me.realFontSize);
1144 draw_Text(me.realUpperMargin * eY + (me.columnMapOrigin + (me.columnMapSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1147 s = draw_TextShortenToWidth(typestr, me.columnTypeSize, 0, me.realFontSize);
1148 draw_Text(me.realUpperMargin * eY + (me.columnTypeOrigin + (me.columnTypeSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1150 // server playercount
1151 s = strcat(ftos(gethostcachenumber(SLIST_FIELD_NUMHUMANS, item)), "/", ftos(gethostcachenumber(SLIST_FIELD_MAXPLAYERS, item)));
1152 draw_Text(me.realUpperMargin * eY + (me.columnPlayersOrigin + (me.columnPlayersSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1155 float XonoticServerList_keyDown(entity me, float scan, float ascii, float shift)
1157 float i = XonoticServerList_MapItems(me.selectedItem);
1160 org = boxToGlobal(eY * (me.selectedItem * me.itemHeight - me.scrollPos), me.origin, me.size);
1161 sz = boxToGlobalSize(eY * me.itemHeight + eX * (1 - me.controlWidth), me.size);
1163 if(scan != K_MOUSE1 && scan != K_MOUSE2) { me.lastBumpSelectTime = 0; }
1165 if(scan == K_ENTER || scan == K_KP_ENTER)
1167 ServerList_Connect_Click(NULL, me);
1170 else if(scan == K_MOUSE2 || scan == K_SPACE)
1172 if((me.nItems != 0) && (i >= 0))
1174 main.serverInfoDialog.loadServerInfo(main.serverInfoDialog, i);
1175 DialogOpenButton_Click_withCoords(me, main.serverInfoDialog, org, sz);
1180 else if(scan == K_INS || scan == K_MOUSE3 || scan == K_KP_INS)
1182 if((me.nItems != 0) && (i >= 0))
1184 ToggleFavorite(me.selectedServer);
1185 me.ipAddressBoxFocused = -1;
1190 else if(SUPER(XonoticServerList).keyDown(me, scan, ascii, shift))
1192 else if(!me.controlledTextbox)
1195 return me.controlledTextbox.keyDown(me.controlledTextbox, scan, ascii, shift);