2024-08-16 23:19:07 +01:00
import { request } from "node:http" ;
2024-05-21 17:59:04 +00:00
import { BotGeneratorHelper } from "@spt/helpers/BotGeneratorHelper" ;
import { BotHelper } from "@spt/helpers/BotHelper" ;
import { BotWeaponGeneratorHelper } from "@spt/helpers/BotWeaponGeneratorHelper" ;
import { ItemHelper } from "@spt/helpers/ItemHelper" ;
import { PresetHelper } from "@spt/helpers/PresetHelper" ;
import { ProbabilityHelper } from "@spt/helpers/ProbabilityHelper" ;
import { ProfileHelper } from "@spt/helpers/ProfileHelper" ;
import { WeightedRandomHelper } from "@spt/helpers/WeightedRandomHelper" ;
import { IPreset } from "@spt/models/eft/common/IGlobals" ;
2024-09-24 12:47:29 +01:00
import { IMods , IModsChances } from "@spt/models/eft/common/tables/IBotType" ;
import { IItem } from "@spt/models/eft/common/tables/IItem" ;
import { ISlot , ITemplateItem } from "@spt/models/eft/common/tables/ITemplateItem" ;
2024-05-21 17:59:04 +00:00
import { BaseClasses } from "@spt/models/enums/BaseClasses" ;
import { ConfigTypes } from "@spt/models/enums/ConfigTypes" ;
import { ModSpawn } from "@spt/models/enums/ModSpawn" ;
import { IChooseRandomCompatibleModResult } from "@spt/models/spt/bots/IChooseRandomCompatibleModResult" ;
2024-07-23 11:12:53 -04:00
import {
IFilterPlateModsForSlotByLevelResult ,
Result ,
} from "@spt/models/spt/bots/IFilterPlateModsForSlotByLevelResult" ;
2024-06-19 11:11:28 +01:00
import { IGenerateEquipmentProperties } from "@spt/models/spt/bots/IGenerateEquipmentProperties" ;
2024-05-21 23:22:16 +01:00
import { IGenerateWeaponRequest } from "@spt/models/spt/bots/IGenerateWeaponRequest" ;
import { IModToSpawnRequest } from "@spt/models/spt/bots/IModToSpawnRequest" ;
2024-05-21 17:59:04 +00:00
import { EquipmentFilterDetails , EquipmentFilters , IBotConfig } from "@spt/models/spt/config/IBotConfig" ;
import { ExhaustableArray } from "@spt/models/spt/server/ExhaustableArray" ;
import { ILogger } from "@spt/models/spt/utils/ILogger" ;
import { ConfigServer } from "@spt/servers/ConfigServer" ;
import { BotEquipmentFilterService } from "@spt/services/BotEquipmentFilterService" ;
import { BotEquipmentModPoolService } from "@spt/services/BotEquipmentModPoolService" ;
2024-05-21 23:22:16 +01:00
import { BotWeaponModLimitService } from "@spt/services/BotWeaponModLimitService" ;
2024-05-28 14:52:22 +01:00
import { DatabaseService } from "@spt/services/DatabaseService" ;
2024-05-21 17:59:04 +00:00
import { ItemFilterService } from "@spt/services/ItemFilterService" ;
import { LocalisationService } from "@spt/services/LocalisationService" ;
import { HashUtil } from "@spt/utils/HashUtil" ;
import { RandomUtil } from "@spt/utils/RandomUtil" ;
2024-07-23 11:12:53 -04:00
import { ICloner } from "@spt/utils/cloners/ICloner" ;
import { inject , injectable } from "tsyringe" ;
2023-03-03 15:23:46 +00:00
@injectable ( )
2024-07-23 11:12:53 -04:00
export class BotEquipmentModGenerator {
2023-03-03 15:23:46 +00:00
protected botConfig : IBotConfig ;
constructor (
2024-05-28 14:04:20 +00:00
@inject ( "PrimaryLogger" ) protected logger : ILogger ,
2023-03-03 15:23:46 +00:00
@inject ( "HashUtil" ) protected hashUtil : HashUtil ,
@inject ( "RandomUtil" ) protected randomUtil : RandomUtil ,
@inject ( "ProbabilityHelper" ) protected probabilityHelper : ProbabilityHelper ,
2024-05-28 14:52:22 +01:00
@inject ( "DatabaseService" ) protected databaseService : DatabaseService ,
2023-03-03 15:23:46 +00:00
@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 ,
2024-05-28 14:04:20 +00:00
@inject ( "PrimaryCloner" ) protected cloner : ICloner ,
2024-07-23 11:12:53 -04:00
) {
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
2024-10-08 20:14:43 +01:00
* @param parentTemplate Template object of item to add mods to
* @param specificBlacklist The relevant blacklist from bot . json equipment dictionary
2023-03-03 15:23:46 +00:00
* @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 (
2024-09-24 12:47:29 +01:00
equipment : IItem [ ] ,
2023-11-13 11:05:05 -05:00
parentId : string ,
parentTemplate : ITemplateItem ,
2024-01-07 14:46:25 +00:00
settings : IGenerateEquipmentProperties ,
2024-10-08 20:14:43 +01:00
specificBlacklist : EquipmentFilterDetails ,
2024-02-05 18:51:32 -05:00
shouldForceSpawn = false ,
2024-09-24 12:47:29 +01:00
) : IItem [ ] {
2024-02-05 18:51:32 -05:00
let forceSpawn = shouldForceSpawn ;
2024-10-08 20:14:43 +01:00
// Get mod pool for the desired item
2024-01-07 14:46:25 +00:00
const compatibleModsPool = settings . modPool [ parentTemplate . _id ] ;
2024-07-23 11:12:53 -04:00
if ( ! compatibleModsPool ) {
2024-02-02 13:54:07 -05:00
this . logger . warning (
2024-10-08 20:14:43 +01:00
` bot: ${ settings . botData . role } lacks a mod slot pool for item: ${ parentTemplate . _id } ${ parentTemplate . _name } ` ,
2024-02-02 13:54:07 -05:00
) ;
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
2024-07-23 11:12:53 -04:00
for ( const modSlotName in compatibleModsPool ) {
2024-10-08 20:14:43 +01:00
// Get the templates slot object from db
2024-01-25 10:55:33 +00:00
const itemSlotTemplate = this . getModItemSlotFromDb ( modSlotName , parentTemplate ) ;
2024-07-23 11:12:53 -04:00
if ( ! itemSlotTemplate ) {
2023-11-13 11:05:05 -05:00
this . logger . error (
this . localisationService . getText ( "bot-mod_slot_missing_from_item" , {
2024-01-25 10:55:33 +00:00
modSlot : modSlotName ,
2023-11-13 11:05:05 -05:00
parentId : parentTemplate._id ,
parentName : parentTemplate._name ,
2024-10-08 20:14:43 +01:00
botRole : settings.botData.role ,
2023-11-13 11:05:05 -05:00
} ) ,
) ;
2024-07-25 09:38:28 +01:00
2023-03-03 15:23:46 +00:00
continue ;
}
2024-07-25 09:38:28 +01:00
2024-02-02 13:54:07 -05:00
const modSpawnResult = this . shouldModBeSpawned (
itemSlotTemplate ,
modSlotName . toLowerCase ( ) ,
settings . spawnChances . equipmentMods ,
2024-02-05 19:16:36 -05:00
settings . botEquipmentConfig ,
2024-02-02 13:54:07 -05:00
) ;
2024-07-25 09:38:28 +01:00
// Rolled to skip mod and it shouldnt be force-spawned
2024-07-23 11:12:53 -04:00
if ( modSpawnResult === ModSpawn . SKIP && ! forceSpawn ) {
2023-03-03 15:23:46 +00:00
continue ;
}
// Ensure submods for nvgs all spawn together
2024-07-23 11:12:53 -04:00
if ( modSlotName === "mod_nvg" ) {
2023-12-27 23:29:37 +00:00
forceSpawn = true ;
}
2023-03-03 15:23:46 +00:00
2024-07-25 09:38:28 +01:00
// Get pool of items we can add for this slot
2024-01-25 10:55:33 +00:00
let modPoolToChooseFrom = compatibleModsPool [ modSlotName ] ;
2024-07-25 09:38:28 +01:00
2024-10-08 20:14:43 +01:00
// Filter the pool of items in blacklist
const filteredModPool = this . filterModsByBlacklist ( modPoolToChooseFrom , specificBlacklist , modSlotName ) ;
if ( filteredModPool . length > 0 ) {
// use filtered pool as it has items in it
modPoolToChooseFrom = filteredModPool ;
}
2024-07-25 09:38:28 +01:00
// Slot can hold armor plates + we are filtering possible items by bot level, handle
2024-02-02 13:54:07 -05:00
if (
2024-07-23 11:12:53 -04:00
settings . botEquipmentConfig . filterPlatesByLevel &&
this . itemHelper . isRemovablePlateSlot ( modSlotName . toLowerCase ( ) )
) {
2024-07-25 09:38:28 +01:00
const plateSlotFilteringOutcome = this . filterPlateModsForSlotByLevel (
2024-02-02 13:54:07 -05:00
settings ,
modSlotName . toLowerCase ( ) ,
compatibleModsPool [ modSlotName ] ,
parentTemplate ,
) ;
2024-07-25 09:38:28 +01:00
if ( [ Result . UNKNOWN_FAILURE , Result . NO_DEFAULT_FILTER ] . includes ( plateSlotFilteringOutcome . result ) ) {
2024-02-02 13:54:07 -05:00
this . logger . debug (
` Plate slot: ${ modSlotName } selection for armor: ${ parentTemplate . _id } failed: ${
2024-07-25 09:38:28 +01:00
Result [ plateSlotFilteringOutcome . result ]
2024-02-02 13:54:07 -05:00
} , skipping ` ,
) ;
2024-01-07 14:46:25 +00:00
2024-01-12 17:00:22 +00:00
continue ;
}
2024-02-02 13:54:07 -05:00
2024-07-25 09:38:28 +01:00
if ( [ Result . LACKS_PLATE_WEIGHTS ] . includes ( plateSlotFilteringOutcome . result ) ) {
2024-02-02 13:54:07 -05:00
this . logger . warning (
` Plate slot: ${ modSlotName } lacks weights for armor: ${ parentTemplate . _id } , unable to adjust plate choice, using existing data ` ,
) ;
2024-01-12 17:00:22 +00:00
}
2024-02-02 13:54:07 -05:00
2024-07-25 09:38:28 +01:00
// Replace mod pool with pool of chosen plate items
modPoolToChooseFrom = plateSlotFilteringOutcome . plateModTpls ;
2024-01-12 17:00:22 +00:00
}
2023-11-13 11:05:05 -05:00
2024-07-25 09:38:28 +01:00
// Choose random mod from pool and check its compatibility
2024-05-28 14:35:38 +00:00
let modTpl : string | undefined ;
2024-01-12 17:00:22 +00:00
let found = false ;
2024-07-28 11:42:45 +01:00
const exhaustableModPool = this . createExhaustableArray ( modPoolToChooseFrom ) ;
2024-07-23 11:12:53 -04:00
while ( exhaustableModPool . hasValues ( ) ) {
2023-03-03 15:23:46 +00:00
modTpl = exhaustableModPool . getRandomValue ( ) ;
2024-07-23 11:12:53 -04:00
if (
modTpl &&
! this . botGeneratorHelper . isItemIncompatibleWithCurrentItems ( equipment , modTpl , modSlotName )
. 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
2024-07-23 11:12:53 -04:00
if ( ! found && itemSlotTemplate . _required ) {
2024-01-25 13:45:42 +00:00
modTpl = this . getRandomModTplFromItemDb ( modTpl , itemSlotTemplate , modSlotName , equipment ) ;
2023-03-03 15:23:46 +00:00
found = ! ! modTpl ;
}
2024-07-25 09:38:28 +01:00
// Compatible item not found + not required - skip
2024-07-23 11:12:53 -04:00
if ( ! ( found || itemSlotTemplate . _required ) ) {
2023-03-03 15:23:46 +00:00
continue ;
}
2024-07-25 10:34:54 +01:00
// Get chosen mods db template and check it fits into slot
2023-03-03 15:23:46 +00:00
const modTemplate = this . itemHelper . getItem ( modTpl ) ;
2024-10-08 20:14:43 +01:00
if (
! this . isModValidForSlot (
modTemplate ,
itemSlotTemplate ,
modSlotName ,
parentTemplate ,
settings . botData . role ,
)
) {
2023-03-03 15:23:46 +00:00
continue ;
}
2024-01-25 10:55:33 +00:00
// Generate new id to ensure all items are unique on bot
2023-03-03 15:23:46 +00:00
const modId = this . hashUtil . generate ( ) ;
2024-10-08 20:14:43 +01:00
equipment . push (
this . createModItem ( modId , modTpl , parentId , modSlotName , modTemplate [ 1 ] , settings . botData . role ) ,
) ;
2023-03-03 15:23:46 +00:00
2024-07-25 09:38:28 +01:00
// Does item being added exist in mod pool - has its own mod pool
2024-07-23 11:12:53 -04:00
if ( Object . keys ( settings . modPool ) . includes ( modTpl ) ) {
2024-07-25 09:38:28 +01:00
// Call self again with mod being added as item to add child mods to
2024-10-08 20:14:43 +01:00
this . generateModsForEquipment (
equipment ,
modId ,
modTemplate [ 1 ] ,
settings ,
specificBlacklist ,
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-07-25 09:38:28 +01:00
* @param armorItem The armor items db template
2024-01-07 15:34:59 +00:00
* @returns Array of plate tpls to choose from
* /
2024-02-02 13:54:07 -05:00
protected filterPlateModsForSlotByLevel (
settings : IGenerateEquipmentProperties ,
modSlot : string ,
existingPlateTplPool : string [ ] ,
armorItem : ITemplateItem ,
2024-07-23 11:12:53 -04:00
) : IFilterPlateModsForSlotByLevelResult {
const result : IFilterPlateModsForSlotByLevelResult = {
result : Result.UNKNOWN_FAILURE ,
plateModTpls : undefined ,
} ;
2024-01-12 17:00:22 +00:00
2024-01-07 14:46:25 +00:00
// Not pmc or not a plate slot, return original mod pool array
2024-07-23 11:12:53 -04:00
if ( ! this . itemHelper . isRemovablePlateSlot ( modSlot ) ) {
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
2024-05-17 15:32:41 -04:00
const plateSlotWeights = settings . botEquipmentConfig ? . armorPlateWeighting ? . find (
( armorWeight ) = >
2024-10-08 20:14:43 +01:00
settings . botData . level >= armorWeight . levelRange . min &&
settings . botData . level <= armorWeight . levelRange . max ,
2024-02-02 13:54:07 -05:00
) ;
2024-07-23 11:12:53 -04:00
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 ] ;
2024-07-23 11:12:53 -04:00
if ( ! plateWeights ) {
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 ) ;
2024-02-02 13:54:07 -05:00
2024-01-07 15:34:59 +00:00
// Convert the array of ids into database items
2024-05-17 15:32:41 -04:00
const platesFromDb = existingPlateTplPool . map ( ( plateTpl ) = > this . itemHelper . getItem ( plateTpl ) [ 1 ] ) ;
2024-01-07 15:34:59 +00:00
// Filter plates to the chosen level based on its armorClass property
2024-05-17 15:32:41 -04:00
const platesOfDesiredLevel = platesFromDb . filter ( ( item ) = > item . _props . armorClass === chosenArmorPlateLevel ) ;
2024-07-23 11:12:53 -04:00
if ( platesOfDesiredLevel . length === 0 ) {
2024-02-02 13:54:07 -05:00
this . logger . debug (
2024-05-12 10:29:18 +01:00
` Plate filter was too restrictive for armor: ${ armorItem . _name } ${ armorItem . _id } , unable to find plates of level: ${ chosenArmorPlateLevel } . Using mod items default plate ` ,
2024-02-02 13:54:07 -05:00
) ;
2024-01-07 15:34:59 +00:00
2024-05-17 15:32:41 -04:00
const relatedItemDbModSlot = armorItem . _props . Slots . find ( ( slot ) = > slot . _name . toLowerCase ( ) === modSlot ) ;
2024-02-02 13:54:07 -05:00
const defaultPlate = relatedItemDbModSlot . _props . filters [ 0 ] . Plate ;
2024-07-23 11:12:53 -04:00
if ( ! defaultPlate ) {
2024-01-12 17:00:22 +00:00
// 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 ) ;
2024-07-23 11:12:53 -04:00
if ( defaultPreset ) {
2024-05-17 15:32:41 -04:00
const relatedPresetSlot = defaultPreset . _items . find (
( item ) = > item . slotId ? . toLowerCase ( ) === modSlot ,
2024-02-02 13:54:07 -05:00
) ;
2024-07-23 11:12:53 -04:00
if ( relatedPresetSlot ) {
2024-01-12 17:00:22 +00:00
result . result = Result . SUCCESS ;
result . plateModTpls = [ relatedPresetSlot . _tpl ] ;
return result ;
}
}
result . result = Result . NO_DEFAULT_FILTER ;
return result ;
}
result . result = Result . SUCCESS ;
2024-01-25 13:45:42 +00:00
result . plateModTpls = [ defaultPlate ] ;
2024-01-12 17:00:22 +00:00
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 ;
2024-05-17 15:32:41 -04:00
result . plateModTpls = platesOfDesiredLevel . map ( ( item ) = > item . _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
2024-05-21 23:22:16 +01:00
* @param sessionId Session id
* @param request Data used to generate the weapon
2023-03-03 15:23:46 +00:00
* @returns Weapon + mods array
* /
2024-09-24 12:47:29 +01:00
public generateModsForWeapon ( sessionId : string , request : IGenerateWeaponRequest ) : IItem [ ] {
2023-03-03 15:23:46 +00:00
const pmcProfile = this . profileHelper . getPmcProfile ( sessionId ) ;
// Get pool of mods that fit weapon
2024-05-21 23:22:16 +01:00
const compatibleModsPool = request . modPool [ request . parentTemplate . _id ] ;
2023-03-03 15:23:46 +00:00
2023-11-13 11:05:05 -05:00
if (
2024-05-17 15:32:41 -04:00
! (
2024-07-23 11:12:53 -04:00
request . parentTemplate . _props . Slots . length ||
request . parentTemplate . _props . Cartridges ? . length ||
request . parentTemplate . _props . Chambers ? . length
2024-05-17 15:32:41 -04:00
)
2024-07-23 11:12:53 -04: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" , {
2024-05-21 23:22:16 +01:00
weaponName : request.parentTemplate._name ,
weaponId : request.parentTemplate._id ,
botRole : request.botData.role ,
2023-11-13 11:05:05 -05:00
} ) ,
) ;
2023-03-03 15:23:46 +00:00
2024-05-21 23:22:16 +01:00
return request . weapon ;
2023-03-03 15:23:46 +00:00
}
2024-05-21 23:22:16 +01:00
const botEquipConfig = this . botConfig . equipment [ request . botData . equipmentRole ] ;
2023-11-13 11:05:05 -05:00
const botEquipBlacklist = this . botEquipmentFilterService . getBotEquipmentBlacklist (
2024-05-21 23:22:16 +01:00
request . botData . equipmentRole ,
2023-11-13 11:05:05 -05:00
pmcProfile . Info . Level ,
) ;
2024-07-23 11:12:53 -04:00
const botWeaponSightWhitelist = this . botEquipmentFilterService . getBotWeaponSightWhitelist (
request . botData . equipmentRole ,
) ;
const randomisationSettings = this . botHelper . getBotRandomizationDetails ( request . botData . level , botEquipConfig ) ;
2023-03-03 15:23:46 +00:00
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 ) ) ;
2024-07-23 11:12:53 -04:00
for ( const modSlot of sortedModKeys ) {
2023-03-03 15:23:46 +00:00
// Check weapon has slot for mod to fit in
2024-05-21 23:22:16 +01:00
const modsParentSlot = this . getModItemSlotFromDb ( modSlot , request . parentTemplate ) ;
2024-07-23 11:12:53 -04:00
if ( ! modsParentSlot ) {
2023-11-13 11:05:05 -05:00
this . logger . error (
this . localisationService . getText ( "bot-weapon_missing_mod_slot" , {
modSlot : modSlot ,
2024-05-21 23:22:16 +01:00
weaponId : request.parentTemplate._id ,
weaponName : request.parentTemplate._name ,
botRole : request.botData.role ,
2023-11-13 11:05:05 -05:00
} ) ,
) ;
2023-03-03 15:23:46 +00:00
continue ;
}
// Check spawn chance of mod
2024-05-21 23:22:16 +01:00
const modSpawnResult = this . shouldModBeSpawned (
modsParentSlot ,
modSlot ,
request . modSpawnChances ,
2024-07-23 11:12:53 -04:00
botEquipConfig ,
) ;
if ( modSpawnResult === ModSpawn . SKIP ) {
2023-03-03 15:23:46 +00:00
continue ;
}
2023-10-29 20:45:35 +00:00
const isRandomisableSlot = randomisationSettings ? . randomisedWeaponModSlots ? . includes ( modSlot ) ? ? false ;
2024-05-21 23:22:16 +01:00
const modToSpawnRequest : IModToSpawnRequest = {
modSlot : modSlot ,
isRandomisableSlot : isRandomisableSlot ,
botWeaponSightWhitelist : botWeaponSightWhitelist ,
botEquipBlacklist : botEquipBlacklist ,
itemModPool : compatibleModsPool ,
weapon : request.weapon ,
ammoTpl : request.ammoTpl ,
parentTemplate : request.parentTemplate ,
modSpawnResult : modSpawnResult ,
weaponStats : request.weaponStats ,
2024-08-16 23:19:07 +01:00
conflictingItemTpls : request.conflictingItemTpls ,
botData : request.botData ,
2024-05-21 23:22:16 +01:00
} ;
const modToAdd = this . chooseModToPutIntoSlot ( modToSpawnRequest ) ;
2023-03-03 15:23:46 +00:00
// Compatible mod not found
2024-07-23 11:12:53 -04:00
if ( ! modToAdd || typeof modToAdd === "undefined" ) {
2023-03-03 15:23:46 +00:00
continue ;
}
2024-07-23 11:12:53 -04:00
if (
! this . isModValidForSlot ( modToAdd , modsParentSlot , modSlot , request . parentTemplate , request . botData . role )
) {
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 (
2024-05-21 23:22:16 +01:00
request . botData . equipmentRole ,
2023-11-13 11:05:05 -05:00
modToAddTemplate ,
2024-05-21 23:22:16 +01:00
request . modLimits ,
request . parentTemplate ,
request . weapon ,
2023-11-13 11:05:05 -05:00
)
2024-07-23 11:12:53 -04:00
) {
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
2024-07-23 11:12:53 -04:00
if ( this . modSlotCanHoldScope ( modSlot , modToAddTemplate . _parent ) ) {
2023-03-03 15:23:46 +00:00
// 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" ] ;
2024-05-21 23:22:16 +01:00
this . adjustSlotSpawnChances ( request . modSpawnChances , scopeSlots , 100 ) ;
2023-03-03 15:23:46 +00:00
// Hydrate pool of mods that fit into mount as its a randomisable slot
2024-07-23 11:12:53 -04:00
if ( isRandomisableSlot ) {
2023-03-03 15:23:46 +00:00
// Add scope mods to modPool dictionary to ensure the mount has a scope in the pool to pick
2024-07-23 11:12:53 -04:00
this . addCompatibleModsForProvidedMod (
"mod_scope" ,
modToAddTemplate ,
request . modPool ,
botEquipBlacklist ,
) ;
2023-03-03 15:23:46 +00:00
}
}
2023-10-10 11:03:20 +00:00
// If picked item is muzzle adapter that can hold a child, adjust spawn chance
2024-07-23 11:12:53 -04:00
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
2024-05-21 23:22:16 +01:00
this . adjustSlotSpawnChances ( request . modSpawnChances , muzzleSlots , 95 ) ;
2023-10-10 11:03:20 +00:00
}
2023-03-03 15:23:46 +00:00
// If front/rear sight are to be added, set opposite to 100% chance
2024-07-23 11:12:53 -04:00
if ( this . modIsFrontOrRearSight ( modSlot , modToAddTemplate . _id ) ) {
2024-05-21 23:22:16 +01:00
request . modSpawnChances . mod_sight_front = 100 ;
request . modSpawnChances . mod_sight_rear = 100 ;
2023-03-03 15:23:46 +00:00
}
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 (
2024-07-23 11:12:53 -04:00
modSlot === "mod_handguard" &&
modToAddTemplate . _props . Slots . some ( ( slot ) = > slot . _name === "mod_handguard" ) &&
! request . weapon . some ( ( item ) = > item . slotId === "mod_launcher" )
) {
2023-10-26 20:31:05 +01:00
// Needed for handguards with lower
2024-05-21 23:22:16 +01:00
request . modSpawnChances . mod_handguard = 100 ;
2023-10-26 20:31:05 +01:00
}
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
2024-08-12 17:43:42 +01:00
// Or if bot has stock force enabled
if ( this . shouldForceSubStockSlots ( modSlot , botEquipConfig , modToAddTemplate ) ) {
2023-03-03 15:23:46 +00:00
// Stock mod can take additional stocks, could be a locking device, force 100% chance
2024-08-12 17:43:42 +01:00
const subStockSlots = [ "mod_stock" , "mod_stock_000" , "mod_stock_001" , "mod_stock_akms" ] ;
this . adjustSlotSpawnChances ( request . modSpawnChances , subStockSlots , 100 ) ;
2024-05-21 23:22:16 +01:00
}
// Gather stats on mods being added to weapon
2024-07-23 11:12:53 -04:00
if ( this . itemHelper . isOfBaseclass ( modToAddTemplate . _id , BaseClasses . IRON_SIGHT ) ) {
if ( modSlot === "mod_sight_front" ) {
2024-05-21 23:22:16 +01:00
request . weaponStats . hasFrontIronSight = true ;
2024-07-23 11:12:53 -04:00
} else if ( modSlot === "mod_sight_rear" ) {
2024-05-21 23:22:16 +01:00
request . weaponStats . hasRearIronSight = true ;
}
2024-07-23 11:12:53 -04:00
} else if (
! request . weaponStats . hasOptic &&
this . itemHelper . isOfBaseclass ( modToAddTemplate . _id , BaseClasses . SIGHTS )
) {
2024-05-21 23:22:16 +01:00
request . weaponStats . hasOptic = true ;
2023-03-03 15:23:46 +00:00
}
const modId = this . hashUtil . generate ( ) ;
2024-07-23 11:12:53 -04:00
request . weapon . push (
this . createModItem (
modId ,
modToAddTemplate . _id ,
request . weaponId ,
modSlot ,
modToAddTemplate ,
request . botData . role ,
) ,
) ;
2023-11-13 11:05:05 -05:00
2024-08-16 23:19:07 +01:00
// Update conflicting item list now item has been chosen
for ( const conflictingItem of modToAddTemplate . _props . ConflictingItems ) {
request . conflictingItemTpls . add ( conflictingItem ) ;
}
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
2024-05-29 15:15:45 +01:00
const modParentItem = this . itemHelper . getItem ( modToAddTemplate . _parent ) [ 1 ] ;
2024-07-23 11:12:53 -04:00
if ( this . botWeaponGeneratorHelper . magazineIsCylinderRelated ( modParentItem . _name ) ) {
2023-03-03 15:23:46 +00:00
// We don't have child mods, we need to create the camoras for the magazines instead
2024-05-21 23:22:16 +01:00
this . fillCamora ( request . weapon , request . modPool , modId , modToAddTemplate ) ;
2024-07-23 11:12:53 -04:00
} else {
2024-05-21 23:22:16 +01:00
let containsModInPool = Object . keys ( request . modPool ) . includes ( modToAddTemplate . _id ) ;
2023-03-03 15:23:46 +00:00
// 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
2024-07-23 11:12:53 -04:00
if ( isRandomisableSlot && ! containsModInPool && modToAddTemplate . _props . Slots . length > 0 ) {
2023-03-03 15:23:46 +00:00
const modFromService = this . botEquipmentModPoolService . getModsForWeaponSlot ( modToAddTemplate . _id ) ;
2024-07-23 11:12:53 -04:00
if ( Object . keys ( modFromService ? ? { } ) . length > 0 ) {
2024-05-21 23:22:16 +01:00
request . modPool [ modToAddTemplate . _id ] = modFromService ;
2023-03-03 15:23:46 +00:00
containsModInPool = true ;
}
}
2024-07-23 11:12:53 -04:00
if ( containsModInPool ) {
2024-05-21 23:22:16 +01:00
const recursiveRequestData : IGenerateWeaponRequest = {
weapon : request.weapon ,
modPool : request.modPool ,
weaponId : modId ,
parentTemplate : modToAddTemplate ,
modSpawnChances : request.modSpawnChances ,
ammoTpl : request.ammoTpl ,
botData : {
role : request.botData.role ,
level : request.botData.level ,
2024-07-23 11:12:53 -04:00
equipmentRole : request.botData.equipmentRole ,
} ,
2024-05-21 23:22:16 +01:00
modLimits : request.modLimits ,
weaponStats : request.weaponStats ,
2024-08-16 23:19:07 +01:00
conflictingItemTpls : request.conflictingItemTpls ,
2024-05-21 23:22:16 +01:00
} ;
2023-11-13 11:05:05 -05:00
// Call self recursively to add mods to this mod
2024-07-23 11:12:53 -04:00
this . generateModsForWeapon ( sessionId , recursiveRequestData ) ;
2023-03-03 15:23:46 +00:00
}
}
}
2024-05-21 23:22:16 +01:00
return request . weapon ;
2023-03-03 15:23:46 +00:00
}
2024-08-12 17:43:42 +01:00
/ * *
* Should the provided bot have its stock chance values altered to 100 %
* @param modSlot Slot to check
* @param botEquipConfig Bots equipment config / chance values
* @param modToAddTemplate Mod being added to bots weapon
* @returns True if it should
* /
protected shouldForceSubStockSlots (
modSlot : string ,
botEquipConfig : EquipmentFilters ,
modToAddTemplate : ITemplateItem ,
) : boolean {
// Slots a weapon can store its stock in
const stockSlots = [ "mod_stock" , "mod_stock_000" , "mod_stock_001" , "mod_stock_akms" ] ;
// Can the stock hold child items
const hasSubSlots = modToAddTemplate . _props . Slots ? . length > 0 ;
return ( stockSlots . includes ( modSlot ) && hasSubSlots ) || botEquipConfig . forceStock ;
}
2023-03-03 15:23:46 +00:00
/ * *
* Is this modslot a front or rear sight
* @param modSlot Slot to check
* @returns true if it ' s a front / rear sight
* /
2024-07-23 11:12:53 -04:00
protected modIsFrontOrRearSight ( modSlot : string , tpl : string ) : boolean {
2024-05-12 09:31:40 +01:00
// Gas block /w front sight is special case, deem it a 'front sight' too
2024-07-23 11:12:53 -04:00
if ( modSlot === "mod_gas_block" && tpl === "5ae30e795acfc408fb139a0b" ) {
2024-05-17 15:32:41 -04:00
// 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
* /
2024-07-23 11:12:53 -04:00
protected modSlotCanHoldScope ( modSlot : string , modsParentId : string ) : boolean {
2024-05-17 15:32:41 -04:00
return (
[
"mod_scope" ,
"mod_mount" ,
"mod_mount_000" ,
"mod_scope_000" ,
"mod_scope_001" ,
"mod_scope_002" ,
"mod_scope_003" ,
] . 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 (
2024-09-24 12:47:29 +01:00
modSpawnChances : IModsChances ,
2023-11-13 11:05:05 -05:00
modSlotsToAdjust : string [ ] ,
newChancePercent : number ,
2024-07-23 11:12:53 -04:00
) : void {
if ( ! modSpawnChances ) {
2023-03-21 14:19:49 +00:00
this . logger . warning ( "Unable to adjust scope spawn chances as spawn chance object is empty" ) ;
return ;
}
2024-07-23 11:12:53 -04:00
if ( ! modSlotsToAdjust ) {
2023-10-10 11:03:20 +00:00
return ;
}
2023-03-21 14:19:49 +00:00
2024-07-23 11:12:53 -04:00
for ( const modName of modSlotsToAdjust ) {
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
}
2024-06-19 10:41:55 +01:00
/ * *
* Does the provided modSlot allow muzzle - related items
* @param modSlot Slot id to check
* @param modsParentId OPTIONAL : parent id of modslot being checked
* @returns True if modSlot can have muzzle - related items
* /
2024-07-23 11:12:53 -04:00
protected modSlotCanHoldMuzzleDevices ( modSlot : string , modsParentId? : string ) : boolean {
2023-10-10 11:03:20 +00:00
return [ "mod_muzzle" , "mod_muzzle_000" , "mod_muzzle_001" ] . includes ( modSlot . toLowerCase ( ) ) ;
}
2024-06-19 10:41:55 +01:00
/ * *
* Sort mod slots into an ordering that maximises chance of a successful weapon generation
* @param unsortedSlotKeys Array of mod slot strings to sort
* @returns Sorted array
* /
2024-07-23 11:12:53 -04:00
protected sortModKeys ( unsortedSlotKeys : string [ ] ) : string [ ] {
2024-06-19 10:41:55 +01:00
// No need to sort with only 1 item in array
2024-07-23 11:12:53 -04:00
if ( unsortedSlotKeys . length <= 1 ) {
2024-06-19 10:41:55 +01:00
return unsortedSlotKeys ;
2023-03-03 15:23:46 +00:00
}
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" ;
2024-07-23 11:12:53 -04:00
if ( unsortedSlotKeys . includes ( modHandguardKey ) ) {
2023-10-29 16:35:03 +00:00
sortedKeys . push ( modHandguardKey ) ;
2024-06-19 10:41:55 +01:00
unsortedSlotKeys . splice ( unsortedSlotKeys . indexOf ( modHandguardKey ) , 1 ) ;
2023-10-29 16:35:03 +00:00
}
2024-07-23 11:12:53 -04:00
if ( unsortedSlotKeys . includes ( modBarrelKey ) ) {
2023-03-03 15:23:46 +00:00
sortedKeys . push ( modBarrelKey ) ;
2024-06-19 10:41:55 +01:00
unsortedSlotKeys . splice ( unsortedSlotKeys . indexOf ( modBarrelKey ) , 1 ) ;
2023-03-03 15:23:46 +00:00
}
2024-07-23 11:12:53 -04:00
if ( unsortedSlotKeys . includes ( modMount001Key ) ) {
2023-03-03 15:23:46 +00:00
sortedKeys . push ( modMount001Key ) ;
2024-06-19 10:41:55 +01:00
unsortedSlotKeys . splice ( unsortedSlotKeys . indexOf ( modMount001Key ) , 1 ) ;
2023-03-03 15:23:46 +00:00
}
2024-07-23 11:12:53 -04:00
if ( unsortedSlotKeys . includes ( modRecieverKey ) ) {
2023-03-03 15:23:46 +00:00
sortedKeys . push ( modRecieverKey ) ;
2024-06-19 10:41:55 +01:00
unsortedSlotKeys . splice ( unsortedSlotKeys . indexOf ( modRecieverKey ) , 1 ) ;
2023-03-03 15:23:46 +00:00
}
2023-11-13 11:05:05 -05:00
2024-07-23 11:12:53 -04:00
if ( unsortedSlotKeys . includes ( modPistolGrip ) ) {
2023-03-03 15:23:46 +00:00
sortedKeys . push ( modPistolGrip ) ;
2024-06-19 10:41:55 +01:00
unsortedSlotKeys . splice ( unsortedSlotKeys . indexOf ( modPistolGrip ) , 1 ) ;
2023-03-03 15:23:46 +00:00
}
2024-07-23 11:12:53 -04:00
if ( unsortedSlotKeys . includes ( modGasBlockKey ) ) {
2023-10-29 16:35:03 +00:00
sortedKeys . push ( modGasBlockKey ) ;
2024-06-19 10:41:55 +01:00
unsortedSlotKeys . splice ( unsortedSlotKeys . indexOf ( modGasBlockKey ) , 1 ) ;
2023-03-03 15:23:46 +00:00
}
2024-07-23 11:12:53 -04:00
if ( unsortedSlotKeys . includes ( modStockKey ) ) {
2023-03-03 15:23:46 +00:00
sortedKeys . push ( modStockKey ) ;
2024-06-19 10:41:55 +01:00
unsortedSlotKeys . splice ( unsortedSlotKeys . indexOf ( modStockKey ) , 1 ) ;
2023-03-03 15:23:46 +00:00
}
2024-07-23 11:12:53 -04:00
if ( unsortedSlotKeys . includes ( modMountKey ) ) {
2023-03-03 15:23:46 +00:00
sortedKeys . push ( modMountKey ) ;
2024-06-19 10:41:55 +01:00
unsortedSlotKeys . splice ( unsortedSlotKeys . indexOf ( modMountKey ) , 1 ) ;
2023-03-03 15:23:46 +00:00
}
2024-07-23 11:12:53 -04:00
if ( unsortedSlotKeys . includes ( modScopeKey ) ) {
2023-03-03 15:23:46 +00:00
sortedKeys . push ( modScopeKey ) ;
2024-06-19 10:41:55 +01:00
unsortedSlotKeys . splice ( unsortedSlotKeys . indexOf ( modScopeKey ) , 1 ) ;
2023-03-03 15:23:46 +00:00
}
2024-06-19 10:41:55 +01:00
sortedKeys . push ( . . . unsortedSlotKeys ) ;
2023-03-03 15:23:46 +00:00
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
* /
2024-09-24 12:47:29 +01:00
protected getModItemSlotFromDb ( modSlot : string , parentTemplate : ITemplateItem ) : ISlot {
2023-12-27 23:29:37 +00:00
const modSlotLower = modSlot . toLowerCase ( ) ;
2024-07-23 11:12:53 -04:00
switch ( modSlotLower ) {
2023-03-03 15:23:46 +00:00
case "patron_in_weapon" :
case "patron_in_weapon_000" :
case "patron_in_weapon_001" :
2024-05-17 15:32:41 -04:00
return parentTemplate . _props . Chambers . find ( ( chamber ) = > chamber . _name . includes ( modSlotLower ) ) ;
2023-03-03 15:23:46 +00:00
case "cartridges" :
2024-05-17 15:32:41 -04:00
return parentTemplate . _props . Cartridges . find ( ( c ) = > c . _name . toLowerCase ( ) === modSlotLower ) ;
2023-03-03 15:23:46 +00:00
default :
2024-05-17 15:32:41 -04: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
2024-10-08 20:14:43 +01:00
* @param itemSlot slot the item sits in from db
* @param modSlotName Name of slot the mod sits in
2023-03-03 15:23:46 +00:00
* @param modSpawnChances Chances for various mod spawns
2024-02-05 22:02:03 +00:00
* @param botEquipConfig Various config settings for generating this type of bot
2024-01-25 13:45:42 +00:00
* @returns ModSpawn . SPAWN when mod should be spawned , ModSpawn . DEFAULT_MOD when default mod should spawn , ModSpawn . SKIP when mod is skipped
2023-03-03 15:23:46 +00:00
* /
2024-02-05 22:02:03 +00:00
protected shouldModBeSpawned (
2024-09-24 12:47:29 +01:00
itemSlot : ISlot ,
2024-10-08 20:14:43 +01:00
modSlotName : string ,
2024-09-24 12:47:29 +01:00
modSpawnChances : IModsChances ,
2024-02-05 22:02:03 +00:00
botEquipConfig : EquipmentFilters ,
2024-07-23 11:12:53 -04:00
) : ModSpawn {
2024-01-25 13:45:42 +00:00
const slotRequired = itemSlot . _required ;
2024-10-08 20:14:43 +01:00
if ( this . getAmmoContainers ( ) . includes ( modSlotName ) ) {
// Always force mags/cartridges in weapon to spawn
2024-02-02 13:54:07 -05:00
return ModSpawn . SPAWN ;
2024-01-25 13:45:42 +00:00
}
2024-10-08 20:14:43 +01:00
const spawnMod = this . probabilityHelper . rollChance ( modSpawnChances [ modSlotName ] ) ;
if ( ! spawnMod && ( slotRequired || botEquipConfig . weaponSlotIdsToMakeRequired ? . includes ( modSlotName ) ) ) {
// Edge case: Mod is required but spawn chance roll failed, choose default mod spawn for slot
2024-01-25 13:45:42 +00:00
return ModSpawn . DEFAULT_MOD ;
2023-03-03 15:23:46 +00:00
}
2024-02-02 13:54:07 -05:00
return spawnMod ? ModSpawn.SPAWN : ModSpawn.SKIP ;
2023-03-03 15:23:46 +00:00
}
/ * *
2024-05-21 23:22:16 +01:00
* Choose a mod to fit into the desired slot
* @param request Data used to choose an appropriate mod with
2024-01-25 13:45:42 +00:00
* @returns itemHelper . getItem ( ) result
2023-03-03 15:23:46 +00:00
* /
2024-07-23 11:12:53 -04:00
protected chooseModToPutIntoSlot ( request : IModToSpawnRequest ) : [ boolean , ITemplateItem ] | undefined {
2024-01-25 13:45:42 +00:00
/** Slot mod will fill */
2024-05-28 14:35:38 +00:00
const parentSlot = request . parentTemplate . _props . Slots ? . find ( ( i ) = > i . _name === request . modSlot ) ;
2024-05-21 23:22:16 +01:00
const weaponTemplate = this . itemHelper . getItem ( request . weapon [ 0 ] . _tpl ) [ 1 ] ;
2023-11-13 11:05:05 -05:00
2023-03-03 15:23:46 +00:00
// It's ammo, use predefined ammo parameter
2024-07-23 11:12:53 -04:00
if ( this . getAmmoContainers ( ) . includes ( request . modSlot ) && request . modSlot !== "mod_magazine" ) {
2024-05-21 23:22:16 +01:00
return this . itemHelper . getItem ( request . ammoTpl ) ;
2023-03-03 15:23:46 +00:00
}
2024-02-02 13:54:07 -05:00
2024-01-27 18:12:13 +00:00
// Ensure there's a pool of mods to pick from
2024-08-16 23:19:07 +01:00
let modPool = this . getModPoolForSlot ( request , weaponTemplate ) ;
if ( ! modPool && ! parentSlot ? . _required ) {
2024-01-27 18:12:13 +00:00
// Nothing in mod pool + item not required
2024-07-23 11:12:53 -04:00
this . logger . debug (
2024-08-16 23:19:07 +01:00
` Mod pool for optional slot: ${ request . modSlot } on item: ${ request . parentTemplate . _name } was empty, skipping mod ` ,
2024-07-23 11:12:53 -04:00
) ;
2024-05-27 20:06:07 +00:00
return undefined ;
2024-01-27 18:12:13 +00:00
}
2023-03-03 15:23:46 +00:00
2024-01-27 18:12:13 +00:00
// Filter out non-whitelisted scopes, use full modpool if filtered pool would have no elements
2024-07-23 11:12:53 -04:00
if ( request . modSlot . includes ( "mod_scope" ) && request . botWeaponSightWhitelist ) {
2024-01-27 18:12:13 +00:00
// scope pool has more than one scope
2024-07-23 11:12:53 -04:00
if ( modPool . length > 1 ) {
2024-05-21 23:22:16 +01:00
modPool = this . filterSightsByWeaponType ( request . weapon [ 0 ] , modPool , request . botWeaponSightWhitelist ) ;
}
}
2024-07-23 17:30:20 +01:00
if ( request . modSlot === "mod_gas_block" ) {
2024-07-23 11:12:53 -04:00
if ( request . weaponStats . hasOptic && modPool . length > 1 ) {
2024-05-21 23:22:16 +01:00
// Attempt to limit modpool to low profile gas blocks when weapon has an optic
2024-07-23 11:12:53 -04:00
const onlyLowProfileGasBlocks = modPool . filter ( ( tpl ) = >
this . botConfig . lowProfileGasBlockTpls . includes ( tpl ) ,
) ;
if ( onlyLowProfileGasBlocks . length > 0 ) {
2024-05-21 23:22:16 +01:00
modPool = onlyLowProfileGasBlocks ;
}
2024-07-23 11:12:53 -04:00
} else if ( request . weaponStats . hasRearIronSight && modPool . length > 1 ) {
2024-05-21 23:22:16 +01:00
// Attempt to limit modpool to high profile gas blocks when weapon has rear iron sight + no front iron sight
2024-07-23 11:12:53 -04:00
const onlyHighProfileGasBlocks = modPool . filter (
( tpl ) = > ! this . botConfig . lowProfileGasBlockTpls . includes ( tpl ) ,
) ;
if ( onlyHighProfileGasBlocks . length > 0 ) {
2024-05-21 23:22:16 +01:00
modPool = onlyHighProfileGasBlocks ;
}
2023-03-03 15:23:46 +00:00
}
2024-01-27 18:12:13 +00:00
}
2024-02-02 13:54:07 -05:00
2024-01-27 18:12:13 +00:00
// Pick random mod that's compatible
2024-07-28 11:42:45 +01:00
const chosenModResult = this . getCompatibleWeaponModTplForSlotFromPool (
2024-08-16 23:19:07 +01:00
request ,
2024-02-02 13:54:07 -05:00
modPool ,
2024-07-23 17:30:20 +01:00
parentSlot ,
2024-05-21 23:22:16 +01:00
request . modSpawnResult ,
request . weapon ,
request . modSlot ,
2024-02-02 13:54:07 -05:00
) ;
2024-07-23 17:30:20 +01:00
if ( chosenModResult . slotBlocked && ! parentSlot . _required ) {
2024-01-27 18:12:13 +00:00
// Don't bother trying to fit mod, slot is completely blocked
2024-05-27 20:06:07 +00:00
return undefined ;
2024-01-27 18:12:13 +00:00
}
// Log if mod chosen was incompatible
2024-07-23 17:30:20 +01:00
if ( chosenModResult . incompatible && parentSlot . _required ) {
2024-01-27 18:12:13 +00:00
this . logger . debug ( chosenModResult . reason ) ;
2023-03-03 15:23:46 +00:00
}
// Get random mod to attach from items db for required slots if none found above
2024-07-23 11:12:53 -04:00
if ( ! chosenModResult . found && parentSlot !== undefined && parentSlot . _required ) {
2024-05-21 23:22:16 +01:00
chosenModResult . chosenTpl = this . getRandomModTplFromItemDb ( "" , parentSlot , request . modSlot , request . weapon ) ;
2024-01-27 18:12:13 +00:00
chosenModResult . found = true ;
2023-03-03 15:23:46 +00:00
}
// Compatible item not found + not required
2024-07-23 11:12:53 -04:00
if ( ! chosenModResult . found && parentSlot !== undefined && ! parentSlot . _required ) {
2024-05-27 20:06:07 +00:00
return undefined ;
2023-03-03 15:23:46 +00:00
}
2024-07-23 11:12:53 -04:00
if ( ! chosenModResult . found && parentSlot !== undefined ) {
if ( parentSlot . _required ) {
2023-11-13 11:05:05 -05:00
this . logger . warning (
2024-05-21 23:22:16 +01:00
` Required slot unable to be filled, ${ request . modSlot } on ${ request . parentTemplate . _name } ${ request . parentTemplate . _id } for weapon: ${ request . weapon [ 0 ] . _tpl } ` ,
2023-11-13 11:05:05 -05:00
) ;
2023-03-03 15:23:46 +00:00
}
2024-05-27 20:06:07 +00:00
return undefined ;
2023-03-03 15:23:46 +00:00
}
2024-07-23 17:30:20 +01:00
return this . itemHelper . getItem ( chosenModResult . chosenTpl ) ;
2024-01-27 18:12:13 +00:00
}
2024-06-19 10:41:55 +01:00
/ * *
2024-07-28 11:42:45 +01:00
* Choose a weapon mod tpl for a given slot from a pool of choices
* Checks chosen tpl is compatible with all existing weapon items
2024-06-19 10:41:55 +01:00
* @param modPool Pool of mods that can be picked from
* @param parentSlot Slot the picked mod will have as a parent
* @param choiceTypeEnum How should chosen tpl be treated : DEFAULT_MOD / SPAWN / SKIP
* @param weapon Array of weapon items chosen item will be added to
* @param modSlotName Name of slot picked mod will be placed into
* @returns Chosen weapon details
* /
2024-07-28 11:42:45 +01:00
protected getCompatibleWeaponModTplForSlotFromPool (
2024-08-16 23:19:07 +01:00
request : IModToSpawnRequest ,
2024-01-27 18:12:13 +00:00
modPool : string [ ] ,
2024-09-24 12:47:29 +01:00
parentSlot : ISlot ,
2024-06-19 10:41:55 +01:00
choiceTypeEnum : ModSpawn ,
2024-09-24 12:47:29 +01:00
weapon : IItem [ ] ,
2024-06-19 10:41:55 +01:00
modSlotName : string ,
2024-07-23 11:12:53 -04:00
) : IChooseRandomCompatibleModResult {
2024-07-28 11:42:45 +01:00
// Filter out incompatible mods from pool
2024-08-16 23:19:07 +01:00
let preFilteredModPool = this . getFilteredModPool ( modPool , request . conflictingItemTpls ) ;
2024-07-28 00:12:04 +01:00
if ( preFilteredModPool . length === 0 ) {
return {
incompatible : true ,
found : false ,
reason : ` Unable to add mod to ${ ModSpawn [ choiceTypeEnum ] } slot: ${ modSlotName } . All: ${ modPool . length } had conflicts ` ,
} ;
}
// Filter mod pool to only items that appear in parents allowed list
preFilteredModPool = preFilteredModPool . filter ( ( tpl ) = > parentSlot . _props . filters [ 0 ] . Filter . includes ( tpl ) ) ;
if ( preFilteredModPool . length === 0 ) {
return { incompatible : true , found : false , reason : "No mods found in parents allowed list" } ;
}
2024-07-28 11:42:45 +01:00
return this . getCompatibleModFromPool ( preFilteredModPool , choiceTypeEnum , weapon ) ;
}
2024-01-27 18:12:13 +00:00
2024-07-28 11:42:45 +01:00
/ * *
*
* @param modPool Pool of item Tpls to choose from
* @param modSpawnType How should the slot choice be handled - forced / normal etc
* @param weapon Weapon mods at current time
* @param modSlotName Name of mod slot being filled
* @returns IChooseRandomCompatibleModResult
* /
protected getCompatibleModFromPool (
modPool : string [ ] ,
modSpawnType : ModSpawn ,
2024-09-24 12:47:29 +01:00
weapon : IItem [ ] ,
2024-07-28 11:42:45 +01:00
) : IChooseRandomCompatibleModResult {
// Create exhaustable pool to pick mod item from
const exhaustableModPool = this . createExhaustableArray ( modPool ) ;
// Create default response if no compatible item is found below
const chosenModResult : IChooseRandomCompatibleModResult = {
incompatible : true ,
found : false ,
reason : "unknown" ,
} ;
// Limit how many attempts to find a compatible mod can occur before giving up
2024-07-28 00:12:04 +01:00
const maxBlockedAttempts = Math . round ( modPool . length * 0.75 ) ; // 75% of pool size
2024-01-29 22:41:08 +00:00
let blockedAttemptCount = 0 ;
2024-07-28 11:42:45 +01:00
let chosenTpl : string ;
2024-07-23 11:12:53 -04:00
while ( exhaustableModPool . hasValues ( ) ) {
2024-07-23 17:30:20 +01:00
chosenTpl = exhaustableModPool . getRandomValue ( ) ;
2024-07-28 11:42:45 +01:00
const pickedItemDetails = this . itemHelper . getItem ( chosenTpl ) ;
if ( ! pickedItemDetails [ 0 ] ) {
// Not valid item, try again
continue ;
}
if ( ! pickedItemDetails [ 1 ] . _props ) {
// no props data, try again
continue ;
}
// Success - Default wanted + only 1 item in pool
if ( modSpawnType === ModSpawn . DEFAULT_MOD && modPool . length === 1 ) {
2024-01-27 18:12:13 +00:00
chosenModResult . found = true ;
2024-01-29 21:27:36 +00:00
chosenModResult . incompatible = false ;
2024-01-27 18:12:13 +00:00
chosenModResult . chosenTpl = chosenTpl ;
break ;
}
2024-07-28 11:42:45 +01:00
// Check if existing weapon mods are incompatible with chosen item
const existingItemBlockingChoice = weapon . find ( ( item ) = >
pickedItemDetails [ 1 ] . _props . ConflictingItems ? . includes ( item . _tpl ) ,
2024-01-27 18:12:13 +00:00
) ;
2024-07-28 11:42:45 +01:00
if ( existingItemBlockingChoice ) {
2024-01-30 21:14:15 +00:00
// Give max of x attempts of picking a mod if blocked by another
2024-07-23 11:12:53 -04:00
if ( blockedAttemptCount > maxBlockedAttempts ) {
2024-07-28 11:42:45 +01:00
blockedAttemptCount = 0 ; // reset
2024-01-29 22:41:08 +00:00
break ;
}
blockedAttemptCount ++ ;
2024-07-28 11:42:45 +01:00
// Not compatible - Try again
2024-01-29 22:41:08 +00:00
continue ;
2024-01-27 18:12:13 +00:00
}
2024-02-02 13:54:07 -05:00
2024-07-28 11:42:45 +01:00
// Edge case- Some mod combos will never work, make sure this isnt the case
if ( this . weaponModComboIsIncompatible ( weapon , chosenTpl ) ) {
chosenModResult . reason = ` Chosen weapon mod: ${ chosenTpl } can never be compatible with existing weapon mods ` ;
2024-01-27 18:12:13 +00:00
break ;
}
2024-07-28 11:42:45 +01:00
// Success
chosenModResult . found = true ;
chosenModResult . incompatible = false ;
chosenModResult . chosenTpl = chosenTpl ;
break ;
2024-01-27 18:12:13 +00:00
}
return chosenModResult ;
2023-03-03 15:23:46 +00:00
}
2024-07-28 11:42:45 +01:00
protected createExhaustableArray < T > ( itemsToAddToArray : T [ ] ) {
return new ExhaustableArray < T > ( itemsToAddToArray , this . randomUtil , this . cloner ) ;
}
2024-07-28 00:12:04 +01:00
/ * *
* Get a list of mod tpls that are compatible with the current weapon
2024-08-16 23:19:07 +01:00
* @param modPool
* @param tplBlacklist Tpls that are incompatible and should not be used
2024-07-28 00:12:04 +01:00
* @returns string array of compatible mod tpls with weapon
* /
2024-08-16 23:19:07 +01:00
protected getFilteredModPool ( modPool : string [ ] , tplBlacklist : Set < string > ) : string [ ] {
return modPool . filter ( ( tpl ) = > ! tplBlacklist . has ( tpl ) ) ;
2024-07-28 00:12:04 +01:00
}
2024-01-25 13:45:42 +00:00
/ * *
* Filter mod pool down based on various criteria :
* Is slot flagged as randomisable
* Is slot required
* Is slot flagged as default mod only
2024-08-16 23:19:07 +01:00
* @param request
2024-01-25 13:45:42 +00:00
* @param weaponTemplate Mods root parent ( weapon / equipment )
2024-06-19 10:41:55 +01:00
* @returns Array of mod tpls
2024-01-25 13:45:42 +00:00
* /
2024-08-16 23:19:07 +01:00
protected getModPoolForSlot ( request : IModToSpawnRequest , weaponTemplate : ITemplateItem ) : string [ ] {
2024-01-25 13:45:42 +00:00
// Mod is flagged as being default only, try and find it in globals
2024-08-16 23:19:07 +01:00
if ( request . modSpawnResult === ModSpawn . DEFAULT_MOD ) {
return this . getModPoolForDefaultSlot ( request , weaponTemplate ) ;
}
2024-01-30 21:14:15 +00:00
2024-08-16 23:19:07 +01:00
if ( request . isRandomisableSlot ) {
return this . getDynamicModPool ( request . parentTemplate . _id , request . modSlot , request . botEquipBlacklist ) ;
}
2024-04-18 14:29:16 +00:00
2024-08-16 23:19:07 +01:00
// Required mod is not default or randomisable, use existing pool
return request . itemModPool [ request . modSlot ] ;
}
2024-01-25 13:45:42 +00:00
2024-08-16 23:19:07 +01:00
protected getModPoolForDefaultSlot ( request : IModToSpawnRequest , weaponTemplate : ITemplateItem ) : string [ ] {
const { itemModPool , modSlot , parentTemplate , botData , conflictingItemTpls } = request ;
const matchingModFromPreset = this . getMatchingModFromPreset ( request , weaponTemplate ) ;
if ( ! matchingModFromPreset ) {
2024-07-23 11:12:53 -04:00
if ( itemModPool [ modSlot ] ? . length > 1 ) {
2024-05-17 15:32:41 -04:00
this . logger . debug (
2024-08-16 23:19:07 +01:00
` ${ botData . role } No default: ${ modSlot } mod found for: ${ weaponTemplate . _name } , using existing pool ` ,
2024-05-17 15:32:41 -04:00
) ;
2024-05-12 10:15:08 +01:00
}
2024-01-25 13:45:42 +00:00
2024-01-30 21:14:15 +00:00
// Couldnt find default in globals, use existing mod pool data
2024-01-25 13:45:42 +00:00
return itemModPool [ modSlot ] ;
}
2024-08-16 23:19:07 +01:00
// Only filter mods down to single default item if it already exists in existing itemModPool, OR the default item has no children
// Filtering mod pool to item that wasnt already there can have problems;
// You'd have a mod being picked without any sub-mods in its chain, possibly resulting in missing required mods not being added
// Mod is in existing mod pool
if ( itemModPool [ modSlot ] . includes ( matchingModFromPreset . _tpl ) ) {
// Found mod on preset + it already exists in mod pool
return [ matchingModFromPreset . _tpl ] ;
2024-01-25 13:45:42 +00:00
}
2024-08-16 23:19:07 +01:00
// Get an array of items that are allowed in slot from parent item
// Check the filter of the slot to ensure a chosen mod fits
const parentSlotCompatibleItems = parentTemplate . _props . Slots ? . find (
( slot ) = > slot . _name . toLowerCase ( ) === modSlot . toLowerCase ( ) ,
) ? . _props . filters [ 0 ] . Filter ;
// Mod isnt in existing pool, only add if it has no children and exists inside parent filter
if (
parentSlotCompatibleItems ? . includes ( matchingModFromPreset . _tpl ) &&
this . itemHelper . getItem ( matchingModFromPreset . _tpl ) [ 1 ] . _props . Slots ? . length === 0
) {
// Chosen mod has no conflicts + no children + is in parent compat list
if ( ! conflictingItemTpls . has ( matchingModFromPreset . _tpl ) ) {
return [ matchingModFromPreset . _tpl ] ;
}
// Above chosen mod had conflicts with existing weapon mods
this . logger . debug (
` ${ botData . role } Chosen default: ${ modSlot } mod found for: ${ weaponTemplate . _name } weapon conflicts with item on weapon, cannot use default ` ,
) ;
const existingModPool = itemModPool [ modSlot ] ;
if ( existingModPool . length === 1 ) {
// The only item in pool isn't compatible
this . logger . debug (
` ${ botData . role } ${ modSlot } Mod pool for: ${ weaponTemplate . _name } weapon has only incompatible items, using parent list instead ` ,
) ;
// Last ditch, use full pool of items minus conflicts
const newListOfModsForSlot = parentSlotCompatibleItems . filter ( ( tpl ) = > ! conflictingItemTpls . has ( tpl ) ) ;
if ( newListOfModsForSlot . length > 0 ) {
return newListOfModsForSlot ;
}
}
// Return full mod pool
return itemModPool [ modSlot ] ;
}
// Tried everything, return mod pool
2024-01-25 13:45:42 +00:00
return itemModPool [ modSlot ] ;
}
2024-08-16 23:19:07 +01:00
protected getMatchingModFromPreset ( request : IModToSpawnRequest , weaponTemplate : ITemplateItem ) {
const matchingPreset = this . getMatchingPreset ( weaponTemplate , request . parentTemplate . _id ) ;
return matchingPreset ? . _items . find ( ( item ) = > item ? . slotId ? . toLowerCase ( ) === request . modSlot . toLowerCase ( ) ) ;
}
2024-02-05 15:36:05 +00:00
/ * *
2024-06-19 10:41:55 +01:00
* Get default preset for weapon OR get specific weapon presets for edge cases ( mp5 / silenced dvl )
* @param weaponTemplate Weapons db template
* @param parentItemTpl Tpl of the parent item
2024-05-12 10:15:08 +01:00
* @returns Default preset found
2024-02-05 15:36:05 +00:00
* /
2024-07-23 11:12:53 -04:00
protected getMatchingPreset ( weaponTemplate : ITemplateItem , parentItemTpl : string ) : IPreset | undefined {
2024-02-05 15:36:05 +00:00
// Edge case - using mp5sd reciever means default mp5 handguard doesnt fit
const isMp5sd = parentItemTpl === "5926f2e086f7745aae644231" ;
2024-07-23 11:12:53 -04:00
if ( isMp5sd ) {
2024-02-05 15:36:05 +00:00
return this . presetHelper . getPreset ( "59411abb86f77478f702b5d2" ) ;
}
2024-05-12 10:15:08 +01:00
// Edge case - dvl 500mm is the silenced barrel and has specific muzzle mods
const isDvl500mmSilencedBarrel = parentItemTpl === "5888945a2459774bf43ba385" ;
2024-07-23 11:12:53 -04:00
if ( isDvl500mmSilencedBarrel ) {
2024-02-05 15:36:05 +00:00
return this . presetHelper . getPreset ( "59e8d2b386f77445830dd299" ) ;
}
return this . presetHelper . getDefaultPreset ( weaponTemplate . _id ) ;
}
2023-11-17 12:40:23 +00:00
/ * *
* Temp fix to prevent certain combinations of weapons with mods that are known to be incompatible
2024-06-19 10:41:55 +01:00
* @param weapon Array of items that make up a weapon
2023-11-17 12:40:23 +00:00
* @param modTpl Mod to check compatibility with weapon
* @returns True if incompatible
* /
2024-09-24 12:47:29 +01:00
protected weaponModComboIsIncompatible ( weapon : IItem [ ] , modTpl : string ) : boolean {
2023-11-17 12:40:23 +00:00
// STM-9 + AR-15 Lone Star Ion Lite handguard
2024-07-23 11:12:53 -04:00
if ( weapon [ 0 ] . _tpl === "60339954d62c9b14ed777c06" && modTpl === "5d4405f0a4b9361e6a4e6bd9" ) {
2023-11-17 12:40:23 +00:00
return true ;
}
return false ;
}
2023-03-03 15:23:46 +00:00
/ * *
2024-06-19 10:41:55 +01:00
* Create a mod item with provided parameters as properties + add upd property
2023-03-03 15:23:46 +00:00
* @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
2024-06-19 10:41:55 +01:00
* @param botRole The bots role mod is being created for
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 ,
2024-09-24 12:47:29 +01:00
) : IItem {
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
* /
2024-07-23 11:12:53 -04:00
protected getAmmoContainers ( ) : string [ ] {
2023-03-03 15:23:46 +00:00
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
2024-06-19 10:41:55 +01:00
* @param fallbackModTpl Default value to return if parentSlot Filter is empty
* @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
2024-06-19 10:41:55 +01:00
* @param items Items to ensure picked mod is compatible with
* @returns Item tpl
2023-03-03 15:23:46 +00:00
* /
2024-05-28 14:35:38 +00:00
protected getRandomModTplFromItemDb (
2024-06-19 10:41:55 +01:00
fallbackModTpl : string ,
2024-09-24 12:47:29 +01:00
parentSlot : ISlot ,
2024-05-28 14:35:38 +00:00
modSlot : string ,
2024-09-24 12:47:29 +01:00
items : IItem [ ] ,
2024-07-23 11:12:53 -04:00
) : string | undefined {
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
2024-07-28 11:42:45 +01:00
const exhaustableModPool = this . createExhaustableArray ( allowedItems ) ;
2024-06-19 10:41:55 +01:00
let tmpModTpl = fallbackModTpl ;
2024-07-23 11:12:53 -04:00
while ( exhaustableModPool . hasValues ( ) ) {
2024-07-23 17:30:20 +01:00
tmpModTpl = exhaustableModPool . getRandomValue ( ) ;
2024-07-23 11:12:53 -04:00
if ( ! this . botGeneratorHelper . isItemIncompatibleWithCurrentItems ( items , tmpModTpl , modSlot ) . incompatible ) {
2023-03-03 15:23:46 +00:00
return tmpModTpl ;
}
}
// No mod found
2024-05-27 20:06:07 +00:00
return undefined ;
2023-03-03 15:23:46 +00:00
}
/ * *
2024-04-22 09:39:32 +01:00
* Check if mod exists in db + is for a required slot
* @param modToAdd Db template of mod to check
* @param slotAddedToTemplate Slot object the item will be placed as child into
* @param modSlot Slot the mod will fill
* @param parentTemplate Db template of the mods being added
2024-06-19 10:41:55 +01:00
* @param botRole Bots wildspawntype ( assault / pmcBot / exUsec etc )
* @returns True if valid for slot
2023-03-03 15:23:46 +00:00
* /
2023-11-13 11:05:05 -05:00
protected isModValidForSlot (
modToAdd : [ boolean , ITemplateItem ] ,
2024-09-24 12:47:29 +01:00
slotAddedToTemplate : ISlot ,
2023-11-13 11:05:05 -05:00
modSlot : string ,
parentTemplate : ITemplateItem ,
2024-02-02 13:54:07 -05:00
botRole : string ,
2024-07-23 11:12:53 -04:00
) : boolean {
2024-04-22 09:39:32 +01:00
const modBeingAddedDbTemplate = modToAdd [ 1 ] ;
2024-01-27 18:12:13 +00:00
2024-04-22 09:39:32 +01:00
// Mod lacks db template object
2024-07-23 11:12:53 -04:00
if ( ! modBeingAddedDbTemplate ) {
2023-11-13 11:05:05 -05:00
this . logger . error (
this . localisationService . getText ( "bot-no_item_template_found_when_adding_mod" , {
2024-04-22 09:39:32 +01:00
modId : modBeingAddedDbTemplate?._id ? ? "UNKNOWN" ,
2023-11-13 11:05:05 -05:00
modSlot : modSlot ,
} ) ,
) ;
2024-04-22 09:39:32 +01:00
this . logger . debug ( ` Item -> ${ parentTemplate ? . _id } ; Slot -> ${ modSlot } ` ) ;
2023-03-03 15:23:46 +00:00
return false ;
}
2024-04-22 09:39:32 +01:00
// Mod has invalid db item
2024-07-23 11:12:53 -04:00
if ( ! modToAdd [ 0 ] ) {
2024-04-22 09:39:32 +01:00
// Parent slot must be filled but db object is invalid, show warning and return false
2024-07-23 11:12:53 -04:00
if ( slotAddedToTemplate . _required ) {
2023-11-13 11:05:05 -05:00
this . logger . warning (
this . localisationService . getText ( "bot-unable_to_add_mod_item_invalid" , {
2024-04-22 09:39:32 +01:00
itemName : modBeingAddedDbTemplate?._name ? ? "UNKNOWN" ,
2023-11-13 11:05:05 -05:00
modSlot : modSlot ,
parentItemName : parentTemplate._name ,
2024-02-02 13:54:07 -05:00
botRole : botRole ,
2023-11-13 11:05:05 -05:00
} ) ,
) ;
2023-03-03 15:23:46 +00:00
}
return false ;
}
2024-04-22 09:39:32 +01:00
// Mod was found in db
2023-03-03 15:23:46 +00:00
return true ;
}
/ * *
* Find mod tpls of a provided type and add to modPool
2024-06-19 10:41:55 +01:00
* @param desiredSlotName Slot to look up and add we are adding tpls for ( e . g mod_scope )
2023-03-03 15:23:46 +00:00
* @param modTemplate db object for modItem we get compatible mods from
* @param modPool Pool of mods we are adding to
2024-06-19 10:41:55 +01:00
* @param botEquipBlacklist A blacklist of items that cannot be picked
2023-03-03 15:23:46 +00:00
* /
2023-11-13 11:05:05 -05:00
protected addCompatibleModsForProvidedMod (
desiredSlotName : string ,
modTemplate : ITemplateItem ,
2024-09-24 12:47:29 +01:00
modPool : IMods ,
2023-11-13 11:05:05 -05:00
botEquipBlacklist : EquipmentFilterDetails ,
2024-07-23 11:12:53 -04:00
) : void {
2024-05-28 14:35:38 +00:00
const desiredSlotObject = modTemplate . _props . Slots ? . find ( ( slot ) = > slot . _name . includes ( desiredSlotName ) ) ;
2024-07-23 11:12:53 -04:00
if ( desiredSlotObject ) {
2023-03-03 15:23:46 +00:00
const supportedSubMods = desiredSlotObject . _props . filters [ 0 ] . Filter ;
2024-07-23 11:12:53 -04:00
if ( supportedSubMods ) {
2023-03-03 15:23:46 +00:00
// Filter mods
2024-10-08 20:14:43 +01:00
let filteredMods = this . filterModsByBlacklist ( supportedSubMods , botEquipBlacklist , desiredSlotName ) ;
2024-07-23 11:12:53 -04: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 ;
}
2024-07-23 11:12:53 -04:00
if ( ! modPool [ modTemplate . _id ] ) {
2023-03-03 15:23:46 +00:00
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
2024-06-19 10:41:55 +01:00
* @param botEquipBlacklist Equipment that should not be picked
* @returns Array of compatible items for that slot
2023-03-03 15:23:46 +00:00
* /
2023-11-13 11:05:05 -05:00
protected getDynamicModPool (
parentItemId : string ,
modSlot : string ,
botEquipBlacklist : EquipmentFilterDetails ,
2024-07-23 11:12:53 -04:00
) : string [ ] {
2024-05-13 17:58:17 +00:00
const modsFromDynamicPool = this . cloner . clone (
2023-11-13 11:05:05 -05:00
this . botEquipmentModPoolService . getCompatibleModsForWeaponSlot ( parentItemId , modSlot ) ,
) ;
2023-03-03 15:23:46 +00:00
2024-10-08 20:14:43 +01:00
const filteredMods = this . filterModsByBlacklist ( modsFromDynamicPool , botEquipBlacklist , modSlot ) ;
2024-07-23 11:12:53 -04:00
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
2024-06-19 10:41:55 +01:00
* @param allowedMods Base mods to filter
* @param botEquipBlacklist Equipment blacklist
* @param modSlot Slot mods belong to
2023-03-03 15:23:46 +00:00
* @returns Filtered array of mod tpls
* /
2024-10-08 20:14:43 +01:00
protected filterModsByBlacklist (
2023-11-13 11:05:05 -05:00
allowedMods : string [ ] ,
botEquipBlacklist : EquipmentFilterDetails ,
modSlot : string ,
2024-07-23 11:12:53 -04:00
) : string [ ] {
2024-06-19 10:41:55 +01:00
// No blacklist, nothing to filter out
2024-07-23 11:12:53 -04:00
if ( ! botEquipBlacklist ) {
2023-03-03 15:23:46 +00:00
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
2024-05-17 15:32:41 -04:00
const blacklist = this . itemFilterService
. getBlacklistedItems ( )
. concat ( botEquipBlacklist . equipment [ modSlot ] || [ ] ) ;
result = allowedMods . filter ( ( tpl ) = > ! blacklist . includes ( tpl ) ) ;
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
2024-06-19 10:41:55 +01:00
* @param modPool ModPool which should include available cartridges
* @param cylinderMagParentId The CylinderMagazine ' s UID
* @param cylinderMagTemplate The CylinderMagazine ' s template
2023-03-03 15:23:46 +00:00
* /
2024-06-19 10:41:55 +01:00
protected fillCamora (
2024-09-24 12:47:29 +01:00
items : IItem [ ] ,
modPool : IMods ,
2024-06-19 10:41:55 +01:00
cylinderMagParentId : string ,
2024-07-23 11:12:53 -04:00
cylinderMagTemplate : ITemplateItem ,
) : void {
2024-06-19 10:41:55 +01:00
let itemModPool = modPool [ cylinderMagTemplate . _id ] ;
2024-07-23 11:12:53 -04: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" , {
2024-06-19 10:41:55 +01:00
weaponId : cylinderMagTemplate._id ,
weaponName : cylinderMagTemplate._name ,
2023-11-13 11:05:05 -05:00
} ) ,
) ;
2024-06-19 10:41:55 +01:00
const camoraSlots = cylinderMagTemplate . _props . Slots . filter ( ( slot ) = > slot . _name . startsWith ( "camora" ) ) ;
2023-11-04 20:11:09 +00:00
// Attempt to generate camora slots for item
2024-06-19 10:41:55 +01:00
modPool [ cylinderMagTemplate . _id ] = { } ;
2024-07-23 11:12:53 -04:00
for ( const camora of camoraSlots ) {
2024-06-19 10:41:55 +01:00
modPool [ cylinderMagTemplate . _id ] [ camora . _name ] = camora . _props . filters [ 0 ] . Filter ;
2023-11-04 20:11:09 +00:00
}
2024-06-19 10:41:55 +01:00
itemModPool = modPool [ cylinderMagTemplate . _id ] ;
2023-03-03 15:23:46 +00:00
}
2024-05-27 20:06:07 +00:00
let exhaustableModPool = undefined ;
2023-03-03 15:23:46 +00:00
let modSlot = "cartridges" ;
const camoraFirstSlot = "camora_000" ;
2024-07-23 11:12:53 -04:00
if ( modSlot in itemModPool ) {
2024-07-28 11:42:45 +01:00
exhaustableModPool = this . createExhaustableArray ( itemModPool [ modSlot ] ) ;
2024-07-23 11:12:53 -04:00
} else if ( camoraFirstSlot in itemModPool ) {
2023-03-03 15:23:46 +00:00
modSlot = camoraFirstSlot ;
2024-07-28 11:42:45 +01:00
exhaustableModPool = this . createExhaustableArray ( this . mergeCamoraPools ( itemModPool ) ) ;
2024-07-23 11:12:53 -04:00
} else {
2024-06-19 10:41:55 +01:00
this . logger . error ( this . localisationService . getText ( "bot-missing_cartridge_slot" , cylinderMagTemplate . _id ) ) ;
2023-03-03 15:23:46 +00:00
return ;
}
let modTpl : string ;
let found = false ;
2024-07-23 11:12:53 -04:00
while ( exhaustableModPool . hasValues ( ) ) {
2023-03-03 15:23:46 +00:00
modTpl = exhaustableModPool . getRandomValue ( ) ;
2024-07-23 11:12:53 -04:00
if ( ! this . botGeneratorHelper . isItemIncompatibleWithCurrentItems ( items , modTpl , modSlot ) . incompatible ) {
2023-03-03 15:23:46 +00:00
found = true ;
break ;
}
}
2024-07-23 11:12:53 -04:00
if ( ! found ) {
2023-03-03 15:23:46 +00:00
this . logger . error ( this . localisationService . getText ( "bot-no_compatible_camora_ammo_found" , modSlot ) ) ;
return ;
}
2024-07-23 11:12:53 -04:00
for ( const slot of cylinderMagTemplate . _props . Slots ) {
2023-03-03 15:23:46 +00:00
const modSlotId = slot . _name ;
const modId = this . hashUtil . generate ( ) ;
2024-06-19 10:40:59 +01:00
items . push ( { _id : modId , _tpl : modTpl , parentId : cylinderMagParentId , 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
2024-06-19 10:40:59 +01:00
* @param camorasWithShells Dictionary of camoras we want to merge into one array
* @returns String array of shells for multiple camora sources
2023-03-03 15:23:46 +00:00
* /
2024-07-23 11:12:53 -04:00
protected mergeCamoraPools ( camorasWithShells : Record < string , string [ ] > ) : string [ ] {
2024-06-19 10:40:59 +01:00
const uniqueShells = new Set < string > ( ) ;
2024-07-23 11:12:53 -04:00
for ( const shells of Object . values ( camorasWithShells ) ) {
2024-08-16 23:19:07 +01:00
// Add all shells to the set
for ( const shell of shells ) {
uniqueShells . add ( shell ) ;
}
2023-03-03 15:23:46 +00:00
}
2024-06-19 10:40:59 +01:00
return Array . from ( uniqueShells ) ;
2023-03-03 15:23:46 +00:00
}
/ * *
* 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 (
2024-09-24 12:47:29 +01:00
weapon : IItem ,
2023-11-13 11:05:05 -05:00
scopes : string [ ] ,
botWeaponSightWhitelist : Record < string , string [ ] > ,
2024-07-23 11:12:53 -04:00
) : 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 ] ;
2024-07-23 11:12:53 -04:00
if ( ! whitelistedSightTypes ) {
2023-11-13 11:05:05 -05:00
this . logger . debug (
2024-05-17 15:32:41 -04:00
` Unable to find whitelist for weapon type: ${ weaponDetails [ 1 ] . _parent } ${ weaponDetails [ 1 ] . _name } , skipping sight filtering ` ,
2023-11-13 11:05:05 -05:00
) ;
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 [ ] = [ ] ;
2024-07-23 11:12:53 -04:00
for ( const item of scopes ) {
2023-03-03 15:23:46 +00:00
// Mods is a scope, check base class is allowed
2024-07-23 11:12:53 -04:00
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 ] ;
2024-07-23 11:12:53 -04:00
if ( this . itemHelper . isOfBaseclass ( item , BaseClasses . MOUNT ) && itemDetails . _props . Slots . length > 0 ) {
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
2024-05-17 15:32:41 -04:00
const scopeSlot = itemDetails . _props . Slots . filter ( ( slot ) = >
2024-05-07 23:57:08 -04:00
[ "mod_scope" , "mod_scope_000" ] . includes ( slot . _name ) ,
2023-11-13 11:05:05 -05:00
) ;
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 (
2024-05-17 15:32:41 -04:00
scopeSlot ? . every ( ( slot ) = >
slot . _props . filters [ 0 ] . Filter . every (
( tpl ) = >
2024-07-23 11:12:53 -04:00
this . itemHelper . isOfBaseclasses ( tpl , whitelistedSightTypes ) ||
this . itemHelper . isOfBaseclass ( tpl , BaseClasses . MOUNT ) ,
2024-05-07 23:57:08 -04:00
) ,
2023-11-13 11:05:05 -05:00
)
2024-07-23 11:12:53 -04: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
2024-07-23 11:12:53 -04: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
}