/// --------------------------------------------- /// 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.Shared.Inventory; using Opsive.UltimateCharacterController.Items; using Opsive.UltimateCharacterController.Items.Actions; using Opsive.UltimateCharacterController.Utility; using System.Collections.Generic; using UnityEngine; /// /// Equips or unequips an ItemSet. Can be started manually by calling EquipUnequip.StartEquipUnequip(ItemSetIndex). /// [DefaultStartType(AbilityStartType.ButtonDown)] [DefaultInputName("Equip First Item", 0)] [DefaultInputName("Equip Second Item", 1)] [DefaultInputName("Equip Third Item", 2)] [DefaultInputName("Equip Fourth Item", 3)] [DefaultInputName("Equip Fifth Item", 4)] [DefaultInputName("Equip Sixth Item", 5)] [DefaultInputName("Equip Seventh Item", 6)] [DefaultInputName("Equip Eighth Item", 7)] [DefaultInputName("Equip Ninth Item", 8)] [DefaultInputName("Equip Tenth Item", 9)] [AllowDuplicateTypes] public class EquipUnequip : ItemSetAbilityBase { /// /// Specifies when to equip a new Item. /// public enum AutoEquipType { Always = 1, // Always equip a picked up item. Unequipped = 2, // Equip the item if there are no items equipped. OutOfUsableItem = 4, // Equip the item if the current item has no more usable ItemIdentifiers left. NotPreset = 8, // Equip the item if the item hasn't been added to the inventory already. FirstTime = 16 // Equip the item the first time the item has been added. } /// /// Specifies what action take place with the next update. /// private enum EquipUnequipAction { Inactive, // No actions are currently necessary. Unequip, // The Unequip method should be called. UnequipComplete, // The UnequipComplete method should be called. Equip, // The Equip method should be called. EquipComplete // The EquipComplete method should be called. } [Tooltip("Mask which specifies when to auto equip a new item.")] [HideInInspector] [SerializeField] protected AutoEquipType m_AutoEquip = AutoEquipType.Unequipped | AutoEquipType.OutOfUsableItem | AutoEquipType.NotPreset | AutoEquipType.FirstTime; [Tooltip("The Item State Index while equipping.")] [SerializeField] protected int m_EquipItemStateIndex = 4; [Tooltip("The Item State Index while unequipping.")] [SerializeField] protected int m_UnequipItemStateIndex = 5; [Tooltip("The value to add to the Item Substate Index when the character is aiming.")] [SerializeField] protected int m_AimItemSubstateIndexAddition = 100; public AutoEquipType AutoEquip { get { return m_AutoEquip; } set { m_AutoEquip = value; } } private bool m_StartEquipUnequip; private int m_StartEquipUnequipIndex; private int m_ActiveItemSetIndex = -1; private int m_PrevActiveItemSetIndex = -1; private EquipUnequipAction[] m_EquipUnequipActions; private Item[] m_EquipItems; private Item[] m_UnequipItems; private ScheduledEventBase[] m_ItemEvents; private bool m_CanEquip; private bool[] m_EquippingItems; private bool[] m_UnequippingItems; private Dictionary m_InventoryAmount = new Dictionary(); private bool m_ImmediateEquipUnequip; private bool m_PlayEquipAudio; private bool m_Aiming; public override bool CanReceiveMultipleStarts { get { return true; } } public int ActiveItemSetIndex { get { return m_ActiveItemSetIndex; } } public override bool CanStayActivatedOnDeath { get { return true; } } /// /// Initialize the default values. /// public override void Awake() { base.Awake(); m_EquipUnequipActions = new EquipUnequipAction[m_Inventory.SlotCount]; m_EquipItems = new Item[m_Inventory.SlotCount]; m_UnequipItems = new Item[m_Inventory.SlotCount]; m_ItemEvents = new ScheduledEventBase[m_Inventory.SlotCount]; m_EquippingItems = new bool[m_Inventory.SlotCount]; m_UnequippingItems = new bool[m_Inventory.SlotCount]; EventHandler.RegisterEvent(m_GameObject, "OnItemPickupStartPickup", WillStartPickup); EventHandler.RegisterEvent(m_GameObject, "OnItemPickupStopPickup", StopPickup); EventHandler.RegisterEvent(m_GameObject, "OnInventoryPickupItem", OnPickupItem); EventHandler.RegisterEvent(m_GameObject, "OnInventoryPickupItemIdentifier", OnPickupItemIdentifier); EventHandler.RegisterEvent(m_GameObject, "OnItemSetIndexChange", OnItemSetIndexChange); // Animation events cannot have multiple parameters so use the event name to determine which slot to equip/unequip. EventHandler.RegisterEvent(m_GameObject, "OnAnimatorItemUnequip", OnItemUnequip); EventHandler.RegisterEvent(m_GameObject, "OnAnimatorItemUnequipComplete", OnItemUnequipComplete); EventHandler.RegisterEvent(m_GameObject, "OnAnimatorItemUnequipFirstSlot", OnItemUnequipFirstSlot); EventHandler.RegisterEvent(m_GameObject, "OnAnimatorItemUnequipCompleteFirstSlot", OnItemUnequipCompleteFirstSlot); EventHandler.RegisterEvent(m_GameObject, "OnAnimatorItemUnequipSecondSlot", OnItemUnequipSecondSlot); EventHandler.RegisterEvent(m_GameObject, "OnAnimatorItemUnequipCompleteSecondSlot", OnItemUnequipCompleteSecondSlot); EventHandler.RegisterEvent(m_GameObject, "OnAnimatorItemUnequipThirdSlot", OnItemUnequipThirdSlot); EventHandler.RegisterEvent(m_GameObject, "OnAnimatorItemUnequipCompleteThirdSlot", OnItemUnequipCompleteThirdSlot); EventHandler.RegisterEvent(m_GameObject, "OnEquipUnequipVerifyUnequipItem", OnVerifyUnequipItem); EventHandler.RegisterEvent(m_GameObject, "OnAnimatorItemEquip", OnItemEquip); EventHandler.RegisterEvent(m_GameObject, "OnAnimatorItemEquipComplete", OnItemEquipComplete); EventHandler.RegisterEvent(m_GameObject, "OnAnimatorItemEquipFirstSlot", OnItemEquipFirstSlot); EventHandler.RegisterEvent(m_GameObject, "OnAnimatorItemEquipCompleteFirstSlot", OnItemEquipCompleteFirstSlot); EventHandler.RegisterEvent(m_GameObject, "OnAnimatorItemEquipSecondSlot", OnItemEquipSecondSlot); EventHandler.RegisterEvent(m_GameObject, "OnAnimatorItemEquipCompleteSecondSlot", OnItemEquipCompleteSecondSlot); EventHandler.RegisterEvent(m_GameObject, "OnAnimatorItemEquipThirdSlot", OnItemEquipThirdSlot); EventHandler.RegisterEvent(m_GameObject, "OnAnimatorItemEquipCompleteThirdSlot", OnItemEquipCompleteThirdSlot); EventHandler.RegisterEvent(m_GameObject, "OnInventoryRemoveItem", OnRemoveItem); EventHandler.RegisterEvent(m_GameObject, "OnAimAbilityStart", OnAim); EventHandler.RegisterEvent(m_GameObject, "OnDeath", OnDeath); EventHandler.RegisterEvent(m_GameObject, "OnInventoryRespawned", OnInventoryRespawned); } /// /// The ItemPickup component is starting to pick up ItemIdentifiers. /// public void WillStartPickup() { // Remember the initial item inventory list to be able to determine if an item has been added. m_InventoryAmount.Clear(); var allItems = m_Inventory.GetAllItems(); for (int i = 0; i < allItems.Count; ++i) { // A duplicate item will exist if the item shares IItemIdentifiers. if (m_InventoryAmount.ContainsKey(allItems[i].ItemIdentifier)) { continue; } m_InventoryAmount.Add(allItems[i].ItemIdentifier, m_Inventory.GetItemIdentifierAmount(allItems[i].ItemIdentifier)); } m_PlayEquipAudio = true; } /// /// The ItemPickup component is no longer picking up any ItemIdentifiers. /// private void StopPickup() { m_PlayEquipAudio = false; } /// /// An item has been picked up within the inventory. Determine if the ability should start. /// /// The item that has been equipped. /// The amount of item picked up. /// Was the item be picked up immediately? /// Should the item be force equipped? private void OnPickupItem(Item item, int amount, bool immediatePickup, bool forceEquip) { // The ability doesn't need to respond if the category doesn't match. if (!m_ItemSetManager.IsCategoryMember(item.ItemDefinition, m_ItemSetCategoryIndex) || !Enabled) { return; } // Determine if the item should be auto equipped. There are a variety of circumstances which will allow the item to be equipped. if (ShouldEquip(item.ItemIdentifier, item.SlotID, amount)) { // The ItemSetManager will manage which items are equipped. var itemSetIndex = m_ItemSetManager.GetItemSetIndex(item, m_ItemSetCategoryIndex, true); // The itemSet may not be valid for the item yet. if (itemSetIndex != -1) { // The ItemSet can be equipped immediately or play the equip animation. If equipping immediately ensure // the character starts with the topmost ItemSet no matter the init order. if (immediatePickup && (forceEquip || m_ActiveItemSetIndex == -1 || itemSetIndex < m_ActiveItemSetIndex)) { // The ItemSet should be updated immediately. m_StartEquipUnequipIndex = m_ActiveItemSetIndex = itemSetIndex; EventHandler.ExecuteEvent(this, "OnEquipUnequipItemSetIndexChange", m_ActiveItemSetIndex); m_ItemSetManager.UpdateActiveItemSet(m_ItemSetCategoryIndex, m_ActiveItemSetIndex); for (int i = 0; i < m_Inventory.SlotCount; ++i) { // The current slot will be updated immediately. ForceEquipUnequip(i, false); var itemIdentifier = m_ItemSetManager.GetEquipItemIdentifier(m_ItemSetCategoryIndex, itemSetIndex, i); if (itemIdentifier == null) { // Unequip the current item if no items should be equipped with the current item set. var unequipItem = m_Inventory.GetActiveItem(i); if (unequipItem != null && m_ItemSetManager.IsCategoryMember(unequipItem.ItemIdentifier.GetItemDefinition(), m_ItemSetCategoryIndex)) { m_Inventory.UnequipItem(i); EventHandler.ExecuteEvent(m_GameObject, "OnAbilityUnequipItemComplete", unequipItem, i); } continue; } // Only manage the ItemIdentifier if the category matches. if (m_ItemSetManager.IsCategoryMember(itemIdentifier.GetItemDefinition(), m_ItemSetCategoryIndex)) { var equippedItem = m_Inventory.GetActiveItem(i); if (equippedItem != null) { // No changes are necessary if the ItemIdentifier that should be equipped is the same as the ItemIdentifier that is already equipped. if (itemIdentifier == equippedItem.ItemIdentifier) { continue; } m_Inventory.UnequipItem(i); EventHandler.ExecuteEvent(m_GameObject, "OnAbilityUnequipItemComplete", equippedItem, i); } var equipItem = m_Inventory.GetItem(itemIdentifier, i); if (equipItem != null) { equipItem.WillEquip(); EventHandler.ExecuteEvent(m_GameObject, "OnAbilityWillEquipItem", equipItem, i); equipItem.StartEquip(true); } m_Inventory.EquipItem(itemIdentifier, i, !m_PlayEquipAudio); } } } else if (forceEquip) { StartEquipUnequip(itemSetIndex); } } else if (m_ItemSetManager.ActiveItemSetIndex[m_ItemSetCategoryIndex] == -1) { // The ItemSet isn't currently valid. Set the default ItemSet so at least one ItemSet is active which will allow the states to switch // the ItemSet when it becomes valid. m_ItemSetManager.SetDefaultItemSet(m_ItemSetCategoryIndex); } } } /// /// Should the item be equipped? /// /// The IItemIdentifier that may be equipped. /// The ID of the slot may be equipped. /// The amount of item picked up. /// True if the item should be equipped. public bool ShouldEquip(IItemIdentifier itemIdentifier, int slotID, int amount) { // The character shouldn't equip the item if an item is currently in use or is reloading. if (m_CharacterLocomotion.IsAbilityTypeActive() #if ULTIMATE_CHARACTER_CONTROLLER_SHOOTER || m_CharacterLocomotion.IsAbilityTypeActive() #endif ) { return false; } var shouldEquip = false; var currentItem = m_Inventory.GetActiveItem(slotID); if (!m_InventoryAmount.TryGetValue(itemIdentifier, out var itemAmount)) { itemAmount = -1; } if ((m_AutoEquip & AutoEquipType.Always) != 0) { shouldEquip = true; } else if ((m_AutoEquip & AutoEquipType.Unequipped) != 0 && currentItem == null && m_EquipItems[slotID] == null) { shouldEquip = true; } else if ((m_AutoEquip & AutoEquipType.NotPreset) != 0 && itemAmount < amount) { shouldEquip = true; } else if ((m_AutoEquip & AutoEquipType.FirstTime) != 0 && itemAmount <= 0) { shouldEquip = true; } else if ((m_AutoEquip & AutoEquipType.OutOfUsableItem) != 0 && (currentItem is IUsableItem) && m_Inventory.GetItemIdentifierAmount((currentItem as IUsableItem).GetConsumableItemIdentifier()) == 0) { shouldEquip = true; } // An active ability may prevent the item equip. if (shouldEquip && !IsEquipAllowed(-1, itemIdentifier.GetItemDefinition(), slotID)) { shouldEquip = false; } return shouldEquip; } /// /// Can the specified ItemDefinition be equipped? An ability may prevent it from being equipped. /// /// The index of the ItemSet that is being equipped. /// The ItemDefinition of the item that is trying to be equipped. /// The slot ID of the item that is trying to be equipped. /// True if the ItemIdentifier can be equipped. private bool IsEquipAllowed(int itemSetIndex, ItemDefinitionBase itemDefinition, int slotID) { if (itemDefinition == null || (itemSetIndex != -1 && m_ItemSetManager != null && m_ItemSetManager.CategoryItemSets[m_ItemSetCategoryIndex].DefaultItemSetIndex == itemSetIndex)) { return true; } for (int i = 0; i < m_CharacterLocomotion.ActiveAbilityCount; ++i) { var ability = m_CharacterLocomotion.ActiveAbilities[i]; if (!MathUtility.InLayerMask(slotID, ability.AllowEquippedSlotsMask)) { return false; } // The AllowItemIdentifiers list may prevent the item from being equipped. if (ability.AllowItemDefinitions != null && ability.AllowItemDefinitions.Length > 0) { var allowed = false; for (int j = 0; j < ability.AllowItemDefinitions.Length; ++j) { if (ability.AllowItemDefinitions[j] == itemDefinition) { allowed = true; break; } } if (!allowed) { return false; } } } return true; } /// /// An ItemIdentifier has been picked up within the inventory. /// /// The ItemIdentifier that has been picked up. /// The amount of item picked up. /// Was the item be picked up immediately? /// Should the item be force equipped? private void OnPickupItemIdentifier(IItemIdentifier itemIdentifier, int amount, bool immediatePickup, bool forceEquip) { // The ability doesn't need to respond if the category doesn't match. var itemDefinition = itemIdentifier.GetItemDefinition(); if (!m_ItemSetManager.IsCategoryMember(itemDefinition, m_ItemSetCategoryIndex) || !Enabled) { return; } // An ItemIdentifier may be picked up after the item is picked up. If this occurs OnPickUpItem will never be called and the item won't be equipped // even if it should be. Loop through the available items to determine if the ItemIdentifier should cause an item to be equipped. var allItems = m_Inventory.GetAllItems(); for (int i = 0; i < allItems.Count; ++i) { if (allItems[i].ItemIdentifier == itemIdentifier) { continue; } var itemActions = allItems[i].ItemActions; for (int j = 0; j < itemActions.Length; ++j) { var usableItem = itemActions[j] as IUsableItem; if (usableItem != null && usableItem.GetConsumableItemIdentifier() == itemIdentifier) { OnPickupItem(allItems[i], m_Inventory.GetItemIdentifierAmount(allItems[i].ItemIdentifier), immediatePickup, forceEquip); } } } } /// /// Starts equipping/unequipping to the specified ItemSet. /// /// The ItemSet to equip/unequip the items to. public void StartEquipUnequip(int itemSetIndex) { StartEquipUnequip(itemSetIndex, false); } /// /// Starts equipping/unequipping to the specified ItemSet. /// /// The ItemSet to equip/unequip the items to. /// Should the ability be force started? This will stop all abilities that would prevent EquipUnequip from starting. public void StartEquipUnequip(int itemSetIndex, bool forceEquipUnequip) { StartEquipUnequip(itemSetIndex, forceEquipUnequip, m_ImmediateEquipUnequip); } /// /// Starts equipping/unequipping to the specified ItemSet. /// /// The ItemSet to equip/unequip the items to. /// Should the ability be force started? This will stop all abilities that would prevent EquipUnequip from starting. /// Should the items be equipped or unequipped immediately? public void StartEquipUnequip(int itemSetIndex, bool forceEquipUnequip, bool immediateEquipUnequip) { if (IsActive && itemSetIndex == m_ActiveItemSetIndex && !immediateEquipUnequip) { return; } // Equip unequip normally will not start if use or reload is active. If the ability is forced then it should first stop the abilities. if (forceEquipUnequip) { var activeItemAbilities = m_CharacterLocomotion.ActiveItemAbilities; for (int i = m_CharacterLocomotion.ActiveItemAbilityCount - 1; i > -1; --i) { if (activeItemAbilities[i] is Use #if ULTIMATE_CHARACTER_CONTROLLER_SHOOTER || activeItemAbilities[i] is Reload #endif ) { m_CharacterLocomotion.TryStopAbility(activeItemAbilities[i], true); } } } m_ActiveItemSetIndex = itemSetIndex; m_ImmediateEquipUnequip = immediateEquipUnequip; StartAbility(); } /// /// Can the ability be started? /// /// True if the ability can be started. public override bool CanStartAbility() { // An attribute may prevent the ability from starting. if (!base.CanStartAbility()) { return false; } // If the InputIndex is -1 then the ability has been started manually. var itemSetIndex = m_ActiveItemSetIndex; if (InputIndex != -1) { // Item specified by button index. itemSetIndex = InputIndex; } // Don't start if the ItemSetIndex is the same or invalid. The check has already been performed for the manual start type. if (InputIndex != -1 && (itemSetIndex == m_ActiveItemSetIndex || !m_ItemSetManager.IsItemSetValid(m_ItemSetCategoryIndex, itemSetIndex, true))) { InputIndex = -1; return false; } // Don't start the ability if another ability is preventing the ability from being equipped. for (int i = 0; i < m_Inventory.SlotCount; ++i) { var itemIdentifier = m_ItemSetManager.GetEquipItemIdentifier(m_ItemSetCategoryIndex, itemSetIndex, i); if (itemIdentifier != null && !IsEquipAllowed(itemSetIndex, itemIdentifier.GetItemDefinition(), i)) { return false; } } // Don't try to equip to an invalid ItemSet index. if (itemSetIndex >= m_ItemSetManager.CategoryItemSets[m_ItemSetCategoryIndex].ItemSetList.Count) { return false; } m_ActiveItemSetIndex = itemSetIndex; return true; } /// /// The ability has started. /// protected override void AbilityStarted() { base.AbilityStarted(); m_StartEquipUnequip = true; m_StartEquipUnequipIndex = m_ActiveItemSetIndex; if (m_ImmediateEquipUnequip) { Update(); } } /// /// 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.ShouldStopActiveAbility(startingAbility)) { return true; } if (startingAbility is EquipScroll) { return true; } // The item can't be used or reloaded if it is being unequipped. if (startingAbility is Use #if ULTIMATE_CHARACTER_CONTROLLER_SHOOTER || startingAbility is Reload #endif ) { for (int i = 0; i < m_UnequipItems.Length; ++i) { if (m_UnequipItems[i] != null) { return true; } } } return false; } /// /// 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) { if (m_UnequipItems[slotID] != null) { return m_UnequipItemStateIndex; } if (m_CanEquip && m_EquipItems[slotID] != null) { return m_EquipItemStateIndex; } 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_UnequipItems[slotID] != null) { return m_UnequipItems[slotID].UnequipAnimatorAudioStateSet.GetItemSubstateIndex() + (m_Aiming ? m_AimItemSubstateIndexAddition : 0); } if (m_CanEquip && m_EquipItems[slotID] != null) { return m_EquipItems[slotID].EquipAnimatorAudioStateSet.GetItemSubstateIndex() + (m_Aiming ? m_AimItemSubstateIndexAddition : 0); } return -1; } /// /// Updates the ability /// public override void Update() { base.Update(); if (m_StartEquipUnequip) { m_StartEquipUnequip = false; // The ActiveItemIndexSetIndex may have been changed due to the EquipUnequipAction above. m_ActiveItemSetIndex = m_StartEquipUnequipIndex; // Equip any unequipped items which are within the ItemSetIndex and belong to the specified category. // Unequip any items which are equipped and are not within the ItemSetIndex and belong to the specified category. var unequip = false; var equip = false; for (int i = 0; i < m_Inventory.SlotCount; ++i) { // Stop any equips/unequips that have already started. ForceEquipUnequip(i, true); m_EquipUnequipActions[i] = EquipUnequipAction.Inactive; // Determine the item that is currently equipped and the item that should be equipped. Item currentItem = null, targetItem = null; var currentItemIdentifier = m_ItemSetManager.GetEquipItemIdentifier(m_ItemSetCategoryIndex, i); if (currentItemIdentifier != null) { currentItem = m_Inventory.GetItem(currentItemIdentifier, i); } var targetItemIdentifier = m_ItemSetManager.GetEquipItemIdentifier(m_ItemSetCategoryIndex, m_ActiveItemSetIndex, i); var skipEquip = false; // If the target ItemIdentifier doesn't equal the equip ItemIdentifier from the ItemSetManager then the equip ItemIdentifier is equipped in a different category. // Only the lower categories should be searched because they have a higher priority. for (int j = 0; j < m_ItemSetCategoryIndex; ++j) { var equipItemIdentifier = m_ItemSetManager.GetEquipItemIdentifier(j, i); if (equipItemIdentifier != null && equipItemIdentifier != targetItemIdentifier) { skipEquip = true; break; } } if (skipEquip) { continue; } if (targetItemIdentifier != null && m_Inventory.GetItemIdentifierAmount(targetItemIdentifier) > 0) { targetItem = m_Inventory.GetItem(targetItemIdentifier, i); } // Nothing needs to be done if the current item is equal to the item that should be equipped. The target item may not be active if // the ability is quickly moving through the items. if (currentItem == targetItem && (targetItem == null || (targetItem != null && targetItem.IsActive()))) { continue; } // ForceEquipUnequip may be unequipping the item. if (m_UnequipItems[i] != null) { unequip = true; } else if (currentItem != targetItem && currentItem != null && currentItem.IsActive() && m_ItemSetManager.IsCategoryMember(currentItem.ItemIdentifier.GetItemDefinition(), m_ItemSetCategoryIndex)) { // The item first needs to be unequipped before another item can be equipped. m_UnequipItems[i] = currentItem; m_UnequippingItems[i] = true; unequip = true; currentItem.StartUnequip(m_ImmediateEquipUnequip); } if (targetItemIdentifier != null && m_ItemSetManager.IsCategoryMember(targetItemIdentifier.GetItemDefinition(), m_ItemSetCategoryIndex)) { m_EquipItems[i] = targetItem; // Wait to equip until unequip is complete. m_EquippingItems[i] = false; equip = true; } } // The ability can be stopped if no action needs to be performed. if (unequip || equip) { EventHandler.ExecuteEvent(this, "OnEquipUnequipItemSetIndexChange", m_ActiveItemSetIndex); m_ItemSetManager.UpdateNextItemSet(m_ItemSetCategoryIndex, m_ActiveItemSetIndex); m_CanEquip = !unequip; // The ability can equip as soon as the unequips are complete. var canEqup = m_CanEquip; // The can equp status may change if an item is unequipped immediately. Remember the initial value so the item isn't equipped twice. // Wait to schedule the events until after all of the equip/unequip items have been determined. Otherwise if the event is fired immediate (with a duration // of 0) then the ability could end before all slots have a chance to determine if any items need to be equipped/unequipped. for (int i = 0; i < m_Inventory.SlotCount; ++i) { if (m_UnequipItems[i] == null && m_EquipItems[i] == null) { continue; } if (m_UnequipItems[i] != null) { if (!m_UnequipItems[i].UnequipEvent.WaitForAnimationEvent || m_ImmediateEquipUnequip) { var duration = m_ImmediateEquipUnequip ? 0 : m_UnequipItems[i].UnequipEvent.Duration; m_ItemEvents[i] = Scheduler.ScheduleFixed(duration, ItemUnequip, i, duration == 0); } } else if (m_EquipItems[i] != null && canEqup) { m_EquippingItems[i] = true; m_EquipItems[i].WillEquip(); EventHandler.ExecuteEvent(m_GameObject, "OnAbilityWillEquipItem", m_EquipItems[i], i); m_EquipItems[i].StartEquip(m_ImmediateEquipUnequip); if (!m_EquipItems[i].EquipEvent.WaitForAnimationEvent || m_ImmediateEquipUnequip) { var duration = m_ImmediateEquipUnequip ? 0 : m_EquipItems[i].EquipEvent.Duration; m_ItemEvents[i] = Scheduler.ScheduleFixed(duration, ItemEquip, i, duration == 0); } } } m_CharacterLocomotion.UpdateItemAbilityAnimatorParameters(); } else { EventHandler.ExecuteEvent(this, "OnEquipUnequipItemSetIndexChange", m_ActiveItemSetIndex); StopAbility(); } return; } // The equip/unequip actions may occur during an animation event which will occur after the animator has updated. To prevent the animator from being out // of sync with the controller for a frame the ability should be updated within the Update loop. for (int i = 0; i < m_EquipUnequipActions.Length; ++i) { if (m_EquipUnequipActions[i] == EquipUnequipAction.Inactive) { continue; } switch (m_EquipUnequipActions[i]) { case EquipUnequipAction.Unequip: ItemUnequip(i, true); break; case EquipUnequipAction.UnequipComplete: ItemUnequipComplete(i, true); break; case EquipUnequipAction.Equip: ItemEquip(i, true); break; case EquipUnequipAction.EquipComplete: ItemEquipComplete(i, true); break; } } } /// /// Equips and unequips all of the pending items. /// /// The slot ID to equip or unequip the item at. /// Is the ability being started? private void ForceEquipUnequip(int slotID, bool startAbility) { if (m_EquipItems[slotID] != null || m_UnequipItems[slotID] != null) { Scheduler.Cancel(m_ItemEvents[slotID]); var startUnequip = false; // If the item is no longer being equipped then it is unequipped. if (startAbility) { if (m_EquipItems[slotID] != null && m_EquippingItems[slotID]) { m_EquipItems[slotID].StartUnequip(true); m_Inventory.UnequipItem(m_EquipItems[slotID].ItemIdentifier, slotID); EventHandler.ExecuteEvent(m_GameObject, "OnAbilityUnequipItemComplete", m_EquipItems[slotID], slotID); } else if (m_EquipItems[slotID] != null && m_CanEquip) { // Don't unequip the item currently being equipped if the ItemSet uses it. var equipItemIdentifier = m_ItemSetManager.GetEquipItemIdentifier(m_ItemSetCategoryIndex, m_ActiveItemSetIndex, slotID); if (equipItemIdentifier == null || m_EquipItems[slotID].ItemIdentifier != equipItemIdentifier) { if (m_UnequipItems[slotID] != null) { m_Inventory.UnequipItem(m_UnequipItems[slotID].ItemIdentifier, slotID); EventHandler.ExecuteEvent(m_GameObject, "OnAbilityUnequipItemComplete", m_UnequipItems[slotID], slotID); } // The equipping item should be unequipped smoothly. m_UnequipItems[slotID] = m_EquipItems[slotID]; m_UnequippingItems[slotID] = true; m_UnequipItems[slotID].StartUnequip(m_ImmediateEquipUnequip); startUnequip = true; } } } else { // Equip the equip item immediately if the ability is being stopped to prevent the items from getting into an invalid state. if (m_EquipItems[slotID] != null) { m_EquipItems[slotID].WillEquip(); EventHandler.ExecuteEvent(m_GameObject, "OnAbilityWillEquipItem", m_EquipItems[slotID], slotID); m_EquipItems[slotID].StartEquip(m_ImmediateEquipUnequip); m_Inventory.EquipItem(m_EquipItems[slotID].ItemIdentifier, slotID, m_ImmediateEquipUnequip); } } m_EquipItems[slotID] = null; m_EquippingItems[slotID] = false; if (!startUnequip && m_UnequipItems[slotID] != null) { if (m_UnequippingItems[slotID] && startAbility) { // Reset the unequpping item back to being equip if the item set uses the same item again. If a different item should be equipped // then the ability will unequip it through AbilityStarted. var itemIdentifier = m_ItemSetManager.GetEquipItemIdentifier(m_ItemSetCategoryIndex, m_ActiveItemSetIndex, slotID); if (itemIdentifier == m_UnequipItems[slotID].ItemIdentifier) { m_UnequipItems[slotID].WillEquip(); EventHandler.ExecuteEvent(m_GameObject, "OnAbilityWillEquipItem", m_UnequipItems[slotID], slotID); m_UnequipItems[slotID].StartEquip(m_ImmediateEquipUnequip); m_Inventory.EquipItem(m_UnequipItems[slotID].ItemIdentifier, slotID, true); m_UnequipItems[slotID] = null; m_UnequippingItems[slotID] = false; } else if (m_EquipUnequipActions[slotID] == EquipUnequipAction.Unequip) { m_Inventory.UnequipItem(m_UnequipItems[slotID].ItemIdentifier, slotID); EventHandler.ExecuteEvent(m_GameObject, "OnAbilityUnequipItemComplete", m_UnequipItems[slotID], slotID); m_UnequipItems[slotID] = null; m_UnequippingItems[slotID] = false; } } else { m_UnequipItems[slotID].StartUnequip(true); m_Inventory.UnequipItem(m_UnequipItems[slotID].ItemIdentifier, slotID); EventHandler.ExecuteEvent(m_GameObject, "OnAbilityUnequipItemComplete", m_UnequipItems[slotID], slotID); m_UnequipItems[slotID] = null; m_UnequippingItems[slotID] = false; } } m_CharacterLocomotion.UpdateItemAbilityAnimatorParameters(); } } /// /// The Aim ability has started or stopped. /// /// Has the Aim ability started? /// Was the ability started from input? private void OnAim(bool aim, bool inputStart) { if (!inputStart) { return; } m_Aiming = aim; } /// /// The Animation Event has unequipped all of the items. /// private void OnItemUnequip() { for (int i = 0; i < m_UnequipItems.Length; ++i) { if (m_UnequipItems[i] != null) { ItemUnequip(i, false); } } } /// /// The Animation Event has unequipped the first item slot. /// private void OnItemUnequipFirstSlot() { ItemUnequip(0, false); } /// /// The Animation Event has unequipped the second item slot. /// private void OnItemUnequipSecondSlot() { ItemUnequip(1, false); } /// /// The Animation Event has unequipped the third item slot. /// private void OnItemUnequipThirdSlot() { ItemUnequip(2, false); } /// /// The animation is done unequipping the item. /// /// The slot ID of the item that was unequipped. /// Can the item be updated? If false the status enum will be set and the item will be updated within the Update loop. private void ItemUnequip(int slotID, bool canUpdate) { var unequipItem = m_UnequipItems[slotID]; if (unequipItem == null || !m_UnequippingItems[slotID]) { return; } // If the item can't be updated then the event should wait until the Update loop. This ensures items are updated in the proper order. if (!canUpdate) { m_EquipUnequipActions[slotID] = EquipUnequipAction.Unequip; return; } m_EquipUnequipActions[slotID] = EquipUnequipAction.Inactive; // Clear out the unequipped item and notify those interested. m_Inventory.UnequipItem(slotID); m_UnequippingItems[slotID] = false; if (!unequipItem.UnequipCompleteEvent.WaitForAnimationEvent) { var duration = m_ImmediateEquipUnequip ? 0 : unequipItem.UnequipCompleteEvent.Duration; m_ItemEvents[slotID] = Scheduler.ScheduleFixed(duration, ItemUnequipComplete, slotID, duration == 0); } } /// /// The unequip animation has completed for all of the items. /// private void OnItemUnequipComplete() { for (int i = 0; i < m_UnequipItems.Length; ++i) { if (m_UnequipItems[i] != null) { ItemUnequipComplete(i, false); } } } /// /// The unequip animation has completed for the first item slot. /// private void OnItemUnequipCompleteFirstSlot() { ItemUnequipComplete(0, false); } /// /// The unequip animation has completed for the second item slot. /// private void OnItemUnequipCompleteSecondSlot() { ItemUnequipComplete(1, false); } /// /// The unequip animation has completed for the third item slot. /// private void OnItemUnequipCompleteThirdSlot() { ItemUnequipComplete(2, false); } /// /// The animation is done unequipping the item. /// /// The slot ID of the item that was unequipped. /// Can the item be updated? If false the status enum will be set and the item will be updated within the Update loop. private void ItemUnequipComplete(int slotID, bool canUpdate) { var unequipItem = m_UnequipItems[slotID]; if (unequipItem == null) { return; } // If the item can't be updated then the event should wait until the Update loop. This ensures items are updated in the proper order. if (!canUpdate) { m_EquipUnequipActions[slotID] = EquipUnequipAction.UnequipComplete; return; } m_EquipUnequipActions[slotID] = EquipUnequipAction.Inactive; m_UnequipItems[slotID] = null; Scheduler.Cancel(m_ItemEvents[slotID]); EventHandler.ExecuteEvent(m_GameObject, "OnAbilityUnequipItemComplete", unequipItem, slotID); m_CharacterLocomotion.UpdateItemAbilityAnimatorParameters(); // The ability shouldn't start to equip or stop until all unequip items have been unequipped. for (int i = 0; i < m_UnequipItems.Length; ++i) { if (m_UnequipItems[i] != null) { return; } } // Start equipping the next item if all of the items have been unequipped. The next item doesn't have to be in the same slot // because the item could have been waiting for the item in the current slot to be unequipped. m_CanEquip = true; var stopAbility = true; for (int i = 0; i < m_EquipItems.Length; ++i) { if (m_EquipItems[i] != null && !m_EquippingItems[i]) { stopAbility = false; m_EquippingItems[i] = true; m_EquipItems[i].WillEquip(); EventHandler.ExecuteEvent(m_GameObject, "OnAbilityWillEquipItem", m_EquipItems[i], i); // EquipItems[i] may be null if the item has an equip duration of 0. if (m_EquipItems[i] != null && (m_ImmediateEquipUnequip || !m_EquipItems[i].EquipEvent.WaitForAnimationEvent)) { var duration = m_ImmediateEquipUnequip ? 0 : m_EquipItems[i].EquipEvent.Duration; m_ItemEvents[i] = Scheduler.ScheduleFixed(duration, ItemEquip, i, duration == 0); } } } // Stop the ability if no items need to be unequipped/equipped. if (stopAbility) { TryStopEquipUnequipAbility(); } } /// /// An item was just equipped. Verify that the unequip item in the specified slot still needs to be unequipped. /// /// The index that the item was equipped. /// The slot ID of the equipped item. private void OnVerifyUnequipItem(int categoryIndex, int slotID) { if (m_UnequipItems[slotID] == null || m_ItemSetCategoryIndex == categoryIndex) { return; } // Don't unequip the item if it should be equipped. if (m_UnequipItems[slotID].ItemIdentifier == m_ItemSetManager.GetEquipItemIdentifier(slotID)) { m_UnequipItems[slotID] = null; m_UnequippingItems[slotID] = false; m_CharacterLocomotion.UpdateItemAbilityAnimatorParameters(); } } /// /// The Animation Event has equipped all of the items. /// private void OnItemEquip() { for (int i = 0; i < m_EquipItems.Length; ++i) { if (m_EquipItems[i] != null) { ItemEquip(i, false); } } } /// /// The Animation Event has equipped the first item slot. /// private void OnItemEquipFirstSlot() { ItemEquip(0, false); } /// /// The Animation Event has equipped the second item slot. /// private void OnItemEquipSecondSlot() { ItemEquip(1, false); } /// /// The Animation Event has equipped the third item slot. /// private void OnItemEquipThirdSlot() { ItemEquip(2, false); } /// /// The animation is done equipping the item. /// /// The slot that the unequipped item belongs to. /// Can the item be updated? If false the status enum will be set and the item will be updated within the Update loop. private void ItemEquip(int slotID, bool canUpdate) { if (!m_CanEquip) { return; } var equipItem = m_EquipItems[slotID]; if (equipItem == null || !m_EquippingItems[slotID]) { return; } // If the item can't be updated then the event should wait until the Update loop. This ensures items are updated in the proper order. if (!canUpdate) { m_EquipUnequipActions[slotID] = EquipUnequipAction.Equip; return; } m_EquipUnequipActions[slotID] = EquipUnequipAction.Inactive; // Clear out the equipped item and notify those interested. m_EquippingItems[slotID] = false; m_CharacterLocomotion.UpdateItemAbilityAnimatorParameters(); equipItem.StartEquip(m_ImmediateEquipUnequip); m_Inventory.EquipItem(equipItem.ItemIdentifier, slotID, m_ImmediateEquipUnequip); // The new ItemSet is active as soon as the new items are equipped. var equip = false; for (int i = 0; i < m_EquipItems.Length; ++i) { if (m_EquippingItems[slotID]) { equip = true; break; } } if (!equip) { m_ItemSetManager.UpdateActiveItemSet(m_ItemSetCategoryIndex, m_ActiveItemSetIndex); // Throughout the duration that the item was equipped a different category may have started to equip. // Verify that the unequip item should still be unequipped. EventHandler.ExecuteEvent(m_GameObject, "OnEquipUnequipVerifyUnequipItem", m_ItemSetCategoryIndex, slotID); } if (!equipItem.EquipCompleteEvent.WaitForAnimationEvent || m_ImmediateEquipUnequip) { var duration = m_ImmediateEquipUnequip ? 0 : equipItem.EquipCompleteEvent.Duration; m_ItemEvents[slotID] = Scheduler.ScheduleFixed(duration, ItemEquipComplete, slotID, duration == 0); } } /// /// The equip animation has completed for all of the items. /// private void OnItemEquipComplete() { for (int i = 0; i < m_EquipItems.Length; ++i) { if (m_EquipItems[i] != null) { ItemEquipComplete(i, false); } } } /// /// The equip animation has completed for the first item slot. /// private void OnItemEquipCompleteFirstSlot() { ItemEquipComplete(0, false); } /// /// The equip animation has completed for the second item slot. /// private void OnItemEquipCompleteSecondSlot() { ItemEquipComplete(1, false); } /// /// The equip animation has completed for the third item slot. /// private void OnItemEquipCompleteThirdSlot() { ItemEquipComplete(2, false); } /// /// The animation is done equipping the item. /// /// The slot ID of the item that was unequipped. /// Can the item be updated? If false the status enum will be set and the item will be updated within the Update loop. private void ItemEquipComplete(int slotID, bool canUpdate) { if (!m_CanEquip) { return; } var equipItem = m_EquipItems[slotID]; if (equipItem == null) { return; } // If the item can't be updated then the event should wait until the Update loop. This ensures items are updated in the proper order. if (!canUpdate) { m_EquipUnequipActions[slotID] = EquipUnequipAction.EquipComplete; return; } m_EquipUnequipActions[slotID] = EquipUnequipAction.Inactive; m_EquipItems[slotID] = null; Scheduler.Cancel(m_ItemEvents[slotID]); TryStopEquipUnequipAbility(); } /// /// The animation event has indicated that the ability should stop. /// private void TryStopEquipUnequipAbility() { // Don't stop the ability unless all slots have been equipped/unequipped. var stopAbility = true; for (int i = 0; i < m_EquipItems.Length; ++i) { if (m_EquipItems[i] != null || m_UnequipItems[i] != null) { stopAbility = false; break; } } if (stopAbility) { StopAbility(); } } /// /// The ability has stopped running. /// /// Was the ability force stopped? protected override void AbilityStopped(bool force) { base.AbilityStopped(force); m_ImmediateEquipUnequip = false; if (force) { // If the ability was force stopped then it won't have a chance at finishing the item equip/unequip. Don't even try. for (int i = 0; i < m_Inventory.SlotCount; ++i) { ForceEquipUnequip(i, false); } } m_CharacterLocomotion.UpdateItemAbilityAnimatorParameters(); m_ItemSetManager.UpdateActiveItemSet(m_ItemSetCategoryIndex, m_ActiveItemSetIndex); } /// /// The ItemSetManager has changed the active ItemSet. /// /// The index of the category that changed. /// The updated active ItemSet index value. protected void OnItemSetIndexChange(int categoryIndex, int itemSetIndex) { if (m_ItemSetCategoryIndex != categoryIndex) { return; } m_ActiveItemSetIndex = itemSetIndex; } /// /// An item has been removed. /// /// The item that was removed. /// The slot that the item was removed from. private void OnRemoveItem(Item item, int slotID) { if (m_ItemSetManager.IsCategoryMember(item.ItemDefinition, m_ItemSetCategoryIndex)) { // The item may not be included in the active ItemSet. if (m_ActiveItemSetIndex == m_ItemSetManager.GetItemSetIndex(item, m_ItemSetCategoryIndex, false)) { var prevImmediateEquipUnequip = m_ImmediateEquipUnequip; // If the ItemSet contains an item that isn't being removed then the character should animate moving to the next ItemSet. for (int i = 0; i < m_Inventory.SlotCount; ++i) { var equipItemIdentifier = m_ItemSetManager.GetEquipItemIdentifier(m_ItemSetCategoryIndex, m_ActiveItemSetIndex, i); if (equipItemIdentifier != null && equipItemIdentifier != item.ItemIdentifier) { m_ImmediateEquipUnequip = false; break; } } StartEquipUnequip(m_ItemSetManager.GetDefaultItemSetIndex(m_ItemSetCategoryIndex), true, true); m_ImmediateEquipUnequip = prevImmediateEquipUnequip; } } } /// /// The character has died. /// /// The position of the force. /// The amount of force which killed the character. /// The GameObject that killed the character. private void OnDeath(Vector3 position, Vector3 force, GameObject attacker) { m_PrevActiveItemSetIndex = m_ActiveItemSetIndex; // Don't immediately unequip if in first person view to allow the arms move off the screen. StartEquipUnequip(-1, false, !m_CharacterLocomotion.FirstPersonPerspective); if (m_Inventory.RemoveAllOnDeath) { m_InventoryAmount.Clear(); } } /// /// The inventory has reloaded after respawning. /// private void OnInventoryRespawned() { // If the items weren't removed upon death then the ability should equip what they previously had equipped. if (!m_Inventory.RemoveAllOnDeath && m_PrevActiveItemSetIndex != -1) { StartEquipUnequip(m_PrevActiveItemSetIndex, true, true); // No equip animations need to play - it should be like the character started fresh. m_CharacterLocomotion.UpdateItemAbilityAnimatorParameters(); EventHandler.ExecuteEvent(m_GameObject, "OnCharacterSnapAnimator"); } } /// /// Called when the character is destroyed. /// public override void OnDestroy() { base.OnDestroy(); EventHandler.UnregisterEvent(m_GameObject, "OnItemPickupStartPickup", WillStartPickup); EventHandler.UnregisterEvent(m_GameObject, "OnItemPickupStopPickup", StopPickup); EventHandler.UnregisterEvent(m_GameObject, "OnInventoryPickupItem", OnPickupItem); EventHandler.UnregisterEvent(m_GameObject, "OnInventoryPickupItemIdentifier", OnPickupItemIdentifier); EventHandler.UnregisterEvent(m_GameObject, "OnItemSetIndexChange", OnItemSetIndexChange); EventHandler.UnregisterEvent(m_GameObject, "OnAnimatorItemUnequip", OnItemUnequip); EventHandler.UnregisterEvent(m_GameObject, "OnAnimatorItemUnequipComplete", OnItemUnequipComplete); EventHandler.UnregisterEvent(m_GameObject, "OnAnimatorItemUnequipFirstSlot", OnItemUnequipFirstSlot); EventHandler.UnregisterEvent(m_GameObject, "OnAnimatorItemUnequipCompleteFirstSlot", OnItemUnequipCompleteFirstSlot); EventHandler.UnregisterEvent(m_GameObject, "OnAnimatorItemUnequipSecondSlot", OnItemUnequipSecondSlot); EventHandler.UnregisterEvent(m_GameObject, "OnAnimatorItemUnequipCompleteSecondSlot", OnItemUnequipCompleteSecondSlot); EventHandler.UnregisterEvent(m_GameObject, "OnAnimatorItemUnequipThirdSlot", OnItemUnequipThirdSlot); EventHandler.UnregisterEvent(m_GameObject, "OnAnimatorItemUnequipCompleteThirdSlot", OnItemUnequipCompleteThirdSlot); EventHandler.UnregisterEvent(m_GameObject, "OnEquipUnequipVerifyUnequip", OnVerifyUnequipItem); EventHandler.UnregisterEvent(m_GameObject, "OnAnimatorItemEquip", OnItemEquip); EventHandler.UnregisterEvent(m_GameObject, "OnAnimatorItemEquipComplete", OnItemEquipComplete); EventHandler.UnregisterEvent(m_GameObject, "OnAnimatorItemEquipFirstSlot", OnItemEquipFirstSlot); EventHandler.UnregisterEvent(m_GameObject, "OnAnimatorItemEquipCompleteFirstSlot", OnItemEquipCompleteFirstSlot); EventHandler.UnregisterEvent(m_GameObject, "OnAnimatorItemEquipSecondSlot", OnItemEquipSecondSlot); EventHandler.UnregisterEvent(m_GameObject, "OnAnimatorItemEquipCompleteSecondSlot", OnItemEquipCompleteSecondSlot); EventHandler.UnregisterEvent(m_GameObject, "OnAnimatorItemEquipThirdSlot", OnItemEquipThirdSlot); EventHandler.UnregisterEvent(m_GameObject, "OnAnimatorItemEquipCompleteThirdSlot", OnItemEquipCompleteThirdSlot); EventHandler.UnregisterEvent(m_GameObject, "OnInventoryRemoveItem", OnRemoveItem); EventHandler.UnregisterEvent(m_GameObject, "OnAimAbilityStart", OnAim); EventHandler.UnregisterEvent(m_GameObject, "OnDeath", OnDeath); EventHandler.UnregisterEvent(m_GameObject, "OnInventoryRespawned", OnInventoryRespawned); } } }