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