2 CLASS(XonoticServerList) EXTENDS(XonoticListBox)
3 METHOD(XonoticServerList, configureXonoticServerList, void(entity))
4 ATTRIB(XonoticServerList, rowsPerItem, float, 1)
5 METHOD(XonoticServerList, draw, void(entity))
6 METHOD(XonoticServerList, drawListBoxItem, void(entity, float, vector, float))
7 METHOD(XonoticServerList, clickListBoxItem, void(entity, float, vector))
8 METHOD(XonoticServerList, resizeNotify, void(entity, vector, vector, vector, vector))
9 METHOD(XonoticServerList, keyDown, float(entity, float, float, float))
11 ATTRIB(XonoticServerList, iconsSizeFactor, float, 0.85)
13 ATTRIB(XonoticServerList, realFontSize, vector, '0 0 0')
14 ATTRIB(XonoticServerList, realUpperMargin, float, 0)
15 ATTRIB(XonoticServerList, columnIconsOrigin, float, 0)
16 ATTRIB(XonoticServerList, columnIconsSize, float, 0)
17 ATTRIB(XonoticServerList, columnPingOrigin, float, 0)
18 ATTRIB(XonoticServerList, columnPingSize, float, 0)
19 ATTRIB(XonoticServerList, columnNameOrigin, float, 0)
20 ATTRIB(XonoticServerList, columnNameSize, float, 0)
21 ATTRIB(XonoticServerList, columnMapOrigin, float, 0)
22 ATTRIB(XonoticServerList, columnMapSize, float, 0)
23 ATTRIB(XonoticServerList, columnTypeOrigin, float, 0)
24 ATTRIB(XonoticServerList, columnTypeSize, float, 0)
25 ATTRIB(XonoticServerList, columnPlayersOrigin, float, 0)
26 ATTRIB(XonoticServerList, columnPlayersSize, float, 0)
28 ATTRIB(XonoticServerList, selectedServer, string, string_null) // to restore selected server when needed
29 METHOD(XonoticServerList, setSelected, void(entity, float))
30 METHOD(XonoticServerList, setSortOrder, void(entity, float, float))
31 ATTRIB(XonoticServerList, filterShowEmpty, float, 1)
32 ATTRIB(XonoticServerList, filterShowFull, float, 1)
33 ATTRIB(XonoticServerList, filterString, string, string_null)
34 ATTRIB(XonoticServerList, controlledTextbox, entity, NULL)
35 ATTRIB(XonoticServerList, ipAddressBox, entity, NULL)
36 ATTRIB(XonoticServerList, favoriteButton, entity, NULL)
37 ATTRIB(XonoticServerList, nextRefreshTime, float, 0)
38 METHOD(XonoticServerList, refreshServerList, void(entity, float)) // refresh mode: 0 = just reparametrize, 1 = send new requests, 2 = clear
39 ATTRIB(XonoticServerList, needsRefresh, float, 1)
40 METHOD(XonoticServerList, focusEnter, void(entity))
41 METHOD(XonoticServerList, positionSortButton, void(entity, entity, float, float, string, void(entity, entity)))
42 ATTRIB(XonoticServerList, sortButton1, entity, NULL)
43 ATTRIB(XonoticServerList, sortButton2, entity, NULL)
44 ATTRIB(XonoticServerList, sortButton3, entity, NULL)
45 ATTRIB(XonoticServerList, sortButton4, entity, NULL)
46 ATTRIB(XonoticServerList, sortButton5, entity, NULL)
47 ATTRIB(XonoticServerList, connectButton, entity, NULL)
48 ATTRIB(XonoticServerList, infoButton, entity, NULL)
49 ATTRIB(XonoticServerList, currentSortOrder, float, 0)
50 ATTRIB(XonoticServerList, currentSortField, float, -1)
51 ATTRIB(XonoticServerList, lastBumpSelectTime, float, 0)
52 ATTRIB(XonoticServerList, lastClickedServer, float, -1)
53 ATTRIB(XonoticServerList, lastClickedTime, float, 0)
55 ATTRIB(XonoticServerList, ipAddressBoxFocused, float, -1)
57 ATTRIB(XonoticServerList, seenIPv4, float, 0)
58 ATTRIB(XonoticServerList, seenIPv6, float, 0)
59 ENDCLASS(XonoticServerList)
60 entity makeXonoticServerList();
62 #ifndef IMPLEMENTATION
63 var float autocvar_menu_slist_categories = TRUE;
64 var float autocvar_menu_slist_categories_onlyifmultiple = TRUE;
65 var float autocvar_menu_slist_purethreshold = 10;
66 var string autocvar_menu_slist_recommended = "76.124.107.5:26004";
68 // server cache fields
69 #define SLIST_FIELDS \
70 SLIST_FIELD(CNAME, "cname") \
71 SLIST_FIELD(PING, "ping") \
72 SLIST_FIELD(GAME, "game") \
73 SLIST_FIELD(MOD, "mod") \
74 SLIST_FIELD(MAP, "map") \
75 SLIST_FIELD(NAME, "name") \
76 SLIST_FIELD(MAXPLAYERS, "maxplayers") \
77 SLIST_FIELD(NUMPLAYERS, "numplayers") \
78 SLIST_FIELD(NUMHUMANS, "numhumans") \
79 SLIST_FIELD(NUMBOTS, "numbots") \
80 SLIST_FIELD(PROTOCOL, "protocol") \
81 SLIST_FIELD(FREESLOTS, "freeslots") \
82 SLIST_FIELD(PLAYERS, "players") \
83 SLIST_FIELD(QCSTATUS, "qcstatus") \
84 SLIST_FIELD(CATEGORY, "category") \
85 SLIST_FIELD(ISFAVORITE, "isfavorite")
87 #define SLIST_FIELD(suffix,name) float SLIST_FIELD_##suffix;
92 float SLSF_DESCENDING = 1;
93 float SLSF_FAVORITES = 2;
94 float SLSF_CATEGORIES = 4;
96 float Get_Cat_Num_FromString(string input);
97 entity Get_Cat_Ent(float catnum);
99 float IsServerInList(string list, string srv);
100 #define IsFavorite(srv) IsServerInList(cvar_string("net_slist_favorites"), srv)
101 #define IsRecommended(srv) IsServerInList(cvar_string("menu_slist_recommended"), srv) // todo: use update notification instead of cvar
103 float CheckCategoryOverride(float cat);
104 float CheckCategoryForEntry(float entry);
105 float m_getserverlistentrycategory(float entry) { return CheckCategoryOverride(CheckCategoryForEntry(entry)); }
107 // fields for category entities
108 #define MAX_CATEGORIES 9
109 #define CATEGORY_FIRST 1
110 entity categories[MAX_CATEGORIES];
111 float category_ent_count;
114 .string cat_enoverride_string;
115 .string cat_dioverride_string;
116 .float cat_enoverride;
117 .float cat_dioverride;
119 // fields for drawing categories
120 float category_name[MAX_CATEGORIES];
121 float category_item[MAX_CATEGORIES];
122 float category_draw_count;
125 SLIST_CATEGORY(CAT_FAVORITED, "", "", _("Favorites")) \
126 SLIST_CATEGORY(CAT_RECOMMENDED, "", "CAT_SERVERS", _("Recommended")) \
127 SLIST_CATEGORY(CAT_NORMAL, "", "CAT_SERVERS", _("Normal Servers")) \
128 SLIST_CATEGORY(CAT_SERVERS, "CAT_NORMAL", "CAT_SERVERS", _("Servers")) \
129 SLIST_CATEGORY(CAT_XPM, "CAT_NORMAL", "CAT_SERVERS", _("Competitive Mode")) \
130 SLIST_CATEGORY(CAT_MODIFIED, "", "CAT_SERVERS", _("Modified Servers")) \
131 SLIST_CATEGORY(CAT_OVERKILL, "", "CAT_SERVERS", _("Overkill Mode")) \
132 SLIST_CATEGORY(CAT_MINSTAGIB, "", "CAT_SERVERS", _("MinstaGib Mode")) \
133 SLIST_CATEGORY(CAT_DEFRAG, "", "CAT_SERVERS", _("Defrag Mode"))
135 // C is stupid, must use extra macro for concatenation
136 #define SLIST_ADD_CAT_CVAR(name,default) var string autocvar_menu_slist_categories_##name##_override = default;
137 #define SLIST_CATEGORY(name,enoverride,dioverride,string) \
138 SLIST_ADD_CAT_CVAR(name, enoverride) \
140 void RegisterSLCategory_##name() \
142 SET_FIELD_COUNT(name, CATEGORY_FIRST, category_ent_count) \
143 CHECK_MAX_COUNT(name, MAX_CATEGORIES, category_ent_count, "SLIST_CATEGORY") \
144 entity cat = spawn(); \
145 categories[name - 1] = cat; \
146 cat.classname = "slist_category"; \
147 cat.cat_name = strzone(#name); \
148 cat.cat_enoverride_string = strzone(autocvar_menu_slist_categories_##name##_override); \
149 cat.cat_dioverride_string = strzone(dioverride); \
150 cat.cat_string = strzone(string); \
152 ACCUMULATE_FUNCTION(RegisterSLCategories, RegisterSLCategory_##name);
156 void RegisterSLCategories_Done()
161 #define PROCESS_OVERRIDE(override_string,override_field) \
162 for(i = 0; i < category_ent_count; ++i) \
164 s = categories[i].override_string; \
165 if((s != "") && (s != categories[i].cat_name)) \
167 catnum = Get_Cat_Num_FromString(s); \
170 strunzone(categories[i].override_string); \
171 categories[i].override_field = catnum; \
175 strunzone(categories[i].override_string); \
176 categories[i].override_field = 0; \
179 PROCESS_OVERRIDE(cat_enoverride_string, cat_enoverride)
180 PROCESS_OVERRIDE(cat_dioverride_string, cat_dioverride)
181 #undef PROCESS_OVERRIDE
183 ACCUMULATE_FUNCTION(RegisterSLCategories, RegisterSLCategories_Done);
185 #undef SLIST_ADD_CAT_CVAR
186 #undef SLIST_CATEGORY
189 void ServerList_Connect_Click(entity btn, entity me);
190 void ServerList_Categories_Click(entity box, entity me);
191 void ServerList_ShowEmpty_Click(entity box, entity me);
192 void ServerList_ShowFull_Click(entity box, entity me);
193 void ServerList_Filter_Change(entity box, entity me);
194 void ServerList_Favorite_Click(entity btn, entity me);
195 void ServerList_Info_Click(entity btn, entity me);
196 void ServerList_Update_favoriteButton(entity btn, entity me);
200 #ifdef IMPLEMENTATION
202 // Supporting Functions
203 float Get_Cat_Num_FromString(string input)
206 for(i = 0; i < category_ent_count; ++i) { if(categories[i].cat_name == input) { return (i + 1); } }
207 print(sprintf("Get_Cat_Num_FromString('%s'): Improper category name!\n", input));
210 entity Get_Cat_Ent(float catnum)
212 if((catnum > 0) && (catnum <= category_ent_count))
214 return categories[catnum - 1];
218 error(sprintf("Get_Cat_Ent(%d): Improper category number!\n", catnum));
224 float IsServerInList(string list, string srv)
230 srv = netaddress_resolve(srv, 26000);
233 p = crypto_getidfp(srv);
234 n = tokenize_console(list);
235 for(i = 0; i < n; ++i)
237 if(substring(argv(i), 0, 1) != "[" && strlen(argv(i)) == 44 && strstrofs(argv(i), ".", 0) < 0)
245 if(srv == netaddress_resolve(argv(i), 26000))
252 float CheckCategoryOverride(float cat)
254 entity catent = Get_Cat_Ent(cat);
257 float override = (autocvar_menu_slist_categories ? catent.cat_enoverride : catent.cat_dioverride);
258 if(override) { return override; }
263 error(sprintf("CheckCategoryOverride(%d): Improper category number!\n", cat));
268 float CheckCategoryForEntry(float entry)
270 string s, k, v, modtype = "";
271 float j, m, impure = 0;
272 s = gethostcachestring(SLIST_FIELD_QCSTATUS, entry);
273 m = tokenizebyseparator(s, ":");
275 for(j = 2; j < m; ++j)
279 k = substring(argv(j), 0, 1);
280 v = substring(argv(j), 1, -1);
281 if(k == "P") { impure = stof(v); }
282 else if(k == "M") { modtype = strtolower(v); }
285 //print(sprintf("modtype = %s\n", modtype));
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; }
303 // "cts" is allowed as compat, xdf is replacement
305 case "xdf": { return CAT_DEFRAG; }
307 //if(modname != "CTS")
308 //if(modname != "NIX")
309 //if(modname != "NewToys")
311 default: { dprint(sprintf("Found strange mod type: %s\n", modtype)); return CAT_MODIFIED; }
314 else { return (impure ? CAT_MODIFIED : CAT_NORMAL); }
316 // should never hit this point
317 error(sprintf("CheckCategoryForEntry(%d): Function fell through without normal return!\n", entry));
321 float XonoticServerList_MapItems(float num)
325 if not(category_draw_count) { return num; } // there are no categories to process
327 for(i = 0, n = 1; n <= category_draw_count; ++i, ++n)
329 //print(sprintf("num: %d, i: %d, category_draw_count: %d, category_item[i]: %d\n", num, i, category_draw_count, category_item[i]));
330 if(category_item[i] == (num - i)) { /*print("inserting cat... \\/\n");*/ return -category_name[i]; }
331 else if(n == category_draw_count) { /*print("end item... \\/\n");*/ return (num - n); }
332 else if((num - i) <= category_item[n]) { /*print("next item... \\/\n");*/ return (num - n); }
335 // should never hit this point
336 error("wtf XonoticServerList_MapItems fail?");
340 void ToggleFavorite(string srv)
342 string s, s0, s1, s2, srv_resolved, p;
344 srv_resolved = netaddress_resolve(srv, 26000);
345 p = crypto_getidfp(srv_resolved);
346 s = cvar_string("net_slist_favorites");
347 n = tokenize_console(s);
349 for(i = 0; i < n; ++i)
351 if(substring(argv(i), 0, 1) != "[" && strlen(argv(i)) == 44 && strstrofs(argv(i), ".", 0) < 0)
359 if(srv_resolved != netaddress_resolve(argv(i), 26000))
364 s0 = substring(s, 0, argv_end_index(i - 1));
366 s2 = substring(s, argv_start_index(i + 1), -1);
367 if(s0 != "" && s2 != "")
369 cvar_set("net_slist_favorites", strcat(s0, s1, s2));
370 s = cvar_string("net_slist_favorites");
371 n = tokenize_console(s);
382 cvar_set("net_slist_favorites", strcat(s, s1, p));
384 cvar_set("net_slist_favorites", strcat(s, s1, srv));
390 void ServerList_Update_favoriteButton(entity btn, entity me)
392 me.favoriteButton.setText(me.favoriteButton,
393 (IsFavorite(me.ipAddressBox.text) ?
394 _("Remove") : _("Bookmark")
399 entity makeXonoticServerList()
402 me = spawnXonoticServerList();
403 me.configureXonoticServerList(me);
406 void XonoticServerList_configureXonoticServerList(entity me)
408 me.configureXonoticListBox(me);
411 #define SLIST_FIELD(suffix,name) SLIST_FIELD_##suffix = gethostcacheindexforkey(name);
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) != XonoticServerList_MapItems(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 = XonoticServerList_MapItems(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 = XonoticServerList_MapItems(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 = XonoticServerList_MapItems(me.selectedItem);
474 me.lastBumpSelectTime = time;
475 SET_SELECTED_SERVER(num);
481 void XonoticServerList_refreshServerList(entity me, float mode)
483 // 0: just reparametrize
484 // 1: also ask for new servers
486 //print("refresh of type ", ftos(mode), "\n");
487 /* if(mode == 2) // borken
490 localcmd("net_slist\n");
491 me.needsRefresh = 1; // net_slist kills sort order, so we need to restore it later
497 string s, typestr, modstr;
500 m = strstrofs(s, ":", 0);
503 typestr = substring(s, 0, m);
504 s = substring(s, m + 1, strlen(s) - m - 1);
505 while(substring(s, 0, 1) == " ")
506 s = substring(s, 1, strlen(s) - 1);
511 modstr = cvar_string("menu_slist_modfilter");
513 m = SLIST_MASK_AND - 1;
514 resethostcachemasks();
516 // ping: reject negative ping (no idea why this happens in the first place, engine bug)
517 sethostcachemasknumber(++m, SLIST_FIELD_PING, 0, SLIST_TEST_GREATEREQUAL);
520 if(!me.filterShowFull)
522 sethostcachemasknumber(++m, SLIST_FIELD_FREESLOTS, 1, SLIST_TEST_GREATEREQUAL); // legacy
523 sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, ":S0:", SLIST_TEST_NOTCONTAIN); // g_maxplayers support
527 if(!me.filterShowEmpty)
528 sethostcachemasknumber(++m, SLIST_FIELD_NUMHUMANS, 1, SLIST_TEST_GREATEREQUAL);
530 // gametype filtering
532 sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, strcat(typestr, ":"), SLIST_TEST_STARTSWITH);
537 if(substring(modstr, 0, 1) == "!")
538 sethostcachemaskstring(++m, SLIST_FIELD_MOD, resolvemod(substring(modstr, 1, strlen(modstr) - 1)), SLIST_TEST_NOTEQUAL);
540 sethostcachemaskstring(++m, SLIST_FIELD_MOD, resolvemod(modstr), SLIST_TEST_EQUAL);
544 n = tokenizebyseparator(_Nex_ExtResponseSystem_BannedServers, " ");
545 for(i = 0; i < n; ++i)
547 sethostcachemaskstring(++m, SLIST_FIELD_CNAME, argv(i), SLIST_TEST_NOTSTARTSWITH);
549 m = SLIST_MASK_OR - 1;
552 sethostcachemaskstring(++m, SLIST_FIELD_NAME, s, SLIST_TEST_CONTAINS);
553 sethostcachemaskstring(++m, SLIST_FIELD_MAP, s, SLIST_TEST_CONTAINS);
554 sethostcachemaskstring(++m, SLIST_FIELD_PLAYERS, s, SLIST_TEST_CONTAINS);
555 sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, strcat(s, ":"), SLIST_TEST_STARTSWITH);
559 //listflags |= SLSF_FAVORITES;
560 listflags |= SLSF_CATEGORIES;
561 if(me.currentSortOrder < 0) { listflags |= SLSF_DESCENDING; }
562 sethostcachesort(me.currentSortField, listflags);
565 if(mode >= 1) { refreshhostcache(FALSE); }
567 void XonoticServerList_focusEnter(entity me)
569 if(time < me.nextRefreshTime)
571 //print("sorry, no refresh yet\n");
574 me.nextRefreshTime = time + 10;
575 me.refreshServerList(me, 1);
578 void XonoticServerList_draw(entity me)
580 float i, found, owned, num;
582 if(_Nex_ExtResponseSystem_BannedServersNeedsRefresh)
586 _Nex_ExtResponseSystem_BannedServersNeedsRefresh = 0;
589 if(me.currentSortField == -1)
591 me.setSortOrder(me, SLIST_FIELD_PING, +1);
592 me.refreshServerList(me, 2);
594 else if(me.needsRefresh == 1)
596 me.needsRefresh = 2; // delay by one frame to make sure "slist" has been executed
598 else if(me.needsRefresh == 2)
601 me.refreshServerList(me, 0);
604 owned = ((me.selectedServer == me.ipAddressBox.text) && (me.ipAddressBox.text != ""));
606 for(i = 0; i < category_draw_count; ++i) { category_name[i] = -1; category_item[i] = -1; }
607 category_draw_count = 0;
609 if(autocvar_menu_slist_categories >= 0) // if less than 0, don't even draw a category heading for favorites
611 float itemcount = gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT);
612 me.nItems = itemcount;
614 //float visible = floor(me.scrollPos / me.itemHeight);
615 // ^ unfortunately no such optimization can be made-- we must process through the
616 // entire list, otherwise there is no way to know which item is first in its category.
619 for(i = 0; i < itemcount; ++i)
621 cat = gethostcachenumber(SLIST_FIELD_CATEGORY, i);
624 if(category_draw_count == 0)
626 category_name[category_draw_count] = cat;
627 category_item[category_draw_count] = i;
628 ++category_draw_count;
634 for(x = 0; x < category_draw_count; ++x) { if(cat == category_name[x]) { found = 1; } }
637 category_name[category_draw_count] = cat;
638 category_item[category_draw_count] = i;
639 ++category_draw_count;
645 if(autocvar_menu_slist_categories_onlyifmultiple && (category_draw_count == 1))
647 category_name[0] = category_name[1] = -1;
648 category_item[0] = category_item[1] = -1;
649 category_draw_count = 0;
650 me.nItems = itemcount;
653 else { me.nItems = gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT); }
655 me.connectButton.disabled = ((me.nItems == 0) && (me.ipAddressBox.text == ""));
656 me.infoButton.disabled = ((me.nItems == 0) || !owned);
657 me.favoriteButton.disabled = ((me.nItems == 0) && (me.ipAddressBox.text == ""));
660 if(me.selectedServer)
662 for(i = 0; i < me.nItems; ++i)
664 num = XonoticServerList_MapItems(i);
667 if(gethostcachestring(SLIST_FIELD_CNAME, num) == me.selectedServer)
669 if(i != me.selectedItem)
671 me.lastClickedServer = -1;
684 if(me.selectedItem >= me.nItems) { me.selectedItem = me.nItems - 1; }
685 if(me.selectedServer) { strunzone(me.selectedServer); }
687 num = XonoticServerList_MapItems(me.selectedItem);
688 if(num >= 0) { me.selectedServer = strzone(gethostcachestring(SLIST_FIELD_CNAME, num)); }
694 if(me.selectedServer != me.ipAddressBox.text)
696 me.ipAddressBox.setText(me.ipAddressBox, me.selectedServer);
697 me.ipAddressBox.cursorPos = strlen(me.selectedServer);
698 me.ipAddressBoxFocused = -1;
702 if(me.ipAddressBoxFocused != me.ipAddressBox.focused)
704 if(me.ipAddressBox.focused || me.ipAddressBoxFocused < 0)
705 ServerList_Update_favoriteButton(NULL, me);
706 me.ipAddressBoxFocused = me.ipAddressBox.focused;
709 SUPER(XonoticServerList).draw(me);
711 void ServerList_PingSort_Click(entity btn, entity me)
713 me.setSortOrder(me, SLIST_FIELD_PING, +1);
715 void ServerList_NameSort_Click(entity btn, entity me)
717 me.setSortOrder(me, SLIST_FIELD_NAME, -1); // why?
719 void ServerList_MapSort_Click(entity btn, entity me)
721 me.setSortOrder(me, SLIST_FIELD_MAP, -1); // why?
723 void ServerList_PlayerSort_Click(entity btn, entity me)
725 me.setSortOrder(me, SLIST_FIELD_NUMHUMANS, -1);
727 void ServerList_TypeSort_Click(entity btn, entity me)
732 m = strstrofs(s, ":", 0);
735 s = substring(s, 0, m);
736 while(substring(s, m+1, 1) == " ") // skip spaces
742 for(i = 1; ; i *= 2) // 20 modes ought to be enough for anyone
744 t = MapInfo_Type_ToString(i);
746 if(t == "") // it repeats (default case)
749 // choose the first one
750 s = MapInfo_Type_ToString(1);
755 // the type was found
756 // choose the next one
757 s = MapInfo_Type_ToString(i * 2);
759 s = MapInfo_Type_ToString(1);
766 s = strcat(s, substring(me.filterString, m+1, strlen(me.filterString) - m - 1));
768 me.controlledTextbox.setText(me.controlledTextbox, s);
769 me.controlledTextbox.keyDown(me.controlledTextbox, K_END, 0, 0);
770 me.controlledTextbox.keyUp(me.controlledTextbox, K_END, 0, 0);
771 //ServerList_Filter_Change(me.controlledTextbox, me);
773 void ServerList_Filter_Change(entity box, entity me)
776 strunzone(me.filterString);
778 me.filterString = strzone(box.text);
780 me.filterString = string_null;
781 me.refreshServerList(me, 0);
783 me.ipAddressBox.setText(me.ipAddressBox, "");
784 me.ipAddressBox.cursorPos = 0;
785 me.ipAddressBoxFocused = -1;
787 void ServerList_Categories_Click(entity box, entity me)
789 box.setChecked(box, autocvar_menu_slist_categories = !autocvar_menu_slist_categories);
790 ///refreshhostcache(TRUE);
792 //cvar_set("net_slist_pause", "0");
793 //Destroy_Category_Entities();
794 //CALL_ACCUMULATED_FUNCTION(RegisterSLCategories);
795 //me.refreshServerList(me, 0);
799 me.ipAddressBox.setText(me.ipAddressBox, "");
800 me.ipAddressBox.cursorPos = 0;
801 me.ipAddressBoxFocused = -1;
803 void ServerList_ShowEmpty_Click(entity box, entity me)
805 box.setChecked(box, me.filterShowEmpty = !me.filterShowEmpty);
806 me.refreshServerList(me, 0);
808 me.ipAddressBox.setText(me.ipAddressBox, "");
809 me.ipAddressBox.cursorPos = 0;
810 me.ipAddressBoxFocused = -1;
812 void ServerList_ShowFull_Click(entity box, entity me)
814 box.setChecked(box, me.filterShowFull = !me.filterShowFull);
815 me.refreshServerList(me, 0);
817 me.ipAddressBox.setText(me.ipAddressBox, "");
818 me.ipAddressBox.cursorPos = 0;
819 me.ipAddressBoxFocused = -1;
821 void XonoticServerList_setSortOrder(entity me, float fld, float direction)
823 if(me.currentSortField == fld)
824 direction = -me.currentSortOrder;
825 me.currentSortOrder = direction;
826 me.currentSortField = fld;
827 me.sortButton1.forcePressed = (fld == SLIST_FIELD_PING);
828 me.sortButton2.forcePressed = (fld == SLIST_FIELD_NAME);
829 me.sortButton3.forcePressed = (fld == SLIST_FIELD_MAP);
830 me.sortButton4.forcePressed = 0;
831 me.sortButton5.forcePressed = (fld == SLIST_FIELD_NUMHUMANS);
833 if(me.selectedServer)
834 strunzone(me.selectedServer);
835 me.selectedServer = string_null;
836 me.refreshServerList(me, 0);
838 void XonoticServerList_positionSortButton(entity me, entity btn, float theOrigin, float theSize, string theTitle, void(entity, entity) theFunc)
840 vector originInLBSpace, sizeInLBSpace;
841 originInLBSpace = eY * (-me.itemHeight);
842 sizeInLBSpace = eY * me.itemHeight + eX * (1 - me.controlWidth);
844 vector originInDialogSpace, sizeInDialogSpace;
845 originInDialogSpace = boxToGlobal(originInLBSpace, me.Container_origin, me.Container_size);
846 sizeInDialogSpace = boxToGlobalSize(sizeInLBSpace, me.Container_size);
848 btn.Container_origin_x = originInDialogSpace_x + sizeInDialogSpace_x * theOrigin;
849 btn.Container_size_x = sizeInDialogSpace_x * theSize;
850 btn.setText(btn, theTitle);
851 btn.onClick = theFunc;
852 btn.onClickEntity = me;
855 void XonoticServerList_resizeNotify(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
857 SUPER(XonoticServerList).resizeNotify(me, relOrigin, relSize, absOrigin, absSize);
859 me.realFontSize_y = me.fontSize / (absSize_y * me.itemHeight);
860 me.realFontSize_x = me.fontSize / (absSize_x * (1 - me.controlWidth));
861 me.realUpperMargin = 0.5 * (1 - me.realFontSize_y);
863 me.columnIconsOrigin = 0;
864 me.columnIconsSize = me.realFontSize_x * 4 * me.iconsSizeFactor;
865 me.columnPingSize = me.realFontSize_x * 3;
866 me.columnMapSize = me.realFontSize_x * 10;
867 me.columnTypeSize = me.realFontSize_x * 4;
868 me.columnPlayersSize = me.realFontSize_x * 5;
869 me.columnNameSize = 1 - me.columnPlayersSize - me.columnMapSize - me.columnPingSize - me.columnIconsSize - me.columnTypeSize - 5 * me.realFontSize_x;
870 me.columnPingOrigin = me.columnIconsOrigin + me.columnIconsSize + me.realFontSize_x;
871 me.columnNameOrigin = me.columnPingOrigin + me.columnPingSize + me.realFontSize_x;
872 me.columnMapOrigin = me.columnNameOrigin + me.columnNameSize + me.realFontSize_x;
873 me.columnTypeOrigin = me.columnMapOrigin + me.columnMapSize + me.realFontSize_x;
874 me.columnPlayersOrigin = me.columnTypeOrigin + me.columnTypeSize + me.realFontSize_x;
876 me.positionSortButton(me, me.sortButton1, me.columnPingOrigin, me.columnPingSize, _("Ping"), ServerList_PingSort_Click);
877 me.positionSortButton(me, me.sortButton2, me.columnNameOrigin, me.columnNameSize, _("Host name"), ServerList_NameSort_Click);
878 me.positionSortButton(me, me.sortButton3, me.columnMapOrigin, me.columnMapSize, _("Map"), ServerList_MapSort_Click);
879 me.positionSortButton(me, me.sortButton4, me.columnTypeOrigin, me.columnTypeSize, _("Type"), ServerList_TypeSort_Click);
880 me.positionSortButton(me, me.sortButton5, me.columnPlayersOrigin, me.columnPlayersSize, _("Players"), ServerList_PlayerSort_Click);
883 f = me.currentSortField;
886 me.currentSortField = -1;
887 me.setSortOrder(me, f, me.currentSortOrder); // force resetting the sort order
890 void ServerList_Connect_Click(entity btn, entity me)
892 localcmd(sprintf("connect %s\n",
893 ((me.ipAddressBox.text != "") ?
894 me.ipAddressBox.text : me.selectedServer
898 void ServerList_Favorite_Click(entity btn, entity me)
901 ipstr = netaddress_resolve(me.ipAddressBox.text, 26000);
904 ToggleFavorite(me.ipAddressBox.text);
905 me.ipAddressBoxFocused = -1;
908 void ServerList_Info_Click(entity btn, entity me)
910 main.serverInfoDialog.loadServerInfo(main.serverInfoDialog, XonoticServerList_MapItems(me.selectedItem));
911 DialogOpenButton_Click(me, main.serverInfoDialog);
913 void XonoticServerList_clickListBoxItem(entity me, float i, vector where)
915 me.lastBumpSelectTime = 0; // must reset this for new clicks
917 float num = XonoticServerList_MapItems(i);
920 if(num == me.lastClickedServer)
921 if(time < me.lastClickedTime + 0.3)
924 ServerList_Connect_Click(NULL, me);
926 me.lastClickedServer = num;
927 me.lastClickedTime = time;
930 void XonoticServerList_drawListBoxItem(entity me, float i, vector absSize, float isSelected)
932 // layout: Ping, Server name, Map name, NP, TP, MP
937 float m, pure, freeslots, j, sflags;
938 string s, typestr, versionstr, k, v, modname;
940 float item = XonoticServerList_MapItems(i);
941 //print(sprintf("time: %f, i: %d, item: %d, nitems: %d\n", time, i, item, me.nItems));
945 entity catent = Get_Cat_Ent(-item);
949 eY * me.realUpperMargin
951 eX * (me.columnNameOrigin + (me.columnNameSize - draw_TextWidth(catent.cat_string, 0, me.realFontSize)) * 0.5),
963 draw_Fill('0 0 0', '1 1 0', SKINCOLOR_LISTBOX_SELECTED, SKINALPHA_LISTBOX_SELECTED);
965 s = gethostcachestring(SLIST_FIELD_QCSTATUS, item);
966 m = tokenizebyseparator(s, ":");
971 versionstr = argv(1);
977 for(j = 2; j < m; ++j)
981 k = substring(argv(j), 0, 1);
982 v = substring(argv(j), 1, -1);
993 #ifdef COMPAT_NO_MOD_IS_XONOTIC
999 SLIST_FIELD_MOD = gethostcacheindexforkey("mod");
1000 s = gethostcachestring(SLIST_FIELD_MOD, item);
1002 if(modname == "Xonotic")
1006 // list the mods here on which the pure server check actually works
1007 if(modname != "Xonotic")
1008 if(modname != "MinstaGib")
1009 if(modname != "CTS")
1010 if(modname != "NIX")
1011 if(modname != "NewToys")
1014 if(gethostcachenumber(SLIST_FIELD_FREESLOTS, item) <= 0)
1015 theAlpha = SKINALPHA_SERVERLIST_FULL;
1016 else if(freeslots == 0)
1017 theAlpha = SKINALPHA_SERVERLIST_FULL; // g_maxplayers support
1018 else if not(gethostcachenumber(SLIST_FIELD_NUMHUMANS, item))
1019 theAlpha = SKINALPHA_SERVERLIST_EMPTY;
1023 p = gethostcachenumber(SLIST_FIELD_PING, item);
1025 #define PING_MED 200
1026 #define PING_HIGH 500
1028 theColor = SKINCOLOR_SERVERLIST_LOWPING + (SKINCOLOR_SERVERLIST_MEDPING - SKINCOLOR_SERVERLIST_LOWPING) * (p / PING_LOW);
1029 else if(p < PING_MED)
1030 theColor = SKINCOLOR_SERVERLIST_MEDPING + (SKINCOLOR_SERVERLIST_HIGHPING - SKINCOLOR_SERVERLIST_MEDPING) * ((p - PING_LOW) / (PING_MED - PING_LOW));
1031 else if(p < PING_HIGH)
1033 theColor = SKINCOLOR_SERVERLIST_HIGHPING;
1034 theAlpha *= 1 + (SKINALPHA_SERVERLIST_HIGHPING - 1) * ((p - PING_MED) / (PING_HIGH - PING_MED));
1039 theAlpha *= SKINALPHA_SERVERLIST_HIGHPING;
1042 if(gethostcachenumber(SLIST_FIELD_ISFAVORITE, item))
1044 theColor = theColor * (1 - SKINALPHA_SERVERLIST_FAVORITE) + SKINCOLOR_SERVERLIST_FAVORITE * SKINALPHA_SERVERLIST_FAVORITE;
1045 theAlpha = theAlpha * (1 - SKINALPHA_SERVERLIST_FAVORITE) + SKINALPHA_SERVERLIST_FAVORITE;
1048 s = gethostcachestring(SLIST_FIELD_CNAME, item);
1051 if(substring(s, 0, 1) == "[")
1056 else if(strstrofs("0123456789", substring(s, 0, 1), 0) >= 0)
1062 q = stof(substring(crypto_getencryptlevel(s), 0, 1));
1063 if((q <= 0 && cvar("crypto_aeslevel") >= 3) || (q >= 3 && cvar("crypto_aeslevel") <= 0))
1065 theColor = SKINCOLOR_SERVERLIST_IMPOSSIBLE;
1066 theAlpha = SKINALPHA_SERVERLIST_IMPOSSIBLE;
1071 if(cvar("crypto_aeslevel") >= 2)
1076 if(cvar("crypto_aeslevel") >= 1)
1086 // 2: AES recommended but not available
1087 // 3: AES possible and will be used
1088 // 4: AES recommended and will be used
1094 vector iconSize = '0 0 0';
1095 iconSize_y = me.realFontSize_y * me.iconsSizeFactor;
1096 iconSize_x = me.realFontSize_x * me.iconsSizeFactor;
1098 vector iconPos = '0 0 0';
1099 iconPos_x = (me.columnIconsSize - 3 * iconSize_x) * 0.5;
1100 iconPos_y = (1 - iconSize_y) * 0.5;
1104 if not(me.seenIPv4 && me.seenIPv6)
1106 iconPos_x += iconSize_x * 0.5;
1108 else if(me.seenIPv4 && me.seenIPv6)
1112 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_ipv6"), 0); // PRECACHE_PIC_MIPMAP
1114 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_ipv4"), 0); // PRECACHE_PIC_MIPMAP
1116 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1117 iconPos_x += iconSize_x;
1122 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_aeslevel", ftos(q)), 0); // PRECACHE_PIC_MIPMAP
1123 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1125 iconPos_x += iconSize_x;
1127 if(modname == "Xonotic")
1131 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_pure1"), PRECACHE_PIC_MIPMAP);
1132 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1137 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_mod_", modname), PRECACHE_PIC_MIPMAP);
1138 if(draw_PictureSize(n) == '0 0 0')
1139 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_mod_"), PRECACHE_PIC_MIPMAP);
1141 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1143 draw_Picture(iconPos, n, iconSize, '1 1 1', SKINALPHA_SERVERLIST_ICON_NONPURE);
1145 iconPos_x += iconSize_x;
1147 if(sflags >= 0 && (sflags & SERVERFLAG_PLAYERSTATS))
1149 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_stats1"), 0); // PRECACHE_PIC_MIPMAP
1150 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1152 iconPos_x += iconSize_x;
1160 draw_Text(me.realUpperMargin * eY + (me.columnPingOrigin + me.columnPingSize - draw_TextWidth(s, 0, me.realFontSize)) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1163 s = draw_TextShortenToWidth(gethostcachestring(SLIST_FIELD_NAME, item), me.columnNameSize, 0, me.realFontSize);
1164 draw_Text(me.realUpperMargin * eY + me.columnNameOrigin * eX, s, me.realFontSize, theColor, theAlpha, 0);
1167 s = draw_TextShortenToWidth(gethostcachestring(SLIST_FIELD_MAP, item), me.columnMapSize, 0, me.realFontSize);
1168 draw_Text(me.realUpperMargin * eY + (me.columnMapOrigin + (me.columnMapSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1171 s = draw_TextShortenToWidth(typestr, me.columnTypeSize, 0, me.realFontSize);
1172 draw_Text(me.realUpperMargin * eY + (me.columnTypeOrigin + (me.columnTypeSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1174 // server playercount
1175 s = strcat(ftos(gethostcachenumber(SLIST_FIELD_NUMHUMANS, item)), "/", ftos(gethostcachenumber(SLIST_FIELD_MAXPLAYERS, item)));
1176 draw_Text(me.realUpperMargin * eY + (me.columnPlayersOrigin + (me.columnPlayersSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1179 float XonoticServerList_keyDown(entity me, float scan, float ascii, float shift)
1181 float i = XonoticServerList_MapItems(me.selectedItem);
1184 org = boxToGlobal(eY * (me.selectedItem * me.itemHeight - me.scrollPos), me.origin, me.size);
1185 sz = boxToGlobalSize(eY * me.itemHeight + eX * (1 - me.controlWidth), me.size);
1187 if(scan != K_MOUSE1 && scan != K_MOUSE2) { me.lastBumpSelectTime = 0; }
1189 if(scan == K_ENTER || scan == K_KP_ENTER)
1191 ServerList_Connect_Click(NULL, me);
1194 else if(scan == K_MOUSE2 || scan == K_SPACE)
1196 if((me.nItems != 0) && (i >= 0))
1198 main.serverInfoDialog.loadServerInfo(main.serverInfoDialog, i);
1199 DialogOpenButton_Click_withCoords(me, main.serverInfoDialog, org, sz);
1204 else if(scan == K_INS || scan == K_MOUSE3 || scan == K_KP_INS)
1206 if((me.nItems != 0) && (i >= 0))
1208 ToggleFavorite(me.selectedServer);
1209 me.ipAddressBoxFocused = -1;
1214 else if(SUPER(XonoticServerList).keyDown(me, scan, ascii, shift))
1216 else if(!me.controlledTextbox)
1219 return me.controlledTextbox.keyDown(me.controlledTextbox, scan, ascii, shift);