mirror of
https://github.com/w23/xash3d-fwgs
synced 2024-12-17 14:59:57 +01:00
5e0a0765ce
The `.editorconfig` file in this repo is configured to trim all trailing whitespace regardless of whether the line is modified. Trims all trailing whitespace in the repository to make the codebase easier to work with in editors that respect `.editorconfig`. `git blame` becomes less useful on these lines but it already isn't very useful. Commands: ``` find . -type f -name '*.h' -exec sed --in-place 's/[[:space:]]\+$//' {} \+ find . -type f -name '*.c' -exec sed --in-place 's/[[:space:]]\+$//' {} \+ ```
3214 lines
77 KiB
C
3214 lines
77 KiB
C
/*
|
|
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"
|
|
#if XASH_LOW_MEMORY != 2
|
|
int CL_UPDATE_BACKUP = SINGLEPLAYER_BACKUP;
|
|
#endif
|
|
/*
|
|
===============
|
|
CL_UserMsgStub
|
|
|
|
Default stub for missed callbacks
|
|
===============
|
|
*/
|
|
int CL_UserMsgStub( const char *pszName, int iSize, void *pbuf )
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
CL_ParseViewEntity
|
|
|
|
==================
|
|
*/
|
|
void CL_ParseViewEntity( sizebuf_t *msg )
|
|
{
|
|
cl.viewentity = MSG_ReadWord( msg );
|
|
|
|
// check entity bounds in case we want
|
|
// to use this directly in clgame.entities[] array
|
|
cl.viewentity = bound( 0, cl.viewentity, clgame.maxEntities - 1 );
|
|
}
|
|
|
|
/*
|
|
==================
|
|
CL_ParseSoundPacket
|
|
|
|
==================
|
|
*/
|
|
void CL_ParseSoundPacket( sizebuf_t *msg )
|
|
{
|
|
vec3_t pos;
|
|
int chan, sound;
|
|
float volume, attn;
|
|
int flags, pitch, entnum;
|
|
sound_t handle = 0;
|
|
|
|
flags = MSG_ReadUBitLong( msg, MAX_SND_FLAGS_BITS );
|
|
sound = MSG_ReadUBitLong( msg, MAX_SOUND_BITS );
|
|
chan = MSG_ReadUBitLong( msg, MAX_SND_CHAN_BITS );
|
|
|
|
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_ReadUBitLong( msg, MAX_ENTITY_BITS );
|
|
|
|
// 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_NONSENTENCE );
|
|
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( chan == CHAN_STATIC )
|
|
{
|
|
S_AmbientSound( pos, entnum, handle, volume, attn, pitch, flags );
|
|
}
|
|
else
|
|
{
|
|
S_StartSound( pos, entnum, chan, handle, volume, attn, pitch, flags );
|
|
}
|
|
}
|
|
|
|
/*
|
|
==================
|
|
CL_ParseRestoreSoundPacket
|
|
|
|
==================
|
|
*/
|
|
void CL_ParseRestoreSoundPacket( sizebuf_t *msg )
|
|
{
|
|
vec3_t pos;
|
|
int chan, sound;
|
|
float volume, attn;
|
|
int flags, pitch, entnum;
|
|
double samplePos, forcedEnd;
|
|
int wordIndex;
|
|
sound_t handle = 0;
|
|
|
|
flags = MSG_ReadUBitLong( msg, MAX_SND_FLAGS_BITS );
|
|
sound = MSG_ReadUBitLong( msg, MAX_SOUND_BITS );
|
|
chan = MSG_ReadUBitLong( msg, MAX_SND_CHAN_BITS );
|
|
|
|
if( flags & SND_VOLUME )
|
|
volume = (float)MSG_ReadByte( msg ) / 255.0f;
|
|
else volume = VOL_NORM;
|
|
|
|
if( flags & SND_ATTENUATION )
|
|
attn = (float)MSG_ReadByte( msg ) / 64.0f;
|
|
else attn = ATTN_NONE;
|
|
|
|
if( flags & SND_PITCH )
|
|
pitch = MSG_ReadByte( msg );
|
|
else pitch = PITCH_NORM;
|
|
|
|
// entity reletive
|
|
entnum = MSG_ReadUBitLong( msg, MAX_ENTITY_BITS );
|
|
|
|
// positioned in space
|
|
MSG_ReadVec3Coord( msg, pos );
|
|
|
|
if( flags & SND_SENTENCE )
|
|
{
|
|
char sentenceName[32];
|
|
|
|
if( flags & SND_SEQUENCE )
|
|
Q_snprintf( sentenceName, sizeof( sentenceName ), "!%i", sound + MAX_SOUNDS_NONSENTENCE );
|
|
else Q_snprintf( sentenceName, sizeof( sentenceName ), "!%i", sound );
|
|
|
|
handle = S_RegisterSound( sentenceName );
|
|
}
|
|
else handle = cl.sound_index[sound]; // see precached sound
|
|
|
|
wordIndex = MSG_ReadByte( msg );
|
|
|
|
// 16 bytes here
|
|
MSG_ReadBytes( msg, &samplePos, sizeof( samplePos ));
|
|
MSG_ReadBytes( msg, &forcedEnd, sizeof( forcedEnd ));
|
|
|
|
if( !cl.audio_prepped )
|
|
return; // too early
|
|
|
|
S_RestoreSound( pos, entnum, chan, handle, volume, attn, pitch, flags, samplePos, forcedEnd, wordIndex );
|
|
}
|
|
|
|
/*
|
|
==================
|
|
CL_ParseServerTime
|
|
|
|
==================
|
|
*/
|
|
void CL_ParseServerTime( sizebuf_t *msg )
|
|
{
|
|
double dt;
|
|
|
|
cl.mtime[1] = cl.mtime[0];
|
|
cl.mtime[0] = MSG_ReadFloat( msg );
|
|
|
|
if( cls.demoplayback == DEMO_QUAKE1 )
|
|
return; // don't mess the time
|
|
|
|
if( cl.maxclients == 1 )
|
|
cl.time = cl.mtime[0];
|
|
|
|
dt = cl.time - cl.mtime[0];
|
|
|
|
if( fabs( dt ) > cl_clockreset->value ) // 0.1 by default
|
|
{
|
|
cl.time = cl.mtime[0];
|
|
cl.timedelta = 0.0f;
|
|
}
|
|
else if( dt != 0.0 )
|
|
{
|
|
cl.timedelta = dt;
|
|
}
|
|
|
|
if( cl.oldtime > cl.time )
|
|
cl.oldtime = cl.time;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
CL_ParseSignon
|
|
|
|
==================
|
|
*/
|
|
void CL_ParseSignon( sizebuf_t *msg )
|
|
{
|
|
int i = MSG_ReadByte( msg );
|
|
|
|
if( i <= cls.signon )
|
|
{
|
|
Con_Reportf( S_ERROR "received signon %i when at %i\n", i, cls.signon );
|
|
CL_Disconnect();
|
|
return;
|
|
}
|
|
|
|
cls.signon = i;
|
|
CL_SignonReply();
|
|
}
|
|
|
|
/*
|
|
==================
|
|
CL_ParseMovevars
|
|
|
|
==================
|
|
*/
|
|
void CL_ParseMovevars( sizebuf_t *msg )
|
|
{
|
|
Delta_InitClient (); // finalize client delta's
|
|
|
|
MSG_ReadDeltaMovevars( msg, &clgame.oldmovevars, &clgame.movevars );
|
|
|
|
// water alpha is not allowed
|
|
if( !FBitSet( world.flags, FWORLD_WATERALPHA ))
|
|
clgame.movevars.wateralpha = 1.0f;
|
|
|
|
// update sky if changed
|
|
if( Q_strcmp( clgame.oldmovevars.skyName, clgame.movevars.skyName ) && cl.video_prepped )
|
|
ref.dllFuncs.R_SetupSky( clgame.movevars.skyName );
|
|
|
|
memcpy( &clgame.oldmovevars, &clgame.movevars, sizeof( movevars_t ));
|
|
clgame.entities->curstate.scale = clgame.movevars.waveHeight;
|
|
|
|
// keep features an actual!
|
|
clgame.oldmovevars.features = clgame.movevars.features = host.features;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
CL_ParseParticles
|
|
|
|
==================
|
|
*/
|
|
void CL_ParseParticles( sizebuf_t *msg )
|
|
{
|
|
vec3_t org, dir;
|
|
int i, count, color;
|
|
float life;
|
|
|
|
MSG_ReadVec3Coord( msg, org );
|
|
|
|
for( i = 0; i < 3; i++ )
|
|
dir[i] = MSG_ReadChar( msg ) * 0.0625f;
|
|
|
|
count = MSG_ReadByte( msg );
|
|
color = MSG_ReadByte( msg );
|
|
if( count == 255 ) count = 1024;
|
|
life = MSG_ReadByte( msg ) * 0.125f;
|
|
|
|
if( life != 0.0f && count == 1 )
|
|
{
|
|
particle_t *p;
|
|
|
|
p = R_AllocParticle( NULL );
|
|
if( !p ) return;
|
|
|
|
p->die += life;
|
|
p->color = color;
|
|
p->type = pt_static;
|
|
|
|
VectorCopy( org, p->org );
|
|
VectorCopy( dir, p->vel );
|
|
}
|
|
else R_RunParticleEffect( org, dir, color, count );
|
|
}
|
|
|
|
/*
|
|
==================
|
|
CL_ParseStaticEntity
|
|
|
|
static client entity
|
|
==================
|
|
*/
|
|
void CL_ParseStaticEntity( sizebuf_t *msg )
|
|
{
|
|
int i, newnum;
|
|
entity_state_t from, to;
|
|
cl_entity_t *ent;
|
|
|
|
memset( &from, 0, sizeof( from ));
|
|
newnum = MSG_ReadUBitLong( msg, MAX_ENTITY_BITS );
|
|
MSG_ReadDeltaEntity( msg, &from, &to, 0, DELTA_STATIC, cl.mtime[0] );
|
|
|
|
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 = to;
|
|
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( to.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
|
|
}
|
|
|
|
|
|
/*
|
|
==================
|
|
CL_WeaponAnim
|
|
|
|
Set new weapon animation
|
|
==================
|
|
*/
|
|
void GAME_EXPORT CL_WeaponAnim( int iAnim, int body )
|
|
{
|
|
cl_entity_t *view = &clgame.viewent;
|
|
|
|
cl.local.weaponstarttime = 0.0f;
|
|
cl.local.weaponsequence = iAnim;
|
|
view->curstate.framerate = 1.0f;
|
|
view->curstate.body = body;
|
|
|
|
#if 0 // g-cont. for GlowShell testing
|
|
view->curstate.renderfx = kRenderFxGlowShell;
|
|
view->curstate.rendercolor.r = 255;
|
|
view->curstate.rendercolor.g = 128;
|
|
view->curstate.rendercolor.b = 0;
|
|
view->curstate.renderamt = 150;
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
==================
|
|
CL_ParseStaticDecal
|
|
|
|
==================
|
|
*/
|
|
void CL_ParseStaticDecal( sizebuf_t *msg )
|
|
{
|
|
vec3_t origin;
|
|
int decalIndex, entityIndex, modelIndex;
|
|
cl_entity_t *ent = NULL;
|
|
float scale;
|
|
int flags;
|
|
|
|
MSG_ReadVec3Coord( msg, origin );
|
|
decalIndex = MSG_ReadWord( msg );
|
|
entityIndex = MSG_ReadShort( msg );
|
|
|
|
if( entityIndex > 0 )
|
|
modelIndex = MSG_ReadWord( msg );
|
|
else modelIndex = 0;
|
|
flags = MSG_ReadByte( msg );
|
|
scale = (float)MSG_ReadWord( msg ) / 4096.0f;
|
|
|
|
CL_FireCustomDecal( CL_DecalIndex( decalIndex ), entityIndex, modelIndex, origin, flags, scale );
|
|
}
|
|
|
|
/*
|
|
==================
|
|
CL_ParseSoundFade
|
|
|
|
==================
|
|
*/
|
|
void CL_ParseSoundFade( sizebuf_t *msg )
|
|
{
|
|
float fadePercent, fadeOutSeconds;
|
|
float holdTime, fadeInSeconds;
|
|
|
|
fadePercent = (float)MSG_ReadByte( msg );
|
|
holdTime = (float)MSG_ReadByte( msg );
|
|
fadeOutSeconds = (float)MSG_ReadByte( msg );
|
|
fadeInSeconds = (float)MSG_ReadByte( msg );
|
|
|
|
S_FadeClientVolume( fadePercent, fadeOutSeconds, holdTime, fadeInSeconds );
|
|
}
|
|
|
|
/*
|
|
==================
|
|
CL_RequestMissingResources
|
|
|
|
==================
|
|
*/
|
|
qboolean CL_RequestMissingResources( void )
|
|
{
|
|
resource_t *p;
|
|
|
|
if( !cls.dl.doneregistering && ( cls.dl.custom || cls.state == ca_validate ))
|
|
{
|
|
p = cl.resourcesneeded.pNext;
|
|
|
|
if( p == &cl.resourcesneeded )
|
|
{
|
|
cls.dl.doneregistering = true;
|
|
host.downloadcount = 0;
|
|
cls.dl.custom = false;
|
|
}
|
|
else if( !FBitSet( p->ucFlags, RES_WASMISSING ))
|
|
{
|
|
CL_MoveToOnHandList( cl.resourcesneeded.pNext );
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void CL_BatchResourceRequest( qboolean initialize )
|
|
{
|
|
byte data[MAX_INIT_MSG];
|
|
resource_t *p, *n;
|
|
sizebuf_t msg;
|
|
|
|
MSG_Init( &msg, "Resource Batch", data, sizeof( data ));
|
|
|
|
// client resources is not precached by server
|
|
if( initialize ) CL_AddClientResources();
|
|
|
|
for( p = cl.resourcesneeded.pNext; p && p != &cl.resourcesneeded; p = n )
|
|
{
|
|
n = p->pNext;
|
|
|
|
if( !FBitSet( p->ucFlags, RES_WASMISSING ))
|
|
{
|
|
CL_MoveToOnHandList( p );
|
|
continue;
|
|
}
|
|
|
|
if( cls.state == ca_active && !cl_download_ingame.value )
|
|
{
|
|
Con_Printf( "skipping in game download of %s\n", p->szFileName );
|
|
CL_MoveToOnHandList( p );
|
|
continue;
|
|
}
|
|
|
|
switch( p->type )
|
|
{
|
|
case t_sound:
|
|
if( !CL_CheckFile( &msg, p ))
|
|
break;
|
|
CL_MoveToOnHandList( p );
|
|
break;
|
|
case t_skin:
|
|
CL_MoveToOnHandList( p );
|
|
break;
|
|
case t_model:
|
|
if( !CL_CheckFile( &msg, p ))
|
|
break;
|
|
CL_MoveToOnHandList( p );
|
|
break;
|
|
case t_decal:
|
|
if( !HPAK_GetDataPointer( CUSTOM_RES_PATH, p, NULL, NULL ))
|
|
{
|
|
if( !FBitSet( p->ucFlags, RES_REQUESTED ))
|
|
{
|
|
MSG_BeginClientCmd( &msg, clc_stringcmd );
|
|
MSG_WriteString( &msg, va( "dlfile !MD5%s", MD5_Print( p->rgucMD5_hash ) ) );
|
|
SetBits( p->ucFlags, RES_REQUESTED );
|
|
}
|
|
break;
|
|
}
|
|
CL_MoveToOnHandList( p );
|
|
break;
|
|
case t_generic:
|
|
if( !COM_IsSafeFileToDownload( p->szFileName ))
|
|
{
|
|
CL_RemoveFromResourceList( p );
|
|
Mem_Free( p );
|
|
break;
|
|
}
|
|
if( !CL_CheckFile( &msg, p ))
|
|
break;
|
|
CL_MoveToOnHandList( p );
|
|
break;
|
|
case t_eventscript:
|
|
if( !CL_CheckFile( &msg, p ))
|
|
break;
|
|
CL_MoveToOnHandList( p );
|
|
break;
|
|
case t_world:
|
|
ASSERT( 0 );
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( cls.state != ca_disconnected )
|
|
{
|
|
if( !cl.downloadUrl[0] && !MSG_GetNumBytesWritten( &msg ) && CL_PrecacheResources( ))
|
|
{
|
|
CL_RegisterResources( &msg );
|
|
}
|
|
if( cl.downloadUrl[0] && host.downloadcount == 0 && CL_PrecacheResources( ) )
|
|
{
|
|
CL_RegisterResources( &msg );
|
|
}
|
|
|
|
Netchan_CreateFragments( &cls.netchan, &msg );
|
|
Netchan_FragSend( &cls.netchan );
|
|
}
|
|
}
|
|
|
|
int CL_EstimateNeededResources( void )
|
|
{
|
|
resource_t *p;
|
|
int nTotalSize = 0;
|
|
|
|
for( p = cl.resourcesneeded.pNext; p != &cl.resourcesneeded; p = p->pNext )
|
|
{
|
|
switch( p->type )
|
|
{
|
|
case t_sound:
|
|
if( p->szFileName[0] != '*' && !FS_FileExists( va( DEFAULT_SOUNDPATH "%s", p->szFileName ), false ) )
|
|
{
|
|
SetBits( p->ucFlags, RES_WASMISSING );
|
|
nTotalSize += p->nDownloadSize;
|
|
}
|
|
break;
|
|
case t_model:
|
|
if( p->szFileName[0] != '*' && !FS_FileExists( p->szFileName, false ) )
|
|
{
|
|
SetBits( p->ucFlags, RES_WASMISSING );
|
|
nTotalSize += p->nDownloadSize;
|
|
}
|
|
break;
|
|
case t_skin:
|
|
case t_generic:
|
|
case t_eventscript:
|
|
if( !FS_FileExists( p->szFileName, false ) )
|
|
{
|
|
SetBits( p->ucFlags, RES_WASMISSING );
|
|
nTotalSize += p->nDownloadSize;
|
|
}
|
|
break;
|
|
case t_decal:
|
|
if( FBitSet( p->ucFlags, RES_CUSTOM ))
|
|
{
|
|
SetBits( p->ucFlags, RES_WASMISSING );
|
|
nTotalSize += p->nDownloadSize;
|
|
}
|
|
break;
|
|
case t_world:
|
|
ASSERT( 0 );
|
|
break;
|
|
}
|
|
}
|
|
|
|
return nTotalSize;
|
|
}
|
|
|
|
void CL_StartResourceDownloading( const char *pszMessage, qboolean bCustom )
|
|
{
|
|
resourceinfo_t ri;
|
|
|
|
if( COM_CheckString( pszMessage ))
|
|
Con_DPrintf( "%s", pszMessage );
|
|
|
|
cls.dl.nTotalSize = COM_SizeofResourceList( &cl.resourcesneeded, &ri );
|
|
cls.dl.nTotalToTransfer = CL_EstimateNeededResources();
|
|
|
|
if( bCustom )
|
|
{
|
|
cls.dl.custom = true;
|
|
}
|
|
else
|
|
{
|
|
cls.state = ca_validate;
|
|
cls.dl.custom = false;
|
|
}
|
|
|
|
cls.dl.doneregistering = false;
|
|
cls.dl.fLastStatusUpdate = host.realtime;
|
|
cls.dl.nRemainingToTransfer = cls.dl.nTotalToTransfer;
|
|
memset( cls.dl.rgStats, 0, sizeof( cls.dl.rgStats ));
|
|
cls.dl.nCurStat = 0;
|
|
|
|
CL_BatchResourceRequest( !bCustom );
|
|
}
|
|
|
|
customization_t *CL_PlayerHasCustomization( int nPlayerNum, resourcetype_t type )
|
|
{
|
|
customization_t *pList;
|
|
|
|
for( pList = cl.players[nPlayerNum].customdata.pNext; pList; pList = pList->pNext )
|
|
{
|
|
if( pList->resource.type == type )
|
|
return pList;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
void CL_RemoveCustomization( int nPlayerNum, customization_t *pRemove )
|
|
{
|
|
customization_t *pList;
|
|
customization_t *pNext;
|
|
|
|
for( pList = cl.players[nPlayerNum].customdata.pNext; pList; pList = pNext )
|
|
{
|
|
pNext = pList->pNext;
|
|
|
|
if( pRemove != pList )
|
|
continue;
|
|
|
|
if( pList->bInUse && pList->pBuffer )
|
|
Mem_Free( pList->pBuffer );
|
|
|
|
if( pList->bInUse && pList->pInfo )
|
|
{
|
|
if( pList->resource.type == t_decal )
|
|
{
|
|
if( cls.state == ca_active )
|
|
ref.dllFuncs.R_DecalRemoveAll( pList->nUserData1 );
|
|
FS_FreeImage( pList->pInfo );
|
|
}
|
|
}
|
|
|
|
cl.players[nPlayerNum].customdata.pNext = pNext;
|
|
Mem_Free( pList );
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
==================
|
|
CL_ParseCustomization
|
|
|
|
==================
|
|
*/
|
|
void CL_ParseCustomization( sizebuf_t *msg )
|
|
{
|
|
customization_t *pExistingCustomization;
|
|
customization_t *pList;
|
|
qboolean bFound;
|
|
resource_t *pRes;
|
|
int i;
|
|
|
|
i = MSG_ReadByte( msg );
|
|
if( i >= MAX_CLIENTS )
|
|
Host_Error( "Bogus player index during customization parsing.\n" );
|
|
|
|
pRes = Mem_Calloc( cls.mempool, sizeof( resource_t ));
|
|
pRes->type = MSG_ReadByte( msg );
|
|
|
|
Q_strncpy( pRes->szFileName, MSG_ReadString( msg ), sizeof( pRes->szFileName ));
|
|
pRes->nIndex = MSG_ReadShort( msg );
|
|
pRes->nDownloadSize = MSG_ReadLong( msg );
|
|
pRes->ucFlags = MSG_ReadByte( msg ) & ~RES_WASMISSING;
|
|
pRes->pNext = pRes->pPrev = NULL;
|
|
|
|
if( FBitSet( pRes->ucFlags, RES_CUSTOM ))
|
|
MSG_ReadBytes( msg, pRes->rgucMD5_hash, 16 );
|
|
pRes->playernum = i;
|
|
|
|
if( !cl_allow_download.value )
|
|
{
|
|
Con_DPrintf( "Refusing new resource, cl_allow_download set to 0\n" );
|
|
Mem_Free( pRes );
|
|
return;
|
|
}
|
|
|
|
if( cls.state == ca_active && !cl_download_ingame.value )
|
|
{
|
|
Con_DPrintf( "Refusing new resource, cl_download_ingame set to 0\n" );
|
|
Mem_Free( pRes );
|
|
return;
|
|
}
|
|
|
|
pExistingCustomization = CL_PlayerHasCustomization( i, pRes->type );
|
|
|
|
if( pExistingCustomization )
|
|
CL_RemoveCustomization( i, pExistingCustomization );
|
|
bFound = false;
|
|
|
|
for( pList = cl.players[pRes->playernum].customdata.pNext; pList; pList = pList->pNext )
|
|
{
|
|
if( !memcmp( pList->resource.rgucMD5_hash, pRes->rgucMD5_hash, 16 ))
|
|
{
|
|
bFound = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( HPAK_GetDataPointer( CUSTOM_RES_PATH, pRes, NULL, NULL ))
|
|
{
|
|
qboolean bError = false;
|
|
|
|
if( !bFound )
|
|
{
|
|
pList = &cl.players[pRes->playernum].customdata;
|
|
|
|
if( !COM_CreateCustomization( pList, pRes, pRes->playernum, FCUST_FROMHPAK, NULL, NULL ))
|
|
bError = true;
|
|
}
|
|
else
|
|
{
|
|
Con_DPrintf( "Duplicate resource ignored for local client\n" );
|
|
}
|
|
|
|
if( bError ) Con_DPrintf( "Error loading customization\n" );
|
|
Mem_Free( pRes );
|
|
}
|
|
else
|
|
{
|
|
SetBits( pRes->ucFlags, RES_WASMISSING );
|
|
CL_AddToResourceList( pRes, &cl.resourcesneeded );
|
|
Con_Printf( "Requesting %s from server\n", pRes->szFileName );
|
|
CL_StartResourceDownloading( "Custom resource propagation...\n", true );
|
|
}
|
|
}
|
|
|
|
/*
|
|
==================
|
|
CL_ParseResourceRequest
|
|
|
|
==================
|
|
*/
|
|
void CL_ParseResourceRequest( sizebuf_t *msg )
|
|
{
|
|
byte buffer[MAX_INIT_MSG];
|
|
int i, arg, nStartIndex;
|
|
sizebuf_t sbuf;
|
|
|
|
MSG_Init( &sbuf, "ResourceBlock", buffer, sizeof( buffer ));
|
|
|
|
arg = MSG_ReadLong( msg );
|
|
nStartIndex = MSG_ReadLong( msg );
|
|
|
|
if( cl.servercount != arg )
|
|
return;
|
|
|
|
if( nStartIndex < 0 && nStartIndex > cl.num_resources )
|
|
return;
|
|
|
|
MSG_BeginClientCmd( &sbuf, clc_resourcelist );
|
|
MSG_WriteShort( &sbuf, cl.num_resources );
|
|
|
|
for( i = nStartIndex; i < cl.num_resources; i++ )
|
|
{
|
|
MSG_WriteString( &sbuf, cl.resourcelist[i].szFileName );
|
|
MSG_WriteByte( &sbuf, cl.resourcelist[i].type );
|
|
MSG_WriteShort( &sbuf, cl.resourcelist[i].nIndex );
|
|
MSG_WriteLong( &sbuf, cl.resourcelist[i].nDownloadSize );
|
|
MSG_WriteByte( &sbuf, cl.resourcelist[i].ucFlags );
|
|
|
|
if( FBitSet( cl.resourcelist[i].ucFlags, RES_CUSTOM ))
|
|
MSG_WriteBytes( &sbuf, cl.resourcelist[i].rgucMD5_hash, 16 );
|
|
}
|
|
|
|
if( MSG_GetNumBytesWritten( &sbuf ) > 0 )
|
|
{
|
|
Netchan_CreateFragments( &cls.netchan, &sbuf );
|
|
Netchan_FragSend( &cls.netchan );
|
|
}
|
|
}
|
|
|
|
/*
|
|
==================
|
|
CL_CreateCustomizationList
|
|
|
|
loading custom decal for self
|
|
==================
|
|
*/
|
|
void CL_CreateCustomizationList( void )
|
|
{
|
|
resource_t *pResource;
|
|
player_info_t *pPlayer;
|
|
int i;
|
|
|
|
pPlayer = &cl.players[cl.playernum];
|
|
pPlayer->customdata.pNext = NULL;
|
|
|
|
for( i = 0; i < cl.num_resources; i++ )
|
|
{
|
|
pResource = &cl.resourcelist[i];
|
|
|
|
if( !COM_CreateCustomization( &pPlayer->customdata, pResource, cl.playernum, 0, NULL, NULL ))
|
|
Con_Printf( "problem with client customization %s, ignoring...", pResource->szFileName );
|
|
}
|
|
}
|
|
|
|
/*
|
|
==================
|
|
CL_ParseFileTransferFailed
|
|
|
|
==================
|
|
*/
|
|
void CL_ParseFileTransferFailed( sizebuf_t *msg )
|
|
{
|
|
const char *name = MSG_ReadString( msg );
|
|
|
|
if( !cls.demoplayback )
|
|
CL_ProcessFile( false, name );
|
|
}
|
|
|
|
/*
|
|
=====================================================================
|
|
|
|
SERVER CONNECTING MESSAGES
|
|
|
|
=====================================================================
|
|
*/
|
|
/*
|
|
==================
|
|
CL_ParseServerData
|
|
==================
|
|
*/
|
|
void CL_ParseServerData( sizebuf_t *msg )
|
|
{
|
|
char gamefolder[MAX_QPATH];
|
|
qboolean background;
|
|
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 ();
|
|
cls.state = ca_connected;
|
|
|
|
// parse protocol version number
|
|
i = MSG_ReadLong( msg );
|
|
|
|
if( i != PROTOCOL_VERSION )
|
|
Host_Error( "Server use invalid protocol (%i should be %i)\n", i, PROTOCOL_VERSION );
|
|
|
|
cl.servercount = MSG_ReadLong( msg );
|
|
cl.checksum = MSG_ReadLong( msg );
|
|
cl.playernum = MSG_ReadByte( msg );
|
|
cl.maxclients = MSG_ReadByte( msg );
|
|
clgame.maxEntities = MSG_ReadWord( msg );
|
|
clgame.maxEntities = bound( MIN_EDICTS, clgame.maxEntities, MAX_EDICTS );
|
|
clgame.maxModels = MSG_ReadWord( msg );
|
|
Q_strncpy( clgame.mapname, MSG_ReadString( msg ), MAX_STRING );
|
|
Q_strncpy( clgame.maptitle, MSG_ReadString( msg ), MAX_STRING );
|
|
background = MSG_ReadOneBit( msg );
|
|
Q_strncpy( gamefolder, MSG_ReadString( msg ), MAX_QPATH );
|
|
host.features = (uint)MSG_ReadLong( msg );
|
|
|
|
// receive the player hulls
|
|
for( i = 0; i < MAX_MAP_HULLS * 3; i++ )
|
|
{
|
|
host.player_mins[i/3][i%3] = MSG_ReadChar( msg );
|
|
host.player_maxs[i/3][i%3] = MSG_ReadChar( msg );
|
|
}
|
|
|
|
if( clgame.maxModels > MAX_MODELS )
|
|
Con_Printf( S_WARN "server model limit is above client model limit %i > %i\n", clgame.maxModels, MAX_MODELS );
|
|
|
|
// 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" );
|
|
|
|
// set the background state
|
|
if( cls.demoplayback && ( cls.demonum != -1 ))
|
|
{
|
|
// re-init mouse
|
|
host.mouse_visible = false;
|
|
cl.background = true;
|
|
}
|
|
else cl.background = background;
|
|
|
|
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 );
|
|
|
|
if( !cls.changelevel )
|
|
{
|
|
// continue playing if we are changing level
|
|
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
|
|
|
|
// 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)
|
|
}
|
|
|
|
for( i = 0; i < MAX_CLIENTS; i++ )
|
|
COM_ClearCustomizationList( &cl.players[i].customdata, true );
|
|
CL_CreateCustomizationList();
|
|
|
|
// request resources from server
|
|
CL_ServerCommand( true, "sendres %i\n", cl.servercount );
|
|
|
|
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;
|
|
}
|
|
|
|
/*
|
|
===================
|
|
CL_ParseClientData
|
|
===================
|
|
*/
|
|
void CL_ParseClientData( sizebuf_t *msg )
|
|
{
|
|
float parsecounttime;
|
|
int i, j, command_ack;
|
|
clientdata_t *from_cd, *to_cd;
|
|
weapon_data_t *from_wd, *to_wd;
|
|
weapon_data_t nullwd[64];
|
|
clientdata_t nullcd;
|
|
frame_t *frame;
|
|
int idx;
|
|
|
|
// This is the last movement that the server ack'd
|
|
command_ack = cls.netchan.incoming_acknowledged;
|
|
|
|
// this is the frame update that this message corresponds to
|
|
i = cls.netchan.incoming_sequence;
|
|
|
|
// did we drop some frames?
|
|
if( i > cl.last_incoming_sequence + 1 )
|
|
{
|
|
// mark as dropped
|
|
for( j = cl.last_incoming_sequence + 1; j < i; j++ )
|
|
{
|
|
if( cl.frames[j & CL_UPDATE_MASK].receivedtime >= 0.0 )
|
|
{
|
|
cl.frames[j & CL_UPDATE_MASK].receivedtime = -1.0f;
|
|
cl.frames[j & CL_UPDATE_MASK].latency = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
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 ));
|
|
|
|
// send time for that frame.
|
|
parsecounttime = cl.commands[command_ack & CL_UPDATE_MASK].senttime;
|
|
|
|
// current time that we got a response to the command packet.
|
|
cl.commands[command_ack & CL_UPDATE_MASK].receivedtime = host.realtime;
|
|
|
|
if( cl.last_command_ack != -1 )
|
|
{
|
|
int last_predicted;
|
|
clientdata_t *pcd, *ppcd;
|
|
entity_state_t *ps, *pps;
|
|
weapon_data_t *wd, *pwd;
|
|
|
|
if( !cls.spectator )
|
|
{
|
|
last_predicted = ( cl.last_incoming_sequence + ( command_ack - cl.last_command_ack )) & CL_UPDATE_MASK;
|
|
|
|
pps = &cl.predicted_frames[last_predicted].playerstate;
|
|
pwd = cl.predicted_frames[last_predicted].weapondata;
|
|
ppcd = &cl.predicted_frames[last_predicted].client;
|
|
|
|
ps = &frame->playerstate[cl.playernum];
|
|
wd = frame->weapondata;
|
|
pcd = &frame->clientdata;
|
|
}
|
|
else
|
|
{
|
|
ps = &cls.spectator_state.playerstate;
|
|
pps = &cls.spectator_state.playerstate;
|
|
pcd = &cls.spectator_state.client;
|
|
ppcd = &cls.spectator_state.client;
|
|
wd = cls.spectator_state.weapondata;
|
|
pwd = cls.spectator_state.weapondata;
|
|
}
|
|
|
|
clgame.dllFuncs.pfnTxferPredictionData( ps, pps, pcd, ppcd, wd, pwd );
|
|
}
|
|
|
|
// do this after all packets read for this frame?
|
|
cl.last_command_ack = cls.netchan.incoming_acknowledged;
|
|
cl.last_incoming_sequence = cls.netchan.incoming_sequence;
|
|
|
|
if( !cls.demoplayback )
|
|
{
|
|
// calculate latency of this frame.
|
|
// sent time is set when usercmd is sent to server in CL_Move
|
|
// this is the # of seconds the round trip took.
|
|
float latency = host.realtime - parsecounttime;
|
|
|
|
// fill into frame latency
|
|
frame->latency = latency;
|
|
|
|
// negative latency makes no sense. Huge latency is a problem.
|
|
if( latency >= 0.0f && latency <= 2.0f )
|
|
{
|
|
// drift the average latency towards the observed latency
|
|
// if round trip was fastest so far, just use that for latency value
|
|
// otherwise, move in 1 ms steps toward observed channel latency.
|
|
if( latency < cls.latency )
|
|
cls.latency = latency;
|
|
else cls.latency += 0.001f; // drift up, so corrections are needed
|
|
}
|
|
}
|
|
else
|
|
{
|
|
frame->latency = 0.0f;
|
|
}
|
|
|
|
// clientdata for spectators ends here
|
|
if( cls.spectator )
|
|
{
|
|
cl.local.health = 1;
|
|
return;
|
|
}
|
|
|
|
to_cd = &frame->clientdata;
|
|
to_wd = frame->weapondata;
|
|
|
|
// clear to old value before delta parsing
|
|
if( MSG_ReadOneBit( msg ))
|
|
{
|
|
int delta_sequence = MSG_ReadByte( msg );
|
|
|
|
from_cd = &cl.frames[delta_sequence & CL_UPDATE_MASK].clientdata;
|
|
from_wd = cl.frames[delta_sequence & CL_UPDATE_MASK].weapondata;
|
|
}
|
|
else
|
|
{
|
|
memset( &nullcd, 0, sizeof( nullcd ));
|
|
memset( nullwd, 0, sizeof( nullwd ));
|
|
from_cd = &nullcd;
|
|
from_wd = nullwd;
|
|
}
|
|
|
|
MSG_ReadClientData( msg, from_cd, to_cd, cl.mtime[0] );
|
|
|
|
for( i = 0; i < 64; i++ )
|
|
{
|
|
// check for end of weapondata (and clientdata_t message)
|
|
if( !MSG_ReadOneBit( msg )) break;
|
|
|
|
// read the weapon idx
|
|
idx = MSG_ReadUBitLong( msg, cls.legacymode ? MAX_LEGACY_WEAPON_BITS : MAX_WEAPON_BITS );
|
|
|
|
MSG_ReadWeaponData( msg, &from_wd[idx], &to_wd[idx], cl.mtime[0] );
|
|
}
|
|
|
|
// make a local copy of physinfo
|
|
Q_strncpy( cls.physinfo, frame->clientdata.physinfo, sizeof( cls.physinfo ));
|
|
|
|
cl.local.maxspeed = frame->clientdata.maxspeed;
|
|
cl.local.pushmsec = frame->clientdata.pushmsec;
|
|
cl.local.weapons = frame->clientdata.weapons;
|
|
cl.local.health = frame->clientdata.health;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
CL_ParseBaseline
|
|
==================
|
|
*/
|
|
void CL_ParseBaseline( sizebuf_t *msg )
|
|
{
|
|
int i, newnum;
|
|
entity_state_t nullstate;
|
|
qboolean player;
|
|
cl_entity_t *ent;
|
|
|
|
Delta_InitClient (); // finalize client delta's
|
|
|
|
memset( &nullstate, 0, sizeof( nullstate ));
|
|
|
|
while( 1 )
|
|
{
|
|
newnum = MSG_ReadUBitLong( msg, MAX_ENTITY_BITS );
|
|
if( newnum == LAST_EDICT ) break; // end of baselines
|
|
player = CL_IsPlayerIndex( newnum );
|
|
|
|
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;
|
|
|
|
MSG_ReadDeltaEntity( msg, &ent->prevstate, &ent->baseline, newnum, player, 1.0f );
|
|
}
|
|
|
|
cl.instanced_baseline_count = MSG_ReadUBitLong( msg, 6 );
|
|
|
|
for( i = 0; i < cl.instanced_baseline_count; i++ )
|
|
{
|
|
newnum = MSG_ReadUBitLong( msg, MAX_ENTITY_BITS );
|
|
MSG_ReadDeltaEntity( msg, &nullstate, &cl.instanced_baseline[i], newnum, false, 1.0f );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
CL_ParseLightStyle
|
|
================
|
|
*/
|
|
void CL_ParseLightStyle( sizebuf_t *msg )
|
|
{
|
|
int style;
|
|
const char *s;
|
|
float f;
|
|
|
|
style = MSG_ReadByte( msg );
|
|
s = MSG_ReadString( msg );
|
|
f = MSG_ReadFloat( msg );
|
|
|
|
CL_SetLightstyle( style, s, f );
|
|
}
|
|
|
|
/*
|
|
================
|
|
CL_ParseSetAngle
|
|
|
|
set the view angle to this absolute value
|
|
================
|
|
*/
|
|
void CL_ParseSetAngle( sizebuf_t *msg )
|
|
{
|
|
cl.viewangles[0] = MSG_ReadBitAngle( msg, 16 );
|
|
cl.viewangles[1] = MSG_ReadBitAngle( msg, 16 );
|
|
cl.viewangles[2] = MSG_ReadBitAngle( msg, 16 );
|
|
}
|
|
|
|
/*
|
|
================
|
|
CL_ParseAddAngle
|
|
|
|
add the view angle yaw
|
|
================
|
|
*/
|
|
void CL_ParseAddAngle( sizebuf_t *msg )
|
|
{
|
|
pred_viewangle_t *a;
|
|
float delta_yaw;
|
|
|
|
delta_yaw = MSG_ReadBitAngle( msg, 16 );
|
|
#if 0
|
|
cl.viewangles[YAW] += delta_yaw;
|
|
return;
|
|
#endif
|
|
// update running counter
|
|
cl.addangletotal += delta_yaw;
|
|
|
|
// select entry into circular buffer
|
|
cl.angle_position = (cl.angle_position + 1) & ANGLE_MASK;
|
|
a = &cl.predicted_angle[cl.angle_position];
|
|
|
|
// record update
|
|
a->starttime = cl.mtime[0];
|
|
a->total = cl.addangletotal;
|
|
}
|
|
|
|
/*
|
|
================
|
|
CL_ParseCrosshairAngle
|
|
|
|
offset crosshair angles
|
|
================
|
|
*/
|
|
void CL_ParseCrosshairAngle( sizebuf_t *msg )
|
|
{
|
|
cl.crosshairangle[0] = MSG_ReadChar( msg ) * 0.2f;
|
|
cl.crosshairangle[1] = MSG_ReadChar( msg ) * 0.2f;
|
|
cl.crosshairangle[2] = 0.0f; // not used for screen space
|
|
}
|
|
|
|
/*
|
|
================
|
|
CL_ParseRestore
|
|
|
|
reading decals, etc.
|
|
================
|
|
*/
|
|
void CL_ParseRestore( sizebuf_t *msg )
|
|
{
|
|
string filename;
|
|
int i, mapCount;
|
|
char *pMapName;
|
|
|
|
// mapname.HL2
|
|
Q_strncpy( filename, MSG_ReadString( msg ), sizeof( filename ));
|
|
mapCount = MSG_ReadByte( msg );
|
|
|
|
// g-cont. acutally in Xash3D this does nothing.
|
|
// decals already restored on a server, and correctly transferred through levels
|
|
// but i'm leave this message for backward compatibility
|
|
for( i = 0; i < mapCount; i++ )
|
|
{
|
|
pMapName = MSG_ReadString( msg );
|
|
Con_Printf( "Loading decals from %s\n", pMapName );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
CL_RegisterUserMessage
|
|
|
|
register new user message or update existing
|
|
================
|
|
*/
|
|
void CL_RegisterUserMessage( sizebuf_t *msg )
|
|
{
|
|
char *pszName;
|
|
int svc_num, size, bits;
|
|
|
|
svc_num = MSG_ReadByte( msg );
|
|
|
|
if( cls.legacymode )
|
|
{
|
|
size = MSG_ReadByte( msg );
|
|
bits = 8;
|
|
}
|
|
else
|
|
{
|
|
size = MSG_ReadWord( msg );
|
|
bits = 16;
|
|
}
|
|
|
|
pszName = MSG_ReadString( msg );
|
|
|
|
// important stuff
|
|
if( size == ( BIT( bits ) - 1 ) )
|
|
size = -1;
|
|
svc_num = bound( 0, svc_num, 255 );
|
|
|
|
CL_LinkUserMessage( pszName, svc_num, size );
|
|
}
|
|
|
|
/*
|
|
================
|
|
CL_RegisterUserMessage
|
|
|
|
register new user message or update existing
|
|
================
|
|
*/
|
|
/*
|
|
void CL_LegacyRegisterUserMessage( sizebuf_t *msg )
|
|
{
|
|
char *pszName;
|
|
int svc_num, size;
|
|
|
|
svc_num = MSG_ReadByte( msg );
|
|
size = MSG_ReadByte( msg );
|
|
pszName = MSG_ReadString( msg );
|
|
|
|
// important stuff
|
|
if( size == 0xFF ) size = -1;
|
|
svc_num = bound( 0, svc_num, 255 );
|
|
|
|
CL_LinkUserMessage( pszName, svc_num, size );
|
|
}
|
|
*/
|
|
/*
|
|
================
|
|
CL_UpdateUserinfo
|
|
|
|
collect userinfo from all players
|
|
================
|
|
*/
|
|
void CL_UpdateUserinfo( sizebuf_t *msg )
|
|
{
|
|
int slot, id;
|
|
qboolean active;
|
|
player_info_t *player;
|
|
|
|
slot = MSG_ReadUBitLong( msg, MAX_CLIENT_BITS );
|
|
|
|
if( slot >= MAX_CLIENTS )
|
|
Host_Error( "CL_ParseServerMessage: svc_updateuserinfo >= MAX_CLIENTS\n" );
|
|
|
|
id = MSG_ReadLong( msg ); // unique user ID
|
|
player = &cl.players[slot];
|
|
active = MSG_ReadOneBit( msg ) ? true : false;
|
|
|
|
if( active )
|
|
{
|
|
Q_strncpy( player->userinfo, MSG_ReadString( msg ), sizeof( player->userinfo ));
|
|
Q_strncpy( player->name, Info_ValueForKey( player->userinfo, "name" ), sizeof( player->name ));
|
|
Q_strncpy( player->model, Info_ValueForKey( player->userinfo, "model" ), sizeof( player->model ));
|
|
player->topcolor = Q_atoi( Info_ValueForKey( player->userinfo, "topcolor" ));
|
|
player->bottomcolor = Q_atoi( Info_ValueForKey( player->userinfo, "bottomcolor" ));
|
|
player->spectator = Q_atoi( Info_ValueForKey( player->userinfo, "*hltv" ));
|
|
MSG_ReadBytes( msg, player->hashedcdkey, sizeof( player->hashedcdkey ));
|
|
|
|
if( slot == cl.playernum ) memcpy( &gameui.playerinfo, player, sizeof( player_info_t ));
|
|
}
|
|
else memset( player, 0, sizeof( *player ));
|
|
}
|
|
|
|
/*
|
|
==============
|
|
CL_ParseResource
|
|
|
|
downloading and precache resource in-game
|
|
==============
|
|
*/
|
|
void CL_ParseResource( sizebuf_t *msg )
|
|
{
|
|
resource_t *pResource;
|
|
|
|
pResource = Mem_Calloc( cls.mempool, sizeof( resource_t ));
|
|
pResource->type = MSG_ReadUBitLong( msg, 4 );
|
|
|
|
Q_strncpy( pResource->szFileName, MSG_ReadString( msg ), sizeof( pResource->szFileName ));
|
|
pResource->nIndex = MSG_ReadUBitLong( msg, MAX_MODEL_BITS );
|
|
pResource->nDownloadSize = MSG_ReadSBitLong( msg, 24 );
|
|
pResource->ucFlags = MSG_ReadUBitLong( msg, 3 ) & ~RES_WASMISSING;
|
|
|
|
if( FBitSet( pResource->ucFlags, RES_CUSTOM ))
|
|
MSG_ReadBytes( msg, pResource->rgucMD5_hash, sizeof( pResource->rgucMD5_hash ));
|
|
|
|
if( MSG_ReadOneBit( msg ))
|
|
MSG_ReadBytes( msg, pResource->rguc_reserved, sizeof( pResource->rguc_reserved ));
|
|
|
|
if( pResource->type == t_sound && pResource->nIndex > MAX_SOUNDS )
|
|
{
|
|
Mem_Free( pResource );
|
|
Host_Error( "bad sound index\n" );
|
|
}
|
|
|
|
if( pResource->type == t_model && pResource->nIndex > MAX_MODELS )
|
|
{
|
|
Mem_Free( pResource );
|
|
Host_Error( "bad model index\n" );
|
|
}
|
|
|
|
if( pResource->type == t_eventscript && pResource->nIndex > MAX_EVENTS )
|
|
{
|
|
Mem_Free( pResource );
|
|
Host_Error( "bad event index\n" );
|
|
}
|
|
|
|
if( pResource->type == t_generic && pResource->nIndex > MAX_CUSTOM )
|
|
{
|
|
Mem_Free( pResource );
|
|
Host_Error( "bad file index\n" );
|
|
}
|
|
|
|
if( pResource->type == t_decal && pResource->nIndex > MAX_DECALS )
|
|
{
|
|
Mem_Free( pResource );
|
|
Host_Error( "bad decal index\n" );
|
|
}
|
|
|
|
CL_AddToResourceList( pResource, &cl.resourcesneeded );
|
|
}
|
|
|
|
/*
|
|
================
|
|
CL_UpdateUserPings
|
|
|
|
collect pings and packet lossage from clients
|
|
================
|
|
*/
|
|
void CL_UpdateUserPings( sizebuf_t *msg )
|
|
{
|
|
int i, slot;
|
|
player_info_t *player;
|
|
|
|
for( i = 0; i < MAX_CLIENTS; i++ )
|
|
{
|
|
if( !MSG_ReadOneBit( msg )) break; // end of message
|
|
|
|
slot = MSG_ReadUBitLong( msg, MAX_CLIENT_BITS );
|
|
|
|
if( slot >= MAX_CLIENTS )
|
|
Host_Error( "CL_ParseServerMessage: svc_pings > MAX_CLIENTS\n" );
|
|
|
|
player = &cl.players[slot];
|
|
player->ping = MSG_ReadUBitLong( msg, 12 );
|
|
player->packet_loss = MSG_ReadUBitLong( msg, 7 );
|
|
}
|
|
}
|
|
|
|
void CL_SendConsistencyInfo( sizebuf_t *msg )
|
|
{
|
|
qboolean user_changed_diskfile;
|
|
vec3_t mins, maxs;
|
|
string filename;
|
|
CRC32_t crcFile;
|
|
byte md5[16];
|
|
consistency_t *pc;
|
|
int i;
|
|
|
|
if( !cl.need_force_consistency_response )
|
|
return;
|
|
cl.need_force_consistency_response = false;
|
|
|
|
MSG_BeginClientCmd( msg, clc_fileconsistency );
|
|
|
|
for( i = 0; i < cl.num_consistency; i++ )
|
|
{
|
|
pc = &cl.consistency_list[i];
|
|
|
|
user_changed_diskfile = false;
|
|
MSG_WriteOneBit( msg, 1 );
|
|
MSG_WriteUBitLong( msg, pc->orig_index, MAX_MODEL_BITS );
|
|
|
|
if( pc->issound )
|
|
Q_snprintf( filename, sizeof( filename ), DEFAULT_SOUNDPATH "%s", pc->filename );
|
|
else Q_strncpy( filename, pc->filename, sizeof( filename ));
|
|
|
|
if( Q_strstr( filename, "models/" ))
|
|
{
|
|
CRC32_Init( &crcFile );
|
|
CRC32_File( &crcFile, filename );
|
|
crcFile = CRC32_Final( crcFile );
|
|
user_changed_diskfile = !Mod_ValidateCRC( filename, crcFile );
|
|
}
|
|
|
|
switch( pc->check_type )
|
|
{
|
|
case force_exactfile:
|
|
MD5_HashFile( md5, filename, NULL );
|
|
pc->value = *(int *)md5;
|
|
|
|
if( user_changed_diskfile )
|
|
MSG_WriteUBitLong( msg, 0, 32 );
|
|
else MSG_WriteUBitLong( msg, pc->value, 32 );
|
|
break;
|
|
case force_model_samebounds:
|
|
case force_model_specifybounds:
|
|
if( !Mod_GetStudioBounds( filename, mins, maxs ))
|
|
Host_Error( "unable to find %s\n", filename );
|
|
if( user_changed_diskfile )
|
|
ClearBounds( maxs, mins ); // g-cont. especially swapped
|
|
MSG_WriteBytes( msg, mins, 12 );
|
|
MSG_WriteBytes( msg, maxs, 12 );
|
|
break;
|
|
default:
|
|
Host_Error( "Unknown consistency type %i\n", pc->check_type );
|
|
break;
|
|
}
|
|
}
|
|
|
|
MSG_WriteOneBit( msg, 0 );
|
|
}
|
|
|
|
/*
|
|
==================
|
|
CL_RegisterResources
|
|
|
|
Clean up and move to next part of sequence.
|
|
==================
|
|
*/
|
|
void CL_RegisterResources( sizebuf_t *msg )
|
|
{
|
|
model_t *mod;
|
|
int i;
|
|
|
|
if( cls.dl.custom || ( cls.signon == SIGNONS && cls.state == ca_active ) )
|
|
{
|
|
cls.dl.custom = false;
|
|
return;
|
|
}
|
|
|
|
if( !cls.demoplayback )
|
|
CL_SendConsistencyInfo( msg );
|
|
|
|
// All done precaching.
|
|
cl.worldmodel = CL_ModelHandle( 1 ); // get world pointer
|
|
|
|
if( cl.worldmodel && cl.maxclients > 0 )
|
|
{
|
|
ASSERT( clgame.entities != NULL );
|
|
clgame.entities->model = cl.worldmodel;
|
|
|
|
if( !cl.video_prepped && !cl.audio_prepped )
|
|
{
|
|
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;
|
|
|
|
CL_ClearWorld ();
|
|
|
|
// update the ref state.
|
|
R_UpdateRefState ();
|
|
|
|
// 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( msg, clc_stringcmd );
|
|
MSG_WriteString( msg, va( "spawn %i", cl.servercount ));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Con_Printf( S_ERROR "client world model is NULL\n" );
|
|
CL_Disconnect();
|
|
}
|
|
}
|
|
|
|
void CL_ParseConsistencyInfo( sizebuf_t *msg )
|
|
{
|
|
int lastcheck;
|
|
int delta;
|
|
int i;
|
|
int isdelta;
|
|
resource_t *pResource;
|
|
resource_t *skip_crc_change;
|
|
int skip;
|
|
consistency_t *pc;
|
|
byte nullbuffer[32];
|
|
|
|
memset( nullbuffer, 0, 32 );
|
|
|
|
cl.need_force_consistency_response = MSG_ReadOneBit( msg );
|
|
pResource = cl.resourcesneeded.pNext;
|
|
|
|
if( !cl.need_force_consistency_response )
|
|
return;
|
|
|
|
skip_crc_change = NULL;
|
|
lastcheck = 0;
|
|
|
|
while( MSG_ReadOneBit( msg ))
|
|
{
|
|
isdelta = MSG_ReadOneBit( msg );
|
|
|
|
if( isdelta ) delta = MSG_ReadUBitLong( msg, 5 ) + lastcheck;
|
|
else delta = MSG_ReadUBitLong( msg, MAX_MODEL_BITS );
|
|
|
|
skip = delta - lastcheck;
|
|
|
|
for( i = 0; i < skip; i++ )
|
|
{
|
|
if( pResource != skip_crc_change && Q_strstr( pResource->szFileName, "models/" ))
|
|
Mod_NeedCRC( pResource->szFileName, false );
|
|
pResource = pResource->pNext;
|
|
}
|
|
|
|
if( cl.num_consistency >= MAX_MODELS )
|
|
Host_Error( "CL_CheckConsistency: MAX_MODELS limit exceeded (%d)\n", MAX_MODELS );
|
|
|
|
pc = &cl.consistency_list[cl.num_consistency];
|
|
cl.num_consistency++;
|
|
|
|
memset( pc, 0, sizeof( consistency_t ));
|
|
pc->filename = pResource->szFileName;
|
|
pc->issound = (pResource->type == t_sound);
|
|
pc->orig_index = delta;
|
|
pc->value = 0;
|
|
|
|
if( pResource->type == t_model && memcmp( nullbuffer, pResource->rguc_reserved, 32 ))
|
|
pc->check_type = pResource->rguc_reserved[0];
|
|
|
|
skip_crc_change = pResource;
|
|
lastcheck = delta;
|
|
}
|
|
}
|
|
|
|
/*
|
|
==============
|
|
CL_ParseResourceList
|
|
|
|
==============
|
|
*/
|
|
void CL_ParseResourceList( sizebuf_t *msg )
|
|
{
|
|
resource_t *pResource;
|
|
int i, total;
|
|
|
|
total = MSG_ReadUBitLong( msg, MAX_RESOURCE_BITS );
|
|
|
|
for( i = 0; i < total; i++ )
|
|
{
|
|
pResource = Mem_Calloc( cls.mempool, sizeof( resource_t ));
|
|
pResource->type = MSG_ReadUBitLong( msg, 4 );
|
|
|
|
Q_strncpy( pResource->szFileName, MSG_ReadString( msg ), sizeof( pResource->szFileName ));
|
|
pResource->nIndex = MSG_ReadUBitLong( msg, MAX_MODEL_BITS );
|
|
pResource->nDownloadSize = MSG_ReadSBitLong( msg, 24 );
|
|
pResource->ucFlags = MSG_ReadUBitLong( msg, 3 ) & ~RES_WASMISSING;
|
|
|
|
if( FBitSet( pResource->ucFlags, RES_CUSTOM ))
|
|
MSG_ReadBytes( msg, pResource->rgucMD5_hash, sizeof( pResource->rgucMD5_hash ));
|
|
|
|
if( MSG_ReadOneBit( msg ))
|
|
MSG_ReadBytes( msg, pResource->rguc_reserved, sizeof( pResource->rguc_reserved ));
|
|
|
|
CL_AddToResourceList( pResource, &cl.resourcesneeded );
|
|
}
|
|
|
|
CL_ParseConsistencyInfo( msg );
|
|
|
|
CL_StartResourceDownloading( "Verifying and downloading resources...\n", false );
|
|
}
|
|
|
|
/*
|
|
==================
|
|
CL_ParseVoiceInit
|
|
|
|
==================
|
|
*/
|
|
void CL_ParseVoiceInit( sizebuf_t *msg )
|
|
{
|
|
// TODO: ???
|
|
}
|
|
|
|
/*
|
|
==================
|
|
CL_ParseVoiceData
|
|
|
|
==================
|
|
*/
|
|
void CL_ParseVoiceData( sizebuf_t *msg )
|
|
{
|
|
// TODO: ???
|
|
}
|
|
|
|
/*
|
|
==================
|
|
CL_ParseResLocation
|
|
|
|
==================
|
|
*/
|
|
void CL_ParseResLocation( sizebuf_t *msg )
|
|
{
|
|
char *data = MSG_ReadString( msg );
|
|
char token[256];
|
|
|
|
if( Q_strlen( data ) > 256 )
|
|
{
|
|
Con_Printf( S_ERROR "Resource location too long!\n" );
|
|
return;
|
|
}
|
|
|
|
while( ( data = COM_ParseFile( data, token ) ) )
|
|
{
|
|
Con_Reportf( "Adding %s as download location\n", token );
|
|
|
|
if( !cl.downloadUrl[0] )
|
|
Q_strncpy( cl.downloadUrl, token, sizeof( token ) );
|
|
|
|
HTTP_AddCustomServer( token );
|
|
}
|
|
}
|
|
|
|
/*
|
|
==============
|
|
CL_ParseHLTV
|
|
|
|
spectator message (hltv)
|
|
sended from game.dll
|
|
==============
|
|
*/
|
|
void CL_ParseHLTV( sizebuf_t *msg )
|
|
{
|
|
switch( MSG_ReadByte( msg ))
|
|
{
|
|
case HLTV_ACTIVE:
|
|
cl.proxy_redirect = true;
|
|
cls.spectator = true;
|
|
break;
|
|
case HLTV_STATUS:
|
|
MSG_ReadLong( msg );
|
|
MSG_ReadShort( msg );
|
|
MSG_ReadWord( msg );
|
|
MSG_ReadLong( msg );
|
|
MSG_ReadLong( msg );
|
|
MSG_ReadWord( msg );
|
|
break;
|
|
case HLTV_LISTEN:
|
|
cls.signon = SIGNONS;
|
|
NET_StringToAdr( MSG_ReadString( msg ), &cls.hltv_listen_address );
|
|
// NET_JoinGroup( cls.netchan.sock, cls.hltv_listen_address );
|
|
SCR_EndLoadingPlaque();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
==============
|
|
CL_ParseDirector
|
|
|
|
spectator message (director)
|
|
sended from game.dll
|
|
==============
|
|
*/
|
|
void CL_ParseDirector( sizebuf_t *msg )
|
|
{
|
|
int iSize = MSG_ReadByte( msg );
|
|
byte pbuf[256];
|
|
|
|
// parse user message into buffer
|
|
MSG_ReadBytes( msg, pbuf, iSize );
|
|
clgame.dllFuncs.pfnDirectorMessage( iSize, pbuf );
|
|
}
|
|
|
|
/*
|
|
==============
|
|
CL_ParseScreenShake
|
|
|
|
Set screen shake
|
|
==============
|
|
*/
|
|
void CL_ParseScreenShake( sizebuf_t *msg )
|
|
{
|
|
clgame.shake.amplitude = (float)(word)MSG_ReadShort( msg ) * (1.0f / (float)(1<<12));
|
|
clgame.shake.duration = (float)(word)MSG_ReadShort( msg ) * (1.0f / (float)(1<<12));
|
|
clgame.shake.frequency = (float)(word)MSG_ReadShort( msg ) * (1.0f / (float)(1<<8));
|
|
clgame.shake.time = cl.time + max( clgame.shake.duration, 0.01f );
|
|
clgame.shake.next_shake = 0.0f; // apply immediately
|
|
}
|
|
|
|
/*
|
|
==============
|
|
CL_ParseScreenFade
|
|
|
|
Set screen fade
|
|
==============
|
|
*/
|
|
void CL_ParseScreenFade( sizebuf_t *msg )
|
|
{
|
|
float duration, holdTime;
|
|
screenfade_t *sf = &clgame.fade;
|
|
float flScale;
|
|
|
|
duration = (float)MSG_ReadWord( msg );
|
|
holdTime = (float)MSG_ReadWord( msg );
|
|
sf->fadeFlags = MSG_ReadShort( msg );
|
|
flScale = ( sf->fadeFlags & FFADE_LONGFADE ) ? (1.0f / 256.0f) : (1.0f / 4096.0f);
|
|
|
|
sf->fader = MSG_ReadByte( msg );
|
|
sf->fadeg = MSG_ReadByte( msg );
|
|
sf->fadeb = MSG_ReadByte( msg );
|
|
sf->fadealpha = MSG_ReadByte( msg );
|
|
sf->fadeSpeed = 0.0f;
|
|
sf->fadeEnd = duration * flScale;
|
|
sf->fadeReset = holdTime * flScale;
|
|
|
|
// calc fade speed
|
|
if( duration > 0 )
|
|
{
|
|
if( sf->fadeFlags & FFADE_OUT )
|
|
{
|
|
if( sf->fadeEnd )
|
|
{
|
|
sf->fadeSpeed = -(float)sf->fadealpha / sf->fadeEnd;
|
|
}
|
|
|
|
sf->fadeEnd += cl.time;
|
|
sf->fadeReset += sf->fadeEnd;
|
|
}
|
|
else
|
|
{
|
|
if( sf->fadeEnd )
|
|
{
|
|
sf->fadeSpeed = (float)sf->fadealpha / sf->fadeEnd;
|
|
}
|
|
|
|
sf->fadeReset += cl.time;
|
|
sf->fadeEnd += sf->fadeReset;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
==============
|
|
CL_ParseCvarValue
|
|
|
|
Find the client cvar value
|
|
and sent it back to the server
|
|
==============
|
|
*/
|
|
void CL_ParseCvarValue( sizebuf_t *msg )
|
|
{
|
|
const char *cvarName = MSG_ReadString( msg );
|
|
convar_t *cvar = Cvar_FindVar( cvarName );
|
|
|
|
// build the answer
|
|
MSG_BeginClientCmd( &cls.netchan.message, clc_requestcvarvalue );
|
|
MSG_WriteString( &cls.netchan.message, cvar ? cvar->string : "Not Found" );
|
|
}
|
|
|
|
/*
|
|
==============
|
|
CL_ParseCvarValue2
|
|
|
|
Find the client cvar value
|
|
and sent it back to the server
|
|
==============
|
|
*/
|
|
void CL_ParseCvarValue2( sizebuf_t *msg )
|
|
{
|
|
int requestID = MSG_ReadLong( msg );
|
|
const char *cvarName = MSG_ReadString( msg );
|
|
convar_t *cvar = Cvar_FindVar( cvarName );
|
|
|
|
// build the answer
|
|
MSG_BeginClientCmd( &cls.netchan.message, clc_requestcvarvalue2 );
|
|
MSG_WriteLong( &cls.netchan.message, requestID );
|
|
MSG_WriteString( &cls.netchan.message, cvarName );
|
|
|
|
if( cvar )
|
|
{
|
|
// cheater can change value ignoring Cvar_Set so we responce incorrect value
|
|
if( cvar->value != Q_atof( cvar->string ))
|
|
MSG_WriteString( &cls.netchan.message, va( "%s (%g)", cvar->string, cvar->value ));
|
|
else MSG_WriteString( &cls.netchan.message, cvar->string );
|
|
}
|
|
else
|
|
{
|
|
MSG_WriteString( &cls.netchan.message, "Not Found" );
|
|
}
|
|
}
|
|
|
|
/*
|
|
==============
|
|
CL_DispatchUserMessage
|
|
|
|
Dispatch user message by engine request
|
|
==============
|
|
*/
|
|
qboolean CL_DispatchUserMessage( const char *pszName, int iSize, void *pbuf )
|
|
{
|
|
int i;
|
|
|
|
if( !COM_CheckString( pszName ))
|
|
return false;
|
|
|
|
for( i = 0; i < MAX_USER_MESSAGES; i++ )
|
|
{
|
|
// search for user message
|
|
if( !Q_strcmp( clgame.msg[i].name, pszName ))
|
|
break;
|
|
}
|
|
|
|
if( i == MAX_USER_MESSAGES )
|
|
{
|
|
Con_DPrintf( S_ERROR "UserMsg: bad message %s\n", pszName );
|
|
return false;
|
|
}
|
|
|
|
if( clgame.msg[i].func )
|
|
{
|
|
clgame.msg[i].func( pszName, iSize, pbuf );
|
|
}
|
|
else
|
|
{
|
|
Con_DPrintf( S_ERROR "UserMsg: No pfn %s %d\n", clgame.msg[i].name, clgame.msg[i].number );
|
|
clgame.msg[i].func = CL_UserMsgStub; // throw warning only once
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
==============
|
|
CL_ParseUserMessage
|
|
|
|
handles all user messages
|
|
==============
|
|
*/
|
|
void CL_ParseUserMessage( sizebuf_t *msg, int svc_num )
|
|
{
|
|
byte pbuf[MAX_USERMSG_LENGTH];
|
|
int i, iSize;
|
|
|
|
// NOTE: any user message is really parse at engine, not in client.dll
|
|
if( svc_num <= svc_lastmsg || svc_num > ( MAX_USER_MESSAGES + svc_lastmsg ))
|
|
{
|
|
// out or range
|
|
Host_Error( "CL_ParseUserMessage: illegible server message %d\n", svc_num );
|
|
return;
|
|
}
|
|
|
|
for( i = 0; i < MAX_USER_MESSAGES; i++ )
|
|
{
|
|
// search for user message
|
|
if( clgame.msg[i].number == svc_num )
|
|
break;
|
|
}
|
|
|
|
if( i == MAX_USER_MESSAGES ) // probably unregistered
|
|
Host_Error( "CL_ParseUserMessage: illegible server message %d\n", svc_num );
|
|
|
|
// NOTE: some user messages handled into engine
|
|
if( !Q_strcmp( clgame.msg[i].name, "ScreenShake" ))
|
|
{
|
|
CL_ParseScreenShake( msg );
|
|
return;
|
|
}
|
|
else if( !Q_strcmp( clgame.msg[i].name, "ScreenFade" ))
|
|
{
|
|
CL_ParseScreenFade( msg );
|
|
return;
|
|
}
|
|
|
|
iSize = clgame.msg[i].size;
|
|
|
|
// message with variable sizes receive an actual size as first byte
|
|
if( iSize == -1 )
|
|
{
|
|
if( cls.legacymode )
|
|
iSize = MSG_ReadByte( msg );
|
|
else iSize = MSG_ReadWord( msg );
|
|
}
|
|
|
|
if( iSize >= MAX_USERMSG_LENGTH )
|
|
{
|
|
Msg("WTF??? %d %d\n", i, svc_num );
|
|
return;
|
|
}
|
|
|
|
// parse user message into buffer
|
|
MSG_ReadBytes( msg, pbuf, iSize );
|
|
|
|
if( cl_trace_messages->value )
|
|
{
|
|
Con_Reportf( "^3USERMSG %s SIZE %i SVC_NUM %i\n",
|
|
clgame.msg[i].name, iSize, clgame.msg[i].number );
|
|
}
|
|
|
|
if( clgame.msg[i].func )
|
|
{
|
|
clgame.msg[i].func( clgame.msg[i].name, iSize, pbuf );
|
|
|
|
#ifdef HACKS_RELATED_HLMODS
|
|
// run final credits for Half-Life because hl1 doesn't have call END_SECTION
|
|
if( !Q_stricmp( clgame.msg[i].name, "HudText" ) && !Q_stricmp( GI->gamefolder, "valve" ))
|
|
{
|
|
// it's a end, so we should run credits
|
|
if( !Q_strcmp( (char *)pbuf, "END3" ))
|
|
Host_Credits();
|
|
}
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
Con_DPrintf( S_ERROR "UserMsg: No pfn %s %d\n", clgame.msg[i].name, clgame.msg[i].number );
|
|
clgame.msg[i].func = CL_UserMsgStub; // throw warning only once
|
|
}
|
|
}
|
|
|
|
/*
|
|
=====================================================================
|
|
|
|
ACTION MESSAGES
|
|
|
|
=====================================================================
|
|
*/
|
|
/*
|
|
=====================
|
|
CL_ParseServerMessage
|
|
|
|
dispatch messages
|
|
=====================
|
|
*/
|
|
void CL_ParseServerMessage( 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_event:
|
|
CL_ParseEvent( msg );
|
|
cl.frames[cl.parsecountmod].graphdata.event += MSG_GetNumBytesRead( msg ) - bufStart;
|
|
break;
|
|
case svc_changing:
|
|
old_background = cl.background;
|
|
if( MSG_ReadOneBit( msg ))
|
|
{
|
|
cls.changelevel = true;
|
|
S_StopAllSounds( true );
|
|
|
|
Con_Printf( "Server changing, reconnecting\n" );
|
|
|
|
if( cls.demoplayback )
|
|
{
|
|
SCR_BeginLoadingPlaque( cl.background );
|
|
cls.changedemo = true;
|
|
}
|
|
|
|
CL_ClearState ();
|
|
CL_InitEdicts (); // 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;
|
|
}
|
|
break;
|
|
case svc_setview:
|
|
CL_ParseViewEntity( msg );
|
|
break;
|
|
case svc_sound:
|
|
CL_ParseSoundPacket( msg );
|
|
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
|
|
// dsiable Cry Of Fear antisave protection
|
|
if( !Q_strnicmp( s, "disconnect", 10 ) && cls.signon != SIGNONS )
|
|
break; // too early
|
|
#endif
|
|
Cbuf_AddText( s );
|
|
break;
|
|
case svc_setangle:
|
|
CL_ParseSetAngle( msg );
|
|
break;
|
|
case svc_serverdata:
|
|
Cbuf_Execute(); // make sure any stuffed commands are done
|
|
CL_ParseServerData( msg );
|
|
break;
|
|
case svc_lightstyle:
|
|
CL_ParseLightStyle( msg );
|
|
break;
|
|
case svc_updateuserinfo:
|
|
CL_UpdateUserinfo( msg );
|
|
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_ParseStaticEntity( 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 );
|
|
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_finale:
|
|
CL_ParseFinaleCutscene( msg, 2 );
|
|
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_cutscene:
|
|
CL_ParseFinaleCutscene( msg, 3 );
|
|
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_choke:
|
|
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_ParseResourceList( 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_voiceinit:
|
|
CL_ParseVoiceInit( msg );
|
|
break;
|
|
case svc_voicedata:
|
|
CL_ParseVoiceData( msg );
|
|
break;
|
|
case svc_resourcelocation:
|
|
CL_ParseResLocation( msg );
|
|
break;
|
|
case svc_querycvarvalue:
|
|
CL_ParseCvarValue( msg );
|
|
break;
|
|
case svc_querycvarvalue2:
|
|
CL_ParseCvarValue2( msg );
|
|
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 );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
==================
|
|
CL_ParseBaseline
|
|
==================
|
|
*/
|
|
void CL_LegacyParseBaseline( sizebuf_t *msg )
|
|
{
|
|
int i, newnum;
|
|
entity_state_t nullstate;
|
|
qboolean player;
|
|
cl_entity_t *ent;
|
|
|
|
Delta_InitClient (); // finalize client delta's
|
|
|
|
memset( &nullstate, 0, sizeof( nullstate ));
|
|
|
|
newnum = MSG_ReadWord( msg );
|
|
player = CL_IsPlayerIndex( newnum );
|
|
|
|
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;
|
|
|
|
MSG_ReadDeltaEntity( msg, &ent->prevstate, &ent->baseline, newnum, player, 1.0f );
|
|
|
|
}
|
|
|
|
/*
|
|
==================
|
|
CL_ParseServerData
|
|
==================
|
|
*/
|
|
void CL_ParseLegacyServerData( sizebuf_t *msg )
|
|
{
|
|
string gamefolder;
|
|
qboolean background;
|
|
int i;
|
|
|
|
Con_Reportf( "Legacy serverdata packet received.\n" );
|
|
|
|
cls.timestart = Sys_DoubleTime();
|
|
|
|
cls.demowaiting = false; // server is changed
|
|
//clgame.load_sequence++; // now all hud sprites are invalid
|
|
|
|
// wipe the client_t struct
|
|
if( !cls.changelevel && !cls.changedemo )
|
|
CL_ClearState ();
|
|
cls.state = ca_connected;
|
|
|
|
// parse protocol version number
|
|
i = MSG_ReadLong( msg );
|
|
//cls.serverProtocol = i;
|
|
|
|
if( i != 48 )
|
|
Host_Error( "Server uses invalid protocol (%i should be %i)\n", i, PROTOCOL_VERSION );
|
|
|
|
cl.servercount = MSG_ReadLong( msg );
|
|
cl.checksum = MSG_ReadLong( msg );
|
|
cl.playernum = MSG_ReadByte( msg );
|
|
cl.maxclients = MSG_ReadByte( msg );
|
|
clgame.maxEntities = MSG_ReadWord( msg );
|
|
clgame.maxEntities = bound( 30, clgame.maxEntities, 4096 );
|
|
clgame.maxModels = 512;
|
|
Q_strncpy( clgame.mapname, MSG_ReadString( msg ), MAX_STRING );
|
|
Q_strncpy( clgame.maptitle, MSG_ReadString( msg ), MAX_STRING );
|
|
background = MSG_ReadOneBit( msg );
|
|
Q_strncpy( gamefolder, MSG_ReadString( msg ), MAX_STRING );
|
|
host.features = (uint)MSG_ReadLong( msg );
|
|
|
|
// 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" );
|
|
|
|
// set the background state
|
|
if( cls.demoplayback && ( cls.demonum != -1 ))
|
|
{
|
|
// re-init mouse
|
|
host.mouse_visible = false;
|
|
cl.background = true;
|
|
}
|
|
else cl.background = background;
|
|
|
|
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 );
|
|
|
|
if( !cls.changelevel )
|
|
{
|
|
// continue playing if we are changing level
|
|
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
|
|
|
|
// 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)
|
|
}
|
|
|
|
for( i = 0; i < MAX_CLIENTS; i++ )
|
|
COM_ClearCustomizationList( &cl.players[i].customdata, true );
|
|
CL_CreateCustomizationList();
|
|
|
|
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;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
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 );
|
|
}
|
|
|
|
|
|
void CL_LegacyUpdateUserinfo( sizebuf_t *msg )
|
|
{
|
|
int slot, id = 0;
|
|
qboolean active;
|
|
player_info_t *player;
|
|
|
|
slot = MSG_ReadUBitLong( msg, MAX_CLIENT_BITS );
|
|
|
|
if( slot >= MAX_CLIENTS )
|
|
Host_Error( "CL_ParseServerMessage: svc_updateuserinfo >= MAX_CLIENTS\n" );
|
|
|
|
//id = MSG_ReadLong( msg ); // unique user ID
|
|
player = &cl.players[slot];
|
|
active = MSG_ReadOneBit( msg ) ? true : false;
|
|
|
|
if( active )
|
|
{
|
|
Q_strncpy( player->userinfo, MSG_ReadString( msg ), sizeof( player->userinfo ));
|
|
Q_strncpy( player->name, Info_ValueForKey( player->userinfo, "name" ), sizeof( player->name ));
|
|
Q_strncpy( player->model, Info_ValueForKey( player->userinfo, "model" ), sizeof( player->model ));
|
|
player->topcolor = Q_atoi( Info_ValueForKey( player->userinfo, "topcolor" ));
|
|
player->bottomcolor = Q_atoi( Info_ValueForKey( player->userinfo, "bottomcolor" ));
|
|
player->spectator = Q_atoi( Info_ValueForKey( player->userinfo, "*hltv" ));
|
|
//MSG_ReadBytes( msg, player->hashedcdkey, sizeof( player->hashedcdkey ));
|
|
|
|
if( slot == cl.playernum ) memcpy( &gameui.playerinfo, player, sizeof( player_info_t ));
|
|
}
|
|
else memset( player, 0, sizeof( *player ));
|
|
}
|
|
#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;
|
|
}
|
|
|
|
host.downloadcount = 0;
|
|
|
|
for( i = 0; i < reslist.rescount; i++ )
|
|
{
|
|
const char *path;
|
|
|
|
if( reslist.restype[i] == t_sound )
|
|
path = va( DEFAULT_SOUNDPATH "%s", reslist.resnames[i] );
|
|
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 ))
|
|
{
|
|
cls.changelevel = true;
|
|
S_StopAllSounds( true );
|
|
|
|
Con_Printf( "Server changing, reconnecting\n" );
|
|
|
|
if( cls.demoplayback )
|
|
{
|
|
SCR_BeginLoadingPlaque( cl.background );
|
|
cls.changedemo = true;
|
|
}
|
|
|
|
CL_ClearState ();
|
|
CL_InitEdicts (); // 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;
|
|
}
|
|
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
|
|
// dsiable Cry Of Fear antisave protection
|
|
if( !Q_strnicmp( s, "disconnect", 10 ) && cls.signon != SIGNONS )
|
|
break; // too early
|
|
#endif
|
|
|
|
Con_Reportf( "Stufftext: %s", s );
|
|
Cbuf_AddText( s );
|
|
break;
|
|
case svc_setangle:
|
|
CL_ParseSetAngle( msg );
|
|
break;
|
|
case svc_serverdata:
|
|
Cbuf_Execute(); // make sure any stuffed commands are done
|
|
CL_ParseLegacyServerData( msg );
|
|
break;
|
|
case svc_lightstyle:
|
|
CL_ParseLightStyle( msg );
|
|
break;
|
|
case svc_updateuserinfo:
|
|
CL_LegacyUpdateUserinfo( msg );
|
|
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_ParseStaticEntity( msg );
|
|
break;
|
|
case svc_event_reliable:
|
|
CL_ParseReliableEvent( msg );
|
|
cl.frames[cl.parsecountmod].graphdata.event += MSG_GetNumBytesRead( msg ) - bufStart;
|
|
break;
|
|
case svc_spawnbaseline:
|
|
CL_LegacyParseBaseline( msg );
|
|
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_ParseFinaleCutscene( msg, 3 );
|
|
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_voiceinit:
|
|
CL_ParseVoiceInit( msg );
|
|
break;
|
|
case svc_voicedata:
|
|
CL_ParseVoiceData( msg );
|
|
break;
|
|
case svc_resourcelocation:
|
|
CL_ParseResLocation( msg );
|
|
break;
|
|
case svc_querycvarvalue:
|
|
CL_ParseCvarValue( msg );
|
|
break;
|
|
case svc_querycvarvalue2:
|
|
CL_ParseCvarValue2( msg );
|
|
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;
|
|
|
|
// update the ref state.
|
|
R_UpdateRefState ();
|
|
|
|
// 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_WriteString( &cls.netchan.message, va( "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;
|
|
}
|