How to save and load each inventory saver by key using ES3 in a different file?

Hi,

I already got the currency working, but with inventories there are a few issues.
These are all my item collections

invitems.png


CODE :

private byte[] GetSaverSerializedData(string saverKey)
{
SaveSystemManager.Save(saveSlot, false);

var isDataRetrieved = SaveSystemManager.TryGetSaveData(saverKey, out Serialization serializedData);

var data = ES3.Serialize(serializedData);
return data;
}


private void LoadSaver(byte[] data)
{
var saveData = ES3.Deserialize<Serialization>(data);

var s = FindObjectsOfType<SaverBase>().FirstOrDefault(s => s.FullKey.Equals(StashSaverKey));

s.DeserializeAndLoadSaveData(saveData);
}

[Button]
public void Save()
{
var dataToSave = SaveSaver(StashSaverKey);
ES3.Save(InventoriesSaveFile.StashKey, dataToSave, GetStashLocation());
}

[Button]
public void Load()
{
var data = ES3.Load<byte[]>(InventoriesSaveFile.StashKey, GetStashLocation());
LoadSaver(data);
}

1) I'd like to save them at different times not all at once so the question is about this script InventorySystemManagerItemSaver. Should I explicitely save it because I see it has a key too or it saves and loads automatically as long as it is in the scene and I use the code above to save a saver by key? I don't see any results when I load the items thats why I ask what should I do with that script. If I must save that saver too the question is when? I save my other savers at different time, should I save it with every saver in every saver's save file, then load it with every saver?

2) InventoryBridgeSaver - Does that save the Equippable Slots only? (Loadout too if it makes sense) or even if you have other collections not related to UCC it will save them so InventorySaver is not needed?

3) Looking at the image above I think I need for the UI part of saving:

(Main) Default - ItemShapeGridDataSaver
-EquippableSlots - ??
-Hotbar - ??
-Equippable - ??
-Wearable Internal Torso - ItemShapeGridDataSaver
-Wearable Internal Head - ItemShapeGridDataSaver
-Wearable External - ??


Stash / Storage inventory uses ItemShapeGridDataSaver + InventorySaver
I save and load normally during 1 play session in the menu scene, when I quit the game and restart the items never load its just empty collection.

3) Is it possible loading broken saves to break your item definitions inside the database? A few times I see my stackable item defs losing their parent and I gotta manually assign it in the database after loading a broken save while experimenting.

I have so many questions and no answers in the documentations nor the demos.
 
Last edited:
1)
The InventorySystemManagerItemSaver saves the ItemAttributes for Mutable items. The InventorSaver only saves the Item ID and notifies that the InventorySystemManagerItemSaver should save that item. If you check the top of each of those scripts you'll see the priority of the save and load order. This is important because the InventorySystemManagerItemSaver needs to save after the InventorySaver otherwise some items wouldn't be saved propely. The InventorySystemManagerItemSaver should be the last to save and the first to load.

My guess is that your are not loading saving and loading the InventorySystemManagerItemSaver and that's why it's not working as expected. But there could be other factors in play

2) if you are using an InventoryBridgeSaver for your character, do not use an InventorySaver. The InventoryBridgeSaver will save the nventory for you. Essentially it does the same thing but additionally it also re-equips items.

3)
For Hotbar, EquippableSlots, etc... you could use the ItemViewSlotsContainerSaver. It's a generic saver that saves the state of ItemViewSlotContainers. I've used it mostly for hotbars, but it should work in other cases too.
I don't think you are using any, but if you have an InventoryGrid, you also have an InventoryGridSaver and InventoryGridIndexDataSaver.


4)
That's not normal at all. No changes at runtime should affect ItemDefinitions in any way. Loading and Saving should only affect items, not definitions or categories.


I hope that helps guide you in the right direction
I'll update the Save/Load documentation page to explain things in more detail like I did above.
 
I will make a post with what bugs and tricks I found so far to make the save system better for others, but the last thing Im stuck with is the hotbar / equippable slots using ItemViewSlotContainerSaver

1) For the UCC Equippable slots I cannot call collection.RemoveAll() for some reason it throws an exception.

2) When I load my hotbar and equippable slots I get a nullpointer

ItemSlotCollection.cs

if (item.IsUnique == false && m_ItemsBySlot.Item.StackableEquivalentTo(item)) { return i; }


I found that m_ItemsBySlot.Item is NULL.

I use ItemViewSlotCollectionSaver on one other occasion - when I save/load the equipment (helmet, armor etc) there 1 slot accepts 1 item and I got no problem loading, while in the hotbar example it accepts everything with a category = Hotbar, perhaps that's why the error occurs?

3) One other thing I have to mention not related to saving, but related to that ItemSlotCollection method I took the piece of code from - after the last uis update my gun started appearing on the last slot by default instead of the first like it used to. If I do m_ItemsBySlot.Item == null => return i;

My item is back in the 1st slot, that doesnt fix the loading issue tho, itd always load my gun on slot1 even if I save it in slot2.
 
Last edited:
When you get an error in the console. Please copy paste the full error message stack so that I can see where it comes from.

In the last update we made a lot of changes to ItemSlotCollection and it seems to have caused some issues in a few places. I'm in the process of fixing all those issues for the next update. And your issue seems related to that so your error messages would really help me identify the problems.
 
If I call RemoveAll on the Equippable Slots collection I get


NullReferenceException: Object reference not set to an instance of an object
Opsive.UltimateInventorySystem.Core.InventoryCollections.ItemSlotCollection.GetItemSlotIndex (Opsive.UltimateInventorySystem.Core.Item item) (at Assets/Opsive/UltimateInventorySystem/Scripts/Core/InventoryCollections/ItemSlotCollection.cs:496)
Opsive.UltimateInventorySystem.Core.InventoryCollections.ItemSlotCollection.RemoveItem (Opsive.UltimateInventorySystem.Core.DataStructures.ItemInfo itemInfo) (at Assets/Opsive/UltimateInventorySystem/Scripts/Core/InventoryCollections/ItemSlotCollection.cs:432)
Opsive.UltimateInventorySystem.Core.InventoryCollections.ItemCollection.RemoveAll (System.Boolean disableUpdateEventsWhileRemoving) (at Assets/Opsive/UltimateInventorySystem/Scripts/Core/InventoryCollections/ItemCollection.cs:907)
_Project.Scripts.ThirdParty.OpsiveAssets.Inventory.Utility.InventorySaverUtilityBio.EmptyCollectionWithId (Opsive.UltimateInventorySystem.Core.InventoryCollections.Inventory inventory, System.String collectionId) (at Assets/_Project/Scripts/ThirdParty/OpsiveAssets/Inventory/Utility/InventorySaverUtilityBio.cs:61)
_Project.Scripts.ThirdParty.EasySaveAsset.SaveLoaders.Savers.OpsiveSaveSystemInventorySaveLoader.Load () (at Assets/_Project/Scripts/ThirdParty/EasySaveAsset/SaveLoaders/Savers/OpsiveSaveSystemInventorySaveLoader.cs:105)
System.Reflection.RuntimeMethodInfo.Invoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) (at <c9d3ffd4b98649ee9989e1908eaca019>:0)
Rethrow as TargetInvocationException: Exception has been thrown by the target of an invocation.
System.Reflection.RuntimeMethodInfo.Invoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) (at <c9d3ffd4b98649ee9989e1908eaca019>:0)
System.Reflection.MethodBase.Invoke (System.Object obj, System.Object[] parameters) (at <c9d3ffd4b98649ee9989e1908eaca019>:0)
Sirenix.OdinInspector.Editor.Drawers.DefaultMethodDrawer.InvokeMethodInfo (System.Reflection.MethodInfo methodInfo) (at <995d6fe9fb55440a871106ca37ef1003>:0)
UnityEngine.Debug:LogException(Exception)
Sirenix.OdinInspector.Editor.Drawers.DefaultMethodDrawer:InvokeMethodInfo(MethodInfo)
Sirenix.OdinInspector.Editor.Drawers.DefaultMethodDrawer:InvokeButton()
Sirenix.OdinInspector.Editor.Drawers.DefaultMethodDrawer:DrawNormalButton()
Sirenix.OdinInspector.Editor.Drawers.DefaultMethodDrawer:DrawPropertyLayout(GUIContent)
Sirenix.OdinInspector.Editor.OdinDrawer:DrawProperty(GUIContent)
Sirenix.OdinInspector.Editor.InspectorProperty:Draw(GUIContent)
Sirenix.OdinInspector.Editor.InspectorProperty:Draw()
Sirenix.OdinInspector.Editor.Drawers.UnityObjectRootDrawer`1:DrawPropertyLayout(GUIContent)
Sirenix.OdinInspector.Editor.OdinDrawer:CallNextDrawer(GUIContent)
Sirenix.OdinInspector.Editor.Drawers.FixBrokenUnityObjectWrapperDrawer`1:DrawPropertyLayout(GUIContent)
Sirenix.OdinInspector.Editor.OdinDrawer:DrawProperty(GUIContent)
Sirenix.OdinInspector.Editor.InspectorProperty:Draw(GUIContent)
Sirenix.OdinInspector.Editor.PropertyTree:DrawProperties()
Sirenix.OdinInspector.Editor.PropertyTree:Draw(Boolean)
Sirenix.OdinInspector.Editor.OdinEditor:DrawTree()
Sirenix.OdinInspector.Editor.OdinEditor:DrawOdinInspector()
Sirenix.OdinInspector.Editor.OdinEditor:OnInspectorGUI()
UnityEngine.GUIUtility:processEvent(Int32, IntPtr, Boolean&)
 
The item collection error that I mentioned in (2) - it might be offset by a line or two because I did a change to test for null in that GetTargetSlotIndex method

the line it points to is this

if (item.IsUnique == false && m_ItemsBySlot.Item.StackableEquivalentTo(item)) { return i; }


NullReferenceException: Object reference not set to an instance of an object
Opsive.UltimateInventorySystem.Core.InventoryCollections.ItemSlotCollection.GetTargetSlotIndex (Opsive.UltimateInventorySystem.Core.Item item) (at Assets/Opsive/UltimateInventorySystem/Scripts/Core/InventoryCollections/ItemSlotCollection.cs:468)
Opsive.UltimateInventorySystem.Core.InventoryCollections.ItemSlotCollection.AddItem (Opsive.UltimateInventorySystem.Core.DataStructures.ItemInfo itemInfo, Opsive.UltimateInventorySystem.Core.DataStructures.ItemStack stackTarget) (at Assets/Opsive/UltimateInventorySystem/Scripts/Core/InventoryCollections/ItemSlotCollection.cs:187)
Opsive.UltimateInventorySystem.Core.InventoryCollections.ItemCollection.AddItem (Opsive.UltimateInventorySystem.Core.Item item, System.Int32 amount) (at Assets/Opsive/UltimateInventorySystem/Scripts/Core/InventoryCollections/ItemCollection.cs:620)
Opsive.UltimateInventorySystem.Core.InventoryCollections.ItemCollection.AddItems (Opsive.Shared.Utility.ListSlice`1[T] itemAmounts) (at Assets/Opsive/UltimateInventorySystem/Scripts/Core/InventoryCollections/ItemCollection.cs:656)
Opsive.UltimateCharacterController.Integrations.UltimateInventorySystem.InventoryBridgeSaver.DeserializeAndLoadSaveData (Opsive.Shared.Utility.Serialization serializedSaveData) (at Assets/Opsive/UltimateCharacterController/Integrations/UltimateInventorySystem/Scripts/InventoryBridgeSaver.cs:203)
_Project.Scripts.ThirdParty.OpsiveAssets.Inventory.Utility.InventorySaverUtilityBio.LoadSaver (System.String data, System.String saverKey) (at Assets/_Project/Scripts/ThirdParty/OpsiveAssets/Inventory/Utility/InventorySaverUtilityBio.cs:43)
_Project.Scripts.ThirdParty.EasySaveAsset.SaveLoaders.Savers.OpsiveSaveSystemInventorySaveLoader.Load () (at Assets/_Project/Scripts/ThirdParty/EasySaveAsset/SaveLoaders/Savers/OpsiveSaveSystemInventorySaveLoader.cs:109)
System.Reflection.RuntimeMethodInfo.Invoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) (at <c9d3ffd4b98649ee9989e1908eaca019>:0)
Rethrow as TargetInvocationException: Exception has been thrown by the target of an invocation.
System.Reflection.RuntimeMethodInfo.Invoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) (at <c9d3ffd4b98649ee9989e1908eaca019>:0)
System.Reflection.MethodBase.Invoke (System.Object obj, System.Object[] parameters) (at <c9d3ffd4b98649ee9989e1908eaca019>:0)
Sirenix.OdinInspector.Editor.Drawers.DefaultMethodDrawer.InvokeMethodInfo (System.Reflection.MethodInfo methodInfo) (at <995d6fe9fb55440a871106ca37ef1003>:0)
UnityEngine.Debug:LogException(Exception)
Sirenix.OdinInspector.Editor.Drawers.DefaultMethodDrawer:InvokeMethodInfo(MethodInfo)
Sirenix.OdinInspector.Editor.Drawers.DefaultMethodDrawer:InvokeButton()
Sirenix.OdinInspector.Editor.Drawers.DefaultMethodDrawer:DrawNormalButton()
Sirenix.OdinInspector.Editor.Drawers.DefaultMethodDrawer:DrawPropertyLayout(GUIContent)
Sirenix.OdinInspector.Editor.OdinDrawer:DrawProperty(GUIContent)
Sirenix.OdinInspector.Editor.InspectorProperty:Draw(GUIContent)
Sirenix.OdinInspector.Editor.InspectorProperty:Draw()
Sirenix.OdinInspector.Editor.Drawers.UnityObjectRootDrawer`1:DrawPropertyLayout(GUIContent)
Sirenix.OdinInspector.Editor.OdinDrawer:CallNextDrawer(GUIContent)
Sirenix.OdinInspector.Editor.Drawers.FixBrokenUnityObjectWrapperDrawer`1:DrawPropertyLayout(GUIContent)
Sirenix.OdinInspector.Editor.OdinDrawer:DrawProperty(GUIContent)
Sirenix.OdinInspector.Editor.InspectorProperty:Draw(GUIContent)
Sirenix.OdinInspector.Editor.PropertyTree:DrawProperties()
Sirenix.OdinInspector.Editor.PropertyTree:Draw(Boolean)
Sirenix.OdinInspector.Editor.OdinEditor:DrawTree()
Sirenix.OdinInspector.Editor.OdinEditor:DrawOdinInspector()
Sirenix.OdinInspector.Editor.OdinEditor:OnInspectorGUI()
UnityEngine.GUIUtility:processEvent(Int32, IntPtr, Boolean&)
 
1)
This is a known bug, I added a null check:
Code:
/// <summary>
/// Returns the slot where the item is set.
/// </summary>
/// <param name="item">The item that is in the collection.</param>
/// <returns>The slot where the item is equipped. -1 indicates no slot.</returns>
public int GetItemSlotIndex(Item item)
{
    var stackableEquivalentItemIndex = -1;
    for (int i = 0; i < m_ItemSlotSet.ItemSlots.Count; i++) {
        var itemSlot = m_ItemSlotSet.ItemSlots[i];
        if (itemSlot.Category != null && !itemSlot.Category.InherentlyContains(item)) {
            continue;
        }
        if (m_ItemsBySlot[i] == null) { continue; }
        
        if (m_ItemsBySlot[i].Item == null) { continue; }
        if (m_ItemsBySlot[i].Item == item) { return i; }
        if (m_ItemsBySlot[i].Item.StackableEquivalentTo(item)) {
            stackableEquivalentItemIndex = i;
        }
    }
    return stackableEquivalentItemIndex;
}


2) Someone reported the same error at the same time you did. Here is the fix:

Code:
/// <summary>
/// Returns a slot that is suitable for the item provided.
/// </summary>
/// <param name="item">The item that needs a slot.</param>
/// <returns>The desired slot. -1 indicates no slot.</returns>
public int GetTargetSlotIndex(Item item)
{
    if (m_ItemSlotSet == null || m_ItemSlotSet.ItemSlots == null) {
        Debug.LogWarning("Item Slot Collection is missing an Item Slot Set.");
        return -1;
    }
    var matchingCategoryUsedSlot = -1;
    var matchingCategoryEmptySlot = -1;
    for (int i = 0; i < m_ItemSlotSet.ItemSlots.Count; i++) {
        var itemSlot = m_ItemSlotSet.ItemSlots[i];
        if (!itemSlot.Category.InherentlyContains(item)) {
            continue;
        }
        matchingCategoryUsedSlot = i;
        if (m_ItemsBySlot[i] == null || m_ItemsBySlot[i].Item == null) {
            if (matchingCategoryEmptySlot != -1) { continue; }
            matchingCategoryEmptySlot = i;
            continue;
        }
        if (item.IsUnique == false && m_ItemsBySlot[i].Item.StackableEquivalentTo(item)) { return i; }
    }
    if (matchingCategoryEmptySlot != -1) { return matchingCategoryEmptySlot; }
    return matchingCategoryUsedSlot;
}


3) There's actually another bug in ItemCollection.cs. I mention it in this post
All of these issues were caused by the last update. We have an update planned next week so all of those will be fixed then.

I'm very sorry about that.
 
No errors after changing those 2 scripts still my item refuses to remember I put it on slot 2 when I saved it seems it finds the first empty slot perhaps ?

-----
I also want to report another bug which you dont see probably because the demo for save and load uses another UI for picking up slots and you go back the grids redraw. Basically the items wont change positions unless you start clicking on the Ui to force it to update.

Its an easy fix you have to FindObjectsOfType ItemShapeGrid, loop thro them all and call grid.ReDraw();

The InventoryBridgeSaver is missing the "Additive" option from the InventorySaver, thus the collections arent emptied before loading, Im doing it manually as of right now, but will be nice to copy paste that for loop you got in the InventorySaver to remove all items.
 
No errors after changing those 2 scripts still my item refuses to remember I put it on slot 2 when I saved it seems it finds the first empty slot perhaps
In what UI is that? Is it the Hotbar or something else? Are you using the ItemViewSlotContainerSaver that I mentioned in the previous post. Can you send me a screenshot?

I also want to report another bug which you dont see probably because the demo for save and load uses another UI for picking up slots and you go back the grids redraw. Basically the items wont change positions unless you start clicking on the Ui to force it to update.

Its an easy fix you have to FindObjectsOfType ItemShapeGrid, loop thro them all and call grid.ReDraw();
Is your InventoryGrid set to redrawn on Inventory update? You can also set it to update on enable:
1680268323158.png
If you are using a DisplayPanel you can also tell the binding to draw on open:
1680268394110.png

The InventoryBridgeSaver is missing the "Additive" option from the InventorySaver, thus the collections arent emptied before loading, Im doing it manually as of right now, but will be nice to copy paste that for loop you got in the InventorySaver to remove all items.
You're right, I've added it now:
Code:
[Tooltip("Is the save data added to the loadout of does it overwrite it.")]
[SerializeField] protected bool m_Additive;

/// <summary>
/// Deserialize and load the save data.
/// </summary>
/// <param name="serializedSaveData">The serialized save data.</param>
public override void DeserializeAndLoadSaveData(Serialization serializedSaveData)
{
    if (m_Inventory == null) { return; }
    var savedData = serializedSaveData.DeserializeFields(MemberVisibility.All) as InventoryBridgeSaveData?;
    if (!m_Additive) {
        var itemCollectionCount = m_Inventory.GetItemCollectionCount();
        for (int i = 0; i < itemCollectionCount; i++) {
            m_Inventory.GetItemCollection(i).RemoveAll();
        }
    }
    
    if (savedData.HasValue == false) {
        return;
    }
 
1) I use ItemViewSlotContainerSaver and my hotbar is set similar to UCC equipment setup, I also have UCC equipment for guns etc that has the same issue

iscv.png

It only accepts items of type Hotbar and I got 3 slots. first two are empty I put it on the 3rd slot - save - load and it's back on the first slot.

UCC weaponry

prr.png
 
Last edited:
Ah I see, those a ItemSlotCollection Views.
In that case the ItemViewSlotContainerSaver is not necessary because the slot of the item is defined by the slot within the ItemSlotCollection not by the UI itslef.
So if it's not working as expected just with an InventorySaver that means there is a bug. I'll look into it further, I might need to do a special case for saving items within an ItemSlotCollection by saving the slot index of the item.
I'll add that in my list of TODOs and keep you posted. It probably won't make the next update which is planned later this week, but pehaps the next one after that.
 
Top