summaryrefslogtreecommitdiff
path: root/source/mvd_client.c
diff options
context:
space:
mode:
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 );