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;
}
}
}