import { inject, injectable } from "tsyringe"; import { ItemHelper } from "@spt/helpers/ItemHelper"; import { WeightedRandomHelper } from "@spt/helpers/WeightedRandomHelper"; import { ITemplateItem } from "@spt/models/eft/common/tables/ITemplateItem"; import { ConfigTypes } from "@spt/models/enums/ConfigTypes"; import { IPmcConfig } from "@spt/models/spt/config/IPmcConfig"; import { ConfigServer } from "@spt/servers/ConfigServer"; import { DatabaseService } from "@spt/services/DatabaseService"; import { ItemFilterService } from "@spt/services/ItemFilterService"; import { RagfairPriceService } from "@spt/services/RagfairPriceService"; import { SeasonalEventService } from "@spt/services/SeasonalEventService"; /** * Handle the generation of dynamic PMC loot in pockets and backpacks * and the removal of blacklisted items */ @injectable() export class PMCLootGenerator { protected pocketLootPool: Record = {}; protected vestLootPool: Record = {}; protected backpackLootPool: Record = {}; protected pmcConfig: IPmcConfig; protected roubleTpl = "5449016a4bdc2d6f028b456f"; constructor( @inject("ItemHelper") protected itemHelper: ItemHelper, @inject("DatabaseService") protected databaseService: DatabaseService, @inject("ConfigServer") protected configServer: ConfigServer, @inject("ItemFilterService") protected itemFilterService: ItemFilterService, @inject("RagfairPriceService") protected ragfairPriceService: RagfairPriceService, @inject("SeasonalEventService") protected seasonalEventService: SeasonalEventService, @inject("WeightedRandomHelper") protected weightedRandomHelper: WeightedRandomHelper, ) { this.pmcConfig = this.configServer.getConfig(ConfigTypes.PMC); } /** * Create an array of loot items a PMC can have in their pockets * @returns string array of tpls */ public generatePMCPocketLootPool(botRole: string): Record { // Hydrate loot dictionary if empty if (Object.keys(this.pocketLootPool).length === 0) { const items = this.databaseService.getItems(); const pmcPriceOverrides = this.databaseService.getBots().types[botRole === "pmcBEAR" ? "bear" : "usec"].inventory.items .Pockets; const allowedItemTypes = this.pmcConfig.pocketLoot.whitelist; const pmcItemBlacklist = this.pmcConfig.pocketLoot.blacklist; const itemBlacklist = this.itemFilterService.getBlacklistedItems(); // Blacklist inactive seasonal items 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, ); for (const itemToAdd of itemsToAdd) { // If pmc has override, use that. Otherwise use flea price if (pmcPriceOverrides[itemToAdd._id]) { this.pocketLootPool[itemToAdd._id] = pmcPriceOverrides[itemToAdd._id]; } else { // Set price of item as its weight const price = this.ragfairPriceService.getDynamicItemPrice(itemToAdd._id, this.roubleTpl); this.pocketLootPool[itemToAdd._id] = price; } } const highestPrice = Math.max(...Object.values(this.backpackLootPool)); for (const key of Object.keys(this.pocketLootPool)) { // Invert price so cheapest has a larger weight // Times by highest price so most expensive item has weight of 1 this.pocketLootPool[key] = Math.round((1 / this.pocketLootPool[key]) * highestPrice); } this.weightedRandomHelper.reduceWeightValues(this.pocketLootPool); } return this.pocketLootPool; } /** * Create an array of loot items a PMC can have in their vests * @returns string array of tpls */ public generatePMCVestLootPool(botRole: string): Record { // Hydrate loot dictionary if empty if (Object.keys(this.vestLootPool).length === 0) { const items = this.databaseService.getItems(); const pmcPriceOverrides = this.databaseService.getBots().types[botRole === "pmcBEAR" ? "bear" : "usec"].inventory.items .TacticalVest; const allowedItemTypes = this.pmcConfig.vestLoot.whitelist; const pmcItemBlacklist = this.pmcConfig.vestLoot.blacklist; const itemBlacklist = this.itemFilterService.getBlacklistedItems(); // Blacklist seasonal items 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), ); for (const itemToAdd of itemsToAdd) { // If pmc has override, use that. Otherwise use flea price if (pmcPriceOverrides[itemToAdd._id]) { this.vestLootPool[itemToAdd._id] = pmcPriceOverrides[itemToAdd._id]; } else { // Set price of item as its weight const price = this.ragfairPriceService.getDynamicItemPrice(itemToAdd._id, this.roubleTpl); this.vestLootPool[itemToAdd._id] = price; } } const highestPrice = Math.max(...Object.values(this.backpackLootPool)); for (const key of Object.keys(this.vestLootPool)) { // Invert price so cheapest has a larger weight // Times by highest price so most expensive item has weight of 1 this.vestLootPool[key] = Math.round((1 / this.vestLootPool[key]) * highestPrice); } this.weightedRandomHelper.reduceWeightValues(this.vestLootPool); } return this.vestLootPool; } /** * Check if item has a width/height that lets it fit into a 2x2 slot * 1x1 / 1x2 / 2x1 / 2x2 * @param item Item to check size of * @returns true if it fits */ protected itemFitsInto2By2Slot(item: ITemplateItem): boolean { return item._props.Width <= 2 && item._props.Height <= 2; } /** * Create an array of loot items a PMC can have in their backpack * @returns string array of tpls */ public generatePMCBackpackLootPool(botRole: string): Record { // Hydrate loot dictionary if empty if (Object.keys(this.backpackLootPool).length === 0) { const items = this.databaseService.getItems(); const pmcPriceOverrides = this.databaseService.getBots().types[botRole === "pmcBEAR" ? "bear" : "usec"].inventory.items .Backpack; const allowedItemTypes = this.pmcConfig.backpackLoot.whitelist; const pmcItemBlacklist = this.pmcConfig.backpackLoot.blacklist; const itemBlacklist = this.itemFilterService.getBlacklistedItems(); // Blacklist seasonal items 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), ); for (const itemToAdd of itemsToAdd) { // If pmc has price override, use that. Otherwise use flea price if (pmcPriceOverrides[itemToAdd._id]) { this.backpackLootPool[itemToAdd._id] = pmcPriceOverrides[itemToAdd._id]; } else { // Set price of item as its weight const price = this.ragfairPriceService.getDynamicItemPrice(itemToAdd._id, this.roubleTpl); this.backpackLootPool[itemToAdd._id] = price; } } const highestPrice = Math.max(...Object.values(this.backpackLootPool)); for (const key of Object.keys(this.backpackLootPool)) { // Invert price so cheapest has a larger weight // Times by highest price so most expensive item has weight of 1 this.backpackLootPool[key] = Math.round((1 / this.backpackLootPool[key]) * highestPrice); } this.weightedRandomHelper.reduceWeightValues(this.backpackLootPool); } return this.backpackLootPool; } }