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 const float REFRESHSERVERLIST_RESORT = 0; // sort the server list again to update for changes to e.g. favorite status, categories
65 const float REFRESHSERVERLIST_REFILTER = 1; // ..., also update filter and sort criteria
66 const float REFRESHSERVERLIST_ASK = 2; // ..., also suggest querying servers now
67 const float REFRESHSERVERLIST_RESET = 3; // ..., also clear the list first
69 var float autocvar_menu_slist_categories = TRUE;
70 var float autocvar_menu_slist_categories_onlyifmultiple = TRUE;
71 var float autocvar_menu_slist_purethreshold = 10;
72 var string autocvar_menu_slist_recommended = "76.124.107.5:26004";
74 // server cache fields
75 #define SLIST_FIELDS \
76 SLIST_FIELD(CNAME, "cname") \
77 SLIST_FIELD(PING, "ping") \
78 SLIST_FIELD(GAME, "game") \
79 SLIST_FIELD(MOD, "mod") \
80 SLIST_FIELD(MAP, "map") \
81 SLIST_FIELD(NAME, "name") \
82 SLIST_FIELD(MAXPLAYERS, "maxplayers") \
83 SLIST_FIELD(NUMPLAYERS, "numplayers") \
84 SLIST_FIELD(NUMHUMANS, "numhumans") \
85 SLIST_FIELD(NUMBOTS, "numbots") \
86 SLIST_FIELD(PROTOCOL, "protocol") \
87 SLIST_FIELD(FREESLOTS, "freeslots") \
88 SLIST_FIELD(PLAYERS, "players") \
89 SLIST_FIELD(QCSTATUS, "qcstatus") \
90 SLIST_FIELD(CATEGORY, "category") \
91 SLIST_FIELD(ISFAVORITE, "isfavorite")
93 #define SLIST_FIELD(suffix,name) float SLIST_FIELD_##suffix;
98 float SLSF_DESCENDING = 1;
99 float SLSF_FAVORITES = 2;
100 float SLSF_CATEGORIES = 4;
102 float Get_Cat_Num_FromString(string input);
103 entity Get_Cat_Ent(float catnum);
105 float IsServerInList(string list, string srv);
106 #define IsFavorite(srv) IsServerInList(cvar_string("net_slist_favorites"), srv)
107 #define IsRecommended(srv) IsServerInList(cvar_string("menu_slist_recommended"), srv) // todo: use update notification instead of cvar
109 float CheckCategoryOverride(float cat);
110 float CheckCategoryForEntry(float entry);
111 float m_getserverlistentrycategory(float entry) { return CheckCategoryOverride(CheckCategoryForEntry(entry)); }
113 // fields for category entities
114 #define MAX_CATEGORIES 9
115 #define CATEGORY_FIRST 1
116 entity categories[MAX_CATEGORIES];
117 float category_ent_count;
120 .string cat_enoverride_string;
121 .string cat_dioverride_string;
122 .float cat_enoverride;
123 .float cat_dioverride;
125 // fields for drawing categories
126 float category_name[MAX_CATEGORIES];
127 float category_item[MAX_CATEGORIES];
128 float category_draw_count;
131 SLIST_CATEGORY(CAT_FAVORITED, "", "", _("Favorites")) \
132 SLIST_CATEGORY(CAT_RECOMMENDED, "", "CAT_SERVERS", _("Recommended")) \
133 SLIST_CATEGORY(CAT_NORMAL, "", "CAT_SERVERS", _("Normal Servers")) \
134 SLIST_CATEGORY(CAT_SERVERS, "CAT_NORMAL", "CAT_SERVERS", _("Servers")) \
135 SLIST_CATEGORY(CAT_XPM, "CAT_NORMAL", "CAT_SERVERS", _("Competitive Mode")) \
136 SLIST_CATEGORY(CAT_MODIFIED, "", "CAT_SERVERS", _("Modified Servers")) \
137 SLIST_CATEGORY(CAT_OVERKILL, "", "CAT_SERVERS", _("Overkill Mode")) \
138 SLIST_CATEGORY(CAT_MINSTAGIB, "", "CAT_SERVERS", _("MinstaGib Mode")) \
139 SLIST_CATEGORY(CAT_DEFRAG, "", "CAT_SERVERS", _("Defrag Mode"))
141 // C is stupid, must use extra macro for concatenation
142 #define SLIST_ADD_CAT_CVAR(name,default) var string autocvar_menu_slist_categories_##name##_override = default;
143 #define SLIST_CATEGORY(name,enoverride,dioverride,string) \
144 SLIST_ADD_CAT_CVAR(name, enoverride) \
146 void RegisterSLCategory_##name() \
148 SET_FIELD_COUNT(name, CATEGORY_FIRST, category_ent_count) \
149 CHECK_MAX_COUNT(name, MAX_CATEGORIES, category_ent_count, "SLIST_CATEGORY") \
150 entity cat = spawn(); \
151 categories[name - 1] = cat; \
152 cat.classname = "slist_category"; \
153 cat.cat_name = strzone(#name); \
154 cat.cat_enoverride_string = strzone(autocvar_menu_slist_categories_##name##_override); \
155 cat.cat_dioverride_string = strzone(dioverride); \
156 cat.cat_string = strzone(string); \
158 ACCUMULATE_FUNCTION(RegisterSLCategories, RegisterSLCategory_##name);
162 void RegisterSLCategories_Done()
167 #define PROCESS_OVERRIDE(override_string,override_field) \
168 for(i = 0; i < category_ent_count; ++i) \
170 s = categories[i].override_string; \
171 if((s != "") && (s != categories[i].cat_name)) \
173 catnum = Get_Cat_Num_FromString(s); \
176 strunzone(categories[i].override_string); \
177 categories[i].override_field = catnum; \
181 strunzone(categories[i].override_string); \
182 categories[i].override_field = 0; \
185 PROCESS_OVERRIDE(cat_enoverride_string, cat_enoverride)
186 PROCESS_OVERRIDE(cat_dioverride_string, cat_dioverride)
187 #undef PROCESS_OVERRIDE
189 ACCUMULATE_FUNCTION(RegisterSLCategories, RegisterSLCategories_Done);
191 #undef SLIST_ADD_CAT_CVAR
192 #undef SLIST_CATEGORY
195 void ServerList_Connect_Click(entity btn, entity me);
196 void ServerList_Categories_Click(entity box, entity me);
197 void ServerList_ShowEmpty_Click(entity box, entity me);
198 void ServerList_ShowFull_Click(entity box, entity me);
199 void ServerList_Filter_Change(entity box, entity me);
200 void ServerList_Favorite_Click(entity btn, entity me);
201 void ServerList_Info_Click(entity btn, entity me);
202 void ServerList_Update_favoriteButton(entity btn, entity me);
206 #ifdef IMPLEMENTATION
208 // Supporting Functions
209 float Get_Cat_Num_FromString(string input)
212 for(i = 0; i < category_ent_count; ++i) { if(categories[i].cat_name == input) { return (i + 1); } }
213 print(sprintf("Get_Cat_Num_FromString('%s'): Improper category name!\n", input));
216 entity Get_Cat_Ent(float catnum)
218 if((catnum > 0) && (catnum <= category_ent_count))
220 return categories[catnum - 1];
224 error(sprintf("Get_Cat_Ent(%d): Improper category number!\n", catnum));
230 float IsServerInList(string list, string srv)
236 srv = netaddress_resolve(srv, 26000);
239 p = crypto_getidfp(srv);
240 n = tokenize_console(list);
241 for(i = 0; i < n; ++i)
243 if(substring(argv(i), 0, 1) != "[" && strlen(argv(i)) == 44 && strstrofs(argv(i), ".", 0) < 0)
251 if(srv == netaddress_resolve(argv(i), 26000))
258 float CheckCategoryOverride(float cat)
260 entity catent = Get_Cat_Ent(cat);
263 float override = (autocvar_menu_slist_categories ? catent.cat_enoverride : catent.cat_dioverride);
264 if(override) { return override; }
269 error(sprintf("CheckCategoryOverride(%d): Improper category number!\n", cat));
274 float CheckCategoryForEntry(float entry)
276 string s, k, v, modtype = "";
277 float j, m, impure = 0;
278 s = gethostcachestring(SLIST_FIELD_QCSTATUS, entry);
279 m = tokenizebyseparator(s, ":");
281 for(j = 2; j < m; ++j)
285 k = substring(argv(j), 0, 1);
286 v = substring(argv(j), 1, -1);
287 if(k == "P") { impure = stof(v); }
288 else if(k == "M") { modtype = strtolower(v); }
291 //print(sprintf("modtype = %s\n", modtype));
293 if(impure > autocvar_menu_slist_purethreshold) { impure = TRUE; }
294 else { impure = FALSE; }
296 if(gethostcachenumber(SLIST_FIELD_ISFAVORITE, entry)) { return CAT_FAVORITED; }
297 if(IsRecommended(gethostcachestring(SLIST_FIELD_CNAME, entry))) { return CAT_RECOMMENDED; }
298 else if(modtype != "xonotic")
302 // old servers which don't report their mod name are considered modified now
303 case "": { return CAT_MODIFIED; }
305 case "xpm": { return CAT_XPM; }
306 case "minstagib": { return CAT_MINSTAGIB; }
307 case "overkill": { return CAT_OVERKILL; }
309 // "cts" is allowed as compat, xdf is replacement
311 case "xdf": { return CAT_DEFRAG; }
313 //if(modname != "CTS")
314 //if(modname != "NIX")
315 //if(modname != "NewToys")
317 default: { dprint(sprintf("Found strange mod type: %s\n", modtype)); return CAT_MODIFIED; }
320 else { return (impure ? CAT_MODIFIED : CAT_NORMAL); }
322 // should never hit this point
323 error(sprintf("CheckCategoryForEntry(%d): Function fell through without normal return!\n", entry));
327 float XonoticServerList_MapItems(float num)
331 if not(category_draw_count) { return num; } // there are no categories to process
333 for(i = 0, n = 1; n <= category_draw_count; ++i, ++n)
335 //print(sprintf("num: %d, i: %d, category_draw_count: %d, category_item[i]: %d\n", num, i, category_draw_count, category_item[i]));
336 if(category_item[i] == (num - i)) { /*print("inserting cat... \\/\n");*/ return -category_name[i]; }
337 else if(n == category_draw_count) { /*print("end item... \\/\n");*/ return (num - n); }
338 else if((num - i) <= category_item[n]) { /*print("next item... \\/\n");*/ return (num - n); }
341 // should never hit this point
342 error("wtf XonoticServerList_MapItems fail?");
346 void XonoticServerList_toggleFavorite(entity me, string srv)
348 string s, s0, s1, s2, srv_resolved, p;
350 srv_resolved = netaddress_resolve(srv, 26000);
351 p = crypto_getidfp(srv_resolved);
352 s = cvar_string("net_slist_favorites");
353 n = tokenize_console(s);
355 for(i = 0; i < n; ++i)
357 if(substring(argv(i), 0, 1) != "[" && strlen(argv(i)) == 44 && strstrofs(argv(i), ".", 0) < 0)
365 if(srv_resolved != netaddress_resolve(argv(i), 26000))
370 s0 = substring(s, 0, argv_end_index(i - 1));
372 s2 = substring(s, argv_start_index(i + 1), -1);
373 if(s0 != "" && s2 != "")
375 cvar_set("net_slist_favorites", strcat(s0, s1, s2));
376 s = cvar_string("net_slist_favorites");
377 n = tokenize_console(s);
388 cvar_set("net_slist_favorites", strcat(s, s1, p));
390 cvar_set("net_slist_favorites", strcat(s, s1, srv));
393 me.refreshServerList(me, REFRESHSERVERLIST_RESORT);
396 void ServerList_Update_favoriteButton(entity btn, entity me)
398 me.favoriteButton.setText(me.favoriteButton,
399 (IsFavorite(me.ipAddressBox.text) ?
400 _("Remove") : _("Bookmark")
405 entity makeXonoticServerList()
408 me = spawnXonoticServerList();
409 me.configureXonoticServerList(me);
412 void XonoticServerList_configureXonoticServerList(entity me)
414 me.configureXonoticListBox(me);
417 #define SLIST_FIELD(suffix,name) SLIST_FIELD_##suffix = gethostcacheindexforkey(name);
424 void XonoticServerList_setSelected(entity me, float i)
426 // todo: add logic to skip categories
428 save = me.selectedItem;
429 SUPER(XonoticServerList).setSelected(me, i);
431 if(me.selectedItem == save)
437 //if(gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT) != XonoticServerList_MapItems(me.nItems))
438 // { error("^1XonoticServerList_setSelected(); ERROR: ^7Host cache viewcount mismatches nItems!\n"); return; } // sorry, it would be wrong
439 // ^ todo: make this work somehow?
441 #define SET_SELECTED_SERVER(cachenum) \
442 if(me.selectedServer) { strunzone(me.selectedServer); } \
443 me.selectedServer = strzone(gethostcachestring(SLIST_FIELD_CNAME, cachenum)); \
444 me.ipAddressBox.setText(me.ipAddressBox, me.selectedServer); \
445 me.ipAddressBox.cursorPos = strlen(me.selectedServer); \
446 me.ipAddressBoxFocused = -1; \
449 num = XonoticServerList_MapItems(me.selectedItem);
451 if(num >= 0) { SET_SELECTED_SERVER(num); }
452 else if(save > me.selectedItem)
454 if(me.selectedItem == 0) { return; }
457 if(me.lastClickedTime >= me.lastBumpSelectTime)
459 SUPER(XonoticServerList).setSelected(me, me.selectedItem - 1);
460 num = XonoticServerList_MapItems(me.selectedItem);
463 me.lastBumpSelectTime = time;
464 SET_SELECTED_SERVER(num);
469 else if(save < me.selectedItem)
471 if(me.selectedItem == me.nItems) { return; }
474 if(me.lastClickedTime >= me.lastBumpSelectTime)
476 SUPER(XonoticServerList).setSelected(me, me.selectedItem + 1);
477 num = XonoticServerList_MapItems(me.selectedItem);
480 me.lastBumpSelectTime = time;
481 SET_SELECTED_SERVER(num);
488 void XonoticServerList_refreshServerList(entity me, float mode)
490 //print("refresh of type ", ftos(mode), "\n");
492 if(mode >= REFRESHSERVERLIST_REFILTER)
496 string s, typestr, modstr;
500 m = strstrofs(s, ":", 0);
503 typestr = substring(s, 0, m);
504 s = substring(s, m + 1, strlen(s) - m - 1);
505 while(substring(s, 0, 1) == " ")
506 s = substring(s, 1, strlen(s) - 1);
511 modstr = cvar_string("menu_slist_modfilter");
513 m = SLIST_MASK_AND - 1;
514 resethostcachemasks();
516 // ping: reject negative ping (no idea why this happens in the first place, engine bug)
517 sethostcachemasknumber(++m, SLIST_FIELD_PING, 0, SLIST_TEST_GREATEREQUAL);
520 if(!me.filterShowFull)
522 sethostcachemasknumber(++m, SLIST_FIELD_FREESLOTS, 1, SLIST_TEST_GREATEREQUAL); // legacy
523 sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, ":S0:", SLIST_TEST_NOTCONTAIN); // g_maxplayers support
527 if(!me.filterShowEmpty)
528 sethostcachemasknumber(++m, SLIST_FIELD_NUMHUMANS, 1, SLIST_TEST_GREATEREQUAL);
530 // gametype filtering
532 sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, strcat(typestr, ":"), SLIST_TEST_STARTSWITH);
537 if(substring(modstr, 0, 1) == "!")
538 sethostcachemaskstring(++m, SLIST_FIELD_MOD, resolvemod(substring(modstr, 1, strlen(modstr) - 1)), SLIST_TEST_NOTEQUAL);
540 sethostcachemaskstring(++m, SLIST_FIELD_MOD, resolvemod(modstr), SLIST_TEST_EQUAL);
544 n = tokenizebyseparator(_Nex_ExtResponseSystem_BannedServers, " ");
545 for(i = 0; i < n; ++i)
547 sethostcachemaskstring(++m, SLIST_FIELD_CNAME, argv(i), SLIST_TEST_NOTSTARTSWITH);
549 m = SLIST_MASK_OR - 1;
552 sethostcachemaskstring(++m, SLIST_FIELD_NAME, s, SLIST_TEST_CONTAINS);
553 sethostcachemaskstring(++m, SLIST_FIELD_MAP, s, SLIST_TEST_CONTAINS);
554 sethostcachemaskstring(++m, SLIST_FIELD_PLAYERS, s, SLIST_TEST_CONTAINS);
555 sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, strcat(s, ":"), SLIST_TEST_STARTSWITH);
559 //listflags |= SLSF_FAVORITES;
560 listflags |= SLSF_CATEGORIES;
561 if(me.currentSortOrder < 0) { listflags |= SLSF_DESCENDING; }
562 sethostcachesort(me.currentSortField, listflags);
566 if(mode >= REFRESHSERVERLIST_ASK)
567 refreshhostcache(mode >= REFRESHSERVERLIST_RESET);
569 void XonoticServerList_focusEnter(entity me)
571 if(time < me.nextRefreshTime)
573 //print("sorry, no refresh yet\n");
576 me.nextRefreshTime = time + 10;
577 me.refreshServerList(me, REFRESHSERVERLIST_ASK);
580 void XonoticServerList_draw(entity me)
582 float i, found, owned, num;
584 if(_Nex_ExtResponseSystem_BannedServersNeedsRefresh)
588 _Nex_ExtResponseSystem_BannedServersNeedsRefresh = 0;
591 if(me.currentSortField == -1)
593 me.setSortOrder(me, SLIST_FIELD_PING, +1);
594 me.refreshServerList(me, REFRESHSERVERLIST_RESET);
596 else if(me.needsRefresh == 1)
598 me.needsRefresh = 2; // delay by one frame to make sure "slist" has been executed
600 else if(me.needsRefresh == 2)
603 me.refreshServerList(me, REFRESHSERVERLIST_REFILTER);
606 owned = ((me.selectedServer == me.ipAddressBox.text) && (me.ipAddressBox.text != ""));
608 for(i = 0; i < category_draw_count; ++i) { category_name[i] = -1; category_item[i] = -1; }
609 category_draw_count = 0;
611 if(autocvar_menu_slist_categories >= 0) // if less than 0, don't even draw a category heading for favorites
613 float itemcount = gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT);
614 me.nItems = itemcount;
616 //float visible = floor(me.scrollPos / me.itemHeight);
617 // ^ unfortunately no such optimization can be made-- we must process through the
618 // entire list, otherwise there is no way to know which item is first in its category.
621 for(i = 0; i < itemcount; ++i)
623 cat = gethostcachenumber(SLIST_FIELD_CATEGORY, i);
626 if(category_draw_count == 0)
628 category_name[category_draw_count] = cat;
629 category_item[category_draw_count] = i;
630 ++category_draw_count;
636 for(x = 0; x < category_draw_count; ++x) { if(cat == category_name[x]) { found = 1; } }
639 category_name[category_draw_count] = cat;
640 category_item[category_draw_count] = i;
641 ++category_draw_count;
647 if(autocvar_menu_slist_categories_onlyifmultiple && (category_draw_count == 1))
649 category_name[0] = category_name[1] = -1;
650 category_item[0] = category_item[1] = -1;
651 category_draw_count = 0;
652 me.nItems = itemcount;
655 else { me.nItems = gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT); }
657 me.connectButton.disabled = ((me.nItems == 0) && (me.ipAddressBox.text == ""));
658 me.infoButton.disabled = ((me.nItems == 0) || !owned);
659 me.favoriteButton.disabled = ((me.nItems == 0) && (me.ipAddressBox.text == ""));
662 if(me.selectedServer)
664 for(i = 0; i < me.nItems; ++i)
666 num = XonoticServerList_MapItems(i);
669 if(gethostcachestring(SLIST_FIELD_CNAME, num) == me.selectedServer)
671 if(i != me.selectedItem)
673 me.lastClickedServer = -1;
686 if(me.selectedItem >= me.nItems) { me.selectedItem = me.nItems - 1; }
687 if(me.selectedServer) { strunzone(me.selectedServer); }
689 num = XonoticServerList_MapItems(me.selectedItem);
690 if(num >= 0) { me.selectedServer = strzone(gethostcachestring(SLIST_FIELD_CNAME, num)); }
696 if(me.selectedServer != me.ipAddressBox.text)
698 me.ipAddressBox.setText(me.ipAddressBox, me.selectedServer);
699 me.ipAddressBox.cursorPos = strlen(me.selectedServer);
700 me.ipAddressBoxFocused = -1;
704 if(me.ipAddressBoxFocused != me.ipAddressBox.focused)
706 if(me.ipAddressBox.focused || me.ipAddressBoxFocused < 0)
707 ServerList_Update_favoriteButton(NULL, me);
708 me.ipAddressBoxFocused = me.ipAddressBox.focused;
711 SUPER(XonoticServerList).draw(me);
713 void ServerList_PingSort_Click(entity btn, entity me)
715 me.setSortOrder(me, SLIST_FIELD_PING, +1);
717 void ServerList_NameSort_Click(entity btn, entity me)
719 me.setSortOrder(me, SLIST_FIELD_NAME, -1); // why?
721 void ServerList_MapSort_Click(entity btn, entity me)
723 me.setSortOrder(me, SLIST_FIELD_MAP, -1); // why?
725 void ServerList_PlayerSort_Click(entity btn, entity me)
727 me.setSortOrder(me, SLIST_FIELD_NUMHUMANS, -1);
729 void ServerList_TypeSort_Click(entity btn, entity me)
734 m = strstrofs(s, ":", 0);
737 s = substring(s, 0, m);
738 while(substring(s, m+1, 1) == " ") // skip spaces
744 for(i = 1; ; i *= 2) // 20 modes ought to be enough for anyone
746 t = MapInfo_Type_ToString(i);
748 if(t == "") // it repeats (default case)
751 // choose the first one
752 s = MapInfo_Type_ToString(1);
757 // the type was found
758 // choose the next one
759 s = MapInfo_Type_ToString(i * 2);
761 s = MapInfo_Type_ToString(1);
768 s = strcat(s, substring(me.filterString, m+1, strlen(me.filterString) - m - 1));
770 me.controlledTextbox.setText(me.controlledTextbox, s);
771 me.controlledTextbox.keyDown(me.controlledTextbox, K_END, 0, 0);
772 me.controlledTextbox.keyUp(me.controlledTextbox, K_END, 0, 0);
773 //ServerList_Filter_Change(me.controlledTextbox, me);
775 void ServerList_Filter_Change(entity box, entity me)
778 strunzone(me.filterString);
780 me.filterString = strzone(box.text);
782 me.filterString = string_null;
783 me.refreshServerList(me, REFRESHSERVERLIST_REFILTER);
785 me.ipAddressBox.setText(me.ipAddressBox, "");
786 me.ipAddressBox.cursorPos = 0;
787 me.ipAddressBoxFocused = -1;
789 void ServerList_Categories_Click(entity box, entity me)
791 box.setChecked(box, autocvar_menu_slist_categories = !autocvar_menu_slist_categories);
792 ///refreshhostcache(TRUE);
794 //cvar_set("net_slist_pause", "0");
795 //Destroy_Category_Entities();
796 //CALL_ACCUMULATED_FUNCTION(RegisterSLCategories);
797 //me.refreshServerList(me, REFRESHSERVERLIST_REFILTER);
799 me.refreshServerList(me, REFRESHSERVERLIST_RESORT);
801 me.ipAddressBox.setText(me.ipAddressBox, "");
802 me.ipAddressBox.cursorPos = 0;
803 me.ipAddressBoxFocused = -1;
805 void ServerList_ShowEmpty_Click(entity box, entity me)
807 box.setChecked(box, me.filterShowEmpty = !me.filterShowEmpty);
808 me.refreshServerList(me, REFRESHSERVERLIST_REFILTER);
810 me.ipAddressBox.setText(me.ipAddressBox, "");
811 me.ipAddressBox.cursorPos = 0;
812 me.ipAddressBoxFocused = -1;
814 void ServerList_ShowFull_Click(entity box, entity me)
816 box.setChecked(box, me.filterShowFull = !me.filterShowFull);
817 me.refreshServerList(me, REFRESHSERVERLIST_REFILTER);
819 me.ipAddressBox.setText(me.ipAddressBox, "");
820 me.ipAddressBox.cursorPos = 0;
821 me.ipAddressBoxFocused = -1;
823 void XonoticServerList_setSortOrder(entity me, float fld, float direction)
825 if(me.currentSortField == fld)
826 direction = -me.currentSortOrder;
827 me.currentSortOrder = direction;
828 me.currentSortField = fld;
829 me.sortButton1.forcePressed = (fld == SLIST_FIELD_PING);
830 me.sortButton2.forcePressed = (fld == SLIST_FIELD_NAME);
831 me.sortButton3.forcePressed = (fld == SLIST_FIELD_MAP);
832 me.sortButton4.forcePressed = 0;
833 me.sortButton5.forcePressed = (fld == SLIST_FIELD_NUMHUMANS);
835 if(me.selectedServer)
836 strunzone(me.selectedServer);
837 me.selectedServer = string_null;
838 me.refreshServerList(me, REFRESHSERVERLIST_REFILTER);
840 void XonoticServerList_positionSortButton(entity me, entity btn, float theOrigin, float theSize, string theTitle, void(entity, entity) theFunc)
842 vector originInLBSpace, sizeInLBSpace;
843 originInLBSpace = eY * (-me.itemHeight);
844 sizeInLBSpace = eY * me.itemHeight + eX * (1 - me.controlWidth);
846 vector originInDialogSpace, sizeInDialogSpace;
847 originInDialogSpace = boxToGlobal(originInLBSpace, me.Container_origin, me.Container_size);
848 sizeInDialogSpace = boxToGlobalSize(sizeInLBSpace, me.Container_size);
850 btn.Container_origin_x = originInDialogSpace_x + sizeInDialogSpace_x * theOrigin;
851 btn.Container_size_x = sizeInDialogSpace_x * theSize;
852 btn.setText(btn, theTitle);
853 btn.onClick = theFunc;
854 btn.onClickEntity = me;
857 void XonoticServerList_resizeNotify(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
859 SUPER(XonoticServerList).resizeNotify(me, relOrigin, relSize, absOrigin, absSize);
861 me.realFontSize_y = me.fontSize / (absSize_y * me.itemHeight);
862 me.realFontSize_x = me.fontSize / (absSize_x * (1 - me.controlWidth));
863 me.realUpperMargin = 0.5 * (1 - me.realFontSize_y);
865 me.columnIconsOrigin = 0;
866 me.columnIconsSize = me.realFontSize_x * 4 * me.iconsSizeFactor;
867 me.columnPingSize = me.realFontSize_x * 3;
868 me.columnMapSize = me.realFontSize_x * 10;
869 me.columnTypeSize = me.realFontSize_x * 4;
870 me.columnPlayersSize = me.realFontSize_x * 5;
871 me.columnNameSize = 1 - me.columnPlayersSize - me.columnMapSize - me.columnPingSize - me.columnIconsSize - me.columnTypeSize - 5 * me.realFontSize_x;
872 me.columnPingOrigin = me.columnIconsOrigin + me.columnIconsSize + me.realFontSize_x;
873 me.columnNameOrigin = me.columnPingOrigin + me.columnPingSize + me.realFontSize_x;
874 me.columnMapOrigin = me.columnNameOrigin + me.columnNameSize + me.realFontSize_x;
875 me.columnTypeOrigin = me.columnMapOrigin + me.columnMapSize + me.realFontSize_x;
876 me.columnPlayersOrigin = me.columnTypeOrigin + me.columnTypeSize + me.realFontSize_x;
878 me.positionSortButton(me, me.sortButton1, me.columnPingOrigin, me.columnPingSize, _("Ping"), ServerList_PingSort_Click);
879 me.positionSortButton(me, me.sortButton2, me.columnNameOrigin, me.columnNameSize, _("Host name"), ServerList_NameSort_Click);
880 me.positionSortButton(me, me.sortButton3, me.columnMapOrigin, me.columnMapSize, _("Map"), ServerList_MapSort_Click);
881 me.positionSortButton(me, me.sortButton4, me.columnTypeOrigin, me.columnTypeSize, _("Type"), ServerList_TypeSort_Click);
882 me.positionSortButton(me, me.sortButton5, me.columnPlayersOrigin, me.columnPlayersSize, _("Players"), ServerList_PlayerSort_Click);
885 f = me.currentSortField;
888 me.currentSortField = -1;
889 me.setSortOrder(me, f, me.currentSortOrder); // force resetting the sort order
892 void ServerList_Connect_Click(entity btn, entity me)
894 localcmd(sprintf("connect %s\n",
895 ((me.ipAddressBox.text != "") ?
896 me.ipAddressBox.text : me.selectedServer
900 void ServerList_Favorite_Click(entity btn, entity me)
903 ipstr = netaddress_resolve(me.ipAddressBox.text, 26000);
906 me.toggleFavorite(me, me.ipAddressBox.text);
907 me.ipAddressBoxFocused = -1;
910 void ServerList_Info_Click(entity btn, entity me)
912 main.serverInfoDialog.loadServerInfo(main.serverInfoDialog, XonoticServerList_MapItems(me.selectedItem));
913 DialogOpenButton_Click(me, main.serverInfoDialog);
915 void XonoticServerList_clickListBoxItem(entity me, float i, vector where)
917 float num = XonoticServerList_MapItems(i);
920 if(num == me.lastClickedServer)
921 if(time < me.lastClickedTime + 0.3)
924 ServerList_Connect_Click(NULL, me);
926 me.lastClickedServer = num;
927 me.lastClickedTime = time;
930 void XonoticServerList_drawListBoxItem(entity me, float i, vector absSize, float isSelected)
932 // layout: Ping, Server name, Map name, NP, TP, MP
937 float m, pure, freeslots, j, sflags;
938 string s, typestr, versionstr, k, v, modname;
940 float item = XonoticServerList_MapItems(i);
941 //print(sprintf("time: %f, i: %d, item: %d, nitems: %d\n", time, i, item, me.nItems));
945 entity catent = Get_Cat_Ent(-item);
949 eY * me.realUpperMargin
951 eX * (me.columnNameOrigin + (me.columnNameSize - draw_TextWidth(catent.cat_string, 0, me.realFontSize)) * 0.5),
963 draw_Fill('0 0 0', '1 1 0', SKINCOLOR_LISTBOX_SELECTED, SKINALPHA_LISTBOX_SELECTED);
965 s = gethostcachestring(SLIST_FIELD_QCSTATUS, item);
966 m = tokenizebyseparator(s, ":");
971 versionstr = argv(1);
977 for(j = 2; j < m; ++j)
981 k = substring(argv(j), 0, 1);
982 v = substring(argv(j), 1, -1);
993 #ifdef COMPAT_NO_MOD_IS_XONOTIC
999 SLIST_FIELD_MOD = gethostcacheindexforkey("mod");
1000 s = gethostcachestring(SLIST_FIELD_MOD, item);
1002 if(modname == "Xonotic")
1006 // list the mods here on which the pure server check actually works
1007 if(modname != "Xonotic")
1008 if(modname != "MinstaGib")
1009 if(modname != "CTS")
1010 if(modname != "NIX")
1011 if(modname != "NewToys")
1014 if(gethostcachenumber(SLIST_FIELD_FREESLOTS, item) <= 0)
1015 theAlpha = SKINALPHA_SERVERLIST_FULL;
1016 else if(freeslots == 0)
1017 theAlpha = SKINALPHA_SERVERLIST_FULL; // g_maxplayers support
1018 else if not(gethostcachenumber(SLIST_FIELD_NUMHUMANS, item))
1019 theAlpha = SKINALPHA_SERVERLIST_EMPTY;
1023 p = gethostcachenumber(SLIST_FIELD_PING, item);
1025 #define PING_MED 200
1026 #define PING_HIGH 500
1028 theColor = SKINCOLOR_SERVERLIST_LOWPING + (SKINCOLOR_SERVERLIST_MEDPING - SKINCOLOR_SERVERLIST_LOWPING) * (p / PING_LOW);
1029 else if(p < PING_MED)
1030 theColor = SKINCOLOR_SERVERLIST_MEDPING + (SKINCOLOR_SERVERLIST_HIGHPING - SKINCOLOR_SERVERLIST_MEDPING) * ((p - PING_LOW) / (PING_MED - PING_LOW));
1031 else if(p < PING_HIGH)
1033 theColor = SKINCOLOR_SERVERLIST_HIGHPING;
1034 theAlpha *= 1 + (SKINALPHA_SERVERLIST_HIGHPING - 1) * ((p - PING_MED) / (PING_HIGH - PING_MED));
1039 theAlpha *= SKINALPHA_SERVERLIST_HIGHPING;
1042 if(gethostcachenumber(SLIST_FIELD_ISFAVORITE, item))
1044 theColor = theColor * (1 - SKINALPHA_SERVERLIST_FAVORITE) + SKINCOLOR_SERVERLIST_FAVORITE * SKINALPHA_SERVERLIST_FAVORITE;
1045 theAlpha = theAlpha * (1 - SKINALPHA_SERVERLIST_FAVORITE) + SKINALPHA_SERVERLIST_FAVORITE;
1048 s = gethostcachestring(SLIST_FIELD_CNAME, item);
1051 if(substring(s, 0, 1) == "[")
1056 else if(strstrofs("0123456789", substring(s, 0, 1), 0) >= 0)
1062 q = stof(substring(crypto_getencryptlevel(s), 0, 1));
1063 if((q <= 0 && cvar("crypto_aeslevel") >= 3) || (q >= 3 && cvar("crypto_aeslevel") <= 0))
1065 theColor = SKINCOLOR_SERVERLIST_IMPOSSIBLE;
1066 theAlpha = SKINALPHA_SERVERLIST_IMPOSSIBLE;
1071 if(cvar("crypto_aeslevel") >= 2)
1076 if(cvar("crypto_aeslevel") >= 1)
1086 // 2: AES recommended but not available
1087 // 3: AES possible and will be used
1088 // 4: AES recommended and will be used
1094 vector iconSize = '0 0 0';
1095 iconSize_y = me.realFontSize_y * me.iconsSizeFactor;
1096 iconSize_x = me.realFontSize_x * me.iconsSizeFactor;
1098 vector iconPos = '0 0 0';
1099 iconPos_x = (me.columnIconsSize - 3 * iconSize_x) * 0.5;
1100 iconPos_y = (1 - iconSize_y) * 0.5;
1104 if not(me.seenIPv4 && me.seenIPv6)
1106 iconPos_x += iconSize_x * 0.5;
1108 else if(me.seenIPv4 && me.seenIPv6)
1112 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_ipv6"), 0); // PRECACHE_PIC_MIPMAP
1114 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_ipv4"), 0); // PRECACHE_PIC_MIPMAP
1116 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1117 iconPos_x += iconSize_x;
1122 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_aeslevel", ftos(q)), 0); // PRECACHE_PIC_MIPMAP
1123 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1125 iconPos_x += iconSize_x;
1127 if(modname == "Xonotic")
1131 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_pure1"), PRECACHE_PIC_MIPMAP);
1132 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1137 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_mod_", modname), PRECACHE_PIC_MIPMAP);
1138 if(draw_PictureSize(n) == '0 0 0')
1139 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_mod_"), PRECACHE_PIC_MIPMAP);
1141 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1143 draw_Picture(iconPos, n, iconSize, '1 1 1', SKINALPHA_SERVERLIST_ICON_NONPURE);
1145 iconPos_x += iconSize_x;
1147 if(sflags >= 0 && (sflags & SERVERFLAG_PLAYERSTATS))
1149 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_stats1"), 0); // PRECACHE_PIC_MIPMAP
1150 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1152 iconPos_x += iconSize_x;
1160 draw_Text(me.realUpperMargin * eY + (me.columnPingOrigin + me.columnPingSize - draw_TextWidth(s, 0, me.realFontSize)) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1163 s = draw_TextShortenToWidth(gethostcachestring(SLIST_FIELD_NAME, item), me.columnNameSize, 0, me.realFontSize);
1164 draw_Text(me.realUpperMargin * eY + me.columnNameOrigin * eX, s, me.realFontSize, theColor, theAlpha, 0);
1167 s = draw_TextShortenToWidth(gethostcachestring(SLIST_FIELD_MAP, item), me.columnMapSize, 0, me.realFontSize);
1168 draw_Text(me.realUpperMargin * eY + (me.columnMapOrigin + (me.columnMapSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1171 s = draw_TextShortenToWidth(typestr, me.columnTypeSize, 0, me.realFontSize);
1172 draw_Text(me.realUpperMargin * eY + (me.columnTypeOrigin + (me.columnTypeSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1174 // server playercount
1175 s = strcat(ftos(gethostcachenumber(SLIST_FIELD_NUMHUMANS, item)), "/", ftos(gethostcachenumber(SLIST_FIELD_MAXPLAYERS, item)));
1176 draw_Text(me.realUpperMargin * eY + (me.columnPlayersOrigin + (me.columnPlayersSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1179 float XonoticServerList_keyDown(entity me, float scan, float ascii, float shift)
1181 float i = XonoticServerList_MapItems(me.selectedItem);
1184 org = boxToGlobal(eY * (me.selectedItem * me.itemHeight - me.scrollPos), me.origin, me.size);
1185 sz = boxToGlobalSize(eY * me.itemHeight + eX * (1 - me.controlWidth), me.size);
1187 me.lastBumpSelectTime = 0;
1189 if(scan == K_ENTER || scan == K_KP_ENTER)
1191 ServerList_Connect_Click(NULL, me);
1194 else if(scan == K_MOUSE2 || scan == K_SPACE)
1196 if((me.nItems != 0) && (i >= 0))
1198 main.serverInfoDialog.loadServerInfo(main.serverInfoDialog, i);
1199 DialogOpenButton_Click_withCoords(me, main.serverInfoDialog, org, sz);
1204 else if(scan == K_INS || scan == K_MOUSE3 || scan == K_KP_INS)
1206 if((me.nItems != 0) && (i >= 0))
1208 me.toggleFavorite(me, me.selectedServer);
1209 me.ipAddressBoxFocused = -1;
1214 else if(SUPER(XonoticServerList).keyDown(me, scan, ascii, shift))
1216 else if(!me.controlledTextbox)
1219 return me.controlledTextbox.keyDown(me.controlledTextbox, scan, ascii, shift);