211 lines
7.5 KiB
C#
211 lines
7.5 KiB
C#
using System.Collections.Concurrent;
|
|
using System.Collections.Frozen;
|
|
using SPTarkov.DI.Annotations;
|
|
using SPTarkov.Server.Core.Constants;
|
|
using SPTarkov.Server.Core.Models.Eft.Common;
|
|
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<BotHelper> logger, DatabaseService databaseService, RandomUtil randomUtil, ConfigServer configServer)
|
|
{
|
|
private static readonly FrozenSet<string> _pmcTypeIds =
|
|
[
|
|
Sides.Usec.ToLowerInvariant(),
|
|
Sides.Bear.ToLowerInvariant(),
|
|
Sides.PmcBear.ToLowerInvariant(),
|
|
Sides.PmcUsec.ToLowerInvariant(),
|
|
];
|
|
|
|
protected readonly BotConfig BotConfig = configServer.GetConfig<BotConfig>();
|
|
protected readonly PmcConfig PMCConfig = configServer.GetConfig<PmcConfig>();
|
|
private readonly ConcurrentDictionary<string, List<string>> _pmcNameCache = new();
|
|
|
|
/// <summary>
|
|
/// Get a template object for the specified botRole from bots.types db
|
|
/// </summary>
|
|
/// <param name="role">botRole to get template for</param>
|
|
/// <returns>BotType object</returns>
|
|
public BotType? GetBotTemplate(string role)
|
|
{
|
|
if (!databaseService.GetBots().Types.TryGetValue(role.ToLowerInvariant(), out var bot))
|
|
{
|
|
logger.Error($"Unable to get bot of type: {role} from DB");
|
|
|
|
return null;
|
|
}
|
|
|
|
return bot;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Is the passed in bot role a PMC (USEC/Bear/PMC)
|
|
/// </summary>
|
|
/// <param name="botRole">bot role to check</param>
|
|
/// <returns>true if is pmc</returns>
|
|
public bool IsBotPmc(string? botRole)
|
|
{
|
|
return _pmcTypeIds.Contains(botRole?.ToLowerInvariant() ?? string.Empty);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
public bool IsBotZombie(string botRole)
|
|
{
|
|
return botRole.StartsWith("infected", StringComparison.CurrentCultureIgnoreCase);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get randomization settings for bot from config/bot.json
|
|
/// </summary>
|
|
/// <param name="botLevel">level of bot</param>
|
|
/// <param name="botEquipConfig">bot equipment json</param>
|
|
/// <returns>RandomisationDetails</returns>
|
|
public RandomisationDetails? GetBotRandomizationDetails(int botLevel, EquipmentFilters botEquipConfig)
|
|
{
|
|
// No randomisation details found, skip
|
|
|
|
return botEquipConfig.Randomisation?.FirstOrDefault(randDetails =>
|
|
botLevel >= randDetails.LevelRange.Min && botLevel <= randDetails.LevelRange.Max
|
|
);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the corresponding side when pmcBEAR or pmcUSEC is passed in
|
|
/// </summary>
|
|
/// <param name="botRole">role to get side for</param>
|
|
/// <returns>side (usec/bear)</returns>
|
|
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();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the corresponding side when pmcBEAR or pmcUSEC is passed in
|
|
/// </summary>
|
|
/// <param name="botRole">role to get side for</param>
|
|
/// <returns>side (usec/bear)</returns>
|
|
public string GetPmcSideByRole(WildSpawnType botRole)
|
|
{
|
|
switch (botRole)
|
|
{
|
|
case WildSpawnType.pmcBEAR:
|
|
return Sides.Bear;
|
|
case WildSpawnType.pmcUSEC:
|
|
return Sides.Usec;
|
|
default:
|
|
return GetRandomizedPmcSide();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get a randomized PMC side based on bot config value 'isUsec'
|
|
/// </summary>
|
|
/// <returns>pmc side as string</returns>
|
|
protected string GetRandomizedPmcSide()
|
|
{
|
|
return randomUtil.GetChance100(PMCConfig.IsUsec) ? Sides.Usec : Sides.Bear;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get a PMC name that fits the desired length
|
|
/// </summary>
|
|
/// <param name="maxLength">Max length of name, inclusive</param>
|
|
/// <param name="side">OPTIONAL - what side PMC to get name from (usec/bear)</param>
|
|
/// <returns>name of PMC</returns>
|
|
public string GetPmcNicknameOfMaxLength(int maxLength, string? side = null)
|
|
{
|
|
var chosenFaction = GetPmcFactionBySide(side);
|
|
var eligibleNames = GetOrAddEligiblePmcNamesFromCache(chosenFaction, maxLength);
|
|
|
|
return randomUtil.GetRandomElement(eligibleNames);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Choose a faction based on the passed in side (usec/bear) or choose randomly if not provided
|
|
/// </summary>
|
|
/// <param name="side">usec/bear</param>
|
|
/// <returns>usec/bear</returns>
|
|
protected string GetPmcFactionBySide(string? side)
|
|
{
|
|
var chosenFaction = (side ?? (randomUtil.GetInt(0, 1) == 0 ? Sides.Usec : Sides.Bear)).ToLowerInvariant();
|
|
return chosenFaction;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Cache Pmcs against their length and faction, return values that match requirement
|
|
/// </summary>
|
|
/// <param name="chosenFaction">bear/usec</param>
|
|
/// <param name="maxLength">Max length of name</param>
|
|
/// <returns>Collection of names</returns>
|
|
protected List<string> GetOrAddEligiblePmcNamesFromCache(string chosenFaction, int maxLength)
|
|
{
|
|
var cacheKey = $"{chosenFaction}{maxLength.ToString()}";
|
|
if (_pmcNameCache.TryGetValue(cacheKey, out var eligibleNames))
|
|
{
|
|
// Exists in cache, return
|
|
return eligibleNames;
|
|
}
|
|
|
|
// Not found in cache, generate and store
|
|
var generatedNames = GatherPmcNamesOfLength(chosenFaction, maxLength);
|
|
_pmcNameCache.TryAdd(cacheKey, generatedNames);
|
|
|
|
return generatedNames;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get names that match the side and length defined in parameters
|
|
/// </summary>
|
|
/// <param name="chosenFaction">bear/usec</param>
|
|
/// <param name="maxLength">max length of name to return</param>
|
|
/// <returns></returns>
|
|
protected List<string> GatherPmcNamesOfLength(string chosenFaction, int maxLength)
|
|
{
|
|
// Ensure faction is legit before gathering
|
|
if (!databaseService.GetBots().Types.TryGetValue(chosenFaction, out var chosenFactionDetails))
|
|
{
|
|
logger.Error($"Unknown faction: {chosenFaction} Defaulting to: {Sides.Usec}");
|
|
chosenFaction = Sides.Usec.ToLowerInvariant();
|
|
chosenFactionDetails = databaseService.GetBots().Types[chosenFaction];
|
|
}
|
|
|
|
var matchingNames = chosenFactionDetails.FirstNames.Where(name => name.Length <= maxLength).ToList();
|
|
if (matchingNames.Count != 0)
|
|
{
|
|
return matchingNames;
|
|
}
|
|
|
|
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 chosenFactionDetails.FirstNames.ToList();
|
|
}
|
|
}
|