diff --git a/Libraries/Core/Generators/BotEquipmentModGenerator.cs b/Libraries/Core/Generators/BotEquipmentModGenerator.cs index eaf37413..b667962b 100644 --- a/Libraries/Core/Generators/BotEquipmentModGenerator.cs +++ b/Libraries/Core/Generators/BotEquipmentModGenerator.cs @@ -860,7 +860,7 @@ public class BotEquipmentModGenerator( request.RandomisationSettings.MinimumMagazineSize is not null ) { - modPool = GetFilterdMagazinePoolByCapacity(request, modPool); + modPool = GetFilterdMagazinePoolByCapacity(request, modPool).ToList(); } // Pick random mod that's compatible @@ -919,9 +919,24 @@ public class BotEquipmentModGenerator( /// Pool of magazine tpls to filter /// Filtered pool of magazine tpls /// - public List GetFilterdMagazinePoolByCapacity(ModToSpawnRequest modSpawnRequest, List modPool) + public IEnumerable GetFilterdMagazinePoolByCapacity(ModToSpawnRequest modSpawnRequest, List modPool) { - throw new NotImplementedException(); + var weaponTpl = modSpawnRequest.Weapon[0].Template; + modSpawnRequest.RandomisationSettings.MinimumMagazineSize.TryGetValue(weaponTpl, out var minMagSizeFromSettings); + var minMagazineSize = minMagSizeFromSettings; + var desiredMagazineTpls = modPool.Where((magTpl) => { + var magazineDb = _itemHelper.GetItem(magTpl).Value; + return magazineDb.Properties is not null && magazineDb.Properties.Cartridges.FirstOrDefault().MaxCount >= minMagazineSize; + }); + + if (!desiredMagazineTpls.Any()) + { + _logger.Warning("Magazine size filter for ${ weaponTpl} was too strict, ignoring filter"); + + return modPool; + } + + return desiredMagazineTpls; } /// @@ -1420,7 +1435,8 @@ public class BotEquipmentModGenerator( var result = new List(); // Get item blacklist and mod equipment blacklist as one array - var blacklist = _itemFilterService.GetBlacklistedItems().Concat(botEquipBlacklist.Equipment[modSlot]); + botEquipBlacklist.Equipment.TryGetValue(modSlot, out var equipmentBlacklistValues); + var blacklist = _itemFilterService.GetBlacklistedItems().Concat(equipmentBlacklistValues ?? []); result = allowedMods.Where((tpl) => !blacklist.Contains(tpl)).ToList(); return result; diff --git a/Libraries/Core/Generators/BotGenerator.cs b/Libraries/Core/Generators/BotGenerator.cs index 3c4d65e9..3514c13e 100644 --- a/Libraries/Core/Generators/BotGenerator.cs +++ b/Libraries/Core/Generators/BotGenerator.cs @@ -668,8 +668,8 @@ public class BotGenerator( // Common skills have additional props if (isCommonSkills) { - ((Common)skillToAdd).PointsEarnedDuringSession = 0; - ((Common)skillToAdd).LastAccess = 0; + skillToAdd.PointsEarnedDuringSession = 0; + skillToAdd.LastAccess = 0; } return skillToAdd; diff --git a/Libraries/Core/Models/Eft/Common/Tables/BotType.cs b/Libraries/Core/Models/Eft/Common/Tables/BotType.cs index c95094d9..3df1638a 100644 --- a/Libraries/Core/Models/Eft/Common/Tables/BotType.cs +++ b/Libraries/Core/Models/Eft/Common/Tables/BotType.cs @@ -2,6 +2,7 @@ using System.Text.Json.Serialization; using Core.Models.Common; using Core.Models.Enums; using Core.Utils.Json.Converters; +using SptCommon.Extensions; namespace Core.Models.Eft.Common.Tables; @@ -57,6 +58,19 @@ public record Appearance [JsonPropertyName("voice")] [JsonConverter(typeof(ArrayToObjectFactoryConverter))] public Dictionary? Voice { get; set; } + + public Dictionary this[string propName] + { + get + { + var matchingProp = GetType() + .GetProperties() + .SingleOrDefault(p => p.GetJsonName() == propName) + ?.GetValue(this); + + return (Dictionary)matchingProp; + } + } } public record Chances diff --git a/Libraries/Core/Services/BotEquipmentFilterService.cs b/Libraries/Core/Services/BotEquipmentFilterService.cs index c8b490bb..c3ae5ce9 100644 --- a/Libraries/Core/Services/BotEquipmentFilterService.cs +++ b/Libraries/Core/Services/BotEquipmentFilterService.cs @@ -348,8 +348,45 @@ public class BotEquipmentFilterService bool showEditWarnings = true) { //TODO, bad typing by key with method below due to, EquipmentSlots - // MAKE CONSISTENT BEFORE IMPLEMENTING - throw new NotImplementedException(); + if (weightingAdjustments is null) + { + return; + } + + if (weightingAdjustments.Add?.Count > 0) + { + foreach (var poolAdjustmentKvP in weightingAdjustments.Add) + { + var locationToUpdate = botItemPool[Enum.Parse(poolAdjustmentKvP.Key)]; + foreach (var itemToAddKvP in poolAdjustmentKvP.Value) + { + locationToUpdate[itemToAddKvP.Key] = itemToAddKvP.Value; + } + } + } + + if (weightingAdjustments.Edit?.Count > 0) + { + foreach (var poolAdjustmentKvP in weightingAdjustments.Edit) + { + var locationToUpdate = botItemPool[Enum.Parse(poolAdjustmentKvP.Key)]; + foreach (var itemToEditKvP in poolAdjustmentKvP.Value) + { + // Only make change if item exists as we're editing, not adding + if (locationToUpdate[itemToEditKvP.Key] != null || locationToUpdate[itemToEditKvP.Key] == 0) + { + locationToUpdate[itemToEditKvP.Key] = itemToEditKvP.Value; + } + else + { + if (showEditWarnings) + { + _logger.Debug($"Tried to edit a non - existent item for slot: {poolAdjustmentKvP} {itemToEditKvP}"); + } + } + } + } + } } /// @@ -414,6 +451,44 @@ public class BotEquipmentFilterService Appearance botItemPool, bool showEditWarnings = true) { - throw new NotImplementedException(); + if (weightingAdjustments is null) + { + return; + } + + if (weightingAdjustments.Add?.Count > 0) + { + foreach (var poolAdjustmentKvP in weightingAdjustments.Add) + { + var locationToUpdate = botItemPool[poolAdjustmentKvP.Key]; + foreach (var itemToAddKvP in poolAdjustmentKvP.Value) + { + locationToUpdate[itemToAddKvP.Key] = itemToAddKvP.Value; + } + } + } + + if (weightingAdjustments.Edit?.Count > 0) + { + foreach (var poolAdjustmentKvP in weightingAdjustments.Edit) + { + var locationToUpdate = botItemPool[poolAdjustmentKvP.Key]; + foreach (var itemToEditKvP in poolAdjustmentKvP.Value) + { + // Only make change if item exists as we're editing, not adding + if (locationToUpdate[itemToEditKvP.Key] != null || locationToUpdate[itemToEditKvP.Key] == 0) + { + locationToUpdate[itemToEditKvP.Key] = itemToEditKvP.Value; + } + else + { + if (showEditWarnings) + { + _logger.Debug($"Tried to edit a non - existent item for slot: {poolAdjustmentKvP} {itemToEditKvP}"); + } + } + } + } + } } } diff --git a/Libraries/Core/Services/BotEquipmentModPoolService.cs b/Libraries/Core/Services/BotEquipmentModPoolService.cs index 1b89b291..3f9c958a 100644 --- a/Libraries/Core/Services/BotEquipmentModPoolService.cs +++ b/Libraries/Core/Services/BotEquipmentModPoolService.cs @@ -1,18 +1,144 @@ -using SptCommon.Annotations; +using SptCommon.Annotations; using Core.Models.Eft.Common.Tables; +using Core.Models.Utils; +using Core.Helpers; +using Core.Servers; +using Core.Models.Enums; +using Core.Models.Spt.Config; +using System.Collections.Generic; namespace Core.Services; [Injectable(InjectionType.Singleton)] public class BotEquipmentModPoolService { + protected ISptLogger _logger; + protected ItemHelper _itemHelper; + protected DatabaseService _databaseService; + protected LocalisationService _localisationService; + protected ConfigServer _configServer; + + protected bool _weaponPoolGenerated = false; + protected Dictionary>> _weaponModPool; + protected Dictionary>> _gearModPool; + protected BotConfig _botConfig; + + public BotEquipmentModPoolService( + ISptLogger logger, + ItemHelper itemHelper, + DatabaseService databaseService, + LocalisationService localisationService, + ConfigServer configServer + ) + { + _logger = logger; + _itemHelper = itemHelper; + _databaseService = databaseService; + _localisationService = localisationService; + _configServer = configServer; + _botConfig = _configServer.GetConfig(); + + _weaponModPool = new Dictionary>>(); + _gearModPool = new Dictionary>>(); + } + /** * Store dictionary of mods for each item passed in * @param items items to find related mods and store in modPool */ - protected void GeneratePool(List items, string poolType) + protected void GeneratePool(IEnumerable? items, string poolType) { - throw new NotImplementedException(); + if (items is null) + { + _logger.Error(_localisationService.GetText("bot-unable_to_generate_item_pool_no_items", poolType)); + + return; + } + + // Get weapon or gear pool + var pool = poolType == "weapon" ? _weaponModPool : _gearModPool; + foreach (var item in items) { + if (item.Properties is null) + { + _logger.Error(_localisationService.GetText("bot-item_missing_props_property", new { + itemTpl = item.Id, + name = item.Name, + })); + + continue; + } + + // Skip item without slots + if (item.Properties.Slots is null || item.Properties.Slots.Count == 0) + { + continue; + } + + // Add base item (weapon/armor) to pool + if (!pool.ContainsKey(item.Id)) + { + pool[item.Id] = new Dictionary>(); + } + + // iterate over each items mod slots e.g. mod_muzzle + foreach (var slot in item.Properties.Slots) { + // Get mods that fit into the current mod slot + var itemsThatFit = slot.Props.Filters.FirstOrDefault().Filter; + + // Get weapon/armor pool to add mod slots + mod tpls to + + foreach (var itemToAddTpl in itemsThatFit) + { + if (!pool[item.Id].ContainsKey(slot.Name)) + { + // Ensure Mod slot key + blank dict value exist + pool[item.Id][slot.Name] = new(); + } + + if (!pool[item.Id][slot.Name].Contains(itemToAddTpl)) + { + // Add tpl to list keyed by mod slot + pool[item.Id][slot.Name].Add(itemToAddTpl); + } + + var subItemDetails = _itemHelper.GetItem(itemToAddTpl).Value; + var hasSubItemsToAdd = (subItemDetails?.Properties?.Slots?.Count ?? 0) > 0; + if (hasSubItemsToAdd && !pool.ContainsKey(subItemDetails.Id)) + { + // Recursive call + GeneratePool([subItemDetails], poolType); + } + } + } + + //foreach (var slot in item.Properties.Slots) + //{ + // var itemsThatFit = slot.Props.Filters[0].Filter; + // foreach (var itemToAddTpl in itemsThatFit) + // { + // // Ensure key/value exists + // pool.TryAdd(slot.Name, new Dictionary>()); + // pool.TryGetValue(item.Id, out var poolToAddTo); + // poolToAddTo ??= new Dictionary>(); + + // // only add item to pool if it doesn't already exist + // poolToAddTo.TryAdd(slot.Name, []); + // if (!poolToAddTo[slot.Name].Any(x => x == itemToAddTpl)) + // { + // poolToAddTo[slot.Name].Add(itemToAddTpl); + + // // Check item added into array for slots, need to iterate over those + // var subItemDetails = _itemHelper.GetItem(itemToAddTpl).Value; + // var hasSubItemsToAdd = (subItemDetails?.Properties?.Slots?.Count ?? 0) > 0; + // if (hasSubItemsToAdd && !pool.ContainsKey(subItemDetails.Id)) + // { + // // Recursive call + // GeneratePool([subItemDetails], poolType); + // } + // } + // } + //} + } } /** @@ -31,7 +157,13 @@ public class BotEquipmentModPoolService */ public List GetCompatibleModsForWeaponSlot(string itemTpl, string slotName) { - throw new NotImplementedException(); + if (!_weaponPoolGenerated) + { + // Get every weapon in db and generate mod pool + GenerateWeaponPool(); + } + + return _weaponModPool[itemTpl][slotName].ToList(); } /** @@ -70,7 +202,12 @@ public class BotEquipmentModPoolService */ protected void GenerateWeaponPool() { - throw new NotImplementedException(); + var weapons = _databaseService.GetItems().Values.Where( + (item) => item.Type == "Item" && _itemHelper.IsOfBaseclass(item.Id, BaseClasses.WEAPON)); + GeneratePool(weapons, "weapon"); + + // Flag pool as being complete + _weaponPoolGenerated = true; } /** diff --git a/Libraries/Core/Services/ItemFilterService.cs b/Libraries/Core/Services/ItemFilterService.cs index 09506eff..1f300b76 100644 --- a/Libraries/Core/Services/ItemFilterService.cs +++ b/Libraries/Core/Services/ItemFilterService.cs @@ -71,7 +71,7 @@ public class ItemFilterService( */ public List GetBlacklistedItems() { - throw new NotImplementedException(); + return _cloner.Clone(_itemConfig.Blacklist); } /** diff --git a/Libraries/Core/Services/RepairService.cs b/Libraries/Core/Services/RepairService.cs index 152dfc81..b6f8437a 100644 --- a/Libraries/Core/Services/RepairService.cs +++ b/Libraries/Core/Services/RepairService.cs @@ -1,16 +1,29 @@ -using System.Text.Json.Serialization; +using System.Text.Json.Serialization; +using Core.Helpers; using SptCommon.Annotations; using Core.Models.Eft.Common; using Core.Models.Eft.Common.Tables; using Core.Models.Eft.ItemEvent; using Core.Models.Eft.Repair; using Core.Models.Enums; +using Core.Utils; namespace Core.Services; [Injectable(InjectionType.Singleton)] public class RepairService { + private readonly RandomUtil _randomUtil; + private readonly WeightedRandomHelper _weightedRandomHelper; + + public RepairService( + RandomUtil randomUtil, + WeightedRandomHelper weightedRandomHelper) + { + _randomUtil = randomUtil; + _weightedRandomHelper = weightedRandomHelper; + } + /// /// Use trader to repair an items durability ///