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