Skip to content

Commit

Permalink
Use chances instead of interpolation for group sizes and bot difficul…
Browse files Browse the repository at this point in the history
…ties. Added array validation.
  • Loading branch information
dwesterwick committed Feb 14, 2025
1 parent dc06e99 commit 217c2c2
Show file tree
Hide file tree
Showing 20 changed files with 251 additions and 57 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -353,8 +353,8 @@ Since normal AI Limit mods will disable bots that are questing (which will preve
* **fraction_of_max_players**: When determining how many total bots of this type will spawn throughout the raid, the maximum player count for the map is multiplied by this value. This is **1** by default for PMC's and **1.5** by default for player Scavs.
* **fraction_of_max_players_vs_raidET**: If you spawn late into the raid as a Scav, the minimum and maximum initial PMC's will be reduced by a factor determined by this array. The array contains [fraction of raid time remaining, fraction of max players] pairs, and there is no limit to the number of pairs.
* **time_randomness**: The maximum percentage of total raid time (before reducing it for Scav raids) that player-Scav spawns can be randomly adjusted when generating a spawn schedule for them. However, player Scavs will never be allowed to spawn earlier than the minimum reduced raid time in the SPT configuration for the map, and they will never be allowed to spawn later than the maximum reduced raid time for the map. This is **10%** by default.
* **bots_per_group_distribution**: An array describing how likely bot groups of various sizes are allowed to spawn. When generating bot groups, this mod will select a random number for each group between 0 and 1. It will then use interpolation to determine how many bots to add to the group using this array. The first column is the look-up value for the random number selected for the group, and the second column is the number of bots to add to the group. The interpolated value for number of bots is rounded to the nearest integer.
* **bot_difficulty_as_online**: An array describing the chances that members of a new bot group will be of a certain difficulty. When generating bot groups, this mod will select a random number for each group between 0 and 1. It will then use interpolation to determine the difficulty of all bots in the group using this array. The first column is the look-up value for the random number selected for the group, and the second column is a number corresponding to the difficulty that will be used (0 = easy, 1 = normal, 2 = hard, 3 = impossible). The interpolated value for number of bots is rounded to the nearest integer.
* **bots_per_group_distribution**: An array describing the chances that bot groups of various sizes are allowed to spawn. The left column is the bot group size, and the right column is the chance that a group of that size will spawn.
* **bot_difficulty_as_online**: An array describing the chances that members of a new bot group will be of a certain difficulty. The left column is the difficulty value (0 = easy, 1 = normal, 2 = hard, 3 = impossible), and the right column is the chance that bots with that difficulty will spawn.

**---------- Known Issues ----------**

Expand Down
5 changes: 3 additions & 2 deletions bepinex_dev/SPTQuestingBots/Components/LocationData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,8 @@ public void UpdateMaxTotalBots()
{
MaxTotalBots = botmax;
}
LoggingController.LogInfo("Max total bots on the map (" + CurrentLocation.Id + ") at the same time: " + MaxTotalBots);

//LoggingController.LogInfo("Max total bots on the map (" + CurrentLocation.Id + ") at the same time: " + MaxTotalBots);
}

public bool IsPointOnLightkeeperIsland(Vector3 position)
Expand Down Expand Up @@ -191,7 +192,7 @@ public void FindAllLockedDoors()
}
}

LoggingController.LogInfo("Found " + areLockedDoorsUnlocked.Count + " locked doors");
LoggingController.LogDebug("Found " + areLockedDoorsUnlocked.Count + " locked doors");
//LoggingController.LogInfo("Found locked doors: " + string.Join(", ", areLockedDoorsUnlocked.Select(s => s.Key.Id)));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ public abstract class BotGenerator : MonoBehaviour
public int MaxAliveBots { get; protected set; } = 10;
public float RetryTimeSeconds { get; protected set; } = 10;

protected List<SpawnPointParams> PendingSpawnPoints = new List<SpawnPointParams>();

protected readonly List<Models.BotSpawnInfo> BotGroups = new List<Models.BotSpawnInfo>();
private readonly Stopwatch retrySpawnTimer = Stopwatch.StartNew();
private readonly Stopwatch updateTimer = Stopwatch.StartNew();
Expand All @@ -38,8 +40,6 @@ public abstract class BotGenerator : MonoBehaviour
public static int CurrentBotGeneratorProgress { get; private set; } = 0;
public static string CurrentBotGeneratorType { get; private set; } = "???";

protected List<SpawnPointParams> PendingSpawnPoints = new List<SpawnPointParams>();

private static Task botGenerationTask = null;
private static readonly List<Func<Task>> botGeneratorList = new List<Func<Task>>();
private static readonly Dictionary<Func<BotGenerator>, bool> registeredBotGenerators = new Dictionary<Func<BotGenerator>, bool>();
Expand Down Expand Up @@ -509,7 +509,7 @@ protected BotDifficulty GetBotDifficulty(EFT.Bots.EBotDifficulty raidDifficulty,
return raidDifficulty.ToBotDifficulty();
}

return (BotDifficulty)Math.Round(ConfigController.InterpolateForFirstCol(difficultyChances, random.NextDouble()));
return (BotDifficulty)Math.Round(ConfigController.GetValueFromTotalChanceFraction(difficultyChances, random.NextDouble()));
}

private IEnumerator spawnBotGroups(Models.BotSpawnInfo[] botGroups)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ protected override int GetMaxGeneratedBots()
double groupSizeFactor = ConfigController.InterpolateForFirstCol(ConfigController.Config.BotSpawns.PMCs.FractionOfMaxPlayersVsRaidET, RaidHelpers.GetRaidTimeRemainingFraction());

// Determine how many bots to spawn in the group, but do not exceed the maximum number of bots allowed to spawn
int botsInGroup = (int)Math.Round(ConfigController.InterpolateForFirstCol(ConfigController.Config.BotSpawns.PMCs.BotsPerGroupDistribution, random.NextDouble()));
int botsInGroup = (int)Math.Round(ConfigController.GetValueFromTotalChanceFraction(ConfigController.Config.BotSpawns.PMCs.BotsPerGroupDistribution, random.NextDouble()));
botsInGroup = (int)Math.Ceiling(botsInGroup * Math.Min(1, groupSizeFactor));
botsInGroup = (int)Math.Min(botsInGroup, MaxBotsToGenerate);
botsInGroup = (int)Math.Max(botsInGroup, 1);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ protected override int GetNumberOfBotsAllowedToSpawn()
protected override async Task<Models.BotSpawnInfo> GenerateBotGroupTask()
{
// Determine how many bots to spawn in the group, but do not exceed the maximum number of bots allowed to spawn
int botsInGroup = (int)Math.Round(ConfigController.InterpolateForFirstCol(ConfigController.Config.BotSpawns.PScavs.BotsPerGroupDistribution, random.NextDouble()));
int botsInGroup = (int)Math.Round(ConfigController.GetValueFromTotalChanceFraction(ConfigController.Config.BotSpawns.PScavs.BotsPerGroupDistribution, random.NextDouble()));
botsInGroup = (int)Math.Min(botsInGroup, MaxBotsToGenerate);
botsInGroup = (int)Math.Max(botsInGroup, 1);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -769,7 +769,7 @@ public static void WriteQuestLogFile(long timestamp)
return;
}

LoggingController.LogInfo("Writing quest log file...");
LoggingController.LogDebug("Writing quest log file...");

if (allQuests.Count == 0)
{
Expand Down Expand Up @@ -819,7 +819,7 @@ public static void WriteBotJobAssignmentLogFile(long timestamp)
return;
}

LoggingController.LogInfo("Writing bot job assignment log file...");
LoggingController.LogDebug("Writing bot job assignment log file...");

if (botJobAssignments.Count == 0)
{
Expand Down
44 changes: 44 additions & 0 deletions bepinex_dev/SPTQuestingBots/Controllers/ConfigController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,8 @@ public static bool TryDeserializeObject<T>(string json, string errorMessage, out

public static double InterpolateForFirstCol(double[][] array, double value)
{
validateArray(array);

if (array.Length == 1)
{
return array.Last()[1];
Expand All @@ -277,5 +279,47 @@ public static double InterpolateForFirstCol(double[][] array, double value)

return array.Last()[1];
}

public static double GetValueFromTotalChanceFraction(double[][] array, double fraction)
{
validateArray(array);

if (array.Length == 1)
{
return array.Last()[1];
}

double chancesSum = array.Sum(x => x[1]);
double targetCumulativeChances = chancesSum * fraction;

int i = 0;
double cumulativeChances = 0;
while (i < array.Length)
{
cumulativeChances += array[i][1];

if (cumulativeChances > targetCumulativeChances)
{
return array[i][0];
}

i++;
}

return array.Last()[0];
}

private static void validateArray(double[][] array)
{
if (array.Length == 0)
{
throw new ArgumentOutOfRangeException("The array must have at least one row.");
}

if (array.Any(x => x.Length != 2))
{
throw new ArgumentOutOfRangeException("All rows in the array must have two columns.");
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ public static void CreateLogFile(string logName, string filename, string content

File.WriteAllText(filename, content);

LogInfo("Writing " + logName + " log file...done.");
LogDebug("Writing " + logName + " log file...done.");
}
catch (Exception e)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

namespace SPTQuestingBots.Patches
{
public class AddActivePlayerPatch : ModulePatch
public class BotsControllerSetSettingsPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
Expand All @@ -23,7 +23,7 @@ protected static void PatchPostfix()
{
if (Singleton<GameWorld>.Instance.gameObject.TryGetComponent(out Components.LocationData oldLocationData))
{
LoggingController.LogWarning("There is already a LocationData component added to the current GameWorld instance.");
LoggingController.LogError("There is already a LocationData component added to the current GameWorld instance.");
return;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,14 @@ protected static void PatchPostfix()
// Stop updating debug overlays
if (Singleton<GameWorld>.Instance.gameObject.TryGetComponent(out Components.DebugData debugController))
{
LoggingController.LogInfo("Disabling " + debugController.GetType().FullName + "...");
//LoggingController.LogInfo("Disabling " + debugController.GetType().FullName + "...");
debugController.enabled = false;
}

// Disable all bot generators
foreach (Components.Spawning.BotGenerator botGenerator in Singleton<GameWorld>.Instance.gameObject.GetComponents(typeof(Components.Spawning.BotGenerator)))
{
LoggingController.LogInfo("Disabling " + botGenerator.GetType().FullName + "...");
//LoggingController.LogInfo("Disabling " + botGenerator.GetType().FullName + "...");
botGenerator.enabled = false;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
using System.Text;
using System.Threading.Tasks;
using EFT;
using EFT.Interactive;
using SPT.Reflection.Patching;
using SPTQuestingBots.Helpers;

Expand Down
2 changes: 1 addition & 1 deletion bepinex_dev/SPTQuestingBots/QuestingBotsPlugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ protected void Awake()
LoggingController.LogInfo("Loading QuestingBots...enabling patches...");

new Patches.TarkovInitPatch().Enable();
new Patches.AddActivePlayerPatch().Enable();
new Patches.BotsControllerSetSettingsPatch().Enable();
new Patches.BotsControllerStopPatch().Enable();
new Patches.BotOwnerBrainActivatePatch().Enable();
new Patches.IsFollowerSuitableForBossPatch().Enable();
Expand Down
2 changes: 1 addition & 1 deletion bepinex_dev/SPTQuestingBots/SPTQuestingBots.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@
<Compile Include="Patches\Spawning\ScavLimits\SpawnPointIsValidPatch.cs" />
<Compile Include="Patches\Spawning\ScavLimits\TrySpawnFreeAndDelayPatch.cs" />
<Compile Include="Patches\TarkovInitPatch.cs" />
<Compile Include="Patches\AddActivePlayerPatch.cs" />
<Compile Include="Patches\BotsControllerSetSettingsPatch.cs" />
<Compile Include="Patches\BotsControllerStopPatch.cs" />
<Compile Include="Patches\Spawning\Advanced\ExceptAIPatch.cs" />
<Compile Include="Patches\Spawning\GameStartPatch.cs" />
Expand Down
36 changes: 18 additions & 18 deletions config/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -327,17 +327,17 @@
[1, 1]
],
"bots_per_group_distribution" : [
[0.40, 1],
[0.80, 2],
[0.94, 3],
[0.98, 4],
[1.00, 5]
[1, 40],
[2, 30],
[3, 22],
[4, 5],
[5, 3]
],
"bot_difficulty_as_online" : [
[0.00, 0],
[0.50, 1],
[0.90, 2],
[1.00, 3]
[0, 25],
[1, 50],
[2, 20],
[3, 5]
]
},
"player_scavs": {
Expand All @@ -349,17 +349,17 @@
"fraction_of_max_players": 1.5,
"time_randomness": 10,
"bots_per_group_distribution" : [
[0.80, 1],
[0.90, 2],
[0.96, 3],
[0.99, 4],
[1.00, 5]
[1, 75],
[2, 16],
[3, 5],
[4, 3],
[5, 1]
],
"bot_difficulty_as_online" : [
[0.00, 0],
[0.60, 1],
[0.95, 2],
[1.00, 3]
[0, 33],
[1, 65],
[2, 10],
[3, 2]
]
}
},
Expand Down
Binary file modified dist/BepInEx/plugins/DanW-SPTQuestingBots/SPTQuestingBots.dll
Binary file not shown.
Binary file modified dist/DanW-SPTQuestingBots.zip
Binary file not shown.
4 changes: 2 additions & 2 deletions dist/user/mods/DanW-SPTQuestingBots/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -353,8 +353,8 @@ Since normal AI Limit mods will disable bots that are questing (which will preve
* **fraction_of_max_players**: When determining how many total bots of this type will spawn throughout the raid, the maximum player count for the map is multiplied by this value. This is **1** by default for PMC's and **1.5** by default for player Scavs.
* **fraction_of_max_players_vs_raidET**: If you spawn late into the raid as a Scav, the minimum and maximum initial PMC's will be reduced by a factor determined by this array. The array contains [fraction of raid time remaining, fraction of max players] pairs, and there is no limit to the number of pairs.
* **time_randomness**: The maximum percentage of total raid time (before reducing it for Scav raids) that player-Scav spawns can be randomly adjusted when generating a spawn schedule for them. However, player Scavs will never be allowed to spawn earlier than the minimum reduced raid time in the SPT configuration for the map, and they will never be allowed to spawn later than the maximum reduced raid time for the map. This is **10%** by default.
* **bots_per_group_distribution**: An array describing how likely bot groups of various sizes are allowed to spawn. When generating bot groups, this mod will select a random number for each group between 0 and 1. It will then use interpolation to determine how many bots to add to the group using this array. The first column is the look-up value for the random number selected for the group, and the second column is the number of bots to add to the group. The interpolated value for number of bots is rounded to the nearest integer.
* **bot_difficulty_as_online**: An array describing the chances that members of a new bot group will be of a certain difficulty. When generating bot groups, this mod will select a random number for each group between 0 and 1. It will then use interpolation to determine the difficulty of all bots in the group using this array. The first column is the look-up value for the random number selected for the group, and the second column is a number corresponding to the difficulty that will be used (0 = easy, 1 = normal, 2 = hard, 3 = impossible). The interpolated value for number of bots is rounded to the nearest integer.
* **bots_per_group_distribution**: An array describing the chances that bot groups of various sizes are allowed to spawn. The left column is the bot group size, and the right column is the chance that a group of that size will spawn.
* **bot_difficulty_as_online**: An array describing the chances that members of a new bot group will be of a certain difficulty. The left column is the difficulty value (0 = easy, 1 = normal, 2 = hard, 3 = impossible), and the right column is the chance that bots with that difficulty will spawn.

**---------- Known Issues ----------**

Expand Down
36 changes: 18 additions & 18 deletions dist/user/mods/DanW-SPTQuestingBots/config/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -327,17 +327,17 @@
[1, 1]
],
"bots_per_group_distribution" : [
[0.40, 1],
[0.80, 2],
[0.94, 3],
[0.98, 4],
[1.00, 5]
[1, 40],
[2, 30],
[3, 22],
[4, 5],
[5, 3]
],
"bot_difficulty_as_online" : [
[0.00, 0],
[0.50, 1],
[0.90, 2],
[1.00, 3]
[0, 25],
[1, 50],
[2, 20],
[3, 5]
]
},
"player_scavs": {
Expand All @@ -349,17 +349,17 @@
"fraction_of_max_players": 1.5,
"time_randomness": 10,
"bots_per_group_distribution" : [
[0.80, 1],
[0.90, 2],
[0.96, 3],
[0.99, 4],
[1.00, 5]
[1, 75],
[2, 16],
[3, 5],
[4, 3],
[5, 1]
],
"bot_difficulty_as_online" : [
[0.00, 0],
[0.60, 1],
[0.95, 2],
[1.00, 3]
[0, 33],
[1, 65],
[2, 10],
[3, 2]
]
}
},
Expand Down
Loading

0 comments on commit 217c2c2

Please sign in to comment.