/*
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 "server.h"
#include "mvd.h"

#define MVD_ShowSVC( cmd ) do { \
	Com_Printf( "%3i:%s\n", msg_read.readcount - 1, \
        MSG_ServerCommandString( cmd ) ); \
    } while( 0 )

/* frame being currently parsed, valid for each packet */
static 	mvdFrame_t	*parseFrame;	

/* latest valid gamestate, delta decompression code gets
 * it's baselines from it */
static	mvdGamestate_t *parseGS;

static void MVD_LinkEntity( 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 = parseGS->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( &parseGS->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_ParseBaseline( void ) {
	entityStateEx_t *base, **chunk;
	int entnum, bits;

	if( !mvd.nextGS ) {
		Com_Error( ERR_DROP, "MVD_ParseBaseline: not parsing a gamestate" );
	}

	entnum = MSG_ParseEntityBits( &bits );
	if( entnum < 1 || entnum >= MAX_EDICTS ) {
		Com_Error( ERR_DROP, "MVD_ParseBaseline: bad number: %d", entnum );
	}
	
	chunk = &mvd.nextGS->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 );
}

static void MVD_ParseEntityString( void ) {
	const char *data, *p;
	char key[MAX_STRING_CHARS];
	char value[MAX_STRING_CHARS];
	char classname[MAX_QPATH];
	vec3_t origin;
	vec3_t angles;
	
	parseGS->spawnSet = qfalse;

	data = ( const char * )parseGS->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, parseGS->spawnOrigin );
			VectorCopy( angles, parseGS->spawnAngles );
			parseGS->spawnSet = qtrue;
			continue;
		}
		
		if( parseGS->spawnSet ) {
			continue;
		}
			
		if( !strcmp( classname + 12, "start" ) ||
                !strcmp( classname + 12, "deathmatch" ) )
        {
			VectorCopy( origin, parseGS->spawnOrigin );
			VectorCopy( angles, parseGS->spawnAngles );
			parseGS->spawnSet = qtrue;
		}

	}

	if( !parseGS->spawnSet ) {
		Com_WPrintf( "Couldn't find spawn point for MVD spectators\n" );
	}
}

static void MVD_Precache_f( void ) {
	client_t *client;
	mvdClient_t *mvdcl;
	entityStateEx_t *base;
	int i, j;
	uint32 checksum, check;
	char *s;

	if( !mvd.nextGS ) {
		Com_Error( ERR_DROP, "MVD_Precache_f: not parsing a gamestate" );
	}

	/* parse mapname */
	if( !mvd.nextGS->mapname[0] ) {
		Com_Error( ERR_DROP, "MVD_Precache_f: no world model specified" );
	}

	/* we have completely parsed the gamestate */
	mvd.nextGS->serverPacketNum = mvd.serverPacketNum;
	mvd.gamestateSequence++;

	MVD_ClientCommand( va( "begin %d\n", mvd.servercount ) );
	mvd.validPacketNum = -1;

	if( mvd.netchan &&
		mvd.serverProtocol > PROTOCOL_VERSION_DEFAULT &&
		mvd.serverProtocol < PROTOCOL_VERSION_MVD )
	{
        MSG_WriteByte( clc_setting );
        MSG_WriteShort( CLS_RECORDING );
        MSG_WriteShort( 1 );
        MSG_FlushTo( &mvd.netchan->message );
    }

	mvd.lastLayoutPacket = mvd.serverPacketNum - LAYOUT_TIMEOUT / 2;

	if( mvd.state < MVD_PRIMED ) {
		int maxPlayers = mvd.nextGS->maxclients;

		if( !maxPlayers ) {
            if( mvd.serverProtocol != PROTOCOL_VERSION_OLD ) {
				Com_Error( ERR_DROP, "MVD_Precache_f: no maxclients specified" );
			}
			maxPlayers = 1; /* ugly hack */
		} else if( maxPlayers < 0 || maxPlayers > CLIENTNUM_RESERVED ) {
			Com_Error( ERR_DROP, "MVD_Precache_f: bad maxclients: %d",
                    maxPlayers );
		}

        if( mvd.serverProtocol == PROTOCOL_VERSION_MVD ) {
            if( mvd.clientNum != CLIENTNUM_NONE && mvd.clientNum >= maxPlayers ) {
			    Com_Error( ERR_DROP, "MVD_Precache_f: bad clientNum: %d >= %d",
                        mvd.clientNum, maxPlayers );
            }
    		mvd.maxPlayers = maxPlayers;
	    	SV_InitGame( maxPlayers );
        } else {
            /* non-MVD protocol needs exactly one player slot */
            mvd.maxPlayers = 1;
            SV_InitGame( 1 );
            svs.maxGameClients = maxPlayers;
        }
		MVD_TransitionGamestate( mvd.gamestateSequence );

		parseFrame = &mvd.frames[mvd.serverPacketNum % mvd.frameBackup];
		parseFrame->serverPacketNum = mvd.serverPacketNum;

		mvd.state = MVD_PRIMED;
	}

	/* tell all unbuffered clients to reconnect */
	MSG_WriteByte( svc_stufftext );
	MSG_WriteString( "reconnect\n" );

    FOR_EACH_CLIENT( client ) {
		if( client->state < cs_connected ) {
			continue;
		}
		if( client->protocol != PROTOCOL_VERSION_MVD ) {
			mvdcl = ( mvdClient_t * )client->edict->client;
			if( !mvdcl->admin ) {
				continue;
			}
		}

		SV_ClientAddMessage( client, MSG_RELIABLE );
	}

	SZ_Clear( &msg_write );

	parseFrame->gamestateSequence = mvd.gamestateSequence;
	parseFrame->flags |= MFF_GAMESTATE;

	parseGS = mvd.nextGS;
	mvd.nextGS = NULL;

	/* load the map and link the baselines */
	if( mvd.serverProtocol == PROTOCOL_VERSION_MVD ) {
		CM_LoadMap( &parseGS->cm, parseGS->mapname, CM_LOAD_VISONLY, &checksum );

		check = atoi( mvd.configstrings[CS_MAPCHECKSUM] );
		if( check != checksum ) {
			Com_Error( ERR_DROP, "MVD_Precache_f: local map version differs from server" );
		}

		for( i = 1; i < parseGS->cm.cache->numcmodels; i++ ) {
			s = mvd.configstrings[ CS_MODELS + 1 + i ];
			if( *s != '*' ) {
				Com_Error( ERR_DROP, "MVD_Precache_f: bad inline model %d: %s", i, s );
			}
			if( atoi( s + 1 ) != i ) {
				Com_Error( ERR_DROP, "MVD_Precache_f: mismatched inline model %d: %s", i, s );
			}
		}

		MVD_ParseEntityString();

		for( i = 0; i < SV_BASELINES_CHUNKS; i++ ) {
			base = parseGS->baselines[i];
			if( !base ) {
				continue;
			} 
			for( j = 0; j < SV_BASELINES_PER_CHUNK; j++, base++ ) {
				if( base->s.number ) {
					MVD_LinkEntity( base );
				}
			}
		}
	}
}

static void MVD_ParseServerData( void ) {
	int protocol, i;
	char *gamedir;

	if( mvd.gamestateSequence - mvd.activeGamestateSequence >= MAX_GAMESTATES - 1 ) {
		Com_Error( ERR_DROP, "MVD_ParseServerData: dropped gamestate" );
	}

	mvd.nextGS = &mvd.gamestates[( mvd.gamestateSequence + 1 ) & GAMESTATE_MASK];
	memset( mvd.nextGS, 0, sizeof( *mvd.nextGS ) );

	memset( mvd.configstrings, 0, sizeof( mvd.configstrings ) );

	/* parse protocol version */
	protocol = MSG_ReadLong();
	if( protocol != mvd.serverProtocol ) {
		if( !mvd.demoplayback ) {
			Com_Error( ERR_DROP,
				"MVD client requested protocol %i, but "
				"server returned %i (should not happen).",
				mvd.serverProtocol, protocol );
		}
		if( protocol == PROTOCOL_VERSION_OLD ) {
			Com_DPrintf( "Using protocol %i for compatibility with old demos\n",
				PROTOCOL_VERSION_OLD );
		} else if( protocol < PROTOCOL_VERSION_DEFAULT ||
                protocol > PROTOCOL_VERSION_MVD )
        {
			Com_Error( ERR_DROP,
				"Demo uses unsupported protocol version %i.\n"
				"Supported protocols are %i, %i, %i and %i.",
				protocol, PROTOCOL_VERSION_DEFAULT, PROTOCOL_VERSION_R1Q2,
				PROTOCOL_VERSION_Q2PRO, PROTOCOL_VERSION_MVD );
		}
		mvd.serverProtocol = protocol;
	}

	mvd.servercount = MSG_ReadLong();	/* servercount */
	MSG_ReadByte();						/* attractLoop */
	gamedir = MSG_ReadString();			/* gamedir */
	i = MSG_ReadShort();				/* clientNum */
    if( ( unsigned )i >= MAX_CLIENTS ) {
        i = CLIENTNUM_NONE;
    }
    if( mvd.clientNum != i ) {
        if( mvd.state > MVD_PRIMED ) {
    	    Com_Error( ERR_DROP, "Changed clientNum: %d != %d\n",
                mvd.clientNum, i );
        }
        mvd.clientNum = i;
    }
	MSG_ReadString();					/* levelname */
	
	/* change gamedir */
	if( !mvd.demoplayback ) {
		Cvar_UserSet( "game", gamedir );
	}

	if( mvd.serverProtocol == PROTOCOL_VERSION_R1Q2 ) {
		i = MSG_ReadByte();
		if( i ) {
			Com_Error( ERR_DROP, "MVD_ParseServerData: 'enhanced' R1Q2 "
				"servers are not supported" );
		}
		i = MSG_ReadShort();
		if( i != PROTOCOL_VERSION_R1Q2_MINOR ) {
			if( mvd.demoplayback ) {
				Com_WPrintf(
		"Demo uses different minor R1Q2 protocol version (%i instead of %i).\n"
		"It may not play back properly.\n",
					i, PROTOCOL_VERSION_R1Q2_MINOR );
			} else {
				if( i < PROTOCOL_VERSION_R1Q2_MINOR ) {
					Com_Error( ERR_DROP,
			    "Server uses OLDER minor R1Q2 protocol version (%i < %i).\n"
				"Try reconnecting using original Quake2 protocol.",
						i, PROTOCOL_VERSION_R1Q2_MINOR );
				} else {
					Com_Error( ERR_DROP,
				"Server uses NEWER minor R1Q2 protocol version (%i > %i).\n"
				"Consider updating your MVD client or try reconnecting using "
				"original Quake2 protocol.",
						i, PROTOCOL_VERSION_R1Q2_MINOR );
				}
			}
		}
		MSG_ReadByte(); /* advancedDeltas */
		MSG_ReadByte(); /* strafeHack */
	} else if( mvd.serverProtocol == PROTOCOL_VERSION_Q2PRO ) {
		i = MSG_ReadShort();
		if( i != PROTOCOL_VERSION_Q2PRO_MINOR ) {
			if( mvd.demoplayback ) {
				Com_WPrintf(
	    "Demo uses different minor Q2PRO protocol version (%i instead of %i).\n"
		"It may not play back properly.\n",
					i, PROTOCOL_VERSION_Q2PRO_MINOR );
			} else {
				if( i < PROTOCOL_VERSION_Q2PRO_MINOR ) {
					Com_Error( ERR_DROP,
			"Server uses OLDER minor Q2PRO protocol version (%i < %i).\n"
			"Try reconnecting using original Quake2 protocol or R1Q2 protocol.",
						i, PROTOCOL_VERSION_Q2PRO_MINOR );
				} else {
					Com_Error( ERR_DROP,
			"Server uses NEWER minor Q2PRO protocol version (%i > %i).\n"
			"Consider updating your MVD client or try reconnecting using "
			"original Quake2 protocol or R1Q2 protocol.",
						i, PROTOCOL_VERSION_Q2PRO_MINOR );
				}
			}
		}
		MSG_ReadByte(); /* gametype */
		MSG_ReadByte(); /* strafeHack */
		MSG_ReadByte(); //atu QWMod
	} else if( mvd.serverProtocol == PROTOCOL_VERSION_MVD ) {
		i = MSG_ReadShort();
		if( i != PROTOCOL_VERSION_MVD_MINOR ) {
			if( mvd.demoplayback ) {
				Com_WPrintf(
		"Demo uses different minor MVD protocol version (%i instead of %i).\n"
		"It may not play back properly.\n",
					i, PROTOCOL_VERSION_MVD_MINOR );
			} else {
				if( i < PROTOCOL_VERSION_MVD_MINOR ) {
					Com_Error( ERR_DROP,
				"Server uses OLDER minor MVD protocol version (%i < %i).\n"
				"Try reconnecting using non-MVD protocol.",
						i, PROTOCOL_VERSION_MVD_MINOR );
				} else {
					Com_Error( ERR_DROP,
			    "Server uses NEWER minor MVD protocol version (%i > %i).\n"
				"Consider updating your MVD client or try reconnecting using "
				"non-MVD protocol.",
						i, PROTOCOL_VERSION_MVD_MINOR );
				}
			}
		}
	}

	mvd.validPacketNum = -1;
}

static void MVD_WriteFrameDatagram( byte *data, int length ) {
	int ofs, mask;

	if( mvd.state < MVD_PRIMED ) {
		Com_DPrintf( "MVD_WriteFrameDatagram: server data not yet received\n" );
		return;
	}

	/* write complete message into the circular buffer */
	ofs = mvd.nextMessageBytes;
	mask = mvd.maxMessageBytes - 1;
	parseFrame->numMessageBytes += length;
	mvd.nextMessageBytes += length;
	for( ; length; length--, ofs++ ) {
		mvd.messageBytes[ofs & mask] = *data++;
	}
}

static void MVD_WriteFrameReliableDatagram( byte *data, int length ) {
	int ofs, mask;

	if( mvd.state < MVD_PRIMED ) {
		Com_DPrintf( "MVD_WriteFrameReliableDatagram: server data not yet received\n" );
		return;
	}

	/* write complete message into the circular buffer */
	ofs = mvd.nextReliableMessageBytes;
	mask = mvd.maxReliableMessageBytes - 1;
	parseFrame->numReliableMessageBytes += length;
	parseFrame->flags |= MFF_RELIABLE;
	mvd.nextReliableMessageBytes += length;
	for( ; length; length--, ofs++ ) {
		mvd.reliableMessageBytes[ofs & mask] = *data++;
	}
}


static void MVD_ParseMulticast( void ) {
	multicast_t to;
	int length;
	byte *data;
	client_t *client;
    union {
        vec_t v[3];
        uint32 l[3];
    } origin;

	/* including svc_multicast byte */
	data = msg_read.data + msg_read.readcount - 1;

	length = MSG_ReadShort();
	to = ( length >> 12 ) & 7;
	length &= 0xFFF;

	if( to > MULTICAST_PVS_R ) {
		Com_Error( ERR_DROP, "MVD_ParseMulticast: bad to" );
	}

	if( length < 1 ) {
		Com_Error( ERR_DROP, "MVD_ParseMulticast: bad length" );
	}

	if( to != MULTICAST_ALL && to != MULTICAST_ALL_R ) {
		origin.l[0] = MSG_ReadLong();
		origin.l[1] = MSG_ReadLong();
		origin.l[2] = MSG_ReadLong();
	} else {
		VectorClear( origin.v );
	}

	msg_read.readcount += length;
	if( msg_read.readcount > msg_read.cursize ) {
		Com_Error( ERR_DROP, "MVD_ParseMulticast: read past end of message" );
	}

	if( to != MULTICAST_ALL && to != MULTICAST_ALL_R ) {
		length += 12;
	}
	length += 3;

	if( to >= MULTICAST_ALL_R ) {
		MVD_WriteFrameReliableDatagram( data, length );
	} else {
		MVD_WriteFrameDatagram( data, length );
	}

	/* add this immediately to MVD clients */
    FOR_EACH_CLIENT( client ) {
		if( client->state < cs_spawned ) {
			continue;
		}
        if( client->protocol == PROTOCOL_VERSION_MVD ) {
    		client->AddMessage( client, data, length,
	    		to >= MULTICAST_ALL_R ? qtrue : qfalse );
        }
	}

	/* add this immediately to unbuffered clients */
	MVD_Multicast( qtrue, origin.v, to );

}

static void MVD_ParseUnicast( void ) {
	int length, clientNum;
	byte *data;
	client_t *client;

	/* including svc_unicast byte */
	data = msg_read.data + msg_read.readcount - 1;

	clientNum = MSG_ReadByte(); /* skip clientNum */
	length = MSG_ReadShort();
	clientNum &= 0x7F;
	if( ( unsigned )clientNum >= mvd.maxPlayers ) {
		Com_Error( ERR_DROP, "MVD_ParseUnicast: bad clientNum" );
	}
	if( length < 1 ) {
		Com_Error( ERR_DROP, "MVD_ParseUnicast: bad length" );
	}

	if( clientNum == mvd.clientNum && msg_read.data[msg_read.readcount] == svc_layout ) {
		mvd.lastLayoutPacket = mvd.serverPacketNum;
	}

	msg_read.readcount += length;
	if( msg_read.readcount > msg_read.cursize ) {
		Com_Error( ERR_DROP, "MVD_ParseUnicast: read past end of message" );
	}

	length += 4;
	
	MVD_WriteFrameReliableDatagram( data, length );

	/* add this immediately to MVD clients */
    FOR_EACH_CLIENT( client ) {
		if( client->state < cs_connected ) {
			continue;
		}
        if( client->protocol == PROTOCOL_VERSION_MVD ) {
    		client->AddMessage( client, data, length, qtrue );
        }
	}
}

static void MVD_ParseConfigstring( void ) {
	mvdConfigstring_t *cs;
	int length, index, stringLength;
	byte *data;
	char *string;
	client_t *client;
	mvdClient_t *mvdcl;

	/* including svc_configstring byte */
	data = msg_read.data + msg_read.readcount - 1;

	length = msg_read.readcount;
	index = MSG_ReadShort();
	if( index < 0 || index >= MAX_CONFIGSTRINGS ) {
		Com_Error( ERR_DROP, "MVD_ParseConfigstring: bad index: %d", index );
	}
	string = MSG_ReadString();
	length = msg_read.readcount - length + 1;

	stringLength = strlen( string );
	if( sizeof( mvd.configstrings[0] ) * index + stringLength >
		sizeof( mvd.configstrings ) - 1 )
	{
		Com_Error( ERR_DROP, "MVD_ParseConfigstring: oversize configstring" );
	}

	/* archive in our latest configstrings array */
	strcpy( mvd.configstrings[index], string );

	if( !mvd.nextGS ) {
		if( mvd.state < MVD_PRIMED ) {
			Com_DPrintf( "Out-of-place CS %d: %s\n", index, string );
			return;
		}
		/* add this stuff immediately for camera people */
        FOR_EACH_CLIENT( client ) {
			if( client->state < cs_connected ) {
				continue;
			}
			if( client->protocol != PROTOCOL_VERSION_MVD ) {
				mvdcl = ( mvdClient_t * )client->edict->client;
				if( !mvdcl->admin ) {
					continue;
				}
			}
			client->AddMessage( client, data, length, qtrue );
		}
		MVD_WriteFrameReliableDatagram( data, length );	
		return;
	}

	/* assume server is smart enough and doesn't send dup. configstrings */
	cs = MVD_Mallocz( sizeof( *cs ) + stringLength );
	strcpy( cs->string, string );
	cs->index = index;
	if( mvd.nextGS->tailCS ) {
		mvd.nextGS->tailCS->next = cs;
	} else {
		mvd.nextGS->headCS = cs;
	}
	mvd.nextGS->tailCS = cs;

	/* parse additional data */
	switch( index ) {
	case CS_MODELS + 1:
		Q_strncpyz( mvd.nextGS->mapname, string, MAX_QPATH );
		break;
	case CS_MAXCLIENTS:
		mvd.nextGS->maxclients = atoi( string );
		break;
	case CS_NAME:
		Q_strncpyz( mvd.nextGS->fullname, string, MAX_QPATH );
		break;
	default:
		break;
	}
}

static void MVD_FixEntityStates( mvdFrame_t *prevFrame, mvdFrame_t *currFrame ) {
	entityStateEx_t *prevStates[MAX_EDICTS];
	entityStateEx_t *currStates[MAX_CLIENTS + 1];
	entityStateEx_t *state, *prev;
	playerStateEx_t *ps;
	int i, j;

	for( i = 0; i < MAX_EDICTS; i++ ) {
		prevStates[i] = NULL;
	}

	for( i = 0; i < MAX_CLIENTS; i++ ) {
		currStates[i] = NULL;
	}

	for( i = 0; i < prevFrame->numEntityStates; i++ ) {
		j = ( prevFrame->firstEntityState + i ) % mvd.maxEntityStates;
		state = &mvd.entityStates[j];
		
		if( ( unsigned )state->s.number >= MAX_EDICTS ) {
			Com_Error( ERR_DROP, "MVD_FixEntityStates: bad entity number: %d",
				state->s.number );
		}

		prevStates[state->s.number] = state;
	}

	for( i = 0; i < currFrame->numEntityStates; i++ ) {
		j = ( currFrame->firstEntityState + i ) % mvd.maxEntityStates;
		state = &mvd.entityStates[j];
		
		if( ( unsigned )state->s.number >= MAX_EDICTS ) {
			Com_Error( ERR_DROP, "MVD_FixEntityStates: bad entity number: %d",
				state->s.number );
		}
		
		if( state->s.number <= MAX_CLIENTS ) {
			currStates[state->s.number] = state;
		}
	}

	if( mvd.serverProtocol >= PROTOCOL_VERSION_Q2PRO ) {
		for( i = 0; i < currFrame->numPlayerStates; i++ ) {
			j = ( currFrame->firstPlayerState + i ) % mvd.maxPlayerStates;
			ps = &mvd.playerStates[j];
				
			if( ( unsigned )ps->number >= mvd.maxPlayers ) {
				Com_Error( ERR_DROP, "MVD_FixEntityStates: bad player number: %d\n",
					ps->number );
			}
			if( ( unsigned )ps->clientNum >= MAX_CLIENTS ) {
				Com_Error( ERR_DROP, "MVD_FixEntityStates: bad clientNum: %d\n",
					ps->clientNum );
			}
	   
			if( ps->ps.pmove.pm_type >= PM_DEAD ) {
				continue;
			}
			if( ps->clientNum == CLIENTNUM_NONE ) {
				continue;
			}
			state = currStates[ ps->clientNum + 1 ];
			if( !state ) {
				continue;
			}

			VectorScale( ps->ps.pmove.origin, 0.125f, state->s.origin );
			VectorCopy( ps->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;

			if( mvd.serverProtocol == PROTOCOL_VERSION_MVD ) {
				MVD_LinkEntity( state );
			}

		}
	}

	for( i = 0; i < currFrame->numEntityStates; i++ ) {
		j = ( currFrame->firstEntityState + i ) % mvd.maxEntityStates;
		state = &mvd.entityStates[j];
		
		if( ( unsigned )state->s.number >= MAX_EDICTS ) {
			Com_Error( ERR_DROP, "MVD_FixEntityStates: bad entity number: %d",
				state->s.number );
		}
		
		if( state->s.renderfx & RF_BEAM ) {
			continue;
		}

		prev = prevStates[state->s.number];
		if( prev ) {
			VectorCopy( prev->s.origin, state->s.old_origin );
		}
	}
	
}


/*
==================
MVD_ParseDeltaEntity
==================
*/

#define RELINK_MASK	\
	(U_MODEL|U_ORIGIN1|U_ORIGIN2|U_ORIGIN3|U_SOLID)

static void MVD_ParseDeltaEntity( mvdFrame_t *frame, int newnum,
								 entityStateEx_t *old, int bits )
{
	entityStateEx_t	*state;
	int i;

	i = mvd.nextEntityStates % mvd.maxEntityStates;
	state = &mvd.entityStates[i];
	mvd.nextEntityStates++;
	frame->numEntityStates++;

	if( mvd_shownet->integer > 2 ) {
		MSG_ShowDeltaEntityBits( bits );
	}

	MSG_ParseDeltaEntity( ( entity_state_t * )old, ( entity_state_t * )state, newnum, bits );

	if( mvd.serverProtocol == PROTOCOL_VERSION_MVD && newnum > parseGS->maxclients ) {
		if( !old || !old->linked || ( bits & RELINK_MASK ) ) {
			MVD_LinkEntity( 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( mvdFrame_t *oldframe,
									mvdFrame_t *frame )
{
	int			newnum;
	int			bits;
	entityStateEx_t	*oldstate, *base;
	int			oldindex, oldnum;
	int i;

	frame->firstEntityState = mvd.nextEntityStates;
	frame->numEntityStates = 0;

	// delta from the entities present in oldframe
	oldindex = 0;
	oldstate = NULL;
	if( !oldframe ) {
		oldnum = 99999;
	} else {
		if( oldindex >= oldframe->numEntityStates ) {
			oldnum = 99999;
		} else {
			i = oldframe->firstEntityState + oldindex;
			i %= mvd.maxEntityStates;
			oldstate = &mvd.entityStates[i];
			oldnum = oldstate->s.number;
		}
	}

	while( 1 ) {
		newnum = MSG_ParseEntityBits( &bits );
		if( newnum < 0 || newnum >= MAX_EDICTS ) {
			Com_Error( ERR_DROP, "ParsePacketEntities: bad number %i",
				newnum );
		}

		if( msg_read.readcount > msg_read.cursize ) {
			Com_Error( ERR_DROP, "ParsePacketEntities: end of message" );
		}

		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( frame, oldnum, oldstate, 0 );
			
			oldindex++;

			if( oldindex >= oldframe->numEntityStates ) {
				oldnum = 99999;
			} else {
				i = oldframe->firstEntityState + oldindex;
				i %= mvd.maxEntityStates;
				oldstate = &mvd.entityStates[i];
				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 ) {
				Com_Error( ERR_DROP, "U_REMOVE: NULL oldframe" );
			}
			if( !oldstate ) {
				Com_Error( ERR_DROP, "U_REMOVE: NULL oldstate" );
			}

			oldindex++;

			if( oldindex >= oldframe->numEntityStates ) {
				oldnum = 99999;
			} else {
				i = oldframe->firstEntityState + oldindex;
				i %= mvd.maxEntityStates;
				oldstate = &mvd.entityStates[i];
				oldnum = oldstate->s.number;
			}
			continue;
		}

		if( oldnum == newnum ) {	
			// delta from previous state
			if( mvd_shownet->integer > 2 ) {
				Com_Printf( "   delta: %i ", newnum );
			}
			MVD_ParseDeltaEntity( frame, newnum, oldstate, bits );
			if( mvd_shownet->integer > 2 ) {
				Com_Printf( "\n" );
			}

			oldindex++;

			if( oldindex >= oldframe->numEntityStates ) {
				oldnum = 99999;
			} else {
				i = oldframe->firstEntityState + oldindex;
				i %= mvd.maxEntityStates;
				oldstate = &mvd.entityStates[i];
				oldnum = oldstate->s.number;
			}
			continue;
		}

		if( oldnum > newnum ) {	
			// delta from baseline
			if( mvd_shownet->integer > 2 ) {
				Com_Printf( "   baseline: %i ", newnum );
			}
			base = parseGS->baselines[newnum >> SV_BASELINES_SHIFT];
			if( base ) {
				base += newnum & SV_BASELINES_MASK;
			}
			MVD_ParseDeltaEntity( 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( frame, oldnum, oldstate, 0 );
		
		oldindex++;

		if( oldindex >= oldframe->numEntityStates ) {
			oldnum = 99999;
		} else {
			i = oldframe->firstEntityState + oldindex;
			i %= mvd.maxEntityStates;
			oldstate = &mvd.entityStates[i];
			oldnum = oldstate->s.number;
		}
	}
}

/*
==================
MVD_ParseDeltaPlayer
==================
*/
static void MVD_ParseDeltaPlayer( mvdFrame_t *frame, int newnum,
								 playerStateEx_t *old, int bits )
{
	playerStateEx_t	*state;
	int i;

	i = mvd.nextPlayerStates % mvd.maxPlayerStates;
	state = &mvd.playerStates[i];
	mvd.nextPlayerStates++;
	frame->numPlayerStates++;

	if( mvd_shownet->integer > 2 ) {
		MSG_ShowDeltaPlayerstateBits_Enhanced( bits );
	}
	MSG_ParseDeltaPlayerstate_Enhanced( old, state, bits );

	state->number = newnum;
    state->clientNum = newnum;
}

/*
==================
MVD_ParsePacketPlayers
==================
*/
static void MVD_ParsePacketPlayers( mvdFrame_t *oldframe, mvdFrame_t *frame ) {
	int			newnum;
	int			bits;
	playerStateEx_t	*oldstate;
	int			oldindex, oldnum;
	int			i;

	frame->firstPlayerState = mvd.nextPlayerStates;
	frame->numPlayerStates = 0;

	// delta from the players present in oldframe
	oldindex = 0;
	oldstate = NULL;
	if( !oldframe ) {
		oldnum = 99999;
	} else {
		if( oldindex >= oldframe->numPlayerStates ) {
			oldnum = 99999;
		} else {
			i = oldframe->firstPlayerState + oldindex;
			i %= mvd.maxPlayerStates;
			oldstate = &mvd.playerStates[i];
			oldnum = oldstate->number;
		}
	}

	while( 1 ) {
		newnum = MSG_ReadByte();
		if( newnum < 0 || newnum >= MAX_CLIENTS ) {
			Com_Error( ERR_DROP, "Delta_ParsePacketPlayers: bad number %i",
				newnum );
		}

		if( msg_read.readcount > msg_read.cursize ) {
			Com_Error( ERR_DROP, "Delta_ParsePacketPlayers: end of message" );
		}

		if( newnum == CLIENTNUM_NONE ) {
			break;
		}

		bits = MSG_ReadShort();
		if( !( bits & PS_REMOVE ) ) {
			/* read extraflags */
			bits |= MSG_ReadByte() << PS_BITS;
		}

		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( frame, oldnum, oldstate, 0 );
			
			oldindex++;

			if( oldindex >= oldframe->numPlayerStates ) {
				oldnum = 99999;
			} else {
				i = oldframe->firstPlayerState + oldindex;
				i %= mvd.maxPlayerStates;
				oldstate = &mvd.playerStates[i];
				oldnum = oldstate->number;
			}
		}

		if( bits & PS_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 ) {
				Com_Error( ERR_DROP, "U_REMOVE: NULL oldframe" );
			}
			if( !oldstate ) {
				Com_Error( ERR_DROP, "U_REMOVE: NULL oldstate" );
			}

			oldindex++;

			if( oldindex >= oldframe->numPlayerStates ) {
				oldnum = 99999;
			} else {
				i = oldframe->firstPlayerState + oldindex;
				i %= mvd.maxPlayerStates;
				oldstate = &mvd.playerStates[i];
				oldnum = oldstate->number;
			}
			continue;
		}

		if( oldnum == newnum ) {	
			// delta from previous state
			if( mvd_shownet->integer > 2 ) {
				Com_Printf( "   delta: %i ", newnum );
			}
			MVD_ParseDeltaPlayer( frame, newnum, oldstate, bits );
			if( mvd_shownet->integer > 2 ) {
				Com_Printf( "\n" );
			}

			oldindex++;

			if( oldindex >= oldframe->numPlayerStates ) {
				oldnum = 99999;
			} else {
				i = oldframe->firstPlayerState + oldindex;
				i %= mvd.maxPlayerStates;
				oldstate = &mvd.playerStates[i];
				oldnum = oldstate->number;
			}
			continue;
		}

		if( oldnum > newnum ) {	
			// delta from baseline
			if( mvd_shownet->integer > 2 ) {
				Com_Printf( "   baseline: %i ", newnum );
			}
			MVD_ParseDeltaPlayer( 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( frame, oldnum, oldstate, 0 );
		
		oldindex++;

		if( oldindex >= oldframe->numPlayerStates ) {
			oldnum = 99999;
		} else {
			i = oldframe->firstPlayerState + oldindex;
			i %= mvd.maxPlayerStates;
			oldstate = &mvd.playerStates[i];
			oldnum = oldstate->number;
		}
	}
}

static void MVD_SpawnClients( void ) {
	client_t *client;
	mvdClient_t *mvdcl;

    FOR_EACH_CLIENT( client ) {
		if( client->state != cs_ready ) {
			continue;
		}
		if( client->protocol != PROTOCOL_VERSION_MVD ) {
			mvdcl = ( mvdClient_t * )client->edict->client;
			if( !mvdcl->admin ) {
				continue;
			}
		}
		Com_DPrintf( "Going from cs_ready to cs_spawned for %s\n",
                client->name );
		client->state = cs_spawned;
		client->sendTime = 0;
		client->surpressCount = 0;
		client->commandMsec = 1800;
		
		// call the game begin function
		sv_client = client;
		sv_player = client->edict;
		ge->ClientBegin( client->edict );
		sv_client = NULL;
		sv_player = NULL;
		
	}
}

/*
================
MVD_ParseFrame
================
*/
static void MVD_ParseFrame( void ) {
	mvdFrame_t *oldframe;
	int currentframe, deltaframe, delta, deltaPacketNum;
	int length;
    uint32 bits;

	if( mvd.state < MVD_PRIMED ) {
		Com_Error( ERR_DROP, "MVD_ParseFrame: server data not yet received" );
	}

	if( parseFrame->serverFrame ) {
		Com_Error( ERR_DROP, "MVD_ParseFrame: duplicated frame in packet" );
	}

    bits = MSG_ReadLong();

    currentframe = bits & FRAMENUM_MASK;
    delta = bits >> FRAMENUM_BITS;

	if( delta == 31 ) {
		deltaframe = -1;
	} else {
		deltaframe = currentframe - delta;
	}

	mvd.packetNums[currentframe & UPDATE_MASK] = mvd.serverPacketNum;

	parseFrame->serverFrame = currentframe;
	parseFrame->flags |= MFF_ACTIVE;

	/* 
	 * If the frame is delta compressed from data that we
	 * no longer have available, we must suck up the rest of
	 * the frame, but not use it, then ask for a non-compressed
	 * message
	 */
	if( deltaframe > 0 ) {
	    deltaPacketNum = mvd.packetNums[deltaframe & UPDATE_MASK];
	    parseFrame->deltaPacketNum = deltaPacketNum;
        
		oldframe = &mvd.frames[deltaPacketNum % mvd.frameBackup];
        if( deltaframe == currentframe ) {
			/* should never happen */
		    Com_WPrintf( "Delta from current frame (should not happen).\n" );
        } else if( !( oldframe->flags & MFF_VALID ) ) {	
			/* should never happen */
			Com_WPrintf( "Delta from invalid frame (should not happen).\n" );
		} else if( oldframe->serverPacketNum != deltaPacketNum ) {
			/* The frame that the server did the delta from
			 * is too old, so we can't reconstruct it properly.
			 */
			Com_WPrintf( "Delta frame too old.\n" );
		} else if(
			mvd.nextEntityStates -
			oldframe->firstEntityState >
			mvd.maxEntityStates - MAX_EDICTS )
		{
			Com_WPrintf( "Delta entityStates too old.\n" );
		} else if(
			mvd.nextPlayerStates -
			oldframe->firstPlayerState >
			mvd.maxPlayerStates - mvd.maxPlayers )
		{
			Com_WPrintf( "Delta playerStates too old.\n" );
		} else {
			parseFrame->flags |= MFF_VALID;	/* valid delta parse */
		}
	} else {
		oldframe = NULL;
		mvd.demowaiting = qfalse;
		parseFrame->flags |= MFF_VALID;		/* uncompressed frame */
	}

	/* read portalbits */
	length = MSG_ReadByte();
	if( length ) {
		if( ( unsigned )length > sizeof( parseFrame->portalbytes ) ) {
			Com_Error( ERR_DROP, "MVD_ParseFrame: invalid portalbits length" );
		}
		if( msg_read.readcount + length > msg_read.cursize ) {
			Com_Error( ERR_DROP, "MVD_ParseFrame: read past end of message" );
		}
		MSG_ReadData( parseFrame->portalbytes, length );
		parseFrame->numPortalBytes = length;
	} else {
		memset( parseFrame->portalbytes, 255, sizeof( parseFrame->portalbytes ) );
		parseFrame->numPortalBytes = 0;
	}

	if( mvd_shownet->integer > 1 ) {
		Com_Printf( "%3i:playerinfo\n", msg_read.readcount - 1 );
	}

	MVD_ParsePacketPlayers( oldframe, parseFrame );

	if( mvd_shownet->integer > 1 ) {
		Com_Printf( "%3i:packetentities\n", msg_read.readcount - 1 );
	}

	MVD_ParsePacketEntities( oldframe, parseFrame );

	if( mvd_shownet->integer > 1 ) {
		Com_Printf( "%3i: frame:%i  delta:%i\n",
			msg_read.readcount - 1, parseFrame->serverPacketNum,
			parseFrame->deltaPacketNum );
	}

	if( !( parseFrame->flags & MFF_VALID ) ) {
		return; /* do not change anything */
	}

	if( mvd.validPacketNum == -1 ) {
		MVD_SpawnClients();
	}
	mvd.validPacketNum = mvd.serverPacketNum;
	mvd.lastServerFrame = currentframe;

	if( mvd.state < MVD_BUFFERING ) {
		mvd.state = MVD_BUFFERING;
		Com_DPrintf( "Started buffering at frame %d\n", mvd.serverPacketNum );
		mvd.activePacketNum = mvd.serverPacketNum;
	}

	oldframe = &mvd.frames[( mvd.serverPacketNum - 1 ) % mvd.frameBackup];
	MVD_FixEntityStates( oldframe, parseFrame );
}


static void MVD_ParsePrint( void ) {
	int level;
	char *string;

	level = MSG_ReadByte();
	string = MSG_ReadString();

	Com_Printf( "[MVD] %s", string );
}

static void MVD_Reconnect_f( void ) {
	MVD_ClientCommand( "new" );
}

static void MVD_Wait_f( void ) {
	mvd_buffer.waitCount = 1;
}

static void MVD_Changing_f( void ) {
	client_t *client;
	mvdClient_t *mvdcl;

	MSG_WriteByte( svc_stufftext );
	MSG_WriteString( "changing\n" );

    FOR_EACH_CLIENT( client ) {
		if( client->state < cs_connected ) {
			continue;
		}
		if( client->protocol != PROTOCOL_VERSION_MVD ) {
			mvdcl = ( mvdClient_t * )client->edict->client;
			if( !mvdcl->admin ) {
				continue;
			}
		}
		client->state = cs_connected;
		client->lastframe = -1;
		client->sendTime = 0;
		client->surpressCount = 0;

		SV_ClientAddMessage( client, MSG_RELIABLE );
	}

	SZ_Clear( &msg_write );

	SV_SendAsyncPackets();
}

static ucmd_t mvdcmds[] = {
	{ "cmd", MVD_ForwardToServer_f },
	{ "reconnect", MVD_Reconnect_f },
	{ "precache", MVD_Precache_f },

	{ "connect", MVD_Connect_f },
	{ "set", Cvar_Set_f },
	{ "alias", Cmd_Alias_f },

	{ "changing", MVD_Changing_f },
	{ "play", NULL },
	{ "exec", NULL },
	{ "wait", MVD_Wait_f },

	{ NULL, NULL }
};

void MVD_ExecuteString( const char *line ) {
	char *cmd, *alias;
	ucmd_t *u;

	if( !line[0] ) {
		return;
	}

	Cmd_TokenizeString( line, qtrue );

	cmd = Cmd_Argv( 0 );
	if( !cmd[0] ) {
		return;
	}
	for( u = mvdcmds; u->name; u++ ) {
		if( !strcmp( cmd, u->name ) ) {
			if( u->func ) {
				u->func();
			}
			return;
		}
	}

	alias = Cmd_AliasCommand( cmd );
	if( alias ) {
		if( ++mvd_buffer.aliasCount == ALIAS_LOOP_COUNT ) {
			Com_WPrintf( "MVD_ExecuteString: runaway alias loop\n" );
			return;
		}
		Cbuf_InsertTextEx( &mvd_buffer, alias );
		return;
	}

	if( Cvar_Command() ) {
		return;
	}

	MVD_ClientCommand( Cmd_RawArgsFrom( 0 ) );
}

static void MVD_ParseStuffText( void ) {
	char *string;

	string = MSG_ReadString();

	if( mvd.demoplayback ) {
		if( !strcmp( string, "precache\n" ) ) {
			MVD_Precache_f();
		}
		return;
	}

	Com_DPrintf( "[MVD] stufftext: %s\n", string );

	Cbuf_AddTextEx( &mvd_buffer, string );
}

/* 'hard' reconnecting discards anything in delay buffer,
 * and starts buffering again from the beginning */
static void MVD_ParseReconnect( void ) {
	netadr_t adr;

	if( !mvd.netchan ) {
		SV_Nextserver();
		return;
	}

	Com_Printf( "[MVD] Server disconnected, reconnecting...\n" );

	adr = mvd.netchan->remote_address;
	SV_Shutdown( "MVD server restarted\n", KILL_RESTART );

	mvd.state = MVD_CHALLENGING;
	mvd.connectTime = -9999;
	mvd.connectCount = 0;
	mvd.serverAddress = adr;

	Cvar_SetInteger( "mvd_running", 1 );
	Cvar_SetInteger( "sv_running", ss_loading );
}

static void MVD_ParseMessage_r( void ) {
	int			cmd;

	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 ) {
			Com_Error( ERR_DROP, "MVD_ParseMessage: 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;
		}

		if( mvd_shownet->integer > 1 ) {
			MVD_ShowSVC( cmd );
		}

		switch( cmd ) {
		case svc_serverdata:
			MVD_ParseServerData();
			break;
		case svc_multicast:
			MVD_ParseMulticast();
			break;
		case svc_unicast:
			MVD_ParseUnicast();
			break;
		case svc_configstring:
			MVD_ParseConfigstring();
			break;
		case svc_spawnbaseline:
			MVD_ParseBaseline();
			break;
		case svc_frame:
			MVD_ParseFrame();
			break;
		case svc_stufftext:
			MVD_ParseStuffText();
			break;
		case svc_print:
			MVD_ParsePrint();
			break;
		case svc_zpacket:
			MSG_ParseZPacket( MVD_ParseMessage_r );
			break;
		case svc_reconnect:
			MVD_ParseReconnect();
			return;
		case svc_disconnect:
			Com_Error( ERR_DISCONNECT, "[MVD] Server disconnected" );
			break;
		default:
			Com_Error( ERR_DROP, "MVD_ParseMessage: illegible command: %d", cmd );
			break;
		}
	}

}

/*
=====================================================================

  DEFAULT PROTOCOL WORKAROUNDS

=====================================================================
*/

#define DEFAULT_PLAYER_NUMBER   0

static void MVD_ParseDefaultFrame( int extrabits ) {
	uint32 bits, extraflags;
	int currentframe, deltaframe, delta, deltaPacketNum;
	mvdFrame_t *oldframe;
	playerStateEx_t *from, *to;
	int length;

	if( mvd.state < MVD_PRIMED ) {
		Com_Error( ERR_DROP, "MVD_ParseDefaultFrame: server data not yet received" );
	}

	if( parseFrame->serverFrame ) {
		Com_Error( ERR_DROP, "MVD_ParseDefaultFrame: duplicated frame in packet" );
	}

	extraflags = 0;
	if( mvd.serverProtocol > PROTOCOL_VERSION_DEFAULT ) {
		bits = MSG_ReadLong();

		currentframe = bits & FRAMENUM_MASK;
		delta = bits >> FRAMENUM_BITS;

		if( delta == 31 ) {
			deltaframe = -1;
		} else {
			deltaframe = currentframe - delta;
		}

		bits = MSG_ReadByte();
		extraflags = ( extrabits << 4 ) | ( bits >> SURPRESSCOUNT_BITS );
	} else {
		currentframe = MSG_ReadLong();
		deltaframe = MSG_ReadLong();
		if( mvd.serverProtocol != PROTOCOL_VERSION_OLD ) {
			MSG_ReadByte(); /* surpressCount */
		}
	}

	mvd.packetNums[currentframe & UPDATE_MASK] = mvd.serverPacketNum;

	parseFrame->serverFrame = currentframe;
	parseFrame->flags |= MFF_ACTIVE;

	/* If the frame is delta compressed from data that we
	   no longer have available, we must suck up the rest of
	   the frame, but not use it, then ask for a non-compressed
	   message
	*/
	if( deltaframe > 0 ) {
	    deltaPacketNum = mvd.packetNums[deltaframe & UPDATE_MASK];
	    parseFrame->deltaPacketNum = deltaPacketNum;
        
		oldframe = &mvd.frames[deltaPacketNum % mvd.frameBackup];
		from = &mvd.playerStates[oldframe->firstPlayerState % mvd.maxPlayerStates];
		if( deltaframe == currentframe ) {
            /* old buggy q2 servers still cause this on map change */
			Com_DPrintf( "Delta from current frame (should not happen).\n" );
		} else if( !( oldframe->flags & MFF_VALID ) ) {	
			/* should never happen */
			Com_WPrintf( "Delta from invalid frame (should not happen).\n" );
		} else if( oldframe->serverPacketNum != deltaPacketNum ) {
			/* The frame that the server did the delta from
			   is too old, so we can't reconstruct it properly.
			*/
			Com_WPrintf( "Delta frame too old.\n" );
		} else if(
			mvd.nextEntityStates -
			oldframe->firstEntityState >
			mvd.maxEntityStates - MAX_EDICTS )
		{
			Com_WPrintf( "Delta entityStates too old.\n" );
		} else {
			parseFrame->flags |= MFF_VALID;	/* valid delta parse */
		}
	} else {
		oldframe = NULL;
		from = NULL;
		mvd.demowaiting = qfalse;
	    parseFrame->deltaPacketNum = -1;
		parseFrame->flags |= MFF_VALID;		/* uncompressed frame */
	}

	/* read areabits */
	length = MSG_ReadByte();
	if( length ) {
		if( ( unsigned )length > sizeof( parseFrame->portalbytes ) ) {
			Com_Error( ERR_DROP,
                    "MVD_ParseDefaultFrame: invalid areabits length" );
        }
		if( msg_read.readcount + length > msg_read.cursize ) {
			Com_Error( ERR_DROP,
                    "MVD_ParseDefaultFrame: read past end of message" );
		}
		MSG_ReadData( parseFrame->portalbytes, length );
		parseFrame->numPortalBytes = length;
	} else {
		memset( parseFrame->portalbytes, 255, sizeof( parseFrame->portalbytes ) );
		parseFrame->numPortalBytes = 0;
	}

	if( mvd.serverProtocol <= PROTOCOL_VERSION_DEFAULT ) {
		if( MSG_ReadByte() != svc_playerinfo ) {
			Com_Error( ERR_DROP, "MVD_ParseDefaultFrame: not playerinfo" );
		}
	}

	if( mvd_shownet->integer > 2 ) {
		Com_Printf( "%3i:playerinfo\n", msg_read.readcount - 1 );
	}

	parseFrame->firstPlayerState = mvd.nextPlayerStates;
	to = &mvd.playerStates[mvd.nextPlayerStates % mvd.maxPlayerStates];
	mvd.nextPlayerStates++;
	parseFrame->numPlayerStates++;

	/* parse playerstate */
	bits = MSG_ReadShort();
	if( mvd.serverProtocol > PROTOCOL_VERSION_DEFAULT ) {
		bits |= extraflags << PS_BITS; /* pass extra bits */
		if( mvd_shownet->integer > 2 ) {
			MSG_ShowDeltaPlayerstateBits_Enhanced( bits );
			Com_Printf( "\n" );
		}
		MSG_ParseDeltaPlayerstate_Enhanced( from, to, bits );

		to->number = DEFAULT_PLAYER_NUMBER;
		if( mvd.serverProtocol != PROTOCOL_VERSION_Q2PRO ) {
			to->clientNum = mvd.clientNum;
		}
	} else {
		MSG_ParseDeltaPlayerstate_Default(
			( player_state_t * )from,
			( player_state_t * )to, bits );
		if( mvd_shownet->integer > 2 ) {
			MSG_ShowDeltaPlayerstateBits_Default( bits );
			Com_Printf( "\n" );
		}
		to->number = DEFAULT_PLAYER_NUMBER;
		to->clientNum = mvd.clientNum;
	}

	/* parse packetentities */
	if( mvd.serverProtocol <= PROTOCOL_VERSION_DEFAULT ) {
		if( MSG_ReadByte() != svc_packetentities ) {
			Com_Error( ERR_DROP, "MVD_ParseDefaultFrame: not packetentities" );
		}
	}

	if( mvd_shownet->integer > 2 ) {
		Com_Printf( "%3i:packetentities\n", msg_read.readcount - 1 );
	}

	MVD_ParsePacketEntities( oldframe, parseFrame );

	if( mvd_shownet->integer > 1 ) {
		Com_Printf( "%3i: frame:%i  delta:%i\n",
			msg_read.readcount - 1, parseFrame->serverPacketNum,
			parseFrame->deltaPacketNum );
	}

	if( !( parseFrame->flags & MFF_VALID ) ) {
		return; /* do not change anything */
	}

	if( mvd.validPacketNum == -1 ) {
		MVD_SpawnClients();
	}
	mvd.validPacketNum = mvd.serverPacketNum;
	mvd.lastServerFrame = currentframe;

	if( mvd.state < MVD_BUFFERING ) {
		mvd.state = MVD_BUFFERING;
		Com_DPrintf( "Started buffering at frame %d\n", mvd.serverPacketNum );
		mvd.activePacketNum = mvd.serverPacketNum;
	}

	oldframe = &mvd.frames[( mvd.serverPacketNum - 1 ) % mvd.frameBackup];
	MVD_FixEntityStates( oldframe, parseFrame );
}

static void MVD_WriteDefaultMulticast( byte *data, int length, multicast_t type ) {
	int size;
	client_t *client;
	mvdClient_t *mvdcl;

	size = ( type << 12 ) | ( length & 0xFFF );
	MSG_WriteByte( svc_multicast );
	MSG_WriteShort( size );

	if( type >= MULTICAST_ALL_R ) {
		MVD_WriteFrameReliableDatagram( msg_write.data, msg_write.cursize );
		MVD_WriteFrameReliableDatagram( data, length );
	} else {
		MVD_WriteFrameDatagram( msg_write.data, msg_write.cursize );
		MVD_WriteFrameDatagram( data, length );
	}

	SZ_Clear( &msg_write );

	/* add this stuff immediately for camera people */
    FOR_EACH_CLIENT( client ) {
		if( client->state < cs_spawned ) {
			continue;
		}
		mvdcl = ( mvdClient_t * )client->edict->client;
		if( !mvdcl->admin ) {
			continue;
		}
		client->AddMessage( client, data, length,
			type >= MULTICAST_ALL_R ? qtrue : qfalse );
	}
}

static void MVD_WriteDefaultUnicast( byte *data, int length ) {
    int clientNum;
	client_t *client;
	mvdClient_t *mvdcl;
    
	clientNum = ( qtrue << 7 ) | DEFAULT_PLAYER_NUMBER;
    
	MSG_WriteByte( svc_unicast );
    MSG_WriteByte( clientNum );
	MSG_WriteShort( length );
    
	MVD_WriteFrameReliableDatagram( msg_write.data, msg_write.cursize );
	MVD_WriteFrameReliableDatagram( data, length );
    
	SZ_Clear( &msg_write );

	/* add this stuff immediately for camera people */
    FOR_EACH_CLIENT( client ) {
		if( client->state < cs_connected ) {
			continue;
		}
		mvdcl = ( mvdClient_t * )client->edict->client;
		if( !mvdcl->admin ) {
			continue;
		}
		client->AddMessage( client, data, length, qtrue );
	}
}

static void MVD_ParseDefaultPrint( void ) {
	int length;
	byte *data;
	char *string;

	data = msg_read.data + msg_read.readcount - 1;
	length = msg_read.readcount;
	MSG_ReadByte();
	string = MSG_ReadString();
	length = msg_read.readcount - length + 1;

	if( mvd.state < MVD_PRIMED ) {
		Com_Printf( "[MVD] %s", string );
		return;
	}

    /* FIXME: broadcast all prints */
	MVD_WriteDefaultMulticast( data, length, MULTICAST_ALL_R );
}

static void MVD_ParseString( void ) {
	int length;
	byte *data;

	data = msg_read.data + msg_read.readcount - 1;
	length = msg_read.readcount;
	MSG_ReadString();
	length = msg_read.readcount - length + 1;

	if( *data == svc_layout ) {
		mvd.lastLayoutPacket = mvd.serverPacketNum;
	}

	MVD_WriteDefaultUnicast( data, length );
}

static void MVD_ParseTempEntity( void ) {
	byte *data;
	int length;

	data = msg_read.data + msg_read.readcount - 1;
	if( mvd_shownet->integer > 2 ) {
		Com_Printf( "    type %d\n", data[1] );
	}
	switch( data[1] ) {
	case TE_BLOOD:
	case TE_GUNSHOT:
	case TE_SPARKS:
	case TE_BULLET_SPARKS:
	case TE_SCREEN_SPARKS:
	case TE_SHIELD_SPARKS:
	case TE_SHOTGUN:
	case TE_BLASTER:
	case TE_GREENBLOOD:
	case TE_BLASTER2:
	case TE_FLECHETTE:
	case TE_HEATBEAM_SPARKS:
	case TE_HEATBEAM_STEAM:
	case TE_ELECTRIC_SPARKS:
	case TE_MOREBLOOD:
		length = 7;
		break;

	case TE_SPLASH:
	case TE_LASER_SPARKS:
	case TE_WELDING_SPARKS:
	case TE_TUNNEL_SPARKS:
		length = 9;
		break;

	case TE_BLUEHYPERBLASTER:
    case TE_RAILTRAIL:
	case TE_BFG_LASER:
	case TE_BUBBLETRAIL:
	case TE_DEBUGTRAIL:
	case TE_BUBBLETRAIL2:
        length = 12;
        break;

	case TE_EXPLOSION2:
	case TE_GRENADE_EXPLOSION:
	case TE_GRENADE_EXPLOSION_WATER:
	case TE_PLASMA_EXPLOSION:
	case TE_EXPLOSION1:
	case TE_EXPLOSION1_BIG:
	case TE_ROCKET_EXPLOSION:
	case TE_ROCKET_EXPLOSION_WATER:
	case TE_EXPLOSION1_NP:
	case TE_BFG_EXPLOSION:
	case TE_BFG_BIGEXPLOSION:
	case TE_BOSSTPORT:
	case TE_PLAIN_EXPLOSION:
	case TE_CHAINFIST_SMOKE:
	case TE_TRACKER_EXPLOSION:
	case TE_TELEPORT_EFFECT:
	case TE_DBALL_GOAL:
	case TE_WIDOWSPLASH:
	case TE_NUKEBLAST:
		length = 6;
		break;

	case TE_PARASITE_ATTACK:
	case TE_MEDIC_CABLE_ATTACK:
	case TE_HEATBEAM:
	case TE_MONSTER_HEATBEAM:
		length = 14;
		break;

	case TE_GRAPPLE_CABLE:
		length = 20;
		break;

	case TE_LIGHTNING:
		length = 16;
		break;

	case TE_FLASHLIGHT:
	case TE_WIDOWBEAMOUT:
		length = 8;
		break;

	case TE_FORCEWALL:
		length = 13;
		break;

	case TE_STEAM:
		length = 14;
		if( data[2] != 0xff && data[3] != 0xff ) {
			length += 4;
		}
		break;

	default:
		Com_Error( ERR_DROP, "MVD_ParseTempEntity: bad entity type" );
		length = 0;
		break;
	}

	length++;
	msg_read.readcount += length;
	if( msg_read.readcount > msg_read.cursize ) {
		Com_Error( ERR_DROP, "MVD_ParseTempEntity: read past end of message" );
	}

	MVD_WriteDefaultMulticast( data, length + 1, MULTICAST_ALL );
}

static void MVD_ParseMuzzleFlash( void ) {
	byte *data;

	data = msg_read.data + msg_read.readcount - 1;
	msg_read.readcount += 3;
	if( msg_read.readcount > msg_read.cursize ) {
		Com_Error( ERR_DROP, "MVD_ParseMuzzleFlash: read past end of message" );
	}
	
	MVD_WriteDefaultMulticast( data, 4, MULTICAST_ALL );

}

static void MVD_ParseStartSound( void ) {
	int flags, length;
	byte *data;

	data = msg_read.data + msg_read.readcount - 1;
	flags = data[1];

	length = 2;
	if( flags & SND_VOLUME ) {
		length++;
	}
	if( flags & SND_ATTENUATION ) {
		length++;
	}
	if( flags & SND_OFFSET ) {
		length++;
	}
	if( flags & SND_ENT ) {
		length += 2; 
	}
	if( flags & SND_POS ) {
		length += 6;
	}

	msg_read.readcount += length;
	if( msg_read.readcount > msg_read.cursize ) {
		Com_Error( ERR_DROP, "MVD_ParseStartSound: read past end of message" );
	}
	
	MVD_WriteDefaultMulticast( data, length + 1, MULTICAST_ALL );
}

static void MVD_ParseInventory( void ) {
	byte *data;

	data = msg_read.data + msg_read.readcount - 1;

	msg_read.readcount += MAX_ITEMS*2;
	if( msg_read.readcount > msg_read.cursize ) {
		Com_Error( ERR_DROP, "MVD_ParseInventory: read past end of message" );
	}

	MVD_WriteDefaultUnicast( data, MAX_ITEMS*2 + 1 );
}


static void MVD_ParseDefaultMessage_r( void ) {
	int			cmd;
	int			extrabits;

	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 ) {
			Com_Error( ERR_DROP, "MVD_ParseDefaultMessage: 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 svc_serverdata:
			MVD_ParseServerData();
			break;
		case svc_configstring:
			MVD_ParseConfigstring();
			break;
		case svc_spawnbaseline:
			MVD_ParseBaseline();
			break;
		case svc_frame:
			MVD_ParseDefaultFrame( extrabits );
			break;
		case svc_stufftext:
			MVD_ParseStuffText();
			break;
		case svc_print:
			MVD_ParseDefaultPrint();
			break;
		case svc_centerprint:
		case svc_layout:
			MVD_ParseString();
			break;
		case svc_sound:
			MVD_ParseStartSound();
			break;
		case svc_temp_entity:
			MVD_ParseTempEntity();
			break;
		case svc_muzzleflash:
		case svc_muzzleflash2:
			MVD_ParseMuzzleFlash();
			break;
		case svc_download:
			Com_Error( ERR_DROP, "MVD_ParseDefaultMessage: server sending download" );
			break;
		case svc_inventory:
			MVD_ParseInventory();
			break;
		case svc_zpacket:
			MSG_ParseZPacket( MVD_ParseDefaultMessage_r );
			break;
		case svc_reconnect:
			MVD_ParseReconnect();
			return;
		case svc_disconnect:
			Com_Error( ERR_DISCONNECT, "[MVD] Server disconnected" );
			break;
		default:
			Com_Error( ERR_DROP, "MVD_ParseDefaultMessage: illegible command: %d", cmd );
			break;
		}
	}

}

/*
=====================================================================

  ...

=====================================================================
*/

void MVD_ParseMessage( void ) {
	/* each packet has a 'frame' associated with it,
	 * even if there is no svc_frame within. */
	if( mvd.state < MVD_PRIMED ) {
		parseFrame = NULL;
		parseGS = NULL;
	} else {
		parseFrame = &mvd.frames[mvd.serverPacketNum % mvd.frameBackup];
		memset( parseFrame, 0, sizeof( *parseFrame ) );
		parseFrame->firstMessageByte = mvd.nextMessageBytes;
		parseFrame->numMessageBytes = 0;
		parseFrame->firstReliableMessageByte = mvd.nextReliableMessageBytes;
		parseFrame->numReliableMessageBytes = 0;
		parseFrame->gamestateSequence = mvd.gamestateSequence;
		parseFrame->serverPacketNum = mvd.serverPacketNum;
		parseGS = &mvd.gamestates[mvd.gamestateSequence & GAMESTATE_MASK];
	}
	
	if( mvd.serverProtocol == PROTOCOL_VERSION_MVD ) {
		MVD_ParseMessage_r();
	} else {
		MVD_ParseDefaultMessage_r();
	}

	if( !mvd.demoplayback &&
		mvd.state > MVD_PRIMED &&
		mvd_autoscores->string[0] &&
		mvd.serverPacketNum - mvd.lastLayoutPacket > LAYOUT_TIMEOUT )
	{
		MVD_ClientCommand( mvd_autoscores->string );
		mvd.lastLayoutPacket = mvd.serverPacketNum;
		
	}
}

qboolean MVD_ReadNextMessage( fileHandle_t f ) {
	int		msglen;

	// read msglen
	if( FS_Read( &msglen, 4, f ) != 4 ) {
		return qfalse;
	}

	if( msglen == -1 ) {
		return qfalse;
	}

	msglen = LittleLong( msglen );
	if( ( unsigned )msglen >= msg_read.maxsize ) {
		Com_EPrintf( "MVD_ReadNextMessage: bad msglen: %d\n", msglen );
		return qfalse;
	}

	msg_read.cursize = msglen;
	msg_read.readcount = 0;

	// read packet data
	if( FS_Read( msg_read.data, msglen, f ) != msglen ) {
		return qfalse;
	}

	return qtrue;
}

qboolean MVD_ParseNextMessage( void ) {
	int pos;

	if( !MVD_ReadNextMessage( mvd.demofile ) ) {
		return qfalse;
	}

	mvd.serverPacketNum++;
	MVD_ParseMessage();

	if( mvd.demofileSize ) {
		pos = FS_Tell( mvd.demofile ) - mvd.demofileFrameOffset;
		if( pos < 0 ) {
			pos = 0;
		}
		mvd.demofilePercent = pos * 100 / mvd.demofileSize;
	}

	return qtrue;
}



