using System.Collections.Concurrent; using System.Collections.Frozen; using SPTarkov.Server.Core.Constants; using SPTarkov.DI.Annotations; using SPTarkov.Server.Core.Models.Eft.Common.Tables; using SPTarkov.Server.Core.Models.Spt.Config; using SPTarkov.Server.Core.Models.Utils; using SPTarkov.Server.Core.Servers; using SPTarkov.Server.Core.Services; using SPTarkov.Server.Core.Utils; namespace SPTarkov.Server.Core.Helpers; [Injectable] public class BotHelper( ISptLogger _logger, DatabaseService _databaseService, RandomUtil _randomUtil, ConfigServer _configServer ) { 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 /// /// botRole to get template for /// BotType object public BotType? GetBotTemplate(string role) { if (!_databaseService.GetBots().Types.TryGetValue(role?.ToLower(), out var bot)) { _logger.Error($"Unable to get bot of type: {role} from DB"); return null; } return bot; } /// /// Is the passed in bot role a PMC (USEC/Bear/PMC) /// /// bot role to check /// true if is pmc public bool IsBotPmc(string? botRole) { return _pmcTypeIds.Contains(botRole?.ToLower()); } public bool IsBotBoss(string botRole) { return !IsBotFollower(botRole) && _botConfig.Bosses.Any(x => string.Equals(x, botRole, StringComparison.CurrentCultureIgnoreCase)); } public bool IsBotFollower(string botRole) { return botRole?.StartsWith("follower", StringComparison.CurrentCultureIgnoreCase) ?? false; } public bool IsBotZombie(string botRole) { return botRole?.StartsWith("infected", StringComparison.CurrentCultureIgnoreCase) ?? false; } /// /// Add a bot to the FRIENDLY_BOT_TYPES list /// /// bot settings to alter /// bot type to add to friendly list public void AddBotToFriendlyList(DifficultyCategories difficultySettings, string typeToAdd) { const string friendlyBotTypesKey = "FRIENDLY_BOT_TYPES"; // Null guard if (difficultySettings.Mind[friendlyBotTypesKey] is null) { difficultySettings.Mind[friendlyBotTypesKey] = new List(); } ((List) difficultySettings.Mind[friendlyBotTypesKey]).Add(typeToAdd); } /// /// Add a bot to the REVENGE_BOT_TYPES list /// /// bot settings to alter /// bot type to add to revenge list public void AddBotToRevengeList(DifficultyCategories difficultySettings, string[] typesToAdd) { const string revengePropKey = "REVENGE_BOT_TYPES"; // Nothing to add if (typesToAdd is null) { return; } // Null guard if (difficultySettings.Mind[revengePropKey] is null) { difficultySettings.Mind[revengePropKey] = new List(); } var revengeArray = (List) difficultySettings.Mind[revengePropKey]; foreach (var botTypeToAdd in typesToAdd) { if (!revengeArray.Contains(botTypeToAdd)) { revengeArray.Add(botTypeToAdd); } } } /// /// is the provided role a PMC, case-agnostic /// /// Role to check /// True if role is PMC public bool BotRoleIsPmc(string botRole) { HashSet listToCheck = [_pmcConfig.UsecType.ToLower(), _pmcConfig.BearType.ToLower()]; return listToCheck.Contains( botRole.ToLower() ); } /// /// Get randomization settings for bot from config/bot.json /// /// level of bot /// bot equipment json /// RandomisationDetails public RandomisationDetails GetBotRandomizationDetails(int botLevel, EquipmentFilters botEquipConfig) { // No randomisation details found, skip if (botEquipConfig is null || botEquipConfig.Randomisation is null) { return null; } return botEquipConfig.Randomisation.FirstOrDefault(randDetails => botLevel >= randDetails.LevelRange.Min && botLevel <= randDetails.LevelRange.Max ); } /// /// Choose between pmcBEAR and pmcUSEC at random based on the % defined in pmcConfig.isUsec /// /// pmc role public string GetRandomizedPmcRole() { return _randomUtil.GetChance100(_pmcConfig.IsUsec) ? _pmcConfig.UsecType : _pmcConfig.BearType; } /// /// Get the corresponding side when pmcBEAR or pmcUSEC is passed in /// /// role to get side for /// side (usec/bear) public string GetPmcSideByRole(string botRole) { if (string.Equals(_pmcConfig.BearType, botRole, StringComparison.OrdinalIgnoreCase)) { return Sides.Bear; } if (string.Equals(_pmcConfig.UsecType, botRole, StringComparison.OrdinalIgnoreCase)) { return Sides.Usec; } return GetRandomizedPmcSide(); } /// /// Get a randomized PMC side based on bot config value 'isUsec' /// /// pmc side as string protected string GetRandomizedPmcSide() { return _randomUtil.GetChance100(_pmcConfig.IsUsec) ? Sides.Usec : Sides.Bear; } /// /// Get a name from a PMC that fits the desired length /// /// Max length of name, inclusive /// OPTIONAL - what side PMC to get name from (usec/bear) /// name of PMC public string GetPmcNicknameOfMaxLength(int maxLength, string? side = null) { 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: {Sides.Usec}"); chosenFaction = Sides.Usec.ToLower(); chosenFactionDetails = _databaseService.GetBots().Types[chosenFaction]; } var matchingNames = chosenFactionDetails.FirstNames.Where(name => name.Length <= maxLength).ToList(); 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" ); // Return a random string from names return _randomUtil.GetCollectionValue(chosenFactionDetails.FirstNames); } _pmcNameCache.TryAdd(cacheKey, matchingNames); eligibleNames = matchingNames; } return _randomUtil.GetCollectionValue(eligibleNames); } }