diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyRootComponent.h b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyRootComponent.h index 4c9f94c004..8e488c1029 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyRootComponent.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyRootComponent.h @@ -57,6 +57,8 @@ namespace Multiplayer void BindNetworkHierarchyLeaveEventHandler(NetworkHierarchyLeaveEvent::Handler& handler) override; //! @} + const AZStd::vector& GetHierarchicalEntitiesRef() const; + protected: void SetTopLevelHierarchyRootEntity(AZ::Entity* hierarchyRoot); @@ -97,4 +99,24 @@ namespace Multiplayer //! Set to false when deactivating or otherwise not to be included in hierarchy considerations. bool m_isHierarchyEnabled = true; }; + + + //! NetworkCharacterComponentController + //! This is the network controller for NetworkHierarchyRootComponent. + //! Class provides the ability to process input for hierarchies. + class NetworkHierarchyRootComponentController + : public NetworkHierarchyRootComponentControllerBase + { + public: + NetworkHierarchyRootComponentController(NetworkHierarchyRootComponent& parent); + + // NetworkHierarchyRootComponentControllerBase + void OnActivate(Multiplayer::EntityIsMigrating entityIsMigrating) override; + void OnDeactivate(Multiplayer::EntityIsMigrating entityIsMigrating) override; + + //! MultiplayerController interface + Multiplayer::MultiplayerController::InputPriorityOrder GetInputOrder() const override; + void CreateInput(Multiplayer::NetworkInput& input, float deltaTime) override; + void ProcessInput(Multiplayer::NetworkInput& input, float deltaTime) override; + }; } diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkInput/NetworkInput.h b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkInput/NetworkInput.h index 1e02d6bf56..4575d88e24 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkInput/NetworkInput.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkInput/NetworkInput.h @@ -29,6 +29,7 @@ namespace Multiplayer friend class NetworkInputMigrationVector; friend class NetworkInputHistory; friend class NetworkInputChild; + friend class NetworkSubInput; NetworkInput(const NetworkInput&); NetworkInput& operator= (const NetworkInput&); @@ -80,4 +81,28 @@ namespace Multiplayer ConstNetworkEntityHandle m_owner; bool m_wasAttached = false; }; + + // Used by the NetworkHierarchyRootComponent. This component allows the gameplay programmer to specify dependent entities + // Since it is possible to for the Client/Server to disagree about the state of related entities, this input encodes the entity that + // is associated with it. + class NetworkSubInput final + { + public: + NetworkSubInput() = default; + NetworkSubInput(const NetworkSubInput&) = default; + NetworkSubInput(const ConstNetworkEntityHandle& entityHandle); + + NetworkSubInput& operator=(const NetworkSubInput&) = default; + + void Attach(const ConstNetworkEntityHandle& entityHandle); + const ConstNetworkEntityHandle& GetOwner() const; + NetworkInput& GetNetworkInput(); + const NetworkInput& GetNetworkInput() const; + + bool Serialize(AzNetworking::ISerializer& serializer); + private: + ConstNetworkEntityHandle m_owner; + NetworkInput m_networkInput; + }; + } diff --git a/Gems/Multiplayer/Code/Source/AutoGen/NetworkHierarchyRootComponent.AutoComponent.xml b/Gems/Multiplayer/Code/Source/AutoGen/NetworkHierarchyRootComponent.AutoComponent.xml index 0f33e1f642..c93f300da3 100644 --- a/Gems/Multiplayer/Code/Source/AutoGen/NetworkHierarchyRootComponent.AutoComponent.xml +++ b/Gems/Multiplayer/Code/Source/AutoGen/NetworkHierarchyRootComponent.AutoComponent.xml @@ -4,11 +4,13 @@ Name="NetworkHierarchyRootComponent" Namespace="Multiplayer" OverrideComponent="true" - OverrideController="false" + OverrideController="true" OverrideInclude="Multiplayer/Components/NetworkHierarchyRootComponent.h" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + + diff --git a/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyRootComponent.cpp b/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyRootComponent.cpp index 4a5e9ce8d2..ca4bbec66b 100644 --- a/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyRootComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyRootComponent.cpp @@ -139,6 +139,11 @@ namespace Multiplayer return m_hierarchicalEntities; } + const AZStd::vector& NetworkHierarchyRootComponent::GetHierarchicalEntitiesRef() const + { + return m_hierarchicalEntities; + } + AZ::Entity* NetworkHierarchyRootComponent::GetHierarchicalRoot() const { if (m_rootEntity) @@ -326,4 +331,99 @@ namespace Multiplayer RebuildHierarchy(); } } + + NetworkHierarchyRootComponentController::NetworkHierarchyRootComponentController(NetworkHierarchyRootComponent& parent) + : NetworkHierarchyRootComponentControllerBase(parent) + { + + } + + void NetworkHierarchyRootComponentController::OnActivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) + { + + } + + void NetworkHierarchyRootComponentController::OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) + { + + } + + Multiplayer::MultiplayerController::InputPriorityOrder NetworkHierarchyRootComponentController::GetInputOrder() const + { + return Multiplayer::MultiplayerController::InputPriorityOrder::SubEntities; + } + + void NetworkHierarchyRootComponentController::CreateInput(Multiplayer::NetworkInput& input, float deltaTime) + { + NetworkHierarchyRootComponent& component = GetParent(); + if(!component.IsHierarchicalRoot()) + { + return; + } + + const AZStd::vector& entities = component.GetHierarchicalEntitiesRef(); + + auto* networkInput = input.FindComponentInput(); + networkInput->m_childInputs.clear(); + networkInput->m_childInputs.reserve(entities.size()); + + for (AZ::Entity* child : entities) + { + if(child == component.GetEntity()) + { + return; // Avoid infinite recursion + } + + auto* netComp = child->FindComponent(); + AZ_Assert(netComp, "No NetSystemComponent, this should be impossible"); + // Validate we still have a controller and we aren't in the middle of removing them + if (netComp->HasController()) + { + ConstNetworkEntityHandle childEntityHandle = netComp->GetEntityHandle(); + NetworkSubInput subInput; + subInput.Attach(childEntityHandle); + subInput.GetNetworkInput().SetClientInputId(input.GetClientInputId()); + + netComp->CreateInput(subInput.GetNetworkInput(), deltaTime); + + // make sure our input sub commands have the same time as the original + subInput.GetNetworkInput().SetClientInputId(input.GetClientInputId()); + networkInput->m_childInputs.emplace_back(subInput); + } + } + } + + void NetworkHierarchyRootComponentController::ProcessInput(Multiplayer::NetworkInput& input, float deltaTime) + { + // Prevent replaying process input commands for child entities that weren't part of the hierarchy at that time + //if (!m_firstProcessInputOccurred) + //{ + // m_firstProcessInputOccurred = true; + // m_firstProcessInputTime = input.GetInputId().GetGameTimePoint(); + //} + //else if (m_firstProcessInputTime > input.GetInputId().GetGameTimePoint()) + //{ + // return; + //} + + if (auto* networkInput = input.FindComponentInput()) + { + for (NetworkSubInput& subInput : networkInput->m_childInputs) + { + const ConstNetworkEntityHandle& childEntity = subInput.GetOwner(); + if (auto* localChild = childEntity.GetEntity()) + { + auto* netComp = localChild->FindComponent(); + AZ_Assert(netComp, "No NetSystemComponent, this should be impossible"); + // We do not rewind entity role changes, so make sure we are the correct role prior to processing + if (netComp->HasController()) + { + subInput.GetNetworkInput().SetClientInputId(input.GetClientInputId()); + netComp->ProcessInput(subInput.GetNetworkInput(), deltaTime); + } + } + } + } + } + } diff --git a/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInput.cpp b/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInput.cpp index 35ca02bfba..2bbda48551 100644 --- a/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInput.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInput.cpp @@ -188,4 +188,53 @@ namespace Multiplayer } m_wasAttached = rhs.m_wasAttached; } + + NetworkSubInput::NetworkSubInput(const ConstNetworkEntityHandle& entityHandle) + : m_owner(entityHandle) + { + Attach(m_owner); + } + + void NetworkSubInput::Attach(const ConstNetworkEntityHandle& entityHandle) + { + m_owner = entityHandle; + if (auto* localEnt = entityHandle.GetEntity()) + { + NetBindComponent* netBindComponent = localEnt->FindComponent(); + if (netBindComponent) + { + m_networkInput.AttachNetBindComponent(netBindComponent); + } + } + } + + const ConstNetworkEntityHandle& NetworkSubInput::GetOwner() const + { + return m_owner; + } + + NetworkInput& NetworkSubInput::GetNetworkInput() + { + return m_networkInput; + } + + const NetworkInput& NetworkSubInput::GetNetworkInput() const + { + return m_networkInput; + } + + bool NetworkSubInput::Serialize(AzNetworking::ISerializer& serializer) + { + NetEntityId tmpId = m_owner.GetNetEntityId(); + serializer.Serialize(tmpId, "OwnerId"); + + if (serializer.GetSerializerMode() == AzNetworking::SerializerMode::WriteToObject) + { + m_owner = AZ::Interface::Get()->GetEntity(tmpId); + } + + serializer.Serialize(m_networkInput, "NetInput"); + return serializer.IsValid(); + } + }