Server/project/src/generators/BotInventoryGenerator.ts

489 lines
18 KiB
TypeScript
Raw Normal View History

2023-03-03 16:23:46 +01:00
import { inject, injectable } from "tsyringe";
import { BotEquipmentModGenerator } from "@spt-aki/generators/BotEquipmentModGenerator";
import { BotLootGenerator } from "@spt-aki/generators/BotLootGenerator";
import { BotWeaponGenerator } from "@spt-aki/generators/BotWeaponGenerator";
import { BotGeneratorHelper } from "@spt-aki/helpers/BotGeneratorHelper";
import { BotHelper } from "@spt-aki/helpers/BotHelper";
import { ItemHelper } from "@spt-aki/helpers/ItemHelper";
import { WeightedRandomHelper } from "@spt-aki/helpers/WeightedRandomHelper";
import { Inventory as PmcInventory } from "@spt-aki/models/eft/common/tables/IBotBase";
import { Chances, Generation, IBotType, Inventory, Mods } from "@spt-aki/models/eft/common/tables/IBotType";
import { ConfigTypes } from "@spt-aki/models/enums/ConfigTypes";
import { EquipmentSlots } from "@spt-aki/models/enums/EquipmentSlots";
import { EquipmentFilterDetails, IBotConfig, RandomisationDetails } from "@spt-aki/models/spt/config/IBotConfig";
import { ILogger } from "@spt-aki/models/spt/utils/ILogger";
import { ConfigServer } from "@spt-aki/servers/ConfigServer";
import { DatabaseServer } from "@spt-aki/servers/DatabaseServer";
import { BotEquipmentModPoolService } from "@spt-aki/services/BotEquipmentModPoolService";
import { LocalisationService } from "@spt-aki/services/LocalisationService";
import { HashUtil } from "@spt-aki/utils/HashUtil";
import { RandomUtil } from "@spt-aki/utils/RandomUtil";
2023-03-03 16:23:46 +01:00
@injectable()
export class BotInventoryGenerator
{
protected botConfig: IBotConfig;
constructor(
@inject("WinstonLogger") protected logger: ILogger,
@inject("HashUtil") protected hashUtil: HashUtil,
@inject("RandomUtil") protected randomUtil: RandomUtil,
@inject("DatabaseServer") protected databaseServer: DatabaseServer,
@inject("BotWeaponGenerator") protected botWeaponGenerator: BotWeaponGenerator,
@inject("BotLootGenerator") protected botLootGenerator: BotLootGenerator,
@inject("BotGeneratorHelper") protected botGeneratorHelper: BotGeneratorHelper,
@inject("BotHelper") protected botHelper: BotHelper,
@inject("WeightedRandomHelper") protected weightedRandomHelper: WeightedRandomHelper,
@inject("ItemHelper") protected itemHelper: ItemHelper,
2023-03-03 16:23:46 +01:00
@inject("LocalisationService") protected localisationService: LocalisationService,
@inject("BotEquipmentModPoolService") protected botEquipmentModPoolService: BotEquipmentModPoolService,
@inject("BotEquipmentModGenerator") protected botEquipmentModGenerator: BotEquipmentModGenerator,
2023-11-13 17:05:05 +01:00
@inject("ConfigServer") protected configServer: ConfigServer,
2023-03-03 16:23:46 +01:00
)
{
this.botConfig = this.configServer.getConfig(ConfigTypes.BOT);
}
/**
* Add equipment/weapons/loot to bot
* @param sessionId Session id
* @param botJsonTemplate Base json db file for the bot having its loot generated
2023-03-03 16:23:46 +01:00
* @param botRole Role bot has (assault/pmcBot)
* @param isPmc Is bot being converted into a pmc
* @param botLevel Level of bot being generated
* @returns PmcInventory object with equipment/weapons/loot
*/
2023-11-13 17:05:05 +01:00
public generateInventory(
sessionId: string,
botJsonTemplate: IBotType,
botRole: string,
isPmc: boolean,
botLevel: number,
): PmcInventory
2023-03-03 16:23:46 +01:00
{
const templateInventory = botJsonTemplate.inventory;
const equipmentChances = botJsonTemplate.chances;
const itemGenerationLimitsMinMax = botJsonTemplate.generation;
// Generate base inventory with no items
const botInventory = this.generateInventoryBase();
this.generateAndAddEquipmentToBot(templateInventory, equipmentChances, botRole, botInventory, botLevel);
// Roll weapon spawns (primary/secondary/holster) and generate a weapon for each roll that passed
2023-11-13 17:05:05 +01:00
this.generateAndAddWeaponsToBot(
templateInventory,
equipmentChances,
sessionId,
botInventory,
botRole,
isPmc,
itemGenerationLimitsMinMax,
botLevel,
);
2023-03-03 16:23:46 +01:00
// Pick loot and add to bots containers (rig/backpack/pockets/secure)
this.botLootGenerator.generateLoot(sessionId, botJsonTemplate, isPmc, botRole, botInventory, botLevel);
2023-03-03 16:23:46 +01:00
return botInventory;
}
/**
* Create a pmcInventory object with all the base/generic items needed
* @returns PmcInventory object
*/
protected generateInventoryBase(): PmcInventory
{
const equipmentId = this.hashUtil.generate();
const equipmentTpl = "55d7217a4bdc2d86028b456d";
const stashId = this.hashUtil.generate();
const stashTpl = "566abbc34bdc2d92178b4576";
const questRaidItemsId = this.hashUtil.generate();
const questRaidItemsTpl = "5963866286f7747bf429b572";
const questStashItemsId = this.hashUtil.generate();
const questStashItemsTpl = "5963866b86f7747bfa1c4462";
const sortingTableId = this.hashUtil.generate();
const sortingTableTpl = "602543c13fee350cd564d032";
return {
items: [
{
2023-10-31 23:52:09 +01:00
_id: equipmentId,
2023-11-13 17:05:05 +01:00
_tpl: equipmentTpl,
2023-03-03 16:23:46 +01:00
},
{
2023-10-31 23:52:09 +01:00
_id: stashId,
2023-11-13 17:05:05 +01:00
_tpl: stashTpl,
2023-03-03 16:23:46 +01:00
},
{
2023-10-31 23:52:09 +01:00
_id: questRaidItemsId,
2023-11-13 17:05:05 +01:00
_tpl: questRaidItemsTpl,
2023-03-03 16:23:46 +01:00
},
{
2023-10-31 23:52:09 +01:00
_id: questStashItemsId,
2023-11-13 17:05:05 +01:00
_tpl: questStashItemsTpl,
2023-03-03 16:23:46 +01:00
},
{
2023-10-31 23:52:09 +01:00
_id: sortingTableId,
2023-11-13 17:05:05 +01:00
_tpl: sortingTableTpl,
},
2023-03-03 16:23:46 +01:00
],
equipment: equipmentId,
stash: stashId,
questRaidItems: questRaidItemsId,
questStashItems: questStashItemsId,
sortingTable: sortingTableId,
hideoutAreaStashes: {},
2023-11-13 17:05:05 +01:00
fastPanel: {},
2023-03-03 16:23:46 +01:00
};
}
/**
* Add equipment to a bot
* @param templateInventory bot/x.json data from db
* @param equipmentChances Chances items will be added to bot
* @param botRole Role bot has (assault/pmcBot)
* @param botInventory Inventory to add equipment to
* @param botLevel Level of bot
*/
2023-11-13 17:05:05 +01:00
protected generateAndAddEquipmentToBot(
templateInventory: Inventory,
equipmentChances: Chances,
botRole: string,
botInventory: PmcInventory,
botLevel: number,
): void
2023-03-03 16:23:46 +01:00
{
// These will be handled later
const excludedSlots: string[] = [
EquipmentSlots.FIRST_PRIMARY_WEAPON,
EquipmentSlots.SECOND_PRIMARY_WEAPON,
EquipmentSlots.HOLSTER,
EquipmentSlots.ARMOR_VEST,
EquipmentSlots.TACTICAL_VEST,
EquipmentSlots.FACE_COVER,
EquipmentSlots.HEADWEAR,
2023-11-13 17:05:05 +01:00
EquipmentSlots.EARPIECE,
2023-03-03 16:23:46 +01:00
];
const botEquipConfig = this.botConfig.equipment[this.botGeneratorHelper.getBotEquipmentRole(botRole)];
const randomistionDetails = this.botHelper.getBotRandomizationDetails(botLevel, botEquipConfig);
for (const equipmentSlot in templateInventory.equipment)
{
2023-11-13 17:05:05 +01:00
// Weapons have special generation and will be generated separately; ArmorVest should be generated after TactivalVest
2023-03-03 16:23:46 +01:00
if (excludedSlots.includes(equipmentSlot))
{
continue;
}
2023-11-13 17:05:05 +01:00
this.generateEquipment(
equipmentSlot,
templateInventory.equipment[equipmentSlot],
templateInventory.mods,
equipmentChances,
botRole,
botInventory,
randomistionDetails,
);
2023-03-03 16:23:46 +01:00
}
// Generate below in specific order
2023-11-13 17:05:05 +01:00
this.generateEquipment(
EquipmentSlots.FACE_COVER,
templateInventory.equipment.FaceCover,
templateInventory.mods,
equipmentChances,
botRole,
botInventory,
randomistionDetails,
);
this.generateEquipment(
EquipmentSlots.HEADWEAR,
templateInventory.equipment.Headwear,
templateInventory.mods,
equipmentChances,
botRole,
botInventory,
randomistionDetails,
);
this.generateEquipment(
EquipmentSlots.EARPIECE,
templateInventory.equipment.Earpiece,
templateInventory.mods,
equipmentChances,
botRole,
botInventory,
randomistionDetails,
);
this.generateEquipment(
EquipmentSlots.TACTICAL_VEST,
templateInventory.equipment.TacticalVest,
templateInventory.mods,
equipmentChances,
botRole,
botInventory,
randomistionDetails,
);
this.generateEquipment(
EquipmentSlots.ARMOR_VEST,
templateInventory.equipment.ArmorVest,
templateInventory.mods,
equipmentChances,
botRole,
botInventory,
randomistionDetails,
);
2023-03-03 16:23:46 +01:00
}
/**
* Add a piece of equipment with mods to inventory from the provided pools
* @param equipmentSlot Slot to select an item for
* @param equipmentPool Possible items to choose from
* @param modPool Possible mods to apply to item chosen
* @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
*/
protected generateEquipment(
equipmentSlot: string,
equipmentPool: Record<string, number>,
modPool: Mods,
spawnChances: Chances,
botRole: string,
inventory: PmcInventory,
2023-11-13 17:05:05 +01:00
randomisationDetails: RandomisationDetails,
): void
2023-03-03 16:23:46 +01:00
{
2023-11-13 17:05:05 +01:00
const spawnChance =
([EquipmentSlots.POCKETS, EquipmentSlots.SECURED_CONTAINER] as string[]).includes(equipmentSlot) ?
100 :
spawnChances.equipment[equipmentSlot];
2023-03-03 16:23:46 +01:00
if (typeof spawnChance === "undefined")
{
2023-11-13 17:05:05 +01:00
this.logger.warning(
this.localisationService.getText("bot-no_spawn_chance_defined_for_equipment_slot", equipmentSlot),
);
2023-03-03 16:23:46 +01:00
return;
}
const shouldSpawn = this.randomUtil.getChance100(spawnChance);
if (Object.keys(equipmentPool).length && shouldSpawn)
{
const id = this.hashUtil.generate();
const equipmentItemTpl = this.weightedRandomHelper.getWeightedValue<string>(equipmentPool);
const itemTemplate = this.itemHelper.getItem(equipmentItemTpl);
2023-03-03 16:23:46 +01:00
if (!itemTemplate[0])
2023-03-03 16:23:46 +01:00
{
this.logger.error(this.localisationService.getText("bot-missing_item_template", equipmentItemTpl));
this.logger.info(`EquipmentSlot -> ${equipmentSlot}`);
return;
}
2023-11-13 17:05:05 +01:00
if (
this.botGeneratorHelper.isItemIncompatibleWithCurrentItems(
inventory.items,
equipmentItemTpl,
equipmentSlot,
).incompatible
)
2023-03-03 16:23:46 +01:00
{
// Bad luck - randomly picked item was not compatible with current gear
return;
}
const item = {
2023-10-31 23:52:09 +01:00
_id: id,
_tpl: equipmentItemTpl,
parentId: inventory.equipment,
slotId: equipmentSlot,
2023-11-13 17:05:05 +01:00
...this.botGeneratorHelper.generateExtraPropertiesForItem(itemTemplate[1], botRole),
2023-03-03 16:23:46 +01:00
};
// use dynamic mod pool if enabled in config
const botEquipmentRole = this.botGeneratorHelper.getBotEquipmentRole(botRole);
2023-11-13 17:05:05 +01:00
if (
this.botConfig.equipment[botEquipmentRole] &&
randomisationDetails?.randomisedArmorSlots?.includes(equipmentSlot)
)
2023-03-03 16:23:46 +01:00
{
2023-11-13 17:05:05 +01:00
modPool[equipmentItemTpl] = this.getFilteredDynamicModsForItem(
equipmentItemTpl,
this.botConfig.equipment[botEquipmentRole].blacklist,
);
2023-03-03 16:23:46 +01:00
}
2023-11-13 17:05:05 +01:00
if (
typeof (modPool[equipmentItemTpl]) !== "undefined" ||
Object.keys(modPool[equipmentItemTpl] || {}).length > 0
)
2023-03-03 16:23:46 +01:00
{
2023-11-13 17:05:05 +01:00
const items = this.botEquipmentModGenerator.generateModsForEquipment(
[item],
modPool,
id,
itemTemplate[1],
spawnChances.mods,
botRole,
);
2023-03-03 16:23:46 +01:00
inventory.items.push(...items);
}
else
{
inventory.items.push(item);
}
}
}
/**
* Get all possible mods for item and filter down based on equipment blacklist from bot.json config
2023-11-13 17:05:05 +01:00
* @param itemTpl Item mod pool is being retrieved and filtered
2023-03-03 16:23:46 +01:00
* @param equipmentBlacklist blacklist to filter mod pool with
* @returns Filtered pool of mods
*/
2023-11-13 17:05:05 +01:00
protected getFilteredDynamicModsForItem(
itemTpl: string,
equipmentBlacklist: EquipmentFilterDetails[],
): Record<string, string[]>
2023-03-03 16:23:46 +01:00
{
const modPool = this.botEquipmentModPoolService.getModsForGearSlot(itemTpl);
for (const modSlot of Object.keys(modPool ?? []))
2023-03-03 16:23:46 +01:00
{
const blacklistedMods = equipmentBlacklist[0]?.equipment[modSlot] || [];
2023-11-13 17:05:05 +01:00
const filteredMods = modPool[modSlot].filter((x) => !blacklistedMods.includes(x));
2023-03-03 16:23:46 +01:00
if (filteredMods.length > 0)
{
modPool[modSlot] = filteredMods;
2023-03-03 16:23:46 +01:00
}
}
return modPool;
2023-03-03 16:23:46 +01:00
}
/**
* Work out what weapons bot should have equipped and add them to bot inventory
* @param templateInventory bot/x.json data from db
* @param equipmentChances Chances bot can have equipment equipped
* @param sessionId Session id
* @param botInventory Inventory to add weapons to
* @param botRole assault/pmcBot/bossTagilla etc
* @param isPmc Is the bot being generated as a pmc
* @param botLevel level of bot having weapon generated
* @param itemGenerationLimitsMinMax Limits for items the bot can have
*/
2023-11-13 17:05:05 +01:00
protected generateAndAddWeaponsToBot(
templateInventory: Inventory,
equipmentChances: Chances,
sessionId: string,
botInventory: PmcInventory,
botRole: string,
isPmc: boolean,
itemGenerationLimitsMinMax: Generation,
botLevel: number,
): void
2023-03-03 16:23:46 +01:00
{
const weaponSlotsToFill = this.getDesiredWeaponsForBot(equipmentChances);
for (const weaponSlot of weaponSlotsToFill)
{
// Add weapon to bot if true and bot json has something to put into the slot
if (weaponSlot.shouldSpawn && Object.keys(templateInventory.equipment[weaponSlot.slot]).length)
{
2023-11-13 17:05:05 +01:00
this.addWeaponAndMagazinesToInventory(
sessionId,
weaponSlot,
templateInventory,
botInventory,
equipmentChances,
botRole,
isPmc,
itemGenerationLimitsMinMax,
botLevel,
);
2023-03-03 16:23:46 +01:00
}
}
}
/**
* Calculate if the bot should have weapons in Primary/Secondary/Holster slots
* @param equipmentChances Chances bot has certain equipment
* @returns What slots bot should have weapons generated for
*/
2023-11-13 17:05:05 +01:00
protected getDesiredWeaponsForBot(equipmentChances: Chances): {slot: EquipmentSlots; shouldSpawn: boolean;}[]
2023-03-03 16:23:46 +01:00
{
const shouldSpawnPrimary = this.randomUtil.getChance100(equipmentChances.equipment.FirstPrimaryWeapon);
return [
{
slot: EquipmentSlots.FIRST_PRIMARY_WEAPON,
2023-11-13 17:05:05 +01:00
shouldSpawn: shouldSpawnPrimary,
2023-03-03 16:23:46 +01:00
},
{
slot: EquipmentSlots.SECOND_PRIMARY_WEAPON,
2023-11-13 17:05:05 +01:00
shouldSpawn: shouldSpawnPrimary ?
this.randomUtil.getChance100(equipmentChances.equipment.SecondPrimaryWeapon) :
false,
2023-03-03 16:23:46 +01:00
},
{
slot: EquipmentSlots.HOLSTER,
2023-11-13 17:05:05 +01:00
shouldSpawn: shouldSpawnPrimary ?
this.randomUtil.getChance100(equipmentChances.equipment.Holster) // Primary weapon = roll for chance at pistol
:
true, // No primary = force pistol
},
2023-03-03 16:23:46 +01:00
];
}
/**
* Add weapon + spare mags/ammo to bots inventory
* @param sessionId Session id
* @param weaponSlot Weapon slot being generated
* @param templateInventory bot/x.json data from db
* @param botInventory Inventory to add weapon+mags/ammo to
* @param equipmentChances Chances bot can have equipment equipped
* @param botRole assault/pmcBot/bossTagilla etc
* @param isPmc Is the bot being generated as a pmc
2023-11-13 17:05:05 +01:00
* @param itemGenerationWeights
2023-03-03 16:23:46 +01:00
*/
protected addWeaponAndMagazinesToInventory(
sessionId: string,
2023-11-13 17:05:05 +01:00
weaponSlot: {slot: EquipmentSlots; shouldSpawn: boolean;},
2023-03-03 16:23:46 +01:00
templateInventory: Inventory,
botInventory: PmcInventory,
equipmentChances: Chances,
botRole: string,
isPmc: boolean,
itemGenerationWeights: Generation,
2023-11-13 17:05:05 +01:00
botLevel: number,
): void
2023-03-03 16:23:46 +01:00
{
const generatedWeapon = this.botWeaponGenerator.generateRandomWeapon(
sessionId,
weaponSlot.slot,
templateInventory,
botInventory.equipment,
equipmentChances.mods,
botRole,
isPmc,
2023-11-13 17:05:05 +01:00
botLevel,
);
2023-03-03 16:23:46 +01:00
botInventory.items.push(...generatedWeapon.weapon);
2023-11-13 17:05:05 +01:00
this.botWeaponGenerator.addExtraMagazinesToInventory(
generatedWeapon,
itemGenerationWeights.items.magazines,
botInventory,
botRole,
);
2023-03-03 16:23:46 +01:00
}
2023-11-13 17:05:05 +01:00
}