diff --git a/project/src/generators/RagfairAssortGenerator.ts b/project/src/generators/RagfairAssortGenerator.ts index a874a26c..301e2866 100644 --- a/project/src/generators/RagfairAssortGenerator.ts +++ b/project/src/generators/RagfairAssortGenerator.ts @@ -16,7 +16,7 @@ import { JsonUtil } from "@spt-aki/utils/JsonUtil"; @injectable() export class RagfairAssortGenerator { - protected generatedAssortItems: Item[] = []; + protected generatedAssortItems: Item[][] = []; protected ragfairConfig: IRagfairConfig; protected ragfairItemInvalidBaseTypes: string[] = [ @@ -43,10 +43,11 @@ export class RagfairAssortGenerator } /** - * Get an array of unique items that can be sold on the flea - * @returns array of unique items + * Get an array of arrays that can be sold on the flea + * Each sub array contains item + children (if any) + * @returns array of arrays */ - public getAssortItems(): Item[] + public getAssortItems(): Item[][] { if (!this.assortsAreGenerated()) { @@ -66,16 +67,17 @@ export class RagfairAssortGenerator } /** - * Generate an array of items the flea can sell - * @returns array of unique items + * Generate an array of arrays (item + children) the flea can sell + * @returns array of arrays (item + children) */ - protected generateRagfairAssortItems(): Item[] + protected generateRagfairAssortItems(): Item[][] { - const results: Item[] = []; + const results: Item[][] = []; /** Get cloned items from db */ const dbItemsClone = this.itemHelper.getItems().filter(item => item._type !== "Node"); + /** Store processed preset tpls so we dont add them when procesing non-preset items */ const processedArmorItems: string[] = []; const seasonalEventActive = this.seasonalEventService.seasonalEventEnabled(); const seasonalItemTplBlacklist = this.seasonalEventService.getInactiveSeasonalEventItems(); @@ -83,13 +85,21 @@ export class RagfairAssortGenerator const presets = this.getPresetsToAdd(); for (const preset of presets) { - const presetItemTpl = preset._items[0]._tpl; + // Update Ids and clone + const presetAndMods: Item[] = this.itemHelper.replaceIDs( + null, + this.jsonUtil.clone(preset._items), + ); + this.itemHelper.remapRootItemId(presetAndMods); // Add presets base item tpl to the processed list so its skipped later on when processing items - processedArmorItems.push(presetItemTpl) + processedArmorItems.push(preset._items[0]._tpl); - // Preset id must be passed through to ensure flea shows preset - results.push(this.createRagfairAssortItem(presetItemTpl, preset._id)); + presetAndMods[0].parentId = "hideout"; + presetAndMods[0].slotId = "hideout"; + presetAndMods[0].upd = { StackObjectsCount: 99999999, UnlimitedCount: true, sptPresetId: preset._id}; + + results.push(presetAndMods); } for (const item of dbItemsClone) @@ -114,7 +124,9 @@ export class RagfairAssortGenerator continue; } - results.push(this.createRagfairAssortItem(item._id, item._id)); // tplid and id must be the same so hideout recipe rewards work + const ragfairAssort = this.createRagfairAssortRootItem(item._id, item._id); // tplid and id must be the same so hideout recipe rewards work + + results.push([ragfairAssort]); } return results; @@ -136,9 +148,9 @@ export class RagfairAssortGenerator * Create a base assort item and return it with populated values + 999999 stack count + unlimited count = true * @param tplId tplid to add to item * @param id id to add to item - * @returns hydrated Item object + * @returns Hydrated Item object */ - protected createRagfairAssortItem(tplId: string, id = this.hashUtil.generate()): Item + protected createRagfairAssortRootItem(tplId: string, id = this.hashUtil.generate()): Item { return { _id: id, diff --git a/project/src/generators/RagfairOfferGenerator.ts b/project/src/generators/RagfairOfferGenerator.ts index 813b13a4..f4ebc1c7 100644 --- a/project/src/generators/RagfairOfferGenerator.ts +++ b/project/src/generators/RagfairOfferGenerator.ts @@ -312,21 +312,22 @@ export class RagfairOfferGenerator * 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 + public async generateDynamicOffers(expiredOffers: Item[][] = null): Promise { + const replacingExpiredOffers = expiredOffers?.length > 0; const config = this.ragfairConfig.dynamic; // get assort items from param if they exist, otherwise grab freshly generated assorts - const assortItemsToProcess: Item[] = expiredOffers + const assortItemsToProcess: Item[][] = replacingExpiredOffers ? 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) + for (const assortItemWithChildren of assortItemsToProcess) { assorOffersForItemsProcesses.push( - this.createOffersForItems(assortItemIndex, assortItemsToProcess, expiredOffers, config), + this.createOffersFromAssort(assortItemWithChildren, replacingExpiredOffers, config), ); } @@ -334,47 +335,34 @@ export class RagfairOfferGenerator } /** - * @param assortItemIndex Index of assort item - * @param assortItemsToProcess Item array containing index - * @param expiredOffers Currently expired offers on flea + * @param assortItemWithChildren Item with its children to process into offers + * @param isExpiredOffer is an expired offer * @param config Ragfair dynamic config */ - protected async createOffersForItems( - assortItemIndex: string, - assortItemsToProcess: Item[], - expiredOffers: Item[], + protected async createOffersFromAssort( + assortItemWithChildren: Item[], + isExpiredOffer: boolean, config: Dynamic, ): Promise { - const assortItem = assortItemsToProcess[assortItemIndex]; - const itemDetails = this.itemHelper.getItem(assortItem._tpl); - - const isPreset = this.presetHelper.isPreset(assortItem._id); + const itemDetails = this.itemHelper.getItem(assortItemWithChildren[0]._tpl); + const isPreset = this.presetHelper.isPreset(assortItemWithChildren[0].upd.sptPresetId); // Only perform checks on newly generated items, skip expired items being refreshed - if (!(expiredOffers || this.ragfairServerHelper.isItemValidRagfairItem(itemDetails))) + if (!(isExpiredOffer || this.ragfairServerHelper.isItemValidRagfairItem(itemDetails))) { return; } - // Get item + sub-items (weapons + armors), otherwise just get item - const itemWithChildren: Item[] = isPreset - ? this.ragfairServerHelper.getPresetItems(assortItem) - : [ - ...[assortItem], - ...this.itemHelper.findAndReturnChildrenByAssort(assortItem._id, this.ragfairAssortGenerator.getAssortItems(), - ), - ]; - // Armor presets can hold plates above the allowed flea level, remove if necessary if (isPreset && this.ragfairConfig.dynamic.blacklist.enableBsgList) { - this.removeBannedPlatesFromPreset(itemWithChildren, this.ragfairConfig.dynamic.blacklist.armorPlateMaxProtectionLevel); + this.removeBannedPlatesFromPreset(assortItemWithChildren, this.ragfairConfig.dynamic.blacklist.armorPlateMaxProtectionLevel); } // Get number of offers to create - // Limit to 1 offer when processing expired - const offerCount = expiredOffers + // Limit to 1 offer when processing expired - like-for-like replacement + const offerCount = isExpiredOffer ? 1 : Math.round(this.randomUtil.getInt(config.offerItemCount.min, config.offerItemCount.max)); @@ -385,13 +373,13 @@ export class RagfairOfferGenerator if (!isPreset) { // Presets get unique id generated during getPresetItems() earlier + would require regenerating all children to match - itemWithChildren[0]._id = this.hashUtil.generate(); + assortItemWithChildren[0]._id = this.hashUtil.generate(); } - delete itemWithChildren[0].parentId; - delete itemWithChildren[0].slotId; + delete assortItemWithChildren[0].parentId; + delete assortItemWithChildren[0].slotId; - assortSingleOfferProcesses.push(this.createSingleOfferForItem(itemWithChildren, isPreset, itemDetails)); + assortSingleOfferProcesses.push(this.createSingleOfferForItem(assortItemWithChildren, isPreset, itemDetails)); } await Promise.all(assortSingleOfferProcesses); diff --git a/project/src/helpers/TraderAssortHelper.ts b/project/src/helpers/TraderAssortHelper.ts index 9045eda8..146d8b44 100644 --- a/project/src/helpers/TraderAssortHelper.ts +++ b/project/src/helpers/TraderAssortHelper.ts @@ -261,7 +261,7 @@ export class TraderAssortHelper protected getRagfairDataAsTraderAssort(): ITraderAssort { return { - items: this.ragfairAssortGenerator.getAssortItems(), + items: this.ragfairAssortGenerator.getAssortItems().flat(), barter_scheme: {}, loyal_level_items: {}, nextResupply: null, diff --git a/project/src/servers/RagfairServer.ts b/project/src/servers/RagfairServer.ts index 0afaaba1..3e2c4683 100644 --- a/project/src/servers/RagfairServer.ts +++ b/project/src/servers/RagfairServer.ts @@ -47,27 +47,27 @@ export class RagfairServer // Generate trader offers const traders = this.getUpdateableTraders(); - for (const traderID of traders) + for (const traderId of traders) { // Skip generating fence offers - if (traderID === Traders.FENCE) + if (traderId === Traders.FENCE) { continue; } - if (this.ragfairOfferService.traderOffersNeedRefreshing(traderID)) + if (this.ragfairOfferService.traderOffersNeedRefreshing(traderId)) { - this.ragfairOfferGenerator.generateFleaOffersForTrader(traderID); + this.ragfairOfferGenerator.generateFleaOffersForTrader(traderId); } } - // Regen expired offers when over threshold count + // Regenerate expired offers when over threshold limit if (this.ragfairOfferService.getExpiredOfferCount() >= this.ragfairConfig.dynamic.expiredOfferThreshold) { - const expiredOfferItems = this.ragfairOfferService.getExpiredOfferItems(); - await this.ragfairOfferGenerator.generateDynamicOffers(expiredOfferItems); + const expiredAssortsWithChildren = this.ragfairOfferService.getExpiredOfferAssorts(); + await this.ragfairOfferGenerator.generateDynamicOffers(expiredAssortsWithChildren); - // reset expired offers now we've genned them + // Clear out expired offers now we've generated them this.ragfairOfferService.resetExpiredOffers(); } diff --git a/project/src/services/RagfairOfferService.ts b/project/src/services/RagfairOfferService.ts index 1209a866..a63475a2 100644 --- a/project/src/services/RagfairOfferService.ts +++ b/project/src/services/RagfairOfferService.ts @@ -21,6 +21,7 @@ import { TimeUtil } from "@spt-aki/utils/TimeUtil"; export class RagfairOfferService { protected playerOffersLoaded = false; + /** Offer id + offer object */ protected expiredOffers: Record = {}; protected ragfairConfig: IRagfairConfig; @@ -71,27 +72,35 @@ export class RagfairOfferService this.expiredOffers[staleOffer._id] = staleOffer; } + /** + * Get total count of current expired offers + * @returns Number of expired offers + */ public getExpiredOfferCount(): number { return Object.keys(this.expiredOffers).length; } /** - * Get an array of expired items not yet processed into new offers - * @returns items that need to be turned into offers + * Get an array of arrays of expired offer items + children + * @returns Expired offer assorts */ - public getExpiredOfferItems(): Item[] + public getExpiredOfferAssorts(): Item[][] { - const expiredItems: Item[] = []; + const expiredItems: Item[][] = []; for (const expiredOfferId in this.expiredOffers) { - expiredItems.push(this.expiredOffers[expiredOfferId].items[0]); + const expiredOffer = this.expiredOffers[expiredOfferId]; + expiredItems.push(expiredOffer.items); } return expiredItems; } + /** + * Clear out internal expiredOffers dictionary of all items + */ public resetExpiredOffers(): void { this.expiredOffers = {};