using System.Collections.Concurrent; using SPTarkov.Common.Annotations; using SPTarkov.Server.Core.Helpers; using SPTarkov.Server.Core.Models.Eft.Common.Tables; using SPTarkov.Server.Core.Models.Enums; using SPTarkov.Server.Core.Models.Spt.Config; using SPTarkov.Server.Core.Models.Utils; using SPTarkov.Server.Core.Servers; using SPTarkov.Server.Core.Services; namespace SPTarkov.Server.Core.Generators; [Injectable] public class PMCLootGenerator { private readonly ConfigServer _configServer; private readonly DatabaseService _databaseService; private readonly ItemFilterService _itemFilterService; private readonly ItemHelper _itemHelper; private readonly ISptLogger _logger; private readonly PmcConfig _pmcConfig; private readonly RagfairPriceService _ragfairPriceService; private readonly SeasonalEventService _seasonalEventService; private readonly WeightedRandomHelper _weightedRandomHelper; private ConcurrentDictionary? _backpackLootPool; private ConcurrentDictionary? _pocketLootPool; private ConcurrentDictionary? _vestLootPool; public PMCLootGenerator( ISptLogger logger, DatabaseService databaseService, ItemHelper itemHelper, ItemFilterService itemFilterService, RagfairPriceService ragfairPriceService, SeasonalEventService seasonalEventService, WeightedRandomHelper weightedRandomHelper, ConfigServer configServer ) { _logger = logger; _databaseService = databaseService; _itemHelper = itemHelper; _itemFilterService = itemFilterService; _ragfairPriceService = ragfairPriceService; _seasonalEventService = seasonalEventService; _weightedRandomHelper = weightedRandomHelper; _configServer = configServer; _pmcConfig = _configServer.GetConfig(); } /// /// Create a List of loot items a PMC can have in their pockets /// /// /// Dictionary of string and number public ConcurrentDictionary GeneratePMCPocketLootPool(string botRole) { // Hydrate loot dictionary if empty if (_pocketLootPool is null) { _pocketLootPool = new ConcurrentDictionary(); var items = _databaseService.GetItems(); var pmcPriceOverrides = _databaseService.GetBots().Types[string.Equals(botRole, "pmcbear", StringComparison.OrdinalIgnoreCase) ? "bear" : "usec"].BotInventory.Items .Pockets; var allowedItemTypeWhitelist = _pmcConfig.PocketLoot.Whitelist; var blacklist = GetLootBlacklist(); var itemsToAdd = items.Where(item => allowedItemTypeWhitelist.Contains(item.Value.Parent) && _itemHelper.IsValidItem(item.Value.Id) && !blacklist.Contains(item.Value.Id) && !blacklist.Contains(item.Value.Parent) && ItemFitsInto1By2Slot(item.Value) ).Select(x => x.Key); foreach (var tpl in itemsToAdd) // If pmc has price override, use that. Otherwise, use flea price { if (pmcPriceOverrides.TryGetValue(tpl, out var priceOverride)) { _pocketLootPool.TryAdd(tpl, priceOverride); } else { // Set price of item as its weight var price = _ragfairPriceService.GetDynamicItemPrice(tpl, Money.ROUBLES); _pocketLootPool[tpl] = price ?? 0; } } var highestPrice = _pocketLootPool.Max(price => price.Value); foreach (var (key, _) in _pocketLootPool) // Invert price so cheapest has a larger weight // Times by highest price so most expensive item has weight of 1 { _pocketLootPool[key] = Math.Round(1 / _pocketLootPool[key] * highestPrice); } _weightedRandomHelper.ReduceWeightValues(_pocketLootPool); } return _pocketLootPool; } protected HashSet GetLootBlacklist() { var blacklist = new HashSet(); blacklist.UnionWith(_pmcConfig.PocketLoot.Blacklist); blacklist.UnionWith(_pmcConfig.GlobalLootBlacklist); blacklist.UnionWith(_itemFilterService.GetBlacklistedItems()); blacklist.UnionWith(_itemFilterService.GetBlacklistedLootableItems()); blacklist.UnionWith(_seasonalEventService.GetInactiveSeasonalEventItems()); return blacklist; } /// /// Create a List of loot items a PMC can have in their vests /// /// /// Dictionary of string and number public ConcurrentDictionary GeneratePMCVestLootPool(string botRole) { // Hydrate loot dictionary if empty if (_vestLootPool is null) { _vestLootPool = new ConcurrentDictionary(); var items = _databaseService.GetItems(); var pmcPriceOverrides = _databaseService.GetBots().Types[string.Equals(botRole, "pmcbear", StringComparison.OrdinalIgnoreCase) ? "bear" : "usec"].BotInventory.Items .TacticalVest; var allowedItemTypeWhitelist = _pmcConfig.VestLoot.Whitelist; var blacklist = GetLootBlacklist(); var itemsToAdd = items.Where(item => allowedItemTypeWhitelist.Contains(item.Value.Parent) && _itemHelper.IsValidItem(item.Value.Id) && !blacklist.Contains(item.Value.Id) && !blacklist.Contains(item.Value.Parent) && ItemFitsInto2By2Slot(item.Value) ).Select(x => x.Key); foreach (var tpl in itemsToAdd) // If pmc has price override, use that. Otherwise, use flea price { if (pmcPriceOverrides.TryGetValue(tpl, out var overridePrice)) { _vestLootPool.TryAdd(tpl, overridePrice); } else { // Set price of item as its weight var price = _ragfairPriceService.GetDynamicItemPrice(tpl, Money.ROUBLES); _vestLootPool[tpl] = price ?? 0; } } var highestPrice = _vestLootPool.Max(price => price.Value); foreach (var (key, _) in _vestLootPool) // Invert price so cheapest has a larger weight // Times by highest price so most expensive item has weight of 1 { _vestLootPool[key] = Math.Round(1 / _vestLootPool[key] * highestPrice); } _weightedRandomHelper.ReduceWeightValues(_vestLootPool); } return _vestLootPool; } /// /// Check if item has a width/height that lets it fit into a 2x2 slot /// 1x1 / 1x2 / 2x1 / 2x2 /// /// Item to check size of /// true if it fits protected bool ItemFitsInto2By2Slot(TemplateItem item) { return item.Properties.Width <= 2 && item.Properties.Height <= 2; } /// /// Check if item has a width/height that lets it fit into a 1x2 slot /// 1x1 / 1x2 / 2x1 /// /// Item to check size of /// true if it fits protected bool ItemFitsInto1By2Slot(TemplateItem item) { return $"{item.Properties.Width}x{item.Properties.Height}" switch { "1x1" or "1x2" or "2x1" => true, _ => false }; } /// /// Create a List of loot items a PMC can have in their backpack /// /// /// Dictionary of string and number public ConcurrentDictionary GeneratePMCBackpackLootPool(string botRole) { // Hydrate loot dictionary if empty if (_backpackLootPool is null) { _backpackLootPool = new ConcurrentDictionary(); var items = _databaseService.GetItems(); var pmcPriceOverrides = _databaseService.GetBots().Types[string.Equals(botRole, "pmcbear", StringComparison.OrdinalIgnoreCase) ? "bear" : "usec"].BotInventory.Items .Backpack; var allowedItemTypeWhitelist = _pmcConfig.BackpackLoot.Whitelist; var blacklist = GetLootBlacklist(); var itemsToAdd = items.Where(item => allowedItemTypeWhitelist.Contains(item.Value.Parent) && _itemHelper.IsValidItem(item.Value.Id) && !blacklist.Contains(item.Value.Id) && !blacklist.Contains(item.Value.Parent) ).Select(x => x.Key); foreach (var tpl in itemsToAdd) // If pmc has price override, use that. Otherwise, use flea price { if (pmcPriceOverrides.TryGetValue(tpl, out var priceOverride)) { _backpackLootPool.TryAdd(tpl, priceOverride); } else { // Set price of item as its weight var price = _ragfairPriceService.GetDynamicItemPrice(tpl, Money.ROUBLES); _backpackLootPool[tpl] = price ?? 0; } } var highestPrice = _backpackLootPool.Max(price => price.Value); foreach (var (key, _) in _backpackLootPool) // Invert price so cheapest has a larger weight // Times by highest price so most expensive item has weight of 1 { _backpackLootPool[key] = Math.Round(1 / _backpackLootPool[key] * highestPrice); } _weightedRandomHelper.ReduceWeightValues(_backpackLootPool); } return _backpackLootPool; } }