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 void ServerList_Connect_Click(entity btn, entity me);
62 void ServerList_ShowEmpty_Click(entity box, entity me);
63 void ServerList_ShowFull_Click(entity box, entity me);
64 void ServerList_Filter_Change(entity box, entity me);
65 void ServerList_Favorite_Click(entity btn, entity me);
66 void ServerList_Info_Click(entity btn, entity me);
67 void ServerList_Update_favoriteButton(entity btn, entity me);
69 #ifndef IMPLEMENTATION
70 float SLIST_FIELD_CNAME;
71 float SLIST_FIELD_PING;
72 float SLIST_FIELD_GAME;
73 float SLIST_FIELD_MOD;
74 float SLIST_FIELD_MAP;
75 float SLIST_FIELD_NAME;
76 float SLIST_FIELD_MAXPLAYERS;
77 float SLIST_FIELD_NUMPLAYERS;
78 float SLIST_FIELD_NUMHUMANS;
79 float SLIST_FIELD_NUMBOTS;
80 float SLIST_FIELD_PROTOCOL;
81 float SLIST_FIELD_FREESLOTS;
82 float SLIST_FIELD_PLAYERS;
83 float SLIST_FIELD_QCSTATUS;
84 float SLIST_FIELD_CATEGORY;
85 float SLIST_FIELD_ISFAVORITE;
86 float SLSF_DESCENDING = 1;
87 float SLSF_FAVORITES = 2;
88 float SLSF_CATEGORIES = 4;
90 #define CATEGORY_FIRST 1
91 #define MAX_CATEGORIES 9
92 entity categories[MAX_CATEGORIES];
93 float category_ent_count;
95 float category_name[MAX_CATEGORIES];
96 float category_item[MAX_CATEGORIES];
101 .string cat_override_string;
105 SLIST_CATEGORY(CAT_FAVORITED, "", "", _("Favorites")) \
106 SLIST_CATEGORY(CAT_RECOMMENDED, "", "CAT_SERVERS", _("Recommended")) \
107 SLIST_CATEGORY(CAT_NORMAL, "", "CAT_SERVERS", _("Normal Servers")) \
108 SLIST_CATEGORY(CAT_SERVERS, "CAT_NORMAL", "CAT_SERVERS", _("Servers")) \
109 SLIST_CATEGORY(CAT_XPM, "CAT_NORMAL", "CAT_SERVERS", _("Competitive Mode")) \
110 SLIST_CATEGORY(CAT_MODIFIED, "", "CAT_SERVERS", _("Modified Servers")) \
111 SLIST_CATEGORY(CAT_OVERKILL, "", "CAT_SERVERS", _("Overkill Mode")) \
112 SLIST_CATEGORY(CAT_MINSTAGIB, "", "CAT_SERVERS", _("MinstaGib Mode")) \
113 SLIST_CATEGORY(CAT_DEFRAG, "", "CAT_SERVERS", _("Defrag Mode"))
115 void RegisterSLCategories_First()
117 /*notif_global_error = FALSE;
120 #define dedi (server_is_dedicated ? "a dedicated " : "")
125 print(sprintf("Beginning notification initialization on %s%s program...\n", dedi, PROGNAME));*/
127 // maybe do another implementation of this with checksums? for now, we don't need versioning
128 /*if(autocvar_notification_version != NOTIF_VERSION)
131 if(autocvar_notification_version_mismatch_client_error)
133 if(autocvar_notification_version_mismatch_server_error)
135 notif_global_error = TRUE;
137 print(sprintf("^1NOTIFICATION VERSION MISMATCH: ^7program = %s, config = %d, code = %d.\n",
138 PROGNAME, autocvar_notification_version, NOTIF_VERSION));
142 float Get_Cat_Num_FromString(string input)
145 for(i = 0; i < category_ent_count; ++i) { if(categories[i].cat_name == input) { return (i + 1); } }
146 print(sprintf("Get_Cat_Num_FromString('%s'): Improper category name!\n", input));
149 entity Get_Cat_Ent(float catnum)
151 if((catnum > 0) && (catnum <= category_ent_count))
153 return categories[catnum - 1];
157 error(sprintf("Get_Cat_Ent(%d): Improper category number!\n", catnum));
162 void RegisterSLCategories_Done()
166 for(i = 0; i < category_ent_count; ++i)
168 s = categories[i].cat_override_string;
169 if((s != "") && (s != categories[i].cat_name))
171 catnum = Get_Cat_Num_FromString(s);
174 strunzone(categories[i].cat_override_string);
175 categories[i].cat_override = catnum;
179 strunzone(categories[i].cat_override_string);
180 categories[i].cat_override = 0;
184 var float autocvar_menu_serverlist_categories = TRUE;
186 // C is stupid, must use extra macro for concatenation
187 #define SLIST_ADD_CAT_CVAR(name,default) var string autocvar_menu_serverlist_categories_##name##_override = default;
188 #define SLIST_CATEGORY(name,enoverride,deoverride,string) \
189 SLIST_ADD_CAT_CVAR(name, enoverride) \
191 void RegisterSLCategory_##name() \
193 SET_FIELD_COUNT(name, CATEGORY_FIRST, category_ent_count) \
194 CHECK_MAX_COUNT(name, MAX_CATEGORIES, category_ent_count, "SLIST_CATEGORY") \
195 entity cat = spawn(); \
196 categories[name - 1] = cat; \
197 cat.classname = "slist_category"; \
198 cat.cat_name = strzone(#name); \
199 cat.cat_override_string = strzone((autocvar_menu_serverlist_categories ? \
200 autocvar_menu_serverlist_categories_##name##_override \
203 cat.cat_string = strzone(string); \
205 ACCUMULATE_FUNCTION(RegisterSLCategories, RegisterSLCategory_##name);
207 ACCUMULATE_FUNCTION(RegisterSLCategories, RegisterSLCategories_First);
209 ACCUMULATE_FUNCTION(RegisterSLCategories, RegisterSLCategories_Done);
210 #undef SLIST_ADD_CAT_CVAR
211 #undef SLIST_CATEGORY
213 var float autocvar_menu_serverlist_purethreshold = 10;
214 var string autocvar_menu_serverlist_recommended = "76.124.107.5:26004";
219 #ifdef IMPLEMENTATION
220 float IsServerInList(string list, string srv)
226 srv = netaddress_resolve(srv, 26000);
229 p = crypto_getidfp(srv);
230 n = tokenize_console(list);
231 for(i = 0; i < n; ++i)
233 if(substring(argv(i), 0, 1) != "[" && strlen(argv(i)) == 44 && strstrofs(argv(i), ".", 0) < 0)
241 if(srv == netaddress_resolve(argv(i), 26000))
248 #define IsFavorite(srv) IsServerInList(cvar_string("net_slist_favorites"), srv)
249 #define IsRecommended(srv) IsServerInList(cvar_string("menu_serverlist_recommended"), srv) // todo: use update notification instead of cvar
251 float cat_checkoverride(float cat)
253 entity catent = Get_Cat_Ent(cat);
256 if(catent.cat_override) { return catent.cat_override; }
261 error(sprintf("cat_checkoverride(%d): Improper category number!\n", cat));
266 float m_getserverlistentrycategory(float entry)
268 string s, k, v, modtype = "";
270 s = gethostcachestring(SLIST_FIELD_QCSTATUS, entry);
271 m = tokenizebyseparator(s, ":");
275 // typestr = argv(0);
276 // versionstr = argv(1);
282 for(j = 2; j < m; ++j)
286 k = substring(argv(j), 0, 1);
287 v = substring(argv(j), 1, -1);
288 if(k == "P") { impure = stof(v); }
289 else if(k == "M") { modtype = strtolower(v); }
292 //print(sprintf("modtype = %s\n", modtype));
294 if(impure > autocvar_menu_serverlist_purethreshold) { impure = TRUE; }
295 else { impure = FALSE; }
297 if(gethostcachenumber(SLIST_FIELD_ISFAVORITE, entry)) { return cat_checkoverride(CAT_FAVORITED); }
298 if(IsRecommended(gethostcachestring(SLIST_FIELD_CNAME, entry))) { return cat_checkoverride(CAT_RECOMMENDED); }
299 else if(modtype != "xonotic")
303 // old servers which don't report their mod name are considered modified now
304 case "": { return cat_checkoverride(CAT_MODIFIED); }
306 case "xpm": { return cat_checkoverride(CAT_XPM); }
307 case "minstagib": { return cat_checkoverride(CAT_MINSTAGIB); }
308 case "overkill": { return cat_checkoverride(CAT_OVERKILL); }
310 // "cts" is allowed as compat, xdf is replacement
312 case "xdf": { return cat_checkoverride(CAT_DEFRAG); }
314 //if(modname != "CTS")
315 //if(modname != "NIX")
316 //if(modname != "NewToys")
318 default: { print(sprintf("Found strange mod type: %s\n", modtype)); return cat_checkoverride(CAT_MODIFIED); }
321 else { return cat_checkoverride((impure ? CAT_MODIFIED : CAT_NORMAL)); }
323 // should never hit this point
324 error("wtf m_getserverlistentrycategory fail?");
328 float XonoticServerList_MapItems(float num)
332 if not(totcat) { return num; } // there are no categories to process
334 for(i = 0, n = 1; n <= totcat; ++i, ++n)
336 //print(sprintf("num: %d, i: %d, totcat: %d, category_item[i]: %d\n", num, i, totcat, category_item[i]));
337 if(category_item[i] == (num - i)) { /*print("inserting cat... \\/\n");*/ return -category_name[i]; }
338 else if(n == totcat) { /*print("end item... \\/\n");*/ return (num - n); }
339 else if((num - i) <= category_item[n]) { /*print("next item... \\/\n");*/ return (num - n); }
342 // should never hit this point
343 error("wtf XonoticServerList_MapItems fail?");
347 void ServerList_UpdateFieldIDs()
349 SLIST_FIELD_CNAME = gethostcacheindexforkey( "cname" );
350 SLIST_FIELD_PING = gethostcacheindexforkey( "ping" );
351 SLIST_FIELD_GAME = gethostcacheindexforkey( "game" );
352 SLIST_FIELD_MOD = gethostcacheindexforkey( "mod" );
353 SLIST_FIELD_MAP = gethostcacheindexforkey( "map" );
354 SLIST_FIELD_NAME = gethostcacheindexforkey( "name" );
355 SLIST_FIELD_MAXPLAYERS = gethostcacheindexforkey( "maxplayers" );
356 SLIST_FIELD_NUMPLAYERS = gethostcacheindexforkey( "numplayers" );
357 SLIST_FIELD_NUMHUMANS = gethostcacheindexforkey( "numhumans" );
358 SLIST_FIELD_NUMBOTS = gethostcacheindexforkey( "numbots" );
359 SLIST_FIELD_PROTOCOL = gethostcacheindexforkey( "protocol" );
360 SLIST_FIELD_FREESLOTS = gethostcacheindexforkey( "freeslots" );
361 SLIST_FIELD_PLAYERS = gethostcacheindexforkey( "players" );
362 SLIST_FIELD_QCSTATUS = gethostcacheindexforkey( "qcstatus" );
363 SLIST_FIELD_CATEGORY = gethostcacheindexforkey( "category" );
364 SLIST_FIELD_ISFAVORITE = gethostcacheindexforkey( "isfavorite" );
367 void ToggleFavorite(string srv)
369 string s, s0, s1, s2, srv_resolved, p;
371 srv_resolved = netaddress_resolve(srv, 26000);
372 p = crypto_getidfp(srv_resolved);
373 s = cvar_string("net_slist_favorites");
374 n = tokenize_console(s);
376 for(i = 0; i < n; ++i)
378 if(substring(argv(i), 0, 1) != "[" && strlen(argv(i)) == 44 && strstrofs(argv(i), ".", 0) < 0)
386 if(srv_resolved != netaddress_resolve(argv(i), 26000))
391 s0 = substring(s, 0, argv_end_index(i - 1));
393 s2 = substring(s, argv_start_index(i + 1), -1);
394 if(s0 != "" && s2 != "")
396 cvar_set("net_slist_favorites", strcat(s0, s1, s2));
397 s = cvar_string("net_slist_favorites");
398 n = tokenize_console(s);
409 cvar_set("net_slist_favorites", strcat(s, s1, p));
411 cvar_set("net_slist_favorites", strcat(s, s1, srv));
417 void ServerList_Update_favoriteButton(entity btn, entity me)
419 if(IsFavorite(me.ipAddressBox.text))
420 me.favoriteButton.setText(me.favoriteButton, _("Remove"));
422 me.favoriteButton.setText(me.favoriteButton, _("Bookmark"));
425 entity makeXonoticServerList()
428 me = spawnXonoticServerList();
429 me.configureXonoticServerList(me);
432 void XonoticServerList_configureXonoticServerList(entity me)
434 me.configureXonoticListBox(me);
436 ServerList_UpdateFieldIDs();
440 void XonoticServerList_setSelected(entity me, float i)
443 save = me.selectedItem;
444 SUPER(XonoticServerList).setSelected(me, i);
446 if(me.selectedItem == save)
451 //if(XonoticServerList_MapItems(gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT)) != me.nItems)
452 // { error("^1XonoticServerList_setSelected(); ERROR: ^7Host cache viewcount mismatches nItems!\n"); return; } // sorry, it would be wrong
454 num = XonoticServerList_MapItems(me.selectedItem);
457 if(me.selectedServer)
458 strunzone(me.selectedServer);
459 me.selectedServer = strzone(gethostcachestring(SLIST_FIELD_CNAME, num));
461 me.ipAddressBox.setText(me.ipAddressBox, me.selectedServer);
462 me.ipAddressBox.cursorPos = strlen(me.selectedServer);
463 me.ipAddressBoxFocused = -1;
466 void XonoticServerList_refreshServerList(entity me, float mode)
468 // 0: just reparametrize
469 // 1: also ask for new servers
471 //print("refresh of type ", ftos(mode), "\n");
472 /* if(mode == 2) // borken
475 localcmd("net_slist\n");
476 me.needsRefresh = 1; // net_slist kills sort order, so we need to restore it later
482 string s, typestr, modstr;
485 m = strstrofs(s, ":", 0);
488 typestr = substring(s, 0, m);
489 s = substring(s, m + 1, strlen(s) - m - 1);
490 while(substring(s, 0, 1) == " ")
491 s = substring(s, 1, strlen(s) - 1);
496 modstr = cvar_string("menu_slist_modfilter");
498 m = SLIST_MASK_AND - 1;
499 resethostcachemasks();
501 // ping: reject negative ping (no idea why this happens in the first place, engine bug)
502 sethostcachemasknumber(++m, SLIST_FIELD_PING, 0, SLIST_TEST_GREATEREQUAL);
505 if(!me.filterShowFull)
507 sethostcachemasknumber(++m, SLIST_FIELD_FREESLOTS, 1, SLIST_TEST_GREATEREQUAL); // legacy
508 sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, ":S0:", SLIST_TEST_NOTCONTAIN); // g_maxplayers support
512 if(!me.filterShowEmpty)
513 sethostcachemasknumber(++m, SLIST_FIELD_NUMHUMANS, 1, SLIST_TEST_GREATEREQUAL);
515 // gametype filtering
517 sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, strcat(typestr, ":"), SLIST_TEST_STARTSWITH);
522 if(substring(modstr, 0, 1) == "!")
523 sethostcachemaskstring(++m, SLIST_FIELD_MOD, resolvemod(substring(modstr, 1, strlen(modstr) - 1)), SLIST_TEST_NOTEQUAL);
525 sethostcachemaskstring(++m, SLIST_FIELD_MOD, resolvemod(modstr), SLIST_TEST_EQUAL);
529 n = tokenizebyseparator(_Nex_ExtResponseSystem_BannedServers, " ");
530 for(i = 0; i < n; ++i)
532 sethostcachemaskstring(++m, SLIST_FIELD_CNAME, argv(i), SLIST_TEST_NOTSTARTSWITH);
534 m = SLIST_MASK_OR - 1;
537 sethostcachemaskstring(++m, SLIST_FIELD_NAME, s, SLIST_TEST_CONTAINS);
538 sethostcachemaskstring(++m, SLIST_FIELD_MAP, s, SLIST_TEST_CONTAINS);
539 sethostcachemaskstring(++m, SLIST_FIELD_PLAYERS, s, SLIST_TEST_CONTAINS);
540 sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, strcat(s, ":"), SLIST_TEST_STARTSWITH);
544 //listflags |= SLSF_FAVORITES;
545 listflags |= SLSF_CATEGORIES;
546 if(me.currentSortOrder < 0) { listflags |= SLSF_DESCENDING; }
547 sethostcachesort(me.currentSortField, listflags);
550 if(mode >= 1) { refreshhostcache(); }
552 void XonoticServerList_focusEnter(entity me)
554 if(time < me.nextRefreshTime)
556 //print("sorry, no refresh yet\n");
559 me.nextRefreshTime = time + 10;
560 me.refreshServerList(me, 1);
563 void XonoticServerList_draw(entity me)
565 float i, found, owned, num;
567 if(_Nex_ExtResponseSystem_BannedServersNeedsRefresh)
571 _Nex_ExtResponseSystem_BannedServersNeedsRefresh = 0;
574 if(me.currentSortField == -1)
576 me.setSortOrder(me, SLIST_FIELD_PING, +1);
577 me.refreshServerList(me, 2);
579 else if(me.needsRefresh == 1)
581 me.needsRefresh = 2; // delay by one frame to make sure "slist" has been executed
583 else if(me.needsRefresh == 2)
586 me.refreshServerList(me, 0);
589 owned = ((me.selectedServer == me.ipAddressBox.text) && (me.ipAddressBox.text != ""));
591 for(i = 0; i < totcat; ++i) { category_name[i] = -1; category_item[i] = -1; }
594 float itemcount = gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT);
595 //float visible = floor(me.scrollPos / me.itemHeight);
596 me.nItems = itemcount;
599 for(i = 0; i < itemcount; ++i)
601 cat = gethostcachenumber(SLIST_FIELD_CATEGORY, i);
606 category_name[totcat] = cat;
607 category_item[totcat] = i;
614 for(x = 0; x < totcat; ++x) { if(cat == category_name[x]) { found = 1; } }
617 category_name[totcat] = cat;
618 category_item[totcat] = i;
626 //print(sprintf("^1SERVERLIST_DRAW^7: servercount: %d, nitems: %d\n", itemcount, me.nItems));
628 me.connectButton.disabled = ((me.nItems == 0) && (me.ipAddressBox.text == ""));
629 me.infoButton.disabled = ((me.nItems == 0) || !owned);
630 me.favoriteButton.disabled = ((me.nItems == 0) && (me.ipAddressBox.text == ""));
633 if(me.selectedServer)
635 for(i = 0; i < me.nItems; ++i)
637 num = XonoticServerList_MapItems(i);
640 if(gethostcachestring(SLIST_FIELD_CNAME, num) == me.selectedServer)
642 if(i != me.selectedItem)
644 me.lastClickedServer = -1;
657 if(me.selectedItem >= me.nItems) { me.selectedItem = me.nItems - 1; }
658 if(me.selectedServer) { strunzone(me.selectedServer); }
660 num = XonoticServerList_MapItems(me.selectedItem);
661 if(num >= 0) { me.selectedServer = strzone(gethostcachestring(SLIST_FIELD_CNAME, num)); }
667 if(me.selectedServer != me.ipAddressBox.text)
669 me.ipAddressBox.setText(me.ipAddressBox, me.selectedServer);
670 me.ipAddressBox.cursorPos = strlen(me.selectedServer);
671 me.ipAddressBoxFocused = -1;
675 if(me.ipAddressBoxFocused != me.ipAddressBox.focused)
677 if(me.ipAddressBox.focused || me.ipAddressBoxFocused < 0)
678 ServerList_Update_favoriteButton(NULL, me);
679 me.ipAddressBoxFocused = me.ipAddressBox.focused;
682 SUPER(XonoticServerList).draw(me);
684 void ServerList_PingSort_Click(entity btn, entity me)
686 me.setSortOrder(me, SLIST_FIELD_PING, +1);
688 void ServerList_NameSort_Click(entity btn, entity me)
690 me.setSortOrder(me, SLIST_FIELD_NAME, -1); // why?
692 void ServerList_MapSort_Click(entity btn, entity me)
694 me.setSortOrder(me, SLIST_FIELD_MAP, -1); // why?
696 void ServerList_PlayerSort_Click(entity btn, entity me)
698 me.setSortOrder(me, SLIST_FIELD_NUMHUMANS, -1);
700 void ServerList_TypeSort_Click(entity btn, entity me)
705 m = strstrofs(s, ":", 0);
708 s = substring(s, 0, m);
709 while(substring(s, m+1, 1) == " ") // skip spaces
715 for(i = 1; ; i *= 2) // 20 modes ought to be enough for anyone
717 t = MapInfo_Type_ToString(i);
719 if(t == "") // it repeats (default case)
722 // choose the first one
723 s = MapInfo_Type_ToString(1);
728 // the type was found
729 // choose the next one
730 s = MapInfo_Type_ToString(i * 2);
732 s = MapInfo_Type_ToString(1);
739 s = strcat(s, substring(me.filterString, m+1, strlen(me.filterString) - m - 1));
741 me.controlledTextbox.setText(me.controlledTextbox, s);
742 me.controlledTextbox.keyDown(me.controlledTextbox, K_END, 0, 0);
743 me.controlledTextbox.keyUp(me.controlledTextbox, K_END, 0, 0);
744 //ServerList_Filter_Change(me.controlledTextbox, me);
746 void ServerList_Filter_Change(entity box, entity me)
749 strunzone(me.filterString);
751 me.filterString = strzone(box.text);
753 me.filterString = string_null;
754 me.refreshServerList(me, 0);
756 me.ipAddressBox.setText(me.ipAddressBox, "");
757 me.ipAddressBox.cursorPos = 0;
758 me.ipAddressBoxFocused = -1;
760 void ServerList_ShowEmpty_Click(entity box, entity me)
762 box.setChecked(box, me.filterShowEmpty = !me.filterShowEmpty);
763 me.refreshServerList(me, 0);
765 me.ipAddressBox.setText(me.ipAddressBox, "");
766 me.ipAddressBox.cursorPos = 0;
767 me.ipAddressBoxFocused = -1;
769 void ServerList_ShowFull_Click(entity box, entity me)
771 box.setChecked(box, me.filterShowFull = !me.filterShowFull);
772 me.refreshServerList(me, 0);
774 me.ipAddressBox.setText(me.ipAddressBox, "");
775 me.ipAddressBox.cursorPos = 0;
776 me.ipAddressBoxFocused = -1;
778 void XonoticServerList_setSortOrder(entity me, float fld, float direction)
780 if(me.currentSortField == fld)
781 direction = -me.currentSortOrder;
782 me.currentSortOrder = direction;
783 me.currentSortField = fld;
784 me.sortButton1.forcePressed = (fld == SLIST_FIELD_PING);
785 me.sortButton2.forcePressed = (fld == SLIST_FIELD_NAME);
786 me.sortButton3.forcePressed = (fld == SLIST_FIELD_MAP);
787 me.sortButton4.forcePressed = 0;
788 me.sortButton5.forcePressed = (fld == SLIST_FIELD_NUMHUMANS);
790 if(me.selectedServer)
791 strunzone(me.selectedServer);
792 me.selectedServer = string_null;
793 me.refreshServerList(me, 0);
795 void XonoticServerList_positionSortButton(entity me, entity btn, float theOrigin, float theSize, string theTitle, void(entity, entity) theFunc)
797 vector originInLBSpace, sizeInLBSpace;
798 originInLBSpace = eY * (-me.itemHeight);
799 sizeInLBSpace = eY * me.itemHeight + eX * (1 - me.controlWidth);
801 vector originInDialogSpace, sizeInDialogSpace;
802 originInDialogSpace = boxToGlobal(originInLBSpace, me.Container_origin, me.Container_size);
803 sizeInDialogSpace = boxToGlobalSize(sizeInLBSpace, me.Container_size);
805 btn.Container_origin_x = originInDialogSpace_x + sizeInDialogSpace_x * theOrigin;
806 btn.Container_size_x = sizeInDialogSpace_x * theSize;
807 btn.setText(btn, theTitle);
808 btn.onClick = theFunc;
809 btn.onClickEntity = me;
812 void XonoticServerList_resizeNotify(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
814 SUPER(XonoticServerList).resizeNotify(me, relOrigin, relSize, absOrigin, absSize);
816 me.realFontSize_y = me.fontSize / (absSize_y * me.itemHeight);
817 me.realFontSize_x = me.fontSize / (absSize_x * (1 - me.controlWidth));
818 me.realUpperMargin = 0.5 * (1 - me.realFontSize_y);
820 me.columnIconsOrigin = 0;
821 me.columnIconsSize = me.realFontSize_x * 4 * me.iconsSizeFactor;
822 me.columnPingSize = me.realFontSize_x * 3;
823 me.columnMapSize = me.realFontSize_x * 10;
824 me.columnTypeSize = me.realFontSize_x * 4;
825 me.columnPlayersSize = me.realFontSize_x * 5;
826 me.columnNameSize = 1 - me.columnPlayersSize - me.columnMapSize - me.columnPingSize - me.columnIconsSize - me.columnTypeSize - 5 * me.realFontSize_x;
827 me.columnPingOrigin = me.columnIconsOrigin + me.columnIconsSize + me.realFontSize_x;
828 me.columnNameOrigin = me.columnPingOrigin + me.columnPingSize + me.realFontSize_x;
829 me.columnMapOrigin = me.columnNameOrigin + me.columnNameSize + me.realFontSize_x;
830 me.columnTypeOrigin = me.columnMapOrigin + me.columnMapSize + me.realFontSize_x;
831 me.columnPlayersOrigin = me.columnTypeOrigin + me.columnTypeSize + me.realFontSize_x;
833 me.positionSortButton(me, me.sortButton1, me.columnPingOrigin, me.columnPingSize, _("Ping"), ServerList_PingSort_Click);
834 me.positionSortButton(me, me.sortButton2, me.columnNameOrigin, me.columnNameSize, _("Host name"), ServerList_NameSort_Click);
835 me.positionSortButton(me, me.sortButton3, me.columnMapOrigin, me.columnMapSize, _("Map"), ServerList_MapSort_Click);
836 me.positionSortButton(me, me.sortButton4, me.columnTypeOrigin, me.columnTypeSize, _("Type"), ServerList_TypeSort_Click);
837 me.positionSortButton(me, me.sortButton5, me.columnPlayersOrigin, me.columnPlayersSize, _("Players"), ServerList_PlayerSort_Click);
840 f = me.currentSortField;
843 me.currentSortField = -1;
844 me.setSortOrder(me, f, me.currentSortOrder); // force resetting the sort order
847 void ServerList_Connect_Click(entity btn, entity me)
849 if(me.ipAddressBox.text == "")
850 localcmd("connect ", me.selectedServer, "\n");
852 localcmd("connect ", me.ipAddressBox.text, "\n");
854 void ServerList_Favorite_Click(entity btn, entity me)
857 ipstr = netaddress_resolve(me.ipAddressBox.text, 26000);
860 ToggleFavorite(me.ipAddressBox.text);
861 me.ipAddressBoxFocused = -1;
864 void ServerList_Info_Click(entity btn, entity me)
866 main.serverInfoDialog.loadServerInfo(main.serverInfoDialog, XonoticServerList_MapItems(me.selectedItem));
867 DialogOpenButton_Click(me, main.serverInfoDialog);
869 void XonoticServerList_clickListBoxItem(entity me, float i, vector where)
871 float num = XonoticServerList_MapItems(i);
872 if(num == me.lastClickedServer)
873 if(time < me.lastClickedTime + 0.3)
876 ServerList_Connect_Click(NULL, me);
878 me.lastClickedServer = num;
879 me.lastClickedTime = time;
881 void XonoticServerList_drawListBoxItem(entity me, float i, vector absSize, float isSelected)
883 // layout: Ping, Server name, Map name, NP, TP, MP
888 float m, pure, freeslots, j, sflags;
889 string s, typestr, versionstr, k, v, modname;
891 float cache = XonoticServerList_MapItems(i);
892 //print(sprintf("time: %f, i: %d, cache: %d, nitems: %d\n", time, i, cache, me.nItems));
896 entity catent = Get_Cat_Ent(-cache);
897 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; }
901 draw_Fill('0 0 0', '1 1 0', SKINCOLOR_LISTBOX_SELECTED, SKINALPHA_LISTBOX_SELECTED);
903 s = gethostcachestring(SLIST_FIELD_QCSTATUS, cache);
904 m = tokenizebyseparator(s, ":");
909 versionstr = argv(1);
915 for(j = 2; j < m; ++j)
919 k = substring(argv(j), 0, 1);
920 v = substring(argv(j), 1, -1);
931 #ifdef COMPAT_NO_MOD_IS_XONOTIC
937 SLIST_FIELD_MOD = gethostcacheindexforkey("mod");
938 s = gethostcachestring(SLIST_FIELD_MOD, cache);
940 if(modname == "Xonotic")
944 // list the mods here on which the pure server check actually works
945 if(modname != "Xonotic")
946 if(modname != "MinstaGib")
949 if(modname != "NewToys")
952 if(gethostcachenumber(SLIST_FIELD_FREESLOTS, cache) <= 0)
953 theAlpha = SKINALPHA_SERVERLIST_FULL;
954 else if(freeslots == 0)
955 theAlpha = SKINALPHA_SERVERLIST_FULL; // g_maxplayers support
956 else if not(gethostcachenumber(SLIST_FIELD_NUMHUMANS, cache))
957 theAlpha = SKINALPHA_SERVERLIST_EMPTY;
961 p = gethostcachenumber(SLIST_FIELD_PING, cache);
964 #define PING_HIGH 500
966 theColor = SKINCOLOR_SERVERLIST_LOWPING + (SKINCOLOR_SERVERLIST_MEDPING - SKINCOLOR_SERVERLIST_LOWPING) * (p / PING_LOW);
967 else if(p < PING_MED)
968 theColor = SKINCOLOR_SERVERLIST_MEDPING + (SKINCOLOR_SERVERLIST_HIGHPING - SKINCOLOR_SERVERLIST_MEDPING) * ((p - PING_LOW) / (PING_MED - PING_LOW));
969 else if(p < PING_HIGH)
971 theColor = SKINCOLOR_SERVERLIST_HIGHPING;
972 theAlpha *= 1 + (SKINALPHA_SERVERLIST_HIGHPING - 1) * ((p - PING_MED) / (PING_HIGH - PING_MED));
977 theAlpha *= SKINALPHA_SERVERLIST_HIGHPING;
980 if(gethostcachenumber(SLIST_FIELD_ISFAVORITE, cache))
982 theColor = theColor * (1 - SKINALPHA_SERVERLIST_FAVORITE) + SKINCOLOR_SERVERLIST_FAVORITE * SKINALPHA_SERVERLIST_FAVORITE;
983 theAlpha = theAlpha * (1 - SKINALPHA_SERVERLIST_FAVORITE) + SKINALPHA_SERVERLIST_FAVORITE;
986 s = gethostcachestring(SLIST_FIELD_CNAME, cache);
989 if(substring(s, 0, 1) == "[")
994 else if(strstrofs("0123456789", substring(s, 0, 1), 0) >= 0)
1000 q = stof(substring(crypto_getencryptlevel(s), 0, 1));
1001 if((q <= 0 && cvar("crypto_aeslevel") >= 3) || (q >= 3 && cvar("crypto_aeslevel") <= 0))
1003 theColor = SKINCOLOR_SERVERLIST_IMPOSSIBLE;
1004 theAlpha = SKINALPHA_SERVERLIST_IMPOSSIBLE;
1009 if(cvar("crypto_aeslevel") >= 2)
1014 if(cvar("crypto_aeslevel") >= 1)
1024 // 2: AES recommended but not available
1025 // 3: AES possible and will be used
1026 // 4: AES recommended and will be used
1030 vector iconSize = '0 0 0';
1031 iconSize_y = me.realFontSize_y * me.iconsSizeFactor;
1032 iconSize_x = me.realFontSize_x * me.iconsSizeFactor;
1034 vector iconPos = '0 0 0';
1035 iconPos_x = (me.columnIconsSize - 3 * iconSize_x) * 0.5;
1036 iconPos_y = (1 - iconSize_y) * 0.5;
1040 if not(me.seenIPv4 && me.seenIPv6)
1042 iconPos_x += iconSize_x * 0.5;
1044 else if(me.seenIPv4 && me.seenIPv6)
1048 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_ipv6"), 0); // PRECACHE_PIC_MIPMAP
1050 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_ipv4"), 0); // PRECACHE_PIC_MIPMAP
1052 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1053 iconPos_x += iconSize_x;
1058 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_aeslevel", ftos(q)), 0); // PRECACHE_PIC_MIPMAP
1059 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1061 iconPos_x += iconSize_x;
1063 if(modname == "Xonotic")
1067 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_pure1"), PRECACHE_PIC_MIPMAP);
1068 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1073 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_mod_", modname), PRECACHE_PIC_MIPMAP);
1074 if(draw_PictureSize(n) == '0 0 0')
1075 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_mod_"), PRECACHE_PIC_MIPMAP);
1077 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1079 draw_Picture(iconPos, n, iconSize, '1 1 1', SKINALPHA_SERVERLIST_ICON_NONPURE);
1081 iconPos_x += iconSize_x;
1083 if(sflags >= 0 && (sflags & SERVERFLAG_PLAYERSTATS))
1085 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_stats1"), 0); // PRECACHE_PIC_MIPMAP
1086 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
1088 iconPos_x += iconSize_x;
1092 draw_Text(me.realUpperMargin * eY + (me.columnPingOrigin + me.columnPingSize - draw_TextWidth(s, 0, me.realFontSize)) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1093 s = draw_TextShortenToWidth(gethostcachestring(SLIST_FIELD_NAME, cache), me.columnNameSize, 0, me.realFontSize);
1094 draw_Text(me.realUpperMargin * eY + me.columnNameOrigin * eX, s, me.realFontSize, theColor, theAlpha, 0);
1095 s = draw_TextShortenToWidth(gethostcachestring(SLIST_FIELD_MAP, cache), me.columnMapSize, 0, me.realFontSize);
1096 draw_Text(me.realUpperMargin * eY + (me.columnMapOrigin + (me.columnMapSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1097 s = draw_TextShortenToWidth(typestr, me.columnTypeSize, 0, me.realFontSize);
1098 draw_Text(me.realUpperMargin * eY + (me.columnTypeOrigin + (me.columnTypeSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1099 s = strcat(ftos(gethostcachenumber(SLIST_FIELD_NUMHUMANS, cache)), "/", ftos(gethostcachenumber(SLIST_FIELD_MAXPLAYERS, cache)));
1100 draw_Text(me.realUpperMargin * eY + (me.columnPlayersOrigin + (me.columnPlayersSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1103 float XonoticServerList_keyDown(entity me, float scan, float ascii, float shift)
1105 float i = XonoticServerList_MapItems(me.selectedItem);
1108 org = boxToGlobal(eY * (me.selectedItem * me.itemHeight - me.scrollPos), me.origin, me.size);
1109 sz = boxToGlobalSize(eY * me.itemHeight + eX * (1 - me.controlWidth), me.size);
1111 if(scan == K_ENTER || scan == K_KP_ENTER)
1113 ServerList_Connect_Click(NULL, me);
1116 else if(scan == K_MOUSE2 || scan == K_SPACE)
1118 if((me.nItems != 0) && (i >= 0))
1120 main.serverInfoDialog.loadServerInfo(main.serverInfoDialog, i);
1121 DialogOpenButton_Click_withCoords(me, main.serverInfoDialog, org, sz);
1126 else if(scan == K_INS || scan == K_MOUSE3 || scan == K_KP_INS)
1128 if((me.nItems != 0) && (i >= 0))
1130 ToggleFavorite(me.selectedServer);
1131 me.ipAddressBoxFocused = -1;
1136 else if(SUPER(XonoticServerList).keyDown(me, scan, ascii, shift))
1138 else if(!me.controlledTextbox)
1141 return me.controlledTextbox.keyDown(me.controlledTextbox, scan, ascii, shift);