summaryrefslogtreecommitdiff
path: root/source/mvd_client.c
diff options
context:
space:
mode:
authorAndrey Nazarov <skuller@skuller.net>2008-10-11 15:05:50 +0000
committerAndrey Nazarov <skuller@skuller.net>2008-10-11 15:05:50 +0000
commitd02633af4e780c4b6f6d938c67d84d2c968adb79 (patch)
tree3379b9615e285346ad6b1f87639912e01ecd44c7 /source/mvd_client.c
parentf8abe42a0d1a42653b39f6cf320d3fbdd1279bb3 (diff)
Major redesign of GTV protocol: added support for persistent GTV connections,
bidirectional pinging, low traffic (`suspended') modes. HTTP server is now gone (remote console logging is temporary gone too), custom binary protocol is used for GTV connections now. MVD client no longer serves other MVD clients, only regular spectators. Changed FIFO buffers to be regular circular buffers, not BIP-buffers. Removed `sv_http_*', `sv_console_auth' variables. Added `sv_mvd_maxclients' variable, `addgtvhost', `delgtvhost' and `listgtvhosts' commands. Renamed `sv_mvd_max*' cvars for consistency. Reset `sv_ghostime' default value back to 6, but changed semantics: it now waits for any packet from client, not just `begin' packet. Added `--disable-mvd-server' and `--disable-mvd-client' options to configure script. FS_Restart() no longer chokes on real files opened for reading. Fixed client chat prompt length. Stubbed out more debugging stuff from dedicated server builds.
Diffstat (limited to 'source/mvd_client.c')
-rw-r--r--source/mvd_client.c1847
1 files changed, 1058 insertions, 789 deletions
diff --git a/source/mvd_client.c b/source/mvd_client.c
index bd3c794..a4e3370 100644
--- a/source/mvd_client.c
+++ b/source/mvd_client.c
@@ -1,5 +1,5 @@
/*
-Copyright (C) 2003-2006 Andrey Nazarov
+Copyright (C) 2003-2008 Andrey Nazarov
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
@@ -19,15 +19,61 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
//
-// mvd_client.c
+// mvd_client.c -- MVD/GTV client
//
#include "sv_local.h"
#include "mvd_local.h"
+#include "gtv.h"
#include <setjmp.h>
+#define GTV_DEFAULT_BACKOFF (5*1000)
+#define GTV_MAXIMUM_BACKOFF (5*3600*1000)
+
+typedef enum {
+ GTV_DISCONNECTED, // disconnected
+ GTV_CONNECTING, // connect() in progress
+ GTV_PREPARING, // waiting for server hello
+ GTV_CONNECTED, // keeping connection alive
+ GTV_ACTIVE, // actively running MVD stream
+ GTV_OVERFLOWED // recovering from delay buffer overflow
+} gtv_state_t;
+
+typedef struct gtv_s {
+ list_t entry;
+
+ int id;
+ char name[MAX_MVD_NAME];
+ gtv_state_t state;
+ mvd_t *mvd;
+
+ // connection variables
+ netstream_t stream;
+ char address[MAX_QPATH];
+ byte *data;
+ size_t msglen;
+#if USE_ZLIB
+ qboolean z_act; // true when actively inflating
+ z_stream z_str;
+ fifo_t z_buf;
+#endif
+ unsigned last_rcvd;
+ unsigned last_sent;
+ void (*drop)( struct gtv_s * );
+ void (*destroy)( struct gtv_s * );
+ void (*run)( struct gtv_s * );
+ unsigned retry_time;
+ unsigned retry_backoff;
+
+ // demo related variables
+ fileHandle_t demoplayback;
+ int demoloop;
+ string_entry_t *demohead, *demoentry;
+} gtv_t;
+
+static LIST_DECL( mvd_gtvs );
+
LIST_DECL( mvd_channels );
-LIST_DECL( mvd_ready );
LIST_DECL( mvd_active );
mvd_t mvd_waitingRoom;
@@ -36,52 +82,31 @@ int mvd_chanid;
jmp_buf mvd_jmpbuf;
-cvar_t *mvd_running;
-cvar_t *mvd_shownet;
-cvar_t *mvd_debug;
-cvar_t *mvd_timeout;
-cvar_t *mvd_wait_delay;
-cvar_t *mvd_wait_percent;
-cvar_t *mvd_chase_msgs;
+cvar_t *mvd_shownet;
+cvar_t *mvd_timeout;
+cvar_t *mvd_wait_delay;
+cvar_t *mvd_wait_percent;
+cvar_t *mvd_chase_msgs;
// ====================================================================
-void MVD_Disconnect( mvd_t *mvd ) {
- if( mvd->demoplayback ) {
- FS_FCloseFile( mvd->demoplayback );
- mvd->demoplayback = 0;
- } else {
- if( Com_IsDedicated() ) {
- MVD_BroadcastPrintf( mvd, PRINT_HIGH, 0,
- "[MVD] Disconnected from the game server!\n" );
- }
- NET_Close( &mvd->stream );
- }
-
- mvd->state = MVD_DISCONNECTED;
-}
-
-static void MVD_FreePlayer( mvd_player_t *player ) {
- mvd_cs_t *cs, *next;
-
- for( cs = player->configstrings; cs; cs = next ) {
- next = cs->next;
- Z_Free( cs );
- }
-}
-
void MVD_Free( mvd_t *mvd ) {
int i;
-#if USE_ZLIB
- if( mvd->z.state ) {
- inflateEnd( &mvd->z );
+ // destroy any existing connection
+ if( mvd->gtv ) {
+ mvd->gtv->destroy( mvd->gtv );
}
- if( mvd->zbuf.data ) {
- Z_Free( mvd->zbuf.data );
+
+ // stop demo recording
+ if( mvd->demorecording ) {
+ uint16_t msglen = 0;
+ FS_Write( &msglen, 2, mvd->demorecording );
+ FS_FCloseFile( mvd->demorecording );
+ mvd->demorecording = 0;
}
-#endif
+
for( i = 0; i < mvd->maxclients; i++ ) {
MVD_FreePlayer( &mvd->players[i] );
}
@@ -89,16 +114,15 @@ void MVD_Free( mvd_t *mvd ) {
CM_FreeMap( &mvd->cm );
+ Z_Free( mvd->delay.data );
+
List_Remove( &mvd->active );
- List_Remove( &mvd->ready );
List_Remove( &mvd->entry );
Z_Free( mvd );
}
void MVD_Destroy( mvd_t *mvd ) {
- udpClient_t *u, *unext;
- tcpClient_t *t;
- uint16_t length;
+ mvd_client_t *client, *next;
// update channel menus
if( !LIST_EMPTY( &mvd->active ) ) {
@@ -106,42 +130,14 @@ void MVD_Destroy( mvd_t *mvd ) {
}
// cause UDP clients to reconnect
- LIST_FOR_EACH_SAFE( udpClient_t, u, unext, &mvd->udpClients, entry ) {
- MVD_SwitchChannel( u, &mvd_waitingRoom );
- }
-
- // send EOF to TCP clients and kick them
- length = 0;
- LIST_FOR_EACH( tcpClient_t, t, &mvd->tcpClients, mvdEntry ) {
- SV_HttpWrite( t, &length, 2 );
- SV_HttpFinish( t );
- SV_HttpDrop( t, "channel destroyed" );
- NET_Run( &t->stream );
- }
-
- // stop demo recording
- if( mvd->demorecording ) {
- uint16_t msglen = 0;
- FS_Write( &msglen, 2, mvd->demorecording );
- FS_FCloseFile( mvd->demorecording );
- mvd->demorecording = 0;
+ LIST_FOR_EACH_SAFE( mvd_client_t, client, next, &mvd->clients, entry ) {
+ MVD_SwitchChannel( client, &mvd_waitingRoom );
}
- // disconnect if still connected
- MVD_Disconnect( mvd );
-
// free all channel data
MVD_Free( mvd );
}
-void MVD_Drop( mvd_t *mvd ) {
- if( mvd->state < MVD_WAITING ) {
- MVD_Destroy( mvd );
- } else {
- MVD_Disconnect( mvd );
- }
-}
-
void MVD_Destroyf( mvd_t *mvd, const char *fmt, ... ) {
va_list argptr;
char text[MAXPRINTMSG];
@@ -157,7 +153,54 @@ void MVD_Destroyf( mvd_t *mvd, const char *fmt, ... ) {
longjmp( mvd_jmpbuf, -1 );
}
-void MVD_Dropf( mvd_t *mvd, const char *fmt, ... ) {
+mvd_t *MVD_SetChannel( int arg ) {
+ char *s = Cmd_Argv( arg );
+ mvd_t *mvd;
+ int id;
+
+ if( LIST_EMPTY( &mvd_channels ) ) {
+ Com_Printf( "No active channels.\n" );
+ return NULL;
+ }
+
+ if( !*s ) {
+ if( List_Count( &mvd_channels ) == 1 ) {
+ return LIST_FIRST( mvd_t, &mvd_channels, entry );
+ }
+ Com_Printf( "Please specify an exact channel ID.\n" );
+ return NULL;
+ }
+
+ if( COM_IsUint( s ) ) {
+ id = atoi( s );
+ LIST_FOR_EACH( mvd_t, mvd, &mvd_channels, entry ) {
+ if( mvd->id == id ) {
+ return mvd;
+ }
+ }
+ } else {
+ LIST_FOR_EACH( mvd_t, mvd, &mvd_channels, entry ) {
+ if( !strcmp( mvd->name, s ) ) {
+ return mvd;
+ }
+ }
+ }
+
+ Com_Printf( "No such channel ID: %s\n", s );
+ return NULL;
+}
+
+
+/*
+====================================================================
+
+COMMON GTV STUFF
+
+====================================================================
+*/
+
+static void q_noreturn q_printf( 2, 3 )
+gtv_dropf( gtv_t *gtv, const char *fmt, ... ) {
va_list argptr;
char text[MAXPRINTMSG];
@@ -165,660 +208,908 @@ void MVD_Dropf( mvd_t *mvd, const char *fmt, ... ) {
Q_vsnprintf( text, sizeof( text ), fmt, argptr );
va_end( argptr );
- Com_Printf( "[%s] %s\n", mvd->name, text );
+ Com_Printf( "[%s] %s\n", gtv->name, text );
- MVD_Drop( mvd );
+ gtv->drop( gtv );
longjmp( mvd_jmpbuf, -1 );
}
-void MVD_DPrintf( const char *fmt, ... ) {
+static void q_noreturn q_printf( 2, 3 )
+gtv_destroyf( gtv_t *gtv, const char *fmt, ... ) {
va_list argptr;
char text[MAXPRINTMSG];
- if( !mvd_debug->integer ) {
- return;
- }
-
va_start( argptr, fmt );
Q_vsnprintf( text, sizeof( text ), fmt, argptr );
va_end( argptr );
- Com_Printf( S_COLOR_BLUE "%s", text );
+ Com_Printf( "[%s] %s\n", gtv->name, text );
+
+ gtv->destroy( gtv );
+
+ longjmp( mvd_jmpbuf, -1 );
}
-static void MVD_HttpPrintf( mvd_t *mvd, const char *fmt, ... ) {
- char buffer[MAX_STRING_CHARS];
- va_list argptr;
- size_t len;
+static mvd_t *create_channel( gtv_t *gtv ) {
+ mvd_t *mvd;
- va_start( argptr, fmt );
- len = Q_vsnprintf( buffer, sizeof( buffer ), fmt, argptr );
- va_end( argptr );
+ mvd = MVD_Mallocz( sizeof( *mvd ) );
+ mvd->gtv = gtv;
+ mvd->id = gtv->id;
+ Q_strlcpy( mvd->name, gtv->name, sizeof( mvd->name ) );
+ mvd->pool.edicts = mvd->edicts;
+ mvd->pool.edict_size = sizeof( edict_t );
+ mvd->pool.max_edicts = MAX_EDICTS;
+ mvd->pm_type = PM_SPECTATOR;
+ mvd->min_packets = mvd_wait_delay->value * 10;
+ List_Init( &mvd->clients );
+ List_Init( &mvd->entry );
+ List_Init( &mvd->active );
+
+ return mvd;
+}
- if( len >= sizeof( buffer ) || FIFO_Write( &mvd->stream.send, buffer, len ) != len ) {
- MVD_Dropf( mvd, "%s: overflow", __func__ );
+static gtv_t *gtv_set_conn( int arg ) {
+ char *s = Cmd_Argv( arg );
+ gtv_t *gtv;
+ int id;
+
+ if( LIST_EMPTY( &mvd_gtvs ) ) {
+ Com_Printf( "No GTV connections.\n" );
+ return NULL;
}
+
+ if( !*s ) {
+ if( List_Count( &mvd_gtvs ) == 1 ) {
+ return LIST_FIRST( gtv_t, &mvd_gtvs, entry );
+ }
+ Com_Printf( "Please specify an exact connection ID.\n" );
+ return NULL;
+ }
+
+ if( COM_IsUint( s ) ) {
+ id = atoi( s );
+ LIST_FOR_EACH( gtv_t, gtv, &mvd_gtvs, entry ) {
+ if( gtv->id == id ) {
+ return gtv;
+ }
+ }
+ } else {
+ LIST_FOR_EACH( gtv_t, gtv, &mvd_gtvs, entry ) {
+ if( !strcmp( gtv->name, s ) ) {
+ return gtv;
+ }
+ }
+ }
+
+ Com_Printf( "No such connection ID: %s\n", s );
+ return NULL;
}
-void MVD_ClearState( mvd_t *mvd ) {
- mvd_player_t *player;
- int i;
+/*
+==============
+MVD_Frame
- // clear all entities, don't trust num_edicts as it is possible
- // to miscount removed entities
- memset( mvd->edicts, 0, sizeof( mvd->edicts ) );
- mvd->pool.num_edicts = 0;
+Called from main server loop.
+==============
+*/
+int MVD_Frame( void ) {
+ gtv_t *gtv, *next;
+ int connections = 0;
- // clear all players
- for( i = 0; i < mvd->maxclients; i++ ) {
- player = &mvd->players[i];
- MVD_FreePlayer( player );
- memset( player, 0, sizeof( *player ) );
+ // run all GTV connections (but not demos)
+ LIST_FOR_EACH_SAFE( gtv_t, gtv, next, &mvd_gtvs, entry ) {
+ if( setjmp( mvd_jmpbuf ) ) {
+ continue;
+ }
+
+ gtv->run( gtv );
+
+ connections++;
}
- mvd->numplayers = 0;
- // free current map
- CM_FreeMap( &mvd->cm );
+ return connections;
+}
+
+
+/*
+====================================================================
+
+DEMO PLAYER
+
+====================================================================
+*/
+
+static void demo_play_next( gtv_t *gtv, string_entry_t *entry );
- if( mvd->intermission ) {
- // save oldscores
- //strcpy( mvd->oldscores, mvd->layout );
+static qboolean demo_read_message( fileHandle_t f ) {
+ size_t ret;
+ uint16_t msglen;
+
+ ret = FS_Read( &msglen, 2, f );
+ if( ret != 2 ) {
+ return qfalse;
+ }
+ if( !msglen ) {
+ return qfalse;
+ }
+ msglen = LittleShort( msglen );
+ if( msglen > MAX_MSGLEN ) {
+ return qfalse;
+ }
+ ret = FS_Read( msg_read_buffer, msglen, f );
+ if( ret != msglen ) {
+ return qfalse;
}
- memset( mvd->configstrings, 0, sizeof( mvd->configstrings ) );
- mvd->layout[0] = 0;
+ SZ_Init( &msg_read, msg_read_buffer, sizeof( msg_read_buffer ) );
+ msg_read.cursize = msglen;
- mvd->framenum = 0;
- // intermission flag will be cleared in MVD_ChangeLevel
+ return qtrue;
}
-void MVD_BeginWaiting( mvd_t *mvd ) {
- int maxDelay = mvd_wait_delay->value * 1000;
+static qboolean demo_read_frame( mvd_t *mvd ) {
+ gtv_t *gtv = mvd->gtv;
- mvd->state = MVD_WAITING;
- mvd->waitTime = svs.realtime;
- mvd->waitDelay = 5000 + 1000 * mvd->waitCount;
- if( mvd->waitDelay > maxDelay ) {
- mvd->waitDelay = maxDelay;
+ if( mvd->state == MVD_WAITING ) {
+ return qfalse; // paused by user
+ }
+ if( !gtv ) {
+ MVD_Destroyf( mvd, "End of MVD stream reached" );
}
- mvd->waitCount++;
-}
-static void MVD_EmitGamestate( mvd_t *mvd ) {
- char *string;
- int i, j;
- entity_state_t *es;
- player_state_t *ps;
- size_t length;
- uint8_t *patch;
- int flags, extra, portalbytes;
- byte portalbits[MAX_MAP_AREAS/8];
+ if( !demo_read_message( gtv->demoplayback ) ) {
+ demo_play_next( gtv, gtv->demoentry->next );
+ return qtrue;
+ }
- patch = SZ_GetSpace( &msg_write, 2 );
+ MVD_ParseMessage( mvd );
+ return qtrue;
+}
- // send the serverdata
- MSG_WriteByte( mvd_serverdata );
- MSG_WriteLong( PROTOCOL_VERSION_MVD );
- MSG_WriteShort( PROTOCOL_VERSION_MVD_CURRENT );
- MSG_WriteLong( mvd->servercount );
- MSG_WriteString( mvd->gamedir );
- MSG_WriteShort( mvd->clientNum );
+static void demo_play_next( gtv_t *gtv, string_entry_t *entry ) {
+ uint32_t magic = 0;
- // send configstrings
- for( i = 0; i < MAX_CONFIGSTRINGS; i++ ) {
- string = mvd->configstrings[i];
- if( !string[0] ) {
- continue;
- }
- length = strlen( string );
- if( length > MAX_QPATH ) {
- length = MAX_QPATH;
+ if( !entry ) {
+ if( gtv->demoloop ) {
+ if( --gtv->demoloop == 0 ) {
+ gtv_destroyf( gtv, "End of play list reached" );
+ }
}
+ entry = gtv->demohead;
+ }
- MSG_WriteShort( i );
- MSG_WriteData( string, length );
- MSG_WriteByte( 0 );
+ // close previous file
+ if( gtv->demoplayback ) {
+ FS_FCloseFile( gtv->demoplayback );
+ gtv->demoplayback = 0;
}
- MSG_WriteShort( MAX_CONFIGSTRINGS );
- // send baseline frame
- portalbytes = CM_WritePortalBits( &sv.cm, portalbits );
- MSG_WriteByte( portalbytes );
- MSG_WriteData( portalbits, portalbytes );
-
- // send base player states
- flags = 0;
- if( sv_mvd_noblend->integer ) {
- flags |= MSG_PS_IGNORE_BLEND;
+ // open new file
+ FS_FOpenFile( entry->string, &gtv->demoplayback, FS_MODE_READ );
+ if( !gtv->demoplayback ) {
+ gtv_destroyf( gtv, "Couldn't reopen %s", entry->string );
}
- if( sv_mvd_nogun->integer ) {
- flags |= MSG_PS_IGNORE_GUNINDEX|MSG_PS_IGNORE_GUNFRAMES;
+
+ // figure out if file is compressed and check magic
+ if( FS_Read( &magic, 4, gtv->demoplayback ) != 4 ) {
+ gtv_destroyf( gtv, "Couldn't read magic from %s", entry->string );
}
- for( i = 0; i < mvd->maxclients; i++ ) {
- ps = &mvd->players[i].ps;
- extra = 0;
- if( !PPS_INUSE( ps ) ) {
- extra |= MSG_PS_REMOVE;
+ if( ( ( LittleLong( magic ) & 0xe0ffffff ) == 0x00088b1f ) ) {
+ if( !FS_FilterFile( gtv->demoplayback ) ) {
+ gtv_destroyf( gtv, "Couldn't install gzip filter on %s", entry->string );
+ }
+ if( FS_Read( &magic, 4, gtv->demoplayback ) != 4 ) {
+ gtv_destroyf( gtv, "Couldn't read magic from %s", entry->string );
}
- MSG_WriteDeltaPlayerstate_Packet( NULL, ps, i, flags | extra );
}
- MSG_WriteByte( CLIENTNUM_NONE );
+ if( magic != MVD_MAGIC ) {
+ gtv_destroyf( gtv, "%s is not a MVD2 file", entry->string );
+ }
- // send base entity states
- for( i = 1; i < mvd->pool.num_edicts; i++ ) {
- es = &mvd->edicts[i].s;
- flags = 0;
- if( i <= mvd->maxclients ) {
- ps = &mvd->players[ i - 1 ].ps;
- if( PPS_INUSE( ps ) && ps->pmove.pm_type == PM_NORMAL ) {
- flags |= MSG_ES_FIRSTPERSON;
- }
- }
- if( ( j = es->number ) == 0 ) {
- flags |= MSG_ES_REMOVE;
- }
- es->number = i;
- MSG_WriteDeltaEntity( NULL, es, flags );
- es->number = j;
+ // read the first message
+ if( !demo_read_message( gtv->demoplayback ) ) {
+ gtv_destroyf( gtv, "Couldn't read first message from %s", entry->string );
}
- MSG_WriteShort( 0 );
- // TODO: write private layouts/configstrings
+ // create MVD channel
+ if( !gtv->mvd ) {
+ gtv->mvd = create_channel( gtv );
+ gtv->mvd->read_frame = demo_read_frame;
+ }
- length = msg_write.cursize - 2;
- patch[0] = length & 255;
- patch[1] = ( length >> 8 ) & 255;
-}
+ // parse gamestate
+ MVD_ParseMessage( gtv->mvd );
+ if( !gtv->mvd->state ) {
+ gtv_destroyf( gtv, "First message of %s does not contain gamestate", entry->string );
+ }
-void MVD_SendGamestate( tcpClient_t *client ) {
- MVD_EmitGamestate( client->mvd );
+ gtv->mvd->state = MVD_READING;
- Com_DPrintf( "Sent gamestate to MVD client %s\n",
- NET_AdrToString( &client->stream.address ) );
- client->state = cs_spawned;
+ Com_Printf( "[%s] Reading from %s\n", gtv->name, entry->string );
- SV_HttpWrite( client, msg_write.data, msg_write.cursize );
- SZ_Clear( &msg_write );
+ // reset state
+ gtv->demoentry = entry;
+
+ // set channel address
+ Q_strlcpy( gtv->address, COM_SkipPath( entry->string ), sizeof( gtv->address ) );
}
-void MVD_GetStatus( void ) {
- char buffer[MAX_STRING_CHARS];
- mvd_t *mvd;
- int count, len;
+static void demo_destroy( gtv_t *gtv ) {
+ string_entry_t *entry, *next;
+ mvd_t *mvd = gtv->mvd;
+
+ if( mvd ) {
+ mvd->gtv = NULL;
+ if( !mvd->state ) {
+ MVD_Free( mvd );
+ }
+ }
- SV_HttpPrintf( "HTTP/1.0 200 OK\r\n" );
+ if( gtv->demoplayback ) {
+ FS_FCloseFile( gtv->demoplayback );
+ gtv->demoplayback = 0;
+ }
- if( http_client->method == HTTP_METHOD_HEAD ) {
- SV_HttpPrintf( "\r\n" );
- SV_HttpDrop( http_client, "200 OK" );
- return;
+ for( entry = gtv->demohead; entry; entry = next ) {
+ next = entry->next;
+ Z_Free( entry );
}
- SV_HttpPrintf(
- "Content-Type: text/html; charset=us-ascii\r\n"
- "\r\n" );
+ Z_Free( gtv );
+}
+
- count = SV_CountClients();
- len = Q_EscapeMarkup( buffer, sv_hostname->string, sizeof( buffer ) );
- Q_snprintf( buffer + len, sizeof( buffer ) - len, " - %d/%d",
- count, sv_maxclients->integer - sv_reserved_slots->integer );
+/*
+====================================================================
- SV_HttpHeader( buffer );
+GTV CONNECTIONS
- buffer[len] = 0;
- SV_HttpPrintf( "<h1>%s</h1><p>This server has ", buffer );
+====================================================================
+*/
- count = List_Count( &mvd_ready );
- if( count ) {
- SV_HttpPrintf( "%d channel%s available, %d active. ",
- count, count == 1 ? "" : "s", List_Count( &mvd_active ) );
- } else {
- SV_HttpPrintf( "no channels available. " );
+static qboolean gtv_wait_stop( mvd_t *mvd ) {
+ int usage;
+
+ // see how many frames are buffered
+ if( mvd->num_packets >= mvd->min_packets ) {
+ Com_Printf( "[%s] Waiting finished, reading...\n", mvd->name );
+ mvd->state = MVD_READING;
+ return qtrue;
}
- count = List_Count( &mvd_waitingRoom.udpClients );
- if( count ) {
- SV_HttpPrintf( "Waiting room has %d spectator%s.</p>",
- count, count == 1 ? "" : "s" );
- } else {
- SV_HttpPrintf( "Waiting room is empty.</p>" );
+ // see how much data is buffered
+ usage = FIFO_Percent( &mvd->delay );
+ if( usage >= mvd_wait_percent->value ) {
+ Com_Printf( "[%s] Buffering finished, reading...\n", mvd->name );
+ mvd->state = MVD_READING;
+ return qtrue;
}
-
- if( !LIST_EMPTY( &mvd_ready ) ) {
- SV_HttpPrintf(
- "<table border=\"1\"><tr>"
- "<th>ID</th><th>Name</th><th>Map</th><th>Specs</th><th>Players</th></tr>" );
-
- LIST_FOR_EACH( mvd_t, mvd, &mvd_ready, ready ) {
- SV_HttpPrintf( "<tr><td>" );
- if( sv_mvd_enable->integer ) {
- SV_HttpPrintf( "<a href=\"http://%s/mvdstream/%d\">%d</a>",
- http_host, mvd->id, mvd->id );
- } else {
- SV_HttpPrintf( "%d", mvd->id );
- }
- SV_HttpPrintf( "</td><td>" );
-
- Q_EscapeMarkup( buffer, mvd->name, sizeof( buffer ) );
- if( sv_mvd_enable->integer ) {
- SV_HttpPrintf( "<a href=\"http://%s/mvdstream/%d\">%s</a>",
- http_host, mvd->id, buffer );
- } else {
- SV_HttpPrintf( "%s", buffer );
- }
- Q_EscapeMarkup( buffer, mvd->mapname, sizeof( buffer ) );
- count = List_Count( &mvd->udpClients );
- SV_HttpPrintf( "</td><td>%s</td><td>%d</td><td>%d</td></tr>",
- buffer, count, mvd->numplayers );
- }
- SV_HttpPrintf( "</table>" );
+ return qfalse;
+}
+
+// ran out of buffers
+static void gtv_wait_start( mvd_t *mvd ) {
+ int tr = mvd_wait_delay->value * 10;
+
+ // if not connected, kill it
+ if( !mvd->gtv ) {
+ MVD_Destroyf( mvd, "End of MVD stream reached" );
}
- SV_HttpPrintf(
- "<p><a href=\"quake2://%s\">Join this server</a></p>", http_host );
+ Com_Printf( "[%s] Buffering data...\n", mvd->name );
- SV_HttpFooter();
+ // notify spectators
+ if( Com_IsDedicated() ) {
+ MVD_BroadcastPrintf( mvd, PRINT_HIGH, 0,
+ "[MVD] Buffering data, please wait...\n" );
+ }
- SV_HttpDrop( http_client, "200 OK" );
+ mvd->state = MVD_WAITING;
+ mvd->min_packets = 50 + 10 * mvd->underflows;
+ if( mvd->min_packets > tr ) {
+ mvd->min_packets = tr;
+ }
+ mvd->underflows++;
}
-static mvd_t *MVD_SetStream( const char *uri ) {
- mvd_t *mvd;
- int id;
+static qboolean gtv_read_frame( mvd_t *mvd ) {
+ uint16_t msglen;
- if( LIST_EMPTY( &mvd_ready ) ) {
- SV_HttpReject( "503 Service Unavailable",
- "No MVD streams are available on this server." );
- return NULL;
+ switch( mvd->state ) {
+ case MVD_WAITING:
+ if( !gtv_wait_stop( mvd ) ) {
+ return qfalse;
+ }
+ break;
+ case MVD_READING:
+ if( !mvd->num_packets ) {
+ gtv_wait_start( mvd );
+ return qfalse;
+ }
+ break;
+ default:
+ MVD_Destroyf( mvd, "%s: bad mvd->state", __func__ );
}
- if( *uri == '/' ) {
- uri++;
+ // NOTE: if we got here, delay buffer MUST contain
+ // at least one complete, non-empty packet
+
+ // parse msglen
+ if( FIFO_Read( &mvd->delay, &msglen, 2 ) != 2 ) {
+ MVD_Destroyf( mvd, "%s: partial data", __func__ );
}
- if( *uri == 0 ) {
- if( List_Count( &mvd_ready ) == 1 ) {
- return LIST_FIRST( mvd_t, &mvd_ready, ready );
- }
- strcpy( http_header, "Cache-Control: no-cache\r\n" );
- SV_HttpReject( "300 Multiple Choices",
- "Please specify an exact stream ID." );
- return NULL;
+ msglen = LittleShort( msglen );
+ if( msglen < 1 || msglen > MAX_MSGLEN ) {
+ MVD_Destroyf( mvd, "%s: invalid msglen", __func__ );
}
- id = atoi( uri );
- LIST_FOR_EACH( mvd_t, mvd, &mvd_ready, ready ) {
- if( mvd->id == id ) {
- return mvd;
- }
+ // read this message
+ if( !FIFO_ReadMessage( &mvd->delay, msglen ) ) {
+ MVD_Destroyf( mvd, "%s: partial data", __func__ );
}
- SV_HttpReject( "404 Not Found",
- "Requested MVD stream was not found on this server." );
- return NULL;
-}
+ // decrement buffered packets counter
+ mvd->num_packets--;
-void MVD_GetStream( const char *uri ) {
- mvd_t *mvd;
+ // parse it
+ MVD_ParseMessage( mvd );
+ return qtrue;
+}
- mvd = MVD_SetStream( uri );
- if( !mvd ) {
- return;
+static void write_stream( gtv_t *gtv, void *data, size_t len ) {
+ if( !FIFO_TryWrite( &gtv->stream.send, data, len ) ) {
+ gtv_destroyf( gtv, "Send buffer overflowed" );
}
- if( http_client->method == HTTP_METHOD_HEAD ) {
- SV_HttpPrintf( "HTTP/1.0 200 OK\r\n\r\n" );
- SV_HttpDrop( http_client, "200 OK " );
- return;
- }
+ // don't timeout
+ gtv->last_sent = svs.realtime;
+}
- List_Append( &mvd->tcpClients, &http_client->mvdEntry );
- http_client->mvd = mvd;
+static void write_message( gtv_t *gtv, gtv_clientop_t op ) {
+ byte header[3];
+ size_t len = msg_write.cursize + 1;
- SV_MvdInitStream();
+ header[0] = len & 255;
+ header[1] = ( len >> 8 ) & 255;
+ header[2] = op;
+ write_stream( gtv, header, sizeof( header ) );
- MVD_SendGamestate( http_client );
+ write_stream( gtv, msg_write.data, msg_write.cursize );
}
-void MVD_ChangeLevel( mvd_t *mvd ) {
- udpClient_t *client;
+#if USE_ZLIB
+static voidpf gtv_zalloc OF(( voidpf opaque, uInt items, uInt size )) {
+ return MVD_Malloc( items * size );
+}
- if( sv.state != ss_broadcast ) {
- MVD_Spawn_f(); // the game is just starting
- return;
+static void gtv_zfree OF(( voidpf opaque, voidpf address )) {
+ Z_Free( address );
+}
+#endif
+
+static void parse_hello( gtv_t *gtv ) {
+ int flags;
+
+ if( gtv->state >= GTV_CONNECTED ) {
+ gtv_destroyf( gtv, "Duplicated server hello" );
}
- // cause all UDP clients to reconnect
- MSG_WriteByte( svc_stufftext );
- MSG_WriteString( va( "changing map=%s; reconnect\n", mvd->mapname ) );
+ flags = MSG_ReadLong();
- LIST_FOR_EACH( udpClient_t, client, &mvd->udpClients, entry ) {
- if( mvd->intermission && client->cl->state == cs_spawned ) {
- // make them switch to previous target instead of MVD dummy
- client->target = client->oldtarget;
- client->oldtarget = NULL;
+#if USE_ZLIB
+ if( flags & GTF_DEFLATE ) {
+ if( !gtv->z_str.state ) {
+ gtv->z_str.zalloc = gtv_zalloc;
+ gtv->z_str.zfree = gtv_zfree;
+ if( inflateInit( &gtv->z_str ) != Z_OK ) {
+ gtv_destroyf( gtv, "inflateInit() failed: %s",
+ gtv->z_str.msg );
+ }
+ }
+ if( !gtv->z_buf.data ) {
+ gtv->z_buf.data = MVD_Malloc( MAX_MSGLEN );
+ gtv->z_buf.size = MAX_MSGLEN;
}
- SV_ClientReset( client->cl );
- client->cl->spawncount = mvd->servercount;
- SV_ClientAddMessage( client->cl, MSG_RELIABLE );
+ gtv->z_act = qtrue; // remaining data is deflated
}
+#endif
- SZ_Clear( &msg_write );
+ gtv->state = GTV_CONNECTED;
- mvd->intermission = qfalse;
+ Com_Printf( "[%s] Sending stream request...\n", gtv->name );
- SV_SendAsyncPackets();
+ // send stream request
+ write_message( gtv, GTC_STREAM_START );
}
-static void MVD_PlayNext( mvd_t *mvd, string_entry_t *entry ) {
- uint32_t magic = 0;
+static void parse_stream_start( gtv_t *gtv ) {
+ if( gtv->state != GTV_CONNECTED ) {
+ gtv_destroyf( gtv, "Unexpected stream start packet" );
+ }
- if( !entry ) {
- if( mvd->demoloop ) {
- if( --mvd->demoloop == 0 ) {
- MVD_Destroyf( mvd, "End of play list reached" );
- return;
- }
+ // create the channel
+ if( !gtv->mvd ) {
+ mvd_t *mvd = create_channel( gtv );
+
+ // allocate delay buffer
+ mvd->delay.data = MVD_Malloc( MAX_MSGLEN * 2 );
+ mvd->delay.size = MAX_MSGLEN * 2;
+ mvd->read_frame = gtv_read_frame;
+
+ gtv->mvd = mvd;
+ } else {
+ if( Com_IsDedicated() ) {
+ // notify spectators
+ MVD_BroadcastPrintf( gtv->mvd, PRINT_HIGH, 0,
+ "[MVD] Stream has been resumed.\n" );
}
- entry = mvd->demohead;
+ // reset delay to default
+ gtv->mvd->min_packets = mvd_wait_delay->value * 10;
+ gtv->mvd->underflows = 0;
}
- if( mvd->demoplayback ) {
- FS_FCloseFile( mvd->demoplayback );
- mvd->demoplayback = 0;
- }
+ Com_Printf( "[%s] Stream start marker received.\n", gtv->name );
+
+ gtv->state = GTV_ACTIVE;
+}
- FS_FOpenFile( entry->string, &mvd->demoplayback, FS_MODE_READ );
- if( !mvd->demoplayback ) {
- MVD_Destroyf( mvd, "Couldn't reopen %s", entry->string );
+static void parse_stream_stop( gtv_t *gtv ) {
+ if( gtv->state < GTV_ACTIVE ) {
+ gtv_destroyf( gtv, "Unexpected stream stop packet" );
}
- if( FS_Read( &magic, 4, mvd->demoplayback ) != 4 ) {
- MVD_Destroyf( mvd, "Couldn't read magic out of %s", entry->string );
+ if( gtv->mvd && Com_IsDedicated() && gtv->state == GTV_ACTIVE ) {
+ // notify spectators
+ MVD_BroadcastPrintf( gtv->mvd, PRINT_HIGH, 0,
+ "[MVD] Stream has been suspended.\n" );
}
- if( ( ( LittleLong( magic ) & 0xe0ffffff ) == 0x00088b1f ) ) {
- if( !FS_FilterFile( mvd->demoplayback ) ) {
- MVD_Destroyf( mvd, "Couldn't install gzip filter on %s", entry->string );
- }
- if( FS_Read( &magic, 4, mvd->demoplayback ) != 4 ) {
- MVD_Destroyf( mvd, "Couldn't read magic out of %s", entry->string );
- }
+
+ Com_Printf( "[%s] Stream stop marker received.\n", gtv->name );
+
+ gtv->state = GTV_CONNECTED;
+}
+
+static void parse_stream_data( gtv_t *gtv ) {
+ mvd_t *mvd = gtv->mvd;
+
+ if( gtv->state < GTV_ACTIVE ) {
+ gtv_destroyf( gtv, "Unexpected stream data packet" );
}
- if( magic != MVD_MAGIC ) {
- MVD_Destroyf( mvd, "%s is not a MVD2 file", entry->string );
+
+ // ignore if still recovering from overflow
+ if( gtv->state == GTV_OVERFLOWED ) {
+ msg_read.readcount = msg_read.cursize;
+ return;
}
- Com_Printf( "[%s] Reading from %s\n", mvd->name, entry->string );
+ if( !mvd->state ) {
+ // parse it in place
+ MVD_ParseMessage( mvd );
+ } else {
+ byte *data = msg_read.data + 1;
+ size_t len = msg_read.cursize - 1;
+ uint16_t msglen;
+
+ // see if this packet fits
+ if( FIFO_Write( &mvd->delay, NULL, len + 2 ) != len + 2 ) {
+ if( mvd->state == MVD_WAITING ) {
+ // if delay buffer overflowed in waiting state,
+ // something is seriously wrong, disconnect for safety
+ gtv_destroyf( gtv, "Delay buffer overflowed in waiting state" );
+ }
+
+ // oops, overflowed
+ Com_Printf( "[%s] Delay buffer overflowed!\n", gtv->name );
- // reset state
- mvd->demoentry = entry;
+ if( Com_IsDedicated() ) {
+ // notify spectators
+ MVD_BroadcastPrintf( mvd, PRINT_HIGH, 0,
+ "[MVD] Delay buffer overflowed!\n" );
+ }
- // set channel address
- Q_strlcpy( mvd->address, COM_SkipPath( entry->string ), sizeof( mvd->address ) );
+ // clear entire delay buffer
+ FIFO_Clear( &mvd->delay );
+ mvd->state = MVD_WAITING;
+ mvd->min_packets = 50;
+ mvd->overflows++;
+
+ // request restart of the stream
+ write_message( gtv, GTC_STREAM_STOP );
+ write_message( gtv, GTC_STREAM_START );
+
+ // ignore any pending data
+ gtv->state = GTV_OVERFLOWED;
+ return;
+ }
+
+ // write it into delay buffer
+ msglen = LittleShort( len );
+ FIFO_Write( &mvd->delay, &msglen, 2 );
+ FIFO_Write( &mvd->delay, data, len );
+
+ // increment buffered packets counter
+ mvd->num_packets++;
+
+ msg_read.readcount = msg_read.cursize;
+ }
}
-void MVD_Finish( mvd_t *mvd, const char *reason ) {
- Com_Printf( "[%s] %s\n", mvd->name, reason );
+static void send_hello( gtv_t *gtv ) {
+ int flags, maxbuf;
- if( mvd->demoentry ) {
- MVD_PlayNext( mvd, mvd->demoentry->next );
- mvd->state = MVD_PREPARING;
- List_Delete( &mvd_ready );
+ flags = 0;
+#if USE_ZLIB
+ flags |= GTF_DEFLATE;
+#endif
+
+ if( gtv->mvd ) {
+ maxbuf = gtv->mvd->min_packets - 10;
} else {
- MVD_Destroy( mvd );
+ maxbuf = mvd_wait_delay->value * 10 - 10;
+ }
+ if( maxbuf < 0 ) {
+ maxbuf = 0;
}
- longjmp( mvd_jmpbuf, -1 );
+ MSG_WriteShort( GTV_PROTOCOL_VERSION );
+ MSG_WriteLong( flags );
+ MSG_WriteShort( maxbuf );
+ MSG_WriteLong( 0 );
+ MSG_WriteString( NULL );
+ MSG_WriteString( com_version->string );
+ write_message( gtv, GTC_HELLO );
+ SZ_Clear( &msg_write );
}
-static void MVD_ReadDemo( mvd_t *mvd ) {
- byte *data;
- size_t length, read, total = 0;
- do {
- data = FIFO_Reserve( &mvd->stream.recv, &length );
- if( !length ) {
- return;
+static qboolean parse_message( gtv_t *gtv, fifo_t *fifo ) {
+ uint32_t magic;
+ uint16_t msglen;
+ int cmd;
+
+ // check magic
+ if( gtv->state < GTV_PREPARING ) {
+ if( !FIFO_TryRead( fifo, &magic, 4 ) ) {
+ return qfalse;
+ }
+ if( magic != MVD_MAGIC ) {
+ gtv_destroyf( gtv, "Not a MVD/GTV stream" );
}
- read = FS_Read( data, length, mvd->demoplayback );
- FIFO_Commit( &mvd->stream.recv, read );
- total += read;
- } while( read );
+ gtv->state = GTV_PREPARING;
- if( !total ) {
- MVD_Dropf( mvd, "End of MVD file reached" );
+ // send client hello
+ send_hello( gtv );
+ }
+
+ // parse msglen
+ if( !gtv->msglen ) {
+ if( !FIFO_TryRead( fifo, &msglen, 2 ) ) {
+ return qfalse;
+ }
+ msglen = LittleShort( msglen );
+ if( !msglen ) {
+ gtv_dropf( gtv, "End of MVD/GTV stream" );
+ }
+ if( msglen > MAX_MSGLEN ) {
+ gtv_destroyf( gtv, "Oversize message" );
+ }
+ gtv->msglen = msglen;
}
-}
-static htcoding_t MVD_FindCoding( const char *name ) {
- if( !Q_stricmp( name, "identity" ) ) {
- return HTTP_CODING_NONE;
+ // read this message
+ if( !FIFO_ReadMessage( fifo, gtv->msglen ) ) {
+ return qfalse;
}
- if( !Q_stricmp( name, "gzip" ) ) {
- return HTTP_CODING_GZIP;
+
+ gtv->msglen = 0;
+
+ cmd = MSG_ReadByte();
+
+ if( mvd_shownet->integer == -1 ) {
+ Com_Printf( "[%"PRIz"]%d ", msg_read.cursize, cmd );
}
- if( !Q_stricmp( name, "x-gzip" ) ) {
- return HTTP_CODING_GZIP;
+
+ switch( cmd ) {
+ case GTS_HELLO:
+ parse_hello( gtv );
+ break;
+ case GTS_PONG:
+ break;
+ case GTS_STREAM_START:
+ parse_stream_start( gtv );
+ break;
+ case GTS_STREAM_STOP:
+ parse_stream_stop( gtv );
+ break;
+ case GTS_STREAM_DATA:
+ parse_stream_data( gtv );
+ break;
+ case GTS_ERROR:
+ gtv_destroyf( gtv, "Server side error occured." );
+ break;
+ case GTS_BADREQUEST:
+ gtv_destroyf( gtv, "Server refused to process our request." );
+ break;
+ case GTS_NOACCESS:
+ gtv_destroyf( gtv,
+ "You don't have permission to access "
+ "MVD/GTV stream on this server." );
+ break;
+ case GTS_DISCONNECT:
+ gtv_destroyf( gtv, "Server has been shut down." );
+ break;
+ case GTS_RECONNECT:
+ gtv_dropf( gtv, "Server has been restarted." );
+ break;
+ default:
+ gtv_destroyf( gtv, "Unknown command byte" );
}
- if( !Q_stricmp( name, "deflate" ) ) {
- return HTTP_CODING_DEFLATE;
+
+ if( msg_read.readcount > msg_read.cursize ) {
+ gtv_destroyf( gtv, "Read past end of message" );
}
- return HTTP_CODING_UNKNOWN;
+
+ gtv->last_rcvd = svs.realtime; // don't timeout
+ return qtrue;
}
-static qboolean MVD_ParseResponse( mvd_t *mvd ) {
- char key[MAX_TOKEN_CHARS];
- char *p, *token;
- const char *line;
- byte *b, *data;
- size_t length;
+#if USE_ZLIB
+static int inflate_stream( fifo_t *dst, fifo_t *src, z_streamp z ) {
+ byte *data;
+ size_t avail_in, avail_out;
+ int ret = Z_BUF_ERROR;
- while( 1 ) {
- data = FIFO_Peek( &mvd->stream.recv, &length );
- if( !length ) {
+ do {
+ data = FIFO_Peek( src, &avail_in );
+ if( !avail_in ) {
break;
}
- if( ( b = memchr( data, '\n', length ) ) != NULL ) {
- length = b - data + 1;
- }
- if( mvd->responseLength + length > MAX_NET_STRING - 1 ) {
- MVD_Dropf( mvd, "Response line exceeded maximum length" );
+ z->next_in = data;
+ z->avail_in = ( uInt )avail_in;
+
+ data = FIFO_Reserve( dst, &avail_out );
+ if( !avail_out ) {
+ break;
}
+ z->next_out = data;
+ z->avail_out = ( uInt )avail_out;
- memcpy( mvd->response + mvd->responseLength, data, length );
- mvd->responseLength += length;
- mvd->response[mvd->responseLength] = 0;
+ ret = inflate( z, Z_SYNC_FLUSH );
- FIFO_Decommit( &mvd->stream.recv, length );
+ FIFO_Decommit( src, avail_in - z->avail_in );
+ FIFO_Commit( dst, avail_out - z->avail_out );
+ } while( ret == Z_OK );
- if( !b ) {
- continue;
- }
+ return ret;
+}
- line = mvd->response;
- mvd->responseLength = 0;
-
- if( !mvd->statusCode ) {
- // parse version
- token = COM_SimpleParse( &line, NULL );
- if( !token[0] ) {
- continue; // empty line?
- }
- if( strncmp( token, "HTTP/", 5 ) ) {
- MVD_Dropf( mvd, "Malformed HTTP version" );
- }
+static void inflate_more( gtv_t *gtv ) {
+ int ret = inflate_stream( &gtv->z_buf, &gtv->stream.recv, &gtv->z_str );
- // parse status code
- token = COM_SimpleParse( &line, NULL );
- mvd->statusCode = atoi( token );
- if( !mvd->statusCode ) {
- MVD_Dropf( mvd, "Malformed HTTP status code" );
- }
+ switch( ret ) {
+ case Z_BUF_ERROR:
+ case Z_OK:
+ break;
+ case Z_STREAM_END:
+ inflateReset( &gtv->z_str );
+ gtv->z_act = qfalse;
+ break;
+ default:
+ gtv_destroyf( gtv, "inflate() failed: %s", gtv->z_str.msg );
+ }
+}
+#endif
- // parse reason phrase
- if( line ) {
- while( *line && *line <= ' ' ) {
- line++;
- }
- Q_ClearStr( mvd->statusText, line, MAX_QPATH );
- }
- } else {
- // parse header fields
- token = COM_SimpleParse( &line, NULL );
- if( !token[0] ) {
- return qtrue; // end of header
- }
- strcpy( key, token );
- p = strchr( key, ':' );
- if( !p ) {
- MVD_Dropf( mvd, "Malformed HTTP header field" );
- }
- *p = 0;
- Q_strlwr( key );
-
- token = COM_SimpleParse( &line, NULL );
- if( !strcmp( key, "content-type" ) ) {
- } else if( !strcmp( key, "content-encoding" ) ) {
- mvd->contentCoding = MVD_FindCoding( token );
- } else if( !strcmp( key, "content-length" ) ) {
- mvd->contentLength = atoi( token );
- } else if( !strcmp( key, "transfer-encoding" ) ) {
- } else if( !strcmp( key, "location" ) ) {
- }
- }
+static neterr_t run_connect( gtv_t *gtv ) {
+ neterr_t ret;
+ uint32_t magic;
+
+ // run connection
+ if( ( ret = NET_RunConnect( &gtv->stream ) ) != NET_OK ) {
+ return ret;
}
- return qfalse;
+ Com_Printf( "[%s] Connected to the game server!\n", gtv->name );
+
+ // allocate buffers
+ if( !gtv->data ) {
+ gtv->data = MVD_Malloc( MAX_MSGLEN + 128 );
+ }
+ gtv->stream.recv.data = gtv->data;
+ gtv->stream.recv.size = MAX_MSGLEN;
+ gtv->stream.send.data = gtv->data + MAX_MSGLEN;
+ gtv->stream.send.size = 128;
+
+ // don't timeout
+ gtv->last_rcvd = svs.realtime;
+
+ // send magic
+ magic = MVD_MAGIC;
+ write_stream( gtv, &magic, 4 );
+
+ return NET_OK;
}
-static inline float MVD_BufferPercent( mvd_t *mvd ) {
- size_t usage = FIFO_Usage( &mvd->stream.recv );
- size_t size = mvd->stream.recv.size;
+static neterr_t run_stream( gtv_t *gtv ) {
+ neterr_t ret;
+
+ // run network stream
+ if( ( ret = NET_RunStream( &gtv->stream ) ) != NET_OK ) {
+ return ret;
+ }
-#ifdef USE_ZLIB
- usage += FIFO_Usage( &mvd->zbuf );
- size += mvd->zbuf.size;
+#if USE_ZLIB
+ if( gtv->z_act ) {
+ do {
+ // decompress more data
+ if( gtv->z_act ) {
+ inflate_more( gtv );
+ }
+ } while( parse_message( gtv, &gtv->z_buf ) );
+ } else
#endif
+ while( parse_message( gtv, &gtv->stream.recv ) )
+ ;
- if( !size ) {
- return 0;
+ if( mvd_shownet->integer == -1 ) {
+ Com_Printf( "\n" );
}
- return usage * 100.0f / size;
+ return NET_OK;
}
-int MVD_Frame( void ) {
- mvd_t *mvd, *next;
- neterr_t ret;
- float usage;
- int connections = 0;
+static void check_timeouts( gtv_t *gtv ) {
+ unsigned timeout = mvd_timeout->value * 1000;
- LIST_FOR_EACH_SAFE( mvd_t, mvd, next, &mvd_channels, entry ) {
- if( mvd->state <= MVD_DEAD || mvd->state >= MVD_DISCONNECTED ) {
- continue;
- }
+ // drop if no data has been received for too long
+ if( svs.realtime - gtv->last_rcvd > timeout ) {
+ gtv_dropf( gtv, "Server connection timed out." );
+ }
- if( setjmp( mvd_jmpbuf ) ) {
- continue;
+ // ping if no data has been sent for too long
+ if( gtv->state < GTV_CONNECTED ) {
+ return;
+ }
+ if( svs.realtime - gtv->last_sent > 60000 ) {
+ write_message( gtv, GTC_PING );
+ }
+}
+
+static qboolean check_reconnect( gtv_t *gtv ) {
+ netadr_t adr;
+
+ if( svs.realtime - gtv->retry_time < gtv->retry_backoff ) {
+ return qfalse;
+ }
+
+ Com_Printf( "[%s] Attempting to reconnect to %s...\n",
+ gtv->name, gtv->address );
+
+ gtv->state = GTV_CONNECTING;
+
+ // don't timeout
+ gtv->last_sent = gtv->last_rcvd = svs.realtime;
+
+ if( !NET_StringToAdr( gtv->address, &adr, PORT_SERVER ) ) {
+ gtv_dropf( gtv, "Unable to lookup %s\n", gtv->address );
+ }
+
+ if( NET_Connect( &adr, &gtv->stream ) == NET_ERROR ) {
+ gtv_dropf( gtv, "%s to %s\n", NET_ErrorString(),
+ NET_AdrToString( &adr ) );
+ }
+
+ return qtrue;
+}
+
+static void gtv_run( gtv_t *gtv ) {
+ neterr_t ret = NET_AGAIN;
+
+ // check if it is time to reconnect
+ if( !gtv->state ) {
+ if( !check_reconnect( gtv ) ) {
+ return;
}
+ }
- if( mvd->demoentry ) {
- if( mvd->demoplayback ) {
- MVD_ReadDemo( mvd );
- }
- if( mvd->state == MVD_PREPARING ) {
- MVD_Parse( mvd );
- }
- continue;
+ // run network stream
+ switch( gtv->stream.state ) {
+ case NS_CONNECTING:
+ ret = run_connect( gtv );
+ if( ret == NET_AGAIN ) {
+ return;
+ }
+ if( ret == NET_OK ) {
+ case NS_CONNECTED:
+ ret = run_stream( gtv );
}
+ break;
+ default:
+ return;
+ }
- connections++;
+ switch( ret ) {
+ case NET_AGAIN:
+ case NET_OK:
+ check_timeouts( gtv );
+ break;
+ case NET_ERROR:
+ gtv_dropf( gtv, "%s to %s", NET_ErrorString(),
+ NET_AdrToString( &gtv->stream.address ) );
+ break;
+ case NET_CLOSED:
+ gtv_dropf( gtv, "Server has closed connection." );
+ break;
+ }
+}
- // process network stream
- ret = NET_Run( &mvd->stream );
- switch( ret ) {
- case NET_AGAIN:
- // check timeout
- if( mvd->lastReceived > svs.realtime ) {
- mvd->lastReceived = svs.realtime;
- }
- if( svs.realtime - mvd->lastReceived > mvd_timeout->value * 1000 ) {
- MVD_Dropf( mvd, "Connection timed out" );
- }
- continue;
- case NET_ERROR:
- MVD_Dropf( mvd, "%s to %s", NET_ErrorString(),
- NET_AdrToString( &mvd->stream.address ) );
- case NET_CLOSED:
- MVD_Dropf( mvd, "Connection closed" );
- case NET_OK:
- break;
+static void gtv_destroy( gtv_t *gtv ) {
+ mvd_t *mvd = gtv->mvd;
+
+ // any associated MVD channel is orphaned
+ if( mvd ) {
+ mvd->gtv = NULL;
+ if( !mvd->state ) {
+ // free it here, since it is not yet
+ // added to global channel list
+ MVD_Free( mvd );
+ } else if( Com_IsDedicated() ) {
+ // notify spectators
+ MVD_BroadcastPrintf( mvd, PRINT_HIGH, 0,
+ "[MVD] Disconnected from the game server!\n" );
}
+ }
- // don't timeout
- mvd->lastReceived = svs.realtime;
+ // make sure network connection is closed
+ NET_Close( &gtv->stream );
- // run MVD state machine
- switch( mvd->state ) {
- case MVD_CONNECTING:
- Com_Printf( "[%s] Connected, awaiting response...\n", mvd->name );
- mvd->state = MVD_CONNECTED;
- // fall through
- case MVD_CONNECTED:
- if( !MVD_ParseResponse( mvd ) ) {
- continue;
- }
- if( mvd->statusCode != 200 ) {
- MVD_Dropf( mvd, "HTTP request failed: %d %s",
- mvd->statusCode, mvd->statusText );
- }
- switch( mvd->contentCoding ) {
- case HTTP_CODING_NONE:
- break;
+ // unlink from the list of connections
+ List_Remove( &gtv->entry );
+
+ // free all memory buffers
#if USE_ZLIB
- case HTTP_CODING_GZIP:
- case HTTP_CODING_DEFLATE:
- if( inflateInit2( &mvd->z, 47 ) != Z_OK ) {
- MVD_Dropf( mvd, "inflateInit2() failed: %s", mvd->z.msg );
- }
- mvd->zbuf.data = MVD_Malloc( MAX_MSGLEN * 2 );
- mvd->zbuf.size = MAX_MSGLEN * 2;
- break;
+ inflateEnd( &gtv->z_str );
+ Z_Free( gtv->z_buf.data );
#endif
- default:
- MVD_Dropf( mvd, "Unsupported content encoding: %d",
- mvd->contentCoding );
- break;
- }
- Com_Printf( "[%s] Got response, awaiting gamestate...\n", mvd->name );
- mvd->state = MVD_CHECKING;
- // fall through
- case MVD_CHECKING:
- case MVD_PREPARING:
- MVD_Parse( mvd );
- if( mvd->state <= MVD_PREPARING ) {
- break;
- }
- // fall through
- case MVD_WAITING:
- if( svs.realtime - mvd->waitTime >= mvd->waitDelay ) {
- Com_Printf( "[%s] Waiting finished, reading...\n", mvd->name );
- mvd->state = MVD_READING;
- break;
- }
- usage = MVD_BufferPercent( mvd );
- if( usage >= mvd_wait_percent->value ) {
- Com_Printf( "[%s] Buffering finished, reading...\n", mvd->name );
- mvd->state = MVD_READING;
- }
- break;
- default:
- break;
+ Z_Free( gtv->data );
+ Z_Free( gtv );
+}
+
+static void gtv_drop( gtv_t *gtv ) {
+ if( gtv->stream.state < NS_CONNECTED ) {
+ gtv->retry_backoff += 15*1000;
+ } else {
+ // notify spectators
+ if( Com_IsDedicated() && gtv->mvd ) {
+ MVD_BroadcastPrintf( gtv->mvd, PRINT_HIGH, 0,
+ "[MVD] Lost connection to the game server!\n" );
+ }
+
+ if( gtv->state == GTV_CONNECTED ) {
+ gtv->retry_backoff = GTV_DEFAULT_BACKOFF;
+ } else {
+ gtv->retry_backoff += 30*1000;
}
}
- return connections;
+ if( gtv->retry_backoff > GTV_MAXIMUM_BACKOFF ) {
+ gtv->retry_backoff = GTV_MAXIMUM_BACKOFF;
+ }
+ Com_Printf( "[%s] Reconnecting in %d seconds.\n",
+ gtv->name, gtv->retry_backoff / 1000 );
+
+ NET_Close( &gtv->stream );
+#if USE_ZLIB
+ inflateReset( &gtv->z_str );
+ FIFO_Clear( &gtv->z_buf );
+ gtv->z_act = qfalse;
+#endif
+ gtv->msglen = 0;
+ gtv->state = GTV_DISCONNECTED;
+ gtv->retry_time = svs.realtime;
}
@@ -830,43 +1121,6 @@ OPERATOR COMMANDS
====================================================================
*/
-mvd_t *MVD_SetChannel( int arg ) {
- char *s = Cmd_Argv( arg );
- mvd_t *mvd;
- int id;
-
- if( LIST_EMPTY( &mvd_channels ) ) {
- Com_Printf( "No active channels.\n" );
- return NULL;
- }
-
- if( !*s ) {
- if( List_Count( &mvd_channels ) == 1 ) {
- return LIST_FIRST( mvd_t, &mvd_channels, entry );
- }
- Com_Printf( "Please specify an exact channel ID.\n" );
- return NULL;
- }
-
- if( COM_IsUint( s ) ) {
- id = atoi( s );
- LIST_FOR_EACH( mvd_t, mvd, &mvd_channels, entry ) {
- if( mvd->id == id ) {
- return mvd;
- }
- }
- } else {
- LIST_FOR_EACH( mvd_t, mvd, &mvd_channels, entry ) {
- if( !strcmp( mvd->name, s ) ) {
- return mvd;
- }
- }
- }
-
- Com_Printf( "No such channel ID: %s\n", s );
- return NULL;
-}
-
void MVD_Spawn_f( void ) {
SV_InitGame( qtrue );
@@ -884,7 +1138,7 @@ void MVD_Spawn_f( void ) {
sv.state = ss_broadcast;
}
-void MVD_ListChannels_f( void ) {
+static void MVD_ListChannels_f( void ) {
mvd_t *mvd;
int usage;
@@ -893,37 +1147,62 @@ void MVD_ListChannels_f( void ) {
return;
}
- Com_Printf( "id name map spc plr stat buf address \n"
- "-- ------------ -------- --- --- ---- --- --------------\n" );
+ Com_Printf( "id name map spc plr stat buf pack address \n"
+ "-- ------------ -------- --- --- ---- --- ---- --------------\n" );
LIST_FOR_EACH( mvd_t, mvd, &mvd_channels, entry ) {
Com_Printf( "%2d %-12.12s %-8.8s %3d %3d ",
mvd->id, mvd->name, mvd->mapname,
- List_Count( &mvd->udpClients ), mvd->numplayers );
+ List_Count( &mvd->clients ), mvd->numplayers );
switch( mvd->state ) {
case MVD_DEAD:
Com_Printf( "DEAD" );
break;
- case MVD_CONNECTING:
- case MVD_CONNECTED:
- Com_Printf( "CNCT" );
- break;
- case MVD_CHECKING:
- case MVD_PREPARING:
- Com_Printf( "PREP" );
- break;
case MVD_WAITING:
Com_Printf( "WAIT" );
break;
case MVD_READING:
Com_Printf( "READ" );
break;
- case MVD_DISCONNECTED:
+ }
+ usage = FIFO_Percent( &mvd->delay );
+ Com_Printf( " %3d %4u %s\n", usage, mvd->num_packets,
+ mvd->gtv ? mvd->gtv->address : "" );
+ }
+}
+
+static void MVD_ListGtvs_f( void ) {
+ gtv_t *gtv;
+
+ if( LIST_EMPTY( &mvd_gtvs ) ) {
+ Com_Printf( "No GTV connections.\n" );
+ return;
+ }
+
+ Com_Printf( "id name stat address \n"
+ "-- ------------ ---- --------------\n" );
+
+ LIST_FOR_EACH( gtv_t, gtv, &mvd_gtvs, entry ) {
+ Com_Printf( "%2d %-12.12s ", gtv->id, gtv->name );
+ switch( gtv->state ) {
+ case GTV_DISCONNECTED:
Com_Printf( "DISC" );
break;
+ case GTV_CONNECTING:
+ case GTV_PREPARING:
+ Com_Printf( "CNCT" );
+ break;
+ case GTV_CONNECTED:
+ Com_Printf( "IDLE" );
+ break;
+ case GTV_ACTIVE:
+ Com_Printf( "STRM" );
+ break;
+ case GTV_OVERFLOWED:
+ Com_Printf( "OVER" );
+ break;
}
- usage = MVD_BufferPercent( mvd );
- Com_Printf( " %3d %s\n", usage, mvd->address );
+ Com_Printf( " %s\n", NET_AdrToString( &gtv->stream.address ) );
}
}
@@ -951,6 +1230,76 @@ void MVD_StreamedStop_f( void ) {
Com_Printf( "[%s] Stopped recording.\n", mvd->name );
}
+static void MVD_EmitGamestate( mvd_t *mvd ) {
+ char *string;
+ int i;
+ edict_t *ent;
+ player_state_t *ps;
+ size_t length;
+ int flags, portalbytes;
+ byte portalbits[MAX_MAP_AREAS/8];
+
+ // send the serverdata
+ MSG_WriteByte( mvd_serverdata );
+ MSG_WriteLong( PROTOCOL_VERSION_MVD );
+ MSG_WriteShort( PROTOCOL_VERSION_MVD_CURRENT );
+ MSG_WriteLong( mvd->servercount );
+ MSG_WriteString( mvd->gamedir );
+ MSG_WriteShort( mvd->clientNum );
+
+ // send configstrings
+ for( i = 0; i < MAX_CONFIGSTRINGS; i++ ) {
+ string = mvd->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 );
+
+ // send baseline frame
+ portalbytes = CM_WritePortalBits( &sv.cm, portalbits );
+ MSG_WriteByte( portalbytes );
+ MSG_WriteData( portalbits, portalbytes );
+
+ // send base player states
+ for( i = 0; i < mvd->maxclients; i++ ) {
+ ps = &mvd->players[i].ps;
+ flags = 0;
+ if( !PPS_INUSE( ps ) ) {
+ flags |= MSG_PS_REMOVE;
+ }
+ MSG_WriteDeltaPlayerstate_Packet( NULL, ps, i, flags );
+ }
+ MSG_WriteByte( CLIENTNUM_NONE );
+
+ // send base entity states
+ for( i = 1; i < mvd->pool.num_edicts; i++ ) {
+ ent = &mvd->edicts[i];
+ flags = 0;
+ if( i <= mvd->maxclients ) {
+ ps = &mvd->players[ i - 1 ].ps;
+ if( PPS_INUSE( ps ) && ps->pmove.pm_type == PM_NORMAL ) {
+ flags |= MSG_ES_FIRSTPERSON;
+ }
+ }
+ if( !ent->inuse ) {
+ flags |= MSG_ES_REMOVE;
+ }
+ MSG_WriteDeltaEntity( NULL, &ent->s, flags );
+ }
+ MSG_WriteShort( 0 );
+
+ // TODO: write private layouts/configstrings
+}
+
extern const cmd_option_t o_mvdrecord[];
void MVD_StreamedRecord_f( void ) {
@@ -958,6 +1307,7 @@ void MVD_StreamedRecord_f( void ) {
fileHandle_t f;
mvd_t *mvd;
uint32_t magic;
+ uint16_t msglen;
qboolean gzip = qfalse;
int c;
size_t len;
@@ -972,6 +1322,8 @@ void MVD_StreamedRecord_f( void ) {
case 'z':
gzip = qtrue;
break;
+ default:
+ return;
}
}
@@ -991,11 +1343,6 @@ void MVD_StreamedRecord_f( void ) {
return;
}
- if( mvd->state < MVD_WAITING ) {
- Com_Printf( "[%s] Channel not ready.\n", mvd->name );
- return;
- }
-
//
// open the demo file
//
@@ -1012,7 +1359,7 @@ void MVD_StreamedRecord_f( void ) {
return;
}
- Com_Printf( "[%s] Recording into %s.\n", mvd->name, buffer );
+ Com_Printf( "[%s] Recording into %s\n", mvd->name, buffer );
if( gzip ) {
FS_FilterFile( f );
@@ -1022,197 +1369,119 @@ void MVD_StreamedRecord_f( void ) {
MVD_EmitGamestate( mvd );
+ // write magic
magic = MVD_MAGIC;
- FS_Write( &magic, 4, mvd->demorecording );
- FS_Write( msg_write.data, msg_write.cursize, mvd->demorecording );
+ FS_Write( &magic, 4, f );
+
+ // write gamestate
+ msglen = LittleShort( msg_write.cursize );
+ FS_Write( &msglen, 2, f );
+ FS_Write( msg_write.data, msg_write.cursize, f );
SZ_Clear( &msg_write );
}
static const cmd_option_t o_mvdconnect[] = {
{ "h", "help", "display this message" },
- { "e:string", "encoding", "specify default encoding as <string>" },
- { "i:number", "id", "specify remote stream ID as <number>" },
{ "n:string", "name", "specify channel name as <string>" },
- { "r:string", "referer", "specify referer as <string> in HTTP request" },
+ //{ "u:string", "user", "specify username as <string>" },
+ //{ "p:string", "pass", "specify password as <string>" },
{ NULL }
};
-void MVD_Connect_c( genctx_t *ctx, int argnum ) {
+static void MVD_Connect_c( genctx_t *ctx, int argnum ) {
Cmd_Option_c( o_mvdconnect, Com_Address_g, ctx, argnum );
}
/*
==============
MVD_Connect_f
-
-[http://]host[:port][/resource]
==============
*/
-void MVD_Connect_f( void ) {
+static void MVD_Connect_f( void ) {
netadr_t adr;
netstream_t stream;
- char buffer[MAX_STRING_CHARS];
- char resource[MAX_STRING_CHARS];
- char credentials[MAX_STRING_CHARS];
- char *id = "", *name = NULL, *referer = NULL, *host, *p;
- htcoding_t coding = HTTP_CODING_NONE;
- mvd_t *mvd;
- uint16_t port;
+ char *name = NULL;
+ gtv_t *gtv;
int c;
while( ( c = Cmd_ParseOptions( o_mvdconnect ) ) != -1 ) {
switch( c ) {
case 'h':
- Cmd_PrintUsage( o_mvdconnect, "<uri>" );
- Com_Printf( "Create new MVD channel and connect to URI.\n" );
+ Cmd_PrintUsage( o_mvdconnect, "<address[:port]>" );
+ Com_Printf( "Connect to the specified MVD/GTV server.\n" );
Cmd_PrintHelp( o_mvdconnect );
- Com_Printf(
- "Full URI syntax: [http://][user:pass@]<host>[:port][/resource]\n"
- "If resource is given, default port is 80 and stream ID is ignored.\n"
- "Otherwise, default port is %d and stream ID is undefined.\n\n"
-#if USE_ZLIB
- "Accepted content encodings: gzip, deflate.\n"
-#endif
- , PORT_SERVER );
return;
- case 'e':
- coding = MVD_FindCoding( cmd_optarg );
- if( coding == HTTP_CODING_UNKNOWN ) {
- Com_Printf( "Unknown content encoding: %s.\n", cmd_optarg );
- Cmd_PrintHint();
- return;
- }
- break;
- case 'i':
- id = cmd_optarg;
- break;
case 'n':
name = cmd_optarg;
break;
- case 'r':
- referer = cmd_optarg;
- break;
default:
return;
}
}
if( !cmd_optarg[0] ) {
- Com_Printf( "Missing URI argument.\n" );
+ Com_Printf( "Missing address argument.\n" );
Cmd_PrintHint();
return;
}
- Cmd_ArgvBuffer( cmd_optind, buffer, sizeof( buffer ) );
-
- // skip optional http:// prefix
- host = buffer;
- if( !strncmp( host, "http://", 7 ) ) {
- host += 7;
- }
-
- // parse credentials
- p = strchr( host, '@' );
- if( p ) {
- *p = 0;
- strcpy( credentials, host );
- host = p + 1;
- } else {
- credentials[0] = 0;
- }
-
- // parse resource
- p = strchr( host, '/' );
- if( p ) {
- *p = 0;
- strcpy( resource, p + 1 );
- port = 80;
- } else {
- Q_concat( resource, sizeof( resource ), "mvdstream/", id, NULL );
- port = PORT_SERVER;
- }
-
// resolve hostname
- if( !NET_StringToAdr( host, &adr, port ) ) {
- Com_Printf( "Bad server address: %s\n", host );
+ if( !NET_StringToAdr( cmd_optarg, &adr, PORT_SERVER ) ) {
+ Com_Printf( "Bad server address: %s\n", cmd_optarg );
return;
}
+ // don't allow multiple connections
+ LIST_FOR_EACH( gtv_t, gtv, &mvd_gtvs, entry ) {
+ if( NET_IsEqualAdr( &adr, &gtv->stream.address ) ) {
+ Com_Printf( "Already connected to %s\n",
+ NET_AdrToString( &adr ) );
+ return;
+ }
+ }
+
+ // create new socket and start connecting
if( NET_Connect( &adr, &stream ) == NET_ERROR ) {
- Com_Printf( "%s to %s\n", NET_ErrorString(),
+ Com_EPrintf( "%s to %s\n", NET_ErrorString(),
NET_AdrToString( &adr ) );
return;
}
- Z_TagReserve( sizeof( *mvd ) + MAX_MSGLEN * 2 + 256, TAG_MVD );
-
- mvd = Z_ReservedAllocz( sizeof( *mvd ) );
- mvd->id = mvd_chanid++;
- mvd->state = MVD_CONNECTING;
- mvd->contentCoding = coding;
- mvd->stream = stream;
- mvd->stream.recv.data = Z_ReservedAlloc( MAX_MSGLEN * 2 );
- mvd->stream.recv.size = MAX_MSGLEN * 2;
- mvd->stream.send.data = Z_ReservedAlloc( 256 );
- mvd->stream.send.size = 256;
- mvd->pool.edicts = mvd->edicts;
- mvd->pool.edict_size = sizeof( edict_t );
- mvd->pool.max_edicts = MAX_EDICTS;
- mvd->pm_type = PM_SPECTATOR;
- mvd->lastReceived = svs.realtime;
- mvd->waitDelay = mvd_wait_delay->value * 1000;
- List_Init( &mvd->udpClients );
- List_Init( &mvd->tcpClients );
- List_Append( &mvd_channels, &mvd->entry );
- List_Init( &mvd->ready );
- List_Init( &mvd->active );
+ // create new connection
+ gtv = MVD_Mallocz( sizeof( *gtv ) );
+ gtv->id = mvd_chanid++;
+ gtv->state = GTV_CONNECTING;
+ gtv->stream = stream;
+ gtv->last_sent = gtv->last_rcvd = svs.realtime;
+ gtv->run = gtv_run;
+ gtv->drop = gtv_drop;
+ gtv->destroy = gtv_destroy;
+ List_Append( &mvd_gtvs, &gtv->entry );
// set channel name
if( name ) {
- Q_strlcpy( mvd->name, name, sizeof( mvd->name ) );
+ Q_strlcpy( gtv->name, name, sizeof( gtv->name ) );
} else {
- Q_snprintf( mvd->name, sizeof( mvd->name ), "net%d", mvd->id );
+ Q_snprintf( gtv->name, sizeof( gtv->name ), "net%d", gtv->id );
}
- Q_strlcpy( mvd->address, host, sizeof( mvd->address ) );
+ Q_strlcpy( gtv->address, cmd_optarg, sizeof( gtv->address ) );
- Com_Printf( "[%s] Connecting to %s...\n", mvd->name, NET_AdrToString( &adr ) );
-
- MVD_HttpPrintf( mvd,
- "GET /%s HTTP/1.0\r\n"
- "Host: %s\r\n"
- "User-Agent: " APPLICATION "/" VERSION "\r\n"
-#if USE_ZLIB
- "Accept-Encoding: gzip, deflate\r\n"
-#endif
- "Accept: application/*\r\n",
- resource, host );
- if( credentials[0] ) {
- Q_Encode64( buffer, credentials, sizeof( buffer ) );
- MVD_HttpPrintf( mvd, "Authorization: Basic %s\r\n", buffer );
- }
- if( referer ) {
- MVD_HttpPrintf( mvd, "Referer: %s\r\n", referer );
- }
- MVD_HttpPrintf( mvd, "\r\n" );
+ Com_Printf( "[%s] Connecting to %s...\n",
+ gtv->name, NET_AdrToString( &adr ) );
}
static void MVD_Disconnect_f( void ) {
- mvd_t *mvd;
-
- mvd = MVD_SetChannel( 1 );
- if( !mvd ) {
- return;
- }
+ gtv_t *gtv;
- if( mvd->state == MVD_DISCONNECTED ) {
- Com_Printf( "[%s] Already disconnected.\n", mvd->name );
+ gtv = gtv_set_conn( 1 );
+ if( !gtv ) {
return;
}
- Com_Printf( "[%s] Channel was disconnected.\n", mvd->name );
- MVD_Drop( mvd );
+ Com_Printf( "[%s] Connection destroyed.\n", gtv->name );
+ gtv->destroy( gtv );
}
static void MVD_Kill_f( void ) {
@@ -1235,17 +1504,21 @@ static void MVD_Pause_f( void ) {
return;
}
+ if( !mvd->gtv || !mvd->gtv->demoplayback ) {
+ Com_Printf( "[%s] Only demo channels can be paused.\n", mvd->name );
+ return;
+ }
+
switch( mvd->state ) {
case MVD_WAITING:
- Com_Printf( "[%s] Channel was resumed.\n", mvd->name );
+ //Com_Printf( "[%s] Channel was resumed.\n", mvd->name );
mvd->state = MVD_READING;
break;
case MVD_READING:
- Com_Printf( "[%s] Channel was paused.\n", mvd->name );
- MVD_BeginWaiting( mvd );
+ //Com_Printf( "[%s] Channel was paused.\n", mvd->name );
+ mvd->state = MVD_WAITING;
break;
default:
- Com_Printf( "[%s] Channel is not ready.\n", mvd->name );
break;
}
}
@@ -1305,8 +1578,8 @@ static void MVD_Control_f( void ) {
Q_strlcpy( mvd->name, name, sizeof( mvd->name ) );
}
if( loop != -1 ) {
- Com_Printf( "[%s] Loop count changed to %d.\n", mvd->name, loop );
- mvd->demoloop = loop;
+ //Com_Printf( "[%s] Loop count changed to %d.\n", mvd->name, loop );
+ //mvd->demoloop = loop;
}
}
@@ -1325,12 +1598,12 @@ static void MVD_Play_c( genctx_t *ctx, int argnum ) {
Cmd_Option_c( o_mvdplay, MVD_File_g, ctx, argnum );
}
-void MVD_Play_f( void ) {
+static void MVD_Play_f( void ) {
char *name = NULL, *s;
char buffer[MAX_OSPATH];
int loop = 1;
size_t len;
- mvd_t *mvd;
+ gtv_t *gtv;
int c, argc;
string_entry_t *entry, *head;
int i;
@@ -1367,6 +1640,7 @@ void MVD_Play_f( void ) {
return;
}
+ // build the playlist
head = NULL;
for( i = argc - 1; i >= cmd_optind; i-- ) {
s = Cmd_Argv( i );
@@ -1384,7 +1658,7 @@ void MVD_Play_f( void ) {
}
len = strlen( buffer );
- entry = Z_Malloc( sizeof( *entry ) + len );
+ entry = MVD_Malloc( sizeof( *entry ) + len );
memcpy( entry->string, buffer, len + 1 );
entry->next = head;
head = entry;
@@ -1394,52 +1668,47 @@ void MVD_Play_f( void ) {
return;
}
- Z_TagReserve( sizeof( *mvd ) + MAX_MSGLEN * 2, TAG_MVD );
-
- mvd = Z_ReservedAllocz( sizeof( *mvd ) );
- mvd->id = mvd_chanid++;
- mvd->state = MVD_PREPARING;
- mvd->demohead = head;
- mvd->demoloop = loop;
- mvd->stream.recv.data = Z_ReservedAlloc( MAX_MSGLEN * 2 );
- mvd->stream.recv.size = MAX_MSGLEN * 2;
- mvd->pool.edicts = mvd->edicts;
- mvd->pool.edict_size = sizeof( edict_t );
- mvd->pool.max_edicts = MAX_EDICTS;
- mvd->pm_type = PM_SPECTATOR;
- List_Init( &mvd->udpClients );
- List_Init( &mvd->tcpClients );
- List_Append( &mvd_channels, &mvd->entry );
- List_Init( &mvd->ready );
- List_Init( &mvd->active );
+ // create new connection
+ gtv = MVD_Mallocz( sizeof( *gtv ) );
+ gtv->id = mvd_chanid++;
+ gtv->state = GTV_CONNECTED;
+ gtv->demohead = head;
+ gtv->demoloop = loop;
+ gtv->drop = demo_destroy;
+ gtv->destroy = demo_destroy;
+ //List_Append( &mvd_gtvs, &gtv->entry );
// set channel name
if( name ) {
- Q_strlcpy( mvd->name, name, sizeof( mvd->name ) );
+ Q_strlcpy( gtv->name, name, sizeof( gtv->name ) );
} else {
- Q_snprintf( mvd->name, sizeof( mvd->name ), "dem%d", mvd->id );
+ Q_snprintf( gtv->name, sizeof( gtv->name ), "dem%d", gtv->id );
}
- MVD_PlayNext( mvd, mvd->demohead );
+ demo_play_next( gtv, gtv->demohead );
}
void MVD_Shutdown( void ) {
- mvd_t *mvd, *next;
+ gtv_t *gtv, *gtv_next;
+ mvd_t *mvd, *mvd_next;
- LIST_FOR_EACH_SAFE( mvd_t, mvd, next, &mvd_channels, entry ) {
- MVD_Disconnect( mvd );
+ // kill all connections
+ LIST_FOR_EACH_SAFE( gtv_t, gtv, gtv_next, &mvd_gtvs, entry ) {
+ gtv->destroy( gtv );
+ }
+
+ // kill all channels
+ LIST_FOR_EACH_SAFE( mvd_t, mvd, mvd_next, &mvd_channels, entry ) {
MVD_Free( mvd );
}
+ List_Init( &mvd_gtvs );
List_Init( &mvd_channels );
- List_Init( &mvd_ready );
List_Init( &mvd_active );
- if( mvd_clients ) {
- Z_Free( mvd_clients );
- mvd_clients = NULL;
- }
+ Z_Free( mvd_clients );
+ mvd_clients = NULL;
mvd_chanid = 0;
@@ -1453,6 +1722,7 @@ static const cmdreg_t c_mvd[] = {
{ "mvdkill", MVD_Kill_f },
{ "mvdspawn", MVD_Spawn_f },
{ "mvdchannels", MVD_ListChannels_f },
+ { "mvdgtvs", MVD_ListGtvs_f },
{ "mvdcontrol", MVD_Control_f },
{ "mvdpause", MVD_Pause_f },
@@ -1467,10 +1737,9 @@ MVD_Register
*/
void MVD_Register( void ) {
mvd_shownet = Cvar_Get( "mvd_shownet", "0", 0 );
- mvd_debug = Cvar_Get( "mvd_debug", "0", 0 );
- mvd_timeout = Cvar_Get( "mvd_timeout", "120", 0 );
+ mvd_timeout = Cvar_Get( "mvd_timeout", "90", 0 );
mvd_wait_delay = Cvar_Get( "mvd_wait_delay", "20", 0 );
- mvd_wait_percent = Cvar_Get( "mvd_wait_percent", "50", 0 );
+ mvd_wait_percent = Cvar_Get( "mvd_wait_percent", "35", 0 );
mvd_chase_msgs = Cvar_Get( "mvd_chase_msgs", "0", 0 );
Cmd_Register( c_mvd );