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
This commit is contained in:
Dev 2024-02-05 19:52:46 +00:00
parent 3653ab9f83
commit d36593ca57
4 changed files with 145 additions and 27 deletions

View File

@ -49,12 +49,19 @@
"discountOptions": { "discountOptions": {
"assortSize": 50, "assortSize": 50,
"itemPriceMult": 0.8, "itemPriceMult": 0.8,
"presetPriceMult": 1.3 "presetPriceMult": 1.2
}, },
"partialRefreshTimeSeconds": 240, "partialRefreshTimeSeconds": 240,
"partialRefreshChangePercent": 15, "partialRefreshChangePercent": 15,
"assortSize": 140, "assortSize": 150,
"maxPresetsPercent": 10, "weaponPresetMinMax": {
"min": 8,
"max": 15
},
"equipmentPresetMinMax": {
"min": 8,
"max": 15
},
"itemPriceMult": 1.2, "itemPriceMult": 1.2,
"presetPriceMult": 2.0, "presetPriceMult": 2.0,
"regenerateAssortsOnRefresh": false, "regenerateAssortsOnRefresh": false,
@ -66,7 +73,7 @@
"555ef6e44bdc2de9068b457e": 5, "555ef6e44bdc2de9068b457e": 5,
"5a341c4086f77401f2541505": 5, "5a341c4086f77401f2541505": 5,
"55818ad54bdc2ddc698b4569": 5, "55818ad54bdc2ddc698b4569": 5,
"5448e53e4bdc2d60728b4567": 5, "5448e53e4bdc2d60728b4567": 6,
"55818ac54bdc2d5b648b456e": 5, "55818ac54bdc2d5b648b456e": 5,
"55818af64bdc2d5b648b4570": 5, "55818af64bdc2d5b648b4570": 5,
"55818a304bdc2db5418b457d": 4, "55818a304bdc2db5418b457d": 4,
@ -97,7 +104,7 @@
"min": 35, "min": 35,
"max": 60 "max": 60
}, },
"chancePlateExistsInArmorPercent": 40, "chancePlateExistsInArmorPercent": 50,
"armorMaxDurabilityPercentMinMax": { "armorMaxDurabilityPercentMinMax": {
"min": 35, "min": 35,
"max": 60 "max": 60
@ -119,17 +126,29 @@
"57864a66245977548f04a81f": 71000, "57864a66245977548f04a81f": 71000,
"5448e54d4bdc2dcc718b4568": 200000, "5448e54d4bdc2dcc718b4568": 200000,
"5a2c3a9486f774688b05e574": 70000, "5a2c3a9486f774688b05e574": 70000,
"5a341c4086f77401f2541505": 125000,
"5448f3a64bdc2d60728b456a": 70000, "5448f3a64bdc2d60728b456a": 70000,
"5447b6194bdc2d67278b4567": 500000, "5447b6194bdc2d67278b4567": 500000,
"550aa4cd4bdc2dd8348b456c": 70000, "550aa4cd4bdc2dd8348b456c": 70000,
"57864ee62459775490116fc1": 95000, "57864ee62459775490116fc1": 95000,
"5448bc234bdc2d3c308b4569": 29000, "5448bc234bdc2d3c308b4569": 29000,
"5447b6094bdc2dc3278b4567": 35009,
"5447bedf4bdc2d87278b4568": 27008,
"5447bed64bdc2d97278b4568": 27007,
"5447b5e04bdc2d62278b4567": 33006,
"5447b5fc4bdc2d87278b4567": 42005,
"5447b5f14bdc2d61278b4567": 44004,
"5447b5cf4bdc2d65278b4567": 30003,
"5447b6254bdc2dc3278b4568": 28002,
"5447e1d04bdc2dff2f8b4567": 19001,
"55818ae44bdc2dde698b456c": 45000, "55818ae44bdc2dde698b456c": 45000,
"55818add4bdc2d5b648b456f": 35000, "55818add4bdc2d5b648b456f": 35000,
"590c745b86f7743cc433c5f2": 64000, "590c745b86f7743cc433c5f2": 64000,
"57864bb7245977548b3b66c2": 90000, "57864bb7245977548b3b66c2": 85000,
"5c99f98d86f7745c314214b3": 15000
"5448e5284bdc2dcb718b4567": 40001,
"5a341c4086f77401f2541505": 105000
}, },
"presetSlotsToRemoveChancePercent": { "presetSlotsToRemoveChancePercent": {
"mod_scope": 70, "mod_scope": 70,
@ -264,4 +283,4 @@
}, },
"btrDeliveryExpireHours": 240 "btrDeliveryExpireHours": 240
} }
} }

View File

@ -1,6 +1,7 @@
import { inject, injectable } from "tsyringe"; import { inject, injectable } from "tsyringe";
import { Category } from "@spt-aki/models/eft/common/tables/IHandbookBase"; 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 { Money } from "@spt-aki/models/enums/Money";
import { DatabaseServer } from "@spt-aki/servers/DatabaseServer"; import { DatabaseServer } from "@spt-aki/servers/DatabaseServer";
import { JsonUtil } from "@spt-aki/utils/JsonUtil"; import { JsonUtil } from "@spt-aki/utils/JsonUtil";
@ -102,6 +103,17 @@ export class HandbookHelper
return handbookItem.Price; 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 * Get all items in template with the given parent category
* @param parentId * @param parentId

View File

@ -26,7 +26,8 @@ export interface FenceConfig
partialRefreshTimeSeconds: number; partialRefreshTimeSeconds: number;
partialRefreshChangePercent: number; partialRefreshChangePercent: number;
assortSize: number; assortSize: number;
maxPresetsPercent: number; weaponPresetMinMax: MinMax;
equipmentPresetMinMax: MinMax;
itemPriceMult: number; itemPriceMult: number;
presetPriceMult: number; presetPriceMult: number;
armorMaxDurabilityPercentMinMax: MinMax; armorMaxDurabilityPercentMinMax: MinMax;

View File

@ -422,9 +422,15 @@ export class FenceService
this.addItemAssorts(assortCount, assorts, baseFenceAssort, itemTypeCounts, loyaltyLevel); this.addItemAssorts(assortCount, assorts, baseFenceAssort, itemTypeCounts, loyaltyLevel);
// Add presets // Add presets
const maxPresetCount = Math.round(assortCount * (this.traderConfig.fence.maxPresetsPercent / 100)); const weaponPresetCount = this.randomUtil.getInt(
const randomisedPresetCount = this.randomUtil.getInt(0, maxPresetCount); this.traderConfig.fence.weaponPresetMinMax.min,
this.addPresetsToAssort(randomisedPresetCount, assorts, baseFenceAssort, loyaltyLevel); 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( protected addItemAssorts(
@ -509,39 +515,37 @@ export class FenceService
/** /**
* Find presets in base fence assort and add desired number to 'assorts' parameter * Find presets in base fence assort and add desired number to 'assorts' parameter
* @param desiredPresetCount * @param desiredWeaponPresetsCount
* @param assorts * @param assorts
* @param baseFenceAssort * @param baseFenceAssort
* @param loyaltyLevel Which loyalty level is required to see/buy item * @param loyaltyLevel Which loyalty level is required to see/buy item
*/ */
protected addPresetsToAssort( protected addPresetsToAssort(
desiredPresetCount: number, desiredWeaponPresetsCount: number,
desiredEquipmentPresetsCount: number,
assorts: ITraderAssort, assorts: ITraderAssort,
baseFenceAssort: ITraderAssort, baseFenceAssort: ITraderAssort,
loyaltyLevel: number, loyaltyLevel: number,
): void ): void
{ {
let presetsAddedCount = 0; let weaponPresetsAddedCount = 0;
if (desiredPresetCount <= 0) if (desiredWeaponPresetsCount <= 0)
{ {
return; return;
} }
const presetRootItems = baseFenceAssort.items.filter((item) => item.upd?.sptPresetId); const weaponPresetRootItems = baseFenceAssort.items.filter((item) =>
while (presetsAddedCount < desiredPresetCount) 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 rootItemDb = this.itemHelper.getItem(randomPresetRoot._tpl)[1];
const presetWithChildrenClone = this.jsonUtil.clone( const presetWithChildrenClone = this.jsonUtil.clone(
this.itemHelper.findAndReturnChildrenAsItems(baseFenceAssort.items, randomPresetRoot._id), 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)) if (this.itemHelper.isOfBaseclass(rootItemDb._id, BaseClasses.WEAPON))
{ {
this.randomiseItemUpdProperties(rootItemDb, presetWithChildrenClone[0]); this.randomiseItemUpdProperties(rootItemDb, presetWithChildrenClone[0]);
@ -549,6 +553,26 @@ export class FenceService
this.removeRandomModsOfItem(presetWithChildrenClone); 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 // 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.reparentItemAndChildren(presetWithChildrenClone[0], presetWithChildrenClone);
this.itemHelper.remapRootItemId(presetWithChildrenClone); this.itemHelper.remapRootItemId(presetWithChildrenClone);
@ -562,7 +586,69 @@ export class FenceService
assorts.barter_scheme[presetWithChildrenClone[0]._id] = baseFenceAssort.barter_scheme[randomPresetRoot._id]; assorts.barter_scheme[presetWithChildrenClone[0]._id] = baseFenceAssort.barter_scheme[randomPresetRoot._id];
assorts.loyal_level_items[presetWithChildrenClone[0]._id] = loyaltyLevel; 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++;
} }
} }