//========= Copyright (c) 1996-2005, Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ //=============================================================================// #include "client_pch.h" #include "networkstringtabledefs.h" #include #include #include "userid.h" #include "pure_server.h" #include "netmessages.h" #include "cl_demo.h" #include "host_state.h" #include "host.h" #include "gl_matsysiface.h" #include "vgui_baseui_interface.h" #include "tier0/icommandline.h" #include #include "checksum_engine.h" #include "filesystem_engine.h" #include "logofile_shared.h" #include "sound.h" #include "decal.h" #include "networkstringtableclient.h" #include "dt_send_eng.h" #include "ents_shared.h" #include "cl_ents_parse.h" #include "cl_entityreport.h" #include "MapReslistGenerator.h" #include "DownloadListGenerator.h" #include "GameEventManager.h" #include "vgui_baseui_interface.h" #include "clockdriftmgr.h" #include "snd_audio_source.h" #include "vgui_controls/Controls.h" #include "vgui/ILocalize.h" #include "download.h" #include "checksum_engine.h" #include "ModelInfo.h" #include "materialsystem/imaterial.h" #include "materialsystem/materialsystem_config.h" #include "tier1/fmtstr.h" #include "cl_steamauth.h" #include "matchmaking/imatchframework.h" #include "audio/private/snd_sfx.h" #include "tier0/platform.h" #include "tier0/systeminformation.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" ConVar cl_timeout( "cl_timeout", "30", FCVAR_ARCHIVE, "After this many seconds without receiving a packet from the server, the client will disconnect itself" #ifndef _DEBUG , true, 4, true, 30 #endif ); static ConVar cl_forcepreload( "cl_forcepreload", "0", FCVAR_ARCHIVE, "Whether we should force preloading."); static ConVar cl_downloadfilter( "cl_downloadfilter", "all", FCVAR_ARCHIVE, "Determines which files can be downloaded from the server (all, none, nosounds)" ); static ConVar cl_download_demoplayer( "cl_download_demoplayer", "1", FCVAR_RELEASE, "Determines whether downloads of external resources are allowed during demo playback (0:no,1:workshop,2:all)" ); ConVar cl_debug_ugc_downloads( "cl_debug_ugc_downloads", "0", FCVAR_RELEASE ); extern ConVar sv_downloadurl; extern ConVar sv_consistency; extern ConVar cl_hideserverip; extern bool g_bServerGameDLLGreaterThanV5; ////////////////////////////////////////////////////////////////////// // Construction/Destruction ////////////////////////////////////////////////////////////////////// CClientState::CClientState() { m_bMarkedCRCsUnverified = false; demonum = -1; m_tickRemainder = 0; m_frameTime = 0; m_pAreaBits = NULL; m_hWaitForResourcesHandle = NULL; m_bUpdateSteamResources = false; m_bShownSteamResourceUpdateProgress = false; m_pPureServerWhitelist = NULL; m_bCheckCRCsWithServer = false; m_flLastCRCBatchTime = 0; m_nFriendsID = 0; m_FriendsName[ 0 ] = 0; m_flLastServerTickTime = -1.0f; lastoutgoingcommand = 0; chokedcommands = 0; last_command_ack = 0; last_server_tick = 0; command_ack = 0; m_nSoundSequence = 0; serverCRC = 0; serverClientSideDllCRC = 0; viewangles.Init(); Q_memset( m_chAreaBits, 0, sizeof( m_chAreaBits ) ); Q_memset( m_chAreaPortalBits, 0, sizeof( m_chAreaPortalBits ) ); m_bAreaBitsValid = false; addangletotal = 0.0f; prevaddangletotal = 0.0f; cdtrack = 0; Q_memset( m_FriendsName, 0, sizeof( m_FriendsName ) ); m_pModelPrecacheTable = NULL; m_pDynamicModelTable = NULL; m_pGenericPrecacheTable = NULL; m_pSoundPrecacheTable = NULL; m_pDecalPrecacheTable = NULL; m_pInstanceBaselineTable = NULL; m_pLightStyleTable = NULL; m_pUserInfoTable = NULL; m_pServerStartupTable = NULL; m_pDownloadableFileTable = NULL; m_bDownloadResources = false; m_bDownloadingUGCMap = false; insimulation = false; oldtickcount = 0; ishltv = false; #if defined( REPLAY_ENABLED ) isreplay = false; #endif ResetHltvReplayState(); } void CClientState::ResetHltvReplayState() { m_nHltvReplayDelay = 0; m_nHltvReplayStopAt = 0; m_nHltvReplayStartAt = 0; m_nHltvReplaySlowdownBeginAt = 0; m_nHltvReplaySlowdownEndAt = 0; m_flHltvReplaySlowdownRate = 1.0f; } CClientState::~CClientState() { if ( m_pPureServerWhitelist ) m_pPureServerWhitelist->Release(); } // HL1 CD Key #define GUID_LEN 13 /* ======================= CL_GetCDKeyHash() Connections will now use a hashed cd key value A LAN server will know not to allows more then xxx users with the same CD Key ======================= */ const char *CClientState::GetCDKeyHash( void ) { if ( IsPC() ) { char szKeyBuffer[256]; // Keys are about 13 chars long. static char szHashedKeyBuffer[64]; int nKeyLength; bool bDedicated = false; MD5Context_t ctx; unsigned char digest[16]; // The MD5 Hash nKeyLength = Q_snprintf( szKeyBuffer, sizeof( szKeyBuffer ), "%s", registry->ReadString( "key", "" ) ); if (bDedicated) { ConMsg("Key has no meaning on dedicated server...\n"); return ""; } if ( nKeyLength == 0 ) { nKeyLength = 13; Q_strncpy( szKeyBuffer, "1234567890123", sizeof( szKeyBuffer ) ); Assert( Q_strlen( szKeyBuffer ) == nKeyLength ); DevMsg( "Missing CD Key from registry, inserting blank key\n" ); registry->WriteString( "key", szKeyBuffer ); } if (nKeyLength <= 0 || nKeyLength >= 256 ) { ConMsg("Bogus key length on CD Key...\n"); return ""; } // Now get the md5 hash of the key memset( &ctx, 0, sizeof( ctx ) ); memset( digest, 0, sizeof( digest ) ); MD5Init(&ctx); MD5Update(&ctx, (unsigned char*)szKeyBuffer, nKeyLength); MD5Final(digest, &ctx); Q_strncpy ( szHashedKeyBuffer, MD5_Print ( digest, sizeof( digest ) ), sizeof( szHashedKeyBuffer ) ); return szHashedKeyBuffer; } return "12345678901234567890123456789012"; } void CClientState::SendClientInfo( void ) { CCLCMsg_ClientInfo_t info; info.set_send_table_crc( SendTable_GetCRC() ); info.set_server_count( m_nServerCount ); info.set_is_hltv( false ); #if defined( REPLAY_ENABLED ) info.set_is_replay( false ); #endif #if !defined( NO_STEAM ) info.set_friends_id( Steam3Client().SteamUser() ? Steam3Client().SteamUser()->GetSteamID().GetAccountID() : 0 ); #else info.set_friends_id( 0 ); #endif info.set_friends_name( m_FriendsName ); CheckOwnCustomFiles(); // load & verfiy custom player files for ( int i=0; i< MAX_CUSTOM_FILES; i++ ) { info.add_custom_files( m_nCustomFiles[i].crc ); } m_NetChannel->SendNetMsg( info ); } void CClientState::SendLoadingProgress( int nProgress ) { if ( !m_NetChannel || nProgress <= m_nLastProgressPercent ) { return; } CCLCMsg_LoadingProgress_t info; info.set_progress( nProgress ); m_nLastProgressPercent = nProgress; m_NetChannel->SendNetMsg( info ); } void CClientState::SendServerCmdKeyValues( KeyValues *pKeyValues ) { if ( !pKeyValues ) return; // Ensure keyvalues are deleted per contract obligations KeyValues::AutoDelete autodelete_pKeyValues( pKeyValues ); if ( !m_NetChannel ) return; CCLCMsg_CmdKeyValues_t msg; CmdKeyValuesHelper::CLCMsg_SetKeyValues( msg, pKeyValues ); m_NetChannel->SendNetMsg( msg ); } bool CClientState::SendNetMsg( INetMessage &msg, bool bForceReliable, bool bVoice ) { if ( m_NetChannel ) return m_NetChannel->SendNetMsg( msg, bForceReliable, bVoice ); else return false; } extern IVEngineClient *engineClient; //----------------------------------------------------------------------------- // Purpose: A svc_signonnum has been received, perform a client side setup // Output : void CL_SignonReply //----------------------------------------------------------------------------- bool CClientState::SetSignonState ( int state, int count, const CNETMsg_SignonState *msg ) { int nOldSignonState = m_nSignonState; ResetHltvReplayState(); if ( !CBaseClientState::SetSignonState( state, count, msg ) ) { CL_Retry(); return false; } // ConDMsg ("Signon state: %i\n", state ); COM_TimestampedLog( "CClientState::SetSignonState: start %i", state ); switch ( m_nSignonState ) { case SIGNONSTATE_CHALLENGE : m_bMarkedCRCsUnverified = false; // Remember that we just connected to a new server so it'll // reverify any necessary file CRCs on this server. EngineVGui()->UpdateProgressBar(PROGRESS_SIGNONCHALLENGE); break; case SIGNONSTATE_CONNECTED : { EngineVGui()->UpdateProgressBar(PROGRESS_SIGNONCONNECTED); // make sure it's turned off when connecting EngineVGui()->HideDebugSystem(); SCR_BeginLoadingPlaque (); // Clear channel and stuff m_NetChannel->Clear(); // allow longer timeout m_NetChannel->SetTimeout( SIGNON_TIME_OUT ); m_NetChannel->SetMaxBufferSize( true, NET_MAX_PAYLOAD ); // set user settings (rate etc) CNETMsg_SetConVar_t convars; Host_BuildUserInfoUpdateMessage( m_nSplitScreenSlot, convars.mutable_convars(), false ); m_NetChannel->SendNetMsg( convars ); } break; case SIGNONSTATE_NEW : { EngineVGui()->UpdateProgressBar(PROGRESS_SIGNONNEW); if ( cl_download_demoplayer.GetBool() || !demoplayer->IsPlayingBack() ) { // When playing back a demo we need to suspend packet reading here if ( demoplayer->IsPlayingBack() ) { demoplayer->SetPacketReadSuspended( true ); } // start making sure we have all the specified resources StartUpdatingSteamResources(); } else { // during demo playback dont try to download resource FinishSignonState_New(); } // don't tell the server yet that we've entered this state COM_TimestampedLog( "CClientState::SetSignonState: end %i", state ); return true; } break; case SIGNONSTATE_PRESPAWN : EngineVGui()->UpdateProgressBar(PROGRESS_SENDSIGNONDATA); m_nSoundSequence = 1; // reset sound sequence number after receiving signon sounds break; case SIGNONSTATE_SPAWN : { extern float NET_GetFakeLag(); Assert( g_ClientDLL ); EngineVGui()->UpdateProgressBar(PROGRESS_SIGNONSPAWN); // Tell client .dll about the transition char mapname[256]; CL_SetupMapName( modelloader->GetName( host_state.worldmodel ), mapname, sizeof( mapname ) ); COM_TimestampedLog( "LevelInitPreEntity: start %i", state ); // enable prediction if the server isn't local or the user is requesting fake lag g_ClientGlobalVariables.m_bRemoteClient = !Host_IsLocalServer() || (NET_GetFakeLag() != 0.0f); g_ClientDLL->LevelInitPreEntity(mapname); COM_TimestampedLog( "LevelInitPreEntity: end %i", state ); audiosourcecache->LevelInit( mapname ); // stop recording demo header demorecorder->SetSignonState( SIGNONSTATE_SPAWN ); } break; case SIGNONSTATE_FULL: { CL_FullyConnected(); if ( !m_NetChannel ) return false; // disconnected during connection m_NetChannel->SetTimeout( cl_timeout.GetFloat() ); m_NetChannel->SetMaxBufferSize( true, NET_MAX_DATAGRAM_PAYLOAD ); HostState_OnClientConnected(); // If we came through a level transition with splitscreen guys, then we need to force their // signon state to be SIGNONSTATE_FULL, too FOR_EACH_VALID_SPLITSCREEN_PLAYER( hh ) { CBaseClientState &cl = GetLocalClient( hh ); if ( &cl == this ) continue; cl.m_nSignonState = SIGNONSTATE_FULL; } } break; case SIGNONSTATE_CHANGELEVEL: m_NetChannel->SetTimeout( SIGNON_TIME_OUT ); // allow 5 minutes timeout m_nLastProgressPercent = -1; if ( m_nMaxClients > 1 ) { // start progress bar immediately for multiplayer level transitions EngineVGui()->EnabledProgressBarForNextLoad(); } SCR_BeginLoadingPlaque( msg->map_name().c_str() ); if ( m_nMaxClients > 1 ) { EngineVGui()->UpdateProgressBar(PROGRESS_CHANGELEVEL); } break; } COM_TimestampedLog( "CClientState::SetSignonState: end %i", state ); KeyValues *pEvent = new KeyValues( "OnEngineClientSignonStateChange" ); pEvent->SetInt( "slot", m_nSplitScreenSlot ); if ( m_bServerConnectionRedirect && nOldSignonState >= SIGNONSTATE_CONNECTED && state < SIGNONSTATE_CONNECTED ) nOldSignonState = state; // during server redirect attempt to keep the MMS session pEvent->SetInt( "old", nOldSignonState ); pEvent->SetInt( "new", state ); pEvent->SetInt( "count", count ); g_pMatchFramework->GetEventsSubscription()->BroadcastEvent( pEvent ); if ( m_nSignonState >= SIGNONSTATE_CONNECTED ) { // tell server that we entered now that state CNETMsg_SignonState_t msgSignonState( m_nSignonState, count); m_NetChannel->SendNetMsg( msgSignonState ); } return true; } bool CClientState::HookClientStringTable( char const *tableName ) { INetworkStringTable *table = GetStringTable( tableName ); if ( !table ) { // If engine takes a pass, allow client dll to hook in its callbacks if ( g_ClientDLL ) { g_ClientDLL->InstallStringTableCallback( tableName ); } return false; } // Hook Model Precache table if ( !Q_strcasecmp( tableName, MODEL_PRECACHE_TABLENAME ) ) { m_pModelPrecacheTable = table; return true; } if ( !Q_strcasecmp( tableName, GENERIC_PRECACHE_TABLENAME ) ) { m_pGenericPrecacheTable = table; return true; } if ( !Q_strcasecmp( tableName, SOUND_PRECACHE_TABLENAME ) ) { m_pSoundPrecacheTable = table; return true; } if ( !Q_strcasecmp( tableName, DECAL_PRECACHE_TABLENAME ) ) { // Cache the id m_pDecalPrecacheTable = table; return true; } if ( !Q_strcasecmp( tableName, INSTANCE_BASELINE_TABLENAME ) ) { // Cache the id m_pInstanceBaselineTable = table; return true; } if ( !Q_strcasecmp( tableName, LIGHT_STYLES_TABLENAME ) ) { // Cache the id m_pLightStyleTable = table; return true; } if ( !Q_strcasecmp( tableName, USER_INFO_TABLENAME ) ) { // Cache the id m_pUserInfoTable = table; return true; } if ( !Q_strcasecmp( tableName, SERVER_STARTUP_DATA_TABLENAME ) ) { // Cache the id m_pServerStartupTable = table; return true; } if ( !Q_strcasecmp( tableName, DOWNLOADABLE_FILE_TABLENAME ) ) { // Cache the id m_pDownloadableFileTable = table; return true; } if ( !Q_strcasecmp( tableName, DYNAMIC_MODEL_TABLENAME ) ) { m_pDynamicModelTable = table; return true; } // If engine takes a pass, allow client dll to hook in its callbacks g_ClientDLL->InstallStringTableCallback( tableName ); return false; } bool CClientState::InstallEngineStringTableCallback( char const *tableName ) { INetworkStringTable *table = GetStringTable( tableName ); if ( !table ) return false; // Hook Model Precache table if ( !Q_strcasecmp( tableName, MODEL_PRECACHE_TABLENAME ) ) { table->SetStringChangedCallback( NULL, Callback_ModelChanged ); return true; } if ( !Q_strcasecmp( tableName, GENERIC_PRECACHE_TABLENAME ) ) { // Install the callback table->SetStringChangedCallback( NULL, Callback_GenericChanged ); return true; } if ( !Q_strcasecmp( tableName, SOUND_PRECACHE_TABLENAME ) ) { // Install the callback table->SetStringChangedCallback( NULL, Callback_SoundChanged ); return true; } if ( !Q_strcasecmp( tableName, DECAL_PRECACHE_TABLENAME ) ) { // Install the callback table->SetStringChangedCallback( NULL, Callback_DecalChanged ); return true; } if ( !Q_strcasecmp( tableName, INSTANCE_BASELINE_TABLENAME ) ) { // Install the callback (already done above) table->SetStringChangedCallback( NULL, Callback_InstanceBaselineChanged ); return true; } if ( !Q_strcasecmp( tableName, LIGHT_STYLES_TABLENAME ) ) { return true; } if ( !Q_strcasecmp( tableName, USER_INFO_TABLENAME ) ) { // Install the callback table->SetStringChangedCallback( NULL, Callback_UserInfoChanged ); return true; } if ( !Q_strcasecmp( tableName, SERVER_STARTUP_DATA_TABLENAME ) ) { return true; } if ( !Q_strcasecmp( tableName, DOWNLOADABLE_FILE_TABLENAME ) ) { return true; } if ( !Q_strcasecmp( tableName, DYNAMIC_MODEL_TABLENAME ) ) { table->SetStringChangedCallback( NULL, Callback_DynamicModelChanged ); m_pDynamicModelTable = table; return true; } // The the client.dll have a shot at it return false; } void CClientState::InstallStringTableCallback( char const *tableName ) { // Let engine hook callbacks before we read in any data values at all if ( !InstallEngineStringTableCallback( tableName ) ) { // If engine takes a pass, allow client dll to hook in its callbacks g_ClientDLL->InstallStringTableCallback( tableName ); } } bool CClientState::IsPaused() const { return m_bPaused || ( g_LostVideoMemory && Host_IsSinglePlayerGame() ) || !host_initialized || demoplayer->IsPlaybackPaused() || EngineVGui()->ShouldPause(); } float CClientState::GetTime() const { int nTickCount = GetClientTickCount(); float flTickTime = nTickCount * host_state.interval_per_tick; float flResult; // Timestamps are rounded to exact tick during simulation if ( insimulation ) { return flTickTime; } #if defined(_X360) || defined( _PS3 ) // This function is called enough under ComputeLightingState to make this little cache worthwhile [10/6/2010 tom] static float lastResult; static int lastTick; if ( lastTick == nTickCount ) { return lastResult; } lastTick = nTickCount; #endif // Tracker 77931: If the game is paused, then lock the client clock at the previous tick boundary // (otherwise we'll keep interpolating through the "remainder" time causing the paused characters // to twitch like they have the shakes) // TODO: Since this rounds down on the frame we paused, we could see a slight backsliding. We could remember the last "remainder" before pause and re-use it and // set insimulation == false to be more exact. We'd still have to deal with the timing difference between // when pause/unpause happens on the server versus the client if ( GetBaseLocalClient().IsPaused() ) { // Go just before next tick flResult = flTickTime + host_state.interval_per_tick - 0.00001f; } else { flResult = flTickTime + m_tickRemainder; } #if defined(_X360) || defined( _PS3 ) lastResult = flResult; #endif return flResult; } float CClientState::GetFrameTime() const { if ( CClockDriftMgr::IsClockCorrectionEnabled() ) { return IsPaused() ? 0 : m_frameTime; } else { if ( insimulation ) { int nElapsedTicks = ( GetClientTickCount() - oldtickcount ); return nElapsedTicks * host_state.interval_per_tick; } else { return IsPaused() ? 0 : m_frameTime; } } } float CClientState::GetClientInterpAmount() { // we need client cvar cl_interp_ratio static const ConVar *s_cl_interp_ratio = NULL; if ( !s_cl_interp_ratio ) { s_cl_interp_ratio = g_pCVar->FindVar( "cl_interp_ratio" ); if ( !s_cl_interp_ratio ) return 0.1f; } static const ConVar *s_cl_interp = NULL; if ( !s_cl_interp ) { s_cl_interp = g_pCVar->FindVar( "cl_interp" ); if ( !s_cl_interp ) return 0.1f; } float flInterpRatio = s_cl_interp_ratio->GetFloat(); float flInterp = s_cl_interp->GetFloat(); const ConVar_ServerBounded *pBounded = dynamic_cast( s_cl_interp_ratio ); if ( pBounded ) flInterpRatio = pBounded->GetFloat(); //#define FIXME_INTERP_RATIO return MAX( flInterpRatio / cl_updaterate->GetFloat(), flInterp ); } //----------------------------------------------------------------------------- // Purpose: // Clear all the variables in the CClientState. //----------------------------------------------------------------------------- void CClientState::Clear( void ) { CBaseClientState::Clear(); m_pModelPrecacheTable = NULL; m_pDynamicModelTable = NULL; m_pGenericPrecacheTable = NULL; m_pSoundPrecacheTable = NULL; m_pDecalPrecacheTable = NULL; m_pInstanceBaselineTable = NULL; m_pLightStyleTable = NULL; m_pUserInfoTable = NULL; m_pServerStartupTable = NULL; m_pAreaBits = NULL; // Clear all download vars. m_pDownloadableFileTable = NULL; m_hWaitForResourcesHandle = NULL; m_bUpdateSteamResources = false; m_bShownSteamResourceUpdateProgress = false; m_bDownloadResources = false; m_bDownloadingUGCMap = false; m_modelIndexLoaded = -1; m_lastModelPercent = -1; DeleteClientFrames( -1 ); // clear all viewangles.Init(); m_flLastServerTickTime = 0.0f; oldtickcount = 0; insimulation = false; addangle.RemoveAll(); addangletotal = 0.0f; prevaddangletotal = 0.0f; memset(model_precache, 0, sizeof(model_precache)); memset(sound_precache, 0, sizeof(sound_precache)); ishltv = false; #if defined( REPLAY_ENABLED ) isreplay = false; #endif cdtrack = 0; serverCRC = 0; serverClientSideDllCRC = 0; last_command_ack = 0; last_server_tick = 0; command_ack = 0; m_nSoundSequence = 0; // make sure the client isn't active anymore, but stay // connected if we are. if ( m_nSignonState > SIGNONSTATE_CONNECTED ) { m_nSignonState = SIGNONSTATE_CONNECTED; } } void CClientState::ClearSounds() { int c = ARRAYSIZE( sound_precache ); for ( int i = 0; i < c; ++i ) { sound_precache[ i ].SetSound( NULL ); } } bool CClientState::ProcessConnectionlessPacket( netpacket_t *packet ) { Assert( packet ); return CBaseClientState::ProcessConnectionlessPacket( packet ); } void CClientState::ConnectionStart( INetChannel *chan ) { CBaseClientState::ConnectionStart( chan ); m_SVCMsgHltvReplay.Bind< CSVCMsg_HltvReplay_t >( chan, UtlMakeDelegate( this, &CClientState::SVCMsg_HltvReplay ) ); } void CClientState::ConnectionStop( ) { CBaseClientState::ConnectionStop( ); m_SVCMsgHltvReplay.Unbind(); } float CClientState::GetHltvReplayTimeScale()const { extern ConVar spec_replay_rate_base; if ( m_nHltvReplayDelay ) { int nCurrentTick = GetClientTickCount(); if ( nCurrentTick >= m_nHltvReplaySlowdownBeginAt && nCurrentTick < m_nHltvReplaySlowdownEndAt ) return spec_replay_rate_base.GetFloat() * m_flHltvReplaySlowdownRate; else return spec_replay_rate_base.GetFloat(); } return 1.0f; } float CL_GetHltvReplayTimeScale() { return GetBaseLocalClient().GetHltvReplayTimeScale(); } void CClientState::StopHltvReplay() { ForceFullUpdate( "Force StopHltvReplay on client" ); m_nHltvReplayDelay = 0; m_nHltvReplayStopAt = 0; m_nHltvReplayStartAt = 0; if ( g_ClientDLL ) { CSVCMsg_HltvReplay msg; g_ClientDLL->OnHltvReplay( msg ); } } void CClientState::FullConnect( const ns_address &adr, int nEncryptionKey ) { CBaseClientState::FullConnect( adr, nEncryptionKey ); m_NetChannel->SetDemoRecorder( g_pClientDemoRecorder ); m_NetChannel->SetDataRate( cl_rate->GetFloat() ); // Not in the demo loop now demonum = -1; // We don't have a backed up cmd history yet lastoutgoingcommand = -1; // we didn't send commands yet chokedcommands = 0; // Report connection success. if ( !adr.IsLoopback() ) { ConMsg( "Connected to %s\n", cl_hideserverip.GetInt()>0 ? "