mirror of
https://github.com/w23/xash3d-fwgs
synced 2024-12-16 14:10:11 +01:00
5e0a0765ce
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:]]\+$//' {} \+ ```
1133 lines
29 KiB
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();
|
|
}
|