EnergyBallComponent.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  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 <Source/Components/Multiplayer/EnergyBallComponent.h>
  8. #include <Source/AutoGen/NetworkHealthComponent.AutoComponent.h>
  9. #include <Multiplayer/Components/NetworkTransformComponent.h>
  10. #include <Multiplayer/Components/NetworkRigidBodyComponent.h>
  11. #include <MultiplayerSampleTypes.h>
  12. #include <AzCore/Component/TransformBus.h>
  13. #include <AzCore/EBus/IEventScheduler.h>
  14. #include <AzFramework/Physics/Components/SimulatedBodyComponentBus.h>
  15. #include <AzFramework/Physics/RigidBodyBus.h>
  16. #include <WeaponNotificationBus.h>
  17. #if AZ_TRAIT_CLIENT
  18. # include <PopcornFX/PopcornFXBus.h>
  19. # include <DebugDraw/DebugDrawBus.h>
  20. #endif
  21. namespace MultiplayerSample
  22. {
  23. AZ_CVAR(float, sv_EnergyBallImpulseScalar, 500.0f, nullptr, AZ::ConsoleFunctorFlags::Null, "A fudge factor for imparting impulses on rigid bodies due to weapon hits");
  24. AZ_CVAR(bool, cl_EnergyBallDebugDraw, false, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "When turned on this will draw the current energy ball location");
  25. AZ_CVAR(float, cl_EnergyBallDebugDrawSeconds, 0.0f, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The number of seconds of draw history to preserve for the energy ball");
  26. void EnergyBallComponent::Reflect(AZ::ReflectContext* context)
  27. {
  28. AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
  29. if (serializeContext)
  30. {
  31. serializeContext->Class<EnergyBallComponent, EnergyBallComponentBase>()
  32. ->Version(1);
  33. }
  34. EnergyBallComponentBase::Reflect(context);
  35. }
  36. void EnergyBallComponent::OnActivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating)
  37. {
  38. #if AZ_TRAIT_CLIENT
  39. m_effect = GetExplosionEffect();
  40. m_effect.Initialize(GameEffect::EmitterType::FireAndForget);
  41. AZ::EntityBus::Handler::BusConnect(GetEntityId());
  42. if (cl_EnergyBallDebugDraw)
  43. {
  44. m_debugDrawEvent.Enqueue(AZ::TimeMs{ 0 }, true);
  45. }
  46. #endif
  47. }
  48. void EnergyBallComponent::OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating)
  49. {
  50. #if AZ_TRAIT_CLIENT
  51. m_effect = {};
  52. AZ::EntityBus::Handler::BusDisconnect();
  53. m_debugDrawEvent.RemoveFromQueue();
  54. #endif
  55. }
  56. #if AZ_TRAIT_CLIENT
  57. void EnergyBallComponent::OnEntityDeactivated([[maybe_unused]] const AZ::EntityId& entityId)
  58. {
  59. // Perform hit / explosion logic when this entity deactivates, but *before* the deactivation sequence is
  60. // actually running. This allows us to call the WeaponsNotificationBus to notify other components (like Script Canvas)
  61. // on this entity to perform hit logic. If we waited to run this until OnDeactivate, the other components would no
  62. // longer be active and wouldn't have a chance to process the logic.
  63. // Create an explosion effect wherever the ball was last at before deactivating.
  64. m_effect.TriggerEffect(GetEntity()->GetTransform()->GetWorldTM());
  65. auto hitEvent = GetHitEvent();
  66. // Notify this entity about the weapon impact for every entity that was hit, this allows for blast decals.
  67. for (const HitEntity& hitEntity : hitEvent.m_hitEntities)
  68. {
  69. const AZ::Transform hitTransform = AZ::Transform::CreateLookAt(hitEntity.m_hitPosition, hitEntity.m_hitPosition + hitEntity.m_hitNormal, AZ::Transform::Axis::ZPositive);
  70. const Multiplayer::ConstNetworkEntityHandle handle = Multiplayer::GetNetworkEntityManager()->GetEntity(hitEntity.m_hitNetEntityId);
  71. const AZ::EntityId hitEntityId = handle.Exists() ? handle.GetEntity()->GetId() : AZ::EntityId();
  72. WeaponNotificationBus::Broadcast(&WeaponNotificationBus::Events::OnWeaponImpact, GetEntity()->GetId(), hitTransform, hitEntityId);
  73. }
  74. }
  75. void EnergyBallComponent::DebugDraw()
  76. {
  77. if (cl_EnergyBallDebugDraw)
  78. {
  79. auto* shapeConfig = GetGatherParams().GetCurrentShapeConfiguration();
  80. if (shapeConfig->GetShapeType() == Physics::ShapeType::Sphere)
  81. {
  82. const Physics::SphereShapeConfiguration* sphere = static_cast<const Physics::SphereShapeConfiguration*>(shapeConfig);
  83. float debugRadius = sphere->m_radius;
  84. DebugDraw::DebugDrawRequestBus::Broadcast(
  85. &DebugDraw::DebugDrawRequestBus::Events::DrawSphereAtLocation,
  86. GetEntity()->GetTransform()->GetWorldTM().GetTranslation(),
  87. debugRadius,
  88. AZ::Colors::Green,
  89. cl_EnergyBallDebugDrawSeconds
  90. );
  91. }
  92. else if (shapeConfig->GetShapeType() == Physics::ShapeType::Box)
  93. {
  94. const Physics::BoxShapeConfiguration* box = static_cast<const Physics::BoxShapeConfiguration*>(shapeConfig);
  95. AZ::Obb obb = AZ::Obb::CreateFromPositionRotationAndHalfLengths(
  96. GetEntity()->GetTransform()->GetWorldTM().GetTranslation(),
  97. GetEntity()->GetTransform()->GetWorldTM().GetRotation(),
  98. box->m_dimensions / 2.0f
  99. );
  100. DebugDraw::DebugDrawRequestBus::Broadcast(
  101. &DebugDraw::DebugDrawRequestBus::Events::DrawObb,
  102. obb,
  103. AZ::Colors::Green,
  104. cl_EnergyBallDebugDrawSeconds
  105. );
  106. }
  107. else if (shapeConfig->GetShapeType() == Physics::ShapeType::Capsule)
  108. {
  109. AZ_Error("EnergyBall", false, "Capsule shape type not currently supported with energy ball debug visualization.");
  110. }
  111. }
  112. }
  113. #endif
  114. EnergyBallComponentController::EnergyBallComponentController(EnergyBallComponent& parent)
  115. : EnergyBallComponentControllerBase(parent)
  116. {
  117. }
  118. void EnergyBallComponentController::OnActivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating)
  119. {
  120. }
  121. void EnergyBallComponentController::OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating)
  122. {
  123. #if AZ_TRAIT_SERVER
  124. m_collisionCheckEvent.RemoveFromQueue();
  125. m_killEvent.RemoveFromQueue();
  126. #endif
  127. }
  128. #if AZ_TRAIT_SERVER
  129. void EnergyBallComponentController::HandleRPC_LaunchBall(AzNetworking::IConnection* invokingConnection, const AZ::Vector3& startingPosition, const AZ::Vector3& direction, const Multiplayer::NetEntityId& owningNetEntityId)
  130. {
  131. AZ_Assert(!m_killEvent.IsScheduled(), "Launching the same ball more than once isn't supported.");
  132. m_collisionCheckEvent.Enqueue(AZ::TimeMs{ 10 }, true);
  133. SetVelocity(direction * GetGatherParams().m_travelSpeed);
  134. m_shooterNetEntityId = owningNetEntityId;
  135. m_filteredNetEntityIds.clear();
  136. m_filteredNetEntityIds.insert(owningNetEntityId);
  137. m_filteredNetEntityIds.insert(GetNetEntityId());
  138. m_direction = direction;
  139. // Move the entity to the start position
  140. GetNetworkTransformComponentController()->HandleMultiplayerTeleport(invokingConnection, startingPosition);
  141. // We want to sweep our transform during intersect tests to avoid the ball tunneling through targets
  142. m_lastSweepTransform = GetEntity()->GetTransform()->GetWorldTM();
  143. // Enqueue our kill event
  144. m_killEvent.Enqueue(GetLifetimeMs(), false);
  145. }
  146. void EnergyBallComponentController::CheckForCollisions()
  147. {
  148. const AZ::Vector3& position = GetEntity()->GetTransform()->GetWorldTM().GetTranslation();
  149. const HitEffect& effect = GetHitEffect();
  150. // Sweep from our last checked transform to our current position to avoid tunneling
  151. const ActivateEvent activateEvent{ m_lastSweepTransform, position, m_shooterNetEntityId, GetNetEntityId() };
  152. IntersectResults results;
  153. GatherEntities(GetGatherParams(), activateEvent, m_filteredNetEntityIds, results);
  154. if (!results.empty())
  155. {
  156. for (const IntersectResult& result : results)
  157. {
  158. const HitEntity hitEntity{ result.m_position, result.m_normal, result.m_netEntityId };
  159. ModifyHitEvent().m_hitEntities.emplace_back(hitEntity);
  160. const Multiplayer::ConstNetworkEntityHandle handle = Multiplayer::GetNetworkEntityManager()->GetEntity(result.m_netEntityId);
  161. if (handle.Exists())
  162. {
  163. // Presently set to 1 until we capture falloff range
  164. float hitDistance = 1.f;
  165. float maxDistance = 1.f;
  166. float damage = effect.m_hitMagnitude * powf((effect.m_hitFalloff * (1.0f - hitDistance / maxDistance)), effect.m_hitExponent);
  167. // Look for physics rigid body component and make impact updates
  168. if (Multiplayer::NetworkRigidBodyComponent* rigidBodyComponent = handle.GetEntity()->FindComponent<Multiplayer::NetworkRigidBodyComponent>())
  169. {
  170. const AZ::Vector3 explosionCentre = position;
  171. const AZ::Vector3 hitObject = handle.GetEntity()->GetTransform()->GetWorldTM().GetTranslation();
  172. const AZ::Vector3 impulse = (hitObject - position).GetNormalized() * damage * sv_EnergyBallImpulseScalar;
  173. rigidBodyComponent->SendApplyImpulse(impulse, position);
  174. }
  175. // Look for health component and directly update health based on hit parameters
  176. if (NetworkHealthComponent* healthComponent = handle.GetEntity()->FindComponent<NetworkHealthComponent>())
  177. {
  178. healthComponent->SendHealthDelta(damage * -1.0f);
  179. }
  180. }
  181. }
  182. KillEnergyBall();
  183. }
  184. // Update our last sweep transform for the next time we check collision
  185. m_lastSweepTransform = GetEntity()->GetTransform()->GetWorldTM();
  186. }
  187. void EnergyBallComponentController::KillEnergyBall()
  188. {
  189. m_collisionCheckEvent.RemoveFromQueue();
  190. m_killEvent.RemoveFromQueue();
  191. SetVelocity(AZ::Vector3::CreateZero());
  192. auto& hitEvent = ModifyHitEvent();
  193. hitEvent.m_target = GetEntity()->GetTransform()->GetWorldTM().GetTranslation();
  194. hitEvent.m_shooterNetEntityId = m_shooterNetEntityId;
  195. hitEvent.m_projectileNetEntityId = GetNetEntityId();
  196. // Immediately remove the entity.
  197. const Multiplayer::NetEntityId netEntityId = GetNetEntityId();
  198. const Multiplayer::ConstNetworkEntityHandle entityHandle = Multiplayer::GetNetworkEntityManager()->GetEntity(netEntityId);
  199. Multiplayer::GetNetworkEntityManager()->MarkForRemoval(entityHandle);
  200. }
  201. #endif
  202. }