diff --git a/Core/Generators/BotEquipmentModGenerator.cs b/Core/Generators/BotEquipmentModGenerator.cs index 8fcc9094..ffc8a4b3 100644 --- a/Core/Generators/BotEquipmentModGenerator.cs +++ b/Core/Generators/BotEquipmentModGenerator.cs @@ -1,19 +1,65 @@ -using Core.Annotations; +using Core.Annotations; using Core.Models.Eft.Common; using Core.Models.Eft.Common.Tables; using Core.Models.Enums; using Core.Models.Spt.Bots; using Core.Models.Spt.Config; +using Core.Models.Utils; +using Core.Helpers; +using Core.Models.Spt.Server; +using Core.Servers; +using Core.Services; +using Core.Utils; +using Core.Utils.Cloners; +using System.Collections.Generic; namespace Core.Generators; [Injectable] public class BotEquipmentModGenerator { + private readonly ISptLogger _logger; + private readonly HashUtil _hashUtil; + private readonly RandomUtil _randomUtil; + private readonly ItemHelper _itemHelper; + private readonly BotGeneratorHelper _botGeneratorHelper; + private readonly BotEquipmentModPoolService _botEquipmentModPoolService; + private readonly PresetHelper _presetHelper; + private readonly ProbabilityHelper _probabilityHelper; + private readonly LocalisationService _localisationService; + private readonly ItemFilterService _itemFilterService; + private readonly ConfigServer _configServer; + private readonly ICloner _cloner; private BotConfig _botConfig; - public BotEquipmentModGenerator() + public BotEquipmentModGenerator( + ISptLogger logger, + HashUtil hashUtil, + RandomUtil randomUtil, + ItemHelper itemHelper, + BotGeneratorHelper botGeneratorHelper, + BotEquipmentModPoolService botEquipmentModPoolService, + PresetHelper presetHelper, + ProbabilityHelper probabilityHelper, + LocalisationService localisationService, + ItemFilterService itemFilterService, + ConfigServer configServer, + ICloner cloner) { + _logger = logger; + _hashUtil = hashUtil; + _randomUtil = randomUtil; + _itemHelper = itemHelper; + _botGeneratorHelper = botGeneratorHelper; + _botEquipmentModPoolService = botEquipmentModPoolService; + _presetHelper = presetHelper; + _probabilityHelper = probabilityHelper; + _localisationService = localisationService; + _itemFilterService = itemFilterService; + _configServer = configServer; + _cloner = cloner; + + _botConfig = _configServer.GetConfig(); } /// @@ -26,10 +72,153 @@ public class BotEquipmentModGenerator /// The relevant blacklist from bot.json equipment dictionary /// should this mod be forced to spawn /// Item + compatible mods as an array - public Item GenerateModsForEquipment(List equipment, string parentId, TemplateItem parentTemplate, GenerateEquipmentProperties settings, + public List GenerateModsForEquipment(List equipment, string parentId, TemplateItem parentTemplate, GenerateEquipmentProperties settings, EquipmentFilterDetails specificBlacklist, bool shouldForceSpawn = false) { - throw new NotImplementedException(); + var forceSpawn = shouldForceSpawn; + + // Get mod pool for the desired item + var compatibleModsPool = settings.ModPool[parentTemplate.Id]; + if (compatibleModsPool is null) + { + _logger.Warning($"bot: { settings.BotData.Role} lacks a mod slot pool for item: { parentTemplate.Id} { parentTemplate.Name}"); + } + + // Iterate over mod pool and choose mods to add to item + foreach (var modSlotKvP in compatibleModsPool) + { + var modSlotName = modSlotKvP.Key; + // Get the templates slot object from db + var itemSlotTemplate = GetModItemSlotFromDb(modSlotName, parentTemplate); + if (itemSlotTemplate is null) + { + _logger.Error(_localisationService.GetText("bot-mod_slot_missing_from_item", new { + modSlot = modSlotName, + parentId= parentTemplate.Id, + parentName= parentTemplate.Name, + botRole= settings.BotData.Role + })); + + continue; + } + + var modSpawnResult = ShouldModBeSpawned( + itemSlotTemplate, + modSlotName, + settings.SpawnChances.EquipmentModsChances, + settings.BotEquipmentConfig); + + // Rolled to skip mod and it shouldnt be force-spawned + if (modSpawnResult == ModSpawn.SKIP && !forceSpawn) + { + continue; + } + + // Ensure submods for nvgs all spawn together + if (modSlotName == "mod_nvg") + { + forceSpawn = true; + } + + // Get pool of items we can add for this slot + var modPoolToChooseFrom = modSlotKvP.Value; + + // Filter the pool of items in blacklist + var filteredModPool = FilterModsByBlacklist(modPoolToChooseFrom, specificBlacklist, modSlotName); + if (filteredModPool.Count > 0) + { + // use filtered pool as it has items in it + modPoolToChooseFrom = filteredModPool; + } + + // Slot can hold armor plates + we are filtering possible items by bot level, handle + if ( + settings.BotEquipmentConfig.FilterPlatesByLevel.GetValueOrDefault(false) && + _itemHelper.IsRemovablePlateSlot(modSlotName.ToLower()) + ) + { + var plateSlotFilteringOutcome = FilterPlateModsForSlotByLevel( + settings, + modSlotName.ToLower(), + compatibleModsPool[modSlotName], + parentTemplate); + if (plateSlotFilteringOutcome.Result is Result.UNKNOWN_FAILURE or Result.NO_DEFAULT_FILTER) + { + _logger.Debug($"Plate slot: {modSlotName} selection for armor: {parentTemplate.Id} failed: {plateSlotFilteringOutcome.Result}, skipping"); + + continue; + } + + if (plateSlotFilteringOutcome.Result == Result.LACKS_PLATE_WEIGHTS) + { + _logger.Warning($"Plate slot: {modSlotName} lacks weights for armor: { parentTemplate.Id}, unable to adjust plate choice, using existing data"); + } + + // Replace mod pool with pool of chosen plate items + modPoolToChooseFrom = plateSlotFilteringOutcome.PlateModTemplates; + } + + // Choose random mod from pool and check its compatibility + string modTpl = null; + var found = false; + var exhaustableModPool = CreateExhaustableArray(modPoolToChooseFrom); + while (exhaustableModPool.HasValues()) + { + modTpl = exhaustableModPool.GetRandomValue(); + if (modTpl is not null && !_botGeneratorHelper.IsItemIncompatibleWithCurrentItems(equipment, modTpl, modSlotName).Incompatible.GetValueOrDefault(false)) + { + found = true; + break; + } + } + + // Compatible item not found but slot REQUIRES item, get random item from db + if (!found && itemSlotTemplate.Required.GetValueOrDefault(false)) + { + modTpl = GetRandomModTplFromItemDb(modTpl, itemSlotTemplate, modSlotName, equipment); + found = modTpl is not null; + } + + // Compatible item not found + not required - skip + if (!(found || itemSlotTemplate.Required.GetValueOrDefault(false))) + { + continue; + } + + // Get chosen mods db template and check it fits into slot + var modTemplate = _itemHelper.GetItem(modTpl); + if ( + !IsModValidForSlot( + modTemplate, + itemSlotTemplate, + modSlotName, + parentTemplate, + settings.BotData.Role) + ) + { + continue; + } + + // Generate new id to ensure all items are unique on bot + var modId = _hashUtil.Generate(); + equipment.Add( + CreateModItem(modId, modTpl, parentId, modSlotName, modTemplate.Value, settings.BotData.Role)); + + // Does item being added exist in mod pool - has its own mod pool + if (settings.ModPool.ContainsKey(modTpl)) + { + // Call self again with mod being added as item to add child mods to + GenerateModsForEquipment( + equipment, + modId, + modTemplate.Value, + settings, + specificBlacklist, + forceSpawn); + } + } + + return equipment; } /// @@ -66,7 +255,13 @@ public class BotEquipmentModGenerator /// True if it should public bool ShouldForceSubStockSlots(string modSlot, EquipmentFilters botEquipConfig, TemplateItem modToAddTemplate) { - throw new NotImplementedException(); + // Slots a weapon can store its stock in + string[] stockSlots = ["mod_stock", "mod_stock_000", "mod_stock_001", "mod_stock_akms"]; + + // Can the stock hold child items + var hasSubSlots = modToAddTemplate.Properties.Slots?.Count > 0; + + return (stockSlots.Contains(modSlot) && hasSubSlots) || botEquipConfig.ForceStock.GetValueOrDefault(false); } /// @@ -77,18 +272,35 @@ public class BotEquipmentModGenerator /// true if it's a front/rear sight public bool ModIsFrontOrRearSight(string modSlot, string tpl) { - throw new NotImplementedException(); + // Gas block /w front sight is special case, deem it a 'front sight' too + if (modSlot == "mod_gas_block" && tpl == "5ae30e795acfc408fb139a0b") + { + // M4A1 front sight with gas block + return true; + } + + return ((string[])["mod_sight_front", "mod_sight_rear"]).Contains(modSlot); } /// /// Does the provided mod details show the mod can hold a scope /// /// e.g. mod_scope, mod_mount - /// Parent id of mod item + /// Parent id of mod item /// true if it can hold a scope - public bool ModSlotCanHoldScope(string modSlot, string ModsParentId) + public bool ModSlotCanHoldScope(string modSlot, string modsParentId) { - throw new NotImplementedException(); + return ( + ((string[])[ + "mod_scope", + "mod_mount", + "mod_mount_000", + "mod_scope_000", + "mod_scope_001", + "mod_scope_002", + "mod_scope_003", + ]).Contains(modSlot.ToLower()) && modsParentId == BaseClasses.MOUNT + ); } /// @@ -97,9 +309,25 @@ public class BotEquipmentModGenerator /// Chance dictionary to update /// /// - public void AdjustSlotSpawnChances(Dictionary modSpawnChances, List modSlotsToAdjust, double newChancePercent) + public void AdjustSlotSpawnChances(Dictionary? modSpawnChances, List? modSlotsToAdjust, double newChancePercent) { - throw new NotImplementedException(); + if (modSpawnChances is null) + { + _logger.Warning("AdjustSlotSpawnChances() modSpawnChances missing"); + + return; + } + + if (modSlotsToAdjust is null) + { + _logger.Warning("AdjustSlotSpawnChances() modSlotsToAdjust missing"); + + return; + } + + foreach (var modName in modSlotsToAdjust) { + modSpawnChances[modName] = newChancePercent; + } } /// @@ -108,9 +336,9 @@ public class BotEquipmentModGenerator /// Slot id to check /// OPTIONAL: parent id of modslot being checked /// True if modSlot can have muzzle-related items - public bool AodSlotCanHoldMuzzleDevices(string modSlot, string? modsParentId) + public bool ModSlotCanHoldMuzzleDevices(string modSlot, string? modsParentId) { - throw new NotImplementedException(); + return ((string[])["mod_muzzle", "mod_muzzle_000", "mod_muzzle_001"]).Contains(modSlot.ToLower()); } /// @@ -130,9 +358,20 @@ public class BotEquipmentModGenerator /// e.g patron_in_weapon /// item template /// Slot item - public Slot GetModItemSlotFromDb(string modSlot, TemplateItem parentTemplate) + public Slot? GetModItemSlotFromDb(string modSlot, TemplateItem parentTemplate) { - throw new NotImplementedException(); + var modSlotLower = modSlot.ToLower(); + switch (modSlotLower) + { + case "patron_in_weapon": + case "patron_in_weapon_000": + case "patron_in_weapon_001": + return parentTemplate.Properties.Chambers.FirstOrDefault((chamber) => chamber.Name.Contains(modSlotLower)); + case "cartridges": + return parentTemplate.Properties.Cartridges.FirstOrDefault((c) => c.Name.ToLower() == modSlotLower); + default: + return parentTemplate.Properties.Slots.FirstOrDefault((s) => s.Name.ToLower() == modSlotLower); + } } /// @@ -145,7 +384,20 @@ public class BotEquipmentModGenerator /// ModSpawn.SPAWN when mod should be spawned, ModSpawn.DEFAULT_MOD when default mod should spawn, ModSpawn.SKIP when mod is skipped public ModSpawn ShouldModBeSpawned(Slot itemSlot, string modSlotName, Dictionary modSpawnChances, EquipmentFilters botEquipConfig) { - throw new NotImplementedException(); + var slotRequired = itemSlot.Required; + if (GetAmmoContainers().Contains(modSlotName)) + { + // Always force mags/cartridges in weapon to spawn + return ModSpawn.SPAWN; + } + var spawnMod = _probabilityHelper.RollChance(modSpawnChances[modSlotName]); + if (!spawnMod && (slotRequired.GetValueOrDefault(false) || botEquipConfig.WeaponSlotIdsToMakeRequired.Contains(modSlotName))) + { + // Edge case: Mod is required but spawn chance roll failed, choose default mod spawn for slot + return ModSpawn.DEFAULT_MOD; + } + + return spawnMod ? ModSpawn.SPAWN : ModSpawn.SKIP; } /// @@ -199,9 +451,9 @@ public class BotEquipmentModGenerator throw new NotImplementedException(); } - public object CreateExhaustableArray(T itemsToAddToArray) // TODO: this wont likely be needed, reimplement for C# + public ExhaustableArray CreateExhaustableArray(List itemsToAddToArray) // TODO: this wont likely be needed, reimplement for C# { - throw new NotImplementedException(); + return new ExhaustableArray(itemsToAddToArray, _randomUtil, _cloner); } /// @@ -210,9 +462,9 @@ public class BotEquipmentModGenerator /// /// Tpls that are incompatible and should not be used /// string array of compatible mod tpls with weapon - public List GetFilteredModPool(List modPool, List tplBlacklist) // TODO: tplBlacklist was Set + public List GetFilteredModPool(List modPool, List tplBlacklist) { - throw new NotImplementedException(); + return modPool.Where((tpl) => !tplBlacklist.Contains(tpl)).ToList(); } /// @@ -226,7 +478,19 @@ public class BotEquipmentModGenerator /// Array of mod tpls public List GetModPoolForSlot(ModToSpawnRequest request, TemplateItem weaponTemplate) { - throw new NotImplementedException(); + // Mod is flagged as being default only, try and find it in globals + if (request.ModSpawnResult == ModSpawn.DEFAULT_MOD) + { + return GetModPoolForDefaultSlot(request, weaponTemplate); + } + + if (request.IsRandomisableSlot.GetValueOrDefault(false)) + { + return GetDynamicModPool(request.ParentTemplate.Id, request.ModSlot, request.BotEquipBlacklist); + } + + // Required mod is not default or randomisable, use existing pool + return request.ItemModPool[request.ModSlot]; } public List GetModPoolForDefaultSlot(ModToSpawnRequest request, TemplateItem weaponTemplate) @@ -234,9 +498,10 @@ public class BotEquipmentModGenerator throw new NotImplementedException(); } - public object GetMatchingModFromPreset(ModToSpawnRequest request, TemplateItem weaponTemplate) // TODO: no return type given in node server + public Item GetMatchingModFromPreset(ModToSpawnRequest request, TemplateItem weaponTemplate) { - throw new NotImplementedException(); + var matchingPreset = GetMatchingPreset(weaponTemplate, request.ParentTemplate.Id); + return matchingPreset?.Items.FirstOrDefault((item) => item?.SlotId?.ToLower() == request.ModSlot.ToLower()); } /// @@ -247,7 +512,21 @@ public class BotEquipmentModGenerator /// Default preset found public Preset? GetMatchingPreset(TemplateItem weaponTemplate, string parentItemTpl) { - throw new NotImplementedException(); + // Edge case - using mp5sd reciever means default mp5 handguard doesn't fit + var isMp5sd = parentItemTpl == "5926f2e086f7745aae644231"; + if (isMp5sd) + { + return _presetHelper.GetPreset("59411abb86f77478f702b5d2"); + } + + // Edge case - dvl 500mm is the silenced barrel and has specific muzzle mods + var isDvl500mmSilencedBarrel = parentItemTpl == "5888945a2459774bf43ba385"; + if (isDvl500mmSilencedBarrel) + { + return _presetHelper.GetPreset("59e8d2b386f77445830dd299"); + } + + return _presetHelper.GetDefaultPreset(weaponTemplate.Id); } /// @@ -258,7 +537,13 @@ public class BotEquipmentModGenerator /// True if incompatible public bool WeaponModComboIsIncompatible(List weapon, string modTpl) { - throw new NotImplementedException(); + // STM-9 + AR-15 Lone Star Ion Lite handguard + if (weapon[0].Template == "60339954d62c9b14ed777c06" && modTpl == "5d4405f0a4b9361e6a4e6bd9") + { + return true; + } + + return false; } /// @@ -273,7 +558,12 @@ public class BotEquipmentModGenerator /// Item object public Item CreateModItem(string modId, string modTpl, string parentId, string modSlot, TemplateItem modTemplate, string botRole) { - throw new NotImplementedException(); + return new Item { + Id = modId, + Template = modTpl, + ParentId = parentId, + SlotId = modSlot, + Upd = _botGeneratorHelper.GenerateExtraPropertiesForItem(modTemplate, botRole)}; } /// @@ -283,7 +573,7 @@ public class BotEquipmentModGenerator /// string array public List GetAmmoContainers() { - throw new NotImplementedException(); + return ["mod_magazine", "patron_in_weapon", "patron_in_weapon_000", "patron_in_weapon_001", "cartridges"]; } /// @@ -296,12 +586,27 @@ public class BotEquipmentModGenerator /// Item tpl public string? GetRandomModTplFromItemDb(string fallbackModTpl, Slot parentSlot, string modSlot, List items) { - throw new NotImplementedException(); + // Find compatible mods and make an array of them + var allowedItems = parentSlot.Props.Filters[0].Filter; + + // Find mod item that fits slot from sorted mod array + var exhaustableModPool = CreateExhaustableArray(allowedItems); + var tmpModTpl = fallbackModTpl; + while (exhaustableModPool.HasValues()) + { + tmpModTpl = exhaustableModPool.GetRandomValue(); + if (!_botGeneratorHelper.IsItemIncompatibleWithCurrentItems(items, tmpModTpl, modSlot).Incompatible.GetValueOrDefault(false)) + { + return tmpModTpl; + } + } + + // No mod found + return null; } /// /// Check if mod exists in db + is for a required slot - /// TODO: modToAdd type was [boolean, ITemplateItem] in node /// /// Db template of mod to check /// Slot object the item will be placed as child into @@ -309,7 +614,7 @@ public class BotEquipmentModGenerator /// Db template of the mods being added /// Bots wildspawntype (assault/pmcBot/exUsec etc) /// True if valid for slot - public bool IsModValidForSlot(object modToAdd, Slot slotAddedToTemplate, string modSlot, TemplateItem parentTemplate, string botRole) + public bool IsModValidForSlot(KeyValuePair modToAdd, Slot slotAddedToTemplate, string modSlot, TemplateItem parentTemplate, string botRole) { throw new NotImplementedException(); } @@ -336,7 +641,19 @@ public class BotEquipmentModGenerator /// Array of compatible items for that slot public List GetDynamicModPool(string parentItemId, string modSlot, EquipmentFilterDetails botEquipBlacklist) { - throw new NotImplementedException(); + var modsFromDynamicPool = _cloner.Clone( + _botEquipmentModPoolService.GetCompatibleModsForWeaponSlot(parentItemId, modSlot) + ); + + var filteredMods = FilterModsByBlacklist(modsFromDynamicPool, botEquipBlacklist, modSlot); + if (!filteredMods.Any()) + { + _logger.Warning(_localisationService.GetText("bot-unable_to_filter_mod_slot_all_blacklisted", modSlot)); + + return modsFromDynamicPool; + } + + return filteredMods; } /// @@ -346,9 +663,21 @@ public class BotEquipmentModGenerator /// Equipment blacklist /// Slot mods belong to /// Filtered array of mod tpls - public List FilterModsByBlacklist(List allowedMods, EquipmentFilterDetails botEquipBlacklist, string modSlot) + public List FilterModsByBlacklist(List allowedMods, EquipmentFilterDetails? botEquipBlacklist, string modSlot) { - throw new NotImplementedException(); + // No blacklist, nothing to filter out + if (botEquipBlacklist is null) + { + return allowedMods; + } + + var result = new List(); + + // Get item blacklist and mod equipment blacklist as one array + var blacklist = _itemFilterService.GetBlacklistedItems().Concat(botEquipBlacklist.Equipment[modSlot]); + result = allowedMods.Where((tpl) => !blacklist.Contains(tpl)).ToList(); + + return result; } /// @@ -374,7 +703,14 @@ public class BotEquipmentModGenerator /// String array of shells for multiple camora sources public List MergeCamoraPools(Dictionary> camorasWithShells) { - throw new NotImplementedException(); + var uniqueShells = new HashSet(); + foreach (var shell in camorasWithShells + .SelectMany(shellKvP => shellKvP.Value)) + { + uniqueShells.Add(shell); + } + + return uniqueShells.ToList(); } /// diff --git a/Core/Generators/BotInventoryGenerator.cs b/Core/Generators/BotInventoryGenerator.cs index f243b22a..059ac12d 100644 --- a/Core/Generators/BotInventoryGenerator.cs +++ b/Core/Generators/BotInventoryGenerator.cs @@ -359,7 +359,7 @@ public class BotInventoryGenerator var tacVestsWithArmor = templateEquipment[EquipmentSlots.TacticalVest].Where(kvp => _itemHelper.ItemHasSlots(kvp.Key)) .ToDictionary(kvp => kvp.Key, kvp => kvp.Value); - if (tacVestsWithArmor.Count() == 0) + if (!tacVestsWithArmor.Any()) { _logger.Debug($"Unable to filter to only armored rigs as bot: {botRole} has none in pool"); @@ -381,7 +381,7 @@ public class BotInventoryGenerator var tacVestsWithoutArmor = templateEquipment[EquipmentSlots.TacticalVest].Where(kvp => !_itemHelper.ItemHasSlots(kvp.Key)) .ToDictionary(kvp => kvp.Key, kvp => kvp.Value); - if (!allowEmptyResult && tacVestsWithoutArmor.Count() == 0) + if (!allowEmptyResult && !tacVestsWithoutArmor.Any()) { _logger.Debug($"Unable to filter to only unarmored rigs as bot: {botRole} has none in pool"); @@ -398,13 +398,12 @@ public class BotInventoryGenerator /// true when item added public bool GenerateEquipment(GenerateEquipmentProperties settings) { - _logger.Error("NOT IMPLEMENTED - GenerateEquipment"); List slotsToCheck = [EquipmentSlots.Pockets.ToString(), EquipmentSlots.SecuredContainer.ToString()]; double? spawnChance = slotsToCheck.Contains(settings.RootEquipmentSlot.ToString()) ? 100 - : settings.SpawnChances.EquipmentChances[settings.RootEquipmentSlot.ToString()]; + : settings.SpawnChances.EquipmentChances.GetValueOrDefault(settings.RootEquipmentSlot.ToString()); - if (spawnChance is null) + if (!spawnChance.HasValue) { _logger.Warning(_localisationService.GetText("bot-no_spawn_chance_defined_for_equipment_slot", settings.RootEquipmentSlot)); @@ -414,22 +413,22 @@ public class BotInventoryGenerator // Roll dice on equipment item var shouldSpawn = _randomUtil.GetChance100(spawnChance ?? 0); - if (shouldSpawn && settings.RootEquipmentPool.Count() == 0) + if (shouldSpawn && !settings.RootEquipmentPool.Any()) { - TemplateItem pickedItemDb = new TemplateItem(); + var pickedItemDb = new TemplateItem(); var found = false; - // Limit attempts to find a compatible item as its expensive to check them all + // Limit attempts to find a compatible item as it's expensive to check them all var maxAttempts = Math.Round(settings.RootEquipmentPool.Count() * 0.75); // Roughly 75% of pool size var attempts = 0; while (!found) { - if (settings.RootEquipmentPool.Count() == 0) + if (!settings.RootEquipmentPool.Any()) { return false; } - var chosenItemTpl = _weightedRandomHelper.GetWeightedValue(settings.RootEquipmentPool); + var chosenItemTpl = _weightedRandomHelper.GetWeightedValue(settings.RootEquipmentPool); var dbResult = _itemHelper.GetItem(chosenItemTpl); if (!dbResult.Key) @@ -490,8 +489,8 @@ public class BotInventoryGenerator ); // Edge case: Filter the armor items mod pool if bot exists in config dict + config has armor slot - if ((_botConfig.Equipment[settings.BotData.EquipmentRole] is not null) && - (settings.RandomisationDetails.RandomisedArmorSlots.Contains(settings.RootEquipmentSlot.ToString()))) + if (_botConfig.Equipment[settings.BotData.EquipmentRole] is not null && + settings.RandomisationDetails.RandomisedArmorSlots.Contains(settings.RootEquipmentSlot.ToString())) { // Filter out mods from relevant blacklist settings.ModPool[pickedItemDb.Id] = GetFilteredDynamicModsForItem( @@ -501,7 +500,7 @@ public class BotInventoryGenerator } // Does item have slots for sub-mods to be inserted into - if (pickedItemDb.Properties.Slots?.Count() > 0 && (settings.GenerateModsBlacklist.Contains(pickedItemDb.Id))) + if (pickedItemDb.Properties.Slots.Any() && settings.GenerateModsBlacklist.Contains(pickedItemDb.Id)) { var childItemsToAdd = _botEquipmentModGenerator.GenerateModsForEquipment( [item], diff --git a/Core/Models/Spt/Server/ExhaustableArray.cs b/Core/Models/Spt/Server/ExhaustableArray.cs index 91e9171c..9225ff33 100644 --- a/Core/Models/Spt/Server/ExhaustableArray.cs +++ b/Core/Models/Spt/Server/ExhaustableArray.cs @@ -1,46 +1,49 @@ -// using System.Collections.Generic; -// -// namespace Core.Models.Spt.Server; -// -// public class ExhaustableArray -// { -// private List pool; -// -// public ExhaustableArray(List itemPool, RandomUtil randomUtil, ICloner cloner) -// { -// this.pool = cloner.Clone(itemPool); -// } -// -// public T GetRandomValue() -// { -// if (pool == null || pool.Count == 0) -// { -// return default; -// } -// -// int index = randomUtil.GetInt(0, pool.Count - 1); -// T toReturn = cloner.Clone(pool[index]); -// pool.RemoveAt(index); -// return toReturn; -// } -// -// public T GetFirstValue() -// { -// if (pool == null || pool.Count == 0) -// { -// return default; -// } -// -// T toReturn = cloner.Clone(pool[0]); -// pool.RemoveAt(0); -// return toReturn; -// } -// -// public bool HasValues() -// { -// return pool != null && pool.Count > 0; -// } -// } +using Core.Utils; +using Core.Utils.Cloners; -// TODO: Convert this to C# properly +namespace Core.Models.Spt.Server; +public class ExhaustableArray +{ + private readonly RandomUtil _randomUtil; + private readonly ICloner _cloner; + private List pool; + + public ExhaustableArray(List itemPool, RandomUtil randomUtil, ICloner cloner) + { + _randomUtil = randomUtil; + _cloner = cloner; + this.pool = _cloner.Clone(itemPool); + } + + public T GetRandomValue() + { + if (pool == null || pool.Count == 0) + { + return default; + } + + var index = _randomUtil.GetInt(0, pool.Count - 1); + T toReturn = _cloner.Clone(pool[index]); + pool.RemoveAt(index); + + return toReturn; + } + + public T GetFirstValue() + { + if (pool == null || pool.Count == 0) + { + return default; + } + + T toReturn = _cloner.Clone(pool[0]); + pool.RemoveAt(0); + return toReturn; + } + + public bool HasValues() + { + return pool?.Count > 0; + } +}