xash3d-fwgs/engine/client/cl_qparse.c

1133 lines
29 KiB
C

/*
cl_qparse.c - parse a message received from the Quake demo
Copyright (C) 2018 Uncle Mike
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 3 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.
*/
#include "common.h"
#include "client.h"
#include "net_encode.h"
#include "particledef.h"
#include "cl_tent.h"
#include "shake.h"
#include "hltv.h"
#include "input.h"
#define STAT_HEALTH 0
#define STAT_FRAGS 1
#define STAT_WEAPON 2
#define STAT_AMMO 3
#define STAT_ARMOR 4
#define STAT_WEAPONFRAME 5
#define STAT_SHELLS 6
#define STAT_NAILS 7
#define STAT_ROCKETS 8
#define STAT_CELLS 9
#define STAT_ACTIVEWEAPON 10
#define STAT_TOTALSECRETS 11
#define STAT_TOTALMONSTERS 12
#define STAT_SECRETS 13 // bumped on client side by svc_foundsecret
#define STAT_MONSTERS 14 // bumped by svc_killedmonster
#define MAX_STATS 32
static char cmd_buf[8192];
static char msg_buf[8192];
static sizebuf_t msg_demo;
/*
==================
CL_DispatchQuakeMessage
==================
*/
static void CL_DispatchQuakeMessage( const char *name )
{
CL_DispatchUserMessage( name, msg_demo.iCurBit >> 3, msg_demo.pData );
MSG_Clear( &msg_demo ); // don't forget to clear buffer
}
/*
==================
CL_ParseQuakeStats
redirect to qwrap->client
==================
*/
static void CL_ParseQuakeStats( sizebuf_t *msg )
{
MSG_WriteByte( &msg_demo, MSG_ReadByte( msg )); // stat num
MSG_WriteLong( &msg_demo, MSG_ReadLong( msg )); // stat value
CL_DispatchQuakeMessage( "Stats" );
}
/*
==================
CL_EntityTeleported
check for instant movement in case
we don't want interpolate this
==================
*/
static qboolean CL_QuakeEntityTeleported( cl_entity_t *ent, entity_state_t *newstate )
{
float len, maxlen;
vec3_t delta;
VectorSubtract( newstate->origin, ent->prevstate.origin, delta );
// compute potential max movement in units per frame and compare with entity movement
maxlen = ( clgame.movevars.maxvelocity * ( 1.0f / GAME_FPS ));
len = VectorLength( delta );
return (len > maxlen);
}
/*
==================
CL_ParseQuakeStats
redirect to qwrap->client
==================
*/
static int CL_UpdateQuakeStats( sizebuf_t *msg, int statnum, qboolean has_update )
{
int value = 0;
MSG_WriteByte( &msg_demo, statnum ); // stat num
if( has_update )
{
if( statnum == STAT_HEALTH )
value = MSG_ReadShort( msg );
else value = MSG_ReadByte( msg );
}
MSG_WriteLong( &msg_demo, value );
CL_DispatchQuakeMessage( "Stats" );
return value;
}
/*
==================
CL_UpdateQuakeGameMode
redirect to qwrap->client
==================
*/
static void CL_UpdateQuakeGameMode( int gamemode )
{
MSG_WriteByte( &msg_demo, gamemode );
CL_DispatchQuakeMessage( "GameMode" );
}
/*
==================
CL_ParseQuakeSound
==================
*/
static void CL_ParseQuakeSound( sizebuf_t *msg )
{
int channel, sound;
int flags, entnum;
float volume, attn;
sound_t handle;
vec3_t pos;
flags = MSG_ReadByte( msg );
if( FBitSet( flags, SND_VOLUME ))
volume = (float)MSG_ReadByte( msg ) / 255.0f;
else volume = VOL_NORM;
if( FBitSet( flags, SND_ATTENUATION ))
attn = (float)MSG_ReadByte( msg ) / 64.0f;
else attn = ATTN_NONE;
channel = MSG_ReadWord( msg );
sound = MSG_ReadByte( msg ); // Quake1 have max 255 precached sounds. erm
// positioned in space
MSG_ReadVec3Coord( msg, pos );
entnum = channel >> 3; // entity reletive
channel &= 7;
// see precached sound
handle = cl.sound_index[sound];
if( !cl.audio_prepped )
return; // too early
S_StartSound( pos, entnum, channel, handle, volume, attn, PITCH_NORM, flags );
}
/*
==================
CL_ParseQuakeServerInfo
==================
*/
static void CL_ParseQuakeServerInfo( sizebuf_t *msg )
{
resource_t *pResource;
const char *pResName;
int gametype;
int i;
Con_Reportf( "Serverdata packet received.\n" );
cls.timestart = Sys_DoubleTime();
cls.demowaiting = false; // server is changed
// wipe the client_t struct
if( !cls.changelevel && !cls.changedemo )
CL_ClearState ();
cl.background = (cls.demonum != -1) ? true : false;
cls.state = ca_connected;
// parse protocol version number
i = MSG_ReadLong( msg );
if( i != PROTOCOL_VERSION_QUAKE )
{
Con_Printf( "\n" S_ERROR "Server use invalid protocol (%i should be %i)\n", i, PROTOCOL_VERSION_QUAKE );
CL_StopPlayback();
Host_AbortCurrentFrame();
}
cl.maxclients = MSG_ReadByte( msg );
gametype = MSG_ReadByte( msg );
clgame.maxEntities = GI->max_edicts;
clgame.maxEntities = bound( 600, clgame.maxEntities, MAX_EDICTS );
clgame.maxModels = MAX_MODELS;
Q_strncpy( clgame.maptitle, MSG_ReadString( msg ), MAX_STRING );
// Re-init hud video, especially if we changed game directories
clgame.dllFuncs.pfnVidInit();
if( Con_FixedFont( ))
{
// seperate the printfs so the server message can have a color
Con_Print( "\n\35\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\37\n" );
Con_Print( va( "%c%s\n\n", 2, clgame.maptitle ));
}
// multiplayer game?
if( cl.maxclients > 1 )
{
// allow console in multiplayer games
host.allow_console = true;
// loading user settings
CSCR_LoadDefaultCVars( "user.scr" );
if( r_decals->value > mp_decals.value )
Cvar_SetValue( "r_decals", mp_decals.value );
}
else Cvar_Reset( "r_decals" );
// re-init mouse
if( cl.background )
host.mouse_visible = false;
if( cl.background ) // tell the game parts about background state
Cvar_FullSet( "cl_background", "1", FCVAR_READ_ONLY );
else Cvar_FullSet( "cl_background", "0", FCVAR_READ_ONLY );
S_StopBackgroundTrack ();
if( !cls.changedemo )
UI_SetActiveMenu( cl.background );
else if( !cls.demoplayback )
Key_SetKeyDest( key_menu );
// don't reset cursor in background mode
if( cl.background )
IN_MouseRestorePos();
// will be changed later
cl.viewentity = cl.playernum + 1;
gameui.globals->maxClients = cl.maxclients;
Q_strncpy( gameui.globals->maptitle, clgame.maptitle, sizeof( gameui.globals->maptitle ));
if( !cls.changelevel && !cls.changedemo )
CL_InitEdicts (); // re-arrange edicts
// Quake just have a large packet of initialization data
for( i = 1; i < MAX_MODELS; i++ )
{
pResName = MSG_ReadString( msg );
if( !COM_CheckString( pResName ))
break; // end of list
pResource = Mem_Calloc( cls.mempool, sizeof( resource_t ));
pResource->type = t_model;
Q_strncpy( pResource->szFileName, pResName, sizeof( pResource->szFileName ));
if( i == 1 ) Q_strncpy( clgame.mapname, pResName, sizeof( clgame.mapname ));
pResource->nDownloadSize = -1;
pResource->nIndex = i;
CL_AddToResourceList( pResource, &cl.resourcesneeded );
}
for( i = 1; i < MAX_SOUNDS; i++ )
{
pResName = MSG_ReadString( msg );
if( !COM_CheckString( pResName ))
break; // end of list
pResource = Mem_Calloc( cls.mempool, sizeof( resource_t ));
pResource->type = t_sound;
Q_strncpy( pResource->szFileName, pResName, sizeof( pResource->szFileName ));
pResource->nDownloadSize = -1;
pResource->nIndex = i;
CL_AddToResourceList( pResource, &cl.resourcesneeded );
}
// get splash name
if( cls.demoplayback && ( cls.demonum != -1 ))
Cvar_Set( "cl_levelshot_name", va( "levelshots/%s_%s", cls.demoname, refState.wideScreen ? "16x9" : "4x3" ));
else Cvar_Set( "cl_levelshot_name", va( "levelshots/%s_%s", clgame.mapname, refState.wideScreen ? "16x9" : "4x3" ));
Cvar_SetValue( "scr_loading", 0.0f ); // reset progress bar
if(( cl_allow_levelshots->value && !cls.changelevel ) || cl.background )
{
if( !FS_FileExists( va( "%s.bmp", cl_levelshot_name->string ), true ))
Cvar_Set( "cl_levelshot_name", "*black" ); // render a black screen
cls.scrshot_request = scrshot_plaque; // request levelshot even if exist (check filetime)
}
memset( &clgame.movevars, 0, sizeof( clgame.movevars ));
memset( &clgame.oldmovevars, 0, sizeof( clgame.oldmovevars ));
memset( &clgame.centerPrint, 0, sizeof( clgame.centerPrint ));
cl.video_prepped = false;
cl.audio_prepped = false;
// GAME_COOP or GAME_DEATHMATCH
CL_UpdateQuakeGameMode( gametype );
// now we can start to precache
CL_BatchResourceRequest( true );
clgame.movevars.wateralpha = 1.0f;
clgame.entities->curstate.scale = 0.0f;
clgame.movevars.waveHeight = 0.0f;
clgame.movevars.zmax = 14172.0f; // 8192 * 1.74
clgame.movevars.gravity = 800.0f; // quake doesn't write gravity in demos
clgame.movevars.maxvelocity = 2000.0f;
memcpy( &clgame.oldmovevars, &clgame.movevars, sizeof( movevars_t ));
}
/*
==================
CL_ParseQuakeClientData
==================
*/
static void CL_ParseQuakeClientData( sizebuf_t *msg )
{
int i, bits = MSG_ReadWord( msg );
frame_t *frame;
// this is the frame update that this message corresponds to
i = cls.netchan.incoming_sequence;
cl.parsecount = i; // ack'd incoming messages.
cl.parsecountmod = cl.parsecount & CL_UPDATE_MASK; // index into window.
frame = &cl.frames[cl.parsecountmod]; // frame at index.
frame->time = cl.mtime[0]; // mark network received time
frame->receivedtime = host.realtime; // time now that we are parsing.
memset( &frame->graphdata, 0, sizeof( netbandwidthgraph_t ));
memset( frame->flags, 0, sizeof( frame->flags ));
frame->first_entity = cls.next_client_entities;
frame->num_entities = 0;
frame->valid = true; // assume valid
if( FBitSet( bits, SU_VIEWHEIGHT ))
frame->clientdata.view_ofs[2] = MSG_ReadChar( msg );
else frame->clientdata.view_ofs[2] = 22.0f;
if( FBitSet( bits, SU_IDEALPITCH ))
cl.local.idealpitch = MSG_ReadChar( msg );
else cl.local.idealpitch = 0;
for( i = 0; i < 3; i++ )
{
if( FBitSet( bits, SU_PUNCH1 << i ))
frame->clientdata.punchangle[i] = (float)MSG_ReadChar( msg );
else frame->clientdata.punchangle[i] = 0.0f;
if( FBitSet( bits, ( SU_VELOCITY1 << i )))
frame->clientdata.velocity[i] = MSG_ReadChar( msg ) * 16.0f;
else frame->clientdata.velocity[i] = 0;
}
if( FBitSet( bits, SU_ONGROUND ))
SetBits( frame->clientdata.flags, FL_ONGROUND );
if( FBitSet( bits, SU_INWATER ))
SetBits( frame->clientdata.flags, FL_INWATER );
// [always sent]
MSG_WriteLong( &msg_demo, MSG_ReadLong( msg ));
CL_DispatchQuakeMessage( "Items" );
if( FBitSet( bits, SU_WEAPONFRAME ))
CL_UpdateQuakeStats( msg, STAT_WEAPONFRAME, true );
else CL_UpdateQuakeStats( msg, STAT_WEAPONFRAME, false );
if( FBitSet( bits, SU_ARMOR ))
CL_UpdateQuakeStats( msg, STAT_ARMOR, true );
else CL_UpdateQuakeStats( msg, STAT_ARMOR, false );
if( FBitSet( bits, SU_WEAPON ))
frame->clientdata.viewmodel = CL_UpdateQuakeStats( msg, STAT_WEAPON, true );
else frame->clientdata.viewmodel = CL_UpdateQuakeStats( msg, STAT_WEAPON, false );
cl.local.health = CL_UpdateQuakeStats( msg, STAT_HEALTH, true );
CL_UpdateQuakeStats( msg, STAT_AMMO, true );
CL_UpdateQuakeStats( msg, STAT_SHELLS, true );
CL_UpdateQuakeStats( msg, STAT_NAILS, true );
CL_UpdateQuakeStats( msg, STAT_ROCKETS, true );
CL_UpdateQuakeStats( msg, STAT_CELLS, true );
CL_UpdateQuakeStats( msg, STAT_ACTIVEWEAPON, true );
}
/*
==================
CL_ParseQuakeEntityData
Parse an entity update message from the server
If an entities model or origin changes from frame to frame, it must be
relinked. Other attributes can change without relinking.
==================
*/
void CL_ParseQuakeEntityData( sizebuf_t *msg, int bits )
{
int i, newnum, pack;
qboolean forcelink;
entity_state_t *state;
frame_t *frame;
cl_entity_t *ent;
// first update is the final signon stage where we actually receive an entity (i.e., the world at least)
if( cls.signon == ( SIGNONS - 1 ))
{
// we are done with signon sequence.
cls.signon = SIGNONS;
// Clear loading plaque.
CL_SignonReply ();
}
// alloc next slot to store update
state = &cls.packet_entities[cls.next_client_entities % cls.num_client_entities];
cl.validsequence = cls.netchan.incoming_sequence;
frame = &cl.frames[cl.parsecountmod];
pack = frame->num_entities;
if( FBitSet( bits, U_MOREBITS ))
{
i = MSG_ReadByte( msg );
SetBits( bits, i << 8 );
}
if( FBitSet( bits, U_LONGENTITY ))
newnum = MSG_ReadWord( msg );
else newnum = MSG_ReadByte( msg );
memset( state, 0, sizeof( *state ));
SetBits( state->entityType, ENTITY_NORMAL );
state->number = newnum;
// mark all the players
ent = CL_EDICT_NUM( newnum );
ent->index = newnum; // enumerate entity index
ent->player = CL_IsPlayerIndex( newnum );
state->animtime = cl.mtime[0];
if( ent->curstate.msg_time != cl.mtime[1] )
forcelink = true; // no previous frame to lerp from
else forcelink = false;
if( FBitSet( bits, U_MODEL ))
state->modelindex = MSG_ReadByte( msg );
else state->modelindex = ent->baseline.modelindex;
if( FBitSet( bits, U_FRAME ))
state->frame = MSG_ReadByte( msg );
else state->frame = ent->baseline.frame;
if( FBitSet( bits, U_COLORMAP ))
state->colormap = MSG_ReadByte( msg );
else state->colormap = ent->baseline.colormap;
if( FBitSet( bits, U_SKIN ))
state->skin = MSG_ReadByte( msg );
else state->skin = ent->baseline.skin;
if( FBitSet( bits, U_EFFECTS ))
state->effects = MSG_ReadByte( msg );
else state->effects = ent->baseline.effects;
if( FBitSet( bits, U_ORIGIN1 ))
state->origin[0] = MSG_ReadCoord( msg );
else state->origin[0] = ent->baseline.origin[0];
if( FBitSet( bits, U_ANGLE1 ))
state->angles[0] = MSG_ReadAngle( msg );
else state->angles[0] = ent->baseline.angles[0];
if( FBitSet( bits, U_ORIGIN2 ))
state->origin[1] = MSG_ReadCoord( msg );
else state->origin[1] = ent->baseline.origin[1];
if( FBitSet( bits, U_ANGLE2 ))
state->angles[1] = MSG_ReadAngle( msg );
else state->angles[1] = ent->baseline.angles[1];
if( FBitSet( bits, U_ORIGIN3 ))
state->origin[2] = MSG_ReadCoord( msg );
else state->origin[2] = ent->baseline.origin[2];
if( FBitSet( bits, U_ANGLE3 ))
state->angles[2] = MSG_ReadAngle( msg );
else state->angles[2] = ent->baseline.angles[2];
if( FBitSet( bits, U_TRANS ))
{
int temp = MSG_ReadFloat( msg );
float alpha = MSG_ReadFloat( msg );
if( alpha == 0.0f ) alpha = 1.0f;
if( alpha < 1.0f )
{
state->rendermode = kRenderTransTexture;
state->renderamt = (int)(alpha * 255.0f);
}
if( temp == 2 && MSG_ReadFloat( msg ))
SetBits( state->effects, EF_FULLBRIGHT );
}
if( FBitSet( bits, U_NOLERP ))
state->movetype = MOVETYPE_STEP;
else state->movetype = MOVETYPE_NOCLIP;
if( CL_QuakeEntityTeleported( ent, state ))
{
// remove smooth stepping
if( cl.viewentity == ent->index )
cl.skip_interp = true;
forcelink = true;
}
if( FBitSet( state->effects, 16 ))
SetBits( state->effects, EF_NODRAW );
if(( newnum - 1 ) == cl.playernum )
VectorCopy( state->origin, frame->clientdata.origin );
if( forcelink )
{
VectorCopy( state->origin, ent->baseline.vuser1 );
SetBits( state->effects, EF_NOINTERP );
// interpolation must be reset
SETVISBIT( frame->flags, pack );
// release beams from previous entity
CL_KillDeadBeams( ent );
}
// add entity to packet
cls.next_client_entities++;
frame->num_entities++;
}
/*
==================
CL_ParseQuakeParticles
==================
*/
void CL_ParseQuakeParticle( sizebuf_t *msg )
{
int count, color;
vec3_t org, dir;
MSG_ReadVec3Coord( msg, org );
dir[0] = MSG_ReadChar( msg ) * 0.0625f;
dir[1] = MSG_ReadChar( msg ) * 0.0625f;
dir[2] = MSG_ReadChar( msg ) * 0.0625f;
count = MSG_ReadByte( msg );
color = MSG_ReadByte( msg );
if( count == 255 ) count = 1024;
R_RunParticleEffect( org, dir, color, count );
}
/*
===================
CL_ParseQuakeStaticSound
===================
*/
void CL_ParseQuakeStaticSound( sizebuf_t *msg )
{
int sound_num;
float vol, attn;
vec3_t org;
MSG_ReadVec3Coord( msg, org );
sound_num = MSG_ReadByte( msg );
vol = (float)MSG_ReadByte( msg ) / 255.0f;
attn = (float)MSG_ReadByte( msg ) / 64.0f;
S_StartSound( org, 0, CHAN_STATIC, cl.sound_index[sound_num], vol, attn, PITCH_NORM, 0 );
}
/*
==================
CL_ParseQuakeDamage
redirect to qwrap->client
==================
*/
static void CL_ParseQuakeDamage( sizebuf_t *msg )
{
MSG_WriteByte( &msg_demo, MSG_ReadByte( msg )); // armor
MSG_WriteByte( &msg_demo, MSG_ReadByte( msg )); // blood
MSG_WriteCoord( &msg_demo, MSG_ReadCoord( msg )); // direction
MSG_WriteCoord( &msg_demo, MSG_ReadCoord( msg )); // direction
MSG_WriteCoord( &msg_demo, MSG_ReadCoord( msg )); // direction
CL_DispatchQuakeMessage( "Damage" );
}
/*
===================
CL_ParseQuakeStaticEntity
===================
*/
static void CL_ParseQuakeStaticEntity( sizebuf_t *msg )
{
entity_state_t state;
cl_entity_t *ent;
int i;
memset( &state, 0, sizeof( state ));
state.modelindex = MSG_ReadByte( msg );
state.frame = MSG_ReadByte( msg );
state.colormap = MSG_ReadByte( msg );
state.skin = MSG_ReadByte( msg );
state.origin[0] = MSG_ReadCoord( msg );
state.angles[0] = MSG_ReadAngle( msg );
state.origin[1] = MSG_ReadCoord( msg );
state.angles[1] = MSG_ReadAngle( msg );
state.origin[2] = MSG_ReadCoord( msg );
state.angles[2] = MSG_ReadAngle( msg );
i = clgame.numStatics;
if( i >= MAX_STATIC_ENTITIES )
{
Con_Printf( S_ERROR "CL_ParseStaticEntity: static entities limit exceeded!\n" );
return;
}
ent = &clgame.static_entities[i];
clgame.numStatics++;
ent->index = 0; // ???
ent->baseline = state;
ent->curstate = state;
ent->prevstate = state;
// statics may be respawned in game e.g. for demo recording
if( cls.state == ca_connected || cls.state == ca_validate )
ent->trivial_accept = INVALID_HANDLE;
// setup the new static entity
VectorCopy( ent->curstate.origin, ent->origin );
VectorCopy( ent->curstate.angles, ent->angles );
ent->model = CL_ModelHandle( state.modelindex );
ent->curstate.framerate = 1.0f;
CL_ResetLatchedVars( ent, true );
if( ent->model != NULL )
{
// auto 'solid' faces
if( FBitSet( ent->model->flags, MODEL_TRANSPARENT ) && Host_IsQuakeCompatible())
{
ent->curstate.rendermode = kRenderTransAlpha;
ent->curstate.renderamt = 255;
}
}
R_AddEfrags( ent ); // add link
}
/*
===================
CL_ParseQuakeBaseline
===================
*/
static void CL_ParseQuakeBaseline( sizebuf_t *msg )
{
entity_state_t state;
cl_entity_t *ent;
int newnum;
memset( &state, 0, sizeof( state ));
newnum = MSG_ReadWord( msg ); // entnum
if( newnum >= clgame.maxEntities )
Host_Error( "CL_AllocEdict: no free edicts\n" );
ent = CL_EDICT_NUM( newnum );
memset( &ent->prevstate, 0, sizeof( ent->prevstate ));
ent->index = newnum;
// parse baseline
state.modelindex = MSG_ReadByte( msg );
state.frame = MSG_ReadByte( msg );
state.colormap = MSG_ReadByte( msg );
state.skin = MSG_ReadByte( msg );
state.origin[0] = MSG_ReadCoord( msg );
state.angles[0] = MSG_ReadAngle( msg );
state.origin[1] = MSG_ReadCoord( msg );
state.angles[1] = MSG_ReadAngle( msg );
state.origin[2] = MSG_ReadCoord( msg );
state.angles[2] = MSG_ReadAngle( msg );
ent->player = CL_IsPlayerIndex( newnum );
memcpy( &ent->baseline, &state, sizeof( entity_state_t ));
memcpy( &ent->prevstate, &state, sizeof( entity_state_t ));
}
/*
===================
CL_ParseQuakeTempEntity
===================
*/
static void CL_ParseQuakeTempEntity( sizebuf_t *msg )
{
int type = MSG_ReadByte( msg );
MSG_WriteByte( &msg_demo, type );
if( type == 17 )
MSG_WriteString( &msg_demo, MSG_ReadString( msg ));
// TE_LIGHTNING1, TE_LIGHTNING2, TE_LIGHTNING3, TE_BEAM, TE_LIGHTNING4
if( type == 5 || type == 6 || type == 9 || type == 13 || type == 17 )
MSG_WriteWord( &msg_demo, MSG_ReadWord( msg ));
// all temp ents have position at beginning
MSG_WriteCoord( &msg_demo, MSG_ReadCoord( msg ));
MSG_WriteCoord( &msg_demo, MSG_ReadCoord( msg ));
MSG_WriteCoord( &msg_demo, MSG_ReadCoord( msg ));
// TE_LIGHTNING1, TE_LIGHTNING2, TE_LIGHTNING3, TE_BEAM, TE_EXPLOSION3, TE_LIGHTNING4
if( type == 5 || type == 6 || type == 9 || type == 13 || type == 16 || type == 17 )
{
// write endpos for beams
MSG_WriteCoord( &msg_demo, MSG_ReadCoord( msg ));
MSG_WriteCoord( &msg_demo, MSG_ReadCoord( msg ));
MSG_WriteCoord( &msg_demo, MSG_ReadCoord( msg ));
}
// TE_EXPLOSION2
if( type == 12 )
{
MSG_WriteByte( &msg_demo, MSG_ReadByte( msg ));
MSG_WriteByte( &msg_demo, MSG_ReadByte( msg ));
}
// TE_SMOKE (nehahra)
if( type == 18 )
MSG_WriteByte( &msg_demo, MSG_ReadByte( msg ));
CL_DispatchQuakeMessage( "TempEntity" );
}
/*
===================
CL_ParseQuakeSignon
very important message
===================
*/
static void CL_ParseQuakeSignon( sizebuf_t *msg )
{
int i = MSG_ReadByte( msg );
if( i == 3 ) cls.signon = SIGNONS - 1;
Con_Reportf( "CL_Signon: %d\n", i );
}
/*
==================
CL_ParseNehahraShowLMP
redirect to qwrap->client
==================
*/
static void CL_ParseNehahraShowLMP( sizebuf_t *msg )
{
MSG_WriteString( &msg_demo, MSG_ReadString( msg ));
MSG_WriteString( &msg_demo, MSG_ReadString( msg ));
MSG_WriteByte( &msg_demo, MSG_ReadByte( msg ));
MSG_WriteByte( &msg_demo, MSG_ReadByte( msg ));
CL_DispatchQuakeMessage( "Stats" );
}
/*
==================
CL_ParseNehahraHideLMP
redirect to qwrap->client
==================
*/
static void CL_ParseNehahraHideLMP( sizebuf_t *msg )
{
MSG_WriteString( &msg_demo, MSG_ReadString( msg ));
CL_DispatchQuakeMessage( "Stats" );
}
/*
==================
CL_QuakeStuffText
==================
*/
void CL_QuakeStuffText( const char *text )
{
Q_strncat( cmd_buf, text, sizeof( cmd_buf ));
Cbuf_AddText( text );
}
/*
==================
CL_QuakeExecStuff
==================
*/
void CL_QuakeExecStuff( void )
{
char *text = cmd_buf;
char token[256];
int argc = 0;
// check if no commands this frame
if( !COM_CheckString( text ))
return;
while( 1 )
{
// skip whitespace up to a /n
while( *text && ((byte)*text) <= ' ' && *text != '\r' && *text != '\n' )
text++;
if( *text == '\n' || *text == '\r' )
{
// a newline seperates commands in the buffer
if( *text == '\r' && text[1] == '\n' )
text++;
argc = 0;
text++;
}
if( !*text ) break;
host.com_ignorebracket = true;
text = COM_ParseFile( text, token );
host.com_ignorebracket = false;
if( !text ) break;
if( argc == 0 )
{
// debug: find all missed commands and cvars to add them into QWrap
if( !Cvar_Exists( token ) && !Cmd_Exists( token ))
Con_Printf( S_WARN "'%s' is not exist\n", token );
// else Msg( "cmd: %s\n", token );
// process some special commands
if( !Q_stricmp( token, "playdemo" ))
cls.changedemo = true;
argc++;
}
}
// reset the buffer
cmd_buf[0] = '\0';
}
/*
==================
CL_ParseQuakeMessage
==================
*/
void CL_ParseQuakeMessage( sizebuf_t *msg, qboolean normal_message )
{
int cmd, param1, param2;
size_t bufStart;
const char *str;
cls.starting_count = MSG_GetNumBytesRead( msg ); // updates each frame
CL_Parse_Debug( true ); // begin parsing
// init excise buffer
MSG_Init( &msg_demo, "UserMsg", msg_buf, sizeof( msg_buf ));
if( normal_message )
{
// assume no entity/player update this packet
if( cls.state == ca_active )
{
cl.frames[cls.netchan.incoming_sequence & CL_UPDATE_MASK].valid = false;
cl.frames[cls.netchan.incoming_sequence & CL_UPDATE_MASK].choked = false;
}
else
{
CL_ResetFrame( &cl.frames[cls.netchan.incoming_sequence & CL_UPDATE_MASK] );
}
}
// parse the message
while( 1 )
{
if( MSG_CheckOverflow( msg ))
{
Host_Error( "CL_ParseServerMessage: overflow!\n" );
return;
}
// mark start position
bufStart = MSG_GetNumBytesRead( msg );
// end of message (align bits)
if( MSG_GetNumBitsLeft( msg ) < 8 )
break;
cmd = MSG_ReadServerCmd( msg );
// if the high bit of the command byte is set, it is a fast update
if( FBitSet( cmd, 128 ))
{
CL_ParseQuakeEntityData( msg, cmd & 127 );
continue;
}
// record command for debugging spew on parse problem
CL_Parse_RecordCommand( cmd, bufStart );
// other commands
switch( cmd )
{
case svc_nop:
// this does nothing
break;
case svc_disconnect:
CL_DemoCompleted ();
break;
case svc_updatestat:
CL_ParseQuakeStats( msg );
break;
case svc_version:
param1 = MSG_ReadLong( msg );
if( param1 != PROTOCOL_VERSION_QUAKE )
Host_Error( "Server is protocol %i instead of %i\n", param1, PROTOCOL_VERSION_QUAKE );
break;
case svc_setview:
CL_ParseViewEntity( msg );
break;
case svc_sound:
CL_ParseQuakeSound( msg );
cl.frames[cl.parsecountmod].graphdata.sound += MSG_GetNumBytesRead( msg ) - bufStart;
break;
case svc_time:
Cbuf_AddText( "\n" ); // new frame was started
CL_ParseServerTime( msg );
break;
case svc_print:
str = MSG_ReadString( msg );
Con_Printf( "%s%s", str, *str == 2 ? "\n" : "" );
break;
case svc_stufftext:
CL_QuakeStuffText( MSG_ReadString( msg ));
break;
case svc_setangle:
cl.viewangles[0] = MSG_ReadAngle( msg );
cl.viewangles[1] = MSG_ReadAngle( msg );
cl.viewangles[2] = MSG_ReadAngle( msg );
break;
case svc_serverdata:
Cbuf_Execute(); // make sure any stuffed commands are done
CL_ParseQuakeServerInfo( msg );
break;
case svc_lightstyle:
param1 = MSG_ReadByte( msg );
str = MSG_ReadString( msg );
CL_SetLightstyle( param1, str, cl.mtime[0] );
break;
case svc_updatename:
param1 = MSG_ReadByte( msg );
Q_strncpy( cl.players[param1].name, MSG_ReadString( msg ), sizeof( cl.players[0].name ));
Q_strncpy( cl.players[param1].model, "player", sizeof( cl.players[0].name ));
break;
case svc_updatefrags:
param1 = MSG_ReadByte( msg );
param2 = MSG_ReadShort( msg );
// HACKHACK: store frags into spectator
cl.players[param1].spectator = param2;
break;
case svc_clientdata:
CL_ParseQuakeClientData( msg );
cl.frames[cl.parsecountmod].graphdata.client += MSG_GetNumBytesRead( msg ) - bufStart;
break;
case svc_stopsound:
param1 = MSG_ReadWord( msg );
S_StopSound( param1 >> 3, param1 & 7, NULL );
cl.frames[cl.parsecountmod].graphdata.sound += MSG_GetNumBytesRead( msg ) - bufStart;
break;
case svc_updatecolors:
param1 = MSG_ReadByte( msg );
param2 = MSG_ReadByte( msg );
cl.players[param1].topcolor = param2 & 0xF;
cl.players[param1].bottomcolor = (param2 & 0xF0) >> 4;
break;
case svc_particle:
CL_ParseQuakeParticle( msg );
break;
case svc_damage:
CL_ParseQuakeDamage( msg );
break;
case svc_spawnstatic:
CL_ParseQuakeStaticEntity( msg );
break;
case svc_spawnbinary:
// never used in Quake
break;
case svc_spawnbaseline:
CL_ParseQuakeBaseline( msg );
break;
case svc_temp_entity:
CL_ParseQuakeTempEntity( msg );
cl.frames[cl.parsecountmod].graphdata.tentities += MSG_GetNumBytesRead( msg ) - bufStart;
break;
case svc_setpause:
cl.paused = MSG_ReadByte( msg );
break;
case svc_signonnum:
CL_ParseQuakeSignon( msg );
break;
case svc_centerprint:
str = MSG_ReadString( msg );
CL_DispatchUserMessage( "HudText", Q_strlen( str ), (void *)str );
break;
case svc_killedmonster:
CL_DispatchQuakeMessage( "KillMonster" ); // just an event
break;
case svc_foundsecret:
CL_DispatchQuakeMessage( "FoundSecret" ); // just an event
break;
case svc_spawnstaticsound:
CL_ParseQuakeStaticSound( msg );
break;
case svc_intermission:
cl.intermission = 1;
break;
case svc_finale:
CL_ParseFinaleCutscene( msg, 2 );
break;
case svc_cdtrack:
param1 = MSG_ReadByte( msg );
param1 = bound( 0, param1, MAX_CDTRACKS - 1 ); // tracknum
param2 = MSG_ReadByte( msg );
param2 = bound( 0, param2, MAX_CDTRACKS - 1 ); // loopnum
if(( cls.demoplayback || cls.demorecording ) && ( cls.forcetrack != -1 ))
S_StartBackgroundTrack( clgame.cdtracks[cls.forcetrack], clgame.cdtracks[cls.forcetrack], 0, false );
else S_StartBackgroundTrack( clgame.cdtracks[param1], clgame.cdtracks[param2], 0, false );
break;
case svc_sellscreen:
Cmd_ExecuteString( "help" ); // open quake menu
break;
case svc_cutscene:
CL_ParseFinaleCutscene( msg, 3 );
break;
case svc_hidelmp:
CL_ParseNehahraHideLMP( msg );
break;
case svc_showlmp:
CL_ParseNehahraShowLMP( msg );
break;
case svc_skybox:
Q_strncpy( clgame.movevars.skyName, MSG_ReadString( msg ), sizeof( clgame.movevars.skyName ));
break;
case svc_skyboxsize:
MSG_ReadCoord( msg ); // obsolete
break;
case svc_fog:
if( MSG_ReadByte( msg ))
{
float fog_settings[4];
int packed_fog[4];
fog_settings[3] = MSG_ReadFloat( msg ); // density
fog_settings[0] = MSG_ReadByte( msg ); // red
fog_settings[1] = MSG_ReadByte( msg ); // green
fog_settings[2] = MSG_ReadByte( msg ); // blue
packed_fog[0] = fog_settings[0] * 255;
packed_fog[1] = fog_settings[1] * 255;
packed_fog[2] = fog_settings[2] * 255;
packed_fog[3] = fog_settings[3] * 255;
clgame.movevars.fog_settings = (packed_fog[1]<<24)|(packed_fog[2]<<16)|(packed_fog[3]<<8)|packed_fog[0];
}
else
{
clgame.movevars.fog_settings = 0;
}
break;
default:
Host_Error( "CL_ParseServerMessage: Illegible server message\n" );
break;
}
}
cl.frames[cl.parsecountmod].graphdata.msgbytes += MSG_GetNumBytesRead( msg ) - cls.starting_count;
CL_Parse_Debug( false ); // done
// now process packet.
CL_ProcessPacket( &cl.frames[cl.parsecountmod] );
// add new entities into physic lists
CL_SetSolidEntities();
// check deferred cmds
CL_QuakeExecStuff();
}