From 31ae5feb41e52c3e0929a15d85389edf3b603abf Mon Sep 17 00:00:00 2001 From: Chomp <27521899+chompDev@users.noreply.github.com> Date: Sun, 4 May 2025 12:51:45 +0100 Subject: [PATCH] Bot generation performance (#231) * chore: Bot generation performance improvements (#227) * Bot generation performance improvements * Revert debug logging logic * Get rid of Info log which is bound to be printed inevitably at each bot wave generation * Use assault const * Removed comments Removed lowercase side constants Renamed equipment role to better explain its purpose Fixed `Blocks` property being serialised to client Updated `_slotsWithNoCompatIssues` to use existing enum type Reduced log line to be debug * Fixed `GetRandomTextThatMatchesPartialKey` incorrectly checking eft locales instead of spt locales * Updated various methods to be protected --------- Co-authored-by: hulkhan22 Co-authored-by: Chomp --- .../Constants/ContainerConstants.cs | 13 +++ .../Constants/RoleConstants.cs | 6 ++ .../Constants/SideConstants.cs | 12 +++ .../Constants/SlotConstants.cs | 6 ++ .../Controllers/BotController.cs | 82 ++++++++++--------- .../Generators/BotGenerator.cs | 28 ++++--- .../Helpers/BotGeneratorHelper.cs | 82 ++++++++++++------- .../SPTarkov.Server.Core/Helpers/BotHelper.cs | 32 +++++--- .../Helpers/DurabilityLimitsHelper.cs | 2 +- .../Models/Eft/Common/Tables/TemplateItem.cs | 24 ++++++ .../Services/BotLootCacheService.cs | 7 +- .../Services/LocalisationService.cs | 11 ++- .../Services/MatchBotDetailsCacheService.cs | 16 ++-- 13 files changed, 212 insertions(+), 109 deletions(-) create mode 100644 Libraries/SPTarkov.Server.Core/Constants/ContainerConstants.cs create mode 100644 Libraries/SPTarkov.Server.Core/Constants/RoleConstants.cs create mode 100644 Libraries/SPTarkov.Server.Core/Constants/SideConstants.cs create mode 100644 Libraries/SPTarkov.Server.Core/Constants/SlotConstants.cs diff --git a/Libraries/SPTarkov.Server.Core/Constants/ContainerConstants.cs b/Libraries/SPTarkov.Server.Core/Constants/ContainerConstants.cs new file mode 100644 index 00000000..3d157ced --- /dev/null +++ b/Libraries/SPTarkov.Server.Core/Constants/ContainerConstants.cs @@ -0,0 +1,13 @@ +namespace SPTarkov.Server.Core.Constants; + +public static class Containers +{ + public const string ArmorVest = "ArmorVest"; + public const string Earpiece = "Earpiece"; + public const string FaceCover = "FaceCover"; + public const string Headwear = "Headwear"; + public const string Eyewear = "Eyewear"; + public const string Collapsible = "Collapsible"; + public const string LeftStance = "LeftStance"; + public const string Folding = "Folding"; +} diff --git a/Libraries/SPTarkov.Server.Core/Constants/RoleConstants.cs b/Libraries/SPTarkov.Server.Core/Constants/RoleConstants.cs new file mode 100644 index 00000000..99ef40d8 --- /dev/null +++ b/Libraries/SPTarkov.Server.Core/Constants/RoleConstants.cs @@ -0,0 +1,6 @@ +namespace SPTarkov.Server.Core.Constants; + +public class Roles +{ + public const string Assault = "assault"; +} diff --git a/Libraries/SPTarkov.Server.Core/Constants/SideConstants.cs b/Libraries/SPTarkov.Server.Core/Constants/SideConstants.cs new file mode 100644 index 00000000..4cdb50ac --- /dev/null +++ b/Libraries/SPTarkov.Server.Core/Constants/SideConstants.cs @@ -0,0 +1,12 @@ +namespace SPTarkov.Server.Core.Constants; + +public static class Sides +{ + public const string Usec = "Usec"; + public const string Bear = "Bear"; + public const string Savage = "Savage"; + public const string PmcUsec = "pmcUSEC"; + public const string PmcBear = "pmcBEAR"; + + public const string PmcEquipmentRole = "pmc"; +} diff --git a/Libraries/SPTarkov.Server.Core/Constants/SlotConstants.cs b/Libraries/SPTarkov.Server.Core/Constants/SlotConstants.cs new file mode 100644 index 00000000..c8b8152f --- /dev/null +++ b/Libraries/SPTarkov.Server.Core/Constants/SlotConstants.cs @@ -0,0 +1,6 @@ +namespace SPTarkov.Server.Core.Constants; + +public static class Slots +{ + public const string Dogtag = "Dogtag"; +} diff --git a/Libraries/SPTarkov.Server.Core/Controllers/BotController.cs b/Libraries/SPTarkov.Server.Core/Controllers/BotController.cs index fd70e82f..2411948a 100644 --- a/Libraries/SPTarkov.Server.Core/Controllers/BotController.cs +++ b/Libraries/SPTarkov.Server.Core/Controllers/BotController.cs @@ -1,6 +1,7 @@ using System.Diagnostics; using System.Text.Json.Serialization; using SPTarkov.Common.Annotations; +using SPTarkov.Server.Core.Constants; using SPTarkov.Server.Core.Context; using SPTarkov.Server.Core.Generators; using SPTarkov.Server.Core.Helpers; @@ -125,10 +126,10 @@ public class BotController( if (!botTypesDb.TryGetValue(botTypeLower, out var botDetails)) { // No bot of this type found, copy details from assault - result[botTypeLower] = result["assault"]; + result[botTypeLower] = result[Roles.Assault]; if (_logger.IsLogEnabled(LogLevel.Debug)) { - _logger.Debug($"Unable to find bot: {botTypeLower} in db, copying 'assault'"); + _logger.Debug($"Unable to find bot: {botTypeLower} in db, copying '{Roles.Assault}'"); } continue; @@ -180,13 +181,12 @@ public class BotController( /// List of generated bots protected List GenerateBotWaves(GenerateBotsRequestData request, PmcData? pmcProfile, string sessionId) { - var result = new List(); - + var generatedBotList = new List(); var raidSettings = GetMostRecentRaidSettings(); - var allPmcsHaveSameNameAsPlayer = _randomUtil.GetChance100( _pmcConfig.AllPMCsHavePlayerNameWithRandomPrefixChance ); + var stopwatch = Stopwatch.StartNew(); // Map conditions to promises for bot generation @@ -197,24 +197,19 @@ public class BotController( condition, pmcProfile, allPmcsHaveSameNameAsPlayer, - raidSettings, - Math.Max(GetBotPresetGenerationLimit(condition.Role), - condition.Limit), // Choose largest between value passed in from request vs what's in bot.config - _botHelper.IsBotPmc(condition.Role)); + raidSettings); - lock (_botListLock) - { - result.AddRange(GenerateBotWave(condition, botWaveGenerationDetails, sessionId)); - } + GenerateBotWave(condition, botWaveGenerationDetails, generatedBotList, sessionId); })).ToArray()); stopwatch.Stop(); + if (_logger.IsLogEnabled(LogLevel.Debug)) { _logger.Debug($"Took {stopwatch.ElapsedMilliseconds}ms to GenerateMultipleBotsAndCache()"); } - return result; + return generatedBotList; } /// @@ -222,9 +217,14 @@ public class BotController( /// /// /// + /// List of bots to fill /// Session/Player id /// - protected List GenerateBotWave(GenerateCondition generateRequest, BotGenerationDetails botGenerationDetails, string sessionId) + protected void GenerateBotWave( + GenerateCondition generateRequest, + BotGenerationDetails botGenerationDetails, + List botList, + string sessionId) { var isEventBot = generateRequest.Role?.Contains("event", StringComparison.OrdinalIgnoreCase); if (isEventBot.GetValueOrDefault(false)) @@ -243,30 +243,36 @@ public class BotController( _logger.Debug($"Generating wave of: {botGenerationDetails.BotCountToGenerate} bots of type: {role} {botGenerationDetails.BotDifficulty}"); } - var results = new List(); - for (var i = 0; i < botGenerationDetails.BotCountToGenerate; i++) + Parallel.For(0, botGenerationDetails.BotCountToGenerate.Value, (i) => { + BotBase bot = null; + try { - var bot = _botGenerator.PrepareAndGenerateBot(sessionId, _cloner.Clone(botGenerationDetails)); - - // The client expects the Side for PMCs to be `Savage` - // We do this here so it's after we cache the bot in the match details lookup, as when you die, they will have the right side - if (bot.Info.Side is "Bear" or "Usec") - { - bot.Info.Side = "Savage"; - } - - results.Add(bot); - - // Store bot details in cache so post-raid PMC messages can use data - _matchBotDetailsCacheService.CacheBot(bot); + bot = _botGenerator.PrepareAndGenerateBot(sessionId, _cloner.Clone(botGenerationDetails)); } catch (Exception e) { - _logger.Error($"Failed to generate bot: {botGenerationDetails.Role} #{i + 1}: {e.Message} {e.StackTrace}"); + _logger.Error( + $"Failed to generate bot: {botGenerationDetails.Role} #{i + 1}: {e.Message} {e.StackTrace}"); + return; } - } + + // The client expects the Side for PMCs to be `Savage` + // We do this here so it's after we cache the bot in the match details lookup, as when you die, they will have the right side + if (bot.Info.Side is Sides.Bear or Sides.Usec) + { + bot.Info.Side = Sides.Savage; + } + + lock (_botListLock) + { + botList.Add(bot); + } + + // Store bot details in cache so post-raid PMC messages can use data + _matchBotDetailsCacheService.CacheBot(bot); + }); if (_logger.IsLogEnabled(LogLevel.Debug)) { @@ -275,8 +281,6 @@ public class BotController( $"({botGenerationDetails.EventRole ?? botGenerationDetails.Role ?? ""}) {botGenerationDetails.BotDifficulty} bots" ); } - - return results; } /// @@ -314,17 +318,15 @@ public class BotController( /// Player who is generating bots /// Should all PMCs have same name as player /// Settings chosen pre-raid by player in client - /// How many bots to generate - /// Force bot being generated to be a PMC /// BotGenerationDetails protected BotGenerationDetails GetBotGenerationDetailsForWave( GenerateCondition condition, PmcData? pmcProfile, bool allPmcsHaveSameNameAsPlayer, - GetRaidConfigurationRequestData? raidSettings, - int? botCountToGenerate, - bool generateAsPmc) + GetRaidConfigurationRequestData? raidSettings) { + var generateAsPmc = _botHelper.IsBotPmc(condition.Role); + return new BotGenerationDetails { IsPmc = generateAsPmc, @@ -334,7 +336,7 @@ public class BotController( PlayerName = pmcProfile?.Info?.Nickname, BotRelativeLevelDeltaMax = _pmcConfig.BotRelativeLevelDeltaMax, BotRelativeLevelDeltaMin = _pmcConfig.BotRelativeLevelDeltaMin, - BotCountToGenerate = botCountToGenerate, + BotCountToGenerate = Math.Max(GetBotPresetGenerationLimit(condition.Role), condition.Limit), // Choose largest between value passed in from request vs what's in bot.config BotDifficulty = condition.Difficulty, LocationSpecificPmcLevelOverride = GetPmcLevelRangeForMap(raidSettings?.Location), // Min/max levels for PMCs to generate within IsPlayerScav = false, diff --git a/Libraries/SPTarkov.Server.Core/Generators/BotGenerator.cs b/Libraries/SPTarkov.Server.Core/Generators/BotGenerator.cs index b1d5e263..bc9c7de0 100644 --- a/Libraries/SPTarkov.Server.Core/Generators/BotGenerator.cs +++ b/Libraries/SPTarkov.Server.Core/Generators/BotGenerator.cs @@ -1,4 +1,5 @@ using SPTarkov.Common.Annotations; +using SPTarkov.Server.Core.Constants; using SPTarkov.Server.Core.Helpers; using SPTarkov.Server.Core.Models.Common; using SPTarkov.Server.Core.Models.Eft.Common; @@ -12,6 +13,7 @@ using SPTarkov.Server.Core.Services; using SPTarkov.Server.Core.Utils; using SPTarkov.Server.Core.Utils.Cloners; using BodyPart = SPTarkov.Server.Core.Models.Eft.Common.Tables.BodyPart; +using BodyParts = SPTarkov.Server.Core.Constants.BodyParts; using LogLevel = SPTarkov.Server.Core.Models.Spt.Logging.LogLevel; @@ -53,12 +55,12 @@ public class BotGenerator( var bot = GetCloneOfBotBase(); bot.Info.Settings.BotDifficulty = difficulty; bot.Info.Settings.Role = role; - bot.Info.Side = "Savage"; + bot.Info.Side = Sides.Savage; var botGenDetails = new BotGenerationDetails { IsPmc = false, - Side = "Savage", + Side = Sides.Savage, Role = role, BotRelativeLevelDeltaMax = 0, BotRelativeLevelDeltaMin = 0, @@ -116,7 +118,7 @@ public class BotGenerator( public BotBase PrepareAndGenerateBot(string sessionId, BotGenerationDetails? botGenerationDetails) { var preparedBotBase = GetPreparedBotBase( - botGenerationDetails.EventRole ?? botGenerationDetails.Role, // Use eventRole if provided, + botGenerationDetails.EventRole ?? botGenerationDetails.Role, // Use eventRole if provided botGenerationDetails.Side, botGenerationDetails.BotDifficulty ); @@ -306,7 +308,7 @@ public class BotGenerator( /// True if name should be simulated pscav public bool ShouldSimulatePlayerScav(string botRole) { - return botRole == "assault" && _randomUtil.GetChance100(_botConfig.ChanceAssaultScavHasPlayerScavName); + return botRole == Roles.Assault && _randomUtil.GetChance100(_botConfig.ChanceAssaultScavHasPlayerScavName); } /// @@ -504,7 +506,7 @@ public class BotGenerator( BodyParts = new Dictionary { { - "Head", new BodyPartHealth + BodyParts.Head, new BodyPartHealth { Health = new CurrentMinMax { @@ -514,7 +516,7 @@ public class BotGenerator( } }, { - "Chest", new BodyPartHealth + BodyParts.Chest, new BodyPartHealth { Health = new CurrentMinMax { @@ -524,7 +526,7 @@ public class BotGenerator( } }, { - "Stomach", new BodyPartHealth + BodyParts.Stomach, new BodyPartHealth { Health = new CurrentMinMax { @@ -534,7 +536,7 @@ public class BotGenerator( } }, { - "LeftArm", new BodyPartHealth + BodyParts.LeftArm, new BodyPartHealth { Health = new CurrentMinMax { @@ -544,7 +546,7 @@ public class BotGenerator( } }, { - "RightArm", new BodyPartHealth + BodyParts.RightArm, new BodyPartHealth { Health = new CurrentMinMax { @@ -554,7 +556,7 @@ public class BotGenerator( } }, { - "LeftLeg", new BodyPartHealth + BodyParts.LeftLeg, new BodyPartHealth { Health = new CurrentMinMax { @@ -564,7 +566,7 @@ public class BotGenerator( } }, { - "RightLeg", new BodyPartHealth + BodyParts.RightLeg, new BodyPartHealth { Health = new CurrentMinMax { @@ -783,7 +785,7 @@ public class BotGenerator( Id = _hashUtil.Generate(), Template = GetDogtagTplByGameVersionAndSide(bot.Info.Side, bot.Info.GameVersion), ParentId = bot.Inventory.Equipment, - SlotId = "Dogtag", + SlotId = Slots.Dogtag, Upd = new Upd { SpawnedInSession = true @@ -801,7 +803,7 @@ public class BotGenerator( /// item tpl public string GetDogtagTplByGameVersionAndSide(string side, string gameVersion) { - if (string.Equals(side, "usec", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(side, Sides.Usec, StringComparison.OrdinalIgnoreCase)) { switch (gameVersion) { diff --git a/Libraries/SPTarkov.Server.Core/Helpers/BotGeneratorHelper.cs b/Libraries/SPTarkov.Server.Core/Helpers/BotGeneratorHelper.cs index 1873ea63..eaad7771 100644 --- a/Libraries/SPTarkov.Server.Core/Helpers/BotGeneratorHelper.cs +++ b/Libraries/SPTarkov.Server.Core/Helpers/BotGeneratorHelper.cs @@ -1,5 +1,6 @@ using System.Collections.Frozen; using SPTarkov.Common.Annotations; +using SPTarkov.Server.Core.Constants; using SPTarkov.Server.Core.Context; using SPTarkov.Server.Core.Models.Eft.Common.Tables; using SPTarkov.Server.Core.Models.Eft.Match; @@ -15,22 +16,50 @@ using LogLevel = SPTarkov.Server.Core.Models.Spt.Logging.LogLevel; namespace SPTarkov.Server.Core.Helpers; [Injectable] -public class BotGeneratorHelper( - ISptLogger _logger, - RandomUtil _randomUtil, - DurabilityLimitsHelper _durabilityLimitsHelper, - ItemHelper _itemHelper, - InventoryHelper _inventoryHelper, - ContainerHelper _containerHelper, - ApplicationContext _applicationContext, - LocalisationService _localisationService, - ConfigServer _configServer -) +public class BotGeneratorHelper { // Equipment slot ids that do not conflict with other slots - protected static readonly FrozenSet _slotsWithNoCompatIssues = ["Scabbard", "Backpack", "SecureContainer", "Holster", "ArmBand"]; - protected BotConfig _botConfig = _configServer.GetConfig(); - protected PmcConfig _pmcConfig = _configServer.GetConfig(); + private static readonly FrozenSet _slotsWithNoCompatIssues = [ + EquipmentSlots.Scabbard.ToString(), + EquipmentSlots.Backpack.ToString(), + EquipmentSlots.SecuredContainer.ToString(), + EquipmentSlots.Holster.ToString(), + EquipmentSlots.ArmBand.ToString() + ]; + + private readonly BotConfig _botConfig; + private readonly ISptLogger _logger; + private readonly RandomUtil _randomUtil; + private readonly DurabilityLimitsHelper _durabilityLimitsHelper; + private readonly ItemHelper _itemHelper; + private readonly InventoryHelper _inventoryHelper; + private readonly ContainerHelper _containerHelper; + private readonly ApplicationContext _applicationContext; + private readonly LocalisationService _localisationService; + private readonly string[] _pmcTypes; + + public BotGeneratorHelper(ISptLogger logger, + RandomUtil randomUtil, + DurabilityLimitsHelper durabilityLimitsHelper, + ItemHelper itemHelper, + InventoryHelper inventoryHelper, + ContainerHelper containerHelper, + ApplicationContext applicationContext, + LocalisationService localisationService, + ConfigServer configServer) + { + _logger = logger; + _randomUtil = randomUtil; + _durabilityLimitsHelper = durabilityLimitsHelper; + _itemHelper = itemHelper; + _inventoryHelper = inventoryHelper; + _containerHelper = containerHelper; + _applicationContext = applicationContext; + _localisationService = localisationService; + _botConfig = configServer.GetConfig(); + var pmcConfig = configServer.GetConfig(); + _pmcTypes = [pmcConfig.UsecType.ToLower(), pmcConfig.BearType.ToLower()]; + } /// /// Adds properties to an item @@ -392,9 +421,8 @@ public class BotGeneratorHelper( } // Does an equipped item have a property that blocks the desired item - check for prop "BlocksX" .e.g BlocksEarpiece / BlocksFaceCover - var blockingPropertyName = $"blocks{equipmentSlot}"; var templateItems = equippedItemsDb.ToList(); - var blockingItem = templateItems.FirstOrDefault(item => HasBlockingProperty(item, blockingPropertyName)); + var blockingItem = templateItems.FirstOrDefault(item => HasBlockingProperty(item, equipmentSlot)); if (blockingItem is not null) // this.logger.warning(`1 incompatibility found between - {itemToEquip[1]._name} and {blockingItem._name} - {equipmentSlot}`); { @@ -424,7 +452,7 @@ public class BotGeneratorHelper( // Does item being checked get blocked/block existing item if (itemToEquip.Properties.BlocksHeadwear ?? false) { - var existingHeadwear = itemsEquipped.FirstOrDefault(x => x.SlotId == "Headwear"); + var existingHeadwear = itemsEquipped.FirstOrDefault(x => x.SlotId == Containers.Headwear); if (existingHeadwear is not null) { return new ChooseRandomCompatibleModResult @@ -440,7 +468,7 @@ public class BotGeneratorHelper( // Does item being checked get blocked/block existing item if (itemToEquip.Properties.BlocksFaceCover.GetValueOrDefault(false)) { - var existingFaceCover = itemsEquipped.FirstOrDefault(item => item.SlotId == "FaceCover"); + var existingFaceCover = itemsEquipped.FirstOrDefault(item => item.SlotId == Containers.FaceCover); if (existingFaceCover is not null) { return new ChooseRandomCompatibleModResult @@ -456,7 +484,7 @@ public class BotGeneratorHelper( // Does item being checked get blocked/block existing item if (itemToEquip.Properties.BlocksEarpiece.GetValueOrDefault(false)) { - var existingEarpiece = itemsEquipped.FirstOrDefault(item => item.SlotId == "Earpiece"); + var existingEarpiece = itemsEquipped.FirstOrDefault(item => item.SlotId == Containers.Earpiece); if (existingEarpiece is not null) { return new ChooseRandomCompatibleModResult @@ -472,7 +500,7 @@ public class BotGeneratorHelper( // Does item being checked get blocked/block existing item if (itemToEquip.Properties.BlocksArmorVest.GetValueOrDefault(false)) { - var existingArmorVest = itemsEquipped.FirstOrDefault(item => item.SlotId == "ArmorVest"); + var existingArmorVest = itemsEquipped.FirstOrDefault(item => item.SlotId == Containers.ArmorVest); if (existingArmorVest is not null) { return new ChooseRandomCompatibleModResult @@ -507,10 +535,7 @@ public class BotGeneratorHelper( protected bool HasBlockingProperty(TemplateItem? item, string blockingPropertyName) { - return item?.Properties?.GetType().GetProperties() - .FirstOrDefault(x => x.PropertyType == typeof(bool) - && x.Name.ToLower() == blockingPropertyName - && (bool) x.GetValue(item.Properties)) is not null; + return item != null && item.Blocks.TryGetValue(blockingPropertyName, out var blocks) && blocks; } /// @@ -520,11 +545,8 @@ public class BotGeneratorHelper( /// Equipment role (e.g. pmc / assault / bossTagilla) public string GetBotEquipmentRole(string botRole) { - string[] pmcs = [_pmcConfig.UsecType.ToLower(), _pmcConfig.BearType.ToLower()]; - return pmcs.Contains( - botRole.ToLower() - ) - ? "pmc" + return _pmcTypes.Contains(botRole, StringComparer.OrdinalIgnoreCase) + ? Sides.PmcEquipmentRole : botRole; } @@ -532,7 +554,7 @@ public class BotGeneratorHelper( /// Adds an item with all its children into specified equipmentSlots, wherever it fits. /// /// Slot to add item+children into - /// Root item id to use as mod items parentid + /// Root item id to use as mod items parentId /// Root itms tpl id /// Item to add /// Inventory to add item+children into diff --git a/Libraries/SPTarkov.Server.Core/Helpers/BotHelper.cs b/Libraries/SPTarkov.Server.Core/Helpers/BotHelper.cs index a1294e20..7d3eced0 100644 --- a/Libraries/SPTarkov.Server.Core/Helpers/BotHelper.cs +++ b/Libraries/SPTarkov.Server.Core/Helpers/BotHelper.cs @@ -1,6 +1,7 @@ using System.Collections.Concurrent; using System.Collections.Frozen; using SPTarkov.Common.Annotations; +using SPTarkov.Server.Core.Constants; using SPTarkov.Server.Core.Models.Eft.Common.Tables; using SPTarkov.Server.Core.Models.Spt.Config; using SPTarkov.Server.Core.Models.Utils; @@ -18,10 +19,17 @@ public class BotHelper( ConfigServer _configServer ) { - protected static readonly FrozenSet _pmcTypeIds = ["usec", "bear", "pmc", "pmcbear", "pmcusec"]; - protected BotConfig _botConfig = _configServer.GetConfig(); - protected PmcConfig _pmcConfig = _configServer.GetConfig(); - protected ConcurrentDictionary> _pmcNameCache = new(); + private static readonly FrozenSet _pmcTypeIds = + [ + Sides.Usec.ToLower(), + Sides.Bear.ToLower(), + Sides.PmcBear.ToLower(), + Sides.PmcUsec.ToLower() + ]; + + private readonly BotConfig _botConfig = _configServer.GetConfig(); + private readonly PmcConfig _pmcConfig = _configServer.GetConfig(); + private readonly ConcurrentDictionary> _pmcNameCache = new(); /// /// Get a template object for the specified botRole from bots.types db @@ -163,12 +171,12 @@ public class BotHelper( { if (string.Equals(_pmcConfig.BearType, botRole, StringComparison.OrdinalIgnoreCase)) { - return "Bear"; + return Sides.Bear; } - if (string.Equals(_pmcConfig.UsecType, botRole.ToLower(), StringComparison.OrdinalIgnoreCase)) + if (string.Equals(_pmcConfig.UsecType, botRole, StringComparison.OrdinalIgnoreCase)) { - return "Usec"; + return Sides.Usec; } return GetRandomizedPmcSide(); @@ -180,7 +188,7 @@ public class BotHelper( /// pmc side as string protected string GetRandomizedPmcSide() { - return _randomUtil.GetChance100(_pmcConfig.IsUsec) ? "Usec" : "Bear"; + return _randomUtil.GetChance100(_pmcConfig.IsUsec) ? Sides.Usec : Sides.Bear; } /// @@ -191,14 +199,14 @@ public class BotHelper( /// name of PMC public string GetPmcNicknameOfMaxLength(int maxLength, string? side = null) { - var chosenFaction = (side ?? (_randomUtil.GetInt(0, 1) == 0 ? "usec" : "bear")).ToLowerInvariant(); + var chosenFaction = (side ?? (_randomUtil.GetInt(0, 1) == 0 ? Sides.Usec : Sides.Bear)).ToLowerInvariant(); var cacheKey = $"{chosenFaction}{maxLength}"; if (!_pmcNameCache.TryGetValue(cacheKey, out var eligibleNames)) { if (!_databaseService.GetBots().Types.TryGetValue(chosenFaction, out var chosenFactionDetails)) { - _logger.Error($"Unknown faction: {chosenFaction} Defaulting to: USEC"); - chosenFaction = "usec"; + _logger.Error($"Unknown faction: {chosenFaction} Defaulting to: {Sides.Usec}"); + chosenFaction = Sides.Usec.ToLower(); chosenFactionDetails = _databaseService.GetBots().Types[chosenFaction]; } @@ -206,7 +214,7 @@ public class BotHelper( if (!matchingNames.Any()) { _logger.Warning( - $"Unable to filter: {chosenFaction} PMC names to only those under: {maxLength}, none found that match that criteria, selecting from entire name pool instead`,\n" + $"Unable to filter: {chosenFaction} PMC names to only those under: {maxLength}, none found that match that criteria, selecting from entire name pool instead" ); // Return a random string from names diff --git a/Libraries/SPTarkov.Server.Core/Helpers/DurabilityLimitsHelper.cs b/Libraries/SPTarkov.Server.Core/Helpers/DurabilityLimitsHelper.cs index 0bc5e1b4..bcb84c44 100644 --- a/Libraries/SPTarkov.Server.Core/Helpers/DurabilityLimitsHelper.cs +++ b/Libraries/SPTarkov.Server.Core/Helpers/DurabilityLimitsHelper.cs @@ -110,7 +110,7 @@ public class DurabilityLimitsHelper( var roleExistsInConfig = _botConfig.Durability.BotDurabilities.ContainsKey(botRole); if (!roleExistsInConfig) { - _logger.Warning($"{botRole} doesn't exist in bot config durability values, using default fallback"); + _logger.Debug($"{botRole} doesn't exist in bot config durability values, using default fallback"); return "default"; } diff --git a/Libraries/SPTarkov.Server.Core/Models/Eft/Common/Tables/TemplateItem.cs b/Libraries/SPTarkov.Server.Core/Models/Eft/Common/Tables/TemplateItem.cs index 2406841a..5aee09c0 100644 --- a/Libraries/SPTarkov.Server.Core/Models/Eft/Common/Tables/TemplateItem.cs +++ b/Libraries/SPTarkov.Server.Core/Models/Eft/Common/Tables/TemplateItem.cs @@ -1,4 +1,5 @@ using System.Text.Json.Serialization; +using SPTarkov.Server.Core.Constants; using SPTarkov.Server.Core.Models.Enums; using SPTarkov.Server.Core.Utils.Json.Converters; @@ -6,6 +7,8 @@ namespace SPTarkov.Server.Core.Models.Eft.Common.Tables; public record TemplateItem { + private Dictionary? _blocks; + private string? _id; private string? _name; @@ -87,6 +90,27 @@ public record TemplateItem _prototype = string.Intern(value); } } + + /// + /// Used for easy access during bot generation to any slot/container this item is blocking. + /// + [JsonIgnore] + public Dictionary Blocks + { + get + { + return _blocks ??= new Dictionary() + { + { Containers.LeftStance, Properties?.BlockLeftStance ?? false }, + { Containers.Collapsible, Properties?.BlocksCollapsible ?? false }, + { Containers.Earpiece, Properties?.BlocksEarpiece ?? false }, + { Containers.Eyewear, Properties?.BlocksEyewear ?? false }, + { Containers.FaceCover, Properties?.BlocksFaceCover ?? false }, + { Containers.Folding, Properties?.BlocksFolding ?? false }, + { Containers.Headwear, Properties?.BlocksHeadwear ?? false } + }; + } + } } public record Props diff --git a/Libraries/SPTarkov.Server.Core/Services/BotLootCacheService.cs b/Libraries/SPTarkov.Server.Core/Services/BotLootCacheService.cs index c7e33407..549b28ac 100644 --- a/Libraries/SPTarkov.Server.Core/Services/BotLootCacheService.cs +++ b/Libraries/SPTarkov.Server.Core/Services/BotLootCacheService.cs @@ -8,6 +8,7 @@ using SPTarkov.Server.Core.Models.Enums; using SPTarkov.Server.Core.Models.Spt.Bots; using SPTarkov.Server.Core.Models.Utils; using SPTarkov.Server.Core.Utils.Cloners; +using LogLevel = SPTarkov.Server.Core.Models.Spt.Logging.LogLevel; namespace SPTarkov.Server.Core.Services; @@ -17,7 +18,6 @@ public class BotLootCacheService( ItemHelper _itemHelper, PMCLootGenerator _pmcLootGenerator, LocalisationService _localisationService, - RagfairPriceService _ragfairPriceService, ICloner _cloner ) { @@ -580,7 +580,10 @@ public class BotLootCacheService( ) ) { - _logger.Info($"Unable to add loot cache for bot role: {botRole} - already exists"); + if (_logger.IsLogEnabled(LogLevel.Debug)) + { + _logger.Debug($"Unable to add loot cache for bot role: {botRole} - already exists"); + } } } diff --git a/Libraries/SPTarkov.Server.Core/Services/LocalisationService.cs b/Libraries/SPTarkov.Server.Core/Services/LocalisationService.cs index 1fd2d5ff..ae87ca7a 100644 --- a/Libraries/SPTarkov.Server.Core/Services/LocalisationService.cs +++ b/Libraries/SPTarkov.Server.Core/Services/LocalisationService.cs @@ -84,9 +84,14 @@ public class LocalisationService /// Locale text public string GetRandomTextThatMatchesPartialKey(string partialKey) { - var values = _localeService.GetLocaleKeysThatStartsWithValue(partialKey); - var chosenKey = _randomUtil.GetArrayValue(values); + var matchingKeys = GetKeys().Where(x => x.Contains(partialKey)).ToList(); + if (!matchingKeys.Any()) + { + _logger.Warning($"No locale keys found for: {partialKey}"); - return GetText(chosenKey); + return string.Empty; + } + + return GetText(_randomUtil.GetArrayValue(matchingKeys)); } } diff --git a/Libraries/SPTarkov.Server.Core/Services/MatchBotDetailsCacheService.cs b/Libraries/SPTarkov.Server.Core/Services/MatchBotDetailsCacheService.cs index f03077aa..08c151b3 100644 --- a/Libraries/SPTarkov.Server.Core/Services/MatchBotDetailsCacheService.cs +++ b/Libraries/SPTarkov.Server.Core/Services/MatchBotDetailsCacheService.cs @@ -1,5 +1,6 @@ using System.Collections.Concurrent; using SPTarkov.Common.Annotations; +using SPTarkov.Server.Core.Constants; using SPTarkov.Server.Core.Models.Eft.Common.Tables; using SPTarkov.Server.Core.Models.Enums; using SPTarkov.Server.Core.Models.Spt.Bots; @@ -8,7 +9,7 @@ using SPTarkov.Server.Core.Models.Utils; namespace SPTarkov.Server.Core.Services; /// -/// Cache bots in a dictionary, keyed by the bots name, keying by name isnt ideal as its not unique but this is used by the post-raid system which doesnt have any bot ids, only name +/// Cache bots in a dictionary, keyed by the bots ID /// [Injectable(InjectionType.Singleton)] public class MatchBotDetailsCacheService( @@ -17,14 +18,14 @@ public class MatchBotDetailsCacheService( { private static readonly HashSet _sidesToCache = [ - "pmcUSEC", - "pmcBEAR" + Sides.PmcUsec, + Sides.PmcBear ]; protected readonly ConcurrentDictionary BotDetailsCache = new(); /// - /// Store a bot in the cache, keyed by its name. + /// Store a bot in the cache, keyed by its ID. /// /// Bot details to cache public void CacheBot(BotBase botToCache) @@ -49,7 +50,7 @@ public class MatchBotDetailsCacheService( BotDetailsCache.TryAdd(botToCache.Id, new BotDetailsForChatMessages() { Nickname = botToCache.Info.Nickname.Trim(), - Side = botToCache.Info.Side == "pmcUSEC" ? DogtagSide.Usec : DogtagSide.Bear, + Side = botToCache.Info.Side == Sides.PmcUsec ? DogtagSide.Usec : DogtagSide.Bear, Aid = botToCache.Aid, Type = botToCache.Info.MemberCategory, Level = botToCache.Info.Level, @@ -65,10 +66,9 @@ public class MatchBotDetailsCacheService( } /// - /// Find a bot in the cache by its name and side. + /// Find a bot in the cache by its ID. /// - /// Name of bot to find - /// Side of the bot + /// ID of bot to find /// public BotDetailsForChatMessages? GetBotById(string? id) {