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