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 float num = XonoticServerList_MapItems(i);
918 if(num == me.lastClickedServer)
919 if(time < me.lastClickedTime + 0.3)
922 ServerList_Connect_Click(NULL, me);
924 me.lastClickedServer = num;
925 me.lastClickedTime = time;
928 void XonoticServerList_drawListBoxItem(entity me, float i, vector absSize, float isSelected)
930 // layout: Ping, Server name, Map name, NP, TP, MP
935 float m, pure, freeslots, j, sflags;
936 string s, typestr, versionstr, k, v, modname;
938 float item = XonoticServerList_MapItems(i);
939 //print(sprintf("time: %f, i: %d, item: %d, nitems: %d\n", time, i, item, me.nItems));
943 entity catent = Get_Cat_Ent(-item);
947 eY * me.realUpperMargin
949 eX * (me.columnNameOrigin + (me.columnNameSize - draw_TextWidth(catent.cat_string, 0, me.realFontSize)) * 0.5),
961 draw_Fill('0 0 0', '1 1 0', SKINCOLOR_LISTBOX_SELECTED, SKINALPHA_LISTBOX_SELECTED);
963 s = gethostcachestring(SLIST_FIELD_QCSTATUS, item);
964 m = tokenizebyseparator(s, ":");
969 versionstr = argv(1);
975 for(j = 2; j < m; ++j)
979 k = substring(argv(j), 0, 1);
980 v = substring(argv(j), 1, -1);
991 #ifdef COMPAT_NO_MOD_IS_XONOTIC
997 SLIST_FIELD_MOD = gethostcacheindexforkey("mod");
998 s = gethostcachestring(SLIST_FIELD_MOD, item);
1000 if(modname == "Xonotic")
1004 // list the mods here on which the pure server check actually works
1005 if(modname != "Xonotic")
1006 if(modname != "MinstaGib")
1007 if(modname != "CTS")
1008 if(modname != "NIX")
1009 if(modname != "NewToys")
1012 if(gethostcachenumber(SLIST_FIELD_FREESLOTS, item) <= 0)
1013 theAlpha = SKINALPHA_SERVERLIST_FULL;
1014 else if(freeslots == 0)
1015 theAlpha = SKINALPHA_SERVERLIST_FULL; // g_maxplayers support
1016 else if not(gethostcachenumber(SLIST_FIELD_NUMHUMANS, item))
1017 theAlpha = SKINALPHA_SERVERLIST_EMPTY;
1021 p = gethostcachenumber(SLIST_FIELD_PING, item);
1023 #define PING_MED 200
1024 #define PING_HIGH 500
1026 theColor = SKINCOLOR_SERVERLIST_LOWPING + (SKINCOLOR_SERVERLIST_MEDPING - SKINCOLOR_SERVERLIST_LOWPING) * (p / PING_LOW);
1027 else if(p < PING_MED)
1028 theColor = SKINCOLOR_SERVERLIST_MEDPING + (SKINCOLOR_SERVERLIST_HIGHPING - SKINCOLOR_SERVERLIST_MEDPING) * ((p - PING_LOW) / (PING_MED - PING_LOW));
1029 else if(p < PING_HIGH)
1031 theColor = SKINCOLOR_SERVERLIST_HIGHPING;
1032 theAlpha *= 1 + (SKINALPHA_SERVERLIST_HIGHPING - 1) * ((p - PING_MED) / (PING_HIGH - PING_MED));
1037 theAlpha *= SKINALPHA_SERVERLIST_HIGHPING;
1040 if(gethostcachenumber(SLIST_FIELD_ISFAVORITE, item))
1042 theColor = theColor * (1 - SKINALPHA_SERVERLIST_FAVORITE) + SKINCOLOR_SERVERLIST_FAVORITE * SKINALPHA_SERVERLIST_FAVORITE;
1043 theAlpha = theAlpha * (1 - SKINALPHA_SERVERLIST_FAVORITE) + SKINALPHA_SERVERLIST_FAVORITE;
1046 s = gethostcachestring(SLIST_FIELD_CNAME, item);
1049 if(substring(s, 0, 1) == "[")
1054 else if(strstrofs("0123456789", substring(s, 0, 1), 0) >= 0)
1060 q = stof(substring(crypto_getencryptlevel(s), 0, 1));
1061 if((q <= 0 && cvar("crypto_aeslevel") >= 3) || (q >= 3 && cvar("crypto_aeslevel") <= 0))
1063 theColor = SKINCOLOR_SERVERLIST_IMPOSSIBLE;
1064 theAlpha = SKINALPHA_SERVERLIST_IMPOSSIBLE;
1069 if(cvar("crypto_aeslevel") >= 2)
1074 if(cvar("crypto_aeslevel") >= 1)
1084 // 2: AES recommended but not available
1085 // 3: AES possible and will be used
1086 // 4: AES recommended and will be used
1092 vector iconSize = '0 0 0';
1093 iconSize_y = me.realFontSize_y * me.iconsSizeFactor;
1094 iconSize_x = me.realFontSize_x * me.iconsSizeFactor;
1096 vector iconPos = '0 0 0';
1097 iconPos_x = (me.columnIconsSize - 3 * iconSize_x) * 0.5;
1098 iconPos_y = (1 - iconSize_y) * 0.5;
1102 if not(me.seenIPv4 && me.seenIPv6)
1104 iconPos_x += iconSize_x * 0.5;
1106 else if(me.seenIPv4 && me.seenIPv6)
1110 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_ipv6"), 0); // PRECACHE_PIC_MIPMAP
1112 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_ipv4"), 0); // PRECACHE_PIC_MIPMAP
1114 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1115 iconPos_x += iconSize_x;
1120 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_aeslevel", ftos(q)), 0); // PRECACHE_PIC_MIPMAP
1121 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1123 iconPos_x += iconSize_x;
1125 if(modname == "Xonotic")
1129 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_pure1"), PRECACHE_PIC_MIPMAP);
1130 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1135 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_mod_", modname), PRECACHE_PIC_MIPMAP);
1136 if(draw_PictureSize(n) == '0 0 0')
1137 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_mod_"), PRECACHE_PIC_MIPMAP);
1139 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1141 draw_Picture(iconPos, n, iconSize, '1 1 1', SKINALPHA_SERVERLIST_ICON_NONPURE);
1143 iconPos_x += iconSize_x;
1145 if(sflags >= 0 && (sflags & SERVERFLAG_PLAYERSTATS))
1147 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_stats1"), 0); // PRECACHE_PIC_MIPMAP
1148 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1150 iconPos_x += iconSize_x;
1158 draw_Text(me.realUpperMargin * eY + (me.columnPingOrigin + me.columnPingSize - draw_TextWidth(s, 0, me.realFontSize)) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1161 s = draw_TextShortenToWidth(gethostcachestring(SLIST_FIELD_NAME, item), me.columnNameSize, 0, me.realFontSize);
1162 draw_Text(me.realUpperMargin * eY + me.columnNameOrigin * eX, s, me.realFontSize, theColor, theAlpha, 0);
1165 s = draw_TextShortenToWidth(gethostcachestring(SLIST_FIELD_MAP, item), me.columnMapSize, 0, me.realFontSize);
1166 draw_Text(me.realUpperMargin * eY + (me.columnMapOrigin + (me.columnMapSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1169 s = draw_TextShortenToWidth(typestr, me.columnTypeSize, 0, me.realFontSize);
1170 draw_Text(me.realUpperMargin * eY + (me.columnTypeOrigin + (me.columnTypeSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1172 // server playercount
1173 s = strcat(ftos(gethostcachenumber(SLIST_FIELD_NUMHUMANS, item)), "/", ftos(gethostcachenumber(SLIST_FIELD_MAXPLAYERS, item)));
1174 draw_Text(me.realUpperMargin * eY + (me.columnPlayersOrigin + (me.columnPlayersSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1177 float XonoticServerList_keyDown(entity me, float scan, float ascii, float shift)
1179 float i = XonoticServerList_MapItems(me.selectedItem);
1182 org = boxToGlobal(eY * (me.selectedItem * me.itemHeight - me.scrollPos), me.origin, me.size);
1183 sz = boxToGlobalSize(eY * me.itemHeight + eX * (1 - me.controlWidth), me.size);
1185 me.lastBumpSelectTime = 0;
1187 if(scan == K_ENTER || scan == K_KP_ENTER)
1189 ServerList_Connect_Click(NULL, me);
1192 else if(scan == K_MOUSE2 || scan == K_SPACE)
1194 if((me.nItems != 0) && (i >= 0))
1196 main.serverInfoDialog.loadServerInfo(main.serverInfoDialog, i);
1197 DialogOpenButton_Click_withCoords(me, main.serverInfoDialog, org, sz);
1202 else if(scan == K_INS || scan == K_MOUSE3 || scan == K_KP_INS)
1204 if((me.nItems != 0) && (i >= 0))
1206 ToggleFavorite(me.selectedServer);
1207 me.ipAddressBoxFocused = -1;
1212 else if(SUPER(XonoticServerList).keyDown(me, scan, ascii, shift))
1214 else if(!me.controlledTextbox)
1217 return me.controlledTextbox.keyDown(me.controlledTextbox, scan, ascii, shift);