NetworkMatchComponent.cpp 27 KB

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