diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/IMultiplayer.h b/Gems/Multiplayer/Code/Include/Multiplayer/IMultiplayer.h index 0a6c8da9be..45482a8dc7 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/IMultiplayer.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/IMultiplayer.h @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -128,6 +129,15 @@ namespace Multiplayer //! @return pointer to the network entity manager instance bound to this multiplayer instance virtual INetworkEntityManager* GetNetworkEntityManager() = 0; + //! Sets user-defined filtering manager for entities. + //! This allows selectively choosing which entities to replicate on a per client connection. + //! See IFilterEntityManager for details. + //! @param entityFilter non-owning pointer, the caller is responsible for memory management. + virtual void SetFilterEntityManager(IFilterEntityManager* entityFilter) = 0; + + //! @return pointer to the user-defined filtering manager of entities. By default, this isn't set and returns nullptr. + virtual IFilterEntityManager* GetFilterEntityManager() = 0; + //! Retrieve the stats object bound to this multiplayer instance. //! @return the stats object bound to this multiplayer instance MultiplayerStats& GetStats() { return m_stats; } diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/IFilterEntityManager.h b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/IFilterEntityManager.h new file mode 100644 index 0000000000..5ab8409454 --- /dev/null +++ b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/IFilterEntityManager.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include +#include + +namespace Multiplayer +{ + //! @class IFilterEntityManager + //! @brief IFilterEntityManager provides an interface for filtering entities out from replication down to clients. + //! + //! By default, all entities with NetBindComponent on them are replicated to all clients. + //! (There is a built-in distance filtering, where only entities within vicinity of a player are sent to that player. + //! This is controlled by sv_ClientAwarenessRadius AZ_CVAR variable.) + //! + //! There are use cases where you want to limit the entities sent to a client, for example "fog of war" or + //! "out of line of sight" anti-cheating mechanic by omitting information clients should not have access to. + //! + //! By implementing IFilterEntityManager interface and setting it on GetMultiplayer()->SetFilterEntityManager() + //! entities can be filtered by IsEntityFiltered(...) returning true. + //! + //! Note: one cannot filter out entities in Level prefab (spawned by LoadLevel console command). Level prefabs are fully + //! spawned on each client. Filtering of entities is applied to dynamically spawned prefabs, and specifically + //! entities must have NetBindComponent on them. + class IFilterEntityManager + { + public: + AZ_RTTI(IFilterEntityManager, "{91F879F2-3DAF-43B8-B474-B312D26C0F48}"); + + virtual ~IFilterEntityManager() = default; + + //! Return true if a given entity should be filtered out, false otherwise. + //! Important: this method is a hot code path, it will be called over all entities around each player frequently. + //! Ideally, this method should be implemented as a quick look up. + //! + //! @param entity the entity to be considered for filtering + //! @param controllerEntity player's entity for the associated connection + //! @param connectionId the affected connection should the entity be filtered out. + //! @return if false the given entity will be not be replicated to the connection + virtual bool IsEntityFiltered(AZ::Entity* entity, ConstNetworkEntityHandle controllerEntity, AzNetworking::ConnectionId connectionId) = 0; + }; +} diff --git a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp index dd0135c34b..531f133ae3 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp @@ -812,6 +812,16 @@ namespace Multiplayer return &m_networkEntityManager; } + void MultiplayerSystemComponent::SetFilterEntityManager(IFilterEntityManager* entityFilter) + { + m_filterEntityManager = entityFilter; + } + + IFilterEntityManager* MultiplayerSystemComponent::GetFilterEntityManager() + { + return m_filterEntityManager; + } + void MultiplayerSystemComponent::DumpStats([[maybe_unused]] const AZ::ConsoleCommandContainer& arguments) { const MultiplayerStats& stats = GetStats(); diff --git a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h index 4ce22a214a..30734bbae1 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h +++ b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h @@ -114,6 +114,8 @@ namespace Multiplayer AZ::TimeMs GetCurrentHostTimeMs() const override; INetworkTime* GetNetworkTime() override; INetworkEntityManager* GetNetworkEntityManager() override; + void SetFilterEntityManager(IFilterEntityManager* entityFilter) override; + IFilterEntityManager* GetFilterEntityManager() override; //! @} //! Console commands. @@ -138,6 +140,8 @@ namespace Multiplayer NetworkEntityManager m_networkEntityManager; NetworkTime m_networkTime; MultiplayerAgentType m_agentType = MultiplayerAgentType::Uninitialized; + + IFilterEntityManager* m_filterEntityManager = nullptr; // non-owning pointer SessionInitEvent m_initEvent; SessionShutdownEvent m_shutdownEvent; diff --git a/Gems/Multiplayer/Code/Source/ReplicationWindows/ServerToClientReplicationWindow.cpp b/Gems/Multiplayer/Code/Source/ReplicationWindows/ServerToClientReplicationWindow.cpp index 088a36cced..70ee567a9e 100644 --- a/Gems/Multiplayer/Code/Source/ReplicationWindows/ServerToClientReplicationWindow.cpp +++ b/Gems/Multiplayer/Code/Source/ReplicationWindows/ServerToClientReplicationWindow.cpp @@ -63,13 +63,6 @@ namespace Multiplayer m_controlledEntityTransform = entity ? entity->GetTransform() : nullptr; AZ_Assert(m_controlledEntityTransform, "Controlled player entity must have a transform"); - //// this one is optional - //mp_ControlledFilteredEntityComponent = m_controlledEntity->FindController(); - //if (mp_ControlledFilteredEntityComponent) - //{ - // mp_ControlledFilteredEntityComponent->AddFilteredEntityEventHandle(m_FilteredEntityAddedEventHandle); - //} - m_updateWindowEvent.Enqueue(sv_ClientReplicationWindowUpdateMs, true); AZ::Interface::Get()->RegisterEntityActivatedEventHandler(m_entityActivatedEventHandler); @@ -145,21 +138,23 @@ namespace Multiplayer } ); - NetworkEntityTracker* networkEntityTracker = GetNetworkEntityTracker(); + NetworkEntityTracker* networkEntityTracker = GetNetworkEntityTracker(); + IFilterEntityManager* filterEntityManager = GetMultiplayer()->GetFilterEntityManager(); // Add all the neighbors for (AzFramework::VisibilityEntry* visEntry : gatheredEntries) { - //if (mp_ControlledFilteredEntityComponent && mp_ControlledFilteredEntityComponent->IsEntityFiltered(iterator.Get())) - //{ - // continue; - //} - - // We want to find the closest extent to the player and prioritize using that distance AZ::Entity* entity = static_cast(visEntry->m_userData); + + if (filterEntityManager && filterEntityManager->IsEntityFiltered(entity, m_controlledEntity, m_connection->GetConnectionId())) + { + continue; + } + NetBindComponent* entryNetBindComponent = entity->template FindComponent(); if (entryNetBindComponent != nullptr) { + // We want to find the closest extent to the player and prioritize using that distance const AZ::Vector3 supportNormal = controlledEntityPosition - visEntry->m_boundingVolume.GetCenter(); const AZ::Vector3 closestPosition = visEntry->m_boundingVolume.GetSupport(supportNormal); const float gatherDistanceSquared = controlledEntityPosition.GetDistanceSq(closestPosition); @@ -212,10 +207,13 @@ namespace Multiplayer { if (netBindComponent->HasController()) { - //if (mp_ControlledFilteredEntityComponent && mp_ControlledFilteredEntityComponent->IsEntityFiltered(newEntity)) - //{ - // return; - //} + if (IFilterEntityManager* filter = GetMultiplayer()->GetFilterEntityManager()) + { + if (filter->IsEntityFiltered(entity, m_controlledEntity, m_connection->GetConnectionId())) + { + return; + } + } AZ::TransformInterface* transformInterface = entity->GetTransform(); if (transformInterface != nullptr) @@ -274,6 +272,8 @@ 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(); @@ -314,10 +314,4 @@ namespace Multiplayer // } // } //} - - //void ServerToClientReplicationWindow::OnAddFilteredEntity(NetEntityId filteredEntityId) - //{ - // ConstEntityPtr filteredEntity = gNovaGame->GetEntityManager().GetEntity(filteredEntityId); - // m_replicationSet.erase(filteredEntityId); - //} } diff --git a/Gems/Multiplayer/Code/Source/ReplicationWindows/ServerToClientReplicationWindow.h b/Gems/Multiplayer/Code/Source/ReplicationWindows/ServerToClientReplicationWindow.h index 31b100523a..322c53c663 100644 --- a/Gems/Multiplayer/Code/Source/ReplicationWindows/ServerToClientReplicationWindow.h +++ b/Gems/Multiplayer/Code/Source/ReplicationWindows/ServerToClientReplicationWindow.h @@ -54,7 +54,6 @@ namespace Multiplayer void OnEntityDeactivated(AZ::Entity* entity); //void CollectControlledEntitiesRecursive(ReplicationSet& replicationSet, EntityHierarchyComponent::Authority& hierarchyController); - //void OnAddFilteredEntity(NetEntityId filteredEntityId); void EvaluateConnection(); void AddEntityToReplicationSet(ConstNetworkEntityHandle& entityHandle, float priority, float distanceSquared); @@ -73,7 +72,6 @@ namespace Multiplayer AZ::EntityActivatedEvent::Handler m_entityActivatedEventHandler; AZ::EntityDeactivatedEvent::Handler m_entityDeactivatedEventHandler; - //FilteredEntityComponent::Authority* m_controlledFilteredEntityComponent = nullptr; //NetBindComponent* m_controlledNetBindComponent = nullptr; const AzNetworking::IConnection* m_connection = nullptr; diff --git a/Gems/Multiplayer/Code/multiplayer_files.cmake b/Gems/Multiplayer/Code/multiplayer_files.cmake index 8521ccbaf4..b376df1848 100644 --- a/Gems/Multiplayer/Code/multiplayer_files.cmake +++ b/Gems/Multiplayer/Code/multiplayer_files.cmake @@ -21,6 +21,7 @@ set(FILES Include/Multiplayer/EntityDomains/IEntityDomain.h Include/Multiplayer/NetworkEntity/INetworkEntityManager.h Include/Multiplayer/INetworkSpawnableLibrary.h + Include/Multiplayer/NetworkEntity/IFilterEntityManager.h Include/Multiplayer/NetworkEntity/NetworkEntityRpcMessage.h Include/Multiplayer/NetworkEntity/NetworkEntityUpdateMessage.h Include/Multiplayer/NetworkEntity/NetworkEntityHandle.h