2 CLASS(XonoticServerList) EXTENDS(XonoticListBox)
3 METHOD(XonoticServerList, configureXonoticServerList, void(entity))
4 ATTRIB(XonoticServerList, rowsPerItem, float, 1)
5 METHOD(XonoticServerList, draw, void(entity))
6 METHOD(XonoticServerList, drawListBoxItem, void(entity, float, vector, float))
7 METHOD(XonoticServerList, clickListBoxItem, void(entity, float, vector))
8 METHOD(XonoticServerList, resizeNotify, void(entity, vector, vector, vector, vector))
9 METHOD(XonoticServerList, keyDown, float(entity, float, float, float))
10 METHOD(XonoticServerList, toggleFavorite, void(entity, string))
12 ATTRIB(XonoticServerList, iconsSizeFactor, float, 0.85)
14 ATTRIB(XonoticServerList, realFontSize, vector, '0 0 0')
15 ATTRIB(XonoticServerList, realUpperMargin, float, 0)
16 ATTRIB(XonoticServerList, columnIconsOrigin, float, 0)
17 ATTRIB(XonoticServerList, columnIconsSize, float, 0)
18 ATTRIB(XonoticServerList, columnPingOrigin, float, 0)
19 ATTRIB(XonoticServerList, columnPingSize, float, 0)
20 ATTRIB(XonoticServerList, columnNameOrigin, float, 0)
21 ATTRIB(XonoticServerList, columnNameSize, float, 0)
22 ATTRIB(XonoticServerList, columnMapOrigin, float, 0)
23 ATTRIB(XonoticServerList, columnMapSize, float, 0)
24 ATTRIB(XonoticServerList, columnTypeOrigin, float, 0)
25 ATTRIB(XonoticServerList, columnTypeSize, float, 0)
26 ATTRIB(XonoticServerList, columnPlayersOrigin, float, 0)
27 ATTRIB(XonoticServerList, columnPlayersSize, float, 0)
29 ATTRIB(XonoticServerList, selectedServer, string, string_null) // to restore selected server when needed
30 METHOD(XonoticServerList, setSelected, void(entity, float))
31 METHOD(XonoticServerList, setSortOrder, void(entity, float, float))
32 ATTRIB(XonoticServerList, filterShowEmpty, float, 1)
33 ATTRIB(XonoticServerList, filterShowFull, float, 1)
34 ATTRIB(XonoticServerList, filterString, string, string_null)
35 ATTRIB(XonoticServerList, controlledTextbox, entity, NULL)
36 ATTRIB(XonoticServerList, ipAddressBox, entity, NULL)
37 ATTRIB(XonoticServerList, favoriteButton, entity, NULL)
38 ATTRIB(XonoticServerList, nextRefreshTime, float, 0)
39 METHOD(XonoticServerList, refreshServerList, void(entity, float)) // refresh mode: REFRESHSERVERLIST_*
40 ATTRIB(XonoticServerList, needsRefresh, float, 1)
41 METHOD(XonoticServerList, focusEnter, void(entity))
42 METHOD(XonoticServerList, positionSortButton, void(entity, entity, float, float, string, void(entity, entity)))
43 ATTRIB(XonoticServerList, sortButton1, entity, NULL)
44 ATTRIB(XonoticServerList, sortButton2, entity, NULL)
45 ATTRIB(XonoticServerList, sortButton3, entity, NULL)
46 ATTRIB(XonoticServerList, sortButton4, entity, NULL)
47 ATTRIB(XonoticServerList, sortButton5, entity, NULL)
48 ATTRIB(XonoticServerList, connectButton, entity, NULL)
49 ATTRIB(XonoticServerList, infoButton, entity, NULL)
50 ATTRIB(XonoticServerList, currentSortOrder, float, 0)
51 ATTRIB(XonoticServerList, currentSortField, float, -1)
52 ATTRIB(XonoticServerList, lastBumpSelectTime, float, 0)
53 ATTRIB(XonoticServerList, lastClickedServer, float, -1)
54 ATTRIB(XonoticServerList, lastClickedTime, float, 0)
56 ATTRIB(XonoticServerList, ipAddressBoxFocused, float, -1)
58 ATTRIB(XonoticServerList, seenIPv4, float, 0)
59 ATTRIB(XonoticServerList, seenIPv6, float, 0)
60 ENDCLASS(XonoticServerList)
61 entity makeXonoticServerList();
63 #ifndef IMPLEMENTATION
64 var float autocvar_menu_slist_categories = TRUE;
65 var float autocvar_menu_slist_categories_onlyifmultiple = TRUE;
66 var float autocvar_menu_slist_purethreshold = 10;
67 var float autocvar_menu_slist_recommendations = 2;
68 //var string autocvar_menu_slist_recommended = "76.124.107.5:26004";
70 // server cache fields
71 #define SLIST_FIELDS \
72 SLIST_FIELD(CNAME, "cname") \
73 SLIST_FIELD(PING, "ping") \
74 SLIST_FIELD(GAME, "game") \
75 SLIST_FIELD(MOD, "mod") \
76 SLIST_FIELD(MAP, "map") \
77 SLIST_FIELD(NAME, "name") \
78 SLIST_FIELD(MAXPLAYERS, "maxplayers") \
79 SLIST_FIELD(NUMPLAYERS, "numplayers") \
80 SLIST_FIELD(NUMHUMANS, "numhumans") \
81 SLIST_FIELD(NUMBOTS, "numbots") \
82 SLIST_FIELD(PROTOCOL, "protocol") \
83 SLIST_FIELD(FREESLOTS, "freeslots") \
84 SLIST_FIELD(PLAYERS, "players") \
85 SLIST_FIELD(QCSTATUS, "qcstatus") \
86 SLIST_FIELD(CATEGORY, "category") \
87 SLIST_FIELD(ISFAVORITE, "isfavorite")
89 #define SLIST_FIELD(suffix,name) float SLIST_FIELD_##suffix;
93 const float REFRESHSERVERLIST_RESORT = 0; // sort the server list again to update for changes to e.g. favorite status, categories
94 const float REFRESHSERVERLIST_REFILTER = 1; // ..., also update filter and sort criteria
95 const float REFRESHSERVERLIST_ASK = 2; // ..., also suggest querying servers now
96 const float REFRESHSERVERLIST_RESET = 3; // ..., also clear the list first
98 // function declarations
99 entity RetrieveCategoryEnt(float catnum);
101 float IsServerInList(string list, string srv);
102 #define IsFavorite(srv) IsServerInList(cvar_string("net_slist_favorites"), srv)
103 #define IsRecommended(srv) IsServerInList(_Nex_ExtResponseSystem_RecommendedServers, srv)
105 float CheckCategoryOverride(float cat);
106 float CheckCategoryForEntry(float entry);
107 float m_gethostcachecategory(float entry) { return CheckCategoryOverride(CheckCategoryForEntry(entry)); }
109 void RegisterSLCategories();
111 void ServerList_Connect_Click(entity btn, entity me);
112 void ServerList_Categories_Click(entity box, entity me);
113 void ServerList_ShowEmpty_Click(entity box, entity me);
114 void ServerList_ShowFull_Click(entity box, entity me);
115 void ServerList_Filter_Change(entity box, entity me);
116 void ServerList_Favorite_Click(entity btn, entity me);
117 void ServerList_Info_Click(entity btn, entity me);
118 void ServerList_Update_favoriteButton(entity btn, entity me);
120 // fields for category entities
121 #define MAX_CATEGORIES 9
122 #define CATEGORY_FIRST 1
123 entity categories[MAX_CATEGORIES];
124 float category_ent_count;
127 .string cat_enoverride_string;
128 .string cat_dioverride_string;
129 .float cat_enoverride;
130 .float cat_dioverride;
132 // fields for drawing categories
133 float category_name[MAX_CATEGORIES];
134 float category_item[MAX_CATEGORIES];
135 float category_draw_count;
137 #define SLIST_CATEGORIES \
138 SLIST_CATEGORY(CAT_FAVORITED, "", "", ZCTX(_("SLCAT^Favorites"))) \
139 SLIST_CATEGORY(CAT_RECOMMENDED, "", "CAT_SERVERS", ZCTX(_("SLCAT^Recommended"))) \
140 SLIST_CATEGORY(CAT_NORMAL, "", "CAT_SERVERS", ZCTX(_("SLCAT^Normal Servers"))) \
141 SLIST_CATEGORY(CAT_SERVERS, "CAT_NORMAL", "CAT_SERVERS", ZCTX(_("SLCAT^Servers"))) \
142 SLIST_CATEGORY(CAT_XPM, "CAT_NORMAL", "CAT_SERVERS", ZCTX(_("SLCAT^Competitive Mode"))) \
143 SLIST_CATEGORY(CAT_MODIFIED, "", "CAT_SERVERS", ZCTX(_("SLCAT^Modified Servers"))) \
144 SLIST_CATEGORY(CAT_OVERKILL, "", "CAT_SERVERS", ZCTX(_("SLCAT^Overkill Mode"))) \
145 SLIST_CATEGORY(CAT_MINSTAGIB, "", "CAT_SERVERS", ZCTX(_("SLCAT^MinstaGib Mode"))) \
146 SLIST_CATEGORY(CAT_DEFRAG, "", "CAT_SERVERS", ZCTX(_("SLCAT^Defrag Mode")))
148 #define SLIST_CATEGORY_AUTOCVAR(name) autocvar_menu_slist_categories_##name##_override
149 #define SLIST_CATEGORY(name,enoverride,dioverride,str) \
151 var string SLIST_CATEGORY_AUTOCVAR(name) = enoverride;
153 #undef SLIST_CATEGORY
157 #ifdef IMPLEMENTATION
159 void RegisterSLCategories()
162 #define SLIST_CATEGORY(name,enoverride,dioverride,str) \
163 SET_FIELD_COUNT(name, CATEGORY_FIRST, category_ent_count) \
164 CHECK_MAX_COUNT(name, MAX_CATEGORIES, category_ent_count, "SLIST_CATEGORY") \
166 categories[name - 1] = cat; \
167 cat.classname = "slist_category"; \
168 cat.cat_name = strzone(#name); \
169 cat.cat_enoverride_string = strzone(SLIST_CATEGORY_AUTOCVAR(name)); \
170 cat.cat_dioverride_string = strzone(dioverride); \
171 cat.cat_string = strzone(str);
173 #undef SLIST_CATEGORY
178 #define PROCESS_OVERRIDE(override_string,override_field) \
179 for(i = 0; i < category_ent_count; ++i) \
181 s = categories[i].override_string; \
182 if((s != "") && (s != categories[i].cat_name)) \
185 for(x = 0; x < category_ent_count; ++x) \
186 { if(categories[x].cat_name == s) { \
192 strunzone(categories[i].override_string); \
193 categories[i].override_field = catnum; \
199 "RegisterSLCategories(): Improper override '%s' for category '%s'!\n", \
201 categories[i].cat_name \
205 strunzone(categories[i].override_string); \
206 categories[i].override_field = 0; \
208 PROCESS_OVERRIDE(cat_enoverride_string, cat_enoverride)
209 PROCESS_OVERRIDE(cat_dioverride_string, cat_dioverride)
210 #undef PROCESS_OVERRIDE
213 // Supporting Functions
214 entity RetrieveCategoryEnt(float catnum)
216 if((catnum > 0) && (catnum <= category_ent_count))
218 return categories[catnum - 1];
222 error(sprintf("RetrieveCategoryEnt(%d): Improper category number!\n", catnum));
227 float IsServerInList(string list, string srv)
233 srv = netaddress_resolve(srv, 26000);
236 p = crypto_getidfp(srv);
237 n = tokenize_console(list);
238 for(i = 0; i < n; ++i)
240 if(substring(argv(i), 0, 1) != "[" && strlen(argv(i)) == 44 && strstrofs(argv(i), ".", 0) < 0)
248 if(srv == netaddress_resolve(argv(i), 26000))
255 float CheckCategoryOverride(float cat)
257 entity catent = RetrieveCategoryEnt(cat);
260 float override = (autocvar_menu_slist_categories ? catent.cat_enoverride : catent.cat_dioverride);
261 if(override) { return override; }
266 error(sprintf("CheckCategoryOverride(%d): Improper category number!\n", cat));
271 float CheckCategoryForEntry(float entry)
273 string s, k, v, modtype = "";
274 float j, m, impure = 0;
275 s = gethostcachestring(SLIST_FIELD_QCSTATUS, entry);
276 m = tokenizebyseparator(s, ":");
278 for(j = 2; j < m; ++j)
282 k = substring(argv(j), 0, 1);
283 v = substring(argv(j), 1, -1);
284 if(k == "P") { impure = stof(v); }
285 else if(k == "M") { modtype = strtolower(v); }
288 if(impure > autocvar_menu_slist_purethreshold) { impure = TRUE; }
289 else { impure = FALSE; }
291 // check if this server is favorited
292 if(gethostcachenumber(SLIST_FIELD_ISFAVORITE, entry)) { return CAT_FAVORITED; }
294 // now check if it's recommended
295 switch(autocvar_menu_slist_recommendations)
299 if(IsRecommended(gethostcachestring(SLIST_FIELD_CNAME, entry)))
300 { return CAT_RECOMMENDED; }
308 gethostcachenumber(SLIST_FIELD_NUMHUMANS, entry)
310 (gethostcachenumber(SLIST_FIELD_PING, entry) < 200)
312 { return CAT_RECOMMENDED; }
318 // if not favorited or recommended, check modname
319 if(modtype != "xonotic")
323 // old servers which don't report their mod name are considered modified now
324 case "": { return CAT_MODIFIED; }
326 case "xpm": { return CAT_XPM; }
327 case "minstagib": { return CAT_MINSTAGIB; }
328 case "overkill": { return CAT_OVERKILL; }
329 //case "nix": { return CAT_NIX; }
330 //case "newtoys": { return CAT_NEWTOYS; }
332 // "cts" is allowed as compat, xdf is replacement
334 case "xdf": { return CAT_DEFRAG; }
336 default: { dprint(sprintf("Found strange mod type: %s\n", modtype)); return CAT_MODIFIED; }
340 // must be normal or impure server
341 return (impure ? CAT_MODIFIED : CAT_NORMAL);
344 float CheckItemNumber(float num)
348 if not(category_draw_count) { return num; } // there are no categories to process
350 for(i = 0, n = 1; n <= category_draw_count; ++i, ++n)
352 if(category_item[i] == (num - i)) { return -category_name[i]; }
353 else if(n == category_draw_count) { return (num - n); }
354 else if((num - i) <= category_item[n]) { return (num - n); }
357 // should never hit this point
358 error(sprintf("CheckItemNumber(%d): Function fell through without normal return!\n", num));
362 void XonoticServerList_toggleFavorite(entity me, string srv)
364 string s, s0, s1, s2, srv_resolved, p;
366 srv_resolved = netaddress_resolve(srv, 26000);
367 p = crypto_getidfp(srv_resolved);
368 s = cvar_string("net_slist_favorites");
369 n = tokenize_console(s);
371 for(i = 0; i < n; ++i)
373 if(substring(argv(i), 0, 1) != "[" && strlen(argv(i)) == 44 && strstrofs(argv(i), ".", 0) < 0)
381 if(srv_resolved != netaddress_resolve(argv(i), 26000))
386 s0 = substring(s, 0, argv_end_index(i - 1));
388 s2 = substring(s, argv_start_index(i + 1), -1);
389 if(s0 != "" && s2 != "")
391 cvar_set("net_slist_favorites", strcat(s0, s1, s2));
392 s = cvar_string("net_slist_favorites");
393 n = tokenize_console(s);
404 cvar_set("net_slist_favorites", strcat(s, s1, p));
406 cvar_set("net_slist_favorites", strcat(s, s1, srv));
409 me.refreshServerList(me, REFRESHSERVERLIST_RESORT);
412 void ServerList_Update_favoriteButton(entity btn, entity me)
414 me.favoriteButton.setText(me.favoriteButton,
415 (IsFavorite(me.ipAddressBox.text) ?
416 _("Remove") : _("Bookmark")
421 entity makeXonoticServerList()
424 me = spawnXonoticServerList();
425 me.configureXonoticServerList(me);
428 void XonoticServerList_configureXonoticServerList(entity me)
430 me.configureXonoticListBox(me);
433 #define SLIST_FIELD(suffix,name) SLIST_FIELD_##suffix = gethostcacheindexforkey(name);
441 RegisterSLCategories();
443 void XonoticServerList_setSelected(entity me, float i)
445 // todo: add logic to skip categories
447 save = me.selectedItem;
448 SUPER(XonoticServerList).setSelected(me, i);
450 if(me.selectedItem == save)
456 //if(gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT) != CheckItemNumber(me.nItems))
457 // { error("^1XonoticServerList_setSelected(); ERROR: ^7Host cache viewcount mismatches nItems!\n"); return; } // sorry, it would be wrong
458 // ^ todo: make this work somehow?
460 #define SET_SELECTED_SERVER(cachenum) \
461 if(me.selectedServer) { strunzone(me.selectedServer); } \
462 me.selectedServer = strzone(gethostcachestring(SLIST_FIELD_CNAME, cachenum)); \
463 me.ipAddressBox.setText(me.ipAddressBox, me.selectedServer); \
464 me.ipAddressBox.cursorPos = strlen(me.selectedServer); \
465 me.ipAddressBoxFocused = -1; \
468 num = CheckItemNumber(me.selectedItem);
470 if(num >= 0) { SET_SELECTED_SERVER(num); }
471 else if(save > me.selectedItem)
473 if(me.selectedItem == 0) { return; }
476 if(me.lastClickedTime >= me.lastBumpSelectTime)
478 SUPER(XonoticServerList).setSelected(me, me.selectedItem - 1);
479 num = CheckItemNumber(me.selectedItem);
482 me.lastBumpSelectTime = time;
483 SET_SELECTED_SERVER(num);
488 else if(save < me.selectedItem)
490 if(me.selectedItem == me.nItems) { return; }
493 if(me.lastClickedTime >= me.lastBumpSelectTime)
495 SUPER(XonoticServerList).setSelected(me, me.selectedItem + 1);
496 num = CheckItemNumber(me.selectedItem);
499 me.lastBumpSelectTime = time;
500 SET_SELECTED_SERVER(num);
507 void XonoticServerList_refreshServerList(entity me, float mode)
509 //print("refresh of type ", ftos(mode), "\n");
511 if(mode >= REFRESHSERVERLIST_REFILTER)
515 string s, typestr, modstr;
519 m = strstrofs(s, ":", 0);
522 typestr = substring(s, 0, m);
523 s = substring(s, m + 1, strlen(s) - m - 1);
524 while(substring(s, 0, 1) == " ")
525 s = substring(s, 1, strlen(s) - 1);
530 modstr = cvar_string("menu_slist_modfilter");
532 m = SLIST_MASK_AND - 1;
533 resethostcachemasks();
535 // ping: reject negative ping (no idea why this happens in the first place, engine bug)
536 sethostcachemasknumber(++m, SLIST_FIELD_PING, 0, SLIST_TEST_GREATEREQUAL);
539 if(!me.filterShowFull)
541 sethostcachemasknumber(++m, SLIST_FIELD_FREESLOTS, 1, SLIST_TEST_GREATEREQUAL); // legacy
542 sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, ":S0:", SLIST_TEST_NOTCONTAIN); // g_maxplayers support
546 if(!me.filterShowEmpty)
547 sethostcachemasknumber(++m, SLIST_FIELD_NUMHUMANS, 1, SLIST_TEST_GREATEREQUAL);
549 // gametype filtering
551 sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, strcat(typestr, ":"), SLIST_TEST_STARTSWITH);
556 if(substring(modstr, 0, 1) == "!")
557 sethostcachemaskstring(++m, SLIST_FIELD_MOD, resolvemod(substring(modstr, 1, strlen(modstr) - 1)), SLIST_TEST_NOTEQUAL);
559 sethostcachemaskstring(++m, SLIST_FIELD_MOD, resolvemod(modstr), SLIST_TEST_EQUAL);
563 n = tokenizebyseparator(_Nex_ExtResponseSystem_BannedServers, " ");
564 for(i = 0; i < n; ++i)
566 sethostcachemaskstring(++m, SLIST_FIELD_CNAME, argv(i), SLIST_TEST_NOTSTARTSWITH);
568 m = SLIST_MASK_OR - 1;
571 sethostcachemaskstring(++m, SLIST_FIELD_NAME, s, SLIST_TEST_CONTAINS);
572 sethostcachemaskstring(++m, SLIST_FIELD_MAP, s, SLIST_TEST_CONTAINS);
573 sethostcachemaskstring(++m, SLIST_FIELD_PLAYERS, s, SLIST_TEST_CONTAINS);
574 sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, strcat(s, ":"), SLIST_TEST_STARTSWITH);
578 //listflags |= SLSF_FAVORITES;
579 listflags |= SLSF_CATEGORIES;
580 if(me.currentSortOrder < 0) { listflags |= SLSF_DESCENDING; }
581 sethostcachesort(me.currentSortField, listflags);
585 if(mode >= REFRESHSERVERLIST_ASK)
586 refreshhostcache(mode >= REFRESHSERVERLIST_RESET);
588 void XonoticServerList_focusEnter(entity me)
590 if(time < me.nextRefreshTime)
592 //print("sorry, no refresh yet\n");
595 me.nextRefreshTime = time + 10;
596 me.refreshServerList(me, REFRESHSERVERLIST_ASK);
599 void XonoticServerList_draw(entity me)
601 float i, found, owned, num;
603 if(_Nex_ExtResponseSystem_BannedServersNeedsRefresh)
607 _Nex_ExtResponseSystem_BannedServersNeedsRefresh = 0;
610 if(_Nex_ExtResponseSystem_RecommendedServersNeedsRefresh)
614 _Nex_ExtResponseSystem_RecommendedServersNeedsRefresh = 0;
617 if(me.currentSortField == -1)
619 me.setSortOrder(me, SLIST_FIELD_PING, +1);
620 me.refreshServerList(me, REFRESHSERVERLIST_RESET);
622 else if(me.needsRefresh == 1)
624 me.needsRefresh = 2; // delay by one frame to make sure "slist" has been executed
626 else if(me.needsRefresh == 2)
629 me.refreshServerList(me, REFRESHSERVERLIST_REFILTER);
631 else if(me.needsRefresh == 3)
634 me.refreshServerList(me, REFRESHSERVERLIST_RESORT);
637 owned = ((me.selectedServer == me.ipAddressBox.text) && (me.ipAddressBox.text != ""));
639 for(i = 0; i < category_draw_count; ++i) { category_name[i] = -1; category_item[i] = -1; }
640 category_draw_count = 0;
642 if(autocvar_menu_slist_categories >= 0) // if less than 0, don't even draw a category heading for favorites
644 float itemcount = gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT);
645 me.nItems = itemcount;
647 //float visible = floor(me.scrollPos / me.itemHeight);
648 // ^ unfortunately no such optimization can be made-- we must process through the
649 // entire list, otherwise there is no way to know which item is first in its category.
652 for(i = 0; i < itemcount; ++i) // FIXME this loop is TOTALLY unacceptable (O(servers)). Make it O(categories * log(servers)). Yes, that is possible.
654 cat = gethostcachenumber(SLIST_FIELD_CATEGORY, i);
657 if(category_draw_count == 0)
659 category_name[category_draw_count] = cat;
660 category_item[category_draw_count] = i;
661 ++category_draw_count;
667 for(x = 0; x < category_draw_count; ++x) { if(cat == category_name[x]) { found = 1; } }
670 category_name[category_draw_count] = cat;
671 category_item[category_draw_count] = i;
672 ++category_draw_count;
678 if(autocvar_menu_slist_categories_onlyifmultiple && (category_draw_count == 1))
680 category_name[0] = -1;
681 category_item[0] = -1;
682 category_draw_count = 0;
683 me.nItems = itemcount;
686 else { me.nItems = gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT); }
688 me.connectButton.disabled = ((me.nItems == 0) && (me.ipAddressBox.text == ""));
689 me.infoButton.disabled = ((me.nItems == 0) || !owned);
690 me.favoriteButton.disabled = ((me.nItems == 0) && (me.ipAddressBox.text == ""));
693 if(me.selectedServer)
695 for(i = 0; i < me.nItems; ++i)
697 num = CheckItemNumber(i);
700 if(gethostcachestring(SLIST_FIELD_CNAME, num) == me.selectedServer)
702 if(i != me.selectedItem)
704 me.lastClickedServer = -1;
717 if(me.selectedItem >= me.nItems) { me.selectedItem = me.nItems - 1; }
718 if(me.selectedServer) { strunzone(me.selectedServer); }
720 num = CheckItemNumber(me.selectedItem);
721 if(num >= 0) { me.selectedServer = strzone(gethostcachestring(SLIST_FIELD_CNAME, num)); }
727 if(me.selectedServer != me.ipAddressBox.text)
729 me.ipAddressBox.setText(me.ipAddressBox, me.selectedServer);
730 me.ipAddressBox.cursorPos = strlen(me.selectedServer);
731 me.ipAddressBoxFocused = -1;
735 if(me.ipAddressBoxFocused != me.ipAddressBox.focused)
737 if(me.ipAddressBox.focused || me.ipAddressBoxFocused < 0)
738 ServerList_Update_favoriteButton(NULL, me);
739 me.ipAddressBoxFocused = me.ipAddressBox.focused;
742 SUPER(XonoticServerList).draw(me);
744 void ServerList_PingSort_Click(entity btn, entity me)
746 me.setSortOrder(me, SLIST_FIELD_PING, +1);
748 void ServerList_NameSort_Click(entity btn, entity me)
750 me.setSortOrder(me, SLIST_FIELD_NAME, -1); // why?
752 void ServerList_MapSort_Click(entity btn, entity me)
754 me.setSortOrder(me, SLIST_FIELD_MAP, -1); // why?
756 void ServerList_PlayerSort_Click(entity btn, entity me)
758 me.setSortOrder(me, SLIST_FIELD_NUMHUMANS, -1);
760 void ServerList_TypeSort_Click(entity btn, entity me)
765 m = strstrofs(s, ":", 0);
768 s = substring(s, 0, m);
769 while(substring(s, m+1, 1) == " ") // skip spaces
775 for(i = 1; ; i *= 2) // 20 modes ought to be enough for anyone
777 t = MapInfo_Type_ToString(i);
779 if(t == "") // it repeats (default case)
782 // choose the first one
783 s = MapInfo_Type_ToString(1);
788 // the type was found
789 // choose the next one
790 s = MapInfo_Type_ToString(i * 2);
792 s = MapInfo_Type_ToString(1);
799 s = strcat(s, substring(me.filterString, m+1, strlen(me.filterString) - m - 1));
801 me.controlledTextbox.setText(me.controlledTextbox, s);
802 me.controlledTextbox.keyDown(me.controlledTextbox, K_END, 0, 0);
803 me.controlledTextbox.keyUp(me.controlledTextbox, K_END, 0, 0);
804 //ServerList_Filter_Change(me.controlledTextbox, me);
806 void ServerList_Filter_Change(entity box, entity me)
809 strunzone(me.filterString);
811 me.filterString = strzone(box.text);
813 me.filterString = string_null;
814 me.refreshServerList(me, REFRESHSERVERLIST_REFILTER);
816 me.ipAddressBox.setText(me.ipAddressBox, "");
817 me.ipAddressBox.cursorPos = 0;
818 me.ipAddressBoxFocused = -1;
820 void ServerList_Categories_Click(entity box, entity me)
822 box.setChecked(box, autocvar_menu_slist_categories = !autocvar_menu_slist_categories);
823 ///refreshhostcache(TRUE);
825 //cvar_set("net_slist_pause", "0");
826 //Destroy_Category_Entities();
827 //CALL_ACCUMULATED_FUNCTION(RegisterSLCategories);
828 //me.refreshServerList(me, REFRESHSERVERLIST_REFILTER);
830 me.refreshServerList(me, REFRESHSERVERLIST_RESORT);
832 me.ipAddressBox.setText(me.ipAddressBox, "");
833 me.ipAddressBox.cursorPos = 0;
834 me.ipAddressBoxFocused = -1;
836 void ServerList_ShowEmpty_Click(entity box, entity me)
838 box.setChecked(box, me.filterShowEmpty = !me.filterShowEmpty);
839 me.refreshServerList(me, REFRESHSERVERLIST_REFILTER);
841 me.ipAddressBox.setText(me.ipAddressBox, "");
842 me.ipAddressBox.cursorPos = 0;
843 me.ipAddressBoxFocused = -1;
845 void ServerList_ShowFull_Click(entity box, entity me)
847 box.setChecked(box, me.filterShowFull = !me.filterShowFull);
848 me.refreshServerList(me, REFRESHSERVERLIST_REFILTER);
850 me.ipAddressBox.setText(me.ipAddressBox, "");
851 me.ipAddressBox.cursorPos = 0;
852 me.ipAddressBoxFocused = -1;
854 void XonoticServerList_setSortOrder(entity me, float fld, float direction)
856 if(me.currentSortField == fld)
857 direction = -me.currentSortOrder;
858 me.currentSortOrder = direction;
859 me.currentSortField = fld;
860 me.sortButton1.forcePressed = (fld == SLIST_FIELD_PING);
861 me.sortButton2.forcePressed = (fld == SLIST_FIELD_NAME);
862 me.sortButton3.forcePressed = (fld == SLIST_FIELD_MAP);
863 me.sortButton4.forcePressed = 0;
864 me.sortButton5.forcePressed = (fld == SLIST_FIELD_NUMHUMANS);
866 if(me.selectedServer)
867 strunzone(me.selectedServer);
868 me.selectedServer = string_null;
869 me.refreshServerList(me, REFRESHSERVERLIST_REFILTER);
871 void XonoticServerList_positionSortButton(entity me, entity btn, float theOrigin, float theSize, string theTitle, void(entity, entity) theFunc)
873 vector originInLBSpace, sizeInLBSpace;
874 originInLBSpace = eY * (-me.itemHeight);
875 sizeInLBSpace = eY * me.itemHeight + eX * (1 - me.controlWidth);
877 vector originInDialogSpace, sizeInDialogSpace;
878 originInDialogSpace = boxToGlobal(originInLBSpace, me.Container_origin, me.Container_size);
879 sizeInDialogSpace = boxToGlobalSize(sizeInLBSpace, me.Container_size);
881 btn.Container_origin_x = originInDialogSpace_x + sizeInDialogSpace_x * theOrigin;
882 btn.Container_size_x = sizeInDialogSpace_x * theSize;
883 btn.setText(btn, theTitle);
884 btn.onClick = theFunc;
885 btn.onClickEntity = me;
888 void XonoticServerList_resizeNotify(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
890 SUPER(XonoticServerList).resizeNotify(me, relOrigin, relSize, absOrigin, absSize);
892 me.realFontSize_y = me.fontSize / (absSize_y * me.itemHeight);
893 me.realFontSize_x = me.fontSize / (absSize_x * (1 - me.controlWidth));
894 me.realUpperMargin = 0.5 * (1 - me.realFontSize_y);
896 me.columnIconsOrigin = 0;
897 me.columnIconsSize = me.realFontSize_x * 4 * me.iconsSizeFactor;
898 me.columnPingSize = me.realFontSize_x * 3;
899 me.columnMapSize = me.realFontSize_x * 10;
900 me.columnTypeSize = me.realFontSize_x * 4;
901 me.columnPlayersSize = me.realFontSize_x * 5;
902 me.columnNameSize = 1 - me.columnPlayersSize - me.columnMapSize - me.columnPingSize - me.columnIconsSize - me.columnTypeSize - 5 * me.realFontSize_x;
903 me.columnPingOrigin = me.columnIconsOrigin + me.columnIconsSize + me.realFontSize_x;
904 me.columnNameOrigin = me.columnPingOrigin + me.columnPingSize + me.realFontSize_x;
905 me.columnMapOrigin = me.columnNameOrigin + me.columnNameSize + me.realFontSize_x;
906 me.columnTypeOrigin = me.columnMapOrigin + me.columnMapSize + me.realFontSize_x;
907 me.columnPlayersOrigin = me.columnTypeOrigin + me.columnTypeSize + me.realFontSize_x;
909 me.positionSortButton(me, me.sortButton1, me.columnPingOrigin, me.columnPingSize, _("Ping"), ServerList_PingSort_Click);
910 me.positionSortButton(me, me.sortButton2, me.columnNameOrigin, me.columnNameSize, _("Host name"), ServerList_NameSort_Click);
911 me.positionSortButton(me, me.sortButton3, me.columnMapOrigin, me.columnMapSize, _("Map"), ServerList_MapSort_Click);
912 me.positionSortButton(me, me.sortButton4, me.columnTypeOrigin, me.columnTypeSize, _("Type"), ServerList_TypeSort_Click);
913 me.positionSortButton(me, me.sortButton5, me.columnPlayersOrigin, me.columnPlayersSize, _("Players"), ServerList_PlayerSort_Click);
916 f = me.currentSortField;
919 me.currentSortField = -1;
920 me.setSortOrder(me, f, me.currentSortOrder); // force resetting the sort order
923 void ServerList_Connect_Click(entity btn, entity me)
925 localcmd(sprintf("connect %s\n",
926 ((me.ipAddressBox.text != "") ?
927 me.ipAddressBox.text : me.selectedServer
931 void ServerList_Favorite_Click(entity btn, entity me)
934 ipstr = netaddress_resolve(me.ipAddressBox.text, 26000);
937 me.toggleFavorite(me, me.ipAddressBox.text);
938 me.ipAddressBoxFocused = -1;
941 void ServerList_Info_Click(entity btn, entity me)
943 main.serverInfoDialog.loadServerInfo(main.serverInfoDialog, CheckItemNumber(me.selectedItem));
944 DialogOpenButton_Click(me, main.serverInfoDialog);
946 void XonoticServerList_clickListBoxItem(entity me, float i, vector where)
948 float num = CheckItemNumber(i);
951 if(num == me.lastClickedServer)
952 if(time < me.lastClickedTime + 0.3)
955 ServerList_Connect_Click(NULL, me);
957 me.lastClickedServer = num;
958 me.lastClickedTime = time;
961 void XonoticServerList_drawListBoxItem(entity me, float i, vector absSize, float isSelected)
963 // layout: Ping, Server name, Map name, NP, TP, MP
968 float m, pure, freeslots, j, sflags;
969 string s, typestr, versionstr, k, v, modname;
971 float item = CheckItemNumber(i);
972 //print(sprintf("time: %f, i: %d, item: %d, nitems: %d\n", time, i, item, me.nItems));
976 entity catent = RetrieveCategoryEnt(-item);
980 eY * me.realUpperMargin
982 eX * (me.columnNameOrigin + (me.columnNameSize - draw_TextWidth(catent.cat_string, 0, me.realFontSize)) * 0.5),
994 draw_Fill('0 0 0', '1 1 0', SKINCOLOR_LISTBOX_SELECTED, SKINALPHA_LISTBOX_SELECTED);
996 s = gethostcachestring(SLIST_FIELD_QCSTATUS, item);
997 m = tokenizebyseparator(s, ":");
1002 versionstr = argv(1);
1008 for(j = 2; j < m; ++j)
1012 k = substring(argv(j), 0, 1);
1013 v = substring(argv(j), 1, -1);
1017 freeslots = stof(v);
1024 #ifdef COMPAT_NO_MOD_IS_XONOTIC
1026 modname = "Xonotic";
1030 SLIST_FIELD_MOD = gethostcacheindexforkey("mod");
1031 s = gethostcachestring(SLIST_FIELD_MOD, item);
1033 if(modname == "Xonotic")
1037 // list the mods here on which the pure server check actually works
1038 if(modname != "Xonotic")
1039 if(modname != "MinstaGib")
1040 if(modname != "CTS")
1041 if(modname != "NIX")
1042 if(modname != "NewToys")
1045 if(gethostcachenumber(SLIST_FIELD_FREESLOTS, item) <= 0)
1046 theAlpha = SKINALPHA_SERVERLIST_FULL;
1047 else if(freeslots == 0)
1048 theAlpha = SKINALPHA_SERVERLIST_FULL; // g_maxplayers support
1049 else if not(gethostcachenumber(SLIST_FIELD_NUMHUMANS, item))
1050 theAlpha = SKINALPHA_SERVERLIST_EMPTY;
1054 p = gethostcachenumber(SLIST_FIELD_PING, item);
1056 #define PING_MED 200
1057 #define PING_HIGH 500
1059 theColor = SKINCOLOR_SERVERLIST_LOWPING + (SKINCOLOR_SERVERLIST_MEDPING - SKINCOLOR_SERVERLIST_LOWPING) * (p / PING_LOW);
1060 else if(p < PING_MED)
1061 theColor = SKINCOLOR_SERVERLIST_MEDPING + (SKINCOLOR_SERVERLIST_HIGHPING - SKINCOLOR_SERVERLIST_MEDPING) * ((p - PING_LOW) / (PING_MED - PING_LOW));
1062 else if(p < PING_HIGH)
1064 theColor = SKINCOLOR_SERVERLIST_HIGHPING;
1065 theAlpha *= 1 + (SKINALPHA_SERVERLIST_HIGHPING - 1) * ((p - PING_MED) / (PING_HIGH - PING_MED));
1070 theAlpha *= SKINALPHA_SERVERLIST_HIGHPING;
1073 if(gethostcachenumber(SLIST_FIELD_ISFAVORITE, item))
1075 theColor = theColor * (1 - SKINALPHA_SERVERLIST_FAVORITE) + SKINCOLOR_SERVERLIST_FAVORITE * SKINALPHA_SERVERLIST_FAVORITE;
1076 theAlpha = theAlpha * (1 - SKINALPHA_SERVERLIST_FAVORITE) + SKINALPHA_SERVERLIST_FAVORITE;
1079 s = gethostcachestring(SLIST_FIELD_CNAME, item);
1082 if(substring(s, 0, 1) == "[")
1087 else if(strstrofs("0123456789", substring(s, 0, 1), 0) >= 0)
1093 q = stof(substring(crypto_getencryptlevel(s), 0, 1));
1094 if((q <= 0 && cvar("crypto_aeslevel") >= 3) || (q >= 3 && cvar("crypto_aeslevel") <= 0))
1096 theColor = SKINCOLOR_SERVERLIST_IMPOSSIBLE;
1097 theAlpha = SKINALPHA_SERVERLIST_IMPOSSIBLE;
1102 if(cvar("crypto_aeslevel") >= 2)
1107 if(cvar("crypto_aeslevel") >= 1)
1117 // 2: AES recommended but not available
1118 // 3: AES possible and will be used
1119 // 4: AES recommended and will be used
1125 vector iconSize = '0 0 0';
1126 iconSize_y = me.realFontSize_y * me.iconsSizeFactor;
1127 iconSize_x = me.realFontSize_x * me.iconsSizeFactor;
1129 vector iconPos = '0 0 0';
1130 iconPos_x = (me.columnIconsSize - 3 * iconSize_x) * 0.5;
1131 iconPos_y = (1 - iconSize_y) * 0.5;
1135 if not(me.seenIPv4 && me.seenIPv6)
1137 iconPos_x += iconSize_x * 0.5;
1139 else if(me.seenIPv4 && me.seenIPv6)
1143 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_ipv6"), 0); // PRECACHE_PIC_MIPMAP
1145 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_ipv4"), 0); // PRECACHE_PIC_MIPMAP
1147 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1148 iconPos_x += iconSize_x;
1153 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_aeslevel", ftos(q)), 0); // PRECACHE_PIC_MIPMAP
1154 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1156 iconPos_x += iconSize_x;
1158 if(modname == "Xonotic")
1162 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_pure1"), PRECACHE_PIC_MIPMAP);
1163 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1168 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_mod_", modname), PRECACHE_PIC_MIPMAP);
1169 if(draw_PictureSize(n) == '0 0 0')
1170 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_mod_"), PRECACHE_PIC_MIPMAP);
1172 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1174 draw_Picture(iconPos, n, iconSize, '1 1 1', SKINALPHA_SERVERLIST_ICON_NONPURE);
1176 iconPos_x += iconSize_x;
1178 if(sflags >= 0 && (sflags & SERVERFLAG_PLAYERSTATS))
1180 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_stats1"), 0); // PRECACHE_PIC_MIPMAP
1181 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1183 iconPos_x += iconSize_x;
1191 draw_Text(me.realUpperMargin * eY + (me.columnPingOrigin + me.columnPingSize - draw_TextWidth(s, 0, me.realFontSize)) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1194 s = draw_TextShortenToWidth(gethostcachestring(SLIST_FIELD_NAME, item), me.columnNameSize, 0, me.realFontSize);
1195 draw_Text(me.realUpperMargin * eY + me.columnNameOrigin * eX, s, me.realFontSize, theColor, theAlpha, 0);
1198 s = draw_TextShortenToWidth(gethostcachestring(SLIST_FIELD_MAP, item), me.columnMapSize, 0, me.realFontSize);
1199 draw_Text(me.realUpperMargin * eY + (me.columnMapOrigin + (me.columnMapSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1202 s = draw_TextShortenToWidth(typestr, me.columnTypeSize, 0, me.realFontSize);
1203 draw_Text(me.realUpperMargin * eY + (me.columnTypeOrigin + (me.columnTypeSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1205 // server playercount
1206 s = strcat(ftos(gethostcachenumber(SLIST_FIELD_NUMHUMANS, item)), "/", ftos(gethostcachenumber(SLIST_FIELD_MAXPLAYERS, item)));
1207 draw_Text(me.realUpperMargin * eY + (me.columnPlayersOrigin + (me.columnPlayersSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1210 float XonoticServerList_keyDown(entity me, float scan, float ascii, float shift)
1212 float i = CheckItemNumber(me.selectedItem);
1215 org = boxToGlobal(eY * (me.selectedItem * me.itemHeight - me.scrollPos), me.origin, me.size);
1216 sz = boxToGlobalSize(eY * me.itemHeight + eX * (1 - me.controlWidth), me.size);
1218 me.lastBumpSelectTime = 0;
1220 if(scan == K_ENTER || scan == K_KP_ENTER)
1222 ServerList_Connect_Click(NULL, me);
1225 else if(scan == K_MOUSE2 || scan == K_SPACE)
1227 if((me.nItems != 0) && (i >= 0))
1229 main.serverInfoDialog.loadServerInfo(main.serverInfoDialog, i);
1230 DialogOpenButton_Click_withCoords(me, main.serverInfoDialog, org, sz);
1235 else if(scan == K_INS || scan == K_MOUSE3 || scan == K_KP_INS)
1237 if((me.nItems != 0) && (i >= 0))
1239 me.toggleFavorite(me, me.selectedServer);
1240 me.ipAddressBoxFocused = -1;
1245 else if(SUPER(XonoticServerList).keyDown(me, scan, ascii, shift))
1247 else if(!me.controlledTextbox)
1250 return me.controlledTextbox.keyDown(me.controlledTextbox, scan, ascii, shift);