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

3.9.0-DEV (!128)

Reviewed-on: SPT-AKI/Modules#128
This commit is contained in:
chomp 2024-05-21 17:53:20 +00:00
commit 9b7154bded
62 changed files with 484 additions and 494 deletions

View File

@ -22,7 +22,7 @@ git config --local user.email "USERNAME@SOMETHING.com"
```
## Requirements
- Escape From Tarkov 29197
- Escape From Tarkov 29862
- Visual Studio Code -OR- Visual Studio 2022
- .NET 6 SDK
- [PowerShell v7](https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell-on-windows)

View File

@ -6,8 +6,8 @@
</PropertyGroup>
<PropertyGroup>
<Company>SPT Aki</Company>
<Copyright>Copyright @ SPT Aki 2024</Copyright>
<Company>SPT</Company>
<Copyright>Copyright @ SPT 2024</Copyright>
</PropertyGroup>
<ItemGroup>

View File

@ -3,12 +3,13 @@
<PropertyGroup>
<Version>1.0.0.0</Version>
<TargetFramework>net471</TargetFramework>
<AssemblyName>spt-common</AssemblyName>
<Configuration>Release</Configuration>
</PropertyGroup>
<PropertyGroup>
<Company>Aki</Company>
<Copyright>Copyright @ Aki 2024</Copyright>
<Company>SPT</Company>
<Copyright>Copyright @ SPT 2024</Copyright>
</PropertyGroup>
<ItemGroup>

View File

@ -1,85 +0,0 @@
#region DEPRECATED, REMOVE IN 3.8.1
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using Aki.Common.Utils;
namespace Aki.Common.Http
{
[Obsolete("Request is deprecated, please use Aki.Common.Http.Client instead.")]
public class Request
{
[Obsolete("Request.Send() is deprecated, please use Aki.Common.Http.Client instead.")]
public byte[] Send(string url, string method, byte[] data = null, bool compress = true, string mime = null, Dictionary<string, string> headers = null)
{
if (!WebConstants.IsValidMethod(method))
{
throw new ArgumentException("request method is invalid");
}
Uri uri = new Uri(url);
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
if (uri.Scheme == "https")
{
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
request.ServerCertificateValidationCallback = delegate { return true; };
}
request.Timeout = 15000;
request.Method = method;
request.Headers.Add("Accept-Encoding", "deflate");
if (headers != null)
{
foreach (KeyValuePair<string, string> item in headers)
{
request.Headers.Add(item.Key, item.Value);
}
}
if (method != WebConstants.Get && method != WebConstants.Head && data != null)
{
byte[] body = (compress) ? Zlib.Compress(data, ZlibCompression.Maximum) : data;
request.ContentType = WebConstants.IsValidMime(mime) ? mime : "application/octet-stream";
request.ContentLength = body.Length;
if (compress)
{
request.Headers.Add("Content-Encoding", "deflate");
}
using (Stream stream = request.GetRequestStream())
{
stream.Write(body, 0, body.Length);
}
}
using (WebResponse response = request.GetResponse())
{
using (MemoryStream ms = new MemoryStream())
{
response.GetResponseStream().CopyTo(ms);
byte[] body = ms.ToArray();
if (body.Length == 0)
{
return null;
}
if (Zlib.IsCompressed(body))
{
return Zlib.Decompress(body);
}
return body;
}
}
}
}
}
#endregion

View File

@ -129,89 +129,5 @@ namespace Aki.Common.Http
{
return Task.Run(() => PutJsonAsync(path, json)).Result;
}
#region DEPRECATED, REMOVE IN 3.8.1
[Obsolete("GetData(path, isHost) is deprecated, please use GetData(path) instead.")]
public static byte[] GetData(string path, bool hasHost)
{
var url = (hasHost) ? path : Host + path;
_logger.LogInfo($"Request GET data: {SessionId}:{url}");
var headers = new Dictionary<string, string>()
{
{ "Cookie", $"PHPSESSID={SessionId}" },
{ "SessionId", SessionId }
};
var request = new Request();
var data = request.Send(url, "GET", null, headers: headers);
ValidateData(url, data);
return data;
}
[Obsolete("GetJson(path, isHost) is deprecated, please use GetJson(path) instead.")]
public static string GetJson(string path, bool hasHost)
{
var url = (hasHost) ? path : Host + path;
_logger.LogInfo($"Request GET json: {SessionId}:{url}");
var headers = new Dictionary<string, string>()
{
{ "Cookie", $"PHPSESSID={SessionId}" },
{ "SessionId", SessionId }
};
var request = new Request();
var data = request.Send(url, "GET", headers: headers);
var body = Encoding.UTF8.GetString(data);
ValidateJson(url, body);
return body;
}
[Obsolete("PostJson(path, json, isHost) is deprecated, please use PostJson(path, json) instead.")]
public static string PostJson(string path, string json, bool hasHost)
{
var url = (hasHost) ? path : Host + path;
_logger.LogInfo($"Request POST json: {SessionId}:{url}");
var payload = Encoding.UTF8.GetBytes(json);
var mime = WebConstants.Mime[".json"];
var headers = new Dictionary<string, string>()
{
{ "Cookie", $"PHPSESSID={SessionId}" },
{ "SessionId", SessionId }
};
var request = new Request();
var data = request.Send(url, "POST", payload, true, mime, headers);
var body = Encoding.UTF8.GetString(data);
ValidateJson(url, body);
return body;
}
[Obsolete("PutJson(path, json, isHost) is deprecated, please use PutJson(path, json) instead.")]
public static void PutJson(string path, string json, bool hasHost)
{
var url = (hasHost) ? path : Host + path;
_logger.LogInfo($"Request PUT json: {SessionId}:{url}");
var payload = Encoding.UTF8.GetBytes(json);
var mime = WebConstants.Mime[".json"];
var headers = new Dictionary<string, string>()
{
{ "Cookie", $"PHPSESSID={SessionId}" },
{ "SessionId", SessionId }
};
var request = new Request();
request.Send(url, "PUT", payload, true, mime, headers);
}
#endregion
}
}

View File

@ -1,78 +0,0 @@
#region DEPRECATED, REMOVE IN 3.8.1
using System;
using System.Collections.Generic;
using System.Linq;
namespace Aki.Common.Http
{
[Obsolete("WebConstants is deprecated, please use System.Net.Http functionality instead.")]
public static class WebConstants
{
[Obsolete("Get is deprecated, please use HttpMethod.Get instead.")]
public const string Get = "GET";
[Obsolete("Head is deprecated, please use HttpMethod.Head instead.")]
public const string Head = "HEAD";
[Obsolete("Post is deprecated, please use HttpMethod.Post instead.")]
public const string Post = "POST";
[Obsolete("Put is deprecated, please use HttpMethod.Put instead.")]
public const string Put = "PUT";
[Obsolete("Delete is deprecated, please use HttpMethod.Delete instead.")]
public const string Delete = "DELETE";
[Obsolete("Connect is deprecated, please use HttpMethod.Connect instead.")]
public const string Connect = "CONNECT";
[Obsolete("Options is deprecated, please use HttpMethod.Options instead.")]
public const string Options = "OPTIONS";
[Obsolete("Trace is deprecated, please use HttpMethod.Trace instead.")]
public const string Trace = "TRACE";
[Obsolete("Mime is deprecated, there is sadly no replacement.")]
public static Dictionary<string, string> Mime { get; private set; }
static WebConstants()
{
Mime = new Dictionary<string, string>()
{
{ ".bin", "application/octet-stream" },
{ ".txt", "text/plain" },
{ ".htm", "text/html" },
{ ".html", "text/html" },
{ ".css", "text/css" },
{ ".js", "text/javascript" },
{ ".jpeg", "image/jpeg" },
{ ".jpg", "image/jpeg" },
{ ".png", "image/png" },
{ ".ico", "image/vnd.microsoft.icon" },
{ ".json", "application/json" }
};
}
[Obsolete("IsValidMethod is deprecated, please check against HttpMethod entries instead.")]
public static bool IsValidMethod(string method)
{
return method == Get
|| method == Head
|| method == Post
|| method == Put
|| method == Delete
|| method == Connect
|| method == Options
|| method == Trace;
}
[Obsolete("isValidMime is deprecated, there is sadly no replacement available.")]
public static bool IsValidMime(string mime)
{
return Mime.Any(x => x.Value == mime);
}
}
}
#endregion

View File

@ -2,13 +2,13 @@
<PropertyGroup>
<TargetFramework>net471</TargetFramework>
<AssemblyName>aki-core</AssemblyName>
<AssemblyName>spt-core</AssemblyName>
<Configuration>Release</Configuration>
</PropertyGroup>
<PropertyGroup>
<Company>Aki</Company>
<Copyright>Copyright @ Aki 2024</Copyright>
<Company>SPT</Company>
<Copyright>Copyright @ SPT 2024</Copyright>
</PropertyGroup>
<ItemGroup>

View File

@ -15,7 +15,7 @@ namespace Aki.Core
{
_logger = Logger;
Logger.LogInfo("Loading: Aki.Core");
Logger.LogInfo("Loading: SPT.Core");
try
{
@ -36,7 +36,7 @@ namespace Aki.Core
throw;
}
Logger.LogInfo("Completed: Aki.Core");
Logger.LogInfo("Completed: SPT.Core");
}
}
}

View File

@ -8,7 +8,7 @@ namespace Aki.Core.Patches
{
public class GameValidationPatch : ModulePatch
{
private const string PluginName = "Aki.Core";
private const string PluginName = "SPT.Core";
private const string ErrorMessage = "Validation failed";
private static BepInEx.Logging.ManualLogSource _logger = null;
private static bool _hasRun = false;

View File

@ -27,9 +27,9 @@ namespace Aki.Core.Utils
new FileInfo(Path.Combine(v2, "UnityCrashHandler64.exe"))
};
ServerLog.Debug("Aki.Core", Gfs(v2, "UnityCrashHandler64.exe")?.Length.ToString() ?? "0");
ServerLog.Debug("Aki.Core", Gfs(v2, "Uninstall.exe")?.Length.ToString() ?? "0");
ServerLog.Debug("Aki.Core", Gfs(v2, "Register.bat")?.Length.ToString() ?? "0");
ServerLog.Debug("SPT.Core", Gfs(v2, "UnityCrashHandler64.exe")?.Length.ToString() ?? "0");
ServerLog.Debug("SPT.Core", Gfs(v2, "Uninstall.exe")?.Length.ToString() ?? "0");
ServerLog.Debug("SPT.Core", Gfs(v2, "Register.bat")?.Length.ToString() ?? "0");
v0 = v4.Length - 1;

View File

@ -35,7 +35,7 @@ namespace Aki.Custom.Airdrops
}
catch
{
Debug.LogError("[AKI-AIRDROPS]: Unable to get config from server, airdrop won't occur");
Debug.LogError("[SPT-AIRDROPS]: Unable to get config from server, airdrop won't occur");
Destroy(this);
throw;
}
@ -52,7 +52,7 @@ namespace Aki.Custom.Airdrops
}
catch
{
Debug.LogError("[AKI-AIRDROPS]: Unable to create plane or crate, airdrop won't occur");
Debug.LogError("[SPT-AIRDROPS]: Unable to create plane or crate, airdrop won't occur");
Destroy(this);
throw;
}
@ -98,7 +98,7 @@ namespace Aki.Custom.Airdrops
}
catch
{
Debug.LogError("[AKI-AIRDROPS]: An error occurred during the airdrop FixedUpdate process");
Debug.LogError("[SPT-AIRDROPS]: An error occurred during the airdrop FixedUpdate process");
Destroy(airdropBox.gameObject);
Destroy(airdropPlane.gameObject);
Destroy(this);

View File

@ -69,7 +69,7 @@ namespace Aki.Custom.Airdrops.Utils
break;
}
default:
Debug.LogError($"[AKI-AIRDROPS]: Map with name {playerLocation} not handled, defaulting spawn chance to 25%");
Debug.LogError($"[SPT-AIRDROPS]: Map with name {playerLocation} not handled, defaulting spawn chance to 25%");
result = 25;
break;
}
@ -104,7 +104,7 @@ namespace Aki.Custom.Airdrops.Utils
if (flareAirdropPoints.Count == 0 && isFlare)
{
Debug.LogError($"[AKI-AIRDROPS]: Airdrop called in by flare, Unable to find an airdropPoint within {flareSpawnRadiusDistance}m, defaulting to normal drop");
Debug.LogError($"[SPT-AIRDROPS]: Airdrop called in by flare, Unable to find an airdropPoint within {flareSpawnRadiusDistance}m, defaulting to normal drop");
flareAirdropPoints.Add(allAirdropPoints.OrderBy(_ => Guid.NewGuid()).FirstOrDefault());
}

View File

@ -31,7 +31,7 @@ namespace Aki.Custom.Airdrops.Utils
}
else
{
Debug.LogError($"[AKI-AIRDROPS]: unable to find template: {containerId}");
Debug.LogError($"[SPT-AIRDROPS]: unable to find template: {containerId}");
}
}

View File

@ -2,13 +2,13 @@
<PropertyGroup>
<TargetFramework>net471</TargetFramework>
<AssemblyName>aki-custom</AssemblyName>
<AssemblyName>spt-custom</AssemblyName>
<Configuration>Release</Configuration>
</PropertyGroup>
<PropertyGroup>
<Company>Aki</Company>
<Copyright>Copyright @ Aki 2024</Copyright>
<Company>SPT</Company>
<Copyright>Copyright @ SPT 2024</Copyright>
</PropertyGroup>
<ItemGroup>

View File

@ -16,7 +16,7 @@ namespace Aki.Custom
{
public void Awake()
{
Logger.LogInfo("Loading: Aki.Custom");
Logger.LogInfo("Loading: SPT.Custom");
try
{
@ -56,6 +56,7 @@ namespace Aki.Custom
new RagfairFeePatch().Enable();
new ScavQuestPatch().Enable();
new FixBrokenSpawnOnSandboxPatch().Enable();
new BTRControllerInitPatch().Enable();
new BTRPathLoadPatch().Enable();
new BTRActivateTraderDialogPatch().Enable();
new BTRInteractionPatch().Enable();
@ -70,11 +71,13 @@ namespace Aki.Custom
new BTREndRaidItemDeliveryPatch().Enable();
new BTRDestroyAtRaidEndPatch().Enable();
new BTRVehicleMovementSpeedPatch().Enable();
new BTRPathConfigMapPrefixPatch().Enable();
new ScavItemCheckmarkPatch().Enable();
new ResetTraderServicesPatch().Enable();
new CultistAmuletRemovalPatch().Enable();
new HalloweenExtractPatch().Enable();
new ClampRagdollPatch().Enable();
new DisablePvEPatch().Enable();
HookObject.AddOrGetComponent<MenuNotificationManager>();
}

View File

@ -6,11 +6,10 @@ using EFT.InventoryLogic;
using EFT.UI;
using EFT.Vehicle;
using HarmonyLib;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using UnityEngine;
using Random = UnityEngine.Random;
@ -27,6 +26,7 @@ namespace Aki.Custom.BTR
private BTRView btrClientSide;
private BotOwner btrBotShooter;
private BTRDataPacket btrDataPacket = default;
private bool btrInitialized = false;
private bool btrBotShooterInitialized = false;
private float coverFireTime = 90f;
@ -48,17 +48,9 @@ namespace Aki.Custom.BTR
private Player.FirearmController firearmController;
private WeaponSoundPlayer weaponSoundPlayer;
private MethodInfo _updateTaxiPriceMethod;
private float originalDamageCoeff;
BTRManager()
{
Type btrControllerType = typeof(BTRControllerClass);
_updateTaxiPriceMethod = AccessTools.GetDeclaredMethods(btrControllerType).Single(IsUpdateTaxiPriceMethod);
}
private void Awake()
private async void Awake()
{
try
{
@ -76,11 +68,11 @@ namespace Aki.Custom.BTR
btrController = gameWorld.BtrController;
InitBtr();
await InitBtr();
}
catch
{
ConsoleScreen.LogError("[AKI-BTR] Unable to spawn BTR. Check logs.");
ConsoleScreen.LogError("[SPT-BTR] Unable to spawn BTR. Check logs.");
Destroy(this);
throw;
}
@ -133,14 +125,10 @@ namespace Aki.Custom.BTR
}
}
// Find `BTRControllerClass.method_9(PathDestination currentDestinationPoint, bool lastRoutePoint)`
private bool IsUpdateTaxiPriceMethod(MethodInfo method)
{
return (method.GetParameters().Length == 2 && method.GetParameters()[0].ParameterType == typeof(PathDestination));
}
private void Update()
{
if (!btrInitialized) return;
btrController.SyncBTRVehicleFromServer(UpdateDataPacket());
if (btrController.BotShooterBtr == null) return;
@ -169,9 +157,11 @@ namespace Aki.Custom.BTR
}
}
private void InitBtr()
private async Task InitBtr()
{
// Initial setup
await btrController.InitBtrController();
botEventHandler = Singleton<BotEventHandler>.Instance;
var botsController = Singleton<IBotGame>.Instance.BotsController;
btrBotService = botsController.BotTradersServices.BTRServices;
@ -187,6 +177,11 @@ namespace Aki.Custom.BTR
ConfigureSettingsFromServer();
var btrMapConfig = btrController.MapPathsConfiguration;
if (btrMapConfig == null)
{
ConsoleScreen.LogError($"{nameof(btrController.MapPathsConfiguration)}");
return;
}
btrServerSide.CurrentPathConfig = btrMapConfig.PathsConfiguration.pathsConfigurations.RandomElement();
btrServerSide.Initialization(btrMapConfig);
btrController.method_14(); // creates and assigns the BTR a fake stash
@ -213,6 +208,8 @@ namespace Aki.Custom.BTR
// Pull services data for the BTR from the server
TraderServicesManager.Instance.GetTraderServicesDataFromServer(BTRUtil.BTRTraderId);
btrInitialized = true;
}
private void ConfigureSettingsFromServer()
@ -249,7 +246,7 @@ namespace Aki.Custom.BTR
TraderServicesManager.Instance.RemovePurchasedService(ETraderServiceType.PlayerTaxi, BTRUtil.BTRTraderId);
// Update the prices for the taxi service
_updateTaxiPriceMethod.Invoke(btrController, new object[] { destinationPoint, isFinal });
btrController.UpdateTaxiPrice(destinationPoint, isFinal);
// Update the UI
TraderServicesManager.Instance.GetTraderServicesDataFromServer(BTRUtil.BTRTraderId);
@ -257,14 +254,9 @@ namespace Aki.Custom.BTR
private bool IsBtrService(ETraderServiceType serviceType)
{
if (serviceType == ETraderServiceType.BtrItemsDelivery
return serviceType == ETraderServiceType.BtrItemsDelivery
|| serviceType == ETraderServiceType.PlayerTaxi
|| serviceType == ETraderServiceType.BtrBotCover)
{
return true;
}
return false;
|| serviceType == ETraderServiceType.BtrBotCover;
}
private void BtrTraderServicePurchased(ETraderServiceType serviceType, string subserviceId)
@ -338,7 +330,7 @@ namespace Aki.Custom.BTR
}
catch
{
ConsoleScreen.LogError("[AKI-BTR] lastInteractedBtrSide is null when it shouldn't be. Check logs.");
ConsoleScreen.LogError($"[SPT-BTR] {nameof(lastInteractedBtrSide)} is null when it shouldn't be. Check logs.");
throw;
}
}
@ -349,8 +341,8 @@ namespace Aki.Custom.BTR
btrDataPacket.rotation = btrServerSide.transform.rotation;
if (btrTurretServer != null && btrTurretServer.gunsBlockRoot != null)
{
btrDataPacket.turretRotation = btrTurretServer.transform.rotation;
btrDataPacket.gunsBlockRotation = btrTurretServer.gunsBlockRoot.rotation;
btrDataPacket.turretRotation = btrTurretServer.transform.localEulerAngles.y;
btrDataPacket.gunsBlockRotation = btrTurretServer.gunsBlockRoot.localEulerAngles.x;
}
btrDataPacket.State = (byte)btrServerSide.BtrState;
btrDataPacket.RouteState = (byte)btrServerSide.VehicleRouteState;
@ -390,12 +382,7 @@ namespace Aki.Custom.BTR
private bool HasTarget()
{
if (currentTarget != null)
{
return true;
}
return false;
return currentTarget != null;
}
private void SetAim()
@ -428,12 +415,7 @@ namespace Aki.Custom.BTR
private bool CanShoot()
{
if (currentTarget.IsVisible && btrBotShooter.BotBtrData.CanShoot())
{
return true;
}
return false;
return currentTarget.IsVisible && btrBotShooter.BotBtrData.CanShoot();
}
private void StartShooting()
@ -469,9 +451,11 @@ namespace Aki.Custom.BTR
{
targetHeadPos = currentTarget.Person.PlayerBones.Head.position;
}
Vector3 aimDirection = Vector3.Normalize(targetHeadPos - machineGunMuzzle.position);
ballisticCalculator.Shoot(btrMachineGunAmmo, machineGunMuzzle.position, aimDirection, btrBotShooter.ProfileId, btrMachineGunWeapon, 1f, 0);
firearmController.method_54(weaponSoundPlayer, btrMachineGunAmmo, machineGunMuzzle.position, aimDirection, false);
firearmController.PlayWeaponSound(weaponSoundPlayer, btrMachineGunAmmo, machineGunMuzzle.position, aimDirection, false);
burstCount--;
yield return new WaitForSecondsRealtime(0.092308f); // 650 RPM
}
@ -504,13 +488,13 @@ namespace Aki.Custom.BTR
if (btrClientSide != null)
{
Debug.LogWarning("[AKI-BTR] BTRManager - Destroying btrClientSide");
Debug.LogWarning($"[SPT-BTR] {nameof(BTRManager)} - Destroying btrClientSide");
Destroy(btrClientSide.gameObject);
}
if (btrServerSide != null)
{
Debug.LogWarning("[AKI-BTR] BTRManager - Destroying btrServerSide");
Debug.LogWarning($"[SPT-BTR] {nameof(BTRManager)} - Destroying btrServerSide");
Destroy(btrServerSide.gameObject);
}
}

View File

@ -8,7 +8,7 @@ namespace Aki.Custom.BTR
{
public override bool IsStatic => false;
public override void AddPenalty(GInterface94 player)
public override void AddPenalty(GInterface106 player)
{
}
@ -16,7 +16,7 @@ namespace Aki.Custom.BTR
{
}
public override void ProceedDamage(GInterface94 player, BodyPartCollider bodyPart)
public override void ProceedDamage(GInterface106 player, BodyPartCollider bodyPart)
{
bodyPart.ApplyInstantKill(new DamageInfo()
{
@ -31,7 +31,7 @@ namespace Aki.Custom.BTR
});
}
public override void RemovePenalty(GInterface94 player)
public override void RemovePenalty(GInterface106 player)
{
}
}

View File

@ -6,7 +6,7 @@ using EFT.Vehicle;
using HarmonyLib;
using System;
using System.Reflection;
using BTRDialog = EFT.UI.TraderDialogScreen.GClass3132;
using BTRDialog = EFT.UI.TraderDialogScreen.GClass3153;
namespace Aki.Custom.BTR.Patches
{
@ -28,12 +28,7 @@ namespace Aki.Custom.BTR.Patches
{
FieldInfo btrField = type.GetField("btr");
if (btrField != null && btrField.FieldType == typeof(BTRSide))
{
return true;
}
return false;
return btrField != null && btrField.FieldType == typeof(BTRSide);
}
[PatchPrefix]

View File

@ -60,7 +60,7 @@ namespace Aki.Custom.BTR.Patches
var valueTuple = (ValueTuple<ObservedPlayerView, bool>)_valueTuple0Field.GetValue(__instance);
if (!valueTuple.Item2 && !InitTurretView(__instance, turretPlayer))
{
Logger.LogError("[AKI-BTR] BTRBotAttachPatch - BtrBot initialization failed");
Logger.LogError("[SPT-BTR] BTRBotAttachPatch - BtrBot initialization failed");
return false;
}

View File

@ -0,0 +1,34 @@
using Aki.Reflection.Patching;
using HarmonyLib;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
namespace Aki.Custom.BTR.Patches
{
public class BTRControllerInitPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.FirstMethod(typeof(BTRControllerClass), IsTargetMethod);
}
private bool IsTargetMethod(MethodInfo method)
{
ParameterInfo[] parameters = method.GetParameters();
return method.ReturnType == typeof(Task)
&& parameters.Length == 1
&& parameters[0].ParameterType == typeof(CancellationToken);
}
[PatchPrefix]
private static bool PatchPrefix(ref Task __result)
{
// The BTRControllerClass constructor expects the original method to return a Task,
// as it calls another method on said Task.
__result = Task.CompletedTask;
return false;
}
}
}

View File

@ -11,7 +11,7 @@ namespace Aki.Custom.BTR.Patches
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(BaseLocalGame<GamePlayerOwner>), nameof(BaseLocalGame<GamePlayerOwner>.Stop));
return AccessTools.Method(typeof(BaseLocalGame<EftGamePlayerOwner>), nameof(BaseLocalGame<EftGamePlayerOwner>.Stop));
}
[PatchPrefix]
@ -26,7 +26,7 @@ namespace Aki.Custom.BTR.Patches
var btrManager = gameWorld.GetComponent<BTRManager>();
if (btrManager != null)
{
Logger.LogWarning("[AKI-BTR] BTRDestroyAtRaidEndPatch - Raid Ended: Destroying BTRManager");
Logger.LogWarning("[SPT-BTR] BTRDestroyAtRaidEndPatch - Raid Ended: Destroying BTRManager");
Object.Destroy(btrManager);
}
}

View File

@ -32,13 +32,13 @@ namespace Aki.Custom.BTR.Patches
GameWorld gameWorld = Singleton<GameWorld>.Instance;
if (gameWorld == null)
{
Logger.LogError("[AKI-BTR] BTREndRaidItemDeliveryPatch - GameWorld is null");
Logger.LogError("[SPT-BTR] BTREndRaidItemDeliveryPatch - GameWorld is null");
return;
}
var player = gameWorld.MainPlayer;
if (player == null)
{
Logger.LogError("[AKI-BTR] BTREndRaidItemDeliveryPatch - Player is null");
Logger.LogError("[SPT-BTR] BTREndRaidItemDeliveryPatch - Player is null");
return;
}
@ -50,7 +50,7 @@ namespace Aki.Custom.BTR.Patches
if (!gameWorld.BtrController.HasNonEmptyTransferContainer(player.Profile.Id))
{
Logger.LogDebug("[AKI-BTR] BTREndRaidItemDeliveryPatch - No items in transfer container");
Logger.LogDebug("[SPT-BTR] BTREndRaidItemDeliveryPatch - No items in transfer container");
return;
}

View File

@ -35,7 +35,7 @@ namespace Aki.Custom.BTR.Patches
BTRView btrView = gameWorld.BtrController.BtrView;
if (btrView == null)
{
Logger.LogError($"[AKI-BTR] BTRExtractPassengersPatch - btrView is null");
Logger.LogError($"[SPT-BTR] BTRExtractPassengersPatch - btrView is null");
return;
}

View File

@ -46,7 +46,7 @@ namespace Aki.Custom.BTR.Patches
BTRView btrView = gameWorld.BtrController.BtrView;
if (btrView == null)
{
Logger.LogError("[AKI-BTR] BTRInteractionPatch - btrView is null");
Logger.LogError("[SPT-BTR] BTRInteractionPatch - btrView is null");
return;
}

View File

@ -19,14 +19,14 @@ namespace Aki.Custom.BTR.Patches
var gameWorld = Singleton<GameWorld>.Instance;
if (gameWorld == null)
{
Logger.LogError("[AKI-BTR] BTRIsDoorsClosedPatch - GameWorld is null");
Logger.LogError("[SPT-BTR] BTRIsDoorsClosedPatch - GameWorld is null");
return true;
}
var serverSideBTR = gameWorld.BtrController.BtrVehicle;
if (serverSideBTR == null)
{
Logger.LogError("[AKI-BTR] BTRIsDoorsClosedPatch - serverSideBTR is null");
Logger.LogError("[SPT-BTR] BTRIsDoorsClosedPatch - serverSideBTR is null");
return true;
}

View File

@ -39,7 +39,7 @@ namespace Aki.Custom.BTR.Patches
}
catch (System.Exception)
{
ConsoleScreen.LogError("[AKI-BTR] Exception thrown, check logs.");
ConsoleScreen.LogError("[SPT-BTR] Exception thrown, check logs.");
throw;
}
}

View File

@ -0,0 +1,53 @@
using System;
using System.Reflection;
using Aki.Reflection.Patching;
using Comfort.Common;
using EFT;
using EFT.UI;
using HarmonyLib;
namespace Aki.Custom.BTR.Patches
{
/// <summary>
/// Fixes an issue where in a pathConfig.once type, finding destination path points was impossible because destinationID would be prefixed with "Map/", which the pathPoints do not contain.
/// </summary>
public class BTRPathConfigMapPrefixPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.FirstMethod(typeof(BTRControllerClass), IsTargetMethod);
}
private bool IsTargetMethod(MethodInfo method)
{
ParameterInfo[] parameters = method.GetParameters();
// BTRControllerClass.method_8
return method.ReturnType == typeof(int)
&& parameters.Length == 2
&& parameters[0].ParameterType == typeof(string)
&& parameters[0].Name == "destinationID"
&& parameters[1].ParameterType == typeof(int)
&& parameters[1].Name == "currentDestinationIndex";
}
[PatchPrefix]
private static void PatchPrefix(ref string destinationID)
{
try
{
var locationIdSlash = Singleton<GameWorld>.Instance.LocationId + "/";
if (destinationID.Contains(locationIdSlash))
{
// destinationID is in the form of "Map/pX", strip the "Map/" part.
destinationID = destinationID.Replace(locationIdSlash, "");
}
}
catch (Exception)
{
ConsoleScreen.LogError($"[SPT-BTR] Exception in {nameof(BTRPathConfigMapPrefixPatch)}, check logs.");
}
}
}
}

View File

@ -14,7 +14,16 @@ namespace Aki.Custom.BTR.Patches
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(BTRView), nameof(BTRView.method_1));
return AccessTools.FirstMethod(typeof(BTRView), IsTargetMethod);
}
private bool IsTargetMethod(MethodBase method)
{
var parameters = method.GetParameters();
return parameters.Length == 1
&& parameters[0].ParameterType == typeof(DamageInfo)
&& parameters[0].Name == "damageInfo";
}
[PatchPrefix]
@ -23,12 +32,15 @@ namespace Aki.Custom.BTR.Patches
var botEventHandler = Singleton<BotEventHandler>.Instance;
if (botEventHandler == null)
{
Logger.LogError($"[AKI-BTR] BTRReceiveDamageInfoPatch - BotEventHandler is null");
Logger.LogError($"[SPT-BTR] {nameof(BTRReceiveDamageInfoPatch)} - BotEventHandler is null");
return;
}
var shotBy = (Player)damageInfo.Player.iPlayer;
if (shotBy != null)
{
botEventHandler.InterruptTraderServiceBtrSupportByBetrayer(shotBy);
}
}
}
}

View File

@ -0,0 +1,66 @@
using EFT;
using EFT.Vehicle;
using HarmonyLib;
using System;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using UnityEngine;
namespace Aki.Custom.BTR.Utils
{
public static class BTRReflectionHelper
{
private static Type _btrControllerType = typeof(BTRControllerClass);
private static Type _firearmControllerType = typeof(Player.FirearmController);
private static MethodInfo _initBtrControllerMethod = AccessTools.GetDeclaredMethods(_btrControllerType).Single(IsInitBtrControllerMethod);
private static MethodInfo _updateTaxiPriceMethod = AccessTools.GetDeclaredMethods(_btrControllerType).Single(IsUpdateTaxiPriceMethod);
private static MethodInfo _playWeaponSoundMethod = AccessTools.GetDeclaredMethods(_firearmControllerType).Single(IsPlayWeaponSoundMethod);
public static Task InitBtrController(this BTRControllerClass controller)
{
return (Task)_initBtrControllerMethod.Invoke(controller, null);
}
public static void UpdateTaxiPrice(this BTRControllerClass controller, PathDestination destinationPoint, bool isFinal)
{
_updateTaxiPriceMethod.Invoke(controller, new object[] { destinationPoint, isFinal });
}
public static void PlayWeaponSound(this Player.FirearmController controller, WeaponSoundPlayer weaponSoundPlayer, BulletClass ammo, Vector3 shotPosition, Vector3 shotDirection, bool multiShot)
{
_playWeaponSoundMethod.Invoke(controller, new object[] { weaponSoundPlayer, ammo, shotPosition, shotDirection, multiShot });
}
// Find `BTRControllerClass.method_1()`
private static bool IsInitBtrControllerMethod(MethodInfo method)
{
return method.ReturnType == typeof(Task)
&& method.GetParameters().Length == 0;
}
// Find `BTRControllerClass.method_9(PathDestination currentDestinationPoint, bool lastRoutePoint)`
private static bool IsUpdateTaxiPriceMethod(MethodInfo method)
{
ParameterInfo[] parameters = method.GetParameters();
return parameters.Length == 2
&& parameters[0].ParameterType == typeof(PathDestination);
}
// Find `Player.FirearmController.method_54(WeaponSoundPlayer weaponSoundPlayer, BulletClass ammo, Vector3 shotPosition, Vector3 shotDirection, bool multiShot)`
private static bool IsPlayWeaponSoundMethod(MethodInfo method)
{
ParameterInfo[] parameters = method.GetParameters();
return parameters.Length == 5
&& parameters[0].ParameterType == typeof(WeaponSoundPlayer)
&& parameters[1].ParameterType == typeof(BulletClass)
&& parameters[2].ParameterType == typeof(Vector3)
&& parameters[3].ParameterType == typeof(Vector3)
&& parameters[4].ParameterType == typeof(bool);
}
}
}

View File

@ -1,23 +0,0 @@
#region DEPRECATED, REMOVE IN 3.8.1
using System;
namespace Aki.Custom.Models
{
[Obsolete("BundleInfo is deprecated, please use BundleItem instead.")]
public class BundleInfo
{
public string Key { get; }
public string Path { get; set; }
public string[] DependencyKeys { get; }
public BundleInfo(string key, string path, string[] dependencyKeys)
{
Key = key;
Path = path;
DependencyKeys = dependencyKeys;
}
}
}
#endregion

View File

@ -0,0 +1,43 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Reflection;
namespace Aki.Custom.Models
{
[Serializable]
public struct DifficultyInfo
{
public Dictionary<string, object> this[string key]
{
get
{
switch (key)
{
case "easy":
return easy;
case "hard":
return hard;
case "impossible":
return impossible;
case "normal":
return normal;
default:
throw new ArgumentException($"Difficulty '{key}' does not exist in DifficultyInfo.");
}
}
}
[JsonProperty("easy")]
public Dictionary<string, object> easy;
[JsonProperty("hard")]
public Dictionary<string, object> hard;
[JsonProperty("impossible")]
public Dictionary<string, object> impossible;
[JsonProperty("normal")]
public Dictionary<string, object> normal;
}
}

View File

@ -13,7 +13,7 @@ namespace Aki.SinglePlayer.Patches.MainMenu
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(TarkovApplication), nameof(TarkovApplication.method_27));
return AccessTools.Method(typeof(TarkovApplication), nameof(TarkovApplication.method_28));
}
[PatchPrefix]

View File

@ -1,4 +1,4 @@
using Aki.Common.Http;
using Aki.Custom.Utils;
using Aki.Reflection.Patching;
using Aki.Reflection.Utils;
using EFT;
@ -21,7 +21,7 @@ namespace Aki.Custom.Patches
[PatchPrefix]
private static bool PatchPrefix(ref string __result, BotDifficulty botDifficulty, WildSpawnType role)
{
__result = RequestHandler.GetJson($"/singleplayer/settings/bot/difficulty/{role}/{botDifficulty}");
__result = DifficultyManager.Get(botDifficulty, role);
var resultIsNullEmpty = string.IsNullOrWhiteSpace(__result);
if (resultIsNullEmpty)
{

View File

@ -2,6 +2,7 @@ using Aki.Reflection.Patching;
using Aki.Reflection.Utils;
using Aki.Common.Http;
using System.Reflection;
using Aki.Custom.Utils;
namespace Aki.Custom.Patches
{
@ -19,6 +20,11 @@ namespace Aki.Custom.Patches
[PatchPrefix]
private static bool PatchPrefix(ref string __result)
{
// fetch all bot difficulties to be used in BotDifficultyPatch
// this is called here since core difficulties are fetched before bot-specific difficulties are
DifficultyManager.Update();
// update core difficulty
__result = RequestHandler.GetJson("/singleplayer/settings/bot/difficulty/core/core");
return string.IsNullOrWhiteSpace(__result);
}

View File

@ -0,0 +1,29 @@
using Aki.Common.Http;
using Aki.Reflection.Patching;
using System.Reflection;
using EFT;
using EFT.UI;
using HarmonyLib;
using UnityEngine;
using TMPro;
using System.Linq;
namespace Aki.Custom.Patches
{
public class DisablePvEPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(ChangeGameModeButton), nameof(ChangeGameModeButton.Show));
}
[PatchPrefix]
private static bool PatchPrefix(ESessionMode sessionMode, Profile profile, ref GameObject ____notAvailableState)
{
____notAvailableState.SetActive(true);
Object.FindObjectsOfType<HoverTooltipArea>().Where(o => o.name == "Locked").SingleOrDefault()?.SetMessageText("<color=#51c6db>SPT-AKI</color> is already PvE.");
return false;
}
}
}

View File

@ -12,8 +12,8 @@ using Aki.Common.Utils;
using Aki.Custom.Models;
using Aki.Custom.Utils;
using Aki.Reflection.Patching;
using Aki.Reflection.Utils;
using DependencyGraph = DependencyGraph<IEasyBundle>;
using Aki.Reflection.Utils;
namespace Aki.Custom.Patches
{
@ -23,7 +23,7 @@ namespace Aki.Custom.Patches
static EasyAssetsPatch()
{
_bundlesField = typeof(EasyAssets).GetField($"{EasyBundleHelper.Type.Name.ToLowerInvariant()}_0", PatchConstants.PrivateFlags);
_bundlesField = typeof(EasyAssets).GetFields(PatchConstants.PrivateFlags).FirstOrDefault(field => field.FieldType == typeof(EasyAssetHelperClass[]));
}
public EasyAssetsPatch()

View File

@ -13,7 +13,7 @@ namespace Aki.Custom.Patches
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(BaseLocalGame<GamePlayerOwner>), nameof(BaseLocalGame<GamePlayerOwner>.Stop));
return AccessTools.Method(typeof(BaseLocalGame<EftGamePlayerOwner>), nameof(BaseLocalGame<EftGamePlayerOwner>.Stop));
}
// Look at BaseLocalGame<TPlayerOwner> and find a method named "Stop"
@ -31,7 +31,7 @@ namespace Aki.Custom.Patches
var player = Singleton<GameWorld>.Instance.MainPlayer;
if (profileId == player?.Profile.Id)
{
GClass3107.Instance.CloseAllScreensForced();
GClass3127.Instance.CloseAllScreensForced();
}
return true;

View File

@ -12,7 +12,7 @@ namespace Aki.Custom.Patches
{
protected override MethodBase GetTargetMethod()
{
var desiredType = typeof(BaseLocalGame<GamePlayerOwner>);
var desiredType = typeof(BaseLocalGame<EftGamePlayerOwner>);
var desiredMethod = desiredType.GetMethods(BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.Public).SingleCustom(IsTargetMethod); // method_6
Logger.LogDebug($"{this.GetType().Name} Type: {desiredType?.Name}");

View File

@ -17,8 +17,8 @@ namespace Aki.Custom.Patches
public RagfairFeePatch()
{
// Remember to update prefix parameter if below lines are broken
_ = nameof(GClass3069.IsAllSelectedItemSame);
_ = nameof(GClass3069.AutoSelectSimilar);
_ = nameof(GClass3087.IsAllSelectedItemSame);
_ = nameof(GClass3087.AutoSelectSimilar);
}
protected override MethodBase GetTargetMethod()
@ -30,18 +30,18 @@ namespace Aki.Custom.Patches
/// Calculate tax to charge player and send to server before the offer is sent
/// </summary>
/// <param name="___item_0">Item sold</param>
/// <param name="___gclass3069_0">OfferItemCount</param>
/// <param name="___gclass3087_0">OfferItemCount</param>
/// <param name="___double_0">RequirementsPrice</param>
/// <param name="___bool_0">SellInOnePiece</param>
[PatchPrefix]
private static void PatchPrefix(ref Item ___item_0, ref GClass3069 ___gclass3069_0, ref double ___double_0, ref bool ___bool_0)
private static void PatchPrefix(ref Item ___item_0, ref GClass3087 ___gclass3087_0, ref double ___double_0, ref bool ___bool_0)
{
RequestHandler.PutJson("/client/ragfair/offerfees", new
{
id = ___item_0.Id,
tpl = ___item_0.TemplateId,
count = ___gclass3069_0.OfferItemCount,
fee = Mathf.CeilToInt((float)GClass2089.CalculateTaxPrice(___item_0, ___gclass3069_0.OfferItemCount, ___double_0, ___bool_0))
count = ___gclass3087_0.OfferItemCount,
fee = Mathf.CeilToInt((float)GClass2103.CalculateTaxPrice(___item_0, ___gclass3087_0.OfferItemCount, ___double_0, ___bool_0))
}.ToJson());
}
}

View File

@ -10,7 +10,7 @@ namespace Aki.Custom.Patches
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(BaseLocalGame<GamePlayerOwner>), nameof(BaseLocalGame<GamePlayerOwner>.Stop));
return AccessTools.Method(typeof(BaseLocalGame<EftGamePlayerOwner>), nameof(BaseLocalGame<EftGamePlayerOwner>.Stop));
}
[PatchPrefix]

View File

@ -14,7 +14,7 @@ namespace Aki.Custom.Patches
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(BaseLocalGame<GamePlayerOwner>), nameof(BaseLocalGame<GamePlayerOwner>.method_5));
return AccessTools.Method(typeof(BaseLocalGame<EftGamePlayerOwner>), nameof(BaseLocalGame<EftGamePlayerOwner>.method_5));
}
[PatchPostfix]

View File

@ -0,0 +1,34 @@
using System.Collections.Generic;
using EFT;
using Aki.Common.Http;
using Aki.Common.Utils;
using Aki.Custom.Models;
namespace Aki.Custom.Utils
{
public static class DifficultyManager
{
public static Dictionary<string, DifficultyInfo> Difficulties { get; private set; }
static DifficultyManager()
{
Difficulties = new Dictionary<string, DifficultyInfo>();
}
public static void Update()
{
// remove existing list
Difficulties.Clear();
// get new difficulties
var json = RequestHandler.GetJson("/singleplayer/settings/bot/difficulties");
Difficulties = Json.Deserialize<Dictionary<string, DifficultyInfo>>(json);
}
public static string Get(BotDifficulty botDifficulty, WildSpawnType role)
{
var difficultyMatrix = Difficulties[role.ToString().ToLower()];
return Json.Serialize(difficultyMatrix[botDifficulty.ToString().ToLower()]);
}
}
}

View File

@ -2,13 +2,13 @@
<PropertyGroup>
<TargetFramework>net471</TargetFramework>
<AssemblyName>aki-debugging</AssemblyName>
<AssemblyName>spt-debugging</AssemblyName>
<Configuration>Release</Configuration>
</PropertyGroup>
<PropertyGroup>
<Company>Aki</Company>
<Copyright>Copyright @ Aki 2024</Copyright>
<Company>SPT</Company>
<Copyright>Copyright @ SPT 2024</Copyright>
</PropertyGroup>
<ItemGroup>

View File

@ -14,7 +14,7 @@ namespace Aki.Debugging
public void Awake()
{
Logger.LogInfo("Loading: Aki.Debugging");
Logger.LogInfo("Loading: SPT.Debugging");
try
{

View File

@ -5,7 +5,7 @@ using EFT;
using EFT.UI;
using HarmonyLib;
using System.Reflection;
using DialogControlClass = GClass1957;
using DialogControlClass = GClass1971;
namespace Aki.Debugging.Patches
{

View File

@ -14,11 +14,11 @@ namespace Aki.Debugging.Patches
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(BaseLocalGame<GamePlayerOwner>), nameof(BaseLocalGame<GamePlayerOwner>.Update));
return AccessTools.Method(typeof(BaseLocalGame<EftGamePlayerOwner>), nameof(BaseLocalGame<EftGamePlayerOwner>.Update));
}
[PatchPrefix]
private static void PatchPrefix(BaseLocalGame<GamePlayerOwner> __instance)
private static void PatchPrefix(BaseLocalGame<EftGamePlayerOwner> __instance)
{
if (!Input.GetKeyDown(KeyCode.LeftControl)) return;

View File

@ -9,7 +9,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using ExitSettingsClass = GClass1225;
using ExitSettingsClass = GClass1234;
namespace Aki.Debugging.Patches
{

View File

@ -17,11 +17,11 @@ namespace Aki.Debugging.Patches
{
private readonly List<ISpawnPoint> playerSpawnPoints;
private readonly Random _rnd = new Random();
private readonly GStruct380 _spawnSettings = new GStruct380();
private readonly GStruct381 _spawnSettings = new GStruct381();
public SptSpawnHelper()
{
IEnumerable<ISpawnPoint> locationSpawnPoints = GClass2928.CreateFromScene();
IEnumerable<ISpawnPoint> locationSpawnPoints = GClass2946.CreateFromScene();
var playerSpawns = locationSpawnPoints.Where(x => x.Categories.HasFlag(ESpawnCategoryMask.Player)).ToList();
this.playerSpawnPoints = locationSpawnPoints.Where(x => x.Categories.HasFlag(ESpawnCategoryMask.Player)).ToList();
@ -62,7 +62,7 @@ namespace Aki.Debugging.Patches
}
[PatchPrefix]
public static bool PatchPrefix(GClass1472 __instance, GClass591 data)
public static bool PatchPrefix(GClass1483 __instance, GClass591 data)
{
var firstBotRole = data.Profiles[0].Info.Settings.Role;

View File

@ -38,7 +38,7 @@ namespace Aki.Debugging.Patches
{
tarkovapp.HideoutControllerAccess.UnloadHideout();
}
tarkovapp.method_48();
tarkovapp.method_49();
}
}
}

View File

@ -3,13 +3,13 @@
<PropertyGroup>
<TargetFramework>net471</TargetFramework>
<AssemblyName>aki_PrePatch</AssemblyName>
<AssemblyName>spt-prepatch</AssemblyName>
<Configuration>Release</Configuration>
</PropertyGroup>
<PropertyGroup>
<Company>Aki</Company>
<Copyright>Copyright @ Aki 2024</Copyright>
<Company>SPT</Company>
<Copyright>Copyright @ SPT 2024</Copyright>
</PropertyGroup>
<ItemGroup>

View File

@ -11,8 +11,8 @@ namespace Aki.PrePatch
{
public static IEnumerable<string> TargetDLLs { get; } = new[] { "Assembly-CSharp.dll" };
public static int sptUsecValue = 47;
public static int sptBearValue = 48;
public static int sptUsecValue = 100;
public static int sptBearValue = 101;
private static string _sptPluginFolder = "plugins/spt";
@ -86,14 +86,14 @@ namespace Aki.PrePatch
}
// Validate that the folder exists, and contains our plugins
string[] sptPlugins = new string[] { "aki-core.dll", "aki-custom.dll", "aki-singleplayer.dll" };
string[] sptPlugins = new string[] { "spt-common.dll", "spt-reflection.dll", "spt-core.dll", "spt-custom.dll", "spt-singleplayer.dll" };
string[] foundPlugins = Directory.GetFiles(sptPluginPath).Select(x => Path.GetFileName(x)).ToArray();
foreach (string plugin in sptPlugins)
foreach (string pluginNameAndSuffix in sptPlugins)
{
if (!foundPlugins.Contains(plugin))
if (!foundPlugins.Contains(pluginNameAndSuffix))
{
message = $"Required SPT plugins missing from '{sptPluginPath}'{exitMessage}";
message = $"Required SPT plugin: {pluginNameAndSuffix} missing from '{sptPluginPath}' {exitMessage}";
logger.LogError(message);
return false;
}

View File

@ -3,11 +3,12 @@
<PropertyGroup>
<TargetFramework>net471</TargetFramework>
<Configuration>Release</Configuration>
<AssemblyName>spt-reflection</AssemblyName>
</PropertyGroup>
<PropertyGroup>
<Company>Aki</Company>
<Copyright>Copyright @ Aki 2024</Copyright>
<Company>SPT</Company>
<Copyright>Copyright @ SPT 2024</Copyright>
</PropertyGroup>
<ItemGroup>

View File

@ -2,13 +2,13 @@
<PropertyGroup>
<TargetFramework>net471</TargetFramework>
<AssemblyName>aki-singleplayer</AssemblyName>
<AssemblyName>spt-singleplayer</AssemblyName>
<Configuration>Release</Configuration>
</PropertyGroup>
<PropertyGroup>
<Company>Aki</Company>
<Copyright>Copyright @ Aki 2024</Copyright>
<Company>SPT</Company>
<Copyright>Copyright @ SPT 2024</Copyright>
</PropertyGroup>
<ItemGroup>

View File

@ -16,7 +16,7 @@ namespace Aki.SinglePlayer
{
public void Awake()
{
Logger.LogInfo("Loading: Aki.SinglePlayer");
Logger.LogInfo("Loading: SPT.SinglePlayer");
try
{

View File

@ -32,7 +32,7 @@ namespace Aki.SinglePlayer.Patches.MainMenu
// this.method_41();
//}
return AccessTools.Method(typeof(MainMenuController), nameof(MainMenuController.method_72));
return AccessTools.Method(typeof(MainMenuController), nameof(MainMenuController.method_73));
}
[PatchPrefix]

View File

@ -14,7 +14,7 @@ namespace Aki.SinglePlayer.Patches.Quests
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(BaseLocalGame<GamePlayerOwner>), nameof(BaseLocalGame<GamePlayerOwner>.Stop));
return AccessTools.Method(typeof(BaseLocalGame<EftGamePlayerOwner>), nameof(BaseLocalGame<EftGamePlayerOwner>.Stop));
}
// Unused, but left here in case patch breaks and finding the intended method is difficult

View File

@ -32,7 +32,7 @@ namespace Aki.SinglePlayer.Patches.ScavMode
_ = MatchmakerPlayerControllerClass.MAX_SCAV_COUNT; // UPDATE REFS TO THIS CLASS BELOW !!!
// `MatchmakerInsuranceScreen` OnShowNextScreen
_onReadyScreenMethod = AccessTools.Method(typeof(MainMenuController), nameof(MainMenuController.method_43));
_onReadyScreenMethod = AccessTools.Method(typeof(MainMenuController), nameof(MainMenuController.method_44));
_isLocalField = AccessTools.Field(typeof(MainMenuController), "bool_0");
_menuControllerField = typeof(TarkovApplication).GetFields(PatchConstants.PrivateFlags).FirstOrDefault(x => x.FieldType == typeof(MainMenuController));
@ -46,65 +46,66 @@ namespace Aki.SinglePlayer.Patches.ScavMode
protected override MethodBase GetTargetMethod()
{
// `MatchMakerSelectionLocationScreen` OnShowNextScreen
return AccessTools.Method(typeof(MainMenuController), nameof(MainMenuController.method_69));
return AccessTools.Method(typeof(MainMenuController), nameof(MainMenuController.method_71));
}
[PatchTranspiler]
private static IEnumerable<CodeInstruction> PatchTranspiler(ILGenerator generator, IEnumerable<CodeInstruction> instructions)
{
/* The original msil looks something like this:
* 0 0000 ldarg.0
* 1 0001 call instance void MainMenuController::method_69()
* 2 0006 ldarg.0
* 3 0007 call instance void MainMenuController::method_41()
* 4 000C ldarg.0
* 5 000D call instance bool MainMenuController::method_46()
* 6 0012 brtrue.s 8 (0015) ldarg.0
* 7 0014 ret
* 8 0015 ldarg.0
* 9 0016 ldfld class EFT.RaidSettings MainMenuController::raidSettings_0
* 10 001B callvirt instance bool EFT.RaidSettings::get_IsPmc()
* 11 0020 brfalse.s 15 (0029) ldarg.0
* 12 0022 ldarg.0
* 13 0023 call instance void MainMenuController::method_42()
* 14 0028 ret
* 15 0029 ldarg.0
* 16 002A call instance void MainMenuController::method_44()
* 17 002F ret
*
* The goal is to replace the call to method_44 with our own LoadOfflineRaidScreenForScav function.
* method_44 expects one argument which is the implicit "this" pointer.
* The ldarg.0 instruction loads "this" onto the stack and the function call will consume it.
* But because our own LoadOfflineRaidScreenForScav method is static
* it won't consume a "this" pointer from the stack, so we have to remove the ldarg.0 instruction.
* But the brfalse instruction at 0020 jumps to the ldarg.0, so we can not simply delete it.
* Instead, we first need to transfer the jump label from the ldarg.0 instruction to our new
* call instruction and only then we remove it.
*/
var codes = new List<CodeInstruction>(instructions);
var onReadyScreenMethodOperand = AccessTools.Method(typeof(MainMenuController), _onReadyScreenMethod.Name);
// The original method call that we want to replace
var onReadyScreenMethodIndex = -1;
var onReadyScreenMethodCode = new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(MainMenuController), _onReadyScreenMethod.Name));
var callCodeIndex = codes.FindLastIndex(code => code.opcode == OpCodes.Call
&& (MethodInfo)code.operand == onReadyScreenMethodOperand);
// We additionally need to replace an instruction that jumps to a label on certain conditions, since we change the jump target instruction
var jumpWhenFalse_Index = -1;
for (var i = 0; i < codes.Count; i++)
{
if (codes[i].opcode == onReadyScreenMethodCode.opcode && codes[i].operand == onReadyScreenMethodCode.operand)
{
onReadyScreenMethodIndex = i;
continue;
}
if (codes[i].opcode == OpCodes.Brfalse)
{
if (jumpWhenFalse_Index != -1)
{
// If this warning is ever logged, the condition for locating the exact brfalse instruction will have to be updated
Logger.LogWarning($"[{nameof(LoadOfflineRaidScreenPatch)}] Found extra instructions with the brfalse opcode! " +
"This breaks an old assumption that there is only one such instruction in the method body and is now very likely to cause bugs!");
}
jumpWhenFalse_Index = i;
}
}
if (onReadyScreenMethodIndex == -1)
if (callCodeIndex == -1)
{
throw new Exception($"{nameof(LoadOfflineRaidScreenPatch)} failed: Could not find {nameof(_onReadyScreenMethod)} reference code.");
}
if (jumpWhenFalse_Index == -1)
var loadThisIndex = callCodeIndex - 1;
if (codes[loadThisIndex].opcode != OpCodes.Ldarg_0)
{
throw new Exception($"{nameof(LoadOfflineRaidScreenPatch)} failed: Could not find jump (brfalse) reference code.");
throw new Exception($"{nameof(LoadOfflineRaidScreenPatch)} failed: Expected ldarg.0 before call instruction but found {codes[loadThisIndex]}");
}
// Define the new jump label
var brFalseLabel = generator.DefineLabel();
// Overwrite the call instruction with the call to LoadOfflineRaidScreenForScav, preserving the label for the 0020 brfalse jump
codes[callCodeIndex] = new CodeInstruction(OpCodes.Call,
AccessTools.Method(typeof(LoadOfflineRaidScreenPatch), nameof(LoadOfflineRaidScreenForScav))) {
labels = codes[loadThisIndex].labels
};
// We build the method call for our substituted method and replace the initial method call with our own, also adding our new label
var callCode = new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(LoadOfflineRaidScreenPatch), nameof(LoadOfflineRaidScreenForScav))) { labels = { brFalseLabel } };
codes[onReadyScreenMethodIndex] = callCode;
// We build a new brfalse instruction and give it our new label, then replace the original brfalse instruction
var newBrFalseCode = new CodeInstruction(OpCodes.Brfalse, brFalseLabel);
codes[jumpWhenFalse_Index] = newBrFalseCode;
// This will remove a stray ldarg.0 instruction. It's only needed if we wanted to reference something from `this` in the method body.
// This is done last to ensure that previous instruction indexes don't shift around (probably why this used to just turn it into a Nop OpCode)
codes.RemoveAt(onReadyScreenMethodIndex - 1);
// Remove the ldarg.0 instruction which we no longer need because LoadOfflineRaidScreenForScav is static
codes.RemoveAt(loadThisIndex);
return codes.AsEnumerable();
}
@ -123,12 +124,12 @@ namespace Aki.SinglePlayer.Patches.ScavMode
.Single(field => field.FieldType == typeof(MatchmakerPlayerControllerClass))
?.GetValue(menuController) as MatchmakerPlayerControllerClass;
var gclass = new MatchmakerOfflineRaidScreen.GClass3155(profile?.Info, ref raidSettings, matchmakerPlayersController);
var gclass = new MatchmakerOfflineRaidScreen.GClass3178(profile?.Info, ref raidSettings, matchmakerPlayersController, ESessionMode.Regular);
gclass.OnShowNextScreen += LoadOfflineRaidNextScreen;
// `MatchmakerOfflineRaidScreen` OnShowReadyScreen
gclass.OnShowReadyScreen += (OfflineRaidAction)Delegate.CreateDelegate(typeof(OfflineRaidAction), menuController, nameof(MainMenuController.method_73));
gclass.OnShowReadyScreen += (OfflineRaidAction)Delegate.CreateDelegate(typeof(OfflineRaidAction), menuController, nameof(MainMenuController.method_75));
gclass.ShowScreen(EScreenState.Queued);
}

View File

@ -12,7 +12,7 @@ namespace Aki.SinglePlayer.Patches.ScavMode
// TODO: REMAP/UPDATE GCLASS REF
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(GClass1790), nameof(GClass1790.OnEnemyKill));
return AccessTools.Method(typeof(GClass1799), nameof(GClass1799.OnEnemyKill));
}
[PatchPrefix]

View File

@ -19,7 +19,7 @@ namespace Aki.SinglePlayer.Utils.TraderServices
gameWorld = Singleton<GameWorld>.Instance;
if (gameWorld == null || TraderServicesManager.Instance == null)
{
logger.LogError("[AKI-LKS] GameWorld or TraderServices null");
logger.LogError("[SPT-LKS] GameWorld or TraderServices null");
Destroy(this);
return;
}
@ -27,7 +27,7 @@ namespace Aki.SinglePlayer.Utils.TraderServices
botsController = Singleton<IBotGame>.Instance.BotsController;
if (botsController == null)
{
logger.LogError("[AKI-LKS] BotsController null");
logger.LogError("[SPT-LKS] BotsController null");
Destroy(this);
return;
}

View File

@ -10,9 +10,9 @@ using System.Linq;
using System.Reflection;
using UnityEngine;
using static BackendConfigSettingsClass;
using TraderServiceClass = GClass1794;
using QuestDictClass = GClass2133<string>;
using StandingListClass = GClass2135<float>;
using TraderServiceClass = GClass1805;
using QuestDictClass = GClass2145<string>;
using StandingListClass = GClass2147<float>;
namespace Aki.SinglePlayer.Utils.TraderServices
{

Binary file not shown.

View File

@ -3,8 +3,6 @@ $bepinexFolder = "..\Build\BepInEx"
$bepinexPatchFolder = "..\Build\BepInEx\patchers"
$bepinexPluginFolder = "..\Build\BepInEx\plugins"
$bepinexSptFolder = "..\Build\BepInEx\plugins\spt"
$eftDataFolder = "..\Build\EscapeFromTarkov_Data"
$managedFolder = "..\Build\EscapeFromTarkov_Data\Managed"
$projReleaseFolder = ".\bin\Release\net471"
$licenseFile = "..\..\LICENSE.md"
@ -12,19 +10,19 @@ $licenseFile = "..\..\LICENSE.md"
if (Test-Path "$buildFolder") { Remove-Item -Path "$buildFolder" -Recurse -Force }
# Create build folder and subfolders if they don't exist
$foldersToCreate = @("$buildFolder", "$bepinexFolder", "$bepinexPatchFolder", "$bepinexPluginFolder", "$bepinexSptFolder", "$eftDataFolder", "$managedFolder")
$foldersToCreate = @("$buildFolder", "$bepinexFolder", "$bepinexPatchFolder", "$bepinexPluginFolder", "$bepinexSptFolder")
foreach ($folder in $foldersToCreate) {
if (-not (Test-Path "$folder")) { New-Item -Path "$folder" -ItemType Directory }
}
# Move DLLs from project's bin-release folder to the build folder
Copy-Item "$projReleaseFolder\Aki.Common.dll" -Destination "$managedFolder"
Copy-Item "$projReleaseFolder\Aki.Reflection.dll" -Destination "$managedFolder"
Copy-Item "$projReleaseFolder\aki_PrePatch.dll" -Destination "$bepinexPatchFolder"
Copy-Item "$projReleaseFolder\aki-core.dll" -Destination "$bepinexSptFolder"
Copy-Item "$projReleaseFolder\aki-custom.dll" -Destination "$bepinexSptFolder"
Copy-Item "$projReleaseFolder\aki-debugging.dll" -Destination "$bepinexSptFolder"
Copy-Item "$projReleaseFolder\aki-singleplayer.dll" -Destination "$bepinexSptFolder"
Copy-Item "$projReleaseFolder\spt-common.dll" -Destination "$bepinexSptFolder"
Copy-Item "$projReleaseFolder\spt-reflection.dll" -Destination "$bepinexSptFolder"
Copy-Item "$projReleaseFolder\spt-prepatch.dll" -Destination "$bepinexPatchFolder"
Copy-Item "$projReleaseFolder\spt-core.dll" -Destination "$bepinexSptFolder"
Copy-Item "$projReleaseFolder\spt-custom.dll" -Destination "$bepinexSptFolder"
Copy-Item "$projReleaseFolder\spt-debugging.dll" -Destination "$bepinexSptFolder"
Copy-Item "$projReleaseFolder\spt-singleplayer.dll" -Destination "$bepinexSptFolder"
# If any new DLLs need to be copied, add here
# Write the contents of the license file to a txt