diff --git a/Gems/Multiplayer/Code/CMakeLists.txt b/Gems/Multiplayer/Code/CMakeLists.txt index f811ac64b3..c95afbbb74 100644 --- a/Gems/Multiplayer/Code/CMakeLists.txt +++ b/Gems/Multiplayer/Code/CMakeLists.txt @@ -59,6 +59,56 @@ ly_add_target( Gem::CertificateManager ) +if(PAL_TRAIT_BUILD_HOST_TOOLS) + + ly_add_target( + NAME Multiplayer.Editor.Static STATIC + NAMESPACE Gem + FILES_CMAKE + multiplayer_editor_files.cmake + COMPILE_DEFINITIONS + PUBLIC + MULTIPLAYER_EDITOR + INCLUDE_DIRECTORIES + PRIVATE + . + Source + ${pal_source_dir} + PUBLIC + Include + BUILD_DEPENDENCIES + PUBLIC + Legacy::CryCommon + Legacy::Editor.Headers + AZ::AzCore + AZ::AzFramework + AZ::AzNetworking + AZ::AzToolsFramework + AZ::GFxFramework + Gem::Multiplayer.Static + ) + + ly_add_target( + NAME Multiplayer.Editor GEM_MODULE + NAMESPACE Gem + FILES_CMAKE + multiplayer_editor_shared_files.cmake + INCLUDE_DIRECTORIES + PRIVATE + . + Source + ${pal_source_dir} + PUBLIC + Include + BUILD_DEPENDENCIES + PRIVATE + Gem::Multiplayer.Editor.Static + RUNTIME_DEPENDENCIES + Gem::LmbrCentral.Editor + ) + +endif() + ################################################################################ # Tests ################################################################################ diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorDispatcher.cpp b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorDispatcher.cpp new file mode 100644 index 0000000000..470aa61cd0 --- /dev/null +++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorDispatcher.cpp @@ -0,0 +1,21 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#include + +namespace Multiplayer +{ + MultiplayerEditorDispatcher::MultiplayerEditorDispatcher() + { + ; + } +} diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorDispatcher.h b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorDispatcher.h new file mode 100644 index 0000000000..c1058dc8a0 --- /dev/null +++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorDispatcher.h @@ -0,0 +1,36 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#pragma once + +#include + +#include +#include +#include +#include + +#include + + +namespace Multiplayer +{ + //! MultiplayerEditorDispatcher is responsible for dispatching delta from the Editor to an Editor launched local server + class MultiplayerEditorDispatcher final + { + public: + MultiplayerEditorDispatcher(); + ~MultiplayerEditorDispatcher() = default; + + private: + }; +} diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp new file mode 100644 index 0000000000..0227d8b159 --- /dev/null +++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp @@ -0,0 +1,149 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#include +#include +#include +#include +#include +#include + +namespace Multiplayer +{ + using namespace AzNetworking; + + AZ_CVAR(bool, editorsv_enabled, true, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, + "Whether Editor launching a local server to connect to is supported"); + AZ_CVAR(AZ::CVarFixedString, editorsv_process, "", nullptr, AZ::ConsoleFunctorFlags::DontReplicate, + "The server executable that should be run. Empty to use the current project's ServerLauncher"); + + void MultiplayerEditorSystemComponent::Reflect(AZ::ReflectContext* context) + { + if (AZ::SerializeContext* serializeContext = azrtti_cast(context)) + { + serializeContext->Class() + ->Version(1); + } + } + + void MultiplayerEditorSystemComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required) + { + required.push_back(AZ_CRC_CE("MultiplayerService")); + } + + void MultiplayerEditorSystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided) + { + provided.push_back(AZ_CRC_CE("MultiplayerEditorService")); + } + + void MultiplayerEditorSystemComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible) + { + incompatible.push_back(AZ_CRC_CE("MultiplayerEditorService")); + } + + MultiplayerEditorSystemComponent::MultiplayerEditorSystemComponent() + { + ; + } + + void MultiplayerEditorSystemComponent::Activate() + { + AzToolsFramework::EditorEvents::Bus::Handler::BusConnect(); + } + + void MultiplayerEditorSystemComponent::Deactivate() + { + AzToolsFramework::EditorEvents::Bus::Handler::BusDisconnect(); + } + + void MultiplayerEditorSystemComponent::NotifyRegisterViews() + { + m_editor = nullptr; + AzToolsFramework::EditorRequests::Bus::BroadcastResult(m_editor, &AzToolsFramework::EditorRequests::GetEditor); + m_editor->RegisterNotifyListener(this); + } + + void MultiplayerEditorSystemComponent::OnEditorNotifyEvent(EEditorNotifyEvent event) + { + switch (event) + { + case eNotify_OnBeginGameMode: + { + AZ::TickBus::Handler::BusConnect(); + + if (editorsv_enabled) + { + // Assemble the server's path + AZ::CVarFixedString serverProcess = editorsv_process; + if (serverProcess.empty()) + { + // If enabled but no process name is supplied, try this project's ServerLauncher + serverProcess = AZ::Utils::GetProjectName() + ".ServerLauncher"; + } + + AZ::IO::FixedMaxPathString serverPath = AZ::Utils::GetExecutableDirectory(); + if (!serverProcess.contains(AZ_TRAIT_OS_PATH_SEPARATOR)) + { + // If only the process name is specified, append that as well + serverPath.append(AZ_TRAIT_OS_PATH_SEPARATOR + serverProcess); + } + else + { + // If any path was already specified, then simply assign + serverPath = serverProcess; + } + + if (!serverProcess.ends_with(AZ_TRAIT_OS_EXECUTABLE_EXTENSION)) + { + // Add this platform's exe extension if it's not specified + serverPath.append(AZ_TRAIT_OS_EXECUTABLE_EXTENSION); + } + + // Start the configured server if it's available + AzFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo; + processLaunchInfo.m_commandlineParameters = + AZStd::string::format("\"%s\"", serverPath.c_str()); + processLaunchInfo.m_showWindow = true; + processLaunchInfo.m_processPriority = AzFramework::ProcessPriority::PROCESSPRIORITY_NORMAL; + + m_serverProcess = AzFramework::ProcessWatcher::LaunchProcess( + processLaunchInfo, AzFramework::ProcessCommunicationType::COMMUNICATOR_TYPE_NONE); + } + break; + } + case eNotify_OnQuit: + m_editor->UnregisterNotifyListener(this); + m_editor = nullptr; + // Fall through here as the cleanup steps in EndGameMode are good sanity + case eNotify_OnEndGameMode: + AZ::TickBus::Handler::BusDisconnect(); + // Kill the configured server if it's active + if (m_serverProcess) + { + m_serverProcess->TerminateProcess(0); + m_serverProcess = nullptr; + } + break; + } + } + + void MultiplayerEditorSystemComponent::OnTick([[maybe_unused]] float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint time) + { + + } + + int MultiplayerEditorSystemComponent::GetTickOrder() + { + // Tick immediately after the network system component + return AZ::TICK_PLACEMENT + 1; + } +} diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.h b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.h new file mode 100644 index 0000000000..8c18a2e57a --- /dev/null +++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.h @@ -0,0 +1,75 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#pragma once + +#include + +#include +#include +#include +#include + +#include +#include + + +namespace AzNetworking +{ + class INetworkInterface; +} + +namespace Multiplayer +{ + //! Multiplayer system component wraps the bridging logic between the game and transport layer. + class MultiplayerEditorSystemComponent final + : public AZ::Component + , private AZ::TickBus::Handler + , private AzToolsFramework::EditorEvents::Bus::Handler + , private IEditorNotifyListener + { + public: + AZ_COMPONENT(MultiplayerEditorSystemComponent, "{9F335CC0-5574-4AD3-A2D8-2FAEF356946C}"); + + static void Reflect(AZ::ReflectContext* context); + static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required); + static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided); + static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible); + + MultiplayerEditorSystemComponent(); + ~MultiplayerEditorSystemComponent() override = default; + + //! AZ::Component overrides. + //! @{ + void Activate() override; + void Deactivate() override; + //! @} + + //! EditorEvents::Bus::Handler overrides. + //! @{ + void NotifyRegisterViews() override; + //! @} + + private: + + //! AZ::TickBus::Handler overrides. + //! @{ + void OnTick(float deltaTime, AZ::ScriptTimePoint time) override; + int GetTickOrder() override; + //! @} + //! + void OnEditorNotifyEvent(EEditorNotifyEvent event) override; + + IEditor* m_editor = nullptr; + AzFramework::ProcessWatcher* m_serverProcess = nullptr; + }; +} diff --git a/Gems/Multiplayer/Code/Source/MultiplayerGem.cpp b/Gems/Multiplayer/Code/Source/MultiplayerGem.cpp index 80f1635bdc..424cb5b14f 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerGem.cpp +++ b/Gems/Multiplayer/Code/Source/MultiplayerGem.cpp @@ -15,6 +15,10 @@ #include #include +#ifdef MULTIPLAYER_EDITOR + #include +#endif + namespace Multiplayer { MultiplayerModule::MultiplayerModule() @@ -23,6 +27,9 @@ namespace Multiplayer m_descriptors.insert(m_descriptors.end(), { AzNetworking::NetworkingSystemComponent::CreateDescriptor(), MultiplayerSystemComponent::CreateDescriptor(), +#ifdef MULTIPLAYER_EDITOR + MultiplayerEditorSystemComponent::CreateDescriptor(), +#endif }); } @@ -32,8 +39,11 @@ namespace Multiplayer { azrtti_typeid(), azrtti_typeid(), +#ifdef MULTIPLAYER_EDITOR + azrtti_typeid(), +#endif }; } } -AZ_DECLARE_MODULE_CLASS(Gem_Multiplayer2, Multiplayer::MultiplayerModule); +AZ_DECLARE_MODULE_CLASS(Gem_Multiplayer, Multiplayer::MultiplayerModule); diff --git a/Gems/Multiplayer/Code/multiplayer_editor_files.cmake b/Gems/Multiplayer/Code/multiplayer_editor_files.cmake new file mode 100644 index 0000000000..ce3e3227e0 --- /dev/null +++ b/Gems/Multiplayer/Code/multiplayer_editor_files.cmake @@ -0,0 +1,15 @@ +# +# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +# its licensors. +# +# For complete copyright and license terms please see the LICENSE at the root of this +# distribution (the "License"). All use of this software is governed by the License, +# or, if provided, by the license below or the license accompanying this file. Do not +# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# + +set(FILES + Source/Editor/MultiplayerEditorDispatcher.cpp + Source/Editor/MultiplayerEditorDispatcher.h +) diff --git a/Gems/Multiplayer/Code/multiplayer_editor_shared_files.cmake b/Gems/Multiplayer/Code/multiplayer_editor_shared_files.cmake new file mode 100644 index 0000000000..e0c50f864e --- /dev/null +++ b/Gems/Multiplayer/Code/multiplayer_editor_shared_files.cmake @@ -0,0 +1,17 @@ +# +# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +# its licensors. +# +# For complete copyright and license terms please see the LICENSE at the root of this +# distribution (the "License"). All use of this software is governed by the License, +# or, if provided, by the license below or the license accompanying this file. Do not +# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# + +set(FILES + Source/MultiplayerGem.cpp + Source/MultiplayerGem.h + Source/Editor/MultiplayerEditorSystemComponent.cpp + Source/Editor/MultiplayerEditorSystemComponent.h +)