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