Add plate filtering system based on bot level

This commit is contained in:
Dev 2024-01-07 15:34:59 +00:00
parent 0fcc411123
commit db13df89d9
3 changed files with 230 additions and 81 deletions

View File

@ -1376,62 +1376,164 @@
} }
} }
], ],
"armorPlateWeighting" : [ "armorPlateWeighting": [{
{ "levelRange": {
"levelRange": {
"min": 1, "min": 1,
"max": 10 "max": 10
}, },
"frontPlateWeights": { "front_plate": {
"level2": 25, "2": 25,
"level3": 20, "3": 20,
"level4": 5, "4": 5,
"level5": 1, "5": 1,
"level6": 1 "6": 1
}, },
"backPlateWeights": { "back_plate": {
"level2": 25, "2": 25,
"level3": 20, "3": 20,
"level4": 5, "4": 5,
"level5": 1, "5": 1,
"level6": 1 "6": 1
}, },
"sidePlateWeights": { "side_plate": {
"level2": 25, "2": 25,
"level3": 20, "3": 20,
"level4": 5, "4": 5,
"level5": 1, "5": 1,
"level6": 1 "6": 1
} }
}, }, {
{ "levelRange": {
"levelRange": {
"min": 11, "min": 11,
"max": 14 "max": 14
}, },
"frontPlateWeights": { "front_plate": {
"level2": 30, "2": 30,
"level3": 15, "3": 15,
"level4": 5, "4": 5,
"level5": 1, "5": 1,
"level6": 1 "6": 1
}, },
"backPlateWeights": { "back_plate": {
"level2": 30, "2": 30,
"level3": 15, "3": 15,
"level4": 5, "4": 5,
"level5": 1, "5": 1,
"level6": 1 "6": 1
}, },
"sidePlateWeights": { "side_plate": {
"level2": 30, "2": 30,
"level3": 15, "3": 15,
"level4": 5, "4": 5,
"level5": 1, "5": 1,
"level6": 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": [{ "whitelist": [{
"levelRange": { "levelRange": {
"min": 101, "min": 101,

View File

@ -6,6 +6,7 @@ import { BotWeaponGeneratorHelper } from "@spt-aki/helpers/BotWeaponGeneratorHel
import { ItemHelper } from "@spt-aki/helpers/ItemHelper"; import { ItemHelper } from "@spt-aki/helpers/ItemHelper";
import { ProbabilityHelper } from "@spt-aki/helpers/ProbabilityHelper"; import { ProbabilityHelper } from "@spt-aki/helpers/ProbabilityHelper";
import { ProfileHelper } from "@spt-aki/helpers/ProfileHelper"; 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 { Mods, ModsChances } from "@spt-aki/models/eft/common/tables/IBotType";
import { Item } from "@spt-aki/models/eft/common/tables/IItem"; import { Item } from "@spt-aki/models/eft/common/tables/IItem";
import { ITemplateItem, Slot } from "@spt-aki/models/eft/common/tables/ITemplateItem"; import { ITemplateItem, Slot } from "@spt-aki/models/eft/common/tables/ITemplateItem";
@ -45,6 +46,7 @@ export class BotEquipmentModGenerator
@inject("BotHelper") protected botHelper: BotHelper, @inject("BotHelper") protected botHelper: BotHelper,
@inject("BotGeneratorHelper") protected botGeneratorHelper: BotGeneratorHelper, @inject("BotGeneratorHelper") protected botGeneratorHelper: BotGeneratorHelper,
@inject("BotWeaponGeneratorHelper") protected botWeaponGeneratorHelper: BotWeaponGeneratorHelper, @inject("BotWeaponGeneratorHelper") protected botWeaponGeneratorHelper: BotWeaponGeneratorHelper,
@inject("WeightedRandomHelper") protected weightedRandomHelper: WeightedRandomHelper,
@inject("LocalisationService") protected localisationService: LocalisationService, @inject("LocalisationService") protected localisationService: LocalisationService,
@inject("BotEquipmentModPoolService") protected botEquipmentModPoolService: BotEquipmentModPoolService, @inject("BotEquipmentModPoolService") protected botEquipmentModPoolService: BotEquipmentModPoolService,
@inject("ConfigServer") protected configServer: ConfigServer, @inject("ConfigServer") protected configServer: ConfigServer,
@ -59,8 +61,6 @@ export class BotEquipmentModGenerator
* @param modPool Mod list to choose frm * @param modPool Mod list to choose frm
* @param parentId parentid of item to add mod to * @param parentId parentid of item to add mod to
* @param parentTemplate template objet of item to add mods 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 * @param forceSpawn should this mod be forced to spawn
* @returns Item + compatible mods as an array * @returns Item + compatible mods as an array
*/ */
@ -106,7 +106,7 @@ export class BotEquipmentModGenerator
forceSpawn = true; 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 modTpl: string;
let found = false; let found = false;
@ -169,16 +169,58 @@ export class BotEquipmentModGenerator
return equipment; 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 // 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; 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<string, number> = 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<string>(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);
} }

View File

@ -168,8 +168,8 @@ export class BotInventoryGenerator
} }
this.generateEquipment({ this.generateEquipment({
equipmentSlot: equipmentSlot, rootEquipmentSlot: equipmentSlot,
equipmentPool: templateInventory.equipment[equipmentSlot], rootEquipmentPool: templateInventory.equipment[equipmentSlot],
modPool: templateInventory.mods, modPool: templateInventory.mods,
spawnChances: equipmentChances, spawnChances: equipmentChances,
botRole: botRole, botRole: botRole,
@ -182,8 +182,8 @@ export class BotInventoryGenerator
// Generate below in specific order // Generate below in specific order
this.generateEquipment({ this.generateEquipment({
equipmentSlot: EquipmentSlots.FACE_COVER, rootEquipmentSlot: EquipmentSlots.FACE_COVER,
equipmentPool: templateInventory.equipment.FaceCover, rootEquipmentPool: templateInventory.equipment.FaceCover,
modPool: templateInventory.mods, modPool: templateInventory.mods,
spawnChances: equipmentChances, spawnChances: equipmentChances,
botRole: botRole, botRole: botRole,
@ -193,8 +193,8 @@ export class BotInventoryGenerator
randomisationDetails: randomistionDetails randomisationDetails: randomistionDetails
}); });
this.generateEquipment({ this.generateEquipment({
equipmentSlot: EquipmentSlots.HEADWEAR, rootEquipmentSlot: EquipmentSlots.HEADWEAR,
equipmentPool: templateInventory.equipment.Headwear, rootEquipmentPool: templateInventory.equipment.Headwear,
modPool: templateInventory.mods, modPool: templateInventory.mods,
spawnChances: equipmentChances, spawnChances: equipmentChances,
botRole: botRole, botRole: botRole,
@ -204,8 +204,8 @@ export class BotInventoryGenerator
randomisationDetails: randomistionDetails randomisationDetails: randomistionDetails
}); });
this.generateEquipment({ this.generateEquipment({
equipmentSlot: EquipmentSlots.EARPIECE, rootEquipmentSlot: EquipmentSlots.EARPIECE,
equipmentPool: templateInventory.equipment.Earpiece, rootEquipmentPool: templateInventory.equipment.Earpiece,
modPool: templateInventory.mods, modPool: templateInventory.mods,
spawnChances: equipmentChances, spawnChances: equipmentChances,
botRole: botRole, botRole: botRole,
@ -215,8 +215,8 @@ export class BotInventoryGenerator
randomisationDetails: randomistionDetails randomisationDetails: randomistionDetails
}); });
this.generateEquipment({ this.generateEquipment({
equipmentSlot: EquipmentSlots.TACTICAL_VEST, rootEquipmentSlot: EquipmentSlots.TACTICAL_VEST,
equipmentPool: templateInventory.equipment.TacticalVest, rootEquipmentPool: templateInventory.equipment.TacticalVest,
modPool: templateInventory.mods, modPool: templateInventory.mods,
spawnChances: equipmentChances, spawnChances: equipmentChances,
botRole: botRole, botRole: botRole,
@ -226,8 +226,8 @@ export class BotInventoryGenerator
randomisationDetails: randomistionDetails randomisationDetails: randomistionDetails
}); });
this.generateEquipment({ this.generateEquipment({
equipmentSlot: EquipmentSlots.ARMOR_VEST, rootEquipmentSlot: EquipmentSlots.ARMOR_VEST,
equipmentPool: templateInventory.equipment.ArmorVest, rootEquipmentPool: templateInventory.equipment.ArmorVest,
modPool: templateInventory.mods, modPool: templateInventory.mods,
spawnChances: equipmentChances, spawnChances: equipmentChances,
botRole: botRole, botRole: botRole,
@ -246,34 +246,34 @@ export class BotInventoryGenerator
* @param spawnChances Chances items will be chosen to be added * @param spawnChances Chances items will be chosen to be added
* @param botRole Role of bot e.g. assault * @param botRole Role of bot e.g. assault
* @param inventory Inventory to add item into * @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 protected generateEquipment(settings: IGenerateEquipmentProperties): void
{ {
const spawnChance = const spawnChance =
([EquipmentSlots.POCKETS, EquipmentSlots.SECURED_CONTAINER] as string[]).includes(settings.equipmentSlot) ([EquipmentSlots.POCKETS, EquipmentSlots.SECURED_CONTAINER] as string[]).includes(settings.rootEquipmentSlot)
? 100 ? 100
: settings.spawnChances.equipment[settings.equipmentSlot]; : settings.spawnChances.equipment[settings.rootEquipmentSlot];
if (typeof spawnChance === "undefined") if (typeof spawnChance === "undefined")
{ {
this.logger.warning( 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; return;
} }
const shouldSpawn = this.randomUtil.getChance100(spawnChance); 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 id = this.hashUtil.generate();
const equipmentItemTpl = this.weightedRandomHelper.getWeightedValue<string>(settings.equipmentPool); const equipmentItemTpl = this.weightedRandomHelper.getWeightedValue<string>(settings.rootEquipmentPool);
const itemTemplate = this.itemHelper.getItem(equipmentItemTpl); const itemTemplate = this.itemHelper.getItem(equipmentItemTpl);
if (!itemTemplate[0]) if (!itemTemplate[0])
{ {
this.logger.error(this.localisationService.getText("bot-missing_item_template", equipmentItemTpl)); this.logger.error(this.localisationService.getText("bot-missing_item_template", equipmentItemTpl));
this.logger.info(`EquipmentSlot -> ${settings.equipmentSlot}`); this.logger.info(`EquipmentSlot -> ${settings.rootEquipmentSlot}`);
return; return;
} }
@ -282,7 +282,7 @@ export class BotInventoryGenerator
this.botGeneratorHelper.isItemIncompatibleWithCurrentItems( this.botGeneratorHelper.isItemIncompatibleWithCurrentItems(
settings.inventory.items, settings.inventory.items,
equipmentItemTpl, equipmentItemTpl,
settings.equipmentSlot, settings.rootEquipmentSlot,
).incompatible ).incompatible
) )
{ {
@ -294,7 +294,7 @@ export class BotInventoryGenerator
_id: id, _id: id,
_tpl: equipmentItemTpl, _tpl: equipmentItemTpl,
parentId: settings.inventory.equipment, parentId: settings.inventory.equipment,
slotId: settings.equipmentSlot, slotId: settings.rootEquipmentSlot,
...this.botGeneratorHelper.generateExtraPropertiesForItem(itemTemplate[1], settings.botRole), ...this.botGeneratorHelper.generateExtraPropertiesForItem(itemTemplate[1], settings.botRole),
}; };
@ -302,7 +302,7 @@ export class BotInventoryGenerator
const botEquipmentRole = this.botGeneratorHelper.getBotEquipmentRole(settings.botRole); const botEquipmentRole = this.botGeneratorHelper.getBotEquipmentRole(settings.botRole);
if ( if (
this.botConfig.equipment[botEquipmentRole] this.botConfig.equipment[botEquipmentRole]
&& settings.randomisationDetails?.randomisedArmorSlots?.includes(settings.equipmentSlot) && settings.randomisationDetails?.randomisedArmorSlots?.includes(settings.rootEquipmentSlot)
) )
{ {
settings.modPool[equipmentItemTpl] = this.getFilteredDynamicModsForItem( settings.modPool[equipmentItemTpl] = this.getFilteredDynamicModsForItem(
@ -316,7 +316,6 @@ export class BotInventoryGenerator
{ {
const items = this.botEquipmentModGenerator.generateModsForEquipment( const items = this.botEquipmentModGenerator.generateModsForEquipment(
[item], [item],
settings.modPool,
id, id,
itemTemplate[1], itemTemplate[1],
settings settings
@ -467,13 +466,19 @@ export class BotInventoryGenerator
export interface IGenerateEquipmentProperties export interface IGenerateEquipmentProperties
{ {
equipmentSlot: string, /** Root Slot being generated */
equipmentPool: Record<string, number>, rootEquipmentSlot: string,
/** Equipment pool for root slot being generated */
rootEquipmentPool: Record<string, number>,
modPool: Mods, modPool: Mods,
/** Dictionary of mod items and their chance to spawn for this bot type */
spawnChances: Chances, spawnChances: Chances,
/** Role being generated for */
botRole: string, botRole: string,
/** level of bot being generated */
botLevel: number, botLevel: number,
inventory: PmcInventory, inventory: PmcInventory,
botEquipmentConfig: EquipmentFilters, botEquipmentConfig: EquipmentFilters,
/** Settings from bot.json to adjust how item is generated */
randomisationDetails: RandomisationDetails randomisationDetails: RandomisationDetails
} }