From db13df89d96e02d8256ca49c578438fbe891eed2 Mon Sep 17 00:00:00 2001 From: Dev Date: Sun, 7 Jan 2024 15:34:59 +0000 Subject: [PATCH] Add plate filtering system based on bot level --- project/assets/configs/bot.json | 202 +++++++++++++----- .../generators/BotEquipmentModGenerator.ts | 54 ++++- .../src/generators/BotInventoryGenerator.ts | 55 ++--- 3 files changed, 230 insertions(+), 81 deletions(-) diff --git a/project/assets/configs/bot.json b/project/assets/configs/bot.json index e24419d4..13b37f51 100644 --- a/project/assets/configs/bot.json +++ b/project/assets/configs/bot.json @@ -1376,62 +1376,164 @@ } } ], - "armorPlateWeighting" : [ - { - "levelRange": { + "armorPlateWeighting": [{ + "levelRange": { "min": 1, "max": 10 }, - "frontPlateWeights": { - "level2": 25, - "level3": 20, - "level4": 5, - "level5": 1, - "level6": 1 - }, - "backPlateWeights": { - "level2": 25, - "level3": 20, - "level4": 5, - "level5": 1, - "level6": 1 - }, - "sidePlateWeights": { - "level2": 25, - "level3": 20, - "level4": 5, - "level5": 1, - "level6": 1 - } - }, - { - "levelRange": { + "front_plate": { + "2": 25, + "3": 20, + "4": 5, + "5": 1, + "6": 1 + }, + "back_plate": { + "2": 25, + "3": 20, + "4": 5, + "5": 1, + "6": 1 + }, + "side_plate": { + "2": 25, + "3": 20, + "4": 5, + "5": 1, + "6": 1 + } + }, { + "levelRange": { "min": 11, "max": 14 }, - "frontPlateWeights": { - "level2": 30, - "level3": 15, - "level4": 5, - "level5": 1, - "level6": 1 - }, - "backPlateWeights": { - "level2": 30, - "level3": 15, - "level4": 5, - "level5": 1, - "level6": 1 - }, - "sidePlateWeights": { - "level2": 30, - "level3": 15, - "level4": 5, - "level5": 1, - "level6": 1 - } - } - ], + "front_plate": { + "2": 30, + "3": 15, + "4": 5, + "5": 1, + "6": 1 + }, + "back_plate": { + "2": 30, + "3": 15, + "4": 5, + "5": 1, + "6": 1 + }, + "side_plate": { + "2": 30, + "3": 15, + "4": 5, + "5": 1, + "6": 1 + } + }, { + "levelRange": { + "min": 15, + "max": 24 + }, + "front_plate": { + "2": 10, + "3": 15, + "4": 20, + "5": 8, + "6": 4 + }, + "back_plate": { + "2": 10, + "3": 15, + "4": 20, + "5": 8, + "6": 4 + }, + "side_plate": { + "2": 10, + "3": 15, + "4": 20, + "5": 8, + "6": 4 + } + }, { + "levelRange": { + "min": 25, + "max": 35 + }, + "front_plate": { + "2": 3, + "3": 15, + "4": 30, + "5": 20, + "6": 10 + }, + "back_plate": { + "2": 3, + "3": 15, + "4": 30, + "5": 20, + "6": 10 + }, + "side_plate": { + "2": 3, + "3": 15, + "4": 30, + "5": 20, + "6": 10 + } + }, { + "levelRange": { + "min": 36, + "max": 55 + }, + "front_plate": { + "2": 1, + "3": 10, + "4": 30, + "5": 40, + "6": 20 + }, + "back_plate": { + "2": 1, + "3": 10, + "4": 30, + "5": 40, + "6": 20 + }, + "side_plate": { + "2": 1, + "3": 10, + "4": 30, + "5": 40, + "6": 20 + } + }, { + "levelRange": { + "min": 56, + "max": 90 + }, + "front_plate": { + "2": 0, + "3": 0, + "4": 10, + "5": 40, + "6": 50 + }, + "back_plate": { + "2": 0, + "3": 0, + "4": 10, + "5": 40, + "6": 50 + }, + "side_plate": { + "2": 0, + "3": 0, + "4": 10, + "5": 40, + "6": 50 + } + } + ], "whitelist": [{ "levelRange": { "min": 101, diff --git a/project/src/generators/BotEquipmentModGenerator.ts b/project/src/generators/BotEquipmentModGenerator.ts index da20a64a..6b2b9391 100644 --- a/project/src/generators/BotEquipmentModGenerator.ts +++ b/project/src/generators/BotEquipmentModGenerator.ts @@ -6,6 +6,7 @@ import { BotWeaponGeneratorHelper } from "@spt-aki/helpers/BotWeaponGeneratorHel import { ItemHelper } from "@spt-aki/helpers/ItemHelper"; import { ProbabilityHelper } from "@spt-aki/helpers/ProbabilityHelper"; import { ProfileHelper } from "@spt-aki/helpers/ProfileHelper"; +import { WeightedRandomHelper } from "@spt-aki/helpers/WeightedRandomHelper"; 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"; @@ -45,6 +46,7 @@ export class BotEquipmentModGenerator @inject("BotHelper") protected botHelper: BotHelper, @inject("BotGeneratorHelper") protected botGeneratorHelper: BotGeneratorHelper, @inject("BotWeaponGeneratorHelper") protected botWeaponGeneratorHelper: BotWeaponGeneratorHelper, + @inject("WeightedRandomHelper") protected weightedRandomHelper: WeightedRandomHelper, @inject("LocalisationService") protected localisationService: LocalisationService, @inject("BotEquipmentModPoolService") protected botEquipmentModPoolService: BotEquipmentModPoolService, @inject("ConfigServer") protected configServer: ConfigServer, @@ -59,8 +61,6 @@ export class BotEquipmentModGenerator * @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 modSpawnChances dictionary of mod items and their chance to spawn for this bot type - * @param botRole the bot role being generated for * @param forceSpawn should this mod be forced to spawn * @returns Item + compatible mods as an array */ @@ -106,7 +106,7 @@ export class BotEquipmentModGenerator forceSpawn = true; } - const modPoolToChooseFrom = this.filterPlateModsForSlot(settings.botRole, modSlot, compatibleModsPool[modSlot]); + const modPoolToChooseFrom = this.filterPlateModsForSlot(settings, modSlot.toLowerCase(), compatibleModsPool[modSlot], parentTemplate); let modTpl: string; let found = false; @@ -169,16 +169,58 @@ export class BotEquipmentModGenerator return equipment; } - protected filterPlateModsForSlot(botRole: string, modSlot: string, modPool: string[]): string[] + /** + * Filter a bots plate pool based on its current level + * @param settings Bot equipment generation settings + * @param modSlot Slot being filtered + * @param modPool Plates tpls to choose from + * @param armorItem + * @returns Array of plate tpls to choose from + */ + protected filterPlateModsForSlot(settings: IGenerateEquipmentProperties, modSlot: string, modPool: string[], armorItem: ITemplateItem): string[] { // Not pmc or not a plate slot, return original mod pool array - if (!this.botHelper.isBotPmc(botRole) || !this.slotIsPlate(modSlot)) + if (!this.slotIsPlate(modSlot)) { return modPool; } - const filteredModPool = []; + // 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) + { + return modPool; + } + // Get the specific plate slot weights (front/back/side) + const plateWeights: Record = plateSlotWeights[modSlot]; + if (!plateWeights) + { + this.logger.warning(`Plate slot ${modSlot} lacks weight data, skipping`); + + return modPool; + } + + // Choose a plate level based on weighting + const chosenArmorPlateLevel = this.weightedRandomHelper.getWeightedValue(plateWeights); + + // Convert the array of ids into database items + const platesFromDb = modPool.map(x => this.itemHelper.getItem(x)[1]); + + // Filter plates to the chosen level based on its armorClass property + const filteredPlates = platesFromDb.filter(x => x._props.armorClass === chosenArmorPlateLevel); + if (!filteredPlates) + { + this.logger.warning(`Plate filter was too restrictive, unable to find plates of level: ${chosenArmorPlateLevel}. Returning all ${modPool.length} plates instead`); + + return modPool; + } + + // Only return the items ids + return filteredPlates.map(x => x._id); } diff --git a/project/src/generators/BotInventoryGenerator.ts b/project/src/generators/BotInventoryGenerator.ts index f4d0da2d..1bd9188f 100644 --- a/project/src/generators/BotInventoryGenerator.ts +++ b/project/src/generators/BotInventoryGenerator.ts @@ -168,8 +168,8 @@ export class BotInventoryGenerator } this.generateEquipment({ - equipmentSlot: equipmentSlot, - equipmentPool: templateInventory.equipment[equipmentSlot], + rootEquipmentSlot: equipmentSlot, + rootEquipmentPool: templateInventory.equipment[equipmentSlot], modPool: templateInventory.mods, spawnChances: equipmentChances, botRole: botRole, @@ -182,8 +182,8 @@ export class BotInventoryGenerator // Generate below in specific order this.generateEquipment({ - equipmentSlot: EquipmentSlots.FACE_COVER, - equipmentPool: templateInventory.equipment.FaceCover, + rootEquipmentSlot: EquipmentSlots.FACE_COVER, + rootEquipmentPool: templateInventory.equipment.FaceCover, modPool: templateInventory.mods, spawnChances: equipmentChances, botRole: botRole, @@ -193,8 +193,8 @@ export class BotInventoryGenerator randomisationDetails: randomistionDetails }); this.generateEquipment({ - equipmentSlot: EquipmentSlots.HEADWEAR, - equipmentPool: templateInventory.equipment.Headwear, + rootEquipmentSlot: EquipmentSlots.HEADWEAR, + rootEquipmentPool: templateInventory.equipment.Headwear, modPool: templateInventory.mods, spawnChances: equipmentChances, botRole: botRole, @@ -204,8 +204,8 @@ export class BotInventoryGenerator randomisationDetails: randomistionDetails }); this.generateEquipment({ - equipmentSlot: EquipmentSlots.EARPIECE, - equipmentPool: templateInventory.equipment.Earpiece, + rootEquipmentSlot: EquipmentSlots.EARPIECE, + rootEquipmentPool: templateInventory.equipment.Earpiece, modPool: templateInventory.mods, spawnChances: equipmentChances, botRole: botRole, @@ -215,8 +215,8 @@ export class BotInventoryGenerator randomisationDetails: randomistionDetails }); this.generateEquipment({ - equipmentSlot: EquipmentSlots.TACTICAL_VEST, - equipmentPool: templateInventory.equipment.TacticalVest, + rootEquipmentSlot: EquipmentSlots.TACTICAL_VEST, + rootEquipmentPool: templateInventory.equipment.TacticalVest, modPool: templateInventory.mods, spawnChances: equipmentChances, botRole: botRole, @@ -226,8 +226,8 @@ export class BotInventoryGenerator randomisationDetails: randomistionDetails }); this.generateEquipment({ - equipmentSlot: EquipmentSlots.ARMOR_VEST, - equipmentPool: templateInventory.equipment.ArmorVest, + rootEquipmentSlot: EquipmentSlots.ARMOR_VEST, + rootEquipmentPool: templateInventory.equipment.ArmorVest, modPool: templateInventory.mods, spawnChances: equipmentChances, botRole: botRole, @@ -246,34 +246,34 @@ export class BotInventoryGenerator * @param spawnChances Chances items will be chosen to be added * @param botRole Role of bot e.g. assault * @param inventory Inventory to add item into - * @param randomisationDetails settings from bot.json to adjust how item is generated + * @param randomisationDetails */ protected generateEquipment(settings: IGenerateEquipmentProperties): void { const spawnChance = - ([EquipmentSlots.POCKETS, EquipmentSlots.SECURED_CONTAINER] as string[]).includes(settings.equipmentSlot) + ([EquipmentSlots.POCKETS, EquipmentSlots.SECURED_CONTAINER] as string[]).includes(settings.rootEquipmentSlot) ? 100 - : settings.spawnChances.equipment[settings.equipmentSlot]; + : settings.spawnChances.equipment[settings.rootEquipmentSlot]; if (typeof spawnChance === "undefined") { this.logger.warning( - this.localisationService.getText("bot-no_spawn_chance_defined_for_equipment_slot", settings.equipmentSlot), + this.localisationService.getText("bot-no_spawn_chance_defined_for_equipment_slot", settings.rootEquipmentSlot), ); return; } const shouldSpawn = this.randomUtil.getChance100(spawnChance); - if (Object.keys(settings.equipmentPool).length && shouldSpawn) + if (Object.keys(settings.rootEquipmentPool).length && shouldSpawn) { const id = this.hashUtil.generate(); - const equipmentItemTpl = this.weightedRandomHelper.getWeightedValue(settings.equipmentPool); + const equipmentItemTpl = this.weightedRandomHelper.getWeightedValue(settings.rootEquipmentPool); const itemTemplate = this.itemHelper.getItem(equipmentItemTpl); if (!itemTemplate[0]) { this.logger.error(this.localisationService.getText("bot-missing_item_template", equipmentItemTpl)); - this.logger.info(`EquipmentSlot -> ${settings.equipmentSlot}`); + this.logger.info(`EquipmentSlot -> ${settings.rootEquipmentSlot}`); return; } @@ -282,7 +282,7 @@ export class BotInventoryGenerator this.botGeneratorHelper.isItemIncompatibleWithCurrentItems( settings.inventory.items, equipmentItemTpl, - settings.equipmentSlot, + settings.rootEquipmentSlot, ).incompatible ) { @@ -294,7 +294,7 @@ export class BotInventoryGenerator _id: id, _tpl: equipmentItemTpl, parentId: settings.inventory.equipment, - slotId: settings.equipmentSlot, + slotId: settings.rootEquipmentSlot, ...this.botGeneratorHelper.generateExtraPropertiesForItem(itemTemplate[1], settings.botRole), }; @@ -302,7 +302,7 @@ export class BotInventoryGenerator const botEquipmentRole = this.botGeneratorHelper.getBotEquipmentRole(settings.botRole); if ( this.botConfig.equipment[botEquipmentRole] - && settings.randomisationDetails?.randomisedArmorSlots?.includes(settings.equipmentSlot) + && settings.randomisationDetails?.randomisedArmorSlots?.includes(settings.rootEquipmentSlot) ) { settings.modPool[equipmentItemTpl] = this.getFilteredDynamicModsForItem( @@ -316,7 +316,6 @@ export class BotInventoryGenerator { const items = this.botEquipmentModGenerator.generateModsForEquipment( [item], - settings.modPool, id, itemTemplate[1], settings @@ -467,13 +466,19 @@ export class BotInventoryGenerator export interface IGenerateEquipmentProperties { - equipmentSlot: string, - equipmentPool: Record, + /** Root Slot being generated */ + rootEquipmentSlot: string, + /** Equipment pool for root slot being generated */ + rootEquipmentPool: Record, modPool: Mods, + /** Dictionary of mod items and their chance to spawn for this bot type */ spawnChances: Chances, + /** Role being generated for */ botRole: string, + /** level of bot being generated */ botLevel: number, inventory: PmcInventory, botEquipmentConfig: EquipmentFilters, + /** Settings from bot.json to adjust how item is generated */ randomisationDetails: RandomisationDetails }