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;
93 const float SLSF_DESCENDING = 1;
94 const float SLSF_FAVORITES = 2;
95 const float SLSF_CATEGORIES = 4;
97 const float REFRESHSERVERLIST_RESORT = 0; // sort the server list again to update for changes to e.g. favorite status, categories
98 const float REFRESHSERVERLIST_REFILTER = 1; // ..., also update filter and sort criteria
99 const float REFRESHSERVERLIST_ASK = 2; // ..., also suggest querying servers now
100 const float REFRESHSERVERLIST_RESET = 3; // ..., also clear the list first
102 // function declarations
103 entity RetrieveCategoryEnt(float catnum);
105 float IsServerInList(string list, string srv);
106 #define IsFavorite(srv) IsServerInList(cvar_string("net_slist_favorites"), srv)
107 #define IsRecommended(srv) IsServerInList(cvar_string("menu_slist_recommended"), srv) // todo: use update notification instead of cvar
109 float CheckCategoryOverride(float cat);
110 float CheckCategoryForEntry(float entry);
111 float m_getserverlistentrycategory(float entry) { return CheckCategoryOverride(CheckCategoryForEntry(entry)); }
113 void RegisterSLCategories();
115 void ServerList_Connect_Click(entity btn, entity me);
116 void ServerList_Categories_Click(entity box, entity me);
117 void ServerList_ShowEmpty_Click(entity box, entity me);
118 void ServerList_ShowFull_Click(entity box, entity me);
119 void ServerList_Filter_Change(entity box, entity me);
120 void ServerList_Favorite_Click(entity btn, entity me);
121 void ServerList_Info_Click(entity btn, entity me);
122 void ServerList_Update_favoriteButton(entity btn, entity me);
124 // fields for category entities
125 #define MAX_CATEGORIES 9
126 #define CATEGORY_FIRST 1
127 entity categories[MAX_CATEGORIES];
128 float category_ent_count;
131 .string cat_enoverride_string;
132 .string cat_dioverride_string;
133 .float cat_enoverride;
134 .float cat_dioverride;
136 // fields for drawing categories
137 float category_name[MAX_CATEGORIES];
138 float category_item[MAX_CATEGORIES];
139 float category_draw_count;
141 #define SLIST_CATEGORIES \
142 SLIST_CATEGORY(CAT_FAVORITED, "", "", ZCTX(_("SLCAT^Favorites"))) \
143 SLIST_CATEGORY(CAT_RECOMMENDED, "", "CAT_SERVERS", ZCTX(_("SLCAT^Recommended"))) \
144 SLIST_CATEGORY(CAT_NORMAL, "", "CAT_SERVERS", ZCTX(_("SLCAT^Normal Servers"))) \
145 SLIST_CATEGORY(CAT_SERVERS, "CAT_NORMAL", "CAT_SERVERS", ZCTX(_("SLCAT^Servers"))) \
146 SLIST_CATEGORY(CAT_XPM, "CAT_NORMAL", "CAT_SERVERS", ZCTX(_("SLCAT^Competitive Mode"))) \
147 SLIST_CATEGORY(CAT_MODIFIED, "", "CAT_SERVERS", ZCTX(_("SLCAT^Modified Servers"))) \
148 SLIST_CATEGORY(CAT_OVERKILL, "", "CAT_SERVERS", ZCTX(_("SLCAT^Overkill Mode"))) \
149 SLIST_CATEGORY(CAT_MINSTAGIB, "", "CAT_SERVERS", ZCTX(_("SLCAT^MinstaGib Mode"))) \
150 SLIST_CATEGORY(CAT_DEFRAG, "", "CAT_SERVERS", ZCTX(_("SLCAT^Defrag Mode")))
152 #define SLIST_CATEGORY_AUTOCVAR(name) autocvar_menu_slist_categories_##name##_override
153 #define SLIST_CATEGORY(name,enoverride,dioverride,str) \
155 var string SLIST_CATEGORY_AUTOCVAR(name) = enoverride;
157 #undef SLIST_CATEGORY
161 #ifdef IMPLEMENTATION
163 void RegisterSLCategories()
166 #define SLIST_CATEGORY(name,enoverride,dioverride,str) \
167 SET_FIELD_COUNT(name, CATEGORY_FIRST, category_ent_count) \
168 CHECK_MAX_COUNT(name, MAX_CATEGORIES, category_ent_count, "SLIST_CATEGORY") \
170 categories[name - 1] = cat; \
171 cat.classname = "slist_category"; \
172 cat.cat_name = strzone(#name); \
173 cat.cat_enoverride_string = strzone(SLIST_CATEGORY_AUTOCVAR(name)); \
174 cat.cat_dioverride_string = strzone(dioverride); \
175 cat.cat_string = strzone(str);
177 #undef SLIST_CATEGORY
182 #define PROCESS_OVERRIDE(override_string,override_field) \
183 for(i = 0; i < category_ent_count; ++i) \
185 s = categories[i].override_string; \
186 if((s != "") && (s != categories[i].cat_name)) \
189 for(x = 0; x < category_ent_count; ++x) \
190 { if(categories[x].cat_name == s) { \
196 strunzone(categories[i].override_string); \
197 categories[i].override_field = catnum; \
203 "RegisterSLCategories(): Improper override '%s' for category '%s'!\n", \
205 categories[i].cat_name \
209 strunzone(categories[i].override_string); \
210 categories[i].override_field = 0; \
212 PROCESS_OVERRIDE(cat_enoverride_string, cat_enoverride)
213 PROCESS_OVERRIDE(cat_dioverride_string, cat_dioverride)
214 #undef PROCESS_OVERRIDE
217 // Supporting Functions
218 entity RetrieveCategoryEnt(float catnum)
220 if((catnum > 0) && (catnum <= category_ent_count))
222 return categories[catnum - 1];
226 error(sprintf("RetrieveCategoryEnt(%d): Improper category number!\n", catnum));
231 float IsServerInList(string list, string srv)
237 srv = netaddress_resolve(srv, 26000);
240 p = crypto_getidfp(srv);
241 n = tokenize_console(list);
242 for(i = 0; i < n; ++i)
244 if(substring(argv(i), 0, 1) != "[" && strlen(argv(i)) == 44 && strstrofs(argv(i), ".", 0) < 0)
252 if(srv == netaddress_resolve(argv(i), 26000))
259 float CheckCategoryOverride(float cat)
261 entity catent = RetrieveCategoryEnt(cat);
264 float override = (autocvar_menu_slist_categories ? catent.cat_enoverride : catent.cat_dioverride);
265 if(override) { return override; }
270 error(sprintf("CheckCategoryOverride(%d): Improper category number!\n", cat));
275 float CheckCategoryForEntry(float entry)
277 string s, k, v, modtype = "";
278 float j, m, impure = 0;
279 s = gethostcachestring(SLIST_FIELD_QCSTATUS, entry);
280 m = tokenizebyseparator(s, ":");
282 for(j = 2; j < m; ++j)
286 k = substring(argv(j), 0, 1);
287 v = substring(argv(j), 1, -1);
288 if(k == "P") { impure = stof(v); }
289 else if(k == "M") { modtype = strtolower(v); }
292 if(impure > autocvar_menu_slist_purethreshold) { impure = TRUE; }
293 else { impure = FALSE; }
295 if(gethostcachenumber(SLIST_FIELD_ISFAVORITE, entry)) { return CAT_FAVORITED; }
296 if(IsRecommended(gethostcachestring(SLIST_FIELD_CNAME, entry))) { return CAT_RECOMMENDED; }
297 else if(modtype != "xonotic")
301 // old servers which don't report their mod name are considered modified now
302 case "": { return CAT_MODIFIED; }
304 case "xpm": { return CAT_XPM; }
305 case "minstagib": { return CAT_MINSTAGIB; }
306 case "overkill": { return CAT_OVERKILL; }
307 //case "nix": { return CAT_NIX; }
308 //case "newtoys": { return CAT_NEWTOYS; }
310 // "cts" is allowed as compat, xdf is replacement
312 case "xdf": { return CAT_DEFRAG; }
314 default: { dprint(sprintf("Found strange mod type: %s\n", modtype)); return CAT_MODIFIED; }
317 else { return (impure ? CAT_MODIFIED : CAT_NORMAL); }
319 // should never hit this point
320 error(sprintf("CheckCategoryForEntry(%d): Function fell through without normal return!\n", entry));
324 float CheckItemNumber(float num)
328 if not(category_draw_count) { return num; } // there are no categories to process
330 for(i = 0, n = 1; n <= category_draw_count; ++i, ++n)
332 if(category_item[i] == (num - i)) { return -category_name[i]; }
333 else if(n == category_draw_count) { return (num - n); }
334 else if((num - i) <= category_item[n]) { return (num - n); }
337 // should never hit this point
338 error(sprintf("CheckItemNumber(%d): Function fell through without normal return!\n", num));
342 void XonoticServerList_toggleFavorite(entity me, string srv)
344 string s, s0, s1, s2, srv_resolved, p;
346 srv_resolved = netaddress_resolve(srv, 26000);
347 p = crypto_getidfp(srv_resolved);
348 s = cvar_string("net_slist_favorites");
349 n = tokenize_console(s);
351 for(i = 0; i < n; ++i)
353 if(substring(argv(i), 0, 1) != "[" && strlen(argv(i)) == 44 && strstrofs(argv(i), ".", 0) < 0)
361 if(srv_resolved != netaddress_resolve(argv(i), 26000))
366 s0 = substring(s, 0, argv_end_index(i - 1));
368 s2 = substring(s, argv_start_index(i + 1), -1);
369 if(s0 != "" && s2 != "")
371 cvar_set("net_slist_favorites", strcat(s0, s1, s2));
372 s = cvar_string("net_slist_favorites");
373 n = tokenize_console(s);
384 cvar_set("net_slist_favorites", strcat(s, s1, p));
386 cvar_set("net_slist_favorites", strcat(s, s1, srv));
389 me.refreshServerList(me, REFRESHSERVERLIST_RESORT);
392 void ServerList_Update_favoriteButton(entity btn, entity me)
394 me.favoriteButton.setText(me.favoriteButton,
395 (IsFavorite(me.ipAddressBox.text) ?
396 _("Remove") : _("Bookmark")
401 entity makeXonoticServerList()
404 me = spawnXonoticServerList();
405 me.configureXonoticServerList(me);
408 void XonoticServerList_configureXonoticServerList(entity me)
410 me.configureXonoticListBox(me);
413 #define SLIST_FIELD(suffix,name) SLIST_FIELD_##suffix = gethostcacheindexforkey(name);
421 RegisterSLCategories();
423 void XonoticServerList_setSelected(entity me, float i)
425 // todo: add logic to skip categories
427 save = me.selectedItem;
428 SUPER(XonoticServerList).setSelected(me, i);
430 if(me.selectedItem == save)
436 //if(gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT) != CheckItemNumber(me.nItems))
437 // { error("^1XonoticServerList_setSelected(); ERROR: ^7Host cache viewcount mismatches nItems!\n"); return; } // sorry, it would be wrong
438 // ^ todo: make this work somehow?
440 #define SET_SELECTED_SERVER(cachenum) \
441 if(me.selectedServer) { strunzone(me.selectedServer); } \
442 me.selectedServer = strzone(gethostcachestring(SLIST_FIELD_CNAME, cachenum)); \
443 me.ipAddressBox.setText(me.ipAddressBox, me.selectedServer); \
444 me.ipAddressBox.cursorPos = strlen(me.selectedServer); \
445 me.ipAddressBoxFocused = -1; \
448 num = CheckItemNumber(me.selectedItem);
450 if(num >= 0) { SET_SELECTED_SERVER(num); }
451 else if(save > me.selectedItem)
453 if(me.selectedItem == 0) { return; }
456 if(me.lastClickedTime >= me.lastBumpSelectTime)
458 SUPER(XonoticServerList).setSelected(me, me.selectedItem - 1);
459 num = CheckItemNumber(me.selectedItem);
462 me.lastBumpSelectTime = time;
463 SET_SELECTED_SERVER(num);
468 else if(save < me.selectedItem)
470 if(me.selectedItem == me.nItems) { return; }
473 if(me.lastClickedTime >= me.lastBumpSelectTime)
475 SUPER(XonoticServerList).setSelected(me, me.selectedItem + 1);
476 num = CheckItemNumber(me.selectedItem);
479 me.lastBumpSelectTime = time;
480 SET_SELECTED_SERVER(num);
487 void XonoticServerList_refreshServerList(entity me, float mode)
489 //print("refresh of type ", ftos(mode), "\n");
491 if(mode >= REFRESHSERVERLIST_REFILTER)
495 string s, typestr, modstr;
499 m = strstrofs(s, ":", 0);
502 typestr = substring(s, 0, m);
503 s = substring(s, m + 1, strlen(s) - m - 1);
504 while(substring(s, 0, 1) == " ")
505 s = substring(s, 1, strlen(s) - 1);
510 modstr = cvar_string("menu_slist_modfilter");
512 m = SLIST_MASK_AND - 1;
513 resethostcachemasks();
515 // ping: reject negative ping (no idea why this happens in the first place, engine bug)
516 sethostcachemasknumber(++m, SLIST_FIELD_PING, 0, SLIST_TEST_GREATEREQUAL);
519 if(!me.filterShowFull)
521 sethostcachemasknumber(++m, SLIST_FIELD_FREESLOTS, 1, SLIST_TEST_GREATEREQUAL); // legacy
522 sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, ":S0:", SLIST_TEST_NOTCONTAIN); // g_maxplayers support
526 if(!me.filterShowEmpty)
527 sethostcachemasknumber(++m, SLIST_FIELD_NUMHUMANS, 1, SLIST_TEST_GREATEREQUAL);
529 // gametype filtering
531 sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, strcat(typestr, ":"), SLIST_TEST_STARTSWITH);
536 if(substring(modstr, 0, 1) == "!")
537 sethostcachemaskstring(++m, SLIST_FIELD_MOD, resolvemod(substring(modstr, 1, strlen(modstr) - 1)), SLIST_TEST_NOTEQUAL);
539 sethostcachemaskstring(++m, SLIST_FIELD_MOD, resolvemod(modstr), SLIST_TEST_EQUAL);
543 n = tokenizebyseparator(_Nex_ExtResponseSystem_BannedServers, " ");
544 for(i = 0; i < n; ++i)
546 sethostcachemaskstring(++m, SLIST_FIELD_CNAME, argv(i), SLIST_TEST_NOTSTARTSWITH);
548 m = SLIST_MASK_OR - 1;
551 sethostcachemaskstring(++m, SLIST_FIELD_NAME, s, SLIST_TEST_CONTAINS);
552 sethostcachemaskstring(++m, SLIST_FIELD_MAP, s, SLIST_TEST_CONTAINS);
553 sethostcachemaskstring(++m, SLIST_FIELD_PLAYERS, s, SLIST_TEST_CONTAINS);
554 sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, strcat(s, ":"), SLIST_TEST_STARTSWITH);
558 //listflags |= SLSF_FAVORITES;
559 listflags |= SLSF_CATEGORIES;
560 if(me.currentSortOrder < 0) { listflags |= SLSF_DESCENDING; }
561 sethostcachesort(me.currentSortField, listflags);
565 if(mode >= REFRESHSERVERLIST_ASK)
566 refreshhostcache(mode >= REFRESHSERVERLIST_RESET);
568 void XonoticServerList_focusEnter(entity me)
570 if(time < me.nextRefreshTime)
572 //print("sorry, no refresh yet\n");
575 me.nextRefreshTime = time + 10;
576 me.refreshServerList(me, REFRESHSERVERLIST_ASK);
579 void XonoticServerList_draw(entity me)
581 float i, found, owned, num;
583 if(_Nex_ExtResponseSystem_BannedServersNeedsRefresh)
587 _Nex_ExtResponseSystem_BannedServersNeedsRefresh = 0;
590 if(me.currentSortField == -1)
592 me.setSortOrder(me, SLIST_FIELD_PING, +1);
593 me.refreshServerList(me, REFRESHSERVERLIST_RESET);
595 else if(me.needsRefresh == 1)
597 me.needsRefresh = 2; // delay by one frame to make sure "slist" has been executed
599 else if(me.needsRefresh == 2)
602 me.refreshServerList(me, REFRESHSERVERLIST_REFILTER);
605 owned = ((me.selectedServer == me.ipAddressBox.text) && (me.ipAddressBox.text != ""));
607 for(i = 0; i < category_draw_count; ++i) { category_name[i] = -1; category_item[i] = -1; }
608 category_draw_count = 0;
610 if(autocvar_menu_slist_categories >= 0) // if less than 0, don't even draw a category heading for favorites
612 float itemcount = gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT);
613 me.nItems = itemcount;
615 //float visible = floor(me.scrollPos / me.itemHeight);
616 // ^ unfortunately no such optimization can be made-- we must process through the
617 // entire list, otherwise there is no way to know which item is first in its category.
620 for(i = 0; i < itemcount; ++i)
622 cat = gethostcachenumber(SLIST_FIELD_CATEGORY, i);
625 if(category_draw_count == 0)
627 category_name[category_draw_count] = cat;
628 category_item[category_draw_count] = i;
629 ++category_draw_count;
635 for(x = 0; x < category_draw_count; ++x) { if(cat == category_name[x]) { found = 1; } }
638 category_name[category_draw_count] = cat;
639 category_item[category_draw_count] = i;
640 ++category_draw_count;
646 if(autocvar_menu_slist_categories_onlyifmultiple && (category_draw_count == 1))
648 category_name[0] = -1;
649 category_item[0] = -1;
650 category_draw_count = 0;
651 me.nItems = itemcount;
654 else { me.nItems = gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT); }
656 me.connectButton.disabled = ((me.nItems == 0) && (me.ipAddressBox.text == ""));
657 me.infoButton.disabled = ((me.nItems == 0) || !owned);
658 me.favoriteButton.disabled = ((me.nItems == 0) && (me.ipAddressBox.text == ""));
661 if(me.selectedServer)
663 for(i = 0; i < me.nItems; ++i)
665 num = CheckItemNumber(i);
668 if(gethostcachestring(SLIST_FIELD_CNAME, num) == me.selectedServer)
670 if(i != me.selectedItem)
672 me.lastClickedServer = -1;
685 if(me.selectedItem >= me.nItems) { me.selectedItem = me.nItems - 1; }
686 if(me.selectedServer) { strunzone(me.selectedServer); }
688 num = CheckItemNumber(me.selectedItem);
689 if(num >= 0) { me.selectedServer = strzone(gethostcachestring(SLIST_FIELD_CNAME, num)); }
695 if(me.selectedServer != me.ipAddressBox.text)
697 me.ipAddressBox.setText(me.ipAddressBox, me.selectedServer);
698 me.ipAddressBox.cursorPos = strlen(me.selectedServer);
699 me.ipAddressBoxFocused = -1;
703 if(me.ipAddressBoxFocused != me.ipAddressBox.focused)
705 if(me.ipAddressBox.focused || me.ipAddressBoxFocused < 0)
706 ServerList_Update_favoriteButton(NULL, me);
707 me.ipAddressBoxFocused = me.ipAddressBox.focused;
710 SUPER(XonoticServerList).draw(me);
712 void ServerList_PingSort_Click(entity btn, entity me)
714 me.setSortOrder(me, SLIST_FIELD_PING, +1);
716 void ServerList_NameSort_Click(entity btn, entity me)
718 me.setSortOrder(me, SLIST_FIELD_NAME, -1); // why?
720 void ServerList_MapSort_Click(entity btn, entity me)
722 me.setSortOrder(me, SLIST_FIELD_MAP, -1); // why?
724 void ServerList_PlayerSort_Click(entity btn, entity me)
726 me.setSortOrder(me, SLIST_FIELD_NUMHUMANS, -1);
728 void ServerList_TypeSort_Click(entity btn, entity me)
733 m = strstrofs(s, ":", 0);
736 s = substring(s, 0, m);
737 while(substring(s, m+1, 1) == " ") // skip spaces
743 for(i = 1; ; i *= 2) // 20 modes ought to be enough for anyone
745 t = MapInfo_Type_ToString(i);
747 if(t == "") // it repeats (default case)
750 // choose the first one
751 s = MapInfo_Type_ToString(1);
756 // the type was found
757 // choose the next one
758 s = MapInfo_Type_ToString(i * 2);
760 s = MapInfo_Type_ToString(1);
767 s = strcat(s, substring(me.filterString, m+1, strlen(me.filterString) - m - 1));
769 me.controlledTextbox.setText(me.controlledTextbox, s);
770 me.controlledTextbox.keyDown(me.controlledTextbox, K_END, 0, 0);
771 me.controlledTextbox.keyUp(me.controlledTextbox, K_END, 0, 0);
772 //ServerList_Filter_Change(me.controlledTextbox, me);
774 void ServerList_Filter_Change(entity box, entity me)
777 strunzone(me.filterString);
779 me.filterString = strzone(box.text);
781 me.filterString = string_null;
782 me.refreshServerList(me, REFRESHSERVERLIST_REFILTER);
784 me.ipAddressBox.setText(me.ipAddressBox, "");
785 me.ipAddressBox.cursorPos = 0;
786 me.ipAddressBoxFocused = -1;
788 void ServerList_Categories_Click(entity box, entity me)
790 box.setChecked(box, autocvar_menu_slist_categories = !autocvar_menu_slist_categories);
791 ///refreshhostcache(TRUE);
793 //cvar_set("net_slist_pause", "0");
794 //Destroy_Category_Entities();
795 //CALL_ACCUMULATED_FUNCTION(RegisterSLCategories);
796 //me.refreshServerList(me, REFRESHSERVERLIST_REFILTER);
798 me.refreshServerList(me, REFRESHSERVERLIST_RESORT);
800 me.ipAddressBox.setText(me.ipAddressBox, "");
801 me.ipAddressBox.cursorPos = 0;
802 me.ipAddressBoxFocused = -1;
804 void ServerList_ShowEmpty_Click(entity box, entity me)
806 box.setChecked(box, me.filterShowEmpty = !me.filterShowEmpty);
807 me.refreshServerList(me, REFRESHSERVERLIST_REFILTER);
809 me.ipAddressBox.setText(me.ipAddressBox, "");
810 me.ipAddressBox.cursorPos = 0;
811 me.ipAddressBoxFocused = -1;
813 void ServerList_ShowFull_Click(entity box, entity me)
815 box.setChecked(box, me.filterShowFull = !me.filterShowFull);
816 me.refreshServerList(me, REFRESHSERVERLIST_REFILTER);
818 me.ipAddressBox.setText(me.ipAddressBox, "");
819 me.ipAddressBox.cursorPos = 0;
820 me.ipAddressBoxFocused = -1;
822 void XonoticServerList_setSortOrder(entity me, float fld, float direction)
824 if(me.currentSortField == fld)
825 direction = -me.currentSortOrder;
826 me.currentSortOrder = direction;
827 me.currentSortField = fld;
828 me.sortButton1.forcePressed = (fld == SLIST_FIELD_PING);
829 me.sortButton2.forcePressed = (fld == SLIST_FIELD_NAME);
830 me.sortButton3.forcePressed = (fld == SLIST_FIELD_MAP);
831 me.sortButton4.forcePressed = 0;
832 me.sortButton5.forcePressed = (fld == SLIST_FIELD_NUMHUMANS);
834 if(me.selectedServer)
835 strunzone(me.selectedServer);
836 me.selectedServer = string_null;
837 me.refreshServerList(me, REFRESHSERVERLIST_REFILTER);
839 void XonoticServerList_positionSortButton(entity me, entity btn, float theOrigin, float theSize, string theTitle, void(entity, entity) theFunc)
841 vector originInLBSpace, sizeInLBSpace;
842 originInLBSpace = eY * (-me.itemHeight);
843 sizeInLBSpace = eY * me.itemHeight + eX * (1 - me.controlWidth);
845 vector originInDialogSpace, sizeInDialogSpace;
846 originInDialogSpace = boxToGlobal(originInLBSpace, me.Container_origin, me.Container_size);
847 sizeInDialogSpace = boxToGlobalSize(sizeInLBSpace, me.Container_size);
849 btn.Container_origin_x = originInDialogSpace_x + sizeInDialogSpace_x * theOrigin;
850 btn.Container_size_x = sizeInDialogSpace_x * theSize;
851 btn.setText(btn, theTitle);
852 btn.onClick = theFunc;
853 btn.onClickEntity = me;
856 void XonoticServerList_resizeNotify(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
858 SUPER(XonoticServerList).resizeNotify(me, relOrigin, relSize, absOrigin, absSize);
860 me.realFontSize_y = me.fontSize / (absSize_y * me.itemHeight);
861 me.realFontSize_x = me.fontSize / (absSize_x * (1 - me.controlWidth));
862 me.realUpperMargin = 0.5 * (1 - me.realFontSize_y);
864 me.columnIconsOrigin = 0;
865 me.columnIconsSize = me.realFontSize_x * 4 * me.iconsSizeFactor;
866 me.columnPingSize = me.realFontSize_x * 3;
867 me.columnMapSize = me.realFontSize_x * 10;
868 me.columnTypeSize = me.realFontSize_x * 4;
869 me.columnPlayersSize = me.realFontSize_x * 5;
870 me.columnNameSize = 1 - me.columnPlayersSize - me.columnMapSize - me.columnPingSize - me.columnIconsSize - me.columnTypeSize - 5 * me.realFontSize_x;
871 me.columnPingOrigin = me.columnIconsOrigin + me.columnIconsSize + me.realFontSize_x;
872 me.columnNameOrigin = me.columnPingOrigin + me.columnPingSize + me.realFontSize_x;
873 me.columnMapOrigin = me.columnNameOrigin + me.columnNameSize + me.realFontSize_x;
874 me.columnTypeOrigin = me.columnMapOrigin + me.columnMapSize + me.realFontSize_x;
875 me.columnPlayersOrigin = me.columnTypeOrigin + me.columnTypeSize + me.realFontSize_x;
877 me.positionSortButton(me, me.sortButton1, me.columnPingOrigin, me.columnPingSize, _("Ping"), ServerList_PingSort_Click);
878 me.positionSortButton(me, me.sortButton2, me.columnNameOrigin, me.columnNameSize, _("Host name"), ServerList_NameSort_Click);
879 me.positionSortButton(me, me.sortButton3, me.columnMapOrigin, me.columnMapSize, _("Map"), ServerList_MapSort_Click);
880 me.positionSortButton(me, me.sortButton4, me.columnTypeOrigin, me.columnTypeSize, _("Type"), ServerList_TypeSort_Click);
881 me.positionSortButton(me, me.sortButton5, me.columnPlayersOrigin, me.columnPlayersSize, _("Players"), ServerList_PlayerSort_Click);
884 f = me.currentSortField;
887 me.currentSortField = -1;
888 me.setSortOrder(me, f, me.currentSortOrder); // force resetting the sort order
891 void ServerList_Connect_Click(entity btn, entity me)
893 localcmd(sprintf("connect %s\n",
894 ((me.ipAddressBox.text != "") ?
895 me.ipAddressBox.text : me.selectedServer
899 void ServerList_Favorite_Click(entity btn, entity me)
902 ipstr = netaddress_resolve(me.ipAddressBox.text, 26000);
905 me.toggleFavorite(me, me.ipAddressBox.text);
906 me.ipAddressBoxFocused = -1;
909 void ServerList_Info_Click(entity btn, entity me)
911 main.serverInfoDialog.loadServerInfo(main.serverInfoDialog, CheckItemNumber(me.selectedItem));
912 DialogOpenButton_Click(me, main.serverInfoDialog);
914 void XonoticServerList_clickListBoxItem(entity me, float i, vector where)
916 float num = CheckItemNumber(i);
919 if(num == me.lastClickedServer)
920 if(time < me.lastClickedTime + 0.3)
923 ServerList_Connect_Click(NULL, me);
925 me.lastClickedServer = num;
926 me.lastClickedTime = time;
929 void XonoticServerList_drawListBoxItem(entity me, float i, vector absSize, float isSelected)
931 // layout: Ping, Server name, Map name, NP, TP, MP
936 float m, pure, freeslots, j, sflags;
937 string s, typestr, versionstr, k, v, modname;
939 float item = CheckItemNumber(i);
940 //print(sprintf("time: %f, i: %d, item: %d, nitems: %d\n", time, i, item, me.nItems));
944 entity catent = RetrieveCategoryEnt(-item);
948 eY * me.realUpperMargin
950 eX * (me.columnNameOrigin + (me.columnNameSize - draw_TextWidth(catent.cat_string, 0, me.realFontSize)) * 0.5),
962 draw_Fill('0 0 0', '1 1 0', SKINCOLOR_LISTBOX_SELECTED, SKINALPHA_LISTBOX_SELECTED);
964 s = gethostcachestring(SLIST_FIELD_QCSTATUS, item);
965 m = tokenizebyseparator(s, ":");
970 versionstr = argv(1);
976 for(j = 2; j < m; ++j)
980 k = substring(argv(j), 0, 1);
981 v = substring(argv(j), 1, -1);
992 #ifdef COMPAT_NO_MOD_IS_XONOTIC
998 SLIST_FIELD_MOD = gethostcacheindexforkey("mod");
999 s = gethostcachestring(SLIST_FIELD_MOD, item);
1001 if(modname == "Xonotic")
1005 // list the mods here on which the pure server check actually works
1006 if(modname != "Xonotic")
1007 if(modname != "MinstaGib")
1008 if(modname != "CTS")
1009 if(modname != "NIX")
1010 if(modname != "NewToys")
1013 if(gethostcachenumber(SLIST_FIELD_FREESLOTS, item) <= 0)
1014 theAlpha = SKINALPHA_SERVERLIST_FULL;
1015 else if(freeslots == 0)
1016 theAlpha = SKINALPHA_SERVERLIST_FULL; // g_maxplayers support
1017 else if not(gethostcachenumber(SLIST_FIELD_NUMHUMANS, item))
1018 theAlpha = SKINALPHA_SERVERLIST_EMPTY;
1022 p = gethostcachenumber(SLIST_FIELD_PING, item);
1024 #define PING_MED 200
1025 #define PING_HIGH 500
1027 theColor = SKINCOLOR_SERVERLIST_LOWPING + (SKINCOLOR_SERVERLIST_MEDPING - SKINCOLOR_SERVERLIST_LOWPING) * (p / PING_LOW);
1028 else if(p < PING_MED)
1029 theColor = SKINCOLOR_SERVERLIST_MEDPING + (SKINCOLOR_SERVERLIST_HIGHPING - SKINCOLOR_SERVERLIST_MEDPING) * ((p - PING_LOW) / (PING_MED - PING_LOW));
1030 else if(p < PING_HIGH)
1032 theColor = SKINCOLOR_SERVERLIST_HIGHPING;
1033 theAlpha *= 1 + (SKINALPHA_SERVERLIST_HIGHPING - 1) * ((p - PING_MED) / (PING_HIGH - PING_MED));
1038 theAlpha *= SKINALPHA_SERVERLIST_HIGHPING;
1041 if(gethostcachenumber(SLIST_FIELD_ISFAVORITE, item))
1043 theColor = theColor * (1 - SKINALPHA_SERVERLIST_FAVORITE) + SKINCOLOR_SERVERLIST_FAVORITE * SKINALPHA_SERVERLIST_FAVORITE;
1044 theAlpha = theAlpha * (1 - SKINALPHA_SERVERLIST_FAVORITE) + SKINALPHA_SERVERLIST_FAVORITE;
1047 s = gethostcachestring(SLIST_FIELD_CNAME, item);
1050 if(substring(s, 0, 1) == "[")
1055 else if(strstrofs("0123456789", substring(s, 0, 1), 0) >= 0)
1061 q = stof(substring(crypto_getencryptlevel(s), 0, 1));
1062 if((q <= 0 && cvar("crypto_aeslevel") >= 3) || (q >= 3 && cvar("crypto_aeslevel") <= 0))
1064 theColor = SKINCOLOR_SERVERLIST_IMPOSSIBLE;
1065 theAlpha = SKINALPHA_SERVERLIST_IMPOSSIBLE;
1070 if(cvar("crypto_aeslevel") >= 2)
1075 if(cvar("crypto_aeslevel") >= 1)
1085 // 2: AES recommended but not available
1086 // 3: AES possible and will be used
1087 // 4: AES recommended and will be used
1093 vector iconSize = '0 0 0';
1094 iconSize_y = me.realFontSize_y * me.iconsSizeFactor;
1095 iconSize_x = me.realFontSize_x * me.iconsSizeFactor;
1097 vector iconPos = '0 0 0';
1098 iconPos_x = (me.columnIconsSize - 3 * iconSize_x) * 0.5;
1099 iconPos_y = (1 - iconSize_y) * 0.5;
1103 if not(me.seenIPv4 && me.seenIPv6)
1105 iconPos_x += iconSize_x * 0.5;
1107 else if(me.seenIPv4 && me.seenIPv6)
1111 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_ipv6"), 0); // PRECACHE_PIC_MIPMAP
1113 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_ipv4"), 0); // PRECACHE_PIC_MIPMAP
1115 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1116 iconPos_x += iconSize_x;
1121 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_aeslevel", ftos(q)), 0); // PRECACHE_PIC_MIPMAP
1122 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1124 iconPos_x += iconSize_x;
1126 if(modname == "Xonotic")
1130 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_pure1"), PRECACHE_PIC_MIPMAP);
1131 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1136 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_mod_", modname), PRECACHE_PIC_MIPMAP);
1137 if(draw_PictureSize(n) == '0 0 0')
1138 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_mod_"), PRECACHE_PIC_MIPMAP);
1140 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1142 draw_Picture(iconPos, n, iconSize, '1 1 1', SKINALPHA_SERVERLIST_ICON_NONPURE);
1144 iconPos_x += iconSize_x;
1146 if(sflags >= 0 && (sflags & SERVERFLAG_PLAYERSTATS))
1148 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_stats1"), 0); // PRECACHE_PIC_MIPMAP
1149 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1151 iconPos_x += iconSize_x;
1159 draw_Text(me.realUpperMargin * eY + (me.columnPingOrigin + me.columnPingSize - draw_TextWidth(s, 0, me.realFontSize)) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1162 s = draw_TextShortenToWidth(gethostcachestring(SLIST_FIELD_NAME, item), me.columnNameSize, 0, me.realFontSize);
1163 draw_Text(me.realUpperMargin * eY + me.columnNameOrigin * eX, s, me.realFontSize, theColor, theAlpha, 0);
1166 s = draw_TextShortenToWidth(gethostcachestring(SLIST_FIELD_MAP, item), me.columnMapSize, 0, me.realFontSize);
1167 draw_Text(me.realUpperMargin * eY + (me.columnMapOrigin + (me.columnMapSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1170 s = draw_TextShortenToWidth(typestr, me.columnTypeSize, 0, me.realFontSize);
1171 draw_Text(me.realUpperMargin * eY + (me.columnTypeOrigin + (me.columnTypeSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1173 // server playercount
1174 s = strcat(ftos(gethostcachenumber(SLIST_FIELD_NUMHUMANS, item)), "/", ftos(gethostcachenumber(SLIST_FIELD_MAXPLAYERS, item)));
1175 draw_Text(me.realUpperMargin * eY + (me.columnPlayersOrigin + (me.columnPlayersSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1178 float XonoticServerList_keyDown(entity me, float scan, float ascii, float shift)
1180 float i = CheckItemNumber(me.selectedItem);
1183 org = boxToGlobal(eY * (me.selectedItem * me.itemHeight - me.scrollPos), me.origin, me.size);
1184 sz = boxToGlobalSize(eY * me.itemHeight + eX * (1 - me.controlWidth), me.size);
1186 me.lastBumpSelectTime = 0;
1188 if(scan == K_ENTER || scan == K_KP_ENTER)
1190 ServerList_Connect_Click(NULL, me);
1193 else if(scan == K_MOUSE2 || scan == K_SPACE)
1195 if((me.nItems != 0) && (i >= 0))
1197 main.serverInfoDialog.loadServerInfo(main.serverInfoDialog, i);
1198 DialogOpenButton_Click_withCoords(me, main.serverInfoDialog, org, sz);
1203 else if(scan == K_INS || scan == K_MOUSE3 || scan == K_KP_INS)
1205 if((me.nItems != 0) && (i >= 0))
1207 me.toggleFavorite(me, me.selectedServer);
1208 me.ipAddressBoxFocused = -1;
1213 else if(SUPER(XonoticServerList).keyDown(me, scan, ascii, shift))
1215 else if(!me.controlledTextbox)
1218 return me.controlledTextbox.keyDown(me.controlledTextbox, scan, ascii, shift);