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