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))
10 METHOD(XonoticServerList, toggleFavorite, void(entity, string))
12 ATTRIB(XonoticServerList, iconsSizeFactor, float, 0.85)
14 ATTRIB(XonoticServerList, realFontSize, vector, '0 0 0')
15 ATTRIB(XonoticServerList, realUpperMargin, float, 0)
16 ATTRIB(XonoticServerList, columnIconsOrigin, float, 0)
17 ATTRIB(XonoticServerList, columnIconsSize, float, 0)
18 ATTRIB(XonoticServerList, columnPingOrigin, float, 0)
19 ATTRIB(XonoticServerList, columnPingSize, float, 0)
20 ATTRIB(XonoticServerList, columnNameOrigin, float, 0)
21 ATTRIB(XonoticServerList, columnNameSize, float, 0)
22 ATTRIB(XonoticServerList, columnMapOrigin, float, 0)
23 ATTRIB(XonoticServerList, columnMapSize, float, 0)
24 ATTRIB(XonoticServerList, columnTypeOrigin, float, 0)
25 ATTRIB(XonoticServerList, columnTypeSize, float, 0)
26 ATTRIB(XonoticServerList, columnPlayersOrigin, float, 0)
27 ATTRIB(XonoticServerList, columnPlayersSize, float, 0)
29 ATTRIB(XonoticServerList, selectedServer, string, string_null) // to restore selected server when needed
30 METHOD(XonoticServerList, setSelected, void(entity, float))
31 METHOD(XonoticServerList, setSortOrder, void(entity, float, float))
32 ATTRIB(XonoticServerList, filterShowEmpty, float, 1)
33 ATTRIB(XonoticServerList, filterShowFull, float, 1)
34 ATTRIB(XonoticServerList, filterString, string, string_null)
35 ATTRIB(XonoticServerList, controlledTextbox, entity, NULL)
36 ATTRIB(XonoticServerList, ipAddressBox, entity, NULL)
37 ATTRIB(XonoticServerList, favoriteButton, entity, NULL)
38 ATTRIB(XonoticServerList, nextRefreshTime, float, 0)
39 METHOD(XonoticServerList, refreshServerList, void(entity, float)) // refresh mode: REFRESHSERVERLIST_*
40 ATTRIB(XonoticServerList, needsRefresh, float, 1)
41 METHOD(XonoticServerList, focusEnter, void(entity))
42 METHOD(XonoticServerList, positionSortButton, void(entity, entity, float, float, string, void(entity, entity)))
43 ATTRIB(XonoticServerList, sortButton1, entity, NULL)
44 ATTRIB(XonoticServerList, sortButton2, entity, NULL)
45 ATTRIB(XonoticServerList, sortButton3, entity, NULL)
46 ATTRIB(XonoticServerList, sortButton4, entity, NULL)
47 ATTRIB(XonoticServerList, sortButton5, entity, NULL)
48 ATTRIB(XonoticServerList, connectButton, entity, NULL)
49 ATTRIB(XonoticServerList, infoButton, entity, NULL)
50 ATTRIB(XonoticServerList, currentSortOrder, float, 0)
51 ATTRIB(XonoticServerList, currentSortField, float, -1)
52 ATTRIB(XonoticServerList, lastBumpSelectTime, float, 0)
53 ATTRIB(XonoticServerList, lastClickedServer, float, -1)
54 ATTRIB(XonoticServerList, lastClickedTime, float, 0)
56 ATTRIB(XonoticServerList, ipAddressBoxFocused, float, -1)
58 ATTRIB(XonoticServerList, seenIPv4, float, 0)
59 ATTRIB(XonoticServerList, seenIPv6, float, 0)
60 ENDCLASS(XonoticServerList)
61 entity makeXonoticServerList();
63 #ifndef IMPLEMENTATION
64 var float autocvar_menu_slist_categories = TRUE;
65 var float autocvar_menu_slist_categories_onlyifmultiple = TRUE;
66 var float autocvar_menu_slist_purethreshold = 10;
67 //var string autocvar_menu_slist_recommended = "76.124.107.5:26004";
69 // server cache fields
70 #define SLIST_FIELDS \
71 SLIST_FIELD(CNAME, "cname") \
72 SLIST_FIELD(PING, "ping") \
73 SLIST_FIELD(GAME, "game") \
74 SLIST_FIELD(MOD, "mod") \
75 SLIST_FIELD(MAP, "map") \
76 SLIST_FIELD(NAME, "name") \
77 SLIST_FIELD(MAXPLAYERS, "maxplayers") \
78 SLIST_FIELD(NUMPLAYERS, "numplayers") \
79 SLIST_FIELD(NUMHUMANS, "numhumans") \
80 SLIST_FIELD(NUMBOTS, "numbots") \
81 SLIST_FIELD(PROTOCOL, "protocol") \
82 SLIST_FIELD(FREESLOTS, "freeslots") \
83 SLIST_FIELD(PLAYERS, "players") \
84 SLIST_FIELD(QCSTATUS, "qcstatus") \
85 SLIST_FIELD(CATEGORY, "category") \
86 SLIST_FIELD(ISFAVORITE, "isfavorite")
88 #define SLIST_FIELD(suffix,name) float SLIST_FIELD_##suffix;
92 const float REFRESHSERVERLIST_RESORT = 0; // sort the server list again to update for changes to e.g. favorite status, categories
93 const float REFRESHSERVERLIST_REFILTER = 1; // ..., also update filter and sort criteria
94 const float REFRESHSERVERLIST_ASK = 2; // ..., also suggest querying servers now
95 const float REFRESHSERVERLIST_RESET = 3; // ..., also clear the list first
97 // function declarations
98 entity RetrieveCategoryEnt(float catnum);
100 float IsServerInList(string list, string srv);
101 #define IsFavorite(srv) IsServerInList(cvar_string("net_slist_favorites"), srv)
102 #define IsRecommended(srv) IsServerInList(_Nex_ExtResponseSystem_RecommendedServers, srv)
104 float CheckCategoryOverride(float cat);
105 float CheckCategoryForEntry(float entry);
106 float m_gethostcachecategory(float entry) { return CheckCategoryOverride(CheckCategoryForEntry(entry)); }
108 void RegisterSLCategories();
110 void ServerList_Connect_Click(entity btn, entity me);
111 void ServerList_Categories_Click(entity box, entity me);
112 void ServerList_ShowEmpty_Click(entity box, entity me);
113 void ServerList_ShowFull_Click(entity box, entity me);
114 void ServerList_Filter_Change(entity box, entity me);
115 void ServerList_Favorite_Click(entity btn, entity me);
116 void ServerList_Info_Click(entity btn, entity me);
117 void ServerList_Update_favoriteButton(entity btn, entity me);
119 // fields for category entities
120 #define MAX_CATEGORIES 9
121 #define CATEGORY_FIRST 1
122 entity categories[MAX_CATEGORIES];
123 float category_ent_count;
126 .string cat_enoverride_string;
127 .string cat_dioverride_string;
128 .float cat_enoverride;
129 .float cat_dioverride;
131 // fields for drawing categories
132 float category_name[MAX_CATEGORIES];
133 float category_item[MAX_CATEGORIES];
134 float category_draw_count;
136 #define SLIST_CATEGORIES \
137 SLIST_CATEGORY(CAT_FAVORITED, "", "", ZCTX(_("SLCAT^Favorites"))) \
138 SLIST_CATEGORY(CAT_RECOMMENDED, "", "CAT_SERVERS", ZCTX(_("SLCAT^Recommended"))) \
139 SLIST_CATEGORY(CAT_NORMAL, "", "CAT_SERVERS", ZCTX(_("SLCAT^Normal Servers"))) \
140 SLIST_CATEGORY(CAT_SERVERS, "CAT_NORMAL", "CAT_SERVERS", ZCTX(_("SLCAT^Servers"))) \
141 SLIST_CATEGORY(CAT_XPM, "CAT_NORMAL", "CAT_SERVERS", ZCTX(_("SLCAT^Competitive Mode"))) \
142 SLIST_CATEGORY(CAT_MODIFIED, "", "CAT_SERVERS", ZCTX(_("SLCAT^Modified Servers"))) \
143 SLIST_CATEGORY(CAT_OVERKILL, "", "CAT_SERVERS", ZCTX(_("SLCAT^Overkill Mode"))) \
144 SLIST_CATEGORY(CAT_MINSTAGIB, "", "CAT_SERVERS", ZCTX(_("SLCAT^MinstaGib Mode"))) \
145 SLIST_CATEGORY(CAT_DEFRAG, "", "CAT_SERVERS", ZCTX(_("SLCAT^Defrag Mode")))
147 #define SLIST_CATEGORY_AUTOCVAR(name) autocvar_menu_slist_categories_##name##_override
148 #define SLIST_CATEGORY(name,enoverride,dioverride,str) \
150 var string SLIST_CATEGORY_AUTOCVAR(name) = enoverride;
152 #undef SLIST_CATEGORY
156 #ifdef IMPLEMENTATION
158 void RegisterSLCategories()
161 #define SLIST_CATEGORY(name,enoverride,dioverride,str) \
162 SET_FIELD_COUNT(name, CATEGORY_FIRST, category_ent_count) \
163 CHECK_MAX_COUNT(name, MAX_CATEGORIES, category_ent_count, "SLIST_CATEGORY") \
165 categories[name - 1] = cat; \
166 cat.classname = "slist_category"; \
167 cat.cat_name = strzone(#name); \
168 cat.cat_enoverride_string = strzone(SLIST_CATEGORY_AUTOCVAR(name)); \
169 cat.cat_dioverride_string = strzone(dioverride); \
170 cat.cat_string = strzone(str);
172 #undef SLIST_CATEGORY
177 #define PROCESS_OVERRIDE(override_string,override_field) \
178 for(i = 0; i < category_ent_count; ++i) \
180 s = categories[i].override_string; \
181 if((s != "") && (s != categories[i].cat_name)) \
184 for(x = 0; x < category_ent_count; ++x) \
185 { if(categories[x].cat_name == s) { \
191 strunzone(categories[i].override_string); \
192 categories[i].override_field = catnum; \
198 "RegisterSLCategories(): Improper override '%s' for category '%s'!\n", \
200 categories[i].cat_name \
204 strunzone(categories[i].override_string); \
205 categories[i].override_field = 0; \
207 PROCESS_OVERRIDE(cat_enoverride_string, cat_enoverride)
208 PROCESS_OVERRIDE(cat_dioverride_string, cat_dioverride)
209 #undef PROCESS_OVERRIDE
212 // Supporting Functions
213 entity RetrieveCategoryEnt(float catnum)
215 if((catnum > 0) && (catnum <= category_ent_count))
217 return categories[catnum - 1];
221 error(sprintf("RetrieveCategoryEnt(%d): Improper category number!\n", catnum));
226 float IsServerInList(string list, string srv)
232 srv = netaddress_resolve(srv, 26000);
235 p = crypto_getidfp(srv);
236 n = tokenize_console(list);
237 for(i = 0; i < n; ++i)
239 if(substring(argv(i), 0, 1) != "[" && strlen(argv(i)) == 44 && strstrofs(argv(i), ".", 0) < 0)
247 if(srv == netaddress_resolve(argv(i), 26000))
254 float CheckCategoryOverride(float cat)
256 entity catent = RetrieveCategoryEnt(cat);
259 float override = (autocvar_menu_slist_categories ? catent.cat_enoverride : catent.cat_dioverride);
260 if(override) { return override; }
265 error(sprintf("CheckCategoryOverride(%d): Improper category number!\n", cat));
270 float CheckCategoryForEntry(float entry)
272 string s, k, v, modtype = "";
273 float j, m, impure = 0;
274 s = gethostcachestring(SLIST_FIELD_QCSTATUS, entry);
275 m = tokenizebyseparator(s, ":");
277 for(j = 2; j < m; ++j)
281 k = substring(argv(j), 0, 1);
282 v = substring(argv(j), 1, -1);
283 if(k == "P") { impure = stof(v); }
284 else if(k == "M") { modtype = strtolower(v); }
287 if(impure > autocvar_menu_slist_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; }
302 //case "nix": { return CAT_NIX; }
303 //case "newtoys": { return CAT_NEWTOYS; }
305 // "cts" is allowed as compat, xdf is replacement
307 case "xdf": { return CAT_DEFRAG; }
309 default: { dprint(sprintf("Found strange mod type: %s\n", modtype)); return CAT_MODIFIED; }
312 else { return (impure ? CAT_MODIFIED : CAT_NORMAL); }
314 // should never hit this point
315 error(sprintf("CheckCategoryForEntry(%d): Function fell through without normal return!\n", entry));
319 float CheckItemNumber(float num)
323 if not(category_draw_count) { return num; } // there are no categories to process
325 for(i = 0, n = 1; n <= category_draw_count; ++i, ++n)
327 if(category_item[i] == (num - i)) { return -category_name[i]; }
328 else if(n == category_draw_count) { return (num - n); }
329 else if((num - i) <= category_item[n]) { return (num - n); }
332 // should never hit this point
333 error(sprintf("CheckItemNumber(%d): Function fell through without normal return!\n", num));
337 void XonoticServerList_toggleFavorite(entity me, 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));
384 me.refreshServerList(me, REFRESHSERVERLIST_RESORT);
387 void ServerList_Update_favoriteButton(entity btn, entity me)
389 me.favoriteButton.setText(me.favoriteButton,
390 (IsFavorite(me.ipAddressBox.text) ?
391 _("Remove") : _("Bookmark")
396 entity makeXonoticServerList()
399 me = spawnXonoticServerList();
400 me.configureXonoticServerList(me);
403 void XonoticServerList_configureXonoticServerList(entity me)
405 me.configureXonoticListBox(me);
408 #define SLIST_FIELD(suffix,name) SLIST_FIELD_##suffix = gethostcacheindexforkey(name);
416 RegisterSLCategories();
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) != CheckItemNumber(me.nItems))
432 // { error("^1XonoticServerList_setSelected(); ERROR: ^7Host cache viewcount mismatches nItems!\n"); return; } // sorry, it would be wrong
433 // ^ todo: make this work somehow?
435 #define SET_SELECTED_SERVER(cachenum) \
436 if(me.selectedServer) { strunzone(me.selectedServer); } \
437 me.selectedServer = strzone(gethostcachestring(SLIST_FIELD_CNAME, cachenum)); \
438 me.ipAddressBox.setText(me.ipAddressBox, me.selectedServer); \
439 me.ipAddressBox.cursorPos = strlen(me.selectedServer); \
440 me.ipAddressBoxFocused = -1; \
443 num = CheckItemNumber(me.selectedItem);
445 if(num >= 0) { SET_SELECTED_SERVER(num); }
446 else if(save > me.selectedItem)
448 if(me.selectedItem == 0) { return; }
451 if(me.lastClickedTime >= me.lastBumpSelectTime)
453 SUPER(XonoticServerList).setSelected(me, me.selectedItem - 1);
454 num = CheckItemNumber(me.selectedItem);
457 me.lastBumpSelectTime = time;
458 SET_SELECTED_SERVER(num);
463 else if(save < me.selectedItem)
465 if(me.selectedItem == me.nItems) { return; }
468 if(me.lastClickedTime >= me.lastBumpSelectTime)
470 SUPER(XonoticServerList).setSelected(me, me.selectedItem + 1);
471 num = CheckItemNumber(me.selectedItem);
474 me.lastBumpSelectTime = time;
475 SET_SELECTED_SERVER(num);
482 void XonoticServerList_refreshServerList(entity me, float mode)
484 //print("refresh of type ", ftos(mode), "\n");
486 if(mode >= REFRESHSERVERLIST_REFILTER)
490 string s, typestr, modstr;
494 m = strstrofs(s, ":", 0);
497 typestr = substring(s, 0, m);
498 s = substring(s, m + 1, strlen(s) - m - 1);
499 while(substring(s, 0, 1) == " ")
500 s = substring(s, 1, strlen(s) - 1);
505 modstr = cvar_string("menu_slist_modfilter");
507 m = SLIST_MASK_AND - 1;
508 resethostcachemasks();
510 // ping: reject negative ping (no idea why this happens in the first place, engine bug)
511 sethostcachemasknumber(++m, SLIST_FIELD_PING, 0, SLIST_TEST_GREATEREQUAL);
514 if(!me.filterShowFull)
516 sethostcachemasknumber(++m, SLIST_FIELD_FREESLOTS, 1, SLIST_TEST_GREATEREQUAL); // legacy
517 sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, ":S0:", SLIST_TEST_NOTCONTAIN); // g_maxplayers support
521 if(!me.filterShowEmpty)
522 sethostcachemasknumber(++m, SLIST_FIELD_NUMHUMANS, 1, SLIST_TEST_GREATEREQUAL);
524 // gametype filtering
526 sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, strcat(typestr, ":"), SLIST_TEST_STARTSWITH);
531 if(substring(modstr, 0, 1) == "!")
532 sethostcachemaskstring(++m, SLIST_FIELD_MOD, resolvemod(substring(modstr, 1, strlen(modstr) - 1)), SLIST_TEST_NOTEQUAL);
534 sethostcachemaskstring(++m, SLIST_FIELD_MOD, resolvemod(modstr), SLIST_TEST_EQUAL);
538 n = tokenizebyseparator(_Nex_ExtResponseSystem_BannedServers, " ");
539 for(i = 0; i < n; ++i)
541 sethostcachemaskstring(++m, SLIST_FIELD_CNAME, argv(i), SLIST_TEST_NOTSTARTSWITH);
543 m = SLIST_MASK_OR - 1;
546 sethostcachemaskstring(++m, SLIST_FIELD_NAME, s, SLIST_TEST_CONTAINS);
547 sethostcachemaskstring(++m, SLIST_FIELD_MAP, s, SLIST_TEST_CONTAINS);
548 sethostcachemaskstring(++m, SLIST_FIELD_PLAYERS, s, SLIST_TEST_CONTAINS);
549 sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, strcat(s, ":"), SLIST_TEST_STARTSWITH);
553 //listflags |= SLSF_FAVORITES;
554 listflags |= SLSF_CATEGORIES;
555 if(me.currentSortOrder < 0) { listflags |= SLSF_DESCENDING; }
556 sethostcachesort(me.currentSortField, listflags);
560 if(mode >= REFRESHSERVERLIST_ASK)
561 refreshhostcache(mode >= REFRESHSERVERLIST_RESET);
563 void XonoticServerList_focusEnter(entity me)
565 if(time < me.nextRefreshTime)
567 //print("sorry, no refresh yet\n");
570 me.nextRefreshTime = time + 10;
571 me.refreshServerList(me, REFRESHSERVERLIST_ASK);
574 void XonoticServerList_draw(entity me)
576 float i, found, owned, num;
578 if(_Nex_ExtResponseSystem_BannedServersNeedsRefresh)
582 _Nex_ExtResponseSystem_BannedServersNeedsRefresh = 0;
585 if(_Nex_ExtResponseSystem_RecommendedServersNeedsRefresh)
589 _Nex_ExtResponseSystem_RecommendedServersNeedsRefresh = 0;
592 if(me.currentSortField == -1)
594 me.setSortOrder(me, SLIST_FIELD_PING, +1);
595 me.refreshServerList(me, REFRESHSERVERLIST_RESET);
597 else if(me.needsRefresh == 1)
599 me.needsRefresh = 2; // delay by one frame to make sure "slist" has been executed
601 else if(me.needsRefresh == 2)
604 me.refreshServerList(me, REFRESHSERVERLIST_REFILTER);
606 else if(me.needsRefresh == 3)
609 me.refreshServerList(me, REFRESHSERVERLIST_RESORT);
612 owned = ((me.selectedServer == me.ipAddressBox.text) && (me.ipAddressBox.text != ""));
614 for(i = 0; i < category_draw_count; ++i) { category_name[i] = -1; category_item[i] = -1; }
615 category_draw_count = 0;
617 if(autocvar_menu_slist_categories >= 0) // if less than 0, don't even draw a category heading for favorites
619 float itemcount = gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT);
620 me.nItems = itemcount;
622 //float visible = floor(me.scrollPos / me.itemHeight);
623 // ^ unfortunately no such optimization can be made-- we must process through the
624 // entire list, otherwise there is no way to know which item is first in its category.
627 for(i = 0; i < itemcount; ++i) // FIXME this loop is TOTALLY unacceptable (O(servers)). Make it O(categories * log(servers)). Yes, that is possible.
629 cat = gethostcachenumber(SLIST_FIELD_CATEGORY, i);
632 if(category_draw_count == 0)
634 category_name[category_draw_count] = cat;
635 category_item[category_draw_count] = i;
636 ++category_draw_count;
642 for(x = 0; x < category_draw_count; ++x) { if(cat == category_name[x]) { found = 1; } }
645 category_name[category_draw_count] = cat;
646 category_item[category_draw_count] = i;
647 ++category_draw_count;
653 if(autocvar_menu_slist_categories_onlyifmultiple && (category_draw_count == 1))
655 category_name[0] = -1;
656 category_item[0] = -1;
657 category_draw_count = 0;
658 me.nItems = itemcount;
661 else { me.nItems = gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT); }
663 me.connectButton.disabled = ((me.nItems == 0) && (me.ipAddressBox.text == ""));
664 me.infoButton.disabled = ((me.nItems == 0) || !owned);
665 me.favoriteButton.disabled = ((me.nItems == 0) && (me.ipAddressBox.text == ""));
668 if(me.selectedServer)
670 for(i = 0; i < me.nItems; ++i)
672 num = CheckItemNumber(i);
675 if(gethostcachestring(SLIST_FIELD_CNAME, num) == me.selectedServer)
677 if(i != me.selectedItem)
679 me.lastClickedServer = -1;
692 if(me.selectedItem >= me.nItems) { me.selectedItem = me.nItems - 1; }
693 if(me.selectedServer) { strunzone(me.selectedServer); }
695 num = CheckItemNumber(me.selectedItem);
696 if(num >= 0) { me.selectedServer = strzone(gethostcachestring(SLIST_FIELD_CNAME, num)); }
702 if(me.selectedServer != me.ipAddressBox.text)
704 me.ipAddressBox.setText(me.ipAddressBox, me.selectedServer);
705 me.ipAddressBox.cursorPos = strlen(me.selectedServer);
706 me.ipAddressBoxFocused = -1;
710 if(me.ipAddressBoxFocused != me.ipAddressBox.focused)
712 if(me.ipAddressBox.focused || me.ipAddressBoxFocused < 0)
713 ServerList_Update_favoriteButton(NULL, me);
714 me.ipAddressBoxFocused = me.ipAddressBox.focused;
717 SUPER(XonoticServerList).draw(me);
719 void ServerList_PingSort_Click(entity btn, entity me)
721 me.setSortOrder(me, SLIST_FIELD_PING, +1);
723 void ServerList_NameSort_Click(entity btn, entity me)
725 me.setSortOrder(me, SLIST_FIELD_NAME, -1); // why?
727 void ServerList_MapSort_Click(entity btn, entity me)
729 me.setSortOrder(me, SLIST_FIELD_MAP, -1); // why?
731 void ServerList_PlayerSort_Click(entity btn, entity me)
733 me.setSortOrder(me, SLIST_FIELD_NUMHUMANS, -1);
735 void ServerList_TypeSort_Click(entity btn, entity me)
740 m = strstrofs(s, ":", 0);
743 s = substring(s, 0, m);
744 while(substring(s, m+1, 1) == " ") // skip spaces
750 for(i = 1; ; i *= 2) // 20 modes ought to be enough for anyone
752 t = MapInfo_Type_ToString(i);
754 if(t == "") // it repeats (default case)
757 // choose the first one
758 s = MapInfo_Type_ToString(1);
763 // the type was found
764 // choose the next one
765 s = MapInfo_Type_ToString(i * 2);
767 s = MapInfo_Type_ToString(1);
774 s = strcat(s, substring(me.filterString, m+1, strlen(me.filterString) - m - 1));
776 me.controlledTextbox.setText(me.controlledTextbox, s);
777 me.controlledTextbox.keyDown(me.controlledTextbox, K_END, 0, 0);
778 me.controlledTextbox.keyUp(me.controlledTextbox, K_END, 0, 0);
779 //ServerList_Filter_Change(me.controlledTextbox, me);
781 void ServerList_Filter_Change(entity box, entity me)
784 strunzone(me.filterString);
786 me.filterString = strzone(box.text);
788 me.filterString = string_null;
789 me.refreshServerList(me, REFRESHSERVERLIST_REFILTER);
791 me.ipAddressBox.setText(me.ipAddressBox, "");
792 me.ipAddressBox.cursorPos = 0;
793 me.ipAddressBoxFocused = -1;
795 void ServerList_Categories_Click(entity box, entity me)
797 box.setChecked(box, autocvar_menu_slist_categories = !autocvar_menu_slist_categories);
798 ///refreshhostcache(TRUE);
800 //cvar_set("net_slist_pause", "0");
801 //Destroy_Category_Entities();
802 //CALL_ACCUMULATED_FUNCTION(RegisterSLCategories);
803 //me.refreshServerList(me, REFRESHSERVERLIST_REFILTER);
805 me.refreshServerList(me, REFRESHSERVERLIST_RESORT);
807 me.ipAddressBox.setText(me.ipAddressBox, "");
808 me.ipAddressBox.cursorPos = 0;
809 me.ipAddressBoxFocused = -1;
811 void ServerList_ShowEmpty_Click(entity box, entity me)
813 box.setChecked(box, me.filterShowEmpty = !me.filterShowEmpty);
814 me.refreshServerList(me, REFRESHSERVERLIST_REFILTER);
816 me.ipAddressBox.setText(me.ipAddressBox, "");
817 me.ipAddressBox.cursorPos = 0;
818 me.ipAddressBoxFocused = -1;
820 void ServerList_ShowFull_Click(entity box, entity me)
822 box.setChecked(box, me.filterShowFull = !me.filterShowFull);
823 me.refreshServerList(me, REFRESHSERVERLIST_REFILTER);
825 me.ipAddressBox.setText(me.ipAddressBox, "");
826 me.ipAddressBox.cursorPos = 0;
827 me.ipAddressBoxFocused = -1;
829 void XonoticServerList_setSortOrder(entity me, float fld, float direction)
831 if(me.currentSortField == fld)
832 direction = -me.currentSortOrder;
833 me.currentSortOrder = direction;
834 me.currentSortField = fld;
835 me.sortButton1.forcePressed = (fld == SLIST_FIELD_PING);
836 me.sortButton2.forcePressed = (fld == SLIST_FIELD_NAME);
837 me.sortButton3.forcePressed = (fld == SLIST_FIELD_MAP);
838 me.sortButton4.forcePressed = 0;
839 me.sortButton5.forcePressed = (fld == SLIST_FIELD_NUMHUMANS);
841 if(me.selectedServer)
842 strunzone(me.selectedServer);
843 me.selectedServer = string_null;
844 me.refreshServerList(me, REFRESHSERVERLIST_REFILTER);
846 void XonoticServerList_positionSortButton(entity me, entity btn, float theOrigin, float theSize, string theTitle, void(entity, entity) theFunc)
848 vector originInLBSpace, sizeInLBSpace;
849 originInLBSpace = eY * (-me.itemHeight);
850 sizeInLBSpace = eY * me.itemHeight + eX * (1 - me.controlWidth);
852 vector originInDialogSpace, sizeInDialogSpace;
853 originInDialogSpace = boxToGlobal(originInLBSpace, me.Container_origin, me.Container_size);
854 sizeInDialogSpace = boxToGlobalSize(sizeInLBSpace, me.Container_size);
856 btn.Container_origin_x = originInDialogSpace_x + sizeInDialogSpace_x * theOrigin;
857 btn.Container_size_x = sizeInDialogSpace_x * theSize;
858 btn.setText(btn, theTitle);
859 btn.onClick = theFunc;
860 btn.onClickEntity = me;
863 void XonoticServerList_resizeNotify(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
865 SUPER(XonoticServerList).resizeNotify(me, relOrigin, relSize, absOrigin, absSize);
867 me.realFontSize_y = me.fontSize / (absSize_y * me.itemHeight);
868 me.realFontSize_x = me.fontSize / (absSize_x * (1 - me.controlWidth));
869 me.realUpperMargin = 0.5 * (1 - me.realFontSize_y);
871 me.columnIconsOrigin = 0;
872 me.columnIconsSize = me.realFontSize_x * 4 * me.iconsSizeFactor;
873 me.columnPingSize = me.realFontSize_x * 3;
874 me.columnMapSize = me.realFontSize_x * 10;
875 me.columnTypeSize = me.realFontSize_x * 4;
876 me.columnPlayersSize = me.realFontSize_x * 5;
877 me.columnNameSize = 1 - me.columnPlayersSize - me.columnMapSize - me.columnPingSize - me.columnIconsSize - me.columnTypeSize - 5 * me.realFontSize_x;
878 me.columnPingOrigin = me.columnIconsOrigin + me.columnIconsSize + me.realFontSize_x;
879 me.columnNameOrigin = me.columnPingOrigin + me.columnPingSize + me.realFontSize_x;
880 me.columnMapOrigin = me.columnNameOrigin + me.columnNameSize + me.realFontSize_x;
881 me.columnTypeOrigin = me.columnMapOrigin + me.columnMapSize + me.realFontSize_x;
882 me.columnPlayersOrigin = me.columnTypeOrigin + me.columnTypeSize + me.realFontSize_x;
884 me.positionSortButton(me, me.sortButton1, me.columnPingOrigin, me.columnPingSize, _("Ping"), ServerList_PingSort_Click);
885 me.positionSortButton(me, me.sortButton2, me.columnNameOrigin, me.columnNameSize, _("Host name"), ServerList_NameSort_Click);
886 me.positionSortButton(me, me.sortButton3, me.columnMapOrigin, me.columnMapSize, _("Map"), ServerList_MapSort_Click);
887 me.positionSortButton(me, me.sortButton4, me.columnTypeOrigin, me.columnTypeSize, _("Type"), ServerList_TypeSort_Click);
888 me.positionSortButton(me, me.sortButton5, me.columnPlayersOrigin, me.columnPlayersSize, _("Players"), ServerList_PlayerSort_Click);
891 f = me.currentSortField;
894 me.currentSortField = -1;
895 me.setSortOrder(me, f, me.currentSortOrder); // force resetting the sort order
898 void ServerList_Connect_Click(entity btn, entity me)
900 localcmd(sprintf("connect %s\n",
901 ((me.ipAddressBox.text != "") ?
902 me.ipAddressBox.text : me.selectedServer
906 void ServerList_Favorite_Click(entity btn, entity me)
909 ipstr = netaddress_resolve(me.ipAddressBox.text, 26000);
912 me.toggleFavorite(me, me.ipAddressBox.text);
913 me.ipAddressBoxFocused = -1;
916 void ServerList_Info_Click(entity btn, entity me)
918 main.serverInfoDialog.loadServerInfo(main.serverInfoDialog, CheckItemNumber(me.selectedItem));
919 DialogOpenButton_Click(me, main.serverInfoDialog);
921 void XonoticServerList_clickListBoxItem(entity me, float i, vector where)
923 float num = CheckItemNumber(i);
926 if(num == me.lastClickedServer)
927 if(time < me.lastClickedTime + 0.3)
930 ServerList_Connect_Click(NULL, me);
932 me.lastClickedServer = num;
933 me.lastClickedTime = time;
936 void XonoticServerList_drawListBoxItem(entity me, float i, vector absSize, float isSelected)
938 // layout: Ping, Server name, Map name, NP, TP, MP
943 float m, pure, freeslots, j, sflags;
944 string s, typestr, versionstr, k, v, modname;
946 float item = CheckItemNumber(i);
947 //print(sprintf("time: %f, i: %d, item: %d, nitems: %d\n", time, i, item, me.nItems));
951 entity catent = RetrieveCategoryEnt(-item);
955 eY * me.realUpperMargin
957 eX * (me.columnNameOrigin + (me.columnNameSize - draw_TextWidth(catent.cat_string, 0, me.realFontSize)) * 0.5),
969 draw_Fill('0 0 0', '1 1 0', SKINCOLOR_LISTBOX_SELECTED, SKINALPHA_LISTBOX_SELECTED);
971 s = gethostcachestring(SLIST_FIELD_QCSTATUS, item);
972 m = tokenizebyseparator(s, ":");
977 versionstr = argv(1);
983 for(j = 2; j < m; ++j)
987 k = substring(argv(j), 0, 1);
988 v = substring(argv(j), 1, -1);
999 #ifdef COMPAT_NO_MOD_IS_XONOTIC
1001 modname = "Xonotic";
1005 SLIST_FIELD_MOD = gethostcacheindexforkey("mod");
1006 s = gethostcachestring(SLIST_FIELD_MOD, item);
1008 if(modname == "Xonotic")
1012 // list the mods here on which the pure server check actually works
1013 if(modname != "Xonotic")
1014 if(modname != "MinstaGib")
1015 if(modname != "CTS")
1016 if(modname != "NIX")
1017 if(modname != "NewToys")
1020 if(gethostcachenumber(SLIST_FIELD_FREESLOTS, item) <= 0)
1021 theAlpha = SKINALPHA_SERVERLIST_FULL;
1022 else if(freeslots == 0)
1023 theAlpha = SKINALPHA_SERVERLIST_FULL; // g_maxplayers support
1024 else if not(gethostcachenumber(SLIST_FIELD_NUMHUMANS, item))
1025 theAlpha = SKINALPHA_SERVERLIST_EMPTY;
1029 p = gethostcachenumber(SLIST_FIELD_PING, item);
1031 #define PING_MED 200
1032 #define PING_HIGH 500
1034 theColor = SKINCOLOR_SERVERLIST_LOWPING + (SKINCOLOR_SERVERLIST_MEDPING - SKINCOLOR_SERVERLIST_LOWPING) * (p / PING_LOW);
1035 else if(p < PING_MED)
1036 theColor = SKINCOLOR_SERVERLIST_MEDPING + (SKINCOLOR_SERVERLIST_HIGHPING - SKINCOLOR_SERVERLIST_MEDPING) * ((p - PING_LOW) / (PING_MED - PING_LOW));
1037 else if(p < PING_HIGH)
1039 theColor = SKINCOLOR_SERVERLIST_HIGHPING;
1040 theAlpha *= 1 + (SKINALPHA_SERVERLIST_HIGHPING - 1) * ((p - PING_MED) / (PING_HIGH - PING_MED));
1045 theAlpha *= SKINALPHA_SERVERLIST_HIGHPING;
1048 if(gethostcachenumber(SLIST_FIELD_ISFAVORITE, item))
1050 theColor = theColor * (1 - SKINALPHA_SERVERLIST_FAVORITE) + SKINCOLOR_SERVERLIST_FAVORITE * SKINALPHA_SERVERLIST_FAVORITE;
1051 theAlpha = theAlpha * (1 - SKINALPHA_SERVERLIST_FAVORITE) + SKINALPHA_SERVERLIST_FAVORITE;
1054 s = gethostcachestring(SLIST_FIELD_CNAME, item);
1057 if(substring(s, 0, 1) == "[")
1062 else if(strstrofs("0123456789", substring(s, 0, 1), 0) >= 0)
1068 q = stof(substring(crypto_getencryptlevel(s), 0, 1));
1069 if((q <= 0 && cvar("crypto_aeslevel") >= 3) || (q >= 3 && cvar("crypto_aeslevel") <= 0))
1071 theColor = SKINCOLOR_SERVERLIST_IMPOSSIBLE;
1072 theAlpha = SKINALPHA_SERVERLIST_IMPOSSIBLE;
1077 if(cvar("crypto_aeslevel") >= 2)
1082 if(cvar("crypto_aeslevel") >= 1)
1092 // 2: AES recommended but not available
1093 // 3: AES possible and will be used
1094 // 4: AES recommended and will be used
1100 vector iconSize = '0 0 0';
1101 iconSize_y = me.realFontSize_y * me.iconsSizeFactor;
1102 iconSize_x = me.realFontSize_x * me.iconsSizeFactor;
1104 vector iconPos = '0 0 0';
1105 iconPos_x = (me.columnIconsSize - 3 * iconSize_x) * 0.5;
1106 iconPos_y = (1 - iconSize_y) * 0.5;
1110 if not(me.seenIPv4 && me.seenIPv6)
1112 iconPos_x += iconSize_x * 0.5;
1114 else if(me.seenIPv4 && me.seenIPv6)
1118 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_ipv6"), 0); // PRECACHE_PIC_MIPMAP
1120 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_ipv4"), 0); // PRECACHE_PIC_MIPMAP
1122 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1123 iconPos_x += iconSize_x;
1128 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_aeslevel", ftos(q)), 0); // PRECACHE_PIC_MIPMAP
1129 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1131 iconPos_x += iconSize_x;
1133 if(modname == "Xonotic")
1137 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_pure1"), PRECACHE_PIC_MIPMAP);
1138 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1143 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_mod_", modname), PRECACHE_PIC_MIPMAP);
1144 if(draw_PictureSize(n) == '0 0 0')
1145 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_mod_"), PRECACHE_PIC_MIPMAP);
1147 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1149 draw_Picture(iconPos, n, iconSize, '1 1 1', SKINALPHA_SERVERLIST_ICON_NONPURE);
1151 iconPos_x += iconSize_x;
1153 if(sflags >= 0 && (sflags & SERVERFLAG_PLAYERSTATS))
1155 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_stats1"), 0); // PRECACHE_PIC_MIPMAP
1156 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1158 iconPos_x += iconSize_x;
1166 draw_Text(me.realUpperMargin * eY + (me.columnPingOrigin + me.columnPingSize - draw_TextWidth(s, 0, me.realFontSize)) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1169 s = draw_TextShortenToWidth(gethostcachestring(SLIST_FIELD_NAME, item), me.columnNameSize, 0, me.realFontSize);
1170 draw_Text(me.realUpperMargin * eY + me.columnNameOrigin * eX, s, me.realFontSize, theColor, theAlpha, 0);
1173 s = draw_TextShortenToWidth(gethostcachestring(SLIST_FIELD_MAP, item), me.columnMapSize, 0, me.realFontSize);
1174 draw_Text(me.realUpperMargin * eY + (me.columnMapOrigin + (me.columnMapSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1177 s = draw_TextShortenToWidth(typestr, me.columnTypeSize, 0, me.realFontSize);
1178 draw_Text(me.realUpperMargin * eY + (me.columnTypeOrigin + (me.columnTypeSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1180 // server playercount
1181 s = strcat(ftos(gethostcachenumber(SLIST_FIELD_NUMHUMANS, item)), "/", ftos(gethostcachenumber(SLIST_FIELD_MAXPLAYERS, item)));
1182 draw_Text(me.realUpperMargin * eY + (me.columnPlayersOrigin + (me.columnPlayersSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1185 float XonoticServerList_keyDown(entity me, float scan, float ascii, float shift)
1187 float i = CheckItemNumber(me.selectedItem);
1190 org = boxToGlobal(eY * (me.selectedItem * me.itemHeight - me.scrollPos), me.origin, me.size);
1191 sz = boxToGlobalSize(eY * me.itemHeight + eX * (1 - me.controlWidth), me.size);
1193 me.lastBumpSelectTime = 0;
1195 if(scan == K_ENTER || scan == K_KP_ENTER)
1197 ServerList_Connect_Click(NULL, me);
1200 else if(scan == K_MOUSE2 || scan == K_SPACE)
1202 if((me.nItems != 0) && (i >= 0))
1204 main.serverInfoDialog.loadServerInfo(main.serverInfoDialog, i);
1205 DialogOpenButton_Click_withCoords(me, main.serverInfoDialog, org, sz);
1210 else if(scan == K_INS || scan == K_MOUSE3 || scan == K_KP_INS)
1212 if((me.nItems != 0) && (i >= 0))
1214 me.toggleFavorite(me, me.selectedServer);
1215 me.ipAddressBoxFocused = -1;
1220 else if(SUPER(XonoticServerList).keyDown(me, scan, ascii, shift))
1222 else if(!me.controlledTextbox)
1225 return me.controlledTextbox.keyDown(me.controlledTextbox, scan, ascii, shift);