1 /*
2 Copyright (C) 2003-2006 Andrey Nazarov
3
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (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.
12
13 See the GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18
19 */
20
21 //
22 // mvd_record.c - local multiview demo recorder
23 //
24
25 #include "server.h"
26 #include "mvd.h"
27
28 cvar_t *mvd_running;
29 cvar_t *mvd_nodelta;
30 cvar_t *mvd_wait_clients;
31 cvar_t *mvd_cache_megs;
32 cvar_t *mvd_cache_count;
33 cvar_t *mvd_max_size;
34 cvar_t *mvd_max_duration;
35 cvar_t *mvd_shownet;
36 cvar_t *mvd_debug;
37 cvar_t *mvd_noblend;
38 cvar_t *mvd_nogun;
39 cvar_t *mvd_motd;
40 cvar_t *mvd_custom_fov;
41 cvar_t *mvd_nextserver;
42 cvar_t *mvd_timeout;
43 cvar_t *mvd_admin_password;
44 cvar_t *mvd_flood_msgs;
45 cvar_t *mvd_flood_persecond;
46 cvar_t *mvd_flood_waitdelay;
47 cvar_t *mvd_autoscores;
48 cvar_t *mvd_safecmd;
49
50 #define DCS_BYTES 260
51 #define DCS_DWORDS (DCS_BYTES/4)
52
53 #if( DCS_BYTES != MAX_CONFIGSTRINGS/8 )
54 #error Invalid DCS_BYTES
55 #endif
56
57 typedef struct {
58 qboolean recording;
59
60 qboolean paused;
61 byte dirty_configstrings[DCS_BYTES];
62
63 fileHandle_t demofile;
64 mvdCacheEntry_t *cache;
65
66 // multicast and unicast datagrams acclumulated last frame
67 sizebuf_t multicast;
68 byte multicast_buf[MAX_MSGLEN];
69
70 entity_state_t *baselines[SV_BASELINES_CHUNKS];
71
72 deltaFrame_t frames[MVD_REC_NUM_FRAMES];
73 int frameNum;
74 } mvdRecorder_t;
75
76 static mvdRecorder_t rec;
77
78
79 list_t rec_cache;
80 static int rec_sequence;
81
82
83 /*
84 =============
85 MVD_RecWrite
86 =============
87 */
MVD_RecWrite(void)88 static void MVD_RecWrite( void ) {
89 int length;
90
91 if( !msg_write.cursize ) {
92 return;
93 }
94
95 length = LittleLong( msg_write.cursize );
96 FS_Write( &length, 4, rec.demofile );
97 FS_Write( msg_write.data, msg_write.cursize, rec.demofile );
98
99 SZ_Clear( &msg_write );
100 }
101
102 /*
103 ==================
104 MVD_PlayerIsActive
105
106 Can't just use client->state == cs_spawned
107 I want to capture bots too!
108 FIXME: some better way to do this?
109 Called on game servers only.
110 ==================
111 */
MVD_PlayerIsActive(int clientNum)112 qboolean MVD_PlayerIsActive( int clientNum ) {
113 edict_t *ent;
114
115 if( sv.state != ss_game ) {
116 Com_Error( ERR_DROP, "MVD_PlayerIsActive: called on non-game server" );
117 }
118
119 ent = EDICT_NUM( clientNum + 1 );
120 if( !ent->inuse ) {
121 return qfalse;
122 }
123
124 if( !ent->client ) {
125 return qfalse;
126 }
127
128 // HACK: make sure player_state_t is valid
129 if( !ent->client->ps.fov ) {
130 return qfalse;
131 }
132
133 // HACK: if pm_type == PM_FREEZE, assume intermission is running
134 // if PMF_NO_PREDICTION is set, they are following someone!
135 if( ent->client->ps.pmove.pm_type == PM_FREEZE &&
136 !( ent->client->ps.pmove.pm_flags & PMF_NO_PREDICTION ) )
137 {
138 return qtrue;
139 }
140
141 // if set to invisible, skip
142 if( ent->svflags & SVF_NOCLIENT ) {
143 return qfalse;
144 }
145
146 if( ent->s.modelindex || ent->s.effects || ent->s.sound || ent->s.event ) {
147 return qtrue;
148 }
149
150 return qfalse;
151
152 }
153
154 /*
155 ==================
156 MVD_RecFlush
157
158 Flushes accumulated multicast datagram,
159 dumps entire message to disk and
160 clears the write buffer.
161 ==================
162 */
MVD_RecFlush(void)163 static void MVD_RecFlush( void ) {
164 MSG_WriteData( rec.multicast.data, rec.multicast.cursize );
165 SZ_Clear( &rec.multicast );
166 MVD_RecWrite();
167 }
168
169 /*
170 ==================
171 MVD_RecBeginFrame
172 ==================
173 */
MVD_RecBeginFrame(void)174 void MVD_RecBeginFrame( void ) {
175 int i, j;
176 int index, length;
177
178 if( !rec.recording ) {
179 return;
180 }
181
182 if( sv.state != ss_game ) {
183 rec.paused = qfalse;
184 return;
185 }
186
187 if( mvd_wait_clients->integer == 1 ) {
188 for( i = 0; i < sv_maxclients->integer; i++ ) {
189 if( MVD_PlayerIsActive( i ) ) {
190 break;
191 }
192 }
193 } else if( mvd_wait_clients->integer > 1 ) {
194 for( i = 0; i < sv_maxclients->integer; i++ ) {
195 if( svs.clientpool[i].state == cs_spawned ) {
196 break;
197 }
198 }
199 } else {
200 i = 0;
201 }
202
203 if( i == sv_maxclients->integer ) {
204 if( rec.paused ) {
205 return;
206 }
207 Com_Printf( "MVD recording paused - no active clients.\n" );
208 for( i = 0; i < DCS_DWORDS; i++ ) {
209 (( uint32 * )rec.dirty_configstrings)[i] = 0;
210 }
211 rec.paused = qtrue;
212 return;
213 }
214
215 if( !rec.paused ) {
216 return;
217 }
218 for( i = 0; i < DCS_DWORDS; i++ ) {
219 if( (( uint32 * )rec.dirty_configstrings)[i] == 0 ) {
220 continue;
221 }
222 index = i << 5;
223 for( j = 0; j < 32; j++, index++ ) {
224 if( !Q_IsBitSet( rec.dirty_configstrings, index ) ) {
225 continue;
226 }
227 SZ_WriteByte( &rec.multicast, svc_configstring );
228 SZ_WriteShort( &rec.multicast, index );
229 length = strlen( sv.configstrings[index] );
230 if( length > MAX_QPATH ) {
231 length = MAX_QPATH;
232 }
233 SZ_Write( &rec.multicast, sv.configstrings[index], length );
234 SZ_WriteByte( &rec.multicast, 0 );
235 }
236
237 }
238
239 Com_Printf( "MVD recording unpaused, flushed %d bytes.\n", rec.multicast.cursize );
240 // will be subsequently written to disk in MVD_RecEndFrame
241
242 rec.paused = qfalse;
243
244 }
245
246 /*
247 ==================
248 MVD_RecEndFrame
249
250 Save everything in the world, including all player states,
251 delta compress the data.
252 ==================
253 */
MVD_RecEndFrame(void)254 void MVD_RecEndFrame( void ) {
255 edict_t *ent;
256 entity_state_t *es;
257 playerStateEx_t *ps;
258 deltaFrame_t *frame, *oldframe;
259 int i, oldFrameNum, delta;
260 byte portalbytes[MAX_MAP_AREAS/8];
261 int numbytes;
262 msgPsFlags_t flags;
263 //mvdPlayer_t *player;
264
265 if( !rec.recording ) {
266 return;
267 }
268
269 if( rec.paused ) {
270 return; // don't write any frames on empty server
271 }
272
273 if( mvd_nodelta->integer || rec.frameNum < 2 ) {
274 oldFrameNum = -1;
275 delta = 255;
276 oldframe = NULL;
277 } else {
278 oldFrameNum = rec.frameNum - 1;
279 delta = 1;
280 oldframe = &rec.frames[oldFrameNum & MVD_REC_FRAMES_MASK];
281 }
282
283 frame = &rec.frames[rec.frameNum & MVD_REC_FRAMES_MASK];
284
285 MSG_WriteByte( svc_frame );
286 MSG_WriteLong( rec.frameNum ); // frame num
287 MSG_WriteByte( delta ); // delta frame num
288
289 numbytes = CM_WritePortalBits( &sv.cm, portalbytes );
290 MSG_WriteByte( numbytes ); // num of areabytes
291 MSG_WriteData( portalbytes, numbytes );
292
293 SV_DeltaBeginFrame( frame );
294
295 for( i = 1; i < ge->num_edicts; i++ ) {
296 ent = EDICT_NUM( i );
297
298 if( ent->inuse && !( ent->svflags & SVF_NOCLIENT ) &&
299 ( ent->s.modelindex || ent->s.effects || ent->s.sound || ent->s.event ) )
300 {
301 es = SV_DeltaAllocEntity();
302 *es = ent->s;
303 es->number = i;
304 }
305 }
306
307 /*if( sv.state == ss_broadcast ) {
308 for( i = 0; i < mvd.maxPlayers; i++ ) {
309 player = &mvd.players[i];
310 if( player->packetNum == mvd.activePacketNum ) {
311 ps = SV_DeltaAllocPlayer();
312 *ps = player->ps;
313 }
314 }
315 } else */{
316 for( i = 0 ; i < sv_maxclients->integer; i++ ) {
317 if( MVD_PlayerIsActive( i ) ) {
318 ps = SV_DeltaAllocPlayer();
319
320 ent = EDICT_NUM( i + 1 );
321 ps->ps = ent->client->ps;
322 ps->number = i;
323 ps->clientNum = i;
324 }
325 }
326 }
327
328 flags = MSG_PS_IGNORE_PREDICTION|MSG_PS_IGNORE_DELTAANGLES;
329 if( mvd_noblend->integer ) {
330 flags |= MSG_PS_IGNORE_BLEND;
331 }
332 if( mvd_nogun->integer ) {
333 flags |= MSG_PS_IGNORE_GUNINDEX|MSG_PS_IGNORE_GUNFRAMES;
334 }
335 SV_EmitPacketPlayers( oldframe, frame, flags );
336 SV_EmitPacketEntities( oldframe, frame, CLIENTNUM_ANY, rec.baselines );
337
338 rec.frameNum++;
339
340 MVD_RecFlush();
341
342 if( !rec.cache ) {
343 return;
344 }
345
346 numbytes = FS_RawTell( rec.demofile );
347 rec.cache->size = numbytes;
348 rec.cache->numFrames = rec.frameNum;
349
350 if( mvd_max_size->value && numbytes > mvd_max_size->value * 1000 ) {
351 Com_Printf( "Stopping MVD autorecord, maximum size reached\n" );
352 MVD_RecStop();
353 return;
354 }
355
356 if( mvd_max_duration->value && rec.frameNum > mvd_max_duration->value * 600 ) {
357 Com_Printf( "Stopping MVD autorecord, maximum duration reached\n" );
358 MVD_RecStop();
359 return;
360 }
361
362 }
363
364 /*
365 ==============
366 MVD_RecMulticast
367 ==============
368 */
MVD_RecMulticast(const vec3_t origin,multicast_t to)369 void MVD_RecMulticast( const vec3_t origin, multicast_t to ) {
370 sizebuf_t *buf;
371 int size;
372
373 /* ignore milticasts if paused */
374 if( !rec.recording || rec.paused ) {
375 return;
376 }
377
378 size = ( to << 12 ) | ( msg_write.cursize & 0xFFF );
379
380 buf = &rec.multicast;
381 SZ_WriteByte( buf, svc_multicast );
382 SZ_WriteShort( buf, size );
383
384 /* write exact position for PVS/PHS checks */
385 if( to != MULTICAST_ALL && to != MULTICAST_ALL_R ) {
386 SZ_WriteExactPos( buf, origin );
387 }
388
389 SZ_Write( buf, msg_write.data, msg_write.cursize );
390 }
391
392 /*
393 ==============
394 MVD_RecUnicast
395
396 NOTE: clientNum should be already ORed with reliable bit
397 ==============
398 */
MVD_RecUnicast(int clientNum)399 void MVD_RecUnicast( int clientNum ) {
400 sizebuf_t *buf;
401
402 /* FIXME: ignore unicasts if paused */
403 if( !rec.recording || rec.paused ) {
404 return;
405 }
406
407 if( !MVD_PlayerIsActive( clientNum & 0x7F ) ) {
408 return;
409 }
410
411 buf = &rec.multicast;
412 SZ_WriteByte( buf, svc_unicast );
413 SZ_WriteByte( buf, clientNum );
414 SZ_WriteShort( buf, msg_write.cursize );
415 SZ_Write( buf, msg_write.data, msg_write.cursize );
416
417 }
418
419 /*
420 ==============
421 MVD_RecConfigstring
422 ==============
423 */
MVD_RecConfigstring(int index,const char * string)424 void MVD_RecConfigstring( int index, const char *string ) {
425 sizebuf_t *buf;
426
427 if( !rec.recording ) {
428 return;
429 }
430 if( rec.paused ) {
431 Q_SetBit( rec.dirty_configstrings, index );
432 return;
433 }
434 buf = &rec.multicast;
435 SZ_WriteByte( buf, svc_configstring );
436 SZ_WriteShort( buf, index );
437 SZ_WriteString( buf, string );
438 }
439
440 /*
441 ==============
442 MVD_RecStop
443
444 Stops server MVD recording.
445 ==============
446 */
MVD_RecStop(void)447 void MVD_RecStop( void ) {
448 int i, length;
449
450 if( !rec.recording ) {
451 return;
452 }
453
454 // free baselines
455 for( i = 0; i < SV_BASELINES_CHUNKS; i++ ) {
456 if( rec.baselines[i] ) {
457 Z_Free( rec.baselines[i] );
458 }
459 }
460
461 // FIXME: flush accumulated datagram
462 MVD_RecFlush();
463
464 // write demo EOF marker
465 length = -1;
466 FS_Write( &length, 4, rec.demofile );
467
468 if( rec.cache ) {
469 rec.cache->completed = qtrue;
470 rec.cache->size = FS_RawTell( rec.demofile );
471 }
472
473 FS_FCloseFile( rec.demofile );
474
475 memset( &rec, 0, sizeof( rec ) );
476
477 }
478
479 /*
480 ==============
481 MVD_Record_f
482
483 Begins server MVD recording.
484 Every entity, every playerinfo and every message will be recorded.
485 ==============
486 */
MVD_Record_f(void)487 static void MVD_Record_f( void ) {
488 char buffer[MAX_QPATH];
489 char *name;
490 fileHandle_t demofile;
491 char *string;
492 int i, length;
493 edict_t *ent;
494 entity_state_t *base, **chunk;
495
496 if( Cmd_Argc() < 2 ) {
497 Com_Printf( "Usage: %s [/]<filename>\n", Cmd_Argv( 0 ) );
498 return;
499 }
500
501 if( sv.state != ss_game ) {
502 Com_Printf( "Must be running a game server to record.\n" );
503 return;
504 }
505
506 if( !sv_mvd_enable->integer ) {
507 Com_Printf( "MVD recording disabled on this server.\n" );
508 return;
509 }
510
511 if( rec.recording ) {
512 Com_Printf( "Already recording a local MVD.\n" );
513 return;
514 }
515
516 //
517 // open the demo file
518 //
519 name = Cmd_Argv( 1 );
520 if( name[0] == '/' ) {
521 Q_strncpyz( buffer, name + 1, sizeof( buffer ) );
522 } else {
523 Com_sprintf( buffer, sizeof( buffer ), "demos/%s", name );
524 COM_DefaultExtension( buffer, ".mvd2", sizeof( buffer ) );
525 }
526
527 FS_FOpenFile( buffer, &demofile, FS_MODE_WRITE );
528 if( !demofile ) {
529 Com_EPrintf( "Couldn't open %s for writing\n", buffer );
530 return;
531 }
532
533 rec.demofile = demofile;
534 rec.recording = qtrue;
535 rec.frameNum = 1;
536
537 // setup a buffer for accumulating multicast datagrams
538 SZ_Init( &rec.multicast, rec.multicast_buf, sizeof( rec.multicast_buf ) );
539
540 //
541 // write a single giant message with all the startup info
542 //
543 MSG_WriteByte( svc_serverdata );
544 MSG_WriteLong( PROTOCOL_VERSION_MVD );
545 MSG_WriteLong( svs.spawncount );
546 MSG_WriteByte( ATR_DEMO );
547 string = Cvar_VariableString( "gamedir" );
548 MSG_WriteString( string );
549 MSG_WriteShort( CLIENTNUM_NONE );
550 MSG_WriteString( sv.configstrings[CS_NAME] );
551 MSG_WriteShort( PROTOCOL_VERSION_MVD_MINOR );
552
553 for( i = 0; i < MAX_CONFIGSTRINGS; i++ ) {
554 string = sv.configstrings[i];
555 if( string[0] ) {
556 MSG_WriteByte( svc_configstring );
557 MSG_WriteShort( i );
558 length = strlen( string );
559 if( length > MAX_QPATH ) {
560 length = MAX_QPATH;
561 }
562 MSG_WriteData( string, length );
563 MSG_WriteByte( 0 );
564 }
565 }
566
567 for( i = 1; i < MAX_EDICTS; i++ ) {
568 ent = EDICT_NUM( i );
569
570 if( !ent->s.modelindex && !ent->s.sound && !ent->s.effects ) {
571 continue;
572 }
573
574 ent->s.number = i;
575
576 chunk = &rec.baselines[i >> SV_BASELINES_SHIFT];
577 if( *chunk == NULL ) {
578 *chunk = SV_Malloc( sizeof( *base ) * SV_BASELINES_PER_CHUNK );
579 }
580
581 base = *chunk + ( i & SV_BASELINES_MASK );
582
583 *base = ent->s;
584
585 MSG_WriteByte( svc_spawnbaseline );
586 MSG_WriteDeltaEntity( NULL, base, MSG_ES_FORCE );
587 }
588
589 MSG_WriteByte( svc_stufftext );
590 MSG_WriteString( "precache\n" );
591
592 MVD_RecWrite();
593
594 if( !Q_stricmp( Cmd_Argv( 0 ), "mvdautorecord" ) ) {
595 mvdCacheEntry_t *entry;
596 int length, pathlength;
597 const char *path = FS_GetFileFullPath( demofile );
598
599 rec_sequence++;
600
601 length = strlen( buffer ) + 1;
602 pathlength = strlen( path ) + 1;
603 entry = Z_Malloc( sizeof( *entry ) + length + pathlength );
604 entry->filename = ( char * )( entry + 1 );
605 entry->fullpath = entry->filename + length;
606 strcpy( entry->filename, buffer );
607 strcpy( entry->fullpath, path );
608 entry->size = 0;
609 entry->numFrames = 0;
610 entry->completed = qfalse;
611 entry->time = time( NULL );
612 entry->sequence = rec_sequence;
613
614 List_Append( &rec_cache, entry );
615 rec.cache = entry;
616
617 Com_Printf( "Autorecording local MVD to %s\n", buffer );
618 } else {
619 Com_Printf( "Recording local MVD to %s\n", buffer );
620 }
621 }
622
623
624 /*
625 ==============
626 MVD_Stop_f
627
628 Ends server MVD recording
629 ==============
630 */
MVD_Stop_f(void)631 static void MVD_Stop_f( void ) {
632 if( !rec.recording ) {
633 Com_Printf( "Not recording a local MVD.\n" );
634 return;
635 }
636
637 Com_Printf( "Local MVD recording completed.\n" );
638 MVD_RecStop();
639 }
640
641 /*
642 ==============
643 MVD_ListCache_f
644 ==============
645 */
MVD_ListCache(qboolean fromConsole)646 void MVD_ListCache( qboolean fromConsole ) {
647 mvdCacheEntry_t *entry;
648 listElem_t *elem;
649 int min, sec;
650 int size;
651
652 elem = rec_cache.last;
653 if( elem && !(( mvdCacheEntry_t * )elem)->completed && !fromConsole ) {
654 elem = elem->prev;
655 }
656 if( !elem ) {
657 Com_Printf( "MVD cache is empty.\n" );
658 return;
659 }
660
661 Com_Printf( "+-----+-----------+----------+--------------------------------------------+\n" );
662 Com_Printf( "| # | Size | Duration | Filename |\n" );
663 Com_Printf( "+-----+-----------+----------+--------------------------------------------+\n" );
664 for( ; elem; elem = elem->prev ) {
665 entry = ( mvdCacheEntry_t * )elem;
666
667 if( !fromConsole && !entry->completed ) {
668 continue;
669 }
670
671 Com_Printf( "| %-3d | ", entry->sequence );
672
673 size = entry->size;
674 if( size < 1000 ) {
675 Com_Printf( "%3d bytes ", size );
676 } else if( size < 1000000 ) {
677 Com_Printf( "%3d.%d KB ", size / 1000, ( size / 100 ) % 10 );
678 } else {
679 Com_Printf( "%3d.%d MB ", size / 1000000, ( size / 100000 ) % 10 );
680 }
681 sec = entry->numFrames / 10;
682 min = sec / 60;
683 sec %= 60;
684 Com_Printf( "| %02d:%02d | ", min, sec );
685 if( fromConsole ) {
686 Com_Printf( "%-40.40s", entry->filename );
687 } else {
688 Com_Printf( "%-40.40s", COM_SkipPath( entry->filename ) );
689 }
690 if( !entry->completed ) {
691 Com_Printf( " * |\n" );
692 } else {
693 Com_Printf( " |\n" );
694 }
695 }
696 Com_Printf( "+-----+-----------+----------+--------------------------------------------+\n" );
697 }
698
MVD_ListCache_f(void)699 static void MVD_ListCache_f( void ) {
700 MVD_ListCache( qtrue );
701 }
702
703 /*
704 ==============
705 MVD_ClearCache_f
706 ==============
707 */
MVD_ClearCache_f(void)708 static void MVD_ClearCache_f( void ) {
709 mvdCacheEntry_t *entry;
710 listElem_t *elem, *next;
711 int count;
712
713 if( !rec_cache.first ) {
714 Com_Printf( "MVD cache is empty.\n" );
715 return;
716 }
717
718 count = 0;
719 for( elem = rec_cache.first; elem; elem = next ) {
720 next = elem->next;
721 entry = ( mvdCacheEntry_t * )elem;
722
723 if( !entry->completed ) {
724 Com_Printf( "Stopping MVD autorecord, file requested for removal\n" );
725 MVD_RecStop();
726 }
727
728 if( remove( entry->fullpath ) ) {
729 Com_EPrintf( "Couldn't remove %s\n", entry->filename );
730 } else {
731 Com_Printf( "Removed %s\n", entry->filename );
732 }
733 Z_Free( entry );
734 count++;
735 }
736
737 Com_Printf( "%d files removed\n", count );
738
739 List_Clear( &rec_cache );
740 rec_sequence = 0;
741 }
742
743 /*
744 ==============
745 MVD_FlushCache_f
746 ==============
747 */
MVD_FlushCache_f(void)748 static void MVD_FlushCache_f( void ) {
749 mvdCacheEntry_t *entry;
750 listElem_t *elem, *prev;
751 int size, count;
752
753 if( !rec_cache.last ) {
754 Com_Printf( "MVD cache is empty.\n" );
755 return;
756 }
757
758 size = count = 0;
759 for( elem = rec_cache.last; elem; elem = elem->prev ) {
760 entry = ( mvdCacheEntry_t * )elem;
761
762 size += entry->size;
763 count++;
764 if( mvd_cache_megs->value && size > mvd_cache_megs->value * 1000000 ) {
765 break;
766 }
767 if( mvd_cache_count->integer && count > mvd_cache_count->integer ) {
768 break;
769 }
770 }
771
772 count = 0;
773 for( ; elem; elem = prev ) {
774 prev = elem->prev;
775 List_DeleteElem( elem );
776 entry = ( mvdCacheEntry_t * )elem;
777
778 if( !entry->completed ) {
779 Com_Printf( "Stopping MVD autorecord, file requested for removal\n" );
780 MVD_RecStop();
781 }
782
783 if( remove( entry->fullpath ) ) {
784 Com_EPrintf( "Couldn't remove %s\n", entry->filename );
785 } else {
786 Com_Printf( "Removed %s\n", entry->filename );
787 }
788 Z_Free( entry );
789 count++;
790 }
791
792 Com_Printf( "%d files removed\n", count );
793 }
794
795 /*
796 ==============
797 MVD_Register
798 ==============
799 */
MVD_Register(void)800 void MVD_Register( void ) {
801 mvd_running = Cvar_Get( "mvd_running", "0", CVAR_ROM );
802 mvd_nodelta = Cvar_Get( "mvd_nodelta", "0", 0 );
803 mvd_wait_clients = Cvar_Get( "mvd_wait_clients", "1", 0 );
804 mvd_cache_megs = Cvar_Get( "mvd_cache_megs", "16", 0 );
805 mvd_cache_count = Cvar_Get( "mvd_cache_count", "0", 0 );
806 mvd_max_size = Cvar_Get( "mvd_max_size", "0", 0 );
807 mvd_max_duration = Cvar_Get( "mvd_max_duration", "0", 0 );
808 mvd_shownet = Cvar_Get( "mvd_shownet", "0", 0 );
809 mvd_debug = Cvar_Get( "mvd_debug", "0", 0 );
810 mvd_buffer_size = Cvar_Get( "mvd_buffer_size", "16", CVAR_LATCH );
811 mvd_pause = Cvar_Get( "mvd_pause", "0", 0 );
812 mvd_noblend = Cvar_Get( "mvd_noblend", "0", CVAR_LATCH );
813 mvd_nogun = Cvar_Get( "mvd_nogun", "1", CVAR_LATCH );
814 mvd_motd = Cvar_Get( "mvd_motd", "Hello ${sv_client}!\\n"
815 "This is an experimental MVD server!", 0 );
816 mvd_custom_fov = Cvar_Get( "mvd_custom_fov", "1", 0 );
817 mvd_nextserver = Cvar_Get( "mvd_nextserver", "1", 0 );
818 mvd_timeout = Cvar_Get( "mvd_timeout", "120", 0 );
819 mvd_admin_password = Cvar_Get( "mvd_admin_password", "", CVAR_PRIVATE );
820 mvd_flood_msgs = Cvar_Get( "flood_msgs", "4", 0 );
821 mvd_flood_persecond = Cvar_Get( "flood_persecond", "4", 0 ); // FIXME: rename this
822 mvd_flood_waitdelay = Cvar_Get( "flood_waitdelay", "10", 0 );
823 mvd_autoscores = Cvar_Get( "mvd_autoscores", "", 0 );
824 mvd_safecmd = Cvar_Get( "mvd_safecmd", "", 0 );
825
826 /* register some client variables q2admin might expect to be set */
827 Cvar_Get( "cl_yawspeed", "140", CVAR_USER_CREATED );
828 Cvar_Get( "cl_pitchspeed", "150", CVAR_USER_CREATED );
829 Cvar_Get( "cl_anglespeedkey", "1.5", CVAR_USER_CREATED );
830 Cvar_Get( "cl_maxfps", "30", CVAR_USER_CREATED );
831
832 Cmd_AddCommandEx( "mvdrecord", MVD_Record_f, MVD_Play_g );
833 Cmd_AddCommandEx( "mvdautorecord", MVD_Record_f, MVD_Play_g );
834 Cmd_AddCommand( "mvdstop", MVD_Stop_f );
835 Cmd_AddCommandEx( "mvdstreamrecord", MVD_StreamedRecord_f, MVD_Play_g );
836 Cmd_AddCommand( "mvdstreamstop", MVD_StreamedStop_f );
837 Cmd_AddCommand( "mvdlist", MVD_ListCache_f );
838 Cmd_AddCommand( "mvdclear", MVD_ClearCache_f );
839 Cmd_AddCommand( "mvdflush", MVD_FlushCache_f );
840 Cmd_AddCommandEx( "mvdplay", MVD_Play_f, MVD_Play_g );
841 Cmd_AddCommand( "mvdconnect", MVD_Connect_f );
842 Cmd_AddCommand( "mvdisconnect", MVD_Disconnect_f );
843 Cmd_AddCommand( "mvdjump", MVD_Jump_f );
844 Cmd_AddCommand( "mvdcmd", MVD_ForwardToServer_f );
845
846 #ifndef DEDICATED_ONLY
847 if( !dedicated->integer ) {
848 Cmd_AddCommand( "+mvdback", MVD_BackwardsDown_f );
849 Cmd_AddCommand( "-mvdback", MVD_BackwardsUp_f );
850 }
851 #endif
852
853 SZ_Init( &mvd_buffer.text, mvd_buffer_text, sizeof( mvd_buffer_text ) );
854 mvd_buffer.exec = MVD_ExecuteString;
855
856 }
857
858
859
860