Server/project/src/helpers/TraderAssortHelper.ts
Dev 77dba80fbd Merge branch 'master' of https://dev.sp-tarkov.com/SPT-AKI/Server into 3.8.0
# Conflicts:
#	project/assets/configs/core.json
#	project/assets/database/locales/global/ch.json
#	project/assets/database/locales/global/cz.json
#	project/assets/database/locales/global/en.json
#	project/assets/database/locales/global/es-mx.json
#	project/assets/database/locales/global/es.json
#	project/assets/database/locales/global/fr.json
#	project/assets/database/locales/global/ge.json
#	project/assets/database/locales/global/hu.json
#	project/assets/database/locales/global/it.json
#	project/assets/database/locales/global/jp.json
#	project/assets/database/locales/global/kr.json
#	project/assets/database/locales/global/pl.json
#	project/assets/database/locales/global/po.json
#	project/assets/database/locales/global/ru.json
#	project/assets/database/locales/global/sk.json
#	project/assets/database/locales/global/tu.json
#	project/src/controllers/TradeController.ts
#	project/src/generators/LocationGenerator.ts
2023-12-08 22:20:16 +00:00

250 lines
10 KiB
TypeScript

import { inject, injectable } from "tsyringe";
import { RagfairAssortGenerator } from "@spt-aki/generators/RagfairAssortGenerator";
import { RagfairOfferGenerator } from "@spt-aki/generators/RagfairOfferGenerator";
import { AssortHelper } from "@spt-aki/helpers/AssortHelper";
import { PaymentHelper } from "@spt-aki/helpers/PaymentHelper";
import { ProfileHelper } from "@spt-aki/helpers/ProfileHelper";
import { TraderHelper } from "@spt-aki/helpers/TraderHelper";
import { Item } from "@spt-aki/models/eft/common/tables/IItem";
import { ITrader, ITraderAssort } from "@spt-aki/models/eft/common/tables/ITrader";
import { ConfigTypes } from "@spt-aki/models/enums/ConfigTypes";
import { Traders } from "@spt-aki/models/enums/Traders";
import { ITraderConfig } from "@spt-aki/models/spt/config/ITraderConfig";
import { ILogger } from "@spt-aki/models/spt/utils/ILogger";
import { ConfigServer } from "@spt-aki/servers/ConfigServer";
import { DatabaseServer } from "@spt-aki/servers/DatabaseServer";
import { FenceService } from "@spt-aki/services/FenceService";
import { LocalisationService } from "@spt-aki/services/LocalisationService";
import { TraderAssortService } from "@spt-aki/services/TraderAssortService";
import { TraderPurchasePersisterService } from "@spt-aki/services/TraderPurchasePersisterService";
import { JsonUtil } from "@spt-aki/utils/JsonUtil";
import { MathUtil } from "@spt-aki/utils/MathUtil";
import { TimeUtil } from "@spt-aki/utils/TimeUtil";
@injectable()
export class TraderAssortHelper
{
protected traderConfig: ITraderConfig;
protected mergedQuestAssorts: Record<string, Record<string, string>> = { started: {}, success: {}, fail: {} };
protected createdMergedQuestAssorts = false;
constructor(
@inject("WinstonLogger") protected logger: ILogger,
@inject("JsonUtil") protected jsonUtil: JsonUtil,
@inject("MathUtil") protected mathUtil: MathUtil,
@inject("TimeUtil") protected timeUtil: TimeUtil,
@inject("DatabaseServer") protected databaseServer: DatabaseServer,
@inject("ProfileHelper") protected profileHelper: ProfileHelper,
@inject("AssortHelper") protected assortHelper: AssortHelper,
@inject("PaymentHelper") protected paymentHelper: PaymentHelper,
@inject("RagfairAssortGenerator") protected ragfairAssortGenerator: RagfairAssortGenerator,
@inject("RagfairOfferGenerator") protected ragfairOfferGenerator: RagfairOfferGenerator,
@inject("TraderAssortService") protected traderAssortService: TraderAssortService,
@inject("LocalisationService") protected localisationService: LocalisationService,
@inject("TraderPurchasePersisterService") protected traderPurchasePersisterService:
TraderPurchasePersisterService,
@inject("TraderHelper") protected traderHelper: TraderHelper,
@inject("FenceService") protected fenceService: FenceService,
@inject("ConfigServer") protected configServer: ConfigServer,
)
{
this.traderConfig = this.configServer.getConfig(ConfigTypes.TRADER);
}
/**
* Get a traders assorts
* Can be used for returning ragfair / fence assorts
* Filter out assorts not unlocked due to level OR quest completion
* @param sessionId session id
* @param traderId traders id
* @param flea Should assorts player hasn't unlocked be returned - default false
* @returns a traders' assorts
*/
public getAssort(sessionId: string, traderId: string, flea = false): ITraderAssort
{
// Special case for getting ragfair items as they're dynamically generated
if (traderId === "ragfair")
{
return this.getRagfairDataAsTraderAssort();
}
const trader = this.jsonUtil.clone(this.databaseServer.getTables().traders[traderId]);
const pmcProfile = this.profileHelper.getPmcProfile(sessionId);
if (traderId === Traders.FENCE)
{
return this.fenceService.getFenceAssorts(pmcProfile);
}
// Strip assorts player should not see yet
if (!flea)
{
trader.assort = this.assortHelper.stripLockedLoyaltyAssort(pmcProfile, traderId, trader.assort);
}
// Append nextResupply value to assorts so client knows when refresh is occuring
trader.assort.nextResupply = trader.base.nextResupply;
// Adjust displayed assort counts based on values stored in profile
const assortPurchasesfromTrader = this.traderPurchasePersisterService.getProfileTraderPurchases(
sessionId,
traderId,
);
for (const assortId in assortPurchasesfromTrader)
{
// Find assort we want to update current buy count of
const assortToAdjust = trader.assort.items.find((x) => x._id === assortId);
if (!assortToAdjust)
{
this.logger.debug(
`Cannot find trader: ${trader.base.nickname} assort: ${assortId} to adjust BuyRestrictionCurrent value, skipping`,
);
continue;
}
if (!assortToAdjust.upd)
{
this.logger.debug(
`Unable to adjust assort ${assortToAdjust._id} item: ${assortToAdjust._tpl} BuyRestrictionCurrent value, assort has an undefined upd object`,
);
continue;
}
assortToAdjust.upd.BuyRestrictionCurrent = assortPurchasesfromTrader[assortId].count;
}
// Get rid of quest locked assorts
if (!this.createdMergedQuestAssorts)
{
this.hydrateMergedQuestAssorts();
this.createdMergedQuestAssorts = true;
}
trader.assort = this.assortHelper.stripLockedQuestAssort(
pmcProfile,
traderId,
trader.assort,
this.mergedQuestAssorts,
flea,
);
// Multiply price if multiplier is other than 1
if (this.traderConfig.traderPriceMultipler !== 1)
{
this.multiplyItemPricesByConfigMultiplier(trader.assort);
}
return trader.assort;
}
/**
* Create a dict of all assort id = quest id mappings used to work out what items should be shown to player based on the quests they've started/completed/failed
*/
protected hydrateMergedQuestAssorts(): void
{
const traders = this.databaseServer.getTables().traders;
// Loop every trader
for (const traderId in traders)
{
// Trader has quest assort data
const trader = traders[traderId];
if (trader.questassort)
{
// Started/Success/fail
for (const questStatus in trader.questassort)
{
// Each assort to quest id record
for (const assortId in trader.questassort[questStatus])
{
// Null guard
if (!this.mergedQuestAssorts[questStatus])
{
this.mergedQuestAssorts[questStatus] = {};
}
this.mergedQuestAssorts[questStatus][assortId] = trader.questassort[questStatus][assortId];
}
}
}
}
}
/**
* Reset a traders assorts and move nextResupply value to future
* Flag trader as needing a flea offer reset to be picked up by flea update() function
* @param trader trader details to alter
*/
public resetExpiredTrader(trader: ITrader): void
{
trader.assort.items = this.getPristineTraderAssorts(trader.base._id);
// Update resupply value to next timestamp
trader.base.nextResupply = this.traderHelper.getNextUpdateTimestamp(trader.base._id);
// Flag a refresh is needed so ragfair update() will pick it up
trader.base.refreshTraderRagfairOffers = true;
}
/**
* Does the supplied trader need its assorts refreshed
* @param traderID Trader to check
* @returns true they need refreshing
*/
public traderAssortsHaveExpired(traderID: string): boolean
{
const time = this.timeUtil.getTimestamp();
const trader = this.databaseServer.getTables().traders[traderID];
return trader.base.nextResupply <= time;
}
/**
* Iterate over all assorts barter_scheme values, find barters selling for money and multiply by multipler in config
* @param traderAssort Assorts to multiple price of
*/
protected multiplyItemPricesByConfigMultiplier(traderAssort: ITraderAssort): void
{
if (!this.traderConfig.traderPriceMultipler || this.traderConfig.traderPriceMultipler <= 0)
{
this.traderConfig.traderPriceMultipler = 0.01;
this.logger.warning(this.localisationService.getText("trader-price_multipler_is_zero_use_default"));
}
for (const assortId in traderAssort.barter_scheme)
{
const schemeDetails = traderAssort.barter_scheme[assortId][0];
if (schemeDetails.length === 1 && this.paymentHelper.isMoneyTpl(schemeDetails[0]._tpl))
{
schemeDetails[0].count = Math.ceil(schemeDetails[0].count * this.traderConfig.traderPriceMultipler);
}
}
}
/**
* Get an array of pristine trader items prior to any alteration by player (as they were on server start)
* @param traderId trader id
* @returns array of Items
*/
protected getPristineTraderAssorts(traderId: string): Item[]
{
return this.jsonUtil.clone(this.traderAssortService.getPristineTraderAssort(traderId).items);
}
/**
* Returns generated ragfair offers in a trader assort format
* @returns Trader assort object
*/
protected getRagfairDataAsTraderAssort(): ITraderAssort
{
return {
items: this.ragfairAssortGenerator.getAssortItems(),
barter_scheme: {},
loyal_level_items: {},
nextResupply: null,
};
}
}