diff --git a/Core/Generators/BotLootGenerator.cs b/Core/Generators/BotLootGenerator.cs index a0e0504e..c871299d 100644 --- a/Core/Generators/BotLootGenerator.cs +++ b/Core/Generators/BotLootGenerator.cs @@ -31,8 +31,8 @@ public class BotLootGenerator private readonly ConfigServer _configServer; private readonly ICloner _cloner; - private BotConfig _botConfig; - private PmcConfig _pmcConfig; + private readonly BotConfig _botConfig; + private readonly PmcConfig _pmcConfig; public BotLootGenerator( ISptLogger logger, @@ -77,13 +77,13 @@ public class BotLootGenerator /// /// /// - public ItemSpawnLimitSettings GetItemSpawnLimitsForBot(string botRole) + private ItemSpawnLimitSettings GetItemSpawnLimitsForBot(string botRole) { // Init item limits Dictionary limitsForBotDict = new(); InitItemLimitArray(botRole, limitsForBotDict); - return new() { CurrentLimits = limitsForBotDict, GlobalLimits = GetItemSpawnLimitsForBotType(botRole) }; + return new ItemSpawnLimitSettings { CurrentLimits = limitsForBotDict, GlobalLimits = GetItemSpawnLimitsForBotType(botRole) }; } /// @@ -98,10 +98,10 @@ public class BotLootGenerator public void GenerateLoot(string sessionId, BotType botJsonTemplate, bool isPmc, string botRole, BotBaseInventory botInventory, int botLevel) { // Limits on item types to be added as loot - var itemCounts = botJsonTemplate.BotGeneration.Items; + var itemCounts = botJsonTemplate.BotGeneration?.Items; if ( - itemCounts.BackpackLoot.Weights is null || + itemCounts?.BackpackLoot.Weights is null || itemCounts.PocketLoot.Weights is null || itemCounts.VestLoot.Weights is null || itemCounts.SpecialItems.Weights is null || @@ -268,7 +268,7 @@ public class BotLootGenerator botInventory, EquipmentSlots.Backpack, botJsonTemplate.BotInventory, - botJsonTemplate.BotChances.WeaponModsChances, + botJsonTemplate.BotChances?.WeaponModsChances, botRole, isPmc, botLevel, @@ -360,16 +360,14 @@ public class BotLootGenerator 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 - ); + if (!isPmc) return null; + + var matchingValue = _pmcConfig?.LootItemLimitsRub?.FirstOrDefault( + minMaxValue => botLevel >= minMaxValue.Min && botLevel <= minMaxValue.Max + ); - return matchingValue; - } + return matchingValue; - return null; } /// @@ -379,17 +377,15 @@ public class BotLootGenerator /// Bots level /// Is the bot a PMC /// int - public double? GetBackpackRoubleTotalByLevel(int botLevel, bool isPmc) + private double? GetBackpackRoubleTotalByLevel(int botLevel, bool isPmc) { - if (isPmc) - { - var matchingValue = _pmcConfig.MaxBackpackLootTotalRub.FirstOrDefault( - (minMaxValue) => botLevel >= minMaxValue.Min && botLevel <= minMaxValue.Max - ); - return matchingValue?.Value; - } + if (!isPmc) return 0; + + var matchingValue = _pmcConfig.MaxBackpackLootTotalRub.FirstOrDefault( + (minMaxValue) => botLevel >= minMaxValue.Min && botLevel <= minMaxValue.Max + ); + return matchingValue?.Value; - return 0; } /// @@ -398,16 +394,16 @@ public class BotLootGenerator /// /// /// - public List GetAvailableContainersBotCanStoreItemsIn(BotBaseInventory botInventory) + private List GetAvailableContainersBotCanStoreItemsIn(BotBaseInventory botInventory) { List result = [EquipmentSlots.Pockets]; - if (botInventory.Items.Any((item) => item.SlotId == EquipmentSlots.TacticalVest.ToString())) + if ((botInventory.Items ?? []).Any((item) => item.SlotId == EquipmentSlots.TacticalVest.ToString())) { result.Add(EquipmentSlots.TacticalVest); } - if (botInventory.Items.Any((item) => item.SlotId == EquipmentSlots.Backpack.ToString())) + if ((botInventory.Items ?? []).Any((item) => item.SlotId == EquipmentSlots.Backpack.ToString())) { result.Add(EquipmentSlots.Backpack); } @@ -420,11 +416,11 @@ public class BotLootGenerator /// /// Inventory to add items to /// Role of bot (pmcBEAR/pmcUSEC) - public void AddForcedMedicalItemsToPmcSecure(BotBaseInventory botInventory, string botRole) + private void AddForcedMedicalItemsToPmcSecure(BotBaseInventory botInventory, string botRole) { // surv12 AddLootFromPool( - new() { { "5d02797c86f774203f38e30a", 1 } }, + new Dictionary { { "5d02797c86f774203f38e30a", 1 } }, [EquipmentSlots.SecuredContainer], 1, botInventory, @@ -436,7 +432,7 @@ public class BotLootGenerator // AFAK AddLootFromPool( - new() { { "60098ad7c2240c0fe85c570a", 1 } }, + new Dictionary { { "60098ad7c2240c0fe85c570a", 1 } }, [EquipmentSlots.SecuredContainer], 10, botInventory, @@ -460,152 +456,152 @@ public class BotLootGenerator /// Total value of loot allowed in roubles /// Is bot being generated for a pmc /// - public void AddLootFromPool + private void AddLootFromPool ( Dictionary pool, List equipmentSlots, double totalItemCount, - BotBaseInventory inventoryToAddItemsTo, // TODO: type for containersIdFull was Set + BotBaseInventory inventoryToAddItemsTo, string botRole, - ItemSpawnLimitSettings itemSpawnLimits, + ItemSpawnLimitSettings? itemSpawnLimits, double totalValueLimitRub = 0, bool isPmc = false, - List containersIdFull = null + List? containersIdFull = null ) { // Loot pool has items var poolSize = pool.Count; - if (poolSize > 0) + + if (poolSize <= 0) return; + + double currentTotalRub = 0; + + var fitItemIntoContainerAttempts = 0; + for (var i = 0; i < totalItemCount; i++) { - double currentTotalRub = 0; - - var fitItemIntoContainerAttempts = 0; - for (var i = 0; i < totalItemCount; i++) + // Pool can become empty if item spawn limits keep removing items + if (pool.Count == 0) { - // Pool can become empty if item spawn limits keep removing items - if (pool.Count == 0) - { - return; - } + return; + } - var weightedItemTpl = _weightedRandomHelper.GetWeightedValue(pool); - var itemResult = _itemHelper.GetItem(weightedItemTpl); - var itemToAddTemplate = itemResult.Value; - if (!itemResult.Key) - { - _logger.Warning($"Unable to process item tpl: {weightedItemTpl} for slots: {equipmentSlots} on bot: {botRole}"); + var weightedItemTpl = _weightedRandomHelper.GetWeightedValue(pool); + var (key, itemToAddTemplate) = _itemHelper.GetItem(weightedItemTpl); + + if (!key) + { + _logger.Warning($"Unable to process item tpl: {weightedItemTpl} for slots: {equipmentSlots} on bot: {botRole}"); + continue; + } + + if (itemSpawnLimits is not null) + { + if (ItemHasReachedSpawnLimit(itemToAddTemplate, botRole, itemSpawnLimits)) + { + // Remove item from pool to prevent it being picked again + pool.Remove(weightedItemTpl); + + i--; continue; } + } - if (itemSpawnLimits is not null) + var newRootItemId = _hashUtil.Generate(); + List itemWithChildrenToAdd = + [ + new() { - if (ItemHasReachedSpawnLimit(itemToAddTemplate, botRole, itemSpawnLimits)) - { - // Remove item from pool to prevent it being picked again - pool.Remove(weightedItemTpl); + Id = newRootItemId, + Template = itemToAddTemplate?.Id ?? string.Empty, + Upd = _botGeneratorHelper.GenerateExtraPropertiesForItem(itemToAddTemplate, botRole) + }, + ]; - i--; - continue; - } - } - - var newRootItemId = _hashUtil.Generate(); - List itemWithChildrenToAdd = - [ - new() - { - Id = newRootItemId, - Template = itemToAddTemplate.Id, - Upd = _botGeneratorHelper.GenerateExtraPropertiesForItem(itemToAddTemplate, botRole) - }, - ]; - - // Is Simple-Wallet / WZ wallet - if (_botConfig.WalletLoot.WalletTplPool.Contains(weightedItemTpl)) + // Is Simple-Wallet / WZ wallet + if (_botConfig.WalletLoot.WalletTplPool.Contains(weightedItemTpl)) + { + var addCurrencyToWallet = _randomUtil.GetChance100(_botConfig.WalletLoot.ChancePercent); + if (addCurrencyToWallet) { - var addCurrencyToWallet = _randomUtil.GetChance100(_botConfig.WalletLoot.ChancePercent); - if (addCurrencyToWallet) + // Create the currency items we want to add to wallet + var itemsToAdd = CreateWalletLoot(newRootItemId); + + // Get the container grid for the wallet + var containerGrid = _inventoryHelper.GetContainerSlotMap(weightedItemTpl); + + // Check if all the chosen currency items fit into wallet + var canAddToContainer = _inventoryHelper.CanPlaceItemsInContainer( + _cloner.Clone(containerGrid), // MUST clone grid before passing in as function modifies grid + itemsToAdd + ); + if (canAddToContainer) { - // Create the currency items we want to add to wallet - var itemsToAdd = CreateWalletLoot(newRootItemId); - - // Get the container grid for the wallet - var containerGrid = _inventoryHelper.GetContainerSlotMap(weightedItemTpl); - - // Check if all the chosen currency items fit into wallet - var canAddToContainer = _inventoryHelper.CanPlaceItemsInContainer( - _cloner.Clone(containerGrid), // MUST clone grid before passing in as function modifies grid - itemsToAdd - ); - if (canAddToContainer) + // Add each currency to wallet + foreach (var itemToAdd in itemsToAdd) { - // Add each currency to wallet - foreach (var itemToAdd in itemsToAdd) - { - _inventoryHelper.PlaceItemInContainer( - containerGrid, - itemToAdd, - itemWithChildrenToAdd[0].Id, - "main" - ); - } - - itemWithChildrenToAdd.AddRange(itemsToAdd.SelectMany(x => x)); + _inventoryHelper.PlaceItemInContainer( + containerGrid, + itemToAdd, + itemWithChildrenToAdd[0].Id, + "main" + ); } + + itemWithChildrenToAdd.AddRange(itemsToAdd.SelectMany(x => x)); } } + } - // Some items (ammoBox/ammo) need extra changes - AddRequiredChildItemsToParent(itemToAddTemplate, itemWithChildrenToAdd, isPmc, botRole); + // Some items (ammoBox/ammo) need extra changes + AddRequiredChildItemsToParent(itemToAddTemplate, itemWithChildrenToAdd, isPmc, botRole); - // Attempt to add item to container(s) - var itemAddedResult = _botGeneratorHelper.AddItemWithChildrenToEquipmentSlot( - equipmentSlots, - newRootItemId, - itemToAddTemplate.Id, - itemWithChildrenToAdd, - inventoryToAddItemsTo, - containersIdFull - ); + // Attempt to add item to container(s) + var itemAddedResult = _botGeneratorHelper.AddItemWithChildrenToEquipmentSlot( + equipmentSlots, + newRootItemId, + itemToAddTemplate?.Id, + itemWithChildrenToAdd, + inventoryToAddItemsTo, + containersIdFull + ); - // Handle when item cannot be added - if (itemAddedResult != ItemAddedResult.SUCCESS) + // Handle when item cannot be added + if (itemAddedResult != ItemAddedResult.SUCCESS) + { + if (itemAddedResult == ItemAddedResult.NO_CONTAINERS) { - if (itemAddedResult == ItemAddedResult.NO_CONTAINERS) - { - // Bot has no container to put item in, exit - _logger.Debug($"Unable to add: {totalItemCount} items to bot as it lacks a container to include them"); - break; - } - - fitItemIntoContainerAttempts++; - if (fitItemIntoContainerAttempts >= 4) - { - _logger.Debug( - $"Failed placing item: {i} of: {totalItemCount} items into: {botRole} " + - $"containers: {string.Join(",", equipmentSlots)}. Tried: {fitItemIntoContainerAttempts} " + - $"times, reason: {itemAddedResult.ToString()}, skipping" - ); - - break; - } - - // Try again, failed but still under attempt limit - continue; + // Bot has no container to put item in, exit + _logger.Debug($"Unable to add: {totalItemCount} items to bot as it lacks a container to include them"); + break; } - // Item added okay, reset counter for next item - fitItemIntoContainerAttempts = 0; - - // Stop adding items to bots pool if rolling total is over total limit - if (totalValueLimitRub > 0) + fitItemIntoContainerAttempts++; + if (fitItemIntoContainerAttempts >= 4) { - currentTotalRub += _handbookHelper.GetTemplatePrice(itemToAddTemplate.Id); - if (currentTotalRub > totalValueLimitRub) - { - break; - } + _logger.Debug( + $"Failed placing item: {i} of: {totalItemCount} items into: {botRole} " + + $"containers: {string.Join(",", equipmentSlots)}. Tried: {fitItemIntoContainerAttempts} " + + $"times, reason: {itemAddedResult.ToString()}, skipping" + ); + + break; + } + + // Try again, failed but still under attempt limit + continue; + } + + // Item added okay, reset counter for next item + fitItemIntoContainerAttempts = 0; + + // Stop adding items to bots pool if rolling total is over total limit + if (totalValueLimitRub > 0) + { + currentTotalRub += _handbookHelper.GetTemplatePrice(itemToAddTemplate.Id); + if (currentTotalRub > totalValueLimitRub) + { + break; } } } @@ -630,8 +626,9 @@ public class BotLootGenerator { // Choose the size of the currency stack - default is 5k, 10k, 15k, 20k, 25k var chosenStackCount = _weightedRandomHelper.GetWeightedValue(_botConfig.WalletLoot.StackSizeWeight); - List items = new List(); - items.Add( + List items = + [ + new Item { Id = _hashUtil.Generate(), @@ -642,7 +639,9 @@ public class BotLootGenerator StackObjectsCount = double.Parse(chosenStackCount) } } - ); + + + ]; result.Add(items); } @@ -656,7 +655,7 @@ public class BotLootGenerator /// Item to add children to /// Is the item being generated for a pmc (affects money/ammo stack sizes) /// role bot has that owns item - public void AddRequiredChildItemsToParent(TemplateItem itemToAddTemplate, List itemToAddChildrenTo, bool isPmc, string botRole) + public void AddRequiredChildItemsToParent(TemplateItem? itemToAddTemplate, List itemToAddChildrenTo, bool isPmc, string botRole) { // Fill ammo box if (_itemHelper.IsOfBaseclass(itemToAddTemplate.Id, BaseClasses.AMMO_BOX)) @@ -695,8 +694,8 @@ public class BotLootGenerator public void AddLooseWeaponsToInventorySlot(string sessionId, BotBaseInventory botInventory, EquipmentSlots equipmentSlot, - BotTypeInventory templateInventory, - Dictionary modChances, + BotTypeInventory? templateInventory, + Dictionary? modChances, string botRole, bool isPmc, int botLevel, @@ -771,7 +770,7 @@ public class BotLootGenerator /// Bot type /// /// true if item has reached spawn limit - public bool ItemHasReachedSpawnLimit(TemplateItem itemTemplate, string botRole, ItemSpawnLimitSettings? itemSpawnLimits) + private bool ItemHasReachedSpawnLimit(TemplateItem? itemTemplate, string botRole, ItemSpawnLimitSettings? itemSpawnLimits) { // PMCs and scavs have different sections of bot config for spawn limits if (itemSpawnLimits is not null && itemSpawnLimits.GlobalLimits?.Count == 0) { diff --git a/Core/Helpers/BotGeneratorHelper.cs b/Core/Helpers/BotGeneratorHelper.cs index dbc8128a..ca8365c3 100644 --- a/Core/Helpers/BotGeneratorHelper.cs +++ b/Core/Helpers/BotGeneratorHelper.cs @@ -17,7 +17,6 @@ public class BotGeneratorHelper { private readonly ISptLogger _logger; private readonly RandomUtil _randomUtil; - private readonly DatabaseService _databaseService; private readonly DurabilityLimitsHelper _durabilityLimitsHelper; private readonly ItemHelper _itemHelper; private readonly InventoryHelper _inventoryHelper; @@ -33,7 +32,6 @@ public class BotGeneratorHelper ( ISptLogger logger, RandomUtil randomUtil, - DatabaseService databaseService, DurabilityLimitsHelper durabilityLimitsHelper, ItemHelper itemHelper, InventoryHelper inventoryHelper, @@ -45,7 +43,6 @@ public class BotGeneratorHelper { _logger = logger; _randomUtil = randomUtil; - _databaseService = databaseService; _durabilityLimitsHelper = durabilityLimitsHelper; _itemHelper = itemHelper; _inventoryHelper = inventoryHelper; @@ -65,7 +62,7 @@ public class BotGeneratorHelper /// Item extra properties are being generated for /// Used by weapons to randomize the durability values. Null for non-equipped items /// Item Upd object with extra properties - public Upd GenerateExtraPropertiesForItem(TemplateItem itemTemplate, string botRole = null) + public Upd GenerateExtraPropertiesForItem(TemplateItem? itemTemplate, string? botRole = null) { // Get raid settings, if no raid, default to day var raidSettings = _applicationContext @@ -75,7 +72,7 @@ public class BotGeneratorHelper Upd itemProperties = new(); - if (itemTemplate.Properties.MaxDurability is not null) + if (itemTemplate?.Properties?.MaxDurability is not null) { if (itemTemplate.Properties.WeapClass is not null) { @@ -89,63 +86,54 @@ public class BotGeneratorHelper } } - if (itemTemplate.Properties.HasHinge ?? false) + if (itemTemplate?.Properties?.HasHinge ?? false) { - itemProperties.Togglable = new() { On = true }; + itemProperties.Togglable = new UpdTogglable { On = true }; } - if (itemTemplate.Properties.Foldable ?? false) + if (itemTemplate?.Properties?.Foldable ?? false) { - itemProperties.Foldable = new() { Folded = false }; + itemProperties.Foldable = new UpdFoldable { Folded = false }; } - if (itemTemplate.Properties.WeapFireType?.Any() ?? false) + if (itemTemplate?.Properties?.WeapFireType?.Count == 0) { - if (itemTemplate.Properties.WeapFireType.Contains("fullauto")) - { - itemProperties.FireMode = new() { FireMode = "fullauto" }; - } - else - { - itemProperties.FireMode = new() { FireMode = _randomUtil.GetArrayValue(itemTemplate.Properties.WeapFireType) }; - } + itemProperties.FireMode = itemTemplate.Properties.WeapFireType.Contains("fullauto") + ? new UpdFireMode { FireMode = "fullauto" } + : new UpdFireMode { FireMode = _randomUtil.GetArrayValue(itemTemplate.Properties.WeapFireType) }; } - if (itemTemplate.Properties.MaxHpResource is not null) + if (itemTemplate?.Properties?.MaxHpResource is not null) { - itemProperties.MedKit = new() + itemProperties.MedKit = new UpdMedKit { - HpResource = (int)GetRandomizedResourceValue( + HpResource = GetRandomizedResourceValue( itemTemplate.Properties.MaxHpResource ?? 0, - _botConfig.LootItemResourceRandomization[botRole]?.Meds + _botConfig.LootItemResourceRandomization[botRole ?? string.Empty].Meds ) }; } - if (itemTemplate.Properties.MaxResource is not null && itemTemplate.Properties.FoodUseTime is not null) + if (itemTemplate?.Properties?.MaxResource is not null && itemTemplate.Properties?.FoodUseTime is not null) { - itemProperties.FoodDrink = new() + itemProperties.FoodDrink = new UpdFoodDrink { - HpPercent = (int)GetRandomizedResourceValue( + HpPercent = GetRandomizedResourceValue( itemTemplate.Properties.MaxResource ?? 0, - _botConfig.LootItemResourceRandomization[botRole]?.Food + _botConfig.LootItemResourceRandomization[botRole ?? string.Empty].Food ), }; } - if (itemTemplate.Parent == BaseClasses.FLASHLIGHT) + if (itemTemplate?.Parent == BaseClasses.FLASHLIGHT) { // Get chance from botconfig for bot type var lightLaserActiveChance = raidIsNight ? GetBotEquipmentSettingFromConfig(botRole, "lightIsActiveNightChancePercent", 50) : GetBotEquipmentSettingFromConfig(botRole, "lightIsActiveDayChancePercent", 25); - itemProperties.Light = new() - { - IsActive = _randomUtil.GetChance100(lightLaserActiveChance), - SelectedMode = 0, - }; + itemProperties.Light = new UpdLight { IsActive = _randomUtil.GetChance100(lightLaserActiveChance), SelectedMode = 0, }; } - else if (itemTemplate.Parent == BaseClasses.TACTICAL_COMBO) + else if (itemTemplate?.Parent == BaseClasses.TACTICAL_COMBO) { // Get chance from botconfig for bot type, use 50% if no value found var lightLaserActiveChance = GetBotEquipmentSettingFromConfig( @@ -153,40 +141,32 @@ public class BotGeneratorHelper "laserIsActiveChancePercent", 50 ); - itemProperties.Light = new() + itemProperties.Light = new UpdLight { IsActive = _randomUtil.GetChance100(lightLaserActiveChance), SelectedMode = 0, }; } - if (itemTemplate.Parent == BaseClasses.NIGHTVISION) + if (itemTemplate?.Parent == BaseClasses.NIGHTVISION) { // Get chance from botconfig for bot type var nvgActiveChance = raidIsNight ? GetBotEquipmentSettingFromConfig(botRole, "nvgIsActiveChanceNightPercent", 90) : GetBotEquipmentSettingFromConfig(botRole, "nvgIsActiveChanceDayPercent", 15); - itemProperties.Togglable = new() - { - On = _randomUtil.GetChance100(nvgActiveChance) - }; + itemProperties.Togglable = new UpdTogglable { On = _randomUtil.GetChance100(nvgActiveChance) }; } // Togglable face shield - if ((itemTemplate.Properties.HasHinge ?? false) && (itemTemplate.Properties.FaceShieldComponent ?? false)) - { - // Get chance from botconfig for bot type, use 75% if no value found - var faceShieldActiveChance = GetBotEquipmentSettingFromConfig( - botRole, - "faceShieldIsActiveChancePercent", - 75 - ); - itemProperties.Togglable = new() - { - On = _randomUtil.GetChance100(faceShieldActiveChance) - } - ; - } + if (!(itemTemplate?.Properties?.HasHinge ?? false) || !(itemTemplate.Properties.FaceShieldComponent ?? false)) return itemProperties; + + // Get chance from botconfig for bot type, use 75% if no value found + var faceShieldActiveChance = GetBotEquipmentSettingFromConfig( + botRole, + "faceShieldIsActiveChancePercent", + 75 + ); + itemProperties.Togglable = new UpdTogglable { On = _randomUtil.GetChance100(faceShieldActiveChance) }; return itemProperties; } @@ -197,7 +177,7 @@ public class BotGeneratorHelper /// Max resource value of medical items /// Value provided from config /// Randomized value from maxHpResource - protected double GetRandomizedResourceValue(double maxResource, RandomisedResourceValues randomizationValues) + private double GetRandomizedResourceValue(double maxResource, RandomisedResourceValues? randomizationValues) { if (randomizationValues is null) { @@ -222,7 +202,7 @@ public class BotGeneratorHelper /// the setting of the weapon attachment/helmet equipment to be activated /// default value for the chance of activation if the botrole or bot equipment role is undefined /// Percent chance to be active - protected double GetBotEquipmentSettingFromConfig(string botRole, string setting, double defaultValue) + private double? GetBotEquipmentSettingFromConfig(string? botRole, string setting, double defaultValue) { if (botRole is null) { @@ -237,9 +217,9 @@ public class BotGeneratorHelper "bot-missing_equipment_settings", new { - botRole = botRole, - setting = setting, - defaultValue = defaultValue + botRole, + setting, + defaultValue } ) ); @@ -248,26 +228,24 @@ public class BotGeneratorHelper } var props = botEquipmentSettings.GetType().GetProperties(); - var propValue = (double?)props.FirstOrDefault(x => x.Name.ToLower() == setting.ToLower()).GetValue(botEquipmentSettings); + var propValue = (double?)props.FirstOrDefault(x => string.Equals(x.Name, setting, StringComparison.CurrentCultureIgnoreCase)) + ?.GetValue(botEquipmentSettings); - if (propValue is null) - { - _logger.Warning( - _localisationService.GetText( - "bot-missing_equipment_settings_property", - new - { - botRole = botRole, - setting = setting, - defaultValue = defaultValue - } - ) - ); + if (propValue is not null) return propValue; - return defaultValue; - } + _logger.Warning( + _localisationService.GetText( + "bot-missing_equipment_settings_property", + new + { + botRole, + setting, + defaultValue + } + ) + ); - return propValue ?? 0; + return defaultValue; } /// @@ -276,7 +254,7 @@ public class BotGeneratorHelper /// weapon object being generated for /// type of bot being generated for /// Repairable object - protected UpdRepairable GenerateWeaponRepairableProperties(TemplateItem itemTemplate, string botRole = null) + private UpdRepairable GenerateWeaponRepairableProperties(TemplateItem itemTemplate, string? botRole = null) { var maxDurability = _durabilityLimitsHelper.GetRandomizedMaxWeaponDurability(itemTemplate, botRole); var currentDurability = _durabilityLimitsHelper.GetRandomizedWeaponDurability( @@ -285,7 +263,7 @@ public class BotGeneratorHelper maxDurability ); - return new() { Durability = (int)currentDurability, MaxDurability = (int)maxDurability }; + return new UpdRepairable { Durability = (int)currentDurability, MaxDurability = (int)maxDurability }; } /// @@ -294,11 +272,11 @@ public class BotGeneratorHelper /// weapon object being generated for /// type of bot being generated for /// Repairable object - protected UpdRepairable GenerateArmorRepairableProperties(TemplateItem itemTemplate, string botRole = null) + private UpdRepairable GenerateArmorRepairableProperties(TemplateItem itemTemplate, string? botRole = null) { - double? maxDurability = null; - double? currentDurability = null; - if (itemTemplate.Properties.ArmorClass == 0) + double? maxDurability; + double? currentDurability; + if (itemTemplate.Properties?.ArmorClass == 0) { maxDurability = itemTemplate.Properties.MaxDurability; currentDurability = itemTemplate.Properties.MaxDurability; @@ -309,11 +287,11 @@ public class BotGeneratorHelper currentDurability = _durabilityLimitsHelper.GetRandomizedArmorDurability( itemTemplate, botRole, - maxDurability ?? 0 + maxDurability ); } - return new() { Durability = (int)currentDurability, MaxDurability = (int)maxDurability }; + return new UpdRepairable { Durability = (int)currentDurability!, MaxDurability = (int)maxDurability! }; } /// @@ -329,15 +307,14 @@ public class BotGeneratorHelper List slotsToCheck = ["Scabbard", "Backpack", "SecureContainer", "Holster", "ArmBand"]; if (slotsToCheck.Contains(equipmentSlot)) { - return new() { Incompatible = false, Found = false, Reason = "" }; + return new ChooseRandomCompatibleModResult { Incompatible = false, Found = false, Reason = "" }; } // TODO: Can probably be optimized to cache itemTemplates as items are added to inventory - var equippedItemsDb = itemsEquipped.Select((equippedItem) => _itemHelper.GetItem(equippedItem.Template).Value); - var itemToEquipDb = _itemHelper.GetItem(tplToCheck); - var itemToEquip = itemToEquipDb.Value; + var equippedItemsDb = itemsEquipped.Select(equippedItem => _itemHelper.GetItem(equippedItem.Template).Value).ToList(); + var (key, itemToEquip) = _itemHelper.GetItem(tplToCheck); - if (!itemToEquipDb.Key) + if (!key) { _logger.Warning( _localisationService.GetText( @@ -350,29 +327,30 @@ public class BotGeneratorHelper ) ); - return new() { Incompatible = true, Found = false, Reason = $"item: {tplToCheck} does not exist in the database" }; + return new ChooseRandomCompatibleModResult { Incompatible = true, Found = false, Reason = $"item: {tplToCheck} does not exist in the database" }; } - if (itemToEquip.Properties is null) + if (itemToEquip?.Properties is null) { _logger.Warning( _localisationService.GetText( "bot-compatibility_check_missing_props", new { - id = itemToEquip.Id, - name = itemToEquip.Name, + id = itemToEquip?.Id, + name = itemToEquip?.Name, slot = equipmentSlot, } ) ); - return new() { Incompatible = true, Found = false, Reason = $"item: {tplToCheck} does not have a _props field" }; + return new ChooseRandomCompatibleModResult { Incompatible = true, Found = false, Reason = $"item: {tplToCheck} does not have a _props field" }; } // Does an equipped item have a property that blocks the desired item - check for prop "BlocksX" .e.g BlocksEarpiece / BlocksFaceCover - var blockingItem = equippedItemsDb.FirstOrDefault( - (item) => item.Properties.GetType().GetProperties().FirstOrDefault(x => x.Name.ToLower() == $"blocks{equipmentSlot}").GetValue(item) is not null + var templateItems = equippedItemsDb.ToList(); + var blockingItem = templateItems.FirstOrDefault( + item => item?.Properties?.GetType().GetProperties().FirstOrDefault(x => x.Name.ToLower() == $"blocks{equipmentSlot}")?.GetValue(item) is not null ); if (blockingItem is not null) { @@ -386,11 +364,11 @@ public class BotGeneratorHelper } // Check if any of the current inventory templates have the incoming item defined as incompatible - blockingItem = equippedItemsDb.FirstOrDefault((x) => x.Properties.ConflictingItems?.Contains(tplToCheck) ?? false); + blockingItem = templateItems.FirstOrDefault(x => x?.Properties?.ConflictingItems?.Contains(tplToCheck) ?? false); if (blockingItem is not null) { // this.logger.warning(`2 incompatibility found between - ${itemToEquip[1]._name} and ${blockingItem._props.Name} - ${equipmentSlot}`); - return new() + return new ChooseRandomCompatibleModResult { Incompatible = true, Found = false, @@ -405,7 +383,7 @@ public class BotGeneratorHelper var existingHeadwear = itemsEquipped.FirstOrDefault((x) => x.SlotId == "Headwear"); if (existingHeadwear is not null) { - return new() + return new ChooseRandomCompatibleModResult { Incompatible = true, Found = false, @@ -421,7 +399,7 @@ public class BotGeneratorHelper var existingFaceCover = itemsEquipped.FirstOrDefault((item) => item.SlotId == "FaceCover"); if (existingFaceCover is not null) { - return new() + return new ChooseRandomCompatibleModResult { Incompatible = true, Found = false, @@ -437,7 +415,7 @@ public class BotGeneratorHelper var existingEarpiece = itemsEquipped.FirstOrDefault((item) => item.SlotId == "Earpiece"); if (existingEarpiece is not null) { - return new() + return new ChooseRandomCompatibleModResult { Incompatible = true, Found = false, @@ -453,7 +431,7 @@ public class BotGeneratorHelper var existingArmorVest = itemsEquipped.FirstOrDefault((item) => item.SlotId == "ArmorVest"); if (existingArmorVest is not null) { - return new() + return new ChooseRandomCompatibleModResult { Incompatible = true, Found = false, @@ -468,7 +446,7 @@ public class BotGeneratorHelper if (blockingInventoryItem is not null) { // this.logger.warning(`3 incompatibility found between - ${itemToEquip[1]._name} and ${blockingInventoryItem._tpl} - ${equipmentSlot}`) - return new() + return new ChooseRandomCompatibleModResult { Incompatible = true, Found = false, @@ -476,7 +454,7 @@ public class BotGeneratorHelper }; } - return new() { Incompatible = false, Reason = "" }; + return new ChooseRandomCompatibleModResult { Incompatible = false, Reason = "" }; } /// @@ -502,16 +480,17 @@ public class BotGeneratorHelper /// Root itms tpl id /// Item to add /// Inventory to add item+children into + /// /// ItemAddedResult result object public ItemAddedResult AddItemWithChildrenToEquipmentSlot( List equipmentSlots, string rootItemId, - string rootItemTplId, + string? rootItemTplId, List itemWithChildren, BotBaseInventory inventory, - List containersIdFull = null) + List? containersIdFull = null) { - /** Track how many containers are unable to be found */ + // Track how many containers are unable to be found var missingContainerCount = 0; foreach (var equipmentSlotId in equipmentSlots) { @@ -521,7 +500,7 @@ public class BotGeneratorHelper } // Get container to put item into - var container = inventory.Items.FirstOrDefault((item) => item.SlotId == equipmentSlotId.ToString()); + var container = (inventory.Items ?? []).FirstOrDefault(item => item.SlotId == equipmentSlotId.ToString()); if (container is null) { missingContainerCount++; @@ -529,7 +508,7 @@ public class BotGeneratorHelper { // Bot doesnt have any containers we want to add item to _logger.Debug( - $"Unable to add item: {itemWithChildren[0].Template} to bot as it lacks the following containers: {string.Join(",", equipmentSlots)}" + $"Unable to add item: {itemWithChildren.FirstOrDefault()?.Template} to bot as it lacks the following containers: {string.Join(",", equipmentSlots)}" ); return ItemAddedResult.NO_CONTAINERS; @@ -540,8 +519,8 @@ public class BotGeneratorHelper } // Get container details from db - var containerTemplate = _itemHelper.GetItem(container.Template); - if (!containerTemplate.Key) + var (key, value) = _itemHelper.GetItem(container.Template); + if (!key) { _logger.Warning(_localisationService.GetText("bot-missing_container_with_tpl", container.Template)); @@ -549,7 +528,7 @@ public class BotGeneratorHelper continue; } - if (!containerTemplate.Value.Properties.Grids?.Any() ?? false) + if (value?.Properties?.Grids?.Count == 0) { // Container has no slots to hold items continue; @@ -560,11 +539,11 @@ public class BotGeneratorHelper // Iterate over each grid in the container and look for a big enough space for the item to be placed in var currentGridCount = 1; - var totalSlotGridCount = containerTemplate.Value.Properties.Grids.Count; - foreach (var slotGrid in containerTemplate.Value.Properties.Grids) + var totalSlotGridCount = value?.Properties?.Grids?.Count; + foreach (var slotGrid in value?.Properties?.Grids ?? []) { // Grid is empty, skip or item size is bigger than grid - if (slotGrid.Props.CellsH == 0 || slotGrid.Props.CellsV == 0 || itemSize[0] * itemSize[1] > slotGrid.Props.CellsV * slotGrid.Props.CellsH) + if (slotGrid.Props?.CellsH == 0 || slotGrid.Props?.CellsV == 0 || itemSize[0] * itemSize[1] > slotGrid.Props?.CellsV * slotGrid.Props?.CellsH) continue; // Can't put item type in grid, skip all grids as we're assuming they have the same rules @@ -575,55 +554,60 @@ public class BotGeneratorHelper } // Get all root items in found container - var existingContainerItems = inventory.Items.Where( - (item) => item.ParentId == container.Id && item.SlotId == slotGrid.Name + var existingContainerItems = (inventory.Items ?? []).Where( + item => item.ParentId == container.Id && item.SlotId == slotGrid.Name ) .ToList(); // Get root items in container we can iterate over to find out what space is free - var containerItemsToCheck = existingContainerItems.Where((x) => x.SlotId == slotGrid.Name); + var containerItemsToCheck = existingContainerItems.Where(x => x.SlotId == slotGrid.Name); foreach (var item in containerItemsToCheck) { // Look for children on items, insert into array if found // (used later when figuring out how much space weapon takes up) var itemWithChildrens = _itemHelper.FindAndReturnChildrenAsItems(inventory.Items, item.Id); - if (itemWithChildrens.Count > 1) - { - existingContainerItems.Remove(item); - existingContainerItems.AddRange(itemWithChildrens); - } + if (itemWithChildrens.Count <= 1) continue; + + existingContainerItems.Remove(item); + existingContainerItems.AddRange(itemWithChildrens); } // Get rid of items free/used spots in current grid - var slotGridMap = _inventoryHelper.GetContainerMap( - slotGrid.Props.CellsH.GetValueOrDefault(), - slotGrid.Props.CellsV.GetValueOrDefault(), - existingContainerItems, - container.Id - ); - - // Try to fit item into grid - var findSlotResult = _containerHelper.FindSlotForItem(slotGridMap, itemSize[0], itemSize[1]); - - // Open slot found, add item to inventory - if (findSlotResult.Success ?? false) + if (slotGrid.Props is not null) { - var parentItem = itemWithChildren.FirstOrDefault((i) => i.Id == rootItemId); + var slotGridMap = _inventoryHelper.GetContainerMap( + slotGrid.Props.CellsH.GetValueOrDefault(), + slotGrid.Props.CellsV.GetValueOrDefault(), + existingContainerItems, + container.Id + ); - // Set items parent to container id - parentItem.ParentId = container.Id; - parentItem.SlotId = slotGrid.Name; - parentItem.Location = new ItemLocation() + // Try to fit item into grid + var findSlotResult = _containerHelper.FindSlotForItem(slotGridMap, itemSize[0], itemSize[1]); + + // Open slot found, add item to inventory + if (findSlotResult.Success ?? false) + { + var parentItem = itemWithChildren.FirstOrDefault((i) => i.Id == rootItemId); + + // Set items parent to container id + if (parentItem is not null) { - X = findSlotResult.X, - Y = findSlotResult.Y, - R = (findSlotResult.Rotation ?? false) ? 1 : 0, + parentItem.ParentId = container.Id; + parentItem.SlotId = slotGrid.Name; + parentItem.Location = new ItemLocation() + { + X = findSlotResult.X, + Y = findSlotResult.Y, + R = (findSlotResult.Rotation ?? false) ? 1 : 0, + } + ; } - ; - inventory.Items.AddRange(itemWithChildren); + (inventory.Items ?? []).AddRange(itemWithChildren); - return ItemAddedResult.SUCCESS; + return ItemAddedResult.SUCCESS; + } } // If we've checked all grids in container and reached this point, there's no space for item @@ -637,13 +621,12 @@ public class BotGeneratorHelper } // if we got to this point, the item couldnt be placed on the container - if (containersIdFull is not null) + if (containersIdFull is null) continue; + + // if the item was a one by one, we know it must be full. Or if the maps cant find a slot for a one by one + if (itemSize[0] == 1 && itemSize[1] == 1) { - // if the item was a one by one, we know it must be full. Or if the maps cant find a slot for a one by one - if (itemSize[0] == 1 && itemSize[1] == 1) - { - containersIdFull.Add(equipmentSlotId.ToString()); - } + containersIdFull.Add(equipmentSlotId.ToString()); } } @@ -656,40 +639,37 @@ public class BotGeneratorHelper /// Items sub-grid we want to place item inside /// Item tpl being placed /// True if allowed - protected bool ItemAllowedInContainer(Grid slotGrid, string itemTpl) + private bool ItemAllowedInContainer(Grid? slotGrid, string? itemTpl) { - var propFilters = slotGrid.Props.Filters; - var excludedFilter = propFilters[0]?.ExcludedFilter ?? []; - var filter = propFilters[0]?.Filter ?? []; + var propFilters = slotGrid?.Props?.Filters; + var excludedFilter = propFilters?.FirstOrDefault()?.ExcludedFilter ?? []; + var filter = propFilters?.FirstOrDefault()?.Filter ?? []; - if (propFilters.Count == 0) + if (propFilters?.Count == 0) { // no filters, item is fine to add return true; } // Check if item base type is excluded - if (excludedFilter is not null || filter is not null) + var itemDetails = _itemHelper.GetItem(itemTpl).Value; + + // if item to add is found in exclude filter, not allowed + if (excludedFilter.Contains(itemDetails?.Parent ?? string.Empty)) { - var itemDetails = _itemHelper.GetItem(itemTpl).Value; + return false; + } - // if item to add is found in exclude filter, not allowed - if (excludedFilter?.Contains(itemDetails.Parent) ?? false) - { - return false; - } + // If Filter array only contains 1 filter and its for basetype 'item', allow it + if (filter.Count == 1 && filter.Contains(BaseClasses.ITEM)) + { + return true; + } - // If Filter array only contains 1 filter and its for basetype 'item', allow it - if (filter?.Count == 1 && filter.Contains(BaseClasses.ITEM)) - { - return true; - } - - // If allowed filter has something in it + filter doesnt have basetype 'item', not allowed - if (filter?.Count > 0 && !filter.Contains(itemDetails.Parent)) - { - return false; - } + // If allowed filter has something in it + filter doesnt have basetype 'item', not allowed + if (filter.Count > 0 && !filter.Contains(itemDetails?.Parent ?? string.Empty)) + { + return false; } return true; diff --git a/Core/Helpers/DurabilityLimitsHelper.cs b/Core/Helpers/DurabilityLimitsHelper.cs index 60e2a824..cede4caa 100644 --- a/Core/Helpers/DurabilityLimitsHelper.cs +++ b/Core/Helpers/DurabilityLimitsHelper.cs @@ -23,7 +23,7 @@ public class DurabilityLimitsHelper /// Item to get max durability for /// Role of bot to get max durability for /// max durability - public double GetRandomizedMaxArmorDurability(TemplateItem itemTemplate, string? botRole = null) + public double GetRandomizedMaxArmorDurability(TemplateItem? itemTemplate, string? botRole = null) { throw new NotImplementedException(); } @@ -47,7 +47,7 @@ public class DurabilityLimitsHelper /// Role of bot to get current durability for /// Max durability of armor /// Current armor durability - public double GetRandomizedArmorDurability(TemplateItem itemTemplate, string? botRole, double maxDurability) + public double GetRandomizedArmorDurability(TemplateItem? itemTemplate, string? botRole, double? maxDurability) { throw new NotImplementedException(); } diff --git a/Core/Helpers/InventoryHelper.cs b/Core/Helpers/InventoryHelper.cs index 10a50dda..d0746744 100644 --- a/Core/Helpers/InventoryHelper.cs +++ b/Core/Helpers/InventoryHelper.cs @@ -80,7 +80,7 @@ public class InventoryHelper /// Container grid to fit items into /// Items to try and fit into grid /// True all fit - public bool CanPlaceItemsInContainer(List> containerFS2D, List> itemsWithChildren) + public bool CanPlaceItemsInContainer(List>? containerFS2D, List> itemsWithChildren) { throw new NotImplementedException(); } @@ -91,7 +91,7 @@ public class InventoryHelper /// Container grid /// Item to check fits /// True it fits - public bool CanPlaceItemInContainer(List> containerFS2D, List itemWithChildren) + public bool CanPlaceItemInContainer(List>? containerFS2D, List itemWithChildren) { throw new NotImplementedException(); } @@ -178,7 +178,7 @@ public class InventoryHelper /// Items id to get size of /// /// [width, height] - public List GetItemSize(string itemTpl, string itemId, List inventoryItems) + public List GetItemSize(string? itemTpl, string itemId, List inventoryItems) { throw new NotImplementedException(); } diff --git a/Core/Helpers/ItemHelper.cs b/Core/Helpers/ItemHelper.cs index ec089eaa..24250cc7 100644 --- a/Core/Helpers/ItemHelper.cs +++ b/Core/Helpers/ItemHelper.cs @@ -502,7 +502,7 @@ public class ItemHelper * @param tpl items template id to look up * @returns bool - is valid + template item object as array */ - public KeyValuePair GetItem(string tpl) + public KeyValuePair GetItem(string? tpl) { // -> Gets item from if (_databaseService.GetItems().Keys.Contains(tpl)) @@ -710,7 +710,7 @@ public class ItemHelper * @param modsOnly Include only mod items, exclude items stored inside root item * @returns A list of Item objects */ - public List FindAndReturnChildrenAsItems(List items, string baseItemId, bool modsOnly = false) + public List FindAndReturnChildrenAsItems(List? items, string baseItemId, bool modsOnly = false) { List list = []; foreach (var childItem in items) diff --git a/Core/Models/Spt/Config/BotConfig.cs b/Core/Models/Spt/Config/BotConfig.cs index 6e37936d..ab0d0e37 100644 --- a/Core/Models/Spt/Config/BotConfig.cs +++ b/Core/Models/Spt/Config/BotConfig.cs @@ -42,7 +42,7 @@ public record BotConfig : BaseConfig /** Blacklist/whitelist items on a bot */ [JsonPropertyName("equipment")] - public Dictionary Equipment { get; set; } + public Dictionary Equipment { get; set; } /** Show a bots botType value after their name */ [JsonPropertyName("showTypeInNickname")] diff --git a/Core/Utils/RandomUtil.cs b/Core/Utils/RandomUtil.cs index ec5205b3..db7c90e1 100644 --- a/Core/Utils/RandomUtil.cs +++ b/Core/Utils/RandomUtil.cs @@ -115,9 +115,9 @@ public class RandomUtil /// /// The percentage chance (0-100) that the event will occur. /// `true` if the event occurs, `false` otherwise. - public bool GetChance100(double chancePercent) + public bool GetChance100(double? chancePercent) { - chancePercent = Math.Clamp(chancePercent, 0f, 100f); + chancePercent = Math.Clamp(chancePercent ?? 0, 0f, 100f); return GetIntEx(100) <= chancePercent; }