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