mirror of
https://github.com/id-Software/DOOM-3-BFG.git
synced 2024-11-23 04:31:58 +00:00
914 lines
39 KiB
C++
914 lines
39 KiB
C++
/*
|
|
===========================================================================
|
|
|
|
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 <http://www.gnu.org/licenses/>.
|
|
|
|
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<short, MAX_PEERS> 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;
|
|
};
|