diff --git a/project/Aki.Debugging/AkiDebuggingPlugin.cs b/project/Aki.Debugging/AkiDebuggingPlugin.cs index 54021cf..2c901fc 100644 --- a/project/Aki.Debugging/AkiDebuggingPlugin.cs +++ b/project/Aki.Debugging/AkiDebuggingPlugin.cs @@ -18,8 +18,11 @@ namespace Aki.Debugging new EndRaidDebug().Enable(); // new CoordinatesPatch().Enable(); // new StaticLootDumper().Enable(); + new BTRPathLoadPatch().Enable(); + new BTRInteractionPatch().Enable(); new BTRBotAttachPatch().Enable(); new BTRBotInitPatch().Enable(); + new BTRIsDoorsClosedPath().Enable(); new BTRPatch().Enable(); } catch (Exception ex) diff --git a/project/Aki.Debugging/BTR/BTRManager.cs b/project/Aki.Debugging/BTR/BTRManager.cs index f69eaa0..cd3aaef 100644 --- a/project/Aki.Debugging/BTR/BTRManager.cs +++ b/project/Aki.Debugging/BTR/BTRManager.cs @@ -5,22 +5,26 @@ using HarmonyLib; using UnityEngine; using BTRController = GClass2911; using BTRDataPacket = GStruct378; +using PlayerInteractPacket = GStruct167; namespace Aki.Debugging.BTR { public class BTRManager : MonoBehaviour { + private GameWorld gameWorld; private BTRController btrController; private BTRVehicle serverSideBtr; private BTRView clientSideBtr; private BTRDataPacket btrDataPacket = default; + private EPlayerBtrState previousPlayerBtrState; + private BTRSide previousInteractedBtrSide; private void Start() { try { - var gameWorld = Singleton.Instance; + gameWorld = Singleton.Instance; if (gameWorld == null) { @@ -55,6 +59,9 @@ namespace Aki.Debugging.BTR clientSideBtr.transform.rotation = btrDataPacket.rotation; DisableServerSideRenderers(); + + previousPlayerBtrState = gameWorld.MainPlayer.BtrState; + gameWorld.MainPlayer.OnBtrStateChanged += HandleBtrDoorState; } catch { @@ -69,6 +76,80 @@ namespace Aki.Debugging.BTR btrController.SyncBTRVehicleFromServer(UpdateDataPacket()); } + public void HandleBtrDoorState(EPlayerBtrState playerBtrState) + { + if (previousPlayerBtrState == EPlayerBtrState.Approach && playerBtrState == EPlayerBtrState.GoIn + || previousPlayerBtrState == EPlayerBtrState.Inside && playerBtrState == EPlayerBtrState.GoOut) + { + // Open Door + UpdateBTRSideDoorState(1); + } + else if (previousPlayerBtrState == EPlayerBtrState.GoIn && playerBtrState == EPlayerBtrState.Inside + || previousPlayerBtrState == EPlayerBtrState.GoOut && playerBtrState == EPlayerBtrState.Outside) + { + // Close Door + UpdateBTRSideDoorState(0); + } + + previousPlayerBtrState = playerBtrState; + } + + // Please tell me there's a better way than this xd + public void OnPlayerInteractDoor(PlayerInteractPacket interactPacket) + { + var playerGoIn = interactPacket.InteractionType == EInteractionType.GoIn; + var playerGoOut = interactPacket.InteractionType == EInteractionType.GoOut; + + if (interactPacket.SideId == 0) + { + if (interactPacket.SlotId == 0) + { + if (playerGoIn) serverSideBtr.LeftSlot0State = 1; + else if (playerGoOut) serverSideBtr.LeftSlot0State = 0; + } + else if (interactPacket.SlotId == 1) + { + if (playerGoIn) serverSideBtr.LeftSlot1State = 1; + else if (playerGoOut) serverSideBtr.LeftSlot1State = 0; + } + } + else if (interactPacket.SideId == 1) + { + if (interactPacket.SlotId == 0) + { + if (playerGoIn) serverSideBtr.RightSlot0State = 1; + else if (playerGoOut) serverSideBtr.RightSlot0State = 0; + } + else if (interactPacket.SlotId == 1) + { + if (playerGoIn) serverSideBtr.RightSlot1State = 1; + else if (playerGoOut) serverSideBtr.RightSlot1State = 0; + } + } + } + + private void UpdateBTRSideDoorState(byte state) + { + var player = gameWorld.MainPlayer; + var btrSides = (BTRSide[])AccessTools.Field(typeof(BTRView), "_btrSides").GetValue(btrController.BtrView); + + for (int i = 0; i < btrSides.Length; i++) + { + if (player.BtrInteractionSide != null && btrSides[i] == player.BtrInteractionSide + || previousInteractedBtrSide != null && btrSides[i] == previousInteractedBtrSide) + { + if (i == 0) serverSideBtr.LeftSideState = state; + else if (i == 1) serverSideBtr.RightSideState = state; + + if ((previousInteractedBtrSide != player.BtrInteractionSide && player.BtrInteractionSide != null) + || previousInteractedBtrSide == null) + { + previousInteractedBtrSide = player.BtrInteractionSide; + } + } + } + } + private BTRDataPacket UpdateDataPacket() { btrDataPacket.position = serverSideBtr.transform.position; @@ -122,6 +203,11 @@ namespace Aki.Debugging.BTR btrController.Dispose(); } + + if (gameWorld != null) + { + gameWorld.MainPlayer.OnBtrStateChanged -= HandleBtrDoorState; + } Destroy(this); } } diff --git a/project/Aki.Debugging/BTR/Patches/BTRBotAttachPatch.cs b/project/Aki.Debugging/BTR/Patches/BTRBotAttachPatch.cs index 4f6fa7f..01c471f 100644 --- a/project/Aki.Debugging/BTR/Patches/BTRBotAttachPatch.cs +++ b/project/Aki.Debugging/BTR/Patches/BTRBotAttachPatch.cs @@ -36,7 +36,7 @@ namespace Aki.Debugging.BTR.Patches if (!btrTurretViewTupleField.Item2) { var btrTurretViewMethod = AccessTools.Method(btrTurretView.GetType(), "method_1"); - btrTurretViewMethod.Invoke(btrTurretView, new object[]{btrBotId}); + btrTurretViewMethod.Invoke(btrTurretView, new object[] { btrBotId }); } if (!btrTurretViewTupleField.Item2) { @@ -59,11 +59,12 @@ namespace Aki.Debugging.BTR.Patches 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."); + 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 index 0c060ca..108479f 100644 --- a/project/Aki.Debugging/BTR/Patches/BTRBotInitPatch.cs +++ b/project/Aki.Debugging/BTR/Patches/BTRBotInitPatch.cs @@ -16,7 +16,6 @@ namespace Aki.Debugging.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, 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 { diff --git a/project/Aki.Debugging/BTR/Patches/BTRInteractionPatch.cs b/project/Aki.Debugging/BTR/Patches/BTRInteractionPatch.cs new file mode 100644 index 0000000..3e2efa2 --- /dev/null +++ b/project/Aki.Debugging/BTR/Patches/BTRInteractionPatch.cs @@ -0,0 +1,49 @@ +using Aki.Reflection.Patching; +using Comfort.Common; +using EFT; +using EFT.Vehicle; +using System; +using System.Reflection; + +namespace Aki.Debugging.BTR.Patches +{ + public class BTRInteractionPatch : ModulePatch + { + protected override MethodBase GetTargetMethod() + { + var bindingFlags = BindingFlags.NonPublic | BindingFlags.Instance; + return typeof(Player).GetMethod("BtrInteraction", bindingFlags); + } + + [PatchPostfix] + public static void PatchPostfix(object __instance, BTRSide btr, byte placeId, EInteractionType interaction) + { + var gameWorld = Singleton.Instance; + var player = (Player)__instance; + + try + { + var interactionBtrPacket = btr.GetInteractWithBtrPacket(placeId, interaction); + player.UpdateInteractionCast(); + + if (interactionBtrPacket.HasInteraction) + { + BTRView btrView = gameWorld.BtrController.BtrView; + if (btrView == null) + { + throw new NullReferenceException("BtrView not found"); + } + + var btrManager = gameWorld.GetComponent(); + btrManager.OnPlayerInteractDoor(interactionBtrPacket); + + btrView.Interaction(player, interactionBtrPacket); + } + } + catch (Exception ex19) + { + UnityEngine.Debug.LogException(ex19); + } + } + } +} diff --git a/project/Aki.Debugging/BTR/Patches/BTRIsDoorsClosedPatch.cs b/project/Aki.Debugging/BTR/Patches/BTRIsDoorsClosedPatch.cs new file mode 100644 index 0000000..b450e1f --- /dev/null +++ b/project/Aki.Debugging/BTR/Patches/BTRIsDoorsClosedPatch.cs @@ -0,0 +1,44 @@ +using Aki.Reflection.Patching; +using Comfort.Common; +using EFT; +using EFT.Vehicle; +using HarmonyLib; +using System.Reflection; + +namespace Aki.Debugging.BTR.Patches +{ + public class BTRIsDoorsClosedPath : ModulePatch + { + protected override MethodBase GetTargetMethod() + { + return AccessTools.Method(typeof(VehicleBase), "IsDoorsClosed"); + } + + [PatchPrefix] + public static bool PatchPrefix(ref bool __result) + { + var btrView = Singleton.Instance?.BtrController?.BtrView; + if (btrView == null) + { + return true; + } + + var btrSides = (BTRSide[])AccessTools.Field(typeof(BTRView), "_btrSides").GetValue(btrView); + int doorsClosed = 0; + foreach (var side in btrSides) + { + if (side.State == BTRSide.EState.Free) + { + doorsClosed++; + } + } + if (doorsClosed == btrSides.Length) + { + __result = true; + return false; + } + + return true; + } + } +} diff --git a/project/Aki.Debugging/BTR/Patches/BTRPatch.cs b/project/Aki.Debugging/BTR/Patches/BTRPatch.cs index 54aa36d..ae75e74 100644 --- a/project/Aki.Debugging/BTR/Patches/BTRPatch.cs +++ b/project/Aki.Debugging/BTR/Patches/BTRPatch.cs @@ -6,7 +6,9 @@ using EFT.UI; namespace Aki.Debugging.BTR.Patches { - // Adds a BTRManager component to the GameWorld game object when raid starts. + /// + /// Adds a BTRManager component to the GameWorld game object when raid starts. + /// public class BTRPatch : ModulePatch { protected override MethodBase GetTargetMethod() @@ -36,7 +38,7 @@ namespace Aki.Debugging.BTR.Patches } catch (System.Exception) { - ConsoleScreen.LogError("[AKI-BTR] Exception thrown, check logs."); + ConsoleScreen.LogError("[AKI-BTR]: Exception thrown, check logs."); throw; } } diff --git a/project/Aki.Debugging/BTR/Patches/BTRPathLoadPatch.cs b/project/Aki.Debugging/BTR/Patches/BTRPathLoadPatch.cs new file mode 100644 index 0000000..69afa44 --- /dev/null +++ b/project/Aki.Debugging/BTR/Patches/BTRPathLoadPatch.cs @@ -0,0 +1,35 @@ +using Aki.Reflection.Patching; +using Comfort.Common; +using EFT; +using HarmonyLib; +using System.Reflection; + +namespace Aki.Debugging.BTR.Patches +{ + // The BTRManager MapPathsConfiguration loading depends on the game state being set to Starting + // so set it to Starting while the method is running, then reset it afterwards + public class BTRPathLoadPatch : ModulePatch + { + private static PropertyInfo _statusProperty; + private static GameStatus originalStatus; + protected override MethodBase GetTargetMethod() + { + _statusProperty = AccessTools.Property(typeof(AbstractGame), "Status"); + + return AccessTools.Method(typeof(GClass2911), "method_1"); + } + + [PatchPrefix] + public static void PatchPrefix() + { + originalStatus = Singleton.Instance.Status; + _statusProperty.SetValue(Singleton.Instance, GameStatus.Starting); + } + + [PatchPostfix] + public static void PatchPostfix() + { + _statusProperty.SetValue(Singleton.Instance, originalStatus); + } + } +}