diff --git a/project/assets/configs/bot.json b/project/assets/configs/bot.json index d8c5b359..c09df9ce 100644 --- a/project/assets/configs/bot.json +++ b/project/assets/configs/bot.json @@ -891,6 +891,7 @@ "mod_foregrip": 10, "mod_handguard": 10, "mod_launcher": 0, + "mod_reciever": 15, "mod_magazine": 10, "mod_mount": 15, "mod_mount_000": 10, @@ -1120,7 +1121,7 @@ "TacticalVest": { "6034d0230ca681766b6a0fb5": 120, "6034cf5fffd42c541047f72e": 150, - "597867e986f7741b265c6bd3": 120 + "59e7635f86f7742cbf2c1095": 120 }, "Backpack": { }, @@ -1131,11 +1132,9 @@ "edit": { "ArmorVest": { "5648a7494bdc2d9d488b4583": 290, - "5c0e446786f7742013381639": 210, "5b44d22286f774172b0c9de8": 280, "5c0e5bab86f77461f55ed1f3": 280, "5c0e5edb86f77461f55ed1f7": 270, - "5ab8e4ed86f7742d8e50c7fa": 260, "5df8a2ca86f7740bfe6df777": 185, "64be79c487d1510151095552": 270, @@ -1144,7 +1143,6 @@ "5e4abb5086f77406975c9342": 4, "6038b4ca92ec1c3103795a0d": 4, "6038b4b292ec1c3103795a0b": 4, - "628dc750b910320f4c27a732": 4, "5b44cd8b86f774503d30cba2": 4, "5ca21c6986f77479963115a7": 4, "5b44d0de86f774503d30cba8": 4, @@ -1154,6 +1152,7 @@ "545cdb794bdc2d3a198b456a": 4 }, "TacticalVest": { + "5c0e446786f7742013381639": 210, "5e4abfed86f77406a2713cf7": 200, "5d5d8ca986f7742798716522": 200, "5d5d646386f7742797261fd9": 230, @@ -1167,6 +1166,7 @@ "60a3c68c37ea821725773ef5": 1, "5e4ac41886f77406a511c9a8": 1, "5b44cad286f77402a54ae7e5": 1, + "628dc750b910320f4c27a732": 1, "5c0e3eb886f7742015526062": 230, "572b7adb24597762ae139821": 250, "544a5caa4bdc2d1a388b4568": 50, diff --git a/project/assets/database/bots/types/bear.json b/project/assets/database/bots/types/bear.json index 866542d7..57f8e96c 100644 --- a/project/assets/database/bots/types/bear.json +++ b/project/assets/database/bots/types/bear.json @@ -5615,7 +5615,8 @@ "5926dad986f7741f82604363" ], "mod_muzzle": [ - "5926e16e86f7742f5a0f7ecb" + "5926e16e86f7742f5a0f7ecb", + "5c0000c00db834001a6697fc" ], "mod_sight_rear": [ "5926d2be86f774134d668e4e" diff --git a/project/assets/database/bots/types/usec.json b/project/assets/database/bots/types/usec.json index aa0d3c87..a7b9035a 100644 --- a/project/assets/database/bots/types/usec.json +++ b/project/assets/database/bots/types/usec.json @@ -5612,7 +5612,8 @@ "5926dad986f7741f82604363" ], "mod_muzzle": [ - "5926e16e86f7742f5a0f7ecb" + "5926e16e86f7742f5a0f7ecb", + "5c0000c00db834001a6697fc" ], "mod_sight_rear": [ "5926d2be86f774134d668e4e" diff --git a/project/src/generators/BotEquipmentModGenerator.ts b/project/src/generators/BotEquipmentModGenerator.ts index 37b1e317..a0d93815 100644 --- a/project/src/generators/BotEquipmentModGenerator.ts +++ b/project/src/generators/BotEquipmentModGenerator.ts @@ -13,6 +13,7 @@ import { Item } from "@spt-aki/models/eft/common/tables/IItem"; import { ITemplateItem, Slot } from "@spt-aki/models/eft/common/tables/ITemplateItem"; import { BaseClasses } from "@spt-aki/models/enums/BaseClasses"; import { ConfigTypes } from "@spt-aki/models/enums/ConfigTypes"; +import { ModSpawn } from "@spt-aki/models/enums/ModSpawn"; import { EquipmentFilterDetails, IBotConfig } from "@spt-aki/models/spt/config/IBotConfig"; import { ILogger } from "@spt-aki/models/spt/utils/ILogger"; import { ConfigServer } from "@spt-aki/servers/ConfigServer"; @@ -97,8 +98,8 @@ export class BotEquipmentModGenerator ); continue; } - - if (!(this.shouldModBeSpawned(itemSlotTemplate, modSlotName.toLowerCase(), settings.spawnChances.equipmentMods) || forceSpawn)) + const modSpawnResult = this.shouldModBeSpawned(itemSlotTemplate, modSlotName.toLowerCase(), settings.spawnChances.equipmentMods); + if (modSpawnResult === ModSpawn.SKIP && !forceSpawn) { continue; } @@ -151,7 +152,7 @@ export class BotEquipmentModGenerator // Compatible item not found but slot REQUIRES item, get random item from db if (!found && itemSlotTemplate._required) { - modTpl = this.getModTplFromItemDb(modTpl, itemSlotTemplate, modSlotName, equipment); + modTpl = this.getRandomModTplFromItemDb(modTpl, itemSlotTemplate, modSlotName, equipment); found = !!modTpl; } @@ -251,7 +252,8 @@ export class BotEquipmentModGenerator this.logger.debug(`Plate filter was too restrictive for armor: ${armorItem._id}, unable to find plates of level: ${chosenArmorPlateLevel}. Using mod items default plate`); const relatedItemDbModSlot = armorItem._props.Slots.find(slot => slot._name.toLowerCase() === modSlot); - if (!relatedItemDbModSlot._props.filters[0].Plate) + const defaultPlate = relatedItemDbModSlot._props.filters[0].Plate + if (!defaultPlate) { // No relevant plate found after filtering AND no default plate @@ -275,7 +277,7 @@ export class BotEquipmentModGenerator } result.result = Result.SUCCESS; - result.plateModTpls = [relatedItemDbModSlot._props.filters[0].Plate]; + result.plateModTpls = [defaultPlate]; return result; } @@ -292,7 +294,7 @@ export class BotEquipmentModGenerator * @param sessionId session id * @param weapon Weapon to add mods to * @param modPool Pool of compatible mods to attach to weapon - * @param weaponParentId parentId of weapon + * @param weaponId parentId of weapon * @param parentTemplate Weapon which mods will be generated on * @param modSpawnChances Mod spawn chances * @param ammoTpl Ammo tpl to use when generating magazines/cartridges @@ -306,7 +308,7 @@ export class BotEquipmentModGenerator sessionId: string, weapon: Item[], modPool: Mods, - weaponParentId: string, + weaponId: string, parentTemplate: ITemplateItem, modSpawnChances: ModsChances, ammoTpl: string, @@ -366,7 +368,8 @@ export class BotEquipmentModGenerator } // Check spawn chance of mod - if (!this.shouldModBeSpawned(modsParentSlot, modSlot, modSpawnChances)) + const modSpawnResult = this.shouldModBeSpawned(modsParentSlot, modSlot, modSpawnChances); + if (modSpawnResult === ModSpawn.SKIP) { continue; } @@ -381,6 +384,7 @@ export class BotEquipmentModGenerator weapon, ammoTpl, parentTemplate, + modSpawnResult ); // Compatible mod not found @@ -465,7 +469,7 @@ export class BotEquipmentModGenerator const modId = this.hashUtil.generate(); weapon.push( - this.createModItem(modId, modToAddTemplate._id, weaponParentId, modSlot, modToAddTemplate, botRole), + this.createModItem(modId, modToAddTemplate._id, weaponId, modSlot, modToAddTemplate, botRole), ); // I first thought we could use the recursive generateModsForItems as previously for cylinder magazines. @@ -684,24 +688,28 @@ export class BotEquipmentModGenerator /** * Randomly choose if a mod should be spawned, 100% for required mods OR mod is ammo slot - * never return true for an item that has 0% spawn chance * @param itemSlot slot the item sits in * @param modSlot slot the mod sits in * @param modSpawnChances Chances for various mod spawns - * @returns boolean true if it should spawn + * @returns ModSpawn.SPAWN when mod should be spawned, ModSpawn.DEFAULT_MOD when default mod should spawn, ModSpawn.SKIP when mod is skipped */ - protected shouldModBeSpawned(itemSlot: Slot, modSlot: string, modSpawnChances: ModsChances): boolean + protected shouldModBeSpawned(itemSlot: Slot, modSlot: string, modSpawnChances: ModsChances): ModSpawn { - const modSpawnChance = itemSlot._required || this.getAmmoContainers().includes(modSlot) // Required OR it is ammo - ? 100 - : modSpawnChances[modSlot]; - - if (modSpawnChance === 100) + const slotRequired = itemSlot._required; + if (this.getAmmoContainers().includes(modSlot)) { - return true; + return ModSpawn.SPAWN + } + const spawnMod = this.probabilityHelper.rollChance(modSpawnChances[modSlot]); + if (!spawnMod && slotRequired) + { + // Mod is required but spawn chance roll failed, choose default mod spawn for slot + return ModSpawn.DEFAULT_MOD; } - return this.probabilityHelper.rollChance(modSpawnChance); + return spawnMod + ? ModSpawn.SPAWN + : ModSpawn.SKIP; } /** @@ -713,7 +721,7 @@ export class BotEquipmentModGenerator * @param weapon array with only weapon tpl in it, ready for mods to be added * @param ammoTpl ammo tpl to use if slot requires a cartridge to be added (e.g. mod_magazine) * @param parentTemplate Parent item the mod will go into - * @returns ITemplateItem + * @returns itemHelper.getItem() result */ protected chooseModToPutIntoSlot( modSlot: string, @@ -724,11 +732,15 @@ export class BotEquipmentModGenerator weapon: Item[], ammoTpl: string, parentTemplate: ITemplateItem, + modSpawnResult: ModSpawn ): [boolean, ITemplateItem] { + /** Chosen mod tpl */ let modTpl: string; let found = false; + /** Slot mod will fill */ const parentSlot = parentTemplate._props.Slots.find((i) => i._name === modSlot); + const weaponTemplate = this.itemHelper.getItem(weapon[0]._tpl)[1]; // It's ammo, use predefined ammo parameter if (this.getAmmoContainers().includes(modSlot) && modSlot !== "mod_magazine") @@ -737,15 +749,11 @@ export class BotEquipmentModGenerator } else { - // Get randomised pool of mods if this is a slot we randomise - if (isRandomisableSlot) - { - itemModPool[modSlot] = this.getDynamicModPool(parentTemplate._id, modSlot, botEquipBlacklist); - } - // Ensure there's a pool of mods to pick from - if (!(itemModPool[modSlot] || parentSlot._required)) + let modPool = this.getModPoolForSlot(itemModPool, modSpawnResult, parentTemplate, weaponTemplate, modSlot, botEquipBlacklist, isRandomisableSlot); + if (!(modPool || parentSlot._required)) { + // Nothing in mod pool + item not required this.logger.debug( `Mod pool for slot: ${modSlot} on item: ${parentTemplate._name} was empty, skipping mod`, ); @@ -756,18 +764,18 @@ export class BotEquipmentModGenerator if (modSlot.includes("mod_scope") && botWeaponSightWhitelist) { // scope pool has more than one scope - if (itemModPool[modSlot].length > 1) + if (modPool.length > 1) { - itemModPool[modSlot] = this.filterSightsByWeaponType( + modPool = this.filterSightsByWeaponType( weapon[0], - itemModPool[modSlot], + modPool, botWeaponSightWhitelist, ); } } - + // Pick random mod and check it's compatible - const exhaustableModPool = new ExhaustableArray(itemModPool[modSlot], this.randomUtil, this.jsonUtil); + const exhaustableModPool = new ExhaustableArray(modPool, this.randomUtil, this.jsonUtil); let modCompatibilityResult: { incompatible: boolean; reason: string; } = { incompatible: false, reason: "", @@ -799,7 +807,7 @@ export class BotEquipmentModGenerator // Get random mod to attach from items db for required slots if none found above if (!found && parentSlot !== undefined && parentSlot._required) { - modTpl = this.getModTplFromItemDb(modTpl, parentSlot, modSlot, weapon); + modTpl = this.getRandomModTplFromItemDb(modTpl, parentSlot, modSlot, weapon); found = !!modTpl; } @@ -826,6 +834,54 @@ export class BotEquipmentModGenerator return this.itemHelper.getItem(modTpl); } + /** + * Filter mod pool down based on various criteria: + * Is slot flagged as randomisable + * Is slot required + * Is slot flagged as default mod only + * @param itemModPool Existing pool of mods to choose + * @param modSpawnResult outcome of random roll to select if mod should be added + * @param parentTemplate Mods parent + * @param weaponTemplate Mods root parent (weapon/equipment) + * @param modSlot name of mod slot to choose for + * @param botEquipBlacklist + * @param isRandomisableSlot is flagged as a randomisable slot + * @returns + */ + protected getModPoolForSlot( + itemModPool: Record, + modSpawnResult: ModSpawn, + parentTemplate: ITemplateItem, + weaponTemplate: ITemplateItem, + modSlot: string, + botEquipBlacklist: EquipmentFilterDetails, + isRandomisableSlot: boolean): string[] + { + // Mod is flagged as being default only, try and find it in globals + if (modSpawnResult === ModSpawn.DEFAULT_MOD) + { + const defaultWeaponPreset = this.presetHelper.getDefaultPreset(weaponTemplate._id) + const matchingMod = defaultWeaponPreset._items.find(item => item?.slotId?.toLowerCase() === modSlot.toLowerCase()); + if (matchingMod) + { + return [matchingMod._tpl]; + } + + this.logger.warning(`No default: ${modSlot} mod found on: ${weaponTemplate._name}`); + + // Couuldnt find default in globals, use existing mod pool data + return itemModPool[modSlot]; + } + + if (isRandomisableSlot) + { + return this.getDynamicModPool(parentTemplate._id, modSlot, botEquipBlacklist); + } + + // Required mod is not default or randomisable, use existing pool + return itemModPool[modSlot]; + } + /** * Temp fix to prevent certain combinations of weapons with mods that are known to be incompatible * @param weapon Weapon @@ -888,7 +944,7 @@ export class BotEquipmentModGenerator * @param items items to ensure picked mod is compatible with * @returns item tpl */ - protected getModTplFromItemDb(modTpl: string, parentSlot: Slot, modSlot: string, items: Item[]): string + protected getRandomModTplFromItemDb(modTpl: string, parentSlot: Slot, modSlot: string, items: Item[]): string { // Find compatible mods and make an array of them const allowedItems = parentSlot._props.filters[0].Filter; diff --git a/project/src/generators/BotWeaponGenerator.ts b/project/src/generators/BotWeaponGenerator.ts index 02fd855c..427b98d3 100644 --- a/project/src/generators/BotWeaponGenerator.ts +++ b/project/src/generators/BotWeaponGenerator.ts @@ -175,7 +175,7 @@ export class BotWeaponGenerator sessionId, weaponWithModsArray, modPool, - weaponWithModsArray[0]._id, + weaponWithModsArray[0]._id, // Weapon root id weaponItemTemplate, modChances, ammoTpl, diff --git a/project/src/models/enums/ModSpawn.ts b/project/src/models/enums/ModSpawn.ts new file mode 100644 index 00000000..170229e0 --- /dev/null +++ b/project/src/models/enums/ModSpawn.ts @@ -0,0 +1,5 @@ +export enum ModSpawn { + DEFAULT_MOD, + SPAWN, + SKIP +} \ No newline at end of file