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