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