0
0
mirror of https://github.com/sp-tarkov/modules.git synced 2025-02-13 02:50:45 -05:00

Player can now enter and exit the BTR (!51)

Todo:

* BTR Driver services
  * Send to items to player stash (2x4)
  * Provide covering fire
  * Taxi to a specific location on Streets
* BTR turns hostile if you shoot it
* BTR automatically follows a set path from raid start, stopping at locations for 90 seconds each stop

Co-authored-by: Nympfonic <arys.steam@gmail.com>
Reviewed-on: SPT-AKI/Modules#51
Co-authored-by: Arys <arys@noreply.dev.sp-tarkov.com>
Co-committed-by: Arys <arys@noreply.dev.sp-tarkov.com>
This commit is contained in:
Arys 2024-01-04 08:51:06 +00:00 committed by chomp
parent e4f91a58e4
commit 33e6c151af
8 changed files with 225 additions and 6 deletions

View File

@ -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)

View File

@ -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<GameWorld>.Instance;
gameWorld = Singleton<GameWorld>.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);
}
}

View File

@ -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;
}
}

View File

@ -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
{

View File

@ -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<GameWorld>.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>();
btrManager.OnPlayerInteractDoor(interactionBtrPacket);
btrView.Interaction(player, interactionBtrPacket);
}
}
catch (Exception ex19)
{
UnityEngine.Debug.LogException(ex19);
}
}
}
}

View File

@ -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<GameWorld>.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;
}
}
}

View File

@ -6,7 +6,9 @@ using EFT.UI;
namespace Aki.Debugging.BTR.Patches
{
// Adds a BTRManager component to the GameWorld game object when raid starts.
/// <summary>
/// Adds a BTRManager component to the GameWorld game object when raid starts.
/// </summary>
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;
}
}

View File

@ -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<AbstractGame>.Instance.Status;
_statusProperty.SetValue(Singleton<AbstractGame>.Instance, GameStatus.Starting);
}
[PatchPostfix]
public static void PatchPostfix()
{
_statusProperty.SetValue(Singleton<AbstractGame>.Instance, originalStatus);
}
}
}