[Solved][Issue] Equip Unequip ability throwing null reference - Item Category ID comparison

DarkLiKally

New member
Hello,

like discussed on Discord I'll move to the forums with that issue.

Environment:
Unity 2021.2.7f1
UCC 2.4.4
UIS 1.2.4
Latest downloadable integration package
I followed the integration guides - both video and text.

My Inventory database has the following categories (simplified to the relevant ones):
EquippableInSlot (parent)
EquippableInSlotSingle (child, for single slot)
EquippableInSlotMulti (child, for multi slot)
WeaponOneHand (child of single)
WeaponTwoHand (child of single)

I also did the reserialize category ID trick mentioned in the docs on the abilities of my UCC locomotion.

My Inventory Item Set Manager is configured as follows:
1641410842523.png

I'm trying to equip a two handed weapon, that item definition is in category WeaponTwoHand.
The Character Inventory Bridge is throwing the following error:

Code:
NullReferenceException: Object reference not set to an instance of an object
Opsive.UltimateCharacterController.Inventory.ItemSet.StateChange () (at Assets/Opsive/UltimateCharacterController/Scripts/Inventory/ItemSet.cs:179)
Opsive.UltimateCharacterController.Integrations.UltimateInventorySystem.InventoryItemSetManager.StateChange () (at Assets/Opsive/UltimateCharacterController/Integrations/UltimateInventorySystem/Scripts/ItemSet/InventoryItemSetManager.cs:733)
Opsive.UltimateCharacterController.Integrations.UltimateInventorySystem.BridgeEquippableProcessing.UpdateItemSetItems (System.Boolean equip) (at Assets/Opsive/UltimateCharacterController/Integrations/UltimateInventorySystem/Scripts/BridgeEquippableProcessing.cs:478)
Opsive.UltimateCharacterController.Integrations.UltimateInventorySystem.BridgeEquippableProcessing.OnItemAddedToEquippable (Opsive.UltimateInventorySystem.Core.DataStructures.ItemInfo itemInfo, Opsive.UltimateInventorySystem.Core.DataStructures.ItemStack addedItemStack) (at Assets/Opsive/UltimateCharacterController/Integrations/UltimateInventorySystem/Scripts/BridgeEquippableProcessing.cs:202)
Opsive.UltimateCharacterController.Integrations.UltimateInventorySystem.BridgeEquippableProcessing.OnAddItemToInventory (Opsive.UltimateInventorySystem.Core.DataStructures.ItemInfo itemInfo, Opsive.UltimateInventorySystem.Core.DataStructures.ItemStack addedItemStack) (at Assets/Opsive/UltimateCharacterController/Integrations/UltimateInventorySystem/Scripts/BridgeEquippableProcessing.cs:782)
Opsive.Shared.Events.InvokableAction`2[T1,T2].Invoke (T1 arg1, T2 arg2) (at <27da9e1afec54f2fb2a11d46a234f9df>:0)
Opsive.Shared.Events.EventHandler.ExecuteEvent[T1,T2] (System.Object obj, System.String eventName, T1 arg1, T2 arg2) (at <27da9e1afec54f2fb2a11d46a234f9df>:0)
Opsive.UltimateInventorySystem.Core.InventoryCollections.ItemCollection.NotifyAdd (Opsive.UltimateInventorySystem.Core.DataStructures.ItemInfo itemInfo, Opsive.UltimateInventorySystem.Core.DataStructures.ItemStack addedItemStack) (at Assets/Opsive/UltimateInventorySystem/Scripts/Core/InventoryCollections/ItemCollection.cs:430)
Opsive.UltimateInventorySystem.Core.InventoryCollections.ItemSlotCollection.SetItemAmount (Opsive.UltimateInventorySystem.Core.DataStructures.ItemInfo itemInfo, System.Int32 slotIndex, System.Boolean removePreviousItem) (at Assets/Opsive/UltimateInventorySystem/Scripts/Core/InventoryCollections/ItemSlotCollection.cs:276)
Opsive.UltimateInventorySystem.Core.InventoryCollections.ItemSlotCollection.AddItemInternal (Opsive.UltimateInventorySystem.Core.DataStructures.ItemInfo itemInfo, System.Int32 slotIndex) (at Assets/Opsive/UltimateInventorySystem/Scripts/Core/InventoryCollections/ItemSlotCollection.cs:236)
Opsive.UltimateInventorySystem.Core.InventoryCollections.ItemSlotCollection.AddItem (Opsive.UltimateInventorySystem.Core.DataStructures.ItemInfo itemInfo, System.Int32 slotIndex) (at Assets/Opsive/UltimateInventorySystem/Scripts/Core/InventoryCollections/ItemSlotCollection.cs:200)
Opsive.UltimateCharacterController.Integrations.UltimateInventorySystem.CharacterInventoryBridge.MoveItemToEquippable (Opsive.UltimateInventorySystem.Core.DataStructures.ItemInfo itemInfo, System.Int32 equippableCollectionIndex, System.Int32 slotIndex) (at Assets/Opsive/UltimateCharacterController/Integrations/UltimateInventorySystem/Scripts/CharacterInventoryBridge.cs:451)
Opsive.UltimateCharacterController.Integrations.UltimateInventorySystem.CharacterInventoryBridge.MoveEquip (Opsive.UltimateInventorySystem.Core.DataStructures.ItemInfo itemInfo, System.Int32 equippableItemCollectionSet, System.Int32 slotID, System.Boolean equip) (at Assets/Opsive/UltimateCharacterController/Integrations/UltimateInventorySystem/Scripts/CharacterInventoryBridge.cs:532)
Opsive.UltimateCharacterController.Integrations.UltimateInventorySystem.CharacterInventoryBridge.MoveEquip (Opsive.UltimateInventorySystem.Core.DataStructures.ItemInfo itemInfo, System.Boolean equip) (at Assets/Opsive/UltimateCharacterController/Integrations/UltimateInventorySystem/Scripts/CharacterInventoryBridge.cs:518)
Opsive.UltimateCharacterController.Integrations.UltimateInventorySystem.InventoryItemPickup.DoItemIdentifierPickupInternal (UnityEngine.GameObject character, Opsive.UltimateCharacterController.Inventory.InventoryBase inventory, System.Int32 slotID, System.Boolean immediatePickup, System.Boolean forceEquip) (at Assets/Opsive/UltimateCharacterController/Integrations/UltimateInventorySystem/Scripts/Pickup/InventoryItemPickup.cs:111)
Opsive.UltimateCharacterController.Objects.CharacterAssist.ItemPickupBase.DoItemIdentifierPickup (UnityEngine.GameObject character, Opsive.UltimateCharacterController.Inventory.InventoryBase inventory, System.Int32 slotID, System.Boolean immediatePickup, System.Boolean forceEquip) (at Assets/Opsive/UltimateCharacterController/Scripts/Objects/CharacterAssist/ItemPickupBase.cs:267)
Opsive.UltimateCharacterController.Objects.CharacterAssist.ItemPickupBase.DoItemPickup (UnityEngine.GameObject character, Opsive.UltimateCharacterController.Inventory.InventoryBase inventory, System.Int32 slotID, System.Boolean immediatePickup, System.Boolean pickupItemIdentifier) (at Assets/Opsive/UltimateCharacterController/Scripts/Objects/CharacterAssist/ItemPickupBase.cs:229)
Opsive.UltimateCharacterController.Objects.CharacterAssist.ItemPickupBase.TryItemPickup (Opsive.UltimateCharacterController.Inventory.InventoryBase inventory, System.Int32 slotID) (at Assets/Opsive/UltimateCharacterController/Scripts/Objects/CharacterAssist/ItemPickupBase.cs:124)
Opsive.UltimateCharacterController.Objects.CharacterAssist.ItemPickupBase.TriggerEnter (UnityEngine.GameObject other, System.Int32 slotID) (at Assets/Opsive/UltimateCharacterController/Scripts/Objects/CharacterAssist/ItemPickupBase.cs:113)
Opsive.UltimateCharacterController.Objects.CharacterAssist.ItemPickupBase.DoPickup (UnityEngine.GameObject target) (at Assets/Opsive/UltimateCharacterController/Scripts/Objects/CharacterAssist/ItemPickupBase.cs:91)
Opsive.UltimateCharacterController.Objects.CharacterAssist.ItemPickupBase.TriggerEnter (UnityEngine.GameObject other) (at Assets/Opsive/UltimateCharacterController/Scripts/Objects/CharacterAssist/ItemPickupBase.cs:82)
Opsive.UltimateCharacterController.Objects.CharacterAssist.ObjectPickup.OnTriggerEnter (UnityEngine.Collider other) (at Assets/Opsive/UltimateCharacterController/Scripts/Objects/CharacterAssist/ObjectPickup.cs:165)

When I jump into my debugger there is this line at
Assets/Opsive/UltimateCharacterController/Scripts/Inventory/ItemSet.cs:124
that is comparing the two marked IDs:
1641411086935.png
The loop only has one entry since I have only one "Equip Unequip" ability on my UCC for now.
Since it is comparing those two different IDs the "m_EquipUnequip" is still null after the loop.
Because of this the line
Assets/Opsive/UltimateCharacterController/Scripts/Inventory/ItemSet.cs:179
from the stack trace above is throwing that null reference exception.

The ID 106.... is the actual ID of my EquippableInSlot category.
I have no idea where this "Equippable" category with the 35.... ID is coming from.
I have only one `ItemSetManagerBase` on my GO which is the Inventory Item Set Manager above. I also made sure that I have the correct inventory database selected everywhere (removed all demo, empty and other databases from my project).
When I edit the abilities on my UCC for items (i.e. the Equip Unequip one) I also get only the "Equippable" one as suggestion - which does not exist in my databse.

Thanks for your investigation and help!

Greetings,
DLK

-------
EDIT:

I somehow made it pass that code spot - I recreated some things removed and re-added some components and so on. It seems there was some old serialized data in some component.
I renamed a category of the mentioned ones before so maybe that was causing the issue? but why it got a new ID there too?
I removed the Inventory Item Set Manager and the rules scriptable object and recreated them.
Now the Ability on UCC does not offer me the option for selecting the item category anymore - the select box just disappeared.

The above issue was definitely not resolved a clean way now - now the code is getting the m_EquipUnequip set and receiving the correct category IDs but I get a new error now:
Assets/Opsive/UltimateCharacterController/Scripts/Character/Abilities/Items/EquipUnequip.cs:413
m_ItemSetCategoryIndex = -1
which results in an Index ouf of range exception.
 
Last edited:
You are right there is a problem with the abilities inspector. It caches the categories and doesn't update when the available categories change. I had to close and reopen Unity to make the dropdown update with the correct categories.

The reason the cached category do not change is because the ItemSetManager is initialized once, then it skips initialization to boost perfromance in the Editor (the Ability editor still uses IMGUI so it draws every frame). I changed the InventoryItemSetManager such that it will initialize if the category sets if it doesn't match the rules even if the initialize isn't forced.

So within the InventoryItemSetManager change/add the following functions:

C#:
/// <summary>
/// Initializes the ItemSetManager.
/// </summary>
/// <param name="force">Should the ItemSet be force initialized?</param>
public override void Initialize(bool force)
{
    if (m_Initialized && !force && CategoryItemSetMatchCategoryRules()) {
        return;
    }
    m_TemporaryItemSetStateList = new List<ItemSetStateInfo>();
    m_ItemSetToRuleMap = new Dictionary<ItemSet, IItemSetRule>();
    m_RuleToItemSetsMap = new Dictionary<IItemSetRule, List<ItemSet>>();
    Initialize(m_ItemSetRulesObject);
}
/// <summary>
/// Check if the Category Item Set categories matches the category rules.
/// </summary>
/// <returns></returns>
public bool CategoryItemSetMatchCategoryRules()
{
    if (m_ItemSetRulesObject == null && m_CategoryItemSets == null) {
        return true;
    }
    
    if (m_ItemSetRulesObject == null || m_CategoryItemSets == null) {
        return false;
    }
    if (m_ItemSetRulesObject.CategoryItemSetRules.Length != m_CategoryItemSets.Length) {
        return false;
    }
    for (int i = 0; i < m_CategoryItemSets.Length; i++) {
        if (!ReferenceEquals(m_CategoryItemSets[i].ItemCategory, m_ItemSetRulesObject.CategoryItemSetRules[i].ItemCategory)) {
            return false;
        }
    }
    return true;
}
/// <summary>
/// Initialize using an array of categories.
/// </summary>
/// <param name="categories">The categories.</param>
public void Initialize(ItemSetRulesObject itemSetRulesObject)
{
    m_ItemSetRulesObject = itemSetRulesObject;
    m_Initialized = true;
    if (m_ItemSetRulesObject == null) {
        m_CategoryItemSets = null;
        return;
    }
    
    m_ItemSetRulesObject.Initialize(false);
    var validCategoryCount = m_ItemSetRulesObject.CategoryItemSetRules.Length;
    // Initialize the categories.
    if (m_CategoryItemSets == null) {
        m_CategoryItemSets = new CategoryItemSet[validCategoryCount];
    } else if (m_CategoryItemSets.Length != validCategoryCount) {
        System.Array.Resize(ref m_CategoryItemSets, validCategoryCount);
    }
    
    if (m_CategoryIndexMap == null) {
        m_CategoryIndexMap = new Dictionary<IItemCategoryIdentifier, int>();
    } else {
        m_CategoryIndexMap.Clear();
    }
    
    m_ActiveItemSetIndex = new int[m_CategoryItemSets.Length];
    m_NextItemSetIndex = new int[m_CategoryItemSets.Length];
    m_TemporarySetCount = new int[m_CategoryItemSets.Length];
    m_TemporaryAssignedActiveItemSet = new int[m_CategoryItemSets.Length];
    m_TemporaryAssignedNextItemSet = new int[m_CategoryItemSets.Length];
    m_TemporaryActiveNextItemSet = new (ItemSet, ItemSet)[m_CategoryItemSets.Length];
    m_IgnoreItemSetOnAddItem = IgnoreItemSetOnAddItem;
    
    var index = 0;
    for (int i = 0; i < m_ItemSetRulesObject.CategoryItemSetRules.Length; ++i) {
        var category = m_ItemSetRulesObject.CategoryItemSetRules[i].ItemCategory;
        m_ActiveItemSetIndex[index] = -1;
        m_NextItemSetIndex[index] = -1;
        uint categoryID;
        string categoryName;
        
        if (category == null) {
            if (Application.isPlaying) {
                Debug.LogError($"The category at index {i} is null within the ItemSetRuleObject.");
            }
            
            categoryID = RandomID.Empty;
            categoryName = "Null";
            
        } else {
            
            // Create a mapping between the category and index.
            m_CategoryIndexMap.Add(category, index);
            
            categoryID = category.ID;
            categoryName = category.name;
        }
        
        if (m_CategoryItemSets[index] == null) {
            m_CategoryItemSets[index] = new CategoryItemSet(categoryID, categoryName, category);
        } else {
            m_CategoryItemSets[index].CategoryID = categoryID;
            m_CategoryItemSets[index].CategoryName = categoryName;
            m_CategoryItemSets[index].ItemCategory = category;
        }
        m_CategoryItemSets[index].ItemSetList = new List<ItemSet>();
        index++;
    }
}

With this change the category dropdown within the Equip/Unequip abilities will update correctly.
 
You are right there is a problem with the abilities inspector. It caches the categories and doesn't update when the available categories change. I had to close and reopen Unity to make the dropdown update with the correct categories.

The reason the cached category do not change is because the ItemSetManager is initialized once, then it skips initialization to boost perfromance in the Editor (the Ability editor still uses IMGUI so it draws every frame). I changed the InventoryItemSetManager such that it will initialize if the category sets if it doesn't match the rules even if the initialize isn't forced.

So within the InventoryItemSetManager change/add the following functions:

C#:
/// <summary>
/// Initializes the ItemSetManager.
/// </summary>
/// <param name="force">Should the ItemSet be force initialized?</param>
public override void Initialize(bool force)
{
    if (m_Initialized && !force && CategoryItemSetMatchCategoryRules()) {
        return;
    }
    m_TemporaryItemSetStateList = new List<ItemSetStateInfo>();
    m_ItemSetToRuleMap = new Dictionary<ItemSet, IItemSetRule>();
    m_RuleToItemSetsMap = new Dictionary<IItemSetRule, List<ItemSet>>();
    Initialize(m_ItemSetRulesObject);
}
/// <summary>
/// Check if the Category Item Set categories matches the category rules.
/// </summary>
/// <returns></returns>
public bool CategoryItemSetMatchCategoryRules()
{
    if (m_ItemSetRulesObject == null && m_CategoryItemSets == null) {
        return true;
    }
   
    if (m_ItemSetRulesObject == null || m_CategoryItemSets == null) {
        return false;
    }
    if (m_ItemSetRulesObject.CategoryItemSetRules.Length != m_CategoryItemSets.Length) {
        return false;
    }
    for (int i = 0; i < m_CategoryItemSets.Length; i++) {
        if (!ReferenceEquals(m_CategoryItemSets[i].ItemCategory, m_ItemSetRulesObject.CategoryItemSetRules[i].ItemCategory)) {
            return false;
        }
    }
    return true;
}
/// <summary>
/// Initialize using an array of categories.
/// </summary>
/// <param name="categories">The categories.</param>
public void Initialize(ItemSetRulesObject itemSetRulesObject)
{
    m_ItemSetRulesObject = itemSetRulesObject;
    m_Initialized = true;
    if (m_ItemSetRulesObject == null) {
        m_CategoryItemSets = null;
        return;
    }
   
    m_ItemSetRulesObject.Initialize(false);
    var validCategoryCount = m_ItemSetRulesObject.CategoryItemSetRules.Length;
    // Initialize the categories.
    if (m_CategoryItemSets == null) {
        m_CategoryItemSets = new CategoryItemSet[validCategoryCount];
    } else if (m_CategoryItemSets.Length != validCategoryCount) {
        System.Array.Resize(ref m_CategoryItemSets, validCategoryCount);
    }
   
    if (m_CategoryIndexMap == null) {
        m_CategoryIndexMap = new Dictionary<IItemCategoryIdentifier, int>();
    } else {
        m_CategoryIndexMap.Clear();
    }
   
    m_ActiveItemSetIndex = new int[m_CategoryItemSets.Length];
    m_NextItemSetIndex = new int[m_CategoryItemSets.Length];
    m_TemporarySetCount = new int[m_CategoryItemSets.Length];
    m_TemporaryAssignedActiveItemSet = new int[m_CategoryItemSets.Length];
    m_TemporaryAssignedNextItemSet = new int[m_CategoryItemSets.Length];
    m_TemporaryActiveNextItemSet = new (ItemSet, ItemSet)[m_CategoryItemSets.Length];
    m_IgnoreItemSetOnAddItem = IgnoreItemSetOnAddItem;
   
    var index = 0;
    for (int i = 0; i < m_ItemSetRulesObject.CategoryItemSetRules.Length; ++i) {
        var category = m_ItemSetRulesObject.CategoryItemSetRules[i].ItemCategory;
        m_ActiveItemSetIndex[index] = -1;
        m_NextItemSetIndex[index] = -1;
        uint categoryID;
        string categoryName;
       
        if (category == null) {
            if (Application.isPlaying) {
                Debug.LogError($"The category at index {i} is null within the ItemSetRuleObject.");
            }
           
            categoryID = RandomID.Empty;
            categoryName = "Null";
           
        } else {
           
            // Create a mapping between the category and index.
            m_CategoryIndexMap.Add(category, index);
           
            categoryID = category.ID;
            categoryName = category.name;
        }
       
        if (m_CategoryItemSets[index] == null) {
            m_CategoryItemSets[index] = new CategoryItemSet(categoryID, categoryName, category);
        } else {
            m_CategoryItemSets[index].CategoryID = categoryID;
            m_CategoryItemSets[index].CategoryName = categoryName;
            m_CategoryItemSets[index].ItemCategory = category;
        }
        m_CategoryItemSets[index].ItemSetList = new List<ItemSet>();
        index++;
    }
}

With this change the category dropdown within the Equip/Unequip abilities will update correctly.

That fixes all problems :) Thank you!

Now I'm facing another issue - but I think that's a misconfiguration on my side.
I can pick up items, use the equip toggler and so on, the slots are assigned correctly, but the item does not move from the Holster to Hands of my character.
Think I have to check my setup again :) Thanks for the fast fix.
 
Top