diff --git a/project/Aki.SinglePlayer/AkiSingleplayerPlugin.cs b/project/Aki.SinglePlayer/AkiSingleplayerPlugin.cs index d2fa9a3..ac846b6 100644 --- a/project/Aki.SinglePlayer/AkiSingleplayerPlugin.cs +++ b/project/Aki.SinglePlayer/AkiSingleplayerPlugin.cs @@ -60,6 +60,8 @@ namespace Aki.SinglePlayer new MidRaidAchievementChangePatch().Enable(); new GetTraderServicesPatch().Enable(); new PurchaseTraderServicePatch().Enable(); + new ScavSellAllPriceStorePatch().Enable(); + new ScavSellAllRequestPatch().Enable(); } catch (Exception ex) { diff --git a/project/Aki.SinglePlayer/Models/ScavMode/SellAllRequest.cs b/project/Aki.SinglePlayer/Models/ScavMode/SellAllRequest.cs new file mode 100644 index 0000000..206c0c3 --- /dev/null +++ b/project/Aki.SinglePlayer/Models/ScavMode/SellAllRequest.cs @@ -0,0 +1,20 @@ +using EFT.InventoryLogic.BackendInventoryInteraction; +using Newtonsoft.Json; + +namespace Aki.SinglePlayer.Models.ScavMode +{ + public class SellAllRequest + { + [JsonProperty("Action")] + public string Action; + + [JsonProperty("totalValue")] + public int TotalValue; + + [JsonProperty("fromOwner")] + public OwnerInfo FromOwner; + + [JsonProperty("toOwner")] + public OwnerInfo ToOwner; + } +} diff --git a/project/Aki.SinglePlayer/Patches/ScavMode/ScavSellAllPriceStorePatch.cs b/project/Aki.SinglePlayer/Patches/ScavMode/ScavSellAllPriceStorePatch.cs new file mode 100644 index 0000000..9dae79c --- /dev/null +++ b/project/Aki.SinglePlayer/Patches/ScavMode/ScavSellAllPriceStorePatch.cs @@ -0,0 +1,67 @@ +using Aki.Reflection.Patching; +using EFT.InventoryLogic; +using EFT.UI; +using HarmonyLib; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace Aki.SinglePlayer.Patches.ScavMode +{ + /** + * After fetching the list of items for the post-raid scav inventory screen, calculate + * the total "Sell All" value, and store it for retrieval if the user hits "Sell All" + */ + public class ScavSellAllPriceStorePatch : ModulePatch + { + private static string FENCE_ID = "579dc571d53a0658a154fbec"; + private static string ROUBLE_TID = "5449016a4bdc2d6f028b456f"; + + private static FieldInfo _sessionField; + + public static int StoredPrice; + + protected override MethodBase GetTargetMethod() + { + Type scavInventoryScreenType = typeof(ScavengerInventoryScreen); + _sessionField = AccessTools.GetDeclaredFields(scavInventoryScreenType).FirstOrDefault(f => f.FieldType == typeof(ISession)); + + return AccessTools.FirstMethod(scavInventoryScreenType, IsTargetMethod); + } + + private bool IsTargetMethod(MethodBase method) + { + // Look for a method with one parameter named `items` + // method_3(out IEnumerable items) + if (method.GetParameters().Length == 1 && method.GetParameters()[0].Name == "items") + { + return true; + } + + return false; + } + + [PatchPostfix] + private static void PatchPostfix(ScavengerInventoryScreen __instance, IEnumerable items) + { + ISession session = _sessionField.GetValue(__instance) as ISession; + TraderClass traderClass = session.Traders.FirstOrDefault(x => x.Id == FENCE_ID); + + int totalPrice = 0; + foreach (Item item in items) + { + if (item.TemplateId == ROUBLE_TID) + { + totalPrice += item.StackObjectsCount; + } + else + { + totalPrice += traderClass.GetItemPriceOnScavSell(item, true); + } + } + + StoredPrice = totalPrice; + } + } +} diff --git a/project/Aki.SinglePlayer/Patches/ScavMode/ScavSellAllRequestPatch.cs b/project/Aki.SinglePlayer/Patches/ScavMode/ScavSellAllRequestPatch.cs new file mode 100644 index 0000000..59dc409 --- /dev/null +++ b/project/Aki.SinglePlayer/Patches/ScavMode/ScavSellAllRequestPatch.cs @@ -0,0 +1,81 @@ +using Aki.Reflection.Patching; +using Aki.SinglePlayer.Models.ScavMode; +using Comfort.Common; +using EFT.InventoryLogic.BackendInventoryInteraction; +using EFT.InventoryLogic; +using HarmonyLib; +using System.Reflection; +using System.Threading.Tasks; +using System; +using Aki.Reflection.Utils; + +namespace Aki.SinglePlayer.Patches.ScavMode +{ + /** + * When the user clicks "Sell All" after a scav raid, create a custom request object + * that includes the calculated sell price + */ + public class ScavSellAllRequestPatch : ModulePatch + { + private static MethodInfo _sendOperationMethod; + private string TargetMethodName = "SellAllFromSavage"; + + protected override MethodBase GetTargetMethod() + { + // We want to find a type that contains `SellAllFromSavage` but doesn't extend from `IBackendStatus` + Type targetType = PatchConstants.EftTypes.SingleCustom(IsTargetType); + + Logger.LogDebug($"{this.GetType().Name} Type: {targetType?.Name}"); + + // So we can call "SendOperationRightNow" without directly referencing a GClass + _sendOperationMethod = AccessTools.Method(targetType, "SendOperationRightNow"); + + return AccessTools.Method(targetType, TargetMethodName); + } + + private bool IsTargetType(Type type) + { + // Isn't an interface, isn't part of the dummy class, and contains our target method + if (!type.IsInterface + && type.DeclaringType != typeof(BackendDummyClass) + && type.GetMethod(TargetMethodName) != null) + { + return true; + } + + return false; + } + + [PatchPrefix] + private static bool PatchPrefix(object __instance, ref Task __result, string playerId, string petId) + { + // Build request with additional information + OwnerInfo fromOwner = new OwnerInfo + { + Id = petId, + Type = EOwnerType.Profile + }; + OwnerInfo toOwner = new OwnerInfo + { + Id = playerId, + Type = EOwnerType.Profile + }; + + SellAllRequest request = new SellAllRequest + { + Action = "SellAllFromSavage", + TotalValue = ScavSellAllPriceStorePatch.StoredPrice, // Retrieve value stored in earlier patch + FromOwner = fromOwner, // Scav + ToOwner = toOwner // PMC + }; + + // We'll re-use the same logic/methods that the base code used + TaskCompletionSource taskCompletionSource = new TaskCompletionSource(); + _sendOperationMethod.Invoke(__instance, new object[] { request, new Callback(taskCompletionSource.SetResult) }); + __result = taskCompletionSource.Task; + + // Skip original + return false; + } + } +}