Quake II RTX doxygen  1.0 dev
game.c
Go to the documentation of this file.
1 /*
2 Copyright (C) 2003-2006 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 //
20 // mvd_game.c
21 //
22 
23 #include "client.h"
24 
25 static cvar_t *mvd_admin_password;
26 static cvar_t *mvd_part_filter;
27 static cvar_t *mvd_flood_msgs;
28 static cvar_t *mvd_flood_persecond;
29 static cvar_t *mvd_flood_waitdelay;
30 static cvar_t *mvd_flood_mute;
31 static cvar_t *mvd_filter_version;
32 static cvar_t *mvd_stats_score;
33 static cvar_t *mvd_stats_hack;
34 static cvar_t *mvd_freeze_hack;
35 static cvar_t *mvd_chase_prefix;
36 
38 
40 
41 static int mvd_numplayers;
42 
43 static void MVD_UpdateClient(mvd_client_t *client);
44 
45 /*
46 ==============================================================================
47 
48 LAYOUTS
49 
50 ==============================================================================
51 */
52 
53 // clients per screen page
54 #define PAGE_CLIENTS 16
55 
56 #define VER_OFS (272 - (int)(sizeof(VERSION_STRING) - 1) * CHAR_WIDTH)
57 
58 static void MVD_LayoutClients(mvd_client_t *client)
59 {
60  static const char header[] =
61  "xv 16 yv 0 string2 \" Name RTT Status\"";
62  char layout[MAX_STRING_CHARS];
63  char buffer[MAX_QPATH];
64  char *status1, *status2;
65  size_t len, total;
67  mvd_t *mvd = client->mvd;
68  int y, i, prestep, flags;
69 
70  // calculate prestep
71  if (client->layout_cursor < 0) {
72  client->layout_cursor = 0;
73  } else if (client->layout_cursor) {
74  total = List_Count(&mvd->clients);
75  if (client->layout_cursor > total / PAGE_CLIENTS) {
76  client->layout_cursor = total / PAGE_CLIENTS;
77  }
78  }
79 
80  prestep = client->layout_cursor * PAGE_CLIENTS;
81 
82  memcpy(layout, header, sizeof(header) - 1);
83  total = sizeof(header) - 1;
84 
85  y = 8;
86  i = 0;
88  if (++i < prestep) {
89  continue;
90  }
91  if (cl->cl->state < cs_spawned) {
92  continue;
93  }
94  if (cl->target && cl->target != mvd->dummy) {
95  status1 = "-> ";
96  status2 = cl->target->name;
97  } else {
98  status1 = "observing";
99  status2 = "";
100  }
101  len = Q_snprintf(buffer, sizeof(buffer),
102  "yv %d string \"%3d %-15.15s %3d %s%s\"",
103  y, i, cl->cl->name, cl->ping, status1, status2);
104  if (len >= sizeof(buffer)) {
105  continue;
106  }
107  if (total + len >= sizeof(layout)) {
108  break;
109  }
110  memcpy(layout + total, buffer, len);
111  total += len;
112 
113  if (y > 8 * PAGE_CLIENTS) {
114  break;
115  }
116  y += 8;
117  }
118 
119  layout[total] = 0;
120 
121  // the very first layout update is reliably delivered
122  flags = MSG_CLEAR | MSG_COMPRESS;
123  if (!client->layout_time) {
124  flags |= MSG_RELIABLE;
125  }
126 
127  // send the layout
129  MSG_WriteData(layout, total + 1);
130  SV_ClientAddMessage(client->cl, flags);
131 
132  client->layout_time = svs.realtime;
133 }
134 
136 {
137  mvd_client_t *c;
138  int count = 0;
139 
140  FOR_EACH_MVDCL(c, mvd) {
141  if (c->cl->state == cs_spawned) {
142  count++;
143  }
144  }
145  return count;
146 }
147 
148 static void MVD_LayoutChannels(mvd_client_t *client)
149 {
150  static const char header[] =
151  "xv 32 yv 8 picn inventory "
152  "xv %d yv 172 string2 " VERSION_STRING " "
153  "xv 0 yv 32 cstring \"\020Channel Chooser\021\""
154  "xv 64 yv 48 string2 \"Name Map S/P\""
155  "yv 56 string \"------------ ------- ---\" xv 56 ";
156  static const char nochans[] =
157  "yv 72 string \" No active channels.\""
158  "yv 80 string \" Please wait until players\""
159  "yv 88 string \" connect.\""
160  ;
161  static const char inactive[] =
162  "yv 72 string \" Traffic saving mode.\""
163  "yv 80 string \" Press any key to wake\""
164  "yv 88 string \" this server up.\""
165  ;
166  char layout[MAX_STRING_CHARS];
167  char buffer[MAX_QPATH];
168  mvd_t *mvd;
169  size_t len, total;
170  int cursor, y;
171 
172  total = Q_scnprintf(layout, sizeof(layout),
173  header, VER_OFS);
174 
175  // FIXME: improve this
176  cursor = List_Count(&mvd_channel_list);
177  if (cursor) {
178  if (client->layout_cursor < 0) {
179  client->layout_cursor = cursor - 1;
180  } else if (client->layout_cursor > cursor - 1) {
181  client->layout_cursor = 0;
182  }
183 
184  y = 64;
185  cursor = 0;
186  FOR_EACH_MVD(mvd) {
187  len = Q_snprintf(buffer, sizeof(buffer),
188  "yv %d string%s \"%c%-12.12s %-7.7s %d/%d\" ", y,
189  mvd == client->mvd ? "2" : "",
190  cursor == client->layout_cursor ? 0x8d : 0x20,
191  mvd->name, mvd->mapname,
192  MVD_CountClients(mvd), mvd->numplayers);
193  if (len >= sizeof(buffer)) {
194  continue;
195  }
196  if (total + len >= sizeof(layout)) {
197  break;
198  }
199  memcpy(layout + total, buffer, len);
200  total += len;
201  y += 8;
202  if (y > 164) {
203  break;
204  }
205 
206  cursor++;
207  }
208  } else {
209  client->layout_cursor = 0;
210  if (mvd_active) {
211  memcpy(layout + total, nochans, sizeof(nochans) - 1);
212  total += sizeof(nochans) - 1;
213  } else {
214  memcpy(layout + total, inactive, sizeof(inactive) - 1);
215  total += sizeof(inactive) - 1;
216  }
217  }
218 
219  layout[total] = 0;
220 
221  // send the layout
223  MSG_WriteData(layout, total + 1);
225 
226  client->layout_time = svs.realtime;
227 }
228 
229 #define MENU_ITEMS 10
230 #define YES "\xD9\xE5\xF3"
231 #define NO "\xCE\xEF"
232 #define DEFAULT "\xC4\xE5\xE6\xE1\xF5\xEC\xF4"
233 
234 static int clamp_menu_cursor(mvd_client_t *client)
235 {
236  if (client->layout_cursor < 0) {
237  client->layout_cursor = MENU_ITEMS - 1;
238  } else if (client->layout_cursor > MENU_ITEMS - 1) {
239  client->layout_cursor = 0;
240  }
241  return client->layout_cursor;
242 }
243 
244 static void MVD_LayoutMenu(mvd_client_t *client)
245 {
246  static const char format[] =
247  "xv 32 yv 8 picn inventory "
248  "xv 0 yv 32 cstring \"\020Main Menu\021\" xv 56 "
249  "yv 48 string2 \"%c%s in-eyes mode\""
250  "yv 56 string2 \"%cShow scoreboard\""
251  "yv 64 string2 \"%cShow spectators (%d)\""
252  "yv 72 string2 \"%cShow channels (%d)\""
253  "yv 80 string2 \"%cLeave this channel\""
254  "yv 96 string \"%cIgnore spectator chat: %s\""
255  "yv 104 string \"%cIgnore connect msgs: %s\""
256  "yv 112 string \"%cIgnore player chat: %s\""
257  "yv 120 string \"%cIgnore player FOV: %7s\""
258  "yv 128 string \" (use 'set uf %d u' in cfg)\""
259  "yv 144 string2 \"%cExit menu\""
260  "%s xv %d yv 172 string2 " VERSION_STRING;
261  char layout[MAX_STRING_CHARS];
262  char cur[MENU_ITEMS];
263  size_t total;
264 
265  memset(cur, 0x20, sizeof(cur));
266  cur[clamp_menu_cursor(client)] = 0x8d;
267 
268  total = Q_scnprintf(layout, sizeof(layout), format,
269  cur[0], client->target ? "Leave" : "Enter", cur[1],
270  cur[2], MVD_CountClients(client->mvd),
271  cur[3], List_Count(&mvd_channel_list), cur[4],
272  cur[5], (client->uf & UF_MUTE_OBSERVERS) ? YES : NO,
273  cur[6], (client->uf & UF_MUTE_MISC) ? YES : NO,
274  cur[7], (client->uf & UF_MUTE_PLAYERS) ? YES : NO,
275  cur[8], (client->uf & UF_LOCALFOV) ? YES :
276  (client->uf & UF_PLAYERFOV) ? NO " " : DEFAULT,
277  client->uf,
278  cur[9], client->mvd->state == MVD_WAITING ?
279  "xv 0 yv 160 cstring [BUFFERING]" : "",
280  VER_OFS);
281 
282  // send the layout
284  MSG_WriteData(layout, total + 1);
286 
287  client->layout_time = svs.realtime;
288 }
289 
290 static void MVD_LayoutScores(mvd_client_t *client)
291 {
292  mvd_t *mvd = client->mvd;
293  int flags = MSG_CLEAR | MSG_COMPRESS;
294  char *layout;
295 
296  if (client->layout_type == LAYOUT_OLDSCORES) {
297  layout = mvd->oldscores;
298  } else {
299  layout = mvd->layout;
300  }
301  if (!layout || !layout[0]) {
302  layout = "xv 100 yv 60 string \"<no scoreboard>\"";
303  }
304 
305  // end-of-match scoreboard is reliably delivered
306  if (!client->layout_time || mvd->intermission) {
307  flags |= MSG_RELIABLE;
308  }
309 
310  // send the layout
313  SV_ClientAddMessage(client->cl, flags);
314 
315  client->layout_time = svs.realtime;
316 }
317 
318 static void MVD_LayoutFollow(mvd_client_t *client)
319 {
320  mvd_t *mvd = client->mvd;
321  char *name = client->target ? client->target->name : "<no target>";
322  char layout[MAX_STRING_CHARS];
323  size_t total;
324 
325  total = Q_scnprintf(layout, sizeof(layout),
326  "%s string \"[%s] Chasing %s\"",
327  mvd_chase_prefix->string, mvd->name, name);
328 
329  // send the layout
331  MSG_WriteData(layout, total + 1);
333 
334  client->layout_time = svs.realtime;
335 }
336 
337 static void MVD_SetNewLayout(mvd_client_t *client, mvd_layout_t type)
338 {
339  // force an update
340  client->layout_type = type;
341  client->layout_time = 0;
342  client->layout_cursor = 0;
343 }
344 
345 static void MVD_SetDefaultLayout(mvd_client_t *client)
346 {
347  mvd_t *mvd = client->mvd;
348  mvd_layout_t type;
349 
350  if (mvd == &mvd_waitingRoom) {
351  type = LAYOUT_CHANNELS;
352  } else if (mvd->intermission) {
353  type = LAYOUT_SCORES;
354  } else if (client->target && !(mvd->flags & MVF_SINGLEPOV)) {
355  type = LAYOUT_FOLLOW;
356  } else {
357  type = LAYOUT_NONE;
358  }
359 
360  MVD_SetNewLayout(client, type);
361 }
362 
363 static void MVD_ToggleLayout(mvd_client_t *client, mvd_layout_t type)
364 {
365  if (client->layout_type == type) {
366  MVD_SetDefaultLayout(client);
367  } else {
368  MVD_SetNewLayout(client, type);
369  }
370 }
371 
372 static void MVD_SetFollowLayout(mvd_client_t *client)
373 {
374  if (!client->layout_type) {
375  MVD_SetDefaultLayout(client);
376  } else if (client->layout_type == LAYOUT_FOLLOW) {
377  client->layout_time = 0; // force an update
378  }
379 }
380 
381 // this is the only function that actually writes layouts
383 {
384  mvd_client_t *client;
385 
386  FOR_EACH_MVDCL(client, mvd) {
387  if (client->cl->state != cs_spawned) {
388  continue;
389  }
390  client->ps.stats[STAT_LAYOUTS] = client->layout_type ? 1 : 0;
391  switch (client->layout_type) {
392  case LAYOUT_FOLLOW:
393  if (!client->layout_time) {
394  MVD_LayoutFollow(client);
395  }
396  break;
397  case LAYOUT_OLDSCORES:
398  case LAYOUT_SCORES:
399  if (!client->layout_time) {
400  MVD_LayoutScores(client);
401  }
402  break;
403  case LAYOUT_MENU:
404  if (mvd->dirty || !client->layout_time) {
405  MVD_LayoutMenu(client);
406  }
407  break;
408  case LAYOUT_CLIENTS:
409  if (svs.realtime - client->layout_time > LAYOUT_MSEC) {
410  MVD_LayoutClients(client);
411  }
412  break;
413  case LAYOUT_CHANNELS:
414  if (mvd_dirty || !client->layout_time) {
415  MVD_LayoutChannels(client);
416  }
417  break;
418  default:
419  break;
420  }
421  }
422 
423  mvd->dirty = qfalse;
424 }
425 
426 
427 /*
428 ==============================================================================
429 
430 CHASE CAMERA
431 
432 ==============================================================================
433 */
434 
435 static void write_cs_list(mvd_client_t *client, mvd_cs_t *cs)
436 {
437  for (; cs; cs = cs->next) {
438  MSG_WriteByte(svc_configstring);
439  MSG_WriteShort(cs->index);
440  MSG_WriteString(cs->string);
442  }
443 }
444 
445 static void MVD_FollowStop(mvd_client_t *client)
446 {
447  mvd_t *mvd = client->mvd;
448  int i;
449 
450  client->ps.viewangles[ROLL] = 0;
451 
452  for (i = 0; i < 3; i++) {
453  client->ps.pmove.delta_angles[i] = ANGLE2SHORT(
454  client->ps.viewangles[i]) - client->lastcmd.angles[i];
455  }
456 
457  VectorClear(client->ps.kick_angles);
458  Vector4Clear(client->ps.blend);
459  client->ps.pmove.pm_flags = 0;
460  client->ps.pmove.pm_type = mvd->pm_type;
461  client->ps.rdflags = 0;
462  client->ps.gunindex = 0;
463  client->ps.fov = client->fov;
464 
465  // send delta configstrings
466  if (mvd->dummy)
468 
469  client->clientNum = mvd->clientNum;
470  client->oldtarget = client->target;
471  client->target = NULL;
472 
473  if (client->layout_type == LAYOUT_FOLLOW) {
474  MVD_SetDefaultLayout(client);
475  }
476 
477  MVD_UpdateClient(client);
478 }
479 
480 static void MVD_FollowStart(mvd_client_t *client, mvd_player_t *target)
481 {
482  if (client->target == target) {
483  return;
484  }
485 
486  client->oldtarget = client->target;
487  client->target = target;
488 
489  // send delta configstrings
490  write_cs_list(client, target->configstrings);
491 
492  SV_ClientPrintf(client->cl, PRINT_LOW, "[MVD] Chasing %s.\n", target->name);
493 
494  MVD_SetFollowLayout(client);
495  MVD_UpdateClient(client);
496 }
497 
498 static qboolean MVD_TestTarget(mvd_client_t *client, mvd_player_t *target)
499 {
500  mvd_t *mvd = client->mvd;
501 
502  if (!target)
503  return qfalse;
504 
505  if (!target->inuse)
506  return qfalse;
507 
508  if (target == mvd->dummy)
509  return qfalse;
510 
511  if (!client->chase_auto)
512  return qtrue;
513 
514  return Q_IsBitSet(client->chase_bitmap, target - mvd->players);
515 }
516 
518 {
519  mvd_t *mvd = client->mvd;
520  mvd_player_t *target;
521 
522  if (!mvd->players) {
523  return NULL;
524  }
525 
526  if (!from) {
527  from = mvd->players + mvd->maxclients - 1;
528  }
529 
530  target = from;
531  do {
532  if (target == mvd->players + mvd->maxclients - 1) {
533  target = mvd->players;
534  } else {
535  target++;
536  }
537  if (target == from) {
538  return NULL;
539  }
540  } while (!MVD_TestTarget(client, target));
541 
542  MVD_FollowStart(client, target);
543  return target;
544 }
545 
547 {
548  mvd_t *mvd = client->mvd;
549  mvd_player_t *target;
550 
551  if (!mvd->players) {
552  return NULL;
553  }
554 
555  if (!from) {
556  from = mvd->players;
557  }
558 
559  target = from;
560  do {
561  if (target == mvd->players) {
562  target = mvd->players + mvd->maxclients - 1;
563  } else {
564  target--;
565  }
566  if (target == from) {
567  return NULL;
568  }
569  } while (!MVD_TestTarget(client, target));
570 
571  MVD_FollowStart(client, target);
572  return target;
573 }
574 
576 {
577  int count[MAX_CLIENTS];
579  mvd_player_t *player, *target = NULL;
580  int i, maxcount = -1;
581 
582  memset(count, 0, sizeof(count));
583 
585  if (other->cl->state == cs_spawned && other->target) {
586  count[other->target - mvd->players]++;
587  }
588  }
589  for (i = 0, player = mvd->players; i < mvd->maxclients; i++, player++) {
590  if (player->inuse && player != mvd->dummy && maxcount < count[i]) {
591  maxcount = count[i];
592  target = player;
593  }
594  }
595  return target;
596 }
597 
598 static void MVD_UpdateTarget(mvd_client_t *client)
599 {
600  mvd_t *mvd = client->mvd;
601  mvd_player_t *target;
602  entity_state_t *ent;
603  int i;
604 
605  // find new target for effects auto chasecam
606  if (client->chase_mask && !mvd->intermission) {
607  for (i = 0; i < mvd->maxclients; i++) {
608  target = &mvd->players[i];
609  if (!target->inuse || target == mvd->dummy) {
610  continue;
611  }
612  ent = &mvd->edicts[i + 1].s;
613  if (ent->effects & client->chase_mask) {
614  MVD_FollowStart(client, target);
615  return;
616  }
617  }
618  }
619 
620  // check if current target is still active
621  target = client->target;
622  if (target) {
623  if (target->inuse) {
624  return;
625  }
626  if (!MVD_FollowNext(client, target)) {
627  MVD_FollowStop(client);
628  return;
629  }
630  }
631 
632  // find the next target for auto chasecam
633  target = client->oldtarget;
634  if (client->chase_auto) {
635  if (MVD_TestTarget(client, target)) {
636  MVD_FollowStart(client, target);
637  } else {
638  MVD_FollowNext(client, target);
639  }
640  return;
641  }
642 
643  // if the map has just started, try to return to the previous target
644  if (target && target->inuse && client->chase_wait) {
645  MVD_FollowStart(client, target);
646  }
647 }
648 
649 static void MVD_UpdateClient(mvd_client_t *client)
650 {
651  mvd_t *mvd = client->mvd;
652  mvd_player_t *target = client->target;
653  int i;
654 
655  if (!target) {
656  // copy stats of the dummy MVD observer
657  if (mvd->dummy) {
658  for (i = 0; i < MAX_STATS; i++) {
659  client->ps.stats[i] = mvd->dummy->ps.stats[i];
660  }
661  }
662  } else {
663  // copy entire player state
664  client->ps = target->ps;
665  if ((client->uf & UF_LOCALFOV) || (!(client->uf & UF_PLAYERFOV)
666  && client->ps.fov >= 90)) {
667  client->ps.fov = client->fov;
668  }
669  client->ps.pmove.pm_flags |= PMF_NO_PREDICTION;
670  client->ps.pmove.pm_type = PM_FREEZE;
671  client->clientNum = target - mvd->players;
672 
673  if (target != mvd->dummy) {
674  if (mvd_stats_hack->integer && mvd->dummy) {
675  // copy stats of the dummy MVD observer
676  for (i = 0; i < MAX_STATS; i++) {
677  if (mvd_stats_hack->integer & (1 << i)) {
678  client->ps.stats[i] = mvd->dummy->ps.stats[i];
679  }
680  }
681  }
682 
683  // chasing someone counts as activity
685  }
686  }
687 
688  // override score
689  switch (mvd_stats_score->integer) {
690  case 0:
691  client->ps.stats[STAT_FRAGS] = 0;
692  break;
693  case 1:
694  client->ps.stats[STAT_FRAGS] = mvd->id;
695  break;
696  }
697 }
698 
699 /*
700 ==============================================================================
701 
702 SPECTATOR COMMANDS
703 
704 ==============================================================================
705 */
706 
707 void MVD_BroadcastPrintf(mvd_t *mvd, int level, int mask, const char *fmt, ...)
708 {
709  va_list argptr;
710  char text[MAX_STRING_CHARS];
711  size_t len;
713  client_t *cl;
714 
715  va_start(argptr, fmt);
716  len = Q_vsnprintf(text, sizeof(text), fmt, argptr);
717  va_end(argptr);
718 
719  if (len >= sizeof(text)) {
720  Com_WPrintf("%s: overflow\n", __func__);
721  return;
722  }
723 
724  if (level == PRINT_CHAT && mvd_filter_version->integer) {
725  char *s;
726 
727  while ((s = strstr(text, "!version")) != NULL) {
728  s[6] = '0';
729  }
730  }
731 
732  MSG_WriteByte(svc_print);
734  MSG_WriteData(text, len + 1);
735 
737  cl = other->cl;
738  if (cl->state < cs_spawned) {
739  continue;
740  }
741  if (level < cl->messagelevel) {
742  continue;
743  }
744  if (other->uf & mask) {
745  continue;
746  }
748  }
749 
751 }
752 
754 {
755  cl->gamedir = mvd->gamedir;
756  cl->mapname = mvd->mapname;
757  cl->configstrings = (char *)mvd->configstrings;
758  cl->slot = mvd->clientNum;
759  cl->cm = &mvd->cm;
760  cl->pool = &mvd->pool;
761  cl->spawncount = mvd->servercount;
762  cl->maxclients = mvd->maxclients;
763 }
764 
766 {
767  client_t *cl = client->cl;
768 
769  List_Remove(&client->entry);
770  List_SeqAdd(&mvd->clients, &client->entry);
771  client->mvd = mvd;
772  client->begin_time = 0;
773  client->target = NULL;
774  client->oldtarget = NULL;
775  client->chase_mask = 0;
776  client->chase_auto = 0;
777  client->chase_wait = 0;
778  memset(client->chase_bitmap, 0, sizeof(client->chase_bitmap));
780 
781  // needs to reconnect
783  MSG_WriteString(va("changing map=%s; reconnect\n", mvd->mapname));
786 }
787 
788 static qboolean MVD_PartFilter(mvd_client_t *client)
789 {
790  unsigned i, delta, treshold;
791  float f = mvd_part_filter->value;
792 
793  if (!f) {
794  return qtrue; // show everyone
795  }
796  if (f < 0) {
797  return qfalse; // hide everyone
798  }
799  if (client->admin) {
800  return qtrue; // show admins
801  }
802  if (!client->floodHead) {
803  return qfalse; // not talked yet
804  }
805 
806  // take the most recent sample
807  i = (client->floodHead - 1) & FLOOD_MASK;
808  delta = svs.realtime - client->floodSamples[i];
809  treshold = f * 1000;
810 
811  return delta < treshold;
812 }
813 
815 {
816  if (mvd == client->mvd) {
817  SV_ClientPrintf(client->cl, PRINT_HIGH,
818  "[MVD] You are already %s.\n", mvd == &mvd_waitingRoom ?
819  "in the Waiting Room" : "on this channel");
820  return; // nothing to do
821  }
822  if (client->begin_time) {
823  if (svs.realtime - client->begin_time < 2000) {
824  SV_ClientPrintf(client->cl, PRINT_HIGH,
825  "[MVD] You may not switch channels too soon.\n");
826  return;
827  }
828  if (MVD_PartFilter(client)) {
829  MVD_BroadcastPrintf(client->mvd, PRINT_MEDIUM, UF_MUTE_MISC,
830  "[MVD] %s left the channel\n", client->cl->name);
831  }
832  }
833 
834  MVD_SwitchChannel(client, mvd);
835 }
836 
837 static void MVD_Admin_f(mvd_client_t *client)
838 {
839  char *s = mvd_admin_password->string;
840 
841  if (client->admin) {
842  client->admin = qfalse;
843  SV_ClientPrintf(client->cl, PRINT_HIGH, "[MVD] Lost admin status.\n");
844  return;
845  }
846 
847  if (!NET_IsLocalAddress(&client->cl->netchan->remote_address)) {
848  if (Cmd_Argc() < 2) {
849  SV_ClientPrintf(client->cl, PRINT_HIGH, "Usage: %s <password>\n", Cmd_Argv(0));
850  return;
851  }
852  if (!s[0] || strcmp(s, Cmd_Argv(1))) {
853  SV_ClientPrintf(client->cl, PRINT_HIGH, "[MVD] Invalid password.\n");
854  return;
855  }
856  }
857 
858  client->admin = qtrue;
859  SV_ClientPrintf(client->cl, PRINT_HIGH, "[MVD] Granted admin status.\n");
860 }
861 
862 static void MVD_Forward_f(mvd_client_t *client)
863 {
864  mvd_t *mvd = client->mvd;
865 
866  if (!client->admin) {
867  SV_ClientPrintf(client->cl, PRINT_HIGH,
868  "[MVD] You don't have admin status.\n");
869  return;
870  }
871 
872  if (!mvd->forward_cmd) {
873  SV_ClientPrintf(client->cl, PRINT_HIGH,
874  "[MVD] This channel does not support command forwarding.\n");
875  return;
876  }
877 
878  mvd->forward_cmd(client);
879 }
880 
881 static void MVD_Say_f(mvd_client_t *client, int argnum)
882 {
883  mvd_t *mvd = client->mvd;
884  unsigned delta, delay = mvd_flood_waitdelay->value * 1000;
885  unsigned treshold = mvd_flood_persecond->value * 1000;
886  char text[150], hightext[150];
888  client_t *cl;
889  unsigned i, j;
890 
891  if (mvd_flood_mute->integer && !client->admin) {
892  SV_ClientPrintf(client->cl, PRINT_HIGH,
893  "[MVD] Spectators may not talk on this server.\n");
894  return;
895  }
896  if (client->uf & UF_MUTE_OBSERVERS) {
897  SV_ClientPrintf(client->cl, PRINT_HIGH,
898  "[MVD] Please turn off ignore mode first.\n");
899  return;
900  }
901 
902  if (client->floodTime) {
903  delta = svs.realtime - client->floodTime;
904  if (delta < delay) {
905  SV_ClientPrintf(client->cl, PRINT_HIGH,
906  "[MVD] You can't talk for %u more seconds.\n",
907  (delay - delta) / 1000);
908  return;
909  }
910  }
911 
913  if (client->floodHead >= j) {
914  i = (client->floodHead - j) & FLOOD_MASK;
915  delta = svs.realtime - client->floodSamples[i];
916  if (delta < treshold) {
917  SV_ClientPrintf(client->cl, PRINT_HIGH,
918  "[MVD] You can't talk for %u seconds.\n", delay / 1000);
919  client->floodTime = svs.realtime;
920  return;
921  }
922  }
923 
924  client->floodSamples[client->floodHead & FLOOD_MASK] = svs.realtime;
925  client->floodHead++;
926 
927  Q_snprintf(text, sizeof(text), "[MVD] %s: %s",
928  client->cl->name, Cmd_ArgsFrom(argnum));
929 
930  // color text for legacy clients
931  for (i = 0; text[i]; i++)
932  hightext[i] = text[i] | 128;
933  hightext[i] = 0;
934 
936  cl = other->cl;
937  if (cl->state < cs_spawned) {
938  continue;
939  }
940  if (!client->admin && (other->uf & UF_MUTE_OBSERVERS)) {
941  continue;
942  }
943 
944  if (cl->protocol == PROTOCOL_VERSION_Q2PRO &&
945  cl->version >= PROTOCOL_VERSION_Q2PRO_SERVER_STATE) {
946  SV_ClientPrintf(cl, PRINT_CHAT, "%s\n", text);
947  } else {
948  SV_ClientPrintf(cl, PRINT_HIGH, "%s\n", hightext);
949  }
950  }
951 }
952 
953 static void MVD_Observe_f(mvd_client_t *client)
954 {
955  mvd_t *mvd = client->mvd;
956 
957  if (!mvd->players) {
958  SV_ClientPrintf(client->cl, PRINT_HIGH,
959  "[MVD] Please enter a channel first.\n");
960  return;
961  }
962 
963  client->chase_mask = 0;
964  client->chase_auto = qfalse;
965  client->chase_wait = qfalse;
966 
967  if (mvd->intermission) {
968  return;
969  }
970 
971  if (client->target) {
972  MVD_FollowStop(client);
973  return;
974  }
975 
976  if (client->oldtarget && client->oldtarget->inuse) {
977  MVD_FollowStart(client, client->oldtarget);
978  return;
979  }
980 
981  if (!MVD_FollowNext(client, NULL)) {
982  SV_ClientPrintf(client->cl, PRINT_MEDIUM, "[MVD] No players to chase.\n");
983  }
984 }
985 
986 static mvd_player_t *MVD_SetPlayer(mvd_client_t *client, const char *s)
987 {
988  mvd_t *mvd = client->mvd;
989  mvd_player_t *player, *match;
990  int i, count;
991 
992  // numeric values are just slot numbers
993  if (COM_IsUint(s)) {
994  i = atoi(s);
995  if (i < 0 || i >= mvd->maxclients) {
996  SV_ClientPrintf(client->cl, PRINT_HIGH,
997  "[MVD] Player slot number %d is invalid.\n", i);
998  return NULL;
999  }
1000 
1001  player = &mvd->players[i];
1002  if (!player->inuse || player == mvd->dummy) {
1003  SV_ClientPrintf(client->cl, PRINT_HIGH,
1004  "[MVD] Player slot number %d is not active.\n", i);
1005  return NULL;
1006  }
1007 
1008  return player;
1009  }
1010 
1011  // check for a name match
1012  match = NULL;
1013  count = 0;
1014  for (i = 0, player = mvd->players; i < mvd->maxclients; i++, player++) {
1015  if (!player->inuse || !player->name[0] || player == mvd->dummy) {
1016  continue;
1017  }
1018  if (!Q_stricmp(player->name, s)) {
1019  return player; // exact match
1020  }
1021  if (Q_stristr(player->name, s)) {
1022  match = player; // partial match
1023  count++;
1024  }
1025  }
1026 
1027  if (!match) {
1028  SV_ClientPrintf(client->cl, PRINT_HIGH,
1029  "[MVD] No players matching '%s' found.\n", s);
1030  return NULL;
1031  }
1032 
1033  if (count > 1) {
1034  SV_ClientPrintf(client->cl, PRINT_HIGH,
1035  "[MVD] '%s' matches multiple players.\n", s);
1036  return NULL;
1037  }
1038 
1039  return match;
1040 }
1041 
1042 static void MVD_Follow_f(mvd_client_t *client)
1043 {
1044  mvd_t *mvd = client->mvd;
1045  mvd_player_t *player;
1046  char *s;
1047  int mask;
1048 
1049  if (!mvd->players) {
1050  SV_ClientPrintf(client->cl, PRINT_HIGH,
1051  "[MVD] Please enter a channel first.\n");
1052  return;
1053  }
1054 
1055  if (mvd->intermission) {
1056  return;
1057  }
1058 
1059  if (Cmd_Argc() < 2) {
1060  MVD_Observe_f(client);
1061  return;
1062  }
1063 
1064  s = Cmd_Argv(1);
1065  if (*s == '!') {
1066  s++;
1067  switch (*s) {
1068  case 'q':
1069  mask = EF_QUAD;
1070  break;
1071  case 'i':
1072  mask = EF_PENT;
1073  break;
1074  case 'r':
1075  mask = EF_FLAG1;
1076  break;
1077  case 'b':
1078  mask = EF_FLAG2;
1079  break;
1080  case '!':
1081  goto match;
1082  case 'p':
1083  if (client->oldtarget) {
1084  if (client->oldtarget->inuse) {
1085  MVD_FollowStart(client, client->oldtarget);
1086  } else {
1087  SV_ClientPrintf(client->cl, PRINT_HIGH,
1088  "[MVD] Previous chase target is not active.\n");
1089  }
1090  } else {
1091  SV_ClientPrintf(client->cl, PRINT_HIGH,
1092  "[MVD] You have no previous chase target.\n");
1093  }
1094  return;
1095  default:
1096  SV_ClientPrintf(client->cl, PRINT_HIGH,
1097  "[MVD] Unknown chase target '%s'. Valid targets are: "
1098  "q[uad]/i[nvulner]/r[ed_flag]/b[lue_flag]/p[revious_target].\n", s);
1099  return;
1100  }
1101  SV_ClientPrintf(client->cl, PRINT_MEDIUM,
1102  "[MVD] Chasing players with '%s' powerup.\n", s);
1103  client->chase_mask = mask;
1104  client->chase_auto = qfalse;
1105  client->chase_wait = qfalse;
1106  return;
1107  }
1108 
1109 match:
1110  player = MVD_SetPlayer(client, s);
1111  if (player) {
1112  MVD_FollowStart(client, player);
1113  client->chase_mask = 0;
1114  client->chase_wait = qfalse;
1115  }
1116 }
1117 
1118 static qboolean count_chase_bits(mvd_client_t *client)
1119 {
1120  mvd_t *mvd = client->mvd;
1121  int i, j, count = 0;
1122 
1123  for (i = 0; i < (mvd->maxclients + CHAR_BIT - 1) / CHAR_BIT; i++)
1124  if (client->chase_bitmap[i])
1125  for (j = 0; j < 8; j++)
1126  if (client->chase_bitmap[i] & (1 << j))
1127  count++;
1128 
1129  return count;
1130 }
1131 
1132 static void MVD_AutoFollow_f(mvd_client_t *client)
1133 {
1134  mvd_t *mvd = client->mvd;
1135  mvd_player_t *player;
1136  char *s, *p;
1137  int i, j, argc;
1138 
1139  if (!mvd->players) {
1140  SV_ClientPrintf(client->cl, PRINT_HIGH,
1141  "[MVD] Please enter a channel first.\n");
1142  return;
1143  }
1144 
1145  if (mvd->intermission)
1146  return;
1147 
1148  argc = Cmd_Argc();
1149  if (argc < 2) {
1150  i = count_chase_bits(client);
1151  SV_ClientPrintf(client->cl, PRINT_HIGH,
1152  "[MVD] Auto chasing is %s (%d target%s).\n",
1153  client->chase_auto ? "ON" : "OFF", i, i == 1 ? "" : "s");
1154  return;
1155  }
1156 
1157  s = Cmd_Argv(1);
1158  if (!strcmp(s, "add") || !strcmp(s, "rm") || !strcmp(s, "del")) {
1159  if (argc < 3) {
1160  SV_ClientPrintf(client->cl, PRINT_HIGH,
1161  "Usage: %s %s <wildcard> [...]\n",
1162  Cmd_Argv(0), Cmd_Argv(1));
1163  return;
1164  }
1165 
1166  for (i = 2; i < argc; i++) {
1167  p = Cmd_Argv(i);
1168  for (j = 0; j < mvd->maxclients; j++) {
1169  player = &mvd->players[j];
1170  if (!player->name[0] || player == mvd->dummy)
1171  continue;
1172 
1173  if (!Com_WildCmpEx(p, player->name, 0, qtrue))
1174  continue;
1175 
1176  if (*s == 'a')
1177  Q_SetBit(client->chase_bitmap, j);
1178  else
1179  Q_ClearBit(client->chase_bitmap, j);
1180  }
1181  }
1182 
1183  if (*s == 'a') {
1184  MVD_UpdateTarget(client);
1185  return;
1186  }
1187 
1188  if (!count_chase_bits(client))
1189  client->chase_auto = qfalse;
1190  return;
1191  }
1192 
1193  if (!strcmp(s, "set")) {
1194  memset(client->chase_bitmap, 0, sizeof(client->chase_bitmap));
1195 
1196  for (i = 2; i < argc; i++) {
1197  j = atoi(Cmd_Argv(i));
1198  if (j >= 0 && j < mvd->maxclients)
1199  Q_SetBit(client->chase_bitmap, j);
1200  }
1201 
1202  if (!count_chase_bits(client))
1203  client->chase_auto = qfalse;
1204  return;
1205  }
1206 
1207  if (!strcmp(s, "list")) {
1208  char buffer[1000];
1209  size_t total, len;
1210 
1211  total = 0;
1212  for (i = 0; i < mvd->maxclients; i++) {
1213  if (!Q_IsBitSet(client->chase_bitmap, i))
1214  continue;
1215 
1216  player = &mvd->players[i];
1217  if (player == mvd->dummy)
1218  continue;
1219 
1220  len = strlen(player->name);
1221  if (len == 0)
1222  continue;
1223 
1224  if (total + len + 2 >= sizeof(buffer))
1225  break;
1226 
1227  if (total) {
1228  buffer[total + 0] = ',';
1229  buffer[total + 1] = ' ';
1230  total += 2;
1231  }
1232 
1233  memcpy(buffer + total, player->name, len);
1234  total += len;
1235  }
1236  buffer[total] = 0;
1237 
1238  if (total)
1239  SV_ClientPrintf(client->cl, PRINT_HIGH,
1240  "[MVD] Auto chasing %s\n", buffer);
1241  else
1242  SV_ClientPrintf(client->cl, PRINT_HIGH,
1243  "[MVD] Auto chase list is empty.\n");
1244  return;
1245  }
1246 
1247  if (!strcmp(s, "on")) {
1248  if (!count_chase_bits(client)) {
1249  SV_ClientPrintf(client->cl, PRINT_HIGH,
1250  "[MVD] Please add auto chase targets first.\n");
1251  return;
1252  }
1253 
1254  client->chase_mask = 0;
1255  client->chase_auto = qtrue;
1256  client->chase_wait = qfalse;
1257  if (!MVD_TestTarget(client, client->target))
1258  MVD_FollowNext(client, client->target);
1259  return;
1260  }
1261 
1262  if (!strcmp(s, "off")) {
1263  client->chase_auto = qfalse;
1264  return;
1265  }
1266 
1267  SV_ClientPrintf(client->cl, PRINT_HIGH,
1268  "[MVD] Usage: %s <add|rm|set|list|on|off> [...]\n",
1269  Cmd_Argv(0));
1270 }
1271 
1272 static void MVD_Invuse_f(mvd_client_t *client)
1273 {
1274  mvd_t *mvd;
1275  int uf = client->uf;
1276 
1277  if (client->layout_type == LAYOUT_MENU) {
1278  switch (clamp_menu_cursor(client)) {
1279  case 0:
1280  MVD_SetDefaultLayout(client);
1281  MVD_Observe_f(client);
1282  return;
1283  case 1:
1285  break;
1286  case 2:
1288  break;
1289  case 3:
1291  break;
1292  case 4:
1294  return;
1295  case 5:
1296  client->uf ^= UF_MUTE_OBSERVERS;
1297  break;
1298  case 6:
1299  client->uf ^= UF_MUTE_MISC;
1300  break;
1301  case 7:
1302  client->uf ^= UF_MUTE_PLAYERS;
1303  break;
1304  case 8:
1305  client->uf &= ~(UF_LOCALFOV | UF_PLAYERFOV);
1306  if (uf & UF_LOCALFOV)
1307  client->uf |= UF_PLAYERFOV;
1308  else if (!(uf & UF_PLAYERFOV))
1309  client->uf |= UF_LOCALFOV;
1310  break;
1311  case 9:
1312  MVD_SetDefaultLayout(client);
1313  break;
1314  }
1315  if (uf != client->uf) {
1316  SV_ClientCommand(client->cl, "set uf %d u\n", client->uf);
1317  client->layout_time = 0; // force an update
1318  }
1319  return;
1320  }
1321 
1322  if (client->layout_type == LAYOUT_CHANNELS) {
1323  mvd = LIST_INDEX(mvd_t, client->layout_cursor, &mvd_channel_list, entry);
1324  if (mvd) {
1325  MVD_TrySwitchChannel(client, mvd);
1326  }
1327  return;
1328  }
1329 }
1330 
1331 static void MVD_Join_f(mvd_client_t *client)
1332 {
1333  mvd_t *mvd;
1334 
1336  mvd = MVD_SetChannel(1);
1337  Com_EndRedirect();
1338 
1339  if (!mvd) {
1340  return;
1341  }
1342  if (mvd->state < MVD_WAITING) {
1343  SV_ClientPrintf(client->cl, PRINT_HIGH,
1344  "[MVD] This channel is not ready yet.\n");
1345  return;
1346  }
1347 
1348  MVD_TrySwitchChannel(client, mvd);
1349 }
1350 
1352 {
1353  mvd_player_t *player;
1354  char buffer[MAX_QPATH];
1355  size_t len, total;
1356  int i;
1357 
1358  total = 0;
1359  for (i = 0; i < mvd->maxclients; i++) {
1360  player = &mvd->players[i];
1361  if (!player->inuse || player == mvd->dummy) {
1362  continue;
1363  }
1364  len = strlen(player->name);
1365  if (len == 0) {
1366  continue;
1367  }
1368  if (total + len + 2 >= sizeof(buffer)) {
1369  break;
1370  }
1371  if (total) {
1372  buffer[total + 0] = ',';
1373  buffer[total + 1] = ' ';
1374  total += 2;
1375  }
1376  memcpy(buffer + total, player->name, len);
1377  total += len;
1378  }
1379  buffer[total] = 0;
1380 
1381  SV_ClientPrintf(cl, PRINT_HIGH,
1382  "%2d %-12.12s %-8.8s %3d %3d %s\n", mvd->id,
1383  mvd->name, mvd->mapname,
1384  List_Count(&mvd->clients),
1385  mvd->numplayers, buffer);
1386 }
1387 
1388 static void mvd_channel_list_f(mvd_client_t *client)
1389 {
1390  mvd_t *mvd;
1391 
1392  SV_ClientPrintf(client->cl, PRINT_HIGH,
1393  "id name map spc plr who is playing\n"
1394  "-- ------------ -------- --- --- --------------\n");
1395 
1396  FOR_EACH_MVD(mvd) {
1397  print_channel(client->cl, mvd);
1398  }
1399 }
1400 
1401 static void MVD_Clients_f(mvd_client_t *client)
1402 {
1403  // TODO: dump them in console
1404  client->layout_type = LAYOUT_CLIENTS;
1405  client->layout_time = 0;
1406  client->layout_cursor = 0;
1407 }
1408 
1409 static void MVD_Commands_f(mvd_client_t *client)
1410 {
1411  SV_ClientPrintf(client->cl, PRINT_HIGH,
1412  "chase [player_id] toggle chasecam mode\n"
1413  "autochase <...> control automatic chasecam\n"
1414  "observe toggle observer mode\n"
1415  "menu show main menu\n"
1416  "score show scoreboard\n"
1417  "oldscore show previous scoreboard\n"
1418  "channels list active channels\n"
1419  "join [channel_id] join specified channel\n"
1420  "leave go to the Waiting Room\n"
1421  );
1422 }
1423 
1424 static void MVD_GameClientCommand(edict_t *ent)
1425 {
1426  mvd_client_t *client = EDICT_MVDCL(ent);
1427  char *cmd;
1428 
1429  if (client->cl->state < cs_spawned) {
1430  return;
1431  }
1432 
1433  cmd = Cmd_Argv(0);
1434  if (!*cmd) {
1435  return;
1436  }
1437 
1438  // don't timeout
1440 
1441  if (!strcmp(cmd, "!mvdadmin")) {
1442  MVD_Admin_f(client);
1443  return;
1444  }
1445  if (!strcmp(cmd, "fwd")) {
1446  MVD_Forward_f(client);
1447  return;
1448  }
1449  if (!strcmp(cmd, "say") || !strcmp(cmd, "say_team")) {
1450  MVD_Say_f(client, 1);
1451  return;
1452  }
1453  if (!strcmp(cmd, "follow") || !strcmp(cmd, "chase")) {
1454  MVD_Follow_f(client);
1455  return;
1456  }
1457  if (!strcmp(cmd, "autofollow") || !strcmp(cmd, "autochase")) {
1458  MVD_AutoFollow_f(client);
1459  return;
1460  }
1461  if (!strcmp(cmd, "observe") || !strcmp(cmd, "spectate") ||
1462  !strcmp(cmd, "observer") || !strcmp(cmd, "spectator") ||
1463  !strcmp(cmd, "obs") || !strcmp(cmd, "spec")) {
1464  MVD_Observe_f(client);
1465  return;
1466  }
1467  if (!strcmp(cmd, "inven") || !strcmp(cmd, "menu")) {
1468  MVD_ToggleLayout(client, LAYOUT_MENU);
1469  return;
1470  }
1471  if (!strcmp(cmd, "invnext")) {
1472  if (client->layout_type >= LAYOUT_MENU) {
1473  client->layout_cursor++;
1474  client->layout_time = 0;
1475  } else if (!client->mvd->intermission) {
1476  MVD_FollowNext(client, client->target);
1477  client->chase_mask = 0;
1478  }
1479  return;
1480  }
1481  if (!strcmp(cmd, "invprev")) {
1482  if (client->layout_type >= LAYOUT_MENU) {
1483  client->layout_cursor--;
1484  client->layout_time = 0;
1485  } else if (!client->mvd->intermission) {
1486  MVD_FollowPrev(client, client->target);
1487  client->chase_mask = 0;
1488  }
1489  return;
1490  }
1491  if (!strcmp(cmd, "invuse")) {
1492  MVD_Invuse_f(client);
1493  return;
1494  }
1495  if (!strcmp(cmd, "help") || !strcmp(cmd, "score")) {
1497  return;
1498  }
1499  if (!strcmp(cmd, "oldscore") || !strcmp(cmd, "oldscores") ||
1500  !strcmp(cmd, "lastscore") || !strcmp(cmd, "lastscores")) {
1502  return;
1503  }
1504  if (!strcmp(cmd, "putaway")) {
1505  MVD_SetDefaultLayout(client);
1506  return;
1507  }
1508  if (!strcmp(cmd, "channels")) {
1509  mvd_channel_list_f(client);
1510  return;
1511  }
1512  if (!strcmp(cmd, "clients") || !strcmp(cmd, "players")) {
1513  MVD_Clients_f(client);
1514  return;
1515  }
1516  if (!strcmp(cmd, "join")) {
1517  MVD_Join_f(client);
1518  return;
1519  }
1520  if (!strcmp(cmd, "leave")) {
1522  return;
1523  }
1524  if (!strcmp(cmd, "commands")) {
1525  MVD_Commands_f(client);
1526  return;
1527  }
1528 
1529  MVD_Say_f(client, 0);
1530 }
1531 
1532 /*
1533 ==============================================================================
1534 
1535 CONFIGSTRING MANAGEMENT
1536 
1537 ==============================================================================
1538 */
1539 
1541 {
1542  mvd_cs_t *cs, *next;
1543 
1544  for (cs = player->configstrings; cs; cs = next) {
1545  next = cs->next;
1546  Z_Free(cs);
1547  }
1548  player->configstrings = NULL;
1549 }
1550 
1551 static void reset_unicast_strings(mvd_t *mvd, int index)
1552 {
1553  mvd_cs_t *cs, **next_p;
1554  mvd_player_t *player;
1555  int i;
1556 
1557  for (i = 0; i < mvd->maxclients; i++) {
1558  player = &mvd->players[i];
1559  next_p = &player->configstrings;
1560  for (cs = player->configstrings; cs; cs = cs->next) {
1561  if (cs->index == index) {
1562  Com_DPrintf("%s: reset %d on %d\n", __func__, index, i);
1563  *next_p = cs->next;
1564  Z_Free(cs);
1565  break;
1566  }
1567  next_p = &cs->next;
1568  }
1569  }
1570 }
1571 
1572 static void set_player_name(mvd_t *mvd, int index)
1573 {
1574  mvd_player_t *player;
1575  char *string, *p;
1576 
1577  string = mvd->configstrings[CS_PLAYERSKINS + index];
1578  player = &mvd->players[index];
1579  Q_strlcpy(player->name, string, sizeof(player->name));
1580  p = strchr(player->name, '\\');
1581  if (p) {
1582  *p = 0;
1583  }
1584 }
1585 
1586 static void update_player_name(mvd_t *mvd, int index)
1587 {
1588  mvd_client_t *client;
1589 
1590  // parse player name
1591  set_player_name(mvd, index);
1592 
1593  // update layouts
1594  FOR_EACH_MVDCL(client, mvd) {
1595  if (client->cl->state < cs_spawned) {
1596  continue;
1597  }
1598  if (client->layout_type != LAYOUT_FOLLOW) {
1599  continue;
1600  }
1601  if (client->target == mvd->players + index) {
1602  client->layout_time = 0;
1603  }
1604  }
1605 }
1606 
1608 {
1609  int i;
1610 
1611  for (i = 0; i < mvd->maxclients; i++) {
1612  set_player_name(mvd, i);
1613  }
1614 }
1615 
1617 {
1618  char *s = mvd->configstrings[index];
1619  mvd_client_t *client;
1620 
1621  if (index >= CS_PLAYERSKINS && index < CS_PLAYERSKINS + mvd->maxclients) {
1622  // update player name
1623  update_player_name(mvd, index - CS_PLAYERSKINS);
1624  } else if (index >= CS_GENERAL) {
1625  // reset unicast versions of this string
1626  reset_unicast_strings(mvd, index);
1627  }
1628 
1629  MSG_WriteByte(svc_configstring);
1630  MSG_WriteShort(index);
1631  MSG_WriteString(s);
1632 
1633  // broadcast configstring change
1634  FOR_EACH_MVDCL(client, mvd) {
1635  if (client->cl->state < cs_primed) {
1636  continue;
1637  }
1639  }
1640 
1641  SZ_Clear(&msg_write);
1642 }
1643 
1644 /*
1645 ==============================================================================
1646 
1647 MISC GAME FUNCTIONS
1648 
1649 ==============================================================================
1650 */
1651 
1652 void MVD_LinkEdict(mvd_t *mvd, edict_t *ent)
1653 {
1654  int index;
1655  mmodel_t *cm;
1656  bsp_t *cache = mvd->cm.cache;
1657 
1658  if (!cache) {
1659  return;
1660  }
1661 
1662  if (ent->s.solid == PACKED_BSP) {
1663  index = ent->s.modelindex;
1664  if (index < 1 || index > cache->nummodels) {
1665  Com_WPrintf("%s: entity %d: bad inline model index: %d\n",
1666  __func__, ent->s.number, index);
1667  return;
1668  }
1669  cm = &cache->models[index - 1];
1670  VectorCopy(cm->mins, ent->mins);
1671  VectorCopy(cm->maxs, ent->maxs);
1672  ent->solid = SOLID_BSP;
1673  } else if (ent->s.solid) {
1674  MSG_UnpackSolid16(ent->s.solid, ent->mins, ent->maxs);
1675  ent->solid = SOLID_BBOX;
1676  } else {
1677  VectorClear(ent->mins);
1678  VectorClear(ent->maxs);
1679  ent->solid = SOLID_NOT;
1680  }
1681 
1682  SV_LinkEdict(&mvd->cm, ent);
1683 }
1684 
1686 {
1687  int index = client - svs.client_pool;
1688  mvd_client_t *cl = &mvd_clients[index];
1689 
1690  List_Remove(&cl->entry);
1691 
1692  memset(cl, 0, sizeof(*cl));
1693  cl->cl = client;
1694 }
1695 
1696 static void MVD_GameInit(void)
1697 {
1699  edict_t *edicts;
1700  cvar_t *mvd_default_map;
1701  char buffer[MAX_QPATH];
1702  unsigned checksum;
1703  bsp_t *bsp;
1704  int i;
1705  qerror_t ret;
1706 
1707  Com_Printf("----- MVD_GameInit -----\n");
1708 
1709  mvd_admin_password = Cvar_Get("mvd_admin_password", "", CVAR_PRIVATE);
1710  mvd_part_filter = Cvar_Get("mvd_part_filter", "0", 0);
1711  mvd_flood_msgs = Cvar_Get("flood_msgs", "4", 0);
1712  mvd_flood_persecond = Cvar_Get("flood_persecond", "4", 0); // FIXME: rename this
1713  mvd_flood_waitdelay = Cvar_Get("flood_waitdelay", "10", 0);
1714  mvd_flood_mute = Cvar_Get("flood_mute", "0", 0);
1715  mvd_filter_version = Cvar_Get("mvd_filter_version", "0", 0);
1716  mvd_default_map = Cvar_Get("mvd_default_map", "q2dm1", CVAR_LATCH);
1717  mvd_stats_score = Cvar_Get("mvd_stats_score", "0", 0);
1718  mvd_stats_hack = Cvar_Get("mvd_stats_hack", "0", 0);
1719  mvd_freeze_hack = Cvar_Get("mvd_freeze_hack", "1", 0);
1720  mvd_chase_prefix = Cvar_Get("mvd_chase_prefix", "xv 0 yb -64", 0);
1721  Cvar_Set("g_features", va("%d", MVD_FEATURES));
1722 
1723  Z_TagReserve((sizeof(edict_t) +
1724  sizeof(mvd_client_t)) * sv_maxclients->integer +
1725  sizeof(edict_t), TAG_MVD);
1727  sv_maxclients->integer);
1728  edicts = Z_ReservedAllocz(sizeof(edict_t) *
1729  (sv_maxclients->integer + 1));
1730 
1731  for (i = 0; i < sv_maxclients->integer; i++) {
1732  mvd_clients[i].cl = &svs.client_pool[i];
1733  edicts[i + 1].client = (gclient_t *)&mvd_clients[i];
1734  }
1735 
1736  mvd_ge.edicts = edicts;
1737  mvd_ge.edict_size = sizeof(edict_t);
1738  mvd_ge.num_edicts = sv_maxclients->integer + 1;
1739  mvd_ge.max_edicts = sv_maxclients->integer + 1;
1740 
1741  Q_snprintf(buffer, sizeof(buffer),
1742  "maps/%s.bsp", mvd_default_map->string);
1743 
1744  ret = BSP_Load(buffer, &bsp);
1745  if (!bsp) {
1746  Com_EPrintf("Couldn't load %s for the Waiting Room: %s\n",
1747  buffer, Q_ErrorString(ret));
1748  Cvar_Reset(mvd_default_map);
1749  strcpy(buffer, "maps/q2dm1.bsp");
1750  checksum = 80717714;
1751  VectorSet(mvd->spawnOrigin, 984, 192, 784);
1752  VectorSet(mvd->spawnAngles, 25, 72, 0);
1753  } else {
1754  // get the spectator spawn point
1755  MVD_ParseEntityString(mvd, bsp->entitystring);
1756  checksum = bsp->checksum;
1757  BSP_Free(bsp);
1758  }
1759 
1760  strcpy(mvd->name, "Waiting Room");
1761  Cvar_VariableStringBuffer("game", mvd->gamedir, sizeof(mvd->gamedir));
1762  Q_strlcpy(mvd->mapname, mvd_default_map->string, sizeof(mvd->mapname));
1763  List_Init(&mvd->clients);
1764 
1765  strcpy(mvd->configstrings[CS_NAME], "Waiting Room");
1766  strcpy(mvd->configstrings[CS_SKY], "unit1_");
1767  strcpy(mvd->configstrings[CS_MAXCLIENTS], "8");
1768  sprintf(mvd->configstrings[CS_MAPCHECKSUM], "%d", checksum);
1769  strcpy(mvd->configstrings[CS_MODELS + 1], buffer);
1770  strcpy(mvd->configstrings[CS_LIGHTS], "m");
1771 
1772  mvd->dummy = &mvd_dummy;
1773  mvd->pm_type = PM_FREEZE;
1774  mvd->servercount = sv.spawncount;
1775 
1776  // set serverinfo variables
1777  SV_InfoSet("mapname", mvd->mapname);
1778  //SV_InfoSet("gamedir", "gtv");
1779  SV_InfoSet("gamename", "gtv");
1780  SV_InfoSet("gamedate", __DATE__);
1781  MVD_InfoSet("mvd_channels", "0");
1782  MVD_InfoSet("mvd_players", "0");
1783  mvd_numplayers = 0;
1784 }
1785 
1786 static void MVD_GameShutdown(void)
1787 {
1788  Com_Printf("----- MVD_GameShutdown -----\n");
1789 
1790  MVD_Shutdown();
1791 
1792  mvd_ge.edicts = NULL;
1793  mvd_ge.edict_size = 0;
1794  mvd_ge.num_edicts = 0;
1795  mvd_ge.max_edicts = 0;
1796 
1797  Cvar_Set("g_features", "0");
1798 }
1799 
1800 static void MVD_GameSpawnEntities(const char *mapname, const char *entstring, const char *spawnpoint)
1801 {
1802 }
1803 static void MVD_GameWriteGame(const char *filename, qboolean autosave)
1804 {
1805 }
1806 static void MVD_GameReadGame(const char *filename)
1807 {
1808 }
1809 static void MVD_GameWriteLevel(const char *filename)
1810 {
1811 }
1812 static void MVD_GameReadLevel(const char *filename)
1813 {
1814 }
1815 
1816 static qboolean MVD_GameClientConnect(edict_t *ent, char *userinfo)
1817 {
1818  mvd_client_t *client = EDICT_MVDCL(ent);
1819  mvd_t *mvd;
1820 
1821  // if there is exactly one active channel, assign them to it,
1822  // otherwise, assign to Waiting Room
1823  if (LIST_SINGLE(&mvd_channel_list)) {
1824  mvd = LIST_FIRST(mvd_t, &mvd_channel_list, entry);
1825  } else {
1826  mvd = &mvd_waitingRoom;
1827  }
1828  List_SeqAdd(&mvd->clients, &client->entry);
1829  client->mvd = mvd;
1830 
1831  // override server state
1832  MVD_SetServerState(client->cl, mvd);
1833 
1834  // don't timeout
1836 
1837  return qtrue;
1838 }
1839 
1840 static void MVD_GameClientBegin(edict_t *ent)
1841 {
1842  mvd_client_t *client = EDICT_MVDCL(ent);
1843  mvd_t *mvd = client->mvd;
1844  mvd_player_t *target;
1845 
1846  client->floodTime = 0;
1847  client->floodHead = 0;
1848  memset(&client->lastcmd, 0, sizeof(client->lastcmd));
1849  memset(&client->ps, 0, sizeof(client->ps));
1850  client->jump_held = 0;
1851  client->layout_type = LAYOUT_NONE;
1852  client->layout_time = 0;
1853  client->layout_cursor = 0;
1854  client->notified = qfalse;
1855 
1856  // skip notifications for local clients
1857  if (NET_IsLocalAddress(&client->cl->netchan->remote_address))
1858  client->notified = qtrue;
1859 
1860  // skip notifications for Waiting Room channel
1861  if (mvd == &mvd_waitingRoom)
1862  client->notified = qtrue;
1863 
1864  if (!client->begin_time) {
1865  if (MVD_PartFilter(client)) {
1866  MVD_BroadcastPrintf(mvd, PRINT_MEDIUM, UF_MUTE_MISC,
1867  "[MVD] %s entered the channel\n", client->cl->name);
1868  }
1869  target = MVD_MostFollowed(mvd);
1870  } else if (mvd->flags & MVF_SINGLEPOV) {
1871  target = MVD_MostFollowed(mvd);
1872  } else {
1873  target = client->oldtarget;
1874  }
1875 
1876  client->target = NULL;
1877  client->begin_time = svs.realtime;
1878 
1879  MVD_SetDefaultLayout(client);
1880 
1881  if (mvd->intermission) {
1882  // force them to chase dummy MVD client
1883  client->target = mvd->dummy;
1884  MVD_SetFollowLayout(client);
1885  MVD_UpdateClient(client);
1886  } else if (target && target->inuse) {
1887  // start normal chase cam mode
1888  MVD_FollowStart(client, target);
1889  } else {
1890  // spawn the spectator
1891  VectorScale(mvd->spawnOrigin, 8, client->ps.pmove.origin);
1892  VectorCopy(mvd->spawnAngles, client->ps.viewangles);
1893  MVD_FollowStop(client);
1894 
1895  // if the map has just changed, player might not have spawned yet.
1896  // save the old target and try to return to it later.
1897  client->oldtarget = target;
1898  client->chase_wait = qtrue;
1899  }
1900 
1901  mvd_dirty = qtrue;
1902 }
1903 
1904 static void MVD_GameClientUserinfoChanged(edict_t *ent, char *userinfo)
1905 {
1906  mvd_client_t *client = EDICT_MVDCL(ent);
1907  float fov;
1908 
1909  client->uf = atoi(Info_ValueForKey(userinfo, "uf"));
1910 
1911  fov = atof(Info_ValueForKey(userinfo, "fov"));
1912  if (fov < 1) {
1913  fov = 90;
1914  } else if (fov > 160) {
1915  fov = 160;
1916  }
1917  client->fov = fov;
1918  if (!client->target)
1919  client->ps.fov = fov;
1920 }
1921 
1922 void MVD_GameClientNameChanged(edict_t *ent, const char *name)
1923 {
1924  mvd_client_t *client = EDICT_MVDCL(ent);
1925  client_t *cl = client->cl;
1926 
1927  if (client->begin_time && MVD_PartFilter(client)) {
1928  MVD_BroadcastPrintf(client->mvd, PRINT_MEDIUM, UF_MUTE_MISC,
1929  "[MVD] %s changed name to %s\n", cl->name, name);
1930  }
1931 }
1932 
1933 // called early from SV_DropClient to prevent multiple disconnect messages
1934 void MVD_GameClientDrop(edict_t *ent, const char *prefix, const char *reason)
1935 {
1936  mvd_client_t *client = EDICT_MVDCL(ent);
1937  client_t *cl = client->cl;
1938 
1939  if (client->begin_time && MVD_PartFilter(client)) {
1940  MVD_BroadcastPrintf(client->mvd, PRINT_MEDIUM, UF_MUTE_MISC,
1941  "[MVD] %s%s%s\n", cl->name, prefix, reason);
1942  }
1943 
1944  client->begin_time = 0;
1945 }
1946 
1947 static void MVD_GameClientDisconnect(edict_t *ent)
1948 {
1949  mvd_client_t *client = EDICT_MVDCL(ent);
1950  client_t *cl = client->cl;
1951 
1952  if (client->begin_time && MVD_PartFilter(client)) {
1953  MVD_BroadcastPrintf(client->mvd, PRINT_MEDIUM, UF_MUTE_MISC,
1954  "[MVD] %s disconnected\n", cl->name);
1955  }
1956 
1957  client->begin_time = 0;
1958 }
1959 
1961 {
1962  mvd_t *mvd = client->mvd;
1963  trace_t trace;
1964  vec3_t start, end, forward;
1965  mvd_player_t *player, *target;
1966  edict_t *ent;
1967  float fraction;
1968  int i;
1969 
1970  if (!mvd->players)
1971  return NULL;
1972 
1973  if (mvd->intermission)
1974  return NULL;
1975 
1976  VectorMA(client->ps.viewoffset, 0.125f, client->ps.pmove.origin, start);
1977  AngleVectors(client->ps.viewangles, forward, NULL, NULL);
1978  VectorMA(start, 8192, forward, end);
1979 
1980  if (mvd->cm.cache) {
1981  CM_BoxTrace(&trace, start, end, vec3_origin, vec3_origin,
1982  mvd->cm.cache->nodes, CONTENTS_SOLID);
1983  fraction = trace.fraction;
1984  } else {
1985  fraction = 1;
1986  }
1987 
1988  target = NULL;
1989  for (i = 0; i < mvd->maxclients; i++) {
1990  player = &mvd->players[i];
1991  if (!player->inuse || player == mvd->dummy)
1992  continue;
1993 
1994  ent = &mvd->edicts[i + 1];
1995  if (!ent->inuse)
1996  continue;
1997 
1998  if (ent->solid != SOLID_BBOX)
1999  continue;
2000 
2001  CM_TransformedBoxTrace(&trace, start, end, vec3_origin, vec3_origin,
2002  CM_HeadnodeForBox(ent->mins, ent->maxs),
2003  CONTENTS_MONSTER, ent->s.origin, vec3_origin);
2004 
2005  if (trace.fraction < fraction) {
2006  fraction = trace.fraction;
2007  target = player;
2008  }
2009  }
2010 
2011  return target;
2012 }
2013 
2014 static trace_t q_gameabi MVD_Trace(vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end)
2015 {
2016  trace_t trace;
2017 
2018  memset(&trace, 0, sizeof(trace));
2019  VectorCopy(end, trace.endpos);
2020  trace.fraction = 1;
2021 
2022  return trace;
2023 }
2024 
2025 static int MVD_PointContents(vec3_t p)
2026 {
2027  return 0;
2028 }
2029 
2030 static void MVD_GameClientThink(edict_t *ent, usercmd_t *cmd)
2031 {
2032  mvd_client_t *client = EDICT_MVDCL(ent);
2033  usercmd_t *old = &client->lastcmd;
2034  pmove_t pm;
2035 
2036  if (cmd->buttons != old->buttons
2037  || cmd->forwardmove != old->forwardmove
2038  || cmd->sidemove != old->sidemove
2039  || cmd->upmove != old->upmove) {
2040  // don't timeout
2042  }
2043 
2044  if ((cmd->buttons & ~old->buttons) & BUTTON_ATTACK) {
2045  mvd_player_t *hit;
2046 
2047  // small hack to allow picking up targets by aim
2048  if (client->target == NULL && (hit = MVD_HitPlayer(client)) != NULL) {
2049  client->oldtarget = hit;
2050  }
2051 
2052  MVD_Observe_f(client);
2053  }
2054 
2055  if (client->target) {
2056  if (cmd->upmove >= 10) {
2057  if (client->jump_held < 1) {
2058  if (!client->mvd->intermission) {
2059  MVD_FollowNext(client, client->target);
2060  client->chase_mask = 0;
2061  }
2062  client->jump_held = 1;
2063  }
2064  } else if (cmd->upmove <= -10) {
2065  if (client->jump_held > -1) {
2066  if (!client->mvd->intermission) {
2067  MVD_FollowPrev(client, client->target);
2068  client->chase_mask = 0;
2069  }
2070  client->jump_held = -1;
2071  }
2072  } else {
2073  client->jump_held = 0;
2074  }
2075  } else {
2076  memset(&pm, 0, sizeof(pm));
2077  pm.trace = MVD_Trace;
2078  pm.pointcontents = MVD_PointContents;
2079  pm.s = client->ps.pmove;
2080  pm.cmd = *cmd;
2081 
2082  PF_Pmove(&pm);
2083 
2084  client->ps.pmove = pm.s;
2085  if (pm.s.pm_type != PM_FREEZE) {
2086  VectorCopy(pm.viewangles, client->ps.viewangles);
2087  }
2088  }
2089 
2090  *old = *cmd;
2091 }
2092 
2094 {
2095  mvd_client_t *client;
2096 
2097  // set this early so MVD_SetDefaultLayout works
2098  mvd->intermission = qtrue;
2099 
2100 #if 0
2101  // save oldscores
2102  // FIXME: unfortunately this will also reset oldscores during
2103  // match timeout with certain mods (OpenTDM should work fine though)
2104  strcpy(mvd->oldscores, mvd->layout);
2105 #endif
2106 
2107  // force all clients to switch to the MVD dummy
2108  // and open the scoreboard, unless they had some special layout up
2109  FOR_EACH_MVDCL(client, mvd) {
2110  if (client->cl->state != cs_spawned) {
2111  continue;
2112  }
2113  client->oldtarget = client->target;
2114  client->target = mvd->dummy;
2115  if (client->layout_type < LAYOUT_SCORES) {
2116  MVD_SetDefaultLayout(client);
2117  }
2118  }
2119 }
2120 
2122 {
2123  mvd_client_t *client;
2124  mvd_player_t *target;
2125 
2126  // set this early so MVD_SetDefaultLayout works
2127  mvd->intermission = qfalse;
2128 
2129  // force all clients to switch to previous mode
2130  // and close the scoreboard
2131  FOR_EACH_MVDCL(client, mvd) {
2132  if (client->cl->state != cs_spawned) {
2133  continue;
2134  }
2135  if (client->layout_type == LAYOUT_SCORES) {
2136  client->layout_type = 0;
2137  }
2138  target = client->oldtarget;
2139  if (target && target->inuse) {
2140  // start normal chase cam mode
2141  MVD_FollowStart(client, target);
2142  } else {
2143  MVD_FollowStop(client);
2144  }
2145  client->oldtarget = NULL;
2146  }
2147 }
2148 
2149 static void MVD_NotifyClient(mvd_client_t *client)
2150 {
2151  mvd_t *mvd = client->mvd;
2152 
2153  if (client->notified)
2154  return;
2155 
2156  if (svs.realtime - client->begin_time < 2000)
2157  return;
2158 
2159  // notify them if visibility data is missing
2160  if (!mvd->cm.cache) {
2161  SV_ClientPrintf(client->cl, PRINT_HIGH,
2162  "[MVD] GTV server doesn't have this map!\n");
2163  }
2164 
2165  // notify them if channel is in waiting state
2166  if (mvd->state == MVD_WAITING) {
2167  SV_ClientPrintf(client->cl, PRINT_HIGH,
2168  "[MVD] Buffering data, please wait...\n");
2169  }
2170 
2171  client->notified = qtrue;
2172 }
2173 
2174 // called just after new frame is parsed
2176 {
2177  mvd_client_t *client;
2178 
2179  // check for intermission
2180  if (mvd_freeze_hack->integer && mvd->dummy) {
2181  if (!mvd->intermission) {
2182  if (mvd->dummy->ps.pmove.pm_type == PM_FREEZE) {
2184  }
2185  } else if (mvd->dummy->ps.pmove.pm_type != PM_FREEZE) {
2187  }
2188  } else if (mvd->intermission) {
2190  }
2191 
2192  // update UDP clients
2193  FOR_EACH_MVDCL(client, mvd) {
2194  if (client->cl->state == cs_spawned) {
2195  MVD_UpdateTarget(client);
2196  MVD_UpdateClient(client);
2197  MVD_NotifyClient(client);
2198  }
2199  }
2200 }
2201 
2203 {
2204  uint16_t msglen;
2205  ssize_t ret;
2206 
2207  msglen = LittleShort(msg_read.cursize);
2208  ret = FS_Write(&msglen, 2, mvd->demorecording);
2209  if (ret != 2)
2210  goto fail;
2211  ret = FS_Write(msg_read.data, msg_read.cursize, mvd->demorecording);
2212  if (ret == msg_read.cursize)
2213  return;
2214 
2215 fail:
2216  Com_EPrintf("[%s] Couldn't write demo: %s\n", mvd->name, Q_ErrorString(ret));
2218 }
2219 
2220 static void MVD_GameRunFrame(void)
2221 {
2222  mvd_t *mvd, *next;
2223  int numplayers = 0;
2224 
2225  LIST_FOR_EACH_SAFE(mvd_t, mvd, next, &mvd_channel_list, entry) {
2226  if (setjmp(mvd_jmpbuf)) {
2227  continue;
2228  }
2229 
2230  // parse stream
2231  if (!mvd->read_frame(mvd)) {
2232  goto update;
2233  }
2234 
2235  // write this message to demofile
2236  if (mvd->demorecording) {
2238  }
2239 
2240 update:
2242  numplayers += mvd->numplayers;
2243  }
2244 
2246 
2247  if (mvd_dirty) {
2248  MVD_InfoSet("mvd_channels", va("%d", List_Count(&mvd_channel_list)));
2249  mvd_dirty = qfalse;
2250  }
2251 
2252  if (numplayers != mvd_numplayers) {
2253  MVD_InfoSet("mvd_players", va("%d", numplayers));
2254  mvd_numplayers = numplayers;
2255  mvd_dirty = qtrue; // update layouts next frame
2256  }
2257 }
2258 
2259 static void MVD_GameServerCommand(void)
2260 {
2261 }
2262 
2264 {
2265  mvd_t *mvd;
2266  edict_t *ent;
2267  int i;
2268 
2269  // reset events and old origins
2270  FOR_EACH_MVD(mvd) {
2271  for (i = 1; i < mvd->pool.num_edicts; i++) {
2272  ent = &mvd->edicts[i];
2273  if (!ent->inuse) {
2274  continue;
2275  }
2276  if (!(ent->s.renderfx & RF_BEAM)) {
2277  VectorCopy(ent->s.origin, ent->s.old_origin);
2278  }
2279  ent->s.event = 0;
2280  }
2281  }
2282 }
2283 
2284 
2285 game_export_t mvd_ge = {
2286  GAME_API_VERSION,
2287 
2288  MVD_GameInit,
2303 };
2304 
FLOOD_SAMPLES
#define FLOOD_SAMPLES
Definition: client.h:53
mvd_chase_prefix
static cvar_t * mvd_chase_prefix
Definition: game.c:35
MVD_LayoutScores
static void MVD_LayoutScores(mvd_client_t *client)
Definition: game.c:290
mvd_player_t::configstrings
mvd_cs_t * configstrings
Definition: client.h:66
MVD_PrepWorldFrame
void MVD_PrepWorldFrame(void)
Definition: game.c:2263
MVD_Commands_f
static void MVD_Commands_f(mvd_client_t *client)
Definition: game.c:1409
mvd_channel_list
list_t mvd_channel_list
MVD_GameReadGame
static void MVD_GameReadGame(const char *filename)
Definition: game.c:1806
client_state_s::configstrings
char configstrings[MAX_CONFIGSTRINGS][MAX_QPATH]
Definition: client.h:289
mvd_client_t::layout_time
unsigned layout_time
Definition: client.h:93
Cvar_Set
cvar_t * Cvar_Set(const char *var_name, const char *value)
Definition: cvar.c:466
CM_BoxTrace
void CM_BoxTrace(trace_t *trace, vec3_t start, vec3_t end, vec3_t mins, vec3_t maxs, mnode_t *headnode, int brushmask)
Definition: cmodel.c:683
FLOOD_MASK
#define FLOOD_MASK
Definition: client.h:54
mvd_filter_version
static cvar_t * mvd_filter_version
Definition: game.c:31
mvd_layout_t
mvd_layout_t
Definition: client.h:43
set_player_name
static void set_player_name(mvd_t *mvd, int index)
Definition: game.c:1572
MVD_Invuse_f
static void MVD_Invuse_f(mvd_client_t *client)
Definition: game.c:1272
cs_spawned
@ cs_spawned
Definition: server.h:192
mvd_server_t::players
player_packed_t * players
Definition: mvd.c:69
mvd_client_t::chase_bitmap
byte chase_bitmap[MAX_CLIENTS/CHAR_BIT]
Definition: client.h:90
msg_read
sizebuf_t msg_read
Definition: msg.c:37
pm
static pmove_t * pm
Definition: pmove.c:44
mvd_dummy
mvd_player_t mvd_dummy
Definition: game.c:39
mvd_last_activity
unsigned mvd_last_activity
Definition: client.c:106
mvd_waitingRoom
mvd_t mvd_waitingRoom
Definition: client.c:101
VER_OFS
#define VER_OFS
Definition: game.c:56
svs
server_static_t svs
Definition: init.c:21
mvd_client_t::layout_cursor
int layout_cursor
Definition: client.h:94
mvd_client_t::begin_time
unsigned begin_time
Definition: client.h:81
mvd_part_filter
static cvar_t * mvd_part_filter
Definition: game.c:26
MVD_TrySwitchChannel
static void MVD_TrySwitchChannel(mvd_client_t *client, mvd_t *mvd)
Definition: game.c:814
MVD_SetChannel
mvd_t * MVD_SetChannel(int arg)
Definition: client.c:230
MVD_GameClientDrop
void MVD_GameClientDrop(edict_t *ent, const char *prefix, const char *reason)
Definition: game.c:1934
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
MVD_Forward_f
static void MVD_Forward_f(mvd_client_t *client)
Definition: game.c:862
LAYOUT_CHANNELS
@ LAYOUT_CHANNELS
Definition: client.h:50
mvd_client_t::lastcmd
usercmd_t lastcmd
Definition: client.h:100
mvd_client_t::admin
qboolean admin
Definition: client.h:79
MVD_LayoutChannels
static void MVD_LayoutChannels(mvd_client_t *client)
Definition: game.c:148
count_chase_bits
static qboolean count_chase_bits(mvd_client_t *client)
Definition: game.c:1118
MVD_GameInit
static void MVD_GameInit(void)
Definition: game.c:1696
MVD_Shutdown
void MVD_Shutdown(void)
Definition: client.c:2528
MVD_RemoveClient
void MVD_RemoveClient(client_t *client)
Definition: game.c:1685
MVD_GameClientThink
static void MVD_GameClientThink(edict_t *ent, usercmd_t *cmd)
Definition: game.c:2030
MVD_HitPlayer
static mvd_player_t * MVD_HitPlayer(mvd_client_t *client)
Definition: game.c:1960
MVD_PartFilter
static qboolean MVD_PartFilter(mvd_client_t *client)
Definition: game.c:788
MVD_StopRecord
void MVD_StopRecord(mvd_t *mvd)
Definition: client.c:125
Cvar_Get
cvar_t * Cvar_Get(const char *var_name, const char *var_value, int flags)
Definition: cvar.c:257
mvd_channel_list_f
static void mvd_channel_list_f(mvd_client_t *client)
Definition: game.c:1388
Q_ErrorString
const char * Q_ErrorString(qerror_t error)
Definition: error.c:51
mvd_client_t::chase_mask
int chase_mask
Definition: client.h:87
MVD_TestTarget
static qboolean MVD_TestTarget(mvd_client_t *client, mvd_player_t *target)
Definition: game.c:498
mvd_flood_waitdelay
static cvar_t * mvd_flood_waitdelay
Definition: game.c:29
mvd_clients
mvd_client_t * mvd_clients
Definition: game.c:37
client_s::state
clstate_t state
Definition: server.h:260
MSG_CLEAR
#define MSG_CLEAR
Definition: server.h:215
mvd_cs_s::string
char string[1]
Definition: client.h:59
mvd_client_t::floodTime
unsigned floodTime
Definition: client.h:98
MSG_RELIABLE
#define MSG_RELIABLE
Definition: server.h:214
MVD_GameShutdown
static void MVD_GameShutdown(void)
Definition: game.c:1786
MVD_UpdateTarget
static void MVD_UpdateTarget(mvd_client_t *client)
Definition: game.c:598
MVD_GameClientDisconnect
static void MVD_GameClientDisconnect(edict_t *ent)
Definition: game.c:1947
MVD_SetServerState
static void MVD_SetServerState(client_t *cl, mvd_t *mvd)
Definition: game.c:753
mvd_freeze_hack
static cvar_t * mvd_freeze_hack
Definition: game.c:34
MVD_UpdateLayouts
static void MVD_UpdateLayouts(mvd_t *mvd)
Definition: game.c:382
mvd_client_t::notified
qboolean notified
Definition: client.h:80
MVD_SetFollowLayout
static void MVD_SetFollowLayout(mvd_client_t *client)
Definition: game.c:372
LAYOUT_SCORES
@ LAYOUT_SCORES
Definition: client.h:46
CM_HeadnodeForBox
mnode_t * CM_HeadnodeForBox(vec3_t mins, vec3_t maxs)
Definition: cmodel.c:190
client_state_s::gamedir
char gamedir[MAX_QPATH]
Definition: client.h:277
mvd_client_t::entry
list_t entry
Definition: client.h:76
MVD_GameRunFrame
static void MVD_GameRunFrame(void)
Definition: game.c:2220
SV_InfoSet
#define SV_InfoSet(var, val)
Definition: server.h:73
layout
layout(set=GOD_RAYS_DESC_SET_IDX, binding=0) uniform sampler2DArray TEX_SHADOW_MAP
mvd_client_t::layout_type
mvd_layout_t layout_type
Definition: client.h:92
LAYOUT_CLIENTS
@ LAYOUT_CLIENTS
Definition: client.h:49
mvd_dirty
qboolean mvd_dirty
Definition: client.c:102
Q_vsnprintf
size_t Q_vsnprintf(char *dest, size_t size, const char *fmt, va_list argptr)
Definition: shared.c:791
other
@ other
Definition: ogg.c:63
MVD_FollowNext
static mvd_player_t * MVD_FollowNext(mvd_client_t *client, mvd_player_t *from)
Definition: game.c:517
MVD_GameWriteGame
static void MVD_GameWriteGame(const char *filename, qboolean autosave)
Definition: game.c:1803
FOR_EACH_MVD
#define FOR_EACH_MVD(mvd)
Definition: client.h:26
MSG_WriteByte
void MSG_WriteByte(int c)
Definition: msg.c:107
MVD_GameReadLevel
static void MVD_GameReadLevel(const char *filename)
Definition: game.c:1812
MVD_LayoutMenu
static void MVD_LayoutMenu(mvd_client_t *client)
Definition: game.c:244
Cmd_Argv
char * Cmd_Argv(int arg)
Definition: cmd.c:899
MVD_AutoFollow_f
static void MVD_AutoFollow_f(mvd_client_t *client)
Definition: game.c:1132
MVD_LinkEdict
void MVD_LinkEdict(mvd_t *mvd, edict_t *ent)
Definition: game.c:1652
svc_stufftext
#define svc_stufftext
Definition: g_local.h:41
MVD_GameServerCommand
static void MVD_GameServerCommand(void)
Definition: game.c:2259
mvd_client_t::uf
int uf
Definition: client.h:83
MVD_SetPlayerNames
void MVD_SetPlayerNames(mvd_t *mvd)
Definition: game.c:1607
LAYOUT_FOLLOW
@ LAYOUT_FOLLOW
Definition: client.h:45
Cmd_Argc
int Cmd_Argc(void)
Definition: cmd.c:889
SV_ClientAddMessage
void SV_ClientAddMessage(client_t *client, int flags)
Definition: send.c:399
MVD_ParseEntityString
void MVD_ParseEntityString(mvd_t *mvd, const char *data)
Definition: parse.c:64
mvd_cs_s::index
int index
Definition: client.h:58
mvd_client_t::mvd
struct mvd_s * mvd
Definition: client.h:77
vec3_origin
vec3_t vec3_origin
Definition: shared.c:21
MVD_GameClientCommand
static void MVD_GameClientCommand(edict_t *ent)
Definition: game.c:1424
MVD_FollowStop
static void MVD_FollowStop(mvd_client_t *client)
Definition: game.c:445
mvd_client_t::clientNum
int clientNum
Definition: client.h:73
mvd_s::state
mvd_state_t state
Definition: client.h:130
MVD_Admin_f
static void MVD_Admin_f(mvd_client_t *client)
Definition: game.c:837
Cmd_ArgsFrom
char * Cmd_ArgsFrom(int from)
Definition: cmd.c:981
MSG_COMPRESS
#define MSG_COMPRESS
Definition: server.h:216
BSP_Free
void BSP_Free(bsp_t *bsp)
Definition: bsp.c:932
MVD_GameClientUserinfoChanged
static void MVD_GameClientUserinfoChanged(edict_t *ent, char *userinfo)
Definition: game.c:1904
MVD_SetNewLayout
static void MVD_SetNewLayout(mvd_client_t *client, mvd_layout_t type)
Definition: game.c:337
mvd_player_t::inuse
qboolean inuse
Definition: client.h:64
MENU_ITEMS
#define MENU_ITEMS
Definition: game.c:229
mvd_s
Definition: client.h:127
mvd_active
qboolean mvd_active
Definition: client.c:105
forward
static vec3_t forward
Definition: p_view.c:27
sv
server_t sv
Definition: init.c:22
mvd
static mvd_server_t mvd
Definition: mvd.c:81
MVD_UpdateClients
void MVD_UpdateClients(mvd_t *mvd)
Definition: game.c:2175
PF_Pmove
void PF_Pmove(pmove_t *pm)
Definition: game.c:708
msg_write
sizebuf_t msg_write
Definition: msg.c:34
mvd_client_t::target
mvd_player_t * target
Definition: client.h:85
MVD_FreePlayer
void MVD_FreePlayer(mvd_player_t *player)
Definition: game.c:1540
Com_EndRedirect
void Com_EndRedirect(void)
Definition: common.c:172
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
mvd_s::intermission
qboolean intermission
Definition: client.h:172
server_static_s::client_pool
client_t * client_pool
Definition: server.h:456
MVD_GameClientNameChanged
void MVD_GameClientNameChanged(edict_t *ent, const char *name)
Definition: game.c:1922
MVD_PointContents
static int MVD_PointContents(vec3_t p)
Definition: game.c:2025
SV_ClientCommand
void SV_ClientCommand(client_t *client, const char *fmt,...)
Definition: send.c:185
AngleVectors
void AngleVectors(vec3_t angles, vec3_t forward, vec3_t right, vec3_t up)
Definition: shared.c:23
LAYOUT_NONE
@ LAYOUT_NONE
Definition: client.h:44
mvd_stats_score
static cvar_t * mvd_stats_score
Definition: game.c:32
MVD_FEATURES
#define MVD_FEATURES
Definition: client.h:39
Q_strlcpy
size_t Q_strlcpy(char *dst, const char *src, size_t size)
Definition: shared.c:715
MVD_SetDefaultLayout
static void MVD_SetDefaultLayout(mvd_client_t *client)
Definition: game.c:345
mvd_flood_mute
static cvar_t * mvd_flood_mute
Definition: game.c:30
MVD_CountClients
static int MVD_CountClients(mvd_t *mvd)
Definition: game.c:135
Z_ReservedAllocz
void * Z_ReservedAllocz(size_t size)
Definition: zone.c:367
mvd_client_t::oldtarget
mvd_player_t * oldtarget
Definition: client.h:86
MVD_BroadcastPrintf
void MVD_BroadcastPrintf(mvd_t *mvd, int level, int mask, const char *fmt,...)
Definition: game.c:707
mvd_client_t::cl
client_t * cl
Definition: client.h:78
MSG_WriteShort
void MSG_WriteShort(int c)
Definition: msg.c:125
print_channel
static void print_channel(client_t *cl, mvd_t *mvd)
Definition: game.c:1351
MVD_FollowStart
static void MVD_FollowStart(mvd_client_t *client, mvd_player_t *target)
Definition: game.c:480
SV_LinkEdict
void SV_LinkEdict(cm_t *cm, edict_t *ent)
Definition: world.c:156
MVD_WAITING
@ MVD_WAITING
Definition: client.h:109
CM_TransformedBoxTrace
void CM_TransformedBoxTrace(trace_t *trace, vec3_t start, vec3_t end, vec3_t mins, vec3_t maxs, mnode_t *headnode, int brushmask, vec3_t origin, vec3_t angles)
Definition: cmodel.c:764
mvd_numplayers
static int mvd_numplayers
Definition: game.c:41
EDICT_MVDCL
#define EDICT_MVDCL(ent)
Definition: client.h:32
mvd_client_t
Definition: client.h:69
MVD_NotifyClient
static void MVD_NotifyClient(mvd_client_t *client)
Definition: game.c:2149
mvd_client_t::jump_held
int jump_held
Definition: client.h:102
client_state_s::mapname
char mapname[MAX_QPATH]
Definition: client.h:290
Com_WildCmpEx
qboolean Com_WildCmpEx(const char *filter, const char *string, int term, qboolean ignorecase)
Definition: utils.c:122
MVD_UpdateConfigstring
void MVD_UpdateConfigstring(mvd_t *mvd, int index)
Definition: game.c:1616
FOR_EACH_MVDCL
#define FOR_EACH_MVDCL(cl, mvd)
Definition: client.h:29
mvd_client_t::ps
player_state_t ps
Definition: client.h:71
LAYOUT_MENU
@ LAYOUT_MENU
Definition: client.h:48
MVD_FollowPrev
static mvd_player_t * MVD_FollowPrev(mvd_client_t *client, mvd_player_t *from)
Definition: game.c:546
Z_TagReserve
void Z_TagReserve(size_t size, memtag_t tag)
Definition: zone.c:342
cl
client_state_t cl
Definition: main.c:99
MVD_ToggleLayout
static void MVD_ToggleLayout(mvd_client_t *client, mvd_layout_t type)
Definition: game.c:363
mvd_stats_hack
static cvar_t * mvd_stats_hack
Definition: game.c:33
FS_Write
ssize_t FS_Write(const void *buf, size_t len, qhandle_t f)
Definition: files.c:1643
mvd_client_t::floodSamples
unsigned floodSamples[FLOOD_SAMPLES]
Definition: client.h:96
mvd_cs_s
Definition: client.h:56
mvd_server_t::dummy
client_t * dummy
Definition: mvd.c:57
reset_unicast_strings
static void reset_unicast_strings(mvd_t *mvd, int index)
Definition: game.c:1551
mvd_client_t::chase_auto
qboolean chase_auto
Definition: client.h:88
mvd_jmpbuf
jmp_buf mvd_jmpbuf
Definition: client.c:108
COM_IsUint
qboolean COM_IsUint(const char *s)
Definition: shared.c:330
MSG_WriteString
void MSG_WriteString(const char *string)
Definition: msg.c:160
c
statCounters_t c
Definition: main.c:30
DEFAULT
#define DEFAULT
Definition: game.c:232
MVD_UpdateClient
static void MVD_UpdateClient(mvd_client_t *client)
Definition: game.c:649
mvd_client_t::chase_wait
qboolean chase_wait
Definition: client.h:89
mvd_player_t
Definition: client.h:62
MVD_IntermissionStart
static void MVD_IntermissionStart(mvd_t *mvd)
Definition: game.c:2093
Cvar_ClampInteger
int Cvar_ClampInteger(cvar_t *var, int min, int max)
Definition: cvar.c:549
server_static_s::realtime
unsigned realtime
Definition: server.h:454
mvd_cs_s::next
struct mvd_cs_s * next
Definition: client.h:57
LAYOUT_OLDSCORES
@ LAYOUT_OLDSCORES
Definition: client.h:47
level
level_locals_t level
Definition: g_main.c:22
NO
#define NO
Definition: game.c:231
mvd_flood_msgs
static cvar_t * mvd_flood_msgs
Definition: game.c:27
cs_primed
@ cs_primed
Definition: server.h:191
SV_ClientPrintf
void SV_ClientPrintf(client_t *client, int level, const char *fmt,...)
Definition: send.c:121
mvd_client_t::floodHead
unsigned floodHead
Definition: client.h:97
MVD_WriteDemoMessage
static void MVD_WriteDemoMessage(mvd_t *mvd)
Definition: game.c:2202
update_player_name
static void update_player_name(mvd_t *mvd, int index)
Definition: game.c:1586
mvd_player_t::name
char name[16]
Definition: client.h:65
MVD_GameWriteLevel
static void MVD_GameWriteLevel(const char *filename)
Definition: game.c:1809
MVD_MostFollowed
static mvd_player_t * MVD_MostFollowed(mvd_t *mvd)
Definition: game.c:575
client_s::netchan
netchan_t * netchan
Definition: server.h:357
client_s
Definition: server.h:256
MVD_IntermissionStop
static void MVD_IntermissionStop(mvd_t *mvd)
Definition: game.c:2121
svc_layout
#define svc_layout
Definition: g_local.h:39
mvd_ge
game_export_t mvd_ge
Definition: game.c:2285
MVD_GameClientBegin
static void MVD_GameClientBegin(edict_t *ent)
Definition: game.c:1840
client.h
LAYOUT_MSEC
#define LAYOUT_MSEC
Definition: client.h:41
BSP_Load
qerror_t BSP_Load(const char *name, bsp_t **bsp_p)
Definition: bsp.c:1087
PAGE_CLIENTS
#define PAGE_CLIENTS
Definition: game.c:54
MVD_LayoutFollow
static void MVD_LayoutFollow(mvd_client_t *client)
Definition: game.c:318
MVD_Say_f
static void MVD_Say_f(mvd_client_t *client, int argnum)
Definition: game.c:881
mvd_admin_password
static cvar_t * mvd_admin_password
Definition: game.c:25
MVD_Join_f
static void MVD_Join_f(mvd_client_t *client)
Definition: game.c:1331
mvd_client_t::fov
float fov
Definition: client.h:82
MVD_GameSpawnEntities
static void MVD_GameSpawnEntities(const char *mapname, const char *entstring, const char *spawnpoint)
Definition: game.c:1800
mvd_flood_persecond
static cvar_t * mvd_flood_persecond
Definition: game.c:28
MVD_Observe_f
static void MVD_Observe_f(mvd_client_t *client)
Definition: game.c:953
server_t::spawncount
int spawncount
Definition: server.h:147
MVD_LayoutClients
static void MVD_LayoutClients(mvd_client_t *client)
Definition: game.c:58
MVD_SwitchChannel
void MVD_SwitchChannel(mvd_client_t *client, mvd_t *mvd)
Definition: game.c:765
client_state_s::maxclients
int maxclients
Definition: client.h:279
MVD_Clients_f
static void MVD_Clients_f(mvd_client_t *client)
Definition: game.c:1401
YES
#define YES
Definition: game.c:230
Q_scnprintf
size_t Q_scnprintf(char *dest, size_t size, const char *fmt,...)
Definition: shared.c:867
mvd_player_t::ps
player_state_t ps
Definition: client.h:63
MVD_Trace
static trace_t q_gameabi MVD_Trace(vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end)
Definition: game.c:2014
client_s::configstrings
char * configstrings
Definition: server.h:343
MVD_Follow_f
static void MVD_Follow_f(mvd_client_t *client)
Definition: game.c:1042
MVD_SetPlayer
static mvd_player_t * MVD_SetPlayer(mvd_client_t *client, const char *s)
Definition: game.c:986
MVD_InfoSet
#define MVD_InfoSet(var, val)
Definition: client.h:35
clamp_menu_cursor
static int clamp_menu_cursor(mvd_client_t *client)
Definition: game.c:234
SZ_Clear
void SZ_Clear(sizebuf_t *buf)
Definition: sizebuf.c:40
mvd_server_t::clients
gtv_client_t * clients
Definition: mvd.c:78
SV_ClientReset
void SV_ClientReset(client_t *client)
Definition: init.c:24
SV_ClientRedirect
#define SV_ClientRedirect()
Definition: server.h:584
MVD_GameClientConnect
static qboolean MVD_GameClientConnect(edict_t *ent, char *userinfo)
Definition: game.c:1816
write_cs_list
static void write_cs_list(mvd_client_t *client, mvd_cs_t *cs)
Definition: game.c:435
client_s::name
char name[MAX_CLIENT_NAME]
Definition: server.h:276
sv_maxclients
cvar_t * sv_maxclients
Definition: main.c:58