From 083e3b97e0c727e92d0592e0bfa87700f95a1be2 Mon Sep 17 00:00:00 2001 From: Chomp Date: Mon, 23 Jun 2025 22:26:20 +0100 Subject: [PATCH] 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 --- .../Generators/BotEquipmentModGenerator.cs | 75 +++++++++++-------- .../Generators/BotInventoryGenerator.cs | 6 +- .../Services/BotEquipmentModPoolService.cs | 33 +++++--- 3 files changed, 71 insertions(+), 43 deletions(-) diff --git a/Libraries/SPTarkov.Server.Core/Generators/BotEquipmentModGenerator.cs b/Libraries/SPTarkov.Server.Core/Generators/BotEquipmentModGenerator.cs index cc9fa15d..4f429874 100644 --- a/Libraries/SPTarkov.Server.Core/Generators/BotEquipmentModGenerator.cs +++ b/Libraries/SPTarkov.Server.Core/Generators/BotEquipmentModGenerator.cs @@ -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>()); - modPool[modTemplate.Id][desiredSlotObject.Name] = supportedSubMods.ToHashSet(); + modPool[modTemplate.Id][desiredSlotObject.Name] = supportedSubModsSet; } /// @@ -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; + } /// /// Take a list of tpls and filter out blacklisted values using itemFilterService + botEquipmentBlacklist /// - /// Base mods to filter - /// Equipment blacklist - /// Slot mods belong to - /// Filtered array of mod tpls + /// Base mod tpls to filter + /// Equipment blacklist details for bot level range + /// Mod slot mods belong to + /// New set of tpls not in blacklist(s) public HashSet FilterModsByBlacklist( - HashSet allowedMods, + HashSet 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(); + // 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; } /// @@ -1963,7 +1976,7 @@ public class BotEquipmentModGenerator( itemModPool = modPool[cylinderMagTemplate.Id]; } - ExhaustableArray exhaustableModPool = null; + ExhaustableArray? exhaustableModPool = null; var modSlot = "cartridges"; const string camoraFirstSlot = "camora_000"; if (itemModPool.TryGetValue(modSlot, out var value)) diff --git a/Libraries/SPTarkov.Server.Core/Generators/BotInventoryGenerator.cs b/Libraries/SPTarkov.Server.Core/Generators/BotInventoryGenerator.cs index e7e8e841..390c6bef 100644 --- a/Libraries/SPTarkov.Server.Core/Generators/BotInventoryGenerator.cs +++ b/Libraries/SPTarkov.Server.Core/Generators/BotInventoryGenerator.cs @@ -410,7 +410,7 @@ public class BotInventoryGenerator( /// /// is bot a PMC /// - protected Dictionary GetPocketPoolByGameEdition( + protected Dictionary? 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 diff --git a/Libraries/SPTarkov.Server.Core/Services/BotEquipmentModPoolService.cs b/Libraries/SPTarkov.Server.Core/Services/BotEquipmentModPoolService.cs index 2326365f..fdede61b 100644 --- a/Libraries/SPTarkov.Server.Core/Services/BotEquipmentModPoolService.cs +++ b/Libraries/SPTarkov.Server.Core/Services/BotEquipmentModPoolService.cs @@ -26,7 +26,13 @@ public class BotEquipmentModPoolService( ConcurrentDictionary> > GearModPool { - get { return _gearModPool ??= GenerateGearPool(); } + get + { + lock (_lockObject) + { + return _gearModPool ??= GenerateGearPool(); + } + } } private ConcurrentDictionary< @@ -38,7 +44,13 @@ public class BotEquipmentModPoolService( ConcurrentDictionary> > WeaponModPool { - get { return _weaponModPool ??= GenerateWeaponPool(); } + get + { + lock (_lockObject) + { + return _weaponModPool ??= GenerateWeaponPool(); + } + } } /// @@ -85,7 +97,7 @@ public class BotEquipmentModPoolService( // Add base item (weapon/armor) to pool pool.TryAdd(item.Id, new ConcurrentDictionary>()); - // 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> > 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"); } /// @@ -255,7 +269,7 @@ public class BotEquipmentModPoolService( ConcurrentDictionary> > 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"); } }