]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/menu/xonotic/serverlist.c
aacad0e2f65e9eb6fcb79287dfdc15e220294537
[xonotic/xonotic-data.pk3dir.git] / qcsrc / menu / xonotic / serverlist.c
1 #ifdef INTERFACE
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
11         ATTRIB(XonoticServerList, iconsSizeFactor, float, 0.85)
12
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)
27
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)
53
54         ATTRIB(XonoticServerList, ipAddressBoxFocused, float, -1)
55
56         ATTRIB(XonoticServerList, seenIPv4, float, 0)
57         ATTRIB(XonoticServerList, seenIPv6, float, 0)
58 ENDCLASS(XonoticServerList)
59 entity makeXonoticServerList();
60 void ServerList_Connect_Click(entity btn, entity me);
61 void ServerList_ShowEmpty_Click(entity box, entity me);
62 void ServerList_ShowFull_Click(entity box, entity me);
63 void ServerList_Filter_Change(entity box, entity me);
64 void ServerList_Favorite_Click(entity btn, entity me);
65 void ServerList_Info_Click(entity btn, entity me);
66 void ServerList_Update_favoriteButton(entity btn, entity me);
67 #endif
68
69 #ifdef 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_ISFAVORITE;
85 void ServerList_UpdateFieldIDs()
86 {
87         SLIST_FIELD_CNAME = gethostcacheindexforkey( "cname" );
88         SLIST_FIELD_PING = gethostcacheindexforkey( "ping" );
89         SLIST_FIELD_GAME = gethostcacheindexforkey( "game" );
90         SLIST_FIELD_MOD = gethostcacheindexforkey( "mod" );
91         SLIST_FIELD_MAP = gethostcacheindexforkey( "map" );
92         SLIST_FIELD_NAME = gethostcacheindexforkey( "name" );
93         SLIST_FIELD_MAXPLAYERS = gethostcacheindexforkey( "maxplayers" );
94         SLIST_FIELD_NUMPLAYERS = gethostcacheindexforkey( "numplayers" );
95         SLIST_FIELD_NUMHUMANS = gethostcacheindexforkey( "numhumans" );
96         SLIST_FIELD_NUMBOTS = gethostcacheindexforkey( "numbots" );
97         SLIST_FIELD_PROTOCOL = gethostcacheindexforkey( "protocol" );
98         SLIST_FIELD_FREESLOTS = gethostcacheindexforkey( "freeslots" );
99         SLIST_FIELD_PLAYERS = gethostcacheindexforkey( "players" );
100         SLIST_FIELD_QCSTATUS = gethostcacheindexforkey( "qcstatus" );
101         SLIST_FIELD_ISFAVORITE = gethostcacheindexforkey( "isfavorite" );
102 }
103
104 float IsFavorite(string srv)
105 {
106         string p;
107         float i, n;
108         if(srv == "")
109                 return FALSE;
110         srv = netaddress_resolve(srv, 26000);
111         if(srv == "")
112                 return FALSE;
113         p = crypto_getidfp(srv);
114         n = tokenize_console(cvar_string("net_slist_favorites"));
115         for(i = 0; i < n; ++i)
116         {
117                 if(substring(argv(i), 0, 1) != "[" && strlen(argv(i)) == 44 && strstrofs(argv(i), ".", 0) < 0)
118                 {
119                         if(p)
120                                 if(argv(i) == p)
121                                         return TRUE;
122                 }
123                 else
124                 {
125                         if(srv == netaddress_resolve(argv(i), 26000))
126                                 return TRUE;
127                 }
128         }
129         return FALSE;
130 }
131
132 void ToggleFavorite(string srv)
133 {
134         string s, s0, s1, s2, srv_resolved, p;
135         float i, n, f;
136         srv_resolved = netaddress_resolve(srv, 26000);
137         p = crypto_getidfp(srv_resolved);
138         s = cvar_string("net_slist_favorites");
139         n = tokenize_console(s);
140         f = 0;
141         for(i = 0; i < n; ++i)
142         {
143                 if(substring(argv(i), 0, 1) != "[" && strlen(argv(i)) == 44 && strstrofs(argv(i), ".", 0) < 0)
144                 {
145                         if(p)
146                                 if(argv(i) != p)
147                                         continue;
148                 }
149                 else
150                 {
151                         if(srv_resolved != netaddress_resolve(argv(i), 26000))
152                                 continue;
153                 }
154                 s0 = s1 = s2 = "";
155                 if(i > 0)
156                         s0 = substring(s, 0, argv_end_index(i - 1));
157                 if(i < n-1)
158                         s2 = substring(s, argv_start_index(i + 1), -1);
159                 if(s0 != "" && s2 != "")
160                         s1 = " ";
161                 cvar_set("net_slist_favorites", strcat(s0, s1, s2));
162                 s = cvar_string("net_slist_favorites");
163                 n = tokenize_console(s);
164                 f = 1;
165                 --i;
166         }
167         
168         if(!f)
169         {
170                 s1 = "";
171                 if(s != "")
172                         s1 = " ";
173                 if(p)
174                         cvar_set("net_slist_favorites", strcat(s, s1, p));
175                 else
176                         cvar_set("net_slist_favorites", strcat(s, s1, srv));
177         }
178
179         resorthostcache();
180 }
181
182 void ServerList_Update_favoriteButton(entity btn, entity me)
183 {
184         if(IsFavorite(me.ipAddressBox.text))
185                 me.favoriteButton.setText(me.favoriteButton, _("Remove"));
186         else
187                 me.favoriteButton.setText(me.favoriteButton, _("Bookmark"));
188 }
189
190 entity makeXonoticServerList()
191 {
192         entity me;
193         me = spawnXonoticServerList();
194         me.configureXonoticServerList(me);
195         return me;
196 }
197 void XonoticServerList_configureXonoticServerList(entity me)
198 {
199         me.configureXonoticListBox(me);
200
201         ServerList_UpdateFieldIDs();
202
203         me.nItems = 0;
204 }
205 void XonoticServerList_setSelected(entity me, float i)
206 {
207         float save;
208         save = me.selectedItem;
209         SUPER(XonoticServerList).setSelected(me, i);
210         /*
211         if(me.selectedItem == save)
212                 return;
213         */
214         if(me.nItems == 0)
215                 return;
216         if(gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT) != me.nItems)
217                 return; // sorry, it would be wrong
218
219         if(me.selectedServer)
220                 strunzone(me.selectedServer);
221         me.selectedServer = strzone(gethostcachestring(SLIST_FIELD_CNAME, me.selectedItem));
222
223         me.ipAddressBox.setText(me.ipAddressBox, me.selectedServer);
224         me.ipAddressBox.cursorPos = strlen(me.selectedServer);
225         me.ipAddressBoxFocused = -1;
226 }
227 void XonoticServerList_refreshServerList(entity me, float mode)
228 {
229         // 0: just reparametrize
230         // 1: also ask for new servers
231         // 2: clear
232         //print("refresh of type ", ftos(mode), "\n");
233         /* if(mode == 2) // borken
234         {
235                 // clear list
236                 localcmd("net_slist\n");
237                 me.needsRefresh = 1; // net_slist kills sort order, so we need to restore it later
238         }
239         else */
240         {
241                 float m, o, i, n; // moin moin
242                 string s, typestr, modstr;
243                 s = me.filterString;
244
245                 m = strstrofs(s, ":", 0);
246                 if(m >= 0)
247                 {
248                         typestr = substring(s, 0, m);
249                         s = substring(s, m + 1, strlen(s) - m - 1);
250                         while(substring(s, 0, 1) == " ")
251                                 s = substring(s, 1, strlen(s) - 1);
252                 }
253                 else
254                         typestr = "";
255
256                 modstr = cvar_string("menu_slist_modfilter");
257
258                 m = SLIST_MASK_AND - 1;
259                 resethostcachemasks();
260
261                 // ping: reject negative ping (no idea why this happens in the first place, engine bug)
262                 sethostcachemasknumber(++m, SLIST_FIELD_PING, 0, SLIST_TEST_GREATEREQUAL);
263
264                 // show full button
265                 if(!me.filterShowFull)
266                 {
267                         sethostcachemasknumber(++m, SLIST_FIELD_FREESLOTS, 1, SLIST_TEST_GREATEREQUAL); // legacy
268                         sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, ":S0:", SLIST_TEST_NOTCONTAIN); // g_maxplayers support
269                 }
270
271                 // show empty button
272                 if(!me.filterShowEmpty)
273                         sethostcachemasknumber(++m, SLIST_FIELD_NUMHUMANS, 1, SLIST_TEST_GREATEREQUAL);
274
275                 // gametype filtering
276                 if(typestr != "")
277                         sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, strcat(typestr, ":"), SLIST_TEST_STARTSWITH);
278
279                 // mod filtering
280                 if(modstr != "")
281                 {
282                         if(substring(modstr, 0, 1) == "!")
283                                 sethostcachemaskstring(++m, SLIST_FIELD_MOD, resolvemod(substring(modstr, 1, strlen(modstr) - 1)), SLIST_TEST_NOTEQUAL);
284                         else
285                                 sethostcachemaskstring(++m, SLIST_FIELD_MOD, resolvemod(modstr), SLIST_TEST_EQUAL);
286                 }
287
288                 // server banning
289                 n = tokenizebyseparator(_Nex_ExtResponseSystem_BannedServers, " ");
290                 for(i = 0; i < n; ++i)
291                         sethostcachemaskstring(++m, SLIST_FIELD_CNAME, argv(i), SLIST_TEST_NOTEQUAL);
292
293                 m = SLIST_MASK_OR - 1;
294                 if(s != "")
295                 {
296                         sethostcachemaskstring(++m, SLIST_FIELD_NAME, s, SLIST_TEST_CONTAINS);
297                         sethostcachemaskstring(++m, SLIST_FIELD_MAP, s, SLIST_TEST_CONTAINS);
298                         sethostcachemaskstring(++m, SLIST_FIELD_PLAYERS, s, SLIST_TEST_CONTAINS);
299                         sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, strcat(s, ":"), SLIST_TEST_STARTSWITH);
300                 }
301                 o = 2; // favorites first
302                 if(me.currentSortOrder < 0)
303                         o |= 1; // descending
304                 sethostcachesort(me.currentSortField, o);
305                 resorthostcache();
306                 if(mode >= 1)
307                         refreshhostcache();
308         }
309 }
310 void XonoticServerList_focusEnter(entity me)
311 {
312         if(time < me.nextRefreshTime)
313         {
314                 //print("sorry, no refresh yet\n");
315                 return;
316         }
317         me.nextRefreshTime = time + 10;
318         me.refreshServerList(me, 1);
319 }
320 void XonoticServerList_draw(entity me)
321 {
322         float i, found, owned;
323
324         if(me.currentSortField == -1)
325         {
326                 me.setSortOrder(me, SLIST_FIELD_PING, +1);
327                 me.refreshServerList(me, 2);
328         }
329         else if(me.needsRefresh == 1)
330         {
331                 me.needsRefresh = 2; // delay by one frame to make sure "slist" has been executed
332         }
333         else if(me.needsRefresh == 2)
334         {
335                 me.needsRefresh = 0;
336                 me.refreshServerList(me, 0);
337         }
338
339         owned = ((me.selectedServer == me.ipAddressBox.text) && (me.ipAddressBox.text != ""));
340
341         me.nItems = gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT);
342
343         me.connectButton.disabled = ((me.nItems == 0) && (me.ipAddressBox.text == ""));
344         me.infoButton.disabled = ((me.nItems == 0) || !owned);
345         me.favoriteButton.disabled = ((me.nItems == 0) && (me.ipAddressBox.text == ""));
346
347         found = 0;
348         if(me.selectedServer)
349         {
350                 for(i = 0; i < me.nItems; ++i)
351                         if(gethostcachestring(SLIST_FIELD_CNAME, i) == me.selectedServer)
352                         {
353                                 if(i != me.selectedItem)
354                                 {
355                                         me.lastClickedServer = -1;
356                                         me.selectedItem = i;
357                                 }
358                                 found = 1;
359                                 break;
360                         }
361         }
362         if(!found)
363                 if(me.nItems > 0)
364                 {
365                         if(me.selectedItem >= me.nItems)
366                                 me.selectedItem = me.nItems - 1;
367                         if(me.selectedServer)
368                                 strunzone(me.selectedServer);
369                         me.selectedServer = strzone(gethostcachestring(SLIST_FIELD_CNAME, me.selectedItem));
370                 }
371
372         if(owned)
373         {
374                 if(me.selectedServer != me.ipAddressBox.text)
375                 {
376                         me.ipAddressBox.setText(me.ipAddressBox, me.selectedServer);
377                         me.ipAddressBox.cursorPos = strlen(me.selectedServer);
378                         me.ipAddressBoxFocused = -1;
379                 }
380         }
381
382         if(me.ipAddressBoxFocused != me.ipAddressBox.focused)
383         {
384                 if(me.ipAddressBox.focused || me.ipAddressBoxFocused < 0)
385                         ServerList_Update_favoriteButton(NULL, me);
386                 me.ipAddressBoxFocused = me.ipAddressBox.focused;
387         }
388
389         SUPER(XonoticServerList).draw(me);
390 }
391 void ServerList_PingSort_Click(entity btn, entity me)
392 {
393         me.setSortOrder(me, SLIST_FIELD_PING, +1);
394 }
395 void ServerList_NameSort_Click(entity btn, entity me)
396 {
397         me.setSortOrder(me, SLIST_FIELD_NAME, -1); // why?
398 }
399 void ServerList_MapSort_Click(entity btn, entity me)
400 {
401         me.setSortOrder(me, SLIST_FIELD_MAP, -1); // why?
402 }
403 void ServerList_PlayerSort_Click(entity btn, entity me)
404 {
405         me.setSortOrder(me, SLIST_FIELD_NUMHUMANS, -1);
406 }
407 void ServerList_TypeSort_Click(entity btn, entity me)
408 {
409         string s, t;
410         float i, m;
411         s = me.filterString;
412         m = strstrofs(s, ":", 0);
413         if(m >= 0)
414         {
415                 s = substring(s, 0, m);
416                 while(substring(s, m+1, 1) == " ") // skip spaces
417                         ++m;
418         }
419         else
420                 s = "";
421
422         for(i = 1; ; ++i) // 20 modes ought to be enough for anyone
423         {
424                 t = GametypeNameFromType(i);
425                 if(i > 1)
426                         if(t == GametypeNameFromType(0)) // it repeats (default case)
427                         {
428                                 // no type was found
429                                 // choose the first one
430                                 s = t;
431                                 break;
432                         }
433                 if(s == GametypeNameFromType(i))
434                 {
435                         // the type was found
436                         // choose the next one
437                         s = GametypeNameFromType(i + 1);
438                         if(s == GametypeNameFromType(0))
439                                 s = "";
440                         break;
441                 }
442         }
443
444         if(s != "")
445                 s = strcat(s, ":");
446         s = strcat(s, substring(me.filterString, m+1, strlen(me.filterString) - m - 1));
447
448         me.controlledTextbox.setText(me.controlledTextbox, s);
449         me.controlledTextbox.keyDown(me.controlledTextbox, K_END, 0, 0);
450         me.controlledTextbox.keyUp(me.controlledTextbox, K_END, 0, 0);
451         //ServerList_Filter_Change(me.controlledTextbox, me);
452 }
453 void ServerList_Filter_Change(entity box, entity me)
454 {
455         if(me.filterString)
456                 strunzone(me.filterString);
457         if(box.text != "")
458                 me.filterString = strzone(box.text);
459         else
460                 me.filterString = string_null;
461         me.refreshServerList(me, 0);
462
463         me.ipAddressBox.setText(me.ipAddressBox, "");
464         me.ipAddressBox.cursorPos = 0;
465         me.ipAddressBoxFocused = -1;
466 }
467 void ServerList_ShowEmpty_Click(entity box, entity me)
468 {
469         box.setChecked(box, me.filterShowEmpty = !me.filterShowEmpty);
470         me.refreshServerList(me, 0);
471
472         me.ipAddressBox.setText(me.ipAddressBox, "");
473         me.ipAddressBox.cursorPos = 0;
474         me.ipAddressBoxFocused = -1;
475 }
476 void ServerList_ShowFull_Click(entity box, entity me)
477 {
478         box.setChecked(box, me.filterShowFull = !me.filterShowFull);
479         me.refreshServerList(me, 0);
480
481         me.ipAddressBox.setText(me.ipAddressBox, "");
482         me.ipAddressBox.cursorPos = 0;
483         me.ipAddressBoxFocused = -1;
484 }
485 void XonoticServerList_setSortOrder(entity me, float fld, float direction)
486 {
487         if(me.currentSortField == fld)
488                 direction = -me.currentSortOrder;
489         me.currentSortOrder = direction;
490         me.currentSortField = fld;
491         me.sortButton1.forcePressed = (fld == SLIST_FIELD_PING);
492         me.sortButton2.forcePressed = (fld == SLIST_FIELD_NAME);
493         me.sortButton3.forcePressed = (fld == SLIST_FIELD_MAP);
494         me.sortButton4.forcePressed = 0;
495         me.sortButton5.forcePressed = (fld == SLIST_FIELD_NUMHUMANS);
496         me.selectedItem = 0;
497         if(me.selectedServer)
498                 strunzone(me.selectedServer);
499         me.selectedServer = string_null;
500         me.refreshServerList(me, 0);
501 }
502 void XonoticServerList_positionSortButton(entity me, entity btn, float theOrigin, float theSize, string theTitle, void(entity, entity) theFunc)
503 {
504         vector originInLBSpace, sizeInLBSpace;
505         originInLBSpace = eY * (-me.itemHeight);
506         sizeInLBSpace = eY * me.itemHeight + eX * (1 - me.controlWidth);
507
508         vector originInDialogSpace, sizeInDialogSpace;
509         originInDialogSpace = boxToGlobal(originInLBSpace, me.Container_origin, me.Container_size);
510         sizeInDialogSpace = boxToGlobalSize(sizeInLBSpace, me.Container_size);
511
512         btn.Container_origin_x = originInDialogSpace_x + sizeInDialogSpace_x * theOrigin;
513         btn.Container_size_x   =                         sizeInDialogSpace_x * theSize;
514         btn.setText(btn, theTitle);
515         btn.onClick = theFunc;
516         btn.onClickEntity = me;
517         btn.resized = 1;
518 }
519 void XonoticServerList_resizeNotify(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
520 {
521         SUPER(XonoticServerList).resizeNotify(me, relOrigin, relSize, absOrigin, absSize);
522
523         me.realFontSize_y = me.fontSize / (absSize_y * me.itemHeight);
524         me.realFontSize_x = me.fontSize / (absSize_x * (1 - me.controlWidth));
525         me.realUpperMargin = 0.5 * (1 - me.realFontSize_y);
526
527         me.columnIconsOrigin = 0;
528         me.columnIconsSize = me.realFontSize_x * 3 * me.iconsSizeFactor;
529         me.columnPingSize = me.realFontSize_x * 3;
530         me.columnMapSize = me.realFontSize_x * 10;
531         me.columnTypeSize = me.realFontSize_x * 4;
532         me.columnPlayersSize = me.realFontSize_x * 5;
533         me.columnNameSize = 1 - me.columnPlayersSize - me.columnMapSize - me.columnPingSize - me.columnIconsSize - me.columnTypeSize - 5 * me.realFontSize_x;
534         me.columnPingOrigin = me.columnIconsOrigin + me.columnIconsSize + me.realFontSize_x;
535         me.columnNameOrigin = me.columnPingOrigin + me.columnPingSize + me.realFontSize_x;
536         me.columnMapOrigin = me.columnNameOrigin + me.columnNameSize + me.realFontSize_x;
537         me.columnTypeOrigin = me.columnMapOrigin + me.columnMapSize + me.realFontSize_x;
538         me.columnPlayersOrigin = me.columnTypeOrigin + me.columnTypeSize + me.realFontSize_x;
539
540         me.positionSortButton(me, me.sortButton1, me.columnPingOrigin, me.columnPingSize, _("Ping"), ServerList_PingSort_Click);
541         me.positionSortButton(me, me.sortButton2, me.columnNameOrigin, me.columnNameSize, _("Host name"), ServerList_NameSort_Click);
542         me.positionSortButton(me, me.sortButton3, me.columnMapOrigin, me.columnMapSize, _("Map"), ServerList_MapSort_Click);
543         me.positionSortButton(me, me.sortButton4, me.columnTypeOrigin, me.columnTypeSize, _("Type"), ServerList_TypeSort_Click);
544         me.positionSortButton(me, me.sortButton5, me.columnPlayersOrigin, me.columnPlayersSize, _("Players"), ServerList_PlayerSort_Click);
545
546         float f;
547         f = me.currentSortField;
548         if(f >= 0)
549         {
550                 me.currentSortField = -1;
551                 me.setSortOrder(me, f, me.currentSortOrder); // force resetting the sort order
552         }
553 }
554 void ServerList_Connect_Click(entity btn, entity me)
555 {
556         if(me.ipAddressBox.text == "")
557                 localcmd("connect ", me.selectedServer, "\n");
558         else
559                 localcmd("connect ", me.ipAddressBox.text, "\n");
560 }
561 void ServerList_Favorite_Click(entity btn, entity me)
562 {
563         string ipstr;
564         ipstr = netaddress_resolve(me.ipAddressBox.text, 26000);
565         if(ipstr != "")
566         {
567                 ToggleFavorite(me.ipAddressBox.text);
568                 me.ipAddressBoxFocused = -1;
569         }
570 }
571 void ServerList_Info_Click(entity btn, entity me)
572 {
573         main.serverInfoDialog.loadServerInfo(main.serverInfoDialog, me.selectedItem);
574         DialogOpenButton_Click(me, main.serverInfoDialog);
575 }
576 void XonoticServerList_clickListBoxItem(entity me, float i, vector where)
577 {
578         if(i == me.lastClickedServer)
579                 if(time < me.lastClickedTime + 0.3)
580                 {
581                         // DOUBLE CLICK!
582                         ServerList_Connect_Click(NULL, me);
583                 }
584         me.lastClickedServer = i;
585         me.lastClickedTime = time;
586 }
587 void XonoticServerList_drawListBoxItem(entity me, float i, vector absSize, float isSelected)
588 {
589         // layout: Ping, Server name, Map name, NP, TP, MP
590         string s;
591         float p, q;
592         float isv4, isv6;
593         vector theColor;
594         float theAlpha;
595
596         if(isSelected)
597                 draw_Fill('0 0 0', '1 1 0', SKINCOLOR_LISTBOX_SELECTED, SKINALPHA_LISTBOX_SELECTED);
598
599         if(gethostcachenumber(SLIST_FIELD_FREESLOTS, i) <= 0)
600                 theAlpha = SKINALPHA_SERVERLIST_FULL;
601         else if(strstrofs(gethostcachestring(SLIST_FIELD_QCSTATUS, i), ":S0:", 0) >= 0)
602                 theAlpha = SKINALPHA_SERVERLIST_FULL; // g_maxplayers support
603         else if not(gethostcachenumber(SLIST_FIELD_NUMHUMANS, i))
604                 theAlpha = SKINALPHA_SERVERLIST_EMPTY;
605         else
606                 theAlpha = 1;
607
608         p = gethostcachenumber(SLIST_FIELD_PING, i);
609 #define PING_LOW 75
610 #define PING_MED 200
611 #define PING_HIGH 500
612         if(p < PING_LOW)
613                 theColor = SKINCOLOR_SERVERLIST_LOWPING + (SKINCOLOR_SERVERLIST_MEDPING - SKINCOLOR_SERVERLIST_LOWPING) * (p / PING_LOW);
614         else if(p < PING_MED)
615                 theColor = SKINCOLOR_SERVERLIST_MEDPING + (SKINCOLOR_SERVERLIST_HIGHPING - SKINCOLOR_SERVERLIST_MEDPING) * ((p - PING_LOW) / (PING_MED - PING_LOW));
616         else if(p < PING_HIGH)
617         {
618                 theColor = SKINCOLOR_SERVERLIST_HIGHPING;
619                 theAlpha *= 1 + (SKINALPHA_SERVERLIST_HIGHPING - 1) * ((p - PING_MED) / (PING_HIGH - PING_MED));
620         }
621         else
622         {
623                 theColor = eX;
624                 theAlpha *= SKINALPHA_SERVERLIST_HIGHPING;
625         }
626
627         if(gethostcachenumber(SLIST_FIELD_ISFAVORITE, i))
628         {
629                 theColor = theColor * (1 - SKINALPHA_SERVERLIST_FAVORITE) + SKINCOLOR_SERVERLIST_FAVORITE * SKINALPHA_SERVERLIST_FAVORITE;
630                 theAlpha = theAlpha * (1 - SKINALPHA_SERVERLIST_FAVORITE) + SKINALPHA_SERVERLIST_FAVORITE;
631         }
632
633         s = gethostcachestring(SLIST_FIELD_CNAME, i);
634
635         isv4 = isv6 = 0;
636         if(substring(s, 0, 1) == "[")
637         {
638                 isv6 = 1;
639                 me.seenIPv6 += 1;
640         }
641         else if(strstrofs("0123456789", substring(s, 0, 1), 0) >= 0)
642         {
643                 isv4 = 1;
644                 me.seenIPv4 += 1;
645         }
646
647         q = stof(substring(crypto_getencryptlevel(s), 0, 1));
648         if((q <= 0 && cvar("crypto_aeslevel") >= 3) || (q >= 3 && cvar("crypto_aeslevel") <= 0))
649         {
650                 theColor = SKINCOLOR_SERVERLIST_IMPOSSIBLE;
651                 theAlpha = SKINALPHA_SERVERLIST_IMPOSSIBLE;
652         }
653
654         if(q == 1)
655         {
656                 if(cvar("crypto_aeslevel") >= 2)
657                         q |= 4;
658         }
659         if(q == 2)
660         {
661                 if(cvar("crypto_aeslevel") >= 1)
662                         q |= 4;
663         }
664         if(q == 3)
665                 q = 5;
666         if(q >= 3)
667                 q -= 2;
668         // possible status:
669         // 0: crypto off
670         // 1: AES possible
671         // 2: AES recommended but not available
672         // 3: AES possible and will be used
673         // 4: AES recommended and will be used
674         // 5: AES required
675
676         s = gethostcachestring(SLIST_FIELD_QCSTATUS, i);
677         {
678                 vector iconSize;
679                 iconSize_y = me.realFontSize_y * me.iconsSizeFactor;
680                 iconSize_x = me.realFontSize_x * me.iconsSizeFactor;
681
682                 vector iconPos;
683                 iconPos_x = (me.columnIconsSize - 3 * iconSize_x) * 0.5;
684                 iconPos_y = (1 - iconSize_y) * 0.5;
685
686                 if not(me.seenIPv4 && me.seenIPv6)
687                 {
688                         iconPos_x += iconSize_x * 0.5;
689                 }
690                 else if(me.seenIPv4 && me.seenIPv6)
691                 {
692                         if(isv6)
693                                 draw_Picture(iconPos, strcat(SKINGFX_SERVERLIST_ICON, "_ipv6"), iconSize, '1 1 1', 1);
694                         else if(isv4)
695                                 draw_Picture(iconPos, strcat(SKINGFX_SERVERLIST_ICON, "_ipv4"), iconSize, '1 1 1', 1);
696                         iconPos_x += iconSize_x;
697                 }
698
699                 draw_Picture(iconPos, strcat(SKINGFX_SERVERLIST_ICON, "_aeslevel", ftos(q)), iconSize, '1 1 1', 1);
700                 iconPos_x += iconSize_x;
701
702                 draw_Picture(iconPos, strcat(SKINGFX_SERVERLIST_ICON, "_pure", ftos(strstrofs(s, ":P0:", 0) >= 0)), iconSize, '1 1 1', 1);
703                 iconPos_x += iconSize_x;
704         }
705
706         s = ftos(p);
707         draw_Text(me.realUpperMargin * eY + (me.columnPingOrigin + me.columnPingSize - draw_TextWidth(s, 0, me.realFontSize)) * eX, s, me.realFontSize, theColor, theAlpha, 0);
708         s = draw_TextShortenToWidth(gethostcachestring(SLIST_FIELD_NAME, i), me.columnNameSize, 0, me.realFontSize);
709         draw_Text(me.realUpperMargin * eY + me.columnNameOrigin * eX, s, me.realFontSize, theColor, theAlpha, 0);
710         s = draw_TextShortenToWidth(gethostcachestring(SLIST_FIELD_MAP, i), me.columnMapSize, 0, me.realFontSize);
711         draw_Text(me.realUpperMargin * eY + (me.columnMapOrigin + (me.columnMapSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
712         s = gethostcachestring(SLIST_FIELD_QCSTATUS, i);
713         p = strstrofs(s, ":", 0);
714         if(p >= 0)
715                 s = substring(s, 0, p);
716         else
717                 s = "";
718         s = draw_TextShortenToWidth(s, me.columnMapSize, 0, me.realFontSize);
719         draw_Text(me.realUpperMargin * eY + (me.columnTypeOrigin + (me.columnTypeSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
720         s = strcat(ftos(gethostcachenumber(SLIST_FIELD_NUMHUMANS, i)), "/", ftos(gethostcachenumber(SLIST_FIELD_MAXPLAYERS, i)));
721         draw_Text(me.realUpperMargin * eY + (me.columnPlayersOrigin + (me.columnPlayersSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
722 }
723
724 float XonoticServerList_keyDown(entity me, float scan, float ascii, float shift)
725 {
726         float i;
727         vector org, sz;
728
729         org = boxToGlobal(eY * (me.selectedItem * me.itemHeight - me.scrollPos), me.origin, me.size);
730         sz = boxToGlobalSize(eY * me.itemHeight + eX * (1 - me.controlWidth), me.size);
731
732         if(scan == K_ENTER || scan == K_KP_ENTER)
733         {
734                 ServerList_Connect_Click(NULL, me);
735                 return 1;
736         }
737         else if(scan == K_MOUSE2 || scan == K_SPACE)
738         {
739                 if(me.nItems != 0)
740                 {
741                         main.serverInfoDialog.loadServerInfo(main.serverInfoDialog, me.selectedItem);
742                         DialogOpenButton_Click_withCoords(me, main.serverInfoDialog, org, sz);
743                 }
744         }
745         else if(scan == K_INS || scan == K_MOUSE3 || scan == K_KP_INS)
746         {
747                 i = me.selectedItem;
748                 if(i < me.nItems)
749                 {
750                         ToggleFavorite(me.selectedServer);
751                         me.ipAddressBoxFocused = -1;
752                 }
753         }
754         else if(SUPER(XonoticServerList).keyDown(me, scan, ascii, shift))
755                 return 1;
756         else if(!me.controlledTextbox)
757                 return 0;
758         else
759                 return me.controlledTextbox.keyDown(me.controlledTextbox, scan, ascii, shift);
760 }
761 #endif