diff --git a/project/assets/configs/trader.json b/project/assets/configs/trader.json index 089204e3..62bbee0a 100644 --- a/project/assets/configs/trader.json +++ b/project/assets/configs/trader.json @@ -61,7 +61,7 @@ }, "partialRefreshTimeSeconds": 240, "partialRefreshChangePercent": 15, - "assortSize": 140, + "assortSize": 120, "weaponPresetMinMax": { "min": 12, "max": 19 @@ -116,12 +116,13 @@ "5485a8684bdc2da71d8b4567": 23, "5f4fbaaca5573a5ac31db429": 0, "5447e0e74bdc2d3c308b4567": 4, - "57bef4c42459772e8d35a53b": 1, + "57bef4c42459772e8d35a53b": 2, "5b3f15d486f77432d0509248": 3, "543be6564bdc2df4348b4568": 0, "5448ecbe4bdc2d60728b4568": 0, "5671435f4bdc2d96058b4569": 0, - "543be5cb4bdc2deb348b4568": 3 + "543be5cb4bdc2deb348b4568": 3, + "5448e53e4bdc2d60728b4567": 7 }, "preventDuplicateOffersOfCategory": [ "543be5cb4bdc2deb348b4568", @@ -134,9 +135,17 @@ "543be5e94bdc2df1348b4568", "5448eb774bdc2d0a728b4567", "5447e1d04bdc2dff2f8b4567", + "5448e53e4bdc2d60728b4567", "5448ecbe4bdc2d60728b4568", "543be6674bdc2df1348b4569", - "5448fe124bdc2da5018b4567" + "5448fe124bdc2da5018b4567", + "567849dd4bdc2d150f8b456e", + "5a341c4686f77469e155819e", + "5448e5284bdc2dcb718b4567", + "5447e0e74bdc2d3c308b4567", + "5b3f15d486f77432d0509248", + "5645bcb74bdc2ded0b8b4578", + "5795f317245977243854e041" ], "weaponDurabilityPercentMinMax": { "current": { diff --git a/project/src/services/FenceService.ts b/project/src/services/FenceService.ts index fe6b6d10..1e44630e 100644 --- a/project/src/services/FenceService.ts +++ b/project/src/services/FenceService.ts @@ -6,7 +6,7 @@ import { PresetHelper } from "@spt-aki/helpers/PresetHelper"; import { MinMax } from "@spt-aki/models/common/MinMax"; import { IFenceLevel } from "@spt-aki/models/eft/common/IGlobals"; import { IPmcData } from "@spt-aki/models/eft/common/IPmcData"; -import { Item, Repairable } from "@spt-aki/models/eft/common/tables/IItem"; +import { Item, Repairable, Upd } from "@spt-aki/models/eft/common/tables/IItem"; import { ITemplateItem } from "@spt-aki/models/eft/common/tables/ITemplateItem"; import { IBarterScheme, ITraderAssort } from "@spt-aki/models/eft/common/tables/ITrader"; import { BaseClasses } from "@spt-aki/models/enums/BaseClasses"; @@ -602,24 +602,28 @@ export class FenceService this.itemHelper.remapRootItemId(desiredAssortItemAndChildrenClone); const rootItemBeingAdded = desiredAssortItemAndChildrenClone[0]; + + // Set stack size based on possible overrides, e.g. ammos, otherwise set to 1 rootItemBeingAdded.upd.StackObjectsCount = this.getSingleItemStackCount(itemDbDetails); - // Skip items already in the assort if it exists in the prevent duplicate list - const existingItem = assorts.items.find((item) => item._tpl === rootItemBeingAdded._tpl); - if (this.itemShouldBeForceStacked(existingItem, itemDbDetails)) - { - i--; - existingItem.upd.StackObjectsCount++; - continue; - } - - // Only randomise single items + // Only randomise upd values for single const isSingleStack = rootItemBeingAdded.upd.StackObjectsCount === 1; if (isSingleStack) { this.randomiseItemUpdProperties(itemDbDetails, rootItemBeingAdded); } + // Skip items already in the assort if it exists in the prevent duplicate list + const existingItemThatMatches = this.getMatchingItem(rootItemBeingAdded, itemDbDetails, assorts.items); + const shouldBeStacked = this.itemShouldBeForceStacked(existingItemThatMatches, itemDbDetails); + if (shouldBeStacked && existingItemThatMatches) + { // Decrement loop counter so another items gets added + i--; + existingItemThatMatches.upd.StackObjectsCount++; + + continue; + } + // Add mods to armors so they dont show as red in the trade screen if (this.itemHelper.itemRequiresSoftInserts(rootItemBeingAdded._tpl)) { @@ -642,6 +646,63 @@ export class FenceService } } + /** + * Find an assort item that matches the first parameter, also matches based on upd properties + * e.g. salewa hp resource units left + * @param rootItemBeingAdded item to look for a match against + * @param itemDbDetails Db details of matching item + * @param fenceItemAssorts Items to search through + * @returns Matching assort item + */ + protected getMatchingItem(rootItemBeingAdded: Item, itemDbDetails: ITemplateItem, fenceItemAssorts: Item[]): Item + { + const matchingItems = fenceItemAssorts.filter((item) => item._tpl === rootItemBeingAdded._tpl); + if (matchingItems.length === 0) + { + // Nothing matches by tpl, exit early + return null; + } + + const isMedical = this.itemHelper.isOfBaseclasses(rootItemBeingAdded._tpl, [ + BaseClasses.MEDICAL, + BaseClasses.MEDKIT, + ]); + const isGearAndHasSlots = + this.itemHelper.isOfBaseclasses(rootItemBeingAdded._tpl, [ + BaseClasses.ARMORED_EQUIPMENT, + BaseClasses.SEARCHABLE_ITEM, + ]) && itemDbDetails._props.Slots.length > 0; + + // Only one match and its not medical or armored gear + if (matchingItems.length === 1 && (!(isMedical || isGearAndHasSlots))) + { + return matchingItems[0]; + } + + // Items have sub properties that need to be checked against + for (const item of matchingItems) + { + if (isMedical && rootItemBeingAdded.upd.MedKit?.HpResource === item.upd.MedKit?.HpResource) + { + // e.g. bandages with multiple use + // Both null === both max resoruce left + return item; + } + + // Armors/helmets etc + if ( + isGearAndHasSlots + && rootItemBeingAdded.upd.Repairable?.Durability === item.upd.Repairable?.Durability + && rootItemBeingAdded.upd.Repairable?.MaxDurability === item.upd.Repairable?.MaxDurability + ) + { + return item; + } + } + + return null; + } + /** * Should this item be forced into only 1 stack on fence * @param existingItem Existing item from fence assort @@ -656,39 +717,11 @@ export class FenceService return false; } - // Item type not in config list - if ( - !this.itemHelper.isOfBaseclasses( - itemDbDetails._id, - this.traderConfig.fence.preventDuplicateOffersOfCategory, - ) - ) - { - return false; - } - - // Don't stack armor with slots (plates/inserts etc) - if ( - this.itemHelper.isOfBaseclass(itemDbDetails._id, BaseClasses.ARMORED_EQUIPMENT) - && itemDbDetails._props.Slots.length > 0 - ) - { - return false; - } - - // Don't stack meds (e.g. bandages) with unequal usages remaining - // TODO - Doesnt look beyond the first matching fence item, e.g. bandage A has 1 usage left = not a match, but bandage B has 2 uses, would have matched - if ( - this.itemHelper.isOfBaseclasses(itemDbDetails._id, [BaseClasses.MEDICAL, BaseClasses.MEDKIT]) - && existingItem.upd.MedKit?.HpResource // null medkit object means its brand new/max resource - && (itemDbDetails._props.MaxHpResource !== existingItem.upd.MedKit.HpResource) - ) - { - return false; - } - - // Passed all checks, will be forced stacked - return true; + // Item type in config list + return this.itemHelper.isOfBaseclasses( + itemDbDetails._id, + this.traderConfig.fence.preventDuplicateOffersOfCategory, + ); } /**