Improved handling of fence assort item refresh. Duplicate stacks should be much less common
Stacked items can now be partially reduced instead of all or nothing
This commit is contained in:
parent
b0576a3115
commit
64ddca4124
@ -157,7 +157,7 @@
|
||||
"543be6564bdc2df4348b4568": 0,
|
||||
"5448ecbe4bdc2d60728b4568": 0,
|
||||
"5671435f4bdc2d96058b4569": 0,
|
||||
"543be5cb4bdc2deb348b4568": 3,
|
||||
"543be5cb4bdc2deb348b4568": 5,
|
||||
"5448e53e4bdc2d60728b4567": 7
|
||||
},
|
||||
"preventDuplicateOffersOfCategory": [
|
||||
|
@ -0,0 +1,9 @@
|
||||
import { Item } from "@spt-aki/models/eft/common/tables/IItem";
|
||||
import { IBarterScheme } from "@spt-aki/models/eft/common/tables/ITrader";
|
||||
|
||||
export interface ICreateFenceAssortsResult
|
||||
{
|
||||
sptItems: Item[][];
|
||||
barter_scheme: Record<string, IBarterScheme[][]>;
|
||||
loyal_level_items: Record<string, number>;
|
||||
}
|
@ -3,16 +3,16 @@ import { inject, injectable } from "tsyringe";
|
||||
import { HandbookHelper } from "@spt-aki/helpers/HandbookHelper";
|
||||
import { ItemHelper } from "@spt-aki/helpers/ItemHelper";
|
||||
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, Upd } from "@spt-aki/models/eft/common/tables/IItem";
|
||||
import { Item, Repairable } 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";
|
||||
import { ConfigTypes } from "@spt-aki/models/enums/ConfigTypes";
|
||||
import { Traders } from "@spt-aki/models/enums/Traders";
|
||||
import { IItemDurabilityCurrentMax, ITraderConfig } from "@spt-aki/models/spt/config/ITraderConfig";
|
||||
import { ICreateFenceAssortsResult } from "@spt-aki/models/spt/fence/ICreateFenceAssortsResult";
|
||||
import {
|
||||
IFenceAssortGenerationValues,
|
||||
IGenerationAssortValues,
|
||||
@ -234,13 +234,15 @@ export class FenceService
|
||||
// Get count of what item pools need new items (item/weapon/equipment)
|
||||
const itemCountsToReplace = this.getCountOfItemsToGenerate();
|
||||
|
||||
const newItems = this.createFenceAssortSkeleton();
|
||||
this.createAssorts(itemCountsToReplace.normal, newItems, 1);
|
||||
this.fenceAssort.items.push(...newItems.items);
|
||||
const newItems = this.createAssorts(itemCountsToReplace.normal, 1);
|
||||
|
||||
const newDiscountItems = this.createFenceAssortSkeleton();
|
||||
this.createAssorts(itemCountsToReplace.discount, newDiscountItems, 2);
|
||||
this.fenceDiscountAssort.items.push(...newDiscountItems.items);
|
||||
// Push newly generated assorts into existing data
|
||||
this.updateFenceAssorts(newItems, this.fenceAssort);
|
||||
|
||||
const newDiscountItems = this.createAssorts(itemCountsToReplace.discount, 2);
|
||||
|
||||
// Push newly generated discount assorts into existing data
|
||||
this.updateFenceAssorts(newDiscountItems, this.fenceDiscountAssort);
|
||||
|
||||
// Add new barter items to fence barter scheme
|
||||
for (const barterItemKey in newItems.barter_scheme)
|
||||
@ -271,6 +273,46 @@ export class FenceService
|
||||
this.incrementPartialRefreshTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the process of folding new assorts into existing assorts, when a new assort exists already, increment its StackObjectsCount instead
|
||||
* @param newFenceAssorts Assorts to fold into existing fence assorts
|
||||
* @param existingFenceAssorts Current fence assorts new assorts will be added to
|
||||
*/
|
||||
protected updateFenceAssorts(newFenceAssorts: ICreateFenceAssortsResult, existingFenceAssorts: ITraderAssort): void
|
||||
{
|
||||
for (const itemWithChildren of newFenceAssorts.sptItems)
|
||||
{
|
||||
// Find the root item
|
||||
const newRootItem = itemWithChildren.find((item) => item.slotId === "hideout");
|
||||
|
||||
// Find a matching root item with same tpl in existing assort
|
||||
const existingRootItem = existingFenceAssorts.items.find((item) =>
|
||||
item._tpl === newRootItem._tpl && item.slotId === "hideout"
|
||||
);
|
||||
|
||||
// Check if same type of item exists + its on list of item types to always stack
|
||||
if (existingRootItem && this.itemInPreventDupeCategoryList(newRootItem._tpl))
|
||||
{
|
||||
// Guard against a missing stack count
|
||||
if (!existingRootItem.upd.StackObjectsCount)
|
||||
{
|
||||
existingRootItem.upd.StackObjectsCount = 1;
|
||||
}
|
||||
|
||||
// Merge new items count into existing, dont add new loyalty/barter data as it already exists
|
||||
existingRootItem.upd.StackObjectsCount += newRootItem.upd.StackObjectsCount;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// New assort to be added to existing assorts
|
||||
existingFenceAssorts.items.push(...itemWithChildren);
|
||||
existingFenceAssorts.barter_scheme[newRootItem._id] = newFenceAssorts.barter_scheme[newRootItem._id];
|
||||
existingFenceAssorts.loyal_level_items[newRootItem._id] =
|
||||
newFenceAssorts.loyal_level_items[newRootItem._id];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment fence next refresh timestamp by current timestamp + partialRefreshTimeSeconds from config
|
||||
*/
|
||||
@ -386,18 +428,26 @@ export class FenceService
|
||||
*/
|
||||
protected removeRandomItemFromAssorts(assort: ITraderAssort, rootItems: Item[]): void
|
||||
{
|
||||
const rootItemToRemove = this.randomUtil.getArrayValue(rootItems);
|
||||
|
||||
// Clean up any mods if item had them
|
||||
const itemWithChildren = this.itemHelper.findAndReturnChildrenAsItems(assort.items, rootItemToRemove._id);
|
||||
for (const itemToDelete of itemWithChildren)
|
||||
{
|
||||
// Delete item from assort items array
|
||||
assort.items.splice(assort.items.indexOf(itemToDelete), 1);
|
||||
const rootItemToAdjust = this.randomUtil.getArrayValue(rootItems);
|
||||
const itemCountToRemove = this.randomUtil.getInt(1, rootItemToAdjust.upd.StackObjectsCount);
|
||||
if (itemCountToRemove > 1 && itemCountToRemove < rootItemToAdjust.upd.StackObjectsCount)
|
||||
{ // More than 1 + less then full stack
|
||||
// Reduce stack size but keep stack
|
||||
rootItemToAdjust.upd.StackObjectsCount -= itemCountToRemove;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Remove up item + any mods
|
||||
const itemWithChildren = this.itemHelper.findAndReturnChildrenAsItems(assort.items, rootItemToAdjust._id);
|
||||
for (const itemToDelete of itemWithChildren)
|
||||
{
|
||||
// Delete item from assort items array
|
||||
assort.items.splice(assort.items.indexOf(itemToDelete), 1);
|
||||
}
|
||||
|
||||
delete assort.barter_scheme[rootItemToRemove._id];
|
||||
delete assort.loyal_level_items[rootItemToRemove._id];
|
||||
delete assort.barter_scheme[rootItemToAdjust._id];
|
||||
delete assort.loyal_level_items[rootItemToAdjust._id];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -437,16 +487,35 @@ export class FenceService
|
||||
this.createInitialFenceAssortGenerationValues();
|
||||
|
||||
// Create basic fence assort
|
||||
const assorts = this.createFenceAssortSkeleton();
|
||||
this.createAssorts(this.desiredAssortCounts.normal, assorts, 1);
|
||||
const assorts = this.createAssorts(this.desiredAssortCounts.normal, 1);
|
||||
|
||||
// Store in this.fenceAssort
|
||||
this.setFenceAssort(assorts);
|
||||
this.setFenceAssort(this.convertIntoFenceAssort(assorts));
|
||||
|
||||
// Create level 2 assorts accessible at rep level 6
|
||||
const discountAssorts = this.createFenceAssortSkeleton();
|
||||
this.createAssorts(this.desiredAssortCounts.discount, discountAssorts, 2);
|
||||
const discountAssorts = this.createAssorts(this.desiredAssortCounts.discount, 2);
|
||||
|
||||
// Store in this.fenceDiscountAssort
|
||||
this.setFenceDiscountAssort(discountAssorts);
|
||||
this.setFenceDiscountAssort(this.convertIntoFenceAssort(discountAssorts));
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the intermediary assort data generated into format client can process
|
||||
* @param intermediaryAssorts Generated assorts that will be converted
|
||||
* @returns ITraderAssort
|
||||
*/
|
||||
protected convertIntoFenceAssort(intermediaryAssorts: ICreateFenceAssortsResult): ITraderAssort
|
||||
{
|
||||
const result = this.createFenceAssortSkeleton();
|
||||
for (const itemWithChilden of intermediaryAssorts.sptItems)
|
||||
{
|
||||
result.items.push(...itemWithChilden);
|
||||
}
|
||||
|
||||
result.barter_scheme = intermediaryAssorts.barter_scheme;
|
||||
result.loyal_level_items = intermediaryAssorts.loyal_level_items;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -506,14 +575,22 @@ export class FenceService
|
||||
* @param assortCount Number of assorts to generate
|
||||
* @param assorts object to add created assorts to
|
||||
*/
|
||||
protected createAssorts(itemCounts: IGenerationAssortValues, assorts: ITraderAssort, loyaltyLevel: number): void
|
||||
protected createAssorts(itemCounts: IGenerationAssortValues, loyaltyLevel: number): ICreateFenceAssortsResult
|
||||
{
|
||||
const result: ICreateFenceAssortsResult = { sptItems: [], barter_scheme: {}, loyal_level_items: {} };
|
||||
|
||||
const baseFenceAssortClone = this.jsonUtil.clone(this.databaseServer.getTables().traders[Traders.FENCE].assort);
|
||||
const itemTypeLimitCounts = this.initItemLimitCounter(this.traderConfig.fence.itemTypeLimits);
|
||||
|
||||
if (itemCounts.item > 0)
|
||||
{
|
||||
this.addItemAssorts(itemCounts.item, assorts, baseFenceAssortClone, itemTypeLimitCounts, loyaltyLevel);
|
||||
const itemResult = this.addItemAssorts(
|
||||
itemCounts.item,
|
||||
result,
|
||||
baseFenceAssortClone,
|
||||
itemTypeLimitCounts,
|
||||
loyaltyLevel,
|
||||
);
|
||||
}
|
||||
|
||||
if (itemCounts.weaponPreset > 0 || itemCounts.equipmentPreset > 0)
|
||||
@ -522,11 +599,13 @@ export class FenceService
|
||||
this.addPresetsToAssort(
|
||||
itemCounts.weaponPreset,
|
||||
itemCounts.equipmentPreset,
|
||||
assorts,
|
||||
result,
|
||||
baseFenceAssortClone,
|
||||
loyaltyLevel,
|
||||
);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -539,15 +618,15 @@ export class FenceService
|
||||
*/
|
||||
protected addItemAssorts(
|
||||
assortCount: number,
|
||||
assorts: ITraderAssort,
|
||||
assorts: ICreateFenceAssortsResult,
|
||||
baseFenceAssortClone: ITraderAssort,
|
||||
itemTypeLimits: Record<string, { current: number; max: number; }>,
|
||||
loyaltyLevel: number,
|
||||
): void
|
||||
{
|
||||
const priceLimits = this.traderConfig.fence.itemCategoryRoublePriceLimit;
|
||||
const assortRootItems = baseFenceAssortClone.items.filter((x) =>
|
||||
x.parentId === "hideout" && !x.upd?.sptPresetId
|
||||
const assortRootItems = baseFenceAssortClone.items.filter((item) =>
|
||||
item.parentId === "hideout" && !item.upd?.sptPresetId
|
||||
);
|
||||
|
||||
for (let i = 0; i < assortCount; i++)
|
||||
@ -614,7 +693,7 @@ export class FenceService
|
||||
}
|
||||
|
||||
// Skip items already in the assort if it exists in the prevent duplicate list
|
||||
const existingItemThatMatches = this.getMatchingItem(rootItemBeingAdded, itemDbDetails, assorts.items);
|
||||
const existingItemThatMatches = this.getMatchingItem(rootItemBeingAdded, itemDbDetails, assorts.sptItems);
|
||||
const shouldBeStacked = this.itemShouldBeForceStacked(existingItemThatMatches, itemDbDetails);
|
||||
if (shouldBeStacked && existingItemThatMatches)
|
||||
{ // Decrement loop counter so another items gets added
|
||||
@ -630,7 +709,7 @@ export class FenceService
|
||||
this.randomiseArmorModDurability(desiredAssortItemAndChildrenClone, itemDbDetails);
|
||||
}
|
||||
|
||||
assorts.items.push(...desiredAssortItemAndChildrenClone);
|
||||
assorts.sptItems.push(desiredAssortItemAndChildrenClone);
|
||||
|
||||
assorts.barter_scheme[rootItemBeingAdded._id] = this.jsonUtil.clone(
|
||||
baseFenceAssortClone.barter_scheme[chosenBaseAssortRoot._id],
|
||||
@ -651,15 +730,15 @@ export class FenceService
|
||||
* 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
|
||||
* @param itemsWithChildren Items to search through
|
||||
* @returns Matching assort item
|
||||
*/
|
||||
protected getMatchingItem(rootItemBeingAdded: Item, itemDbDetails: ITemplateItem, fenceItemAssorts: Item[]): Item
|
||||
protected getMatchingItem(rootItemBeingAdded: Item, itemDbDetails: ITemplateItem, itemsWithChildren: Item[][]): Item
|
||||
{
|
||||
// Get matching root items
|
||||
const matchingItems = fenceItemAssorts.filter((item) =>
|
||||
item._tpl === rootItemBeingAdded._tpl && item.parentId === "hideout"
|
||||
);
|
||||
const matchingItems = itemsWithChildren.filter((itemWithChildren) =>
|
||||
itemWithChildren.find((item) => item._tpl === rootItemBeingAdded._tpl && item.parentId === "hideout")
|
||||
).flatMap((x) => x);
|
||||
if (matchingItems.length === 0)
|
||||
{
|
||||
// Nothing matches by tpl and is root item, exit early
|
||||
@ -726,11 +805,13 @@ export class FenceService
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.itemInPreventDupeCategoryList(itemDbDetails._id);
|
||||
}
|
||||
|
||||
protected itemInPreventDupeCategoryList(tpl: string): boolean
|
||||
{
|
||||
// Item type in config list
|
||||
return this.itemHelper.isOfBaseclasses(
|
||||
itemDbDetails._id,
|
||||
this.traderConfig.fence.preventDuplicateOffersOfCategory,
|
||||
);
|
||||
return this.itemHelper.isOfBaseclasses(tpl, this.traderConfig.fence.preventDuplicateOffersOfCategory);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -799,7 +880,7 @@ export class FenceService
|
||||
protected addPresetsToAssort(
|
||||
desiredWeaponPresetsCount: number,
|
||||
desiredEquipmentPresetsCount: number,
|
||||
assorts: ITraderAssort,
|
||||
assorts: ICreateFenceAssortsResult,
|
||||
baseFenceAssort: ITraderAssort,
|
||||
loyaltyLevel: number,
|
||||
): void
|
||||
@ -848,7 +929,7 @@ export class FenceService
|
||||
// Remapping IDs causes parentid to be altered
|
||||
presetWithChildrenClone[0].parentId = "hideout";
|
||||
|
||||
assorts.items.push(...presetWithChildrenClone);
|
||||
assorts.sptItems.push(presetWithChildrenClone);
|
||||
|
||||
// Set assort price
|
||||
// Must be careful to use correct id as the item has had its IDs regenerated
|
||||
@ -908,7 +989,7 @@ export class FenceService
|
||||
// Remapping IDs causes parentid to be altered
|
||||
presetWithChildrenClone[0].parentId = "hideout";
|
||||
|
||||
assorts.items.push(...presetWithChildrenClone);
|
||||
assorts.sptItems.push(presetWithChildrenClone);
|
||||
|
||||
// Set assort price
|
||||
// Must be careful to use correct id as the item has had its IDs regenerated
|
||||
|
Loading…
Reference in New Issue
Block a user