diff --git a/Libraries/SPTarkov.Server.Core/Extensions/ItemExtensions.cs b/Libraries/SPTarkov.Server.Core/Extensions/ItemExtensions.cs
index 317618f2..d0b6b370 100644
--- a/Libraries/SPTarkov.Server.Core/Extensions/ItemExtensions.cs
+++ b/Libraries/SPTarkov.Server.Core/Extensions/ItemExtensions.cs
@@ -230,7 +230,6 @@ public static class ItemExtensions
}
///
- /// TODO: return IEnumerable and update all calling code
/// Get an item with its attachments (children)
///
/// List of items (item + possible children)
@@ -239,48 +238,63 @@ public static class ItemExtensions
/// list of Item objects
public static List- GetItemWithChildren(this IEnumerable
- items, MongoId baseItemId, bool excludeStoredItems = false)
{
- // Use dictionary to make key lookup faster, convert to list before being returned
+ // Convert to list if not already
var itemList = items.ToList();
- OrderedDictionary result = [];
- // Find desired root item
- var desiredRootItem = itemList.FirstOrDefault(item => item.Id == baseItemId);
- if (desiredRootItem is null)
+ // Create dict of items by parentId
+ var childrenByParent = new Dictionary>(itemList.Count);
+ foreach (var child in itemList)
+ {
+ var key = child.ParentId;
+ if (key is null)
+ {
+ continue;
+ }
+ if (childrenByParent.TryGetValue(key, out var list))
+ {
+ list.Add(child);
+ }
+ else
+ {
+ childrenByParent[key] = [child];
+ }
+ }
+
+ // Find root item
+ var root = itemList.FirstOrDefault(i => i.Id == baseItemId);
+ if (root is null)
{
// Root not found, nothing to return, exit
return [];
}
- result.Add(desiredRootItem.Id, desiredRootItem);
- var rootItemIdString = desiredRootItem.Id.ToString();
- foreach (var item in itemList)
+ var result = new List
- ();
+ var stack = new Stack
- ();
+ stack.Push(root);
+
+ while (stack.Count > 0)
{
- if (result.ContainsKey(item.Id))
+ var current = stack.Pop();
+ result.Add(current);
+
+ if (!childrenByParent.TryGetValue(current.Id.ToString(), out var children))
{
- // Already processed, skip
+ // No children, skip to next
continue;
}
- // Skip items with different parentId
- if (item.ParentId != rootItemIdString)
+ foreach (var child in children)
{
- continue;
- }
-
- // Is stored in parent and disallowed
- if (excludeStoredItems && item.Location is not null)
- {
- continue;
- }
-
- // Item may have children, check
- foreach (var subItem in GetItemWithChildren(itemList, item.Id))
- {
- result.Add(subItem.Id, subItem);
+ // child item has a location property = is stored inside parent
+ if (excludeStoredItems && child.Location is not null)
+ {
+ continue;
+ }
+ stack.Push(child);
}
}
- return result.Values.ToList();
+ return result;
}
///
diff --git a/Libraries/SPTarkov.Server.Core/Helpers/BotGeneratorHelper.cs b/Libraries/SPTarkov.Server.Core/Helpers/BotGeneratorHelper.cs
index 64a99867..8e6bec40 100644
--- a/Libraries/SPTarkov.Server.Core/Helpers/BotGeneratorHelper.cs
+++ b/Libraries/SPTarkov.Server.Core/Helpers/BotGeneratorHelper.cs
@@ -435,7 +435,7 @@ public class BotGeneratorHelper(
/// Root items tpl id
/// Item to add
/// Inventory to add item+children into
- ///
+ /// Container Ids with no space for more items
/// ItemAddedResult result object
public ItemAddedResult AddItemWithChildrenToEquipmentSlot(
HashSet equipmentSlots,
@@ -446,6 +446,8 @@ public class BotGeneratorHelper(
HashSet? containersIdFull = null
)
{
+ var itemWithChildrenList = itemWithChildren.ToList();
+
// Track how many containers are unable to be found
var missingContainerCount = 0;
foreach (var equipmentSlotId in equipmentSlots)
@@ -455,8 +457,8 @@ public class BotGeneratorHelper(
continue;
}
- // Get container to put item into
- var container = inventory.Items.FirstOrDefault(item => item.SlotId == equipmentSlotId.ToString());
+ // Get container from inventory to put item into
+ var container = inventory.Items?.FirstOrDefault(item => item.SlotId == equipmentSlotId.ToString());
if (container is null)
{
missingContainerCount++;
@@ -466,7 +468,7 @@ public class BotGeneratorHelper(
if (logger.IsLogEnabled(LogLevel.Debug))
{
logger.Debug(
- $"Unable to add item: {itemWithChildren.FirstOrDefault()?.Template} to bot as it lacks the following containers: {string.Join(",", equipmentSlots)}"
+ $"Unable to add item: {itemWithChildrenList.FirstOrDefault()?.Template} to bot as it lacks the following containers: {string.Join(",", equipmentSlots)}"
);
}
@@ -494,7 +496,7 @@ public class BotGeneratorHelper(
}
// Get x/y grid size of item
- var (itemWidth, itemHeight) = inventoryHelper.GetItemSize(rootItemTplId, rootItemId, itemWithChildren);
+ var (itemWidth, itemHeight) = inventoryHelper.GetItemSize(rootItemTplId, rootItemId, itemWithChildrenList);
// Iterate over each grid in the container and look for a big enough space for the item to be placed in
var currentGridCount = 1;
@@ -520,7 +522,7 @@ public class BotGeneratorHelper(
);
// Get root items in container we can iterate over to find out what space is free
- var containerItemsToCheck = existingContainerItems.Where(x => x.SlotId == slotGrid.Name).ToList();
+ var containerItemsToCheck = existingContainerItems.Where(x => x.SlotId == slotGrid.Name);
var containerItemsWithChildren = GetContainerItemsWithChildren(containerItemsToCheck, inventory.Items);
if (slotGrid.Props is not null)
@@ -539,7 +541,7 @@ public class BotGeneratorHelper(
// Free slot found, add item
if (findSlotResult.Success ?? false)
{
- var parentItem = itemWithChildren.FirstOrDefault(i => i.Id == rootItemId);
+ var parentItem = itemWithChildrenList.FirstOrDefault(i => i.Id == rootItemId);
// Set items parent to container id
if (parentItem is not null)
@@ -554,7 +556,7 @@ public class BotGeneratorHelper(
};
}
- (inventory.Items ?? []).AddRange(itemWithChildren);
+ (inventory.Items ?? []).AddRange(itemWithChildrenList);
return ItemAddedResult.SUCCESS;
}
@@ -570,7 +572,7 @@ public class BotGeneratorHelper(
// No space in this grid, move to next container grid and try again
}
- // if we got to this point, the item couldn't be placed on the container
+ // If we got to this point, the item couldn't be placed on the container
if (containersIdFull is null)
{
continue;
@@ -608,14 +610,14 @@ public class BotGeneratorHelper(
return result;
}
- // Filter out all items without location prop, (child items)
- var itemsWithoutLocation = inventoryItems.Where(item => item.Location is null);
+ // Get collection of items likely to be children of root items
+ var itemsWithoutLocation = inventoryItems.Where(item => item.Location is null && item.ParentId is not null).ToList();
foreach (var rootItem in containerRootItems)
{
// Check item in container for children, store for later insertion into `containerItemsToCheck`
// (used later when figuring out how much space weapon takes up)
- List
- itemsToFilter = [.. itemsWithoutLocation, rootItem];
- var itemWithChildItems = itemsToFilter.GetItemWithChildren(rootItem.Id);
+ itemsWithoutLocation.Insert(0, rootItem);
+ var itemWithChildItems = itemsWithoutLocation.GetItemWithChildren(rootItem.Id);
// Item had children, replace existing data with item + its children
result.AddRange(itemWithChildItems);
diff --git a/UnitTests/Tests/Extensions/ItemTests.cs b/UnitTests/Tests/Extensions/ItemTests.cs
index 20dcd819..71a75e02 100644
--- a/UnitTests/Tests/Extensions/ItemTests.cs
+++ b/UnitTests/Tests/Extensions/ItemTests.cs
@@ -63,7 +63,12 @@ public class ItemTests
public void GetItemWithChildren_mods_and_inventory_item()
{
var testData = new List
- ();
- var rootItem = new Item { Id = new MongoId(), Template = ItemTpl.AMMOBOX_127X33_COPPER_20RND };
+ var rootItem = new Item
+ {
+ Id = new MongoId(),
+ Template = ItemTpl.AMMOBOX_127X33_COPPER_20RND,
+ ParentId = new MongoId(),
+ };
var childItem = new Item
{
Id = new MongoId(),
@@ -83,7 +88,8 @@ public class ItemTests
var result = testData.GetItemWithChildren(rootItem.Id, false);
- Assert.AreEqual(result[1].Id, childItem.Id);
+ Assert.Contains(childItem, result);
+ Assert.Contains(childItem2, result);
Assert.AreEqual(result.Count, 3);
}
@@ -292,4 +298,30 @@ public class ItemTests
Assert.AreEqual(false, profile.Inventory.Items.FirstOrDefault(item => item.Id == item2Id).Upd.SpawnedInSession);
Assert.AreEqual(true, profile.Inventory.Items.FirstOrDefault(item => item.Id == item3Id).Upd.SpawnedInSession);
}
+
+ [Test]
+ public void GetItemWithChildren_rootIdNotFound()
+ {
+ var testData = new List
- ();
+ var rootItem = new Item { Id = new MongoId(), Template = ItemTpl.AMMOBOX_127X33_COPPER_20RND };
+ var childItem = new Item
+ {
+ Id = new MongoId(),
+ Template = ItemTpl.AMMO_127X33_COPPER,
+ ParentId = rootItem.Id,
+ };
+ var childOfChild = new Item
+ {
+ Id = new MongoId(),
+ Template = ItemTpl.AMMO_26X75_GREEN,
+ ParentId = childItem.Id,
+ };
+ testData.Add(rootItem);
+ testData.Add(childItem);
+ testData.Add(childOfChild);
+
+ var result = testData.GetItemWithChildren(new MongoId(), true);
+
+ Assert.AreEqual(result.Count, 0);
+ }
}