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 localcmd(sprintf("connect %s\n",
843 ((me.ipAddressBox.text != "") ?
844 me.ipAddressBox.text : me.selectedServer
848 void ServerList_Favorite_Click(entity btn, entity me)
851 ipstr = netaddress_resolve(me.ipAddressBox.text, 26000);
854 ToggleFavorite(me.ipAddressBox.text);
855 me.ipAddressBoxFocused = -1;
858 void ServerList_Info_Click(entity btn, entity me)
860 main.serverInfoDialog.loadServerInfo(main.serverInfoDialog, XonoticServerList_MapItems(me.selectedItem));
861 DialogOpenButton_Click(me, main.serverInfoDialog);
863 void XonoticServerList_clickListBoxItem(entity me, float i, vector where)
865 float num = XonoticServerList_MapItems(i);
866 if(num == me.lastClickedServer)
867 if(time < me.lastClickedTime + 0.3)
870 ServerList_Connect_Click(NULL, me);
872 me.lastClickedServer = num;
873 me.lastClickedTime = time;
875 void XonoticServerList_drawListBoxItem(entity me, float i, vector absSize, float isSelected)
877 // layout: Ping, Server name, Map name, NP, TP, MP
882 float m, pure, freeslots, j, sflags;
883 string s, typestr, versionstr, k, v, modname;
885 float item = XonoticServerList_MapItems(i);
886 //print(sprintf("time: %f, i: %d, item: %d, nitems: %d\n", time, i, item, me.nItems));
890 entity catent = Get_Cat_Ent(-item);
891 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; }
895 draw_Fill('0 0 0', '1 1 0', SKINCOLOR_LISTBOX_SELECTED, SKINALPHA_LISTBOX_SELECTED);
897 s = gethostcachestring(SLIST_FIELD_QCSTATUS, item);
898 m = tokenizebyseparator(s, ":");
903 versionstr = argv(1);
909 for(j = 2; j < m; ++j)
913 k = substring(argv(j), 0, 1);
914 v = substring(argv(j), 1, -1);
925 #ifdef COMPAT_NO_MOD_IS_XONOTIC
931 SLIST_FIELD_MOD = gethostcacheindexforkey("mod");
932 s = gethostcachestring(SLIST_FIELD_MOD, item);
934 if(modname == "Xonotic")
938 // list the mods here on which the pure server check actually works
939 if(modname != "Xonotic")
940 if(modname != "MinstaGib")
943 if(modname != "NewToys")
946 if(gethostcachenumber(SLIST_FIELD_FREESLOTS, item) <= 0)
947 theAlpha = SKINALPHA_SERVERLIST_FULL;
948 else if(freeslots == 0)
949 theAlpha = SKINALPHA_SERVERLIST_FULL; // g_maxplayers support
950 else if not(gethostcachenumber(SLIST_FIELD_NUMHUMANS, item))
951 theAlpha = SKINALPHA_SERVERLIST_EMPTY;
955 p = gethostcachenumber(SLIST_FIELD_PING, item);
958 #define PING_HIGH 500
960 theColor = SKINCOLOR_SERVERLIST_LOWPING + (SKINCOLOR_SERVERLIST_MEDPING - SKINCOLOR_SERVERLIST_LOWPING) * (p / PING_LOW);
961 else if(p < PING_MED)
962 theColor = SKINCOLOR_SERVERLIST_MEDPING + (SKINCOLOR_SERVERLIST_HIGHPING - SKINCOLOR_SERVERLIST_MEDPING) * ((p - PING_LOW) / (PING_MED - PING_LOW));
963 else if(p < PING_HIGH)
965 theColor = SKINCOLOR_SERVERLIST_HIGHPING;
966 theAlpha *= 1 + (SKINALPHA_SERVERLIST_HIGHPING - 1) * ((p - PING_MED) / (PING_HIGH - PING_MED));
971 theAlpha *= SKINALPHA_SERVERLIST_HIGHPING;
974 if(gethostcachenumber(SLIST_FIELD_ISFAVORITE, item))
976 theColor = theColor * (1 - SKINALPHA_SERVERLIST_FAVORITE) + SKINCOLOR_SERVERLIST_FAVORITE * SKINALPHA_SERVERLIST_FAVORITE;
977 theAlpha = theAlpha * (1 - SKINALPHA_SERVERLIST_FAVORITE) + SKINALPHA_SERVERLIST_FAVORITE;
980 s = gethostcachestring(SLIST_FIELD_CNAME, item);
983 if(substring(s, 0, 1) == "[")
988 else if(strstrofs("0123456789", substring(s, 0, 1), 0) >= 0)
994 q = stof(substring(crypto_getencryptlevel(s), 0, 1));
995 if((q <= 0 && cvar("crypto_aeslevel") >= 3) || (q >= 3 && cvar("crypto_aeslevel") <= 0))
997 theColor = SKINCOLOR_SERVERLIST_IMPOSSIBLE;
998 theAlpha = SKINALPHA_SERVERLIST_IMPOSSIBLE;
1003 if(cvar("crypto_aeslevel") >= 2)
1008 if(cvar("crypto_aeslevel") >= 1)
1018 // 2: AES recommended but not available
1019 // 3: AES possible and will be used
1020 // 4: AES recommended and will be used
1024 vector iconSize = '0 0 0';
1025 iconSize_y = me.realFontSize_y * me.iconsSizeFactor;
1026 iconSize_x = me.realFontSize_x * me.iconsSizeFactor;
1028 vector iconPos = '0 0 0';
1029 iconPos_x = (me.columnIconsSize - 3 * iconSize_x) * 0.5;
1030 iconPos_y = (1 - iconSize_y) * 0.5;
1034 if not(me.seenIPv4 && me.seenIPv6)
1036 iconPos_x += iconSize_x * 0.5;
1038 else if(me.seenIPv4 && me.seenIPv6)
1042 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_ipv6"), 0); // PRECACHE_PIC_MIPMAP
1044 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_ipv4"), 0); // PRECACHE_PIC_MIPMAP
1046 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1047 iconPos_x += iconSize_x;
1052 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_aeslevel", ftos(q)), 0); // PRECACHE_PIC_MIPMAP
1053 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1055 iconPos_x += iconSize_x;
1057 if(modname == "Xonotic")
1061 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_pure1"), PRECACHE_PIC_MIPMAP);
1062 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1067 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_mod_", modname), PRECACHE_PIC_MIPMAP);
1068 if(draw_PictureSize(n) == '0 0 0')
1069 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_mod_"), PRECACHE_PIC_MIPMAP);
1071 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1073 draw_Picture(iconPos, n, iconSize, '1 1 1', SKINALPHA_SERVERLIST_ICON_NONPURE);
1075 iconPos_x += iconSize_x;
1077 if(sflags >= 0 && (sflags & SERVERFLAG_PLAYERSTATS))
1079 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_stats1"), 0); // PRECACHE_PIC_MIPMAP
1080 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1082 iconPos_x += iconSize_x;
1086 draw_Text(me.realUpperMargin * eY + (me.columnPingOrigin + me.columnPingSize - draw_TextWidth(s, 0, me.realFontSize)) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1087 s = draw_TextShortenToWidth(gethostcachestring(SLIST_FIELD_NAME, item), me.columnNameSize, 0, me.realFontSize);
1088 draw_Text(me.realUpperMargin * eY + me.columnNameOrigin * eX, s, me.realFontSize, theColor, theAlpha, 0);
1089 s = draw_TextShortenToWidth(gethostcachestring(SLIST_FIELD_MAP, item), me.columnMapSize, 0, me.realFontSize);
1090 draw_Text(me.realUpperMargin * eY + (me.columnMapOrigin + (me.columnMapSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1091 s = draw_TextShortenToWidth(typestr, me.columnTypeSize, 0, me.realFontSize);
1092 draw_Text(me.realUpperMargin * eY + (me.columnTypeOrigin + (me.columnTypeSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1093 s = strcat(ftos(gethostcachenumber(SLIST_FIELD_NUMHUMANS, item)), "/", ftos(gethostcachenumber(SLIST_FIELD_MAXPLAYERS, item)));
1094 draw_Text(me.realUpperMargin * eY + (me.columnPlayersOrigin + (me.columnPlayersSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1097 float XonoticServerList_keyDown(entity me, float scan, float ascii, float shift)
1099 float i = XonoticServerList_MapItems(me.selectedItem);
1102 org = boxToGlobal(eY * (me.selectedItem * me.itemHeight - me.scrollPos), me.origin, me.size);
1103 sz = boxToGlobalSize(eY * me.itemHeight + eX * (1 - me.controlWidth), me.size);
1105 if(scan == K_ENTER || scan == K_KP_ENTER)
1107 ServerList_Connect_Click(NULL, me);
1110 else if(scan == K_MOUSE2 || scan == K_SPACE)
1112 if((me.nItems != 0) && (i >= 0))
1114 main.serverInfoDialog.loadServerInfo(main.serverInfoDialog, i);
1115 DialogOpenButton_Click_withCoords(me, main.serverInfoDialog, org, sz);
1120 else if(scan == K_INS || scan == K_MOUSE3 || scan == K_KP_INS)
1122 if((me.nItems != 0) && (i >= 0))
1124 ToggleFavorite(me.selectedServer);
1125 me.ipAddressBoxFocused = -1;
1130 else if(SUPER(XonoticServerList).keyDown(me, scan, ascii, shift))
1132 else if(!me.controlledTextbox)
1135 return me.controlledTextbox.keyDown(me.controlledTextbox, scan, ascii, shift);