/// --------------------------------------------- /// 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.Shared.Game; using Opsive.UltimateCharacterController.Input; using Opsive.UltimateCharacterController.Items; using Opsive.UltimateCharacterController.Items.Actions; using Opsive.UltimateCharacterController.Utility; using UnityEngine; /// /// ItemAbility which will start using the IUsableItem. /// [DefaultStartType(AbilityStartType.ButtonDown)] [DefaultStopType(AbilityStopType.ButtonUp)] [DefaultInputName("Fire1")] [DefaultItemStateIndex(2)] [DefaultState("Use")] [AllowDuplicateTypes] public class Use : ItemAbility { [Tooltip("The slot that should be used. -1 will use all of the slots.")] [SerializeField] protected int m_SlotID = -1; [Tooltip("The ID of the ItemAction component that can be used.")] [SerializeField] protected int m_ActionID; [Tooltip("Should the ability rotate the character to face the look source target?")] [SerializeField] protected bool m_RotateTowardsLookSourceTarget = true; public int SlotID { get { return m_SlotID; } set { if (m_SlotID != value) { UnregisterSlotEvents(m_SlotID); m_SlotID = value; RegisterSlotEvents(m_SlotID); } } } public int ActionID { get { return m_ActionID; } set { m_ActionID = value; } } public bool RotateTowardsLookSourceTarget { get { return m_RotateTowardsLookSourceTarget; } set { m_RotateTowardsLookSourceTarget = value; } } private ILookSource m_LookSource; protected IUsableItem[] m_UsableItems; private PlayerInput m_PlayerInput; private bool[] m_WaitForUseEvent; private bool[] m_CanStopAbility; private bool[] m_WaitForUseCompleteEvent; private bool[] m_UseCompleted; private Item m_FaceTargetItem; private ScheduledEventBase[] m_UseEvent; private ScheduledEventBase[] m_CanStopEvent; private bool m_Started; public IUsableItem[] UsableItems { get { return m_UsableItems; } } public Item FaceTargetItem { get { return m_FaceTargetItem; } } public override bool CanReceiveMultipleStarts { get { return true; } } #if UNITY_EDITOR public override string AbilityDescription { get { var description = string.Empty; if (m_SlotID != -1) { description += "Slot " + m_SlotID; } if (m_ActionID != 0) { if (!string.IsNullOrEmpty(description)) { description += ", "; } description += "Action " + m_ActionID; } return description; } } #endif /// /// Initialize the default values. /// public override void Awake() { base.Awake(); m_PlayerInput = m_GameObject.GetCachedComponent(); var count = m_SlotID == -1 ? m_Inventory.SlotCount : 1; m_UsableItems = new IUsableItem[count]; m_WaitForUseEvent = new bool[count]; m_CanStopAbility = new bool[count]; m_WaitForUseCompleteEvent = new bool[count]; m_UseCompleted = new bool[count]; m_UseEvent = new ScheduledEventBase[count]; m_CanStopEvent = new ScheduledEventBase[count]; for (int i = 0; i < count; ++i) { m_WaitForUseEvent[i] = false; m_UseCompleted[i] = true; } // The look source may have already been assigned if the ability was added to the character after the look source was assigned. m_LookSource = m_CharacterLocomotion.LookSource; EventHandler.RegisterEvent(m_GameObject, "OnCharacterAttachLookSource", OnAttachLookSource); EventHandler.RegisterEvent(m_GameObject, "OnEnableGameplayInput", OnEnableGameplayInput); EventHandler.RegisterEvent(m_GameObject, "OnAnimatorItemUse", OnItemUse); EventHandler.RegisterEvent(m_GameObject, "OnAnimatorItemUseComplete", OnItemUseComplete); #if ULTIMATE_CHARACTER_CONTROLLER_SHOOTER EventHandler.RegisterEvent(m_GameObject, "OnCharacterItemAbilityActive", OnItemAbilityActive); #endif // Register for the interested slot events. RegisterSlotEvents(m_SlotID); } /// /// Registers for the interested events according to the slot id. /// /// The slot id to register for. private void RegisterSlotEvents(int slotID) { if (!Application.isPlaying) { return; } if (slotID == 0) { EventHandler.RegisterEvent(m_GameObject, "OnAnimatorItemUseFirstSlot", OnItemUseFirstSlot); EventHandler.RegisterEvent(m_GameObject, "OnAnimatorItemUseCompleteFirstSlot", OnItemUseCompleteFirstSlot); } else if (slotID == 1) { EventHandler.RegisterEvent(m_GameObject, "OnAnimatorItemUseSecondSlot", OnItemUseSecondSlot); EventHandler.RegisterEvent(m_GameObject, "OnAnimatorItemUseCompleteSecondSlot", OnItemUseCompleteSecondSlot); } else if (slotID == 2) { EventHandler.RegisterEvent(m_GameObject, "OnAnimatorItemUseThirdSlot", OnItemUseThirdSlot); EventHandler.RegisterEvent(m_GameObject, "OnAnimatorItemUseCompleteThirdSlot", OnItemUseCompleteThirdSlot); } else if (slotID != -1) { Debug.LogError("Error: The Use ability does not listen to slot " + m_SlotID); } } /// /// Unregisters from the interested events according to the slot id. /// /// The slot id to unregister from. private void UnregisterSlotEvents(int slotID) { if (!Application.isPlaying) { return; } if (slotID == 0) { EventHandler.UnregisterEvent(m_GameObject, "OnAnimatorItemUseFirstSlot", OnItemUseFirstSlot); EventHandler.UnregisterEvent(m_GameObject, "OnAnimatorItemUseCompleteFirstSlot", OnItemUseCompleteFirstSlot); } else if (slotID == 1) { EventHandler.UnregisterEvent(m_GameObject, "OnAnimatorItemUseSecondSlot", OnItemUseSecondSlot); EventHandler.UnregisterEvent(m_GameObject, "OnAnimatorItemUseCompleteSecondSlot", OnItemUseCompleteSecondSlot); } else if (slotID == 2) { EventHandler.UnregisterEvent(m_GameObject, "OnAnimatorItemUseThirdSlot", OnItemUseThirdSlot); EventHandler.UnregisterEvent(m_GameObject, "OnAnimatorItemUseCompleteThirdSlot", OnItemUseCompleteThirdSlot); } } /// /// A new ILookSource object has been attached to the character. /// /// The ILookSource object attached to the character. private void OnAttachLookSource(ILookSource lookSource) { m_LookSource = lookSource; } /// /// Returns the Item State Index which corresponds to the slot ID. /// /// The ID of the slot that corresponds to the Item State Index. /// The Item State Index which corresponds to the slot ID. public override int GetItemStateIndex(int slotID) { // Return the ItemStateIndex if the SlotID matches the requested slotID. if (m_SlotID == -1) { if (m_UsableItems[slotID] != null) { return m_ItemStateIndex; } } else if (m_SlotID == slotID && m_UsableItems[0] != null) { return m_ItemStateIndex; } return -1; } /// /// 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) { if (m_SlotID == -1) { if (m_UsableItems[slotID] != null) { return m_UsableItems[slotID].GetItemSubstateIndex(); } } else if (m_SlotID == slotID && m_UsableItems[0] != null) { return m_UsableItems[0].GetItemSubstateIndex(); } return -1; } /// /// Can the item be used? /// /// True if the item can be used. public override bool CanStartAbility() { // An attribute may prevent the ability from starting. if (!base.CanStartAbility()) { return false; } // Don't use the item if the cursor is over any UI. if (m_PlayerInput != null && m_PlayerInput.IsPointerOverUI()) { return false; } // A look source must exist. if (m_LookSource == null) { return false; } // If the SlotID is -1 then the ability should use every equipped item at the same time. If only one slot has a UsableItem then the // ability can start. If the SlotID is not -1 then the ability should use the item in the specified slot. var canUse = false; if (m_SlotID == -1) { for (int i = 0; i < m_UsableItems.Length; ++i) { var item = m_Inventory.GetActiveItem(i); if (item == null) { continue; } var itemAction = item.GetItemAction(m_ActionID); if (itemAction == null) { continue; } m_UsableItems[i] = itemAction as IUsableItem; // The item can't be used if it isn't a usable item. if (m_UsableItems[i] != null) { if (m_UseCompleted[i] && !m_CanStopAbility[i] && m_UsableItems[i].IsItemInUse() && m_UsableItems[i].CanStopItemUse()) { m_UsableItems[i].StopItemUse(); } if (!m_UsableItems[i].CanUseItem(this, UsableItem.UseAbilityState.Start)) { continue; } canUse = true; } } } else { var item = m_Inventory.GetActiveItem(m_SlotID); if (item != null) { var itemAction = item.GetItemAction(m_ActionID); if (itemAction != null) { m_UsableItems[0] = itemAction as IUsableItem; // The item can't be used if it isn't a usable item. if (m_UsableItems[0] != null) { // If the item has completed use and is waiting on the CanStop event then it should reset so it can be used again. if (m_UseCompleted[0] && !m_CanStopAbility[0] && m_UsableItems[0].IsItemInUse() && m_UsableItems[0].CanStopItemUse()) { m_UsableItems[0].StopItemUse(); } if (m_UsableItems[0].CanUseItem(this, UsableItem.UseAbilityState.Start)) { canUse = true; } } } } } return canUse; } /// /// Does the ability use the specified ItemAction type? /// /// The ItemAction type to compare against. /// True if the ability uses the specified ItemAction type. public bool UsesItemActionType(System.Type itemActionType) { // If the SlotID is -1 then the ability should can every equipped item at the same time. If only one slot has an action which is of the specified type // then the entire method will return true. If the SlotID is not -1 then the ability will only check against the single ItemAction. if (m_SlotID == -1) { for (int i = 0; i < m_UsableItems.Length; ++i) { var item = m_Inventory.GetActiveItem(i); if (item == null) { continue; } var itemAction = item.GetItemAction(m_ActionID); if (itemAction == null) { return false; } // It only takes one ItemAction for the ability to use the specified ItemAction. if (itemAction.GetType().IsAssignableFrom(itemActionType)) { return true; } } } else { var item = m_Inventory.GetActiveItem(m_SlotID); if (item != null) { var itemAction = item.GetItemAction(m_ActionID); if (itemAction == null) { return false; } return itemAction.GetType().IsAssignableFrom(itemActionType); } } return false; } /// /// 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; } if (startingAbility is Use && startingAbility != this) { // The same item should not be able to be used by multiple use abilities at the same time. Different items can be used at the same time, such as // a primary item and a secondary grenade throw or dual pistols. var startingUseAbility = startingAbility as Use; for (int i = 0; i < m_UsableItems.Length; ++i) { if (m_UsableItems[i] == null) { continue; } for (int j = 0; j < startingUseAbility.UsableItems.Length; ++j) { if (startingUseAbility.UsableItems[j] == null) { continue; } if (m_UsableItems[i].Item == startingUseAbility.UsableItems[j].Item) { return true; } } } } #if ULTIMATE_CHARACTER_CONTROLLER_SHOOTER if (startingAbility is Reload) { // The Use ability has priority over the Reload ability. Prevent the reload ability from starting if the use ability is active. if (startingAbility.InputIndex != -1) { // If the item isn't actively being used then it shouldn't block reload. var shouldBlock = false; for (int i = 0; i < m_UsableItems.Length; ++i) { if (m_UsableItems[i] != null && !m_UseCompleted[i]) { shouldBlock = true; break; } } if (!shouldBlock) { return false; } var reloadAbility = startingAbility as Reload; StopItemReload(reloadAbility); // The ability should only be blocked if there aren't any items left to reload. An item may still be reloaded if it's parented to a different // slot from what is being used. shouldBlock = true; for (int i = 0; i < reloadAbility.ReloadableItems.Length; ++i) { if (reloadAbility.ReloadableItems[i] != null) { shouldBlock = false; } } return shouldBlock; } } #endif // Active items can block starting abilities. for (int i = 0; i < m_UsableItems.Length; ++i) { if (m_UsableItems[i] == null) { continue; } if (!m_UsableItems[i].CanStartAbility(startingAbility)) { return true; } } return false; } /// /// 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 Use starts while EquipUnequip is active then EquipUnequip should stop. if (activeAbility is EquipUnequip) { return true; } #if ULTIMATE_CHARACTER_CONTROLLER_SHOOTER if (activeAbility is Reload) { // The Use ability has priority over the Reload ability. Stop Reload if it is currently reloading the item. StopItemReload(activeAbility as Reload); } #endif return base.ShouldStopActiveAbility(activeAbility); } #if ULTIMATE_CHARACTER_CONTROLLER_SHOOTER /// /// Stops any item that is trying to reload while it is being used. /// /// A reference to the reload ability. /// True if the same item is trying to be used and reloaded. private void StopItemReload(Reload reloadAbility) { for (int i = 0; i < m_UsableItems.Length; ++i) { if (m_UsableItems[i] == null) { continue; } for (int j = 0; j < reloadAbility.ReloadableItems.Length; ++j) { if (reloadAbility.ReloadableItems[j] == null) { continue; } if (m_UsableItems[i].Item == reloadAbility.ReloadableItems[j].Item) { reloadAbility.StopItemReload(j); } } } } #endif /// /// The ability has started. /// protected override void AbilityStarted() { // Shootable weapons will deduct the attribute on each use. var enableAttributeModifier = true; #if ULTIMATE_CHARACTER_CONTROLLER_SHOOTER for (int i = 0; i < m_UsableItems.Length; ++i) { if (enableAttributeModifier && m_UsableItems[i] != null && m_UsableItems[i] is ShootableWeapon) { enableAttributeModifier = false; break; } } #endif base.AbilityStarted(enableAttributeModifier); // The item may require root motion to prevent sliding. It may also require the character to face the target before it can actually be used. m_FaceTargetItem = null; var itemStartedUse = false; for (int i = 0; i < m_UsableItems.Length; ++i) { if (m_UsableItems[i] == null) { m_CanStopAbility[i] = true; continue; } m_UsableItems[i].StartItemUse(this); // An Animator Audio State Set may prevent the item from being used. if (!m_UsableItems[i].IsItemInUse()) { m_CanStopAbility[i] = true; continue; } itemStartedUse = true; m_WaitForUseEvent[i] = true; m_WaitForUseCompleteEvent[i] = false; m_UseCompleted[i] = false; ResetCanStopEvent(i); if (m_UsableItems[i].ForceRootMotionPosition) { m_CharacterLocomotion.ForceRootMotionPosition = true; } if (m_UsableItems[i].ForceRootMotionRotation) { m_CharacterLocomotion.ForceRootMotionRotation = true; } if (m_UsableItems[i].FaceTarget && !m_CharacterLocomotion.ActiveMovementType.UseIndependentLook(true)) { m_FaceTargetItem = m_UsableItems[i].Item; } ScheduleUseEvent(i); EventHandler.ExecuteEvent(m_GameObject, "OnItemStartUse", m_UsableItems[i], true); } // The ability can start multiple times. Ensure the events are only subscribed to once. if (itemStartedUse && !m_Started) { EventHandler.ExecuteEvent(m_GameObject, "OnUseAbilityStart", true, this); m_Started = true; } else if (!itemStartedUse) { // The ability should be stopped if no items are being used. var stopAbility = true; for (int i = 0; i < m_UseCompleted.Length; ++i) { if (m_UsableItems[i] == null) { continue; } if (!m_UseCompleted[i] || !m_CanStopAbility[i]) { stopAbility = false; break; } } if (stopAbility) { StopAbility(); } } } /// /// Resets the CanStop event back to its default value. /// /// The id of the slot that should be reset. private void ResetCanStopEvent(int slotID) { // Melee weapons will not have a stop use delay so should not reset the event. if (m_UsableItems[slotID] != null && m_UsableItems[slotID].StopUseAbilityDelay == 0) { m_CanStopAbility[slotID] = true; return; } m_CanStopAbility[slotID] = m_StopType == AbilityStopType.Manual; if (m_CanStopEvent[slotID] != null) { Scheduler.Cancel(m_CanStopEvent[slotID]); m_CanStopEvent[slotID] = null; } } /// /// Schedules the use event. /// /// The id of the slot that should be scheduled. private void ScheduleUseEvent(int slotID) { if (m_UseEvent[slotID] != null) { Scheduler.Cancel(m_UseEvent[slotID]); } if (!m_UsableItems[slotID].UseEvent.WaitForAnimationEvent) { m_UseEvent[slotID] = Scheduler.ScheduleFixed(m_UsableItems[slotID].UseEvent.Duration, UseItem, slotID); } } /// /// The animation has used all of the items. /// private void OnItemUse() { for (int i = 0; i < m_UsableItems.Length; ++i) { if (m_UsableItems[i] != null) { UseItem(i); } } } /// /// The animation has used the first item slot. /// private void OnItemUseFirstSlot() { UseItem(0); } /// /// The animation has used the second item slot. /// private void OnItemUseSecondSlot() { UseItem(1); } /// /// The animation has used the third item slot. /// private void OnItemUseThirdSlot() { UseItem(2); } /// /// The ItemUse event has been triggered. /// /// The id of the slot that was used. private void UseItem(int slotID) { var usableItem = m_UsableItems[slotID]; if (usableItem == null) { return; } m_WaitForUseEvent[slotID] = false; } /// /// Updates the ability. /// public override void Update() { // Do not call the base method to prevent an attribute from stopping the use. } /// /// 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() { // Enable the collision layer so the weapons can apply damage the originating character. m_CharacterLocomotion.EnableColliderCollisionLayer(true); // Tries to use the item. This is done within Update because the item can be used multiple times when the input button is held down. for (int i = 0; i < m_UsableItems.Length; ++i) { if (m_UsableItems[i] != null) { // Allow the items currently in use to be updated. m_UsableItems[i].UseItemUpdate(); // If the InputIndex isn't -1 and the stop event isn't null then the ability is trying to be stopped. The ability must remain active for as long // as the StopAbilityDelay but during this time the item should not be used. if (InputIndex != -1 && m_CanStopEvent[i] != null && m_UsableItems[i].CanStopItemUse()) { continue; } // Don't use the item if the item is waiting for the ItemUse event or has already been used. if (m_WaitForUseEvent[i]) { continue; } if (m_UsableItems[i].CanUseItem(this, UsableItem.UseAbilityState.Update)) { m_UsableItems[i].UseItem(); #if ULTIMATE_CHARACTER_CONTROLLER_SHOOTER // Each use should update the attribute. if (m_UsableItems[i] is ShootableWeapon && m_AttributeModifier != null) { m_AttributeModifier.EnableModifier(true); } #endif // Using the item may have killed the character and stopped the ability. if (!IsActive) { return; } // The ability may have been stopped immediately after use. This will happen if for example a shootable weapon automatically reloads when it // is out of ammo. if (m_UsableItems[i] != null) { // A custom use animation should be played. m_CharacterLocomotion.UpdateItemAbilityAnimatorParameters(); // The ability can be stopped after it has been used to allow hip firing. if (m_UsableItems[i].StopUseAbilityDelay > 0) { ResetCanStopEvent(i); m_CanStopEvent[i] = Scheduler.ScheduleFixed(m_UsableItems[i].StopUseAbilityDelay, AbilityCanStop, i); } else { m_CanStopAbility[i] = true; } // The item needs to be used before the complete event can be called. if (!m_UsableItems[i].IsItemUsePending()) { ScheduleCompleteEvent(i, true); } } } } } m_CharacterLocomotion.EnableColliderCollisionLayer(false); } /// /// Schedules the complete event. /// /// The index of the complete event to schedule. /// Should the event be scheduled? If false the Use Complete array will only be set. protected void ScheduleCompleteEvent(int index, bool scheduleEvent) { m_WaitForUseCompleteEvent[index] = true; if (scheduleEvent) { if (m_UseEvent[index] != null) { Scheduler.Cancel(m_UseEvent[index]); } if (!m_UsableItems[index].UseCompleteEvent.WaitForAnimationEvent) { m_UseEvent[index] = Scheduler.ScheduleFixed(m_UsableItems[index].UseCompleteEvent.Duration, UseCompleteItem, index); } } } /// /// The item has been used and the ability can now stop. /// /// The ID of the slot that can stop. private void AbilityCanStop(int slotID) { m_CanStopAbility[slotID] = true; m_CanStopEvent[slotID] = null; // The ability should be stopped if all items have finished being used. var stopAbility = true; for (int i = 0; i < m_UseCompleted.Length; ++i) { if (!m_UseCompleted[i]) { stopAbility = false; break; } } if (stopAbility) { StopAbility(); } } /// /// Update the controller's rotation values. /// public override void UpdateRotation() { // The rotation doesn't need to be updated if the item doesn't need to face the target. if (m_FaceTargetItem == null) { return; } // The look source may be null if a remote player is still being initialized. if (m_LookSource == null || !m_RotateTowardsLookSourceTarget) { return; } // Determine the direction that the character should be facing. var lookDirection = m_LookSource.LookDirection(m_LookSource.LookPosition(), true, m_CharacterLayerManager.IgnoreInvisibleCharacterLayers, false); var rotation = m_Transform.rotation * Quaternion.Euler(m_CharacterLocomotion.DeltaRotation); var localLookDirection = MathUtility.InverseTransformDirection(lookDirection, rotation); localLookDirection.y = 0; lookDirection = MathUtility.TransformDirection(localLookDirection, rotation); var targetRotation = Quaternion.LookRotation(lookDirection, rotation * Vector3.up); m_CharacterLocomotion.DeltaRotation = (Quaternion.Inverse(m_Transform.rotation) * targetRotation).eulerAngles; } /// /// The use animation has completed for all of the items. /// private void OnItemUseComplete() { for (int i = 0; i < m_UsableItems.Length; ++i) { if (m_UsableItems[i] != null) { UseCompleteItem(i); } } } /// /// The use animation has completed for the first item slot. /// private void OnItemUseCompleteFirstSlot() { UseCompleteItem(0); } /// /// The use animation has completed for the second item slot. /// private void OnItemUseCompleteSecondSlot() { UseCompleteItem(1); } /// /// The use animation has completed for the third item slot. /// private void OnItemUseCompleteThirdSlot() { UseCompleteItem(2); } /// /// The animator has finished playing the use animation. /// /// The id of the slot that was used. protected virtual void UseCompleteItem(int slotID) { var usableItem = m_UsableItems[slotID]; if (usableItem == null || !m_WaitForUseCompleteEvent[slotID]) { return; } m_WaitForUseCompleteEvent[slotID] = false; m_CharacterLocomotion.UpdateItemAbilityAnimatorParameters(); m_UseCompleted[slotID] = true; m_UsableItems[slotID].ItemUseComplete(); // The ability should stop when all the items have been used. var stopAbility = true; for (int i = 0; i < m_UseCompleted.Length; ++i) { if (m_UsableItems[i] == null) { continue; } if (!m_UseCompleted[i] || !m_CanStopAbility[i]) { stopAbility = false; break; } } if (stopAbility) { StopAbility(); } } /// /// Can the ability be stopped? /// /// True if the ability can be stopped. public override bool CanStopAbility() { for (int i = 0; i < m_UsableItems.Length; ++i) { // If the item is currently being used and it cannot be stopped then the ability cannot stop either. if (m_UsableItems[i] != null && m_UsableItems[i].IsItemInUse()) { m_UsableItems[i].TryStopItemUse(); // The UsableItem may not be able to be stopped (for example, if a throwable item should be used when the button press is released). if (!m_UsableItems[i].CanStopItemUse()) { return false; } if (!m_UseCompleted[i]) { // The complete event may not have been called if the item use was still pending. if (m_UseEvent[i] == null || !m_UseEvent[i].Active) { ScheduleCompleteEvent(i, true); } return false; } } // Don't stop if CanStopAbility is false. This will allow hip firing to keep the item held up momentarily after being used. The ability should always // be able to stop during a reload. if (!m_CanStopAbility[i] #if ULTIMATE_CHARACTER_CONTROLLER_SHOOTER && !m_CharacterLocomotion.IsAbilityTypeActive() #endif ) { return false; } } return true; } /// /// The ability has stopped running. /// /// Was the ability force stopped? protected override void AbilityStopped(bool force) { base.AbilityStopped(force); // The item may require root motion to prevent sliding. for (int i = 0; i < m_UsableItems.Length; ++i) { if (m_UsableItems[i] != null) { if (m_UsableItems[i].ForceRootMotionPosition) { m_CharacterLocomotion.ForceRootMotionPosition = false; } if (m_UsableItems[i].ForceRootMotionRotation) { m_CharacterLocomotion.ForceRootMotionRotation = false; } m_UsableItems[i].StopItemUse(); EventHandler.ExecuteEvent(m_GameObject, "OnItemStartUse", m_UsableItems[i], false); m_UsableItems[i] = null; m_UseCompleted[i] = true; if (m_UseEvent[i] != null) { Scheduler.Cancel(m_UseEvent[i]); m_UseEvent[i] = null; } ResetCanStopEvent(i); } } m_Started = false; EventHandler.ExecuteEvent(m_GameObject, "OnUseAbilityStart", false, this); } #if ULTIMATE_CHARACTER_CONTROLLER_SHOOTER /// /// The item ability has been started or stopped. /// /// The item ability which was started or stopped. /// True if the ability was started, false if it was stopped. private void OnItemAbilityActive(ItemAbility itemAbility, bool active) { if (!(itemAbility is Reload)) { return; } // Use currently is not active, but it may have to start if the Use ability is trying to be started. if (!active && InputIndex != -1 && m_PlayerInput != null) { // Change the start type so the button up won't affect if the ability can start. var startType = m_StartType; if (startType == AbilityStartType.ButtonDown) { m_StartType = AbilityStartType.ButtonDownContinuous; } if (CanInputStartAbility(m_PlayerInput)) { if (IsActive) { // The use state should be reset if the ability is currently active. for (int i = 0; i < m_UsableItems.Length; ++i) { if (m_UsableItems[i] == null) { continue; } m_UsableItems[i].StartItemUse(this); ResetCanStopEvent(i); } InputIndex = -1; } else { // The ability isn't active, but it should be. StartAbility(); } } m_StartType = startType; return; } var reloadAbility = itemAbility as Reload; for (int i = 0; i < reloadAbility.ReloadableItems.Length; ++i) { if (reloadAbility.ReloadableItems[i] == null) { continue; } var slotID = reloadAbility.ReloadableItems[i].Item.SlotID; if (m_SlotID != -1) { if (m_SlotID != slotID) { continue; } // If a slot ID is specified then there will only be one element. slotID = 0; } // If the reload ability is active the CanStop event shouldn't fire so the character can continue to fire after reloading. if (active) { // If the ability index is not -1 then the item is trying to be stopped. Prevent the item from being used again when reload is complete. if (InputIndex != -1) { StopAbility(true); } else { ResetCanStopEvent(slotID); } } else { m_CanStopEvent[slotID] = Scheduler.ScheduleFixed(m_UsableItems[slotID].StopUseAbilityDelay, AbilityCanStop, slotID); } } } #endif /// /// Enables or disables gameplay input. An example of when it will not be enabled is when there is a fullscreen UI over the main camera. /// /// True if the input is enabled. private void OnEnableGameplayInput(bool enable) { // Force stop the ability if the character no longer has input. if (!enable && IsActive) { StopAbility(true); } } /// /// Called when the character is destroyed. /// public override void OnDestroy() { base.OnDestroy(); EventHandler.UnregisterEvent(m_GameObject, "OnCharacterAttachLookSource", OnAttachLookSource); EventHandler.UnregisterEvent(m_GameObject, "OnEnableGameplayInput", OnEnableGameplayInput); EventHandler.UnregisterEvent(m_GameObject, "OnAnimatorItemUse", OnItemUse); EventHandler.UnregisterEvent(m_GameObject, "OnAnimatorItemUseComplete", OnItemUseComplete); #if ULTIMATE_CHARACTER_CONTROLLER_SHOOTER EventHandler.UnregisterEvent(m_GameObject, "OnCharacterItemAbilityActive", OnItemAbilityActive); #endif UnregisterSlotEvents(m_SlotID); } } }