NetworkMatchComponent.cpp 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
  3. *
  4. * SPDX-License-Identifier: Apache-2.0 OR MIT
  5. *
  6. */
  7. #include <GameplayEffectsNotificationBus.h>
  8. #include <MultiplayerSampleTypes.h>
  9. #include <UiGameOverBus.h>
  10. #include <GameState/GameStateMatchEnded.h>
  11. #include <GameState/GameStateMatchInProgress.h>
  12. #include <GameState/GameStatePreparingMatch.h>
  13. #include <Source/Components/Multiplayer/MatchPlayerCoinsComponent.h>
  14. #include <Source/Components/Multiplayer/PlayerIdentityComponent.h>
  15. #include <Source/Components/NetworkTeleportCompatibleComponent.h>
  16. #include <Source/Components/NetworkHealthComponent.h>
  17. #include <Source/Components/NetworkMatchComponent.h>
  18. #include <Source/Components/NetworkRandomComponent.h>
  19. #include <GameState/GameStateRequestBus.h>
  20. #include <GameState/GameStateWaitingForPlayers.h>
  21. #include "NetworkRandomComponent.h"
  22. #include "Multiplayer/GemSpawnerComponent.h"
  23. #include <Multiplayer/Components/ISimplePlayerSpawner.h>
  24. #if AZ_TRAIT_CLIENT
  25. # include <AzFramework/Input/Buses/Requests/InputSystemCursorRequestBus.h>
  26. # include <AzFramework/Input/Devices/Mouse/InputDeviceMouse.h>
  27. # include <LyShine/Bus/UiCursorBus.h>
  28. #endif
  29. namespace MultiplayerSample
  30. {
  31. void NetworkMatchComponent::Reflect(AZ::ReflectContext* context)
  32. {
  33. AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
  34. if (serializeContext)
  35. {
  36. serializeContext->Class<NetworkMatchComponent, NetworkMatchComponentBase>()
  37. ->Version(1);
  38. }
  39. NetworkMatchComponentBase::Reflect(context);
  40. if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
  41. {
  42. behaviorContext->EBus<MultiplayerSample::NetworkMatchComponentRequestBus>("Network Match Component Requests")
  43. ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
  44. ->Attribute(AZ::Script::Attributes::Module, "multiplayersample")
  45. ->Attribute(AZ::Script::Attributes::Category, "MultiplayerSample")
  46. ->Event("Is player action allowed", &MultiplayerSample::NetworkMatchComponentRequestBus::Events::IsPlayerActionAllowed)
  47. ->Event("Get roundtime remaining in seconds", &MultiplayerSample::NetworkMatchComponentRequestBus::Events::GetRoundTimeRemainingSec)
  48. ->Event("Get total roundtime in seconds", &MultiplayerSample::NetworkMatchComponentRequestBus::Events::GetTotalRoundTimeSec)
  49. ->Event("Get current round number", &MultiplayerSample::NetworkMatchComponentRequestBus::Events::GetCurrentRoundNumber)
  50. ->Event("Get total round count", &MultiplayerSample::NetworkMatchComponentRequestBus::Events::GetTotalRoundCount)
  51. ->Event("Get total player count", &MultiplayerSample::NetworkMatchComponentRequestBus::Events::GetTotalPlayerCount)
  52. ;
  53. }
  54. }
  55. void NetworkMatchComponent::OnActivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating)
  56. {
  57. #if AZ_TRAIT_CLIENT
  58. AZ::Interface<INetworkMatch>::Register(this);
  59. #endif
  60. if (IsNetEntityRoleAuthority() || IsNetEntityRoleServer())
  61. {
  62. PlayerIdentityNotificationBus::Handler::BusConnect();
  63. }
  64. NetworkMatchComponentRequestBus::Handler::BusConnect();
  65. }
  66. void NetworkMatchComponent::OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating)
  67. {
  68. NetworkMatchComponentRequestBus::Handler::BusDisconnect();
  69. PlayerIdentityNotificationBus::Handler::BusDisconnect();
  70. #if AZ_TRAIT_CLIENT
  71. AZ::Interface<INetworkMatch>::Unregister(this);
  72. #endif
  73. }
  74. bool NetworkMatchComponent::IsPlayerActionAllowed() const
  75. {
  76. // Disable player actions between rounds (rest period)
  77. if (GetRoundTime() <= 0 && GetRoundRestTimeRemaining() > 0)
  78. {
  79. return false;
  80. }
  81. #if AZ_TRAIT_CLIENT
  82. // Don't allow player movement if the system cursor is visible
  83. AzFramework::SystemCursorState systemCursorState{ AzFramework::SystemCursorState::Unknown };
  84. AzFramework::InputSystemCursorRequestBus::EventResult(systemCursorState, AzFramework::InputDeviceMouse::Id,
  85. &AzFramework::InputSystemCursorRequests::GetSystemCursorState);
  86. if ((systemCursorState == AzFramework::SystemCursorState::UnconstrainedAndVisible) ||
  87. (systemCursorState == AzFramework::SystemCursorState::ConstrainedAndVisible))
  88. {
  89. return false;
  90. }
  91. // Don't allow player movement if the UI cursor is visible
  92. bool isCursorVisible = false;
  93. UiCursorBus::BroadcastResult(isCursorVisible, &UiCursorInterface::IsUiCursorVisible);
  94. if (isCursorVisible)
  95. {
  96. return false;
  97. }
  98. #endif
  99. return true;
  100. }
  101. float NetworkMatchComponent::GetRoundTimeRemainingSec() const
  102. {
  103. return aznumeric_cast<float>(GetRoundTime());
  104. }
  105. float NetworkMatchComponent::GetTotalRoundTimeSec() const
  106. {
  107. return GetRoundDuration();
  108. }
  109. int32_t NetworkMatchComponent::GetCurrentRoundNumber() const
  110. {
  111. return aznumeric_cast<int32_t>(GetRoundNumber());
  112. }
  113. int32_t NetworkMatchComponent::GetTotalRoundCount() const
  114. {
  115. return aznumeric_cast<int32_t>(GetTotalRounds());
  116. }
  117. int32_t NetworkMatchComponent::GetTotalPlayerCount() const
  118. {
  119. return aznumeric_cast<int32_t>(GetPlayerCount());
  120. }
  121. void NetworkMatchComponent::AddRoundNumberEventHandler(AZ::Event<uint16_t>::Handler& handler)
  122. {
  123. RoundNumberAddEvent(handler);
  124. }
  125. void NetworkMatchComponent::AddRoundRestTimeRemainingEventHandler(AZ::Event<RoundTimeSec>::Handler& handler)
  126. {
  127. RoundRestTimeRemainingAddEvent(handler);
  128. }
  129. #if AZ_TRAIT_SERVER
  130. void NetworkMatchComponent::OnPlayerActivated(Multiplayer::NetEntityId playerEntity)
  131. {
  132. RPC_PlayerActivated(playerEntity);
  133. }
  134. void NetworkMatchComponent::OnPlayerDeactivated(Multiplayer::NetEntityId playerEntity)
  135. {
  136. RPC_PlayerDeactivated(playerEntity);
  137. }
  138. #endif
  139. #if AZ_TRAIT_CLIENT
  140. void NetworkMatchComponent::HandleRPC_EndMatch(
  141. [[maybe_unused]] AzNetworking::IConnection* invokingConnection, [[maybe_unused]] const MatchResultsSummary& results)
  142. {
  143. if (IsNetEntityRoleClient())
  144. {
  145. UiGameOverBus::Broadcast(&UiGameOverBus::Events::SetGameOverScreenEnabled, true);
  146. UiGameOverBus::Broadcast(&UiGameOverBus::Events::DisplayResults, results);
  147. const char* playerIdentityName = nullptr;
  148. PlayerIdentityRequestBus::BroadcastResult(playerIdentityName, &PlayerIdentityRequestBus::Events::GetPlayerIdentityName);
  149. if (playerIdentityName)
  150. {
  151. if (results.m_winningPlayerName == playerIdentityName)
  152. {
  153. // Local player is the winner
  154. LocalOnlyGameplayEffectsNotificationBus::Broadcast(
  155. &LocalOnlyGameplayEffectsNotificationBus::Events::OnEffect, SoundEffect::VictoryFanfare);
  156. }
  157. else
  158. {
  159. LocalOnlyGameplayEffectsNotificationBus::Broadcast(
  160. &LocalOnlyGameplayEffectsNotificationBus::Events::OnEffect, SoundEffect::LosingFanfare);
  161. }
  162. }
  163. }
  164. }
  165. #endif // AZ_TRAIT_CLIENT
  166. // Controller methods
  167. NetworkMatchComponentController::NetworkMatchComponentController(NetworkMatchComponent& parent)
  168. : NetworkMatchComponentControllerBase(parent)
  169. {
  170. }
  171. void NetworkMatchComponentController::OnActivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating)
  172. {
  173. #if AZ_TRAIT_SERVER
  174. AZ::SimpleLcgRandom randomNumberGenerator(aznumeric_cast<int64_t>(AZ::GetElapsedTimeMs()));
  175. m_playerNameRandomStartingIndexPrefix = randomNumberGenerator.GetRandom() % AutoAssignedPlayerNamePrefix.size();
  176. m_playerNameRandomStartingIndexPostfix = randomNumberGenerator.GetRandom() % AutoAssignedPlayerNamePostfix.size();
  177. #endif
  178. GameState::GameStateRequests::AddGameStateFactoryOverrideForType<GameStateWaitingForPlayers>([this]()
  179. {
  180. return AZStd::make_shared<GameStateWaitingForPlayers>(this);
  181. });
  182. GameState::GameStateRequests::AddGameStateFactoryOverrideForType<GameStatePreparingMatch>([this]()
  183. {
  184. return AZStd::make_shared<GameStatePreparingMatch>(this);
  185. });
  186. GameState::GameStateRequests::AddGameStateFactoryOverrideForType<GameStateMatchInProgress>([this]()
  187. {
  188. return AZStd::make_shared<GameStateMatchInProgress>(this);
  189. });
  190. GameState::GameStateRequests::AddGameStateFactoryOverrideForType<GameStateMatchEnded>([this]()
  191. {
  192. return AZStd::make_shared<GameStateMatchEnded>(this);
  193. });
  194. GameState::GameStateRequests::CreateAndPushNewOverridableGameStateOfType<GameStateWaitingForPlayers>();
  195. PlayerMatchLifecycleBus::Handler::BusConnect();
  196. }
  197. void NetworkMatchComponentController::OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating)
  198. {
  199. PlayerMatchLifecycleBus::Handler::BusDisconnect();
  200. GameState::GameStateRequestBus::Broadcast(&GameState::GameStateRequestBus::Events::PopAllGameStates);
  201. GameState::GameStateRequests::RemoveGameStateFactoryOverrideForType<GameStateWaitingForPlayers>();
  202. GameState::GameStateRequests::RemoveGameStateFactoryOverrideForType<GameStatePreparingMatch>();
  203. GameState::GameStateRequests::RemoveGameStateFactoryOverrideForType<GameStateMatchInProgress>();
  204. GameState::GameStateRequests::RemoveGameStateFactoryOverrideForType<GameStateMatchEnded>();
  205. #if AZ_TRAIT_SERVER
  206. m_roundTickEvent.RemoveFromQueue();
  207. m_restTickEvent.RemoveFromQueue();
  208. #endif
  209. }
  210. #if AZ_TRAIT_SERVER
  211. void NetworkMatchComponentController::StartMatch()
  212. {
  213. SetRoundTime(RoundTimeSec{ GetRoundDuration() });
  214. SetRoundNumber(1);
  215. GetGemSpawnerComponentController()->SpawnGems();
  216. // Tick once a second, this way we can keep the time as an 2 byte integer instead of a float.
  217. m_roundTickEvent.Enqueue(AZ::TimeMs{ 1000 }, true);
  218. }
  219. void NetworkMatchComponentController::EndMatch()
  220. {
  221. //Signal event to end the match
  222. m_roundTickEvent.RemoveFromQueue();
  223. m_restTickEvent.RemoveFromQueue();
  224. MatchResultsSummary results;
  225. const AZStd::vector<PlayerCoinState>& coinStates = GetMatchPlayerCoinsComponentController()->GetParent().
  226. GetPlayerCoinCounts();
  227. int highestCoins = -1;
  228. AZStd::vector<PlayerState> potentialWinners;
  229. for (const Multiplayer::NetEntityId playerNetEntity : m_players)
  230. {
  231. PlayerState state;
  232. const auto playerHandle = Multiplayer::GetNetworkEntityManager()->GetEntity(playerNetEntity);
  233. if (playerHandle.Exists())
  234. {
  235. if (PlayerIdentityComponent* identity = playerHandle.GetEntity()->FindComponent<PlayerIdentityComponent>())
  236. {
  237. state.m_playerName = identity->GetPlayerName();
  238. RespawnPlayer(playerNetEntity, PlayerResetOptions{ true, 100 });
  239. }
  240. if (const NetworkHealthComponent* armor = playerHandle.GetEntity()->FindComponent<NetworkHealthComponent>())
  241. {
  242. // Treating health as armor
  243. state.m_remainingArmor = aznumeric_cast<uint8_t>(armor->GetHealth());
  244. }
  245. }
  246. else
  247. {
  248. continue;
  249. }
  250. const auto coinStateIterator = AZStd::find_if(coinStates.begin(), coinStates.end(), [playerNetEntity](const PlayerCoinState& state)
  251. {
  252. return state.m_playerId == playerNetEntity;
  253. });
  254. if (coinStateIterator != coinStates.end())
  255. {
  256. state.m_score = coinStateIterator->m_coins;
  257. if (highestCoins < aznumeric_cast<int>(state.m_score))
  258. {
  259. highestCoins = aznumeric_cast<int>(state.m_score);
  260. // There is no tie so far.
  261. potentialWinners.clear();
  262. potentialWinners.push_back(state);
  263. }
  264. else if (highestCoins == aznumeric_cast<int>(state.m_score))
  265. {
  266. // A potential tie - decide based on remaining armor later.
  267. potentialWinners.push_back(state);
  268. }
  269. }
  270. results.m_playerStates.push_back(state);
  271. }
  272. FindWinner(results, potentialWinners);
  273. RPC_EndMatch(results);
  274. GetMatchPlayerCoinsComponentController()->ResetAllCoins();
  275. }
  276. #endif
  277. void NetworkMatchComponentController::FindWinner(MatchResultsSummary& results,
  278. const AZStd::vector<PlayerState>& potentialWinners)
  279. {
  280. if (potentialWinners.empty())
  281. {
  282. results.m_winningPlayerName = "No players in the match";
  283. }
  284. else if (potentialWinners.size() == 1)
  285. {
  286. results.m_winningPlayerName = potentialWinners.front().m_playerName;
  287. }
  288. else if (potentialWinners.size() > 1)
  289. {
  290. // A tie - find the player with the largest armor remaining.
  291. AZStd::vector<const PlayerState*> playersTiedByArmor;
  292. playersTiedByArmor.push_back(&potentialWinners.front());
  293. for (const PlayerState& potential : potentialWinners)
  294. {
  295. if (potential.m_remainingArmor == playersTiedByArmor.front()->m_remainingArmor)
  296. {
  297. playersTiedByArmor.push_back(&potential);
  298. }
  299. else if (potential.m_remainingArmor > playersTiedByArmor.front()->m_remainingArmor)
  300. {
  301. playersTiedByArmor.clear();
  302. playersTiedByArmor.push_back(&potential);
  303. }
  304. }
  305. if (playersTiedByArmor.size() > 1)
  306. {
  307. // If multiple players are still tied on armor, randomly choose a player
  308. const AZ::u64 randomlyChosenWinnerIndex = GetNetworkRandomComponentController()->GetRandomUint64() % playersTiedByArmor.size();
  309. results.m_winningPlayerName = playersTiedByArmor[randomlyChosenWinnerIndex]->m_playerName;
  310. }
  311. else
  312. {
  313. results.m_winningPlayerName = playersTiedByArmor.front()->m_playerName;
  314. }
  315. }
  316. }
  317. #if AZ_TRAIT_SERVER
  318. void NetworkMatchComponentController::StartRound()
  319. {
  320. uint16_t roundNumber = GetRoundNumber() + 1;
  321. // We need to do this whether or not we're going beyond the number of total rounds so that
  322. // the game state code can detect that it's time to end the game.
  323. SetRoundNumber(roundNumber);
  324. if (roundNumber <= GetTotalRounds())
  325. {
  326. // stop the rest timer
  327. m_restTickEvent.RemoveFromQueue();
  328. // start the round timer
  329. SetRoundTime(RoundTimeSec{ GetRoundDuration() });
  330. m_roundTickEvent.Enqueue(AZ::TimeMs{ 1000 }, true); // Tick once a second, this way we can keep the time as an 2 byte integer instead of a float.
  331. GetGemSpawnerComponentController()->SpawnGems();
  332. }
  333. }
  334. void NetworkMatchComponentController::EndRound()
  335. {
  336. // Check if we're in-between rounds, or if this is the end of the match...
  337. if (GetRoundNumber() < GetTotalRounds()) // In-between
  338. {
  339. // stop the round timer
  340. m_roundTickEvent.RemoveFromQueue();
  341. // start the rest timer
  342. ModifyRoundRestTimeRemaining() = RoundTimeSec{ GetRestDurationBetweenRounds() };
  343. m_restTickEvent.Enqueue(AZ::TimeMs{ 1000 }, true);
  344. // Respawn players before the new round starts
  345. for (const Multiplayer::NetEntityId playerNetEntity : m_players)
  346. {
  347. const Multiplayer::ConstNetworkEntityHandle playerHandle = Multiplayer::GetNetworkEntityManager()->GetEntity(playerNetEntity);
  348. if (!playerHandle.Exists())
  349. {
  350. continue;
  351. }
  352. constexpr bool resetShields = true;
  353. constexpr uint16_t coinPenalty = 0;
  354. RespawnPlayer(playerNetEntity, PlayerResetOptions{ resetShields, coinPenalty });
  355. }
  356. }
  357. else // Match ended
  358. {
  359. // Incrementing the round number will trigger GameStateMatchEnded
  360. ModifyRoundNumber()++;
  361. }
  362. }
  363. void NetworkMatchComponentController::HandleRPC_PlayerActivated([[maybe_unused]] AzNetworking::IConnection* invokingConnection,
  364. const Multiplayer::NetEntityId& playerEntity)
  365. {
  366. const auto playerIterator = AZStd::find(m_players.begin(), m_players.end(), playerEntity);
  367. if (playerIterator == m_players.end())
  368. {
  369. m_players.push_back(playerEntity);
  370. AssignPlayerIdentity(playerEntity);
  371. }
  372. SetPlayerCount(aznumeric_cast<int16_t>(m_players.size()));
  373. }
  374. void NetworkMatchComponentController::HandleRPC_PlayerDeactivated([[maybe_unused]] AzNetworking::IConnection* invokingConnection,
  375. const Multiplayer::NetEntityId& playerEntity)
  376. {
  377. const auto playerIterator = AZStd::find(m_players.begin(), m_players.end(), playerEntity);
  378. if (playerIterator != m_players.end())
  379. {
  380. m_players.erase(playerIterator);
  381. }
  382. else
  383. {
  384. AZ_Warning("NetworkMatchComponentController", false, "An unknown player deactivated %llu", aznumeric_cast<AZ::u64>(playerEntity));
  385. }
  386. SetPlayerCount(aznumeric_cast<int16_t>(m_players.size()));
  387. }
  388. #endif
  389. void NetworkMatchComponentController::OnPlayerArmorZero([[maybe_unused]] Multiplayer::NetEntityId playerEntity)
  390. {
  391. #if AZ_TRAIT_SERVER
  392. const auto playerIterator = AZStd::find(m_players.begin(), m_players.end(), playerEntity);
  393. if (playerIterator != m_players.end())
  394. {
  395. if (Multiplayer::ConstNetworkEntityHandle playerHandle = Multiplayer::GetNetworkEntityManager()->GetEntity(playerEntity))
  396. {
  397. RespawnPlayer(playerEntity, PlayerResetOptions{ true, GetRespawnPenaltyPercent() });
  398. }
  399. }
  400. else
  401. {
  402. AZ_Warning("NetworkMatchComponentController", false, "An unknown player reported depleted armor: %llu", aznumeric_cast<AZ::u64>(playerEntity));
  403. }
  404. #endif
  405. }
  406. #if AZ_TRAIT_SERVER
  407. void NetworkMatchComponentController::RoundTickOnceASecond()
  408. {
  409. // m_roundTickEvent is configured to tick once a second
  410. SetRoundTime(RoundTimeSec(GetRoundTime() - 1.f));
  411. if (GetRoundTime() <= RoundTimeSec(0.f))
  412. {
  413. EndRound();
  414. }
  415. }
  416. void NetworkMatchComponentController::RestTickOnceASecond()
  417. {
  418. // m_restTickEvent is configured to tick once a second
  419. SetRoundRestTimeRemaining(RoundTimeSec(GetRoundRestTimeRemaining() - 1.f));
  420. if (GetRoundRestTimeRemaining() <= RoundTimeSec(0.f))
  421. {
  422. StartRound();
  423. }
  424. }
  425. PlayerNameString NetworkMatchComponentController::GeneratePlayerName()
  426. {
  427. // The first-name will be offset depending on how many times all prefix names have been used.
  428. // This has the affect of exhausting all the possible name combinations before hitting a name collision.
  429. const int prefixOffset = aznumeric_cast<int>(AZStd::floorf( static_cast<float>(m_nextPlayerId) / AutoAssignedPlayerNamePrefix.size()));
  430. const PlayerNameString prefixName = AutoAssignedPlayerNamePrefix[(++m_playerNameRandomStartingIndexPrefix + prefixOffset) % AutoAssignedPlayerNamePrefix.size()];
  431. const PlayerNameString postfixName = AutoAssignedPlayerNamePostfix[++m_playerNameRandomStartingIndexPostfix % AutoAssignedPlayerNamePostfix.size()];
  432. const PlayerNameString playerName = prefixName + postfixName;
  433. return playerName;
  434. }
  435. void NetworkMatchComponentController::AssignPlayerIdentity(Multiplayer::NetEntityId playerEntity)
  436. {
  437. const Multiplayer::ConstNetworkEntityHandle entityHandle = Multiplayer::GetNetworkEntityManager()->GetEntity(playerEntity);
  438. if (entityHandle.Exists())
  439. {
  440. if (PlayerIdentityComponent* identity = entityHandle.GetEntity()->FindComponent<PlayerIdentityComponent>())
  441. {
  442. identity->RPC_AssignPlayerName(GeneratePlayerName());
  443. }
  444. else
  445. {
  446. AZ_Warning("NetworkMatchComponentController", false, "Player entity did not have PlayerIdentityComponent");
  447. }
  448. }
  449. m_nextPlayerId++;
  450. }
  451. void NetworkMatchComponentController::RespawnPlayer(Multiplayer::NetEntityId playerEntity, PlayerResetOptions resets)
  452. {
  453. const auto playerHandle = Multiplayer::GetNetworkEntityManager()->GetEntity(playerEntity);
  454. if (playerHandle.Exists())
  455. {
  456. // reset state
  457. if (PlayerIdentityComponent* identity = playerHandle.GetEntity()->FindComponent<PlayerIdentityComponent>())
  458. {
  459. identity->RPC_ResetPlayerState(resets);
  460. }
  461. // move to valid respawn point
  462. if (NetworkTeleportCompatibleComponent* teleport = playerHandle.GetEntity()->FindComponent<NetworkTeleportCompatibleComponent>())
  463. {
  464. AZ::Transform respawnPoint = AZ::Transform::CreateIdentity();
  465. if (auto simplePlayerSpawner = AZ::Interface<Multiplayer::ISimplePlayerSpawner>::Get())
  466. {
  467. respawnPoint = simplePlayerSpawner->GetNextSpawnPoint();
  468. // Increment the next spawn point so any new players or respawned players don't spawn in on top of us at this location.
  469. const uint32_t spawnPointCount = simplePlayerSpawner->GetSpawnPointCount();
  470. simplePlayerSpawner->SetNextSpawnPointIndex((simplePlayerSpawner->GetNextSpawnPointIndex()+1) % spawnPointCount);
  471. }
  472. else
  473. {
  474. AZ_Warning("NetworkMatchComponentController", false, "Failed to find a valid respawn point; moving to the world origin.");
  475. }
  476. teleport->Teleport(respawnPoint.GetTranslation());
  477. }
  478. }
  479. else
  480. {
  481. AZ_Warning("NetworkMatchComponentController", false, "Attempted respawn of an unknown player: %llu", aznumeric_cast<AZ::u64>(playerEntity));
  482. }
  483. }
  484. #endif
  485. }