/// --------------------------------------------- /// Ultimate Character Controller /// Copyright (c) Opsive. All Rights Reserved. /// https://www.opsive.com /// --------------------------------------------- namespace Opsive.UltimateCharacterController.Character.Abilities.Items { using Opsive.Shared.Events; using Opsive.UltimateCharacterController.Items.Actions; using Opsive.UltimateCharacterController.SurfaceSystem; using Opsive.UltimateCharacterController.Utility; using UnityEngine; /// /// ItemAbility which will start using the MeleeWeapon while in the air. /// [DefaultStartType(AbilityStartType.ButtonDown)] [DefaultInputName("Fire1")] [DefaultItemStateIndex(2)] [DefaultState("Use")] [AllowDuplicateTypes] public class InAirMeleeUse : Use { [Tooltip("The amount of force that should be applied at the start of the ability.")] [SerializeField] protected Vector3 m_UpwardForce = new Vector3(0, 1, 0); [Tooltip("The number of frames that the upward force is applied in.")] [SerializeField] protected int m_UpwardForceFrames = 4; [Tooltip("The amount of force that should be applied when the character starts to fall.")] [SerializeField] protected Vector3 m_DownwardForce = new Vector3(0, -15, 0); [Tooltip("The number of frames that the downward force is applied in.")] [SerializeField] protected int m_DownwardForceFrames = 4; [Tooltip("Can the ability stop while the character is still in the air?")] [SerializeField] protected bool m_AllowInAirStop = false; [Tooltip("The value of the ItemSubstateIndex parameter when the character has becomes grounded after performing the use.")] [SerializeField] protected int m_OnGroundedSubstateIndex = 11; [Tooltip("The impact effect that should be played when the character is grounded.")] [SerializeField] protected SurfaceImpact m_GroundImpact; [Tooltip("The amount of positional force to add to the camera when the character is grounded.")] [SerializeField] protected MinMaxVector3 m_GroundPositionCameraRecoil; [Tooltip("The amount of rotational recoil to add to the camera when the character is grounded.")] [SerializeField] protected MinMaxVector3 m_GroundRotationCameraRecoil = new MinMaxVector3(new Vector3(-40f, -40f, 0), new Vector3(40f, 40f, 0), new Vector3(20, 20, 0)); public Vector3 UpwardForce { get { return m_UpwardForce; } set { m_UpwardForce = value; } } public int UpwardForceFrames { get { return m_UpwardForceFrames; } set { m_UpwardForceFrames = value; } } public Vector3 DownwardForce { get { return m_DownwardForce; } set { m_DownwardForce = value; } } public int DownwardForceFrames { get { return m_DownwardForceFrames; } set { m_DownwardForceFrames = value; } } public bool AllowInAirStop { get { return m_AllowInAirStop; } set { m_AllowInAirStop = value; } } public MinMaxVector3 GroundPositionCameraRecoil { get { return m_GroundPositionCameraRecoil; } set { m_GroundPositionCameraRecoil = value; } } public MinMaxVector3 GroundRotationCameraRecoil { get { return m_GroundRotationCameraRecoil; } set { m_GroundRotationCameraRecoil = value; } } private Jump m_JumpAbility; private bool m_AppliedDownwardForce; private bool m_UseOnGroundedSubstate; public override bool CanReceiveMultipleStarts { get { return false; } } /// /// Initialize the default values. /// public override void Awake() { base.Awake(); m_JumpAbility = m_CharacterLocomotion.GetAbility(); EventHandler.RegisterEvent(m_GameObject, "OnCharacterGrounded", OnGrounded); } /// /// Can the item be used? /// /// True if the item can be used. public override bool CanStartAbility() { // The character needs to be in the air for the ability to start. if (m_CharacterLocomotion.Grounded) { return false; } if (!base.CanStartAbility()) { return false; } // The IUsableWeapon must be a MeleeWeapon. var usableMeleeWeapon = false; if (m_SlotID == -1) { for (int i = 0; i < m_UsableItems.Length; ++i) { if (m_UsableItems[i] is MeleeWeapon) { usableMeleeWeapon = true; } else { m_UsableItems[i] = null; } } } else { if (m_UsableItems[0] is MeleeWeapon) { usableMeleeWeapon = true; } else { m_UsableItems[0] = null; } } return usableMeleeWeapon; } /// /// Called when the current ability is attempting to start and another ability is active. /// Returns true or false depending on if the active ability should be stopped. /// /// The ability that is currently active. /// True if the ability should be stopped. public override bool ShouldStopActiveAbility(Ability activeAbility) { if (base.ShouldStopActiveAbility(activeAbility)) { return true; } return activeAbility is Jump || activeAbility is Use; } /// /// Called when another ability is attempting to start and the current ability is active. /// Returns true or false depending on if the new ability should be blocked from starting. /// /// The ability that is starting. /// True if the ability should be blocked. public override bool ShouldBlockAbilityStart(Ability startingAbility) { if (base.ShouldBlockAbilityStart(startingAbility)) { return true; } return startingAbility is Jump || startingAbility is Use; } /// /// The ability has started. /// protected override void AbilityStarted() { base.AbilityStarted(); m_CharacterLocomotion.AddRelativeForce(m_UpwardForce, m_UpwardForceFrames); m_AppliedDownwardForce = false; m_UseOnGroundedSubstate = false; // If the character is near the ground the use complete event may occur before use. Prevent the character from getting stuck by // allowing the complete event to occur. for (int i = 0; i < m_UsableItems.Length; ++i) { ScheduleCompleteEvent(i, false); } } /// /// Returns the Item Substate Index which corresponds to the slot ID. /// /// The ID of the slot that corresponds to the Item Substate Index. /// The Item Substate Index which corresponds to the slot ID. public override int GetItemSubstateIndex(int slotID) { // The OnGrounded Substate Index value should be triggered when the character lands with the melee attack. The ability will then stop when the item use event is sent. if (m_UseOnGroundedSubstate) { if ((m_SlotID == -1 && m_UsableItems[slotID] != null) || (m_SlotID != -1 && m_UsableItems[0] != null)) { return m_OnGroundedSubstateIndex; } } return base.GetItemSubstateIndex(slotID); } /// /// Updates the ability after the controller has updated. This will ensure the character is in the most up to date position. /// public override void LateUpdate() { base.LateUpdate(); if (!m_AppliedDownwardForce && m_CharacterLocomotion.LocalVelocity.y < 0) { m_CharacterLocomotion.AddRelativeForce(m_DownwardForce, m_DownwardForceFrames); m_AppliedDownwardForce = true; } } /// /// Can the ability be stopped? /// /// True if the ability can be stopped. public override bool CanStopAbility() { // The ability can stop when the character is grounded or the character can stop while in the air. if ((m_AllowInAirStop || !m_CharacterLocomotion.Grounded) && (m_JumpAbility == null || !m_JumpAbility.IsActive)) { return false; } return base.CanStopAbility(); } /// /// The character has changed grounded states. /// /// Is the character on the ground? private void OnGrounded(bool grounded) { if (!IsActive) { return; } if (grounded) { SurfaceManager.SpawnEffect(m_CharacterLocomotion.GroundRaycastHit, m_GroundImpact, m_CharacterLocomotion.GravityDirection, m_CharacterLocomotion.TimeScale, m_GameObject); EventHandler.ExecuteEvent(m_GameObject, "OnAddSecondaryCameraForce", m_GroundPositionCameraRecoil.RandomValue, m_GroundRotationCameraRecoil.RandomValue, 0f); // The item may have completed its use before the object is grounded. if (CanStopAbility()) { StopAbility(); } else { m_UseOnGroundedSubstate = true; m_CharacterLocomotion.UpdateItemAbilityAnimatorParameters(); } } } /// /// Called when the character is destroyed. /// public override void OnDestroy() { base.OnDestroy(); EventHandler.UnregisterEvent(m_GameObject, "OnCharacterGrounded", OnGrounded); } } }