Server/project/src/services/RepairService.ts

599 lines
24 KiB
TypeScript
Raw Normal View History

2023-03-03 15:23:46 +00:00
import { inject, injectable } from "tsyringe";
import { ItemHelper } from "@spt-aki/helpers/ItemHelper";
import { ProfileHelper } from "@spt-aki/helpers/ProfileHelper";
import { RepairHelper } from "@spt-aki/helpers/RepairHelper";
import { TraderHelper } from "@spt-aki/helpers/TraderHelper";
import { WeightedRandomHelper } from "@spt-aki/helpers/WeightedRandomHelper";
import { IArmorType } from "@spt-aki/models/eft/common/IGlobals";
import { IPmcData } from "@spt-aki/models/eft/common/IPmcData";
import { Item } from "@spt-aki/models/eft/common/tables/IItem";
import { ITemplateItem } from "@spt-aki/models/eft/common/tables/ITemplateItem";
import { IItemEventRouterResponse } from "@spt-aki/models/eft/itemEvent/IItemEventRouterResponse";
import { RepairKitsInfo } from "@spt-aki/models/eft/repair/IRepairActionDataRequest";
import { RepairItem } from "@spt-aki/models/eft/repair/ITraderRepairActionDataRequest";
import { IProcessBuyTradeRequestData } from "@spt-aki/models/eft/trade/IProcessBuyTradeRequestData";
import { BaseClasses } from "@spt-aki/models/enums/BaseClasses";
import { ConfigTypes } from "@spt-aki/models/enums/ConfigTypes";
import { SkillTypes } from "@spt-aki/models/enums/SkillTypes";
import { BonusSettings, IRepairConfig } from "@spt-aki/models/spt/config/IRepairConfig";
import { ILogger } from "@spt-aki/models/spt/utils/ILogger";
import { ConfigServer } from "@spt-aki/servers/ConfigServer";
import { DatabaseServer } from "@spt-aki/servers/DatabaseServer";
import { LocalisationService } from "@spt-aki/services/LocalisationService";
import { PaymentService } from "@spt-aki/services/PaymentService";
import { RandomUtil } from "@spt-aki/utils/RandomUtil";
2023-03-03 15:23:46 +00:00
@injectable()
export class RepairService
{
protected repairConfig: IRepairConfig;
constructor(
@inject("WinstonLogger") protected logger: ILogger,
@inject("DatabaseServer") protected databaseServer: DatabaseServer,
@inject("ProfileHelper") protected profileHelper: ProfileHelper,
2023-03-03 15:23:46 +00:00
@inject("RandomUtil") protected randomUtil: RandomUtil,
@inject("ItemHelper") protected itemHelper: ItemHelper,
@inject("TraderHelper") protected traderHelper: TraderHelper,
@inject("WeightedRandomHelper") protected weightedRandomHelper: WeightedRandomHelper,
@inject("PaymentService") protected paymentService: PaymentService,
@inject("RepairHelper") protected repairHelper: RepairHelper,
2023-07-19 11:00:34 +01:00
@inject("LocalisationService") protected localisationService: LocalisationService,
@inject("ConfigServer") protected configServer: ConfigServer,
2023-03-03 15:23:46 +00:00
)
{
this.repairConfig = this.configServer.getConfig(ConfigTypes.REPAIR);
}
/**
* Use trader to repair an items durability
* @param sessionID Session id
* @param pmcData profile to find item to repair in
* @param repairItemDetails details of the item to repair
* @param traderId Trader being used to repair item
* @returns RepairDetails object
*/
public repairItemByTrader(
sessionID: string,
pmcData: IPmcData,
repairItemDetails: RepairItem,
traderId: string,
): RepairDetails
2023-03-03 15:23:46 +00:00
{
const itemToRepair = pmcData.Inventory.items.find((x) => x._id === repairItemDetails._id);
2023-03-03 15:23:46 +00:00
if (itemToRepair === undefined)
{
throw new Error(`Item ${repairItemDetails._id} not found in profile inventory, unable to repair`);
}
const priceCoef = this.traderHelper.getLoyaltyLevel(traderId, pmcData).repair_price_coef;
const traderRepairDetails = this.traderHelper.getTrader(traderId, sessionID).repair;
const repairQualityMultiplier = traderRepairDetails.quality;
const repairRate = (priceCoef <= 0) ? 1 : (priceCoef / 100 + 1);
2023-03-03 15:23:46 +00:00
const itemToRepairDetails = this.databaseServer.getTables().templates.items[itemToRepair._tpl];
const repairItemIsArmor = !!itemToRepairDetails._props.ArmorMaterial;
2023-03-03 15:23:46 +00:00
this.repairHelper.updateItemDurability(
itemToRepair,
itemToRepairDetails,
repairItemIsArmor,
repairItemDetails.count,
false,
repairQualityMultiplier,
repairQualityMultiplier !== 0 && this.repairConfig.applyRandomizeDurabilityLoss,
2023-03-03 15:23:46 +00:00
);
// get repair price
const itemRepairCost = this.databaseServer.getTables().templates.items[itemToRepair._tpl]._props.RepairCost;
const repairCost = Math.round(
(itemRepairCost * repairItemDetails.count * repairRate) * this.repairConfig.priceMultiplier,
);
2023-03-03 15:23:46 +00:00
this.logger.debug(`item base repair cost: ${itemRepairCost}`, true);
this.logger.debug(`price multipler: ${this.repairConfig.priceMultiplier}`, true);
this.logger.debug(`repair cost: ${repairCost}`, true);
return {
repairCost: repairCost,
repairedItem: itemToRepair,
repairedItemIsArmor: repairItemIsArmor,
repairAmount: repairItemDetails.count,
repairedByKit: false,
2023-03-03 15:23:46 +00:00
};
}
/**
* @param sessionID Session id
* @param pmcData profile to take money from
* @param repairedItemId Repaired item id
* @param repairCost Cost to repair item in roubles
* @param traderId Id of the trader who repaired the item / who is paid
* @param output
2023-03-03 15:23:46 +00:00
*/
public payForRepair(
sessionID: string,
pmcData: IPmcData,
repairedItemId: string,
repairCost: number,
traderId: string,
output: IItemEventRouterResponse,
): void
2023-03-03 15:23:46 +00:00
{
const options: IProcessBuyTradeRequestData = {
// eslint-disable-next-line @typescript-eslint/naming-convention
scheme_items: [{
id: "5449016a4bdc2d6f028b456f", // Rouble tpl
count: Math.round(repairCost),
}],
2023-03-03 15:23:46 +00:00
tid: traderId,
Action: "SptRepair",
2023-03-03 15:23:46 +00:00
type: "",
// eslint-disable-next-line @typescript-eslint/naming-convention
item_id: "",
count: 0,
// eslint-disable-next-line @typescript-eslint/naming-convention
scheme_id: 0,
2023-03-03 15:23:46 +00:00
};
this.paymentService.payMoney(pmcData, options, sessionID, output);
}
/**
* Add skill points to profile after repairing an item
* @param sessionId Session id
* @param repairDetails details of item repaired, cost/item
* @param pmcData Profile to add points to
*/
public addRepairSkillPoints(sessionId: string, repairDetails: RepairDetails, pmcData: IPmcData): void
2023-03-03 15:23:46 +00:00
{
// Handle kit repair of weapon
if (
repairDetails.repairedByKit
&& this.itemHelper.isOfBaseclass(repairDetails.repairedItem._tpl, BaseClasses.WEAPON)
)
2023-03-03 15:23:46 +00:00
{
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>
2023-11-02 08:56:02 +00:00
const skillPoints = this.getWeaponRepairSkillPoints(repairDetails);
if (skillPoints > 0)
{
this.profileHelper.addSkillPointsToPlayer(pmcData, SkillTypes.WEAPON_TREATMENT, skillPoints, true);
}
2023-03-03 15:23:46 +00:00
}
// Handle kit repair of armor
if (
repairDetails.repairedByKit
&& this.itemHelper.isOfBaseclasses(repairDetails.repairedItem._tpl, [BaseClasses.ARMOR, BaseClasses.VEST])
)
2023-03-03 15:23:46 +00:00
{
const itemDetails = this.itemHelper.getItem(repairDetails.repairedItem._tpl);
if (!itemDetails[0])
{
// No item found
this.logger.error(
this.localisationService.getText(
"repair-unable_to_find_item_in_db",
repairDetails.repairedItem._tpl,
),
);
2023-03-03 15:23:46 +00:00
return;
}
const isHeavyArmor = itemDetails[1]._props.ArmorType === "Heavy";
const vestSkillToLevel = isHeavyArmor ? SkillTypes.HEAVY_VESTS : SkillTypes.LIGHT_VESTS;
const pointsToAddToVestSkill = repairDetails.repairPoints
* this.repairConfig.armorKitSkillPointGainPerRepairPointMultiplier;
2023-03-03 15:23:46 +00:00
this.profileHelper.addSkillPointsToPlayer(pmcData, vestSkillToLevel, pointsToAddToVestSkill);
2023-03-03 15:23:46 +00:00
}
// Handle giving INT to player - differs if using kit/trader and weapon vs armor
const intellectGainedFromRepair = this.getIntellectGainedFromRepair(repairDetails);
if (intellectGainedFromRepair > 0)
{
this.profileHelper.addSkillPointsToPlayer(pmcData, SkillTypes.INTELLECT, intellectGainedFromRepair);
}
}
protected getIntellectGainedFromRepair(repairDetails: RepairDetails): number
{
if (repairDetails.repairedByKit)
{
// Weapons/armor have different multipliers
const intRepairMultiplier =
(this.itemHelper.isOfBaseclass(repairDetails.repairedItem._tpl, BaseClasses.WEAPON))
? this.repairConfig.repairKitIntellectGainMultiplier.weapon
: this.repairConfig.repairKitIntellectGainMultiplier.armor;
// Limit gain to a max value defined in config.maxIntellectGainPerRepair
return Math.min(
repairDetails.repairPoints * intRepairMultiplier,
this.repairConfig.maxIntellectGainPerRepair.kit,
);
}
else
{
// Trader repair - Not as accurate as kit, needs data from live
return Math.min(
repairDetails.repairAmount / 10,
this.repairConfig.maxIntellectGainPerRepair.trader,
);
}
2023-03-03 15:23:46 +00:00
}
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>
2023-11-02 08:56:02 +00:00
/**
* 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
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>
2023-11-02 08:56:02 +00:00
{
// 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 Math.max(skillPoints, 0);
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>
2023-11-02 08:56:02 +00:00
}
2023-03-03 15:23:46 +00:00
/**
* @param sessionId Session id
* @param pmcData Profile to update repaired item in
* @param repairKits Array of Repair kits to use
* @param itemToRepairId Item id to repair
* @param output IItemEventRouterResponse
* @returns Details of repair, item/price
*/
public repairItemByKit(
sessionId: string,
pmcData: IPmcData,
repairKits: RepairKitsInfo[],
itemToRepairId: string,
output: IItemEventRouterResponse,
): RepairDetails
2023-03-03 15:23:46 +00:00
{
// Find item to repair in inventory
const itemToRepair = pmcData.Inventory.items.find((x: { _id: string; }) => x._id === itemToRepairId);
if (itemToRepair === undefined)
{
throw new Error(`Item ${itemToRepairId} not found, unable to repair`);
}
const itemsDb = this.databaseServer.getTables().templates.items;
const itemToRepairDetails = itemsDb[itemToRepair._tpl];
const repairItemIsArmor = !!itemToRepairDetails._props.ArmorMaterial;
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>
2023-11-02 08:56:02 +00:00
const repairAmount = repairKits[0].count / this.getKitDivisor(itemToRepairDetails, repairItemIsArmor, pmcData);
const shouldApplyDurabilityLoss = this.shouldRepairKitApplyDurabilityLoss(
pmcData,
this.repairConfig.applyRandomizeDurabilityLoss,
);
2023-03-03 15:23:46 +00:00
this.repairHelper.updateItemDurability(
itemToRepair,
itemToRepairDetails,
repairItemIsArmor,
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>
2023-11-02 08:56:02 +00:00
repairAmount,
true,
1,
shouldApplyDurabilityLoss,
);
2023-03-03 15:23:46 +00:00
// Find and use repair kit defined in body
for (const repairKit of repairKits)
{
const repairKitInInventory = pmcData.Inventory.items.find((x) => x._id === repairKit._id);
const repairKitDetails = itemsDb[repairKitInInventory._tpl];
2023-03-03 15:23:46 +00:00
const repairKitReductionAmount = repairKit.count;
this.addMaxResourceToKitIfMissing(repairKitDetails, repairKitInInventory);
// reduce usages on repairkit used
repairKitInInventory.upd.RepairKit.Resource -= repairKitReductionAmount;
output.profileChanges[sessionId].items.change.push(repairKitInInventory);
}
return {
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>
2023-11-02 08:56:02 +00:00
repairPoints: repairKits[0].count,
2023-03-03 15:23:46 +00:00
repairedItem: itemToRepair,
repairedItemIsArmor: repairItemIsArmor,
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>
2023-11-02 08:56:02 +00:00
repairAmount: repairAmount,
repairedByKit: true,
2023-03-03 15:23:46 +00:00
};
}
/**
* Calculate value repairkit points need to be divided by to get the durability points to be added to an item
* @param itemToRepairDetails Item to repair details
* @param isArmor Is the item being repaired armor
* @param pmcData Player profile
* @returns Number to divide kit points by
*/
protected getKitDivisor(itemToRepairDetails: ITemplateItem, isArmor: boolean, pmcData: IPmcData): number
2023-03-03 15:23:46 +00:00
{
const globals = this.databaseServer.getTables().globals;
const globalRepairSettings = globals.config.RepairSettings;
const intellectRepairPointsPerLevel = globals.config.SkillsSettings.Intellect.RepairPointsCostReduction;
const profileIntellectLevel = this.profileHelper.getSkillFromProfile(pmcData, SkillTypes.INTELLECT)?.Progress
?? 0;
2023-03-03 15:23:46 +00:00
const intellectPointReduction = intellectRepairPointsPerLevel * Math.trunc(profileIntellectLevel / 100);
if (isArmor)
{
const durabilityPointCostArmor = globalRepairSettings.durabilityPointCostArmor;
const repairArmorBonus = this.getBonusMultiplierValue("RepairArmorBonus", pmcData);
const armorBonus = 1.0 - (repairArmorBonus - 1.0) - intellectPointReduction;
2023-03-03 15:23:46 +00:00
const materialType = itemToRepairDetails._props.ArmorMaterial ?? "";
const armorMaterial = globals.config.ArmorMaterials[materialType] as IArmorType;
const destructability = 1 + armorMaterial.Destructibility;
2023-03-03 15:23:46 +00:00
const armorClass = parseInt(`${itemToRepairDetails._props.armorClass}`);
const armorClassDivisor = globals.config.RepairSettings.armorClassDivisor;
const armorClassMultiplier = 1.0 + armorClass / armorClassDivisor;
2023-03-03 15:23:46 +00:00
return durabilityPointCostArmor * armorBonus * destructability * armorClassMultiplier;
}
else
2023-03-03 15:23:46 +00:00
{
const repairWeaponBonus = this.getBonusMultiplierValue("RepairWeaponBonus", pmcData) - 1;
const repairPointMultiplier = 1.0 - repairWeaponBonus - intellectPointReduction;
2023-03-03 15:23:46 +00:00
const durabilityPointCostGuns = globals.config.RepairSettings.durabilityPointCostGuns;
return durabilityPointCostGuns * repairPointMultiplier;
}
}
/**
* Get the bonus multiplier for a skill from a player profile
* @param skillBonusName Name of bonus to get multipler of
* @param pmcData Player profile to look in for skill
* @returns Multiplier value
*/
protected getBonusMultiplierValue(skillBonusName: string, pmcData: IPmcData): number
{
const bonusesMatched = pmcData?.Bonuses?.filter((b) => b.type === skillBonusName);
2023-03-03 15:23:46 +00:00
let value = 1;
if (bonusesMatched != null)
{
const sumedPercentage = bonusesMatched.map((b) => b.value).reduce((v1, v2) => v1 + v2, 0);
2023-03-03 15:23:46 +00:00
value = 1 + sumedPercentage / 100;
}
return value;
}
/**
* Should a repair kit apply total durability loss on repair
* @param pmcData Player profile
* @param applyRandomizeDurabilityLoss Value from repair config
* @returns True if loss should be applied
*/
protected shouldRepairKitApplyDurabilityLoss(pmcData: IPmcData, applyRandomizeDurabilityLoss: boolean): boolean
{
let shouldApplyDurabilityLoss = applyRandomizeDurabilityLoss;
if (shouldApplyDurabilityLoss)
{
// Random loss not disabled via config, perform charisma check
const hasEliteCharisma = this.profileHelper.hasEliteSkillLevel(SkillTypes.CHARISMA, pmcData);
if (hasEliteCharisma)
{
// 50/50 chance of loss being ignored at elite level
shouldApplyDurabilityLoss = this.randomUtil.getChance100(50);
}
}
return shouldApplyDurabilityLoss;
}
2023-03-03 15:23:46 +00:00
/**
* Update repair kits Resource object if it doesn't exist
* @param repairKitDetails Repair kit details from db
* @param repairKitInInventory Repair kit to update
*/
protected addMaxResourceToKitIfMissing(repairKitDetails: ITemplateItem, repairKitInInventory: Item): void
{
const maxRepairAmount = repairKitDetails._props.MaxRepairResource;
if (!repairKitInInventory.upd)
{
this.logger.debug(`Repair kit: ${repairKitInInventory._id} in inventory lacks upd object, adding`);
repairKitInInventory.upd = { RepairKit: { Resource: maxRepairAmount } };
}
2023-03-03 15:23:46 +00:00
if (!repairKitInInventory.upd.RepairKit?.Resource)
{
repairKitInInventory.upd.RepairKit = { Resource: maxRepairAmount };
2023-03-03 15:23:46 +00:00
}
}
/**
* Chance to apply buff to an item (Armor/weapon) if repaired by armor kit
* @param repairDetails Repair details of item
* @param pmcData Player profile
*/
public addBuffToItem(repairDetails: RepairDetails, pmcData: IPmcData): void
{
// Buffs are repair kit only
if (!repairDetails.repairedByKit)
{
return;
}
if (this.shouldBuffItem(repairDetails, pmcData))
{
if (this.itemHelper.isOfBaseclasses(repairDetails.repairedItem._tpl, [BaseClasses.ARMOR, BaseClasses.VEST]))
{
const armorConfig = this.repairConfig.repairKit.armor;
this.addBuff(armorConfig, repairDetails.repairedItem);
2023-03-03 15:23:46 +00:00
}
else if (this.itemHelper.isOfBaseclass(repairDetails.repairedItem._tpl, BaseClasses.WEAPON))
{
const weaponConfig = this.repairConfig.repairKit.weapon;
this.addBuff(weaponConfig, repairDetails.repairedItem);
2023-03-03 15:23:46 +00:00
}
// TODO: Knife repair kits may be added at some point, a bracket needs to be added here
}
}
/**
* Add random buff to item
* @param itemConfig weapon/armor config
2023-03-03 15:23:46 +00:00
* @param repairDetails Details for item to repair
*/
public addBuff(itemConfig: BonusSettings, item: Item): void
2023-03-03 15:23:46 +00:00
{
const bonusRarity = this.weightedRandomHelper.getWeightedValue<string>(itemConfig.rarityWeight);
const bonusType = this.weightedRandomHelper.getWeightedValue<string>(itemConfig.bonusTypeWeight);
2023-03-03 15:23:46 +00:00
const bonusValues = itemConfig[bonusRarity][bonusType].valuesMinMax;
const bonusValue = this.randomUtil.getFloat(bonusValues.min, bonusValues.max);
const bonusThresholdPercents = itemConfig[bonusRarity][bonusType].activeDurabilityPercentMinMax;
const bonusThresholdPercent = this.randomUtil.getInt(bonusThresholdPercents.min, bonusThresholdPercents.max);
item.upd.Buff = {
2023-03-03 15:23:46 +00:00
rarity: bonusRarity,
buffType: bonusType,
value: bonusValue,
thresholdDurability: this.randomUtil.getPercentOfValue(
bonusThresholdPercent,
item.upd.Repairable.Durability,
),
2023-03-03 15:23:46 +00:00
};
}
/**
* Check if item should be buffed by checking the item type and relevant player skill level
* @param repairDetails Item that was repaired
* @param itemTpl tpl of item to be buffed
* @param pmcData Player profile
* @returns True if item should have buff applied
*/
protected shouldBuffItem(repairDetails: RepairDetails, pmcData: IPmcData): boolean
{
const globals = this.databaseServer.getTables().globals;
const hasTemplate = this.itemHelper.getItem(repairDetails.repairedItem._tpl);
if (!hasTemplate[0])
{
2023-03-03 15:23:46 +00:00
return false;
}
2023-03-03 15:23:46 +00:00
const template = hasTemplate[1];
const itemSkillType = this.getItemSkillType(template);
if (!itemSkillType)
{
2023-03-03 15:23:46 +00:00
return false;
}
2023-03-03 15:23:46 +00:00
const commonBuffMinChanceValue =
globals.config.SkillsSettings[itemSkillType as string].BuffSettings.CommonBuffMinChanceValue;
const commonBuffChanceLevelBonus =
globals.config.SkillsSettings[itemSkillType as string].BuffSettings.CommonBuffChanceLevelBonus;
const receivedDurabilityMaxPercent =
globals.config.SkillsSettings[itemSkillType as string].BuffSettings.ReceivedDurabilityMaxPercent;
2023-03-03 15:23:46 +00:00
const skillLevel = Math.trunc(
(this.profileHelper.getSkillFromProfile(pmcData, itemSkillType)?.Progress ?? 0) / 100,
);
2023-03-03 15:23:46 +00:00
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>
2023-11-02 08:56:02 +00:00
const durabilityToRestorePercent = repairDetails.repairPoints / template._props.MaxDurability;
const durabilityMultiplier = this.getDurabilityMultiplier(
receivedDurabilityMaxPercent,
durabilityToRestorePercent,
);
2023-03-03 15:23:46 +00:00
const doBuff = commonBuffMinChanceValue + commonBuffChanceLevelBonus * skillLevel * durabilityMultiplier;
if (Math.random() <= doBuff)
{
return true;
}
return false;
}
2023-03-03 15:23:46 +00:00
/**
* Based on item, what underlying skill does this item use for buff settings
* @param itemTemplate Item to check for skill
* @returns Skill name
*/
protected getItemSkillType(itemTemplate: ITemplateItem): SkillTypes
2023-03-03 15:23:46 +00:00
{
if (this.itemHelper.isOfBaseclass(itemTemplate._id, BaseClasses.ARMOR))
{
if (itemTemplate._props.ArmorType === "Light")
{
return SkillTypes.LIGHT_VESTS;
2023-03-03 15:23:46 +00:00
}
else if (itemTemplate._props.ArmorType === "Heavy")
{
return SkillTypes.HEAVY_VESTS;
2023-03-03 15:23:46 +00:00
}
}
else if (this.itemHelper.isOfBaseclass(itemTemplate._id, BaseClasses.WEAPON))
{
return SkillTypes.WEAPON_TREATMENT;
2023-03-03 15:23:46 +00:00
}
else if (this.itemHelper.isOfBaseclass(itemTemplate._id, BaseClasses.KNIFE))
{
return SkillTypes.MELEE;
2023-03-03 15:23:46 +00:00
}
return undefined;
}
/**
* Ensure multiplier is between 1 and 0.01
* @param receiveDurabilityMaxPercent Max durabiltiy percent
* @param receiveDurabilityPercent current durability percent
* @returns durability multipler value
*/
protected getDurabilityMultiplier(receiveDurabilityMaxPercent: number, receiveDurabilityPercent: number): number
{
receiveDurabilityMaxPercent = (receiveDurabilityMaxPercent > 0) ? receiveDurabilityMaxPercent : 0.01;
2023-03-03 15:23:46 +00:00
const num = receiveDurabilityPercent / receiveDurabilityMaxPercent;
if (num > 1)
{
return 1.0;
}
if (num < 0.01)
{
return 0.01;
}
return num;
}
}
export class RepairDetails
{
repairCost?: number;
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>
2023-11-02 08:56:02 +00:00
repairPoints?: number;
2023-03-03 15:23:46 +00:00
repairedItem: Item;
repairedItemIsArmor: boolean;
repairAmount: number;
repairedByKit: boolean;
}