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