2023-03-03 15:23:46 +00:00
import { inject , injectable } from "tsyringe" ;
2023-10-19 17:21:17 +00:00
import { HandbookHelper } from "@spt-aki/helpers/HandbookHelper" ;
import { ItemHelper } from "@spt-aki/helpers/ItemHelper" ;
import { PresetHelper } from "@spt-aki/helpers/PresetHelper" ;
2024-01-07 22:11:05 +00:00
import { MinMax } from "@spt-aki/models/common/MinMax" ;
2024-01-20 17:56:19 +00:00
import { IFenceLevel } from "@spt-aki/models/eft/common/IGlobals" ;
2023-10-19 17:21:17 +00:00
import { IPmcData } from "@spt-aki/models/eft/common/IPmcData" ;
2024-01-07 22:11:05 +00:00
import { Item , Repairable } from "@spt-aki/models/eft/common/tables/IItem" ;
2023-10-19 17:21:17 +00:00
import { ITemplateItem } from "@spt-aki/models/eft/common/tables/ITemplateItem" ;
2024-02-18 20:20:24 +00:00
import { IBarterScheme , ITraderAssort } from "@spt-aki/models/eft/common/tables/ITrader" ;
2023-10-19 17:21:17 +00:00
import { BaseClasses } from "@spt-aki/models/enums/BaseClasses" ;
import { ConfigTypes } from "@spt-aki/models/enums/ConfigTypes" ;
import { Traders } from "@spt-aki/models/enums/Traders" ;
2024-02-10 09:57:44 +00:00
import { IItemDurabilityCurrentMax , ITraderConfig } from "@spt-aki/models/spt/config/ITraderConfig" ;
2024-02-09 12:39:58 +00:00
import {
IFenceAssortGenerationValues ,
IGenerationAssortValues ,
} from "@spt-aki/models/spt/fence/IFenceAssortGenerationValues" ;
2023-10-19 17:21:17 +00:00
import { ILogger } from "@spt-aki/models/spt/utils/ILogger" ;
import { ConfigServer } from "@spt-aki/servers/ConfigServer" ;
import { DatabaseServer } from "@spt-aki/servers/DatabaseServer" ;
import { LocalisationService } from "@spt-aki/services/LocalisationService" ;
import { JsonUtil } from "@spt-aki/utils/JsonUtil" ;
import { RandomUtil } from "@spt-aki/utils/RandomUtil" ;
import { TimeUtil } from "@spt-aki/utils/TimeUtil" ;
2023-03-03 15:23:46 +00:00
/ * *
* Handle actions surrounding Fence
* e . g . generating or refreshing assorts / get next refresh time
* /
@injectable ( )
export class FenceService
{
2024-02-09 12:39:58 +00:00
protected traderConfig : ITraderConfig ;
/** Time when some items in assort will be replaced */
protected nextPartialRefreshTimestamp : number ;
2023-03-03 15:23:46 +00:00
/** Main assorts you see at all rep levels */
protected fenceAssort : ITraderAssort = undefined ;
2024-02-09 12:39:58 +00:00
2024-01-20 17:56:19 +00:00
/** Assorts shown on a separate tab when you max out fence rep */
2023-03-03 15:23:46 +00:00
protected fenceDiscountAssort : ITraderAssort = undefined ;
2024-02-09 12:39:58 +00:00
/** Hydrated on initial assort generation as part of generateFenceAssorts() */
protected desiredAssortCounts : IFenceAssortGenerationValues ;
2023-03-03 15:23:46 +00:00
constructor (
@inject ( "WinstonLogger" ) protected logger : ILogger ,
@inject ( "JsonUtil" ) protected jsonUtil : JsonUtil ,
@inject ( "TimeUtil" ) protected timeUtil : TimeUtil ,
@inject ( "RandomUtil" ) protected randomUtil : RandomUtil ,
@inject ( "DatabaseServer" ) protected databaseServer : DatabaseServer ,
@inject ( "HandbookHelper" ) protected handbookHelper : HandbookHelper ,
@inject ( "ItemHelper" ) protected itemHelper : ItemHelper ,
@inject ( "PresetHelper" ) protected presetHelper : PresetHelper ,
@inject ( "LocalisationService" ) protected localisationService : LocalisationService ,
2023-11-15 20:35:05 -05:00
@inject ( "ConfigServer" ) protected configServer : ConfigServer ,
2023-03-03 15:23:46 +00:00
)
{
this . traderConfig = this . configServer . getConfig ( ConfigTypes . TRADER ) ;
}
/ * *
* Replace main fence assort with new assort
* @param assort New assorts to replace old with
* /
2023-08-09 22:04:35 +01:00
public setFenceAssort ( assort : ITraderAssort ) : void
2023-03-03 15:23:46 +00:00
{
this . fenceAssort = assort ;
}
/ * *
* Replace high rep level fence assort with new assort
2024-01-20 17:56:19 +00:00
* @param discountAssort New assorts to replace old with
2023-03-03 15:23:46 +00:00
* /
2024-01-20 17:56:19 +00:00
public setFenceDiscountAssort ( discountAssort : ITraderAssort ) : void
2023-03-03 15:23:46 +00:00
{
2024-01-20 17:56:19 +00:00
this . fenceDiscountAssort = discountAssort ;
2023-03-03 15:23:46 +00:00
}
/ * *
* Get assorts player can purchase
* Adjust prices based on fence level of player
* @param pmcProfile Player profile
* @returns ITraderAssort
* /
public getFenceAssorts ( pmcProfile : IPmcData ) : ITraderAssort
{
if ( this . traderConfig . fence . regenerateAssortsOnRefresh )
{
2024-02-06 20:44:40 +00:00
// Using base assorts made earlier, do some alterations and store in this.fenceAssort
2023-03-03 15:23:46 +00:00
this . generateFenceAssorts ( ) ;
}
// Clone assorts so we can adjust prices before sending to client
const assort = this . jsonUtil . clone ( this . fenceAssort ) ;
2024-02-18 20:20:24 +00:00
this . adjustAssortItemPricesByConfigMultiplier ( assort , 1 , this . traderConfig . fence . presetPriceMult ) ;
2023-03-03 15:23:46 +00:00
// merge normal fence assorts + discount assorts if player standing is large enough
if ( pmcProfile . TradersInfo [ Traders . FENCE ] . standing >= 6 )
{
const discountAssort = this . jsonUtil . clone ( this . fenceDiscountAssort ) ;
2024-02-18 20:20:24 +00:00
this . adjustAssortItemPricesByConfigMultiplier (
2023-11-15 20:35:05 -05:00
discountAssort ,
this . traderConfig . fence . discountOptions . itemPriceMult ,
this . traderConfig . fence . discountOptions . presetPriceMult ,
) ;
2023-03-03 15:23:46 +00:00
const mergedAssorts = this . mergeAssorts ( assort , discountAssort ) ;
return mergedAssorts ;
}
return assort ;
}
/ * *
* Adjust all items contained inside an assort by a multiplier
2024-01-20 16:20:39 +00:00
* @param assort ( clone ) Assort that contains items with prices to adjust
2023-03-03 15:23:46 +00:00
* @param itemMultipler multipler to use on items
* @param presetMultiplier preset multipler to use on presets
* /
2024-02-18 20:20:24 +00:00
protected adjustAssortItemPricesByConfigMultiplier (
assort : ITraderAssort ,
itemMultipler : number ,
presetMultiplier : number ,
) : void
2023-03-03 15:23:46 +00:00
{
for ( const item of assort . items )
{
// Skip sub-items when adjusting prices
if ( item . slotId !== "hideout" )
{
continue ;
}
this . adjustItemPriceByModifier ( item , assort , itemMultipler , presetMultiplier ) ;
}
}
/ * *
* Merge two trader assort files together
* @param firstAssort assort 1 #
* @param secondAssort assort # 2
* @returns merged assort
* /
2023-11-15 20:35:05 -05:00
protected mergeAssorts ( firstAssort : ITraderAssort , secondAssort : ITraderAssort ) : ITraderAssort
2023-03-03 15:23:46 +00:00
{
for ( const itemId in secondAssort . barter_scheme )
{
firstAssort . barter_scheme [ itemId ] = secondAssort . barter_scheme [ itemId ] ;
}
for ( const item of secondAssort . items )
{
firstAssort . items . push ( item ) ;
}
for ( const itemId in secondAssort . loyal_level_items )
{
firstAssort . loyal_level_items [ itemId ] = secondAssort . loyal_level_items [ itemId ] ;
}
return firstAssort ;
}
/ * *
* Adjust assorts price by a modifier
* @param item assort item details
* @param assort assort to be modified
* @param modifier value to multiply item price by
* @param presetModifier value to multiply preset price by
* /
2023-11-15 20:35:05 -05:00
protected adjustItemPriceByModifier (
item : Item ,
assort : ITraderAssort ,
modifier : number ,
presetModifier : number ,
) : void
2023-03-03 15:23:46 +00:00
{
// Is preset
if ( item . upd . sptPresetId )
{
if ( assort . barter_scheme [ item . _id ] )
{
2024-02-12 23:59:02 +00:00
assort . barter_scheme [ item . _id ] [ 0 ] [ 0 ] . count *= presetModifier ;
2023-03-03 15:23:46 +00:00
}
}
2023-10-10 11:03:20 +00:00
else if ( assort . barter_scheme [ item . _id ] )
{
assort . barter_scheme [ item . _id ] [ 0 ] [ 0 ] . count *= modifier ;
}
2023-03-03 15:23:46 +00:00
else
{
2023-10-10 11:03:20 +00:00
this . logger . warning ( ` adjustItemPriceByModifier() - no action taken for item: ${ item . _tpl } ` ) ;
2024-02-05 19:49:42 +00:00
return ;
}
2023-03-03 15:23:46 +00:00
}
/ * *
* Get fence assorts with no price adjustments based on fence rep
* @returns ITraderAssort
* /
public getRawFenceAssorts ( ) : ITraderAssort
{
2024-02-02 17:15:28 +00:00
return this . mergeAssorts ( this . jsonUtil . clone ( this . fenceAssort ) , this . jsonUtil . clone ( this . fenceDiscountAssort ) ) ;
2023-03-03 15:23:46 +00:00
}
/ * *
* Does fence need to perform a partial refresh because its passed the refresh timer defined in trader . json
* @returns true if it needs a partial refresh
* /
public needsPartialRefresh ( ) : boolean
{
2024-02-09 12:39:58 +00:00
return this . timeUtil . getTimestamp ( ) > this . nextPartialRefreshTimestamp ;
2023-03-03 15:23:46 +00:00
}
/ * *
* Replace a percentage of fence assorts with freshly generated items
* /
public performPartialRefresh ( ) : void
{
2024-02-09 12:39:58 +00:00
const itemCountToReplace = this . getCountOfItemsToReplace ( this . traderConfig . fence . assortSize ) ;
2023-11-15 20:35:05 -05:00
const discountItemCountToReplace = this . getCountOfItemsToReplace (
this . traderConfig . fence . discountOptions . assortSize ,
) ;
2023-03-03 15:23:46 +00:00
2024-02-09 12:39:58 +00:00
// Simulate players buying items
this . deleteRandomAssorts ( itemCountToReplace , this . fenceAssort ) ;
this . deleteRandomAssorts ( discountItemCountToReplace , this . fenceDiscountAssort ) ;
2023-03-03 15:23:46 +00:00
2024-02-09 12:39:58 +00:00
// Get count of what item pools need new items (item/weapon/equipment)
const itemCountsToReplace = this . getCountOfItemsToGenerate ( ) ;
2023-03-03 15:23:46 +00:00
2024-01-20 17:56:19 +00:00
const newItems = this . createFenceAssortSkeleton ( ) ;
2024-02-09 12:39:58 +00:00
this . createAssorts ( itemCountsToReplace . normal , newItems , 2 ) ;
2023-03-03 15:23:46 +00:00
this . fenceAssort . items . push ( . . . newItems . items ) ;
2024-02-09 12:39:58 +00:00
const newDiscountItems = this . createFenceAssortSkeleton ( ) ;
this . createAssorts ( itemCountsToReplace . discount , newDiscountItems , 2 ) ;
2023-03-03 15:23:46 +00:00
this . fenceDiscountAssort . items . push ( . . . newDiscountItems . items ) ;
// Add new barter items to fence barter scheme
for ( const barterItemKey in newItems . barter_scheme )
{
this . fenceAssort . barter_scheme [ barterItemKey ] = newItems . barter_scheme [ barterItemKey ] ;
}
// Add loyalty items to fence assorts loyalty object
for ( const loyaltyItemKey in newItems . loyal_level_items )
{
this . fenceAssort . loyal_level_items [ loyaltyItemKey ] = newItems . loyal_level_items [ loyaltyItemKey ] ;
}
// Add new barter items to fence assorts discounted barter scheme
for ( const barterItemKey in newDiscountItems . barter_scheme )
{
this . fenceDiscountAssort . barter_scheme [ barterItemKey ] = newDiscountItems . barter_scheme [ barterItemKey ] ;
}
// Add loyalty items to fence discount assorts loyalty object
for ( const loyaltyItemKey in newDiscountItems . loyal_level_items )
{
2023-11-15 20:35:05 -05:00
this . fenceDiscountAssort . loyal_level_items [ loyaltyItemKey ] =
newDiscountItems . loyal_level_items [ loyaltyItemKey ] ;
2023-03-03 15:23:46 +00:00
}
2024-02-09 12:39:58 +00:00
// Reset the clock
2023-03-03 15:23:46 +00:00
this . incrementPartialRefreshTime ( ) ;
}
/ * *
* Increment fence next refresh timestamp by current timestamp + partialRefreshTimeSeconds from config
* /
protected incrementPartialRefreshTime ( ) : void
{
2024-02-09 12:39:58 +00:00
this . nextPartialRefreshTimestamp = this . timeUtil . getTimestamp ( )
2023-11-15 20:35:05 -05:00
+ this . traderConfig . fence . partialRefreshTimeSeconds ;
2023-03-03 15:23:46 +00:00
}
/ * *
* Compare the current fence offer count to what the config wants it to be ,
* If value is lower add extra count to value to generate more items to fill gap
* @param existingItemCountToReplace count of items to generate
* @returns number of items to generate
* /
2024-02-09 12:39:58 +00:00
protected getCountOfItemsToGenerate ( ) : IFenceAssortGenerationValues
2023-03-03 15:23:46 +00:00
{
2024-02-09 12:39:58 +00:00
const currentItemAssortCount = Object . keys ( this . fenceAssort . loyal_level_items ) . length ;
const rootPresetItems = this . fenceAssort . items . filter ( ( item ) = >
item . slotId === "hideout" && item . upd . sptPresetId
) ;
// Get count of weapons
const currentWeaponPresetCount = rootPresetItems . reduce ( ( count , item ) = >
2023-03-03 15:23:46 +00:00
{
2024-02-09 12:39:58 +00:00
return this . itemHelper . isOfBaseclass ( item . _tpl , BaseClasses . WEAPON ) ? count + 1 : count ;
2023-04-24 13:34:40 +01:00
} , 0 ) ;
2024-02-09 12:39:58 +00:00
// Get count of equipment
const currentEquipmentPresetCount = rootPresetItems . reduce ( ( count , item ) = >
{
return this . itemHelper . armorItemCanHoldMods ( item . _tpl ) ? count + 1 : count ;
} , 0 ) ;
const itemCountToGenerate = Math . max ( this . desiredAssortCounts . normal . item - currentItemAssortCount , 0 ) ;
const weaponCountToGenerate = Math . max (
this . desiredAssortCounts . normal . weaponPreset - currentWeaponPresetCount ,
0 ,
) ;
const equipmentCountToGenerate = Math . max (
this . desiredAssortCounts . normal . equipmentPreset - currentEquipmentPresetCount ,
0 ,
) ;
const normalValues : IGenerationAssortValues = {
item : itemCountToGenerate ,
weaponPreset : weaponCountToGenerate ,
equipmentPreset : equipmentCountToGenerate ,
} ;
// Discount tab handling
const currentDiscountItemAssortCount = Object . keys ( this . fenceDiscountAssort . loyal_level_items ) . length ;
const rootDiscountPresetItems = this . fenceDiscountAssort . items . filter ( ( item ) = >
item . slotId === "hideout" && item . upd . sptPresetId
) ;
// Get count of weapons
const currentDiscountWeaponPresetCount = rootDiscountPresetItems . reduce ( ( count , item ) = >
{
return this . itemHelper . isOfBaseclass ( item . _tpl , BaseClasses . WEAPON ) ? count + 1 : count ;
} , 0 ) ;
// Get count of equipment
const currentDiscountEquipmentPresetCount = rootDiscountPresetItems . reduce ( ( count , item ) = >
{
return this . itemHelper . armorItemCanHoldMods ( item . _tpl ) ? count + 1 : count ;
} , 0 ) ;
const itemDiscountCountToGenerate = Math . max (
this . desiredAssortCounts . discount . item - currentDiscountItemAssortCount ,
0 ,
) ;
const weaponDiscountCountToGenerate = Math . max (
this . desiredAssortCounts . discount . weaponPreset - currentDiscountWeaponPresetCount ,
0 ,
) ;
const equipmentDiscountCountToGenerate = Math . max (
this . desiredAssortCounts . discount . equipmentPreset - currentDiscountEquipmentPresetCount ,
0 ,
) ;
const discountValues : IGenerationAssortValues = {
item : itemDiscountCountToGenerate ,
weaponPreset : weaponDiscountCountToGenerate ,
equipmentPreset : equipmentDiscountCountToGenerate ,
} ;
return { normal : normalValues , discount : discountValues } ;
}
/ * *
* Delete desired number of items from assort ( including children )
* @param itemCountToReplace
* @param discountItemCountToReplace
* /
protected deleteRandomAssorts ( itemCountToReplace : number , assort : ITraderAssort ) : void
{
if ( assort ? . items ? . length > 0 )
{
const rootItems = assort . items . filter ( ( item ) = > item . slotId === "hideout" ) ;
for ( let index = 0 ; index < itemCountToReplace ; index ++ )
{
this . removeRandomItemFromAssorts ( assort , rootItems ) ;
}
}
2023-03-03 15:23:46 +00:00
}
/ * *
2024-02-07 23:44:15 +00:00
* Choose an item at random and remove it + mods from assorts
2023-04-24 13:34:40 +01:00
* @param assort Items to remove from
2024-02-07 23:44:15 +00:00
* @param rootItems Assort root items to pick from to remove
2023-03-03 15:23:46 +00:00
* /
2024-02-07 23:44:15 +00:00
protected removeRandomItemFromAssorts ( assort : ITraderAssort , rootItems : Item [ ] ) : void
2023-03-03 15:23:46 +00:00
{
2024-02-07 23:44:15 +00:00
const rootItemToRemove = this . randomUtil . getArrayValue ( rootItems ) ;
2023-03-03 15:23:46 +00:00
2024-02-07 23:44:15 +00:00
// Clean up any mods if item had them
const itemWithChildren = this . itemHelper . findAndReturnChildrenAsItems ( assort . items , rootItemToRemove . _id ) ;
2024-01-20 16:20:39 +00:00
for ( const itemToDelete of itemWithChildren )
{
// Delete item from assort items array
assort . items . splice ( assort . items . indexOf ( itemToDelete ) , 1 ) ;
}
2023-03-03 15:23:46 +00:00
2024-02-07 23:44:15 +00:00
delete assort . barter_scheme [ rootItemToRemove . _id ] ;
delete assort . loyal_level_items [ rootItemToRemove . _id ] ;
2023-03-03 15:23:46 +00:00
}
/ * *
* Get an integer rounded count of items to replace based on percentrage from traderConfig value
* @param totalItemCount total item count
* @returns rounded int of items to replace
* /
protected getCountOfItemsToReplace ( totalItemCount : number ) : number
{
return Math . round ( totalItemCount * ( this . traderConfig . fence . partialRefreshChangePercent / 100 ) ) ;
}
/ * *
* Get the count of items fence offers
* @returns number
* /
public getOfferCount ( ) : number
{
if ( ! this . fenceAssort ? . items ? . length )
{
return 0 ;
}
return this . fenceAssort . items . length ;
}
/ * *
* Create trader assorts for fence and store in fenceService cache
2024-01-20 17:56:19 +00:00
* Uses fence base cache generatedon server start as a base
2023-03-03 15:23:46 +00:00
* /
public generateFenceAssorts ( ) : void
{
// Reset refresh time now assorts are being generated
this . incrementPartialRefreshTime ( ) ;
2024-02-09 12:39:58 +00:00
// Choose assort counts using config
this . createInitialFenceAssortGenerationValues ( ) ;
2023-03-03 15:23:46 +00:00
// Create basic fence assort
2024-02-06 20:44:40 +00:00
const assorts = this . createFenceAssortSkeleton ( ) ;
2024-02-09 12:39:58 +00:00
this . createAssorts ( this . desiredAssortCounts . normal , assorts , 1 ) ;
2024-02-06 20:44:40 +00:00
// Store in this.fenceAssort
this . setFenceAssort ( assorts ) ;
2023-03-03 15:23:46 +00:00
// Create level 2 assorts accessible at rep level 6
2024-02-06 20:44:40 +00:00
const discountAssorts = this . createFenceAssortSkeleton ( ) ;
2024-02-09 12:39:58 +00:00
this . createAssorts ( this . desiredAssortCounts . discount , discountAssorts , 2 ) ;
2024-02-06 20:44:40 +00:00
// Store in this.fenceDiscountAssort
2023-03-03 15:23:46 +00:00
this . setFenceDiscountAssort ( discountAssorts ) ;
}
2024-02-09 12:39:58 +00:00
/ * *
* Create object that contains calculated fence assort item values to make based on config
* Stored in this . desiredAssortCounts
* /
protected createInitialFenceAssortGenerationValues ( ) : void
{
const result : IFenceAssortGenerationValues = {
normal : { item : 0 , weaponPreset : 0 , equipmentPreset : 0 } ,
discount : { item : 0 , weaponPreset : 0 , equipmentPreset : 0 } ,
} ;
result . normal . item = this . traderConfig . fence . assortSize ;
result . normal . weaponPreset = this . randomUtil . getInt (
this . traderConfig . fence . weaponPresetMinMax . min ,
this . traderConfig . fence . weaponPresetMinMax . max ,
) ;
result . normal . equipmentPreset = this . randomUtil . getInt (
this . traderConfig . fence . equipmentPresetMinMax . min ,
this . traderConfig . fence . equipmentPresetMinMax . max ,
) ;
result . discount . item = this . traderConfig . fence . discountOptions . assortSize ;
result . discount . weaponPreset = this . randomUtil . getInt (
this . traderConfig . fence . discountOptions . weaponPresetMinMax . min ,
this . traderConfig . fence . discountOptions . weaponPresetMinMax . max ,
) ;
result . discount . equipmentPreset = this . randomUtil . getInt (
this . traderConfig . fence . discountOptions . equipmentPresetMinMax . min ,
this . traderConfig . fence . discountOptions . equipmentPresetMinMax . max ,
) ;
this . desiredAssortCounts = result ;
}
2023-03-03 15:23:46 +00:00
/ * *
* Create skeleton to hold assort items
* @returns ITraderAssort object
* /
2024-01-20 17:56:19 +00:00
protected createFenceAssortSkeleton ( ) : ITraderAssort
2023-03-03 15:23:46 +00:00
{
return {
items : [ ] ,
barter_scheme : { } ,
loyal_level_items : { } ,
2023-11-15 20:35:05 -05:00
nextResupply : this.getNextFenceUpdateTimestamp ( ) ,
2023-03-03 15:23:46 +00:00
} ;
}
/ * *
* Hydrate assorts parameter object with generated assorts
* @param assortCount Number of assorts to generate
* @param assorts object to add created assorts to
* /
2024-02-09 12:39:58 +00:00
protected createAssorts ( itemCounts : IGenerationAssortValues , assorts : ITraderAssort , loyaltyLevel : number ) : void
2023-03-03 15:23:46 +00:00
{
2024-02-06 20:44:40 +00:00
const baseFenceAssortClone = this . jsonUtil . clone ( this . databaseServer . getTables ( ) . traders [ Traders . FENCE ] . assort ) ;
2024-02-09 12:39:58 +00:00
const itemTypeLimitCounts = this . initItemLimitCounter ( this . traderConfig . fence . itemTypeLimits ) ;
2024-02-02 13:54:07 -05:00
2024-02-09 12:39:58 +00:00
if ( itemCounts . item > 0 )
{
this . addItemAssorts ( itemCounts . item , assorts , baseFenceAssortClone , itemTypeLimitCounts , loyaltyLevel ) ;
}
2024-02-02 13:54:07 -05:00
2024-02-09 12:39:58 +00:00
if ( itemCounts . weaponPreset > 0 || itemCounts . equipmentPreset > 0 )
{
// Add presets
this . addPresetsToAssort (
itemCounts . weaponPreset ,
itemCounts . equipmentPreset ,
assorts ,
baseFenceAssortClone ,
loyaltyLevel ,
) ;
}
2023-03-03 15:23:46 +00:00
}
2024-02-09 12:39:58 +00:00
/ * *
* Add item assorts to existing assort data
* @param assortCount Number to add
* @param assorts Assorts data to add to
2024-02-18 20:20:24 +00:00
* @param baseFenceAssortClone Base data to draw from
2024-02-15 23:14:31 +00:00
* @param itemTypeLimits
2024-02-09 12:39:58 +00:00
* @param loyaltyLevel Loyalty level to set new item to
* /
2023-11-15 20:35:05 -05:00
protected addItemAssorts (
assortCount : number ,
assorts : ITraderAssort ,
2024-02-18 20:20:24 +00:00
baseFenceAssortClone : ITraderAssort ,
2024-02-15 23:14:31 +00:00
itemTypeLimits : Record < string , { current : number ; max : number ; } > ,
2023-11-15 20:35:05 -05:00
loyaltyLevel : number ,
) : void
2023-03-03 15:23:46 +00:00
{
2023-10-10 11:03:20 +00:00
const priceLimits = this . traderConfig . fence . itemCategoryRoublePriceLimit ;
2024-02-18 20:20:24 +00:00
const assortRootItems = baseFenceAssortClone . items . filter ( ( x ) = >
x . parentId === "hideout" && ! x . upd ? . sptPresetId
) ;
2024-02-16 13:41:16 +00:00
2023-03-03 15:23:46 +00:00
for ( let i = 0 ; i < assortCount ; i ++ )
{
2024-02-02 17:15:28 +00:00
const chosenBaseAssortRoot = this . randomUtil . getArrayValue ( assortRootItems ) ;
if ( ! chosenBaseAssortRoot )
2024-01-20 16:20:39 +00:00
{
2024-02-02 13:54:07 -05:00
this . logger . error (
this . localisationService . getText ( "fence-unable_to_find_assort_by_id" , chosenBaseAssortRoot . _id ) ,
) ;
2024-01-20 16:20:39 +00:00
continue ;
}
2024-02-05 22:22:03 -05:00
let desiredAssortItemAndChildrenClone = this . jsonUtil . clone (
2024-02-18 20:20:24 +00:00
this . itemHelper . findAndReturnChildrenAsItems ( baseFenceAssortClone . items , chosenBaseAssortRoot . _id ) ,
2024-02-02 13:54:07 -05:00
) ;
2023-03-03 15:23:46 +00:00
2024-02-02 17:15:28 +00:00
const itemDbDetails = this . itemHelper . getItem ( chosenBaseAssortRoot . _tpl ) [ 1 ] ;
2024-02-15 23:14:31 +00:00
const itemLimitCount = this . getMatchingItemLimit ( itemTypeLimits , itemDbDetails . _id ) ;
if ( itemLimitCount ? . current >= itemLimitCount ? . max )
2024-01-20 16:20:39 +00:00
{
// Skip adding item as assort as limit reached, decrement i counter so we still get another item
i -- ;
continue ;
}
2024-02-02 13:54:07 -05:00
2024-02-02 17:15:28 +00:00
const itemIsPreset = this . presetHelper . isPreset ( chosenBaseAssortRoot . _id ) ;
2023-03-03 15:23:46 +00:00
2024-02-18 20:20:24 +00:00
const price = baseFenceAssortClone . barter_scheme [ chosenBaseAssortRoot . _id ] [ 0 ] [ 0 ] . count ;
2023-03-03 15:23:46 +00:00
if ( price === 0 || ( price === 1 && ! itemIsPreset ) || price === 100 )
{
2024-02-02 17:15:28 +00:00
// Don't allow "special" items / presets
2023-03-03 15:23:46 +00:00
i -- ;
continue ;
}
2024-02-02 13:54:07 -05:00
2024-01-20 16:20:39 +00:00
if ( price > priceLimits [ itemDbDetails . _parent ] )
2023-03-03 15:23:46 +00:00
{
2024-02-02 17:15:28 +00:00
// Too expensive for fence, try another item
2024-01-20 16:20:39 +00:00
i -- ;
continue ;
}
2023-03-03 15:23:46 +00:00
2024-01-20 16:20:39 +00:00
// Increment count as item is being added
if ( itemLimitCount )
{
itemLimitCount . current ++ ;
}
2024-01-07 22:11:05 +00:00
2024-01-20 17:56:19 +00:00
// MUST randomise Ids as its possible to add the same base fence assort twice = duplicate IDs = dead client
2024-02-05 22:22:03 -05:00
desiredAssortItemAndChildrenClone = this . itemHelper . replaceIDs ( desiredAssortItemAndChildrenClone ) ;
2024-02-02 17:15:28 +00:00
this . itemHelper . remapRootItemId ( desiredAssortItemAndChildrenClone ) ;
2024-01-20 17:56:19 +00:00
2024-01-20 16:20:39 +00:00
const rootItemBeingAdded = desiredAssortItemAndChildrenClone [ 0 ] ;
rootItemBeingAdded . upd . StackObjectsCount = this . getSingleItemStackCount ( itemDbDetails ) ;
2023-10-10 11:03:20 +00:00
2024-03-05 08:49:10 +00:00
// Skip items already in the assort if it exists in the prevent duplicate list
2024-03-05 10:43:47 +00:00
const existingItem = assorts . items . find ( ( item ) = > item . _tpl === rootItemBeingAdded . _tpl ) ;
if ( this . itemShouldBeForceStacked ( existingItem , itemDbDetails ) )
2024-03-05 08:49:10 +00:00
{
i -- ;
2024-03-06 21:38:00 +00:00
existingItem . upd . StackObjectsCount ++ ;
2024-03-05 08:49:10 +00:00
continue ;
}
2024-02-16 13:41:16 +00:00
// Only randomise single items
2024-02-18 20:20:24 +00:00
const isSingleStack = rootItemBeingAdded . upd . StackObjectsCount === 1 ;
if ( isSingleStack )
2024-02-16 13:41:16 +00:00
{
this . randomiseItemUpdProperties ( itemDbDetails , rootItemBeingAdded ) ;
}
2024-03-05 10:43:47 +00:00
// Add mods to armors so they dont show as red in the trade screen
2024-01-20 16:20:39 +00:00
if ( this . itemHelper . itemRequiresSoftInserts ( rootItemBeingAdded . _tpl ) )
{
this . randomiseArmorModDurability ( desiredAssortItemAndChildrenClone , itemDbDetails ) ;
2024-01-07 22:11:05 +00:00
}
2024-01-20 16:20:39 +00:00
assorts . items . push ( . . . desiredAssortItemAndChildrenClone ) ;
2024-02-18 20:20:24 +00:00
assorts . barter_scheme [ rootItemBeingAdded . _id ] = this . jsonUtil . clone (
baseFenceAssortClone . barter_scheme [ chosenBaseAssortRoot . _id ] ,
) ;
2024-02-18 21:10:41 +00:00
// Only adjust item price by quality for solo items, never multi-stack
2024-02-18 20:20:24 +00:00
if ( isSingleStack )
{
this . adjustItemPriceByQuality ( assorts . barter_scheme , rootItemBeingAdded , itemDbDetails ) ;
}
2024-01-20 16:20:39 +00:00
assorts . loyal_level_items [ rootItemBeingAdded . _id ] = loyaltyLevel ;
2024-01-07 22:11:05 +00:00
}
}
2023-03-03 15:23:46 +00:00
2024-03-05 10:43:47 +00:00
/ * *
* Should this item be forced into only 1 stack on fence
* @param existingItem Existing item from fence assort
* @param itemDbDetails Item we want to add db details
* @returns True item should be force stacked
* /
protected itemShouldBeForceStacked ( existingItem : Item , itemDbDetails : ITemplateItem ) : boolean
{
// No existing item in assort
if ( ! existingItem )
{
return false ;
}
// Item type not in config list
if (
! this . itemHelper . isOfBaseclasses (
itemDbDetails . _id ,
this . traderConfig . fence . preventDuplicateOffersOfCategory ,
)
)
{
return false ;
}
2024-03-06 21:38:00 +00:00
// Don't stack armor with slots (plates/inserts etc)
if (
this . itemHelper . isOfBaseclass ( itemDbDetails . _id , BaseClasses . ARMORED_EQUIPMENT )
&& itemDbDetails . _props . Slots . length > 0
)
2024-03-05 10:43:47 +00:00
{
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 ;
}
2024-02-18 20:20:24 +00:00
/ * *
* Adjust price of item based on what is left to buy ( resource / uses left )
* @param barterSchemes All barter scheme for item having price adjusted
* @param itemRoot Root item having price adjusted
* @param itemTemplate Db template of item
* /
protected adjustItemPriceByQuality (
barterSchemes : Record < string , IBarterScheme [ ] [ ] > ,
itemRoot : Item ,
itemTemplate : ITemplateItem ,
) : void
{
// Healing items
if ( itemRoot . upd ? . MedKit )
{
const itemTotalMax = itemTemplate . _props . MaxHpResource ;
const current = itemRoot . upd . MedKit . HpResource ;
// Current and max match, no adjustment necessary
if ( itemTotalMax === current )
{
return ;
}
const multipler = current / itemTotalMax ;
// Multiply item cost by desired multiplier
const basePrice = barterSchemes [ itemRoot . _id ] [ 0 ] [ 0 ] . count ;
barterSchemes [ itemRoot . _id ] [ 0 ] [ 0 ] . count = Math . round ( basePrice * multipler ) ;
return ;
}
// Adjust price based on durability
2024-02-19 16:58:07 +00:00
if ( itemRoot . upd ? . Repairable || this . itemHelper . isOfBaseclass ( itemRoot . _tpl , BaseClasses . KEY_MECHANICAL ) )
2024-02-18 20:20:24 +00:00
{
const itemQualityModifier = this . itemHelper . getItemQualityModifier ( itemRoot ) ;
const basePrice = barterSchemes [ itemRoot . _id ] [ 0 ] [ 0 ] . count ;
barterSchemes [ itemRoot . _id ] [ 0 ] [ 0 ] . count = Math . round ( basePrice * itemQualityModifier ) ;
}
}
2024-02-15 23:14:31 +00:00
protected getMatchingItemLimit (
itemTypeLimits : Record < string , { current : number ; max : number ; } > ,
itemTpl : string ,
) : { current : number ; max : number ; }
{
for ( const baseTypeKey in itemTypeLimits )
{
if ( this . itemHelper . isOfBaseclass ( itemTpl , baseTypeKey ) )
{
return itemTypeLimits [ baseTypeKey ] ;
}
}
}
2024-01-20 17:56:19 +00:00
/ * *
* Find presets in base fence assort and add desired number to 'assorts' parameter
2024-02-05 19:52:46 +00:00
* @param desiredWeaponPresetsCount
2024-02-09 12:39:58 +00:00
* @param assorts Assorts to add preset to
* @param baseFenceAssort Base data to draw from
2024-01-20 17:56:19 +00:00
* @param loyaltyLevel Which loyalty level is required to see / buy item
* /
2024-02-02 13:54:07 -05:00
protected addPresetsToAssort (
2024-02-05 19:52:46 +00:00
desiredWeaponPresetsCount : number ,
desiredEquipmentPresetsCount : number ,
2024-02-02 13:54:07 -05:00
assorts : ITraderAssort ,
baseFenceAssort : ITraderAssort ,
loyaltyLevel : number ,
) : void
2024-01-20 17:56:19 +00:00
{
2024-02-05 19:52:46 +00:00
let weaponPresetsAddedCount = 0 ;
2024-02-09 12:39:58 +00:00
if ( desiredWeaponPresetsCount > 0 )
2024-01-20 17:56:19 +00:00
{
2024-02-09 12:39:58 +00:00
const weaponPresetRootItems = baseFenceAssort . items . filter ( ( item ) = >
item . upd ? . sptPresetId && this . itemHelper . isOfBaseclass ( item . _tpl , BaseClasses . WEAPON )
) ;
while ( weaponPresetsAddedCount < desiredWeaponPresetsCount )
2024-02-08 21:58:47 +00:00
{
2024-02-09 12:39:58 +00:00
const randomPresetRoot = this . randomUtil . getArrayValue ( weaponPresetRootItems ) ;
if ( this . traderConfig . fence . blacklist . includes ( randomPresetRoot . _tpl ) )
{
continue ;
}
2024-02-08 21:58:47 +00:00
2024-02-09 12:39:58 +00:00
const rootItemDb = this . itemHelper . getItem ( randomPresetRoot . _tpl ) [ 1 ] ;
2024-02-05 19:52:46 +00:00
2024-02-09 12:39:58 +00:00
const presetWithChildrenClone = this . jsonUtil . clone (
this . itemHelper . findAndReturnChildrenAsItems ( baseFenceAssort . items , randomPresetRoot . _id ) ,
) ;
2024-02-05 19:52:46 +00:00
2024-02-09 12:39:58 +00:00
this . randomiseItemUpdProperties ( rootItemDb , presetWithChildrenClone [ 0 ] ) ;
2024-02-05 19:52:46 +00:00
2024-02-09 12:39:58 +00:00
this . removeRandomModsOfItem ( presetWithChildrenClone ) ;
2024-02-05 19:52:46 +00:00
2024-02-09 12:39:58 +00:00
// Check chosen item is below price cap
const priceLimitRouble = this . traderConfig . fence . itemCategoryRoublePriceLimit [ rootItemDb . _parent ] ;
const itemPrice = this . handbookHelper . getTemplatePriceForItems ( presetWithChildrenClone )
* this . itemHelper . getItemQualityModifierForOfferItems ( presetWithChildrenClone ) ;
if ( priceLimitRouble )
2024-02-05 19:52:46 +00:00
{
2024-02-09 12:39:58 +00:00
if ( itemPrice > priceLimitRouble )
{
// Too expensive, try again
continue ;
}
2024-02-05 19:52:46 +00:00
}
2024-02-09 12:39:58 +00:00
// 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 ) ;
2024-02-05 19:52:46 +00:00
2024-02-09 12:39:58 +00:00
// Remapping IDs causes parentid to be altered
presetWithChildrenClone [ 0 ] . parentId = "hideout" ;
2024-02-05 19:52:46 +00:00
2024-02-09 12:39:58 +00:00
assorts . items . push ( . . . presetWithChildrenClone ) ;
2024-02-05 19:52:46 +00:00
2024-02-09 12:39:58 +00:00
// Set assort price
// Must be careful to use correct id as the item has had its IDs regenerated
assorts . barter_scheme [ presetWithChildrenClone [ 0 ] . _id ] = [ [ {
_tpl : "5449016a4bdc2d6f028b456f" ,
count : Math.round ( itemPrice ) ,
} ] ] ;
assorts . loyal_level_items [ presetWithChildrenClone [ 0 ] . _id ] = loyaltyLevel ;
2024-02-05 19:52:46 +00:00
2024-02-09 12:39:58 +00:00
weaponPresetsAddedCount ++ ;
}
2024-02-05 19:52:46 +00:00
}
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 ] ;
2024-02-02 13:54:07 -05:00
const presetWithChildrenClone = this . jsonUtil . clone (
this . itemHelper . findAndReturnChildrenAsItems ( baseFenceAssort . items , randomPresetRoot . _id ) ,
) ;
2024-01-20 17:56:19 +00:00
// 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 ) ;
}
2024-02-05 19:52:46 +00:00
this . removeRandomModsOfItem ( presetWithChildrenClone ) ;
// Check chosen item is below price cap
const priceLimitRouble = this . traderConfig . fence . itemCategoryRoublePriceLimit [ rootItemDb . _parent ] ;
2024-02-06 20:44:40 +00:00
const itemPrice = this . handbookHelper . getTemplatePriceForItems ( presetWithChildrenClone )
* this . itemHelper . getItemQualityModifierForOfferItems ( presetWithChildrenClone ) ;
2024-02-05 19:52:46 +00:00
if ( priceLimitRouble )
2024-01-20 17:56:19 +00:00
{
2024-02-06 20:44:40 +00:00
if ( itemPrice > priceLimitRouble )
2024-02-05 19:52:46 +00:00
{
// Too expensive, try again
continue ;
}
}
2024-01-20 17:56:19 +00:00
// MUST randomise Ids as its possible to add the same base fence assort twice = duplicate IDs = dead client
2024-02-01 21:02:39 +00:00
this . itemHelper . reparentItemAndChildren ( presetWithChildrenClone [ 0 ] , presetWithChildrenClone ) ;
2024-01-20 17:56:19 +00:00
this . itemHelper . remapRootItemId ( presetWithChildrenClone ) ;
2024-02-05 18:21:02 +00:00
// Remapping IDs causes parentid to be altered
presetWithChildrenClone [ 0 ] . parentId = "hideout" ;
2024-01-20 17:56:19 +00:00
assorts . items . push ( . . . presetWithChildrenClone ) ;
2024-02-07 18:01:00 +00:00
// Set assort price
2024-01-20 17:56:19 +00:00
// Must be careful to use correct id as the item has had its IDs regenerated
2024-02-06 20:44:40 +00:00
assorts . barter_scheme [ presetWithChildrenClone [ 0 ] . _id ] = [ [ {
_tpl : "5449016a4bdc2d6f028b456f" ,
2024-02-07 23:17:22 +00:00
count : Math.round ( itemPrice ) ,
2024-02-06 20:44:40 +00:00
} ] ] ;
2024-01-20 17:56:19 +00:00
assorts . loyal_level_items [ presetWithChildrenClone [ 0 ] . _id ] = loyaltyLevel ;
2024-02-05 19:52:46 +00:00
equipmentPresetsAddedCount ++ ;
2024-01-20 17:56:19 +00:00
}
}
2024-01-07 23:11:18 +00:00
/ * *
2024-01-20 16:20:39 +00:00
* Adjust plate / soft insert durability values
2024-01-07 23:11:18 +00:00
* @param armor Armor item array to add mods into
* @param itemDbDetails Armor items db template
* /
2024-01-20 16:20:39 +00:00
protected randomiseArmorModDurability ( armor : Item [ ] , itemDbDetails : ITemplateItem ) : void
2024-01-07 22:11:05 +00:00
{
2024-01-20 16:20:39 +00:00
// Armor has no mods, make no changes
2024-01-07 23:11:18 +00:00
const hasMods = itemDbDetails . _props . Slots . length > 0 ;
2024-01-07 22:11:05 +00:00
if ( ! hasMods )
{
return ;
}
2023-03-03 15:23:46 +00:00
2024-01-20 16:20:39 +00:00
// Check for and adjust soft insert durability values
2024-02-02 13:54:07 -05:00
const requiredSlots = itemDbDetails . _props . Slots . filter ( ( slot ) = > slot . _required ) ;
2024-01-07 22:11:05 +00:00
const hasRequiredSlots = requiredSlots . length > 0 ;
if ( hasRequiredSlots )
{
for ( const requiredSlot of requiredSlots )
{
const modItemDbDetails = this . itemHelper . getItem ( requiredSlot . _props . filters [ 0 ] . Plate ) [ 1 ] ;
2024-01-20 16:20:39 +00:00
const durabilityValues = this . getRandomisedArmorDurabilityValues (
modItemDbDetails ,
2024-02-02 13:54:07 -05:00
this . traderConfig . fence . armorMaxDurabilityPercentMinMax ,
) ;
2024-01-13 20:47:59 +00:00
const plateTpl = requiredSlot . _props . filters [ 0 ] . Plate ; // `Plate` property appears to be the 'default' item for slot
if ( plateTpl === "" )
{
// Some bsg plate properties are empty, skip mod
continue ;
}
2024-01-20 16:20:39 +00:00
// Find items mod to apply dura changes to
2024-02-02 13:54:07 -05:00
const modItemToAdjust = armor . find ( ( mod ) = >
mod . slotId . toLowerCase ( ) === requiredSlot . _name . toLowerCase ( )
) ;
2024-01-20 16:20:39 +00:00
if ( ! modItemToAdjust . upd )
{
2024-02-02 13:54:07 -05:00
modItemToAdjust . upd = { } ;
2024-01-20 16:20:39 +00:00
}
if ( ! modItemToAdjust . upd . Repairable )
{
modItemToAdjust . upd . Repairable = {
Durability : modItemDbDetails._props.MaxDurability ,
2024-02-02 13:54:07 -05:00
MaxDurability : modItemDbDetails._props.MaxDurability ,
2024-01-20 16:20:39 +00:00
} ;
}
modItemToAdjust . upd . Repairable . Durability = durabilityValues . Durability ;
modItemToAdjust . upd . Repairable . MaxDurability = durabilityValues . MaxDurability ;
2024-01-13 20:47:59 +00:00
// 25% chance to add shots to visor when its below max durability
2024-02-02 13:54:07 -05:00
if (
this . randomUtil . getChance100 ( 25 )
&& modItemToAdjust . parentId === BaseClasses . ARMORED_EQUIPMENT
&& modItemToAdjust . slotId === "mod_equipment_000"
&& modItemToAdjust . upd . Repairable . Durability < modItemDbDetails . _props . MaxDurability
)
{ // Is damaged
modItemToAdjust . upd . FaceShield = { Hits : this.randomUtil.getInt ( 1 , 3 ) } ;
2024-01-13 20:47:59 +00:00
}
2024-01-07 22:11:05 +00:00
}
}
2023-03-03 15:23:46 +00:00
2024-01-20 16:20:39 +00:00
// Check for and adjust plate durability values
2024-02-02 13:54:07 -05:00
const plateSlots = itemDbDetails . _props . Slots . filter ( ( slot ) = >
this . itemHelper . isRemovablePlateSlot ( slot . _name )
) ;
2024-01-10 14:47:09 +00:00
if ( plateSlots . length > 0 )
2024-01-07 22:11:05 +00:00
{
for ( const plateSlot of plateSlots )
{
2024-01-07 23:11:18 +00:00
// Chance to not add plate
if ( ! this . randomUtil . getChance100 ( this . traderConfig . fence . chancePlateExistsInArmorPercent ) )
2024-01-07 22:11:05 +00:00
{
continue ;
}
2024-01-07 23:11:18 +00:00
2024-02-02 13:54:07 -05:00
const plateTpl = plateSlot . _props . filters [ 0 ] . Plate ;
2024-01-10 14:47:09 +00:00
if ( ! plateTpl )
{
2024-01-14 10:09:43 +00:00
// Bsg data lacks a default plate, skip adding mod
2024-01-10 14:47:09 +00:00
continue ;
}
const modItemDbDetails = this . itemHelper . getItem ( plateTpl ) [ 1 ] ;
2024-01-20 16:20:39 +00:00
const durabilityValues = this . getRandomisedArmorDurabilityValues (
modItemDbDetails ,
2024-02-02 13:54:07 -05:00
this . traderConfig . fence . armorMaxDurabilityPercentMinMax ,
) ;
2024-01-20 16:20:39 +00:00
// Find items mod to apply dura changes to
2024-02-02 13:54:07 -05:00
const modItemToAdjust = armor . find ( ( mod ) = > mod . slotId . toLowerCase ( ) === plateSlot . _name . toLowerCase ( ) ) ;
2024-01-20 16:20:39 +00:00
if ( ! modItemToAdjust . upd )
{
2024-02-02 13:54:07 -05:00
modItemToAdjust . upd = { } ;
2024-01-20 16:20:39 +00:00
}
if ( ! modItemToAdjust . upd . Repairable )
{
modItemToAdjust . upd . Repairable = {
Durability : modItemDbDetails._props.MaxDurability ,
2024-02-02 13:54:07 -05:00
MaxDurability : modItemDbDetails._props.MaxDurability ,
2024-01-20 16:20:39 +00:00
} ;
}
modItemToAdjust . upd . Repairable . Durability = durabilityValues . Durability ;
modItemToAdjust . upd . Repairable . MaxDurability = durabilityValues . MaxDurability ;
2023-03-03 15:23:46 +00:00
}
}
}
2023-07-20 12:00:24 +01:00
/ * *
2023-10-28 19:39:45 +01:00
* Get stack size of a singular item ( no mods )
2023-07-20 12:00:24 +01:00
* @param itemDbDetails item being added to fence
* @returns Stack size
* /
protected getSingleItemStackCount ( itemDbDetails : ITemplateItem ) : number
{
if ( this . itemHelper . isOfBaseclass ( itemDbDetails . _id , BaseClasses . AMMO ) )
{
2024-02-08 21:58:47 +00:00
const overrideValues = this . traderConfig . fence . itemStackSizeOverrideMinMax [ itemDbDetails . _parent ] ;
if ( overrideValues )
{
return this . randomUtil . getInt ( overrideValues . min , overrideValues . max ) ;
}
2023-07-20 12:00:24 +01:00
// No override, use stack max size from item db
2023-10-28 19:39:45 +01:00
return itemDbDetails . _props . StackMaxSize === 1
? 1
: this . randomUtil . getInt ( itemDbDetails . _props . StackMinRandom , itemDbDetails . _props . StackMaxRandom ) ;
2023-07-20 12:00:24 +01:00
}
2023-11-15 20:35:05 -05:00
2024-02-08 21:58:47 +00:00
// Check for override in config, use values if exists
2024-03-05 10:43:47 +00:00
let overrideValues = this . traderConfig . fence . itemStackSizeOverrideMinMax [ itemDbDetails . _id ] ;
if ( overrideValues )
{
return this . randomUtil . getInt ( overrideValues . min , overrideValues . max ) ;
}
// Check for parent override
overrideValues = this . traderConfig . fence . itemStackSizeOverrideMinMax [ itemDbDetails . _parent ] ;
2024-02-08 21:58:47 +00:00
if ( overrideValues )
{
return this . randomUtil . getInt ( overrideValues . min , overrideValues . max ) ;
}
2023-07-20 12:00:24 +01:00
return 1 ;
}
2023-10-10 11:03:20 +00:00
/ * *
* Remove parts of a weapon prior to being listed on flea
2024-01-11 17:42:58 +00:00
* @param itemAndMods Weapon to remove parts from
2023-10-10 11:03:20 +00:00
* /
2024-01-11 17:42:58 +00:00
protected removeRandomModsOfItem ( itemAndMods : Item [ ] ) : void
2023-10-10 11:03:20 +00:00
{
// Items to be removed from inventory
const toDelete : string [ ] = [ ] ;
2024-01-11 17:42:58 +00:00
// Find mods to remove from item that could've been scavenged by other players in-raid
for ( const itemMod of itemAndMods )
2023-10-10 11:03:20 +00:00
{
2024-01-11 17:42:58 +00:00
if ( this . presetModItemWillBeRemoved ( itemMod , toDelete ) )
2023-10-10 11:03:20 +00:00
{
// Skip if not an item
2024-01-11 17:42:58 +00:00
const itemDbDetails = this . itemHelper . getItem ( itemMod . _tpl ) ;
2023-10-10 11:03:20 +00:00
if ( ! itemDbDetails [ 0 ] )
{
continue ;
}
// Remove item and its sub-items to prevent orphans
2024-01-11 17:42:58 +00:00
toDelete . push ( . . . this . itemHelper . findAndReturnChildrenByItems ( itemAndMods , itemMod . _id ) ) ;
2023-10-10 11:03:20 +00:00
}
}
// Reverse loop and remove items
2024-01-11 17:42:58 +00:00
for ( let index = itemAndMods . length - 1 ; index >= 0 ; -- index )
2023-10-10 11:03:20 +00:00
{
2024-01-11 17:42:58 +00:00
if ( toDelete . includes ( itemAndMods [ index ] . _id ) )
2023-10-10 11:03:20 +00:00
{
2024-01-11 17:42:58 +00:00
itemAndMods . splice ( index , 1 ) ;
2023-10-10 11:03:20 +00:00
}
}
}
/ * *
* Roll % chance check to see if item should be removed
* @param weaponMod Weapon mod being checked
* @param itemsBeingDeleted Current list of items on weapon being deleted
* @returns True if item will be removed
* /
protected presetModItemWillBeRemoved ( weaponMod : Item , itemsBeingDeleted : string [ ] ) : boolean
{
const slotIdsThatCanFail = this . traderConfig . fence . presetSlotsToRemoveChancePercent ;
const removalChance = slotIdsThatCanFail [ weaponMod . slotId ] ;
if ( ! removalChance )
{
return false ;
}
// Roll from 0 to 9999, then divide it by 100: 9999 = 99.99%
const randomChance = this . randomUtil . getInt ( 0 , 9999 ) / 100 ;
2023-11-15 20:35:05 -05:00
2024-02-08 21:58:47 +00:00
return removalChance > randomChance && ! itemsBeingDeleted . includes ( weaponMod . _id ) ;
2023-10-10 11:03:20 +00:00
}
2023-03-03 15:23:46 +00:00
/ * *
* Randomise items ' upd properties e . g . med packs / weapons / armor
* @param itemDetails Item being randomised
* @param itemToAdjust Item being edited
* /
protected randomiseItemUpdProperties ( itemDetails : ITemplateItem , itemToAdjust : Item ) : void
{
2023-08-04 08:49:22 +01:00
if ( ! itemDetails . _props )
{
2023-11-15 20:35:05 -05:00
this . logger . error (
` Item ${ itemDetails . _name } lacks a _props field, unable to randomise item: ${ itemToAdjust . _id } ` ,
) ;
2023-08-04 08:49:22 +01:00
return ;
}
2023-03-03 15:23:46 +00:00
// Randomise hp resource of med items
if ( "MaxHpResource" in itemDetails . _props && itemDetails . _props . MaxHpResource > 0 )
{
2023-11-15 20:35:05 -05:00
itemToAdjust . upd . MedKit = { HpResource : this.randomUtil.getInt ( 1 , itemDetails . _props . MaxHpResource ) } ;
2023-03-03 15:23:46 +00:00
}
// Randomise armor durability
2023-11-15 20:35:05 -05:00
if (
2024-01-20 16:20:39 +00:00
( itemDetails . _parent === BaseClasses . ARMORED_EQUIPMENT
2024-01-08 17:43:19 +00:00
|| itemDetails . _parent === BaseClasses . FACECOVER
2024-02-02 13:54:07 -05:00
|| itemDetails . _parent === BaseClasses . ARMOR_PLATE ) && itemDetails . _props . MaxDurability > 0
2023-11-15 20:35:05 -05:00
)
2023-03-03 15:23:46 +00:00
{
2024-02-02 13:54:07 -05:00
const values = this . getRandomisedArmorDurabilityValues (
itemDetails ,
this . traderConfig . fence . armorMaxDurabilityPercentMinMax ,
) ;
2024-01-07 22:11:05 +00:00
itemToAdjust . upd . Repairable = { Durability : values.Durability , MaxDurability : values.MaxDurability } ;
2023-03-03 15:23:46 +00:00
return ;
}
// Randomise Weapon durability
if ( this . itemHelper . isOfBaseclass ( itemDetails . _id , BaseClasses . WEAPON ) )
{
2024-02-10 09:57:44 +00:00
const weaponDurabilityLimits = this . traderConfig . fence . weaponDurabilityPercentMinMax ;
const maxDuraMin = weaponDurabilityLimits . max . min / 100 * itemDetails . _props . MaxDurability ;
const maxDuraMax = weaponDurabilityLimits . max . max / 100 * itemDetails . _props . MaxDurability ;
const chosenMaxDurability = this . randomUtil . getInt ( maxDuraMin , maxDuraMax ) ;
const currentDuraMin = weaponDurabilityLimits . current . min / 100 * itemDetails . _props . MaxDurability ;
const currentDuraMax = weaponDurabilityLimits . current . max / 100 * itemDetails . _props . MaxDurability ;
const currentDurability = Math . min (
this . randomUtil . getInt ( currentDuraMin , currentDuraMax ) ,
chosenMaxDurability ,
) ;
2023-03-03 15:23:46 +00:00
2024-02-10 09:57:44 +00:00
itemToAdjust . upd . Repairable = { Durability : currentDurability , MaxDurability : chosenMaxDurability } ;
2023-03-03 15:23:46 +00:00
return ;
}
2023-10-10 11:03:20 +00:00
if ( this . itemHelper . isOfBaseclass ( itemDetails . _id , BaseClasses . REPAIR_KITS ) )
{
2023-11-15 20:35:05 -05:00
itemToAdjust . upd . RepairKit = { Resource : this.randomUtil.getInt ( 1 , itemDetails . _props . MaxRepairResource ) } ;
2023-10-10 11:03:20 +00:00
return ;
}
// Mechanical key + has limited uses
2023-11-15 20:35:05 -05:00
if (
this . itemHelper . isOfBaseclass ( itemDetails . _id , BaseClasses . KEY_MECHANICAL )
&& itemDetails . _props . MaximumNumberOfUsage > 1
)
2023-10-10 11:03:20 +00:00
{
itemToAdjust . upd . Key = {
2023-11-15 20:35:05 -05:00
NumberOfUsages : this.randomUtil.getInt ( 0 , itemDetails . _props . MaximumNumberOfUsage - 1 ) ,
2023-10-10 11:03:20 +00:00
} ;
return ;
}
// Randomise items that use resources (e.g. fuel)
2023-03-03 15:23:46 +00:00
if ( itemDetails . _props . MaxResource > 0 )
{
const resourceMax = itemDetails . _props . MaxResource ;
const resourceCurrent = this . randomUtil . getInt ( 1 , itemDetails . _props . MaxResource ) ;
2023-11-15 20:35:05 -05:00
itemToAdjust . upd . Resource = { Value : resourceMax - resourceCurrent , UnitsConsumed : resourceCurrent } ;
2023-03-03 15:23:46 +00:00
}
}
2024-01-15 12:41:54 +00:00
/ * *
* Generate a randomised current and max durabiltiy value for an armor item
* @param itemDetails Item to create values for
2024-02-10 09:57:44 +00:00
* @param equipmentDurabilityLimits Max durabiltiy percent min / max values
2024-01-15 12:41:54 +00:00
* @returns Durability + MaxDurability values
* /
2024-02-02 13:54:07 -05:00
protected getRandomisedArmorDurabilityValues (
itemDetails : ITemplateItem ,
2024-02-10 09:57:44 +00:00
equipmentDurabilityLimits : IItemDurabilityCurrentMax ,
2024-02-02 13:54:07 -05:00
) : Repairable
2024-01-07 22:11:05 +00:00
{
2024-02-10 09:57:44 +00:00
const maxDuraMin = equipmentDurabilityLimits . max . min / 100 * itemDetails . _props . MaxDurability ;
const maxDuraMax = equipmentDurabilityLimits . max . max / 100 * itemDetails . _props . MaxDurability ;
const chosenMaxDurability = this . randomUtil . getInt ( maxDuraMin , maxDuraMax ) ;
const currentDuraMin = equipmentDurabilityLimits . current . min / 100 * itemDetails . _props . MaxDurability ;
const currentDuraMax = equipmentDurabilityLimits . current . max / 100 * itemDetails . _props . MaxDurability ;
const chosenCurrentDurability = Math . min (
this . randomUtil . getInt ( currentDuraMin , currentDuraMax ) ,
chosenMaxDurability ,
) ;
2024-01-07 22:11:05 +00:00
2024-02-10 09:57:44 +00:00
return { Durability : chosenCurrentDurability , MaxDurability : chosenMaxDurability } ;
2024-01-07 22:11:05 +00:00
}
2023-03-03 15:23:46 +00:00
/ * *
* Construct item limit record to hold max and current item count
* @param limits limits as defined in config
* @returns record , key : item tplId , value : current / max item count allowed
* /
2023-11-15 20:35:05 -05:00
protected initItemLimitCounter ( limits : Record < string , number > ) : Record < string , { current : number ; max : number ; } >
2023-03-03 15:23:46 +00:00
{
2023-11-15 20:35:05 -05:00
const itemTypeCounts : Record < string , { current : number ; max : number ; } > = { } ;
2023-03-03 15:23:46 +00:00
for ( const x in limits )
{
2023-11-15 20:35:05 -05:00
itemTypeCounts [ x ] = { current : 0 , max : limits [ x ] } ;
2023-03-03 15:23:46 +00:00
}
return itemTypeCounts ;
}
/ * *
* Get the next update timestamp for fence
* @returns future timestamp
* /
public getNextFenceUpdateTimestamp ( ) : number
{
const time = this . timeUtil . getTimestamp ( ) ;
const updateSeconds = this . getFenceRefreshTime ( ) ;
return time + updateSeconds ;
}
/ * *
* Get fence refresh time in seconds
2024-01-15 12:41:54 +00:00
* @returns Refresh time in seconds
2023-03-03 15:23:46 +00:00
* /
protected getFenceRefreshTime ( ) : number
{
2023-11-15 20:35:05 -05:00
return this . traderConfig . updateTime . find ( ( x ) = > x . traderId === Traders . FENCE ) . seconds ;
2023-03-03 15:23:46 +00:00
}
/ * *
* Get fence level the passed in profile has
* @param pmcData Player profile
* @returns FenceLevel object
* /
2023-07-18 15:44:14 +01:00
public getFenceInfo ( pmcData : IPmcData ) : IFenceLevel
2023-03-03 15:23:46 +00:00
{
const fenceSettings = this . databaseServer . getTables ( ) . globals . config . FenceSettings ;
const pmcFenceInfo = pmcData . TradersInfo [ fenceSettings . FenceId ] ;
if ( ! pmcFenceInfo )
{
return fenceSettings . Levels [ "0" ] ;
}
const fenceLevels = ( Object . keys ( fenceSettings . Levels ) ) . map ( ( value ) = > Number . parseInt ( value ) ) ;
const minLevel = Math . min ( . . . fenceLevels ) ;
const maxLevel = Math . max ( . . . fenceLevels ) ;
const pmcFenceLevel = Math . floor ( pmcFenceInfo . standing ) ;
if ( pmcFenceLevel < minLevel )
{
return fenceSettings . Levels [ minLevel . toString ( ) ] ;
}
if ( pmcFenceLevel > maxLevel )
{
return fenceSettings . Levels [ maxLevel . toString ( ) ] ;
}
return fenceSettings . Levels [ pmcFenceLevel . toString ( ) ] ;
}
/ * *
2024-02-02 15:56:37 +00:00
* Remove or lower stack size of an assort from fence by id
* @param assortId assort id to adjust
* @param buyCount Count of items bought
2023-03-03 15:23:46 +00:00
* /
2024-02-02 15:56:37 +00:00
public amendOrRemoveFenceOffer ( assortId : string , buyCount : number ) : void
{
2024-02-02 17:22:12 +00:00
let isNormalAssort = true ;
2024-02-02 13:54:07 -05:00
let fenceAssortItem = this . fenceAssort . items . find ( ( item ) = > item . _id === assortId ) ;
2024-02-02 15:56:37 +00:00
if ( ! fenceAssortItem )
{
// Not in main assorts, check secondary section
2024-02-02 13:54:07 -05:00
fenceAssortItem = this . fenceDiscountAssort . items . find ( ( item ) = > item . _id === assortId ) ;
2024-02-02 15:56:37 +00:00
if ( ! fenceAssortItem )
{
this . logger . error ( ` Offer with id: ${ assortId } not found ` ) ;
return ;
}
2024-02-02 17:22:12 +00:00
isNormalAssort = false ;
2024-02-02 15:56:37 +00:00
}
// Player wants to buy whole stack, delete stack
if ( fenceAssortItem . upd . StackObjectsCount === buyCount )
{
2024-02-02 17:22:12 +00:00
this . deleteOffer ( assortId , isNormalAssort ? this . fenceAssort.items : this.fenceDiscountAssort.items ) ;
2024-02-02 15:56:37 +00:00
return ;
}
// Adjust stack size
fenceAssortItem . upd . StackObjectsCount -= buyCount ;
}
protected deleteOffer ( assortId : string , assorts : Item [ ] ) : void
2023-03-03 15:23:46 +00:00
{
2024-01-08 11:34:52 +00:00
// Assort could have child items, remove those too
2024-02-02 15:56:37 +00:00
const itemWithChildrenToRemove = this . itemHelper . findAndReturnChildrenAsItems ( assorts , assortId ) ;
2024-01-08 11:34:52 +00:00
for ( const itemToRemove of itemWithChildrenToRemove )
2023-03-03 15:23:46 +00:00
{
2024-02-02 13:54:07 -05:00
let indexToRemove = assorts . findIndex ( ( item ) = > item . _id === itemToRemove . _id ) ;
2023-03-03 15:23:46 +00:00
2024-01-08 11:34:52 +00:00
// No offer found in main assort, check discount items
if ( indexToRemove === - 1 )
{
2024-02-02 15:56:37 +00:00
indexToRemove = this . fenceDiscountAssort . items . findIndex ( ( item ) = > item . _id === itemToRemove . _id ) ;
2024-01-08 11:34:52 +00:00
this . fenceDiscountAssort . items . splice ( indexToRemove , 1 ) ;
2023-03-03 15:23:46 +00:00
2024-01-08 11:34:52 +00:00
if ( indexToRemove === - 1 )
{
2024-02-02 13:54:07 -05:00
this . logger . warning (
` unable to remove fence assort item: ${ itemToRemove . _id } tpl: ${ itemToRemove . _tpl } ` ,
) ;
2024-01-08 11:34:52 +00:00
}
return ;
}
// Remove offer from assort
2024-02-02 15:56:37 +00:00
assorts . splice ( indexToRemove , 1 ) ;
2024-01-08 11:34:52 +00:00
}
2023-03-03 15:23:46 +00:00
}
2023-11-15 20:35:05 -05:00
}