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(); }