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 float Get_Cat_Num_FromString(string input);
104 entity Get_Cat_Ent(float catnum);
106 float IsServerInList(string list, string srv);
107 #define IsFavorite(srv) IsServerInList(cvar_string("net_slist_favorites"), srv)
108 #define IsRecommended(srv) IsServerInList(cvar_string("menu_slist_recommended"), srv) // todo: use update notification instead of cvar
110 float CheckCategoryOverride(float cat);
111 float CheckCategoryForEntry(float entry);
112 float m_getserverlistentrycategory(float entry) { return CheckCategoryOverride(CheckCategoryForEntry(entry)); }
114 void RegisterSLCategories();
116 void ServerList_Connect_Click(entity btn, entity me);
117 void ServerList_Categories_Click(entity box, entity me);
118 void ServerList_ShowEmpty_Click(entity box, entity me);
119 void ServerList_ShowFull_Click(entity box, entity me);
120 void ServerList_Filter_Change(entity box, entity me);
121 void ServerList_Favorite_Click(entity btn, entity me);
122 void ServerList_Info_Click(entity btn, entity me);
123 void ServerList_Update_favoriteButton(entity btn, entity me);
125 // fields for category entities
126 #define MAX_CATEGORIES 9
127 #define CATEGORY_FIRST 1
128 entity categories[MAX_CATEGORIES];
129 float category_ent_count;
132 .string cat_enoverride_string;
133 .string cat_dioverride_string;
134 .float cat_enoverride;
135 .float cat_dioverride;
137 // fields for drawing categories
138 float category_name[MAX_CATEGORIES];
139 float category_item[MAX_CATEGORIES];
140 float category_draw_count;
142 #define SLIST_CATEGORIES \
143 SLIST_CATEGORY(CAT_FAVORITED, "", "", ZCTX(_("SLCAT^Favorites"))) \
144 SLIST_CATEGORY(CAT_RECOMMENDED, "", "CAT_SERVERS", ZCTX(_("SLCAT^Recommended"))) \
145 SLIST_CATEGORY(CAT_NORMAL, "", "CAT_SERVERS", ZCTX(_("SLCAT^Normal Servers"))) \
146 SLIST_CATEGORY(CAT_SERVERS, "CAT_NORMAL", "CAT_SERVERS", ZCTX(_("SLCAT^Servers"))) \
147 SLIST_CATEGORY(CAT_XPM, "CAT_NORMAL", "CAT_SERVERS", ZCTX(_("SLCAT^Competitive Mode"))) \
148 SLIST_CATEGORY(CAT_MODIFIED, "", "CAT_SERVERS", ZCTX(_("SLCAT^Modified Servers"))) \
149 SLIST_CATEGORY(CAT_OVERKILL, "", "CAT_SERVERS", ZCTX(_("SLCAT^Overkill Mode"))) \
150 SLIST_CATEGORY(CAT_MINSTAGIB, "", "CAT_SERVERS", ZCTX(_("SLCAT^MinstaGib Mode"))) \
151 SLIST_CATEGORY(CAT_DEFRAG, "", "CAT_SERVERS", ZCTX(_("SLCAT^Defrag Mode")))
153 #define SLIST_CATEGORY_AUTOCVAR(name) autocvar_menu_slist_categories_##name##_override
154 #define SLIST_CATEGORY(name,enoverride,dioverride,str) \
156 var string SLIST_CATEGORY_AUTOCVAR(name) = enoverride;
158 #undef SLIST_CATEGORY
162 #ifdef IMPLEMENTATION
164 void RegisterSLCategories()
167 #define SLIST_CATEGORY(name,enoverride,dioverride,str) \
168 SET_FIELD_COUNT(name, CATEGORY_FIRST, category_ent_count) \
169 CHECK_MAX_COUNT(name, MAX_CATEGORIES, category_ent_count, "SLIST_CATEGORY") \
171 categories[name - 1] = cat; \
172 cat.classname = "slist_category"; \
173 cat.cat_name = strzone(#name); \
174 cat.cat_enoverride_string = strzone(SLIST_CATEGORY_AUTOCVAR(name)); \
175 cat.cat_dioverride_string = strzone(dioverride); \
176 cat.cat_string = strzone(str);
178 #undef SLIST_CATEGORY
183 #define PROCESS_OVERRIDE(override_string,override_field) \
184 for(i = 0; i < category_ent_count; ++i) \
186 s = categories[i].override_string; \
187 if((s != "") && (s != categories[i].cat_name)) \
189 catnum = Get_Cat_Num_FromString(s); \
192 strunzone(categories[i].override_string); \
193 categories[i].override_field = catnum; \
197 strunzone(categories[i].override_string); \
198 categories[i].override_field = 0; \
200 PROCESS_OVERRIDE(cat_enoverride_string, cat_enoverride)
201 PROCESS_OVERRIDE(cat_dioverride_string, cat_dioverride)
202 #undef PROCESS_OVERRIDE
205 // Supporting Functions
206 float Get_Cat_Num_FromString(string input)
209 for(i = 0; i < category_ent_count; ++i) { if(categories[i].cat_name == input) { return (i + 1); } }
210 print(sprintf("Get_Cat_Num_FromString('%s'): Improper category name!\n", input));
213 entity Get_Cat_Ent(float catnum)
215 if((catnum > 0) && (catnum <= category_ent_count))
217 return categories[catnum - 1];
221 error(sprintf("Get_Cat_Ent(%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 = Get_Cat_Ent(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(me.currentSortField == -1)
587 me.setSortOrder(me, SLIST_FIELD_PING, +1);
588 me.refreshServerList(me, REFRESHSERVERLIST_RESET);
590 else if(me.needsRefresh == 1)
592 me.needsRefresh = 2; // delay by one frame to make sure "slist" has been executed
594 else if(me.needsRefresh == 2)
597 me.refreshServerList(me, REFRESHSERVERLIST_REFILTER);
600 owned = ((me.selectedServer == me.ipAddressBox.text) && (me.ipAddressBox.text != ""));
602 for(i = 0; i < category_draw_count; ++i) { category_name[i] = -1; category_item[i] = -1; }
603 category_draw_count = 0;
605 if(autocvar_menu_slist_categories >= 0) // if less than 0, don't even draw a category heading for favorites
607 float itemcount = gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT);
608 me.nItems = itemcount;
610 //float visible = floor(me.scrollPos / me.itemHeight);
611 // ^ unfortunately no such optimization can be made-- we must process through the
612 // entire list, otherwise there is no way to know which item is first in its category.
615 for(i = 0; i < itemcount; ++i)
617 cat = gethostcachenumber(SLIST_FIELD_CATEGORY, i);
620 if(category_draw_count == 0)
622 category_name[category_draw_count] = cat;
623 category_item[category_draw_count] = i;
624 ++category_draw_count;
630 for(x = 0; x < category_draw_count; ++x) { if(cat == category_name[x]) { found = 1; } }
633 category_name[category_draw_count] = cat;
634 category_item[category_draw_count] = i;
635 ++category_draw_count;
641 if(autocvar_menu_slist_categories_onlyifmultiple && (category_draw_count == 1))
643 category_name[0] = -1;
644 category_item[0] = -1;
645 category_draw_count = 0;
646 me.nItems = itemcount;
649 else { me.nItems = gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT); }
651 me.connectButton.disabled = ((me.nItems == 0) && (me.ipAddressBox.text == ""));
652 me.infoButton.disabled = ((me.nItems == 0) || !owned);
653 me.favoriteButton.disabled = ((me.nItems == 0) && (me.ipAddressBox.text == ""));
656 if(me.selectedServer)
658 for(i = 0; i < me.nItems; ++i)
660 num = CheckItemNumber(i);
663 if(gethostcachestring(SLIST_FIELD_CNAME, num) == me.selectedServer)
665 if(i != me.selectedItem)
667 me.lastClickedServer = -1;
680 if(me.selectedItem >= me.nItems) { me.selectedItem = me.nItems - 1; }
681 if(me.selectedServer) { strunzone(me.selectedServer); }
683 num = CheckItemNumber(me.selectedItem);
684 if(num >= 0) { me.selectedServer = strzone(gethostcachestring(SLIST_FIELD_CNAME, num)); }
690 if(me.selectedServer != me.ipAddressBox.text)
692 me.ipAddressBox.setText(me.ipAddressBox, me.selectedServer);
693 me.ipAddressBox.cursorPos = strlen(me.selectedServer);
694 me.ipAddressBoxFocused = -1;
698 if(me.ipAddressBoxFocused != me.ipAddressBox.focused)
700 if(me.ipAddressBox.focused || me.ipAddressBoxFocused < 0)
701 ServerList_Update_favoriteButton(NULL, me);
702 me.ipAddressBoxFocused = me.ipAddressBox.focused;
705 SUPER(XonoticServerList).draw(me);
707 void ServerList_PingSort_Click(entity btn, entity me)
709 me.setSortOrder(me, SLIST_FIELD_PING, +1);
711 void ServerList_NameSort_Click(entity btn, entity me)
713 me.setSortOrder(me, SLIST_FIELD_NAME, -1); // why?
715 void ServerList_MapSort_Click(entity btn, entity me)
717 me.setSortOrder(me, SLIST_FIELD_MAP, -1); // why?
719 void ServerList_PlayerSort_Click(entity btn, entity me)
721 me.setSortOrder(me, SLIST_FIELD_NUMHUMANS, -1);
723 void ServerList_TypeSort_Click(entity btn, entity me)
728 m = strstrofs(s, ":", 0);
731 s = substring(s, 0, m);
732 while(substring(s, m+1, 1) == " ") // skip spaces
738 for(i = 1; ; i *= 2) // 20 modes ought to be enough for anyone
740 t = MapInfo_Type_ToString(i);
742 if(t == "") // it repeats (default case)
745 // choose the first one
746 s = MapInfo_Type_ToString(1);
751 // the type was found
752 // choose the next one
753 s = MapInfo_Type_ToString(i * 2);
755 s = MapInfo_Type_ToString(1);
762 s = strcat(s, substring(me.filterString, m+1, strlen(me.filterString) - m - 1));
764 me.controlledTextbox.setText(me.controlledTextbox, s);
765 me.controlledTextbox.keyDown(me.controlledTextbox, K_END, 0, 0);
766 me.controlledTextbox.keyUp(me.controlledTextbox, K_END, 0, 0);
767 //ServerList_Filter_Change(me.controlledTextbox, me);
769 void ServerList_Filter_Change(entity box, entity me)
772 strunzone(me.filterString);
774 me.filterString = strzone(box.text);
776 me.filterString = string_null;
777 me.refreshServerList(me, REFRESHSERVERLIST_REFILTER);
779 me.ipAddressBox.setText(me.ipAddressBox, "");
780 me.ipAddressBox.cursorPos = 0;
781 me.ipAddressBoxFocused = -1;
783 void ServerList_Categories_Click(entity box, entity me)
785 box.setChecked(box, autocvar_menu_slist_categories = !autocvar_menu_slist_categories);
786 ///refreshhostcache(TRUE);
788 //cvar_set("net_slist_pause", "0");
789 //Destroy_Category_Entities();
790 //CALL_ACCUMULATED_FUNCTION(RegisterSLCategories);
791 //me.refreshServerList(me, REFRESHSERVERLIST_REFILTER);
793 me.refreshServerList(me, REFRESHSERVERLIST_RESORT);
795 me.ipAddressBox.setText(me.ipAddressBox, "");
796 me.ipAddressBox.cursorPos = 0;
797 me.ipAddressBoxFocused = -1;
799 void ServerList_ShowEmpty_Click(entity box, entity me)
801 box.setChecked(box, me.filterShowEmpty = !me.filterShowEmpty);
802 me.refreshServerList(me, REFRESHSERVERLIST_REFILTER);
804 me.ipAddressBox.setText(me.ipAddressBox, "");
805 me.ipAddressBox.cursorPos = 0;
806 me.ipAddressBoxFocused = -1;
808 void ServerList_ShowFull_Click(entity box, entity me)
810 box.setChecked(box, me.filterShowFull = !me.filterShowFull);
811 me.refreshServerList(me, REFRESHSERVERLIST_REFILTER);
813 me.ipAddressBox.setText(me.ipAddressBox, "");
814 me.ipAddressBox.cursorPos = 0;
815 me.ipAddressBoxFocused = -1;
817 void XonoticServerList_setSortOrder(entity me, float fld, float direction)
819 if(me.currentSortField == fld)
820 direction = -me.currentSortOrder;
821 me.currentSortOrder = direction;
822 me.currentSortField = fld;
823 me.sortButton1.forcePressed = (fld == SLIST_FIELD_PING);
824 me.sortButton2.forcePressed = (fld == SLIST_FIELD_NAME);
825 me.sortButton3.forcePressed = (fld == SLIST_FIELD_MAP);
826 me.sortButton4.forcePressed = 0;
827 me.sortButton5.forcePressed = (fld == SLIST_FIELD_NUMHUMANS);
829 if(me.selectedServer)
830 strunzone(me.selectedServer);
831 me.selectedServer = string_null;
832 me.refreshServerList(me, REFRESHSERVERLIST_REFILTER);
834 void XonoticServerList_positionSortButton(entity me, entity btn, float theOrigin, float theSize, string theTitle, void(entity, entity) theFunc)
836 vector originInLBSpace, sizeInLBSpace;
837 originInLBSpace = eY * (-me.itemHeight);
838 sizeInLBSpace = eY * me.itemHeight + eX * (1 - me.controlWidth);
840 vector originInDialogSpace, sizeInDialogSpace;
841 originInDialogSpace = boxToGlobal(originInLBSpace, me.Container_origin, me.Container_size);
842 sizeInDialogSpace = boxToGlobalSize(sizeInLBSpace, me.Container_size);
844 btn.Container_origin_x = originInDialogSpace_x + sizeInDialogSpace_x * theOrigin;
845 btn.Container_size_x = sizeInDialogSpace_x * theSize;
846 btn.setText(btn, theTitle);
847 btn.onClick = theFunc;
848 btn.onClickEntity = me;
851 void XonoticServerList_resizeNotify(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
853 SUPER(XonoticServerList).resizeNotify(me, relOrigin, relSize, absOrigin, absSize);
855 me.realFontSize_y = me.fontSize / (absSize_y * me.itemHeight);
856 me.realFontSize_x = me.fontSize / (absSize_x * (1 - me.controlWidth));
857 me.realUpperMargin = 0.5 * (1 - me.realFontSize_y);
859 me.columnIconsOrigin = 0;
860 me.columnIconsSize = me.realFontSize_x * 4 * me.iconsSizeFactor;
861 me.columnPingSize = me.realFontSize_x * 3;
862 me.columnMapSize = me.realFontSize_x * 10;
863 me.columnTypeSize = me.realFontSize_x * 4;
864 me.columnPlayersSize = me.realFontSize_x * 5;
865 me.columnNameSize = 1 - me.columnPlayersSize - me.columnMapSize - me.columnPingSize - me.columnIconsSize - me.columnTypeSize - 5 * me.realFontSize_x;
866 me.columnPingOrigin = me.columnIconsOrigin + me.columnIconsSize + me.realFontSize_x;
867 me.columnNameOrigin = me.columnPingOrigin + me.columnPingSize + me.realFontSize_x;
868 me.columnMapOrigin = me.columnNameOrigin + me.columnNameSize + me.realFontSize_x;
869 me.columnTypeOrigin = me.columnMapOrigin + me.columnMapSize + me.realFontSize_x;
870 me.columnPlayersOrigin = me.columnTypeOrigin + me.columnTypeSize + me.realFontSize_x;
872 me.positionSortButton(me, me.sortButton1, me.columnPingOrigin, me.columnPingSize, _("Ping"), ServerList_PingSort_Click);
873 me.positionSortButton(me, me.sortButton2, me.columnNameOrigin, me.columnNameSize, _("Host name"), ServerList_NameSort_Click);
874 me.positionSortButton(me, me.sortButton3, me.columnMapOrigin, me.columnMapSize, _("Map"), ServerList_MapSort_Click);
875 me.positionSortButton(me, me.sortButton4, me.columnTypeOrigin, me.columnTypeSize, _("Type"), ServerList_TypeSort_Click);
876 me.positionSortButton(me, me.sortButton5, me.columnPlayersOrigin, me.columnPlayersSize, _("Players"), ServerList_PlayerSort_Click);
879 f = me.currentSortField;
882 me.currentSortField = -1;
883 me.setSortOrder(me, f, me.currentSortOrder); // force resetting the sort order
886 void ServerList_Connect_Click(entity btn, entity me)
888 localcmd(sprintf("connect %s\n",
889 ((me.ipAddressBox.text != "") ?
890 me.ipAddressBox.text : me.selectedServer
894 void ServerList_Favorite_Click(entity btn, entity me)
897 ipstr = netaddress_resolve(me.ipAddressBox.text, 26000);
900 me.toggleFavorite(me, me.ipAddressBox.text);
901 me.ipAddressBoxFocused = -1;
904 void ServerList_Info_Click(entity btn, entity me)
906 main.serverInfoDialog.loadServerInfo(main.serverInfoDialog, CheckItemNumber(me.selectedItem));
907 DialogOpenButton_Click(me, main.serverInfoDialog);
909 void XonoticServerList_clickListBoxItem(entity me, float i, vector where)
911 float num = CheckItemNumber(i);
914 if(num == me.lastClickedServer)
915 if(time < me.lastClickedTime + 0.3)
918 ServerList_Connect_Click(NULL, me);
920 me.lastClickedServer = num;
921 me.lastClickedTime = time;
924 void XonoticServerList_drawListBoxItem(entity me, float i, vector absSize, float isSelected)
926 // layout: Ping, Server name, Map name, NP, TP, MP
931 float m, pure, freeslots, j, sflags;
932 string s, typestr, versionstr, k, v, modname;
934 float item = CheckItemNumber(i);
935 //print(sprintf("time: %f, i: %d, item: %d, nitems: %d\n", time, i, item, me.nItems));
939 entity catent = Get_Cat_Ent(-item);
943 eY * me.realUpperMargin
945 eX * (me.columnNameOrigin + (me.columnNameSize - draw_TextWidth(catent.cat_string, 0, me.realFontSize)) * 0.5),
957 draw_Fill('0 0 0', '1 1 0', SKINCOLOR_LISTBOX_SELECTED, SKINALPHA_LISTBOX_SELECTED);
959 s = gethostcachestring(SLIST_FIELD_QCSTATUS, item);
960 m = tokenizebyseparator(s, ":");
965 versionstr = argv(1);
971 for(j = 2; j < m; ++j)
975 k = substring(argv(j), 0, 1);
976 v = substring(argv(j), 1, -1);
987 #ifdef COMPAT_NO_MOD_IS_XONOTIC
993 SLIST_FIELD_MOD = gethostcacheindexforkey("mod");
994 s = gethostcachestring(SLIST_FIELD_MOD, item);
996 if(modname == "Xonotic")
1000 // list the mods here on which the pure server check actually works
1001 if(modname != "Xonotic")
1002 if(modname != "MinstaGib")
1003 if(modname != "CTS")
1004 if(modname != "NIX")
1005 if(modname != "NewToys")
1008 if(gethostcachenumber(SLIST_FIELD_FREESLOTS, item) <= 0)
1009 theAlpha = SKINALPHA_SERVERLIST_FULL;
1010 else if(freeslots == 0)
1011 theAlpha = SKINALPHA_SERVERLIST_FULL; // g_maxplayers support
1012 else if not(gethostcachenumber(SLIST_FIELD_NUMHUMANS, item))
1013 theAlpha = SKINALPHA_SERVERLIST_EMPTY;
1017 p = gethostcachenumber(SLIST_FIELD_PING, item);
1019 #define PING_MED 200
1020 #define PING_HIGH 500
1022 theColor = SKINCOLOR_SERVERLIST_LOWPING + (SKINCOLOR_SERVERLIST_MEDPING - SKINCOLOR_SERVERLIST_LOWPING) * (p / PING_LOW);
1023 else if(p < PING_MED)
1024 theColor = SKINCOLOR_SERVERLIST_MEDPING + (SKINCOLOR_SERVERLIST_HIGHPING - SKINCOLOR_SERVERLIST_MEDPING) * ((p - PING_LOW) / (PING_MED - PING_LOW));
1025 else if(p < PING_HIGH)
1027 theColor = SKINCOLOR_SERVERLIST_HIGHPING;
1028 theAlpha *= 1 + (SKINALPHA_SERVERLIST_HIGHPING - 1) * ((p - PING_MED) / (PING_HIGH - PING_MED));
1033 theAlpha *= SKINALPHA_SERVERLIST_HIGHPING;
1036 if(gethostcachenumber(SLIST_FIELD_ISFAVORITE, item))
1038 theColor = theColor * (1 - SKINALPHA_SERVERLIST_FAVORITE) + SKINCOLOR_SERVERLIST_FAVORITE * SKINALPHA_SERVERLIST_FAVORITE;
1039 theAlpha = theAlpha * (1 - SKINALPHA_SERVERLIST_FAVORITE) + SKINALPHA_SERVERLIST_FAVORITE;
1042 s = gethostcachestring(SLIST_FIELD_CNAME, item);
1045 if(substring(s, 0, 1) == "[")
1050 else if(strstrofs("0123456789", substring(s, 0, 1), 0) >= 0)
1056 q = stof(substring(crypto_getencryptlevel(s), 0, 1));
1057 if((q <= 0 && cvar("crypto_aeslevel") >= 3) || (q >= 3 && cvar("crypto_aeslevel") <= 0))
1059 theColor = SKINCOLOR_SERVERLIST_IMPOSSIBLE;
1060 theAlpha = SKINALPHA_SERVERLIST_IMPOSSIBLE;
1065 if(cvar("crypto_aeslevel") >= 2)
1070 if(cvar("crypto_aeslevel") >= 1)
1080 // 2: AES recommended but not available
1081 // 3: AES possible and will be used
1082 // 4: AES recommended and will be used
1088 vector iconSize = '0 0 0';
1089 iconSize_y = me.realFontSize_y * me.iconsSizeFactor;
1090 iconSize_x = me.realFontSize_x * me.iconsSizeFactor;
1092 vector iconPos = '0 0 0';
1093 iconPos_x = (me.columnIconsSize - 3 * iconSize_x) * 0.5;
1094 iconPos_y = (1 - iconSize_y) * 0.5;
1098 if not(me.seenIPv4 && me.seenIPv6)
1100 iconPos_x += iconSize_x * 0.5;
1102 else if(me.seenIPv4 && me.seenIPv6)
1106 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_ipv6"), 0); // PRECACHE_PIC_MIPMAP
1108 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_ipv4"), 0); // PRECACHE_PIC_MIPMAP
1110 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1111 iconPos_x += iconSize_x;
1116 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_aeslevel", ftos(q)), 0); // PRECACHE_PIC_MIPMAP
1117 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1119 iconPos_x += iconSize_x;
1121 if(modname == "Xonotic")
1125 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_pure1"), PRECACHE_PIC_MIPMAP);
1126 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1131 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_mod_", modname), PRECACHE_PIC_MIPMAP);
1132 if(draw_PictureSize(n) == '0 0 0')
1133 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_mod_"), PRECACHE_PIC_MIPMAP);
1135 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1137 draw_Picture(iconPos, n, iconSize, '1 1 1', SKINALPHA_SERVERLIST_ICON_NONPURE);
1139 iconPos_x += iconSize_x;
1141 if(sflags >= 0 && (sflags & SERVERFLAG_PLAYERSTATS))
1143 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_stats1"), 0); // PRECACHE_PIC_MIPMAP
1144 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1146 iconPos_x += iconSize_x;
1154 draw_Text(me.realUpperMargin * eY + (me.columnPingOrigin + me.columnPingSize - draw_TextWidth(s, 0, me.realFontSize)) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1157 s = draw_TextShortenToWidth(gethostcachestring(SLIST_FIELD_NAME, item), me.columnNameSize, 0, me.realFontSize);
1158 draw_Text(me.realUpperMargin * eY + me.columnNameOrigin * eX, s, me.realFontSize, theColor, theAlpha, 0);
1161 s = draw_TextShortenToWidth(gethostcachestring(SLIST_FIELD_MAP, item), me.columnMapSize, 0, me.realFontSize);
1162 draw_Text(me.realUpperMargin * eY + (me.columnMapOrigin + (me.columnMapSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1165 s = draw_TextShortenToWidth(typestr, me.columnTypeSize, 0, me.realFontSize);
1166 draw_Text(me.realUpperMargin * eY + (me.columnTypeOrigin + (me.columnTypeSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1168 // server playercount
1169 s = strcat(ftos(gethostcachenumber(SLIST_FIELD_NUMHUMANS, item)), "/", ftos(gethostcachenumber(SLIST_FIELD_MAXPLAYERS, item)));
1170 draw_Text(me.realUpperMargin * eY + (me.columnPlayersOrigin + (me.columnPlayersSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1173 float XonoticServerList_keyDown(entity me, float scan, float ascii, float shift)
1175 float i = CheckItemNumber(me.selectedItem);
1178 org = boxToGlobal(eY * (me.selectedItem * me.itemHeight - me.scrollPos), me.origin, me.size);
1179 sz = boxToGlobalSize(eY * me.itemHeight + eX * (1 - me.controlWidth), me.size);
1181 me.lastBumpSelectTime = 0;
1183 if(scan == K_ENTER || scan == K_KP_ENTER)
1185 ServerList_Connect_Click(NULL, me);
1188 else if(scan == K_MOUSE2 || scan == K_SPACE)
1190 if((me.nItems != 0) && (i >= 0))
1192 main.serverInfoDialog.loadServerInfo(main.serverInfoDialog, i);
1193 DialogOpenButton_Click_withCoords(me, main.serverInfoDialog, org, sz);
1198 else if(scan == K_INS || scan == K_MOUSE3 || scan == K_KP_INS)
1200 if((me.nItems != 0) && (i >= 0))
1202 me.toggleFavorite(me, me.selectedServer);
1203 me.ipAddressBoxFocused = -1;
1208 else if(SUPER(XonoticServerList).keyDown(me, scan, ascii, shift))
1210 else if(!me.controlledTextbox)
1213 return me.controlledTextbox.keyDown(me.controlledTextbox, scan, ascii, shift);