From 48faa97082b6f281ec306c700221656e942b1e99 Mon Sep 17 00:00:00 2001 From: Tyfon <29051038+tyfon7@users.noreply.github.com> Date: Tue, 23 Jul 2024 16:46:45 -0700 Subject: [PATCH 01/73] autopopulate flea offer price; update flea offer price when bulk; ammobox unload in place fixes --- ExtraProperties.cs | 15 +++ Patches/AddOfferClickablePricesPatches.cs | 118 +++++++++++++++++++++- Patches/UnloadAmmoPatches.cs | 27 +++-- R.cs | 16 +++ Settings.cs | 38 +++++++ UIFixes.csproj | 2 +- server/package.json | 2 +- 7 files changed, 207 insertions(+), 11 deletions(-) diff --git a/ExtraProperties.cs b/ExtraProperties.cs index 9c58ec5..3fa7806 100644 --- a/ExtraProperties.cs +++ b/ExtraProperties.cs @@ -1,5 +1,7 @@ using EFT.InventoryLogic; using EFT.UI.DragAndDrop; +using EFT.UI.Ragfair; +using System; using System.Runtime.CompilerServices; using UnityEngine; @@ -87,3 +89,16 @@ public static class ExtraItemViewStatsProperties public static void SetHideMods(this ItemViewStats itemViewStats, bool value) => properties.GetOrCreateValue(itemViewStats).HideMods = value; } +public static class ExtraItemMarketPricesPanelProperties +{ + private static readonly ConditionalWeakTable properties = new(); + + private class Properties + { + public Action OnMarketPricesCallback = null; + } + + 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; +} + diff --git a/Patches/AddOfferClickablePricesPatches.cs b/Patches/AddOfferClickablePricesPatches.cs index f4ab190..40271e6 100644 --- a/Patches/AddOfferClickablePricesPatches.cs +++ b/Patches/AddOfferClickablePricesPatches.cs @@ -1,7 +1,9 @@ -using EFT.UI.Ragfair; +using EFT.InventoryLogic; +using EFT.UI.Ragfair; using HarmonyLib; using JetBrains.Annotations; using SPT.Reflection.Patching; +using System; using System.Linq; using System.Reflection; using TMPro; @@ -16,6 +18,9 @@ public static class AddOfferClickablePricesPatches public static void Enable() { new AddButtonPatch().Enable(); + new MarketPriceUpdatePatch().Enable(); + new BulkTogglePatch().Enable(); + new MultipleStacksPatch().Enable(); } public class AddButtonPatch : ModulePatch @@ -43,10 +48,99 @@ public static class AddOfferClickablePricesPatches Button maximumButton = panel.MaximumLabel.GetOrAddComponent(); maximumButton.onClick.AddListener(() => SetRequirement(__instance, rublesRequirement, ____pricesPanel.Maximum)); ____pricesPanel.AddDisposable(maximumButton.onClick.RemoveAllListeners); + + ____pricesPanel.SetOnMarketPricesCallback(() => PopulateOfferPrice(__instance, ____pricesPanel, rublesRequirement)); } } - private static void SetRequirement(AddOfferWindow window, RequirementView requirement, float price) + public class MarketPriceUpdatePatch : ModulePatch + { + protected override MethodBase GetTargetMethod() + { + return AccessTools.Method(typeof(ItemMarketPricesPanel), nameof(ItemMarketPricesPanel.method_1)); + } + + [PatchPostfix] + public static void Postfix(ItemMarketPricesPanel __instance) + { + var action = __instance.GetOnMarketPricesCallback(); + action?.Invoke(); + } + } + + public class BulkTogglePatch : ModulePatch + { + protected override MethodBase GetTargetMethod() + { + return AccessTools.Method(typeof(AddOfferWindow), nameof(AddOfferWindow.method_12)); + } + + [PatchPostfix] + public static void Postfix(AddOfferWindow __instance, bool arg, ItemMarketPricesPanel ____pricesPanel, RequirementView[] ____requirementViews) + { + if (!Settings.UpdatePriceOnBulk.Value) + { + return; + } + + RequirementView rublesRequirement = ____requirementViews.First(rv => rv.name == "Requirement (RUB)"); + double currentPrice = rublesRequirement.Requirement.PreciseCount; + if (currentPrice <= 0) + { + return; + } + + // SetRequirement will multiply (or not), so just need the individual price + double individualPrice = arg ? currentPrice : Math.Ceiling(currentPrice / __instance.Int32_0); + SetRequirement(__instance, rublesRequirement, individualPrice); + } + } + + // Called when item selection changes. Handles updating price if bulk is (or was) checked + public class MultipleStacksPatch : ModulePatch + { + private static bool WasBulk; + + protected override MethodBase GetTargetMethod() + { + return AccessTools.Method(typeof(AddOfferWindow), nameof(AddOfferWindow.method_9)); + } + + [PatchPrefix] + public static void Prefix(AddOfferWindow __instance) + { + WasBulk = __instance.R().BulkOffer; + } + + [PatchPostfix] + public static void Postfix(AddOfferWindow __instance, Item item, bool selected, ItemMarketPricesPanel ____pricesPanel, RequirementView[] ____requirementViews) + { + if (!Settings.UpdatePriceOnBulk.Value || __instance.Int32_0 < 1) + { + return; + } + + // Bulk can autochange when selecting/deselecting, so only bail if it wasn't and still isn't bulk + if (!WasBulk && !__instance.R().BulkOffer) + { + return; + } + + var rublesRequirement = ____requirementViews.First(rv => rv.name == "Requirement (RUB)"); + double currentPrice = rublesRequirement.Requirement.PreciseCount; + + // Need to figure out the price per item *before* this item was added/removed + int oldCount = __instance.Int32_0 + (selected ? -item.StackObjectsCount : item.StackObjectsCount); + if (oldCount <= 0) + { + return; + } + + SetRequirement(__instance, rublesRequirement, currentPrice / oldCount); + } + } + + private static void SetRequirement(AddOfferWindow window, RequirementView requirement, double price) { if (window.R().BulkOffer) { @@ -56,6 +150,26 @@ public static class AddOfferClickablePricesPatches requirement.method_0(price.ToString("F0")); } + private static void PopulateOfferPrice(AddOfferWindow window, ItemMarketPricesPanel pricesPanel, RequirementView rublesRequirement) + { + switch (Settings.AutoOfferPrice.Value) + { + case AutoFleaPrice.Minimum: + SetRequirement(window, rublesRequirement, pricesPanel.Minimum); + break; + case AutoFleaPrice.Average: + SetRequirement(window, rublesRequirement, pricesPanel.Average); + break; + case AutoFleaPrice.Maximum: + SetRequirement(window, rublesRequirement, pricesPanel.Maximum); + break; + case AutoFleaPrice.None: + default: + break; + } + } + + public class HighlightButton : Button { private Color originalColor; diff --git a/Patches/UnloadAmmoPatches.cs b/Patches/UnloadAmmoPatches.cs index 118b798..19089b2 100644 --- a/Patches/UnloadAmmoPatches.cs +++ b/Patches/UnloadAmmoPatches.cs @@ -1,4 +1,5 @@ using Comfort.Common; +using EFT; using EFT.InventoryLogic; using EFT.UI; using HarmonyLib; @@ -115,15 +116,21 @@ public static class UnloadAmmoPatches [PatchPrefix] public static void Prefix(Item item) { - if (item is AmmoBox) + if (Settings.UnloadAmmoBoxInPlace.Value && item is AmmoBox) { UnloadState = new(); } } [PatchPostfix] - public static void Postfix() + public static async void Postfix(Task __result) { + if (!Settings.UnloadAmmoBoxInPlace.Value) + { + return; + } + + await __result; UnloadState = null; } } @@ -143,8 +150,7 @@ public static class UnloadAmmoPatches return; } - AmmoBox box = item.Parent.Container.ParentItem as AmmoBox; - if (box == null) + if (item.Parent.Container.ParentItem is not AmmoBox box) { return; } @@ -191,10 +197,17 @@ public static class UnloadAmmoPatches public UnloadAmmoBoxState() { - fakeStash = (StashClass)Singleton.Instance.CreateItem("FakeStash", "566abbc34bdc2d92178b4576", null); + if (Plugin.InRaid()) + { + fakeStash = Singleton.Instance.R().Stash; + } + else + { + fakeStash = (StashClass)Singleton.Instance.CreateItem("FakeStash", "566abbc34bdc2d92178b4576", null); - var profile = PatchConstants.BackEndSession.Profile; - fakeController = new(fakeStash, profile.ProfileId, profile.Nickname); + var profile = PatchConstants.BackEndSession.Profile; + fakeController = new(fakeStash, profile.ProfileId, profile.Nickname); + } } } } diff --git a/R.cs b/R.cs index 6ea5f3d..b9c8aaf 100644 --- a/R.cs +++ b/R.cs @@ -67,6 +67,7 @@ public static class R InventoryScreen.InitTypes(); ScavengerInventoryScreen.InitTypes(); LocalizedText.InitTypes(); + GameWorld.InitTypes(); } public abstract class Wrapper(object value) @@ -867,6 +868,20 @@ public static class R set { StringCaseField.SetValue(Value, value); } } } + + public class GameWorld(object value) : Wrapper(value) + { + public static Type Type { get; private set; } + private static FieldInfo StashField; + + public static void InitTypes() + { + Type = typeof(EFT.GameWorld); + StashField = AccessTools.Field(Type, "stashClass"); + } + + public StashClass Stash { get { return (StashClass)StashField.GetValue(Value); } } + } } public static class RExtentensions @@ -898,4 +913,5 @@ public static class RExtentensions public static R.InventoryScreen R(this InventoryScreen value) => new(value); 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); } diff --git a/Settings.cs b/Settings.cs index 7d1cc59..57863a2 100644 --- a/Settings.cs +++ b/Settings.cs @@ -37,6 +37,14 @@ internal enum SortingTableDisplay Both } +internal enum AutoFleaPrice +{ + None, + Minimum, + Average, + Maximum +} + internal class Settings { // Categories @@ -87,6 +95,7 @@ internal class Settings public static ConfigEntry SwapItems { get; set; } public static ConfigEntry SwapMags { get; set; } public static ConfigEntry AlwaysSwapMags { get; set; } + public static ConfigEntry UnloadAmmoBoxInPlace { get; set; } // Advanced public static ConfigEntry SwapImpossibleContainers { get; set; } public static ConfigEntry ReorderGrids { get; set; } public static ConfigEntry SynchronizeStashScrolling { get; set; } @@ -125,6 +134,8 @@ internal class Settings public static ConfigEntry ShowRequiredQuest { get; set; } public static ConfigEntry AutoExpandCategories { get; set; } public static ConfigEntry ClearFiltersOnSearch { get; set; } + public static ConfigEntry AutoOfferPrice { get; set; } + public static ConfigEntry UpdatePriceOnBulk { get; set; } public static ConfigEntry KeepAddOfferOpen { get; set; } public static ConfigEntry PurchaseAllKeybind { get; set; } public static ConfigEntry KeepAddOfferOpenIgnoreMaxOffers { get; set; } // Advanced @@ -452,6 +463,15 @@ internal class Settings null, new ConfigurationManagerAttributes { }))); + configEntries.Add(UnloadAmmoBoxInPlace = config.Bind( + InventorySection, + "Unload Ammo Boxes In-Place", + true, + new ConfigDescription( + "Whether to unload ammo boxes in-place, otherwise there needs to be free space somewhere", + null, + new ConfigurationManagerAttributes { IsAdvanced = true }))); + configEntries.Add(SwapImpossibleContainers = config.Bind( InventorySection, "Swap with Incompatible Containers", @@ -734,6 +754,24 @@ internal class Settings null, new ConfigurationManagerAttributes { }))); + configEntries.Add(AutoOfferPrice = config.Bind( + FleaMarketSection, + "Autopopulate Offer Price", + AutoFleaPrice.None, + new ConfigDescription( + "Autopopulte new offers with min/avg/max market price, or leave blank", + null, + new ConfigurationManagerAttributes { }))); + + configEntries.Add(UpdatePriceOnBulk = config.Bind( + FleaMarketSection, + "Update Offer Price on Bulk", + true, + new ConfigDescription( + "Automatically multiply or divide the price when you check/uncheck bulk, or or when you change the number of selected items while bulk is checked.", + null, + new ConfigurationManagerAttributes { }))); + configEntries.Add(ShowRequiredQuest = config.Bind( FleaMarketSection, "Show Required Quest for Locked Offers", diff --git a/UIFixes.csproj b/UIFixes.csproj index fd3a371..69bc19d 100644 --- a/UIFixes.csproj +++ b/UIFixes.csproj @@ -4,7 +4,7 @@ net471 Tyfon.UIFixes SPT UI Fixes - 2.3.1 + 2.3.2 true latest Debug;Release diff --git a/server/package.json b/server/package.json index c5a5469..68db11c 100644 --- a/server/package.json +++ b/server/package.json @@ -1,6 +1,6 @@ { "name": "uifixes", - "version": "2.3.1", + "version": "2.3.2", "main": "src/mod.js", "license": "MIT", "author": "Tyfon", From 960cb725ec4e0f3187cbddd0011d3687046eca7b Mon Sep 17 00:00:00 2001 From: Tyfon <29051038+tyfon7@users.noreply.github.com> Date: Tue, 23 Jul 2024 19:00:00 -0700 Subject: [PATCH 02/73] limit nonstandard dragging --- Patches/LimitDragPatches.cs | 44 +++++++++++++++++++++++++++++++++++++ Plugin.cs | 1 + Settings.cs | 14 ++++++++++-- 3 files changed, 57 insertions(+), 2 deletions(-) create mode 100644 Patches/LimitDragPatches.cs diff --git a/Patches/LimitDragPatches.cs b/Patches/LimitDragPatches.cs new file mode 100644 index 0000000..3795443 --- /dev/null +++ b/Patches/LimitDragPatches.cs @@ -0,0 +1,44 @@ +using System; +using System.Reflection; +using EFT.UI; +using HarmonyLib; +using SPT.Reflection.Patching; +using UnityEngine; +using UnityEngine.EventSystems; + +namespace UIFixes; + +public static class LimitDragPatches +{ + public static void Enable() + { + new OnDragEventPatch(typeof(DragTrigger), nameof(DragTrigger.OnDrag)).Enable(); + new OnDragEventPatch(typeof(DragTrigger), nameof(DragTrigger.OnBeginDrag)).Enable(); + new OnDragEventPatch(typeof(DragTrigger), nameof(DragTrigger.OnEndDrag)).Enable(); + + new OnDragEventPatch(typeof(UIDragComponent), "UnityEngine.EventSystems.IDragHandler.OnDrag").Enable(); + new OnDragEventPatch(typeof(UIDragComponent), "UnityEngine.EventSystems.IBeginDragHandler.OnBeginDrag").Enable(); + } + + public class OnDragEventPatch(Type type, string methodName) : ModulePatch + { + private readonly string methodName = methodName; + private readonly Type type = type; + + protected override MethodBase GetTargetMethod() + { + return AccessTools.Method(type, methodName); + } + + [PatchPrefix] + public static bool Prefix(PointerEventData eventData) + { + if (!Settings.LimitNonstandardDrags.Value) + { + return true; + } + + return eventData.button == PointerEventData.InputButton.Left && !Input.GetKey(KeyCode.LeftShift) && !Input.GetKey(KeyCode.RightShift); + } + } +} \ No newline at end of file diff --git a/Plugin.cs b/Plugin.cs index 5072daa..46f2e68 100644 --- a/Plugin.cs +++ b/Plugin.cs @@ -68,6 +68,7 @@ public class Plugin : BaseUnityPlugin ReloadInPlacePatches.Enable(); BarterOfferPatches.Enable(); new UnlockCursorPatch().Enable(); + LimitDragPatches.Enable(); } public static bool InRaid() diff --git a/Settings.cs b/Settings.cs index 57863a2..f940946 100644 --- a/Settings.cs +++ b/Settings.cs @@ -70,6 +70,8 @@ internal class Settings public static ConfigEntry UseHomeEnd { get; set; } public static ConfigEntry RebindPageUpDown { get; set; } public static ConfigEntry MouseScrollMulti { get; set; } + public static ConfigEntry UseRaidMouseScrollMulti { get; set; } // Advanced + public static ConfigEntry MouseScrollMultiInRaid { get; set; } // Advanced public static ConfigEntry InspectKeyBind { get; set; } public static ConfigEntry OpenKeyBind { get; set; } public static ConfigEntry ExamineKeyBind { get; set; } @@ -81,8 +83,7 @@ internal class Settings public static ConfigEntry FilterByKeyBind { get; set; } public static ConfigEntry LinkedSearchKeyBind { get; set; } public static ConfigEntry SortingTableKeyBind { get; set; } - public static ConfigEntry UseRaidMouseScrollMulti { get; set; } // Advanced - public static ConfigEntry MouseScrollMultiInRaid { get; set; } // Advanced + public static ConfigEntry LimitNonstandardDrags { get; set; } // Advanced public static ConfigEntry ItemContextBlocksTextInputs { get; set; } // Advanced // Inventory @@ -372,6 +373,15 @@ internal class Settings null, new ConfigurationManagerAttributes { }))); + configEntries.Add(LimitNonstandardDrags = config.Bind( + InputSection, + "Limit Nonstandard Drags", + true, + new ConfigDescription( + "Constrain dragging to the left mouse, when shift is not down", + null, + new ConfigurationManagerAttributes { IsAdvanced = true }))); + configEntries.Add(ItemContextBlocksTextInputs = config.Bind( InputSection, "Block Text Inputs on Item Mouseover", From 8fb47995763f5aa52c19b563e1a8715d3a4b8fab Mon Sep 17 00:00:00 2001 From: Tyfon <29051038+tyfon7@users.noreply.github.com> Date: Wed, 24 Jul 2024 00:28:00 -0700 Subject: [PATCH 03/73] comment out ammo box in-place temporarily --- Patches/UnloadAmmoPatches.cs | 186 +++++++++++++++++------------------ Settings.cs | 18 ++-- 2 files changed, 102 insertions(+), 102 deletions(-) diff --git a/Patches/UnloadAmmoPatches.cs b/Patches/UnloadAmmoPatches.cs index 19089b2..9d94e1f 100644 --- a/Patches/UnloadAmmoPatches.cs +++ b/Patches/UnloadAmmoPatches.cs @@ -15,7 +15,7 @@ namespace UIFixes; public static class UnloadAmmoPatches { - private static UnloadAmmoBoxState UnloadState = null; + //private static UnloadAmmoBoxState UnloadState = null; public static void Enable() { @@ -24,8 +24,8 @@ public static class UnloadAmmoPatches new UnloadScavTransferPatch().Enable(); new NoScavStashPatch().Enable(); - new UnloadAmmoBoxPatch().Enable(); - new QuickFindUnloadAmmoBoxPatch().Enable(); + //new UnloadAmmoBoxPatch().Enable(); + //new QuickFindUnloadAmmoBoxPatch().Enable(); } public class TradingPlayerPatch : ModulePatch @@ -106,108 +106,108 @@ public static class UnloadAmmoPatches } } - public class UnloadAmmoBoxPatch : ModulePatch - { - protected override MethodBase GetTargetMethod() - { - return AccessTools.Method(typeof(ItemUiContext), nameof(ItemUiContext.UnloadAmmo)); - } + // public class UnloadAmmoBoxPatch : ModulePatch + // { + // protected override MethodBase GetTargetMethod() + // { + // return AccessTools.Method(typeof(ItemUiContext), nameof(ItemUiContext.UnloadAmmo)); + // } - [PatchPrefix] - public static void Prefix(Item item) - { - if (Settings.UnloadAmmoBoxInPlace.Value && item is AmmoBox) - { - UnloadState = new(); - } - } + // [PatchPrefix] + // public static void Prefix(Item item) + // { + // if (Settings.UnloadAmmoBoxInPlace.Value && item is AmmoBox) + // { + // UnloadState = new(); + // } + // } - [PatchPostfix] - public static async void Postfix(Task __result) - { - if (!Settings.UnloadAmmoBoxInPlace.Value) - { - return; - } + // [PatchPostfix] + // public static async void Postfix(Task __result) + // { + // if (!Settings.UnloadAmmoBoxInPlace.Value) + // { + // return; + // } - await __result; - UnloadState = null; - } - } + // await __result; + // UnloadState = null; + // } + // } - public class QuickFindUnloadAmmoBoxPatch : ModulePatch - { - protected override MethodBase GetTargetMethod() - { - return AccessTools.Method(typeof(InteractionsHandlerClass), nameof(InteractionsHandlerClass.QuickFindAppropriatePlace)); - } + // public class QuickFindUnloadAmmoBoxPatch : ModulePatch + // { + // protected override MethodBase GetTargetMethod() + // { + // return AccessTools.Method(typeof(InteractionsHandlerClass), nameof(InteractionsHandlerClass.QuickFindAppropriatePlace)); + // } - [PatchPrefix] - public static void Prefix(Item item, TraderControllerClass controller, ref IEnumerable targets, ref InteractionsHandlerClass.EMoveItemOrder order) - { - if (UnloadState == null) - { - return; - } + // [PatchPrefix] + // public static void Prefix(Item item, TraderControllerClass controller, ref IEnumerable targets, ref InteractionsHandlerClass.EMoveItemOrder order) + // { + // if (UnloadState == null) + // { + // return; + // } - if (item.Parent.Container.ParentItem is not AmmoBox box) - { - return; - } + // if (item.Parent.Container.ParentItem is not AmmoBox box) + // { + // return; + // } - // Ammo boxes with multiple stacks will loop through this code, so we only want to move the box once - if (UnloadState.initialized) - { - order = UnloadState.order; - targets = UnloadState.targets; - } - else - { - // Have to do this for them, since the calls to get parent will be wrong once we move the box - if (!order.HasFlag(InteractionsHandlerClass.EMoveItemOrder.IgnoreItemParent)) - { - LootItemClass parent = (item.GetNotMergedParent() as LootItemClass) ?? (item.GetRootMergedItem() as EquipmentClass); - if (parent != null) - { - UnloadState.targets = targets = order.HasFlag(InteractionsHandlerClass.EMoveItemOrder.PrioritizeParent) ? - parent.ToEnumerable().Concat(targets).Distinct() : - targets.Concat(parent.ToEnumerable()).Distinct(); - } + // // Ammo boxes with multiple stacks will loop through this code, so we only want to move the box once + // if (UnloadState.initialized) + // { + // order = UnloadState.order; + // targets = UnloadState.targets; + // } + // else + // { + // // Have to do this for them, since the calls to get parent will be wrong once we move the box + // if (!order.HasFlag(InteractionsHandlerClass.EMoveItemOrder.IgnoreItemParent)) + // { + // LootItemClass parent = (item.GetNotMergedParent() as LootItemClass) ?? (item.GetRootMergedItem() as EquipmentClass); + // if (parent != null) + // { + // UnloadState.targets = targets = order.HasFlag(InteractionsHandlerClass.EMoveItemOrder.PrioritizeParent) ? + // parent.ToEnumerable().Concat(targets).Distinct() : + // targets.Concat(parent.ToEnumerable()).Distinct(); + // } - UnloadState.order = order |= InteractionsHandlerClass.EMoveItemOrder.IgnoreItemParent; - } + // UnloadState.order = order |= InteractionsHandlerClass.EMoveItemOrder.IgnoreItemParent; + // } - var operation = InteractionsHandlerClass.Move(box, UnloadState.fakeStash.Grid.FindLocationForItem(box), controller, false); - operation.Value.RaiseEvents(controller, CommandStatus.Begin); - operation.Value.RaiseEvents(controller, CommandStatus.Succeed); + // var operation = InteractionsHandlerClass.Move(box, UnloadState.fakeStash.Grid.FindLocationForItem(box), controller, false); + // operation.Value.RaiseEvents(controller, CommandStatus.Begin); + // operation.Value.RaiseEvents(controller, CommandStatus.Succeed); - UnloadState.initialized = true; - } - } - } + // UnloadState.initialized = true; + // } + // } + // } - public class UnloadAmmoBoxState - { - public StashClass fakeStash; - public TraderControllerClass fakeController; + // public class UnloadAmmoBoxState + // { + // public StashClass fakeStash; + // public TraderControllerClass fakeController; - public bool initialized; - public InteractionsHandlerClass.EMoveItemOrder order; - public IEnumerable targets; + // public bool initialized; + // public InteractionsHandlerClass.EMoveItemOrder order; + // public IEnumerable targets; - public UnloadAmmoBoxState() - { - if (Plugin.InRaid()) - { - fakeStash = Singleton.Instance.R().Stash; - } - else - { - fakeStash = (StashClass)Singleton.Instance.CreateItem("FakeStash", "566abbc34bdc2d92178b4576", null); + // public UnloadAmmoBoxState() + // { + // if (Plugin.InRaid()) + // { + // fakeStash = Singleton.Instance.R().Stash; + // } + // else + // { + // fakeStash = (StashClass)Singleton.Instance.CreateItem("FakeStash", "566abbc34bdc2d92178b4576", null); - var profile = PatchConstants.BackEndSession.Profile; - fakeController = new(fakeStash, profile.ProfileId, profile.Nickname); - } - } - } + // var profile = PatchConstants.BackEndSession.Profile; + // fakeController = new(fakeStash, profile.ProfileId, profile.Nickname); + // } + // } + // } } diff --git a/Settings.cs b/Settings.cs index f940946..c426ab4 100644 --- a/Settings.cs +++ b/Settings.cs @@ -96,7 +96,7 @@ internal class Settings public static ConfigEntry SwapItems { get; set; } public static ConfigEntry SwapMags { get; set; } public static ConfigEntry AlwaysSwapMags { get; set; } - public static ConfigEntry UnloadAmmoBoxInPlace { get; set; } // Advanced + //public static ConfigEntry UnloadAmmoBoxInPlace { get; set; } // Advanced public static ConfigEntry SwapImpossibleContainers { get; set; } public static ConfigEntry ReorderGrids { get; set; } public static ConfigEntry SynchronizeStashScrolling { get; set; } @@ -473,14 +473,14 @@ internal class Settings null, new ConfigurationManagerAttributes { }))); - configEntries.Add(UnloadAmmoBoxInPlace = config.Bind( - InventorySection, - "Unload Ammo Boxes In-Place", - true, - new ConfigDescription( - "Whether to unload ammo boxes in-place, otherwise there needs to be free space somewhere", - null, - new ConfigurationManagerAttributes { IsAdvanced = true }))); + // configEntries.Add(UnloadAmmoBoxInPlace = config.Bind( + // InventorySection, + // "Unload Ammo Boxes In-Place", + // true, + // new ConfigDescription( + // "Whether to unload ammo boxes in-place, otherwise there needs to be free space somewhere", + // null, + // new ConfigurationManagerAttributes { IsAdvanced = true }))); configEntries.Add(SwapImpossibleContainers = config.Bind( InventorySection, From a51d21a8558857ed53526092a0172b31beb40ca4 Mon Sep 17 00:00:00 2001 From: Tyfon <29051038+tyfon7@users.noreply.github.com> Date: Wed, 24 Jul 2024 12:50:29 -0700 Subject: [PATCH 04/73] Reload in-place for fika --- Patches/ReloadInPlacePatches.cs | 6 ++++++ Plugin.cs | 14 ++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/Patches/ReloadInPlacePatches.cs b/Patches/ReloadInPlacePatches.cs index 1d6dbce..bb5b5da 100644 --- a/Patches/ReloadInPlacePatches.cs +++ b/Patches/ReloadInPlacePatches.cs @@ -103,6 +103,12 @@ public static class ReloadInPlacePatches { protected override MethodBase GetTargetMethod() { + if (Plugin.FikaPresent()) + { + Type type = Type.GetType("Fika.Core.Coop.ClientClasses.CoopClientFirearmController, Fika.Core"); + return AccessTools.Method(type, "ReloadMag"); + } + return AccessTools.Method(typeof(Player.FirearmController), nameof(Player.FirearmController.ReloadMag)); } diff --git a/Plugin.cs b/Plugin.cs index 46f2e68..e23bd66 100644 --- a/Plugin.cs +++ b/Plugin.cs @@ -1,10 +1,12 @@ using BepInEx; +using BepInEx.Bootstrap; using Comfort.Common; using EFT; namespace UIFixes; [BepInPlugin(PluginInfo.PLUGIN_GUID, PluginInfo.PLUGIN_NAME, PluginInfo.PLUGIN_VERSION)] +[BepInDependency("com.fika.core", BepInDependency.DependencyFlags.SoftDependency)] public class Plugin : BaseUnityPlugin { public void Awake() @@ -76,4 +78,16 @@ public class Plugin : BaseUnityPlugin bool? inRaid = Singleton.Instance?.InRaid; return inRaid.HasValue && inRaid.Value; } + + private static bool? IsFikaPresent; + + public static bool FikaPresent() + { + if (!IsFikaPresent.HasValue) + { + IsFikaPresent = Chainloader.PluginInfos.ContainsKey("com.fika.core"); + } + + return IsFikaPresent.Value; + } } From 0e7e0f82a66392c359d2d63fa218165c5a0e0cea Mon Sep 17 00:00:00 2001 From: Tyfon <29051038+tyfon7@users.noreply.github.com> Date: Wed, 24 Jul 2024 13:32:20 -0700 Subject: [PATCH 05/73] Always swap means always swap --- Patches/ReloadInPlacePatches.cs | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/Patches/ReloadInPlacePatches.cs b/Patches/ReloadInPlacePatches.cs index bb5b5da..a1a7b73 100644 --- a/Patches/ReloadInPlacePatches.cs +++ b/Patches/ReloadInPlacePatches.cs @@ -1,4 +1,5 @@ using EFT; +using EFT.InventoryLogic; using EFT.UI; using HarmonyLib; using SPT.Reflection.Patching; @@ -12,6 +13,7 @@ public static class ReloadInPlacePatches { private static bool IsReloading = false; private static MagazineClass FoundMagazine = null; + private static ItemAddress FoundAddress = null; public static void Enable() { @@ -19,6 +21,7 @@ public static class ReloadInPlacePatches new ReloadInPlacePatch().Enable(); new ReloadInPlaceFindMagPatch().Enable(); new ReloadInPlaceFindSpotPatch().Enable(); + new AlwaysSwapPatch().Enable(); // This patches the firearmsController code when you hit R in raid with an external magazine class new SwapIfNoSpacePatch().Enable(); @@ -42,6 +45,7 @@ public static class ReloadInPlacePatches { IsReloading = false; FoundMagazine = null; + FoundAddress = null; } } @@ -58,6 +62,7 @@ public static class ReloadInPlacePatches if (IsReloading) { FoundMagazine = __result; + FoundAddress = FoundMagazine.Parent; } } } @@ -66,7 +71,7 @@ public static class ReloadInPlacePatches { protected override MethodBase GetTargetMethod() { - Type type = typeof(ItemUiContext).GetNestedTypes().Single(t => t.GetField("currentMagazine") != null); + Type type = typeof(ItemUiContext).GetNestedTypes().Single(t => t.GetField("currentMagazine") != null); // ItemUiContext.Class2546 return AccessTools.Method(type, "method_0"); } @@ -99,6 +104,30 @@ public static class ReloadInPlacePatches } } + public class AlwaysSwapPatch : ModulePatch + { + protected override MethodBase GetTargetMethod() + { + Type type = typeof(ItemUiContext).GetNestedTypes().Single(t => t.GetField("func_3") != null); // ItemUiContext.Class2536 + return AccessTools.Method(type, "method_4"); + } + + [PatchPostfix] + public static void Postfix(ItemAddressClass g, ref int __result) + { + if (!Settings.AlwaysSwapMags.Value) + { + return; + } + + if (!g.Equals(FoundAddress)) + { + // Addresses that aren't the found address get massive value increase so found address is sorted first + __result += 1000; + } + } + } + public class SwapIfNoSpacePatch : ModulePatch { protected override MethodBase GetTargetMethod() @@ -132,6 +161,7 @@ public static class ReloadInPlacePatches } InventoryControllerClass controller = __instance.Weapon.Owner as InventoryControllerClass; + ItemAddress magAddress = magazine.Parent; // Null address means it couldn't find a spot. Try to remove magazine (temporarily) and try again var operation = InteractionsHandlerClass.Remove(magazine, controller, false, false); @@ -143,6 +173,7 @@ public static class ReloadInPlacePatches gridItemAddress = controller.Inventory.Equipment.GetPrioritizedGridsForUnloadedObject(false) .Select(grid => grid.FindLocationForItem(currentMagazine)) .Where(address => address != null) + .OrderByDescending(address => Settings.AlwaysSwapMags.Value && address.Equals(magAddress)) // Prioritize swapping if desired .OrderBy(address => address.Grid.GridWidth.Value * address.Grid.GridHeight.Value) .FirstOrDefault(); // BSG's version checks null again, but there's no nulls already. If there's no matches, the enumerable is empty From 458a2df57cc1dcd86a392fc40d071a739cc143a4 Mon Sep 17 00:00:00 2001 From: Tyfon <29051038+tyfon7@users.noreply.github.com> Date: Thu, 25 Jul 2024 12:19:34 -0700 Subject: [PATCH 06/73] Fix right-mouse multiselect clearing on context menu --- Multiselect/DrawMultiSelect.cs | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/Multiselect/DrawMultiSelect.cs b/Multiselect/DrawMultiSelect.cs index bf3e469..eaefc31 100644 --- a/Multiselect/DrawMultiSelect.cs +++ b/Multiselect/DrawMultiSelect.cs @@ -63,8 +63,8 @@ public class DrawMultiSelect : MonoBehaviour { bool shiftDown = Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift); - // Only need to check we aren't over draggables/clickables if the multiselect key is left mouse - if (Settings.SelectionBoxKey.Value.MainKey == KeyCode.Mouse0 && !shiftDown && !MouseIsOverClickable()) + // Special case: if selection key is mouse0 (left), don't start selection if over a clickable + if (Settings.SelectionBoxKey.Value.MainKey == KeyCode.Mouse0 && !shiftDown && MouseIsOverClickable()) { return; } @@ -75,7 +75,11 @@ public class DrawMultiSelect : MonoBehaviour if (!secondary) { - MultiSelect.Clear(); + // Special case: if selection key is any mouse key (center,right), don't clear selection on mouse down if over item + if (Settings.SelectionBoxKey.Value.MainKey != KeyCode.Mouse1 && Settings.SelectionBoxKey.Value.MainKey != KeyCode.Mouse2 || !MouseIsOverItem()) + { + MultiSelect.Clear(); + } } } @@ -165,12 +169,17 @@ public class DrawMultiSelect : MonoBehaviour } } - private bool MouseIsOverClickable() + private bool MouseIsOverItem() { // checking ItemUiContext is a quick and easy way to know the mouse is over an item - if (ItemUiContext.Instance.R().ItemContext != null) + return ItemUiContext.Instance.R().ItemContext != null; + } + + private bool MouseIsOverClickable() + { + if (MouseIsOverItem()) { - return false; + return true; } PointerEventData eventData = new(EventSystem.current) @@ -194,11 +203,11 @@ public class DrawMultiSelect : MonoBehaviour if (draggables.Any() || clickables.Any()) { - return false; + return true; } } - return true; + return false; } private bool IsOnTop(Rect itemRect, Transform itemTransform, GraphicRaycaster raycaster) From e2d51f25d9f6aa2039f9480961fb9007cd46290a Mon Sep 17 00:00:00 2001 From: Tyfon <29051038+tyfon7@users.noreply.github.com> Date: Thu, 25 Jul 2024 12:25:53 -0700 Subject: [PATCH 07/73] Re-enable ammobox unload in-place, fixed for fika --- Patches/UnloadAmmoPatches.cs | 206 +++++++++++++++++++---------------- R.cs | 6 +- Settings.cs | 18 +-- 3 files changed, 124 insertions(+), 106 deletions(-) diff --git a/Patches/UnloadAmmoPatches.cs b/Patches/UnloadAmmoPatches.cs index 9d94e1f..f88a2e4 100644 --- a/Patches/UnloadAmmoPatches.cs +++ b/Patches/UnloadAmmoPatches.cs @@ -1,5 +1,6 @@ using Comfort.Common; using EFT; +using EFT.Communications; using EFT.InventoryLogic; using EFT.UI; using HarmonyLib; @@ -15,8 +16,6 @@ namespace UIFixes; public static class UnloadAmmoPatches { - //private static UnloadAmmoBoxState UnloadState = null; - public static void Enable() { new TradingPlayerPatch().Enable(); @@ -24,8 +23,7 @@ public static class UnloadAmmoPatches new UnloadScavTransferPatch().Enable(); new NoScavStashPatch().Enable(); - //new UnloadAmmoBoxPatch().Enable(); - //new QuickFindUnloadAmmoBoxPatch().Enable(); + new UnloadAmmoBoxPatch().Enable(); } public class TradingPlayerPatch : ModulePatch @@ -106,108 +104,128 @@ public static class UnloadAmmoPatches } } - // public class UnloadAmmoBoxPatch : ModulePatch - // { - // protected override MethodBase GetTargetMethod() - // { - // return AccessTools.Method(typeof(ItemUiContext), nameof(ItemUiContext.UnloadAmmo)); - // } + public class UnloadAmmoBoxPatch : ModulePatch + { + protected override MethodBase GetTargetMethod() + { + return AccessTools.Method(typeof(ItemUiContext), nameof(ItemUiContext.UnloadAmmo)); + } - // [PatchPrefix] - // public static void Prefix(Item item) - // { - // if (Settings.UnloadAmmoBoxInPlace.Value && item is AmmoBox) - // { - // UnloadState = new(); - // } - // } + [PatchPrefix] + public static bool Prefix(Item item, ref Task __result, InventoryContainerClass ___inventoryControllerClass) + { + if (!Settings.UnloadAmmoBoxInPlace.Value || item is not AmmoBox ammoBox) + { + return true; + } - // [PatchPostfix] - // public static async void Postfix(Task __result) - // { - // if (!Settings.UnloadAmmoBoxInPlace.Value) - // { - // return; - // } + if (ammoBox.Cartridges.Last is not BulletClass lastBullet) + { + return true; + } - // await __result; - // UnloadState = null; - // } - // } + __result = UnloadAmmoBox(ammoBox, ___inventoryControllerClass); + return false; + } - // public class QuickFindUnloadAmmoBoxPatch : ModulePatch - // { - // protected override MethodBase GetTargetMethod() - // { - // return AccessTools.Method(typeof(InteractionsHandlerClass), nameof(InteractionsHandlerClass.QuickFindAppropriatePlace)); - // } + private static async Task UnloadAmmoBox(AmmoBox ammoBox, InventoryControllerClass inventoryController) + { + BulletClass lastBullet = ammoBox.Cartridges.Last as BulletClass; + IEnumerable containers = inventoryController.Inventory.Stash != null ? + [inventoryController.Inventory.Equipment, inventoryController.Inventory.Stash] : + [inventoryController.Inventory.Equipment]; - // [PatchPrefix] - // public static void Prefix(Item item, TraderControllerClass controller, ref IEnumerable targets, ref InteractionsHandlerClass.EMoveItemOrder order) - // { - // if (UnloadState == null) - // { - // return; - // } + // Explicitly add the current parent before its moved. IgnoreParentItem will be sent along later + containers = containers.Prepend(ammoBox.Parent.Container.ParentItem as LootItemClass); - // if (item.Parent.Container.ParentItem is not AmmoBox box) - // { - // return; - // } + // Move the box to a temporary stash so it can unload in place + TraderControllerClass tempController = GetTempController(); + StashClass tempStash = tempController.RootItem as StashClass; + var moveOperation = InteractionsHandlerClass.Move(ammoBox, tempStash.Grid.FindLocationForItem(ammoBox), inventoryController, true); + if (moveOperation.Succeeded) + { + IResult networkResult = await inventoryController.TryRunNetworkTransaction(moveOperation); + if (networkResult.Failed) + { + moveOperation = new GClass3370(networkResult.Error); + } - // // Ammo boxes with multiple stacks will loop through this code, so we only want to move the box once - // if (UnloadState.initialized) - // { - // order = UnloadState.order; - // targets = UnloadState.targets; - // } - // else - // { - // // Have to do this for them, since the calls to get parent will be wrong once we move the box - // if (!order.HasFlag(InteractionsHandlerClass.EMoveItemOrder.IgnoreItemParent)) - // { - // LootItemClass parent = (item.GetNotMergedParent() as LootItemClass) ?? (item.GetRootMergedItem() as EquipmentClass); - // if (parent != null) - // { - // UnloadState.targets = targets = order.HasFlag(InteractionsHandlerClass.EMoveItemOrder.PrioritizeParent) ? - // parent.ToEnumerable().Concat(targets).Distinct() : - // targets.Concat(parent.ToEnumerable()).Distinct(); - // } + // Surprise! The operation is STILL not done. + await Task.Yield(); + } - // UnloadState.order = order |= InteractionsHandlerClass.EMoveItemOrder.IgnoreItemParent; - // } + if (moveOperation.Failed) + { + NotificationManagerClass.DisplayWarningNotification(moveOperation.Error.ToString(), ENotificationDurationType.Default); + return; + } - // var operation = InteractionsHandlerClass.Move(box, UnloadState.fakeStash.Grid.FindLocationForItem(box), controller, false); - // operation.Value.RaiseEvents(controller, CommandStatus.Begin); - // operation.Value.RaiseEvents(controller, CommandStatus.Succeed); + bool unloadedAny = false; + ItemOperation operation = default; + for (BulletClass bullet = lastBullet; bullet != null; bullet = ammoBox.Cartridges.Last as BulletClass) + { + operation = InteractionsHandlerClass.QuickFindAppropriatePlace( + bullet, + inventoryController, + containers, + InteractionsHandlerClass.EMoveItemOrder.UnloadAmmo | InteractionsHandlerClass.EMoveItemOrder.IgnoreItemParent, + true); - // UnloadState.initialized = true; - // } - // } - // } + if (operation.Failed) + { + break; + } - // public class UnloadAmmoBoxState - // { - // public StashClass fakeStash; - // public TraderControllerClass fakeController; + unloadedAny = true; - // public bool initialized; - // public InteractionsHandlerClass.EMoveItemOrder order; - // public IEnumerable targets; + IResult networkResult = await inventoryController.TryRunNetworkTransaction(operation); + if (networkResult.Failed) + { + operation = new GClass3370(networkResult.Error); + break; + } - // public UnloadAmmoBoxState() - // { - // if (Plugin.InRaid()) - // { - // fakeStash = Singleton.Instance.R().Stash; - // } - // else - // { - // fakeStash = (StashClass)Singleton.Instance.CreateItem("FakeStash", "566abbc34bdc2d92178b4576", null); + if (operation.Value is GInterface343 raisable) + { + raisable.TargetItem.RaiseRefreshEvent(false, true); + } - // var profile = PatchConstants.BackEndSession.Profile; - // fakeController = new(fakeStash, profile.ProfileId, profile.Nickname); - // } - // } - // } + // Surprise! The operation STILL IS NOT DONE. + await Task.Yield(); + } + + if (unloadedAny && Singleton.Instantiated) + { + Singleton.Instance.PlayItemSound(lastBullet.ItemSound, EInventorySoundType.drop, false); + } + + if (operation.Succeeded) + { + tempController.DestroyItem(ammoBox); + } + else + { + ammoBox.RaiseRefreshEvent(false, true); + } + + if (operation.Failed) + { + NotificationManagerClass.DisplayWarningNotification(operation.Error.ToString(), ENotificationDurationType.Default); + } + } + + private static TraderControllerClass GetTempController() + { + if (Plugin.InRaid()) + { + return Singleton.Instance.R().TraderController; + } + else + { + var profile = PatchConstants.BackEndSession.Profile; + StashClass fakeStash = (StashClass)Singleton.Instance.CreateItem("FakeStash", "566abbc34bdc2d92178b4576", null); + return new(fakeStash, profile.ProfileId, profile.Nickname); + } + } + } } diff --git a/R.cs b/R.cs index b9c8aaf..ac173f2 100644 --- a/R.cs +++ b/R.cs @@ -872,15 +872,15 @@ public static class R public class GameWorld(object value) : Wrapper(value) { public static Type Type { get; private set; } - private static FieldInfo StashField; + private static FieldInfo TraderControllerField; public static void InitTypes() { Type = typeof(EFT.GameWorld); - StashField = AccessTools.Field(Type, "stashClass"); + TraderControllerField = AccessTools.Field(Type, "traderControllerClass"); } - public StashClass Stash { get { return (StashClass)StashField.GetValue(Value); } } + public TraderControllerClass TraderController { get { return (TraderControllerClass)TraderControllerField.GetValue(Value); } } } } diff --git a/Settings.cs b/Settings.cs index c426ab4..f940946 100644 --- a/Settings.cs +++ b/Settings.cs @@ -96,7 +96,7 @@ internal class Settings public static ConfigEntry SwapItems { get; set; } public static ConfigEntry SwapMags { get; set; } public static ConfigEntry AlwaysSwapMags { get; set; } - //public static ConfigEntry UnloadAmmoBoxInPlace { get; set; } // Advanced + public static ConfigEntry UnloadAmmoBoxInPlace { get; set; } // Advanced public static ConfigEntry SwapImpossibleContainers { get; set; } public static ConfigEntry ReorderGrids { get; set; } public static ConfigEntry SynchronizeStashScrolling { get; set; } @@ -473,14 +473,14 @@ internal class Settings null, new ConfigurationManagerAttributes { }))); - // configEntries.Add(UnloadAmmoBoxInPlace = config.Bind( - // InventorySection, - // "Unload Ammo Boxes In-Place", - // true, - // new ConfigDescription( - // "Whether to unload ammo boxes in-place, otherwise there needs to be free space somewhere", - // null, - // new ConfigurationManagerAttributes { IsAdvanced = true }))); + configEntries.Add(UnloadAmmoBoxInPlace = config.Bind( + InventorySection, + "Unload Ammo Boxes In-Place", + true, + new ConfigDescription( + "Whether to unload ammo boxes in-place, otherwise there needs to be free space somewhere", + null, + new ConfigurationManagerAttributes { IsAdvanced = true }))); configEntries.Add(SwapImpossibleContainers = config.Bind( InventorySection, From 8ac18a3c85eb7031966aad05237d832d96f7935c Mon Sep 17 00:00:00 2001 From: Tyfon <29051038+tyfon7@users.noreply.github.com> Date: Thu, 25 Jul 2024 14:00:51 -0700 Subject: [PATCH 08/73] ToggleHold sprint, tactical, headlight, goggles --- Patches/AimToggleHoldPatches.cs | 95 +++++++++++++++++++++++++++------ Settings.cs | 42 ++++++++++++++- 2 files changed, 119 insertions(+), 18 deletions(-) diff --git a/Patches/AimToggleHoldPatches.cs b/Patches/AimToggleHoldPatches.cs index e8d3210..10cda72 100644 --- a/Patches/AimToggleHoldPatches.cs +++ b/Patches/AimToggleHoldPatches.cs @@ -1,7 +1,9 @@ using Comfort.Common; using EFT.InputSystem; using HarmonyLib; +using JsonType; using SPT.Reflection.Patching; +using System; using System.Collections.Generic; using System.Linq; using System.Reflection; @@ -12,17 +14,18 @@ public static class AimToggleHoldPatches { public static void Enable() { - new AddStatesPatch().Enable(); + new AddTwoKeyStatesPatch().Enable(); + new AddOneKeyStatesPatch().Enable(); new UpdateInputPatch().Enable(); - Settings.ToggleOrHoldAim.SettingChanged += (_, _) => - { - // Will "save" control settings, running GClass1911.UpdateInput, which will set (or unset) toggle/hold behavior - Singleton.Instance.Control.Controller.method_3(); - }; + Settings.ToggleOrHoldAim.SettingChanged += OnSettingChanged; + Settings.ToggleOrHoldSprint.SettingChanged += OnSettingChanged; + Settings.ToggleOrHoldTactical.SettingChanged += OnSettingChanged; + Settings.ToggleOrHoldHeadlight.SettingChanged += OnSettingChanged; + Settings.ToggleOrHoldGoggles.SettingChanged += OnSettingChanged; } - public class AddStatesPatch : ModulePatch + public class AddTwoKeyStatesPatch : ModulePatch { private static FieldInfo StateMachineArray; @@ -35,17 +38,61 @@ public static class AimToggleHoldPatches [PatchPostfix] public static void Postfix(ToggleKeyCombination __instance, EGameKey gameKey, ECommand disableCommand, KeyCombination.KeyCombinationState[] ___keyCombinationState_1) { - if (!Settings.ToggleOrHoldAim.Value || gameKey != EGameKey.Aim) + bool useToggleHold = gameKey switch + { + EGameKey.Aim => Settings.ToggleOrHoldAim.Value, + EGameKey.Sprint => Settings.ToggleOrHoldSprint.Value, + _ => false + }; + + if (!useToggleHold) { return; } List states = new(___keyCombinationState_1) - { - new ToggleHoldIdleState(__instance), - new ToggleHoldClickOrHoldState(__instance), - new ToggleHoldHoldState(__instance, disableCommand) - }; + { + new ToggleHoldIdleState(__instance), + new ToggleHoldClickOrHoldState(__instance), + new ToggleHoldHoldState(__instance, disableCommand) + }; + + StateMachineArray.SetValue(__instance, states.ToArray()); + } + } + + public class AddOneKeyStatesPatch : ModulePatch + { + private static FieldInfo StateMachineArray; + + protected override MethodBase GetTargetMethod() + { + StateMachineArray = AccessTools.Field(typeof(KeyCombination), "keyCombinationState_1"); + return AccessTools.GetDeclaredConstructors(typeof(KeyCombination)).Single(); + } + + [PatchPostfix] + public static void Postfix(ToggleKeyCombination __instance, EGameKey gameKey, ECommand command, KeyCombination.KeyCombinationState[] ___keyCombinationState_1) + { + bool useToggleHold = gameKey switch + { + EGameKey.Tactical => Settings.ToggleOrHoldTactical.Value, + EGameKey.ToggleGoggles => Settings.ToggleOrHoldGoggles.Value, + EGameKey.ToggleHeadLight => Settings.ToggleOrHoldHeadlight.Value, + _ => false + }; + + if (!useToggleHold) + { + return; + } + + List states = new(___keyCombinationState_1) + { + new ToggleHoldIdleState(__instance), + new ToggleHoldClickOrHoldState(__instance), + new ToggleHoldHoldState(__instance, command) + }; StateMachineArray.SetValue(__instance, states.ToArray()); } @@ -61,12 +108,26 @@ public static class AimToggleHoldPatches [PatchPostfix] public static void Postfix(KeyCombination __instance) { - if (!Settings.ToggleOrHoldAim.Value || __instance.GameKey != EGameKey.Aim) + bool useToggleHold = __instance.GameKey switch { - return; - } + EGameKey.Aim => Settings.ToggleOrHoldAim.Value, + EGameKey.Tactical => Settings.ToggleOrHoldTactical.Value, + EGameKey.ToggleGoggles => Settings.ToggleOrHoldGoggles.Value, + EGameKey.ToggleHeadLight => Settings.ToggleOrHoldHeadlight.Value, + EGameKey.Sprint => Settings.ToggleOrHoldSprint.Value, + _ => false + }; - __instance.method_0((KeyCombination.EKeyState)ToggleHoldState.Idle); + if (useToggleHold) + { + __instance.method_0((KeyCombination.EKeyState)ToggleHoldState.Idle); + } } } + + private static void OnSettingChanged(object sender, EventArgs args) + { + // Will "save" control settings, running GClass1911.UpdateInput, which will set (or unset) toggle/hold behavior + Singleton.Instance.Control.Controller.method_3(); + } } diff --git a/Settings.cs b/Settings.cs index f940946..b879569 100644 --- a/Settings.cs +++ b/Settings.cs @@ -67,6 +67,10 @@ internal class Settings // Input public static ConfigEntry ToggleOrHoldAim { get; set; } + public static ConfigEntry ToggleOrHoldSprint { get; set; } + public static ConfigEntry ToggleOrHoldTactical { get; set; } + public static ConfigEntry ToggleOrHoldHeadlight { get; set; } + public static ConfigEntry ToggleOrHoldGoggles { get; set; } public static ConfigEntry UseHomeEnd { get; set; } public static ConfigEntry RebindPageUpDown { get; set; } public static ConfigEntry MouseScrollMulti { get; set; } @@ -225,7 +229,43 @@ internal class Settings "Use Toggle/Hold Aiming", false, new ConfigDescription( - "Tap the aim key to toggle aiming, or hold the aim key for continuous aiming", + "Tap the aim key to toggle aiming, or hold the key for continuous aiming", + null, + new ConfigurationManagerAttributes { }))); + + configEntries.Add(ToggleOrHoldSprint = config.Bind( + InputSection, + "Use Toggle/Hold Sprint", + false, + new ConfigDescription( + "Tap the sprint key to toggle sprinting, or hold the key for continuous sprinting", + null, + new ConfigurationManagerAttributes { }))); + + configEntries.Add(ToggleOrHoldTactical = config.Bind( + InputSection, + "Use Toggle/Hold Tactical Device", + false, + new ConfigDescription( + "Tap the tactical device key to toggle your tactical device, or hold the key for continuous", + null, + new ConfigurationManagerAttributes { }))); + + configEntries.Add(ToggleOrHoldHeadlight = config.Bind( + InputSection, + "Use Toggle/Hold Headlight", + false, + new ConfigDescription( + "Tap the headlight key to toggle your headlight, or hold the key for continuous", + null, + new ConfigurationManagerAttributes { }))); + + configEntries.Add(ToggleOrHoldGoggles = config.Bind( + InputSection, + "Use Toggle/Hold Goggles", + false, + new ConfigDescription( + "Tap the goggles key to toggle night vision/goggles/faceshield, or hold the key for continuous", null, new ConfigurationManagerAttributes { }))); From c6ad6bc37586f7babefc6c41dbece870e6eaaf53 Mon Sep 17 00:00:00 2001 From: Tyfon <29051038+tyfon7@users.noreply.github.com> Date: Thu, 25 Jul 2024 14:02:02 -0700 Subject: [PATCH 09/73] Rev version, 2.3.3 --- UIFixes.csproj | 2 +- server/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/UIFixes.csproj b/UIFixes.csproj index 69bc19d..6fb0279 100644 --- a/UIFixes.csproj +++ b/UIFixes.csproj @@ -4,7 +4,7 @@ net471 Tyfon.UIFixes SPT UI Fixes - 2.3.2 + 2.3.3 true latest Debug;Release diff --git a/server/package.json b/server/package.json index 68db11c..e75bbfa 100644 --- a/server/package.json +++ b/server/package.json @@ -1,6 +1,6 @@ { "name": "uifixes", - "version": "2.3.2", + "version": "2.3.3", "main": "src/mod.js", "license": "MIT", "author": "Tyfon", From 1c4decd79c1278cabd0461888c32ac152331e7a2 Mon Sep 17 00:00:00 2001 From: Tyfon <29051038+tyfon7@users.noreply.github.com> Date: Thu, 25 Jul 2024 14:07:17 -0700 Subject: [PATCH 10/73] update readme --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index db4c2ad..ddc3557 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,8 @@ New UI features enabled by this mod - Flea market history - press the new back button to go back to the previous search - ✨ Linked flea search from empty slots - find mods that fit that specific slot - Keybinds for most context menu actions -- ✨ Toggle/Hold Aiming - tap to toggle ADS, or hold to ADS and stop when you release +- ✨ Toggle/Hold input - tap a key for "Press" mechanics, hold the key for "Continuous" mechanics + - Can be set for aiming, sprinting, tactical devices, headlights, and goggles/faceshields. ## Improved features From 887688d7aa8b92543f75e9738aa67f3be0a8e90c Mon Sep 17 00:00:00 2001 From: Tyfon <29051038+tyfon7@users.noreply.github.com> Date: Fri, 26 Jul 2024 00:44:51 -0700 Subject: [PATCH 11/73] Expand dropdowns by a smaller amount --- Patches/FixFleaPatches.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Patches/FixFleaPatches.cs b/Patches/FixFleaPatches.cs index 1590249..5190e59 100644 --- a/Patches/FixFleaPatches.cs +++ b/Patches/FixFleaPatches.cs @@ -141,15 +141,15 @@ public static class FixFleaPatches { protected override MethodBase GetTargetMethod() { - return AccessTools.DeclaredMethod(typeof(DropDownBox), nameof(DropDownBox.Init)); + return AccessTools.DeclaredMethod(typeof(DropDownBox), nameof(DropDownBox.Show)); } [PatchPostfix] - public static void Postfix(ref float ____maxVisibleHeight) + public static void Postfix(DropDownBox __instance, ref float ____maxVisibleHeight) { if (____maxVisibleHeight == 120f) { - ____maxVisibleHeight = 240f; + ____maxVisibleHeight = 150f; } } } From 476b14f9a079f958f7dcd5052acf275f6d6df03a Mon Sep 17 00:00:00 2001 From: Tyfon <29051038+tyfon7@users.noreply.github.com> Date: Fri, 26 Jul 2024 00:54:24 -0700 Subject: [PATCH 12/73] Disable scroll when in a textbox --- Patches/ScrollPatches.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Patches/ScrollPatches.cs b/Patches/ScrollPatches.cs index 9ea6d43..3093562 100644 --- a/Patches/ScrollPatches.cs +++ b/Patches/ScrollPatches.cs @@ -8,6 +8,7 @@ using SPT.Reflection.Patching; using System; using System.Collections.Generic; using System.Reflection; +using TMPro; using UnityEngine; using UnityEngine.Events; using UnityEngine.EventSystems; @@ -34,6 +35,12 @@ public static class ScrollPatches private static bool HandleInput(ScrollRect scrollRect) { + if (EventSystem.current?.currentSelectedGameObject != null && + EventSystem.current.currentSelectedGameObject.GetComponent() != null) + { + return false; + } + if (scrollRect != null) { if (Settings.UseHomeEnd.Value) From a29361958e07174e1ea9d4025e42589e0d4e6356 Mon Sep 17 00:00:00 2001 From: Tyfon <29051038+tyfon7@users.noreply.github.com> Date: Fri, 26 Jul 2024 01:26:45 -0700 Subject: [PATCH 13/73] Use correct inventoryController in ammobox unload --- Patches/UnloadAmmoPatches.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Patches/UnloadAmmoPatches.cs b/Patches/UnloadAmmoPatches.cs index f88a2e4..af2299b 100644 --- a/Patches/UnloadAmmoPatches.cs +++ b/Patches/UnloadAmmoPatches.cs @@ -201,7 +201,7 @@ public static class UnloadAmmoPatches if (operation.Succeeded) { - tempController.DestroyItem(ammoBox); + inventoryController.DestroyItem(ammoBox); } else { From 4abb5bda3b2019f698e82e6e5302b4895df59a3d Mon Sep 17 00:00:00 2001 From: Tyfon <29051038+tyfon7@users.noreply.github.com> Date: Fri, 26 Jul 2024 01:27:23 -0700 Subject: [PATCH 14/73] rev version 2.3.4 --- UIFixes.csproj | 2 +- server/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/UIFixes.csproj b/UIFixes.csproj index 6fb0279..541d334 100644 --- a/UIFixes.csproj +++ b/UIFixes.csproj @@ -4,7 +4,7 @@ net471 Tyfon.UIFixes SPT UI Fixes - 2.3.3 + 2.3.4 true latest Debug;Release diff --git a/server/package.json b/server/package.json index e75bbfa..b4db240 100644 --- a/server/package.json +++ b/server/package.json @@ -1,6 +1,6 @@ { "name": "uifixes", - "version": "2.3.3", + "version": "2.3.4", "main": "src/mod.js", "license": "MIT", "author": "Tyfon", From 9f9b26cda821731b823b27c8120e3a84e10bd547 Mon Sep 17 00:00:00 2001 From: Tyfon <29051038+tyfon7@users.noreply.github.com> Date: Sun, 28 Jul 2024 13:17:33 -0700 Subject: [PATCH 15/73] Remove server barter fix (fixed in spt) --- Patches/UnloadAmmoPatches.cs | 4 ++-- server/src/mod.ts | 32 -------------------------------- 2 files changed, 2 insertions(+), 34 deletions(-) diff --git a/Patches/UnloadAmmoPatches.cs b/Patches/UnloadAmmoPatches.cs index af2299b..7de05b6 100644 --- a/Patches/UnloadAmmoPatches.cs +++ b/Patches/UnloadAmmoPatches.cs @@ -223,8 +223,8 @@ public static class UnloadAmmoPatches else { var profile = PatchConstants.BackEndSession.Profile; - StashClass fakeStash = (StashClass)Singleton.Instance.CreateItem("FakeStash", "566abbc34bdc2d92178b4576", null); - return new(fakeStash, profile.ProfileId, profile.Nickname); + StashClass fakeStash = Singleton.Instance.CreateFakeStash(); + return new TraderControllerClass(fakeStash, profile.ProfileId, profile.Nickname); } } } diff --git a/server/src/mod.ts b/server/src/mod.ts index ce41278..a4301ea 100644 --- a/server/src/mod.ts +++ b/server/src/mod.ts @@ -13,8 +13,6 @@ import type { ICloner } from "@spt/utils/cloners/ICloner"; import { RagfairLinkedSlotItemService } from "./RagfairLinkedSlotItemService"; import config from "../config/config.json"; -import { RagfairOfferGenerator } from "@spt/generators/RagfairOfferGenerator"; -import { IRagfairOffer } from "@spt/models/eft/ragfair/IRagfairOffer"; class UIFixes implements IPreSptLoadMod { private databaseService: DatabaseService; @@ -52,36 +50,6 @@ class UIFixes implements IPreSptLoadMod { { frequency: "Always" } ); - // Trader offers with dogtag barter - fixed in next SPT release *after* 3.9.3 - container.afterResolution( - "RagfairOfferGenerator", - (_, ragfairOfferGenerator: RagfairOfferGenerator) => { - const original = ragfairOfferGenerator["createOffer"]; // By name because protected - - ragfairOfferGenerator["createOffer"] = (userID, time, items, barterScheme, loyalLevel, isPackOffer) => { - const offer: IRagfairOffer = original.call( - ragfairOfferGenerator, - userID, - time, - items, - barterScheme, - loyalLevel, - isPackOffer - ); - - for (let i = 0; i < offer.requirements.length; i++) { - if (barterScheme[i]["level"] !== undefined) { - offer.requirements[i]["level"] = barterScheme[i]["level"]; - offer.requirements[i]["side"] = barterScheme[i]["side"]; - } - } - - return offer; - }; - }, - { frequency: "Always" } - ); - // Better tool return - starting production if (config.putToolsBack) { container.afterResolution( From 14dd7c7ad2c0240c69c80b79e966658d7a5189ae Mon Sep 17 00:00:00 2001 From: Tyfon <29051038+tyfon7@users.noreply.github.com> Date: Mon, 29 Jul 2024 13:23:28 -0700 Subject: [PATCH 16/73] Fix IgnoreItemParent left true --- Patches/MultiSelectPatches.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Patches/MultiSelectPatches.cs b/Patches/MultiSelectPatches.cs index 174b699..92ccbe7 100644 --- a/Patches/MultiSelectPatches.cs +++ b/Patches/MultiSelectPatches.cs @@ -273,7 +273,7 @@ public static class MultiSelectPatches } DisableMerge = false; - IgnoreItemParent = true; + IgnoreItemParent = false; if (succeeded) { From 54812d4a03a191a3487d4d3d32fbddf114e1b23d Mon Sep 17 00:00:00 2001 From: Tyfon <29051038+tyfon7@users.noreply.github.com> Date: Mon, 29 Jul 2024 13:24:20 -0700 Subject: [PATCH 17/73] Better error handling in server --- server/src/mod.ts | 114 +++++++++++++++++++++++++++------------------- 1 file changed, 66 insertions(+), 48 deletions(-) diff --git a/server/src/mod.ts b/server/src/mod.ts index a4301ea..d0bb8f8 100644 --- a/server/src/mod.ts +++ b/server/src/mod.ts @@ -40,10 +40,14 @@ class UIFixes implements IPreSptLoadMod { original.call(inRaidHelper, pmcData, sessionId); // Restore the quickbinds for items that still exist - for (const index in fastPanel) { - if (pmcData.Inventory.items.find(i => i._id == fastPanel[index])) { - pmcData.Inventory.fastPanel[index] = fastPanel[index]; + try { + for (const index in fastPanel) { + if (pmcData.Inventory.items.find(i => i._id == fastPanel[index])) { + pmcData.Inventory.fastPanel[index] = fastPanel[index]; + } } + } catch (error) { + this.logger.error(`UIFixes failed to restore quickbinds\n ${error}`); } }; }, @@ -61,14 +65,20 @@ class UIFixes implements IPreSptLoadMod { const result = original.call(hideoutHelper, pmcData, body, sessionID); // The items haven't been deleted yet, augment the list with their parentId - const bodyAsSingle = body as IHideoutSingleProductionStartRequestData; - if (bodyAsSingle && bodyAsSingle.tools?.length > 0) { - const requestTools = bodyAsSingle.tools; - const tools = pmcData.Hideout.Production[body.recipeId].sptRequiredTools; - for (let i = 0; i < tools.length; i++) { - const originalTool = pmcData.Inventory.items.find(x => x._id === requestTools[i].id); - tools[i]["uifixes.returnTo"] = [originalTool.parentId, originalTool.slotId]; + try { + const bodyAsSingle = body as IHideoutSingleProductionStartRequestData; + if (bodyAsSingle && bodyAsSingle.tools?.length > 0) { + const requestTools = bodyAsSingle.tools; + const tools = pmcData.Hideout.Production[body.recipeId].sptRequiredTools; + for (let i = 0; i < tools.length; i++) { + const originalTool = pmcData.Inventory.items.find( + x => x._id === requestTools[i].id + ); + tools[i]["uifixes.returnTo"] = [originalTool.parentId, originalTool.slotId]; + } } + } catch (error) { + this.logger.error(`UIFixes failed to save tool origin\n ${error}`); } return result; @@ -89,51 +99,59 @@ class UIFixes implements IPreSptLoadMod { // If a tool marked with uifixes is there, try to return it to its original container const tool = itemWithModsToAddClone[0]; if (tool["uifixes.returnTo"]) { - const [containerId, slotId] = tool["uifixes.returnTo"]; + try { + const [containerId, slotId] = tool["uifixes.returnTo"]; - const container = pmcData.Inventory.items.find(x => x._id === containerId); - if (container) { - const containerTemplate = itemHelper.getItem(container._tpl)[1]; - const containerFS2D = inventoryHelper.getContainerMap( - containerTemplate._props.Grids[0]._props.cellsH, - containerTemplate._props.Grids[0]._props.cellsV, - pmcData.Inventory.items, - containerId - ); + const container = pmcData.Inventory.items.find(x => x._id === containerId); + if (container) { + const [foundTemplate, containerTemplate] = itemHelper.getItem(container._tpl); + if (foundTemplate && containerTemplate) { + const containerFS2D = inventoryHelper.getContainerMap( + containerTemplate._props.Grids[0]._props.cellsH, + containerTemplate._props.Grids[0]._props.cellsV, + pmcData.Inventory.items, + containerId + ); - // will change the array so clone it - if ( - inventoryHelper.canPlaceItemInContainer( - cloner.clone(containerFS2D), - itemWithModsToAddClone - ) - ) { - // At this point everything should succeed - inventoryHelper.placeItemInContainer( - containerFS2D, - itemWithModsToAddClone, - containerId, - slotId - ); + // will change the array so clone it + if ( + inventoryHelper.canPlaceItemInContainer( + cloner.clone(containerFS2D), + itemWithModsToAddClone + ) + ) { + // At this point everything should succeed + inventoryHelper.placeItemInContainer( + containerFS2D, + itemWithModsToAddClone, + containerId, + slotId + ); - // protected function, bypass typescript - inventoryHelper["setFindInRaidStatusForItem"]( - itemWithModsToAddClone, - request.foundInRaid - ); + // protected function, bypass typescript + inventoryHelper["setFindInRaidStatusForItem"]( + itemWithModsToAddClone, + request.foundInRaid + ); - // Add item + mods to output and profile inventory - output.profileChanges[sessionId].items.new.push(...itemWithModsToAddClone); - pmcData.Inventory.items.push(...itemWithModsToAddClone); + // Add item + mods to output and profile inventory + output.profileChanges[sessionId].items.new.push(...itemWithModsToAddClone); + pmcData.Inventory.items.push(...itemWithModsToAddClone); - this.logger.debug( - `Added ${itemWithModsToAddClone[0].upd?.StackObjectsCount ?? 1} item: ${ - itemWithModsToAddClone[0]._tpl - } with: ${itemWithModsToAddClone.length - 1} mods to ${containerId}` - ); + this.logger.debug( + `Added ${itemWithModsToAddClone[0].upd?.StackObjectsCount ?? 1} item: ${ + itemWithModsToAddClone[0]._tpl + } with: ${itemWithModsToAddClone.length - 1} mods to ${containerId}` + ); - return; + return; + } + } } + } catch (error) { + this.logger.error( + `UIFixes failed to put a tool back, it will be returned to your stash as normal.\n ${error}` + ); } } From 9324922f82f9e4bd857fcf8325b2d70e5c60005f Mon Sep 17 00:00:00 2001 From: Tyfon <29051038+tyfon7@users.noreply.github.com> Date: Mon, 12 Aug 2024 17:05:24 -0700 Subject: [PATCH 18/73] More better server logging, rev version --- UIFixes.csproj | 2 +- server/package.json | 2 +- server/src/mod.ts | 14 ++++++++------ 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/UIFixes.csproj b/UIFixes.csproj index 541d334..8f3c137 100644 --- a/UIFixes.csproj +++ b/UIFixes.csproj @@ -4,7 +4,7 @@ net471 Tyfon.UIFixes SPT UI Fixes - 2.3.4 + 2.4.0 true latest Debug;Release diff --git a/server/package.json b/server/package.json index b4db240..19639d8 100644 --- a/server/package.json +++ b/server/package.json @@ -1,6 +1,6 @@ { "name": "uifixes", - "version": "2.3.4", + "version": "2.4.0", "main": "src/mod.js", "license": "MIT", "author": "Tyfon", diff --git a/server/src/mod.ts b/server/src/mod.ts index d0bb8f8..c4dfa83 100644 --- a/server/src/mod.ts +++ b/server/src/mod.ts @@ -47,7 +47,7 @@ class UIFixes implements IPreSptLoadMod { } } } catch (error) { - this.logger.error(`UIFixes failed to restore quickbinds\n ${error}`); + this.logger.error(`UIFixes: Failed to restore quickbinds\n ${error}`); } }; }, @@ -78,7 +78,7 @@ class UIFixes implements IPreSptLoadMod { } } } catch (error) { - this.logger.error(`UIFixes failed to save tool origin\n ${error}`); + this.logger.error(`UIFixes: Failed to save tool origin\n ${error}`); } return result; @@ -149,10 +149,12 @@ class UIFixes implements IPreSptLoadMod { } } } catch (error) { - this.logger.error( - `UIFixes failed to put a tool back, it will be returned to your stash as normal.\n ${error}` - ); + this.logger.error(`UIFixes: Encounted an error trying to put tool back.\n ${error}`); } + + this.logger.info( + "UIFixes: Unable to put tool back in its original container, returning it to stash." + ); } return original.call(inventoryHelper, sessionId, request, pmcData, output); @@ -200,7 +202,7 @@ class UIFixes implements IPreSptLoadMod { if (!quests[questId]) { this.logger.error( - `Trader ${traderId} questassort references unknown quest ${JSON.stringify(questId)}!` + `UIFixes: Trader ${traderId} questassort references unknown quest ${JSON.stringify(questId)}!` ); continue; } From e76d2515cfe939b62d0b9a305f8fe4cbe152ff18 Mon Sep 17 00:00:00 2001 From: Tyfon <29051038+tyfon7@users.noreply.github.com> Date: Mon, 12 Aug 2024 17:36:43 -0700 Subject: [PATCH 19/73] Block hideout camera movement when area is selected --- Patches/HideoutCameraPatches.cs | 20 ++++++++++++++++++++ Plugin.cs | 1 + 2 files changed, 21 insertions(+) create mode 100644 Patches/HideoutCameraPatches.cs diff --git a/Patches/HideoutCameraPatches.cs b/Patches/HideoutCameraPatches.cs new file mode 100644 index 0000000..076e932 --- /dev/null +++ b/Patches/HideoutCameraPatches.cs @@ -0,0 +1,20 @@ +using EFT.Hideout; +using HarmonyLib; +using SPT.Reflection.Patching; +using System.Reflection; + +namespace UIFixes; + +public class HideoutCameraPatch : ModulePatch +{ + protected override MethodBase GetTargetMethod() + { + return AccessTools.Method(typeof(HideoutCameraController), nameof(HideoutCameraController.LateUpdate)); + } + + [PatchPrefix] + public static bool Prefix(HideoutCameraController __instance) + { + return !__instance.AreaSelected; + } +} diff --git a/Plugin.cs b/Plugin.cs index e23bd66..390f2db 100644 --- a/Plugin.cs +++ b/Plugin.cs @@ -71,6 +71,7 @@ public class Plugin : BaseUnityPlugin BarterOfferPatches.Enable(); new UnlockCursorPatch().Enable(); LimitDragPatches.Enable(); + new HideoutCameraPatch().Enable(); } public static bool InRaid() From 1d3a2ac85af0024d72ca6ca1c997ac5491ca8451 Mon Sep 17 00:00:00 2001 From: Tyfon <29051038+tyfon7@users.noreply.github.com> Date: Fri, 16 Aug 2024 11:00:10 -0700 Subject: [PATCH 20/73] grow items left or up --- ExtraProperties.cs | 15 +- GlobalUsings.cs | 3 + Patches/WeaponModdingPatches.cs | 315 ++++++++++++++++++++++++++++++++ Plugin.cs | 1 + R.cs | 32 ++++ 5 files changed, 365 insertions(+), 1 deletion(-) create mode 100644 Patches/WeaponModdingPatches.cs 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); } From 75c60fb09a20ed48ff11982a0d5fdd53f30c650e Mon Sep 17 00:00:00 2001 From: Tyfon <29051038+tyfon7@users.noreply.github.com> Date: Fri, 16 Aug 2024 11:54:50 -0700 Subject: [PATCH 21/73] Set tags with enter; tags over names --- Patches/TagPatches.cs | 73 +++++++++++ Patches/WeaponModdingPatches.cs | 206 ++++++++++++++++---------------- Plugin.cs | 1 + Settings.cs | 10 ++ 4 files changed, 187 insertions(+), 103 deletions(-) create mode 100644 Patches/TagPatches.cs diff --git a/Patches/TagPatches.cs b/Patches/TagPatches.cs new file mode 100644 index 0000000..cfa8d47 --- /dev/null +++ b/Patches/TagPatches.cs @@ -0,0 +1,73 @@ +using EFT.UI; +using EFT.UI.DragAndDrop; +using HarmonyLib; +using SPT.Reflection.Patching; +using System.Reflection; +using System.Threading.Tasks; +using TMPro; +using UnityEngine; +using UnityEngine.UI; + +namespace UIFixes; + +public static class TagPatches +{ + public static void Enable() + { + new OnEnterPatch().Enable(); + new TagsOverCaptionsPatch().Enable(); + } + + public class OnEnterPatch : ModulePatch + { + protected override MethodBase GetTargetMethod() + { + return AccessTools.DeclaredMethod(typeof(EditTagWindow), nameof(EditTagWindow.Show)); + } + + [PatchPostfix] + public static void Postfix(EditTagWindow __instance, ValidationInputField ____tagInput) + { + ____tagInput.onSubmit.AddListener(value => __instance.method_4()); + } + } + + public class TagsOverCaptionsPatch : ModulePatch + { + protected override MethodBase GetTargetMethod() + { + return AccessTools.Method(typeof(GridItemView), nameof(GridItemView.method_21)); + } + + [PatchPostfix] + public static async void Postfix(GridItemView __instance, TextMeshProUGUI ___TagName, TextMeshProUGUI ___Caption, Image ____tagColor, Task __result) + { + await __result; + + // Rerun logic with preferred priority. Running again rather than prefix overwrite because this also fixes the existing race condition + ___TagName.gameObject.SetActive(false); + ___Caption.gameObject.SetActive(true); + await Task.Yield(); + RectTransform tagTransform = ____tagColor.rectTransform; + float tagSpace = __instance.RectTransform.sizeDelta.x - ___Caption.renderedWidth - 2f; + if (tagSpace < 40f) + { + tagTransform.sizeDelta = new Vector2(__instance.RectTransform.sizeDelta.x, tagTransform.sizeDelta.y); + if (Settings.TagsOverCaptions.Value) + { + ___TagName.gameObject.SetActive(true); + float tagSize = Mathf.Clamp(___TagName.preferredWidth + 12f, 40f, __instance.RectTransform.sizeDelta.x - 2f); + tagTransform.sizeDelta = new Vector2(tagSize, ____tagColor.rectTransform.sizeDelta.y); + + ___Caption.gameObject.SetActive(false); + } + } + else + { + ___TagName.gameObject.SetActive(true); + float tagSize = Mathf.Clamp(___TagName.preferredWidth + 12f, 40f, tagSpace); + tagTransform.sizeDelta = new Vector2(tagSize, ____tagColor.rectTransform.sizeDelta.y); + } + } + } +} \ No newline at end of file diff --git a/Patches/WeaponModdingPatches.cs b/Patches/WeaponModdingPatches.cs index bf50d4f..4ffdd36 100644 --- a/Patches/WeaponModdingPatches.cs +++ b/Patches/WeaponModdingPatches.cs @@ -192,124 +192,124 @@ public static class WeaponModdingPatches } } - public class ModdingMoveToSortingTablePatch : ModulePatch - { - private static bool InPatch = false; + // public class ModdingMoveToSortingTablePatch : ModulePatch + // { + // private static bool InPatch = false; - protected override MethodBase GetTargetMethod() - { - return AccessTools.DeclaredMethod(typeof(GClass2848), nameof(GClass2848.Select)); - } + // 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; - } + // [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; - } + // // 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; - } + // // 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; - } + // 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; - } + // 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; - } + // 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); + // // 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; - } - } + // InPatch = true; + // __result = __instance.Select(item, itemAddress, simulate, out error); + // InPatch = false; + // } + // } - public class PresetMoveToSortingTablePatch : ModulePatch - { - private static bool InPatch = false; + // public class PresetMoveToSortingTablePatch : ModulePatch + // { + // private static bool InPatch = false; - protected override MethodBase GetTargetMethod() - { - return AccessTools.Method(typeof(GClass2849), nameof(GClass2849.method_2)); - } + // 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; - } + // [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; - } + // // move it to sorting table + // SortingTableClass sortingTable = inventoryController.Inventory.SortingTable; + // if (sortingTable == null) + // { + // return; + // } - ItemAddressClass sortingTableAddress = sortingTable.Grid.FindLocationForItem(item); - if (sortingTableAddress == 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; - } + // 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; + // InPatch = true; + // __result = __instance.method_2(item, modsWithSlots, itemController, simulate); + // InPatch = false; - if (__result.Succeeded) - { - __result.Value.Prepend(sortingTableMove); - } - else - { - sortingTableMove.Value.RollBack(); - } - } - } + // 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 51beea6..9384555 100644 --- a/Plugin.cs +++ b/Plugin.cs @@ -73,6 +73,7 @@ public class Plugin : BaseUnityPlugin LimitDragPatches.Enable(); new HideoutCameraPatch().Enable(); WeaponModdingPatches.Enable(); + TagPatches.Enable(); } public static bool InRaid() diff --git a/Settings.cs b/Settings.cs index b879569..ac40102 100644 --- a/Settings.cs +++ b/Settings.cs @@ -115,6 +115,7 @@ internal class Settings public static ConfigEntry ShowGPCurrency { get; set; } public static ConfigEntry ShowOutOfStockCheckbox { get; set; } public static ConfigEntry SortingTableButton { get; set; } + public static ConfigEntry TagsOverCaptions { get; set; } public static ConfigEntry LoadMagPresetOnBullets { get; set; } // Advanced // Inspect Panels @@ -648,6 +649,15 @@ internal class Settings null, new ConfigurationManagerAttributes { }))); + configEntries.Add(TagsOverCaptions = config.Bind( + InventorySection, + "Prioritize Tags Over Names", + true, + new ConfigDescription( + "When there isn't enough space to show both the tag and the name of an item, show the tag", + null, + new ConfigurationManagerAttributes { }))); + configEntries.Add(LoadMagPresetOnBullets = config.Bind( InventorySection, "Mag Presets Context Menu on Bullets", From 852b9b007850119c508973f6b2cadf96a3d29b75 Mon Sep 17 00:00:00 2001 From: Tyfon <29051038+tyfon7@users.noreply.github.com> Date: Fri, 16 Aug 2024 15:51:05 -0700 Subject: [PATCH 22/73] Bindable tactical devices --- Patches/QuickAccessPanelPatches.cs | 30 +++++++ Patches/TacticalBindsPatches.cs | 138 +++++++++++++++++++++++++++++ Plugin.cs | 1 + Settings.cs | 17 ++++ 4 files changed, 186 insertions(+) create mode 100644 Patches/TacticalBindsPatches.cs diff --git a/Patches/QuickAccessPanelPatches.cs b/Patches/QuickAccessPanelPatches.cs index 580568c..f2c15d9 100644 --- a/Patches/QuickAccessPanelPatches.cs +++ b/Patches/QuickAccessPanelPatches.cs @@ -3,10 +3,13 @@ using Comfort.Common; using EFT.InputSystem; using EFT.InventoryLogic; using EFT.UI; +using EFT.UI.DragAndDrop; using EFT.UI.Settings; using HarmonyLib; using SPT.Reflection.Patching; using System.Reflection; +using UnityEngine; +using UnityEngine.UI; namespace UIFixes; @@ -17,6 +20,7 @@ public static class QuickAccessPanelPatches new FixWeaponBindsDisplayPatch().Enable(); new FixVisibilityPatch().Enable(); new TranslateCommandHackPatch().Enable(); + new RotationPatch().Enable(); } public class FixWeaponBindsDisplayPatch : ModulePatch @@ -101,4 +105,30 @@ public static class QuickAccessPanelPatches FixVisibilityPatch.Ignorable = false; } } + + public class RotationPatch : ModulePatch + { + protected override MethodBase GetTargetMethod() + { + return AccessTools.Method(typeof(QuickSlotItemView), nameof(QuickSlotItemView.UpdateScale)); + } + + [PatchPostfix] + public static void Postfix(QuickSlotItemView __instance, Image ___MainImage) + { + if (__instance.IconScale == null) + { + return; + } + + // Already square items don't need to be rotated + XYCellSizeStruct size = __instance.Item.CalculateCellSize(); + if (size.X == size.Y) + { + Transform transform = ___MainImage.transform; + transform.localRotation = Quaternion.identity; + transform.localScale = Vector3.one; + } + } + } } diff --git a/Patches/TacticalBindsPatches.cs b/Patches/TacticalBindsPatches.cs new file mode 100644 index 0000000..7365281 --- /dev/null +++ b/Patches/TacticalBindsPatches.cs @@ -0,0 +1,138 @@ +using Comfort.Common; +using EFT; +using EFT.InventoryLogic; +using HarmonyLib; +using SPT.Reflection.Patching; +using System.Reflection; +using UnityEngine; + +namespace UIFixes; + +public static class TacticalBindsPatches +{ + public static void Enable() + { + new BindableTacticalPatch().Enable(); + new ReachableTacticalPatch().Enable(); + new UseTacticalPatch().Enable(); + } + + public class BindableTacticalPatch : ModulePatch + { + protected override MethodBase GetTargetMethod() + { + return AccessTools.Method(typeof(InventoryControllerClass), nameof(InventoryControllerClass.IsAtBindablePlace)); + } + + [PatchPostfix] + public static void Postfix(InventoryControllerClass __instance, Item item, ref bool __result) + { + if (__result) + { + return; + } + + __result = IsEquippedTacticalDevice(__instance, item); + } + } + + public class ReachableTacticalPatch : ModulePatch + { + protected override MethodBase GetTargetMethod() + { + return AccessTools.Method(typeof(InventoryControllerClass), nameof(InventoryControllerClass.IsAtReachablePlace)); + } + + [PatchPostfix] + public static void Postfix(InventoryControllerClass __instance, Item item, ref bool __result) + { + if (__result) + { + return; + } + + __result = IsEquippedTacticalDevice(__instance, item); + } + } + + public class UseTacticalPatch : ModulePatch + { + protected override MethodBase GetTargetMethod() + { + return AccessTools.Method(typeof(Player), nameof(Player.SetQuickSlotItem)); + } + + [PatchPrefix] + public static bool Prefix(Player __instance, EBoundItem quickSlot, Callback callback) + { + Item boundItem = __instance.InventoryControllerClass.Inventory.FastAccess.GetBoundItem(quickSlot); + if (boundItem == null) + { + return true; + } + + LightComponent lightComponent = boundItem.GetItemComponent(); + if (lightComponent == null) + { + return true; + } + + // Don't return true past this point; if the default handler tries to use a tactical device, very bad things happen + + if (__instance.HandsController is not Player.FirearmController firearmController || + firearmController.Item != boundItem.GetRootItem()) + { + callback(null); + return false; + } + + FirearmLightStateStruct lightState = new() + { + Id = lightComponent.Item.Id, + IsActive = lightComponent.IsActive, + LightMode = lightComponent.SelectedMode + }; + + if (IsTacticalModeModifierPressed()) + { + lightState.LightMode++; + } + else + { + lightState.IsActive = !lightState.IsActive; + } + + firearmController.SetLightsState([lightState], false); + + callback(null); + return false; + } + } + + private static bool IsEquippedTacticalDevice(InventoryControllerClass inventoryController, Item item) + { + LightComponent lightComponent = item.GetItemComponent(); + if (lightComponent == null) + { + return false; + } + + if (item.GetRootItem() is Weapon weapon) + { + return inventoryController.Inventory.Equipment.Contains(weapon); + } + + return false; + } + + private static bool IsTacticalModeModifierPressed() + { + return Settings.TacticalModeModifier.Value switch + { + TacticalBindModifier.Shift => Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift), + TacticalBindModifier.Control => Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl), + TacticalBindModifier.Alt => Input.GetKey(KeyCode.LeftAlt) || Input.GetKey(KeyCode.RightAlt), + _ => false, + }; + } +} \ No newline at end of file diff --git a/Plugin.cs b/Plugin.cs index 9384555..7c4a0bd 100644 --- a/Plugin.cs +++ b/Plugin.cs @@ -74,6 +74,7 @@ public class Plugin : BaseUnityPlugin new HideoutCameraPatch().Enable(); WeaponModdingPatches.Enable(); TagPatches.Enable(); + TacticalBindsPatches.Enable(); } public static bool InRaid() diff --git a/Settings.cs b/Settings.cs index ac40102..2c26dea 100644 --- a/Settings.cs +++ b/Settings.cs @@ -45,6 +45,13 @@ internal enum AutoFleaPrice Maximum } +internal enum TacticalBindModifier +{ + Shift, + Control, + Alt +} + internal class Settings { // Categories @@ -71,6 +78,7 @@ internal class Settings public static ConfigEntry ToggleOrHoldTactical { get; set; } public static ConfigEntry ToggleOrHoldHeadlight { get; set; } public static ConfigEntry ToggleOrHoldGoggles { get; set; } + public static ConfigEntry TacticalModeModifier { get; set; } public static ConfigEntry UseHomeEnd { get; set; } public static ConfigEntry RebindPageUpDown { get; set; } public static ConfigEntry MouseScrollMulti { get; set; } @@ -270,6 +278,15 @@ internal class Settings null, new ConfigurationManagerAttributes { }))); + configEntries.Add(TacticalModeModifier = config.Bind( + InputSection, + "Change Quickbound Tactical Mode", + TacticalBindModifier.Shift, + new ConfigDescription( + "Holding this modifer when activating a quickbound tactical device will switch its active mode", + null, + new ConfigurationManagerAttributes { }))); + configEntries.Add(UseHomeEnd = config.Bind( InputSection, "Enable Home/End Keys", From fe8a02d9c3c91c20b240e641b6d223f04281e399 Mon Sep 17 00:00:00 2001 From: Tyfon <29051038+tyfon7@users.noreply.github.com> Date: Fri, 16 Aug 2024 18:18:04 -0700 Subject: [PATCH 23/73] Add offer context menu --- Patches/AddOfferContextMenuPatches.cs | 190 ++++++++++++++++++++++++++ Plugin.cs | 1 + R.cs | 2 + Settings.cs | 20 +++ 4 files changed, 213 insertions(+) create mode 100644 Patches/AddOfferContextMenuPatches.cs diff --git a/Patches/AddOfferContextMenuPatches.cs b/Patches/AddOfferContextMenuPatches.cs new file mode 100644 index 0000000..1dff872 --- /dev/null +++ b/Patches/AddOfferContextMenuPatches.cs @@ -0,0 +1,190 @@ +using Comfort.Common; +using EFT.InventoryLogic; +using EFT.UI; +using EFT.UI.Ragfair; +using HarmonyLib; +using SPT.Reflection.Patching; +using SPT.Reflection.Utils; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using UnityEngine; + +namespace UIFixes; + +public static class AddOfferContextMenuPatches +{ + private const EItemInfoButton AddOfferInfoButton = (EItemInfoButton)77; + + private static Item AddOfferItem = null; + + public static void Enable() + { + new AddOfferMenuPatch().Enable(); + new AddOfferIsActivePatch().Enable(); + new AddOfferIsInteractivePatch().Enable(); + new AddOfferNameIconPatch().Enable(); + + new AddOfferExecutePatch().Enable(); + new ShowAddOfferWindowPatch().Enable(); + new SelectItemPatch().Enable(); + } + + public class AddOfferMenuPatch : ModulePatch + { + protected override MethodBase GetTargetMethod() + { + return AccessTools.DeclaredProperty(R.InventoryInteractions.CompleteType, "AvailableInteractions").GetMethod; + } + + [PatchPostfix] + public static void Postfix(ref IEnumerable __result) + { + if (Settings.AddOfferContextMenu.Value) + { + var list = __result.ToList(); + list.Insert(list.IndexOf(EItemInfoButton.Tag), AddOfferInfoButton); + __result = list; + } + } + } + + public class AddOfferNameIconPatch : ModulePatch + { + protected override MethodBase GetTargetMethod() + { + return AccessTools.Method(typeof(InteractionButtonsContainer), nameof(InteractionButtonsContainer.Show)).MakeGenericMethod(typeof(EItemInfoButton)); + } + + [PatchPrefix] + public static void Prefix(ref IReadOnlyDictionary names, ref IReadOnlyDictionary icons) + { + names ??= new Dictionary() + { + { AddOfferInfoButton, "ragfair/OFFER ADD" } + }; + + icons ??= new Dictionary() + { + { AddOfferInfoButton, EFTHardSettings.Instance.StaticIcons.GetSmallCurrencySign(ECurrencyType.RUB) } + }; + } + } + + public class AddOfferIsActivePatch : ModulePatch + { + protected override MethodBase GetTargetMethod() + { + return AccessTools.Method(R.ContextMenuHelper.Type, "IsActive"); + } + + [PatchPrefix] + public static bool Prefix(EItemInfoButton button, ref bool __result) + { + if (button != AddOfferInfoButton) + { + return true; + } + + if (Plugin.InRaid()) + { + __result = false; + return false; + } + + __result = true; + return false; + } + } + + public class AddOfferIsInteractivePatch : ModulePatch + { + protected override MethodBase GetTargetMethod() + { + return AccessTools.Method(R.ContextMenuHelper.Type, "IsInteractive"); + } + + [PatchPostfix] + public static void Postfix(EItemInfoButton button, ref IResult __result, Item ___item_0) + { + if (button != AddOfferInfoButton) + { + return; + } + + ISession session = PatchConstants.BackEndSession; + RagFairClass ragfair = session.RagFair; + if (ragfair.MyOffersCount >= ragfair.GetMaxOffersCount(ragfair.MyRating)) + { + __result = new FailedResult("ragfair/Reached maximum amount of offers"); + } + + RagfairOfferSellHelperClass ragfairHelper = new(session.Profile, session.Profile.Inventory.Stash.Grid); + if (!ragfairHelper.method_4(___item_0, out string error)) + { + __result = new FailedResult(error); + } + } + } + + public class AddOfferExecutePatch : ModulePatch + { + protected override MethodBase GetTargetMethod() + { + return AccessTools.Method(R.InventoryInteractions.Type, "ExecuteInteractionInternal"); + } + + [PatchPrefix] + public static bool Prefix(ItemInfoInteractionsAbstractClass __instance, EItemInfoButton interaction, Item ___item_0) + { + if (interaction != AddOfferInfoButton) + { + return true; + } + + AddOfferItem = ___item_0; + + __instance.ExecuteInteractionInternal(EItemInfoButton.FilterSearch); + return false; + } + } + + public class ShowAddOfferWindowPatch : ModulePatch + { + protected override MethodBase GetTargetMethod() + { + return AccessTools.Method(typeof(RagfairScreen), nameof(RagfairScreen.Show)); + } + + [PatchPostfix] + public static void Postfix(RagfairScreen __instance) + { + if (AddOfferItem == null) + { + return; + } + + __instance.method_27(); // click the add offer button + } + } + + public class SelectItemPatch : ModulePatch + { + protected override MethodBase GetTargetMethod() + { + return AccessTools.Method(typeof(AddOfferWindow), nameof(AddOfferWindow.Show)); + } + + [PatchPostfix] + public static void Postfix(RagfairOfferSellHelperClass ___ragfairOfferSellHelperClass) + { + if (AddOfferItem == null) + { + return; + } + + ___ragfairOfferSellHelperClass.SelectItem(AddOfferItem); + AddOfferItem = null; + } + } +} \ No newline at end of file diff --git a/Plugin.cs b/Plugin.cs index 7c4a0bd..71abfb2 100644 --- a/Plugin.cs +++ b/Plugin.cs @@ -75,6 +75,7 @@ public class Plugin : BaseUnityPlugin WeaponModdingPatches.Enable(); TagPatches.Enable(); TacticalBindsPatches.Enable(); + AddOfferContextMenuPatches.Enable(); } public static bool InRaid() diff --git a/R.cs b/R.cs index ba934f1..1d2ad26 100644 --- a/R.cs +++ b/R.cs @@ -736,10 +736,12 @@ public static class R public class InventoryInteractions(object value) : Wrapper(value) { public static Type Type { get; private set; } + public static Type CompleteType { get; private set; } public static void InitTypes() { Type = PatchConstants.EftTypes.Single(t => t.GetField("HIDEOUT_WEAPON_MODIFICATION_REQUIRED") != null); // GClass3045 + CompleteType = PatchConstants.EftTypes.Single(t => t != Type && Type.IsAssignableFrom(t)); // GClass3046 } } diff --git a/Settings.cs b/Settings.cs index 2c26dea..71138d2 100644 --- a/Settings.cs +++ b/Settings.cs @@ -94,6 +94,8 @@ internal class Settings public static ConfigEntry UnpackKeyBind { get; set; } public static ConfigEntry FilterByKeyBind { get; set; } public static ConfigEntry LinkedSearchKeyBind { get; set; } + public static ConfigEntry AddOfferContextMenu { get; set; } // Advanced + public static ConfigEntry AddOfferKeyBind { get; set; } public static ConfigEntry SortingTableKeyBind { get; set; } public static ConfigEntry LimitNonstandardDrags { get; set; } // Advanced public static ConfigEntry ItemContextBlocksTextInputs { get; set; } // Advanced @@ -422,6 +424,24 @@ internal class Settings null, new ConfigurationManagerAttributes { }))); + configEntries.Add(AddOfferContextMenu = config.Bind( + InputSection, + "Add Offer Context Menu", + true, + new ConfigDescription( + "Add a context menu to list the item on the flea market", + null, + new ConfigurationManagerAttributes { IsAdvanced = true }))); + + configEntries.Add(AddOfferKeyBind = config.Bind( + InputSection, + "Linked Search Shortcut", + new KeyboardShortcut(KeyCode.None), + new ConfigDescription( + "Keybind to list item on the flea market", + null, + new ConfigurationManagerAttributes { }))); + configEntries.Add(SortingTableKeyBind = config.Bind( InputSection, "Transfer to/from Sorting Table", From 344fd9334a1a41ca083f258cd5ed2c6ce486f27f Mon Sep 17 00:00:00 2001 From: Tyfon <29051038+tyfon7@users.noreply.github.com> Date: Sat, 17 Aug 2024 00:14:30 -0700 Subject: [PATCH 24/73] Fix add offer fat scrollbar --- Patches/FixFleaPatches.cs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/Patches/FixFleaPatches.cs b/Patches/FixFleaPatches.cs index 5190e59..c2364cf 100644 --- a/Patches/FixFleaPatches.cs +++ b/Patches/FixFleaPatches.cs @@ -19,6 +19,8 @@ public static class FixFleaPatches new ToggleOnOpenPatch().Enable(); new DropdownHeightPatch().Enable(); + new AddOfferWindowDoubleScrollPatch().Enable(); + new OfferItemFixMaskPatch().Enable(); new OfferViewTweaksPatch().Enable(); @@ -101,6 +103,28 @@ public static class FixFleaPatches } } + public class AddOfferWindowDoubleScrollPatch : ModulePatch + { + protected override MethodBase GetTargetMethod() + { + return AccessTools.Method(typeof(AddOfferWindow), nameof(AddOfferWindow.Awake)); + } + + [PatchPostfix] + public static void Postfix(AddOfferWindow __instance, GameObject ____noOfferPanel, GameObject ____selectedItemPanel) + { + // Not sure how they messed it this up, but the widths on some of these are hardcoded + // badly, so things move around + Transform stashPart = __instance.transform.Find("Inner/Contents/StashPart"); + var stashLayout = stashPart.gameObject.GetComponent(); + stashLayout.preferredWidth = 644f; + + var noItemLayout = ____noOfferPanel.GetComponent(); + var requirementLayout = ____selectedItemPanel.GetComponent(); + requirementLayout.preferredWidth = noItemLayout.preferredWidth = 450f; + } + } + public class SearchPatch : ModulePatch { protected override MethodBase GetTargetMethod() From 28238b8e4d597ca591413ddfd3eac76594e7757b Mon Sep 17 00:00:00 2001 From: Tyfon <29051038+tyfon7@users.noreply.github.com> Date: Sat, 17 Aug 2024 00:56:27 -0700 Subject: [PATCH 25/73] ContextMenu in trading screen; possible multi-select click not working in raid fix --- Patches/AddOfferContextMenuPatches.cs | 26 +++++++++++++++++++++++--- Patches/MultiSelectPatches.cs | 2 +- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/Patches/AddOfferContextMenuPatches.cs b/Patches/AddOfferContextMenuPatches.cs index 1dff872..a76c86c 100644 --- a/Patches/AddOfferContextMenuPatches.cs +++ b/Patches/AddOfferContextMenuPatches.cs @@ -20,7 +20,8 @@ public static class AddOfferContextMenuPatches public static void Enable() { - new AddOfferMenuPatch().Enable(); + new AddOfferInventoryMenuPatch().Enable(); + new AddOfferTradingMenuPatch().Enable(); new AddOfferIsActivePatch().Enable(); new AddOfferIsInteractivePatch().Enable(); new AddOfferNameIconPatch().Enable(); @@ -30,7 +31,7 @@ public static class AddOfferContextMenuPatches new SelectItemPatch().Enable(); } - public class AddOfferMenuPatch : ModulePatch + public class AddOfferInventoryMenuPatch : ModulePatch { protected override MethodBase GetTargetMethod() { @@ -49,6 +50,25 @@ public static class AddOfferContextMenuPatches } } + public class AddOfferTradingMenuPatch : ModulePatch + { + protected override MethodBase GetTargetMethod() + { + return AccessTools.DeclaredProperty(R.TradingInteractions.Type, "AvailableInteractions").GetMethod; + } + + [PatchPostfix] + public static void Postfix(ref IEnumerable __result) + { + if (Settings.AddOfferContextMenu.Value) + { + var list = __result.ToList(); + list.Insert(list.IndexOf(EItemInfoButton.Tag), AddOfferInfoButton); + __result = list; + } + } + } + public class AddOfferNameIconPatch : ModulePatch { protected override MethodBase GetTargetMethod() @@ -131,7 +151,7 @@ public static class AddOfferContextMenuPatches { protected override MethodBase GetTargetMethod() { - return AccessTools.Method(R.InventoryInteractions.Type, "ExecuteInteractionInternal"); + return AccessTools.Method(typeof(BaseItemInfoInteractions), nameof(BaseItemInfoInteractions.ExecuteInteractionInternal)); } [PatchPrefix] diff --git a/Patches/MultiSelectPatches.cs b/Patches/MultiSelectPatches.cs index 92ccbe7..c9f4a1e 100644 --- a/Patches/MultiSelectPatches.cs +++ b/Patches/MultiSelectPatches.cs @@ -167,7 +167,7 @@ public static class MultiSelectPatches ___ItemController is InventoryControllerClass inventoryController) { SortingTableClass sortingTable = inventoryController.Inventory.SortingTable; - if (sortingTable != null && sortingTable.IsVisible) + if (sortingTable != null && sortingTable.IsVisible && !Plugin.InRaid()) { couldBeSortingTableMove = true; } From b03f23d12834e147250eea483e36d527d90fbadf Mon Sep 17 00:00:00 2001 From: Tyfon <29051038+tyfon7@users.noreply.github.com> Date: Sat, 17 Aug 2024 01:11:01 -0700 Subject: [PATCH 26/73] Use flea market icon for add offer --- Patches/AddOfferContextMenuPatches.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Patches/AddOfferContextMenuPatches.cs b/Patches/AddOfferContextMenuPatches.cs index a76c86c..d66940b 100644 --- a/Patches/AddOfferContextMenuPatches.cs +++ b/Patches/AddOfferContextMenuPatches.cs @@ -71,6 +71,8 @@ public static class AddOfferContextMenuPatches public class AddOfferNameIconPatch : ModulePatch { + private static Sprite FleaSprite = null; + protected override MethodBase GetTargetMethod() { return AccessTools.Method(typeof(InteractionButtonsContainer), nameof(InteractionButtonsContainer.Show)).MakeGenericMethod(typeof(EItemInfoButton)); @@ -84,9 +86,10 @@ public static class AddOfferContextMenuPatches { AddOfferInfoButton, "ragfair/OFFER ADD" } }; + FleaSprite ??= Resources.FindObjectsOfTypeAll().Single(s => s.name == "icon_flea_market"); icons ??= new Dictionary() { - { AddOfferInfoButton, EFTHardSettings.Instance.StaticIcons.GetSmallCurrencySign(ECurrencyType.RUB) } + { AddOfferInfoButton, FleaSprite } }; } } From 3dcf4a367e996416f35b675ccead829584a233b3 Mon Sep 17 00:00:00 2001 From: Tyfon <29051038+tyfon7@users.noreply.github.com> Date: Sat, 17 Aug 2024 01:17:36 -0700 Subject: [PATCH 27/73] Focus tag dialog text --- Patches/TagPatches.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Patches/TagPatches.cs b/Patches/TagPatches.cs index cfa8d47..c53679c 100644 --- a/Patches/TagPatches.cs +++ b/Patches/TagPatches.cs @@ -29,6 +29,8 @@ public static class TagPatches public static void Postfix(EditTagWindow __instance, ValidationInputField ____tagInput) { ____tagInput.onSubmit.AddListener(value => __instance.method_4()); + ____tagInput.ActivateInputField(); + ____tagInput.Select(); } } From d16e70fcba5d5a3d1d758dc432a4d608e4765ef0 Mon Sep 17 00:00:00 2001 From: Tyfon <29051038+tyfon7@users.noreply.github.com> Date: Sat, 17 Aug 2024 03:11:58 -0700 Subject: [PATCH 28/73] Still scale quickbind icons --- Patches/QuickAccessPanelPatches.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Patches/QuickAccessPanelPatches.cs b/Patches/QuickAccessPanelPatches.cs index f2c15d9..5cf6f34 100644 --- a/Patches/QuickAccessPanelPatches.cs +++ b/Patches/QuickAccessPanelPatches.cs @@ -121,13 +121,17 @@ public static class QuickAccessPanelPatches return; } - // Already square items don't need to be rotated - XYCellSizeStruct size = __instance.Item.CalculateCellSize(); - if (size.X == size.Y) + // Already square items don't need to be rotated. Still need to be scaled though! + XYCellSizeStruct cellSize = __instance.Item.CalculateCellSize(); + if (cellSize.X == cellSize.Y) { Transform transform = ___MainImage.transform; transform.localRotation = Quaternion.identity; - transform.localScale = Vector3.one; + + Vector3 size = ___MainImage.rectTransform.rect.size; + float xScale = __instance.IconScale.Value.x / Mathf.Abs(size.x); + float yScale = __instance.IconScale.Value.y / Mathf.Abs(size.y); + transform.localScale = Vector3.one * Mathf.Min(xScale, yScale); } } } From cdaf3ed441fab2890b06e1153d7d250cc9c89a71 Mon Sep 17 00:00:00 2001 From: Tyfon <29051038+tyfon7@users.noreply.github.com> Date: Mon, 19 Aug 2024 13:39:04 -0700 Subject: [PATCH 29/73] Bindable headlights, nvg; toggle/hold applies --- GlobalUsings.cs | 1 + Patches/AimToggleHoldPatches.cs | 53 +++++---- Patches/TacticalBindsPatches.cs | 194 +++++++++++++++++++++++++++++--- 3 files changed, 213 insertions(+), 35 deletions(-) diff --git a/GlobalUsings.cs b/GlobalUsings.cs index e4c8818..5580321 100644 --- a/GlobalUsings.cs +++ b/GlobalUsings.cs @@ -19,6 +19,7 @@ global using ItemSorter = GClass2772; global using ItemWithLocation = GClass2521; global using SearchableGrid = GClass2516; global using CursorManager = GClass3034; +global using Helmet = GClass2651; // State machine states global using FirearmReadyState = EFT.Player.FirearmController.GClass1619; diff --git a/Patches/AimToggleHoldPatches.cs b/Patches/AimToggleHoldPatches.cs index 10cda72..039d8ed 100644 --- a/Patches/AimToggleHoldPatches.cs +++ b/Patches/AimToggleHoldPatches.cs @@ -74,15 +74,7 @@ public static class AimToggleHoldPatches [PatchPostfix] public static void Postfix(ToggleKeyCombination __instance, EGameKey gameKey, ECommand command, KeyCombination.KeyCombinationState[] ___keyCombinationState_1) { - bool useToggleHold = gameKey switch - { - EGameKey.Tactical => Settings.ToggleOrHoldTactical.Value, - EGameKey.ToggleGoggles => Settings.ToggleOrHoldGoggles.Value, - EGameKey.ToggleHeadLight => Settings.ToggleOrHoldHeadlight.Value, - _ => false - }; - - if (!useToggleHold) + if (!UseToggleHold(gameKey)) { return; } @@ -108,23 +100,44 @@ public static class AimToggleHoldPatches [PatchPostfix] public static void Postfix(KeyCombination __instance) { - bool useToggleHold = __instance.GameKey switch - { - EGameKey.Aim => Settings.ToggleOrHoldAim.Value, - EGameKey.Tactical => Settings.ToggleOrHoldTactical.Value, - EGameKey.ToggleGoggles => Settings.ToggleOrHoldGoggles.Value, - EGameKey.ToggleHeadLight => Settings.ToggleOrHoldHeadlight.Value, - EGameKey.Sprint => Settings.ToggleOrHoldSprint.Value, - _ => false - }; - - if (useToggleHold) + if (UseToggleHold(__instance.GameKey)) { __instance.method_0((KeyCombination.EKeyState)ToggleHoldState.Idle); } } } + private static bool UseToggleHold(EGameKey gameKey) + { + return gameKey switch + { + EGameKey.Aim => Settings.ToggleOrHoldAim.Value, + EGameKey.Tactical => Settings.ToggleOrHoldTactical.Value, + EGameKey.ToggleGoggles => Settings.ToggleOrHoldGoggles.Value, + EGameKey.ToggleHeadLight => Settings.ToggleOrHoldHeadlight.Value, + EGameKey.Sprint => Settings.ToggleOrHoldSprint.Value, + EGameKey.Slot4 => UseToggleHoldQuickBind(EGameKey.Slot4), + EGameKey.Slot5 => UseToggleHoldQuickBind(EGameKey.Slot5), + EGameKey.Slot6 => UseToggleHoldQuickBind(EGameKey.Slot6), + EGameKey.Slot7 => UseToggleHoldQuickBind(EGameKey.Slot7), + EGameKey.Slot8 => UseToggleHoldQuickBind(EGameKey.Slot8), + EGameKey.Slot9 => UseToggleHoldQuickBind(EGameKey.Slot9), + EGameKey.Slot0 => UseToggleHoldQuickBind(EGameKey.Slot0), + _ => false + }; + } + + private static bool UseToggleHoldQuickBind(EGameKey gameKey) + { + return Quickbind.GetType(gameKey) switch + { + Quickbind.ItemType.Tactical => Settings.ToggleOrHoldTactical.Value, + Quickbind.ItemType.Headlight => Settings.ToggleOrHoldHeadlight.Value, + Quickbind.ItemType.NightVision => Settings.ToggleOrHoldGoggles.Value, + _ => false, + }; + } + private static void OnSettingChanged(object sender, EventArgs args) { // Will "save" control settings, running GClass1911.UpdateInput, which will set (or unset) toggle/hold behavior diff --git a/Patches/TacticalBindsPatches.cs b/Patches/TacticalBindsPatches.cs index 7365281..c1ccf1a 100644 --- a/Patches/TacticalBindsPatches.cs +++ b/Patches/TacticalBindsPatches.cs @@ -1,9 +1,13 @@ using Comfort.Common; using EFT; +using EFT.InputSystem; using EFT.InventoryLogic; using HarmonyLib; using SPT.Reflection.Patching; +using System.Collections.Generic; +using System.Linq; using System.Reflection; +using System.Threading.Tasks; using UnityEngine; namespace UIFixes; @@ -15,6 +19,10 @@ public static class TacticalBindsPatches new BindableTacticalPatch().Enable(); new ReachableTacticalPatch().Enable(); new UseTacticalPatch().Enable(); + + new BindTacticalPatch().Enable(); + new UnbindTacticalPatch().Enable(); + new InitQuickBindsPatch().Enable(); } public class BindableTacticalPatch : ModulePatch @@ -72,20 +80,33 @@ public static class TacticalBindsPatches } LightComponent lightComponent = boundItem.GetItemComponent(); - if (lightComponent == null) - { - return true; - } - - // Don't return true past this point; if the default handler tries to use a tactical device, very bad things happen - - if (__instance.HandsController is not Player.FirearmController firearmController || - firearmController.Item != boundItem.GetRootItem()) + if (lightComponent != null) { + ToggleLight(__instance, boundItem, lightComponent); callback(null); return false; } + NightVisionComponent nightVisionComponent = boundItem.GetItemComponent(); + if (nightVisionComponent != null) + { + Item rootItem = boundItem.GetRootItemNotEquipment(); + if (rootItem is Helmet helmet && + __instance.Inventory.Equipment.GetSlot(EquipmentSlot.Headwear).ContainedItem == helmet) + { + __instance.InventoryControllerClass.TryRunNetworkTransaction( + nightVisionComponent.Togglable.Set(!nightVisionComponent.Togglable.On, true, false)); + } + + callback(null); + return false; + } + + return true; + } + + private static void ToggleLight(Player player, Item boundItem, LightComponent lightComponent) + { FirearmLightStateStruct lightState = new() { Id = lightComponent.Item.Id, @@ -102,24 +123,96 @@ public static class TacticalBindsPatches lightState.IsActive = !lightState.IsActive; } - firearmController.SetLightsState([lightState], false); + Item rootItem = boundItem.GetRootItemNotEquipment(); + if (rootItem is Weapon weapon && + player.HandsController is Player.FirearmController firearmController && + firearmController.Item == weapon) + { + firearmController.SetLightsState([lightState], false); + } - callback(null); - return false; + if (rootItem is Helmet helmet && + player.Inventory.Equipment.GetSlot(EquipmentSlot.Headwear).ContainedItem == helmet) + { + lightComponent.SetLightState(lightState); + player.SendHeadlightsPacket(false); + player.SwitchHeadLightsAnimation(); + } + } + } + + public class InitQuickBindsPatch : ModulePatch + { + protected override MethodBase GetTargetMethod() + { + return AccessTools.Method(typeof(MainMenuController), nameof(MainMenuController.method_5)); + } + + [PatchPostfix] + public static async void Postfix(MainMenuController __instance, Task __result) + { + await __result; + + for (EBoundItem index = EBoundItem.Item4; index <= EBoundItem.Item10; index++) + { + if (__instance.InventoryController.Inventory.FastAccess.BoundItems.ContainsKey(index)) + { + UpdateQuickbindType(__instance.InventoryController.Inventory.FastAccess.BoundItems[index], index); + } + } + + // Will "save" control settings, running GClass1911.UpdateInput, which will set (or unset) toggle/hold behavior + Singleton.Instance.Control.Controller.method_3(); + } + } + + public class BindTacticalPatch : ModulePatch + { + protected override MethodBase GetTargetMethod() + { + return AccessTools.Method(typeof(GClass2818), nameof(GClass2818.Run)); + } + + [PatchPostfix] + public static void Postfix(InventoryControllerClass controller, Item item, EBoundItem index) + { + UpdateQuickbindType(item, index); + + // Will "save" control settings, running GClass1911.UpdateInput, which will set (or unset) toggle/hold behavior + Singleton.Instance.Control.Controller.method_3(); + } + } + + public class UnbindTacticalPatch : ModulePatch + { + protected override MethodBase GetTargetMethod() + { + return AccessTools.Method(typeof(GClass2819), nameof(GClass2819.Run)); + } + + [PatchPostfix] + public static void Postfix(InventoryControllerClass controller, Item item, EBoundItem index) + { + Quickbind.SetType(index, Quickbind.ItemType.Other); + + // Will "save" control settings, running GClass1911.UpdateInput, which will set (or unset) toggle/hold behavior + Singleton.Instance.Control.Controller.method_3(); } } private static bool IsEquippedTacticalDevice(InventoryControllerClass inventoryController, Item item) { LightComponent lightComponent = item.GetItemComponent(); - if (lightComponent == null) + NightVisionComponent nightVisionComponent = item.GetItemComponent(); + if (lightComponent == null && nightVisionComponent == null) { return false; } - if (item.GetRootItem() is Weapon weapon) + Item rootItem = item.GetRootItemNotEquipment(); + if (rootItem is Weapon || rootItem is Helmet) { - return inventoryController.Inventory.Equipment.Contains(weapon); + return inventoryController.Inventory.Equipment.Contains(rootItem); } return false; @@ -135,4 +228,75 @@ public static class TacticalBindsPatches _ => false, }; } + + private static void UpdateQuickbindType(Item item, EBoundItem index) + { + if (item == null) + { + Quickbind.SetType(index, Quickbind.ItemType.Other); + return; + } + + LightComponent lightComponent = item.GetItemComponent(); + if (lightComponent != null) + { + Item rootItem = item.GetRootItemNotEquipment(); + if (rootItem is Weapon) + { + Quickbind.SetType(index, Quickbind.ItemType.Tactical); + return; + } + + if (rootItem is Helmet) + { + Quickbind.SetType(index, Quickbind.ItemType.Headlight); + return; + } + } + + NightVisionComponent nvComponent = item.GetItemComponent(); + if (nvComponent != null) + { + Quickbind.SetType(index, Quickbind.ItemType.NightVision); + return; + } + + Quickbind.SetType(index, Quickbind.ItemType.Other); + } + + private static Item GetRootItemNotEquipment(this Item item) + { + return item.GetAllParentItemsAndSelf(true).LastOrDefault(i => i is not EquipmentClass) ?? item; + } +} + +public static class Quickbind +{ + public enum ItemType + { + Other, + Tactical, + Headlight, + NightVision + } + + private static readonly Dictionary TacticalQuickbinds = new() + { + { EBoundItem.Item4, ItemType.Other }, + { EBoundItem.Item5, ItemType.Other }, + { EBoundItem.Item6, ItemType.Other }, + { EBoundItem.Item7, ItemType.Other }, + { EBoundItem.Item8, ItemType.Other }, + { EBoundItem.Item9, ItemType.Other }, + { EBoundItem.Item10, ItemType.Other }, + }; + + public static ItemType GetType(EBoundItem index) => TacticalQuickbinds[index]; + public static void SetType(EBoundItem index, ItemType type) => TacticalQuickbinds[index] = type; + + public static ItemType GetType(EGameKey gameKey) + { + int offset = gameKey - EGameKey.Slot4; + return GetType(EBoundItem.Item4 + offset); + } } \ No newline at end of file From 03daf17cb7c0b22259ba2837eb80137d2d542f2e Mon Sep 17 00:00:00 2001 From: Tyfon <29051038+tyfon7@users.noreply.github.com> Date: Mon, 19 Aug 2024 13:39:55 -0700 Subject: [PATCH 30/73] rev version 2.4.1 --- UIFixes.csproj | 2 +- server/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/UIFixes.csproj b/UIFixes.csproj index 8f3c137..61168c9 100644 --- a/UIFixes.csproj +++ b/UIFixes.csproj @@ -4,7 +4,7 @@ net471 Tyfon.UIFixes SPT UI Fixes - 2.4.0 + 2.4.1 true latest Debug;Release diff --git a/server/package.json b/server/package.json index 19639d8..676e546 100644 --- a/server/package.json +++ b/server/package.json @@ -1,6 +1,6 @@ { "name": "uifixes", - "version": "2.4.0", + "version": "2.4.1", "main": "src/mod.js", "license": "MIT", "author": "Tyfon", From 1f7d2b6f84646fcc2862d04482ff5b54567b3bf5 Mon Sep 17 00:00:00 2001 From: Tyfon <29051038+tyfon7@users.noreply.github.com> Date: Mon, 19 Aug 2024 13:52:10 -0700 Subject: [PATCH 31/73] update readme --- README.md | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index ddc3557..36ad276 100644 --- a/README.md +++ b/README.md @@ -15,10 +15,11 @@ New UI features enabled by this mod - Ctrl-click and Alt-click to quick move or equip them all. Compatible with Quick Move to Containers! - Context menu to insure all, equip all, unequip all, unload ammo from all - Swap items in place - drag one item over another to swap their locations! +- ✨ Add offer to the flea market from an item's context menu - Flea market history - press the new back button to go back to the previous search -- ✨ Linked flea search from empty slots - find mods that fit that specific slot +- Linked flea search from empty slots - find mods that fit that specific slot - Keybinds for most context menu actions -- ✨ Toggle/Hold input - tap a key for "Press" mechanics, hold the key for "Continuous" mechanics +- Toggle/Hold input - tap a key for "Press" mechanics, hold the key for "Continuous" mechanics - Can be set for aiming, sprinting, tactical devices, headlights, and goggles/faceshields. ## Improved features @@ -30,15 +31,15 @@ Existing SPT features made better - Rebind Home/End, PageUp/PageDown to work like you would expect - Customizable mouse scrolling speed - Moving stacks into containers always moves entire stack -- ✨ Items made stackable by other mods follow normal stacking behavior +- Items made stackable by other mods follow normal stacking behavior - Allow found in raid money and ammo automatically stack with non-found-in-raid items - Synchronize stash scroll position everywhere your stash is visible - Insure and repair items directly from the context menu - Load ammo via context menu _in raid_ - Load ammo preset will pull ammo from inventory, not just stash -- ✨ Multi-grid vest and backpack grids reordered to be left to right, top to bottom. -- ✨ Sorting will stack and combine stacks of items -- ✨ Shift-clicking sort will only sort loose items, leaving containers in place +- Multi-grid vest and backpack grids reordered to be left to right, top to bottom. +- Sorting will stack and combine stacks of items +- Shift-clicking sort will only sort loose items, leaving containers in place #### Inspect windows @@ -62,11 +63,12 @@ Existing SPT features made better - Option to keep the Add Offer window open after placing your offer - Set prices in the Add Offer window by clicking the min/avg/max market prices (multiplies for bulk orders) - Autoselect Similar checkbox is remembered across sessions and application restarts -- ✨ Replace barter offers icons with actual item images, plus owned/required counts on expansion -- ✨ Clears filters for you when you type in search bar and there's no match +- Replace barter offers icons with actual item images, plus owned/required counts on expansion +- Clears filters for you when you type in search bar and there's no match #### Weapon modding/presets +- ✨ Weapons can grow left or up, not just right and down - Enable zooming with mousewheel - Skip needless unsaved changes warnings when not actually closing the screen @@ -77,9 +79,10 @@ Existing SPT features made better #### In raid -- ✨ Reloading will swap magazines in-place, instead of dropping them on the ground when there's no room. -- ✨ Grenade quickbinds will transfer to the next grenade of the same type after throwing. -- ✨ Option to change the behavior of the grenade key from selecting a random grenade to a deterministic one +- ✨ Quickbind tactical devices to control them individually +- Reloading will swap magazines in-place, instead of dropping them on the ground when there's no room. +- Grenade quickbinds will transfer to the next grenade of the same type after throwing. +- Option to change the behavior of the grenade key from selecting a random grenade to a deterministic one #### Mail From 9f085cadaaba53e377cdd0d608868183fee79caa Mon Sep 17 00:00:00 2001 From: Henry Beckwith Date: Tue, 20 Aug 2024 18:23:41 -0400 Subject: [PATCH 32/73] removed errant periods --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 36ad276..1a27965 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ New UI features enabled by this mod - Linked flea search from empty slots - find mods that fit that specific slot - Keybinds for most context menu actions - Toggle/Hold input - tap a key for "Press" mechanics, hold the key for "Continuous" mechanics - - Can be set for aiming, sprinting, tactical devices, headlights, and goggles/faceshields. + - Can be set for aiming, sprinting, tactical devices, headlights, and goggles/faceshields ## Improved features @@ -37,7 +37,7 @@ Existing SPT features made better - Insure and repair items directly from the context menu - Load ammo via context menu _in raid_ - Load ammo preset will pull ammo from inventory, not just stash -- Multi-grid vest and backpack grids reordered to be left to right, top to bottom. +- Multi-grid vest and backpack grids reordered to be left to right, top to bottom - Sorting will stack and combine stacks of items - Shift-clicking sort will only sort loose items, leaving containers in place @@ -46,7 +46,7 @@ Existing SPT features made better - Show the total stats (including sub-mods) when inspecting mods (optional, toggleable _in_ the inspect pane with a new button) - See stats change as you add/remove mods, with color-coded deltas - Remember last window size when you change it, with restore button to resize to default -- Move left and move right buttons + keybinds to quickly snap inspect windows to the left or right half of the screen, for easy comparisons. +- Move left and move right buttons + keybinds to quickly snap inspect windows to the left or right half of the screen, for easy comparisons - Auto-expand descriptions when possible (great for showing extra text from mods like Item Info) - Quickbinds will not be removed from items you keep when you die @@ -80,8 +80,8 @@ Existing SPT features made better #### In raid - ✨ Quickbind tactical devices to control them individually -- Reloading will swap magazines in-place, instead of dropping them on the ground when there's no room. -- Grenade quickbinds will transfer to the next grenade of the same type after throwing. +- Reloading will swap magazines in-place, instead of dropping them on the ground when there's no room +- Grenade quickbinds will transfer to the next grenade of the same type after throwing - Option to change the behavior of the grenade key from selecting a random grenade to a deterministic one #### Mail From 0d7ee4de6827e9b3ae2a90d7615ef267dd8abd4f Mon Sep 17 00:00:00 2001 From: Tyfon <29051038+tyfon7@users.noreply.github.com> Date: Wed, 21 Aug 2024 10:57:45 -0700 Subject: [PATCH 33/73] Check __runOriginal in ExecutePossibleActions patch, compat fix for MergeConsumables --- Patches/FixTraderControllerSimulateFalsePatch.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/Patches/FixTraderControllerSimulateFalsePatch.cs b/Patches/FixTraderControllerSimulateFalsePatch.cs index 0a68ee6..df3b855 100644 --- a/Patches/FixTraderControllerSimulateFalsePatch.cs +++ b/Patches/FixTraderControllerSimulateFalsePatch.cs @@ -16,8 +16,20 @@ public class FixTraderControllerSimulateFalsePatch : ModulePatch // Recreating this function to add the comment section, so calling this with simulate = false doesn't break everything [PatchPrefix] [HarmonyPriority(Priority.Last)] - public static bool Prefix(TraderControllerClass __instance, ItemContextAbstractClass itemContext, Item targetItem, bool partialTransferOnly, bool simulate, ref ItemOperation __result) + public static bool Prefix( + TraderControllerClass __instance, + ItemContextAbstractClass itemContext, + Item targetItem, + bool partialTransferOnly, + bool simulate, + ref ItemOperation __result, + bool __runOriginal) { + if (!__runOriginal) + { + return __runOriginal; + } + TargetItemOperation opStruct; opStruct.targetItem = targetItem; opStruct.traderControllerClass = __instance; From 7bdd98ecd17c2e5b4087f4c1dce96e003453031d Mon Sep 17 00:00:00 2001 From: Tyfon <29051038+tyfon7@users.noreply.github.com> Date: Wed, 21 Aug 2024 15:39:27 -0700 Subject: [PATCH 34/73] Mod weapons in hands/in raid; Move add offer setting --- Extensions.cs | 22 ++ GlobalUsings.cs | 3 + Patches/TacticalBindsPatches.cs | 5 - Patches/WeaponModdingPatches.cs | 432 ++++++++++++++++++++++++-------- Settings.cs | 49 +++- 5 files changed, 389 insertions(+), 122 deletions(-) create mode 100644 Extensions.cs diff --git a/Extensions.cs b/Extensions.cs new file mode 100644 index 0000000..fb844eb --- /dev/null +++ b/Extensions.cs @@ -0,0 +1,22 @@ +using System.Linq; +using EFT.InventoryLogic; + +namespace UIFixes; + +public static class Extensions +{ + public static Item GetRootItemNotEquipment(this Item item) + { + return item.GetAllParentItemsAndSelf(true).LastOrDefault(i => i is not EquipmentClass) ?? item; + } + + public static Item GetRootItemNotEquipment(this ItemAddress itemAddress) + { + if (itemAddress.Container == null || itemAddress.Container.ParentItem == null) + { + return null; + } + + return itemAddress.Container.ParentItem.GetRootItemNotEquipment(); + } +} \ No newline at end of file diff --git a/GlobalUsings.cs b/GlobalUsings.cs index 5580321..c8d0597 100644 --- a/GlobalUsings.cs +++ b/GlobalUsings.cs @@ -39,6 +39,9 @@ global using NoPossibleActionsError = GClass3317; global using CannotSortError = GClass3325; global using FailedToSortError = GClass3326; global using MoveSameSpaceError = InteractionsHandlerClass.GClass3353; +global using NotModdableInRaidError = GClass3321; +global using MultitoolNeededError = GClass3322; +global using ModVitalPartInRaidError = GClass3323; // Operations global using ItemOperation = GStruct413; diff --git a/Patches/TacticalBindsPatches.cs b/Patches/TacticalBindsPatches.cs index c1ccf1a..d0ce65c 100644 --- a/Patches/TacticalBindsPatches.cs +++ b/Patches/TacticalBindsPatches.cs @@ -263,11 +263,6 @@ public static class TacticalBindsPatches Quickbind.SetType(index, Quickbind.ItemType.Other); } - - private static Item GetRootItemNotEquipment(this Item item) - { - return item.GetAllParentItemsAndSelf(true).LastOrDefault(i => i is not EquipmentClass) ?? item; - } } public static class Quickbind diff --git a/Patches/WeaponModdingPatches.cs b/Patches/WeaponModdingPatches.cs index 4ffdd36..5f88d07 100644 --- a/Patches/WeaponModdingPatches.cs +++ b/Patches/WeaponModdingPatches.cs @@ -1,16 +1,23 @@ -using Diz.LanguageExtensions; +using Comfort.Common; +using EFT; using EFT.InventoryLogic; +using EFT.UI.DragAndDrop; using HarmonyLib; using SPT.Reflection.Patching; using System; +using System.Collections; using System.Collections.Generic; using System.Linq; using System.Reflection; +using UnityEngine; namespace UIFixes; public static class WeaponModdingPatches { + private const string MultitoolId = "544fb5454bdc2df8738b456a"; + private static readonly string[] EquippedSlots = ["FirstPrimaryWeapon", "SecondPrimaryWeapon", "Holster"]; + public static void Enable() { new ResizePatch().Enable(); @@ -18,8 +25,13 @@ public static class WeaponModdingPatches new ResizeOperationRollbackPatch().Enable(); new MoveBeforeNetworkTransactionPatch().Enable(); - //new ModdingMoveToSortingTablePatch().Enable(); - //new PresetMoveToSortingTablePatch().Enable(); + new ModEquippedPatch().Enable(); + new InspectLockedPatch().Enable(); + new ModCanBeMovedPatch().Enable(); + new ModCanDetachPatch().Enable(); + new ModCanApplyPatch().Enable(); + new ModRaidModdablePatch().Enable(); + new EmptyVitalPartsPatch().Enable(); } public class ResizePatch : ModulePatch @@ -192,124 +204,332 @@ public static class WeaponModdingPatches } } - // public class ModdingMoveToSortingTablePatch : ModulePatch - // { - // private static bool InPatch = false; + public class ModEquippedPatch : ModulePatch + { + protected override MethodBase GetTargetMethod() + { + return AccessTools.Method(R.ContextMenuHelper.Type, "IsInteractive"); + } - // protected override MethodBase GetTargetMethod() - // { - // return AccessTools.DeclaredMethod(typeof(GClass2848), nameof(GClass2848.Select)); - // } + // Enable/disable options in the context menu + [PatchPostfix] + public static void Postfix(EItemInfoButton button, ref IResult __result, Item ___item_0) + { + // These two are only visible out of raid, enable them + if (Settings.ModifyEquippedWeapons.Value && (button == EItemInfoButton.Modding || button == EItemInfoButton.EditBuild)) + { + if (__result.Succeed || !Singleton.Instance.HasBonus(EBonusType.UnlockWeaponModification)) + { + return; + } - // [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; - // } + __result = SuccessfulResult.New; + return; + } - // // get top level item (weapon) - // Item rootItem = itemAddress.Container.ParentItem.GetRootMergedItem(); - // if (rootItem == null) - // { - // return; - // } + // This is surprisingly active in raid? Only enable out of raid. + if (button == EItemInfoButton.Disassemble) + { + if (!Plugin.InRaid() && Settings.ModifyEquippedWeapons.Value) + { + __result = SuccessfulResult.New; + return; + } + } - // // move it to sorting table - // SortingTableClass sortingTable = __instance.InventoryControllerClass.Inventory.SortingTable; - // if (sortingTable == null) - // { - // return; - // } + // These are on mods; normally the context menu is disabled so these are individually not disabled + // Need to do the disabling as appropriate + if (___item_0 is Mod mod && (button == EItemInfoButton.Uninstall || button == EItemInfoButton.Discard)) + { + if (!CanModify(mod, out string error)) + { + __result = new FailedResult(error); + return; + } + } + } + } - // ItemAddressClass sortingTableAddress = sortingTable.Grid.FindLocationForItem(rootItem); - // if (sortingTableAddress == null) - // { - // return; - // } + public class InspectLockedPatch : ModulePatch + { + protected override MethodBase GetTargetMethod() + { + return AccessTools.Method(typeof(ModSlotView), nameof(ModSlotView.method_14)); + } - // var sortingTableMove = InteractionsHandlerClass.Move(rootItem, sortingTableAddress, __instance.InventoryControllerClass, simulate); - // if (sortingTableMove.Failed || sortingTableMove.Value == null) - // { - // return; - // } + // Enable context menu on normally unmoddable slots, maybe keep them gray + [PatchPostfix] + public static void Postfix(ModSlotView __instance, ref bool ___bool_1, CanvasGroup ____canvasGroup) + { + // Keep it grayed out and warning text if its not draggable, even if context menu is enabled + if (__instance.Slot.ContainedItem is Mod mod && CanModify(mod, out string error)) + { + ___bool_1 = false; + ____canvasGroup.alpha = 1f; + } - // if (simulate) - // { - // // Just testing, and it was moveable to sorting table, so assume everything is fine. - // error = null; - // __result = true; - // return; - // } + ____canvasGroup.blocksRaycasts = true; + ____canvasGroup.interactable = true; + } + } - // // Actually selecting it, so do it and then redo the select - // __instance.InventoryControllerClass.RunNetworkTransaction(sortingTableMove.Value); + public class ModCanBeMovedPatch : ModulePatch + { + protected override MethodBase GetTargetMethod() + { + return AccessTools.Method(typeof(Mod), nameof(Mod.CanBeMoved)); + } - // InPatch = true; - // __result = __instance.Select(item, itemAddress, simulate, out error); - // InPatch = false; - // } - // } + // As far as I can tell this never gets called, but hey + [PatchPostfix] + public static void Postfix(Mod __instance, IContainer toContainer, ref GStruct416 __result) + { + if (__result.Succeeded) + { + return; + } - // public class PresetMoveToSortingTablePatch : ModulePatch - // { - // private static bool InPatch = false; + if (!CanModify(__instance, out string itemError)) + { + return; + } - // protected override MethodBase GetTargetMethod() - // { - // return AccessTools.Method(typeof(GClass2849), nameof(GClass2849.method_2)); - // } + if (toContainer is not Slot toSlot || !CanModify(R.SlotItemAddress.Create(toSlot), out string slotError)) + { + return; + } - // [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; - // } + __result = true; + } + } - // // move it to sorting table - // SortingTableClass sortingTable = inventoryController.Inventory.SortingTable; - // if (sortingTable == null) - // { - // return; - // } + public class ModCanDetachPatch : ModulePatch + { + private static Type TargetMethodReturnType; - // ItemAddressClass sortingTableAddress = sortingTable.Grid.FindLocationForItem(item); - // if (sortingTableAddress == null) - // { - // return; - // } + protected override MethodBase GetTargetMethod() + { + MethodInfo method = AccessTools.Method(typeof(InteractionsHandlerClass), nameof(InteractionsHandlerClass.smethod_1)); + TargetMethodReturnType = method.ReturnType; + return method; + } - // var sortingTableMove = InteractionsHandlerClass.Move(item, sortingTableAddress, inventoryController, simulate); // only called with simulate = false - // if (sortingTableMove.Failed || sortingTableMove.Value == null) - // { - // return; - // } + // This gets invoked when dragging items around between slots + [PatchPostfix] + public static void Postfix(Item item, ItemAddress to, TraderControllerClass itemController, ref GStruct416 __result) + { + if (item is not Mod mod) + { + return; + } - // InPatch = true; - // __result = __instance.method_2(item, modsWithSlots, itemController, simulate); - // InPatch = false; + if (Plugin.InRaid() && __result.Succeeded) + { + // In raid successes are all fine + return; + } - // if (__result.Succeeded) - // { - // __result.Value.Prepend(sortingTableMove); - // } - // else - // { - // sortingTableMove.Value.RollBack(); - // } - // } - // } + bool canModify = CanModify(mod, out string error) && CanModify(to, out error); + if (canModify == __result.Succeeded) + { + // In agreement, just check the error is best to show + if (Settings.ModifyRaidWeapons.Value == ModRaidWeapon.WithTool && + (__result.Error is NotModdableInRaidError || __result.Error is ModVitalPartInRaidError)) + { + // Double check this is an unequipped weapon + Weapon weapon = item.GetRootItemNotEquipment() as Weapon ?? to.GetRootItemNotEquipment() as Weapon; + if (weapon != null && !EquippedSlots.Contains(weapon.Parent.Container.ID)) + { + __result = new MultitoolNeededError(item); + } + } + } + + if (__result.Failed && canModify) + { + // Override result with success if DestinationCheck passes + var destinationCheck = InteractionsHandlerClass.DestinationCheck(item.Parent, to, itemController.OwnerType); + if (destinationCheck.Failed) + { + return; + } + + __result = default; + } + else if (__result.Succeeded && !canModify) + { + // Out of raid, likely dragging a mod that was previously non-interactive, need to actually block + __result = new VitalPartInHandsError(); + } + } + + private class VitalPartInHandsError : InventoryError + { + public override string GetLocalizedDescription() + { + return "Vital mod weapon in hands".Localized(); + } + + public override string ToString() + { + return "Vital mod weapon in hands"; + } + } + } + + public class ModCanApplyPatch : ModulePatch + { + protected override MethodBase GetTargetMethod() + { + return AccessTools.Method(typeof(LootItemClass), nameof(LootItemClass.Apply)); + } + + // Gets called when dropping mods on top of weapons + [PatchPrefix] + public static void Prefix(LootItemClass __instance, Item item) + { + if (!Plugin.InRaid()) + { + return; + } + + if (__instance is not Weapon weapon || item is not Mod mod || EquippedSlots.Contains(weapon.Parent.Container.ID)) + { + return; + } + + if (CanModify(mod, out string error)) + { + ModRaidModdablePatch.Override = true; + EmptyVitalPartsPatch.Override = true; + } + } + + [PatchPostfix] + public static void Postfix(LootItemClass __instance, ref ItemOperation __result) + { + ModRaidModdablePatch.Override = false; + EmptyVitalPartsPatch.Override = false; + + // If setting is multitool, may need to change some errors + if (Settings.ModifyRaidWeapons.Value == ModRaidWeapon.WithTool) + { + if (__instance is not Weapon weapon || EquippedSlots.Contains(weapon.Parent.Container.ID)) + { + return; + } + + if (__result.Error is NotModdableInRaidError || __result.Error is ModVitalPartInRaidError) + { + __result = new MultitoolNeededError(__instance); + } + } + } + } + + public class ModRaidModdablePatch : ModulePatch + { + public static bool Override = false; + + protected override MethodBase GetTargetMethod() + { + return AccessTools.Property(typeof(Mod), nameof(Mod.RaidModdable)).GetMethod; + } + + [PatchPostfix] + public static void Postfix(ref bool __result) + { + __result = __result || Override; + } + } + + public class EmptyVitalPartsPatch : ModulePatch + { + public static bool Override = false; + + protected override MethodBase GetTargetMethod() + { + return AccessTools.Property(typeof(LootItemClass), nameof(LootItemClass.VitalParts)).GetMethod; + } + + [PatchPrefix] + public static bool Prefix(ref IEnumerable __result) + { + if (Override) + { + __result = []; + return false; + } + + return true; + } + } + + private static bool CanModify(Mod item, out string error) + { + return CanModify(item, item?.Parent, out error); + } + + private static bool CanModify(ItemAddress itemAddress, out string error) + { + return CanModify(null, itemAddress, out error); + } + + private static bool CanModify(Mod item, ItemAddress itemAddress, out string error) + { + error = null; + + // If it's raidmoddable and not in a vital slot, then it's all good + if ((item == null || item.RaidModdable) && + (!R.SlotItemAddress.Type.IsAssignableFrom(itemAddress.GetType()) || !new R.SlotItemAddress(itemAddress).Slot.Required)) + { + return true; + } + + Item rootItem = itemAddress.GetRootItemNotEquipment(); + if (rootItem is not Weapon weapon) + { + return true; + } + + // Can't modify weapon in hands + if (EquippedSlots.Contains(weapon.Parent.Container.ID)) + { + if (Plugin.InRaid()) + { + error = "Inventory Errors/Not moddable in raid"; + return false; + } + + + if (!Settings.ModifyEquippedWeapons.Value) + { + error = "Vital mod weapon in hands"; + return false; + } + } + + // Not in raid, not in hands: anything is possible + if (!Plugin.InRaid()) + { + return true; + } + + if (Settings.ModifyRaidWeapons.Value == ModRaidWeapon.Never) + { + error = "Inventory Errors/Not moddable in raid"; + return false; + } + + Player player = Singleton.Instance.MainPlayer; + bool hasMultitool = player.Equipment.GetAllItems().Any(i => i.TemplateId == MultitoolId); + + if (Settings.ModifyRaidWeapons.Value == ModRaidWeapon.WithTool && !hasMultitool) + { + error = "Inventory Errors/Not moddable without multitool"; + return false; + } + + return true; + } } \ No newline at end of file diff --git a/Settings.cs b/Settings.cs index 71138d2..6eeda98 100644 --- a/Settings.cs +++ b/Settings.cs @@ -52,6 +52,14 @@ internal enum TacticalBindModifier Alt } +internal enum ModRaidWeapon +{ + Never, + [Description("With Multitool")] + WithTool, + Always +} + internal class Settings { // Categories @@ -94,7 +102,6 @@ internal class Settings public static ConfigEntry UnpackKeyBind { get; set; } public static ConfigEntry FilterByKeyBind { get; set; } public static ConfigEntry LinkedSearchKeyBind { get; set; } - public static ConfigEntry AddOfferContextMenu { get; set; } // Advanced public static ConfigEntry AddOfferKeyBind { get; set; } public static ConfigEntry SortingTableKeyBind { get; set; } public static ConfigEntry LimitNonstandardDrags { get; set; } // Advanced @@ -112,6 +119,8 @@ internal class Settings public static ConfigEntry AlwaysSwapMags { get; set; } public static ConfigEntry UnloadAmmoBoxInPlace { get; set; } // Advanced public static ConfigEntry SwapImpossibleContainers { get; set; } + public static ConfigEntry ModifyEquippedWeapons { get; set; } // Advanced + public static ConfigEntry ModifyRaidWeapons { get; set; } public static ConfigEntry ReorderGrids { get; set; } public static ConfigEntry SynchronizeStashScrolling { get; set; } public static ConfigEntry GreedyStackMove { get; set; } @@ -122,6 +131,7 @@ internal class Settings public static ConfigEntry AutoOpenSortingTable { get; set; } public static ConfigEntry DefaultSortingTableBind { get; set; } // Advanced public static ConfigEntry ContextMenuOnRight { get; set; } + public static ConfigEntry AddOfferContextMenu { get; set; } public static ConfigEntry ShowGPCurrency { get; set; } public static ConfigEntry ShowOutOfStockCheckbox { get; set; } public static ConfigEntry SortingTableButton { get; set; } @@ -424,15 +434,6 @@ internal class Settings null, new ConfigurationManagerAttributes { }))); - configEntries.Add(AddOfferContextMenu = config.Bind( - InputSection, - "Add Offer Context Menu", - true, - new ConfigDescription( - "Add a context menu to list the item on the flea market", - null, - new ConfigurationManagerAttributes { IsAdvanced = true }))); - configEntries.Add(AddOfferKeyBind = config.Bind( InputSection, "Linked Search Shortcut", @@ -569,6 +570,24 @@ internal class Settings null, new ConfigurationManagerAttributes { }))); + configEntries.Add(ModifyEquippedWeapons = config.Bind( + InventorySection, + "Modify Equipped Weapons", + true, + new ConfigDescription( + "Enable the modification of equipped weapons, including vital parts, out of raid", + null, + new ConfigurationManagerAttributes { IsAdvanced = true }))); + + configEntries.Add(ModifyRaidWeapons = config.Bind( + InventorySection, + "Modify Weapons In Raid", + ModRaidWeapon.Never, + new ConfigDescription( + "When to enable the modification of vital parts of unequipped weapons, in raid", + null, + new ConfigurationManagerAttributes { }))); + configEntries.Add(ReorderGrids = config.Bind( InventorySection, "Standardize Grid Order", @@ -659,6 +678,15 @@ internal class Settings null, new ConfigurationManagerAttributes { }))); + configEntries.Add(AddOfferContextMenu = config.Bind( + InputSection, + "Add Offer Context Menu", + true, + new ConfigDescription( + "Add a context menu to list the item on the flea market", + null, + new ConfigurationManagerAttributes { }))); + configEntries.Add(ShowGPCurrency = config.Bind( InventorySection, "Show GP Coins in Currency", @@ -916,7 +944,6 @@ internal class Settings RecalcOrder(configEntries); - MakeDependent(EnableMultiSelect, EnableMultiSelectInRaid); MakeDependent(EnableMultiSelect, ShowMultiSelectDebug, false); MakeDependent(EnableMultiSelect, EnableMultiClick); From 6144a11a9f8aa9288db07ecbbbec0d582bf81fbb Mon Sep 17 00:00:00 2001 From: Tyfon <29051038+tyfon7@users.noreply.github.com> Date: Thu, 22 Aug 2024 12:12:07 -0700 Subject: [PATCH 35/73] default ammo box unload in place off if fika present --- Settings.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Settings.cs b/Settings.cs index 6eeda98..77c03d9 100644 --- a/Settings.cs +++ b/Settings.cs @@ -1,4 +1,5 @@ -using BepInEx.Configuration; +using BepInEx.Bootstrap; +using BepInEx.Configuration; using System; using System.Collections.Generic; using System.ComponentModel; @@ -555,7 +556,7 @@ internal class Settings configEntries.Add(UnloadAmmoBoxInPlace = config.Bind( InventorySection, "Unload Ammo Boxes In-Place", - true, + !Chainloader.PluginInfos.ContainsKey("com.fika.core"), // default false if fika present, has issues with ground loot new ConfigDescription( "Whether to unload ammo boxes in-place, otherwise there needs to be free space somewhere", null, From 495922a8e1a687e7fdd821ab249753966fcc96a4 Mon Sep 17 00:00:00 2001 From: Tyfon <29051038+tyfon7@users.noreply.github.com> Date: Thu, 22 Aug 2024 12:13:04 -0700 Subject: [PATCH 36/73] rev version 2.4.2 --- UIFixes.csproj | 2 +- server/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/UIFixes.csproj b/UIFixes.csproj index 61168c9..91d8885 100644 --- a/UIFixes.csproj +++ b/UIFixes.csproj @@ -4,7 +4,7 @@ net471 Tyfon.UIFixes SPT UI Fixes - 2.4.1 + 2.4.2 true latest Debug;Release diff --git a/server/package.json b/server/package.json index 676e546..27843a6 100644 --- a/server/package.json +++ b/server/package.json @@ -1,6 +1,6 @@ { "name": "uifixes", - "version": "2.4.1", + "version": "2.4.2", "main": "src/mod.js", "license": "MIT", "author": "Tyfon", From 3f8ef956855d3d5e1b33182ea3591f088e3939c4 Mon Sep 17 00:00:00 2001 From: Tyfon <29051038+tyfon7@users.noreply.github.com> Date: Thu, 22 Aug 2024 12:18:54 -0700 Subject: [PATCH 37/73] Disable MergeConsumables compat change until that mod implements Rollback() --- Patches/FixTraderControllerSimulateFalsePatch.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Patches/FixTraderControllerSimulateFalsePatch.cs b/Patches/FixTraderControllerSimulateFalsePatch.cs index df3b855..4621bae 100644 --- a/Patches/FixTraderControllerSimulateFalsePatch.cs +++ b/Patches/FixTraderControllerSimulateFalsePatch.cs @@ -25,10 +25,14 @@ public class FixTraderControllerSimulateFalsePatch : ModulePatch ref ItemOperation __result, bool __runOriginal) { - if (!__runOriginal) - { - return __runOriginal; - } + // TODO: The following commented code is necessary for compatibility with any mod that prefix patches and returns false, + // specifically MergeConsumables. However, that mod currently doesn't implement Rollback() on its operations, + // which breaks multi-select and has very nasty side effects. + // Until that is fixed, leaving this as commented because while this breaks that mod, there are no game-breaking effects. + // if (!__runOriginal) + // { + // return __runOriginal; + // } TargetItemOperation opStruct; opStruct.targetItem = targetItem; From 3eea62ca06eeae9088d103f6d4b2799df059d9d5 Mon Sep 17 00:00:00 2001 From: Tyfon <29051038+tyfon7@users.noreply.github.com> Date: Thu, 22 Aug 2024 12:21:18 -0700 Subject: [PATCH 38/73] Update readme --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 1a27965..f03ad83 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ Existing SPT features made better #### Inventory +- ✨ Modify equipped weapons - Rebind Home/End, PageUp/PageDown to work like you would expect - Customizable mouse scrolling speed - Moving stacks into containers always moves entire stack @@ -80,6 +81,7 @@ Existing SPT features made better #### In raid - ✨ Quickbind tactical devices to control them individually +- ✨ Option to make unequipped weapons moddable in raid, optionally with multitool. - Reloading will swap magazines in-place, instead of dropping them on the ground when there's no room - Grenade quickbinds will transfer to the next grenade of the same type after throwing - Option to change the behavior of the grenade key from selecting a random grenade to a deterministic one From 303ac665b3f76d094f8edf58c7fa7282706bf646 Mon Sep 17 00:00:00 2001 From: Tyfon <29051038+tyfon7@users.noreply.github.com> Date: Thu, 22 Aug 2024 12:29:49 -0700 Subject: [PATCH 39/73] Disable add offer context menu for status reasons (level, banned, disabled) --- Patches/AddOfferContextMenuPatches.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Patches/AddOfferContextMenuPatches.cs b/Patches/AddOfferContextMenuPatches.cs index d66940b..09e3c0a 100644 --- a/Patches/AddOfferContextMenuPatches.cs +++ b/Patches/AddOfferContextMenuPatches.cs @@ -137,15 +137,23 @@ public static class AddOfferContextMenuPatches ISession session = PatchConstants.BackEndSession; RagFairClass ragfair = session.RagFair; + if (ragfair.Status != RagFairClass.ERagFairStatus.Available) + { + __result = new FailedResult(ragfair.GetFormattedStatusDescription()); + return; + } + if (ragfair.MyOffersCount >= ragfair.GetMaxOffersCount(ragfair.MyRating)) { __result = new FailedResult("ragfair/Reached maximum amount of offers"); + return; } RagfairOfferSellHelperClass ragfairHelper = new(session.Profile, session.Profile.Inventory.Stash.Grid); if (!ragfairHelper.method_4(___item_0, out string error)) { __result = new FailedResult(error); + return; } } } From b858d71436fbf3644d69551b587adc8ac6452135 Mon Sep 17 00:00:00 2001 From: Tyfon <29051038+tyfon7@users.noreply.github.com> Date: Thu, 22 Aug 2024 12:36:01 -0700 Subject: [PATCH 40/73] . --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f03ad83..e03a7a1 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,7 @@ Existing SPT features made better #### In raid - ✨ Quickbind tactical devices to control them individually -- ✨ Option to make unequipped weapons moddable in raid, optionally with multitool. +- ✨ Option to make unequipped weapons moddable in raid, optionally with multitool - Reloading will swap magazines in-place, instead of dropping them on the ground when there's no room - Grenade quickbinds will transfer to the next grenade of the same type after throwing - Option to change the behavior of the grenade key from selecting a random grenade to a deterministic one From 4a829c112d72f02f01366483a08beb7e365f0d06 Mon Sep 17 00:00:00 2001 From: Tyfon <29051038+tyfon7@users.noreply.github.com> Date: Fri, 23 Aug 2024 18:54:12 -0700 Subject: [PATCH 41/73] Fix Add Offer keybind setting --- Settings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Settings.cs b/Settings.cs index 77c03d9..e4ba108 100644 --- a/Settings.cs +++ b/Settings.cs @@ -437,7 +437,7 @@ internal class Settings configEntries.Add(AddOfferKeyBind = config.Bind( InputSection, - "Linked Search Shortcut", + "Add Offer Shortcut", new KeyboardShortcut(KeyCode.None), new ConfigDescription( "Keybind to list item on the flea market", From 135fb316e1712affa714a583d9841b9dc7d2a215 Mon Sep 17 00:00:00 2001 From: Tyfon <29051038+tyfon7@users.noreply.github.com> Date: Fri, 23 Aug 2024 19:19:21 -0700 Subject: [PATCH 42/73] Add Reload keybind --- Patches/ContextMenuShortcutPatches.cs | 5 +++++ Settings.cs | 10 ++++++++++ 2 files changed, 15 insertions(+) diff --git a/Patches/ContextMenuShortcutPatches.cs b/Patches/ContextMenuShortcutPatches.cs index e2de8f9..5171029 100644 --- a/Patches/ContextMenuShortcutPatches.cs +++ b/Patches/ContextMenuShortcutPatches.cs @@ -78,6 +78,11 @@ public static class ContextMenuShortcutPatches TryInteraction(__instance, itemContext, EItemInfoButton.UseAll, [EItemInfoButton.Use]); } + if (Settings.ReloadKeyBind.Value.IsDown()) + { + TryInteraction(__instance, itemContext, EItemInfoButton.Reload); + } + if (Settings.UnloadKeyBind.Value.IsDown()) { TryInteraction(__instance, itemContext, EItemInfoButton.Unload, [EItemInfoButton.UnloadAmmo]); diff --git a/Settings.cs b/Settings.cs index e4ba108..b3b5116 100644 --- a/Settings.cs +++ b/Settings.cs @@ -99,6 +99,7 @@ internal class Settings public static ConfigEntry TopUpKeyBind { get; set; } public static ConfigEntry UseKeyBind { get; set; } public static ConfigEntry UseAllKeyBind { get; set; } + public static ConfigEntry ReloadKeyBind { get; set; } public static ConfigEntry UnloadKeyBind { get; set; } public static ConfigEntry UnpackKeyBind { get; set; } public static ConfigEntry FilterByKeyBind { get; set; } @@ -399,6 +400,15 @@ internal class Settings null, new ConfigurationManagerAttributes { }))); + configEntries.Add(ReloadKeyBind = config.Bind( + InputSection, + "Reload Weapon Shortcut", + new KeyboardShortcut(KeyCode.R), + new ConfigDescription( + "Keybind to reload a weapon. Note that this is solely in the menus, and doesn't affect the normal reload key.", + null, + new ConfigurationManagerAttributes { }))); + configEntries.Add(UnloadKeyBind = config.Bind( InputSection, "Unload Mag/Ammo Shortcut", From ce6ea3fcc190f74bc6b5b7b8ac70ec7fe9753b2c Mon Sep 17 00:00:00 2001 From: Tyfon <29051038+tyfon7@users.noreply.github.com> Date: Sat, 24 Aug 2024 12:04:21 -0700 Subject: [PATCH 43/73] Handle parentless weapon (handbook) --- Patches/WeaponModdingPatches.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Patches/WeaponModdingPatches.cs b/Patches/WeaponModdingPatches.cs index 5f88d07..f7e5f4b 100644 --- a/Patches/WeaponModdingPatches.cs +++ b/Patches/WeaponModdingPatches.cs @@ -487,7 +487,7 @@ public static class WeaponModdingPatches } Item rootItem = itemAddress.GetRootItemNotEquipment(); - if (rootItem is not Weapon weapon) + if (rootItem is not Weapon weapon || weapon.CurrentAddress == null) { return true; } From d481368cceaa6d2d1da4170b594ce2dba12f21d7 Mon Sep 17 00:00:00 2001 From: Tyfon <29051038+tyfon7@users.noreply.github.com> Date: Thu, 29 Aug 2024 01:21:35 -0700 Subject: [PATCH 44/73] Block snap left/right when textbox is active --- Patches/ContextMenuShortcutPatches.cs | 4 +--- Patches/GridWindowButtonsPatch.cs | 5 +++++ Patches/HideoutSearchPatches.cs | 4 +--- Patches/ScrollPatches.cs | 3 +-- Plugin.cs | 8 ++++++++ 5 files changed, 16 insertions(+), 8 deletions(-) diff --git a/Patches/ContextMenuShortcutPatches.cs b/Patches/ContextMenuShortcutPatches.cs index 5171029..a926e84 100644 --- a/Patches/ContextMenuShortcutPatches.cs +++ b/Patches/ContextMenuShortcutPatches.cs @@ -46,9 +46,7 @@ public static class ContextMenuShortcutPatches return; } - if (!Settings.ItemContextBlocksTextInputs.Value && - EventSystem.current?.currentSelectedGameObject != null && - EventSystem.current.currentSelectedGameObject.GetComponent() != null) + if (!Settings.ItemContextBlocksTextInputs.Value && Plugin.TextboxActive()) { return; } diff --git a/Patches/GridWindowButtonsPatch.cs b/Patches/GridWindowButtonsPatch.cs index dc009ae..568a27f 100644 --- a/Patches/GridWindowButtonsPatch.cs +++ b/Patches/GridWindowButtonsPatch.cs @@ -73,6 +73,11 @@ public class GridWindowButtonsPatch : ModulePatch public void Update() { + if (Plugin.TextboxActive()) + { + return; + } + bool isTopWindow = window.transform.GetSiblingIndex() == window.transform.parent.childCount - 1; if (Settings.SnapLeftKeybind.Value.IsDown() && isTopWindow) { diff --git a/Patches/HideoutSearchPatches.cs b/Patches/HideoutSearchPatches.cs index 9dd0c8a..7e5b730 100644 --- a/Patches/HideoutSearchPatches.cs +++ b/Patches/HideoutSearchPatches.cs @@ -223,9 +223,7 @@ public static class HideoutSearchPatches [PatchPrefix] public static bool Prefix(ECommand command, ref InputNode.ETranslateResult __result) { - if (command == ECommand.Enter && - EventSystem.current?.currentSelectedGameObject != null && - EventSystem.current.currentSelectedGameObject.GetComponent() != null) + if (command == ECommand.Enter && Plugin.TextboxActive()) { __result = InputNode.ETranslateResult.Block; return false; diff --git a/Patches/ScrollPatches.cs b/Patches/ScrollPatches.cs index 3093562..cc0876d 100644 --- a/Patches/ScrollPatches.cs +++ b/Patches/ScrollPatches.cs @@ -35,8 +35,7 @@ public static class ScrollPatches private static bool HandleInput(ScrollRect scrollRect) { - if (EventSystem.current?.currentSelectedGameObject != null && - EventSystem.current.currentSelectedGameObject.GetComponent() != null) + if (Plugin.TextboxActive()) { return false; } diff --git a/Plugin.cs b/Plugin.cs index 71abfb2..4d84df8 100644 --- a/Plugin.cs +++ b/Plugin.cs @@ -2,6 +2,8 @@ using BepInEx.Bootstrap; using Comfort.Common; using EFT; +using TMPro; +using UnityEngine.EventSystems; namespace UIFixes; @@ -84,6 +86,12 @@ public class Plugin : BaseUnityPlugin return inRaid.HasValue && inRaid.Value; } + public static bool TextboxActive() + { + return EventSystem.current?.currentSelectedGameObject != null && + EventSystem.current.currentSelectedGameObject.GetComponent() != null; + } + private static bool? IsFikaPresent; public static bool FikaPresent() From a7071cd57232312da676300fda6574a90ab50734 Mon Sep 17 00:00:00 2001 From: Tyfon <29051038+tyfon7@users.noreply.github.com> Date: Fri, 30 Aug 2024 13:40:00 -0700 Subject: [PATCH 45/73] Re-enable MergeConsumables fix --- Patches/FixTraderControllerSimulateFalsePatch.cs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/Patches/FixTraderControllerSimulateFalsePatch.cs b/Patches/FixTraderControllerSimulateFalsePatch.cs index 4621bae..df3b855 100644 --- a/Patches/FixTraderControllerSimulateFalsePatch.cs +++ b/Patches/FixTraderControllerSimulateFalsePatch.cs @@ -25,14 +25,10 @@ public class FixTraderControllerSimulateFalsePatch : ModulePatch ref ItemOperation __result, bool __runOriginal) { - // TODO: The following commented code is necessary for compatibility with any mod that prefix patches and returns false, - // specifically MergeConsumables. However, that mod currently doesn't implement Rollback() on its operations, - // which breaks multi-select and has very nasty side effects. - // Until that is fixed, leaving this as commented because while this breaks that mod, there are no game-breaking effects. - // if (!__runOriginal) - // { - // return __runOriginal; - // } + if (!__runOriginal) + { + return __runOriginal; + } TargetItemOperation opStruct; opStruct.targetItem = targetItem; From d7338200217d7d094b445c030cd695cb2791b15f Mon Sep 17 00:00:00 2001 From: Tyfon <29051038+tyfon7@users.noreply.github.com> Date: Fri, 30 Aug 2024 13:41:43 -0700 Subject: [PATCH 46/73] Rev version, 2.4.3 --- UIFixes.csproj | 2 +- server/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/UIFixes.csproj b/UIFixes.csproj index 91d8885..ec540c5 100644 --- a/UIFixes.csproj +++ b/UIFixes.csproj @@ -4,7 +4,7 @@ net471 Tyfon.UIFixes SPT UI Fixes - 2.4.2 + 2.4.3 true latest Debug;Release diff --git a/server/package.json b/server/package.json index 27843a6..df88f0c 100644 --- a/server/package.json +++ b/server/package.json @@ -1,6 +1,6 @@ { "name": "uifixes", - "version": "2.4.2", + "version": "2.4.3", "main": "src/mod.js", "license": "MIT", "author": "Tyfon", From a61f0f81566dba95475c626e1260d69123f06fbf Mon Sep 17 00:00:00 2001 From: Tyfon <29051038+tyfon7@users.noreply.github.com> Date: Sat, 31 Aug 2024 10:18:21 -0700 Subject: [PATCH 47/73] More MergeConsumables compat - allow swap/stack --- Patches/FixTraderControllerSimulateFalsePatch.cs | 7 ++++++- Plugin.cs | 12 ++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/Patches/FixTraderControllerSimulateFalsePatch.cs b/Patches/FixTraderControllerSimulateFalsePatch.cs index df3b855..fbb6322 100644 --- a/Patches/FixTraderControllerSimulateFalsePatch.cs +++ b/Patches/FixTraderControllerSimulateFalsePatch.cs @@ -27,7 +27,12 @@ public class FixTraderControllerSimulateFalsePatch : ModulePatch { if (!__runOriginal) { - return __runOriginal; + // This is a little hairy, as *some* prefix didn't want to run. If MergeConsumables is present, assume it's that. + // If MC succeeded, bail out. If it failed, we might still want to swap + if (Plugin.MergeConsumablesPresent() && __result.Succeeded) + { + return __runOriginal; + } } TargetItemOperation opStruct; diff --git a/Plugin.cs b/Plugin.cs index 4d84df8..0806dac 100644 --- a/Plugin.cs +++ b/Plugin.cs @@ -103,4 +103,16 @@ public class Plugin : BaseUnityPlugin return IsFikaPresent.Value; } + + private static bool? IsMergeConsumablesPresent; + + public static bool MergeConsumablesPresent() + { + if (!IsMergeConsumablesPresent.HasValue) + { + IsMergeConsumablesPresent = Chainloader.PluginInfos.ContainsKey("com.lacyway.mc"); + } + + return IsMergeConsumablesPresent.Value; + } } From a81204b6ea3cc432dc62915900b53899bb70300d Mon Sep 17 00:00:00 2001 From: Tyfon <29051038+tyfon7@users.noreply.github.com> Date: Sat, 31 Aug 2024 10:54:34 -0700 Subject: [PATCH 48/73] Prioritize smaller slots --- Patches/ReorderGridsPatch.cs | 55 +++++++++++++++++++++++------------- Settings.cs | 12 ++++++++ 2 files changed, 47 insertions(+), 20 deletions(-) diff --git a/Patches/ReorderGridsPatch.cs b/Patches/ReorderGridsPatch.cs index 019d5f9..abd5901 100644 --- a/Patches/ReorderGridsPatch.cs +++ b/Patches/ReorderGridsPatch.cs @@ -64,6 +64,8 @@ public static class ReorderGridsPatches ____presetGridViews = orderedGridView; __instance.SetReordered(false); } + + GridMaps.Remove(compoundItem.TemplateId); } return; @@ -99,26 +101,9 @@ public static class ReorderGridsPatches } var pairs = compoundItem.Grids.Zip(____presetGridViews, (g, gv) => new KeyValuePair(g, gv)); + var sortedPairs = SortGrids(__instance, pairs); - RectTransform parentView = __instance.RectTransform(); - Vector2 parentPosition = parentView.pivot.y == 1 ? parentView.position : new Vector2(parentView.position.x, parentView.position.y + parentView.sizeDelta.y); - Vector2 gridSize = new(64f * parentView.lossyScale.x, 64f * parentView.lossyScale.y); - - var sorted = pairs.OrderBy(pair => - { - var grid = pair.Key; - var gridView = pair.Value; - - float xOffset = gridView.transform.position.x - parentPosition.x; - float yOffset = -(gridView.transform.position.y - parentPosition.y); // invert y since grid coords are upper-left origin - - int x = (int)Math.Round(xOffset / gridSize.x, MidpointRounding.AwayFromZero); - int y = (int)Math.Round(yOffset / gridSize.y, MidpointRounding.AwayFromZero); - - return y * 100 + x; - }); - - GridView[] orderedGridViews = sorted.Select(pair => pair.Value).ToArray(); + GridView[] orderedGridViews = sortedPairs.Select(pair => pair.Value).ToArray(); // Populate the gridmap if (!GridMaps.ContainsKey(compoundItem.TemplateId)) @@ -132,11 +117,41 @@ public static class ReorderGridsPatches GridMaps.Add(compoundItem.TemplateId, map); } - compoundItem.Grids = sorted.Select(pair => pair.Key).ToArray(); + compoundItem.Grids = sortedPairs.Select(pair => pair.Key).ToArray(); ____presetGridViews = orderedGridViews; compoundItem.SetReordered(true); __instance.SetReordered(true); } + + private static IOrderedEnumerable> SortGrids( + TemplatedGridsView __instance, + IEnumerable> pairs) + { + RectTransform parentView = __instance.RectTransform(); + Vector2 parentPosition = parentView.pivot.y == 1 ? parentView.position : new Vector2(parentView.position.x, parentView.position.y + parentView.sizeDelta.y); + Vector2 gridSize = new(64f * parentView.lossyScale.x, 64f * parentView.lossyScale.y); + + int calculateCoords(KeyValuePair pair) + { + var grid = pair.Key; + var gridView = pair.Value; + + float xOffset = gridView.transform.position.x - parentPosition.x; + float yOffset = -(gridView.transform.position.y - parentPosition.y); // invert y since grid coords are upper-left origin + + int x = (int)Math.Round(xOffset / gridSize.x, MidpointRounding.AwayFromZero); + int y = (int)Math.Round(yOffset / gridSize.y, MidpointRounding.AwayFromZero); + + return y * 100 + x; + } + + if (Settings.PrioritizeSmallerGrids.Value) + { + return pairs.OrderBy(pair => pair.Key.GridWidth.Value).ThenBy(pair => pair.Key.GridHeight.Value).ThenBy(calculateCoords); + } + + return pairs.OrderBy(calculateCoords); + } } } diff --git a/Settings.cs b/Settings.cs index b3b5116..ac77f1d 100644 --- a/Settings.cs +++ b/Settings.cs @@ -124,6 +124,7 @@ internal class Settings public static ConfigEntry ModifyEquippedWeapons { get; set; } // Advanced public static ConfigEntry ModifyRaidWeapons { get; set; } public static ConfigEntry ReorderGrids { get; set; } + public static ConfigEntry PrioritizeSmallerGrids { get; set; } public static ConfigEntry SynchronizeStashScrolling { get; set; } public static ConfigEntry GreedyStackMove { get; set; } public static ConfigEntry StackBeforeSort { get; set; } @@ -608,6 +609,15 @@ internal class Settings null, new ConfigurationManagerAttributes { }))); + configEntries.Add(PrioritizeSmallerGrids = config.Bind( + InventorySection, + "Prioritize Smaller Slots (requires restart)", + false, + new ConfigDescription( + "When adding items to containers with multiple slots, place the item in the smallest slot that can hold it, rather than just the first empty space. Requires Standardize Grid Order.", + null, + new ConfigurationManagerAttributes { }))); + configEntries.Add(SynchronizeStashScrolling = config.Bind( InventorySection, "Synchronize Stash Scroll Position", @@ -960,6 +970,8 @@ internal class Settings MakeDependent(EnableMultiSelect, EnableMultiClick); MakeExclusive(EnableMultiClick, AutoOpenSortingTable, false); + + MakeDependent(ReorderGrids, PrioritizeSmallerGrids, false); } private static void RecalcOrder(List configEntries) From 6e96461ad269434e4f1506d4a141882398d36b00 Mon Sep 17 00:00:00 2001 From: Tyfon <29051038+tyfon7@users.noreply.github.com> Date: Wed, 4 Sep 2024 02:28:51 -0700 Subject: [PATCH 49/73] Fix nullref when flea back button is disabled --- Patches/FleaPrevSearchPatches.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Patches/FleaPrevSearchPatches.cs b/Patches/FleaPrevSearchPatches.cs index baa031a..2cd0e40 100644 --- a/Patches/FleaPrevSearchPatches.cs +++ b/Patches/FleaPrevSearchPatches.cs @@ -327,7 +327,10 @@ public static class FleaPrevSearchPatches [PatchPostfix] public static void Postfix(EViewListType type) { - PreviousFilterButton.Instance?.gameObject.SetActive(type == EViewListType.AllOffers); + if (PreviousFilterButton.Instance != null) + { + PreviousFilterButton.Instance.gameObject.SetActive(type == EViewListType.AllOffers); + } } } @@ -369,7 +372,10 @@ public static class FleaPrevSearchPatches return; } - PreviousFilterButton.Instance.OnOffersLoaded(__instance); + if (PreviousFilterButton.Instance != null) + { + PreviousFilterButton.Instance.OnOffersLoaded(__instance); + } if (Settings.AutoExpandCategories.Value) { From f62d957059394c36c45412f1c06b5716c6334d61 Mon Sep 17 00:00:00 2001 From: Tyfon <29051038+tyfon7@users.noreply.github.com> Date: Wed, 4 Sep 2024 03:29:26 -0700 Subject: [PATCH 50/73] Add ctrl-F to highlight search; fix flea enter during filter not working --- Patches/FixFleaPatches.cs | 76 ++++++++++++++++++++++++++++++--- Patches/HideoutSearchPatches.cs | 4 +- SearchKeyListener.cs | 20 +++++++++ Settings.cs | 10 +++++ 4 files changed, 103 insertions(+), 7 deletions(-) create mode 100644 SearchKeyListener.cs diff --git a/Patches/FixFleaPatches.cs b/Patches/FixFleaPatches.cs index c2364cf..b178f3b 100644 --- a/Patches/FixFleaPatches.cs +++ b/Patches/FixFleaPatches.cs @@ -1,9 +1,11 @@ -using EFT.UI; +using EFT.HandBook; +using EFT.UI; using EFT.UI.Ragfair; using HarmonyLib; using SPT.Reflection.Patching; using System.Linq; using System.Reflection; +using System.Threading.Tasks; using TMPro; using UnityEngine; using UnityEngine.UI; @@ -12,6 +14,8 @@ namespace UIFixes; public static class FixFleaPatches { + private static Task SearchFilterTask; + public static void Enable() { // These are anal AF @@ -24,7 +28,10 @@ public static class FixFleaPatches new OfferItemFixMaskPatch().Enable(); new OfferViewTweaksPatch().Enable(); + new SearchFilterPatch().Enable(); new SearchPatch().Enable(); + new SearchKeyPatch().Enable(); + new SearchKeyHandbookPatch().Enable(); } public class DoNotToggleOnMouseOverPatch : ModulePatch @@ -125,6 +132,20 @@ public static class FixFleaPatches } } + public class SearchFilterPatch : ModulePatch + { + protected override MethodBase GetTargetMethod() + { + return AccessTools.Method(typeof(BrowseCategoriesPanel), nameof(BrowseCategoriesPanel.Filter)); + } + + [PatchPostfix] + public static void Postfix(Task __result) + { + SearchFilterTask = __result; + } + } + public class SearchPatch : ModulePatch { protected override MethodBase GetTargetMethod() @@ -145,19 +166,64 @@ public static class FixFleaPatches return true; } + if (SearchFilterTask != null && !SearchFilterTask.IsCompleted) + { + SearchFilterTask.ContinueWith(t => DoSearch(__instance), TaskScheduler.FromCurrentSynchronizationContext()); + return true; + } + if (__instance.FilteredNodes.Values.Sum(node => node.Count) > 0) { return true; } - __instance.Ragfair.CancellableFilters.Clear(); + DoSearch(__instance); + return false; + } - FilterRule filterRule = __instance.Ragfair.method_3(EViewListType.AllOffers); + private static void DoSearch(RagfairCategoriesPanel panel) + { + if (panel.FilteredNodes.Values.Sum(node => node.Count) > 0) + { + return; + } + + panel.Ragfair.CancellableFilters.Clear(); + + FilterRule filterRule = panel.Ragfair.method_3(EViewListType.AllOffers); filterRule.HandbookId = string.Empty; - __instance.Ragfair.AddSearchesInRule(filterRule, true); + panel.Ragfair.AddSearchesInRule(filterRule, true); + } + } - return false; + public class SearchKeyPatch : ModulePatch + { + protected override MethodBase GetTargetMethod() + { + return AccessTools.Method(typeof(BrowseCategoriesPanel), nameof(BrowseCategoriesPanel.Awake)); + } + + [PatchPostfix] + public static void Postfix(TMP_InputField ___SearchInputField) + { + ___SearchInputField.GetOrAddComponent(); + } + } + + // Have to target HandbookCategoriesPanel specifically because even though it inherits from BrowseCategoriesPanel, + // BSG couldn't be bothered to call base.Awake() + public class SearchKeyHandbookPatch : ModulePatch + { + protected override MethodBase GetTargetMethod() + { + return AccessTools.Method(typeof(HandbookCategoriesPanel), nameof(HandbookCategoriesPanel.Awake)); + } + + [PatchPostfix] + public static void Postfix(TMP_InputField ___SearchInputField) + { + ___SearchInputField.GetOrAddComponent(); } } diff --git a/Patches/HideoutSearchPatches.cs b/Patches/HideoutSearchPatches.cs index 7e5b730..1198a00 100644 --- a/Patches/HideoutSearchPatches.cs +++ b/Patches/HideoutSearchPatches.cs @@ -7,8 +7,6 @@ using System; using System.Collections.Generic; using System.Linq; using System.Reflection; -using TMPro; -using UnityEngine.EventSystems; using UnityEngine.UI; namespace UIFixes; @@ -108,6 +106,8 @@ public static class HideoutSearchPatches areaScreenSubstrate.method_8(); } + ____searchInputField.GetOrAddComponent(); + ____searchInputField.ActivateInputField(); ____searchInputField.Select(); } diff --git a/SearchKeyListener.cs b/SearchKeyListener.cs new file mode 100644 index 0000000..ad73078 --- /dev/null +++ b/SearchKeyListener.cs @@ -0,0 +1,20 @@ +using TMPro; +using UnityEngine; + +namespace UIFixes; + +public class SearchKeyListener : MonoBehaviour +{ + public void Update() + { + if (Settings.SearchKeyBind.Value.IsDown()) + { + TMP_InputField searchField = GetComponent(); + if (searchField != null) + { + searchField.ActivateInputField(); + searchField.Select(); + } + } + } +} \ No newline at end of file diff --git a/Settings.cs b/Settings.cs index ac77f1d..18b9f6a 100644 --- a/Settings.cs +++ b/Settings.cs @@ -106,6 +106,7 @@ internal class Settings public static ConfigEntry LinkedSearchKeyBind { get; set; } public static ConfigEntry AddOfferKeyBind { get; set; } public static ConfigEntry SortingTableKeyBind { get; set; } + public static ConfigEntry SearchKeyBind { get; set; } public static ConfigEntry LimitNonstandardDrags { get; set; } // Advanced public static ConfigEntry ItemContextBlocksTextInputs { get; set; } // Advanced @@ -464,6 +465,15 @@ internal class Settings null, new ConfigurationManagerAttributes { }))); + configEntries.Add(SearchKeyBind = config.Bind( + InputSection, + "Highlight Search Box", + new KeyboardShortcut(KeyCode.F, KeyCode.LeftControl), + new ConfigDescription( + "Keybind to highlight the search box in hideout crafting, handbook, and flea market", + null, + new ConfigurationManagerAttributes { }))); + configEntries.Add(LimitNonstandardDrags = config.Bind( InputSection, "Limit Nonstandard Drags", From 64b7e546c1f65632d18e31c56a9a0e27eac1b259 Mon Sep 17 00:00:00 2001 From: Tyfon <29051038+tyfon7@users.noreply.github.com> Date: Wed, 4 Sep 2024 11:01:51 -0700 Subject: [PATCH 51/73] rev version, 2.4.4 --- UIFixes.csproj | 2 +- server/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/UIFixes.csproj b/UIFixes.csproj index ec540c5..0edcb05 100644 --- a/UIFixes.csproj +++ b/UIFixes.csproj @@ -4,7 +4,7 @@ net471 Tyfon.UIFixes SPT UI Fixes - 2.4.3 + 2.4.4 true latest Debug;Release diff --git a/server/package.json b/server/package.json index df88f0c..63f4fd2 100644 --- a/server/package.json +++ b/server/package.json @@ -1,6 +1,6 @@ { "name": "uifixes", - "version": "2.4.3", + "version": "2.4.4", "main": "src/mod.js", "license": "MIT", "author": "Tyfon", From 983deffea5361f611c3d76ad2d970e703c822704 Mon Sep 17 00:00:00 2001 From: Tyfon <29051038+tyfon7@users.noreply.github.com> Date: Tue, 10 Sep 2024 10:12:35 -0700 Subject: [PATCH 52/73] Fix add offer keybind --- Patches/AddOfferContextMenuPatches.cs | 16 +++++++--------- Patches/ContextMenuPatches.cs | 5 +++++ Patches/ContextMenuShortcutPatches.cs | 5 +++++ 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/Patches/AddOfferContextMenuPatches.cs b/Patches/AddOfferContextMenuPatches.cs index 09e3c0a..d7c6e62 100644 --- a/Patches/AddOfferContextMenuPatches.cs +++ b/Patches/AddOfferContextMenuPatches.cs @@ -14,8 +14,6 @@ namespace UIFixes; public static class AddOfferContextMenuPatches { - private const EItemInfoButton AddOfferInfoButton = (EItemInfoButton)77; - private static Item AddOfferItem = null; public static void Enable() @@ -44,7 +42,7 @@ public static class AddOfferContextMenuPatches if (Settings.AddOfferContextMenu.Value) { var list = __result.ToList(); - list.Insert(list.IndexOf(EItemInfoButton.Tag), AddOfferInfoButton); + list.Insert(list.IndexOf(EItemInfoButton.Tag), EItemInfoButtonExt.AddOffer); __result = list; } } @@ -63,7 +61,7 @@ public static class AddOfferContextMenuPatches if (Settings.AddOfferContextMenu.Value) { var list = __result.ToList(); - list.Insert(list.IndexOf(EItemInfoButton.Tag), AddOfferInfoButton); + list.Insert(list.IndexOf(EItemInfoButton.Tag), EItemInfoButtonExt.AddOffer); __result = list; } } @@ -83,13 +81,13 @@ public static class AddOfferContextMenuPatches { names ??= new Dictionary() { - { AddOfferInfoButton, "ragfair/OFFER ADD" } + { EItemInfoButtonExt.AddOffer, "ragfair/OFFER ADD" } }; FleaSprite ??= Resources.FindObjectsOfTypeAll().Single(s => s.name == "icon_flea_market"); icons ??= new Dictionary() { - { AddOfferInfoButton, FleaSprite } + { EItemInfoButtonExt.AddOffer, FleaSprite } }; } } @@ -104,7 +102,7 @@ public static class AddOfferContextMenuPatches [PatchPrefix] public static bool Prefix(EItemInfoButton button, ref bool __result) { - if (button != AddOfferInfoButton) + if (button != EItemInfoButtonExt.AddOffer) { return true; } @@ -130,7 +128,7 @@ public static class AddOfferContextMenuPatches [PatchPostfix] public static void Postfix(EItemInfoButton button, ref IResult __result, Item ___item_0) { - if (button != AddOfferInfoButton) + if (button != EItemInfoButtonExt.AddOffer) { return; } @@ -168,7 +166,7 @@ public static class AddOfferContextMenuPatches [PatchPrefix] public static bool Prefix(ItemInfoInteractionsAbstractClass __instance, EItemInfoButton interaction, Item ___item_0) { - if (interaction != AddOfferInfoButton) + if (interaction != EItemInfoButtonExt.AddOffer) { return true; } diff --git a/Patches/ContextMenuPatches.cs b/Patches/ContextMenuPatches.cs index 2347dfc..0a4efad 100644 --- a/Patches/ContextMenuPatches.cs +++ b/Patches/ContextMenuPatches.cs @@ -13,6 +13,11 @@ using UnityEngine; namespace UIFixes; +public static class EItemInfoButtonExt +{ + public const EItemInfoButton AddOffer = (EItemInfoButton)77; +} + public static class ContextMenuPatches { private static InsuranceInteractions CurrentInsuranceInteractions = null; diff --git a/Patches/ContextMenuShortcutPatches.cs b/Patches/ContextMenuShortcutPatches.cs index a926e84..825d508 100644 --- a/Patches/ContextMenuShortcutPatches.cs +++ b/Patches/ContextMenuShortcutPatches.cs @@ -112,6 +112,11 @@ public static class ContextMenuShortcutPatches [EItemInfoButton.Fold, EItemInfoButton.Unfold, EItemInfoButton.TurnOn, EItemInfoButton.TurnOff, EItemInfoButton.CheckMagazine]); } + if (Settings.AddOfferKeyBind.Value.IsDown()) + { + TryInteraction(__instance, itemContext, EItemInfoButtonExt.AddOffer); + } + Interactions = null; } From 1e2abaab649cc6b7874d9847f300d44143f418d7 Mon Sep 17 00:00:00 2001 From: Tyfon <29051038+tyfon7@users.noreply.github.com> Date: Tue, 10 Sep 2024 10:13:06 -0700 Subject: [PATCH 53/73] shorten inventory operation queue time --- Patches/OperationQueuePatch.cs | 24 ++++++++++++++++++++++++ Plugin.cs | 1 + Settings.cs | 10 ++++++++++ 3 files changed, 35 insertions(+) create mode 100644 Patches/OperationQueuePatch.cs diff --git a/Patches/OperationQueuePatch.cs b/Patches/OperationQueuePatch.cs new file mode 100644 index 0000000..bf50b87 --- /dev/null +++ b/Patches/OperationQueuePatch.cs @@ -0,0 +1,24 @@ +using System.Reflection; +using HarmonyLib; +using SPT.Reflection.Patching; +using UnityEngine; + +namespace UIFixes; + +public class OperationQueuePatch : ModulePatch +{ + protected override MethodBase GetTargetMethod() + { + return AccessTools.Method(typeof(ProfileEndpointFactoryAbstractClass), nameof(ProfileEndpointFactoryAbstractClass.TrySendCommands)); + } + + [PatchPrefix] + public static void Prefix(ref float ___float_0) + { + // The target method is hardcoded to 60 seconds. Rather than try to change that, just lie to it about when it last sent + if (Time.realtimeSinceStartup - ___float_0 > Settings.OperationQueueTime.Value) + { + ___float_0 = 0; + } + } +} \ No newline at end of file diff --git a/Plugin.cs b/Plugin.cs index 0806dac..5ad6d83 100644 --- a/Plugin.cs +++ b/Plugin.cs @@ -78,6 +78,7 @@ public class Plugin : BaseUnityPlugin TagPatches.Enable(); TacticalBindsPatches.Enable(); AddOfferContextMenuPatches.Enable(); + new OperationQueuePatch().Enable(); } public static bool InRaid() diff --git a/Settings.cs b/Settings.cs index 18b9f6a..39faa1d 100644 --- a/Settings.cs +++ b/Settings.cs @@ -80,6 +80,7 @@ internal class Settings public static ConfigEntry AutoSwitchTrading { get; set; } public static ConfigEntry ClickOutOfDialogs { get; set; } // Advanced public static ConfigEntry RestoreAsyncScrollPositions { get; set; } // Advanced + public static ConfigEntry OperationQueueTime { get; set; } // Advanced // Input public static ConfigEntry ToggleOrHoldAim { get; set; } @@ -248,6 +249,15 @@ internal class Settings null, new ConfigurationManagerAttributes { IsAdvanced = true }))); + configEntries.Add(OperationQueueTime = config.Bind( + GeneralSection, + "Server Operation Queue Time", + 15, + new ConfigDescription( + "The client waits this long to batch inventory operations before sending them to the server. Vanilla Tarkov is 60 (!)", + new AcceptableValueRange(0, 60), + new ConfigurationManagerAttributes { IsAdvanced = true }))); + // Input configEntries.Add(ToggleOrHoldAim = config.Bind( InputSection, From a9db6ce524bbc52a8620a077fd091fc1f1b01ee6 Mon Sep 17 00:00:00 2001 From: Tyfon <29051038+tyfon7@users.noreply.github.com> Date: Thu, 12 Sep 2024 02:43:25 -0700 Subject: [PATCH 54/73] Fix null ref in reload-in-place --- Patches/ReloadInPlacePatches.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Patches/ReloadInPlacePatches.cs b/Patches/ReloadInPlacePatches.cs index a1a7b73..364bb4b 100644 --- a/Patches/ReloadInPlacePatches.cs +++ b/Patches/ReloadInPlacePatches.cs @@ -59,7 +59,7 @@ public static class ReloadInPlacePatches [PatchPostfix] public static void Postfix(MagazineClass __result) { - if (IsReloading) + if (__result != null && IsReloading) { FoundMagazine = __result; FoundAddress = FoundMagazine.Parent; From 0cc567a70418a75e395452dc19df6dfe3bb48564 Mon Sep 17 00:00:00 2001 From: Tyfon <29051038+tyfon7@users.noreply.github.com> Date: Sun, 15 Sep 2024 14:38:22 -0700 Subject: [PATCH 55/73] Try to fix null ref in trader auto switch --- Patches/TradingAutoSwitchPatches.cs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/Patches/TradingAutoSwitchPatches.cs b/Patches/TradingAutoSwitchPatches.cs index 7926174..c840046 100644 --- a/Patches/TradingAutoSwitchPatches.cs +++ b/Patches/TradingAutoSwitchPatches.cs @@ -58,29 +58,34 @@ public static class TradingAutoSwitchPatches TradingItemView __instance, PointerEventData.InputButton button, bool doubleClick, - ETradingItemViewType ___etradingItemViewType_0, bool ___bool_8) + ETradingItemViewType ___etradingItemViewType_0, + bool ___bool_8) { - if (!Settings.AutoSwitchTrading.Value) + if (!Settings.AutoSwitchTrading.Value || SellTab == null || BuyTab == null) + { + return true; + } + + var assortmentController = __instance.R().TraderAssortmentController; + if (assortmentController == null) { return true; } - var tradingItemView = __instance.R(); if (button != PointerEventData.InputButton.Left || ___etradingItemViewType_0 == ETradingItemViewType.TradingTable) { return true; } bool ctrlPressed = Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl); - if (!ctrlPressed && doubleClick) { return true; } - if (!___bool_8 && ctrlPressed && tradingItemView.TraderAssortmentController.QuickFindTradingAppropriatePlace(__instance.Item, null)) + if (!___bool_8 && ctrlPressed && assortmentController.QuickFindTradingAppropriatePlace(__instance.Item, null)) { - __instance.ItemContext.CloseDependentWindows(); + __instance.ItemContext?.CloseDependentWindows(); __instance.HideTooltip(); Singleton.Instance.PlayItemSound(__instance.Item.ItemSound, EInventorySoundType.pickup, false); @@ -91,7 +96,7 @@ public static class TradingAutoSwitchPatches if (___bool_8) { - tradingItemView.TraderAssortmentController.SelectItem(__instance.Item); + assortmentController.SelectItem(__instance.Item); BuyTab.OnPointerClick(null); From f3062b2d405cce2bf16f899972dac7beed547294 Mon Sep 17 00:00:00 2001 From: Tyfon <29051038+tyfon7@users.noreply.github.com> Date: Sun, 15 Sep 2024 15:45:39 -0700 Subject: [PATCH 56/73] Swap magazines when dragging mag over weapon --- GlobalUsings.cs | 1 + Patches/SwapPatches.cs | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/GlobalUsings.cs b/GlobalUsings.cs index c8d0597..443870f 100644 --- a/GlobalUsings.cs +++ b/GlobalUsings.cs @@ -42,6 +42,7 @@ global using MoveSameSpaceError = InteractionsHandlerClass.GClass3353; global using NotModdableInRaidError = GClass3321; global using MultitoolNeededError = GClass3322; global using ModVitalPartInRaidError = GClass3323; +global using SlotNotEmptyError = EFT.InventoryLogic.Slot.GClass3339; // Operations global using ItemOperation = GStruct413; diff --git a/Patches/SwapPatches.cs b/Patches/SwapPatches.cs index af16c2d..9869e1e 100644 --- a/Patches/SwapPatches.cs +++ b/Patches/SwapPatches.cs @@ -43,6 +43,7 @@ public static class SwapPatches new DetectGridHighlightPrecheckPatch().Enable(); new DetectSlotHighlightPrecheckPatch().Enable(); new SlotCanAcceptSwapPatch().Enable(); + new WeaponApplyPatch().Enable(); new DetectFilterForSwapPatch().Enable(); new FixNoGridErrorPatch().Enable(); new SwapOperationRaiseEventsPatch().Enable(); @@ -408,6 +409,39 @@ public static class SwapPatches } } + public class WeaponApplyPatch : ModulePatch + { + protected override MethodBase GetTargetMethod() + { + return AccessTools.Method(typeof(Weapon), nameof(Weapon.Apply)); + } + + // Allow dragging magazines onto weapons and do a mag swap + [PatchPostfix] + public static void Postfix(Weapon __instance, TraderControllerClass itemController, Item item, bool simulate, ref ItemOperation __result) + { + if (!Settings.SwapItems.Value || MultiSelect.Active) + { + return; + } + + // Check if the source container is a non-interactable GridView. Specifically for StashSearch, but may exist in other scenarios? + if (SourceContainer != null && SourceContainer is GridView && new R.GridView(SourceContainer).NonInteractable) + { + return; + } + + if (__result.Succeeded || item is not MagazineClass || __result.Error is not SlotNotEmptyError) + { + return; + } + + Slot magazineSlot = __instance.GetMagazineSlot(); + + __result = InteractionsHandlerClass.Swap(item, magazineSlot.ContainedItem.Parent, magazineSlot.ContainedItem, item.Parent, itemController, simulate); + } + } + // The patched method here is called when iterating over all slots to highlight ones that the dragged item can interact with // Since swap has no special highlight, I just skip the patch here (minor perf savings, plus makes debugging a million times easier) public class DetectGridHighlightPrecheckPatch : ModulePatch From 9295d85b134f84acf0dcde1db2c5f714c8599acf Mon Sep 17 00:00:00 2001 From: Tyfon <29051038+tyfon7@users.noreply.github.com> Date: Sun, 15 Sep 2024 17:12:47 -0700 Subject: [PATCH 57/73] adjust multiselect clickable detection; make modify equipped weapons not advanced --- Multiselect/DrawMultiSelect.cs | 38 ++++++++++++++++++++++++---------- Patches/SwapPatches.cs | 2 +- Settings.cs | 4 ++-- 3 files changed, 30 insertions(+), 14 deletions(-) diff --git a/Multiselect/DrawMultiSelect.cs b/Multiselect/DrawMultiSelect.cs index eaefc31..bb3852e 100644 --- a/Multiselect/DrawMultiSelect.cs +++ b/Multiselect/DrawMultiSelect.cs @@ -188,23 +188,39 @@ public class DrawMultiSelect : MonoBehaviour }; List results = []; + preloaderRaycaster.Raycast(eventData, results); // preload objects are on top, so check that first localRaycaster.Raycast(eventData, results); - preloaderRaycaster.Raycast(eventData, results); - foreach (GameObject gameObject in results.Select(r => r.gameObject)) + GameObject gameObject = results.FirstOrDefault().gameObject; + if (gameObject == null) { - var draggables = gameObject.GetComponents() - .Where(c => c is IDragHandler || c is IBeginDragHandler || c is TextMeshProUGUI) // tmp_inputfield is draggable, but textmesh isn't so explicitly include - .Where(c => c is not ScrollRectNoDrag) // this disables scrolling, it doesn't add it - .Where(c => c.name != "Inner"); // there's a random DragTrigger sitting in ItemInfoWindows + return false; + } - var clickables = gameObject.GetComponents() - .Where(c => c is IPointerClickHandler || c is IPointerDownHandler || c is IPointerUpHandler); + var draggables = gameObject.GetComponentsInParent() + .Where(c => c is IDragHandler || c is IBeginDragHandler || c is TextMeshProUGUI) // tmp_inputfield is draggable, but textmesh isn't so explicitly include + .Where(c => c is not ScrollRectNoDrag) // this disables scrolling, it doesn't add it + .Where(c => c.name != "Inner"); // there's a random DragTrigger sitting in ItemInfoWindows - if (draggables.Any() || clickables.Any()) + var clickables = gameObject.GetComponentsInParent() + .Where(c => c is IPointerClickHandler || c is IPointerDownHandler || c is IPointerUpHandler) + .Where(c => c is not EmptySlotMenuTrigger); // ignore empty slots that are right-clickable due to UIFixes + + // Windows are clickable to focus them, but that shouldn't block selection + var windows = clickables + .Where(c => c is UIInputNode) // Windows<>'s parent, cheap check + .Where(c => { - return true; - } + // Most window types implement IPointerClickHandler and inherit directly from Window<> + Type baseType = c.GetType().BaseType; + return baseType != null && baseType.IsGenericType && baseType.GetGenericTypeDefinition() == typeof(Window<>); + }); + + clickables = clickables.Except(windows); + + if (draggables.Any() || clickables.Any()) + { + return true; } return false; diff --git a/Patches/SwapPatches.cs b/Patches/SwapPatches.cs index 9869e1e..87b07d0 100644 --- a/Patches/SwapPatches.cs +++ b/Patches/SwapPatches.cs @@ -146,7 +146,7 @@ public static class SwapPatches { protected override MethodBase GetTargetMethod() { - return AccessTools.Method(typeof(ItemView), nameof(ItemView.OnDrag)); + return AccessTools.Method(typeof(ItemView), nameof(ItemView.OnBeginDrag)); } [PatchPrefix] diff --git a/Settings.cs b/Settings.cs index 39faa1d..36f2a7f 100644 --- a/Settings.cs +++ b/Settings.cs @@ -123,7 +123,7 @@ internal class Settings public static ConfigEntry AlwaysSwapMags { get; set; } public static ConfigEntry UnloadAmmoBoxInPlace { get; set; } // Advanced public static ConfigEntry SwapImpossibleContainers { get; set; } - public static ConfigEntry ModifyEquippedWeapons { get; set; } // Advanced + public static ConfigEntry ModifyEquippedWeapons { get; set; } public static ConfigEntry ModifyRaidWeapons { get; set; } public static ConfigEntry ReorderGrids { get; set; } public static ConfigEntry PrioritizeSmallerGrids { get; set; } @@ -609,7 +609,7 @@ internal class Settings new ConfigDescription( "Enable the modification of equipped weapons, including vital parts, out of raid", null, - new ConfigurationManagerAttributes { IsAdvanced = true }))); + new ConfigurationManagerAttributes { }))); configEntries.Add(ModifyRaidWeapons = config.Bind( InventorySection, From 69f250263c7b3adf273d966cde8f2e4cffdedd85 Mon Sep 17 00:00:00 2001 From: Tyfon <29051038+tyfon7@users.noreply.github.com> Date: Sun, 15 Sep 2024 17:17:57 -0700 Subject: [PATCH 58/73] rev version 2.4.5 --- UIFixes.csproj | 2 +- server/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/UIFixes.csproj b/UIFixes.csproj index 0edcb05..e36208e 100644 --- a/UIFixes.csproj +++ b/UIFixes.csproj @@ -4,7 +4,7 @@ net471 Tyfon.UIFixes SPT UI Fixes - 2.4.4 + 2.4.5 true latest Debug;Release diff --git a/server/package.json b/server/package.json index 63f4fd2..24f0f1e 100644 --- a/server/package.json +++ b/server/package.json @@ -1,6 +1,6 @@ { "name": "uifixes", - "version": "2.4.4", + "version": "2.4.5", "main": "src/mod.js", "license": "MIT", "author": "Tyfon", From 28143b07beb59d6860bfee0efab28a24cd7be937 Mon Sep 17 00:00:00 2001 From: Tyfon <29051038+tyfon7@users.noreply.github.com> Date: Mon, 16 Sep 2024 00:32:22 -0700 Subject: [PATCH 59/73] mousewheel split/use dialogs --- Patches/SliderPatch.cs | 73 ++++++++++++++++++++++++++++++++++++++++++ Plugin.cs | 1 + 2 files changed, 74 insertions(+) create mode 100644 Patches/SliderPatch.cs diff --git a/Patches/SliderPatch.cs b/Patches/SliderPatch.cs new file mode 100644 index 0000000..8953d19 --- /dev/null +++ b/Patches/SliderPatch.cs @@ -0,0 +1,73 @@ +using EFT.UI; +using HarmonyLib; +using SPT.Reflection.Patching; +using System.Reflection; +using UnityEngine; +using UnityEngine.UI; + +namespace UIFixes; + +public static class SliderPatches +{ + public static void Enable() + { + new IntSliderPatch().Enable(); + new StepSliderPatch().Enable(); + } + + public class IntSliderPatch : ModulePatch + { + protected override MethodBase GetTargetMethod() + { + return AccessTools.Method(typeof(IntSlider), nameof(IntSlider.Awake)); + } + + [PatchPostfix] + public static void Postfix(Slider ____slider) + { + ____slider.GetOrAddComponent().Init(____slider); + } + } + + public class StepSliderPatch : ModulePatch + { + protected override MethodBase GetTargetMethod() + { + return AccessTools.Method(typeof(StepSlider), nameof(StepSlider.Awake)); + } + + [PatchPostfix] + public static void Postfix(Slider ____slider) + { + ____slider.GetOrAddComponent().Init(____slider); + } + } + + public class SliderMouseListener : MonoBehaviour + { + private Slider slider; + + public void Init(Slider slider) + { + this.slider = slider; + } + + public void Update() + { + if (slider == null) + { + return; + } + + if (Input.mouseScrollDelta.y > float.Epsilon) + { + slider.value = Mathf.Min(slider.value + 1, slider.maxValue); + + } + else if (Input.mouseScrollDelta.y < -float.Epsilon) + { + slider.value = Mathf.Max(slider.value - 1, slider.minValue); + } + } + } +} \ No newline at end of file diff --git a/Plugin.cs b/Plugin.cs index 5ad6d83..ca3f3db 100644 --- a/Plugin.cs +++ b/Plugin.cs @@ -79,6 +79,7 @@ public class Plugin : BaseUnityPlugin TacticalBindsPatches.Enable(); AddOfferContextMenuPatches.Enable(); new OperationQueuePatch().Enable(); + SliderPatches.Enable(); } public static bool InRaid() From 8dc7c775b78289509239134c6ab145eeac732741 Mon Sep 17 00:00:00 2001 From: Tyfon <29051038+tyfon7@users.noreply.github.com> Date: Thu, 19 Sep 2024 11:40:58 -0700 Subject: [PATCH 60/73] Extra null check in multiselect ShowPreview --- Patches/MultiSelectPatches.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Patches/MultiSelectPatches.cs b/Patches/MultiSelectPatches.cs index c9f4a1e..961f8f5 100644 --- a/Patches/MultiSelectPatches.cs +++ b/Patches/MultiSelectPatches.cs @@ -1390,6 +1390,11 @@ public static class MultiSelectPatches return; } + if (gridAddress == null) + { + return; + } + if (gridAddress.Grid != gridView.Grid) { GridView otherGridView = gridView.transform.parent.GetComponentsInChildren().FirstOrDefault(gv => gv.Grid == gridAddress.Grid); From 418cbedf5b1263915254543492a7b9a813290dc0 Mon Sep 17 00:00:00 2001 From: Tyfon <29051038+tyfon7@users.noreply.github.com> Date: Thu, 19 Sep 2024 12:54:13 -0700 Subject: [PATCH 61/73] Skip tool return for tools in root stash --- server/src/mod.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/server/src/mod.ts b/server/src/mod.ts index c4dfa83..5907ee0 100644 --- a/server/src/mod.ts +++ b/server/src/mod.ts @@ -74,6 +74,15 @@ class UIFixes implements IPreSptLoadMod { const originalTool = pmcData.Inventory.items.find( x => x._id === requestTools[i].id ); + + // If the tool is in the stash itself, skip it. Same check as InventoryHelper.isItemInStash + if ( + originalTool.parentId === pmcData.Inventory.stash && + originalTool.slotId === "hideout" + ) { + continue; + } + tools[i]["uifixes.returnTo"] = [originalTool.parentId, originalTool.slotId]; } } From 2ff3fba9bcff7797dbf866006b9c02a496eba35a Mon Sep 17 00:00:00 2001 From: Tyfon <29051038+tyfon7@users.noreply.github.com> Date: Fri, 20 Sep 2024 11:45:29 -0700 Subject: [PATCH 62/73] Initial interop --- Multiselect/MultiSelectController.cs | 31 ++++++++ Multiselect/MultiSelectInterop.cs | 115 +++++++++++++++++++++++++++ UIFixes.csproj | 2 +- server/package.json | 2 +- 4 files changed, 148 insertions(+), 2 deletions(-) create mode 100644 Multiselect/MultiSelectController.cs create mode 100644 Multiselect/MultiSelectInterop.cs diff --git a/Multiselect/MultiSelectController.cs b/Multiselect/MultiSelectController.cs new file mode 100644 index 0000000..c1b05fc --- /dev/null +++ b/Multiselect/MultiSelectController.cs @@ -0,0 +1,31 @@ +using EFT.InventoryLogic; +using EFT.UI; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace UIFixes; + +// This class exists to create a layer between MultiSelectInterop and the MultiSelect implementation. +public static class MultiSelectController +{ + public static int GetCount() + { + return MultiSelect.Count; + } + + public static IEnumerable GetItems() + { + return MultiSelect.SortedItemContexts().Select(ic => ic.Item); + } + + public static Task Apply(Func func, ItemUiContext itemUiContext = null) + { + itemUiContext ??= ItemUiContext.Instance; + var taskSerializer = itemUiContext.gameObject.AddComponent(); + return taskSerializer.Initialize(GetItems(), func); + } +} + +public class ItemTaskSerializer : TaskSerializer { } diff --git a/Multiselect/MultiSelectInterop.cs b/Multiselect/MultiSelectInterop.cs new file mode 100644 index 0000000..c374ce1 --- /dev/null +++ b/Multiselect/MultiSelectInterop.cs @@ -0,0 +1,115 @@ +using BepInEx; +using BepInEx.Bootstrap; +using EFT.InventoryLogic; +using EFT.UI; +using HarmonyLib; +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Threading.Tasks; + +namespace UIFixesInterop; + +/// +/// Provides access to UI Fixes' multiselect functionality. +/// +internal class MultiSelect +{ + private static readonly Version RequiredVersion = new(2, 5); + + private static bool? UIFixesLoaded; + + private static Type MultiSelectType; + private static MethodInfo GetCountMethod; + private static MethodInfo GetItemsMethod; + private static MethodInfo ApplyMethod; + + /// Count represents the number of items in the current selection, 0 if UI Fixes is not present. + public int Count + { + get + { + if (!Loaded()) + { + return 0; + } + + return (int)GetCountMethod.Invoke(null, []); + } + } + + /// Items is an enumerable list of items in the current selection, empty if UI Fixes is not present + public IEnumerable Items + { + get + { + if (!Loaded()) + { + return []; + } + + return (IEnumerable)GetItemsMethod.Invoke(null, []); + } + } + + /// + /// This method takes an Action and calls it *sequentially* on each item in the current selection. + /// Will no-op if UI Fixes is not present. + /// + /// The action to call on each item. + /// Optional ItemUiContext; will use ItemUiContext.Instance if not provided. + public void Apply(Action action, ItemUiContext itemUiContext = null) + { + if (!Loaded()) + { + return; + } + + Func func = item => + { + action(item); + return Task.CompletedTask; + }; + + ApplyMethod.Invoke(null, [func, itemUiContext]); + } + + /// + /// This method takes an Func that returns a Task and calls it *sequentially* on each item in the current selection. + /// Will return a completed task immediately if UI Fixes is not present. + /// + /// The function to call on each item + /// Optional ItemUiContext; will use ItemUiContext.Instance if not provided. + /// A Task that will complete when all the function calls are complete. + public Task Apply(Func func, ItemUiContext itemUiContext = null) + { + if (!Loaded()) + { + return Task.CompletedTask; + } + + return (Task)ApplyMethod.Invoke(null, [func, itemUiContext]); + } + + private bool Loaded() + { + if (!UIFixesLoaded.HasValue) + { + bool present = Chainloader.PluginInfos.TryGetValue("Tyfon.UIFixes", out PluginInfo pluginInfo); + UIFixesLoaded = present && pluginInfo.Metadata.Version >= RequiredVersion; + + if (UIFixesLoaded.Value) + { + MultiSelectType = Type.GetType("UIFixes.MultiSelectController, UIFixes"); + if (MultiSelectType != null) + { + GetCountMethod = AccessTools.Method(MultiSelectType, "GetCount"); + GetItemsMethod = AccessTools.Method(MultiSelectType, "GetItems"); + ApplyMethod = AccessTools.Method(MultiSelectType, "Apply"); + } + } + } + + return UIFixesLoaded.Value; + } +} \ No newline at end of file diff --git a/UIFixes.csproj b/UIFixes.csproj index e36208e..e0fa01e 100644 --- a/UIFixes.csproj +++ b/UIFixes.csproj @@ -4,7 +4,7 @@ net471 Tyfon.UIFixes SPT UI Fixes - 2.4.5 + 2.5.0 true latest Debug;Release diff --git a/server/package.json b/server/package.json index 24f0f1e..a51f217 100644 --- a/server/package.json +++ b/server/package.json @@ -1,6 +1,6 @@ { "name": "uifixes", - "version": "2.4.5", + "version": "2.5.0", "main": "src/mod.js", "license": "MIT", "author": "Tyfon", From 698cd61a88b3967c240ad44c796526dde5ebc04b Mon Sep 17 00:00:00 2001 From: Tyfon <29051038+tyfon7@users.noreply.github.com> Date: Fri, 20 Sep 2024 11:47:09 -0700 Subject: [PATCH 63/73] Move source into src --- .../ConfigurationManagerAttributes.cs | 0 {ContextMenus => src/ContextMenus}/EmptySlotMenu.cs | 0 {ContextMenus => src/ContextMenus}/EmptySlotMenuTrigger.cs | 0 {ContextMenus => src/ContextMenus}/InsuranceInteractions.cs | 0 {ContextMenus => src/ContextMenus}/RepairInteractions.cs | 0 Extensions.cs => src/Extensions.cs | 0 ExtraProperties.cs => src/ExtraProperties.cs | 0 GlobalUsings.cs => src/GlobalUsings.cs | 0 {Multiselect => src/Multiselect}/DrawMultiSelect.cs | 0 {Multiselect => src/Multiselect}/MultiGrid.cs | 0 {Multiselect => src/Multiselect}/MultiSelect.cs | 0 {Multiselect => src/Multiselect}/MultiSelectController.cs | 0 {Multiselect => src/Multiselect}/MultiSelectDebug.cs | 0 {Multiselect => src/Multiselect}/MultiSelectInterop.cs | 0 {Patches => src/Patches}/AddOfferClickablePricesPatches.cs | 0 {Patches => src/Patches}/AddOfferContextMenuPatches.cs | 0 {Patches => src/Patches}/AddOfferRememberAutoselectPatches.cs | 0 {Patches => src/Patches}/AimToggleHoldPatches.cs | 0 {Patches => src/Patches}/AssortUnlocksPatch.cs | 0 {Patches => src/Patches}/AutofillQuestItemsPatch.cs | 0 {Patches => src/Patches}/BarterOfferPatches.cs | 0 {Patches => src/Patches}/ConfirmationDialogKeysPatches.cs | 0 {Patches => src/Patches}/ContextMenuPatches.cs | 0 {Patches => src/Patches}/ContextMenuShortcutPatches.cs | 0 {Patches => src/Patches}/FilterOutOfStockPatches.cs | 0 {Patches => src/Patches}/FixFleaPatches.cs | 0 {Patches => src/Patches}/FixMailRecieveAllPatch.cs | 0 {Patches => src/Patches}/FixTooltipPatches.cs | 0 {Patches => src/Patches}/FixTraderControllerSimulateFalsePatch.cs | 0 {Patches => src/Patches}/FixUnloadLastBulletPatch.cs | 0 {Patches => src/Patches}/FleaPrevSearchPatches.cs | 0 {Patches => src/Patches}/FleaSlotSearchPatches.cs | 0 {Patches => src/Patches}/FocusFleaOfferNumberPatches.cs | 0 {Patches => src/Patches}/FocusTradeQuantityPatch.cs | 0 {Patches => src/Patches}/GPCoinPatches.cs | 0 {Patches => src/Patches}/GridWindowButtonsPatch.cs | 0 {Patches => src/Patches}/HideoutCameraPatches.cs | 0 {Patches => src/Patches}/HideoutLevelPatches.cs | 0 {Patches => src/Patches}/HideoutSearchPatches.cs | 0 {Patches => src/Patches}/InspectWindowResizePatches.cs | 0 {Patches => src/Patches}/InspectWindowStatsPatches.cs | 0 {Patches => src/Patches}/KeepMessagesOpenPatches.cs | 0 {Patches => src/Patches}/KeepOfferWindowOpenPatches.cs | 0 {Patches => src/Patches}/KeepWindowsOnScreenPatches.cs | 0 {Patches => src/Patches}/LimitDragPatches.cs | 0 {Patches => src/Patches}/LoadAmmoInRaidPatches.cs | 0 {Patches => src/Patches}/LoadMagPresetsPatch.cs | 0 {Patches => src/Patches}/LoadMultipleMagazinesPatches.cs | 0 {Patches => src/Patches}/MoveSortingTablePatches.cs | 0 {Patches => src/Patches}/MoveTaskbarPatch.cs | 0 {Patches => src/Patches}/MultiSelectPatches.cs | 0 {Patches => src/Patches}/NoRandomGrenadesPatch.cs | 0 {Patches => src/Patches}/OpenSortingTablePatches.cs | 0 {Patches => src/Patches}/OperationQueuePatch.cs | 0 {Patches => src/Patches}/PutToolsBackPatch.cs | 0 {Patches => src/Patches}/QuickAccessPanelPatches.cs | 0 {Patches => src/Patches}/RebindGrenadesPatch.cs | 0 {Patches => src/Patches}/ReloadInPlacePatches.cs | 0 {Patches => src/Patches}/RememberRepairerPatches.cs | 0 {Patches => src/Patches}/RemoveDoorActionsPatch.cs | 0 {Patches => src/Patches}/ReorderGridsPatch.cs | 0 {Patches => src/Patches}/ScrollPatches.cs | 0 {Patches => src/Patches}/SliderPatch.cs | 0 {Patches => src/Patches}/SortPatches.cs | 0 {Patches => src/Patches}/StackFirItemsPatches.cs | 0 {Patches => src/Patches}/StackMoveGreedyPatches.cs | 0 {Patches => src/Patches}/SwapPatches.cs | 0 {Patches => src/Patches}/SyncScrollPositionPatches.cs | 0 {Patches => src/Patches}/TacticalBindsPatches.cs | 0 {Patches => src/Patches}/TagPatches.cs | 0 {Patches => src/Patches}/TradingAutoSwitchPatches.cs | 0 {Patches => src/Patches}/TransferConfirmPatch.cs | 0 {Patches => src/Patches}/UnloadAmmoPatches.cs | 0 {Patches => src/Patches}/UnlockCursorPatch.cs | 0 {Patches => src/Patches}/WeaponModdingPatches.cs | 0 {Patches => src/Patches}/WeaponPresetConfirmPatches.cs | 0 {Patches => src/Patches}/WeaponZoomPatches.cs | 0 Plugin.cs => src/Plugin.cs | 0 R.cs => src/R.cs | 0 SearchKeyListener.cs => src/SearchKeyListener.cs | 0 Settings.cs => src/Settings.cs | 0 Sorter.cs => src/Sorter.cs | 0 TaskSerializer.cs => src/TaskSerializer.cs | 0 ToggleHold.cs => src/ToggleHold.cs | 0 84 files changed, 0 insertions(+), 0 deletions(-) rename ConfigurationManagerAttributes.cs => src/ConfigurationManagerAttributes.cs (100%) rename {ContextMenus => src/ContextMenus}/EmptySlotMenu.cs (100%) rename {ContextMenus => src/ContextMenus}/EmptySlotMenuTrigger.cs (100%) rename {ContextMenus => src/ContextMenus}/InsuranceInteractions.cs (100%) rename {ContextMenus => src/ContextMenus}/RepairInteractions.cs (100%) rename Extensions.cs => src/Extensions.cs (100%) rename ExtraProperties.cs => src/ExtraProperties.cs (100%) rename GlobalUsings.cs => src/GlobalUsings.cs (100%) rename {Multiselect => src/Multiselect}/DrawMultiSelect.cs (100%) rename {Multiselect => src/Multiselect}/MultiGrid.cs (100%) rename {Multiselect => src/Multiselect}/MultiSelect.cs (100%) rename {Multiselect => src/Multiselect}/MultiSelectController.cs (100%) rename {Multiselect => src/Multiselect}/MultiSelectDebug.cs (100%) rename {Multiselect => src/Multiselect}/MultiSelectInterop.cs (100%) rename {Patches => src/Patches}/AddOfferClickablePricesPatches.cs (100%) rename {Patches => src/Patches}/AddOfferContextMenuPatches.cs (100%) rename {Patches => src/Patches}/AddOfferRememberAutoselectPatches.cs (100%) rename {Patches => src/Patches}/AimToggleHoldPatches.cs (100%) rename {Patches => src/Patches}/AssortUnlocksPatch.cs (100%) rename {Patches => src/Patches}/AutofillQuestItemsPatch.cs (100%) rename {Patches => src/Patches}/BarterOfferPatches.cs (100%) rename {Patches => src/Patches}/ConfirmationDialogKeysPatches.cs (100%) rename {Patches => src/Patches}/ContextMenuPatches.cs (100%) rename {Patches => src/Patches}/ContextMenuShortcutPatches.cs (100%) rename {Patches => src/Patches}/FilterOutOfStockPatches.cs (100%) rename {Patches => src/Patches}/FixFleaPatches.cs (100%) rename {Patches => src/Patches}/FixMailRecieveAllPatch.cs (100%) rename {Patches => src/Patches}/FixTooltipPatches.cs (100%) rename {Patches => src/Patches}/FixTraderControllerSimulateFalsePatch.cs (100%) rename {Patches => src/Patches}/FixUnloadLastBulletPatch.cs (100%) rename {Patches => src/Patches}/FleaPrevSearchPatches.cs (100%) rename {Patches => src/Patches}/FleaSlotSearchPatches.cs (100%) rename {Patches => src/Patches}/FocusFleaOfferNumberPatches.cs (100%) rename {Patches => src/Patches}/FocusTradeQuantityPatch.cs (100%) rename {Patches => src/Patches}/GPCoinPatches.cs (100%) rename {Patches => src/Patches}/GridWindowButtonsPatch.cs (100%) rename {Patches => src/Patches}/HideoutCameraPatches.cs (100%) rename {Patches => src/Patches}/HideoutLevelPatches.cs (100%) rename {Patches => src/Patches}/HideoutSearchPatches.cs (100%) rename {Patches => src/Patches}/InspectWindowResizePatches.cs (100%) rename {Patches => src/Patches}/InspectWindowStatsPatches.cs (100%) rename {Patches => src/Patches}/KeepMessagesOpenPatches.cs (100%) rename {Patches => src/Patches}/KeepOfferWindowOpenPatches.cs (100%) rename {Patches => src/Patches}/KeepWindowsOnScreenPatches.cs (100%) rename {Patches => src/Patches}/LimitDragPatches.cs (100%) rename {Patches => src/Patches}/LoadAmmoInRaidPatches.cs (100%) rename {Patches => src/Patches}/LoadMagPresetsPatch.cs (100%) rename {Patches => src/Patches}/LoadMultipleMagazinesPatches.cs (100%) rename {Patches => src/Patches}/MoveSortingTablePatches.cs (100%) rename {Patches => src/Patches}/MoveTaskbarPatch.cs (100%) rename {Patches => src/Patches}/MultiSelectPatches.cs (100%) rename {Patches => src/Patches}/NoRandomGrenadesPatch.cs (100%) rename {Patches => src/Patches}/OpenSortingTablePatches.cs (100%) rename {Patches => src/Patches}/OperationQueuePatch.cs (100%) rename {Patches => src/Patches}/PutToolsBackPatch.cs (100%) rename {Patches => src/Patches}/QuickAccessPanelPatches.cs (100%) rename {Patches => src/Patches}/RebindGrenadesPatch.cs (100%) rename {Patches => src/Patches}/ReloadInPlacePatches.cs (100%) rename {Patches => src/Patches}/RememberRepairerPatches.cs (100%) rename {Patches => src/Patches}/RemoveDoorActionsPatch.cs (100%) rename {Patches => src/Patches}/ReorderGridsPatch.cs (100%) rename {Patches => src/Patches}/ScrollPatches.cs (100%) rename {Patches => src/Patches}/SliderPatch.cs (100%) rename {Patches => src/Patches}/SortPatches.cs (100%) rename {Patches => src/Patches}/StackFirItemsPatches.cs (100%) rename {Patches => src/Patches}/StackMoveGreedyPatches.cs (100%) rename {Patches => src/Patches}/SwapPatches.cs (100%) rename {Patches => src/Patches}/SyncScrollPositionPatches.cs (100%) rename {Patches => src/Patches}/TacticalBindsPatches.cs (100%) rename {Patches => src/Patches}/TagPatches.cs (100%) rename {Patches => src/Patches}/TradingAutoSwitchPatches.cs (100%) rename {Patches => src/Patches}/TransferConfirmPatch.cs (100%) rename {Patches => src/Patches}/UnloadAmmoPatches.cs (100%) rename {Patches => src/Patches}/UnlockCursorPatch.cs (100%) rename {Patches => src/Patches}/WeaponModdingPatches.cs (100%) rename {Patches => src/Patches}/WeaponPresetConfirmPatches.cs (100%) rename {Patches => src/Patches}/WeaponZoomPatches.cs (100%) rename Plugin.cs => src/Plugin.cs (100%) rename R.cs => src/R.cs (100%) rename SearchKeyListener.cs => src/SearchKeyListener.cs (100%) rename Settings.cs => src/Settings.cs (100%) rename Sorter.cs => src/Sorter.cs (100%) rename TaskSerializer.cs => src/TaskSerializer.cs (100%) rename ToggleHold.cs => src/ToggleHold.cs (100%) diff --git a/ConfigurationManagerAttributes.cs b/src/ConfigurationManagerAttributes.cs similarity index 100% rename from ConfigurationManagerAttributes.cs rename to src/ConfigurationManagerAttributes.cs diff --git a/ContextMenus/EmptySlotMenu.cs b/src/ContextMenus/EmptySlotMenu.cs similarity index 100% rename from ContextMenus/EmptySlotMenu.cs rename to src/ContextMenus/EmptySlotMenu.cs diff --git a/ContextMenus/EmptySlotMenuTrigger.cs b/src/ContextMenus/EmptySlotMenuTrigger.cs similarity index 100% rename from ContextMenus/EmptySlotMenuTrigger.cs rename to src/ContextMenus/EmptySlotMenuTrigger.cs diff --git a/ContextMenus/InsuranceInteractions.cs b/src/ContextMenus/InsuranceInteractions.cs similarity index 100% rename from ContextMenus/InsuranceInteractions.cs rename to src/ContextMenus/InsuranceInteractions.cs diff --git a/ContextMenus/RepairInteractions.cs b/src/ContextMenus/RepairInteractions.cs similarity index 100% rename from ContextMenus/RepairInteractions.cs rename to src/ContextMenus/RepairInteractions.cs diff --git a/Extensions.cs b/src/Extensions.cs similarity index 100% rename from Extensions.cs rename to src/Extensions.cs diff --git a/ExtraProperties.cs b/src/ExtraProperties.cs similarity index 100% rename from ExtraProperties.cs rename to src/ExtraProperties.cs diff --git a/GlobalUsings.cs b/src/GlobalUsings.cs similarity index 100% rename from GlobalUsings.cs rename to src/GlobalUsings.cs diff --git a/Multiselect/DrawMultiSelect.cs b/src/Multiselect/DrawMultiSelect.cs similarity index 100% rename from Multiselect/DrawMultiSelect.cs rename to src/Multiselect/DrawMultiSelect.cs diff --git a/Multiselect/MultiGrid.cs b/src/Multiselect/MultiGrid.cs similarity index 100% rename from Multiselect/MultiGrid.cs rename to src/Multiselect/MultiGrid.cs diff --git a/Multiselect/MultiSelect.cs b/src/Multiselect/MultiSelect.cs similarity index 100% rename from Multiselect/MultiSelect.cs rename to src/Multiselect/MultiSelect.cs diff --git a/Multiselect/MultiSelectController.cs b/src/Multiselect/MultiSelectController.cs similarity index 100% rename from Multiselect/MultiSelectController.cs rename to src/Multiselect/MultiSelectController.cs diff --git a/Multiselect/MultiSelectDebug.cs b/src/Multiselect/MultiSelectDebug.cs similarity index 100% rename from Multiselect/MultiSelectDebug.cs rename to src/Multiselect/MultiSelectDebug.cs diff --git a/Multiselect/MultiSelectInterop.cs b/src/Multiselect/MultiSelectInterop.cs similarity index 100% rename from Multiselect/MultiSelectInterop.cs rename to src/Multiselect/MultiSelectInterop.cs diff --git a/Patches/AddOfferClickablePricesPatches.cs b/src/Patches/AddOfferClickablePricesPatches.cs similarity index 100% rename from Patches/AddOfferClickablePricesPatches.cs rename to src/Patches/AddOfferClickablePricesPatches.cs diff --git a/Patches/AddOfferContextMenuPatches.cs b/src/Patches/AddOfferContextMenuPatches.cs similarity index 100% rename from Patches/AddOfferContextMenuPatches.cs rename to src/Patches/AddOfferContextMenuPatches.cs diff --git a/Patches/AddOfferRememberAutoselectPatches.cs b/src/Patches/AddOfferRememberAutoselectPatches.cs similarity index 100% rename from Patches/AddOfferRememberAutoselectPatches.cs rename to src/Patches/AddOfferRememberAutoselectPatches.cs diff --git a/Patches/AimToggleHoldPatches.cs b/src/Patches/AimToggleHoldPatches.cs similarity index 100% rename from Patches/AimToggleHoldPatches.cs rename to src/Patches/AimToggleHoldPatches.cs diff --git a/Patches/AssortUnlocksPatch.cs b/src/Patches/AssortUnlocksPatch.cs similarity index 100% rename from Patches/AssortUnlocksPatch.cs rename to src/Patches/AssortUnlocksPatch.cs diff --git a/Patches/AutofillQuestItemsPatch.cs b/src/Patches/AutofillQuestItemsPatch.cs similarity index 100% rename from Patches/AutofillQuestItemsPatch.cs rename to src/Patches/AutofillQuestItemsPatch.cs diff --git a/Patches/BarterOfferPatches.cs b/src/Patches/BarterOfferPatches.cs similarity index 100% rename from Patches/BarterOfferPatches.cs rename to src/Patches/BarterOfferPatches.cs diff --git a/Patches/ConfirmationDialogKeysPatches.cs b/src/Patches/ConfirmationDialogKeysPatches.cs similarity index 100% rename from Patches/ConfirmationDialogKeysPatches.cs rename to src/Patches/ConfirmationDialogKeysPatches.cs diff --git a/Patches/ContextMenuPatches.cs b/src/Patches/ContextMenuPatches.cs similarity index 100% rename from Patches/ContextMenuPatches.cs rename to src/Patches/ContextMenuPatches.cs diff --git a/Patches/ContextMenuShortcutPatches.cs b/src/Patches/ContextMenuShortcutPatches.cs similarity index 100% rename from Patches/ContextMenuShortcutPatches.cs rename to src/Patches/ContextMenuShortcutPatches.cs diff --git a/Patches/FilterOutOfStockPatches.cs b/src/Patches/FilterOutOfStockPatches.cs similarity index 100% rename from Patches/FilterOutOfStockPatches.cs rename to src/Patches/FilterOutOfStockPatches.cs diff --git a/Patches/FixFleaPatches.cs b/src/Patches/FixFleaPatches.cs similarity index 100% rename from Patches/FixFleaPatches.cs rename to src/Patches/FixFleaPatches.cs diff --git a/Patches/FixMailRecieveAllPatch.cs b/src/Patches/FixMailRecieveAllPatch.cs similarity index 100% rename from Patches/FixMailRecieveAllPatch.cs rename to src/Patches/FixMailRecieveAllPatch.cs diff --git a/Patches/FixTooltipPatches.cs b/src/Patches/FixTooltipPatches.cs similarity index 100% rename from Patches/FixTooltipPatches.cs rename to src/Patches/FixTooltipPatches.cs diff --git a/Patches/FixTraderControllerSimulateFalsePatch.cs b/src/Patches/FixTraderControllerSimulateFalsePatch.cs similarity index 100% rename from Patches/FixTraderControllerSimulateFalsePatch.cs rename to src/Patches/FixTraderControllerSimulateFalsePatch.cs diff --git a/Patches/FixUnloadLastBulletPatch.cs b/src/Patches/FixUnloadLastBulletPatch.cs similarity index 100% rename from Patches/FixUnloadLastBulletPatch.cs rename to src/Patches/FixUnloadLastBulletPatch.cs diff --git a/Patches/FleaPrevSearchPatches.cs b/src/Patches/FleaPrevSearchPatches.cs similarity index 100% rename from Patches/FleaPrevSearchPatches.cs rename to src/Patches/FleaPrevSearchPatches.cs diff --git a/Patches/FleaSlotSearchPatches.cs b/src/Patches/FleaSlotSearchPatches.cs similarity index 100% rename from Patches/FleaSlotSearchPatches.cs rename to src/Patches/FleaSlotSearchPatches.cs diff --git a/Patches/FocusFleaOfferNumberPatches.cs b/src/Patches/FocusFleaOfferNumberPatches.cs similarity index 100% rename from Patches/FocusFleaOfferNumberPatches.cs rename to src/Patches/FocusFleaOfferNumberPatches.cs diff --git a/Patches/FocusTradeQuantityPatch.cs b/src/Patches/FocusTradeQuantityPatch.cs similarity index 100% rename from Patches/FocusTradeQuantityPatch.cs rename to src/Patches/FocusTradeQuantityPatch.cs diff --git a/Patches/GPCoinPatches.cs b/src/Patches/GPCoinPatches.cs similarity index 100% rename from Patches/GPCoinPatches.cs rename to src/Patches/GPCoinPatches.cs diff --git a/Patches/GridWindowButtonsPatch.cs b/src/Patches/GridWindowButtonsPatch.cs similarity index 100% rename from Patches/GridWindowButtonsPatch.cs rename to src/Patches/GridWindowButtonsPatch.cs diff --git a/Patches/HideoutCameraPatches.cs b/src/Patches/HideoutCameraPatches.cs similarity index 100% rename from Patches/HideoutCameraPatches.cs rename to src/Patches/HideoutCameraPatches.cs diff --git a/Patches/HideoutLevelPatches.cs b/src/Patches/HideoutLevelPatches.cs similarity index 100% rename from Patches/HideoutLevelPatches.cs rename to src/Patches/HideoutLevelPatches.cs diff --git a/Patches/HideoutSearchPatches.cs b/src/Patches/HideoutSearchPatches.cs similarity index 100% rename from Patches/HideoutSearchPatches.cs rename to src/Patches/HideoutSearchPatches.cs diff --git a/Patches/InspectWindowResizePatches.cs b/src/Patches/InspectWindowResizePatches.cs similarity index 100% rename from Patches/InspectWindowResizePatches.cs rename to src/Patches/InspectWindowResizePatches.cs diff --git a/Patches/InspectWindowStatsPatches.cs b/src/Patches/InspectWindowStatsPatches.cs similarity index 100% rename from Patches/InspectWindowStatsPatches.cs rename to src/Patches/InspectWindowStatsPatches.cs diff --git a/Patches/KeepMessagesOpenPatches.cs b/src/Patches/KeepMessagesOpenPatches.cs similarity index 100% rename from Patches/KeepMessagesOpenPatches.cs rename to src/Patches/KeepMessagesOpenPatches.cs diff --git a/Patches/KeepOfferWindowOpenPatches.cs b/src/Patches/KeepOfferWindowOpenPatches.cs similarity index 100% rename from Patches/KeepOfferWindowOpenPatches.cs rename to src/Patches/KeepOfferWindowOpenPatches.cs diff --git a/Patches/KeepWindowsOnScreenPatches.cs b/src/Patches/KeepWindowsOnScreenPatches.cs similarity index 100% rename from Patches/KeepWindowsOnScreenPatches.cs rename to src/Patches/KeepWindowsOnScreenPatches.cs diff --git a/Patches/LimitDragPatches.cs b/src/Patches/LimitDragPatches.cs similarity index 100% rename from Patches/LimitDragPatches.cs rename to src/Patches/LimitDragPatches.cs diff --git a/Patches/LoadAmmoInRaidPatches.cs b/src/Patches/LoadAmmoInRaidPatches.cs similarity index 100% rename from Patches/LoadAmmoInRaidPatches.cs rename to src/Patches/LoadAmmoInRaidPatches.cs diff --git a/Patches/LoadMagPresetsPatch.cs b/src/Patches/LoadMagPresetsPatch.cs similarity index 100% rename from Patches/LoadMagPresetsPatch.cs rename to src/Patches/LoadMagPresetsPatch.cs diff --git a/Patches/LoadMultipleMagazinesPatches.cs b/src/Patches/LoadMultipleMagazinesPatches.cs similarity index 100% rename from Patches/LoadMultipleMagazinesPatches.cs rename to src/Patches/LoadMultipleMagazinesPatches.cs diff --git a/Patches/MoveSortingTablePatches.cs b/src/Patches/MoveSortingTablePatches.cs similarity index 100% rename from Patches/MoveSortingTablePatches.cs rename to src/Patches/MoveSortingTablePatches.cs diff --git a/Patches/MoveTaskbarPatch.cs b/src/Patches/MoveTaskbarPatch.cs similarity index 100% rename from Patches/MoveTaskbarPatch.cs rename to src/Patches/MoveTaskbarPatch.cs diff --git a/Patches/MultiSelectPatches.cs b/src/Patches/MultiSelectPatches.cs similarity index 100% rename from Patches/MultiSelectPatches.cs rename to src/Patches/MultiSelectPatches.cs diff --git a/Patches/NoRandomGrenadesPatch.cs b/src/Patches/NoRandomGrenadesPatch.cs similarity index 100% rename from Patches/NoRandomGrenadesPatch.cs rename to src/Patches/NoRandomGrenadesPatch.cs diff --git a/Patches/OpenSortingTablePatches.cs b/src/Patches/OpenSortingTablePatches.cs similarity index 100% rename from Patches/OpenSortingTablePatches.cs rename to src/Patches/OpenSortingTablePatches.cs diff --git a/Patches/OperationQueuePatch.cs b/src/Patches/OperationQueuePatch.cs similarity index 100% rename from Patches/OperationQueuePatch.cs rename to src/Patches/OperationQueuePatch.cs diff --git a/Patches/PutToolsBackPatch.cs b/src/Patches/PutToolsBackPatch.cs similarity index 100% rename from Patches/PutToolsBackPatch.cs rename to src/Patches/PutToolsBackPatch.cs diff --git a/Patches/QuickAccessPanelPatches.cs b/src/Patches/QuickAccessPanelPatches.cs similarity index 100% rename from Patches/QuickAccessPanelPatches.cs rename to src/Patches/QuickAccessPanelPatches.cs diff --git a/Patches/RebindGrenadesPatch.cs b/src/Patches/RebindGrenadesPatch.cs similarity index 100% rename from Patches/RebindGrenadesPatch.cs rename to src/Patches/RebindGrenadesPatch.cs diff --git a/Patches/ReloadInPlacePatches.cs b/src/Patches/ReloadInPlacePatches.cs similarity index 100% rename from Patches/ReloadInPlacePatches.cs rename to src/Patches/ReloadInPlacePatches.cs diff --git a/Patches/RememberRepairerPatches.cs b/src/Patches/RememberRepairerPatches.cs similarity index 100% rename from Patches/RememberRepairerPatches.cs rename to src/Patches/RememberRepairerPatches.cs diff --git a/Patches/RemoveDoorActionsPatch.cs b/src/Patches/RemoveDoorActionsPatch.cs similarity index 100% rename from Patches/RemoveDoorActionsPatch.cs rename to src/Patches/RemoveDoorActionsPatch.cs diff --git a/Patches/ReorderGridsPatch.cs b/src/Patches/ReorderGridsPatch.cs similarity index 100% rename from Patches/ReorderGridsPatch.cs rename to src/Patches/ReorderGridsPatch.cs diff --git a/Patches/ScrollPatches.cs b/src/Patches/ScrollPatches.cs similarity index 100% rename from Patches/ScrollPatches.cs rename to src/Patches/ScrollPatches.cs diff --git a/Patches/SliderPatch.cs b/src/Patches/SliderPatch.cs similarity index 100% rename from Patches/SliderPatch.cs rename to src/Patches/SliderPatch.cs diff --git a/Patches/SortPatches.cs b/src/Patches/SortPatches.cs similarity index 100% rename from Patches/SortPatches.cs rename to src/Patches/SortPatches.cs diff --git a/Patches/StackFirItemsPatches.cs b/src/Patches/StackFirItemsPatches.cs similarity index 100% rename from Patches/StackFirItemsPatches.cs rename to src/Patches/StackFirItemsPatches.cs diff --git a/Patches/StackMoveGreedyPatches.cs b/src/Patches/StackMoveGreedyPatches.cs similarity index 100% rename from Patches/StackMoveGreedyPatches.cs rename to src/Patches/StackMoveGreedyPatches.cs diff --git a/Patches/SwapPatches.cs b/src/Patches/SwapPatches.cs similarity index 100% rename from Patches/SwapPatches.cs rename to src/Patches/SwapPatches.cs diff --git a/Patches/SyncScrollPositionPatches.cs b/src/Patches/SyncScrollPositionPatches.cs similarity index 100% rename from Patches/SyncScrollPositionPatches.cs rename to src/Patches/SyncScrollPositionPatches.cs diff --git a/Patches/TacticalBindsPatches.cs b/src/Patches/TacticalBindsPatches.cs similarity index 100% rename from Patches/TacticalBindsPatches.cs rename to src/Patches/TacticalBindsPatches.cs diff --git a/Patches/TagPatches.cs b/src/Patches/TagPatches.cs similarity index 100% rename from Patches/TagPatches.cs rename to src/Patches/TagPatches.cs diff --git a/Patches/TradingAutoSwitchPatches.cs b/src/Patches/TradingAutoSwitchPatches.cs similarity index 100% rename from Patches/TradingAutoSwitchPatches.cs rename to src/Patches/TradingAutoSwitchPatches.cs diff --git a/Patches/TransferConfirmPatch.cs b/src/Patches/TransferConfirmPatch.cs similarity index 100% rename from Patches/TransferConfirmPatch.cs rename to src/Patches/TransferConfirmPatch.cs diff --git a/Patches/UnloadAmmoPatches.cs b/src/Patches/UnloadAmmoPatches.cs similarity index 100% rename from Patches/UnloadAmmoPatches.cs rename to src/Patches/UnloadAmmoPatches.cs diff --git a/Patches/UnlockCursorPatch.cs b/src/Patches/UnlockCursorPatch.cs similarity index 100% rename from Patches/UnlockCursorPatch.cs rename to src/Patches/UnlockCursorPatch.cs diff --git a/Patches/WeaponModdingPatches.cs b/src/Patches/WeaponModdingPatches.cs similarity index 100% rename from Patches/WeaponModdingPatches.cs rename to src/Patches/WeaponModdingPatches.cs diff --git a/Patches/WeaponPresetConfirmPatches.cs b/src/Patches/WeaponPresetConfirmPatches.cs similarity index 100% rename from Patches/WeaponPresetConfirmPatches.cs rename to src/Patches/WeaponPresetConfirmPatches.cs diff --git a/Patches/WeaponZoomPatches.cs b/src/Patches/WeaponZoomPatches.cs similarity index 100% rename from Patches/WeaponZoomPatches.cs rename to src/Patches/WeaponZoomPatches.cs diff --git a/Plugin.cs b/src/Plugin.cs similarity index 100% rename from Plugin.cs rename to src/Plugin.cs diff --git a/R.cs b/src/R.cs similarity index 100% rename from R.cs rename to src/R.cs diff --git a/SearchKeyListener.cs b/src/SearchKeyListener.cs similarity index 100% rename from SearchKeyListener.cs rename to src/SearchKeyListener.cs diff --git a/Settings.cs b/src/Settings.cs similarity index 100% rename from Settings.cs rename to src/Settings.cs diff --git a/Sorter.cs b/src/Sorter.cs similarity index 100% rename from Sorter.cs rename to src/Sorter.cs diff --git a/TaskSerializer.cs b/src/TaskSerializer.cs similarity index 100% rename from TaskSerializer.cs rename to src/TaskSerializer.cs diff --git a/ToggleHold.cs b/src/ToggleHold.cs similarity index 100% rename from ToggleHold.cs rename to src/ToggleHold.cs From 82e5e081776b4cceb1d9b023ca0ec210f54c9530 Mon Sep 17 00:00:00 2001 From: Tyfon <29051038+tyfon7@users.noreply.github.com> Date: Fri, 20 Sep 2024 14:52:50 -0700 Subject: [PATCH 64/73] Change quickslot saving on death to override InraidController for compat with Insurance Plus --- server/src/mod.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/server/src/mod.ts b/server/src/mod.ts index 5907ee0..8378b37 100644 --- a/server/src/mod.ts +++ b/server/src/mod.ts @@ -1,7 +1,7 @@ import type { DependencyContainer } from "tsyringe"; +import type { InraidController } from "@spt/controllers/InraidController"; import type { HideoutHelper } from "@spt/helpers/HideoutHelper"; -import type { InRaidHelper } from "@spt/helpers/InRaidHelper"; import type { InventoryHelper } from "@spt/helpers/InventoryHelper"; import type { ItemHelper } from "@spt/helpers/ItemHelper"; import type { IHideoutSingleProductionStartRequestData } from "@spt/models/eft/hideout/IHideoutSingleProductionStartRequestData"; @@ -28,16 +28,16 @@ class UIFixes implements IPreSptLoadMod { // Keep quickbinds for items that aren't actually lost on death container.afterResolution( - "InRaidHelper", - (_, inRaidHelper: InRaidHelper) => { - const original = inRaidHelper.deleteInventory; + "InraidController", + (_, inRaidController: InraidController) => { + const original = inRaidController["performPostRaidActionsWhenDead"]; // protected, can only access by name - inRaidHelper.deleteInventory = (pmcData, sessionId) => { + inRaidController["performPostRaidActionsWhenDead"] = (postRaidSaveRequest, pmcData, sessionId) => { // Copy the existing quickbinds const fastPanel = cloner.clone(pmcData.Inventory.fastPanel); // Nukes the inventory and the fastpanel - original.call(inRaidHelper, pmcData, sessionId); + const result = original.call(inRaidController, postRaidSaveRequest, pmcData, sessionId); // Restore the quickbinds for items that still exist try { @@ -49,6 +49,8 @@ class UIFixes implements IPreSptLoadMod { } catch (error) { this.logger.error(`UIFixes: Failed to restore quickbinds\n ${error}`); } + + return result; }; }, { frequency: "Always" } From 3321d9bb02656a58aa2a2f124102d07d2835e635 Mon Sep 17 00:00:00 2001 From: Tyfon <29051038+tyfon7@users.noreply.github.com> Date: Sat, 21 Sep 2024 00:36:22 -0700 Subject: [PATCH 65/73] Interop fixes, readme --- README.md | 25 ++++ src/Multiselect/MultiSelectInterop.cs | 208 ++++++++++++++------------ 2 files changed, 141 insertions(+), 92 deletions(-) diff --git a/README.md b/README.md index e03a7a1..e64a565 100644 --- a/README.md +++ b/README.md @@ -116,3 +116,28 @@ Fixing bugs that BSG won't or can't - Skips "You can return to this later" warnings when not transferring all items - "Receive All" button no longer shows up when there is nothing to receive + +## Interop + +UI Fixes offers interop with other mods that want to use the multi-select functionality, _without_ taking a hard dependency on `Tyfon.UIFixes.dll`. + +To do this, simply download and add [MultiSelectInterop.cs](src/Multiselect/MultiSelectInterop.cs) to your client project. It will take care of testing if UI Fixes is present and, using reflection, interoping with the mod. + +MultiSelectInterop exposes a small static surface to give you access to the multi-selection. + +```cs +public static class MultiSelect +{ + // Returns the number of items in the current selection + public static int Count { get; } + + // Returns the items in the current selection + public static IEnumerable Items { get; } + + // Executes an operation on each item in the selection, sequentially + // Passing an ItemUiContext is optional as it will use ItemUiContext.Instance if needed + // The second overload takes an async operation and returns a task representing the aggregate. + public static void Apply(Action action, ItemUiContext context = null); + public static Task Apply(Func func, ItemUiContext context = null); +} +``` diff --git a/src/Multiselect/MultiSelectInterop.cs b/src/Multiselect/MultiSelectInterop.cs index c374ce1..43d1b6c 100644 --- a/src/Multiselect/MultiSelectInterop.cs +++ b/src/Multiselect/MultiSelectInterop.cs @@ -8,108 +8,132 @@ using System.Collections.Generic; using System.Reflection; using System.Threading.Tasks; -namespace UIFixesInterop; +/* +UI Fixes Multi-Select InterOp -/// -/// Provides access to UI Fixes' multiselect functionality. -/// -internal class MultiSelect +First, add the following attribute to your plugin class: + +[BepInDependency("Tyfon.UIFixes", BepInDependency.DependencyFlags.SoftDependency)] + +This will ensure UI Fixes is loaded already when your code is run. It will fail gracefully if UI Fixes is missing. + +Second, add this file to your project. Use the below UIFixesInterop.MultiSelect static methods, no explicit initialization required. + +Some things to keep in mind: +- While you can use MultiSelect.Items to get the items, this should only be used for reading purproses. If you need to +execute an operation on the items, I strongly suggest using the provided MultiSelect.Apply() method. + +- Apply() will execute the provided operation on each item, sequentially (sorted by grid order), maximum of one operation per frame. +It does this because strange bugs manifest if you try to do more than one thing in a single frame. + +- If the operation you are passing to Apply() does anything async, use the overload that takes a Func. It will wait +until each operation is over before doing the next. This is especially important if an operation could be affected by the preceding one, +for example in a quick-move where the avaiable space changes. It's also required if you are doing anything in-raid that will trigger +an animation, as starting the next one before it is complete will likely cancel the first. +*/ +namespace UIFixesInterop { - private static readonly Version RequiredVersion = new(2, 5); - - private static bool? UIFixesLoaded; - - private static Type MultiSelectType; - private static MethodInfo GetCountMethod; - private static MethodInfo GetItemsMethod; - private static MethodInfo ApplyMethod; - - /// Count represents the number of items in the current selection, 0 if UI Fixes is not present. - public int Count - { - get - { - if (!Loaded()) - { - return 0; - } - - return (int)GetCountMethod.Invoke(null, []); - } - } - - /// Items is an enumerable list of items in the current selection, empty if UI Fixes is not present - public IEnumerable Items - { - get - { - if (!Loaded()) - { - return []; - } - - return (IEnumerable)GetItemsMethod.Invoke(null, []); - } - } - /// - /// This method takes an Action and calls it *sequentially* on each item in the current selection. - /// Will no-op if UI Fixes is not present. + /// Provides access to UI Fixes' multiselect functionality. /// - /// The action to call on each item. - /// Optional ItemUiContext; will use ItemUiContext.Instance if not provided. - public void Apply(Action action, ItemUiContext itemUiContext = null) + internal static class MultiSelect { - if (!Loaded()) + private static readonly Version RequiredVersion = new Version(2, 5); + + private static bool? UIFixesLoaded; + + private static Type MultiSelectType; + private static MethodInfo GetCountMethod; + private static MethodInfo GetItemsMethod; + private static MethodInfo ApplyMethod; + + /// Count represents the number of items in the current selection, 0 if UI Fixes is not present. + public static int Count { - return; - } - - Func func = item => - { - action(item); - return Task.CompletedTask; - }; - - ApplyMethod.Invoke(null, [func, itemUiContext]); - } - - /// - /// This method takes an Func that returns a Task and calls it *sequentially* on each item in the current selection. - /// Will return a completed task immediately if UI Fixes is not present. - /// - /// The function to call on each item - /// Optional ItemUiContext; will use ItemUiContext.Instance if not provided. - /// A Task that will complete when all the function calls are complete. - public Task Apply(Func func, ItemUiContext itemUiContext = null) - { - if (!Loaded()) - { - return Task.CompletedTask; - } - - return (Task)ApplyMethod.Invoke(null, [func, itemUiContext]); - } - - private bool Loaded() - { - if (!UIFixesLoaded.HasValue) - { - bool present = Chainloader.PluginInfos.TryGetValue("Tyfon.UIFixes", out PluginInfo pluginInfo); - UIFixesLoaded = present && pluginInfo.Metadata.Version >= RequiredVersion; - - if (UIFixesLoaded.Value) + get { - MultiSelectType = Type.GetType("UIFixes.MultiSelectController, UIFixes"); - if (MultiSelectType != null) + if (!Loaded()) { - GetCountMethod = AccessTools.Method(MultiSelectType, "GetCount"); - GetItemsMethod = AccessTools.Method(MultiSelectType, "GetItems"); - ApplyMethod = AccessTools.Method(MultiSelectType, "Apply"); + return 0; + } + + return (int)GetCountMethod.Invoke(null, new object[] { }); + } + } + + /// Items is an enumerable list of items in the current selection, empty if UI Fixes is not present. + public static IEnumerable Items + { + get + { + if (!Loaded()) + { + return new Item[] { }; + } + + return (IEnumerable)GetItemsMethod.Invoke(null, new object[] { }); + } + } + + /// + /// This method takes an Action and calls it *sequentially* on each item in the current selection. + /// Will no-op if UI Fixes is not present. + /// + /// The action to call on each item. + /// Optional ItemUiContext; will use ItemUiContext.Instance if not provided. + public static void Apply(Action action, ItemUiContext itemUiContext = null) + { + if (!Loaded()) + { + return; + } + + Func func = item => + { + action(item); + return Task.CompletedTask; + }; + + ApplyMethod.Invoke(null, new object[] { func, itemUiContext }); + } + + /// + /// This method takes an Func that returns a Task and calls it *sequentially* on each item in the current selection. + /// Will return a completed task immediately if UI Fixes is not present. + /// + /// The function to call on each item + /// Optional ItemUiContext; will use ItemUiContext.Instance if not provided. + /// A Task that will complete when all the function calls are complete. + public static Task Apply(Func func, ItemUiContext itemUiContext = null) + { + if (!Loaded()) + { + return Task.CompletedTask; + } + + return (Task)ApplyMethod.Invoke(null, new object[] { func, itemUiContext }); + } + + private static bool Loaded() + { + if (!UIFixesLoaded.HasValue) + { + bool present = Chainloader.PluginInfos.TryGetValue("Tyfon.UIFixes", out PluginInfo pluginInfo); + UIFixesLoaded = present && pluginInfo.Metadata.Version >= RequiredVersion; + + if (UIFixesLoaded.Value) + { + MultiSelectType = Type.GetType("UIFixes.MultiSelectController, Tyfon.UIFixes"); + if (MultiSelectType != null) + { + GetCountMethod = AccessTools.Method(MultiSelectType, "GetCount"); + GetItemsMethod = AccessTools.Method(MultiSelectType, "GetItems"); + ApplyMethod = AccessTools.Method(MultiSelectType, "Apply"); + } } } - } - return UIFixesLoaded.Value; + return UIFixesLoaded.Value; + } } } \ No newline at end of file From 466f5c24b8fd35ec388aea22fe7cccd9231999fb Mon Sep 17 00:00:00 2001 From: Tyfon <29051038+tyfon7@users.noreply.github.com> Date: Mon, 23 Sep 2024 09:26:04 -0700 Subject: [PATCH 66/73] Required search keybind --- src/Patches/ContextMenuShortcutPatches.cs | 5 +++++ src/Settings.cs | 10 ++++++++++ 2 files changed, 15 insertions(+) diff --git a/src/Patches/ContextMenuShortcutPatches.cs b/src/Patches/ContextMenuShortcutPatches.cs index 825d508..24e3c9c 100644 --- a/src/Patches/ContextMenuShortcutPatches.cs +++ b/src/Patches/ContextMenuShortcutPatches.cs @@ -101,6 +101,11 @@ public static class ContextMenuShortcutPatches TryInteraction(__instance, itemContext, EItemInfoButton.LinkedSearch); } + if (Settings.RequiredSearchKeyBind.Value.IsDown()) + { + TryInteraction(__instance, itemContext, EItemInfoButton.NeededSearch); + } + if (Settings.SortingTableKeyBind.Value.IsDown()) { MoveToFromSortingTable(itemContext, __instance); diff --git a/src/Settings.cs b/src/Settings.cs index 36f2a7f..bf16312 100644 --- a/src/Settings.cs +++ b/src/Settings.cs @@ -105,6 +105,7 @@ internal class Settings public static ConfigEntry UnpackKeyBind { get; set; } public static ConfigEntry FilterByKeyBind { get; set; } public static ConfigEntry LinkedSearchKeyBind { get; set; } + public static ConfigEntry RequiredSearchKeyBind { get; set; } public static ConfigEntry AddOfferKeyBind { get; set; } public static ConfigEntry SortingTableKeyBind { get; set; } public static ConfigEntry SearchKeyBind { get; set; } @@ -457,6 +458,15 @@ internal class Settings null, new ConfigurationManagerAttributes { }))); + configEntries.Add(RequiredSearchKeyBind = config.Bind( + InputSection, + "Required Search Shortcut", + new KeyboardShortcut(KeyCode.None), + new ConfigDescription( + "Keybind to search flea market for items to barter for this item", + null, + new ConfigurationManagerAttributes { }))); + configEntries.Add(AddOfferKeyBind = config.Bind( InputSection, "Add Offer Shortcut", From 5259d4e60ced86fb219d37a3c14b2d41cdc9c12f Mon Sep 17 00:00:00 2001 From: Tyfon <29051038+tyfon7@users.noreply.github.com> Date: Thu, 26 Sep 2024 15:40:14 -0700 Subject: [PATCH 67/73] Wishlist context menu everywhere --- src/GlobalUsings.cs | 2 +- src/Multiselect/MultiSelect.cs | 32 +++++++++ src/Patches/ContextMenuPatches.cs | 112 ++++++++++++++++++++---------- src/Patches/MultiSelectPatches.cs | 8 ++- src/Settings.cs | 12 +++- 5 files changed, 127 insertions(+), 39 deletions(-) diff --git a/src/GlobalUsings.cs b/src/GlobalUsings.cs index 443870f..b80732c 100644 --- a/src/GlobalUsings.cs +++ b/src/GlobalUsings.cs @@ -1,4 +1,4 @@ -// These shouln't change (unless they do) +// These shouldn't change (unless they do) global using GridItemAddress = ItemAddressClass; global using DragItemContext = ItemContextClass; global using InsuranceItem = ItemClass; diff --git a/src/Multiselect/MultiSelect.cs b/src/Multiselect/MultiSelect.cs index 5859374..8637f61 100644 --- a/src/Multiselect/MultiSelect.cs +++ b/src/Multiselect/MultiSelect.cs @@ -419,6 +419,38 @@ public class MultiSelect } } + public static void WishlistAll(ItemUiContext itemUiContext, BaseItemInfoInteractions interactions, bool add, bool allOrNothing) + { + EItemInfoButton interaction = add ? EItemInfoButton.AddToWishlist : EItemInfoButton.RemoveFromWishlist; + if (!allOrNothing || InteractionCount(interaction, itemUiContext) == Count) + { + var taskSerializer = itemUiContext.gameObject.AddComponent(); + taskSerializer.Initialize(ItemContexts.Where(ic => InteractionAvailable(ic, interaction, itemUiContext)), + itemContext => + { + TaskCompletionSource taskSource = new(); + void callback() + { + interactions.RequestRedrawForItem(); + taskSource.Complete(); + } + + if (add) + { + itemUiContext.AddToWishList(itemContext.Item, callback); + } + else + { + itemUiContext.RemoveFromWishList(itemContext.Item, callback); + } + + return taskSource.Task; + }); + + itemUiContext.Tooltip?.Close(); + } + } + private static void ShowSelection(GridItemView itemView) { GameObject selectedMark = itemView.transform.Find("SelectedMark")?.gameObject; diff --git a/src/Patches/ContextMenuPatches.cs b/src/Patches/ContextMenuPatches.cs index 0a4efad..bab1ba7 100644 --- a/src/Patches/ContextMenuPatches.cs +++ b/src/Patches/ContextMenuPatches.cs @@ -4,6 +4,7 @@ using EFT.UI; using EFT.UI.DragAndDrop; using HarmonyLib; using SPT.Reflection.Patching; +using SPT.Reflection.Utils; using System; using System.Collections.Generic; using System.Linq; @@ -47,6 +48,9 @@ public static class ContextMenuPatches new EmptyModSlotMenuRemovePatch().Enable(); new EmptySlotMenuPatch().Enable(); new EmptySlotMenuRemovePatch().Enable(); + + new InventoryWishlistPatch().Enable(); + new TradingWishlistPatch().Enable(); } public class ContextMenuNamesPatch : ModulePatch @@ -64,65 +68,51 @@ public static class ContextMenuPatches return; } + int count = 0; if (caption == EItemInfoButton.Insure.ToString()) { InsuranceCompanyClass insurance = ItemUiContext.Instance.Session.InsuranceCompany; - int count = MultiSelect.ItemContexts.Select(ic => InsuranceItem.FindOrCreate(ic.Item)) + count = MultiSelect.ItemContexts.Select(ic => InsuranceItem.FindOrCreate(ic.Item)) .Where(i => insurance.ItemTypeAvailableForInsurance(i) && !insurance.InsuredItems.Contains(i)) .Count(); - if (count > 0) - { - ____text.text += " (x" + count + ")"; - } } else if (caption == EItemInfoButton.Equip.ToString()) { - int count = MultiSelect.InteractionCount(EItemInfoButton.Equip, ItemUiContext.Instance); - if (count > 0) - { - ____text.text += " (x" + count + ")"; - } + count = MultiSelect.InteractionCount(EItemInfoButton.Equip, ItemUiContext.Instance); } else if (caption == EItemInfoButton.Unequip.ToString()) { - int count = MultiSelect.InteractionCount(EItemInfoButton.Unequip, ItemUiContext.Instance); - if (count > 0) - { - ____text.text += " (x" + count + ")"; - } + count = MultiSelect.InteractionCount(EItemInfoButton.Unequip, ItemUiContext.Instance); } else if (caption == EItemInfoButton.LoadAmmo.ToString()) { - int count = MultiSelect.InteractionCount(EItemInfoButton.LoadAmmo, ItemUiContext.Instance); - if (count > 0) - { - ____text.text += " (x" + count + ")"; - } + count = MultiSelect.InteractionCount(EItemInfoButton.LoadAmmo, ItemUiContext.Instance); } else if (caption == EItemInfoButton.UnloadAmmo.ToString()) { - int count = MultiSelect.InteractionCount(EItemInfoButton.UnloadAmmo, ItemUiContext.Instance); - if (count > 0) - { - ____text.text += " (x" + count + ")"; - } + count = MultiSelect.InteractionCount(EItemInfoButton.UnloadAmmo, ItemUiContext.Instance); } else if (caption == EItemInfoButton.ApplyMagPreset.ToString()) { - int count = MultiSelect.InteractionCount(EItemInfoButton.ApplyMagPreset, ItemUiContext.Instance); - if (count > 0) - { - ____text.text += " (x" + count + ")"; - } + count = MultiSelect.InteractionCount(EItemInfoButton.ApplyMagPreset, ItemUiContext.Instance); } else if (caption == EItemInfoButton.Unpack.ToString()) { - int count = MultiSelect.InteractionCount(EItemInfoButton.Unpack, ItemUiContext.Instance); - if (count > 0) - { - ____text.text += " (x" + count + ")"; - } + count = MultiSelect.InteractionCount(EItemInfoButton.Unpack, ItemUiContext.Instance); + } + else if (caption == EItemInfoButton.AddToWishlist.ToString()) + { + count = MultiSelect.InteractionCount(EItemInfoButton.AddToWishlist, ItemUiContext.Instance); + } + else if (caption == EItemInfoButton.RemoveFromWishlist.ToString()) + { + count = MultiSelect.InteractionCount(EItemInfoButton.RemoveFromWishlist, ItemUiContext.Instance); + } + + if (count > 0) + { + ____text.text += " (x" + count + ")"; } } } @@ -519,7 +509,9 @@ public static class ContextMenuPatches { protected override MethodBase GetTargetMethod() { - return AccessTools.Method(typeof(InteractionButtonsContainer), nameof(InteractionButtonsContainer.SetSubInteractions)).MakeGenericMethod([typeof(InsuranceInteractions.EInsurers)]); + return AccessTools.Method( + typeof(InteractionButtonsContainer), + nameof(InteractionButtonsContainer.SetSubInteractions)).MakeGenericMethod([typeof(InsuranceInteractions.EInsurers)]); } // Existing logic tries to place it on the right, moving to the left if necessary. They didn't do it correctly, so it always goes on the left. @@ -530,6 +522,54 @@ public static class ContextMenuPatches } } + public class InventoryWishlistPatch : ModulePatch + { + protected override MethodBase GetTargetMethod() + { + // R.InventoryActions.Type is only ever referenced by it's child class, which overrides AvailableInteractions + Type type = PatchConstants.EftTypes.First(t => t.BaseType == R.InventoryInteractions.Type); + return AccessTools.DeclaredProperty(type, "AvailableInteractions").GetMethod; + } + + [PatchPostfix] + public static void Postfix(ref IEnumerable __result) + { + if (!Settings.WishlistContextEverywhere.Value) + { + return; + } + + var list = __result.ToList(); + int index = list.IndexOf(EItemInfoButton.Tag); + list.Insert(index, EItemInfoButton.RemoveFromWishlist); + list.Insert(index, EItemInfoButton.AddToWishlist); + __result = list; + } + } + + public class TradingWishlistPatch : ModulePatch + { + protected override MethodBase GetTargetMethod() + { + return AccessTools.DeclaredProperty(R.TradingInteractions.Type, "AvailableInteractions").GetMethod; + } + + [PatchPostfix] + public static void Postfix(ref IEnumerable __result) + { + if (!Settings.WishlistContextEverywhere.Value) + { + return; + } + + var list = __result.ToList(); + int index = list.IndexOf(EItemInfoButton.Tag); + list.Insert(index, EItemInfoButton.RemoveFromWishlist); + list.Insert(index, EItemInfoButton.AddToWishlist); + __result = list; + } + } + private static void PositionContextMenuFlyout(SimpleContextMenuButton button, SimpleContextMenu flyoutMenu) { RectTransform buttonTransform = button.RectTransform(); diff --git a/src/Patches/MultiSelectPatches.cs b/src/Patches/MultiSelectPatches.cs index 961f8f5..7be9306 100644 --- a/src/Patches/MultiSelectPatches.cs +++ b/src/Patches/MultiSelectPatches.cs @@ -315,7 +315,7 @@ public static class MultiSelectPatches } [PatchPrefix] - public static bool Prefix(EItemInfoButton interaction, ItemUiContext ___itemUiContext_1) + public static bool Prefix(BaseItemInfoInteractions __instance, EItemInfoButton interaction, ItemUiContext ___itemUiContext_1) { if (!MultiSelect.Active) { @@ -336,6 +336,12 @@ public static class MultiSelectPatches case EItemInfoButton.Unpack: MultiSelect.UnpackAll(___itemUiContext_1, false); return false; + case EItemInfoButton.AddToWishlist: + MultiSelect.WishlistAll(___itemUiContext_1, __instance, true, false); + return false; + case EItemInfoButton.RemoveFromWishlist: + MultiSelect.WishlistAll(___itemUiContext_1, __instance, false, false); + return false; default: return true; } diff --git a/src/Settings.cs b/src/Settings.cs index bf16312..06cc81f 100644 --- a/src/Settings.cs +++ b/src/Settings.cs @@ -138,6 +138,7 @@ internal class Settings public static ConfigEntry DefaultSortingTableBind { get; set; } // Advanced public static ConfigEntry ContextMenuOnRight { get; set; } public static ConfigEntry AddOfferContextMenu { get; set; } + public static ConfigEntry WishlistContextEverywhere { get; set; } public static ConfigEntry ShowGPCurrency { get; set; } public static ConfigEntry ShowOutOfStockCheckbox { get; set; } public static ConfigEntry SortingTableButton { get; set; } @@ -730,7 +731,7 @@ internal class Settings new ConfigurationManagerAttributes { }))); configEntries.Add(AddOfferContextMenu = config.Bind( - InputSection, + InventorySection, "Add Offer Context Menu", true, new ConfigDescription( @@ -738,6 +739,15 @@ internal class Settings null, new ConfigurationManagerAttributes { }))); + configEntries.Add(WishlistContextEverywhere = config.Bind( + InventorySection, + "Wishlist Context Menu Everywhere", + true, + new ConfigDescription( + "Add/Remove to wishlist available in the context menu on all screens", + null, + new ConfigurationManagerAttributes { }))); + configEntries.Add(ShowGPCurrency = config.Bind( InventorySection, "Show GP Coins in Currency", From 406d37e921be67e41dd96d7cd6dc7f9ac4e66d33 Mon Sep 17 00:00:00 2001 From: Tyfon <29051038+tyfon7@users.noreply.github.com> Date: Sun, 29 Sep 2024 23:48:53 -0700 Subject: [PATCH 68/73] FIre OnPointerExit from empty slot keybind --- src/ContextMenus/EmptySlotMenuTrigger.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/ContextMenus/EmptySlotMenuTrigger.cs b/src/ContextMenus/EmptySlotMenuTrigger.cs index 25db1bd..00920fa 100644 --- a/src/ContextMenus/EmptySlotMenuTrigger.cs +++ b/src/ContextMenus/EmptySlotMenuTrigger.cs @@ -32,6 +32,9 @@ public class EmptySlotMenuTrigger : MonoBehaviour, IPointerClickHandler, IPointe using EmptySlotContext context = new(slot, parentContext, itemUiContext); var interactions = itemUiContext.GetItemContextInteractions(context, null); interactions.ExecuteInteraction(EItemInfoButton.LinkedSearch); + + // Call this explicitly since screen transition prevents it from firing normally + OnPointerExit(null); } } From 44518e52d56c5e903f5c66d498fed7cf0a5af1d5 Mon Sep 17 00:00:00 2001 From: Tyfon <29051038+tyfon7@users.noreply.github.com> Date: Wed, 2 Oct 2024 13:35:48 -0700 Subject: [PATCH 69/73] Block GridView.MagnifyIfPossible to prevent multidrag issues with sorting table --- src/Patches/MultiSelectPatches.cs | 45 ++++++++++++++++++++++++++----- 1 file changed, 38 insertions(+), 7 deletions(-) diff --git a/src/Patches/MultiSelectPatches.cs b/src/Patches/MultiSelectPatches.cs index 7be9306..081478c 100644 --- a/src/Patches/MultiSelectPatches.cs +++ b/src/Patches/MultiSelectPatches.cs @@ -35,6 +35,8 @@ public static class MultiSelectPatches private static bool DisableMerge = false; private static bool IgnoreItemParent = false; + private static bool DisableMagnify = false; // Causes issues during multi drag + private static readonly Color ValidMoveColor = new(0.06f, 0.38f, 0.06f, 0.57f); public static void Enable() @@ -55,6 +57,7 @@ public static class MultiSelectPatches new DisableSplitPatch().Enable(); new DisableSplitTargetPatch().Enable(); new FixSearchedContextPatch().Enable(); + new DisableMagnifyPatch().Enable(); // Actions new ItemViewClickPatch().Enable(); @@ -535,6 +538,24 @@ public static class MultiSelectPatches } } + // MagnifyIfPossible gets called when a dynamic grid (sorting table) resizes. It causes GridViews to be killed and recreated asynchronously (!) + // This causes all sorts of issues with multiselect move, as there are race conditions and items get dropped and views duplicated + // I'm not 100% sure what it does, it appears to be trying to unload items that may now be out of sight, an optimization I'm willing + // to sacrifice for this actually work properly. + public class DisableMagnifyPatch : ModulePatch + { + protected override MethodBase GetTargetMethod() + { + return AccessTools.Method(typeof(GridView), nameof(GridView.MagnifyIfPossible), []); + } + + [PatchPrefix] + public static bool Prefix() + { + return !DisableMagnify; + } + } + public class GridViewCanAcceptPatch : ModulePatch { protected override MethodBase GetTargetMethod() @@ -592,6 +613,7 @@ public static class MultiSelectPatches Item targetItem = __instance.method_8(targetItemContext); DisableMerge = targetItem == null; + DisableMagnify = true; bool isGridPlacement = targetItem == null; // If everything selected is the same type and is a stackable type, allow partial success @@ -719,6 +741,8 @@ public static class MultiSelectPatches operations.Pop().Value?.RollBack(); } + DisableMagnify = false; + // result and operation are set to the last one that completed - so success if they all passed, or the first failure return false; } @@ -1314,28 +1338,35 @@ public static class MultiSelectPatches int firstStart = FindOrigin != null ? invertDimensions ? FindOrigin.LocationInGrid.x : FindOrigin.LocationInGrid.y : 0; int secondStart = FindOrigin != null ? invertDimensions ? FindOrigin.LocationInGrid.y : FindOrigin.LocationInGrid.x : 0; - // Walks the first dimension until it finds a row/column with enough space, then walks down that row - // /column until it finds a column/row with enough space + // Walks the first dimension until it finds a row/column with enough space, + // then walks down that row/column until it finds a column/row with enough space // Starts at origin, wraps around for (int i = 0; i < firstDimensionSize; i++) { - int firstDim = (firstStart + i) % firstDimensionSize; - //for (int j = i == firstStart ? secondStart : 0; j + itemSecondSize <= secondDimensionSize; j++) + int firstDim = (firstStart + i) % firstDimensionSize; // loop around from start for (int j = 0; j < secondDimensionSize; j++) { + // second dimension starts at FindOrigin, but after first dimension increases, starts back at 0 + // e.g. there wasn't room on the first row, then on the second row we start with first column int secondDim = firstDim == firstStart ? (secondStart + j) % secondDimensionSize : j; if (secondDim + itemSecondSize > secondDimensionSize) { continue; } - int secondDimOpenSpaces = (invertDimensions ? secondDimensionSpaces[secondDim * firstDimensionSize + firstDim] : secondDimensionSpaces[firstDim * secondDimensionSize + secondDim]); - if (secondDimOpenSpaces >= itemSecondSize || secondDimOpenSpaces == -1) // no idea what -1 means + // Open spaces is a look-ahead number of open spaces in that dimension + // -1 means "infinite", the grid can stretch in that direction (and there's no item further in that direction) + int secondDimOpenSpaces = invertDimensions ? + secondDimensionSpaces[secondDim * firstDimensionSize + firstDim] : + secondDimensionSpaces[firstDim * secondDimensionSize + secondDim]; + if (secondDimOpenSpaces >= itemSecondSize || secondDimOpenSpaces == -1) { bool enoughSpace = true; for (int k = secondDim; enoughSpace && k < secondDim + itemSecondSize; k++) { - int firstDimOpenSpaces = (invertDimensions ? firstDimensionSpaces[k * firstDimensionSize + firstDim] : firstDimensionSpaces[firstDim * secondDimensionSize + k]); + int firstDimOpenSpaces = invertDimensions ? + firstDimensionSpaces[k * firstDimensionSize + firstDim] : + firstDimensionSpaces[firstDim * secondDimensionSize + k]; enoughSpace &= firstDimOpenSpaces >= itemMainSize || firstDimOpenSpaces == -1; } From 1b218179a2e4dd7852fce11e35ac425e2a7939f8 Mon Sep 17 00:00:00 2001 From: Tyfon <29051038+tyfon7@users.noreply.github.com> Date: Wed, 2 Oct 2024 16:18:59 -0700 Subject: [PATCH 70/73] Add Open->All context menu option --- src/ContextMenus/OpenInteractions.cs | 83 ++++++++++++++++++++++++++++ src/Patches/ContextMenuPatches.cs | 26 ++++++++- src/Settings.cs | 10 ++++ 3 files changed, 117 insertions(+), 2 deletions(-) create mode 100644 src/ContextMenus/OpenInteractions.cs diff --git a/src/ContextMenus/OpenInteractions.cs b/src/ContextMenus/OpenInteractions.cs new file mode 100644 index 0000000..d572a5e --- /dev/null +++ b/src/ContextMenus/OpenInteractions.cs @@ -0,0 +1,83 @@ +using Comfort.Common; +using EFT.UI; +using EFT.UI.DragAndDrop; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace UIFixes; + +public class OpenInteractions(ItemContextAbstractClass itemContext, ItemUiContext itemUiContext) : ItemInfoInteractionsAbstractClass(itemUiContext) +{ + private readonly ItemContextAbstractClass itemContext = itemContext; + + public override void ExecuteInteractionInternal(Options interaction) + { + if (itemContext == null || itemContext.Item is not LootItemClass compoundItem) + { + return; + } + + var taskSerializer = itemUiContext_0.gameObject.AddComponent(); + taskSerializer.Initialize(GetNestedContainers(itemContext), containerContext => + { + if (containerContext != null) + { + itemUiContext_0.OpenItem(containerContext.Item as LootItemClass, containerContext, true); + } + + return Task.CompletedTask; + }); + } + + public override bool IsActive(Options button) + { + return true; + } + + public override IResult IsInteractive(Options button) + { + return SuccessfulResult.New; + } + + public override bool HasIcons + { + get { return false; } + } + + public enum Options + { + All + } + + private IEnumerable GetNestedContainers(ItemContextAbstractClass first) + { + var windowRoot = Singleton.Instance; + LootItemClass parent = first.Item as LootItemClass; + + yield return first; + + while (true) + { + var innerContainers = parent.GetFirstLevelItems() + .Where(i => i != parent) + .Where(i => i is LootItemClass innerContainer && innerContainer.Grids.Any()); + if (innerContainers.Count() != 1) + { + yield break; + } + + var targetId = innerContainers.First().Id; + var targetItemView = windowRoot.GetComponentsInChildren().FirstOrDefault(itemView => itemView.Item.Id == targetId); + if (targetItemView == null) + { + yield return null; // Keeps returning null until the window is open + } + + parent = targetItemView.Item as LootItemClass; + yield return targetItemView.ItemContext; + } + } +} + +public class NestedContainerTaskSerializer : TaskSerializer { } \ No newline at end of file diff --git a/src/Patches/ContextMenuPatches.cs b/src/Patches/ContextMenuPatches.cs index bab1ba7..811a0c6 100644 --- a/src/Patches/ContextMenuPatches.cs +++ b/src/Patches/ContextMenuPatches.cs @@ -125,9 +125,20 @@ public static class ContextMenuPatches } [PatchPostfix] - public static void Postfix(ref IEnumerable __result) + public static void Postfix(ref IEnumerable __result, Item ___item_0) { __result = __result.Append(EItemInfoButton.Repair).Append(EItemInfoButton.Insure); + + if (___item_0 is LootItemClass container && container.Grids.Any()) + { + var innerContainers = container.GetFirstLevelItems() + .Where(i => i != container) + .Where(i => i is LootItemClass innerContainer && innerContainer.Grids.Any()); + if (innerContainers.Count() == 1) + { + __result = __result.Append(EItemInfoButton.Open); + } + } } } @@ -141,7 +152,12 @@ public static class ContextMenuPatches } [PatchPrefix] - public static bool Prefix(EItemInfoButton parentInteraction, ISubInteractions subInteractionsWrapper, Item ___item_0, ItemUiContext ___itemUiContext_1) + public static bool Prefix( + EItemInfoButton parentInteraction, + ISubInteractions subInteractionsWrapper, + Item ___item_0, + ItemContextAbstractClass ___itemContextAbstractClass, + ItemUiContext ___itemUiContext_1) { // Clear this, since something else should be active (even a different mouseover of the insurance button) LoadingInsuranceActions = false; @@ -179,6 +195,12 @@ public static class ContextMenuPatches return false; } + if (Settings.OpenAllContextMenu.Value && parentInteraction == EItemInfoButton.Open) + { + subInteractionsWrapper.SetSubInteractions(new OpenInteractions(___itemContextAbstractClass, ___itemUiContext_1)); + return false; + } + return true; } } diff --git a/src/Settings.cs b/src/Settings.cs index 06cc81f..4bb9dc7 100644 --- a/src/Settings.cs +++ b/src/Settings.cs @@ -139,6 +139,7 @@ internal class Settings public static ConfigEntry ContextMenuOnRight { get; set; } public static ConfigEntry AddOfferContextMenu { get; set; } public static ConfigEntry WishlistContextEverywhere { get; set; } + public static ConfigEntry OpenAllContextMenu { get; set; } public static ConfigEntry ShowGPCurrency { get; set; } public static ConfigEntry ShowOutOfStockCheckbox { get; set; } public static ConfigEntry SortingTableButton { get; set; } @@ -748,6 +749,15 @@ internal class Settings null, new ConfigurationManagerAttributes { }))); + configEntries.Add(OpenAllContextMenu = config.Bind( + InventorySection, + "Open All Context Flyout", + true, + new ConfigDescription( + "Add a flyout to the Open context menu to recursively open a stack of containers", + null, + new ConfigurationManagerAttributes { }))); + configEntries.Add(ShowGPCurrency = config.Bind( InventorySection, "Show GP Coins in Currency", From 500538fc82ded0cb2caad63164208bd5c1ba2159 Mon Sep 17 00:00:00 2001 From: Tyfon <29051038+tyfon7@users.noreply.github.com> Date: Wed, 2 Oct 2024 16:19:56 -0700 Subject: [PATCH 71/73] rev version 2.5.1 --- UIFixes.csproj | 10 ++++++---- server/package.json | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/UIFixes.csproj b/UIFixes.csproj index e0fa01e..b2c929c 100644 --- a/UIFixes.csproj +++ b/UIFixes.csproj @@ -4,7 +4,7 @@ net471 Tyfon.UIFixes SPT UI Fixes - 2.5.0 + 2.5.1 true latest Debug;Release @@ -80,11 +80,13 @@ - + - - + \ No newline at end of file diff --git a/server/package.json b/server/package.json index a51f217..749ff16 100644 --- a/server/package.json +++ b/server/package.json @@ -1,6 +1,6 @@ { "name": "uifixes", - "version": "2.5.0", + "version": "2.5.1", "main": "src/mod.js", "license": "MIT", "author": "Tyfon", From 7f447d9cfa808e5a2ada4df83b876db08acf1247 Mon Sep 17 00:00:00 2001 From: Tyfon <29051038+tyfon7@users.noreply.github.com> Date: Wed, 2 Oct 2024 17:41:46 -0700 Subject: [PATCH 72/73] Update readme --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index e64a565..410b73a 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,8 @@ Existing SPT features made better - Multi-grid vest and backpack grids reordered to be left to right, top to bottom - Sorting will stack and combine stacks of items - Shift-clicking sort will only sort loose items, leaving containers in place +- ✨ Open->All context flyout that will recursively open nested containers to get at that innermost bag +- ✨ Add/Remove from wishlist everywhere #### Inspect windows From 58ee16bee7f9d3ef57ba929057d1b846ddc77b9f Mon Sep 17 00:00:00 2001 From: Tyfon <29051038+tyfon7@users.noreply.github.com> Date: Thu, 3 Oct 2024 10:58:56 -0700 Subject: [PATCH 73/73] try/catch trader autoswitch --- src/Patches/TradingAutoSwitchPatches.cs | 34 +++++++++++++++---------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/src/Patches/TradingAutoSwitchPatches.cs b/src/Patches/TradingAutoSwitchPatches.cs index c840046..e89aba9 100644 --- a/src/Patches/TradingAutoSwitchPatches.cs +++ b/src/Patches/TradingAutoSwitchPatches.cs @@ -3,6 +3,7 @@ using EFT.UI; using EFT.UI.DragAndDrop; using HarmonyLib; using SPT.Reflection.Patching; +using System; using System.Reflection; using UnityEngine; using UnityEngine.EventSystems; @@ -83,24 +84,31 @@ public static class TradingAutoSwitchPatches return true; } - if (!___bool_8 && ctrlPressed && assortmentController.QuickFindTradingAppropriatePlace(__instance.Item, null)) + try { - __instance.ItemContext?.CloseDependentWindows(); - __instance.HideTooltip(); - Singleton.Instance.PlayItemSound(__instance.Item.ItemSound, EInventorySoundType.pickup, false); + if (!___bool_8 && ctrlPressed && assortmentController.QuickFindTradingAppropriatePlace(__instance.Item, null)) + { + __instance.ItemContext?.CloseDependentWindows(); + __instance.HideTooltip(); + Singleton.Instance.PlayItemSound(__instance.Item.ItemSound, EInventorySoundType.pickup, false); - SellTab.OnPointerClick(null); + SellTab.OnPointerClick(null); - return false; + return false; + } + + if (___bool_8) + { + assortmentController.SelectItem(__instance.Item); + + BuyTab.OnPointerClick(null); + + return false; + } } - - if (___bool_8) + catch (Exception e) { - assortmentController.SelectItem(__instance.Item); - - BuyTab.OnPointerClick(null); - - return false; + Logger.LogError(e); } return true;