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" : [
{
"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,

View File

@ -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<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({
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<string>(settings.equipmentPool);
const equipmentItemTpl = this.weightedRandomHelper.getWeightedValue<string>(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<string, number>,
/** Root Slot being generated */
rootEquipmentSlot: string,
/** Equipment pool for root slot being generated */
rootEquipmentPool: Record<string, number>,
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
}