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 <clandestine984@gmail.com> Co-authored-by: Chomp <dev@dev.sp-tarkov.com>
This commit is contained in:
@@ -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";
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace SPTarkov.Server.Core.Constants;
|
||||
|
||||
public class Roles
|
||||
{
|
||||
public const string Assault = "assault";
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace SPTarkov.Server.Core.Constants;
|
||||
|
||||
public static class Slots
|
||||
{
|
||||
public const string Dogtag = "Dogtag";
|
||||
}
|
||||
@@ -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(
|
||||
/// <returns>List of generated bots</returns>
|
||||
protected List<BotBase> GenerateBotWaves(GenerateBotsRequestData request, PmcData? pmcProfile, string sessionId)
|
||||
{
|
||||
var result = new List<BotBase>();
|
||||
|
||||
var generatedBotList = new List<BotBase>();
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -222,9 +217,14 @@ public class BotController(
|
||||
/// </summary>
|
||||
/// <param name="generateRequest"></param>
|
||||
/// <param name="botGenerationDetails"></param>
|
||||
/// <param name="botList">List of bots to fill</param>
|
||||
/// <param name="sessionId">Session/Player id</param>
|
||||
/// <returns></returns>
|
||||
protected List<BotBase> GenerateBotWave(GenerateCondition generateRequest, BotGenerationDetails botGenerationDetails, string sessionId)
|
||||
protected void GenerateBotWave(
|
||||
GenerateCondition generateRequest,
|
||||
BotGenerationDetails botGenerationDetails,
|
||||
List<BotBase> 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<BotBase>();
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -314,17 +318,15 @@ public class BotController(
|
||||
/// <param name="pmcProfile">Player who is generating bots</param>
|
||||
/// <param name="allPmcsHaveSameNameAsPlayer">Should all PMCs have same name as player</param>
|
||||
/// <param name="raidSettings">Settings chosen pre-raid by player in client</param>
|
||||
/// <param name="botCountToGenerate">How many bots to generate</param>
|
||||
/// <param name="generateAsPmc">Force bot being generated to be a PMC</param>
|
||||
/// <returns>BotGenerationDetails</returns>
|
||||
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,
|
||||
|
||||
@@ -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(
|
||||
/// <returns>True if name should be simulated pscav</returns>
|
||||
public bool ShouldSimulatePlayerScav(string botRole)
|
||||
{
|
||||
return botRole == "assault" && _randomUtil.GetChance100(_botConfig.ChanceAssaultScavHasPlayerScavName);
|
||||
return botRole == Roles.Assault && _randomUtil.GetChance100(_botConfig.ChanceAssaultScavHasPlayerScavName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -504,7 +506,7 @@ public class BotGenerator(
|
||||
BodyParts = new Dictionary<string, BodyPartHealth>
|
||||
{
|
||||
{
|
||||
"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(
|
||||
/// <returns>item tpl</returns>
|
||||
public string GetDogtagTplByGameVersionAndSide(string side, string gameVersion)
|
||||
{
|
||||
if (string.Equals(side, "usec", StringComparison.OrdinalIgnoreCase))
|
||||
if (string.Equals(side, Sides.Usec, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
switch (gameVersion)
|
||||
{
|
||||
|
||||
@@ -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<BotGeneratorHelper> _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<string> _slotsWithNoCompatIssues = ["Scabbard", "Backpack", "SecureContainer", "Holster", "ArmBand"];
|
||||
protected BotConfig _botConfig = _configServer.GetConfig<BotConfig>();
|
||||
protected PmcConfig _pmcConfig = _configServer.GetConfig<PmcConfig>();
|
||||
private static readonly FrozenSet<string> _slotsWithNoCompatIssues = [
|
||||
EquipmentSlots.Scabbard.ToString(),
|
||||
EquipmentSlots.Backpack.ToString(),
|
||||
EquipmentSlots.SecuredContainer.ToString(),
|
||||
EquipmentSlots.Holster.ToString(),
|
||||
EquipmentSlots.ArmBand.ToString()
|
||||
];
|
||||
|
||||
private readonly BotConfig _botConfig;
|
||||
private readonly ISptLogger<BotGeneratorHelper> _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<BotGeneratorHelper> 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<BotConfig>();
|
||||
var pmcConfig = configServer.GetConfig<PmcConfig>();
|
||||
_pmcTypes = [pmcConfig.UsecType.ToLower(), pmcConfig.BearType.ToLower()];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -520,11 +545,8 @@ public class BotGeneratorHelper(
|
||||
/// <returns>Equipment role (e.g. pmc / assault / bossTagilla)</returns>
|
||||
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.
|
||||
/// </summary>
|
||||
/// <param name="equipmentSlots">Slot to add item+children into</param>
|
||||
/// <param name="rootItemId">Root item id to use as mod items parentid</param>
|
||||
/// <param name="rootItemId">Root item id to use as mod items parentId</param>
|
||||
/// <param name="rootItemTplId">Root itms tpl id</param>
|
||||
/// <param name="itemWithChildren">Item to add</param>
|
||||
/// <param name="inventory">Inventory to add item+children into</param>
|
||||
|
||||
@@ -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<string> _pmcTypeIds = ["usec", "bear", "pmc", "pmcbear", "pmcusec"];
|
||||
protected BotConfig _botConfig = _configServer.GetConfig<BotConfig>();
|
||||
protected PmcConfig _pmcConfig = _configServer.GetConfig<PmcConfig>();
|
||||
protected ConcurrentDictionary<string, List<string>> _pmcNameCache = new();
|
||||
private static readonly FrozenSet<string> _pmcTypeIds =
|
||||
[
|
||||
Sides.Usec.ToLower(),
|
||||
Sides.Bear.ToLower(),
|
||||
Sides.PmcBear.ToLower(),
|
||||
Sides.PmcUsec.ToLower()
|
||||
];
|
||||
|
||||
private readonly BotConfig _botConfig = _configServer.GetConfig<BotConfig>();
|
||||
private 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
|
||||
@@ -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(
|
||||
/// <returns>pmc side as string</returns>
|
||||
protected string GetRandomizedPmcSide()
|
||||
{
|
||||
return _randomUtil.GetChance100(_pmcConfig.IsUsec) ? "Usec" : "Bear";
|
||||
return _randomUtil.GetChance100(_pmcConfig.IsUsec) ? Sides.Usec : Sides.Bear;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -191,14 +199,14 @@ public class BotHelper(
|
||||
/// <returns>name of PMC</returns>
|
||||
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
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
@@ -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<string, bool>? _blocks;
|
||||
|
||||
private string? _id;
|
||||
|
||||
private string? _name;
|
||||
@@ -87,6 +90,27 @@ public record TemplateItem
|
||||
_prototype = string.Intern(value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used for easy access during bot generation to any slot/container this item is blocking.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public Dictionary<string, bool> Blocks
|
||||
{
|
||||
get
|
||||
{
|
||||
return _blocks ??= new Dictionary<string, bool>()
|
||||
{
|
||||
{ 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
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -84,9 +84,14 @@ public class LocalisationService
|
||||
/// <returns> Locale text </returns>
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// </summary>
|
||||
[Injectable(InjectionType.Singleton)]
|
||||
public class MatchBotDetailsCacheService(
|
||||
@@ -17,14 +18,14 @@ public class MatchBotDetailsCacheService(
|
||||
{
|
||||
private static readonly HashSet<string> _sidesToCache =
|
||||
[
|
||||
"pmcUSEC",
|
||||
"pmcBEAR"
|
||||
Sides.PmcUsec,
|
||||
Sides.PmcBear
|
||||
];
|
||||
|
||||
protected readonly ConcurrentDictionary<string, BotDetailsForChatMessages> BotDetailsCache = new();
|
||||
|
||||
/// <summary>
|
||||
/// Store a bot in the cache, keyed by its name.
|
||||
/// Store a bot in the cache, keyed by its ID.
|
||||
/// </summary>
|
||||
/// <param name="botToCache"> Bot details to cache </param>
|
||||
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(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find a bot in the cache by its name and side.
|
||||
/// Find a bot in the cache by its ID.
|
||||
/// </summary>
|
||||
/// <param name="botName"> Name of bot to find </param>
|
||||
/// <param name="botSide"> Side of the bot </param>
|
||||
/// <param name="id"> ID of bot to find </param>
|
||||
/// <returns></returns>
|
||||
public BotDetailsForChatMessages? GetBotById(string? id)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user