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 SLIST_MAX_CATEGORIES 4
91 float category_name[SLIST_MAX_CATEGORIES];
92 float category_item[SLIST_MAX_CATEGORIES];
95 SLIST_CATEGORY(1, SLIST_CAT_FAVORITED, 1, _("Favorites")) \
96 SLIST_CATEGORY(1, SLIST_CAT_RECOMMENDED, 2, _("Recommended")) \
97 SLIST_CATEGORY(1, SLIST_CAT_CORE, 3, _("Core")) \
98 SLIST_CATEGORY(1, SLIST_CAT_MODIFIED, 4, _("Modified"))
100 #define SLIST_CATEGORY(default,name,num,string) const float name = num;
102 #undef SLIST_CATEGORY
107 #ifdef IMPLEMENTATION
108 float m_getserverlistentrycategory(float entry)
110 //print("m_getserverlistentrycategory\n");
111 string modtype = gethostcachestring(SLIST_FIELD_MOD, entry);
115 s = gethostcachestring(SLIST_FIELD_QCSTATUS, entry);
116 m = tokenizebyseparator(s, ":");
120 // typestr = argv(0);
121 // versionstr = argv(1);
127 for(j = 2; j < m; ++j)
131 k = substring(argv(j), 0, 1);
132 v = substring(argv(j), 1, -1);
133 if(k == "P") { pure = stof(v); }
136 if(gethostcachenumber(SLIST_FIELD_ISFAVORITE, entry)) { return SLIST_CAT_FAVORITED; }
137 else if((modtype != "data") || (pure)) { return SLIST_CAT_MODIFIED; }
138 else { return SLIST_CAT_CORE; }
141 float XonoticServerList_MapItems(float num)
145 if not(totcat) { return num; } // there are no categories to process
147 for(i = 0, n = 1; n <= totcat; ++i, ++n)
149 //print(sprintf("num: %d, i: %d, totcat: %d, category_item[i]: %d\n", num, i, totcat, category_item[i]));
150 if(category_item[i] == (num - i)) { /*print("inserting cat... \\/\n");*/ return -category_name[i]; }
151 else if(n == totcat) { /*print("end item... \\/\n");*/ return (num - n); }
152 else if((num - i) <= category_item[n]) { /*print("next item... \\/\n");*/ return (num - n); }
155 error("wtf mapitems fail?"); // should not really be hit
156 return FALSE; // so compiler shuts up
159 void ServerList_UpdateFieldIDs()
161 SLIST_FIELD_CNAME = gethostcacheindexforkey( "cname" );
162 SLIST_FIELD_PING = gethostcacheindexforkey( "ping" );
163 SLIST_FIELD_GAME = gethostcacheindexforkey( "game" );
164 SLIST_FIELD_MOD = gethostcacheindexforkey( "mod" );
165 SLIST_FIELD_MAP = gethostcacheindexforkey( "map" );
166 SLIST_FIELD_NAME = gethostcacheindexforkey( "name" );
167 SLIST_FIELD_MAXPLAYERS = gethostcacheindexforkey( "maxplayers" );
168 SLIST_FIELD_NUMPLAYERS = gethostcacheindexforkey( "numplayers" );
169 SLIST_FIELD_NUMHUMANS = gethostcacheindexforkey( "numhumans" );
170 SLIST_FIELD_NUMBOTS = gethostcacheindexforkey( "numbots" );
171 SLIST_FIELD_PROTOCOL = gethostcacheindexforkey( "protocol" );
172 SLIST_FIELD_FREESLOTS = gethostcacheindexforkey( "freeslots" );
173 SLIST_FIELD_PLAYERS = gethostcacheindexforkey( "players" );
174 SLIST_FIELD_QCSTATUS = gethostcacheindexforkey( "qcstatus" );
175 SLIST_FIELD_CATEGORY = gethostcacheindexforkey( "category" );
176 SLIST_FIELD_ISFAVORITE = gethostcacheindexforkey( "isfavorite" );
179 float IsFavorite(string srv)
185 srv = netaddress_resolve(srv, 26000);
188 p = crypto_getidfp(srv);
189 n = tokenize_console(cvar_string("net_slist_favorites"));
190 for(i = 0; i < n; ++i)
192 if(substring(argv(i), 0, 1) != "[" && strlen(argv(i)) == 44 && strstrofs(argv(i), ".", 0) < 0)
200 if(srv == netaddress_resolve(argv(i), 26000))
207 void ToggleFavorite(string srv)
209 string s, s0, s1, s2, srv_resolved, p;
211 srv_resolved = netaddress_resolve(srv, 26000);
212 p = crypto_getidfp(srv_resolved);
213 s = cvar_string("net_slist_favorites");
214 n = tokenize_console(s);
216 for(i = 0; i < n; ++i)
218 if(substring(argv(i), 0, 1) != "[" && strlen(argv(i)) == 44 && strstrofs(argv(i), ".", 0) < 0)
226 if(srv_resolved != netaddress_resolve(argv(i), 26000))
231 s0 = substring(s, 0, argv_end_index(i - 1));
233 s2 = substring(s, argv_start_index(i + 1), -1);
234 if(s0 != "" && s2 != "")
236 cvar_set("net_slist_favorites", strcat(s0, s1, s2));
237 s = cvar_string("net_slist_favorites");
238 n = tokenize_console(s);
249 cvar_set("net_slist_favorites", strcat(s, s1, p));
251 cvar_set("net_slist_favorites", strcat(s, s1, srv));
257 void ServerList_Update_favoriteButton(entity btn, entity me)
259 if(IsFavorite(me.ipAddressBox.text))
260 me.favoriteButton.setText(me.favoriteButton, _("Remove"));
262 me.favoriteButton.setText(me.favoriteButton, _("Bookmark"));
265 entity makeXonoticServerList()
268 me = spawnXonoticServerList();
269 me.configureXonoticServerList(me);
272 void XonoticServerList_configureXonoticServerList(entity me)
274 me.configureXonoticListBox(me);
276 ServerList_UpdateFieldIDs();
280 void XonoticServerList_setSelected(entity me, float i)
283 save = me.selectedItem;
284 SUPER(XonoticServerList).setSelected(me, i);
286 if(me.selectedItem == save)
291 //if(XonoticServerList_MapItems(gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT)) != me.nItems)
292 // { error("^1XonoticServerList_setSelected(); ERROR: ^7Host cache viewcount mismatches nItems!\n"); return; } // sorry, it would be wrong
294 num = XonoticServerList_MapItems(me.selectedItem);
297 if(me.selectedServer)
298 strunzone(me.selectedServer);
299 me.selectedServer = strzone(gethostcachestring(SLIST_FIELD_CNAME, num));
301 me.ipAddressBox.setText(me.ipAddressBox, me.selectedServer);
302 me.ipAddressBox.cursorPos = strlen(me.selectedServer);
303 me.ipAddressBoxFocused = -1;
306 void XonoticServerList_refreshServerList(entity me, float mode)
308 // 0: just reparametrize
309 // 1: also ask for new servers
311 //print("refresh of type ", ftos(mode), "\n");
312 /* if(mode == 2) // borken
315 localcmd("net_slist\n");
316 me.needsRefresh = 1; // net_slist kills sort order, so we need to restore it later
322 string s, typestr, modstr;
325 m = strstrofs(s, ":", 0);
328 typestr = substring(s, 0, m);
329 s = substring(s, m + 1, strlen(s) - m - 1);
330 while(substring(s, 0, 1) == " ")
331 s = substring(s, 1, strlen(s) - 1);
336 modstr = cvar_string("menu_slist_modfilter");
338 m = SLIST_MASK_AND - 1;
339 resethostcachemasks();
341 // ping: reject negative ping (no idea why this happens in the first place, engine bug)
342 sethostcachemasknumber(++m, SLIST_FIELD_PING, 0, SLIST_TEST_GREATEREQUAL);
345 if(!me.filterShowFull)
347 sethostcachemasknumber(++m, SLIST_FIELD_FREESLOTS, 1, SLIST_TEST_GREATEREQUAL); // legacy
348 sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, ":S0:", SLIST_TEST_NOTCONTAIN); // g_maxplayers support
352 if(!me.filterShowEmpty)
353 sethostcachemasknumber(++m, SLIST_FIELD_NUMHUMANS, 1, SLIST_TEST_GREATEREQUAL);
355 // gametype filtering
357 sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, strcat(typestr, ":"), SLIST_TEST_STARTSWITH);
362 if(substring(modstr, 0, 1) == "!")
363 sethostcachemaskstring(++m, SLIST_FIELD_MOD, resolvemod(substring(modstr, 1, strlen(modstr) - 1)), SLIST_TEST_NOTEQUAL);
365 sethostcachemaskstring(++m, SLIST_FIELD_MOD, resolvemod(modstr), SLIST_TEST_EQUAL);
369 n = tokenizebyseparator(_Nex_ExtResponseSystem_BannedServers, " ");
370 for(i = 0; i < n; ++i)
372 sethostcachemaskstring(++m, SLIST_FIELD_CNAME, argv(i), SLIST_TEST_NOTSTARTSWITH);
374 m = SLIST_MASK_OR - 1;
377 sethostcachemaskstring(++m, SLIST_FIELD_NAME, s, SLIST_TEST_CONTAINS);
378 sethostcachemaskstring(++m, SLIST_FIELD_MAP, s, SLIST_TEST_CONTAINS);
379 sethostcachemaskstring(++m, SLIST_FIELD_PLAYERS, s, SLIST_TEST_CONTAINS);
380 sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, strcat(s, ":"), SLIST_TEST_STARTSWITH);
384 //listflags |= SLSF_FAVORITES;
385 listflags |= SLSF_CATEGORIES;
386 if(me.currentSortOrder < 0) { listflags |= SLSF_DESCENDING; }
387 sethostcachesort(me.currentSortField, listflags);
393 void XonoticServerList_focusEnter(entity me)
395 if(time < me.nextRefreshTime)
397 //print("sorry, no refresh yet\n");
400 me.nextRefreshTime = time + 10;
401 me.refreshServerList(me, 1);
404 void XonoticServerList_draw(entity me)
406 float i, found, owned, num;
408 if(_Nex_ExtResponseSystem_BannedServersNeedsRefresh)
412 _Nex_ExtResponseSystem_BannedServersNeedsRefresh = 0;
415 if(me.currentSortField == -1)
417 me.setSortOrder(me, SLIST_FIELD_PING, +1);
418 me.refreshServerList(me, 2);
420 else if(me.needsRefresh == 1)
422 me.needsRefresh = 2; // delay by one frame to make sure "slist" has been executed
424 else if(me.needsRefresh == 2)
427 me.refreshServerList(me, 0);
430 owned = ((me.selectedServer == me.ipAddressBox.text) && (me.ipAddressBox.text != ""));
432 for(i = 0; i < SLIST_MAX_CATEGORIES; ++i) { category_name[i] = -1; category_item[i] = -1; }
435 float itemcount = gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT);
436 //float visible = floor(me.scrollPos / me.itemHeight);
437 me.nItems = itemcount;
440 for(i = 0; i < itemcount; ++i)
442 cat = gethostcachenumber(SLIST_FIELD_CATEGORY, i);
447 category_name[totcat] = cat;
448 category_item[totcat] = i;
455 for(x = 0; x < totcat; ++x) { if(cat == category_name[x]) { found = 1; } }
458 category_name[totcat] = cat;
459 category_item[totcat] = i;
467 print(sprintf("^1SERVERLIST_DRAW^7: servercount: %d, nitems: %d\n", itemcount, me.nItems));
469 me.connectButton.disabled = ((me.nItems == 0) && (me.ipAddressBox.text == ""));
470 me.infoButton.disabled = ((me.nItems == 0) || !owned);
471 me.favoriteButton.disabled = ((me.nItems == 0) && (me.ipAddressBox.text == ""));
474 if(me.selectedServer)
476 for(i = 0; i < me.nItems; ++i)
478 num = XonoticServerList_MapItems(i);
481 if(gethostcachestring(SLIST_FIELD_CNAME, num) == me.selectedServer)
483 if(i != me.selectedItem)
485 me.lastClickedServer = -1;
498 if(me.selectedItem >= me.nItems) { me.selectedItem = me.nItems - 1; }
499 if(me.selectedServer) { strunzone(me.selectedServer); }
501 num = XonoticServerList_MapItems(me.selectedItem);
502 if(num >= 0) { me.selectedServer = strzone(gethostcachestring(SLIST_FIELD_CNAME, num)); }
508 if(me.selectedServer != me.ipAddressBox.text)
510 me.ipAddressBox.setText(me.ipAddressBox, me.selectedServer);
511 me.ipAddressBox.cursorPos = strlen(me.selectedServer);
512 me.ipAddressBoxFocused = -1;
516 if(me.ipAddressBoxFocused != me.ipAddressBox.focused)
518 if(me.ipAddressBox.focused || me.ipAddressBoxFocused < 0)
519 ServerList_Update_favoriteButton(NULL, me);
520 me.ipAddressBoxFocused = me.ipAddressBox.focused;
523 SUPER(XonoticServerList).draw(me);
525 void ServerList_PingSort_Click(entity btn, entity me)
527 me.setSortOrder(me, SLIST_FIELD_PING, +1);
529 void ServerList_NameSort_Click(entity btn, entity me)
531 me.setSortOrder(me, SLIST_FIELD_NAME, -1); // why?
533 void ServerList_MapSort_Click(entity btn, entity me)
535 me.setSortOrder(me, SLIST_FIELD_MAP, -1); // why?
537 void ServerList_PlayerSort_Click(entity btn, entity me)
539 me.setSortOrder(me, SLIST_FIELD_NUMHUMANS, -1);
541 void ServerList_TypeSort_Click(entity btn, entity me)
546 m = strstrofs(s, ":", 0);
549 s = substring(s, 0, m);
550 while(substring(s, m+1, 1) == " ") // skip spaces
556 for(i = 1; ; i *= 2) // 20 modes ought to be enough for anyone
558 t = MapInfo_Type_ToString(i);
560 if(t == "") // it repeats (default case)
563 // choose the first one
564 s = MapInfo_Type_ToString(1);
569 // the type was found
570 // choose the next one
571 s = MapInfo_Type_ToString(i * 2);
573 s = MapInfo_Type_ToString(1);
580 s = strcat(s, substring(me.filterString, m+1, strlen(me.filterString) - m - 1));
582 me.controlledTextbox.setText(me.controlledTextbox, s);
583 me.controlledTextbox.keyDown(me.controlledTextbox, K_END, 0, 0);
584 me.controlledTextbox.keyUp(me.controlledTextbox, K_END, 0, 0);
585 //ServerList_Filter_Change(me.controlledTextbox, me);
587 void ServerList_Filter_Change(entity box, entity me)
590 strunzone(me.filterString);
592 me.filterString = strzone(box.text);
594 me.filterString = string_null;
595 me.refreshServerList(me, 0);
597 me.ipAddressBox.setText(me.ipAddressBox, "");
598 me.ipAddressBox.cursorPos = 0;
599 me.ipAddressBoxFocused = -1;
601 void ServerList_ShowEmpty_Click(entity box, entity me)
603 box.setChecked(box, me.filterShowEmpty = !me.filterShowEmpty);
604 me.refreshServerList(me, 0);
606 me.ipAddressBox.setText(me.ipAddressBox, "");
607 me.ipAddressBox.cursorPos = 0;
608 me.ipAddressBoxFocused = -1;
610 void ServerList_ShowFull_Click(entity box, entity me)
612 box.setChecked(box, me.filterShowFull = !me.filterShowFull);
613 me.refreshServerList(me, 0);
615 me.ipAddressBox.setText(me.ipAddressBox, "");
616 me.ipAddressBox.cursorPos = 0;
617 me.ipAddressBoxFocused = -1;
619 void XonoticServerList_setSortOrder(entity me, float fld, float direction)
621 if(me.currentSortField == fld)
622 direction = -me.currentSortOrder;
623 me.currentSortOrder = direction;
624 me.currentSortField = fld;
625 me.sortButton1.forcePressed = (fld == SLIST_FIELD_PING);
626 me.sortButton2.forcePressed = (fld == SLIST_FIELD_NAME);
627 me.sortButton3.forcePressed = (fld == SLIST_FIELD_MAP);
628 me.sortButton4.forcePressed = 0;
629 me.sortButton5.forcePressed = (fld == SLIST_FIELD_NUMHUMANS);
631 if(me.selectedServer)
632 strunzone(me.selectedServer);
633 me.selectedServer = string_null;
634 me.refreshServerList(me, 0);
636 void XonoticServerList_positionSortButton(entity me, entity btn, float theOrigin, float theSize, string theTitle, void(entity, entity) theFunc)
638 vector originInLBSpace, sizeInLBSpace;
639 originInLBSpace = eY * (-me.itemHeight);
640 sizeInLBSpace = eY * me.itemHeight + eX * (1 - me.controlWidth);
642 vector originInDialogSpace, sizeInDialogSpace;
643 originInDialogSpace = boxToGlobal(originInLBSpace, me.Container_origin, me.Container_size);
644 sizeInDialogSpace = boxToGlobalSize(sizeInLBSpace, me.Container_size);
646 btn.Container_origin_x = originInDialogSpace_x + sizeInDialogSpace_x * theOrigin;
647 btn.Container_size_x = sizeInDialogSpace_x * theSize;
648 btn.setText(btn, theTitle);
649 btn.onClick = theFunc;
650 btn.onClickEntity = me;
653 void XonoticServerList_resizeNotify(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
655 SUPER(XonoticServerList).resizeNotify(me, relOrigin, relSize, absOrigin, absSize);
657 me.realFontSize_y = me.fontSize / (absSize_y * me.itemHeight);
658 me.realFontSize_x = me.fontSize / (absSize_x * (1 - me.controlWidth));
659 me.realUpperMargin = 0.5 * (1 - me.realFontSize_y);
661 me.columnIconsOrigin = 0;
662 me.columnIconsSize = me.realFontSize_x * 4 * me.iconsSizeFactor;
663 me.columnPingSize = me.realFontSize_x * 3;
664 me.columnMapSize = me.realFontSize_x * 10;
665 me.columnTypeSize = me.realFontSize_x * 4;
666 me.columnPlayersSize = me.realFontSize_x * 5;
667 me.columnNameSize = 1 - me.columnPlayersSize - me.columnMapSize - me.columnPingSize - me.columnIconsSize - me.columnTypeSize - 5 * me.realFontSize_x;
668 me.columnPingOrigin = me.columnIconsOrigin + me.columnIconsSize + me.realFontSize_x;
669 me.columnNameOrigin = me.columnPingOrigin + me.columnPingSize + me.realFontSize_x;
670 me.columnMapOrigin = me.columnNameOrigin + me.columnNameSize + me.realFontSize_x;
671 me.columnTypeOrigin = me.columnMapOrigin + me.columnMapSize + me.realFontSize_x;
672 me.columnPlayersOrigin = me.columnTypeOrigin + me.columnTypeSize + me.realFontSize_x;
674 me.positionSortButton(me, me.sortButton1, me.columnPingOrigin, me.columnPingSize, _("Ping"), ServerList_PingSort_Click);
675 me.positionSortButton(me, me.sortButton2, me.columnNameOrigin, me.columnNameSize, _("Host name"), ServerList_NameSort_Click);
676 me.positionSortButton(me, me.sortButton3, me.columnMapOrigin, me.columnMapSize, _("Map"), ServerList_MapSort_Click);
677 me.positionSortButton(me, me.sortButton4, me.columnTypeOrigin, me.columnTypeSize, _("Type"), ServerList_TypeSort_Click);
678 me.positionSortButton(me, me.sortButton5, me.columnPlayersOrigin, me.columnPlayersSize, _("Players"), ServerList_PlayerSort_Click);
681 f = me.currentSortField;
684 me.currentSortField = -1;
685 me.setSortOrder(me, f, me.currentSortOrder); // force resetting the sort order
688 void ServerList_Connect_Click(entity btn, entity me)
690 if(me.ipAddressBox.text == "")
691 localcmd("connect ", me.selectedServer, "\n");
693 localcmd("connect ", me.ipAddressBox.text, "\n");
695 void ServerList_Favorite_Click(entity btn, entity me)
698 ipstr = netaddress_resolve(me.ipAddressBox.text, 26000);
701 ToggleFavorite(me.ipAddressBox.text);
702 me.ipAddressBoxFocused = -1;
705 void ServerList_Info_Click(entity btn, entity me)
707 main.serverInfoDialog.loadServerInfo(main.serverInfoDialog, XonoticServerList_MapItems(me.selectedItem));
708 DialogOpenButton_Click(me, main.serverInfoDialog);
710 void XonoticServerList_clickListBoxItem(entity me, float i, vector where)
712 float num = XonoticServerList_MapItems(i);
713 if(num == me.lastClickedServer)
714 if(time < me.lastClickedTime + 0.3)
717 ServerList_Connect_Click(NULL, me);
719 me.lastClickedServer = num;
720 me.lastClickedTime = time;
722 void XonoticServerList_drawListBoxItem(entity me, float i, vector absSize, float isSelected)
724 // layout: Ping, Server name, Map name, NP, TP, MP
729 float m, pure, freeslots, j, sflags;
730 string s, typestr, versionstr, k, v, modname;
732 float cache = XonoticServerList_MapItems(i);
733 print(sprintf("time: %f, i: %d, cache: %d, nitems: %d\n", time, i, cache, me.nItems));
739 #define SLIST_CATEGORY(default,name,num,string) case num: { draw_Text(me.realUpperMargin * eY + (me.columnNameOrigin + (me.columnNameSize - draw_TextWidth(string, 0, me.realFontSize)) * 0.5) * eX, string, me.realFontSize, '1 1 1', SKINALPHA_TEXT, 0); return; }
741 #undef SLIST_CATEGORY
747 draw_Fill('0 0 0', '1 1 0', SKINCOLOR_LISTBOX_SELECTED, SKINALPHA_LISTBOX_SELECTED);
749 s = gethostcachestring(SLIST_FIELD_QCSTATUS, cache);
750 m = tokenizebyseparator(s, ":");
755 versionstr = argv(1);
761 for(j = 2; j < m; ++j)
765 k = substring(argv(j), 0, 1);
766 v = substring(argv(j), 1, -1);
777 #ifdef COMPAT_NO_MOD_IS_XONOTIC
783 SLIST_FIELD_MOD = gethostcacheindexforkey("mod");
784 s = gethostcachestring(SLIST_FIELD_MOD, cache);
786 if(modname == "Xonotic")
790 // list the mods here on which the pure server check actually works
791 if(modname != "Xonotic")
792 if(modname != "MinstaGib")
795 if(modname != "NewToys")
798 if(gethostcachenumber(SLIST_FIELD_FREESLOTS, cache) <= 0)
799 theAlpha = SKINALPHA_SERVERLIST_FULL;
800 else if(freeslots == 0)
801 theAlpha = SKINALPHA_SERVERLIST_FULL; // g_maxplayers support
802 else if not(gethostcachenumber(SLIST_FIELD_NUMHUMANS, cache))
803 theAlpha = SKINALPHA_SERVERLIST_EMPTY;
807 p = gethostcachenumber(SLIST_FIELD_PING, cache);
810 #define PING_HIGH 500
812 theColor = SKINCOLOR_SERVERLIST_LOWPING + (SKINCOLOR_SERVERLIST_MEDPING - SKINCOLOR_SERVERLIST_LOWPING) * (p / PING_LOW);
813 else if(p < PING_MED)
814 theColor = SKINCOLOR_SERVERLIST_MEDPING + (SKINCOLOR_SERVERLIST_HIGHPING - SKINCOLOR_SERVERLIST_MEDPING) * ((p - PING_LOW) / (PING_MED - PING_LOW));
815 else if(p < PING_HIGH)
817 theColor = SKINCOLOR_SERVERLIST_HIGHPING;
818 theAlpha *= 1 + (SKINALPHA_SERVERLIST_HIGHPING - 1) * ((p - PING_MED) / (PING_HIGH - PING_MED));
823 theAlpha *= SKINALPHA_SERVERLIST_HIGHPING;
826 if(gethostcachenumber(SLIST_FIELD_ISFAVORITE, cache))
828 theColor = theColor * (1 - SKINALPHA_SERVERLIST_FAVORITE) + SKINCOLOR_SERVERLIST_FAVORITE * SKINALPHA_SERVERLIST_FAVORITE;
829 theAlpha = theAlpha * (1 - SKINALPHA_SERVERLIST_FAVORITE) + SKINALPHA_SERVERLIST_FAVORITE;
832 s = gethostcachestring(SLIST_FIELD_CNAME, cache);
835 if(substring(s, 0, 1) == "[")
840 else if(strstrofs("0123456789", substring(s, 0, 1), 0) >= 0)
846 q = stof(substring(crypto_getencryptlevel(s), 0, 1));
847 if((q <= 0 && cvar("crypto_aeslevel") >= 3) || (q >= 3 && cvar("crypto_aeslevel") <= 0))
849 theColor = SKINCOLOR_SERVERLIST_IMPOSSIBLE;
850 theAlpha = SKINALPHA_SERVERLIST_IMPOSSIBLE;
855 if(cvar("crypto_aeslevel") >= 2)
860 if(cvar("crypto_aeslevel") >= 1)
870 // 2: AES recommended but not available
871 // 3: AES possible and will be used
872 // 4: AES recommended and will be used
876 vector iconSize = '0 0 0';
877 iconSize_y = me.realFontSize_y * me.iconsSizeFactor;
878 iconSize_x = me.realFontSize_x * me.iconsSizeFactor;
880 vector iconPos = '0 0 0';
881 iconPos_x = (me.columnIconsSize - 3 * iconSize_x) * 0.5;
882 iconPos_y = (1 - iconSize_y) * 0.5;
886 if not(me.seenIPv4 && me.seenIPv6)
888 iconPos_x += iconSize_x * 0.5;
890 else if(me.seenIPv4 && me.seenIPv6)
894 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_ipv6"), 0); // PRECACHE_PIC_MIPMAP
896 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_ipv4"), 0); // PRECACHE_PIC_MIPMAP
898 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
899 iconPos_x += iconSize_x;
904 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_aeslevel", ftos(q)), 0); // PRECACHE_PIC_MIPMAP
905 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
907 iconPos_x += iconSize_x;
909 if(modname == "Xonotic")
913 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_pure1"), PRECACHE_PIC_MIPMAP);
914 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
919 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_mod_", modname), PRECACHE_PIC_MIPMAP);
920 if(draw_PictureSize(n) == '0 0 0')
921 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_mod_"), PRECACHE_PIC_MIPMAP);
923 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
925 draw_Picture(iconPos, n, iconSize, '1 1 1', SKINALPHA_SERVERLIST_ICON_NONPURE);
927 iconPos_x += iconSize_x;
929 if(sflags >= 0 && (sflags & SERVERFLAG_PLAYERSTATS))
931 draw_PreloadPictureWithFlags(n = strcat(SKINGFX_SERVERLIST_ICON, "_stats1"), 0); // PRECACHE_PIC_MIPMAP
932 draw_Picture(iconPos, n, iconSize, '1 1 1', 1);
934 iconPos_x += iconSize_x;
938 draw_Text(me.realUpperMargin * eY + (me.columnPingOrigin + me.columnPingSize - draw_TextWidth(s, 0, me.realFontSize)) * eX, s, me.realFontSize, theColor, theAlpha, 0);
939 s = draw_TextShortenToWidth(gethostcachestring(SLIST_FIELD_NAME, cache), me.columnNameSize, 0, me.realFontSize);
940 draw_Text(me.realUpperMargin * eY + me.columnNameOrigin * eX, s, me.realFontSize, theColor, theAlpha, 0);
941 s = draw_TextShortenToWidth(gethostcachestring(SLIST_FIELD_MAP, cache), me.columnMapSize, 0, me.realFontSize);
942 draw_Text(me.realUpperMargin * eY + (me.columnMapOrigin + (me.columnMapSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
943 s = draw_TextShortenToWidth(typestr, me.columnTypeSize, 0, me.realFontSize);
944 draw_Text(me.realUpperMargin * eY + (me.columnTypeOrigin + (me.columnTypeSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
945 s = strcat(ftos(gethostcachenumber(SLIST_FIELD_NUMHUMANS, cache)), "/", ftos(gethostcachenumber(SLIST_FIELD_MAXPLAYERS, cache)));
946 draw_Text(me.realUpperMargin * eY + (me.columnPlayersOrigin + (me.columnPlayersSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
949 float XonoticServerList_keyDown(entity me, float scan, float ascii, float shift)
951 float i = XonoticServerList_MapItems(me.selectedItem);
954 org = boxToGlobal(eY * (me.selectedItem * me.itemHeight - me.scrollPos), me.origin, me.size);
955 sz = boxToGlobalSize(eY * me.itemHeight + eX * (1 - me.controlWidth), me.size);
957 if(scan == K_ENTER || scan == K_KP_ENTER)
959 ServerList_Connect_Click(NULL, me);
962 else if(scan == K_MOUSE2 || scan == K_SPACE)
964 if((me.nItems != 0) && (i >= 0))
966 main.serverInfoDialog.loadServerInfo(main.serverInfoDialog, i);
967 DialogOpenButton_Click_withCoords(me, main.serverInfoDialog, org, sz);
972 else if(scan == K_INS || scan == K_MOUSE3 || scan == K_KP_INS)
974 if((me.nItems != 0) && (i >= 0))
976 ToggleFavorite(me.selectedServer);
977 me.ipAddressBoxFocused = -1;
982 else if(SUPER(XonoticServerList).keyDown(me, scan, ascii, shift))
984 else if(!me.controlledTextbox)
987 return me.controlledTextbox.keyDown(me.controlledTextbox, scan, ascii, shift);