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