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

Player will receive notifications when purchasing BTR service or when they are blacklisted from the BTR (!63)

Todo:

* Taxi service
* Improve turret aim/firing
  * Switch targets when current target out of line of sight
* Allow certain aspects to be configured from server, for example: price multipliers, wait times at each location, etc.
* Perhaps, a persisting blacklist from the BTR for several raids afterwards
* Further code refactoring

Co-authored-by: Nympfonic <arys.steam@gmail.com>
Reviewed-on: SPT-AKI/Modules#63
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-20 09:20:32 +00:00 committed by chomp
parent e771501cd5
commit 879d90b71f
15 changed files with 187 additions and 161 deletions

View File

@ -24,6 +24,7 @@
<Reference Include="UnityEngine.UI" HintPath="..\Shared\Managed\UnityEngine.UI.dll" Private="False" />
<Reference Include="UnityEngine.UIModule" HintPath="..\Shared\Managed\UnityEngine.UIModule.dll" Private="False" />
<Reference Include="Comfort.Common" HintPath="..\Shared\Managed\Comfort.dll" Private="False" />
<Reference Include="DissonanceVoip" HintPath="..\Shared\Managed\DissonanceVoip.dll" Private="False" />
</ItemGroup>
<ItemGroup>

View File

@ -92,24 +92,28 @@ namespace Aki.Debugging.BTR
{
btrController.SyncBTRVehicleFromServer(UpdateDataPacket());
// BotShooterBtr doesn't get assigned to BtrController immediately so we nullcheck this in Update
if (btrController.BotShooterBtr != null && !btrBotShooterInitialized)
if (btrController.BotShooterBtr == null) return;
// BotShooterBtr doesn't get assigned to BtrController immediately so we check this in Update
if (!btrBotShooterInitialized)
{
btrBotShooter = btrController.BotShooterBtr;
btrBotService.Reset(); // Player will be added to Neutrals list and removed from Enemies list
TraderServicesManager.Instance.OnTraderServicePurchased += TraderServicePurchased;
TraderServicesManager.Instance.OnTraderServicePurchased += BTRTraderServicePurchased;
btrBotShooterInitialized = true;
}
if (btrController.BotShooterBtr == null) return;
if (HasTarget() && IsAimingAtTarget() && !isShooting)
{
_shootingTargetCoroutine = StaticManager.BeginCoroutine(ShootTarget());
}
if (_coverFireTimerCoroutine != null && ShouldCancelCoverFireSupport())
{
CancelCoverFireSupport();
}
}
// Please tell me there's a better way than this xd
public void OnPlayerInteractDoor(PlayerInteractPacket interactPacket)
{
btrServerSide.LeftSlot0State = 0;
@ -193,15 +197,37 @@ namespace Aki.Debugging.BTR
_updateTaxiPriceMethod.Invoke(btrController, new object[] { destinationPoint, isFinal });
}
private void TraderServicePurchased(ETraderServiceType serviceType)
private bool IsBtrService(ETraderServiceType serviceType)
{
if (serviceType == ETraderServiceType.BtrItemsDelivery
|| serviceType == ETraderServiceType.PlayerTaxi
|| serviceType == ETraderServiceType.BtrBotCover)
{
return true;
}
return false;
}
private void BTRTraderServicePurchased(ETraderServiceType serviceType)
{
if (!IsBtrService(serviceType))
{
return;
}
List<Player> passengers = gameWorld.AllAlivePlayersList.Where(x => x.BtrState == EPlayerBtrState.Inside).ToList();
List<int> playersToNotify = passengers.Select(x => x.Id).ToList();
btrController.method_6(playersToNotify, serviceType); // notify BTR passengers that a service has been purchased
switch (serviceType)
{
case ETraderServiceType.BtrBotCover:
List<Player> passengers = gameWorld.AllAlivePlayersList.Where(x => x.BtrState == EPlayerBtrState.Inside).ToList();
botEventHandler.ApplyTraderServiceBtrSupport(passengers);
StartCoverFireTimer(90f);
break;
case ETraderServiceType.PlayerTaxi:
break;
}
}
@ -210,6 +236,23 @@ namespace Aki.Debugging.BTR
_coverFireTimerCoroutine = StaticManager.BeginCoroutine(CoverFireTimer(time));
}
private bool ShouldCancelCoverFireSupport()
{
var friendlyPlayersByBtrSupport = (List<Player>)AccessTools.Field(btrBotService.GetType(), "_friendlyPlayersByBtrSupport").GetValue(btrBotService);
if (!friendlyPlayersByBtrSupport.Any())
{
return true;
}
return false;
}
private void CancelCoverFireSupport()
{
StaticManager.KillCoroutine(ref _coverFireTimerCoroutine);
botEventHandler.StopTraderServiceBtrSupport();
}
private IEnumerator CoverFireTimer(float time)
{
yield return new WaitForSecondsRealtime(time);
@ -331,7 +374,6 @@ namespace Aki.Debugging.BTR
Vector3 currentTargetPosition = currentTargetTransform.position;
if (btrTurretServer.CheckPositionInAimingZone(currentTargetPosition))
{
// If turret machine gun aim is close enough to target and has line of sight
if (btrTurretServer.targetTransform == currentTargetTransform && btrBotShooter.BotBtrData.CanShoot())
{
return true;
@ -389,6 +431,11 @@ namespace Aki.Debugging.BTR
isShooting = false;
}
private void OnDestroy()
{
DestroyGameObjects();
}
private void DestroyGameObjects()
{
if (btrController != null)
@ -412,7 +459,7 @@ namespace Aki.Debugging.BTR
if (TraderServicesManager.Instance != null)
{
TraderServicesManager.Instance.OnTraderServicePurchased -= TraderServicePurchased;
TraderServicesManager.Instance.OnTraderServicePurchased -= BTRTraderServicePurchased;
}
StaticManager.KillCoroutine(ref _shootingTargetCoroutine);

View File

@ -1,19 +1,16 @@
using Aki.Debugging.BTR.Utils;
using Aki.Reflection.Patching;
using Aki.Reflection.Utils;
using Aki.Reflection.Patching;
using Comfort.Common;
using EFT;
using EFT.UI.Screens;
using EFT.Vehicle;
using HarmonyLib;
using System;
using System.Linq;
using System.Reflection;
using static EFT.UI.TraderDialogScreen;
namespace Aki.Debugging.BTR.Patches
{
public class BTRActivateTraderDialogPatch : ModulePatch
internal class BTRActivateTraderDialogPatch : ModulePatch
{
private static FieldInfo _playerInventoryControllerField;
private static FieldInfo _playerQuestControllerField;
@ -40,7 +37,7 @@ namespace Aki.Debugging.BTR.Patches
}
[PatchPrefix]
public static bool PatchPrefix()
private static bool PatchPrefix()
{
var gameWorld = Singleton<GameWorld>.Instance;
var player = gameWorld.MainPlayer;

View File

@ -15,9 +15,8 @@ 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, 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
internal class BTRBotAttachPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
@ -25,20 +24,19 @@ namespace Aki.Debugging.BTR.Patches
}
[PatchPrefix]
public static bool PatchPrefix(object __instance, int btrBotId)
private static bool PatchPrefix(BTRTurretView __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);
var __instanceTupleField = (ValueTuple<ObservedPlayerView, bool>)AccessTools.Field(__instance.GetType(), "valueTuple_0")
.GetValue(__instance);
if (!btrTurretViewTupleField.Item2)
if (!__instanceTupleField.Item2)
{
var btrTurretViewMethod = AccessTools.Method(btrTurretView.GetType(), "method_1");
btrTurretViewMethod.Invoke(btrTurretView, new object[] { btrBotId });
var __instanceMethod = AccessTools.Method(__instance.GetType(), "method_1");
__instanceMethod.Invoke(__instance, new object[] { btrBotId });
}
if (!btrTurretViewTupleField.Item2)
if (!__instanceTupleField.Item2)
{
return false;
}
@ -50,12 +48,12 @@ namespace Aki.Debugging.BTR.Patches
}
try
{
var botRootTransform = btrTurretView.BotRoot;
var botRootTransform = __instance.BotRoot;
btrBot.Transform.position = botRootTransform.position;
var aiFirearmController = btrBot.gameObject.GetComponent<Player.FirearmController>();
var currentWeaponPrefab = (WeaponPrefab)AccessTools.Field(aiFirearmController.GetType(), "weaponPrefab_0").GetValue(aiFirearmController);
var firearmController = btrBot.gameObject.GetComponent<Player.FirearmController>();
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);

View File

@ -1,13 +1,14 @@
using Aki.Reflection.Patching;
using Comfort.Common;
using EFT;
using EFT.NextObservedPlayer;
using EFT.UI;
using EFT.Vehicle;
using EFT;
using HarmonyLib;
using System;
using System.Linq;
using System.Reflection;
using UnityEngine;
using Comfort.Common;
using System;
using EFT.NextObservedPlayer;
namespace Aki.Debugging.BTR.Patches
{
@ -17,7 +18,7 @@ namespace Aki.Debugging.BTR.Patches
// 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
internal class BTRBotInitPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
@ -25,23 +26,29 @@ namespace Aki.Debugging.BTR.Patches
}
[PatchPostfix]
public static void PatchPostfix(BTRTurretView __instance, int btrBotId, ref bool __result)
private static void PatchPostfix(BTRTurretView __instance, int btrBotId, ref bool __result)
{
var gameWorld = Singleton<GameWorld>.Instance;
foreach (var playerKeyValue in gameWorld.allAlivePlayersByID)
if (gameWorld == null)
{
if (playerKeyValue.Value.Id == btrBotId)
return;
}
var alivePlayersList = gameWorld.AllAlivePlayersList;
bool doesBtrBotExist = alivePlayersList.Exists(x => x.Id == btrBotId);
if (doesBtrBotExist)
{
try
{
Renderer[] array = playerKeyValue.Value.GetComponentsInChildren<Renderer>();
Player player = alivePlayersList.First(x => x.Id == btrBotId);
Renderer[] array = player.GetComponentsInChildren<Renderer>();
for (int i = 0; i < array.Length; i++)
{
array[i].enabled = false;
}
var aiFirearmController = playerKeyValue.Value.gameObject.GetComponent<Player.FirearmController>();
var aiFirearmController = player.gameObject.GetComponent<Player.FirearmController>();
var currentWeaponPrefab = (WeaponPrefab)AccessTools.Field(aiFirearmController.GetType(), "weaponPrefab_0").GetValue(aiFirearmController);
if (currentWeaponPrefab.RemoveChildrenOf != null)
{
@ -68,7 +75,6 @@ namespace Aki.Debugging.BTR.Patches
btrTurretViewTupleField.SetValue(__instance, tuple);
__result = true;
return;
}
catch
{
@ -78,5 +84,4 @@ namespace Aki.Debugging.BTR.Patches
}
}
}
}
}

View File

@ -3,12 +3,11 @@ using Comfort.Common;
using EFT;
using EFT.Vehicle;
using HarmonyLib;
using System;
using System.Reflection;
namespace Aki.Debugging.BTR.Patches
{
public class BTRExtractPassengersPatch : ModulePatch
internal class BTRExtractPassengersPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
@ -16,14 +15,12 @@ namespace Aki.Debugging.BTR.Patches
}
[PatchPrefix]
public static void PatchPrefix()
private static void PatchPrefix()
{
var gameWorld = Singleton<GameWorld>.Instance;
var player = gameWorld.MainPlayer;
var btrManager = gameWorld.GetComponent<BTRManager>();
try
{
var btrSide = btrManager.LastInteractedBtrSide;
if (btrSide == null)
{
@ -38,7 +35,7 @@ namespace Aki.Debugging.BTR.Patches
BTRView btrView = gameWorld.BtrController.BtrView;
if (btrView == null)
{
throw new NullReferenceException("BtrView not found");
return;
}
btrManager.OnPlayerInteractDoor(interactionBtrPacket);
@ -46,10 +43,5 @@ namespace Aki.Debugging.BTR.Patches
}
}
}
catch (Exception ex19)
{
UnityEngine.Debug.LogException(ex19);
}
}
}
}

View File

@ -1,14 +1,15 @@
using Aki.Reflection.Patching;
using Comfort.Common;
using EFT;
using EFT.GlobalEvents;
using EFT.Vehicle;
using HarmonyLib;
using System;
using System.Reflection;
using GlobalEventHandler = GClass2909;
namespace Aki.Debugging.BTR.Patches
{
public class BTRInteractionPatch : ModulePatch
internal class BTRInteractionPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
@ -24,32 +25,33 @@ namespace Aki.Debugging.BTR.Patches
}
[PatchPostfix]
public static void PatchPostfix(object __instance, BTRSide btr, byte placeId, EInteractionType interaction)
private static void PatchPostfix(Player __instance, BTRSide btr, byte placeId, EInteractionType interaction)
{
var gameWorld = Singleton<GameWorld>.Instance;
var player = (Player)__instance;
var btrManager = gameWorld.GetComponent<BTRManager>();
try
{
var interactionBtrPacket = btr.GetInteractWithBtrPacket(placeId, interaction);
player.UpdateInteractionCast();
__instance.UpdateInteractionCast();
// Prevent player from entering BTR when blacklisted
var btrBot = gameWorld.BtrController.BotShooterBtr;
if (btrBot.BotsGroup.Enemies.ContainsKey(__instance))
{
// Notify player they are blacklisted from entering BTR
GlobalEventHandler.CreateEvent<BtrNotificationInteractionMessageEvent>().Invoke(__instance.Id, EBtrInteractionStatus.Blacklisted);
return;
}
if (interactionBtrPacket.HasInteraction)
{
BTRView btrView = gameWorld.BtrController.BtrView;
if (btrView == null)
{
throw new NullReferenceException("BtrView not found");
return;
}
btrManager.OnPlayerInteractDoor(interactionBtrPacket);
btrView.Interaction(player, interactionBtrPacket);
}
}
catch (Exception ex19)
{
UnityEngine.Debug.LogException(ex19);
btrView.Interaction(__instance, interactionBtrPacket);
}
}
}

View File

@ -6,7 +6,7 @@ using System.Reflection;
namespace Aki.Debugging.BTR.Patches
{
public class BTRIsDoorsClosedPath : ModulePatch
internal class BTRIsDoorsClosedPath : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
@ -14,7 +14,7 @@ namespace Aki.Debugging.BTR.Patches
}
[PatchPrefix]
public static bool PatchPrefix(ref bool __result)
private static bool PatchPrefix(ref bool __result)
{
var serverSideBTR = Singleton<GameWorld>.Instance?.BtrController.BtrVehicle;
if (serverSideBTR == null)

View File

@ -10,7 +10,7 @@ namespace Aki.Debugging.BTR.Patches
/// <summary>
/// Adds a BTRManager component to the GameWorld game object when raid starts.
/// </summary>
public class BTRPatch : ModulePatch
internal class BTRPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
@ -18,7 +18,7 @@ namespace Aki.Debugging.BTR.Patches
}
[PatchPostfix]
public static void PatchPostfix()
private static void PatchPostfix()
{
try
{

View File

@ -8,7 +8,7 @@ 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
internal class BTRPathLoadPatch : ModulePatch
{
private static PropertyInfo _statusProperty;
private static GameStatus originalStatus;
@ -20,14 +20,14 @@ namespace Aki.Debugging.BTR.Patches
}
[PatchPrefix]
public static void PatchPrefix()
private static void PatchPrefix()
{
originalStatus = Singleton<AbstractGame>.Instance.Status;
_statusProperty.SetValue(Singleton<AbstractGame>.Instance, GameStatus.Starting);
}
[PatchPostfix]
public static void PatchPostfix()
private static void PatchPostfix()
{
_statusProperty.SetValue(Singleton<AbstractGame>.Instance, originalStatus);
}

View File

@ -1,15 +1,13 @@
using Aki.Debugging.BTR.Utils;
using Aki.Reflection.Patching;
using Aki.SinglePlayer.Utils.TraderServices;
using Comfort.Common;
using EFT;
using EFT.UI;
using HarmonyLib;
using System.Reflection;
namespace Aki.Debugging.BTR.Patches
{
public class BTRTransferItemsPatch : ModulePatch
internal class BTRTransferItemsPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
@ -18,7 +16,7 @@ namespace Aki.Debugging.BTR.Patches
}
[PatchPostfix]
public static void PatchPostfix(bool ___bool_1)
private static void PatchPostfix(bool ___bool_1)
{
// Didn't extract items
if (!___bool_1)

View File

@ -6,20 +6,20 @@ using UnityEngine;
namespace Aki.Debugging.BTR.Patches
{
public class BTRTurretCanShootPatch : ModulePatch
internal class BTRTurretCanShootPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(BTRTurretServer), "method_1");
return AccessTools.Method(typeof(BTRTurretServer), nameof(BTRTurretServer.method_1));
}
[PatchPrefix]
public static bool PatchPrefix(BTRTurretServer __instance)
private static bool PatchPrefix(BTRTurretServer __instance)
{
Transform defaultTargetTransform = (Transform)AccessTools.Field(__instance.GetType(), "defaultTargetTransform").GetValue(__instance);
bool flag = __instance.targetTransform != null && __instance.targetTransform != defaultTargetTransform;
bool flag2 = (bool)AccessTools.Method(__instance.GetType(), "method_2").Invoke(__instance, null);
bool flag2 = __instance.method_2();
bool flag3 = __instance.targetPosition != __instance.defaultAimingPosition;
var isCanShootProperty = AccessTools.DeclaredProperty(__instance.GetType(), nameof(__instance.IsCanShoot));

View File

@ -6,7 +6,7 @@ using UnityEngine;
namespace Aki.Debugging.BTR.Patches
{
public class BTRTurretDefaultAimingPositionPatch : ModulePatch
internal class BTRTurretDefaultAimingPositionPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
@ -14,7 +14,7 @@ namespace Aki.Debugging.BTR.Patches
}
[PatchPrefix]
public static bool PatchPrefix(BTRTurretServer __instance)
private static bool PatchPrefix(BTRTurretServer __instance)
{
__instance.defaultAimingPosition = Vector3.zero;

View File

@ -1,15 +1,7 @@
using Aki.Common.Http;
using Comfort.Common;
using Comfort.Common;
using EFT;
using EFT.InventoryLogic;
using HarmonyLib;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;
using static BackendConfigSettingsClass;
using TraderServiceClass = GClass1789;
namespace Aki.Debugging.BTR.Utils
{
@ -19,13 +11,6 @@ namespace Aki.Debugging.BTR.Utils
public static readonly string BTRMachineGunWeaponTplId = "657857faeff4c850222dff1b"; // BTR PKTM machine gun
public static readonly string BTRMachineGunAmmoTplId = "5e023d34e8a400319a28ed44"; // 7.62x54mmR BT
static BTRUtil()
{
// Sanity checks for compile time failure in the event the GClass changes
_ = nameof(TraderServiceClass.CanAfford);
_ = nameof(TraderServiceClass.WasPurchasedInThisRaid);
}
/// <summary>
/// Used to create an instance of the item in-raid.
/// </summary>

View File

@ -1,11 +1,9 @@
using Aki.Common.Http;
using Comfort.Common;
using EFT;
using HarmonyLib;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;
using static BackendConfigSettingsClass;
using TraderServiceClass = GClass1789;
@ -14,7 +12,10 @@ namespace Aki.SinglePlayer.Utils.TraderServices
{
public class TraderServicesManager
{
public event Action<ETraderServiceType> OnTraderServicePurchased; // Subscribe to this event to trigger trader service logic
/// <summary>
/// Subscribe to this event to trigger trader service logic.
/// </summary>
public event Action<ETraderServiceType> OnTraderServicePurchased;
private static TraderServicesManager _instance;