|
@@ -25,6 +25,7 @@ namespace MultiplayerSample
|
|
|
{
|
|
|
AZ_CVAR(float, sv_EnergyBallImpulseScalar, 500.0f, nullptr, AZ::ConsoleFunctorFlags::Null, "A fudge factor for imparting impulses on rigid bodies due to weapon hits");
|
|
|
AZ_CVAR(bool, cl_EnergyBallDebugDraw, false, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "When turned on this will draw the current energy ball location");
|
|
|
+ AZ_CVAR(float, cl_EnergyBallDebugDrawSeconds, 0.0f, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The number of seconds of draw history to preserve for the energy ball");
|
|
|
|
|
|
void EnergyBallComponent::Reflect(AZ::ReflectContext* context)
|
|
|
{
|
|
@@ -39,66 +40,41 @@ namespace MultiplayerSample
|
|
|
|
|
|
void EnergyBallComponent::OnActivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating)
|
|
|
{
|
|
|
+#if AZ_TRAIT_CLIENT
|
|
|
m_effect = GetExplosionEffect();
|
|
|
- m_effect.Initialize();
|
|
|
+ m_effect.Initialize(GameEffect::EmitterType::FireAndForget);
|
|
|
|
|
|
-#if AZ_TRAIT_CLIENT
|
|
|
- BallActiveAddEvent(m_ballActiveHandler);
|
|
|
+ AZ::EntityBus::Handler::BusConnect(GetEntityId());
|
|
|
+ if (cl_EnergyBallDebugDraw)
|
|
|
+ {
|
|
|
+ m_debugDrawEvent.Enqueue(AZ::TimeMs{ 0 }, true);
|
|
|
+ }
|
|
|
#endif
|
|
|
}
|
|
|
|
|
|
void EnergyBallComponent::OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating)
|
|
|
{
|
|
|
#if AZ_TRAIT_CLIENT
|
|
|
- m_ballActiveHandler.Disconnect();
|
|
|
+ m_effect = {};
|
|
|
+ AZ::EntityBus::Handler::BusDisconnect();
|
|
|
+ m_debugDrawEvent.RemoveFromQueue();
|
|
|
#endif
|
|
|
}
|
|
|
|
|
|
#if AZ_TRAIT_CLIENT
|
|
|
- void EnergyBallComponent::OnBallActiveChanged(bool active)
|
|
|
+ void EnergyBallComponent::OnEntityDeactivated([[maybe_unused]] const AZ::EntityId& entityId)
|
|
|
{
|
|
|
- if (active)
|
|
|
- {
|
|
|
- bool startSuccess = false;
|
|
|
-
|
|
|
- // Set to true to call "Kill" which is deferred, or false to call "Terminate" which is immediate.
|
|
|
- constexpr bool KillOnRestart = true;
|
|
|
-
|
|
|
- PopcornFX::PopcornFXEmitterComponentRequestBus::EventResult(startSuccess,
|
|
|
- GetEntity()->GetId(), &PopcornFX::PopcornFXEmitterComponentRequestBus::Events::Restart, KillOnRestart);
|
|
|
-
|
|
|
- AZ_Error("EnergyBall", startSuccess, "Restart call for Energy Ball was unsuccessful.");
|
|
|
-
|
|
|
- if (cl_EnergyBallDebugDraw)
|
|
|
- {
|
|
|
- m_debugDrawEvent.Enqueue(AZ::TimeMs{ 0 }, true);
|
|
|
- }
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- // Create an explosion effect wherever the ball was last at.
|
|
|
- m_effect.TriggerEffect(GetEntity()->GetTransform()->GetWorldTM());
|
|
|
-
|
|
|
- bool killSuccess = false;
|
|
|
-
|
|
|
- // This would ideally use Kill instead of Terminate, but there is a bug in PopcornFX 2.15.4 that if Kill is
|
|
|
- // called on the first tick (which can happen), then the effect will get stuck in a permanent waiting-to-die state,
|
|
|
- // and no amount of Restart calls will ever make it show up again.
|
|
|
- PopcornFX::PopcornFXEmitterComponentRequestBus::EventResult(killSuccess,
|
|
|
- GetEntity()->GetId(), &PopcornFX::PopcornFXEmitterComponentRequestBus::Events::Terminate);
|
|
|
-
|
|
|
- AZ_Error("EnergyBall", killSuccess, "Kill call for Energy Ball was unsuccessful.");
|
|
|
-
|
|
|
- m_debugDrawEvent.RemoveFromQueue();
|
|
|
- }
|
|
|
- }
|
|
|
+ // Perform hit / explosion logic when this entity deactivates, but *before* the deactivation sequence is
|
|
|
+ // actually running. This allows us to call the WeaponsNotificationBus to notify other components (like Script Canvas)
|
|
|
+ // on this entity to perform hit logic. If we waited to run this until OnDeactivate, the other components would no
|
|
|
+ // longer be active and wouldn't have a chance to process the logic.
|
|
|
|
|
|
- void EnergyBallComponent::HandleRPC_BallExplosion([[maybe_unused]] AzNetworking::IConnection* invokingConnection, const HitEvent& hitEvent)
|
|
|
- {
|
|
|
- // Create an explosion effect at our current location.
|
|
|
+ // Create an explosion effect wherever the ball was last at before deactivating.
|
|
|
m_effect.TriggerEffect(GetEntity()->GetTransform()->GetWorldTM());
|
|
|
|
|
|
- // Notify every entity that was hit that they've received a weapon impact, this allows for blast decals.
|
|
|
+ auto hitEvent = GetHitEvent();
|
|
|
+
|
|
|
+ // Notify this entity about the weapon impact for every entity that was hit, this allows for blast decals.
|
|
|
for (const HitEntity& hitEntity : hitEvent.m_hitEntities)
|
|
|
{
|
|
|
const AZ::Transform hitTransform = AZ::Transform::CreateLookAt(hitEntity.m_hitPosition, hitEntity.m_hitPosition + hitEntity.m_hitNormal, AZ::Transform::Axis::ZPositive);
|
|
@@ -112,9 +88,6 @@ namespace MultiplayerSample
|
|
|
{
|
|
|
if (cl_EnergyBallDebugDraw)
|
|
|
{
|
|
|
- // Each draw only lasts one frame.
|
|
|
- constexpr float DrawDuration = 0.0f;
|
|
|
-
|
|
|
auto* shapeConfig = GetGatherParams().GetCurrentShapeConfiguration();
|
|
|
if (shapeConfig->GetShapeType() == Physics::ShapeType::Sphere)
|
|
|
{
|
|
@@ -126,7 +99,7 @@ namespace MultiplayerSample
|
|
|
GetEntity()->GetTransform()->GetWorldTM().GetTranslation(),
|
|
|
debugRadius,
|
|
|
AZ::Colors::Green,
|
|
|
- DrawDuration
|
|
|
+ cl_EnergyBallDebugDrawSeconds
|
|
|
);
|
|
|
}
|
|
|
else if (shapeConfig->GetShapeType() == Physics::ShapeType::Box)
|
|
@@ -142,7 +115,7 @@ namespace MultiplayerSample
|
|
|
&DebugDraw::DebugDrawRequestBus::Events::DrawObb,
|
|
|
obb,
|
|
|
AZ::Colors::Green,
|
|
|
- DrawDuration
|
|
|
+ cl_EnergyBallDebugDrawSeconds
|
|
|
);
|
|
|
}
|
|
|
else if (shapeConfig->GetShapeType() == Physics::ShapeType::Capsule)
|
|
@@ -161,33 +134,26 @@ namespace MultiplayerSample
|
|
|
|
|
|
void EnergyBallComponentController::OnActivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating)
|
|
|
{
|
|
|
-#if AZ_TRAIT_SERVER
|
|
|
- SetBallActive(false);
|
|
|
-#endif
|
|
|
}
|
|
|
|
|
|
void EnergyBallComponentController::OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating)
|
|
|
{
|
|
|
#if AZ_TRAIT_SERVER
|
|
|
- SetBallActive(false);
|
|
|
+ m_collisionCheckEvent.RemoveFromQueue();
|
|
|
+ m_killEvent.RemoveFromQueue();
|
|
|
#endif
|
|
|
}
|
|
|
|
|
|
#if AZ_TRAIT_SERVER
|
|
|
void EnergyBallComponentController::HandleRPC_LaunchBall(AzNetworking::IConnection* invokingConnection, const AZ::Vector3& startingPosition, const AZ::Vector3& direction, const Multiplayer::NetEntityId& owningNetEntityId)
|
|
|
{
|
|
|
- if (GetBallActive())
|
|
|
- {
|
|
|
- return;
|
|
|
- }
|
|
|
+ AZ_Assert(!m_killEvent.IsScheduled(), "Launching the same ball more than once isn't supported.");
|
|
|
|
|
|
m_collisionCheckEvent.Enqueue(AZ::TimeMs{ 10 }, true);
|
|
|
|
|
|
- SetBallActive(true);
|
|
|
SetVelocity(direction * GetGatherParams().m_travelSpeed);
|
|
|
|
|
|
- m_hitEvent.m_hitEntities.clear();
|
|
|
-
|
|
|
+ m_shooterNetEntityId = owningNetEntityId;
|
|
|
m_filteredNetEntityIds.clear();
|
|
|
m_filteredNetEntityIds.insert(owningNetEntityId);
|
|
|
m_filteredNetEntityIds.insert(GetNetEntityId());
|
|
@@ -205,11 +171,6 @@ namespace MultiplayerSample
|
|
|
|
|
|
void EnergyBallComponentController::CheckForCollisions()
|
|
|
{
|
|
|
- if (!GetBallActive())
|
|
|
- {
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
const AZ::Vector3& position = GetEntity()->GetTransform()->GetWorldTM().GetTranslation();
|
|
|
const HitEffect& effect = GetHitEffect();
|
|
|
|
|
@@ -224,7 +185,7 @@ namespace MultiplayerSample
|
|
|
for (const IntersectResult& result : results)
|
|
|
{
|
|
|
const HitEntity hitEntity{ result.m_position, result.m_normal, result.m_netEntityId };
|
|
|
- m_hitEvent.m_hitEntities.emplace_back(hitEntity);
|
|
|
+ ModifyHitEvent().m_hitEntities.emplace_back(hitEntity);
|
|
|
|
|
|
const Multiplayer::ConstNetworkEntityHandle handle = Multiplayer::GetNetworkEntityManager()->GetEntity(result.m_netEntityId);
|
|
|
if (handle.Exists())
|
|
@@ -260,35 +221,21 @@ namespace MultiplayerSample
|
|
|
|
|
|
void EnergyBallComponentController::KillEnergyBall()
|
|
|
{
|
|
|
- if (!GetBallActive())
|
|
|
- {
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- SetBallActive(false);
|
|
|
m_collisionCheckEvent.RemoveFromQueue();
|
|
|
+ m_killEvent.RemoveFromQueue();
|
|
|
|
|
|
- m_hitEvent.m_target = GetEntity()->GetTransform()->GetWorldTM().GetTranslation();
|
|
|
- m_hitEvent.m_shooterNetEntityId = m_shooterNetEntityId;
|
|
|
- m_hitEvent.m_projectileNetEntityId = GetNetEntityId();
|
|
|
+ SetVelocity(AZ::Vector3::CreateZero());
|
|
|
|
|
|
- RPC_BallExplosion(m_hitEvent);
|
|
|
+ auto& hitEvent = ModifyHitEvent();
|
|
|
|
|
|
- // Wait 5 seconds before cleaning up the entity so that the explosion effect has a chance to play out
|
|
|
- // Capture just the netEntityId in case we have a level change or some other operation that clears out entities before our lambda triggers
|
|
|
+ hitEvent.m_target = GetEntity()->GetTransform()->GetWorldTM().GetTranslation();
|
|
|
+ hitEvent.m_shooterNetEntityId = m_shooterNetEntityId;
|
|
|
+ hitEvent.m_projectileNetEntityId = GetNetEntityId();
|
|
|
+
|
|
|
+ // Immediately remove the entity.
|
|
|
const Multiplayer::NetEntityId netEntityId = GetNetEntityId();
|
|
|
- AZ::Interface<AZ::IEventScheduler>::Get()->AddCallback([netEntityId]
|
|
|
- {
|
|
|
- // Fetch the entity handle, ensure it's still valid
|
|
|
- const Multiplayer::ConstNetworkEntityHandle entityHandle = Multiplayer::GetNetworkEntityManager()->GetEntity(netEntityId);
|
|
|
- if (entityHandle.Exists())
|
|
|
- {
|
|
|
- Multiplayer::GetNetworkEntityManager()->MarkForRemoval(entityHandle);
|
|
|
- }
|
|
|
- },
|
|
|
- AZ::Name("Cleanup"),
|
|
|
- GetLingertimeMs()
|
|
|
- );
|
|
|
+ const Multiplayer::ConstNetworkEntityHandle entityHandle = Multiplayer::GetNetworkEntityManager()->GetEntity(netEntityId);
|
|
|
+ Multiplayer::GetNetworkEntityManager()->MarkForRemoval(entityHandle);
|
|
|
}
|
|
|
#endif
|
|
|
}
|