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