Quake II RTX doxygen  1.0 dev
servers.c
Go to the documentation of this file.
1 /*
2 Copyright (C) 2012 Andrey Nazarov
3 
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
8 
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13 
14 You should have received a copy of the GNU General Public License along
15 with this program; if not, write to the Free Software Foundation, Inc.,
16 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17 */
18 
19 #include "ui.h"
20 #include "common/files.h"
21 #include "common/net/net.h"
22 #include "client/video.h"
23 #include "system/system.h"
24 
25 /*
26 =============================================================================
27 
28 SERVERS MENU
29 
30 =============================================================================
31 */
32 
33 #define MAX_STATUS_RULES 64
34 #define MAX_STATUS_SERVERS 1024
35 
36 #define SLOT_EXTRASIZE q_offsetof(serverslot_t, name)
37 
38 #define COL_NAME 0
39 #define COL_MOD 1
40 #define COL_MAP 2
41 #define COL_PLAYERS 3
42 #define COL_RTT 4
43 #define COL_MAX 5
44 
45 // how many times to (re)ping
46 #define PING_STAGES 3
47 
48 typedef struct {
49  enum {
53  SLOT_VALID
54  } status;
55  netadr_t address;
56  char *hostname; // original domain name, only used for favorites
57  int numRules;
58  char *rules[MAX_STATUS_RULES];
60  char *players[MAX_STATUS_PLAYERS];
61  unsigned timestamp;
62  uint32_t color;
63  char name[1];
64 } serverslot_t;
65 
66 typedef struct {
71  void *names[MAX_STATUS_SERVERS];
72  char *args;
73  unsigned timestamp;
74  int pingstage;
75  int pingindex;
76  int pingtime;
77  int pingextra;
78  char *status_c;
79  char status_r[32];
80 } m_servers_t;
81 
83 
84 static cvar_t *ui_sortservers;
85 static cvar_t *ui_colorservers;
86 static cvar_t *ui_pingrate;
87 
88 static void UpdateSelection(void)
89 {
90  serverslot_t *s = NULL;
91 
92  if (m_servers.list.numItems) {
93  if (m_servers.list.curvalue >= 0) {
95  if (s->status == SLOT_VALID) {
96  m_servers.status_c = "Press Enter to connect; Space to refresh";
97  } else {
98  m_servers.status_c = "Press Space to refresh; Alt+Space to refresh all";
99  }
100  } else if (m_servers.pingstage) {
101  m_servers.status_c = "Pinging servers; Press Backspace to abort";
102  } else {
103  m_servers.status_c = "Select a server; Press Alt+Space to refresh";
104  }
105  } else {
106  m_servers.status_c = "No servers found; Press Space to refresh";
107  }
108 
109  if (s && s->status == SLOT_VALID && s->numRules && uis.width >= 640) {
111  m_servers.info.items = (void **)s->rules;
113  m_servers.info.curvalue = -1;
114  m_servers.info.prestep = 0;
115  } else {
117  m_servers.info.items = NULL;
118  m_servers.info.numItems = 0;
119  }
120 
121  if (s && s->status == SLOT_VALID && s->numPlayers) {
123  m_servers.players.items = (void **)s->players;
127  } else {
129  m_servers.players.items = NULL;
131  }
132 }
133 
134 static void UpdateStatus(void)
135 {
136  serverslot_t *slot;
137  int i, totalplayers = 0, totalservers = 0;
138 
139  for (i = 0; i < m_servers.list.numItems; i++) {
140  slot = m_servers.list.items[i];
141  if (slot->status == SLOT_VALID) {
142  totalservers++;
143  totalplayers += slot->numPlayers;
144  }
145  }
146 
148  "%d player%s on %d server%s",
149  totalplayers, totalplayers == 1 ? "" : "s",
150  totalservers, totalservers == 1 ? "" : "s");
151 }
152 
153 // doesn't free hostname!
154 static void FreeSlot(serverslot_t *slot)
155 {
156  int i;
157 
158  for (i = 0; i < slot->numRules; i++)
159  Z_Free(slot->rules[i]);
160  for (i = 0; i < slot->numPlayers; i++)
161  Z_Free(slot->players[i]);
162  Z_Free(slot);
163 }
164 
165 static serverslot_t *FindSlot(const netadr_t *search, int *index_p)
166 {
167  serverslot_t *slot, *found = NULL;
168  int i;
169 
170  for (i = 0; i < m_servers.list.numItems; i++) {
171  slot = m_servers.list.items[i];
172  if (!NET_IsEqualBaseAdr(search, &slot->address))
173  continue;
174  if (search->port && search->port != slot->address.port)
175  continue;
176  found = slot;
177  break;
178  }
179 
180  if (index_p)
181  *index_p = i;
182  return found;
183 }
184 
185 static uint32_t ColorForStatus(const serverStatus_t *status)
186 {
187  if (atoi(Info_ValueForKey(status->infostring, "needpass")) >= 1)
188  return uis.color.disabled.u32;
189 
190  if (atoi(Info_ValueForKey(status->infostring, "anticheat")) >= 2)
191  return uis.color.disabled.u32;
192 
193  if (Q_stricmp(Info_ValueForKey(status->infostring, "NoFake"), "ENABLED") == 0)
194  return uis.color.disabled.u32;
195 
196  return U32_WHITE;
197 }
198 
199 /*
200 =================
201 UI_StatusEvent
202 
203 A server status response has been received, validated and parsed.
204 =================
205 */
206 void UI_StatusEvent(const serverStatus_t *status)
207 {
208  serverslot_t *slot;
209  char *hostname, *host, *mod, *map, *maxclients;
210  unsigned timestamp, ping;
211  const char *info = status->infostring;
212  char key[MAX_INFO_STRING];
213  char value[MAX_INFO_STRING];
214  int i;
215 
216  // ignore unless menu is up
217  if (!m_servers.args) {
218  return;
219  }
220 
221  // see if already added
222  slot = FindSlot(&net_from, &i);
223  if (!slot) {
224  // reply to broadcast, create new slot
226  return;
227  }
229  hostname = UI_CopyString(NET_AdrToString(&net_from));
230  timestamp = m_servers.timestamp;
231  } else {
232  // free previous data
233  hostname = slot->hostname;
234  timestamp = slot->timestamp;
235  FreeSlot(slot);
236  }
237 
238  host = Info_ValueForKey(info, "hostname");
239  if (COM_IsWhite(host)) {
240  host = hostname;
241  }
242 
243  mod = Info_ValueForKey(info, "game");
244  if (COM_IsWhite(mod)) {
245  mod = "baseq2";
246  }
247 
248  map = Info_ValueForKey(info, "mapname");
249  if (COM_IsWhite(map)) {
250  map = "???";
251  }
252 
253  maxclients = Info_ValueForKey(info, "maxclients");
254  if (!COM_IsUint(maxclients)) {
255  maxclients = "?";
256  }
257 
258  if (timestamp > com_eventTime)
259  timestamp = com_eventTime;
260 
261  ping = com_eventTime - timestamp;
262  if (ping > 999)
263  ping = 999;
264 
265  slot = UI_FormatColumns(SLOT_EXTRASIZE, host, mod, map,
266  va("%d/%s", status->numPlayers, maxclients),
267  va("%u", ping),
268  NULL);
269  slot->status = SLOT_VALID;
270  slot->address = net_from;
271  slot->hostname = hostname;
272  slot->color = ColorForStatus(status);
273 
274  m_servers.list.items[i] = slot;
275 
276  slot->numRules = 0;
277  while (slot->numRules < MAX_STATUS_RULES) {
278  Info_NextPair(&info, key, value);
279  if (!info)
280  break;
281 
282  if (!key[0])
283  strcpy(key, "<MISSING KEY>");
284 
285  if (!value[0])
286  strcpy(value, "<MISSING VALUE>");
287 
288  slot->rules[slot->numRules++] =
289  UI_FormatColumns(0, key, value, NULL);
290  }
291 
292  slot->numPlayers = status->numPlayers;
293  for (i = 0; i < status->numPlayers; i++) {
294  slot->players[i] =
296  va("%d", status->players[i].score),
297  va("%d", status->players[i].ping),
298  status->players[i].name,
299  NULL);
300  }
301 
302  slot->timestamp = timestamp;
303 
304  // don't sort when manually refreshing
305  if (m_servers.pingstage)
307 
308  UpdateStatus();
309  UpdateSelection();
310 }
311 
312 /*
313 =================
314 UI_ErrorEvent
315 
316 An ICMP destination-unreachable error has been received.
317 =================
318 */
319 void UI_ErrorEvent(netadr_t *from)
320 {
321  serverslot_t *slot;
322  netadr_t address;
323  char *hostname;
324  unsigned timestamp, ping;
325  int i;
326 
327  // ignore unless menu is up
328  if (!m_servers.args)
329  return;
330 
331  slot = FindSlot(from, &i);
332  if (!slot)
333  return;
334 
335  // only mark unreplied slots as invalid
336  if (slot->status != SLOT_PENDING)
337  return;
338 
339  address = slot->address;
340  hostname = slot->hostname;
341  timestamp = slot->timestamp;
342  FreeSlot(slot);
343 
344  if (timestamp > com_eventTime)
345  timestamp = com_eventTime;
346 
347  ping = com_eventTime - timestamp;
348  if (ping > 999)
349  ping = 999;
350 
351  slot = UI_FormatColumns(SLOT_EXTRASIZE, hostname,
352  "???", "???", "down", va("%u", ping), NULL);
353  slot->status = SLOT_ERROR;
354  slot->address = address;
355  slot->hostname = hostname;
356  slot->color = U32_WHITE;
357  slot->numRules = 0;
358  slot->numPlayers = 0;
359  slot->timestamp = timestamp;
360 
361  m_servers.list.items[i] = slot;
362 }
363 
365 {
366  serverslot_t *slot;
367 
368  if (!m_servers.list.numItems)
369  return QMS_BEEP;
370  if (m_servers.list.curvalue < 0)
371  return QMS_BEEP;
372 
374  if (slot->status == SLOT_ERROR)
375  return QMS_BEEP;
376 
377  Cvar_Set("rcon_address", slot->hostname);
378  return QMS_OUT;
379 }
380 
382 {
383  serverslot_t *slot;
384 
385  if (!m_servers.list.numItems)
386  return QMS_BEEP;
387  if (m_servers.list.curvalue < 0)
388  return QMS_BEEP;
389 
391 
393  return QMS_OUT;
394 }
395 
397 {
398  serverslot_t *slot;
399  netadr_t address;
400  char *hostname;
401 
402  if (!m_servers.list.numItems)
403  return QMS_BEEP;
404  if (m_servers.list.curvalue < 0)
405  return QMS_BEEP;
406 
408  address = slot->address;
409  hostname = slot->hostname;
410  FreeSlot(slot);
411 
412  slot = UI_FormatColumns(SLOT_EXTRASIZE, hostname,
413  "???", "???", "?/?", "???", NULL);
414  slot->status = SLOT_PENDING;
415  slot->address = address;
416  slot->hostname = hostname;
417  slot->color = U32_WHITE;
418  slot->numRules = 0;
419  slot->numPlayers = 0;
420  slot->timestamp = com_eventTime;
421 
423 
424  UpdateStatus();
425  UpdateSelection();
426 
427  CL_SendStatusRequest(&slot->address);
428  return QMS_SILENT;
429 }
430 
431 static void AddServer(const netadr_t *address, const char *hostname)
432 {
433  netadr_t tmp;
434  serverslot_t *slot;
435 
437  return;
438 
439  if (!address) {
440  // either address or hostname can be NULL, but not both
441  if (!hostname)
442  return;
443 
444  if (!NET_StringToAdr(hostname, &tmp, PORT_SERVER)) {
445  Com_Printf("Bad server address: %s\n", hostname);
446  return;
447  }
448 
449  address = &tmp;
450  }
451 
452  // ignore if already listed
453  if (FindSlot(address, NULL))
454  return;
455 
456  if (!hostname)
457  hostname = NET_AdrToString(address);
458 
459  // privileged ports are not allowed
460  if (BigShort(address->port) < 1024) {
461  Com_Printf("Bad server port: %s\n", hostname);
462  return;
463  }
464 
465  slot = UI_FormatColumns(SLOT_EXTRASIZE, hostname,
466  "???", "???", "?/?", "???", NULL);
467  slot->status = SLOT_IDLE;
468  slot->address = *address;
469  slot->hostname = UI_CopyString(hostname);
470  slot->color = U32_WHITE;
471  slot->numRules = 0;
472  slot->numPlayers = 0;
473  slot->timestamp = com_eventTime;
474 
476 }
477 
478 static void ParsePlain(void *data, size_t len, size_t chunk)
479 {
480  char *list, *p;
481 
482  if (!data)
483  return;
484 
485  list = data;
486  while (*list) {
487  p = strchr(list, '\n');
488  if (p) {
489  if (p > list && *(p - 1) == '\r')
490  *(p - 1) = 0;
491  *p = 0;
492  }
493 
494  if (*list)
495  AddServer(NULL, list);
496 
497  if (!p)
498  break;
499  list = p + 1;
500  }
501 }
502 
503 static void ParseBinary(void *data, size_t len, size_t chunk)
504 {
505  netadr_t address;
506  byte *ptr;
507 
508  if (!data)
509  return;
510 
511  memset(&address, 0, sizeof(address));
512  address.type = NA_IP;
513 
514  ptr = data;
515  while (len >= chunk) {
516  memcpy(address.ip.u8, ptr, 4);
517  memcpy(&address.port, ptr + 4, 2);
518  ptr += chunk;
519  len -= chunk;
520 
521  AddServer(&address, NULL);
522  }
523 }
524 
525 static void ParseAddressBook(void)
526 {
527  cvar_t *var;
528  int i;
529 
530  for (i = 0; i < MAX_STATUS_SERVERS; i++) {
531  var = Cvar_FindVar(va("adr%i", i));
532  if (!var)
533  break;
534 
535  if (var->string[0])
536  AddServer(NULL, var->string);
537  }
538 }
539 
540 static void ParseMasterArgs(netadr_t *broadcast)
541 {
542  void *data;
543  ssize_t len;
544  void (*parse)(void *, size_t, size_t);
545  size_t chunk;
546  char *s, *p;
547  int i, argc;
548 
550 
551  argc = Cmd_Argc();
552  if (!argc) {
553  // default action to take when no URLs are given
555  broadcast->type = NA_BROADCAST;
556  broadcast->port = BigShort(PORT_SERVER);
557  return;
558  }
559 
560  for (i = 0; i < argc; i++) {
561  s = Cmd_Argv(i);
562  if (!*s)
563  continue;
564 
565  // parse binary format specifier
566  parse = ParsePlain;
567  chunk = 0;
568  if (*s == '+' || *s == '-') {
569  parse = ParseBinary;
570  chunk = strtoul(s, &p, 10);
571  if (s == p) {
572  chunk = 6;
573  s = p + 1;
574  } else {
575  if (chunk < 6)
576  goto ignore;
577  s = p;
578  }
579  }
580 
581  if (!strncmp(s, "file://", 7)) {
582  len = FS_LoadFile(s + 7, &data);
583  if (len < 0)
584  continue;
585  (*parse)(data, len, chunk);
586  FS_FreeFile(data);
587  continue;
588  }
589 
590  if (!strncmp(s, "http://", 7)) {
591 #if USE_CURL
592  len = HTTP_FetchFile(s + 7, &data);
593  if (len < 0)
594  continue;
595  (*parse)(data, len, chunk);
596  Z_Free(data);
597 #else
598  Com_Printf("Can't fetch '%s', no HTTP support compiled in.\n", s);
599 #endif
600  continue;
601  }
602 
603  if (!strncmp(s, "favorites://", 12)) {
605  continue;
606  }
607 
608  if (!strncmp(s, "broadcast://", 12)) {
609  broadcast->type = NA_BROADCAST;
610  broadcast->port = BigShort(PORT_SERVER);
611  continue;
612  }
613 
614  if (!strncmp(s, "quake2://", 9)) {
615  AddServer(NULL, s + 9);
616  continue;
617  }
618 
619 ignore:
620  Com_Printf("Ignoring invalid master URL: %s\n", s);
621  }
622 }
623 
624 static void ClearServers(void)
625 {
626  serverslot_t *slot;
627  int i;
628 
629  for (i = 0; i < m_servers.list.numItems; i++) {
630  slot = m_servers.list.items[i];
631  m_servers.list.items[i] = NULL;
632  Z_Free(slot->hostname);
633  FreeSlot(slot);
634  }
635 
636  m_servers.list.numItems = 0;
637  m_servers.list.curvalue = -1;
638  m_servers.list.prestep = 0;
639  m_servers.info.items = NULL;
640  m_servers.info.numItems = 0;
641  m_servers.players.items = NULL;
643  m_servers.pingstage = 0;
644 }
645 
646 static void FinishPingStage(void)
647 {
648  m_servers.pingstage = 0;
649  m_servers.pingindex = 0;
650  m_servers.pingextra = 0;
651 
652  // if the user didn't select anything yet, select the first item
653  if (m_servers.list.curvalue < 0)
654  m_servers.list.curvalue = 0;
655 
656  UpdateSelection();
657 }
658 
659 static void CalcPingRate(void)
660 {
661  extern cvar_t *info_rate;
662  int rate = Cvar_ClampInteger(ui_pingrate, 0, 100);
663 
664  // assume average 450 bytes per reply packet
665  if (!rate)
666  rate = info_rate->integer / 450;
667 
668  // don't allow more than 100 packets/sec
669  clamp(rate, 1, 100);
670 
671  // drop rate by stage
672  m_servers.pingtime = (1000 * PING_STAGES) / (rate * m_servers.pingstage);
673 }
674 
675 /*
676 =================
677 UI_Frame
678 
679 =================
680 */
681 void UI_Frame(int msec)
682 {
683  serverslot_t *slot;
684 
685  if (!m_servers.pingstage)
686  return;
687 
688  m_servers.pingextra += msec;
690  return;
691 
693 
694  // send out next status packet
697  if (slot->status > SLOT_PENDING)
698  continue;
699  slot->status = SLOT_PENDING;
700  slot->timestamp = com_eventTime;
701  CL_SendStatusRequest(&slot->address);
702  break;
703  }
704 
706  m_servers.pingindex = 0;
707  if (--m_servers.pingstage == 0)
708  FinishPingStage();
709  else
710  CalcPingRate();
711  }
712 }
713 
714 static void PingServers(void)
715 {
716  netadr_t broadcast;
717 
718  S_StopAllSounds();
719 
720  ClearServers();
721  UpdateStatus();
722 
723  // update status string now, because fetching and
724  // resolving will take some time
725  m_servers.status_c = "Resolving servers, please wait...";
727 
728  // fetch and resolve servers
729  memset(&broadcast, 0, sizeof(broadcast));
730  ParseMasterArgs(&broadcast);
731 
733 
734  // optionally ping broadcast
735  if (broadcast.type)
736  CL_SendStatusRequest(&broadcast);
737 
738  if (!m_servers.list.numItems) {
739  FinishPingStage();
740  return;
741  }
742 
743  // begin pinging servers
745  m_servers.pingindex = 0;
746  m_servers.pingextra = 0;
747  CalcPingRate();
748 }
749 
750 static int statuscmp(serverslot_t *s1, serverslot_t *s2)
751 {
752  if (s1->status == s2->status)
753  return 0;
754  if (s1->status != SLOT_VALID && s2->status == SLOT_VALID)
755  return 1;
756  if (s2->status != SLOT_VALID && s1->status == SLOT_VALID)
757  return -1;
758  return 0;
759 }
760 
761 static int namecmp(serverslot_t *s1, serverslot_t *s2, int col)
762 {
763  char *n1 = UI_GetColumn(s1->name, col);
764  char *n2 = UI_GetColumn(s2->name, col);
765 
766  return Q_stricmp(n1, n2) * m_servers.list.sortdir;
767 }
768 
769 static int pingcmp(serverslot_t *s1, serverslot_t *s2)
770 {
771  int n1 = atoi(UI_GetColumn(s1->name, COL_RTT));
772  int n2 = atoi(UI_GetColumn(s2->name, COL_RTT));
773 
774  return (n1 - n2) * m_servers.list.sortdir;
775 }
776 
777 static int playercmp(serverslot_t *s1, serverslot_t *s2)
778 {
779  return (s2->numPlayers - s1->numPlayers) * m_servers.list.sortdir;
780 }
781 
783 {
784  if (s1->address.ip.u32 > s2->address.ip.u32)
785  return 1;
786  if (s1->address.ip.u32 < s2->address.ip.u32)
787  return -1;
788  if (s1->address.port > s2->address.port)
789  return 1;
790  if (s1->address.port < s2->address.port)
791  return -1;
792  return 0;
793 }
794 
795 static int slotcmp(const void *p1, const void *p2)
796 {
797  serverslot_t *s1 = *(serverslot_t **)p1;
798  serverslot_t *s2 = *(serverslot_t **)p2;
799  int r;
800 
801  // sort by validity
802  r = statuscmp(s1, s2);
803  if (r)
804  return r;
805 
806  // sort by primary column
807  switch (m_servers.list.sortcol) {
808  case COL_NAME:
809  break;
810  case COL_MOD:
811  r = namecmp(s1, s2, COL_MOD);
812  break;
813  case COL_MAP:
814  r = namecmp(s1, s2, COL_MAP);
815  break;
816  case COL_PLAYERS:
817  r = playercmp(s1, s2);
818  break;
819  case COL_RTT:
820  r = pingcmp(s1, s2);
821  break;
822  }
823  if (r)
824  return r;
825 
826  // stabilize sort
827  r = namecmp(s1, s2, COL_NAME);
828  if (r)
829  return r;
830 
831  return addresscmp(s1, s2);
832 }
833 
835 {
837  return QMS_SILENT;
838 }
839 
840 static void ui_sortservers_changed(cvar_t *self)
841 {
842  int i = Cvar_ClampInteger(self, -COL_MAX, COL_MAX);
843 
844  if (i > 0) {
845  // ascending
846  m_servers.list.sortdir = 1;
847  m_servers.list.sortcol = i - 1;
848  } else if (i < 0) {
849  // descending
850  m_servers.list.sortdir = -1;
851  m_servers.list.sortcol = -i - 1;
852  } else {
853  // don't sort
854  m_servers.list.sortdir = 1;
855  m_servers.list.sortcol = -1;
856  }
857 
858  if (m_servers.list.numItems) {
860  }
861 }
862 
864 {
865  serverslot_t *slot;
866 
867  if (!m_servers.list.numItems)
868  return QMS_BEEP;
869  if (m_servers.list.curvalue < 0)
870  return QMS_BEEP;
871 
873  if (slot->status == SLOT_ERROR)
874  return QMS_BEEP;
875 
876  Cbuf_AddText(&cmd_buffer, va("connect %s\n", slot->hostname));
877  return QMS_SILENT;
878 }
879 
881 {
882  UpdateSelection();
883  return QMS_MOVE;
884 }
885 
886 static void SizeCompact(void)
887 {
888  int w = uis.width - MLIST_SCROLLBAR_WIDTH;
889 
890 //
891 // server list
892 //
893  m_servers.list.generic.x = 0;
894  m_servers.list.generic.y = CHAR_HEIGHT;
895  m_servers.list.generic.height = uis.height / 2 - CHAR_HEIGHT;
896 
897  m_servers.list.columns[0].width = w - 10 * CHAR_WIDTH - MLIST_PADDING * 2;
898  m_servers.list.columns[1].width = 0;
899  m_servers.list.columns[2].width = 0;
900  m_servers.list.columns[3].width = 7 * CHAR_WIDTH + MLIST_PADDING;
901  m_servers.list.columns[4].width = 3 * CHAR_WIDTH + MLIST_PADDING;
902 
903 //
904 // player list
905 //
907  m_servers.players.generic.y = uis.height / 2 + 1;
908  m_servers.players.generic.height = uis.height / 2 - CHAR_HEIGHT - 2;
909 
910  m_servers.players.columns[0].width = 3 * CHAR_WIDTH + MLIST_PADDING;
911  m_servers.players.columns[1].width = 3 * CHAR_WIDTH + MLIST_PADDING;
912  m_servers.players.columns[2].width = w - 6 * CHAR_WIDTH - MLIST_PADDING * 2;
913 
915 }
916 
917 static void SizeFull(void)
918 {
919  int w = uis.width - MLIST_SCROLLBAR_WIDTH - 21 * CHAR_WIDTH - MLIST_PADDING * 3;
920 
921 //
922 // server list
923 //
924  m_servers.list.generic.x = 0;
925  m_servers.list.generic.y = CHAR_HEIGHT;
926  m_servers.list.generic.height = uis.height / 2 - CHAR_HEIGHT;
927 
928  m_servers.list.columns[0].width = w - 26 * CHAR_WIDTH - MLIST_PADDING * 4;
929  m_servers.list.columns[1].width = 8 * CHAR_WIDTH + MLIST_PADDING;
930  m_servers.list.columns[2].width = 8 * CHAR_WIDTH + MLIST_PADDING;
931  m_servers.list.columns[3].width = 7 * CHAR_WIDTH + MLIST_PADDING;
932  m_servers.list.columns[4].width = 3 * CHAR_WIDTH + MLIST_PADDING;
933 
934 //
935 // server info
936 //
937  m_servers.info.generic.x = 0;
938  m_servers.info.generic.y = uis.height / 2 + 1;
939  m_servers.info.generic.height = uis.height / 2 - CHAR_HEIGHT - 2;
940 
941  m_servers.info.columns[0].width = w / 3;
942  m_servers.info.columns[1].width = w - w / 3;
943 
944 //
945 // player list
946 //
948  m_servers.players.generic.y = CHAR_HEIGHT;
949  m_servers.players.generic.height = uis.height - CHAR_HEIGHT * 2 - 1;
950 
951  m_servers.players.columns[0].width = 3 * CHAR_WIDTH + MLIST_PADDING;
952  m_servers.players.columns[1].width = 3 * CHAR_WIDTH + MLIST_PADDING;
953  m_servers.players.columns[2].width = 15 * CHAR_WIDTH + MLIST_PADDING;
954 
956 }
957 
958 static void Size(menuFrameWork_t *self)
959 {
960  if (uis.width >= 640)
961  SizeFull();
962  else
963  SizeCompact();
964  UpdateSelection();
965 }
966 
967 static menuSound_t Keydown(menuFrameWork_t *self, int key)
968 {
969  // ignore autorepeats
970  if (Key_IsDown(key) > 1)
971  return QMS_NOTHANDLED;
972 
973  switch (key) {
974  case 'r':
975  if (Key_IsDown(K_CTRL))
976  return SetRconAddress();
977  return QMS_NOTHANDLED;
978 
979  case 'c':
980  if (Key_IsDown(K_CTRL))
981  return CopyAddress();
982  return QMS_NOTHANDLED;
983 
984  case K_SPACE:
985  if (Key_IsDown(K_ALT) || !m_servers.list.numItems) {
986  PingServers();
987  return QMS_SILENT;
988  }
989  return PingSelected();
990 
991  case K_BACKSPACE:
992  if (m_servers.pingstage) {
993  FinishPingStage();
994  return QMS_OUT;
995  }
996  return QMS_SILENT;
997 
998  default:
999  return QMS_NOTHANDLED;
1000  }
1001 }
1002 
1003 static void DrawStatus(void)
1004 {
1005  int w;
1006 
1009  else
1010  w = uis.width;
1011 
1012  R_DrawFill8(0, uis.height - CHAR_HEIGHT, w, CHAR_HEIGHT, 4);
1013  R_DrawFill8(w, uis.height - CHAR_HEIGHT, uis.width - w, CHAR_HEIGHT, 0);
1014 
1015  if (m_servers.status_c)
1016  UI_DrawString(uis.width / 2, uis.height - CHAR_HEIGHT, UI_CENTER, m_servers.status_c);
1017 
1018  if (uis.width < 800)
1019  return;
1020 
1021  if (m_servers.list.numItems)
1022  UI_DrawString(uis.width, uis.height - CHAR_HEIGHT, UI_RIGHT, m_servers.status_r);
1023 
1024  if (m_servers.list.numItems && m_servers.list.curvalue >= 0) {
1026  if (slot->status > SLOT_PENDING) {
1027  UI_DrawString(0, uis.height - CHAR_HEIGHT, UI_LEFT, slot->hostname);
1028  }
1029  }
1030 }
1031 
1032 static void Draw(menuFrameWork_t *self)
1033 {
1034  Menu_Draw(self);
1035  DrawStatus();
1036 }
1037 
1038 static qboolean Push(menuFrameWork_t *self)
1039 {
1040  // save our arguments for refreshing
1042  return qtrue;
1043 }
1044 
1045 static void Pop(menuFrameWork_t *self)
1046 {
1047  ClearServers();
1048  if (m_servers.args) {
1050  m_servers.args = NULL;
1051  }
1052 }
1053 
1054 static void Expose(menuFrameWork_t *self)
1055 {
1056  PingServers();
1058 }
1059 
1060 static void Free(menuFrameWork_t *self)
1061 {
1062  memset(&m_servers, 0, sizeof(m_servers));
1063 }
1064 
1065 static void ui_colorservers_changed(cvar_t *self)
1066 {
1067  if (self->integer)
1069  else
1071 }
1072 
1073 void M_Menu_Servers(void)
1074 {
1075  ui_sortservers = Cvar_Get("ui_sortservers", "0", 0);
1077  ui_colorservers = Cvar_Get("ui_colorservers", "0", 0);
1079  ui_pingrate = Cvar_Get("ui_pingrate", "0", 0);
1080 
1081  m_servers.menu.name = "servers";
1082  m_servers.menu.title = "Server Browser";
1083 
1084  m_servers.menu.draw = Draw;
1086  m_servers.menu.push = Push;
1087  m_servers.menu.pop = Pop;
1088  m_servers.menu.size = Size;
1090  m_servers.menu.free = Free;
1092  m_servers.menu.color.u32 = uis.color.background.u32;
1094 
1095 //
1096 // server list
1097 //
1104  m_servers.list.sortdir = 1;
1105  m_servers.list.sortcol = -1;
1106  m_servers.list.sort = Sort;
1109 
1110  m_servers.list.columns[0].uiFlags = UI_LEFT;
1111  m_servers.list.columns[0].name = "Hostname";
1112  m_servers.list.columns[1].uiFlags = UI_CENTER;
1113  m_servers.list.columns[1].name = "Mod";
1114  m_servers.list.columns[2].uiFlags = UI_CENTER;
1115  m_servers.list.columns[2].name = "Map";
1116  m_servers.list.columns[3].uiFlags = UI_CENTER;
1117  m_servers.list.columns[3].name = "Players";
1118  m_servers.list.columns[4].uiFlags = UI_RIGHT;
1119  m_servers.list.columns[4].name = "RTT";
1120 
1122 
1123 //
1124 // server info
1125 //
1131 
1132  m_servers.info.columns[0].uiFlags = UI_LEFT;
1133  m_servers.info.columns[0].name = "Key";
1134  m_servers.info.columns[1].uiFlags = UI_LEFT;
1135  m_servers.info.columns[1].name = "Value";
1136 
1137 //
1138 // player list
1139 //
1145 
1146  m_servers.players.columns[0].uiFlags = UI_RIGHT;
1147  m_servers.players.columns[0].name = "Frg";
1148  m_servers.players.columns[1].uiFlags = UI_RIGHT;
1149  m_servers.players.columns[1].name = "RTT";
1150  m_servers.players.columns[2].uiFlags = UI_LEFT;
1151  m_servers.players.columns[2].name = "Name";
1152 
1156 
1157  List_Append(&ui_menus, &m_servers.menu.entry);
1158 }
1159 
m_servers_t::pingextra
int pingextra
Definition: servers.c:77
COL_RTT
#define COL_RTT
Definition: servers.c:42
ui_sortservers_changed
static void ui_sortservers_changed(cvar_t *self)
Definition: servers.c:840
ui.h
menuList_s::sortcol
int sortcol
Definition: ui.h:224
menuCommon_s
Definition: ui.h:140
menuFrameWork_s::title
char * title
Definition: ui.h:101
menuList_s::columns
menuListColumn_t columns[MAX_COLUMNS]
Definition: ui.h:222
Cvar_Set
cvar_t * Cvar_Set(const char *var_name, const char *value)
Definition: cvar.c:466
uiStatic_s::transparent
qboolean transparent
Definition: ui.h:301
QMS_NOTHANDLED
@ QMS_NOTHANDLED
Definition: ui.h:68
menuCommon_s::change
menuSound_t(* change)(struct menuCommon_s *)
Definition: ui.h:158
serverslot_t::status
enum serverslot_t::@8 status
menuCommon_s::type
menuType_t type
Definition: ui.h:141
UI_StatusEvent
void UI_StatusEvent(const serverStatus_t *status)
Definition: servers.c:206
NET_AdrToString
char * NET_AdrToString(const netadr_t *a)
Definition: net.c:257
menuFrameWork_s::name
char * name
Definition: ui.h:101
maxclients
cvar_t * maxclients
Definition: g_main.c:42
Q_snprintf
size_t Q_snprintf(char *dest, size_t size, const char *fmt,...)
Definition: shared.c:846
MenuList_Sort
void MenuList_Sort(menuList_t *l, int offset, int(*cmpfunc)(const void *, const void *))
Definition: menu.c:1465
FinishPingStage
static void FinishPingStage(void)
Definition: servers.c:646
DrawStatus
static void DrawStatus(void)
Definition: servers.c:1003
menuList_s::generic
menuCommon_t generic
Definition: ui.h:204
m_servers_t::pingindex
int pingindex
Definition: servers.c:75
serverslot_t::numPlayers
int numPlayers
Definition: servers.c:59
m_servers_t::pingstage
int pingstage
Definition: servers.c:74
menuFrameWork_s::size
void(* size)(struct menuFrameWork_s *)
Definition: ui.h:135
menuCommon_s::activate
menuSound_t(* activate)(struct menuCommon_s *)
Definition: ui.h:157
menuCommon_s::height
int height
Definition: ui.h:150
Free
static void Free(menuFrameWork_t *self)
Definition: servers.c:1060
CopyAddress
static menuSound_t CopyAddress(void)
Definition: servers.c:381
S_StopAllSounds
void S_StopAllSounds(void)
Definition: main.c:932
Cvar_Get
cvar_t * Cvar_Get(const char *var_name, const char *var_value, int flags)
Definition: cvar.c:257
menuFrameWork_s::free
void(* free)(struct menuFrameWork_s *)
Definition: ui.h:136
menuFrameWork_s::push
qboolean(* push)(struct menuFrameWork_s *)
Definition: ui.h:131
Pop
static void Pop(menuFrameWork_t *self)
Definition: servers.c:1045
m_servers_t::menu
menuFrameWork_t menu
Definition: servers.c:67
SCR_UpdateScreen
void SCR_UpdateScreen(void)
Definition: screen.c:2142
uiStatic_s::height
int height
Definition: ui.h:292
Expose
static void Expose(menuFrameWork_t *self)
Definition: servers.c:1054
menuFrameWork_s::expose
void(* expose)(struct menuFrameWork_s *)
Definition: ui.h:133
ui_colorservers_changed
static void ui_colorservers_changed(cvar_t *self)
Definition: servers.c:1065
ui_colorservers
static cvar_t * ui_colorservers
Definition: servers.c:85
slotcmp
static int slotcmp(const void *p1, const void *p2)
Definition: servers.c:795
m_servers_t::status_c
char * status_c
Definition: servers.c:78
menuList_s::numcolumns
int numcolumns
Definition: ui.h:223
ParseMasterArgs
static void ParseMasterArgs(netadr_t *broadcast)
Definition: servers.c:540
FreeSlot
static void FreeSlot(serverslot_t *slot)
Definition: servers.c:154
Push
static qboolean Push(menuFrameWork_t *self)
Definition: servers.c:1038
R_DrawFill8
void(* R_DrawFill8)(int x, int y, int w, int h, int c)
Definition: refresh.c:422
menuFrameWork_s::image
qhandle_t image
Definition: ui.h:110
QMS_SILENT
@ QMS_SILENT
Definition: ui.h:69
SLOT_EXTRASIZE
#define SLOT_EXTRASIZE
Definition: servers.c:36
m_servers_t::args
char * args
Definition: servers.c:72
m_servers_t::list
menuList_t list
Definition: servers.c:68
COL_MOD
#define COL_MOD
Definition: servers.c:39
UI_GetColumn
char * UI_GetColumn(char *s, int n)
Definition: ui.c:281
menuFrameWork_s
Definition: ui.h:98
Cmd_TokenizeString
void Cmd_TokenizeString(const char *text, qboolean macroExpand)
Definition: cmd.c:1399
menuList_s::mlFlags
int mlFlags
Definition: ui.h:209
UI_ErrorEvent
void UI_ErrorEvent(netadr_t *from)
Definition: servers.c:319
ClearServers
static void ClearServers(void)
Definition: servers.c:624
menuList_s::items
void ** items
Definition: ui.h:206
serverslot_t::name
char name[1]
Definition: servers.c:63
Cmd_RawArgsFrom
char * Cmd_RawArgsFrom(int from)
Definition: cmd.c:1021
UI_FormatColumns
void * UI_FormatColumns(int extrasize,...)
Definition: ui.c:251
AddServer
static void AddServer(const netadr_t *address, const char *hostname)
Definition: servers.c:431
Cmd_Argv
char * Cmd_Argv(int arg)
Definition: cmd.c:899
Sys_Milliseconds
unsigned Sys_Milliseconds(void)
Definition: system.c:644
addresscmp
static int addresscmp(serverslot_t *s1, serverslot_t *s2)
Definition: servers.c:782
serverslot_t::rules
char * rules[MAX_STATUS_RULES]
Definition: servers.c:58
UI_Frame
void UI_Frame(int msec)
Definition: servers.c:681
Cmd_Argc
int Cmd_Argc(void)
Definition: cmd.c:889
SizeCompact
static void SizeCompact(void)
Definition: servers.c:886
net_from
netadr_t net_from
Definition: net.c:90
m_servers_t::players
menuList_t players
Definition: servers.c:70
MLIST_SCROLLBAR_WIDTH
#define MLIST_SCROLLBAR_WIDTH
Definition: ui.h:189
cmd_buffer
cmdbuf_t cmd_buffer
Definition: cmd.c:49
info_rate
cvar_t * info_rate
Definition: main.c:83
QMS_MOVE
@ QMS_MOVE
Definition: ui.h:71
uis
uiStatic_t uis
Definition: ui.c:24
menuListColumn_s::uiFlags
int uiFlags
Definition: ui.h:200
serverslot_t
Definition: servers.c:48
menuListColumn_s::width
int width
Definition: ui.h:199
ParseBinary
static void ParseBinary(void *data, size_t len, size_t chunk)
Definition: servers.c:503
QMF_HASFOCUS
#define QMF_HASFOCUS
Definition: ui.h:62
PingServers
static void PingServers(void)
Definition: servers.c:714
serverslot_t::timestamp
unsigned timestamp
Definition: servers.c:61
menuList_s::sort
menuSound_t(* sort)(struct menuList_s *)
Definition: ui.h:226
Menu_Draw
void Menu_Draw(menuFrameWork_t *menu)
Definition: menu.c:2196
Cbuf_AddText
void Cbuf_AddText(cmdbuf_t *buf, const char *text)
Definition: cmd.c:95
menuCommon_s::x
int x
Definition: ui.h:149
Info_ValueForKey
char * Info_ValueForKey(const char *s, const char *key)
Definition: shared.c:945
va
char * va(const char *format,...)
Definition: shared.c:429
Z_Free
void Z_Free(void *ptr)
Definition: zone.c:147
Draw
static void Draw(menuFrameWork_t *self)
Definition: servers.c:1032
menuList_s::sortdir
int sortdir
Definition: ui.h:224
Change
static menuSound_t Change(menuCommon_t *self)
Definition: servers.c:880
COL_MAX
#define COL_MAX
Definition: servers.c:43
UI_CopyString
#define UI_CopyString(s)
Definition: ui.h:35
Key_IsDown
int Key_IsDown(int key)
Definition: keys.c:204
QMS_OUT
@ QMS_OUT
Definition: ui.h:72
UI_DrawString
void UI_DrawString(int x, int y, int flags, const char *string)
Definition: ui.c:314
M_Menu_Servers
void M_Menu_Servers(void)
Definition: servers.c:1073
statuscmp
static int statuscmp(serverslot_t *s1, serverslot_t *s2)
Definition: servers.c:750
NET_StringToAdr
qboolean NET_StringToAdr(const char *s, netadr_t *a, int default_port)
Definition: net.c:332
SetRconAddress
static menuSound_t SetRconAddress(void)
Definition: servers.c:364
menuSound_t
menuSound_t
Definition: ui.h:67
void
void(APIENTRY *qwglDrawBuffer)(GLenum mode)
Keydown
static menuSound_t Keydown(menuFrameWork_t *self, int key)
Definition: servers.c:967
QMF_LEFT_JUSTIFY
#define QMF_LEFT_JUSTIFY
Definition: ui.h:59
menuList_s::numItems
int numItems
Definition: ui.h:207
Menu_AddItem
void Menu_AddItem(menuFrameWork_t *menu, void *item)
Definition: menu.c:1785
COM_IsWhite
qboolean COM_IsWhite(const char *s)
Definition: shared.c:366
serverslot_t::numRules
int numRules
Definition: servers.c:57
COL_PLAYERS
#define COL_PLAYERS
Definition: servers.c:41
menuList_s::extrasize
int extrasize
Definition: ui.h:210
ui_menus
list_t ui_menus
MLF_SCROLLBAR
#define MLF_SCROLLBAR
Definition: ui.h:194
COL_MAP
#define COL_MAP
Definition: servers.c:40
m_servers_t::status_r
char status_r[32]
Definition: servers.c:79
menuCommon_s::y
int y
Definition: ui.h:149
menuFrameWork_s::color
color_t color
Definition: ui.h:111
MLF_HEADER
#define MLF_HEADER
Definition: ui.h:193
QMS_BEEP
@ QMS_BEEP
Definition: ui.h:73
serverslot_t::SLOT_ERROR
@ SLOT_ERROR
Definition: servers.c:52
COM_IsUint
qboolean COM_IsUint(const char *s)
Definition: shared.c:330
m_servers
static m_servers_t m_servers
Definition: servers.c:82
ParsePlain
static void ParsePlain(void *data, size_t len, size_t chunk)
Definition: servers.c:478
uiStatic_s::color
struct uiStatic_s::@9 color
menuCommon_s::flags
int flags
Definition: ui.h:152
playercmp
static int playercmp(serverslot_t *s1, serverslot_t *s2)
Definition: servers.c:777
m_servers_t::names
void * names[MAX_STATUS_SERVERS]
Definition: servers.c:71
PING_STAGES
#define PING_STAGES
Definition: servers.c:46
Size
static void Size(menuFrameWork_t *self)
Definition: servers.c:958
uiStatic_s::disabled
color_t disabled
Definition: ui.h:318
namecmp
static int namecmp(serverslot_t *s1, serverslot_t *s2, int col)
Definition: servers.c:761
UpdateStatus
static void UpdateStatus(void)
Definition: servers.c:134
Cvar_ClampInteger
int Cvar_ClampInteger(cvar_t *var, int min, int max)
Definition: cvar.c:549
menuListColumn_s::name
char * name
Definition: ui.h:198
FindSlot
static serverslot_t * FindSlot(const netadr_t *search, int *index_p)
Definition: servers.c:165
m_servers_t::timestamp
unsigned timestamp
Definition: servers.c:73
ui_sortservers
static cvar_t * ui_sortservers
Definition: servers.c:84
serverslot_t::players
char * players[MAX_STATUS_PLAYERS]
Definition: servers.c:60
ui_pingrate
static cvar_t * ui_pingrate
Definition: servers.c:86
MAX_STATUS_SERVERS
#define MAX_STATUS_SERVERS
Definition: servers.c:34
uiStatic_s::width
int width
Definition: ui.h:292
uiStatic_s::background
color_t background
Definition: ui.h:314
UpdateSelection
static void UpdateSelection(void)
Definition: servers.c:88
menuList_s
Definition: ui.h:203
serverslot_t::SLOT_PENDING
@ SLOT_PENDING
Definition: servers.c:51
QMF_HIDDEN
#define QMF_HIDDEN
Definition: ui.h:63
serverslot_t::address
netadr_t address
Definition: servers.c:55
uiStatic_s::backgroundHandle
qhandle_t backgroundHandle
Definition: ui.h:306
MAX_STATUS_RULES
#define MAX_STATUS_RULES
Definition: servers.c:33
m_servers_t
Definition: servers.c:66
ParseAddressBook
static void ParseAddressBook(void)
Definition: servers.c:525
VID_SetClipboardData
void VID_SetClipboardData(const char *data)
Definition: client.c:1354
menuFrameWork_s::entry
list_t entry
Definition: ui.h:99
MLF_COLOR
#define MLF_COLOR
Definition: ui.h:195
serverslot_t::color
uint32_t color
Definition: servers.c:62
MTYPE_LIST
@ MTYPE_LIST
Definition: ui.h:42
Cvar_FindVar
cvar_t * Cvar_FindVar(const char *var_name)
Definition: cvar.c:45
menuFrameWork_s::draw
void(* draw)(struct menuFrameWork_s *)
Definition: ui.h:134
COL_NAME
#define COL_NAME
Definition: servers.c:38
ColorForStatus
static uint32_t ColorForStatus(const serverStatus_t *status)
Definition: servers.c:185
serverslot_t::SLOT_IDLE
@ SLOT_IDLE
Definition: servers.c:50
menuList_s::prestep
int prestep
Definition: ui.h:212
MLIST_PADDING
#define MLIST_PADDING
Definition: ui.h:191
Info_NextPair
void Info_NextPair(const char **string, char *key, char *value)
Definition: shared.c:1189
menuFrameWork_s::pop
void(* pop)(struct menuFrameWork_s *)
Definition: ui.h:132
Sort
static menuSound_t Sort(menuList_t *self)
Definition: servers.c:834
menuFrameWork_s::transparent
qboolean transparent
Definition: ui.h:107
SizeFull
static void SizeFull(void)
Definition: servers.c:917
serverslot_t::hostname
char * hostname
Definition: servers.c:56
menuFrameWork_s::keydown
menuSound_t(* keydown)(struct menuFrameWork_s *, int)
Definition: ui.h:137
menuList_s::curvalue
int curvalue
Definition: ui.h:213
Connect
static menuSound_t Connect(menuCommon_t *self)
Definition: servers.c:863
m_servers_t::pingtime
int pingtime
Definition: servers.c:76
m_servers_t::info
menuList_t info
Definition: servers.c:69
com_eventTime
unsigned com_eventTime
Definition: common.c:122
CalcPingRate
static void CalcPingRate(void)
Definition: servers.c:659
pingcmp
static int pingcmp(serverslot_t *s1, serverslot_t *s2)
Definition: servers.c:769
PingSelected
static menuSound_t PingSelected(void)
Definition: servers.c:396