Loading a save: Cannot add an item to an ItemCollection when it is already a member of an existing Item Collection

desukarhu

Member
Hey,

I'm getting this warning when loading a save (I edited the warning to show what the existing ItemCollection is):
Code:
The Mutable Item Initiates Vest is unable to be added to a new Item Collection ItemCollection Equipment (Equipped) when it is already a member of an existing Item Collection ItemCollection Equipment (Equipped).

What I'm doing is starting the game, when my Player prefab is instantiated I add a bunch of equipment directly into the Equipment ItemCollection (the characters starting equipment so to speak), then I save my players Inventory before changing scenes and change the scene. In the new scene I instantiate the player prefab again and load the saved inventory. That's when I get the warning.

I'm using PixelCrushers save system and his "SaveSystem.LoadScene()" method.

Then if I unequip one of my items I get this warning:
Code:
Item was removed from an Item Collection which was not set on the item.

Despite these warnings everything still seems to be working fine, so I'm unsure if something will break later in the game if I don't fix this.
 
That's very odd...
The item is being added to equipment but it complains it is already added, and then it says it wasn't added correctly...
Maybe I need to add a check to make sure the item isn't the same as the one in the collection.

Could you send me the full warning log, for both logs, this way I can see what part of the save loading code is causing that warning

How big is your project? If I'm unable to find the issue myself it might be considering sending me you project with save data such that I can have a deeper look
 
That's very odd...
The item is being added to equipment but it complains it is already added, and then it says it wasn't added correctly...
Maybe I need to add a check to make sure the item isn't the same as the one in the collection.

Could you send me the full warning log, for both logs, this way I can see what part of the save loading code is causing that warning

How big is your project? If I'm unable to find the issue myself it might be considering sending me you project with save data such that I can have a deeper look
My project is quite big, if you can't figure it out I'll try to find a way to send it to you.

Here's the entire log for the warning:

Code:
[Warning] The Mutable Item Initiates Vest is unable to be added to a new Item Collection ItemCollection Equipment (Equipped) when it is already a member of an existing Item Collection ItemCollection Equipment (Equipped).
Item.AddItemCollection() at /3rd Party Systems/Opsive/UltimateInventorySystem/Scripts/Core/Item.cs:411
409:   if (ItemDefinition.IsMutable == false || m_ItemCollection == itemCollection) { return; }
410:   if (m_ItemCollection != null && m_ItemCollection != itemCollection) {
-->411:       Debug.LogWarning($"The Mutable Item {name} is unable to be added to a new Item Collection {itemCollection} when it is already a member of an existing Item Collection {m_ItemCollection}.");
412:       return;
413:   }

ItemCollection.AddInternal() at /3rd Party Systems/Opsive/UltimateInventorySystem/Scripts/Core/InventoryCollections/ItemCollection.cs:427
425:   }
-->427:   itemInfo.Item.AddItemCollection(this);
429:   if (notifyAdd) { NotifyAdd(itemInfo, addedItemStack); }

ItemSlotCollection.SetItemAmount() at /3rd Party Systems/Opsive/UltimateInventorySystem/Scripts/Core/InventoryCollections/ItemSlotCollection.cs:283
282:   var itemToAdd = (amount, itemInfo);
-->283:   var itemInfoAdded = AddInternal(itemToAdd, null, false);
285:   m_ItemsBySlot[slotIndex] = itemInfoAdded.ItemStack;

ItemSlotCollection.AddItemInternal() at /3rd Party Systems/Opsive/UltimateInventorySystem/Scripts/Core/InventoryCollections/ItemSlotCollection.cs:238
236:   var currentAmount = GetItemAmount(item);
-->238:   var setItemInfo = SetItemAmount((amount + currentAmount, itemInfo), slotIndex, true);
239:   if (setItemInfo.HasValue) {
240:       return setItemInfo.Value;

ItemSlotCollection.AddItem() at /3rd Party Systems/Opsive/UltimateInventorySystem/Scripts/Core/InventoryCollections/ItemSlotCollection.cs:202
200:   public ItemInfo AddItem(ItemInfo itemInfo, int slotIndex)
201:   {
-->202:       var itemInfoAdded = AddItemInternal(itemInfo, slotIndex);
204:       if (itemInfoAdded.Amount < itemInfo.Amount) {

ItemSlotCollection.AddItem() at /3rd Party Systems/Opsive/UltimateInventorySystem/Scripts/Core/InventoryCollections/ItemSlotCollection.cs:177
175:   public override ItemInfo AddItem(ItemInfo itemInfo, ItemStack stackTarget = null)
176:   {
-->177:       return AddItem(itemInfo, GetTargetSlotIndex(itemInfo.Item));
178:   }

ItemCollection.AddItem() at /3rd Party Systems/Opsive/UltimateInventorySystem/Scripts/Core/InventoryCollections/ItemCollection.cs:582
580:   public virtual ItemInfo AddItem(Item item, int amount = 1)
581:   {
-->582:       return AddItem((ItemInfo)(item, amount));
583:   }

ItemCollection.AddItems() at /3rd Party Systems/Opsive/UltimateInventorySystem/Scripts/Core/InventoryCollections/ItemCollection.cs:618
616:   for (var i = 0; i < itemAmounts.Count; i++) {
617:       var itemAmount = itemAmounts[i];
-->618:       totalAdded += AddItem(itemAmount.Item, itemAmount.Amount).Amount;
619:   }
620:   return totalAdded;

InventorySaver.DeserializeAndLoadSaveData() at /3rd Party Systems/Opsive/UltimateInventorySystem/Scripts/SaveSystem/InventorySaver.cs:136
134:           Debug.LogWarning("Item Collection from save data is missing in the scene.");
135:       } else {
-->136:           m_Inventory.GetItemCollection(i).AddItems(itemAmounts);
137:       }
138:   }

SaverBase.Load() at /3rd Party Systems/Opsive/UltimateInventorySystem/Scripts/SaveSystem/SaverBase.cs:84
82:               return;
83:           }
-->84:           DeserializeAndLoadSaveData(serializedSaveData);
85:       }
86:   }

SaveSystemManager.LoadAllSavers() at /3rd Party Systems/Opsive/UltimateInventorySystem/Scripts/SaveSystem/SaveSystemManager.cs:546
544:       OrderSaversByPriority(false);
545:       for (int i = 0; i < m_Savers.Count; i++) {
-->546:           m_Savers[i].Load();
547:       }
548:   }

SaveSystemManager.LoadInternal() at /3rd Party Systems/Opsive/UltimateInventorySystem/Scripts/SaveSystem/SaveSystemManager.cs:534
532:   m_LoadedSaveData = SaveDataInfo.DeepCopy(m_Saves[saveIndex]);
-->534:   LoadAllSavers();
536:   EventHandler.ExecuteEvent(EventNames.c_LoadingSaveComplete_Index, saveIndex);

SaveSystemManager.Load() at /3rd Party Systems/Opsive/UltimateInventorySystem/Scripts/SaveSystem/SaveSystemManager.cs:288
286:   public static void Load(int saveIndex, SaveData saveData)
287:   {
-->288:       Instance.LoadInternal(saveIndex, saveData);
289:   }

UISSaver.ApplyData() at /3rd Party Systems/Pixel Crushers/Common/Third Party Support/Opsive UIS Support/Scripts/UISSaver.cs:30
28:       if (saveData == null) return;
29:       // Load the save data directly without reading from disk.
-->30:       SaveSystemManager.Load(saveSlot, saveData);
31:   }

SaveSystem.ApplySavedGameData() at /Plugins/Pixel Crushers/Common/Scripts/Save System/SaveSystem.cs:674
672:       {
673:           var saver = m_savers[i];
-->674:           if (saver != null) saver.ApplyData(savedGameData.GetData(saver.key));
675:       }
676:   }

SaveSystem/<LoadSceneCoroutine>d__111.MoveNext() at /Plugins/Pixel Crushers/Common/Scripts/Save System/SaveSystem.cs:785
783:       m_playerSpawnpoint = !string.IsNullOrEmpty(spawnpointName) ? GameObject.Find(spawnpointName) : null;
784:       if (!string.IsNullOrEmpty(spawnpointName) && m_playerSpawnpoint == null) Debug.LogWarning("Save System: Can't find spawnpoint '" + spawnpointName + "'. Is spelling and capitalization correct?");
-->785:       ApplySavedGameData(savedGameData);
786:   }

GUIUtility.ProcessEvent()
 
I think I have some idea of what is going on.
You are instantiating your character in each scene transition right?

So in my guess is that your Item is the character inventory item collection, and when the character is destroyed and respawned. The item still thinks it is in an Inventory item collection of the player, even though the player no longer exists. So when you are trying to add the item to the itemcollection it won't let you because it thinks the item is still in another item collection (which is the one from the previous instance of the player.)

In your particular use case simply doing this should do the trick
Code:
/// <summary>
/// Used by an itemCollection, this allows the item to know where it belongs to.
/// Note that immutable items can be part of many itemCollections.
/// </summary>
/// <param name="itemCollection">The ItemCollection that will contain the item.</param>
public virtual void AddItemCollection(ItemCollection itemCollection)
{
    if (ItemDefinition.IsMutable == false || m_ItemCollection == itemCollection) { return; }
    if (m_ItemCollection != null && m_ItemCollection.Inventory != null && m_ItemCollection != itemCollection) {
        Debug.LogWarning($"The Mutable Item '{name}' is unable to be added to a new Item Collection '{itemCollection}' in '{itemCollection.Inventory}' when it is already a member of an existing Item Collection '{m_ItemCollection}' in '{m_ItemCollection.Inventory}'.");
        return;
    }
    m_ItemCollection = itemCollection;
}

I'm also adding a OnDestroy function to ItemCollection to remove all references to items and do some cleanup.
Code:
/// <summary>
/// Remove the items reference to this itemCollection on destroy.
/// </summary>
public virtual void OnDestroy()
{
    m_Initialized = false;
    m_UpdateEventDisabled = true;
    
    for (int i = 0; i < m_ItemStacks.Count; i++) {
        if (m_ItemStacks[i] == null || m_ItemStacks[i].Item == null) {
            continue;
        }
        m_ItemStacks[i].Item.RemoveItemCollection(this);
    }
    
    m_ItemStacks.Clear();
    m_Inventory = null;
}

I call this from the Inventory On Destroy function:
Code:
        /// <summary>
        /// Unregister on destroy.
        /// </summary>
        private void OnDestroy()
        {
            if (m_ItemCollections != null) {
                for (int i = 0; i < m_ItemCollections.Count; i++) {
                    var itemCollection = m_ItemCollections[i];
                    itemCollection.OnDestroy();
                    EventHandler.UnregisterEvent(itemCollection, EventNames.c_ItemCollection_OnUpdate, () => OnItemCollectionUpdate(itemCollection));
                }
            }

            if (m_Currencies != null) {
                for (int i = 0; i < m_Currencies.Length; i++) {
                    var currencyOwner = m_Currencies[i];
                    EventHandler.UnregisterEvent(currencyOwner, EventNames.c_CurrencyOwner_OnUpdate, () => OnCurrencyChanged(currencyOwner));
                }
            }
        }

Let me know if you are still getting the issue after all those changes.
 
I think I have some idea of what is going on.
You are instantiating your character in each scene transition right?

So in my guess is that your Item is the character inventory item collection, and when the character is destroyed and respawned. The item still thinks it is in an Inventory item collection of the player, even though the player no longer exists. So when you are trying to add the item to the itemcollection it won't let you because it thinks the item is still in another item collection (which is the one from the previous instance of the player.)

In your particular use case simply doing this should do the trick
Code:
/// <summary>
/// Used by an itemCollection, this allows the item to know where it belongs to.
/// Note that immutable items can be part of many itemCollections.
/// </summary>
/// <param name="itemCollection">The ItemCollection that will contain the item.</param>
public virtual void AddItemCollection(ItemCollection itemCollection)
{
    if (ItemDefinition.IsMutable == false || m_ItemCollection == itemCollection) { return; }
    if (m_ItemCollection != null && m_ItemCollection.Inventory != null && m_ItemCollection != itemCollection) {
        Debug.LogWarning($"The Mutable Item '{name}' is unable to be added to a new Item Collection '{itemCollection}' in '{itemCollection.Inventory}' when it is already a member of an existing Item Collection '{m_ItemCollection}' in '{m_ItemCollection.Inventory}'.");
        return;
    }
    m_ItemCollection = itemCollection;
}

I'm also adding a OnDestroy function to ItemCollection to remove all references to items and do some cleanup.
Code:
/// <summary>
/// Remove the items reference to this itemCollection on destroy.
/// </summary>
public virtual void OnDestroy()
{
    m_Initialized = false;
    m_UpdateEventDisabled = true;
   
    for (int i = 0; i < m_ItemStacks.Count; i++) {
        if (m_ItemStacks[i] == null || m_ItemStacks[i].Item == null) {
            continue;
        }
        m_ItemStacks[i].Item.RemoveItemCollection(this);
    }
   
    m_ItemStacks.Clear();
    m_Inventory = null;
}

I call this from the Inventory On Destroy function:
Code:
        /// <summary>
        /// Unregister on destroy.
        /// </summary>
        private void OnDestroy()
        {
            if (m_ItemCollections != null) {
                for (int i = 0; i < m_ItemCollections.Count; i++) {
                    var itemCollection = m_ItemCollections[i];
                    itemCollection.OnDestroy();
                    EventHandler.UnregisterEvent(itemCollection, EventNames.c_ItemCollection_OnUpdate, () => OnItemCollectionUpdate(itemCollection));
                }
            }

            if (m_Currencies != null) {
                for (int i = 0; i < m_Currencies.Length; i++) {
                    var currencyOwner = m_Currencies[i];
                    EventHandler.UnregisterEvent(currencyOwner, EventNames.c_CurrencyOwner_OnUpdate, () => OnCurrencyChanged(currencyOwner));
                }
            }
        }

Let me know if you are still getting the issue after all those changes.
That's right, having my player set to DontDestroyOnLoad was too much trouble so I decided to instantiate it in every new scene instead.

Awesome, those changes fixed it! Seems to be working flawlessly now. Thanks a lot.
 
Top