summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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
-rw-r--r--wiki/doc/server.mdwn42
12 files changed, 1005 insertions, 881 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 );
diff --git a/wiki/doc/server.mdwn b/wiki/doc/server.mdwn
index 190dedc..194a8f2 100644
--- a/wiki/doc/server.mdwn
+++ b/wiki/doc/server.mdwn
@@ -6,7 +6,8 @@ Variables
Network
----------
-All cvars below can be changed at runtime and will take effect immediately.
+All network related cvars below can be changed at runtime and will
+take effect immediately.
- `set net_ip ""` (string)
Specifies network interface address server UDP socket should be bound to.
@@ -34,7 +35,8 @@ Misc
- `set sv_iplimit 3` (integer)
Maximum number of simultaneous connections allowed from
-single IP address. Setting this variable to 0 disables the limit.
+single IP address (per connection type, like TCP and UDP).
+Setting this variable to 0 disables the limit.
- `set sv_status_show 2` (integer)
Specifies how the server should respond to status queries
@@ -72,19 +74,20 @@ dummy MVD observer. Clients set their passwords via `password` userinfo variable
When enabled, do not enforce any rate limits for clients whose IP is from
private address space (127.x.x.x, 10.x.x.x, 192.168.x.x, 172.16.x.x).
-- `set sv_ghostime 6` (float)
-Maximum time, in seconds, to keep clients which passed initial
+- `set sv_ghostime 10` (float)
+Maximum time, in seconds, before dropping clients which passed initial
challenge-response connection stage but not yet requested server data chunk.
This also applies to HTTP clients in request processing stage.
Helps to avoid attacks flooding server with zombie clients, combined
with `sv_iplimit` variable.
- `set sv_force_reconnect ""` (string)
-When set to address string, forces new clients to reconnect to this address
-as an additional proxy protection measure. This reconnection process is FAST.
+When set to address string, forces new clients to quickly reconnect to
+this address as an additional proxy protection measure.
- `set sv_show_name_changes 0` (boolean)
-Broadcast player name changes to everyone.
+Broadcast player name changes to everyone. You should probably enable this
+unless game mod already shows name changes.
- `set sv_uptime 0` (boolean)
Display uptime in server info.
@@ -120,7 +123,7 @@ Maximum size in KB of the locally recorded MVD.
Setting this to zero disables the limit.
- `set sv_mvd_max_levels 1` (integer)
-Maximum number of levels (map changes) locally recorded MVD may span.
+Maximum number of levels (map changes) locally recorded MVD may contain.
Setting this to zero disables the limit.
- `set sv_mvd_auth ""` (string)
@@ -153,9 +156,9 @@ Specifies MVD connection timeout value, in seconds.
Time, in seconds, for MVD client to buffer data right after initial
connection. This effectively specifies MVD stream delay seen by observers.
-- `set mvd_wait_percent 2` (float)
-Inuse percentage of the input buffer when MVD state machine transitions
-into running state to prevent buffer overrun.
+- `set mvd_wait_percent 50` (float)
+Maximum inuse percentage of the input buffer before MVD stops buffering
+phase to prevent overrun, regardless of `mvd_wait_delay` value.
- `set mvd_default_map "q2dm1"` (string)
Specifies default map used for the Waiting Room channel.
@@ -185,15 +188,10 @@ slots for "body queue" and disables client side interpolation of
those entities. Use this hack with caution as it may not be compatible
with all mods and causes slightly higher bandwidth usage.
-- `set sv_strafejump_hack 1` (boolean)
+- `set sv_strafejump_hack 1` (integer)
Enables FPS-independent strafe jumping mode for clients using R1Q2
-and Q2PRO protocols.
-
-- `set map_override 0` (boolean)
-When enabled, each time server map is loaded, file `maps/<mapname>.ent`
-is searched. If this file exists, then entity lump of the map being loaded
-is replaced with the contents of this file. Use this feature _only_ if you
-know what you are doing.
+and Q2PRO protocols. Values higher than 1 will force this mode for all
+clients, regardless of their protocol version.
Commands
==========
@@ -211,10 +209,6 @@ connections. Remote client must support passive connections (R1Q2 and Q2PRO
clients do), must be in passive connection mode and the specified _port_
must be reachable. See `passive` [[client#index3h1]] command for more details.
-- `dumpents [/]<entname>`
-Dumps entity lump of the current map into `maps/<entname>.ent`, which you can later
-edit and use as `map_override` file (see above).
-
- `addban <address[/mask]> [comment ...]`
Adds specified address to the ban list. Specify _mask_ to ban entire subnetwork.
If specified, _comment_ will be printed to banned user(s) when they attempt to connect.
@@ -269,7 +263,7 @@ Kill the specified channel. There is no need to specify `chanid` if
there is only one active channel.
- `mvdspawn`
-Spawn MVD client instance, that is, Waiting Room channel.
+(Re)spawn MVD client instance (that is, Waiting Room channel).
- `mvdchannels`
Display status of all MVD channels.