Fixed Weapon cache generation running 15+ times on first load due to threading issues

Expanded weapon and equipment cache to include mods - Fixes randomisation slots causing warnings during bot generation

Optimised `FilterModsByBlacklist` handling of blacklists
This commit is contained in:
Chomp
2025-06-23 22:26:20 +01:00
parent fc1e14f9c5
commit 083e3b97e0
3 changed files with 71 additions and 43 deletions
@@ -183,7 +183,7 @@ public class BotEquipmentModGenerator(
var plateSlotFilteringOutcome = FilterPlateModsForSlotByLevel(
settings,
modSlotName.ToLower(),
compatibleModsPool[modSlotName],
compatibleModsPool.GetValueOrDefault(modSlotName),
parentTemplate
);
switch (plateSlotFilteringOutcome.Result)
@@ -210,7 +210,7 @@ public class BotEquipmentModGenerator(
}
// Choose random mod from pool and check its compatibility
string modTpl = null;
string? modTpl = null;
var found = false;
var exhaustableModPool = CreateExhaustableArray(modPoolToChooseFrom);
while (exhaustableModPool.HasValues())
@@ -1837,9 +1837,11 @@ public class BotEquipmentModGenerator(
return;
}
var supportedSubModsSet = supportedSubMods.ToHashSet();
// Filter mods
var filteredMods = FilterModsByBlacklist(
supportedSubMods.ToHashSet(),
supportedSubModsSet,
botEquipBlacklist,
desiredSlotName
);
@@ -1855,7 +1857,7 @@ public class BotEquipmentModGenerator(
modPool.TryAdd(modTemplate.Id, new Dictionary<string, HashSet<string>>());
modPool[modTemplate.Id][desiredSlotObject.Name] = supportedSubMods.ToHashSet();
modPool[modTemplate.Id][desiredSlotObject.Name] = supportedSubModsSet;
}
/// <summary>
@@ -1875,51 +1877,62 @@ public class BotEquipmentModGenerator(
_botEquipmentModPoolService.GetCompatibleModsForWeaponSlot(parentItemId, modSlot)
);
var filteredMods = FilterModsByBlacklist(modsFromDynamicPool, botEquipBlacklist, modSlot);
if (!filteredMods.Any())
if (modsFromDynamicPool.Count == 0)
{
_logger.Warning(
_localisationService.GetText(
"bot-unable_to_filter_mod_slot_all_blacklisted",
modSlot
)
);
// Mod pool has no items, don't bother doing any filtering below
return modsFromDynamicPool;
}
return filteredMods;
var filteredMods = FilterModsByBlacklist(modsFromDynamicPool, botEquipBlacklist, modSlot);
if (filteredMods.Any())
{
// Filtering left at least 1 item, return it
return filteredMods;
}
_logger.Warning(
_localisationService.GetText(
"bot-unable_to_filter_mod_slot_all_blacklisted",
modSlot
)
);
return modsFromDynamicPool;
}
/// <summary>
/// Take a list of tpls and filter out blacklisted values using itemFilterService + botEquipmentBlacklist
/// </summary>
/// <param name="allowedMods">Base mods to filter</param>
/// <param name="botEquipBlacklist">Equipment blacklist</param>
/// <param name="modSlot">Slot mods belong to</param>
/// <returns>Filtered array of mod tpls</returns>
/// <param name="modTplPool">Base mod tpls to filter</param>
/// <param name="botEquipBlacklist">Equipment blacklist details for bot level range</param>
/// <param name="modSlot">Mod slot mods belong to</param>
/// <returns>New set of tpls not in blacklist(s)</returns>
public HashSet<string> FilterModsByBlacklist(
HashSet<string> allowedMods,
HashSet<string> modTplPool,
EquipmentFilterDetails? botEquipBlacklist,
string modSlot
)
{
// No blacklist, nothing to filter out
if (botEquipBlacklist is null)
if (!modTplPool.Any())
{
return allowedMods;
// Mod pool has no items, don't bother doing any filtering below
return modTplPool;
}
var result = new HashSet<string>();
// Get item blacklist and mod equipment blacklist as one Set
var blacklist = _itemFilterService.GetBlacklistedItems();
if (botEquipBlacklist?.Equipment is not null && botEquipBlacklist.Equipment.TryGetValue(modSlot, out var equipmentBlacklistValues))
{
blacklist.UnionWith(equipmentBlacklistValues);
}
// 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)).ToHashSet();
var result = _cloner.Clone(modTplPool);
return result;
// Filter out blacklisted tpls
result.ExceptWith(blacklist);
return modTplPool;
}
/// <summary>
@@ -1963,7 +1976,7 @@ public class BotEquipmentModGenerator(
itemModPool = modPool[cylinderMagTemplate.Id];
}
ExhaustableArray<string> exhaustableModPool = null;
ExhaustableArray<string>? exhaustableModPool = null;
var modSlot = "cartridges";
const string camoraFirstSlot = "camora_000";
if (itemModPool.TryGetValue(modSlot, out var value))
@@ -410,7 +410,7 @@ public class BotInventoryGenerator(
/// <param name="templateInventory"></param>
/// <param name="isPmc">is bot a PMC</param>
/// <returns></returns>
protected Dictionary<string, double> GetPocketPoolByGameEdition(
protected Dictionary<string, double>? GetPocketPoolByGameEdition(
string chosenGameVersion,
BotTypeInventory templateInventory,
bool isPmc
@@ -510,7 +510,7 @@ public class BotInventoryGenerator(
var shouldSpawn = _randomUtil.GetChance100(spawnChance ?? 0);
if (shouldSpawn && settings.RootEquipmentPool.Any())
{
TemplateItem pickedItemDb = null;
TemplateItem? pickedItemDb = null;
var found = false;
// Limit attempts to find a compatible item as it's expensive to check them all
@@ -590,7 +590,7 @@ public class BotInventoryGenerator(
var botEquipBlacklist = _botEquipmentFilterService.GetBotEquipmentBlacklist(
settings.BotData.EquipmentRole,
settings.GeneratingPlayerLevel.Value
settings.GeneratingPlayerLevel.GetValueOrDefault(1)
);
// Edge case: Filter the armor items mod pool if bot exists in config dict + config has armor slot
@@ -26,7 +26,13 @@ public class BotEquipmentModPoolService(
ConcurrentDictionary<string, HashSet<string>>
> GearModPool
{
get { return _gearModPool ??= GenerateGearPool(); }
get
{
lock (_lockObject)
{
return _gearModPool ??= GenerateGearPool();
}
}
}
private ConcurrentDictionary<
@@ -38,7 +44,13 @@ public class BotEquipmentModPoolService(
ConcurrentDictionary<string, HashSet<string>>
> WeaponModPool
{
get { return _weaponModPool ??= GenerateWeaponPool(); }
get
{
lock (_lockObject)
{
return _weaponModPool ??= GenerateWeaponPool();
}
}
}
/// <summary>
@@ -85,7 +97,7 @@ public class BotEquipmentModPoolService(
// Add base item (weapon/armor) to pool
pool.TryAdd(item.Id, new ConcurrentDictionary<string, HashSet<string>>());
// iterate over each items mod slots e.g. mod_muzzle
// 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
@@ -108,6 +120,8 @@ public class BotEquipmentModPoolService(
var subItemDetails = itemHelper.GetItem(itemToAddTpl).Value;
var hasSubItemsToAdd = (subItemDetails?.Properties?.Slots?.Count ?? 0) > 0;
// Item has Slots + pool doesn't have value
if (hasSubItemsToAdd && !pool.ContainsKey(subItemDetails.Id))
// Recursive call
{
@@ -237,14 +251,14 @@ public class BotEquipmentModPoolService(
ConcurrentDictionary<string, HashSet<string>>
> GenerateWeaponPool()
{
var weapons = databaseService
var weaponsAndMods = databaseService
.GetItems()
.Values.Where(item =>
string.Equals(item.Type, "Item", StringComparison.OrdinalIgnoreCase)
&& itemHelper.IsOfBaseclass(item.Id, BaseClasses.WEAPON)
);
&& itemHelper.IsOfBaseclasses(item.Id, [BaseClasses.WEAPON, BaseClasses.MOD]));
logger.Warning("generating weapon pool");
return GeneratePool(weapons, "weapon");
return GeneratePool(weaponsAndMods, "weapon");
}
/// <summary>
@@ -255,7 +269,7 @@ public class BotEquipmentModPoolService(
ConcurrentDictionary<string, HashSet<string>>
> GenerateGearPool()
{
var gear = databaseService
var gearAndMods = databaseService
.GetItems()
.Values.Where(item =>
string.Equals(item.Type, "Item", StringComparison.OrdinalIgnoreCase)
@@ -266,10 +280,11 @@ public class BotEquipmentModPoolService(
BaseClasses.VEST,
BaseClasses.ARMOR,
BaseClasses.HEADWEAR,
BaseClasses.MOD
]
)
);
return GeneratePool(gear, "gear");
return GeneratePool(gearAndMods, "gear");
}
}