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