From 059334d0cd8f15604a5dff7f5ad82b5bc45b60d0 Mon Sep 17 00:00:00 2001 From: DrakiaXYZ Date: Sun, 10 Mar 2024 09:43:15 +0000 Subject: [PATCH] Fix the BTR turretview bot being visible (!92) Rewrite the BTRTurretView attach patch, so we no longer need the BotInit patch Co-authored-by: DrakiaXYZ <565558+TheDgtl@users.noreply.github.com> Reviewed-on: https://dev.sp-tarkov.com/SPT-AKI/Modules/pulls/92 Co-authored-by: DrakiaXYZ Co-committed-by: DrakiaXYZ --- project/Aki.Custom/Aki.Custom.csproj | 3 +- project/Aki.Custom/AkiCustomPlugin.cs | 1 - .../BTR/Patches/BTRBotAttachPatch.cs | 145 ++++++++++++++---- .../Aki.Custom/BTR/Patches/BTRBotInitPatch.cs | 90 ----------- 4 files changed, 119 insertions(+), 120 deletions(-) delete mode 100644 project/Aki.Custom/BTR/Patches/BTRBotInitPatch.cs diff --git a/project/Aki.Custom/Aki.Custom.csproj b/project/Aki.Custom/Aki.Custom.csproj index 03ebf3c..61b6458 100644 --- a/project/Aki.Custom/Aki.Custom.csproj +++ b/project/Aki.Custom/Aki.Custom.csproj @@ -12,13 +12,14 @@ + - + diff --git a/project/Aki.Custom/AkiCustomPlugin.cs b/project/Aki.Custom/AkiCustomPlugin.cs index 1db4b27..b6bfdc5 100644 --- a/project/Aki.Custom/AkiCustomPlugin.cs +++ b/project/Aki.Custom/AkiCustomPlugin.cs @@ -58,7 +58,6 @@ namespace Aki.Custom new BTRActivateTraderDialogPatch().Enable(); new BTRInteractionPatch().Enable(); new BTRExtractPassengersPatch().Enable(); - new BTRBotInitPatch().Enable(); new BTRBotAttachPatch().Enable(); new BTRReceiveDamageInfoPatch().Enable(); new BTRTurretCanShootPatch().Enable(); diff --git a/project/Aki.Custom/BTR/Patches/BTRBotAttachPatch.cs b/project/Aki.Custom/BTR/Patches/BTRBotAttachPatch.cs index 30f65dc..7c8312f 100644 --- a/project/Aki.Custom/BTR/Patches/BTRBotAttachPatch.cs +++ b/project/Aki.Custom/BTR/Patches/BTRBotAttachPatch.cs @@ -1,12 +1,15 @@ using Aki.Reflection.Patching; using Comfort.Common; using EFT; +using EFT.AssetsManager; using EFT.NextObservedPlayer; -using EFT.UI; using EFT.Vehicle; using HarmonyLib; using System; +using System.Collections.Generic; +using System.Linq; using System.Reflection; +using UnityEngine; namespace Aki.Custom.BTR.Patches { @@ -15,12 +18,25 @@ namespace Aki.Custom.BTR.Patches // 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. - // For now, we do dirty patches to work around the lack of ObservedPlayerView, using Player instead. + // + // This is essentially a full reimplementation of the BTRTurretView class, but using Player instead of ObservedPlayerView. + // public class BTRBotAttachPatch : ModulePatch { + private static FieldInfo _valueTuple0Field; + private static FieldInfo _gunModsToDisableField; + private static FieldInfo _weaponPrefab0Field; + private static readonly List rendererList = new List(256); + protected override MethodBase GetTargetMethod() { - return AccessTools.Method(typeof(BTRTurretView), nameof(BTRTurretView.AttachBot)); + var targetType = typeof(BTRTurretView); + + _valueTuple0Field = AccessTools.Field(targetType, "valueTuple_0"); + _gunModsToDisableField = AccessTools.Field(targetType, "_gunModsToDisable"); + _weaponPrefab0Field = AccessTools.Field(typeof(Player.FirearmController), "weaponPrefab_0"); + + return AccessTools.Method(targetType, nameof(BTRTurretView.AttachBot)); } [PatchPrefix] @@ -32,47 +48,120 @@ namespace Aki.Custom.BTR.Patches return false; } - var btrTurretViewTupleField = (ValueTuple)AccessTools.Field(__instance.GetType(), "valueTuple_0").GetValue(__instance); - if (!btrTurretViewTupleField.Item2) + // Find the BTR turret + var alivePlayersList = gameWorld.AllAlivePlayersList; + Player turretPlayer = alivePlayersList.FirstOrDefault(x => x.Id == btrBotId); + if (turretPlayer == null) { - __instance.method_3(btrBotId); return false; } - var btrController = gameWorld.BtrController; - if (btrController == null) + // Init the turret view + var valueTuple = (ValueTuple)_valueTuple0Field.GetValue(__instance); + if (!valueTuple.Item2 && !InitTurretView(__instance, turretPlayer)) { - Logger.LogError("[AKI-BTR] BTRBotAttachPatch - BtrController is null"); + Logger.LogError("[AKI-BTR] BTRBotAttachPatch - BtrBot initialization failed"); return false; } - var btrBotShooter = btrController.BotShooterBtr; - if (btrBotShooter == null) + WeaponPrefab weaponPrefab; + Transform transform; + if (FindTurretObjects(turretPlayer, out weaponPrefab, out transform)) { - Logger.LogError("[AKI-BTR] BTRBotAttachPatch - BtrBotShooter is null"); + weaponPrefab.transform.SetPositionAndRotation(__instance.GunRoot.position, __instance.GunRoot.rotation); + transform.SetPositionAndRotation(__instance.GunRoot.position, __instance.GunRoot.rotation); + } + + return false; + } + + private static bool InitTurretView(BTRTurretView btrTurretView, Player turretPlayer) + { + EnableTurretObjects(btrTurretView, turretPlayer, false); + + // We only use this for tracking whether the turret is initialized, so we don't need to set the ObservedPlayerView + _valueTuple0Field.SetValue(btrTurretView, new ValueTuple(null, true)); + return true; + } + + private static void EnableTurretObjects(BTRTurretView btrTurretView, Player player, bool enable) + { + // Find the turret weapon transform + WeaponPrefab weaponPrefab; + Transform weaponTransform; + if (!FindTurretObjects(player, out weaponPrefab, out weaponTransform)) + { + return; + } + + // Hide the turret bot + SetVisible(player, weaponPrefab, false); + + // Disable the components we need to disaable + var _gunModsToDisable = (string[])_gunModsToDisableField.GetValue(btrTurretView); + foreach (Transform child in weaponTransform) + { + if (_gunModsToDisable.Contains(child.name)) + { + child.gameObject.SetActive(enable); + } + } + } + + private static bool FindTurretObjects(Player player, out WeaponPrefab weaponPrefab, out Transform weapon) + { + // Find the WeaponPrefab and Transform of the turret weapon + var aiFirearmController = player.gameObject.GetComponent(); + weaponPrefab = (WeaponPrefab)_weaponPrefab0Field.GetValue(aiFirearmController); + + if (weaponPrefab == null) + { + weapon = null; return false; } - - try + + weapon = weaponPrefab.Hierarchy.GetTransform(ECharacterWeaponBones.weapon); + return weapon != null; + } + + /** + * A re-implementation of the ObservedPlayerController.Culling.Mode setter that works for a Player object + */ + private static void SetVisible(Player player, WeaponPrefab weaponPrefab, bool isVisible) + { + // Toggle any animators and colliders + if (player.HealthController.IsAlive) { - var btrBot = btrBotShooter.GetPlayer; - var botRootTransform = __instance.BotRoot; + IAnimator bodyAnimatorCommon = player.GetBodyAnimatorCommon(); + if (bodyAnimatorCommon.enabled != isVisible) + { + bool flag = !bodyAnimatorCommon.enabled; + bodyAnimatorCommon.enabled = isVisible; + FirearmsAnimator firearmsAnimator = player.HandsController.FirearmsAnimator; + if (firearmsAnimator != null && firearmsAnimator.Animator.enabled != isVisible) + { + firearmsAnimator.Animator.enabled = isVisible; + } + } - btrBot.Transform.position = botRootTransform.position; - - var firearmController = btrBot.gameObject.GetComponent(); - var currentWeaponPrefab = (WeaponPrefab)AccessTools.Field(firearmController.GetType(), "weaponPrefab_0").GetValue(firearmController); - currentWeaponPrefab.transform.position = botRootTransform.position; - - btrBot.PlayerBones.Weapon_Root_Anim.SetPositionAndRotation(botRootTransform.position, botRootTransform.rotation); - - return false; + PlayerPoolObject component = player.gameObject.GetComponent(); + foreach (Collider collider in component.Colliders) + { + if (collider.enabled != isVisible) + { + collider.enabled = isVisible; + } + } } - catch + + // Build a list of renderers for this player object and set their rendering state + rendererList.Clear(); + player.PlayerBody.GetRenderersNonAlloc(rendererList); + if (weaponPrefab != null) { - ConsoleScreen.LogError("[AKI-BTR] Could not finish BtrBot initialization. Check logs."); - throw; + rendererList.AddRange(weaponPrefab.Renderers); } + rendererList.ForEach(renderer => renderer.forceRenderingOff = !isVisible); } } } diff --git a/project/Aki.Custom/BTR/Patches/BTRBotInitPatch.cs b/project/Aki.Custom/BTR/Patches/BTRBotInitPatch.cs deleted file mode 100644 index 971e2d4..0000000 --- a/project/Aki.Custom/BTR/Patches/BTRBotInitPatch.cs +++ /dev/null @@ -1,90 +0,0 @@ -using Aki.Reflection.Patching; -using Comfort.Common; -using EFT; -using EFT.NextObservedPlayer; -using EFT.UI; -using EFT.Vehicle; -using HarmonyLib; -using System; -using System.Linq; -using System.Reflection; -using UnityEngine; - -namespace Aki.Custom.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. - // 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), nameof(BTRTurretView.method_3)); - } - - [PatchPrefix] - private static bool PatchPrefix(ref ValueTuple ___valueTuple_0, int btrBotId, ref bool __result) - { - var gameWorld = Singleton.Instance; - if (gameWorld == null) - { - Logger.LogError("[AKI-BTR] BTRBotInitPatch - GameWorld is null"); - __result = false; - return false; - } - - var alivePlayersList = gameWorld.AllAlivePlayersList; - bool doesBtrBotExist = alivePlayersList.Exists(x => x.Id == btrBotId); - if (!doesBtrBotExist) - { - __result = false; - return false; - } - try - { - Player player = alivePlayersList.First(x => x.Id == btrBotId); - - Renderer[] array = player.GetComponentsInChildren(); - for (int i = 0; i < array.Length; i++) - { - array[i].enabled = false; - } - - var aiFirearmController = player.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); - 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; - } - } - - ___valueTuple_0 = new ValueTuple(new ObservedPlayerView(), true); - - __result = true; - return false; - } - catch - { - ConsoleScreen.LogError("[AKI-BTR] BtrBot initialization failed, BtrBot will be visible ingame. Check logs."); - throw; - } - } - } -}