diff options
Diffstat (limited to 'src/mvd_parse.c')
-rw-r--r-- | src/mvd_parse.c | 1189 |
1 files changed, 1189 insertions, 0 deletions
diff --git a/src/mvd_parse.c b/src/mvd_parse.c new file mode 100644 index 0000000..a6ae96d --- /dev/null +++ b/src/mvd_parse.c @@ -0,0 +1,1189 @@ +/* +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 "mvd_local.h" + +static qboolean match_ended_hack; + +#ifdef _DEBUG +#define SHOWNET(level,...) \ + if( mvd_shownet->integer > level ) \ + Com_LPrintf( PRINT_DEVELOPER, __VA_ARGS__ ) + +#define MVD_ShowSVC( cmd ) \ + Com_Printf( "%3"PRIz":%s\n", msg_read.readcount - 1, \ + MVD_ServerCommandString( cmd ) ) + +static const char *MVD_ServerCommandString( int cmd ) { + switch( cmd ) { + case -1: return "END OF MESSAGE"; + default: return "UNKNOWN COMMAND"; +#define M(x) \ + case mvd_##x: return "mvd_" #x; + M(bad) + M(nop) + M(serverdata) + M(configstring) + M(frame) + M(unicast) + M(unicast_r) + M(multicast_all) + M(multicast_pvs) + M(multicast_phs) + M(multicast_all_r) + M(multicast_pvs_r) + M(multicast_phs_r) + M(sound) + M(print) + } +} +#else +#define SHOWNET(...) +#endif + +static void MVD_LinkEdict( mvd_t *mvd, edict_t *ent ) { + int index; + mmodel_t *cm; + int x, zd, zu; + bsp_t *cache = mvd->cm.cache; + + if( !cache ) { + return; + } + + if( ent->s.solid == 31 ) { + index = ent->s.modelindex; + if( index < 1 || index > cache->nummodels ) { + Com_WPrintf( "%s: entity %d: bad inline model index: %d\n", + __func__, ent->s.number, index ); + return; + } + cm = &cache->models[ index - 1 ]; + VectorCopy( cm->mins, ent->mins ); + VectorCopy( cm->maxs, ent->maxs ); + ent->solid = SOLID_BSP; + } 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; + + ent->mins[0] = ent->mins[1] = -x; + ent->maxs[0] = ent->maxs[1] = x; + ent->mins[2] = -zd; + ent->maxs[2] = zu; + ent->solid = SOLID_BBOX; + } else { + VectorClear( ent->mins ); + VectorClear( ent->maxs ); + ent->solid = SOLID_NOT; + } + + SV_LinkEdict( &mvd->cm, ent ); +} + +void MVD_ParseEntityString( mvd_t *mvd, const char *data ) { + const char *p; + char key[MAX_STRING_CHARS]; + char value[MAX_STRING_CHARS]; + char classname[MAX_QPATH]; + vec3_t origin; + vec3_t angles; + + 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_strlcpy( 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_strlcpy( classname, p, sizeof( classname ) ); + continue; + } + + Q_strlcpy( 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 ); + break; + } + + if( !strcmp( classname + 12, "start" ) || + !strcmp( classname + 12, "deathmatch" ) ) + { + VectorCopy( origin, mvd->spawnOrigin ); + VectorCopy( angles, mvd->spawnAngles ); + } + + } +} + +static void MVD_ParseMulticast( mvd_t *mvd, mvd_ops_t op, int extrabits ) { + mvd_client_t *client; + client_t *cl; + byte mask[MAX_MAP_VIS]; + mleaf_t *leaf1, *leaf2; + 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: + leaf1 = NULL; + break; + case mvd_multicast_phs_r: + reliable = qtrue; + // intentional fallthrough + case mvd_multicast_phs: + leafnum = MSG_ReadShort(); + leaf1 = CM_LeafNum( &mvd->cm, leafnum ); + BSP_ClusterVis( mvd->cm.cache, mask, leaf1->cluster, DVIS_PHS ); + break; + case mvd_multicast_pvs_r: + reliable = qtrue; + // intentional fallthrough + case mvd_multicast_pvs: + leafnum = MSG_ReadShort(); + leaf1 = CM_LeafNum( &mvd->cm, leafnum ); + BSP_ClusterVis( mvd->cm.cache, mask, leaf1->cluster, DVIS_PVS ); + break; + default: + MVD_Destroyf( 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_Destroyf( mvd, "read past end of message" ); + } + + // send the data to all relevent clients + FOR_EACH_MVDCL( client, mvd ) { + 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( leaf1 ) { + // find the client's PVS + ps = &client->ps; +#if 0 + VectorMA( ps->viewoffset, 0.125f, ps->pmove.origin, org ); +#else + // FIXME: for some strange reason, game code assumes the server + // uses entity origin for PVS/PHS culling, not the view origin + VectorScale( ps->pmove.origin, 0.125f, org ); +#endif + leaf2 = CM_PointLeaf( &mvd->cm, org ); + if( !CM_AreasConnected( &mvd->cm, leaf1->area, leaf2->area ) ) + continue; + if( !Q_IsBitSet( mask, leaf2->cluster ) ) { + continue; + } + } + + cl->AddMessage( cl, data, length, reliable ); + } +} + +static void MVD_UnicastSend( mvd_t *mvd, qboolean reliable, byte *data, size_t length, mvd_player_t *player ) { + mvd_player_t *target; + mvd_client_t *client; + client_t *cl; + + // send to all relevant clients + FOR_EACH_MVDCL( client, mvd ) { + cl = client->cl; + if( cl->state < cs_spawned ) { + continue; + } + target = client->target ? client->target : mvd->dummy; + if( target == player ) { + cl->AddMessage( cl, data, length, reliable ); + } + } +} + +static void MVD_UnicastLayout( mvd_t *mvd, qboolean reliable, mvd_player_t *player ) { + mvd_client_t *client; + + if( player != mvd->dummy ) { + MSG_ReadString( NULL, 0 ); + return; // we don't care about others + } + + MSG_ReadString( mvd->layout, sizeof( mvd->layout ) ); + + // HACK: if we got "match ended" string this frame, save oldscores + if( match_ended_hack ) { + strcpy( mvd->oldscores, mvd->layout ); + } + + // force an update to all relevant clients + FOR_EACH_MVDCL( client, mvd ) { + if( client->cl->state < cs_spawned ) { + continue; + } + if( client->layout_type == LAYOUT_SCORES ) { + client->layout_time = 0; + } + } +} + +static void MVD_UnicastString( mvd_t *mvd, qboolean reliable, mvd_player_t *player ) { + int index; + char string[MAX_QPATH]; + mvd_cs_t *cs; + byte *data; + size_t readcount, length; + + data = msg_read.data + msg_read.readcount - 1; + readcount = msg_read.readcount - 1; + + index = MSG_ReadShort(); + length = MSG_ReadString( string, sizeof( string ) ); + + if( index < 0 || index >= MAX_CONFIGSTRINGS ) { + MVD_Destroyf( mvd, "%s: bad index: %d", __func__, index ); + } + if( index < CS_GENERAL ) { + Com_DPrintf( "%s: common configstring: %d\n", __func__, index ); + return; + } + if( length >= sizeof( string ) ) { + Com_DPrintf( "%s: oversize configstring: %d\n", __func__, index ); + return; + } + + for( cs = player->configstrings; cs; cs = cs->next ) { + if( cs->index == index ) { + break; + } + } + if( !cs ) { + cs = MVD_Malloc( sizeof( *cs ) + MAX_QPATH - 1 ); + cs->index = index; + cs->next = player->configstrings; + player->configstrings = cs; + } + + memcpy( cs->string, string, length + 1 ); + + length = msg_read.readcount - readcount; + MVD_UnicastSend( mvd, reliable, data, length, player ); +} + +static void MVD_UnicastPrint( mvd_t *mvd, qboolean reliable, mvd_player_t *player ) { + int level; + byte *data; + size_t readcount, length; + mvd_client_t *client; + client_t *cl; + mvd_player_t *target; + + data = msg_read.data + msg_read.readcount - 1; + readcount = msg_read.readcount - 1; + + level = MSG_ReadByte(); + MSG_ReadString( NULL, 0 ); + + length = msg_read.readcount - readcount; + + // send to all relevant clients + FOR_EACH_MVDCL( client, mvd ) { + cl = client->cl; + if( cl->state < cs_spawned ) { + continue; + } + if( level < cl->messagelevel ) { + continue; + } + if( level == PRINT_CHAT && ( client->uf & UF_MUTE_PLAYERS ) ) { + continue; + } + // decide if message should be routed or not + target = ( mvd->flags & MVF_NOMSGS ) ? mvd->dummy : + client->target ? client->target : mvd->dummy; + if( target == player ) { + cl->AddMessage( cl, data, length, reliable ); + } + } +} + +static void MVD_UnicastStuff( mvd_t *mvd, qboolean reliable, mvd_player_t *player ) { + char string[8]; + byte *data; + size_t readcount, length; + + data = msg_read.data + msg_read.readcount - 1; + readcount = msg_read.readcount - 1; + + MSG_ReadString( string, sizeof( string ) ); + if( strncmp( string, "play ", 5 ) ) { + return; + } + + length = msg_read.readcount - readcount; + MVD_UnicastSend( mvd, reliable, data, length, player ); +} + +/* +MVD_ParseUnicast + +Attempt to parse the datagram and find custom configstrings, +layouts, etc. Give up as soon as unknown command byte is encountered. +*/ +static void MVD_ParseUnicast( mvd_t *mvd, mvd_ops_t op, int extrabits ) { + int clientNum; + size_t length, last; + mvd_player_t *player; + byte *data; + qboolean reliable; + int cmd; + + length = MSG_ReadByte(); + length |= extrabits << 8; + clientNum = MSG_ReadByte(); + + if( clientNum < 0 || clientNum >= mvd->maxclients ) { + MVD_Destroyf( mvd, "%s: bad number: %d", __func__, clientNum ); + } + + last = msg_read.readcount + length; + if( last > msg_read.cursize ) { + MVD_Destroyf( mvd, "%s: read past end of message", __func__ ); + } + + player = &mvd->players[clientNum]; + + reliable = op == mvd_unicast_r ? qtrue : qfalse; + + while( msg_read.readcount < last ) { + cmd = MSG_ReadByte(); +#ifdef _DEBUG + if( mvd_shownet->integer > 1 ) { + MSG_ShowSVC( cmd ); + } +#endif + switch( cmd ) { + case svc_layout: + MVD_UnicastLayout( mvd, reliable, player ); + break; + case svc_configstring: + MVD_UnicastString( mvd, reliable, player ); + break; + case svc_print: + MVD_UnicastPrint( mvd, reliable, player ); + break; + case svc_stufftext: + MVD_UnicastStuff( mvd, reliable, player ); + break; + default: + SHOWNET( 1, "%"PRIz":SKIPPING UNICAST\n", msg_read.readcount - 1 ); + // send remaining data and return + data = msg_read.data + msg_read.readcount - 1; + length = last - msg_read.readcount + 1; + MVD_UnicastSend( mvd, reliable, data, length, player ); + msg_read.readcount = last; + return; + } + } + + SHOWNET( 1, "%"PRIz":END OF UNICAST\n", msg_read.readcount - 1 ); + + if( msg_read.readcount > last ) { + MVD_Destroyf( mvd, "%s: read past end of unicast", __func__ ); + } +} + +/* +MVD_ParseSound + +Entity positioned sounds need special handling since origins need to be +explicitly specified for entities out of client PVS, and not all clients +are able to postition sounds on BSP models properly. + +FIXME: this duplicates code in sv_game.c +*/ +static void MVD_ParseSound( mvd_t *mvd, int extrabits ) { + int flags, index; + int volume, attenuation, offset, sendchan; + int entnum; + vec3_t origin; + mvd_client_t *client; + client_t *cl; + byte mask[MAX_MAP_VIS]; + mleaf_t *leaf; + int area; + player_state_t *ps; + message_packet_t *msg; + edict_t *entity; + int i; + + flags = MSG_ReadByte(); + index = MSG_ReadByte(); + + volume = attenuation = offset = 0; + 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_Destroyf( mvd, "%s: bad entnum: %d", __func__, entnum ); + } + + entity = &mvd->edicts[entnum]; + if( !entity->inuse ) { + Com_DPrintf( "%s: entnum not in use: %d\n", __func__, entnum ); + return; + } + + FOR_EACH_MVDCL( client, mvd ) { + cl = client->cl; + + // do not send unreliables to connecting clients + if( cl->state != cs_spawned || cl->download || cl->nodata ) { + continue; + } + + // PHS cull this sound + if( !( extrabits & 1 ) ) { + // get client viewpos + ps = &client->ps; + VectorMA( ps->viewoffset, 0.125f, ps->pmove.origin, origin ); + leaf = CM_PointLeaf( &mvd->cm, origin ); + 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 + } + } + BSP_ClusterVis( mvd->cm.cache, mask, leaf->cluster, DVIS_PHS ); + if( !SV_EdictPV( &mvd->cm, entity, mask ) ) { + continue; // not in PHS + } + } + + // use the entity origin unless it is a bmodel + if( entity->solid == SOLID_BSP ) { + VectorAvg( entity->mins, entity->maxs, origin ); + VectorAdd( entity->s.origin, origin, origin ); + } else { + VectorCopy( entity->s.origin, origin ); + } + + // reliable sounds will always have position explicitly set, + // as no one gurantees reliables to be delivered in time + if( extrabits & 2 ) { + MSG_WriteByte( svc_sound ); + MSG_WriteByte( flags | SND_POS ); + 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 ); + MSG_WritePos( origin ); + + SV_ClientAddMessage( cl, MSG_RELIABLE|MSG_CLEAR ); + continue; + } + + if( LIST_EMPTY( &cl->msg_free_list ) ) { + Com_WPrintf( "%s: %s: out of message slots\n", + __func__, cl->name ); + continue; + } + + // default client doesn't know that bmodels have weird origins + if( entity->solid == SOLID_BSP && cl->protocol == PROTOCOL_VERSION_DEFAULT ) { + flags |= SND_POS; + } + + msg = LIST_FIRST( message_packet_t, &cl->msg_free_list, entry ); + + msg->cursize = 0; + msg->flags = flags; + msg->index = index; + msg->volume = volume; + msg->attenuation = attenuation; + msg->timeofs = offset; + msg->sendchan = sendchan; + for( i = 0; i < 3; i++ ) { + msg->pos[i] = origin[i] * 8; + } + + List_Remove( &msg->entry ); + List_Append( &cl->msg_unreliable_list, &msg->entry ); + cl->msg_unreliable_bytes += MAX_SOUND_PACKET; + + flags &= ~SND_POS; + } +} + +void MVD_FreePlayer( mvd_player_t *player ) { + mvd_cs_t *cs, *next; + + for( cs = player->configstrings; cs; cs = next ) { + next = cs->next; + Z_Free( cs ); + } + player->configstrings = NULL; +} + +static void reset_unicast_strings( mvd_t *mvd, int index ) { + mvd_cs_t *cs, **next_p; + mvd_player_t *player; + int i; + + for( i = 0; i < mvd->maxclients; i++ ) { + player = &mvd->players[i]; + next_p = &player->configstrings; + for( cs = player->configstrings; cs; cs = cs->next ) { + if( cs->index == index ) { + Com_DPrintf( "%s: reset %d on %d\n", __func__, index, i ); + *next_p = cs->next; + Z_Free( cs ); + break; + } + next_p = &cs->next; + } + } +} + +static void set_player_name( mvd_t *mvd, int index ) { + mvd_player_t *player; + char *string, *p; + + string = mvd->configstrings[ CS_PLAYERSKINS + index ]; + player = &mvd->players[index]; + Q_strlcpy( player->name, string, sizeof( player->name ) ); + p = strchr( player->name, '\\' ); + if( p ) { + *p = 0; + } +} + +static void update_player_name( mvd_t *mvd, int index ) { + mvd_client_t *client; + + // parse player name + set_player_name( mvd, index ); + + // update layouts + FOR_EACH_MVDCL( client, mvd ) { + if( client->cl->state < cs_spawned ) { + continue; + } + if( client->layout_type != LAYOUT_FOLLOW ) { + continue; + } + if( client->target == mvd->players + index ) { + client->layout_time = 0; + } + } +} + +static void MVD_ParseConfigstring( mvd_t *mvd ) { + int index; + size_t len, maxlen; + char *string; + mvd_client_t *client; + + index = MSG_ReadShort(); + if( index < 0 || index >= MAX_CONFIGSTRINGS ) { + MVD_Destroyf( mvd, "%s: bad index: %d", __func__, index ); + } + + string = mvd->configstrings[index]; + maxlen = CS_SIZE( index ); + len = MSG_ReadString( string, maxlen ); + if( len >= maxlen ) { + MVD_Destroyf( mvd, "%s: index %d overflowed", __func__, index ); + } + + if( index >= CS_PLAYERSKINS && index < CS_PLAYERSKINS + mvd->maxclients ) { + // update player name + update_player_name( mvd, index - CS_PLAYERSKINS ); + } else if( index >= CS_GENERAL ) { + // reset unicast versions of this string + reset_unicast_strings( mvd, index ); + } + + MSG_WriteByte( svc_configstring ); + MSG_WriteShort( index ); + MSG_WriteData( string, len + 1 ); + + // broadcast configstring change + FOR_EACH_MVDCL( client, mvd ) { + if( client->cl->state < cs_primed ) { + continue; + } + SV_ClientAddMessage( client->cl, MSG_RELIABLE ); + } + + SZ_Clear( &msg_write ); +} + +static void MVD_ParsePrint( mvd_t *mvd ) { + int level; + char string[MAX_STRING_CHARS]; + + level = MSG_ReadByte(); + MSG_ReadString( string, sizeof( string ) ); + + if( level == PRINT_HIGH && strstr( string, "Match ended." ) ) { + match_ended_hack = qtrue; + } + + MVD_BroadcastPrintf( mvd, level, level == PRINT_CHAT ? + UF_MUTE_PLAYERS : 0, "%s", string ); +} + +/* +Fix origin and angles on each player entity by +extracting data from player state. +*/ +static void MVD_PlayerToEntityStates( mvd_t *mvd ) { + mvd_player_t *player; + edict_t *edict; + int i; + + mvd->numplayers = 0; + for( i = 1, player = mvd->players; i <= mvd->maxclients; i++, player++ ) { + if( !player->inuse || player == mvd->dummy ) { + continue; + } + + mvd->numplayers++; + if( player->ps.pmove.pm_type != PM_NORMAL ) { + continue; // can be out of sync, in this case + // server should provide valid data + } + + edict = &mvd->edicts[i]; + if( !edict->inuse ) { + continue; // not present in this frame + } + + Com_PlayerToEntityState( &player->ps, &edict->s ); + + MVD_LinkEdict( mvd, edict ); + } +} + +#define RELINK_MASK (U_MODEL|U_ORIGIN1|U_ORIGIN2|U_ORIGIN3|U_SOLID) + +/* +================== +MVD_ParsePacketEntities +================== +*/ +static void MVD_ParsePacketEntities( mvd_t *mvd ) { + int number; + int bits; + edict_t *ent; + + while( 1 ) { + if( msg_read.readcount > msg_read.cursize ) { + MVD_Destroyf( mvd, "%s: read past end of message", __func__ ); + } + + number = MSG_ParseEntityBits( &bits ); + if( number < 0 || number >= MAX_EDICTS ) { + MVD_Destroyf( mvd, "%s: bad number: %d", __func__, number ); + } + + if( !number ) { + break; + } + + ent = &mvd->edicts[number]; + +#ifdef _DEBUG + if( mvd_shownet->integer > 2 ) { + Com_Printf( " %s: %d ", ent->inuse ? + "delta" : "baseline", number ); + MSG_ShowDeltaEntityBits( bits ); + Com_Printf( "\n" ); + } +#endif + + MSG_ParseDeltaEntity( &ent->s, &ent->s, number, bits, 0 ); + + // lazily relink even if removed + if( bits & RELINK_MASK ) { + MVD_LinkEdict( mvd, ent ); + } + + if( bits & U_REMOVE ) { + SHOWNET( 2, " remove: %d\n", number ); + ent->inuse = qfalse; + continue; + } + + ent->inuse = qtrue; + if( number >= mvd->pool.num_edicts ) { + mvd->pool.num_edicts = number + 1; + } + } +} + +/* +================== +MVD_ParsePacketPlayers +================== +*/ +static void MVD_ParsePacketPlayers( mvd_t *mvd ) { + int number; + int bits; + mvd_player_t *player; + + while( 1 ) { + if( msg_read.readcount > msg_read.cursize ) { + MVD_Destroyf( mvd, "%s: read past end of message", __func__ ); + } + + number = MSG_ReadByte(); + if( number == CLIENTNUM_NONE ) { + break; + } + + if( number < 0 || number >= mvd->maxclients ) { + MVD_Destroyf( mvd, "%s: bad number: %d", __func__, number ); + } + + player = &mvd->players[number]; + + bits = MSG_ReadShort(); + +#ifdef _DEBUG + if( mvd_shownet->integer > 2 ) { + Com_Printf( " %s: %d ", player->inuse ? + "delta" : "baseline", number ); + MSG_ShowDeltaPlayerstateBits_Packet( bits ); + Com_Printf( "\n" ); + } +#endif + + MSG_ParseDeltaPlayerstate_Packet( &player->ps, &player->ps, bits ); + + if( bits & PPS_REMOVE ) { + SHOWNET( 2, " remove: %d\n", number ); + player->inuse = qfalse; + continue; + } + + player->inuse = qtrue; + } +} + +/* +================ +MVD_ParseFrame +================ +*/ +static void MVD_ParseFrame( mvd_t *mvd ) { + int length; + + // read portalbits + length = MSG_ReadByte(); + if( length ) { + if( length < 0 || msg_read.readcount + length > msg_read.cursize ) { + MVD_Destroyf( mvd, "%s: read past end of message", __func__ ); + } + if( length > MAX_MAP_PORTAL_BYTES ) { + MVD_Destroyf( mvd, "%s: bad portalbits length: %d", __func__, length ); + } + CM_SetPortalStates( &mvd->cm, msg_read.data + + msg_read.readcount, length ); + msg_read.readcount += length; + } else { + CM_SetPortalStates( &mvd->cm, NULL, 0 ); + } + + SHOWNET( 1, "%3"PRIz":playerinfo\n", msg_read.readcount - 1 ); + MVD_ParsePacketPlayers( mvd ); + SHOWNET( 1, "%3"PRIz":packetentities\n", msg_read.readcount - 1 ); + MVD_ParsePacketEntities( mvd ); + SHOWNET( 1, "%3"PRIz":frame:%u\n", msg_read.readcount - 1, mvd->framenum ); + MVD_PlayerToEntityStates( mvd ); + + mvd->framenum++; +} + +static void MVD_ClearState( mvd_t *mvd ) { + mvd_player_t *player; + int i; + + // clear all entities, don't trust num_edicts as it is possible + // to miscount removed entities + memset( mvd->edicts, 0, sizeof( mvd->edicts ) ); + mvd->pool.num_edicts = 0; + + // clear all players + for( i = 0; i < mvd->maxclients; i++ ) { + player = &mvd->players[i]; + MVD_FreePlayer( player ); + memset( player, 0, sizeof( *player ) ); + } + mvd->numplayers = 0; + + // free current map + CM_FreeMap( &mvd->cm ); + + if( mvd->intermission ) { + // save oldscores + //strcpy( mvd->oldscores, mvd->layout ); + } + + memset( mvd->configstrings, 0, sizeof( mvd->configstrings ) ); + mvd->layout[0] = 0; + + mvd->framenum = 0; + // intermission flag will be cleared in MVD_ChangeLevel +} + +static void MVD_ChangeLevel( mvd_t *mvd ) { + mvd_client_t *client; + + if( sv.state != ss_broadcast ) { + MVD_Spawn_f(); // the game is just starting + return; + } + + // cause all UDP clients to reconnect + MSG_WriteByte( svc_stufftext ); + MSG_WriteString( va( "changing map=%s; reconnect\n", mvd->mapname ) ); + + FOR_EACH_MVDCL( client, mvd ) { + if( mvd->intermission && client->cl->state == cs_spawned ) { + // make them switch to previous target instead of MVD dummy + client->target = client->oldtarget; + client->oldtarget = NULL; + } + SV_ClientReset( client->cl ); + client->cl->spawncount = mvd->servercount; + SV_ClientAddMessage( client->cl, MSG_RELIABLE ); + } + + SZ_Clear( &msg_write ); + + mvd->intermission = qfalse; + + mvd_dirty = qtrue; + + SV_SendAsyncPackets(); +} + +static void MVD_ParseServerData( mvd_t *mvd, int extrabits ) { + int protocol; + size_t len, maxlen; + char *string; + int i, index; + qerror_t ret; + + // clear the leftover from previous level + MVD_ClearState( mvd ); + + // parse major protocol version + protocol = MSG_ReadLong(); + if( protocol != PROTOCOL_VERSION_MVD ) { + MVD_Destroyf( mvd, "Unsupported protocol: %d", protocol ); + } + + // parse minor protocol version + protocol = MSG_ReadShort(); + if( !MVD_SUPPORTED( protocol ) ) { + MVD_Destroyf( mvd, "Unsupported MVD protocol version: %d.\n" + "Current version is %d.\n", protocol, PROTOCOL_VERSION_MVD_CURRENT ); + } + + mvd->servercount = MSG_ReadLong(); + len = MSG_ReadString( mvd->gamedir, sizeof( mvd->gamedir ) ); + if( len >= sizeof( mvd->gamedir ) ) { + MVD_Destroyf( mvd, "Oversize gamedir string" ); + } + mvd->clientNum = MSG_ReadShort(); + mvd->flags = extrabits; + +#if 0 + // change gamedir unless playing a demo + Cvar_UserSet( "game", mvd->gamedir ); +#endif + + // parse configstrings + while( 1 ) { + index = MSG_ReadShort(); + if( index == MAX_CONFIGSTRINGS ) { + break; + } + + if( index < 0 || index >= MAX_CONFIGSTRINGS ) { + MVD_Destroyf( mvd, "Bad configstring index: %d", index ); + } + + string = mvd->configstrings[index]; + maxlen = CS_SIZE( index ); + len = MSG_ReadString( string, maxlen ); + if( len >= maxlen ) { + MVD_Destroyf( mvd, "Configstring %d overflowed", index ); + } + + if( msg_read.readcount > msg_read.cursize ) { + MVD_Destroyf( mvd, "Read past end of message" ); + } + } + + // parse maxclients + index = atoi( mvd->configstrings[CS_MAXCLIENTS] ); + if( index < 1 || index > MAX_CLIENTS ) { + MVD_Destroyf( mvd, "Invalid maxclients" ); + } + + // check if maxclients changed + if( index != mvd->maxclients ) { + mvd_client_t *client; + + // free any old players + Z_Free( mvd->players ); + + // allocate new players + mvd->players = MVD_Mallocz( sizeof( mvd_player_t ) * index ); + mvd->maxclients = index; + + // clear chase targets + FOR_EACH_MVDCL( client, mvd ) { + client->target = client->oldtarget = NULL; + } + } + + // validate clientNum + if( mvd->clientNum < 0 || mvd->clientNum >= mvd->maxclients ) { + MVD_Destroyf( mvd, "Invalid client num: %d", mvd->clientNum ); + } + mvd->dummy = mvd->players + mvd->clientNum; + + // parse world model + string = mvd->configstrings[ CS_MODELS + 1 ]; + len = strlen( string ); + if( len <= 9 ) { + MVD_Destroyf( mvd, "Bad world model: %s", string ); + } + memcpy( mvd->mapname, string + 5, len - 9 ); // skip "maps/" + mvd->mapname[len - 9] = 0; // cut off ".bsp" + + // load the world model (we are only interesed in visibility info) + Com_Printf( "[%s] -=- Loading %s...\n", mvd->name, string ); + ret = CM_LoadMap( &mvd->cm, string ); + if( ret ) { + Com_EPrintf( "[%s] =!= Couldn't load %s: %s\n", mvd->name, string, Q_ErrorString( ret ) ); + // continue with null visibility + } +#if USE_MAPCHECKSUM + else if( mvd->cm.cache->checksum != atoi( mvd->configstrings[CS_MAPCHECKSUM] ) ) { + Com_EPrintf( "[%s] =!= Local map version differs from server!\n", mvd->name ); + CM_FreeMap( &mvd->cm ); + } +#endif + + // set player names + for( i = 0; i < mvd->maxclients; i++ ) { + set_player_name( mvd, i ); + } + + if( mvd->cm.cache ) { + // get the spawn point for spectators + MVD_ParseEntityString( mvd, mvd->cm.cache->entitystring ); + } + + // parse baseline frame + MVD_ParseFrame( mvd ); + + // if the channel has been just created, init some things + if( !mvd->state ) { + mvd_t *cur; + + // sort this one into the list of active channels + FOR_EACH_MVD( cur ) { + if( cur->id > mvd->id ) { + break; + } + } + List_Append( &cur->entry, &mvd->entry ); + mvd->state = MVD_WAITING; + } + + // case all UDP clients to reconnect + MVD_ChangeLevel( mvd ); +} + +void MVD_ParseMessage( mvd_t *mvd ) { + int cmd, extrabits; + +#ifdef _DEBUG + if( mvd_shownet->integer == 1 ) { + Com_Printf( "%"PRIz" ", msg_read.cursize ); + } else if( mvd_shownet->integer > 1 ) { + Com_Printf( "------------------\n" ); + } +#endif + +// +// parse the message +// + match_ended_hack = qfalse; + while( 1 ) { + if( msg_read.readcount > msg_read.cursize ) { + MVD_Destroyf( mvd, "Read past end of message" ); + } + if( msg_read.readcount == msg_read.cursize ) { + SHOWNET( 1, "%3"PRIz":END OF MESSAGE\n", msg_read.readcount - 1 ); + break; + } + + cmd = MSG_ReadByte(); + extrabits = cmd >> SVCMD_BITS; + cmd &= SVCMD_MASK; + +#ifdef _DEBUG + if( mvd_shownet->integer > 1 ) { + MVD_ShowSVC( cmd ); + } +#endif + + switch( cmd ) { + case mvd_serverdata: + MVD_ParseServerData( mvd, extrabits ); + 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 ); + break; + case mvd_sound: + MVD_ParseSound( mvd, extrabits ); + break; + case mvd_print: + MVD_ParsePrint( mvd ); + break; + case mvd_nop: + break; + default: + MVD_Destroyf( mvd, "Illegible command at %"PRIz": %d", + msg_read.readcount - 1, cmd ); + } + } +} + |