diff --git a/project/Aki.Custom/Patches/IsEnemyPatch.cs b/project/Aki.Custom/Patches/IsEnemyPatch.cs index d406755..28fd774 100644 --- a/project/Aki.Custom/Patches/IsEnemyPatch.cs +++ b/project/Aki.Custom/Patches/IsEnemyPatch.cs @@ -41,7 +41,7 @@ namespace Aki.Custom.Patches [PatchPrefix] private static bool PatchPrefix(ref bool __result, BotsGroup __instance, IPlayer requester) { - if (__instance.InitialBotType == WildSpawnType.peacefullZryachiyEvent) + if (__instance.InitialBotType == WildSpawnType.peacefullZryachiyEvent || __instance.InitialBotType == WildSpawnType.shooterBTR) { return true; // Do original code } diff --git a/project/Aki.Debugging/AkiDebuggingPlugin.cs b/project/Aki.Debugging/AkiDebuggingPlugin.cs index b47e41b..b1f67e8 100644 --- a/project/Aki.Debugging/AkiDebuggingPlugin.cs +++ b/project/Aki.Debugging/AkiDebuggingPlugin.cs @@ -24,6 +24,9 @@ namespace Aki.Debugging new BTRExtractPassengersPatch().Enable(); new BTRBotAttachPatch().Enable(); new BTRBotInitPatch().Enable(); + new BTRReceiveDamageInfoPatch().Enable(); + new BTRTurretCanShootPatch().Enable(); + new BTRTurretDefaultAimingPositionPatch().Enable(); new BTRIsDoorsClosedPath().Enable(); new BTRPatch().Enable(); new BTRTransferItemsPatch().Enable(); diff --git a/project/Aki.Debugging/BTR/BTRManager.cs b/project/Aki.Debugging/BTR/BTRManager.cs index fece7eb..75c9e20 100644 --- a/project/Aki.Debugging/BTR/BTRManager.cs +++ b/project/Aki.Debugging/BTR/BTRManager.cs @@ -1,9 +1,11 @@ using Aki.Debugging.BTR.Utils; using Comfort.Common; using EFT; +using EFT.InventoryLogic; using EFT.Vehicle; using HarmonyLib; using System; +using System.Collections; using System.Collections.Generic; using System.Linq; using System.Reflection; @@ -20,12 +22,20 @@ namespace Aki.Debugging.BTR private BTRControllerClass btrController; private BTRVehicle btrServerSide; private BTRView btrClientSide; + private BotOwner btrBotShooter; private BTRDataPacket btrDataPacket = default; private bool btrBotShooterInitialized = false; private EPlayerBtrState previousPlayerBtrState; private BTRSide lastInteractedBtrSide; public BTRSide LastInteractedBtrSide => lastInteractedBtrSide; + + private BTRTurretServer btrTurretServer; + private Transform btrTurretDefaultTargetTransform; + private Coroutine _shootingTargetCoroutine; + private bool isShooting = false; + private BulletClass btrMachineGunAmmo; + private Item btrMachineGunWeapon; private MethodInfo _updateTaxiPriceMethod; @@ -91,9 +101,6 @@ namespace Aki.Debugging.BTR gameWorld.BtrController = btrController = Singleton.Instance; } - botsController = Singleton.Instance.BotsController; - btrBotService = botsController.BotTradersServices.BTRServices; - InitBTR(); } catch @@ -111,11 +118,17 @@ namespace Aki.Debugging.BTR // BotShooterBtr doesn't get assigned to BtrController immediately so we nullcheck this in Update if (btrController.BotShooterBtr != null && !btrBotShooterInitialized) { + btrBotShooter = btrController.BotShooterBtr; btrBotService.Reset(); // Player will be added to Neutrals list and removed from Enemies list btrBotShooterInitialized = true; } - AimAtEnemy(); + if (btrController.BotShooterBtr == null) return; + + if (IsAimingAtTarget() && !isShooting) + { + _shootingTargetCoroutine = StaticManager.BeginCoroutine(ShootTarget()); + } } public void HandleBtrDoorState(EPlayerBtrState playerBtrState) @@ -172,10 +185,14 @@ namespace Aki.Debugging.BTR private void InitBTR() { + // Initial setup + botsController = Singleton.Instance.BotsController; + btrBotService = botsController.BotTradersServices.BTRServices; var btrControllerType = btrController.GetType(); AccessTools.Method(btrControllerType, "method_3").Invoke(btrController, null); // spawns server-side BTR game object botsController.BotSpawner.SpawnBotBTR(); // spawns the scav bot which controls the BTR's turret + // Initial BTR configuration btrServerSide = btrController.BtrVehicle; btrServerSide.moveSpeed = 20f; var btrMapConfig = btrController.MapPathsConfiguration; @@ -190,11 +207,18 @@ namespace Aki.Debugging.BTR btrServerSide.MoveEnable(); btrServerSide.IncomingToDestinationEvent += ToDestinationEvent; + // Sync initial position and rotation UpdateDataPacket(); btrClientSide = btrController.BtrView; btrClientSide.transform.position = btrDataPacket.position; btrClientSide.transform.rotation = btrDataPacket.rotation; + // Initialise turret variables + btrTurretServer = btrServerSide.BTRTurret; + btrTurretDefaultTargetTransform = (Transform)AccessTools.Field(btrTurretServer.GetType(), "defaultTargetTransform").GetValue(btrTurretServer); + btrMachineGunAmmo = (BulletClass)BTRUtil.CreateItem(BTRUtil.BTRMachineGunAmmoTplId); + btrMachineGunWeapon = BTRUtil.CreateItem(BTRUtil.BTRMachineGunWeaponTplId); + // Pull services data for the BTR from the server BTRUtil.PopulateTraderServicesData(BTRUtil.BTRTraderId); } @@ -233,10 +257,10 @@ namespace Aki.Debugging.BTR { btrDataPacket.position = btrServerSide.transform.position; btrDataPacket.rotation = btrServerSide.transform.rotation; - if (btrServerSide.BTRTurret?.gunsBlockRoot != null) + if (btrTurretServer?.gunsBlockRoot != null) { - btrDataPacket.turretRotation = btrServerSide.BTRTurret.transform.rotation; - btrDataPacket.gunsBlockRotation = btrServerSide.BTRTurret.gunsBlockRoot.rotation; + btrDataPacket.turretRotation = btrTurretServer.transform.rotation; + btrDataPacket.gunsBlockRotation = btrTurretServer.gunsBlockRoot.rotation; } btrDataPacket.State = (byte)btrServerSide.BtrState; btrDataPacket.RouteState = (byte)btrServerSide.VehicleRouteState; @@ -267,23 +291,76 @@ namespace Aki.Debugging.BTR } } - private void AimAtEnemy() + private bool IsAimingAtTarget() { - if (btrController.BotShooterBtr == null) - { - return; - } + var turretInDefaultRotation = btrTurretServer.targetTransform == btrTurretDefaultTargetTransform + && btrTurretServer.targetPosition == btrTurretServer.defaultAimingPosition; - var btrTurret = btrServerSide.BTRTurret; - var enemies = btrController.BotShooterBtr.BotsGroup.Enemies; + var enemies = btrBotShooter.BotsGroup.Enemies; if (enemies.Any()) { - btrTurret.EnableAimingObject(enemies.First().Key.Transform.Original); + IPlayer currentTarget = enemies.First().Key; + Transform currentTargetTransform = currentTarget.Transform.Original; + Vector3 currentTargetPosition = currentTargetTransform.position; + var currentTargetInfo = btrBotShooter.EnemiesController.EnemyInfos[currentTarget]; + + if (!currentTarget.HealthController.IsAlive) + { + enemies.Remove(currentTarget); + } + + if (currentTargetInfo.IsVisible) + { + if (btrTurretServer.CheckPositionInAimingZone(currentTargetPosition)) + { + btrTurretServer.EnableAimingObject(currentTargetTransform); + } + // If turret machine gun aim is close enough to target and has line of sight + if (btrBotShooter.BotBtrData.CanShoot()) + { + return true; + } + } + else if (!currentTargetInfo.IsVisible) + { + // Turret will hold the angle where target was last seen for 3 seconds before resetting its rotation + if (btrTurretServer.targetPosition != currentTargetInfo.EnemyLastPosition && btrTurretServer.targetTransform != null) + { + btrTurretServer.EnableAimingPosition(currentTargetInfo.EnemyLastPosition); + } + else if (currentTargetInfo.TimeLastSeen >= 3f && !turretInDefaultRotation) + { + btrTurretServer.DisableAiming(); + } + } } - else + else if (!enemies.Any() && !turretInDefaultRotation) { - btrTurret.DisableAiming(); + btrTurretServer.DisableAiming(); } + + return false; + } + + /// + /// Custom method to make the BTR coaxial machine gun shoot. + /// + private IEnumerator ShootTarget() + { + isShooting = true; + + Transform machineGunMuzzle = btrTurretServer.machineGunLaunchPoint; + Player btrBotPlayer = btrBotShooter.GetPlayer; + + gameWorld.SharedBallisticsCalculator.Shoot(btrMachineGunAmmo, machineGunMuzzle.position, machineGunMuzzle.forward, btrBotPlayer.ProfileId, btrMachineGunWeapon, 1f, 0); + + Player.FirearmController firearmController = btrBotShooter.GetComponent(); + WeaponPrefab weaponPrefab = (WeaponPrefab)AccessTools.Field(firearmController.GetType(), "weaponPrefab_0").GetValue(firearmController); + WeaponSoundPlayer weaponSoundPlayer = weaponPrefab.GetComponent(); + AccessTools.Method(firearmController.GetType(), "method_54").Invoke(firearmController, new object[] { weaponSoundPlayer, btrMachineGunAmmo, machineGunMuzzle.position, machineGunMuzzle.forward, false }); + + yield return new WaitForSecondsRealtime(0.092308f); // 650 RPM + isShooting = false; } private void DestroyGameObjects() @@ -306,6 +383,8 @@ namespace Aki.Debugging.BTR { gameWorld.MainPlayer.OnBtrStateChanged -= HandleBtrDoorState; } + + StaticManager.KillCoroutine(ref _shootingTargetCoroutine); Destroy(this); } } diff --git a/project/Aki.Debugging/BTR/Patches/BTRReceiveDamageInfoPatch.cs b/project/Aki.Debugging/BTR/Patches/BTRReceiveDamageInfoPatch.cs new file mode 100644 index 0000000..efb9e69 --- /dev/null +++ b/project/Aki.Debugging/BTR/Patches/BTRReceiveDamageInfoPatch.cs @@ -0,0 +1,33 @@ +using Aki.Reflection.Patching; +using Comfort.Common; +using EFT; +using EFT.Vehicle; +using HarmonyLib; +using System.Reflection; + +namespace Aki.Debugging.BTR.Patches +{ + /// + /// Patches an empty method in to handle updating the BTR bot's Neutrals and Enemies lists in response to taking damage. + /// + public class BTRReceiveDamageInfoPatch : ModulePatch + { + protected override MethodBase GetTargetMethod() + { + return AccessTools.Method(typeof(BTRView), "method_1"); + } + + [PatchPrefix] + public static void PatchPrefix(DamageInfo damageInfo) + { + var globalEvents = Singleton.Instance; + if (globalEvents == null) + { + return; + } + + var shotBy = (Player)damageInfo.Player.iPlayer; + globalEvents.InterruptTraderServiceBtrSupportByBetrayer(shotBy); + } + } +} diff --git a/project/Aki.Debugging/BTR/Patches/BTRTurretCanShootPatch.cs b/project/Aki.Debugging/BTR/Patches/BTRTurretCanShootPatch.cs new file mode 100644 index 0000000..273e17e --- /dev/null +++ b/project/Aki.Debugging/BTR/Patches/BTRTurretCanShootPatch.cs @@ -0,0 +1,31 @@ +using Aki.Reflection.Patching; +using EFT.Vehicle; +using HarmonyLib; +using System.Reflection; +using UnityEngine; + +namespace Aki.Debugging.BTR.Patches +{ + public class BTRTurretCanShootPatch : ModulePatch + { + protected override MethodBase GetTargetMethod() + { + return AccessTools.Method(typeof(BTRTurretServer), "method_1"); + } + + [PatchPrefix] + public static bool PatchPrefix(BTRTurretServer __instance) + { + Transform defaultTargetTransform = (Transform)AccessTools.Field(__instance.GetType(), "defaultTargetTransform").GetValue(__instance); + + bool flag = __instance.targetTransform != null && __instance.targetTransform != defaultTargetTransform; + bool flag2 = (bool)AccessTools.Method(__instance.GetType(), "method_2").Invoke(__instance, null); + bool flag3 = __instance.targetPosition != __instance.defaultAimingPosition; + + var isCanShootProperty = AccessTools.DeclaredProperty(__instance.GetType(), nameof(__instance.IsCanShoot)); + isCanShootProperty.SetValue(__instance, (flag || flag3) && flag2); + + return false; + } + } +} diff --git a/project/Aki.Debugging/BTR/Patches/BTRTurretDefaultAimingPositionPatch.cs b/project/Aki.Debugging/BTR/Patches/BTRTurretDefaultAimingPositionPatch.cs new file mode 100644 index 0000000..a050483 --- /dev/null +++ b/project/Aki.Debugging/BTR/Patches/BTRTurretDefaultAimingPositionPatch.cs @@ -0,0 +1,24 @@ +using Aki.Reflection.Patching; +using EFT.Vehicle; +using HarmonyLib; +using System.Reflection; +using UnityEngine; + +namespace Aki.Debugging.BTR.Patches +{ + public class BTRTurretDefaultAimingPositionPatch : ModulePatch + { + protected override MethodBase GetTargetMethod() + { + return AccessTools.Method(typeof(BTRTurretServer), "Start"); + } + + [PatchPrefix] + public static bool PatchPrefix(BTRTurretServer __instance) + { + __instance.defaultAimingPosition = Vector3.zero; + + return false; + } + } +} diff --git a/project/Aki.Debugging/BTR/Utils/BTRUtil.cs b/project/Aki.Debugging/BTR/Utils/BTRUtil.cs index 83b42a3..2bb9b46 100644 --- a/project/Aki.Debugging/BTR/Utils/BTRUtil.cs +++ b/project/Aki.Debugging/BTR/Utils/BTRUtil.cs @@ -2,8 +2,10 @@ using Aki.Debugging.BTR.Models; using Comfort.Common; using EFT; +using EFT.InventoryLogic; using HarmonyLib; using Newtonsoft.Json; +using System; using System.Collections.Generic; using System.Reflection; using UnityEngine; @@ -15,6 +17,8 @@ namespace Aki.Debugging.BTR.Utils public static class BTRUtil { public static readonly string BTRTraderId = Profile.TraderInfo.BTR_TRADER_ID; + public static readonly string BTRMachineGunWeaponTplId = "657857faeff4c850222dff1b"; // BTR PKTM machine gun + public static readonly string BTRMachineGunAmmoTplId = "5e023d34e8a400319a28ed44"; // 7.62x54mmR BT private static FieldInfo _traderAvailableServicesField = AccessTools.Field(typeof(Profile.TraderInfo), "_availableServices"); @@ -145,5 +149,14 @@ namespace Aki.Debugging.BTR.Utils return true; } + + /// + /// Used to create an instance of the item in-raid. + /// + public static Item CreateItem(string tplId) + { + var id = Guid.NewGuid().ToString("N").Substring(0, 24); + return Singleton.Instance.CreateItem(id, tplId, null); + } } }