diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetBindComponent.h b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetBindComponent.h index f604102e37..b22c8dc51c 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetBindComponent.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetBindComponent.h @@ -32,7 +32,7 @@ namespace Multiplayer using EntityStopEvent = AZ::Event; using EntityDirtiedEvent = AZ::Event<>; using EntitySyncRewindEvent = AZ::Event<>; - using EntityServerMigrationEvent = AZ::Event; + using EntityServerMigrationEvent = AZ::Event; using EntityPreRenderEvent = AZ::Event; using EntityCorrectionEvent = AZ::Event<>; @@ -113,7 +113,7 @@ namespace Multiplayer void MarkDirty(); void NotifyLocalChanges(); void NotifySyncRewindState(); - void NotifyServerMigration(const HostId& hostId, AzNetworking::ConnectionId connectionId); + void NotifyServerMigration(const HostId& remoteHostId, const HostId& migrateHostId, AzNetworking::ConnectionId connectionId); void NotifyPreRender(float deltaTime); void NotifyCorrection(); diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/IMultiplayer.h b/Gems/Multiplayer/Code/Include/Multiplayer/IMultiplayer.h index 32af39a43b..b4be69ed4c 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/IMultiplayer.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/IMultiplayer.h @@ -45,7 +45,7 @@ namespace Multiplayer using ClientMigrationStartEvent = AZ::Event; using ClientMigrationEndEvent = AZ::Event<>; using ClientDisconnectedEvent = AZ::Event<>; - using NotifyClientMigrationEvent = AZ::Event; + using NotifyClientMigrationEvent = AZ::Event; using NotifyEntityMigrationEvent = AZ::Event; using ConnectionAcquiredEvent = AZ::Event; using SessionInitEvent = AZ::Event; @@ -131,10 +131,11 @@ namespace Multiplayer virtual void AddSessionShutdownHandler(SessionShutdownEvent::Handler& handler) = 0; //! Signals a NotifyClientMigrationEvent with the provided parameters. - //! @param hostId the host id of the host the client is migrating to - //! @param userIdentifier the user identifier the client will provide the new host to validate identity - //! @param lastClientInputId the last processed clientInputId by the current host - virtual void SendNotifyClientMigrationEvent(const HostId& hostId, uint64_t userIdentifier, ClientInputId lastClientInputId) = 0; + //! @param hostId the host id of the host the client is migrating to + //! @param userIdentifier the user identifier the client will provide the new host to validate identity + //! @param lastClientInputId the last processed clientInputId by the current host + //! @param controlledEntityId the entityId of the clients autonomous entity + virtual void SendNotifyClientMigrationEvent(const HostId& hostId, uint64_t userIdentifier, ClientInputId lastClientInputId, NetEntityId controlledEntityId) = 0; //! Signals a NotifyEntityMigrationEvent with the provided parameters. //! @param entityHandle the network entity handle of the entity being migrated @@ -176,6 +177,11 @@ namespace Multiplayer //! @return pointer to the filtered entity manager, or nullptr if not set virtual IFilterEntityManager* GetFilterEntityManager() = 0; + //! Registers a temp userId to allow a host to look up a players controlled entity in the event of a rejoin or migration event. + //! @param temporaryUserIdentifier the temporary user identifier used to identify a player across hosts + //! @param controlledEntityId the controlled entityId of the players autonomous entity + virtual void RegisterPlayerIdentifierForRejoin(uint64_t temporaryUserIdentifier, NetEntityId controlledEntityId) = 0; + //! Enables or disables automatic instantiation of netbound entities. //! This setting is controlled by the networking layer and should not be touched //! If enabled, netbound entities will instantiate as spawnables are loaded into the game world, generally true for the server diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/MultiplayerTypes.h b/Gems/Multiplayer/Code/Include/Multiplayer/MultiplayerTypes.h index 96035083d8..8284dd7010 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/MultiplayerTypes.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/MultiplayerTypes.h @@ -68,6 +68,7 @@ namespace Multiplayer Server, // A simulated proxy on a server Authority // An authoritative proxy on a server (full authority) }; + const char* GetEnumString(NetEntityRole value); enum class ComponentSerializationType : uint8_t { @@ -113,6 +114,24 @@ namespace Multiplayer bool Serialize(AzNetworking::ISerializer& serializer); }; + inline const char* GetEnumString(NetEntityRole value) + { + switch (value) + { + case NetEntityRole::InvalidRole: + return "InvalidRole"; + case NetEntityRole::Client: + return "Client"; + case NetEntityRole::Autonomous: + return "Autonomous"; + case NetEntityRole::Server: + return "Server"; + case NetEntityRole::Authority: + return "Authority"; + } + return "Unknown"; + } + inline PrefabEntityId::PrefabEntityId(AZ::Name name, uint32_t entityOffset) : m_prefabName(name) , m_entityOffset(entityOffset) diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/EntityReplication/EntityReplicationManager.h b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/EntityReplication/EntityReplicationManager.h index 10346ad777..83f9e238f5 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/EntityReplication/EntityReplicationManager.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/EntityReplication/EntityReplicationManager.h @@ -57,6 +57,10 @@ namespace Multiplayer EntityReplicationManager(AzNetworking::IConnection& connection, AzNetworking::IConnectionListener& connectionListener, Mode mode); ~EntityReplicationManager() = default; + //! Used to override during client migration if your host has a specially assigned publically routable address. + //! @param remoteHostId the publically routable address to use in place of the remote HostId + void SetMigrateHostId(const HostId& remoteHostId); + const HostId& GetMigrateHostId() const; const HostId& GetRemoteHostId() const; void ActivatePendingEntities(); @@ -207,6 +211,7 @@ namespace Multiplayer AZ::TimeMs m_entityPendingRemovalMs = AZ::TimeMs{ 0 }; AZ::TimeMs m_frameTimeMs = AZ::TimeMs{ 0 }; HostId m_remoteHostId = InvalidHostId; + HostId m_migrateHostId = InvalidHostId; uint32_t m_maxRemoteEntitiesPendingCreationCount = AZStd::numeric_limits::max(); uint32_t m_maxPayloadSize = 0; Mode m_updateMode = Mode::Invalid; diff --git a/Gems/Multiplayer/Code/Source/AutoGen/Multiplayer.AutoPackets.xml b/Gems/Multiplayer/Code/Source/AutoGen/Multiplayer.AutoPackets.xml index 091043f034..a5bf169fb4 100644 --- a/Gems/Multiplayer/Code/Source/AutoGen/Multiplayer.AutoPackets.xml +++ b/Gems/Multiplayer/Code/Source/AutoGen/Multiplayer.AutoPackets.xml @@ -9,6 +9,7 @@ + diff --git a/Gems/Multiplayer/Code/Source/Components/NetBindComponent.cpp b/Gems/Multiplayer/Code/Source/Components/NetBindComponent.cpp index fbf83b6e6c..2e451793ed 100644 --- a/Gems/Multiplayer/Code/Source/Components/NetBindComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Components/NetBindComponent.cpp @@ -394,9 +394,9 @@ namespace Multiplayer m_syncRewindEvent.Signal(); } - void NetBindComponent::NotifyServerMigration(const HostId& hostId, AzNetworking::ConnectionId connectionId) + void NetBindComponent::NotifyServerMigration(const HostId& remoteHostId, const HostId& migrateHostId, AzNetworking::ConnectionId connectionId) { - m_entityServerMigrationEvent.Signal(m_netEntityHandle, hostId, connectionId); + m_entityServerMigrationEvent.Signal(m_netEntityHandle, remoteHostId, migrateHostId, connectionId); } void NetBindComponent::NotifyPreRender(float deltaTime) diff --git a/Gems/Multiplayer/Code/Source/ConnectionData/ServerToClientConnectionData.cpp b/Gems/Multiplayer/Code/Source/ConnectionData/ServerToClientConnectionData.cpp index a9b8e03126..29be64ac07 100644 --- a/Gems/Multiplayer/Code/Source/ConnectionData/ServerToClientConnectionData.cpp +++ b/Gems/Multiplayer/Code/Source/ConnectionData/ServerToClientConnectionData.cpp @@ -23,22 +23,16 @@ namespace Multiplayer ServerToClientConnectionData::ServerToClientConnectionData ( AzNetworking::IConnection* connection, - AzNetworking::IConnectionListener& connectionListener, - NetworkEntityHandle controlledEntity + AzNetworking::IConnectionListener& connectionListener ) : m_connection(connection) , m_controlledEntityRemovedHandler([this](const ConstNetworkEntityHandle&) { OnControlledEntityRemove(); }) - , m_controlledEntityMigrationHandler([this](const ConstNetworkEntityHandle& entityHandle, const HostId& remoteHostId, AzNetworking::ConnectionId connectionId) { OnControlledEntityMigration(entityHandle, remoteHostId, connectionId); }) - , m_controlledEntity(controlledEntity) + , m_controlledEntityMigrationHandler([this](const ConstNetworkEntityHandle& entityHandle, const HostId& remoteHostId, const HostId& migrateHostId, AzNetworking::ConnectionId connectionId) + { + OnControlledEntityMigration(entityHandle, remoteHostId, migrateHostId, connectionId); + }) , m_entityReplicationManager(*connection, connectionListener, EntityReplicationManager::Mode::LocalServerToRemoteClient) { - NetBindComponent* netBindComponent = m_controlledEntity.GetNetBindComponent(); - if (netBindComponent != nullptr) - { - netBindComponent->AddEntityStopEventHandler(m_controlledEntityRemovedHandler); - netBindComponent->AddEntityServerMigrationEventHandler(m_controlledEntityMigrationHandler); - } - m_entityReplicationManager.SetMaxRemoteEntitiesPendingCreationCount(sv_ClientMaxRemoteEntitiesPendingCreationCount); m_entityReplicationManager.SetEntityPendingRemovalMs(sv_ClientEntityReplicatorPendingRemovalTimeMs); } @@ -54,6 +48,20 @@ namespace Multiplayer m_controlledEntityRemovedHandler.Disconnect(); } + void ServerToClientConnectionData::SetControlledEntity(NetworkEntityHandle primaryPlayerEntity) + { + m_controlledEntityRemovedHandler.Disconnect(); + m_controlledEntityMigrationHandler.Disconnect(); + + m_controlledEntity = primaryPlayerEntity; + NetBindComponent* netBindComponent = m_controlledEntity.GetNetBindComponent(); + if (netBindComponent != nullptr) + { + netBindComponent->AddEntityStopEventHandler(m_controlledEntityRemovedHandler); + netBindComponent->AddEntityServerMigrationEventHandler(m_controlledEntityMigrationHandler); + } + } + ConnectionDataType ServerToClientConnectionData::GetConnectionDataType() const { return ConnectionDataType::ServerToClient; @@ -94,7 +102,8 @@ namespace Multiplayer void ServerToClientConnectionData::OnControlledEntityMigration ( [[maybe_unused]] const ConstNetworkEntityHandle& entityHandle, - [[maybe_unused]] const HostId& remoteHostId, + const HostId& remoteHostId, + const HostId& migrateHostId, [[maybe_unused]] AzNetworking::ConnectionId connectionId ) { @@ -109,13 +118,13 @@ namespace Multiplayer } // Generate crypto-rand user identifier, send to both server and client so they can negotiate the autonomous entity to assume predictive control over after migration - const uint64_t randomUserIdentifier = AzNetworking::CryptoRand64(); + const uint64_t temporaryUserIdentifier = AzNetworking::CryptoRand64(); // Tell the new host that a client is about to (re)join - GetMultiplayer()->SendNotifyClientMigrationEvent(remoteHostId, randomUserIdentifier, migratedClientInputId); + GetMultiplayer()->SendNotifyClientMigrationEvent(remoteHostId, temporaryUserIdentifier, migratedClientInputId, m_controlledEntity.GetNetEntityId()); // Tell the client who to join - MultiplayerPackets::ClientMigration clientMigration(remoteHostId, randomUserIdentifier, migratedClientInputId); + MultiplayerPackets::ClientMigration clientMigration(migrateHostId, temporaryUserIdentifier, migratedClientInputId); GetConnection()->SendReliablePacket(clientMigration); m_controlledEntity = NetworkEntityHandle(); diff --git a/Gems/Multiplayer/Code/Source/ConnectionData/ServerToClientConnectionData.h b/Gems/Multiplayer/Code/Source/ConnectionData/ServerToClientConnectionData.h index 8dcf08c480..a894d5e55e 100644 --- a/Gems/Multiplayer/Code/Source/ConnectionData/ServerToClientConnectionData.h +++ b/Gems/Multiplayer/Code/Source/ConnectionData/ServerToClientConnectionData.h @@ -20,11 +20,12 @@ namespace Multiplayer ServerToClientConnectionData ( AzNetworking::IConnection* connection, - AzNetworking::IConnectionListener& connectionListener, - NetworkEntityHandle controlledEntity + AzNetworking::IConnectionListener& connectionListener ); ~ServerToClientConnectionData() override; + void SetControlledEntity(NetworkEntityHandle primaryPlayerEntity); + //! IConnectionData interface //! @{ ConnectionDataType GetConnectionDataType() const override; @@ -42,7 +43,7 @@ namespace Multiplayer private: void OnControlledEntityRemove(); - void OnControlledEntityMigration(const ConstNetworkEntityHandle& entityHandle, const HostId& remoteHostId, AzNetworking::ConnectionId connectionId); + void OnControlledEntityMigration(const ConstNetworkEntityHandle& entityHandle, const HostId& remoteHostId, const HostId& migrateHostId, AzNetworking::ConnectionId connectionId); void OnGameplayStarted(); EntityReplicationManager m_entityReplicationManager; diff --git a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp index b7dde3b9e7..da1ab1cb73 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp @@ -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::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(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(static_cast(deltaTime * 1000.0f)); const AZ::TimeMs serverRateMs = static_cast(sv_serverSendRateMs); const float serverRateSeconds = static_cast(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(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(connection->GetUserData()); + AZStd::unique_ptr window = AZStd::make_unique(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::Get()->VisitRegisteredFunctors([&consoleReplicator](AZ::ConsoleFunctorBase* functor) { consoleReplicator.Visit(functor); }); + if (packet.GetTemporaryUserId() == 0) + { + // Sync our console + ConsoleReplicator consoleReplicator(connection); + AZ::Interface::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::Get()->PerformCommand(commandString.c_str()); - AZ::CVarFixedString loadLevelString = "LoadLevel " + packet.GetMap(); - AZ::Interface::Get()->PerformCommand(loadLevelString.c_str()); + if (m_temporaryUserIdentifier == 0) + { + AZ::CVarFixedString commandString = "sv_map " + packet.GetMap(); + AZ::Interface::Get()->PerformCommand(commandString.c_str()); + AZ::CVarFixedString loadLevelString = "LoadLevel " + packet.GetMap(); + AZ::Interface::Get()->PerformCommand(loadLevelString.c_str()); + } + else + { + OnAutonomousEntityReplicatorCreated(); +// IConnectionData* connectionData = reinterpret_cast(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 window = AZStd::make_unique(controlledEntity, connection); - reinterpret_cast(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" : "Remotely 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& commands) { AZ::IConsole* console = AZ::Interface::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(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::Get()->StartHosting(sv_port, sv_isDedicated)) { - AZLOG_ERROR("Failed to start listening on port %u, port is in use?", static_cast(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"); diff --git a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h index 262168b536..f07aa0b68f 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h +++ b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h @@ -116,7 +116,7 @@ namespace Multiplayer void AddConnectionAcquiredHandler(ConnectionAcquiredEvent::Handler& handler) override; void AddSessionInitHandler(SessionInitEvent::Handler& handler) override; void AddSessionShutdownHandler(SessionShutdownEvent::Handler& handler) override; - void SendNotifyClientMigrationEvent(const HostId& hostId, uint64_t userIdentifier, ClientInputId lastClientInputId) override; + void SendNotifyClientMigrationEvent(const HostId& hostId, uint64_t userIdentifier, ClientInputId lastClientInputId, NetEntityId controlledEntityId) override; void SendNotifyEntityMigrationEvent(const ConstNetworkEntityHandle& entityHandle, const HostId& remoteHostId) override; void SendReadyForEntityUpdates(bool readyForEntityUpdates) override; AZ::TimeMs GetCurrentHostTimeMs() const override; @@ -125,6 +125,7 @@ namespace Multiplayer INetworkEntityManager* GetNetworkEntityManager() override; void SetFilterEntityManager(IFilterEntityManager* entityFilter) override; IFilterEntityManager* GetFilterEntityManager() override; + void RegisterPlayerIdentifierForRejoin(uint64_t temporaryUserIdentifier, NetEntityId controlledEntityId) override; void SetShouldSpawnNetworkEntities(bool value) override; bool GetShouldSpawnNetworkEntities() const override; //! @} @@ -138,8 +139,9 @@ namespace Multiplayer void TickVisibleNetworkEntities(float deltaTime, float serverRateSeconds); void OnConsoleCommandInvoked(AZStd::string_view command, const AZ::ConsoleCommandContainer& args, AZ::ConsoleFunctorFlags flags, AZ::ConsoleInvokedFrom invokedFrom); + void OnAutonomousEntityReplicatorCreated(); void ExecuteConsoleCommandList(AzNetworking::IConnection* connection, const AZStd::fixed_vector& commands); - NetworkEntityHandle SpawnDefaultPlayerPrefab(); + NetworkEntityHandle SpawnDefaultPlayerPrefab(uint64_t temporaryUserIdentifier); AZ_CONSOLEFUNC(MultiplayerSystemComponent, DumpStats, AZ::ConsoleFunctorFlags::Null, "Dumps stats for the current multiplayer session"); @@ -162,12 +164,16 @@ namespace Multiplayer ClientMigrationEndEvent m_clientMigrationEndEvent; NotifyClientMigrationEvent m_notifyClientMigrationEvent; NotifyEntityMigrationEvent m_notifyEntityMigrationEvent; + AZ::Event::Handler m_autonomousEntityReplicatorCreatedHandler; AZStd::queue m_pendingConnectionTickets; + AZStd::unordered_map m_playerRejoinData; AZ::TimeMs m_lastReplicatedHostTimeMs = AZ::TimeMs{ 0 }; HostFrameId m_lastReplicatedHostFrameId = HostFrameId(0); + uint64_t m_temporaryUserIdentifier = 0; // Used in the event of a migration or rejoin + double m_serverSendAccumulator = 0.0; float m_renderBlendFactor = 0.0f; float m_tickFactor = 0.0f; diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp index 91ca1bff84..31232cb3f4 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp @@ -47,8 +47,9 @@ namespace Multiplayer , m_entityExitDomainEventHandler([this](const ConstNetworkEntityHandle& entityHandle) { OnEntityExitDomain(entityHandle); }) , m_notifyEntityMigrationHandler([this](const ConstNetworkEntityHandle& entityHandle, const HostId& remoteHostId) { OnPostEntityMigration(entityHandle, remoteHostId); }) { - // Set up our remote host identifier, we use the IP address of the host + // Set up our remote host identifier, by default we use the IP address of the remote host m_remoteHostId = connection.GetRemoteAddress(); + m_migrateHostId = m_remoteHostId; // Our max payload size is whatever is passed in, minus room for a udp packetheader m_maxPayloadSize = connection.GetConnectionMtu() - UdpPacketHeaderSerializeSize - ReplicationManagerPacketOverhead; @@ -65,7 +66,21 @@ namespace Multiplayer networkEntityManager->AddEntityExitDomainHandler(m_entityExitDomainEventHandler); } - GetMultiplayer()->AddNotifyEntityMigrationEventHandler(m_notifyEntityMigrationHandler); + if (m_updateMode == Mode::LocalServerToRemoteServer) + { + GetMultiplayer()->AddNotifyEntityMigrationEventHandler(m_notifyEntityMigrationHandler); + } + } + + void EntityReplicationManager::SetMigrateHostId(const HostId& remoteHostId) + { + // Allows overriding the remote HostId + m_migrateHostId = remoteHostId; + } + + const HostId& EntityReplicationManager::GetMigrateHostId() const + { + return m_migrateHostId; } const HostId& EntityReplicationManager::GetRemoteHostId() const @@ -362,15 +377,28 @@ namespace Multiplayer const bool changedRemoteRole = (remoteNetworkRole != entityReplicator->GetRemoteNetworkRole()); // Check if we've changed our bound local role - this can occur when we gain Autonomous or lose Autonomous on a client bool changedLocalRole(false); - if (AZ::Entity* localEnt = entityReplicator->GetEntityHandle().GetEntity()) + NetBindComponent* netBindComponent = entityReplicator->GetEntityHandle().GetNetBindComponent(); + if (netBindComponent != nullptr) { - NetBindComponent* netBindComponent = entityReplicator->GetEntityHandle().GetNetBindComponent(); - AZ_Assert(netBindComponent != nullptr, "No NetBindComponent"); changedLocalRole = (netBindComponent->GetNetEntityRole() != entityReplicator->GetBoundLocalNetworkRole()); } if (changedRemoteRole || changedLocalRole) { + const uint32_t intEntityId = static_cast(netBindComponent->GetNetEntityId()); + if (changedLocalRole) + { + const char* oldRoleString = GetEnumString(entityReplicator->GetRemoteNetworkRole()); + const char* newRoleString = GetEnumString(remoteNetworkRole); + AZLOG(NET_ReplicatorRoles, "Replicator %u changed local role, old role = %s, new role = %s", intEntityId, oldRoleString, newRoleString); + } + if (changedRemoteRole) + { + const char* oldRoleString = GetEnumString(entityReplicator->GetBoundLocalNetworkRole()); + const char* newRoleString = GetEnumString(netBindComponent->GetNetEntityRole()); + AZLOG(NET_ReplicatorRoles, "Replicator %u changed remote role, old role = %s, new role = %s", intEntityId, oldRoleString, newRoleString); + } + // If we changed roles, we need to reset everything if (!entityReplicator->IsMarkedForRemoval()) { @@ -1107,7 +1135,7 @@ namespace Multiplayer if (m_updateMode == EntityReplicationManager::Mode::LocalServerToRemoteServer) { - netBindComponent->NotifyServerMigration(GetRemoteHostId(), GetConnection().GetConnectionId()); + netBindComponent->NotifyServerMigration(GetRemoteHostId(), GetMigrateHostId(), GetConnection().GetConnectionId()); } bool didSucceed = true; diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.cpp index 807bbf9e21..0b97634183 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.cpp @@ -176,7 +176,6 @@ namespace Multiplayer switch (GetBoundLocalNetworkRole()) { case NetEntityRole::Authority: - { if (GetRemoteNetworkRole() == NetEntityRole::Client || GetRemoteNetworkRole() == NetEntityRole::Autonomous) { m_onSendRpcHandler.Connect(netBindComponent->GetSendAuthorityToClientRpcEvent()); @@ -189,10 +188,8 @@ namespace Multiplayer { m_onForwardRpcHandler.Connect(netBindComponent->GetSendAuthorityToClientRpcEvent()); } - } - break; + break; case NetEntityRole::Server: - { if (GetRemoteNetworkRole() == NetEntityRole::Authority) { m_onSendRpcHandler.Connect(netBindComponent->GetSendServerToAuthorityRpcEvent()); @@ -204,23 +201,21 @@ namespace Multiplayer // Listen for these to forward the rpc along to the other Client replicators m_onSendRpcHandler.Connect(netBindComponent->GetSendAuthorityToClientRpcEvent()); } - // NOTE: e_Autonomous is not connected to e_ServerProxy, it is always connected to an e_Authority - AZ_Assert(GetRemoteNetworkRole() != NetEntityRole::Autonomous, "Unexpected autonomous remote role") - } - break; + else if (GetRemoteNetworkRole() == NetEntityRole::Autonomous) + { + // NOTE: Autonomous is not connected to ServerProxy, it is always connected to an Authority + AZ_Assert(false, "Unexpected autonomous remote role") + } + break; case NetEntityRole::Client: - { // Nothing allowed, no Client to Server communication - } - break; + break; case NetEntityRole::Autonomous: - { if (GetRemoteNetworkRole() == NetEntityRole::Authority) { m_onSendRpcHandler.Connect(netBindComponent->GetSendAutonomousToAuthorityRpcEvent()); } - } - break; + break; default: AZ_Assert(false, "Unexpected network role"); } @@ -255,19 +250,6 @@ namespace Multiplayer AZLOG_WARN("Trying to activate an entity that is not in the Init state (%u)", GetEntityHandle().GetNetEntityId()); } - // First we need to make sure the transform component has been updated with the correct value prior to activation - // This is because vanilla az components may only depend on the transform component, not the multiplayer transform component - //if (auto* locationComponent = FindCommonComponent(GetEntityHandle())) - //{ - // AZ::Transform newTransform = locationComponent->GetTransform(); - // auto* transformComponent = entity->FindComponent(); - // if (transformComponent) - // { - // // We can't use EBus here since the TransFormBus does not get connected until the activate call below - // transformComponent->SetWorldTM(newTransform); - // } - //} - // Ugly, but this is the only time we need to call a non-const function on this entity entity->Activate(); m_replicationManager.m_orphanedEntityRpcs.DispatchOrphanedRpcs(*this); @@ -281,8 +263,7 @@ namespace Multiplayer NetBindComponent* netBindComponent = m_netBindComponent; AZ_Assert(netBindComponent, "No Multiplayer::NetBindComponent"); - bool isAuthority = (GetBoundLocalNetworkRole() == NetEntityRole::Authority) - && (GetBoundLocalNetworkRole() == netBindComponent->GetNetEntityRole()); + bool isAuthority = (GetBoundLocalNetworkRole() == NetEntityRole::Authority) && (GetBoundLocalNetworkRole() == netBindComponent->GetNetEntityRole()); bool isClient = GetRemoteNetworkRole() == NetEntityRole::Client; bool isAutonomous = GetBoundLocalNetworkRole() == NetEntityRole::Autonomous; if (isAuthority || isClient || isAutonomous) @@ -428,10 +409,8 @@ namespace Multiplayer if (const NetworkTransformComponent* networkTransform = entity->FindComponent()) { const NetEntityId parentId = networkTransform->GetParentEntityId(); - /* - * For root entities attached to a level, a network parent won't be set. - * In this case, this entity is the root entity of the hierarchy and it will be activated first. - */ + // For root entities attached to a level, a network parent won't be set. + // In this case, this entity is the root entity of the hierarchy and it will be activated first. if (parentId != InvalidNetEntityId) { ConstNetworkEntityHandle parentHandle = GetNetworkEntityManager()->GetEntity(parentId); @@ -552,42 +531,33 @@ namespace Multiplayer switch (entityRpcMessage.GetRpcDeliveryType()) { case RpcDeliveryType::AuthorityToClient: - { if (((GetBoundLocalNetworkRole() == NetEntityRole::Client) || (GetBoundLocalNetworkRole() == NetEntityRole::Autonomous)) && (GetRemoteNetworkRole() == NetEntityRole::Authority)) { // We are a local client, and we are connected to server, aka AuthorityToClient result = RpcValidationResult::HandleRpc; } - if ((GetBoundLocalNetworkRole() == NetEntityRole::Server) - && (GetRemoteNetworkRole() == NetEntityRole::Authority)) + if ((GetBoundLocalNetworkRole() == NetEntityRole::Server) && (GetRemoteNetworkRole() == NetEntityRole::Authority)) { // We are on a server, and we received this message from another server, therefore we should forward this to any connected clients result = RpcValidationResult::ForwardToClient; } - } - break; + break; case RpcDeliveryType::AuthorityToAutonomous: - { - if ((GetBoundLocalNetworkRole() == NetEntityRole::Autonomous) - && (GetRemoteNetworkRole() == NetEntityRole::Authority)) + if ((GetBoundLocalNetworkRole() == NetEntityRole::Autonomous) && (GetRemoteNetworkRole() == NetEntityRole::Authority)) { // We are an autonomous client, and we are connected to server, aka AuthorityToAutonomous result = RpcValidationResult::HandleRpc; } - if ((GetBoundLocalNetworkRole() == NetEntityRole::Authority) - && (GetRemoteNetworkRole() == NetEntityRole::Server)) + if ((GetBoundLocalNetworkRole() == NetEntityRole::Authority) && (GetRemoteNetworkRole() == NetEntityRole::Server)) { // We are on a server, and we received this message from another server, therefore we should forward this to our autonomous player // This can occur if we've recently migrated result = RpcValidationResult::ForwardToAutonomous; } - } - break; + break; case RpcDeliveryType::AutonomousToAuthority: - { - if ((GetBoundLocalNetworkRole() == NetEntityRole::Authority) - && (GetRemoteNetworkRole() == NetEntityRole::Autonomous)) + if ((GetBoundLocalNetworkRole() == NetEntityRole::Authority) && (GetRemoteNetworkRole() == NetEntityRole::Autonomous)) { if (IsMarkedForRemoval()) { @@ -609,12 +579,9 @@ namespace Multiplayer result = RpcValidationResult::HandleRpc; } } - } - break; + break; case RpcDeliveryType::ServerToAuthority: - { - if ((GetBoundLocalNetworkRole() == NetEntityRole::Authority) - && (GetRemoteNetworkRole() == NetEntityRole::Server)) + if ((GetBoundLocalNetworkRole() == NetEntityRole::Authority) && (GetRemoteNetworkRole() == NetEntityRole::Server)) { // if we're marked for removal, then we should forward to whomever now owns this entity if (IsMarkedForRemoval()) @@ -637,9 +604,9 @@ namespace Multiplayer result = RpcValidationResult::HandleRpc; } } + break; } - break; - } + if (result == RpcValidationResult::DropRpcAndDisconnect) { bool isLocalServer = (GetBoundLocalNetworkRole() == NetEntityRole::Authority) || (GetBoundLocalNetworkRole() == NetEntityRole::Server); @@ -653,30 +620,29 @@ namespace Multiplayer { AZLOG_ERROR ( - "Dropping RPC and Connection EntityId=%u LocalRole=%u RemoteRole=%u RpcDeliveryType=%u ComponentId=%u RpcType=%u IsReliable=%s IsMarkedForRemoval=%s", + "Dropping RPC and Connection EntityId=%u LocalRole=%s RemoteRole=%s RpcDeliveryType=%u RpcName=%s IsReliable=%s IsMarkedForRemoval=%s", aznumeric_cast(m_entityHandle.GetNetEntityId()), - aznumeric_cast(GetBoundLocalNetworkRole()), - aznumeric_cast(GetRemoteNetworkRole()), + GetEnumString(GetBoundLocalNetworkRole()), + GetEnumString(GetRemoteNetworkRole()), aznumeric_cast(entityRpcMessage.GetRpcDeliveryType()), - aznumeric_cast(entityRpcMessage.GetComponentId()), - aznumeric_cast(entityRpcMessage.GetRpcIndex()), + GetMultiplayerComponentRegistry()->GetComponentRpcName(entityRpcMessage.GetComponentId(), entityRpcMessage.GetRpcIndex()), entityRpcMessage.GetReliability() == ReliabilityType::Reliable ? "true" : "false", IsMarkedForRemoval() ? "true" : "false" ); } } + if (result == RpcValidationResult::DropRpc) { AZLOG ( NET_Rpc, - "Dropping RPC EntityId=%u LocalRole=%u RemoteRole=%u RpcDeliveryType=%u ComponentId=%u RpcType=%u IsReliable=%s IsMarkedForRemoval=%s", + "Dropping RPC EntityId=%u LocalRole=%s RemoteRole=%s RpcDeliveryType=%u RpcName=%s IsReliable=%s IsMarkedForRemoval=%s", aznumeric_cast(m_entityHandle.GetNetEntityId()), - aznumeric_cast(GetBoundLocalNetworkRole()), - aznumeric_cast(GetRemoteNetworkRole()), + GetEnumString(GetBoundLocalNetworkRole()), + GetEnumString(GetRemoteNetworkRole()), aznumeric_cast(entityRpcMessage.GetRpcDeliveryType()), - aznumeric_cast(entityRpcMessage.GetComponentId()), - aznumeric_cast(entityRpcMessage.GetRpcIndex()), + GetMultiplayerComponentRegistry()->GetComponentRpcName(entityRpcMessage.GetComponentId(), entityRpcMessage.GetRpcIndex()), entityRpcMessage.GetReliability() == ReliabilityType::Reliable ? "true" : "false", IsMarkedForRemoval() ? "true" : "false" ); @@ -695,13 +661,12 @@ namespace Multiplayer { AZLOG_WARN ( - "Dropping RPC since entity deleted EntityId=%u LocalRole=%u RemoteRole=%u RpcDeliveryType=%u ComponentId=%u RpcType=%u IsReliable=%s IsMarkedForRemoval=%s", + "Dropping RPC since entity deleted EntityId=%u LocalRole=%s RemoteRole=%s RpcDeliveryType=%u RpcName=%s IsReliable=%s IsMarkedForRemoval=%s", aznumeric_cast(m_entityHandle.GetNetEntityId()), - aznumeric_cast(GetBoundLocalNetworkRole()), - aznumeric_cast(GetRemoteNetworkRole()), + GetEnumString(GetBoundLocalNetworkRole()), + GetEnumString(GetRemoteNetworkRole()), aznumeric_cast(entityRpcMessage.GetRpcDeliveryType()), - aznumeric_cast(entityRpcMessage.GetComponentId()), - aznumeric_cast(entityRpcMessage.GetRpcIndex()), + GetMultiplayerComponentRegistry()->GetComponentRpcName(entityRpcMessage.GetComponentId(), entityRpcMessage.GetRpcIndex()), entityRpcMessage.GetReliability() == ReliabilityType::Reliable ? "true" : "false", IsMarkedForRemoval() ? "true" : "false" ); @@ -739,23 +704,23 @@ namespace Multiplayer case RpcValidationResult::DropRpcAndDisconnect: return false; case RpcValidationResult::ForwardToClient: - { - ScopedForwardingMessage forwarding(*this); - m_netBindComponent->GetSendAuthorityToClientRpcEvent().Signal(entityRpcMessage); + { + ScopedForwardingMessage forwarding(*this); + m_netBindComponent->GetSendAuthorityToClientRpcEvent().Signal(entityRpcMessage); + } return true; - } case RpcValidationResult::ForwardToAutonomous: - { - ScopedForwardingMessage forwarding(*this); - m_netBindComponent->GetSendAuthorityToAutonomousRpcEvent().Signal(entityRpcMessage); + { + ScopedForwardingMessage forwarding(*this); + m_netBindComponent->GetSendAuthorityToAutonomousRpcEvent().Signal(entityRpcMessage); + } return true; - } case RpcValidationResult::ForwardToAuthority: - { - ScopedForwardingMessage forwarding(*this); - m_netBindComponent->GetSendServerToAuthorityRpcEvent().Signal(entityRpcMessage); + { + ScopedForwardingMessage forwarding(*this); + m_netBindComponent->GetSendServerToAuthorityRpcEvent().Signal(entityRpcMessage); + } return true; - } default: break; } diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp index db7f7243cc..d1d915ca28 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp @@ -225,11 +225,19 @@ namespace Multiplayer { AZ::Entity* entity = it->second; NetBindComponent* netBindComponent = m_networkEntityTracker.GetNetBindComponent(entity); + AZ::Aabb entityBounds = AZ::Interface::Get()->GetEntityWorldBoundsUnion(entity->GetId()); + entityBounds.Expand(AZ::Vector3(0.01f)); if (netBindComponent->GetNetEntityRole() == NetEntityRole::Authority) { - const AZ::Aabb entityBounds = AZ::Interface::Get()->GetEntityWorldBoundsUnion(entity->GetId()); - debugDisplay->DrawWireBox(entityBounds.GetMin(), entityBounds.GetMax()); + debugDisplay->SetColor(AZ::Colors::Black); + debugDisplay->SetAlpha(0.5f); } + else + { + debugDisplay->SetColor(AZ::Colors::DeepSkyBlue); + debugDisplay->SetAlpha(0.25f); + } + debugDisplay->DrawWireBox(entityBounds.GetMin(), entityBounds.GetMax()); } if (m_entityDomain != nullptr) diff --git a/Gems/Multiplayer/Code/Source/ReplicationWindows/ServerToClientReplicationWindow.cpp b/Gems/Multiplayer/Code/Source/ReplicationWindows/ServerToClientReplicationWindow.cpp index 9d9cc74d2b..8807931ae1 100644 --- a/Gems/Multiplayer/Code/Source/ReplicationWindows/ServerToClientReplicationWindow.cpp +++ b/Gems/Multiplayer/Code/Source/ReplicationWindows/ServerToClientReplicationWindow.cpp @@ -91,17 +91,10 @@ namespace Multiplayer return m_isPoorConnection ? sv_MinEntitiesToReplicate : sv_MaxEntitiesToReplicate; } - bool ServerToClientReplicationWindow::IsInWindow(const ConstNetworkEntityHandle& entityHandle, NetEntityRole& outNetworkRole) const + bool ServerToClientReplicationWindow::IsInWindow([[maybe_unused]] const ConstNetworkEntityHandle& entityHandle, NetEntityRole& outNetworkRole) const { - // TODO: Clean up this interface, this function is used for server->server migrations, and probably shouldn't be exposed in it's current setup AZ_Assert(false, "IsInWindow should not be called on the ServerToClientReplicationWindow"); outNetworkRole = NetEntityRole::InvalidRole; - auto iter = m_replicationSet.find(entityHandle); - if (iter != m_replicationSet.end()) - { - outNetworkRole = iter->second.m_netEntityRole; - return true; - } return false; } @@ -145,7 +138,7 @@ namespace Multiplayer NetworkEntityTracker* networkEntityTracker = GetNetworkEntityTracker(); IFilterEntityManager* filterEntityManager = GetMultiplayer()->GetFilterEntityManager(); - // Add all the neighbors + // Add all the neighbours for (AzFramework::VisibilityEntry* visEntry : gatheredEntries) { AZ::Entity* entity = static_cast(visEntry->m_userData); @@ -300,7 +293,6 @@ namespace Multiplayer void ServerToClientReplicationWindow::AddEntityToReplicationSet(ConstNetworkEntityHandle& entityHandle, float priority, [[maybe_unused]] float distanceSquared) { // Assumption: the entity has been checked for filtering prior to this call. - if (!sv_ReplicateServerProxies) { NetBindComponent* netBindComponent = entityHandle.GetNetBindComponent(); @@ -311,11 +303,11 @@ namespace Multiplayer } } - const bool isQueueFull = (m_candidateQueue.size() >= sv_MaxEntitiesToTrackReplication); // See if have the maximum number of entities in our set + const bool isQueueFull = (m_candidateQueue.size() >= sv_MaxEntitiesToTrackReplication); // See if have the maximum number of entities in our set const bool isInReplicationSet = m_replicationSet.find(entityHandle) != m_replicationSet.end(); if (!isInReplicationSet) { - if (isQueueFull) // if our set is full, then we need to remove the worst priority in our set + if (isQueueFull) // If our set is full, then we need to remove the worst priority in our set { ConstNetworkEntityHandle removeEnt = m_candidateQueue.top().m_entityHandle; m_candidateQueue.pop(); diff --git a/Gems/Multiplayer/Code/Source/ReplicationWindows/ServerToClientReplicationWindow.h b/Gems/Multiplayer/Code/Source/ReplicationWindows/ServerToClientReplicationWindow.h index b034bde90c..3c3d7754f6 100644 --- a/Gems/Multiplayer/Code/Source/ReplicationWindows/ServerToClientReplicationWindow.h +++ b/Gems/Multiplayer/Code/Source/ReplicationWindows/ServerToClientReplicationWindow.h @@ -75,8 +75,6 @@ namespace Multiplayer AZ::EntityActivatedEvent::Handler m_entityActivatedEventHandler; AZ::EntityDeactivatedEvent::Handler m_entityDeactivatedEventHandler; - //NetBindComponent* m_controlledNetBindComponent = nullptr; - AzNetworking::IConnection* m_connection = nullptr; // Cached values to detect a poor network connection