diff --git a/Gems/Multiplayer/Code/Source/Components/NetworkTransformComponent.cpp b/Gems/Multiplayer/Code/Source/Components/NetworkTransformComponent.cpp index bd1e1bf0d9..a6e670a835 100644 --- a/Gems/Multiplayer/Code/Source/Components/NetworkTransformComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Components/NetworkTransformComponent.cpp @@ -44,7 +44,7 @@ namespace Multiplayer GetNetBindComponent()->AddEntityCorrectionEventHandler(m_entityCorrectionEventHandler); ParentEntityIdAddEvent(m_parentChangedEventHandler); - if (!HasController()) + if (!GetNetBindComponent()->IsNetEntityRoleAuthority()) { OnParentChanged(GetParentEntityId()); } diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp index 11aca101b3..8f11985cba 100644 --- a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp @@ -231,79 +231,83 @@ namespace Multiplayer void MultiplayerEditorSystemComponent::OnGameEntitiesStarted() { + IMultiplayerTools* mpTools = AZ::Interface::Get(); + if (!editorsv_enabled || !mpTools) + { + // Early out if Editor server is not enabled. + // This allows to avoid printing an error about missing PrefabEditorEntityOwnershipInterface for non-prefab levels. + return; + } + auto prefabEditorEntityOwnershipInterface = AZ::Interface::Get(); if (!prefabEditorEntityOwnershipInterface) { AZ_Error("MultiplayerEditor", prefabEditorEntityOwnershipInterface != nullptr, "PrefabEditorEntityOwnershipInterface unavailable"); + return; } // BeginGameMode and Prefab Processing have completed at this point - IMultiplayerTools* mpTools = AZ::Interface::Get(); - if (editorsv_enabled && mpTools != nullptr) - { - const AZStd::vector>& assetData = prefabEditorEntityOwnershipInterface->GetPlayInEditorAssetData(); + const AZStd::vector>& assetData = prefabEditorEntityOwnershipInterface->GetPlayInEditorAssetData(); - AZStd::vector buffer; - AZ::IO::ByteContainerStream byteStream(&buffer); + AZStd::vector buffer; + AZ::IO::ByteContainerStream byteStream(&buffer); - // Serialize Asset information and AssetData into a potentially large buffer - for (const auto& asset : assetData) + // Serialize Asset information and AssetData into a potentially large buffer + for (const auto& asset : assetData) + { + AZ::Data::AssetId assetId = asset.GetId(); + AZStd::string assetHint = asset.GetHint(); + uint32_t hintSize = aznumeric_cast(assetHint.size()); + + byteStream.Write(sizeof(AZ::Data::AssetId), reinterpret_cast(&assetId)); + byteStream.Write(sizeof(uint32_t), reinterpret_cast(&hintSize)); + byteStream.Write(assetHint.size(), assetHint.data()); + AZ::Utils::SaveObjectToStream(byteStream, AZ::DataStream::ST_BINARY, asset.GetData(), asset.GetData()->GetType()); + } + + const AZ::CVarFixedString remoteAddress = editorsv_serveraddr; + if (editorsv_launch) + { + if (LocalHost != remoteAddress) { - AZ::Data::AssetId assetId = asset.GetId(); - AZStd::string assetHint = asset.GetHint(); - uint32_t hintSize = aznumeric_cast(assetHint.size()); - - byteStream.Write(sizeof(AZ::Data::AssetId), reinterpret_cast(&assetId)); - byteStream.Write(sizeof(uint32_t), reinterpret_cast(&hintSize)); - byteStream.Write(assetHint.size(), assetHint.data()); - AZ::Utils::SaveObjectToStream(byteStream, AZ::DataStream::ST_BINARY, asset.GetData(), asset.GetData()->GetType()); + AZ_Warning( + "MultiplayerEditor", false, + "Launching editor server skipped because of incompatible settings. " + "When using editorsv_launch=true editorsv_serveraddr must be set to local address (127.0.0.1) instead %s", + remoteAddress.c_str()) + return; } - const AZ::CVarFixedString remoteAddress = editorsv_serveraddr; - if (editorsv_launch) - { - if (LocalHost != remoteAddress) - { - AZ_Warning( - "MultiplayerEditor", false, - "Launching EditorServer skipped because incompatible cvars. editorsv_launch=true, meaning you want to launch an editor-server on this machine, but the editorsv_serveraddr is %s instead of the local address (127.0.0.1). " - "Please either set editorsv_launch=false and keep the remote editor-server, or set editorsv_launch=true and editorsv_serveraddr=127.0.0.1.", - remoteAddress.c_str()) - return; - } + // Begin listening for MPEditor packets before we launch the editor-server. + // The editor-server will send us (the editor) an "EditorServerReadyForLevelData" packet to let us know it's ready to receive data. + INetworkInterface* editorNetworkInterface = + AZ::Interface::Get()->RetrieveNetworkInterface(AZ::Name(MpEditorInterfaceName)); + AZ_Assert(editorNetworkInterface, "MP Editor Network Interface was unregistered before Editor could connect."); + editorNetworkInterface->Listen(editorsv_port); + + // Launch the editor-server + m_serverProcess = LaunchEditorServer(); + } + else + { + // Editorsv_launch=false, so we're expecting an editor-server already exists. + // Connect to the editor-server and then send the EditorServerLevelData packet. + INetworkInterface* editorNetworkInterface = AZ::Interface::Get()->RetrieveNetworkInterface(AZ::Name(MpEditorInterfaceName)); + AZ_Assert(editorNetworkInterface, "MP Editor Network Interface was unregistered before Editor could connect.") - // Begin listening for MPEditor packets before we launch the editor-server. - // The editor-server will send us (the editor) an "EditorServerReadyForLevelData" packet to let us know it's ready to receive data. - INetworkInterface* editorNetworkInterface = - AZ::Interface::Get()->RetrieveNetworkInterface(AZ::Name(MpEditorInterfaceName)); - AZ_Assert(editorNetworkInterface, "MP Editor Network Interface was unregistered before Editor could connect."); - editorNetworkInterface->Listen(editorsv_port); + m_editorConnId = editorNetworkInterface->Connect(AzNetworking::IpAddress(remoteAddress.c_str(), editorsv_port, AzNetworking::ProtocolType::Tcp)); - // Launch the editor-server - m_serverProcess = LaunchEditorServer(); - } - else + if (m_editorConnId == AzNetworking::InvalidConnectionId) { - // Editorsv_launch=false, so we're expecting an editor-server already exists. - // Connect to the editor-server and then send the EditorServerLevelData packet. - INetworkInterface* editorNetworkInterface = AZ::Interface::Get()->RetrieveNetworkInterface(AZ::Name(MpEditorInterfaceName)); - AZ_Assert(editorNetworkInterface, "MP Editor Network Interface was unregistered before Editor could connect.") - - m_editorConnId = editorNetworkInterface->Connect(AzNetworking::IpAddress(remoteAddress.c_str(), editorsv_port, AzNetworking::ProtocolType::Tcp)); - - if (m_editorConnId == AzNetworking::InvalidConnectionId) - { - AZ_Warning( - "MultiplayerEditor", false, - "Editor multiplayer game-mode failed! Could not connect to an editor-server. editorsv_launch is false so we're assuming you're running your own editor-server at editorsv_serveraddr(%s) on editorsv_port(%i). " - "Either set editorsv_launch=true so the editor launches an editor-server for you, or launch your own editor-server by hand before entering game-mode. Remember editor-servers must use editorsv_isDedicated=true.", - remoteAddress.c_str(), - static_cast(editorsv_port)) - return; - } - - SendEditorServerLevelDataPacket(editorNetworkInterface->GetConnectionSet().GetConnection(m_editorConnId)); + AZ_Warning( + "MultiplayerEditor", false, + "Could not connect to a server at editorsv_serveraddr(%s) on editorsv_port(%i). Check server is active or use editorsv_launch to auto-launch a server.", + remoteAddress.c_str(), + static_cast(editorsv_port)) + return; } + + SendEditorServerLevelDataPacket(editorNetworkInterface->GetConnectionSet().GetConnection(m_editorConnId)); } } diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp index b178ebeb02..6885d78075 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp @@ -231,35 +231,9 @@ namespace Multiplayer { for (NetEntityId exitingId : entitiesNotInDomain) { - bool safeToExit = true; NetworkEntityHandle entityHandle = m_networkEntityTracker.Get(exitingId); - // We need special handling for the NetworkHierarchy as well, since related entities need to be migrated together - NetworkHierarchyRootComponentController* hierarchyRootController = entityHandle.FindController(); - NetworkHierarchyChildComponentController* hierarchyChildController = entityHandle.FindController(); - - // Find the root entity - AZ::Entity* hierarchyRootEntity = nullptr; - if (hierarchyRootController) - { - hierarchyRootEntity = hierarchyRootController->GetParent().GetHierarchicalRoot(); - } - else if (hierarchyChildController) - { - hierarchyRootEntity = hierarchyChildController->GetParent().GetHierarchicalRoot(); - } - - if (hierarchyRootEntity) - { - NetEntityId rootNetId = GetNetEntityIdById(hierarchyRootEntity->GetId()); - ConstNetworkEntityHandle rootEntityHandle = GetEntity(rootNetId); - - // Check if the root entity is still tracked by this authority - if (rootEntityHandle.Exists() && rootEntityHandle.GetNetBindComponent()->HasController()) - { - safeToExit = false; - } - } + bool safeToExit = IsHierarchySafeToExit(entityHandle, entitiesNotInDomain);; // Validate that we aren't already planning to remove this entity if (safeToExit) @@ -305,7 +279,13 @@ namespace Multiplayer { AZ::Entity* entity = it->second; NetBindComponent* netBindComponent = m_networkEntityTracker.GetNetBindComponent(entity); + AZ::Aabb entityBounds = AZ::Interface::Get()->GetEntityWorldBoundsUnion(entity->GetId()); + if (!entityBounds.IsValid()) + { + continue; + } + entityBounds.Expand(AZ::Vector3(0.01f)); if ((netBindComponent != nullptr) && netBindComponent->GetNetEntityRole() == NetEntityRole::Authority) { @@ -630,4 +610,40 @@ namespace Multiplayer netEntity->GetName().c_str()); } } + + bool NetworkEntityManager::IsHierarchySafeToExit(NetworkEntityHandle& entityHandle, const NetEntityIdSet& entitiesNotInDomain) + { + bool safeToExit = true; + + // We also need special handling for the NetworkHierarchy as well, since related entities need to be migrated together + NetworkHierarchyRootComponentController* hierarchyRootController = entityHandle.FindController(); + NetworkHierarchyChildComponentController* hierarchyChildController = entityHandle.FindController(); + + AZStd::vector hierarchicalEntities; + + // Get the entities in this hierarchy + if (hierarchyRootController) + { + hierarchicalEntities = hierarchyRootController->GetParent().GetHierarchicalEntities(); + } + else if (hierarchyChildController) + { + hierarchicalEntities = hierarchyChildController->GetParent().GetHierarchicalEntities(); + } + + // Check if *all* entities in the hierarchy are ready to migrate. + // If any are still "in domain", keep the whole hierarchy within the current authority for now + for (AZ::Entity* entity : hierarchicalEntities) + { + NetEntityId netEntityId = GetNetEntityIdById(entity->GetId()); + if (netEntityId != InvalidNetEntityId && !entitiesNotInDomain.contains(netEntityId)) + { + safeToExit = false; + break; + } + } + + return safeToExit; + } + } diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.h b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.h index 7c6fdd94f9..8327d95a39 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.h +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.h @@ -99,6 +99,7 @@ namespace Multiplayer private: void RemoveEntities(); NetEntityId NextId(); + bool IsHierarchySafeToExit(NetworkEntityHandle& entityHandle, const NetEntityIdSet& entitiesNotInDomain); NetworkEntityTracker m_networkEntityTracker; NetworkEntityAuthorityTracker m_networkEntityAuthorityTracker;