diff --git a/Libraries/Core/Services/RepairService.cs b/Libraries/Core/Services/RepairService.cs index 10044c18..73d61187 100644 --- a/Libraries/Core/Services/RepairService.cs +++ b/Libraries/Core/Services/RepairService.cs @@ -1,17 +1,19 @@ using System.Text.Json.Serialization; using Core.Helpers; using Core.Models.Common; -using SptCommon.Annotations; using Core.Models.Eft.Common; using Core.Models.Eft.Common.Tables; using Core.Models.Eft.ItemEvent; using Core.Models.Eft.Repair; +using Core.Models.Eft.Trade; using Core.Models.Enums; +using Core.Models.Spt.Config; using Core.Models.Utils; using Core.Servers; using Core.Utils; -using Core.Models.Spt.Config; -using Core.Models.Eft.Trade; +using SptCommon.Annotations; +using SptCommon.Extensions; +using BonusSettings = Core.Models.Spt.Config.BonusSettings; namespace Core.Services; @@ -36,7 +38,7 @@ public class RepairService( /// - /// Use trader to repair an items durability + /// Use trader to repair an items durability /// /// Session id /// Profile to find item to repair in @@ -50,13 +52,14 @@ public class RepairService( string traderId ) { - var itemToRepair = pmcData.Inventory.Items.FirstOrDefault((item) => item.Id == repairItemDetails.Id); + var itemToRepair = pmcData.Inventory.Items.FirstOrDefault(item => item.Id == repairItemDetails.Id); if (itemToRepair is null) { _logger.Error( _localisationService.GetText( "repair-unable_to_find_item_in_inventory_cant_repair", - repairItemDetails.Id) + repairItemDetails.Id + ) ); } @@ -66,6 +69,7 @@ public class RepairService( { _logger.Error(_localisationService.GetText("repair-unable_to_find_trader_details_by_id", traderId)); } + var repairQualityMultiplier = traderRepairDetails.Quality; var repairRate = priceCoef <= 0 ? 1 : priceCoef / 100 + 1; @@ -80,28 +84,34 @@ public class RepairService( repairItemDetails.Count.Value, false, repairQualityMultiplier.Value, - repairQualityMultiplier != 0 && _repairConfig.ApplyRandomizeDurabilityLoss); + repairQualityMultiplier != 0 && _repairConfig.ApplyRandomizeDurabilityLoss + ); // get repair price var itemRepairCost = items[itemToRepair.Template].Properties.RepairCost; if (itemRepairCost is null) { _logger.Error( - _localisationService.GetText("repair-unable_to_find_item_repair_cost", itemToRepair.Template)); + _localisationService.GetText("repair-unable_to_find_item_repair_cost", itemToRepair.Template) + ); } + var repairCost = Math.Round( - itemRepairCost.Value * repairItemDetails.Count.Value * repairRate.Value * _repairConfig.PriceMultiplier); + itemRepairCost.Value * repairItemDetails.Count.Value * repairRate.Value * _repairConfig.PriceMultiplier + ); - _logger.Debug($"item base repair cost: ${ itemRepairCost}"); - _logger.Debug($"price multiplier: ${ _repairConfig.PriceMultiplier}"); - _logger.Debug($"repair cost: ${ repairCost}"); + _logger.Debug($"item base repair cost: ${itemRepairCost}"); + _logger.Debug($"price multiplier: ${_repairConfig.PriceMultiplier}"); + _logger.Debug($"repair cost: ${repairCost}"); - return new RepairDetails{ - RepairCost = repairCost, - RepairedItem = itemToRepair, - RepairedItemIsArmor = repairItemIsArmor, - RepairAmount = repairItemDetails.Count, - RepairedByKit = false }; + return new RepairDetails + { + RepairCost = repairCost, + RepairedItem = itemToRepair, + RepairedItemIsArmor = repairItemIsArmor, + RepairAmount = repairItemDetails.Count, + RepairedByKit = false + }; } /// @@ -121,8 +131,9 @@ public class RepairService( ItemEventRouterResponse output ) { - var options = new ProcessBuyTradeRequestData { - SchemeItems = [new() { Count = Math.Round(repairCost), Id = Money.ROUBLES }], + var options = new ProcessBuyTradeRequestData + { + SchemeItems = [new IdWithCount { Count = Math.Round(repairCost), Id = Money.ROUBLES }], TransactionId = traderId, Action = "SptRepair", Type = "", @@ -135,7 +146,7 @@ public class RepairService( } /// - /// Add skill points to profile after repairing an item + /// Add skill points to profile after repairing an item /// /// Session id /// Details of item repaired, cost/item @@ -152,7 +163,7 @@ public class RepairService( if (skillPoints > 0) { - _logger.Debug($"Added: { skillPoints} WEAPON_TREATMENT points to skill"); + _logger.Debug($"Added: {skillPoints} WEAPON_TREATMENT points to skill"); _profileHelper.AddSkillPointsToPlayer(pmcData, SkillTypes.WeaponTreatment, skillPoints, true); } } @@ -160,10 +171,13 @@ public class RepairService( // Handle kit repair of armor if ( repairDetails.RepairedByKit.GetValueOrDefault(false) && - _itemHelper.IsOfBaseclasses(repairDetails.RepairedItem.Template, [ - BaseClasses.ARMOR_PLATE, - BaseClasses.BUILT_IN_INSERTS, - ]) + _itemHelper.IsOfBaseclasses( + repairDetails.RepairedItem.Template, + [ + BaseClasses.ARMOR_PLATE, + BaseClasses.BUILT_IN_INSERTS + ] + ) ) { var itemDetails = _itemHelper.GetItem(repairDetails.RepairedItem.Template); @@ -173,7 +187,8 @@ public class RepairService( _logger.Error( _localisationService.GetText( "repair-unable_to_find_item_in_db", - repairDetails.RepairedItem.Template) + repairDetails.RepairedItem.Template + ) ); return; @@ -186,13 +201,15 @@ public class RepairService( _logger.Error( _localisationService.GetText( "repair-item_has_no_repair_points", - repairDetails.RepairedItem.Template) + repairDetails.RepairedItem.Template + ) ); } + var pointsToAddToVestSkill = repairDetails.RepairPoints * _repairConfig.ArmorKitSkillPointGainPerRepairPointMultiplier; - _logger.Debug($"Added: { pointsToAddToVestSkill} { vestSkillToLevel} skill"); + _logger.Debug($"Added: {pointsToAddToVestSkill} {vestSkillToLevel} skill"); _profileHelper.AddSkillPointsToPlayer(pmcData, vestSkillToLevel, pointsToAddToVestSkill); } @@ -200,7 +217,7 @@ public class RepairService( var intellectGainedFromRepair = GetIntellectGainedFromRepair(repairDetails); if (intellectGainedFromRepair > 0) { - _logger.Debug($"Added: { intellectGainedFromRepair} intellect skill"); + _logger.Debug($"Added: {intellectGainedFromRepair} intellect skill"); _profileHelper.AddSkillPointsToPlayer(pmcData, SkillTypes.Intellect, intellectGainedFromRepair); } } @@ -220,13 +237,15 @@ public class RepairService( _logger.Error( _localisationService.GetText( "repair-item_has_no_repair_points", - repairDetails.RepairedItem.Template) + repairDetails.RepairedItem.Template + ) ); } return Math.Min( repairDetails.RepairPoints.Value * intRepairMultiplier, - _repairConfig.MaxIntellectGainPerRepair.Kit); + _repairConfig.MaxIntellectGainPerRepair.Kit + ); } // Trader repair - Not as accurate as kit, needs data from live @@ -234,7 +253,7 @@ public class RepairService( } /// - /// Return an approximation of the amount of skill points live would return for the given repairDetails + /// Return an approximation of the amount of skill points live would return for the given repairDetails /// /// The repair details to calculate skill points for /// The number of skill points to reward the user @@ -287,7 +306,7 @@ public class RepairService( ) { // Find item to repair in inventory - var itemToRepair = pmcData.Inventory.Items.FirstOrDefault((x) => x.Id == itemToRepairId); + var itemToRepair = pmcData.Inventory.Items.FirstOrDefault(x => x.Id == itemToRepairId); if (itemToRepair is null) { _logger.Error(_localisationService.GetText("repair-item_not_found_unable_to_repair", itemToRepairId)); @@ -299,7 +318,8 @@ public class RepairService( var repairAmount = repairKits[0].Count / GetKitDivisor(itemToRepairDetails, repairItemIsArmor, pmcData); var shouldApplyDurabilityLoss = ShouldRepairKitApplyDurabilityLoss( pmcData, - _repairConfig.ApplyRandomizeDurabilityLoss); + _repairConfig.ApplyRandomizeDurabilityLoss + ); _repairHelper.UpdateItemDurability( itemToRepair, @@ -308,16 +328,20 @@ public class RepairService( repairAmount.Value, true, 1, - shouldApplyDurabilityLoss); + shouldApplyDurabilityLoss + ); // Find and use repair kit defined in body - foreach (var repairKit in repairKits) { - var repairKitInInventory = pmcData.Inventory.Items.FirstOrDefault((item) => item.Id == repairKit.Id); + foreach (var repairKit in repairKits) + { + var repairKitInInventory = pmcData.Inventory.Items.FirstOrDefault(item => item.Id == repairKit.Id); if (repairKitInInventory is null) { _logger.Error( - _localisationService.GetText("repair-repair_kit_not_found_in_inventory", repairKit.Id)); + _localisationService.GetText("repair-repair_kit_not_found_in_inventory", repairKit.Id) + ); } + var repairKitDetails = itemsDb[repairKitInInventory.Template]; var repairKitReductionAmount = repairKit.Count; @@ -329,7 +353,8 @@ public class RepairService( output.ProfileChanges[sessionId].Items.ChangedItems.Add(repairKitInInventory); } - return new RepairDetails{ + return new RepairDetails + { RepairPoints = repairKits[0].Count, RepairedItem = itemToRepair, RepairedItemIsArmor = repairItemIsArmor, @@ -339,7 +364,7 @@ public class RepairService( } /// - /// Calculate value repairkit points need to be divided by to get the durability points to be added to an item + /// Calculate value repairkit points need to be divided by to get the durability points to be added to an item /// /// Item to repair details /// Is the item being repaired armor @@ -353,7 +378,7 @@ public class RepairService( var intellectRepairPointsPerLevel = globalConfig.SkillsSettings.Intellect.RepairPointsCostReduction; var profileIntellectLevel = - _profileHelper.GetSkillFromProfile(pmcData, SkillTypes.Intellect)?.Progress ?? 0; + _profileHelper.GetSkillFromProfile(pmcData, SkillTypes.Intellect)?.Progress ?? 0; var intellectPointReduction = intellectRepairPointsPerLevel * Math.Truncate(profileIntellectLevel / 100); if (isArmor) @@ -368,7 +393,10 @@ public class RepairService( var armorClassDivisor = globals.Configuration.RepairSettings.ArmorClassDivisor; var armorClassMultiplier = 1.0 + armorClass / armorClassDivisor; - return durabilityPointCostArmor.Value * armorBonus.Value * destructability.Value * armorClassMultiplier.Value; + return durabilityPointCostArmor.Value * + armorBonus.Value * + destructability.Value * + armorClassMultiplier.Value; } var repairWeaponBonus = GetBonusMultiplierValue(BonusType.RepairWeaponBonus, pmcData) - 1; @@ -379,14 +407,14 @@ public class RepairService( } /// - /// Get the bonus multiplier for a skill from a player profile + /// Get the bonus multiplier for a skill from a player profile /// /// Bonus to get multiplier of /// Player profile to look in for skill /// Multiplier value protected double GetBonusMultiplierValue(BonusType skillBonus, PmcData pmcData) { - var bonusesMatched = pmcData?.Bonuses?.Where((b) => b.Type == skillBonus); + var bonusesMatched = pmcData?.Bonuses?.Where(b => b.Type == skillBonus); var value = 1d; if (bonusesMatched is not null) { @@ -398,7 +426,7 @@ public class RepairService( } /// - /// Should a repair kit apply total durability loss on repair + /// Should a repair kit apply total durability loss on repair /// /// Player profile /// Value from repair config @@ -421,7 +449,7 @@ public class RepairService( } /// - /// Update repair kits Resource object if it doesn't exist + /// Update repair kits Resource object if it doesn't exist /// /// Repair kit details from db /// Repair kit to update @@ -430,17 +458,18 @@ public class RepairService( var maxRepairAmount = repairKitDetails.Properties.MaxRepairResource; if (repairKitInInventory.Upd is null) { - _logger.Debug($"Repair kit: ${ repairKitInInventory.Id} in inventory lacks upd object, adding"); - repairKitInInventory.Upd = new Upd{ RepairKit = new UpdRepairKit{ Resource = maxRepairAmount } }; + _logger.Debug($"Repair kit: ${repairKitInInventory.Id} in inventory lacks upd object, adding"); + repairKitInInventory.Upd = new Upd { RepairKit = new UpdRepairKit { Resource = maxRepairAmount } }; } + if (repairKitInInventory.Upd.RepairKit?.Resource is null) { - repairKitInInventory.Upd.RepairKit = new UpdRepairKit{ Resource = maxRepairAmount }; + repairKitInInventory.Upd.RepairKit = new UpdRepairKit { Resource = maxRepairAmount }; } } /// - /// Chance to apply buff to an item (Armor/weapon) if repaired by armor kit + /// Chance to apply buff to an item (Armor/weapon) if repaired by armor kit /// /// Repair details of item /// Player profile @@ -455,12 +484,15 @@ public class RepairService( if (ShouldBuffItem(repairDetails, pmcData)) { if ( - _itemHelper.IsOfBaseclasses(repairDetails.RepairedItem.Template, [ - BaseClasses.ARMOR, - BaseClasses.VEST, - BaseClasses.HEADWEAR, - BaseClasses.ARMOR_PLATE, - ]) + _itemHelper.IsOfBaseclasses( + repairDetails.RepairedItem.Template, + [ + BaseClasses.ARMOR, + BaseClasses.VEST, + BaseClasses.HEADWEAR, + BaseClasses.ARMOR_PLATE + ] + ) ) { var armorConfig = _repairConfig.RepairKit.Armor; @@ -476,11 +508,11 @@ public class RepairService( } /// - /// Add random buff to item + /// Add random buff to item /// /// weapon/armor config /// Item to repair - public void AddBuff(Models.Spt.Config.BonusSettings itemConfig, Item item) + public void AddBuff(BonusSettings itemConfig, Item item) { var bonusRarityName = _weightedRandomHelper.GetWeightedValue(itemConfig.RarityWeight); var bonusTypeName = _weightedRandomHelper.GetWeightedValue(itemConfig.BonusTypeWeight); @@ -490,18 +522,24 @@ public class RepairService( var bonusValue = _randomUtil.GetDouble(bonusValues.Min.Value, bonusValues.Max.Value); var bonusThresholdPercents = bonusRarity[bonusTypeName].ActiveDurabilityPercentMinMax; - var bonusThresholdPercent = _randomUtil.GetDouble(bonusThresholdPercents.Min.Value, bonusThresholdPercents.Max.Value); + var bonusThresholdPercent = _randomUtil.GetDouble( + bonusThresholdPercents.Min.Value, + bonusThresholdPercents.Max.Value + ); - item.Upd.Buff = new UpdBuff { + item.Upd.Buff = new UpdBuff + { Rarity = bonusRarityName, BuffType = bonusTypeName, Value = bonusValue, - ThresholdDurability = Math.Round(_randomUtil.GetPercentOfValue(bonusThresholdPercent, item.Upd.Repairable.Durability.Value)) + ThresholdDurability = Math.Round( + _randomUtil.GetPercentOfValue(bonusThresholdPercent, item.Upd.Repairable.Durability.Value) + ) }; } /// - /// Check if item should be buffed by checking the item type and relevant player skill level + /// Check if item should be buffed by checking the item type and relevant player skill level /// /// Item that was repaired /// tpl of item to be buffed @@ -509,22 +547,98 @@ public class RepairService( /// True if item should have buff applied protected bool ShouldBuffItem(RepairDetails repairDetails, PmcData pmcData) { - throw new NotImplementedException(); + var globals = _databaseService.GetGlobals(); + + var hasTemplate = _itemHelper.GetItem(repairDetails.RepairedItem.Template); + if (!hasTemplate.Key) + { + return false; + } + + var template = hasTemplate.Value; + + // Returns SkillTypes.LIGHT_VESTS/HEAVY_VESTS/WEAPON_TREATMENT + var itemSkillType = (SkillTypes)GetItemSkillType(template); + if (itemSkillType == null) + { + return false; + } + + // Skill < level 10 + repairing weapon + if ( + itemSkillType == SkillTypes.WeaponTreatment && + _profileHelper.GetSkillFromProfile(pmcData, SkillTypes.WeaponTreatment)?.Progress < 1000 + ) + { + return false; + } + + // Skill < level 10 + repairing armor + if ( + new List { SkillTypes.LightVests, SkillTypes.HeavyVests }.Contains(itemSkillType) && + _profileHelper.GetSkillFromProfile(pmcData, itemSkillType)?.Progress < 1000 + ) + { + return false; + } + + var skillSettings = globals.Configuration.SkillsSettings.GetAllPropsAsDict(); + BuffSettings buffSettings = null; + switch (itemSkillType) + { + case SkillTypes.LightVests: + case SkillTypes.HeavyVests: + buffSettings = ((ArmorSkills)skillSettings[itemSkillType.ToString()]).BuffSettings; + break; + case SkillTypes.WeaponTreatment: + buffSettings = ((WeaponTreatment)skillSettings[itemSkillType.ToString()]).BuffSettings; + break; + default: + _logger.Error($"Unhandled buff type: {itemSkillType}"); + break; + } + + var commonBuffMinChanceValue = buffSettings.CommonBuffMinChanceValue; + var commonBuffChanceLevelBonus = buffSettings.CommonBuffChanceLevelBonus; + var receivedDurabilityMaxPercent = buffSettings.ReceivedDurabilityMaxPercent; + + var skillLevel = + Math.Truncate((_profileHelper.GetSkillFromProfile(pmcData, itemSkillType)?.Progress ?? 0) / 100); + + if (repairDetails.RepairPoints is null) + { + _logger.Error( + _localisationService.GetText("repair-item_has_no_repair_points", repairDetails.RepairedItem.Template) + ); + } + + var durabilityToRestorePercent = repairDetails.RepairPoints / template.Properties.MaxDurability; + var durabilityMultiplier = GetDurabilityMultiplier( + receivedDurabilityMaxPercent.Value, + durabilityToRestorePercent.Value + ); + + var doBuff = commonBuffMinChanceValue + commonBuffChanceLevelBonus * skillLevel * durabilityMultiplier; + var random = new Random(); + return random.Next() <= doBuff; } /// - /// Based on item, what underlying skill does this item use for buff settings + /// Based on item, what underlying skill does this item use for buff settings /// /// Item to check for skill /// Skill name protected SkillTypes? GetItemSkillType(TemplateItem itemTemplate) { - var isArmorRelated = _itemHelper.IsOfBaseclasses(itemTemplate.Id, [ - BaseClasses.ARMOR, - BaseClasses.VEST, - BaseClasses.HEADWEAR, - BaseClasses.ARMOR_PLATE, - ]); + var isArmorRelated = _itemHelper.IsOfBaseclasses( + itemTemplate.Id, + [ + BaseClasses.ARMOR, + BaseClasses.VEST, + BaseClasses.HEADWEAR, + BaseClasses.ARMOR_PLATE + ] + ); if (isArmorRelated) { @@ -554,7 +668,7 @@ public class RepairService( } /// - /// Ensure multiplier is between 1 and 0.01 + /// Ensure multiplier is between 1 and 0.01 /// /// Max durability percent /// current durability percent @@ -572,19 +686,19 @@ public class RepairDetails { [JsonPropertyName("repairCost")] public double? RepairCost { get; set; } - + [JsonPropertyName("repairPoints")] public double? RepairPoints { get; set; } - + [JsonPropertyName("repairedItem")] public Item? RepairedItem { get; set; } - + [JsonPropertyName("repairedItemIsArmor")] public bool? RepairedItemIsArmor { get; set; } - + [JsonPropertyName("repairAmount")] public double? RepairAmount { get; set; } - + [JsonPropertyName("repairedByKit")] public bool? RepairedByKit { get; set; } }