diff --git a/project/assets/configs/repair.json b/project/assets/configs/repair.json index e240c804..af1fdd43 100644 --- a/project/assets/configs/repair.json +++ b/project/assets/configs/repair.json @@ -11,6 +11,13 @@ "kit": 0.6, "trader": 0.6 }, + "weaponTreatment": { + "critSuccessChance": 0.10, + "critSuccessAmount": 4, + "critFailureChance": 0.10, + "critFailureAmount": 4, + "pointGainMultiplier": 0.6 + }, "repairKit": { "armor": { "rarityWeight": { diff --git a/project/src/helpers/QuestHelper.ts b/project/src/helpers/QuestHelper.ts index 2553600f..93edf145 100644 --- a/project/src/helpers/QuestHelper.ts +++ b/project/src/helpers/QuestHelper.ts @@ -8,7 +8,7 @@ import { QuestConditionHelper } from "@spt-aki/helpers/QuestConditionHelper"; import { RagfairServerHelper } from "@spt-aki/helpers/RagfairServerHelper"; import { TraderHelper } from "@spt-aki/helpers/TraderHelper"; import { IPmcData } from "@spt-aki/models/eft/common/IPmcData"; -import { IQuestStatus } from "@spt-aki/models/eft/common/tables/IBotBase"; +import { Common, IQuestStatus } from "@spt-aki/models/eft/common/tables/IBotBase"; import { Item } from "@spt-aki/models/eft/common/tables/IItem"; import { AvailableForConditions, AvailableForProps, IQuest, Reward } from "@spt-aki/models/eft/common/tables/IQuest"; import { IItemEventRouterResponse } from "@spt-aki/models/eft/itemEvent/IItemEventRouterResponse"; @@ -135,7 +135,7 @@ export class QuestHelper * @param skillName Name of skill to increase skill points of * @param progressAmount Amount of skill points to add to skill */ - public rewardSkillPoints(sessionID: string, pmcData: IPmcData, skillName: string, progressAmount: number): void + public rewardSkillPoints(sessionID: string, pmcData: IPmcData, skillName: string, progressAmount: number, scaleToSkillLevel: boolean = false): void { const indexOfSkillToUpdate = pmcData.Skills.Common.findIndex(s => s.Id === skillName); if (indexOfSkillToUpdate === -1) @@ -153,10 +153,66 @@ export class QuestHelper return; } + // Tarkov has special handling of skills under level 9 to scale them to the lower XP requirement + if (scaleToSkillLevel) + { + progressAmount = this.adjustSkillExpForLowLevels(profileSkill, progressAmount); + } + profileSkill.Progress += progressAmount; profileSkill.LastAccess = this.timeUtil.getTimestamp(); } + /** + * Adjust skill experience for low skill levels, mimicing the official client + * @param profileSkill the skill experience is being added to + * @param progressAmount the amount of experience being added to the skill + * @returns the adjusted skill progress gain + */ + public adjustSkillExpForLowLevels(profileSkill: Common, progressAmount: number): number + { + let currentLevel = Math.floor(profileSkill.Progress / 100); + + // Only run this if the current level is under 9 + if (currentLevel >= 9) + { + return progressAmount; + } + + // This calculates how much progress we have in the skill's starting level + let startingLevelProgress = (profileSkill.Progress % 100) * ((currentLevel + 1) / 10); + + // The code below assumes a 1/10th progress skill amount + let remainingProgress = progressAmount / 10; + + // We have to do this loop to handle edge cases where the provided XP bumps your level up + // See "CalculateExpOnFirstLevels" in client for original logic + let adjustedSkillProgress = 0; + while (remainingProgress > 0 && currentLevel < 9) + { + // Calculate how much progress to add, limiting it to the current level max progress + const currentLevelRemainingProgress = ((currentLevel + 1) * 10) - startingLevelProgress; + this.logger.debug(`currentLevelRemainingProgress: ${currentLevelRemainingProgress}`); + const progressToAdd = Math.min(remainingProgress, currentLevelRemainingProgress); + const adjustedProgressToAdd = (10 / (currentLevel + 1)) * progressToAdd; + this.logger.debug(`Progress To Add: ${progressToAdd} Adjusted for level: ${adjustedProgressToAdd}`); + + // Add the progress amount adjusted by level + adjustedSkillProgress += adjustedProgressToAdd; + remainingProgress -= progressToAdd; + startingLevelProgress = 0; + currentLevel++; + } + + // If there's any remaining progress, add it. This handles if you go from level 8 -> 9 + if (remainingProgress > 0) + { + adjustedSkillProgress += remainingProgress; + } + + return adjustedSkillProgress; + } + /** * Get quest name by quest id * @param questId id to get diff --git a/project/src/models/spt/config/IRepairConfig.ts b/project/src/models/spt/config/IRepairConfig.ts index e9544bbc..0b65b465 100644 --- a/project/src/models/spt/config/IRepairConfig.ts +++ b/project/src/models/spt/config/IRepairConfig.ts @@ -12,6 +12,7 @@ export interface IRepairConfig extends IBaseConfig repairKitIntellectGainMultiplier: IIntellectGainValues //** How much INT can be given to player per repair action */ maxIntellectGainPerRepair: IMaxIntellectGainValues; + weaponTreatment: IWeaponTreatmentRepairValues; repairKit: RepairKit } @@ -27,6 +28,18 @@ export interface IMaxIntellectGainValues trader: number } +export interface IWeaponTreatmentRepairValues +{ + /** The chance to gain more weapon maintenance skill */ + critSuccessChance: number + critSuccessAmount: number + /** The chance to gain less weapon maintenance skill */ + critFailureChance: number + critFailureAmount: number + /** The multiplier used for calculating weapon maintenance XP */ + pointGainMultiplier: number +} + export interface RepairKit { armor: BonusSettings diff --git a/project/src/services/RepairService.ts b/project/src/services/RepairService.ts index fc69eb57..5b49b577 100644 --- a/project/src/services/RepairService.ts +++ b/project/src/services/RepairService.ts @@ -147,10 +147,11 @@ export class RepairService repairDetails: RepairDetails, pmcData: IPmcData): void { - if (this.itemHelper.isOfBaseclass(repairDetails.repairedItem._tpl, BaseClasses.WEAPON)) + if (repairDetails.repairedByKit && this.itemHelper.isOfBaseclass(repairDetails.repairedItem._tpl, BaseClasses.WEAPON)) { - const progress = this.databaseServer.getTables().globals.config.SkillsSettings.WeaponTreatment.SkillPointsPerRepair; - this.questHelper.rewardSkillPoints(sessionId, pmcData, "WeaponTreatment", progress); + const skillPoints = this.getWeaponRepairSkillPoints(repairDetails); + + this.questHelper.rewardSkillPoints(sessionId, pmcData, "WeaponTreatment", skillPoints, true); } // Handle kit repairs of armor @@ -167,7 +168,7 @@ export class RepairService const isHeavyArmor = itemDetails[1]._props.ArmorType === "Heavy"; const vestSkillToLevel = (isHeavyArmor) ? "HeavyVests" : "LightVests"; - const pointsToAddToVestSkill = repairDetails.repairAmount * this.repairConfig.armorKitSkillPointGainPerRepairPointMultiplier; + const pointsToAddToVestSkill = repairDetails.repairPoints * this.repairConfig.armorKitSkillPointGainPerRepairPointMultiplier; this.questHelper.rewardSkillPoints(sessionId, pmcData, vestSkillToLevel, pointsToAddToVestSkill); } @@ -181,7 +182,7 @@ export class RepairService : this.repairConfig.repairKitIntellectGainMultiplier.armor; // limit gain to a max value defined in config.maxIntellectGainPerRepair - intellectGainedFromRepair = Math.min(repairDetails.repairAmount * intRepairMultiplier, this.repairConfig.maxIntellectGainPerRepair.kit); + intellectGainedFromRepair = Math.min(repairDetails.repairPoints * intRepairMultiplier, this.repairConfig.maxIntellectGainPerRepair.kit); } else { @@ -191,6 +192,43 @@ export class RepairService this.questHelper.rewardSkillPoints(sessionId, pmcData, SkillTypes.INTELLECT, intellectGainedFromRepair); } + + /** + * Return an appromixation of the amount of skill points live would return for the given repairDetails + * @param repairDetails the repair details to calculate skill points for + * @returns the number of skill points to reward the user + */ + protected getWeaponRepairSkillPoints( + repairDetails: RepairDetails): number + { + // This formula and associated configs is calculated based on 30 repairs done on live + // The points always came out 2-aligned, which is why there's a divide/multiply by 2 with ceil calls + const gainMult = this.repairConfig.weaponTreatment.pointGainMultiplier; + + // First we get a baseline based on our repair amount, and gain multiplier with a bit of rounding + const step1 = Math.ceil(repairDetails.repairAmount / 2) * gainMult; + + // Then we have to get the next even number + const step2 = Math.ceil(step1 / 2) * 2; + + // Then multiply by 2 again to hopefully get to what live would give us + let skillPoints = step2 * 2; + + // You can both crit fail and succeed at the same time, for fun (Balances out to 0 with default settings) + // Add a random chance to crit-fail + if (Math.random() <= this.repairConfig.weaponTreatment.critFailureChance) + { + skillPoints -= this.repairConfig.weaponTreatment.critFailureAmount; + } + + // Add a random chance to crit-succeed + if (Math.random() <= this.repairConfig.weaponTreatment.critSuccessChance) + { + skillPoints += this.repairConfig.weaponTreatment.critSuccessAmount; + } + + return skillPoints; + } /** * @@ -218,12 +256,13 @@ export class RepairService const itemsDb = this.databaseServer.getTables().templates.items; const itemToRepairDetails = itemsDb[itemToRepair._tpl]; const repairItemIsArmor = (!!itemToRepairDetails._props.ArmorMaterial); + const repairAmount = repairKits[0].count / this.getKitDivisor(itemToRepairDetails, repairItemIsArmor, pmcData); this.repairHelper.updateItemDurability( itemToRepair, itemToRepairDetails, repairItemIsArmor, - repairKits[0].count / this.getKitDivisor(itemToRepairDetails, repairItemIsArmor, pmcData), + repairAmount, true, 1, this.repairConfig.applyRandomizeDurabilityLoss); @@ -244,9 +283,10 @@ export class RepairService } return { + repairPoints: repairKits[0].count, repairedItem: itemToRepair, repairedItemIsArmor: repairItemIsArmor, - repairAmount: repairKits[0].count, + repairAmount: repairAmount, repairedByKit: true }; } @@ -414,7 +454,7 @@ export class RepairService const skillLevel = Math.trunc((pmcData?.Skills?.Common?.find(s => s.Id === itemSkillType)?.Progress ?? 0) / 100); - const durabilityToRestorePercent = repairDetails.repairAmount / template._props.MaxDurability; + const durabilityToRestorePercent = repairDetails.repairPoints / template._props.MaxDurability; const durabilityMultiplier = this.getDurabilityMultiplier(receivedDurabilityMaxPercent, durabilityToRestorePercent); const doBuff = commonBuffMinChanceValue + commonBuffChanceLevelBonus * skillLevel * durabilityMultiplier; @@ -483,6 +523,7 @@ export class RepairService export class RepairDetails { repairCost?: number; + repairPoints?: number; repairedItem: Item; repairedItemIsArmor: boolean; repairAmount: number;