From d36593ca577fdf1d61203c312448263ff2fd0265 Mon Sep 17 00:00:00 2001 From: Dev Date: Mon, 5 Feb 2024 19:52:46 +0000 Subject: [PATCH] Rework fence service to handle armor/weapons separately, made config work with a weapon/equipment min/max value Improve price filtering system to work with weapons/equipment --- project/assets/configs/trader.json | 37 ++++-- project/src/helpers/HandbookHelper.ts | 12 ++ .../src/models/spt/config/ITraderConfig.ts | 3 +- project/src/services/FenceService.ts | 120 +++++++++++++++--- 4 files changed, 145 insertions(+), 27 deletions(-) diff --git a/project/assets/configs/trader.json b/project/assets/configs/trader.json index e85b7c0b..7203f93f 100644 --- a/project/assets/configs/trader.json +++ b/project/assets/configs/trader.json @@ -49,12 +49,19 @@ "discountOptions": { "assortSize": 50, "itemPriceMult": 0.8, - "presetPriceMult": 1.3 + "presetPriceMult": 1.2 }, "partialRefreshTimeSeconds": 240, "partialRefreshChangePercent": 15, - "assortSize": 140, - "maxPresetsPercent": 10, + "assortSize": 150, + "weaponPresetMinMax": { + "min": 8, + "max": 15 + }, + "equipmentPresetMinMax": { + "min": 8, + "max": 15 + }, "itemPriceMult": 1.2, "presetPriceMult": 2.0, "regenerateAssortsOnRefresh": false, @@ -66,7 +73,7 @@ "555ef6e44bdc2de9068b457e": 5, "5a341c4086f77401f2541505": 5, "55818ad54bdc2ddc698b4569": 5, - "5448e53e4bdc2d60728b4567": 5, + "5448e53e4bdc2d60728b4567": 6, "55818ac54bdc2d5b648b456e": 5, "55818af64bdc2d5b648b4570": 5, "55818a304bdc2db5418b457d": 4, @@ -97,7 +104,7 @@ "min": 35, "max": 60 }, - "chancePlateExistsInArmorPercent": 40, + "chancePlateExistsInArmorPercent": 50, "armorMaxDurabilityPercentMinMax": { "min": 35, "max": 60 @@ -119,17 +126,29 @@ "57864a66245977548f04a81f": 71000, "5448e54d4bdc2dcc718b4568": 200000, "5a2c3a9486f774688b05e574": 70000, - "5a341c4086f77401f2541505": 125000, "5448f3a64bdc2d60728b456a": 70000, "5447b6194bdc2d67278b4567": 500000, "550aa4cd4bdc2dd8348b456c": 70000, "57864ee62459775490116fc1": 95000, "5448bc234bdc2d3c308b4569": 29000, + + "5447b6094bdc2dc3278b4567": 35009, + "5447bedf4bdc2d87278b4568": 27008, + "5447bed64bdc2d97278b4568": 27007, + "5447b5e04bdc2d62278b4567": 33006, + "5447b5fc4bdc2d87278b4567": 42005, + "5447b5f14bdc2d61278b4567": 44004, + "5447b5cf4bdc2d65278b4567": 30003, + "5447b6254bdc2dc3278b4568": 28002, + "5447e1d04bdc2dff2f8b4567": 19001, + "55818ae44bdc2dde698b456c": 45000, "55818add4bdc2d5b648b456f": 35000, "590c745b86f7743cc433c5f2": 64000, - "57864bb7245977548b3b66c2": 90000, - "5c99f98d86f7745c314214b3": 15000 + "57864bb7245977548b3b66c2": 85000, + + "5448e5284bdc2dcb718b4567": 40001, + "5a341c4086f77401f2541505": 105000 }, "presetSlotsToRemoveChancePercent": { "mod_scope": 70, @@ -264,4 +283,4 @@ }, "btrDeliveryExpireHours": 240 } -} \ No newline at end of file +} diff --git a/project/src/helpers/HandbookHelper.ts b/project/src/helpers/HandbookHelper.ts index 68c85456..3c46baae 100644 --- a/project/src/helpers/HandbookHelper.ts +++ b/project/src/helpers/HandbookHelper.ts @@ -1,6 +1,7 @@ import { inject, injectable } from "tsyringe"; import { Category } from "@spt-aki/models/eft/common/tables/IHandbookBase"; +import { Item } from "@spt-aki/models/eft/common/tables/IItem"; import { Money } from "@spt-aki/models/enums/Money"; import { DatabaseServer } from "@spt-aki/servers/DatabaseServer"; import { JsonUtil } from "@spt-aki/utils/JsonUtil"; @@ -102,6 +103,17 @@ export class HandbookHelper return handbookItem.Price; } + public getTemplatePriceForItems(items: Item[]): number + { + let total = 0; + for (const item of items) + { + total += this.getTemplatePrice(item._tpl); + } + + return total; + } + /** * Get all items in template with the given parent category * @param parentId diff --git a/project/src/models/spt/config/ITraderConfig.ts b/project/src/models/spt/config/ITraderConfig.ts index b9a1dd37..36834832 100644 --- a/project/src/models/spt/config/ITraderConfig.ts +++ b/project/src/models/spt/config/ITraderConfig.ts @@ -26,7 +26,8 @@ export interface FenceConfig partialRefreshTimeSeconds: number; partialRefreshChangePercent: number; assortSize: number; - maxPresetsPercent: number; + weaponPresetMinMax: MinMax; + equipmentPresetMinMax: MinMax; itemPriceMult: number; presetPriceMult: number; armorMaxDurabilityPercentMinMax: MinMax; diff --git a/project/src/services/FenceService.ts b/project/src/services/FenceService.ts index b3963a86..65362df9 100644 --- a/project/src/services/FenceService.ts +++ b/project/src/services/FenceService.ts @@ -422,9 +422,15 @@ export class FenceService this.addItemAssorts(assortCount, assorts, baseFenceAssort, itemTypeCounts, loyaltyLevel); // Add presets - const maxPresetCount = Math.round(assortCount * (this.traderConfig.fence.maxPresetsPercent / 100)); - const randomisedPresetCount = this.randomUtil.getInt(0, maxPresetCount); - this.addPresetsToAssort(randomisedPresetCount, assorts, baseFenceAssort, loyaltyLevel); + const weaponPresetCount = this.randomUtil.getInt( + this.traderConfig.fence.weaponPresetMinMax.min, + this.traderConfig.fence.weaponPresetMinMax.max, + ); + const equipmentPresetCount = this.randomUtil.getInt( + this.traderConfig.fence.equipmentPresetMinMax.min, + this.traderConfig.fence.equipmentPresetMinMax.max, + ); + this.addPresetsToAssort(weaponPresetCount, equipmentPresetCount, assorts, baseFenceAssort, loyaltyLevel); } protected addItemAssorts( @@ -509,39 +515,37 @@ export class FenceService /** * Find presets in base fence assort and add desired number to 'assorts' parameter - * @param desiredPresetCount + * @param desiredWeaponPresetsCount * @param assorts * @param baseFenceAssort * @param loyaltyLevel Which loyalty level is required to see/buy item */ protected addPresetsToAssort( - desiredPresetCount: number, + desiredWeaponPresetsCount: number, + desiredEquipmentPresetsCount: number, assorts: ITraderAssort, baseFenceAssort: ITraderAssort, loyaltyLevel: number, ): void { - let presetsAddedCount = 0; - if (desiredPresetCount <= 0) + let weaponPresetsAddedCount = 0; + if (desiredWeaponPresetsCount <= 0) { return; } - const presetRootItems = baseFenceAssort.items.filter((item) => item.upd?.sptPresetId); - while (presetsAddedCount < desiredPresetCount) + const weaponPresetRootItems = baseFenceAssort.items.filter((item) => + item.upd?.sptPresetId && this.itemHelper.isOfBaseclass(item._tpl, BaseClasses.WEAPON) + ); + while (weaponPresetsAddedCount < desiredWeaponPresetsCount) { - const randomPresetRoot = this.randomUtil.getArrayValue(presetRootItems); + const randomPresetRoot = this.randomUtil.getArrayValue(weaponPresetRootItems); const rootItemDb = this.itemHelper.getItem(randomPresetRoot._tpl)[1]; + const presetWithChildrenClone = this.jsonUtil.clone( this.itemHelper.findAndReturnChildrenAsItems(baseFenceAssort.items, randomPresetRoot._id), ); - // Need to add mods to armors so they dont show as red in the trade screen - if (this.itemHelper.itemRequiresSoftInserts(randomPresetRoot._tpl)) - { - this.randomiseArmorModDurability(presetWithChildrenClone, rootItemDb); - } - if (this.itemHelper.isOfBaseclass(rootItemDb._id, BaseClasses.WEAPON)) { this.randomiseItemUpdProperties(rootItemDb, presetWithChildrenClone[0]); @@ -549,6 +553,26 @@ export class FenceService this.removeRandomModsOfItem(presetWithChildrenClone); + // Check chosen item is below price cap + const priceLimitRouble = this.traderConfig.fence.itemCategoryRoublePriceLimit[rootItemDb._parent]; + if (priceLimitRouble) + { + if (this.handbookHelper.getTemplatePriceForItems(presetWithChildrenClone) > priceLimitRouble) + { + // Too expensive, try again + this.logger.warning( + `Blocked ${rootItemDb._name}, price: ${ + this.handbookHelper.getTemplatePriceForItems(presetWithChildrenClone) + } limit: ${priceLimitRouble}`, + ); + continue; + } + } + else + { + this.logger.warning(`No limit ${rootItemDb._name} ${rootItemDb._parent}`); + } + // MUST randomise Ids as its possible to add the same base fence assort twice = duplicate IDs = dead client this.itemHelper.reparentItemAndChildren(presetWithChildrenClone[0], presetWithChildrenClone); this.itemHelper.remapRootItemId(presetWithChildrenClone); @@ -562,7 +586,69 @@ export class FenceService assorts.barter_scheme[presetWithChildrenClone[0]._id] = baseFenceAssort.barter_scheme[randomPresetRoot._id]; assorts.loyal_level_items[presetWithChildrenClone[0]._id] = loyaltyLevel; - presetsAddedCount++; + weaponPresetsAddedCount++; + } + + let equipmentPresetsAddedCount = 0; + if (desiredEquipmentPresetsCount <= 0) + { + return; + } + + const equipmentPresetRootItems = baseFenceAssort.items.filter((item) => + item.upd?.sptPresetId && this.itemHelper.armorItemCanHoldMods(item._tpl) + ); + while (equipmentPresetsAddedCount < desiredEquipmentPresetsCount) + { + const randomPresetRoot = this.randomUtil.getArrayValue(equipmentPresetRootItems); + const rootItemDb = this.itemHelper.getItem(randomPresetRoot._tpl)[1]; + + const presetWithChildrenClone = this.jsonUtil.clone( + this.itemHelper.findAndReturnChildrenAsItems(baseFenceAssort.items, randomPresetRoot._id), + ); + + // Need to add mods to armors so they dont show as red in the trade screen + if (this.itemHelper.itemRequiresSoftInserts(randomPresetRoot._tpl)) + { + this.randomiseArmorModDurability(presetWithChildrenClone, rootItemDb); + } + + this.removeRandomModsOfItem(presetWithChildrenClone); + + // Check chosen item is below price cap + const priceLimitRouble = this.traderConfig.fence.itemCategoryRoublePriceLimit[rootItemDb._parent]; + if (priceLimitRouble) + { + if (this.handbookHelper.getTemplatePriceForItems(presetWithChildrenClone) > priceLimitRouble) + { + // Too expensive, try again + this.logger.warning( + `Blocked ${rootItemDb._name}, price: ${ + this.handbookHelper.getTemplatePriceForItems(presetWithChildrenClone) + } limit: ${priceLimitRouble}`, + ); + continue; + } + } + else + { + this.logger.warning(`No limit ${rootItemDb._name} ${rootItemDb._parent}`); + } + + // MUST randomise Ids as its possible to add the same base fence assort twice = duplicate IDs = dead client + this.itemHelper.reparentItemAndChildren(presetWithChildrenClone[0], presetWithChildrenClone); + this.itemHelper.remapRootItemId(presetWithChildrenClone); + + // Remapping IDs causes parentid to be altered + presetWithChildrenClone[0].parentId = "hideout"; + + assorts.items.push(...presetWithChildrenClone); + + // Must be careful to use correct id as the item has had its IDs regenerated + assorts.barter_scheme[presetWithChildrenClone[0]._id] = baseFenceAssort.barter_scheme[randomPresetRoot._id]; + assorts.loyal_level_items[presetWithChildrenClone[0]._id] = loyaltyLevel; + + equipmentPresetsAddedCount++; } }