Implement live-like calculation of Weapon Maintenance XP (!164)
- Implement formula based on 30 weapon repairs done on live - Return the repair amount as `repairAmount` from `repairItemByKit` - Add an additional `repairPoints` to `RepairDetails` to return the repair points used - Update `repairAmount` references to `repairPoints` to keep old behavior - Add new parameter to rewardSkillPoints that applies live-like level scaling - Only give weapon maintenance XP when using a repair kit This implementation comes with a "Crit Fail" and "Crit Success" mechanic to account for live sometimes randomly being -4 or +4 off from my estimated values. By default the chance of each is 10%, and they can overlap and cancel each other out Spreadsheet of live repair data: https://docs.google.com/spreadsheets/d/1-tR4WYelhZfKZ3ZDbxr3nd73Y60E1wQRjDWONpMVSew/edit?usp=sharing Useful columns: C: The amount of dura attempted to be repaired, this is used in the estimated skill calculated G: Hand entered value of how much skill gain I actually saw on live (Multiplied by 10 for readability. So "3.2" would be "32") J: The estimated skill gain, based on the calculation included in this merge request K: How far off the estimated skill gain was (Negative implies we guessed high and the actual result was lower) One thing of note: I've modified all the existing references to `repairAmount` to be the new `repairPoints` when a repair kit is used. This is to keep the existing behaviour outside of my direct changes as much as possible. However, this seems to be incorrect in some cases (For example, buff chance is repairPoints/maxDura, but repairPoints will go down the higher your int. I'm assuming this is meant to be repairedDura/maxDura). May want to update these references to use `repairAmount` once they've been confirmed to expect the repair amount instead of repair points used. Co-authored-by: DrakiaXYZ <565558+TheDgtl@users.noreply.github.com> Reviewed-on: https://dev.sp-tarkov.com/SPT-AKI/Server/pulls/164 Co-authored-by: DrakiaXYZ <drakiaxyz@noreply.dev.sp-tarkov.com> Co-committed-by: DrakiaXYZ <drakiaxyz@noreply.dev.sp-tarkov.com>
This commit is contained in:
parent
d74b505a7b
commit
ebeda336db
@ -11,6 +11,13 @@
|
|||||||
"kit": 0.6,
|
"kit": 0.6,
|
||||||
"trader": 0.6
|
"trader": 0.6
|
||||||
},
|
},
|
||||||
|
"weaponTreatment": {
|
||||||
|
"critSuccessChance": 0.10,
|
||||||
|
"critSuccessAmount": 4,
|
||||||
|
"critFailureChance": 0.10,
|
||||||
|
"critFailureAmount": 4,
|
||||||
|
"pointGainMultiplier": 0.6
|
||||||
|
},
|
||||||
"repairKit": {
|
"repairKit": {
|
||||||
"armor": {
|
"armor": {
|
||||||
"rarityWeight": {
|
"rarityWeight": {
|
||||||
|
@ -8,7 +8,7 @@ import { QuestConditionHelper } from "@spt-aki/helpers/QuestConditionHelper";
|
|||||||
import { RagfairServerHelper } from "@spt-aki/helpers/RagfairServerHelper";
|
import { RagfairServerHelper } from "@spt-aki/helpers/RagfairServerHelper";
|
||||||
import { TraderHelper } from "@spt-aki/helpers/TraderHelper";
|
import { TraderHelper } from "@spt-aki/helpers/TraderHelper";
|
||||||
import { IPmcData } from "@spt-aki/models/eft/common/IPmcData";
|
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 { Item } from "@spt-aki/models/eft/common/tables/IItem";
|
||||||
import { AvailableForConditions, AvailableForProps, IQuest, Reward } from "@spt-aki/models/eft/common/tables/IQuest";
|
import { AvailableForConditions, AvailableForProps, IQuest, Reward } from "@spt-aki/models/eft/common/tables/IQuest";
|
||||||
import { IItemEventRouterResponse } from "@spt-aki/models/eft/itemEvent/IItemEventRouterResponse";
|
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 skillName Name of skill to increase skill points of
|
||||||
* @param progressAmount Amount of skill points to add to skill
|
* @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);
|
const indexOfSkillToUpdate = pmcData.Skills.Common.findIndex(s => s.Id === skillName);
|
||||||
if (indexOfSkillToUpdate === -1)
|
if (indexOfSkillToUpdate === -1)
|
||||||
@ -153,10 +153,66 @@ export class QuestHelper
|
|||||||
return;
|
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.Progress += progressAmount;
|
||||||
profileSkill.LastAccess = this.timeUtil.getTimestamp();
|
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
|
* Get quest name by quest id
|
||||||
* @param questId id to get
|
* @param questId id to get
|
||||||
|
@ -12,6 +12,7 @@ export interface IRepairConfig extends IBaseConfig
|
|||||||
repairKitIntellectGainMultiplier: IIntellectGainValues
|
repairKitIntellectGainMultiplier: IIntellectGainValues
|
||||||
//** How much INT can be given to player per repair action */
|
//** How much INT can be given to player per repair action */
|
||||||
maxIntellectGainPerRepair: IMaxIntellectGainValues;
|
maxIntellectGainPerRepair: IMaxIntellectGainValues;
|
||||||
|
weaponTreatment: IWeaponTreatmentRepairValues;
|
||||||
repairKit: RepairKit
|
repairKit: RepairKit
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -27,6 +28,18 @@ export interface IMaxIntellectGainValues
|
|||||||
trader: number
|
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
|
export interface RepairKit
|
||||||
{
|
{
|
||||||
armor: BonusSettings
|
armor: BonusSettings
|
||||||
|
@ -147,10 +147,11 @@ export class RepairService
|
|||||||
repairDetails: RepairDetails,
|
repairDetails: RepairDetails,
|
||||||
pmcData: IPmcData): void
|
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;
|
const skillPoints = this.getWeaponRepairSkillPoints(repairDetails);
|
||||||
this.questHelper.rewardSkillPoints(sessionId, pmcData, "WeaponTreatment", progress);
|
|
||||||
|
this.questHelper.rewardSkillPoints(sessionId, pmcData, "WeaponTreatment", skillPoints, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle kit repairs of armor
|
// Handle kit repairs of armor
|
||||||
@ -167,7 +168,7 @@ export class RepairService
|
|||||||
|
|
||||||
const isHeavyArmor = itemDetails[1]._props.ArmorType === "Heavy";
|
const isHeavyArmor = itemDetails[1]._props.ArmorType === "Heavy";
|
||||||
const vestSkillToLevel = (isHeavyArmor) ? "HeavyVests" : "LightVests";
|
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);
|
this.questHelper.rewardSkillPoints(sessionId, pmcData, vestSkillToLevel, pointsToAddToVestSkill);
|
||||||
}
|
}
|
||||||
@ -181,7 +182,7 @@ export class RepairService
|
|||||||
: this.repairConfig.repairKitIntellectGainMultiplier.armor;
|
: this.repairConfig.repairKitIntellectGainMultiplier.armor;
|
||||||
|
|
||||||
// limit gain to a max value defined in config.maxIntellectGainPerRepair
|
// 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
|
else
|
||||||
{
|
{
|
||||||
@ -192,6 +193,43 @@ export class RepairService
|
|||||||
this.questHelper.rewardSkillPoints(sessionId, pmcData, SkillTypes.INTELLECT, intellectGainedFromRepair);
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param sessionId Session id
|
* @param sessionId Session id
|
||||||
@ -218,12 +256,13 @@ export class RepairService
|
|||||||
const itemsDb = this.databaseServer.getTables().templates.items;
|
const itemsDb = this.databaseServer.getTables().templates.items;
|
||||||
const itemToRepairDetails = itemsDb[itemToRepair._tpl];
|
const itemToRepairDetails = itemsDb[itemToRepair._tpl];
|
||||||
const repairItemIsArmor = (!!itemToRepairDetails._props.ArmorMaterial);
|
const repairItemIsArmor = (!!itemToRepairDetails._props.ArmorMaterial);
|
||||||
|
const repairAmount = repairKits[0].count / this.getKitDivisor(itemToRepairDetails, repairItemIsArmor, pmcData);
|
||||||
|
|
||||||
this.repairHelper.updateItemDurability(
|
this.repairHelper.updateItemDurability(
|
||||||
itemToRepair,
|
itemToRepair,
|
||||||
itemToRepairDetails,
|
itemToRepairDetails,
|
||||||
repairItemIsArmor,
|
repairItemIsArmor,
|
||||||
repairKits[0].count / this.getKitDivisor(itemToRepairDetails, repairItemIsArmor, pmcData),
|
repairAmount,
|
||||||
true,
|
true,
|
||||||
1,
|
1,
|
||||||
this.repairConfig.applyRandomizeDurabilityLoss);
|
this.repairConfig.applyRandomizeDurabilityLoss);
|
||||||
@ -244,9 +283,10 @@ export class RepairService
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
repairPoints: repairKits[0].count,
|
||||||
repairedItem: itemToRepair,
|
repairedItem: itemToRepair,
|
||||||
repairedItemIsArmor: repairItemIsArmor,
|
repairedItemIsArmor: repairItemIsArmor,
|
||||||
repairAmount: repairKits[0].count,
|
repairAmount: repairAmount,
|
||||||
repairedByKit: true
|
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 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 durabilityMultiplier = this.getDurabilityMultiplier(receivedDurabilityMaxPercent, durabilityToRestorePercent);
|
||||||
|
|
||||||
const doBuff = commonBuffMinChanceValue + commonBuffChanceLevelBonus * skillLevel * durabilityMultiplier;
|
const doBuff = commonBuffMinChanceValue + commonBuffChanceLevelBonus * skillLevel * durabilityMultiplier;
|
||||||
@ -483,6 +523,7 @@ export class RepairService
|
|||||||
export class RepairDetails
|
export class RepairDetails
|
||||||
{
|
{
|
||||||
repairCost?: number;
|
repairCost?: number;
|
||||||
|
repairPoints?: number;
|
||||||
repairedItem: Item;
|
repairedItem: Item;
|
||||||
repairedItemIsArmor: boolean;
|
repairedItemIsArmor: boolean;
|
||||||
repairAmount: number;
|
repairAmount: number;
|
||||||
|
Loading…
Reference in New Issue
Block a user