Hello, due to functional requirements, I need to nest multiple levels of items when creating, saving, and loading items.
Upon reviewing the code related to saving nested items, I noticed that it only supports the ItemAmounts type and one level of nesting. There is significant room for improvement.
I believe that the extensibility of reading and writing nested items can be enhanced through an interface.
Here is the interface and the types that inherit from it:
To obtain the nested items to be saved during the saving process, recursive searching of item attributes can be implemented:
UltimateInventorySystem\Scripts\SaveSystem\InventorySystemManagerItemSaver.cs
When loading the saved data, we can directly traverse and load all nested items:
UltimateInventorySystem\Scripts\SaveSystem\InventorySystemManagerItemSaver.cs
I have implemented an additional feature where nested items are recursively initialized and registered during item creation:
The InitializeItemAttributes method is called in the CreateItem method of the override of InventorySystemFactory
I have performed some basic tests on this code, and it is able to load and save nested items with multiple levels.
Are there any issues with this code? When writing this code, I assumed that registered items with the same ID are the same instance of the Item class, without any exceptions. Is this assumption correct? I would appreciate your guidance!
Upon reviewing the code related to saving nested items, I noticed that it only supports the ItemAmounts type and one level of nesting. There is significant room for improvement.
I believe that the extensibility of reading and writing nested items can be enhanced through an interface.
Here is the interface and the types that inherit from it:
Code:
/// <summary>
/// Interface for containers that hold nested items.
/// </summary>
public interface INestedItemsContainer
{
/// <summary>
/// Gets the nested items.
/// </summary>
/// <param name="nestedItems">The list to populate with nested items.</param>
/// <returns>The count of nested items.</returns>
int GetNestedItems(List<Item> nestedItems);
/// <summary>
/// Loads the nested items.
/// </summary>
/// <param name="registeredItems">The list of registered items to load.</param>
void LoadNestedItems(List<Item> registeredItems);
}
Code:
/// <summary>
/// Specifies the amount of each item.
/// </summary>
[Serializable]
public struct ItemAmount : IEquatable<ItemAmount>, IObjectAmount<Item>, INestedItemsContainer
{
...
/// <summary>
/// Gets the nested items.
/// </summary>
/// <param name="nestedItems">The list to populate with nested items.</param>
/// <returns>The count of nested items.</returns>
public int GetNestedItems(List<Item> nestedItems)
{
if (Item == null || Item.ItemDefinition == null) { return 0; }
nestedItems.Add(Item);
return 1;
}
/// <summary>
/// Loads the nested items.
/// </summary>
/// <param name="registeredItems">The list of registered items to load.</param>
public void LoadNestedItems(List<Item> registeredItems)
{
if (Item == null || Item.ItemDefinition == null) { return; }
for (int j = 0; j < registeredItems.Count; j++)
{
var registeredItem = registeredItems[j];
if (Item.ID == registeredItem.ID)
{
m_Item = registeredItem;
break;
}
}
}
...
}
Code:
/// <summary>
/// Item Amounts is an array of item amounts.
/// </summary>
[System.Serializable]
public class ItemAmounts : ObjectAmounts<Item, ItemAmount>, INestedItemsContainer
{
...
/// <summary>
/// Gets the nested items.
/// </summary>
/// <param name="nestedItems">The list to populate with nested items.</param>
/// <returns>The count of nested items.</returns>
public int GetNestedItems(List<Item> nestedItems)
{
int count = 0;
for (int i = 0; i < Count; i++)
{
var nestedItem = Array[i].Item;
if ((nestedItem == null || nestedItem.ItemDefinition == null)) { continue; }
bool find = false;
foreach (var item in nestedItems)
{
if (nestedItem.ID == item.ID)
{
find = true;
break;
}
}
if (find) { continue; }
nestedItems.Add(nestedItem);
count++;
}
return count;
}
/// <summary>
/// Loads the nested items.
/// </summary>
/// <param name="registeredItems">The list of registered items to load.</param>
public void LoadNestedItems(List<Item> registeredItems)
{
for (int i = 0; i < Count; i++)
{
var nestedItem = Array[i].Item;
if (nestedItem == null || nestedItem.ItemDefinition == null) { continue; }
for (int j = 0; j < registeredItems.Count; j++)
{
var registeredItem = registeredItems[j];
if (nestedItem.ID == registeredItem.ID)
{
Array[i] = new ItemAmount(registeredItem, Array[i].Amount);
break;
}
}
}
}
...
}
To obtain the nested items to be saved during the saving process, recursive searching of item attributes can be implemented:
Code:
/// <summary>
/// Recursively retrieves nested items from the specified item.
/// </summary>
/// <param name="item">The item to retrieve nested items from.</param>
/// <param name="nestedItems">The list to populate with nested items.</param>
public void GetNestedItemsRecursive(Item item, List<Item> nestedItems)
{
for (int i = 0; i < item.ItemAttributeCollection.Count; i++)
{
var attribute = item.ItemAttributeCollection[i];
// Don't get if it inherits.
if (attribute.VariantType == VariantType.Inherit) { continue; }
if (!typeof(INestedItemsContainer).IsAssignableFrom(attribute.GetValueType())) { continue; }
var nestedItemsContainer = (INestedItemsContainer)attribute.GetValueAsObject();
var containerNestedItems = new List<Item>();
if (nestedItemsContainer.GetNestedItems(containerNestedItems) == 0) { continue; }
for (int j = containerNestedItems.Count - 1; j >= 0; j--)
{
foreach (var nestedItem in nestedItems)
{
if (nestedItem.ID == containerNestedItems[j].ID)
{
containerNestedItems.RemoveAt(j);
break;
}
}
}
nestedItems.AddRange(containerNestedItems);
foreach (var containerNestedItem in containerNestedItems)
{
GetNestedItemsRecursive(containerNestedItem, nestedItems);
}
}
}
UltimateInventorySystem\Scripts\SaveSystem\InventorySystemManagerItemSaver.cs
Code:
/// <summary>
/// Add nested items to the save data.
/// </summary>
/// <param name="item">The item containing nested items.</param>
/// <param name="itemsToSave">The item list.</param>
protected virtual void AddNestedItemsToSave(Item item, Stack<Item> itemsToSave)
{
var nestedItems = new List<Item>();
GetNestedItemsRecursive(item, nestedItems);
foreach (var nestedItem in nestedItems)
{
nestedItem.Serialize();
itemsToSave.Push(nestedItem);
}
}
When loading the saved data, we can directly traverse and load all nested items:
Code:
/// <summary>
/// Loads nested items for the specified item.
/// </summary>
/// <param name="item">The item to load nested items for.</param>
public void LoadNestedItems(Item item)
{
for (int i = 0; i < item.ItemAttributeCollection.Count; i++)
{
var attribute = item.ItemAttributeCollection[i];
// Don't load if it inherits.
if (attribute.VariantType == VariantType.Inherit) { continue; }
if (!typeof(INestedItemsContainer).IsAssignableFrom(attribute.GetValueType())) { continue; }
var nestedItemsContainer = (INestedItemsContainer)attribute.GetValueAsObject();
var containerNestedItems = new List<Item>();
if (nestedItemsContainer.GetNestedItems(containerNestedItems) == 0) { continue; }
var registeredItems = new List<Item>();
for (int j = 0; j < containerNestedItems.Count; j++)
{
var containerNestedItem = containerNestedItems[j];
var registeredItem = InventorySystemManager.GetItem(containerNestedItem.ID);
registeredItems.Add(registeredItem);
}
nestedItemsContainer.LoadNestedItems(registeredItems);
attribute.SetOverrideValueAsObject(nestedItemsContainer);
}
}
UltimateInventorySystem\Scripts\SaveSystem\InventorySystemManagerItemSaver.cs
Code:
if (m_UsingNestedItems)
{
foreach ( var item in m_CurrentSaveData.Items)
{
LoadNestedItems(item);
}
}
I have implemented an additional feature where nested items are recursively initialized and registered during item creation:
Code:
using UISItem = Opsive.UltimateInventorySystem.Core.Item;
...
/// <summary>
/// Initializes the attributes of an item.
/// </summary>
/// <param name="item">The item to initialize.</param>
public void InitializeItemAttributes(UISItem item)
{
if (item == null || !item.IsInitialized)
{
return;
}
LoadNestedItems(item);
...
}
/// <summary>
/// Loads nested items for the specified item.
/// </summary>
/// <param name="item">The item to load nested items for.</param>
protected void LoadNestedItems(UISItem item)
{
for (int i = 0; i < item.ItemAttributeCollection.Count; i++)
{
var attribute = item.ItemAttributeCollection[i];
if (!typeof(INestedItemsContainer).IsAssignableFrom(attribute.GetValueType())) { continue; }
var nestedItemsContainer = (INestedItemsContainer)attribute.GetValueAsObject();
var nestedItems = new List<UISItem>();
if (nestedItemsContainer.GetNestedItems(nestedItems) == 0) { continue; }
var registeredItems = new List<UISItem>();
for (int j = 0; j < nestedItems.Count; j++)
{
var nestedItem = nestedItems[j];
var registeredItem = InventorySystemManager.GetItem(nestedItem.ID);
if (registeredItem == null)
{
nestedItem.Initialize(false, true, true);
InventorySystemManager.ItemRegister.Register(ref nestedItem);
InitializeItemAttributes(nestedItem);
registeredItems.Add(nestedItem);
}
else
{
registeredItems.Add(registeredItem);
}
}
nestedItemsContainer.LoadNestedItems(registeredItems);
attribute.SetOverrideValueAsObject(nestedItemsContainer);
}
}
I have performed some basic tests on this code, and it is able to load and save nested items with multiple levels.
Are there any issues with this code? When writing this code, I assumed that registered items with the same ID are the same instance of the Item class, without any exceptions. Is this assumption correct? I would appreciate your guidance!