Server/project/src/generators/BotWeaponGenerator.ts
Dev f3964639bd Rename mods to weaponMods and add new object equipmentMods
regenerate bot jsons to include this new data (includes correct inclusion of equipment slot "TacticalVest" which was previously missing)

Fix issue with PMM ammo causes generation issues
2024-01-09 15:31:56 +00:00

797 lines
30 KiB
TypeScript

import { inject, injectAll, injectable } from "tsyringe";
import { BotEquipmentModGenerator } from "@spt-aki/generators/BotEquipmentModGenerator";
import { IInventoryMagGen } from "@spt-aki/generators/weapongen/IInventoryMagGen";
import { InventoryMagGen } from "@spt-aki/generators/weapongen/InventoryMagGen";
import { BotGeneratorHelper } from "@spt-aki/helpers/BotGeneratorHelper";
import { BotWeaponGeneratorHelper } from "@spt-aki/helpers/BotWeaponGeneratorHelper";
import { ItemHelper } from "@spt-aki/helpers/ItemHelper";
import { WeightedRandomHelper } from "@spt-aki/helpers/WeightedRandomHelper";
import { IPreset } from "@spt-aki/models/eft/common/IGlobals";
import { Inventory as PmcInventory } from "@spt-aki/models/eft/common/tables/IBotBase";
import { GenerationData, Inventory, ModsChances } from "@spt-aki/models/eft/common/tables/IBotType";
import { Item } from "@spt-aki/models/eft/common/tables/IItem";
import { ITemplateItem } from "@spt-aki/models/eft/common/tables/ITemplateItem";
import { ConfigTypes } from "@spt-aki/models/enums/ConfigTypes";
import { EquipmentSlots } from "@spt-aki/models/enums/EquipmentSlots";
import { GenerateWeaponResult } from "@spt-aki/models/spt/bots/GenerateWeaponResult";
import { IBotConfig } from "@spt-aki/models/spt/config/IBotConfig";
import { IPmcConfig } from "@spt-aki/models/spt/config/IPmcConfig";
import { IRepairConfig } from "@spt-aki/models/spt/config/IRepairConfig";
import { ILogger } from "@spt-aki/models/spt/utils/ILogger";
import { ConfigServer } from "@spt-aki/servers/ConfigServer";
import { DatabaseServer } from "@spt-aki/servers/DatabaseServer";
import { BotWeaponModLimitService } from "@spt-aki/services/BotWeaponModLimitService";
import { LocalisationService } from "@spt-aki/services/LocalisationService";
import { RepairService } from "@spt-aki/services/RepairService";
import { HashUtil } from "@spt-aki/utils/HashUtil";
import { JsonUtil } from "@spt-aki/utils/JsonUtil";
import { RandomUtil } from "@spt-aki/utils/RandomUtil";
@injectable()
export class BotWeaponGenerator
{
protected readonly modMagazineSlotId = "mod_magazine";
protected botConfig: IBotConfig;
protected pmcConfig: IPmcConfig;
protected repairConfig: IRepairConfig;
constructor(
@inject("JsonUtil") protected jsonUtil: JsonUtil,
@inject("WinstonLogger") protected logger: ILogger,
@inject("HashUtil") protected hashUtil: HashUtil,
@inject("DatabaseServer") protected databaseServer: DatabaseServer,
@inject("ItemHelper") protected itemHelper: ItemHelper,
@inject("WeightedRandomHelper") protected weightedRandomHelper: WeightedRandomHelper,
@inject("BotGeneratorHelper") protected botGeneratorHelper: BotGeneratorHelper,
@inject("RandomUtil") protected randomUtil: RandomUtil,
@inject("ConfigServer") protected configServer: ConfigServer,
@inject("BotWeaponGeneratorHelper") protected botWeaponGeneratorHelper: BotWeaponGeneratorHelper,
@inject("BotWeaponModLimitService") protected botWeaponModLimitService: BotWeaponModLimitService,
@inject("BotEquipmentModGenerator") protected botEquipmentModGenerator: BotEquipmentModGenerator,
@inject("LocalisationService") protected localisationService: LocalisationService,
@inject("RepairService") protected repairService: RepairService,
@injectAll("InventoryMagGen") protected inventoryMagGenComponents: IInventoryMagGen[],
)
{
this.botConfig = this.configServer.getConfig(ConfigTypes.BOT);
this.pmcConfig = this.configServer.getConfig(ConfigTypes.PMC);
this.repairConfig = this.configServer.getConfig(ConfigTypes.REPAIR);
this.inventoryMagGenComponents.sort((a, b) => a.getPriority() - b.getPriority());
}
/**
* Pick a random weapon based on weightings and generate a functional weapon
* @param equipmentSlot Primary/secondary/holster
* @param botTemplateInventory e.g. assault.json
* @param weaponParentId
* @param modChances
* @param botRole role of bot, e.g. assault/followerBully
* @param isPmc Is weapon generated for a pmc
* @returns GenerateWeaponResult object
*/
public generateRandomWeapon(
sessionId: string,
equipmentSlot: string,
botTemplateInventory: Inventory,
weaponParentId: string,
modChances: ModsChances,
botRole: string,
isPmc: boolean,
botLevel: number,
): GenerateWeaponResult
{
const weaponTpl = this.pickWeightedWeaponTplFromPool(equipmentSlot, botTemplateInventory);
return this.generateWeaponByTpl(
sessionId,
weaponTpl,
equipmentSlot,
botTemplateInventory,
weaponParentId,
modChances,
botRole,
isPmc,
botLevel,
);
}
/**
* Get a random weighted weapon from a bots pool of weapons
* @param equipmentSlot Primary/secondary/holster
* @param botTemplateInventory e.g. assault.json
* @returns weapon tpl
*/
public pickWeightedWeaponTplFromPool(equipmentSlot: string, botTemplateInventory: Inventory): string
{
const weaponPool = botTemplateInventory.equipment[equipmentSlot];
return this.weightedRandomHelper.getWeightedValue<string>(weaponPool);
}
/**
* Generated a weapon based on the supplied weapon tpl
* @param weaponTpl weapon tpl to generate (use pickWeightedWeaponTplFromPool())
* @param equipmentSlot slot to fit into, primary/secondary/holster
* @param botTemplateInventory e.g. assault.json
* @param weaponParentId ParentId of the weapon being generated
* @param modChances Dictionary of item types and % chance weapon will have that mod
* @param botRole e.g. assault/exusec
* @param isPmc Is weapon being generated for a pmc
* @returns GenerateWeaponResult object
*/
public generateWeaponByTpl(
sessionId: string,
weaponTpl: string,
equipmentSlot: string,
botTemplateInventory: Inventory,
weaponParentId: string,
modChances: ModsChances,
botRole: string,
isPmc: boolean,
botLevel: number,
): GenerateWeaponResult
{
const modPool = botTemplateInventory.mods;
const weaponItemTemplate = this.itemHelper.getItem(weaponTpl)[1];
if (!weaponItemTemplate)
{
this.logger.error(this.localisationService.getText("bot-missing_item_template", weaponTpl));
this.logger.error(`WeaponSlot -> ${equipmentSlot}`);
return;
}
// Find ammo to use when filling magazines/chamber
if (!botTemplateInventory.Ammo)
{
this.logger.error(this.localisationService.getText("bot-no_ammo_found_in_bot_json", botRole));
throw new Error(this.localisationService.getText("bot-generation_failed"));
}
const ammoTpl = this.getWeightedCompatibleAmmo(botTemplateInventory.Ammo, weaponItemTemplate);
// Create with just base weapon item
let weaponWithModsArray = this.constructWeaponBaseArray(
weaponTpl,
weaponParentId,
equipmentSlot,
weaponItemTemplate,
botRole,
);
// Chance to add randomised weapon enhancement
if (isPmc && this.randomUtil.getChance100(this.pmcConfig.weaponHasEnhancementChancePercent))
{
const weaponConfig = this.repairConfig.repairKit.weapon;
this.repairService.addBuff(weaponConfig, weaponWithModsArray[0]);
}
// Add mods to weapon base
if (Object.keys(modPool).includes(weaponTpl))
{
const botEquipmentRole = this.botGeneratorHelper.getBotEquipmentRole(botRole);
const modLimits = this.botWeaponModLimitService.getWeaponModLimits(botEquipmentRole);
weaponWithModsArray = this.botEquipmentModGenerator.generateModsForWeapon(
sessionId,
weaponWithModsArray,
modPool,
weaponWithModsArray[0]._id,
weaponItemTemplate,
modChances,
ammoTpl,
botRole,
botLevel,
modLimits,
botEquipmentRole,
);
}
// Use weapon preset from globals.json if weapon isnt valid
if (!this.isWeaponValid(weaponWithModsArray, botRole))
{
// Weapon is bad, fall back to weapons preset
weaponWithModsArray = this.getPresetWeaponMods(
weaponTpl,
equipmentSlot,
weaponParentId,
weaponItemTemplate,
botRole,
);
}
// Fill existing magazines to full and sync ammo type
for (const magazine of weaponWithModsArray.filter((x) => x.slotId === this.modMagazineSlotId))
{
this.fillExistingMagazines(weaponWithModsArray, magazine, ammoTpl);
}
// Add cartridge(s) to gun chamber(s)
if (
weaponItemTemplate._props.Chambers?.length > 0
&& weaponItemTemplate._props.Chambers[0]?._props?.filters[0]?.Filter?.includes(ammoTpl)
)
{
// Guns have variety of possible Chamber ids, patron_in_weapon/patron_in_weapon_000/patron_in_weapon_001
const chamberSlotNames = weaponItemTemplate._props.Chambers.map(x => x._name);
this.addCartridgeToChamber(weaponWithModsArray, ammoTpl, chamberSlotNames);
}
// Fill UBGL if found
const ubglMod = weaponWithModsArray.find((x) => x.slotId === "mod_launcher");
let ubglAmmoTpl: string = undefined;
if (ubglMod)
{
const ubglTemplate = this.itemHelper.getItem(ubglMod._tpl)[1];
ubglAmmoTpl = this.getWeightedCompatibleAmmo(botTemplateInventory.Ammo, ubglTemplate);
this.fillUbgl(weaponWithModsArray, ubglMod, ubglAmmoTpl);
}
return {
weapon: weaponWithModsArray,
chosenAmmoTpl: ammoTpl,
chosenUbglAmmoTpl: ubglAmmoTpl,
weaponMods: modPool,
weaponTemplate: weaponItemTemplate,
};
}
/**
* Insert a cartridge(s) into a weapon
* Handles all chambers - patron_in_weapon, patron_in_weapon_000 etc
* @param weaponWithModsArray Weapon and mods
* @param ammoTpl Cartridge to add to weapon
* @param chamberSlotIds name of slots to create or add ammo to
*/
protected addCartridgeToChamber(weaponWithModsArray: Item[], ammoTpl: string, chamberSlotIds: string[]): void
{
for (const slotId of chamberSlotIds)
{
const existingItemWithSlot = weaponWithModsArray.find((x) => x.slotId === slotId);
if (!existingItemWithSlot)
{
// Not found, add new slot to weapon
weaponWithModsArray.push({
_id: this.hashUtil.generate(),
_tpl: ammoTpl,
parentId: weaponWithModsArray[0]._id,
slotId: slotId,
upd: { StackObjectsCount: 1 },
});
}
else
{
// Already exists, update values
existingItemWithSlot._tpl = ammoTpl;
existingItemWithSlot.upd = { StackObjectsCount: 1 };
}
}
}
/**
* Create array with weapon base as only element and
* add additional properties based on weapon type
* @param weaponTpl Weapon tpl to create item with
* @param weaponParentId Weapons parent id
* @param equipmentSlot e.g. primary/secondary/holster
* @param weaponItemTemplate db template for weapon
* @param botRole for durability values
* @returns Base weapon item in array
*/
protected constructWeaponBaseArray(
weaponTpl: string,
weaponParentId: string,
equipmentSlot: string,
weaponItemTemplate: ITemplateItem,
botRole: string,
): Item[]
{
return [{
_id: this.hashUtil.generate(),
_tpl: weaponTpl,
parentId: weaponParentId,
slotId: equipmentSlot,
...this.botGeneratorHelper.generateExtraPropertiesForItem(weaponItemTemplate, botRole),
}];
}
/**
* Get the mods necessary to kit out a weapon to its preset level
* @param weaponTpl weapon to find preset for
* @param equipmentSlot the slot the weapon will be placed in
* @param weaponParentId Value used for the parentid
* @returns array of weapon mods
*/
protected getPresetWeaponMods(
weaponTpl: string,
equipmentSlot: string,
weaponParentId: string,
itemTemplate: ITemplateItem,
botRole: string,
): Item[]
{
// Invalid weapon generated, fallback to preset
this.logger.warning(
this.localisationService.getText("bot-weapon_generated_incorrect_using_default", weaponTpl),
);
const weaponMods = [];
// TODO: Right now, preset weapons trigger a lot of warnings regarding missing ammo in magazines & such
let preset: IPreset;
for (const presetObj of Object.values(this.databaseServer.getTables().globals.ItemPresets))
{
if (presetObj._items[0]._tpl === weaponTpl)
{
preset = this.jsonUtil.clone(presetObj);
break;
}
}
if (preset)
{
const parentItem = preset._items[0];
preset._items[0] = {
...parentItem,
...{
parentId: weaponParentId,
slotId: equipmentSlot,
...this.botGeneratorHelper.generateExtraPropertiesForItem(itemTemplate, botRole),
},
};
weaponMods.push(...preset._items);
}
else
{
throw new Error(this.localisationService.getText("bot-missing_weapon_preset", weaponTpl));
}
return weaponMods;
}
/**
* Checks if all required slots are occupied on a weapon and all it's mods
* @param weaponItemArray Weapon + mods
* @param botRole role of bot weapon is for
* @returns true if valid
*/
protected isWeaponValid(weaponItemArray: Item[], botRole: string): boolean
{
for (const mod of weaponItemArray)
{
const modDbTemplate = this.itemHelper.getItem(mod._tpl)[1];
if (!modDbTemplate._props.Slots?.length)
{
continue;
}
// Iterate over slots in db item, if required, check tpl in that slot matches the filter list
for (const modSlot of modDbTemplate._props.Slots)
{
// Ignore optional mods
if (!modSlot._required)
{
continue;
}
const allowedTpls = modSlot._props.filters[0].Filter;
const slotName = modSlot._name;
const weaponSlotItem = weaponItemArray.find((x) => x.parentId === mod._id && x.slotId === slotName);
if (!weaponSlotItem)
{
this.logger.warning(
this.localisationService.getText("bot-weapons_required_slot_missing_item", {
modSlot: modSlot._name,
modName: modDbTemplate._name,
slotId: mod.slotId,
botRole: botRole,
}),
);
return false;
}
if (!allowedTpls.includes(weaponSlotItem._tpl))
{
this.logger.warning(
this.localisationService.getText("bot-weapon_contains_invalid_item", {
modSlot: modSlot._name,
modName: modDbTemplate._name,
weaponTpl: weaponSlotItem._tpl,
}),
);
return false;
}
}
}
return true;
}
/**
* Generates extra magazines or bullets (if magazine is internal) and adds them to TacticalVest and Pockets.
* Additionally, adds extra bullets to SecuredContainer
* @param generatedWeaponResult object with properties for generated weapon (weapon mods pool / weapon template / ammo tpl)
* @param magWeights Magazine weights for count to add to inventory
* @param inventory Inventory to add magazines to
* @param botRole The bot type we're getting generating extra mags for
*/
public addExtraMagazinesToInventory(
generatedWeaponResult: GenerateWeaponResult,
magWeights: GenerationData,
inventory: PmcInventory,
botRole: string,
): void
{
const weaponAndMods = generatedWeaponResult.weapon;
const weaponTemplate = generatedWeaponResult.weaponTemplate;
const magazineTpl = this.getMagazineTplFromWeaponTemplate(weaponAndMods, weaponTemplate, botRole);
const magTemplate = this.itemHelper.getItem(magazineTpl)[1];
if (!magTemplate)
{
this.logger.error(this.localisationService.getText("bot-unable_to_find_magazine_item", magazineTpl));
return;
}
const ammoTemplate = this.itemHelper.getItem(generatedWeaponResult.chosenAmmoTpl)[1];
if (!ammoTemplate)
{
this.logger.error(
this.localisationService.getText("bot-unable_to_find_ammo_item", generatedWeaponResult.chosenAmmoTpl),
);
return;
}
// Has an UBGL
if (generatedWeaponResult.chosenUbglAmmoTpl)
{
this.addUbglGrenadesToBotInventory(weaponAndMods, generatedWeaponResult, inventory);
}
const inventoryMagGenModel = new InventoryMagGen(
magWeights,
magTemplate,
weaponTemplate,
ammoTemplate,
inventory,
);
this.inventoryMagGenComponents.find((v) => v.canHandleInventoryMagGen(inventoryMagGenModel)).process(
inventoryMagGenModel,
);
// Add x stacks of bullets to SecuredContainer (bots use a magic mag packing skill to reload instantly)
this.addAmmoToSecureContainer(
this.botConfig.secureContainerAmmoStackCount,
generatedWeaponResult.chosenAmmoTpl,
ammoTemplate._props.StackMaxSize,
inventory,
);
}
/**
* Add Grendaes for UBGL to bots vest and secure container
* @param weaponMods Weapon array with mods
* @param generatedWeaponResult result of weapon generation
* @param inventory bot inventory to add grenades to
*/
protected addUbglGrenadesToBotInventory(
weaponMods: Item[],
generatedWeaponResult: GenerateWeaponResult,
inventory: PmcInventory,
): void
{
// Find ubgl mod item + get details of it from db
const ubglMod = weaponMods.find((x) => x.slotId === "mod_launcher");
const ubglDbTemplate = this.itemHelper.getItem(ubglMod._tpl)[1];
// Define min/max of how many grenades bot will have
const ubglMinMax: GenerationData = {
// eslint-disable-next-line @typescript-eslint/naming-convention
weights: { "1": 1, "2": 1 },
whitelist: [],
};
// get ammo template from db
const ubglAmmoDbTemplate = this.itemHelper.getItem(generatedWeaponResult.chosenUbglAmmoTpl)[1];
// Add greandes to bot inventory
const ubglAmmoGenModel = new InventoryMagGen(
ubglMinMax,
ubglDbTemplate,
ubglDbTemplate,
ubglAmmoDbTemplate,
inventory,
);
this.inventoryMagGenComponents.find((v) => v.canHandleInventoryMagGen(ubglAmmoGenModel)).process(
ubglAmmoGenModel,
);
// Store extra grenades in secure container
this.addAmmoToSecureContainer(5, generatedWeaponResult.chosenUbglAmmoTpl, 20, inventory);
}
/**
* Add ammo to the secure container
* @param stackCount How many stacks of ammo to add
* @param ammoTpl Ammo type to add
* @param stackSize Size of the ammo stack to add
* @param inventory Player inventory
*/
protected addAmmoToSecureContainer(
stackCount: number,
ammoTpl: string,
stackSize: number,
inventory: PmcInventory,
): void
{
for (let i = 0; i < stackCount; i++)
{
const id = this.hashUtil.generate();
this.botWeaponGeneratorHelper.addItemWithChildrenToEquipmentSlot(
[EquipmentSlots.SECURED_CONTAINER],
id,
ammoTpl,
[{ _id: id, _tpl: ammoTpl, upd: { StackObjectsCount: stackSize } }],
inventory,
);
}
}
/**
* Get a weapons magazine tpl from a weapon template
* @param weaponMods mods from a weapon template
* @param weaponTemplate Weapon to get magazine tpl for
* @param botRole the bot type we are getting the magazine for
* @returns magazine tpl string
*/
protected getMagazineTplFromWeaponTemplate(
weaponMods: Item[],
weaponTemplate: ITemplateItem,
botRole: string,
): string
{
const magazine = weaponMods.find((m) => m.slotId === this.modMagazineSlotId);
if (!magazine)
{
// Edge case - magazineless chamber loaded weapons dont have magazines, e.g. mp18
// return default mag tpl
if (weaponTemplate._props.ReloadMode === "OnlyBarrel")
{
return this.botWeaponGeneratorHelper.getWeaponsDefaultMagazineTpl(weaponTemplate);
}
// log error if no magazine AND not a chamber loaded weapon (e.g. shotgun revolver)
if (!weaponTemplate._props.isChamberLoad)
{
// Shouldn't happen
this.logger.warning(
this.localisationService.getText("bot-weapon_missing_magazine_or_chamber", {weaponId: weaponTemplate._id, botRole: botRole}),
);
}
const defaultMagTplId = this.botWeaponGeneratorHelper.getWeaponsDefaultMagazineTpl(weaponTemplate);
this.logger.debug(
`[${botRole}] Unable to find magazine for weapon: ${weaponTemplate._id} ${weaponTemplate._name}, using mag template default: ${defaultMagTplId}.`,
);
return defaultMagTplId;
}
return magazine._tpl;
}
/**
* Finds and return a compatible ammo tpl based on the bots ammo weightings (x.json/inventory/equipment/ammo)
* @param ammo a list of ammo tpls the weapon can use
* @param weaponTemplate the weapon we want to pick ammo for
* @returns an ammo tpl that works with the desired gun
*/
protected getWeightedCompatibleAmmo(
ammo: Record<string, Record<string, number>>,
weaponTemplate: ITemplateItem,
): string
{
const desiredCaliber = this.getWeaponCaliber(weaponTemplate);
const compatibleCartridges = this.jsonUtil.clone(ammo[desiredCaliber]);
if (!compatibleCartridges || compatibleCartridges?.length === 0)
{
this.logger.debug(
this.localisationService.getText("bot-no_caliber_data_for_weapon_falling_back_to_default", {
weaponId: weaponTemplate._id,
weaponName: weaponTemplate._name,
defaultAmmo: weaponTemplate._props.defAmmo,
}),
);
// Immediately returns, as default ammo is guaranteed to be compatible
return weaponTemplate._props.defAmmo;
}
let chosenAmmoTpl: string;
while (!chosenAmmoTpl)
{
const possibleAmmo = this.weightedRandomHelper.getWeightedValue<string>(compatibleCartridges);
// Weapon has chamber but does not support cartridge
if (weaponTemplate._props.Chambers[0]
&& !weaponTemplate._props.Chambers[0]._props.filters[0].Filter.includes(possibleAmmo)
)
{
// Ran out of possible choices, use default ammo
if (Object.keys(compatibleCartridges).length === 0)
{
this.logger.debug(
this.localisationService.getText("bot-incompatible_ammo_for_weapon_falling_back_to_default", {
chosenAmmo: chosenAmmoTpl,
weaponId: weaponTemplate._id,
weaponName: weaponTemplate._name,
defaultAmmo: weaponTemplate._props.defAmmo,
}),
);
// Set ammo to default and exit
chosenAmmoTpl = weaponTemplate._props.defAmmo;
break;
}
// Not compatible, remove item from possible list and try again
delete compatibleCartridges[possibleAmmo];
}
else
{
// Compatible ammo found
chosenAmmoTpl = possibleAmmo;
break;
}
}
return chosenAmmoTpl;
}
/**
* Get a weapons compatible cartridge caliber
* @param weaponTemplate Weapon to look up caliber of
* @returns caliber as string
*/
protected getWeaponCaliber(weaponTemplate: ITemplateItem): string
{
if (weaponTemplate._props.Caliber)
{
return weaponTemplate._props.Caliber;
}
if (weaponTemplate._props.ammoCaliber)
{
// 9x18pmm has a typo, should be Caliber9x18PM
return weaponTemplate._props.ammoCaliber === "Caliber9x18PMM"
? "Caliber9x18PM"
: weaponTemplate._props.ammoCaliber;
}
if (weaponTemplate._props.LinkedWeapon)
{
const ammoInChamber = this.itemHelper.getItem(
weaponTemplate._props.Chambers[0]._props.filters[0].Filter[0],
);
if (!ammoInChamber[0])
{
return;
}
return ammoInChamber[1]._props.Caliber;
}
}
/**
* Fill existing magazines to full, while replacing their contents with specified ammo
* @param weaponMods Weapon with children
* @param magazine Magazine item
* @param cartridgeTpl Cartridge to insert into magazine
*/
protected fillExistingMagazines(weaponMods: Item[], magazine: Item, cartridgeTpl: string): void
{
const magazineTemplate = this.itemHelper.getItem(magazine._tpl)[1];
if (!magazineTemplate)
{
this.logger.error(this.localisationService.getText("bot-unable_to_find_magazine_item", magazine._tpl));
return;
}
// Magazine, usually
const parentItem = this.itemHelper.getItem(magazineTemplate._parent)[1];
// the revolver shotgun uses a magazine with chambers, not cartridges ("camora_xxx")
// Exchange of the camora ammo is not necessary we could also just check for stackSize > 0 here
// and remove the else
if (this.botWeaponGeneratorHelper.magazineIsCylinderRelated(parentItem._name))
{
this.fillCamorasWithAmmo(weaponMods, magazine._id, cartridgeTpl);
}
else
{
this.addOrUpdateMagazinesChildWithAmmo(weaponMods, magazine, cartridgeTpl, magazineTemplate);
}
}
/**
* Add desired ammo tpl as item to weaponmods array, placed as child to UBGL
* @param weaponMods Weapon with children
* @param ubglMod UBGL item
* @param ubglAmmoTpl Grenade ammo tpl
*/
protected fillUbgl(weaponMods: Item[], ubglMod: Item, ubglAmmoTpl: string): void
{
weaponMods.push({
_id: this.hashUtil.generate(),
_tpl: ubglAmmoTpl,
parentId: ubglMod._id,
slotId: "patron_in_weapon",
upd: { StackObjectsCount: 1 },
});
}
/**
* Add cartridge item to weapon Item array, if it already exists, update
* @param weaponWithMods Weapon items array to amend
* @param magazine magazine item details we're adding cartridges to
* @param chosenAmmoTpl cartridge to put into the magazine
* @param newStackSize how many cartridges should go into the magazine
* @param magazineTemplate magazines db template
*/
protected addOrUpdateMagazinesChildWithAmmo(
weaponWithMods: Item[],
magazine: Item,
chosenAmmoTpl: string,
magazineTemplate: ITemplateItem,
): void
{
const magazineCartridgeChildItem = weaponWithMods.find((m) =>
m.parentId === magazine._id && m.slotId === "cartridges"
);
if (magazineCartridgeChildItem)
{
// Delete the existing cartridge object and create fresh below
weaponWithMods.splice(weaponWithMods.indexOf(magazineCartridgeChildItem), 1);
}
// Create array with just magazine
const magazineWithCartridges = [magazine];
// Add full cartridge child items to above array
this.itemHelper.fillMagazineWithCartridge(magazineWithCartridges, magazineTemplate, chosenAmmoTpl, 1);
// Replace existing magazine with above array of mag + cartridge stacks
weaponWithMods.splice(weaponWithMods.indexOf(magazine), 1, ...magazineWithCartridges);
}
/**
* Fill each Camora with a bullet
* @param weaponMods Weapon mods to find and update camora mod(s) from
* @param magazineId magazine id to find and add to
* @param ammoTpl ammo template id to hydate with
*/
protected fillCamorasWithAmmo(weaponMods: Item[], magazineId: string, ammoTpl: string): void
{
// for CylinderMagazine we exchange the ammo in the "camoras".
// This might not be necessary since we already filled the camoras with a random whitelisted and compatible ammo type,
// but I'm not sure whether this is also used elsewhere
const camoras = weaponMods.filter((x) => x.parentId === magazineId && x.slotId.startsWith("camora"));
for (const camora of camoras)
{
camora._tpl = ammoTpl;
if (camora.upd)
{
camora.upd.StackObjectsCount = 1;
}
else
{
camora.upd = { StackObjectsCount: 1 };
}
}
}
}