summaryrefslogtreecommitdiff
path: root/source/mvd_parse.c
diff options
context:
space:
mode:
Diffstat (limited to 'source/mvd_parse.c')
-rw-r--r--source/mvd_parse.c1545
1 files changed, 1545 insertions, 0 deletions
diff --git a/source/mvd_parse.c b/source/mvd_parse.c
new file mode 100644
index 0000000..738b010
--- /dev/null
+++ b/source/mvd_parse.c
@@ -0,0 +1,1545 @@
+/*
+Copyright (C) 2003-2006 Andrey Nazarov
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+*/
+
+//
+// mvd_parse.c
+//
+
+#include "sv_local.h"
+#include "mvd_local.h"
+
+#define MVD_ShowSVC( cmd ) do { \
+ Com_Printf( "%3i:%s\n", msg_read.readcount - 1, \
+ MVD_ServerCommandString( cmd ) ); \
+ } while( 0 )
+
+static const char mvd_strings[mvd_num_types][20] = {
+ "mvd_bad",
+ "mvd_nop",
+ "mvd_disconnect",
+ "mvd_reconnect",
+ "mvd_serverdata",
+ "mvd_configstring",
+ "mvd_frame",
+ "mvd_frame_nodelta",
+ "mvd_unicast",
+ "mvd_unicast_r",
+ "mvd_multicast_all",
+ "mvd_multicast_pvs",
+ "mvd_multicast_phs",
+ "mvd_multicast_all_r",
+ "mvd_multicast_pvs_r",
+ "mvd_multicast_phs_r",
+ "mvd_sound",
+ "mvd_print",
+ "mvd_stufftext"
+};
+
+const char *MVD_ServerCommandString( int cmd ) {
+ const char *s;
+
+ if( cmd == -1 ) {
+ s = "END OF MESSAGE";
+ } else if( cmd >= 0 && cmd < mvd_num_types ) {
+ s = mvd_strings[cmd];
+ } else {
+ s = "UNKNOWN COMMAND";
+ }
+
+ return s;
+}
+
+static void MVD_LinkEntity( mvd_t *mvd, entityStateEx_t *ent ) {
+ vec3_t mins, maxs;
+ cleaf_t *leafs[MAX_TOTAL_ENT_LEAFS];
+ int clusters[MAX_TOTAL_ENT_LEAFS];
+ int num_leafs;
+ int i, j;
+ int area;
+ cnode_t *topnode;
+ int modelindex;
+ cmodel_t *cm;
+ int x, zd, zu;
+ cmcache_t *cache;
+
+ cache = mvd->cm.cache;
+ if( !cache ) {
+ return;
+
+ }
+ if( ent->s.solid == 31 ) {
+ modelindex = ent->s.modelindex;
+ if( modelindex < 1 || modelindex > cache->numcmodels ) {
+ Com_WPrintf( "MVD_LinkEntity: entity %d: "
+ "bad inline model index: %d\n",
+ ent->s.number, modelindex );
+ ent->linked = qfalse;
+ return;
+ }
+ cm = &cache->cmodels[ modelindex - 1 ];
+ VectorCopy( cm->mins, mins );
+ VectorCopy( cm->maxs, maxs );
+ } else if( ent->s.solid ) {
+ x = 8 * ( ent->s.solid & 31 );
+ zd = 8 * ( ( ent->s.solid >> 5 ) & 31 );
+ zu = 8 * ( ( ent->s.solid >> 10 ) & 63 ) - 32;
+
+ mins[0] = mins[1] = -x;
+ maxs[0] = maxs[1] = x;
+ mins[2] = -zd;
+ maxs[2] = zu;
+ } else {
+ VectorClear( mins );
+ VectorClear( maxs );
+ }
+
+// set the abs box
+ if( ent->s.solid == 31 &&
+ ( ent->s.angles[0] || ent->s.angles[1] || ent->s.angles[2] ) )
+ { // expand for rotation
+ float max, v;
+ int i;
+
+ max = 0;
+ for( i = 0; i < 3; i++ ) {
+ v = fabs( mins[i] );
+ if( v > max )
+ max = v;
+ v = fabs( maxs[i] );
+ if( v > max )
+ max = v;
+ }
+ for( i = 0; i < 3; i++ ) {
+ mins[i] = ent->s.origin[i] - max;
+ maxs[i] = ent->s.origin[i] + max;
+ }
+ } else {
+ // normal
+ VectorAdd( mins, ent->s.origin, mins );
+ VectorAdd( maxs, ent->s.origin, maxs );
+ }
+
+ // because movement is clipped an epsilon away from an actual edge,
+ // we must fully check even when bounding boxes don't quite touch
+ mins[0] -= 1;
+ mins[1] -= 1;
+ mins[2] -= 1;
+ maxs[0] += 1;
+ maxs[1] += 1;
+ maxs[2] += 1;
+
+ // link to PVS leafs
+ ent->num_clusters = 0;
+ ent->areanum = 0;
+ ent->areanum2 = 0;
+
+ // get all leafs, including solids
+ num_leafs = CM_BoxLeafs( &mvd->cm, mins, maxs,
+ leafs, MAX_TOTAL_ENT_LEAFS, &topnode );
+
+ // set areas
+ for( i = 0; i < num_leafs; i++ ) {
+ clusters[i] = CM_LeafCluster( leafs[i] );
+ area = CM_LeafArea( leafs[i] );
+ if( area ) {
+ // doors may legally straggle two areas,
+ // but nothing should evern need more than that
+ if( ent->areanum && ent->areanum != area ) {
+ ent->areanum2 = area;
+ } else {
+ ent->areanum = area;
+ }
+ }
+ }
+
+ if( num_leafs >= MAX_TOTAL_ENT_LEAFS ) {
+ // assume we missed some leafs, and mark by headnode
+ ent->num_clusters = -1;
+ ent->headnode = topnode;
+ } else {
+ ent->num_clusters = 0;
+ for( i = 0; i < num_leafs; i++ ) {
+ if( clusters[i] == -1 )
+ continue; // not a visible leaf
+ for( j = 0; j < i; j++ )
+ if( clusters[j] == clusters[i] )
+ break;
+ if( j == i ) {
+ if ( ent->num_clusters == MAX_ENT_CLUSTERS ) {
+ // assume we missed some leafs, and mark by headnode
+ ent->num_clusters = -1;
+ ent->headnode = topnode;
+ break;
+ }
+
+ ent->clusternums[ent->num_clusters++] = clusters[i];
+ }
+ }
+ }
+
+ ent->linked = qtrue;
+}
+
+static void MVD_ParseEntityString( mvd_t *mvd ) {
+ const char *data, *p;
+ char key[MAX_STRING_CHARS];
+ char value[MAX_STRING_CHARS];
+ char classname[MAX_QPATH];
+ vec3_t origin;
+ vec3_t angles;
+
+ mvd->spawnSet = qfalse;
+
+ if( !mvd->cm.cache ) {
+ return;
+ }
+ data = mvd->cm.cache->entitystring;
+ if( !data || !data[0] ) {
+ return;
+ }
+
+ while( data ) {
+ p = COM_Parse( &data );
+ if( !p[0] ) {
+ break;
+ }
+ if( p[0] != '{' ) {
+ Com_Error( ERR_DROP, "expected '{', found '%s'", p );
+ }
+
+ classname[0] = 0;
+ VectorClear( origin );
+ VectorClear( angles );
+ while( 1 ) {
+ p = COM_Parse( &data );
+ if( p[0] == '}' ) {
+ break;
+ }
+ if( p[0] == '{' ) {
+ Com_Error( ERR_DROP, "expected key, found '{'" );
+ }
+
+ Q_strncpyz( key, p, sizeof( key ) );
+
+ p = COM_Parse( &data );
+ if( !data ) {
+ Com_Error( ERR_DROP, "expected key/value pair, found EOF" );
+ }
+ if( p[0] == '}' || p[0] == '{' ) {
+ Com_Error( ERR_DROP, "expected value, found '%s'", p );
+ }
+
+ if( !strcmp( key, "classname" ) ) {
+ Q_strncpyz( classname, p, sizeof( classname ) );
+ continue;
+ }
+
+ Q_strncpyz( value, p, sizeof( value ) );
+
+ p = value;
+ if( !strcmp( key, "origin" ) ) {
+ origin[0] = atof( COM_Parse( &p ) );
+ origin[1] = atof( COM_Parse( &p ) );
+ origin[2] = atof( COM_Parse( &p ) );
+ } else if( !strncmp( key, "angle", 5 ) ) {
+ if( key[5] == 0 ) {
+ angles[0] = 0;
+ angles[1] = atof( COM_Parse( &p ) );
+ angles[2] = 0;
+ } else if( key[5] == 's' && key[6] == 0 ) {
+ angles[0] = atof( COM_Parse( &p ) );
+ angles[1] = atof( COM_Parse( &p ) );
+ angles[2] = atof( COM_Parse( &p ) );
+ }
+ }
+ }
+
+ if( !classname[0] ) {
+ Com_Error( ERR_DROP, "entity with no classname" );
+ }
+
+ if( strncmp( classname, "info_player_", 12 ) ) {
+ continue;
+ }
+
+ if( !strcmp( classname + 12, "intermission" ) ) {
+ VectorCopy( origin, mvd->spawnOrigin );
+ VectorCopy( angles, mvd->spawnAngles );
+ mvd->spawnSet = qtrue;
+ continue;
+ }
+
+ if( mvd->spawnSet ) {
+ continue;
+ }
+
+ if( !strcmp( classname + 12, "start" ) ||
+ !strcmp( classname + 12, "deathmatch" ) )
+ {
+ VectorCopy( origin, mvd->spawnOrigin );
+ VectorCopy( angles, mvd->spawnAngles );
+ mvd->spawnSet = qtrue;
+ }
+
+ }
+
+ if( !mvd->spawnSet ) {
+ Com_WPrintf( "Couldn't find spawn point for MVD spectators\n" );
+ }
+}
+
+static void MVD_ParseMulticast( mvd_t *mvd, mvd_ops_t op, int extrabits ) {
+ udpClient_t *client;
+ client_t *cl;
+ byte *mask;
+ cleaf_t *leaf;
+ int cluster;
+ int area1, area2;
+ vec3_t org;
+ qboolean reliable = qfalse;
+ player_state_t *ps;
+ byte *data;
+ int length, leafnum;
+
+ length = MSG_ReadByte();
+ length |= extrabits << 8;
+
+ switch( op ) {
+ case mvd_multicast_all_r:
+ reliable = qtrue; // intentional fallthrough
+ case mvd_multicast_all:
+ area1 = 0;
+ cluster = 0;
+ mask = NULL;
+ break;
+
+ case mvd_multicast_phs_r:
+ reliable = qtrue; // intentional fallthrough
+ case mvd_multicast_phs:
+ leafnum = MSG_ReadShort();
+ leaf = CM_LeafNum( &mvd->cm, leafnum );
+ area1 = CM_LeafArea( leaf );
+ cluster = CM_LeafCluster( leaf );
+ mask = CM_ClusterPHS( &mvd->cm, cluster );
+ break;
+
+ case mvd_multicast_pvs_r:
+ reliable = qtrue; // intentional fallthrough
+ case mvd_multicast_pvs:
+ leafnum = MSG_ReadShort();
+ leaf = CM_LeafNum( &mvd->cm, leafnum );
+ area1 = CM_LeafArea( leaf );
+ cluster = CM_LeafCluster( leaf );
+ mask = CM_ClusterPVS( &mvd->cm, cluster );
+ break;
+
+ default:
+ MVD_Destroy( mvd, "bad op" );
+ }
+
+ // skip data payload
+ data = msg_read.data + msg_read.readcount;
+ msg_read.readcount += length;
+ if( msg_read.readcount > msg_read.cursize ) {
+ MVD_Destroy( mvd, "read past end of message" );
+ }
+
+ // send the data to all relevent clients
+ LIST_FOR_EACH( udpClient_t, client, &mvd->udpClients, entry ) {
+ cl = client->cl;
+ if( cl->state < cs_primed ) {
+ continue;
+ }
+
+ // do not send unreliables to connecting clients
+ if( !reliable && ( cl->state != cs_spawned || cl->download || cl->nodata ) ) {
+ continue;
+ }
+
+ if( mask ) {
+ // find the client's PVS
+ ps = &client->ps;
+ VectorMA( ps->viewoffset, 0.125f, ps->pmove.origin, org );
+ leaf = CM_PointLeaf( &mvd->cm, org );
+ area2 = CM_LeafArea( leaf );
+ if( !CM_AreasConnected( &mvd->cm, area1, area2 ) )
+ continue;
+ cluster = CM_LeafCluster( leaf );
+ if( !Q_IsBitSet( mask, cluster ) ) {
+ continue;
+ }
+ }
+
+ cl->AddMessage( cl, data, length, reliable );
+ }
+
+}
+
+static void MVD_ParseUnicast( mvd_t *mvd, mvd_ops_t op, int extrabits ) {
+ int clientNum, length, last;
+ int flags;
+ udpClient_t *client;
+ mvdPlayer_t *player;
+ clstate_t minstate;
+ int i, c;
+ char *s;
+ qboolean gotLayout, wantLayout;
+ mvdConfigstring_t *cs;
+
+ length = MSG_ReadByte();
+ length |= extrabits << 8;
+ clientNum = MSG_ReadByte();
+
+ if( clientNum < 0 || clientNum >= MAX_CLIENTS ) {
+ MVD_Destroy( mvd, "Bad unicast clientNum" );
+ }
+
+ last = msg_read.readcount + length;
+ if( last > msg_read.cursize ) {
+ MVD_Destroy( mvd, "Read past end of message" );
+ }
+
+ player = &mvd->players[clientNum];
+
+ gotLayout = qfalse;
+
+ // Attempt to parse the datagram and find custom configstrings,
+ // layouts, etc. Give up as soon as unknown command byte is encountered.
+ while( 1 ) {
+ if( msg_read.readcount > last ) {
+ MVD_Destroy( mvd, "Read past end of unicast" );
+ }
+
+ if( msg_read.readcount == last ) {
+ break;
+ }
+
+ c = MSG_ReadByte();
+
+ if( mvd_debug->integer > 1 ) {
+ Com_Printf( "%s\n", MVD_ServerCommandString( c ) );
+ }
+
+ switch( c ) {
+ case svc_layout:
+ s = MSG_ReadString();
+ if( !player->layout ) {
+ player->layout = MVD_Malloc( MAX_STRING_CHARS );
+ }
+ Q_strncpyz( player->layout, s, MAX_STRING_CHARS );
+ gotLayout = qtrue;
+ break;
+ case svc_configstring:
+ i = MSG_ReadShort();
+ s = MSG_ReadString();
+ if( i < 0 || i >= MAX_CONFIGSTRINGS ) {
+ MVD_Destroy( mvd, "bad configstring index" );
+ }
+ length = strlen( s );
+ if( length > MAX_QPATH - 1 ) {
+ Com_WPrintf( "Private configstring %d for player %d "
+ "is %d chars long, ignored.\n", i, clientNum, length );
+ } else {
+ for( cs = player->configstrings; cs; cs = cs->next ) {
+ if( cs->index == i ) {
+ break;
+ }
+ }
+ if( !cs ) {
+ cs = MVD_Malloc( sizeof( *cs ) + MAX_QPATH - 1 );
+ cs->index = i;
+ cs->next = player->configstrings;
+ player->configstrings = cs;
+ }
+ strcpy( cs->string, s );
+ }
+ if( mvd_debug->integer > 1 ) {
+ Com_Printf( " index:%d string: %s\n", i, s );
+ }
+ MSG_WriteByte( svc_configstring );
+ MSG_WriteShort( i );
+ MSG_WriteString( s );
+ break;
+ case svc_print:
+ i = MSG_ReadByte();
+ s = MSG_ReadString();
+ MSG_WriteByte( svc_print );
+ MSG_WriteByte( i );
+ MSG_WriteString( s );
+ break;
+ case svc_stufftext:
+ s = MSG_ReadString();
+ MSG_WriteByte( svc_stufftext );
+ MSG_WriteString( s );
+ break;
+ default:
+ // copy remaining data and stop
+ MSG_WriteByte( c );
+ MSG_WriteData( msg_read.data + msg_read.readcount,
+ last - msg_read.readcount );
+ msg_read.readcount = last;
+ goto breakOut;
+ }
+ }
+
+breakOut:
+ flags = 0;
+ minstate = cs_spawned;
+ if( op == mvd_unicast_r ) {
+ flags |= MSG_RELIABLE;
+ minstate = cs_primed;
+ }
+
+ // send to all relevant clients
+ wantLayout = qfalse;
+ LIST_FOR_EACH( udpClient_t, client, &mvd->udpClients, entry ) {
+ if( client->cl->state < minstate ) {
+ continue;
+ }
+ if( client->scoreboard == SBOARD_SCORES &&
+ clientNum == mvd->clientNum )
+ {
+ wantLayout = qtrue;
+ }
+ if( !client->following ) {
+ if( clientNum == mvd->clientNum ) {
+ SV_ClientAddMessage( client->cl, flags );
+ }
+ continue;
+ }
+ if( client->followClientNum == clientNum ) {
+ SV_ClientAddMessage( client->cl, flags );
+ if( client->scoreboard == SBOARD_FOLLOW ) {
+ wantLayout = qtrue;
+ }
+ }
+ }
+
+ SZ_Clear( &msg_write );
+
+ if( !gotLayout || !wantLayout ) {
+ return;
+ }
+
+ MSG_WriteByte( svc_layout );
+ MSG_WriteString( player->layout );
+
+ LIST_FOR_EACH( udpClient_t, client, &mvd->udpClients, entry ) {
+ if( client->cl->state < minstate ) {
+ continue;
+ }
+ if( client->scoreboard == SBOARD_SCORES &&
+ clientNum == mvd->clientNum )
+ {
+ SV_ClientAddMessage( client->cl, flags );
+ continue;
+ }
+ if( client->followClientNum == clientNum &&
+ client->scoreboard == SBOARD_FOLLOW )
+ {
+ SV_ClientAddMessage( client->cl, flags );
+ }
+ }
+
+ SZ_Clear( &msg_write );
+}
+
+static qboolean MVD_EdictPV( mvd_t *mvd, entityStateEx_t *ent, byte *mask ) {
+ int i, l;
+
+ if( ent->num_clusters == -1 ) {
+ // too many leafs for individual check, go by headnode
+ return CM_HeadnodeVisible( ent->headnode, mask );
+ }
+
+ // check individual leafs
+ for( i = 0; i < ent->num_clusters; i++ ) {
+ l = ent->clusternums[i];
+ if( Q_IsBitSet( mask, l ) ) {
+ return qtrue;
+ }
+ }
+ return qfalse; // not visible
+}
+
+/*
+MVD_ParseSound
+
+Entity positioned sounds need special handling since origins need to be
+explicitly specified on entities out of client PVS, and not all clients
+are able to postition sounds on BSP models properly.
+*/
+static void MVD_ParseSound( mvd_t *mvd, int extrabits ) {
+ vec3_t origin, clientorg;
+ int channel, entnum, sendchan;
+ int flags, index;
+ int volume = 0, attenuation = 0, offset = 0;
+ entityStateEx_t *entity = NULL;
+ udpClient_t *client;
+ int i, j;
+ cleaf_t *leaf;
+ int area, cluster;
+ byte *mask;
+ mvdFrame_t *frame;
+ int modelindex;
+ cmodel_t *cm;
+
+ flags = MSG_ReadByte();
+ index = MSG_ReadByte();
+
+ if( flags & SND_VOLUME )
+ volume = MSG_ReadByte();
+ if( flags & SND_ATTENUATION )
+ attenuation = MSG_ReadByte();
+ if( flags & SND_OFFSET )
+ offset = MSG_ReadByte();
+
+ // entity relative
+ sendchan = MSG_ReadShort();
+ entnum = sendchan >> 3;
+ if( entnum < 0 || entnum >= MAX_EDICTS )
+ MVD_Destroy( mvd, "MVD_ParseSound: bad entnum %d", entnum );
+ channel = sendchan & 7;
+
+ // find this entity in frame
+ frame = &mvd->frames[mvd->framenum & MVD_UPDATE_MASK];
+ for( i = 0; i < frame->numEntities; i++ ) {
+ j = ( frame->firstEntity + i ) & MVD_ENTITIES_MASK;
+ entity = &mvd->entityStates[j];
+ if( entity->s.number == entnum ) {
+ break;
+ }
+ }
+ if( i == frame->numEntities ) {
+ Com_WPrintf( "MVD_ParseSound: entity %d not found in frame\n", entnum );
+ return;
+ }
+
+ // use the entity origin unless it is a bmodel
+ if( entity->s.solid == 31 ) {
+ modelindex = entity->s.modelindex;
+ if( modelindex < 1 || modelindex > mvd->cm.cache->numcmodels ) {
+ Com_WPrintf( "MVD_PaseSound: entity %d has bad inline model index %d\n",
+ entity->s.number, modelindex );
+ return;
+ }
+ cm = &mvd->cm.cache->cmodels[ modelindex - 1 ];
+ VectorAvg( cm->mins, cm->maxs, origin );
+ VectorAdd( entity->s.origin, origin, origin );
+ } else {
+ VectorCopy( entity->s.origin, origin );
+ }
+
+ LIST_FOR_EACH( udpClient_t, client, &mvd->udpClients, entry ) {
+ // do not send sounds to connecting clients
+ if( client->cl->state != cs_spawned || client->cl->download || client->cl->nodata ) {
+ continue;
+ }
+
+ // default client doesn't know that bmodels have weird origins
+ if( entity->s.solid == 31 && client->cl->protocol == PROTOCOL_VERSION_DEFAULT ) {
+ flags |= SND_POS;
+ }
+
+ //if( entity != client->edict ) {
+ // get client viewpos
+ VectorMA( client->ps.viewoffset, 0.125f,
+ client->ps.pmove.origin, clientorg );
+
+ // PHS cull this sound
+ if( ( extrabits & 1 ) == 0 ) {
+ leaf = CM_PointLeaf( &mvd->cm, clientorg );
+ area = CM_LeafArea( leaf );
+ if( !CM_AreasConnected( &mvd->cm, area, entity->areanum ) ) {
+ // doors can legally straddle two areas, so
+ // we may need to check another one
+ if( !entity->areanum2 || !CM_AreasConnected( &mvd->cm, area, entity->areanum2 ) ) {
+ continue; // blocked by a door
+ }
+ }
+ cluster = CM_LeafCluster( leaf );
+ mask = CM_ClusterPHS( &mvd->cm, cluster );
+ if( !MVD_EdictPV( mvd, entity, mask ) ) {
+ continue; // not in PHS
+ }
+ }
+
+ // check if position needs to be explicitly sent
+ if( ( flags & SND_POS ) == 0 ) {
+ mask = CM_FatPVS( &mvd->cm, clientorg );
+ if( !MVD_EdictPV( mvd, entity, mask ) ) {
+ flags |= SND_POS; // not in PVS
+ }
+ }
+// }
+
+ MSG_WriteByte( svc_sound );
+ MSG_WriteByte( flags );
+ MSG_WriteByte( index );
+
+ if( flags & SND_VOLUME )
+ MSG_WriteByte( volume );
+ if( flags & SND_ATTENUATION )
+ MSG_WriteByte( attenuation );
+ if( flags & SND_OFFSET )
+ MSG_WriteByte( offset );
+
+ MSG_WriteShort( sendchan );
+
+ if( flags & SND_POS )
+ MSG_WritePos( origin );
+
+ flags &= ~SND_POS;
+
+ if( extrabits & 2 ) {
+ SV_ClientAddMessage( client->cl, MSG_RELIABLE|MSG_CLEAR );
+ } else {
+ SV_ClientAddMessage( client->cl, MSG_CLEAR );
+ }
+ }
+}
+
+static void MVD_ParseConfigstring( mvd_t *mvd ) {
+ int index, length;
+ char *string;
+ udpClient_t *client;
+
+ index = MSG_ReadShort();
+
+ if( index < 0 || index >= MAX_CONFIGSTRINGS ) {
+ MVD_Destroy( mvd, "Bad configstring index: %d", index );
+ }
+
+ string = MSG_ReadStringLength( &length );
+
+ if( MAX_QPATH * index + length >= sizeof( mvd->configstrings ) ) {
+ MVD_Destroy( mvd, "Oversize configstring" );
+ }
+
+ if( !strcmp( mvd->configstrings[index], string ) ) {
+ return;
+ }
+
+ strcpy( mvd->configstrings[index], string );
+
+ MSG_WriteByte( svc_configstring );
+ MSG_WriteShort( index );
+ MSG_WriteString( string );
+
+ // broadcast configstring change
+ LIST_FOR_EACH( udpClient_t, client, &mvd->udpClients, entry ) {
+ if( client->cl->state < cs_primed ) {
+ continue;
+ }
+ SV_ClientAddMessage( client->cl, MSG_RELIABLE );
+ }
+
+ SZ_Clear( &msg_write );
+}
+
+/*
+Fix origin and angles on each player entity by
+extracting data from player state.
+*/
+static void MVD_FixEntityStates( mvd_t *mvd, mvdFrame_t *frame ) {
+ entityStateEx_t *currStates[MAX_CLIENTS];
+ entityStateEx_t *state;
+ player_state_t *ps;
+ int i, j;
+ int playerNum;
+
+ for( i = 0; i < MAX_CLIENTS; i++ ) {
+ currStates[i] = NULL;
+ }
+
+ for( i = 0; i < frame->numEntities; i++ ) {
+ j = ( frame->firstEntity + i ) & MVD_ENTITIES_MASK;
+ state = &mvd->entityStates[j];
+
+ if( state->s.number < 1 || state->s.number >= MAX_EDICTS ) {
+ MVD_Destroy( mvd, "Bad entity number" );
+ }
+
+ if( state->s.number <= MAX_CLIENTS ) {
+ currStates[ state->s.number - 1 ] = state;
+ }
+ }
+
+ for( i = 0; i < frame->numPlayers; i++ ) {
+ j = ( frame->firstPlayer + i ) & MVD_PLAYERS_MASK;
+ ps = &mvd->playerStates[j];
+ playerNum = ps->pmove.pm_flags;
+
+ if( ps->pmove.pm_type >= PM_DEAD ) {
+ continue;
+ }
+ state = currStates[playerNum];
+ if( !state ) {
+ continue; // not present in this frame
+ }
+
+ VectorCopy( state->s.origin, state->s.old_origin );
+
+ VectorScale( ps->pmove.origin, 0.125f, state->s.origin );
+ VectorCopy( ps->viewangles, state->s.angles );
+
+ if( state->s.angles[PITCH] > 180 ) {
+ state->s.angles[PITCH] -= 360;
+ }
+
+ state->s.angles[PITCH] = state->s.angles[PITCH] / 3;
+
+ MVD_LinkEntity( mvd, state );
+ }
+}
+
+/*
+==================
+MVD_ParseDeltaEntity
+==================
+*/
+#define RELINK_MASK (U_MODEL|U_ORIGIN1|U_ORIGIN2|U_ORIGIN3|U_SOLID)
+
+static inline void MVD_ParseDeltaEntity( mvd_t *mvd,
+ mvdFrame_t *frame,
+ int newnum,
+ entityStateEx_t *old,
+ int bits )
+{
+ entityStateEx_t *state;
+ int i;
+
+ i = mvd->nextEntityStates & MVD_ENTITIES_MASK;
+ state = &mvd->entityStates[i];
+ mvd->nextEntityStates++;
+ frame->numEntities++;
+
+ if( mvd_shownet->integer > 2 ) {
+ MSG_ShowDeltaEntityBits( bits );
+ }
+
+ MSG_ParseDeltaEntity( &old->s, &state->s, newnum, bits );
+
+ if( newnum > mvd->maxclients ) {
+ if( !old || !old->linked || ( bits & RELINK_MASK ) ) {
+ MVD_LinkEntity( mvd, state );
+ } else {
+ state->linked = qtrue;
+ state->num_clusters = old->num_clusters;
+ state->headnode = old->headnode;
+ state->areanum = old->areanum;
+ state->areanum2 = old->areanum2;
+ for( i = 0; i < state->num_clusters; i++ ) {
+ state->clusternums[i] = old->clusternums[i];
+ }
+ }
+ }
+}
+
+/*
+==================
+MVD_ParsePacketEntities
+==================
+*/
+static void MVD_ParsePacketEntities( mvd_t *mvd,
+ mvdFrame_t *oldframe,
+ mvdFrame_t *frame )
+{
+ int newnum;
+ int bits;
+ entityStateEx_t *oldstate, *base;
+ int oldindex, oldnum;
+ int i;
+
+ frame->firstEntity = mvd->nextEntityStates;
+ frame->numEntities = 0;
+
+ // delta from the entities present in oldframe
+ oldindex = 0;
+ oldstate = NULL;
+ if( !oldframe ) {
+ oldnum = 99999;
+ } else {
+ if( oldindex >= oldframe->numEntities ) {
+ oldnum = 99999;
+ } else {
+ i = oldframe->firstEntity + oldindex;
+ oldstate = &mvd->entityStates[i & MVD_ENTITIES_MASK];
+ oldnum = oldstate->s.number;
+ }
+ }
+
+ while( 1 ) {
+ newnum = MSG_ParseEntityBits( &bits );
+ if( msg_read.readcount > msg_read.cursize ) {
+ MVD_Destroy( mvd, "Read past end of message" );
+ }
+
+ if( newnum < 0 || newnum >= MAX_EDICTS ) {
+ MVD_Destroy( mvd, "Bad packetentity number: %d", newnum );
+ }
+
+ if( !newnum ) {
+ break;
+ }
+
+ while( oldnum < newnum ) {
+ // one or more entities from the old packet are unchanged
+ if( mvd_shownet->integer > 2 ) {
+ Com_Printf( " unchanged: %i\n", oldnum );
+ }
+ MVD_ParseDeltaEntity( mvd, frame, oldnum, oldstate, 0 );
+
+ oldindex++;
+
+ if( oldindex >= oldframe->numEntities ) {
+ oldnum = 99999;
+ } else {
+ i = oldframe->firstEntity + oldindex;
+ oldstate = &mvd->entityStates[i & MVD_ENTITIES_MASK];
+ oldnum = oldstate->s.number;
+ }
+ }
+
+ if( bits & U_REMOVE ) {
+ // the entity present in oldframe is not in the current frame
+ if( mvd_shownet->integer > 2 ) {
+ Com_Printf( " remove: %i\n", newnum );
+ }
+ if( oldnum != newnum ) {
+ Com_DPrintf( "U_REMOVE: oldnum != newnum\n" );
+ }
+ if( !oldframe ) {
+ MVD_Destroy( mvd, "U_REMOVE: NULL oldframe" );
+ }
+
+ oldindex++;
+
+ if( oldindex >= oldframe->numEntities ) {
+ oldnum = 99999;
+ } else {
+ i = oldframe->firstEntity + oldindex;
+ oldstate = &mvd->entityStates[i & MVD_ENTITIES_MASK];
+ oldnum = oldstate->s.number;
+ }
+ continue;
+ }
+
+ if( oldnum == newnum ) {
+ // delta from previous state
+ if( mvd_shownet->integer > 2 ) {
+ Com_Printf( " delta: %i ", newnum );
+ }
+ MVD_ParseDeltaEntity( mvd, frame, newnum, oldstate, bits );
+ if( mvd_shownet->integer > 2 ) {
+ Com_Printf( "\n" );
+ }
+
+ oldindex++;
+
+ if( oldindex >= oldframe->numEntities ) {
+ oldnum = 99999;
+ } else {
+ i = oldframe->firstEntity + oldindex;
+ oldstate = &mvd->entityStates[i & MVD_ENTITIES_MASK];
+ oldnum = oldstate->s.number;
+ }
+ continue;
+ }
+
+ if( oldnum > newnum ) {
+ // delta from baseline
+ if( mvd_shownet->integer > 2 ) {
+ Com_Printf( " baseline: %i ", newnum );
+ }
+ base = mvd->baselines[newnum >> SV_BASELINES_SHIFT];
+ if( base ) {
+ base += newnum & SV_BASELINES_MASK;
+ }
+ MVD_ParseDeltaEntity( mvd, frame, newnum, base, bits );
+ if( mvd_shownet->integer > 2 ) {
+ Com_Printf( "\n" );
+ }
+ continue;
+ }
+
+ }
+
+ // any remaining entities in the old frame are copied over
+ while( oldnum != 99999 ) {
+ // one or more entities from the old packet are unchanged
+ if( mvd_shownet->integer > 2 ) {
+ Com_Printf( " unchanged: %i\n", oldnum );
+ }
+ MVD_ParseDeltaEntity( mvd, frame, oldnum, oldstate, 0 );
+
+ oldindex++;
+
+ if( oldindex >= oldframe->numEntities ) {
+ oldnum = 99999;
+ } else {
+ i = oldframe->firstEntity + oldindex;
+ oldstate = &mvd->entityStates[i & MVD_ENTITIES_MASK];
+ oldnum = oldstate->s.number;
+ }
+ }
+}
+
+/*
+==================
+MVD_ParseDeltaPlayer
+==================
+*/
+static inline void MVD_ParseDeltaPlayer( mvd_t *mvd,
+ mvdFrame_t *frame,
+ int newnum,
+ player_state_t *old,
+ int bits )
+{
+ player_state_t *state;
+ int i;
+
+ i = mvd->nextPlayerStates & MVD_PLAYERS_MASK;
+ state = &mvd->playerStates[i];
+ mvd->nextPlayerStates++;
+ frame->numPlayers++;
+
+ if( mvd_shownet->integer > 2 ) {
+ MSG_ShowDeltaPlayerstateBits_Packet( bits );
+ }
+ MSG_ParseDeltaPlayerstate_Packet( old, state, bits );
+
+ // save player number
+ state->pmove.pm_flags = newnum;
+}
+
+/*
+==================
+MVD_ParsePacketPlayers
+==================
+*/
+static void MVD_ParsePacketPlayers( mvd_t *mvd,
+ mvdFrame_t *oldframe,
+ mvdFrame_t *frame )
+{
+ int newnum;
+ int bits;
+ player_state_t *oldstate;
+ int oldindex, oldnum;
+ int i;
+
+ frame->firstPlayer = mvd->nextPlayerStates;
+ frame->numPlayers = 0;
+
+ // delta from the players present in oldframe
+ oldindex = 0;
+ oldstate = NULL;
+ if( !oldframe ) {
+ oldnum = 99999;
+ } else {
+ if( oldindex >= oldframe->numPlayers ) {
+ oldnum = 99999;
+ } else {
+ i = oldframe->firstPlayer + oldindex;
+ oldstate = &mvd->playerStates[i & MVD_PLAYERS_MASK];
+ oldnum = PPS_NUM( oldstate );
+ }
+ }
+
+ while( 1 ) {
+ newnum = MSG_ReadByte();
+ if( msg_read.readcount > msg_read.cursize ) {
+ MVD_Destroy( mvd, "Read past end of message" );
+ }
+
+ if( newnum < 0 || newnum >= MAX_CLIENTS ) {
+ MVD_Destroy( mvd, "Bad packetplayer number: %d", newnum );
+ }
+
+ if( newnum == CLIENTNUM_NONE ) {
+ break;
+ }
+
+ bits = MSG_ReadShort();
+
+ while( oldnum < newnum ) {
+ // one or more players from the old packet are unchanged
+ if( mvd_shownet->integer > 2 ) {
+ Com_Printf( " unchanged: %i\n", oldnum );
+ }
+ MVD_ParseDeltaPlayer( mvd, frame, oldnum, oldstate, 0 );
+
+ oldindex++;
+
+ if( oldindex >= oldframe->numPlayers ) {
+ oldnum = 99999;
+ } else {
+ i = oldframe->firstPlayer + oldindex;
+ oldstate = &mvd->playerStates[i & MVD_PLAYERS_MASK];
+ oldnum = PPS_NUM( oldstate );
+ }
+ }
+
+ if( bits & PPS_REMOVE ) {
+ // the player present in oldframe is not in the current frame
+ if( mvd_shownet->integer > 2 ) {
+ Com_Printf( " remove: %i\n", newnum );
+ }
+ if( oldnum != newnum ) {
+ Com_DPrintf( "PPS_REMOVE: oldnum != newnum\n" );
+ }
+ if( !oldframe ) {
+ MVD_Destroy( mvd, "PPS_REMOVE: NULL oldframe" );
+ }
+
+ oldindex++;
+
+ if( oldindex >= oldframe->numPlayers ) {
+ oldnum = 99999;
+ } else {
+ i = oldframe->firstPlayer + oldindex;
+ oldstate = &mvd->playerStates[i & MVD_PLAYERS_MASK];
+ oldnum = PPS_NUM( oldstate );
+ }
+ continue;
+ }
+
+ if( oldnum == newnum ) {
+ // delta from previous state
+ if( mvd_shownet->integer > 2 ) {
+ Com_Printf( " delta: %i ", newnum );
+ }
+ MVD_ParseDeltaPlayer( mvd, frame, newnum, oldstate, bits );
+ if( mvd_shownet->integer > 2 ) {
+ Com_Printf( "\n" );
+ }
+
+ oldindex++;
+
+ if( oldindex >= oldframe->numPlayers ) {
+ oldnum = 99999;
+ } else {
+ i = oldframe->firstPlayer + oldindex;
+ oldstate = &mvd->playerStates[i & MVD_PLAYERS_MASK];
+ oldnum = PPS_NUM( oldstate );
+ }
+ continue;
+ }
+
+ if( oldnum > newnum ) {
+ // delta from baseline
+ if( mvd_shownet->integer > 2 ) {
+ Com_Printf( " baseline: %i ", newnum );
+ }
+ MVD_ParseDeltaPlayer( mvd, frame, newnum, NULL, bits );
+ if( mvd_shownet->integer > 2 ) {
+ Com_Printf( "\n" );
+ }
+ continue;
+ }
+
+ }
+
+ while( oldnum != 99999 ) {
+ if( mvd_shownet->integer > 2 ) {
+ Com_Printf( " unchanged: %i\n", oldnum );
+ }
+ MVD_ParseDeltaPlayer( mvd, frame, oldnum, oldstate, 0 );
+
+ oldindex++;
+
+ if( oldindex >= oldframe->numPlayers ) {
+ oldnum = 99999;
+ } else {
+ i = oldframe->firstPlayer + oldindex;
+ oldstate = &mvd->playerStates[i & MVD_PLAYERS_MASK];
+ oldnum = PPS_NUM( oldstate );
+ }
+ }
+}
+
+/*
+================
+MVD_ParseFrame
+================
+*/
+static void MVD_ParseFrame( mvd_t *mvd, qboolean delta ) {
+ mvdFrame_t *oldframe, *frame;
+ int length;
+
+ // allocate new frame
+ frame = &mvd->frames[++mvd->framenum & MVD_UPDATE_MASK];
+ frame->serverFrame = MSG_ReadLong();
+ frame->number = mvd->framenum;
+
+ if( delta ) {
+ oldframe = &mvd->frames[( mvd->framenum - 1 ) & MVD_UPDATE_MASK];
+ if( oldframe->number != mvd->framenum - 1 ) {
+ MVD_Destroy( mvd, "Delta from invalid frame" );
+ }
+ } else {
+ oldframe = NULL; // uncompressed frame
+ }
+
+ // read portalbits
+ length = MSG_ReadByte();
+ if( length ) {
+ if( length < 0 || msg_read.readcount + length > msg_read.cursize ) {
+ MVD_Destroy( mvd, "Read past end of message" );
+ }
+ if( length > MAX_MAP_AREAS/8 ) {
+ MVD_Destroy( mvd, "Bad portalbits length" );
+ }
+ CM_SetPortalStates( &mvd->cm, msg_read.data +
+ msg_read.readcount, length );
+ msg_read.readcount += length;
+ } else {
+ CM_SetPortalStates( &mvd->cm, NULL, 0 );
+ }
+
+ if( mvd_shownet->integer > 1 ) {
+ Com_Printf( "%3i:playerinfo\n", msg_read.readcount - 1 );
+ }
+
+ MVD_ParsePacketPlayers( mvd, oldframe, frame );
+
+ if( mvd_shownet->integer > 1 ) {
+ Com_Printf( "%3i:packetentities\n", msg_read.readcount - 1 );
+ }
+
+ MVD_ParsePacketEntities( mvd, oldframe, frame );
+
+ if( mvd_shownet->integer > 1 ) {
+ Com_Printf( "%3i: frame:%i delta:%i\n",
+ msg_read.readcount - 1, mvd->framenum, delta );
+ }
+
+ MVD_FixEntityStates( mvd, frame );
+}
+
+static void MVD_ParseServerData( mvd_t *mvd ) {
+ int protocol, clientNum;
+ int length, index;
+ char *gamedir, *string;
+ entityStateEx_t *base, **chunk;
+ int entnum, bits;
+ uint32 checksum;
+
+ // clear the leftover from previous level
+ MVD_ClearState( mvd );
+
+ // parse major protocol version
+ protocol = MSG_ReadLong();
+ if( protocol != PROTOCOL_VERSION_MVD ) {
+ MVD_Destroy( mvd, "Unsupported protocol: %d", protocol );
+ }
+
+ // parse minor protocol version
+ protocol = MSG_ReadShort();
+ if( protocol != PROTOCOL_VERSION_MVD_MINOR ) {
+ MVD_Destroy( mvd, "MVD protocol version mismatch: %d instead of %d",
+ protocol, PROTOCOL_VERSION_MVD_MINOR );
+ }
+
+ mvd->servercount = MSG_ReadLong();
+ gamedir = MSG_ReadString();
+ clientNum = MSG_ReadShort();
+ if( clientNum < 0 || clientNum >= MAX_CLIENTS ) {
+ MVD_Destroy( mvd, "Invalid client num: %d", clientNum );
+ }
+ mvd->clientNum = clientNum;
+
+ // change gamedir unless playing a demo
+ Q_strncpyz( mvd->gamedir, gamedir, sizeof( mvd->gamedir ) );
+ if( !mvd->demoplayback ) {
+ Cvar_UserSet( "game", gamedir );
+ if( FS_NeedRestart() ) {
+ FS_Restart();
+ }
+ }
+
+ // parse configstrings
+ while( 1 ) {
+ index = MSG_ReadShort();
+ if( index == MAX_CONFIGSTRINGS ) {
+ break;
+ }
+
+ if( index < 0 || index >= MAX_CONFIGSTRINGS ) {
+ MVD_Destroy( mvd, "Bad configstring index: %d", index );
+ }
+
+ string = MSG_ReadStringLength( &length );
+
+ if( MAX_QPATH * index + length > sizeof( mvd->configstrings ) - 1 ) {
+ MVD_Destroy( mvd, "Oversize configstring" );
+ }
+
+ strcpy( mvd->configstrings[index], string );
+
+ if( msg_read.readcount > msg_read.cursize ) {
+ MVD_Destroy( mvd, "Read past end of message" );
+ }
+ }
+
+ mvd->maxclients = atoi( mvd->configstrings[CS_MAXCLIENTS] );
+
+ string = mvd->configstrings[ CS_MODELS + 1];
+ length = strlen( string );
+ if( length <= 9 ) {
+ MVD_Destroy( mvd, "Bad world model: %s", string );
+ }
+ strcpy( mvd->mapname, string + 5 ); // skip "maps/"
+ mvd->mapname[length - 9] = 0; // cut off ".bsp"
+
+ // load the world model (we are only interesed in visibility info)
+ Com_Printf( "Loading %s...\n", string );
+ CM_LoadMap( &mvd->cm, string, CM_LOAD_VISONLY, &checksum );
+
+ if( checksum != atoi( mvd->configstrings[CS_MAPCHECKSUM] ) ) {
+ MVD_Destroy( mvd, "Local map version differs from server" );
+ }
+
+ // get the spawn point for spectators
+ MVD_ParseEntityString( mvd );
+
+ // parse baselines
+ while( 1 ) {
+ entnum = MSG_ParseEntityBits( &bits );
+ if( !entnum ) {
+ break;
+ }
+
+ if( entnum < 0 || entnum >= MAX_EDICTS ) {
+ MVD_Destroy( mvd, "Bad baseline number: %d", entnum );
+ }
+
+ chunk = &mvd->baselines[entnum >> SV_BASELINES_SHIFT];
+ if( *chunk == NULL ) {
+ *chunk = MVD_Mallocz( sizeof( *base ) * SV_BASELINES_PER_CHUNK );
+ }
+
+ base = *chunk + ( entnum & SV_BASELINES_MASK );
+ MSG_ParseDeltaEntity( NULL, &base->s, entnum, bits );
+
+ MVD_LinkEntity( mvd, base );
+
+ if( msg_read.readcount > msg_read.cursize ) {
+ MVD_Destroy( mvd, "Read past end of message" );
+ }
+ }
+
+ // parse uncompressed frame
+ if( MSG_ReadByte() != mvd_frame_nodelta ) {
+ MVD_Destroy( mvd, "Expected uncompressed frame" );
+ }
+ MVD_ParseFrame( mvd, qfalse );
+
+ if( mvd->state < MVD_WAITING ) {
+ List_Append( &mvd_ready, &mvd->ready );
+ mvd->state = mvd->demoplayback ? MVD_READING : MVD_WAITING;
+ }
+
+ MVD_ChangeLevel( mvd );
+}
+
+static qboolean MVD_ParseMessage( mvd_t *mvd, fifo_t *fifo ) {
+ uint16 msglen;
+ uint32 magic;
+ byte *data;
+ int length;
+ int cmd, extrabits;
+
+ // parse magic
+ if( mvd->state == MVD_CHECKING ) {
+ if( !FIFO_Read( fifo, &magic, 4 ) ) {
+ return qfalse;
+ }
+ if( magic != MVD_MAGIC ) {
+ MVD_Destroy( mvd, "Not a MVD stream" );
+ }
+ mvd->state = MVD_PREPARING;
+ }
+
+ // parse msglen
+ msglen = mvd->msglen;
+ if( !msglen ) {
+ if( !FIFO_Read( fifo, &msglen, 2 ) ) {
+ return qfalse;
+ }
+ if( !msglen ) {
+ MVD_Destroy( mvd, "End of MVD stream reached" );
+ }
+ msglen = LittleShort( msglen );
+ if( msglen > MAX_MSGLEN ) {
+ MVD_Destroy( mvd, "Invalid MVD message length: %d bytes", msglen );
+ }
+ mvd->msglen = msglen;
+ }
+
+ // first, try to read in a single block
+ data = FIFO_Peek( fifo, &length );
+ if( length < msglen ) {
+ if( !FIFO_Read( fifo, msg_read_buffer, msglen ) ) {
+ return qfalse; // not yet available
+ }
+ SZ_Init( &msg_read, msg_read_buffer, sizeof( msg_read_buffer ) );
+ } else {
+ SZ_Init( &msg_read, data, msglen );
+ FIFO_Decommit( fifo, msglen );
+ }
+
+ mvd->msglen = 0;
+
+ msg_read.cursize = msglen;
+
+ if( mvd_shownet->integer == 1 ) {
+ Com_Printf( "%i ", msg_read.cursize );
+ } else if( mvd_shownet->integer > 1 ) {
+ Com_Printf( "------------------\n" );
+ }
+
+//
+// parse the message
+//
+ while( 1 ) {
+ if( msg_read.readcount > msg_read.cursize ) {
+ MVD_Destroy( mvd, "Read past end of message" );
+ }
+
+ if( ( cmd = MSG_ReadByte() ) == -1 ) {
+ if( mvd_shownet->integer > 1 ) {
+ Com_Printf( "%3i:END OF MESSAGE\n", msg_read.readcount - 1 );
+ }
+ break;
+ }
+
+ extrabits = cmd >> SVCMD_BITS;
+ cmd &= SVCMD_MASK;
+
+ if( mvd_shownet->integer > 1 ) {
+ MVD_ShowSVC( cmd );
+ }
+
+ switch( cmd ) {
+ case mvd_serverdata:
+ MVD_ParseServerData( mvd );
+ break;
+ case mvd_multicast_all:
+ case mvd_multicast_pvs:
+ case mvd_multicast_phs:
+ case mvd_multicast_all_r:
+ case mvd_multicast_pvs_r:
+ case mvd_multicast_phs_r:
+ MVD_ParseMulticast( mvd, cmd, extrabits );
+ break;
+ case mvd_unicast:
+ case mvd_unicast_r:
+ MVD_ParseUnicast( mvd, cmd, extrabits );
+ break;
+ case mvd_configstring:
+ MVD_ParseConfigstring( mvd );
+ break;
+ case mvd_frame:
+ MVD_ParseFrame( mvd, qtrue );
+ break;
+ case mvd_frame_nodelta:
+ MVD_ParseFrame( mvd, qfalse );
+ break;
+ case mvd_sound:
+ MVD_ParseSound( mvd, extrabits );
+ break;
+ default:
+ MVD_Destroy( mvd, "Illegible command at %d: %d",
+ msg_read.readcount - 1, cmd );
+ }
+ }
+
+ return qtrue;
+}
+
+#if USE_ZLIB
+static int MVD_Decompress( mvd_t *mvd ) {
+ byte *data;
+ int avail_in, avail_out;
+ z_streamp z = &mvd->z;
+ int ret = Z_BUF_ERROR;
+
+ do {
+ data = FIFO_Peek( &mvd->stream.recv, &avail_in );
+ if( !avail_in ) {
+ break;
+ }
+ z->next_in = data;
+ z->avail_in = avail_in;
+
+ data = FIFO_Reserve( &mvd->zbuf, &avail_out );
+ if( !avail_out ) {
+ break;
+ }
+ z->next_out = data;
+ z->avail_out = avail_out;
+
+ ret = inflate( z, Z_SYNC_FLUSH );
+
+ FIFO_Decommit( &mvd->stream.recv, avail_in - z->avail_in );
+ FIFO_Commit( &mvd->zbuf, avail_out - z->avail_out );
+ } while( ret == Z_OK );
+
+ return ret;
+}
+#endif
+
+qboolean MVD_Parse( mvd_t *mvd ) {
+ fifo_t *fifo;
+
+#if USE_ZLIB
+ if( mvd->z.state ) {
+ int ret = MVD_Decompress( mvd );
+
+ switch( ret ) {
+ case Z_BUF_ERROR:
+ case Z_OK:
+ break;
+ case Z_STREAM_END:
+ Com_DPrintf( "End of zlib stream reached\n" );
+ inflateEnd( &mvd->z );
+ break;
+ default:
+ MVD_Destroy( mvd, "inflate() failed: %s", mvd->z.msg );
+ }
+ }
+ if( mvd->zbuf.data ) {
+ fifo = &mvd->zbuf;
+ } else
+#endif
+ {
+ fifo = &mvd->stream.recv;
+ }
+
+ if( MVD_ParseMessage( mvd, fifo ) ) {
+ return qtrue;
+ }
+ if( mvd->state == MVD_DISCONNECTED ) {
+ MVD_Destroy( mvd, "MVD stream was truncated" );
+ }
+ return qfalse;
+}
+