/* cl_parse.c - parse a message received from the server Copyright (C) 2008 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" /* ================== CL_ParseStaticEntity static client entity ================== */ void CL_LegacyParseStaticEntity( sizebuf_t *msg ) { int i; entity_state_t state; cl_entity_t *ent; memset( &state, 0, sizeof( state )); state.modelindex = MSG_ReadShort( msg ); state.sequence = MSG_ReadByte( msg ); state.frame = MSG_ReadByte( msg ); state.colormap = MSG_ReadWord( msg ); state.skin = MSG_ReadByte( msg ); for( i = 0; i < 3; i++ ) { state.origin[i] = MSG_ReadCoord( msg ); state.angles[i] = MSG_ReadBitAngle( msg, 16 ); } state.rendermode = MSG_ReadByte( msg ); if( state.rendermode != kRenderNormal ) { state.renderamt = MSG_ReadByte( msg ); state.rendercolor.r = MSG_ReadByte( msg ); state.rendercolor.g = MSG_ReadByte( msg ); state.rendercolor.b = MSG_ReadByte( msg ); state.renderfx = MSG_ReadByte( msg ); } i = clgame.numStatics; if( i >= MAX_STATIC_ENTITIES ) { Con_Printf( S_ERROR "MAX_STATIC_ENTITIES limit exceeded!\n" ); return; } ent = &clgame.static_entities[i]; clgame.numStatics++; // all states are same ent->baseline = ent->curstate = ent->prevstate = state; ent->index = 0; // static entities doesn't has the numbers // 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->curstate.rendermode == kRenderNormal && 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 } void CL_LegacyParseSoundPacket( sizebuf_t *msg, qboolean is_ambient ) { vec3_t pos; int chan, sound; float volume, attn; int flags, pitch, entnum; sound_t handle = 0; flags = MSG_ReadWord( msg ); if( flags & SND_LEGACY_LARGE_INDEX ) { sound = MSG_ReadWord( msg ); flags &= ~SND_LEGACY_LARGE_INDEX; } else sound = MSG_ReadByte( msg ); chan = 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; if( FBitSet( flags, SND_PITCH )) pitch = MSG_ReadByte( msg ); else pitch = PITCH_NORM; // entity reletive entnum = MSG_ReadWord( msg ); // positioned in space MSG_ReadVec3Coord( msg, pos ); if( FBitSet( flags, SND_SENTENCE )) { char sentenceName[32]; //if( FBitSet( flags, SND_SEQUENCE )) //Q_snprintf( sentenceName, sizeof( sentenceName ), "!#%i", sound + MAX_SOUNDS ); //else Q_snprintf( sentenceName, sizeof( sentenceName ), "!%i", sound ); handle = S_RegisterSound( sentenceName ); } else handle = cl.sound_index[sound]; // see precached sound if( !cl.audio_prepped ) return; // too early // g-cont. sound and ambient sound have only difference with channel if( is_ambient ) { S_AmbientSound( pos, entnum, handle, volume, attn, pitch, flags ); } else { S_StartSound( pos, entnum, chan, handle, volume, attn, pitch, flags ); } } /* ================ CL_PrecacheSound prceache sound from server ================ */ void CL_LegacyPrecacheSound( sizebuf_t *msg ) { int soundIndex; soundIndex = MSG_ReadUBitLong( msg, MAX_SOUND_BITS ); if( soundIndex < 0 || soundIndex >= MAX_SOUNDS ) Host_Error( "CL_PrecacheSound: bad soundindex %i\n", soundIndex ); Q_strncpy( cl.sound_precache[soundIndex], MSG_ReadString( msg ), sizeof( cl.sound_precache[0] )); // when we loading map all resources is precached sequentially //if( !cl.audio_prepped ) return; cl.sound_index[soundIndex] = S_RegisterSound( cl.sound_precache[soundIndex] ); } void CL_LegacyPrecacheModel( sizebuf_t *msg ) { int modelIndex; string model; modelIndex = MSG_ReadUBitLong( msg, MAX_LEGACY_MODEL_BITS ); if( modelIndex < 0 || modelIndex >= MAX_MODELS ) Host_Error( "CL_PrecacheModel: bad modelindex %i\n", modelIndex ); Q_strncpy( model, MSG_ReadString( msg ), MAX_STRING ); //Q_strncpy( cl.model_precache[modelIndex], BF_ReadString( msg ), sizeof( cl.model_precache[0] )); // when we loading map all resources is precached sequentially //if( !cl.video_prepped ) return; if( modelIndex == 1 && !cl.worldmodel ) { CL_ClearWorld (); cl.models[modelIndex] = cl.worldmodel = Mod_LoadWorld( model, true ); return; } //Mod_RegisterModel( cl.model_precache[modelIndex], modelIndex ); cl.models[modelIndex] = Mod_ForName( model, false, false ); cl.nummodels = Q_max( cl.nummodels, modelIndex ); } void CL_LegacyPrecacheEvent( sizebuf_t *msg ) { int eventIndex; eventIndex = MSG_ReadUBitLong( msg, MAX_EVENT_BITS ); if( eventIndex < 0 || eventIndex >= MAX_EVENTS ) Host_Error( "CL_PrecacheEvent: bad eventindex %i\n", eventIndex ); Q_strncpy( cl.event_precache[eventIndex], MSG_ReadString( msg ), sizeof( cl.event_precache[0] )); // can be set now CL_SetEventIndex( cl.event_precache[eventIndex], eventIndex ); } #if XASH_LOW_MEMORY == 0 #define MAX_LEGACY_RESOURCES 2048 #elif XASH_LOW_MEMORY == 2 #define MAX_LEGACY_RESOURCES 1 #elif XASH_LOW_MEMORY == 1 #define MAX_LEGACY_RESOURCES 512 #endif /* ============== CL_ParseResourceList ============== */ void CL_LegacyParseResourceList( sizebuf_t *msg ) { int i = 0; static struct { int rescount; int restype[MAX_LEGACY_RESOURCES]; char resnames[MAX_LEGACY_RESOURCES][MAX_QPATH]; } reslist; memset( &reslist, 0, sizeof( reslist )); reslist.rescount = MSG_ReadWord( msg ) - 1; if( reslist.rescount > MAX_LEGACY_RESOURCES ) Host_Error("MAX_RESOURCES reached\n"); for( i = 0; i < reslist.rescount; i++ ) { reslist.restype[i] = MSG_ReadWord( msg ); Q_strncpy( reslist.resnames[i], MSG_ReadString( msg ), MAX_QPATH ); } if( CL_IsPlaybackDemo() ) return; if( !cl_allow_download.value ) { Con_DPrintf( "Refusing new resource, cl_allow_download set to 0\n" ); reslist.rescount = 0; } if( cls.state == ca_active && !cl_download_ingame.value ) { Con_DPrintf( "Refusing new resource, cl_download_ingame set to 0\n" ); reslist.rescount = 0; } HTTP_ResetProcessState(); host.downloadcount = 0; for( i = 0; i < reslist.rescount; i++ ) { char soundpath[MAX_VA_STRING]; const char *path; if( reslist.restype[i] == t_sound ) { Q_snprintf( soundpath, sizeof( soundpath ), DEFAULT_SOUNDPATH "%s", reslist.resnames[i] ); path = soundpath; } else path = reslist.resnames[i]; if( FS_FileExists( path, false )) continue; // already exists host.downloadcount++; HTTP_AddDownload( path, -1, true ); } if( !host.downloadcount ) { MSG_WriteByte( &cls.netchan.message, clc_stringcmd ); MSG_WriteString( &cls.netchan.message, "continueloading" ); } } /* ===================== CL_ParseLegacyServerMessage dispatch messages ===================== */ void CL_ParseLegacyServerMessage( sizebuf_t *msg, qboolean normal_message ) { size_t bufStart, playerbytes; int cmd, param1, param2; int old_background; const char *s; cls.starting_count = MSG_GetNumBytesRead( msg ); // updates each frame CL_Parse_Debug( true ); // begin parsing 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 ); // record command for debugging spew on parse problem CL_Parse_RecordCommand( cmd, bufStart ); // other commands switch( cmd ) { case svc_bad: Host_Error( "svc_bad\n" ); break; case svc_nop: // this does nothing break; case svc_disconnect: CL_Drop (); Host_AbortCurrentFrame (); break; case svc_legacy_event: CL_ParseEvent( msg ); cl.frames[cl.parsecountmod].graphdata.event += MSG_GetNumBytesRead( msg ) - bufStart; break; case svc_legacy_changing: old_background = cl.background; if( MSG_ReadOneBit( msg )) { int maxclients = cl.maxclients; cls.changelevel = true; S_StopAllSounds( true ); Con_Printf( "Server changing, reconnecting\n" ); if( cls.demoplayback ) { SCR_BeginLoadingPlaque( cl.background ); cls.changedemo = true; } CL_ClearState( ); // a1ba: need to restore cl.maxclients because engine chooses // frame backups count depending on this value // In general, it's incorrect to call CL_InitEdicts right after // CL_ClearState because of this bug. Some time later this logic // should be re-done. CL_InitEdicts( maxclients ); // re-arrange edicts } else Con_Printf( "Server disconnected, reconnecting\n" ); if( cls.demoplayback ) { cl.background = (cls.demonum != -1) ? true : false; cls.state = ca_connected; } else { // g-cont. local client skip the challenge if( SV_Active( )) cls.state = ca_disconnected; else cls.state = ca_connecting; cl.background = old_background; cls.connect_time = MAX_HEARTBEAT; cls.connect_retry = 0; } break; case svc_setview: CL_ParseViewEntity( msg ); break; case svc_sound: CL_LegacyParseSoundPacket( msg, false ); cl.frames[cl.parsecountmod].graphdata.sound += MSG_GetNumBytesRead( msg ) - bufStart; break; case svc_legacy_ambientsound: CL_LegacyParseSoundPacket( msg, true ); cl.frames[cl.parsecountmod].graphdata.sound += MSG_GetNumBytesRead( msg ) - bufStart; break; case svc_time: CL_ParseServerTime( msg ); break; case svc_print: Con_Printf( "%s", MSG_ReadString( msg )); break; case svc_stufftext: s = MSG_ReadString( msg ); #ifdef HACKS_RELATED_HLMODS // disable Cry Of Fear antisave protection if( !Q_strnicmp( s, "disconnect", 10 ) && cls.signon != SIGNONS ) break; // too early #endif Con_Reportf( "Stufftext: %s", s ); Cbuf_AddFilteredText( s ); break; case svc_setangle: CL_ParseSetAngle( msg ); break; case svc_serverdata: Cbuf_Execute(); // make sure any stuffed commands are done CL_ParseServerData( msg, true ); break; case svc_lightstyle: CL_ParseLightStyle( msg ); break; case svc_updateuserinfo: CL_UpdateUserinfo( msg, true ); break; case svc_deltatable: Delta_ParseTableField( msg ); break; case svc_clientdata: CL_ParseClientData( msg ); cl.frames[cl.parsecountmod].graphdata.client += MSG_GetNumBytesRead( msg ) - bufStart; break; case svc_resource: CL_ParseResource( msg ); break; case svc_pings: CL_UpdateUserPings( msg ); break; case svc_particle: CL_ParseParticles( msg ); break; case svc_restoresound: CL_ParseRestoreSoundPacket( msg ); cl.frames[cl.parsecountmod].graphdata.sound += MSG_GetNumBytesRead( msg ) - bufStart; break; case svc_spawnstatic: CL_LegacyParseStaticEntity( msg ); break; case svc_event_reliable: CL_ParseReliableEvent( msg ); cl.frames[cl.parsecountmod].graphdata.event += MSG_GetNumBytesRead( msg ) - bufStart; break; case svc_spawnbaseline: CL_ParseBaseline( msg, true ); break; case svc_temp_entity: CL_ParseTempEntity( msg ); cl.frames[cl.parsecountmod].graphdata.tentities += MSG_GetNumBytesRead( msg ) - bufStart; break; case svc_setpause: cl.paused = ( MSG_ReadOneBit( msg ) != 0 ); break; case svc_signonnum: CL_ParseSignon( msg ); break; case svc_centerprint: CL_CenterPrint( MSG_ReadString( msg ), 0.25f ); break; case svc_intermission: cl.intermission = 1; break; case svc_legacy_modelindex: CL_LegacyPrecacheModel( msg ); break; case svc_legacy_soundindex: CL_LegacyPrecacheSound( msg ); break; case svc_cdtrack: param1 = MSG_ReadByte( msg ); param1 = bound( 1, param1, MAX_CDTRACKS ); // tracknum param2 = MSG_ReadByte( msg ); param2 = bound( 1, param2, MAX_CDTRACKS ); // loopnum S_StartBackgroundTrack( clgame.cdtracks[param1-1], clgame.cdtracks[param2-1], 0, false ); break; case svc_restore: CL_ParseRestore( msg ); break; case svc_legacy_eventindex: CL_LegacyPrecacheEvent(msg); break; case svc_weaponanim: param1 = MSG_ReadByte( msg ); // iAnim param2 = MSG_ReadByte( msg ); // body CL_WeaponAnim( param1, param2 ); break; case svc_bspdecal: CL_ParseStaticDecal( msg ); break; case svc_roomtype: param1 = MSG_ReadShort( msg ); Cvar_SetValue( "room_type", param1 ); break; case svc_addangle: CL_ParseAddAngle( msg ); break; case svc_usermessage: CL_RegisterUserMessage( msg ); break; case svc_packetentities: playerbytes = CL_ParsePacketEntities( msg, false ); cl.frames[cl.parsecountmod].graphdata.players += playerbytes; cl.frames[cl.parsecountmod].graphdata.entities += MSG_GetNumBytesRead( msg ) - bufStart - playerbytes; break; case svc_deltapacketentities: playerbytes = CL_ParsePacketEntities( msg, true ); cl.frames[cl.parsecountmod].graphdata.players += playerbytes; cl.frames[cl.parsecountmod].graphdata.entities += MSG_GetNumBytesRead( msg ) - bufStart - playerbytes; break; case svc_legacy_chokecount: { int i, j; i = MSG_ReadByte( msg ); j = cls.netchan.incoming_acknowledged - 1; for( ; i > 0 && j > cls.netchan.outgoing_sequence - CL_UPDATE_BACKUP; j-- ) { if( cl.frames[j & CL_UPDATE_MASK].receivedtime != -3.0 ) { cl.frames[j & CL_UPDATE_MASK].choked = true; cl.frames[j & CL_UPDATE_MASK].receivedtime = -2.0; i--; } } break; } //cl.frames[cls.netchan.incoming_sequence & CL_UPDATE_MASK].choked = true; //cl.frames[cls.netchan.incoming_sequence & CL_UPDATE_MASK].receivedtime = -2.0; break; case svc_resourcelist: CL_LegacyParseResourceList( msg ); break; case svc_deltamovevars: CL_ParseMovevars( msg ); break; case svc_resourcerequest: CL_ParseResourceRequest( msg ); break; case svc_customization: CL_ParseCustomization( msg ); break; case svc_crosshairangle: CL_ParseCrosshairAngle( msg ); break; case svc_soundfade: CL_ParseSoundFade( msg ); break; case svc_filetxferfailed: CL_ParseFileTransferFailed( msg ); break; case svc_hltv: CL_ParseHLTV( msg ); break; case svc_director: CL_ParseDirector( msg ); break; case svc_resourcelocation: CL_ParseResLocation( msg ); break; case svc_querycvarvalue: CL_ParseCvarValue( msg, false ); break; case svc_querycvarvalue2: CL_ParseCvarValue( msg, true ); break; default: CL_ParseUserMessage( msg, cmd ); cl.frames[cl.parsecountmod].graphdata.usr += MSG_GetNumBytesRead( msg ) - bufStart; break; } } cl.frames[cl.parsecountmod].graphdata.msgbytes += MSG_GetNumBytesRead( msg ) - cls.starting_count; CL_Parse_Debug( false ); // done // we don't know if it is ok to save a demo message until // after we have parsed the frame if( !cls.demoplayback ) { if( cls.demorecording && !cls.demowaiting ) { CL_WriteDemoMessage( false, cls.starting_count, msg ); } else if( cls.state != ca_active ) { CL_WriteDemoMessage( true, cls.starting_count, msg ); } } } void CL_LegacyPrecache_f( void ) { int spawncount, i; model_t *mod; if( !cls.legacymode ) return; spawncount = Q_atoi( Cmd_Argv( 1 )); Con_Printf( "Setting up renderer...\n" ); // load tempent sprites (glowshell, muzzleflashes etc) CL_LoadClientSprites (); // invalidate all decal indexes memset( cl.decal_index, 0, sizeof( cl.decal_index )); cl.video_prepped = true; cl.audio_prepped = true; if( clgame.entities ) clgame.entities->model = cl.worldmodel; // tell rendering system we have a new set of models. ref.dllFuncs.R_NewMap (); CL_SetupOverviewParams(); // release unused SpriteTextures for( i = 1, mod = clgame.sprites; i < MAX_CLIENT_SPRITES; i++, mod++ ) { if( mod->needload == NL_UNREFERENCED && COM_CheckString( mod->name )) Mod_FreeModel( mod ); } // Mod_FreeUnused (); if( host_developer.value <= DEV_NONE ) Con_ClearNotify(); // clear any lines of console text // done with all resources, issue prespawn command. // Include server count in case server disconnects and changes level during d/l MSG_BeginClientCmd( &cls.netchan.message, clc_stringcmd ); MSG_WriteStringf( &cls.netchan.message, "begin %i", spawncount ); cls.signon = SIGNONS; } void CL_LegacyUpdateInfo( void ) { if( !cls.legacymode ) return; if( cls.state != ca_active ) return; MSG_BeginClientCmd( &cls.netchan.message, clc_legacy_userinfo ); MSG_WriteString( &cls.netchan.message, cls.userinfo ); } qboolean CL_LegacyMode( void ) { return cls.legacymode; }