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