q3rally/engine/code/client/cl_parse.c
zturtleman 3b4f4cdfa9 ioquake3 resync to revision 2369 from 2317.
Some revision messages:

Cache servers for each master server in q3_ui, otherwise servers from last updated master for shown for all Internet# sources.
Play correct team sounds when in spectator mode and following a player.
Check last listener number instead of clc.clientNum in S_AL_HearingThroughEntity so sound work correctly when spectate following a client. (Related to bug 5741.)
When in third person, don't play player's sounds as full volume in Base sound system. OpenAL already does this. (Related to bug 5741.)
really fix the confusion with game entity and refentity numbers
to further reduce confusion, rename constants like MAX_ENTITIES to MAX_REFENTITIES
Added Rend2, an alternate renderer. (Bug #4358)
Fix restoring fs_game when default.cfg is missing.
Fix restoring old fs_game upon leaving a server. Patch by Ensiform.
Change more operator commands to require sv_running to be usable. Patch by Ensiform.
Fix some "> MAX_*" to be ">= MAX_*".
Fix follow command to find clients whose name begins with a number.
Fix up "gc" command, make it more like "tell". Based on patch by Ensiform.
Add usage messages for gc, tell, vtell, and votell commands.
Check player names in gc, tell, vtell, and votell commands.
#5799 - Change messagemode text box to display colors like in console input box.
Improve "play" command, based on a patch from Ensiform.
Check for invalid filename in OpenAL's RegisterSound function.
Changed Base sound system to warn not error when sound filename is empty or too long.
Remove references to non-existent functions CM_MarkFragments and CM_LerpTag.
2012-12-06 07:07:19 +00:00

938 lines
24 KiB
C

/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.
This file is part of Quake III Arena source code.
Quake III Arena source code 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.
Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
===========================================================================
*/
// cl_parse.c -- parse a message received from the server
#include "client.h"
char *svc_strings[256] = {
"svc_bad",
"svc_nop",
"svc_gamestate",
"svc_configstring",
"svc_baseline",
"svc_serverCommand",
"svc_download",
"svc_snapshot",
"svc_EOF",
"svc_voip",
};
void SHOWNET( msg_t *msg, char *s) {
if ( cl_shownet->integer >= 2) {
Com_Printf ("%3i:%s\n", msg->readcount-1, s);
}
}
/*
=========================================================================
MESSAGE PARSING
=========================================================================
*/
/*
==================
CL_DeltaEntity
Parses deltas from the given base and adds the resulting entity
to the current frame
==================
*/
void CL_DeltaEntity (msg_t *msg, clSnapshot_t *frame, int newnum, entityState_t *old,
qboolean unchanged) {
entityState_t *state;
// save the parsed entity state into the big circular buffer so
// it can be used as the source for a later delta
state = &cl.parseEntities[cl.parseEntitiesNum & (MAX_PARSE_ENTITIES-1)];
if ( unchanged ) {
*state = *old;
} else {
MSG_ReadDeltaEntity( msg, old, state, newnum );
}
if ( state->number == (MAX_GENTITIES-1) ) {
return; // entity was delta removed
}
cl.parseEntitiesNum++;
frame->numEntities++;
}
/*
==================
CL_ParsePacketEntities
==================
*/
void CL_ParsePacketEntities( msg_t *msg, clSnapshot_t *oldframe, clSnapshot_t *newframe) {
int newnum;
entityState_t *oldstate;
int oldindex, oldnum;
newframe->parseEntitiesNum = cl.parseEntitiesNum;
newframe->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 {
oldstate = &cl.parseEntities[
(oldframe->parseEntitiesNum + oldindex) & (MAX_PARSE_ENTITIES-1)];
oldnum = oldstate->number;
}
}
while ( 1 ) {
// read the entity index number
newnum = MSG_ReadBits( msg, GENTITYNUM_BITS );
if ( newnum == (MAX_GENTITIES-1) ) {
break;
}
if ( msg->readcount > msg->cursize ) {
Com_Error (ERR_DROP,"CL_ParsePacketEntities: end of message");
}
while ( oldnum < newnum ) {
// one or more entities from the old packet are unchanged
if ( cl_shownet->integer == 3 ) {
Com_Printf ("%3i: unchanged: %i\n", msg->readcount, oldnum);
}
CL_DeltaEntity( msg, newframe, oldnum, oldstate, qtrue );
oldindex++;
if ( oldindex >= oldframe->numEntities ) {
oldnum = 99999;
} else {
oldstate = &cl.parseEntities[
(oldframe->parseEntitiesNum + oldindex) & (MAX_PARSE_ENTITIES-1)];
oldnum = oldstate->number;
}
}
if (oldnum == newnum) {
// delta from previous state
if ( cl_shownet->integer == 3 ) {
Com_Printf ("%3i: delta: %i\n", msg->readcount, newnum);
}
CL_DeltaEntity( msg, newframe, newnum, oldstate, qfalse );
oldindex++;
if ( oldindex >= oldframe->numEntities ) {
oldnum = 99999;
} else {
oldstate = &cl.parseEntities[
(oldframe->parseEntitiesNum + oldindex) & (MAX_PARSE_ENTITIES-1)];
oldnum = oldstate->number;
}
continue;
}
if ( oldnum > newnum ) {
// delta from baseline
if ( cl_shownet->integer == 3 ) {
Com_Printf ("%3i: baseline: %i\n", msg->readcount, newnum);
}
CL_DeltaEntity( msg, newframe, newnum, &cl.entityBaselines[newnum], qfalse );
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 ( cl_shownet->integer == 3 ) {
Com_Printf ("%3i: unchanged: %i\n", msg->readcount, oldnum);
}
CL_DeltaEntity( msg, newframe, oldnum, oldstate, qtrue );
oldindex++;
if ( oldindex >= oldframe->numEntities ) {
oldnum = 99999;
} else {
oldstate = &cl.parseEntities[
(oldframe->parseEntitiesNum + oldindex) & (MAX_PARSE_ENTITIES-1)];
oldnum = oldstate->number;
}
}
}
/*
================
CL_ParseSnapshot
If the snapshot is parsed properly, it will be copied to
cl.snap and saved in cl.snapshots[]. If the snapshot is invalid
for any reason, no changes to the state will be made at all.
================
*/
void CL_ParseSnapshot( msg_t *msg ) {
int len;
clSnapshot_t *old;
clSnapshot_t newSnap;
int deltaNum;
int oldMessageNum;
int i, packetNum;
// get the reliable sequence acknowledge number
// NOTE: now sent with all server to client messages
//clc.reliableAcknowledge = MSG_ReadLong( msg );
// read in the new snapshot to a temporary buffer
// we will only copy to cl.snap if it is valid
Com_Memset (&newSnap, 0, sizeof(newSnap));
// we will have read any new server commands in this
// message before we got to svc_snapshot
newSnap.serverCommandNum = clc.serverCommandSequence;
newSnap.serverTime = MSG_ReadLong( msg );
// if we were just unpaused, we can only *now* really let the
// change come into effect or the client hangs.
cl_paused->modified = 0;
newSnap.messageNum = clc.serverMessageSequence;
deltaNum = MSG_ReadByte( msg );
if ( !deltaNum ) {
newSnap.deltaNum = -1;
} else {
newSnap.deltaNum = newSnap.messageNum - deltaNum;
}
newSnap.snapFlags = MSG_ReadByte( msg );
// 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 ( newSnap.deltaNum <= 0 ) {
newSnap.valid = qtrue; // uncompressed frame
old = NULL;
clc.demowaiting = qfalse; // we can start recording now
} else {
old = &cl.snapshots[newSnap.deltaNum & PACKET_MASK];
if ( !old->valid ) {
// should never happen
Com_Printf ("Delta from invalid frame (not supposed to happen!).\n");
} else if ( old->messageNum != newSnap.deltaNum ) {
// The frame that the server did the delta from
// is too old, so we can't reconstruct it properly.
Com_Printf ("Delta frame too old.\n");
} else if ( cl.parseEntitiesNum - old->parseEntitiesNum > MAX_PARSE_ENTITIES-128 ) {
Com_Printf ("Delta parseEntitiesNum too old.\n");
} else {
newSnap.valid = qtrue; // valid delta parse
}
}
// read areamask
len = MSG_ReadByte( msg );
if(len > sizeof(newSnap.areamask))
{
Com_Error (ERR_DROP,"CL_ParseSnapshot: Invalid size %d for areamask", len);
return;
}
MSG_ReadData( msg, &newSnap.areamask, len);
// read playerinfo
SHOWNET( msg, "playerstate" );
if ( old ) {
MSG_ReadDeltaPlayerstate( msg, &old->ps, &newSnap.ps );
} else {
MSG_ReadDeltaPlayerstate( msg, NULL, &newSnap.ps );
}
// read packet entities
SHOWNET( msg, "packet entities" );
CL_ParsePacketEntities( msg, old, &newSnap );
// if not valid, dump the entire thing now that it has
// been properly read
if ( !newSnap.valid ) {
return;
}
// clear the valid flags of any snapshots between the last
// received and this one, so if there was a dropped packet
// it won't look like something valid to delta from next
// time we wrap around in the buffer
oldMessageNum = cl.snap.messageNum + 1;
if ( newSnap.messageNum - oldMessageNum >= PACKET_BACKUP ) {
oldMessageNum = newSnap.messageNum - ( PACKET_BACKUP - 1 );
}
for ( ; oldMessageNum < newSnap.messageNum ; oldMessageNum++ ) {
cl.snapshots[oldMessageNum & PACKET_MASK].valid = qfalse;
}
// copy to the current good spot
cl.snap = newSnap;
cl.snap.ping = 999;
// calculate ping time
for ( i = 0 ; i < PACKET_BACKUP ; i++ ) {
packetNum = ( clc.netchan.outgoingSequence - 1 - i ) & PACKET_MASK;
if ( cl.snap.ps.commandTime >= cl.outPackets[ packetNum ].p_serverTime ) {
cl.snap.ping = cls.realtime - cl.outPackets[ packetNum ].p_realtime;
break;
}
}
// save the frame off in the backup array for later delta comparisons
cl.snapshots[cl.snap.messageNum & PACKET_MASK] = cl.snap;
if (cl_shownet->integer == 3) {
Com_Printf( " snapshot:%i delta:%i ping:%i\n", cl.snap.messageNum,
cl.snap.deltaNum, cl.snap.ping );
}
cl.newSnapshots = qtrue;
}
//=====================================================================
int cl_connectedToPureServer;
int cl_connectedToCheatServer;
/*
==================
CL_SystemInfoChanged
The systeminfo configstring has been changed, so parse
new information out of it. This will happen at every
gamestate, and possibly during gameplay.
==================
*/
void CL_SystemInfoChanged( void ) {
char *systemInfo;
const char *s, *t;
char key[BIG_INFO_KEY];
char value[BIG_INFO_VALUE];
qboolean gameSet;
systemInfo = cl.gameState.stringData + cl.gameState.stringOffsets[ CS_SYSTEMINFO ];
// NOTE TTimo:
// when the serverId changes, any further messages we send to the server will use this new serverId
// https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=475
// in some cases, outdated cp commands might get sent with this news serverId
cl.serverId = atoi( Info_ValueForKey( systemInfo, "sv_serverid" ) );
// don't set any vars when playing a demo
if ( clc.demoplaying ) {
return;
}
#ifdef USE_VOIP
#ifdef LEGACY_PROTOCOL
if(clc.compat)
clc.voipEnabled = qfalse;
else
#endif
{
s = Info_ValueForKey( systemInfo, "sv_voip" );
if ( Cvar_VariableValue( "g_gametype" ) == GT_SINGLE_PLAYER || Cvar_VariableValue("ui_singlePlayerActive"))
clc.voipEnabled = qfalse;
else
clc.voipEnabled = atoi(s);
}
#endif
s = Info_ValueForKey( systemInfo, "sv_cheats" );
cl_connectedToCheatServer = atoi( s );
if ( !cl_connectedToCheatServer ) {
Cvar_SetCheatState();
}
// check pure server string
s = Info_ValueForKey( systemInfo, "sv_paks" );
t = Info_ValueForKey( systemInfo, "sv_pakNames" );
FS_PureServerSetLoadedPaks( s, t );
s = Info_ValueForKey( systemInfo, "sv_referencedPaks" );
t = Info_ValueForKey( systemInfo, "sv_referencedPakNames" );
FS_PureServerSetReferencedPaks( s, t );
gameSet = qfalse;
// scan through all the variables in the systeminfo and locally set cvars to match
s = systemInfo;
while ( s ) {
int cvar_flags;
Info_NextPair( &s, key, value );
if ( !key[0] ) {
break;
}
// ehw!
if (!Q_stricmp(key, "fs_game"))
{
if(FS_CheckDirTraversal(value))
{
Com_Printf(S_COLOR_YELLOW "WARNING: Server sent invalid fs_game value %s\n", value);
continue;
}
gameSet = qtrue;
}
if((cvar_flags = Cvar_Flags(key)) == CVAR_NONEXISTENT)
Cvar_Get(key, value, CVAR_SERVER_CREATED | CVAR_ROM);
else
{
// If this cvar may not be modified by a server discard the value.
if(!(cvar_flags & (CVAR_SYSTEMINFO | CVAR_SERVER_CREATED | CVAR_USER_CREATED)))
{
#ifndef STANDALONE
if(Q_stricmp(key, "g_synchronousClients") && Q_stricmp(key, "pmove_fixed") &&
Q_stricmp(key, "pmove_msec"))
#endif
{
Com_Printf(S_COLOR_YELLOW "WARNING: server is not allowed to set %s=%s\n", key, value);
continue;
}
}
Cvar_SetSafe(key, value);
}
}
// if game folder should not be set and it is set at the client side
if ( !gameSet && *Cvar_VariableString("fs_game") ) {
Cvar_Set( "fs_game", "" );
}
cl_connectedToPureServer = Cvar_VariableValue( "sv_pure" );
}
/*
==================
CL_ParseServerInfo
==================
*/
static void CL_ParseServerInfo(void)
{
const char *serverInfo;
serverInfo = cl.gameState.stringData
+ cl.gameState.stringOffsets[ CS_SERVERINFO ];
clc.sv_allowDownload = atoi(Info_ValueForKey(serverInfo,
"sv_allowDownload"));
Q_strncpyz(clc.sv_dlURL,
Info_ValueForKey(serverInfo, "sv_dlURL"),
sizeof(clc.sv_dlURL));
}
/*
==================
CL_ParseGamestate
==================
*/
void CL_ParseGamestate( msg_t *msg ) {
int i;
entityState_t *es;
int newnum;
entityState_t nullstate;
int cmd;
char *s;
char oldGame[MAX_QPATH];
Con_Close();
clc.connectPacketCount = 0;
// wipe local client state
CL_ClearState();
// a gamestate always marks a server command sequence
clc.serverCommandSequence = MSG_ReadLong( msg );
// parse all the configstrings and baselines
cl.gameState.dataCount = 1; // leave a 0 at the beginning for uninitialized configstrings
while ( 1 ) {
cmd = MSG_ReadByte( msg );
if ( cmd == svc_EOF ) {
break;
}
if ( cmd == svc_configstring ) {
int len;
i = MSG_ReadShort( msg );
if ( i < 0 || i >= MAX_CONFIGSTRINGS ) {
Com_Error( ERR_DROP, "configstring > MAX_CONFIGSTRINGS" );
}
s = MSG_ReadBigString( msg );
len = strlen( s );
if ( len + 1 + cl.gameState.dataCount > MAX_GAMESTATE_CHARS ) {
Com_Error( ERR_DROP, "MAX_GAMESTATE_CHARS exceeded" );
}
// append it to the gameState string buffer
cl.gameState.stringOffsets[ i ] = cl.gameState.dataCount;
Com_Memcpy( cl.gameState.stringData + cl.gameState.dataCount, s, len + 1 );
cl.gameState.dataCount += len + 1;
} else if ( cmd == svc_baseline ) {
newnum = MSG_ReadBits( msg, GENTITYNUM_BITS );
if ( newnum < 0 || newnum >= MAX_GENTITIES ) {
Com_Error( ERR_DROP, "Baseline number out of range: %i", newnum );
}
Com_Memset (&nullstate, 0, sizeof(nullstate));
es = &cl.entityBaselines[ newnum ];
MSG_ReadDeltaEntity( msg, &nullstate, es, newnum );
} else {
Com_Error( ERR_DROP, "CL_ParseGamestate: bad command byte" );
}
}
clc.clientNum = MSG_ReadLong(msg);
// read the checksum feed
clc.checksumFeed = MSG_ReadLong( msg );
// save old gamedir
Cvar_VariableStringBuffer("fs_game", oldGame, sizeof(oldGame));
// parse useful values out of CS_SERVERINFO
CL_ParseServerInfo();
// parse serverId and other cvars
CL_SystemInfoChanged();
// stop recording now so the demo won't have an unnecessary level load at the end.
if(cl_autoRecordDemo->integer && clc.demorecording)
CL_StopRecord_f();
// reinitialize the filesystem if the game directory has changed
if(!cl_oldGameSet && (Cvar_Flags("fs_game") & CVAR_MODIFIED))
{
cl_oldGameSet = qtrue;
Q_strncpyz(cl_oldGame, oldGame, sizeof(cl_oldGame));
}
FS_ConditionalRestart(clc.checksumFeed, qfalse);
// This used to call CL_StartHunkUsers, but now we enter the download state before loading the
// cgame
CL_InitDownloads();
// make sure the game starts
Cvar_Set( "cl_paused", "0" );
}
//=====================================================================
/*
=====================
CL_ParseDownload
A download message has been received from the server
=====================
*/
void CL_ParseDownload ( msg_t *msg ) {
int size;
unsigned char data[MAX_MSGLEN];
uint16_t block;
if (!*clc.downloadTempName) {
Com_Printf("Server sending download, but no download was requested\n");
CL_AddReliableCommand("stopdl", qfalse);
return;
}
// read the data
block = MSG_ReadShort ( msg );
if(!block && !clc.downloadBlock)
{
// block zero is special, contains file size
clc.downloadSize = MSG_ReadLong ( msg );
Cvar_SetValue( "cl_downloadSize", clc.downloadSize );
if (clc.downloadSize < 0)
{
Com_Error( ERR_DROP, "%s", MSG_ReadString( msg ) );
return;
}
}
size = MSG_ReadShort ( msg );
if (size < 0 || size > sizeof(data))
{
Com_Error(ERR_DROP, "CL_ParseDownload: Invalid size %d for download chunk", size);
return;
}
MSG_ReadData(msg, data, size);
if((clc.downloadBlock & 0xFFFF) != block)
{
Com_DPrintf( "CL_ParseDownload: Expected block %d, got %d\n", (clc.downloadBlock & 0xFFFF), block);
return;
}
// open the file if not opened yet
if (!clc.download)
{
clc.download = FS_SV_FOpenFileWrite( clc.downloadTempName );
if (!clc.download) {
Com_Printf( "Could not create %s\n", clc.downloadTempName );
CL_AddReliableCommand("stopdl", qfalse);
CL_NextDownload();
return;
}
}
if (size)
FS_Write( data, size, clc.download );
CL_AddReliableCommand(va("nextdl %d", clc.downloadBlock), qfalse);
clc.downloadBlock++;
clc.downloadCount += size;
// So UI gets access to it
Cvar_SetValue( "cl_downloadCount", clc.downloadCount );
if (!size) { // A zero length block means EOF
if (clc.download) {
FS_FCloseFile( clc.download );
clc.download = 0;
// rename the file
FS_SV_Rename ( clc.downloadTempName, clc.downloadName );
}
// send intentions now
// We need this because without it, we would hold the last nextdl and then start
// loading right away. If we take a while to load, the server is happily trying
// to send us that last block over and over.
// Write it twice to help make sure we acknowledge the download
CL_WritePacket();
CL_WritePacket();
// get another file if needed
CL_NextDownload ();
}
}
#ifdef USE_VOIP
static
qboolean CL_ShouldIgnoreVoipSender(int sender)
{
if (!cl_voip->integer)
return qtrue; // VoIP is disabled.
else if ((sender == clc.clientNum) && (!clc.demoplaying))
return qtrue; // ignore own voice (unless playing back a demo).
else if (clc.voipMuteAll)
return qtrue; // all channels are muted with extreme prejudice.
else if (clc.voipIgnore[sender])
return qtrue; // just ignoring this guy.
else if (clc.voipGain[sender] == 0.0f)
return qtrue; // too quiet to play.
return qfalse;
}
/*
=====================
CL_PlayVoip
Play raw data
=====================
*/
static void CL_PlayVoip(int sender, int samplecnt, const byte *data, int flags)
{
if(flags & VOIP_DIRECT)
{
S_RawSamples(sender + 1, samplecnt, clc.speexSampleRate, 2, 1,
data, clc.voipGain[sender], -1);
}
if(flags & VOIP_SPATIAL)
{
S_RawSamples(sender + MAX_CLIENTS + 1, samplecnt, clc.speexSampleRate, 2, 1,
data, 1.0f, sender);
}
}
/*
=====================
CL_ParseVoip
A VoIP message has been received from the server
=====================
*/
static
void CL_ParseVoip ( msg_t *msg ) {
static short decoded[4096]; // !!! FIXME: don't hardcode.
const int sender = MSG_ReadShort(msg);
const int generation = MSG_ReadByte(msg);
const int sequence = MSG_ReadLong(msg);
const int frames = MSG_ReadByte(msg);
const int packetsize = MSG_ReadShort(msg);
const int flags = MSG_ReadBits(msg, VOIP_FLAGCNT);
char encoded[1024];
int seqdiff = sequence - clc.voipIncomingSequence[sender];
int written = 0;
int i;
Com_DPrintf("VoIP: %d-byte packet from client %d\n", packetsize, sender);
if (sender < 0)
return; // short/invalid packet, bail.
else if (generation < 0)
return; // short/invalid packet, bail.
else if (sequence < 0)
return; // short/invalid packet, bail.
else if (frames < 0)
return; // short/invalid packet, bail.
else if (packetsize < 0)
return; // short/invalid packet, bail.
if (packetsize > sizeof (encoded)) { // overlarge packet?
int bytesleft = packetsize;
while (bytesleft) {
int br = bytesleft;
if (br > sizeof (encoded))
br = sizeof (encoded);
MSG_ReadData(msg, encoded, br);
bytesleft -= br;
}
return; // overlarge packet, bail.
}
if (!clc.speexInitialized) {
MSG_ReadData(msg, encoded, packetsize); // skip payload.
return; // can't handle VoIP without libspeex!
} else if (sender >= MAX_CLIENTS) {
MSG_ReadData(msg, encoded, packetsize); // skip payload.
return; // bogus sender.
} else if (CL_ShouldIgnoreVoipSender(sender)) {
MSG_ReadData(msg, encoded, packetsize); // skip payload.
return; // Channel is muted, bail.
}
// !!! FIXME: make sure data is narrowband? Does decoder handle this?
Com_DPrintf("VoIP: packet accepted!\n");
// This is a new "generation" ... a new recording started, reset the bits.
if (generation != clc.voipIncomingGeneration[sender]) {
Com_DPrintf("VoIP: new generation %d!\n", generation);
speex_bits_reset(&clc.speexDecoderBits[sender]);
clc.voipIncomingGeneration[sender] = generation;
seqdiff = 0;
} else if (seqdiff < 0) { // we're ahead of the sequence?!
// This shouldn't happen unless the packet is corrupted or something.
Com_DPrintf("VoIP: misordered sequence! %d < %d!\n",
sequence, clc.voipIncomingSequence[sender]);
// reset the bits just in case.
speex_bits_reset(&clc.speexDecoderBits[sender]);
seqdiff = 0;
} else if (seqdiff > 100) { // more than 2 seconds of audio dropped?
// just start over.
Com_DPrintf("VoIP: Dropped way too many (%d) frames from client #%d\n",
seqdiff, sender);
speex_bits_reset(&clc.speexDecoderBits[sender]);
seqdiff = 0;
}
if (seqdiff != 0) {
Com_DPrintf("VoIP: Dropped %d frames from client #%d\n",
seqdiff, sender);
// tell speex that we're missing frames...
for (i = 0; i < seqdiff; i++) {
assert((written + clc.speexFrameSize) * 2 < sizeof (decoded));
speex_decode_int(clc.speexDecoder[sender], NULL, decoded + written);
written += clc.speexFrameSize;
}
}
for (i = 0; i < frames; i++) {
char encoded[256];
const int len = MSG_ReadByte(msg);
if (len < 0) {
Com_DPrintf("VoIP: Short packet!\n");
break;
}
MSG_ReadData(msg, encoded, len);
// shouldn't happen, but just in case...
if ((written + clc.speexFrameSize) * 2 > sizeof (decoded)) {
Com_DPrintf("VoIP: playback %d bytes, %d samples, %d frames\n",
written * 2, written, i);
CL_PlayVoip(sender, written, (const byte *) decoded, flags);
written = 0;
}
speex_bits_read_from(&clc.speexDecoderBits[sender], encoded, len);
speex_decode_int(clc.speexDecoder[sender],
&clc.speexDecoderBits[sender], decoded + written);
#if 0
static FILE *encio = NULL;
if (encio == NULL) encio = fopen("voip-incoming-encoded.bin", "wb");
if (encio != NULL) { fwrite(encoded, len, 1, encio); fflush(encio); }
static FILE *decio = NULL;
if (decio == NULL) decio = fopen("voip-incoming-decoded.bin", "wb");
if (decio != NULL) { fwrite(decoded+written, clc.speexFrameSize*2, 1, decio); fflush(decio); }
#endif
written += clc.speexFrameSize;
}
Com_DPrintf("VoIP: playback %d bytes, %d samples, %d frames\n",
written * 2, written, i);
if(written > 0)
CL_PlayVoip(sender, written, (const byte *) decoded, flags);
clc.voipIncomingSequence[sender] = sequence + frames;
}
#endif
/*
=====================
CL_ParseCommandString
Command strings are just saved off until cgame asks for them
when it transitions a snapshot
=====================
*/
void CL_ParseCommandString( msg_t *msg ) {
char *s;
int seq;
int index;
seq = MSG_ReadLong( msg );
s = MSG_ReadString( msg );
// see if we have already executed stored it off
if ( clc.serverCommandSequence >= seq ) {
return;
}
clc.serverCommandSequence = seq;
index = seq & (MAX_RELIABLE_COMMANDS-1);
Q_strncpyz( clc.serverCommands[ index ], s, sizeof( clc.serverCommands[ index ] ) );
}
/*
=====================
CL_ParseServerMessage
=====================
*/
void CL_ParseServerMessage( msg_t *msg ) {
int cmd;
if ( cl_shownet->integer == 1 ) {
Com_Printf ("%i ",msg->cursize);
} else if ( cl_shownet->integer >= 2 ) {
Com_Printf ("------------------\n");
}
MSG_Bitstream(msg);
// get the reliable sequence acknowledge number
clc.reliableAcknowledge = MSG_ReadLong( msg );
//
if ( clc.reliableAcknowledge < clc.reliableSequence - MAX_RELIABLE_COMMANDS ) {
clc.reliableAcknowledge = clc.reliableSequence;
}
//
// parse the message
//
while ( 1 ) {
if ( msg->readcount > msg->cursize ) {
Com_Error (ERR_DROP,"CL_ParseServerMessage: read past end of server message");
break;
}
cmd = MSG_ReadByte( msg );
if (cmd == svc_EOF) {
SHOWNET( msg, "END OF MESSAGE" );
break;
}
if ( cl_shownet->integer >= 2 ) {
if ( (cmd < 0) || (!svc_strings[cmd]) ) {
Com_Printf( "%3i:BAD CMD %i\n", msg->readcount-1, cmd );
} else {
SHOWNET( msg, svc_strings[cmd] );
}
}
// other commands
switch ( cmd ) {
default:
Com_Error (ERR_DROP,"CL_ParseServerMessage: Illegible server message");
break;
case svc_nop:
break;
case svc_serverCommand:
CL_ParseCommandString( msg );
break;
case svc_gamestate:
CL_ParseGamestate( msg );
break;
case svc_snapshot:
CL_ParseSnapshot( msg );
break;
case svc_download:
CL_ParseDownload( msg );
break;
case svc_voip:
#ifdef USE_VOIP
CL_ParseVoip( msg );
#endif
break;
}
}
}