diff --git a/project/assets/configs/bot.json b/project/assets/configs/bot.json index 523ae6d9..38d291ea 100644 --- a/project/assets/configs/bot.json +++ b/project/assets/configs/bot.json @@ -454,7 +454,12 @@ }, "grenades": { "min": 0, - "max": 1 + "max": 1, + "whitelist": [ + "5710c24ad2720bc3458b45a3", + "58d3db5386f77426186285a0", + "5448be9a4bdc2dfd2f8b456a" + ] }, "healing": { "min": 0, diff --git a/project/assets/database/bots/types/bear.json b/project/assets/database/bots/types/bear.json index 580e84db..808378fc 100644 --- a/project/assets/database/bots/types/bear.json +++ b/project/assets/database/bots/types/bear.json @@ -2396,31 +2396,38 @@ "items": { "drugs": { "max": 2, - "min": 0 + "min": 0, + "whitelist": [] }, "grenades": { "max": 3, - "min": 0 + "min": 0, + "whitelist": [] }, "healing": { "max": 3, - "min": 1 + "min": 1, + "whitelist": [] }, "looseLoot": { "max": 23, - "min": 0 + "min": 0, + "whitelist": [] }, "magazines": { "max": 4, - "min": 2 + "min": 2, + "whitelist": [] }, "specialItems": { "max": 0, - "min": 0 + "min": 0, + "whitelist": [] }, "stims": { "max": 1, - "min": 0 + "min": 0, + "whitelist": [] } } }, diff --git a/project/assets/database/bots/types/usec.json b/project/assets/database/bots/types/usec.json index 87e04fee..27bcde3b 100644 --- a/project/assets/database/bots/types/usec.json +++ b/project/assets/database/bots/types/usec.json @@ -2417,31 +2417,38 @@ "items": { "drugs": { "max": 2, - "min": 0 + "min": 0, + "whitelist": [] }, "grenades": { "max": 3, - "min": 0 + "min": 0, + "whitelist": [] }, "healing": { "max": 3, - "min": 1 + "min": 1, + "whitelist": [] }, "looseLoot": { "max": 23, - "min": 0 + "min": 0, + "whitelist": [] }, "magazines": { "max": 4, - "min": 2 + "min": 2, + "whitelist": [] }, "specialItems": { "max": 0, - "min": 0 + "min": 0, + "whitelist": [] }, "stims": { "max": 1, - "min": 0 + "min": 0, + "whitelist": [] } } }, diff --git a/project/src/generators/BotInventoryGenerator.ts b/project/src/generators/BotInventoryGenerator.ts index 4c200e25..27f4f4ec 100644 --- a/project/src/generators/BotInventoryGenerator.ts +++ b/project/src/generators/BotInventoryGenerator.ts @@ -50,7 +50,7 @@ export class BotInventoryGenerator /** * Add equipment/weapons/loot to bot * @param sessionId Session id - * @param botJsonTemplate bot/x.json data from db + * @param botJsonTemplate Base json db file for the bot having its loot generated * @param botRole Role bot has (assault/pmcBot) * @param isPmc Is bot being converted into a pmc * @param botLevel Level of bot being generated @@ -70,7 +70,7 @@ export class BotInventoryGenerator // Roll weapon spawns and generate a weapon for each roll that passed this.generateAndAddWeaponsToBot(templateInventory, equipmentChances, sessionId, botInventory, botRole, isPmc, itemGenerationLimitsMinMax, botLevel); - this.botLootGenerator.generateLoot(sessionId, templateInventory, itemGenerationLimitsMinMax.items, isPmc, botRole, botInventory, equipmentChances, botLevel); + this.botLootGenerator.generateLoot(sessionId, botJsonTemplate, isPmc, botRole, botInventory, botLevel); return botInventory; } diff --git a/project/src/generators/BotLootGenerator.ts b/project/src/generators/BotLootGenerator.ts index 3283ec29..6a722f59 100644 --- a/project/src/generators/BotLootGenerator.ts +++ b/project/src/generators/BotLootGenerator.ts @@ -5,7 +5,7 @@ import { BotWeaponGeneratorHelper } from "../helpers/BotWeaponGeneratorHelper"; import { HandbookHelper } from "../helpers/HandbookHelper"; import { ItemHelper } from "../helpers/ItemHelper"; import { Inventory as PmcInventory } from "../models/eft/common/tables/IBotBase"; -import { Chances, Inventory, ItemMinMax, ModsChances } from "../models/eft/common/tables/IBotType"; +import { IBotType, Inventory, ModsChances } from "../models/eft/common/tables/IBotType"; import { Item } from "../models/eft/common/tables/IItem"; import { ITemplateItem } from "../models/eft/common/tables/ITemplateItem"; import { BaseClasses } from "../models/enums/BaseClasses"; @@ -48,17 +48,16 @@ export class BotLootGenerator /** * Add loot to bots containers * @param sessionId Session id - * @param templateInventory x.json from database/bots - * @param itemCounts Liits on item types to be added as loot + * @param botJsonTemplate Base json db file for the bot having its loot generated * @param isPmc Will bot be a pmc * @param botRole Role of bot, e.g. asssult * @param botInventory Inventory to add loot to - * @param equipmentChances * @param botLevel Level of bot */ - public generateLoot(sessionId: string, templateInventory: Inventory, itemCounts: ItemMinMax, isPmc: boolean, botRole: string, botInventory: PmcInventory, equipmentChances: Chances, botLevel: number): void + public generateLoot(sessionId: string, botJsonTemplate: IBotType, isPmc: boolean, botRole: string, botInventory: PmcInventory, botLevel: number): void { - const lootPool = templateInventory.items; + // Limits on item types to be added as loot + const itemCounts = botJsonTemplate.generation.items; const nValue = this.getBotLootNValue(isPmc); const looseLootMin = itemCounts.looseLoot.min; @@ -76,7 +75,7 @@ export class BotLootGenerator // Special items this.addLootFromPool( - this.botLootCacheService.getLootFromCache(botRole, isPmc, LootCacheType.SPECIAL, lootPool), + this.botLootCacheService.getLootFromCache(botRole, isPmc, LootCacheType.SPECIAL, botJsonTemplate), [EquipmentSlots.POCKETS, EquipmentSlots.BACKPACK, EquipmentSlots.TACTICAL_VEST], specialLootItemCount, botInventory, @@ -84,7 +83,7 @@ export class BotLootGenerator // Meds this.addLootFromPool( - this.botLootCacheService.getLootFromCache(botRole, isPmc, LootCacheType.HEALING_ITEMS, lootPool), + this.botLootCacheService.getLootFromCache(botRole, isPmc, LootCacheType.HEALING_ITEMS, botJsonTemplate), [EquipmentSlots.TACTICAL_VEST, EquipmentSlots.POCKETS, EquipmentSlots.BACKPACK, EquipmentSlots.SECURED_CONTAINER], healingItemCount, botInventory, @@ -95,7 +94,7 @@ export class BotLootGenerator // Drugs this.addLootFromPool( - this.botLootCacheService.getLootFromCache(botRole, isPmc, LootCacheType.DRUG_ITEMS, lootPool), + this.botLootCacheService.getLootFromCache(botRole, isPmc, LootCacheType.DRUG_ITEMS, botJsonTemplate), [EquipmentSlots.TACTICAL_VEST, EquipmentSlots.POCKETS, EquipmentSlots.BACKPACK, EquipmentSlots.SECURED_CONTAINER], drugItemCount, botInventory, @@ -106,7 +105,7 @@ export class BotLootGenerator // Stims this.addLootFromPool( - this.botLootCacheService.getLootFromCache(botRole, isPmc, LootCacheType.STIM_ITEMS, lootPool), + this.botLootCacheService.getLootFromCache(botRole, isPmc, LootCacheType.STIM_ITEMS, botJsonTemplate), [EquipmentSlots.TACTICAL_VEST, EquipmentSlots.POCKETS, EquipmentSlots.BACKPACK, EquipmentSlots.SECURED_CONTAINER], stimItemCount, botInventory, @@ -117,7 +116,7 @@ export class BotLootGenerator // Grenades this.addLootFromPool( - this.botLootCacheService.getLootFromCache(botRole, isPmc, LootCacheType.GRENADE_ITEMS, lootPool), + this.botLootCacheService.getLootFromCache(botRole, isPmc, LootCacheType.GRENADE_ITEMS, botJsonTemplate), [EquipmentSlots.TACTICAL_VEST, EquipmentSlots.POCKETS], grenadeCount, botInventory, @@ -128,12 +127,12 @@ export class BotLootGenerator if (isPmc && this.randomUtil.getChance100(this.botConfig.pmc.looseWeaponInBackpackChancePercent)) { - this.addLooseWeaponsToInventorySlot(sessionId, botInventory, "Backpack", templateInventory, equipmentChances.mods, botRole, isPmc, botLevel); + this.addLooseWeaponsToInventorySlot(sessionId, botInventory, "Backpack", botJsonTemplate.inventory, botJsonTemplate.chances.mods, botRole, isPmc, botLevel); } // Backpack this.addLootFromPool( - this.botLootCacheService.getLootFromCache(botRole, isPmc, LootCacheType.BACKPACK, lootPool), + this.botLootCacheService.getLootFromCache(botRole, isPmc, LootCacheType.BACKPACK, botJsonTemplate), [EquipmentSlots.BACKPACK], lootItemCount, botInventory, @@ -144,7 +143,7 @@ export class BotLootGenerator // Vest this.addLootFromPool( - this.botLootCacheService.getLootFromCache(botRole, isPmc, LootCacheType.VEST, lootPool), + this.botLootCacheService.getLootFromCache(botRole, isPmc, LootCacheType.VEST, botJsonTemplate), [EquipmentSlots.TACTICAL_VEST], vestLootCount, botInventory, @@ -155,7 +154,7 @@ export class BotLootGenerator // Pockets this.addLootFromPool( - this.botLootCacheService.getLootFromCache(botRole, isPmc, LootCacheType.POCKET, lootPool), + this.botLootCacheService.getLootFromCache(botRole, isPmc, LootCacheType.POCKET, botJsonTemplate), [EquipmentSlots.POCKETS], pocketLootCount, botInventory, diff --git a/project/src/models/eft/common/tables/IBotType.ts b/project/src/models/eft/common/tables/IBotType.ts index cf91f67e..d14d0ae1 100644 --- a/project/src/models/eft/common/tables/IBotType.ts +++ b/project/src/models/eft/common/tables/IBotType.ts @@ -1,5 +1,5 @@ -import { MinMax } from "../../../common/MinMax" -import { Skills } from "./IBotBase" +import { MinMax } from "../../../common/MinMax"; +import { Skills } from "./IBotBase"; export interface IBotType { @@ -125,13 +125,19 @@ export interface Generation export interface ItemMinMax { - grenades: MinMax - healing: MinMax - drugs: MinMax - stims: MinMax - looseLoot: MinMax - magazines: MinMax - specialItems: MinMax + grenades: MinMaxWithWhitelist + healing: MinMaxWithWhitelist + drugs: MinMaxWithWhitelist + stims: MinMaxWithWhitelist + looseLoot: MinMaxWithWhitelist + magazines: MinMaxWithWhitelist + specialItems: MinMaxWithWhitelist +} + +export interface MinMaxWithWhitelist extends MinMax +{ + /** Array of item tpls */ + whitelist: string[] } export interface Health diff --git a/project/src/models/spt/config/IBotConfig.ts b/project/src/models/spt/config/IBotConfig.ts index 1a291fc7..23b26ecf 100644 --- a/project/src/models/spt/config/IBotConfig.ts +++ b/project/src/models/spt/config/IBotConfig.ts @@ -1,3 +1,4 @@ +import { MinMaxWithWhitelist } from "../../../models/eft/common/tables/IBotType"; import { MinMax } from "../../common/MinMax"; import { IBaseConfig } from "./IBaseConfig"; import { IBotDurability } from "./IBotDurability"; @@ -97,7 +98,7 @@ export interface ModLimits export interface RandomisationDetails { levelRange: MinMax - generation?: Record + generation?: Record randomisedWeaponModSlots?: string[] randomisedArmorSlots?: string[] /** Equipment chances */ diff --git a/project/src/services/BotEquipmentFilterService.ts b/project/src/services/BotEquipmentFilterService.ts index 1f00b2ee..7475524a 100644 --- a/project/src/services/BotEquipmentFilterService.ts +++ b/project/src/services/BotEquipmentFilterService.ts @@ -1,11 +1,15 @@ import { inject, injectable } from "tsyringe"; import { BotHelper } from "../helpers/BotHelper"; -import { MinMax } from "../models/common/MinMax"; -import { EquipmentChances, Generation, IBotType, ModsChances } from "../models/eft/common/tables/IBotType"; +import { + EquipmentChances, Generation, IBotType, MinMaxWithWhitelist, ModsChances +} from "../models/eft/common/tables/IBotType"; import { ConfigTypes } from "../models/enums/ConfigTypes"; import { BotGenerationDetails } from "../models/spt/bots/BotGenerationDetails"; -import { AdjustmentDetails, EquipmentFilterDetails, EquipmentFilters, IBotConfig, WeightingAdjustmentDetails } from "../models/spt/config/IBotConfig"; +import { + AdjustmentDetails, EquipmentFilterDetails, EquipmentFilters, IBotConfig, + WeightingAdjustmentDetails +} from "../models/spt/config/IBotConfig"; import { ILogger } from "../models/spt/utils/ILogger"; import { ConfigServer } from "../servers/ConfigServer"; @@ -78,7 +82,7 @@ export class BotEquipmentFilterService * @param generationChanges Changes to apply * @param baseBotGeneration dictionary to update */ - protected adjustGenerationChances(generationChanges: Record, baseBotGeneration: Generation): void + protected adjustGenerationChances(generationChanges: Record, baseBotGeneration: Generation): void { if (!generationChanges) { @@ -89,6 +93,8 @@ export class BotEquipmentFilterService { baseBotGeneration.items[itemKey].min = generationChanges[itemKey].min; baseBotGeneration.items[itemKey].max = generationChanges[itemKey].max; + + baseBotGeneration.items[itemKey].whitelist = generationChanges[itemKey].whitelist; } } diff --git a/project/src/services/BotLootCacheService.ts b/project/src/services/BotLootCacheService.ts index 829bac1d..e1278ed8 100644 --- a/project/src/services/BotLootCacheService.ts +++ b/project/src/services/BotLootCacheService.ts @@ -1,7 +1,8 @@ import { inject, injectable } from "tsyringe"; import { PMCLootGenerator } from "../generators/PMCLootGenerator"; -import { Items } from "../models/eft/common/tables/IBotType"; +import { ItemHelper } from "../helpers/ItemHelper"; +import { IBotType } from "../models/eft/common/tables/IBotType"; import { ITemplateItem, Props } from "../models/eft/common/tables/ITemplateItem"; import { BaseClasses } from "../models/enums/BaseClasses"; import { BotLootCache, LootCacheType } from "../models/spt/bots/BotLootCache"; @@ -19,6 +20,7 @@ export class BotLootCacheService constructor( @inject("WinstonLogger") protected logger: ILogger, @inject("JsonUtil") protected jsonUtil: JsonUtil, + @inject("ItemHelper") protected itemHelper: ItemHelper, @inject("DatabaseServer") protected databaseServer: DatabaseServer, @inject("PMCLootGenerator") protected pmcLootGenerator: PMCLootGenerator, @inject("LocalisationService") protected localisationService: LocalisationService, @@ -41,15 +43,15 @@ export class BotLootCacheService * @param botRole bot to get loot for * @param isPmc is the bot a pmc * @param lootType what type of loot is needed (backpack/pocket/stim/vest etc) - * @param lootPool the full pool of loot (needed when cache is empty) + * @param botJsonTemplate Base json db file for the bot having its loot generated * @returns ITemplateItem array */ - public getLootFromCache(botRole: string, isPmc: boolean, lootType: LootCacheType, lootPool: Items): ITemplateItem[] + public getLootFromCache(botRole: string, isPmc: boolean, lootType: LootCacheType, botJsonTemplate: IBotType): ITemplateItem[] { if (!this.botRoleExistsInCache(botRole)) { this.initCacheForBotRole(botRole); - this.addLootToCache(botRole, isPmc, lootPool); + this.addLootToCache(botRole, isPmc, botJsonTemplate); } switch (lootType) @@ -81,11 +83,14 @@ export class BotLootCacheService /** * Generate loot for a bot and store inside a private class property * @param botRole bots role (assault / pmcBot etc) - * @param lootPool the full pool of loot we use to create the various sub-categories with * @param isPmc Is the bot a PMC (alteres what loot is cached) + * @param botJsonTemplate db template for bot having its loot generated */ - protected addLootToCache(botRole: string, isPmc: boolean, lootPool: Items): void + protected addLootToCache(botRole: string, isPmc: boolean, botJsonTemplate: IBotType): void { + // the full pool of loot we use to create the various sub-categories with + const lootPool = botJsonTemplate.inventory.items; + // Flatten all individual slot loot pools into one big pool, while filtering out potentially missing templates const specialLootTemplates: ITemplateItem[] = []; const backpackLootTemplates: ITemplateItem[] = []; @@ -103,32 +108,34 @@ export class BotLootCacheService for (const [slot, pool] of Object.entries(lootPool)) { + // No items to add, skip if (!pool?.length) { continue; } + // Sort loot pool into separate buckets let itemsToAdd: ITemplateItem[] = []; const items = this.databaseServer.getTables().templates.items; switch (slot.toLowerCase()) { case "specialloot": - itemsToAdd = pool.map(lootTpl => items[lootTpl]); + itemsToAdd = pool.map((lootTpl: string) => items[lootTpl]); this.addUniqueItemsToPool(specialLootTemplates, itemsToAdd); break; case "pockets": - itemsToAdd = pool.map(lootTpl => items[lootTpl]); + itemsToAdd = pool.map((lootTpl: string) => items[lootTpl]); this.addUniqueItemsToPool(pocketLootTemplates, itemsToAdd); break; case "tacticalvest": - itemsToAdd = pool.map(lootTpl => items[lootTpl]); + itemsToAdd = pool.map((lootTpl: string) => items[lootTpl]); this.addUniqueItemsToPool(vestLootTemplates, itemsToAdd); break; case "securedcontainer": // Don't add these items to loot pool break; default: - itemsToAdd = pool.map(lootTpl => items[lootTpl]); + itemsToAdd = pool.map((lootTpl: string) => items[lootTpl]); this.addUniqueItemsToPool(backpackLootTemplates, itemsToAdd); } @@ -146,25 +153,36 @@ export class BotLootCacheService this.sortPoolByRagfairPrice(vestLootTemplates); this.sortPoolByRagfairPrice(combinedPoolTemplates); - const specialLootItems = specialLootTemplates.filter(template => - !(this.isBulletOrGrenade(template._props) - || this.isMagazine(template._props))); + // use whitelist if array has values, otherwise process above sorted pools + const specialLootItems = (botJsonTemplate.generation.items.specialItems.whitelist?.length > 0) + ? botJsonTemplate.generation.items.specialItems.whitelist.map(x => this.itemHelper.getItem(x)[1]) + : specialLootTemplates.filter(template => + !(this.isBulletOrGrenade(template._props) + || this.isMagazine(template._props))); - const healingItems = combinedPoolTemplates.filter(template => - this.isMedicalItem(template._props) - && template._parent !== BaseClasses.STIMULATOR - && template._parent !== BaseClasses.DRUGS); + const healingItems = (botJsonTemplate.generation.items.healing.whitelist?.length > 0) + ? botJsonTemplate.generation.items.healing.whitelist.map(x => this.itemHelper.getItem(x)[1]) + : combinedPoolTemplates.filter(template => + this.isMedicalItem(template._props) + && template._parent !== BaseClasses.STIMULATOR + && template._parent !== BaseClasses.DRUGS); - const drugItems = combinedPoolTemplates.filter(template => - this.isMedicalItem(template._props) - && template._parent === BaseClasses.DRUGS); + const drugItems = (botJsonTemplate.generation.items.drugs.whitelist?.length > 0) + ? botJsonTemplate.generation.items.drugs.whitelist.map(x => this.itemHelper.getItem(x)[1]) + : combinedPoolTemplates.filter(template => + this.isMedicalItem(template._props) + && template._parent === BaseClasses.DRUGS); - const stimItems = combinedPoolTemplates.filter(template => - this.isMedicalItem(template._props) - && template._parent === BaseClasses.STIMULATOR); + const stimItems = (botJsonTemplate.generation.items.stims.whitelist?.length > 0) + ? botJsonTemplate.generation.items.stims.whitelist.map(x => this.itemHelper.getItem(x)[1]) + : combinedPoolTemplates.filter(template => + this.isMedicalItem(template._props) + && template._parent === BaseClasses.STIMULATOR); - const grenadeItems = combinedPoolTemplates.filter(template => - this.isGrenade(template._props)); + const grenadeItems = (botJsonTemplate.generation.items.grenades.whitelist?.length > 0) + ? botJsonTemplate.generation.items.grenades.whitelist.map(x => this.itemHelper.getItem(x)[1]) + : combinedPoolTemplates.filter(template => + this.isGrenade(template._props)); // Get loot items (excluding magazines, bullets, grenades and healing items) const backpackLootItems = backpackLootTemplates.filter(template =>