GemSpawnerComponent.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  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 <MultiplayerSampleTypes.h>
  8. #include <AzCore/Component/TransformBus.h>
  9. #include <AzCore/Serialization/EditContext.h>
  10. #include <Components/NetworkMatchComponent.h>
  11. #include <LmbrCentral/Scripting/TagComponentBus.h>
  12. #include <LmbrCentral/Shape/ShapeComponentBus.h>
  13. #include <Source/Components/NetworkRandomComponent.h>
  14. #include <Source/Components/Multiplayer/GemComponent.h>
  15. #include <Source/Components/Multiplayer/GemSpawnerComponent.h>
  16. #include <Source/Components/PerfTest/NetworkPrefabSpawnerComponent.h>
  17. namespace MultiplayerSample
  18. {
  19. void GemSpawnable::Reflect(AZ::ReflectContext* context)
  20. {
  21. AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
  22. if (serializeContext)
  23. {
  24. serializeContext->Class<GemSpawnable>()
  25. ->Version(2)
  26. ->Field("Tag", &GemSpawnable::m_tag)
  27. ->Field("Asset", &GemSpawnable::m_gemAsset)
  28. ->Field("Score", &GemSpawnable::m_scoreValue)
  29. ;
  30. if (AZ::EditContext* editContext = serializeContext->GetEditContext())
  31. {
  32. editContext->Class<GemSpawnable>("GemSpawnable", "Defines a gem type with an asset and a tag name.")
  33. ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
  34. ->DataElement(AZ::Edit::UIHandlers::Default, &GemSpawnable::m_tag, "Tag", "Assigned tag for this gem type")
  35. ->DataElement(AZ::Edit::UIHandlers::Default, &GemSpawnable::m_gemAsset, "Asset", "Spawnable for the gem")
  36. ->DataElement(AZ::Edit::UIHandlers::Default, &GemSpawnable::m_scoreValue, "Score", "Gem's value")
  37. ;
  38. }
  39. }
  40. }
  41. void GemWeightChance::Reflect(AZ::ReflectContext* context)
  42. {
  43. AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
  44. if (serializeContext)
  45. {
  46. serializeContext->Class<GemWeightChance>()
  47. ->Version(1)
  48. ->Field("Gem Tag Type", &GemWeightChance::m_tag)
  49. ->Field("Gem Weight", &GemWeightChance::m_weight)
  50. ;
  51. if (AZ::EditContext* editContext = serializeContext->GetEditContext())
  52. {
  53. editContext->Class<GemWeightChance>("GemWeightChance", "Defines a weighted chance for a gem type to spawn in a given round.")
  54. ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
  55. ->DataElement(AZ::Edit::UIHandlers::Default, &GemWeightChance::m_tag, "Gem Tag Type", "Assigned tag for this gem type")
  56. ->DataElement(AZ::Edit::UIHandlers::Default, &GemWeightChance::m_weight, "Gem Weight", "Weight value in randomly choosing between the gems")
  57. ;
  58. }
  59. }
  60. }
  61. void RoundSpawnTable::Reflect(AZ::ReflectContext* context)
  62. {
  63. AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
  64. if (serializeContext)
  65. {
  66. serializeContext->Class<RoundSpawnTable>()
  67. ->Version(1)
  68. ->Field("Gem Weights", &RoundSpawnTable::m_gemWeights)
  69. ;
  70. if (AZ::EditContext* editContext = serializeContext->GetEditContext())
  71. {
  72. editContext->Class<RoundSpawnTable>("RoundSpawnTable", "Defines chances for gem types to spawn in a given round.")
  73. ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
  74. ->DataElement(AZ::Edit::UIHandlers::Default, &RoundSpawnTable::m_gemWeights, "Gem Weights", "Gem weights for a given round")
  75. ;
  76. }
  77. }
  78. }
  79. void GemSpawnerComponent::Reflect(AZ::ReflectContext* context)
  80. {
  81. AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
  82. if (serializeContext)
  83. {
  84. serializeContext->Class<GemSpawnerComponent, GemSpawnerComponentBase>()
  85. ->Version(1);
  86. }
  87. GemSpawnerComponentBase::Reflect(context);
  88. }
  89. GemSpawnerComponentController::GemSpawnerComponentController(GemSpawnerComponent& parent)
  90. : GemSpawnerComponentControllerBase(parent)
  91. {
  92. }
  93. void GemSpawnerComponentController::OnActivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating)
  94. {
  95. }
  96. void GemSpawnerComponentController::OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating)
  97. {
  98. #if AZ_TRAIT_SERVER
  99. RemoveGems();
  100. #endif
  101. }
  102. #if AZ_TRAIT_SERVER
  103. void GemSpawnerComponentController::SpawnGems()
  104. {
  105. RemoveGems();
  106. // Collect all entities marked with a tag to spawn a gem.
  107. AZ::EBusAggregateResults<AZ::EntityId> aggregator;
  108. LmbrCentral::TagGlobalRequestBus::EventResult(aggregator, AZ::Crc32(GetGemSpawnTag()),
  109. &LmbrCentral::TagGlobalRequests::RequestTaggedEntities);
  110. // If there aren't any spawn tables, don't spawn anything.
  111. if (GetSpawnTablesPerRound().empty())
  112. {
  113. return;
  114. }
  115. // Get the current round's spawn table, or the last defined round as a fallback.
  116. const uint16_t round = GetNetworkMatchComponentController()->GetRoundNumber();
  117. const RoundSpawnTable& table =
  118. GetSpawnTablesPerRound()[AZStd::min(round, aznumeric_cast<uint16_t>(GetSpawnTablesPerRound().size() - 1))];
  119. // Move the table data into a working list where we've done a one-time conversion of tags into CRCs and we can temporarily
  120. // store which tags exist on the entity.
  121. AZStd::vector<GemSpawnEntry> gemSpawnList;
  122. gemSpawnList.reserve(table.m_gemWeights.size());
  123. for (const GemWeightChance& gemWeight : table.m_gemWeights)
  124. {
  125. gemSpawnList.emplace_back(AZ::Crc32(gemWeight.m_tag), gemWeight.m_weight, false);
  126. }
  127. for (const AZ::EntityId gemSpawnEntity : aggregator.values)
  128. {
  129. // Collect the gem tags for this specific entity.
  130. LmbrCentral::Tags tags;
  131. LmbrCentral::TagComponentRequestBus::EventResult(tags, gemSpawnEntity,
  132. &LmbrCentral::TagComponentRequestBus::Events::GetTags);
  133. // Randomly select a gem type for this entity.
  134. const AZ::Crc32 type = ChooseGemType(gemSpawnList, tags);
  135. // If this entity has a valid gem type, spawn it.
  136. if (type != AZ::Crc32(0))
  137. {
  138. AZ::Vector3 position = AZ::Vector3::CreateZero();
  139. AZ::TransformBus::EventResult(position, gemSpawnEntity, &AZ::TransformBus::Events::GetWorldTranslation);
  140. SpawnGem(position, type);
  141. }
  142. }
  143. }
  144. void GemSpawnerComponentController::HandleRPC_SpawnGem(
  145. [[maybe_unused]] AzNetworking::IConnection* invokingConnection,
  146. [[maybe_unused]] const Multiplayer::NetEntityId& playerEntity, const AZ::Vector3& spawnLocation, const AZStd::string& gemTag)
  147. {
  148. SpawnGem(spawnLocation, AZ::Crc32(gemTag));
  149. }
  150. void GemSpawnerComponentController::HandleRPC_SpawnGemWithValue(
  151. [[maybe_unused]] AzNetworking::IConnection* invokingConnection,
  152. [[maybe_unused]] const Multiplayer::NetEntityId& playerEntity,
  153. const AZ::Vector3& spawnLocation, const AZStd::string& gemTag, const uint16_t& gemValue)
  154. {
  155. if (auto gemEntry = GetGemSpawnable(AZ::Crc32(gemTag)); gemEntry)
  156. {
  157. // Spawn the gem with the max value between what's requested and what's in the gem table.
  158. uint16_t value = AZStd::max(gemEntry->m_scoreValue, gemValue);
  159. SpawnGem(spawnLocation, gemEntry->m_gemAsset, value);
  160. }
  161. }
  162. AZStd::optional<const GemSpawnable> GemSpawnerComponentController::GetGemSpawnable(AZ::Crc32 gemTag) const
  163. {
  164. for (const GemSpawnable gemType : GetParent().GetGemSpawnables())
  165. {
  166. if (gemTag == AZ::Crc32(gemType.m_tag.c_str()))
  167. {
  168. return gemType;
  169. }
  170. }
  171. return {};
  172. }
  173. void GemSpawnerComponentController::SpawnGem(const AZ::Vector3& location, const AZ::Crc32& type)
  174. {
  175. if (auto gemEntry = GetGemSpawnable(type); gemEntry)
  176. {
  177. SpawnGem(location, gemEntry->m_gemAsset, gemEntry->m_scoreValue);
  178. }
  179. }
  180. void GemSpawnerComponentController::SpawnGem(const AZ::Vector3& location, const AzFramework::SpawnableAsset& gemAsset, uint16_t gemValue)
  181. {
  182. // Don't spawn gems with 0 value.
  183. if (gemValue == 0)
  184. {
  185. return;
  186. }
  187. PrefabCallbacks callbacks;
  188. callbacks.m_onActivateCallback = [this, gemValue](AZStd::shared_ptr<AzFramework::EntitySpawnTicket> ticket,
  189. AzFramework::SpawnableConstEntityContainerView view)
  190. {
  191. if (view.empty())
  192. {
  193. return;
  194. }
  195. const auto ticketId = ticket->GetId();
  196. for (const AZ::Entity* entity : view)
  197. {
  198. if (GemComponent* gem = entity->FindComponent<GemComponent>())
  199. {
  200. if (GemComponentController* gemController = static_cast<GemComponentController*>(gem->GetController()))
  201. {
  202. gemController->SetRandomPeriodOffset(GetNetworkRandomComponentController()->GetRandomInt() % 1000);
  203. gemController->SetGemScoreValue(gemValue);
  204. gemController->SetGemSpawnerController(this);
  205. }
  206. }
  207. }
  208. // Save the gem spawn ticket, otherwise the gem will immediately despawn due to the ticket's destruction.
  209. // Also track the root entity id so that we can move the gem out of sight while waiting for it to despawn when removing gems.
  210. m_spawnedGems.insert(AZStd::make_pair(ticketId, AZStd::move(ticket)));
  211. };
  212. GetParent().GetNetworkPrefabSpawnerComponent()->SpawnPrefabAsset(
  213. AZ::Transform::CreateFromQuaternionAndTranslation(AZ::Quaternion::CreateIdentity(), location),
  214. gemAsset, AZStd::move(callbacks));
  215. }
  216. void GemSpawnerComponentController::RemoveGems()
  217. {
  218. for (const auto& pair : m_spawnedGems)
  219. {
  220. // Destroy all the entities for each gem.
  221. AzFramework::SpawnableEntitiesInterface::Get()->DespawnAllEntities(*pair.second);
  222. }
  223. m_spawnedGems.clear();
  224. }
  225. void GemSpawnerComponentController::RemoveGem(AzFramework::EntitySpawnTicket::Id gemTicketId)
  226. {
  227. const auto gemIterator = m_spawnedGems.find(gemTicketId);
  228. if (gemIterator != m_spawnedGems.end())
  229. {
  230. AzFramework::SpawnableEntitiesInterface::Get()->DespawnAllEntities(*gemIterator->second);
  231. m_spawnedGems.erase(gemIterator);
  232. }
  233. }
  234. #endif
  235. AZ::Crc32 GemSpawnerComponentController::ChooseGemType(AZStd::vector<GemSpawnEntry>& gemSpawnList, const LmbrCentral::Tags& tags)
  236. {
  237. // Calculate the total weight of all applicable gem tags.
  238. float totalWeight = 0.f;
  239. for (GemSpawnEntry& entry : gemSpawnList)
  240. {
  241. const auto tagIterator = tags.find(entry.m_tag);
  242. entry.m_entityHasTag = tagIterator != tags.end();
  243. if (tagIterator != tags.end())
  244. {
  245. totalWeight += entry.m_weight;
  246. }
  247. }
  248. // Create a random float in the range of [0, totalWeight)
  249. float randomWeight = GetNetworkRandomComponentController()->GetRandomFloat() * totalWeight;
  250. AZ::Crc32 chosenType;
  251. for (GemSpawnEntry& entry : gemSpawnList)
  252. {
  253. if (entry.m_entityHasTag)
  254. {
  255. // For every acceptable tag, reduce the the weight value until the right gem type is found for the random value.
  256. // >----------------------\
  257. // |
  258. // gem1--------gem2-------*--gem3------
  259. if (randomWeight > entry.m_weight)
  260. {
  261. randomWeight -= entry.m_weight;
  262. }
  263. else
  264. {
  265. chosenType = entry.m_tag;
  266. break;
  267. }
  268. }
  269. }
  270. return chosenType;
  271. }
  272. }