Problem with the ItemViewSlotRestriction

Hi,

I should make a forum post that you do not forget that topic.
Here is my problem.

I created a category "RingL" and "RingR" and a parent Category with only "Ring". My ItemDefinitions inherit from "Ring" and I want to equip it in two slots which are based on "RingL" and "RingR". But in "ItemViewSlot:210" it will return false because of the automatically attached script at runtime. The inherit-checkbox in the Restriction-script only checks the children so it cannot be true.

Also a Dual Wild setup where one slot can contain a shild or a weapon cannot be realized.
For Example:

I create a "EQ_Primary" and a "EQ_Secondary" Category and I inherit the Primary by Weapon but Secondary should have parents like Weapon and Shield.
But when I would set the Restriction to Secondary-CategoryType I cannot attach a Weapon nor a Shield to it because it do not look to its parent it only looks down.

I guess it would help to check if the any script based on "ItemViewSlotRestriction"-class is already assigned or not before attach a default script to it?

btw. If you have a "equip"-action but the item has no correct itemVIewSlot where it can be equiped to, then the item will be removed. (If everything is correctly setup it wouldnt happened, but I would recommend to check everything before removing
 
For the first issue of Ring, RingL and RingR, I think its just a question of point of view. The way I see it the slot category restriction should say what category of items it can contain. All Items which inherently has that category should be able to slot there.
But it seems you think of it the other way around: any items with a category that inherently contains the slot category.

In your use case I wouldn't have a RingL and RingR category. I would just have a Ring category and use that for the slot restrictions. Or if you really want a RignL and RingR category they can be parents of the Ring category instead of children.

The the primary and secondary use case, I would simply make weapon have both primary and secondary as parent. While Shield would only have secondary as parent.

Just a reminder that's not just the case for item view slots restrictions. It is the case for most of the restrictions in the system. For example the ItemSlotCollection will only allow you to set items in a specific slot if your item is inherently part of the slot category.

I personally think that the way I set restrictions makes sense but it is a question of point of view.
The issue is that even if you were to change the Item View Slot restriction the item wouldn't be added because it does not fit in the collection slot (because the item does not inherit that category).

I'm afraid that if you wish to make the system to work the way you have it in mind you will need to create not only custom restrictions but also a custom ItemCollection for your items. And that would require you to create a custom equipment UI too. It's all possible but it might be worth looking into changing your category hierarchy instead of doing all this extra work.

That being said I added an option to not add the Item View Slot Category Restrictions automatically. This is done on the ItemslotCollectionView component. where "m_AddViewSlotCategoryRestrictions" is a boolean field.

Code:
/// <summary>
/// Set the item view slot restrictions.
/// </summary>
public void SetItemViewSlotRestrictions()
{
    if (m_ItemSlotSet == null) { return; }
    for (int i = 0; i < m_ItemSlotItemViewSlots.Length; i++) {
        var itemViewSlot = m_ItemSlotItemViewSlots[i];
        if (itemViewSlot == null) {
            return;
            //Debug.LogError($"The item view slot at index {i} is null, the item view slots cannot be null",gameObject);
        }
        var itemSlot = m_ItemSlotSet.GetSlot(i);
        if (itemSlot.HasValue == false) { return; }
        if (m_AddViewSlotCategoryRestrictions) {
            var itemViewSlotRestriction = itemViewSlot.GetComponent<ItemViewSlotCategoryRestriction>();
            if (itemViewSlotRestriction == null) {
                itemViewSlotRestriction = itemViewSlot.gameObject.AddComponent<ItemViewSlotCategoryRestriction>();
            }
            itemViewSlotRestriction.ItemCategory = itemSlot.Value.Category;
        }
    }
}


As for the equip action making the item disappear, you are right it would seem there was a bug in the MoveToCollectionItemAction.
I did double check the code, and for the "CharacterEquipUnequipItemAction" it seem it had taken that into account.
I belive you mentioned you were using the UCC+UIS integration. If that's the case you should use the "CharacterEquipUnequipItemAction" instead of the "MoveToCollectionItemAction".

Here is the new MoveToCollectionItemAction function:
Code:
/// <summary>
/// Move an item from one collection to another.
/// </summary>
/// <param name="itemInfo">The item info.</param>
/// <param name="itemUser">The item user (can be null).</param>
protected override void InvokeActionInternal(ItemInfo itemInfo, ItemUser itemUser)
{
    var item = itemInfo.Item;
    var inventory = itemInfo.Inventory;
    var firstCollection = inventory.GetItemCollection(m_FirstCollectionID);
    var secondCollection = inventory.GetItemCollection(m_SecondCollectionID);
    var originalCollection = m_MoveFromFirstToSecond ? firstCollection : secondCollection;
    var destinationCollection = m_MoveFromFirstToSecond ? secondCollection : firstCollection;
    //This action used to give the item one way and then the other.
    //The action now removes the item, before it adds it to the other collection to allow restrictions to work properly
    var originalItem = originalCollection.RemoveItem(itemInfo);
    var movedItemInfo = ItemInfo.None;
    
    if (destinationCollection is ItemSlotCollection itemSlotCollection) {
        var slotIndex = itemSlotCollection.GetTargetSlotIndex(item);
        if (slotIndex != -1) {
            var previousItemInSlot = itemSlotCollection.GetItemInfoAtSlot(slotIndex);
            if (previousItemInSlot.Item != null) {
                //If the previous item is stackable don't remove it.
                if (previousItemInSlot.Item.StackableEquivalentTo(originalItem.Item)) {
                    previousItemInSlot = ItemInfo.None;
                } else {
                    previousItemInSlot = itemSlotCollection.RemoveItem(slotIndex);
                }
            }
    
            movedItemInfo = itemSlotCollection.AddItem(originalItem, slotIndex);
            if (previousItemInSlot.Item != null) {
                firstCollection.AddItem(previousItemInSlot);
            }
        }
    } else {
        movedItemInfo = destinationCollection.AddItem(originalItem);
    }
    //Not all the item was added, return the items to the default collection.
    if (movedItemInfo.Amount != originalItem.Amount) {
        var amountToReturn = originalItem.Amount - movedItemInfo.Amount;
        firstCollection.AddItem((ItemInfo) (amountToReturn, originalItem));
    }
    
}

Sorry I can't help more than that
 
Hi @Sangemdoko

thanks for the help. I would then use your way of using the category.
I only thought that the ItemSlotCollection which has all Categories which can be equiped
have to be 1:1 matching the amount if slots in the UI.
So I cannot create two Slots with Category restriction "Ring" or is that wrong?

And no I do not use the UCC-integration I use another Character-solution.

With the Dual Wild case I will test it out today and let you know if I have problems with that.

Thx again :)
 
When you have two slots with the same item category, The system will automatically try to set the item in the slot it finds more appropriate.
Normally it will add to the first empty slot it finds. If all slots are occupied it will replace the item in the last slot found (when new item priority is enabled, if not the previous item will stay).
You can use drag and drop to choose the specific slot to add the item to, or you could create a custom item action that gives you the option to equip in a specific slot when it is ambiguous.
Or you can create a custom item slot collection and override the add item functions to decide your own custom priority system.
 
When you have two slots with the same item category, The system will automatically try to set the item in the slot it finds more appropriate.
Normally it will add to the first empty slot it finds. If all slots are occupied it will replace the item in the last slot found (when new item priority is enabled, if not the previous item will stay).
You can use drag and drop to choose the specific slot to add the item to, or you could create a custom item action that gives you the option to equip in a specific slot when it is ambiguous.
Or you can create a custom item slot collection and override the add item functions to decide your own custom priority system.
Mh I think we speak about different things.

My thought was that the ItemSlotCollection which contains all equipable categories has to contain one Category-Type
for exact one ViewSlot. That was the reason I create a RingL and RingR Category.
But it seems that this is not correct like you said isn't it?
So I can have only a Ring Category, assign this to to the Collection and then I can have more then one Slot with that Category right?
 
I have another problem as well now.
I can drag&drop my sword now into the weapons slot (primary)
but not from the slot into the inventory back.
 
Ok after debugging I also regocnized that the index inside the "equipper" script is wrong for me as I droped the item into the slot.
For some reasons the item will be equiped and doesn't care that it return "false" inside the Equip-method.

1618514921109.png
1618514939551.png

I have some guessings.
You take the index of the alignment of the GUI slots and use it for getting the Slot of the assigned ItemSlotSet
Maybe I have to debug a bit more to be sure :D

Update: (m_ItemsBySlot) has the item in the wrong index
Mh The wrong index comes from the ItemDropHandler...
 
Last edited:
Thanks for pointing that out there are a few issues that I missed, but no worries it does not have anything to do using the same category twice.

For the first issue that you mentioned that seems odd. I tried it myself and it seems that it doesn't have anything to do with the category. The custom editor simply doesn't update the size of the item view slots. This was most likely something that broke with a recent change I made.
I was able to fix it fairly easily by changing the order some functions were being called.

In ItemSlotCollectionView script add a new "ResizeItemViewSlotsToItemSlotsSetCount" function and replace "OnInitializeBeforeSettingInventory":
Code:
/// <summary>
/// This method is called before the Inventory is set to the Item View Slots Container.
/// </summary>
protected override void OnInitializeBeforeSettingInventory()
{
    if (m_SetItemViewSlotRestrictions) { SetItemViewSlotRestrictions(); }
}
/// <summary>
/// Resize the length of the Item View Slots.
/// </summary>
/// <returns>Return true if the length changed.</returns>
public bool ResizeItemViewSlotsToItemSlotsSetCount()
{
    if (m_ItemSlotSet == null || m_ItemSlotSet.ItemSlots == null) {
        if (Application.isPlaying) { Debug.LogError("The item slot set cannot be null", gameObject); }
        return false;
    }
    if (m_ItemSlotItemViewSlots == null) {
        m_ItemSlotItemViewSlots = new ItemViewSlot[m_ItemSlotSet.ItemSlots.Count];
        return true;
    }
    if (m_ItemSlotItemViewSlots.Length != m_ItemSlotSet.ItemSlots.Count) {
        Array.Resize(ref m_ItemSlotItemViewSlots, m_ItemSlotSet.ItemSlots.Count);
        return true;
    }
    return false;
}


in the ItemSlotCollectionViewInspector script change the start of the CreateInspector function
Code:
/// <summary>
/// Create the inspector.
/// </summary>
/// <param name="container">The parent container.</param>
protected override void CreateInspector(VisualElement container)
{
    m_ItemSetSlotField = new ObjectField("Item Slot Set");
    m_ItemSetSlotField.objectType = typeof(ItemSlotSet);
    m_ItemSetSlotField.value = m_ItemSlotCollectionView.ItemSlotSet;
    m_ItemSetSlotField.RegisterValueChangedCallback(evt =>
    {
        m_ItemSlotCollectionView.ItemSlotSet = evt.newValue as ItemSlotSet;
        m_ItemSlotCollectionView.Initialize(false);
        m_ItemSlotCollectionView.ResizeItemViewSlotsToItemSlotsSetCount();
        m_List = new List<ItemViewSlot>(m_ItemSlotCollectionView.m_ItemSlotItemViewSlot
        OnValueChanged();
    });
    container.Add(m_ItemSetSlotField);
    
    m_ItemSlotCollectionView.ResizeItemViewSlotsToItemSlotsSetCount();
    m_List = new List<ItemViewSlot>(m_ItemSlotCollectionView.m_ItemSlotItemViewSlots);

...
...


There's another bug that you mentioned which is that the index doesn't always match the item view slot. I'm still trying to figure that out. Essentially the mismatch between the slot hierarchy order and the slot set order causes issues. An easy way to go around this is to make a flat Item View Slot hierarchy and making it the same order as the slot set.
Of course that's not ideal, but it can be a work around while I try fixing this bug.

Also be careful, it seems that you've set the wrong view slots in some fields:
1618557261301.png

I apologize about those bugs, I was so focused on getting the core integration to work that some of the other features didn't get as much love during testing.
 
Thanks for you help. I dont know if I should go with a flat hierarchy because of the alignment of the components.
Maybe I should wait then until you fixed that issue.
And yes I already fixed the missmatch of the slots.

The bug with the wrong index also stoped some event-handling that I activate my weapons in another script (so the event will not being catched cause the item do not run further the Equip-Script in the Equipper)
 
So I was able to find the issue.
There was a mismatch between three arrays, The ItemViewSlots assigned in the editor, the Item View Slot got by the ItemViewSlotContainer (in the hierarchy) and the ViewSlots in the ViewDrawer (also gotten from the hierarchy)

So I had to move a few things around to sync them correctly.

I would recommend you wait until the next update, probably next week. But in case you are in a hurry, here are the changes (I think I haven't forgotten anything)

In the ItemViewSlotsContainer script
Code:
/// <summary>
/// The hot item bar component allows you to use an item action for an item that was
/// </summary>
public class ItemViewSlotsContainer : ItemViewSlotsContainerBase
{
    
...
...
    /// <summary>
    /// Initialize.
    /// </summary>
    /// <param name="force">Force initialize.</param>
    public override void Initialize(bool force)
    {
        if (m_IsInitialized && !force) {
            if (Application.isPlaying) { InitializeItemViewDrawer(force); }
            return;
        }
        if (m_Content == null) { m_Content = transform as RectTransform; }
        m_ItemViewSlots = RetrieveItemViewSlots();
        m_ItemViewDrawer.ViewSlots = m_ItemViewSlots;
        if (Application.isPlaying) { InitializeItemViewDrawer(force); }
        base.Initialize(force);
    }
    protected virtual ItemViewSlot[] RetrieveItemViewSlots()
    {
        return m_Content.GetComponentsInChildren<ItemViewSlot>();
    }

....
...

In the script ItemSlotCollectionView script
Code:
/// <summary>
/// The component used to view an item slot collection, for example an equipment window.
/// </summary>
public class ItemSlotCollectionView : ItemViewSlotsContainer, IDatabaseSwitcher
{
    ....
    ....
    
    protected override ItemViewSlot[] RetrieveItemViewSlots()
    {
        ResizeItemViewSlotsToItemSlotsSetCount();
        
        if (m_ItemSlotItemViewSlots == null) {
            return base.RetrieveItemViewSlots();
        }
        return m_ItemSlotItemViewSlots;
    }
    /// <summary>
    /// This method is called before the Inventory is set to the Item View Slots Container.
    /// </summary>
    protected override void OnInitializeBeforeSettingInventory()
    {
        if (m_SetItemViewSlotRestrictions) { SetItemViewSlotRestrictions(); }
    }
    /// <summary>
    /// Resize the length of the Item View Slots.
    /// </summary>
    /// <returns>Return true if the length changed.</returns>
    public bool ResizeItemViewSlotsToItemSlotsSetCount()
    {
        if (m_ItemSlotSet == null || m_ItemSlotSet.ItemSlots == null) {
            if (Application.isPlaying) { Debug.LogError("The item slot set cannot be null", gameObject); }
            return false;
        }
        if (m_ItemSlotItemViewSlots == null) {
            m_ItemSlotItemViewSlots = new ItemViewSlot[m_ItemSlotSet.ItemSlots.Count];
            return true;
        }
        if (m_ItemSlotItemViewSlots.Length != m_ItemSlotSet.ItemSlots.Count) {
            Array.Resize(ref m_ItemSlotItemViewSlots, m_ItemSlotSet.ItemSlots.Count);
            return true;
        }
        return false;
    }

...
....

And in the ViewDrawer script
Code:
/// <summary>
/// The base class for a box drawer.
/// </summary>
public abstract class ViewDrawerBase : MonoBehaviour
{
    ...
    ...
    
    /// <summary>
    /// Initialize the component.
    /// </summary>
    public virtual void Initialize(bool force)
    {
        if (m_IsInitialized && !force) { return; }
        if (m_Content == null) { m_Content = transform; }
        if (m_ViewSlots == null) {
            m_ViewSlots = m_Content.GetComponentsInChildren<IViewSlot>();
        }
        
        for (int i = 0; i < m_ViewSlots.Length; i++) {
            var viewSlot = m_ViewSlots[i];
            if (m_DisableViewSlotImageComponent) {
                viewSlot.DisableImage();
            }
            if (m_RemoveViewsOnInitialize) {
                RemoveChildrenFromTransform(viewSlot.transform);
            }
        }
        if (m_DrawEmptyViewsOnInitialize) {
            for (int i = 0; i < m_ViewSlots.Length; i++) {
                DrawEmptyView(i, i);
            }
        }
        m_IsInitialized = true;
    }
    
    ...
    ...

I hope that's all of it.
 
So I was able to find the issue.
There was a mismatch between three arrays, The ItemViewSlots assigned in the editor, the Item View Slot got by the ItemViewSlotContainer (in the hierarchy) and the ViewSlots in the ViewDrawer (also gotten from the hierarchy)

So I had to move a few things around to sync them correctly.

I would recommend you wait until the next update, probably next week. But in case you are in a hurry, here are the changes (I think I haven't forgotten anything)

In the ItemViewSlotsContainer script
Code:
/// <summary>
/// The hot item bar component allows you to use an item action for an item that was
/// </summary>
public class ItemViewSlotsContainer : ItemViewSlotsContainerBase
{
  
...
...
    /// <summary>
    /// Initialize.
    /// </summary>
    /// <param name="force">Force initialize.</param>
    public override void Initialize(bool force)
    {
        if (m_IsInitialized && !force) {
            if (Application.isPlaying) { InitializeItemViewDrawer(force); }
            return;
        }
        if (m_Content == null) { m_Content = transform as RectTransform; }
        m_ItemViewSlots = RetrieveItemViewSlots();
        m_ItemViewDrawer.ViewSlots = m_ItemViewSlots;
        if (Application.isPlaying) { InitializeItemViewDrawer(force); }
        base.Initialize(force);
    }
    protected virtual ItemViewSlot[] RetrieveItemViewSlots()
    {
        return m_Content.GetComponentsInChildren<ItemViewSlot>();
    }

....
...

In the script ItemSlotCollectionView script
Code:
/// <summary>
/// The component used to view an item slot collection, for example an equipment window.
/// </summary>
public class ItemSlotCollectionView : ItemViewSlotsContainer, IDatabaseSwitcher
{
    ....
    ....
  
    protected override ItemViewSlot[] RetrieveItemViewSlots()
    {
        ResizeItemViewSlotsToItemSlotsSetCount();
      
        if (m_ItemSlotItemViewSlots == null) {
            return base.RetrieveItemViewSlots();
        }
        return m_ItemSlotItemViewSlots;
    }
    /// <summary>
    /// This method is called before the Inventory is set to the Item View Slots Container.
    /// </summary>
    protected override void OnInitializeBeforeSettingInventory()
    {
        if (m_SetItemViewSlotRestrictions) { SetItemViewSlotRestrictions(); }
    }
    /// <summary>
    /// Resize the length of the Item View Slots.
    /// </summary>
    /// <returns>Return true if the length changed.</returns>
    public bool ResizeItemViewSlotsToItemSlotsSetCount()
    {
        if (m_ItemSlotSet == null || m_ItemSlotSet.ItemSlots == null) {
            if (Application.isPlaying) { Debug.LogError("The item slot set cannot be null", gameObject); }
            return false;
        }
        if (m_ItemSlotItemViewSlots == null) {
            m_ItemSlotItemViewSlots = new ItemViewSlot[m_ItemSlotSet.ItemSlots.Count];
            return true;
        }
        if (m_ItemSlotItemViewSlots.Length != m_ItemSlotSet.ItemSlots.Count) {
            Array.Resize(ref m_ItemSlotItemViewSlots, m_ItemSlotSet.ItemSlots.Count);
            return true;
        }
        return false;
    }

...
....

And in the ViewDrawer script
Code:
/// <summary>
/// The base class for a box drawer.
/// </summary>
public abstract class ViewDrawerBase : MonoBehaviour
{
    ...
    ...
  
    /// <summary>
    /// Initialize the component.
    /// </summary>
    public virtual void Initialize(bool force)
    {
        if (m_IsInitialized && !force) { return; }
        if (m_Content == null) { m_Content = transform; }
        if (m_ViewSlots == null) {
            m_ViewSlots = m_Content.GetComponentsInChildren<IViewSlot>();
        }
      
        for (int i = 0; i < m_ViewSlots.Length; i++) {
            var viewSlot = m_ViewSlots[i];
            if (m_DisableViewSlotImageComponent) {
                viewSlot.DisableImage();
            }
            if (m_RemoveViewsOnInitialize) {
                RemoveChildrenFromTransform(viewSlot.transform);
            }
        }
        if (m_DrawEmptyViewsOnInitialize) {
            for (int i = 0; i < m_ViewSlots.Length; i++) {
                DrawEmptyView(i, i);
            }
        }
        m_IsInitialized = true;
    }
  
    ...
    ...

I hope that's all of it.

Just for notice.
In ItemViewSlotContainer it says that
m_ItemViewDrawer.ViewSlots

has not setter method I added one now.
 
Weird, I don't remember changing that.

Don't you have this in the ViewDrawerBase?
Code:
public IViewSlot[] ViewSlots
{
    get { return m_ViewSlots; }
    set { m_ViewSlots = value; }
}

I hope that fixes it but if not, to avoid having too many changes compared to the released version I would recommend you wait for next weeks update which will include other bug fixes too.
 
Top