diff --git a/Core/Generators/BotGenerator.cs b/Core/Generators/BotGenerator.cs index d22c1594..ce79dda7 100644 --- a/Core/Generators/BotGenerator.cs +++ b/Core/Generators/BotGenerator.cs @@ -435,8 +435,8 @@ public class BotGenerator { // Adjust pocket loot weights to allow for 5 or 6 items var pocketWeights = botJsonTemplate.BotGeneration.Items["pocketLoot"].Weights; - pocketWeights["5"] = 1; - pocketWeights["6"] = 1; + pocketWeights[5] = 1; + pocketWeights[6] = 1; } /// diff --git a/Core/Generators/BotLootGenerator.cs b/Core/Generators/BotLootGenerator.cs index 63b01612..d736f74b 100644 --- a/Core/Generators/BotLootGenerator.cs +++ b/Core/Generators/BotLootGenerator.cs @@ -1,19 +1,74 @@ -using Core.Annotations; +using Core.Annotations; using Core.Models.Eft.Common.Tables; using Core.Models.Enums; using Core.Models.Spt.Bots; using Core.Models.Spt.Config; +using Core.Utils; +using Core.Helpers; +using Core.Services; +using Core.Servers; +using Core.Utils.Cloners; +using ILogger = Core.Models.Utils.ILogger; namespace Core.Generators; [Injectable] public class BotLootGenerator { + private readonly ILogger _logger; + private readonly HashUtil _hashUtil; + private readonly RandomUtil _randomUtil; + private readonly ItemHelper _itemHelper; + private readonly InventoryHelper _inventoryHelper; + private readonly DatabaseService _databaseService; + private readonly HandbookHelper _handbookHelper; + private readonly BotGeneratorHelper _botGeneratorHelper; + private readonly BotWeaponGenerator _botWeaponGenerator; + private readonly WeightedRandomHelper _weightedRandomHelper; + private readonly BotHelper _botHelper; + private readonly BotLootCacheService _botLootCacheService; + private readonly LocalisationService _localisationService; + private readonly ConfigServer _configServer; + private readonly ICloner _cloner; + private BotConfig _botConfig; private PmcConfig _pmcConfig; - public BotLootGenerator() + public BotLootGenerator( + ILogger logger, + HashUtil hashUtil, + RandomUtil randomUtil, + ItemHelper itemHelper, + InventoryHelper inventoryHelper, + DatabaseService databaseService, + HandbookHelper handbookHelper, + BotGeneratorHelper botGeneratorHelper, + BotWeaponGenerator botWeaponGenerator, + WeightedRandomHelper weightedRandomHelper, + BotHelper botHelper, + BotLootCacheService botLootCacheService, + LocalisationService localisationService, + ConfigServer configServer, + ICloner cloner) { + _logger = logger; + _hashUtil = hashUtil; + _randomUtil = randomUtil; + _itemHelper = itemHelper; + _inventoryHelper = inventoryHelper; + _databaseService = databaseService; + _handbookHelper = handbookHelper; + _botGeneratorHelper = botGeneratorHelper; + _botWeaponGenerator = botWeaponGenerator; + _weightedRandomHelper = weightedRandomHelper; + _botHelper = botHelper; + _botLootCacheService = botLootCacheService; + _localisationService = localisationService; + _configServer = configServer; + _cloner = cloner; + + _botConfig = _configServer.GetConfig(); + _pmcConfig = _configServer.GetConfig(); } /// @@ -38,7 +93,264 @@ public class BotLootGenerator /// Level of bot public void GenerateLoot(string sessionId, BotType botJsonTemplate, bool isPmc, string botRole, BotBaseInventory botInventory, int botLevel) { - throw new NotImplementedException(); + // Limits on item types to be added as loot + var itemCounts = botJsonTemplate.BotGeneration.Items; + + if ( + itemCounts["backpackLoot"].Weights is null || + itemCounts["pocketLoot"].Weights is null || + itemCounts["vestLoot"].Weights is null || + itemCounts["specialItems"].Weights is null || + itemCounts["healing"].Weights is null || + itemCounts["drugs"].Weights is null || + itemCounts["food"].Weights is null || + itemCounts["drink"].Weights is null || + itemCounts["currency"].Weights is null || + itemCounts["stims"].Weights is null || + itemCounts["grenades"].Weights is null + ) + { + _logger.Warning(_localisationService.GetText("bot-unable_to_generate_bot_loot", botRole)); + return; + } + var backpackLootCount = _weightedRandomHelper.GetWeightedValue(itemCounts["backpackLoot"].Weights); + var pocketLootCount = _weightedRandomHelper.GetWeightedValue(itemCounts["pocketLoot"].Weights); + var vestLootCount = _weightedRandomHelper.GetWeightedValue(itemCounts["vestLoot"].Weights); + var specialLootItemCount = _weightedRandomHelper.GetWeightedValue(itemCounts["specialItems"].Weights); + var healingItemCount = _weightedRandomHelper.GetWeightedValue(itemCounts["healing"].Weights); + var drugItemCount = _weightedRandomHelper.GetWeightedValue(itemCounts["drugs"].Weights); + + var foodItemCount = _weightedRandomHelper.GetWeightedValue(itemCounts["food"].Weights); + var drinkItemCount = _weightedRandomHelper.GetWeightedValue(itemCounts["drink"].Weights); + + var currencyItemCount = _weightedRandomHelper.GetWeightedValue(itemCounts["currency"].Weights); + + var stimItemCount = _weightedRandomHelper.GetWeightedValue(itemCounts["stims"].Weights); + var grenadeCount = _weightedRandomHelper.GetWeightedValue(itemCounts["grenades"].Weights); + + // If bot has been flagged as not having loot, set below counts to 0 + if (_botConfig.DisableLootOnBotTypes.Contains(botRole.ToLower())) + { + backpackLootCount = 0; + pocketLootCount = 0; + vestLootCount = 0; + currencyItemCount = 0; + } + + // Forced pmc healing loot into secure container + if (isPmc && _pmcConfig.ForceHealingItemsIntoSecure) + { + AddForcedMedicalItemsToPmcSecure(botInventory, botRole); + } + + var botItemLimits = GetItemSpawnLimitsForBot(botRole); + + var containersBotHasAvailable = GetAvailableContainersBotCanStoreItemsIn(botInventory); + + // This set is passed as a reference to fill up the containers that are already full, this alleviates + // generation of the bots by avoiding checking the slots of containers we already know are full + var containersIdFull = new List(); + + // Special items + AddLootFromPool( + _botLootCacheService.GetLootFromCache(botRole, isPmc, LootCacheType.Special, botJsonTemplate), + containersBotHasAvailable, + specialLootItemCount, + botInventory, + botRole, + botItemLimits, + containersIdFull); + + // Healing items / Meds + AddLootFromPool( + _botLootCacheService.GetLootFromCache(botRole, isPmc, LootCacheType.HealingItems, botJsonTemplate), + containersBotHasAvailable, + healingItemCount, + botInventory, + botRole, + null, + containersIdFull, + 0, + isPmc); + + // Drugs + AddLootFromPool( + _botLootCacheService.GetLootFromCache(botRole, isPmc, LootCacheType.DrugItems, botJsonTemplate), + containersBotHasAvailable, + drugItemCount, + botInventory, + botRole, + null, + containersIdFull, + 0, + isPmc); + + // Food + AddLootFromPool( + _botLootCacheService.GetLootFromCache(botRole, isPmc, LootCacheType.FoodItems, botJsonTemplate), + containersBotHasAvailable, + foodItemCount, + botInventory, + botRole, + null, + containersIdFull, + 0, + isPmc); + + // Drink + AddLootFromPool( + _botLootCacheService.GetLootFromCache(botRole, isPmc, LootCacheType.DrinkItems, botJsonTemplate), + containersBotHasAvailable, + drinkItemCount, + botInventory, + botRole, + null, + containersIdFull, + 0, + isPmc); + + // Currency + AddLootFromPool( + _botLootCacheService.GetLootFromCache(botRole, isPmc, LootCacheType.CurrencyItems, botJsonTemplate), + containersBotHasAvailable, + currencyItemCount, + botInventory, + botRole, + null, + containersIdFull, + 0, + isPmc); + + // Stims + AddLootFromPool( + _botLootCacheService.GetLootFromCache(botRole, isPmc, LootCacheType.StimItems, botJsonTemplate), + containersBotHasAvailable, + stimItemCount, + botInventory, + botRole, + botItemLimits, + containersIdFull, + 0, + isPmc); + + // Grenades + AddLootFromPool( + _botLootCacheService.GetLootFromCache(botRole, isPmc, LootCacheType.GrenadeItems, botJsonTemplate), + [EquipmentSlots.Pockets, EquipmentSlots.TacticalVest], // Can't use containersBotHasEquipped as we don't want grenades added to backpack + grenadeCount, + botInventory, + botRole, + null, + containersIdFull, + 0, + isPmc); + + var itemPriceLimits = GetSingleItemLootPriceLimits(botLevel, isPmc); + + // Backpack - generate loot if they have one + if (containersBotHasAvailable.Contains(EquipmentSlots.Backpack)) + { + // Add randomly generated weapon to PMC backpacks + if (isPmc && _randomUtil.GetChance100(_pmcConfig.LooseWeaponInBackpackChancePercent)) + { + AddLooseWeaponsToInventorySlot( + sessionId, + botInventory, + EquipmentSlots.Backpack, + botJsonTemplate.BotInventory, + botJsonTemplate.BotChances.WeaponModsChances, + botRole, + isPmc, + botLevel, + containersIdFull); + } + + var backpackLootRoubleTotal = GetBackpackRoubleTotalByLevel(botLevel, isPmc); + AddLootFromPool( + _botLootCacheService.GetLootFromCache( + botRole, + isPmc, + LootCacheType.Backpack, + botJsonTemplate, + itemPriceLimits?.Backpack), + [EquipmentSlots.Backpack], + backpackLootCount, + botInventory, + botRole, + botItemLimits, + containersIdFull, + backpackLootRoubleTotal, + isPmc); + } + + // TacticalVest - generate loot if they have one + if (containersBotHasAvailable.Contains(EquipmentSlots.TacticalVest)) + { + // Vest + AddLootFromPool( + _botLootCacheService.GetLootFromCache( + botRole, + isPmc, + LootCacheType.Vest, + botJsonTemplate, + itemPriceLimits?.Vest), + [EquipmentSlots.TacticalVest], + vestLootCount, + botInventory, + botRole, + botItemLimits, + containersIdFull, + _pmcConfig.MaxVestLootTotalRub, + isPmc); + } + + // Pockets + AddLootFromPool( + _botLootCacheService.GetLootFromCache( + botRole, + isPmc, + LootCacheType.Pocket, + botJsonTemplate, + itemPriceLimits?.Pocket), + [EquipmentSlots.Pockets], + pocketLootCount, + botInventory, + botRole, + botItemLimits, + containersIdFull, + _pmcConfig.MaxPocketLootTotalRub, + isPmc); + + // Secure + + // only add if not a pmc or is pmc and flag is true + if (!isPmc || (isPmc && _pmcConfig.AddSecureContainerLootFromBotConfig)) + { + AddLootFromPool( + _botLootCacheService.GetLootFromCache(botRole, isPmc, LootCacheType.Secure, botJsonTemplate), + [EquipmentSlots.SecuredContainer], + 50, + botInventory, + botRole, + null, + containersIdFull, + - 1, + isPmc); + } + } + + private MinMaxLootItemValue? GetSingleItemLootPriceLimits(int botLevel, bool isPmc) + { + // TODO - extend to other bot types + if (isPmc) + { + var matchingValue = _pmcConfig.LootItemLimitsRub.FirstOrDefault( + (minMaxValue) => botLevel >= minMaxValue.Min && botLevel <= minMaxValue.Max); + + return matchingValue; + } + + return null; } /// @@ -87,9 +399,16 @@ public class BotLootGenerator /// Total value of loot allowed in roubles /// Is bot being generated for a pmc /// - public void AddLootFromPool(Dictionary pool, List equipmentSlots, int totalItemCount, + public void AddLootFromPool( + Dictionary pool, + List equipmentSlots, + int totalItemCount, BotBaseInventory inventoryToAddItemsTo, // TODO: type for containersIdFull was Set - string botRole, ItemSpawnLimitSettings? itemSpawnLimits, List containersIdFull, int totalValueLimitRub = 0, bool isPmc = false) + string botRole, + ItemSpawnLimitSettings itemSpawnLimits, + List containersIdFull, + int totalValueLimitRub = 0, + bool isPmc = false) { throw new NotImplementedException(); } @@ -129,9 +448,15 @@ public class BotLootGenerator /// are we generating for a pmc /// /// - public void AddLooseWeaponsToInventorySlot(string sessionId, BotBaseInventory botInventory, string equipmentSlot, - BotBaseInventory templateInventory, // TODO: type for containersIdFull was Set - Dictionary modsChances, string botRole, bool isPmc, int botLevel, List? containersIdFull) + public void AddLooseWeaponsToInventorySlot(string sessionId, + BotBaseInventory botInventory, + EquipmentSlots equipmentSlot, + BotTypeInventory templateInventory, + Dictionary modsChances, + string botRole, + bool isPmc, + int botLevel, + List? containersIdFull) // TODO: type for containersIdFull was Set { throw new NotImplementedException(); } diff --git a/Core/Models/Eft/Common/Tables/BotType.cs b/Core/Models/Eft/Common/Tables/BotType.cs index 180b1487..df4bae4d 100644 --- a/Core/Models/Eft/Common/Tables/BotType.cs +++ b/Core/Models/Eft/Common/Tables/BotType.cs @@ -303,7 +303,7 @@ public class GenerationData { /** key: number of items, value: weighting */ [JsonPropertyName("weights")] - public Dictionary? Weights { get; set; } + public Dictionary? Weights { get; set; } /** Array of item tpls */ [JsonPropertyName("whitelist")] diff --git a/Core/Models/Spt/Bots/BotLootCache.cs b/Core/Models/Spt/Bots/BotLootCache.cs index fa7cdfa7..e6e20078 100644 --- a/Core/Models/Spt/Bots/BotLootCache.cs +++ b/Core/Models/Spt/Bots/BotLootCache.cs @@ -44,7 +44,7 @@ public class BotLootCache public Dictionary? GrenadeItems { get; set; } } -public static class LootCacheType +public class LootCacheType { public const string Special = "Special"; public const string Backpack = "Backpack"; @@ -59,4 +59,4 @@ public static class LootCacheType public const string FoodItems = "FoodItems"; public const string DrinkItems = "DrinkItems"; public const string CurrencyItems = "CurrencyItems"; -} \ No newline at end of file +} diff --git a/Core/Models/Spt/Config/PmcConfig.cs b/Core/Models/Spt/Config/PmcConfig.cs index a3be7e67..eaf4b093 100644 --- a/Core/Models/Spt/Config/PmcConfig.cs +++ b/Core/Models/Spt/Config/PmcConfig.cs @@ -1,4 +1,4 @@ -using System.Text.Json.Serialization; +using System.Text.Json.Serialization; using Core.Models.Common; using Core.Models.Eft.Common; using Core.Models.Enums; @@ -79,10 +79,10 @@ public class PmcConfig : BaseConfig public List MaxBackpackLootTotalRub { get; set; } [JsonPropertyName("maxPocketLootTotalRub")] - public double MaxPocketLootTotalRub { get; set; } + public int MaxPocketLootTotalRub { get; set; } [JsonPropertyName("maxVestLootTotalRub")] - public double MaxVestLootTotalRub { get; set; } + public int MaxVestLootTotalRub { get; set; } /** Percentage chance a bot from a wave is converted into a PMC, first key = map, second key = bot wildspawn type (assault/exusec), value: min+max chance to be converted */ [JsonPropertyName("convertIntoPmcChance")] @@ -117,7 +117,7 @@ public class PmcConfig : BaseConfig public int? AddPrefixToSameNamePMCAsPlayerChance { get; set; } [JsonPropertyName("lootItemLimitsRub")] - public List? LootItemLimitsRub { get; set; } + public List? LootItemLimitsRub { get; set; } } public class HostilitySettings @@ -171,13 +171,16 @@ public class MinMaxLootValue : MinMax { [JsonPropertyName("value")] public double Value { get; set; } - +} + +public class MinMaxLootItemValue : MinMax +{ [JsonPropertyName("backpack")] public MinMax Backpack { get; set; } - + [JsonPropertyName("pocket")] public MinMax Pocket { get; set; } - + [JsonPropertyName("vest")] public MinMax Vest { get; set; } } diff --git a/Core/Services/BotLootCacheService.cs b/Core/Services/BotLootCacheService.cs index 02dde678..c114badb 100644 --- a/Core/Services/BotLootCacheService.cs +++ b/Core/Services/BotLootCacheService.cs @@ -1,6 +1,7 @@ using Core.Annotations; using Core.Generators; using Core.Helpers; +using Core.Models.Common; using Core.Models.Eft.Common.Tables; using Core.Models.Spt.Bots; using Core.Utils.Cloners; @@ -54,7 +55,8 @@ public class BotLootCacheService string botRole, bool isPmc, string lootType, - BotType botJsonTemplate) + BotType botJsonTemplate, + MinMax? itemPriceMinMax = null) { throw new NotImplementedException(); }