Converted FindAndReturnChildrenAsItems into extension method

This commit is contained in:
Chomp
2025-06-28 12:38:34 +01:00
parent 00610acefe
commit 42e79c981b
16 changed files with 82 additions and 121 deletions
@@ -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)
@@ -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);
@@ -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
@@ -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)
@@ -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)
@@ -276,5 +276,52 @@ namespace SPTarkov.Server.Core.Extensions
// Ensure item has 'StackObjectsCount' property
item.Upd.StackObjectsCount ??= 1;
}
/// <summary>
/// A variant of FindAndReturnChildren where the output is list of item objects instead of their ids.
/// </summary>
/// <param name="items">List of items (item + possible children)</param>
/// <param name="baseItemId">Parent item's id</param>
/// <param name="modsOnly">OPTIONAL - Include only mod items, exclude items stored inside root item</param>
/// <returns>list of Item objects</returns>
public static List<Item> FindAndReturnChildrenAsItems(
this IEnumerable<Item> items,
string baseItemId,
bool modsOnly = false
)
{
// Use dictionary to make key lookup faster, convert to list before being returned
OrderedDictionary<string, Item> 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();
}
}
}
@@ -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));
@@ -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);
@@ -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
);
@@ -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)
{
@@ -712,53 +712,6 @@ public class ItemHelper(
return Math.Sqrt(durability ?? 0);
}
/// <summary>
/// A variant of FindAndReturnChildren where the output is list of item objects instead of their ids.
/// </summary>
/// <param name="items">List of items (item + possible children)</param>
/// <param name="baseItemId">Parent item's id</param>
/// <param name="modsOnly">OPTIONAL - Include only mod items, exclude items stored inside root item</param>
/// <returns>list of Item objects</returns>
public List<Item> FindAndReturnChildrenAsItems(
IEnumerable<Item> items,
string baseItemId,
bool modsOnly = false
)
{
// Use dictionary to make key lookup faster, convert to list before being returned
OrderedDictionary<string, Item> 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();
}
/// <summary>
/// Find children of the item in a given assort (weapons parts for example, need recursive loop function)
/// </summary>
@@ -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;
@@ -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
@@ -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(
@@ -332,8 +332,7 @@ public class CircleOfCultistService(
List<Item> sacrificedItems = [];
foreach (var rootItem in inventoryRootItemsInCultistGrid)
{
var rootItemWithChildren = _itemHelper.FindAndReturnChildrenAsItems(
pmcData.Inventory.Items,
var rootItemWithChildren = pmcData.Inventory.Items.FindAndReturnChildrenAsItems(
rootItem.Id
);
sacrificedItems.AddRange(rootItemWithChildren);
@@ -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<Item> 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);
@@ -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