/* =========================================================================== Doom 3 BFG Edition GPL Source Code Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). Doom 3 BFG Edition Source Code 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. Doom 3 BFG Edition Source Code 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. You should have received a copy of the GNU General Public License along with Doom 3 BFG Edition Source Code. If not, see . In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. =========================================================================== */ #include "sys_lobby_backend.h" #define INVALID_LOBBY_USER_NAME " " // Used to be "INVALID" but Sony might not like that. class idSessionCallbacks; class idDebugGraph; /* ======================== idLobby ======================== */ class idLobby : public idLobbyBase { public: idLobby(); enum lobbyType_t { TYPE_PARTY = 0, TYPE_GAME = 1, TYPE_GAME_STATE = 2, TYPE_INVALID = 0xff }; enum lobbyState_t { STATE_IDLE, STATE_CREATE_LOBBY_BACKEND, STATE_SEARCHING, STATE_OBTAINING_ADDRESS, STATE_CONNECT_HELLO_WAIT, STATE_FINALIZE_CONNECT, STATE_FAILED, NUM_STATES }; enum failedReason_t { FAILED_UNKNOWN, FAILED_CONNECT_FAILED, FAILED_MIGRATION_CONNECT_FAILED, }; void Initialize( lobbyType_t sessionType_, class idSessionCallbacks * callbacks ); void StartHosting( const idMatchParameters & parms ); void StartFinding( const idMatchParameters & parms_ ); void Pump(); void ProcessSnapAckQueue(); void Shutdown( bool retainMigrationInfo = false, bool skipGoodbye = false ); // Goto idle state void HandlePacket( lobbyAddress_t & remoteAddress, idBitMsg fragMsg, idPacketProcessor::sessionId_t sessionID ); lobbyState_t GetState() { return state; } virtual bool HasActivePeers() const; virtual bool IsLobbyFull() const { return NumFreeSlots() == 0; } int NumFreeSlots() const; public: enum reliablePlayerToPlayer_t { //RELIABLE_PLAYER_TO_PLAYER_VOICE_EVENT, RELIABLE_PLAYER_TO_PLAYER_GAME_DATA, // Game messages would be reserved here in the same way that RELIABLE_GAME_DATA is. // I'm worried about using up the 0xff values we have for reliable type, so I'm not // going to reserve anything here just yet. NUM_RELIABLE_PLAYER_TO_PLAYER, }; enum reliableType_t { RELIABLE_HELLO, // host to peer : connection established RELIABLE_USER_CONNECTED, // host to peer : a new session user connected RELIABLE_USER_DISCONNECTED, // host to peer : a session user disconnected RELIABLE_START_LOADING, // host to peer : peer should begin loading the map RELIABLE_LOADING_DONE, // peer to host : finished loading map RELIABLE_IN_GAME, // peer to host : first full snap received, in game now RELIABLE_SNAPSHOT_ACK, // peer to host : got a snapshot RELIABLE_RESOURCE_ACK, // peer to host : got some new resources RELIABLE_CONNECT_AND_MOVE_TO_LOBBY, // host to peer : connect to this server RELIABLE_PARTY_CONNECT_OK, // host to peer RELIABLE_PARTY_LEAVE_GAME_LOBBY, // host to peer : leave game lobby RELIABLE_MATCH_PARMS, // host to peer : update in match parms RELIABLE_UPDATE_MATCH_PARMS, // peer to host : peer updating match parms // User join in progress msg's (join in progress for the party/game lobby, not inside a match) RELIABLE_USER_CONNECT_REQUEST, // peer to host: local user wants to join session in progress RELIABLE_USER_CONNECT_DENIED, // host to peer: user join session in progress denied (not enough slots) // User leave in progress msg's (leave in progress for the party/game lobby, not inside a match) RELIABLE_USER_DISCONNECT_REQUEST, // peer to host: request host to remove user from session RELIABLE_KICK_PLAYER, // host to peer : kick a player RELIABLE_MATCHFINISHED, // host to peer - Match is in post looking at score board RELIABLE_ENDMATCH, // host to peer - End match, and go to game lobby RELIABLE_ENDMATCH_PREMATURE, // host to peer - End match prematurely, and go to game lobby (onl possible in unrated/custom games) RELIABLE_SESSION_USER_MODIFIED, // peer to host : user changed something (emblem, name, etc) RELIABLE_UPDATE_SESSION_USER, // host to peers : inform all peers of the change RELIABLE_HEADSET_STATE, // * to * : headset state change for user RELIABLE_VOICE_STATE, // * to * : voice state changed for user pair (mute, unmute, etc) RELIABLE_PING, // * to * : send host->peer, then reflected RELIABLE_PING_VALUES, // host to peers : ping data from lobbyUser_t for everyone RELIABLE_BANDWIDTH_VALUES, // peer to host: data back about bandwidth test RELIABLE_ARBITRATE, // host to peer : start arbitration RELIABLE_ARBITRATE_OK, // peer to host : ack arbitration request RELIABLE_POST_STATS, // host to peer : here, write these stats now (hacky) RELIABLE_MIGRATION_GAME_DATA, // host to peers: game data to use incase of a migration RELIABLE_START_MATCH_GAME_LOBBY_HOST, // game lobby host to game state lobby host: start the match, since all players are in RELIABLE_DUMMY_MSG, // used as a placeholder for old removed msg's RELIABLE_PLAYER_TO_PLAYER_BEGIN, // use reliablePlayerToPlayer_t RELIABLE_PLAYER_TO_PLAYER_END = RELIABLE_PLAYER_TO_PLAYER_BEGIN + NUM_RELIABLE_PLAYER_TO_PLAYER, // * to * : misc reliable game data above this RELIABLE_GAME_DATA = RELIABLE_PLAYER_TO_PLAYER_END }; // JGM: Reliable type in packet is a byte and there are a lot of reliable game messages. // Feel free to bump this up since it's arbitrary anyway, but take a look at gameReliable_t. // At the moment, both Doom and Rage have around 32 gameReliable_t values. compile_time_assert( RELIABLE_GAME_DATA < 64 ); static const char * stateToString[ NUM_STATES ]; // Consts static const int PEER_HEARTBEAT_IN_SECONDS = 5; // Make sure something was sent every 5 seconds, so we don't time out static const int CONNECT_REQUEST_FREQUENCY_IN_SECONDS = 5; // Frequency at which we resend a request to connect to a server (will increase in frequency over time down to MIN_CONNECT_FREQUENCY_IN_SECONDS) static const int MIN_CONNECT_FREQUENCY_IN_SECONDS = 1; // Min frequency of connection attempts static const int MAX_CONNECT_ATTEMPTS = 5; static const int BANDWIDTH_REPORTING_MAX = 10240; // make bps to report receiving (clamp if higher). For quantizing static const int BANDWIDTH_REPORTING_BITS = 16; // number of bits to use for bandwidth reporting static const int MAX_BPS_HISTORY = 32; // size of outgoing bps history to maintain for each client static const int MAX_SNAP_SIZE = idPacketProcessor::MAX_MSG_SIZE; static const int MAX_SNAPSHOT_QUEUE = 64; static const int OOB_HELLO = 0; static const int OOB_GOODBYE = 1; static const int OOB_GOODBYE_W_PARTY = 2; static const int OOB_GOODBYE_FULL = 3; static const int OOB_RESOURCE_LIST = 4; static const int OOB_VOICE_AUDIO = 5; static const int OOB_MATCH_QUERY = 6; static const int OOB_MATCH_QUERY_ACK = 7; static const int OOB_SYSTEMLINK_QUERY = 8; static const int OOB_MIGRATE_INVITE = 9; static const int OOB_BANDWIDTH_TEST = 10; enum connectionState_t { CONNECTION_FREE = 0, // Free peer slot CONNECTION_CONNECTING = 1, // Waiting for response from host for initial connection CONNECTION_ESTABLISHED = 2, // Connection is established and active }; struct peer_t { peer_t() { loaded = false; inGame = false; networkChecksum = 0; lastSnapTime = 0; snapHz = 0.0f; numResources = 0; lastHeartBeat = 0; connectionState = CONNECTION_FREE; packetProc = NULL; snapProc = NULL; nextPing = 0; // do it asap lastPingRtt = 0; sessionID = idPacketProcessor::SESSION_ID_INVALID; startResourceLoadTime = 0; nextThrottleCheck = 0; maxSnapQueueSize = 0; throttledSnapRate = 0; pauseSnapshots = false; receivedBps = -1.0f; maxSnapBps = -1.0f; receivedThrottle = 0; receivedThrottleTime = 0; throttleSnapsForXSeconds = 0; recoverPing = 0; failedPingRecoveries = 0; rightBeforeSnapsPing = 0; bandwidthTestLastSendTime = 0; bandwidthSequenceNum = 0; bandwidthTestBytes = 0; bandwidthChallengeStartSendTime = 0; bandwidthChallengeResults = false; bandwidthChallengeSendComplete = false; numSnapsSent = 0; ResetConnectState(); }; void ResetConnectState() { lastResourceTime = 0; lastSnapTime = 0; snapHz = lastProcTime = 0; lastInBandProcTime = 0; lastFragmentSendTime = 0; needToSubmitPendingSnap = false; lastSnapJobTime = true; startResourceLoadTime = 0; receivedBps = -1.0; maxSnapBps = -1.0f; receivedThrottle = 0; receivedThrottleTime = 0; throttleSnapsForXSeconds = 0; recoverPing = 0; failedPingRecoveries = 0; rightBeforeSnapsPing = 0; bandwidthTestLastSendTime = 0; bandwidthSequenceNum = 0; bandwidthTestBytes = 0; bandwidthChallengeStartSendTime = 0; bandwidthChallengeResults = false; bandwidthChallengeSendComplete = false; memset( sentBpsHistory, 0, sizeof( sentBpsHistory ) ); receivedBpsIndex = 0; debugGraphs.Clear(); } void ResetAllData() { ResetConnectState(); ResetMatchData(); } void ResetMatchData() { loaded = false; networkChecksum = 0; inGame = false; numResources = 0; needToSubmitPendingSnap = false; throttledSnapRate = 0; maxSnapQueueSize = 0; receivedBpsIndex = -1; numSnapsSent = 0; pauseSnapshots = false; // Reset the snapshot processor if ( snapProc != NULL ) { snapProc->Reset( false ); } } void Print() { idLib::Printf(" lastResourceTime: %d\n", lastResourceTime ); idLib::Printf(" lastSnapTime: %d\n", lastSnapTime ); idLib::Printf(" lastProcTime: %d\n", lastProcTime ); idLib::Printf(" lastInBandProcTime: %d\n", lastInBandProcTime ); idLib::Printf(" lastFragmentSendTime: %d\n", lastFragmentSendTime ); idLib::Printf(" needToSubmitPendingSnap: %d\n", needToSubmitPendingSnap ); idLib::Printf(" lastSnapJobTime: %d\n", lastSnapJobTime ); } bool IsActive() const { return connectionState != CONNECTION_FREE; } bool IsConnected() const { return connectionState == CONNECTION_ESTABLISHED; } connectionState_t GetConnectionState() const; connectionState_t connectionState; bool loaded; // true if this peer has finished loading the map bool inGame; // true if this peer received the first snapshot, and is in-game int lastSnapTime; // Last time a snapshot was sent on the network to this peer float snapHz; int lastProcTime; // Used to determine when a packet was processed for sending to this peer int lastInBandProcTime; // Last time a in-band packet was processed for sending int lastFragmentSendTime; // Last time a fragment was sent out (fragments are processed msg's, waiting to be fully sent) unsigned long networkChecksum; // Checksum used to determine if a peer loaded the network resources the EXACT same as the server did int pauseSnapshots; lobbyAddress_t address; int numResources; // number of network resources we know the peer has idPacketProcessor * packetProc; // Processes packets for this peer idSnapshotProcessor * snapProc; // Processes snapshots for this peer idStaticList< idDebugGraph *, 4 > debugGraphs;// int lastResourceTime; // Used to throttle the sending of resources int lastHeartBeat; int nextPing; // next Sys_Milliseconds when I'll send this peer a RELIABLE_PING int lastPingRtt; bool needToSubmitPendingSnap; int lastSnapJobTime; // Last time a snapshot was sent to the joblist for this peer int startResourceLoadTime; // Used to determine how long a peer has been loading resources int maxSnapQueueSize; // how big has the snap queue gotten? int throttledSnapRate; // effective snap rate for this peer int nextThrottleCheck; int numSnapsSent; float sentBpsHistory[ MAX_BPS_HISTORY ]; int receivedBpsIndex; float receivedBps; // peer's reported bps (they tell us their effective downstream) float maxSnapBps; float receivedThrottle; // amount of accumlated time this client has been lagging behind int receivedThrottleTime; // last time we did received based throttle calculations int throttleSnapsForXSeconds; int recoverPing; int failedPingRecoveries; int rightBeforeSnapsPing; int bandwidthChallengeStartSendTime; // time we sent first packet of bw challenge to this peer int bandwidthTestLastSendTime; // last time in MS we sent them a bw challenge packet int bandwidthTestBytes; // used to measure number of bytes we sent them int bandwidthSequenceNum; // number of challenge sequences we sent them bool bandwidthChallengeResults; // we got results back bool bandwidthChallengeSendComplete; // we finished sending everything idPacketProcessor::sessionId_t sessionID; }; const char * GetLobbyName() { switch ( lobbyType ) { case TYPE_PARTY: return "TYPE_PARTY"; case TYPE_GAME: return "TYPE_GAME"; case TYPE_GAME_STATE: return "TYPE_GAME_STATE"; } return "LOBBY_INVALID"; } virtual lobbyUserID_t AllocLobbyUserSlotForBot( const char * botName ); // find a open user slot for the bot, and return the userID. virtual void RemoveBotFromLobbyUserList( lobbyUserID_t lobbyUserID ); // release the session user slot, so that it can be claimed by a player, etc. virtual bool GetLobbyUserIsBot( lobbyUserID_t lobbyUserID ) const; // check to see if the lobby user is a bot or not virtual int GetNumLobbyUsers() const { return userList.Num(); } virtual int GetNumActiveLobbyUsers() const; virtual bool AllPeersInGame() const; lobbyUser_t * GetLobbyUser( int index ) { return ( index >= 0 && index < GetNumLobbyUsers() ) ? userList[index] : NULL; } const lobbyUser_t * GetLobbyUser( int index ) const { return ( index >= 0 && index < GetNumLobbyUsers() ) ? userList[index] : NULL; } virtual bool IsLobbyUserConnected( int index ) const { return !IsLobbyUserDisconnected( index ); } virtual int PeerIndexFromLobbyUser( lobbyUserID_t lobbyUserID ) const; virtual int GetPeerTimeSinceLastPacket( int peerIndex ) const; virtual int PeerIndexForHost() const { return host; } virtual int PeerIndexOnHost() const { return peerIndexOnHost; } // Returns -1 if we are the host virtual const idMatchParameters & GetMatchParms() const { return parms; } lobbyType_t GetActingGameStateLobbyType() const; // If IsHost is true, we are a host accepting connections from peers bool IsHost() const { return isHost; } // If IsPeer is true, we are a peer, with an active connection to a host bool IsPeer() const { if ( host == -1 ) { return false; // Can't possibly be a peer if we haven't setup a host } assert( !IsHost() ); return peers[host].IsConnected(); } bool IsConnectingPeer() const { if ( host == -1 ) { return false; // Can't possibly be a peer if we haven't setup a host } assert( !IsHost() ); return peers[host].connectionState == CONNECTION_CONNECTING; } // IsRunningAsHostOrPeer means we are either an active host, and can accept connections from peers, or we are a peer with an active connection to a host bool IsRunningAsHostOrPeer() const { return IsHost() || IsPeer(); } bool IsLobbyActive() const { return IsRunningAsHostOrPeer(); } struct reliablePlayerToPlayerHeader_t { int fromSessionUserIndex; int toSessionUserIndex; reliablePlayerToPlayerHeader_t(); // Both read and write return false if the data is invalid. // The state of the msg and object are undefined if false is returned. // The network packets contain userIds, and Read/Write will translate from userId to a // sessionUserIndex. The sessionUserIndex should be the same on all peers, but the // userId has to be used in case the target player quits while the message is on the // wire from the originating peer to the server. bool Read( idLobby * lobby, idBitMsg & msg ); bool Write( idLobby * lobby, idBitMsg & msg ); }; int GetTotalOutgoingRate(); // returns total instant outgoing bandwidth in B/s //private: public: // Turning this on for now, for the sake of getting this up and running to see where things are // State functions void State_Idle(); void State_Create_Lobby_Backend(); void State_Searching(); void State_Obtaining_Address(); void State_Finalize_Connect(); void State_Connect_Hello_Wait(); void SetState( lobbyState_t newState ); void StartCreating(); int FindPeer( const lobbyAddress_t & remoteAddress, idPacketProcessor::sessionId_t sessionID, bool ignoreSessionID = false ); int FindAnyPeer( const lobbyAddress_t & remoteAddress ) const; int FindFreePeer() const; int AddPeer( const lobbyAddress_t & remoteAddress, idPacketProcessor::sessionId_t sessionID ); void DisconnectPeerFromSession( int p ); void SetPeerConnectionState( int p, connectionState_t newState, bool skipGoodbye = false ); void DisconnectAllPeers(); virtual void SendReliable( int type, idBitMsg & msg, bool callReceiveReliable = true, peerMask_t sessionUserMask = MAX_UNSIGNED_TYPE( peerMask_t ) ); virtual void SendReliableToLobbyUser( lobbyUserID_t lobbyUserID, int type, idBitMsg & msg ); virtual void SendReliableToHost( int type, idBitMsg & msg ); void SendGoodbye( const lobbyAddress_t & remoteAddress, bool wasFull = false ); void QueueReliableMessage( int peerNum, byte type ) { QueueReliableMessage( peerNum, type, NULL, 0 ); } void QueueReliableMessage( int p, byte type, const byte * data, int dataLen ); virtual int GetNumConnectedPeers() const; virtual int GetNumConnectedPeersInGame() const; void SendMatchParmsToPeers(); static bool IsReliablePlayerToPlayerType( byte type ); void HandleReliablePlayerToPlayerMsg( int peerNum, idBitMsg & msg, int type ); void HandleReliablePlayerToPlayerMsg( const reliablePlayerToPlayerHeader_t & info, idBitMsg & msg, int reliableType ); void SendConnectionLess( const lobbyAddress_t & remoteAddress, byte type ) { SendConnectionLess( remoteAddress, type, NULL, 0 ); } void SendConnectionLess( const lobbyAddress_t & remoteAddress, byte type, const byte * data, int dataLen ); void SendConnectionRequest(); void ConnectTo( const lobbyConnectInfo_t & connectInfo, bool fromInvite ); void HandleGoodbyeFromPeer( int peerNum, lobbyAddress_t & remoteAddress, int msgType ); void HandleConnectionAttemptFailed(); bool ConnectToNextSearchResult(); bool CheckVersion( idBitMsg & msg, lobbyAddress_t peerAddress ); bool VerifyNumConnectingUsers( idBitMsg & msg ); bool VerifyLobbyUserIDs( idBitMsg & msg ); int HandleInitialPeerConnection( idBitMsg & msg, const lobbyAddress_t & peerAddress, int peerNum ); void InitStateLobbyHost(); void SendMembersToLobby( lobbyType_t destLobbyType, const lobbyConnectInfo_t & connectInfo, bool waitForOtherMembers ); void SendMembersToLobby( idLobby & destLobby, bool waitForOtherMembers ); void SendPeerMembersToLobby( int peerIndex, lobbyType_t destLobbyType, const lobbyConnectInfo_t & connectInfo, bool waitForOtherMembers ); void SendPeerMembersToLobby( int peerIndex, lobbyType_t destLobbyType, bool waitForOtherMembers ); void NotifyPartyOfLeavingGameLobby(); uint32 GetPartyTokenAsHost(); virtual void DrawDebugNetworkHUD() const; virtual void DrawDebugNetworkHUD2() const; virtual void DrawDebugNetworkHUD_ServerSnapshotMetrics( bool draw ); void CheckHeartBeats(); bool IsLosingConnectionToHost() const; bool IsMigratedStatsGame() const; bool ShouldRelaunchMigrationGame() const; bool ShouldShowMigratingDialog() const; bool IsMigrating() const; // Pings struct pktPing_t { int timestamp; }; void PingPeers(); void SendPingValues(); void PumpPings(); void HandleReliablePing( int p, idBitMsg & msg ); void HandlePingReply( int p, const pktPing_t & ping ); void HandlePingValues( idBitMsg & msg ); void HandleBandwidhTestValue( int p, idBitMsg & msg ); void HandleMigrationGameData( idBitMsg & msg ); void HandleHeadsetStateChange( int fromPeer, idBitMsg & msg ); bool SendAnotherFragment( int p ); bool CanSendMoreData( int p ); void ProcessOutgoingMsg( int p, const void * data, int size, bool isOOB, int userData ); void ResendReliables( int p ); void PumpPackets(); void UpdateMatchParms( const idMatchParameters & p ); // SessionID helpers idPacketProcessor::sessionId_t EncodeSessionID( uint32 key ) const; void DecodeSessionID( idPacketProcessor::sessionId_t sessionID, uint32 & key ) const; idPacketProcessor::sessionId_t GenerateSessionID() const; bool SessionIDCanBeUsedForInBand( idPacketProcessor::sessionId_t sessionID ) const; idPacketProcessor::sessionId_t IncrementSessionID( idPacketProcessor::sessionId_t sessionID ) const; void HandleHelloAck( int p, idBitMsg & msg ); virtual const char * GetLobbyUserName( lobbyUserID_t lobbyUserID ) const; virtual bool GetLobbyUserWeaponAutoReload( lobbyUserID_t lobbyUserID ) const; virtual bool GetLobbyUserWeaponAutoSwitch( lobbyUserID_t lobbyUserID ) const; virtual int GetLobbyUserSkinIndex( lobbyUserID_t lobbyUserID ) const; virtual int GetLobbyUserLevel( lobbyUserID_t lobbyUserID ) const; virtual int GetLobbyUserQoS( lobbyUserID_t lobbyUserID ) const; virtual int GetLobbyUserTeam( lobbyUserID_t lobbyUserID ) const; virtual bool SetLobbyUserTeam( lobbyUserID_t lobbyUserID, int teamNumber ); virtual int GetLobbyUserPartyToken( lobbyUserID_t lobbyUserID ) const; virtual idPlayerProfile * GetProfileFromLobbyUser( lobbyUserID_t lobbyUserID ); virtual idLocalUser * GetLocalUserFromLobbyUser( lobbyUserID_t lobbyUserID ); virtual int GetNumLobbyUsersOnTeam( int teamNumber ) const; const char * GetPeerName( int peerNum ) const; virtual const char * GetHostUserName() const; void HandleReliableMsg( int p, idBitMsg & msg ); // Bandwidth / Qos / Throttling void BeginBandwidthTest(); bool BandwidthTestStarted(); void ServerUpdateBandwidthTest(); void ClientUpdateBandwidthTest(); void ThrottlePeerSnapRate( int peerNum ); // // sys_session_instance_users.cpp // lobbyUser_t * AllocUser( const lobbyUser_t & defaults ); void FreeUser( lobbyUser_t * user ); bool VerifyUser( const lobbyUser_t * lobbyUser ) const; void FreeAllUsers(); void RegisterUser( lobbyUser_t * lobbyUser ); void UnregisterUser( lobbyUser_t * lobbyUser ); bool IsSessionUserLocal( const lobbyUser_t * lobbyUser ) const; bool IsSessionUserIndexLocal( int i ) const; int GetLobbyUserIndexByID( lobbyUserID_t lobbyUserId, bool ignoreLobbyType = false ) const; lobbyUser_t * GetLobbyUserByID( lobbyUserID_t lobbyUserId, bool ignoreLobbyType = false ); // Helper function to create a lobby user from a local user lobbyUser_t CreateLobbyUserFromLocalUser( const idLocalUser * localUser ); // This function is designed to initialize the session users of type lobbyType (TYPE_GAME or TYPE_PARTY) // to the current list of local users that are being tracked by the sign-in manager void InitSessionUsersFromLocalUsers( bool onlineMatch ); // Convert an local userhandle to a session user (-1 if there is no session user with this handle) int GetLobbyUserIndexByLocalUserHandle( const localUserHandle_t localUserHandle ) const; // This takes a session user, and converts to a controller user idLocalUser * GetLocalUserFromLobbyUserIndex( int lobbyUserIndex ); // Takes a controller user, and converts to a session user (will return NULL if there is no session user for this controller user) lobbyUser_t * GetSessionUserFromLocalUser( const idLocalUser * controller ); void RemoveUsersWithDisconnectedPeers(); void RemoveSessionUsersByIDList( idList< lobbyUserID_t > & usersToRemoveByID ); void SendNewUsersToPeers( int skipPeer, int userStart, int numUsers ); void SendPeersMicStatusToNewUsers( int peerNumber ); void AddUsersFromMsg( idBitMsg & msg, int fromPeer ); void UpdateSessionUserOnPeers( idBitMsg & msg ); void HandleUpdateSessionUser( idBitMsg & msg ); void CreateUserUpdateMessage( int userIndex, idBitMsg & msg ); void UpdateLocalSessionUsers(); int PeerIndexForSessionUserIndex( int sessionUserIndex ) const; void HandleUserConnectFailure( int p, idBitMsg & inMsg, int reliableType ); void ProcessUserDisconnectMsg( idBitMsg & msg ); void CompactDisconnectedUsers(); // Sends a request to the host to join a local user to a session void RequestLocalUserJoin( idLocalUser * localUser ); // Sends a request to the host to remove a session user from the session void RequestSessionUserDisconnect( int sessionUserIndex ); // This function sycs the session users with the current list of of local users on the signin manager. // It will remove the session users that are either no longer on the signin manager, or it // will remove them if they are no longer allowed to be in the session. // If it finds a local users that are not in a particular session, it will add that user if allowed. void SyncLobbyUsersWithLocalUsers( bool allowJoin, bool onlineMatch ); bool ValidateConnectedUser( const lobbyUser_t * user ) const; virtual bool IsLobbyUserDisconnected( int userIndex ) const; virtual bool IsLobbyUserValid( lobbyUserID_t lobbyUserID ) const; virtual bool IsLobbyUserLoaded( lobbyUserID_t lobbyUserID ) const; virtual bool LobbyUserHasFirstFullSnap( lobbyUserID_t lobbyUserID ) const; virtual lobbyUserID_t GetLobbyUserIdByOrdinal( int userIndex ) const; virtual int GetLobbyUserIndexFromLobbyUserID( lobbyUserID_t lobbyUserID ) const; virtual void EnableSnapshotsForLobbyUser( lobbyUserID_t lobbyUserID ); virtual bool IsPeerDisconnected( int peerIndex ) const { return !peers[peerIndex].IsConnected(); } float GetAverageSessionLevel(); float GetAverageLocalUserLevel( bool onlineOnly ); void QueueReliablePlayerToPlayerMessage( int fromSessionUserIndex, int toSessionUserIndex, reliablePlayerToPlayer_t type, const byte * data, int dataLen ); virtual void KickLobbyUser( lobbyUserID_t lobbyUserID ); int GetNumConnectedUsers() const; // // sys_session_instance_migrate.cpp // bool IsBetterHost( int ping1, lobbyUserID_t userId1, int ping2, lobbyUserID_t userId2 ); int FindMigrationInviteIndex( lobbyAddress_t & address ); void UpdateHostMigration(); void BuildMigrationInviteList( bool inviteOldHost ); void PickNewHost( bool forceMe = false, bool inviteOldHost = false ); void PickNewHostInternal( bool forceMe, bool inviteOldHost ); void BecomeHost(); void EndMigration(); void ResetAllMigrationState(); void SendMigrationGameData(); bool GetMigrationGameData( idBitMsg &msg, bool reading ); bool GetMigrationGameDataUser( lobbyUserID_t lobbyUserID, idBitMsg &msg, bool reading ); // // Snapshots // sys_session_instance_snapshot.cpp // void UpdateSnaps(); bool SendCompletedSnaps(); bool SendResources( int p ); bool SubmitPendingSnap( int p ); void SendCompletedPendingSnap( int p ); void CheckPeerThrottle( int p ); void ApplySnapshotDelta( int p, int snapshotNumber ); bool ApplySnapshotDeltaInternal( int p, int snapshotNumber ); void SendSnapshotToPeer( idSnapShot & ss, int p ); bool AllPeersHaveBaseState(); void ThrottleSnapsForXSeconds( int p, int seconds, bool recoverPing ); bool FirstSnapHasBeenSent( int p ); virtual bool EnsureAllPeersHaveBaseState(); virtual bool AllPeersHaveStaleSnapObj( int objId ); virtual bool AllPeersHaveExpectedSnapObj( int objId ); virtual void MarkSnapObjDeleted( int objId ); virtual void RefreshSnapObj( int objId ); void ResetBandwidthStats(); void DetectSaturation( int p ); virtual void AddSnapObjTemplate( int objID, idBitMsg & msg ); static const int MAX_PEERS = MAX_PLAYERS; //------------------------ // Pings //------------------------ struct pktPingValues_t { idArray pings; }; static const int PING_INTERVAL_MS = 3000; int lastPingValuesRecvTime; // so clients can display something when server stops pinging int nextSendPingValuesTime; // the next time to send RELIABLE_PING_VALUES static const int MIGRATION_GAME_DATA_INTERVAL_MS = 1000; int nextSendMigrationGameTime; // when to send next migration game data int nextSendMigrationGamePeer; // who to send next migration game data to lobbyType_t lobbyType; lobbyState_t state; // State of this lobby failedReason_t failedReason; int host; // which peer is the host of this type of session (-1 if we are the host) int peerIndexOnHost; // -1 if we are the host lobbyAddress_t hostAddress; // address of the host for this type of session bool isHost; // true if we are the host idLobbyBackend * lobbyBackend; int helloStartTime; // Used to determine when the first hello was sent int lastConnectRequest; // Used to determine when the last hello was sent int connectionAttempts; // Number of connection attempts bool needToDisplayMigrateMsg; // If true, we migrated as host, so we need to display the msg as soon as the lobby is active gameDialogMessages_t migrationDlg; // current migration dialog we should be showing uint8 migrateMsgFlags; // cached match flags from the old game we migrated from, so we know what type of msg to display bool joiningMigratedGame; // we are joining a migrated game and need to tell the session mgr if we succeed or fail // ------------------------ // Bandwidth challenge // ------------------------ int bandwidthChallengeEndTime; // When the challenge will end/timeout int bandwidthChallengeStartTime; // time in MS the challenge started bool bandwidthChallengeFinished; // (HOST) test is finished and we received results back from all peers (or timed out) int bandwidthChallengeNumGoodSeq; // (PEER) num of good, in order packets we recevieved int lastSnapBspHistoryUpdateSequence; void SaveDisconnectedUser( const lobbyUser_t & user ); // This is needed to get the a user's gamertag after disconnection. idSessionCallbacks * sessionCB; enum migrationState_t { MIGRATE_NONE, MIGRATE_PICKING_HOST, MIGRATE_BECOMING_HOST, }; struct migrationInvite_t { migrationInvite_t() { lastInviteTime = -1; pingMs = 0; migrationGameData = -1; } lobbyAddress_t address; int pingMs; lobbyUserID_t userId; int lastInviteTime; int migrationGameData; }; struct migrationInfo_t { migrationInfo_t() { state = MIGRATE_NONE; ourPingMs = 0; ourUserId = lobbyUserID_t(); } migrationState_t state; idStaticList< migrationInvite_t, MAX_PEERS > invites; int migrationStartTime; int ourPingMs; lobbyUserID_t ourUserId; struct persistUntilGameEnds_t { persistUntilGameEnds_t() { Clear(); } void Clear() { wasMigratedHost = false; wasMigratedJoin = false; wasMigratedGame = false; ourGameData = -1; hasGameData = false; hasRelaunchedMigratedGame = false; memset( gameData, 0, sizeof( gameData ) ); memset( gameDataUser, 0, sizeof( gameDataUser ) ); } int ourGameData; bool wasMigratedHost; // we are hosting a migrated session bool wasMigratedJoin; // we joined a migrated session bool wasMigratedGame; // If true, we migrated from a game bool hasRelaunchedMigratedGame; // A generic blob of data that the gamechallenge (or anything else) can read and write to for host migration static const int MIGRATION_GAME_DATA_SIZE = 32; byte gameData[ MIGRATION_GAME_DATA_SIZE ]; static const int MIGRATION_GAME_DATA_USER_SIZE = 64; byte gameDataUser[ MAX_PLAYERS ][ MIGRATION_GAME_DATA_USER_SIZE ]; bool hasGameData; } persistUntilGameEndsData; }; struct disconnectedUser_t { lobbyUserID_t lobbyUserID; // Locally generated to be unique, and internally keeps the local user handle char gamertag[lobbyUser_t::MAX_GAMERTAG]; }; migrationInfo_t migrationInfo; bool showHostLeftTheSession; bool connectIsFromInvite; idList< lobbyConnectInfo_t > searchResults; typedef idStaticList< lobbyUser_t *, MAX_PLAYERS > idLobbyUserList; typedef idStaticList< lobbyUser_t, MAX_PLAYERS > idLobbyUserPool; idLobbyUserList userList; // list of currently connected users to this lobby idLobbyUserList freeUsers; // list of free users idLobbyUserPool userPool; idList< disconnectedUser_t> disconnectedUsers; // List of users which were connected, but aren't anymore, for printing their name on the hud idStaticList< peer_t, MAX_PEERS > peers; // Unique machines connected to this lobby uint32 partyToken; idMatchParameters parms; bool loaded; // Used for game sessions, whether this machine is loaded or not bool respondToArbitrate; // true when the host has requested us to arbitrate our session (for TYPE_GAME only) bool everyoneArbitrated; bool waitForPartyOk; bool startLoadingFromHost; //------------------------ // Snapshot jobs //------------------------ static const int SNAP_OBJ_JOB_MEMORY = 1024 * 128; // 128k of obj memory lzwCompressionData_t * lzwData; // Shared across all snapshot jobs uint8 * objMemory; // Shared across all snapshot jobs bool haveSubmittedSnaps; // True if we previously submitted snaps to jobs idSnapShot * localReadSS; struct snapDeltaAck_t { int p; int snapshotNumber; }; idStaticList< snapDeltaAck_t, 16 > snapDeltaAckQueue; }; /* ======================== idSessionCallbacks ======================== */ class idSessionCallbacks { public: virtual idLobby & GetPartyLobby() = 0; virtual idLobby & GetGameLobby() = 0; virtual idLobby & GetActingGameStateLobby() = 0; virtual idLobby * GetLobbyFromType( idLobby::lobbyType_t lobbyType ) = 0; virtual int GetUniquePlayerId() const = 0; virtual idSignInManagerBase & GetSignInManager() = 0; virtual void SendRawPacket( const lobbyAddress_t & to, const void * data, int size, bool useDirectPort ) = 0; virtual bool BecomingHost( idLobby & lobby ) = 0; // Called when a lobby is about to become host virtual void BecameHost( idLobby & lobby ) = 0; // Called when a lobby becomes a host virtual bool BecomingPeer( idLobby & lobby ) = 0; // Called when a lobby is about to become peer virtual void BecamePeer( idLobby & lobby ) = 0; // Called when a lobby becomes a peer virtual void FailedGameMigration( idLobby & lobby ) = 0; virtual void MigrationEnded( idLobby & lobby ) = 0; virtual void GoodbyeFromHost( idLobby & lobby, int peerNum, const lobbyAddress_t & remoteAddress, int msgType ) = 0; virtual uint32 GetSessionOptions() = 0; virtual bool AnyPeerHasAddress( const lobbyAddress_t & remoteAddress ) const = 0; virtual idSession::sessionState_t GetState() const = 0; virtual void ClearMigrationState() = 0; // Called when the lobby receives a RELIABLE_ENDMATCH msg virtual void EndMatchInternal( bool premature=false ) = 0; // Called when the game lobby receives leaderboard stats virtual void RecvLeaderboardStats( idBitMsg & msg ) = 0; // Called once the lobby received its first full snap (used to advance from LOADING to INGAME state) virtual void ReceivedFullSnap() = 0; // Called when lobby received RELIABLE_PARTY_LEAVE_GAME_LOBBY msg virtual void LeaveGameLobby() = 0; virtual void PrePickNewHost( idLobby & lobby, bool forceMe, bool inviteOldHost ) = 0; virtual bool PreMigrateInvite( idLobby & lobby ) = 0; virtual void HandleOobVoiceAudio( const lobbyAddress_t & from, const idBitMsg & msg ) = 0; // ConnectAndMoveToLobby is called when the lobby receives a RELIABLE_CONNECT_AND_MOVE_TO_LOBBY virtual void ConnectAndMoveToLobby( idLobby::lobbyType_t destLobbyType, const lobbyConnectInfo_t & connectInfo, bool waitForPartyOk ) = 0; virtual class idVoiceChatMgr * GetVoiceChat() = 0; virtual void HandleServerQueryRequest( lobbyAddress_t & remoteAddr, idBitMsg & msg, int msgType ) = 0; virtual void HandleServerQueryAck( lobbyAddress_t & remoteAddr, idBitMsg & msg ) = 0; virtual void HandlePeerMatchParamUpdate( int peer, int msg ) = 0; virtual idLobbyBackend * CreateLobbyBackend( const idMatchParameters & p, float skillLevel, idLobbyBackend::lobbyBackendType_t lobbyType ) = 0; virtual idLobbyBackend * FindLobbyBackend( const idMatchParameters & p, int numPartyUsers, float skillLevel, idLobbyBackend::lobbyBackendType_t lobbyType ) = 0; virtual idLobbyBackend * JoinFromConnectInfo( const lobbyConnectInfo_t & connectInfo , idLobbyBackend::lobbyBackendType_t lobbyType ) = 0; virtual void DestroyLobbyBackend( idLobbyBackend * lobbyBackend ) = 0; };