Feature: Add ability to whitelist items inside each bots generation object + filter per bot level inside bot.json equipment.randomisation section (!73)

(cherry picked from commit 253b4d0120db8245e6c2e863ab7d2fbdc5196128)

Co-authored-by: Dev <dev@noreply.dev.sp-tarkov.com>
Reviewed-on: https://dev.sp-tarkov.com/SPT-AKI/Server/pulls/73
This commit is contained in:
chomp 2023-03-17 18:20:16 +00:00
parent 611b63a4cd
commit ed4dfc201d
9 changed files with 120 additions and 71 deletions

View File

@ -454,7 +454,12 @@
},
"grenades": {
"min": 0,
"max": 1
"max": 1,
"whitelist": [
"5710c24ad2720bc3458b45a3",
"58d3db5386f77426186285a0",
"5448be9a4bdc2dfd2f8b456a"
]
},
"healing": {
"min": 0,

View File

@ -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": []
}
}
},

View File

@ -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": []
}
}
},

View File

@ -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;
}

View File

@ -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,

View File

@ -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

View File

@ -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<string, MinMax>
generation?: Record<string, MinMaxWithWhitelist>
randomisedWeaponModSlots?: string[]
randomisedArmorSlots?: string[]
/** Equipment chances */

View File

@ -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<string, MinMax>, baseBotGeneration: Generation): void
protected adjustGenerationChances(generationChanges: Record<string, MinMaxWithWhitelist>, 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;
}
}

View File

@ -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 =>