diff --git a/project/Aki.Debugging/AkiDebuggingPlugin.cs b/project/Aki.Debugging/AkiDebuggingPlugin.cs index 39124e9..54021cf 100644 --- a/project/Aki.Debugging/AkiDebuggingPlugin.cs +++ b/project/Aki.Debugging/AkiDebuggingPlugin.cs @@ -1,5 +1,6 @@ using System; using Aki.Common; +using Aki.Debugging.BTR.Patches; using Aki.Debugging.Patches; using BepInEx; @@ -16,8 +17,10 @@ namespace Aki.Debugging { new EndRaidDebug().Enable(); // new CoordinatesPatch().Enable(); - // new StaticLootDumper().Enable(); - new BtrTestPatch().Enable(); + // new StaticLootDumper().Enable(); + new BTRBotAttachPatch().Enable(); + new BTRBotInitPatch().Enable(); + new BTRPatch().Enable(); } catch (Exception ex) { diff --git a/project/Aki.Debugging/BTR/BTRManager.cs b/project/Aki.Debugging/BTR/BTRManager.cs new file mode 100644 index 0000000..f69eaa0 --- /dev/null +++ b/project/Aki.Debugging/BTR/BTRManager.cs @@ -0,0 +1,128 @@ +using Comfort.Common; +using EFT; +using EFT.Vehicle; +using HarmonyLib; +using UnityEngine; +using BTRController = GClass2911; +using BTRDataPacket = GStruct378; + +namespace Aki.Debugging.BTR +{ + public class BTRManager : MonoBehaviour + { + private BTRController btrController; + private BTRVehicle serverSideBtr; + private BTRView clientSideBtr; + private BTRDataPacket btrDataPacket = default; + + + private void Start() + { + try + { + var gameWorld = Singleton.Instance; + + if (gameWorld == null) + { + Destroy(this); + } + + if (gameWorld.BtrController == null) + { + if (!Singleton.Instantiated) + { + Singleton.Create(new BTRController()); + } + + gameWorld.BtrController = btrController = Singleton.Instance; + } + + var btrControllerType = btrController.GetType(); + AccessTools.Method(btrControllerType, "method_3").Invoke(btrController, null); // spawns server-side BTR game object + Singleton.Instance.BotsController.BotSpawner.SpawnBotBTR(); // spawns the scav bot which controls the BTR's turret + + serverSideBtr = btrController.BtrVehicle; + serverSideBtr.moveSpeed = 20f; + var btrMapConfig = btrController.MapPathsConfiguration; + serverSideBtr.CurrentPathConfig = btrMapConfig.PathsConfiguration.pathsConfigurations.RandomElement(); + serverSideBtr.Initialization(btrMapConfig); + AccessTools.Method(btrControllerType, "method_14").Invoke(btrController, null); // creates and assigns the BTR a fake stash + + clientSideBtr = btrController.BtrView; + + UpdateDataPacket(); + clientSideBtr.transform.position = btrDataPacket.position; + clientSideBtr.transform.rotation = btrDataPacket.rotation; + + DisableServerSideRenderers(); + } + catch + { + Debug.LogError("[AKI-BTR]: Unable to spawn BTR"); + DestroyGameObjects(); + throw; + } + } + + private void Update() + { + btrController.SyncBTRVehicleFromServer(UpdateDataPacket()); + } + + private BTRDataPacket UpdateDataPacket() + { + btrDataPacket.position = serverSideBtr.transform.position; + btrDataPacket.rotation = serverSideBtr.transform.rotation; + if (serverSideBtr.BTRTurret?.gunsBlockRoot != null) + { + btrDataPacket.turretRotation = serverSideBtr.BTRTurret.transform.rotation; + btrDataPacket.gunsBlockRotation = serverSideBtr.BTRTurret.gunsBlockRoot.rotation; + } + btrDataPacket.State = (byte)serverSideBtr.BtrState; + btrDataPacket.RouteState = (byte)serverSideBtr.VehicleRouteState; + btrDataPacket.LeftSideState = serverSideBtr.LeftSideState; + btrDataPacket.LeftSlot0State = serverSideBtr.LeftSlot0State; + btrDataPacket.LeftSlot1State = serverSideBtr.LeftSlot1State; + btrDataPacket.RightSideState = serverSideBtr.RightSideState; + btrDataPacket.RightSlot0State = serverSideBtr.RightSlot0State; + btrDataPacket.RightSlot1State = serverSideBtr.RightSlot1State; + btrDataPacket.currentSpeed = serverSideBtr.currentSpeed; + btrDataPacket.timeToEndPause = serverSideBtr.timeToEndPause; + btrDataPacket.moveDirection = (byte)serverSideBtr.VehicleMoveDirection; + btrDataPacket.MoveSpeed = serverSideBtr.moveSpeed; + if (btrController.BotShooterBtr != null) + { + btrDataPacket.BtrBotId = btrController.BotShooterBtr.Id; + } + + return btrDataPacket; + } + + private void DisableServerSideRenderers() + { + var meshRenderers = serverSideBtr.transform.GetComponentsInChildren(); + foreach (var renderer in meshRenderers) + { + renderer.enabled = false; + } + } + + private void DestroyGameObjects() + { + if (btrController != null) + { + if (serverSideBtr != null) + { + Destroy(serverSideBtr.gameObject); + } + if (clientSideBtr != null) + { + Destroy(clientSideBtr.gameObject); + } + + btrController.Dispose(); + } + Destroy(this); + } + } +} diff --git a/project/Aki.Debugging/BTR/Patches/BTRBotAttachPatch.cs b/project/Aki.Debugging/BTR/Patches/BTRBotAttachPatch.cs new file mode 100644 index 0000000..4f6fa7f --- /dev/null +++ b/project/Aki.Debugging/BTR/Patches/BTRBotAttachPatch.cs @@ -0,0 +1,71 @@ +using Aki.Reflection.Patching; +using Comfort.Common; +using EFT; +using EFT.NextObservedPlayer; +using EFT.UI; +using EFT.Vehicle; +using HarmonyLib; +using System; +using System.Reflection; + +namespace Aki.Debugging.BTR.Patches +{ + // Fixes the BTR Bot initialization in AttachBot() of BTRTurretView + // + // Context: + // ClientGameWorld in LiveEFT will register the server-side BTR Bot as type ObservedPlayerView and is stored in GameWorld's allObservedPlayersByID dictionary. + // In SPT, GameWorld.allObservedPlayersByID is empty which results in the game never finishing the initialization of the BTR Bot which includes disabling its gun, voice and mesh renderers. + // Perhaps some research should be done into getting the dictionary populated as ObservedPlayerView seems to be utilised by several aspects of the BTR's functionality. + // For now, we do dirty patches to work around the lack of ObservedPlayerView, using Player instead. + public class BTRBotAttachPatch : ModulePatch + { + protected override MethodBase GetTargetMethod() + { + return AccessTools.Method(typeof(BTRTurretView), nameof(BTRTurretView.AttachBot)); + } + + [PatchPrefix] + public static bool PatchPrefix(object __instance, int btrBotId) + { + var gameWorld = Singleton.Instance; + var btrTurretView = (BTRTurretView)__instance; + + var btrTurretViewTupleField = (ValueTuple)AccessTools.Field(btrTurretView.GetType(), "valueTuple_0") + .GetValue(btrTurretView); + + if (!btrTurretViewTupleField.Item2) + { + var btrTurretViewMethod = AccessTools.Method(btrTurretView.GetType(), "method_1"); + btrTurretViewMethod.Invoke(btrTurretView, new object[]{btrBotId}); + } + if (!btrTurretViewTupleField.Item2) + { + return false; + } + + var btrBot = gameWorld.BtrController.BotShooterBtr?.GetPlayer; + if (btrBot == null) + { + return false; + } + try + { + var botRootTransform = btrTurretView.BotRoot; + + btrBot.Transform.position = botRootTransform.position; + + var aiFirearmController = btrBot.gameObject.GetComponent(); + var currentWeaponPrefab = (WeaponPrefab)AccessTools.Field(aiFirearmController.GetType(), "weaponPrefab_0").GetValue(aiFirearmController); + currentWeaponPrefab.transform.position = botRootTransform.position; + + btrBot.PlayerBones.Weapon_Root_Anim.SetPositionAndRotation(botRootTransform.position, botRootTransform.rotation); + return false; + } + catch + { + ConsoleScreen.LogError("[AKI-BTR] Could not finish BtrBot initialization. Check logs."); + throw; + } + } + } +} diff --git a/project/Aki.Debugging/BTR/Patches/BTRBotInitPatch.cs b/project/Aki.Debugging/BTR/Patches/BTRBotInitPatch.cs new file mode 100644 index 0000000..0c060ca --- /dev/null +++ b/project/Aki.Debugging/BTR/Patches/BTRBotInitPatch.cs @@ -0,0 +1,87 @@ +using Aki.Reflection.Patching; +using EFT.UI; +using EFT.Vehicle; +using EFT; +using HarmonyLib; +using System.Reflection; +using UnityEngine; +using Comfort.Common; +using System; +using EFT.NextObservedPlayer; + +namespace Aki.Debugging.BTR.Patches +{ + // Fixes the BTR Bot initialization in method_1() of BTRTurretView + // + // Context: + // ClientGameWorld in LiveEFT will register the server-side BTR Bot as type ObservedPlayerView and is stored in GameWorld's allObservedPlayersByID dictionary. + // In SPT, allObservedPlayersByID is empty which results in the game never finishing the initialization of the BTR Bot which includes disabling its gun, voice and mesh renderers. + // Perhaps some research should be done into getting the dictionary populated as ObservedPlayerView seems to be utilised by several aspects of the BTR's functionality. + // For now, we do dirty patches to work around the lack of ObservedPlayerView, using Player instead. + public class BTRBotInitPatch : ModulePatch + { + protected override MethodBase GetTargetMethod() + { + return AccessTools.Method(typeof(BTRTurretView), "method_1"); + } + + [PatchPostfix] + public static void PatchPostfix(object __instance, int btrBotId, ref bool __result) + { + var gameWorld = Singleton.Instance; + var btrTurretView = (BTRTurretView)__instance; + + foreach (var playerKeyValue in gameWorld.allAlivePlayersByID) + { + if (playerKeyValue.Value.Id == btrBotId) + { + try + { + Renderer[] array = playerKeyValue.Value.GetComponentsInChildren(); + for (int i = 0; i < array.Length; i++) + { + array[i].enabled = false; + } + + var aiFirearmController = playerKeyValue.Value.gameObject.GetComponent(); + var currentWeaponPrefab = (WeaponPrefab)AccessTools.Field(aiFirearmController.GetType(), "weaponPrefab_0").GetValue(aiFirearmController); + if (currentWeaponPrefab.RemoveChildrenOf != null) + { + foreach (var text in currentWeaponPrefab.RemoveChildrenOf) + { + var transform = currentWeaponPrefab.transform.FindTransform(text); + if (transform != null) + { + transform.gameObject.SetActive(false); + } + } + } + foreach (var renderer in currentWeaponPrefab.GetComponentsInChildren()) + { + if (renderer.name == "MuzzleJetCombinedMesh") + { + renderer.transform.localPosition = new Vector3(0.18f, 0f, -0.095f); + } + else + { + renderer.enabled = false; + } + } + + var tuple = new ValueTuple(new ObservedPlayerView(), true); + var btrTurretViewTupleField = AccessTools.Field(btrTurretView.GetType(), "valueTuple_0"); + btrTurretViewTupleField.SetValue(btrTurretView, tuple); + + __result = true; + return; + } + catch + { + ConsoleScreen.LogError("[AKI-BTR] BtrBot initialization failed, BtrBot will be visible ingame. Check logs."); + throw; + } + } + } + } + } +} diff --git a/project/Aki.Debugging/BTR/Patches/BTRPatch.cs b/project/Aki.Debugging/BTR/Patches/BTRPatch.cs new file mode 100644 index 0000000..54aa36d --- /dev/null +++ b/project/Aki.Debugging/BTR/Patches/BTRPatch.cs @@ -0,0 +1,44 @@ +using System.Reflection; +using Aki.Reflection.Patching; +using Comfort.Common; +using EFT; +using EFT.UI; + +namespace Aki.Debugging.BTR.Patches +{ + // Adds a BTRManager component to the GameWorld game object when raid starts. + public class BTRPatch : ModulePatch + { + protected override MethodBase GetTargetMethod() + { + return typeof(GameWorld).GetMethod(nameof(GameWorld.OnGameStarted)); + } + + [PatchPostfix] + public static void PatchPostfix() + { + try + { + var gameWorld = Singleton.Instance; + if (gameWorld.MainPlayer.Location.ToLower() != "tarkovstreets") + { + // only run patch on streets + return; + } + + if (gameWorld.LocationId.IsNullOrEmpty()) + { + // GameWorld's LocationId needs to be set otherwise BTR doesn't get spawned in automatically + gameWorld.LocationId = gameWorld.MainPlayer.Location; + } + + gameWorld.gameObject.AddComponent(); + } + catch (System.Exception) + { + ConsoleScreen.LogError("[AKI-BTR] Exception thrown, check logs."); + throw; + } + } + } +} \ No newline at end of file diff --git a/project/Aki.Debugging/Patches/BtrTestPatch.cs b/project/Aki.Debugging/Patches/BtrTestPatch.cs deleted file mode 100644 index 9cb233f..0000000 --- a/project/Aki.Debugging/Patches/BtrTestPatch.cs +++ /dev/null @@ -1,69 +0,0 @@ -using System.Reflection; -using Aki.Reflection.Patching; -using Comfort.Common; -using EFT; -using EFT.UI; - -namespace Aki.Debugging.Patches -{ - public class BtrTestPatch : ModulePatch - { - protected override MethodBase GetTargetMethod() - { - return typeof(GameWorld).GetMethod(nameof(GameWorld.OnGameStarted)); - } - - [PatchPostfix] - public static void PatchPostfix() - { - try - { - var gameWorld = Singleton.Instance; - if (gameWorld.MainPlayer.Location.ToLower() != "tarkovstreets") - { - // only run patch on streets - return; - } - - var botGame = Singleton.Instance; - - if (gameWorld.BtrController == null) - { - if (!Singleton.Instantiated) - { - Singleton.Create(new GClass2911()); - } - - gameWorld.BtrController = Singleton.Instance; - - ConsoleScreen.LogWarning($"[AKI-BTR] BtrController instance is null: {gameWorld.BtrController == null}"); - ConsoleScreen.LogWarning($"[AKI-BTR] Singleton GClass2911 is null: {Singleton.Instance == null}"); - ConsoleScreen.LogWarning($"[AKI-BTR] BtrController.BotShooterBtr instance is null: {gameWorld.BtrController?.BotShooterBtr == null}"); - ConsoleScreen.LogWarning($"[AKI-BTR] BtrController.BtrVehicle instance is null: {gameWorld.BtrController?.BtrVehicle == null}"); - } - ConsoleScreen.LogWarning($"[AKI-BTR] botspawner is enabled: {botGame.BotsController.IsEnable}"); - - ConsoleScreen.LogWarning("[AKI-BTR] Post patch, spawning btr"); - botGame.BotsController.BotSpawner.SpawnBotBTR(); - - ConsoleScreen.LogWarning($"[AKI-BTR] btr vehicle is null: {gameWorld.BtrController?.BtrVehicle == null}"); - ConsoleScreen.LogWarning($"[AKI-BTR] btr vehicle gameobject is null: {gameWorld.BtrController?.BtrVehicle?.gameObject == null}"); - ConsoleScreen.LogWarning($"[AKI-BTR] BtrController.BotShooterBtr instance is null: {gameWorld.BtrController?.BotShooterBtr == null}"); - - var btrTransform = gameWorld.BtrController?.BtrVehicle?.gameObject?.transform; - if (btrTransform != null) - { - ConsoleScreen.LogWarning($"[AKI-BTR] Btr Location {btrTransform}"); - } else - { - ConsoleScreen.LogWarning($"[AKI-BTR] wasnt able to get BTR location"); - } - } - catch (System.Exception) - { - ConsoleScreen.LogError("[AKI-BTR] Exception thrown, check logs"); - throw; - } - } - } -} \ No newline at end of file