diff --git a/project/Aki.Custom/AkiCustomPlugin.cs b/project/Aki.Custom/AkiCustomPlugin.cs index e43c9a5..9f77dc7 100644 --- a/project/Aki.Custom/AkiCustomPlugin.cs +++ b/project/Aki.Custom/AkiCustomPlugin.cs @@ -32,6 +32,9 @@ namespace Aki.Custom // new SessionIdPatch().Enable(); new VersionLabelPatch().Enable(); new IsEnemyPatch().Enable(); + new BotCalledDataTryCallPatch().Enable(); + new BotCallForHelpCallBotPatch().Enable(); + new BotOwnerDisposePatch().Enable(); new LocationLootCacheBustingPatch().Enable(); //new AddSelfAsEnemyPatch().Enable(); new CheckAndAddEnemyPatch().Enable(); diff --git a/project/Aki.Custom/Patches/BotCallForHelpCallBotPatch.cs b/project/Aki.Custom/Patches/BotCallForHelpCallBotPatch.cs new file mode 100644 index 0000000..97938be --- /dev/null +++ b/project/Aki.Custom/Patches/BotCallForHelpCallBotPatch.cs @@ -0,0 +1,54 @@ +using Aki.Reflection.Patching; +using EFT; +using HarmonyLib; +using System.Reflection; +using UnityEngine; + +namespace Aki.Custom.Patches +{ + /** + * BSG passes the wrong target location into the TryCall method for BotCallForHelp, it's passing in + * the bots current target instead of the target of the bot calling for help + * + * This results in both an NRE, and the called bots target location being wrong + */ + internal class BotCallForHelpCallBotPatch : ModulePatch + { + private static FieldInfo _originalPanicTypeField; + + protected override MethodBase GetTargetMethod() + { + _originalPanicTypeField = AccessTools.Field(typeof(BotCallForHelp), "_originalPanicType"); + + return AccessTools.FirstMethod(typeof(BotCallForHelp), IsTargetMethod); + } + + protected bool IsTargetMethod(MethodBase method) + { + var parameters = method.GetParameters(); + return (parameters.Length == 1 + && parameters[0].Name == "calledBot"); + } + + [PatchPrefix] + private static bool PatchPrefix(ref bool __result, BotCallForHelp __instance, BotOwner calledBot, BotOwner ___botOwner_0) + { + if (__instance.method_2(calledBot) && ___botOwner_0.Memory.GoalEnemy != null) + { + _originalPanicTypeField.SetValue(calledBot.CallForHelp, calledBot.DangerPointsData.PanicType); + calledBot.DangerPointsData.PanicType = PanicType.none; + calledBot.Brain.BaseBrain.CalcActionNextFrame(); + // Note: This differs from BSG's implementation in that we pass in botOwner_0's enemy pos instead of calledBot's enemy pos + calledBot.CalledData.TryCall(new Vector3?(___botOwner_0.Memory.GoalEnemy.Person.Position), ___botOwner_0, true); + __result = true; + } + else + { + __result = false; + } + + // Skip original + return false; + } + } +} diff --git a/project/Aki.Custom/Patches/BotCalledDataTryCallPatch.cs b/project/Aki.Custom/Patches/BotCalledDataTryCallPatch.cs new file mode 100644 index 0000000..e8c0646 --- /dev/null +++ b/project/Aki.Custom/Patches/BotCalledDataTryCallPatch.cs @@ -0,0 +1,49 @@ +using Aki.Reflection.Patching; +using EFT; +using HarmonyLib; +using System.Reflection; + +namespace Aki.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 + */ + internal 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/Aki.Custom/Patches/BotOwnerDisposePatch.cs b/project/Aki.Custom/Patches/BotOwnerDisposePatch.cs new file mode 100644 index 0000000..9266b91 --- /dev/null +++ b/project/Aki.Custom/Patches/BotOwnerDisposePatch.cs @@ -0,0 +1,28 @@ +using Aki.Reflection.Patching; +using EFT; +using HarmonyLib; +using System.Reflection; + +namespace Aki.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 + */ + internal 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(); + } + } + } +}