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

BTR Progress Update (!50)

Current progress:

* BTR spawns in, clientside BTR is synced with serverside BTR
* BTR Bot spawns in, is assigned to BTRController (GClass2911) correctly
* BTR Bot is initialized properly (invisible, makes no sound, doesn't shoot at player)

Todo:

BTR is currently missing functionality

* It needs to allow player to enter and exit
* Player should be able to interact with BTR driver/open BTR stash UI/pay money to get taxi'd to a chosen location/pay money for it to provide covering fire
* Turret should rotate to shoot player if they attack it
* It should be able to drive on its own from raid start, going from location to location, on a set path

Co-authored-by: Nympfonic <arys.steam@gmail.com>
Reviewed-on: SPT-AKI/Modules#50
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-03 16:06:50 +00:00 committed by chomp
parent a31f19755f
commit e4f91a58e4
6 changed files with 335 additions and 71 deletions

View File

@ -1,5 +1,6 @@
using System; using System;
using Aki.Common; using Aki.Common;
using Aki.Debugging.BTR.Patches;
using Aki.Debugging.Patches; using Aki.Debugging.Patches;
using BepInEx; using BepInEx;
@ -16,8 +17,10 @@ namespace Aki.Debugging
{ {
new EndRaidDebug().Enable(); new EndRaidDebug().Enable();
// new CoordinatesPatch().Enable(); // new CoordinatesPatch().Enable();
// new StaticLootDumper().Enable(); // new StaticLootDumper().Enable();
new BtrTestPatch().Enable(); new BTRBotAttachPatch().Enable();
new BTRBotInitPatch().Enable();
new BTRPatch().Enable();
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@ -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<GameWorld>.Instance;
if (gameWorld == null)
{
Destroy(this);
}
if (gameWorld.BtrController == null)
{
if (!Singleton<BTRController>.Instantiated)
{
Singleton<BTRController>.Create(new BTRController());
}
gameWorld.BtrController = btrController = Singleton<BTRController>.Instance;
}
var btrControllerType = btrController.GetType();
AccessTools.Method(btrControllerType, "method_3").Invoke(btrController, null); // spawns server-side BTR game object
Singleton<IBotGame>.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<MeshRenderer>();
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);
}
}
}

View File

@ -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<GameWorld>.Instance;
var btrTurretView = (BTRTurretView)__instance;
var btrTurretViewTupleField = (ValueTuple<ObservedPlayerView, bool>)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<Player.FirearmController>();
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;
}
}
}
}

View File

@ -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<GameWorld>.Instance;
var btrTurretView = (BTRTurretView)__instance;
foreach (var playerKeyValue in gameWorld.allAlivePlayersByID)
{
if (playerKeyValue.Value.Id == btrBotId)
{
try
{
Renderer[] array = playerKeyValue.Value.GetComponentsInChildren<Renderer>();
for (int i = 0; i < array.Length; i++)
{
array[i].enabled = false;
}
var aiFirearmController = playerKeyValue.Value.gameObject.GetComponent<Player.FirearmController>();
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<Renderer>())
{
if (renderer.name == "MuzzleJetCombinedMesh")
{
renderer.transform.localPosition = new Vector3(0.18f, 0f, -0.095f);
}
else
{
renderer.enabled = false;
}
}
var tuple = new ValueTuple<ObservedPlayerView, bool>(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;
}
}
}
}
}
}

View File

@ -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<GameWorld>.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<BTRManager>();
}
catch (System.Exception)
{
ConsoleScreen.LogError("[AKI-BTR] Exception thrown, check logs.");
throw;
}
}
}
}

View File

@ -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<GameWorld>.Instance;
if (gameWorld.MainPlayer.Location.ToLower() != "tarkovstreets")
{
// only run patch on streets
return;
}
var botGame = Singleton<IBotGame>.Instance;
if (gameWorld.BtrController == null)
{
if (!Singleton<GClass2911>.Instantiated)
{
Singleton<GClass2911>.Create(new GClass2911());
}
gameWorld.BtrController = Singleton<GClass2911>.Instance;
ConsoleScreen.LogWarning($"[AKI-BTR] BtrController instance is null: {gameWorld.BtrController == null}");
ConsoleScreen.LogWarning($"[AKI-BTR] Singleton GClass2911 is null: {Singleton<GClass2911>.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;
}
}
}
}