diff --git a/Libraries/Core/Generators/BotEquipmentModGenerator.cs b/Libraries/Core/Generators/BotEquipmentModGenerator.cs
index 1405f2fe..d70472d0 100644
--- a/Libraries/Core/Generators/BotEquipmentModGenerator.cs
+++ b/Libraries/Core/Generators/BotEquipmentModGenerator.cs
@@ -244,11 +244,11 @@ public class BotEquipmentModGenerator(
* @param modSlot front/back
* @returns Armor IItem
*/
- protected Item GetDefaultPresetArmorSlot(string armorItemTpl, string modSlot)
+ protected Item? GetDefaultPresetArmorSlot(string armorItemTpl, string modSlot)
{
var defaultPreset = _presetHelper.GetDefaultPreset(armorItemTpl);
- return defaultPreset?.Items.FirstOrDefault((item) => item.SlotId?.ToLower() == modSlot);
+ return defaultPreset?.Items?.FirstOrDefault((item) => item.SlotId?.ToLower() == modSlot);
}
@@ -260,11 +260,6 @@ public class BotEquipmentModGenerator(
/// Weapon + mods array
public List- GenerateModsForWeapon(string sessionId, GenerateWeaponRequest request)
{
- var pmcProfile = _profileHelper.GetPmcProfile(sessionId);
-
- // Get pool of mods that fit weapon
- var compatibleModsPool = request.ModPool[request.ParentTemplate.Id];
-
if (
!(
request.ParentTemplate.Properties.Slots.Any() ||
@@ -288,7 +283,12 @@ public class BotEquipmentModGenerator(
return request.Weapon;
}
- var botEquipConfig = _botConfig.Equipment[request.BotData.EquipmentRole];
+ var pmcProfile = _profileHelper.GetPmcProfile(sessionId);
+
+ // Get pool of mods that fit weapon
+ request.ModPool.TryGetValue(request.ParentTemplate.Id, out var compatibleModsPool);
+
+ _botConfig.Equipment.TryGetValue(request.BotData.EquipmentRole, out var botEquipConfig);
var botEquipBlacklist = _botEquipmentFilterService.GetBotEquipmentBlacklist(
request.BotData.EquipmentRole,
pmcProfile.Info.Level ?? 0
@@ -494,13 +494,26 @@ public class BotEquipmentModGenerator(
if (isRandomisableSlot && !containsModInPool && modToAddTemplate.Value.Properties.Slots.Any())
{
var modFromService = _botEquipmentModPoolService.GetModsForWeaponSlot(modToAddTemplate.Value.Id);
- if (modFromService.Keys.Any())
+ if (modFromService?.Keys.Count > 0)
{
request.ModPool[modToAddTemplate.Value.Id] = modFromService;
containsModInPool = true;
}
}
+ // Fallback when mods with REQUIRED children are not in the pool, add them and process
+ if (!containsModInPool && !isRandomisableSlot)
+ {
+ // Check for required mods the item we've added needs to be classified as 'valid'
+ var modFromService = _botEquipmentModPoolService.GetRequiredModsForWeaponSlot(modToAddTemplate.Value.Id);
+ if (modFromService?.Keys.Count > 0)
+ {
+ request.ModPool[modToAddTemplate.Value.Id] = modFromService;
+ containsModInPool = true;
+ }
+
+ }
+
if (containsModInPool)
{
GenerateWeaponRequest recursiveRequestData = new()
@@ -871,16 +884,16 @@ public class BotEquipmentModGenerator(
request.Weapon,
request.ModSlot
);
- if (chosenModResult.SlotBlocked ?? false && !(parentSlot.Required ?? false))
+ if (chosenModResult.SlotBlocked.GetValueOrDefault(false) && !parentSlot.Required.GetValueOrDefault(false))
{
// Don't bother trying to fit mod, slot is completely blocked
return null;
}
// Log if mod chosen was incompatible
- if (chosenModResult.Incompatible ?? false && !(parentSlot.Required ?? false))
+ if (chosenModResult.Incompatible.GetValueOrDefault(false) && !(parentSlot.Required.GetValueOrDefault(false)))
{
- _logger.Debug(chosenModResult.Reason);
+ _logger.Debug($"Unable to find compatible mod of type: {parentSlot.Name}, in slot: {request.ModSlot} reason: {chosenModResult.Reason}");
}
// Get random mod to attach from items db for required slots if none found above
@@ -891,14 +904,14 @@ public class BotEquipmentModGenerator(
}
// Compatible item not found + not required
- if (!(chosenModResult.Found ?? false) && parentSlot != null && (!parentSlot.Required ?? false))
+ if (!(chosenModResult.Found.GetValueOrDefault(false)) && parentSlot is not null && (!parentSlot.Required.GetValueOrDefault(false)))
{
return null;
}
- if (!(chosenModResult.Found ?? false) && parentSlot != null)
+ if (!(chosenModResult.Found ?? false) && parentSlot is not null)
{
- if (parentSlot.Required ?? false)
+ if (parentSlot.Required.GetValueOrDefault(false))
{
_logger.Warning(
$"Required slot unable to be filled, {request.ModSlot} on {request.ParentTemplate.Name} {request.ParentTemplate.Id} for weapon: {request.Weapon[0].Template}"
@@ -966,7 +979,7 @@ public class BotEquipmentModGenerator(
// Filter mod pool to only items that appear in parents allowed list
preFilteredModPool = preFilteredModPool.Where((tpl) => parentSlot.Props.Filters[0].Filter.Contains(tpl)).ToList();
- if (preFilteredModPool.Count() == 0)
+ if (preFilteredModPool.Count == 0)
{
return new() { Incompatible = true, Found = false, Reason = "No mods found in parents allowed list" };
}
@@ -995,9 +1008,9 @@ public class BotEquipmentModGenerator(
};
// Limit how many attempts to find a compatible mod can occur before giving up
- var maxBlockedAttempts = Math.Round(modPool.Count() * 0.75); // 75% of pool size
+ var maxBlockedAttempts = Math.Round(modPool.Count * 0.75); // 75% of pool size
var blockedAttemptCount = 0;
- string chosenTpl = null;
+ string chosenTpl;
while (exhaustableModPool.HasValues())
{
chosenTpl = exhaustableModPool.GetRandomValue();
@@ -1015,7 +1028,7 @@ public class BotEquipmentModGenerator(
}
// Success - Default wanted + only 1 item in pool
- if (modSpawnType == ModSpawn.DEFAULT_MOD && modPool.Count() == 1)
+ if (modSpawnType == ModSpawn.DEFAULT_MOD && modPool.Count == 1)
{
chosenModResult.Found = true;
chosenModResult.Incompatible = false;
@@ -1032,15 +1045,20 @@ public class BotEquipmentModGenerator(
if (existingItemBlockingChoice is not null)
{
// Give max of x attempts of picking a mod if blocked by another
- if (blockedAttemptCount > maxBlockedAttempts)
+ // OR Blocked and modpool only had 1 item
+ if (blockedAttemptCount > maxBlockedAttempts || modPool.Count == 1)
{
blockedAttemptCount = 0; // reset
+ //chosenModResult.SlotBlocked = true; // Later in code we try to find replacement, but only when "slotBlocked" is not true
+ chosenModResult.Reason = "Blocked";
+
break;
}
blockedAttemptCount++;
// Not compatible - Try again
+ ;
continue;
}
@@ -1073,7 +1091,7 @@ public class BotEquipmentModGenerator(
///
/// Tpls that are incompatible and should not be used
/// string array of compatible mod tpls with weapon
- public List GetFilteredModPool(HashSet modPool, List tplBlacklist)
+ public List GetFilteredModPool(HashSet modPool, HashSet tplBlacklist)
{
return modPool.Where((tpl) => !tplBlacklist.Contains(tpl)).ToList();
}
diff --git a/Libraries/Core/Generators/BotWeaponGenerator.cs b/Libraries/Core/Generators/BotWeaponGenerator.cs
index 6799090c..7a625bda 100644
--- a/Libraries/Core/Generators/BotWeaponGenerator.cs
+++ b/Libraries/Core/Generators/BotWeaponGenerator.cs
@@ -176,7 +176,7 @@ public class BotWeaponGenerator(
);
}
- // Use weapon preset from globals.json if weapon isnt valid
+ // Use weapon preset from globals.json if weapon isn't valid
if (!IsWeaponValid(weaponWithModsArray, botRole))
{
// Weapon is bad, fall back to weapons preset
diff --git a/Libraries/Core/Models/Spt/Bots/GenerateWeaponRequest.cs b/Libraries/Core/Models/Spt/Bots/GenerateWeaponRequest.cs
index e1ec77d4..a1fef553 100644
--- a/Libraries/Core/Models/Spt/Bots/GenerateWeaponRequest.cs
+++ b/Libraries/Core/Models/Spt/Bots/GenerateWeaponRequest.cs
@@ -43,7 +43,7 @@ public record GenerateWeaponRequest
/** Array of item tpls the weapon does not support */
[JsonPropertyName("conflictingItemTpls")]
- public List? ConflictingItemTpls { get; set; }
+ public HashSet? ConflictingItemTpls { get; set; }
}
public record BotData
diff --git a/Libraries/Core/Models/Spt/Bots/ModToSpawnRequest.cs b/Libraries/Core/Models/Spt/Bots/ModToSpawnRequest.cs
index a5a8edad..96d0d652 100644
--- a/Libraries/Core/Models/Spt/Bots/ModToSpawnRequest.cs
+++ b/Libraries/Core/Models/Spt/Bots/ModToSpawnRequest.cs
@@ -74,7 +74,7 @@ public record ModToSpawnRequest
/// List of item tpls the weapon does not support
///
[JsonPropertyName("conflictingItemTpls")]
- public List? ConflictingItemTpls { get; set; }
+ public HashSet? ConflictingItemTpls { get; set; }
[JsonPropertyName("botData")]
public BotData? BotData { get; set; }
diff --git a/Libraries/Core/Services/BotEquipmentModPoolService.cs b/Libraries/Core/Services/BotEquipmentModPoolService.cs
index f2ba4968..5473178d 100644
--- a/Libraries/Core/Services/BotEquipmentModPoolService.cs
+++ b/Libraries/Core/Services/BotEquipmentModPoolService.cs
@@ -170,6 +170,31 @@ public class BotEquipmentModPoolService
return _weaponModPool[itemTpl];
}
+ public Dictionary>? GetRequiredModsForWeaponSlot(string itemTpl)
+ {
+ var result = new Dictionary>();
+
+ // Get item from db
+ var itemDb = _itemHelper.GetItem(itemTpl).Value;
+ if (itemDb.Properties.Slots is not null)
+ {
+ // Loop over slots flagged as 'required'
+ foreach (var slot in itemDb.Properties.Slots.Where(slot => slot.Required.GetValueOrDefault(false)))
+ {
+ // Create dict entry for mod slot
+ result.Add(slot.Name, []);
+
+ // Add compatible tpls to dicts hashset
+ foreach (var compatibleItemTpl in slot.Props.Filters.FirstOrDefault().Filter)
+ {
+ result[slot.Name].Add(compatibleItemTpl);
+ }
+ }
+ }
+
+ return result;
+ }
+
/**
* Create weapon mod pool and set generated flag to true
*/