Added item extensions

This commit is contained in:
Chomp
2025-06-28 10:04:52 +01:00
parent 0be02bc3f5
commit 6126dc2394
17 changed files with 251 additions and 274 deletions
@@ -1,5 +1,6 @@
using SPTarkov.DI.Annotations; using SPTarkov.DI.Annotations;
using SPTarkov.Server.Core.DI; using SPTarkov.Server.Core.DI;
using SPTarkov.Server.Core.Extensions;
using SPTarkov.Server.Core.Helpers; using SPTarkov.Server.Core.Helpers;
using SPTarkov.Server.Core.Models.Eft.Profile; using SPTarkov.Server.Core.Models.Eft.Profile;
using SPTarkov.Server.Core.Models.Spt.Config; using SPTarkov.Server.Core.Models.Spt.Config;
@@ -114,7 +115,7 @@ public class BtrDeliveryCallbacks(
var rootItemParentId = _hashUtil.Generate(); var rootItemParentId = _hashUtil.Generate();
// Update the delivery items to have the new root parent ID for root/orphaned items // Update the delivery items to have the new root parent ID for root/orphaned items
package.Items = _itemHelper.AdoptOrphanedItems(rootItemParentId, package.Items); package.Items = package.Items.AdoptOrphanedItems(rootItemParentId);
_btrDeliveryService.SendBTRDelivery(sessionId, package.Items); _btrDeliveryService.SendBTRDelivery(sessionId, package.Items);
@@ -305,7 +305,7 @@ public class GameController(
/// <param name="pmcProfile">Player profile</param> /// <param name="pmcProfile">Player profile</param>
protected void WarnOnActiveBotReloadSkill(PmcData pmcProfile) protected void WarnOnActiveBotReloadSkill(PmcData pmcProfile)
{ {
var botReloadSkill = _profileHelper.GetSkillFromProfile(pmcProfile, SkillTypes.BotReload); var botReloadSkill = pmcProfile.GetSkillFromProfile(SkillTypes.BotReload);
if (botReloadSkill?.Progress > 0) if (botReloadSkill?.Progress > 0)
{ {
_logger.Warning( _logger.Warning(
@@ -1,4 +1,5 @@
using SPTarkov.DI.Annotations; using SPTarkov.DI.Annotations;
using SPTarkov.Server.Core.Extensions;
using SPTarkov.Server.Core.Helpers; using SPTarkov.Server.Core.Helpers;
using SPTarkov.Server.Core.Models.Common; using SPTarkov.Server.Core.Models.Common;
using SPTarkov.Server.Core.Models.Eft.Common; using SPTarkov.Server.Core.Models.Eft.Common;
@@ -123,7 +124,7 @@ public class InsuranceController(
var rootItemParentId = _hashUtil.Generate(); var rootItemParentId = _hashUtil.Generate();
// Update the insured items to have the new root parent ID for root/orphaned items // Update the insured items to have the new root parent ID for root/orphaned items
insured.Items = _itemHelper.AdoptOrphanedItems(rootItemParentId, insured.Items); insured.Items = insured.Items.AdoptOrphanedItems(rootItemParentId);
var simulateItemsBeingTaken = _insuranceConfig.SimulateItemsBeingTaken; var simulateItemsBeingTaken = _insuranceConfig.SimulateItemsBeingTaken;
if (simulateItemsBeingTaken) if (simulateItemsBeingTaken)
@@ -135,7 +136,7 @@ public class InsuranceController(
RemoveItemsFromInsurance(insured, itemsToDelete); RemoveItemsFromInsurance(insured, itemsToDelete);
// There's a chance we've orphaned weapon attachments, so adopt any orphaned items again // There's a chance we've orphaned weapon attachments, so adopt any orphaned items again
insured.Items = _itemHelper.AdoptOrphanedItems(rootItemParentId, insured.Items); insured.Items = insured.Items.AdoptOrphanedItems(rootItemParentId);
} }
SendMail(sessionId, insured); SendMail(sessionId, insured);
@@ -192,7 +193,7 @@ public class InsuranceController(
// Populate a Map object of items for quick lookup by their ID and use it to populate a Map of main-parent items // Populate a Map object of items for quick lookup by their ID and use it to populate a Map of main-parent items
// and each of their attachments. For example, a gun mapped to each of its attachments. // and each of their attachments. For example, a gun mapped to each of its attachments.
var itemsMap = _itemHelper.GenerateItemsMap(insured.Items); var itemsMap = insured.Items.GenerateItemsMap();
var parentAttachmentsMap = PopulateParentAttachmentsMap( var parentAttachmentsMap = PopulateParentAttachmentsMap(
rootItemParentId, rootItemParentId,
insured, insured,
@@ -1,4 +1,5 @@
using SPTarkov.DI.Annotations; using SPTarkov.DI.Annotations;
using SPTarkov.Server.Core.Extensions;
using SPTarkov.Server.Core.Generators.RepeatableQuestGeneration; using SPTarkov.Server.Core.Generators.RepeatableQuestGeneration;
using SPTarkov.Server.Core.Helpers; using SPTarkov.Server.Core.Helpers;
using SPTarkov.Server.Core.Models.Eft.Common; using SPTarkov.Server.Core.Models.Eft.Common;
@@ -222,8 +223,7 @@ public class RepeatableQuestController(
var traderOfReplacedQuest = pmcData.TradersInfo[replacedQuestTraderId]; var traderOfReplacedQuest = pmcData.TradersInfo[replacedQuestTraderId];
traderOfReplacedQuest.Standing -= previousChangeRequirement.ChangeStandingCost; traderOfReplacedQuest.Standing -= previousChangeRequirement.ChangeStandingCost;
var charismaBonus = var charismaBonus = pmcData.GetSkillFromProfile(SkillTypes.Charisma)?.Progress ?? 0;
_profileHelper.GetSkillFromProfile(pmcData, SkillTypes.Charisma)?.Progress ?? 0;
foreach (var cost in previousChangeRequirement.ChangeCost) foreach (var cost in previousChangeRequirement.ChangeCost)
{ {
// Not free, Charge player + apply charisma bonus to cost of replacement // Not free, Charge player + apply charisma bonus to cost of replacement
@@ -0,0 +1,195 @@
using SPTarkov.Server.Core.Models.Eft.Common.Tables;
namespace SPTarkov.Server.Core.Extensions
{
public static class ItemExtensions
{
/// <summary>
/// This method will compare two items and see if they are equivalent
/// This method will NOT compare IDs on the items
/// </summary>
/// <param name="item1">first item to compare</param>
/// <param name="item2">second item to compare</param>
/// <param name="compareUpdProperties">Upd properties to compare between the items</param>
/// <returns>true if they are the same</returns>
public static bool IsSameItem(
this Item item1,
Item item2,
HashSet<string>? 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<string, Func<Upd, Upd, bool>>
{
{ "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;
}
/// <summary>
/// Check if item is stored inside a container
/// </summary>
/// <param name="itemToCheck">Item to check is inside of container</param>
/// <param name="desiredContainerSlotId">Name of slot to check item is in e.g. SecuredContainer/Backpack</param>
/// <param name="items">Inventory with child parent items to check</param>
/// <returns>True when item is in container</returns>
public static bool ItemIsInsideContainer(
this Item itemToCheck,
string desiredContainerSlotId,
IEnumerable<Item> 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);
}
/// <summary>
/// Get the size of a stack, return 1 if no stack object count property found
/// </summary>
/// <param name="item">Item to get stack size of</param>
/// <returns>size of stack</returns>
public static int GetItemStackSize(this Item item)
{
if (item.Upd?.StackObjectsCount is not null)
{
return (int)item.Upd.StackObjectsCount;
}
return 1;
}
/// <summary>
/// Create a dictionary from a collection of items, keyed by item id
/// </summary>
/// <param name="items">Collection of items</param>
/// <returns>Dictionary of items</returns>
public static Dictionary<string, Item> GenerateItemsMap(this IEnumerable<Item> items)
{
// Convert list to dictionary, keyed by items Id
return items.ToDictionary(item => item.Id);
}
/// <summary>
/// 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.
/// </summary>
/// <param name="rootId">The ID of the "root" of the container</param>
/// <param name="items">Array of Items that should be adjusted</param>
/// <returns>Returns Array of Items that have been adopted</returns>
public static List<Item> AdoptOrphanedItems(this List<Item> 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;
}
}
}
@@ -62,5 +62,16 @@ namespace SPTarkov.Server.Core.Extensions
return profile.TaskConditionCounters.Count > 0; return profile.TaskConditionCounters.Count > 0;
} }
/// <summary>
/// Get a specific common skill from supplied profile
/// </summary>
/// <param name="profile">Player profile</param>
/// <param name="skill">Skill to look up and return value from</param>
/// <returns>Common skill object from desired profile</returns>
public static CommonSkill? GetSkillFromProfile(this PmcData profile, SkillTypes skill)
{
return profile?.Skills?.Common?.FirstOrDefault(s => s.Id == skill);
}
} }
} }
@@ -1,4 +1,5 @@
using SPTarkov.DI.Annotations; using SPTarkov.DI.Annotations;
using SPTarkov.Server.Core.Extensions;
using SPTarkov.Server.Core.Models.Eft.Common; using SPTarkov.Server.Core.Models.Eft.Common;
using SPTarkov.Server.Core.Models.Eft.Common.Tables; using SPTarkov.Server.Core.Models.Eft.Common.Tables;
using SPTarkov.Server.Core.Models.Eft.Hideout; using SPTarkov.Server.Core.Models.Eft.Hideout;
@@ -1349,10 +1350,7 @@ public class HideoutHelper(
/// <returns>Consumption bonus</returns> /// <returns>Consumption bonus</returns>
protected double? GetHideoutManagementConsumptionBonus(PmcData pmcData) protected double? GetHideoutManagementConsumptionBonus(PmcData pmcData)
{ {
var hideoutManagementSkill = _profileHelper.GetSkillFromProfile( var hideoutManagementSkill = pmcData.GetSkillFromProfile(SkillTypes.HideoutManagement);
pmcData,
SkillTypes.HideoutManagement
);
if (hideoutManagementSkill is null || hideoutManagementSkill.Progress == 0) if (hideoutManagementSkill is null || hideoutManagementSkill.Progress == 0)
{ {
return 0; return 0;
@@ -1384,7 +1382,7 @@ public class HideoutHelper(
double valuePerLevel double valuePerLevel
) )
{ {
var profileSkill = _profileHelper.GetSkillFromProfile(pmcData, skill); var profileSkill = pmcData.GetSkillFromProfile(skill);
if (profileSkill is null || profileSkill.Progress == 0) if (profileSkill is null || profileSkill.Progress == 0)
{ {
return 0; return 0;
@@ -1563,10 +1561,7 @@ public class HideoutHelper(
.ToList(); .ToList();
// Calculate bonus percent (apply hideoutManagement bonus) // Calculate bonus percent (apply hideoutManagement bonus)
var hideoutManagementSkill = _profileHelper.GetSkillFromProfile( var hideoutManagementSkill = pmcData.GetSkillFromProfile(SkillTypes.HideoutManagement);
pmcData,
SkillTypes.HideoutManagement
);
var hideoutManagementSkillBonusPercent = 1 + hideoutManagementSkill.Progress / 10000; // 5100 becomes 0.51, add 1 to it, 1.51 var hideoutManagementSkillBonusPercent = 1 + hideoutManagementSkill.Progress / 10000; // 5100 becomes 0.51, add 1 to it, 1.51
var bonus = var bonus =
GetDogtagCombatSkillBonusPercent(pmcData, activeDogtags) GetDogtagCombatSkillBonusPercent(pmcData, activeDogtags)
@@ -1,5 +1,6 @@
using SPTarkov.Common.Extensions; using SPTarkov.Common.Extensions;
using SPTarkov.DI.Annotations; using SPTarkov.DI.Annotations;
using SPTarkov.Server.Core.Extensions;
using SPTarkov.Server.Core.Models.Eft.Common; using SPTarkov.Server.Core.Models.Eft.Common;
using SPTarkov.Server.Core.Models.Eft.Common.Tables; using SPTarkov.Server.Core.Models.Eft.Common.Tables;
using SPTarkov.Server.Core.Models.Spt.Config; using SPTarkov.Server.Core.Models.Spt.Config;
@@ -25,8 +26,9 @@ public class InRaidHelper(
"pocket3", "pocket3",
"pocket4", "pocket4",
]; ];
protected InRaidConfig _inRaidConfig = _configServer.GetConfig<InRaidConfig>(); protected readonly InRaidConfig _inRaidConfig = _configServer.GetConfig<InRaidConfig>();
protected LostOnDeathConfig _lostOnDeathConfig = _configServer.GetConfig<LostOnDeathConfig>(); protected readonly LostOnDeathConfig _lostOnDeathConfig =
_configServer.GetConfig<LostOnDeathConfig>();
/// <summary> /// <summary>
/// Deprecated. Reset the skill points earned in a raid to 0, ready for next raid. /// Deprecated. Reset the skill points earned in a raid to 0, ready for next raid.
@@ -116,7 +118,7 @@ public class InRaidHelper(
&& !(dbItems[item.Template].Properties.QuestItem ?? false) && !(dbItems[item.Template].Properties.QuestItem ?? false)
&& !( && !(
_inRaidConfig.KeepFiRSecureContainerOnDeath _inRaidConfig.KeepFiRSecureContainerOnDeath
&& _itemHelper.ItemIsInsideContainer(item, "SecuredContainer", items) && item.ItemIsInsideContainer("SecuredContainer", items)
); );
}); });
@@ -201,13 +203,7 @@ public class InRaidHelper(
) )
) )
{ {
if ( if (inventoryItem.ItemIsInsideContainer(secureContainerSlotId, pmcData.Inventory.Items))
_itemHelper.ItemIsInsideContainer(
inventoryItem,
secureContainerSlotId,
pmcData.Inventory.Items
)
)
{ {
itemsInsideContainer.Add(inventoryItem); itemsInsideContainer.Add(inventoryItem);
} }
@@ -3,6 +3,7 @@ using System.Text.Json;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using SPTarkov.Common.Extensions; using SPTarkov.Common.Extensions;
using SPTarkov.DI.Annotations; using SPTarkov.DI.Annotations;
using SPTarkov.Server.Core.Extensions;
using SPTarkov.Server.Core.Models.Eft.Common; using SPTarkov.Server.Core.Models.Eft.Common;
using SPTarkov.Server.Core.Models.Eft.Common.Tables; using SPTarkov.Server.Core.Models.Eft.Common.Tables;
using SPTarkov.Server.Core.Models.Eft.Inventory; using SPTarkov.Server.Core.Models.Eft.Inventory;
@@ -27,7 +28,6 @@ public class InventoryHelper(
HttpResponseUtil _httpResponseUtil, HttpResponseUtil _httpResponseUtil,
DialogueHelper _dialogueHelper, DialogueHelper _dialogueHelper,
ContainerHelper _containerHelper, ContainerHelper _containerHelper,
DatabaseServer _databaseServer,
EventOutputHolder _eventOutputHolder, EventOutputHolder _eventOutputHolder,
ProfileHelper _profileHelper, ProfileHelper _profileHelper,
ItemHelper _itemHelper, ItemHelper _itemHelper,
@@ -42,7 +42,8 @@ public class InventoryHelper(
BaseClasses.FUNCTIONAL_MOD, BaseClasses.FUNCTIONAL_MOD,
BaseClasses.MOD, BaseClasses.MOD,
]; ];
protected InventoryConfig _inventoryConfig = _configServer.GetConfig<InventoryConfig>(); protected readonly InventoryConfig _inventoryConfig =
_configServer.GetConfig<InventoryConfig>();
/// <summary> /// <summary>
/// Add multiple items to player stash (assuming they all fit) /// Add multiple items to player stash (assuming they all fit)
@@ -655,7 +656,7 @@ public class InventoryHelper(
var remainingCount = countToRemove; var remainingCount = countToRemove;
foreach (var itemToReduce in itemsToReduce) foreach (var itemToReduce in itemsToReduce)
{ {
var itemStackSize = _itemHelper.GetItemStackSize(itemToReduce); var itemStackSize = itemToReduce.GetItemStackSize();
// Remove whole stack // Remove whole stack
if (remainingCount >= itemStackSize) if (remainingCount >= itemStackSize)
@@ -1,5 +1,6 @@
using System.Collections.Frozen; using System.Collections.Frozen;
using SPTarkov.DI.Annotations; using SPTarkov.DI.Annotations;
using SPTarkov.Server.Core.Extensions;
using SPTarkov.Server.Core.Models.Eft.Common; using SPTarkov.Server.Core.Models.Eft.Common;
using SPTarkov.Server.Core.Models.Eft.Common.Tables; using SPTarkov.Server.Core.Models.Eft.Common.Tables;
using SPTarkov.Server.Core.Models.Enums; using SPTarkov.Server.Core.Models.Enums;
@@ -178,98 +179,7 @@ public class ItemHelper(
return false; return false;
} }
if (!IsSameItem(itemOf1, itemOf2, compareUpdProperties)) if (!itemOf1.IsSameItem(itemOf2, compareUpdProperties))
{
return false;
}
}
return true;
}
/// <summary>
/// This method will compare two items and see if they are equivalent
/// This method will NOT compare IDs on the items
/// </summary>
/// <param name="item1">first item to compare</param>
/// <param name="item2">second item to compare</param>
/// <param name="compareUpdProperties">Upd properties to compare between the items</param>
/// <returns>true if they are the same</returns>
public bool IsSameItem(Item item1, Item item2, HashSet<string>? 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<string, Func<Upd, Upd, bool>>
{
{ "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 false;
} }
@@ -1709,37 +1619,6 @@ public class ItemHelper(
); );
} }
/// <summary>
/// Check if item is stored inside a container
/// </summary>
/// <param name="itemToCheck">Item to check is inside of container</param>
/// <param name="desiredContainerSlotId">Name of slot to check item is in e.g. SecuredContainer/Backpack</param>
/// <param name="items">Inventory with child parent items to check</param>
/// <returns>True when item is in container</returns>
public bool ItemIsInsideContainer(
Item itemToCheck,
string desiredContainerSlotId,
List<Item> 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 ItemIsInsideContainer(parent, desiredContainerSlotId, items);
}
/// <summary> /// <summary>
/// Add child items (cartridges) to a magazine /// Add child items (cartridges) to a magazine
/// </summary> /// </summary>
@@ -1987,21 +1866,6 @@ public class ItemHelper(
}; };
} }
/// <summary>
/// Get the size of a stack, return 1 if no stack object count property found
/// </summary>
/// <param name="item">Item to get stack size of</param>
/// <returns>size of stack</returns>
public int GetItemStackSize(Item item)
{
if (item.Upd?.StackObjectsCount is not null)
{
return (int)item.Upd.StackObjectsCount;
}
return 1;
}
/// <summary> /// <summary>
/// Get the name of an item from the locale file using the item tpl /// Get the name of an item from the locale file using the item tpl
/// </summary> /// </summary>
@@ -2256,45 +2120,6 @@ public class ItemHelper(
return newId; return newId;
} }
// 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 List<Item> AdoptOrphanedItems(string rootId, List<Item> items)
{
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;
}
// Populate a Map object of items for quick lookup using their ID.
//
// An array of Items that should be added to a Map.
// Returns A Map where the keys are the item IDs and the values are the corresponding Item objects.
public Dictionary<string, Item> GenerateItemsMap(List<Item> items)
{
// Convert list to dictionary, keyed by items Id
return items.ToDictionary(item => item.Id);
}
// Add a blank upd object to passed in item if it does not exist already // Add a blank upd object to passed in item if it does not exist already
// item to add upd to // item to add upd to
// text to write to log when upd object was not found // text to write to log when upd object was not found
@@ -2372,19 +2197,6 @@ public class ItemHelper(
return null; return null;
} }
// Remove FiR status from passed in items
// Items to update FiR status of
public void RemoveSpawnedInSessionPropertyFromItems(List<Item> items)
{
foreach (var item in items)
{
if (item.Upd is not null)
{
item.Upd.SpawnedInSession = null;
}
}
}
/// <summary> /// <summary>
/// Get a 2D grid of a container's item slots /// Get a 2D grid of a container's item slots
/// </summary> /// </summary>
@@ -565,25 +565,6 @@ public class ProfileHelper(
profileSkill.LastAccess = _timeUtil.GetTimeStamp(); profileSkill.LastAccess = _timeUtil.GetTimeStamp();
} }
/// <summary>
/// Get a specific common skill from supplied profile
/// </summary>
/// <param name="pmcData">Player profile</param>
/// <param name="skill">Skill to look up and return value from</param>
/// <returns>Common skill object from desired profile</returns>
public CommonSkill? GetSkillFromProfile(PmcData pmcData, SkillTypes skill)
{
var skillToReturn = pmcData?.Skills?.Common.FirstOrDefault(s => s.Id == skill);
if (skillToReturn == null)
{
_logger.Warning(
$"Profile {pmcData.SessionId} does not have a skill named: {skill.ToString()}"
);
}
return skillToReturn;
}
/// <summary> /// <summary>
/// Is the provided session id for a developer account /// Is the provided session id for a developer account
/// </summary> /// </summary>
@@ -1,14 +1,12 @@
using SPTarkov.Common.Extensions; using SPTarkov.Common.Extensions;
using SPTarkov.DI.Annotations; using SPTarkov.DI.Annotations;
using SPTarkov.Server.Core.Extensions;
using SPTarkov.Server.Core.Models.Eft.Common; using SPTarkov.Server.Core.Models.Eft.Common;
using SPTarkov.Server.Core.Models.Eft.Common.Tables; using SPTarkov.Server.Core.Models.Eft.Common.Tables;
using SPTarkov.Server.Core.Models.Eft.ItemEvent; using SPTarkov.Server.Core.Models.Eft.ItemEvent;
using SPTarkov.Server.Core.Models.Enums; using SPTarkov.Server.Core.Models.Enums;
using SPTarkov.Server.Core.Models.Spt.Config;
using SPTarkov.Server.Core.Models.Utils; using SPTarkov.Server.Core.Models.Utils;
using SPTarkov.Server.Core.Servers;
using SPTarkov.Server.Core.Services; using SPTarkov.Server.Core.Services;
using SPTarkov.Server.Core.Utils;
using SPTarkov.Server.Core.Utils.Cloners; using SPTarkov.Server.Core.Utils.Cloners;
namespace SPTarkov.Server.Core.Helpers; namespace SPTarkov.Server.Core.Helpers;
@@ -16,23 +14,14 @@ namespace SPTarkov.Server.Core.Helpers;
[Injectable] [Injectable]
public class QuestRewardHelper( public class QuestRewardHelper(
ISptLogger<QuestRewardHelper> _logger, ISptLogger<QuestRewardHelper> _logger,
HashUtil _hashUtil,
TimeUtil _timeUtil,
ItemHelper _itemHelper,
PaymentHelper _paymentHelper, PaymentHelper _paymentHelper,
TraderHelper _traderHelper,
DatabaseService _databaseService, DatabaseService _databaseService,
QuestConditionHelper _questConditionHelper,
ProfileHelper _profileHelper, ProfileHelper _profileHelper,
PresetHelper _presetHelper,
RewardHelper _rewardHelper, RewardHelper _rewardHelper,
LocalisationService _localisationService, LocalisationService _localisationService,
ICloner _cloner, ICloner _cloner
ConfigServer _configServer
) )
{ {
protected QuestConfig _questConfig = _configServer.GetConfig<QuestConfig>();
/// <summary> /// <summary>
/// Value for in game reward traders to not duplicate quest rewards. /// Value for in game reward traders to not duplicate quest rewards.
/// Value can be modified by modders by overriding this value with new traders. /// Value can be modified by modders by overriding this value with new traders.
@@ -161,10 +150,7 @@ public class QuestRewardHelper(
); );
// Calculate hideout management bonus as a percentage (up to 51% bonus) // Calculate hideout management bonus as a percentage (up to 51% bonus)
var hideoutManagementSkill = _profileHelper.GetSkillFromProfile( var hideoutManagementSkill = pmcData.GetSkillFromProfile(SkillTypes.HideoutManagement);
pmcData,
SkillTypes.HideoutManagement
);
// 5100 becomes 0.51, add 1 to it, 1.51 // 5100 becomes 0.51, add 1 to it, 1.51
// We multiply the money reward bonuses by the hideout management skill multiplier, giving the new result // We multiply the money reward bonuses by the hideout management skill multiplier, giving the new result
@@ -1,5 +1,6 @@
using SPTarkov.Common.Extensions; using SPTarkov.Common.Extensions;
using SPTarkov.DI.Annotations; using SPTarkov.DI.Annotations;
using SPTarkov.Server.Core.Extensions;
using SPTarkov.Server.Core.Helpers; using SPTarkov.Server.Core.Helpers;
using SPTarkov.Server.Core.Models.Eft.Common; using SPTarkov.Server.Core.Models.Eft.Common;
using SPTarkov.Server.Core.Models.Eft.Common.Tables; using SPTarkov.Server.Core.Models.Eft.Common.Tables;
@@ -42,7 +43,7 @@ public class CircleOfCultistService(
) )
{ {
protected const string CircleOfCultistSlotId = "CircleOfCultistsGrid1"; protected const string CircleOfCultistSlotId = "CircleOfCultistsGrid1";
protected HideoutConfig _hideoutConfig = _configServer.GetConfig<HideoutConfig>(); protected readonly HideoutConfig _hideoutConfig = _configServer.GetConfig<HideoutConfig>();
/// <summary> /// <summary>
/// Start a sacrifice event /// Start a sacrifice event
@@ -176,10 +177,7 @@ public class CircleOfCultistService(
); );
// Adjust value generated by the players hideout management skill // Adjust value generated by the players hideout management skill
var hideoutManagementSkill = _profileHelper.GetSkillFromProfile( var hideoutManagementSkill = pmcData.GetSkillFromProfile(SkillTypes.HideoutManagement);
pmcData,
SkillTypes.HideoutManagement
);
if (hideoutManagementSkill is not null) if (hideoutManagementSkill is not null)
{ {
rewardAmountMultiplier *= (float)(1 + hideoutManagementSkill.Progress / 10000); // 5100 becomes 0.51, add 1 to it, 1.51, multiply the bonus by it (e.g. 1.2 x 1.51) rewardAmountMultiplier *= (float)(1 + hideoutManagementSkill.Progress / 10000); // 5100 becomes 0.51, add 1 to it, 1.51, multiply the bonus by it (e.g. 1.2 x 1.51)
@@ -1,4 +1,5 @@
using SPTarkov.DI.Annotations; using SPTarkov.DI.Annotations;
using SPTarkov.Server.Core.Extensions;
using SPTarkov.Server.Core.Helpers; using SPTarkov.Server.Core.Helpers;
using SPTarkov.Server.Core.Models.Eft.Common; using SPTarkov.Server.Core.Models.Eft.Common;
using SPTarkov.Server.Core.Models.Eft.Common.Tables; using SPTarkov.Server.Core.Models.Eft.Common.Tables;
@@ -21,7 +22,6 @@ public class InsuranceService(
DatabaseService _databaseService, DatabaseService _databaseService,
RandomUtil _randomUtil, RandomUtil _randomUtil,
ItemHelper _itemHelper, ItemHelper _itemHelper,
HashUtil _hashUtil,
TimeUtil _timeUtil, TimeUtil _timeUtil,
SaveServer _saveServer, SaveServer _saveServer,
TraderHelper _traderHelper, TraderHelper _traderHelper,
@@ -32,7 +32,8 @@ public class InsuranceService(
ICloner _cloner ICloner _cloner
) )
{ {
protected InsuranceConfig _insuranceConfig = _configServer.GetConfig<InsuranceConfig>(); protected readonly InsuranceConfig _insuranceConfig =
_configServer.GetConfig<InsuranceConfig>();
protected Dictionary<string, Dictionary<string, List<Item>>?> _insured = new(); protected Dictionary<string, Dictionary<string, List<Item>>?> _insured = new();
/// <summary> /// <summary>
@@ -302,13 +303,13 @@ public class InsuranceService(
/// <returns>True if item</returns> /// <returns>True if item</returns>
protected bool ItemCannotBeLostOnDeath(Item lostItem, List<Item> inventoryItems) protected bool ItemCannotBeLostOnDeath(Item lostItem, List<Item> inventoryItems)
{ {
if (lostItem.SlotId?.ToLower().StartsWith("specialslot") ?? false) if (lostItem.SlotId?.StartsWith("specialslot", StringComparison.OrdinalIgnoreCase) ?? false)
{ {
return true; return true;
} }
// We check secure container items even tho they are omitted from lostInsuredItems, just in case // We check secure container items even tho they are omitted from lostInsuredItems, just in case
if (_itemHelper.ItemIsInsideContainer(lostItem, "SecuredContainer", inventoryItems)) if (lostItem.ItemIsInsideContainer("SecuredContainer", inventoryItems))
{ {
return true; return true;
} }
@@ -1,4 +1,5 @@
using SPTarkov.DI.Annotations; using SPTarkov.DI.Annotations;
using SPTarkov.Server.Core.Extensions;
using SPTarkov.Server.Core.Helpers; using SPTarkov.Server.Core.Helpers;
using SPTarkov.Server.Core.Models.Eft.Common.Tables; using SPTarkov.Server.Core.Models.Eft.Common.Tables;
using SPTarkov.Server.Core.Models.Eft.Profile; using SPTarkov.Server.Core.Models.Eft.Profile;
@@ -195,7 +196,7 @@ public class MailSendService(
{ {
var rootItemParentId = _hashUtil.Generate(); var rootItemParentId = _hashUtil.Generate();
details.Items.AddRange(_itemHelper.AdoptOrphanedItems(rootItemParentId, items)); details.Items.AddRange(items.AdoptOrphanedItems(rootItemParentId));
details.ItemsMaxStorageLifetimeSeconds = maxStorageTimeSeconds; details.ItemsMaxStorageLifetimeSeconds = maxStorageTimeSeconds;
} }
@@ -1,5 +1,6 @@
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using SPTarkov.DI.Annotations; using SPTarkov.DI.Annotations;
using SPTarkov.Server.Core.Extensions;
using SPTarkov.Server.Core.Helpers; using SPTarkov.Server.Core.Helpers;
using SPTarkov.Server.Core.Models.Eft.Common; using SPTarkov.Server.Core.Models.Eft.Common;
using SPTarkov.Server.Core.Models.Eft.Common.Tables; using SPTarkov.Server.Core.Models.Eft.Common.Tables;
@@ -88,10 +89,7 @@ public class ProfileFixerService(
// Otherwise we need to generate a new unique stash ID for this message's attachments // Otherwise we need to generate a new unique stash ID for this message's attachments
message.Items.Stash = _hashUtil.Generate(); message.Items.Stash = _hashUtil.Generate();
message.Items.Data = _itemHelper.AdoptOrphanedItems( message.Items.Data = message.Items.Data.AdoptOrphanedItems(message.Items.Stash);
message.Items.Stash,
message.Items.Data
);
// Because `adoptOrphanedItems` sets the slotId to `hideout`, we need to re-set it to `main` to work with mail // Because `adoptOrphanedItems` sets the slotId to `hideout`, we need to re-set it to `main` to work with mail
foreach (var item in message.Items.Data.Where(item => item.SlotId == "hideout")) foreach (var item in message.Items.Data.Where(item => item.SlotId == "hideout"))
@@ -1,6 +1,7 @@
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using SPTarkov.Common.Extensions; using SPTarkov.Common.Extensions;
using SPTarkov.DI.Annotations; using SPTarkov.DI.Annotations;
using SPTarkov.Server.Core.Extensions;
using SPTarkov.Server.Core.Helpers; using SPTarkov.Server.Core.Helpers;
using SPTarkov.Server.Core.Models.Common; using SPTarkov.Server.Core.Models.Common;
using SPTarkov.Server.Core.Models.Eft.Common; using SPTarkov.Server.Core.Models.Eft.Common;
@@ -420,7 +421,7 @@ public class RepairService(
.Intellect .Intellect
.RepairPointsCostReduction; .RepairPointsCostReduction;
var profileIntellectLevel = var profileIntellectLevel =
_profileHelper.GetSkillFromProfile(pmcData, SkillTypes.Intellect)?.Progress ?? 0; pmcData.GetSkillFromProfile(SkillTypes.Intellect)?.Progress ?? 0;
var intellectPointReduction = var intellectPointReduction =
intellectRepairPointsPerLevel * Math.Truncate(profileIntellectLevel / 100); intellectRepairPointsPerLevel * Math.Truncate(profileIntellectLevel / 100);
@@ -629,8 +630,7 @@ public class RepairService(
// Skill < level 10 + repairing weapon // Skill < level 10 + repairing weapon
if ( if (
itemSkillType == SkillTypes.WeaponTreatment itemSkillType == SkillTypes.WeaponTreatment
&& _profileHelper.GetSkillFromProfile(pmcData, SkillTypes.WeaponTreatment)?.Progress && pmcData.GetSkillFromProfile(SkillTypes.WeaponTreatment)?.Progress < 1000
< 1000
) )
{ {
return false; return false;
@@ -641,7 +641,7 @@ public class RepairService(
new HashSet<SkillTypes> { SkillTypes.LightVests, SkillTypes.HeavyVests }.Contains( new HashSet<SkillTypes> { SkillTypes.LightVests, SkillTypes.HeavyVests }.Contains(
itemSkillType.Value itemSkillType.Value
) )
&& _profileHelper.GetSkillFromProfile(pmcData, itemSkillType.Value)?.Progress < 1000 && pmcData.GetSkillFromProfile(itemSkillType.Value)?.Progress < 1000
) )
{ {
return false; return false;
@@ -670,7 +670,7 @@ public class RepairService(
var receivedDurabilityMaxPercent = buffSettings.ReceivedDurabilityMaxPercent; var receivedDurabilityMaxPercent = buffSettings.ReceivedDurabilityMaxPercent;
var skillLevel = Math.Truncate( var skillLevel = Math.Truncate(
(_profileHelper.GetSkillFromProfile(pmcData, itemSkillType.Value)?.Progress ?? 0) / 100 (pmcData.GetSkillFromProfile(itemSkillType.Value)?.Progress ?? 0) / 100
); );
if (repairDetails.RepairPoints is null) if (repairDetails.RepairPoints is null)