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