How to get ItemSlotSet Slots to correspond directly to UCC Slots?

AlexM

New member
Hello! I've been trying to implement a dual wield system that allows one handed weapons to be equipped in either hand. I'm having problems getting the weapons into the equip slot corresponding to its ItemSlotSet slot. Basically, I have "Held Right" and "Held Left" categories that inherit the equippable category. Then there are more specific weapon type categories like "Both Hand", "One Hand", and "Off Hand". The "both hand" and "off hand" categories inherit "Held Right" and "Held Left", respectively. "one hand" however, inherits both parents. The ItemSlotSet is set up to have the first slot be "Held Right" and the second be "Held Left". My UI allows a drag equip to the desired slot, and that part works. I would like for the "Held Right" slot to correspond to the UCC slot 0 and "Held Left" to slot 1. Unfortunately, that isn't the way it's working out right now. It always seems to be that the first item equipped gets sent right to slot 0 and the second to slot 1, regardless of its spot in the ItemSlotSet. Ideally, this would be without creating two separate Item Definitions, because I'd like it to show as one item in the inventory. I have the Multi Item prefab array set up, and everything else works perfectly. I just can't quite figure out how to make left always be left and right always be right. Any help or advice would be so great! Thank you for your time, and your wonderful asset!
 
I see what you mean. That's actually something we do not have a solution for built-in. But it can be added with a custom ItemSetRule.

ItemSetRules are the objects that define what items will be equipped in which slot by creating the necessary ItemSets. By default we have two types of ItemSetRules, one for ItemDefinitions and one for ItemCategories. And they create ItemSets for all valid permutations of the items.

In your case you do not want all permutations, you want a single way for the items to be equipped defined by the current order of the items in the itemSlotCollection.

So you'll want to create a custom ItemSetRule, You can do so by inheriting the ItemSetRuleObject class to make a scriptable object rule.
Then you will need to override the abstract functions. The important one is "GetItemSetsFor" which returns the ItemSets you will create. In your case probably just one ItemSet matching the current Items in the ItemSlotCollection.

You'll need to use a static function to get the reference to the player inventory, because the ItemSetRule doesn't pass anything other than an Item list as parameter.
Code:
// Get an Inventory Identifier by ID (a component which sits next to an Inventory)
var inventoryIdentifier = InventorySystemManager.GetInventoryIdentifier(id);
var inventory = inventoryIdentifier.Inventory;
From there you can get the ItemSlotCollection by name, and get the items in each slot and create an ItemSet. Feel free to look at the ItemSetRuleWithCategories and ItemSetRuleWIthItemDefinitions script to see how it's implemented. Your script will probably be much simpler since you do not need to compute all possible valid permuations

Once you created your custom ItemSetRule, you must create an scriptableObject instance that you can assign in the list of the ItemSetRules, by selecting "Custom" (or scriptable object, I do not remember the exact name) when pressing the "+" icon to add a rule.

I hope that makes sense, if you need any more pointers don't hesitate to ask.
 
Thanks for the reply! So, I'm not much of a programmer, to be honest. I usually use Playmaker for my scripting needs. I know this is probably a lot, but could you explain in a bit more detail how I'd go about making the custom rules? I have a little experience in C# from a class I took, but it feels a little overwhelming looking at these scripts. I'm kinda wishing I took more classes on it now haha o_O
 
Since this is a feature I could see other users use I decided to implement it.

I did not have the time to test it, but I believe this should work. If you have any issues with it do let me know.

Add the script below in the "Opsive\UltimateCharacterController\Integrations\UltimateInventorySystem\Scripts\ItemSet" folder.
Then open the project in the file explorer. And replace the content of the "ItemSlotCollectionItemSetRuleObject.cs.meta" file that was automatically created by this (this will prevent issues when you update the asset):

fileFormatVersion: 2
guid: 189cdc2c949f421fbe6aec7107abf0d1
timeCreated: 1637310278

To create the scriptable object rule, right click in the project view -> Create/Opsive/Inventory/Item Slot Collection Item Set Rule Object
That will create the object, then assign it in the ItemSetRules list by choosing the option "Add Item Set Rule Object Container".

I hope that helps
 

Attachments

  • ItemSlotCollectionItemSetRuleObject.cs
    6.6 KB · Views: 2
Thank you so so much! You're a life saver! I'll test it right now. I'll post my results in this thread in a little bit.
 
Ok, so I've spent some time working with this, and it seems to be working perfectly at runtime! There are a few strange problems though.

First, when I created the Item Set Rule Object, I got the error: "The same field name is serialized multiple times in the class or its parent class. This is not supported: Base(MonoBehaviour) m_Enabled
UnityEditor.InspectorWindow:RedrawFromNative ()"

Second, despite working during runtime, the Item Set Rule Object Container is showing "none" in the inspector where I put the Object. I keep trying to put it back in, but the inspector will change back to none if I select anything else.

Otherwise, this is working really well. I think that this should definitely be a part of the official integration, if possible! Thank you so much for your help with this, I really appreciate it.
 
Thank you for testing this for me.

Here is the fix for the object not appearing in the inspector, Replace this class within the ItemSetRulesObjectInspector.cs file:

Code:
public class ItemSetRuleObjectContainerField : ItemSetRuleBaseField
{
    protected ItemSetRuleObjectContainer m_ItemSetRuleObjectContainer;
    
    protected ObjectField m_ObjectField;
    protected Label m_Label;
    public override float GetHeight(bool selected)
    {
        return 100;
    }
    public ItemSetRuleObjectContainerField(InventorySystemDatabase database) : base(database)
    {
        m_ObjectField = new ObjectField("Item Set Rule Object");
        m_ObjectField.objectType = typeof(ItemSetRuleObject);
        m_ObjectField.RegisterValueChangedCallback(evt =>
        {
            m_ItemSetRuleObjectContainer.ItemSetRuleObject = evt.newValue as ItemSetRuleObject;
            HandleValueChanged();
        });
        
        m_Label = new Label("Inherit the ItemSetRuleObject script to create custom Item Set Rules.");
    }
    public override void Refresh(IItemSetRule newItemSetRule, bool selected)
    {
        m_ItemSetRuleObjectContainer = newItemSetRule as ItemSetRuleObjectContainer;
        
        base.Refresh(newItemSetRule, selected);
        if (m_ItemSetRuleObjectContainer?.ItemSetRuleObject == null) {
            m_Container.Add(m_Label);
        }
        
        m_ObjectField.SetValueWithoutNotify(m_ItemSetRuleObjectContainer?.ItemSetRuleObject);
        m_Container.Add(m_ObjectField);
    }
}

For the m_Enabled error, I changed the field name to m_IsEnabled instead:


Code:
public class ItemSlotCollectionItemSetRuleObject : ItemSetRuleObject
{
    [SerializeField] protected string m_ItemSlotCollectionName = "EquippableSlots";
    
    [Tooltip("A list of all states that the component can change to.")]
    [HideInInspector] [SerializeField] protected State[] m_States = new State[] { new State("Default", true) };
    [Tooltip("The state to change to when the ItemSet is active.")]
    [SerializeField] protected string m_State;
    [Tooltip("Is the ItemSet the default ItemSet?")]
    [SerializeField] protected bool m_Default = false;
    [Tooltip("Is the ItemSet enabled?")]
    [SerializeField] protected bool m_IsEnabled = true;
    [Tooltip("Can the ItemSet be switched to by the EquipNext/EquipPrevious abilities?")]
    [SerializeField] protected bool m_CanSwitchTo = true;
    [Tooltip("The ItemSet index that should be activated when the current ItemSet is active and disabled.")]
    [SerializeField] protected int m_DisabledIndex = -1;
    protected ResizableArray<ItemSet> m_TemporaryItemSets;
    protected int m_SlotCount = 0;
    
    [Opsive.Shared.Utility.NonSerialized] public State[] States { get { return m_States; } set { m_States = value; } }
    [Shared.Utility.NonSerialized] public string State { get { return m_State; } set { m_State = value; } }
    public override bool Default { get { return m_Default; } set { m_Default = value; } }
    public bool IsEnabled { get { return m_IsEnabled; } set { m_IsEnabled = value; } }
    public bool CanSwitchTo { get { return m_CanSwitchTo; } set { m_CanSwitchTo = value; } }
    public int DisabledIndex { get { return m_DisabledIndex; } set { m_DisabledIndex = value; } }

I also made the Default property override such that it can function correctly. This required a change in the ItemSetRuleObject.cs file:

Code:
/// <summary>
/// The item set rule object, override this to create custom item set rules.
/// </summary>
public abstract class ItemSetRuleObject : ScriptableObject, IItemSetRule
{
    /// <summary>
    /// Is the item set the default one.
    /// </summary>
    public virtual bool Default
    {
        get { return false; }
        // ReSharper disable once ValueParameterNotUsed
        set { }
    }

With those changes things should work just fine, let me know if they don't
 
Ok, so this is working perfectly! Just one thing, on line 140 of ItemSlotCollectionItemSetRuleObject.cs "itemSet.Enabled = Enabled;" Enabled needs to be changed to IsEnabled. I think this is a case closed now! The one thing I'm a little worried about now is updating. For now, I'm gonna be really careful with updates, but do you think this will be officially implemented?
 
Never mind, actually, I just found a new problem :(

So, everything was working fine after I first changed the scripts. When I restarted my editor after a backup, though, I started getting errors on equip. I have a drag equip and a UI button equip, and each had similar but different errors. I'll attach the error logs to this post.

I found that reordering the Item Set Rules fixes this completely, and it goes back to working normally. I did try this a few times to test, restarting my editor and checking, and it's pretty consistent.

Not sure what would be happening here, honestly, but the fix is pretty straightforward. As long as I mess with the Item Set Rule order, it's fine and I can continue working. I can see this being a problem later though.

Let me know what you think! Thanks for the help so far :)
 

Attachments

  • Click Equip Errors.txt
    9.7 KB · Views: 1
  • Drag and Drop Errors.txt
    14.4 KB · Views: 2
Last edited:
I think I know what the error is. The InventoryItemSetManager initializes the slot count on the ItemSetRules only in the editor.
So instead what I'll do is serialize the slot count on the scriptable object. You'll just need to remember to change it if you add or remove slots.

Code:
public class ItemSlotCollectionItemSetRuleObject : ItemSetRuleObject
{
    [SerializeField] protected string m_ItemSlotCollectionName = "EquippableSlots";
    
    [Tooltip("The Slot Count when creating new ItemSets.")]
    [SerializeField] protected int m_SlotCount = 2;

I hope that fixes the issue
 
Apologies for the late reply, I was on vacation for US Thanksgiving. This works! No problems to speak of now.

One last question for this thread, though. Could you please point me to a solution for two handed items that would work with this? Maybe something that disables left hand equips with a 2h weapon in the right hand? Or replaces it on a one handed equip? I've already found a few other threads that discuss the issue, but I'm not sure what would work with this new Item Rule.
 
I'm glad it works.

You simply need to prevent the player from equipping another weapon when a two handed weapon is equipped. There are many ways to do this.
None are relevant to the ItemSetRule defined above. You simply need to prevent the player from adding items to the ItemSlotCollection.
(the "slots" below refer to the itemslotcollection slots, not UCC Item slots)

1) And a custom ItemRestriction that prevents items to be added to slot 2 if a two handed item is equipped in slot 0
2) Add a dummy item that gets added automatically to slot 1 when a 2 handed item is equipped to slot 0
3) Make a custom Equip item Action that prevents the player from equipping the item in slot 1 if slot 0 has a 2handed weapon. Or unequip the 2handed weapon before equipping the 1 handed item. If you also use drag and drop, make sure to use a similar Drop Action logic.

There is no right answer here. It really depends on what solution you personally prefer and what makes the most sense for your game. You can also combine some of these options toghether if you like.
 
Top