NetworkWeaponsComponent.cpp 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705
  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/NetworkWeaponsComponent.h>
  8. #include <Source/Components/NetworkPlayerMovementComponent.h>
  9. #include <Source/Components/NetworkAiComponent.h>
  10. #include <Source/Components/NetworkAnimationComponent.h>
  11. #include <Source/Components/NetworkHealthComponent.h>
  12. #include <Multiplayer/Components/NetworkRigidBodyComponent.h>
  13. #include <Source/Components/NetworkMatchComponent.h>
  14. #include <Source/Components/NetworkSimplePlayerCameraComponent.h>
  15. #include <Source/Components/Multiplayer/PlayerIdentityComponent.h>
  16. #include <Source/Weapons/BaseWeapon.h>
  17. #include <AzCore/Component/TransformBus.h>
  18. #include <AzCore/Math/Plane.h>
  19. #include <AzFramework/Physics/PhysicsScene.h>
  20. #include <AzFramework/Physics/Common/PhysicsSceneQueries.h>
  21. #include <WeaponNotificationBus.h>
  22. #if AZ_TRAIT_CLIENT
  23. # include <DebugDraw/DebugDrawBus.h>
  24. #endif
  25. namespace MultiplayerSample
  26. {
  27. AZ_CVAR(bool, cl_WeaponsDrawDebug, false, nullptr, AZ::ConsoleFunctorFlags::Null, "If enabled, weapons will debug draw various important events");
  28. AZ_CVAR(float, cl_WeaponsDrawDebugSize, 0.125f, nullptr, AZ::ConsoleFunctorFlags::Null, "The size of sphere to debug draw during weapon events");
  29. AZ_CVAR(float, cl_WeaponsDrawDebugDurationSec, 10.0f, nullptr, AZ::ConsoleFunctorFlags::Null, "The number of seconds to display debug draw data");
  30. AZ_CVAR(float, sv_WeaponsImpulseScalar, 750.0f, nullptr, AZ::ConsoleFunctorFlags::Null, "A fudge factor for imparting impulses on rigid bodies due to weapon hits");
  31. AZ_CVAR(float, sv_WeaponsStartPositionClampRange, 1.f, nullptr, AZ::ConsoleFunctorFlags::Null, "A fudge factor between the where the client and server say a shot started");
  32. AZ_CVAR(float, sv_WeaponsDotClamp, 0.35f, nullptr, AZ::ConsoleFunctorFlags::Null, "Acceptable dot product range for a shot between the camera raycast and weapon raycast.");
  33. class BehaviorWeaponNotificationBusHandler
  34. : public WeaponNotificationBus::Handler
  35. , public AZ::BehaviorEBusHandler
  36. {
  37. public:
  38. AZ_EBUS_BEHAVIOR_BINDER(BehaviorWeaponNotificationBusHandler, "{8F083B95-4519-4A24-8824-ED5ADA8FC52E}", AZ::SystemAllocator,
  39. OnWeaponActivate,
  40. OnWeaponImpact,
  41. OnWeaponDamage,
  42. OnConfirmedHitPlayer);
  43. void OnWeaponActivate(AZ::EntityId shooterEntityId, const AZ::Transform& transform) override
  44. {
  45. Call(FN_OnWeaponActivate, shooterEntityId, transform);
  46. }
  47. void OnWeaponImpact(AZ::EntityId shooterEntityId, const AZ::Transform& transform, AZ::EntityId hitEntityId) override
  48. {
  49. Call(FN_OnWeaponImpact, shooterEntityId, transform, hitEntityId);
  50. }
  51. void OnWeaponDamage(AZ::EntityId shooterEntityId, const AZ::Transform& transform, AZ::EntityId hitEntityId) override
  52. {
  53. Call(FN_OnWeaponDamage, shooterEntityId, transform, hitEntityId);
  54. }
  55. void OnConfirmedHitPlayer(AZ::EntityId byPlayerEntity, AZ::EntityId otherPlayerEntity) override
  56. {
  57. Call(FN_OnConfirmedHitPlayer, byPlayerEntity, otherPlayerEntity);
  58. }
  59. };
  60. void NetworkWeaponsComponent::Reflect(AZ::ReflectContext* context)
  61. {
  62. AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
  63. if (serializeContext)
  64. {
  65. serializeContext->Class<NetworkWeaponsComponent, NetworkWeaponsComponentBase>()
  66. ->Version(1);
  67. }
  68. NetworkWeaponsComponentBase::Reflect(context);
  69. AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context);
  70. if (behaviorContext)
  71. {
  72. behaviorContext->EBus<WeaponNotificationBus>("WeaponNotificationBus")
  73. ->Handler<BehaviorWeaponNotificationBusHandler>();
  74. }
  75. }
  76. NetworkWeaponsComponent::NetworkWeaponsComponent()
  77. : NetworkWeaponsComponentBase()
  78. , m_activationCountHandler([this](int32_t index, uint8_t value) { OnUpdateActivationCounts(index, value); })
  79. {
  80. ;
  81. }
  82. void NetworkWeaponsComponent::OnInit()
  83. {
  84. AZStd::uninitialized_fill_n(m_fireBoneJointIds.data(), MaxWeaponsPerComponent, InvalidBoneId);
  85. }
  86. void NetworkWeaponsComponent::OnActivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating)
  87. {
  88. for (uint32_t weaponIndex = 0; weaponIndex < MaxWeaponsPerComponent; ++weaponIndex)
  89. {
  90. const ConstructParams constructParams
  91. {
  92. GetEntityHandle(),
  93. aznumeric_cast<WeaponIndex>(weaponIndex),
  94. GetWeaponParams(weaponIndex),
  95. *this
  96. };
  97. m_weapons[weaponIndex] = AZStd::move(CreateWeapon(constructParams));
  98. }
  99. if (IsNetEntityRoleClient())
  100. {
  101. ActivationCountsAddEvent(m_activationCountHandler);
  102. }
  103. m_tickSimulatedWeapons.Enqueue(AZ::Time::ZeroTimeMs);
  104. #if AZ_TRAIT_CLIENT
  105. if (m_debugDraw == nullptr)
  106. {
  107. m_debugDraw = DebugDraw::DebugDrawRequestBus::FindFirstHandler();
  108. }
  109. #endif
  110. }
  111. void NetworkWeaponsComponent::OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating)
  112. {
  113. m_tickSimulatedWeapons.RemoveFromQueue();
  114. }
  115. #if AZ_TRAIT_CLIENT
  116. void NetworkWeaponsComponent::HandleSendConfirmHit([[maybe_unused]] AzNetworking::IConnection* invokingConnection, const WeaponIndex& weaponIndex, const HitEvent& hitEvent)
  117. {
  118. if (GetWeapon(weaponIndex) == nullptr)
  119. {
  120. AZLOG_ERROR("Got confirmed hit for null weapon index");
  121. return;
  122. }
  123. WeaponHitInfo weaponHitInfo(*GetWeapon(weaponIndex), hitEvent);
  124. OnWeaponConfirmHit(weaponHitInfo);
  125. }
  126. #endif
  127. void NetworkWeaponsComponent::ActivateWeaponWithParams(WeaponIndex weaponIndex, WeaponState& weaponState, const FireParams& fireParams, bool validateActivations)
  128. {
  129. const AZ::Transform transform = AZ::Transform::CreateLookAt(fireParams.m_sourcePosition, fireParams.m_targetPosition);
  130. ActivateEvent activateEvent{ transform, fireParams.m_targetPosition, GetNetEntityId(), Multiplayer::InvalidNetEntityId };
  131. IWeapon* weapon = GetWeapon(weaponIndex);
  132. weapon->Activate(weaponState, GetEntityHandle(), activateEvent, validateActivations);
  133. }
  134. IWeapon* NetworkWeaponsComponent::GetWeapon(WeaponIndex weaponIndex) const
  135. {
  136. return m_weapons[aznumeric_cast<uint32_t>(weaponIndex)].get();
  137. }
  138. void NetworkWeaponsComponent::AddOnWeaponActivateEventHandler(OnWeaponActivateEvent::Handler& handler)
  139. {
  140. handler.Connect(m_onWeaponActivateEvent);
  141. }
  142. void NetworkWeaponsComponent::AddOnWeaponPredictHitEventHandler(OnWeaponPredictHitEvent::Handler& handler)
  143. {
  144. handler.Connect(m_onWeaponPredictHitEvent);
  145. }
  146. void NetworkWeaponsComponent::AddOnWeaponConfirmHitEventHandler(OnWeaponConfirmHitEvent::Handler& handler)
  147. {
  148. handler.Connect(m_onWeaponConfirmHitEvent);
  149. }
  150. void NetworkWeaponsComponent::OnWeaponActivate([[maybe_unused]] const WeaponActivationInfo& activationInfo)
  151. {
  152. // If we're replaying inputs then early out
  153. if (GetNetBindComponent()->IsReprocessingInput())
  154. {
  155. return;
  156. }
  157. m_onWeaponActivateEvent.Signal(activationInfo);
  158. WeaponNotificationBus::Broadcast(&WeaponNotificationBus::Events::OnWeaponActivate, GetEntity()->GetId(), activationInfo.m_activateEvent.m_initialTransform);
  159. #if AZ_TRAIT_CLIENT
  160. if (cl_WeaponsDrawDebug && m_debugDraw)
  161. {
  162. m_debugDraw->DrawSphereAtLocation
  163. (
  164. activationInfo.m_activateEvent.m_initialTransform.GetTranslation(),
  165. cl_WeaponsDrawDebugSize,
  166. AZ::Colors::Green,
  167. cl_WeaponsDrawDebugDurationSec
  168. );
  169. m_debugDraw->DrawSphereAtLocation
  170. (
  171. activationInfo.m_activateEvent.m_targetPosition,
  172. cl_WeaponsDrawDebugSize,
  173. AZ::Colors::Yellow,
  174. cl_WeaponsDrawDebugDurationSec
  175. );
  176. }
  177. activationInfo.m_weapon.ExecuteActivateEffect(activationInfo.m_activateEvent.m_initialTransform, activationInfo.m_activateEvent.m_targetPosition);
  178. #endif
  179. }
  180. void NetworkWeaponsComponent::OnWeaponHit(const WeaponHitInfo& hitInfo)
  181. {
  182. if (IsNetEntityRoleAuthority())
  183. {
  184. #if AZ_TRAIT_SERVER
  185. OnWeaponConfirmHit(hitInfo);
  186. static_cast<NetworkWeaponsComponentController*>(GetController())->SendConfirmHit(hitInfo.m_weapon.GetWeaponIndex(), hitInfo.m_hitEvent);
  187. #endif
  188. }
  189. else
  190. {
  191. OnWeaponPredictHit(hitInfo);
  192. }
  193. }
  194. void NetworkWeaponsComponent::OnWeaponPredictHit(const WeaponHitInfo& hitInfo)
  195. {
  196. // If we're replaying inputs then early out
  197. if (GetNetBindComponent()->IsReprocessingInput())
  198. {
  199. return;
  200. }
  201. m_onWeaponPredictHitEvent.Signal(hitInfo);
  202. for (const auto& hitEntity : hitInfo.m_hitEvent.m_hitEntities)
  203. {
  204. const AZ::Transform hitTransform = AZ::Transform::CreateLookAt(hitEntity.m_hitPosition, hitEntity.m_hitPosition + hitEntity.m_hitNormal, AZ::Transform::Axis::ZPositive);
  205. const Multiplayer::ConstNetworkEntityHandle handle = Multiplayer::GetNetworkEntityManager()->GetEntity(hitEntity.m_hitNetEntityId);
  206. const AZ::EntityId hitEntityId = handle.Exists() ? handle.GetEntity()->GetId() : AZ::EntityId();
  207. WeaponNotificationBus::Broadcast(&WeaponNotificationBus::Events::OnWeaponImpact, GetEntity()->GetId(), hitTransform, hitEntityId);
  208. #if AZ_TRAIT_CLIENT
  209. if (cl_WeaponsDrawDebug && m_debugDraw)
  210. {
  211. m_debugDraw->DrawSphereAtLocation
  212. (
  213. hitEntity.m_hitPosition,
  214. cl_WeaponsDrawDebugSize,
  215. AZ::Colors::Orange,
  216. cl_WeaponsDrawDebugDurationSec
  217. );
  218. m_debugDraw->DrawLineLocationToLocation
  219. (
  220. hitEntity.m_hitPosition,
  221. hitEntity.m_hitPosition + hitEntity.m_hitNormal,
  222. AZ::Colors::Black,
  223. cl_WeaponsDrawDebugDurationSec
  224. );
  225. }
  226. hitInfo.m_weapon.ExecuteImpactEffect(hitInfo.m_weapon.GetFireParams().m_sourcePosition, hitEntity.m_hitPosition);
  227. #endif
  228. AZLOG
  229. (
  230. NET_Weapons,
  231. "Predicted hit on entity %" PRIu64 " at position %f x %f x %f",
  232. hitEntity.m_hitNetEntityId,
  233. hitEntity.m_hitPosition.GetX(),
  234. hitEntity.m_hitPosition.GetY(),
  235. hitEntity.m_hitPosition.GetZ()
  236. );
  237. }
  238. }
  239. void NetworkWeaponsComponent::OnWeaponConfirmHit(const WeaponHitInfo& hitInfo)
  240. {
  241. #if AZ_TRAIT_SERVER
  242. if (IsNetEntityRoleAuthority())
  243. {
  244. for (const HitEntity& hitEntity : hitInfo.m_hitEvent.m_hitEntities)
  245. {
  246. Multiplayer::ConstNetworkEntityHandle entityHandle = Multiplayer::GetMultiplayer()->GetNetworkEntityManager()->GetEntity(hitEntity.m_hitNetEntityId);
  247. if (entityHandle != nullptr && entityHandle.GetEntity() != nullptr)
  248. {
  249. const WeaponParams& weaponParams = hitInfo.m_weapon.GetParams();
  250. const HitEffect effect = weaponParams.m_damageEffect;
  251. // Presently set to 1 until we capture falloff range
  252. float hitDistance = 1.f;
  253. float maxDistance = 1.f;
  254. float damage = effect.m_hitMagnitude * powf((effect.m_hitFalloff * (1.0f - hitDistance / maxDistance)), effect.m_hitExponent);
  255. // Look for physics rigid body component and make impact updates
  256. if (Multiplayer::NetworkRigidBodyComponent* rigidBodyComponent = entityHandle.GetEntity()->FindComponent<Multiplayer::NetworkRigidBodyComponent>())
  257. {
  258. const AZ::Vector3 hitLocation = hitEntity.m_hitPosition;
  259. const AZ::Vector3 impulse = -hitEntity.m_hitNormal * damage * sv_WeaponsImpulseScalar;
  260. rigidBodyComponent->SendApplyImpulse(impulse, hitLocation);
  261. }
  262. // Look for health component and directly update health based on hit parameters
  263. if (NetworkHealthComponent* healthComponent = entityHandle.GetEntity()->FindComponent<NetworkHealthComponent>())
  264. {
  265. healthComponent->SendHealthDelta(damage * -1.0f);
  266. }
  267. }
  268. }
  269. }
  270. #endif
  271. m_onWeaponConfirmHitEvent.Signal(hitInfo);
  272. // If we're a simulated weapon, or if the weapon is not predictive, then issue material hit effects since the predicted callback above will not get triggered
  273. // Note that materialfx are not hooked up currently as the engine currently doesn't support them
  274. [[maybe_unused]] bool shouldIssueMaterialEffects = !HasController() || !hitInfo.m_weapon.GetParams().m_locallyPredicted;
  275. for (const auto& hitEntity : hitInfo.m_hitEvent.m_hitEntities)
  276. {
  277. const AZ::Transform hitTransform = AZ::Transform::CreateLookAt(hitEntity.m_hitPosition, hitEntity.m_hitPosition + hitEntity.m_hitNormal, AZ::Transform::Axis::ZPositive);
  278. const Multiplayer::ConstNetworkEntityHandle handle = Multiplayer::GetNetworkEntityManager()->GetEntity(hitEntity.m_hitNetEntityId);
  279. const AZ::EntityId hitEntityId = handle.Exists() ? handle.GetEntity()->GetId() : AZ::EntityId();
  280. WeaponNotificationBus::Broadcast(&WeaponNotificationBus::Events::OnWeaponDamage, GetEntity()->GetId(), hitTransform, hitEntityId);
  281. #if AZ_TRAIT_CLIENT
  282. if (cl_WeaponsDrawDebug && m_debugDraw)
  283. {
  284. m_debugDraw->DrawSphereAtLocation
  285. (
  286. hitEntity.m_hitPosition,
  287. cl_WeaponsDrawDebugSize,
  288. AZ::Colors::Red,
  289. cl_WeaponsDrawDebugDurationSec
  290. );
  291. }
  292. if (handle.Exists())
  293. {
  294. if (const AZ::Entity* entity = handle.GetEntity())
  295. {
  296. if (entity->FindComponent<PlayerIdentityComponent>())
  297. {
  298. // Hit confirmed on a player
  299. WeaponNotificationBus::Broadcast(&WeaponNotificationBus::Events::OnConfirmedHitPlayer, GetEntityId(), entity->GetId());
  300. }
  301. }
  302. }
  303. hitInfo.m_weapon.ExecuteDamageEffect(hitInfo.m_weapon.GetFireParams().m_sourcePosition, hitEntity.m_hitPosition);
  304. #endif
  305. AZLOG
  306. (
  307. NET_Weapons,
  308. "Confirmed hit on entity %" PRIu64 " at position %f x %f x %f",
  309. hitEntity.m_hitNetEntityId,
  310. hitEntity.m_hitPosition.GetX(),
  311. hitEntity.m_hitPosition.GetY(),
  312. hitEntity.m_hitPosition.GetZ()
  313. );
  314. }
  315. }
  316. void NetworkWeaponsComponent::OnUpdateActivationCounts(int32_t index, uint8_t value)
  317. {
  318. IWeapon* weapon = GetWeapon(aznumeric_cast<WeaponIndex>(index));
  319. if (weapon == nullptr)
  320. {
  321. return;
  322. }
  323. if (HasController() && weapon->GetParams().m_locallyPredicted)
  324. {
  325. // If this is a predicted weapon, exit out because autonomous weapons predict activations
  326. return;
  327. }
  328. AZLOG(NET_Weapons, "Client activation event for weapon index %u", index);
  329. WeaponState& weaponState = m_simulatedWeaponStates[index];
  330. const FireParams& fireParams = GetActivationParams(index);
  331. weapon->SetFireParams(fireParams);
  332. while (weaponState.m_activationCount != value)
  333. {
  334. constexpr bool validateActivations = false;
  335. ActivateWeaponWithParams(aznumeric_cast<WeaponIndex>(index), weaponState, fireParams, validateActivations);
  336. }
  337. }
  338. void NetworkWeaponsComponent::OnTickSimulatedWeapons(float seconds)
  339. {
  340. for (int weaponIndex = 0; weaponIndex < m_simulatedWeaponStates.size(); ++weaponIndex)
  341. {
  342. if (auto* weapon = GetWeapon(static_cast<WeaponIndex>(weaponIndex)))
  343. {
  344. weapon->TickActiveShots(m_simulatedWeaponStates[weaponIndex], seconds);
  345. }
  346. }
  347. }
  348. NetworkWeaponsComponentController::NetworkWeaponsComponentController(NetworkWeaponsComponent& parent)
  349. : NetworkWeaponsComponentControllerBase(parent)
  350. , m_updateAI{[this] { UpdateAI(); }, AZ::Name{ "WeaponsControllerAI" } }
  351. {
  352. ;
  353. }
  354. void NetworkWeaponsComponentController::OnActivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating)
  355. {
  356. const NetworkAiComponent* networkAiComponent = GetParent().GetNetworkAiComponent();
  357. m_aiEnabled = (networkAiComponent != nullptr) ? networkAiComponent->GetEnabled() : false;
  358. if (m_aiEnabled)
  359. {
  360. m_updateAI.Enqueue(AZ::TimeMs{ 0 }, true);
  361. m_networkAiComponentController = GetNetworkAiComponentController();
  362. }
  363. else if (IsNetEntityRoleAutonomous())
  364. {
  365. StartingPointInput::InputEventNotificationBus::MultiHandler::BusConnect(DrawEventId);
  366. StartingPointInput::InputEventNotificationBus::MultiHandler::BusConnect(FirePrimaryEventId);
  367. StartingPointInput::InputEventNotificationBus::MultiHandler::BusConnect(FireSecondaryEventId);
  368. }
  369. }
  370. void NetworkWeaponsComponentController::OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating)
  371. {
  372. if (IsNetEntityRoleAutonomous() && !m_aiEnabled)
  373. {
  374. StartingPointInput::InputEventNotificationBus::MultiHandler::BusDisconnect(DrawEventId);
  375. StartingPointInput::InputEventNotificationBus::MultiHandler::BusDisconnect(FirePrimaryEventId);
  376. StartingPointInput::InputEventNotificationBus::MultiHandler::BusDisconnect(FireSecondaryEventId);
  377. }
  378. }
  379. AZ::Vector3 NetworkWeaponsComponent::GetCurrentShotStartPosition()
  380. {
  381. constexpr uint32_t weaponIndexInt = 0;
  382. const char* fireBoneName = GetFireBoneNames(weaponIndexInt).c_str();
  383. const int32_t boneIdx = GetNetworkAnimationComponent()->GetBoneIdByName(fireBoneName);
  384. AZ::Transform fireBoneTransform = AZ::Transform::CreateIdentity();
  385. if (!GetNetworkAnimationComponent()->GetJointTransformById(boneIdx, fireBoneTransform))
  386. {
  387. AZLOG_WARN("Failed to get transform for fire bone joint Id %u", boneIdx);
  388. }
  389. return fireBoneTransform.GetTranslation();
  390. }
  391. void NetworkWeaponsComponentController::CreateInput(Multiplayer::NetworkInput& input, [[maybe_unused]] float deltaTime)
  392. {
  393. INetworkMatch* networkMatchComponent = AZ::Interface<INetworkMatch>::Get();
  394. if (!networkMatchComponent || (networkMatchComponent->PlayerActionsAllowed() != AllowedPlayerActions::All))
  395. {
  396. m_weaponDrawn = false;
  397. m_weaponFiring = false;
  398. return;
  399. }
  400. // Inputs for your own component always exist
  401. NetworkWeaponsComponentNetworkInput* weaponInput = input.FindComponentInput<NetworkWeaponsComponentNetworkInput>();
  402. // Use simple debounce for weapon draw to prevent multiple inputs between frames
  403. if (m_weaponDrawnChanged || (m_weaponFiring.AnySet() && !m_weaponDrawn))
  404. {
  405. m_weaponDrawn = !m_weaponDrawn;
  406. m_weaponDrawnChanged = false;
  407. }
  408. weaponInput->m_firing = m_weaponFiring;
  409. // All weapon indices point to the same bone so only send one instance
  410. if (weaponInput->m_firing.AnySet())
  411. {
  412. // Draw weapon automatically if firing
  413. m_weaponDrawn = true;
  414. weaponInput->m_shotStartPosition = GetParent().GetCurrentShotStartPosition();
  415. }
  416. weaponInput->m_draw = m_weaponDrawn;
  417. }
  418. void NetworkWeaponsComponentController::ProcessInput(Multiplayer::NetworkInput& input, [[maybe_unused]] float deltaTime)
  419. {
  420. NetworkWeaponsComponentNetworkInput* weaponInput = input.FindComponentInput<NetworkWeaponsComponentNetworkInput>();
  421. NetworkPlayerMovementComponentNetworkInput* playerInput = input.FindComponentInput<NetworkPlayerMovementComponentNetworkInput>();
  422. // Enable aiming if our weapon drawn flag is raised
  423. GetNetworkAnimationComponentController()->ModifyActiveAnimStates().SetBit(aznumeric_cast<uint32_t>(CharacterAnimState::Aiming), weaponInput->m_draw);
  424. if ((playerInput != nullptr) && playerInput->m_sprint)
  425. {
  426. // Stop aiming whenever we're sprinting
  427. GetNetworkAnimationComponentController()->ModifyActiveAnimStates().SetBit(aznumeric_cast<uint32_t>(CharacterAnimState::Aiming), false);
  428. }
  429. // Always disable the recoil shooting flag when we start ProcessInput
  430. // It will raise again if we actually enter an activation scenario for any weapon
  431. if (!weaponInput->m_firing.AnySet())
  432. {
  433. GetNetworkAnimationComponentController()->ModifyActiveAnimStates().SetBit(aznumeric_cast<uint32_t>(CharacterAnimState::Shooting), false);
  434. }
  435. const AZ::Transform cameraTransform = GetNetworkSimplePlayerCameraComponentController()->GetCameraTransform(/*collisionEnabled=*/false);
  436. for (uint32_t weaponIndexInt = 0; weaponIndexInt < MaxWeaponsPerComponent; ++weaponIndexInt)
  437. {
  438. if (weaponInput->m_firing.GetBit(weaponIndexInt))
  439. {
  440. const char* fireBoneName = GetFireBoneNames(weaponIndexInt).c_str();
  441. int32_t boneIdx = GetNetworkAnimationComponentController()->GetParent().GetBoneIdByName(fireBoneName);
  442. AZ::Transform fireBoneTransform;
  443. if (!GetNetworkAnimationComponentController()->GetParent().GetJointTransformById(boneIdx, fireBoneTransform))
  444. {
  445. AZLOG_WARN("Failed to get transform for fire bone joint Id %u", boneIdx);
  446. }
  447. // Validate the proposed start position is reasonably close to the related bone
  448. if ((fireBoneTransform.GetTranslation() - weaponInput->m_shotStartPosition).GetLength() > sv_WeaponsStartPositionClampRange)
  449. {
  450. weaponInput->m_shotStartPosition = fireBoneTransform.GetTranslation();
  451. AZLOG_WARN("Shot origin was outside of clamp range, resetting to bone position");
  452. }
  453. // Setup a default aim target
  454. const WeaponParams weaponParams = GetWeaponParams(weaponIndexInt);
  455. AZ::Vector3 aimTarget = cameraTransform.GetTranslation() + cameraTransform.GetBasisY() * weaponParams.m_weaponMaxAimDistance;
  456. // Given a plane centered on the shot start position with the orientation of the camera
  457. // find the intersection of the camera ray with this plane and use it as the
  458. // start position for the trace to avoid any hits behind the weapon
  459. const AZ::Plane weaponPlane = AZ::Plane::CreateFromNormalAndPoint(cameraTransform.GetBasisY(), weaponInput->m_shotStartPosition);
  460. AZ::Vector3 rayStart = cameraTransform.GetTranslation();
  461. // on success, rayStart will contain the intersection point, on false we'll fallback to the camera translation
  462. if (!weaponPlane.CastRay(cameraTransform.GetTranslation(), cameraTransform.GetBasisY(), rayStart))
  463. {
  464. AZLOG_WARN("Falling back to detect aim target based on camera origin");
  465. }
  466. // Cast the ray in the physics system from the center of the camera forward
  467. if (auto* sceneInterface = AZ::Interface<AzPhysics::SceneInterface>::Get())
  468. {
  469. if (AzPhysics::SceneHandle sceneHandle = sceneInterface->GetSceneHandle(AzPhysics::DefaultPhysicsSceneName);
  470. sceneHandle != AzPhysics::InvalidSceneHandle)
  471. {
  472. AzPhysics::RayCastRequest physicsRayRequest;
  473. physicsRayRequest.m_start = rayStart;
  474. physicsRayRequest.m_direction = cameraTransform.GetBasisY();
  475. physicsRayRequest.m_distance = weaponParams.m_weaponMaxAimDistance;
  476. physicsRayRequest.m_queryType = AzPhysics::SceneQuery::QueryType::StaticAndDynamic;
  477. physicsRayRequest.m_reportMultipleHits = true;
  478. if (AzPhysics::SceneQueryHits result = sceneInterface->QueryScene(sceneHandle, &physicsRayRequest))
  479. {
  480. float minDistance = AZStd::numeric_limits<float>::max();
  481. for (const AzPhysics::SceneQueryHit& hit : result.m_hits)
  482. {
  483. // Set target to closest found intersection within dot tolerance, if any
  484. AZ::Vector3 targetDirection = hit.m_position - weaponInput->m_shotStartPosition;
  485. AZ::Vector3 aimDirection = physicsRayRequest.m_direction;
  486. targetDirection.Normalize();
  487. aimDirection.Normalize();
  488. if ((targetDirection.Dot(aimDirection) > sv_WeaponsDotClamp) && (hit.m_distance <= minDistance))
  489. {
  490. aimTarget = hit.m_position;
  491. minDistance = hit.m_distance;
  492. }
  493. }
  494. }
  495. }
  496. }
  497. FireParams fireParams{ weaponInput->m_shotStartPosition, aimTarget, Multiplayer::InvalidNetEntityId };
  498. TryStartFire(aznumeric_cast<WeaponIndex>(weaponIndexInt), fireParams);
  499. }
  500. }
  501. UpdateWeaponFiring(deltaTime);
  502. }
  503. void NetworkWeaponsComponentController::UpdateWeaponFiring([[maybe_unused]] float deltaTime)
  504. {
  505. for (uint32_t weaponIndexInt = 0; weaponIndexInt < MaxWeaponsPerComponent; ++weaponIndexInt)
  506. {
  507. IWeapon* weapon = GetParent().GetWeapon(aznumeric_cast<WeaponIndex>(weaponIndexInt));
  508. if ((weapon == nullptr) || !weapon->GetParams().m_locallyPredicted)
  509. {
  510. continue;
  511. }
  512. WeaponState& weaponState = ModifyWeaponStates(weaponIndexInt);
  513. if ((weaponState.m_status == WeaponStatus::Firing) && (weaponState.m_cooldownTime <= 0.0f))
  514. {
  515. AZLOG(NET_Weapons, "Weapon predicted activation event for weapon index %u", weaponIndexInt);
  516. const bool validateActivations = true;
  517. const FireParams& fireParams = weapon->GetFireParams();
  518. GetParent().ActivateWeaponWithParams(
  519. aznumeric_cast<WeaponIndex>(weaponIndexInt), weaponState, fireParams, validateActivations);
  520. #if AZ_TRAIT_SERVER
  521. if (IsNetEntityRoleAuthority())
  522. {
  523. SetActivationParams(weaponIndexInt, fireParams);
  524. SetActivationCounts(weaponIndexInt, weaponState.m_activationCount);
  525. }
  526. #endif
  527. }
  528. weapon->UpdateWeaponState(weaponState, deltaTime);
  529. }
  530. }
  531. bool NetworkWeaponsComponentController::TryStartFire(WeaponIndex weaponIndex, const FireParams& fireParams)
  532. {
  533. const uint32_t weaponIndexInt = aznumeric_cast<uint32_t>(weaponIndex);
  534. AZLOG(NET_Weapons, "Weapon start fire on %u", weaponIndexInt);
  535. IWeapon* weapon = GetParent().GetWeapon(weaponIndex);
  536. if (weapon == nullptr)
  537. {
  538. return false;
  539. }
  540. const uint32_t animBit = static_cast<uint32_t>(weapon->GetParams().m_animFlag);
  541. WeaponState& weaponState = ModifyWeaponStates(weaponIndexInt);
  542. if (weapon->TryStartFire(weaponState, fireParams))
  543. {
  544. if (!GetNetworkAnimationComponentController()->GetActiveAnimStates().GetBit(animBit))
  545. {
  546. GetNetworkAnimationComponentController()->ModifyActiveAnimStates().SetBit(animBit, true);
  547. }
  548. return true;
  549. }
  550. else if (GetNetworkAnimationComponentController()->GetActiveAnimStates().GetBit(animBit))
  551. {
  552. GetNetworkAnimationComponentController()->ModifyActiveAnimStates().SetBit(animBit, false);
  553. }
  554. return false;
  555. }
  556. void NetworkWeaponsComponentController::OnPressed([[maybe_unused]] float value)
  557. {
  558. const StartingPointInput::InputEventNotificationId* inputId = StartingPointInput::InputEventNotificationBus::GetCurrentBusId();
  559. if (inputId == nullptr)
  560. {
  561. return;
  562. }
  563. else if (*inputId == DrawEventId)
  564. {
  565. m_weaponDrawnChanged = true;
  566. }
  567. else if (*inputId == FirePrimaryEventId)
  568. {
  569. m_weaponFiring.SetBit(aznumeric_cast<uint32_t>(PrimaryWeaponIndex), true);
  570. }
  571. else if (*inputId == FireSecondaryEventId)
  572. {
  573. m_weaponFiring.SetBit(aznumeric_cast<uint32_t>(SecondaryWeaponIndex), true);
  574. }
  575. }
  576. void NetworkWeaponsComponentController::OnReleased([[maybe_unused]] float value)
  577. {
  578. const StartingPointInput::InputEventNotificationId* inputId = StartingPointInput::InputEventNotificationBus::GetCurrentBusId();
  579. if (inputId == nullptr)
  580. {
  581. return;
  582. }
  583. else if (*inputId == FirePrimaryEventId)
  584. {
  585. m_weaponFiring.SetBit(aznumeric_cast<uint32_t>(PrimaryWeaponIndex), false);
  586. }
  587. else if (*inputId == FireSecondaryEventId)
  588. {
  589. m_weaponFiring.SetBit(aznumeric_cast<uint32_t>(SecondaryWeaponIndex), false);
  590. }
  591. }
  592. void NetworkWeaponsComponentController::OnHeld([[maybe_unused]] float value)
  593. {
  594. ;
  595. }
  596. void NetworkWeaponsComponentController::UpdateAI()
  597. {
  598. #if AZ_TRAIT_SERVER
  599. float deltaTime = static_cast<float>(m_updateAI.TimeInQueueMs()) / 1000.f;
  600. if (m_networkAiComponentController != nullptr)
  601. {
  602. m_networkAiComponentController->TickWeapons(*this, deltaTime);
  603. }
  604. #endif
  605. }
  606. } // namespace MultiplayerSample