diff --git a/Core/Generators/PMCLootGenerator.cs b/Core/Generators/PMCLootGenerator.cs index 5443d142..2e0748f6 100644 --- a/Core/Generators/PMCLootGenerator.cs +++ b/Core/Generators/PMCLootGenerator.cs @@ -1,4 +1,4 @@ -using Core.Annotations; +using Core.Annotations; using Core.Models.Eft.Common.Tables; namespace Core.Generators; @@ -15,7 +15,7 @@ public class PMCLootGenerator /// /// /// Dictionary of string and number - public Dictionary GeneratePMCPocketLootPool(string botRole) + public Dictionary GeneratePMCPocketLootPool(string botRole) { throw new NotImplementedException(); } @@ -25,7 +25,7 @@ public class PMCLootGenerator /// /// /// Dictionary of string and number - public Dictionary GeneratePMCVestLootPool(string botRole) + public Dictionary GeneratePMCVestLootPool(string botRole) { throw new NotImplementedException(); } @@ -57,7 +57,7 @@ public class PMCLootGenerator /// /// /// Dictionary of string and number - public Dictionary GeneratePMCBackpackLootPool(string botRole) + public Dictionary GeneratePMCBackpackLootPool(string botRole) { throw new NotImplementedException(); } diff --git a/Core/Helpers/ItemHelper.cs b/Core/Helpers/ItemHelper.cs index 313af33f..9dbfa05e 100644 --- a/Core/Helpers/ItemHelper.cs +++ b/Core/Helpers/ItemHelper.cs @@ -1044,7 +1044,12 @@ public class ItemHelper throw new NotImplementedException(); } - public bool IsOfBaseclass(string tpl, List baseClassTpls) + public bool IsOfBaseclass(string tpl, string baseClassTpl) + { + return _itemBaseClassService.ItemHasBaseClass(tpl, [baseClassTpl]); + } + + public bool isOfBaseclasses(string tpl, List baseClassTpls) { return _itemBaseClassService.ItemHasBaseClass(tpl, baseClassTpls); } diff --git a/Core/Models/Spt/Bots/BotLootCache.cs b/Core/Models/Spt/Bots/BotLootCache.cs index e6e20078..758f573e 100644 --- a/Core/Models/Spt/Bots/BotLootCache.cs +++ b/Core/Models/Spt/Bots/BotLootCache.cs @@ -5,43 +5,43 @@ namespace Core.Models.Spt.Bots; public class BotLootCache { [JsonPropertyName("backpackLoot")] - public Dictionary? BackpackLoot { get; set; } + public Dictionary? BackpackLoot { get; set; } [JsonPropertyName("pocketLoot")] - public Dictionary? PocketLoot { get; set; } + public Dictionary? PocketLoot { get; set; } [JsonPropertyName("vestLoot")] - public Dictionary? VestLoot { get; set; } + public Dictionary? VestLoot { get; set; } [JsonPropertyName("secureLoot")] - public Dictionary? SecureLoot { get; set; } + public Dictionary? SecureLoot { get; set; } [JsonPropertyName("combinedPoolLoot")] - public Dictionary? CombinedPoolLoot { get; set; } + public Dictionary? CombinedPoolLoot { get; set; } [JsonPropertyName("specialItems")] - public Dictionary? SpecialItems { get; set; } + public Dictionary? SpecialItems { get; set; } [JsonPropertyName("healingItems")] - public Dictionary? HealingItems { get; set; } + public Dictionary? HealingItems { get; set; } [JsonPropertyName("drugItems")] - public Dictionary? DrugItems { get; set; } + public Dictionary? DrugItems { get; set; } [JsonPropertyName("foodItems")] - public Dictionary? FoodItems { get; set; } + public Dictionary? FoodItems { get; set; } [JsonPropertyName("drinkItems")] - public Dictionary? DrinkItems { get; set; } + public Dictionary? DrinkItems { get; set; } [JsonPropertyName("currencyItems")] - public Dictionary? CurrencyItems { get; set; } + public Dictionary? CurrencyItems { get; set; } [JsonPropertyName("stimItems")] - public Dictionary? StimItems { get; set; } + public Dictionary? StimItems { get; set; } [JsonPropertyName("grenadeItems")] - public Dictionary? GrenadeItems { get; set; } + public Dictionary? GrenadeItems { get; set; } } public class LootCacheType diff --git a/Core/Services/BotLootCacheService.cs b/Core/Services/BotLootCacheService.cs index c114badb..1f1aaa79 100644 --- a/Core/Services/BotLootCacheService.cs +++ b/Core/Services/BotLootCacheService.cs @@ -3,6 +3,7 @@ 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.Utils.Cloners; using ILogger = Core.Models.Utils.ILogger; @@ -69,7 +70,294 @@ public class BotLootCacheService /// db template for bot having its loot generated protected void AddLootToCache(string botRole, bool isPmc, BotType botJsonTemplate) { - throw new NotImplementedException(); + // 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; } /// @@ -82,7 +370,7 @@ public class BotLootCacheService throw new NotImplementedException(); } - protected void AddItemsToPool(Dictionary poolToAddTo, Dictionary poolOfItemsToAdd) + protected void AddItemsToPool(Dictionary poolToAddTo, Dictionary poolOfItemsToAdd) { throw new NotImplementedException(); }