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 float Get_Cat_Num_FromString(string input);
99 entity Get_Cat_Ent(float catnum);
101 float IsServerInList(string list, string srv);
102 #define IsFavorite(srv) IsServerInList(cvar_string("net_slist_favorites"), srv)
103 #define IsRecommended(srv) IsServerInList(cvar_string("menu_slist_recommended"), srv) // todo: use update notification instead of cvar
105 float CheckCategoryOverride(float cat);
106 float CheckCategoryForEntry(float entry);
107 float m_getserverlistentrycategory(float entry) { return CheckCategoryOverride(CheckCategoryForEntry(entry)); }
109 void RegisterSLCategories();
111 void ServerList_Connect_Click(entity btn, entity me);
112 void ServerList_Categories_Click(entity box, entity me);
113 void ServerList_ShowEmpty_Click(entity box, entity me);
114 void ServerList_ShowFull_Click(entity box, entity me);
115 void ServerList_Filter_Change(entity box, entity me);
116 void ServerList_Favorite_Click(entity btn, entity me);
117 void ServerList_Info_Click(entity btn, entity me);
118 void ServerList_Update_favoriteButton(entity btn, entity me);
120 // fields for category entities
121 #define MAX_CATEGORIES 9
122 #define CATEGORY_FIRST 1
123 entity categories[MAX_CATEGORIES];
124 float category_ent_count;
127 .string cat_enoverride_string;
128 .string cat_dioverride_string;
129 .float cat_enoverride;
130 .float cat_dioverride;
132 // fields for drawing categories
133 float category_name[MAX_CATEGORIES];
134 float category_item[MAX_CATEGORIES];
135 float category_draw_count;
137 #define SLIST_CATEGORIES \
138 SLIST_CATEGORY(CAT_FAVORITED, "", "", ZCTX(_("SLCAT^Favorites"))) \
139 SLIST_CATEGORY(CAT_RECOMMENDED, "", "CAT_SERVERS", ZCTX(_("SLCAT^Recommended"))) \
140 SLIST_CATEGORY(CAT_NORMAL, "", "CAT_SERVERS", ZCTX(_("SLCAT^Normal Servers"))) \
141 SLIST_CATEGORY(CAT_SERVERS, "CAT_NORMAL", "CAT_SERVERS", ZCTX(_("SLCAT^Servers"))) \
142 SLIST_CATEGORY(CAT_XPM, "CAT_NORMAL", "CAT_SERVERS", ZCTX(_("SLCAT^Competitive Mode"))) \
143 SLIST_CATEGORY(CAT_MODIFIED, "", "CAT_SERVERS", ZCTX(_("SLCAT^Modified Servers"))) \
144 SLIST_CATEGORY(CAT_OVERKILL, "", "CAT_SERVERS", ZCTX(_("SLCAT^Overkill Mode"))) \
145 SLIST_CATEGORY(CAT_MINSTAGIB, "", "CAT_SERVERS", ZCTX(_("SLCAT^MinstaGib Mode"))) \
146 SLIST_CATEGORY(CAT_DEFRAG, "", "CAT_SERVERS", ZCTX(_("SLCAT^Defrag Mode")))
148 #define SLIST_CATEGORY_AUTOCVAR(name) autocvar_menu_slist_categories_##name##_override
149 #define SLIST_CATEGORY(name,enoverride,dioverride,str) \
151 var string SLIST_CATEGORY_AUTOCVAR(name) = enoverride;
153 #undef SLIST_CATEGORY
157 #ifdef IMPLEMENTATION
159 void RegisterSLCategories()
162 #define SLIST_CATEGORY(name,enoverride,dioverride,str) \
163 SET_FIELD_COUNT(name, CATEGORY_FIRST, category_ent_count) \
164 CHECK_MAX_COUNT(name, MAX_CATEGORIES, category_ent_count, "SLIST_CATEGORY") \
166 categories[name - 1] = cat; \
167 cat.classname = "slist_category"; \
168 cat.cat_name = strzone(#name); \
169 cat.cat_enoverride_string = strzone(SLIST_CATEGORY_AUTOCVAR(name)); \
170 cat.cat_dioverride_string = strzone(dioverride); \
171 cat.cat_string = strzone(str);
173 #undef SLIST_CATEGORY
178 #define PROCESS_OVERRIDE(override_string,override_field) \
179 for(i = 0; i < category_ent_count; ++i) \
181 s = categories[i].override_string; \
182 if((s != "") && (s != categories[i].cat_name)) \
184 catnum = Get_Cat_Num_FromString(s); \
187 strunzone(categories[i].override_string); \
188 categories[i].override_field = catnum; \
192 strunzone(categories[i].override_string); \
193 categories[i].override_field = 0; \
195 PROCESS_OVERRIDE(cat_enoverride_string, cat_enoverride)
196 PROCESS_OVERRIDE(cat_dioverride_string, cat_dioverride)
197 #undef PROCESS_OVERRIDE
200 // Supporting Functions
201 float Get_Cat_Num_FromString(string input)
204 for(i = 0; i < category_ent_count; ++i) { if(categories[i].cat_name == input) { return (i + 1); } }
205 print(sprintf("Get_Cat_Num_FromString('%s'): Improper category name!\n", input));
208 entity Get_Cat_Ent(float catnum)
210 if((catnum > 0) && (catnum <= category_ent_count))
212 return categories[catnum - 1];
216 error(sprintf("Get_Cat_Ent(%d): Improper category number!\n", catnum));
221 float IsServerInList(string list, string srv)
227 srv = netaddress_resolve(srv, 26000);
230 p = crypto_getidfp(srv);
231 n = tokenize_console(list);
232 for(i = 0; i < n; ++i)
234 if(substring(argv(i), 0, 1) != "[" && strlen(argv(i)) == 44 && strstrofs(argv(i), ".", 0) < 0)
242 if(srv == netaddress_resolve(argv(i), 26000))
249 float CheckCategoryOverride(float cat)
251 entity catent = Get_Cat_Ent(cat);
254 float override = (autocvar_menu_slist_categories ? catent.cat_enoverride : catent.cat_dioverride);
255 if(override) { return override; }
260 error(sprintf("CheckCategoryOverride(%d): Improper category number!\n", cat));
265 float CheckCategoryForEntry(float entry)
267 string s, k, v, modtype = "";
268 float j, m, impure = 0;
269 s = gethostcachestring(SLIST_FIELD_QCSTATUS, entry);
270 m = tokenizebyseparator(s, ":");
272 for(j = 2; j < m; ++j)
276 k = substring(argv(j), 0, 1);
277 v = substring(argv(j), 1, -1);
278 if(k == "P") { impure = stof(v); }
279 else if(k == "M") { modtype = strtolower(v); }
282 if(impure > autocvar_menu_slist_purethreshold) { impure = TRUE; }
283 else { impure = FALSE; }
285 if(gethostcachenumber(SLIST_FIELD_ISFAVORITE, entry)) { return CAT_FAVORITED; }
286 if(IsRecommended(gethostcachestring(SLIST_FIELD_CNAME, entry))) { return CAT_RECOMMENDED; }
287 else if(modtype != "xonotic")
291 // old servers which don't report their mod name are considered modified now
292 case "": { return CAT_MODIFIED; }
294 case "xpm": { return CAT_XPM; }
295 case "minstagib": { return CAT_MINSTAGIB; }
296 case "overkill": { return CAT_OVERKILL; }
297 //case "nix": { return CAT_NIX; }
298 //case "newtoys": { return CAT_NEWTOYS; }
300 // "cts" is allowed as compat, xdf is replacement
302 case "xdf": { return CAT_DEFRAG; }
304 default: { dprint(sprintf("Found strange mod type: %s\n", modtype)); return CAT_MODIFIED; }
307 else { return (impure ? CAT_MODIFIED : CAT_NORMAL); }
309 // should never hit this point
310 error(sprintf("CheckCategoryForEntry(%d): Function fell through without normal return!\n", entry));
314 float CheckItemNumber(float num)
318 if not(category_draw_count) { return num; } // there are no categories to process
320 for(i = 0, n = 1; n <= category_draw_count; ++i, ++n)
322 if(category_item[i] == (num - i)) { return -category_name[i]; }
323 else if(n == category_draw_count) { return (num - n); }
324 else if((num - i) <= category_item[n]) { return (num - n); }
327 // should never hit this point
328 error(sprintf("CheckItemNumber(%d): Function fell through without normal return!\n", num));
332 void XonoticServerList_toggleFavorite(entity me, string srv)
334 string s, s0, s1, s2, srv_resolved, p;
336 srv_resolved = netaddress_resolve(srv, 26000);
337 p = crypto_getidfp(srv_resolved);
338 s = cvar_string("net_slist_favorites");
339 n = tokenize_console(s);
341 for(i = 0; i < n; ++i)
343 if(substring(argv(i), 0, 1) != "[" && strlen(argv(i)) == 44 && strstrofs(argv(i), ".", 0) < 0)
351 if(srv_resolved != netaddress_resolve(argv(i), 26000))
356 s0 = substring(s, 0, argv_end_index(i - 1));
358 s2 = substring(s, argv_start_index(i + 1), -1);
359 if(s0 != "" && s2 != "")
361 cvar_set("net_slist_favorites", strcat(s0, s1, s2));
362 s = cvar_string("net_slist_favorites");
363 n = tokenize_console(s);
374 cvar_set("net_slist_favorites", strcat(s, s1, p));
376 cvar_set("net_slist_favorites", strcat(s, s1, srv));
379 me.refreshServerList(me, REFRESHSERVERLIST_RESORT);
382 void ServerList_Update_favoriteButton(entity btn, entity me)
384 me.favoriteButton.setText(me.favoriteButton,
385 (IsFavorite(me.ipAddressBox.text) ?
386 _("Remove") : _("Bookmark")
391 entity makeXonoticServerList()
394 me = spawnXonoticServerList();
395 me.configureXonoticServerList(me);
398 void XonoticServerList_configureXonoticServerList(entity me)
400 me.configureXonoticListBox(me);
403 #define SLIST_FIELD(suffix,name) SLIST_FIELD_##suffix = gethostcacheindexforkey(name);
411 RegisterSLCategories();
413 void XonoticServerList_setSelected(entity me, float i)
415 // todo: add logic to skip categories
417 save = me.selectedItem;
418 SUPER(XonoticServerList).setSelected(me, i);
420 if(me.selectedItem == save)
426 //if(gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT) != CheckItemNumber(me.nItems))
427 // { error("^1XonoticServerList_setSelected(); ERROR: ^7Host cache viewcount mismatches nItems!\n"); return; } // sorry, it would be wrong
428 // ^ todo: make this work somehow?
430 #define SET_SELECTED_SERVER(cachenum) \
431 if(me.selectedServer) { strunzone(me.selectedServer); } \
432 me.selectedServer = strzone(gethostcachestring(SLIST_FIELD_CNAME, cachenum)); \
433 me.ipAddressBox.setText(me.ipAddressBox, me.selectedServer); \
434 me.ipAddressBox.cursorPos = strlen(me.selectedServer); \
435 me.ipAddressBoxFocused = -1; \
438 num = CheckItemNumber(me.selectedItem);
440 if(num >= 0) { SET_SELECTED_SERVER(num); }
441 else if(save > me.selectedItem)
443 if(me.selectedItem == 0) { return; }
446 if(me.lastClickedTime >= me.lastBumpSelectTime)
448 SUPER(XonoticServerList).setSelected(me, me.selectedItem - 1);
449 num = CheckItemNumber(me.selectedItem);
452 me.lastBumpSelectTime = time;
453 SET_SELECTED_SERVER(num);
458 else if(save < me.selectedItem)
460 if(me.selectedItem == me.nItems) { return; }
463 if(me.lastClickedTime >= me.lastBumpSelectTime)
465 SUPER(XonoticServerList).setSelected(me, me.selectedItem + 1);
466 num = CheckItemNumber(me.selectedItem);
469 me.lastBumpSelectTime = time;
470 SET_SELECTED_SERVER(num);
477 void XonoticServerList_refreshServerList(entity me, float mode)
479 //print("refresh of type ", ftos(mode), "\n");
481 if(mode >= REFRESHSERVERLIST_REFILTER)
485 string s, typestr, modstr;
489 m = strstrofs(s, ":", 0);
492 typestr = substring(s, 0, m);
493 s = substring(s, m + 1, strlen(s) - m - 1);
494 while(substring(s, 0, 1) == " ")
495 s = substring(s, 1, strlen(s) - 1);
500 modstr = cvar_string("menu_slist_modfilter");
502 m = SLIST_MASK_AND - 1;
503 resethostcachemasks();
505 // ping: reject negative ping (no idea why this happens in the first place, engine bug)
506 sethostcachemasknumber(++m, SLIST_FIELD_PING, 0, SLIST_TEST_GREATEREQUAL);
509 if(!me.filterShowFull)
511 sethostcachemasknumber(++m, SLIST_FIELD_FREESLOTS, 1, SLIST_TEST_GREATEREQUAL); // legacy
512 sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, ":S0:", SLIST_TEST_NOTCONTAIN); // g_maxplayers support
516 if(!me.filterShowEmpty)
517 sethostcachemasknumber(++m, SLIST_FIELD_NUMHUMANS, 1, SLIST_TEST_GREATEREQUAL);
519 // gametype filtering
521 sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, strcat(typestr, ":"), SLIST_TEST_STARTSWITH);
526 if(substring(modstr, 0, 1) == "!")
527 sethostcachemaskstring(++m, SLIST_FIELD_MOD, resolvemod(substring(modstr, 1, strlen(modstr) - 1)), SLIST_TEST_NOTEQUAL);
529 sethostcachemaskstring(++m, SLIST_FIELD_MOD, resolvemod(modstr), SLIST_TEST_EQUAL);
533 n = tokenizebyseparator(_Nex_ExtResponseSystem_BannedServers, " ");
534 for(i = 0; i < n; ++i)
536 sethostcachemaskstring(++m, SLIST_FIELD_CNAME, argv(i), SLIST_TEST_NOTSTARTSWITH);
538 m = SLIST_MASK_OR - 1;
541 sethostcachemaskstring(++m, SLIST_FIELD_NAME, s, SLIST_TEST_CONTAINS);
542 sethostcachemaskstring(++m, SLIST_FIELD_MAP, s, SLIST_TEST_CONTAINS);
543 sethostcachemaskstring(++m, SLIST_FIELD_PLAYERS, s, SLIST_TEST_CONTAINS);
544 sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, strcat(s, ":"), SLIST_TEST_STARTSWITH);
548 //listflags |= SLSF_FAVORITES;
549 listflags |= SLSF_CATEGORIES;
550 if(me.currentSortOrder < 0) { listflags |= SLSF_DESCENDING; }
551 sethostcachesort(me.currentSortField, listflags);
555 if(mode >= REFRESHSERVERLIST_ASK)
556 refreshhostcache(mode >= REFRESHSERVERLIST_RESET);
558 void XonoticServerList_focusEnter(entity me)
560 if(time < me.nextRefreshTime)
562 //print("sorry, no refresh yet\n");
565 me.nextRefreshTime = time + 10;
566 me.refreshServerList(me, REFRESHSERVERLIST_ASK);
569 void XonoticServerList_draw(entity me)
571 float i, found, owned, num;
573 if(_Nex_ExtResponseSystem_BannedServersNeedsRefresh)
577 _Nex_ExtResponseSystem_BannedServersNeedsRefresh = 0;
580 if(me.currentSortField == -1)
582 me.setSortOrder(me, SLIST_FIELD_PING, +1);
583 me.refreshServerList(me, REFRESHSERVERLIST_RESET);
585 else if(me.needsRefresh == 1)
587 me.needsRefresh = 2; // delay by one frame to make sure "slist" has been executed
589 else if(me.needsRefresh == 2)
592 me.refreshServerList(me, REFRESHSERVERLIST_REFILTER);
595 owned = ((me.selectedServer == me.ipAddressBox.text) && (me.ipAddressBox.text != ""));
597 for(i = 0; i < category_draw_count; ++i) { category_name[i] = -1; category_item[i] = -1; }
598 category_draw_count = 0;
600 if(autocvar_menu_slist_categories >= 0) // if less than 0, don't even draw a category heading for favorites
602 float itemcount = gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT);
603 me.nItems = itemcount;
605 //float visible = floor(me.scrollPos / me.itemHeight);
606 // ^ unfortunately no such optimization can be made-- we must process through the
607 // entire list, otherwise there is no way to know which item is first in its category.
610 for(i = 0; i < itemcount; ++i)
612 cat = gethostcachenumber(SLIST_FIELD_CATEGORY, i);
615 if(category_draw_count == 0)
617 category_name[category_draw_count] = cat;
618 category_item[category_draw_count] = i;
619 ++category_draw_count;
625 for(x = 0; x < category_draw_count; ++x) { if(cat == category_name[x]) { found = 1; } }
628 category_name[category_draw_count] = cat;
629 category_item[category_draw_count] = i;
630 ++category_draw_count;
636 if(autocvar_menu_slist_categories_onlyifmultiple && (category_draw_count == 1))
638 category_name[0] = -1;
639 category_item[0] = -1;
640 category_draw_count = 0;
641 me.nItems = itemcount;
644 else { me.nItems = gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT); }
646 me.connectButton.disabled = ((me.nItems == 0) && (me.ipAddressBox.text == ""));
647 me.infoButton.disabled = ((me.nItems == 0) || !owned);
648 me.favoriteButton.disabled = ((me.nItems == 0) && (me.ipAddressBox.text == ""));
651 if(me.selectedServer)
653 for(i = 0; i < me.nItems; ++i)
655 num = CheckItemNumber(i);
658 if(gethostcachestring(SLIST_FIELD_CNAME, num) == me.selectedServer)
660 if(i != me.selectedItem)
662 me.lastClickedServer = -1;
675 if(me.selectedItem >= me.nItems) { me.selectedItem = me.nItems - 1; }
676 if(me.selectedServer) { strunzone(me.selectedServer); }
678 num = CheckItemNumber(me.selectedItem);
679 if(num >= 0) { me.selectedServer = strzone(gethostcachestring(SLIST_FIELD_CNAME, num)); }
685 if(me.selectedServer != me.ipAddressBox.text)
687 me.ipAddressBox.setText(me.ipAddressBox, me.selectedServer);
688 me.ipAddressBox.cursorPos = strlen(me.selectedServer);
689 me.ipAddressBoxFocused = -1;
693 if(me.ipAddressBoxFocused != me.ipAddressBox.focused)
695 if(me.ipAddressBox.focused || me.ipAddressBoxFocused < 0)
696 ServerList_Update_favoriteButton(NULL, me);
697 me.ipAddressBoxFocused = me.ipAddressBox.focused;
700 SUPER(XonoticServerList).draw(me);
702 void ServerList_PingSort_Click(entity btn, entity me)
704 me.setSortOrder(me, SLIST_FIELD_PING, +1);
706 void ServerList_NameSort_Click(entity btn, entity me)
708 me.setSortOrder(me, SLIST_FIELD_NAME, -1); // why?
710 void ServerList_MapSort_Click(entity btn, entity me)
712 me.setSortOrder(me, SLIST_FIELD_MAP, -1); // why?
714 void ServerList_PlayerSort_Click(entity btn, entity me)
716 me.setSortOrder(me, SLIST_FIELD_NUMHUMANS, -1);
718 void ServerList_TypeSort_Click(entity btn, entity me)
723 m = strstrofs(s, ":", 0);
726 s = substring(s, 0, m);
727 while(substring(s, m+1, 1) == " ") // skip spaces
733 for(i = 1; ; i *= 2) // 20 modes ought to be enough for anyone
735 t = MapInfo_Type_ToString(i);
737 if(t == "") // it repeats (default case)
740 // choose the first one
741 s = MapInfo_Type_ToString(1);
746 // the type was found
747 // choose the next one
748 s = MapInfo_Type_ToString(i * 2);
750 s = MapInfo_Type_ToString(1);
757 s = strcat(s, substring(me.filterString, m+1, strlen(me.filterString) - m - 1));
759 me.controlledTextbox.setText(me.controlledTextbox, s);
760 me.controlledTextbox.keyDown(me.controlledTextbox, K_END, 0, 0);
761 me.controlledTextbox.keyUp(me.controlledTextbox, K_END, 0, 0);
762 //ServerList_Filter_Change(me.controlledTextbox, me);
764 void ServerList_Filter_Change(entity box, entity me)
767 strunzone(me.filterString);
769 me.filterString = strzone(box.text);
771 me.filterString = string_null;
772 me.refreshServerList(me, REFRESHSERVERLIST_REFILTER);
774 me.ipAddressBox.setText(me.ipAddressBox, "");
775 me.ipAddressBox.cursorPos = 0;
776 me.ipAddressBoxFocused = -1;
778 void ServerList_Categories_Click(entity box, entity me)
780 box.setChecked(box, autocvar_menu_slist_categories = !autocvar_menu_slist_categories);
781 ///refreshhostcache(TRUE);
783 //cvar_set("net_slist_pause", "0");
784 //Destroy_Category_Entities();
785 //CALL_ACCUMULATED_FUNCTION(RegisterSLCategories);
786 //me.refreshServerList(me, REFRESHSERVERLIST_REFILTER);
788 me.refreshServerList(me, REFRESHSERVERLIST_RESORT);
790 me.ipAddressBox.setText(me.ipAddressBox, "");
791 me.ipAddressBox.cursorPos = 0;
792 me.ipAddressBoxFocused = -1;
794 void ServerList_ShowEmpty_Click(entity box, entity me)
796 box.setChecked(box, me.filterShowEmpty = !me.filterShowEmpty);
797 me.refreshServerList(me, REFRESHSERVERLIST_REFILTER);
799 me.ipAddressBox.setText(me.ipAddressBox, "");
800 me.ipAddressBox.cursorPos = 0;
801 me.ipAddressBoxFocused = -1;
803 void ServerList_ShowFull_Click(entity box, entity me)
805 box.setChecked(box, me.filterShowFull = !me.filterShowFull);
806 me.refreshServerList(me, REFRESHSERVERLIST_REFILTER);
808 me.ipAddressBox.setText(me.ipAddressBox, "");
809 me.ipAddressBox.cursorPos = 0;
810 me.ipAddressBoxFocused = -1;
812 void XonoticServerList_setSortOrder(entity me, float fld, float direction)
814 if(me.currentSortField == fld)
815 direction = -me.currentSortOrder;
816 me.currentSortOrder = direction;
817 me.currentSortField = fld;
818 me.sortButton1.forcePressed = (fld == SLIST_FIELD_PING);
819 me.sortButton2.forcePressed = (fld == SLIST_FIELD_NAME);
820 me.sortButton3.forcePressed = (fld == SLIST_FIELD_MAP);
821 me.sortButton4.forcePressed = 0;
822 me.sortButton5.forcePressed = (fld == SLIST_FIELD_NUMHUMANS);
824 if(me.selectedServer)
825 strunzone(me.selectedServer);
826 me.selectedServer = string_null;
827 me.refreshServerList(me, REFRESHSERVERLIST_REFILTER);
829 void XonoticServerList_positionSortButton(entity me, entity btn, float theOrigin, float theSize, string theTitle, void(entity, entity) theFunc)
831 vector originInLBSpace, sizeInLBSpace;
832 originInLBSpace = eY * (-me.itemHeight);
833 sizeInLBSpace = eY * me.itemHeight + eX * (1 - me.controlWidth);
835 vector originInDialogSpace, sizeInDialogSpace;
836 originInDialogSpace = boxToGlobal(originInLBSpace, me.Container_origin, me.Container_size);
837 sizeInDialogSpace = boxToGlobalSize(sizeInLBSpace, me.Container_size);
839 btn.Container_origin_x = originInDialogSpace_x + sizeInDialogSpace_x * theOrigin;
840 btn.Container_size_x = sizeInDialogSpace_x * theSize;
841 btn.setText(btn, theTitle);
842 btn.onClick = theFunc;
843 btn.onClickEntity = me;
846 void XonoticServerList_resizeNotify(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
848 SUPER(XonoticServerList).resizeNotify(me, relOrigin, relSize, absOrigin, absSize);
850 me.realFontSize_y = me.fontSize / (absSize_y * me.itemHeight);
851 me.realFontSize_x = me.fontSize / (absSize_x * (1 - me.controlWidth));
852 me.realUpperMargin = 0.5 * (1 - me.realFontSize_y);
854 me.columnIconsOrigin = 0;
855 me.columnIconsSize = me.realFontSize_x * 4 * me.iconsSizeFactor;
856 me.columnPingSize = me.realFontSize_x * 3;
857 me.columnMapSize = me.realFontSize_x * 10;
858 me.columnTypeSize = me.realFontSize_x * 4;
859 me.columnPlayersSize = me.realFontSize_x * 5;
860 me.columnNameSize = 1 - me.columnPlayersSize - me.columnMapSize - me.columnPingSize - me.columnIconsSize - me.columnTypeSize - 5 * me.realFontSize_x;
861 me.columnPingOrigin = me.columnIconsOrigin + me.columnIconsSize + me.realFontSize_x;
862 me.columnNameOrigin = me.columnPingOrigin + me.columnPingSize + me.realFontSize_x;
863 me.columnMapOrigin = me.columnNameOrigin + me.columnNameSize + me.realFontSize_x;
864 me.columnTypeOrigin = me.columnMapOrigin + me.columnMapSize + me.realFontSize_x;
865 me.columnPlayersOrigin = me.columnTypeOrigin + me.columnTypeSize + me.realFontSize_x;
867 me.positionSortButton(me, me.sortButton1, me.columnPingOrigin, me.columnPingSize, _("Ping"), ServerList_PingSort_Click);
868 me.positionSortButton(me, me.sortButton2, me.columnNameOrigin, me.columnNameSize, _("Host name"), ServerList_NameSort_Click);
869 me.positionSortButton(me, me.sortButton3, me.columnMapOrigin, me.columnMapSize, _("Map"), ServerList_MapSort_Click);
870 me.positionSortButton(me, me.sortButton4, me.columnTypeOrigin, me.columnTypeSize, _("Type"), ServerList_TypeSort_Click);
871 me.positionSortButton(me, me.sortButton5, me.columnPlayersOrigin, me.columnPlayersSize, _("Players"), ServerList_PlayerSort_Click);
874 f = me.currentSortField;
877 me.currentSortField = -1;
878 me.setSortOrder(me, f, me.currentSortOrder); // force resetting the sort order
881 void ServerList_Connect_Click(entity btn, entity me)
883 localcmd(sprintf("connect %s\n",
884 ((me.ipAddressBox.text != "") ?
885 me.ipAddressBox.text : me.selectedServer
889 void ServerList_Favorite_Click(entity btn, entity me)
892 ipstr = netaddress_resolve(me.ipAddressBox.text, 26000);
895 me.toggleFavorite(me, me.ipAddressBox.text);
896 me.ipAddressBoxFocused = -1;
899 void ServerList_Info_Click(entity btn, entity me)
901 main.serverInfoDialog.loadServerInfo(main.serverInfoDialog, CheckItemNumber(me.selectedItem));
902 DialogOpenButton_Click(me, main.serverInfoDialog);
904 void XonoticServerList_clickListBoxItem(entity me, float i, vector where)
906 float num = CheckItemNumber(i);
909 if(num == me.lastClickedServer)
910 if(time < me.lastClickedTime + 0.3)
913 ServerList_Connect_Click(NULL, me);
915 me.lastClickedServer = num;
916 me.lastClickedTime = time;
919 void XonoticServerList_drawListBoxItem(entity me, float i, vector absSize, float isSelected)
921 // layout: Ping, Server name, Map name, NP, TP, MP
926 float m, pure, freeslots, j, sflags;
927 string s, typestr, versionstr, k, v, modname;
929 float item = CheckItemNumber(i);
930 //print(sprintf("time: %f, i: %d, item: %d, nitems: %d\n", time, i, item, me.nItems));
934 entity catent = Get_Cat_Ent(-item);
938 eY * me.realUpperMargin
940 eX * (me.columnNameOrigin + (me.columnNameSize - draw_TextWidth(catent.cat_string, 0, me.realFontSize)) * 0.5),
952 draw_Fill('0 0 0', '1 1 0', SKINCOLOR_LISTBOX_SELECTED, SKINALPHA_LISTBOX_SELECTED);
954 s = gethostcachestring(SLIST_FIELD_QCSTATUS, item);
955 m = tokenizebyseparator(s, ":");
960 versionstr = argv(1);
966 for(j = 2; j < m; ++j)
970 k = substring(argv(j), 0, 1);
971 v = substring(argv(j), 1, -1);
982 #ifdef COMPAT_NO_MOD_IS_XONOTIC
988 SLIST_FIELD_MOD = gethostcacheindexforkey("mod");
989 s = gethostcachestring(SLIST_FIELD_MOD, item);
991 if(modname == "Xonotic")
995 // list the mods here on which the pure server check actually works
996 if(modname != "Xonotic")
997 if(modname != "MinstaGib")
1000 if(modname != "NewToys")
1003 if(gethostcachenumber(SLIST_FIELD_FREESLOTS, item) <= 0)
1004 theAlpha = SKINALPHA_SERVERLIST_FULL;
1005 else if(freeslots == 0)
1006 theAlpha = SKINALPHA_SERVERLIST_FULL; // g_maxplayers support
1007 else if not(gethostcachenumber(SLIST_FIELD_NUMHUMANS, item))
1008 theAlpha = SKINALPHA_SERVERLIST_EMPTY;
1012 p = gethostcachenumber(SLIST_FIELD_PING, item);
1014 #define PING_MED 200
1015 #define PING_HIGH 500
1017 theColor = SKINCOLOR_SERVERLIST_LOWPING + (SKINCOLOR_SERVERLIST_MEDPING - SKINCOLOR_SERVERLIST_LOWPING) * (p / PING_LOW);
1018 else if(p < PING_MED)
1019 theColor = SKINCOLOR_SERVERLIST_MEDPING + (SKINCOLOR_SERVERLIST_HIGHPING - SKINCOLOR_SERVERLIST_MEDPING) * ((p - PING_LOW) / (PING_MED - PING_LOW));
1020 else if(p < PING_HIGH)
1022 theColor = SKINCOLOR_SERVERLIST_HIGHPING;
1023 theAlpha *= 1 + (SKINALPHA_SERVERLIST_HIGHPING - 1) * ((p - PING_MED) / (PING_HIGH - PING_MED));
1028 theAlpha *= SKINALPHA_SERVERLIST_HIGHPING;
1031 if(gethostcachenumber(SLIST_FIELD_ISFAVORITE, item))
1033 theColor = theColor * (1 - SKINALPHA_SERVERLIST_FAVORITE) + SKINCOLOR_SERVERLIST_FAVORITE * SKINALPHA_SERVERLIST_FAVORITE;
1034 theAlpha = theAlpha * (1 - SKINALPHA_SERVERLIST_FAVORITE) + SKINALPHA_SERVERLIST_FAVORITE;
1037 s = gethostcachestring(SLIST_FIELD_CNAME, item);
1040 if(substring(s, 0, 1) == "[")
1045 else if(strstrofs("0123456789", substring(s, 0, 1), 0) >= 0)
1051 q = stof(substring(crypto_getencryptlevel(s), 0, 1));
1052 if((q <= 0 && cvar("crypto_aeslevel") >= 3) || (q >= 3 && cvar("crypto_aeslevel") <= 0))
1054 theColor = SKINCOLOR_SERVERLIST_IMPOSSIBLE;
1055 theAlpha = SKINALPHA_SERVERLIST_IMPOSSIBLE;
1060 if(cvar("crypto_aeslevel") >= 2)
1065 if(cvar("crypto_aeslevel") >= 1)
1075 // 2: AES recommended but not available
1076 // 3: AES possible and will be used
1077 // 4: AES recommended and will be used
1083 vector iconSize = '0 0 0';
1084 iconSize_y = me.realFontSize_y * me.iconsSizeFactor;
1085 iconSize_x = me.realFontSize_x * me.iconsSizeFactor;
1087 vector iconPos = '0 0 0';
1088 iconPos_x = (me.columnIconsSize - 3 * iconSize_x) * 0.5;
1089 iconPos_y = (1 - iconSize_y) * 0.5;
1093 if not(me.seenIPv4 && me.seenIPv6)
1095 iconPos_x += iconSize_x * 0.5;
1097 else if(me.seenIPv4 && me.seenIPv6)
1101 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_ipv6"), 0); // PRECACHE_PIC_MIPMAP
1103 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_ipv4"), 0); // PRECACHE_PIC_MIPMAP
1105 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1106 iconPos_x += iconSize_x;
1111 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_aeslevel", ftos(q)), 0); // PRECACHE_PIC_MIPMAP
1112 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1114 iconPos_x += iconSize_x;
1116 if(modname == "Xonotic")
1120 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_pure1"), PRECACHE_PIC_MIPMAP);
1121 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1126 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_mod_", modname), PRECACHE_PIC_MIPMAP);
1127 if(draw_PictureSize(n) == '0 0 0')
1128 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_mod_"), PRECACHE_PIC_MIPMAP);
1130 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1132 draw_Picture(iconPos, n, iconSize, '1 1 1', SKINALPHA_SERVERLIST_ICON_NONPURE);
1134 iconPos_x += iconSize_x;
1136 if(sflags >= 0 && (sflags & SERVERFLAG_PLAYERSTATS))
1138 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_stats1"), 0); // PRECACHE_PIC_MIPMAP
1139 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1141 iconPos_x += iconSize_x;
1149 draw_Text(me.realUpperMargin * eY + (me.columnPingOrigin + me.columnPingSize - draw_TextWidth(s, 0, me.realFontSize)) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1152 s = draw_TextShortenToWidth(gethostcachestring(SLIST_FIELD_NAME, item), me.columnNameSize, 0, me.realFontSize);
1153 draw_Text(me.realUpperMargin * eY + me.columnNameOrigin * eX, s, me.realFontSize, theColor, theAlpha, 0);
1156 s = draw_TextShortenToWidth(gethostcachestring(SLIST_FIELD_MAP, item), me.columnMapSize, 0, me.realFontSize);
1157 draw_Text(me.realUpperMargin * eY + (me.columnMapOrigin + (me.columnMapSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1160 s = draw_TextShortenToWidth(typestr, me.columnTypeSize, 0, me.realFontSize);
1161 draw_Text(me.realUpperMargin * eY + (me.columnTypeOrigin + (me.columnTypeSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1163 // server playercount
1164 s = strcat(ftos(gethostcachenumber(SLIST_FIELD_NUMHUMANS, item)), "/", ftos(gethostcachenumber(SLIST_FIELD_MAXPLAYERS, item)));
1165 draw_Text(me.realUpperMargin * eY + (me.columnPlayersOrigin + (me.columnPlayersSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1168 float XonoticServerList_keyDown(entity me, float scan, float ascii, float shift)
1170 float i = CheckItemNumber(me.selectedItem);
1173 org = boxToGlobal(eY * (me.selectedItem * me.itemHeight - me.scrollPos), me.origin, me.size);
1174 sz = boxToGlobalSize(eY * me.itemHeight + eX * (1 - me.controlWidth), me.size);
1176 me.lastBumpSelectTime = 0;
1178 if(scan == K_ENTER || scan == K_KP_ENTER)
1180 ServerList_Connect_Click(NULL, me);
1183 else if(scan == K_MOUSE2 || scan == K_SPACE)
1185 if((me.nItems != 0) && (i >= 0))
1187 main.serverInfoDialog.loadServerInfo(main.serverInfoDialog, i);
1188 DialogOpenButton_Click_withCoords(me, main.serverInfoDialog, org, sz);
1193 else if(scan == K_INS || scan == K_MOUSE3 || scan == K_KP_INS)
1195 if((me.nItems != 0) && (i >= 0))
1197 me.toggleFavorite(me, me.selectedServer);
1198 me.ipAddressBoxFocused = -1;
1203 else if(SUPER(XonoticServerList).keyDown(me, scan, ascii, shift))
1205 else if(!me.controlledTextbox)
1208 return me.controlledTextbox.keyDown(me.controlledTextbox, scan, ascii, shift);