xash3d-fwgs/engine/client/cl_qparse.c
Gleb Mazovetskiy 5e0a0765ce Trim all trailing whitespace
The `.editorconfig` file in this repo is configured to trim all trailing
whitespace regardless of whether the line is modified.

Trims all trailing whitespace in the repository to make the codebase easier
to work with in editors that respect `.editorconfig`.

`git blame` becomes less useful on these lines but it already isn't very useful.

Commands:

```
find . -type f -name '*.h' -exec sed --in-place 's/[[:space:]]\+$//' {} \+
find . -type f -name '*.c' -exec sed --in-place 's/[[:space:]]\+$//' {} \+
```
2021-01-04 20:55:10 +03:00

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();
}