using SPTarkov.Server.Core.Models.Eft.Common.Tables; namespace SPTarkov.Server.Core.Extensions { public static class ItemExtensions { /// /// This method will compare two items and see if they are equivalent /// This method will NOT compare IDs on the items /// /// first item to compare /// second item to compare /// Upd properties to compare between the items /// true if they are the same public static bool IsSameItem( this Item item1, Item item2, HashSet? compareUpdProperties = null ) { // Different tpl == different item if (item1.Template != item2.Template) { return false; } // Both lack upd object + same tpl = same if (item1.Upd is null && item2.Upd is null) { return true; } // item1 lacks upd, item2 has one if (item1.Upd is null && item2.Upd is not null) { return false; } // item1 has upd, item2 lacks one if (item1.Upd is not null && item2.Upd is null) { return false; } // key = Upd property Type as string, value = comparison function that returns bool var comparers = new Dictionary> { { "Key", (upd1, upd2) => upd1.Key?.NumberOfUsages == upd2.Key?.NumberOfUsages }, { "Buff", (upd1, upd2) => upd1.Buff?.Value == upd2.Buff?.Value && upd1.Buff?.BuffType == upd2.Buff?.BuffType }, { "CultistAmulet", (upd1, upd2) => upd1.CultistAmulet?.NumberOfUsages == upd2.CultistAmulet?.NumberOfUsages }, { "Dogtag", (upd1, upd2) => upd1.Dogtag?.ProfileId == upd2.Dogtag?.ProfileId }, { "FaceShield", (upd1, upd2) => upd1.FaceShield?.Hits == upd2.FaceShield?.Hits }, { "Foldable", (upd1, upd2) => upd1.Foldable?.Folded.GetValueOrDefault(false) == upd2.Foldable?.Folded.GetValueOrDefault(false) }, { "FoodDrink", (upd1, upd2) => upd1.FoodDrink?.HpPercent == upd2.FoodDrink?.HpPercent }, { "MedKit", (upd1, upd2) => upd1.MedKit?.HpResource == upd2.MedKit?.HpResource }, { "RecodableComponent", (upd1, upd2) => upd1.RecodableComponent?.IsEncoded == upd2.RecodableComponent?.IsEncoded }, { "RepairKit", (upd1, upd2) => upd1.RepairKit?.Resource == upd2.RepairKit?.Resource }, { "Resource", (upd1, upd2) => upd1.Resource?.UnitsConsumed == upd2.Resource?.UnitsConsumed }, }; // Choose above keys or passed in keys to compare items with var valuesToCompare = compareUpdProperties?.Count > 0 ? compareUpdProperties : comparers.Keys.ToHashSet(); foreach (var propertyName in valuesToCompare) { if (!comparers.TryGetValue(propertyName, out var comparer)) // Key not found, skip { continue; } if (!comparer(item1.Upd, item2.Upd)) { return false; } } return true; } /// /// Check if item is stored inside a container /// /// Item to check is inside of container /// Name of slot to check item is in e.g. SecuredContainer/Backpack /// Inventory with child parent items to check /// True when item is in container public static bool ItemIsInsideContainer( this Item itemToCheck, string desiredContainerSlotId, IEnumerable items ) { // Get items parent var parent = items.FirstOrDefault(item => item.Id.Equals(itemToCheck.ParentId, StringComparison.OrdinalIgnoreCase) ); if (parent is null) // No parent, end of line, not inside container { return false; } if (parent.SlotId == desiredContainerSlotId) { return true; } return parent.ItemIsInsideContainer(desiredContainerSlotId, items); } /// /// Get the size of a stack, return 1 if no stack object count property found /// /// Item to get stack size of /// size of stack public static int GetItemStackSize(this Item item) { if (item.Upd?.StackObjectsCount is not null) { return (int)item.Upd.StackObjectsCount; } return 1; } /// /// Create a dictionary from a collection of items, keyed by item id /// /// Collection of items /// Dictionary of items public static Dictionary GenerateItemsMap(this IEnumerable items) { // Convert list to dictionary, keyed by items Id return items.ToDictionary(item => item.Id); } /// /// Adopts orphaned items by resetting them as root "hideout" items. Helpful in situations where a parent has been /// deleted from a group of items and there are children still referencing the missing parent. This method will /// remove the reference from the children to the parent and set item properties to root values. /// /// The ID of the "root" of the container /// Array of Items that should be adjusted /// Returns Array of Items that have been adopted public static List AdoptOrphanedItems(this List items, string rootId) { foreach (var item in items) { // Check if the item's parent exists. var parentExists = items.Any(parentItem => parentItem.Id.Equals(item.ParentId, StringComparison.OrdinalIgnoreCase) ); // If the parent does not exist and the item is not already a 'hideout' item, adopt the orphaned item by // setting the parent ID to the PMCs inventory equipment ID, the slot ID to 'hideout', and remove the location. if (!parentExists && item.ParentId != rootId && item.SlotId != "hideout") { item.ParentId = rootId; item.SlotId = "hideout"; item.Location = null; } } return items; } /// /// Recursive function that looks at every item from parameter and gets their children's Ids + includes parent item in results /// /// List of items (item + possible children) /// Parent item's id /// list of child item ids public static List FindAndReturnChildrenByItems( this IEnumerable items, string baseItemId ) { List list = []; foreach (var childItem in items) { if ( string.Equals( childItem.ParentId, baseItemId, StringComparison.OrdinalIgnoreCase ) ) { list.AddRange(FindAndReturnChildrenByItems(items, childItem.Id)); } } list.Add(baseItemId); // Required, push original item id onto array return list; } /// Check if the passed in item has buy count restrictions /// /// Item to check /// true if it has buy restrictions public static bool HasBuyRestrictions(this Item itemToCheck) { return itemToCheck.Upd?.BuyRestrictionCurrent is not null && itemToCheck.Upd?.BuyRestrictionMax is not null; } /// /// Gets the identifier for a child using slotId, locationX and locationY. /// /// Item. /// SlotId OR slotid, locationX, locationY. public static string GetChildId(this Item item) { if (item.Location is null) { return item.SlotId; } var LocationTyped = (ItemLocation)item.Location; return $"{item.SlotId},{LocationTyped.X},{LocationTyped.Y}"; } public static bool IsVertical(this ItemLocation itemLocation) { var castValue = itemLocation.R.ToString(); return castValue == "1" || string.Equals(castValue, "vertical", StringComparison.OrdinalIgnoreCase) || string.Equals( itemLocation.Rotation?.ToString(), "vertical", StringComparison.OrdinalIgnoreCase ); } /// /// Update items upd.StackObjectsCount to be 1 if its upd is missing or StackObjectsCount is undefined /// /// Item to update /// Fixed item public static void FixItemStackCount(this Item item) { // Ensure item has 'Upd' object item.Upd ??= new Upd { StackObjectsCount = 1 }; // Ensure item has 'StackObjectsCount' property item.Upd.StackObjectsCount ??= 1; } } }