summaryrefslogtreecommitdiff
path: root/source
diff options
context:
space:
mode:
authorAndrey Nazarov <skuller@skuller.net>2008-10-03 12:49:06 +0000
committerAndrey Nazarov <skuller@skuller.net>2008-10-03 12:49:06 +0000
commitf8abe42a0d1a42653b39f6cf320d3fbdd1279bb3 (patch)
tree4e56e3647a4f9933f71c5f2cc03b4fa40bfc7709 /source
parent3fb2508c6c5976d418c377847bba1037fa843fff (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.c10
-rw-r--r--source/net_common.c13
-rw-r--r--source/protocol.h3
-rw-r--r--source/q_msg.c4
-rw-r--r--source/sv_game.c108
-rw-r--r--source/sv_http.c2
-rw-r--r--source/sv_init.c23
-rw-r--r--source/sv_local.h43
-rw-r--r--source/sv_main.c41
-rw-r--r--source/sv_mvd.c1588
-rw-r--r--source/sv_send.c9
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 );