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