2023-03-03 15:23:46 +00:00
import { inject , injectable } from "tsyringe" ;
2023-10-19 17:21:17 +00:00
import { BotGeneratorHelper , ExhaustableArray } from "@spt-aki/helpers/BotGeneratorHelper" ;
import { BotHelper } from "@spt-aki/helpers/BotHelper" ;
import { BotWeaponGeneratorHelper } from "@spt-aki/helpers/BotWeaponGeneratorHelper" ;
import { ItemHelper } from "@spt-aki/helpers/ItemHelper" ;
2024-01-12 17:00:22 +00:00
import { PresetHelper } from "@spt-aki/helpers/PresetHelper" ;
2023-10-19 17:21:17 +00:00
import { ProbabilityHelper } from "@spt-aki/helpers/ProbabilityHelper" ;
import { ProfileHelper } from "@spt-aki/helpers/ProfileHelper" ;
2024-01-07 15:34:59 +00:00
import { WeightedRandomHelper } from "@spt-aki/helpers/WeightedRandomHelper" ;
2023-10-19 17:21:17 +00:00
import { Mods , ModsChances } from "@spt-aki/models/eft/common/tables/IBotType" ;
import { Item } from "@spt-aki/models/eft/common/tables/IItem" ;
import { ITemplateItem , Slot } from "@spt-aki/models/eft/common/tables/ITemplateItem" ;
import { BaseClasses } from "@spt-aki/models/enums/BaseClasses" ;
import { ConfigTypes } from "@spt-aki/models/enums/ConfigTypes" ;
import { EquipmentFilterDetails , IBotConfig } from "@spt-aki/models/spt/config/IBotConfig" ;
import { ILogger } from "@spt-aki/models/spt/utils/ILogger" ;
import { ConfigServer } from "@spt-aki/servers/ConfigServer" ;
import { DatabaseServer } from "@spt-aki/servers/DatabaseServer" ;
import { BotEquipmentFilterService } from "@spt-aki/services/BotEquipmentFilterService" ;
import { BotEquipmentModPoolService } from "@spt-aki/services/BotEquipmentModPoolService" ;
import { BotModLimits , BotWeaponModLimitService } from "@spt-aki/services/BotWeaponModLimitService" ;
import { ItemFilterService } from "@spt-aki/services/ItemFilterService" ;
import { LocalisationService } from "@spt-aki/services/LocalisationService" ;
import { HashUtil } from "@spt-aki/utils/HashUtil" ;
import { JsonUtil } from "@spt-aki/utils/JsonUtil" ;
import { RandomUtil } from "@spt-aki/utils/RandomUtil" ;
2024-01-07 14:46:25 +00:00
import { IGenerateEquipmentProperties } from "./BotInventoryGenerator" ;
2024-01-12 17:00:22 +00:00
import { IFilterPlateModsForSlotByLevelResult , Result } from "./IFilterPlateModsForSlotByLevelResult" ;
2023-03-03 15:23:46 +00:00
@injectable ( )
export class BotEquipmentModGenerator
{
protected botConfig : IBotConfig ;
constructor (
@inject ( "WinstonLogger" ) protected logger : ILogger ,
@inject ( "JsonUtil" ) protected jsonUtil : JsonUtil ,
@inject ( "HashUtil" ) protected hashUtil : HashUtil ,
@inject ( "RandomUtil" ) protected randomUtil : RandomUtil ,
@inject ( "ProbabilityHelper" ) protected probabilityHelper : ProbabilityHelper ,
@inject ( "DatabaseServer" ) protected databaseServer : DatabaseServer ,
@inject ( "ItemHelper" ) protected itemHelper : ItemHelper ,
@inject ( "BotEquipmentFilterService" ) protected botEquipmentFilterService : BotEquipmentFilterService ,
@inject ( "ItemFilterService" ) protected itemFilterService : ItemFilterService ,
@inject ( "ProfileHelper" ) protected profileHelper : ProfileHelper ,
@inject ( "BotWeaponModLimitService" ) protected botWeaponModLimitService : BotWeaponModLimitService ,
@inject ( "BotHelper" ) protected botHelper : BotHelper ,
@inject ( "BotGeneratorHelper" ) protected botGeneratorHelper : BotGeneratorHelper ,
@inject ( "BotWeaponGeneratorHelper" ) protected botWeaponGeneratorHelper : BotWeaponGeneratorHelper ,
2024-01-07 15:34:59 +00:00
@inject ( "WeightedRandomHelper" ) protected weightedRandomHelper : WeightedRandomHelper ,
2024-01-12 17:00:22 +00:00
@inject ( "PresetHelper" ) protected presetHelper : PresetHelper ,
2023-03-03 15:23:46 +00:00
@inject ( "LocalisationService" ) protected localisationService : LocalisationService ,
@inject ( "BotEquipmentModPoolService" ) protected botEquipmentModPoolService : BotEquipmentModPoolService ,
2023-11-13 11:05:05 -05:00
@inject ( "ConfigServer" ) protected configServer : ConfigServer ,
2023-03-03 15:23:46 +00:00
)
{
this . botConfig = this . configServer . getConfig ( ConfigTypes . BOT ) ;
}
2023-11-13 11:05:05 -05:00
2023-03-03 15:23:46 +00:00
/ * *
* Check mods are compatible and add to array
* @param equipment Equipment item to add mods to
* @param modPool Mod list to choose frm
* @param parentId parentid of item to add mod to
* @param parentTemplate template objet of item to add mods to
* @param forceSpawn should this mod be forced to spawn
* @returns Item + compatible mods as an array
* /
2023-11-13 11:05:05 -05:00
public generateModsForEquipment (
equipment : Item [ ] ,
parentId : string ,
parentTemplate : ITemplateItem ,
2024-01-07 14:46:25 +00:00
settings : IGenerateEquipmentProperties ,
2023-11-13 11:05:05 -05:00
forceSpawn = false ,
) : Item [ ]
2023-03-03 15:23:46 +00:00
{
2024-01-07 14:46:25 +00:00
const compatibleModsPool = settings . modPool [ parentTemplate . _id ] ;
2023-12-28 18:36:37 +00:00
if ( ! compatibleModsPool )
{
2024-01-07 14:46:25 +00:00
this . logger . warning ( ` bot: ${ settings . botRole } lacks a mod slot pool for item: ${ parentTemplate . _id } ${ parentTemplate . _name } ` ) ;
2023-12-28 18:36:37 +00:00
}
2023-03-03 15:23:46 +00:00
// Iterate over mod pool and choose mods to add to item
for ( const modSlot in compatibleModsPool )
{
const itemSlot = this . getModItemSlot ( modSlot , parentTemplate ) ;
if ( ! itemSlot )
{
2023-11-13 11:05:05 -05:00
this . logger . error (
this . localisationService . getText ( "bot-mod_slot_missing_from_item" , {
modSlot : modSlot ,
parentId : parentTemplate._id ,
parentName : parentTemplate._name ,
2024-01-07 14:46:25 +00:00
botRole : settings.botRole
2023-11-13 11:05:05 -05:00
} ) ,
) ;
2023-03-03 15:23:46 +00:00
continue ;
}
2024-01-09 20:16:49 +00:00
if ( ! ( this . shouldModBeSpawned ( itemSlot , modSlot . toLowerCase ( ) , settings . spawnChances . equipmentMods ) || forceSpawn ) )
2023-03-03 15:23:46 +00:00
{
continue ;
}
// Ensure submods for nvgs all spawn together
2023-12-27 23:29:37 +00:00
if ( modSlot === "mod_nvg" )
{
forceSpawn = true ;
}
2023-03-03 15:23:46 +00:00
2024-01-12 17:00:22 +00:00
let modPoolToChooseFrom = compatibleModsPool [ modSlot ] ;
if ( settings . botEquipmentConfig . filterPlatesByLevel && this . itemHelper . isRemovablePlateSlot ( modSlot . toLowerCase ( ) ) )
{
const outcome = this . filterPlateModsForSlotByLevel ( settings , modSlot . toLowerCase ( ) , compatibleModsPool [ modSlot ] , parentTemplate ) ;
if ( [ Result . UNKNOWN_FAILURE , Result . NO_DEFAULT_FILTER ] . includes ( outcome . result ) )
{
this . logger . warning ( ` Plate slot: ${ modSlot } selection for armor: ${ parentTemplate . _id } failed: ${ Result [ outcome . result ] } , skipping ` ) ;
2024-01-07 14:46:25 +00:00
2024-01-12 17:00:22 +00:00
continue ;
}
if ( [ Result . LACKS_PLATE_WEIGHTS ] . includes ( outcome . result ) )
{
this . logger . warning ( ` Plate slot: ${ modSlot } lacks weights for armor: ${ parentTemplate . _id } , unable to adjust plate choice, using existing data ` ) ;
}
modPoolToChooseFrom = outcome . plateModTpls ;
}
2023-11-13 11:05:05 -05:00
2023-03-03 15:23:46 +00:00
// Find random mod and check its compatible
2024-01-12 17:00:22 +00:00
let modTpl : string ;
let found = false ;
2023-11-13 11:05:05 -05:00
const exhaustableModPool = new ExhaustableArray (
2024-01-07 14:46:25 +00:00
modPoolToChooseFrom ,
2023-11-13 11:05:05 -05:00
this . randomUtil ,
this . jsonUtil ,
) ;
2023-03-03 15:23:46 +00:00
while ( exhaustableModPool . hasValues ( ) )
{
modTpl = exhaustableModPool . getRandomValue ( ) ;
2023-11-13 11:05:05 -05:00
if (
! this . botGeneratorHelper . isItemIncompatibleWithCurrentItems ( equipment , modTpl , modSlot ) . incompatible
)
2023-03-03 15:23:46 +00:00
{
found = true ;
break ;
}
}
2023-11-13 11:05:05 -05:00
// Compatible item not found but slot REQUIRES item, get random item from db
const parentSlot = parentTemplate . _props . Slots . find ( ( i ) = > i . _name === modSlot ) ;
2023-03-03 15:23:46 +00:00
if ( ! found && parentSlot !== undefined && parentSlot . _required )
{
modTpl = this . getModTplFromItemDb ( modTpl , parentSlot , modSlot , equipment ) ;
found = ! ! modTpl ;
}
// Compatible item not found + not required
if ( ! found && parentSlot !== undefined && ! parentSlot . _required )
{
2023-11-13 11:05:05 -05:00
// Don't add item
2023-03-03 15:23:46 +00:00
continue ;
}
const modTemplate = this . itemHelper . getItem ( modTpl ) ;
2024-01-07 14:46:25 +00:00
if ( ! this . isModValidForSlot ( modTemplate , itemSlot , modSlot , parentTemplate , settings . botRole ) )
2023-03-03 15:23:46 +00:00
{
continue ;
}
const modId = this . hashUtil . generate ( ) ;
2024-01-07 14:46:25 +00:00
equipment . push ( this . createModItem ( modId , modTpl , parentId , modSlot , modTemplate [ 1 ] , settings . botRole ) ) ;
2023-03-03 15:23:46 +00:00
2024-01-12 17:00:22 +00:00
// Does the item being added have possible child mods?
2024-01-07 14:46:25 +00:00
if ( Object . keys ( settings . modPool ) . includes ( modTpl ) )
2023-03-03 15:23:46 +00:00
{
2024-01-12 17:00:22 +00:00
// Call self recursively with item being checkced item we just added to bot
2023-11-13 11:05:05 -05:00
this . generateModsForEquipment (
equipment ,
modId ,
modTemplate [ 1 ] ,
2024-01-07 14:46:25 +00:00
settings ,
2023-11-13 11:05:05 -05:00
forceSpawn ,
) ;
2023-03-03 15:23:46 +00:00
}
}
return equipment ;
}
2024-01-07 15:34:59 +00:00
/ * *
* Filter a bots plate pool based on its current level
* @param settings Bot equipment generation settings
2024-01-12 17:00:22 +00:00
* @param modSlot Armor slot being filtered
* @param existingPlateTplPool Plates tpls to choose from
2024-01-07 15:34:59 +00:00
* @param armorItem
* @returns Array of plate tpls to choose from
* /
2024-01-12 17:00:22 +00:00
protected filterPlateModsForSlotByLevel ( settings : IGenerateEquipmentProperties , modSlot : string , existingPlateTplPool : string [ ] , armorItem : ITemplateItem ) : IFilterPlateModsForSlotByLevelResult
2024-01-07 14:46:25 +00:00
{
2024-01-12 17:00:22 +00:00
const result : IFilterPlateModsForSlotByLevelResult = {
result : Result.UNKNOWN_FAILURE ,
plateModTpls : null
} ;
2024-01-07 14:46:25 +00:00
// Not pmc or not a plate slot, return original mod pool array
2024-01-10 14:47:09 +00:00
if ( ! this . itemHelper . isRemovablePlateSlot ( modSlot ) )
2024-01-07 15:34:59 +00:00
{
2024-01-12 17:00:22 +00:00
result . result = Result . NOT_PLATE_HOLDING_SLOT ;
result . plateModTpls = existingPlateTplPool ;
return result ;
2024-01-07 15:34:59 +00:00
}
// Get the front/back/side weights based on bots level
const plateSlotWeights = settings
. botEquipmentConfig
? . armorPlateWeighting
? . find ( x = > settings . botLevel >= x . levelRange . min && settings . botLevel <= x . levelRange . max ) ;
if ( ! plateSlotWeights )
{
2024-01-12 17:00:22 +00:00
// No weights, return original array of plate tpls
result . result = Result . LACKS_PLATE_WEIGHTS ;
result . plateModTpls = existingPlateTplPool ;
return result ;
2024-01-07 15:34:59 +00:00
}
// Get the specific plate slot weights (front/back/side)
const plateWeights : Record < string , number > = plateSlotWeights [ modSlot ] ;
if ( ! plateWeights )
2024-01-07 14:46:25 +00:00
{
2024-01-12 17:00:22 +00:00
// No weights, return original array of plate tpls
result . result = Result . LACKS_PLATE_WEIGHTS ;
result . plateModTpls = existingPlateTplPool ;
2024-01-07 15:34:59 +00:00
2024-01-12 17:00:22 +00:00
return result ;
2024-01-07 14:46:25 +00:00
}
2024-01-07 15:34:59 +00:00
// Choose a plate level based on weighting
const chosenArmorPlateLevel = this . weightedRandomHelper . getWeightedValue < string > ( plateWeights ) ;
// Convert the array of ids into database items
2024-01-12 17:00:22 +00:00
const platesFromDb = existingPlateTplPool . map ( x = > this . itemHelper . getItem ( x ) [ 1 ] ) ;
2024-01-07 15:34:59 +00:00
// Filter plates to the chosen level based on its armorClass property
const filteredPlates = platesFromDb . filter ( x = > x . _props . armorClass === chosenArmorPlateLevel ) ;
2024-01-09 23:46:57 +00:00
if ( filteredPlates . length === 0 )
2024-01-07 15:34:59 +00:00
{
2024-01-12 17:00:22 +00:00
this . logger . debug ( ` Plate filter was too restrictive for armor: ${ armorItem . _id } , unable to find plates of level: ${ chosenArmorPlateLevel } . Using mod items default plate ` ) ;
2024-01-07 15:34:59 +00:00
2024-01-09 23:46:57 +00:00
const relatedItemDbModSlot = armorItem . _props . Slots . find ( slot = > slot . _name . toLowerCase ( ) === modSlot ) ;
2024-01-12 17:00:22 +00:00
if ( ! relatedItemDbModSlot . _props . filters [ 0 ] . Plate )
{
// No relevant plate found after filtering AND no default plate
// Last attempt, get default preset and see if it has a plate default
const defaultPreset = this . presetHelper . getDefaultPreset ( armorItem . _id ) ;
if ( defaultPreset )
{
const relatedPresetSlot = defaultPreset . _items . find ( item = > item . slotId ? . toLowerCase ( ) === modSlot ) ;
if ( relatedPresetSlot )
{
result . result = Result . SUCCESS ;
result . plateModTpls = [ relatedPresetSlot . _tpl ] ;
return result ;
}
}
result . result = Result . NO_DEFAULT_FILTER ;
return result ;
}
result . result = Result . SUCCESS ;
result . plateModTpls = [ relatedItemDbModSlot . _props . filters [ 0 ] . Plate ] ;
return result ;
2024-01-07 15:34:59 +00:00
}
2024-01-07 14:46:25 +00:00
2024-01-07 15:34:59 +00:00
// Only return the items ids
2024-01-12 17:00:22 +00:00
result . result = Result . SUCCESS ;
result . plateModTpls = filteredPlates . map ( x = > x . _id ) ;
2024-01-07 14:46:25 +00:00
2024-01-12 17:00:22 +00:00
return result ;
2024-01-07 14:46:25 +00:00
}
2023-03-03 15:23:46 +00:00
/ * *
* Add mods to a weapon using the provided mod pool
* @param sessionId session id
* @param weapon Weapon to add mods to
* @param modPool Pool of compatible mods to attach to weapon
* @param weaponParentId parentId of weapon
* @param parentTemplate Weapon which mods will be generated on
* @param modSpawnChances Mod spawn chances
* @param ammoTpl Ammo tpl to use when generating magazines / cartridges
* @param botRole Role of bot weapon is generated for
2023-11-13 11:05:05 -05:00
* @param botLevel Level of the bot weapon is being generated for
* @param modLimits limits placed on certain mod types per gun
2023-03-03 15:23:46 +00:00
* @param botEquipmentRole role of bot when accessing bot . json equipment config settings
* @returns Weapon + mods array
* /
public generateModsForWeapon (
sessionId : string ,
weapon : Item [ ] ,
modPool : Mods ,
weaponParentId : string ,
parentTemplate : ITemplateItem ,
modSpawnChances : ModsChances ,
ammoTpl : string ,
botRole : string ,
botLevel : number ,
modLimits : BotModLimits ,
2023-11-13 11:05:05 -05:00
botEquipmentRole : string ,
) : Item [ ]
2023-03-03 15:23:46 +00:00
{
const pmcProfile = this . profileHelper . getPmcProfile ( sessionId ) ;
// Get pool of mods that fit weapon
const compatibleModsPool = modPool [ parentTemplate . _id ] ;
2023-11-13 11:05:05 -05:00
if (
2023-11-13 12:29:16 -05:00
! ( ( parentTemplate . _props . Slots . length || parentTemplate . _props . Cartridges ? . length )
|| parentTemplate . _props . Chambers ? . length )
2023-11-13 11:05:05 -05:00
)
2023-03-03 15:23:46 +00:00
{
2023-11-13 11:05:05 -05:00
this . logger . error (
this . localisationService . getText ( "bot-unable_to_add_mods_to_weapon_missing_ammo_slot" , {
weaponName : parentTemplate._name ,
weaponId : parentTemplate._id ,
2023-12-28 10:58:29 +00:00
botRole : botRole
2023-11-13 11:05:05 -05:00
} ) ,
) ;
2023-03-03 15:23:46 +00:00
return weapon ;
}
const botEquipConfig = this . botConfig . equipment [ botEquipmentRole ] ;
2023-11-13 11:05:05 -05:00
const botEquipBlacklist = this . botEquipmentFilterService . getBotEquipmentBlacklist (
botEquipmentRole ,
pmcProfile . Info . Level ,
) ;
2023-03-03 15:23:46 +00:00
const botWeaponSightWhitelist = this . botEquipmentFilterService . getBotWeaponSightWhitelist ( botEquipmentRole ) ;
const randomisationSettings = this . botHelper . getBotRandomizationDetails ( botLevel , botEquipConfig ) ;
2023-10-29 20:45:35 +00:00
// Iterate over mod pool and choose mods to attach
2023-10-10 11:03:20 +00:00
const sortedModKeys = this . sortModKeys ( Object . keys ( compatibleModsPool ) ) ;
2023-03-03 15:23:46 +00:00
for ( const modSlot of sortedModKeys )
{
// Check weapon has slot for mod to fit in
const modsParentSlot = this . getModItemSlot ( modSlot , parentTemplate ) ;
if ( ! modsParentSlot )
{
2023-11-13 11:05:05 -05:00
this . logger . error (
this . localisationService . getText ( "bot-weapon_missing_mod_slot" , {
modSlot : modSlot ,
weaponId : parentTemplate._id ,
weaponName : parentTemplate._name ,
botRole : botRole ,
} ) ,
) ;
2023-03-03 15:23:46 +00:00
continue ;
}
// Check spawn chance of mod
if ( ! this . shouldModBeSpawned ( modsParentSlot , modSlot , modSpawnChances ) )
{
continue ;
}
2023-10-29 20:45:35 +00:00
const isRandomisableSlot = randomisationSettings ? . randomisedWeaponModSlots ? . includes ( modSlot ) ? ? false ;
2023-11-13 11:05:05 -05:00
const modToAdd = this . chooseModToPutIntoSlot (
modSlot ,
isRandomisableSlot ,
botWeaponSightWhitelist ,
botEquipBlacklist ,
compatibleModsPool ,
weapon ,
ammoTpl ,
parentTemplate ,
) ;
2023-03-03 15:23:46 +00:00
// Compatible mod not found
2023-11-13 11:05:05 -05:00
if ( ! modToAdd || typeof modToAdd === "undefined" )
2023-03-03 15:23:46 +00:00
{
continue ;
}
2023-12-28 00:06:45 +00:00
if ( ! this . isModValidForSlot ( modToAdd , modsParentSlot , modSlot , parentTemplate , botRole ) )
2023-03-03 15:23:46 +00:00
{
continue ;
}
2023-10-29 20:45:35 +00:00
const modToAddTemplate = modToAdd [ 1 ] ;
2023-03-03 15:23:46 +00:00
// Skip adding mod to weapon if type limit reached
2023-11-13 11:05:05 -05:00
if (
this . botWeaponModLimitService . weaponModHasReachedLimit (
botEquipmentRole ,
modToAddTemplate ,
modLimits ,
parentTemplate ,
weapon ,
)
)
2023-03-03 15:23:46 +00:00
{
continue ;
}
// If item is a mount for scopes, set scope chance to 100%, this helps fix empty mounts appearing on weapons
if ( this . modSlotCanHoldScope ( modSlot , modToAddTemplate . _parent ) )
{
// mod_mount was picked to be added to weapon, force scope chance to ensure its filled
2023-11-13 12:31:52 -05:00
const scopeSlots = [ "mod_scope" , "mod_scope_000" , "mod_scope_001" , "mod_scope_002" , "mod_scope_003" ] ;
2023-10-10 11:03:20 +00:00
this . adjustSlotSpawnChances ( modSpawnChances , scopeSlots , 100 ) ;
2023-03-03 15:23:46 +00:00
// Hydrate pool of mods that fit into mount as its a randomisable slot
if ( isRandomisableSlot )
{
// Add scope mods to modPool dictionary to ensure the mount has a scope in the pool to pick
this . addCompatibleModsForProvidedMod ( "mod_scope" , modToAddTemplate , modPool , botEquipBlacklist ) ;
}
}
2023-10-10 11:03:20 +00:00
// If picked item is muzzle adapter that can hold a child, adjust spawn chance
if ( this . modSlotCanHoldMuzzleDevices ( modSlot , modToAddTemplate . _parent ) )
{
2023-11-13 12:31:52 -05:00
const muzzleSlots = [ "mod_muzzle" , "mod_muzzle_000" , "mod_muzzle_001" ] ;
2023-10-10 11:03:20 +00:00
// Make chance of muzzle devices 95%, nearly certain but not guaranteed
this . adjustSlotSpawnChances ( modSpawnChances , muzzleSlots , 95 ) ;
}
2023-03-03 15:23:46 +00:00
// If front/rear sight are to be added, set opposite to 100% chance
2023-10-17 20:45:40 +01:00
if ( this . modIsFrontOrRearSight ( modSlot , modToAddTemplate . _id ) )
2023-03-03 15:23:46 +00:00
{
modSpawnChances . mod_sight_front = 100 ;
modSpawnChances . mod_sight_rear = 100 ;
}
2023-10-26 20:31:05 +01:00
// Handguard mod can take a sub handguard mod + weapon has no UBGL (takes same slot)
// Force spawn chance to be 100% to ensure it gets added
2023-11-13 11:05:05 -05:00
if (
2023-11-13 12:29:16 -05:00
modSlot === "mod_handguard" && modToAddTemplate . _props . Slots . find ( ( x ) = > x . _name === "mod_handguard" )
&& ! weapon . find ( ( x ) = > x . slotId === "mod_launcher" )
2023-11-13 11:05:05 -05:00
)
2023-10-26 20:31:05 +01:00
{
// Needed for handguards with lower
modSpawnChances . mod_handguard = 100 ;
}
2023-11-03 17:40:00 +00:00
// If stock mod can take a sub stock mod, force spawn chance to be 100% to ensure sub-stock gets added
// Or if mod_stock is configured to be forced on
2023-11-13 11:05:05 -05:00
if (
2023-11-13 12:31:52 -05:00
modSlot === "mod_stock" && ( modToAddTemplate . _props . Slots . find ( ( x ) = >
2023-11-13 12:29:16 -05:00
x . _name . includes ( "mod_stock" ) || botEquipConfig . forceStock
) )
2023-11-13 11:05:05 -05:00
)
2023-03-03 15:23:46 +00:00
{
// Stock mod can take additional stocks, could be a locking device, force 100% chance
2023-11-03 17:40:00 +00:00
const stockSlots = [ "mod_stock" , "mod_stock_000" , "mod_stock_akms" ] ;
this . adjustSlotSpawnChances ( modSpawnChances , stockSlots , 100 ) ;
2023-03-03 15:23:46 +00:00
}
const modId = this . hashUtil . generate ( ) ;
2023-11-13 11:05:05 -05:00
weapon . push (
this . createModItem ( modId , modToAddTemplate . _id , weaponParentId , modSlot , modToAddTemplate , botRole ) ,
) ;
2023-03-03 15:23:46 +00:00
// I first thought we could use the recursive generateModsForItems as previously for cylinder magazines.
2023-11-13 11:05:05 -05:00
// However, the recursion doesn't go over the slots of the parent mod but over the modPool which is given by the bot config
2023-03-03 15:23:46 +00:00
// where we decided to keep cartridges instead of camoras. And since a CylinderMagazine only has one cartridge entry and
// this entry is not to be filled, we need a special handling for the CylinderMagazine
const modParentItem = this . databaseServer . getTables ( ) . templates . items [ modToAddTemplate . _parent ] ;
if ( this . botWeaponGeneratorHelper . magazineIsCylinderRelated ( modParentItem . _name ) )
{
// We don't have child mods, we need to create the camoras for the magazines instead
this . fillCamora ( weapon , modPool , modId , modToAddTemplate ) ;
}
else
{
let containsModInPool = Object . keys ( modPool ) . includes ( modToAddTemplate . _id ) ;
// Sometimes randomised slots are missing sub-mods, if so, get values from mod pool service
// Check for a randomisable slot + without data in modPool + item being added as additional slots
if ( isRandomisableSlot && ! containsModInPool && modToAddTemplate . _props . Slots . length > 0 )
{
const modFromService = this . botEquipmentModPoolService . getModsForWeaponSlot ( modToAddTemplate . _id ) ;
if ( Object . keys ( modFromService ? ? { } ) . length > 0 )
{
modPool [ modToAddTemplate . _id ] = modFromService ;
containsModInPool = true ;
}
}
if ( containsModInPool )
{
2023-11-13 11:05:05 -05:00
// Call self recursively to add mods to this mod
this . generateModsForWeapon (
sessionId ,
weapon ,
modPool ,
modId ,
modToAddTemplate ,
modSpawnChances ,
ammoTpl ,
botRole ,
botLevel ,
modLimits ,
botEquipmentRole ,
) ;
2023-03-03 15:23:46 +00:00
}
}
}
return weapon ;
}
/ * *
* Is this modslot a front or rear sight
* @param modSlot Slot to check
* @returns true if it ' s a front / rear sight
* /
2023-10-17 20:45:40 +01:00
protected modIsFrontOrRearSight ( modSlot : string , tpl : string ) : boolean
2023-03-03 15:23:46 +00:00
{
2023-11-13 11:05:05 -05:00
if ( modSlot === "mod_gas_block" && tpl === "5ae30e795acfc408fb139a0b" )
{ // M4A1 front sight with gas block
2023-10-17 20:45:40 +01:00
return true ;
}
2023-03-03 15:23:46 +00:00
return [ "mod_sight_front" , "mod_sight_rear" ] . includes ( modSlot ) ;
}
/ * *
* Does the provided mod details show the mod can hold a scope
* @param modSlot e . g . mod_scope , mod_mount
* @param modsParentId Parent id of mod item
* @returns true if it can hold a scope
* /
protected modSlotCanHoldScope ( modSlot : string , modsParentId : string ) : boolean
{
2023-11-13 11:05:05 -05:00
return [
"mod_scope" ,
"mod_mount" ,
"mod_mount_000" ,
"mod_scope_000" ,
"mod_scope_001" ,
"mod_scope_002" ,
"mod_scope_003" ,
2023-11-13 12:31:52 -05:00
] . includes ( modSlot . toLowerCase ( ) ) && modsParentId === BaseClasses . MOUNT ;
2023-03-03 15:23:46 +00:00
}
/ * *
2023-10-10 11:03:20 +00:00
* Set mod spawn chances to defined amount
* @param modSpawnChances Chance dictionary to update
2023-03-03 15:23:46 +00:00
* /
2023-11-13 11:05:05 -05:00
protected adjustSlotSpawnChances (
modSpawnChances : ModsChances ,
modSlotsToAdjust : string [ ] ,
newChancePercent : number ,
) : void
2023-03-03 15:23:46 +00:00
{
2023-03-21 14:19:49 +00:00
if ( ! modSpawnChances )
{
this . logger . warning ( "Unable to adjust scope spawn chances as spawn chance object is empty" ) ;
return ;
}
2023-10-10 11:03:20 +00:00
if ( ! modSlotsToAdjust )
{
return ;
}
2023-03-21 14:19:49 +00:00
2023-10-10 11:03:20 +00:00
for ( const modName of modSlotsToAdjust )
2023-03-21 14:19:49 +00:00
{
2023-10-10 11:03:20 +00:00
modSpawnChances [ modName ] = newChancePercent ;
2023-03-21 14:19:49 +00:00
}
2023-03-03 15:23:46 +00:00
}
2023-10-10 11:03:20 +00:00
protected modSlotCanHoldMuzzleDevices ( modSlot : string , modsParentId : string ) : boolean
{
return [ "mod_muzzle" , "mod_muzzle_000" , "mod_muzzle_001" ] . includes ( modSlot . toLowerCase ( ) ) ;
}
2023-03-03 15:23:46 +00:00
protected sortModKeys ( unsortedKeys : string [ ] ) : string [ ]
{
if ( unsortedKeys . length <= 1 )
{
return unsortedKeys ;
}
const sortedKeys : string [ ] = [ ] ;
const modRecieverKey = "mod_reciever" ;
const modMount001Key = "mod_mount_001" ;
2023-10-29 16:35:03 +00:00
const modGasBlockKey = "mod_gas_block" ;
2023-03-03 15:23:46 +00:00
const modPistolGrip = "mod_pistol_grip" ;
const modStockKey = "mod_stock" ;
const modBarrelKey = "mod_barrel" ;
2023-10-29 16:35:03 +00:00
const modHandguardKey = "mod_handguard" ;
2023-03-03 15:23:46 +00:00
const modMountKey = "mod_mount" ;
const modScopeKey = "mod_scope" ;
2023-10-29 16:35:03 +00:00
if ( unsortedKeys . includes ( modHandguardKey ) )
{
sortedKeys . push ( modHandguardKey ) ;
unsortedKeys . splice ( unsortedKeys . indexOf ( modHandguardKey ) , 1 ) ;
}
2023-03-03 15:23:46 +00:00
if ( unsortedKeys . includes ( modBarrelKey ) )
{
sortedKeys . push ( modBarrelKey ) ;
unsortedKeys . splice ( unsortedKeys . indexOf ( modBarrelKey ) , 1 ) ;
}
if ( unsortedKeys . includes ( modMount001Key ) )
{
sortedKeys . push ( modMount001Key ) ;
unsortedKeys . splice ( unsortedKeys . indexOf ( modMount001Key ) , 1 ) ;
}
if ( unsortedKeys . includes ( modRecieverKey ) )
{
sortedKeys . push ( modRecieverKey ) ;
unsortedKeys . splice ( unsortedKeys . indexOf ( modRecieverKey ) , 1 ) ;
}
2023-11-13 11:05:05 -05:00
2023-03-03 15:23:46 +00:00
if ( unsortedKeys . includes ( modPistolGrip ) )
{
sortedKeys . push ( modPistolGrip ) ;
unsortedKeys . splice ( unsortedKeys . indexOf ( modPistolGrip ) , 1 ) ;
}
2023-10-29 16:35:03 +00:00
if ( unsortedKeys . includes ( modGasBlockKey ) )
2023-03-03 15:23:46 +00:00
{
2023-10-29 16:35:03 +00:00
sortedKeys . push ( modGasBlockKey ) ;
unsortedKeys . splice ( unsortedKeys . indexOf ( modGasBlockKey ) , 1 ) ;
2023-03-03 15:23:46 +00:00
}
if ( unsortedKeys . includes ( modStockKey ) )
{
sortedKeys . push ( modStockKey ) ;
unsortedKeys . splice ( unsortedKeys . indexOf ( modStockKey ) , 1 ) ;
}
if ( unsortedKeys . includes ( modMountKey ) )
{
sortedKeys . push ( modMountKey ) ;
unsortedKeys . splice ( unsortedKeys . indexOf ( modMountKey ) , 1 ) ;
}
if ( unsortedKeys . includes ( modScopeKey ) )
{
sortedKeys . push ( modScopeKey ) ;
unsortedKeys . splice ( unsortedKeys . indexOf ( modScopeKey ) , 1 ) ;
}
sortedKeys . push ( . . . unsortedKeys ) ;
return sortedKeys ;
}
/ * *
* Get a Slot property for an item ( chamber / cartridge / slot )
* @param modSlot e . g patron_in_weapon
* @param parentTemplate item template
* @returns Slot item
* /
protected getModItemSlot ( modSlot : string , parentTemplate : ITemplateItem ) : Slot
{
2023-12-27 23:29:37 +00:00
const modSlotLower = modSlot . toLowerCase ( ) ;
switch ( modSlotLower )
2023-03-03 15:23:46 +00:00
{
case "patron_in_weapon" :
case "patron_in_weapon_000" :
case "patron_in_weapon_001" :
2023-12-27 23:29:37 +00:00
return parentTemplate . _props . Chambers . find ( ( chamber ) = > chamber . _name . includes ( modSlotLower ) ) ;
2023-03-03 15:23:46 +00:00
case "cartridges" :
2023-12-27 23:29:37 +00:00
return parentTemplate . _props . Cartridges . find ( ( c ) = > c . _name . toLowerCase ( ) === modSlotLower ) ;
2023-03-03 15:23:46 +00:00
default :
2023-12-27 23:29:37 +00:00
return parentTemplate . _props . Slots . find ( ( s ) = > s . _name . toLowerCase ( ) === modSlotLower ) ;
2023-03-03 15:23:46 +00:00
}
}
/ * *
2023-03-21 14:19:49 +00:00
* Randomly choose if a mod should be spawned , 100 % for required mods OR mod is ammo slot
2023-03-03 15:23:46 +00:00
* never return true for an item that has 0 % spawn chance
* @param itemSlot slot the item sits in
* @param modSlot slot the mod sits in
* @param modSpawnChances Chances for various mod spawns
* @returns boolean true if it should spawn
* /
protected shouldModBeSpawned ( itemSlot : Slot , modSlot : string , modSpawnChances : ModsChances ) : boolean
{
const modSpawnChance = itemSlot . _required || this . getAmmoContainers ( ) . includes ( modSlot ) // Required OR it is ammo
2023-11-13 12:29:16 -05:00
? 100
: modSpawnChances [ modSlot ] ;
2023-03-03 15:23:46 +00:00
if ( modSpawnChance === 100 )
{
return true ;
}
return this . probabilityHelper . rollChance ( modSpawnChance ) ;
}
/ * *
* @param modSlot Slot mod will fit into
* @param isRandomisableSlot Will generate a randomised mod pool if true
* @param modsParent Parent slot the item will be a part of
* @param botEquipBlacklist Blacklist to prevent mods from being picked
* @param itemModPool Pool of items to pick from
* @param weapon array with only weapon tpl in it , ready for mods to be added
* @param ammoTpl ammo tpl to use if slot requires a cartridge to be added ( e . g . mod_magazine )
* @param parentTemplate Parent item the mod will go into
* @returns ITemplateItem
* /
protected chooseModToPutIntoSlot (
modSlot : string ,
isRandomisableSlot : boolean ,
botWeaponSightWhitelist : Record < string , string [ ] > ,
botEquipBlacklist : EquipmentFilterDetails ,
itemModPool : Record < string , string [ ] > ,
weapon : Item [ ] ,
ammoTpl : string ,
2023-11-13 11:05:05 -05:00
parentTemplate : ITemplateItem ,
) : [ boolean , ITemplateItem ]
2023-03-03 15:23:46 +00:00
{
let modTpl : string ;
let found = false ;
2023-11-13 11:05:05 -05:00
const parentSlot = parentTemplate . _props . Slots . find ( ( i ) = > i . _name === modSlot ) ;
2023-03-03 15:23:46 +00:00
// It's ammo, use predefined ammo parameter
if ( this . getAmmoContainers ( ) . includes ( modSlot ) && modSlot !== "mod_magazine" )
{
modTpl = ammoTpl ;
}
else
{
// Get randomised pool of mods if this is a slot we randomise
if ( isRandomisableSlot )
{
itemModPool [ modSlot ] = this . getDynamicModPool ( parentTemplate . _id , modSlot , botEquipBlacklist ) ;
}
// Ensure there's a pool of mods to pick from
if ( ! ( itemModPool [ modSlot ] || parentSlot . _required ) )
{
2023-11-13 11:05:05 -05:00
this . logger . debug (
` Mod pool for slot: ${ modSlot } on item: ${ parentTemplate . _name } was empty, skipping mod ` ,
) ;
2023-03-03 15:23:46 +00:00
return null ;
}
// Filter out non-whitelisted scopes, use full modpool if filtered pool would have no elements
2023-11-13 11:05:05 -05:00
if ( modSlot . includes ( "mod_scope" ) && botWeaponSightWhitelist )
2023-03-03 15:23:46 +00:00
{
// scope pool has more than one scope
if ( itemModPool [ modSlot ] . length > 1 )
{
2023-11-13 11:05:05 -05:00
itemModPool [ modSlot ] = this . filterSightsByWeaponType (
weapon [ 0 ] ,
itemModPool [ modSlot ] ,
botWeaponSightWhitelist ,
) ;
2023-03-03 15:23:46 +00:00
}
}
2023-11-13 11:05:05 -05:00
2023-03-03 15:23:46 +00:00
// Pick random mod and check it's compatible
const exhaustableModPool = new ExhaustableArray ( itemModPool [ modSlot ] , this . randomUtil , this . jsonUtil ) ;
2023-11-13 12:38:16 -05:00
let modCompatibilityResult : { incompatible : boolean ; reason : string ; } = {
incompatible : false ,
reason : "" ,
} ;
2023-03-03 15:23:46 +00:00
while ( exhaustableModPool . hasValues ( ) )
{
modTpl = exhaustableModPool . getRandomValue ( ) ;
2023-11-13 11:05:05 -05:00
modCompatibilityResult = this . botGeneratorHelper . isItemIncompatibleWithCurrentItems (
weapon ,
modTpl ,
modSlot ,
) ;
2023-11-17 12:40:23 +00:00
if ( ! modCompatibilityResult . incompatible && ! this . weaponModComboIsIncompatible ( weapon , modTpl ) )
2023-03-03 15:23:46 +00:00
{
found = true ;
2023-10-29 20:45:35 +00:00
2023-03-03 15:23:46 +00:00
break ;
}
}
// Log mod chosen was incompatible
if ( modCompatibilityResult . incompatible && parentSlot . _required )
{
this . logger . debug ( modCompatibilityResult . reason ) ;
2023-11-17 12:40:23 +00:00
this . logger . debug ( ` Weapon: ${ weapon . map ( x = > ` ${ x . _tpl } ${ x . slotId ? ? "" } ` ) . join ( "," ) } ` )
2023-03-03 15:23:46 +00:00
}
}
// Get random mod to attach from items db for required slots if none found above
if ( ! found && parentSlot !== undefined && parentSlot . _required )
{
modTpl = this . getModTplFromItemDb ( modTpl , parentSlot , modSlot , weapon ) ;
found = ! ! modTpl ;
}
// Compatible item not found + not required
if ( ! found && parentSlot !== undefined && ! parentSlot . _required )
{
return null ;
}
if ( ! found && parentSlot !== undefined )
{
if ( parentSlot . _required )
{
2023-11-13 11:05:05 -05:00
this . logger . warning (
` Required slot unable to be filled, ${ modSlot } on ${ parentTemplate . _name } ${ parentTemplate . _id } for weapon: ${
weapon [ 0 ] . _tpl
} ` ,
) ;
2023-03-03 15:23:46 +00:00
}
return null ;
}
return this . itemHelper . getItem ( modTpl ) ;
}
2023-11-17 12:40:23 +00:00
/ * *
* Temp fix to prevent certain combinations of weapons with mods that are known to be incompatible
* @param weapon Weapon
* @param modTpl Mod to check compatibility with weapon
* @returns True if incompatible
* /
protected weaponModComboIsIncompatible ( weapon : Item [ ] , modTpl : string ) : boolean
{
// STM-9 + AR-15 Lone Star Ion Lite handguard
if ( weapon [ 0 ] . _tpl === "60339954d62c9b14ed777c06" && modTpl === "5d4405f0a4b9361e6a4e6bd9" )
{
return true ;
}
return false ;
}
2023-03-03 15:23:46 +00:00
/ * *
* Create a mod item with parameters as properties
* @param modId _id
* @param modTpl _tpl
* @param parentId parentId
* @param modSlot slotId
2023-11-13 11:05:05 -05:00
* @param modTemplate Used to add additional properties in the upd object
2023-03-03 15:23:46 +00:00
* @returns Item object
* /
2023-11-13 11:05:05 -05:00
protected createModItem (
modId : string ,
modTpl : string ,
parentId : string ,
modSlot : string ,
modTemplate : ITemplateItem ,
botRole : string ,
) : Item
2023-03-03 15:23:46 +00:00
{
return {
2023-10-31 22:52:09 +00:00
_id : modId ,
_tpl : modTpl ,
parentId : parentId ,
slotId : modSlot ,
2023-11-13 11:05:05 -05:00
. . . this . botGeneratorHelper . generateExtraPropertiesForItem ( modTemplate , botRole ) ,
2023-03-03 15:23:46 +00:00
} ;
}
/ * *
* Get a list of containers that hold ammo
* e . g . mod_magazine / patron_in_weapon_000
* @returns string array
* /
protected getAmmoContainers ( ) : string [ ]
{
return [ "mod_magazine" , "patron_in_weapon" , "patron_in_weapon_000" , "patron_in_weapon_001" , "cartridges" ] ;
}
/ * *
* Get a random mod from an items compatible mods Filter array
* @param modTpl ? ? ? ? default value to return if nothing found
2023-11-13 11:05:05 -05:00
* @param parentSlot item mod will go into , used to get compatible items
2023-03-03 15:23:46 +00:00
* @param modSlot Slot to get mod to fill
* @param items items to ensure picked mod is compatible with
* @returns item tpl
* /
protected getModTplFromItemDb ( modTpl : string , parentSlot : Slot , modSlot : string , items : Item [ ] ) : string
{
2023-11-13 11:05:05 -05:00
// Find compatible mods and make an array of them
2023-03-03 15:23:46 +00:00
const allowedItems = parentSlot . _props . filters [ 0 ] . Filter ;
// Find mod item that fits slot from sorted mod array
const exhaustableModPool = new ExhaustableArray ( allowedItems , this . randomUtil , this . jsonUtil ) ;
let tmpModTpl = modTpl ;
while ( exhaustableModPool . hasValues ( ) )
{
tmpModTpl = exhaustableModPool . getRandomValue ( ) ;
if ( ! this . botGeneratorHelper . isItemIncompatibleWithCurrentItems ( items , tmpModTpl , modSlot ) . incompatible )
{
return tmpModTpl ;
}
}
// No mod found
return null ;
}
/ * *
* Log errors if mod is not compatible with slot
* @param modToAdd template of mod to check
* @param itemSlot slot the item will be placed in
* @param modSlot slot the mod will fill
* @param parentTemplate template of the mods parent item
2023-12-28 00:06:45 +00:00
* @param botRole
2023-03-03 15:23:46 +00:00
* @returns true if valid
* /
2023-11-13 11:05:05 -05:00
protected isModValidForSlot (
modToAdd : [ boolean , ITemplateItem ] ,
itemSlot : Slot ,
modSlot : string ,
parentTemplate : ITemplateItem ,
2023-12-28 00:06:45 +00:00
botRole : string
2023-11-13 11:05:05 -05:00
) : boolean
2023-03-03 15:23:46 +00:00
{
// Mod lacks template item
if ( ! modToAdd [ 1 ] )
{
2023-11-13 11:05:05 -05:00
this . logger . error (
this . localisationService . getText ( "bot-no_item_template_found_when_adding_mod" , {
modId : modToAdd [ 1 ] . _id ,
modSlot : modSlot ,
} ) ,
) ;
2023-03-03 15:23:46 +00:00
this . logger . debug ( ` Item -> ${ parentTemplate . _id } ; Slot -> ${ modSlot } ` ) ;
return false ;
}
// Mod isn't a valid item
if ( ! modToAdd [ 0 ] )
{
// Slot must be filled, show warning
if ( itemSlot . _required )
{
2023-11-13 11:05:05 -05:00
this . logger . warning (
this . localisationService . getText ( "bot-unable_to_add_mod_item_invalid" , {
itemName : modToAdd [ 1 ] . _name ,
modSlot : modSlot ,
parentItemName : parentTemplate._name ,
} ) ,
) ;
2023-03-03 15:23:46 +00:00
}
return false ;
}
2023-11-13 11:05:05 -05:00
// If mod id doesn't exist in slots filter list and mod id doesn't have any of the slots filters as a base class, mod isn't valid for the slot
if (
2023-11-13 12:29:16 -05:00
! ( itemSlot . _props . filters [ 0 ] . Filter . includes ( modToAdd [ 1 ] . _id )
|| this . itemHelper . isOfBaseclasses ( modToAdd [ 1 ] . _id , itemSlot . _props . filters [ 0 ] . Filter ) )
2023-11-13 11:05:05 -05:00
)
2023-03-03 15:23:46 +00:00
{
2023-11-13 11:05:05 -05:00
this . logger . warning (
this . localisationService . getText ( "bot-mod_not_in_slot_filter_list" , {
modId : modToAdd [ 1 ] . _id ,
modSlot : modSlot ,
parentName : parentTemplate._name ,
2023-12-28 00:07:41 +00:00
botRole : botRole
2023-11-13 11:05:05 -05:00
} ) ,
) ;
2023-03-03 15:23:46 +00:00
return false ;
}
return true ;
}
/ * *
* Find mod tpls of a provided type and add to modPool
* @param desiredSlotName slot to look up and add we are adding tpls for ( e . g mod_scope )
* @param modTemplate db object for modItem we get compatible mods from
* @param modPool Pool of mods we are adding to
* /
2023-11-13 11:05:05 -05:00
protected addCompatibleModsForProvidedMod (
desiredSlotName : string ,
modTemplate : ITemplateItem ,
modPool : Mods ,
botEquipBlacklist : EquipmentFilterDetails ,
) : void
2023-03-03 15:23:46 +00:00
{
2023-11-13 11:05:05 -05:00
const desiredSlotObject = modTemplate . _props . Slots . find ( ( x ) = > x . _name . includes ( desiredSlotName ) ) ;
2023-03-03 15:23:46 +00:00
if ( desiredSlotObject )
{
const supportedSubMods = desiredSlotObject . _props . filters [ 0 ] . Filter ;
if ( supportedSubMods )
{
// Filter mods
2023-11-13 11:05:05 -05:00
let filteredMods = this . filterWeaponModsByBlacklist (
supportedSubMods ,
botEquipBlacklist ,
desiredSlotName ,
) ;
2023-03-03 15:23:46 +00:00
if ( filteredMods . length === 0 )
{
2023-11-13 11:05:05 -05:00
this . logger . warning (
this . localisationService . getText ( "bot-unable_to_filter_mods_all_blacklisted" , {
slotName : desiredSlotObject._name ,
itemName : modTemplate._name ,
} ) ,
) ;
2023-03-03 15:23:46 +00:00
filteredMods = supportedSubMods ;
}
if ( ! modPool [ modTemplate . _id ] )
{
modPool [ modTemplate . _id ] = { } ;
}
modPool [ modTemplate . _id ] [ desiredSlotObject . _name ] = supportedSubMods ;
}
}
}
/ * *
* Get the possible items that fit a slot
* @param parentItemId item tpl to get compatible items for
* @param modSlot Slot item should fit in
* @param botEquipBlacklist equipment that should not be picked
* @returns array of compatible items for that slot
* /
2023-11-13 11:05:05 -05:00
protected getDynamicModPool (
parentItemId : string ,
modSlot : string ,
botEquipBlacklist : EquipmentFilterDetails ,
) : string [ ]
2023-03-03 15:23:46 +00:00
{
2023-11-13 11:05:05 -05:00
const modsFromDynamicPool = this . jsonUtil . clone (
this . botEquipmentModPoolService . getCompatibleModsForWeaponSlot ( parentItemId , modSlot ) ,
) ;
2023-03-03 15:23:46 +00:00
const filteredMods = this . filterWeaponModsByBlacklist ( modsFromDynamicPool , botEquipBlacklist , modSlot ) ;
if ( filteredMods . length === 0 )
{
2023-11-13 11:05:05 -05:00
this . logger . warning (
this . localisationService . getText ( "bot-unable_to_filter_mod_slot_all_blacklisted" , modSlot ) ,
) ;
2023-03-03 15:23:46 +00:00
return modsFromDynamicPool ;
}
return filteredMods ;
}
/ * *
* Take a list of tpls and filter out blacklisted values using itemFilterService + botEquipmentBlacklist
* @param allowedMods base mods to filter
* @param botEquipBlacklist equipment blacklist
* @param modSlot slot mods belong to
* @returns Filtered array of mod tpls
* /
2023-11-13 11:05:05 -05:00
protected filterWeaponModsByBlacklist (
allowedMods : string [ ] ,
botEquipBlacklist : EquipmentFilterDetails ,
modSlot : string ,
) : string [ ]
2023-03-03 15:23:46 +00:00
{
if ( ! botEquipBlacklist )
{
return allowedMods ;
}
2023-11-13 11:05:05 -05:00
2023-03-03 15:23:46 +00:00
let result : string [ ] = [ ] ;
2023-11-13 11:05:05 -05:00
// Get item blacklist and mod equipment blacklist as one array
const blacklist = this . itemFilterService . getBlacklistedItems ( ) . concat (
botEquipBlacklist . equipment [ modSlot ] || [ ] ,
) ;
result = allowedMods . filter ( ( x ) = > ! blacklist . includes ( x ) ) ;
2023-03-03 15:23:46 +00:00
return result ;
}
/ * *
* With the shotgun revolver ( 60 db29ce99594040e04c4a27 ) 12.12 introduced CylinderMagazines .
* Those magazines ( e . g . 60 dc519adf4c47305f6d410d ) have a "Cartridges" entry with a _max_count = 0 .
* Ammo is not put into the magazine directly but assigned to the magazine ' s slots : The "camora_xxx" slots .
* This function is a helper called by generateModsForItem for mods with parent type "CylinderMagazine"
* @param items The items where the CylinderMagazine ' s camora are appended to
2023-11-04 20:11:09 +00:00
* @param modPool modPool which should include available cartridges
2023-03-03 15:23:46 +00:00
* @param parentId The CylinderMagazine ' s UID
* @param parentTemplate The CylinderMagazine ' s template
* /
protected fillCamora ( items : Item [ ] , modPool : Mods , parentId : string , parentTemplate : ITemplateItem ) : void
{
2023-11-04 20:11:09 +00:00
let itemModPool = modPool [ parentTemplate . _id ] ;
2023-03-03 15:23:46 +00:00
if ( ! itemModPool )
{
2023-11-13 11:05:05 -05:00
this . logger . warning (
this . localisationService . getText ( "bot-unable_to_fill_camora_slot_mod_pool_empty" , {
weaponId : parentTemplate._id ,
weaponName : parentTemplate._name ,
} ) ,
) ;
const camoraSlots = parentTemplate . _props . Slots . filter ( ( x ) = > x . _name . startsWith ( "camora" ) ) ;
2023-11-04 20:11:09 +00:00
// Attempt to generate camora slots for item
modPool [ parentTemplate . _id ] = { } ;
for ( const camora of camoraSlots )
{
modPool [ parentTemplate . _id ] [ camora . _name ] = camora . _props . filters [ 0 ] . Filter ;
}
itemModPool = modPool [ parentTemplate . _id ] ;
2023-03-03 15:23:46 +00:00
}
let exhaustableModPool = null ;
let modSlot = "cartridges" ;
const camoraFirstSlot = "camora_000" ;
if ( modSlot in itemModPool )
{
exhaustableModPool = new ExhaustableArray ( itemModPool [ modSlot ] , this . randomUtil , this . jsonUtil ) ;
}
else if ( camoraFirstSlot in itemModPool )
{
modSlot = camoraFirstSlot ;
2023-11-13 11:05:05 -05:00
exhaustableModPool = new ExhaustableArray (
this . mergeCamoraPoolsTogether ( itemModPool ) ,
this . randomUtil ,
this . jsonUtil ,
) ;
2023-03-03 15:23:46 +00:00
}
else
{
this . logger . error ( this . localisationService . getText ( "bot-missing_cartridge_slot" , parentTemplate . _id ) ) ;
return ;
}
let modTpl : string ;
let found = false ;
while ( exhaustableModPool . hasValues ( ) )
{
modTpl = exhaustableModPool . getRandomValue ( ) ;
if ( ! this . botGeneratorHelper . isItemIncompatibleWithCurrentItems ( items , modTpl , modSlot ) . incompatible )
{
found = true ;
break ;
}
}
if ( ! found )
{
this . logger . error ( this . localisationService . getText ( "bot-no_compatible_camora_ammo_found" , modSlot ) ) ;
return ;
}
for ( const slot of parentTemplate . _props . Slots )
{
const modSlotId = slot . _name ;
const modId = this . hashUtil . generate ( ) ;
2023-11-13 12:38:16 -05:00
items . push ( { _id : modId , _tpl : modTpl , parentId : parentId , slotId : modSlotId } ) ;
2023-03-03 15:23:46 +00:00
}
}
/ * *
2023-11-13 11:05:05 -05:00
* Take a record of camoras and merge the compatible shells into one array
2023-03-03 15:23:46 +00:00
* @param camorasWithShells camoras we want to merge into one array
2023-11-13 11:05:05 -05:00
* @returns string array of shells for multiple camora sources
2023-03-03 15:23:46 +00:00
* /
2023-11-13 11:05:05 -05:00
protected mergeCamoraPoolsTogether ( camorasWithShells : Record < string , string [ ] > ) : string [ ]
2023-03-03 15:23:46 +00:00
{
const poolResult : string [ ] = [ ] ;
for ( const camoraKey in camorasWithShells )
{
const shells = camorasWithShells [ camoraKey ] ;
for ( const shell of shells )
{
// Only add distinct shells
if ( ! poolResult . includes ( shell ) )
{
poolResult . push ( shell ) ;
}
}
}
return poolResult ;
}
/ * *
* Filter out non - whitelisted weapon scopes
2023-10-10 11:03:20 +00:00
* Controlled by bot . json weaponSightWhitelist
* e . g . filter out rifle scopes from SMGs
2023-03-03 15:23:46 +00:00
* @param weapon Weapon scopes will be added to
* @param scopes Full scope pool
2023-11-13 11:05:05 -05:00
* @param botWeaponSightWhitelist Whitelist of scope types by weapon base type
2023-10-10 11:03:20 +00:00
* @returns Array of scope tpls that have been filtered to just ones allowed for that weapon type
2023-03-03 15:23:46 +00:00
* /
2023-11-13 11:05:05 -05:00
protected filterSightsByWeaponType (
weapon : Item ,
scopes : string [ ] ,
botWeaponSightWhitelist : Record < string , string [ ] > ,
) : string [ ]
2023-03-03 15:23:46 +00:00
{
const weaponDetails = this . itemHelper . getItem ( weapon . _tpl ) ;
// Return original scopes array if whitelist not found
const whitelistedSightTypes = botWeaponSightWhitelist [ weaponDetails [ 1 ] . _parent ] ;
if ( ! whitelistedSightTypes )
{
2023-11-13 11:05:05 -05:00
this . logger . debug (
` Unable to find whitelist for weapon type: ${ weaponDetails [ 1 ] . _parent } ${
weaponDetails [ 1 ] . _name
} , skipping sight filtering ` ,
) ;
2023-10-10 11:03:20 +00:00
2023-03-03 15:23:46 +00:00
return scopes ;
}
// Filter items that are not directly scopes OR mounts that do not hold the type of scope we allow for this weapon type
const filteredScopesAndMods : string [ ] = [ ] ;
for ( const item of scopes )
{
// Mods is a scope, check base class is allowed
if ( this . itemHelper . isOfBaseclasses ( item , whitelistedSightTypes ) )
{
2023-10-10 11:03:20 +00:00
// Add mod to allowed list
2023-03-03 15:23:46 +00:00
filteredScopesAndMods . push ( item ) ;
continue ;
}
2023-10-10 11:03:20 +00:00
// Edge case, what if item is a mount for a scope and not directly a scope?
// Check item is mount + has child items
2023-03-03 15:23:46 +00:00
const itemDetails = this . itemHelper . getItem ( item ) [ 1 ] ;
2023-11-13 11:05:05 -05:00
if ( this . itemHelper . isOfBaseclass ( item , BaseClasses . MOUNT ) && itemDetails . _props . Slots . length > 0 )
2023-03-03 15:23:46 +00:00
{
2023-10-10 11:03:20 +00:00
// Check to see if mount has a scope slot (only include primary slot, ignore the rest like the backup sight slots)
// Should only find 1 as there's currently no items with a mod_scope AND a mod_scope_000
2023-11-13 11:05:05 -05:00
const scopeSlot = itemDetails . _props . Slots . filter ( ( x ) = >
[ "mod_scope" , "mod_scope_000" ] . includes ( x . _name )
) ;
2023-10-10 11:03:20 +00:00
// Mods scope slot found must allow ALL whitelisted scope types OR be a mount
2023-11-13 11:05:05 -05:00
if (
scopeSlot ? . every ( ( x ) = >
x . _props . filters [ 0 ] . Filter . every ( ( x ) = >
2023-11-13 12:29:16 -05:00
this . itemHelper . isOfBaseclasses ( x , whitelistedSightTypes )
|| this . itemHelper . isOfBaseclass ( x , BaseClasses . MOUNT )
2023-11-13 11:05:05 -05:00
)
)
)
2023-03-03 15:23:46 +00:00
{
2023-10-10 11:03:20 +00:00
// Add mod to allowed list
2023-03-03 15:23:46 +00:00
filteredScopesAndMods . push ( item ) ;
}
}
}
2023-11-13 11:05:05 -05:00
// No mods added to return list after filtering has occurred, send back the original mod list
2023-03-03 15:23:46 +00:00
if ( ! filteredScopesAndMods || filteredScopesAndMods . length === 0 )
{
2023-11-13 11:05:05 -05:00
this . logger . debug (
` Scope whitelist too restrictive for: ${ weapon . _tpl } ${ weaponDetails [ 1 ] . _name } , skipping filter ` ,
) ;
2023-03-03 15:23:46 +00:00
return scopes ;
}
return filteredScopesAndMods ;
}
2023-11-13 11:05:05 -05:00
}