You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1133 lines
29 KiB
1133 lines
29 KiB
/* |
|
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 "gl_local.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.0 / 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, glState.wideScreen ? "16x9" : "4x3" )); |
|
else Cvar_Set( "cl_levelshot_name", va( "levelshots/%s_%s", clgame.mapname, glState.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 ) && CL_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(); |
|
} |