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)
{