diff --git a/Libraries/SPTarkov.Server.Core/Controllers/InsuranceController.cs b/Libraries/SPTarkov.Server.Core/Controllers/InsuranceController.cs
index c892a0a9..8025a706 100644
--- a/Libraries/SPTarkov.Server.Core/Controllers/InsuranceController.cs
+++ b/Libraries/SPTarkov.Server.Core/Controllers/InsuranceController.cs
@@ -407,13 +407,12 @@ public class InsuranceController(
if (itemRoll ?? false)
{
// Check to see if this item is a parent in the parentAttachmentsMap. If so, do a look-up for *all* of
- // its children and mark them for deletion as well. Additionally remove the parent (and its children)
+ // its children and mark them for deletion as well. Also remove parent (and its children)
// from the parentAttachmentsMap so that it's children are not rolled for later in the process.
if (parentAttachmentsMap.ContainsKey(insuredItem.Id))
{
// This call will also return the parent item itself, queueing it for deletion as well.
- var itemAndChildren = _itemHelper.FindAndReturnChildrenAsItems(
- insured.Items,
+ var itemAndChildren = insured.Items.FindAndReturnChildrenAsItems(
insuredItem.Id
);
foreach (var item in itemAndChildren)
diff --git a/Libraries/SPTarkov.Server.Core/Controllers/ProfileController.cs b/Libraries/SPTarkov.Server.Core/Controllers/ProfileController.cs
index e5cfa1aa..508de31f 100644
--- a/Libraries/SPTarkov.Server.Core/Controllers/ProfileController.cs
+++ b/Libraries/SPTarkov.Server.Core/Controllers/ProfileController.cs
@@ -1,4 +1,5 @@
using SPTarkov.DI.Annotations;
+using SPTarkov.Server.Core.Extensions;
using SPTarkov.Server.Core.Generators;
using SPTarkov.Server.Core.Helpers;
using SPTarkov.Server.Core.Models.Eft.Common;
@@ -310,8 +311,7 @@ public class ProfileController(
foreach (var rootItems in hideoutRootItems)
{
// Check each root items for children and add
- var itemWithChildren = _itemHelper.FindAndReturnChildrenAsItems(
- profileToViewPmc.Inventory.Items,
+ var itemWithChildren = profileToViewPmc.Inventory.Items.FindAndReturnChildrenAsItems(
rootItems.Id
);
itemsToReturn.AddRange(itemWithChildren);
diff --git a/Libraries/SPTarkov.Server.Core/Controllers/QuestController.cs b/Libraries/SPTarkov.Server.Core/Controllers/QuestController.cs
index b6e22c32..8d017cbe 100644
--- a/Libraries/SPTarkov.Server.Core/Controllers/QuestController.cs
+++ b/Libraries/SPTarkov.Server.Core/Controllers/QuestController.cs
@@ -335,8 +335,7 @@ public class QuestController(
// element `location` properties of the parent so they are sequential, while retaining order
if (removedItem.Location?.GetType() == typeof(int))
{
- var childItems = _itemHelper.FindAndReturnChildrenAsItems(
- pmcData.Inventory.Items,
+ var childItems = pmcData.Inventory.Items.FindAndReturnChildrenAsItems(
removedItem.ParentId
);
childItems.RemoveAt(0); // Remove the parent
diff --git a/Libraries/SPTarkov.Server.Core/Controllers/RagfairController.cs b/Libraries/SPTarkov.Server.Core/Controllers/RagfairController.cs
index bfcf12bc..08073d0e 100644
--- a/Libraries/SPTarkov.Server.Core/Controllers/RagfairController.cs
+++ b/Libraries/SPTarkov.Server.Core/Controllers/RagfairController.cs
@@ -649,8 +649,7 @@ public class RagfairController
// multi-offers are all the same item,
// Get first item and its children and use as template
- var inventoryItems = _itemHelper.FindAndReturnChildrenAsItems(
- pmcData.Inventory.Items,
+ var inventoryItems = pmcData.Inventory.Items.FindAndReturnChildrenAsItems(
firstOfferItemId // Choose first item as they're all the same item
);
@@ -766,9 +765,8 @@ public class RagfairController
// multi-offers are all the same item,
// Get first item and its children and use as template
- var firstInventoryItemAndChildren = _itemHelper.FindAndReturnChildrenAsItems(
- pmcData.Inventory.Items,
- offerRequest.Items[0]
+ var firstInventoryItemAndChildren = pmcData.Inventory.Items.FindAndReturnChildrenAsItems(
+ offerRequest.Items.FirstOrDefault()
);
// Find items to be listed on flea (+ children) from player inventory
@@ -1147,9 +1145,7 @@ public class RagfairController
rootItem.FixItemStackCount();
- itemsToReturn.Add(
- _itemHelper.FindAndReturnChildrenAsItems(pmcData.Inventory.Items, itemId)
- );
+ itemsToReturn.Add(pmcData.Inventory.Items.FindAndReturnChildrenAsItems(itemId));
}
if (itemsToReturn?.Count == 0)
diff --git a/Libraries/SPTarkov.Server.Core/Controllers/TradeController.cs b/Libraries/SPTarkov.Server.Core/Controllers/TradeController.cs
index 6d305816..95f34706 100644
--- a/Libraries/SPTarkov.Server.Core/Controllers/TradeController.cs
+++ b/Libraries/SPTarkov.Server.Core/Controllers/TradeController.cs
@@ -1,4 +1,5 @@
using SPTarkov.DI.Annotations;
+using SPTarkov.Server.Core.Extensions;
using SPTarkov.Server.Core.Helpers;
using SPTarkov.Server.Core.Models.Eft.Common;
using SPTarkov.Server.Core.Models.Eft.Common.Tables;
@@ -367,7 +368,7 @@ public class TradeController(
TraderBase traderDetails
)
{
- var itemWithChildren = _itemHelper.FindAndReturnChildrenAsItems(items, parentItemId);
+ var itemWithChildren = items.FindAndReturnChildrenAsItems(parentItemId);
var totalPrice = 0;
foreach (var itemToSell in itemWithChildren)
diff --git a/Libraries/SPTarkov.Server.Core/Extensions/ItemExtensions.cs b/Libraries/SPTarkov.Server.Core/Extensions/ItemExtensions.cs
index 3e8a0610..18c42444 100644
--- a/Libraries/SPTarkov.Server.Core/Extensions/ItemExtensions.cs
+++ b/Libraries/SPTarkov.Server.Core/Extensions/ItemExtensions.cs
@@ -276,5 +276,52 @@ namespace SPTarkov.Server.Core.Extensions
// Ensure item has 'StackObjectsCount' property
item.Upd.StackObjectsCount ??= 1;
}
+
+ ///
+ /// A variant of FindAndReturnChildren where the output is list of item objects instead of their ids.
+ ///
+ /// List of items (item + possible children)
+ /// Parent item's id
+ /// OPTIONAL - Include only mod items, exclude items stored inside root item
+ /// list of Item objects
+ public static List- FindAndReturnChildrenAsItems(
+ this IEnumerable
- items,
+ string baseItemId,
+ bool modsOnly = false
+ )
+ {
+ // Use dictionary to make key lookup faster, convert to list before being returned
+ OrderedDictionary result = [];
+ foreach (var childItem in items)
+ {
+ // Include itself
+ if (string.Equals(childItem.Id, baseItemId, StringComparison.Ordinal))
+ {
+ // Root item MUST be at 0 index for things like flea market offers
+ result.Insert(0, childItem.Id, childItem);
+ continue;
+ }
+
+ // Is stored in parent and disallowed
+ if (modsOnly && childItem.Location is not null)
+ {
+ continue;
+ }
+
+ // Items parentId matches root item AND returned items doesn't contain current child
+ if (
+ !result.ContainsKey(childItem.Id)
+ && string.Equals(childItem.ParentId, baseItemId, StringComparison.Ordinal)
+ )
+ {
+ foreach (var item in FindAndReturnChildrenAsItems(items, childItem.Id))
+ {
+ result.Add(item.Id, item);
+ }
+ }
+ }
+
+ return result.Values.ToList();
+ }
}
}
diff --git a/Libraries/SPTarkov.Server.Core/Generators/LocationLootGenerator.cs b/Libraries/SPTarkov.Server.Core/Generators/LocationLootGenerator.cs
index 4f2058fd..1a358a6b 100644
--- a/Libraries/SPTarkov.Server.Core/Generators/LocationLootGenerator.cs
+++ b/Libraries/SPTarkov.Server.Core/Generators/LocationLootGenerator.cs
@@ -1,5 +1,6 @@
using System.Text.Json.Serialization;
using SPTarkov.DI.Annotations;
+using SPTarkov.Server.Core.Extensions;
using SPTarkov.Server.Core.Helpers;
using SPTarkov.Server.Core.Models.Eft.Common;
using SPTarkov.Server.Core.Models.Eft.Common.Tables;
@@ -1145,7 +1146,7 @@ public class LocationLootGenerator(
{
// Also used by armors to get child mods
// Get item + children and add into array we return
- var itemWithChildren = _itemHelper.FindAndReturnChildrenAsItems(items, chosenItem.Id);
+ var itemWithChildren = items.FindAndReturnChildrenAsItems(chosenItem.Id);
// Ensure all IDs are unique
itemWithChildren = _itemHelper.ReplaceIDs(_cloner.Clone(itemWithChildren));
diff --git a/Libraries/SPTarkov.Server.Core/Helpers/BotGeneratorHelper.cs b/Libraries/SPTarkov.Server.Core/Helpers/BotGeneratorHelper.cs
index a3a114f2..15d30e47 100644
--- a/Libraries/SPTarkov.Server.Core/Helpers/BotGeneratorHelper.cs
+++ b/Libraries/SPTarkov.Server.Core/Helpers/BotGeneratorHelper.cs
@@ -1,6 +1,7 @@
using System.Collections.Frozen;
using SPTarkov.DI.Annotations;
using SPTarkov.Server.Core.Constants;
+using SPTarkov.Server.Core.Extensions;
using SPTarkov.Server.Core.Models.Eft.Common.Tables;
using SPTarkov.Server.Core.Models.Enums;
using SPTarkov.Server.Core.Models.Spt.Bots;
@@ -800,10 +801,7 @@ public class BotGeneratorHelper(
{
// Check item in container for children, store for later insertion into `containerItemsToCheck`
// (used later when figuring out how much space weapon takes up)
- var itemWithChildItems = _itemHelper.FindAndReturnChildrenAsItems(
- itemsWithoutLocation,
- rootItem.Id
- );
+ var itemWithChildItems = itemsWithoutLocation.FindAndReturnChildrenAsItems(rootItem.Id);
// Item had children, replace existing data with item + its children
result.Add(rootItem);
diff --git a/Libraries/SPTarkov.Server.Core/Helpers/InRaidHelper.cs b/Libraries/SPTarkov.Server.Core/Helpers/InRaidHelper.cs
index 1bbdc3a7..e78562bf 100644
--- a/Libraries/SPTarkov.Server.Core/Helpers/InRaidHelper.cs
+++ b/Libraries/SPTarkov.Server.Core/Helpers/InRaidHelper.cs
@@ -75,14 +75,12 @@ public class InRaidHelper(
);
// Get all items that have a parent of `serverProfile.Inventory.equipment` (All items player had on them at end of raid)
- var postRaidInventoryItems = _itemHelper.FindAndReturnChildrenAsItems(
- postRaidProfile.Inventory.Items,
+ var postRaidInventoryItems = postRaidProfile.Inventory.Items.FindAndReturnChildrenAsItems(
postRaidProfile.Inventory.Equipment
);
// Get all items that have a parent of `serverProfile.Inventory.questRaidItems` (Quest items player had on them at end of raid)
- var postRaidQuestItems = _itemHelper.FindAndReturnChildrenAsItems(
- postRaidProfile.Inventory.Items,
+ var postRaidQuestItems = postRaidProfile.Inventory.Items.FindAndReturnChildrenAsItems(
postRaidProfile.Inventory.QuestRaidItems
);
diff --git a/Libraries/SPTarkov.Server.Core/Helpers/InventoryHelper.cs b/Libraries/SPTarkov.Server.Core/Helpers/InventoryHelper.cs
index f9b8cf2f..65686a6f 100644
--- a/Libraries/SPTarkov.Server.Core/Helpers/InventoryHelper.cs
+++ b/Libraries/SPTarkov.Server.Core/Helpers/InventoryHelper.cs
@@ -508,10 +508,7 @@ public class InventoryHelper(
}
// Get children of item, they get deleted too
- var itemAndChildrenToRemove = _itemHelper.FindAndReturnChildrenAsItems(
- profile.Inventory.Items,
- itemId
- );
+ var itemAndChildrenToRemove = profile.Inventory.Items.FindAndReturnChildrenAsItems(itemId);
if (!itemAndChildrenToRemove.Any())
{
if (_logger.IsLogEnabled(LogLevel.Debug))
@@ -591,11 +588,10 @@ public class InventoryHelper(
if (messageWithReward is not null)
{
// Find item + any possible children and remove them from mails items array
- var itemWithChildern = _itemHelper.FindAndReturnChildrenAsItems(
- messageWithReward.Items.Data,
+ var itemWithChildren = messageWithReward.Items.Data.FindAndReturnChildrenAsItems(
removeRequest.Item
);
- foreach (var itemToDelete in itemWithChildern)
+ foreach (var itemToDelete in itemWithChildren)
{
// Get index of item to remove from reward array + remove it
var indexOfItemToRemove = messageWithReward.Items.Data.IndexOf(itemToDelete);
@@ -649,10 +645,7 @@ public class InventoryHelper(
}
// Goal is to keep removing items until we can remove part of an items stack
- var itemsToReduce = _itemHelper.FindAndReturnChildrenAsItems(
- pmcData.Inventory.Items,
- itemId
- );
+ var itemsToReduce = pmcData.Inventory.Items.FindAndReturnChildrenAsItems(itemId);
var remainingCount = countToRemove;
foreach (var itemToReduce in itemsToReduce)
{
diff --git a/Libraries/SPTarkov.Server.Core/Helpers/ItemHelper.cs b/Libraries/SPTarkov.Server.Core/Helpers/ItemHelper.cs
index f3eb037e..fb837020 100644
--- a/Libraries/SPTarkov.Server.Core/Helpers/ItemHelper.cs
+++ b/Libraries/SPTarkov.Server.Core/Helpers/ItemHelper.cs
@@ -712,53 +712,6 @@ public class ItemHelper(
return Math.Sqrt(durability ?? 0);
}
- ///
- /// A variant of FindAndReturnChildren where the output is list of item objects instead of their ids.
- ///
- /// List of items (item + possible children)
- /// Parent item's id
- /// OPTIONAL - Include only mod items, exclude items stored inside root item
- /// list of Item objects
- public List
- FindAndReturnChildrenAsItems(
- IEnumerable
- items,
- string baseItemId,
- bool modsOnly = false
- )
- {
- // Use dictionary to make key lookup faster, convert to list before being returned
- OrderedDictionary result = [];
- foreach (var childItem in items)
- {
- // Include itself
- if (string.Equals(childItem.Id, baseItemId, StringComparison.Ordinal))
- {
- // Root item MUST be at 0 index for things like flea market offers
- result.Insert(0, childItem.Id, childItem);
- continue;
- }
-
- // Is stored in parent and disallowed
- if (modsOnly && childItem.Location is not null)
- {
- continue;
- }
-
- // Items parentId matches root item AND returned items doesn't contain current child
- if (
- !result.ContainsKey(childItem.Id)
- && string.Equals(childItem.ParentId, baseItemId, StringComparison.Ordinal)
- )
- {
- foreach (var item in FindAndReturnChildrenAsItems(items, childItem.Id))
- {
- result.Add(item.Id, item);
- }
- }
- }
-
- return result.Values.ToList();
- }
-
///
/// Find children of the item in a given assort (weapons parts for example, need recursive loop function)
///
@@ -1402,7 +1355,7 @@ public class ItemHelper(
var forcedLeft = 0;
var forcedRight = 0;
- var children = FindAndReturnChildrenAsItems(items, rootItemId);
+ var children = items.FindAndReturnChildrenAsItems(rootItemId);
foreach (var ci in children)
{
var itemTemplate = GetItem(ci.Template).Value;
diff --git a/Libraries/SPTarkov.Server.Core/Helpers/ProfileHelper.cs b/Libraries/SPTarkov.Server.Core/Helpers/ProfileHelper.cs
index 5a060e47..ef54876e 100644
--- a/Libraries/SPTarkov.Server.Core/Helpers/ProfileHelper.cs
+++ b/Libraries/SPTarkov.Server.Core/Helpers/ProfileHelper.cs
@@ -678,10 +678,7 @@ public class ProfileHelper(
foreach (var itemId in profile.Inventory?.FavoriteItems ?? [])
{
// When viewing another users profile, the client expects a full item with children, so get that
- var itemAndChildren = _itemHelper.FindAndReturnChildrenAsItems(
- profile.Inventory.Items,
- itemId
- );
+ var itemAndChildren = profile.Inventory.Items.FindAndReturnChildrenAsItems(itemId);
if (itemAndChildren?.Count > 0)
{
// To get the client to actually see the items, we set the main item's parent to null, so it's treated as a root item
diff --git a/Libraries/SPTarkov.Server.Core/Helpers/TradeHelper.cs b/Libraries/SPTarkov.Server.Core/Helpers/TradeHelper.cs
index 6af675c1..bae8b9a2 100644
--- a/Libraries/SPTarkov.Server.Core/Helpers/TradeHelper.cs
+++ b/Libraries/SPTarkov.Server.Core/Helpers/TradeHelper.cs
@@ -152,10 +152,7 @@ public class TradeHelper(
return;
}
- offerItems = _itemHelper.FindAndReturnChildrenAsItems(
- fenceItems,
- buyRequestData.ItemId
- );
+ offerItems = fenceItems.FindAndReturnChildrenAsItems(buyRequestData.ItemId);
}
else
{
@@ -223,10 +220,7 @@ public class TradeHelper(
.Items;
// Get item + children for purchase
- var relevantItems = _itemHelper.FindAndReturnChildrenAsItems(
- traderItems,
- buyRequestData.ItemId
- );
+ var relevantItems = traderItems.FindAndReturnChildrenAsItems(buyRequestData.ItemId);
if (relevantItems.Count == 0)
{
_logger.Error(
diff --git a/Libraries/SPTarkov.Server.Core/Services/CircleOfCultistService.cs b/Libraries/SPTarkov.Server.Core/Services/CircleOfCultistService.cs
index 117dc3b0..16962987 100644
--- a/Libraries/SPTarkov.Server.Core/Services/CircleOfCultistService.cs
+++ b/Libraries/SPTarkov.Server.Core/Services/CircleOfCultistService.cs
@@ -332,8 +332,7 @@ public class CircleOfCultistService(
List
- sacrificedItems = [];
foreach (var rootItem in inventoryRootItemsInCultistGrid)
{
- var rootItemWithChildren = _itemHelper.FindAndReturnChildrenAsItems(
- pmcData.Inventory.Items,
+ var rootItemWithChildren = pmcData.Inventory.Items.FindAndReturnChildrenAsItems(
rootItem.Id
);
sacrificedItems.AddRange(rootItemWithChildren);
diff --git a/Libraries/SPTarkov.Server.Core/Services/FenceService.cs b/Libraries/SPTarkov.Server.Core/Services/FenceService.cs
index 3256ad89..8b316280 100644
--- a/Libraries/SPTarkov.Server.Core/Services/FenceService.cs
+++ b/Libraries/SPTarkov.Server.Core/Services/FenceService.cs
@@ -145,9 +145,7 @@ public class FenceService(
{
// HUGE THANKS TO LACYWAY AND LEAVES FOR PROVIDING THIS SOLUTION FOR SPT TO IMPLEMENT!!
// Copy the item and its children
- var clonedItems = _cloner.Clone(
- itemHelper.FindAndReturnChildrenAsItems(items, mainItem.Id)
- );
+ var clonedItems = _cloner.Clone(items.FindAndReturnChildrenAsItems(mainItem.Id));
// I BLAME LACY FOR THIS ISSUE, I SPENT HOURS FIXING IT /s
// i think on node the one with hideout usually came first
var root = clonedItems.FirstOrDefault(x => x.SlotId == "hideout");
@@ -423,8 +421,7 @@ public class FenceService(
// Check if same type of item exists + its on list of item types to always stack
if (existingRootItem != null && ItemInPreventDupeCategoryList(newRootItem.Template))
{
- var existingFullItemTree = itemHelper.FindAndReturnChildrenAsItems(
- existingFenceAssorts.Items,
+ var existingFullItemTree = existingFenceAssorts.Items.FindAndReturnChildrenAsItems(
existingRootItem.Id
);
if (
@@ -580,10 +577,7 @@ public class FenceService(
}
// Remove item + child mods (if any)
- var itemWithChildren = itemHelper.FindAndReturnChildrenAsItems(
- assort.Items,
- rootItemToAdjust.Id
- );
+ var itemWithChildren = assort.Items.FindAndReturnChildrenAsItems(rootItemToAdjust.Id);
foreach (var itemToDelete in itemWithChildren)
// Delete item from assort items array
{
@@ -824,10 +818,7 @@ public class FenceService(
.ToList();
var desiredAssortItemAndChildrenClone = _cloner.Clone(
- itemHelper.FindAndReturnChildrenAsItems(
- childItemsAndSingleRoot,
- chosenBaseAssortRoot.Id
- )
+ childItemsAndSingleRoot.FindAndReturnChildrenAsItems(chosenBaseAssortRoot.Id)
);
var itemDbDetails = itemHelper.GetItem(chosenBaseAssortRoot.Template).Value;
@@ -1121,10 +1112,7 @@ public class FenceService(
var rootItemDb = itemHelper.GetItem(randomPresetRoot.Template).Value;
var presetWithChildrenClone = _cloner.Clone(
- itemHelper.FindAndReturnChildrenAsItems(
- baseFenceAssort.Items,
- randomPresetRoot.Id
- )
+ baseFenceAssort.Items.FindAndReturnChildrenAsItems(randomPresetRoot.Id)
);
RandomiseItemUpdProperties(rootItemDb, presetWithChildrenClone[0]);
@@ -1204,7 +1192,7 @@ public class FenceService(
var rootItemDb = itemHelper.GetItem(randomPresetRoot.Template).Value;
var presetWithChildrenClone = _cloner.Clone(
- itemHelper.FindAndReturnChildrenAsItems(baseFenceAssort.Items, randomPresetRoot.Id)
+ baseFenceAssort.Items.FindAndReturnChildrenAsItems(randomPresetRoot.Id)
);
// Need to add mods to armors so they don't show as red in the trade screen
@@ -1799,7 +1787,7 @@ public class FenceService(
protected void DeleteOffer(string assortId, List
- assorts)
{
// Assort could have child items, remove those too
- var itemWithChildrenToRemove = itemHelper.FindAndReturnChildrenAsItems(assorts, assortId);
+ var itemWithChildrenToRemove = assorts.FindAndReturnChildrenAsItems(assortId);
foreach (var itemToRemove in itemWithChildrenToRemove)
{
var indexToRemove = assorts.FindIndex(item => item.Id == itemToRemove.Id);
diff --git a/Libraries/SPTarkov.Server.Core/Services/RagfairTaxService.cs b/Libraries/SPTarkov.Server.Core/Services/RagfairTaxService.cs
index c97260e0..eaba6f9a 100644
--- a/Libraries/SPTarkov.Server.Core/Services/RagfairTaxService.cs
+++ b/Libraries/SPTarkov.Server.Core/Services/RagfairTaxService.cs
@@ -1,4 +1,5 @@
using SPTarkov.DI.Annotations;
+using SPTarkov.Server.Core.Extensions;
using SPTarkov.Server.Core.Helpers;
using SPTarkov.Server.Core.Models.Eft.Common;
using SPTarkov.Server.Core.Models.Eft.Common.Tables;
@@ -168,10 +169,7 @@ public class RagfairTaxService(
if (isRootItem)
{
// Since we get a flat list of all child items, we only want to recurse from parent item
- var itemChildren = _itemHelper.FindAndReturnChildrenAsItems(
- pmcData.Inventory.Items,
- item.Id
- );
+ var itemChildren = pmcData.Inventory.Items.FindAndReturnChildrenAsItems(item.Id);
if (itemChildren.Count > 1)
{
var itemChildrenClone = _cloner.Clone(itemChildren); // Clone is expensive, only run if necessary