diff --git a/ContextMenus/EmptySlotMenu.cs b/ContextMenus/EmptySlotMenu.cs index ad3b571..4cf8df9 100644 --- a/ContextMenus/EmptySlotMenu.cs +++ b/ContextMenus/EmptySlotMenu.cs @@ -4,26 +4,25 @@ using EFT.UI.Ragfair; using System; using System.Collections.Generic; -namespace UIFixes.ContextMenus +namespace UIFixes; + +public class EmptySlotMenu(Slot slot, ItemContextAbstractClass itemContext, ItemUiContext itemUiContext, Action closeAction) : BaseItemInfoInteractions(itemContext, itemUiContext, closeAction) { - public class EmptySlotMenu(Slot slot, ItemContextAbstractClass itemContext, ItemUiContext itemUiContext, Action closeAction) : BaseItemInfoInteractions(itemContext, itemUiContext, closeAction) + private static readonly List Actions = [EItemInfoButton.LinkedSearch]; + + private readonly Slot slot = slot; + + public override IEnumerable AvailableInteractions => Actions; + + public override void ExecuteInteractionInternal(EItemInfoButton interaction) { - private static readonly List Actions = [EItemInfoButton.LinkedSearch]; - - private readonly Slot slot = slot; - - public override IEnumerable AvailableInteractions => Actions; - - public override void ExecuteInteractionInternal(EItemInfoButton interaction) + switch (interaction) { - switch (interaction) - { - case EItemInfoButton.LinkedSearch: - Search(new(EFilterType.LinkedSearch, slot.ParentItem.TemplateId + ":" + slot.Id, true)); - break; - default: - break; - } + case EItemInfoButton.LinkedSearch: + Search(new(EFilterType.LinkedSearch, slot.ParentItem.TemplateId + ":" + slot.Id, true)); + break; + default: + break; } } -} +} \ No newline at end of file diff --git a/ContextMenus/EmptySlotMenuTrigger.cs b/ContextMenus/EmptySlotMenuTrigger.cs index 038272c..25db1bd 100644 --- a/ContextMenus/EmptySlotMenuTrigger.cs +++ b/ContextMenus/EmptySlotMenuTrigger.cs @@ -4,79 +4,78 @@ using System; using UnityEngine; using UnityEngine.EventSystems; -namespace UIFixes.ContextMenus +namespace UIFixes; + +public class EmptySlotMenuTrigger : MonoBehaviour, IPointerClickHandler, IPointerDownHandler, IPointerUpHandler, IPointerEnterHandler, IPointerExitHandler { - public class EmptySlotMenuTrigger : MonoBehaviour, IPointerClickHandler, IPointerDownHandler, IPointerUpHandler, IPointerEnterHandler, IPointerExitHandler + private ItemUiContext itemUiContext; + private Slot slot; + private ItemContextAbstractClass parentContext; + private bool hovered = false; + + public void Init(Slot slot, ItemContextAbstractClass parentContext, ItemUiContext itemUiContext) { - private ItemUiContext itemUiContext; - private Slot slot; - private ItemContextAbstractClass parentContext; - private bool hovered = false; - - public void Init(Slot slot, ItemContextAbstractClass parentContext, ItemUiContext itemUiContext) - { - this.itemUiContext = itemUiContext; - this.slot = slot; - this.parentContext = parentContext; - } - - public void Update() - { - if (!hovered) - { - return; - } - - if (Settings.LinkedSearchKeyBind.Value.IsDown()) - { - using EmptySlotContext context = new(slot, parentContext, itemUiContext); - var interactions = itemUiContext.GetItemContextInteractions(context, null); - interactions.ExecuteInteraction(EItemInfoButton.LinkedSearch); - } - } - - public void OnPointerClick(PointerEventData eventData) - { - if (eventData.button == PointerEventData.InputButton.Right) - { - EmptySlotContext context = new(slot, parentContext, itemUiContext); - itemUiContext.ShowContextMenu(context, eventData.position); - } - } - - public void OnPointerDown(PointerEventData eventData) { } - - public void OnPointerEnter(PointerEventData eventData) - { - hovered = true; - } - - public void OnPointerExit(PointerEventData eventData) - { - hovered = false; - } - - public void OnPointerUp(PointerEventData eventData) { } + this.itemUiContext = itemUiContext; + this.slot = slot; + this.parentContext = parentContext; } - public class EmptySlotContext(Slot slot, ItemContextAbstractClass parentContext, ItemUiContext itemUiContext) : ItemContextAbstractClass(parentContext.Item, parentContext.ViewType, parentContext) + public void Update() { - private readonly Slot slot = slot; - private readonly ItemUiContext itemUiContext = itemUiContext; - - public override ItemInfoInteractionsAbstractClass GetItemContextInteractions(Action closeAction) + if (!hovered) { - return new EmptySlotMenu(slot, ItemContextAbstractClass, itemUiContext, () => - { - Dispose(); - closeAction?.Invoke(); - }); + return; } - public override ItemContextAbstractClass CreateChild(Item item) + if (Settings.LinkedSearchKeyBind.Value.IsDown()) { - // Should never happen - throw new NotImplementedException(); + using EmptySlotContext context = new(slot, parentContext, itemUiContext); + var interactions = itemUiContext.GetItemContextInteractions(context, null); + interactions.ExecuteInteraction(EItemInfoButton.LinkedSearch); } } + + public void OnPointerClick(PointerEventData eventData) + { + if (eventData.button == PointerEventData.InputButton.Right) + { + EmptySlotContext context = new(slot, parentContext, itemUiContext); + itemUiContext.ShowContextMenu(context, eventData.position); + } + } + + public void OnPointerDown(PointerEventData eventData) { } + + public void OnPointerEnter(PointerEventData eventData) + { + hovered = true; + } + + public void OnPointerExit(PointerEventData eventData) + { + hovered = false; + } + + public void OnPointerUp(PointerEventData eventData) { } } + +public class EmptySlotContext(Slot slot, ItemContextAbstractClass parentContext, ItemUiContext itemUiContext) : ItemContextAbstractClass(parentContext.Item, parentContext.ViewType, parentContext) +{ + private readonly Slot slot = slot; + private readonly ItemUiContext itemUiContext = itemUiContext; + + public override ItemInfoInteractionsAbstractClass GetItemContextInteractions(Action closeAction) + { + return new EmptySlotMenu(slot, ItemContextAbstractClass, itemUiContext, () => + { + Dispose(); + closeAction?.Invoke(); + }); + } + + public override ItemContextAbstractClass CreateChild(Item item) + { + // Should never happen + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/ContextMenus/InsuranceInteractions.cs b/ContextMenus/InsuranceInteractions.cs index 2331374..7acd029 100644 --- a/ContextMenus/InsuranceInteractions.cs +++ b/ContextMenus/InsuranceInteractions.cs @@ -5,101 +5,100 @@ using System; using System.Collections.Generic; using System.Linq; -namespace UIFixes +namespace UIFixes; + +public class InsuranceInteractions(IEnumerable items, ItemUiContext uiContext, int playerRubles) : ItemInfoInteractionsAbstractClass(uiContext) { - public class InsuranceInteractions(IEnumerable items, ItemUiContext uiContext, int playerRubles) : ItemInfoInteractionsAbstractClass(uiContext) + private readonly InsuranceCompanyClass insurance = uiContext.Session.InsuranceCompany; + private readonly List items = items.ToList(); + private readonly int playerRubles = playerRubles; + private List insurableItems; + private readonly Dictionary prices = []; + + public InsuranceInteractions(Item item, ItemUiContext uiContext, int playerRubles) : this([item], uiContext, playerRubles) { } + + public void LoadAsync(Action callback) { - private readonly InsuranceCompanyClass insurance = uiContext.Session.InsuranceCompany; - private readonly List items = items.ToList(); - private readonly int playerRubles = playerRubles; - private List insurableItems; - private readonly Dictionary prices = []; + IEnumerable InsuranceItemes = items.Select(InsuranceItem.FindOrCreate); + insurableItems = InsuranceItemes.SelectMany(insurance.GetItemChildren) + .Flatten(insurance.GetItemChildren) + .Concat(InsuranceItemes) + .Where(i => insurance.ItemTypeAvailableForInsurance(i) && !insurance.InsuredItems.Contains(i)) + .ToList(); - public InsuranceInteractions(Item item, ItemUiContext uiContext, int playerRubles) : this([item], uiContext, playerRubles) { } - - public void LoadAsync(Action callback) + insurance.GetInsurePriceAsync(insurableItems, _ => { - IEnumerable InsuranceItemes = items.Select(InsuranceItem.FindOrCreate); - insurableItems = InsuranceItemes.SelectMany(insurance.GetItemChildren) - .Flatten(insurance.GetItemChildren) - .Concat(InsuranceItemes) - .Where(i => insurance.ItemTypeAvailableForInsurance(i) && !insurance.InsuredItems.Contains(i)) - .ToList(); - - insurance.GetInsurePriceAsync(insurableItems, _ => + foreach (var insurer in insurance.Insurers) { - foreach (var insurer in insurance.Insurers) - { - int price = this.insurableItems.Select(i => insurance.InsureSummary[insurer.Id][i]).Where(s => s.Loaded).Sum(s => s.Amount); - prices[insurer.Id] = price; + int price = this.insurableItems.Select(i => insurance.InsureSummary[insurer.Id][i]).Where(s => s.Loaded).Sum(s => s.Amount); + prices[insurer.Id] = price; - string priceColor = price > playerRubles ? "#FF0000" : "#ADB8BC"; + string priceColor = price > playerRubles ? "#FF0000" : "#ADB8BC"; - string text = string.Format("{0} ({2} ₽)", insurer.LocalizedName, priceColor, price); + string text = string.Format("{0} ({2} ₽)", insurer.LocalizedName, priceColor, price); - base.method_2(MakeInteractionId(insurer.Id), text, () => this.Insure(insurer.Id)); - } - - callback(); - }); - } - - private void Insure(string insurerId) - { - insurance.SelectedInsurerId = insurerId; - insurance.InsureItems(this.insurableItems, result => { }); - } - - public IResult GetButtonInteraction(string interactionId) - { - string traderId = interactionId.Split(':')[1]; - if (prices[traderId] > playerRubles) - { - return new FailedResult("ragfair/Not enough money", 0); + base.method_2(MakeInteractionId(insurer.Id), text, () => this.Insure(insurer.Id)); } - return SuccessfulResult.New; - } - - public override void ExecuteInteractionInternal(EInsurers interaction) - { - } - - public override bool IsActive(EInsurers button) - { - return button == EInsurers.None && !this.insurance.Insurers.Any(); - } - - public override IResult IsInteractive(EInsurers button) - { - return new FailedResult("No insurers?", 0); - } - - public override bool HasIcons - { - get { return false; } - } - - public enum EInsurers - { - None - } - private static string MakeInteractionId(string traderId) - { - return "UIFixesInsurerId:" + traderId; - } - - public static bool IsInsuranceInteractionId(string id) - { - return id.StartsWith("UIFixesInsurerId:"); - } + callback(); + }); } - public static class InsuranceExtensions + private void Insure(string insurerId) { - public static bool IsInsuranceInteraction(this DynamicInteractionClass interaction) + insurance.SelectedInsurerId = insurerId; + insurance.InsureItems(this.insurableItems, result => { }); + } + + public IResult GetButtonInteraction(string interactionId) + { + string traderId = interactionId.Split(':')[1]; + if (prices[traderId] > playerRubles) { - return interaction.Id.StartsWith("UIFixesInsurerId:"); + return new FailedResult("ragfair/Not enough money", 0); } + + return SuccessfulResult.New; + } + + public override void ExecuteInteractionInternal(EInsurers interaction) + { + } + + public override bool IsActive(EInsurers button) + { + return button == EInsurers.None && !this.insurance.Insurers.Any(); + } + + public override IResult IsInteractive(EInsurers button) + { + return new FailedResult("No insurers?", 0); + } + + public override bool HasIcons + { + get { return false; } + } + + public enum EInsurers + { + None + } + private static string MakeInteractionId(string traderId) + { + return "UIFixesInsurerId:" + traderId; + } + + public static bool IsInsuranceInteractionId(string id) + { + return id.StartsWith("UIFixesInsurerId:"); + } +} + +public static class InsuranceExtensions +{ + public static bool IsInsuranceInteraction(this DynamicInteractionClass interaction) + { + return interaction.Id.StartsWith("UIFixesInsurerId:"); } } diff --git a/ContextMenus/RepairInteractions.cs b/ContextMenus/RepairInteractions.cs index 26f8e8b..80a156d 100644 --- a/ContextMenus/RepairInteractions.cs +++ b/ContextMenus/RepairInteractions.cs @@ -6,176 +6,175 @@ using System; using System.Globalization; using System.Linq; -namespace UIFixes +namespace UIFixes; + +public class RepairInteractions : ItemInfoInteractionsAbstractClass { - public class RepairInteractions : ItemInfoInteractionsAbstractClass + private readonly RepairControllerClass repairController; + private readonly int playerRubles; + private readonly R.RepairStrategy repairStrategy; + + public RepairInteractions(Item item, ItemUiContext uiContext, int playerRubles) : base(uiContext) { - private readonly RepairControllerClass repairController; - private readonly int playerRubles; - private readonly R.RepairStrategy repairStrategy; + repairController = uiContext.Session.RepairController; - public RepairInteractions(Item item, ItemUiContext uiContext, int playerRubles) : base(uiContext) + // Add empty action because otherwise RepairControllerClass.action_0 is null and it pukes on successful repair + repairController.OnSuccessfulRepairChangedEvent += () => { }; + + this.playerRubles = playerRubles; + + repairStrategy = R.RepairStrategy.Create(item, repairController); + + Load(); + } + + private void Load() + { + foreach (IRepairer repairer in repairStrategy.Repairers) { - repairController = uiContext.Session.RepairController; - - // Add empty action because otherwise RepairControllerClass.action_0 is null and it pukes on successful repair - repairController.OnSuccessfulRepairChangedEvent += () => {}; - - this.playerRubles = playerRubles; - - repairStrategy = R.RepairStrategy.Create(item, repairController); - - Load(); - } - - private void Load() - { - foreach (IRepairer repairer in repairStrategy.Repairers) - { - repairStrategy.CurrentRepairer = repairer; - - float repairAmount = GetClampedRepairAmount(repairStrategy); - - string text; - if (repairAmount < float.Epsilon || !repairStrategy.CanRepair(repairStrategy.CurrentRepairer, repairStrategy.CurrentRepairer.Targets)) - { - text = string.Format("{0}", repairer.LocalizedName); - } - else if (R.RepairKit.Type.IsInstanceOfType(repairer)) - { - var repairKit = new R.RepairKit(repairer); - float pointsLeft = repairKit.GetRepairPoints(); - double amount = repairStrategy.GetRepairPrice(repairAmount, repairKit.Value); - - string costColor = amount > pointsLeft ? "#FF0000" : "#ADB8BC"; - text = string.Format("{0} ({2} {3})", repairer.LocalizedName, costColor, Math.Round(amount, 2).ToString(CultureInfo.InvariantCulture), "RP".Localized()); - } - else - { - int price = repairStrategy.GetCurrencyPrice(repairAmount); - - string priceColor = price > playerRubles ? "#FF0000" : "#ADB8BC"; - text = string.Format("{0} ({2} ₽)", repairer.LocalizedName, priceColor, price); - } - - base.method_2(MakeInteractionId(repairer.RepairerId), text, () => this.Repair(repairer.RepairerId)); - } - } - - private static float GetClampedRepairAmount(R.RepairStrategy repairStrategy) - { - float repairAmount = repairStrategy.HowMuchRepairScoresCanAccept(); - - // The repair window round-trips this amount through a UI element that operatoes on percents, so it divides this by the max durability - // The UI element however has a minimum value of 0.001, which artificially caps how small a repair can be. To emulate this I have to do the same math - float percentAmount = repairAmount / repairStrategy.TemplateDurability(); - - return percentAmount < 0.001f ? 0 : repairAmount; - } - - private async void Repair(string repairerId) - { - repairStrategy.CurrentRepairer = repairStrategy.Repairers.Single(r => r.RepairerId == repairerId); - IResult result = await repairStrategy.RepairItem(repairStrategy.HowMuchRepairScoresCanAccept(), null); - if (result.Succeed) - { - Singleton.Instance.PlayUISound(EUISoundType.RepairComplete); - NotificationManagerClass.DisplayMessageNotification(string.Format("{0} {1:F1}", "Item successfully repaired to".Localized(null), repairStrategy.Durability()), ENotificationDurationType.Default, ENotificationIconType.Default, null); - } - } - - public IResult GetButtonInteraction(string interactionId) - { - string repairerId = interactionId.Split(':')[1]; - IRepairer repairer = repairStrategy.Repairers.Single(r => r.RepairerId == repairerId); repairStrategy.CurrentRepairer = repairer; - if (!repairStrategy.CanRepair(repairStrategy.CurrentRepairer, repairStrategy.CurrentRepairer.Targets)) - { - return new FailedResult(ERepairStatusWarning.ExceptionRepairItem.ToString()); - } - float repairAmount = GetClampedRepairAmount(repairStrategy); - if (R.RepairKit.Type.IsInstanceOfType(repairer)) + string text; + if (repairAmount < float.Epsilon || !repairStrategy.CanRepair(repairStrategy.CurrentRepairer, repairStrategy.CurrentRepairer.Targets)) + { + text = string.Format("{0}", repairer.LocalizedName); + } + else if (R.RepairKit.Type.IsInstanceOfType(repairer)) { var repairKit = new R.RepairKit(repairer); float pointsLeft = repairKit.GetRepairPoints(); double amount = repairStrategy.GetRepairPrice(repairAmount, repairKit.Value); - if (amount > pointsLeft) - { - return new FailedResult(ERepairStatusWarning.NotEnoughRepairPoints.ToString()); - } - // This check is only for repair kits - if (repairStrategy.IsNoCorrespondingArea()) - { - return new FailedResult(ERepairStatusWarning.NoCorrespondingArea.ToString()); - } + string costColor = amount > pointsLeft ? "#FF0000" : "#ADB8BC"; + text = string.Format("{0} ({2} {3})", repairer.LocalizedName, costColor, Math.Round(amount, 2).ToString(CultureInfo.InvariantCulture), "RP".Localized()); } else { int price = repairStrategy.GetCurrencyPrice(repairAmount); - if (price > playerRubles) - { - return new FailedResult(ERepairStatusWarning.NotEnoughMoney.ToString()); - } + + string priceColor = price > playerRubles ? "#FF0000" : "#ADB8BC"; + text = string.Format("{0} ({2} ₽)", repairer.LocalizedName, priceColor, price); } - if (repairAmount < float.Epsilon) - { - return new FailedResult(ERepairStatusWarning.NothingToRepair.ToString()); - } - - // BrokenItemError is not actually an error, they just implemented it that way - it shows a bunch of red text but it doesn't prevent repair - // Leaving this here to remember - /*if (repairStrategy.BrokenItemError()) - { - return new FailedResult(ERepairStatusWarning.BrokenItem.ToString()); - }*/ - - return SuccessfulResult.New; - } - - public override void ExecuteInteractionInternal(ERepairers interaction) - { - } - - public override bool IsActive(ERepairers button) - { - return button == ERepairers.None && !this.repairController.TraderRepairers.Any(); - } - - public override IResult IsInteractive(ERepairers button) - { - return new FailedResult("No repairers?", 0); - } - - public override bool HasIcons - { - get { return false; } - } - - public enum ERepairers - { - None - } - private static string MakeInteractionId(string traderId) - { - return "UIFixesRepairerId:" + traderId; - } - - public static bool IsRepairInteractionId(string id) - { - return id.StartsWith("UIFixesRepairerId:"); + base.method_2(MakeInteractionId(repairer.RepairerId), text, () => this.Repair(repairer.RepairerId)); } } - public static class RepairExtensions + private static float GetClampedRepairAmount(R.RepairStrategy repairStrategy) { - public static bool IsRepairInteraction(this DynamicInteractionClass interaction) + float repairAmount = repairStrategy.HowMuchRepairScoresCanAccept(); + + // The repair window round-trips this amount through a UI element that operatoes on percents, so it divides this by the max durability + // The UI element however has a minimum value of 0.001, which artificially caps how small a repair can be. To emulate this I have to do the same math + float percentAmount = repairAmount / repairStrategy.TemplateDurability(); + + return percentAmount < 0.001f ? 0 : repairAmount; + } + + private async void Repair(string repairerId) + { + repairStrategy.CurrentRepairer = repairStrategy.Repairers.Single(r => r.RepairerId == repairerId); + IResult result = await repairStrategy.RepairItem(repairStrategy.HowMuchRepairScoresCanAccept(), null); + if (result.Succeed) { - return interaction.Id.StartsWith("UIFixesRepairerId:"); + Singleton.Instance.PlayUISound(EUISoundType.RepairComplete); + NotificationManagerClass.DisplayMessageNotification(string.Format("{0} {1:F1}", "Item successfully repaired to".Localized(null), repairStrategy.Durability()), ENotificationDurationType.Default, ENotificationIconType.Default, null); } } + + public IResult GetButtonInteraction(string interactionId) + { + string repairerId = interactionId.Split(':')[1]; + IRepairer repairer = repairStrategy.Repairers.Single(r => r.RepairerId == repairerId); + repairStrategy.CurrentRepairer = repairer; + + if (!repairStrategy.CanRepair(repairStrategy.CurrentRepairer, repairStrategy.CurrentRepairer.Targets)) + { + return new FailedResult(ERepairStatusWarning.ExceptionRepairItem.ToString()); + } + + float repairAmount = GetClampedRepairAmount(repairStrategy); + + if (R.RepairKit.Type.IsInstanceOfType(repairer)) + { + var repairKit = new R.RepairKit(repairer); + float pointsLeft = repairKit.GetRepairPoints(); + double amount = repairStrategy.GetRepairPrice(repairAmount, repairKit.Value); + if (amount > pointsLeft) + { + return new FailedResult(ERepairStatusWarning.NotEnoughRepairPoints.ToString()); + } + + // This check is only for repair kits + if (repairStrategy.IsNoCorrespondingArea()) + { + return new FailedResult(ERepairStatusWarning.NoCorrespondingArea.ToString()); + } + } + else + { + int price = repairStrategy.GetCurrencyPrice(repairAmount); + if (price > playerRubles) + { + return new FailedResult(ERepairStatusWarning.NotEnoughMoney.ToString()); + } + } + + if (repairAmount < float.Epsilon) + { + return new FailedResult(ERepairStatusWarning.NothingToRepair.ToString()); + } + + // BrokenItemError is not actually an error, they just implemented it that way - it shows a bunch of red text but it doesn't prevent repair + // Leaving this here to remember + /*if (repairStrategy.BrokenItemError()) + { + return new FailedResult(ERepairStatusWarning.BrokenItem.ToString()); + }*/ + + return SuccessfulResult.New; + } + + public override void ExecuteInteractionInternal(ERepairers interaction) + { + } + + public override bool IsActive(ERepairers button) + { + return button == ERepairers.None && !this.repairController.TraderRepairers.Any(); + } + + public override IResult IsInteractive(ERepairers button) + { + return new FailedResult("No repairers?", 0); + } + + public override bool HasIcons + { + get { return false; } + } + + public enum ERepairers + { + None + } + private static string MakeInteractionId(string traderId) + { + return "UIFixesRepairerId:" + traderId; + } + + public static bool IsRepairInteractionId(string id) + { + return id.StartsWith("UIFixesRepairerId:"); + } +} + +public static class RepairExtensions +{ + public static bool IsRepairInteraction(this DynamicInteractionClass interaction) + { + return interaction.Id.StartsWith("UIFixesRepairerId:"); + } } diff --git a/ExtraProperties.cs b/ExtraProperties.cs index 8d81f98..bcdcdc7 100644 --- a/ExtraProperties.cs +++ b/ExtraProperties.cs @@ -2,31 +2,30 @@ using EFT.UI.DragAndDrop; using System.Runtime.CompilerServices; -namespace UIFixes +namespace UIFixes; + +public static class ExtraItemProperties { - public static class ExtraItemProperties + private static readonly ConditionalWeakTable properties = new(); + + private class Properties { - private static readonly ConditionalWeakTable properties = new(); - - private class Properties - { - public bool Reordered; - } - - public static bool GetReordered(this Item item) => properties.GetOrCreateValue(item).Reordered; - public static void SetReordered(this Item item, bool value) => properties.GetOrCreateValue(item).Reordered = value; + public bool Reordered; } - public static class ExtraTemplatedGridsViewProperties - { - private static readonly ConditionalWeakTable properties = new(); - - private class Properties - { - public bool Reordered; - } - - public static bool GetReordered(this TemplatedGridsView gridsView) => properties.GetOrCreateValue(gridsView).Reordered; - public static void SetReordered(this TemplatedGridsView gridsView, bool value) => properties.GetOrCreateValue(gridsView).Reordered = value; - } + public static bool GetReordered(this Item item) => properties.GetOrCreateValue(item).Reordered; + public static void SetReordered(this Item item, bool value) => properties.GetOrCreateValue(item).Reordered = value; +} + +public static class ExtraTemplatedGridsViewProperties +{ + private static readonly ConditionalWeakTable properties = new(); + + private class Properties + { + public bool Reordered; + } + + public static bool GetReordered(this TemplatedGridsView gridsView) => properties.GetOrCreateValue(gridsView).Reordered; + public static void SetReordered(this TemplatedGridsView gridsView, bool value) => properties.GetOrCreateValue(gridsView).Reordered = value; } diff --git a/Multiselect/DrawMultiSelect.cs b/Multiselect/DrawMultiSelect.cs index b227033..2f765e2 100644 --- a/Multiselect/DrawMultiSelect.cs +++ b/Multiselect/DrawMultiSelect.cs @@ -9,236 +9,235 @@ using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.UI; -namespace UIFixes +namespace UIFixes; + +public class DrawMultiSelect : MonoBehaviour { - public class DrawMultiSelect : MonoBehaviour + private Texture2D selectTexture; + + private Vector3 selectOrigin; + private Vector3 selectEnd; + + private GraphicRaycaster localRaycaster; + private GraphicRaycaster preloaderRaycaster; + + private bool drawing; + private bool secondary; + + public void Start() { - private Texture2D selectTexture; + selectTexture = new Texture2D(1, 1); + selectTexture.SetPixel(0, 0, new Color(1f, 1f, 1f, 0.6f)); + selectTexture.Apply(); - private Vector3 selectOrigin; - private Vector3 selectEnd; - - private GraphicRaycaster localRaycaster; - private GraphicRaycaster preloaderRaycaster; - - private bool drawing; - private bool secondary; - - public void Start() + localRaycaster = GetComponentInParent(); + if (localRaycaster == null) { - selectTexture = new Texture2D(1, 1); - selectTexture.SetPixel(0, 0, new Color(1f, 1f, 1f, 0.6f)); - selectTexture.Apply(); - - localRaycaster = GetComponentInParent(); - if (localRaycaster == null) - { - throw new InvalidOperationException("DrawMultiSelect couldn't find GraphicRayCaster in parents"); - } - - preloaderRaycaster = Singleton.Instance.transform.GetChild(0).GetComponent(); - if (preloaderRaycaster == null) - { - throw new InvalidOperationException("DrawMultiSelect couldn't find the PreloaderUI GraphicRayCaster"); - } + throw new InvalidOperationException("DrawMultiSelect couldn't find GraphicRayCaster in parents"); } - public void OnDisable() + preloaderRaycaster = Singleton.Instance.transform.GetChild(0).GetComponent(); + if (preloaderRaycaster == null) { - drawing = false; - MultiSelect.Clear(); - } - - public void Update() - { - if (!MultiSelect.Enabled) - { - return; - } - - // checking ItemUiContext is a quick and easy way to know the mouse is over an item - if (Input.GetKeyDown(Settings.SelectionBoxKey.Value.MainKey) && ItemUiContext.Instance.R().ItemContext == null) - { - PointerEventData eventData = new(EventSystem.current) - { - position = Input.mousePosition - }; - - List results = []; - localRaycaster.Raycast(eventData, results); - preloaderRaycaster.Raycast(eventData, results); - - foreach (GameObject gameObject in results.Select(r => r.gameObject)) - { - 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 - - var clickables = gameObject.GetComponents() - .Where(c => c is IPointerClickHandler || c is IPointerDownHandler || c is IPointerUpHandler); - - if (draggables.Any() || clickables.Any()) - { - return; - } - } - - selectOrigin = Input.mousePosition; - drawing = true; - secondary = Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift); - } - - if (drawing) - { - selectEnd = Input.mousePosition; - - Rect selectRect = new(selectOrigin, selectEnd - selectOrigin); - - // If not secondary, then we can kick out any non-rendered items, plus they won't be covered by the foreach below - if (!secondary) - { - MultiSelect.Prune(); - } - - foreach (GridItemView gridItemView in transform.root.GetComponentsInChildren().Concat(Singleton.Instance.GetComponentsInChildren())) - { - RectTransform itemTransform = gridItemView.GetComponent(); - Rect itemRect = new((Vector2)itemTransform.position + itemTransform.rect.position * itemTransform.lossyScale, itemTransform.rect.size * itemTransform.lossyScale); - - if (selectRect.Overlaps(itemRect, true)) - { - // Don't re-raycast already selected items - if there were visible before they still are - if (MultiSelect.IsSelected(gridItemView, secondary)) - { - continue; - } - - // Otherwise, ensure it's not overlapped by window UI - PointerEventData eventData = new(EventSystem.current); - - if (IsOnTop(itemRect, itemTransform, preloaderRaycaster)) // no preloaderUI on top of this? - { - if (itemTransform.IsDescendantOf(Singleton.Instance.transform)) - { - MultiSelect.Select(gridItemView, secondary); - continue; - } - - if (IsOnTop(itemRect, itemTransform, localRaycaster)) // no local UI on top of this? - { - MultiSelect.Select(gridItemView, secondary); - continue; - } - } - } - - MultiSelect.Deselect(gridItemView, secondary); - } - } - - if (drawing && !Input.GetKey(Settings.SelectionBoxKey.Value.MainKey)) - { - drawing = false; - if (secondary) - { - MultiSelect.CombineSecondary(); - secondary = false; - } - } - } - - public void OnGUI() - { - if (drawing) - { - // Invert Y because GUI has upper-left origin - Rect area = new(selectOrigin.x, Screen.height - selectOrigin.y, selectEnd.x - selectOrigin.x, selectOrigin.y - selectEnd.y); - - Rect lineArea = area; - lineArea.height = 1; // Top - GUI.DrawTexture(lineArea, selectTexture); - - lineArea.y = area.yMax - 1; // Bottom - GUI.DrawTexture(lineArea, selectTexture); - - lineArea = area; - lineArea.width = 1; // Left - GUI.DrawTexture(lineArea, selectTexture); - - lineArea.x = area.xMax - 1; // Right - GUI.DrawTexture(lineArea, selectTexture); - } - } - - private bool IsOnTop(Rect itemRect, Transform itemTransform, GraphicRaycaster raycaster) - { - // Otherwise, ensure it's not overlapped by window UI - PointerEventData eventData = new(EventSystem.current); - - float widthMargin = 0.1f * (itemRect.xMax - itemRect.xMin); - float heightMargin = 0.1f * (itemRect.yMax - itemRect.yMin); - - List raycastResults = []; - - // Lower left - eventData.position = new Vector2(itemRect.xMin + widthMargin, itemRect.yMin + heightMargin); - raycaster.Raycast(eventData, raycastResults); - if (raycastResults.Any() && !raycastResults[0].gameObject.transform.IsDescendantOf(itemTransform)) - { - return false; - } - - // Upper left - raycastResults.Clear(); - eventData.position = new Vector2(itemRect.xMin + widthMargin, itemRect.yMax - heightMargin); - raycaster.Raycast(eventData, raycastResults); - if (raycastResults.Any() && !raycastResults[0].gameObject.transform.IsDescendantOf(itemTransform)) - { - return false; - } - - // Upper right - raycastResults.Clear(); - eventData.position = new Vector2(itemRect.xMax - widthMargin, itemRect.yMax - heightMargin); - raycaster.Raycast(eventData, raycastResults); - if (raycastResults.Any() && !raycastResults[0].gameObject.transform.IsDescendantOf(itemTransform)) - { - return false; - } - - // Lower right - raycastResults.Clear(); - eventData.position = new Vector2(itemRect.xMax - widthMargin, itemRect.yMin + heightMargin); - raycaster.Raycast(eventData, raycastResults); - if (raycastResults.Any() && !raycastResults[0].gameObject.transform.IsDescendantOf(itemTransform)) - { - return false; - } - - return true; + throw new InvalidOperationException("DrawMultiSelect couldn't find the PreloaderUI GraphicRayCaster"); } } - public static class TransformExtensions + public void OnDisable() { - public static bool IsDescendantOf(this Transform transform, Transform target) + drawing = false; + MultiSelect.Clear(); + } + + public void Update() + { + if (!MultiSelect.Enabled) { + return; + } + + // checking ItemUiContext is a quick and easy way to know the mouse is over an item + if (Input.GetKeyDown(Settings.SelectionBoxKey.Value.MainKey) && ItemUiContext.Instance.R().ItemContext == null) + { + PointerEventData eventData = new(EventSystem.current) + { + position = Input.mousePosition + }; + + List results = []; + localRaycaster.Raycast(eventData, results); + preloaderRaycaster.Raycast(eventData, results); + + foreach (GameObject gameObject in results.Select(r => r.gameObject)) + { + 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 + + var clickables = gameObject.GetComponents() + .Where(c => c is IPointerClickHandler || c is IPointerDownHandler || c is IPointerUpHandler); + + if (draggables.Any() || clickables.Any()) + { + return; + } + } + + selectOrigin = Input.mousePosition; + drawing = true; + secondary = Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift); + } + + if (drawing) + { + selectEnd = Input.mousePosition; + + Rect selectRect = new(selectOrigin, selectEnd - selectOrigin); + + // If not secondary, then we can kick out any non-rendered items, plus they won't be covered by the foreach below + if (!secondary) + { + MultiSelect.Prune(); + } + + foreach (GridItemView gridItemView in transform.root.GetComponentsInChildren().Concat(Singleton.Instance.GetComponentsInChildren())) + { + RectTransform itemTransform = gridItemView.GetComponent(); + Rect itemRect = new((Vector2)itemTransform.position + itemTransform.rect.position * itemTransform.lossyScale, itemTransform.rect.size * itemTransform.lossyScale); + + if (selectRect.Overlaps(itemRect, true)) + { + // Don't re-raycast already selected items - if there were visible before they still are + if (MultiSelect.IsSelected(gridItemView, secondary)) + { + continue; + } + + // Otherwise, ensure it's not overlapped by window UI + PointerEventData eventData = new(EventSystem.current); + + if (IsOnTop(itemRect, itemTransform, preloaderRaycaster)) // no preloaderUI on top of this? + { + if (itemTransform.IsDescendantOf(Singleton.Instance.transform)) + { + MultiSelect.Select(gridItemView, secondary); + continue; + } + + if (IsOnTop(itemRect, itemTransform, localRaycaster)) // no local UI on top of this? + { + MultiSelect.Select(gridItemView, secondary); + continue; + } + } + } + + MultiSelect.Deselect(gridItemView, secondary); + } + } + + if (drawing && !Input.GetKey(Settings.SelectionBoxKey.Value.MainKey)) + { + drawing = false; + if (secondary) + { + MultiSelect.CombineSecondary(); + secondary = false; + } + } + } + + public void OnGUI() + { + if (drawing) + { + // Invert Y because GUI has upper-left origin + Rect area = new(selectOrigin.x, Screen.height - selectOrigin.y, selectEnd.x - selectOrigin.x, selectOrigin.y - selectEnd.y); + + Rect lineArea = area; + lineArea.height = 1; // Top + GUI.DrawTexture(lineArea, selectTexture); + + lineArea.y = area.yMax - 1; // Bottom + GUI.DrawTexture(lineArea, selectTexture); + + lineArea = area; + lineArea.width = 1; // Left + GUI.DrawTexture(lineArea, selectTexture); + + lineArea.x = area.xMax - 1; // Right + GUI.DrawTexture(lineArea, selectTexture); + } + } + + private bool IsOnTop(Rect itemRect, Transform itemTransform, GraphicRaycaster raycaster) + { + // Otherwise, ensure it's not overlapped by window UI + PointerEventData eventData = new(EventSystem.current); + + float widthMargin = 0.1f * (itemRect.xMax - itemRect.xMin); + float heightMargin = 0.1f * (itemRect.yMax - itemRect.yMin); + + List raycastResults = []; + + // Lower left + eventData.position = new Vector2(itemRect.xMin + widthMargin, itemRect.yMin + heightMargin); + raycaster.Raycast(eventData, raycastResults); + if (raycastResults.Any() && !raycastResults[0].gameObject.transform.IsDescendantOf(itemTransform)) + { + return false; + } + + // Upper left + raycastResults.Clear(); + eventData.position = new Vector2(itemRect.xMin + widthMargin, itemRect.yMax - heightMargin); + raycaster.Raycast(eventData, raycastResults); + if (raycastResults.Any() && !raycastResults[0].gameObject.transform.IsDescendantOf(itemTransform)) + { + return false; + } + + // Upper right + raycastResults.Clear(); + eventData.position = new Vector2(itemRect.xMax - widthMargin, itemRect.yMax - heightMargin); + raycaster.Raycast(eventData, raycastResults); + if (raycastResults.Any() && !raycastResults[0].gameObject.transform.IsDescendantOf(itemTransform)) + { + return false; + } + + // Lower right + raycastResults.Clear(); + eventData.position = new Vector2(itemRect.xMax - widthMargin, itemRect.yMin + heightMargin); + raycaster.Raycast(eventData, raycastResults); + if (raycastResults.Any() && !raycastResults[0].gameObject.transform.IsDescendantOf(itemTransform)) + { + return false; + } + + return true; + } +} + +public static class TransformExtensions +{ + public static bool IsDescendantOf(this Transform transform, Transform target) + { + if (transform == target) + { + return true; + } + + while (transform.parent != null) + { + transform = transform.parent; if (transform == target) { return true; } - - while (transform.parent != null) - { - transform = transform.parent; - if (transform == target) - { - return true; - } - } - - return false; } + + return false; } } diff --git a/Multiselect/MultiGrid.cs b/Multiselect/MultiGrid.cs index f3dc954..614a702 100644 --- a/Multiselect/MultiGrid.cs +++ b/Multiselect/MultiGrid.cs @@ -5,123 +5,122 @@ using System.Collections.Generic; using System.Linq; using UnityEngine; -namespace UIFixes +namespace UIFixes; + +public static class MultiGrid { - public static class MultiGrid + private static readonly Dictionary> GridOffsets = []; + private static readonly Dictionary>> GridsByLocation = []; + + public static LocationInGrid GetGridLocation(GridItemAddress realAddress) { - private static readonly Dictionary> GridOffsets = []; - private static readonly Dictionary>> GridsByLocation = []; - - public static LocationInGrid GetGridLocation(GridItemAddress realAddress) + if (!IsMultiGrid(realAddress)) { - if (!IsMultiGrid(realAddress)) - { - return realAddress.LocationInGrid; - } - - Vector2Int gridOffset = GridOffsets[realAddress.Container.ParentItem.TemplateId][realAddress.Grid.Id]; - return new LocationInGrid(realAddress.LocationInGrid.x + gridOffset.x, realAddress.LocationInGrid.y + gridOffset.y, realAddress.LocationInGrid.r); + return realAddress.LocationInGrid; } - public static GridItemAddress GetRealAddress(StashGridClass originGrid, LocationInGrid multigridLocation) + Vector2Int gridOffset = GridOffsets[realAddress.Container.ParentItem.TemplateId][realAddress.Grid.Id]; + return new LocationInGrid(realAddress.LocationInGrid.x + gridOffset.x, realAddress.LocationInGrid.y + gridOffset.y, realAddress.LocationInGrid.r); + } + + public static GridItemAddress GetRealAddress(StashGridClass originGrid, LocationInGrid multigridLocation) + { + if (!IsMultiGrid(originGrid.ParentItem)) { - if (!IsMultiGrid(originGrid.ParentItem)) - { - // Clamp to the actual grid - multigridLocation.x = Math.Max(0, Math.Min(originGrid.GridWidth.Value, multigridLocation.x)); - multigridLocation.y = Math.Max(0, Math.Min(originGrid.GridHeight.Value, multigridLocation.y)); + // Clamp to the actual grid + multigridLocation.x = Math.Max(0, Math.Min(originGrid.GridWidth.Value, multigridLocation.x)); + multigridLocation.y = Math.Max(0, Math.Min(originGrid.GridHeight.Value, multigridLocation.y)); - return new GridItemAddress(originGrid, multigridLocation); - } - - var gridsByLocation = GridsByLocation[originGrid.ParentItem.TemplateId]; - - // Clamp to known "meta" grid - int x = Math.Max(0, Math.Min(gridsByLocation.Keys.Max(), multigridLocation.x)); - int y = Math.Max(0, Math.Min(gridsByLocation[x].Keys.Max(), multigridLocation.y)); - - // Sanity check - if (!gridsByLocation.ContainsKey(x) || !gridsByLocation[x].ContainsKey(y)) - { - // Perhaps some weird layout with gaps in the middle? Fall back to a known good - x = gridsByLocation.Keys.First(); - y = gridsByLocation[x].Keys.First(); - } - - string gridId = gridsByLocation[x][y]; - StashGridClass grid = (originGrid.ParentItem as LootItemClass).Grids.Single(g => g.Id == gridId); - Vector2Int offsets = GridOffsets[originGrid.ParentItem.TemplateId][gridId]; - - LocationInGrid location = new(x - offsets.x, y - offsets.y, multigridLocation.r); - return new GridItemAddress(grid, location); + return new GridItemAddress(originGrid, multigridLocation); } - public static void Cache(GridView initialGridView) + var gridsByLocation = GridsByLocation[originGrid.ParentItem.TemplateId]; + + // Clamp to known "meta" grid + int x = Math.Max(0, Math.Min(gridsByLocation.Keys.Max(), multigridLocation.x)); + int y = Math.Max(0, Math.Min(gridsByLocation[x].Keys.Max(), multigridLocation.y)); + + // Sanity check + if (!gridsByLocation.ContainsKey(x) || !gridsByLocation[x].ContainsKey(y)) { - if (initialGridView == null) + // Perhaps some weird layout with gaps in the middle? Fall back to a known good + x = gridsByLocation.Keys.First(); + y = gridsByLocation[x].Keys.First(); + } + + string gridId = gridsByLocation[x][y]; + StashGridClass grid = (originGrid.ParentItem as LootItemClass).Grids.Single(g => g.Id == gridId); + Vector2Int offsets = GridOffsets[originGrid.ParentItem.TemplateId][gridId]; + + LocationInGrid location = new(x - offsets.x, y - offsets.y, multigridLocation.r); + return new GridItemAddress(grid, location); + } + + public static void Cache(GridView initialGridView) + { + if (initialGridView == null) + { + return; + } + + Item parent = initialGridView.Grid.ParentItem; + if (GridOffsets.ContainsKey(parent.TemplateId) || !IsMultiGrid(parent)) + { + return; + } + + Dictionary gridOffsets = []; + Dictionary> gridsByLocation = []; + + // Sometimes the parent's pivot is 0,1; sometimes it's 0,0. Thanks BSG + RectTransform parentView = initialGridView.transform.parent.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); + + foreach (GridView gridView in parentView.GetComponentsInChildren()) + { + // Get absolute offsets + 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); + + gridOffsets.Add(gridView.Grid.Id, new Vector2Int(x, y)); + + // Populate reverse lookup + for (int i = 0; i < gridView.Grid.GridWidth.Value; i++) { - return; - } - - Item parent = initialGridView.Grid.ParentItem; - if (GridOffsets.ContainsKey(parent.TemplateId) || !IsMultiGrid(parent)) - { - return; - } - - Dictionary gridOffsets = []; - Dictionary> gridsByLocation = []; - - // Sometimes the parent's pivot is 0,1; sometimes it's 0,0. Thanks BSG - RectTransform parentView = initialGridView.transform.parent.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); - - foreach (GridView gridView in parentView.GetComponentsInChildren()) - { - // Get absolute offsets - 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); - - gridOffsets.Add(gridView.Grid.Id, new Vector2Int(x, y)); - - // Populate reverse lookup - for (int i = 0; i < gridView.Grid.GridWidth.Value; i++) + if (!gridsByLocation.ContainsKey(x + i)) { - if (!gridsByLocation.ContainsKey(x + i)) - { - gridsByLocation.Add(x + i, []); - } + gridsByLocation.Add(x + i, []); + } - var rowGrids = gridsByLocation[x + i]; - for (int j = 0; j < gridView.Grid.GridHeight.Value; j++) - { - rowGrids.Add(y + j, gridView.Grid.Id); - } + var rowGrids = gridsByLocation[x + i]; + for (int j = 0; j < gridView.Grid.GridHeight.Value; j++) + { + rowGrids.Add(y + j, gridView.Grid.Id); } } - - GridOffsets.Add(parent.TemplateId, gridOffsets); - GridsByLocation.Add(parent.TemplateId, gridsByLocation); } - private static bool IsMultiGrid(GridItemAddress itemAddress) + GridOffsets.Add(parent.TemplateId, gridOffsets); + GridsByLocation.Add(parent.TemplateId, gridsByLocation); + } + + private static bool IsMultiGrid(GridItemAddress itemAddress) + { + return IsMultiGrid(itemAddress.Container.ParentItem); + } + + private static bool IsMultiGrid(Item item) + { + if (item is not LootItemClass lootItem) { - return IsMultiGrid(itemAddress.Container.ParentItem); + return false; } - private static bool IsMultiGrid(Item item) - { - if (item is not LootItemClass lootItem) - { - return false; - } - - return lootItem.Grids.Length > 1; - } + return lootItem.Grids.Length > 1; } } diff --git a/Multiselect/MultiSelect.cs b/Multiselect/MultiSelect.cs index 6821cb6..db9b4a3 100644 --- a/Multiselect/MultiSelect.cs +++ b/Multiselect/MultiSelect.cs @@ -7,553 +7,552 @@ using System.Threading.Tasks; using TMPro; using UnityEngine; -namespace UIFixes +namespace UIFixes; + +public class MultiSelect { - public class MultiSelect + private static GameObject SelectedMarkTemplate = null; + private static GameObject SelectedBackgroundTemplate = null; + + private static readonly Dictionary SelectedItems = []; + private static readonly Dictionary SecondaryItems = []; + + private static MultiSelectItemContextTaskSerializer LoadUnloadSerializer = null; + + public static bool Enabled { - private static GameObject SelectedMarkTemplate = null; - private static GameObject SelectedBackgroundTemplate = null; - - private static readonly Dictionary SelectedItems = []; - private static readonly Dictionary SecondaryItems = []; - - private static MultiSelectItemContextTaskSerializer LoadUnloadSerializer = null; - - public static bool Enabled + get { - get - { - return Settings.EnableMultiSelect.Value && (!Plugin.InRaid() || Settings.EnableMultiSelectInRaid.Value); - } + return Settings.EnableMultiSelect.Value && (!Plugin.InRaid() || Settings.EnableMultiSelectInRaid.Value); + } + } + + public static void Initialize() + { + // Grab the selection objects from ragfair as templates + RagfairNewOfferItemView ragfairNewOfferItemView = ItemViewFactory.CreateFromPool("ragfair_layout"); + + if (SelectedMarkTemplate == null) + { + SelectedMarkTemplate = UnityEngine.Object.Instantiate(ragfairNewOfferItemView.R().SelectedMark, null, false); + UnityEngine.Object.DontDestroyOnLoad(SelectedMarkTemplate); } - public static void Initialize() + if (SelectedBackgroundTemplate == null) { - // Grab the selection objects from ragfair as templates - RagfairNewOfferItemView ragfairNewOfferItemView = ItemViewFactory.CreateFromPool("ragfair_layout"); - - if (SelectedMarkTemplate == null) - { - SelectedMarkTemplate = UnityEngine.Object.Instantiate(ragfairNewOfferItemView.R().SelectedMark, null, false); - UnityEngine.Object.DontDestroyOnLoad(SelectedMarkTemplate); - } - - if (SelectedBackgroundTemplate == null) - { - SelectedBackgroundTemplate = UnityEngine.Object.Instantiate(ragfairNewOfferItemView.R().SelectedBackground, null, false); - UnityEngine.Object.DontDestroyOnLoad(SelectedBackgroundTemplate); - } - - ragfairNewOfferItemView.ReturnToPool(); + SelectedBackgroundTemplate = UnityEngine.Object.Instantiate(ragfairNewOfferItemView.R().SelectedBackground, null, false); + UnityEngine.Object.DontDestroyOnLoad(SelectedBackgroundTemplate); } - public static void Toggle(GridItemView itemView, bool secondary = false) + ragfairNewOfferItemView.ReturnToPool(); + } + + public static void Toggle(GridItemView itemView, bool secondary = false) + { + var dictionary = secondary ? SecondaryItems : SelectedItems; + MultiSelectItemContext itemContext = dictionary.FirstOrDefault(x => x.Value == itemView).Key; + if (itemContext != null) { - var dictionary = secondary ? SecondaryItems : SelectedItems; - MultiSelectItemContext itemContext = dictionary.FirstOrDefault(x => x.Value == itemView).Key; - if (itemContext != null) + Deselect(itemContext, secondary); + } + else + { + Select(itemView, secondary); + } + } + + public static void Clear() + { + // ToList() because modifying the collection + foreach (MultiSelectItemContext itemContext in SelectedItems.Keys.ToList()) + { + Deselect(itemContext); + } + } + + public static void Select(GridItemView itemView, bool secondary = false) + { + var dictionary = secondary ? SecondaryItems : SelectedItems; + + if (itemView.IsSelectable() && !SelectedItems.Any(x => x.Key.Item == itemView.Item) && !SecondaryItems.Any(x => x.Key.Item == itemView.Item)) + { + MultiSelectItemContext itemContext = new(itemView.ItemContext, itemView.ItemRotation); + + // Subscribe to window closures to deselect + var windowContext = itemView.GetComponentInParent()?.WindowContext ?? itemView.GetComponentInParent()?.WindowContext; + if (windowContext != null) { - Deselect(itemContext, secondary); - } - else - { - Select(itemView, secondary); + windowContext.OnClose += () => Deselect(itemContext); } + + // Thread unsafe way of ensuring we don't multiple subscribe. I'm sure it's fine. + itemContext.Item.Owner.AddItemEvent -= OnItemAdded; + itemContext.Item.Owner.AddItemEvent += OnItemAdded; + + // Cache the gridview in case we need it + MultiGrid.Cache(itemView.Container as GridView); + + dictionary.Add(itemContext, itemView); + ShowSelection(itemView); + } + } + + public static void Deselect(MultiSelectItemContext itemContext, bool secondary = false) + { + var dictionary = secondary ? SecondaryItems : SelectedItems; + + if (dictionary.TryGetValue(itemContext, out GridItemView itemView)) + { + HideSelection(itemView); } - public static void Clear() + dictionary.Remove(itemContext); + itemContext.Dispose(); + } + + public static void Deselect(GridItemView itemView, bool secondary = false) + { + var dictionary = secondary ? SecondaryItems : SelectedItems; + + MultiSelectItemContext itemContext = dictionary.FirstOrDefault(x => x.Value == itemView).Key; + if (itemContext != null) { - // ToList() because modifying the collection - foreach (MultiSelectItemContext itemContext in SelectedItems.Keys.ToList()) - { - Deselect(itemContext); - } - } - - public static void Select(GridItemView itemView, bool secondary = false) - { - var dictionary = secondary ? SecondaryItems : SelectedItems; - - if (itemView.IsSelectable() && !SelectedItems.Any(x => x.Key.Item == itemView.Item) && !SecondaryItems.Any(x => x.Key.Item == itemView.Item)) - { - MultiSelectItemContext itemContext = new(itemView.ItemContext, itemView.ItemRotation); - - // Subscribe to window closures to deselect - var windowContext = itemView.GetComponentInParent()?.WindowContext ?? itemView.GetComponentInParent()?.WindowContext; - if (windowContext != null) - { - windowContext.OnClose += () => Deselect(itemContext); - } - - // Thread unsafe way of ensuring we don't multiple subscribe. I'm sure it's fine. - itemContext.Item.Owner.AddItemEvent -= OnItemAdded; - itemContext.Item.Owner.AddItemEvent += OnItemAdded; - - // Cache the gridview in case we need it - MultiGrid.Cache(itemView.Container as GridView); - - dictionary.Add(itemContext, itemView); - ShowSelection(itemView); - } - } - - public static void Deselect(MultiSelectItemContext itemContext, bool secondary = false) - { - var dictionary = secondary ? SecondaryItems : SelectedItems; - - if (dictionary.TryGetValue(itemContext, out GridItemView itemView)) - { - HideSelection(itemView); - } - dictionary.Remove(itemContext); itemContext.Dispose(); + HideSelection(itemView); + } + } + + public static void OnKillItemView(GridItemView itemView) + { + MultiSelectItemContext itemContext = SelectedItems.FirstOrDefault(x => x.Value == itemView).Key; + if (itemContext != null) + { + SelectedItems[itemContext] = null; + HideSelection(itemView); + } + } + + public static void OnNewItemView(GridItemView itemView) + { + if (!itemView.IsSelectable()) + { + return; } - public static void Deselect(GridItemView itemView, bool secondary = false) + MultiSelectItemContext itemContext = SelectedItems.FirstOrDefault(x => x.Key.Item == itemView.Item).Key; + if (itemContext != null) { - var dictionary = secondary ? SecondaryItems : SelectedItems; + // Refresh the context. Note that the address might still be old + Deselect(itemContext); + Select(itemView); + } + } - MultiSelectItemContext itemContext = dictionary.FirstOrDefault(x => x.Value == itemView).Key; - if (itemContext != null) + // Occurs when an item is added somewhere. If it's from a move, and that item was multiselected, + // the context needs to be updated with the new address + private static void OnItemAdded(GEventArgs2 eventArgs) + { + if (eventArgs.Status != CommandStatus.Succeed) + { + return; + } + + MultiSelectItemContext oldItemContext = SelectedItems.FirstOrDefault(x => x.Key.Item == eventArgs.Item).Key; + if (oldItemContext != null) + { + MultiSelectItemContext newContext = oldItemContext.Refresh(); + SelectedItems.Add(newContext, SelectedItems[oldItemContext]); + + SelectedItems.Remove(oldItemContext); + oldItemContext.Dispose(); + } + } + + public static bool IsSelected(GridItemView itemView, bool secondary = false) + { + var dictionary = secondary ? SecondaryItems : SelectedItems; + return dictionary.Any(x => x.Key.Item == itemView.Item); + } + + public static void Prune() + { + foreach (var entry in SelectedItems.ToList()) + { + if (entry.Value == null) { - dictionary.Remove(itemContext); - itemContext.Dispose(); - HideSelection(itemView); + Deselect(entry.Key); + } + } + } + + public static void CombineSecondary() + { + foreach (var entry in SecondaryItems) + { + SelectedItems.Add(entry.Key, entry.Value); + } + + SecondaryItems.Clear(); + } + + public static IEnumerable ItemContexts + { + get { return SelectedItems.Keys; } + } + + public static IEnumerable SecondaryContexts + { + get { return SecondaryItems.Keys; } + } + + public static int Count + { + get { return SelectedItems.Count; } + } + + public static int SecondaryCount + { + get { return SecondaryItems.Count; } + } + + public static bool Active + { + get { return SelectedItems.Count > 0; } + } + + // Sort the items to prioritize the items that share a grid with the dragged item, prepend the dragContext as the first one + // Can pass no itemContext, and it just sorts items by their grid order + public static IEnumerable SortedItemContexts(DragItemContext first = null, bool prepend = true) + { + static int gridOrder(LocationInGrid loc) => 100 * loc.y + loc.x; + + var result = SelectedItems.Keys + .Where(ic => first == null || ic.Item != first.Item) + .OrderByDescending(ic => ic.ItemAddress is GridItemAddress) + .ThenByDescending(ic => first != null && first.ItemAddress.Container.ParentItem == ic.ItemAddress.Container.ParentItem) + .ThenBy(ic => ic.ItemAddress is GridItemAddress selectedGridAddress ? gridOrder(MultiGrid.GetGridLocation(selectedGridAddress)) : 0); + + if (first != null && prepend) + { + MultiSelectItemContext multiSelectItemContext = SelectedItems.Keys.FirstOrDefault(c => c.Item == first.Item); + if (multiSelectItemContext != null) + { + multiSelectItemContext.UpdateDragContext(first); + return result.Prepend(multiSelectItemContext); } } - public static void OnKillItemView(GridItemView itemView) + return result; + } + + public static void ShowDragCount(DraggedItemView draggedItemView) + { + if (draggedItemView != null && Count > 1) { - MultiSelectItemContext itemContext = SelectedItems.FirstOrDefault(x => x.Value == itemView).Key; - if (itemContext != null) - { - SelectedItems[itemContext] = null; - HideSelection(itemView); - } + GameObject textOverlay = new("MultiSelectText", [typeof(RectTransform), typeof(TextMeshProUGUI)]); + textOverlay.transform.parent = draggedItemView.transform; + textOverlay.transform.SetAsLastSibling(); + textOverlay.SetActive(true); + + RectTransform overlayRect = textOverlay.GetComponent(); + overlayRect.anchorMin = Vector2.zero; + overlayRect.anchorMax = Vector2.one; + overlayRect.anchoredPosition = new Vector2(0.5f, 0.5f); + + TextMeshProUGUI text = textOverlay.GetComponent(); + text.text = MultiSelect.Count.ToString(); + text.fontSize = 36; + text.alignment = TextAlignmentOptions.Baseline; + } + } + + public static int InteractionCount(EItemInfoButton interaction, ItemUiContext itemUiContext) + { + return ItemContexts.Count(ic => InteractionAvailable(ic, interaction, itemUiContext)); + } + + private static bool InteractionAvailable(DragItemContext itemContext, EItemInfoButton interaction, ItemUiContext itemUiContext) + { + // Since itemContext is for "drag", no context actions are allowed. Get the underlying "inventory" context + ItemContextAbstractClass innerContext = itemContext.ItemContextAbstractClass; + if (innerContext == null) + { + return false; } - public static void OnNewItemView(GridItemView itemView) + bool createdContext = false; + if (innerContext.Item != itemContext.Item) { - if (!itemView.IsSelectable()) - { - return; - } - - MultiSelectItemContext itemContext = SelectedItems.FirstOrDefault(x => x.Key.Item == itemView.Item).Key; - if (itemContext != null) - { - // Refresh the context. Note that the address might still be old - Deselect(itemContext); - Select(itemView); - } + // Actual context went away and we're looking at inventory/stash context + innerContext = innerContext.CreateChild(itemContext.Item); + createdContext = true; } - // Occurs when an item is added somewhere. If it's from a move, and that item was multiselected, - // the context needs to be updated with the new address - private static void OnItemAdded(GEventArgs2 eventArgs) + var contextInteractions = itemUiContext.GetItemContextInteractions(innerContext, null); + bool result = contextInteractions.IsInteractionAvailable(interaction); + + if (createdContext) { - if (eventArgs.Status != CommandStatus.Succeed) - { - return; - } - - MultiSelectItemContext oldItemContext = SelectedItems.FirstOrDefault(x => x.Key.Item == eventArgs.Item).Key; - if (oldItemContext != null) - { - MultiSelectItemContext newContext = oldItemContext.Refresh(); - SelectedItems.Add(newContext, SelectedItems[oldItemContext]); - - SelectedItems.Remove(oldItemContext); - oldItemContext.Dispose(); - } + innerContext.Dispose(); } - public static bool IsSelected(GridItemView itemView, bool secondary = false) - { - var dictionary = secondary ? SecondaryItems : SelectedItems; - return dictionary.Any(x => x.Key.Item == itemView.Item); - } + return result; + } - public static void Prune() + public static void EquipAll(ItemUiContext itemUiContext, bool allOrNothing) + { + if (!allOrNothing || InteractionCount(EItemInfoButton.Equip, itemUiContext) == Count) { - foreach (var entry in SelectedItems.ToList()) - { - if (entry.Value == null) + var taskSerializer = itemUiContext.gameObject.AddComponent(); + taskSerializer.Initialize( + SortedItemContexts().Where(ic => InteractionAvailable(ic, EItemInfoButton.Equip, itemUiContext)), + itemContext => itemUiContext.QuickEquip(itemContext.Item)); + + itemUiContext.Tooltip?.Close(); + } + } + + public static void UnequipAll(ItemUiContext itemUiContext, bool allOrNothing) + { + if (!allOrNothing || InteractionCount(EItemInfoButton.Unequip, itemUiContext) == Count) + { + var taskSerializer = itemUiContext.gameObject.AddComponent(); + taskSerializer.Initialize( + SortedItemContexts().Where(ic => InteractionAvailable(ic, EItemInfoButton.Unequip, itemUiContext)), + itemContext => itemUiContext.Uninstall(itemContext.ItemContextAbstractClass)); + + itemUiContext.Tooltip?.Close(); + } + } + + public static Task LoadAmmoAll(ItemUiContext itemUiContext, string ammoTemplateId, bool allOrNothing) + { + StopLoading(true); + if (!allOrNothing || InteractionCount(EItemInfoButton.LoadAmmo, itemUiContext) == Count) + { + LoadUnloadSerializer = itemUiContext.gameObject.AddComponent(); + Task result = LoadUnloadSerializer.Initialize( + SortedItemContexts() + .Where(ic => ic.Item is MagazineClass && InteractionAvailable(ic, EItemInfoButton.LoadAmmo, itemUiContext)) + .SelectMany(ic => ic.RepeatUntilFull()), + itemContext => { - Deselect(entry.Key); - } - } + IgnoreStopLoading = true; + return itemUiContext.LoadAmmoByType(itemContext.Item as MagazineClass, ammoTemplateId, itemContext.UpdateView); + }); + + itemUiContext.Tooltip?.Close(); + + return result.ContinueWith(t => LoadUnloadSerializer = null); } - public static void CombineSecondary() + return Task.CompletedTask; + } + + public static void UnloadAmmoAll(ItemUiContext itemUiContext, bool allOrNothing) + { + StopLoading(true); + if (!allOrNothing || InteractionCount(EItemInfoButton.UnloadAmmo, itemUiContext) == Count) { - foreach (var entry in SecondaryItems) - { - SelectedItems.Add(entry.Key, entry.Value); - } - - SecondaryItems.Clear(); - } - - public static IEnumerable ItemContexts - { - get { return SelectedItems.Keys; } - } - - public static IEnumerable SecondaryContexts - { - get { return SecondaryItems.Keys; } - } - - public static int Count - { - get { return SelectedItems.Count; } - } - - public static int SecondaryCount - { - get { return SecondaryItems.Count; } - } - - public static bool Active - { - get { return SelectedItems.Count > 0; } - } - - // Sort the items to prioritize the items that share a grid with the dragged item, prepend the dragContext as the first one - // Can pass no itemContext, and it just sorts items by their grid order - public static IEnumerable SortedItemContexts(DragItemContext first = null, bool prepend = true) - { - static int gridOrder(LocationInGrid loc) => 100 * loc.y + loc.x; - - var result = SelectedItems.Keys - .Where(ic => first == null || ic.Item != first.Item) - .OrderByDescending(ic => ic.ItemAddress is GridItemAddress) - .ThenByDescending(ic => first != null && first.ItemAddress.Container.ParentItem == ic.ItemAddress.Container.ParentItem) - .ThenBy(ic => ic.ItemAddress is GridItemAddress selectedGridAddress ? gridOrder(MultiGrid.GetGridLocation(selectedGridAddress)) : 0); - - if (first != null && prepend) - { - MultiSelectItemContext multiSelectItemContext = SelectedItems.Keys.FirstOrDefault(c => c.Item == first.Item); - if (multiSelectItemContext != null) + LoadUnloadSerializer = itemUiContext.gameObject.AddComponent(); + LoadUnloadSerializer.Initialize( + SortedItemContexts().Where(ic => InteractionAvailable(ic, EItemInfoButton.UnloadAmmo, itemUiContext)), + itemContext => { - multiSelectItemContext.UpdateDragContext(first); - return result.Prepend(multiSelectItemContext); - } - } - - return result; - } - - public static void ShowDragCount(DraggedItemView draggedItemView) - { - if (draggedItemView != null && Count > 1) - { - GameObject textOverlay = new("MultiSelectText", [typeof(RectTransform), typeof(TextMeshProUGUI)]); - textOverlay.transform.parent = draggedItemView.transform; - textOverlay.transform.SetAsLastSibling(); - textOverlay.SetActive(true); - - RectTransform overlayRect = textOverlay.GetComponent(); - overlayRect.anchorMin = Vector2.zero; - overlayRect.anchorMax = Vector2.one; - overlayRect.anchoredPosition = new Vector2(0.5f, 0.5f); - - TextMeshProUGUI text = textOverlay.GetComponent(); - text.text = MultiSelect.Count.ToString(); - text.fontSize = 36; - text.alignment = TextAlignmentOptions.Baseline; - } - } - - public static int InteractionCount(EItemInfoButton interaction, ItemUiContext itemUiContext) - { - return ItemContexts.Count(ic => InteractionAvailable(ic, interaction, itemUiContext)); - } - - private static bool InteractionAvailable(DragItemContext itemContext, EItemInfoButton interaction, ItemUiContext itemUiContext) - { - // Since itemContext is for "drag", no context actions are allowed. Get the underlying "inventory" context - ItemContextAbstractClass innerContext = itemContext.ItemContextAbstractClass; - if (innerContext == null) - { - return false; - } - - bool createdContext = false; - if (innerContext.Item != itemContext.Item) - { - // Actual context went away and we're looking at inventory/stash context - innerContext = innerContext.CreateChild(itemContext.Item); - createdContext = true; - } - - var contextInteractions = itemUiContext.GetItemContextInteractions(innerContext, null); - bool result = contextInteractions.IsInteractionAvailable(interaction); - - if (createdContext) - { - innerContext.Dispose(); - } - - return result; - } - - public static void EquipAll(ItemUiContext itemUiContext, bool allOrNothing) - { - if (!allOrNothing || InteractionCount(EItemInfoButton.Equip, itemUiContext) == Count) - { - var taskSerializer = itemUiContext.gameObject.AddComponent(); - taskSerializer.Initialize( - SortedItemContexts().Where(ic => InteractionAvailable(ic, EItemInfoButton.Equip, itemUiContext)), - itemContext => itemUiContext.QuickEquip(itemContext.Item)); - - itemUiContext.Tooltip?.Close(); - } - } - - public static void UnequipAll(ItemUiContext itemUiContext, bool allOrNothing) - { - if (!allOrNothing || InteractionCount(EItemInfoButton.Unequip, itemUiContext) == Count) - { - var taskSerializer = itemUiContext.gameObject.AddComponent(); - taskSerializer.Initialize( - SortedItemContexts().Where(ic => InteractionAvailable(ic, EItemInfoButton.Unequip, itemUiContext)), - itemContext => itemUiContext.Uninstall(itemContext.ItemContextAbstractClass)); - - itemUiContext.Tooltip?.Close(); - } - } - - public static Task LoadAmmoAll(ItemUiContext itemUiContext, string ammoTemplateId, bool allOrNothing) - { - StopLoading(true); - if (!allOrNothing || InteractionCount(EItemInfoButton.LoadAmmo, itemUiContext) == Count) - { - LoadUnloadSerializer = itemUiContext.gameObject.AddComponent(); - Task result = LoadUnloadSerializer.Initialize( - SortedItemContexts() - .Where(ic => ic.Item is MagazineClass && InteractionAvailable(ic, EItemInfoButton.LoadAmmo, itemUiContext)) - .SelectMany(ic => ic.RepeatUntilFull()), - itemContext => - { - IgnoreStopLoading = true; - return itemUiContext.LoadAmmoByType(itemContext.Item as MagazineClass, ammoTemplateId, itemContext.UpdateView); - }); - - itemUiContext.Tooltip?.Close(); - - return result.ContinueWith(t => LoadUnloadSerializer = null); - } - - return Task.CompletedTask; - } - - public static void UnloadAmmoAll(ItemUiContext itemUiContext, bool allOrNothing) - { - StopLoading(true); - if (!allOrNothing || InteractionCount(EItemInfoButton.UnloadAmmo, itemUiContext) == Count) - { - LoadUnloadSerializer = itemUiContext.gameObject.AddComponent(); - LoadUnloadSerializer.Initialize( - SortedItemContexts().Where(ic => InteractionAvailable(ic, EItemInfoButton.UnloadAmmo, itemUiContext)), - itemContext => - { - if (itemContext.Item is AmmoBox) - { - Deselect(itemContext); - } - - IgnoreStopLoading = true; - return itemUiContext.UnloadAmmo(itemContext.Item); - }).ContinueWith(t => LoadUnloadSerializer = null); - - itemUiContext.Tooltip?.Close(); - } - } - - private static bool IgnoreStopLoading = false; - - public static void StopLoading(bool force = false) - { - if (LoadUnloadSerializer == null) - { - return; - } - - if (!IgnoreStopLoading || force) - { - LoadUnloadSerializer.Cancel(); - LoadUnloadSerializer = null; - } - else - { - IgnoreStopLoading = false; - } - } - - public static void UnpackAll(ItemUiContext itemUiContext, bool allOrNothing) - { - if (!allOrNothing || InteractionCount(EItemInfoButton.Unpack, itemUiContext) == Count) - { - var taskSerializer = itemUiContext.gameObject.AddComponent(); - taskSerializer.Initialize( - SortedItemContexts().Where(ic => InteractionAvailable(ic, EItemInfoButton.Unpack, itemUiContext)), - itemContext => + if (itemContext.Item is AmmoBox) { Deselect(itemContext); - return itemUiContext.UnpackItem(itemContext.Item); - }); + } - itemUiContext.Tooltip?.Close(); - } - } + IgnoreStopLoading = true; + return itemUiContext.UnloadAmmo(itemContext.Item); + }).ContinueWith(t => LoadUnloadSerializer = null); - private static void ShowSelection(GridItemView itemView) - { - GameObject selectedMark = itemView.transform.Find("SelectedMark")?.gameObject; - if (selectedMark == null) - { - selectedMark = UnityEngine.Object.Instantiate(SelectedMarkTemplate, itemView.transform, false); - selectedMark.name = "SelectedMark"; - } - - selectedMark.SetActive(true); - - GameObject selectedBackground = itemView.transform.Find("SelectedBackground")?.gameObject; - if (selectedBackground == null) - { - selectedBackground = UnityEngine.Object.Instantiate(SelectedBackgroundTemplate, itemView.transform, false); - selectedBackground.transform.SetAsFirstSibling(); - selectedBackground.name = "SelectedBackground"; - } - - selectedBackground.SetActive(true); - } - - private static void HideSelection(GridItemView itemView) - { - if (itemView == null) - { - return; - } - - GameObject selectedMark = itemView.transform.Find("SelectedMark")?.gameObject; - GameObject selectedBackground = itemView.transform.Find("SelectedBackground")?.gameObject; - - selectedMark?.SetActive(false); - selectedBackground?.SetActive(false); + itemUiContext.Tooltip?.Close(); } } - public class MultiSelectItemContext : DragItemContext + private static bool IgnoreStopLoading = false; + + public static void StopLoading(bool force = false) { - public MultiSelectItemContext(ItemContextAbstractClass itemContext, ItemRotation rotation) : base(itemContext, rotation) + if (LoadUnloadSerializer == null) { - // Adjust event handlers - if (ItemContextAbstractClass != null) - { - // Listen for underlying context being disposed, it might mean the item is gone (merged, destroyed, etc) - ItemContextAbstractClass.OnDisposed += OnParentDispose; - // This serves no purpose and causes stack overflows - ItemContextAbstractClass.OnCloseWindow -= CloseDependentWindows; - } + return; } - public MultiSelectItemContext Refresh() + if (!IgnoreStopLoading || force) { - return new MultiSelectItemContext(ItemContextAbstractClass, ItemRotation); + LoadUnloadSerializer.Cancel(); + LoadUnloadSerializer = null; } - - public void UpdateDragContext(DragItemContext itemContext) + else { - SetPosition(itemContext.CursorPosition, itemContext.ItemPosition); - ItemRotation = itemContext.ItemRotation; - } - - public override void Dispose() - { - base.Dispose(); - if (ItemContextAbstractClass != null) - { - ItemContextAbstractClass.OnDisposed -= OnParentDispose; - } - } - - private void OnParentDispose() - { - if (Item.CurrentAddress == null || Item.CurrentAddress.Container.ParentItem is MagazineClass) - { - // This item was entirely merged away, or went into a magazine - MultiSelect.Deselect(this); - } - } - - // used by ItemUiContext.QuickFindAppropriatePlace, the one that picks a container, i.e. ctrl-click - // DragItemContext (drag) defaults to None, but we want what the underlying item allows - public override bool CanQuickMoveTo(ETargetContainer targetContainer) - { - if (ItemContextAbstractClass != null) - { - return ItemContextAbstractClass.CanQuickMoveTo(targetContainer); - } - - return base.CanQuickMoveTo(targetContainer); + IgnoreStopLoading = false; } } - // Specific type of TaskSerializer because Unity can't understand generics - public class MultiSelectItemContextTaskSerializer : TaskSerializer { } - - public static class MultiSelectExtensions + public static void UnpackAll(ItemUiContext itemUiContext, bool allOrNothing) { - public static bool IsSelectable(this ItemView itemView) + if (!allOrNothing || InteractionCount(EItemInfoButton.Unpack, itemUiContext) == Count) { - // Common non-interactable stuff - if (!itemView.IsInteractable || !itemView.IsSearched || itemView.RemoveError.Value != null) - { - return false; - } - - // Ironically, SelectableSlotItemView is not selectable. Those are for picking as a choice - if (itemView is SelectableSlotItemView) - { - return false; - } - - // You can't multi-select trader's items or items being sold - if (itemView is TradingItemView tradingItemView) - { - if (itemView is not TradingPlayerItemView || tradingItemView.R().IsBeingSold) + var taskSerializer = itemUiContext.gameObject.AddComponent(); + taskSerializer.Initialize( + SortedItemContexts().Where(ic => InteractionAvailable(ic, EItemInfoButton.Unpack, itemUiContext)), + itemContext => { - return false; - } - } + Deselect(itemContext); + return itemUiContext.UnpackItem(itemContext.Item); + }); - return true; + itemUiContext.Tooltip?.Close(); + } + } + + private static void ShowSelection(GridItemView itemView) + { + GameObject selectedMark = itemView.transform.Find("SelectedMark")?.gameObject; + if (selectedMark == null) + { + selectedMark = UnityEngine.Object.Instantiate(SelectedMarkTemplate, itemView.transform, false); + selectedMark.name = "SelectedMark"; } - public static IEnumerable RepeatUntilEmpty(this T itemContext) where T : ItemContextAbstractClass + selectedMark.SetActive(true); + + GameObject selectedBackground = itemView.transform.Find("SelectedBackground")?.gameObject; + if (selectedBackground == null) { - while (itemContext.Item.StackObjectsCount > 0) + selectedBackground = UnityEngine.Object.Instantiate(SelectedBackgroundTemplate, itemView.transform, false); + selectedBackground.transform.SetAsFirstSibling(); + selectedBackground.name = "SelectedBackground"; + } + + selectedBackground.SetActive(true); + } + + private static void HideSelection(GridItemView itemView) + { + if (itemView == null) + { + return; + } + + GameObject selectedMark = itemView.transform.Find("SelectedMark")?.gameObject; + GameObject selectedBackground = itemView.transform.Find("SelectedBackground")?.gameObject; + + selectedMark?.SetActive(false); + selectedBackground?.SetActive(false); + } +} + +public class MultiSelectItemContext : DragItemContext +{ + public MultiSelectItemContext(ItemContextAbstractClass itemContext, ItemRotation rotation) : base(itemContext, rotation) + { + // Adjust event handlers + if (ItemContextAbstractClass != null) + { + // Listen for underlying context being disposed, it might mean the item is gone (merged, destroyed, etc) + ItemContextAbstractClass.OnDisposed += OnParentDispose; + // This serves no purpose and causes stack overflows + ItemContextAbstractClass.OnCloseWindow -= CloseDependentWindows; + } + } + + public MultiSelectItemContext Refresh() + { + return new MultiSelectItemContext(ItemContextAbstractClass, ItemRotation); + } + + public void UpdateDragContext(DragItemContext itemContext) + { + SetPosition(itemContext.CursorPosition, itemContext.ItemPosition); + ItemRotation = itemContext.ItemRotation; + } + + public override void Dispose() + { + base.Dispose(); + if (ItemContextAbstractClass != null) + { + ItemContextAbstractClass.OnDisposed -= OnParentDispose; + } + } + + private void OnParentDispose() + { + if (Item.CurrentAddress == null || Item.CurrentAddress.Container.ParentItem is MagazineClass) + { + // This item was entirely merged away, or went into a magazine + MultiSelect.Deselect(this); + } + } + + // used by ItemUiContext.QuickFindAppropriatePlace, the one that picks a container, i.e. ctrl-click + // DragItemContext (drag) defaults to None, but we want what the underlying item allows + public override bool CanQuickMoveTo(ETargetContainer targetContainer) + { + if (ItemContextAbstractClass != null) + { + return ItemContextAbstractClass.CanQuickMoveTo(targetContainer); + } + + return base.CanQuickMoveTo(targetContainer); + } +} + +// Specific type of TaskSerializer because Unity can't understand generics +public class MultiSelectItemContextTaskSerializer : TaskSerializer { } + +public static class MultiSelectExtensions +{ + public static bool IsSelectable(this ItemView itemView) + { + // Common non-interactable stuff + if (!itemView.IsInteractable || !itemView.IsSearched || itemView.RemoveError.Value != null) + { + return false; + } + + // Ironically, SelectableSlotItemView is not selectable. Those are for picking as a choice + if (itemView is SelectableSlotItemView) + { + return false; + } + + // You can't multi-select trader's items or items being sold + if (itemView is TradingItemView tradingItemView) + { + if (itemView is not TradingPlayerItemView || tradingItemView.R().IsBeingSold) { + return false; + } + } + + return true; + } + + public static IEnumerable RepeatUntilEmpty(this T itemContext) where T : ItemContextAbstractClass + { + while (itemContext.Item.StackObjectsCount > 0) + { + yield return itemContext; + } + } + + public static IEnumerable RepeatUntilFull(this T itemContext) where T : ItemContextAbstractClass + { + if (itemContext.Item is MagazineClass magazine) + { + int ammoCount = -1; + while (magazine.Count > ammoCount && magazine.Count < magazine.MaxCount) + { + ammoCount = magazine.Count; yield return itemContext; } } - - public static IEnumerable RepeatUntilFull(this T itemContext) where T : ItemContextAbstractClass - { - if (itemContext.Item is MagazineClass magazine) - { - int ammoCount = -1; - while (magazine.Count > ammoCount && magazine.Count < magazine.MaxCount) - { - ammoCount = magazine.Count; - yield return itemContext; - } - } - } } } diff --git a/Multiselect/MultiSelectDebug.cs b/Multiselect/MultiSelectDebug.cs index c397333..2660b8a 100644 --- a/Multiselect/MultiSelectDebug.cs +++ b/Multiselect/MultiSelectDebug.cs @@ -2,62 +2,61 @@ using System.Text; using UnityEngine; -namespace UIFixes +namespace UIFixes; + +public class MultiSelectDebug : MonoBehaviour { - public class MultiSelectDebug : MonoBehaviour + private GUIStyle guiStyle; + private Rect guiRect = new(20, 70, 0, 0); + + GUIContent guiContent; + + public void OnGUI() { - private GUIStyle guiStyle; - private Rect guiRect = new(20, 70, 0, 0); - - GUIContent guiContent; - - public void OnGUI() + if (!MultiSelect.Enabled || !Settings.ShowMultiSelectDebug.Value) { - if (!MultiSelect.Enabled || !Settings.ShowMultiSelectDebug.Value) - { - return; - } - - guiStyle ??= new GUIStyle(GUI.skin.box) - { - alignment = TextAnchor.MiddleLeft, - fontSize = 14, - margin = new RectOffset(3, 3, 3, 3), - richText = true - }; - - guiContent ??= new GUIContent(); - - StringBuilder builder = new(); - - builder.Append("MultiSelect\n"); - builder.AppendFormat("Active: {1}\n", MultiSelect.Active ? "green" : "red", MultiSelect.Active); - builder.AppendFormat("Items: {0}\n", MultiSelect.Count); - - foreach (DragItemContext itemContext in MultiSelect.SortedItemContexts()) - { - LocationInGrid location = itemContext.ItemAddress is GridItemAddress gridAddress ? MultiGrid.GetGridLocation(gridAddress) : null; - builder.AppendFormat("x{0} {1} {2} {3}\n", - itemContext.Item.StackObjectsCount, - itemContext.ItemAddress.Container.ID, - location != null ? $"({location.x}, {location.y})" : "(slot)", - itemContext.Item.Name.Localized()); - } - - if (MultiSelect.SecondaryContexts.Any()) - { - builder.AppendFormat("Secondary Items: {0}\n", MultiSelect.SecondaryCount); - foreach (DragItemContext itemContext in MultiSelect.SecondaryContexts) - { - builder.AppendFormat("x{0} {1}\n", itemContext.Item.StackObjectsCount, itemContext.Item.ToString()); - } - } - - guiContent.text = builder.ToString(); - - guiRect.size = guiStyle.CalcSize(guiContent); - - GUI.Box(guiRect, guiContent, guiStyle); + return; } + + guiStyle ??= new GUIStyle(GUI.skin.box) + { + alignment = TextAnchor.MiddleLeft, + fontSize = 14, + margin = new RectOffset(3, 3, 3, 3), + richText = true + }; + + guiContent ??= new GUIContent(); + + StringBuilder builder = new(); + + builder.Append("MultiSelect\n"); + builder.AppendFormat("Active: {1}\n", MultiSelect.Active ? "green" : "red", MultiSelect.Active); + builder.AppendFormat("Items: {0}\n", MultiSelect.Count); + + foreach (DragItemContext itemContext in MultiSelect.SortedItemContexts()) + { + LocationInGrid location = itemContext.ItemAddress is GridItemAddress gridAddress ? MultiGrid.GetGridLocation(gridAddress) : null; + builder.AppendFormat("x{0} {1} {2} {3}\n", + itemContext.Item.StackObjectsCount, + itemContext.ItemAddress.Container.ID, + location != null ? $"({location.x}, {location.y})" : "(slot)", + itemContext.Item.Name.Localized()); + } + + if (MultiSelect.SecondaryContexts.Any()) + { + builder.AppendFormat("Secondary Items: {0}\n", MultiSelect.SecondaryCount); + foreach (DragItemContext itemContext in MultiSelect.SecondaryContexts) + { + builder.AppendFormat("x{0} {1}\n", itemContext.Item.StackObjectsCount, itemContext.Item.ToString()); + } + } + + guiContent.text = builder.ToString(); + + guiRect.size = guiStyle.CalcSize(guiContent); + + GUI.Box(guiRect, guiContent, guiStyle); } } diff --git a/Patches/AddOfferClickablePricesPatches.cs b/Patches/AddOfferClickablePricesPatches.cs index daa2bcd..f4ab190 100644 --- a/Patches/AddOfferClickablePricesPatches.cs +++ b/Patches/AddOfferClickablePricesPatches.cs @@ -9,90 +9,89 @@ using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.UI; -namespace UIFixes +namespace UIFixes; + +public static class AddOfferClickablePricesPatches { - public static class AddOfferClickablePricesPatches + public static void Enable() { - public static void Enable() + new AddButtonPatch().Enable(); + } + + public class AddButtonPatch : ModulePatch + { + protected override MethodBase GetTargetMethod() { - new AddButtonPatch().Enable(); + return AccessTools.Method(typeof(AddOfferWindow), nameof(AddOfferWindow.Show)); } - public class AddButtonPatch : ModulePatch + [PatchPostfix] + public static void Postfix(AddOfferWindow __instance, ItemMarketPricesPanel ____pricesPanel, RequirementView[] ____requirementViews) { - protected override MethodBase GetTargetMethod() - { - return AccessTools.Method(typeof(AddOfferWindow), nameof(AddOfferWindow.Show)); - } + var panel = ____pricesPanel.R(); - [PatchPostfix] - public static void Postfix(AddOfferWindow __instance, ItemMarketPricesPanel ____pricesPanel, RequirementView[] ____requirementViews) - { - var panel = ____pricesPanel.R(); + var rublesRequirement = ____requirementViews.First(rv => rv.name == "Requirement (RUB)"); - var rublesRequirement = ____requirementViews.First(rv => rv.name == "Requirement (RUB)"); + Button lowestButton = panel.LowestLabel.GetOrAddComponent(); + lowestButton.onClick.AddListener(() => SetRequirement(__instance, rublesRequirement, ____pricesPanel.Minimum)); + ____pricesPanel.AddDisposable(lowestButton.onClick.RemoveAllListeners); - Button lowestButton = panel.LowestLabel.GetOrAddComponent(); - lowestButton.onClick.AddListener(() => SetRequirement(__instance, rublesRequirement, ____pricesPanel.Minimum)); - ____pricesPanel.AddDisposable(lowestButton.onClick.RemoveAllListeners); + Button averageButton = panel.AverageLabel.GetOrAddComponent(); + averageButton.onClick.AddListener(() => SetRequirement(__instance, rublesRequirement, ____pricesPanel.Average)); + ____pricesPanel.AddDisposable(averageButton.onClick.RemoveAllListeners); - Button averageButton = panel.AverageLabel.GetOrAddComponent(); - averageButton.onClick.AddListener(() => SetRequirement(__instance, rublesRequirement, ____pricesPanel.Average)); - ____pricesPanel.AddDisposable(averageButton.onClick.RemoveAllListeners); + Button maximumButton = panel.MaximumLabel.GetOrAddComponent(); + maximumButton.onClick.AddListener(() => SetRequirement(__instance, rublesRequirement, ____pricesPanel.Maximum)); + ____pricesPanel.AddDisposable(maximumButton.onClick.RemoveAllListeners); + } + } - Button maximumButton = panel.MaximumLabel.GetOrAddComponent(); - maximumButton.onClick.AddListener(() => SetRequirement(__instance, rublesRequirement, ____pricesPanel.Maximum)); - ____pricesPanel.AddDisposable(maximumButton.onClick.RemoveAllListeners); - } + private static void SetRequirement(AddOfferWindow window, RequirementView requirement, float price) + { + if (window.R().BulkOffer) + { + price *= window.Int32_0; // offer item count } - private static void SetRequirement(AddOfferWindow window, RequirementView requirement, float price) + requirement.method_0(price.ToString("F0")); + } + + public class HighlightButton : Button + { + private Color originalColor; + bool originalOverrideColorTags; + + private TextMeshProUGUI _text; + private TextMeshProUGUI Text { - if (window.R().BulkOffer) + get { - price *= window.Int32_0; // offer item count - } - - requirement.method_0(price.ToString("F0")); - } - - public class HighlightButton : Button - { - private Color originalColor; - bool originalOverrideColorTags; - - private TextMeshProUGUI _text; - private TextMeshProUGUI Text - { - get + if (_text == null) { - if (_text == null) - { - _text = GetComponent(); - } - - return _text; + _text = GetComponent(); } + + return _text; } + } - public override void OnPointerEnter([NotNull] PointerEventData eventData) - { - base.OnPointerEnter(eventData); + public override void OnPointerEnter([NotNull] PointerEventData eventData) + { + base.OnPointerEnter(eventData); - originalColor = Text.color; - originalOverrideColorTags = Text.overrideColorTags; + originalColor = Text.color; + originalOverrideColorTags = Text.overrideColorTags; - Text.overrideColorTags = true; - Text.color = Color.white; - } + Text.overrideColorTags = true; + Text.color = Color.white; + } - public override void OnPointerExit([NotNull] PointerEventData eventData) - { - base.OnPointerExit(eventData); + public override void OnPointerExit([NotNull] PointerEventData eventData) + { + base.OnPointerExit(eventData); - Text.overrideColorTags = originalOverrideColorTags; - Text.color = originalColor; - } + Text.overrideColorTags = originalOverrideColorTags; + Text.color = originalColor; } } } diff --git a/Patches/AddOfferRememberAutoselectPatches.cs b/Patches/AddOfferRememberAutoselectPatches.cs index 60a2e3e..fab7d33 100644 --- a/Patches/AddOfferRememberAutoselectPatches.cs +++ b/Patches/AddOfferRememberAutoselectPatches.cs @@ -5,57 +5,56 @@ using SPT.Reflection.Patching; using System.Reflection; using UnityEngine; -namespace UIFixes +namespace UIFixes; + +public static class AddOfferRememberAutoselectPatches { - public static class AddOfferRememberAutoselectPatches + private static readonly string PlayerPrefKey = "UIFixes.AddOffer.AutoselectSimilar"; + + public static void Enable() { - private static readonly string PlayerPrefKey = "UIFixes.AddOffer.AutoselectSimilar"; + new RememberAutoselectPatch().Enable(); + new RestoreAutoselectPatch().Enable(); - public static void Enable() + Settings.RememberAutoselectSimilar.Subscribe(enabled => { - new RememberAutoselectPatch().Enable(); - new RestoreAutoselectPatch().Enable(); - - Settings.RememberAutoselectSimilar.Subscribe(enabled => + if (!enabled) { - if (!enabled) - { - PlayerPrefs.DeleteKey(PlayerPrefKey); - } - }); + PlayerPrefs.DeleteKey(PlayerPrefKey); + } + }); + } + + public class RememberAutoselectPatch : ModulePatch + { + protected override MethodBase GetTargetMethod() + { + return AccessTools.Method(typeof(AddOfferWindow), nameof(AddOfferWindow.method_7)); } - public class RememberAutoselectPatch : ModulePatch + [PatchPostfix] + public static void Postfix(bool value) { - protected override MethodBase GetTargetMethod() + if (Settings.RememberAutoselectSimilar.Value) { - return AccessTools.Method(typeof(AddOfferWindow), nameof(AddOfferWindow.method_7)); - } - - [PatchPostfix] - public static void Postfix(bool value) - { - if (Settings.RememberAutoselectSimilar.Value) - { - PlayerPrefs.SetInt(PlayerPrefKey, value ? 1 : 0); - } + PlayerPrefs.SetInt(PlayerPrefKey, value ? 1 : 0); } } + } - public class RestoreAutoselectPatch : ModulePatch + public class RestoreAutoselectPatch : ModulePatch + { + protected override MethodBase GetTargetMethod() { - protected override MethodBase GetTargetMethod() - { - return AccessTools.Method(typeof(AddOfferWindow), nameof(AddOfferWindow.Awake)); - } + return AccessTools.Method(typeof(AddOfferWindow), nameof(AddOfferWindow.Awake)); + } - [PatchPrefix] - public static void Prefix(UpdatableToggle ____autoSelectSimilar) + [PatchPrefix] + public static void Prefix(UpdatableToggle ____autoSelectSimilar) + { + if (Settings.RememberAutoselectSimilar.Value && PlayerPrefs.HasKey(PlayerPrefKey)) { - if (Settings.RememberAutoselectSimilar.Value && PlayerPrefs.HasKey(PlayerPrefKey)) - { - ____autoSelectSimilar.UpdateValue(PlayerPrefs.GetInt(PlayerPrefKey) == 1); - } + ____autoSelectSimilar.UpdateValue(PlayerPrefs.GetInt(PlayerPrefKey) == 1); } } } diff --git a/Patches/AimToggleHoldPatches.cs b/Patches/AimToggleHoldPatches.cs index d8cf42b..e8d3210 100644 --- a/Patches/AimToggleHoldPatches.cs +++ b/Patches/AimToggleHoldPatches.cs @@ -6,68 +6,67 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; -namespace UIFixes -{ - public static class AimToggleHoldPatches - { - public static void Enable() - { - new AddStatesPatch().Enable(); - new UpdateInputPatch().Enable(); +namespace UIFixes; - Settings.ToggleOrHoldAim.SettingChanged += (_, _) => - { - // Will "save" control settings, running GClass1911.UpdateInput, which will set (or unset) toggle/hold behavior - Singleton.Instance.Control.Controller.method_3(); - }; +public static class AimToggleHoldPatches +{ + public static void Enable() + { + new AddStatesPatch().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(); + }; + } + + public class AddStatesPatch : ModulePatch + { + private static FieldInfo StateMachineArray; + + protected override MethodBase GetTargetMethod() + { + StateMachineArray = AccessTools.Field(typeof(KeyCombination), "keyCombinationState_1"); + return AccessTools.GetDeclaredConstructors(typeof(ToggleKeyCombination)).Single(); } - public class AddStatesPatch : ModulePatch + [PatchPostfix] + public static void Postfix(ToggleKeyCombination __instance, EGameKey gameKey, ECommand disableCommand, KeyCombination.KeyCombinationState[] ___keyCombinationState_1) { - private static FieldInfo StateMachineArray; - - protected override MethodBase GetTargetMethod() + if (!Settings.ToggleOrHoldAim.Value || gameKey != EGameKey.Aim) { - StateMachineArray = AccessTools.Field(typeof(KeyCombination), "keyCombinationState_1"); - return AccessTools.GetDeclaredConstructors(typeof(ToggleKeyCombination)).Single(); + return; } - [PatchPostfix] - public static void Postfix(ToggleKeyCombination __instance, EGameKey gameKey, ECommand disableCommand, KeyCombination.KeyCombinationState[] ___keyCombinationState_1) - { - if (!Settings.ToggleOrHoldAim.Value || gameKey != EGameKey.Aim) - { - return; - } - - List states = new(___keyCombinationState_1) + List states = new(___keyCombinationState_1) { new ToggleHoldIdleState(__instance), new ToggleHoldClickOrHoldState(__instance), new ToggleHoldHoldState(__instance, disableCommand) }; - StateMachineArray.SetValue(__instance, states.ToArray()); - } + StateMachineArray.SetValue(__instance, states.ToArray()); + } + } + + public class UpdateInputPatch : ModulePatch + { + protected override MethodBase GetTargetMethod() + { + return AccessTools.Method(typeof(KeyCombination), nameof(KeyCombination.UpdateInput)); } - public class UpdateInputPatch : ModulePatch + [PatchPostfix] + public static void Postfix(KeyCombination __instance) { - protected override MethodBase GetTargetMethod() + if (!Settings.ToggleOrHoldAim.Value || __instance.GameKey != EGameKey.Aim) { - return AccessTools.Method(typeof(KeyCombination), nameof(KeyCombination.UpdateInput)); + return; } - [PatchPostfix] - public static void Postfix(KeyCombination __instance) - { - if (!Settings.ToggleOrHoldAim.Value || __instance.GameKey != EGameKey.Aim) - { - return; - } - - __instance.method_0((KeyCombination.EKeyState)ToggleHoldState.Idle); - } + __instance.method_0((KeyCombination.EKeyState)ToggleHoldState.Idle); } } } diff --git a/Patches/AssortUnlocksPatch.cs b/Patches/AssortUnlocksPatch.cs index f42ad1e..4a172b0 100644 --- a/Patches/AssortUnlocksPatch.cs +++ b/Patches/AssortUnlocksPatch.cs @@ -9,56 +9,55 @@ using System.Collections.Generic; using System.Reflection; using System.Threading.Tasks; -namespace UIFixes -{ - public class AssortUnlocksPatch : ModulePatch - { - private static bool Loading = false; - private static Dictionary AssortUnlocks = null; +namespace UIFixes; - protected override MethodBase GetTargetMethod() +public class AssortUnlocksPatch : ModulePatch +{ + private static bool Loading = false; + private static Dictionary AssortUnlocks = null; + + protected override MethodBase GetTargetMethod() + { + return AccessTools.Method(typeof(OfferView), nameof(OfferView.method_10)); + } + + [PatchPostfix] + public static void Postfix(OfferView __instance, HoverTooltipArea ____hoverTooltipArea) + { + if (!Settings.ShowRequiredQuest.Value) { - return AccessTools.Method(typeof(OfferView), nameof(OfferView.method_10)); + return; } - [PatchPostfix] - public static void Postfix(OfferView __instance, HoverTooltipArea ____hoverTooltipArea) + if (AssortUnlocks == null && !Loading) { - if (!Settings.ShowRequiredQuest.Value) - { - return; - } + Loading = true; - if (AssortUnlocks == null && !Loading) + Task response = RequestHandler.GetJsonAsync("/uifixes/assortUnlocks"); + response.ContinueWith(task => { - Loading = true; - - Task response = RequestHandler.GetJsonAsync("/uifixes/assortUnlocks"); - response.ContinueWith(task => + string json = task.Result; + if (!String.IsNullOrEmpty(json)) { - string json = task.Result; - if (!String.IsNullOrEmpty(json)) + try { - try - { - AssortUnlocks = JsonConvert.DeserializeObject>(json); - } - catch (Exception ex) - { - Logger.LogError(ex); - } + AssortUnlocks = JsonConvert.DeserializeObject>(json); + } + catch (Exception ex) + { + Logger.LogError(ex); } - - Loading = false; - }); - } - - if (__instance.Offer_0.Locked) - { - if (AssortUnlocks != null && AssortUnlocks.TryGetValue(__instance.Offer_0.Item.Id, out string questName)) - { - ____hoverTooltipArea.SetMessageText(____hoverTooltipArea.String_1 + " (" + questName.Localized() + ")", true); } + + Loading = false; + }); + } + + if (__instance.Offer_0.Locked) + { + if (AssortUnlocks != null && AssortUnlocks.TryGetValue(__instance.Offer_0.Item.Id, out string questName)) + { + ____hoverTooltipArea.SetMessageText(____hoverTooltipArea.String_1 + " (" + questName.Localized() + ")", true); } } } diff --git a/Patches/AutofillQuestItemsPatch.cs b/Patches/AutofillQuestItemsPatch.cs index 2a3306b..70b57ca 100644 --- a/Patches/AutofillQuestItemsPatch.cs +++ b/Patches/AutofillQuestItemsPatch.cs @@ -4,30 +4,29 @@ using SPT.Reflection.Patching; using System.Reflection; using UnityEngine; -namespace UIFixes +namespace UIFixes; + +public class AutofillQuestItemsPatch : ModulePatch { - public class AutofillQuestItemsPatch : ModulePatch + protected override MethodBase GetTargetMethod() { - protected override MethodBase GetTargetMethod() + return AccessTools.DeclaredMethod(typeof(HandoverQuestItemsWindow), nameof(HandoverQuestItemsWindow.Show)); + } + + [PatchPostfix] + public static void Postfix(HandoverQuestItemsWindow __instance) + { + if (Settings.AutofillQuestTurnIns.Value) { - return AccessTools.DeclaredMethod(typeof(HandoverQuestItemsWindow), nameof(HandoverQuestItemsWindow.Show)); + __instance.AutoSelectButtonPressedHandler(); } - [PatchPostfix] - public static void Postfix(HandoverQuestItemsWindow __instance) + // Apparently they never set up the scroll correctly? + Transform scrollArea = __instance.transform.Find("Window/Content/Possessions Grid/Scroll Area"); + if (scrollArea != null) { - if (Settings.AutofillQuestTurnIns.Value) - { - __instance.AutoSelectButtonPressedHandler(); - } - - // Apparently they never set up the scroll correctly? - Transform scrollArea = __instance.transform.Find("Window/Content/Possessions Grid/Scroll Area"); - if (scrollArea != null) - { - ScrollRectNoDrag scroller = scrollArea.GetComponent(); - scroller.content = scrollArea.Find("GridView")?.RectTransform(); - } + ScrollRectNoDrag scroller = scrollArea.GetComponent(); + scroller.content = scrollArea.Find("GridView")?.RectTransform(); } } } diff --git a/Patches/ConfirmationDialogKeysPatches.cs b/Patches/ConfirmationDialogKeysPatches.cs index 023864f..c116f7b 100644 --- a/Patches/ConfirmationDialogKeysPatches.cs +++ b/Patches/ConfirmationDialogKeysPatches.cs @@ -6,220 +6,219 @@ using System.Reflection; using UnityEngine; using UnityEngine.UI; -namespace UIFixes +namespace UIFixes; + +public static class ConfirmDialogKeysPatches { - public static class ConfirmDialogKeysPatches + public static void Enable() { - public static void Enable() - { - new DialogWindowPatch().Enable(); - new ItemUiContextWindowPatch().Enable(); - new ErrorScreenPatch().Enable(); - new AddOfferPatch().Enable(); + new DialogWindowPatch().Enable(); + new ItemUiContextWindowPatch().Enable(); + new ErrorScreenPatch().Enable(); + new AddOfferPatch().Enable(); - new ClickOutPatch().Enable(); - new ClickOutSplitDialogPatch().Enable(); - new ClickOutItemsListPatch().Enable(); - new ClickOutErrorScreenPatch().Enable(); + new ClickOutPatch().Enable(); + new ClickOutSplitDialogPatch().Enable(); + new ClickOutItemsListPatch().Enable(); + new ClickOutErrorScreenPatch().Enable(); + } + + public class DialogWindowPatch : ModulePatch + { + protected override MethodBase GetTargetMethod() + { + return AccessTools.Method(R.DialogWindow.Type, "Update"); } - public class DialogWindowPatch : ModulePatch + [PatchPostfix] + public static void Postfix(object __instance, bool ___bool_0) { - protected override MethodBase GetTargetMethod() + var instance = new R.DialogWindow(__instance); + + if (!___bool_0) { - return AccessTools.Method(R.DialogWindow.Type, "Update"); + return; } - [PatchPostfix] - public static void Postfix(object __instance, bool ___bool_0) + if (Input.GetKeyDown(KeyCode.Return) || Input.GetKeyDown(KeyCode.KeypadEnter) || Input.GetKeyDown(KeyCode.Space)) { - var instance = new R.DialogWindow(__instance); + instance.Accept(); + return; + } + } + } - if (!___bool_0) + public class ItemUiContextWindowPatch : ModulePatch + { + protected override MethodBase GetTargetMethod() + { + return AccessTools.Method(typeof(ItemUiContext), nameof(ItemUiContext.Update)); + } + + [PatchPostfix] + public static void Postfix(SplitDialog ___splitDialog_0, ItemsListWindow ____itemsListWindow) + { + if (Input.GetKeyDown(KeyCode.Return) || Input.GetKeyDown(KeyCode.KeypadEnter) || Input.GetKeyDown(KeyCode.Space)) + { + if (___splitDialog_0 != null && ___splitDialog_0.gameObject.activeSelf) { + ___splitDialog_0.Accept(); return; } - if (Input.GetKeyDown(KeyCode.Return) || Input.GetKeyDown(KeyCode.KeypadEnter) || Input.GetKeyDown(KeyCode.Space)) + if (____itemsListWindow.isActiveAndEnabled) { - instance.Accept(); + ____itemsListWindow.Close(); return; } } } - - public class ItemUiContextWindowPatch : ModulePatch - { - protected override MethodBase GetTargetMethod() - { - return AccessTools.Method(typeof(ItemUiContext), nameof(ItemUiContext.Update)); - } - - [PatchPostfix] - public static void Postfix(SplitDialog ___splitDialog_0, ItemsListWindow ____itemsListWindow) - { - if (Input.GetKeyDown(KeyCode.Return) || Input.GetKeyDown(KeyCode.KeypadEnter) || Input.GetKeyDown(KeyCode.Space)) - { - if (___splitDialog_0 != null && ___splitDialog_0.gameObject.activeSelf) - { - ___splitDialog_0.Accept(); - return; - } - - if (____itemsListWindow.isActiveAndEnabled) - { - ____itemsListWindow.Close(); - return; - } - } - } - } - - public class ErrorScreenPatch : ModulePatch - { - protected override MethodBase GetTargetMethod() - { - return AccessTools.Method(typeof(ErrorScreen), nameof(ErrorScreen.Update)); - } - - [PatchPostfix] - public static void Postfix(ErrorScreen __instance) - { - if (Input.GetKeyDown(KeyCode.Return) || Input.GetKeyDown(KeyCode.KeypadEnter) || Input.GetKeyDown(KeyCode.Space)) - { - __instance.method_4(); - } - } - } - - public class AddOfferPatch : ModulePatch - { - protected override MethodBase GetTargetMethod() - { - return AccessTools.Method(typeof(AddOfferWindow), nameof(AddOfferWindow.Update)); - } - - [PatchPostfix] - public static void Postfix(AddOfferWindow __instance, InteractableElement ____addOfferButton) - { - if (!____addOfferButton.isActiveAndEnabled || !____addOfferButton.Interactable) - { - return; - } - - if (Input.GetKeyDown(KeyCode.Return) || Input.GetKeyDown(KeyCode.KeypadEnter) || Input.GetKeyDown(KeyCode.Space)) - { - __instance.method_1(); - } - } - } - - public class ClickOutPatch : ModulePatch - { - protected override MethodBase GetTargetMethod() - { - return AccessTools.DeclaredMethod(typeof(MessageWindow), nameof(MessageWindow.Show)); - } - - [PatchPostfix] - public static void Postfix(MessageWindow __instance) - { - if (!Settings.ClickOutOfDialogs.Value) - { - return; - } - - // Note the space after firewall, because unity doesn't trim names and BSG is incompetent. - // Also for some reason some MessageWindows have a Window child and some don't. - Transform firewall = __instance.transform.Find("Firewall ") ?? __instance.transform.Find("Window/Firewall "); - Button button = firewall?.gameObject.GetOrAddComponent