diff --git a/ExtraProperties.cs b/ExtraProperties.cs index 3fa7806..0c53ffd 100644 --- a/ExtraProperties.cs +++ b/ExtraProperties.cs @@ -99,6 +99,19 @@ public static class ExtraItemMarketPricesPanelProperties } public static Action GetOnMarketPricesCallback(this ItemMarketPricesPanel panel) => properties.GetOrCreateValue(panel).OnMarketPricesCallback; - public static Action SetOnMarketPricesCallback(this ItemMarketPricesPanel panel, Action handler) => properties.GetOrCreateValue(panel).OnMarketPricesCallback = handler; + public static void SetOnMarketPricesCallback(this ItemMarketPricesPanel panel, Action handler) => properties.GetOrCreateValue(panel).OnMarketPricesCallback = handler; +} + +public static class ExtraEventResultProperties +{ + private static readonly ConditionalWeakTable properties = new(); + + private class Properties + { + public MoveOperation MoveOperation; + } + + public static MoveOperation GetMoveOperation(this GClass2803 result) => properties.GetOrCreateValue(result).MoveOperation; + public static void SetMoveOperation(this GClass2803 result, MoveOperation operation) => properties.GetOrCreateValue(result).MoveOperation = operation; } diff --git a/GlobalUsings.cs b/GlobalUsings.cs index 19651d1..e4c8818 100644 --- a/GlobalUsings.cs +++ b/GlobalUsings.cs @@ -42,6 +42,9 @@ global using MoveSameSpaceError = InteractionsHandlerClass.GClass3353; // Operations global using ItemOperation = GStruct413; global using MoveOperation = GClass2802; +global using AddOperation = GClass2798; +global using ResizeOperation = GClass2803; +global using FoldOperation = GClass2815; global using NoOpMove = GClass2795; global using BindOperation = GClass2818; global using SortOperation = GClass2824; diff --git a/Patches/WeaponModdingPatches.cs b/Patches/WeaponModdingPatches.cs new file mode 100644 index 0000000..bf50d4f --- /dev/null +++ b/Patches/WeaponModdingPatches.cs @@ -0,0 +1,315 @@ +using Diz.LanguageExtensions; +using EFT.InventoryLogic; +using HarmonyLib; +using SPT.Reflection.Patching; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace UIFixes; + +public static class WeaponModdingPatches +{ + public static void Enable() + { + new ResizePatch().Enable(); + new ResizeHelperPatch().Enable(); + new ResizeOperationRollbackPatch().Enable(); + new MoveBeforeNetworkTransactionPatch().Enable(); + + //new ModdingMoveToSortingTablePatch().Enable(); + //new PresetMoveToSortingTablePatch().Enable(); + } + + public class ResizePatch : ModulePatch + { + public static MoveOperation NecessaryMoveOperation = null; + + private static bool InPatch = false; + protected override MethodBase GetTargetMethod() + { + return AccessTools.Method(typeof(StashGridClass), nameof(StashGridClass.Resize)); + } + + [PatchPostfix] + public static void Postfix(StashGridClass __instance, Item item, XYCellSizeStruct oldSize, XYCellSizeStruct newSize, bool simulate, ref bool __result) + { + if (__result || InPatch) + { + return; + } + + if (item.Owner is not InventoryControllerClass inventoryController) + { + return; + } + + LocationInGrid itemLocation = __instance.GetItemLocation(item); + + // The sizes passed in are the template sizes, need to make match the item's rotation + XYCellSizeStruct actualOldSize = itemLocation.r.Rotate(oldSize); + XYCellSizeStruct actualNewSize = itemLocation.r.Rotate(newSize); + + // Figure out which direction(s) its growing + int horizontalGrowth = actualNewSize.X - actualOldSize.X; + int verticalGrowth = actualNewSize.Y - actualOldSize.Y; + + // Can't move up/left more than the position + horizontalGrowth = Math.Min(horizontalGrowth, itemLocation.x); + verticalGrowth = Math.Min(verticalGrowth, itemLocation.y); + + // Try moving it + try + { + InPatch = true; + for (int x = 0; x <= horizontalGrowth; x++) + { + for (int y = 0; y <= verticalGrowth; y++) + { + if (x + y == 0) + { + continue; + } + + LocationInGrid newLocation = new(itemLocation.x - x, itemLocation.y - y, itemLocation.r); + ItemAddress newAddress = new GridItemAddress(__instance, newLocation); + + var moveOperation = InteractionsHandlerClass.Move(item, newAddress, inventoryController, false); + if (moveOperation.Failed || moveOperation.Value == null) + { + continue; + } + + bool resizeResult = __instance.Resize(item, oldSize, newSize, simulate); + + // If simulating, rollback. Note that for some reason, only the Fold case even uses simulate + // The other cases (adding a mod, etc) never simulate, and then rollback later. Likely because there is normally + // no server side-effect of a resize - the only effect is updating the grid's free/used map. + if (simulate || !resizeResult) + { + moveOperation.Value.RollBack(); + } + + if (resizeResult) + { + // Stash the move operation so it can be executed or rolled back later + NecessaryMoveOperation = moveOperation.Value; + + __result = true; + return; + } + } + } + } + finally + { + InPatch = false; + } + } + } + + public class ResizeHelperPatch : ModulePatch + { + protected override MethodBase GetTargetMethod() + { + return AccessTools.Method(typeof(InteractionsHandlerClass), nameof(InteractionsHandlerClass.Resize_Helper)); + } + + [PatchPostfix] + public static void Postfix(ref GStruct414 __result) + { + if (__result.Failed || __result.Value == null) + { + return; + } + + if (ResizePatch.NecessaryMoveOperation != null) + { + __result.Value.SetMoveOperation(ResizePatch.NecessaryMoveOperation); + ResizePatch.NecessaryMoveOperation = null; + } + } + } + + public class ResizeOperationRollbackPatch : ModulePatch + { + protected override MethodBase GetTargetMethod() + { + return AccessTools.Method(typeof(ResizeOperation), nameof(ResizeOperation.RollBack)); + } + + [PatchPostfix] + public static void Postfix(ResizeOperation __instance) + { + MoveOperation moveOperation = __instance.GetMoveOperation(); + if (moveOperation != null) + { + moveOperation.RollBack(); + } + } + } + + public class MoveBeforeNetworkTransactionPatch : ModulePatch + { + private static bool InPatch = false; + + protected override MethodBase GetTargetMethod() + { + return AccessTools.Method(typeof(TraderControllerClass), nameof(TraderControllerClass.RunNetworkTransaction)); + } + + [PatchPrefix] + public static void Prefix(TraderControllerClass __instance, IRaiseEvents operationResult) + { + if (InPatch) + { + return; + } + + MoveOperation extraOperation = null; + if (operationResult is MoveOperation moveOperation) + { + extraOperation = moveOperation.R().AddOperation?.R().ResizeOperation?.GetMoveOperation(); + } + else if (operationResult is FoldOperation foldOperation) + { + extraOperation = foldOperation.ResizeResult?.GetMoveOperation(); + } + + if (extraOperation != null) + { + try + { + InPatch = true; + __instance.RunNetworkTransaction(extraOperation); + } + finally + { + InPatch = false; + } + } + } + } + + public class ModdingMoveToSortingTablePatch : ModulePatch + { + private static bool InPatch = false; + + protected override MethodBase GetTargetMethod() + { + return AccessTools.DeclaredMethod(typeof(GClass2848), nameof(GClass2848.Select)); + } + + [PatchPostfix] + public static void Postfix(GClass2848 __instance, Item item, ItemAddress itemAddress, bool simulate, ref Error error, ref bool __result) + { + if (!Settings.MoveBuildsToSortingTable.Value || InPatch || __result || error is not InteractionsHandlerClass.GClass3363) + { + return; + } + + // get top level item (weapon) + Item rootItem = itemAddress.Container.ParentItem.GetRootMergedItem(); + if (rootItem == null) + { + return; + } + + // move it to sorting table + SortingTableClass sortingTable = __instance.InventoryControllerClass.Inventory.SortingTable; + if (sortingTable == null) + { + return; + } + + ItemAddressClass sortingTableAddress = sortingTable.Grid.FindLocationForItem(rootItem); + if (sortingTableAddress == null) + { + return; + } + + var sortingTableMove = InteractionsHandlerClass.Move(rootItem, sortingTableAddress, __instance.InventoryControllerClass, simulate); + if (sortingTableMove.Failed || sortingTableMove.Value == null) + { + return; + } + + if (simulate) + { + // Just testing, and it was moveable to sorting table, so assume everything is fine. + error = null; + __result = true; + return; + } + + // Actually selecting it, so do it and then redo the select + __instance.InventoryControllerClass.RunNetworkTransaction(sortingTableMove.Value); + + InPatch = true; + __result = __instance.Select(item, itemAddress, simulate, out error); + InPatch = false; + } + } + + public class PresetMoveToSortingTablePatch : ModulePatch + { + private static bool InPatch = false; + + protected override MethodBase GetTargetMethod() + { + return AccessTools.Method(typeof(GClass2849), nameof(GClass2849.method_2)); + } + + [PatchPostfix] + public static void Postfix( + GClass2849 __instance, + Item item, + List modsWithSlots, + TraderControllerClass itemController, + bool simulate, + ref GStruct416>> __result) + { + if (!Settings.MoveBuildsToSortingTable.Value || + InPatch || + __result.Succeeded || + __result.Error is not InteractionsHandlerClass.GClass3363 || + itemController is not InventoryControllerClass inventoryController) + { + return; + } + + // move it to sorting table + SortingTableClass sortingTable = inventoryController.Inventory.SortingTable; + if (sortingTable == null) + { + return; + } + + ItemAddressClass sortingTableAddress = sortingTable.Grid.FindLocationForItem(item); + if (sortingTableAddress == null) + { + return; + } + + var sortingTableMove = InteractionsHandlerClass.Move(item, sortingTableAddress, inventoryController, simulate); // only called with simulate = false + if (sortingTableMove.Failed || sortingTableMove.Value == null) + { + return; + } + + InPatch = true; + __result = __instance.method_2(item, modsWithSlots, itemController, simulate); + InPatch = false; + + if (__result.Succeeded) + { + __result.Value.Prepend(sortingTableMove); + } + else + { + sortingTableMove.Value.RollBack(); + } + } + } +} \ No newline at end of file diff --git a/Plugin.cs b/Plugin.cs index 390f2db..51beea6 100644 --- a/Plugin.cs +++ b/Plugin.cs @@ -72,6 +72,7 @@ public class Plugin : BaseUnityPlugin new UnlockCursorPatch().Enable(); LimitDragPatches.Enable(); new HideoutCameraPatch().Enable(); + WeaponModdingPatches.Enable(); } public static bool InRaid() diff --git a/R.cs b/R.cs index ac173f2..ba934f1 100644 --- a/R.cs +++ b/R.cs @@ -68,6 +68,8 @@ public static class R ScavengerInventoryScreen.InitTypes(); LocalizedText.InitTypes(); GameWorld.InitTypes(); + MoveOperationResult.InitTypes(); + AddOperationResult.InitTypes(); } public abstract class Wrapper(object value) @@ -882,6 +884,34 @@ public static class R public TraderControllerClass TraderController { get { return (TraderControllerClass)TraderControllerField.GetValue(Value); } } } + + public class MoveOperationResult(object value) : Wrapper(value) + { + public static Type Type { get; private set; } + private static FieldInfo AddOperationField; + + public static void InitTypes() + { + Type = typeof(MoveOperation); + AddOperationField = AccessTools.Field(Type, "gclass2798_0"); + } + + public AddOperation AddOperation { get { return (AddOperation)AddOperationField.GetValue(Value); } } + } + + public class AddOperationResult(object value) : Wrapper(value) + { + public static Type Type { get; private set; } + private static FieldInfo ResizeOperationField; + + public static void InitTypes() + { + Type = typeof(AddOperation); + ResizeOperationField = AccessTools.Field(Type, "gclass2803_0"); + } + + public ResizeOperation ResizeOperation { get { return (ResizeOperation)ResizeOperationField.GetValue(Value); } } + } } public static class RExtentensions @@ -914,4 +944,6 @@ public static class RExtentensions public static R.ScavengerInventoryScreen R(this ScavengerInventoryScreen value) => new(value); public static R.LocalizedText R(this LocalizedText value) => new(value); public static R.GameWorld R(this GameWorld value) => new(value); + public static R.MoveOperationResult R(this MoveOperation value) => new(value); + public static R.AddOperationResult R(this AddOperation value) => new(value); }