diff --git a/project/src/generators/BotEquipmentModGenerator.ts b/project/src/generators/BotEquipmentModGenerator.ts index 259d7acd..d572694c 100644 --- a/project/src/generators/BotEquipmentModGenerator.ts +++ b/project/src/generators/BotEquipmentModGenerator.ts @@ -46,12 +46,12 @@ export class BotEquipmentModGenerator @inject("BotWeaponGeneratorHelper") protected botWeaponGeneratorHelper: BotWeaponGeneratorHelper, @inject("LocalisationService") protected localisationService: LocalisationService, @inject("BotEquipmentModPoolService") protected botEquipmentModPoolService: BotEquipmentModPoolService, - @inject("ConfigServer") protected configServer: ConfigServer + @inject("ConfigServer") protected configServer: ConfigServer, ) { this.botConfig = this.configServer.getConfig(ConfigTypes.BOT); } - + /** * Check mods are compatible and add to array * @param equipment Equipment item to add mods to @@ -63,7 +63,15 @@ export class BotEquipmentModGenerator * @param forceSpawn should this mod be forced to spawn * @returns Item + compatible mods as an array */ - public generateModsForEquipment(equipment: Item[], modPool: Mods, parentId: string, parentTemplate: ITemplateItem, modSpawnChances: ModsChances, botRole: string, forceSpawn = false): Item[] + public generateModsForEquipment( + equipment: Item[], + modPool: Mods, + parentId: string, + parentTemplate: ITemplateItem, + modSpawnChances: ModsChances, + botRole: string, + forceSpawn = false, + ): Item[] { const compatibleModsPool = modPool[parentTemplate._id]; @@ -73,7 +81,13 @@ export class BotEquipmentModGenerator const itemSlot = this.getModItemSlot(modSlot, parentTemplate); if (!itemSlot) { - this.logger.error(this.localisationService.getText("bot-mod_slot_missing_from_item", {modSlot: modSlot, parentId: parentTemplate._id, parentName: parentTemplate._name})); + this.logger.error( + this.localisationService.getText("bot-mod_slot_missing_from_item", { + modSlot: modSlot, + parentId: parentTemplate._id, + parentName: parentTemplate._name, + }), + ); continue; } @@ -83,27 +97,33 @@ export class BotEquipmentModGenerator } // Ensure submods for nvgs all spawn together - forceSpawn = (modSlot === "mod_nvg") - ? true - : false; + forceSpawn = (modSlot === "mod_nvg") ? + true : + false; let modTpl: string; let found = false; - + // Find random mod and check its compatible - const exhaustableModPool = new ExhaustableArray(compatibleModsPool[modSlot], this.randomUtil, this.jsonUtil); + const exhaustableModPool = new ExhaustableArray( + compatibleModsPool[modSlot], + this.randomUtil, + this.jsonUtil, + ); while (exhaustableModPool.hasValues()) { modTpl = exhaustableModPool.getRandomValue(); - if (!this.botGeneratorHelper.isItemIncompatibleWithCurrentItems(equipment, modTpl, modSlot).incompatible) + if ( + !this.botGeneratorHelper.isItemIncompatibleWithCurrentItems(equipment, modTpl, modSlot).incompatible + ) { found = true; break; } } - // Combatible item not found but slot REQUIRES item, get random item from db - const parentSlot = parentTemplate._props.Slots.find(i => i._name === modSlot); + // Compatible item not found but slot REQUIRES item, get random item from db + const parentSlot = parentTemplate._props.Slots.find((i) => i._name === modSlot); if (!found && parentSlot !== undefined && parentSlot._required) { modTpl = this.getModTplFromItemDb(modTpl, parentSlot, modSlot, equipment); @@ -113,7 +133,7 @@ export class BotEquipmentModGenerator // Compatible item not found + not required if (!found && parentSlot !== undefined && !parentSlot._required) { - // Dont add item + // Don't add item continue; } @@ -128,8 +148,16 @@ export class BotEquipmentModGenerator if (Object.keys(modPool).includes(modTpl)) { - // Call self recursivly - this.generateModsForEquipment(equipment, modPool, modId, modTemplate[1], modSpawnChances, botRole, forceSpawn); + // Call self recursively + this.generateModsForEquipment( + equipment, + modPool, + modId, + modTemplate[1], + modSpawnChances, + botRole, + forceSpawn, + ); } } @@ -146,8 +174,8 @@ export class BotEquipmentModGenerator * @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 lvel of the bot weapon is being generated for - * @param modLimits limits placed on certian mod types per gun + * @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 * @returns Weapon + mods array */ @@ -162,7 +190,8 @@ export class BotEquipmentModGenerator botRole: string, botLevel: number, modLimits: BotModLimits, - botEquipmentRole: string): Item[] + botEquipmentRole: string, + ): Item[] { const pmcProfile = this.profileHelper.getPmcProfile(sessionId); @@ -171,17 +200,27 @@ export class BotEquipmentModGenerator // Null guard against bad input weapon // biome-ignore lint/complexity/useSimplifiedLogicExpression: - if (!parentTemplate._props.Slots.length - && !parentTemplate._props.Cartridges?.length - && !parentTemplate._props.Chambers?.length) + if ( + !parentTemplate._props.Slots.length && + !parentTemplate._props.Cartridges?.length && + !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})); + this.logger.error( + this.localisationService.getText("bot-unable_to_add_mods_to_weapon_missing_ammo_slot", { + weaponName: parentTemplate._name, + weaponId: parentTemplate._id, + }), + ); return weapon; } const botEquipConfig = this.botConfig.equipment[botEquipmentRole]; - const botEquipBlacklist = this.botEquipmentFilterService.getBotEquipmentBlacklist(botEquipmentRole, pmcProfile.Info.Level); + const botEquipBlacklist = this.botEquipmentFilterService.getBotEquipmentBlacklist( + botEquipmentRole, + pmcProfile.Info.Level, + ); const botWeaponSightWhitelist = this.botEquipmentFilterService.getBotWeaponSightWhitelist(botEquipmentRole); const randomisationSettings = this.botHelper.getBotRandomizationDetails(botLevel, botEquipConfig); @@ -193,7 +232,14 @@ export class BotEquipmentModGenerator const modsParentSlot = this.getModItemSlot(modSlot, parentTemplate); if (!modsParentSlot) { - this.logger.error(this.localisationService.getText("bot-weapon_missing_mod_slot", {modSlot: modSlot, weaponId: parentTemplate._id, weaponName: parentTemplate._name, botRole: botRole})); + this.logger.error( + this.localisationService.getText("bot-weapon_missing_mod_slot", { + modSlot: modSlot, + weaponId: parentTemplate._id, + weaponName: parentTemplate._name, + botRole: botRole, + }), + ); continue; } @@ -205,10 +251,19 @@ export class BotEquipmentModGenerator } const isRandomisableSlot = randomisationSettings?.randomisedWeaponModSlots?.includes(modSlot) ?? false; - const modToAdd = this.chooseModToPutIntoSlot(modSlot, isRandomisableSlot, botWeaponSightWhitelist, botEquipBlacklist, compatibleModsPool, weapon, ammoTpl, parentTemplate); + const modToAdd = this.chooseModToPutIntoSlot( + modSlot, + isRandomisableSlot, + botWeaponSightWhitelist, + botEquipBlacklist, + compatibleModsPool, + weapon, + ammoTpl, + parentTemplate, + ); // Compatible mod not found - if (!modToAdd || typeof (modToAdd) === "undefined") + if (!modToAdd || typeof modToAdd === "undefined") { continue; } @@ -220,7 +275,15 @@ export class BotEquipmentModGenerator const modToAddTemplate = modToAdd[1]; // Skip adding mod to weapon if type limit reached - if (this.botWeaponModLimitService.weaponModHasReachedLimit(botEquipmentRole, modToAddTemplate, modLimits, parentTemplate, weapon)) + if ( + this.botWeaponModLimitService.weaponModHasReachedLimit( + botEquipmentRole, + modToAddTemplate, + modLimits, + parentTemplate, + weapon, + ) + ) { continue; } @@ -234,7 +297,7 @@ export class BotEquipmentModGenerator "mod_scope_000", "mod_scope_001", "mod_scope_002", - "mod_scope_003" + "mod_scope_003", ]; this.adjustSlotSpawnChances(modSpawnChances, scopeSlots, 100); @@ -252,7 +315,7 @@ export class BotEquipmentModGenerator const muzzleSlots = [ "mod_muzzle", "mod_muzzle_000", - "mod_muzzle_001" + "mod_muzzle_001", ]; // Make chance of muzzle devices 95%, nearly certain but not guaranteed this.adjustSlotSpawnChances(modSpawnChances, muzzleSlots, 95); @@ -267,7 +330,10 @@ export class BotEquipmentModGenerator // Handguard mod can take a sub handguard mod + weapon has no UBGL (takes same slot) // Force spawn chance to be 100% to ensure it gets added - if (modSlot === "mod_handguard" && modToAddTemplate._props.Slots.find(x => x._name === "mod_handguard") && !weapon.find(x => x.slotId === "mod_launcher")) + if ( + modSlot === "mod_handguard" && modToAddTemplate._props.Slots.find((x) => x._name === "mod_handguard") && + !weapon.find((x) => x.slotId === "mod_launcher") + ) { // Needed for handguards with lower modSpawnChances.mod_handguard = 100; @@ -275,7 +341,10 @@ export class BotEquipmentModGenerator // If stock mod can take a sub stock mod, force spawn chance to be 100% to ensure sub-stock gets added // Or if mod_stock is configured to be forced on - if (modSlot === "mod_stock" && (modToAddTemplate._props.Slots.find(x => x._name.includes("mod_stock") || botEquipConfig.forceStock))) + if ( + modSlot === "mod_stock" && + (modToAddTemplate._props.Slots.find((x) => x._name.includes("mod_stock") || botEquipConfig.forceStock)) + ) { // Stock mod can take additional stocks, could be a locking device, force 100% chance const stockSlots = ["mod_stock", "mod_stock_000", "mod_stock_akms"]; @@ -283,10 +352,12 @@ export class BotEquipmentModGenerator } const modId = this.hashUtil.generate(); - weapon.push(this.createModItem(modId, modToAddTemplate._id, weaponParentId, modSlot, modToAddTemplate, botRole)); - + weapon.push( + this.createModItem(modId, modToAddTemplate._id, weaponParentId, modSlot, modToAddTemplate, botRole), + ); + // I first thought we could use the recursive generateModsForItems as previously for cylinder magazines. - // However, the recursion doesnt go over the slots of the parent mod but over the modPool which is given by the bot config + // However, the recursion doesn't go over the slots of the parent mod but over the modPool which is given by the bot config // where we decided to keep cartridges instead of camoras. And since a CylinderMagazine only has one cartridge entry and // this entry is not to be filled, we need a special handling for the CylinderMagazine const modParentItem = this.databaseServer.getTables().templates.items[modToAddTemplate._parent]; @@ -312,8 +383,20 @@ export class BotEquipmentModGenerator } if (containsModInPool) { - // Call self recursivly to add mods to this mod - this.generateModsForWeapon(sessionId, weapon, modPool, modId, modToAddTemplate, modSpawnChances, ammoTpl, botRole, botLevel, modLimits, botEquipmentRole); + // Call self recursively to add mods to this mod + this.generateModsForWeapon( + sessionId, + weapon, + modPool, + modId, + modToAddTemplate, + modSpawnChances, + ammoTpl, + botRole, + botLevel, + modLimits, + botEquipmentRole, + ); } } } @@ -328,8 +411,8 @@ export class BotEquipmentModGenerator */ protected modIsFrontOrRearSight(modSlot: string, tpl: string): boolean { - if (modSlot === "mod_gas_block" && tpl === "5ae30e795acfc408fb139a0b") // M4A1 front sight with gas block - { + if (modSlot === "mod_gas_block" && tpl === "5ae30e795acfc408fb139a0b") + { // M4A1 front sight with gas block return true; } @@ -344,15 +427,27 @@ export class BotEquipmentModGenerator */ protected modSlotCanHoldScope(modSlot: string, modsParentId: string): boolean { - return ["mod_scope", "mod_mount", "mod_mount_000", "mod_scope_000", "mod_scope_001", "mod_scope_002", "mod_scope_003"].includes(modSlot.toLowerCase()) - && modsParentId === BaseClasses.MOUNT; + return [ + "mod_scope", + "mod_mount", + "mod_mount_000", + "mod_scope_000", + "mod_scope_001", + "mod_scope_002", + "mod_scope_003", + ].includes(modSlot.toLowerCase()) && + modsParentId === BaseClasses.MOUNT; } /** * Set mod spawn chances to defined amount * @param modSpawnChances Chance dictionary to update */ - protected adjustSlotSpawnChances(modSpawnChances: ModsChances, modSlotsToAdjust: string[], newChancePercent: number): void + protected adjustSlotSpawnChances( + modSpawnChances: ModsChances, + modSlotsToAdjust: string[], + newChancePercent: number, + ): void { if (!modSpawnChances) { @@ -418,7 +513,7 @@ export class BotEquipmentModGenerator sortedKeys.push(modRecieverKey); unsortedKeys.splice(unsortedKeys.indexOf(modRecieverKey), 1); } - + if (unsortedKeys.includes(modPistolGrip)) { sortedKeys.push(modPistolGrip); @@ -467,11 +562,11 @@ export class BotEquipmentModGenerator case "patron_in_weapon": case "patron_in_weapon_000": case "patron_in_weapon_001": - return parentTemplate._props.Chambers.find(c => c._name.includes(modSlot)); + return parentTemplate._props.Chambers.find((c) => c._name.includes(modSlot)); case "cartridges": - return parentTemplate._props.Cartridges.find(c => c._name === modSlot); + return parentTemplate._props.Cartridges.find((c) => c._name === modSlot); default: - return parentTemplate._props.Slots.find(s => s._name === modSlot); + return parentTemplate._props.Slots.find((s) => s._name === modSlot); } } @@ -486,8 +581,9 @@ export class BotEquipmentModGenerator protected shouldModBeSpawned(itemSlot: Slot, modSlot: string, modSpawnChances: ModsChances): boolean { const modSpawnChance = itemSlot._required || this.getAmmoContainers().includes(modSlot) // Required OR it is ammo - ? 100 - : modSpawnChances[modSlot]; + ? + 100 : + modSpawnChances[modSlot]; if (modSpawnChance === 100) { @@ -498,7 +594,6 @@ 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 @@ -517,12 +612,13 @@ export class BotEquipmentModGenerator itemModPool: Record, weapon: Item[], ammoTpl: string, - parentTemplate: ITemplateItem): [boolean, ITemplateItem] + parentTemplate: ITemplateItem, + ): [boolean, ITemplateItem] { let modTpl: string; let found = false; - const parentSlot = parentTemplate._props.Slots.find(i => i._name === modSlot); - + const parentSlot = parentTemplate._props.Slots.find((i) => i._name === modSlot); + // It's ammo, use predefined ammo parameter if (this.getAmmoContainers().includes(modSlot) && modSlot !== "mod_magazine") { @@ -539,27 +635,37 @@ export class BotEquipmentModGenerator // Ensure there's a pool of mods to pick from if (!(itemModPool[modSlot] || parentSlot._required)) { - this.logger.debug(`Mod pool for slot: ${modSlot} on item: ${parentTemplate._name} was empty, skipping mod`); + this.logger.debug( + `Mod pool for slot: ${modSlot} on item: ${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 (modSlot.includes("mod_scope") && botWeaponSightWhitelist) { // scope pool has more than one scope if (itemModPool[modSlot].length > 1) { - itemModPool[modSlot] = this.filterSightsByWeaponType(weapon[0], itemModPool[modSlot], botWeaponSightWhitelist); + itemModPool[modSlot] = this.filterSightsByWeaponType( + weapon[0], + itemModPool[modSlot], + botWeaponSightWhitelist, + ); } } - + // Pick random mod and check it's compatible const exhaustableModPool = new ExhaustableArray(itemModPool[modSlot], this.randomUtil, this.jsonUtil); - let modCompatibilityResult: {incompatible: boolean, reason: string} = {incompatible: false, reason: ""}; + let modCompatibilityResult: {incompatible: boolean; reason: string;} = {incompatible: false, reason: ""}; while (exhaustableModPool.hasValues()) { modTpl = exhaustableModPool.getRandomValue(); - modCompatibilityResult = this.botGeneratorHelper.isItemIncompatibleWithCurrentItems(weapon, modTpl, modSlot); + modCompatibilityResult = this.botGeneratorHelper.isItemIncompatibleWithCurrentItems( + weapon, + modTpl, + modSlot, + ); if (!modCompatibilityResult.incompatible) { found = true; @@ -592,7 +698,11 @@ 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}`); + this.logger.warning( + `Required slot unable to be filled, ${modSlot} on ${parentTemplate._name} ${parentTemplate._id} for weapon: ${ + weapon[0]._tpl + }`, + ); } return null; @@ -607,21 +717,27 @@ export class BotEquipmentModGenerator * @param modTpl _tpl * @param parentId parentId * @param modSlot slotId - * @param modTemplate Used to add additional properites in the upd object + * @param modTemplate Used to add additional properties in the upd object * @returns Item object */ - protected createModItem(modId: string, modTpl: string, parentId: string, modSlot: string, modTemplate: ITemplateItem, botRole: string): Item + protected createModItem( + modId: string, + modTpl: string, + parentId: string, + modSlot: string, + modTemplate: ITemplateItem, + botRole: string, + ): Item { return { _id: modId, _tpl: modTpl, parentId: parentId, slotId: modSlot, - ...this.botGeneratorHelper.generateExtraPropertiesForItem(modTemplate, botRole) + ...this.botGeneratorHelper.generateExtraPropertiesForItem(modTemplate, botRole), }; } - /** * Get a list of containers that hold ammo * e.g. mod_magazine / patron_in_weapon_000 @@ -635,14 +751,14 @@ export class BotEquipmentModGenerator /** * Get a random mod from an items compatible mods Filter array * @param modTpl ???? default value to return if nothing found - * @param parentSlot item mod will go into, used to get combatible items + * @param parentSlot item mod will go into, used to get compatible items * @param modSlot Slot to get mod to fill * @param items items to ensure picked mod is compatible with * @returns item tpl */ protected getModTplFromItemDb(modTpl: string, parentSlot: Slot, modSlot: string, items: Item[]): string { - // Find combatible mods and make an array of them + // Find compatible mods and make an array of them const allowedItems = parentSlot._props.filters[0].Filter; // Find mod item that fits slot from sorted mod array @@ -669,12 +785,22 @@ export class BotEquipmentModGenerator * @param parentTemplate template of the mods parent item * @returns true if valid */ - protected isModValidForSlot(modToAdd: [boolean, ITemplateItem], itemSlot: Slot, modSlot: string, parentTemplate: ITemplateItem): boolean + protected isModValidForSlot( + modToAdd: [boolean, ITemplateItem], + itemSlot: Slot, + modSlot: string, + parentTemplate: ITemplateItem, + ): boolean { // Mod lacks template item if (!modToAdd[1]) { - this.logger.error(this.localisationService.getText("bot-no_item_template_found_when_adding_mod", {modId: modToAdd[1]._id, modSlot: modSlot})); + this.logger.error( + this.localisationService.getText("bot-no_item_template_found_when_adding_mod", { + modId: modToAdd[1]._id, + modSlot: modSlot, + }), + ); this.logger.debug(`Item -> ${parentTemplate._id}; Slot -> ${modSlot}`); return false; @@ -686,16 +812,31 @@ export class BotEquipmentModGenerator // Slot must be filled, show warning if (itemSlot._required) { - this.logger.warning(this.localisationService.getText("bot-unable_to_add_mod_item_invalid", {itemName: modToAdd[1]._name, modSlot: modSlot, parentItemName: parentTemplate._name})); + this.logger.warning( + this.localisationService.getText("bot-unable_to_add_mod_item_invalid", { + itemName: modToAdd[1]._name, + modSlot: modSlot, + parentItemName: parentTemplate._name, + }), + ); } return false; } - // If mod id doesnt exist in slots filter list and mod id doesnt have any of the slots filters as a base class, mod isn't valid for the slot - if (!(itemSlot._props.filters[0].Filter.includes(modToAdd[1]._id) || this.itemHelper.isOfBaseclasses(modToAdd[1]._id, itemSlot._props.filters[0].Filter))) + // If mod id doesn't exist in slots filter list and mod id doesn't have any of the slots filters as a base class, mod isn't valid for the slot + if ( + !(itemSlot._props.filters[0].Filter.includes(modToAdd[1]._id) || + this.itemHelper.isOfBaseclasses(modToAdd[1]._id, itemSlot._props.filters[0].Filter)) + ) { - this.logger.warning(this.localisationService.getText("bot-mod_not_in_slot_filter_list", {modId: modToAdd[1]._id, modSlot: modSlot, parentName: parentTemplate._name})); + this.logger.warning( + this.localisationService.getText("bot-mod_not_in_slot_filter_list", { + modId: modToAdd[1]._id, + modSlot: modSlot, + parentName: parentTemplate._name, + }), + ); return false; } @@ -703,26 +844,39 @@ export class BotEquipmentModGenerator return true; } - /** * Find mod tpls of a provided type and add to modPool * @param desiredSlotName slot to look up and add we are adding tpls for (e.g mod_scope) * @param modTemplate db object for modItem we get compatible mods from * @param modPool Pool of mods we are adding to */ - protected addCompatibleModsForProvidedMod(desiredSlotName: string, modTemplate: ITemplateItem, modPool: Mods, botEquipBlacklist: EquipmentFilterDetails): void + protected addCompatibleModsForProvidedMod( + desiredSlotName: string, + modTemplate: ITemplateItem, + modPool: Mods, + botEquipBlacklist: EquipmentFilterDetails, + ): void { - const desiredSlotObject = modTemplate._props.Slots.find(x => x._name.includes(desiredSlotName)); + const desiredSlotObject = modTemplate._props.Slots.find((x) => x._name.includes(desiredSlotName)); if (desiredSlotObject) { const supportedSubMods = desiredSlotObject._props.filters[0].Filter; if (supportedSubMods) { // Filter mods - let filteredMods = this.filterWeaponModsByBlacklist(supportedSubMods, botEquipBlacklist, desiredSlotName); + let filteredMods = this.filterWeaponModsByBlacklist( + supportedSubMods, + botEquipBlacklist, + desiredSlotName, + ); if (filteredMods.length === 0) { - this.logger.warning(this.localisationService.getText("bot-unable_to_filter_mods_all_blacklisted", {slotName: desiredSlotObject._name, itemName: modTemplate._name})); + this.logger.warning( + this.localisationService.getText("bot-unable_to_filter_mods_all_blacklisted", { + slotName: desiredSlotObject._name, + itemName: modTemplate._name, + }), + ); filteredMods = supportedSubMods; } @@ -736,7 +890,6 @@ export class BotEquipmentModGenerator } } - /** * Get the possible items that fit a slot * @param parentItemId item tpl to get compatible items for @@ -744,14 +897,22 @@ export class BotEquipmentModGenerator * @param botEquipBlacklist equipment that should not be picked * @returns array of compatible items for that slot */ - protected getDynamicModPool(parentItemId: string, modSlot: string, botEquipBlacklist: EquipmentFilterDetails): string[] + protected getDynamicModPool( + parentItemId: string, + modSlot: string, + botEquipBlacklist: EquipmentFilterDetails, + ): string[] { - const modsFromDynamicPool = this.jsonUtil.clone(this.botEquipmentModPoolService.getCompatibleModsForWeaponSlot(parentItemId, modSlot)); + const modsFromDynamicPool = this.jsonUtil.clone( + this.botEquipmentModPoolService.getCompatibleModsForWeaponSlot(parentItemId, modSlot), + ); const filteredMods = this.filterWeaponModsByBlacklist(modsFromDynamicPool, botEquipBlacklist, modSlot); if (filteredMods.length === 0) { - this.logger.warning(this.localisationService.getText("bot-unable_to_filter_mod_slot_all_blacklisted", modSlot)); + this.logger.warning( + this.localisationService.getText("bot-unable_to_filter_mod_slot_all_blacklisted", modSlot), + ); return modsFromDynamicPool; } @@ -765,18 +926,24 @@ export class BotEquipmentModGenerator * @param modSlot slot mods belong to * @returns Filtered array of mod tpls */ - protected filterWeaponModsByBlacklist(allowedMods: string[], botEquipBlacklist: EquipmentFilterDetails, modSlot: string): string[] + protected filterWeaponModsByBlacklist( + allowedMods: string[], + botEquipBlacklist: EquipmentFilterDetails, + modSlot: string, + ): string[] { if (!botEquipBlacklist) { return allowedMods; } - + let result: string[] = []; - // Get item blacklist and mod equipmet blackist as one array - const blacklist = this.itemFilterService.getBlacklistedItems().concat(botEquipBlacklist.equipment[modSlot] || []); - result = allowedMods.filter(x => !blacklist.includes(x)); + // Get item blacklist and mod equipment blacklist as one array + const blacklist = this.itemFilterService.getBlacklistedItems().concat( + botEquipBlacklist.equipment[modSlot] || [], + ); + result = allowedMods.filter((x) => !blacklist.includes(x)); return result; } @@ -796,8 +963,13 @@ export class BotEquipmentModGenerator let itemModPool = modPool[parentTemplate._id]; if (!itemModPool) { - this.logger.warning(this.localisationService.getText("bot-unable_to_fill_camora_slot_mod_pool_empty", {weaponId: parentTemplate._id, weaponName: parentTemplate._name})); - const camoraSlots = parentTemplate._props.Slots.filter(x => x._name.startsWith("camora")); + this.logger.warning( + this.localisationService.getText("bot-unable_to_fill_camora_slot_mod_pool_empty", { + weaponId: parentTemplate._id, + weaponName: parentTemplate._name, + }), + ); + const camoraSlots = parentTemplate._props.Slots.filter((x) => x._name.startsWith("camora")); // Attempt to generate camora slots for item modPool[parentTemplate._id] = {}; @@ -818,7 +990,11 @@ export class BotEquipmentModGenerator else if (camoraFirstSlot in itemModPool) { modSlot = camoraFirstSlot; - exhaustableModPool = new ExhaustableArray(this.mergeCamoraPoolsTogether(itemModPool), this.randomUtil, this.jsonUtil); + exhaustableModPool = new ExhaustableArray( + this.mergeCamoraPoolsTogether(itemModPool), + this.randomUtil, + this.jsonUtil, + ); } else { @@ -854,17 +1030,17 @@ export class BotEquipmentModGenerator _id: modId, _tpl: modTpl, parentId: parentId, - slotId: modSlotId + slotId: modSlotId, }); } } /** - * Take a record of camoras and merge the compatable shells into one array + * Take a record of camoras and merge the compatible shells into one array * @param camorasWithShells camoras we want to merge into one array - * @returns string array of shells fro luitple camora sources + * @returns string array of shells for multiple camora sources */ - protected mergeCamoraPoolsTogether(camorasWithShells: Record ): string[] + protected mergeCamoraPoolsTogether(camorasWithShells: Record): string[] { const poolResult: string[] = []; for (const camoraKey in camorasWithShells) @@ -889,10 +1065,14 @@ export class BotEquipmentModGenerator * e.g. filter out rifle scopes from SMGs * @param weapon Weapon scopes will be added to * @param scopes Full scope pool - * @param botWeaponSightWhitelist Whitelist of scope types by weapon base type + * @param botWeaponSightWhitelist Whitelist of scope types by weapon base type * @returns Array of scope tpls that have been filtered to just ones allowed for that weapon type */ - protected filterSightsByWeaponType(weapon: Item, scopes: string[], botWeaponSightWhitelist: Record): string[] + protected filterSightsByWeaponType( + weapon: Item, + scopes: string[], + botWeaponSightWhitelist: Record, + ): string[] { const weaponDetails = this.itemHelper.getItem(weapon._tpl); @@ -900,7 +1080,11 @@ export class BotEquipmentModGenerator const whitelistedSightTypes = botWeaponSightWhitelist[weaponDetails[1]._parent]; if (!whitelistedSightTypes) { - this.logger.debug(`Unable to find whitelist for weapon type: ${weaponDetails[1]._parent} ${weaponDetails[1]._name}, skipping sight filtering`); + this.logger.debug( + `Unable to find whitelist for weapon type: ${weaponDetails[1]._parent} ${ + weaponDetails[1]._name + }, skipping sight filtering`, + ); return scopes; } @@ -920,14 +1104,23 @@ export class BotEquipmentModGenerator // Edge case, what if item is a mount for a scope and not directly a scope? // Check item is mount + has child items const itemDetails = this.itemHelper.getItem(item)[1]; - if (this.itemHelper.isOfBaseclass(item, BaseClasses.MOUNT) && itemDetails._props.Slots.length > 0 ) + if (this.itemHelper.isOfBaseclass(item, BaseClasses.MOUNT) && itemDetails._props.Slots.length > 0) { // Check to see if mount has a scope slot (only include primary slot, ignore the rest like the backup sight slots) // Should only find 1 as there's currently no items with a mod_scope AND a mod_scope_000 - const scopeSlot = itemDetails._props.Slots.filter(x => ["mod_scope", "mod_scope_000"].includes(x._name)); + const scopeSlot = itemDetails._props.Slots.filter((x) => + ["mod_scope", "mod_scope_000"].includes(x._name) + ); // Mods scope slot found must allow ALL whitelisted scope types OR be a mount - if (scopeSlot?.every(x => x._props.filters[0].Filter.every(x => this.itemHelper.isOfBaseclasses(x, whitelistedSightTypes) || this.itemHelper.isOfBaseclass(x, BaseClasses.MOUNT)))) + if ( + scopeSlot?.every((x) => + x._props.filters[0].Filter.every((x) => + this.itemHelper.isOfBaseclasses(x, whitelistedSightTypes) || + this.itemHelper.isOfBaseclass(x, BaseClasses.MOUNT) + ) + ) + ) { // Add mod to allowed list filteredScopesAndMods.push(item); @@ -935,14 +1128,16 @@ export class BotEquipmentModGenerator } } - // No mods added to return list after filtering has occured, send back the original mod list + // No mods added to return list after filtering has occurred, send back the original mod list if (!filteredScopesAndMods || filteredScopesAndMods.length === 0) { - this.logger.debug(`Scope whitelist too restrictive for: ${weapon._tpl} ${weaponDetails[1]._name}, skipping filter`); + this.logger.debug( + `Scope whitelist too restrictive for: ${weapon._tpl} ${weaponDetails[1]._name}, skipping filter`, + ); return scopes; } return filteredScopesAndMods; } -} \ No newline at end of file +} diff --git a/project/src/generators/BotGenerator.ts b/project/src/generators/BotGenerator.ts index 74b73e29..f43bf0b3 100644 --- a/project/src/generators/BotGenerator.ts +++ b/project/src/generators/BotGenerator.ts @@ -8,9 +8,12 @@ import { ProfileHelper } from "@spt-aki/helpers/ProfileHelper"; import { WeightedRandomHelper } from "@spt-aki/helpers/WeightedRandomHelper"; import { Common, - IBaseJsonSkills, IBaseSkill, IBotBase, Info, Health as PmcHealth, - Skills as botSkills + IBaseJsonSkills, + IBaseSkill, + IBotBase, + Info, + Skills as botSkills, } from "@spt-aki/models/eft/common/tables/IBotBase"; import { Appearance, Health, IBotType } from "@spt-aki/models/eft/common/tables/IBotType"; import { Item, Upd } from "@spt-aki/models/eft/common/tables/IItem"; @@ -53,7 +56,7 @@ export class BotGenerator @inject("BotDifficultyHelper") protected botDifficultyHelper: BotDifficultyHelper, @inject("SeasonalEventService") protected seasonalEventService: SeasonalEventService, @inject("LocalisationService") protected localisationService: LocalisationService, - @inject("ConfigServer") protected configServer: ConfigServer + @inject("ConfigServer") protected configServer: ConfigServer, ) { this.botConfig = this.configServer.getConfig(ConfigTypes.BOT); @@ -65,7 +68,7 @@ export class BotGenerator * @param role e.g. assault / pmcbot * @param difficulty easy/normal/hard/impossible * @param botTemplate base bot template to use (e.g. assault/pmcbot) - * @returns + * @returns */ public generatePlayerScav(sessionId: string, role: string, difficulty: string, botTemplate: IBotType): IBotBase { @@ -82,7 +85,7 @@ export class BotGenerator botRelativeLevelDeltaMax: 0, botCountToGenerate: 1, botDifficulty: difficulty, - isPlayerScav: true + isPlayerScav: true, }; bot = this.generateBot(sessionId, bot, botTemplate, botGenDetails); @@ -93,12 +96,13 @@ export class BotGenerator /** * Create x number of bots of the type/side/difficulty defined in botGenerationDetails * @param sessionId Session id - * @param botGenerationDetails details on how to generate bots + * @param botGenerationDetails details on how to generate bots * @returns array of bots */ public prepareAndGenerateBots( sessionId: string, - botGenerationDetails: BotGenerationDetails): IBotBase[] + botGenerationDetails: BotGenerationDetails, + ): IBotBase[] { const output: IBotBase[] = []; for (let i = 0; i < botGenerationDetails.botCountToGenerate; i++) @@ -108,19 +112,24 @@ export class BotGenerator bot.Info.Settings.Role = botGenerationDetails.role; bot.Info.Side = botGenerationDetails.side; bot.Info.Settings.BotDifficulty = botGenerationDetails.botDifficulty; - + // Get raw json data for bot (Cloned) const botJsonTemplate = this.jsonUtil.clone(this.botHelper.getBotTemplate( - (botGenerationDetails.isPmc) - ? bot.Info.Side - : botGenerationDetails.role)); + (botGenerationDetails.isPmc) ? + bot.Info.Side : + botGenerationDetails.role, + )); bot = this.generateBot(sessionId, bot, botJsonTemplate, botGenerationDetails); - + output.push(bot); } - this.logger.debug(`Generated ${botGenerationDetails.botCountToGenerate} ${output[0].Info.Settings.Role} (${botGenerationDetails.eventRole}) bots`); + this.logger.debug( + `Generated ${botGenerationDetails.botCountToGenerate} ${ + output[0].Info.Settings.Role + } (${botGenerationDetails.eventRole}) bots`, + ); return output; } @@ -142,21 +151,43 @@ export class BotGenerator * @param botGenerationDetails details on how to generate the bot * @returns IBotBase object */ - protected generateBot(sessionId: string, bot: IBotBase, botJsonTemplate: IBotType, botGenerationDetails: BotGenerationDetails): IBotBase + protected generateBot( + sessionId: string, + bot: IBotBase, + botJsonTemplate: IBotType, + botGenerationDetails: BotGenerationDetails, + ): IBotBase { const botRole = botGenerationDetails.role.toLowerCase(); - const botLevel = this.botLevelGenerator.generateBotLevel(botJsonTemplate.experience.level, botGenerationDetails, bot); + const botLevel = this.botLevelGenerator.generateBotLevel( + botJsonTemplate.experience.level, + botGenerationDetails, + bot, + ); if (!botGenerationDetails.isPlayerScav) { - this.botEquipmentFilterService.filterBotEquipment(sessionId, botJsonTemplate, botLevel.level, botGenerationDetails); + this.botEquipmentFilterService.filterBotEquipment( + sessionId, + botJsonTemplate, + botLevel.level, + botGenerationDetails, + ); } - bot.Info.Nickname = this.generateBotNickname(botJsonTemplate, botGenerationDetails.isPlayerScav, botRole, sessionId); + bot.Info.Nickname = this.generateBotNickname( + botJsonTemplate, + botGenerationDetails.isPlayerScav, + botRole, + sessionId, + ); if (!this.seasonalEventService.christmasEventEnabled()) { - this.seasonalEventService.removeChristmasItemsFromBotInventory(botJsonTemplate.inventory, botGenerationDetails.role); + this.seasonalEventService.removeChristmasItemsFromBotInventory( + botJsonTemplate.inventory, + botGenerationDetails.role, + ); } // Remove hideout data if bot is not a PMC or pscav @@ -167,7 +198,10 @@ export class BotGenerator bot.Info.Experience = botLevel.exp; bot.Info.Level = botLevel.level; - bot.Info.Settings.Experience = this.randomUtil.getInt(botJsonTemplate.experience.reward.min, botJsonTemplate.experience.reward.max); + bot.Info.Settings.Experience = this.randomUtil.getInt( + botJsonTemplate.experience.reward.min, + botJsonTemplate.experience.reward.max, + ); bot.Info.Settings.StandingForKill = botJsonTemplate.experience.standingForKill; bot.Info.Voice = this.randomUtil.getArrayValue(botJsonTemplate.appearance.voice); bot.Health = this.generateHealth(botJsonTemplate.health, bot.Info.Side === "Savage"); @@ -175,7 +209,13 @@ export class BotGenerator this.setBotAppearance(bot, botJsonTemplate.appearance, botGenerationDetails); - bot.Inventory = this.botInventoryGenerator.generateInventory(sessionId, botJsonTemplate, botRole, botGenerationDetails.isPmc, botLevel.level); + bot.Inventory = this.botInventoryGenerator.generateInventory( + sessionId, + botJsonTemplate, + botRole, + botGenerationDetails.isPmc, + botLevel.level, + ); if (this.botHelper.isBotPmc(botRole)) { @@ -205,7 +245,6 @@ export class BotGenerator * @param appearance Appearance settings to choose from * @param botGenerationDetails Generation details */ - // eslint-disable-next-line @typescript-eslint/no-unused-vars protected setBotAppearance(bot: IBotBase, appearance: Appearance, botGenerationDetails: BotGenerationDetails): void { bot.Customization.Head = this.randomUtil.getArrayValue(appearance.head); @@ -216,17 +255,24 @@ export class BotGenerator /** * Create a bot nickname - * @param botJsonTemplate x.json from database + * @param botJsonTemplate x.json from database * @param isPlayerScav Will bot be player scav * @param botRole role of bot e.g. assault * @returns Nickname for bot */ - protected generateBotNickname(botJsonTemplate: IBotType, isPlayerScav: boolean, botRole: string, sessionId: string): string + protected generateBotNickname( + botJsonTemplate: IBotType, + isPlayerScav: boolean, + botRole: string, + sessionId: string, + ): string { - let name = `${this.randomUtil.getArrayValue(botJsonTemplate.firstName)} ${this.randomUtil.getArrayValue(botJsonTemplate.lastName) || ""}`; + let name = `${this.randomUtil.getArrayValue(botJsonTemplate.firstName)} ${ + this.randomUtil.getArrayValue(botJsonTemplate.lastName) || "" + }`; name = name.trim(); const playerProfile = this.profileHelper.getPmcProfile(sessionId); - + // Simulate bot looking like a Player scav with the pmc name in brackets if (botRole === "assault" && this.randomUtil.getChance100(this.botConfig.chanceAssaultScavHasPlayerScavName)) { @@ -237,7 +283,7 @@ export class BotGenerator const pmcNames = [ ...this.databaseServer.getTables().bots.types["usec"].firstName, - ...this.databaseServer.getTables().bots.types["bear"].firstName + ...this.databaseServer.getTables().bots.types["bear"].firstName, ]; return `${name} (${this.randomUtil.getArrayValue(pmcNames)})`; @@ -253,7 +299,6 @@ export class BotGenerator { if (this.randomUtil.getChance100(this.pmcConfig.addPrefixToSameNamePMCAsPlayerChance)) { - const prefix = this.localisationService.getRandomTextThatMatchesPartialKey("pmc-name_prefix_"); name = `${prefix} ${name}`; } @@ -268,7 +313,10 @@ export class BotGenerator */ protected logPmcGeneratedCount(output: IBotBase[]): void { - const pmcCount = output.reduce((acc, cur) => cur.Info.Side === "Bear" || cur.Info.Side === "Usec" ? ++acc : acc, 0); + const pmcCount = output.reduce( + (acc, cur) => cur.Info.Side === "Bear" || cur.Info.Side === "Usec" ? ++acc : acc, + 0, + ); this.logger.debug(`Generated ${output.length} total bots. Replaced ${pmcCount} with PMCs`); } @@ -280,68 +328,68 @@ export class BotGenerator */ protected generateHealth(healthObj: Health, playerScav = false): PmcHealth { - const bodyParts = (playerScav) - ? healthObj.BodyParts[0] - : this.randomUtil.getArrayValue(healthObj.BodyParts); + const bodyParts = playerScav ? + healthObj.BodyParts[0] : + this.randomUtil.getArrayValue(healthObj.BodyParts); const newHealth: PmcHealth = { Hydration: { Current: this.randomUtil.getInt(healthObj.Hydration.min, healthObj.Hydration.max), - Maximum: healthObj.Hydration.max + Maximum: healthObj.Hydration.max, }, Energy: { Current: this.randomUtil.getInt(healthObj.Energy.min, healthObj.Energy.max), - Maximum: healthObj.Energy.max + Maximum: healthObj.Energy.max, }, Temperature: { Current: this.randomUtil.getInt(healthObj.Temperature.min, healthObj.Temperature.max), - Maximum: healthObj.Temperature.max + Maximum: healthObj.Temperature.max, }, BodyParts: { Head: { Health: { Current: this.randomUtil.getInt(bodyParts.Head.min, bodyParts.Head.max), - Maximum: Math.round(bodyParts.Head.max) - } + Maximum: Math.round(bodyParts.Head.max), + }, }, Chest: { Health: { Current: this.randomUtil.getInt(bodyParts.Chest.min, bodyParts.Chest.max), - Maximum: Math.round(bodyParts.Chest.max) - } + Maximum: Math.round(bodyParts.Chest.max), + }, }, Stomach: { Health: { Current: this.randomUtil.getInt(bodyParts.Stomach.min, bodyParts.Stomach.max), - Maximum: Math.round(bodyParts.Stomach.max) - } + Maximum: Math.round(bodyParts.Stomach.max), + }, }, LeftArm: { Health: { Current: this.randomUtil.getInt(bodyParts.LeftArm.min, bodyParts.LeftArm.max), - Maximum: Math.round(bodyParts.LeftArm.max) - } + Maximum: Math.round(bodyParts.LeftArm.max), + }, }, RightArm: { Health: { Current: this.randomUtil.getInt(bodyParts.RightArm.min, bodyParts.RightArm.max), - Maximum: Math.round(bodyParts.RightArm.max) - } + Maximum: Math.round(bodyParts.RightArm.max), + }, }, LeftLeg: { Health: { Current: this.randomUtil.getInt(bodyParts.LeftLeg.min, bodyParts.LeftLeg.max), - Maximum: Math.round(bodyParts.LeftLeg.max) - } + Maximum: Math.round(bodyParts.LeftLeg.max), + }, }, RightLeg: { Health: { Current: this.randomUtil.getInt(bodyParts.RightLeg.min, bodyParts.RightLeg.max), - Maximum: Math.round(bodyParts.RightLeg.max) - } - } + Maximum: Math.round(bodyParts.RightLeg.max), + }, + }, }, - UpdateTime: this.timeUtil.getTimestamp() + UpdateTime: this.timeUtil.getTimestamp(), }; return newHealth; @@ -350,14 +398,14 @@ export class BotGenerator /** * Get a bots skills with randomsied progress value between the min and max values * @param botSkills Skills that should have their progress value randomised - * @returns + * @returns */ protected generateSkills(botSkills: IBaseJsonSkills): botSkills { const skillsToReturn: botSkills = { Common: this.getSkillsWithRandomisedProgressValue(botSkills.Common, true), Mastering: this.getSkillsWithRandomisedProgressValue(botSkills.Mastering, false), - Points: 0 + Points: 0, }; return skillsToReturn; @@ -369,7 +417,10 @@ export class BotGenerator * @param isCommonSkills Are the skills 'common' skills * @returns Skills with randomised progress values as an array */ - protected getSkillsWithRandomisedProgressValue(skills: Record, isCommonSkills: boolean): IBaseSkill[] + protected getSkillsWithRandomisedProgressValue( + skills: Record, + isCommonSkills: boolean, + ): IBaseSkill[] { if (Object.keys(skills ?? []).length === 0) { @@ -384,22 +435,22 @@ export class BotGenerator { return null; } - + // All skills have id and progress props const skillToAdd: IBaseSkill = { Id: skillKey, - Progress: this.randomUtil.getInt(skill.min, skill.max) + Progress: this.randomUtil.getInt(skill.min, skill.max), }; - + // Common skills have additional props if (isCommonSkills) { (skillToAdd as Common).PointsEarnedDuringSession = 0; (skillToAdd as Common).LastAccess = 0; } - + return skillToAdd; - }).filter(x => x !== null); + }).filter((x) => x !== null); } /** @@ -422,7 +473,7 @@ export class BotGenerator const defaultInventory = "55d7217a4bdc2d86028b456d"; const itemsByParentHash: Record = {}; const inventoryItemHash: Record = {}; - + // Generate inventoryItem list let inventoryId = ""; for (const item of profile.Inventory.items) @@ -482,7 +533,9 @@ export class BotGenerator } botInfo.GameVersion = this.weightedRandomHelper.getWeightedValue(this.pmcConfig.gameVersionWeight); - botInfo.MemberCategory = Number.parseInt(this.weightedRandomHelper.getWeightedValue(this.pmcConfig.accountTypeWeight)); + botInfo.MemberCategory = Number.parseInt( + this.weightedRandomHelper.getWeightedValue(this.pmcConfig.accountTypeWeight), + ); } /** @@ -505,8 +558,8 @@ export class BotGenerator KillerAccountId: "Unknown", KillerProfileId: "Unknown", KillerName: "Unknown", - WeaponName: "Unknown" - } + WeaponName: "Unknown", + }, }; const inventoryItem: Item = { @@ -515,11 +568,11 @@ export class BotGenerator parentId: bot.Inventory.equipment, slotId: "Dogtag", location: undefined, - upd: upd + upd: upd, }; bot.Inventory.items.push(inventoryItem); return bot; } -} \ No newline at end of file +} diff --git a/project/src/generators/BotInventoryGenerator.ts b/project/src/generators/BotInventoryGenerator.ts index 6da1395a..e6f49862 100644 --- a/project/src/generators/BotInventoryGenerator.ts +++ b/project/src/generators/BotInventoryGenerator.ts @@ -39,7 +39,7 @@ export class BotInventoryGenerator @inject("LocalisationService") protected localisationService: LocalisationService, @inject("BotEquipmentModPoolService") protected botEquipmentModPoolService: BotEquipmentModPoolService, @inject("BotEquipmentModGenerator") protected botEquipmentModGenerator: BotEquipmentModGenerator, - @inject("ConfigServer") protected configServer: ConfigServer + @inject("ConfigServer") protected configServer: ConfigServer, ) { this.botConfig = this.configServer.getConfig(ConfigTypes.BOT); @@ -54,7 +54,13 @@ export class BotInventoryGenerator * @param botLevel Level of bot being generated * @returns PmcInventory object with equipment/weapons/loot */ - public generateInventory(sessionId: string, botJsonTemplate: IBotType, botRole: string, isPmc: boolean, botLevel: number): PmcInventory + public generateInventory( + sessionId: string, + botJsonTemplate: IBotType, + botRole: string, + isPmc: boolean, + botLevel: number, + ): PmcInventory { const templateInventory = botJsonTemplate.inventory; const equipmentChances = botJsonTemplate.chances; @@ -66,7 +72,16 @@ export class BotInventoryGenerator this.generateAndAddEquipmentToBot(templateInventory, equipmentChances, botRole, botInventory, botLevel); // Roll weapon spawns (primary/secondary/holster) and generate a weapon for each roll that passed - this.generateAndAddWeaponsToBot(templateInventory, equipmentChances, sessionId, botInventory, botRole, isPmc, itemGenerationLimitsMinMax, botLevel); + this.generateAndAddWeaponsToBot( + templateInventory, + equipmentChances, + sessionId, + botInventory, + botRole, + isPmc, + itemGenerationLimitsMinMax, + botLevel, + ); // Pick loot and add to bots containers (rig/backpack/pockets/secure) this.botLootGenerator.generateLoot(sessionId, botJsonTemplate, isPmc, botRole, botInventory, botLevel); @@ -99,24 +114,24 @@ export class BotInventoryGenerator items: [ { _id: equipmentId, - _tpl: equipmentTpl + _tpl: equipmentTpl, }, { _id: stashId, - _tpl: stashTpl + _tpl: stashTpl, }, { _id: questRaidItemsId, - _tpl: questRaidItemsTpl + _tpl: questRaidItemsTpl, }, { _id: questStashItemsId, - _tpl: questStashItemsTpl + _tpl: questStashItemsTpl, }, { _id: sortingTableId, - _tpl: sortingTableTpl - } + _tpl: sortingTableTpl, + }, ], equipment: equipmentId, stash: stashId, @@ -124,7 +139,7 @@ export class BotInventoryGenerator questStashItems: questStashItemsId, sortingTable: sortingTableId, hideoutAreaStashes: {}, - fastPanel: {} + fastPanel: {}, }; } @@ -136,7 +151,13 @@ export class BotInventoryGenerator * @param botInventory Inventory to add equipment to * @param botLevel Level of bot */ - protected generateAndAddEquipmentToBot(templateInventory: Inventory, equipmentChances: Chances, botRole: string, botInventory: PmcInventory, botLevel: number): void + protected generateAndAddEquipmentToBot( + templateInventory: Inventory, + equipmentChances: Chances, + botRole: string, + botInventory: PmcInventory, + botLevel: number, + ): void { // These will be handled later const excludedSlots: string[] = [ @@ -147,7 +168,7 @@ export class BotInventoryGenerator EquipmentSlots.TACTICAL_VEST, EquipmentSlots.FACE_COVER, EquipmentSlots.HEADWEAR, - EquipmentSlots.EARPIECE + EquipmentSlots.EARPIECE, ]; const botEquipConfig = this.botConfig.equipment[this.botGeneratorHelper.getBotEquipmentRole(botRole)]; @@ -155,21 +176,69 @@ export class BotInventoryGenerator for (const equipmentSlot in templateInventory.equipment) { - // Weapons have special generation and will be generated seperately; ArmorVest should be generated after TactivalVest + // Weapons have special generation and will be generated separately; ArmorVest should be generated after TactivalVest if (excludedSlots.includes(equipmentSlot)) { continue; } - this.generateEquipment(equipmentSlot, templateInventory.equipment[equipmentSlot], templateInventory.mods, equipmentChances, botRole, botInventory, randomistionDetails); + this.generateEquipment( + equipmentSlot, + templateInventory.equipment[equipmentSlot], + templateInventory.mods, + equipmentChances, + botRole, + botInventory, + randomistionDetails, + ); } // Generate below in specific order - 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); + 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, + ); } /** @@ -189,14 +258,18 @@ export class BotInventoryGenerator spawnChances: Chances, botRole: string, inventory: PmcInventory, - randomisationDetails: RandomisationDetails): void + randomisationDetails: RandomisationDetails, + ): void { - const spawnChance = ([EquipmentSlots.POCKETS, EquipmentSlots.SECURED_CONTAINER] as string[]).includes(equipmentSlot) - ? 100 - : spawnChances.equipment[equipmentSlot]; + const spawnChance = + ([EquipmentSlots.POCKETS, EquipmentSlots.SECURED_CONTAINER] as string[]).includes(equipmentSlot) ? + 100 : + spawnChances.equipment[equipmentSlot]; if (typeof spawnChance === "undefined") { - this.logger.warning(this.localisationService.getText("bot-no_spawn_chance_defined_for_equipment_slot", equipmentSlot)); + this.logger.warning( + this.localisationService.getText("bot-no_spawn_chance_defined_for_equipment_slot", equipmentSlot), + ); return; } @@ -216,7 +289,13 @@ export class BotInventoryGenerator return; } - if (this.botGeneratorHelper.isItemIncompatibleWithCurrentItems(inventory.items, equipmentItemTpl, equipmentSlot).incompatible) + if ( + this.botGeneratorHelper.isItemIncompatibleWithCurrentItems( + inventory.items, + equipmentItemTpl, + equipmentSlot, + ).incompatible + ) { // Bad luck - randomly picked item was not compatible with current gear return; @@ -227,19 +306,35 @@ export class BotInventoryGenerator _tpl: equipmentItemTpl, parentId: inventory.equipment, slotId: equipmentSlot, - ...this.botGeneratorHelper.generateExtraPropertiesForItem(itemTemplate[1], botRole) + ...this.botGeneratorHelper.generateExtraPropertiesForItem(itemTemplate[1], botRole), }; // use dynamic mod pool if enabled in config const botEquipmentRole = this.botGeneratorHelper.getBotEquipmentRole(botRole); - if (this.botConfig.equipment[botEquipmentRole] && randomisationDetails?.randomisedArmorSlots?.includes(equipmentSlot)) + if ( + this.botConfig.equipment[botEquipmentRole] && + randomisationDetails?.randomisedArmorSlots?.includes(equipmentSlot) + ) { - modPool[equipmentItemTpl] = this.getFilteredDynamicModsForItem(equipmentItemTpl, this.botConfig.equipment[botEquipmentRole].blacklist); + modPool[equipmentItemTpl] = this.getFilteredDynamicModsForItem( + equipmentItemTpl, + this.botConfig.equipment[botEquipmentRole].blacklist, + ); } - if (typeof(modPool[equipmentItemTpl]) !== "undefined" || Object.keys(modPool[equipmentItemTpl] || {}).length > 0) + if ( + typeof (modPool[equipmentItemTpl]) !== "undefined" || + Object.keys(modPool[equipmentItemTpl] || {}).length > 0 + ) { - const items = this.botEquipmentModGenerator.generateModsForEquipment([item], modPool, id, itemTemplate[1], spawnChances.mods, botRole); + const items = this.botEquipmentModGenerator.generateModsForEquipment( + [item], + modPool, + id, + itemTemplate[1], + spawnChances.mods, + botRole, + ); inventory.items.push(...items); } else @@ -251,17 +346,20 @@ export class BotInventoryGenerator /** * Get all possible mods for item and filter down based on equipment blacklist from bot.json config - * @param itemTpl Item mod pool is being retreived and filtered + * @param itemTpl Item mod pool is being retrieved and filtered * @param equipmentBlacklist blacklist to filter mod pool with * @returns Filtered pool of mods */ - protected getFilteredDynamicModsForItem(itemTpl: string, equipmentBlacklist: EquipmentFilterDetails[]): Record + protected getFilteredDynamicModsForItem( + itemTpl: string, + equipmentBlacklist: EquipmentFilterDetails[], + ): Record { const modPool = this.botEquipmentModPoolService.getModsForGearSlot(itemTpl); for (const modSlot of Object.keys(modPool ?? [])) { const blacklistedMods = equipmentBlacklist[0]?.equipment[modSlot] || []; - const filteredMods = modPool[modSlot].filter(x => !blacklistedMods.includes(x)); + const filteredMods = modPool[modSlot].filter((x) => !blacklistedMods.includes(x)); if (filteredMods.length > 0) { @@ -283,7 +381,16 @@ export class BotInventoryGenerator * @param botLevel level of bot having weapon generated * @param itemGenerationLimitsMinMax Limits for items the bot can have */ - protected generateAndAddWeaponsToBot(templateInventory: Inventory, equipmentChances: Chances, sessionId: string, botInventory: PmcInventory, botRole: string, isPmc: boolean, itemGenerationLimitsMinMax: Generation, botLevel: number): void + protected generateAndAddWeaponsToBot( + templateInventory: Inventory, + equipmentChances: Chances, + sessionId: string, + botInventory: PmcInventory, + botRole: string, + isPmc: boolean, + itemGenerationLimitsMinMax: Generation, + botLevel: number, + ): void { const weaponSlotsToFill = this.getDesiredWeaponsForBot(equipmentChances); for (const weaponSlot of weaponSlotsToFill) @@ -291,7 +398,17 @@ export class BotInventoryGenerator // 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) { - this.addWeaponAndMagazinesToInventory(sessionId, weaponSlot, templateInventory, botInventory, equipmentChances, botRole, isPmc, itemGenerationLimitsMinMax, botLevel); + this.addWeaponAndMagazinesToInventory( + sessionId, + weaponSlot, + templateInventory, + botInventory, + equipmentChances, + botRole, + isPmc, + itemGenerationLimitsMinMax, + botLevel, + ); } } } @@ -301,26 +418,27 @@ export class BotInventoryGenerator * @param equipmentChances Chances bot has certain equipment * @returns What slots bot should have weapons generated for */ - protected getDesiredWeaponsForBot(equipmentChances: Chances): { slot: EquipmentSlots; shouldSpawn: boolean; }[] + protected getDesiredWeaponsForBot(equipmentChances: Chances): {slot: EquipmentSlots; shouldSpawn: boolean;}[] { const shouldSpawnPrimary = this.randomUtil.getChance100(equipmentChances.equipment.FirstPrimaryWeapon); return [ { slot: EquipmentSlots.FIRST_PRIMARY_WEAPON, - shouldSpawn: shouldSpawnPrimary + shouldSpawn: shouldSpawnPrimary, }, { slot: EquipmentSlots.SECOND_PRIMARY_WEAPON, - shouldSpawn: shouldSpawnPrimary - ? this.randomUtil.getChance100(equipmentChances.equipment.SecondPrimaryWeapon) - : false + shouldSpawn: shouldSpawnPrimary ? + this.randomUtil.getChance100(equipmentChances.equipment.SecondPrimaryWeapon) : + false, }, { slot: EquipmentSlots.HOLSTER, - shouldSpawn: shouldSpawnPrimary - ? this.randomUtil.getChance100(equipmentChances.equipment.Holster) // Primary weapon = roll for chance at pistol - : true // No primary = force pistol - } + shouldSpawn: shouldSpawnPrimary ? + this.randomUtil.getChance100(equipmentChances.equipment.Holster) // Primary weapon = roll for chance at pistol + : + true, // No primary = force pistol + }, ]; } @@ -333,18 +451,19 @@ export class BotInventoryGenerator * @param equipmentChances Chances bot can have equipment equipped * @param botRole assault/pmcBot/bossTagilla etc * @param isPmc Is the bot being generated as a pmc - * @param itemGenerationWeights + * @param itemGenerationWeights */ protected addWeaponAndMagazinesToInventory( sessionId: string, - weaponSlot: { slot: EquipmentSlots; shouldSpawn: boolean; }, + weaponSlot: {slot: EquipmentSlots; shouldSpawn: boolean;}, templateInventory: Inventory, botInventory: PmcInventory, equipmentChances: Chances, botRole: string, isPmc: boolean, itemGenerationWeights: Generation, - botLevel: number): void + botLevel: number, + ): void { const generatedWeapon = this.botWeaponGenerator.generateRandomWeapon( sessionId, @@ -354,10 +473,16 @@ export class BotInventoryGenerator equipmentChances.mods, botRole, isPmc, - botLevel); + botLevel, + ); botInventory.items.push(...generatedWeapon.weapon); - this.botWeaponGenerator.addExtraMagazinesToInventory(generatedWeapon, itemGenerationWeights.items.magazines, botInventory, botRole); + this.botWeaponGenerator.addExtraMagazinesToInventory( + generatedWeapon, + itemGenerationWeights.items.magazines, + botInventory, + botRole, + ); } -} \ No newline at end of file +} diff --git a/project/src/generators/BotLevelGenerator.ts b/project/src/generators/BotLevelGenerator.ts index cc205912..8b2be669 100644 --- a/project/src/generators/BotLevelGenerator.ts +++ b/project/src/generators/BotLevelGenerator.ts @@ -15,9 +15,9 @@ export class BotLevelGenerator constructor( @inject("WinstonLogger") protected logger: ILogger, @inject("RandomUtil") protected randomUtil: RandomUtil, - @inject("DatabaseServer") protected databaseServer: DatabaseServer + @inject("DatabaseServer") protected databaseServer: DatabaseServer, ) - { } + {} /** * Return a randomised bot level and exp value @@ -26,12 +26,20 @@ export class BotLevelGenerator * @param bot being level is being generated for * @returns IRandomisedBotLevelResult object */ - // eslint-disable-next-line @typescript-eslint/no-unused-vars - public generateBotLevel(levelDetails: MinMax, botGenerationDetails: BotGenerationDetails, bot: IBotBase): IRandomisedBotLevelResult + public generateBotLevel( + levelDetails: MinMax, + botGenerationDetails: BotGenerationDetails, + bot: IBotBase, + ): IRandomisedBotLevelResult { const expTable = this.databaseServer.getTables().globals.config.exp.level.exp_table; - const highestLevel = this.getHighestRelativeBotLevel(botGenerationDetails.playerLevel, botGenerationDetails.botRelativeLevelDeltaMax, levelDetails, expTable); - + const highestLevel = this.getHighestRelativeBotLevel( + botGenerationDetails.playerLevel, + botGenerationDetails.botRelativeLevelDeltaMax, + levelDetails, + expTable, + ); + // Get random level based on the exp table. let exp = 0; const level = this.randomUtil.getInt(1, highestLevel); @@ -47,16 +55,21 @@ export class BotLevelGenerator exp += this.randomUtil.getInt(0, expTable[level].exp - 1); } - return { level, exp }; + return {level, exp}; } /** - * Get the highest level a bot can be relative to the players level, but no futher than the max size from globals.exp_table + * Get the highest level a bot can be relative to the players level, but no further than the max size from globals.exp_table * @param playerLevel Players current level * @param relativeDeltaMax max delta above player level to go * @returns highest level possible for bot */ - protected getHighestRelativeBotLevel(playerLevel: number, relativeDeltaMax: number, levelDetails: MinMax, expTable: IExpTable[]): number + protected getHighestRelativeBotLevel( + playerLevel: number, + relativeDeltaMax: number, + levelDetails: MinMax, + expTable: IExpTable[], + ): number { const maxPossibleLevel = Math.min(levelDetails.max, expTable.length); @@ -68,4 +81,4 @@ export class BotLevelGenerator return level; } -} \ No newline at end of file +} diff --git a/project/src/generators/BotLootGenerator.ts b/project/src/generators/BotLootGenerator.ts index e3b6594f..4125fd5c 100644 --- a/project/src/generators/BotLootGenerator.ts +++ b/project/src/generators/BotLootGenerator.ts @@ -30,7 +30,7 @@ export class BotLootGenerator { protected botConfig: IBotConfig; protected pmcConfig: IPmcConfig; - + constructor( @inject("WinstonLogger") protected logger: ILogger, @inject("HashUtil") protected hashUtil: HashUtil, @@ -44,7 +44,7 @@ export class BotLootGenerator @inject("WeightedRandomHelper") protected weightedRandomHelper: WeightedRandomHelper, @inject("BotLootCacheService") protected botLootCacheService: BotLootCacheService, @inject("LocalisationService") protected localisationService: LocalisationService, - @inject("ConfigServer") protected configServer: ConfigServer + @inject("ConfigServer") protected configServer: ConfigServer, ) { this.botConfig = this.configServer.getConfig(ConfigTypes.BOT); @@ -60,15 +60,24 @@ export class BotLootGenerator * @param botInventory Inventory to add loot to * @param botLevel Level of bot */ - public generateLoot(sessionId: string, botJsonTemplate: IBotType, isPmc: boolean, botRole: string, botInventory: PmcInventory, botLevel: number): void + public generateLoot( + sessionId: string, + botJsonTemplate: IBotType, + isPmc: boolean, + botRole: string, + botInventory: PmcInventory, + botLevel: number, + ): void { // Limits on item types to be added as loot const itemCounts = botJsonTemplate.generation.items; - + const backpackLootCount = this.weightedRandomHelper.getWeightedValue(itemCounts.backpackLoot.weights); const pocketLootCount = this.weightedRandomHelper.getWeightedValue(itemCounts.pocketLoot.weights); const vestLootCount = this.weightedRandomHelper.getWeightedValue(itemCounts.vestLoot.weights); - const specialLootItemCount = this.weightedRandomHelper.getWeightedValue(itemCounts.specialItems.weights); + const specialLootItemCount = this.weightedRandomHelper.getWeightedValue( + itemCounts.specialItems.weights, + ); const healingItemCount = this.weightedRandomHelper.getWeightedValue(itemCounts.healing.weights); const drugItemCount = this.weightedRandomHelper.getWeightedValue(itemCounts.drugs.weights); const stimItemCount = this.weightedRandomHelper.getWeightedValue(itemCounts.stims.weights); @@ -88,7 +97,8 @@ export class BotLootGenerator containersBotHasAvailable, specialLootItemCount, botInventory, - botRole); + botRole, + ); // Healing items / Meds this.addLootFromPool( @@ -99,7 +109,8 @@ export class BotLootGenerator botRole, false, 0, - isPmc); + isPmc, + ); // Drugs this.addLootFromPool( @@ -110,7 +121,8 @@ export class BotLootGenerator botRole, false, 0, - isPmc); + isPmc, + ); // Stims this.addLootFromPool( @@ -121,7 +133,8 @@ export class BotLootGenerator botRole, true, 0, - isPmc); + isPmc, + ); // Grenades this.addLootFromPool( @@ -132,8 +145,8 @@ export class BotLootGenerator botRole, false, 0, - isPmc); - + isPmc, + ); // Backpack - generate loot if they have one if (containersBotHasAvailable.includes(EquipmentSlots.BACKPACK)) @@ -141,7 +154,16 @@ export class BotLootGenerator // Add randomly generated weapon to PMC backpacks if (isPmc && this.randomUtil.getChance100(this.pmcConfig.looseWeaponInBackpackChancePercent)) { - this.addLooseWeaponsToInventorySlot(sessionId, botInventory, EquipmentSlots.BACKPACK, botJsonTemplate.inventory, botJsonTemplate.chances.mods, botRole, isPmc, botLevel); + this.addLooseWeaponsToInventorySlot( + sessionId, + botInventory, + EquipmentSlots.BACKPACK, + botJsonTemplate.inventory, + botJsonTemplate.chances.mods, + botRole, + isPmc, + botLevel, + ); } this.addLootFromPool( @@ -152,9 +174,10 @@ export class BotLootGenerator botRole, true, this.pmcConfig.maxBackpackLootTotalRub, - isPmc); + isPmc, + ); } - + // TacticalVest - generate loot if they have one if (containersBotHasAvailable.includes(EquipmentSlots.TACTICAL_VEST)) { @@ -167,10 +190,10 @@ export class BotLootGenerator botRole, true, this.pmcConfig.maxVestLootTotalRub, - isPmc); + isPmc, + ); } - // Pockets this.addLootFromPool( this.botLootCacheService.getLootFromCache(botRole, isPmc, LootCacheType.POCKET, botJsonTemplate), @@ -180,7 +203,8 @@ export class BotLootGenerator botRole, true, this.pmcConfig.maxPocketLootTotalRub, - isPmc); + isPmc, + ); } /** @@ -192,13 +216,12 @@ export class BotLootGenerator { const result = [EquipmentSlots.POCKETS]; - if (botInventory.items.find(x => x.slotId === EquipmentSlots.TACTICAL_VEST)) + if (botInventory.items.find((x) => x.slotId === EquipmentSlots.TACTICAL_VEST)) { - result.push(EquipmentSlots.TACTICAL_VEST); } - if (botInventory.items.find(x => x.slotId === EquipmentSlots.BACKPACK)) + if (botInventory.items.find((x) => x.slotId === EquipmentSlots.BACKPACK)) { result.push(EquipmentSlots.BACKPACK); } @@ -222,7 +245,8 @@ export class BotLootGenerator botRole, false, 0, - true); + true, + ); const surv12 = this.itemHelper.getItem("5d02797c86f774203f38e30a")[1]; this.addLootFromPool( @@ -233,7 +257,8 @@ export class BotLootGenerator botRole, false, 0, - true); + true, + ); const morphine = this.itemHelper.getItem("544fb3f34bdc2d03748b456a")[1]; this.addLootFromPool( @@ -244,7 +269,8 @@ export class BotLootGenerator botRole, false, 0, - true); + true, + ); const afak = this.itemHelper.getItem("60098ad7c2240c0fe85c570a")[1]; this.addLootFromPool( @@ -255,7 +281,8 @@ export class BotLootGenerator botRole, false, 0, - true); + true, + ); } /** @@ -290,14 +317,15 @@ export class BotLootGenerator botRole: string, useLimits = false, totalValueLimitRub = 0, - isPmc = false): void + isPmc = false, + ): void { // Loot pool has items if (pool.length) { let currentTotalRub = 0; const itemLimits: Record = {}; - const itemSpawnLimits: Record> = {}; + const itemSpawnLimits: Record> = {}; let fitItemIntoContainerAttempts = 0; for (let i = 0; i < totalItemCount; i++) { @@ -306,7 +334,7 @@ export class BotLootGenerator const itemsToAdd: Item[] = [{ _id: id, _tpl: itemToAddTemplate._id, - ...this.botGeneratorHelper.generateExtraPropertiesForItem(itemToAddTemplate, botRole) + ...this.botGeneratorHelper.generateExtraPropertiesForItem(itemToAddTemplate, botRole), }]; if (useLimits) @@ -321,11 +349,19 @@ export class BotLootGenerator itemSpawnLimits[botRole] = this.getItemSpawnLimitsForBotType(isPmc, botRole); } - if (this.itemHasReachedSpawnLimit(itemToAddTemplate, botRole, isPmc, itemLimits, itemSpawnLimits[botRole])) + if ( + this.itemHasReachedSpawnLimit( + itemToAddTemplate, + botRole, + isPmc, + itemLimits, + itemSpawnLimits[botRole], + ) + ) { i--; continue; - } + } } // Fill ammo box @@ -345,13 +381,21 @@ export class BotLootGenerator } // Attempt to add item to container(s) - const itemAddedResult = this.botWeaponGeneratorHelper.addItemWithChildrenToEquipmentSlot(equipmentSlots, id, itemToAddTemplate._id, itemsToAdd, inventoryToAddItemsTo); + const itemAddedResult = this.botWeaponGeneratorHelper.addItemWithChildrenToEquipmentSlot( + equipmentSlots, + id, + itemToAddTemplate._id, + itemsToAdd, + inventoryToAddItemsTo, + ); if (itemAddedResult === ItemAddedResult.NO_SPACE) { fitItemIntoContainerAttempts++; if (fitItemIntoContainerAttempts >= 4) { - this.logger.debug(`Failed to place item ${i} of ${totalItemCount} item into ${botRole} container: ${equipmentSlots}, ${fitItemIntoContainerAttempts} times, skipping`); + this.logger.debug( + `Failed to place item ${i} of ${totalItemCount} item into ${botRole} container: ${equipmentSlots}, ${fitItemIntoContainerAttempts} times, skipping`, + ); break; } @@ -383,16 +427,48 @@ export class BotLootGenerator * @param botRole bots role .e.g. pmcBot * @param isPmc are we generating for a pmc */ - protected addLooseWeaponsToInventorySlot(sessionId: string, botInventory: PmcInventory, equipmentSlot: string, templateInventory: Inventory, modChances: ModsChances, botRole: string, isPmc: boolean, botLevel: number): void + protected addLooseWeaponsToInventorySlot( + sessionId: string, + botInventory: PmcInventory, + equipmentSlot: string, + templateInventory: Inventory, + modChances: ModsChances, + botRole: string, + isPmc: boolean, + botLevel: number, + ): void { - const chosenWeaponType = this.randomUtil.getArrayValue([EquipmentSlots.FIRST_PRIMARY_WEAPON, EquipmentSlots.FIRST_PRIMARY_WEAPON, EquipmentSlots.FIRST_PRIMARY_WEAPON, EquipmentSlots.HOLSTER]); - const randomisedWeaponCount = this.randomUtil.getInt(this.pmcConfig.looseWeaponInBackpackLootMinMax.min, this.pmcConfig.looseWeaponInBackpackLootMinMax.max); + const chosenWeaponType = this.randomUtil.getArrayValue([ + EquipmentSlots.FIRST_PRIMARY_WEAPON, + EquipmentSlots.FIRST_PRIMARY_WEAPON, + EquipmentSlots.FIRST_PRIMARY_WEAPON, + EquipmentSlots.HOLSTER, + ]); + const randomisedWeaponCount = this.randomUtil.getInt( + this.pmcConfig.looseWeaponInBackpackLootMinMax.min, + this.pmcConfig.looseWeaponInBackpackLootMinMax.max, + ); if (randomisedWeaponCount > 0) { for (let i = 0; i < randomisedWeaponCount; i++) { - const generatedWeapon = this.botWeaponGenerator.generateRandomWeapon(sessionId, chosenWeaponType, templateInventory, botInventory.equipment, modChances, botRole, isPmc, botLevel); - this.botWeaponGeneratorHelper.addItemWithChildrenToEquipmentSlot([equipmentSlot], generatedWeapon.weapon[0]._id, generatedWeapon.weapon[0]._tpl, [...generatedWeapon.weapon], botInventory); + const generatedWeapon = this.botWeaponGenerator.generateRandomWeapon( + sessionId, + chosenWeaponType, + templateInventory, + botInventory.equipment, + modChances, + botRole, + isPmc, + botLevel, + ); + this.botWeaponGeneratorHelper.addItemWithChildrenToEquipmentSlot( + [equipmentSlot], + generatedWeapon.weapon[0]._id, + generatedWeapon.weapon[0]._tpl, + [...generatedWeapon.weapon], + botInventory, + ); } } } @@ -405,7 +481,12 @@ export class BotLootGenerator */ protected getRandomItemFromPoolByRole(pool: ITemplateItem[], botRole: string): ITemplateItem { - const itemIndex = this.randomUtil.getBiasedRandomNumber(0, pool.length - 1, pool.length - 1, this.getBotLootNValueByRole(botRole)); + const itemIndex = this.randomUtil.getBiasedRandomNumber( + 0, + pool.length - 1, + pool.length - 1, + this.getBotLootNValueByRole(botRole), + ); return pool[itemIndex]; } @@ -432,7 +513,7 @@ export class BotLootGenerator * All values are set to 0 * @param isPmc Is the bot a pmc * @param botRole Role the bot has - * @param limitCount + * @param limitCount */ protected initItemLimitArray(isPmc: boolean, botRole: string, limitCount: Record): void { @@ -443,7 +524,7 @@ export class BotLootGenerator limitCount[limit] = 0; } } - + /** * Check if an item has reached its bot-specific spawn limit * @param itemTemplate Item we check to see if its reached spawn limit @@ -453,7 +534,13 @@ export class BotLootGenerator * @param itemSpawnLimits The limits this bot is allowed to have * @returns true if item has reached spawn limit */ - protected itemHasReachedSpawnLimit(itemTemplate: ITemplateItem, botRole: string, isPmc: boolean, limitCount: Record, itemSpawnLimits: Record): boolean + protected itemHasReachedSpawnLimit( + itemTemplate: ITemplateItem, + botRole: string, + isPmc: boolean, + limitCount: Record, + itemSpawnLimits: Record, + ): boolean { // PMCs and scavs have different sections of bot config for spawn limits if (!!itemSpawnLimits && itemSpawnLimits.length === 0) @@ -484,7 +571,13 @@ export class BotLootGenerator // Prevent edge-case of small loot pools + code trying to add limited item over and over infinitely if (limitCount[idToCheckFor] > itemSpawnLimits[idToCheckFor] * 10) { - this.logger.debug(this.localisationService.getText("bot-item_spawn_limit_reached_skipping_item", {botRole: botRole, itemName: itemTemplate._name, attempts: limitCount[idToCheckFor]})); + this.logger.debug( + this.localisationService.getText("bot-item_spawn_limit_reached_skipping_item", { + botRole: botRole, + itemName: itemTemplate._name, + attempts: limitCount[idToCheckFor], + }), + ); return false; } @@ -505,9 +598,9 @@ export class BotLootGenerator { // PMCs have a different stack max size const minStackSize = itemTemplate._props.StackMinRandom; - const maxStackSize = (isPmc) - ? this.pmcConfig.dynamicLoot.moneyStackLimits[itemTemplate._id] - : itemTemplate._props.StackMaxRandom; + const maxStackSize = isPmc ? + this.pmcConfig.dynamicLoot.moneyStackLimits[itemTemplate._id] : + itemTemplate._props.StackMaxRandom; const randomSize = this.randomUtil.getInt(minStackSize, maxStackSize); if (!moneyItem.upd) @@ -515,7 +608,7 @@ export class BotLootGenerator moneyItem.upd = {}; } - moneyItem.upd.StackObjectsCount = randomSize; + moneyItem.upd.StackObjectsCount = randomSize; } /** @@ -526,16 +619,16 @@ export class BotLootGenerator */ protected randomiseAmmoStackSize(isPmc: boolean, itemTemplate: ITemplateItem, ammoItem: Item): void { - const randomSize = itemTemplate._props.StackMaxSize === 1 - ? 1 - : this.randomUtil.getInt(itemTemplate._props.StackMinRandom, itemTemplate._props.StackMaxRandom); + const randomSize = itemTemplate._props.StackMaxSize === 1 ? + 1 : + this.randomUtil.getInt(itemTemplate._props.StackMinRandom, itemTemplate._props.StackMaxRandom); if (!ammoItem.upd) { ammoItem.upd = {}; } - ammoItem.upd.StackObjectsCount = randomSize ; + ammoItem.upd.StackObjectsCount = randomSize; } /** @@ -557,7 +650,9 @@ export class BotLootGenerator return this.botConfig.itemSpawnLimits[botRole.toLowerCase()]; } - this.logger.warning(this.localisationService.getText("bot-unable_to_find_spawn_limits_fallback_to_defaults", botRole)); + this.logger.warning( + this.localisationService.getText("bot-unable_to_find_spawn_limits_fallback_to_defaults", botRole), + ); return this.botConfig.itemSpawnLimits["default"]; } @@ -570,7 +665,6 @@ export class BotLootGenerator */ protected getMatchingIdFromSpawnLimits(itemTemplate: ITemplateItem, spawnLimits: Record): string { - if (itemTemplate._id in spawnLimits) { return itemTemplate._id; @@ -585,4 +679,4 @@ export class BotLootGenerator // parentId and tplid not found return undefined; } -} \ No newline at end of file +} diff --git a/project/src/generators/BotWeaponGenerator.ts b/project/src/generators/BotWeaponGenerator.ts index b3010744..db97c19f 100644 --- a/project/src/generators/BotWeaponGenerator.ts +++ b/project/src/generators/BotWeaponGenerator.ts @@ -51,7 +51,7 @@ export class BotWeaponGenerator @inject("BotEquipmentModGenerator") protected botEquipmentModGenerator: BotEquipmentModGenerator, @inject("LocalisationService") protected localisationService: LocalisationService, @inject("RepairService") protected repairService: RepairService, - @injectAll("InventoryMagGen") protected inventoryMagGenComponents: IInventoryMagGen[] + @injectAll("InventoryMagGen") protected inventoryMagGenComponents: IInventoryMagGen[], ) { this.botConfig = this.configServer.getConfig(ConfigTypes.BOT); @@ -64,16 +64,35 @@ export class BotWeaponGenerator * 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 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 + 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); + return this.generateWeaponByTpl( + sessionId, + weaponTpl, + equipmentSlot, + botTemplateInventory, + weaponParentId, + modChances, + botRole, + isPmc, + botLevel, + ); } /** @@ -99,8 +118,17 @@ export class BotWeaponGenerator * @param isPmc Is weapon being generated for a pmc * @returns GenerateWeaponResult object */ - // eslint-disable-next-line @typescript-eslint/no-unused-vars - public generateWeaponByTpl(sessionId: string, weaponTpl: string, equipmentSlot: string, botTemplateInventory: Inventory, weaponParentId: string, modChances: ModsChances, botRole: string, isPmc: boolean, botLevel: number): GenerateWeaponResult + 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]; @@ -123,7 +151,13 @@ export class BotWeaponGenerator const ammoTpl = this.getWeightedCompatibleAmmo(botTemplateInventory.Ammo, weaponItemTemplate); // Create with just base weapon item - let weaponWithModsArray = this.constructWeaponBaseArray(weaponTpl, weaponParentId, equipmentSlot, weaponItemTemplate, botRole); + let weaponWithModsArray = this.constructWeaponBaseArray( + weaponTpl, + weaponParentId, + equipmentSlot, + weaponItemTemplate, + botRole, + ); // Chance to add randomised weapon enhancement if (isPmc && this.randomUtil.getChance100(this.pmcConfig.weaponHasEnhancementChancePercent)) @@ -137,32 +171,52 @@ export class BotWeaponGenerator { 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); + 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); + 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)) + for (const magazine of weaponWithModsArray.filter((x) => x.slotId === this.modMagazineSlotId)) { this.fillExistingMagazines(weaponWithModsArray, magazine, ammoTpl); } // Add cartridge to gun chamber if weapon has slot for it - if (weaponItemTemplate._props.Chambers?.length === 1 - && weaponItemTemplate._props.Chambers[0]?._name === "patron_in_weapon" - && weaponItemTemplate._props.Chambers[0]?._props?.filters[0]?.Filter?.includes(ammoTpl)) + if ( + weaponItemTemplate._props.Chambers?.length === 1 && + weaponItemTemplate._props.Chambers[0]?._name === "patron_in_weapon" && + weaponItemTemplate._props.Chambers[0]?._props?.filters[0]?.Filter?.includes(ammoTpl) + ) { this.addCartridgeToChamber(weaponWithModsArray, ammoTpl, "patron_in_weapon"); } // Fill UBGL if found - const ubglMod = weaponWithModsArray.find(x => x.slotId === "mod_launcher"); + const ubglMod = weaponWithModsArray.find((x) => x.slotId === "mod_launcher"); let ubglAmmoTpl: string = undefined; if (ubglMod) { @@ -176,7 +230,7 @@ export class BotWeaponGenerator chosenAmmoTpl: ammoTpl, chosenUbglAmmoTpl: ubglAmmoTpl, weaponMods: modPool, - weaponTemplate: weaponItemTemplate + weaponTemplate: weaponItemTemplate, }; } @@ -189,7 +243,7 @@ export class BotWeaponGenerator protected addCartridgeToChamber(weaponWithModsArray: Item[], ammoTpl: string, desiredSlotId: string): void { // Check for slot first - const existingItemWithSlot = weaponWithModsArray.find(x => x.slotId === desiredSlotId); + const existingItemWithSlot = weaponWithModsArray.find((x) => x.slotId === desiredSlotId); if (!existingItemWithSlot) { // Not found, add fresh @@ -198,14 +252,14 @@ export class BotWeaponGenerator _tpl: ammoTpl, parentId: weaponWithModsArray[0]._id, slotId: desiredSlotId, - upd: {StackObjectsCount: 1} + upd: {StackObjectsCount: 1}, }); } else { // Already exists, update values existingItemWithSlot.upd = { - StackObjectsCount: 1 + StackObjectsCount: 1, }; existingItemWithSlot._tpl = ammoTpl; } @@ -216,19 +270,25 @@ export class BotWeaponGenerator * 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 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[] + 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) + ...this.botGeneratorHelper.generateExtraPropertiesForItem(weaponItemTemplate, botRole), }]; } @@ -239,10 +299,18 @@ export class BotWeaponGenerator * @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[] + 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)); + 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 @@ -260,11 +328,12 @@ export class BotWeaponGenerator { const parentItem = preset._items[0]; preset._items[0] = { - ...parentItem, ...{ + ...parentItem, + ...{ parentId: weaponParentId, slotId: equipmentSlot, - ...this.botGeneratorHelper.generateExtraPropertiesForItem(itemTemplate, botRole) - } + ...this.botGeneratorHelper.generateExtraPropertiesForItem(itemTemplate, botRole), + }, }; weaponMods.push(...preset._items); } @@ -304,17 +373,30 @@ export class BotWeaponGenerator const allowedTpls = modSlot._props.filters[0].Filter; const slotName = modSlot._name; - const weaponSlotItem = weaponItemArray.find(x => x.parentId === mod._id && x.slotId === slotName); + 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})); + 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})); + this.logger.warning( + this.localisationService.getText("bot-weapon_contains_invalid_item", { + modSlot: modSlot._name, + modName: modDbTemplate._name, + weaponTpl: weaponSlotItem._tpl, + }), + ); return false; } @@ -332,12 +414,17 @@ export class BotWeaponGenerator * @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 + 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) { @@ -349,7 +436,9 @@ export class BotWeaponGenerator const ammoTemplate = this.itemHelper.getItem(generatedWeaponResult.chosenAmmoTpl)[1]; if (!ammoTemplate) { - this.logger.error(this.localisationService.getText("bot-unable_to_find_ammo_item", generatedWeaponResult.chosenAmmoTpl)); + this.logger.error( + this.localisationService.getText("bot-unable_to_find_ammo_item", generatedWeaponResult.chosenAmmoTpl), + ); return; } @@ -360,11 +449,24 @@ export class BotWeaponGenerator this.addUbglGrenadesToBotInventory(weaponAndMods, generatedWeaponResult, inventory); } - const inventoryMagGenModel = new InventoryMagGen(magWeights, magTemplate, weaponTemplate, ammoTemplate, inventory); - this.inventoryMagGenComponents.find(v => v.canHandleInventoryMagGen(inventoryMagGenModel)).process(inventoryMagGenModel); + 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); + this.addAmmoToSecureContainer( + this.botConfig.secureContainerAmmoStackCount, + generatedWeaponResult.chosenAmmoTpl, + ammoTemplate._props.StackMaxSize, + inventory, + ); } /** @@ -373,25 +475,37 @@ export class BotWeaponGenerator * @param generatedWeaponResult result of weapon generation * @param inventory bot inventory to add grenades to */ - protected addUbglGrenadesToBotInventory(weaponMods: Item[], generatedWeaponResult: GenerateWeaponResult, inventory: PmcInventory): void + 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 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 = { + const ubglMinMax: GenerationData = { // eslint-disable-next-line @typescript-eslint/naming-convention weights: {"1": 1, "2": 1}, - whitelist: [] + 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); + 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); @@ -404,17 +518,27 @@ export class BotWeaponGenerator * @param stackSize Size of the ammo stack to add * @param inventory Player inventory */ - protected addAmmoToSecureContainer(stackCount: number, ammoTpl: string, stackSize: number, inventory: PmcInventory): void + 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); + this.botWeaponGeneratorHelper.addItemWithChildrenToEquipmentSlot( + [EquipmentSlots.SECURED_CONTAINER], + id, + ammoTpl, + [{ + _id: id, + _tpl: ammoTpl, + upd: {StackObjectsCount: stackSize}, + }], + inventory, + ); } } @@ -425,9 +549,13 @@ export class BotWeaponGenerator * @param botRole the bot type we are getting the magazine for * @returns magazine tpl string */ - protected getMagazineTplFromWeaponTemplate(weaponMods: Item[], weaponTemplate: ITemplateItem, botRole: string): string + protected getMagazineTplFromWeaponTemplate( + weaponMods: Item[], + weaponTemplate: ITemplateItem, + botRole: string, + ): string { - const magazine = weaponMods.find(m => m.slotId === this.modMagazineSlotId); + const magazine = weaponMods.find((m) => m.slotId === this.modMagazineSlotId); if (!magazine) { // Edge case - magazineless chamber loaded weapons dont have magazines, e.g. mp18 @@ -441,11 +569,15 @@ export class BotWeaponGenerator if (!weaponTemplate._props.isChamberLoad) { // Shouldn't happen - this.logger.warning(this.localisationService.getText("bot-weapon_missing_magazine_or_chamber", weaponTemplate._id)); + this.logger.warning( + this.localisationService.getText("bot-weapon_missing_magazine_or_chamber", weaponTemplate._id), + ); } 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}.`); + this.logger.debug( + `[${botRole}] Unable to find magazine for weapon ${weaponTemplate._id} ${weaponTemplate._name}, using mag template default ${defaultMagTplId}.`, + ); return defaultMagTplId; } @@ -459,23 +591,42 @@ export class BotWeaponGenerator * @param weaponTemplate the weapon we want to pick ammo for * @returns an ammo tpl that works with the desired gun */ - protected getWeightedCompatibleAmmo(ammo: Record>, weaponTemplate: ITemplateItem): string + protected getWeightedCompatibleAmmo( + ammo: Record>, + weaponTemplate: ITemplateItem, + ): string { const desiredCaliber = this.getWeaponCaliber(weaponTemplate); const compatibleCartridges = 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})); + 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; } const chosenAmmoTpl = this.weightedRandomHelper.getWeightedValue(compatibleCartridges); - if (weaponTemplate._props.Chambers[0] && !weaponTemplate._props.Chambers[0]._props.filters[0].Filter.includes(chosenAmmoTpl)) + if ( + weaponTemplate._props.Chambers[0] && + !weaponTemplate._props.Chambers[0]._props.filters[0].Filter.includes(chosenAmmoTpl) + ) { - 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})); + 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, + }), + ); // Incompatible ammo found, return default (can happen with .366 and 7.62x39 weapons) return weaponTemplate._props.defAmmo; @@ -503,7 +654,9 @@ export class BotWeaponGenerator if (weaponTemplate._props.LinkedWeapon) { - const ammoInChamber = this.itemHelper.getItem(weaponTemplate._props.Chambers[0]._props.filters[0].Filter[0]); + const ammoInChamber = this.itemHelper.getItem( + weaponTemplate._props.Chambers[0]._props.filters[0].Filter[0], + ); if (!ammoInChamber[0]) { return; @@ -559,9 +712,9 @@ export class BotWeaponGenerator parentId: ubglMod._id, slotId: "patron_in_weapon", upd: { - StackObjectsCount: 1 - } - } + StackObjectsCount: 1, + }, + }, ); } @@ -573,9 +726,16 @@ export class BotWeaponGenerator * @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 + protected addOrUpdateMagazinesChildWithAmmo( + weaponWithMods: Item[], + magazine: Item, + chosenAmmoTpl: string, + magazineTemplate: ITemplateItem, + ): void { - const magazineCartridgeChildItem = weaponWithMods.find(m => m.parentId === magazine._id && m.slotId === "cartridges"); + const magazineCartridgeChildItem = weaponWithMods.find((m) => + m.parentId === magazine._id && m.slotId === "cartridges" + ); if (magazineCartridgeChildItem) { // Delete the existing cartridge object and create fresh below @@ -603,7 +763,7 @@ export class BotWeaponGenerator // 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")); + const camoras = weaponMods.filter((x) => x.parentId === magazineId && x.slotId.startsWith("camora")); for (const camora of camoras) { camora._tpl = ammoTpl; @@ -613,8 +773,8 @@ export class BotWeaponGenerator } else { - camora.upd = { StackObjectsCount: 1 }; + camora.upd = {StackObjectsCount: 1}; } } } -} \ No newline at end of file +} diff --git a/project/src/generators/FenceBaseAssortGenerator.ts b/project/src/generators/FenceBaseAssortGenerator.ts index 3e0e43a2..905c7fa9 100644 --- a/project/src/generators/FenceBaseAssortGenerator.ts +++ b/project/src/generators/FenceBaseAssortGenerator.ts @@ -27,7 +27,7 @@ export class FenceBaseAssortGenerator @inject("ItemHelper") protected itemHelper: ItemHelper, @inject("ItemFilterService") protected itemFilterService: ItemFilterService, @inject("SeasonalEventService") protected seasonalEventService: SeasonalEventService, - @inject("ConfigServer") protected configServer: ConfigServer + @inject("ConfigServer") protected configServer: ConfigServer, ) { this.traderConfig = this.configServer.getConfig(ConfigTypes.TRADER); @@ -43,7 +43,7 @@ export class FenceBaseAssortGenerator const baseFenceAssort = this.databaseServer.getTables().traders[Traders.FENCE].assort; const dbItems = Object.values(this.databaseServer.getTables().templates.items); - for (const item of dbItems.filter(x => this.isValidFenceItem(x))) + for (const item of dbItems.filter((x) => this.isValidFenceItem(x))) { // Skip blacklisted items if (this.itemFilterService.isItemBlacklisted(item._id)) @@ -65,8 +65,10 @@ export class FenceBaseAssortGenerator // Skip items on fence ignore list if (this.traderConfig.fence.blacklist.length > 0) { - if (this.traderConfig.fence.blacklist.includes(item._id) - || this.itemHelper.isOfBaseclasses(item._id, this.traderConfig.fence.blacklist)) + if ( + this.traderConfig.fence.blacklist.includes(item._id) || + this.itemHelper.isOfBaseclasses(item._id, this.traderConfig.fence.blacklist) + ) { continue; } @@ -80,8 +82,10 @@ export class FenceBaseAssortGenerator // Create barter scheme object const barterSchemeToAdd: IBarterScheme = { - count: Math.round(this.handbookHelper.getTemplatePrice(item._id) * this.traderConfig.fence.itemPriceMult), - _tpl: Money.ROUBLES + count: Math.round( + this.handbookHelper.getTemplatePrice(item._id) * this.traderConfig.fence.itemPriceMult, + ), + _tpl: Money.ROUBLES, }; // Add barter data to base @@ -95,8 +99,8 @@ export class FenceBaseAssortGenerator slotId: "hideout", upd: { StackObjectsCount: 9999999, - UnlimitedCount: true - } + UnlimitedCount: true, + }, }; // Add item to base @@ -121,4 +125,4 @@ export class FenceBaseAssortGenerator return false; } -} \ No newline at end of file +} diff --git a/project/src/generators/LocationGenerator.ts b/project/src/generators/LocationGenerator.ts index af25d0d3..a43d738f 100644 --- a/project/src/generators/LocationGenerator.ts +++ b/project/src/generators/LocationGenerator.ts @@ -6,9 +6,14 @@ import { PresetHelper } from "@spt-aki/helpers/PresetHelper"; import { RagfairServerHelper } from "@spt-aki/helpers/RagfairServerHelper"; import { IContainerMinMax, IStaticContainer } from "@spt-aki/models/eft/common/ILocation"; import { ILocationBase } from "@spt-aki/models/eft/common/ILocationBase"; -import { ILooseLoot, Spawnpoint, SpawnpointTemplate, SpawnpointsForced } from "@spt-aki/models/eft/common/ILooseLoot"; +import { ILooseLoot, Spawnpoint, SpawnpointsForced, SpawnpointTemplate } from "@spt-aki/models/eft/common/ILooseLoot"; import { Item } from "@spt-aki/models/eft/common/tables/IItem"; -import { IStaticAmmoDetails, IStaticContainerData, IStaticForcedProps, IStaticLootDetails } from "@spt-aki/models/eft/common/tables/ILootBase"; +import { + IStaticAmmoDetails, + IStaticContainerData, + IStaticForcedProps, + IStaticLootDetails, +} from "@spt-aki/models/eft/common/tables/ILootBase"; import { BaseClasses } from "@spt-aki/models/enums/BaseClasses"; import { ConfigTypes } from "@spt-aki/models/enums/ConfigTypes"; import { Money } from "@spt-aki/models/enums/Money"; @@ -25,17 +30,17 @@ import { ProbabilityObject, ProbabilityObjectArray, RandomUtil } from "@spt-aki/ export interface IContainerItem { - items: Item[] - width: number - height: number + items: Item[]; + width: number; + height: number; } export interface IContainerGroupCount { /** Containers this group has + probabilty to spawn */ - containerIdsWithProbability: Record + containerIdsWithProbability: Record; /** How many containers the map should spawn with this group id */ - chosenCount: number + chosenCount: number; } @injectable() @@ -56,7 +61,7 @@ export class LocationGenerator @inject("ContainerHelper") protected containerHelper: ContainerHelper, @inject("PresetHelper") protected presetHelper: PresetHelper, @inject("LocalisationService") protected localisationService: LocalisationService, - @inject("ConfigServer") protected configServer: ConfigServer + @inject("ConfigServer") protected configServer: ConfigServer, ) { this.locationConfig = this.configServer.getConfig(ConfigTypes.LOCATION); @@ -68,7 +73,10 @@ export class LocationGenerator * @param staticAmmoDist Static ammo distribution - database.loot.staticAmmo * @returns Array of container objects */ - public generateStaticContainers(locationBase: ILocationBase, staticAmmoDist: Record): SpawnpointTemplate[] + public generateStaticContainers( + locationBase: ILocationBase, + staticAmmoDist: Record, + ): SpawnpointTemplate[] { const result: SpawnpointTemplate[] = []; const locationId = locationBase.Id.toLowerCase(); @@ -84,7 +92,9 @@ export class LocationGenerator // Add mounted weapons to output loot result.push(...staticWeaponsOnMap ?? []); - const allStaticContainersOnMap = this.jsonUtil.clone(db.loot.staticContainers[locationBase.Name]?.staticContainers); + const allStaticContainersOnMap = this.jsonUtil.clone( + db.loot.staticContainers[locationBase.Name]?.staticContainers, + ); if (!allStaticContainersOnMap) { this.logger.error(`Unable to find static container data for map: ${locationBase.Name}`); @@ -105,23 +115,40 @@ export class LocationGenerator const staticLootDist = db.loot.staticLoot; const guaranteedContainers = this.getGuaranteedContainers(allStaticContainersOnMap); staticContainerCount += guaranteedContainers.length; - + // Add loot to guaranteed containers and add to result for (const container of guaranteedContainers) { - const containerWithLoot = this.addLootToContainer(container, staticForcedOnMap, staticLootDist, staticAmmoDist, locationId); + const containerWithLoot = this.addLootToContainer( + container, + staticForcedOnMap, + staticLootDist, + staticAmmoDist, + locationId, + ); result.push(containerWithLoot.template); } this.logger.success(`Added ${guaranteedContainers.length} guaranteed containers`); // randomisation is turned off globally or just turned off for this map - if (!this.locationConfig.containerRandomisationSettings.enabled || !this.locationConfig.containerRandomisationSettings.maps[locationId]) + if ( + !this.locationConfig.containerRandomisationSettings.enabled || + !this.locationConfig.containerRandomisationSettings.maps[locationId] + ) { - this.logger.debug(`Container randomisation disabled, Adding ${staticRandomisableContainersOnMap.length} containers to ${locationBase.Name}`); + this.logger.debug( + `Container randomisation disabled, Adding ${staticRandomisableContainersOnMap.length} containers to ${locationBase.Name}`, + ); for (const container of staticRandomisableContainersOnMap) { - const containerWithLoot = this.addLootToContainer(container, staticForcedOnMap, staticLootDist, staticAmmoDist, locationId); + const containerWithLoot = this.addLootToContainer( + container, + staticForcedOnMap, + staticLootDist, + staticAmmoDist, + locationId, + ); result.push(containerWithLoot.template); } @@ -129,7 +156,7 @@ export class LocationGenerator } // Group containers by their groupId - const staticContainerGroupData: IStaticContainer = db.locations[locationId].statics; + const staticContainerGroupData: IStaticContainer = db.locations[locationId].statics; const mapping = this.getGroupIdToContainerMappings(staticContainerGroupData, staticRandomisableContainersOnMap); // For each of the container groups, choose from the pool of containers, hydrate container with loot and add to result array @@ -145,7 +172,9 @@ export class LocationGenerator if (Object.keys(data.containerIdsWithProbability).length === 0) { - this.logger.debug(`Group: ${groupId} has no containers with < 100% spawn chance to choose from, skipping`); + this.logger.debug( + `Group: ${groupId} has no containers with < 100% spawn chance to choose from, skipping`, + ); continue; } @@ -178,48 +207,72 @@ export class LocationGenerator for (const chosenContainerId of chosenContainerIds) { // Look up container object from full list of containers on map - const containerObject = staticRandomisableContainersOnMap.find(x => x.template.Id === chosenContainerId); + const containerObject = staticRandomisableContainersOnMap.find((x) => + x.template.Id === chosenContainerId + ); if (!containerObject) { - this.logger.debug(`Container: ${chosenContainerIds[chosenContainerId]} not found in staticRandomisableContainersOnMap, this is bad`); + this.logger.debug( + `Container: ${ + chosenContainerIds[chosenContainerId] + } not found in staticRandomisableContainersOnMap, this is bad`, + ); continue; } // Add loot to container and push into result object - const containerWithLoot = this.addLootToContainer(containerObject, staticForcedOnMap, staticLootDist, staticAmmoDist, locationId); + const containerWithLoot = this.addLootToContainer( + containerObject, + staticForcedOnMap, + staticLootDist, + staticAmmoDist, + locationId, + ); result.push(containerWithLoot.template); staticContainerCount++; } } - this.logger.success(this.localisationService.getText("location-containers_generated_success", staticContainerCount)); + this.logger.success( + this.localisationService.getText("location-containers_generated_success", staticContainerCount), + ); return result; } /** * Get containers with a non-100% chance to spawn OR are NOT on the container type randomistion blacklist - * @param staticContainers + * @param staticContainers * @returns IStaticContainerData array */ protected getRandomisableContainersOnMap(staticContainers: IStaticContainerData[]): IStaticContainerData[] { return staticContainers - .filter(x => x.probability !== 1 && !x.template.IsAlwaysSpawn && !this.locationConfig.containerRandomisationSettings.containerTypesToNotRandomise.includes(x.template.Items[0]._tpl)); + .filter((x) => + x.probability !== 1 && !x.template.IsAlwaysSpawn && + !this.locationConfig.containerRandomisationSettings.containerTypesToNotRandomise.includes( + x.template.Items[0]._tpl, + ) + ); } /** * Get containers with 100% spawn rate or have a type on the randomistion ignore list - * @param staticContainersOnMap + * @param staticContainersOnMap * @returns IStaticContainerData array */ protected getGuaranteedContainers(staticContainersOnMap: IStaticContainerData[]): IStaticContainerData[] { - return staticContainersOnMap.filter(x => x.probability === 1 || x.template.IsAlwaysSpawn || this.locationConfig.containerRandomisationSettings.containerTypesToNotRandomise.includes(x.template.Items[0]._tpl)); + return staticContainersOnMap.filter((x) => + x.probability === 1 || x.template.IsAlwaysSpawn || + this.locationConfig.containerRandomisationSettings.containerTypesToNotRandomise.includes( + x.template.Items[0]._tpl, + ) + ); } /** - * Choose a number of containers based on their probabilty value to fulfil the desired count in containerData.chosenCount + * Choose a number of containers based on their probability value to fulfil the desired count in containerData.chosenCount * @param groupId Name of the group the containers are being collected for * @param containerData Containers and probability values for a groupId * @returns List of chosen container Ids @@ -231,15 +284,19 @@ export class LocationGenerator const containerIds = Object.keys(containerData.containerIdsWithProbability); if (containerData.chosenCount > containerIds.length) { - this.logger.debug(`Group: ${groupId} wants ${containerData.chosenCount} containers but pool only has ${containerIds.length}, add what's available`); + this.logger.debug( + `Group: ${groupId} wants ${containerData.chosenCount} containers but pool only has ${containerIds.length}, add what's available`, + ); return containerIds; } - // Create probability array with all possible container ids in this group and their relataive probability of spawning + // Create probability array with all possible container ids in this group and their relative probability of spawning const containerDistribution = new ProbabilityObjectArray(this.mathUtil, this.jsonUtil); for (const containerId of containerIds) { - containerDistribution.push(new ProbabilityObject(containerId, containerData.containerIdsWithProbability[containerId])); + containerDistribution.push( + new ProbabilityObject(containerId, containerData.containerIdsWithProbability[containerId]), + ); } chosenContainerIds.push(...containerDistribution.draw(containerData.chosenCount)); @@ -254,7 +311,8 @@ export class LocationGenerator */ protected getGroupIdToContainerMappings( staticContainerGroupData: IStaticContainer | Record, - staticContainersOnMap: IStaticContainerData[]): Record + staticContainersOnMap: IStaticContainerData[], + ): Record { // Create dictionary of all group ids and choose a count of containers the map will spawn of that group const mapping: Record = {}; @@ -266,9 +324,15 @@ export class LocationGenerator mapping[groupId] = { containerIdsWithProbability: {}, chosenCount: this.randomUtil.getInt( - Math.round(groupData.minContainers * this.locationConfig.containerRandomisationSettings.containerGroupMinSizeMultiplier), - Math.round(groupData.maxContainers * this.locationConfig.containerRandomisationSettings.containerGroupMaxSizeMultiplier) - ) + Math.round( + groupData.minContainers * + this.locationConfig.containerRandomisationSettings.containerGroupMinSizeMultiplier, + ), + Math.round( + groupData.maxContainers * + this.locationConfig.containerRandomisationSettings.containerGroupMaxSizeMultiplier, + ), + ), }; } } @@ -290,7 +354,9 @@ export class LocationGenerator if (container.probability === 1) { - this.logger.debug(`Container ${container.template.Id} with group ${groupData.groupId} had 100% chance to spawn was picked as random container, skipping`); + this.logger.debug( + `Container ${container.template.Id} with group ${groupData.groupId} had 100% chance to spawn was picked as random container, skipping`, + ); continue; } mapping[groupData.groupId].containerIdsWithProbability[container.template.Id] = container.probability; @@ -314,7 +380,8 @@ export class LocationGenerator staticForced: IStaticForcedProps[], staticLootDist: Record, staticAmmoDist: Record, - locationName: string): IStaticContainerData + locationName: string, + ): IStaticContainerData { const container = this.jsonUtil.clone(staticContainer); const containerTpl = container.template.Items[0]._tpl; @@ -333,7 +400,7 @@ export class LocationGenerator const containerLootPool = this.getPossibleLootItemsForContainer(containerTpl, staticLootDist); // Some containers need to have items forced into it (quest keys etc) - const tplsForced = staticForced.filter(x => x.containerId === container.template.Id).map(x => x.itemTpl); + const tplsForced = staticForced.filter((x) => x.containerId === container.template.Id).map((x) => x.itemTpl); // Draw random loot // Money spawn more than once in container @@ -341,7 +408,11 @@ export class LocationGenerator const locklist = [Money.ROUBLES, Money.DOLLARS, Money.EUROS]; // Choose items to add to container, factor in weighting + lock money down - const chosenTpls = containerLootPool.draw(itemCountToAdd, this.locationConfig.allowDuplicateItemsInStaticContainers, locklist); + const chosenTpls = containerLootPool.draw( + itemCountToAdd, + this.locationConfig.allowDuplicateItemsInStaticContainers, + locklist, + ); // Add forced loot to chosen item pool const tplsToAddToContainer = tplsForced.concat(chosenTpls); @@ -368,11 +439,18 @@ export class LocationGenerator continue; } - containerMap = this.containerHelper.fillContainerMapWithItem(containerMap, result.x, result.y, width, height, result.rotation); + containerMap = this.containerHelper.fillContainerMapWithItem( + containerMap, + result.x, + result.y, + width, + height, + result.rotation, + ); const rotation = result.rotation ? 1 : 0; items[0].slotId = "main"; - items[0].location = { x: result.x, y: result.y, r: rotation }; + items[0].location = {x: result.x, y: result.y, r: rotation}; // Add loot to container before returning for (const item of items) @@ -409,15 +487,19 @@ export class LocationGenerator * @param locationName Map name (to get per-map multiplier for from config) * @returns item count */ - protected getWeightedCountOfContainerItems(containerTypeId: string, staticLootDist: Record, locationName: string): number + protected getWeightedCountOfContainerItems( + containerTypeId: string, + staticLootDist: Record, + locationName: string, + ): number { - // Create probability array to calcualte the total count of lootable items inside container + // Create probability array to calculate the total count of lootable items inside container const itemCountArray = new ProbabilityObjectArray(this.mathUtil, this.jsonUtil); for (const itemCountDistribution of staticLootDist[containerTypeId].itemcountDistribution) { // Add each count of items into array itemCountArray.push( - new ProbabilityObject(itemCountDistribution.count, itemCountDistribution.relativeProbability) + new ProbabilityObject(itemCountDistribution.count, itemCountDistribution.relativeProbability), ); } @@ -427,11 +509,14 @@ export class LocationGenerator /** * Get all possible loot items that can be placed into a container * Do not add seasonal items if found + current date is inside seasonal event - * @param containerTypeId Contianer to get possible loot for + * @param containerTypeId Container to get possible loot for * @param staticLootDist staticLoot.json - * @returns ProbabilityObjectArray of item tpls + probabilty + * @returns ProbabilityObjectArray of item tpls + probability */ - protected getPossibleLootItemsForContainer(containerTypeId: string, staticLootDist: Record): ProbabilityObjectArray + protected getPossibleLootItemsForContainer( + containerTypeId: string, + staticLootDist: Record, + ): ProbabilityObjectArray { const seasonalEventActive = this.seasonalEventService.seasonalEventEnabled(); const seasonalItemTplBlacklist = this.seasonalEventService.getInactiveSeasonalEventItems(); @@ -446,7 +531,7 @@ export class LocationGenerator } itemDistribution.push( - new ProbabilityObject(icd.tpl, icd.relativeProbability) + new ProbabilityObject(icd.tpl, icd.relativeProbability), ); } @@ -465,12 +550,16 @@ export class LocationGenerator /** * Create array of loose + forced loot using probability system - * @param dynamicLootDist - * @param staticAmmoDist + * @param dynamicLootDist + * @param staticAmmoDist * @param locationName Location to generate loot for * @returns Array of spawn points with loot in them */ - public generateDynamicLoot(dynamicLootDist: ILooseLoot, staticAmmoDist: Record, locationName: string): SpawnpointTemplate[] + public generateDynamicLoot( + dynamicLootDist: ILooseLoot, + staticAmmoDist: Record, + locationName: string, + ): SpawnpointTemplate[] { const loot: SpawnpointTemplate[] = []; @@ -478,14 +567,14 @@ export class LocationGenerator this.addForcedLoot(loot, dynamicLootDist.spawnpointsForced, locationName); const allDynamicSpawnpoints = dynamicLootDist.spawnpoints; - - //Draw from random distribution + + // Draw from random distribution const desiredSpawnpointCount = Math.round( this.getLooseLootMultiplerForLocation(locationName) * - this.randomUtil.randn( - dynamicLootDist.spawnpointCount.mean, - dynamicLootDist.spawnpointCount.std - ) + this.randomUtil.randn( + dynamicLootDist.spawnpointCount.mean, + dynamicLootDist.spawnpointCount.std, + ), ); // Positions not in forced but have 100% chance to spawn @@ -496,7 +585,7 @@ export class LocationGenerator for (const spawnpoint of allDynamicSpawnpoints) { - // Point is blacklsited, skip + // Point is blacklisted, skip if (blacklistedSpawnpoints?.includes(spawnpoint.template.Id)) { this.logger.debug(`Ignoring loose loot location: ${spawnpoint.template.Id}`); @@ -510,7 +599,7 @@ export class LocationGenerator } spawnpointArray.push( - new ProbabilityObject(spawnpoint.template.Id, spawnpoint.probability, spawnpoint) + new ProbabilityObject(spawnpoint.template.Id, spawnpoint.probability, spawnpoint), ); } @@ -525,13 +614,19 @@ export class LocationGenerator } // Filter out duplicate locationIds - chosenSpawnpoints = [...new Map(chosenSpawnpoints.map(x => [x.locationId, x])).values()]; + chosenSpawnpoints = [...new Map(chosenSpawnpoints.map((x) => [x.locationId, x])).values()]; // Do we have enough items in pool to fulfill requirement const tooManySpawnPointsRequested = (desiredSpawnpointCount - chosenSpawnpoints.length) > 0; if (tooManySpawnPointsRequested) { - this.logger.debug(this.localisationService.getText("location-spawn_point_count_requested_vs_found", {requested: desiredSpawnpointCount+guaranteedLoosePoints.length, found: chosenSpawnpoints.length, mapName: locationName})); + this.logger.debug( + this.localisationService.getText("location-spawn_point_count_requested_vs_found", { + requested: desiredSpawnpointCount + guaranteedLoosePoints.length, + found: chosenSpawnpoints.length, + mapName: locationName, + }), + ); } // Iterate over spawnpoints @@ -541,29 +636,37 @@ export class LocationGenerator { if (!spawnPoint.template) { - this.logger.warning(this.localisationService.getText("location-missing_dynamic_template", spawnPoint.locationId)); + this.logger.warning( + this.localisationService.getText("location-missing_dynamic_template", spawnPoint.locationId), + ); continue; } if (!spawnPoint.template.Items || spawnPoint.template.Items.length === 0) { - this.logger.error(this.localisationService.getText("location-spawnpoint_missing_items", spawnPoint.template.Id)); - + this.logger.error( + this.localisationService.getText("location-spawnpoint_missing_items", spawnPoint.template.Id), + ); + continue; } const itemArray = new ProbabilityObjectArray(this.mathUtil, this.jsonUtil); for (const itemDist of spawnPoint.itemDistribution) { - if (!seasonalEventActive && seasonalItemTplBlacklist.includes(spawnPoint.template.Items.find(x => x._id === itemDist.composedKey.key)._tpl)) + if ( + !seasonalEventActive && seasonalItemTplBlacklist.includes( + spawnPoint.template.Items.find((x) => x._id === itemDist.composedKey.key)._tpl, + ) + ) { // Skip seasonal event items if they're not enabled continue; } itemArray.push( - new ProbabilityObject(itemDist.composedKey.key, itemDist.relativeProbability) + new ProbabilityObject(itemDist.composedKey.key, itemDist.relativeProbability), ); } @@ -587,7 +690,11 @@ export class LocationGenerator * @param forcedSpawnPoints forced loot to add * @param name of map currently generating forced loot for */ - protected addForcedLoot(loot: SpawnpointTemplate[], forcedSpawnPoints: SpawnpointsForced[], locationName: string): void + protected addForcedLoot( + loot: SpawnpointTemplate[], + forcedSpawnPoints: SpawnpointsForced[], + locationName: string, + ): void { const lootToForceSingleAmountOnMap = this.locationConfig.forcedLootSingleSpawnById[locationName]; if (lootToForceSingleAmountOnMap) @@ -596,27 +703,32 @@ export class LocationGenerator for (const itemTpl of lootToForceSingleAmountOnMap) { // Get all spawn positions for item tpl in forced loot array - const items = forcedSpawnPoints.filter(x => x.template.Items[0]._tpl === itemTpl); + const items = forcedSpawnPoints.filter((x) => x.template.Items[0]._tpl === itemTpl); if (!items || items.length === 0) { - this.logger.debug(`Unable to adjust loot item ${itemTpl} as it does not exist inside ${locationName} forced loot.`); + this.logger.debug( + `Unable to adjust loot item ${itemTpl} as it does not exist inside ${locationName} forced loot.`, + ); continue; } // Create probability array of all spawn positions for this spawn id - const spawnpointArray = new ProbabilityObjectArray(this.mathUtil, this.jsonUtil); + const spawnpointArray = new ProbabilityObjectArray( + this.mathUtil, + this.jsonUtil, + ); for (const si of items) { // use locationId as template.Id is the same across all items spawnpointArray.push( - new ProbabilityObject(si.locationId, si.probability, si) + new ProbabilityObject(si.locationId, si.probability, si), ); } - + // Choose 1 out of all found spawn positions for spawn id and add to loot array for (const spawnPointLocationId of spawnpointArray.draw(1, false)) { - const itemToAdd = items.find(x => x.locationId === spawnPointLocationId); + const itemToAdd = items.find((x) => x.locationId === spawnPointLocationId); const lootItem = itemToAdd.template; lootItem.Root = this.objectId.generate(); lootItem.Items[0]._id = lootItem.Root; @@ -656,29 +768,36 @@ export class LocationGenerator * @param staticAmmoDist ammo distributions * @returns IContainerItem */ - protected createDynamicLootItem(chosenComposedKey: string, spawnPoint: Spawnpoint, staticAmmoDist: Record): IContainerItem + protected createDynamicLootItem( + chosenComposedKey: string, + spawnPoint: Spawnpoint, + staticAmmoDist: Record, + ): IContainerItem { - const chosenItem = spawnPoint.template.Items.find(x => x._id === chosenComposedKey); + const chosenItem = spawnPoint.template.Items.find((x) => x._id === chosenComposedKey); const chosenTpl = chosenItem._tpl; // Item array to return const itemWithMods: Item[] = []; // Money/Ammo - don't rely on items in spawnPoint.template.Items so we can randomise it ourselves - if (this.itemHelper.isOfBaseclass(chosenTpl, BaseClasses.MONEY) || this.itemHelper.isOfBaseclass(chosenTpl, BaseClasses.AMMO)) + if ( + this.itemHelper.isOfBaseclass(chosenTpl, BaseClasses.MONEY) || + this.itemHelper.isOfBaseclass(chosenTpl, BaseClasses.AMMO) + ) { const itemTemplate = this.itemHelper.getItem(chosenTpl)[1]; - const stackCount = itemTemplate._props.StackMaxSize === 1 - ? 1 - : this.randomUtil.getInt(itemTemplate._props.StackMinRandom, itemTemplate._props.StackMaxRandom); + const stackCount = itemTemplate._props.StackMaxSize === 1 ? + 1 : + this.randomUtil.getInt(itemTemplate._props.StackMinRandom, itemTemplate._props.StackMaxRandom); itemWithMods.push( { _id: this.objectId.generate(), _tpl: chosenTpl, - upd: { StackObjectsCount: stackCount } - } + upd: {StackObjectsCount: stackCount}, + }, ); } else if (this.itemHelper.isOfBaseclass(chosenTpl, BaseClasses.AMMO_BOX)) @@ -687,7 +806,7 @@ export class LocationGenerator const ammoBoxTemplate = this.itemHelper.getItem(chosenTpl)[1]; const ammoBoxItem: Item[] = [{ _id: this.objectId.generate(), - _tpl: chosenTpl + _tpl: chosenTpl, }]; this.itemHelper.addCartridgesToAmmoBox(ammoBoxItem, ammoBoxTemplate); itemWithMods.push(...ammoBoxItem); @@ -698,15 +817,24 @@ export class LocationGenerator const magazineTemplate = this.itemHelper.getItem(chosenTpl)[1]; const magazineItem: Item[] = [{ _id: this.objectId.generate(), - _tpl: chosenTpl + _tpl: chosenTpl, }]; - this.itemHelper.fillMagazineWithRandomCartridge(magazineItem, magazineTemplate, staticAmmoDist, null, this.locationConfig.minFillLooseMagazinePercent / 100); + this.itemHelper.fillMagazineWithRandomCartridge( + magazineItem, + magazineTemplate, + staticAmmoDist, + null, + this.locationConfig.minFillLooseMagazinePercent / 100, + ); itemWithMods.push(...magazineItem); } else { // Get item + children and add into array we return - const itemWithChildren = this.itemHelper.findAndReturnChildrenAsItems(spawnPoint.template.Items, chosenItem._id); + const itemWithChildren = this.itemHelper.findAndReturnChildrenAsItems( + spawnPoint.template.Items, + chosenItem._id, + ); // We need to reparent to ensure ids are unique this.reparentItemAndChildren(itemWithChildren); @@ -720,7 +848,7 @@ export class LocationGenerator return { items: itemWithMods, width: size.width, - height: size.height + height: size.height, }; } @@ -757,14 +885,18 @@ export class LocationGenerator { if (this.itemHelper.isOfBaseclass(chosenTpl, BaseClasses.WEAPON)) { - return items.find(v => v._tpl === chosenTpl && v.parentId === undefined); + return items.find((v) => v._tpl === chosenTpl && v.parentId === undefined); } - return items.find(x => x._tpl === chosenTpl); + return items.find((x) => x._tpl === chosenTpl); } // TODO: rewrite, BIG yikes - protected createStaticLootItem(tpl: string, staticAmmoDist: Record, parentId: string = undefined): IContainerItem + protected createStaticLootItem( + tpl: string, + staticAmmoDist: Record, + parentId: string = undefined, + ): IContainerItem { const itemTemplate = this.itemHelper.getItem(tpl)[1]; let width = itemTemplate._props.Width; @@ -772,8 +904,8 @@ export class LocationGenerator let items: Item[] = [ { _id: this.objectId.generate(), - _tpl: tpl - } + _tpl: tpl, + }, ]; // Use passed in parentId as override for new item @@ -782,13 +914,16 @@ export class LocationGenerator items[0].parentId = parentId; } - if (this.itemHelper.isOfBaseclass(tpl, BaseClasses.MONEY) || this.itemHelper.isOfBaseclass(tpl, BaseClasses.AMMO)) + if ( + this.itemHelper.isOfBaseclass(tpl, BaseClasses.MONEY) || + this.itemHelper.isOfBaseclass(tpl, BaseClasses.AMMO) + ) { // Edge case - some ammos e.g. flares or M406 grenades shouldn't be stacked - const stackCount = itemTemplate._props.StackMaxSize === 1 - ? 1 - : this.randomUtil.getInt(itemTemplate._props.StackMinRandom, itemTemplate._props.StackMaxRandom); - items[0].upd = { StackObjectsCount: stackCount }; + const stackCount = itemTemplate._props.StackMaxSize === 1 ? + 1 : + this.randomUtil.getInt(itemTemplate._props.StackMinRandom, itemTemplate._props.StackMaxRandom); + items[0].upd = {StackObjectsCount: stackCount}; } // No spawn point, use default template else if (this.itemHelper.isOfBaseclass(tpl, BaseClasses.WEAPON)) @@ -806,21 +941,30 @@ export class LocationGenerator // this item already broke it once without being reproducible tpl = "5839a40f24597726f856b511"; AKS-74UB Default // 5ea03f7400685063ec28bfa8 // ppsh default // 5ba26383d4351e00334c93d9 //mp7_devgru - this.logger.warning(this.localisationService.getText("location-preset_not_found", {tpl: tpl, defaultId: defaultPreset._id, defaultName: defaultPreset._name, parentId: parentId})); + this.logger.warning( + this.localisationService.getText("location-preset_not_found", { + tpl: tpl, + defaultId: defaultPreset._id, + defaultName: defaultPreset._name, + parentId: parentId, + }), + ); throw error; } } else { - // RSP30 (62178be9d0050232da3485d9/624c0b3340357b5f566e8766/6217726288ed9f0845317459) doesnt have any default presets and kills this code below as it has no chidren to reparent + // RSP30 (62178be9d0050232da3485d9/624c0b3340357b5f566e8766/6217726288ed9f0845317459) doesn't have any default presets and kills this code below as it has no children to reparent this.logger.debug(`createItem() No preset found for weapon: ${tpl}`); } const rootItem = items[0]; if (!rootItem) { - this.logger.error(this.localisationService.getText("location-missing_root_item", {tpl: tpl, parentId: parentId})); + this.logger.error( + this.localisationService.getText("location-missing_root_item", {tpl: tpl, parentId: parentId}), + ); throw new Error(this.localisationService.getText("location-critical_error_see_log")); } @@ -830,21 +974,25 @@ export class LocationGenerator if (children?.length > 0) { items = this.ragfairServerHelper.reparentPresets(rootItem, children); - } + } } catch (error) { - this.logger.error(this.localisationService.getText("location-unable_to_reparent_item", {tpl: tpl, parentId: parentId})); + this.logger.error( + this.localisationService.getText("location-unable_to_reparent_item", { + tpl: tpl, + parentId: parentId, + }), + ); throw error; } - // Here we should use generalized BotGenerators functions e.g. fillExistingMagazines in the future since // it can handle revolver ammo (it's not restructured to be used here yet.) // General: Make a WeaponController for Ragfair preset stuff and the generating weapons and ammo stuff from // BotGenerator - const magazine = items.filter(x => x.slotId === "mod_magazine")[0]; + const magazine = items.filter((x) => x.slotId === "mod_magazine")[0]; // some weapon presets come without magazine; only fill the mag if it exists if (magazine) { @@ -853,7 +1001,12 @@ export class LocationGenerator // Create array with just magazine const magazineWithCartridges = [magazine]; - this.itemHelper.fillMagazineWithRandomCartridge(magazineWithCartridges, magTemplate, staticAmmoDist, weaponTemplate._props.ammoCaliber); + this.itemHelper.fillMagazineWithRandomCartridge( + magazineWithCartridges, + magTemplate, + staticAmmoDist, + weaponTemplate._props.ammoCaliber, + ); // Replace existing magazine with above array items.splice(items.indexOf(magazine), 1, ...magazineWithCartridges); @@ -872,7 +1025,13 @@ export class LocationGenerator { // Create array with just magazine const magazineWithCartridges = [items[0]]; - this.itemHelper.fillMagazineWithRandomCartridge(magazineWithCartridges, itemTemplate, staticAmmoDist, null, this.locationConfig.minFillStaticMagazinePercent / 100); + this.itemHelper.fillMagazineWithRandomCartridge( + magazineWithCartridges, + itemTemplate, + staticAmmoDist, + null, + this.locationConfig.minFillStaticMagazinePercent / 100, + ); // Replace existing magazine with above array items.splice(items.indexOf(items[0]), 1, ...magazineWithCartridges); @@ -881,7 +1040,7 @@ export class LocationGenerator return { items: items, width: width, - height: height + height: height, }; } -} \ No newline at end of file +} diff --git a/project/src/generators/LootGenerator.ts b/project/src/generators/LootGenerator.ts index fe0a3b9f..7658f480 100644 --- a/project/src/generators/LootGenerator.ts +++ b/project/src/generators/LootGenerator.ts @@ -20,8 +20,8 @@ import { HashUtil } from "@spt-aki/utils/HashUtil"; import { RandomUtil } from "@spt-aki/utils/RandomUtil"; type ItemLimit = { - current: number, - max: number + current: number; + max: number; }; @injectable() @@ -38,7 +38,7 @@ export class LootGenerator @inject("WeightedRandomHelper") protected weightedRandomHelper: WeightedRandomHelper, @inject("LocalisationService") protected localisationService: LocalisationService, @inject("RagfairLinkedItemService") protected ragfairLinkedItemService: RagfairLinkedItemService, - @inject("ItemFilterService") protected itemFilterService: ItemFilterService + @inject("ItemFilterService") protected itemFilterService: ItemFilterService, ) {} @@ -54,7 +54,10 @@ export class LootGenerator const itemTypeCounts = this.initItemLimitCounter(options.itemLimits); const tables = this.databaseServer.getTables(); - const itemBlacklist = new Set([...this.itemFilterService.getBlacklistedItems(), ...options.itemBlacklist]); + const itemBlacklist = new Set([ + ...this.itemFilterService.getBlacklistedItems(), + ...options.itemBlacklist, + ]); if (!options.allowBossItems) { for (const bossItem of this.itemFilterService.getBossItems()) @@ -64,12 +67,17 @@ export class LootGenerator } // Handle sealed weapon containers - const desiredWeaponCrateCount = this.randomUtil.getInt(options.weaponCrateCount.min, options.weaponCrateCount.max); + const desiredWeaponCrateCount = this.randomUtil.getInt( + options.weaponCrateCount.min, + options.weaponCrateCount.max, + ); if (desiredWeaponCrateCount > 0) { // Get list of all sealed containers from db - const sealedWeaponContainerPool = Object.values(tables.templates.items).filter(x => x._name.includes("event_container_airdrop")); - + const sealedWeaponContainerPool = Object.values(tables.templates.items).filter((x) => + x._name.includes("event_container_airdrop") + ); + for (let index = 0; index < desiredWeaponCrateCount; index++) { // Choose one at random + add to results array @@ -78,16 +86,18 @@ export class LootGenerator id: this.hashUtil.generate(), tpl: chosenSealedContainer._id, isPreset: false, - stackCount: 1 + stackCount: 1, }); } } // Get items from items.json that have a type of item + not in global blacklist + basetype is in whitelist - const items = Object.entries(tables.templates.items).filter(x => !itemBlacklist.has(x[1]._id) - && x[1]._type.toLowerCase() === "item" - && !x[1]._props.QuestItem - && options.itemTypeWhitelist.includes(x[1]._parent)); + const items = Object.entries(tables.templates.items).filter((x) => + !itemBlacklist.has(x[1]._id) && + x[1]._type.toLowerCase() === "item" && + !x[1]._props.QuestItem && + options.itemTypeWhitelist.includes(x[1]._parent) + ); const randomisedItemCount = this.randomUtil.getInt(options.itemCount.min, options.itemCount.max); for (let index = 0; index < randomisedItemCount; index++) @@ -95,10 +105,12 @@ export class LootGenerator if (!this.findAndAddRandomItemToLoot(items, itemTypeCounts, options, result)) { index--; - } + } } - const globalDefaultPresets = Object.entries(tables.globals.ItemPresets).filter(x => x[1]._encyclopedia !== undefined); + const globalDefaultPresets = Object.entries(tables.globals.ItemPresets).filter((x) => + x[1]._encyclopedia !== undefined + ); const randomisedPresetCount = this.randomUtil.getInt(options.presetCount.min, options.presetCount.max); const itemBlacklistArray = Array.from(itemBlacklist); for (let index = 0; index < randomisedPresetCount; index++) @@ -124,7 +136,7 @@ export class LootGenerator { itemTypeCounts[itemTypeId] = { current: 0, - max: limits[itemTypeId] + max: limits[itemTypeId], }; } @@ -141,9 +153,10 @@ export class LootGenerator */ protected findAndAddRandomItemToLoot( items: [string, ITemplateItem][], - itemTypeCounts: Record, + itemTypeCounts: Record, options: LootRequest, - result: LootItem[]): boolean + result: LootItem[], + ): boolean { const randomItem = this.randomUtil.getArrayValue(items)[1]; @@ -157,16 +170,18 @@ export class LootGenerator id: this.hashUtil.generate(), tpl: randomItem._id, isPreset: false, - stackCount: 1 + stackCount: 1, }; // Check if armor has level in allowed whitelist - if (randomItem._parent === BaseClasses.ARMOR - || randomItem._parent === BaseClasses.VEST) + if ( + randomItem._parent === BaseClasses.ARMOR || + randomItem._parent === BaseClasses.VEST + ) { if (!options.armorLevelWhitelist.includes(Number(randomItem._props.armorClass))) { - return false; + return false; } } @@ -175,7 +190,7 @@ export class LootGenerator { newLootItem.stackCount = this.getRandomisedStackCount(randomItem, options); } - + newLootItem.tpl = randomItem._id; result.push(newLootItem); @@ -219,9 +234,10 @@ export class LootGenerator */ protected findAndAddRandomPresetToLoot( globalDefaultPresets: [string, IPreset][], - itemTypeCounts: Record, + itemTypeCounts: Record, itemBlacklist: string[], - result: LootItem[]): boolean + result: LootItem[], + ): boolean { // Choose random preset and get details from item.json using encyclopedia value (encyclopedia === tplId) const randomPreset = this.randomUtil.getArrayValue(globalDefaultPresets)[1]; @@ -264,9 +280,9 @@ export class LootGenerator const newLootItem: LootItem = { tpl: randomPreset._items[0]._tpl, isPreset: true, - stackCount: 1 + stackCount: 1, }; - + result.push(newLootItem); if (itemLimitCount) @@ -274,7 +290,7 @@ export class LootGenerator // increment item count as its in limit array itemLimitCount.current++; } - + // item added okay return true; } @@ -289,19 +305,23 @@ export class LootGenerator const itemsToReturn: AddItem[] = []; // choose a weapon to give to the player (weighted) - const chosenWeaponTpl = this.weightedRandomHelper.getWeightedValue(containerSettings.weaponRewardWeight); + const chosenWeaponTpl = this.weightedRandomHelper.getWeightedValue( + containerSettings.weaponRewardWeight, + ); const weaponDetailsDb = this.itemHelper.getItem(chosenWeaponTpl); if (!weaponDetailsDb[0]) { - this.logger.error(this.localisationService.getText("loot-non_item_picked_as_sealed_weapon_crate_reward", chosenWeaponTpl)); + this.logger.error( + this.localisationService.getText("loot-non_item_picked_as_sealed_weapon_crate_reward", chosenWeaponTpl), + ); return itemsToReturn; } - + // Get weapon preset - default or choose a random one from all possible - let chosenWeaponPreset = containerSettings.defaultPresetsOnly - ? this.presetHelper.getDefaultPreset(chosenWeaponTpl) - : this.randomUtil.getArrayValue(this.presetHelper.getPresets(chosenWeaponTpl)); + let chosenWeaponPreset = containerSettings.defaultPresetsOnly ? + this.presetHelper.getDefaultPreset(chosenWeaponTpl) : + this.randomUtil.getArrayValue(this.presetHelper.getPresets(chosenWeaponTpl)); if (!chosenWeaponPreset) { @@ -314,12 +334,14 @@ export class LootGenerator count: 1, // eslint-disable-next-line @typescript-eslint/naming-convention item_id: chosenWeaponPreset._id, - isPreset: true + isPreset: true, }); // Get items related to chosen weapon const linkedItemsToWeapon = this.ragfairLinkedItemService.getLinkedDbItems(chosenWeaponTpl); - itemsToReturn.push(...this.getSealedContainerWeaponModRewards(containerSettings, linkedItemsToWeapon, chosenWeaponPreset)); + itemsToReturn.push( + ...this.getSealedContainerWeaponModRewards(containerSettings, linkedItemsToWeapon, chosenWeaponPreset), + ); // Handle non-weapon mod reward types itemsToReturn.push(...this.getSealedContainerNonWeaponModRewards(containerSettings, weaponDetailsDb[1])); @@ -333,7 +355,10 @@ export class LootGenerator * @param weaponDetailsDb Details for the weapon to reward player * @returns AddItem array */ - protected getSealedContainerNonWeaponModRewards(containerSettings: ISealedAirdropContainerSettings, weaponDetailsDb: ITemplateItem): AddItem[] + protected getSealedContainerNonWeaponModRewards( + containerSettings: ISealedAirdropContainerSettings, + weaponDetailsDb: ITemplateItem, + ): AddItem[] { const rewards: AddItem[] = []; @@ -351,15 +376,15 @@ export class LootGenerator if (rewardTypeId === BaseClasses.AMMO_BOX) { // Get ammoboxes from db - const ammoBoxesDetails = containerSettings.ammoBoxWhitelist.map(x => + const ammoBoxesDetails = containerSettings.ammoBoxWhitelist.map((x) => { const itemDetails = this.itemHelper.getItem(x); return itemDetails[1]; }); - + // Need to find boxes that matches weapons caliber const weaponCaliber = weaponDetailsDb._props.ammoCaliber; - const ammoBoxesMatchingCaliber = ammoBoxesDetails.filter(x => x._props.ammoCaliber === weaponCaliber); + const ammoBoxesMatchingCaliber = ammoBoxesDetails.filter((x) => x._props.ammoCaliber === weaponCaliber); if (ammoBoxesMatchingCaliber.length === 0) { this.logger.debug(`No ammo box with caliber ${weaponCaliber} found, skipping`); @@ -373,7 +398,7 @@ export class LootGenerator count: rewardCount, // eslint-disable-next-line @typescript-eslint/naming-convention item_id: chosenAmmoBox._id, - isPreset: false + isPreset: false, }); continue; @@ -381,11 +406,13 @@ export class LootGenerator // Get all items of the desired type + not quest items + not globally blacklisted const rewardItemPool = Object.values(this.databaseServer.getTables().templates.items) - .filter(x => x._parent === rewardTypeId - && x._type.toLowerCase() === "item" - && !this.itemFilterService.isItemBlacklisted(x._id) - && (!containerSettings.allowBossItems && !this.itemFilterService.isBossItem(x._id)) - && !x._props.QuestItem); + .filter((x) => + x._parent === rewardTypeId && + x._type.toLowerCase() === "item" && + !this.itemFilterService.isItemBlacklisted(x._id) && + (!containerSettings.allowBossItems && !this.itemFilterService.isBossItem(x._id)) && + !x._props.QuestItem + ); if (rewardItemPool.length === 0) { @@ -398,7 +425,7 @@ export class LootGenerator { // choose a random item from pool const chosenRewardItem = this.randomUtil.getArrayValue(rewardItemPool); - this.addOrIncrementItemToArray(chosenRewardItem._id, rewards); + this.addOrIncrementItemToArray(chosenRewardItem._id, rewards); } } @@ -412,7 +439,11 @@ export class LootGenerator * @param chosenWeaponPreset The weapon preset given to player as reward * @returns AddItem array */ - protected getSealedContainerWeaponModRewards(containerSettings: ISealedAirdropContainerSettings, linkedItemsToWeapon: ITemplateItem[], chosenWeaponPreset: IPreset): AddItem[] + protected getSealedContainerWeaponModRewards( + containerSettings: ISealedAirdropContainerSettings, + linkedItemsToWeapon: ITemplateItem[], + chosenWeaponPreset: IPreset, + ): AddItem[] { const modRewards: AddItem[] = []; for (const rewardTypeId in containerSettings.weaponModRewardLimits) @@ -426,16 +457,20 @@ export class LootGenerator continue; } - // Get items that fulfil reward type critera from items that fit on gun - const relatedItems = linkedItemsToWeapon.filter(x => x._parent === rewardTypeId && !this.itemFilterService.isItemBlacklisted(x._id)); + // Get items that fulfil reward type criteria from items that fit on gun + const relatedItems = linkedItemsToWeapon.filter((x) => + x._parent === rewardTypeId && !this.itemFilterService.isItemBlacklisted(x._id) + ); if (!relatedItems || relatedItems.length === 0) { - this.logger.debug(`No items found to fulfil reward type ${rewardTypeId} for weapon: ${chosenWeaponPreset._name}, skipping type`); + this.logger.debug( + `No items found to fulfil reward type ${rewardTypeId} for weapon: ${chosenWeaponPreset._name}, skipping type`, + ); continue; } // Find a random item of the desired type and add as reward - for (let index = 0; index < rewardCount; index++) + for (let index = 0; index < rewardCount; index++) { const chosenItem = this.randomUtil.drawRandomFromList(relatedItems); this.addOrIncrementItemToArray(chosenItem[0]._id, modRewards); @@ -447,7 +482,7 @@ export class LootGenerator /** * Handle event-related loot containers - currently just the halloween jack-o-lanterns that give food rewards - * @param rewardContainerDetails + * @param rewardContainerDetails * @returns AddItem array */ public getRandomLootContainerLoot(rewardContainerDetails: RewardDetails): AddItem[] @@ -458,7 +493,9 @@ export class LootGenerator for (let index = 0; index < rewardContainerDetails.rewardCount; index++) { // Pick random reward from pool, add to request object - const chosenRewardItemTpl = this.weightedRandomHelper.getWeightedValue(rewardContainerDetails.rewardTplPool); + const chosenRewardItemTpl = this.weightedRandomHelper.getWeightedValue( + rewardContainerDetails.rewardTplPool, + ); this.addOrIncrementItemToArray(chosenRewardItemTpl, itemsToReturn); } @@ -473,7 +510,7 @@ export class LootGenerator */ protected addOrIncrementItemToArray(itemTplToAdd: string, resultsArray: AddItem[]): void { - const existingItemIndex = resultsArray.findIndex(x => x.item_id === itemTplToAdd); + const existingItemIndex = resultsArray.findIndex((x) => x.item_id === itemTplToAdd); if (existingItemIndex > -1) { // Exists in array already, increment count @@ -485,4 +522,4 @@ export class LootGenerator resultsArray.push({item_id: itemTplToAdd, count: 1, isPreset: false}); } } -} \ No newline at end of file +} diff --git a/project/src/generators/PMCLootGenerator.ts b/project/src/generators/PMCLootGenerator.ts index 2e146d44..2b6c11a8 100644 --- a/project/src/generators/PMCLootGenerator.ts +++ b/project/src/generators/PMCLootGenerator.ts @@ -10,11 +10,10 @@ import { ItemFilterService } from "@spt-aki/services/ItemFilterService"; import { SeasonalEventService } from "@spt-aki/services/SeasonalEventService"; /** - * Handle the generation of dynamic PMC loot in pockets and backpacks + * Handle the generation of dynamic PMC loot in pockets and backpacks * and the removal of blacklisted items */ @injectable() - export class PMCLootGenerator { protected pocketLootPool: string[] = []; @@ -27,7 +26,7 @@ export class PMCLootGenerator @inject("DatabaseServer") protected databaseServer: DatabaseServer, @inject("ConfigServer") protected configServer: ConfigServer, @inject("ItemFilterService") protected itemFilterService: ItemFilterService, - @inject("SeasonalEventService") protected seasonalEventService: SeasonalEventService + @inject("SeasonalEventService") protected seasonalEventService: SeasonalEventService, ) { this.pmcConfig = this.configServer.getConfig(ConfigTypes.PMC); @@ -47,7 +46,7 @@ export class PMCLootGenerator const allowedItemTypes = this.pmcConfig.pocketLoot.whitelist; const pmcItemBlacklist = this.pmcConfig.pocketLoot.blacklist; const itemBlacklist = this.itemFilterService.getBlacklistedItems(); - + // Blacklist seasonal items if not inside seasonal event // Blacklist seasonal items if not inside seasonal event if (!this.seasonalEventService.seasonalEventEnabled()) @@ -56,14 +55,16 @@ export class PMCLootGenerator itemBlacklist.push(...this.seasonalEventService.getInactiveSeasonalEventItems()); } - const itemsToAdd = Object.values(items).filter(item => allowedItemTypes.includes(item._parent) - && this.itemHelper.isValidItem(item._id) - && !pmcItemBlacklist.includes(item._id) - && !itemBlacklist.includes(item._id) - && item._props.Width === 1 - && item._props.Height === 1); + const itemsToAdd = Object.values(items).filter((item) => + allowedItemTypes.includes(item._parent) && + this.itemHelper.isValidItem(item._id) && + !pmcItemBlacklist.includes(item._id) && + !itemBlacklist.includes(item._id) && + item._props.Width === 1 && + item._props.Height === 1 + ); - this.pocketLootPool = itemsToAdd.map(x => x._id); + this.pocketLootPool = itemsToAdd.map((x) => x._id); } return this.pocketLootPool; @@ -83,7 +84,7 @@ export class PMCLootGenerator const allowedItemTypes = this.pmcConfig.vestLoot.whitelist; const pmcItemBlacklist = this.pmcConfig.vestLoot.blacklist; const itemBlacklist = this.itemFilterService.getBlacklistedItems(); - + // Blacklist seasonal items if not inside seasonal event // Blacklist seasonal items if not inside seasonal event if (!this.seasonalEventService.seasonalEventEnabled()) @@ -92,13 +93,15 @@ export class PMCLootGenerator itemBlacklist.push(...this.seasonalEventService.getInactiveSeasonalEventItems()); } - const itemsToAdd = Object.values(items).filter(item => allowedItemTypes.includes(item._parent) - && this.itemHelper.isValidItem(item._id) - && !pmcItemBlacklist.includes(item._id) - && !itemBlacklist.includes(item._id) - && this.itemFitsInto2By2Slot(item)); + const itemsToAdd = Object.values(items).filter((item) => + allowedItemTypes.includes(item._parent) && + this.itemHelper.isValidItem(item._id) && + !pmcItemBlacklist.includes(item._id) && + !itemBlacklist.includes(item._id) && + this.itemFitsInto2By2Slot(item) + ); - this.vestLootPool = itemsToAdd.map(x => x._id); + this.vestLootPool = itemsToAdd.map((x) => x._id); } return this.vestLootPool; @@ -129,7 +132,7 @@ export class PMCLootGenerator const allowedItemTypes = this.pmcConfig.backpackLoot.whitelist; const pmcItemBlacklist = this.pmcConfig.backpackLoot.blacklist; const itemBlacklist = this.itemFilterService.getBlacklistedItems(); - + // blacklist event items if not inside seasonal event if (!this.seasonalEventService.seasonalEventEnabled()) { @@ -137,14 +140,16 @@ export class PMCLootGenerator itemBlacklist.push(...this.seasonalEventService.getInactiveSeasonalEventItems()); } - const itemsToAdd = Object.values(items).filter(item => allowedItemTypes.includes(item._parent) - && this.itemHelper.isValidItem(item._id) - && !pmcItemBlacklist.includes(item._id) - && !itemBlacklist.includes(item._id)); + const itemsToAdd = Object.values(items).filter((item) => + allowedItemTypes.includes(item._parent) && + this.itemHelper.isValidItem(item._id) && + !pmcItemBlacklist.includes(item._id) && + !itemBlacklist.includes(item._id) + ); - this.backpackLootPool = itemsToAdd.map(x => x._id); + this.backpackLootPool = itemsToAdd.map((x) => x._id); } return this.backpackLootPool; } -} \ No newline at end of file +} diff --git a/project/src/generators/PlayerScavGenerator.ts b/project/src/generators/PlayerScavGenerator.ts index 3e870523..69f1b849 100644 --- a/project/src/generators/PlayerScavGenerator.ts +++ b/project/src/generators/PlayerScavGenerator.ts @@ -47,7 +47,7 @@ export class PlayerScavGenerator @inject("BotLootCacheService") protected botLootCacheService: BotLootCacheService, @inject("LocalisationService") protected localisationService: LocalisationService, @inject("BotGenerator") protected botGenerator: BotGenerator, - @inject("ConfigServer") protected configServer: ConfigServer + @inject("ConfigServer") protected configServer: ConfigServer, ) { this.playerScavConfig = this.configServer.getConfig(ConfigTypes.PLAYERSCAV); @@ -66,9 +66,9 @@ export class PlayerScavGenerator const existingScavData = this.jsonUtil.clone(profile.characters.scav); // scav profile can be empty on first profile creation - const scavKarmaLevel = ((Object.keys(existingScavData).length === 0)) - ? 0 - : this.getScavKarmaLevel(pmcData); + const scavKarmaLevel = (Object.keys(existingScavData).length === 0) ? + 0 : + this.getScavKarmaLevel(pmcData); // use karma level to get correct karmaSettings const playerScavKarmaSettings = this.playerScavConfig.karmaLevel[scavKarmaLevel]; @@ -83,7 +83,12 @@ export class PlayerScavGenerator const baseBotNode: IBotType = this.constructBotBaseTemplate(playerScavKarmaSettings.botTypeForLoot); this.adjustBotTemplateWithKarmaSpecificSettings(playerScavKarmaSettings, baseBotNode); - let scavData = this.botGenerator.generatePlayerScav(sessionID, playerScavKarmaSettings.botTypeForLoot.toLowerCase(), "easy", baseBotNode); + let scavData = this.botGenerator.generatePlayerScav( + sessionID, + playerScavKarmaSettings.botTypeForLoot.toLowerCase(), + "easy", + baseBotNode, + ); // Remove cached bot data after scav was generated this.botLootCacheService.clearCache(); @@ -113,7 +118,6 @@ export class PlayerScavGenerator scavData.Notes = existingScavData.Notes ?? {Notes: []}; scavData.WishList = existingScavData.WishList ?? []; - // Add an extra labs card to pscav backpack based on config chance if (this.randomUtil.getChance100(playerScavKarmaSettings.labsAccessCardChancePercent)) { @@ -121,9 +125,15 @@ export class PlayerScavGenerator const itemsToAdd: Item[] = [{ _id: this.hashUtil.generate(), _tpl: labsCard._id, - ...this.botGeneratorHelper.generateExtraPropertiesForItem(labsCard) + ...this.botGeneratorHelper.generateExtraPropertiesForItem(labsCard), }]; - this.botWeaponGeneratorHelper.addItemWithChildrenToEquipmentSlot(["TacticalVest", "Pockets", "Backpack"], itemsToAdd[0]._id, labsCard._id, itemsToAdd, scavData.Inventory); + this.botWeaponGeneratorHelper.addItemWithChildrenToEquipmentSlot( + ["TacticalVest", "Pockets", "Backpack"], + itemsToAdd[0]._id, + labsCard._id, + itemsToAdd, + scavData.Inventory, + ); } // Remove secure container @@ -251,7 +261,7 @@ export class PlayerScavGenerator return { Common: [], Mastering: [], - Points: 0 + Points: 0, }; } @@ -292,7 +302,7 @@ export class PlayerScavGenerator * take into account scav cooldown bonus * @param scavData scav profile * @param pmcData pmc profile - * @returns + * @returns */ protected setScavCooldownTimer(scavData: IPmcData, pmcData: IPmcData): IPmcData { @@ -314,7 +324,7 @@ export class PlayerScavGenerator const fenceInfo = this.fenceService.getFenceInfo(pmcData); modifier *= fenceInfo.SavageCooldownModifier; scavLockDuration *= modifier; - + const fullProfile = this.profileHelper.getFullProfile(pmcData?.sessionId); if (fullProfile?.info?.edition?.toLowerCase?.().startsWith?.(AccountTypes.SPT_DEVELOPER)) { @@ -323,7 +333,7 @@ export class PlayerScavGenerator } scavData.Info.SavageLockTime = (Date.now() / 1000) + scavLockDuration; - + return scavData; } -} \ No newline at end of file +} diff --git a/project/src/generators/RagfairAssortGenerator.ts b/project/src/generators/RagfairAssortGenerator.ts index 2da09e56..65e301f0 100644 --- a/project/src/generators/RagfairAssortGenerator.ts +++ b/project/src/generators/RagfairAssortGenerator.ts @@ -24,7 +24,7 @@ export class RagfairAssortGenerator @inject("ItemHelper") protected itemHelper: ItemHelper, @inject("DatabaseServer") protected databaseServer: DatabaseServer, @inject("SeasonalEventService") protected seasonalEventService: SeasonalEventService, - @inject("ConfigServer") protected configServer: ConfigServer + @inject("ConfigServer") protected configServer: ConfigServer, ) { this.ragfairConfig = this.configServer.getConfig(ConfigTypes.RAGFAIR); @@ -62,9 +62,9 @@ export class RagfairAssortGenerator const results: Item[] = []; const items = this.itemHelper.getItems(); - const weaponPresets = (this.ragfairConfig.dynamic.showDefaultPresetsOnly) - ? this.getDefaultPresets() - : this.getPresets(); + const weaponPresets = (this.ragfairConfig.dynamic.showDefaultPresetsOnly) ? + this.getDefaultPresets() : + this.getPresets(); const ragfairItemInvalidBaseTypes: string[] = [ BaseClasses.LOOT_CONTAINER, // safe, barrel cache etc @@ -72,7 +72,7 @@ export class RagfairAssortGenerator BaseClasses.SORTING_TABLE, BaseClasses.INVENTORY, BaseClasses.STATIONARY_CONTAINER, - BaseClasses.POCKETS + BaseClasses.POCKETS, ]; const seasonalEventActive = this.seasonalEventService.seasonalEventEnabled(); @@ -84,7 +84,10 @@ export class RagfairAssortGenerator continue; } - if (this.ragfairConfig.dynamic.removeSeasonalItemsWhenNotInEvent && !seasonalEventActive && seasonalItemTplBlacklist.includes(item._id)) + if ( + this.ragfairConfig.dynamic.removeSeasonalItemsWhenNotInEvent && !seasonalEventActive && + seasonalItemTplBlacklist.includes(item._id) + ) { continue; } @@ -99,7 +102,7 @@ export class RagfairAssortGenerator return results; } - + /** * Get presets from globals.json * @returns Preset object array @@ -116,9 +119,9 @@ export class RagfairAssortGenerator */ protected getDefaultPresets(): IPreset[] { - return this.getPresets().filter(x => x._encyclopedia); + return this.getPresets().filter((x) => x._encyclopedia); } - + /** * Create a base assort item and return it with populated values + 999999 stack count + unlimited count = true * @param tplId tplid to add to item @@ -134,8 +137,8 @@ export class RagfairAssortGenerator slotId: "hideout", upd: { StackObjectsCount: 99999999, - UnlimitedCount: true - } + UnlimitedCount: true, + }, }; } -} \ No newline at end of file +} diff --git a/project/src/generators/RagfairOfferGenerator.ts b/project/src/generators/RagfairOfferGenerator.ts index 612b71c2..5f08253e 100644 --- a/project/src/generators/RagfairOfferGenerator.ts +++ b/project/src/generators/RagfairOfferGenerator.ts @@ -33,7 +33,7 @@ import { TimeUtil } from "@spt-aki/utils/TimeUtil"; export class RagfairOfferGenerator { protected ragfairConfig: IRagfairConfig; - protected allowedFleaPriceItemsForBarter: { tpl: string; price: number; }[]; + protected allowedFleaPriceItemsForBarter: {tpl: string; price: number;}[]; constructor( @inject("WinstonLogger") protected logger: ILogger, @@ -54,7 +54,7 @@ export class RagfairOfferGenerator @inject("RagfairCategoriesService") protected ragfairCategoriesService: RagfairCategoriesService, @inject("FenceService") protected fenceService: FenceService, @inject("ItemHelper") protected itemHelper: ItemHelper, - @inject("ConfigServer") protected configServer: ConfigServer + @inject("ConfigServer") protected configServer: ConfigServer, ) { this.ragfairConfig = this.configServer.getConfig(ConfigTypes.RAGFAIR); @@ -70,7 +70,14 @@ export class RagfairOfferGenerator * @param sellInOnePiece Flags sellInOnePiece to be true * @returns IRagfairOffer */ - public createFleaOffer(userID: string, time: number, items: Item[], barterScheme: IBarterScheme[], loyalLevel: number, sellInOnePiece = false): IRagfairOffer + public createFleaOffer( + userID: string, + time: number, + items: Item[], + barterScheme: IBarterScheme[], + loyalLevel: number, + sellInOnePiece = false, + ): IRagfairOffer { const offer = this.createOffer(userID, time, items, barterScheme, loyalLevel, sellInOnePiece); this.ragfairOfferService.addOffer(offer); @@ -88,7 +95,14 @@ export class RagfairOfferGenerator * @param sellInOnePiece Set StackObjectsCount to 1 * @returns IRagfairOffer */ - protected createOffer(userID: string, time: number, items: Item[], barterScheme: IBarterScheme[], loyalLevel: number, sellInOnePiece = false): IRagfairOffer + protected createOffer( + userID: string, + time: number, + items: Item[], + barterScheme: IBarterScheme[], + loyalLevel: number, + sellInOnePiece = false, + ): IRagfairOffer { const isTrader = this.ragfairServerHelper.isTrader(userID); @@ -98,13 +112,13 @@ export class RagfairOfferGenerator const requirement: OfferRequirement = { _tpl: barter._tpl, count: +barter.count.toFixed(2), - onlyFunctional: barter.onlyFunctional ?? false + onlyFunctional: barter.onlyFunctional ?? false, }; offerRequirements.push(requirement); } - const itemCount = items.filter(x => x.slotId === "hideout").length; + const itemCount = items.filter((x) => x.slotId === "hideout").length; const roublePrice = Math.round(this.convertOfferRequirementsIntoRoubles(offerRequirements)); const offer: IRagfairOffer = { @@ -112,13 +126,13 @@ export class RagfairOfferGenerator intId: 0, user: { id: this.getTraderId(userID), - memberType: (userID === "ragfair") - ? MemberCategory.DEFAULT - : this.ragfairServerHelper.getMemberType(userID), + memberType: (userID === "ragfair") ? + MemberCategory.DEFAULT : + this.ragfairServerHelper.getMemberType(userID), nickname: this.ragfairServerHelper.getNickname(userID), rating: this.getRating(userID), isRatingGrowing: this.getRatingGrowing(userID), - avatar: this.getAvatarUrl(isTrader, userID) + avatar: this.getAvatarUrl(isTrader, userID), }, root: items[0]._id, items: this.jsonUtil.clone(items), @@ -134,7 +148,7 @@ export class RagfairOfferGenerator locked: false, unlimitedCount: false, notAvailable: false, - CurrentItemCount: itemCount + CurrentItemCount: itemCount, }; return offer; @@ -150,9 +164,9 @@ export class RagfairOfferGenerator let roublePrice = 0; for (const requirement of offerRequirements) { - roublePrice += this.paymentHelper.isMoneyTpl(requirement._tpl) - ? Math.round(this.calculateRoublePrice(requirement.count, requirement._tpl)) - : this.ragfairPriceService.getFleaPriceForItem(requirement._tpl) * requirement.count; // get flea price for barter offer items + roublePrice += this.paymentHelper.isMoneyTpl(requirement._tpl) ? + Math.round(this.calculateRoublePrice(requirement.count, requirement._tpl)) : + this.ragfairPriceService.getFleaPriceForItem(requirement._tpl) * requirement.count; // get flea price for barter offer items } return roublePrice; @@ -249,7 +263,7 @@ export class RagfairOfferGenerator return true; } - // generated offer + // generated offer // 50/50 growing/falling return this.randomUtil.getBool(); } @@ -275,7 +289,13 @@ export class RagfairOfferGenerator } // Generated fake-player offer - return Math.round(time + this.randomUtil.getInt(this.ragfairConfig.dynamic.endTimeSeconds.min, this.ragfairConfig.dynamic.endTimeSeconds.max)); + return Math.round( + time + + this.randomUtil.getInt( + this.ragfairConfig.dynamic.endTimeSeconds.min, + this.ragfairConfig.dynamic.endTimeSeconds.max, + ), + ); } /** @@ -287,28 +307,34 @@ export class RagfairOfferGenerator const config = this.ragfairConfig.dynamic; // get assort items from param if they exist, otherwise grab freshly generated assorts - const assortItemsToProcess: Item[] = (expiredOffers) - ? expiredOffers - : this.ragfairAssortGenerator.getAssortItems(); + const assortItemsToProcess: Item[] = expiredOffers ? + expiredOffers : + this.ragfairAssortGenerator.getAssortItems(); // Store all functions to create an offer for every item and pass into Promise.all to run async const assorOffersForItemsProcesses = []; for (const assortItemIndex in assortItemsToProcess) { - assorOffersForItemsProcesses.push(this.createOffersForItems(assortItemIndex, assortItemsToProcess, expiredOffers, config)); + assorOffersForItemsProcesses.push( + this.createOffersForItems(assortItemIndex, assortItemsToProcess, expiredOffers, config), + ); } await Promise.all(assorOffersForItemsProcesses); } /** - * * @param assortItemIndex Index of assort item * @param assortItemsToProcess Item array containing index * @param expiredOffers Currently expired offers on flea * @param config Ragfair dynamic config */ - protected async createOffersForItems(assortItemIndex: string, assortItemsToProcess: Item[], expiredOffers: Item[], config: Dynamic): Promise + protected async createOffersForItems( + assortItemIndex: string, + assortItemsToProcess: Item[], + expiredOffers: Item[], + config: Dynamic, + ): Promise { const assortItem = assortItemsToProcess[assortItemIndex]; const itemDetails = this.itemHelper.getItem(assortItem._tpl); @@ -322,15 +348,21 @@ export class RagfairOfferGenerator } // Get item + sub-items if preset, otherwise just get item - const items: Item[] = (isPreset) - ? this.ragfairServerHelper.getPresetItems(assortItem) - : [...[assortItem], ...this.itemHelper.findAndReturnChildrenByAssort(assortItem._id, this.ragfairAssortGenerator.getAssortItems())]; + const items: Item[] = isPreset ? + this.ragfairServerHelper.getPresetItems(assortItem) : + [ + ...[assortItem], + ...this.itemHelper.findAndReturnChildrenByAssort( + assortItem._id, + this.ragfairAssortGenerator.getAssortItems(), + ), + ]; // Get number of offers to create // Limit to 1 offer when processing expired - const offerCount = (expiredOffers) - ? 1 - : Math.round(this.randomUtil.getInt(config.offerItemCount.min, config.offerItemCount.max)); + const offerCount = expiredOffers ? + 1 : + Math.round(this.randomUtil.getInt(config.offerItemCount.min, config.offerItemCount.max)); // Store all functions to create offers for this item and pass into Promise.all to run async const assortSingleOfferProcesses = []; @@ -342,7 +374,6 @@ export class RagfairOfferGenerator await Promise.all(assortSingleOfferProcesses); } - /** * Create one flea offer for a specific item * @param items Item to create offer for @@ -350,23 +381,30 @@ export class RagfairOfferGenerator * @param itemDetails raw db item details * @returns Item array */ - protected async createSingleOfferForItem(items: Item[], isPreset: boolean, itemDetails: [boolean, ITemplateItem]): Promise + protected async createSingleOfferForItem( + items: Item[], + isPreset: boolean, + itemDetails: [boolean, ITemplateItem], + ): Promise { // Set stack size to random value items[0].upd.StackObjectsCount = this.ragfairServerHelper.calculateDynamicStackCount(items[0]._tpl, isPreset); - + const isBarterOffer = this.randomUtil.getChance100(this.ragfairConfig.dynamic.barter.chancePercent); - const isPackOffer = this.randomUtil.getChance100(this.ragfairConfig.dynamic.pack.chancePercent) - && !isBarterOffer - && items.length === 1 - && this.itemHelper.isOfBaseclasses(items[0]._tpl, this.ragfairConfig.dynamic.pack.itemTypeWhitelist); + const isPackOffer = this.randomUtil.getChance100(this.ragfairConfig.dynamic.pack.chancePercent) && + !isBarterOffer && + items.length === 1 && + this.itemHelper.isOfBaseclasses(items[0]._tpl, this.ragfairConfig.dynamic.pack.itemTypeWhitelist); const randomUserId = this.hashUtil.generate(); let barterScheme: IBarterScheme[]; if (isPackOffer) { // Set pack size - const stackSize = this.randomUtil.getInt(this.ragfairConfig.dynamic.pack.itemCountMin, this.ragfairConfig.dynamic.pack.itemCountMax); + const stackSize = this.randomUtil.getInt( + this.ragfairConfig.dynamic.pack.itemCountMin, + this.ragfairConfig.dynamic.pack.itemCountMax, + ); items[0].upd.StackObjectsCount = stackSize; // Don't randomise pack items @@ -391,7 +429,8 @@ export class RagfairOfferGenerator items, barterScheme, 1, - isPreset || isPackOffer); // sellAsOnePiece + isPreset || isPackOffer, + ); // sellAsOnePiece this.ragfairCategoriesService.incrementCategory(offer); } @@ -413,7 +452,12 @@ export class RagfairOfferGenerator // Trader assorts / assort items are missing if (!assorts?.items?.length) { - this.logger.error(this.localisationService.getText("ragfair-no_trader_assorts_cant_generate_flea_offers", trader.base.nickname)); + this.logger.error( + this.localisationService.getText( + "ragfair-no_trader_assorts_cant_generate_flea_offers", + trader.base.nickname, + ), + ); return; } @@ -444,14 +488,20 @@ export class RagfairOfferGenerator } const isPreset = this.presetHelper.isPreset(item._id); - const items: Item[] = (isPreset) - ? this.ragfairServerHelper.getPresetItems(item) - : [...[item], ...this.itemHelper.findAndReturnChildrenByAssort(item._id, assorts.items)]; + const items: Item[] = isPreset ? + this.ragfairServerHelper.getPresetItems(item) : + [...[item], ...this.itemHelper.findAndReturnChildrenByAssort(item._id, assorts.items)]; const barterScheme = assorts.barter_scheme[item._id]; if (!barterScheme) { - this.logger.warning(this.localisationService.getText("ragfair-missing_barter_scheme", {itemId: item._id, tpl: item._tpl, name: trader.base.nickname})); + this.logger.warning( + this.localisationService.getText("ragfair-missing_barter_scheme", { + itemId: item._id, + tpl: item._tpl, + name: trader.base.nickname, + }), + ); continue; } @@ -473,11 +523,11 @@ export class RagfairOfferGenerator * @param userID id of owner of item * @param itemWithMods Item and mods, get condition of first item (only first array item is used) * @param itemDetails db details of first item - * @returns + * @returns */ protected randomiseItemUpdProperties(userID: string, itemWithMods: Item[], itemDetails: ITemplateItem): Item[] { - // Add any missing properties to first item in array + // Add any missing properties to first item in array itemWithMods[0] = this.addMissingConditions(itemWithMods[0]); if (!(this.ragfairServerHelper.isPlayer(userID) || this.ragfairServerHelper.isTrader(userID))) @@ -508,9 +558,9 @@ export class RagfairOfferGenerator { // Get keys from condition config dictionary const configConditions = Object.keys(this.ragfairConfig.dynamic.condition); - for (const baseClass of configConditions) + for (const baseClass of configConditions) { - if (this.itemHelper.isOfBaseclass(tpl, baseClass)) + if (this.itemHelper.isOfBaseclass(tpl, baseClass)) { return baseClass; } @@ -527,7 +577,10 @@ export class RagfairOfferGenerator */ protected randomiseItemCondition(conditionSettingsId: string, item: Item, itemDetails: ITemplateItem): void { - const multiplier = this.randomUtil.getFloat(this.ragfairConfig.dynamic.condition[conditionSettingsId].min, this.ragfairConfig.dynamic.condition[conditionSettingsId].max); + const multiplier = this.randomUtil.getFloat( + this.ragfairConfig.dynamic.condition[conditionSettingsId].min, + this.ragfairConfig.dynamic.condition[conditionSettingsId].max, + ); // Armor or weapons if (item.upd.Repairable) @@ -571,7 +624,7 @@ export class RagfairOfferGenerator return; } - if (item.upd.RepairKit) + if (item.upd.RepairKit) { // randomize repair kit (armor/weapon) uses item.upd.RepairKit.Resource = Math.round(itemDetails._props.MaxRepairResource * multiplier) || 1; @@ -585,7 +638,7 @@ export class RagfairOfferGenerator const remainingFuel = Math.round(totalCapacity * multiplier); item.upd.Resource = { UnitsConsumed: totalCapacity - remainingFuel, - Value: remainingFuel + Value: remainingFuel, }; } } @@ -600,7 +653,9 @@ export class RagfairOfferGenerator item.upd.Repairable.Durability = Math.round(item.upd.Repairable.Durability * multiplier) || 1; // randomize max durability, store to a temporary value so we can still compare the max durability - let tempMaxDurability = Math.round(this.randomUtil.getFloat(item.upd.Repairable.Durability - 5, item.upd.Repairable.MaxDurability + 5)) || item.upd.Repairable.Durability; + let tempMaxDurability = Math.round( + this.randomUtil.getFloat(item.upd.Repairable.Durability - 5, item.upd.Repairable.MaxDurability + 5), + ) || item.upd.Repairable.Durability; // clamp values to max/current if (tempMaxDurability >= item.upd.Repairable.MaxDurability) @@ -626,45 +681,45 @@ export class RagfairOfferGenerator protected addMissingConditions(item: Item): Item { const props = this.itemHelper.getItem(item._tpl)[1]._props; - const isRepairable = ("Durability" in props); - const isMedkit = ("MaxHpResource" in props); - const isKey = ("MaximumNumberOfUsage" in props); - const isConsumable = (props.MaxResource > 1 && "foodUseTime" in props); - const isRepairKit = ("MaxRepairResource" in props); + const isRepairable = "Durability" in props; + const isMedkit = "MaxHpResource" in props; + const isKey = "MaximumNumberOfUsage" in props; + const isConsumable = props.MaxResource > 1 && "foodUseTime" in props; + const isRepairKit = "MaxRepairResource" in props; if (isRepairable && props.Durability > 0) { item.upd.Repairable = { Durability: props.Durability, - MaxDurability: props.Durability + MaxDurability: props.Durability, }; } if (isMedkit && props.MaxHpResource > 0) { item.upd.MedKit = { - HpResource: props.MaxHpResource + HpResource: props.MaxHpResource, }; } - if (isKey) + if (isKey) { item.upd.Key = { - NumberOfUsages: 0 + NumberOfUsages: 0, }; } - if (isConsumable) + if (isConsumable) { item.upd.FoodDrink = { - HpPercent: props.MaxResource + HpPercent: props.MaxResource, }; } - if (isRepairKit) + if (isRepairKit) { item.upd.RepairKit = { - Resource: props.MaxRepairResource + Resource: props.MaxRepairResource, }; } @@ -679,7 +734,11 @@ export class RagfairOfferGenerator protected createBarterBarterScheme(offerItems: Item[]): IBarterScheme[] { // get flea price of item being sold - const priceOfItemOffer = this.ragfairPriceService.getDynamicOfferPriceForOffer(offerItems, Money.ROUBLES, false); + const priceOfItemOffer = this.ragfairPriceService.getDynamicOfferPriceForOffer( + offerItems, + Money.ROUBLES, + false, + ); // Dont make items under a designated rouble value into barter offers if (priceOfItemOffer < this.ragfairConfig.dynamic.barter.minRoubleCostToBecomeBarter) @@ -688,7 +747,10 @@ export class RagfairOfferGenerator } // Get a randomised number of barter items to list offer for - const barterItemCount = this.randomUtil.getInt(this.ragfairConfig.dynamic.barter.itemCountMin, this.ragfairConfig.dynamic.barter.itemCountMax); + const barterItemCount = this.randomUtil.getInt( + this.ragfairConfig.dynamic.barter.itemCountMin, + this.ragfairConfig.dynamic.barter.itemCountMax, + ); // Get desired cost of individual item offer will be listed for e.g. offer = 15k, item count = 3, desired item cost = 5k const desiredItemCost = Math.round(priceOfItemOffer / barterItemCount); @@ -699,7 +761,10 @@ export class RagfairOfferGenerator const fleaPrices = this.getFleaPricesAsArray(); // Filter possible barters to items that match the price range + not itself - const filtered = fleaPrices.filter(x => x.price >= desiredItemCost - offerCostVariance && x.price <= desiredItemCost + offerCostVariance && x.tpl !== offerItems[0]._tpl); + const filtered = fleaPrices.filter((x) => + x.price >= desiredItemCost - offerCostVariance && x.price <= desiredItemCost + offerCostVariance && + x.tpl !== offerItems[0]._tpl + ); // No items on flea have a matching price, fall back to currency if (filtered.length === 0) @@ -713,8 +778,8 @@ export class RagfairOfferGenerator return [ { count: barterItemCount, - _tpl: randomItem.tpl - } + _tpl: randomItem.tpl, + }, ]; } @@ -722,18 +787,20 @@ export class RagfairOfferGenerator * Get an array of flea prices + item tpl, cached in generator class inside `allowedFleaPriceItemsForBarter` * @returns array with tpl/price values */ - protected getFleaPricesAsArray(): { tpl: string; price: number; }[] + protected getFleaPricesAsArray(): {tpl: string; price: number;}[] { // Generate if needed if (!this.allowedFleaPriceItemsForBarter) { const fleaPrices = this.databaseServer.getTables().templates.prices; - const fleaArray = Object.entries(fleaPrices).map(([tpl, price]) => ({ tpl: tpl, price: price })); + const fleaArray = Object.entries(fleaPrices).map(([tpl, price]) => ({tpl: tpl, price: price})); // Only get item prices for items that also exist in items.json - const filteredItems = fleaArray.filter(x => this.itemHelper.getItem(x.tpl)[0]); + const filteredItems = fleaArray.filter((x) => this.itemHelper.getItem(x.tpl)[0]); - this.allowedFleaPriceItemsForBarter = filteredItems.filter(x => !this.itemHelper.isOfBaseclasses(x.tpl, this.ragfairConfig.dynamic.barter.itemTypeBlacklist)); + this.allowedFleaPriceItemsForBarter = filteredItems.filter((x) => + !this.itemHelper.isOfBaseclasses(x.tpl, this.ragfairConfig.dynamic.barter.itemTypeBlacklist) + ); } return this.allowedFleaPriceItemsForBarter; @@ -749,13 +816,14 @@ export class RagfairOfferGenerator protected createCurrencyBarterScheme(offerItems: Item[], isPackOffer: boolean, multipler = 1): IBarterScheme[] { const currency = this.ragfairServerHelper.getDynamicOfferCurrency(); - const price = this.ragfairPriceService.getDynamicOfferPriceForOffer(offerItems, currency, isPackOffer) * multipler; + const price = this.ragfairPriceService.getDynamicOfferPriceForOffer(offerItems, currency, isPackOffer) * + multipler; return [ { count: price, - _tpl: currency - } + _tpl: currency, + }, ]; } -} \ No newline at end of file +} diff --git a/project/src/generators/RepeatableQuestGenerator.ts b/project/src/generators/RepeatableQuestGenerator.ts index 0b7d5372..40f608c2 100644 --- a/project/src/generators/RepeatableQuestGenerator.ts +++ b/project/src/generators/RepeatableQuestGenerator.ts @@ -15,16 +15,25 @@ import { IEliminationCondition, IEquipmentConditionProps, IExploration, - IExplorationCondition, IKillConditionProps, + IExplorationCondition, + IKillConditionProps, IPickup, - IRepeatableQuest, IReward, IRewards + IRepeatableQuest, + IReward, + IRewards, } from "@spt-aki/models/eft/common/tables/IRepeatableQuests"; import { ITemplateItem } 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 { Money } from "@spt-aki/models/enums/Money"; import { Traders } from "@spt-aki/models/enums/Traders"; -import { IBaseQuestConfig, IBossInfo, IEliminationConfig, IQuestConfig, IRepeatableQuestConfig } from "@spt-aki/models/spt/config/IQuestConfig"; +import { + IBaseQuestConfig, + IBossInfo, + IEliminationConfig, + IQuestConfig, + IRepeatableQuestConfig, +} from "@spt-aki/models/spt/config/IQuestConfig"; import { IQuestTypePool } from "@spt-aki/models/spt/repeatable/IQuestTypePool"; import { ILogger } from "@spt-aki/models/spt/utils/ILogger"; import { EventOutputHolder } from "@spt-aki/routers/EventOutputHolder"; @@ -66,7 +75,7 @@ export class RepeatableQuestGenerator @inject("ObjectId") protected objectId: ObjectId, @inject("ItemFilterService") protected itemFilterService: ItemFilterService, @inject("RepeatableQuestHelper") protected repeatableQuestHelper: RepeatableQuestHelper, - @inject("ConfigServer") protected configServer: ConfigServer + @inject("ConfigServer") protected configServer: ConfigServer, ) { this.questConfig = this.configServer.getConfig(ConfigTypes.QUEST); @@ -85,15 +94,17 @@ export class RepeatableQuestGenerator pmcLevel: number, pmcTraderInfo: Record, questTypePool: IQuestTypePool, - repeatableConfig: IRepeatableQuestConfig + repeatableConfig: IRepeatableQuestConfig, ): IRepeatableQuest { const questType = this.randomUtil.drawRandomFromList(questTypePool.types)[0]; // get traders from whitelist and filter by quest type availability - let traders = repeatableConfig.traderWhitelist.filter(x => x.questTypes.includes(questType)).map(x => x.traderId); + let traders = repeatableConfig.traderWhitelist.filter((x) => x.questTypes.includes(questType)).map((x) => + x.traderId + ); // filter out locked traders - traders = traders.filter(x => pmcTraderInfo[x].unlocked); + traders = traders.filter((x) => pmcTraderInfo[x].unlocked); const traderId = this.randomUtil.drawRandomFromList(traders)[0]; switch (questType) @@ -123,15 +134,19 @@ export class RepeatableQuestGenerator pmcLevel: number, traderId: string, questTypePool: IQuestTypePool, - repeatableConfig: IRepeatableQuestConfig + repeatableConfig: IRepeatableQuestConfig, ): IElimination { const eliminationConfig = this.repeatableQuestHelper.getEliminationConfigByPmcLevel(pmcLevel, repeatableConfig); const locationsConfig = repeatableConfig.locations; let targetsConfig = this.repeatableQuestHelper.probabilityObjectArray(eliminationConfig.targets); const bodypartsConfig = this.repeatableQuestHelper.probabilityObjectArray(eliminationConfig.bodyParts); - const weaponCategoryRequirementConfig = this.repeatableQuestHelper.probabilityObjectArray(eliminationConfig.weaponCategoryRequirements); - const weaponRequirementConfig = this.repeatableQuestHelper.probabilityObjectArray(eliminationConfig.weaponRequirements); + const weaponCategoryRequirementConfig = this.repeatableQuestHelper.probabilityObjectArray( + eliminationConfig.weaponCategoryRequirements, + ); + const weaponRequirementConfig = this.repeatableQuestHelper.probabilityObjectArray( + eliminationConfig.weaponRequirements, + ); // the difficulty of the quest varies in difficulty depending on the condition // possible conditions are @@ -146,7 +161,7 @@ export class RepeatableQuestGenerator // Savage: 7, // AnyPmc: 2, // bossBully: 0.5 - //} + // } // higher is more likely. We define the difficulty to be the inverse of the relative probability. // We want to generate a reward which is scaled by the difficulty of this mission. To get a upper bound with which we scale @@ -165,18 +180,26 @@ export class RepeatableQuestGenerator const maxKillDifficulty = eliminationConfig.maxKills; - function difficultyWeighing(target: number, bodyPart: number, dist: number, kill: number, weaponRequirement: number): number + function difficultyWeighing( + target: number, + bodyPart: number, + dist: number, + kill: number, + weaponRequirement: number, + ): number { return Math.sqrt(Math.sqrt(target) + bodyPart + dist + weaponRequirement) * kill; } - targetsConfig = targetsConfig.filter(x => Object.keys(questTypePool.pool.Elimination.targets).includes(x.key)); - if (targetsConfig.length === 0 || targetsConfig.every(x => x.data.isBoss)) + targetsConfig = targetsConfig.filter((x) => + Object.keys(questTypePool.pool.Elimination.targets).includes(x.key) + ); + if (targetsConfig.length === 0 || targetsConfig.every((x) => x.data.isBoss)) { // There are no more targets left for elimination; delete it as a possible quest type // also if only bosses are left we need to leave otherwise it's a guaranteed boss elimination // -> then it would not be a quest with low probability anymore - questTypePool.types = questTypePool.types.filter(t => t !== "Elimination"); + questTypePool.types = questTypePool.types.filter((t) => t !== "Elimination"); return null; } @@ -188,18 +211,23 @@ export class RepeatableQuestGenerator // we use any as location if "any" is in the pool and we do not hit the specific location random // we use any also if the random condition is not met in case only "any" was in the pool let locationKey = "any"; - if (locations.includes("any") && (eliminationConfig.specificLocationProb < Math.random() || locations.length <= 1)) + if ( + locations.includes("any") && + (eliminationConfig.specificLocationProb < Math.random() || locations.length <= 1) + ) { locationKey = "any"; delete questTypePool.pool.Elimination.targets[targetKey]; } else { - locations = locations.filter(l => l !== "any"); + locations = locations.filter((l) => l !== "any"); if (locations.length > 0) { locationKey = this.randomUtil.drawRandomFromList(locations)[0]; - questTypePool.pool.Elimination.targets[targetKey].locations = locations.filter(l => l !== locationKey); + questTypePool.pool.Elimination.targets[targetKey].locations = locations.filter((l) => + l !== locationKey + ); if (questTypePool.pool.Elimination.targets[targetKey].locations.length === 0) { delete questTypePool.pool.Elimination.targets[targetKey]; @@ -243,15 +271,17 @@ export class RepeatableQuestGenerator if (targetsConfig.data(targetKey).isBoss) { // get all boss spawn information - const bossSpawns = Object.values(this.databaseServer.getTables().locations).filter(x => "base" in x && "Id" in x.base).map( - (x) => ({ Id: x.base.Id, BossSpawn: x.base.BossLocationSpawn }) + const bossSpawns = Object.values(this.databaseServer.getTables().locations).filter((x) => + "base" in x && "Id" in x.base + ).map( + (x) => ({Id: x.base.Id, BossSpawn: x.base.BossLocationSpawn}), ); // filter for the current boss to spawn on map const thisBossSpawns = bossSpawns.map( - (x) => ({ Id: x.Id, BossSpawn: x.BossSpawn.filter(e => e.BossName === targetKey) }) - ).filter(x => x.BossSpawn.length > 0); + (x) => ({Id: x.Id, BossSpawn: x.BossSpawn.filter((e) => e.BossName === targetKey)}), + ).filter((x) => x.BossSpawn.length > 0); // remove blacklisted locations - const allowedSpawns = thisBossSpawns.filter(x => !eliminationConfig.distLocationBlacklist.includes(x.Id)); + const allowedSpawns = thisBossSpawns.filter((x) => !eliminationConfig.distLocationBlacklist.includes(x.Id)); // if the boss spawns on nom-blacklisted locations and the current location is allowed we can generate a distance kill requirement isDistanceRequirementAllowed = isDistanceRequirementAllowed && (allowedSpawns.length > 0); } @@ -259,7 +289,10 @@ export class RepeatableQuestGenerator if (eliminationConfig.distProb > Math.random() && isDistanceRequirementAllowed) { // random distance with lower values more likely; simple distribution for starters... - distance = Math.floor(Math.abs(Math.random() - Math.random()) * (1 + eliminationConfig.maxDist - eliminationConfig.minDist) + eliminationConfig.minDist); + distance = Math.floor( + Math.abs(Math.random() - Math.random()) * (1 + eliminationConfig.maxDist - eliminationConfig.minDist) + + eliminationConfig.minDist, + ); distance = Math.ceil(distance / 5) * 5; distanceDifficulty = maxDistDifficulty * distance / eliminationConfig.maxDist; } @@ -296,7 +329,7 @@ export class RepeatableQuestGenerator bodyPartDifficulty / maxBodyPartsDifficulty, distanceDifficulty / maxDistDifficulty, killDifficulty / maxKillDifficulty, - (allowedWeaponsCategory || allowedWeapon) ? 1 : 0 + (allowedWeaponsCategory || allowedWeapon) ? 1 : 0, ); // Aforementioned issue makes it a bit crazy since now all easier quests give significantly lower rewards than Completion / Exploration @@ -305,7 +338,7 @@ export class RepeatableQuestGenerator const difficulty = this.mathUtil.mapToRange(curDifficulty, minDifficulty, maxDifficulty, 0.5, 2); const quest = this.generateRepeatableTemplate("Elimination", traderId, repeatableConfig.side) as IElimination; - + // ASSUMPTION: All fence quests are for scavs if (traderId === Traders.FENCE) { @@ -319,14 +352,30 @@ export class RepeatableQuestGenerator // Only add specific location condition if specific map selected if (locationKey !== "any") { - availableForFinishCondition._props.counter.conditions.push(this.generateEliminationLocation(locationsConfig[locationKey])); + availableForFinishCondition._props.counter.conditions.push( + this.generateEliminationLocation(locationsConfig[locationKey]), + ); } - availableForFinishCondition._props.counter.conditions.push(this.generateEliminationCondition(targetKey, bodyPartsToClient, distance, allowedWeapon, allowedWeaponsCategory)); + availableForFinishCondition._props.counter.conditions.push( + this.generateEliminationCondition( + targetKey, + bodyPartsToClient, + distance, + allowedWeapon, + allowedWeaponsCategory, + ), + ); availableForFinishCondition._props.value = desiredKillCount; availableForFinishCondition._props.id = this.objectId.generate(); quest.location = this.getQuestLocationByMapId(locationKey); - quest.rewards = this.generateReward(pmcLevel, Math.min(difficulty, 1), traderId, repeatableConfig, eliminationConfig); + quest.rewards = this.generateReward( + pmcLevel, + Math.min(difficulty, 1), + traderId, + repeatableConfig, + eliminationConfig, + ); return quest; } @@ -338,7 +387,11 @@ export class RepeatableQuestGenerator * @param eliminationConfig Config * @returns Number of AI to kill */ - protected getEliminationKillCount(targetKey: string, targetsConfig: ProbabilityObjectArray, eliminationConfig: IEliminationConfig): number + protected getEliminationKillCount( + targetKey: string, + targetsConfig: ProbabilityObjectArray, + eliminationConfig: IEliminationConfig, + ): number { if (targetsConfig.data(targetKey).isBoss) { @@ -366,11 +419,11 @@ export class RepeatableQuestGenerator _props: { target: location, id: this.objectId.generate(), - dynamicLocale: true + dynamicLocale: true, }, - _parent: "Location" + _parent: "Location", }; - + return propsObject; } @@ -383,13 +436,19 @@ export class RepeatableQuestGenerator * @param allowedWeaponCategory What category of weapon must be used - undefined = any * @returns IEliminationCondition object */ - protected generateEliminationCondition(target: string, targetedBodyParts: string[], distance: number, allowedWeapon: string, allowedWeaponCategory: string): IEliminationCondition + protected generateEliminationCondition( + target: string, + targetedBodyParts: string[], + distance: number, + allowedWeapon: string, + allowedWeaponCategory: string, + ): IEliminationCondition { const killConditionProps: IKillConditionProps = { target: target, value: 1, id: this.objectId.generate(), - dynamicLocale: true + dynamicLocale: true, }; if (target.startsWith("boss")) @@ -409,7 +468,7 @@ export class RepeatableQuestGenerator { killConditionProps.distance = { compareMethod: ">=", - value: distance + value: distance, }; } @@ -427,7 +486,7 @@ export class RepeatableQuestGenerator return { _props: killConditionProps, - _parent: "Kills" + _parent: "Kills", }; } @@ -442,7 +501,7 @@ export class RepeatableQuestGenerator protected generateCompletionQuest( pmcLevel: number, traderId: string, - repeatableConfig: IRepeatableQuestConfig + repeatableConfig: IRepeatableQuestConfig, ): ICompletion { const completionConfig = repeatableConfig.questConfig.Completion; @@ -456,48 +515,64 @@ export class RepeatableQuestGenerator numberDistinctItems = 2; } - const quest = this.generateRepeatableTemplate("Completion", traderId,repeatableConfig.side) as ICompletion; + const quest = this.generateRepeatableTemplate("Completion", traderId, repeatableConfig.side) as ICompletion; // Filter the items.json items to items the player must retrieve to complete queist: shouldn't be a quest item or "non-existant" let itemSelection = this.getRewardableItems(repeatableConfig); // Be fair, don't let the items be more expensive than the reward - let roublesBudget = Math.floor(this.mathUtil.interp1(pmcLevel, levelsConfig, roublesConfig) * this.randomUtil.getFloat(0.5, 1)); + let roublesBudget = Math.floor( + this.mathUtil.interp1(pmcLevel, levelsConfig, roublesConfig) * this.randomUtil.getFloat(0.5, 1), + ); roublesBudget = Math.max(roublesBudget, 5000); - itemSelection = itemSelection.filter(x => this.itemHelper.getItemPrice(x[0]) < roublesBudget); + itemSelection = itemSelection.filter((x) => this.itemHelper.getItemPrice(x[0]) < roublesBudget); // We also have the option to use whitelist and/or blacklist which is defined in repeatableQuests.json as // [{minPlayerLevel: 1, itemIds: ["id1",...]}, {minPlayerLevel: 15, itemIds: ["id3",...]}] if (repeatableConfig.questConfig.Completion.useWhitelist) { - const itemWhitelist = this.databaseServer.getTables().templates.repeatableQuests.data.Completion.itemsWhitelist; + const itemWhitelist = + this.databaseServer.getTables().templates.repeatableQuests.data.Completion.itemsWhitelist; // Filter and concatenate the arrays according to current player level - const itemIdsWhitelisted = itemWhitelist.filter(p => p.minPlayerLevel <= pmcLevel).reduce((a, p) => a.concat(p.itemIds), []); - itemSelection = itemSelection.filter(x => + const itemIdsWhitelisted = itemWhitelist.filter((p) => p.minPlayerLevel <= pmcLevel).reduce( + (a, p) => a.concat(p.itemIds), + [], + ); + itemSelection = itemSelection.filter((x) => { // Whitelist can contain item tpls and item base type ids - return (itemIdsWhitelisted.some(v => this.itemHelper.isOfBaseclass(x[0], v)) || itemIdsWhitelisted.includes(x[0])); + return (itemIdsWhitelisted.some((v) => this.itemHelper.isOfBaseclass(x[0], v)) || + itemIdsWhitelisted.includes(x[0])); }); // check if items are missing - //const flatList = itemSelection.reduce((a, il) => a.concat(il[0]), []); - //const missing = itemIdsWhitelisted.filter(l => !flatList.includes(l)); + // const flatList = itemSelection.reduce((a, il) => a.concat(il[0]), []); + // const missing = itemIdsWhitelisted.filter(l => !flatList.includes(l)); } if (repeatableConfig.questConfig.Completion.useBlacklist) { - const itemBlacklist = this.databaseServer.getTables().templates.repeatableQuests.data.Completion.itemsBlacklist; + const itemBlacklist = + this.databaseServer.getTables().templates.repeatableQuests.data.Completion.itemsBlacklist; // we filter and concatenate the arrays according to current player level - const itemIdsBlacklisted = itemBlacklist.filter(p => p.minPlayerLevel <= pmcLevel).reduce((a, p) => a.concat(p.itemIds), []); - itemSelection = itemSelection.filter(x => + const itemIdsBlacklisted = itemBlacklist.filter((p) => p.minPlayerLevel <= pmcLevel).reduce( + (a, p) => a.concat(p.itemIds), + [], + ); + itemSelection = itemSelection.filter((x) => { - return itemIdsBlacklisted.every(v => !this.itemHelper.isOfBaseclass(x[0], v)) || !itemIdsBlacklisted.includes(x[0]); + return itemIdsBlacklisted.every((v) => !this.itemHelper.isOfBaseclass(x[0], v)) || + !itemIdsBlacklisted.includes(x[0]); }); } if (itemSelection.length === 0) { - this.logger.error(this.localisationService.getText("repeatable-completion_quest_whitelist_too_small_or_blacklist_too_restrictive")); + this.logger.error( + this.localisationService.getText( + "repeatable-completion_quest_whitelist_too_small_or_blacklist_too_restrictive", + ), + ); return null; } @@ -532,7 +607,7 @@ export class RepeatableQuestGenerator if (roublesBudget > 0) { // reduce the list possible items to fulfill the new budget constraint - itemSelection = itemSelection.filter(x => this.itemHelper.getItemPrice(x[0]) < roublesBudget); + itemSelection = itemSelection.filter((x) => this.itemHelper.getItemPrice(x[0]) < roublesBudget); if (itemSelection.length === 0) { break; @@ -561,12 +636,18 @@ export class RepeatableQuestGenerator { let minDurability = 0; let onlyFoundInRaid = true; - if (this.itemHelper.isOfBaseclass(targetItemId, BaseClasses.WEAPON) || this.itemHelper.isOfBaseclass(targetItemId, BaseClasses.ARMOR)) + if ( + this.itemHelper.isOfBaseclass(targetItemId, BaseClasses.WEAPON) || + this.itemHelper.isOfBaseclass(targetItemId, BaseClasses.ARMOR) + ) { minDurability = 80; } - if (this.itemHelper.isOfBaseclass(targetItemId, BaseClasses.DOG_TAG_USEC) || this.itemHelper.isOfBaseclass(targetItemId, BaseClasses.DOG_TAG_BEAR)) + if ( + this.itemHelper.isOfBaseclass(targetItemId, BaseClasses.DOG_TAG_USEC) || + this.itemHelper.isOfBaseclass(targetItemId, BaseClasses.DOG_TAG_BEAR) + ) { onlyFoundInRaid = false; } @@ -583,10 +664,10 @@ export class RepeatableQuestGenerator minDurability: minDurability, maxDurability: 100, dogtagLevel: 0, - onlyFoundInRaid: onlyFoundInRaid + onlyFoundInRaid: onlyFoundInRaid, }, _parent: "HandoverItem", - dynamicLocale: true + dynamicLocale: true, }; } @@ -603,7 +684,7 @@ export class RepeatableQuestGenerator pmcLevel: number, traderId: string, questTypePool: IQuestTypePool, - repeatableConfig: IRepeatableQuestConfig + repeatableConfig: IRepeatableQuestConfig, ): IExploration { const explorationConfig = repeatableConfig.questConfig.Exploration; @@ -611,7 +692,7 @@ export class RepeatableQuestGenerator if (Object.keys(questTypePool.pool.Exploration.locations).length === 0) { // there are no more locations left for exploration; delete it as a possible quest type - questTypePool.types = questTypePool.types.filter(t => t !== "Exploration"); + questTypePool.types = questTypePool.types.filter((t) => t !== "Exploration"); return null; } @@ -625,7 +706,7 @@ export class RepeatableQuestGenerator const numExtracts = this.randomUtil.randInt(1, explorationConfig.maxExtracts + 1); - const quest = this.generateRepeatableTemplate("Exploration", traderId,repeatableConfig.side) as IExploration; + const quest = this.generateRepeatableTemplate("Exploration", traderId, repeatableConfig.side) as IExploration; const exitStatusCondition: IExplorationCondition = { _parent: "ExitStatus", @@ -633,23 +714,23 @@ export class RepeatableQuestGenerator id: this.objectId.generate(), dynamicLocale: true, status: [ - "Survived" - ] - } + "Survived", + ], + }, }; const locationCondition: IExplorationCondition = { _parent: "Location", _props: { id: this.objectId.generate(), dynamicLocale: true, - target: locationTarget - } + target: locationTarget, + }, }; quest.conditions.AvailableForFinish[0]._props.counter.id = this.objectId.generate(); quest.conditions.AvailableForFinish[0]._props.counter.conditions = [ exitStatusCondition, - locationCondition + locationCondition, ]; quest.conditions.AvailableForFinish[0]._props.value = numExtracts; quest.conditions.AvailableForFinish[0]._props.id = this.objectId.generate(); @@ -659,11 +740,15 @@ export class RepeatableQuestGenerator { // Filter by whitelist, it's also possible that the field "PassageRequirement" does not exist (e.g. Shoreline) // Scav exits are not listed at all in locations.base currently. If that changes at some point, additional filtering will be required - const mapExits = (this.databaseServer.getTables().locations[locationKey.toLowerCase()].base as ILocationBase).exits; + const mapExits = + (this.databaseServer.getTables().locations[locationKey.toLowerCase()].base as ILocationBase).exits; const possibleExists = mapExits.filter( - x => (!("PassageRequirement" in x) - || repeatableConfig.questConfig.Exploration.specificExits.passageRequirementWhitelist.includes(x.PassageRequirement)) - && x.Chance > 0 + (x) => + (!("PassageRequirement" in x) || + repeatableConfig.questConfig.Exploration.specificExits.passageRequirementWhitelist.includes( + x.PassageRequirement, + )) && + x.Chance > 0, ); const exit = this.randomUtil.drawRandomFromList(possibleExists, 1)[0]; const exitCondition = this.generateExplorationExitCondition(exit); @@ -682,7 +767,7 @@ export class RepeatableQuestGenerator pmcLevel: number, traderId: string, questTypePool: IQuestTypePool, - repeatableConfig: IRepeatableQuestConfig + repeatableConfig: IRepeatableQuestConfig, ): IPickup { const pickupConfig = repeatableConfig.questConfig.Pickup; @@ -690,21 +775,28 @@ export class RepeatableQuestGenerator const quest = this.generateRepeatableTemplate("Pickup", traderId, repeatableConfig.side) as IPickup; const itemTypeToFetchWithCount = this.randomUtil.getArrayValue(pickupConfig.ItemTypeToFetchWithMaxCount); - const itemCountToFetch = this.randomUtil.randInt(itemTypeToFetchWithCount.minPickupCount, itemTypeToFetchWithCount.maxPickupCount + 1); + const itemCountToFetch = this.randomUtil.randInt( + itemTypeToFetchWithCount.minPickupCount, + itemTypeToFetchWithCount.maxPickupCount + 1, + ); // Choose location - doesnt seem to work for anything other than 'any' - //const locationKey: string = this.randomUtil.drawRandomFromDict(questTypePool.pool.Pickup.locations)[0]; - //const locationTarget = questTypePool.pool.Pickup.locations[locationKey]; + // const locationKey: string = this.randomUtil.drawRandomFromDict(questTypePool.pool.Pickup.locations)[0]; + // const locationTarget = questTypePool.pool.Pickup.locations[locationKey]; - const findCondition = quest.conditions.AvailableForFinish.find(x => x._parent === "FindItem"); + const findCondition = quest.conditions.AvailableForFinish.find((x) => x._parent === "FindItem"); findCondition._props.target = [itemTypeToFetchWithCount.itemType]; findCondition._props.value = itemCountToFetch; - const counterCreatorCondition = quest.conditions.AvailableForFinish.find(x => x._parent === "CounterCreator"); - //const locationCondition = counterCreatorCondition._props.counter.conditions.find(x => x._parent === "Location"); - //(locationCondition._props as ILocationConditionProps).target = [...locationTarget]; + const counterCreatorCondition = quest.conditions.AvailableForFinish.find((x) => x._parent === "CounterCreator"); + // const locationCondition = counterCreatorCondition._props.counter.conditions.find(x => x._parent === "Location"); + // (locationCondition._props as ILocationConditionProps).target = [...locationTarget]; - const equipmentCondition = counterCreatorCondition._props.counter.conditions.find(x => x._parent === "Equipment"); - (equipmentCondition._props as IEquipmentConditionProps).equipmentInclusive = [[itemTypeToFetchWithCount.itemType]]; + const equipmentCondition = counterCreatorCondition._props.counter.conditions.find((x) => + x._parent === "Equipment" + ); + (equipmentCondition._props as IEquipmentConditionProps).equipmentInclusive = [[ + itemTypeToFetchWithCount.itemType, + ]]; // Add rewards quest.rewards = this.generateReward(pmcLevel, 1, traderId, repeatableConfig, pickupConfig); @@ -736,8 +828,8 @@ export class RepeatableQuestGenerator _props: { exitName: exit.Name, id: this.objectId.generate(), - dynamicLocale: true - } + dynamicLocale: true, + }, }; } @@ -766,7 +858,7 @@ export class RepeatableQuestGenerator difficulty: number, traderId: string, repeatableConfig: IRepeatableQuestConfig, - questConfig: IBaseQuestConfig + questConfig: IBaseQuestConfig, ): IRewards { // difficulty could go from 0.2 ... -> for lowest diffuculty receive 0.2*nominal reward @@ -786,11 +878,22 @@ export class RepeatableQuestGenerator } // rewards are generated based on pmcLevel, difficulty and a random spread - const rewardXP = Math.floor(difficulty * this.mathUtil.interp1(pmcLevel, levelsConfig, xpConfig) * this.randomUtil.getFloat(1 - rewardSpreadConfig, 1 + rewardSpreadConfig)); - const rewardRoubles = Math.floor(difficulty * this.mathUtil.interp1(pmcLevel, levelsConfig, roublesConfig) * this.randomUtil.getFloat(1 - rewardSpreadConfig, 1 + rewardSpreadConfig)); - const rewardNumItems = this.randomUtil.randInt(1, Math.round(this.mathUtil.interp1(pmcLevel, levelsConfig, itemsConfig)) + 1); - const rewardReputation = Math.round(100 * difficulty * this.mathUtil.interp1(pmcLevel, levelsConfig, reputationConfig) - * this.randomUtil.getFloat(1 - rewardSpreadConfig, 1 + rewardSpreadConfig)) / 100; + const rewardXP = Math.floor( + difficulty * this.mathUtil.interp1(pmcLevel, levelsConfig, xpConfig) * + this.randomUtil.getFloat(1 - rewardSpreadConfig, 1 + rewardSpreadConfig), + ); + const rewardRoubles = Math.floor( + difficulty * this.mathUtil.interp1(pmcLevel, levelsConfig, roublesConfig) * + this.randomUtil.getFloat(1 - rewardSpreadConfig, 1 + rewardSpreadConfig), + ); + const rewardNumItems = this.randomUtil.randInt( + 1, + Math.round(this.mathUtil.interp1(pmcLevel, levelsConfig, itemsConfig)) + 1, + ); + const rewardReputation = Math.round( + 100 * difficulty * this.mathUtil.interp1(pmcLevel, levelsConfig, reputationConfig) * + this.randomUtil.getFloat(1 - rewardSpreadConfig, 1 + rewardSpreadConfig), + ) / 100; const skillRewardChance = this.mathUtil.interp1(pmcLevel, levelsConfig, skillRewardChanceConfig); const skillPointReward = this.mathUtil.interp1(pmcLevel, levelsConfig, skillPointRewardConfig); @@ -804,16 +907,18 @@ export class RepeatableQuestGenerator { value: rewardXP, type: "Experience", - index: 0 - } + index: 0, + }, ], - Fail: [] + Fail: [], }; if (traderId === Traders.PEACEKEEPER) { // convert to equivalent dollars - rewards.Success.push(this.generateRewardItem(Money.EUROS, this.handbookHelper.fromRUB(rewardRoubles, Money.EUROS), 1)); + rewards.Success.push( + this.generateRewardItem(Money.EUROS, this.handbookHelper.fromRUB(rewardRoubles, Money.EUROS), 1), + ); } else { @@ -837,14 +942,20 @@ export class RepeatableQuestGenerator } // If we provide ammo we don't want to provide just one bullet - value = this.randomUtil.randInt(repeatableConfig.rewardAmmoStackMinSize, itemSelected._props.StackMaxSize); + value = this.randomUtil.randInt( + repeatableConfig.rewardAmmoStackMinSize, + itemSelected._props.StackMaxSize, + ); } else if (this.itemHelper.isOfBaseclass(itemSelected._id, BaseClasses.WEAPON)) { const defaultPreset = this.presetHelper.getDefaultPreset(itemSelected._id); if (defaultPreset) { - children = this.ragfairServerHelper.reparentPresets(defaultPreset._items[0], defaultPreset._items); + children = this.ragfairServerHelper.reparentPresets( + defaultPreset._items[0], + defaultPreset._items, + ); } } rewards.Success.push(this.generateRewardItem(itemSelected._id, value, index, children)); @@ -859,7 +970,9 @@ export class RepeatableQuestGenerator if (roublesBudget > 0) { // Filter possible reward items to only items with a price below the remaining budget - chosenRewardItems = chosenRewardItems.filter(x => this.itemHelper.getStaticItemPrice(x._id) < roublesBudget); + chosenRewardItems = chosenRewardItems.filter((x) => + this.itemHelper.getStaticItemPrice(x._id) < roublesBudget + ); if (chosenRewardItems.length === 0) { break; // No reward items left, exit @@ -879,7 +992,7 @@ export class RepeatableQuestGenerator target: traderId, value: rewardReputation, type: "TraderStanding", - index: index + index: index, }; rewards.Success.push(reward); } @@ -891,7 +1004,7 @@ export class RepeatableQuestGenerator target: this.randomUtil.getArrayValue(questConfig.possibleSkillRewards), value: skillPointReward, type: "Skill", - index: index + index: index, }; rewards.Success.push(reward); } @@ -905,17 +1018,29 @@ export class RepeatableQuestGenerator * @param roublesBudget Total value of items to return * @returns Array of reward items that fit budget */ - protected chooseRewardItemsWithinBudget(repeatableConfig: IRepeatableQuestConfig, roublesBudget: number): ITemplateItem[] + protected chooseRewardItemsWithinBudget( + repeatableConfig: IRepeatableQuestConfig, + roublesBudget: number, + ): ITemplateItem[] { // First filter for type and baseclass to avoid lookup in handbook for non-available items const rewardableItems = this.getRewardableItems(repeatableConfig); const minPrice = Math.min(25000, 0.5 * roublesBudget); - let itemSelection = rewardableItems.filter(x => this.itemHelper.getItemPrice(x[0]) < roublesBudget && this.itemHelper.getItemPrice(x[0]) > minPrice).map(x => x[1]); + let itemSelection = rewardableItems.filter((x) => + this.itemHelper.getItemPrice(x[0]) < roublesBudget && this.itemHelper.getItemPrice(x[0]) > minPrice + ).map((x) => x[1]); if (itemSelection.length === 0) { - this.logger.warning(this.localisationService.getText("repeatable-no_reward_item_found_in_price_range", {minPrice: minPrice, roublesBudget: roublesBudget})); + this.logger.warning( + this.localisationService.getText("repeatable-no_reward_item_found_in_price_range", { + minPrice: minPrice, + roublesBudget: roublesBudget, + }), + ); // In case we don't find any items in the price range - itemSelection = rewardableItems.filter(x => this.itemHelper.getItemPrice(x[0]) < roublesBudget).map(x => x[1]); + itemSelection = rewardableItems.filter((x) => this.itemHelper.getItemPrice(x[0]) < roublesBudget).map((x) => + x[1] + ); } return itemSelection; @@ -936,7 +1061,7 @@ export class RepeatableQuestGenerator target: id, value: value, type: "Item", - index: index + index: index, }; const rootItem = { @@ -944,8 +1069,8 @@ export class RepeatableQuestGenerator _tpl: tpl, upd: { StackObjectsCount: value, - SpawnedInSession: true - } + SpawnedInSession: true, + }, }; if (preset) @@ -960,7 +1085,7 @@ export class RepeatableQuestGenerator } /** - * Picks rewardable items from items.json. This means they need to fit into the inventory and they shouldn't be keys (debatable) + * Picks rewardable items from items.json. This means they need to fit into the inventory and they shouldn't be keys (debatable) * @param repeatableQuestConfig Config file * @returns List of rewardable items [[_tpl, itemTemplate],...] */ @@ -970,7 +1095,6 @@ export class RepeatableQuestGenerator // also check if the price is greater than 0; there are some items whose price can not be found // those are not in the game yet (e.g. AGS grenade launcher) return Object.entries(this.databaseServer.getTables().templates.items).filter( - // eslint-disable-next-line @typescript-eslint/no-unused-vars ([tpl, itemTemplate]) => { // Base "Item" item has no parent, ignore it @@ -980,7 +1104,7 @@ export class RepeatableQuestGenerator } return this.isValidRewardItem(tpl, repeatableQuestConfig); - } + }, ); } @@ -999,8 +1123,10 @@ export class RepeatableQuestGenerator } // Item is on repeatable or global blacklist - if (repeatableQuestConfig.rewardBlacklist.includes(tpl) - || this.itemFilterService.isItemBlacklisted(tpl)) + if ( + repeatableQuestConfig.rewardBlacklist.includes(tpl) || + this.itemFilterService.isItemBlacklisted(tpl) + ) { return false; } @@ -1011,15 +1137,23 @@ export class RepeatableQuestGenerator return false; } - if (this.itemHelper.isOfBaseclasses(tpl, [BaseClasses.DOG_TAG_USEC, BaseClasses.DOG_TAG_BEAR, BaseClasses.MOUNT, BaseClasses.KEY, BaseClasses.ARMBAND])) + if ( + this.itemHelper.isOfBaseclasses(tpl, [ + BaseClasses.DOG_TAG_USEC, + BaseClasses.DOG_TAG_BEAR, + BaseClasses.MOUNT, + BaseClasses.KEY, + BaseClasses.ARMBAND, + ]) + ) { return false; } // Skip globally blacklisted items + boss items // biome-ignore lint/complexity/useSimplifiedLogicExpression: - valid = !this.itemFilterService.isItemBlacklisted(tpl) - && !this.itemFilterService.isBossItem(tpl); + valid = !this.itemFilterService.isItemBlacklisted(tpl) && + !this.itemFilterService.isBossItem(tpl); return valid; } @@ -1030,36 +1164,59 @@ export class RepeatableQuestGenerator * * @param {string} type Quest type: "Elimination", "Completion" or "Extraction" * @param {string} traderId Trader from which the quest will be provided - * @param {string} side Scav daily or pmc daily/weekly quest + * @param {string} side Scav daily or pmc daily/weekly quest * @returns {object} Object which contains the base elements for repeatable quests of the requests type * (needs to be filled with reward and conditions by called to make a valid quest) */ // @Incomplete: define Type for "type". protected generateRepeatableTemplate(type: string, traderId: string, side: string): IRepeatableQuest { - const quest = this.jsonUtil.clone(this.databaseServer.getTables().templates.repeatableQuests.templates[type]); + const quest = this.jsonUtil.clone( + this.databaseServer.getTables().templates.repeatableQuests.templates[type], + ); quest._id = this.objectId.generate(); quest.traderId = traderId; /* in locale, these id correspond to the text of quests template ids -pmc : Elimination = 616052ea3054fc0e2c24ce6e / Completion = 61604635c725987e815b1a46 / Exploration = 616041eb031af660100c9967 - template ids -scav : Elimination = 62825ef60e88d037dc1eb428 / Completion = 628f588ebb558574b2260fe5 / Exploration = 62825ef60e88d037dc1eb42c + template ids -scav : Elimination = 62825ef60e88d037dc1eb428 / Completion = 628f588ebb558574b2260fe5 / Exploration = 62825ef60e88d037dc1eb42c */ // Get template id from config based on side and type of quest quest.templateId = this.questConfig.questTemplateIds[side.toLowerCase()][type.toLowerCase()]; - quest.name = quest.name.replace("{traderId}", traderId).replace("{templateId}",quest.templateId); - quest.note = quest.note.replace("{traderId}", traderId).replace("{templateId}",quest.templateId); - quest.description = quest.description.replace("{traderId}", traderId).replace("{templateId}",quest.templateId); - quest.successMessageText = quest.successMessageText.replace("{traderId}", traderId).replace("{templateId}",quest.templateId); - quest.failMessageText = quest.failMessageText.replace("{traderId}", traderId).replace("{templateId}",quest.templateId); - quest.startedMessageText = quest.startedMessageText.replace("{traderId}", traderId).replace("{templateId}",quest.templateId); - quest.changeQuestMessageText = quest.changeQuestMessageText.replace("{traderId}", traderId).replace("{templateId}",quest.templateId); - quest.acceptPlayerMessage = quest.acceptPlayerMessage.replace("{traderId}", traderId).replace("{templateId}",quest.templateId); - quest.declinePlayerMessage = quest.declinePlayerMessage.replace("{traderId}", traderId).replace("{templateId}",quest.templateId); - quest.completePlayerMessage = quest.completePlayerMessage.replace("{traderId}", traderId).replace("{templateId}",quest.templateId); + quest.name = quest.name.replace("{traderId}", traderId).replace("{templateId}", quest.templateId); + quest.note = quest.note.replace("{traderId}", traderId).replace("{templateId}", quest.templateId); + quest.description = quest.description.replace("{traderId}", traderId).replace("{templateId}", quest.templateId); + quest.successMessageText = quest.successMessageText.replace("{traderId}", traderId).replace( + "{templateId}", + quest.templateId, + ); + quest.failMessageText = quest.failMessageText.replace("{traderId}", traderId).replace( + "{templateId}", + quest.templateId, + ); + quest.startedMessageText = quest.startedMessageText.replace("{traderId}", traderId).replace( + "{templateId}", + quest.templateId, + ); + quest.changeQuestMessageText = quest.changeQuestMessageText.replace("{traderId}", traderId).replace( + "{templateId}", + quest.templateId, + ); + quest.acceptPlayerMessage = quest.acceptPlayerMessage.replace("{traderId}", traderId).replace( + "{templateId}", + quest.templateId, + ); + quest.declinePlayerMessage = quest.declinePlayerMessage.replace("{traderId}", traderId).replace( + "{templateId}", + quest.templateId, + ); + quest.completePlayerMessage = quest.completePlayerMessage.replace("{traderId}", traderId).replace( + "{templateId}", + quest.templateId, + ); return quest; } -} \ No newline at end of file +} diff --git a/project/src/generators/ScavCaseRewardGenerator.ts b/project/src/generators/ScavCaseRewardGenerator.ts index bf39a911..bb130a30 100644 --- a/project/src/generators/ScavCaseRewardGenerator.ts +++ b/project/src/generators/ScavCaseRewardGenerator.ts @@ -10,7 +10,8 @@ import { ConfigTypes } from "@spt-aki/models/enums/ConfigTypes"; import { Money } from "@spt-aki/models/enums/Money"; import { IScavCaseConfig } from "@spt-aki/models/spt/config/IScavCaseConfig"; import { - RewardCountAndPriceDetails, ScavCaseRewardCountsAndPrices + RewardCountAndPriceDetails, + ScavCaseRewardCountsAndPrices, } from "@spt-aki/models/spt/hideout/ScavCaseRewardCountsAndPrices"; import { ILogger } from "@spt-aki/models/spt/utils/ILogger"; import { ConfigServer } from "@spt-aki/servers/ConfigServer"; @@ -20,7 +21,7 @@ import { RagfairPriceService } from "@spt-aki/services/RagfairPriceService"; import { HashUtil } from "@spt-aki/utils/HashUtil"; import { RandomUtil } from "@spt-aki/utils/RandomUtil"; -/** +/** * Handle the creation of randomised scav case rewards */ @injectable() @@ -38,12 +39,12 @@ export class ScavCaseRewardGenerator @inject("DatabaseServer") protected databaseServer: DatabaseServer, @inject("RagfairPriceService") protected ragfairPriceService: RagfairPriceService, @inject("ItemFilterService") protected itemFilterService: ItemFilterService, - @inject("ConfigServer") protected configServer: ConfigServer + @inject("ConfigServer") protected configServer: ConfigServer, ) { this.scavCaseConfig = this.configServer.getConfig(ConfigTypes.SCAVCASE); } - + /** * Create an array of rewards that will be given to the player upon completing their scav case build * @param recipeId recipe of the scav case craft @@ -54,7 +55,7 @@ export class ScavCaseRewardGenerator this.cacheDbItems(); // Get scavcase details from hideout/scavcase.json - const scavCaseDetails = this.databaseServer.getTables().hideout.scavcase.find(r => r._id === recipeId); + const scavCaseDetails = this.databaseServer.getTables().hideout.scavcase.find((r) => r._id === recipeId); const rewardItemCounts = this.getScavCaseRewardCountsAndPrices(scavCaseDetails); // Get items that fit the price criteria as set by the scavCase config @@ -63,9 +64,17 @@ export class ScavCaseRewardGenerator const superRarePricedItems = this.getFilteredItemsByPrice(this.dbItemsCache, rewardItemCounts.Superrare); // Get randomly picked items from each item collction, the count range of which is defined in hideout/scavcase.json - const randomlyPickedCommonRewards = this.pickRandomRewards(commonPricedItems, rewardItemCounts.Common, "common"); + const randomlyPickedCommonRewards = this.pickRandomRewards( + commonPricedItems, + rewardItemCounts.Common, + "common", + ); const randomlyPickedRareRewards = this.pickRandomRewards(rarePricedItems, rewardItemCounts.Rare, "rare"); - const randomlyPickedSuperRareRewards = this.pickRandomRewards(superRarePricedItems, rewardItemCounts.Superrare, "superrare"); + const randomlyPickedSuperRareRewards = this.pickRandomRewards( + superRarePricedItems, + rewardItemCounts.Superrare, + "superrare", + ); // Add randomised stack sizes to ammo and money rewards const commonRewards = this.randomiseContainerItemRewards(randomlyPickedCommonRewards, "common"); @@ -95,11 +104,13 @@ export class ScavCaseRewardGenerator { return false; } - + // Skip item if item id is on blacklist - if ((item._type !== "Item") - || this.scavCaseConfig.rewardItemBlacklist.includes(item._id) - || this.itemFilterService.isItemBlacklisted(item._id)) + if ( + (item._type !== "Item") || + this.scavCaseConfig.rewardItemBlacklist.includes(item._id) || + this.itemFilterService.isItemBlacklisted(item._id) + ) { return false; } @@ -108,13 +119,13 @@ export class ScavCaseRewardGenerator { return false; } - + // Skip item if parent id is blacklisted if (this.itemHelper.isOfBaseclasses(item._id, this.scavCaseConfig.rewardItemParentBlacklist)) { return false; } - + return true; }); } @@ -139,13 +150,13 @@ export class ScavCaseRewardGenerator { return false; } - + // Skip ammo that doesn't stack as high as value in config if (item._props.StackMaxSize < this.scavCaseConfig.ammoRewards.minStackSize) { return false; } - + return true; }); } @@ -155,27 +166,31 @@ export class ScavCaseRewardGenerator * Pick a number of items to be rewards, the count is defined by the values in `itemFilters` param * @param items item pool to pick rewards from * @param itemFilters how the rewards should be filtered down (by item count) - * @returns + * @returns */ - protected pickRandomRewards(items: ITemplateItem[], itemFilters: RewardCountAndPriceDetails, rarity: string): ITemplateItem[] + protected pickRandomRewards( + items: ITemplateItem[], + itemFilters: RewardCountAndPriceDetails, + rarity: string, + ): ITemplateItem[] { const result: ITemplateItem[] = []; - + let rewardWasMoney = false; let rewardWasAmmo = false; const randomCount = this.randomUtil.getInt(itemFilters.minCount, itemFilters.maxCount); for (let i = 0; i < randomCount; i++) { - if (this.rewardShouldBeMoney() && !rewardWasMoney) // Only allow one reward to be money - { + if (this.rewardShouldBeMoney() && !rewardWasMoney) + { // Only allow one reward to be money result.push(this.getRandomMoney()); if (!this.scavCaseConfig.allowMultipleMoneyRewardsPerRarity) { rewardWasMoney = true; } } - else if (this.rewardShouldBeAmmo() && !rewardWasAmmo) // Only allow one reward to be ammo - { + else if (this.rewardShouldBeAmmo() && !rewardWasAmmo) + { // Only allow one reward to be ammo result.push(this.getRandomAmmo(rarity)); if (!this.scavCaseConfig.allowMultipleAmmoRewardsPerRarity) { @@ -214,10 +229,9 @@ export class ScavCaseRewardGenerator */ protected getRandomMoney(): ITemplateItem { - const money: ITemplateItem[] = []; - money.push(this.databaseServer.getTables().templates.items["5449016a4bdc2d6f028b456f"]); //rub - money.push(this.databaseServer.getTables().templates.items["569668774bdc2da2298b4568"]); //euro + money.push(this.databaseServer.getTables().templates.items["5449016a4bdc2d6f028b456f"]); // rub + money.push(this.databaseServer.getTables().templates.items["569668774bdc2da2298b4568"]); // euro money.push(this.databaseServer.getTables().templates.items["5696686a4bdc2da3298b456a"]); // dollar return this.randomUtil.getArrayValue(money); @@ -234,8 +248,10 @@ export class ScavCaseRewardGenerator { // Is ammo handbook price between desired range const handbookPrice = this.ragfairPriceService.getStaticPriceForItem(ammo._id); - if (handbookPrice >= this.scavCaseConfig.ammoRewards.ammoRewardValueRangeRub[rarity].min - && handbookPrice <= this.scavCaseConfig.ammoRewards.ammoRewardValueRangeRub[rarity].max) + if ( + handbookPrice >= this.scavCaseConfig.ammoRewards.ammoRewardValueRangeRub[rarity].min && + handbookPrice <= this.scavCaseConfig.ammoRewards.ammoRewardValueRangeRub[rarity].max + ) { return true; } @@ -266,7 +282,7 @@ export class ScavCaseRewardGenerator const resultItem = { _id: this.hashUtil.generate(), _tpl: item._id, - upd: undefined + upd: undefined, }; this.addStackCountToAmmoAndMoney(item, resultItem, rarity); @@ -288,29 +304,37 @@ export class ScavCaseRewardGenerator * @param item money or ammo item * @param resultItem money or ammo item with a randomise stack size */ - protected addStackCountToAmmoAndMoney(item: ITemplateItem, resultItem: { _id: string; _tpl: string; upd: Upd; }, rarity: string): void + protected addStackCountToAmmoAndMoney( + item: ITemplateItem, + resultItem: {_id: string; _tpl: string; upd: Upd;}, + rarity: string, + ): void { if (item._parent === BaseClasses.AMMO || item._parent === BaseClasses.MONEY) { resultItem.upd = { - StackObjectsCount: this.getRandomAmountRewardForScavCase(item, rarity) + StackObjectsCount: this.getRandomAmountRewardForScavCase(item, rarity), }; } } - + /** - * * @param dbItems all items from the items.json * @param itemFilters controls how the dbItems will be filtered and returned (handbook price) * @returns filtered dbItems array */ - protected getFilteredItemsByPrice(dbItems: ITemplateItem[], itemFilters: RewardCountAndPriceDetails): ITemplateItem[] + protected getFilteredItemsByPrice( + dbItems: ITemplateItem[], + itemFilters: RewardCountAndPriceDetails, + ): ITemplateItem[] { return dbItems.filter((item) => { const handbookPrice = this.ragfairPriceService.getStaticPriceForItem(item._id); - if (handbookPrice >= itemFilters.minPriceRub - && handbookPrice <= itemFilters.maxPriceRub) + if ( + handbookPrice >= itemFilters.minPriceRub && + handbookPrice <= itemFilters.maxPriceRub + ) { return true; } @@ -330,12 +354,11 @@ export class ScavCaseRewardGenerator // Create reward min/max counts for each type for (const rewardType of rewardTypes) { - result[rewardType] = - { + result[rewardType] = { minCount: scavCaseDetails.EndProducts[rewardType].min, maxCount: scavCaseDetails.EndProducts[rewardType].max, minPriceRub: this.scavCaseConfig.rewardItemValueRangeRub[rewardType.toLowerCase()].min, - maxPriceRub: this.scavCaseConfig.rewardItemValueRangeRub[rewardType.toLowerCase()].max + maxPriceRub: this.scavCaseConfig.rewardItemValueRangeRub[rewardType.toLowerCase()].max, }; } @@ -353,23 +376,35 @@ export class ScavCaseRewardGenerator let amountToGive = 1; if (itemToCalculate._parent === BaseClasses.AMMO) { - amountToGive = this.randomUtil.getInt(this.scavCaseConfig.ammoRewards.minStackSize, itemToCalculate._props.StackMaxSize); + amountToGive = this.randomUtil.getInt( + this.scavCaseConfig.ammoRewards.minStackSize, + itemToCalculate._props.StackMaxSize, + ); } else if (itemToCalculate._parent === BaseClasses.MONEY) { switch (itemToCalculate._id) { case Money.ROUBLES: - amountToGive = this.randomUtil.getInt(this.scavCaseConfig.moneyRewards.rubCount[rarity].min, this.scavCaseConfig.moneyRewards.rubCount[rarity].max); + amountToGive = this.randomUtil.getInt( + this.scavCaseConfig.moneyRewards.rubCount[rarity].min, + this.scavCaseConfig.moneyRewards.rubCount[rarity].max, + ); break; case Money.EUROS: - amountToGive = this.randomUtil.getInt(this.scavCaseConfig.moneyRewards.eurCount[rarity].min, this.scavCaseConfig.moneyRewards.eurCount[rarity].max); + amountToGive = this.randomUtil.getInt( + this.scavCaseConfig.moneyRewards.eurCount[rarity].min, + this.scavCaseConfig.moneyRewards.eurCount[rarity].max, + ); break; case Money.DOLLARS: - amountToGive = this.randomUtil.getInt(this.scavCaseConfig.moneyRewards.usdCount[rarity].min, this.scavCaseConfig.moneyRewards.usdCount[rarity].max); + amountToGive = this.randomUtil.getInt( + this.scavCaseConfig.moneyRewards.usdCount[rarity].min, + this.scavCaseConfig.moneyRewards.usdCount[rarity].max, + ); break; } } return amountToGive; } -} \ No newline at end of file +} diff --git a/project/src/generators/WeatherGenerator.ts b/project/src/generators/WeatherGenerator.ts index 52c8f49b..80288400 100644 --- a/project/src/generators/WeatherGenerator.ts +++ b/project/src/generators/WeatherGenerator.ts @@ -23,7 +23,7 @@ export class WeatherGenerator @inject("RandomUtil") protected randomUtil: RandomUtil, @inject("TimeUtil") protected timeUtil: TimeUtil, @inject("ApplicationContext") protected applicationContext: ApplicationContext, - @inject("ConfigServer") protected configServer: ConfigServer + @inject("ConfigServer") protected configServer: ConfigServer, ) { this.weatherConfig = this.configServer.getConfig(ConfigTypes.WEATHER); @@ -62,16 +62,17 @@ export class WeatherGenerator /** * Get the current in-raid time * @param currentDate (new Date()) - * @returns Date object of current in-raid time + * @returns Date object of current in-raid time */ public getInRaidTime(currentDate: Date): Date { // Get timestamp of when client conneted to server - const gameStartTimeStampMS = this.applicationContext.getLatestValue(ContextVariableType.CLIENT_START_TIMESTAMP).getValue(); + const gameStartTimeStampMS = this.applicationContext.getLatestValue(ContextVariableType.CLIENT_START_TIMESTAMP) + .getValue(); // Get delta between now and when client connected to server in milliseconds - const deltaMSFromNow = (Date.now() - gameStartTimeStampMS); - const acceleratedMS = (deltaMSFromNow * (this.weatherConfig.acceleration - 1)); // For some reason nodejs moves faster than client time, reducing acceleration by 1 when client is 7 helps + const deltaMSFromNow = Date.now() - gameStartTimeStampMS; + const acceleratedMS = deltaMSFromNow * (this.weatherConfig.acceleration - 1); // For some reason nodejs moves faster than client time, reducing acceleration by 1 when client is 7 helps const clientAcceleratedDate = new Date(currentDate.valueOf() + acceleratedMS); return clientAcceleratedDate; @@ -105,15 +106,15 @@ export class WeatherGenerator wind_gustiness: this.getRandomFloat("windGustiness"), rain: rain, // eslint-disable-next-line @typescript-eslint/naming-convention - rain_intensity: (rain > 1) - ? this.getRandomFloat("rainIntensity") - : 0, + rain_intensity: (rain > 1) ? + this.getRandomFloat("rainIntensity") : + 0, fog: this.getWeightedFog(), temp: this.getRandomFloat("temp"), pressure: this.getRandomFloat("pressure"), time: "", date: "", - timestamp: 0 + timestamp: 0, }; this.setCurrentDateTime(result); @@ -139,32 +140,49 @@ export class WeatherGenerator protected getWeightedWindDirection(): WindDirection { - return this.weightedRandomHelper.weightedRandom(this.weatherConfig.weather.windDirection.values, this.weatherConfig.weather.windDirection.weights).item; + return this.weightedRandomHelper.weightedRandom( + this.weatherConfig.weather.windDirection.values, + this.weatherConfig.weather.windDirection.weights, + ).item; } protected getWeightedClouds(): number { - return this.weightedRandomHelper.weightedRandom(this.weatherConfig.weather.clouds.values, this.weatherConfig.weather.clouds.weights).item; + return this.weightedRandomHelper.weightedRandom( + this.weatherConfig.weather.clouds.values, + this.weatherConfig.weather.clouds.weights, + ).item; } protected getWeightedWindSpeed(): number { - return this.weightedRandomHelper.weightedRandom(this.weatherConfig.weather.windSpeed.values, this.weatherConfig.weather.windSpeed.weights).item; + return this.weightedRandomHelper.weightedRandom( + this.weatherConfig.weather.windSpeed.values, + this.weatherConfig.weather.windSpeed.weights, + ).item; } protected getWeightedFog(): number { - return this.weightedRandomHelper.weightedRandom(this.weatherConfig.weather.fog.values, this.weatherConfig.weather.fog.weights).item; + return this.weightedRandomHelper.weightedRandom( + this.weatherConfig.weather.fog.values, + this.weatherConfig.weather.fog.weights, + ).item; } protected getWeightedRain(): number { - return this.weightedRandomHelper.weightedRandom(this.weatherConfig.weather.rain.values, this.weatherConfig.weather.rain.weights).item; + return this.weightedRandomHelper.weightedRandom( + this.weatherConfig.weather.rain.values, + this.weatherConfig.weather.rain.weights, + ).item; } protected getRandomFloat(node: string): number { - return parseFloat(this.randomUtil.getFloat(this.weatherConfig.weather[node].min, - this.weatherConfig.weather[node].max).toPrecision(3)); + return parseFloat( + this.randomUtil.getFloat(this.weatherConfig.weather[node].min, this.weatherConfig.weather[node].max) + .toPrecision(3), + ); } -} \ No newline at end of file +} diff --git a/project/src/generators/weapongen/IInventoryMagGen.ts b/project/src/generators/weapongen/IInventoryMagGen.ts index ac710ae1..86a1becb 100644 --- a/project/src/generators/weapongen/IInventoryMagGen.ts +++ b/project/src/generators/weapongen/IInventoryMagGen.ts @@ -5,4 +5,4 @@ export interface IInventoryMagGen getPriority(): number; canHandleInventoryMagGen(inventoryMagGen: InventoryMagGen): boolean; process(inventoryMagGen: InventoryMagGen): void; -} \ No newline at end of file +} diff --git a/project/src/generators/weapongen/InventoryMagGen.ts b/project/src/generators/weapongen/InventoryMagGen.ts index 5584b747..21ae076f 100644 --- a/project/src/generators/weapongen/InventoryMagGen.ts +++ b/project/src/generators/weapongen/InventoryMagGen.ts @@ -2,40 +2,39 @@ import { Inventory } from "@spt-aki/models/eft/common/tables/IBotBase"; import { GenerationData } from "@spt-aki/models/eft/common/tables/IBotType"; import { ITemplateItem } from "@spt-aki/models/eft/common/tables/ITemplateItem"; -export class InventoryMagGen +export class InventoryMagGen { constructor( private magCounts: GenerationData, private magazineTemplate: ITemplateItem, private weaponTemplate: ITemplateItem, private ammoTemplate: ITemplateItem, - private pmcInventory: Inventory - ) - { - } + private pmcInventory: Inventory, + ) + {} - public getMagCount(): GenerationData + public getMagCount(): GenerationData { return this.magCounts; } - public getMagazineTemplate(): ITemplateItem + public getMagazineTemplate(): ITemplateItem { return this.magazineTemplate; } - public getWeaponTemplate(): ITemplateItem + public getWeaponTemplate(): ITemplateItem { return this.weaponTemplate; } - public getAmmoTemplate(): ITemplateItem + public getAmmoTemplate(): ITemplateItem { return this.ammoTemplate; } - public getPmcInventory(): Inventory + public getPmcInventory(): Inventory { return this.pmcInventory; } -} \ No newline at end of file +} diff --git a/project/src/generators/weapongen/implementations/BarrelInventoryMagGen.ts b/project/src/generators/weapongen/implementations/BarrelInventoryMagGen.ts index 83fb5fd5..2628d5f5 100644 --- a/project/src/generators/weapongen/implementations/BarrelInventoryMagGen.ts +++ b/project/src/generators/weapongen/implementations/BarrelInventoryMagGen.ts @@ -8,36 +8,43 @@ import { RandomUtil } from "@spt-aki/utils/RandomUtil"; @injectable() export class BarrelInventoryMagGen implements IInventoryMagGen { - constructor( @inject("RandomUtil") protected randomUtil: RandomUtil, - @inject("BotWeaponGeneratorHelper") protected botWeaponGeneratorHelper: BotWeaponGeneratorHelper + @inject("BotWeaponGeneratorHelper") protected botWeaponGeneratorHelper: BotWeaponGeneratorHelper, ) - { } + {} - getPriority(): number + getPriority(): number { return 50; } - canHandleInventoryMagGen(inventoryMagGen: InventoryMagGen): boolean + canHandleInventoryMagGen(inventoryMagGen: InventoryMagGen): boolean { return inventoryMagGen.getWeaponTemplate()._props.ReloadMode === "OnlyBarrel"; } - - process(inventoryMagGen: InventoryMagGen): void + + process(inventoryMagGen: InventoryMagGen): void { // Can't be done by _props.ammoType as grenade launcher shoots grenades with ammoType of "buckshot" let randomisedAmmoStackSize: number; - if (inventoryMagGen.getAmmoTemplate()._props.StackMaxRandom === 1) // doesnt stack + if (inventoryMagGen.getAmmoTemplate()._props.StackMaxRandom === 1) { + // doesnt stack randomisedAmmoStackSize = this.randomUtil.getInt(3, 6); } else { - randomisedAmmoStackSize = this.randomUtil.getInt(inventoryMagGen.getAmmoTemplate()._props.StackMinRandom, inventoryMagGen.getAmmoTemplate()._props.StackMaxRandom); + randomisedAmmoStackSize = this.randomUtil.getInt( + inventoryMagGen.getAmmoTemplate()._props.StackMinRandom, + inventoryMagGen.getAmmoTemplate()._props.StackMaxRandom, + ); } - this.botWeaponGeneratorHelper.addAmmoIntoEquipmentSlots(inventoryMagGen.getAmmoTemplate()._id, randomisedAmmoStackSize, inventoryMagGen.getPmcInventory()); + this.botWeaponGeneratorHelper.addAmmoIntoEquipmentSlots( + inventoryMagGen.getAmmoTemplate()._id, + randomisedAmmoStackSize, + inventoryMagGen.getPmcInventory(), + ); } -} \ No newline at end of file +} diff --git a/project/src/generators/weapongen/implementations/ExternalInventoryMagGen.ts b/project/src/generators/weapongen/implementations/ExternalInventoryMagGen.ts index e5d2bda7..11f06b55 100644 --- a/project/src/generators/weapongen/implementations/ExternalInventoryMagGen.ts +++ b/project/src/generators/weapongen/implementations/ExternalInventoryMagGen.ts @@ -12,60 +12,71 @@ import { LocalisationService } from "@spt-aki/services/LocalisationService"; @injectable() export class ExternalInventoryMagGen implements IInventoryMagGen { - constructor( @inject("WinstonLogger") protected logger: ILogger, @inject("ItemHelper") protected itemHelper: ItemHelper, @inject("LocalisationService") protected localisationService: LocalisationService, - @inject("BotWeaponGeneratorHelper") protected botWeaponGeneratorHelper: BotWeaponGeneratorHelper + @inject("BotWeaponGeneratorHelper") protected botWeaponGeneratorHelper: BotWeaponGeneratorHelper, ) - { } + {} - getPriority(): number + getPriority(): number { return 99; } - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - canHandleInventoryMagGen(inventoryMagGen: InventoryMagGen): boolean + + canHandleInventoryMagGen(inventoryMagGen: InventoryMagGen): boolean { return true; // Fallback, if code reaches here it means no other implementation can handle this type of magazine } - process(inventoryMagGen: InventoryMagGen): void + process(inventoryMagGen: InventoryMagGen): void { let magTemplate = inventoryMagGen.getMagazineTemplate(); let magazineTpl = magTemplate._id; - const randomizedMagazineCount = Number(this.botWeaponGeneratorHelper.getRandomizedMagazineCount(inventoryMagGen.getMagCount())); + const randomizedMagazineCount = Number( + this.botWeaponGeneratorHelper.getRandomizedMagazineCount(inventoryMagGen.getMagCount()), + ); for (let i = 0; i < randomizedMagazineCount; i++) { - const magazineWithAmmo = this.botWeaponGeneratorHelper.createMagazineWithAmmo(magazineTpl, inventoryMagGen.getAmmoTemplate()._id, magTemplate); + const magazineWithAmmo = this.botWeaponGeneratorHelper.createMagazineWithAmmo( + magazineTpl, + inventoryMagGen.getAmmoTemplate()._id, + magTemplate, + ); const ableToFitMagazinesIntoBotInventory = this.botWeaponGeneratorHelper.addItemWithChildrenToEquipmentSlot( [EquipmentSlots.TACTICAL_VEST, EquipmentSlots.POCKETS], magazineWithAmmo[0]._id, magazineTpl, magazineWithAmmo, - inventoryMagGen.getPmcInventory()); + inventoryMagGen.getPmcInventory(), + ); if (ableToFitMagazinesIntoBotInventory === ItemAddedResult.NO_SPACE && i < randomizedMagazineCount) { - /* We were unable to fit at least the minimum amount of magazines, - * so we fallback to default magazine and try again. - * Temporary workaround to Killa spawning with no extras if he spawns with a drum mag */ - - if (magazineTpl === this.botWeaponGeneratorHelper.getWeaponsDefaultMagazineTpl(inventoryMagGen.getWeaponTemplate())) + // We were unable to fit at least the minimum amount of magazines, so we fallback to default magazine + // and try again. Temporary workaround to Killa spawning with no extras if he spawns with a drum mag. + // TODO: Fix this properly + if ( + magazineTpl === + this.botWeaponGeneratorHelper.getWeaponsDefaultMagazineTpl(inventoryMagGen.getWeaponTemplate()) + ) { // We were already on default - stop here to prevent infinite looping break; } // Get default magazine tpl, reset loop counter by 1 and try again - magazineTpl = this.botWeaponGeneratorHelper.getWeaponsDefaultMagazineTpl(inventoryMagGen.getWeaponTemplate()); + magazineTpl = this.botWeaponGeneratorHelper.getWeaponsDefaultMagazineTpl( + inventoryMagGen.getWeaponTemplate(), + ); magTemplate = this.itemHelper.getItem(magazineTpl)[1]; if (!magTemplate) { - this.logger.error(this.localisationService.getText("bot-unable_to_find_default_magazine_item", magazineTpl)); + this.logger.error( + this.localisationService.getText("bot-unable_to_find_default_magazine_item", magazineTpl), + ); break; } @@ -78,5 +89,4 @@ export class ExternalInventoryMagGen implements IInventoryMagGen } } } - -} \ No newline at end of file +} diff --git a/project/src/generators/weapongen/implementations/InternalMagazineInventoryMagGen.ts b/project/src/generators/weapongen/implementations/InternalMagazineInventoryMagGen.ts index 9840dae9..800013bd 100644 --- a/project/src/generators/weapongen/implementations/InternalMagazineInventoryMagGen.ts +++ b/project/src/generators/weapongen/implementations/InternalMagazineInventoryMagGen.ts @@ -7,25 +7,31 @@ import { BotWeaponGeneratorHelper } from "@spt-aki/helpers/BotWeaponGeneratorHel @injectable() export class InternalMagazineInventoryMagGen implements IInventoryMagGen { - constructor( - @inject("BotWeaponGeneratorHelper") protected botWeaponGeneratorHelper: BotWeaponGeneratorHelper + @inject("BotWeaponGeneratorHelper") protected botWeaponGeneratorHelper: BotWeaponGeneratorHelper, ) - { } + {} - public getPriority(): number + public getPriority(): number { return 0; } - public canHandleInventoryMagGen(inventoryMagGen: InventoryMagGen): boolean + public canHandleInventoryMagGen(inventoryMagGen: InventoryMagGen): boolean { return inventoryMagGen.getMagazineTemplate()._props.ReloadMagType === "InternalMagazine"; } - public process(inventoryMagGen: InventoryMagGen): void + public process(inventoryMagGen: InventoryMagGen): void { - const bulletCount = this.botWeaponGeneratorHelper.getRandomizedBulletCount(inventoryMagGen.getMagCount(), inventoryMagGen.getMagazineTemplate()); - this.botWeaponGeneratorHelper.addAmmoIntoEquipmentSlots(inventoryMagGen.getAmmoTemplate()._id, bulletCount, inventoryMagGen.getPmcInventory()); + const bulletCount = this.botWeaponGeneratorHelper.getRandomizedBulletCount( + inventoryMagGen.getMagCount(), + inventoryMagGen.getMagazineTemplate(), + ); + this.botWeaponGeneratorHelper.addAmmoIntoEquipmentSlots( + inventoryMagGen.getAmmoTemplate()._id, + bulletCount, + inventoryMagGen.getPmcInventory(), + ); } -} \ No newline at end of file +} diff --git a/project/src/generators/weapongen/implementations/UbglExternalMagGen.ts b/project/src/generators/weapongen/implementations/UbglExternalMagGen.ts index cb050c60..1821eafc 100644 --- a/project/src/generators/weapongen/implementations/UbglExternalMagGen.ts +++ b/project/src/generators/weapongen/implementations/UbglExternalMagGen.ts @@ -9,25 +9,32 @@ import { EquipmentSlots } from "@spt-aki/models/enums/EquipmentSlots"; @injectable() export class UbglExternalMagGen implements IInventoryMagGen { - constructor( - @inject("BotWeaponGeneratorHelper") protected botWeaponGeneratorHelper: BotWeaponGeneratorHelper + @inject("BotWeaponGeneratorHelper") protected botWeaponGeneratorHelper: BotWeaponGeneratorHelper, ) - { } + {} - public getPriority(): number + public getPriority(): number { return 1; } - public canHandleInventoryMagGen(inventoryMagGen: InventoryMagGen): boolean + public canHandleInventoryMagGen(inventoryMagGen: InventoryMagGen): boolean { return inventoryMagGen.getWeaponTemplate()._parent === BaseClasses.UBGL; } - public process(inventoryMagGen: InventoryMagGen): void + public process(inventoryMagGen: InventoryMagGen): void { - const bulletCount = this.botWeaponGeneratorHelper.getRandomizedBulletCount(inventoryMagGen.getMagCount(), inventoryMagGen.getMagazineTemplate()); - this.botWeaponGeneratorHelper.addAmmoIntoEquipmentSlots(inventoryMagGen.getAmmoTemplate()._id, bulletCount, inventoryMagGen.getPmcInventory(), [EquipmentSlots.TACTICAL_VEST]); + const bulletCount = this.botWeaponGeneratorHelper.getRandomizedBulletCount( + inventoryMagGen.getMagCount(), + inventoryMagGen.getMagazineTemplate(), + ); + this.botWeaponGeneratorHelper.addAmmoIntoEquipmentSlots( + inventoryMagGen.getAmmoTemplate()._id, + bulletCount, + inventoryMagGen.getPmcInventory(), + [EquipmentSlots.TACTICAL_VEST], + ); } -} \ No newline at end of file +}