@ -76,6 +76,7 @@ namespace Multiplayer
" The address of the remote server or host to connect to " ) ;
AZ_CVAR ( uint16_t , cl_serverport , DefaultServerPort , nullptr , AZ : : ConsoleFunctorFlags : : DontReplicate , " The port of the remote host to connect to for game traffic " ) ;
AZ_CVAR ( uint16_t , sv_port , DefaultServerPort , nullptr , AZ : : ConsoleFunctorFlags : : DontReplicate , " The port that this multiplayer gem will bind to for game traffic " ) ;
AZ_CVAR ( uint16_t , sv_portRange , 999 , nullptr , AZ : : ConsoleFunctorFlags : : DontReplicate , " The range of ports the host will incrementally attempt to bind to when initializing " ) ;
AZ_CVAR ( AZ : : CVarFixedString , sv_map , " nolevel " , nullptr , AZ : : ConsoleFunctorFlags : : DontReplicate , " The map the server should load " ) ;
AZ_CVAR ( ProtocolType , sv_protocol , ProtocolType : : Udp , nullptr , AZ : : ConsoleFunctorFlags : : DontReplicate , " This flag controls whether we use TCP or UDP for game networking " ) ;
AZ_CVAR ( bool , sv_isDedicated , true , nullptr , AZ : : ConsoleFunctorFlags : : DontReplicate , " Whether the host command creates an independent or client hosted server " ) ;
@ -168,6 +169,7 @@ namespace Multiplayer
AZ : : ConsoleFunctorFlags flags ,
AZ : : ConsoleInvokedFrom invokedFrom
) { OnConsoleCommandInvoked ( command , args , flags , invokedFrom ) ; } )
, m_autonomousEntityReplicatorCreatedHandler ( [ this ] ( [[maybe_unused]] NetEntityId netEntityId ) { OnAutonomousEntityReplicatorCreated ( ) ; } )
{
AZ : : Interface < IMultiplayer > : : Register ( this ) ;
}
@ -205,8 +207,23 @@ namespace Multiplayer
bool MultiplayerSystemComponent : : StartHosting ( uint16_t port , bool isDedicated )
{
if ( port ! = sv_port )
{
sv_port = port ;
}
InitializeMultiplayer ( isDedicated ? MultiplayerAgentType : : DedicatedServer : MultiplayerAgentType : : ClientServer ) ;
return m_networkInterface - > Listen ( port ) ;
const uint16_t maxPort = sv_port + sv_portRange ;
while ( sv_port < = maxPort )
{
if ( m_networkInterface - > Listen ( sv_port ) )
{
return true ;
}
AZLOG_WARN ( " Failed to start listening on port %u, port is in use? " , static_cast < uint32_t > ( sv_port ) ) ;
sv_port = sv_port + 1 ;
}
return false ;
}
bool MultiplayerSystemComponent : : Connect ( const AZStd : : string & remoteAddress , uint16_t port )
@ -310,6 +327,11 @@ namespace Multiplayer
void MultiplayerSystemComponent : : OnTick ( float deltaTime , [[maybe_unused]] AZ : : ScriptTimePoint time )
{
if ( bg_multiplayerDebugDraw )
{
m_networkEntityManager . DebugDraw ( ) ;
}
const AZ : : TimeMs deltaTimeMs = aznumeric_cast < AZ : : TimeMs > ( static_cast < int32_t > ( deltaTime * 1000.0f ) ) ;
const AZ : : TimeMs serverRateMs = static_cast < AZ : : TimeMs > ( sv_serverSendRateMs ) ;
const float serverRateSeconds = static_cast < float > ( serverRateMs ) / 1000.0f ;
@ -394,11 +416,6 @@ namespace Multiplayer
{
m_networkInterface - > GetConnectionSet ( ) . VisitConnections ( visitor ) ;
}
if ( bg_multiplayerDebugDraw )
{
m_networkEntityManager . DebugDraw ( ) ;
}
}
int MultiplayerSystemComponent : : GetTickOrder ( )
@ -469,17 +486,41 @@ namespace Multiplayer
auto visitor = [ ] ( IConnection & connection ) { connection . Disconnect ( DisconnectReason : : TerminatedByUser , TerminationEndpoint : : Local ) ; } ;
m_networkInterface - > GetConnectionSet ( ) . VisitConnections ( visitor ) ;
return true ;
}
}
}
reinterpret_cast < ServerToClientConnectionData * > ( connection - > GetUserData ( ) ) - > SetProviderTicket ( packet . GetTicket ( ) . c_str ( ) ) ;
// Hosts will spawn a new default player prefab for the user that just connected
if ( GetAgentType ( ) = = MultiplayerAgentType : : ClientServer
| | GetAgentType ( ) = = MultiplayerAgentType : : DedicatedServer )
{
// We use a temporary userId so we can maintain client lookups even in the event of wifi handoff
NetworkEntityHandle controlledEntity = SpawnDefaultPlayerPrefab ( packet . GetTemporaryUserId ( ) ) ;
if ( controlledEntity . Exists ( ) )
{
controlledEntity . GetNetBindComponent ( ) - > SetOwningConnectionId ( connection - > GetConnectionId ( ) ) ;
}
// Activate the entity if necessary
if ( controlledEntity . GetEntity ( ) - > GetState ( ) = = AZ : : Entity : : State : : Init )
{
controlledEntity . Activate ( ) ;
}
ServerToClientConnectionData * connectionData = reinterpret_cast < ServerToClientConnectionData * > ( connection - > GetUserData ( ) ) ;
AZStd : : unique_ptr < IReplicationWindow > window = AZStd : : make_unique < ServerToClientReplicationWindow > ( controlledEntity , connection ) ;
connectionData - > GetReplicationManager ( ) . SetReplicationWindow ( AZStd : : move ( window ) ) ;
connectionData - > SetControlledEntity ( controlledEntity ) ;
}
if ( connection - > SendReliablePacket ( MultiplayerPackets : : Accept ( sv_map ) ) )
{
m_didHandshake = true ;
// Sync our console
ConsoleReplicator consoleReplicator ( connection ) ;
AZ : : Interface < AZ : : IConsole > : : Get ( ) - > VisitRegisteredFunctors ( [ & consoleReplicator ] ( AZ : : ConsoleFunctorBase * functor ) { consoleReplicator . Visit ( functor ) ; } ) ;
if ( packet . GetTemporaryUserId ( ) = = 0 )
{
// Sync our console
ConsoleReplicator consoleReplicator ( connection ) ;
AZ : : Interface < AZ : : IConsole > : : Get ( ) - > VisitRegisteredFunctors ( [ & consoleReplicator ] ( AZ : : ConsoleFunctorBase * functor ) { consoleReplicator . Visit ( functor ) ; } ) ;
}
return true ;
}
return false ;
@ -493,10 +534,24 @@ namespace Multiplayer
)
{
m_didHandshake = true ;
AZ : : CVarFixedString commandString = " sv_map " + packet . GetMap ( ) ;
AZ : : Interface < AZ : : IConsole > : : Get ( ) - > PerformCommand ( commandString . c_str ( ) ) ;
AZ : : CVarFixedString loadLevelString = " LoadLevel " + packet . GetMap ( ) ;
AZ : : Interface < AZ : : IConsole > : : Get ( ) - > PerformCommand ( loadLevelString . c_str ( ) ) ;
if ( m_temporaryUserIdentifier = = 0 )
{
AZ : : CVarFixedString commandString = " sv_map " + packet . GetMap ( ) ;
AZ : : Interface < AZ : : IConsole > : : Get ( ) - > PerformCommand ( commandString . c_str ( ) ) ;
AZ : : CVarFixedString loadLevelString = " LoadLevel " + packet . GetMap ( ) ;
AZ : : Interface < AZ : : IConsole > : : Get ( ) - > PerformCommand ( loadLevelString . c_str ( ) ) ;
}
else
{
OnAutonomousEntityReplicatorCreated ( ) ;
// IConnectionData* connectionData = reinterpret_cast<IConnectionData*>(connection->GetUserData());
// if (connectionData)
// {
// // @nt: TODO - delete once dropped RPC problem fixed
// // Connection has migrated, we are now waiting for the autonomous entity replicator to be created
// connectionData->GetReplicationManager().AddAutonomousEntityReplicatorCreatedHandler(m_autonomousEntityReplicatorCreatedHandler);
// }
}
return true ;
}
@ -617,13 +672,17 @@ namespace Multiplayer
// Store the temporary user identifier so we can transmit it with our next Connect packet
// The new server will use this to re-attach our set of autonomous entities
m_temporaryUserIdentifier = packet . GetTemporaryUserIdentifier ( ) ;
// Disconnect our existing server connection
auto visitor = [ ] ( IConnection & connection ) { connection . Disconnect ( DisconnectReason : : ClientMigrated , TerminationEndpoint : : Local ) ; } ;
m_networkInterface - > GetConnectionSet ( ) . VisitConnections ( visitor ) ;
AZLOG_INFO ( " Migrating to new server shard " ) ;
m_clientMigrationStartEvent . Signal ( packet . GetLastClientInputId ( ) ) ;
m_networkInterface - > Connect ( packet . GetRemoteServerAddress ( ) ) ;
if ( m_networkInterface - > Connect ( packet . GetRemoteServerAddress ( ) ) = = AzNetworking : : InvalidConnectionId )
{
AZLOG_ERROR ( " Failed to connect to new host during client migration event " ) ;
}
return true ;
}
@ -653,7 +712,7 @@ namespace Multiplayer
providerTicket = m_pendingConnectionTickets . front ( ) ;
m_pendingConnectionTickets . pop ( ) ;
}
connection - > SendReliablePacket ( MultiplayerPackets : : Connect ( 0 , providerTicket. c_str ( ) ) ) ;
connection - > SendReliablePacket ( MultiplayerPackets : : Connect ( 0 , m_temporaryUserIdentifier, providerTicket. c_str ( ) ) ) ;
}
else
{
@ -661,20 +720,10 @@ namespace Multiplayer
m_connectionAcquiredEvent . Signal ( datum ) ;
}
// Hosts will spawn a new default player prefab for the user that just connected
if ( GetAgentType ( ) = = MultiplayerAgentType : : ClientServer
| | GetAgentType ( ) = = MultiplayerAgentType : : DedicatedServer )
{
NetworkEntityHandle controlledEntity = SpawnDefaultPlayerPrefab ( ) ;
if ( controlledEntity . Exists ( ) )
{
controlledEntity . GetNetBindComponent ( ) - > SetOwningConnectionId ( connection - > GetConnectionId ( ) ) ;
}
controlledEntity . Activate ( ) ;
connection - > SetUserData ( new ServerToClientConnectionData ( connection , * this , controlledEntity ) ) ;
AZStd : : unique_ptr < IReplicationWindow > window = AZStd : : make_unique < ServerToClientReplicationWindow > ( controlledEntity , connection ) ;
reinterpret_cast < ServerToClientConnectionData * > ( connection - > GetUserData ( ) ) - > GetReplicationManager ( ) . SetReplicationWindow ( AZStd : : move ( window ) ) ;
connection - > SetUserData ( new ServerToClientConnectionData ( connection , * this ) ) ;
}
else
{
@ -696,9 +745,9 @@ namespace Multiplayer
void MultiplayerSystemComponent : : OnDisconnect ( AzNetworking : : IConnection * connection , DisconnectReason reason , TerminationEndpoint endpoint )
{
const char * endpointString = ( endpoint = = TerminationEndpoint : : Local ) ? " Disconnecting " : " Remote host disconnected" ;
const char * endpointString = ( endpoint = = TerminationEndpoint : : Local ) ? " Disconnecting " : " Remote ly disconnected" ;
AZStd : : string reasonString = ToString ( reason ) ;
AZLOG_INFO ( " %s due to %s from remote address: %s" , endpointString , reasonString. c_str ( ) , connection- > GetRemoteAddress ( ) . GetString ( ) . c_str ( ) ) ;
AZLOG_INFO ( " %s from remote address %s due to %s" , endpointString , connection- > GetRemoteAddress ( ) . GetString ( ) . c_str ( ) , reasonString . c_str ( ) ) ;
// The client is disconnecting
if ( GetAgentType ( ) = = MultiplayerAgentType : : Client )
@ -780,7 +829,7 @@ namespace Multiplayer
// Spawn the default player for this host since the host is also a player (not a dedicated server)
if ( m_agentType = = MultiplayerAgentType : : ClientServer )
{
NetworkEntityHandle controlledEntity = SpawnDefaultPlayerPrefab ( ) ;
NetworkEntityHandle controlledEntity = SpawnDefaultPlayerPrefab ( 0 ) ;
if ( NetBindComponent * controlledEntityNetBindComponent = controlledEntity . GetNetBindComponent ( ) )
{
controlledEntityNetBindComponent - > SetAllowAutonomy ( true ) ;
@ -831,9 +880,9 @@ namespace Multiplayer
handler . Connect ( m_shutdownEvent ) ;
}
void MultiplayerSystemComponent : : SendNotifyClientMigrationEvent ( const HostId & hostId , uint64_t userIdentifier , ClientInputId lastClientInputId )
void MultiplayerSystemComponent : : SendNotifyClientMigrationEvent ( const HostId & hostId , uint64_t userIdentifier , ClientInputId lastClientInputId , NetEntityId controlledEntityId )
{
m_notifyClientMigrationEvent . Signal ( hostId , userIdentifier , lastClientInputId );
m_notifyClientMigrationEvent . Signal ( hostId , userIdentifier , lastClientInputId , controlledEntityId );
}
void MultiplayerSystemComponent : : SendNotifyEntityMigrationEvent ( const ConstNetworkEntityHandle & entityHandle , const HostId & remoteHostId )
@ -887,6 +936,11 @@ namespace Multiplayer
return m_filterEntityManager ;
}
void MultiplayerSystemComponent : : RegisterPlayerIdentifierForRejoin ( uint64_t temporaryUserIdentifier , NetEntityId controlledEntityId )
{
m_playerRejoinData [ temporaryUserIdentifier ] = controlledEntityId ;
}
void MultiplayerSystemComponent : : SetShouldSpawnNetworkEntities ( bool value )
{
m_spawnNetboundEntities = value ;
@ -1017,6 +1071,13 @@ namespace Multiplayer
m_cvarCommands . PushBackItem ( AZStd : : move ( replicateString ) ) ;
}
void MultiplayerSystemComponent : : OnAutonomousEntityReplicatorCreated ( )
{
m_autonomousEntityReplicatorCreatedHandler . Disconnect ( ) ;
//m_networkEntityManager.GetNetworkEntityAuthorityTracker()->ResetTimeoutTime(AZ::TimeMs{ 2000 });
m_clientMigrationEndEvent . Signal ( ) ;
}
void MultiplayerSystemComponent : : ExecuteConsoleCommandList ( IConnection * connection , const AZStd : : fixed_vector < Multiplayer : : LongNetworkString , 32 > & commands )
{
AZ : : IConsole * console = AZ : : Interface < AZ : : IConsole > : : Get ( ) ;
@ -1028,8 +1089,14 @@ namespace Multiplayer
}
}
NetworkEntityHandle MultiplayerSystemComponent : : SpawnDefaultPlayerPrefab ( )
NetworkEntityHandle MultiplayerSystemComponent : : SpawnDefaultPlayerPrefab ( uint64_t temporaryUserIdentifier )
{
const auto node = m_playerRejoinData . find ( temporaryUserIdentifier ) ;
if ( node ! = m_playerRejoinData . end ( ) )
{
return m_networkEntityManager . GetNetworkEntityTracker ( ) - > Get ( node - > second ) ;
}
PrefabEntityId playerPrefabEntityId ( AZ : : Name ( static_cast < AZ : : CVarFixedString > ( sv_defaultPlayerSpawnAsset ) . c_str ( ) ) ) ;
INetworkEntityManager : : EntityList entityList = m_networkEntityManager . CreateEntitiesImmediate ( playerPrefabEntityId , NetEntityRole : : Authority , AZ : : Transform : : CreateIdentity ( ) , Multiplayer : : AutoActivate : : DoNotActivate ) ;
@ -1045,7 +1112,7 @@ namespace Multiplayer
{
if ( ! AZ : : Interface < IMultiplayer > : : Get ( ) - > StartHosting ( sv_port , sv_isDedicated ) )
{
AZLOG_ERROR ( " Failed to start listening on port %u, port is in use?" , static_cast < uint32_t > ( sv_port ) ) ;
AZLOG_ERROR ( " Failed to start listening on any allocated port" ) ;
}
}
AZ_CONSOLEFREEFUNC ( host , AZ : : ConsoleFunctorFlags : : DontReplicate , " Opens a multiplayer connection as a host for other clients to connect to " ) ;