diff --git a/project/src/generators/BotWeaponGenerator.ts b/project/src/generators/BotWeaponGenerator.ts index 2ba2e561..c0d9383c 100644 --- a/project/src/generators/BotWeaponGenerator.ts +++ b/project/src/generators/BotWeaponGenerator.ts @@ -105,8 +105,8 @@ export class BotWeaponGenerator { /** * 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 weaponTpl Weapon tpl to generate (use pickWeightedWeaponTplFromPool()) + * @param slotName 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 @@ -117,7 +117,7 @@ export class BotWeaponGenerator { public generateWeaponByTpl( sessionId: string, weaponTpl: string, - equipmentSlot: string, + slotName: string, botTemplateInventory: Inventory, weaponParentId: string, modChances: ModsChances, @@ -130,7 +130,7 @@ export class BotWeaponGenerator { if (!weaponItemTemplate) { this.logger.error(this.localisationService.getText("bot-missing_item_template", weaponTpl)); - this.logger.error(`WeaponSlot -> ${equipmentSlot}`); + this.logger.error(`WeaponSlot -> ${slotName}`); return; } @@ -147,20 +147,23 @@ export class BotWeaponGenerator { let weaponWithModsArray = this.constructWeaponBaseArray( weaponTpl, weaponParentId, - equipmentSlot, + slotName, 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 buff to weapon root + this.repairService.addBuff(this.repairConfig.repairKit.weapon, weaponWithModsArray[0]); } // Add mods to weapon base if (Object.keys(modPool).includes(weaponTpl)) { + // Role to treat bot as e.g. pmc/scav/boss const botEquipmentRole = this.botGeneratorHelper.getBotEquipmentRole(botRole); + + // Different limits if bot is boss vs scav const modLimits = this.botWeaponModLimitService.getWeaponModLimits(botEquipmentRole); const generateWeaponModsRequest: IGenerateWeaponRequest = { @@ -185,7 +188,7 @@ export class BotWeaponGenerator { // Weapon is bad, fall back to weapons preset weaponWithModsArray = this.getPresetWeaponMods( weaponTpl, - equipmentSlot, + slotName, weaponParentId, weaponItemTemplate, botRole, @@ -535,18 +538,18 @@ export class BotWeaponGenerator { /** * 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 + * @param cartridgePool Dict of all cartridges keyed by type e.g. Caliber556x45NATO + * @param weaponTemplate Weapon details from db we want to pick ammo for + * @returns Ammo tpl that works with the desired gun */ protected getWeightedCompatibleAmmo( - ammo: Record>, + cartridgePool: Record>, weaponTemplate: ITemplateItem, ): string { const desiredCaliber = this.getWeaponCaliber(weaponTemplate); - const compatibleCartridges = this.cloner.clone(ammo[desiredCaliber]); - if (!compatibleCartridges || compatibleCartridges?.length === 0) { + const cartridgePoolForWeapon = cartridgePool[desiredCaliber]; + if (!cartridgePoolForWeapon || cartridgePoolForWeapon?.length === 0) { this.logger.debug( this.localisationService.getText("bot-no_caliber_data_for_weapon_falling_back_to_default", { weaponId: weaponTemplate._id, @@ -555,45 +558,53 @@ export class BotWeaponGenerator { }), ); - // Immediately returns, as default ammo is guaranteed to be compatible + // Immediately returns, default ammo is guaranteed to be compatible return weaponTemplate._props.defAmmo; } - let chosenAmmoTpl: string; - while (!chosenAmmoTpl) { - const possibleAmmo = this.weightedRandomHelper.getWeightedValue(compatibleCartridges); + // Get cartridges the weapons first chamber allow + const compatibleCartridgesInTemplate = this.getCompatibleCartridgesFromWeaponTemplate(weaponTemplate); + if (!compatibleCartridgesInTemplate) { + // No chamber data found in weapon, send default + return weaponTemplate._props.defAmmo; + } - // 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, - }), - ); + // Inner join the weapons allowed + passed in cartridge pool to get compatible cartridges + const compatibleCartridges = Object.keys(cartridgePoolForWeapon) + .filter((cartridge) => compatibleCartridgesInTemplate.includes(cartridge)) + .reduce((acc, key) => ({ ...acc, [key]: cartridgePoolForWeapon[key] }), {}); - // Set ammo to default and exit - chosenAmmoTpl = weaponTemplate._props.defAmmo; - break; - } + if (!compatibleCartridges) { + // No compatible cartridges, use default + return weaponTemplate._props.defAmmo; + } - // Not compatible, remove item from possible list and try again - delete compatibleCartridges[possibleAmmo]; - } else { - // Compatible ammo found - chosenAmmoTpl = possibleAmmo; - break; + return this.weightedRandomHelper.getWeightedValue(compatibleCartridges); + } + + /** + * Get the cartridge ids from a weapon template that work with the weapon + * @param weaponTemplate Weapon db template to get cartridges for + * @returns Array of cartridge tpls + */ + protected getCompatibleCartridgesFromWeaponTemplate(weaponTemplate: ITemplateItem): string[] { + let cartridges = weaponTemplate._props.Chambers[0]?._props?.filters[0]?.Filter; + if (!cartridges) { + // Fallback to the magazine if possible, e.g. for revolvers + // Grab the magazines template + const firstMagazine = weaponTemplate._props.Slots.find((slot) => slot._name === "mod_magazine"); + const magazineTemplate = this.itemHelper.getItem(firstMagazine._props.filters[0].Filter[0]); + + // Get the first slots array of cartridges + cartridges = magazineTemplate[1]._props.Slots[0]?._props.filters[0].Filter; + if (!cartridges) { + // Normal magazines + // None found, try the cartridges array + cartridges = magazineTemplate[1]._props.Cartridges[0]?._props.filters[0].Filter; } } - return chosenAmmoTpl; + return cartridges; } /**