77dba80fbd
# 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
250 lines
10 KiB
TypeScript
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,
|
|
};
|
|
}
|
|
}
|