//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// // // Purpose: // //=============================================================================// #ifndef BASECLIENTSTATE_H #define BASECLIENTSTATE_H #ifdef _WIN32 #pragma once #endif #include "inetmsghandler.h" #include "protocol.h" #include "client_class.h" #include "cdll_int.h" #include "tier1/netadr.h" #include "common.h" #include "clockdriftmgr.h" #include "convar.h" #include "cl_bounded_cvars.h" #include "tier1/utlstring.h" #include "netmessages.h" #include "utlmap.h" #include "matchmaking/imatchasync.h" #include "netmessages.pb.h" // Only send this many requests before timing out. #define CL_CONNECTION_RETRIES 4 // Mininum time gap (in seconds) before a subsequent connection request is sent. #define CL_MIN_RESEND_TIME 1.5f // Max time. The cvar cl_resend is bounded by these. #define CL_MAX_RESEND_TIME 20.0f // In release, send commands at least this many times per second #define MIN_CMD_RATE 10.0f #define MAX_CMD_RATE 128.0f typedef intp SerializedEntityHandle_t; extern ConVar cl_name; abstract_class CBaseClientState; // This represents a server's class C_ServerClassInfo { public: C_ServerClassInfo(); ~C_ServerClassInfo(); public: ClientClass *m_pClientClass; char *m_ClassName; char *m_DatatableName; // This is an index into the network string table (GetBaseLocalClient().GetInstanceBaselineTable()). int m_InstanceBaselineIndex; // INVALID_STRING_INDEX if not initialized yet. }; #define EndGameAssertMsg( assertion, msg ) \ if ( !(assertion) )\ Host_EndGame msg class CNetworkStringTableContainer; class PackedEntity; class INetworkStringTable; class CEntityReadInfo; struct DeferredConnection_t { DeferredConnection_t() { m_bActive = false; m_nChallenge = 0; m_nAuthprotocol = 0; m_unGSSteamID = 0ull;; m_unLobbyID = 0ull; m_bGSSecure = false; m_bRequiresPassword = false; m_bDCFriendsReqd = false; m_bOfficialValveServer = false; m_nEncryptionKey = 0; m_nEncryptedSize = 0; memset( m_chLobbyType, 0, sizeof( m_chLobbyType ) ); } bool m_bActive; bool m_bGSSecure; bool m_bRequiresPassword; bool m_bDCFriendsReqd; bool m_bOfficialValveServer; char m_chLobbyType[16]; int m_nChallenge; int m_nAuthprotocol; uint64 m_unGSSteamID; uint64 m_unLobbyID; ns_address m_adrServerAddress; int m_nEncryptionKey; int m_nEncryptedSize; }; // 0 == public, 1 == private, 2 == double NAT'd private (for direct connection) struct Remote_t { bool Resolve(); CUtlString m_szAlias; // debugging: "public", "private", "direct" CUtlString m_szRetryAddress; // Address for actual packets (may differ from m_szRetryAddress) ns_address m_adrRemote; }; class CAddressList { public: CAddressList() {} void RemoveAll(); bool IsRemoteInList( char const *pchAdrCheck ) const; bool IsAddressInList( const ns_address &adr ) const; // Only adds if not in list already void AddRemote( char const *pchAddress, char const *pchAlias ); void Describe( CUtlString &str ); int Count() const; Remote_t &Get( int index ); const Remote_t &Get( int index ) const; private: CUtlVector< Remote_t > m_List; }; // Classes to help with sending messages to server, with retries and timeouts // Start by sending the message immediately. Wait for timeout before // sending message again. // If maxAttempts exceeded with no response set state to AOS_FAILED and call callback. // If a response is received set state to AOS_SUCCEEDED and call callback. // Result is only valid if state == AOS_SUCCEEDED class CServerMsg : public IMatchAsyncOperation { public: // Methods of IMatchAsyncOperation bool IsFinished() { return m_eState > AOS_ABORTING; } AsyncOperationState_t GetState() { return m_eState; } uint64 GetResult() { return m_result; } void Abort() { m_eState = AOS_ABORTED; } // Timeout in seconds explicit CServerMsg( CBaseClientState *pParent, IMatchAsyncOperationCallback *pCallback, const ns_address& serverAdr, int socket, uint32 maxAttempts, double timeout ); // Per frame update, check for timeout etc if state == AOS_RUNNING void Update( void ); // Call this function to check if it is ok to process a msg bool IsValidResponse( const ns_address &from, uint32 token ); // Called by derived class when response received. Sets state to AOS_SUCCEEDED // and calls callback void ResponseReceived( uint64 result ); // Methods derived class must implement. Token is a random number that can be // used to match up a message-response pair. A new token is generated for each // SendMsg call; the last token generated can be queried using GetLastToken() virtual void SendMsg( const ns_address& serverAdr, int socket, uint32 token ) = 0; // See comment for SendMsg() uint32 GetLastToken() { return m_lastToken; } const ns_address& GetServerAddr() { return m_serverAdr; } // base client state parent CBaseClientState *m_pParent; // Data members AsyncOperationState_t m_eState; IMatchAsyncOperationCallback *m_pCallback; ns_address m_serverAdr; int m_socket; double m_lastMsgSendTime; double m_timeOut; uint32 m_maxAttempts; uint32 m_numAttempts; uint64 m_result; uint32 m_lastToken; // See comment for SendMsg() }; class CServerMsg_CheckReservation : public CServerMsg { public: explicit CServerMsg_CheckReservation( CBaseClientState *pParent, IMatchAsyncOperationCallback *pCallback, const ns_address &serverAdr, int socket, uint64 reservationCookie, uint32 uiReservationStage ); void Release(); void SendMsg( const ns_address& serverAdr, int socket, uint32 token ); void ResponseReceived( const ns_address& from, bf_read &msg, int32 hostVersion, uint32 token ); uint64 m_reservationCookie; uint32 m_uiReservationStage; }; class CServerMsg_Ping : public CServerMsg { public: explicit CServerMsg_Ping( CBaseClientState *pParent, IMatchAsyncOperationCallback *pCallback, const ns_address &serverAdr, int socket ); void Release(); void SendMsg( const ns_address& serverAdrserverAdr, int socket, uint32 token ); void ResponseReceived( const ns_address& from, bf_read &msg, int32 hostVersion, uint32 token ); double m_timeLastMsgSent; }; // CBaseClientState abstract_class CBaseClientState : public INetChannelHandler, public IConnectionlessPacketHandler { public: CBaseClientState(); virtual ~CBaseClientState(); public: // IConnectionlessPacketHandler interface: virtual bool ProcessConnectionlessPacket(struct netpacket_s *packet); public: // INetMsgHandler interface: virtual void ConnectionStart(INetChannel *chan) OVERRIDE; virtual void ConnectionStop( ) OVERRIDE; virtual void ConnectionClosing( const char *reason ); virtual void ConnectionCrashed(const char *reason); virtual void PacketStart(int incoming_sequence, int outgoing_acknowledged) {}; virtual void PacketEnd( void ) {}; virtual void FileReceived( const char *fileName, unsigned int transferID, bool isReplayDemoFile ); virtual void FileRequested(const char *fileName, unsigned int transferID, bool isReplayDemoFile ); virtual void FileDenied(const char *fileName, unsigned int transferID, bool isReplayDemoFile ); virtual void FileSent(const char *fileName, unsigned int transferID, bool isReplayDemoFile ); virtual bool ChangeSplitscreenUser( int nSplitScreenUserSlot ); // interleaved networking used by SS system is changing the SS player slot that the subsequent messages pertain to public: // IServerMessageHandlers virtual bool NETMsg_Tick( const CNETMsg_Tick& msg ); static bool NETMsg_Tick_Delegate( CBaseClientState *pThis, const CNETMsg_Tick& msg ) { return pThis->NETMsg_Tick( msg ); } virtual bool NETMsg_StringCmd( const CNETMsg_StringCmd& msg ); bool NETMsg_SignonState( const CNETMsg_SignonState& msg ); virtual bool NETMsg_PlayerAvatarData( const CNETMsg_PlayerAvatarData& msg ); virtual bool NETMsg_SetConVar( const CNETMsg_SetConVar& msg ); bool SVCMsg_CmdKeyValues( const CSVCMsg_CmdKeyValues& msg); virtual bool SVCMsg_EncryptedData( const CSVCMsg_EncryptedData& msg ); bool SVCMsg_SendTable( const CSVCMsg_SendTable& msg ); bool SVCMsg_Print( const CSVCMsg_Print& msg ); virtual bool SVCMsg_ServerInfo( const CSVCMsg_ServerInfo& msg ); virtual bool SVCMsg_ClassInfo( const CSVCMsg_ClassInfo& msg ); virtual bool SVCMsg_SetPause( const CSVCMsg_SetPause& msg ); virtual bool SVCMsg_SetView( const CSVCMsg_SetView& msg ); virtual bool SVCMsg_CreateStringTable( const CSVCMsg_CreateStringTable& msg ); virtual bool SVCMsg_UpdateStringTable( const CSVCMsg_UpdateStringTable& msg ); virtual bool SVCMsg_VoiceInit( const CSVCMsg_VoiceInit& msg ) = 0; virtual bool SVCMsg_VoiceData( const CSVCMsg_VoiceData& msg ) = 0; virtual bool SVCMsg_FixAngle( const CSVCMsg_FixAngle& msg ) = 0; virtual bool SVCMsg_Prefetch( const CSVCMsg_Prefetch& msg ) = 0; virtual bool SVCMsg_CrosshairAngle( const CSVCMsg_CrosshairAngle& msg ) = 0; virtual bool SVCMsg_BSPDecal( const CSVCMsg_BSPDecal& msg ) = 0; virtual bool SVCMsg_SplitScreen( const CSVCMsg_SplitScreen& msg ); virtual bool SVCMsg_GetCvarValue( const CSVCMsg_GetCvarValue& msg ); virtual bool SVCMsg_Menu( const CSVCMsg_Menu& msg ); virtual bool SVCMsg_UserMessage( const CSVCMsg_UserMessage& msg ) = 0; virtual bool SVCMsg_PaintmapData( const CSVCMsg_PaintmapData& msg ) = 0; virtual bool SVCMsg_GameEvent( const CSVCMsg_GameEvent& msg ) = 0; virtual bool SVCMsg_GameEventList( const CSVCMsg_GameEventList &msg ); virtual bool SVCMsg_TempEntities( const CSVCMsg_TempEntities& msg ) = 0; virtual bool SVCMsg_PacketEntities( const CSVCMsg_PacketEntities& msg ); virtual bool SVCMsg_Sounds( const CSVCMsg_Sounds& msg ) = 0; virtual bool SVCMsg_EntityMsg( const CSVCMsg_EntityMsg& msg ) = 0; CNetMessageBinder m_NETMsgTick; CNetMessageBinder m_NETMsgStringCmd; CNetMessageBinder m_NETMsgSignonState; CNetMessageBinder m_NETMsgSetConVar; CNetMessageBinder m_NETMsgPlayerAvatarData; CNetMessageBinder m_SVCMsgServerInfo; CNetMessageBinder m_SVCMsgCmdKeyValues; CNetMessageBinder m_SVCMsg_EncryptedData; CNetMessageBinder m_SVCMsgClassInfo; CNetMessageBinder m_SVCMsgSendTable; CNetMessageBinder m_SVCMsgPrint; CNetMessageBinder m_SVCMsgSetPause; CNetMessageBinder m_SVCMsgSetView; CNetMessageBinder m_SVCMsgCreateStringTable; CNetMessageBinder m_SVCMsgUpdateStringTable; CNetMessageBinder m_SVCMsgVoiceInit; CNetMessageBinder m_SVCMsgVoiceData; CNetMessageBinder m_SVCMsgFixAngle; CNetMessageBinder m_SVCMsgPrefetch; CNetMessageBinder m_SVCMsgCrosshairAngle; CNetMessageBinder m_SVCMsgBSPDecal; CNetMessageBinder m_SVCMsgSplitScreen; CNetMessageBinder m_SVCMsgGetCvarValue; CNetMessageBinder m_SVCMsgMenu; CNetMessageBinder m_SVCMsgUserMessage; CNetMessageBinder m_SVCMsgPaintmapData; CNetMessageBinder m_SVCMsgGameEvent; CNetMessageBinder m_SVCMsgGameEventList; CNetMessageBinder m_SVCMsgTempEntities; CNetMessageBinder m_SVCMsgPacketEntities; CNetMessageBinder m_SVCMsgSounds; CNetMessageBinder m_SVCMsgEntityMsg; public: // IMatchEventsSink virtual void OnEvent( KeyValues *pEvent ); public: inline bool IsActive( void ) const { return m_nSignonState == SIGNONSTATE_FULL; }; inline bool IsConnected( void ) const { return m_nSignonState >= SIGNONSTATE_CONNECTED; }; inline bool IsConnecting( void ) const { return m_nSignonState >= SIGNONSTATE_NONE; } virtual void Clear( void ); virtual void FullConnect( const ns_address &adr, int nEncryptionKey ); // a connection was established virtual void Connect( const char *pchPublicAddress, char const *pchPrivateAddress, const char* szJoinType ); // start a connection challenge virtual void ConnectSplitScreen( const char *pchPublicAddress, char const *pchPrivateAddress, int numPlayers, const char* szJoinType ); // start a connection challenge virtual bool SetSignonState ( int state, int count, const CNETMsg_SignonState *msg ); virtual void Disconnect( bool bShowMainMenu = true ); virtual void SendConnectPacket ( const ns_address &netAdrRemote, int challengeNr, int authProtocol, uint64 unGSSteamID, bool bGSSecure ); virtual const char *GetCDKeyHash() { return "123"; } virtual void RunFrame ( void ); virtual void CheckForResend ( bool bForceResendNow = false ); virtual void CheckForReservationResend( void ); virtual void ResendGameDetailsRequest( const ns_address &adr ); virtual void InstallStringTableCallback( char const *tableName ) { } virtual bool HookClientStringTable( char const *tableName ) { return false; } virtual bool LinkClasses( void ); virtual int GetConnectionRetryNumber() const { return m_nRetryMax; } virtual const char *GetClientName() { return cl_name.GetString(); } virtual void ReserveServer( const ns_address &netAdrPublic, const ns_address &netAdrPrivate, uint64 nServerReservationCookie, KeyValues *pKVGameSettings, IMatchAsyncOperationCallback *pCallback, IMatchAsyncOperation **ppAsyncOperation ); bool CheckServerReservation( const ns_address &netAdrPublic, uint64 nServerReservationCookie, uint32 uiReservationStage, IMatchAsyncOperationCallback *pCallback, IMatchAsyncOperation **ppAsyncOperation ); bool ServerPing( const ns_address &netAdrPublic, IMatchAsyncOperationCallback *pCallback, IMatchAsyncOperation **ppAsyncOperation ); struct ReservationResponseReply_t { ReservationResponseReply_t() { m_uiResponse = 0; m_bValveDS = false; m_numGameSlots = 0; } ns_address m_adrFrom; uint8 m_uiResponse; bool m_bValveDS; uint32 m_numGameSlots; }; virtual void HandleReservationResponse( const ReservationResponseReply_t &reply ); virtual void HandleReserveServerChallengeResponse( int nChallengeNr ); virtual void SetServerReservationCookie( uint64 nReservationCookie ) { m_nServerReservationCookie = nReservationCookie; } static ClientClass* FindClientClass(const char *pClassName); CClockDriftMgr& GetClockDriftMgr(); int GetClientTickCount() const; // Get the client tick count. void SetClientTickCount( int tick ); int GetServerTickCount() const; void SetServerTickCount( int tick ); void SetClientAndServerTickCount( int tick ); INetworkStringTable *GetStringTable( const char * name ) const; PackedEntity *GetEntityBaseline( int iBaseline, int nEntityIndex ); void SetEntityBaseline(int iBaseline, ClientClass *pClientClass, int index, SerializedEntityHandle_t handle); void CopyEntityBaseline( int iFrom, int iTo ); void FreeEntityBaselines(); bool GetClassBaseline( int iClass, SerializedEntityHandle_t *pHandle ); void UpdateInstanceBaseline( int nStringNumber ); ClientClass *GetClientClass( int i ); void ForceFullUpdate( char const *pchReason ); void SendStringCmd(const char * command); virtual void ReadPacketEntities( CEntityReadInfo &u ) = 0; int GetViewEntity(); void HandleDeferredConnection(); void SetConnectionPassword( char const *pchCurrentPW ); void ResetConnectionRetries(); virtual bool IsClientStateTv() const { return false; } protected: bool InternalProcessStringCmd( const CNETMsg_StringCmd& msg ); private: bool PrepareSteamConnectResponse( uint64 unGSSteamID, bool bGSSecure, const ns_address &adr, bf_write &msg ); void SendReserveServerMsg(); void SendReserveServerChallenge(); void BuildReserveServerPayload( bf_write &msg, int nChallengeNr ); void SendReservationCheckMsg( const ns_address &netAdrPublic, uint64 nServerReservationCookie ); int FindSplitPlayerSlot( int nPlayerIndex ); void ConnectInternal( const char *pchPublicAddress, char const *pchPrivateAddress, int numPlayers, const char* szJoinType ); void RememberIPAddressForLobby( uint64 unLobbyID, const ns_address &adrRemote ); bool IsRemoteInList( char const *pchAdrCheck ) const; bool ShouldUseDirectConnectAddress( const CAddressList &list ) const; public: // Connection to server. int m_Socket; // network socket INetChannel *m_NetChannel; // Our sequenced channel to the remote server. bool m_bSplitScreenUser; unsigned int m_nChallengeNr; // connection challenge number double m_flConnectTime; // If gap of connect_time to net_time > 3000, then resend connect packet int m_nRetryNumber; // number of retry connection attempts int m_nRetryMax; // max # of retry attempts allowed // Address for actual packets (may differ from m_szRetryAddress) CAddressList m_Remote; struct DirectConnectLobby_t { DirectConnectLobby_t() : m_flEndTime( -1 ), m_unLobbyID( 0ull ) {} float m_flEndTime; ns_address m_adrRemote; uint64 m_unLobbyID; }; DirectConnectLobby_t m_DirectConnectLobby; uint64 m_ListenServerSteamID; int m_nSignonState; // see SIGNONSTATE_* definitions double m_flNextCmdTime; // When can we send the next command packet? int m_nServerCount; // server identification for prespawns, must match the svs.spawncount which // is incremented on server spawning. This supercedes svs.spawn_issued, in that // we can now spend a fair amount of time sitting connected to the server // but downloading models, sounds, etc. So much time that it is possible that the // server might change levels again and, if so, we need to know that. int m_nCurrentSequence; // this is the sequence number of the current incoming packet uint64 m_ulGameServerSteamID; // Steam ID of the game server we are trying to connect to, or are connected to. Zero if unknown CClockDriftMgr m_ClockDriftMgr; int m_nDeltaTick; // last valid received snapshot (server) tick bool m_bPaused; // send over by server int m_nViewEntity; // player point of view override int m_nPlayerSlot; // own player entity index-1. skips world. Add 1 to get cl_entitites index; int m_nSplitScreenSlot; char m_szLevelName[ MAX_PATH ]; // for display on solo scoreboard char m_szLevelNameShort[ 40 ]; // removes maps/ and .bsp extension char m_szMapGroupName[ 40 ]; //the name of the map group we are currently playing in char m_szLastLevelNameShort[ 40 ]; // stores the previous value of m_szLevelNameShort when that gets cleared PublishedFileId_t m_unUGCMapFileID; // If a community map, this is the published file id int m_nMaxClients; // max clients on server int m_nNumPlayersToConnect; // number of clients to connect to server. class CAsyncOperation_ReserveServer : public IMatchAsyncOperation { public: explicit CAsyncOperation_ReserveServer( CBaseClientState *pParent ) : m_eState( AOS_RUNNING ), m_pParent( pParent ) { m_numGameSlotsForReservation = 0; } virtual bool IsFinished() { return m_eState > AOS_ABORTING; } virtual AsyncOperationState_t GetState() { return m_eState; } virtual uint64 GetResult(); virtual uint64 GetResultExtraInfo() { return m_numGameSlotsForReservation; } virtual void Abort() {} virtual void Release() { if ( m_pParent && m_pParent->m_pServerReservationOperation == this ) { m_pParent->m_pServerReservationOperation = NULL; m_pParent->m_pServerReservationCallback = NULL; } delete this; } public: AsyncOperationState_t m_eState; ns_address m_adr; CBaseClientState *m_pParent; uint32 m_numGameSlotsForReservation; }; CAsyncOperation_ReserveServer *m_pServerReservationOperation; // server reservation operation IMatchAsyncOperationCallback *m_pServerReservationCallback; // callback for pending reservation request CUtlVector< CServerMsg_CheckReservation * > m_arrSvReservationCheck; CUtlVector< CServerMsg_Ping * > m_arrSvPing; uint64 m_nServerReservationCookie; // cookie to set during reservation and provide upon connection KeyValues *m_pKVGameSettings; // game settings to request on reserved server double m_flReservationMsgSendTime; // time we last sent reservation msg int m_nReservationMsgRetryNumber; // # of times we've retried sending reservation msg CAddressList m_netadrReserveServer; // netadr of server we're trying to reserve bool m_bEnteredPassword; bool m_bWaitingForPassword; #if ENGINE_CONNECT_VIA_MMS bool m_bWaitingForServerGameDetails; #endif DeferredConnection_t m_DeferredConnection; CUtlMap< int32, byte*, int32, CDefLess< int32 > > m_mapGeneratedEncryptionKeys; PackedEntity *m_pEntityBaselines[2][MAX_EDICTS]; // storing entity baselines // This stuff manages the receiving of data tables and instantiating of client versions // of server-side classes. C_ServerClassInfo *m_pServerClasses; int m_nServerClasses; int m_nServerClassBits; char m_szEncryptionKey[STEAM_KEYSIZE]; unsigned int m_iEncryptionKeySize; CNetworkStringTableContainer *m_StringTableContainer; CUtlMap< int, SerializedEntityHandle_t > m_BaselineHandles; typedef CUtlMap< uint32, CNETMsg_PlayerAvatarData_t *, int, CDefLess< uint32 > > PlayerAvatarDataMap_t; PlayerAvatarDataMap_t m_mapPlayerAvatarData; CNETMsg_PlayerAvatarData_t * AllocOwnPlayerAvatarData() const; bool m_bRestrictServerCommands; // If true, then the server is only allowed to execute commands marked with FCVAR_SERVER_CAN_EXECUTE on the client. bool m_bRestrictClientCommands; // If true, then IVEngineClient::ClientCmd is only allowed to execute commands marked with FCVAR_CLIENTCMD_CAN_EXECUTE on the client. // tracks valid reception of server info bool m_bServerInfoProcessed; int m_nServerProtocolVersion; int m_nServerInfoMsgProtocol; bool m_bServerConnectionRedirect; }; inline CClockDriftMgr& CBaseClientState::GetClockDriftMgr() { return m_ClockDriftMgr; } inline void CBaseClientState::SetClientTickCount( int tick ) { m_ClockDriftMgr.m_nClientTick = tick; } inline int CBaseClientState::GetClientTickCount() const { return m_ClockDriftMgr.m_nClientTick; } inline int CBaseClientState::GetServerTickCount() const { return m_ClockDriftMgr.m_nServerTick; } inline void CBaseClientState::SetServerTickCount( int tick ) { m_ClockDriftMgr.m_nServerTick = tick; } inline void CBaseClientState::SetClientAndServerTickCount( int tick ) { m_ClockDriftMgr.m_nServerTick = m_ClockDriftMgr.m_nClientTick = tick; } #endif // BASECLIENTSTATE_H