diff --git a/project/SPT.Custom/Patches/BotCalledDataTryCallPatch.cs b/project/SPT.Custom/Patches/BotCalledDataTryCallPatch.cs
new file mode 100644
index 0000000..663a56f
--- /dev/null
+++ b/project/SPT.Custom/Patches/BotCalledDataTryCallPatch.cs
@@ -0,0 +1,49 @@
+using SPT.Reflection.Patching;
+using EFT;
+using HarmonyLib;
+using System.Reflection;
+
+namespace SPT.Custom.Patches
+{
+ /**
+ * It's possible for `AddEnemy` to return false, in that case, further code in TryCall will fail,
+ * so we do the first bit of `TryCall` ourselves, and skip the original function if AddEnemy fails
+ */
+ public class BotCalledDataTryCallPatch : ModulePatch
+ {
+ protected override MethodBase GetTargetMethod()
+ {
+ return AccessTools.Method(typeof(BotCalledData), nameof(BotCalledData.TryCall));
+ }
+
+ [PatchPrefix]
+ private static bool PatchPrefix(ref bool __result, BotOwner caller, BotOwner ___botOwner_0, BotOwner ____caller)
+ {
+ if (___botOwner_0.EnemiesController.IsEnemy(caller.AIData.Player) || ____caller != null)
+ {
+ __result = false;
+
+ // Skip original
+ return false;
+ }
+
+ if (caller.Memory.GoalEnemy != null)
+ {
+ IPlayer person = caller.Memory.GoalEnemy.Person;
+ if (!___botOwner_0.BotsGroup.Enemies.ContainsKey(person))
+ {
+ if (!___botOwner_0.BotsGroup.AddEnemy(person, EBotEnemyCause.callBot))
+ {
+ __result = false;
+
+ // Skip original
+ return false;
+ }
+ }
+ }
+
+ // Allow original
+ return true;
+ }
+ }
+}
diff --git a/project/SPT.Custom/Patches/BotEnemyTargetPatch.cs b/project/SPT.Custom/Patches/BotEnemyTargetPatch.cs
new file mode 100644
index 0000000..9810079
--- /dev/null
+++ b/project/SPT.Custom/Patches/BotEnemyTargetPatch.cs
@@ -0,0 +1,52 @@
+using SPT.Reflection.Patching;
+using EFT;
+using System.Reflection;
+using HarmonyLib;
+
+namespace SPT.Custom.Patches
+{
+ public class BotEnemyTargetPatch : ModulePatch
+ {
+ protected override MethodBase GetTargetMethod()
+ {
+ return AccessTools.Method(typeof(BotsController), nameof(BotsController.AddEnemyToAllGroupsInBotZone));
+ }
+
+ ///
+ /// AddEnemyToAllGroupsInBotZone()
+ /// Goal: by default, AddEnemyToAllGroupsInBotZone doesn't check if the bot group is on the same side as the player.
+ /// The effect of this is that when you are a Scav and kill a Usec, every bot group in the zone will aggro you including other Scavs.
+ /// This should fix that.
+ ///
+ [PatchPrefix]
+ private static bool PatchPrefix(BotsController __instance, IPlayer aggressor, IPlayer groupOwner, IPlayer target)
+ {
+ BotZone botZone = groupOwner.AIData.BotOwner.BotsGroup.BotZone;
+ foreach (var item in __instance.Groups())
+ {
+ if (item.Key != botZone)
+ {
+ continue;
+ }
+
+ foreach (var group in item.Value.GetGroups(notNull: true))
+ {
+ if (!group.Enemies.ContainsKey(aggressor) && ShouldAttack(aggressor, target, group))
+ {
+ group.AddEnemy(aggressor, EBotEnemyCause.AddEnemyToAllGroupsInBotZone);
+ }
+ }
+ }
+
+ return false;
+ }
+ private static bool ShouldAttack(IPlayer attacker, IPlayer victim, BotsGroup groupToCheck)
+ {
+ // Group should target if player attack a victim on the same side or if the group is not on the same side as the player.
+ bool shouldAttack = attacker.Side != groupToCheck.Side
+ || attacker.Side == victim.Side;
+
+ return !groupToCheck.HaveMemberWithRole(WildSpawnType.gifter) && groupToCheck.ShallRevengeFor(victim) && shouldAttack;
+ }
+ }
+}
diff --git a/project/SPT.Custom/Patches/BotOwnerDisposePatch.cs b/project/SPT.Custom/Patches/BotOwnerDisposePatch.cs
new file mode 100644
index 0000000..9242e71
--- /dev/null
+++ b/project/SPT.Custom/Patches/BotOwnerDisposePatch.cs
@@ -0,0 +1,28 @@
+using SPT.Reflection.Patching;
+using EFT;
+using HarmonyLib;
+using System.Reflection;
+
+namespace SPT.Custom.Patches
+{
+ /**
+ * BotOwner doesn't call SetOff on the CalledData object when a bot is disposed, this can result
+ * in bots that are no longer alive having their `OnEnemyAdd` method called
+ */
+ public class BotOwnerDisposePatch : ModulePatch
+ {
+ protected override MethodBase GetTargetMethod()
+ {
+ return AccessTools.Method(typeof(BotOwner), nameof(BotOwner.Dispose));
+ }
+
+ [PatchPrefix]
+ private static void PatchPrefix(BotOwner __instance)
+ {
+ if (__instance.CalledData != null)
+ {
+ __instance.CalledData.SetOff();
+ }
+ }
+ }
+}
diff --git a/project/SPT.Custom/Patches/BotSelfEnemyPatch.cs b/project/SPT.Custom/Patches/BotSelfEnemyPatch.cs
new file mode 100644
index 0000000..97a0482
--- /dev/null
+++ b/project/SPT.Custom/Patches/BotSelfEnemyPatch.cs
@@ -0,0 +1,40 @@
+using SPT.Reflection.Patching;
+using EFT;
+using System.Reflection;
+using HarmonyLib;
+
+namespace SPT.Custom.Patches
+{
+ ///
+ /// Goal: patch removes the current bot from its own enemy list - occurs when adding bots type to its enemy array in difficulty settings
+ ///
+ public class BotSelfEnemyPatch : ModulePatch
+ {
+ protected override MethodBase GetTargetMethod()
+ {
+ return AccessTools.Method(typeof(BotOwner), nameof(BotOwner.PreActivate));
+ }
+
+ [PatchPrefix]
+ private static bool PatchPrefix(BotOwner __instance, BotsGroup group)
+ {
+ IPlayer selfToRemove = null;
+
+ foreach (var enemy in group.Enemies)
+ {
+ if (enemy.Key.Id == __instance.Id)
+ {
+ selfToRemove = enemy.Key;
+ break;
+ }
+ }
+
+ if (selfToRemove != null)
+ {
+ group.Enemies.Remove(selfToRemove);
+ }
+
+ return true;
+ }
+ }
+}
diff --git a/project/SPT.Custom/SPTCustomPlugin.cs b/project/SPT.Custom/SPTCustomPlugin.cs
index 5d942b9..77e2b56 100644
--- a/project/SPT.Custom/SPTCustomPlugin.cs
+++ b/project/SPT.Custom/SPTCustomPlugin.cs
@@ -34,10 +34,13 @@ namespace SPT.Custom
// new SessionIdPatch().Enable();
new VersionLabelPatch().Enable();
new IsEnemyPatch().Enable();
+ new BotCalledDataTryCallPatch().Enable();
+ new BotOwnerDisposePatch().Enable();
new LocationLootCacheBustingPatch().Enable();
//new AddSelfAsEnemyPatch().Enable();
new CheckAndAddEnemyPatch().Enable();
- //new AddEnemyToAllGroupsInBotZonePatch().Enable();
+ new BotSelfEnemyPatch().Enable(); // needed
+ new AddEnemyToAllGroupsInBotZonePatch().Enable();
new AirdropPatch().Enable();
new AirdropFlarePatch().Enable();
//new AddSptBotSettingsPatch().Enable();