Xonotic
serverlist.qc
Go to the documentation of this file.
1 #include "serverlist.qh"
2 
3 #include "checkbox.qh"
4 #include "inputbox.qh"
5 #include "mainwindow.qh"
7 #include <common/mapinfo.qh>
8 
9 #define IsFavorite(srv) IsServerInList(cvar_string("net_slist_favorites"), srv)
10 #define IsPromoted(srv) IsServerInList(_Nex_ExtResponseSystem_PromotedServers, srv)
11 #define IsRecommended(srv) IsServerInList(_Nex_ExtResponseSystem_RecommendedServers, srv)
12 
13 float m_gethostcachecategory(float entry) { return CategoryOverride(CategoryForEntry(entry)); }
14 
15 void SL_ProcessCategoryOverrides(.string override_field_string, .float override_field)
16 {
17  for (int i = 0; i < category_ent_count; ++i)
18  {
19  string s = categories[i].override_field_string;
20  if (s != "" && s != categories[i].cat_name)
21  {
22  int catnum = 0;
23  for (int x = 0; x < category_ent_count; ++x)
24  {
25  if(categories[x].cat_name == s)
26  {
27  catnum = x + 1;
28  break;
29  }
30  }
31  if (catnum)
32  {
33  strfree(categories[i].override_field_string);
34  categories[i].override_field = catnum;
35  continue;
36  }
37  LOG_INFOF("RegisterSLCategories(): Improper override '%s' for category '%s'!", s, categories[i].cat_name);
38  }
39  strfree(categories[i].override_field_string);
40  categories[i].override_field = 0;
41  }
42 }
43 
45 {
46  entity cat;
47  #define SLIST_CATEGORY(name,enoverride,dioverride,str) \
48  SET_FIELD_COUNT(name, CATEGORY_FIRST, category_ent_count) \
49  CHECK_MAX_COUNT(name, MAX_CATEGORIES, category_ent_count, "SLIST_CATEGORY") \
50  cat = categories[name - 1] = new(slist_category); \
51  cat.cat_name = #name; \
52  cat.cat_enoverride_string = strzone(SLIST_CATEGORY_AUTOCVAR(name)); \
53  cat.cat_dioverride_string = strzone(dioverride); \
54  cat.cat_string = strzone(str);
56  #undef SLIST_CATEGORY
57 
60 }
61 
62 // Supporting Functions
64 {
65  if((catnum > 0) && (catnum <= category_ent_count))
66  {
67  return categories[catnum - 1];
68  }
69  else
70  {
71  error(sprintf("RetrieveCategoryEnt(%d): Improper category number!\n", catnum));
72  return NULL;
73  }
74 }
75 
76 bool IsServerInList(string list, string srv)
77 {
78  if(srv == "")
79  return false;
80  srv = netaddress_resolve(srv, 26000);
81  if(srv == "")
82  return false;
83  string p = crypto_getidfp(srv);
84  int n = tokenize_console(list);
85  for(int i = 0; i < n; ++i)
86  {
87  if(substring(argv(i), 0, 1) != "[" && strlen(argv(i)) == 44 && strstrofs(argv(i), ".", 0) < 0)
88  {
89  if(p && argv(i) == p)
90  return true;
91  }
92  else
93  {
94  if(srv == netaddress_resolve(argv(i), 26000))
95  return true;
96  }
97  }
98  return false;
99 }
100 
101 int CategoryOverride(int cat)
102 {
103  entity catent = RetrieveCategoryEnt(cat);
104  if(catent)
105  {
106  int override = (autocvar_menu_slist_categories ? catent.cat_enoverride : catent.cat_dioverride);
107  if(override) { return override; }
108  else { return cat; }
109  }
110  else
111  {
112  error(sprintf("CategoryOverride(%d): Improper category number!\n", cat));
113  return cat;
114  }
115 }
116 
117 int CategoryForEntry(int entry)
118 {
119  string s, k, v, modtype = "";
120  int j, m, impure = 0, freeslots = 0, sflags = 0;
121  s = gethostcachestring(SLIST_FIELD_QCSTATUS, entry);
122  m = tokenizebyseparator(s, ":");
123 
124  for(j = 2; j < m; ++j)
125  {
126  if(argv(j) == "") { break; }
127  k = substring(argv(j), 0, 1);
128  v = substring(argv(j), 1, -1);
129  switch(k)
130  {
131  case "P": { impure = stof(v); break; }
132  case "S": { freeslots = stof(v); break; }
133  case "F": { sflags = stof(v); break; }
134  case "M": { modtype = strtolower(v); break; }
135  }
136  }
137 
138  if(modtype != "xonotic") { impure += autocvar_menu_slist_modimpurity; }
139 
140  // check if this server is favorited
141  if(gethostcachenumber(SLIST_FIELD_ISFAVORITE, entry)) { return CAT_FAVORITED; }
142 
143  // now check if it's recommended
145  {
146  string cname = gethostcachestring(SLIST_FIELD_CNAME, entry);
147 
148  if(IsPromoted(cname)) { return CAT_RECOMMENDED; }
149  else
150  {
151  float recommended = 0;
153  {
154  if(IsRecommended(cname)) { ++recommended; }
155  else { --recommended; }
156  }
158  {
159  if(
162 
163  && // check for purity requirement
164  (
166  ||
168  )
169 
170  && // check for minimum amount of humans
171  (
172  gethostcachenumber(SLIST_FIELD_NUMHUMANS, entry)
173  >=
175  )
176 
177  && // check for maximum latency
178  (
179  gethostcachenumber(SLIST_FIELD_PING, entry)
180  <=
182  )
183  )
184  { ++recommended; }
185  else
186  { --recommended; }
187  }
188  if(recommended > 0) { return CAT_RECOMMENDED; }
189  }
190  }
191 
192  // if not favorited or recommended, check modname
193  if(modtype != "xonotic")
194  {
195  switch(modtype)
196  {
197  // old servers which don't report their mod name are considered modified now
198  case "": { return CAT_MODIFIED; }
199 
200  case "xpm": { return CAT_XPM; }
201  case "minstagib":
202  case "instagib": { return CAT_INSTAGIB; }
203  case "overkill": { return CAT_OVERKILL; }
204 
205  // "cts" is allowed as compat, xdf is replacement
206  case "cts":
207  case "xdf": { return CAT_DEFRAG; }
208 
209  default: { LOG_TRACEF("Found strange mod type: %s", modtype); return CAT_MODIFIED; }
210  }
211  }
212 
213  // must be normal or impure server
214  return ((impure > autocvar_menu_slist_purethreshold) ? CAT_MODIFIED : CAT_NORMAL);
215 }
216 
217 METHOD(XonoticServerList, toggleFavorite, void(XonoticServerList this, string srv))
218 {
219  bool adding = true;
220  string srv_resolved = netaddress_resolve(srv, 26000);
221  string p = crypto_getidfp(srv_resolved);
222  string s = cvar_string("net_slist_favorites");
223  string ret = s;
224  for (int i = 0, n = tokenize_console(s); i < n; ++i)
225  {
226  bool match;
227  if (substring(argv(i), 0, 1) != "[" && strlen(argv(i)) == 44 && strstrofs(argv(i), ".", 0) < 0)
228  {
229  // it's a pubkey hash
230  match = (p && p == argv(i));
231  }
232  else
233  {
234  // it's an ip
235  match = (srv_resolved == netaddress_resolve(argv(i), 26000));
236  }
237  if (!match) continue;
238  // on match: remove
239  adding = false;
240  string before = (i > 0) ? substring(s, 0, argv_end_index(i - 1)) : "";
241  string after = (i < n - 1) ? substring(s, argv_start_index(i + 1), -1) : "";
242  s = strcat(before, (before != "" && after != "" ? " " : ""), after);
243  ret = s;
244  // keep searching
245  // TODO: why not break here?
246  n = tokenize_console(s);
247  --i; // offset the increment that is about to happen
248  }
249  if (adding)
250  {
251  ret = strcat(s, (s != "" ? " " : ""), p ? p : srv);
252  }
253  cvar_set("net_slist_favorites", ret);
254  this.refreshServerList(this, REFRESHSERVERLIST_RESORT);
255 }
256 
258 {
259  entity e = me.favoriteButton;
260  if(IsFavorite(me.ipAddressBox.text))
261  {
262  e.setText(e, ZCTX(_("SERVER^Remove favorite")));
263  setZonedTooltip(e, _("Remove the currently highlighted server from bookmarks"), string_null);
264  }
265  else
266  {
267  e.setText(e, ZCTX(_("SERVER^Favorite")));
268  setZonedTooltip(e, _("Bookmark the currently highlighted server so that it's faster to find in the future"), string_null);
269  }
270 }
271 
273 {
274  entity me;
275  me = NEW(XonoticServerList);
276  me.configureXonoticServerList(me);
277  return me;
278 }
280 {
281  me.configureXonoticListBox(me);
282 
283  // update field ID's
284  #define SLIST_FIELD(suffix,name) SLIST_FIELD_##suffix = gethostcacheindexforkey(name);
286  #undef SLIST_FIELD
287 
288  // clear list
289  me.nItems = 0;
290 }
292 {
293  me.lockedSelectedItem = false;
294  //int save = me.selectedItem;
295  SUPER(XonoticServerList).setSelected(me, i);
296  /*
297  if(me.selectedItem == save)
298  return;
299  */
300  if(me.nItems == 0)
301  return;
303  return; // sorry, it would be wrong
304 
305  strcpy(me.selectedServer, gethostcachestring(SLIST_FIELD_CNAME, me.selectedItem));
306 
307  me.ipAddressBox.setText(me.ipAddressBox, me.selectedServer);
308  me.ipAddressBox.cursorPos = strlen(me.selectedServer);
309  me.ipAddressBoxFocused = -1;
310 }
312 {
313  //print("refresh of type ", ftos(mode), "\n");
314 
315  if(mode >= REFRESHSERVERLIST_REFILTER)
316  {
317  string s = me.filterString;
318 
319  string typestr;
320  int m = strstrofs(s, ":", 0);
321  if(m >= 0)
322  {
323  typestr = substring(s, 0, m);
324  s = substring(s, m + 1, strlen(s) - m - 1);
325  while(substring(s, 0, 1) == " ")
326  s = substring(s, 1, strlen(s) - 1);
327  }
328  else
329  typestr = "";
330 
331  string modstr = cvar_string("menu_slist_modfilter");
332 
333  m = SLIST_MASK_AND - 1;
335 
336  // ping: reject negative ping (no idea why this happens in the first place, engine bug)
337  sethostcachemasknumber(++m, SLIST_FIELD_PING, 0, SLIST_TEST_GREATEREQUAL);
338 
339  // show full button
340  if(!me.filterShowFull)
341  {
342  sethostcachemasknumber(++m, SLIST_FIELD_FREESLOTS, 1, SLIST_TEST_GREATEREQUAL); // legacy
343  sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, ":S0:", SLIST_TEST_NOTCONTAIN); // g_maxplayers support
344  }
345 
346  // show empty button
347  if(!me.filterShowEmpty)
348  sethostcachemasknumber(++m, SLIST_FIELD_NUMHUMANS, 1, SLIST_TEST_GREATEREQUAL);
349 
350  // show laggy button
351  if(!me.filterShowLaggy && autocvar_menu_slist_maxping > 0)
353 
354  // gametype filtering
355  if(typestr != "")
356  sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, strcat(typestr, ":"), SLIST_TEST_STARTSWITH);
357 
358  // mod filtering
359  if(modstr != "")
360  {
361  if(substring(modstr, 0, 1) == "!")
362  sethostcachemaskstring(++m, SLIST_FIELD_MOD, resolvemod(substring(modstr, 1, strlen(modstr) - 1)), SLIST_TEST_NOTEQUAL);
363  else
364  sethostcachemaskstring(++m, SLIST_FIELD_MOD, resolvemod(modstr), SLIST_TEST_EQUAL);
365  }
366 
367  // server banning
369  for(int i = 0; i < n; ++i)
370  if(argv(i) != "")
371  sethostcachemaskstring(++m, SLIST_FIELD_CNAME, argv(i), SLIST_TEST_NOTSTARTSWITH);
372 
373  m = SLIST_MASK_OR - 1;
374  if(s != "")
375  {
376  sethostcachemaskstring(++m, SLIST_FIELD_NAME, s, SLIST_TEST_CONTAINS);
377  sethostcachemaskstring(++m, SLIST_FIELD_MAP, s, SLIST_TEST_CONTAINS);
378  sethostcachemaskstring(++m, SLIST_FIELD_PLAYERS, s, SLIST_TEST_CONTAINS);
379  sethostcachemaskstring(++m, SLIST_FIELD_QCSTATUS, strcat(s, ":"), SLIST_TEST_STARTSWITH);
380  }
381 
382  int sorting_flags = 0;
383  //sorting_flags |= SLSF_FAVORITES;
384  sorting_flags |= SLSF_CATEGORIES;
385  if(me.currentSortOrder < 0) { sorting_flags |= SLSF_DESCENDING; }
386  sethostcachesort(me.currentSortField, sorting_flags);
387  }
388 
389  resorthostcache();
390  if(mode >= REFRESHSERVERLIST_ASK)
392 }
394 {
395  SUPER(XonoticServerList).focusEnter(me);
396  if(time < me.nextRefreshTime)
397  {
398  //print("sorry, no refresh yet\n");
399  return;
400  }
401  me.nextRefreshTime = time + 10;
402  me.refreshServerList(me, REFRESHSERVERLIST_ASK);
403 }
404 
406 {
407  me.serversHeight = (autocvar_menu_slist_categories > 0 ? 1.0 : me.categoriesHeight);
408 
410  {
411  if(!me.needsRefresh)
412  me.needsRefresh = 2;
414  }
415 
417  {
418  if(!me.needsRefresh)
419  me.needsRefresh = 3;
421  }
422 
424  {
425  if(!me.needsRefresh)
426  me.needsRefresh = 3;
428  }
429 
430  if(me.currentSortField == -1)
431  {
432  me.setSortOrder(me, SLIST_FIELD_PING, +1);
433  me.refreshServerList(me, REFRESHSERVERLIST_RESET);
434  }
435  else if(me.needsRefresh == 1)
436  {
437  me.needsRefresh = 2; // delay by one frame to make sure "slist" has been executed
438  }
439  else if(me.needsRefresh == 2)
440  {
441  me.needsRefresh = 0;
442  me.refreshServerList(me, REFRESHSERVERLIST_REFILTER);
443  }
444  else if(me.needsRefresh == 3)
445  {
446  me.needsRefresh = 0;
447  me.refreshServerList(me, REFRESHSERVERLIST_RESORT);
448  }
449 
450  bool owned = ((me.selectedServer == me.ipAddressBox.text) && (me.ipAddressBox.text != ""));
451 
452  for(int i = 0; i < category_draw_count; ++i) { category_name[i] = -1; category_item[i] = -1; }
453  category_draw_count = 0;
454 
456 
457  if(autocvar_menu_slist_categories >= 0) // if less than 0, don't even draw a category heading for favorites
458  {
459  int itemcount = me.nItems;
460 
461  //float visible = floor(me.scrollPos / me.itemHeight);
462  // ^ unfortunately no such optimization can be made-- we must process through the
463  // entire list, otherwise there is no way to know which item is first in its category.
464 
465  // binary search method suggested by div
466  float begin = 0;
467  for(int j = 1; j <= category_ent_count; ++j) {
468  float first = begin;
469  float last = (itemcount - 1);
470  if (first > last) {
471  // List is empty.
472  break;
473  }
474  float catf = gethostcachenumber(SLIST_FIELD_CATEGORY, first);
475  float catl = gethostcachenumber(SLIST_FIELD_CATEGORY, last);
476  if (catf > j) {
477  // The first one is already > j.
478  // Therefore, category j does not exist.
479  // Higher numbered categories do exist though.
480  } else if (catl < j) {
481  // The last one is < j.
482  // Thus this category - and any following -
483  // don't exist.
484  break;
485  } else if (catf == j) {
486  // Starts at first. This breaks the loop
487  // invariant in the binary search and thus has
488  // to be handled separately.
489  if(gethostcachenumber(SLIST_FIELD_CATEGORY, first) != j)
490  error("Category mismatch I");
491  if(first > 0)
492  if(gethostcachenumber(SLIST_FIELD_CATEGORY, first - 1) == j)
493  error("Category mismatch II");
497  begin = first + 1;
498  } else {
499  // At this point, catf <= j < catl, thus
500  // catf < catl, thus first < last.
501  // INVARIANTS:
502  // last - first >= 1
503  // catf == gethostcachenumber(SLIST_FIELD_CATEGORY(first)
504  // catl == gethostcachenumber(SLIST_FIELD_CATEGORY(last)
505  // catf < j
506  // catl >= j
507  while (last - first > 1) {
508  float middle = floor((first + last) / 2);
509  // By loop condition, middle != first && middle != last.
510  float cat = gethostcachenumber(SLIST_FIELD_CATEGORY, middle);
511  if (cat >= j) {
512  last = middle;
513  catl = cat;
514  } else {
515  first = middle;
516  catf = cat;
517  }
518  }
519  if (catl == j) {
520  if(gethostcachenumber(SLIST_FIELD_CATEGORY, last) != j)
521  error("Category mismatch III");
522  if(last > 0)
523  if(gethostcachenumber(SLIST_FIELD_CATEGORY, last - 1) == j)
524  error("Category mismatch IV");
528  begin = last + 1; // already scanned through these, skip 'em
529  }
530  else
531  begin = last; // already scanned through these, skip 'em
532  }
533  }
534  if(autocvar_menu_slist_categories_onlyifmultiple && (category_draw_count == 1))
535  {
536  category_name[0] = -1;
537  category_item[0] = -1;
538  category_draw_count = 0;
539  me.nItems = itemcount;
540  }
541  }
542 
543  me.connectButton.disabled = (me.ipAddressBox.text == "");
544  //me.disconnectButton.disabled = (!(gamestatus & (GAME_ISSERVER | GAME_CONNECTED)));
545  me.infoButton.disabled = !owned;
546  me.favoriteButton.disabled = (me.ipAddressBox.text == "");
547 
548  bool found = false;
549  if(me.lockedSelectedItem)
550  {
551  if(me.nItems > 0)
552  {
553  if(gethostcachestring(SLIST_FIELD_CNAME, me.selectedItem) != me.selectedServer)
554  {
555  strcpy(me.selectedServer, gethostcachestring(SLIST_FIELD_CNAME, me.selectedItem));
556  }
557  found = true;
558  }
559  }
560  else if(me.selectedServer)
561  {
562  for(int i = 0; i < me.nItems; ++i)
563  {
564  if(gethostcachestring(SLIST_FIELD_CNAME, i) == me.selectedServer)
565  {
566  // don't follow the selected item with SUPER(XonoticServerList).setSelected(me, i);
567  me.selectedItem = i;
568  found = true;
569  break;
570  }
571  }
572  }
573  if(!found)
574  {
575  if(me.nItems > 0)
576  {
577  // selected server disappeared, select the last server (scrolling to it)
578  if(me.selectedItem >= me.nItems)
579  SUPER(XonoticServerList).setSelected(me, me.nItems - 1);
580  strcpy(me.selectedServer, gethostcachestring(SLIST_FIELD_CNAME, me.selectedItem));
581  }
582  }
583 
584  if(owned)
585  {
586  if(me.selectedServer != me.ipAddressBox.text)
587  {
588  me.ipAddressBox.setText(me.ipAddressBox, me.selectedServer);
589  me.ipAddressBox.cursorPos = strlen(me.selectedServer);
590  me.ipAddressBoxFocused = -1;
591  }
592  }
593 
594  if(me.ipAddressBoxFocused != me.ipAddressBox.focused)
595  {
596  if(me.ipAddressBox.focused || me.ipAddressBoxFocused < 0)
598  me.ipAddressBoxFocused = me.ipAddressBox.focused;
599  }
600 
601  SUPER(XonoticServerList).draw(me);
602 }
604 {
605  me.setSortOrder(me, SLIST_FIELD_PING, +1);
606 }
608 {
609  me.setSortOrder(me, SLIST_FIELD_NAME, -1); // why?
610 }
612 {
613  me.setSortOrder(me, SLIST_FIELD_MAP, -1); // why?
614 }
616 {
617  me.setSortOrder(me, SLIST_FIELD_NUMHUMANS, -1);
618 }
620 {
621  string s = me.filterString;
622  int m = strstrofs(s, ":", 0);
623  if(m >= 0)
624  {
625  s = substring(s, 0, m);
626  while(substring(s, m+1, 1) == " ") // skip spaces
627  ++m;
628  }
629  else
630  s = "";
631 
632  Gametype first = NULL; FOREACH(Gametypes, !first, first = it; break);
633  bool flag = false;
634  FOREACH(Gametypes, s == MapInfo_Type_ToString(it), {
635  // the type was found
636  // choose the next one
637  flag = true;
638  s = MapInfo_Type_ToString(REGISTRY_GET(Gametypes, it.m_id + 1));
639  if (s == "") s = MapInfo_Type_ToString(first);
640  break;
641  });
642  if (!flag) {
643  // no type was found
644  // choose the first one
645  s = MapInfo_Type_ToString(first);
646  }
647 
648  if(s != "") s = strcat(s, ":");
649  s = strcat(s, substring(me.filterString, m+1, strlen(me.filterString) - m - 1));
650 
651  me.controlledTextbox.setText(me.controlledTextbox, s);
652  me.controlledTextbox.keyDown(me.controlledTextbox, K_END, 0, 0);
653  me.controlledTextbox.keyUp(me.controlledTextbox, K_END, 0, 0);
654  //ServerList_Filter_Change(me.controlledTextbox, me);
655 }
657 {
658  strfree(me.filterString);
659  if(box.text != "")
660  me.filterString = strzone(box.text);
661  me.refreshServerList(me, REFRESHSERVERLIST_REFILTER);
662 
663  me.ipAddressBox.setText(me.ipAddressBox, "");
664  me.ipAddressBox.cursorPos = 0;
665  me.ipAddressBoxFocused = -1;
666 }
668 {
670  me.refreshServerList(me, REFRESHSERVERLIST_RESORT);
671 
672  me.ipAddressBox.setText(me.ipAddressBox, "");
673  me.ipAddressBox.cursorPos = 0;
674  me.ipAddressBoxFocused = -1;
675 }
677 {
678  box.setChecked(box, me.filterShowEmpty = !me.filterShowEmpty);
679  me.refreshServerList(me, REFRESHSERVERLIST_REFILTER);
680 
681  me.ipAddressBox.setText(me.ipAddressBox, "");
682  me.ipAddressBox.cursorPos = 0;
683  me.ipAddressBoxFocused = -1;
684 }
686 {
687  box.setChecked(box, me.filterShowFull = !me.filterShowFull);
688  me.refreshServerList(me, REFRESHSERVERLIST_REFILTER);
689 
690  me.ipAddressBox.setText(me.ipAddressBox, "");
691  me.ipAddressBox.cursorPos = 0;
692  me.ipAddressBoxFocused = -1;
693 }
695 {
696  box.setChecked(box, me.filterShowLaggy = !me.filterShowLaggy);
697  me.refreshServerList(me, REFRESHSERVERLIST_REFILTER);
698 
699  me.ipAddressBox.setText(me.ipAddressBox, "");
700  me.ipAddressBox.cursorPos = 0;
701  me.ipAddressBoxFocused = -1;
702 }
704 {
705  if(me.currentSortField == fld)
706  direction = -me.currentSortOrder;
707  me.currentSortOrder = direction;
708  me.currentSortField = fld;
709  me.sortButton1.forcePressed = (fld == SLIST_FIELD_PING);
710  me.sortButton2.forcePressed = (fld == SLIST_FIELD_NAME);
711  me.sortButton3.forcePressed = (fld == SLIST_FIELD_MAP);
712  me.sortButton4.forcePressed = 0;
713  me.sortButton5.forcePressed = (fld == SLIST_FIELD_NUMHUMANS);
714  me.selectedItem = 0;
715  strfree(me.selectedServer);
716  me.refreshServerList(me, REFRESHSERVERLIST_REFILTER);
717 }
718 void XonoticServerList_positionSortButton(entity me, entity btn, float theOrigin, float theSize, string theTitle, void(entity, entity) theFunc)
719 {
720  vector originInLBSpace = eY * (-me.itemHeight);
721  vector sizeInLBSpace = eY * me.itemHeight + eX * (1 - me.controlWidth);
722 
723  vector originInDialogSpace = boxToGlobal(originInLBSpace, me.Container_origin, me.Container_size);
724  vector sizeInDialogSpace = boxToGlobalSize(sizeInLBSpace, me.Container_size);
725 
726  btn.Container_origin_x = originInDialogSpace.x + sizeInDialogSpace.x * theOrigin;
727  btn.Container_size_x = sizeInDialogSpace.x * theSize;
728  btn.setText(btn, theTitle);
729  btn.onClick = theFunc;
730  btn.onClickEntity = me;
731  btn.resized = 1;
732 }
733 void XonoticServerList_resizeNotify(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
734 {
735  SUPER(XonoticServerList).resizeNotify(me, relOrigin, relSize, absOrigin, absSize);
736 
737  me.realFontSize_y = me.fontSize / (absSize.y * me.itemHeight);
738  me.realFontSize_x = me.fontSize / (absSize.x * (1 - me.controlWidth));
739  me.realUpperMargin = 0.5 * (1 - me.realFontSize.y);
740 
741  me.columnIconsOrigin = 0;
742  me.columnIconsSize = me.realFontSize.x * 4 * me.iconsSizeFactor;
743  me.columnPingSize = me.realFontSize.x * 3;
744  me.columnMapSize = me.realFontSize.x * 10;
745  me.columnTypeSize = me.realFontSize.x * 4;
746  me.columnPlayersSize = me.realFontSize.x * 5;
747  me.columnNameSize = 1 - me.columnPlayersSize - me.columnMapSize - me.columnPingSize - me.columnIconsSize - me.columnTypeSize - 5 * me.realFontSize.x;
748  me.columnPingOrigin = me.columnIconsOrigin + me.columnIconsSize + me.realFontSize.x;
749  me.columnNameOrigin = me.columnPingOrigin + me.columnPingSize + me.realFontSize.x;
750  me.columnMapOrigin = me.columnNameOrigin + me.columnNameSize + me.realFontSize.x;
751  me.columnTypeOrigin = me.columnMapOrigin + me.columnMapSize + me.realFontSize.x;
752  me.columnPlayersOrigin = me.columnTypeOrigin + me.columnTypeSize + me.realFontSize.x;
753 
754  me.positionSortButton(me, me.sortButton1, me.columnPingOrigin, me.columnPingSize, _("Ping"), ServerList_PingSort_Click);
755  me.positionSortButton(me, me.sortButton2, me.columnNameOrigin, me.columnNameSize, _("Hostname"), ServerList_NameSort_Click);
756  me.positionSortButton(me, me.sortButton3, me.columnMapOrigin, me.columnMapSize, _("Map"), ServerList_MapSort_Click);
757  me.positionSortButton(me, me.sortButton4, me.columnTypeOrigin, me.columnTypeSize, _("Type"), ServerList_TypeSort_Click);
758  me.positionSortButton(me, me.sortButton5, me.columnPlayersOrigin, me.columnPlayersSize, _("Players"), ServerList_PlayerSort_Click);
759 
760  int f = me.currentSortField;
761  if(f >= 0)
762  {
763  me.currentSortField = -1;
764  me.setSortOrder(me, f, me.currentSortOrder); // force resetting the sort order
765  }
766 }
768 {
769  if (me.ipAddressBox.text != "")
770  localcmd(sprintf("connect %s\n", me.ipAddressBox.text));
771 }
773 {
774  string addr = this.ipAddressBox.text;
775  string ipstr = netaddress_resolve(addr, 26000);
776  if (ipstr == "") return;
778  this.toggleFavorite(this, addr);
779  this.ipAddressBoxFocused = -1;
780 }
782 {
783  if (me.nItems != 0)
784  main.serverInfoDialog.loadServerInfo(main.serverInfoDialog, me.selectedItem);
785 
786  float thisPos = me.getItemStart(me, me.selectedItem);
787  float thisHeight = me.getItemHeight(me, me.selectedItem);
788  vector org = boxToGlobal(eY * (thisPos - me.scrollPos), me.origin, me.size);
789  vector sz = boxToGlobalSize(eY * thisHeight + eX * (1 - me.controlWidth), me.size);
790  DialogOpenButton_Click_withCoords(me, main.serverInfoDialog, org, sz);
791 }
793 {
795 }
796 void XonoticServerList_drawListBoxItem(entity me, int i, vector absSize, bool isSelected, bool isFocused)
797 {
798  vector oldscale = draw_scale;
799  vector oldshift = draw_shift;
800 #define SET_YRANGE(start,end) \
801  draw_scale = boxToGlobalSize(eX + eY * ((end) - (start)), oldscale); \
802  draw_shift = boxToGlobal(eY * (start), oldshift, oldscale);
803 
804  int c;
805  for (c = 0; c < category_draw_count; ++c) {
806  // Matches exactly the headings with increased height.
807  if (i == category_item[c])
808  break;
809  }
810 
811  if (c < category_draw_count)
812  {
814  if(catent)
815  {
816  SET_YRANGE(
817  (me.categoriesHeight - 1) / (me.categoriesHeight + 1),
818  me.categoriesHeight / (me.categoriesHeight + 1)
819  );
820  draw_Text(
821  eY * me.realUpperMargin
822  +
823 #if 0
824  eX * (me.columnNameOrigin + (me.columnNameSize - draw_TextWidth(catent.cat_string, 0, me.realFontSize)) * 0.5),
825  catent.cat_string,
826 #else
827  eX * (me.columnNameOrigin),
828  strcat(catent.cat_string, ":"),
829 #endif
830  me.realFontSize,
831  SKINCOLOR_SERVERLIST_CATEGORY,
832  SKINALPHA_SERVERLIST_CATEGORY,
833  0
834  );
835  SET_YRANGE(me.categoriesHeight / (me.categoriesHeight + 1), 1);
836  }
837  }
838 
839  // Now categories are done. Set the Y range in stone.
840  oldscale = draw_scale;
841  oldshift = draw_shift;
842 
843  if(isSelected && !me.lockedSelectedItem)
844  draw_Fill('0 0 0', '1 1 0', SKINCOLOR_LISTBOX_SELECTED, SKINALPHA_LISTBOX_SELECTED);
845  else if(isFocused)
846  {
847  me.focusedItemAlpha = getFadedAlpha(me.focusedItemAlpha, SKINALPHA_LISTBOX_FOCUSED, SKINFADEALPHA_LISTBOX_FOCUSED);
848  draw_Fill('0 0 0', '1 1 0', SKINCOLOR_LISTBOX_FOCUSED, me.focusedItemAlpha);
849  }
850 
851  string s = gethostcachestring(SLIST_FIELD_QCSTATUS, i);
852  int m = tokenizebyseparator(s, ":");
853  string typestr = "", versionstr = "";
854  if(m >= 2)
855  {
856  typestr = argv(0);
857  versionstr = argv(1);
858  }
859  bool pure = false, pure_available = false;
860  int freeslots = -1, sflags = -1;
861  string modname = "";
862  for(int j = 2; j < m; ++j)
863  {
864  if(argv(j) == "")
865  break;
866  string key = substring(argv(j), 0, 1);
867  string value = substring(argv(j), 1, -1);
868  if(key == "P")
869  {
870  pure = (stof(value) == 0);
871  pure_available = true;
872  }
873  else if(key == "S")
874  freeslots = stof(value);
875  else if(key == "F")
876  sflags = stoi(value);
877  else if(key == "M")
878  modname = value;
879  }
880 
881 #ifdef COMPAT_NO_MOD_IS_XONOTIC
882  if(modname == "")
883  modname = "Xonotic";
884 #endif
885 
886  string original_modname = modname;
887  modname = strtolower(modname);
888 
889  /*
890  SLIST_FIELD_MOD = gethostcacheindexforkey("mod");
891  s = gethostcachestring(SLIST_FIELD_MOD, i);
892  if(s != "data")
893  if(modname == "xonotic")
894  modname = s;
895  */
896 
897  // list the mods here on which the pure server check actually works
898  if(modname != "xonotic")
899  if(modname != "instagib" || modname != "minstagib")
900  if(modname != "cts")
901  if(modname != "nix")
902  if(modname != "newtoys")
903  pure_available = false;
904 
905  float theAlpha;
906  if(gethostcachenumber(SLIST_FIELD_FREESLOTS, i) <= 0)
907  theAlpha = SKINALPHA_SERVERLIST_FULL;
908  else if(freeslots == 0)
909  theAlpha = SKINALPHA_SERVERLIST_FULL; // g_maxplayers support
910  else if (!gethostcachenumber(SLIST_FIELD_NUMHUMANS, i))
911  theAlpha = SKINALPHA_SERVERLIST_EMPTY;
912  else
913  theAlpha = 1;
914 
915  float ping = gethostcachenumber(SLIST_FIELD_PING, i);
916  const int PING_LOW = 75;
917  const int PING_MED = 200;
918  const int PING_HIGH = 500;
919  vector theColor;
920  if(ping < PING_LOW)
921  theColor = SKINCOLOR_SERVERLIST_LOWPING + (SKINCOLOR_SERVERLIST_MEDPING - SKINCOLOR_SERVERLIST_LOWPING) * (ping / PING_LOW);
922  else if(ping < PING_MED)
923  theColor = SKINCOLOR_SERVERLIST_MEDPING + (SKINCOLOR_SERVERLIST_HIGHPING - SKINCOLOR_SERVERLIST_MEDPING) * ((ping - PING_LOW) / (PING_MED - PING_LOW));
924  else if(ping < PING_HIGH)
925  {
926  theColor = SKINCOLOR_SERVERLIST_HIGHPING;
927  theAlpha *= 1 + (SKINALPHA_SERVERLIST_HIGHPING - 1) * ((ping - PING_MED) / (PING_HIGH - PING_MED));
928  }
929  else
930  {
931  theColor = eX;
932  theAlpha *= SKINALPHA_SERVERLIST_HIGHPING;
933  }
934 
935  if(gethostcachenumber(SLIST_FIELD_ISFAVORITE, i))
936  {
937  theColor = theColor * (1 - SKINALPHA_SERVERLIST_FAVORITE) + SKINCOLOR_SERVERLIST_FAVORITE * SKINALPHA_SERVERLIST_FAVORITE;
938  theAlpha = theAlpha * (1 - SKINALPHA_SERVERLIST_FAVORITE) + SKINALPHA_SERVERLIST_FAVORITE;
939  }
940 
941  s = gethostcachestring(SLIST_FIELD_CNAME, i);
942 
943  bool isv4 = false, isv6 = false;
944  if(substring(s, 0, 1) == "[")
945  {
946  isv6 = true;
947  me.seenIPv6 += 1;
948  }
949  else if(IS_DIGIT(substring(s, 0, 1)))
950  {
951  isv4 = true;
952  me.seenIPv4 += 1;
953  }
954 
955  int crypto = stof(substring(crypto_getencryptlevel(s), 0, 1));
956  if((crypto <= 0 && cvar("crypto_aeslevel") >= 3) || (crypto >= 3 && cvar("crypto_aeslevel") <= 0))
957  {
958  theColor = SKINCOLOR_SERVERLIST_IMPOSSIBLE;
959  theAlpha = SKINALPHA_SERVERLIST_IMPOSSIBLE;
960  }
961 
962  if(crypto == 1)
963  {
964  if(cvar("crypto_aeslevel") >= 2)
965  crypto |= 4;
966  }
967  if(crypto == 2)
968  {
969  if(cvar("crypto_aeslevel") >= 1)
970  crypto |= 4;
971  }
972  if(crypto == 3)
973  crypto = 5;
974  else if(crypto >= 3)
975  crypto -= 2;
976  // possible status:
977  // 0: crypto off
978  // 1: AES possible
979  // 2: AES recommended but not available
980  // 3: AES possible and will be used
981  // 4: AES recommended and will be used
982  // 5: AES required
983 
984  // --------------
985  // RENDER ICONS
986  // --------------
987  vector iconSize = '0 0 0';
988  iconSize_y = me.realFontSize.y * me.iconsSizeFactor;
989  iconSize_x = me.realFontSize.x * me.iconsSizeFactor * me.serversHeight;
990 
991  vector iconPos = '0 0 0';
992  iconPos_x = (me.columnIconsSize - 3 * iconSize.x) * 0.5;
993  iconPos_y = (1 - iconSize.y) * 0.5;
994 
995  // IP
996  if(me.seenIPv4 && me.seenIPv6)
997  {
998  if(isv6)
999  draw_Picture(iconPos, "icon_ipv6", iconSize, '1 1 1', 1);
1000  else if(isv4)
1001  draw_Picture(iconPos, "icon_ipv4", iconSize, '1 1 1', 1);
1002  }
1003 
1004  iconPos.x += iconSize.x;
1005 
1006  // AES
1007  if(crypto > 0)
1008  draw_Picture(iconPos, strcat("icon_aeslevel", ftos(crypto)), iconSize, '1 1 1', 1);
1009 
1010  iconPos.x += iconSize.x;
1011 
1012  // Mod
1013  if(modname == "xonotic")
1014  {
1015  // Here, pure_available should always be set. If not, consider
1016  // it an impurity.
1017  if(pure_available && pure)
1018  draw_Picture(iconPos, "icon_pure1", iconSize, '1 1 1', 1);
1019  }
1020  else
1021  {
1022  string icon = strcat("icon_mod_", modname);
1023  if(draw_PictureSize(icon) == '0 0 0')
1024  icon = "icon_mod_";
1025 
1026  // In mods, pure_available not being present indicates
1027  // non-support of the feature. Just show the mod icon as is
1028  // then.
1029  if(pure_available && !pure)
1030  draw_Picture(iconPos, icon, iconSize, '1 1 1', SKINALPHA_SERVERLIST_ICON_NONPURE);
1031  else
1032  draw_Picture(iconPos, icon, iconSize, '1 1 1', 1);
1033  }
1034 
1035  iconPos.x += iconSize.x;
1036 
1037  // Stats
1038  if(sflags >= 0 && (sflags & SERVERFLAG_PLAYERSTATS))
1039  {
1040  if (sflags & SERVERFLAG_PLAYERSTATS_CUSTOM)
1041  draw_Picture(iconPos, "icon_mod_", iconSize, '1 1 1', 1); // TODO: custom stats server icon
1042  else
1043  draw_Picture(iconPos, "icon_stats1", iconSize, '1 1 1', 1);
1044  }
1045 
1046  if(isFocused && me.mouseOverIcons && !me.tooltip)
1047  {
1048  string t = "";
1049  if(me.seenIPv4 && me.seenIPv6)
1050  t = strcat(t, (isv6) ? "IPv6, " : "IPv4, ");
1051  t = strcat(t, _("encryption:"), " ", (crypto ? sprintf(_("AES level %d"), crypto) : ZCTX(_("ENC^none"))), ", ");
1052  t = strcat(t, sprintf(_("mod: %s"), ((modname == "xonotic") ? ZCTX(_("MOD^Default")) : original_modname)));
1053  if(pure_available)
1054  t = strcat(t, sprintf(" (%s)", (pure) ? _("official settings") : _("modified settings")));
1055  t = strcat(t, ", ");
1056  t = strcat(t, ((sflags >= 0 && (sflags & SERVERFLAG_PLAYERSTATS)) ? ((sflags & SERVERFLAG_PLAYERSTATS_CUSTOM) ? _("custom stats server") : _("stats enabled")) : _("stats disabled")));
1057  setZonedTooltip(me, t, string_null);
1058  }
1059  // --------------
1060  // RENDER TEXT
1061  // --------------
1062 
1063  // Center it in the row.
1064  SET_YRANGE(
1065  0.5 - 0.5 / me.serversHeight,
1066  0.5 + 0.5 / me.serversHeight
1067  );
1068 
1069  // ping
1070  s = ftos(ping);
1071  draw_Text(me.realUpperMargin * eY + (me.columnPingOrigin + me.columnPingSize - draw_TextWidth(s, 0, me.realFontSize)) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1072 
1073  // server name
1074  s = draw_TextShortenToWidth(gethostcachestring(SLIST_FIELD_NAME, i), me.columnNameSize, 0, me.realFontSize);
1075  draw_Text(me.realUpperMargin * eY + me.columnNameOrigin * eX, s, me.realFontSize, theColor, theAlpha, 0);
1076 
1077  // server map
1078  s = draw_TextShortenToWidth(gethostcachestring(SLIST_FIELD_MAP, i), me.columnMapSize, 0, me.realFontSize);
1079  draw_Text(me.realUpperMargin * eY + (me.columnMapOrigin + (me.columnMapSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1080 
1081  // server gametype
1082  s = draw_TextShortenToWidth(typestr, me.columnTypeSize, 0, me.realFontSize);
1083  draw_Text(me.realUpperMargin * eY + (me.columnTypeOrigin + (me.columnTypeSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1084 
1085  // server playercount
1086  s = strcat(ftos(gethostcachenumber(SLIST_FIELD_NUMHUMANS, i)), "/", ftos(gethostcachenumber(SLIST_FIELD_MAXPLAYERS, i)));
1087  draw_Text(me.realUpperMargin * eY + (me.columnPlayersOrigin + (me.columnPlayersSize - draw_TextWidth(s, 0, me.realFontSize)) * 0.5) * eX, s, me.realFontSize, theColor, theAlpha, 0);
1088 }
1089 
1091 {
1092  clearTooltip(me);
1093 }
1094 
1096 {
1097  if(!SUPER(XonoticServerList).mouseMove(me, pos))
1098  {
1099  me.mouseOverIcons = false;
1100  clearTooltip(me);
1101  return 1;
1102  }
1103 
1104  me.mouseOverIcons = (pos_x <= me.columnIconsSize);
1105  if(!me.mouseOverIcons)
1106  clearTooltip(me);
1107  return 1;
1108 }
1109 
1110 bool XonoticServerList_keyDown(entity me, int scan, bool ascii, bool shift)
1111 {
1112  if(scan == K_ENTER || scan == K_KP_ENTER)
1113  {
1115  return true;
1116  }
1117  else if(scan == K_MOUSE2 || scan == K_SPACE)
1118  {
1119  if(me.nItems != 0)
1120  {
1121  float thisPos = me.getItemStart(me, me.selectedItem);
1122  float thisHeight = me.getItemHeight(me, me.selectedItem);
1123  vector org = boxToGlobal(eY * (thisPos - me.scrollPos), me.origin, me.size);
1124  vector sz = boxToGlobalSize(eY * thisHeight + eX * (1 - me.controlWidth), me.size);
1126  main.serverInfoDialog.loadServerInfo(main.serverInfoDialog, me.selectedItem);
1127  DialogOpenButton_Click_withCoords(me, main.serverInfoDialog, org, sz);
1128  return true;
1129  }
1130  return false;
1131  }
1132  else if(scan == K_INS || scan == K_MOUSE3 || scan == K_KP_INS)
1133  {
1134  if(me.nItems != 0)
1135  {
1136  me.toggleFavorite(me, me.selectedServer);
1137  me.ipAddressBoxFocused = -1;
1138  return true;
1139  }
1140  return false;
1141  }
1142  else if(SUPER(XonoticServerList).keyDown(me, scan, ascii, shift))
1143  return true;
1144  else if(!me.controlledTextbox)
1145  return false;
1146  else
1147  return me.controlledTextbox.keyDown(me.controlledTextbox, scan, ascii, shift);
1148 }
1149 
1151 {
1152  float num_normal_rows = me.nItems;
1153  int num_headers = category_draw_count;
1154  return me.itemHeight * (me.serversHeight * num_normal_rows + me.categoriesHeight * num_headers);
1155 }
1157 {
1158  pos = pos / me.itemHeight;
1159  for (int i = category_draw_count - 1; i >= 0; --i) {
1160  int itemidx = category_item[i];
1161  float itempos = i * me.categoriesHeight + itemidx * me.serversHeight;
1162  if (pos >= itempos + me.categoriesHeight + me.serversHeight)
1163  return itemidx + 1 + floor((pos - (itempos + me.categoriesHeight + me.serversHeight)) / me.serversHeight);
1164  if (pos >= itempos)
1165  return itemidx;
1166  }
1167  // No category matches? Note that category 0 is... 0. Therefore no headings exist at all.
1168  return floor(pos / me.serversHeight);
1169 }
1171 {
1172  for (int i = category_draw_count - 1; i >= 0; --i) {
1173  int itemidx = category_item[i];
1174  float itempos = i * me.categoriesHeight + itemidx * me.serversHeight;
1175  if (item >= itemidx + 1)
1176  return (itempos + me.categoriesHeight + (1 + item - (itemidx + 1)) * me.serversHeight) * me.itemHeight;
1177  if (item >= itemidx)
1178  return itempos * me.itemHeight;
1179  }
1180  // No category matches? Note that category 0 is... 0. Therefore no headings exist at all.
1181  return item * me.serversHeight * me.itemHeight;
1182 }
1184 {
1185  for (int i = 0; i < category_draw_count; ++i) {
1186  // Matches exactly the headings with increased height.
1187  // FIXME server row next to the heading is slightly smaller than others if (autocvar_menu_slist_categories <= 0)
1188  if (item == category_item[i])
1189  return me.itemHeight * (me.categoriesHeight + me.serversHeight);
1190  }
1191  return me.itemHeight * me.serversHeight;
1192 }
string MapInfo_Type_ToString(Gametype t)
Definition: mapinfo.qc:616
void ServerList_NameSort_Click(entity btn, entity me)
Definition: serverlist.qc:607
void ServerList_TypeSort_Click(entity btn, entity me)
Definition: serverlist.qc:619
float K_KP_INS
Definition: keycodes.qc:49
int category_draw_count
Definition: serverlist.qh:150
string string_null
Definition: nil.qh:9
void XonoticServerList_configureXonoticServerList(entity me)
Definition: serverlist.qc:279
void XonoticServerList_resizeNotify(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
Definition: serverlist.qc:733
#define SLIST_CATEGORIES
Definition: serverlist.qh:152
bool IsServerInList(string list, string srv)
Definition: serverlist.qc:76
entity makeXonoticServerList()
Definition: serverlist.qc:272
const vector eY
Definition: vector.qh:45
#define NEW(cname,...)
Definition: oo.qh:105
void RegisterSLCategories()
Definition: serverlist.qc:44
float autocvar_menu_slist_recommendations_purethreshold
Definition: serverlist.qh:119
void XonoticServerList_positionSortButton(entity me, entity btn, float theOrigin, float theSize, string theTitle, void(entity, entity) theFunc)
Definition: serverlist.qc:718
void XonoticServerList_focusEnter(entity me)
Definition: serverlist.qc:393
#define IsPromoted(srv)
Definition: serverlist.qc:10
void SL_ProcessCategoryOverrides(.string override_field_string,.float override_field)
Definition: serverlist.qc:15
float XonoticServerList_getItemHeight(entity me, int item)
Definition: serverlist.qc:1183
int category_ent_count
Definition: serverlist.qh:139
#define SET_YRANGE(start, end)
void ServerList_Update_favoriteButton(entity btn, entity me)
Definition: serverlist.qc:257
float K_MOUSE3
Definition: keycodes.qc:131
int direction
Definition: strafehud.qc:24
entity() spawn
void XonoticServerList_focusedItemChangeNotify(entity me)
Definition: serverlist.qc:1090
#define REGISTRY_GET(id, i)
Definition: registry.qh:43
int category_item[MAX_CATEGORIES]
Definition: serverlist.qh:149
entity RetrieveCategoryEnt(int catnum)
Definition: serverlist.qc:63
float autocvar_menu_slist_recommendations_minfreeslots
Definition: serverlist.qh:117
void DialogOpenButton_Click_withCoords(entity button, entity tab, vector theOrigin, vector theSize)
const float REFRESHSERVERLIST_RESET
Definition: serverlist.qh:124
SLIST_FIELDS float autocvar_menu_slist_categories
Definition: serverlist.qh:110
#define IsRecommended(srv)
Definition: serverlist.qc:11
#define IsFavorite(srv)
Definition: serverlist.qc:9
string cat_enoverride_string
Definition: serverlist.qh:142
float ping
Definition: main.qh:138
const float REFRESHSERVERLIST_RESORT
Definition: serverlist.qh:121
#define METHOD(cname, name, prototype)
Definition: oo.qh:257
float autocvar_menu_slist_categories_onlyifmultiple
Definition: serverlist.qh:111
void ServerList_Categories_Click(entity box, entity me)
Definition: serverlist.qc:667
void ServerList_Info_Click(entity btn, entity me)
Definition: serverlist.qc:781
void ServerList_MapSort_Click(entity btn, entity me)
Definition: serverlist.qc:611
float K_SPACE
Definition: keycodes.qc:10
const int SERVERFLAG_PLAYERSTATS
Definition: constants.qh:17
float autocvar_menu_slist_modimpurity
Definition: serverlist.qh:113
#define SLIST_FIELDS
Definition: serverlist.qh:88
const int SERVERFLAG_PLAYERSTATS_CUSTOM
Definition: constants.qh:18
float K_KP_ENTER
Definition: keycodes.qc:74
#define strcpy(this, s)
Definition: string.qh:49
#define stoi(s)
Definition: int.qh:4
#define SUPER(cname)
Definition: oo.qh:219
string modname
Definition: world.qh:45
float autocvar_menu_slist_purethreshold
Definition: serverlist.qh:112
const float REFRESHSERVERLIST_REFILTER
Definition: serverlist.qh:122
void ServerList_ShowEmpty_Click(entity box, entity me)
Definition: serverlist.qc:676
#define argv_end_index
Definition: dpextensions.qh:30
void XonoticServerList_setSortOrder(entity me, int fld, int direction)
Definition: serverlist.qc:703
void XonoticServerList_drawListBoxItem(entity me, int i, vector absSize, bool isSelected, bool isFocused)
Definition: serverlist.qc:796
float XonoticServerList_getTotalHeight(entity me)
Definition: serverlist.qc:1150
#define ZCTX(s)
Definition: i18n.qh:68
float autocvar_menu_slist_recommendations
Definition: serverlist.qh:115
#define LOG_INFOF(...)
Definition: log.qh:71
float autocvar_menu_slist_recommendations_minhumans
Definition: serverlist.qh:118
void ServerList_ShowLaggy_Click(entity box, entity me)
Definition: serverlist.qc:694
#define argv_start_index
Definition: dpextensions.qh:27
void ServerList_PlayerSort_Click(entity btn, entity me)
Definition: serverlist.qc:615
spree_cen s1 spree_cen s1 spree_cen s1 spree_cen s1 spree_cen s1 spree_cen s1 spree_cen s1 f1 s1 strcat(_("Level %s: "), "^BG%s\3\, _("^BGPress ^F2%s^BG to enter the game"))
float autocvar_menu_slist_maxping
Definition: serverlist.qh:114
float XonoticServerList_mouseMove(entity me, vector pos)
Definition: serverlist.qc:1095
float K_MOUSE2
Definition: keycodes.qc:130
#define NULL
Definition: post.qh:17
#define LOG_TRACEF(...)
Definition: log.qh:82
float m_gethostcachecategory(float entry)
Definition: serverlist.qc:13
#define strstrofs
Definition: dpextensions.qh:42
void XonoticServerList_doubleClickListBoxItem(entity me, int i, vector where)
Definition: serverlist.qc:792
float K_END
Definition: keycodes.qc:42
void ServerList_PingSort_Click(entity btn, entity me)
Definition: serverlist.qc:603
#define IS_DIGIT(d)
Definition: string.qh:507
int CategoryForEntry(int entry)
Definition: serverlist.qc:117
void ServerList_Filter_Change(entity box, entity me)
Definition: serverlist.qc:656
vector(float skel, float bonenum) _skel_get_boneabs_hidden
void ServerList_ShowFull_Click(entity box, entity me)
Definition: serverlist.qc:685
#define tokenize_console
Definition: dpextensions.qh:24
void XonoticServerList_setSelected(entity me, int i)
Definition: serverlist.qc:291
int XonoticServerList_getItemAtPos(entity me, float pos)
Definition: serverlist.qc:1156
vector v
Definition: ent_cs.qc:116
string cat_dioverride_string
Definition: serverlist.qh:143
void XonoticServerList_draw(entity me)
Definition: serverlist.qc:405
float cat_enoverride
Definition: serverlist.qh:144
const vector eX
Definition: vector.qh:44
float cat_dioverride
Definition: serverlist.qh:145
#define tokenizebyseparator
Definition: dpextensions.qh:21
entity categories[MAX_CATEGORIES]
Definition: serverlist.qh:138
const float REFRESHSERVERLIST_ASK
Definition: serverlist.qh:123
#define strfree(this)
Definition: string.qh:56
float K_ENTER
Definition: keycodes.qc:8
string cat_name
Definition: serverlist.qh:140
float XonoticServerList_getItemStart(entity me, int item)
Definition: serverlist.qc:1170
void XonoticServerList_refreshServerList(entity me, int mode)
Definition: serverlist.qc:311
float K_INS
Definition: keycodes.qc:37
float time
Definition: csprogsdefs.qc:16
#define FOREACH(list, cond, body)
Definition: iter.qh:19
float autocvar_menu_slist_recommendations_maxping
Definition: serverlist.qh:116
void ServerList_Connect_Click(entity btn, entity me)
Definition: serverlist.qc:767
int category_name[MAX_CATEGORIES]
Definition: serverlist.qh:148
void ServerList_Favorite_Click(entity btn, entity this)
Definition: serverlist.qc:772
bool XonoticServerList_keyDown(entity me, int scan, bool ascii, bool shift)
Definition: serverlist.qc:1110
int CategoryOverride(int cat)
Definition: serverlist.qc:101