diff options
author | Andrey Nazarov <skuller@skuller.net> | 2008-10-03 12:49:06 +0000 |
---|---|---|
committer | Andrey Nazarov <skuller@skuller.net> | 2008-10-03 12:49:06 +0000 |
commit | f8abe42a0d1a42653b39f6cf320d3fbdd1279bb3 (patch) | |
tree | 4e56e3647a4f9933f71c5f2cc03b4fa40bfc7709 /source | |
parent | 3fb2508c6c5976d418c377847bba1037fa843fff (diff) |
Consolidated all MVD server code in a single file for easier portability.
Perform more strict configstring length checks server side.
No longer stop reading packets in a loop after an ICMP error.
Diffstat (limited to 'source')
-rw-r--r-- | source/cl_main.c | 10 | ||||
-rw-r--r-- | source/net_common.c | 13 | ||||
-rw-r--r-- | source/protocol.h | 3 | ||||
-rw-r--r-- | source/q_msg.c | 4 | ||||
-rw-r--r-- | source/sv_game.c | 108 | ||||
-rw-r--r-- | source/sv_http.c | 2 | ||||
-rw-r--r-- | source/sv_init.c | 23 | ||||
-rw-r--r-- | source/sv_local.h | 43 | ||||
-rw-r--r-- | source/sv_main.c | 41 | ||||
-rw-r--r-- | source/sv_mvd.c | 1588 | ||||
-rw-r--r-- | source/sv_send.c | 9 |
11 files changed, 987 insertions, 857 deletions
diff --git a/source/cl_main.c b/source/cl_main.c index 86d8246..8d2da00 100644 --- a/source/cl_main.c +++ b/source/cl_main.c @@ -2852,14 +2852,10 @@ void CL_ProcessEvents( void ) { CL_PacketEvent( NET_OK ); } - do { - ret = NET_GetPacket( NS_CLIENT ); - if( ret == NET_AGAIN ) { - break; - } + // process network packets + while( ( ret = NET_GetPacket( NS_CLIENT ) ) != NET_AGAIN ) { CL_PacketEvent( ret ); - } while( ret == NET_OK ); - + } } //============================================================================ diff --git a/source/net_common.c b/source/net_common.c index 3fbd1ec..c522d10 100644 --- a/source/net_common.c +++ b/source/net_common.c @@ -410,6 +410,7 @@ NET_GetPacket Fills msg_read_buffer with packet contents, net_from variable receives source address. +Returns NET_ERROR only in case of ICMP error. ============= */ neterr_t NET_GetPacket( netsrc_t sock ) { @@ -500,15 +501,21 @@ neterr_t NET_GetPacket( netsrc_t sock ) { /* ============= NET_SendPacket + +Returns NET_ERROR only in case of ICMP error. ============= */ neterr_t NET_SendPacket( netsrc_t sock, const netadr_t *to, size_t length, const void *data ) { struct sockaddr_in addr; int ret; - if( length < 1 || length > MAX_PACKETLEN ) { - Com_WPrintf( "%s: bad length: %"PRIz" bytes\n", __func__, length ); - return NET_ERROR; + if( !length ) { + return NET_AGAIN; + } + + if( length > MAX_PACKETLEN ) { + Com_WPrintf( "%s: oversize length: %"PRIz" bytes\n", __func__, length ); + return NET_AGAIN; } switch( to->type ) { diff --git a/source/protocol.h b/source/protocol.h index 581ecc3..517e6ce 100644 --- a/source/protocol.h +++ b/source/protocol.h @@ -238,7 +238,8 @@ typedef enum clc_ops_e { #define PPS_STATS (1<<14) #define PPS_REMOVE (1<<15) -#define PPS_NUM( ps ) (ps)->pmove.pm_flags +// this is just a small hack to store inuse flag +// in a field left otherwise unused by MVD code #define PPS_INUSE( ps ) (ps)->pmove.pm_time //============================================== diff --git a/source/q_msg.c b/source/q_msg.c index 15a59b7..c1ca775 100644 --- a/source/q_msg.c +++ b/source/q_msg.c @@ -617,7 +617,7 @@ void MSG_WriteDeltaEntity( const entity_state_t *from, return; // nothing to send! if( flags & MSG_ES_REMOVE ) { - bits |= U_REMOVE; + bits |= U_REMOVE; // used for MVD stream only } //---------- @@ -1304,7 +1304,7 @@ void MSG_WriteDeltaPlayerstate_Packet( const player_state_t *from, } if( flags & MSG_PS_REMOVE ) { - pflags |= PPS_REMOVE; + pflags |= PPS_REMOVE; // used for MVD stream only } // diff --git a/source/sv_game.c b/source/sv_game.c index 355af5e..9051e7a 100644 --- a/source/sv_game.c +++ b/source/sv_game.c @@ -76,9 +76,7 @@ Archived in MVD stream. */ static void PF_Unicast( edict_t *ent, qboolean reliable ) { client_t *client; - int flags, clientNum; - svc_ops_t op; - sizebuf_t *buf; + int clientNum; if( !ent ) { goto clear; @@ -86,58 +84,23 @@ static void PF_Unicast( edict_t *ent, qboolean reliable ) { clientNum = NUM_FOR_EDICT( ent ) - 1; if( clientNum < 0 || clientNum >= sv_maxclients->integer ) { - Com_WPrintf( "PF_Unicast to a non-client %d\n", clientNum ); + Com_WPrintf( "%s to a non-client %d\n", __func__, clientNum ); goto clear; } client = svs.udp_client_pool + clientNum; if( client->state <= cs_zombie ) { - Com_WPrintf( "PF_Unicast to a free/zombie client %d\n", clientNum ); + Com_WPrintf( "%s to a free/zombie client %d\n", __func__, clientNum ); goto clear; } - if( reliable ) { - flags = MSG_RELIABLE; - op = mvd_unicast_r; - buf = &sv.mvd.message; - } else { - flags = 0; - op = mvd_unicast; - buf = &sv.mvd.datagram; - } + SV_ClientAddMessage( client, reliable ? MSG_RELIABLE : 0 ); - // HACK: fix anti-kicking exploit if( msg_write.data[0] == svc_disconnect ) { - SV_ClientAddMessage( client, flags ); + // fix anti-kicking exploit for broken mods client->flags |= CF_DROP; - goto clear; - } - - if( client == svs.mvd.dummy ) { - if( msg_write.data[0] == svc_stufftext && - memcmp( msg_write.data + 1, "play ", 5 ) ) - { - // let MVD client process this internally - SV_ClientAddMessage( client, flags ); - } else if( sv.mvd.paused < PAUSED_FRAMES ) { - // send this to all observers - if( msg_write.data[0] == svc_layout ) { - sv.mvd.layout_time = svs.realtime; - } - SV_MvdUnicast( buf, clientNum, op ); - } - } else { - SV_ClientAddMessage( client, flags ); - if( svs.mvd.dummy && sv.mvd.paused < PAUSED_FRAMES && - SV_MvdPlayerIsActive( ent ) ) - { - if( msg_write.data[0] != svc_layout && - ( msg_write.data[0] != svc_stufftext || - !memcmp( msg_write.data + 1, "play ", 5 ) ) ) - { - SV_MvdUnicast( buf, clientNum, op ); - } - } + } else { + SV_MvdUnicast( ent, clientNum, reliable ); } clear: @@ -168,9 +131,7 @@ static void PF_bprintf( int level, const char *fmt, ... ) { return; } - if( svs.mvd.dummy && sv.mvd.paused < PAUSED_FRAMES ) { - SV_MvdBroadcastPrint( level, string ); - } + SV_MvdBroadcastPrint( level, string ); MSG_WriteByte( svc_print ); MSG_WriteByte( level ); @@ -263,11 +224,7 @@ static void PF_cprintf( edict_t *ent, int level, const char *fmt, ... ) { SV_ClientAddMessage( client, MSG_RELIABLE ); } - if( svs.mvd.dummy && sv.mvd.paused < PAUSED_FRAMES && - ( client == svs.mvd.dummy || SV_MvdPlayerIsActive( ent ) ) ) - { - SV_MvdUnicast( &sv.mvd.message, clientNum, mvd_unicast_r ); - } + SV_MvdUnicast( ent, clientNum, qtrue ); SZ_Clear( &msg_write ); } @@ -369,7 +326,7 @@ Archived in MVD stream. =============== */ void PF_Configstring( int index, const char *val ) { - size_t length, maxlength; + size_t len, maxlen; client_t *client; if( index < 0 || index >= MAX_CONFIGSTRINGS ) @@ -378,11 +335,12 @@ void PF_Configstring( int index, const char *val ) { if( !val ) val = ""; - length = strlen( val ); - maxlength = sizeof( sv.configstrings ) - MAX_QPATH * index; - if( length >= maxlength ) { - Com_Error( ERR_DROP, "%s: index %d overflowed: %"PRIz" > %"PRIz"\n", __func__, - index, length, maxlength - 1 ); + len = strlen( val ); + maxlen = CS_SIZE( index ); + if( len >= maxlen ) { + Com_Error( ERR_DROP, + "%s: index %d overflowed: %"PRIz" > %"PRIz"\n", + __func__, index, len, maxlen - 1 ); } if( !strcmp( sv.configstrings[index], val ) ) { @@ -390,20 +348,18 @@ void PF_Configstring( int index, const char *val ) { } // change the string in sv - memcpy( sv.configstrings[index], val, length + 1 ); + memcpy( sv.configstrings[index], val, len + 1 ); if( sv.state == ss_loading ) { return; } - if( svs.mvd.dummy ) { - SV_MvdConfigstring( index, val ); - } + SV_MvdConfigstring( index, val ); // send the update to everyone MSG_WriteByte( svc_configstring ); MSG_WriteShort( index ); - MSG_WriteData( val, length + 1 ); + MSG_WriteData( val, len + 1 ); FOR_EACH_CLIENT( client ) { if( client->state < cs_primed ) { @@ -630,30 +586,8 @@ static void PF_StartSound( edict_t *edict, int channel, client->msg_bytes += MAX_SOUND_PACKET; } - // FIXME: what about SVF_NOCLIENT entities? removed entities? - if( svs.mvd.dummy && sv.mvd.paused < PAUSED_FRAMES ) { - int extrabits = 0; - - if( channel & CHAN_NO_PHS_ADD ) { - extrabits |= 1 << SVCMD_BITS; - } - if( channel & CHAN_RELIABLE ) { - extrabits |= 2 << SVCMD_BITS; - } - - SZ_WriteByte( &sv.mvd.datagram, mvd_sound | extrabits ); - SZ_WriteByte( &sv.mvd.datagram, flags ); - SZ_WriteByte( &sv.mvd.datagram, soundindex ); - - if( flags & SND_VOLUME ) - SZ_WriteByte( &sv.mvd.datagram, volume * 255 ); - if( flags & SND_ATTENUATION ) - SZ_WriteByte( &sv.mvd.datagram, attenuation * 64 ); - if( flags & SND_OFFSET ) - SZ_WriteByte( &sv.mvd.datagram, timeofs * 1000 ); - - SZ_WriteShort( &sv.mvd.datagram, sendchan ); - } + SV_MvdStartSound( ent, channel, flags, soundindex, + volume * 255, attenuation * 64, timeofs * 1000 ); } static void PF_PositionedSound( vec3_t origin, edict_t *entity, int channel, diff --git a/source/sv_http.c b/source/sv_http.c index 56fa0a9..58fb210 100644 --- a/source/sv_http.c +++ b/source/sv_http.c @@ -107,7 +107,7 @@ static void SV_GetStatus( void ) { if( http_client->method == HTTP_METHOD_HEAD ) { SV_HttpPrintf( "\r\n" ); - SV_HttpDrop( http_client, "200 OK " ); + SV_HttpDrop( http_client, "200 OK" ); return; } diff --git a/source/sv_init.c b/source/sv_init.c index 2164859..49283a5 100644 --- a/source/sv_init.c +++ b/source/sv_init.c @@ -63,12 +63,6 @@ static void SV_SpawnServer( cm_t *cm, const char *server, const char *spawnpoint // reset entity counter svs.nextEntityStates = 0; - if( sv_mvd_enable->integer ) { - // setup buffers for accumulating datagrams - SZ_Init( &sv.mvd.message, svs.mvd.message_data, MAX_MSGLEN ); - SZ_Init( &sv.mvd.datagram, svs.mvd.datagram_data, MAX_MSGLEN ); - } - // save name for levels that don't set message Q_strlcpy( sv.configstrings[CS_NAME], server, MAX_QPATH ); Q_strlcpy( sv.name, server, sizeof( sv.name ) ); @@ -221,19 +215,9 @@ void SV_InitGame( qboolean ismvd ){ svs.numEntityStates = sv_maxclients->integer * UPDATE_BACKUP * MAX_PACKET_ENTITIES; svs.entityStates = SV_Mallocz( sizeof( entity_state_t ) * svs.numEntityStates ); - // allocate MVD recorder buffers - if( !ismvd && sv_mvd_enable->integer ) { - Z_TagReserve( sizeof( player_state_t ) * sv_maxclients->integer + - sizeof( entity_state_t ) * MAX_EDICTS + MAX_MSGLEN * 2, TAG_SERVER ); - svs.mvd.message_data = Z_ReservedAlloc( MAX_MSGLEN ); - svs.mvd.datagram_data = Z_ReservedAlloc( MAX_MSGLEN ); - svs.mvd.players = Z_ReservedAlloc( sizeof( player_state_t ) * sv_maxclients->integer ); - svs.mvd.entities = Z_ReservedAlloc( sizeof( entity_state_t ) * MAX_EDICTS ); - - // reserve the slot for dummy MVD client - if( !sv_reserved_slots->integer ) { - Cvar_Set( "sv_reserved_slots", "1" ); - } + // initialize MVD server + if( !ismvd ) { + SV_MvdInit(); } Cvar_ClampInteger( sv_http_minclients, 0, sv_http_maxclients->integer ); @@ -264,7 +248,6 @@ void SV_InitGame( qboolean ismvd ){ SV_RateInit( &svs.ratelimit_badrcon, 1, sv_badauth_time->value * 1000 ); List_Init( &svs.udp_client_list ); - List_Init( &svs.mvd.clients ); List_Init( &svs.tcp_client_list ); List_Init( &svs.tcp_client_pool ); List_Init( &svs.console_list ); diff --git a/source/sv_local.h b/source/sv_local.h index 783f474..cebfade 100644 --- a/source/sv_local.h +++ b/source/sv_local.h @@ -92,14 +92,6 @@ typedef struct { server_entity_t entities[MAX_EDICTS]; - struct { - int paused; - unsigned layout_time; - sizebuf_t datagram; - sizebuf_t message; // reliable - byte dcs[CS_BITMAP_BYTES]; - } mvd; - unsigned tracecount; } server_t; @@ -371,19 +363,6 @@ typedef struct server_static_s { list_t tcp_client_pool; list_t tcp_client_list; - struct { - list_t clients; - client_t *dummy; - byte *message_data; - byte *datagram_data; - player_state_t *players; // [maxclients] - entity_state_t *entities; // [MAX_EDICTS] - - fileHandle_t recording; - int numlevels; // stop after that many levels - int numframes; // stop after that many frames - } mvd; - list_t console_list; #if USE_ZLIB @@ -470,6 +449,7 @@ extern edict_t *sv_player; // void SV_DropClient( client_t *drop, const char *reason ); void SV_RemoveClient( client_t *client ); +void SV_CleanClient( client_t *client ); void SV_InitOperatorCommands (void); @@ -541,19 +521,22 @@ extern cvar_t *sv_mvd_noblend; extern cvar_t *sv_mvd_nogun; void SV_MvdRegister( void ); -void SV_MvdBeginFrame( void ); -void SV_MvdEndFrame( void ); -void SV_MvdUnicast( sizebuf_t *buf, int clientNum, mvd_ops_t op ); -void SV_MvdMulticast( sizebuf_t *buf, int leafnum, mvd_ops_t op ); +void SV_MvdInit( void ); +void SV_MvdShutdown( void ) ; +void SV_MvdRunFrame( void ); +void SV_MvdMapChanged( void ); +void SV_MvdClientDropped( client_t *client, const char *reason ); + +void SV_MvdUnicast( edict_t *ent, int clientNum, qboolean reliable ); +void SV_MvdMulticast( int leafnum, multicast_t to ); void SV_MvdConfigstring( int index, const char *string ); void SV_MvdBroadcastPrint( int level, const char *string ); -void SV_MvdRecStop( void ); -qboolean SV_MvdPlayerIsActive( const edict_t *ent ); +void SV_MvdStartSound( int entnum, int channel, int flags, + int soundindex, int volume, + int attenuation, int timeofs ); + void SV_MvdInitStream( void ); void SV_MvdGetStream( const char *uri ); -void SV_MvdMapChanged( void ); -void SV_MvdRemoveDummy( void ); -void SV_MvdDropDummy( const char *reason ); // // sv_http.c diff --git a/source/sv_main.c b/source/sv_main.c index fc11635..cdb0147 100644 --- a/source/sv_main.c +++ b/source/sv_main.c @@ -211,9 +211,8 @@ void SV_DropClient( client_t *client, const char *reason ) { Com_DPrintf( "Going to cs_zombie for %s\n", client->name ); - if( client == svs.mvd.dummy ) { - SV_MvdDropDummy( reason ); - } + // give MVD server a chance to detect if it's dummy client was dropped + SV_MvdClientDropped( client, reason ); } @@ -1034,7 +1033,7 @@ static void SV_ConnectionlessPacket( void ) { len = MSG_ReadStringLine( string, sizeof( string ) ); if( len >= sizeof( string ) ) { - Com_DPrintf( "ignored oversize message\n" ); + Com_DPrintf( "ignored oversize connectionless packet\n" ); return; } @@ -1268,13 +1267,9 @@ void SV_ProcessEvents( void ) { #endif // process network packets - do { - ret = NET_GetPacket( NS_SERVER ); - if( ret == NET_AGAIN ) { - break; - } + while( ( ret = NET_GetPacket( NS_SERVER ) ) != NET_AGAIN ) { SV_PacketEvent( ret ); - } while( ret == NET_OK ); + } } @@ -1435,10 +1430,6 @@ static void SV_RunGameFrame( void ) { sv.framenum++; sv.frametime -= 100; - if( svs.mvd.dummy ) { - SV_MvdBeginFrame(); - } - ge->RunFrame(); if( msg_write.cursize ) { @@ -1449,9 +1440,7 @@ static void SV_RunGameFrame( void ) { } // save the entire world state if recording a serverdemo - if( svs.mvd.dummy ) { - SV_MvdEndFrame(); - } + SV_MvdRunFrame(); #if USE_CLIENT if( host_speeds->integer ) @@ -1888,7 +1877,6 @@ static void SV_FinalMessage( const char *message, int cmd ) { client_t *client, *next; tcpClient_t *t, *tnext; netchan_t *netchan; - uint16_t length; int i; MSG_WriteByte( svc_print ); @@ -1913,13 +1901,6 @@ static void SV_FinalMessage( const char *message, int cmd ) { SZ_Clear( &msg_write ); - // send EOF to MVD clients - length = 0; - LIST_FOR_EACH( tcpClient_t, t, &svs.mvd.clients, mvdEntry ) { - SV_HttpWrite( t, &length, 2 ); - SV_HttpFinish( t ); - } - // free any data dynamically allocated LIST_FOR_EACH_SAFE( client_t, client, next, &svs.udp_client_list, entry ) { if( client->state != cs_zombie ) { @@ -1940,12 +1921,6 @@ static void SV_FinalMessage( const char *message, int cmd ) { LIST_FOR_EACH_SAFE( tcpClient_t, t, tnext, &svs.tcp_client_pool, entry ) { Z_Free( t ); } - - if( svs.mvd.dummy ) { - SV_CleanClient( svs.mvd.dummy ); - SV_RemoveClient( svs.mvd.dummy ); - svs.mvd.dummy = NULL; - } } @@ -1970,7 +1945,8 @@ void SV_Shutdown( const char *finalmsg, killtype_t type ) { AC_Disconnect(); #endif - SV_MvdRecStop(); + // shutdown MVD server + SV_MvdShutdown(); if( type == KILL_RESTART ) { SV_FinalMessage( finalmsg, svc_reconnect ); @@ -1991,7 +1967,6 @@ void SV_Shutdown( const char *finalmsg, killtype_t type ) { // free server static data Z_Free( svs.udp_client_pool ); Z_Free( svs.entityStates ); - Z_Free( svs.mvd.message_data ); #if USE_ZLIB deflateEnd( &svs.z ); #endif diff --git a/source/sv_mvd.c b/source/sv_mvd.c index d664bab..28f6a68 100644 --- a/source/sv_mvd.c +++ b/source/sv_mvd.c @@ -23,246 +23,92 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // #include "sv_local.h" -#include "mvd_local.h" -cvar_t *sv_mvd_enable; +cvar_t *sv_mvd_enable; cvar_t *sv_mvd_auth; -//cvar_t *sv_mvd_wait; -cvar_t *sv_mvd_noblend; -cvar_t *sv_mvd_nogun; -cvar_t *sv_mvd_max_size; -cvar_t *sv_mvd_max_duration; -cvar_t *sv_mvd_max_levels; -cvar_t *sv_mvd_begincmd; -cvar_t *sv_mvd_scorecmd; -cvar_t *sv_mvd_autorecord; -#if USE_ZLIB -cvar_t *sv_mvd_encoding; -#endif -cvar_t *sv_mvd_capture_flags; - -static cmdbuf_t dummy_buffer; -static char dummy_buffer_text[MAX_STRING_CHARS]; - -static qboolean SV_MvdCreateDummy( void ); - -static qboolean SV_MvdRecAllow( void ); -static void SV_MvdRecStart( fileHandle_t demofile ); +cvar_t *sv_mvd_noblend; +cvar_t *sv_mvd_nogun; +cvar_t *sv_mvd_nomsgs; -/* -================== -SV_MvdPlayerIsActive +typedef struct { + client_t *dummy; + unsigned layout_time; -Attempts to determine if the given player entity is active, -e.g. the given player state is to be captured into MVD stream. - -Ideally a compatible game DLL should provide us with information -whether given player is to be captured, instead of relying on -this stupid and complex hack. -================== -*/ -qboolean SV_MvdPlayerIsActive( const edict_t *ent ) { - int num; - - if( !ent->inuse ) { - return qfalse; - } - - // not a client at all? - if( !ent->client ) { - return qfalse; - } - - num = NUM_FOR_EDICT( ent ) - 1; - if( num < 0 || num >= sv_maxclients->integer ) { - return qfalse; - } + // reliable data, may not be discarded + sizebuf_t message; - // check if client is actually connected (default) - if( sv_mvd_capture_flags->integer & 1 ) { - if( svs.udp_client_pool[num].state != cs_spawned ) { - return qfalse; - } - } + // unreliable data, may be discarded + sizebuf_t datagram; - // first of all, make sure player_state_t is valid - if( !ent->client->ps.fov ) { - return qfalse; - } + // delta compressor buffers + player_state_t *players; // [maxclients] + entity_state_t *entities; // [MAX_EDICTS] - // always capture dummy MVD client - if( svs.mvd.dummy && ent == svs.mvd.dummy->edict ) { - return qtrue; - } + // local recorder + fileHandle_t recording; + int numlevels; // stop after that many levels + int numframes; // stop after that many frames +} mvd_server_t; - // never capture spectators - if( ent->client->ps.pmove.pm_type == PM_SPECTATOR ) { - return qfalse; - } +static mvd_server_t mvd; - // check entity visibility - if( ( ent->svflags & SVF_NOCLIENT ) || !ES_INUSE( &ent->s ) ) { - // never capture invisible entities - if( sv_mvd_capture_flags->integer & 2 ) { - return qfalse; - } - } else { - // always capture visible entities (default) - if( sv_mvd_capture_flags->integer & 4 ) { - return qtrue; - } - } +// TCP client list +static LIST_DECL( mvd_client_list ); - // they are likely following someone in case of PM_FREEZE - if( ent->client->ps.pmove.pm_type == PM_FREEZE ) { - return qfalse; - } +static cvar_t *sv_mvd_max_size; +static cvar_t *sv_mvd_max_duration; +static cvar_t *sv_mvd_max_levels; +static cvar_t *sv_mvd_begincmd; +static cvar_t *sv_mvd_scorecmd; +static cvar_t *sv_mvd_autorecord; +#if USE_ZLIB +static cvar_t *sv_mvd_encoding; +#endif +static cvar_t *sv_mvd_capture_flags; - // they are likely following someone if PMF_NO_PREDICTION is set - if( ent->client->ps.pmove.pm_flags & PMF_NO_PREDICTION ) { - return qfalse; - } +static qboolean init_mvd( void ); - return qtrue; -} +static void rec_stop( void ); +static qboolean rec_allowed( void ); +static void rec_start( fileHandle_t demofile ); -static void SV_MvdCopyEntity( entity_state_t *dst, const entity_state_t *src, int flags ) { - if( !( flags & MSG_ES_FIRSTPERSON ) ) { - VectorCopy( src->origin, dst->origin ); - VectorCopy( src->angles, dst->angles ); - VectorCopy( src->old_origin, dst->old_origin ); - } - dst->modelindex = src->modelindex; - dst->modelindex2 = src->modelindex2; - dst->modelindex3 = src->modelindex3; - dst->modelindex4 = src->modelindex4; - dst->frame = src->frame; - dst->skinnum = src->skinnum; - dst->effects = src->effects; - dst->renderfx = src->renderfx; - dst->solid = src->solid; - dst->sound = src->sound; - dst->event = 0; -} /* -================== -SV_MvdBuildFrame - -Builds new MVD frame by capturing all entity and player states -and calculating portalbits. The same frame is used for all MVD -clients, as well as local recorder. -================== -*/ -static void SV_MvdEmitFrame( void ) { - player_state_t *oldps, *newps; - entity_state_t *oldes, *newes; - edict_t *ent; - int flags, portalbytes; - byte portalbits[MAX_MAP_AREAS/8]; - int i; - - MSG_WriteByte( mvd_frame ); - - portalbytes = CM_WritePortalBits( &sv.cm, portalbits ); - MSG_WriteByte( portalbytes ); - MSG_WriteData( portalbits, portalbytes ); - - flags = MSG_PS_IGNORE_PREDICTION|MSG_PS_IGNORE_DELTAANGLES; - if( sv_mvd_noblend->integer ) { - flags |= MSG_PS_IGNORE_BLEND; - } - if( sv_mvd_nogun->integer ) { - flags |= MSG_PS_IGNORE_GUNINDEX|MSG_PS_IGNORE_GUNFRAMES; - } - - for( i = 0; i < sv_maxclients->integer; i++ ) { - ent = EDICT_NUM( i + 1 ); - - oldps = &svs.mvd.players[i]; - newps = &ent->client->ps; - - if( !SV_MvdPlayerIsActive( ent ) ) { - if( PPS_INUSE( oldps ) ) { - // the old player isn't present in the new message - MSG_WriteDeltaPlayerstate_Packet( NULL, NULL, i, flags ); - PPS_INUSE( oldps ) = qfalse; - } - continue; - } - - if( PPS_INUSE( oldps ) ) { - // delta update from old position - // because the force parm is false, this will not result - // in any bytes being emited if the player has not changed at all - MSG_WriteDeltaPlayerstate_Packet( oldps, newps, i, flags ); - } else { - // this is a new player, send it from the last state - MSG_WriteDeltaPlayerstate_Packet( oldps, newps, i, - flags | MSG_PS_FORCE ); - } - - *oldps = *newps; - PPS_INUSE( oldps ) = qtrue; - } +============================================================================== - MSG_WriteByte( CLIENTNUM_NONE ); // end of packetplayers - - for( i = 1; i < ge->num_edicts; i++ ) { - ent = EDICT_NUM( i ); - - oldes = &svs.mvd.entities[i]; - newes = &ent->s; - - if( ( ent->svflags & SVF_NOCLIENT ) || !ES_INUSE( newes ) ) { - if( oldes->number ) { - // the old entity isn't present in the new message - MSG_WriteDeltaEntity( oldes, NULL, MSG_ES_FORCE ); - oldes->number = 0; - } - continue; - } +DUMMY MVD CLIENT - flags = 0; - if( i <= sv_maxclients->integer ) { - oldps = &svs.mvd.players[ i - 1 ]; - if( PPS_INUSE( oldps ) && oldps->pmove.pm_type == PM_NORMAL ) { - // do not waste bandwidth on origin/angle updates, - // client will recover them from player state - flags |= MSG_ES_FIRSTPERSON; - } - } - - if( !oldes->number ) { - // this is a new entity, send it from the last state - flags |= MSG_ES_FORCE|MSG_ES_NEWENTITY; - } - - MSG_WriteDeltaEntity( oldes, newes, flags ); +MVD dummy is a fake client maintained entirely server side. +Representing MVD observers, this client is used to obtain base playerstate +for freefloat observers, receive scoreboard updates and text messages, etc. - SV_MvdCopyEntity( oldes, newes, flags ); - oldes->number = i; - } +============================================================================== +*/ - MSG_WriteShort( 0 ); // end of packetentities -} +static cmdbuf_t dummy_buffer; +static char dummy_buffer_text[MAX_STRING_CHARS]; -static void SV_DummyWait_f( void ) { +static void dummy_wait_f( void ) { int count = atoi( Cmd_Argv( 1 ) ); if( count < 1 ) { count = 1; } - dummy_buffer.waitCount = count; + dummy_buffer.waitCount = count; } -static void SV_DummyForward_f( void ) { +static void dummy_forward_f( void ) { Cmd_Shift(); Com_DPrintf( "dummy cmd: %s %s\n", Cmd_Argv( 0 ), Cmd_Args() ); - ge->ClientCommand( svs.mvd.dummy->edict ); + + sv_client = mvd.dummy; + sv_player = sv_client->edict; + ge->ClientCommand( sv_player ); + sv_client = NULL; + sv_player = NULL; } -static void SV_DummyRecord_f( void ) { +static void dummy_record_f( void ) { char buffer[MAX_OSPATH]; fileHandle_t demofile; size_t len; @@ -276,108 +122,108 @@ static void SV_DummyRecord_f( void ) { return; } - if( !SV_MvdRecAllow() ) { + if( !rec_allowed() ) { return; } - len = Q_concat( buffer, sizeof( buffer ), "demos/", Cmd_Argv( 1 ), ".mvd2", NULL ); + len = Q_concat( buffer, sizeof( buffer ), "demos/", Cmd_Argv( 1 ), ".mvd2", NULL ); if( len >= sizeof( buffer ) ) { Com_EPrintf( "Oversize filename specified.\n" ); return; } - FS_FOpenFile( buffer, &demofile, FS_MODE_WRITE ); - if( !demofile ) { - Com_EPrintf( "Couldn't open %s for writing\n", buffer ); - return; - } + FS_FOpenFile( buffer, &demofile, FS_MODE_WRITE ); + if( !demofile ) { + Com_EPrintf( "Couldn't open %s for writing\n", buffer ); + return; + } - if( !SV_MvdCreateDummy() ) { + if( !init_mvd() ) { FS_FCloseFile( demofile ); return; } - SV_MvdRecStart( demofile ); + rec_start( demofile ); - Com_Printf( "Auto-recording local MVD to %s\n", buffer ); + Com_Printf( "Auto-recording local MVD to %s\n", buffer ); } -static void SV_DummyStop_f( void ) { +static void dummy_stop_f( void ) { if( !sv_mvd_autorecord->integer ) { return; } - if( !svs.mvd.recording ) { - Com_Printf( "Not recording a local MVD.\n" ); - return; - } + if( !mvd.recording ) { + Com_Printf( "Not recording a local MVD.\n" ); + return; + } - Com_Printf( "Stopped local MVD auto-recording.\n" ); - SV_MvdRecStop(); + Com_Printf( "Stopped local MVD auto-recording.\n" ); + rec_stop(); } static const ucmd_t dummy_cmds[] = { - { "cmd", SV_DummyForward_f }, - { "set", Cvar_Set_f }, - { "alias", Cmd_Alias_f }, - { "play", NULL }, - { "exec", NULL }, - { "wait", SV_DummyWait_f }, - { "record", SV_DummyRecord_f }, - { "stop", SV_DummyStop_f }, - { NULL, NULL } + { "cmd", dummy_forward_f }, + { "set", Cvar_Set_f }, + { "alias", Cmd_Alias_f }, + { "play", NULL }, + { "exec", NULL }, + { "wait", dummy_wait_f }, + { "record", dummy_record_f }, + { "stop", dummy_stop_f }, + { NULL, NULL } }; -static void SV_DummyExecuteString( const char *line ) { - char *cmd, *alias; - const ucmd_t *u; +static void dummy_exec_string( const char *line ) { + char *cmd, *alias; + const ucmd_t *u; cvar_t *v; - if( !line[0] ) { - return; - } - - Cmd_TokenizeString( line, qtrue ); - - cmd = Cmd_Argv( 0 ); - if( !cmd[0] ) { - return; - } - for( u = dummy_cmds; u->name; u++ ) { - if( !strcmp( cmd, u->name ) ) { - if( u->func ) { - u->func(); - } - return; - } - } - - alias = Cmd_AliasCommand( cmd ); - if( alias ) { - if( ++dummy_buffer.aliasCount == ALIAS_LOOP_COUNT ) { - Com_WPrintf( "SV_DummyExecuteString: runaway alias loop\n" ); - return; - } - Cbuf_InsertTextEx( &dummy_buffer, alias ); - return; - } + if( !line[0] ) { + return; + } + + Cmd_TokenizeString( line, qtrue ); + + cmd = Cmd_Argv( 0 ); + if( !cmd[0] ) { + return; + } + for( u = dummy_cmds; u->name; u++ ) { + if( !strcmp( cmd, u->name ) ) { + if( u->func ) { + u->func(); + } + return; + } + } + + alias = Cmd_AliasCommand( cmd ); + if( alias ) { + if( ++dummy_buffer.aliasCount == ALIAS_LOOP_COUNT ) { + Com_WPrintf( "dummy_exec_string: runaway alias loop\n" ); + return; + } + Cbuf_InsertTextEx( &dummy_buffer, alias ); + return; + } v = Cvar_FindVar( cmd ); - if( v ) { + if( v ) { Cvar_Command( v ); - return; - } + return; + } Com_DPrintf( "dummy stufftext: %s\n", line ); - sv_client = svs.mvd.dummy; - sv_player = svs.mvd.dummy->edict; - ge->ClientCommand( sv_player ); - sv_client = NULL; - sv_player = NULL; + sv_client = mvd.dummy; + sv_player = mvd.dummy->edict; + ge->ClientCommand( sv_player ); + sv_client = NULL; + sv_player = NULL; } -static void SV_DummyAddMessage( client_t *client, byte *data, - size_t length, qboolean reliable ) +static void dummy_add_message( client_t *client, byte *data, + size_t length, qboolean reliable ) { if( !length || !reliable ) { return; @@ -385,66 +231,25 @@ static void SV_DummyAddMessage( client_t *client, byte *data, if( data[0] == svc_stufftext ) { data[length] = 0; - Cbuf_AddTextEx( &dummy_buffer, ( char * )( data + 1 ) ); + Cbuf_AddTextEx( &dummy_buffer, ( char * )( data + 1 ) ); return; } } -static void SV_MvdSpawnDummy( void ) { - client_t *c = svs.mvd.dummy; - player_state_t *ps; - entity_state_t *es; - edict_t *ent; - int i; - - if( !c ) { - return; - } - - sv_client = c; - sv_player = c->edict; - +static void dummy_spawn( void ) { + sv_client = mvd.dummy; + sv_player = sv_client->edict; ge->ClientBegin( sv_player ); - - sv_client = NULL; - sv_player = NULL; + sv_client = NULL; + sv_player = NULL; if( sv_mvd_begincmd->string[0] ) { - Cbuf_AddTextEx( &dummy_buffer, sv_mvd_begincmd->string ); + Cbuf_AddTextEx( &dummy_buffer, sv_mvd_begincmd->string ); } - c->state = cs_spawned; - - memset( svs.mvd.players, 0, sizeof( player_state_t ) * sv_maxclients->integer ); - memset( svs.mvd.entities, 0, sizeof( entity_state_t ) * MAX_EDICTS ); - - sv.mvd.layout_time = svs.realtime; - - // set base player states - for( i = 0; i < sv_maxclients->integer; i++ ) { - ent = EDICT_NUM( i + 1 ); - - if( !SV_MvdPlayerIsActive( ent ) ) { - continue; - } - - ps = &svs.mvd.players[i]; - *ps = ent->client->ps; - PPS_INUSE( ps ) = qtrue; - } - - // set base entity states - for( i = 1; i < ge->num_edicts; i++ ) { - ent = EDICT_NUM( i ); - - if( ( ent->svflags & SVF_NOCLIENT ) || !ES_INUSE( &ent->s ) ) { - continue; - } + mvd.layout_time = svs.realtime; - es = &svs.mvd.entities[i]; - *es = ent->s; - es->number = i; - } + mvd.dummy->state = cs_spawned; } static client_t *find_slot( void ) { @@ -471,17 +276,13 @@ static client_t *find_slot( void ) { return NULL; } -static qboolean SV_MvdCreateDummy( void ) { +static qboolean dummy_create( void ) { client_t *newcl; char userinfo[MAX_INFO_STRING]; char *s; qboolean allow; int number; - if( svs.mvd.dummy ) { - return qtrue; // already created - } - // find a free client slot newcl = find_slot(); if( !newcl ) { @@ -489,13 +290,13 @@ static qboolean SV_MvdCreateDummy( void ) { return qfalse; } - memset( newcl, 0, sizeof( *newcl ) ); + memset( newcl, 0, sizeof( *newcl ) ); number = newcl - svs.udp_client_pool; - newcl->number = newcl->slot = number; - newcl->protocol = -1; + newcl->number = newcl->slot = number; + newcl->protocol = -1; newcl->state = cs_connected; - newcl->AddMessage = SV_DummyAddMessage; - newcl->edict = EDICT_NUM( number + 1 ); + newcl->AddMessage = dummy_add_message; + newcl->edict = EDICT_NUM( number + 1 ); newcl->netchan = SV_Mallocz( sizeof( netchan_t ) ); newcl->netchan->remote_address.type = NA_LOOPBACK; @@ -505,297 +306,289 @@ static qboolean SV_MvdCreateDummy( void ) { "\\name\\[MVDSPEC]\\skin\\male/grunt\\mvdspec\\%d\\ip\\loopback", PROTOCOL_VERSION_MVD_CURRENT ); - svs.mvd.dummy = newcl; + mvd.dummy = newcl; - // get the game a chance to reject this connection or modify the userinfo - sv_client = newcl; - sv_player = newcl->edict; - allow = ge->ClientConnect( newcl->edict, userinfo ); + // get the game a chance to reject this connection or modify the userinfo + sv_client = newcl; + sv_player = newcl->edict; + allow = ge->ClientConnect( newcl->edict, userinfo ); sv_client = NULL; sv_player = NULL; - if ( !allow ) { - s = Info_ValueForKey( userinfo, "rejmsg" ); + if ( !allow ) { + s = Info_ValueForKey( userinfo, "rejmsg" ); if( *s ) { Com_EPrintf( "Dummy MVD client rejected by game DLL: %s\n", s ); } - svs.mvd.dummy = NULL; + mvd.dummy = NULL; return qfalse; - } - - // parse some info from the info strings - strcpy( newcl->userinfo, userinfo ); - SV_UserinfoChanged( newcl ); + } - SV_MvdSpawnDummy(); + // parse some info from the info strings + strcpy( newcl->userinfo, userinfo ); + SV_UserinfoChanged( newcl ); return qtrue; } -void SV_MvdDropDummy( const char *reason ) { - tcpClient_t *client; +static void dummy_run( void ) { + usercmd_t cmd; - if( !svs.mvd.dummy ) { - return; + Cbuf_ExecuteEx( &dummy_buffer ); + if( dummy_buffer.waitCount > 0 ) { + dummy_buffer.waitCount--; } - LIST_FOR_EACH( tcpClient_t, client, &svs.mvd.clients, mvdEntry ) { + + // run ClientThink to prevent timeouts, etc + memset( &cmd, 0, sizeof( cmd ) ); + cmd.msec = 100; + sv_client = mvd.dummy; + sv_player = sv_client->edict; + ge->ClientThink( sv_player, &cmd ); + sv_client = NULL; + sv_player = NULL; + + // check if the layout is constantly updated. if not, + // game mod has probably closed the scoreboard, open it again + if( sv_mvd_scorecmd->string[0] ) { + if( svs.realtime - mvd.layout_time > 9000 ) { + Cbuf_AddTextEx( &dummy_buffer, sv_mvd_scorecmd->string ); + mvd.layout_time = svs.realtime; + } + } +} + +static void dummy_drop( const char *reason ) { + tcpClient_t *client; + + rec_stop(); + + LIST_FOR_EACH( tcpClient_t, client, &mvd_client_list, mvdEntry ) { SV_HttpDrop( client, reason ); } - SV_MvdRecStop(); - SV_RemoveClient( svs.mvd.dummy ); - svs.mvd.dummy = NULL; + SV_RemoveClient( mvd.dummy ); + mvd.dummy = NULL; - SZ_Clear( &sv.mvd.datagram ); - SZ_Clear( &sv.mvd.message ); + SZ_Clear( &mvd.datagram ); + SZ_Clear( &mvd.message ); } /* +============================================================================== + +FRAME UPDATES + +As MVD stream operates over reliable transport, there is no concept of +"baselines" and delta compression is always performed from the last +state seen on the map. There is also no support for "nodelta" frames +(except the very first frame sent as part of the gamestate). + +This allows building only one update per frame and multicasting it to +several destinations at once. + +Additional bandwidth savings are performed by filtering out origin and +angles updates on player entities, as MVD client can easily recover them +from corresponding player states, assuming those are kept in sync by the +game mod. This assumption should be generally true for moving players, +as vanilla Q2 server performs PVS/PHS culling for them using origin from +entity states, but not player states. + +============================================================================== +*/ + +/* ================== -SV_MvdBeginFrame +player_is_active -Checks whether there are active clients on server -and pauses/resumes MVD frame builing proccess accordingly. +Attempts to determine if the given player entity is active, +and the given player state should be captured into MVD stream. -On resume, dumps all configstrings clobbered by game DLL -into the multicast buffer. +Entire function is a nasty hack. Ideally a compatible game DLL +should do it for us by providing some SVF_* flag or something. ================== */ -void SV_MvdBeginFrame( void ) { -#if 0 - int i, j; - int index; - size_t length; - client_t *client; - qboolean found; - - if( sv_mvd_wait->integer > 0 ) { - found = qfalse; - if( sv_mvd_wait->integer == 1 ) { - for( i = 1; i <= sv_maxclients->integer; i++ ) { - edict_t *ent = EDICT_NUM( i ); - if( SV_MvdPlayerIsActive( ent ) ) { - found = qtrue; - break; - } - } - } else { - FOR_EACH_CLIENT( client ) { - if( client->state == cs_spawned ) { - found = qtrue; - break; - } - } - } +static qboolean player_is_active( const edict_t *ent ) { + int num; - if( !found ) { - if( sv.mvd.paused == PAUSED_FRAMES ) { - Com_Printf( "MVD stream paused, no active clients.\n" ); - for( i = 0; i < CS_BITMAP_LONGS; i++ ) { - (( uint32_t * )sv.mvd.dcs)[i] = 0; - } - } - sv.mvd.paused++; - return; - } + if( ( g_features->integer & GMF_PROPERINUSE ) && !ent->inuse ) { + return qfalse; } - if( sv.mvd.paused >= PAUSED_FRAMES ) { - for( i = 0; i < CS_BITMAP_LONGS; i++ ) { - if( (( uint32_t * )sv.mvd.dcs)[i] == 0 ) { - continue; - } - index = i << 5; - for( j = 0; j < 32; j++, index++ ) { - if( !Q_IsBitSet( sv.mvd.dcs, index ) ) { - continue; - } - SZ_WriteByte( &sv.mvd.message, svc_configstring ); - SZ_WriteShort( &sv.mvd.message, index ); - length = strlen( sv.configstrings[index] ); - if( length > MAX_QPATH ) { - length = MAX_QPATH; - } - SZ_Write( &sv.mvd.message, sv.configstrings[index], length ); - SZ_WriteByte( &sv.mvd.message, 0 ); - } - } - - Com_Printf( "MVD stream resumed, flushed %"PRIz" bytes.\n", - sv.mvd.message.cursize ); - // will be subsequently written to disk by SV_MvdEndFrame + // not a client at all? + if( !ent->client ) { + return qfalse; } - sv.mvd.paused = 0; -#endif -} - -void SV_MvdEndFrame( void ) { - tcpClient_t *client; - usercmd_t cmd; - int length; - - Cbuf_ExecuteEx( &dummy_buffer ); - if( dummy_buffer.waitCount > 0 ) { - dummy_buffer.waitCount--; + num = NUM_FOR_EDICT( ent ) - 1; + if( num < 0 || num >= sv_maxclients->integer ) { + return qfalse; } - memset( &cmd, 0, sizeof( cmd ) ); - cmd.msec = 100; - ge->ClientThink( svs.mvd.dummy->edict, &cmd ); + // by default, check if client is actually connected + // it may not be the case for bots! + if( sv_mvd_capture_flags->integer & 1 ) { + if( svs.udp_client_pool[num].state != cs_spawned ) { + return qfalse; + } + } - if( sv.mvd.paused >= PAUSED_FRAMES ) { - goto clear; + // first of all, make sure player_state_t is valid + if( !ent->client->ps.fov ) { + return qfalse; } - if( sv.mvd.message.overflowed ) { - // if reliable message overflowed, kick all clients - Com_EPrintf( "MVD message overflowed\n" ); - SV_MvdDropDummy( "overflowed" ); - goto clear; + // always capture dummy MVD client + if( ent == mvd.dummy->edict ) { + return qtrue; } - if( sv.mvd.datagram.overflowed ) { - Com_WPrintf( "MVD datagram overflowed\n" ); - SZ_Clear( &sv.mvd.datagram ); + // never capture spectators + if( ent->client->ps.pmove.pm_type == PM_SPECTATOR ) { + return qfalse; } - if( sv_mvd_scorecmd->string[0] ) { - if( svs.realtime - sv.mvd.layout_time > 9000 ) { - Cbuf_AddTextEx( &dummy_buffer, sv_mvd_scorecmd->string ); - sv.mvd.layout_time = svs.realtime; + // check entity visibility + if( ( ent->svflags & SVF_NOCLIENT ) || !ES_INUSE( &ent->s ) ) { + // never capture invisible entities + if( sv_mvd_capture_flags->integer & 2 ) { + return qfalse; + } + } else { + // always capture visible entities (default) + if( sv_mvd_capture_flags->integer & 4 ) { + return qtrue; } } - // build delta updates - SV_MvdEmitFrame(); - - // check if frame fits - if( sv.mvd.message.cursize + msg_write.cursize > MAX_MSGLEN ) { - Com_WPrintf( "Dumping MVD frame: %"PRIz" bytes\n", msg_write.cursize ); - SZ_Clear( &msg_write ); + // they are likely following someone in case of PM_FREEZE + if( ent->client->ps.pmove.pm_type == PM_FREEZE ) { + return qfalse; } - // check if unreliable datagram fits - if( sv.mvd.message.cursize + msg_write.cursize + sv.mvd.datagram.cursize > MAX_MSGLEN ) { - Com_WPrintf( "Dumping MVD datagram: %"PRIz" bytes\n", sv.mvd.datagram.cursize ); - SZ_Clear( &sv.mvd.datagram ); + // they are likely following someone if PMF_NO_PREDICTION is set + if( ent->client->ps.pmove.pm_flags & PMF_NO_PREDICTION ) { + return qfalse; } - length = LittleShort( sv.mvd.message.cursize + msg_write.cursize + sv.mvd.datagram.cursize ); + return qtrue; +} - // send frame to clients - LIST_FOR_EACH( tcpClient_t, client, &svs.mvd.clients, mvdEntry ) { - if( client->state == cs_spawned ) { - SV_HttpWrite( client, &length, 2 ); - SV_HttpWrite( client, sv.mvd.message.data, sv.mvd.message.cursize ); - SV_HttpWrite( client, msg_write.data, msg_write.cursize ); - SV_HttpWrite( client, sv.mvd.datagram.data, sv.mvd.datagram.cursize ); -#if USE_ZLIB - client->noflush++; -#endif - } - } +/* +================== +build_gamestate - // write frame to demofile - if( svs.mvd.recording ) { - FS_Write( &length, 2, svs.mvd.recording ); - FS_Write( sv.mvd.message.data, sv.mvd.message.cursize, svs.mvd.recording ); - FS_Write( msg_write.data, msg_write.cursize, svs.mvd.recording ); - FS_Write( sv.mvd.datagram.data, sv.mvd.datagram.cursize, svs.mvd.recording ); +Initialize MVD delta compressor for the first time on the given map. +================== +*/ +static void build_gamestate( void ) { + player_state_t *ps; + entity_state_t *es; + edict_t *ent; + int i; - if( sv_mvd_max_size->value > 0 ) { - int numbytes = FS_RawTell( svs.mvd.recording ); + memset( mvd.players, 0, sizeof( player_state_t ) * sv_maxclients->integer ); + memset( mvd.entities, 0, sizeof( entity_state_t ) * MAX_EDICTS ); - if( numbytes > sv_mvd_max_size->value * 1000 ) { - Com_Printf( "Stopping MVD recording, maximum size reached.\n" ); - SV_MvdRecStop(); - } - } else if( sv_mvd_max_duration->value > 0 && - ++svs.mvd.numframes > sv_mvd_max_duration->value * 600 ) - { - Com_Printf( "Stopping MVD recording, maximum duration reached.\n" ); - SV_MvdRecStop(); + // set base player states + for( i = 0; i < sv_maxclients->integer; i++ ) { + ent = EDICT_NUM( i + 1 ); + + if( !player_is_active( ent ) ) { + continue; } + + ps = &mvd.players[i]; + *ps = ent->client->ps; + PPS_INUSE( ps ) = qtrue; } - SZ_Clear( &msg_write ); + // set base entity states + for( i = 1; i < ge->num_edicts; i++ ) { + ent = EDICT_NUM( i ); -clear: - SZ_Clear( &sv.mvd.datagram ); - SZ_Clear( &sv.mvd.message ); + if( ( ent->svflags & SVF_NOCLIENT ) || !ES_INUSE( &ent->s ) ) { + continue; + } + + es = &mvd.entities[i]; + *es = ent->s; + es->number = i; + } } + /* ================== -SV_MvdEmitGamestate +emit_gamestate -Writes a single giant message with all the startup info. +Writes a single giant message with all the startup info, +followed by an uncompressed (baseline) frame. ================== */ -static void SV_MvdEmitGamestate( void ) { - char *string; - int i, j; +static void emit_gamestate( void ) { + char *string; + int i, j; player_state_t *ps; - entity_state_t *es; + entity_state_t *es; size_t length; uint8_t *patch; - int flags, extra, portalbytes; - byte portalbits[MAX_MAP_AREAS/8]; + int flags, extra, portalbytes; + byte portalbits[MAX_MAP_AREAS/8]; patch = SZ_GetSpace( &msg_write, 2 ); - // send the serverdata - MSG_WriteByte( mvd_serverdata ); - MSG_WriteLong( PROTOCOL_VERSION_MVD ); - MSG_WriteShort( PROTOCOL_VERSION_MVD_CURRENT ); - MSG_WriteLong( sv.spawncount ); - MSG_WriteString( fs_game->string ); - MSG_WriteShort( svs.mvd.dummy->number ); + // send the serverdata + MSG_WriteByte( mvd_serverdata ); + MSG_WriteLong( PROTOCOL_VERSION_MVD ); + MSG_WriteShort( PROTOCOL_VERSION_MVD_CURRENT ); + MSG_WriteLong( sv.spawncount ); + MSG_WriteString( fs_game->string ); + MSG_WriteShort( mvd.dummy->number ); // send configstrings - for( i = 0; i < MAX_CONFIGSTRINGS; i++ ) { + for( i = 0; i < MAX_CONFIGSTRINGS; i++ ) { string = sv.configstrings[i]; - if( !string[0] ) { - continue; - } - length = strlen( string ); - if( length > MAX_QPATH ) { - length = MAX_QPATH; - } - - MSG_WriteShort( i ); - MSG_WriteData( string, length ); - MSG_WriteByte( 0 ); - } - MSG_WriteShort( MAX_CONFIGSTRINGS ); + if( !string[0] ) { + continue; + } + length = strlen( string ); + if( length > MAX_QPATH ) { + length = MAX_QPATH; + } + + MSG_WriteShort( i ); + MSG_WriteData( string, length ); + MSG_WriteByte( 0 ); + } + MSG_WriteShort( MAX_CONFIGSTRINGS ); // send baseline frame - portalbytes = CM_WritePortalBits( &sv.cm, portalbits ); - MSG_WriteByte( portalbytes ); - MSG_WriteData( portalbits, portalbytes ); - + portalbytes = CM_WritePortalBits( &sv.cm, portalbits ); + MSG_WriteByte( portalbytes ); + MSG_WriteData( portalbits, portalbytes ); + // send player states - flags = 0; - if( sv_mvd_noblend->integer ) { - flags |= MSG_PS_IGNORE_BLEND; - } - if( sv_mvd_nogun->integer ) { - flags |= MSG_PS_IGNORE_GUNINDEX|MSG_PS_IGNORE_GUNFRAMES; - } - for( i = 0, ps = svs.mvd.players; i < sv_maxclients->integer; i++, ps++ ) { + flags = 0; + if( sv_mvd_noblend->integer ) { + flags |= MSG_PS_IGNORE_BLEND; + } + if( sv_mvd_nogun->integer ) { + flags |= MSG_PS_IGNORE_GUNINDEX|MSG_PS_IGNORE_GUNFRAMES; + } + for( i = 0, ps = mvd.players; i < sv_maxclients->integer; i++, ps++ ) { extra = 0; if( !PPS_INUSE( ps ) ) { extra |= MSG_PS_REMOVE; } - MSG_WriteDeltaPlayerstate_Packet( NULL, ps, i, flags | extra ); - } - MSG_WriteByte( CLIENTNUM_NONE ); + MSG_WriteDeltaPlayerstate_Packet( NULL, ps, i, flags | extra ); + } + MSG_WriteByte( CLIENTNUM_NONE ); // send entity states - for( i = 1, es = svs.mvd.entities + 1; i < ge->num_edicts; i++, es++ ) { + for( i = 1, es = mvd.entities + 1; i < ge->num_edicts; i++, es++ ) { flags = 0; if( i <= sv_maxclients->integer ) { - ps = &svs.mvd.players[ i - 1 ]; + ps = &mvd.players[ i - 1 ]; if( PPS_INUSE( ps ) && ps->pmove.pm_type == PM_NORMAL ) { flags |= MSG_ES_FIRSTPERSON; } @@ -806,57 +599,255 @@ static void SV_MvdEmitGamestate( void ) { es->number = i; MSG_WriteDeltaEntity( NULL, es, flags ); es->number = j; - } - MSG_WriteShort( 0 ); + } + MSG_WriteShort( 0 ); length = msg_write.cursize - 2; patch[0] = length & 255; patch[1] = ( length >> 8 ) & 255; } -void SV_MvdMapChanged( void ) { - tcpClient_t *client; - if( !svs.mvd.dummy ) { - if( !sv_mvd_autorecord->integer ) { - return; // not listening for autorecord command +static void copy_entity_state( entity_state_t *dst, const entity_state_t *src, int flags ) { + if( !( flags & MSG_ES_FIRSTPERSON ) ) { + VectorCopy( src->origin, dst->origin ); + VectorCopy( src->angles, dst->angles ); + VectorCopy( src->old_origin, dst->old_origin ); + } + dst->modelindex = src->modelindex; + dst->modelindex2 = src->modelindex2; + dst->modelindex3 = src->modelindex3; + dst->modelindex4 = src->modelindex4; + dst->frame = src->frame; + dst->skinnum = src->skinnum; + dst->effects = src->effects; + dst->renderfx = src->renderfx; + dst->solid = src->solid; + dst->sound = src->sound; + dst->event = 0; +} + +/* +================== +emit_frame + +Builds new MVD frame by capturing all entity and player states +and calculating portalbits. The same frame is used for all MVD +clients, as well as local recorder. +================== +*/ +static void emit_frame( void ) { + player_state_t *oldps, *newps; + entity_state_t *oldes, *newes; + edict_t *ent; + int flags, portalbytes; + byte portalbits[MAX_MAP_AREAS/8]; + int i; + + MSG_WriteByte( mvd_frame ); + + // send portal bits + portalbytes = CM_WritePortalBits( &sv.cm, portalbits ); + MSG_WriteByte( portalbytes ); + MSG_WriteData( portalbits, portalbytes ); + + flags = MSG_PS_IGNORE_PREDICTION|MSG_PS_IGNORE_DELTAANGLES; + if( sv_mvd_noblend->integer ) { + flags |= MSG_PS_IGNORE_BLEND; + } + if( sv_mvd_nogun->integer ) { + flags |= MSG_PS_IGNORE_GUNINDEX|MSG_PS_IGNORE_GUNFRAMES; + } + + // send player states + for( i = 0; i < sv_maxclients->integer; i++ ) { + ent = EDICT_NUM( i + 1 ); + + oldps = &mvd.players[i]; + newps = &ent->client->ps; + + if( !player_is_active( ent ) ) { + if( PPS_INUSE( oldps ) ) { + // the old player isn't present in the new message + MSG_WriteDeltaPlayerstate_Packet( NULL, NULL, i, flags ); + PPS_INUSE( oldps ) = qfalse; + } + continue; } - if( !SV_MvdCreateDummy() ) { - return; + + if( PPS_INUSE( oldps ) ) { + // delta update from old position + // because the force parm is false, this will not result + // in any bytes being emited if the player has not changed at all + MSG_WriteDeltaPlayerstate_Packet( oldps, newps, i, flags ); + } else { + // this is a new player, send it from the last state + MSG_WriteDeltaPlayerstate_Packet( oldps, newps, i, + flags | MSG_PS_FORCE ); } - Com_Printf( "Spawning MVD dummy for auto-recording\n" ); + + // shuffle current state to previous + *oldps = *newps; + PPS_INUSE( oldps ) = qtrue; } - SV_MvdSpawnDummy(); - SV_MvdEmitGamestate(); + MSG_WriteByte( CLIENTNUM_NONE ); // end of packetplayers - // send gamestate to all MVD clients - LIST_FOR_EACH( tcpClient_t, client, &svs.mvd.clients, mvdEntry ) { - SV_HttpWrite( client, msg_write.data, msg_write.cursize ); + // send entity states + for( i = 1; i < ge->num_edicts; i++ ) { + ent = EDICT_NUM( i ); + + oldes = &mvd.entities[i]; + newes = &ent->s; + + if( ( ent->svflags & SVF_NOCLIENT ) || !ES_INUSE( newes ) ) { + if( oldes->number ) { + // the old entity isn't present in the new message + MSG_WriteDeltaEntity( oldes, NULL, MSG_ES_FORCE ); + oldes->number = 0; + } + continue; + } + + // calculate flags + flags = 0; + if( i <= sv_maxclients->integer ) { + oldps = &mvd.players[ i - 1 ]; + if( PPS_INUSE( oldps ) && oldps->pmove.pm_type == PM_NORMAL ) { + // do not waste bandwidth on origin/angle updates, + // client will recover them from player state + flags |= MSG_ES_FIRSTPERSON; + } + } + + if( !oldes->number ) { + // this is a new entity, send it from the last state + flags |= MSG_ES_FORCE|MSG_ES_NEWENTITY; + } + + MSG_WriteDeltaEntity( oldes, newes, flags ); + + // shuffle current state to previous + copy_entity_state( oldes, newes, flags ); + oldes->number = i; } - if( svs.mvd.recording ) { - int maxlevels = sv_mvd_max_levels->integer; - - // check if it is time to stop recording - if( maxlevels > 0 && ++svs.mvd.numlevels >= maxlevels ) { - Com_Printf( "Stopping MVD recording, " - "maximum number of level changes reached.\n" ); - SV_MvdRecStop(); - } else { - FS_Write( msg_write.data, msg_write.cursize, svs.mvd.recording ); + MSG_WriteShort( 0 ); // end of packetentities +} + +// if dummy is not yet connected, create and spawn it +static qboolean init_mvd( void ) { + if( !mvd.dummy ) { + if( !dummy_create() ) { + return qfalse; + } + dummy_spawn(); + build_gamestate(); + } + return qtrue; +} + +/* +================== +SV_MvdRunFrame +================== +*/ +void SV_MvdRunFrame( void ) { + tcpClient_t *client; + uint16_t length; + + if( !mvd.dummy ) { + return; + } + + dummy_run(); + + if( mvd.message.overflowed ) { + // if reliable message overflowed, kick all clients + Com_EPrintf( "Reliable MVD message overflowed\n" ); + dummy_drop( "overflowed" ); + goto clear; + } + + if( mvd.datagram.overflowed ) { + Com_WPrintf( "Unreliable MVD datagram overflowed\n" ); + SZ_Clear( &mvd.datagram ); + } + + // emit a single delta update + emit_frame(); + + // check if frame fits + // FIXME: dumping frame should not be allowed in current design + if( mvd.message.cursize + msg_write.cursize > MAX_MSGLEN ) { + Com_WPrintf( "Dumping MVD frame: %"PRIz" bytes\n", msg_write.cursize ); + SZ_Clear( &msg_write ); + } + + // check if unreliable datagram fits + if( mvd.message.cursize + msg_write.cursize + mvd.datagram.cursize > MAX_MSGLEN ) { + Com_WPrintf( "Dumping unreliable MVD datagram: %"PRIz" bytes\n", mvd.datagram.cursize ); + SZ_Clear( &mvd.datagram ); + } + + length = LittleShort( mvd.message.cursize + msg_write.cursize + mvd.datagram.cursize ); + + // send frame to clients + LIST_FOR_EACH( tcpClient_t, client, &mvd_client_list, mvdEntry ) { + if( client->state == cs_spawned ) { + SV_HttpWrite( client, &length, 2 ); + SV_HttpWrite( client, mvd.message.data, mvd.message.cursize ); + SV_HttpWrite( client, msg_write.data, msg_write.cursize ); + SV_HttpWrite( client, mvd.datagram.data, mvd.datagram.cursize ); +#if USE_ZLIB + client->noflush++; +#endif + } + } + + // write frame to demofile + if( mvd.recording ) { + FS_Write( &length, 2, mvd.recording ); + FS_Write( mvd.message.data, mvd.message.cursize, mvd.recording ); + FS_Write( msg_write.data, msg_write.cursize, mvd.recording ); + FS_Write( mvd.datagram.data, mvd.datagram.cursize, mvd.recording ); + + if( sv_mvd_max_size->value > 0 ) { + int numbytes = FS_RawTell( mvd.recording ); + + if( numbytes > sv_mvd_max_size->value * 1000 ) { + Com_Printf( "Stopping MVD recording, maximum size reached.\n" ); + rec_stop(); + } + } else if( sv_mvd_max_duration->value > 0 && + ++mvd.numframes > sv_mvd_max_duration->value * 600 ) + { + Com_Printf( "Stopping MVD recording, maximum duration reached.\n" ); + rec_stop(); } } SZ_Clear( &msg_write ); + +clear: + SZ_Clear( &mvd.datagram ); + SZ_Clear( &mvd.message ); } +/* +============================================================================== + +CLIENT HANDLING + +============================================================================== +*/ + static void SV_MvdClientNew( tcpClient_t *client ) { - Com_DPrintf( "Sending gamestate to MVD client %s\n", + Com_DPrintf( "Sending gamestate to MVD client %s\n", NET_AdrToString( &client->stream.address ) ); - client->state = cs_spawned; + client->state = cs_spawned; - SV_MvdEmitGamestate(); + emit_gamestate(); #if USE_ZLIB client->noflush = 99999; @@ -913,13 +904,13 @@ void SV_MvdGetStream( const char *uri ) { return; } - if( !SV_MvdCreateDummy() ) { + if( !init_mvd() ) { SV_HttpReject( "503 Service Unavailable", "Unable to create dummy MVD client. Server is full." ); return; } - List_Append( &svs.mvd.clients, &http_client->mvdEntry ); + List_Append( &mvd_client_list, &http_client->mvdEntry ); SV_MvdInitStream(); @@ -927,35 +918,243 @@ void SV_MvdGetStream( const char *uri ) { } /* +============================================================================== + +SERVER HOOKS + +These hooks are called by server code when some event occurs. + +============================================================================== +*/ + +/* +================== +SV_MvdMapChanged + +Server has just changed the map, spawn the MVD dummy and go! +================== +*/ +void SV_MvdMapChanged( void ) { + tcpClient_t *client; + + if( !sv_mvd_enable->integer ) { + return; // do noting if disabled + } + + if( !mvd.dummy ) { + if( !sv_mvd_autorecord->integer ) { + return; // not listening for autorecord command + } + if( !dummy_create() ) { + return; + } + Com_Printf( "Spawning MVD dummy for auto-recording\n" ); + } + + SZ_Clear( &mvd.datagram ); + SZ_Clear( &mvd.message ); + + dummy_spawn(); + build_gamestate(); + emit_gamestate(); + + // send gamestate to all MVD clients + LIST_FOR_EACH( tcpClient_t, client, &mvd_client_list, mvdEntry ) { + SV_HttpWrite( client, msg_write.data, msg_write.cursize ); + } + + if( mvd.recording ) { + int maxlevels = sv_mvd_max_levels->integer; + + // check if it is time to stop recording + if( maxlevels > 0 && ++mvd.numlevels >= maxlevels ) { + Com_Printf( "Stopping MVD recording, " + "maximum number of level changes reached.\n" ); + rec_stop(); + } else { + FS_Write( msg_write.data, msg_write.cursize, mvd.recording ); + } + } + + SZ_Clear( &msg_write ); +} + +/* +================== +SV_MvdClientDropped + +Server has just dropped a client, check if that was our MVD dummy client. +================== +*/ +void SV_MvdClientDropped( client_t *client, const char *reason ) { + if( client == mvd.dummy ) { + dummy_drop( reason ); + } +} + +/* +================== +SV_MvdInit + +Server is initializing, prepare MVD server for this game. +================== +*/ +void SV_MvdInit( void ) { + if( !sv_mvd_enable->integer ) { + return; // do noting if disabled + } + + // allocate buffers + Z_TagReserve( sizeof( player_state_t ) * sv_maxclients->integer + + sizeof( entity_state_t ) * MAX_EDICTS + MAX_MSGLEN * 2, TAG_SERVER ); + SZ_Init( &mvd.message, Z_ReservedAlloc( MAX_MSGLEN ), MAX_MSGLEN ); + SZ_Init( &mvd.datagram, Z_ReservedAlloc( MAX_MSGLEN ), MAX_MSGLEN ); + mvd.players = Z_ReservedAlloc( sizeof( player_state_t ) * sv_maxclients->integer ); + mvd.entities = Z_ReservedAlloc( sizeof( entity_state_t ) * MAX_EDICTS ); + + // reserve the slot for dummy MVD client + if( !sv_reserved_slots->integer ) { + Cvar_Set( "sv_reserved_slots", "1" ); + } +} + +/* +================== +SV_MvdShutdown + +Server is shutting down, clean everything up. +================== +*/ +void SV_MvdShutdown( void ) { + tcpClient_t *t; + uint16_t length; + + // stop recording + rec_stop(); + + // send EOF to MVD clients + // NOTE: not clearing mvd_client_list as clients will be + // properly dropped by SV_FinalMessage just after + length = 0; + LIST_FOR_EACH( tcpClient_t, t, &mvd_client_list, mvdEntry ) { + SV_HttpWrite( t, &length, 2 ); + SV_HttpFinish( t ); + } + + // remove MVD dummy + if( mvd.dummy ) { + SV_RemoveClient( mvd.dummy ); + } + + // free static data + Z_Free( mvd.message.data ); + + memset( &mvd, 0, sizeof( mvd ) ); +} + + +/* +============================================================================== + +GAME API HOOKS + +These hooks are called from PF_* functions to add additional +out-of-band data into the MVD stream. + +============================================================================== +*/ + +/* ============== SV_MvdMulticast -TODO: attempt to combine several identical unicast/multicast messages -into one message to save space (useful for shotgun patterns +TODO: would be better to combine identical unicast/multicast messages +into one larger message to save space (useful for shotgun patterns as they often occur in the same BSP leaf) ============== */ -void SV_MvdMulticast( sizebuf_t *buf, int leafnum, mvd_ops_t op ) { - int bits = ( msg_write.cursize >> 8 ) & 7; +void SV_MvdMulticast( int leafnum, multicast_t to ) { + mvd_ops_t op; + sizebuf_t *buf; + int bits; + + // do nothing if not active + if( !mvd.dummy ) { + return; + } + + op = mvd_multicast_all + to; + buf = to < MULTICAST_ALL_R ? &mvd.datagram : &mvd.message; + bits = ( msg_write.cursize >> 8 ) & 7; SZ_WriteByte( buf, op | ( bits << SVCMD_BITS ) ); SZ_WriteByte( buf, msg_write.cursize & 255 ); - if( op != mvd_multicast_all && op != mvd_multicast_all_r ) { - SZ_WriteShort( buf, leafnum ); - } - - SZ_Write( buf, msg_write.data, msg_write.cursize ); + if( op != mvd_multicast_all && op != mvd_multicast_all_r ) { + SZ_WriteShort( buf, leafnum ); + } + + SZ_Write( buf, msg_write.data, msg_write.cursize ); } /* ============== SV_MvdUnicast + +Performs some basic filtering of the unicast data that would be +otherwise discarded by the MVD client. ============== */ -void SV_MvdUnicast( sizebuf_t *buf, int clientNum, mvd_ops_t op ) { - int bits = ( msg_write.cursize >> 8 ) & 7; +void SV_MvdUnicast( edict_t *ent, int clientNum, qboolean reliable ) { + mvd_ops_t op; + sizebuf_t *buf; + int bits; + + // do nothing if not active + if( !mvd.dummy ) { + return; + } + + // discard any data to players not in the game + if( !player_is_active( ent ) ) { + return; + } + + switch( msg_write.data[0] ) { + case svc_layout: + if( ent == mvd.dummy->edict ) { + // special case, send to all observers + mvd.layout_time = svs.realtime; + } else { + // discard any layout updates to players + return; + } + break; + case svc_stufftext: + if( memcmp( msg_write.data + 1, "play ", 5 ) ) { + // discard any stufftexts, except of play sound hacks + return; + } + break; + case svc_print: + if( ent != mvd.dummy->edict && sv_mvd_nomsgs->integer ) { + // optionally discard text messages to players + return; + } + break; + } + + // decide where should it go + if( reliable ) { + op = mvd_unicast_r; + buf = &mvd.message; + } else { + op = mvd_unicast; + buf = &mvd.datagram; + } + // write it + bits = ( msg_write.cursize >> 8 ) & 7; SZ_WriteByte( buf, op | ( bits << SVCMD_BITS ) ); SZ_WriteByte( buf, msg_write.cursize & 255 ); SZ_WriteByte( buf, clientNum ); @@ -968,66 +1167,121 @@ SV_MvdConfigstring ============== */ void SV_MvdConfigstring( int index, const char *string ) { - if( sv.mvd.paused >= PAUSED_FRAMES ) { - Q_SetBit( sv.mvd.dcs, index ); - return; - } - SZ_WriteByte( &sv.mvd.message, mvd_configstring ); - SZ_WriteShort( &sv.mvd.message, index ); - SZ_WriteString( &sv.mvd.message, string ); + if( mvd.dummy ) { + SZ_WriteByte( &mvd.message, mvd_configstring ); + SZ_WriteShort( &mvd.message, index ); + SZ_WriteString( &mvd.message, string ); + } } +/* +============== +SV_MvdBroadcastPrint +============== +*/ void SV_MvdBroadcastPrint( int level, const char *string ) { - SZ_WriteByte( &sv.mvd.message, mvd_print ); - SZ_WriteByte( &sv.mvd.message, level ); - SZ_WriteString( &sv.mvd.message, string ); + if( mvd.dummy ) { + SZ_WriteByte( &mvd.message, mvd_print ); + SZ_WriteByte( &mvd.message, level ); + SZ_WriteString( &mvd.message, string ); + } } /* ============== -SV_MvdRecStop +SV_MvdStartSound + +FIXME: origin will be incorrect on entities not captured this frame +============== +*/ +void SV_MvdStartSound( int entnum, int channel, int flags, + int soundindex, int volume, + int attenuation, int timeofs ) +{ + int extrabits, sendchan; + + if( !mvd.dummy ) { + return; + } + + extrabits = 0; + if( channel & CHAN_NO_PHS_ADD ) { + extrabits |= 1 << SVCMD_BITS; + } + if( channel & CHAN_RELIABLE ) { + // FIXME: write to mvd.message + extrabits |= 2 << SVCMD_BITS; + } + + SZ_WriteByte( &mvd.datagram, mvd_sound | extrabits ); + SZ_WriteByte( &mvd.datagram, flags ); + SZ_WriteByte( &mvd.datagram, soundindex ); + + if( flags & SND_VOLUME ) + SZ_WriteByte( &mvd.datagram, volume ); + if( flags & SND_ATTENUATION ) + SZ_WriteByte( &mvd.datagram, attenuation ); + if( flags & SND_OFFSET ) + SZ_WriteByte( &mvd.datagram, timeofs ); + + sendchan = ( entnum << 3 ) | ( channel & 7 ); + SZ_WriteShort( &mvd.datagram, sendchan ); +} + + +/* +============================================================================== + +LOCAL MVD RECORDER + +============================================================================== +*/ + +/* +============== +rec_stop Stops server local MVD recording. ============== */ -void SV_MvdRecStop( void ) { - int length; +static void rec_stop( void ) { + uint16_t length; - if( !svs.mvd.recording ) { - return; - } + if( !mvd.recording ) { + return; + } - // write demo EOF marker - length = 0; - FS_Write( &length, 2, svs.mvd.recording ); + // write demo EOF marker + length = 0; + FS_Write( &length, 2, mvd.recording ); - FS_FCloseFile( svs.mvd.recording ); - svs.mvd.recording = 0; + FS_FCloseFile( mvd.recording ); + mvd.recording = 0; } -static qboolean SV_MvdRecAllow( void ) { - if( !svs.mvd.entities ) { - Com_Printf( "MVD recording is disabled on this server.\n" ); - return qfalse; - } - if( svs.mvd.recording ) { - Com_Printf( "Already recording a local MVD.\n" ); - return qfalse; - } +static qboolean rec_allowed( void ) { + if( !mvd.entities ) { + Com_Printf( "MVD recording is disabled on this server.\n" ); + return qfalse; + } + if( mvd.recording ) { + Com_Printf( "Already recording a local MVD.\n" ); + return qfalse; + } return qtrue; } -static void SV_MvdRecStart( fileHandle_t demofile ) { +static void rec_start( fileHandle_t demofile ) { uint32_t magic; - svs.mvd.recording = demofile; - svs.mvd.numlevels = 0; - svs.mvd.numframes = 0; - + mvd.recording = demofile; + mvd.numlevels = 0; + mvd.numframes = 0; + magic = MVD_MAGIC; FS_Write( &magic, 4, demofile ); - SV_MvdEmitGamestate(); + emit_gamestate(); FS_Write( msg_write.data, msg_write.cursize, demofile ); SZ_Clear( &msg_write ); } @@ -1039,7 +1293,11 @@ const cmd_option_t o_mvdrecord[] = { { NULL } }; -static void MVD_Record_c( genctx_t *ctx, int argnum ) { +extern void MVD_StreamedStop_f( void ); +extern void MVD_StreamedRecord_f( void ); +extern void MVD_File_g( genctx_t *ctx ); + +static void SV_MvdRecord_c( genctx_t *ctx, int argnum ) { if( argnum == 1 ) { MVD_File_g( ctx ); } @@ -1053,21 +1311,21 @@ Begins server MVD recording. Every entity, every playerinfo and every message will be recorded. ============== */ -static void MVD_Record_f( void ) { - char buffer[MAX_OSPATH]; - fileHandle_t demofile; +static void SV_MvdRecord_f( void ) { + char buffer[MAX_OSPATH]; + fileHandle_t demofile; qboolean gzip = qfalse; int c; size_t len; - if( sv.state != ss_game ) { + if( sv.state != ss_game ) { if( sv.state == ss_broadcast ) { MVD_StreamedRecord_f(); } else { - Com_Printf( "No server running.\n" ); + Com_Printf( "No server running.\n" ); } - return; - } + return; + } while( ( c = Cmd_ParseOptions( o_mvdrecord ) ) != -1 ) { switch( c ) { @@ -1088,13 +1346,13 @@ static void MVD_Record_f( void ) { return; } - if( !SV_MvdRecAllow() ) { + if( !rec_allowed() ) { return; } - // - // open the demo file - // + // + // open the demo file + // len = Q_concat( buffer, sizeof( buffer ), "demos/", cmd_optarg, gzip ? ".mvd2.gz" : ".mvd2", NULL ); if( len >= sizeof( buffer ) ) { @@ -1102,24 +1360,24 @@ static void MVD_Record_f( void ) { return; } - FS_FOpenFile( buffer, &demofile, FS_MODE_WRITE ); - if( !demofile ) { - Com_EPrintf( "Couldn't open %s for writing\n", buffer ); - return; - } + FS_FOpenFile( buffer, &demofile, FS_MODE_WRITE ); + if( !demofile ) { + Com_EPrintf( "Couldn't open %s for writing\n", buffer ); + return; + } - if( !SV_MvdCreateDummy() ) { + if( !init_mvd() ) { FS_FCloseFile( demofile ); return; } - if( gzip ) { + if( gzip ) { FS_FilterFile( demofile ); } - SV_MvdRecStart( demofile ); + rec_start( demofile ); - Com_Printf( "Recording local MVD to %s\n", buffer ); + Com_Printf( "Recording local MVD to %s\n", buffer ); } @@ -1130,22 +1388,22 @@ MVD_Stop_f Ends server MVD recording ============== */ -static void MVD_Stop_f( void ) { +static void SV_MvdStop_f( void ) { if( sv.state == ss_broadcast ) { MVD_StreamedStop_f(); return; } - if( !svs.mvd.recording ) { - Com_Printf( "Not recording a local MVD.\n" ); - return; - } + if( !mvd.recording ) { + Com_Printf( "Not recording a local MVD.\n" ); + return; + } - Com_Printf( "Stopped local MVD recording.\n" ); - SV_MvdRecStop(); + Com_Printf( "Stopped local MVD recording.\n" ); + rec_stop(); } -static void MVD_Stuff_f( void ) { - if( svs.mvd.dummy ) { +static void SV_MvdStuff_f( void ) { + if( mvd.dummy ) { Cbuf_AddTextEx( &dummy_buffer, Cmd_RawArgs() ); Cbuf_AddTextEx( &dummy_buffer, "\n" ); } else { @@ -1154,22 +1412,22 @@ static void MVD_Stuff_f( void ) { } static const cmdreg_t c_svmvd[] = { - { "mvdrecord", MVD_Record_f, MVD_Record_c }, - { "mvdstop", MVD_Stop_f }, - { "mvdstuff", MVD_Stuff_f }, + { "mvdrecord", SV_MvdRecord_f, SV_MvdRecord_c }, + { "mvdstop", SV_MvdStop_f }, + { "mvdstuff", SV_MvdStuff_f }, { NULL } }; void SV_MvdRegister( void ) { - sv_mvd_enable = Cvar_Get( "sv_mvd_enable", "0", CVAR_LATCH ); - sv_mvd_auth = Cvar_Get( "sv_mvd_auth", "", CVAR_PRIVATE ); - //sv_mvd_wait = Cvar_Get( "sv_mvd_wait", "0", CVAR_ROM ); // TODO - sv_mvd_max_size = Cvar_Get( "sv_mvd_max_size", "0", 0 ); - sv_mvd_max_duration = Cvar_Get( "sv_mvd_max_duration", "0", 0 ); - sv_mvd_max_levels = Cvar_Get( "sv_mvd_max_levels", "1", 0 ); - sv_mvd_noblend = Cvar_Get( "sv_mvd_noblend", "0", CVAR_LATCH ); - sv_mvd_nogun = Cvar_Get( "sv_mvd_nogun", "1", CVAR_LATCH ); + sv_mvd_enable = Cvar_Get( "sv_mvd_enable", "0", CVAR_LATCH ); + sv_mvd_auth = Cvar_Get( "sv_mvd_auth", "", CVAR_PRIVATE ); + sv_mvd_max_size = Cvar_Get( "sv_mvd_max_size", "0", 0 ); + sv_mvd_max_duration = Cvar_Get( "sv_mvd_max_duration", "0", 0 ); + sv_mvd_max_levels = Cvar_Get( "sv_mvd_max_levels", "1", 0 ); + sv_mvd_noblend = Cvar_Get( "sv_mvd_noblend", "0", CVAR_LATCH ); + sv_mvd_nogun = Cvar_Get( "sv_mvd_nogun", "1", CVAR_LATCH ); + sv_mvd_nomsgs = Cvar_Get( "sv_mvd_nomsgs", "1", CVAR_LATCH ); sv_mvd_begincmd = Cvar_Get( "sv_mvd_begincmd", "wait 50; putaway; wait 10; help;", 0 ); sv_mvd_scorecmd = Cvar_Get( "sv_mvd_scorecmd", @@ -1180,9 +1438,9 @@ void SV_MvdRegister( void ) { #endif sv_mvd_capture_flags = Cvar_Get( "sv_mvd_capture_flags", "5", 0 ); - dummy_buffer.text = dummy_buffer_text; + dummy_buffer.text = dummy_buffer_text; dummy_buffer.maxsize = sizeof( dummy_buffer_text ); - dummy_buffer.exec = SV_DummyExecuteString; + dummy_buffer.exec = dummy_exec_string; Cmd_Register( c_svmvd ); } diff --git a/source/sv_send.c b/source/sv_send.c index 1c95071..6d85d29 100644 --- a/source/sv_send.c +++ b/source/sv_send.c @@ -249,15 +249,12 @@ void SV_Multicast( vec3_t origin, multicast_t to ) { int flags; vec3_t org; player_state_t *ps; - sizebuf_t *buf; flags = 0; - buf = &sv.mvd.datagram; switch( to ) { case MULTICAST_ALL_R: flags |= MSG_RELIABLE; - buf = &sv.mvd.message; // intentional fallthrough case MULTICAST_ALL: leaf1 = NULL; @@ -265,7 +262,6 @@ void SV_Multicast( vec3_t origin, multicast_t to ) { break; case MULTICAST_PHS_R: flags |= MSG_RELIABLE; - buf = &sv.mvd.message; // intentional fallthrough case MULTICAST_PHS: leaf1 = CM_PointLeaf( &sv.cm, origin ); @@ -274,7 +270,6 @@ void SV_Multicast( vec3_t origin, multicast_t to ) { break; case MULTICAST_PVS_R: flags |= MSG_RELIABLE; - buf = &sv.mvd.message; // intentional fallthrough case MULTICAST_PVS: leaf1 = CM_PointLeaf( &sv.cm, origin ); @@ -314,9 +309,7 @@ void SV_Multicast( vec3_t origin, multicast_t to ) { } // add to MVD datagram - if( svs.mvd.dummy && sv.mvd.paused < PAUSED_FRAMES ) { - SV_MvdMulticast( buf, leafnum, mvd_multicast_all + to ); - } + SV_MvdMulticast( leafnum, to ); // clear the buffer SZ_Clear( &msg_write ); |