Skip to content

Commit

Permalink
Adjusted Scav limiter, fixed DONUTS detection
Browse files Browse the repository at this point in the history
  • Loading branch information
dwesterwick committed Feb 13, 2025
1 parent dc3a4fc commit 39212dd
Show file tree
Hide file tree
Showing 13 changed files with 81 additions and 26 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,8 @@ Since normal AI Limit mods will disable bots that are questing (which will preve
* **bot_spawns.spawn_retry_time**: If any bots fail to spawn, no other attempts will be made to spawn more of them for this amount of time (in seconds). By default, this is **10** s.
* **bot_spawns.delay_game_start_until_bot_gen_finishes**: After the final loading screen shows "0:00.000" for a few seconds, the game will be further delayed from starting if not all bots have been generated. Without doing this, PMC's may not spawn immediately when the raid starts, and the remaining bots will take much longer to generate. This is **true** by default.
* **bot_spawns.spawn_initial_bosses_first**: If initial bosses must spawn before PMC's are allowed to spawn. This does not apply to Factory (Day or Night). This is **false** by default.
* **bot_spawns.non_wave_retry_delay_after_blocked**: If Scavs are blocked by Questing Bots (per the F12 menu settings) from spawning via EFT's "new" spawning system, delay the next spawn check by this many seconds. This does not have an effect on Scav spawning behavior, but it will reduce server load by limiting how many extra bots it will generate between spawn checks. This is **180** s by default.
* **bot_spawns.eft_new_spawn_system_adjustments.non_wave_retry_delay_after_blocked**: If Scavs are blocked by Questing Bots (per the F12 menu settings) from spawning via EFT's "new" spawning system, delay the next spawn check by this many seconds. This does not have an effect on Scav spawning behavior, but it will reduce server load by limiting how many extra bots it will generate between spawn checks. This is **180** s by default.
* **bot_spawns.eft_new_spawn_system_adjustments.scav_spawn_rate_time_window**: If Scavs are blocked by Questing Bots (per the F12 menu settings) from spawning via EFT's "new" spawning system, calculate the Scav spawn rate by tracking all Scav spawns within this time window (in seconds) from the current time in the raid. This is **300** s by default.
* **bot_spawns.bot_cap_adjustments.use_EFT_bot_caps**: SPT's bot caps will be changed to match EFT's bot caps. This is **true** by default.
* **bot_spawns.bot_cap_adjustments.only_decrease_bot_caps**: If **bot_spawns.bot_cap_adjustments.use_EFT_bot_caps=true**, SPT's bot caps will be changed to match EFT's bot caps only if EFT's bot caps are lower. This is **true** by default.
* **bot_spawns.bot_cap_adjustments.map_specific_adjustments**: If **bot_spawns.bot_cap_adjustments.use_EFT_bot_caps=true**, these additional adjustments will be made to SPT's bot caps after changing them to EFT's. This is used to balance bot spawns and performance.
Expand Down
4 changes: 2 additions & 2 deletions bepinex_dev/SPTQuestingBots/Configuration/BotSpawnsConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ public class BotSpawnsConfig
[JsonProperty("spawn_initial_bosses_first")]
public bool SpawnInitialBossesFirst { get; set; } = true;

[JsonProperty("non_wave_retry_delay_after_blocked")]
public float NonWaveRetryDelayAfterBlocked { get; set; } = 20;
[JsonProperty("eft_new_spawn_system_adjustments")]
public EftNewSpawnSystemAdjustmentsConfig EftNewSpawnSystemAdjustments { get; set; } = new EftNewSpawnSystemAdjustmentsConfig();

[JsonProperty("bot_cap_adjustments")]
public BotCapAdjustmentsConfig BotCapAdjustments { get; set; } = new BotCapAdjustmentsConfig();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SPTQuestingBots.Configuration
{
public class EftNewSpawnSystemAdjustmentsConfig
{
[JsonProperty("non_wave_retry_delay_after_blocked")]
public float NonWaveRetryDelayAfterBlocked { get; set; } = 20;

[JsonProperty("scav_spawn_rate_time_window")]
public float ScavSpawnRateTimeWindow { get; set; } = 300;

public EftNewSpawnSystemAdjustmentsConfig()
{

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

namespace SPTQuestingBots.Patches.Spawning.ScavLimits
{
Expand All @@ -19,7 +21,13 @@ public class NonWavesSpawnScenarioCreatePatch : ModulePatch

protected override MethodBase GetTargetMethod()
{
return typeof(NonWavesSpawnScenario).GetMethod("smethod_0", BindingFlags.Public | BindingFlags.Static);
MethodInfo methodInfo = typeof(NonWavesSpawnScenario)
.GetMethods(BindingFlags.Public | BindingFlags.Static)
.First(m => m.HasAllParameterTypes(new Type[] { typeof(AbstractGame) }));

Controllers.LoggingController.LogInfo("Found method for NonWavesSpawnScenarioCreatePatch: " + methodInfo.Name);

return methodInfo;
}

[PatchPostfix]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,7 @@ namespace SPTQuestingBots.Patches.Spawning.ScavLimits
{
public class TrySpawnFreeAndDelayPatch : ModulePatch
{
private const float TIME_WINDOW_TO_CHECK = 60f * 3f;

private static FieldInfo nextRetryTimeField = null;
private static FieldInfo nextRetryTimeDelayField = null;

private enum ScavSpawnBlockReason
{
Expand All @@ -35,7 +33,7 @@ private enum ScavSpawnBlockReason

protected override MethodBase GetTargetMethod()
{
nextRetryTimeField = AccessTools.Field(typeof(NonWavesSpawnScenario), "float_2");
nextRetryTimeDelayField = AccessTools.Field(typeof(NonWavesSpawnScenario), "float_2");

return typeof(BotSpawner).GetMethod(nameof(BotSpawner.TrySpawnFreeAndDelay), BindingFlags.Public | BindingFlags.Instance);
}
Expand All @@ -54,7 +52,7 @@ protected static bool PatchPrefix(BotCreationDataClass data)
}

// Check how many Scavs are queued to spawn, and allow all other roles to spawn
int pendingScavCount = data.Profiles.Count(p => isScavProfile(p));
int pendingScavCount = data.Profiles.Count(p => isNormalScavProfile(p));
if (pendingScavCount == 0)
{
return allowSpawn(pendingScavCount);
Expand All @@ -68,7 +66,7 @@ protected static bool PatchPrefix(BotCreationDataClass data)
}

// Ensure the maximum alive count for Scavs will not be exceeded
int totalAliveScavs = Singleton<GameWorld>.Instance.AllAlivePlayersList.Count(p => isScavProfile(p.Profile));
int totalAliveScavs = Singleton<GameWorld>.Instance.AllAlivePlayersList.Count(p => isNormalScavProfile(p.Profile));
if (totalAliveScavs + pendingScavCount > QuestingBotsPluginConfig.ScavMaxAliveLimit.Value)
{
return blockSpawn(pendingScavCount, ScavSpawnBlockReason.MaxAliveScavs);
Expand All @@ -80,45 +78,60 @@ protected static bool PatchPrefix(BotCreationDataClass data)
return allowSpawn(pendingScavCount);
}

// The bot cap for Factory is sometimes set at 0, in which case the Scav spawn rate limit cannot be used
// The bot cap for Factory is sometimes set at 0, in which case the Scav spawn rate limit cannot be used (assuming the new spawning system is enabled)
if (locationData.MaxTotalBots == 0)
{
allowSpawn(pendingScavCount);
}

int recentlySpawnedScavs = NonWavesSpawnScenarioCreatePatch.GetSpawnedScavCount(TIME_WINDOW_TO_CHECK, true);
float recentScavSpawnRate = recentlySpawnedScavs * 60f / TIME_WINDOW_TO_CHECK;
float timeWindow = ConfigController.Config.BotSpawns.EftNewSpawnSystemAdjustments.ScavSpawnRateTimeWindow;
int recentlySpawnedScavs = NonWavesSpawnScenarioCreatePatch.GetSpawnedScavCount(timeWindow, true);
float recentScavSpawnRate = recentlySpawnedScavs * 60f / timeWindow;

// Prevent too many Scavs from spawning in a short period of time
if (recentScavSpawnRate > QuestingBotsPluginConfig.ScavSpawnRateLimit.Value)
if (recentScavSpawnRate >= QuestingBotsPluginConfig.ScavSpawnRateLimit.Value)
{
return blockSpawn(pendingScavCount, ScavSpawnBlockReason.ScavRateLimit);
}

return allowSpawn(pendingScavCount);
}

private static bool isScavProfile(Profile profile)
private static bool isNormalScavProfile(Profile profile)
{
return !profile.WillBeAPlayerScav() && scavRoles.Contains(profile.Info.Settings.Role);
}

private static bool allowSpawn(int scavCount)
{
NonWavesSpawnScenarioCreatePatch.AddSpawnedScavs(scavCount);

if (scavCount > 0)
{
logScavSpawnRate();
}

return true;
}

private static bool blockSpawn(int scavCount, ScavSpawnBlockReason reason)
{
Controllers.LoggingController.LogWarning("Prevented " + scavCount + " Scav(s) from spawning due to: " + reason.ToString());
Controllers.LoggingController.LogWarning("Prevented " + scavCount + " Scav(s) from spawning due to: " + reason.ToString(), true);
logScavSpawnRate();

int recentlySpawnedScavs = NonWavesSpawnScenarioCreatePatch.GetSpawnedScavCount(TIME_WINDOW_TO_CHECK, true);
float recentScavSpawnRate = recentlySpawnedScavs * 60f / TIME_WINDOW_TO_CHECK;
Controllers.LoggingController.LogWarning(recentlySpawnedScavs + " Scavs have spawned in the last " + TIME_WINDOW_TO_CHECK + "s. Rate=" + recentScavSpawnRate);
float retryDelay = ConfigController.Config.BotSpawns.EftNewSpawnSystemAdjustments.NonWaveRetryDelayAfterBlocked;
nextRetryTimeDelayField.SetValue(NonWavesSpawnScenarioCreatePatch.MostRecentNonWavesSpawnScenario, retryDelay);

nextRetryTimeField.SetValue(NonWavesSpawnScenarioCreatePatch.MostRecentNonWavesSpawnScenario, ConfigController.Config.BotSpawns.NonWaveRetryDelayAfterBlocked);
return false;
}

private static void logScavSpawnRate()
{
float timeWindow = ConfigController.Config.BotSpawns.EftNewSpawnSystemAdjustments.ScavSpawnRateTimeWindow;
int recentlySpawnedScavs = NonWavesSpawnScenarioCreatePatch.GetSpawnedScavCount(timeWindow, true);
float recentScavSpawnRate = recentlySpawnedScavs * 60f / timeWindow;

Controllers.LoggingController.LogWarning(recentlySpawnedScavs + " Scavs have spawned in the last " + timeWindow + "s. Rate=" + recentScavSpawnRate, true);
}
}
}
4 changes: 3 additions & 1 deletion bepinex_dev/SPTQuestingBots/Patches/TarkovInitPatch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ public class TarkovInitPatch : ModulePatch
public static string MinVersion { get; set; } = "0.0.0.0";
public static string MaxVersion { get; set; } = "999999.999999.999999.999999";

private static readonly string donutsGuid = "com.dvize.Donuts";

protected override MethodBase GetTargetMethod()
{
return typeof(TarkovApplication).GetMethod(nameof(TarkovApplication.Init), BindingFlags.Public | BindingFlags.Instance);
Expand Down Expand Up @@ -51,7 +53,7 @@ protected static void PatchPostfix(IAssetsManager assetsManager, InputTree input
Chainloader.DependencyErrors.Add(errorMessage);
}

if (ConfigController.Config.BotSpawns.Enabled && Chainloader.PluginInfos.Any(p => p.Value.Metadata.Name.ToLower().Contains("dvize.donuts")))
if (ConfigController.Config.BotSpawns.Enabled && Chainloader.PluginInfos.Any(p => p.Value.Metadata.GUID == donutsGuid))
{
Chainloader.DependencyErrors.Add("Using Questing Bots spawns with DONUTS may result in too many spawns. Use at your own risk.");
}
Expand Down
4 changes: 2 additions & 2 deletions bepinex_dev/SPTQuestingBots/QuestingBotsPluginConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,9 @@ public static void BuildConfigOptions(ConfigFile Config)
ScavSpawningExclusionRadiusMapFraction = Config.Bind("Scav Spawn Restrictions", "Map Fraction for Scav Spawning Exclusion Radius",
0.1f, new ConfigDescription("Adjusts the distance (relative to the map size) that Scavs are allowed to spawn near human players, PMC's, and player Scavs", new AcceptableValueRange<float>(0.01f, 0.15f)));
ScavSpawnRateLimit = Config.Bind("Scav Spawn Restrictions", "Permitted Scav Spawn Rate",
3f, new ConfigDescription("After the Scav spawn threshold is exceeded, only this number of Scavs will be allowed to spawn per minute (on average)", new AcceptableValueRange<float>(0.5f, 6f)));
2.5f, new ConfigDescription("After the Scav spawn threshold is exceeded, only this number of Scavs will be allowed to spawn per minute (on average)", new AcceptableValueRange<float>(0.5f, 6f)));
ScavSpawnLimitThreshold = Config.Bind("Scav Spawn Restrictions", "Threshold for Scav Spawn Rate Limit",
15, new ConfigDescription("The Scav spawn rate limit will only be active after this many Scavs spawn in the raid", new AcceptableValueRange<int>(1, 50)));
10, new ConfigDescription("The Scav spawn rate limit will only be active after this many Scavs spawn in the raid", new AcceptableValueRange<int>(1, 50)));
ScavMaxAliveLimit = Config.Bind("Scav Spawn Restrictions", "Max Alive Scavs",
15, new ConfigDescription("The maximum number of Scavs that can be alive at the same time (including Sniper Scavs)", new AcceptableValueRange<int>(5, 25)));
}
Expand Down
1 change: 1 addition & 0 deletions bepinex_dev/SPTQuestingBots/SPTQuestingBots.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@
<Compile Include="Configuration\BotTypeValueConfig.cs" />
<Compile Include="Configuration\BrainLayerPrioritiesConfig.cs" />
<Compile Include="Configuration\DistanceAngleConfig.cs" />
<Compile Include="Configuration\EftNewSpawnSystemAdjustmentsConfig.cs" />
<Compile Include="Configuration\ExtractionRequirementsConfig.cs" />
<Compile Include="Configuration\HearingSensorConfig.cs" />
<Compile Include="Configuration\LightkeeperIslandQuestsConfig.cs" />
Expand Down
5 changes: 4 additions & 1 deletion config/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,10 @@
"spawn_retry_time": 10,
"delay_game_start_until_bot_gen_finishes": true,
"spawn_initial_bosses_first": false,
"non_wave_retry_delay_after_blocked": 180,
"eft_new_spawn_system_adjustments" : {
"non_wave_retry_delay_after_blocked": 180,
"scav_spawn_rate_time_window": 300
},
"bot_cap_adjustments": {
"use_EFT_bot_caps": true,
"only_decrease_bot_caps": true,
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.
3 changes: 2 additions & 1 deletion dist/user/mods/DanW-SPTQuestingBots/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,8 @@ Since normal AI Limit mods will disable bots that are questing (which will preve
* **bot_spawns.spawn_retry_time**: If any bots fail to spawn, no other attempts will be made to spawn more of them for this amount of time (in seconds). By default, this is **10** s.
* **bot_spawns.delay_game_start_until_bot_gen_finishes**: After the final loading screen shows "0:00.000" for a few seconds, the game will be further delayed from starting if not all bots have been generated. Without doing this, PMC's may not spawn immediately when the raid starts, and the remaining bots will take much longer to generate. This is **true** by default.
* **bot_spawns.spawn_initial_bosses_first**: If initial bosses must spawn before PMC's are allowed to spawn. This does not apply to Factory (Day or Night). This is **false** by default.
* **bot_spawns.non_wave_retry_delay_after_blocked**: If Scavs are blocked by Questing Bots (per the F12 menu settings) from spawning via EFT's "new" spawning system, delay the next spawn check by this many seconds. This does not have an effect on Scav spawning behavior, but it will reduce server load by limiting how many extra bots it will generate between spawn checks. This is **180** s by default.
* **bot_spawns.eft_new_spawn_system_adjustments.non_wave_retry_delay_after_blocked**: If Scavs are blocked by Questing Bots (per the F12 menu settings) from spawning via EFT's "new" spawning system, delay the next spawn check by this many seconds. This does not have an effect on Scav spawning behavior, but it will reduce server load by limiting how many extra bots it will generate between spawn checks. This is **180** s by default.
* **bot_spawns.eft_new_spawn_system_adjustments.scav_spawn_rate_time_window**: If Scavs are blocked by Questing Bots (per the F12 menu settings) from spawning via EFT's "new" spawning system, calculate the Scav spawn rate by tracking all Scav spawns within this time window (in seconds) from the current time in the raid. This is **300** s by default.
* **bot_spawns.bot_cap_adjustments.use_EFT_bot_caps**: SPT's bot caps will be changed to match EFT's bot caps. This is **true** by default.
* **bot_spawns.bot_cap_adjustments.only_decrease_bot_caps**: If **bot_spawns.bot_cap_adjustments.use_EFT_bot_caps=true**, SPT's bot caps will be changed to match EFT's bot caps only if EFT's bot caps are lower. This is **true** by default.
* **bot_spawns.bot_cap_adjustments.map_specific_adjustments**: If **bot_spawns.bot_cap_adjustments.use_EFT_bot_caps=true**, these additional adjustments will be made to SPT's bot caps after changing them to EFT's. This is used to balance bot spawns and performance.
Expand Down
5 changes: 4 additions & 1 deletion dist/user/mods/DanW-SPTQuestingBots/config/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,10 @@
"spawn_retry_time": 10,
"delay_game_start_until_bot_gen_finishes": true,
"spawn_initial_bosses_first": false,
"non_wave_retry_delay_after_blocked": 180,
"eft_new_spawn_system_adjustments" : {
"non_wave_retry_delay_after_blocked": 180,
"scav_spawn_rate_time_window": 300
},
"bot_cap_adjustments": {
"use_EFT_bot_caps": true,
"only_decrease_bot_caps": true,
Expand Down

0 comments on commit 39212dd

Please sign in to comment.