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