diff --git a/project/assets/configs/bot.json b/project/assets/configs/bot.json index 24667bdf..71750748 100644 --- a/project/assets/configs/bot.json +++ b/project/assets/configs/bot.json @@ -2655,5 +2655,6 @@ "250": 1 } } - } + }, + "lowProfileGasBlockTpls": ["61702f1b67085e45ef140b26", "5dfa3d45dfc58d14537c20b0", "5bb20dcad4351e3bac1212da", "56eabcd4d2720b66698b4574", "6065dc8a132d4d12c81fd8e3", "55d4af3a4bdc2d972f8b456f"] } \ No newline at end of file diff --git a/project/src/generators/BotEquipmentModGenerator.ts b/project/src/generators/BotEquipmentModGenerator.ts index 0bff0a10..6dfd0f21 100644 --- a/project/src/generators/BotEquipmentModGenerator.ts +++ b/project/src/generators/BotEquipmentModGenerator.ts @@ -15,6 +15,8 @@ import { BaseClasses } from "@spt/models/enums/BaseClasses"; import { ConfigTypes } from "@spt/models/enums/ConfigTypes"; import { ModSpawn } from "@spt/models/enums/ModSpawn"; import { IChooseRandomCompatibleModResult } from "@spt/models/spt/bots/IChooseRandomCompatibleModResult"; +import { IGenerateWeaponRequest } from "@spt/models/spt/bots/IGenerateWeaponRequest"; +import { IModToSpawnRequest } from "@spt/models/spt/bots/IModToSpawnRequest"; import { EquipmentFilterDetails, EquipmentFilters, IBotConfig } from "@spt/models/spt/config/IBotConfig"; import { ExhaustableArray } from "@spt/models/spt/server/ExhaustableArray"; import { ILogger } from "@spt/models/spt/utils/ILogger"; @@ -22,7 +24,7 @@ import { ConfigServer } from "@spt/servers/ConfigServer"; import { DatabaseServer } from "@spt/servers/DatabaseServer"; import { BotEquipmentFilterService } from "@spt/services/BotEquipmentFilterService"; import { BotEquipmentModPoolService } from "@spt/services/BotEquipmentModPoolService"; -import { BotModLimits, BotWeaponModLimitService } from "@spt/services/BotWeaponModLimitService"; +import { BotWeaponModLimitService } from "@spt/services/BotWeaponModLimitService"; import { ItemFilterService } from "@spt/services/ItemFilterService"; import { LocalisationService } from "@spt/services/LocalisationService"; import { ICloner } from "@spt/utils/cloners/ICloner"; @@ -313,79 +315,60 @@ export class BotEquipmentModGenerator /** * Add mods to a weapon using the provided mod pool - * @param sessionId session id - * @param weapon Weapon to add mods to - * @param modPool Pool of compatible mods to attach to 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 - * @param botRole Role of bot weapon is generated for - * @param botLevel Level of the bot weapon is being generated for - * @param modLimits limits placed on certain mod types per gun - * @param botEquipmentRole role of bot when accessing bot.json equipment config settings + * @param sessionId Session id + * @param request Data used to generate the weapon * @returns Weapon + mods array */ - public generateModsForWeapon( - sessionId: string, - weapon: Item[], - modPool: Mods, - weaponId: string, - parentTemplate: ITemplateItem, - modSpawnChances: ModsChances, - ammoTpl: string, - botRole: string, - botLevel: number, - modLimits: BotModLimits, - botEquipmentRole: string, - ): Item[] + public generateModsForWeapon(sessionId: string, request: IGenerateWeaponRequest): Item[] { const pmcProfile = this.profileHelper.getPmcProfile(sessionId); // Get pool of mods that fit weapon - const compatibleModsPool = modPool[parentTemplate._id]; + const compatibleModsPool = request.modPool[request.parentTemplate._id]; if ( !( - parentTemplate._props.Slots.length - || parentTemplate._props.Cartridges?.length - || parentTemplate._props.Chambers?.length + request.parentTemplate._props.Slots.length + || request.parentTemplate._props.Cartridges?.length + || request.parentTemplate._props.Chambers?.length ) ) { this.logger.error( this.localisationService.getText("bot-unable_to_add_mods_to_weapon_missing_ammo_slot", { - weaponName: parentTemplate._name, - weaponId: parentTemplate._id, - botRole: botRole, + weaponName: request.parentTemplate._name, + weaponId: request.parentTemplate._id, + botRole: request.botData.role, }), ); - return weapon; + return request.weapon; } - const botEquipConfig = this.botConfig.equipment[botEquipmentRole]; + const botEquipConfig = this.botConfig.equipment[request.botData.equipmentRole]; const botEquipBlacklist = this.botEquipmentFilterService.getBotEquipmentBlacklist( - botEquipmentRole, + request.botData.equipmentRole, pmcProfile.Info.Level, ); - const botWeaponSightWhitelist = this.botEquipmentFilterService.getBotWeaponSightWhitelist(botEquipmentRole); - const randomisationSettings = this.botHelper.getBotRandomizationDetails(botLevel, botEquipConfig); + const botWeaponSightWhitelist + = this.botEquipmentFilterService.getBotWeaponSightWhitelist(request.botData.equipmentRole); + const randomisationSettings + = this.botHelper.getBotRandomizationDetails(request.botData.level, botEquipConfig); // Iterate over mod pool and choose mods to attach const sortedModKeys = this.sortModKeys(Object.keys(compatibleModsPool)); for (const modSlot of sortedModKeys) { // Check weapon has slot for mod to fit in - const modsParentSlot = this.getModItemSlotFromDb(modSlot, parentTemplate); + const modsParentSlot = this.getModItemSlotFromDb(modSlot, request.parentTemplate); if (!modsParentSlot) { this.logger.error( this.localisationService.getText("bot-weapon_missing_mod_slot", { modSlot: modSlot, - weaponId: parentTemplate._id, - weaponName: parentTemplate._name, - botRole: botRole, + weaponId: request.parentTemplate._id, + weaponName: request.parentTemplate._name, + botRole: request.botData.role, }), ); @@ -393,24 +376,30 @@ export class BotEquipmentModGenerator } // Check spawn chance of mod - const modSpawnResult = this.shouldModBeSpawned(modsParentSlot, modSlot, modSpawnChances, botEquipConfig); + const modSpawnResult = this.shouldModBeSpawned( + modsParentSlot, + modSlot, + request.modSpawnChances, + botEquipConfig); if (modSpawnResult === ModSpawn.SKIP) { continue; } const isRandomisableSlot = randomisationSettings?.randomisedWeaponModSlots?.includes(modSlot) ?? false; - const modToAdd = this.chooseModToPutIntoSlot( - modSlot, - isRandomisableSlot, - botWeaponSightWhitelist, - botEquipBlacklist, - compatibleModsPool, - weapon, - ammoTpl, - parentTemplate, - modSpawnResult, - ); + const modToSpawnRequest: IModToSpawnRequest = { + modSlot: modSlot, + isRandomisableSlot: isRandomisableSlot, + botWeaponSightWhitelist: botWeaponSightWhitelist, + botEquipBlacklist: botEquipBlacklist, + itemModPool: compatibleModsPool, + weapon: request.weapon, + ammoTpl: request.ammoTpl, + parentTemplate: request.parentTemplate, + modSpawnResult: modSpawnResult, + weaponStats: request.weaponStats, + }; + const modToAdd = this.chooseModToPutIntoSlot(modToSpawnRequest); // Compatible mod not found if (!modToAdd || typeof modToAdd === "undefined") @@ -418,7 +407,7 @@ export class BotEquipmentModGenerator continue; } - if (!this.isModValidForSlot(modToAdd, modsParentSlot, modSlot, parentTemplate, botRole)) + if (!this.isModValidForSlot(modToAdd, modsParentSlot, modSlot, request.parentTemplate, request.botData.role)) { continue; } @@ -427,11 +416,11 @@ export class BotEquipmentModGenerator // Skip adding mod to weapon if type limit reached if ( this.botWeaponModLimitService.weaponModHasReachedLimit( - botEquipmentRole, + request.botData.equipmentRole, modToAddTemplate, - modLimits, - parentTemplate, - weapon, + request.modLimits, + request.parentTemplate, + request.weapon, ) ) { @@ -443,13 +432,13 @@ export class BotEquipmentModGenerator { // mod_mount was picked to be added to weapon, force scope chance to ensure its filled const scopeSlots = ["mod_scope", "mod_scope_000", "mod_scope_001", "mod_scope_002", "mod_scope_003"]; - this.adjustSlotSpawnChances(modSpawnChances, scopeSlots, 100); + this.adjustSlotSpawnChances(request.modSpawnChances, scopeSlots, 100); // Hydrate pool of mods that fit into mount as its a randomisable slot if (isRandomisableSlot) { // Add scope mods to modPool dictionary to ensure the mount has a scope in the pool to pick - this.addCompatibleModsForProvidedMod("mod_scope", modToAddTemplate, modPool, botEquipBlacklist); + this.addCompatibleModsForProvidedMod("mod_scope", modToAddTemplate, request.modPool, botEquipBlacklist); } } @@ -458,14 +447,14 @@ export class BotEquipmentModGenerator { const muzzleSlots = ["mod_muzzle", "mod_muzzle_000", "mod_muzzle_001"]; // Make chance of muzzle devices 95%, nearly certain but not guaranteed - this.adjustSlotSpawnChances(modSpawnChances, muzzleSlots, 95); + this.adjustSlotSpawnChances(request.modSpawnChances, muzzleSlots, 95); } // If front/rear sight are to be added, set opposite to 100% chance if (this.modIsFrontOrRearSight(modSlot, modToAddTemplate._id)) { - modSpawnChances.mod_sight_front = 100; - modSpawnChances.mod_sight_rear = 100; + request.modSpawnChances.mod_sight_front = 100; + request.modSpawnChances.mod_sight_rear = 100; } // Handguard mod can take a sub handguard mod + weapon has no UBGL (takes same slot) @@ -473,11 +462,11 @@ export class BotEquipmentModGenerator if ( modSlot === "mod_handguard" && modToAddTemplate._props.Slots.find((slot) => slot._name === "mod_handguard") - && !weapon.find((item) => item.slotId === "mod_launcher") + && !request.weapon.find((item) => item.slotId === "mod_launcher") ) { // Needed for handguards with lower - modSpawnChances.mod_handguard = 100; + request.modSpawnChances.mod_handguard = 100; } // If stock mod can take a sub stock mod, force spawn chance to be 100% to ensure sub-stock gets added @@ -491,11 +480,35 @@ export class BotEquipmentModGenerator { // Stock mod can take additional stocks, could be a locking device, force 100% chance const stockSlots = ["mod_stock", "mod_stock_000", "mod_stock_akms"]; - this.adjustSlotSpawnChances(modSpawnChances, stockSlots, 100); + this.adjustSlotSpawnChances(request.modSpawnChances, stockSlots, 100); + } + + // Gather stats on mods being added to weapon + if (this.itemHelper.isOfBaseclass(modToAddTemplate._id, BaseClasses.IRON_SIGHT)) + { + if (modSlot === "mod_sight_front") + { + request.weaponStats.hasFrontIronSight = true; + } + else if (modSlot === "mod_sight_rear") + { + request.weaponStats.hasRearIronSight = true; + } + } + else if (!request.weaponStats.hasOptic + && this.itemHelper.isOfBaseclass(modToAddTemplate._id, BaseClasses.SIGHTS)) + { + request.weaponStats.hasOptic = true; } const modId = this.hashUtil.generate(); - weapon.push(this.createModItem(modId, modToAddTemplate._id, weaponId, modSlot, modToAddTemplate, botRole)); + request.weapon.push(this.createModItem( + modId, + modToAddTemplate._id, + request.weaponId, + modSlot, + modToAddTemplate, + request.botData.role)); // I first thought we could use the recursive generateModsForItems as previously for cylinder magazines. // However, the recursion doesn't go over the slots of the parent mod but over the modPool which is given by the bot config @@ -505,11 +518,11 @@ export class BotEquipmentModGenerator if (this.botWeaponGeneratorHelper.magazineIsCylinderRelated(modParentItem._name)) { // We don't have child mods, we need to create the camoras for the magazines instead - this.fillCamora(weapon, modPool, modId, modToAddTemplate); + this.fillCamora(request.weapon, request.modPool, modId, modToAddTemplate); } else { - let containsModInPool = Object.keys(modPool).includes(modToAddTemplate._id); + let containsModInPool = Object.keys(request.modPool).includes(modToAddTemplate._id); // Sometimes randomised slots are missing sub-mods, if so, get values from mod pool service // Check for a randomisable slot + without data in modPool + item being added as additional slots @@ -518,31 +531,36 @@ export class BotEquipmentModGenerator const modFromService = this.botEquipmentModPoolService.getModsForWeaponSlot(modToAddTemplate._id); if (Object.keys(modFromService ?? {}).length > 0) { - modPool[modToAddTemplate._id] = modFromService; + request.modPool[modToAddTemplate._id] = modFromService; containsModInPool = true; } } if (containsModInPool) { + const recursiveRequestData: IGenerateWeaponRequest = { + weapon: request.weapon, + modPool: request.modPool, + weaponId: modId, + parentTemplate: modToAddTemplate, + modSpawnChances: request.modSpawnChances, + ammoTpl: request.ammoTpl, + botData: { + role: request.botData.role, + level: request.botData.level, + equipmentRole: request.botData.equipmentRole }, + modLimits: request.modLimits, + weaponStats: request.weaponStats, + }; // Call self recursively to add mods to this mod this.generateModsForWeapon( sessionId, - weapon, - modPool, - modId, - modToAddTemplate, - modSpawnChances, - ammoTpl, - botRole, - botLevel, - modLimits, - botEquipmentRole, + recursiveRequestData, ); } } } - return weapon; + return request.weapon; } /** @@ -746,62 +764,72 @@ export class BotEquipmentModGenerator } /** - * @param modSlot Slot mod will fit into - * @param isRandomisableSlot Will generate a randomised mod pool if true - * @param modsParent Parent slot the item will be a part of - * @param botEquipBlacklist Blacklist to prevent mods from being picked - * @param itemModPool Pool of items to pick from - * @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 + * Choose a mod to fit into the desired slot + * @param request Data used to choose an appropriate mod with * @returns itemHelper.getItem() result */ protected chooseModToPutIntoSlot( - modSlot: string, - isRandomisableSlot: boolean, - botWeaponSightWhitelist: Record, - botEquipBlacklist: EquipmentFilterDetails, - itemModPool: Record, - weapon: Item[], - ammoTpl: string, - parentTemplate: ITemplateItem, - modSpawnResult: ModSpawn, + request: IModToSpawnRequest, ): [boolean, ITemplateItem] { /** Slot mod will fill */ - const parentSlot = parentTemplate._props.Slots.find((i) => i._name === modSlot); - const weaponTemplate = this.itemHelper.getItem(weapon[0]._tpl)[1]; + const parentSlot = request.parentTemplate._props.Slots.find((i) => i._name === request.modSlot); + const weaponTemplate = this.itemHelper.getItem(request.weapon[0]._tpl)[1]; // It's ammo, use predefined ammo parameter - if (this.getAmmoContainers().includes(modSlot) && modSlot !== "mod_magazine") + if (this.getAmmoContainers().includes(request.modSlot) && request.modSlot !== "mod_magazine") { - return this.itemHelper.getItem(ammoTpl); + return this.itemHelper.getItem(request.ammoTpl); } // Ensure there's a pool of mods to pick from let modPool = this.getModPoolForSlot( - itemModPool, - modSpawnResult, - parentTemplate, + request.itemModPool, + request.modSpawnResult, + request.parentTemplate, weaponTemplate, - modSlot, - botEquipBlacklist, - isRandomisableSlot, + request.modSlot, + request.botEquipBlacklist, + request.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`); + this.logger.debug(`Mod pool for slot: ${request.modSlot} on item: ${request.parentTemplate._name} was empty, skipping mod`); return null; } // Filter out non-whitelisted scopes, use full modpool if filtered pool would have no elements - if (modSlot.includes("mod_scope") && botWeaponSightWhitelist) + if (request.modSlot.includes("mod_scope") && request.botWeaponSightWhitelist) { // scope pool has more than one scope if (modPool.length > 1) { - modPool = this.filterSightsByWeaponType(weapon[0], modPool, botWeaponSightWhitelist); + modPool = this.filterSightsByWeaponType(request.weapon[0], modPool, request.botWeaponSightWhitelist); + } + } + + if (request.modSlot == "mod_gas_block") + { + if (request.weaponStats.hasOptic && modPool.length > 1) + { + // Attempt to limit modpool to low profile gas blocks when weapon has an optic + const onlyLowProfileGasBlocks = modPool + .filter((tpl) => this.botConfig.lowProfileGasBlockTpls.includes(tpl)); + if (onlyLowProfileGasBlocks.length > 0) + { + modPool = onlyLowProfileGasBlocks; + } + } + else if (request.weaponStats.hasRearIronSight && modPool.length > 1) + { + // Attempt to limit modpool to high profile gas blocks when weapon has rear iron sight + no front iron sight + const onlyHighProfileGasBlocks = modPool + .filter((tpl) => !this.botConfig.lowProfileGasBlockTpls.includes(tpl)); + if (onlyHighProfileGasBlocks.length > 0) + { + modPool = onlyHighProfileGasBlocks; + } } } @@ -809,9 +837,9 @@ export class BotEquipmentModGenerator const chosenModResult = this.pickWeaponModTplForSlotFromPool( modPool, parentSlot, - modSpawnResult, - weapon, - modSlot, + request.modSpawnResult, + request.weapon, + request.modSlot, ); if (chosenModResult.slotBlocked && !parentSlot._required) { @@ -829,7 +857,7 @@ export class BotEquipmentModGenerator // Get random mod to attach from items db for required slots if none found above if (!chosenModResult.found && parentSlot !== undefined && parentSlot._required) { - chosenModResult.chosenTpl = this.getRandomModTplFromItemDb("", parentSlot, modSlot, weapon); + chosenModResult.chosenTpl = this.getRandomModTplFromItemDb("", parentSlot, request.modSlot, request.weapon); chosenModResult.found = true; } @@ -844,7 +872,7 @@ export class BotEquipmentModGenerator if (parentSlot._required) { this.logger.warning( - `Required slot unable to be filled, ${modSlot} on ${parentTemplate._name} ${parentTemplate._id} for weapon: ${weapon[0]._tpl}`, + `Required slot unable to be filled, ${request.modSlot} on ${request.parentTemplate._name} ${request.parentTemplate._id} for weapon: ${request.weapon[0]._tpl}`, ); } diff --git a/project/src/generators/BotWeaponGenerator.ts b/project/src/generators/BotWeaponGenerator.ts index 46bea5b5..b425833f 100644 --- a/project/src/generators/BotWeaponGenerator.ts +++ b/project/src/generators/BotWeaponGenerator.ts @@ -14,6 +14,7 @@ import { ITemplateItem } from "@spt/models/eft/common/tables/ITemplateItem"; import { ConfigTypes } from "@spt/models/enums/ConfigTypes"; import { EquipmentSlots } from "@spt/models/enums/EquipmentSlots"; import { GenerateWeaponResult } from "@spt/models/spt/bots/GenerateWeaponResult"; +import { IGenerateWeaponRequest } from "@spt/models/spt/bots/IGenerateWeaponRequest"; import { IBotConfig } from "@spt/models/spt/config/IBotConfig"; import { IPmcConfig } from "@spt/models/spt/config/IPmcConfig"; import { IRepairConfig } from "@spt/models/spt/config/IRepairConfig"; @@ -170,18 +171,21 @@ export class BotWeaponGenerator { const botEquipmentRole = this.botGeneratorHelper.getBotEquipmentRole(botRole); const modLimits = this.botWeaponModLimitService.getWeaponModLimits(botEquipmentRole); + + const generateWeaponModsRequest: IGenerateWeaponRequest = { + weapon: weaponWithModsArray, // Will become hydrated array of weapon + mods + modPool: modPool, + weaponId: weaponWithModsArray[0]._id, // Weapon root id + parentTemplate: weaponItemTemplate, + modSpawnChances: modChances, + ammoTpl: ammoTpl, + botData: { role: botRole, level: botLevel, equipmentRole: botEquipmentRole }, + modLimits: modLimits, + weaponStats: {}, + }; weaponWithModsArray = this.botEquipmentModGenerator.generateModsForWeapon( sessionId, - weaponWithModsArray, - modPool, - weaponWithModsArray[0]._id, // Weapon root id - weaponItemTemplate, - modChances, - ammoTpl, - botRole, - botLevel, - modLimits, - botEquipmentRole, + generateWeaponModsRequest, ); } diff --git a/project/src/models/spt/bots/IGenerateWeaponRequest.ts b/project/src/models/spt/bots/IGenerateWeaponRequest.ts new file mode 100644 index 00000000..9b38b1cf --- /dev/null +++ b/project/src/models/spt/bots/IGenerateWeaponRequest.ts @@ -0,0 +1,43 @@ +import { Mods, ModsChances } from "@spt/models/eft/common/tables/IBotType"; +import { Item } from "@spt/models/eft/common/tables/IItem"; +import { ITemplateItem } from "@spt/models/eft/common/tables/ITemplateItem"; +import { BotModLimits } from "@spt/services/BotWeaponModLimitService"; + +export interface IGenerateWeaponRequest +{ + /** Weapon to add mods to / result that is returned */ + weapon: Item[] + /** Pool of compatible mods to attach to weapon */ + modPool: Mods + /** ParentId of weapon */ + weaponId: string + /** Weapon which mods will be generated on */ + parentTemplate: ITemplateItem + /** Chance values mod will be added */ + modSpawnChances: ModsChances + /** Ammo tpl to use when generating magazines/cartridges */ + ammoTpl: string + /** Bot-specific properties */ + botData: IBotData + /** limits placed on certain mod types per gun */ + modLimits: BotModLimits + /** Info related to the weapon being generated */ + weaponStats: IWeaponStats +} + +export interface IBotData +{ + /** Role of bot weapon is generated for */ + role: string + /** Level of the bot weapon is being generated for */ + level: number + /** role of bot when accessing bot.json equipment config settings */ + equipmentRole: string +} + +export interface IWeaponStats +{ + hasOptic?: boolean + hasFrontIronSight?: boolean + hasRearIronSight?: boolean +} diff --git a/project/src/models/spt/bots/IModToSpawnRequest.ts b/project/src/models/spt/bots/IModToSpawnRequest.ts new file mode 100644 index 00000000..f7f7c7e4 --- /dev/null +++ b/project/src/models/spt/bots/IModToSpawnRequest.ts @@ -0,0 +1,29 @@ +import { Item } from "@spt/models/eft/common/tables/IItem"; +import { ITemplateItem } from "@spt/models/eft/common/tables/ITemplateItem"; +import { ModSpawn } from "@spt/models/enums/ModSpawn"; +import { IWeaponStats } from "@spt/models/spt/bots/IGenerateWeaponRequest"; +import { EquipmentFilterDetails } from "@spt/models/spt/config/IBotConfig"; + +export interface IModToSpawnRequest +{ + /** Slot mod will fit into */ + modSlot: string + /** Will generate a randomised mod pool if true */ + isRandomisableSlot: boolean + /** Parent slot the item will be a part of */ + botWeaponSightWhitelist: Record + /** Blacklist to prevent mods from being picked */ + botEquipBlacklist: EquipmentFilterDetails + /** Pool of items to pick from */ + itemModPool: Record + /** Array with only weapon tpl in it, ready for mods to be added */ + weapon: Item[] + /** Ammo tpl to use if slot requires a cartridge to be added (e.g. mod_magazine) */ + ammoTpl: string + /** Parent item the mod will go into */ + parentTemplate: ITemplateItem + /** Should mod be spawned/skipped/use default */ + modSpawnResult: ModSpawn + /** Weapon stats for weapon being generated */ + weaponStats: IWeaponStats +} diff --git a/project/src/models/spt/config/IBotConfig.ts b/project/src/models/spt/config/IBotConfig.ts index c731df0e..7599944c 100644 --- a/project/src/models/spt/config/IBotConfig.ts +++ b/project/src/models/spt/config/IBotConfig.ts @@ -41,6 +41,8 @@ export interface IBotConfig extends IBaseConfig walletLoot: IWalletLootSettings /** Currency weights, Keyed by botrole / currency */ currencyStackSize: Record>> + /** Tpls for low profile gas blocks */ + lowProfileGasBlockTpls: string[] } /** Number of bots to generate and store in cache on raid start per bot type */