MultiplayerConnectionViewportMessageSystemComponent.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include "MultiplayerConnectionViewportMessageSystemComponent.h"
  9. #include <Atom/RPI.Public/ViewportContextBus.h>
  10. #include <Atom/RPI.Public/ViewportContext.h>
  11. #include <AzCore/Console/IConsole.h>
  12. #include <AzCore/Console/ILogger.h>
  13. #include <AzFramework/Entity/EntityDebugDisplayBus.h>
  14. #include <AzNetworking/Framework/INetworking.h>
  15. #include <Multiplayer/IMultiplayerSpawner.h>
  16. #include <Multiplayer/MultiplayerConstants.h>
  17. namespace Multiplayer
  18. {
  19. constexpr float defaultConnectionMessageFontSize = 0.7f;
  20. const AZ::Vector2 viewportConnectionBottomRightBorderPadding(-40.0f, -40.0f);
  21. AZ_CVAR_SCOPED(bool, bg_viewportConnectionStatus, true, nullptr, AZ::ConsoleFunctorFlags::DontReplicate,
  22. "This will enable displaying connection status in the client's viewport while running multiplayer.");
  23. AZ_CVAR_SCOPED(float, bg_viewportConnectionMessageFontSize, defaultConnectionMessageFontSize, nullptr, AZ::ConsoleFunctorFlags::DontReplicate,
  24. "The font size used for displaying updates on screen while the multiplayer editor is connecting to the server.");
  25. AZ_CVAR_SCOPED(int, cl_viewportConnectionStatusMaxDrawCount, 4, nullptr, AZ::ConsoleFunctorFlags::DontReplicate,
  26. "Limits the number of connect statuses seen in the viewport. Generally, clients are connected to 1 server, but defining a max draw count in case other connections are established.");
  27. void MultiplayerConnectionViewportMessageSystemComponent::Reflect(AZ::ReflectContext* context)
  28. {
  29. if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
  30. {
  31. serializeContext->Class<MultiplayerConnectionViewportMessageSystemComponent, AZ::Component>()
  32. ->Version(1);
  33. }
  34. }
  35. void MultiplayerConnectionViewportMessageSystemComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required)
  36. {
  37. required.push_back(AZ_CRC_CE("MultiplayerService"));
  38. }
  39. void MultiplayerConnectionViewportMessageSystemComponent::Activate()
  40. {
  41. AZ::RPI::ViewportContextNotificationBus::Handler::BusConnect(
  42. AZ::RPI::ViewportContextRequests::Get()->GetDefaultViewportContextName());
  43. MultiplayerEditorServerNotificationBus::Handler::BusConnect();
  44. if (auto multiplayerSystemComponent = AZ::Interface<IMultiplayer>::Get())
  45. {
  46. multiplayerSystemComponent->AddVersionMismatchHandler(m_versionMismatchEventHandler);
  47. multiplayerSystemComponent->AddLevelLoadBlockedHandler(m_levelLoadBlockedHandler);
  48. multiplayerSystemComponent->AddNoServerLevelLoadedHandler(m_noServerLevelLoadedHandler);
  49. }
  50. }
  51. void MultiplayerConnectionViewportMessageSystemComponent::Deactivate()
  52. {
  53. m_noServerLevelLoadedHandler.Disconnect();
  54. m_levelLoadBlockedHandler.Disconnect();
  55. m_versionMismatchEventHandler.Disconnect();
  56. MultiplayerEditorServerNotificationBus::Handler::BusDisconnect();
  57. AZ::RPI::ViewportContextNotificationBus::Handler::BusDisconnect();
  58. }
  59. void MultiplayerConnectionViewportMessageSystemComponent::OnRenderTick()
  60. {
  61. if (!bg_viewportConnectionStatus)
  62. {
  63. return;
  64. }
  65. AZ::RPI::ViewportContextPtr viewport = AZ::RPI::ViewportContextRequests::Get()->GetDefaultViewportContext();
  66. if (!viewport)
  67. {
  68. return;
  69. }
  70. auto fontQueryInterface = AZ::Interface<AzFramework::FontQueryInterface>::Get();
  71. if (!fontQueryInterface)
  72. {
  73. return;
  74. }
  75. m_fontDrawInterface = fontQueryInterface->GetDefaultFontDrawInterface();
  76. if (!m_fontDrawInterface)
  77. {
  78. return;
  79. }
  80. m_drawParams.m_drawViewportId = viewport->GetId();
  81. m_drawParams.m_scale = AZ::Vector2(bg_viewportConnectionMessageFontSize);
  82. m_lineSpacing = 0.5f*m_fontDrawInterface->GetTextSize(m_drawParams, " ").GetY();
  83. AzFramework::WindowSize viewportSize = viewport->GetViewportSize();
  84. // Display the custom center viewport text
  85. DrawCenterViewportMessage(CenterViewportDebugTitle, m_centerViewportDebugTextColor, m_centerViewportDebugText.c_str(), 1.0f);
  86. // Build the connection status string (just show client connected or disconnected status for now)
  87. const auto multiplayerSystemComponent = AZ::Interface<IMultiplayer>::Get();
  88. MultiplayerAgentType agentType = multiplayerSystemComponent->GetAgentType();
  89. // Display the connection status in the bottom-right viewport
  90. m_drawParams.m_hAlign = AzFramework::TextHorizontalAlignment::Right;
  91. m_drawParams.m_position = AZ::Vector3(aznumeric_cast<float>(viewportSize.m_width), aznumeric_cast<float>(viewportSize.m_height), 1.0f) + AZ::Vector3(viewportConnectionBottomRightBorderPadding) * viewport->GetDpiScalingFactor();
  92. AzNetworking::INetworkInterface* networkInterface = AZ::Interface<AzNetworking::INetworking>::Get()->RetrieveNetworkInterface(AZ::Name(MpNetworkInterfaceName));
  93. switch (agentType)
  94. {
  95. case MultiplayerAgentType::Uninitialized:
  96. if (const auto console = AZ::Interface<AZ::IConsole>::Get())
  97. {
  98. bool isDedicatedServer = false;
  99. if (console->GetCvarValue("sv_isDedicated", isDedicatedServer) != AZ::GetValueResult::Success)
  100. {
  101. AZLOG_WARN("MultiplayerConnectionViewport failed to access cvar (sv_isDedicated).")
  102. break;
  103. }
  104. if (isDedicatedServer)
  105. {
  106. DrawConnectionStatusLine(DedicatedServerNotHosting, AZ::Colors::Red);
  107. DrawConnectionStatusLine(DedicatedServerStatusTitle, AZ::Colors::White);
  108. }
  109. }
  110. break;
  111. case MultiplayerAgentType::Client:
  112. {
  113. if (!networkInterface)
  114. {
  115. break;
  116. }
  117. AzNetworking::IConnectionSet& connectionSet = networkInterface->GetConnectionSet();
  118. m_currentConnectionsDrawCount = 0;
  119. if (connectionSet.GetConnectionCount() > 0)
  120. {
  121. connectionSet.VisitConnections(
  122. [this](AzNetworking::IConnection& connection)
  123. {
  124. m_hostIpAddress = connection.GetRemoteAddress();
  125. this->DrawConnectionStatus(connection.GetConnectionState(), m_hostIpAddress);
  126. });
  127. }
  128. else
  129. {
  130. // If we're a client yet are lacking a connection then we've been unintentionally disconnected
  131. // Display a disconnect message in the viewport
  132. DrawConnectionStatus(AzNetworking::ConnectionState::Disconnected, m_hostIpAddress);
  133. }
  134. }
  135. break;
  136. case MultiplayerAgentType::ClientServer:
  137. {
  138. if (!networkInterface)
  139. {
  140. break;
  141. }
  142. const auto clientServerHostingPort =
  143. AZStd::fixed_string<MaxMessageLength>::format(ServerHostingPort, networkInterface->GetPort());
  144. const auto clientServerClientCount = AZStd::fixed_string<MaxMessageLength>::format(
  145. ClientServerHostingClientCount, 1+networkInterface->GetConnectionSet().GetConnectionCount());
  146. DrawConnectionStatusLine(clientServerClientCount.c_str(), AZ::Colors::Green);
  147. DrawConnectionStatusLine(clientServerHostingPort.c_str(), AZ::Colors::Green);
  148. DrawConnectionStatusLine(ClientServerStatusTitle, AZ::Colors::White);
  149. break;
  150. }
  151. case MultiplayerAgentType::DedicatedServer:
  152. {
  153. if (!networkInterface)
  154. {
  155. break;
  156. }
  157. const auto dedicatedServerHostingPort = AZStd::fixed_string<MaxMessageLength>::format(
  158. ServerHostingPort, networkInterface->GetPort());
  159. const auto dedicatedServerClientCount = AZStd::fixed_string<MaxMessageLength>::format(
  160. DedicatedServerHostingClientCount, networkInterface->GetConnectionSet().GetConnectionCount());
  161. const AZ::Color serverHostStatusColor = networkInterface->GetConnectionSet().GetConnectionCount() > 0 ? AZ::Colors::Green : AZ::Colors::Yellow;
  162. DrawConnectionStatusLine(dedicatedServerClientCount.c_str(), serverHostStatusColor);
  163. DrawConnectionStatusLine(dedicatedServerHostingPort.c_str(), serverHostStatusColor);
  164. DrawConnectionStatusLine(DedicatedServerStatusTitle, AZ::Colors::White);
  165. break;
  166. }
  167. default:
  168. AZLOG_ERROR(
  169. "MultiplayerConnectionViewportMessageSystemComponent doesn't support drawing status for multiplayer agent type %s. Please update code to support the new agent type.",
  170. GetEnumString(agentType));
  171. break;
  172. }
  173. // Display the viewport toast text
  174. if (!m_centerViewportDebugToastText.empty())
  175. {
  176. // Fade out the toast over time
  177. const size_t wordCount = m_centerViewportDebugToastText.find(' ') + 1; // Word count estimated by counting the number of spaces and adding 1.
  178. const AZ::TimeMs toastDuration = static_cast<AZ::TimeMs>(wordCount) * CenterViewportDebugToastTimePerWord + CenterViewportDebugToastTimePrefix + CenterViewportDebugToastTimeFade;
  179. const AZ::TimeMs currentTime = static_cast<AZ::TimeMs>(AZStd::GetTimeUTCMilliSecond());
  180. const AZ::TimeMs remainingTime = toastDuration - (currentTime - m_centerViewportDebugToastStartTime);
  181. const float toastAlpha = AZStd::clamp(aznumeric_cast<float>(remainingTime) / aznumeric_cast<float>(CenterViewportDebugToastTimeFade), 0.0f, 1.0f);
  182. DrawCenterViewportMessage(CenterViewportToastTitle, AZ::Colors::Red, m_centerViewportDebugToastText.c_str(), toastAlpha);
  183. if (toastAlpha < 0.01f)
  184. {
  185. // toast is completely faded out, remove the toast
  186. m_centerViewportDebugToastText.clear();
  187. }
  188. }
  189. }
  190. void MultiplayerConnectionViewportMessageSystemComponent::DrawCenterViewportMessage(const char* title, const AZ::Color& titleColor, const char* message, float alpha)
  191. {
  192. const AZ::RPI::ViewportContextPtr viewport = AZ::RPI::ViewportContextRequests::Get()->GetDefaultViewportContext();
  193. if (!viewport)
  194. {
  195. return;
  196. }
  197. // make sure there's a title and message to render
  198. if (title == nullptr || strlen(title) == 0)
  199. {
  200. return;
  201. }
  202. if (message == nullptr || strlen(message) == 0)
  203. {
  204. return;
  205. }
  206. // only render text that will be visible
  207. if (alpha < 0.01f)
  208. {
  209. return;
  210. }
  211. // Draw background for text contrast
  212. DrawScrim(alpha);
  213. // Find viewport center
  214. AzFramework::WindowSize viewportSize = viewport->GetViewportSize();
  215. const float center_screenposition_x = 0.5f * viewportSize.m_width;
  216. const float center_screenposition_y = 0.5f * viewportSize.m_height;
  217. // Draw title
  218. const float textHeight = m_fontDrawInterface->GetTextSize(m_drawParams, title).GetY();
  219. const float screenposition_title_y = center_screenposition_y - textHeight * 0.5f;
  220. m_drawParams.m_position = AZ::Vector3(center_screenposition_x, screenposition_title_y, 1.0f);
  221. m_drawParams.m_hAlign = AzFramework::TextHorizontalAlignment::Center;
  222. m_drawParams.m_color = titleColor;
  223. m_drawParams.m_color.SetA(alpha);
  224. m_fontDrawInterface->DrawScreenAlignedText2d(m_drawParams, title);
  225. // Draw message under the title
  226. // Calculate line spacing based on the font's actual line height
  227. m_drawParams.m_color = AZ::Colors::White;
  228. m_drawParams.m_color.SetA(alpha);
  229. m_drawParams.m_position.SetY(m_drawParams.m_position.GetY() + textHeight + m_lineSpacing);
  230. m_fontDrawInterface->DrawScreenAlignedText2d(m_drawParams, message);
  231. }
  232. void MultiplayerConnectionViewportMessageSystemComponent::DrawConnectionStatus(AzNetworking::ConnectionState connectionState, const AzNetworking::IpAddress& hostIpAddress)
  233. {
  234. // Limit the amount of connections we draw on screen
  235. if (m_currentConnectionsDrawCount >= cl_viewportConnectionStatusMaxDrawCount)
  236. {
  237. return;
  238. }
  239. ++m_currentConnectionsDrawCount;
  240. AZ::Color connectionStateColor;
  241. switch (connectionState)
  242. {
  243. case AzNetworking::ConnectionState::Connecting:
  244. connectionStateColor = AZ::Colors::Yellow;
  245. break;
  246. case AzNetworking::ConnectionState::Connected:
  247. connectionStateColor = AZ::Colors::Green;
  248. break;
  249. case AzNetworking::ConnectionState::Disconnecting:
  250. connectionStateColor = AZ::Colors::Yellow;
  251. break;
  252. case AzNetworking::ConnectionState::Disconnected:
  253. connectionStateColor = AZ::Colors::Red;
  254. break;
  255. default:
  256. connectionStateColor = AZ::Colors::White;
  257. }
  258. // Draw our host's remote ip address
  259. const auto multiplayerSystemComponent = AZ::Interface<IMultiplayer>::Get();
  260. MultiplayerAgentType agentType = multiplayerSystemComponent->GetAgentType();
  261. if (agentType == MultiplayerAgentType::Client)
  262. {
  263. auto hostAddressText = AZStd::fixed_string<32>::format("Server IP %s", hostIpAddress.GetString().c_str());
  264. DrawConnectionStatusLine(hostAddressText.c_str(), connectionStateColor);
  265. }
  266. // Draw the connect state (example: Connected or Disconnected)
  267. DrawConnectionStatusLine(ToString(connectionState).data(), connectionStateColor);
  268. // Draw the status title
  269. DrawConnectionStatusLine(ClientStatusTitle, AZ::Colors::White);
  270. }
  271. void MultiplayerConnectionViewportMessageSystemComponent::DrawConnectionStatusLine(const char* line, const AZ::Color& color)
  272. {
  273. m_drawParams.m_color = color;
  274. m_fontDrawInterface->DrawScreenAlignedText2d(m_drawParams, line);
  275. // Status text renders in the lower right corner, so we draw from the bottom up.
  276. // Move the font draw position up to get ready for the next text line.
  277. const float textHeight = m_fontDrawInterface->GetTextSize(m_drawParams, line).GetY();
  278. m_drawParams.m_position.SetY(m_drawParams.m_position.GetY() - textHeight - m_lineSpacing);
  279. }
  280. void MultiplayerConnectionViewportMessageSystemComponent::DrawScrim(float alphaMultiplier) const
  281. {
  282. AZ::RPI::ViewportContextPtr viewport = AZ::RPI::ViewportContextRequests::Get()->GetDefaultViewportContext();
  283. if (!viewport)
  284. {
  285. return;
  286. }
  287. AzFramework::DebugDisplayRequestBus::BusPtr debugDisplayBus;
  288. AzFramework::DebugDisplayRequestBus::Bind(debugDisplayBus, viewport->GetId());
  289. AzFramework::DebugDisplayRequests* debugDisplay = AzFramework::DebugDisplayRequestBus::FindFirstHandler(debugDisplayBus);
  290. if (!debugDisplay)
  291. {
  292. return;
  293. }
  294. // we're going to alter the state of depth write and test, store it here so we can restore when we're done drawing.
  295. const AZ::u32 previousState = debugDisplay->GetState();
  296. debugDisplay->DepthWriteOff();
  297. debugDisplay->DepthTestOff();
  298. debugDisplay->DrawQuad2dGradient(
  299. AZ::Vector2(0, 0),
  300. AZ::Vector2(1.f, 0),
  301. AZ::Vector2(1.f, 0.5f),
  302. AZ::Vector2(0, 0.5f),
  303. 0,
  304. AZ::Color(0.f, 0.f, 0.f, 0.f),
  305. AZ::Color(0, 0, 0, ScrimAlpha * alphaMultiplier));
  306. debugDisplay->DrawQuad2dGradient(
  307. AZ::Vector2(0, 0.5f),
  308. AZ::Vector2(1.f, 0.5f),
  309. AZ::Vector2(1.f, 1.f),
  310. AZ::Vector2(0, 1.f),
  311. 0,
  312. AZ::Color(0, 0, 0, ScrimAlpha * alphaMultiplier),
  313. AZ::Color(0.f, 0.f, 0.f, 0.f));
  314. debugDisplay->SetState(previousState);
  315. }
  316. void MultiplayerConnectionViewportMessageSystemComponent::OnServerLaunched()
  317. {
  318. m_centerViewportDebugTextColor = AZ::Colors::Yellow;
  319. m_centerViewportDebugText = OnServerLaunchedMessage;
  320. }
  321. void MultiplayerConnectionViewportMessageSystemComponent::OnServerLaunchFail()
  322. {
  323. m_centerViewportDebugTextColor = AZ::Colors::Red;
  324. m_centerViewportDebugText = OnServerLaunchFailMessage;
  325. }
  326. void MultiplayerConnectionViewportMessageSystemComponent::OnEditorSendingLevelData(uint32_t bytesSent, uint32_t bytesTotal)
  327. {
  328. m_centerViewportDebugTextColor = AZ::Colors::Yellow;
  329. m_centerViewportDebugText =
  330. AZStd::fixed_string<MaxMessageLength>::format(OnEditorSendingLevelDataMessage, bytesSent, bytesTotal);
  331. }
  332. void MultiplayerConnectionViewportMessageSystemComponent::OnEditorSendingLevelDataFailed()
  333. {
  334. m_centerViewportDebugTextColor = AZ::Colors::Red;
  335. m_centerViewportDebugText = OnEditorSendingLevelDataFailedMessage;
  336. }
  337. void MultiplayerConnectionViewportMessageSystemComponent::OnEditorSendingLevelDataSuccess()
  338. {
  339. m_centerViewportDebugTextColor = AZ::Colors::Yellow;
  340. m_centerViewportDebugText = OnEditorSendingLevelDataSuccessMessage;
  341. }
  342. void MultiplayerConnectionViewportMessageSystemComponent::OnEditorConnectionAttempt(uint16_t connectionAttempts, uint16_t maxAttempts)
  343. {
  344. m_centerViewportDebugTextColor = AZ::Colors::Yellow;
  345. m_centerViewportDebugText = AZStd::fixed_string<MaxMessageLength>::format(OnEditorConnectionAttemptMessage, connectionAttempts, maxAttempts);
  346. }
  347. void MultiplayerConnectionViewportMessageSystemComponent::OnEditorConnectionAttemptsFailed(uint16_t failedAttempts)
  348. {
  349. m_centerViewportDebugTextColor = AZ::Colors::Red;
  350. m_centerViewportDebugText = AZStd::fixed_string<MaxMessageLength>::format(OnEditorConnectionAttemptsFailedMessage, failedAttempts);
  351. }
  352. void MultiplayerConnectionViewportMessageSystemComponent::OnConnectToSimulationFail(uint16_t serverPort)
  353. {
  354. m_centerViewportDebugTextColor = AZ::Colors::Red;
  355. m_centerViewportDebugText = AZStd::fixed_string<MaxMessageLength>::format(OnConnectToSimulationFailMessage, serverPort);
  356. }
  357. void MultiplayerConnectionViewportMessageSystemComponent::OnConnectToSimulationSuccess()
  358. {
  359. m_centerViewportDebugText.clear();
  360. }
  361. void MultiplayerConnectionViewportMessageSystemComponent::OnPlayModeEnd()
  362. {
  363. m_centerViewportDebugText.clear();
  364. }
  365. void MultiplayerConnectionViewportMessageSystemComponent::OnEditorServerProcessStoppedUnexpectedly()
  366. {
  367. m_centerViewportDebugTextColor = AZ::Colors::Red;
  368. m_centerViewportDebugText = OnEditorServerStoppedUnexpectedly;
  369. }
  370. void MultiplayerConnectionViewportMessageSystemComponent::OnBlockedLevelLoad()
  371. {
  372. m_centerViewportDebugToastStartTime = static_cast<AZ::TimeMs>(AZStd::GetTimeUTCMilliSecond());
  373. m_centerViewportDebugToastText = OnBlockedLevelLoadMessage;
  374. }
  375. void MultiplayerConnectionViewportMessageSystemComponent::OnNoServerLevelLoadedEvent()
  376. {
  377. const auto multiplayerSystemComponent = AZ::Interface<IMultiplayer>::Get();
  378. if (!multiplayerSystemComponent)
  379. {
  380. return;
  381. }
  382. const MultiplayerAgentType agentType = multiplayerSystemComponent->GetAgentType();
  383. if (agentType == MultiplayerAgentType::Client)
  384. {
  385. m_centerViewportDebugToastText = OnNoServerLevelLoadedMessageClientSide;
  386. }
  387. else
  388. {
  389. m_centerViewportDebugToastText = OnNoServerLevelLoadedMessageServerSide;
  390. }
  391. m_centerViewportDebugToastStartTime = static_cast<AZ::TimeMs>(AZStd::GetTimeUTCMilliSecond());
  392. }
  393. void MultiplayerConnectionViewportMessageSystemComponent::OnVersionMismatchEvent()
  394. {
  395. m_centerViewportDebugToastText = OnVersionMismatch;
  396. m_centerViewportDebugToastStartTime = static_cast<AZ::TimeMs>(AZStd::GetTimeUTCMilliSecond());
  397. }
  398. }