]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/menu/xonotic/serverlist.c
Merge remote branch 'origin/divVerent/typofix'
[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;
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                 if(!me.filterShowFull)
265                 {
266                         sethostcachemasknumber(++m, SLIST_FIELD_FREESLOTS, 1, SLIST_TEST_GREATEREQUAL); // legacy
267                         sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, ":S0:", SLIST_TEST_NOTCONTAIN); // g_maxplayers support
268                 }
269                 if(!me.filterShowEmpty)
270                         sethostcachemasknumber(++m, SLIST_FIELD_NUMHUMANS, 1, SLIST_TEST_GREATEREQUAL);
271                 if(typestr != "")
272                         sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, strcat(typestr, ":"), SLIST_TEST_STARTSWITH);
273                 if(modstr != "")
274                 {
275                         if(substring(modstr, 0, 1) == "!")
276                                 sethostcachemaskstring(++m, SLIST_FIELD_MOD, resolvemod(substring(modstr, 1, strlen(modstr) - 1)), SLIST_TEST_NOTEQUAL);
277                         else
278                                 sethostcachemaskstring(++m, SLIST_FIELD_MOD, resolvemod(modstr), SLIST_TEST_EQUAL);
279                 }
280                 m = SLIST_MASK_OR - 1;
281                 if(s != "")
282                 {
283                         sethostcachemaskstring(++m, SLIST_FIELD_NAME, s, SLIST_TEST_CONTAINS);
284                         sethostcachemaskstring(++m, SLIST_FIELD_MAP, s, SLIST_TEST_CONTAINS);
285                         sethostcachemaskstring(++m, SLIST_FIELD_PLAYERS, s, SLIST_TEST_CONTAINS);
286                         sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, strcat(s, ":"), SLIST_TEST_STARTSWITH);
287                 }
288                 o = 2; // favorites first
289                 if(me.currentSortOrder < 0)
290                         o |= 1; // descending
291                 sethostcachesort(me.currentSortField, o);
292                 resorthostcache();
293                 if(mode >= 1)
294                         refreshhostcache();
295         }
296 }
297 void XonoticServerList_focusEnter(entity me)
298 {
299         if(time < me.nextRefreshTime)
300         {
301                 //print("sorry, no refresh yet\n");
302                 return;
303         }
304         me.nextRefreshTime = time + 10;
305         me.refreshServerList(me, 1);
306 }
307 void XonoticServerList_draw(entity me)
308 {
309         float i, found, owned;
310
311         if(me.currentSortField == -1)
312         {
313                 me.setSortOrder(me, SLIST_FIELD_PING, +1);
314                 me.refreshServerList(me, 2);
315         }
316         else if(me.needsRefresh == 1)
317         {
318                 me.needsRefresh = 2; // delay by one frame to make sure "slist" has been executed
319         }
320         else if(me.needsRefresh == 2)
321         {
322                 me.needsRefresh = 0;
323                 me.refreshServerList(me, 0);
324         }
325
326         owned = ((me.selectedServer == me.ipAddressBox.text) && (me.ipAddressBox.text != ""));
327
328         me.nItems = gethostcachevalue(SLIST_HOSTCACHEVIEWCOUNT);
329
330         me.connectButton.disabled = ((me.nItems == 0) && (me.ipAddressBox.text == ""));
331         me.infoButton.disabled = ((me.nItems == 0) || !owned);
332         me.favoriteButton.disabled = ((me.nItems == 0) && (me.ipAddressBox.text == ""));
333
334         found = 0;
335         if(me.selectedServer)
336         {
337                 for(i = 0; i < me.nItems; ++i)
338                         if(gethostcachestring(SLIST_FIELD_CNAME, i) == me.selectedServer)
339                         {
340                                 if(i != me.selectedItem)
341                                 {
342                                         me.lastClickedServer = -1;
343                                         me.selectedItem = i;
344                                 }
345                                 found = 1;
346                                 break;
347                         }
348         }
349         if(!found)
350                 if(me.nItems > 0)
351                 {
352                         if(me.selectedItem >= me.nItems)
353                                 me.selectedItem = me.nItems - 1;
354                         if(me.selectedServer)
355                                 strunzone(me.selectedServer);
356                         me.selectedServer = strzone(gethostcachestring(SLIST_FIELD_CNAME, me.selectedItem));
357                 }
358
359         if(owned)
360         {
361                 if(me.selectedServer != me.ipAddressBox.text)
362                 {
363                         me.ipAddressBox.setText(me.ipAddressBox, me.selectedServer);
364                         me.ipAddressBox.cursorPos = strlen(me.selectedServer);
365                         me.ipAddressBoxFocused = -1;
366                 }
367         }
368
369         if(me.ipAddressBoxFocused != me.ipAddressBox.focused)
370         {
371                 if(me.ipAddressBox.focused || me.ipAddressBoxFocused < 0)
372                         ServerList_Update_favoriteButton(NULL, me);
373                 me.ipAddressBoxFocused = me.ipAddressBox.focused;
374         }
375
376         SUPER(XonoticServerList).draw(me);
377 }
378 void ServerList_PingSort_Click(entity btn, entity me)
379 {
380         me.setSortOrder(me, SLIST_FIELD_PING, +1);
381 }
382 void ServerList_NameSort_Click(entity btn, entity me)
383 {
384         me.setSortOrder(me, SLIST_FIELD_NAME, -1); // why?
385 }
386 void ServerList_MapSort_Click(entity btn, entity me)
387 {
388         me.setSortOrder(me, SLIST_FIELD_MAP, -1); // why?
389 }
390 void ServerList_PlayerSort_Click(entity btn, entity me)
391 {
392         me.setSortOrder(me, SLIST_FIELD_NUMHUMANS, -1);
393 }
394 void ServerList_TypeSort_Click(entity btn, entity me)
395 {
396         string s, t;
397         float i, m;
398         s = me.filterString;
399         m = strstrofs(s, ":", 0);
400         if(m >= 0)
401         {
402                 s = substring(s, 0, m);
403                 while(substring(s, m+1, 1) == " ") // skip spaces
404                         ++m;
405         }
406         else
407                 s = "";
408
409         for(i = 1; ; ++i) // 20 modes ought to be enough for anyone
410         {
411                 t = GametypeNameFromType(i);
412                 if(i > 1)
413                         if(t == GametypeNameFromType(0)) // it repeats (default case)
414                         {
415                                 // no type was found
416                                 // choose the first one
417                                 s = t;
418                                 break;
419                         }
420                 if(s == GametypeNameFromType(i))
421                 {
422                         // the type was found
423                         // choose the next one
424                         s = GametypeNameFromType(i + 1);
425                         if(s == GametypeNameFromType(0))
426                                 s = "";
427                         break;
428                 }
429         }
430
431         if(s != "")
432                 s = strcat(s, ":");
433         s = strcat(s, substring(me.filterString, m+1, strlen(me.filterString) - m - 1));
434
435         me.controlledTextbox.setText(me.controlledTextbox, s);
436         me.controlledTextbox.keyDown(me.controlledTextbox, K_END, 0, 0);
437         me.controlledTextbox.keyUp(me.controlledTextbox, K_END, 0, 0);
438         //ServerList_Filter_Change(me.controlledTextbox, me);
439 }
440 void ServerList_Filter_Change(entity box, entity me)
441 {
442         if(me.filterString)
443                 strunzone(me.filterString);
444         if(box.text != "")
445                 me.filterString = strzone(box.text);
446         else
447                 me.filterString = string_null;
448         me.refreshServerList(me, 0);
449
450         me.ipAddressBox.setText(me.ipAddressBox, "");
451         me.ipAddressBox.cursorPos = 0;
452         me.ipAddressBoxFocused = -1;
453 }
454 void ServerList_ShowEmpty_Click(entity box, entity me)
455 {
456         box.setChecked(box, me.filterShowEmpty = !me.filterShowEmpty);
457         me.refreshServerList(me, 0);
458
459         me.ipAddressBox.setText(me.ipAddressBox, "");
460         me.ipAddressBox.cursorPos = 0;
461         me.ipAddressBoxFocused = -1;
462 }
463 void ServerList_ShowFull_Click(entity box, entity me)
464 {
465         box.setChecked(box, me.filterShowFull = !me.filterShowFull);
466         me.refreshServerList(me, 0);
467
468         me.ipAddressBox.setText(me.ipAddressBox, "");
469         me.ipAddressBox.cursorPos = 0;
470         me.ipAddressBoxFocused = -1;
471 }
472 void XonoticServerList_setSortOrder(entity me, float field, float direction)
473 {
474         if(me.currentSortField == field)
475                 direction = -me.currentSortOrder;
476         me.currentSortOrder = direction;
477         me.currentSortField = field;
478         me.sortButton1.forcePressed = (field == SLIST_FIELD_PING);
479         me.sortButton2.forcePressed = (field == SLIST_FIELD_NAME);
480         me.sortButton3.forcePressed = (field == SLIST_FIELD_MAP);
481         me.sortButton4.forcePressed = 0;
482         me.sortButton5.forcePressed = (field == SLIST_FIELD_NUMHUMANS);
483         me.selectedItem = 0;
484         if(me.selectedServer)
485                 strunzone(me.selectedServer);
486         me.selectedServer = string_null;
487         me.refreshServerList(me, 0);
488 }
489 void XonoticServerList_positionSortButton(entity me, entity btn, float theOrigin, float theSize, string theTitle, void(entity, entity) theFunc)
490 {
491         vector originInLBSpace, sizeInLBSpace;
492         originInLBSpace = eY * (-me.itemHeight);
493         sizeInLBSpace = eY * me.itemHeight + eX * (1 - me.controlWidth);
494
495         vector originInDialogSpace, sizeInDialogSpace;
496         originInDialogSpace = boxToGlobal(originInLBSpace, me.Container_origin, me.Container_size);
497         sizeInDialogSpace = boxToGlobalSize(sizeInLBSpace, me.Container_size);
498
499         btn.Container_origin_x = originInDialogSpace_x + sizeInDialogSpace_x * theOrigin;
500         btn.Container_size_x   =                         sizeInDialogSpace_x * theSize;
501         btn.setText(btn, theTitle);
502         btn.onClick = theFunc;
503         btn.onClickEntity = me;
504         btn.resized = 1;
505 }
506 void XonoticServerList_resizeNotify(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
507 {
508         SUPER(XonoticServerList).resizeNotify(me, relOrigin, relSize, absOrigin, absSize);
509
510         me.realFontSize_y = me.fontSize / (absSize_y * me.itemHeight);
511         me.realFontSize_x = me.fontSize / (absSize_x * (1 - me.controlWidth));
512         me.realUpperMargin = 0.5 * (1 - me.realFontSize_y);
513
514         me.columnIconsOrigin = 0;
515         me.columnIconsSize = me.realFontSize_x * 3 * me.iconsSizeFactor;
516         me.columnPingSize = me.realFontSize_x * 3;
517         me.columnMapSize = me.realFontSize_x * 10;
518         me.columnTypeSize = me.realFontSize_x * 4;
519         me.columnPlayersSize = me.realFontSize_x * 5;
520         me.columnNameSize = 1 - me.columnPlayersSize - me.columnMapSize - me.columnPingSize - me.columnIconsSize - me.columnTypeSize - 5 * me.realFontSize_x;
521         me.columnPingOrigin = me.columnIconsOrigin + me.columnIconsSize + me.realFontSize_x;
522         me.columnNameOrigin = me.columnPingOrigin + me.columnPingSize + me.realFontSize_x;
523         me.columnMapOrigin = me.columnNameOrigin + me.columnNameSize + me.realFontSize_x;
524         me.columnTypeOrigin = me.columnMapOrigin + me.columnMapSize + me.realFontSize_x;
525         me.columnPlayersOrigin = me.columnTypeOrigin + me.columnTypeSize + me.realFontSize_x;
526
527         me.positionSortButton(me, me.sortButton1, me.columnPingOrigin, me.columnPingSize, _("Ping"), ServerList_PingSort_Click);
528         me.positionSortButton(me, me.sortButton2, me.columnNameOrigin, me.columnNameSize, _("Host name"), ServerList_NameSort_Click);
529         me.positionSortButton(me, me.sortButton3, me.columnMapOrigin, me.columnMapSize, _("Map"), ServerList_MapSort_Click);
530         me.positionSortButton(me, me.sortButton4, me.columnTypeOrigin, me.columnTypeSize, _("Type"), ServerList_TypeSort_Click);
531         me.positionSortButton(me, me.sortButton5, me.columnPlayersOrigin, me.columnPlayersSize, _("Players"), ServerList_PlayerSort_Click);
532
533         float f;
534         f = me.currentSortField;
535         if(f >= 0)
536         {
537                 me.currentSortField = -1;
538                 me.setSortOrder(me, f, me.currentSortOrder); // force resetting the sort order
539         }
540 }
541 void ServerList_Connect_Click(entity btn, entity me)
542 {
543         if(me.ipAddressBox.text == "")
544                 localcmd("connect ", me.selectedServer, "\n");
545         else
546                 localcmd("connect ", me.ipAddressBox.text, "\n");
547 }
548 void ServerList_Favorite_Click(entity btn, entity me)
549 {
550         string ipstr;
551         ipstr = netaddress_resolve(me.ipAddressBox.text, 26000);
552         if(ipstr != "")
553         {
554                 ToggleFavorite(me.ipAddressBox.text);
555                 me.ipAddressBoxFocused = -1;
556         }
557 }
558 void ServerList_Info_Click(entity btn, entity me)
559 {
560         main.serverInfoDialog.loadServerInfo(main.serverInfoDialog, me.selectedItem);
561         DialogOpenButton_Click(me, main.serverInfoDialog);
562 }
563 void XonoticServerList_clickListBoxItem(entity me, float i, vector where)
564 {
565         if(i == me.lastClickedServer)
566                 if(time < me.lastClickedTime + 0.3)
567                 {
568                         // DOUBLE CLICK!
569                         ServerList_Connect_Click(NULL, me);
570                 }
571         me.lastClickedServer = i;
572         me.lastClickedTime = time;
573 }
574 void XonoticServerList_drawListBoxItem(entity me, float i, vector absSize, float isSelected)
575 {
576         // layout: Ping, Server name, Map name, NP, TP, MP
577         string s;
578         float p, q;
579         float isv4, isv6;
580         vector theColor;
581         float theAlpha;
582
583         if(isSelected)
584                 draw_Fill('0 0 0', '1 1 0', SKINCOLOR_LISTBOX_SELECTED, SKINALPHA_LISTBOX_SELECTED);
585
586         if(gethostcachenumber(SLIST_FIELD_FREESLOTS, i) <= 0)
587                 theAlpha = SKINALPHA_SERVERLIST_FULL;
588         else if(strstrofs(gethostcachestring(SLIST_FIELD_QCSTATUS, i), ":S0:", 0) >= 0)
589                 theAlpha = SKINALPHA_SERVERLIST_FULL; // g_maxplayers support
590         else if not(gethostcachenumber(SLIST_FIELD_NUMHUMANS, i))
591                 theAlpha = SKINALPHA_SERVERLIST_EMPTY;
592         else
593                 theAlpha = 1;
594
595         p = gethostcachenumber(SLIST_FIELD_PING, i);
596 #define PING_LOW 75
597 #define PING_MED 200
598 #define PING_HIGH 500
599         if(p < PING_LOW)
600                 theColor = SKINCOLOR_SERVERLIST_LOWPING + (SKINCOLOR_SERVERLIST_MEDPING - SKINCOLOR_SERVERLIST_LOWPING) * (p / PING_LOW);
601         else if(p < PING_MED)
602                 theColor = SKINCOLOR_SERVERLIST_MEDPING + (SKINCOLOR_SERVERLIST_HIGHPING - SKINCOLOR_SERVERLIST_MEDPING) * ((p - PING_LOW) / (PING_MED - PING_LOW));
603         else if(p < PING_HIGH)
604         {
605                 theColor = SKINCOLOR_SERVERLIST_HIGHPING;
606                 theAlpha *= 1 + (SKINALPHA_SERVERLIST_HIGHPING - 1) * ((p - PING_MED) / (PING_HIGH - PING_MED));
607         }
608         else
609         {
610                 theColor = eX;
611                 theAlpha *= SKINALPHA_SERVERLIST_HIGHPING;
612         }
613
614         if(gethostcachenumber(SLIST_FIELD_ISFAVORITE, i))
615         {
616                 theColor = theColor * (1 - SKINALPHA_SERVERLIST_FAVORITE) + SKINCOLOR_SERVERLIST_FAVORITE * SKINALPHA_SERVERLIST_FAVORITE;
617                 theAlpha = theAlpha * (1 - SKINALPHA_SERVERLIST_FAVORITE) + SKINALPHA_SERVERLIST_FAVORITE;
618         }
619
620         s = gethostcachestring(SLIST_FIELD_CNAME, i);
621
622         isv4 = isv6 = 0;
623         if(substring(s, 0, 1) == "[")
624         {
625                 isv6 = 1;
626                 me.seenIPv6 += 1;
627         }
628         else if(strstrofs("0123456789", substring(s, 0, 1), 0) >= 0)
629         {
630                 isv4 = 1;
631                 me.seenIPv4 += 1;
632         }
633
634         q = stof(substring(crypto_getencryptlevel(s), 0, 1));
635         if((q <= 0 && cvar("crypto_aeslevel") >= 3) || (q >= 3 && cvar("crypto_aeslevel") <= 0))
636         {
637                 theColor = SKINCOLOR_SERVERLIST_IMPOSSIBLE;
638                 theAlpha = SKINALPHA_SERVERLIST_IMPOSSIBLE;
639         }
640
641         if(q == 1)
642         {
643                 if(cvar("crypto_aeslevel") >= 2)
644                         q |= 4;
645         }
646         if(q == 2)
647         {
648                 if(cvar("crypto_aeslevel") >= 1)
649                         q |= 4;
650         }
651         if(q == 3)
652                 q = 5;
653         if(q >= 3)
654                 q -= 2;
655         // possible status:
656         // 0: crypto off
657         // 1: AES possible
658         // 2: AES recommended but not available
659         // 3: AES possible and will be used
660         // 4: AES recommended and will be used
661         // 5: AES required
662
663         s = gethostcachestring(SLIST_FIELD_QCSTATUS, i);
664         {
665                 vector iconSize;
666                 iconSize_y = me.realFontSize_y * me.iconsSizeFactor;
667                 iconSize_x = me.realFontSize_x * me.iconsSizeFactor;
668
669                 vector iconPos;
670                 iconPos_x = (me.columnIconsSize - 3 * iconSize_x) * 0.5;
671                 iconPos_y = (1 - iconSize_y) * 0.5;
672
673                 if not(me.seenIPv4 && me.seenIPv6)
674                 {
675                         iconPos_x += iconSize_x * 0.5;
676                 }
677                 else if(me.seenIPv4 && me.seenIPv6)
678                 {
679                         if(isv6)
680                                 draw_Picture(iconPos, strcat(SKINGFX_SERVERLIST_ICON, "_ipv6"), iconSize, '1 1 1', 1);
681                         else if(isv4)
682                                 draw_Picture(iconPos, strcat(SKINGFX_SERVERLIST_ICON, "_ipv4"), iconSize, '1 1 1', 1);
683                         iconPos_x += iconSize_x;
684                 }
685
686                 draw_Picture(iconPos, strcat(SKINGFX_SERVERLIST_ICON, "_aeslevel", ftos(q)), iconSize, '1 1 1', 1);
687                 iconPos_x += iconSize_x;
688
689                 draw_Picture(iconPos, strcat(SKINGFX_SERVERLIST_ICON, "_pure", ftos(strstrofs(s, ":P0:", 0) >= 0)), iconSize, '1 1 1', 1);
690                 iconPos_x += iconSize_x;
691         }
692
693         s = ftos(p);
694         draw_Text(me.realUpperMargin * eY + (me.columnPingOrigin + me.columnPingSize - draw_TextWidth(s, 0, me.realFontSize)) * eX, s, me.realFontSize, theColor, theAlpha, 0);
695         s = draw_TextShortenToWidth(gethostcachestring(SLIST_FIELD_NAME, i), me.columnNameSize, 0, me.realFontSize);
696         draw_Text(me.realUpperMargin * eY + me.columnNameOrigin * eX, s, me.realFontSize, theColor, theAlpha, 0);
697         s = draw_TextShortenToWidth(gethostcachestring(SLIST_FIELD_MAP, i), me.columnMapSize, 0, me.realFontSize);
698         draw_Text(me.realUpperMargin * eY + (me.columnMapOrigin + (me.columnMapSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
699         s = gethostcachestring(SLIST_FIELD_QCSTATUS, i);
700         p = strstrofs(s, ":", 0);
701         if(p >= 0)
702                 s = substring(s, 0, p);
703         else
704                 s = "";
705         s = draw_TextShortenToWidth(s, me.columnMapSize, 0, me.realFontSize);
706         draw_Text(me.realUpperMargin * eY + (me.columnTypeOrigin + (me.columnTypeSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
707         s = strcat(ftos(gethostcachenumber(SLIST_FIELD_NUMHUMANS, i)), "/", ftos(gethostcachenumber(SLIST_FIELD_MAXPLAYERS, i)));
708         draw_Text(me.realUpperMargin * eY + (me.columnPlayersOrigin + (me.columnPlayersSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
709 }
710
711 float XonoticServerList_keyDown(entity me, float scan, float ascii, float shift)
712 {
713         float i;
714         vector org, sz;
715
716         org = boxToGlobal(eY * (me.selectedItem * me.itemHeight - me.scrollPos), me.origin, me.size);
717         sz = boxToGlobalSize(eY * me.itemHeight + eX * (1 - me.controlWidth), me.size);
718
719         if(scan == K_ENTER || scan == K_KP_ENTER)
720         {
721                 ServerList_Connect_Click(NULL, me);
722                 return 1;
723         }
724         else if(scan == K_MOUSE2 || scan == K_SPACE)
725         {
726                 if(me.nItems != 0)
727                 {
728                         main.serverInfoDialog.loadServerInfo(main.serverInfoDialog, me.selectedItem);
729                         DialogOpenButton_Click_withCoords(me, main.serverInfoDialog, org, sz);
730                 }
731         }
732         else if(scan == K_INS || scan == K_MOUSE3 || scan == K_KP_INS)
733         {
734                 i = me.selectedItem;
735                 if(i < me.nItems)
736                 {
737                         ToggleFavorite(me.selectedServer);
738                         me.ipAddressBoxFocused = -1;
739                 }
740         }
741         else if(SUPER(XonoticServerList).keyDown(me, scan, ascii, shift))
742                 return 1;
743         else if(!me.controlledTextbox)
744                 return 0;
745         else
746                 return me.controlledTextbox.keyDown(me.controlledTextbox, scan, ascii, shift);
747 }
748 #endif