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, lastClickedServer, float, -1)
52 ATTRIB(XonoticServerList, lastClickedTime, float, 0)
54 ATTRIB(XonoticServerList, ipAddressBoxFocused, float, -1)
56 ATTRIB(XonoticServerList, seenIPv4, float, 0)
57 ATTRIB(XonoticServerList, seenIPv6, float, 0)
58 ENDCLASS(XonoticServerList)
59 entity makeXonoticServerList();
61 #ifndef IMPLEMENTATION
62 var float autocvar_menu_serverlist_categories = TRUE;
63 var float autocvar_menu_serverlist_categories_onlyifmultiple = TRUE;
64 var float autocvar_menu_serverlist_purethreshold = 10;
65 var string autocvar_menu_serverlist_recommended = "76.124.107.5:26004";
67 // server cache fields
68 #define SLIST_FIELDS \
69 SLIST_FIELD(CNAME, "cname") \
70 SLIST_FIELD(PING, "ping") \
71 SLIST_FIELD(GAME, "game") \
72 SLIST_FIELD(MOD, "mod") \
73 SLIST_FIELD(MAP, "map") \
74 SLIST_FIELD(NAME, "name") \
75 SLIST_FIELD(MAXPLAYERS, "maxplayers") \
76 SLIST_FIELD(NUMPLAYERS, "numplayers") \
77 SLIST_FIELD(NUMHUMANS, "numhumans") \
78 SLIST_FIELD(NUMBOTS, "numbots") \
79 SLIST_FIELD(PROTOCOL, "protocol") \
80 SLIST_FIELD(FREESLOTS, "freeslots") \
81 SLIST_FIELD(PLAYERS, "players") \
82 SLIST_FIELD(QCSTATUS, "qcstatus") \
83 SLIST_FIELD(CATEGORY, "category") \
84 SLIST_FIELD(ISFAVORITE, "isfavorite")
86 #define SLIST_FIELD(suffix,name) float SLIST_FIELD_##suffix;
91 float SLSF_DESCENDING = 1;
92 float SLSF_FAVORITES = 2;
93 float SLSF_CATEGORIES = 4;
95 float Get_Cat_Num_FromString(string input);
96 entity Get_Cat_Ent(float catnum);
98 float IsServerInList(string list, string srv);
99 #define IsFavorite(srv) IsServerInList(cvar_string("net_slist_favorites"), srv)
100 #define IsRecommended(srv) IsServerInList(cvar_string("menu_serverlist_recommended"), srv) // todo: use update notification instead of cvar
102 float CheckCategoryOverride(float cat);
103 float CheckCategoryForEntry(float entry);
104 float m_getserverlistentrycategory(float entry) { return CheckCategoryOverride(CheckCategoryForEntry(entry)); }
106 // fields for category entities
107 #define MAX_CATEGORIES 9
108 #define CATEGORY_FIRST 1
109 entity categories[MAX_CATEGORIES];
110 float category_ent_count;
113 .string cat_override_string;
116 // fields for drawing categories
117 float category_name[MAX_CATEGORIES];
118 float category_item[MAX_CATEGORIES];
119 float category_draw_count;
122 SLIST_CATEGORY(CAT_FAVORITED, "", "", _("Favorites")) \
123 SLIST_CATEGORY(CAT_RECOMMENDED, "", "CAT_SERVERS", _("Recommended")) \
124 SLIST_CATEGORY(CAT_NORMAL, "", "CAT_SERVERS", _("Normal Servers")) \
125 SLIST_CATEGORY(CAT_SERVERS, "CAT_NORMAL", "CAT_SERVERS", _("Servers")) \
126 SLIST_CATEGORY(CAT_XPM, "CAT_NORMAL", "CAT_SERVERS", _("Competitive Mode")) \
127 SLIST_CATEGORY(CAT_MODIFIED, "", "CAT_SERVERS", _("Modified Servers")) \
128 SLIST_CATEGORY(CAT_OVERKILL, "", "CAT_SERVERS", _("Overkill Mode")) \
129 SLIST_CATEGORY(CAT_MINSTAGIB, "", "CAT_SERVERS", _("MinstaGib Mode")) \
130 SLIST_CATEGORY(CAT_DEFRAG, "", "CAT_SERVERS", _("Defrag Mode"))
132 // C is stupid, must use extra macro for concatenation
133 #define SLIST_ADD_CAT_CVAR(name,default) var string autocvar_menu_serverlist_categories_##name##_override = default;
134 #define SLIST_CATEGORY(name,enoverride,deoverride,string) \
135 SLIST_ADD_CAT_CVAR(name, enoverride) \
137 void RegisterSLCategory_##name() \
139 SET_FIELD_COUNT(name, CATEGORY_FIRST, category_ent_count) \
140 CHECK_MAX_COUNT(name, MAX_CATEGORIES, category_ent_count, "SLIST_CATEGORY") \
141 entity cat = spawn(); \
142 categories[name - 1] = cat; \
143 cat.classname = "slist_category"; \
144 cat.cat_name = strzone(#name); \
145 cat.cat_override_string = strzone((autocvar_menu_serverlist_categories ? \
146 autocvar_menu_serverlist_categories_##name##_override \
149 cat.cat_string = strzone(string); \
151 ACCUMULATE_FUNCTION(RegisterSLCategories, RegisterSLCategory_##name);
155 void RegisterSLCategories_Done()
159 for(i = 0; i < category_ent_count; ++i)
161 s = categories[i].cat_override_string;
162 if((s != "") && (s != categories[i].cat_name))
164 catnum = Get_Cat_Num_FromString(s);
167 strunzone(categories[i].cat_override_string);
168 categories[i].cat_override = catnum;
172 strunzone(categories[i].cat_override_string);
173 categories[i].cat_override = 0;
176 ACCUMULATE_FUNCTION(RegisterSLCategories, RegisterSLCategories_Done);
178 #undef SLIST_ADD_CAT_CVAR
179 #undef SLIST_CATEGORY
182 void ServerList_Connect_Click(entity btn, entity me);
183 void ServerList_ShowEmpty_Click(entity box, entity me);
184 void ServerList_ShowFull_Click(entity box, entity me);
185 void ServerList_Filter_Change(entity box, entity me);
186 void ServerList_Favorite_Click(entity btn, entity me);
187 void ServerList_Info_Click(entity btn, entity me);
188 void ServerList_Update_favoriteButton(entity btn, entity me);
192 #ifdef IMPLEMENTATION
194 // Supporting Functions
195 float Get_Cat_Num_FromString(string input)
198 for(i = 0; i < category_ent_count; ++i) { if(categories[i].cat_name == input) { return (i + 1); } }
199 print(sprintf("Get_Cat_Num_FromString('%s'): Improper category name!\n", input));
202 entity Get_Cat_Ent(float catnum)
204 if((catnum > 0) && (catnum <= category_ent_count))
206 return categories[catnum - 1];
210 error(sprintf("Get_Cat_Ent(%d): Improper category number!\n", catnum));
216 float IsServerInList(string list, string srv)
222 srv = netaddress_resolve(srv, 26000);
225 p = crypto_getidfp(srv);
226 n = tokenize_console(list);
227 for(i = 0; i < n; ++i)
229 if(substring(argv(i), 0, 1) != "[" && strlen(argv(i)) == 44 && strstrofs(argv(i), ".", 0) < 0)
237 if(srv == netaddress_resolve(argv(i), 26000))
244 float CheckCategoryOverride(float cat)
246 entity catent = Get_Cat_Ent(cat);
249 if(catent.cat_override) { return catent.cat_override; }
254 error(sprintf("CheckCategoryOverride(%d): Improper category number!\n", cat));
259 float CheckCategoryForEntry(float entry)
261 string s, k, v, modtype = "";
263 s = gethostcachestring(SLIST_FIELD_QCSTATUS, entry);
264 m = tokenizebyseparator(s, ":");
268 // typestr = argv(0);
269 // versionstr = argv(1);
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_serverlist_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(XonoticServerList_MapItems(gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT)) != 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 num = XonoticServerList_MapItems(me.selectedItem);
438 if(me.selectedServer)
439 strunzone(me.selectedServer);
440 me.selectedServer = strzone(gethostcachestring(SLIST_FIELD_CNAME, num));
442 me.ipAddressBox.setText(me.ipAddressBox, me.selectedServer);
443 me.ipAddressBox.cursorPos = strlen(me.selectedServer);
444 me.ipAddressBoxFocused = -1;
447 void XonoticServerList_refreshServerList(entity me, float mode)
449 // 0: just reparametrize
450 // 1: also ask for new servers
452 //print("refresh of type ", ftos(mode), "\n");
453 /* if(mode == 2) // borken
456 localcmd("net_slist\n");
457 me.needsRefresh = 1; // net_slist kills sort order, so we need to restore it later
463 string s, typestr, modstr;
466 m = strstrofs(s, ":", 0);
469 typestr = substring(s, 0, m);
470 s = substring(s, m + 1, strlen(s) - m - 1);
471 while(substring(s, 0, 1) == " ")
472 s = substring(s, 1, strlen(s) - 1);
477 modstr = cvar_string("menu_slist_modfilter");
479 m = SLIST_MASK_AND - 1;
480 resethostcachemasks();
482 // ping: reject negative ping (no idea why this happens in the first place, engine bug)
483 sethostcachemasknumber(++m, SLIST_FIELD_PING, 0, SLIST_TEST_GREATEREQUAL);
486 if(!me.filterShowFull)
488 sethostcachemasknumber(++m, SLIST_FIELD_FREESLOTS, 1, SLIST_TEST_GREATEREQUAL); // legacy
489 sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, ":S0:", SLIST_TEST_NOTCONTAIN); // g_maxplayers support
493 if(!me.filterShowEmpty)
494 sethostcachemasknumber(++m, SLIST_FIELD_NUMHUMANS, 1, SLIST_TEST_GREATEREQUAL);
496 // gametype filtering
498 sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, strcat(typestr, ":"), SLIST_TEST_STARTSWITH);
503 if(substring(modstr, 0, 1) == "!")
504 sethostcachemaskstring(++m, SLIST_FIELD_MOD, resolvemod(substring(modstr, 1, strlen(modstr) - 1)), SLIST_TEST_NOTEQUAL);
506 sethostcachemaskstring(++m, SLIST_FIELD_MOD, resolvemod(modstr), SLIST_TEST_EQUAL);
510 n = tokenizebyseparator(_Nex_ExtResponseSystem_BannedServers, " ");
511 for(i = 0; i < n; ++i)
513 sethostcachemaskstring(++m, SLIST_FIELD_CNAME, argv(i), SLIST_TEST_NOTSTARTSWITH);
515 m = SLIST_MASK_OR - 1;
518 sethostcachemaskstring(++m, SLIST_FIELD_NAME, s, SLIST_TEST_CONTAINS);
519 sethostcachemaskstring(++m, SLIST_FIELD_MAP, s, SLIST_TEST_CONTAINS);
520 sethostcachemaskstring(++m, SLIST_FIELD_PLAYERS, s, SLIST_TEST_CONTAINS);
521 sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, strcat(s, ":"), SLIST_TEST_STARTSWITH);
525 //listflags |= SLSF_FAVORITES;
526 listflags |= SLSF_CATEGORIES;
527 if(me.currentSortOrder < 0) { listflags |= SLSF_DESCENDING; }
528 sethostcachesort(me.currentSortField, listflags);
531 if(mode >= 1) { refreshhostcache(); }
533 void XonoticServerList_focusEnter(entity me)
535 if(time < me.nextRefreshTime)
537 //print("sorry, no refresh yet\n");
540 me.nextRefreshTime = time + 10;
541 me.refreshServerList(me, 1);
544 void XonoticServerList_draw(entity me)
546 float i, found, owned, num;
548 if(_Nex_ExtResponseSystem_BannedServersNeedsRefresh)
552 _Nex_ExtResponseSystem_BannedServersNeedsRefresh = 0;
555 if(me.currentSortField == -1)
557 me.setSortOrder(me, SLIST_FIELD_PING, +1);
558 me.refreshServerList(me, 2);
560 else if(me.needsRefresh == 1)
562 me.needsRefresh = 2; // delay by one frame to make sure "slist" has been executed
564 else if(me.needsRefresh == 2)
567 me.refreshServerList(me, 0);
570 owned = ((me.selectedServer == me.ipAddressBox.text) && (me.ipAddressBox.text != ""));
572 for(i = 0; i < category_draw_count; ++i) { category_name[i] = -1; category_item[i] = -1; }
573 category_draw_count = 0;
575 if(autocvar_menu_serverlist_categories >= 0) // if less than 0, don't even draw a category heading for favorites
577 float itemcount = gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT);
578 me.nItems = itemcount;
580 //float visible = floor(me.scrollPos / me.itemHeight);
581 // ^ unfortunately no such optimization can be made-- we must process through the
582 // entire list, otherwise there is no way to know which item is first in its category.
585 for(i = 0; i < itemcount; ++i)
587 cat = gethostcachenumber(SLIST_FIELD_CATEGORY, i);
590 if(category_draw_count == 0)
592 category_name[category_draw_count] = cat;
593 category_item[category_draw_count] = i;
594 ++category_draw_count;
600 for(x = 0; x < category_draw_count; ++x) { if(cat == category_name[x]) { found = 1; } }
603 category_name[category_draw_count] = cat;
604 category_item[category_draw_count] = i;
605 ++category_draw_count;
611 if(autocvar_menu_serverlist_categories_onlyifmultiple && (category_draw_count == 1))
613 category_name[0] = category_name[1] = -1;
614 category_item[0] = category_item[1] = -1;
615 category_draw_count = 0;
616 me.nItems = itemcount;
619 else { me.nItems = gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT); }
621 me.connectButton.disabled = ((me.nItems == 0) && (me.ipAddressBox.text == ""));
622 me.infoButton.disabled = ((me.nItems == 0) || !owned);
623 me.favoriteButton.disabled = ((me.nItems == 0) && (me.ipAddressBox.text == ""));
626 if(me.selectedServer)
628 for(i = 0; i < me.nItems; ++i)
630 num = XonoticServerList_MapItems(i);
633 if(gethostcachestring(SLIST_FIELD_CNAME, num) == me.selectedServer)
635 if(i != me.selectedItem)
637 me.lastClickedServer = -1;
650 if(me.selectedItem >= me.nItems) { me.selectedItem = me.nItems - 1; }
651 if(me.selectedServer) { strunzone(me.selectedServer); }
653 num = XonoticServerList_MapItems(me.selectedItem);
654 if(num >= 0) { me.selectedServer = strzone(gethostcachestring(SLIST_FIELD_CNAME, num)); }
660 if(me.selectedServer != me.ipAddressBox.text)
662 me.ipAddressBox.setText(me.ipAddressBox, me.selectedServer);
663 me.ipAddressBox.cursorPos = strlen(me.selectedServer);
664 me.ipAddressBoxFocused = -1;
668 if(me.ipAddressBoxFocused != me.ipAddressBox.focused)
670 if(me.ipAddressBox.focused || me.ipAddressBoxFocused < 0)
671 ServerList_Update_favoriteButton(NULL, me);
672 me.ipAddressBoxFocused = me.ipAddressBox.focused;
675 SUPER(XonoticServerList).draw(me);
677 void ServerList_PingSort_Click(entity btn, entity me)
679 me.setSortOrder(me, SLIST_FIELD_PING, +1);
681 void ServerList_NameSort_Click(entity btn, entity me)
683 me.setSortOrder(me, SLIST_FIELD_NAME, -1); // why?
685 void ServerList_MapSort_Click(entity btn, entity me)
687 me.setSortOrder(me, SLIST_FIELD_MAP, -1); // why?
689 void ServerList_PlayerSort_Click(entity btn, entity me)
691 me.setSortOrder(me, SLIST_FIELD_NUMHUMANS, -1);
693 void ServerList_TypeSort_Click(entity btn, entity me)
698 m = strstrofs(s, ":", 0);
701 s = substring(s, 0, m);
702 while(substring(s, m+1, 1) == " ") // skip spaces
708 for(i = 1; ; i *= 2) // 20 modes ought to be enough for anyone
710 t = MapInfo_Type_ToString(i);
712 if(t == "") // it repeats (default case)
715 // choose the first one
716 s = MapInfo_Type_ToString(1);
721 // the type was found
722 // choose the next one
723 s = MapInfo_Type_ToString(i * 2);
725 s = MapInfo_Type_ToString(1);
732 s = strcat(s, substring(me.filterString, m+1, strlen(me.filterString) - m - 1));
734 me.controlledTextbox.setText(me.controlledTextbox, s);
735 me.controlledTextbox.keyDown(me.controlledTextbox, K_END, 0, 0);
736 me.controlledTextbox.keyUp(me.controlledTextbox, K_END, 0, 0);
737 //ServerList_Filter_Change(me.controlledTextbox, me);
739 void ServerList_Filter_Change(entity box, entity me)
742 strunzone(me.filterString);
744 me.filterString = strzone(box.text);
746 me.filterString = string_null;
747 me.refreshServerList(me, 0);
749 me.ipAddressBox.setText(me.ipAddressBox, "");
750 me.ipAddressBox.cursorPos = 0;
751 me.ipAddressBoxFocused = -1;
753 void ServerList_ShowEmpty_Click(entity box, entity me)
755 box.setChecked(box, me.filterShowEmpty = !me.filterShowEmpty);
756 me.refreshServerList(me, 0);
758 me.ipAddressBox.setText(me.ipAddressBox, "");
759 me.ipAddressBox.cursorPos = 0;
760 me.ipAddressBoxFocused = -1;
762 void ServerList_ShowFull_Click(entity box, entity me)
764 box.setChecked(box, me.filterShowFull = !me.filterShowFull);
765 me.refreshServerList(me, 0);
767 me.ipAddressBox.setText(me.ipAddressBox, "");
768 me.ipAddressBox.cursorPos = 0;
769 me.ipAddressBoxFocused = -1;
771 void XonoticServerList_setSortOrder(entity me, float fld, float direction)
773 if(me.currentSortField == fld)
774 direction = -me.currentSortOrder;
775 me.currentSortOrder = direction;
776 me.currentSortField = fld;
777 me.sortButton1.forcePressed = (fld == SLIST_FIELD_PING);
778 me.sortButton2.forcePressed = (fld == SLIST_FIELD_NAME);
779 me.sortButton3.forcePressed = (fld == SLIST_FIELD_MAP);
780 me.sortButton4.forcePressed = 0;
781 me.sortButton5.forcePressed = (fld == SLIST_FIELD_NUMHUMANS);
783 if(me.selectedServer)
784 strunzone(me.selectedServer);
785 me.selectedServer = string_null;
786 me.refreshServerList(me, 0);
788 void XonoticServerList_positionSortButton(entity me, entity btn, float theOrigin, float theSize, string theTitle, void(entity, entity) theFunc)
790 vector originInLBSpace, sizeInLBSpace;
791 originInLBSpace = eY * (-me.itemHeight);
792 sizeInLBSpace = eY * me.itemHeight + eX * (1 - me.controlWidth);
794 vector originInDialogSpace, sizeInDialogSpace;
795 originInDialogSpace = boxToGlobal(originInLBSpace, me.Container_origin, me.Container_size);
796 sizeInDialogSpace = boxToGlobalSize(sizeInLBSpace, me.Container_size);
798 btn.Container_origin_x = originInDialogSpace_x + sizeInDialogSpace_x * theOrigin;
799 btn.Container_size_x = sizeInDialogSpace_x * theSize;
800 btn.setText(btn, theTitle);
801 btn.onClick = theFunc;
802 btn.onClickEntity = me;
805 void XonoticServerList_resizeNotify(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
807 SUPER(XonoticServerList).resizeNotify(me, relOrigin, relSize, absOrigin, absSize);
809 me.realFontSize_y = me.fontSize / (absSize_y * me.itemHeight);
810 me.realFontSize_x = me.fontSize / (absSize_x * (1 - me.controlWidth));
811 me.realUpperMargin = 0.5 * (1 - me.realFontSize_y);
813 me.columnIconsOrigin = 0;
814 me.columnIconsSize = me.realFontSize_x * 4 * me.iconsSizeFactor;
815 me.columnPingSize = me.realFontSize_x * 3;
816 me.columnMapSize = me.realFontSize_x * 10;
817 me.columnTypeSize = me.realFontSize_x * 4;
818 me.columnPlayersSize = me.realFontSize_x * 5;
819 me.columnNameSize = 1 - me.columnPlayersSize - me.columnMapSize - me.columnPingSize - me.columnIconsSize - me.columnTypeSize - 5 * me.realFontSize_x;
820 me.columnPingOrigin = me.columnIconsOrigin + me.columnIconsSize + me.realFontSize_x;
821 me.columnNameOrigin = me.columnPingOrigin + me.columnPingSize + me.realFontSize_x;
822 me.columnMapOrigin = me.columnNameOrigin + me.columnNameSize + me.realFontSize_x;
823 me.columnTypeOrigin = me.columnMapOrigin + me.columnMapSize + me.realFontSize_x;
824 me.columnPlayersOrigin = me.columnTypeOrigin + me.columnTypeSize + me.realFontSize_x;
826 me.positionSortButton(me, me.sortButton1, me.columnPingOrigin, me.columnPingSize, _("Ping"), ServerList_PingSort_Click);
827 me.positionSortButton(me, me.sortButton2, me.columnNameOrigin, me.columnNameSize, _("Host name"), ServerList_NameSort_Click);
828 me.positionSortButton(me, me.sortButton3, me.columnMapOrigin, me.columnMapSize, _("Map"), ServerList_MapSort_Click);
829 me.positionSortButton(me, me.sortButton4, me.columnTypeOrigin, me.columnTypeSize, _("Type"), ServerList_TypeSort_Click);
830 me.positionSortButton(me, me.sortButton5, me.columnPlayersOrigin, me.columnPlayersSize, _("Players"), ServerList_PlayerSort_Click);
833 f = me.currentSortField;
836 me.currentSortField = -1;
837 me.setSortOrder(me, f, me.currentSortOrder); // force resetting the sort order
840 void ServerList_Connect_Click(entity btn, entity me)
842 if(me.ipAddressBox.text == "")
843 localcmd("connect ", me.selectedServer, "\n");
845 localcmd("connect ", me.ipAddressBox.text, "\n");
847 void ServerList_Favorite_Click(entity btn, entity me)
850 ipstr = netaddress_resolve(me.ipAddressBox.text, 26000);
853 ToggleFavorite(me.ipAddressBox.text);
854 me.ipAddressBoxFocused = -1;
857 void ServerList_Info_Click(entity btn, entity me)
859 main.serverInfoDialog.loadServerInfo(main.serverInfoDialog, XonoticServerList_MapItems(me.selectedItem));
860 DialogOpenButton_Click(me, main.serverInfoDialog);
862 void XonoticServerList_clickListBoxItem(entity me, float i, vector where)
864 float num = XonoticServerList_MapItems(i);
865 if(num == me.lastClickedServer)
866 if(time < me.lastClickedTime + 0.3)
869 ServerList_Connect_Click(NULL, me);
871 me.lastClickedServer = num;
872 me.lastClickedTime = time;
874 void XonoticServerList_drawListBoxItem(entity me, float i, vector absSize, float isSelected)
876 // layout: Ping, Server name, Map name, NP, TP, MP
881 float m, pure, freeslots, j, sflags;
882 string s, typestr, versionstr, k, v, modname;
884 float cache = XonoticServerList_MapItems(i);
885 //print(sprintf("time: %f, i: %d, cache: %d, nitems: %d\n", time, i, cache, me.nItems));
889 entity catent = Get_Cat_Ent(-cache);
890 if(catent) { draw_Text(me.realUpperMargin * eY + (me.columnNameOrigin + (me.columnNameSize - draw_TextWidth(catent.cat_string, 0, me.realFontSize)) * 0.5) * eX, catent.cat_string, me.realFontSize, '1 1 1', SKINALPHA_TEXT, 0); return; }
894 draw_Fill('0 0 0', '1 1 0', SKINCOLOR_LISTBOX_SELECTED, SKINALPHA_LISTBOX_SELECTED);
896 s = gethostcachestring(SLIST_FIELD_QCSTATUS, cache);
897 m = tokenizebyseparator(s, ":");
902 versionstr = argv(1);
908 for(j = 2; j < m; ++j)
912 k = substring(argv(j), 0, 1);
913 v = substring(argv(j), 1, -1);
924 #ifdef COMPAT_NO_MOD_IS_XONOTIC
930 SLIST_FIELD_MOD = gethostcacheindexforkey("mod");
931 s = gethostcachestring(SLIST_FIELD_MOD, cache);
933 if(modname == "Xonotic")
937 // list the mods here on which the pure server check actually works
938 if(modname != "Xonotic")
939 if(modname != "MinstaGib")
942 if(modname != "NewToys")
945 if(gethostcachenumber(SLIST_FIELD_FREESLOTS, cache) <= 0)
946 theAlpha = SKINALPHA_SERVERLIST_FULL;
947 else if(freeslots == 0)
948 theAlpha = SKINALPHA_SERVERLIST_FULL; // g_maxplayers support
949 else if not(gethostcachenumber(SLIST_FIELD_NUMHUMANS, cache))
950 theAlpha = SKINALPHA_SERVERLIST_EMPTY;
954 p = gethostcachenumber(SLIST_FIELD_PING, cache);
957 #define PING_HIGH 500
959 theColor = SKINCOLOR_SERVERLIST_LOWPING + (SKINCOLOR_SERVERLIST_MEDPING - SKINCOLOR_SERVERLIST_LOWPING) * (p / PING_LOW);
960 else if(p < PING_MED)
961 theColor = SKINCOLOR_SERVERLIST_MEDPING + (SKINCOLOR_SERVERLIST_HIGHPING - SKINCOLOR_SERVERLIST_MEDPING) * ((p - PING_LOW) / (PING_MED - PING_LOW));
962 else if(p < PING_HIGH)
964 theColor = SKINCOLOR_SERVERLIST_HIGHPING;
965 theAlpha *= 1 + (SKINALPHA_SERVERLIST_HIGHPING - 1) * ((p - PING_MED) / (PING_HIGH - PING_MED));
970 theAlpha *= SKINALPHA_SERVERLIST_HIGHPING;
973 if(gethostcachenumber(SLIST_FIELD_ISFAVORITE, cache))
975 theColor = theColor * (1 - SKINALPHA_SERVERLIST_FAVORITE) + SKINCOLOR_SERVERLIST_FAVORITE * SKINALPHA_SERVERLIST_FAVORITE;
976 theAlpha = theAlpha * (1 - SKINALPHA_SERVERLIST_FAVORITE) + SKINALPHA_SERVERLIST_FAVORITE;
979 s = gethostcachestring(SLIST_FIELD_CNAME, cache);
982 if(substring(s, 0, 1) == "[")
987 else if(strstrofs("0123456789", substring(s, 0, 1), 0) >= 0)
993 q = stof(substring(crypto_getencryptlevel(s), 0, 1));
994 if((q <= 0 && cvar("crypto_aeslevel") >= 3) || (q >= 3 && cvar("crypto_aeslevel") <= 0))
996 theColor = SKINCOLOR_SERVERLIST_IMPOSSIBLE;
997 theAlpha = SKINALPHA_SERVERLIST_IMPOSSIBLE;
1002 if(cvar("crypto_aeslevel") >= 2)
1007 if(cvar("crypto_aeslevel") >= 1)
1017 // 2: AES recommended but not available
1018 // 3: AES possible and will be used
1019 // 4: AES recommended and will be used
1023 vector iconSize = '0 0 0';
1024 iconSize_y = me.realFontSize_y * me.iconsSizeFactor;
1025 iconSize_x = me.realFontSize_x * me.iconsSizeFactor;
1027 vector iconPos = '0 0 0';
1028 iconPos_x = (me.columnIconsSize - 3 * iconSize_x) * 0.5;
1029 iconPos_y = (1 - iconSize_y) * 0.5;
1033 if not(me.seenIPv4 && me.seenIPv6)
1035 iconPos_x += iconSize_x * 0.5;
1037 else if(me.seenIPv4 && me.seenIPv6)
1041 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_ipv6"), 0); // PRECACHE_PIC_MIPMAP
1043 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_ipv4"), 0); // PRECACHE_PIC_MIPMAP
1045 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1046 iconPos_x += iconSize_x;
1051 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_aeslevel", ftos(q)), 0); // PRECACHE_PIC_MIPMAP
1052 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1054 iconPos_x += iconSize_x;
1056 if(modname == "Xonotic")
1060 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_pure1"), PRECACHE_PIC_MIPMAP);
1061 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1066 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_mod_", modname), PRECACHE_PIC_MIPMAP);
1067 if(draw_PictureSize(n) == '0 0 0')
1068 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_mod_"), PRECACHE_PIC_MIPMAP);
1070 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1072 draw_Picture(iconPos, n, iconSize, '1 1 1', SKINALPHA_SERVERLIST_ICON_NONPURE);
1074 iconPos_x += iconSize_x;
1076 if(sflags >= 0 && (sflags & SERVERFLAG_PLAYERSTATS))
1078 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_stats1"), 0); // PRECACHE_PIC_MIPMAP
1079 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1081 iconPos_x += iconSize_x;
1085 draw_Text(me.realUpperMargin * eY + (me.columnPingOrigin + me.columnPingSize - draw_TextWidth(s, 0, me.realFontSize)) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1086 s = draw_TextShortenToWidth(gethostcachestring(SLIST_FIELD_NAME, cache), me.columnNameSize, 0, me.realFontSize);
1087 draw_Text(me.realUpperMargin * eY + me.columnNameOrigin * eX, s, me.realFontSize, theColor, theAlpha, 0);
1088 s = draw_TextShortenToWidth(gethostcachestring(SLIST_FIELD_MAP, cache), me.columnMapSize, 0, me.realFontSize);
1089 draw_Text(me.realUpperMargin * eY + (me.columnMapOrigin + (me.columnMapSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1090 s = draw_TextShortenToWidth(typestr, me.columnTypeSize, 0, me.realFontSize);
1091 draw_Text(me.realUpperMargin * eY + (me.columnTypeOrigin + (me.columnTypeSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1092 s = strcat(ftos(gethostcachenumber(SLIST_FIELD_NUMHUMANS, cache)), "/", ftos(gethostcachenumber(SLIST_FIELD_MAXPLAYERS, cache)));
1093 draw_Text(me.realUpperMargin * eY + (me.columnPlayersOrigin + (me.columnPlayersSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1096 float XonoticServerList_keyDown(entity me, float scan, float ascii, float shift)
1098 float i = XonoticServerList_MapItems(me.selectedItem);
1101 org = boxToGlobal(eY * (me.selectedItem * me.itemHeight - me.scrollPos), me.origin, me.size);
1102 sz = boxToGlobalSize(eY * me.itemHeight + eX * (1 - me.controlWidth), me.size);
1104 if(scan == K_ENTER || scan == K_KP_ENTER)
1106 ServerList_Connect_Click(NULL, me);
1109 else if(scan == K_MOUSE2 || scan == K_SPACE)
1111 if((me.nItems != 0) && (i >= 0))
1113 main.serverInfoDialog.loadServerInfo(main.serverInfoDialog, i);
1114 DialogOpenButton_Click_withCoords(me, main.serverInfoDialog, org, sz);
1119 else if(scan == K_INS || scan == K_MOUSE3 || scan == K_KP_INS)
1121 if((me.nItems != 0) && (i >= 0))
1123 ToggleFavorite(me.selectedServer);
1124 me.ipAddressBoxFocused = -1;
1129 else if(SUPER(XonoticServerList).keyDown(me, scan, ascii, shift))
1131 else if(!me.controlledTextbox)
1134 return me.controlledTextbox.keyDown(me.controlledTextbox, scan, ascii, shift);