]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/menu/xonotic/serverlist.c
Merge remote-tracking branch 'origin/master' into samual/updatecommands
[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) // 20 modes ought to be enough for anyone
431         {
432                 t = GametypeNameFromType(i);
433                 if(i > 1)
434                         if(t == GametypeNameFromType(0)) // it repeats (default case)
435                         {
436                                 // no type was found
437                                 // choose the first one
438                                 s = t;
439                                 break;
440                         }
441                 if(s == GametypeNameFromType(i))
442                 {
443                         // the type was found
444                         // choose the next one
445                         s = GametypeNameFromType(i + 1);
446                         if(s == GametypeNameFromType(0))
447                                 s = "";
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 * 3 * 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         string s;
599         float p, q;
600         float isv4, isv6;
601         vector theColor;
602         float theAlpha;
603
604         if(isSelected)
605                 draw_Fill('0 0 0', '1 1 0', SKINCOLOR_LISTBOX_SELECTED, SKINALPHA_LISTBOX_SELECTED);
606
607         if(gethostcachenumber(SLIST_FIELD_FREESLOTS, i) <= 0)
608                 theAlpha = SKINALPHA_SERVERLIST_FULL;
609         else if(strstrofs(gethostcachestring(SLIST_FIELD_QCSTATUS, i), ":S0:", 0) >= 0)
610                 theAlpha = SKINALPHA_SERVERLIST_FULL; // g_maxplayers support
611         else if not(gethostcachenumber(SLIST_FIELD_NUMHUMANS, i))
612                 theAlpha = SKINALPHA_SERVERLIST_EMPTY;
613         else
614                 theAlpha = 1;
615
616         p = gethostcachenumber(SLIST_FIELD_PING, i);
617 #define PING_LOW 75
618 #define PING_MED 200
619 #define PING_HIGH 500
620         if(p < PING_LOW)
621                 theColor = SKINCOLOR_SERVERLIST_LOWPING + (SKINCOLOR_SERVERLIST_MEDPING - SKINCOLOR_SERVERLIST_LOWPING) * (p / PING_LOW);
622         else if(p < PING_MED)
623                 theColor = SKINCOLOR_SERVERLIST_MEDPING + (SKINCOLOR_SERVERLIST_HIGHPING - SKINCOLOR_SERVERLIST_MEDPING) * ((p - PING_LOW) / (PING_MED - PING_LOW));
624         else if(p < PING_HIGH)
625         {
626                 theColor = SKINCOLOR_SERVERLIST_HIGHPING;
627                 theAlpha *= 1 + (SKINALPHA_SERVERLIST_HIGHPING - 1) * ((p - PING_MED) / (PING_HIGH - PING_MED));
628         }
629         else
630         {
631                 theColor = eX;
632                 theAlpha *= SKINALPHA_SERVERLIST_HIGHPING;
633         }
634
635         if(gethostcachenumber(SLIST_FIELD_ISFAVORITE, i))
636         {
637                 theColor = theColor * (1 - SKINALPHA_SERVERLIST_FAVORITE) + SKINCOLOR_SERVERLIST_FAVORITE * SKINALPHA_SERVERLIST_FAVORITE;
638                 theAlpha = theAlpha * (1 - SKINALPHA_SERVERLIST_FAVORITE) + SKINALPHA_SERVERLIST_FAVORITE;
639         }
640
641         s = gethostcachestring(SLIST_FIELD_CNAME, i);
642
643         isv4 = isv6 = 0;
644         if(substring(s, 0, 1) == "[")
645         {
646                 isv6 = 1;
647                 me.seenIPv6 += 1;
648         }
649         else if(strstrofs("0123456789", substring(s, 0, 1), 0) >= 0)
650         {
651                 isv4 = 1;
652                 me.seenIPv4 += 1;
653         }
654
655         q = stof(substring(crypto_getencryptlevel(s), 0, 1));
656         if((q <= 0 && cvar("crypto_aeslevel") >= 3) || (q >= 3 && cvar("crypto_aeslevel") <= 0))
657         {
658                 theColor = SKINCOLOR_SERVERLIST_IMPOSSIBLE;
659                 theAlpha = SKINALPHA_SERVERLIST_IMPOSSIBLE;
660         }
661
662         if(q == 1)
663         {
664                 if(cvar("crypto_aeslevel") >= 2)
665                         q |= 4;
666         }
667         if(q == 2)
668         {
669                 if(cvar("crypto_aeslevel") >= 1)
670                         q |= 4;
671         }
672         if(q == 3)
673                 q = 5;
674         if(q >= 3)
675                 q -= 2;
676         // possible status:
677         // 0: crypto off
678         // 1: AES possible
679         // 2: AES recommended but not available
680         // 3: AES possible and will be used
681         // 4: AES recommended and will be used
682         // 5: AES required
683
684         s = gethostcachestring(SLIST_FIELD_QCSTATUS, i);
685         {
686                 vector iconSize;
687                 iconSize_y = me.realFontSize_y * me.iconsSizeFactor;
688                 iconSize_x = me.realFontSize_x * me.iconsSizeFactor;
689
690                 vector iconPos;
691                 iconPos_x = (me.columnIconsSize - 3 * iconSize_x) * 0.5;
692                 iconPos_y = (1 - iconSize_y) * 0.5;
693
694                 if not(me.seenIPv4 && me.seenIPv6)
695                 {
696                         iconPos_x += iconSize_x * 0.5;
697                 }
698                 else if(me.seenIPv4 && me.seenIPv6)
699                 {
700                         if(isv6)
701                                 draw_Picture(iconPos, strcat(SKINGFX_SERVERLIST_ICON, "_ipv6"), iconSize, '1 1 1', 1);
702                         else if(isv4)
703                                 draw_Picture(iconPos, strcat(SKINGFX_SERVERLIST_ICON, "_ipv4"), iconSize, '1 1 1', 1);
704                         iconPos_x += iconSize_x;
705                 }
706
707                 draw_Picture(iconPos, strcat(SKINGFX_SERVERLIST_ICON, "_aeslevel", ftos(q)), iconSize, '1 1 1', 1);
708                 iconPos_x += iconSize_x;
709
710                 draw_Picture(iconPos, strcat(SKINGFX_SERVERLIST_ICON, "_pure", ftos(strstrofs(s, ":P0:", 0) >= 0)), iconSize, '1 1 1', 1);
711                 iconPos_x += iconSize_x;
712         }
713
714         s = ftos(p);
715         draw_Text(me.realUpperMargin * eY + (me.columnPingOrigin + me.columnPingSize - draw_TextWidth(s, 0, me.realFontSize)) * eX, s, me.realFontSize, theColor, theAlpha, 0);
716         s = draw_TextShortenToWidth(gethostcachestring(SLIST_FIELD_NAME, i), me.columnNameSize, 0, me.realFontSize);
717         draw_Text(me.realUpperMargin * eY + me.columnNameOrigin * eX, s, me.realFontSize, theColor, theAlpha, 0);
718         s = draw_TextShortenToWidth(gethostcachestring(SLIST_FIELD_MAP, i), me.columnMapSize, 0, me.realFontSize);
719         draw_Text(me.realUpperMargin * eY + (me.columnMapOrigin + (me.columnMapSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
720         s = gethostcachestring(SLIST_FIELD_QCSTATUS, i);
721         p = strstrofs(s, ":", 0);
722         if(p >= 0)
723                 s = substring(s, 0, p);
724         else
725                 s = "";
726         s = draw_TextShortenToWidth(s, me.columnMapSize, 0, me.realFontSize);
727         draw_Text(me.realUpperMargin * eY + (me.columnTypeOrigin + (me.columnTypeSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
728         s = strcat(ftos(gethostcachenumber(SLIST_FIELD_NUMHUMANS, i)), "/", ftos(gethostcachenumber(SLIST_FIELD_MAXPLAYERS, i)));
729         draw_Text(me.realUpperMargin * eY + (me.columnPlayersOrigin + (me.columnPlayersSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
730 }
731
732 float XonoticServerList_keyDown(entity me, float scan, float ascii, float shift)
733 {
734         float i;
735         vector org, sz;
736
737         org = boxToGlobal(eY * (me.selectedItem * me.itemHeight - me.scrollPos), me.origin, me.size);
738         sz = boxToGlobalSize(eY * me.itemHeight + eX * (1 - me.controlWidth), me.size);
739
740         if(scan == K_ENTER || scan == K_KP_ENTER)
741         {
742                 ServerList_Connect_Click(NULL, me);
743                 return 1;
744         }
745         else if(scan == K_MOUSE2 || scan == K_SPACE)
746         {
747                 if(me.nItems != 0)
748                 {
749                         main.serverInfoDialog.loadServerInfo(main.serverInfoDialog, me.selectedItem);
750                         DialogOpenButton_Click_withCoords(me, main.serverInfoDialog, org, sz);
751                 }
752         }
753         else if(scan == K_INS || scan == K_MOUSE3 || scan == K_KP_INS)
754         {
755                 i = me.selectedItem;
756                 if(i < me.nItems)
757                 {
758                         ToggleFavorite(me.selectedServer);
759                         me.ipAddressBoxFocused = -1;
760                 }
761         }
762         else if(SUPER(XonoticServerList).keyDown(me, scan, ascii, shift))
763                 return 1;
764         else if(!me.controlledTextbox)
765                 return 0;
766         else
767                 return me.controlledTextbox.keyDown(me.controlledTextbox, scan, ascii, shift);
768 }
769 #endif