diff --git a/Core/Generators/BotGenerator.cs b/Core/Generators/BotGenerator.cs
index 74e66e31..73971d05 100644
--- a/Core/Generators/BotGenerator.cs
+++ b/Core/Generators/BotGenerator.cs
@@ -309,25 +309,33 @@ public class BotGenerator
}
///
- /// Should this bot have a name like "name (Pmc Name)" and be alterd by client patch to be hostile to player
+ /// Should this bot have a name like "name (Pmc Name)" and be altered by client patch to be hostile to player
///
/// Role bot has
/// True if name should be simulated pscav
public bool ShouldSimulatePlayerScav(string botRole)
{
- throw new NotImplementedException();
+ return botRole == "assault" && _randomUtil.GetChance100(_botConfig.ChanceAssaultScavHasPlayerScavName);
}
///
/// Get exp for kill by bot difficulty
///
- /// Dict of difficulties and experience
+ /// Dict of difficulties and experience
/// the killed bots difficulty
/// Role of bot (optional, used for error logging)
/// Experience for kill
- public int GetExperienceRewardForKillByDifficulty(Dictionary experience, string botDifficulty, string role)
+ public double GetExperienceRewardForKillByDifficulty(Dictionary experiences, string botDifficulty, string role)
{
- throw new NotImplementedException();
+ var result = experiences[botDifficulty.ToLower()];
+ if (result is null)
+ {
+ _logger.Debug("Unable to find experience for kill value for: ${ role} ${ botDifficulty}, falling back to `normal`");
+
+ return _randomUtil.GetDouble(experiences["normal"].Min.Value, experiences["normal"].Max.Value);
+ }
+
+ return _randomUtil.GetDouble(result.Min.Value, result.Max.Value);
}
///
@@ -337,9 +345,16 @@ public class BotGenerator
/// Difficulty of bot to look up
/// Role of bot (optional, used for error logging)
/// Standing change value
- public int GetStandingChangeForKillByDifficulty(Dictionary standingForKill, string botDifficulty, string role)
+ public double GetStandingChangeForKillByDifficulty(Dictionary standingsForKill, string botDifficulty, string role)
{
- throw new NotImplementedException();
+ if (!standingsForKill.TryGetValue(botDifficulty.ToLower(), out var result))
+ {
+ _logger.Warning($"Unable to find standing for kill value for: {role} {botDifficulty}, falling back to `normal`");
+
+ return standingsForKill["normal"];
+ }
+
+ return result;
}
///
@@ -349,9 +364,16 @@ public class BotGenerator
/// Difficulty of bot to look up
/// Role of bot (optional, used for error logging)
/// Standing change value
- public int GetAgressorBonusByDifficulty(Dictionary aggressorBonus, string botDifficulty, string role)
+ public double GetAgressorBonusByDifficulty(Dictionary aggressorBonuses, string botDifficulty, string role)
{
- throw new NotImplementedException();
+ if (!aggressorBonuses.TryGetValue(botDifficulty.ToLower(), out var result))
+ {
+ _logger.Warning($"Unable to find aggressor bonus for kill value for: {role} {botDifficulty}, falling back to `normal`");
+
+ return aggressorBonuses["normal"];
+ }
+
+ return result;
}
///
@@ -361,7 +383,24 @@ public class BotGenerator
/// Generation details of bot
public void FilterBlacklistedGear(BotType botJsonTemplate, BotGenerationDetails botGenerationDetails)
{
- throw new NotImplementedException();
+ var blacklist = _botEquipmentFilterService.GetBotEquipmentBlacklist(
+ _botGeneratorHelper.GetBotEquipmentRole(botGenerationDetails.Role),
+ botGenerationDetails.PlayerLevel.Value);
+
+ if (blacklist?.Gear is null)
+ {
+ // Nothing to filter by
+ return;
+ }
+
+ foreach (var equipmentKvP in blacklist.Gear) {
+ var equipmentDict = botJsonTemplate.BotInventory.Equipment[equipmentKvP.Key];
+
+ foreach (var blacklistedTpl in equipmentKvP.Value) {
+ // Set weighting to 0, will never be picked
+ equipmentDict[blacklistedTpl] = 0;
+ }
+ }
}
///
@@ -370,7 +409,10 @@ public class BotGenerator
/// Bot data to adjust
public void AddAdditionalPocketLootWeightsForUnheardBot(BotType botJsonTemplate)
{
- throw new NotImplementedException();
+ // Adjust pocket loot weights to allow for 5 or 6 items
+ var pocketWeights = botJsonTemplate.BotGeneration.Items["pocketLoot"].Weights;
+ pocketWeights["5"] = 1;
+ pocketWeights["6"] = 1;
}
///
diff --git a/Core/Generators/BotLevelGenerator.cs b/Core/Generators/BotLevelGenerator.cs
index 810e2ec5..6f5dc9dd 100644
--- a/Core/Generators/BotLevelGenerator.cs
+++ b/Core/Generators/BotLevelGenerator.cs
@@ -1,16 +1,32 @@
-using Core.Annotations;
+using Core.Annotations;
using Core.Models.Common;
using Core.Models.Eft.Bot;
using Core.Models.Eft.Common.Tables;
using Core.Models.Spt.Bots;
+using Core.Services;
+using Core.Utils;
+using ILogger = Core.Models.Utils.ILogger;
namespace Core.Generators;
[Injectable]
public class BotLevelGenerator
{
- public BotLevelGenerator()
+ private readonly ILogger _logger;
+ private readonly RandomUtil _randomUtil;
+ private readonly MathUtil _mathUtil;
+ private readonly DatabaseService _databaseService;
+
+ public BotLevelGenerator(
+ ILogger logger,
+ RandomUtil randomUtil,
+ MathUtil mathUtil,
+ DatabaseService databaseService)
{
+ _logger = logger;
+ _randomUtil = randomUtil;
+ _mathUtil = mathUtil;
+ _databaseService = databaseService;
}
///
@@ -22,12 +38,29 @@ public class BotLevelGenerator
/// IRandomisedBotLevelResult object
public RandomisedBotLevelResult GenerateBotLevel(MinMax levelDetails, BotGenerationDetails botGenerationDetails, BotBase bot)
{
- throw new NotImplementedException();
+ var expTable = _databaseService.GetGlobals().Configuration.Exp.Level.ExperienceTable;
+ var botLevelRange = GetRelativeBotLevelRange(botGenerationDetails, levelDetails, expTable.Length);
+
+ // Get random level based on the exp table.
+ double exp = 0;
+ var level = int.Parse(ChooseBotLevel(botLevelRange.Min.Value, botLevelRange.Max.Value, 1, 1.15).ToString()); // TODO - nasty double to string to int conversion
+ for (var i = 0; i < level; i++)
+ {
+ exp += expTable[i].Experience.Value;
+ }
+
+ // Sprinkle in some random exp within the level, unless we are at max level.
+ if (level < expTable.Length - 1)
+ {
+ exp += _randomUtil.GetDouble(0, expTable[level].Experience.Value - 1);
+ }
+
+ return new RandomisedBotLevelResult{ Level = level, Exp = exp };
}
- public int ChooseBotLevel(int min, int max, int shift, int number)
+ public double ChooseBotLevel(double min, double max, int shift, double number)
{
- throw new NotImplementedException();
+ return _randomUtil.GetBiasedRandomNumber(min, max, shift, number);
}
///
@@ -39,6 +72,30 @@ public class BotLevelGenerator
/// A MinMax of the lowest and highest level to generate the bots
public MinMax GetRelativeBotLevelRange(BotGenerationDetails botGenerationDetails, MinMax levelDetails, int maxAvailableLevel)
{
- throw new NotImplementedException();
+ var isPmc = botGenerationDetails.IsPmc.GetValueOrDefault(false);
+ var pmcOverride = botGenerationDetails.LocationSpecificPmcLevelOverride;
+
+ var minPossibleLevel = isPmc && pmcOverride is not null
+ ? Math.Min(
+ Math.Max(levelDetails.Min.Value, pmcOverride.Min.Value), // Biggest between json min and the botgen min
+ maxAvailableLevel // Fallback if value above is crazy (default is 79)
+ )
+ : Math.Min(levelDetails.Min.Value, maxAvailableLevel); // Not pmc with override or non-pmc
+
+ var maxPossibleLevel = isPmc && pmcOverride is not null
+ ? Math.Min(pmcOverride.Max.Value, maxAvailableLevel) // Was a PMC and they have a level override
+ : Math.Min(levelDetails.Max.Value, maxAvailableLevel); // Not pmc with override or non-pmc
+
+ var minLevel = botGenerationDetails.PlayerLevel.Value - botGenerationDetails.BotRelativeLevelDeltaMin.Value;
+ var maxLevel = botGenerationDetails.PlayerLevel.Value + botGenerationDetails.BotRelativeLevelDeltaMin.Value;
+
+ // Bound the level to the min/max possible
+ maxLevel = Math.Min(Math.Max(maxLevel, minPossibleLevel), maxPossibleLevel);
+ minLevel = Math.Min(Math.Max(minLevel, minPossibleLevel), maxPossibleLevel);
+
+ return new MinMax{
+ Min = minLevel,
+ Max = maxLevel,
+ };
}
}
diff --git a/Core/Models/Eft/Bot/RandomisedBotLevelResult.cs b/Core/Models/Eft/Bot/RandomisedBotLevelResult.cs
index a64f7b35..d9187556 100644
--- a/Core/Models/Eft/Bot/RandomisedBotLevelResult.cs
+++ b/Core/Models/Eft/Bot/RandomisedBotLevelResult.cs
@@ -1,12 +1,12 @@
-using System.Text.Json.Serialization;
+using System.Text.Json.Serialization;
namespace Core.Models.Eft.Bot;
public class RandomisedBotLevelResult
{
[JsonPropertyName("level")]
- public int? Level { get; set; }
+ public double? Level { get; set; }
[JsonPropertyName("exp")]
- public int? Exp { get; set; }
-}
\ No newline at end of file
+ public double? Exp { get; set; }
+}
diff --git a/Core/Models/Eft/Common/Globals.cs b/Core/Models/Eft/Common/Globals.cs
index f1c7609c..880d7786 100644
--- a/Core/Models/Eft/Common/Globals.cs
+++ b/Core/Models/Eft/Common/Globals.cs
@@ -1,4 +1,4 @@
-using Core.Models.Eft.Common.Tables;
+using Core.Models.Eft.Common.Tables;
using Core.Models.Eft.Hideout;
namespace Core.Models.Eft.Common;
@@ -1255,7 +1255,7 @@ public class Level
public class ExpTable
{
[JsonPropertyName("exp")]
- public int? Experience { get; set; }
+ public double? Experience { get; set; }
}
public class LootAttempt
diff --git a/Core/Services/BotEquipmentFilterService.cs b/Core/Services/BotEquipmentFilterService.cs
index 1c8ea8a0..0ed34870 100644
--- a/Core/Services/BotEquipmentFilterService.cs
+++ b/Core/Services/BotEquipmentFilterService.cs
@@ -74,7 +74,7 @@ public class BotEquipmentFilterService
/// Role of the bot we want the blacklist for
/// Level of the player
/// EquipmentBlacklistDetails object
- public EquipmentFilterDetails GetBotEquipmentBlacklist(string botRole, int playerLevel)
+ public EquipmentFilterDetails GetBotEquipmentBlacklist(string botRole, double playerLevel)
{
throw new NotImplementedException();
}