using Core.Annotations; using Core.Generators; using Core.Helpers; using Core.Models.Common; using Core.Models.Eft.Common.Tables; using Core.Models.Enums; using Core.Models.Spt.Bots; using Core.Models.Utils; using Core.Utils.Cloners; namespace Core.Services; [Injectable(InjectionType.Singleton)] public class BotLootCacheService { protected ISptLogger _logger; protected ItemHelper _itemHelper; protected PMCLootGenerator _pmcLootGenerator; protected RagfairPriceService _ragfairPriceService; protected ICloner _cloner; protected Dictionary _lootCache = new(); public BotLootCacheService( ISptLogger logger, ItemHelper itemHelper, PMCLootGenerator pmcLootGenerator, RagfairPriceService ragfairPriceService, ICloner cloner ) { _logger = logger; _itemHelper = itemHelper; _pmcLootGenerator = pmcLootGenerator; _ragfairPriceService = ragfairPriceService; _cloner = cloner; } /// /// Remove cached bot loot data /// public void ClearCache() { _lootCache.Clear(); } /// /// Get the fully created loot array, ordered by price low to high /// /// bot to get loot for /// is the bot a pmc /// what type of loot is needed (backpack/pocket/stim/vest etc) /// Base json db file for the bot having its loot generated /// Dictionary public Dictionary GetLootFromCache( string botRole, bool isPmc, string lootType, BotType botJsonTemplate, MinMax? itemPriceMinMax = null) { throw new NotImplementedException(); } /// /// Generate loot for a bot and store inside a private class property /// /// bots role (assault / pmcBot etc) /// Is the bot a PMC (alteres what loot is cached) /// db template for bot having its loot generated protected void AddLootToCache(string botRole, bool isPmc, BotType botJsonTemplate) { // Full pool of loot we use to create the various sub-categories with var lootPool = botJsonTemplate.BotInventory.Items; // Flatten all individual slot loot pools into one big pool, while filtering out potentially missing templates Dictionary specialLootPool = new(); Dictionary backpackLootPool= new(); Dictionary pocketLootPool = new(); Dictionary vestLootPool = new(); Dictionary secureLootTPool = new(); Dictionary combinedLootPool = new(); if (isPmc) { // Replace lootPool from bot json with our own generated list for PMCs lootPool.Backpack = _cloner.Clone(_pmcLootGenerator.GeneratePMCBackpackLootPool(botRole)); lootPool.Pockets = _cloner.Clone(_pmcLootGenerator.GeneratePMCPocketLootPool(botRole)); lootPool.TacticalVest = _cloner.Clone(_pmcLootGenerator.GeneratePMCVestLootPool(botRole)); } // Backpack/Pockets etc var poolsToProcess = new Dictionary> { { "Backpack", lootPool.Backpack }, { "Pockets", lootPool.Pockets }, { "SecuredContainer", lootPool.SecuredContainer }, { "SpecialLoot", lootPool.SpecialLoot }, { "TacticalVest", lootPool.TacticalVest } }; foreach (var kvp in poolsToProcess) { // No items to add, skip if (kvp.Value.Count == 0) { continue; } // Sort loot pool into separate buckets switch (kvp.Key) { case "specialloot": AddItemsToPool(specialLootPool, kvp.Value); break; case "pockets": AddItemsToPool(pocketLootPool, kvp.Value); break; case "tacticalvest": AddItemsToPool(vestLootPool, kvp.Value); break; case "securedcontainer": AddItemsToPool(secureLootTPool, kvp.Value); break; case "backpack": AddItemsToPool(backpackLootPool, kvp.Value); break; default: _logger.Warning($"How did you get here {kvp.Key}"); break; } // Add all items (if any) to combined pool (excluding secure) if (kvp.Value.Count > 0 && kvp.Key.ToLower() != "securedcontainer") { AddItemsToPool(combinedLootPool, kvp.Value); } } // Assign whitelisted special items to bot if any exist var specialLootItems = botJsonTemplate.BotGeneration.Items.SpecialItems.Whitelist.Count > 0 ? botJsonTemplate.BotGeneration.Items.SpecialItems.Whitelist : new Dictionary(); // no whitelist, find and assign from combined item pool if (!specialLootItems.Any()) { // key = tpl, value = weight foreach (var itemKvP in specialLootPool) { var itemTemplate = _itemHelper.GetItem(itemKvP.Key).Value; if (!(IsBulletOrGrenade(itemTemplate.Properties) || IsMagazine(itemTemplate.Properties))) { specialLootItems[itemKvP.Key] = itemKvP.Value; } } } // Assign whitelisted healing items to bot if any exist var healingItems = botJsonTemplate.BotGeneration.Items.Healing.Whitelist.Count > 0 ? botJsonTemplate.BotGeneration.Items.Healing.Whitelist : new Dictionary(); // No whitelist, find and assign from combined item pool if (!healingItems.Any()) { // key = tpl, value = weight foreach (var itemKvP in combinedLootPool) { var itemTemplate = _itemHelper.GetItem(itemKvP.Key).Value; if ( IsMedicalItem(itemTemplate.Properties) && itemTemplate.Parent != BaseClasses.STIMULATOR && itemTemplate.Parent != BaseClasses.DRUGS ) { healingItems[itemKvP.Key] = itemKvP.Value; } } } // Assign whitelisted drugs to bot if any exist var drugItems = botJsonTemplate.BotGeneration.Items.Drugs.Whitelist ?? new Dictionary(); // no drugs whitelist, find and assign from combined item pool if (!drugItems.Any()) { foreach (var itemKvP in (combinedLootPool)) { var itemTemplate = _itemHelper.GetItem(itemKvP.Key).Value; if (IsMedicalItem(itemTemplate.Properties) && itemTemplate.Parent == BaseClasses.DRUGS) { drugItems[itemKvP.Key] = itemKvP.Value; } } } // Assign whitelisted food to bot if any exist var foodItems = botJsonTemplate.BotGeneration.Items.Food.Whitelist ?? new Dictionary(); // No food whitelist, find and assign from combined item pool if (!foodItems.Any()) { foreach (var itemKvP in (combinedLootPool)) { var itemTemplate = _itemHelper.GetItem(itemKvP.Key).Value; if (_itemHelper.IsOfBaseclass(itemTemplate.Id, BaseClasses.FOOD)) { foodItems[itemKvP.Key] = itemKvP.Value; } } } // Assign whitelisted drink to bot if any exist var drinkItems = botJsonTemplate.BotGeneration.Items.Food.Whitelist ?? new Dictionary(); // No drink whitelist, find and assign from combined item pool if (!drinkItems.Any()) { foreach (var itemKvP in combinedLootPool) { var itemTemplate = _itemHelper.GetItem(itemKvP.Key).Value; if (_itemHelper.IsOfBaseclass(itemTemplate.Id, BaseClasses.DRINK)) { drinkItems[itemKvP.Key] = itemKvP.Value; } } } // Assign whitelisted currency to bot if any exist var currencyItems = botJsonTemplate.BotGeneration.Items.Currency.Whitelist ?? new Dictionary(); // No currency whitelist, find and assign from combined item pool if (!currencyItems.Any()) { foreach (var itemKvP in combinedLootPool) { var itemTemplate = _itemHelper.GetItem(itemKvP.Key).Value; if (_itemHelper.IsOfBaseclass(itemTemplate.Id, BaseClasses.MONEY)) { currencyItems[itemKvP.Key] = itemKvP.Value; } } } // Assign whitelisted stims to bot if any exist var stimItems = botJsonTemplate.BotGeneration.Items.Stims.Whitelist ?? new Dictionary(); // No whitelist, find and assign from combined item pool if (!stimItems.Any()) { foreach (var itemKvP in combinedLootPool) { var itemTemplate = _itemHelper.GetItem(itemKvP.Key).Value; if (IsMedicalItem(itemTemplate.Properties) && itemTemplate.Parent == BaseClasses.STIMULATOR) { stimItems[itemKvP.Key] = itemKvP.Value; } } } // Assign whitelisted grenades to bot if any exist var grenadeItems = botJsonTemplate.BotGeneration.Items.Grenades.Whitelist ?? new Dictionary(); // no whitelist, find and assign from combined item pool if (!grenadeItems.Any()) { foreach (var itemKvP in combinedLootPool) { var itemTemplate = _itemHelper.GetItem(itemKvP.Key).Value; if (IsGrenade(itemTemplate.Properties)) { grenadeItems[itemKvP.Key] = itemKvP.Value; } } } // Get backpack loot (excluding magazines, bullets, grenades, drink, food and healing/stim items) var filteredBackpackItems = new Dictionary(); foreach (var itemKvP in backpackLootPool) { var itemResult = _itemHelper.GetItem(itemKvP.Key); if (itemResult.Value is null) { continue; } var itemTemplate = itemResult.Value; if ( IsBulletOrGrenade(itemTemplate.Properties) || IsMagazine(itemTemplate.Properties) || IsMedicalItem(itemTemplate.Properties) || IsGrenade(itemTemplate.Properties) || IsFood(itemTemplate.Id) || IsDrink(itemTemplate.Id) || IsCurrency(itemTemplate.Id) ) { // Is type we don't want as backpack loot, skip continue; } filteredBackpackItems[itemKvP.Key] = itemKvP.Value; } // Get pocket loot (excluding magazines, bullets, grenades, drink, food medical and healing/stim items) var filteredPocketItems = new Dictionary(); foreach (var itemKvP in pocketLootPool) { var itemResult = _itemHelper.GetItem(itemKvP.Key); if (itemResult.Value is null) { continue; } var itemTemplate = itemResult.Value; if ( IsBulletOrGrenade(itemTemplate.Properties) || IsMagazine(itemTemplate.Properties) || IsMedicalItem(itemTemplate.Properties) || IsGrenade(itemTemplate.Properties) || IsFood(itemTemplate.Id) || IsDrink(itemTemplate.Id) || IsCurrency(itemTemplate.Id) || itemTemplate.Properties.Height is null || // lacks height itemTemplate.Properties.Width is null // lacks width ) { continue; } filteredPocketItems[itemKvP.Key] = itemKvP.Value; } // Get vest loot (excluding magazines, bullets, grenades, medical and healing/stim items) var filteredVestItems = new Dictionary(); foreach (var itemKvP in vestLootPool) { var itemResult = _itemHelper.GetItem(itemKvP.Key); if (itemResult.Value is null) { continue; } var itemTemplate = itemResult.Value; if ( IsBulletOrGrenade(itemTemplate.Properties) || IsMagazine(itemTemplate.Properties) || IsMedicalItem(itemTemplate.Properties) || IsGrenade(itemTemplate.Properties) || IsFood(itemTemplate.Id) || IsDrink(itemTemplate.Id) || IsCurrency(itemTemplate.Id) ) { continue; } filteredVestItems[itemKvP.Key] = itemKvP.Value; } var cacheForRole = _lootCache[botRole]; cacheForRole.HealingItems = healingItems; cacheForRole.DrugItems = drugItems; cacheForRole.FoodItems = foodItems; cacheForRole.DrinkItems = drinkItems; cacheForRole.CurrencyItems = currencyItems; cacheForRole.StimItems = stimItems; cacheForRole.GrenadeItems = grenadeItems; cacheForRole.SpecialItems = specialLootItems; cacheForRole.BackpackLoot = filteredBackpackItems; cacheForRole.PocketLoot = filteredPocketItems; cacheForRole.VestLoot = filteredVestItems; cacheForRole.SecureLoot = secureLootTPool; } /// /// Add unique items into combined pool /// /// Pool of items to add to /// items to add to combined pool if unique protected void AddUniqueItemsToPool(List poolToAddTo, List itemsToAdd) { throw new NotImplementedException(); } protected void AddItemsToPool(Dictionary poolToAddTo, Dictionary poolOfItemsToAdd) { throw new NotImplementedException(); } /// /// Ammo/grenades have this property /// /// /// protected bool IsBulletOrGrenade(Props props) { throw new NotImplementedException(); } /// /// Internal and external magazine have this property /// /// /// protected bool IsMagazine(Props props) { return props.ReloadMagType is not null; } /// /// Medical use items (e.g. morphine/lip balm/grizzly) /// /// /// protected bool IsMedicalItem(Props props) { throw new NotImplementedException(); } /// /// Grenades have this property (e.g. smoke/frag/flash grenades) /// /// /// protected bool IsGrenade(Props props) { throw new NotImplementedException(); } protected bool IsFood(string tpl) { throw new NotImplementedException(); } protected bool IsDrink(string tpl) { throw new NotImplementedException(); } protected bool IsCurrency(string tpl) { throw new NotImplementedException(); } /// /// Check if a bot type exists inside the loot cache /// /// role to check for /// true if they exist protected bool BotRoleExistsInCache(string botRole) { throw new NotImplementedException(); } /// /// If lootcache is undefined, init with empty property arrays /// /// Bot role to hydrate protected void InitCacheForBotRole(string botRole) { throw new NotImplementedException(); } /// /// Compares two item prices by their flea (or handbook if that doesnt exist) price /// /// /// /// protected int CompareByValue(int itemAPrice, int itemBPrice) { throw new NotImplementedException(); } }