From 027ebb176751d915886a2ada803a06ecfc927f68 Mon Sep 17 00:00:00 2001 From: acidphantasm Date: Sat, 25 Apr 2026 23:42:27 -0500 Subject: [PATCH 1/4] Logic to skip back plates if the front plate doesn't exist Logic to limit plate classes to the front plate class --- .../Generators/BotEquipmentModGenerator.cs | 97 ++++++++++++++++++- 1 file changed, 93 insertions(+), 4 deletions(-) diff --git a/Libraries/SPTarkov.Server.Core/Generators/BotEquipmentModGenerator.cs b/Libraries/SPTarkov.Server.Core/Generators/BotEquipmentModGenerator.cs index fc7693964..c5e2c6e03 100644 --- a/Libraries/SPTarkov.Server.Core/Generators/BotEquipmentModGenerator.cs +++ b/Libraries/SPTarkov.Server.Core/Generators/BotEquipmentModGenerator.cs @@ -107,9 +107,33 @@ public List GenerateModsForEquipment( logger.Warning($"bot: {settings.BotData.Role} lacks a mod slot pool for item: {parentTemplate.Id} {parentTemplate.Name}"); } + // Order the modpool by front plates, then backplates, then everything else + var orderedCompatibleModsPool = (compatibleModsPool ?? []).OrderBy(pair => + { + if (pair.Key.Equals("front_plate", StringComparison.OrdinalIgnoreCase)) + { + return 0; + } + + if (pair.Key.Equals("back_plate", StringComparison.OrdinalIgnoreCase)) + { + return 1; + } + + return 2; + }) + .ToList(); + + var frontPlateSpawned = false; // Iterate over mod pool and choose mods to add to item - foreach (var (modSlotName, modPool) in compatibleModsPool ?? []) + foreach (var (modSlotName, modPool) in orderedCompatibleModsPool) { + // Skip backplate slot if there's no front plate and bot should skip it via config + if (modSlotName.Equals("back_plate", StringComparison.OrdinalIgnoreCase) && settings.BotEquipmentConfig.SkipBackPlateIfFrontPlateMissing.GetValueOrDefault(false) && !frontPlateSpawned) + { + continue; + } + // Get the templates slot object from db var itemSlotTemplate = GetModItemSlotFromDbTemplate(modSlotName, parentTemplate); if (itemSlotTemplate is null) @@ -166,11 +190,23 @@ public List GenerateModsForEquipment( && itemHelper.IsRemovablePlateSlot(modSlotName.ToLowerInvariant()) ) { + int? frontPlateArmorClass = null; + if (modSlotName.Equals("back_plate", StringComparison.OrdinalIgnoreCase) && settings.BotEquipmentConfig.LimitPlateClassToFrontPlateClass.GetValueOrDefault(false)) + { + var frontPlate = equipment.FirstOrDefault(item => item.SlotId.Equals("front_plate", StringComparison.OrdinalIgnoreCase)); + + if (frontPlate != null) + { + frontPlateArmorClass = itemHelper.GetItem(frontPlate.Template).Value?.Properties?.ArmorClass; + } + } + var plateSlotFilteringOutcome = FilterPlateModsForSlotByLevel( settings, modSlotName.ToLowerInvariant(), compatibleModsPool.GetValueOrDefault(modSlotName), - parentTemplate + parentTemplate, + frontPlateArmorClass ); switch (plateSlotFilteringOutcome.Result) { @@ -238,6 +274,11 @@ public List GenerateModsForEquipment( var modId = new MongoId(); equipment.Add(CreateModItem(modId, modTpl.Value, parentId, modSlotName, modTemplate.Value, settings.BotData.Role)); + if (modSlotName.Equals("front_plate", StringComparison.OrdinalIgnoreCase)) + { + frontPlateSpawned = true; + } + // Does item being added exist in mod pool - has its own mod pool if (settings.ModPool.ContainsKey(modTpl.Value)) // Call self again with mod being added as item to add child mods to @@ -249,6 +290,24 @@ public List GenerateModsForEquipment( return equipment; } + /// + /// Checks to see if the bot should be skipping the back plate or not based on the presence of the front plate + /// + /// Bot equipment role + /// Whether the bot has a front plate or not + /// Whether the bot should skip the back plate or not + private bool ShouldSkipBackPlate( + string role, + bool frontPlateSpawned) + { + if (!botConfig.Equipment.TryGetValue(role, out var config)) + { + return false; + } + + return config.SkipBackPlateIfFrontPlateMissing.GetValueOrDefault(false) && !frontPlateSpawned; + } + /// /// Filter a bots plate pool based on its current level /// @@ -261,7 +320,8 @@ public FilterPlateModsForSlotByLevelResult FilterPlateModsForSlotByLevel( GenerateEquipmentProperties settings, string modSlot, HashSet existingPlateTplPool, - TemplateItem armorItem + TemplateItem armorItem, + int? maxArmorLevel = null ) { var result = new FilterPlateModsForSlotByLevelResult { Result = Result.UNKNOWN_FAILURE, PlateModTemplates = null }; @@ -293,12 +353,22 @@ TemplateItem armorItem // Choose a plate level based on weighting var chosenArmorPlateLevelString = weightedRandomHelper.GetWeightedValue(plateWeights); + // Check if the max plate value was sent over, if it's null then it shouldn't be trying to limit classes + if (maxArmorLevel != null) + { + var chosenLevel = int.Parse(chosenArmorPlateLevelString); + if (chosenLevel > maxArmorLevel.Value) + { + chosenArmorPlateLevelString = maxArmorLevel.Value.ToString(); + } + } + // Convert the array of ids into database items var platesFromDb = existingPlateTplPool.Select(plateTpl => itemHelper.GetItem(plateTpl).Value); // Filter plates to the chosen level based on its armorClass property var platesOfDesiredLevel = platesFromDb.Where(item => - item.Properties.ArmorClass.Value == double.Parse(chosenArmorPlateLevelString, CultureInfo.InvariantCulture) + item.Properties.ArmorClass.Value == int.Parse(chosenArmorPlateLevelString, CultureInfo.InvariantCulture) ); if (platesOfDesiredLevel.Any()) { @@ -384,6 +454,25 @@ TemplateItem armorItem return result; } + /// + /// Checks to see if the bot needs to try to limit the available plate classes to the front plate class + /// + /// Bot equipment role + /// Whether the bot should limit plate classes + private bool ShouldAttemptToLimitPlateClasses(string equipmentRole, int? maxArmorLevel = null) + { + if (maxArmorLevel == null) + { + return false; + } + if (!botConfig.Equipment.TryGetValue(equipmentRole, out var config)) + { + return false; + } + + return config.LimitPlateClassToFrontPlateClass.GetValueOrDefault(false); + } + /// /// Gets the minimum and maximum plate class levels from an array of plates /// From 4eddcba797d3bf61c24d296410cb2773e6d8dd6e Mon Sep 17 00:00:00 2001 From: acidphantasm Date: Sat, 25 Apr 2026 23:42:55 -0500 Subject: [PATCH 2/4] Models for plate class logic, nullable like the rest --- .../Models/Spt/Config/BotConfig.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Libraries/SPTarkov.Server.Core/Models/Spt/Config/BotConfig.cs b/Libraries/SPTarkov.Server.Core/Models/Spt/Config/BotConfig.cs index 498b44432..ee25a0f2e 100644 --- a/Libraries/SPTarkov.Server.Core/Models/Spt/Config/BotConfig.cs +++ b/Libraries/SPTarkov.Server.Core/Models/Spt/Config/BotConfig.cs @@ -217,6 +217,19 @@ public record EquipmentFilters [JsonPropertyName("forceOnlyArmoredRigWhenNoArmor")] public bool? ForceOnlyArmoredRigWhenNoArmor { get; set; } + /// + /// Whether the bot should skip chances to have a back plate if the front plate is missing + /// + [JsonPropertyName("skipBackPlateIfFrontPlateMissing")] + public bool? SkipBackPlateIfFrontPlateMissing { get; set; } + + /// + /// Try to match the bot's back plate level to the front plate's level instead of being a better plate + /// Only works if filtering plates by level + /// + [JsonPropertyName("limitPlateClassToFrontPlateClass")] + public bool? LimitPlateClassToFrontPlateClass { get; set; } + /// /// Should plates be filtered by level /// From e6c3923acf1a01ae54659dc31a4de3ba1d158e78 Mon Sep 17 00:00:00 2001 From: acidphantasm Date: Sat, 25 Apr 2026 23:43:08 -0500 Subject: [PATCH 3/4] Add new configs default true on pmc only --- Libraries/SPTarkov.Server.Assets/SPT_Data/configs/bot.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Libraries/SPTarkov.Server.Assets/SPT_Data/configs/bot.json b/Libraries/SPTarkov.Server.Assets/SPT_Data/configs/bot.json index 909db44f6..d0be49685 100644 --- a/Libraries/SPTarkov.Server.Assets/SPT_Data/configs/bot.json +++ b/Libraries/SPTarkov.Server.Assets/SPT_Data/configs/bot.json @@ -1203,6 +1203,8 @@ ], "faceShieldIsActiveChancePercent": 85, "filterPlatesByLevel": true, + "skipBackPlateIfFrontPlateMissing": true, + "limitPlateClassToFrontPlateClass": true, "forceOnlyArmoredRigWhenNoArmor": true, "laserIsActiveChancePercent": 85, "lightIsActiveDayChancePercent": 25, From 8309c51f3acd9720d51bcc99fc7c01d04dd1f34f Mon Sep 17 00:00:00 2001 From: acidphantasm Date: Sat, 25 Apr 2026 23:46:01 -0500 Subject: [PATCH 4/4] Remove helpers as don't need them anymore (probably?) --- .../Generators/BotEquipmentModGenerator.cs | 37 ------------------- 1 file changed, 37 deletions(-) diff --git a/Libraries/SPTarkov.Server.Core/Generators/BotEquipmentModGenerator.cs b/Libraries/SPTarkov.Server.Core/Generators/BotEquipmentModGenerator.cs index c5e2c6e03..fdfa2cc98 100644 --- a/Libraries/SPTarkov.Server.Core/Generators/BotEquipmentModGenerator.cs +++ b/Libraries/SPTarkov.Server.Core/Generators/BotEquipmentModGenerator.cs @@ -290,24 +290,6 @@ public List GenerateModsForEquipment( return equipment; } - /// - /// Checks to see if the bot should be skipping the back plate or not based on the presence of the front plate - /// - /// Bot equipment role - /// Whether the bot has a front plate or not - /// Whether the bot should skip the back plate or not - private bool ShouldSkipBackPlate( - string role, - bool frontPlateSpawned) - { - if (!botConfig.Equipment.TryGetValue(role, out var config)) - { - return false; - } - - return config.SkipBackPlateIfFrontPlateMissing.GetValueOrDefault(false) && !frontPlateSpawned; - } - /// /// Filter a bots plate pool based on its current level /// @@ -454,25 +436,6 @@ public FilterPlateModsForSlotByLevelResult FilterPlateModsForSlotByLevel( return result; } - /// - /// Checks to see if the bot needs to try to limit the available plate classes to the front plate class - /// - /// Bot equipment role - /// Whether the bot should limit plate classes - private bool ShouldAttemptToLimitPlateClasses(string equipmentRole, int? maxArmorLevel = null) - { - if (maxArmorLevel == null) - { - return false; - } - if (!botConfig.Equipment.TryGetValue(equipmentRole, out var config)) - { - return false; - } - - return config.LimitPlateClassToFrontPlateClass.GetValueOrDefault(false); - } - /// /// Gets the minimum and maximum plate class levels from an array of plates ///