diff --git a/Libraries/Core/Generators/BotEquipmentModGenerator.cs b/Libraries/Core/Generators/BotEquipmentModGenerator.cs index ba1bdf71..ca2531ef 100644 --- a/Libraries/Core/Generators/BotEquipmentModGenerator.cs +++ b/Libraries/Core/Generators/BotEquipmentModGenerator.cs @@ -147,7 +147,7 @@ public class BotEquipmentModGenerator( // Choose random mod from pool and check its compatibility string modTpl = null; var found = false; - var exhaustableModPool = CreateExhaustableArray(modPoolToChooseFrom); + var exhaustableModPool = CreateExhaustableArray(modPoolToChooseFrom.ToList()); while (exhaustableModPool.HasValues()) { modTpl = exhaustableModPool.GetRandomValue(); @@ -220,7 +220,7 @@ public class BotEquipmentModGenerator( /// The armor items db template /// Array of plate tpls to choose from public FilterPlateModsForSlotByLevelResult FilterPlateModsForSlotByLevel(GenerateEquipmentProperties settings, string modSlot, - List existingPlateTplPool, TemplateItem armorItem) + HashSet existingPlateTplPool, TemplateItem armorItem) { throw new NotImplementedException(); } @@ -359,8 +359,7 @@ public class BotEquipmentModGenerator( continue; } - if ( - IsModValidForSlot(modToAdd, modsParentSlot, modSlot, request.ParentTemplate, request.BotData.Role) + if (!IsModValidForSlot(modToAdd, modsParentSlot, modSlot, request.ParentTemplate, request.BotData.Role) ) { continue; @@ -837,7 +836,7 @@ public class BotEquipmentModGenerator( ); if (onlyLowProfileGasBlocks.Count() > 0) { - modPool = onlyLowProfileGasBlocks.ToList(); + modPool = onlyLowProfileGasBlocks.ToHashSet(); } } else if (request.WeaponStats.HasRearIronSight ?? false && modPool.Count() > 1) @@ -848,7 +847,7 @@ public class BotEquipmentModGenerator( ); if (onlyHighProfileGasBlocks.Count() > 0) { - modPool = onlyHighProfileGasBlocks.ToList(); + modPool = onlyHighProfileGasBlocks.ToHashSet(); } } } @@ -860,7 +859,7 @@ public class BotEquipmentModGenerator( request.RandomisationSettings.MinimumMagazineSize is not null ) { - modPool = GetFilterdMagazinePoolByCapacity(request, modPool).ToList(); + modPool = GetFilterdMagazinePoolByCapacity(request, modPool).ToHashSet(); } // Pick random mod that's compatible @@ -919,7 +918,7 @@ public class BotEquipmentModGenerator( /// Pool of magazine tpls to filter /// Filtered pool of magazine tpls /// - public IEnumerable GetFilterdMagazinePoolByCapacity(ModToSpawnRequest modSpawnRequest, List modPool) + public IEnumerable GetFilterdMagazinePoolByCapacity(ModToSpawnRequest modSpawnRequest, HashSet modPool) { var weaponTpl = modSpawnRequest.Weapon[0].Template; modSpawnRequest.RandomisationSettings.MinimumMagazineSize.TryGetValue(weaponTpl, out var minMagSizeFromSettings); @@ -950,7 +949,7 @@ public class BotEquipmentModGenerator( /// Array of weapon items chosen item will be added to /// Name of slot picked mod will be placed into /// Chosen weapon details - public ChooseRandomCompatibleModResult GetCompatibleWeaponModTplForSlotFromPool(ModToSpawnRequest request, List modPool, Slot parentSlot, + public ChooseRandomCompatibleModResult GetCompatibleWeaponModTplForSlotFromPool(ModToSpawnRequest request, HashSet modPool, Slot parentSlot, ModSpawn? choiceTypeEnum, List weapon, string modSlotName) { // Filter out incompatible mods from pool @@ -985,7 +984,7 @@ public class BotEquipmentModGenerator( public ChooseRandomCompatibleModResult GetCompatibleModFromPool(List modPool, ModSpawn? modSpawnType, List weapon) { // Create exhaustable pool to pick mod item from - var exhaustableModPool = CreateExhaustableArray(modPool); + var exhaustableModPool = CreateExhaustableArray(modPool.ToList()); // Create default response if no compatible item is found below ChooseRandomCompatibleModResult chosenModResult = new() @@ -1063,9 +1062,9 @@ public class BotEquipmentModGenerator( return chosenModResult; } - public ExhaustableArray CreateExhaustableArray(List itemsToAddToArray) // TODO: this wont likely be needed, reimplement for C# + public ExhaustableArray CreateExhaustableArray(List itemsToAddToArray) { - return new ExhaustableArray(itemsToAddToArray, _randomUtil, _cloner); + return new ExhaustableArray(itemsToAddToArray.ToList(), _randomUtil, _cloner); } /// @@ -1074,7 +1073,7 @@ 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) + public List GetFilteredModPool(HashSet modPool, List tplBlacklist) { return modPool.Where((tpl) => !tplBlacklist.Contains(tpl)).ToList(); } @@ -1088,7 +1087,7 @@ public class BotEquipmentModGenerator( /// /// Mods root parent (weapon/equipment) /// Array of mod tpls - public List GetModPoolForSlot(ModToSpawnRequest request, TemplateItem weaponTemplate) + public HashSet GetModPoolForSlot(ModToSpawnRequest request, TemplateItem weaponTemplate) { // Mod is flagged as being default only, try and find it in globals if (request.ModSpawnResult == ModSpawn.DEFAULT_MOD) @@ -1105,7 +1104,7 @@ public class BotEquipmentModGenerator( return request.ItemModPool[request.ModSlot]; } - public List GetModPoolForDefaultSlot(ModToSpawnRequest request, TemplateItem weaponTemplate) + public HashSet GetModPoolForDefaultSlot(ModToSpawnRequest request, TemplateItem weaponTemplate) { var matchingModFromPreset = GetMatchingModFromPreset(request, weaponTemplate); if (matchingModFromPreset is null) @@ -1166,7 +1165,7 @@ public class BotEquipmentModGenerator( var newListOfModsForSlot = parentSlotCompatibleItems.Where((tpl) => !request.ConflictingItemTpls.Contains(tpl)); if (newListOfModsForSlot.Count() > 0) { - return newListOfModsForSlot.ToList(); + return newListOfModsForSlot.ToHashSet(); } } @@ -1353,7 +1352,7 @@ public class BotEquipmentModGenerator( /// db object for modItem we get compatible mods from /// Pool of mods we are adding to /// A blacklist of items that cannot be picked - public void AddCompatibleModsForProvidedMod(string desiredSlotName, TemplateItem modTemplate, Dictionary>> modPool, + public void AddCompatibleModsForProvidedMod(string desiredSlotName, TemplateItem modTemplate, Dictionary>> modPool, EquipmentFilterDetails botEquipBlacklist) { var desiredSlotObject = modTemplate.Properties.Slots?.FirstOrDefault((slot) => slot.Name.Contains(desiredSlotName)); @@ -1369,7 +1368,7 @@ public class BotEquipmentModGenerator( } // Filter mods - var filteredMods = FilterModsByBlacklist(supportedSubMods, botEquipBlacklist, desiredSlotName); + var filteredMods = FilterModsByBlacklist(supportedSubMods.ToHashSet(), botEquipBlacklist, desiredSlotName); if (!filteredMods.Any()) { _logger.Warning( @@ -1390,7 +1389,7 @@ public class BotEquipmentModGenerator( modPool[modTemplate.Id] = new(); } - modPool[modTemplate.Id][desiredSlotObject.Name] = supportedSubMods; + modPool[modTemplate.Id][desiredSlotObject.Name] = supportedSubMods.ToHashSet(); } /// @@ -1400,7 +1399,7 @@ public class BotEquipmentModGenerator( /// Slot item should fit in /// Equipment that should not be picked /// Array of compatible items for that slot - public List GetDynamicModPool(string parentItemId, string modSlot, EquipmentFilterDetails botEquipBlacklist) + public HashSet GetDynamicModPool(string parentItemId, string modSlot, EquipmentFilterDetails botEquipBlacklist) { var modsFromDynamicPool = _cloner.Clone( _botEquipmentModPoolService.GetCompatibleModsForWeaponSlot(parentItemId, modSlot) @@ -1424,7 +1423,7 @@ public class BotEquipmentModGenerator( /// Equipment blacklist /// Slot mods belong to /// Filtered array of mod tpls - public List FilterModsByBlacklist(List allowedMods, EquipmentFilterDetails? botEquipBlacklist, string modSlot) + public HashSet FilterModsByBlacklist(HashSet allowedMods, EquipmentFilterDetails? botEquipBlacklist, string modSlot) { // No blacklist, nothing to filter out if (botEquipBlacklist is null) @@ -1432,12 +1431,12 @@ public class BotEquipmentModGenerator( return allowedMods; } - var result = new List(); + var result = new HashSet(); // Get item blacklist and mod equipment blacklist as one array botEquipBlacklist.Equipment.TryGetValue(modSlot, out var equipmentBlacklistValues); var blacklist = _itemFilterService.GetBlacklistedItems().Concat(equipmentBlacklistValues ?? []); - result = allowedMods.Where((tpl) => !blacklist.Contains(tpl)).ToList(); + result = allowedMods.Where((tpl) => !blacklist.Contains(tpl)).ToHashSet(); return result; } @@ -1452,7 +1451,7 @@ public class BotEquipmentModGenerator( /// ModPool which should include available cartridges /// The CylinderMagazine's UID /// The CylinderMagazine's template - public void FillCamora(List items, Dictionary>> modPool, string cylinderMagParentId, + public void FillCamora(List items, Dictionary>> modPool, string cylinderMagParentId, TemplateItem cylinderMagTemplate) { var itemModPool = modPool[cylinderMagTemplate.Id]; @@ -1474,7 +1473,7 @@ public class BotEquipmentModGenerator( modPool[cylinderMagTemplate.Id] = new(); foreach (var camora in camoraSlots) { - modPool[cylinderMagTemplate.Id][camora.Name] = camora.Props.Filters?[0].Filter; + modPool[cylinderMagTemplate.Id][camora.Name] = camora.Props.Filters?[0].Filter.ToHashSet(); } itemModPool = modPool[cylinderMagTemplate.Id]; @@ -1485,12 +1484,12 @@ public class BotEquipmentModGenerator( var camoraFirstSlot = "camora_000"; if (itemModPool.TryGetValue(modSlot, out var value)) { - exhaustableModPool = CreateExhaustableArray(value); + exhaustableModPool = CreateExhaustableArray(value.ToList()); } else if (itemModPool.ContainsKey(camoraFirstSlot)) { modSlot = camoraFirstSlot; - exhaustableModPool = CreateExhaustableArray(MergeCamoraPools(itemModPool)); + exhaustableModPool = CreateExhaustableArray(MergeCamoraPools(itemModPool).ToList()); } else { @@ -1531,12 +1530,12 @@ public class BotEquipmentModGenerator( /// /// Dictionary of camoras we want to merge into one array /// String array of shells for multiple camora sources - public List MergeCamoraPools(Dictionary> camorasWithShells) + public HashSet MergeCamoraPools(Dictionary> camorasWithShells) { return camorasWithShells .SelectMany(shellKvP => shellKvP.Value) .Distinct() - .ToList(); + .ToHashSet(); } /// @@ -1548,7 +1547,7 @@ public class BotEquipmentModGenerator( /// Full scope pool /// Whitelist of scope types by weapon base type /// Array of scope tpls that have been filtered to just ones allowed for that weapon type - public List FilterSightsByWeaponType(Item weapon, List scopes, Dictionary> botWeaponSightWhitelist) + public HashSet FilterSightsByWeaponType(Item weapon, HashSet scopes, Dictionary> botWeaponSightWhitelist) { var weaponDetails = _itemHelper.GetItem(weapon.Template); @@ -1562,7 +1561,7 @@ public class BotEquipmentModGenerator( } // Filter items that are not directly scopes OR mounts that do not hold the type of scope we allow for this weapon type - List filteredScopesAndMods = []; + HashSet filteredScopesAndMods = []; foreach (var item in scopes) { // Mods is a scope, check base class is allowed diff --git a/Libraries/Core/Generators/BotInventoryGenerator.cs b/Libraries/Core/Generators/BotInventoryGenerator.cs index 58ac4554..3b2a4914 100644 --- a/Libraries/Core/Generators/BotInventoryGenerator.cs +++ b/Libraries/Core/Generators/BotInventoryGenerator.cs @@ -201,7 +201,7 @@ public class BotInventoryGenerator( Inventory = botInventory, BotEquipmentConfig = botEquipConfig, RandomisationDetails = randomistionDetails, - GeneratingPlayerLevel = pmcProfile.Info.Level, + GeneratingPlayerLevel = pmcProfile.Info.Level } ); } @@ -222,7 +222,7 @@ public class BotInventoryGenerator( Inventory = botInventory, BotEquipmentConfig = botEquipConfig, RandomisationDetails = randomistionDetails, - GenerateModsBlacklist = [ItemTpl.POCKETS_1X4_TUE], + GenerateModsBlacklist = [ItemTpl.POCKETS_1X4_TUE, ItemTpl.POCKETS_LARGE], GeneratingPlayerLevel = pmcProfile.Info.Level, } ); @@ -483,7 +483,7 @@ public class BotInventoryGenerator( // Does item have slots for sub-mods to be inserted into if (pickedItemDb.Properties?.Slots?.Count > 0 && settings?.GenerateModsBlacklist is not null - && settings.GenerateModsBlacklist.Contains(pickedItemDb.Id)) + && !settings.GenerateModsBlacklist.Contains(pickedItemDb.Id)) { var childItemsToAdd = _botEquipmentModGenerator.GenerateModsForEquipment( [item], @@ -512,17 +512,20 @@ public class BotInventoryGenerator( /// Item mod pool is being retrieved and filtered /// Blacklist to filter mod pool with /// Filtered pool of mods - public Dictionary> GetFilteredDynamicModsForItem(string itemTpl, Dictionary> equipmentBlacklist) + public Dictionary> GetFilteredDynamicModsForItem(string itemTpl, Dictionary> equipmentBlacklist) { var modPool = _botEquipmentModPoolService.GetModsForGearSlot(itemTpl); - foreach (var modSlot in modPool.Keys ?? Enumerable.Empty()) + foreach (var modSlot in modPool) { - var blacklistedMods = equipmentBlacklist[modSlot] ?? []; - var filteredMods = modPool[modSlot].Where((slotName) => !blacklistedMods.Contains(slotName)); + // Get blacklist + equipmentBlacklist.TryGetValue(modSlot.Key, out var blacklistedMods); + + // Get mods not on blacklist + var filteredMods = modPool[modSlot.Key].Where((slotName) => !(blacklistedMods ?? []).Contains(slotName)); if (filteredMods.Any()) { - modPool[modSlot] = filteredMods.ToList(); + modPool[modSlot.Key] = filteredMods.ToHashSet(); } } diff --git a/Libraries/Core/Generators/BotLootGenerator.cs b/Libraries/Core/Generators/BotLootGenerator.cs index 56c3cb8b..0c6f37ef 100644 --- a/Libraries/Core/Generators/BotLootGenerator.cs +++ b/Libraries/Core/Generators/BotLootGenerator.cs @@ -446,7 +446,7 @@ public class BotLootGenerator( return; } - var weightedItemTpl = _weightedRandomHelper.GetWeightedValue(pool); + var weightedItemTpl = _weightedRandomHelper.GetWeightedValue(pool); var (key, itemToAddTemplate) = _itemHelper.GetItem(weightedItemTpl); if (!key) @@ -456,16 +456,13 @@ public class BotLootGenerator( continue; } - if (itemSpawnLimits is not null) + if (itemSpawnLimits is not null && ItemHasReachedSpawnLimit(itemToAddTemplate, botRole, itemSpawnLimits)) { - if (ItemHasReachedSpawnLimit(itemToAddTemplate, botRole, itemSpawnLimits)) - { - // Remove item from pool to prevent it being picked again - pool.Remove(weightedItemTpl); + // Remove item from pool to prevent it being picked again + pool.Remove(weightedItemTpl); - i--; - continue; - } + i--; + continue; } var newRootItemId = _hashUtil.Generate(); @@ -541,9 +538,9 @@ public class BotLootGenerator( if (fitItemIntoContainerAttempts >= 4) { _logger.Debug( - $"Failed placing item: {i} of: {totalItemCount} items into: {botRole} " + + $"Failed placing item: {itemToAddTemplate.Name}: {i} of: {totalItemCount} items into: {botRole} " + $"containers: {string.Join(",", equipmentSlots)}. Tried: {fitItemIntoContainerAttempts} " + - $"times, reason: {itemAddedResult.ToString()}, skipping" + $"times, reason: {itemAddedResult}, skipping" ); break; diff --git a/Libraries/Core/Generators/BotWeaponGenerator.cs b/Libraries/Core/Generators/BotWeaponGenerator.cs index 98b14008..6799090c 100644 --- a/Libraries/Core/Generators/BotWeaponGenerator.cs +++ b/Libraries/Core/Generators/BotWeaponGenerator.cs @@ -348,7 +348,7 @@ public class BotWeaponGenerator( } // Iterate over required slots in db item, check mod exists for that slot - foreach (var modSlotTemplate in modTemplate.Properties.Slots.Where((slot) => slot.Required ?? false)) + foreach (var modSlotTemplate in modTemplate.Properties.Slots?.Where((slot) => slot.Required.GetValueOrDefault(false)) ?? []) { var slotName = modSlotTemplate.Name; var hasWeaponSlotItem = weaponItemList.Any( @@ -553,9 +553,7 @@ public class BotWeaponGenerator( protected string GetWeightedCompatibleAmmo(Dictionary> cartridgePool, TemplateItem weaponTemplate) { var desiredCaliber = GetWeaponCaliber(weaponTemplate); - - var cartridgePoolForWeapon = cartridgePool[desiredCaliber]; - if (cartridgePoolForWeapon is null || cartridgePoolForWeapon?.Count == 0) + if (!cartridgePool.TryGetValue(desiredCaliber, out var cartridgePoolForWeapon) || cartridgePoolForWeapon?.Keys.Count == 0) { _logger.Debug( _localisationService.GetText( diff --git a/Libraries/Core/Generators/WeaponGen/Implementations/ExternalInventoryMagGen.cs b/Libraries/Core/Generators/WeaponGen/Implementations/ExternalInventoryMagGen.cs index 409fe945..d5fcf315 100644 --- a/Libraries/Core/Generators/WeaponGen/Implementations/ExternalInventoryMagGen.cs +++ b/Libraries/Core/Generators/WeaponGen/Implementations/ExternalInventoryMagGen.cs @@ -1,4 +1,4 @@ -using SptCommon.Annotations; +using SptCommon.Annotations; using Core.Helpers; using Core.Models.Eft.Common.Tables; using Core.Models.Enums; @@ -31,7 +31,7 @@ public class ExternalInventoryMagGen( public void Process(InventoryMagGen inventoryMagGen) { - // Cout of attempts to fit a magazine into bot inventory + // Count of attempts to fit a magazine into bot inventory var fitAttempts = 0; // Magazine Db template @@ -41,6 +41,7 @@ public class ExternalInventoryMagGen( List attemptedMagBlacklist = []; var defaultMagazineTpl = _botWeaponGeneratorHelper.GetWeaponsDefaultMagazineTpl(weapon); var randomizedMagazineCount = _botWeaponGeneratorHelper.GetRandomizedMagazineCount(inventoryMagGen.GetMagCount()); + var isShotgun = _itemHelper.IsOfBaseclass(weapon.Id, BaseClasses.SHOTGUN); for (var i = 0; i < randomizedMagazineCount; i++) { var magazineWithAmmo = _botWeaponGeneratorHelper.CreateMagazineWithAmmo( @@ -99,16 +100,23 @@ public class ExternalInventoryMagGen( break; } - // Edge case - some weapons (SKS) have an internal magazine as default, choose random non-internal magazine to add to bot instead + // Edge case - some weapons (SKS + shotguns) have an internal magazine as default, choose random non-internal magazine to add to bot instead if (magTemplate.Properties.ReloadMagType == "InternalMagazine") { var result = GetRandomExternalMagazineForInternalMagazineGun( inventoryMagGen.GetWeaponTemplate().Id, attemptedMagBlacklist ); + if (result?.Id is null) { - _logger.Debug($"Unable to add additional magazine into bot inventory for weapon: {weapon.Name}, attempted: {fitAttempts} times"); + // Highly likely shotgun has no external mags + if (isShotgun) + { + break; + } + + _logger.Debug($"Unable to add additional magazine into bot inventory: vest/pockets for weapon: {weapon.Name}, attempted: {fitAttempts} times. Reason: {fitsIntoInventory}"); break; } diff --git a/Libraries/Core/Helpers/BotGeneratorHelper.cs b/Libraries/Core/Helpers/BotGeneratorHelper.cs index 6b6c6550..4631c44b 100644 --- a/Libraries/Core/Helpers/BotGeneratorHelper.cs +++ b/Libraries/Core/Helpers/BotGeneratorHelper.cs @@ -582,7 +582,7 @@ public class BotGeneratorHelper( { parentItem.ParentId = container.Id; parentItem.SlotId = slotGrid.Name; - parentItem.Location = new ItemLocation() + parentItem.Location = new ItemLocation { X = findSlotResult.X, Y = findSlotResult.Y, diff --git a/Libraries/Core/Helpers/ContainerHelper.cs b/Libraries/Core/Helpers/ContainerHelper.cs index af2c50b0..03c46552 100644 --- a/Libraries/Core/Helpers/ContainerHelper.cs +++ b/Libraries/Core/Helpers/ContainerHelper.cs @@ -28,39 +28,38 @@ public class ContainerHelper return new FindSlotResult(false); } - // Down + // Down = y for (var y = 0; y < limitY; y++) { - // Across + if (container2D[y].All((x) => x == 1)) { // Every item in row is full, skip row continue; } + // Try each slot on the row (across = x) for (var x = 0; x < limitX; x++) { var foundSlot = LocateSlot(container2D, containerX, containerY, x, y, itemWidth, itemHeight); + if (foundSlot) + { + return new FindSlotResult(true, x, y, rotation); + } // Failed to find slot, rotate item and try again - if (!foundSlot && itemWidth * itemHeight > 1) + if (!foundSlot && ItemBiggerThan1X1(itemWidth, itemHeight)) { - // Bigger than 1x1 + // Bigger than 1x1, try rotating foundSlot = LocateSlot(container2D, containerX, containerY, x, y, itemHeight, itemWidth); // Height/Width swapped if (foundSlot) { // Found a slot for it when rotated rotation = true; + + return new FindSlotResult(true, x, y, rotation); } } - - if (!foundSlot) - { - // Didn't fit this hole, try again - continue; - } - - return new FindSlotResult(true, x, y, rotation); } } @@ -68,6 +67,11 @@ public class ContainerHelper return new FindSlotResult(false); } + private bool ItemBiggerThan1X1(int itemWidth, int itemHeight) + { + return itemWidth * itemHeight > 1; + } + /// /// Find a slot inside a container an item can be placed in /// diff --git a/Libraries/Core/Helpers/DurabilityLimitsHelper.cs b/Libraries/Core/Helpers/DurabilityLimitsHelper.cs index 2871d8ad..74205031 100644 --- a/Libraries/Core/Helpers/DurabilityLimitsHelper.cs +++ b/Libraries/Core/Helpers/DurabilityLimitsHelper.cs @@ -163,6 +163,11 @@ public class DurabilityLimitsHelper( return _botConfig.Durability.Default.Weapon.LowestMax; } + if (botRole == "pmc") + { + return _botConfig.Durability.Pmc.Weapon.LowestMax; + } + _botConfig.Durability.BotDurabilities.TryGetValue(botRole, out var durability); return durability.Weapon.LowestMax; } @@ -174,6 +179,11 @@ public class DurabilityLimitsHelper( return _botConfig.Durability.Default.Weapon.HighestMax; } + if (botRole == "pmc") + { + return _botConfig.Durability.Pmc.Weapon.HighestMax; + } + _botConfig.Durability.BotDurabilities.TryGetValue(botRole, out var durability); return durability.Weapon.HighestMax; } @@ -211,6 +221,11 @@ public class DurabilityLimitsHelper( return _botConfig.Durability.Default.Weapon.MinDelta; } + if (botRole == "pmc") + { + return _botConfig.Durability.Pmc.Weapon.MinDelta; + } + _botConfig.Durability.BotDurabilities.TryGetValue(botRole, out var value); return value.Weapon.MinDelta; @@ -223,6 +238,11 @@ public class DurabilityLimitsHelper( return _botConfig.Durability.Default.Weapon.HighestMax; } + if (botRole == "pmc") + { + return _botConfig.Durability.Pmc.Weapon.HighestMax; + } + _botConfig.Durability.BotDurabilities.TryGetValue(botRole, out var value); return value.Weapon.HighestMax; @@ -235,6 +255,11 @@ public class DurabilityLimitsHelper( return _botConfig.Durability.Default.Armor.MinDelta; } + if (botRole == "pmc") + { + return _botConfig.Durability.Pmc.Armor.MinDelta; + } + _botConfig.Durability.BotDurabilities.TryGetValue(botRole, out var value); return value.Armor.MinDelta; @@ -247,6 +272,11 @@ public class DurabilityLimitsHelper( return _botConfig.Durability.Default.Armor.MaxDelta; } + if (botRole == "pmc") + { + return _botConfig.Durability.Pmc.Armor.MaxDelta; + } + _botConfig.Durability.BotDurabilities.TryGetValue(botRole, out var value); return value.Armor.MaxDelta; @@ -259,6 +289,11 @@ public class DurabilityLimitsHelper( return _botConfig.Durability.Default.Armor.MinLimitPercent; } + if (botRole == "pmc") + { + return _botConfig.Durability.Pmc.Armor.MinLimitPercent; + } + _botConfig.Durability.BotDurabilities.TryGetValue(botRole, out var value); return value.Armor.MinLimitPercent; @@ -271,6 +306,11 @@ public class DurabilityLimitsHelper( return _botConfig.Durability.Default.Weapon.MinLimitPercent; } + if (botRole == "pmc") + { + return _botConfig.Durability.Pmc.Weapon.MinLimitPercent; + } + _botConfig.Durability.BotDurabilities.TryGetValue(botRole, out var value); return value.Weapon.MinLimitPercent; diff --git a/Libraries/Core/Helpers/InventoryHelper.cs b/Libraries/Core/Helpers/InventoryHelper.cs index dc90af7d..34f258ee 100644 --- a/Libraries/Core/Helpers/InventoryHelper.cs +++ b/Libraries/Core/Helpers/InventoryHelper.cs @@ -336,7 +336,17 @@ public class InventoryHelper( /// Two-dimensional representation of container protected int[][] GetBlankContainerMap(int containerH, int containerY) { - return Enumerable.Repeat(Enumerable.Repeat(0, containerH).ToArray(), containerY).ToArray(); + //var x = new int[containerY][]; + //for (int i = 0; i < containerY; i++) + //{ + // x[i] = new int[containerH]; + //} + + //return x; + + return Enumerable.Range(0, containerY) + .Select(i => new int[containerH]) + .ToArray(); } /// @@ -395,12 +405,12 @@ public class InventoryHelper( } // Fill the corresponding cells in the container map to show the slot is taken - Array.Fill(containerRow, 1, itemLocation.X.Value, fillTo.Value); + Array.Fill(containerRow, 1, itemLocation.X.Value, fW); } catch (Exception ex) { _logger.Error( _localisationService.GetText("inventory-unable_to_fill_container", new { id = item.Id, - error = ex.Message + error = $"{ex.Message} {ex.StackTrace}" }) ); } diff --git a/Libraries/Core/Helpers/ItemHelper.cs b/Libraries/Core/Helpers/ItemHelper.cs index 8f51b412..f43b16df 100644 --- a/Libraries/Core/Helpers/ItemHelper.cs +++ b/Libraries/Core/Helpers/ItemHelper.cs @@ -1459,9 +1459,10 @@ public class ItemHelper( } // Get max number of cartridges in magazine, choose random value between min/max + var magProps = magTemplate.Properties; var magazineCartridgeMaxCount = IsOfBaseclass(magTemplate.Id, BaseClasses.SPRING_DRIVEN_CYLINDER) - ? magTemplate.Properties?.Slots?.Count() // Edge case for rotating grenade launcher magazine - : magTemplate.Properties?.Cartridges[0]?.MaxCount; + ? magProps?.Slots?.Count // Edge case for rotating grenade launcher magazine + : magProps?.Cartridges.FirstOrDefault()?.MaxCount; if (magazineCartridgeMaxCount is null) { diff --git a/Libraries/Core/Models/Eft/Common/Tables/GlobalTablesUsings.cs b/Libraries/Core/Models/Eft/Common/Tables/GlobalTablesUsings.cs index 6c39a9bf..aa4ce584 100644 --- a/Libraries/Core/Models/Eft/Common/Tables/GlobalTablesUsings.cs +++ b/Libraries/Core/Models/Eft/Common/Tables/GlobalTablesUsings.cs @@ -1,2 +1,2 @@ -global using GlobalAmmo = System.Collections.Generic.Dictionary>; -global using GlobalMods = System.Collections.Generic.Dictionary>>; +global using GlobalAmmo = System.Collections.Generic.Dictionary>; +global using GlobalMods = System.Collections.Generic.Dictionary>>; diff --git a/Libraries/Core/Models/Spt/Bots/FilterPlateModsForSlotByLevelResult.cs b/Libraries/Core/Models/Spt/Bots/FilterPlateModsForSlotByLevelResult.cs index 7f8cebd4..899f6872 100644 --- a/Libraries/Core/Models/Spt/Bots/FilterPlateModsForSlotByLevelResult.cs +++ b/Libraries/Core/Models/Spt/Bots/FilterPlateModsForSlotByLevelResult.cs @@ -8,7 +8,7 @@ public record FilterPlateModsForSlotByLevelResult public Result? Result { get; set; } [JsonPropertyName("plateModTpls")] - public List? PlateModTemplates { get; set; } + public HashSet? PlateModTemplates { get; set; } } public enum Result diff --git a/Libraries/Core/Models/Spt/Bots/ModToSpawnRequest.cs b/Libraries/Core/Models/Spt/Bots/ModToSpawnRequest.cs index 4169edaf..a5a8edad 100644 --- a/Libraries/Core/Models/Spt/Bots/ModToSpawnRequest.cs +++ b/Libraries/Core/Models/Spt/Bots/ModToSpawnRequest.cs @@ -38,7 +38,7 @@ public record ModToSpawnRequest /// Pool of items to pick from /// [JsonPropertyName("itemModPool")] - public Dictionary>? ItemModPool { get; set; } + public Dictionary>? ItemModPool { get; set; } /// /// List with only weapon tpl in it, ready for mods to be added diff --git a/Libraries/Core/Services/BotEquipmentModPoolService.cs b/Libraries/Core/Services/BotEquipmentModPoolService.cs index 3f9c958a..f2ba4968 100644 --- a/Libraries/Core/Services/BotEquipmentModPoolService.cs +++ b/Libraries/Core/Services/BotEquipmentModPoolService.cs @@ -5,7 +5,6 @@ using Core.Helpers; using Core.Servers; using Core.Models.Enums; using Core.Models.Spt.Config; -using System.Collections.Generic; namespace Core.Services; @@ -18,7 +17,8 @@ public class BotEquipmentModPoolService protected LocalisationService _localisationService; protected ConfigServer _configServer; - protected bool _weaponPoolGenerated = false; + protected bool _weaponPoolGenerated; + protected bool _armorPoolGenerated; protected Dictionary>> _weaponModPool; protected Dictionary>> _gearModPool; protected BotConfig _botConfig; @@ -110,34 +110,6 @@ public class BotEquipmentModPoolService } } } - - //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); - // } - // } - // } - //} } } @@ -150,12 +122,12 @@ public class BotEquipmentModPoolService } /** - * Get array of compatible mods for an items mod slot (generate pool if it doesnt exist already) + * Get array of compatible mods for an items mod slot (generate pool if it doesn't exist already) * @param itemTpl item to look up * @param slotName slot to get compatible mods for * @returns tpls that fit the slot */ - public List GetCompatibleModsForWeaponSlot(string itemTpl, string slotName) + public HashSet GetCompatibleModsForWeaponSlot(string itemTpl, string slotName) { if (!_weaponPoolGenerated) { @@ -163,18 +135,7 @@ public class BotEquipmentModPoolService GenerateWeaponPool(); } - return _weaponModPool[itemTpl][slotName].ToList(); - } - - /** - * Get array of compatible mods for an items mod slot (generate pool if it doesnt exist already) - * @param itemTpl item to look up - * @param slotName slot to get compatible mods for - * @returns tpls that fit the slot - */ - public List GetCompatibleModsForGearSlot(string itemTpl, string slotName) - { - throw new NotImplementedException(); + return _weaponModPool[itemTpl][slotName]; } /** @@ -182,9 +143,16 @@ public class BotEquipmentModPoolService * @param itemTpl items tpl to look up mods for * @returns Dictionary of mods (keys are mod slot names) with array of compatible mod tpls as value */ - public Dictionary> GetModsForGearSlot(string itemTpl) + public Dictionary>? GetModsForGearSlot(string itemTpl) { - throw new NotImplementedException(); + if (!_armorPoolGenerated) + { + GenerateGearPool(); + } + + return _gearModPool.TryGetValue(itemTpl, out var value) + ? value + : []; } /** @@ -192,9 +160,14 @@ public class BotEquipmentModPoolService * @param itemTpl Weapons tpl to look up mods for * @returns Dictionary of mods (keys are mod slot names) with array of compatible mod tpls as value */ - public Dictionary> GetModsForWeaponSlot(string itemTpl) + public Dictionary> GetModsForWeaponSlot(string itemTpl) { - throw new NotImplementedException(); + if (!_weaponPoolGenerated) + { + GenerateWeaponPool(); + } + + return _weaponModPool[itemTpl]; } /** @@ -215,6 +188,17 @@ public class BotEquipmentModPoolService */ protected void GenerateGearPool() { - throw new NotImplementedException(); + var gear = _databaseService.GetItems().Values.Where( + (item) => item.Type == "Item" + && _itemHelper.IsOfBaseclasses(item.Id, [ + BaseClasses.ARMORED_EQUIPMENT, + BaseClasses.VEST, + BaseClasses.ARMOR, + BaseClasses.HEADWEAR, + ])); + GeneratePool(gear, "gear"); + + // Flag pool as being complete + _armorPoolGenerated = true; } } diff --git a/Libraries/Core/Services/RepairService.cs b/Libraries/Core/Services/RepairService.cs index b6f8437a..d8b75228 100644 --- a/Libraries/Core/Services/RepairService.cs +++ b/Libraries/Core/Services/RepairService.cs @@ -6,6 +6,7 @@ using Core.Models.Eft.Common.Tables; using Core.Models.Eft.ItemEvent; using Core.Models.Eft.Repair; using Core.Models.Enums; +using Core.Models.Utils; using Core.Utils; namespace Core.Services; @@ -13,13 +14,16 @@ namespace Core.Services; [Injectable(InjectionType.Singleton)] public class RepairService { + private readonly ISptLogger _logger; private readonly RandomUtil _randomUtil; private readonly WeightedRandomHelper _weightedRandomHelper; public RepairService( + ISptLogger _logger, RandomUtil randomUtil, WeightedRandomHelper weightedRandomHelper) { + this._logger = _logger; _randomUtil = randomUtil; _weightedRandomHelper = weightedRandomHelper; } @@ -165,10 +169,26 @@ public class RepairService /// Add random buff to item /// /// weapon/armor config - /// Details for item to repair - public void AddBuff(Core.Models.Spt.Config.BonusSettings itemConfig, Item item) + /// Item to repair + public void AddBuff(Models.Spt.Config.BonusSettings itemConfig, Item item) { - throw new NotImplementedException(); + _logger.Error("NOT IMPLEMENTED - AddBuff"); + //var bonusRarity = _weightedRandomHelper.GetWeightedValue(itemConfig.RarityWeight); + //var bonusType = _weightedRandomHelper.GetWeightedValue(itemConfig.BonusTypeWeight); + + //var bonusValues = itemConfig[bonusRarity][bonusType].valuesMinMax; + //var bonusValue = _randomUtil.GetFloat(bonusValues.min, bonusValues.max); + + //var bonusThresholdPercents = itemConfig[bonusRarity][bonusType].activeDurabilityPercentMinMax; + //var bonusThresholdPercent = _randomUtil.GetInt(bonusThresholdPercents.min, bonusThresholdPercents.max); + + //item.Upd.Buff = new UpdBuff { + // Rarity = bonusRarity, + // BuffType = bonusType, + // Value = bonusValue, + // ThresholdDurability = _randomUtil.GetPercentOfValue(bonusThresholdPercent, item.Upd.Repairable.Durability, 2).toFixed(2), + // ) + //}; } /// diff --git a/Server/Assets/configs/bot.json b/Server/Assets/configs/bot.json index d8203cd6..671ec148 100644 --- a/Server/Assets/configs/bot.json +++ b/Server/Assets/configs/bot.json @@ -88,7 +88,6 @@ "minLimitPercent": 15 } }, - "botDurabilities": { "pmc": { "armor": { "lowestMaxPercent": 90, @@ -105,6 +104,7 @@ "minLimitPercent": 15 } }, + "botDurabilities": { "boss": { "armor": { "lowestMaxPercent": 90, diff --git a/Server/Assets/database/bots/types/bear.json b/Server/Assets/database/bots/types/bear.json index 29d1f29d..4e00c3af 100644 --- a/Server/Assets/database/bots/types/bear.json +++ b/Server/Assets/database/bots/types/bear.json @@ -2826,6 +2826,10 @@ "5efb0d4f4bc50b58e81710f3": 9, "5efb0fc6aeb21837e749c801": 9 }, + "Caliber127x33": { + "668fe62ac62660a5d8071446": 2, + "66a0d1e0ed648d72fe064d06": 5 + }, "Caliber127x55": { "5cadf6ddae9215051e1c23b2": 8, "5cadf6e5ae921500113bb973": 4, diff --git a/Server/Assets/database/bots/types/usec.json b/Server/Assets/database/bots/types/usec.json index 5d2d531b..a0802984 100644 --- a/Server/Assets/database/bots/types/usec.json +++ b/Server/Assets/database/bots/types/usec.json @@ -2818,6 +2818,10 @@ "5efb0d4f4bc50b58e81710f3": 9, "5efb0fc6aeb21837e749c801": 9 }, + "Caliber127x33": { + "668fe62ac62660a5d8071446": 2, + "66a0d1e0ed648d72fe064d06": 5 + }, "Caliber127x55": { "5cadf6ddae9215051e1c23b2": 8, "5cadf6e5ae921500113bb973": 4,