mirror of
https://github.com/FWGS/xash3d-fwgs
synced 2024-11-30 06:00:17 +01:00
1130 lines
29 KiB
C
1130 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" );
|
|
|
|
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 ));
|
|
|
|
// a1ba: didn't filtered, anyway quake protocol
|
|
// only supported for demos, not network games
|
|
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;
|
|
|
|
text = COM_ParseFileSafe( text, token, sizeof( token ), PFILE_IGNOREBRACKET, NULL, NULL );
|
|
|
|
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();
|
|
}
|