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

BTR Cover Fire service and roadkill trigger implemented (!62)

Todo:

* Taxi service
* Refinement of BTR turret aim/shooting
  * BTR should switch targets if it has no line of sight to current target
* Blacklist player from entering BTR after antagonising it
  * Perhaps make blacklist persist for 3 raids (apparently on Live?)
* Code refactor because it currently looks like doodoo

Co-authored-by: Nympfonic <arys.steam@gmail.com>
Reviewed-on: SPT-AKI/Modules#62
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-19 08:47:52 +00:00 committed by chomp
parent a0f2bb9261
commit e771501cd5
6 changed files with 187 additions and 119 deletions

View File

@ -11,13 +11,15 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using UnityEngine; using UnityEngine;
using Random = UnityEngine.Random;
using BotEventHandler = GClass595;
namespace Aki.Debugging.BTR namespace Aki.Debugging.BTR
{ {
public class BTRManager : MonoBehaviour public class BTRManager : MonoBehaviour
{ {
private GameWorld gameWorld; private GameWorld gameWorld;
private BotsController botsController; private BotEventHandler botEventHandler;
private BotBTRService btrBotService; private BotBTRService btrBotService;
private BTRControllerClass btrController; private BTRControllerClass btrController;
@ -30,23 +32,22 @@ namespace Aki.Debugging.BTR
private EPlayerBtrState previousPlayerBtrState; private EPlayerBtrState previousPlayerBtrState;
private BTRSide lastInteractedBtrSide; private BTRSide lastInteractedBtrSide;
public BTRSide LastInteractedBtrSide => lastInteractedBtrSide; public BTRSide LastInteractedBtrSide => lastInteractedBtrSide;
private Coroutine _coverFireTimerCoroutine;
private BTRTurretServer btrTurretServer; private BTRTurretServer btrTurretServer;
private Transform btrTurretDefaultTargetTransform; private Transform btrTurretDefaultTargetTransform;
private Coroutine _shootingTargetCoroutine; private Coroutine _shootingTargetCoroutine;
private IPlayer currentTarget = null;
private bool isShooting = false; private bool isShooting = false;
private BulletClass btrMachineGunAmmo; private BulletClass btrMachineGunAmmo;
private Item btrMachineGunWeapon; private Item btrMachineGunWeapon;
private MethodInfo _updateTaxiPriceMethod; private MethodInfo _updateTaxiPriceMethod;
private Dictionary<ETraderServiceType, Dictionary<string, bool>> ServicePurchasedDict { get; set; }
BTRManager() BTRManager()
{ {
Type btrControllerType = typeof(BTRControllerClass); Type btrControllerType = typeof(BTRControllerClass);
_updateTaxiPriceMethod = AccessTools.GetDeclaredMethods(btrControllerType).Single(IsUpdateTaxiPriceMethod); _updateTaxiPriceMethod = AccessTools.GetDeclaredMethods(btrControllerType).Single(IsUpdateTaxiPriceMethod);
ServicePurchasedDict = new Dictionary<ETraderServiceType, Dictionary<string, bool>>();
} }
// Find `BTRControllerClass.method_9(PathDestination currentDestinationPoint, bool lastRoutePoint)` // Find `BTRControllerClass.method_9(PathDestination currentDestinationPoint, bool lastRoutePoint)`
@ -55,32 +56,6 @@ namespace Aki.Debugging.BTR
return (method.GetParameters().Length == 2 && method.GetParameters()[0].ParameterType == typeof(PathDestination)); return (method.GetParameters().Length == 2 && method.GetParameters()[0].ParameterType == typeof(PathDestination));
} }
public bool IsServicePurchased(ETraderServiceType serviceType, string traderId)
{
if (ServicePurchasedDict.TryGetValue(serviceType, out var traderDict))
{
if (traderDict.TryGetValue(traderId, out var result))
{
return result;
}
}
return false;
}
public void SetServicePurchased(ETraderServiceType serviceType, string traderId)
{
if (ServicePurchasedDict.TryGetValue(serviceType, out var traderDict))
{
traderDict[traderId] = true;
}
else
{
ServicePurchasedDict[serviceType] = new Dictionary<string, bool>();
ServicePurchasedDict[serviceType][traderId] = true;
}
}
private void Start() private void Start()
{ {
try try
@ -90,6 +65,7 @@ namespace Aki.Debugging.BTR
if (gameWorld == null) if (gameWorld == null)
{ {
Destroy(this); Destroy(this);
return;
} }
if (gameWorld.BtrController == null) if (gameWorld.BtrController == null)
@ -121,65 +97,48 @@ namespace Aki.Debugging.BTR
{ {
btrBotShooter = btrController.BotShooterBtr; btrBotShooter = btrController.BotShooterBtr;
btrBotService.Reset(); // Player will be added to Neutrals list and removed from Enemies list btrBotService.Reset(); // Player will be added to Neutrals list and removed from Enemies list
TraderServicesManager.Instance.OnTraderServicePurchased += TraderServicePurchased;
btrBotShooterInitialized = true; btrBotShooterInitialized = true;
} }
if (btrController.BotShooterBtr == null) return; if (btrController.BotShooterBtr == null) return;
if (IsAimingAtTarget() && !isShooting) if (HasTarget() && IsAimingAtTarget() && !isShooting)
{ {
_shootingTargetCoroutine = StaticManager.BeginCoroutine(ShootTarget()); _shootingTargetCoroutine = StaticManager.BeginCoroutine(ShootTarget());
} }
} }
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 // Please tell me there's a better way than this xd
public void OnPlayerInteractDoor(PlayerInteractPacket interactPacket) public void OnPlayerInteractDoor(PlayerInteractPacket interactPacket)
{ {
var playerGoIn = interactPacket.InteractionType == EInteractionType.GoIn; btrServerSide.LeftSlot0State = 0;
var playerGoOut = interactPacket.InteractionType == EInteractionType.GoOut; btrServerSide.LeftSlot1State = 0;
btrServerSide.RightSlot0State = 0;
btrServerSide.RightSlot1State = 0;
if (interactPacket.SideId == 0) bool playerGoIn = interactPacket.InteractionType == EInteractionType.GoIn;
if (interactPacket.SideId == 0 && playerGoIn)
{ {
if (interactPacket.SlotId == 0) if (interactPacket.SlotId == 0)
{ {
if (playerGoIn) btrServerSide.LeftSlot0State = 1; btrServerSide.LeftSlot0State = 1;
else if (playerGoOut) btrServerSide.LeftSlot0State = 0;
} }
else if (interactPacket.SlotId == 1) else if (interactPacket.SlotId == 1)
{ {
if (playerGoIn) btrServerSide.LeftSlot1State = 1; btrServerSide.LeftSlot1State = 1;
else if (playerGoOut) btrServerSide.LeftSlot1State = 0;
} }
} }
else if (interactPacket.SideId == 1) else if (interactPacket.SideId == 1 && playerGoIn)
{ {
if (interactPacket.SlotId == 0) if (interactPacket.SlotId == 0)
{ {
if (playerGoIn) btrServerSide.RightSlot0State = 1; btrServerSide.RightSlot0State = 1;
else if (playerGoOut) btrServerSide.RightSlot0State = 0;
} }
else if (interactPacket.SlotId == 1) else if (interactPacket.SlotId == 1)
{ {
if (playerGoIn) btrServerSide.RightSlot1State = 1; btrServerSide.RightSlot1State = 1;
else if (playerGoOut) btrServerSide.RightSlot1State = 0;
} }
} }
} }
@ -187,19 +146,20 @@ namespace Aki.Debugging.BTR
private void InitBTR() private void InitBTR()
{ {
// Initial setup // Initial setup
botsController = Singleton<IBotGame>.Instance.BotsController; botEventHandler = Singleton<BotEventHandler>.Instance;
var botsController = Singleton<IBotGame>.Instance.BotsController;
btrBotService = botsController.BotTradersServices.BTRServices; btrBotService = botsController.BotTradersServices.BTRServices;
var btrControllerType = btrController.GetType(); btrController.method_3(); // spawns server-side BTR game object
AccessTools.Method(btrControllerType, "method_3").Invoke(btrController, null); // spawns server-side BTR game object
botsController.BotSpawner.SpawnBotBTR(); // spawns the scav bot which controls the BTR's turret botsController.BotSpawner.SpawnBotBTR(); // spawns the scav bot which controls the BTR's turret
// Initial BTR configuration // Initial BTR configuration
btrServerSide = btrController.BtrVehicle; btrServerSide = btrController.BtrVehicle;
btrServerSide.transform.Find("KillBox").gameObject.AddComponent<BTRRoadKillTrigger>();
btrServerSide.moveSpeed = 20f; btrServerSide.moveSpeed = 20f;
var btrMapConfig = btrController.MapPathsConfiguration; var btrMapConfig = btrController.MapPathsConfiguration;
btrServerSide.CurrentPathConfig = btrMapConfig.PathsConfiguration.pathsConfigurations.RandomElement(); btrServerSide.CurrentPathConfig = btrMapConfig.PathsConfiguration.pathsConfigurations.RandomElement();
btrServerSide.Initialization(btrMapConfig); btrServerSide.Initialization(btrMapConfig);
AccessTools.Method(btrControllerType, "method_14").Invoke(btrController, null); // creates and assigns the BTR a fake stash btrController.method_14(); // creates and assigns the BTR a fake stash
DisableServerSideRenderers(); DisableServerSideRenderers();
@ -233,6 +193,47 @@ namespace Aki.Debugging.BTR
_updateTaxiPriceMethod.Invoke(btrController, new object[] { destinationPoint, isFinal }); _updateTaxiPriceMethod.Invoke(btrController, new object[] { destinationPoint, isFinal });
} }
private void TraderServicePurchased(ETraderServiceType serviceType)
{
switch (serviceType)
{
case ETraderServiceType.BtrBotCover:
List<Player> passengers = gameWorld.AllAlivePlayersList.Where(x => x.BtrState == EPlayerBtrState.Inside).ToList();
botEventHandler.ApplyTraderServiceBtrSupport(passengers);
StartCoverFireTimer(90f);
break;
}
}
private void StartCoverFireTimer(float time)
{
_coverFireTimerCoroutine = StaticManager.BeginCoroutine(CoverFireTimer(time));
}
private IEnumerator CoverFireTimer(float time)
{
yield return new WaitForSecondsRealtime(time);
botEventHandler.StopTraderServiceBtrSupport();
}
private 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;
}
private void UpdateBTRSideDoorState(byte state) private void UpdateBTRSideDoorState(byte state)
{ {
var player = gameWorld.MainPlayer; var player = gameWorld.MainPlayer;
@ -243,13 +244,17 @@ namespace Aki.Debugging.BTR
if (player.BtrInteractionSide != null && btrSides[i] == player.BtrInteractionSide if (player.BtrInteractionSide != null && btrSides[i] == player.BtrInteractionSide
|| lastInteractedBtrSide != null && btrSides[i] == lastInteractedBtrSide) || lastInteractedBtrSide != null && btrSides[i] == lastInteractedBtrSide)
{ {
if (i == 0) btrServerSide.LeftSideState = state; switch (i)
else if (i == 1) btrServerSide.RightSideState = state;
if (lastInteractedBtrSide != player.BtrInteractionSide)
{ {
lastInteractedBtrSide = player.BtrInteractionSide; case 0:
btrServerSide.LeftSideState = state;
break;
case 1:
btrServerSide.RightSideState = state;
break;
} }
lastInteractedBtrSide = player.BtrInteractionSide;
} }
} }
} }
@ -292,50 +297,64 @@ namespace Aki.Debugging.BTR
} }
} }
private bool IsAimingAtTarget() private bool HasTarget()
{ {
var turretInDefaultRotation = btrTurretServer.targetTransform == btrTurretDefaultTargetTransform
&& btrTurretServer.targetPosition == btrTurretServer.defaultAimingPosition;
var enemies = btrBotShooter.BotsGroup.Enemies; var enemies = btrBotShooter.BotsGroup.Enemies;
if (enemies.Any()) if (enemies.Any())
{ {
IPlayer currentTarget = enemies.First().Key; currentTarget = enemies.First().Key;
Transform currentTargetTransform = currentTarget.Transform.Original;
Vector3 currentTargetPosition = currentTargetTransform.position;
var currentTargetInfo = btrBotShooter.EnemiesController.EnemyInfos[currentTarget];
if (!currentTarget.HealthController.IsAlive) if (!currentTarget.HealthController.IsAlive)
{ {
enemies.Remove(currentTarget); enemies.Remove(currentTarget);
currentTarget = null;
return false;
} }
return true;
}
return false;
}
private bool IsAimingAtTarget()
{
bool turretInDefaultRotation = btrTurretServer.targetTransform == btrTurretDefaultTargetTransform
&& btrTurretServer.targetPosition == btrTurretServer.defaultAimingPosition;
if (currentTarget != null)
{
Transform currentTargetTransform = currentTarget.Transform.Original;
EnemyInfo currentTargetInfo = btrBotShooter.EnemiesController.EnemyInfos[currentTarget];
if (currentTargetInfo.IsVisible) if (currentTargetInfo.IsVisible)
{ {
Vector3 currentTargetPosition = currentTargetTransform.position;
if (btrTurretServer.CheckPositionInAimingZone(currentTargetPosition)) if (btrTurretServer.CheckPositionInAimingZone(currentTargetPosition))
{ {
btrTurretServer.EnableAimingObject(currentTargetTransform); // If turret machine gun aim is close enough to target and has line of sight
} if (btrTurretServer.targetTransform == currentTargetTransform && btrBotShooter.BotBtrData.CanShoot())
// If turret machine gun aim is close enough to target and has line of sight {
if (btrBotShooter.BotBtrData.CanShoot()) return true;
{ }
return true;
if (btrTurretServer.targetTransform != currentTargetTransform)
{
btrTurretServer.EnableAimingObject(currentTargetTransform);
}
} }
} }
else if (!currentTargetInfo.IsVisible) // Turret will hold the angle where target was last seen for 3 seconds before resetting its rotation
else if (btrTurretServer.targetPosition != currentTargetInfo.EnemyLastPosition && btrTurretServer.targetTransform != null)
{ {
// Turret will hold the angle where target was last seen for 3 seconds before resetting its rotation btrTurretServer.EnableAimingPosition(currentTargetInfo.EnemyLastPosition);
if (btrTurretServer.targetPosition != currentTargetInfo.EnemyLastPosition && btrTurretServer.targetTransform != null) }
{ else if (currentTargetInfo.TimeLastSeen >= 3f && !turretInDefaultRotation)
btrTurretServer.EnableAimingPosition(currentTargetInfo.EnemyLastPosition); {
} currentTarget = null;
else if (currentTargetInfo.TimeLastSeen >= 3f && !turretInDefaultRotation) btrTurretServer.DisableAiming();
{
btrTurretServer.DisableAiming();
}
} }
} }
else if (!enemies.Any() && !turretInDefaultRotation) else if (!turretInDefaultRotation)
{ {
btrTurretServer.DisableAiming(); btrTurretServer.DisableAiming();
} }
@ -351,16 +370,22 @@ namespace Aki.Debugging.BTR
isShooting = true; isShooting = true;
Transform machineGunMuzzle = btrTurretServer.machineGunLaunchPoint; Transform machineGunMuzzle = btrTurretServer.machineGunLaunchPoint;
Player btrBotPlayer = btrBotShooter.GetPlayer;
gameWorld.SharedBallisticsCalculator.Shoot(btrMachineGunAmmo, machineGunMuzzle.position, machineGunMuzzle.forward, btrBotPlayer.ProfileId, btrMachineGunWeapon, 1f, 0);
Player.FirearmController firearmController = btrBotShooter.GetComponent<Player.FirearmController>(); Player.FirearmController firearmController = btrBotShooter.GetComponent<Player.FirearmController>();
WeaponPrefab weaponPrefab = (WeaponPrefab)AccessTools.Field(firearmController.GetType(), "weaponPrefab_0").GetValue(firearmController); WeaponPrefab weaponPrefab = (WeaponPrefab)AccessTools.Field(firearmController.GetType(), "weaponPrefab_0").GetValue(firearmController);
WeaponSoundPlayer weaponSoundPlayer = weaponPrefab.GetComponent<WeaponSoundPlayer>(); WeaponSoundPlayer weaponSoundPlayer = weaponPrefab.GetComponent<WeaponSoundPlayer>();
AccessTools.Method(firearmController.GetType(), "method_54").Invoke(firearmController, new object[] { weaponSoundPlayer, btrMachineGunAmmo, machineGunMuzzle.position, machineGunMuzzle.forward, false });
yield return new WaitForSecondsRealtime(0.092308f); // 650 RPM int burstCount = Random.Range(5, 8);
while (burstCount > 0)
{
gameWorld.SharedBallisticsCalculator.Shoot(btrMachineGunAmmo, machineGunMuzzle.position, machineGunMuzzle.forward, btrBotShooter.ProfileId, btrMachineGunWeapon, 1f, 0);
firearmController.method_54(weaponSoundPlayer, btrMachineGunAmmo, machineGunMuzzle.position, machineGunMuzzle.forward, false);
burstCount--;
yield return new WaitForSecondsRealtime(0.092308f); // 650 RPM
}
float waitTime = Random.Range(0.8f, 1.7f); // 0.8 - 1.7 second pause between bursts
yield return new WaitForSecondsRealtime(waitTime);
isShooting = false; isShooting = false;
} }
@ -385,7 +410,13 @@ namespace Aki.Debugging.BTR
gameWorld.MainPlayer.OnBtrStateChanged -= HandleBtrDoorState; gameWorld.MainPlayer.OnBtrStateChanged -= HandleBtrDoorState;
} }
if (TraderServicesManager.Instance != null)
{
TraderServicesManager.Instance.OnTraderServicePurchased -= TraderServicePurchased;
}
StaticManager.KillCoroutine(ref _shootingTargetCoroutine); StaticManager.KillCoroutine(ref _shootingTargetCoroutine);
StaticManager.KillCoroutine(ref _coverFireTimerCoroutine);
Destroy(this); Destroy(this);
} }
} }

View File

@ -0,0 +1,38 @@
using EFT;
using EFT.Interactive;
using UnityEngine;
namespace Aki.Debugging.BTR
{
public class BTRRoadKillTrigger : DamageTrigger
{
public override bool IsStatic => false;
public override void AddPenalty(GInterface94 player)
{
}
public override void PlaySound()
{
}
public override void ProceedDamage(GInterface94 player, BodyPartCollider bodyPart)
{
bodyPart.ApplyInstantKill(new DamageInfo()
{
Damage = 9999f,
Direction = Vector3.zero,
HitCollider = bodyPart.Collider,
HitNormal = Vector3.zero,
HitPoint = Vector3.zero,
DamageType = EDamageType.Btr,
HittedBallisticCollider = bodyPart,
Player = null
});
}
public override void RemovePenalty(GInterface94 player)
{
}
}
}

View File

@ -25,10 +25,9 @@ namespace Aki.Debugging.BTR.Patches
} }
[PatchPostfix] [PatchPostfix]
public static void PatchPostfix(object __instance, int btrBotId, ref bool __result) public static void PatchPostfix(BTRTurretView __instance, int btrBotId, ref bool __result)
{ {
var gameWorld = Singleton<GameWorld>.Instance; var gameWorld = Singleton<GameWorld>.Instance;
var btrTurretView = (BTRTurretView)__instance;
foreach (var playerKeyValue in gameWorld.allAlivePlayersByID) foreach (var playerKeyValue in gameWorld.allAlivePlayersByID)
{ {
@ -49,10 +48,7 @@ namespace Aki.Debugging.BTR.Patches
foreach (var text in currentWeaponPrefab.RemoveChildrenOf) foreach (var text in currentWeaponPrefab.RemoveChildrenOf)
{ {
var transform = currentWeaponPrefab.transform.FindTransform(text); var transform = currentWeaponPrefab.transform.FindTransform(text);
if (transform != null) transform?.gameObject.SetActive(false);
{
transform.gameObject.SetActive(false);
}
} }
} }
foreach (var renderer in currentWeaponPrefab.GetComponentsInChildren<Renderer>()) foreach (var renderer in currentWeaponPrefab.GetComponentsInChildren<Renderer>())
@ -68,8 +64,8 @@ namespace Aki.Debugging.BTR.Patches
} }
var tuple = new ValueTuple<ObservedPlayerView, bool>(new ObservedPlayerView(), true); var tuple = new ValueTuple<ObservedPlayerView, bool>(new ObservedPlayerView(), true);
var btrTurretViewTupleField = AccessTools.Field(btrTurretView.GetType(), "valueTuple_0"); var btrTurretViewTupleField = AccessTools.Field(__instance.GetType(), "valueTuple_0");
btrTurretViewTupleField.SetValue(btrTurretView, tuple); btrTurretViewTupleField.SetValue(__instance, tuple);
__result = true; __result = true;
return; return;

View File

@ -4,30 +4,31 @@ using EFT;
using EFT.Vehicle; using EFT.Vehicle;
using HarmonyLib; using HarmonyLib;
using System.Reflection; using System.Reflection;
using BotEventHandler = GClass595;
namespace Aki.Debugging.BTR.Patches namespace Aki.Debugging.BTR.Patches
{ {
/// <summary> /// <summary>
/// Patches an empty method in <see cref="BTRView"/> to handle updating the BTR bot's Neutrals and Enemies lists in response to taking damage. /// Patches an empty method in <see cref="BTRView"/> to handle updating the BTR bot's Neutrals and Enemies lists in response to taking damage.
/// </summary> /// </summary>
public class BTRReceiveDamageInfoPatch : ModulePatch internal class BTRReceiveDamageInfoPatch : ModulePatch
{ {
protected override MethodBase GetTargetMethod() protected override MethodBase GetTargetMethod()
{ {
return AccessTools.Method(typeof(BTRView), "method_1"); return AccessTools.Method(typeof(BTRView), nameof(BTRView.method_1));
} }
[PatchPrefix] [PatchPrefix]
public static void PatchPrefix(DamageInfo damageInfo) private static void PatchPrefix(DamageInfo damageInfo)
{ {
var globalEvents = Singleton<GClass595>.Instance; var botEventHandler = Singleton<BotEventHandler>.Instance;
if (globalEvents == null) if (botEventHandler == null)
{ {
return; return;
} }
var shotBy = (Player)damageInfo.Player.iPlayer; var shotBy = (Player)damageInfo.Player.iPlayer;
globalEvents.InterruptTraderServiceBtrSupportByBetrayer(shotBy); botEventHandler.InterruptTraderServiceBtrSupportByBetrayer(shotBy);
} }
} }
} }

View File

@ -19,8 +19,6 @@ namespace Aki.Debugging.BTR.Utils
public static readonly string BTRMachineGunWeaponTplId = "657857faeff4c850222dff1b"; // BTR PKTM machine gun public static readonly string BTRMachineGunWeaponTplId = "657857faeff4c850222dff1b"; // BTR PKTM machine gun
public static readonly string BTRMachineGunAmmoTplId = "5e023d34e8a400319a28ed44"; // 7.62x54mmR BT public static readonly string BTRMachineGunAmmoTplId = "5e023d34e8a400319a28ed44"; // 7.62x54mmR BT
private static FieldInfo _traderAvailableServicesField = AccessTools.Field(typeof(Profile.TraderInfo), "_availableServices");
static BTRUtil() static BTRUtil()
{ {
// Sanity checks for compile time failure in the event the GClass changes // Sanity checks for compile time failure in the event the GClass changes

View File

@ -3,6 +3,7 @@ using Comfort.Common;
using EFT; using EFT;
using HarmonyLib; using HarmonyLib;
using Newtonsoft.Json; using Newtonsoft.Json;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Reflection; using System.Reflection;
using UnityEngine; using UnityEngine;
@ -13,6 +14,8 @@ namespace Aki.SinglePlayer.Utils.TraderServices
{ {
public class TraderServicesManager public class TraderServicesManager
{ {
public event Action<ETraderServiceType> OnTraderServicePurchased; // Subscribe to this event to trigger trader service logic
private static TraderServicesManager _instance; private static TraderServicesManager _instance;
public static TraderServicesManager Instance public static TraderServicesManager Instance
@ -146,6 +149,7 @@ namespace Aki.SinglePlayer.Utils.TraderServices
_servicePurchased[serviceType] = new Dictionary<string, bool>(); _servicePurchased[serviceType] = new Dictionary<string, bool>();
_servicePurchased[serviceType][traderId] = true; _servicePurchased[serviceType][traderId] = true;
} }
OnTraderServicePurchased.Invoke(serviceType);
} }
public bool IsServicePurchased(ETraderServiceType serviceType, string traderId) public bool IsServicePurchased(ETraderServiceType serviceType, string traderId)