4ac12ef70a
These are the formatting & linting configuration changes from the `3.8.0` branch and the changes that they make to the overall project. The majority of these changes are from running two commands: `npm run lint:fix` `npm run style:fix` This has already been run on the `3.8.0` branch and this PR should make `master` play nicer when it comes to merges going forward. There are now four VSCode plugins recommended for server development. They've been added to the workspace file and a user should get a UI notification when the workspace is opened if they're not installed. The four plugins are: https://marketplace.visualstudio.com/items?itemName=EditorConfig.EditorConfig https://marketplace.visualstudio.com/items?itemName=dprint.dprint https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint https://marketplace.visualstudio.com/items?itemName=biomejs.biome Once installed they should just work within the workspace. Also, be sure to `npm i` to get the new dprint application. Co-authored-by: Refringe <brownelltyler@gmail.com> Reviewed-on: https://dev.sp-tarkov.com/SPT-AKI/Server/pulls/168
807 lines
30 KiB
TypeScript
807 lines
30 KiB
TypeScript
import { inject, injectable } from "tsyringe";
|
|
|
|
import { RagfairAssortGenerator } from "@spt-aki/generators/RagfairAssortGenerator";
|
|
import { HandbookHelper } from "@spt-aki/helpers/HandbookHelper";
|
|
import { ItemHelper } from "@spt-aki/helpers/ItemHelper";
|
|
import { PaymentHelper } from "@spt-aki/helpers/PaymentHelper";
|
|
import { PresetHelper } from "@spt-aki/helpers/PresetHelper";
|
|
import { RagfairServerHelper } from "@spt-aki/helpers/RagfairServerHelper";
|
|
import { Item } from "@spt-aki/models/eft/common/tables/IItem";
|
|
import { ITemplateItem } from "@spt-aki/models/eft/common/tables/ITemplateItem";
|
|
import { IBarterScheme } from "@spt-aki/models/eft/common/tables/ITrader";
|
|
import { IRagfairOffer, OfferRequirement } from "@spt-aki/models/eft/ragfair/IRagfairOffer";
|
|
import { BaseClasses } from "@spt-aki/models/enums/BaseClasses";
|
|
import { ConfigTypes } from "@spt-aki/models/enums/ConfigTypes";
|
|
import { MemberCategory } from "@spt-aki/models/enums/MemberCategory";
|
|
import { Money } from "@spt-aki/models/enums/Money";
|
|
import { Dynamic, IRagfairConfig } from "@spt-aki/models/spt/config/IRagfairConfig";
|
|
import { ILogger } from "@spt-aki/models/spt/utils/ILogger";
|
|
import { ConfigServer } from "@spt-aki/servers/ConfigServer";
|
|
import { DatabaseServer } from "@spt-aki/servers/DatabaseServer";
|
|
import { SaveServer } from "@spt-aki/servers/SaveServer";
|
|
import { FenceService } from "@spt-aki/services/FenceService";
|
|
import { LocalisationService } from "@spt-aki/services/LocalisationService";
|
|
import { RagfairCategoriesService } from "@spt-aki/services/RagfairCategoriesService";
|
|
import { RagfairOfferService } from "@spt-aki/services/RagfairOfferService";
|
|
import { RagfairPriceService } from "@spt-aki/services/RagfairPriceService";
|
|
import { HashUtil } from "@spt-aki/utils/HashUtil";
|
|
import { JsonUtil } from "@spt-aki/utils/JsonUtil";
|
|
import { RandomUtil } from "@spt-aki/utils/RandomUtil";
|
|
import { TimeUtil } from "@spt-aki/utils/TimeUtil";
|
|
|
|
@injectable()
|
|
export class RagfairOfferGenerator
|
|
{
|
|
protected ragfairConfig: IRagfairConfig;
|
|
protected allowedFleaPriceItemsForBarter: { tpl: string; price: number; }[];
|
|
|
|
constructor(
|
|
@inject("WinstonLogger") protected logger: ILogger,
|
|
@inject("JsonUtil") protected jsonUtil: JsonUtil,
|
|
@inject("HashUtil") protected hashUtil: HashUtil,
|
|
@inject("RandomUtil") protected randomUtil: RandomUtil,
|
|
@inject("TimeUtil") protected timeUtil: TimeUtil,
|
|
@inject("DatabaseServer") protected databaseServer: DatabaseServer,
|
|
@inject("RagfairServerHelper") protected ragfairServerHelper: RagfairServerHelper,
|
|
@inject("HandbookHelper") protected handbookHelper: HandbookHelper,
|
|
@inject("SaveServer") protected saveServer: SaveServer,
|
|
@inject("PresetHelper") protected presetHelper: PresetHelper,
|
|
@inject("RagfairAssortGenerator") protected ragfairAssortGenerator: RagfairAssortGenerator,
|
|
@inject("RagfairOfferService") protected ragfairOfferService: RagfairOfferService,
|
|
@inject("RagfairPriceService") protected ragfairPriceService: RagfairPriceService,
|
|
@inject("LocalisationService") protected localisationService: LocalisationService,
|
|
@inject("PaymentHelper") protected paymentHelper: PaymentHelper,
|
|
@inject("RagfairCategoriesService") protected ragfairCategoriesService: RagfairCategoriesService,
|
|
@inject("FenceService") protected fenceService: FenceService,
|
|
@inject("ItemHelper") protected itemHelper: ItemHelper,
|
|
@inject("ConfigServer") protected configServer: ConfigServer,
|
|
)
|
|
{
|
|
this.ragfairConfig = this.configServer.getConfig(ConfigTypes.RAGFAIR);
|
|
}
|
|
|
|
/**
|
|
* Create a flea offer and store it in the Ragfair server offers array
|
|
* @param userID Owner of the offer
|
|
* @param time Time offer is listed at
|
|
* @param items Items in the offer
|
|
* @param barterScheme Cost of item (currency or barter)
|
|
* @param loyalLevel Loyalty level needed to buy item
|
|
* @param sellInOnePiece Flags sellInOnePiece to be true
|
|
* @returns IRagfairOffer
|
|
*/
|
|
public createFleaOffer(
|
|
userID: string,
|
|
time: number,
|
|
items: Item[],
|
|
barterScheme: IBarterScheme[],
|
|
loyalLevel: number,
|
|
sellInOnePiece = false,
|
|
): IRagfairOffer
|
|
{
|
|
const offer = this.createOffer(userID, time, items, barterScheme, loyalLevel, sellInOnePiece);
|
|
this.ragfairOfferService.addOffer(offer);
|
|
|
|
return offer;
|
|
}
|
|
|
|
/**
|
|
* Create an offer object ready to send to ragfairOfferService.addOffer()
|
|
* @param userID Owner of the offer
|
|
* @param time Time offer is listed at
|
|
* @param items Items in the offer
|
|
* @param barterScheme Cost of item (currency or barter)
|
|
* @param loyalLevel Loyalty level needed to buy item
|
|
* @param sellInOnePiece Set StackObjectsCount to 1
|
|
* @returns IRagfairOffer
|
|
*/
|
|
protected createOffer(
|
|
userID: string,
|
|
time: number,
|
|
items: Item[],
|
|
barterScheme: IBarterScheme[],
|
|
loyalLevel: number,
|
|
sellInOnePiece = false,
|
|
): IRagfairOffer
|
|
{
|
|
const isTrader = this.ragfairServerHelper.isTrader(userID);
|
|
|
|
const offerRequirements: OfferRequirement[] = [];
|
|
for (const barter of barterScheme)
|
|
{
|
|
const requirement: OfferRequirement = {
|
|
_tpl: barter._tpl,
|
|
count: +barter.count.toFixed(2),
|
|
onlyFunctional: barter.onlyFunctional ?? false,
|
|
};
|
|
|
|
offerRequirements.push(requirement);
|
|
}
|
|
|
|
const itemCount = items.filter((x) => x.slotId === "hideout").length;
|
|
const roublePrice = Math.round(this.convertOfferRequirementsIntoRoubles(offerRequirements));
|
|
|
|
const offer: IRagfairOffer = {
|
|
_id: this.hashUtil.generate(),
|
|
intId: 0,
|
|
user: {
|
|
id: this.getTraderId(userID),
|
|
memberType: (userID === "ragfair")
|
|
? MemberCategory.DEFAULT
|
|
: this.ragfairServerHelper.getMemberType(userID),
|
|
nickname: this.ragfairServerHelper.getNickname(userID),
|
|
rating: this.getRating(userID),
|
|
isRatingGrowing: this.getRatingGrowing(userID),
|
|
avatar: this.getAvatarUrl(isTrader, userID),
|
|
},
|
|
root: items[0]._id,
|
|
items: this.jsonUtil.clone(items),
|
|
requirements: offerRequirements,
|
|
requirementsCost: roublePrice,
|
|
itemsCost: Math.round(this.handbookHelper.getTemplatePrice(items[0]._tpl)), // Handbook price
|
|
summaryCost: roublePrice,
|
|
startTime: time,
|
|
endTime: this.getOfferEndTime(userID, time),
|
|
loyaltyLevel: loyalLevel,
|
|
sellInOnePiece: sellInOnePiece,
|
|
priority: false,
|
|
locked: false,
|
|
unlimitedCount: false,
|
|
notAvailable: false,
|
|
CurrentItemCount: itemCount,
|
|
};
|
|
|
|
return offer;
|
|
}
|
|
|
|
/**
|
|
* Calculate the offer price that's listed on the flea listing
|
|
* @param offerRequirements barter requirements for offer
|
|
* @returns rouble cost of offer
|
|
*/
|
|
protected convertOfferRequirementsIntoRoubles(offerRequirements: OfferRequirement[]): number
|
|
{
|
|
let roublePrice = 0;
|
|
for (const requirement of offerRequirements)
|
|
{
|
|
roublePrice += this.paymentHelper.isMoneyTpl(requirement._tpl)
|
|
? Math.round(this.calculateRoublePrice(requirement.count, requirement._tpl))
|
|
: this.ragfairPriceService.getFleaPriceForItem(requirement._tpl) * requirement.count; // get flea price for barter offer items
|
|
}
|
|
|
|
return roublePrice;
|
|
}
|
|
|
|
/**
|
|
* Get avatar url from trader table in db
|
|
* @param isTrader Is user we're getting avatar for a trader
|
|
* @param userId persons id to get avatar of
|
|
* @returns url of avatar
|
|
*/
|
|
protected getAvatarUrl(isTrader: boolean, userId: string): string
|
|
{
|
|
if (isTrader)
|
|
{
|
|
return this.databaseServer.getTables().traders[userId].base.avatar;
|
|
}
|
|
|
|
return "/files/trader/avatar/unknown.jpg";
|
|
}
|
|
|
|
/**
|
|
* Convert a count of currency into roubles
|
|
* @param currencyCount amount of currency to convert into roubles
|
|
* @param currencyType Type of currency (euro/dollar/rouble)
|
|
* @returns count of roubles
|
|
*/
|
|
protected calculateRoublePrice(currencyCount: number, currencyType: string): number
|
|
{
|
|
if (currencyType === Money.ROUBLES)
|
|
{
|
|
return currencyCount;
|
|
}
|
|
else
|
|
{
|
|
return this.handbookHelper.inRUB(currencyCount, currencyType);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check userId, if its a player, return their pmc _id, otherwise return userId parameter
|
|
* @param userId Users Id to check
|
|
* @returns Users Id
|
|
*/
|
|
protected getTraderId(userId: string): string
|
|
{
|
|
if (this.ragfairServerHelper.isPlayer(userId))
|
|
{
|
|
return this.saveServer.getProfile(userId).characters.pmc._id;
|
|
}
|
|
|
|
return userId;
|
|
}
|
|
|
|
/**
|
|
* Get a flea trading rating for the passed in user
|
|
* @param userId User to get flea rating of
|
|
* @returns Flea rating value
|
|
*/
|
|
protected getRating(userId: string): number
|
|
{
|
|
if (this.ragfairServerHelper.isPlayer(userId))
|
|
{
|
|
// Player offer
|
|
return this.saveServer.getProfile(userId).characters.pmc.RagfairInfo.rating;
|
|
}
|
|
|
|
if (this.ragfairServerHelper.isTrader(userId))
|
|
{
|
|
// Trader offer
|
|
return 1;
|
|
}
|
|
|
|
// Generated pmc offer
|
|
return this.randomUtil.getFloat(this.ragfairConfig.dynamic.rating.min, this.ragfairConfig.dynamic.rating.max);
|
|
}
|
|
|
|
/**
|
|
* Is the offers user rating growing
|
|
* @param userID user to check rating of
|
|
* @returns true if its growing
|
|
*/
|
|
protected getRatingGrowing(userID: string): boolean
|
|
{
|
|
if (this.ragfairServerHelper.isPlayer(userID))
|
|
{
|
|
// player offer
|
|
return this.saveServer.getProfile(userID).characters.pmc.RagfairInfo.isRatingGrowing;
|
|
}
|
|
|
|
if (this.ragfairServerHelper.isTrader(userID))
|
|
{
|
|
// trader offer
|
|
return true;
|
|
}
|
|
|
|
// generated offer
|
|
// 50/50 growing/falling
|
|
return this.randomUtil.getBool();
|
|
}
|
|
|
|
/**
|
|
* Get number of section until offer should expire
|
|
* @param userID Id of the offer owner
|
|
* @param time Time the offer is posted
|
|
* @returns number of seconds until offer expires
|
|
*/
|
|
protected getOfferEndTime(userID: string, time: number): number
|
|
{
|
|
if (this.ragfairServerHelper.isPlayer(userID))
|
|
{
|
|
// Player offer
|
|
return this.timeUtil.getTimestamp() + Math.round(12 * TimeUtil.oneHourAsSeconds);
|
|
}
|
|
|
|
if (this.ragfairServerHelper.isTrader(userID))
|
|
{
|
|
// Trader offer
|
|
return this.databaseServer.getTables().traders[userID].base.nextResupply;
|
|
}
|
|
|
|
// Generated fake-player offer
|
|
return Math.round(
|
|
time
|
|
+ this.randomUtil.getInt(
|
|
this.ragfairConfig.dynamic.endTimeSeconds.min,
|
|
this.ragfairConfig.dynamic.endTimeSeconds.max,
|
|
),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Create multiple offers for items by using a unique list of items we've generated previously
|
|
* @param expiredOffers optional, expired offers to regenerate
|
|
*/
|
|
public async generateDynamicOffers(expiredOffers: Item[] = null): Promise<void>
|
|
{
|
|
const config = this.ragfairConfig.dynamic;
|
|
|
|
// get assort items from param if they exist, otherwise grab freshly generated assorts
|
|
const assortItemsToProcess: Item[] = expiredOffers
|
|
? expiredOffers
|
|
: this.ragfairAssortGenerator.getAssortItems();
|
|
|
|
// Store all functions to create an offer for every item and pass into Promise.all to run async
|
|
const assorOffersForItemsProcesses = [];
|
|
for (const assortItemIndex in assortItemsToProcess)
|
|
{
|
|
assorOffersForItemsProcesses.push(
|
|
this.createOffersForItems(assortItemIndex, assortItemsToProcess, expiredOffers, config),
|
|
);
|
|
}
|
|
|
|
await Promise.all(assorOffersForItemsProcesses);
|
|
}
|
|
|
|
/**
|
|
* @param assortItemIndex Index of assort item
|
|
* @param assortItemsToProcess Item array containing index
|
|
* @param expiredOffers Currently expired offers on flea
|
|
* @param config Ragfair dynamic config
|
|
*/
|
|
protected async createOffersForItems(
|
|
assortItemIndex: string,
|
|
assortItemsToProcess: Item[],
|
|
expiredOffers: Item[],
|
|
config: Dynamic,
|
|
): Promise<void>
|
|
{
|
|
const assortItem = assortItemsToProcess[assortItemIndex];
|
|
const itemDetails = this.itemHelper.getItem(assortItem._tpl);
|
|
|
|
const isPreset = this.presetHelper.isPreset(assortItem._id);
|
|
|
|
// Only perform checks on newly generated items, skip expired items being refreshed
|
|
if (!(expiredOffers || this.ragfairServerHelper.isItemValidRagfairItem(itemDetails)))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Get item + sub-items if preset, otherwise just get item
|
|
const items: Item[] = isPreset
|
|
? this.ragfairServerHelper.getPresetItems(assortItem)
|
|
: [
|
|
...[assortItem],
|
|
...this.itemHelper.findAndReturnChildrenByAssort(
|
|
assortItem._id,
|
|
this.ragfairAssortGenerator.getAssortItems(),
|
|
),
|
|
];
|
|
|
|
// Get number of offers to create
|
|
// Limit to 1 offer when processing expired
|
|
const offerCount = expiredOffers
|
|
? 1
|
|
: Math.round(this.randomUtil.getInt(config.offerItemCount.min, config.offerItemCount.max));
|
|
|
|
// Store all functions to create offers for this item and pass into Promise.all to run async
|
|
const assortSingleOfferProcesses = [];
|
|
for (let index = 0; index < offerCount; index++)
|
|
{
|
|
assortSingleOfferProcesses.push(this.createSingleOfferForItem(items, isPreset, itemDetails));
|
|
}
|
|
|
|
await Promise.all(assortSingleOfferProcesses);
|
|
}
|
|
|
|
/**
|
|
* Create one flea offer for a specific item
|
|
* @param items Item to create offer for
|
|
* @param isPreset Is item a weapon preset
|
|
* @param itemDetails raw db item details
|
|
* @returns Item array
|
|
*/
|
|
protected async createSingleOfferForItem(
|
|
items: Item[],
|
|
isPreset: boolean,
|
|
itemDetails: [boolean, ITemplateItem],
|
|
): Promise<void>
|
|
{
|
|
// Set stack size to random value
|
|
items[0].upd.StackObjectsCount = this.ragfairServerHelper.calculateDynamicStackCount(items[0]._tpl, isPreset);
|
|
|
|
const isBarterOffer = this.randomUtil.getChance100(this.ragfairConfig.dynamic.barter.chancePercent);
|
|
const isPackOffer = this.randomUtil.getChance100(this.ragfairConfig.dynamic.pack.chancePercent)
|
|
&& !isBarterOffer
|
|
&& items.length === 1
|
|
&& this.itemHelper.isOfBaseclasses(items[0]._tpl, this.ragfairConfig.dynamic.pack.itemTypeWhitelist);
|
|
const randomUserId = this.hashUtil.generate();
|
|
|
|
let barterScheme: IBarterScheme[];
|
|
if (isPackOffer)
|
|
{
|
|
// Set pack size
|
|
const stackSize = this.randomUtil.getInt(
|
|
this.ragfairConfig.dynamic.pack.itemCountMin,
|
|
this.ragfairConfig.dynamic.pack.itemCountMax,
|
|
);
|
|
items[0].upd.StackObjectsCount = stackSize;
|
|
|
|
// Don't randomise pack items
|
|
barterScheme = this.createCurrencyBarterScheme(items, isPackOffer, stackSize);
|
|
}
|
|
else if (isBarterOffer)
|
|
{
|
|
// Apply randomised properties
|
|
items = this.randomiseItemUpdProperties(randomUserId, items, itemDetails[1]);
|
|
barterScheme = this.createBarterBarterScheme(items);
|
|
}
|
|
else
|
|
{
|
|
// Apply randomised properties
|
|
items = this.randomiseItemUpdProperties(randomUserId, items, itemDetails[1]);
|
|
barterScheme = this.createCurrencyBarterScheme(items, isPackOffer);
|
|
}
|
|
|
|
const offer = this.createFleaOffer(
|
|
randomUserId,
|
|
this.timeUtil.getTimestamp(),
|
|
items,
|
|
barterScheme,
|
|
1,
|
|
isPreset || isPackOffer,
|
|
); // sellAsOnePiece
|
|
|
|
this.ragfairCategoriesService.incrementCategory(offer);
|
|
}
|
|
|
|
/**
|
|
* Generate trader offers on flea using the traders assort data
|
|
* @param traderID Trader to generate offers for
|
|
*/
|
|
public generateFleaOffersForTrader(traderID: string): void
|
|
{
|
|
// Ensure old offers don't exist
|
|
this.ragfairOfferService.removeAllOffersByTrader(traderID);
|
|
|
|
// Add trader offers
|
|
const time = this.timeUtil.getTimestamp();
|
|
const trader = this.databaseServer.getTables().traders[traderID];
|
|
const assorts = trader.assort;
|
|
|
|
// Trader assorts / assort items are missing
|
|
if (!assorts?.items?.length)
|
|
{
|
|
this.logger.error(
|
|
this.localisationService.getText(
|
|
"ragfair-no_trader_assorts_cant_generate_flea_offers",
|
|
trader.base.nickname,
|
|
),
|
|
);
|
|
return;
|
|
}
|
|
|
|
for (const item of assorts.items)
|
|
{
|
|
// We only want to process 'base' items, no children
|
|
if (item.slotId !== "hideout")
|
|
{
|
|
// skip mod items
|
|
continue;
|
|
}
|
|
|
|
// Run blacklist check on trader offers
|
|
if (this.ragfairConfig.dynamic.blacklist.traderItems)
|
|
{
|
|
const itemDetails = this.itemHelper.getItem(item._tpl);
|
|
if (!itemDetails[0])
|
|
{
|
|
this.logger.warning(this.localisationService.getText("ragfair-tpl_not_a_valid_item", item._tpl));
|
|
continue;
|
|
}
|
|
|
|
// Don't include items that BSG has blacklisted from flea
|
|
if (this.ragfairConfig.dynamic.blacklist.enableBsgList && !itemDetails[1]._props.CanSellOnRagfair)
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
const isPreset = this.presetHelper.isPreset(item._id);
|
|
const items: Item[] = isPreset
|
|
? this.ragfairServerHelper.getPresetItems(item)
|
|
: [...[item], ...this.itemHelper.findAndReturnChildrenByAssort(item._id, assorts.items)];
|
|
|
|
const barterScheme = assorts.barter_scheme[item._id];
|
|
if (!barterScheme)
|
|
{
|
|
this.logger.warning(
|
|
this.localisationService.getText("ragfair-missing_barter_scheme", {
|
|
itemId: item._id,
|
|
tpl: item._tpl,
|
|
name: trader.base.nickname,
|
|
}),
|
|
);
|
|
continue;
|
|
}
|
|
|
|
const barterSchemeItems = assorts.barter_scheme[item._id][0];
|
|
const loyalLevel = assorts.loyal_level_items[item._id];
|
|
|
|
const offer = this.createFleaOffer(traderID, time, items, barterSchemeItems, loyalLevel, false);
|
|
|
|
this.ragfairCategoriesService.incrementCategory(offer);
|
|
|
|
// Refresh complete, reset flag to false
|
|
trader.base.refreshTraderRagfairOffers = false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get array of an item with its mods + condition properties (e.g durability)
|
|
* Apply randomisation adjustments to condition if item base is found in ragfair.json/dynamic/condition
|
|
* @param userID id of owner of item
|
|
* @param itemWithMods Item and mods, get condition of first item (only first array item is used)
|
|
* @param itemDetails db details of first item
|
|
* @returns
|
|
*/
|
|
protected randomiseItemUpdProperties(userID: string, itemWithMods: Item[], itemDetails: ITemplateItem): Item[]
|
|
{
|
|
// Add any missing properties to first item in array
|
|
itemWithMods[0] = this.addMissingConditions(itemWithMods[0]);
|
|
|
|
if (!(this.ragfairServerHelper.isPlayer(userID) || this.ragfairServerHelper.isTrader(userID)))
|
|
{
|
|
const parentId = this.getDynamicConditionIdForTpl(itemDetails._id);
|
|
if (!parentId)
|
|
{
|
|
// No condition details found, don't proceed with modifying item conditions
|
|
return itemWithMods;
|
|
}
|
|
|
|
// Roll random chance to randomise item condition
|
|
if (this.randomUtil.getChance100(this.ragfairConfig.dynamic.condition[parentId].conditionChance * 100))
|
|
{
|
|
this.randomiseItemCondition(parentId, itemWithMods[0], itemDetails);
|
|
}
|
|
}
|
|
|
|
return itemWithMods;
|
|
}
|
|
|
|
/**
|
|
* Get the relevant condition id if item tpl matches in ragfair.json/condition
|
|
* @param tpl Item to look for matching condition object
|
|
* @returns condition id
|
|
*/
|
|
protected getDynamicConditionIdForTpl(tpl: string): string
|
|
{
|
|
// Get keys from condition config dictionary
|
|
const configConditions = Object.keys(this.ragfairConfig.dynamic.condition);
|
|
for (const baseClass of configConditions)
|
|
{
|
|
if (this.itemHelper.isOfBaseclass(tpl, baseClass))
|
|
{
|
|
return baseClass;
|
|
}
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
/**
|
|
* Alter an items condition based on its item base type
|
|
* @param conditionSettingsId also the parentId of item being altered
|
|
* @param item Item to adjust condition details of
|
|
* @param itemDetails db item details of first item in array
|
|
*/
|
|
protected randomiseItemCondition(conditionSettingsId: string, item: Item, itemDetails: ITemplateItem): void
|
|
{
|
|
const multiplier = this.randomUtil.getFloat(
|
|
this.ragfairConfig.dynamic.condition[conditionSettingsId].min,
|
|
this.ragfairConfig.dynamic.condition[conditionSettingsId].max,
|
|
);
|
|
|
|
// Armor or weapons
|
|
if (item.upd.Repairable)
|
|
{
|
|
// Randomise non-0 class armor
|
|
if (itemDetails._props.armorClass && <number>itemDetails._props.armorClass >= 1)
|
|
{
|
|
this.randomiseDurabilityValues(item, multiplier);
|
|
}
|
|
|
|
// Randomise Weapons
|
|
if (this.itemHelper.isOfBaseclass(itemDetails._id, BaseClasses.WEAPON))
|
|
{
|
|
this.randomiseDurabilityValues(item, multiplier);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if (item.upd.MedKit)
|
|
{
|
|
// randomize health
|
|
item.upd.MedKit.HpResource = Math.round(item.upd.MedKit.HpResource * multiplier) || 1;
|
|
|
|
return;
|
|
}
|
|
|
|
if (item.upd.Key && itemDetails._props.MaximumNumberOfUsage > 1)
|
|
{
|
|
// randomize key uses
|
|
item.upd.Key.NumberOfUsages = Math.round(itemDetails._props.MaximumNumberOfUsage * (1 - multiplier)) || 0;
|
|
|
|
return;
|
|
}
|
|
|
|
if (item.upd.FoodDrink)
|
|
{
|
|
// randomize food/drink value
|
|
item.upd.FoodDrink.HpPercent = Math.round(itemDetails._props.MaxResource * multiplier) || 1;
|
|
|
|
return;
|
|
}
|
|
|
|
if (item.upd.RepairKit)
|
|
{
|
|
// randomize repair kit (armor/weapon) uses
|
|
item.upd.RepairKit.Resource = Math.round(itemDetails._props.MaxRepairResource * multiplier) || 1;
|
|
|
|
return;
|
|
}
|
|
|
|
if (this.itemHelper.isOfBaseclass(itemDetails._id, BaseClasses.FUEL))
|
|
{
|
|
const totalCapacity = itemDetails._props.MaxResource;
|
|
const remainingFuel = Math.round(totalCapacity * multiplier);
|
|
item.upd.Resource = { UnitsConsumed: totalCapacity - remainingFuel, Value: remainingFuel };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Adjust an items durability/maxDurability value
|
|
* @param item item (weapon/armor) to adjust
|
|
* @param multiplier Value to multiple durability by
|
|
*/
|
|
protected randomiseDurabilityValues(item: Item, multiplier: number): void
|
|
{
|
|
item.upd.Repairable.Durability = Math.round(item.upd.Repairable.Durability * multiplier) || 1;
|
|
|
|
// randomize max durability, store to a temporary value so we can still compare the max durability
|
|
let tempMaxDurability =
|
|
Math.round(
|
|
this.randomUtil.getFloat(item.upd.Repairable.Durability - 5, item.upd.Repairable.MaxDurability + 5),
|
|
) || item.upd.Repairable.Durability;
|
|
|
|
// clamp values to max/current
|
|
if (tempMaxDurability >= item.upd.Repairable.MaxDurability)
|
|
{
|
|
tempMaxDurability = item.upd.Repairable.MaxDurability;
|
|
}
|
|
if (tempMaxDurability < item.upd.Repairable.Durability)
|
|
{
|
|
tempMaxDurability = item.upd.Repairable.Durability;
|
|
}
|
|
|
|
// after clamping, finally assign to the item's properties
|
|
item.upd.Repairable.MaxDurability = tempMaxDurability;
|
|
}
|
|
|
|
/**
|
|
* Add missing conditions to an item if needed
|
|
* Durabiltiy for repairable items
|
|
* HpResource for medical items
|
|
* @param item item to add conditions to
|
|
* @returns Item with conditions added
|
|
*/
|
|
protected addMissingConditions(item: Item): Item
|
|
{
|
|
const props = this.itemHelper.getItem(item._tpl)[1]._props;
|
|
const isRepairable = "Durability" in props;
|
|
const isMedkit = "MaxHpResource" in props;
|
|
const isKey = "MaximumNumberOfUsage" in props;
|
|
const isConsumable = props.MaxResource > 1 && "foodUseTime" in props;
|
|
const isRepairKit = "MaxRepairResource" in props;
|
|
|
|
if (isRepairable && props.Durability > 0)
|
|
{
|
|
item.upd.Repairable = { Durability: props.Durability, MaxDurability: props.Durability };
|
|
}
|
|
|
|
if (isMedkit && props.MaxHpResource > 0)
|
|
{
|
|
item.upd.MedKit = { HpResource: props.MaxHpResource };
|
|
}
|
|
|
|
if (isKey)
|
|
{
|
|
item.upd.Key = { NumberOfUsages: 0 };
|
|
}
|
|
|
|
if (isConsumable)
|
|
{
|
|
item.upd.FoodDrink = { HpPercent: props.MaxResource };
|
|
}
|
|
|
|
if (isRepairKit)
|
|
{
|
|
item.upd.RepairKit = { Resource: props.MaxRepairResource };
|
|
}
|
|
|
|
return item;
|
|
}
|
|
|
|
/**
|
|
* Create a barter-based barter scheme, if not possible, fall back to making barter scheme currency based
|
|
* @param offerItems Items for sale in offer
|
|
* @returns Barter scheme
|
|
*/
|
|
protected createBarterBarterScheme(offerItems: Item[]): IBarterScheme[]
|
|
{
|
|
// get flea price of item being sold
|
|
const priceOfItemOffer = this.ragfairPriceService.getDynamicOfferPriceForOffer(
|
|
offerItems,
|
|
Money.ROUBLES,
|
|
false,
|
|
);
|
|
|
|
// Dont make items under a designated rouble value into barter offers
|
|
if (priceOfItemOffer < this.ragfairConfig.dynamic.barter.minRoubleCostToBecomeBarter)
|
|
{
|
|
return this.createCurrencyBarterScheme(offerItems, false);
|
|
}
|
|
|
|
// Get a randomised number of barter items to list offer for
|
|
const barterItemCount = this.randomUtil.getInt(
|
|
this.ragfairConfig.dynamic.barter.itemCountMin,
|
|
this.ragfairConfig.dynamic.barter.itemCountMax,
|
|
);
|
|
|
|
// Get desired cost of individual item offer will be listed for e.g. offer = 15k, item count = 3, desired item cost = 5k
|
|
const desiredItemCost = Math.round(priceOfItemOffer / barterItemCount);
|
|
|
|
// amount to go above/below when looking for an item (Wiggle cost of item a little)
|
|
const offerCostVariance = desiredItemCost * this.ragfairConfig.dynamic.barter.priceRangeVariancePercent / 100;
|
|
|
|
const fleaPrices = this.getFleaPricesAsArray();
|
|
|
|
// Filter possible barters to items that match the price range + not itself
|
|
const filtered = fleaPrices.filter((x) =>
|
|
x.price >= desiredItemCost - offerCostVariance && x.price <= desiredItemCost + offerCostVariance
|
|
&& x.tpl !== offerItems[0]._tpl
|
|
);
|
|
|
|
// No items on flea have a matching price, fall back to currency
|
|
if (filtered.length === 0)
|
|
{
|
|
return this.createCurrencyBarterScheme(offerItems, false);
|
|
}
|
|
|
|
// Choose random item from price-filtered flea items
|
|
const randomItem = this.randomUtil.getArrayValue(filtered);
|
|
|
|
return [{ count: barterItemCount, _tpl: randomItem.tpl }];
|
|
}
|
|
|
|
/**
|
|
* Get an array of flea prices + item tpl, cached in generator class inside `allowedFleaPriceItemsForBarter`
|
|
* @returns array with tpl/price values
|
|
*/
|
|
protected getFleaPricesAsArray(): { tpl: string; price: number; }[]
|
|
{
|
|
// Generate if needed
|
|
if (!this.allowedFleaPriceItemsForBarter)
|
|
{
|
|
const fleaPrices = this.databaseServer.getTables().templates.prices;
|
|
const fleaArray = Object.entries(fleaPrices).map(([tpl, price]) => ({ tpl: tpl, price: price }));
|
|
|
|
// Only get item prices for items that also exist in items.json
|
|
const filteredItems = fleaArray.filter((x) => this.itemHelper.getItem(x.tpl)[0]);
|
|
|
|
this.allowedFleaPriceItemsForBarter = filteredItems.filter((x) =>
|
|
!this.itemHelper.isOfBaseclasses(x.tpl, this.ragfairConfig.dynamic.barter.itemTypeBlacklist)
|
|
);
|
|
}
|
|
|
|
return this.allowedFleaPriceItemsForBarter;
|
|
}
|
|
|
|
/**
|
|
* Create a random currency-based barter scheme for an array of items
|
|
* @param offerItems Items on offer
|
|
* @param isPackOffer Is the barter scheme being created for a pack offer
|
|
* @param multipler What to multiply the resulting price by
|
|
* @returns Barter scheme for offer
|
|
*/
|
|
protected createCurrencyBarterScheme(offerItems: Item[], isPackOffer: boolean, multipler = 1): IBarterScheme[]
|
|
{
|
|
const currency = this.ragfairServerHelper.getDynamicOfferCurrency();
|
|
const price = this.ragfairPriceService.getDynamicOfferPriceForOffer(offerItems, currency, isPackOffer)
|
|
* multipler;
|
|
|
|
return [{ count: price, _tpl: currency }];
|
|
}
|
|
}
|