using file-scoped namespaces

This commit is contained in:
Tyfon
2024-07-12 16:17:42 -07:00
parent 29b6094b20
commit 7ea249114d
63 changed files with 8789 additions and 8855 deletions

View File

@@ -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<EItemInfoButton> Actions = [EItemInfoButton.LinkedSearch];
private readonly Slot slot = slot;
public override IEnumerable<EItemInfoButton> AvailableInteractions => Actions;
public override void ExecuteInteractionInternal(EItemInfoButton interaction)
{
private static readonly List<EItemInfoButton> Actions = [EItemInfoButton.LinkedSearch];
private readonly Slot slot = slot;
public override IEnumerable<EItemInfoButton> 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;
}
}
}
}

View File

@@ -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<EItemInfoButton> 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<EItemInfoButton> GetItemContextInteractions(Action closeAction)
{
return new EmptySlotMenu(slot, ItemContextAbstractClass, itemUiContext, () =>
{
Dispose();
closeAction?.Invoke();
});
}
public override ItemContextAbstractClass CreateChild(Item item)
{
// Should never happen
throw new NotImplementedException();
}
}

View File

@@ -5,101 +5,100 @@ using System;
using System.Collections.Generic;
using System.Linq;
namespace UIFixes
namespace UIFixes;
public class InsuranceInteractions(IEnumerable<Item> items, ItemUiContext uiContext, int playerRubles) : ItemInfoInteractionsAbstractClass<InsuranceInteractions.EInsurers>(uiContext)
{
public class InsuranceInteractions(IEnumerable<Item> items, ItemUiContext uiContext, int playerRubles) : ItemInfoInteractionsAbstractClass<InsuranceInteractions.EInsurers>(uiContext)
private readonly InsuranceCompanyClass insurance = uiContext.Session.InsuranceCompany;
private readonly List<Item> items = items.ToList();
private readonly int playerRubles = playerRubles;
private List<InsuranceItem> insurableItems;
private readonly Dictionary<string, int> 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<Item> items = items.ToList();
private readonly int playerRubles = playerRubles;
private List<InsuranceItem> insurableItems;
private readonly Dictionary<string, int> prices = [];
IEnumerable<InsuranceItem> 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<InsuranceItem> 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("<b><color=#C6C4B2>{0}</color> <color={1}>({2} ₽)</color></b>", insurer.LocalizedName, priceColor, price);
string text = string.Format("<b><color=#C6C4B2>{0}</color> <color={1}>({2} ₽)</color></b>", 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:");
}
}

View File

@@ -6,176 +6,175 @@ using System;
using System.Globalization;
using System.Linq;
namespace UIFixes
namespace UIFixes;
public class RepairInteractions : ItemInfoInteractionsAbstractClass<RepairInteractions.ERepairers>
{
public class RepairInteractions : ItemInfoInteractionsAbstractClass<RepairInteractions.ERepairers>
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("<b><color=#C6C4B2>{0}</color></b>", 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("<b><color=#C6C4B2>{0}</color> <color={1}>({2} {3})</color></b>", 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("<b><color=#C6C4B2>{0}</color> <color={1}>({2} ₽)</color></b>", 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<GUISounds>.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("<b><color=#C6C4B2>{0}</color></b>", 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("<b><color=#C6C4B2>{0}</color> <color={1}>({2} {3})</color></b>", 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("<b><color=#C6C4B2>{0}</color> <color={1}>({2} ₽)</color></b>", 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<GUISounds>.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:");
}
}

View File

@@ -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<Item, Properties> properties = new();
private class Properties
{
private static readonly ConditionalWeakTable<Item, Properties> 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<TemplatedGridsView, Properties> 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<TemplatedGridsView, Properties> 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;
}

View File

@@ -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<GraphicRaycaster>();
if (localRaycaster == null)
{
selectTexture = new Texture2D(1, 1);
selectTexture.SetPixel(0, 0, new Color(1f, 1f, 1f, 0.6f));
selectTexture.Apply();
localRaycaster = GetComponentInParent<GraphicRaycaster>();
if (localRaycaster == null)
{
throw new InvalidOperationException("DrawMultiSelect couldn't find GraphicRayCaster in parents");
}
preloaderRaycaster = Singleton<PreloaderUI>.Instance.transform.GetChild(0).GetComponent<GraphicRaycaster>();
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<PreloaderUI>.Instance.transform.GetChild(0).GetComponent<GraphicRaycaster>();
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<RaycastResult> results = [];
localRaycaster.Raycast(eventData, results);
preloaderRaycaster.Raycast(eventData, results);
foreach (GameObject gameObject in results.Select(r => r.gameObject))
{
var draggables = gameObject.GetComponents<MonoBehaviour>()
.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<MonoBehaviour>()
.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<GridItemView>().Concat(Singleton<PreloaderUI>.Instance.GetComponentsInChildren<GridItemView>()))
{
RectTransform itemTransform = gridItemView.GetComponent<RectTransform>();
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<PreloaderUI>.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<RaycastResult> 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<RaycastResult> results = [];
localRaycaster.Raycast(eventData, results);
preloaderRaycaster.Raycast(eventData, results);
foreach (GameObject gameObject in results.Select(r => r.gameObject))
{
var draggables = gameObject.GetComponents<MonoBehaviour>()
.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<MonoBehaviour>()
.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<GridItemView>().Concat(Singleton<PreloaderUI>.Instance.GetComponentsInChildren<GridItemView>()))
{
RectTransform itemTransform = gridItemView.GetComponent<RectTransform>();
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<PreloaderUI>.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<RaycastResult> 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;
}
}

View File

@@ -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<string, Dictionary<string, Vector2Int>> GridOffsets = [];
private static readonly Dictionary<string, Dictionary<int, Dictionary<int, string>>> GridsByLocation = [];
public static LocationInGrid GetGridLocation(GridItemAddress realAddress)
{
private static readonly Dictionary<string, Dictionary<string, Vector2Int>> GridOffsets = [];
private static readonly Dictionary<string, Dictionary<int, Dictionary<int, string>>> 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<string, Vector2Int> gridOffsets = [];
Dictionary<int, Dictionary<int, string>> 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<GridView>())
{
// 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<string, Vector2Int> gridOffsets = [];
Dictionary<int, Dictionary<int, string>> 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<GridView>())
{
// 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;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -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("<b>MultiSelect</b>\n");
builder.AppendFormat("Active: <color={0}>{1}</color>\n", MultiSelect.Active ? "green" : "red", MultiSelect.Active);
builder.AppendFormat("Items: <color=yellow>{0}</color>\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: <color=yellow>{0}</color>\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("<b>MultiSelect</b>\n");
builder.AppendFormat("Active: <color={0}>{1}</color>\n", MultiSelect.Active ? "green" : "red", MultiSelect.Active);
builder.AppendFormat("Items: <color=yellow>{0}</color>\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: <color=yellow>{0}</color>\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);
}
}

View File

@@ -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<HighlightButton>();
lowestButton.onClick.AddListener(() => SetRequirement(__instance, rublesRequirement, ____pricesPanel.Minimum));
____pricesPanel.AddDisposable(lowestButton.onClick.RemoveAllListeners);
Button lowestButton = panel.LowestLabel.GetOrAddComponent<HighlightButton>();
lowestButton.onClick.AddListener(() => SetRequirement(__instance, rublesRequirement, ____pricesPanel.Minimum));
____pricesPanel.AddDisposable(lowestButton.onClick.RemoveAllListeners);
Button averageButton = panel.AverageLabel.GetOrAddComponent<HighlightButton>();
averageButton.onClick.AddListener(() => SetRequirement(__instance, rublesRequirement, ____pricesPanel.Average));
____pricesPanel.AddDisposable(averageButton.onClick.RemoveAllListeners);
Button averageButton = panel.AverageLabel.GetOrAddComponent<HighlightButton>();
averageButton.onClick.AddListener(() => SetRequirement(__instance, rublesRequirement, ____pricesPanel.Average));
____pricesPanel.AddDisposable(averageButton.onClick.RemoveAllListeners);
Button maximumButton = panel.MaximumLabel.GetOrAddComponent<HighlightButton>();
maximumButton.onClick.AddListener(() => SetRequirement(__instance, rublesRequirement, ____pricesPanel.Maximum));
____pricesPanel.AddDisposable(maximumButton.onClick.RemoveAllListeners);
}
}
Button maximumButton = panel.MaximumLabel.GetOrAddComponent<HighlightButton>();
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<TextMeshProUGUI>();
}
return _text;
_text = GetComponent<TextMeshProUGUI>();
}
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;
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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<SharedGameSettingsClass>.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<SharedGameSettingsClass>.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<KeyCombination.KeyCombinationState> states = new(___keyCombinationState_1)
List<KeyCombination.KeyCombinationState> 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);
}
}
}

View File

@@ -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<string, string> AssortUnlocks = null;
namespace UIFixes;
protected override MethodBase GetTargetMethod()
public class AssortUnlocksPatch : ModulePatch
{
private static bool Loading = false;
private static Dictionary<string, string> 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<string> response = RequestHandler.GetJsonAsync("/uifixes/assortUnlocks");
response.ContinueWith(task =>
{
Loading = true;
Task<string> 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<Dictionary<string, string>>(json);
}
catch (Exception ex)
{
Logger.LogError(ex);
}
AssortUnlocks = JsonConvert.DeserializeObject<Dictionary<string, string>>(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);
}
}
}

View File

@@ -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<ScrollRectNoDrag>();
scroller.content = scrollArea.Find("GridView")?.RectTransform();
}
ScrollRectNoDrag scroller = scrollArea.GetComponent<ScrollRectNoDrag>();
scroller.content = scrollArea.Find("GridView")?.RectTransform();
}
}
}

View File

@@ -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<Button>();
if (button != null)
{
button.transition = Selectable.Transition.None;
button.onClick.AddListener(__instance.Close);
__instance.R().UI.AddDisposable(button.onClick.RemoveAllListeners);
}
}
}
public class ClickOutSplitDialogPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
// Using method_0 because there's 2 Show(), and they have 10+ args and f that
return AccessTools.Method(typeof(SplitDialog), nameof(SplitDialog.method_0));
}
[PatchPostfix]
public static void Postfix(SplitDialog __instance)
{
if (!Settings.ClickOutOfDialogs.Value)
{
return;
}
Button button = __instance.transform.Find("Background")?.gameObject.GetOrAddComponent<Button>();
if (button != null)
{
button.transition = Selectable.Transition.None;
button.onClick.RemoveAllListeners(); // There's no disposable here so keeping the listener count down
button.onClick.AddListener(__instance.method_2);
}
}
}
public class ClickOutItemsListPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(ItemsListWindow), nameof(ItemsListWindow.Show));
}
[PatchPostfix]
public static void Postfix(ItemsListWindow __instance)
{
if (!Settings.ClickOutOfDialogs.Value)
{
return;
}
// Note the space after firewall, because unity doesn't trim names and BSG is incompetent
Transform firewall = __instance.transform.Find("Firewall ");
Button button = firewall?.gameObject.GetOrAddComponent<Button>();
if (button != null)
{
button.transition = Selectable.Transition.None;
button.onClick.AddListener(__instance.Close);
__instance.R().UI.AddDisposable(button.onClick.RemoveAllListeners);
}
}
}
public class ClickOutErrorScreenPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.DeclaredMethod(typeof(ErrorScreen), nameof(ErrorScreen.Show));
}
[PatchPostfix]
public static void Postfix(ErrorScreen __instance)
{
if (!Settings.ClickOutOfDialogs.Value)
{
return;
}
// Note the space after firewall, because unity doesn't trim names and BSG is incompetent
Transform firewall = __instance.transform.Find("Firewall ");
Button button = firewall?.gameObject.GetOrAddComponent<Button>();
if (button != null)
{
button.transition = Selectable.Transition.None;
button.onClick.AddListener(__instance.method_4);
__instance.R().UI.AddDisposable(button.onClick.RemoveAllListeners);
}
}
}
}
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<Button>();
if (button != null)
{
button.transition = Selectable.Transition.None;
button.onClick.AddListener(__instance.Close);
__instance.R().UI.AddDisposable(button.onClick.RemoveAllListeners);
}
}
}
public class ClickOutSplitDialogPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
// Using method_0 because there's 2 Show(), and they have 10+ args and f that
return AccessTools.Method(typeof(SplitDialog), nameof(SplitDialog.method_0));
}
[PatchPostfix]
public static void Postfix(SplitDialog __instance)
{
if (!Settings.ClickOutOfDialogs.Value)
{
return;
}
Button button = __instance.transform.Find("Background")?.gameObject.GetOrAddComponent<Button>();
if (button != null)
{
button.transition = Selectable.Transition.None;
button.onClick.RemoveAllListeners(); // There's no disposable here so keeping the listener count down
button.onClick.AddListener(__instance.method_2);
}
}
}
public class ClickOutItemsListPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(ItemsListWindow), nameof(ItemsListWindow.Show));
}
[PatchPostfix]
public static void Postfix(ItemsListWindow __instance)
{
if (!Settings.ClickOutOfDialogs.Value)
{
return;
}
// Note the space after firewall, because unity doesn't trim names and BSG is incompetent
Transform firewall = __instance.transform.Find("Firewall ");
Button button = firewall?.gameObject.GetOrAddComponent<Button>();
if (button != null)
{
button.transition = Selectable.Transition.None;
button.onClick.AddListener(__instance.Close);
__instance.R().UI.AddDisposable(button.onClick.RemoveAllListeners);
}
}
}
public class ClickOutErrorScreenPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.DeclaredMethod(typeof(ErrorScreen), nameof(ErrorScreen.Show));
}
[PatchPostfix]
public static void Postfix(ErrorScreen __instance)
{
if (!Settings.ClickOutOfDialogs.Value)
{
return;
}
// Note the space after firewall, because unity doesn't trim names and BSG is incompetent
Transform firewall = __instance.transform.Find("Firewall ");
Button button = firewall?.gameObject.GetOrAddComponent<Button>();
if (button != null)
{
button.transition = Selectable.Transition.None;
button.onClick.AddListener(__instance.method_4);
__instance.R().UI.AddDisposable(button.onClick.RemoveAllListeners);
}
}
}
}

View File

@@ -9,512 +9,510 @@ using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using TMPro;
using UIFixes.ContextMenus;
using UnityEngine;
namespace UIFixes
namespace UIFixes;
public static class ContextMenuPatches
{
public static class ContextMenuPatches
private static InsuranceInteractions CurrentInsuranceInteractions = null;
private static RepairInteractions CurrentRepairInteractions = null;
private static string CreatedButtonInteractionId = null;
public static void Enable()
{
private static InsuranceInteractions CurrentInsuranceInteractions = null;
private static RepairInteractions CurrentRepairInteractions = null;
private static string CreatedButtonInteractionId = null;
new ContextMenuNamesPatch().Enable();
new PositionSubMenuPatch().Enable();
new PositionInsuranceSubMenuPatch().Enable();
public static void Enable()
new DeclareSubInteractionsInventoryPatch().Enable();
new CreateSubInteractionsInventoryPatch().Enable();
new DeclareSubInteractionsTradingPatch().Enable();
new CreateSubInteractionsTradingPatch().Enable();
new SniffInteractionButtonCreationPatch().Enable();
new ChangeInteractionButtonCreationPatch().Enable();
new EnableInsureInnerItemsPatch().Enable();
new DisableLoadPresetOnBulletsPatch().Enable();
new EmptySlotMenuPatch().Enable();
new EmptySlotMenuRemovePatch().Enable();
}
public class ContextMenuNamesPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
new ContextMenuNamesPatch().Enable();
new PositionSubMenuPatch().Enable();
new PositionInsuranceSubMenuPatch().Enable();
new DeclareSubInteractionsInventoryPatch().Enable();
new CreateSubInteractionsInventoryPatch().Enable();
new DeclareSubInteractionsTradingPatch().Enable();
new CreateSubInteractionsTradingPatch().Enable();
new SniffInteractionButtonCreationPatch().Enable();
new ChangeInteractionButtonCreationPatch().Enable();
new EnableInsureInnerItemsPatch().Enable();
new DisableLoadPresetOnBulletsPatch().Enable();
new EmptySlotMenuPatch().Enable();
new EmptySlotMenuRemovePatch().Enable();
return AccessTools.Method(typeof(ContextMenuButton), nameof(ContextMenuButton.Show));
}
public class ContextMenuNamesPatch : ModulePatch
[PatchPostfix]
public static void Postfix(string caption, TextMeshProUGUI ____text)
{
protected override MethodBase GetTargetMethod()
if (MultiSelect.Count < 1)
{
return AccessTools.Method(typeof(ContextMenuButton), nameof(ContextMenuButton.Show));
return;
}
[PatchPostfix]
public static void Postfix(string caption, TextMeshProUGUI ____text)
if (caption == EItemInfoButton.Insure.ToString())
{
if (MultiSelect.Count < 1)
{
return;
}
InsuranceCompanyClass insurance = ItemUiContext.Instance.Session.InsuranceCompany;
int count = MultiSelect.ItemContexts.Select(ic => InsuranceItem.FindOrCreate(ic.Item))
.Where(i => insurance.ItemTypeAvailableForInsurance(i) && !insurance.InsuredItems.Contains(i))
.Count();
if (caption == EItemInfoButton.Insure.ToString())
if (count > 0)
{
InsuranceCompanyClass insurance = ItemUiContext.Instance.Session.InsuranceCompany;
int count = MultiSelect.ItemContexts.Select(ic => InsuranceItem.FindOrCreate(ic.Item))
.Where(i => insurance.ItemTypeAvailableForInsurance(i) && !insurance.InsuredItems.Contains(i))
.Count();
if (count > 0)
{
____text.text += " (x" + count + ")";
}
}
else if (caption == EItemInfoButton.Equip.ToString())
{
int count = MultiSelect.InteractionCount(EItemInfoButton.Equip, ItemUiContext.Instance);
if (count > 0)
{
____text.text += " (x" + count + ")";
}
}
else if (caption == EItemInfoButton.Unequip.ToString())
{
int count = MultiSelect.InteractionCount(EItemInfoButton.Unequip, ItemUiContext.Instance);
if (count > 0)
{
____text.text += " (x" + count + ")";
}
}
else if (caption == EItemInfoButton.LoadAmmo.ToString())
{
int count = MultiSelect.InteractionCount(EItemInfoButton.LoadAmmo, ItemUiContext.Instance);
if (count > 0)
{
____text.text += " (x" + count + ")";
}
}
else if (caption == EItemInfoButton.UnloadAmmo.ToString())
{
int count = MultiSelect.InteractionCount(EItemInfoButton.UnloadAmmo, ItemUiContext.Instance);
if (count > 0)
{
____text.text += " (x" + count + ")";
}
}
else if (caption == EItemInfoButton.ApplyMagPreset.ToString())
{
int count = MultiSelect.InteractionCount(EItemInfoButton.ApplyMagPreset, ItemUiContext.Instance);
if (count > 0)
{
____text.text += " (x" + count + ")";
}
}
else if (caption == EItemInfoButton.Unpack.ToString())
{
int count = MultiSelect.InteractionCount(EItemInfoButton.Unpack, ItemUiContext.Instance);
if (count > 0)
{
____text.text += " (x" + count + ")";
}
____text.text += " (x" + count + ")";
}
}
}
public class DeclareSubInteractionsInventoryPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
else if (caption == EItemInfoButton.Equip.ToString())
{
return AccessTools.Method(R.InventoryInteractions.Type, "get_SubInteractions");
}
[PatchPostfix]
public static void Postfix(ref IEnumerable<EItemInfoButton> __result)
{
__result = __result.Append(EItemInfoButton.Repair).Append(EItemInfoButton.Insure);
}
}
public class CreateSubInteractionsInventoryPatch : ModulePatch
{
private static bool LoadingInsuranceActions = false;
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(R.InventoryInteractions.Type, "CreateSubInteractions");
}
[PatchPrefix]
public static bool Prefix(EItemInfoButton parentInteraction, ISubInteractions subInteractionsWrapper, Item ___item_0, ItemUiContext ___itemUiContext_1)
{
// Clear this, since something else should be active (even a different mouseover of the insurance button)
LoadingInsuranceActions = false;
if (parentInteraction == EItemInfoButton.Insure)
int count = MultiSelect.InteractionCount(EItemInfoButton.Equip, ItemUiContext.Instance);
if (count > 0)
{
int playerRubles = GetPlayerRubles(___itemUiContext_1);
CurrentInsuranceInteractions = MultiSelect.Active ?
new(MultiSelect.ItemContexts.Select(ic => ic.Item), ___itemUiContext_1, playerRubles) :
new(___item_0, ___itemUiContext_1, playerRubles);
// Because this is async, need to protect against a different subInteractions activating before loading is done
// This isn't thread-safe at all but now the race condition is a microsecond instead of hundreds of milliseconds.
LoadingInsuranceActions = true;
CurrentInsuranceInteractions.LoadAsync(() =>
{
if (LoadingInsuranceActions)
{
subInteractionsWrapper.SetSubInteractions(CurrentInsuranceInteractions);
LoadingInsuranceActions = false;
}
});
return false;
}
if (parentInteraction == EItemInfoButton.Repair)
{
int playerRubles = GetPlayerRubles(___itemUiContext_1);
CurrentRepairInteractions = new(___item_0, ___itemUiContext_1, playerRubles);
subInteractionsWrapper.SetSubInteractions(CurrentRepairInteractions);
return false;
}
return true;
}
}
public class DeclareSubInteractionsTradingPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(R.TradingInteractions.Type, "get_SubInteractions");
}
[PatchPostfix]
public static void Postfix(ref IEnumerable<EItemInfoButton> __result)
{
__result = __result.Append(EItemInfoButton.Repair).Append(EItemInfoButton.Insure);
}
}
public class CreateSubInteractionsTradingPatch : ModulePatch
{
private static bool LoadingInsuranceActions = false;
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(R.TradingInteractions.Type, "CreateSubInteractions");
}
[PatchPrefix]
public static bool Prefix(object __instance, EItemInfoButton parentInteraction, ISubInteractions subInteractionsWrapper, ItemUiContext ___itemUiContext_0)
{
// Clear this, since something else should be active (even a different mouseover of the insurance button)
LoadingInsuranceActions = false;
var wrappedInstance = new R.TradingInteractions(__instance);
if (parentInteraction == EItemInfoButton.Insure)
{
int playerRubles = GetPlayerRubles(___itemUiContext_0);
// CreateSubInteractions is only on the base class here, which doesn't have an Item. But __instance is actually a GClass3054
Item item = wrappedInstance.Item;
CurrentInsuranceInteractions = new(item, ___itemUiContext_0, playerRubles);
CurrentInsuranceInteractions = MultiSelect.Active ?
new(MultiSelect.ItemContexts.Select(ic => ic.Item), ___itemUiContext_0, playerRubles) :
new(item, ___itemUiContext_0, playerRubles);
// Because this is async, need to protect against a different subInteractions activating before loading is done
// This isn't thread-safe at all but now the race condition is a microsecond instead of hundreds of milliseconds.
LoadingInsuranceActions = true;
CurrentInsuranceInteractions.LoadAsync(() =>
{
if (LoadingInsuranceActions)
{
subInteractionsWrapper.SetSubInteractions(CurrentInsuranceInteractions);
LoadingInsuranceActions = false;
}
});
return false;
}
if (parentInteraction == EItemInfoButton.Repair)
{
int playerRubles = GetPlayerRubles(___itemUiContext_0);
// CreateSubInteractions is only on the base class here, which doesn't have an Item. But __instance is actually a GClass3054
Item item = wrappedInstance.Item;
CurrentRepairInteractions = new(item, ___itemUiContext_0, playerRubles);
subInteractionsWrapper.SetSubInteractions(CurrentRepairInteractions);
return false;
}
return true;
}
}
public class SniffInteractionButtonCreationPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(InteractionButtonsContainer), nameof(InteractionButtonsContainer.method_3));
}
[PatchPrefix]
public static void Prefix(DynamicInteractionClass interaction)
{
if (interaction.IsInsuranceInteraction() || interaction.IsRepairInteraction())
{
CreatedButtonInteractionId = interaction.Id;
____text.text += " (x" + count + ")";
}
}
[PatchPostfix]
public static void Postfix()
else if (caption == EItemInfoButton.Unequip.ToString())
{
CreatedButtonInteractionId = null;
}
}
public class ChangeInteractionButtonCreationPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(InteractionButtonsContainer), nameof(InteractionButtonsContainer.method_5));
}
[PatchPrefix]
public static void Prefix(SimpleContextMenuButton button)
{
if (!String.IsNullOrEmpty(CreatedButtonInteractionId))
int count = MultiSelect.InteractionCount(EItemInfoButton.Unequip, ItemUiContext.Instance);
if (count > 0)
{
if (InsuranceInteractions.IsInsuranceInteractionId(CreatedButtonInteractionId) && CurrentInsuranceInteractions != null)
{
button.SetButtonInteraction(CurrentInsuranceInteractions.GetButtonInteraction(CreatedButtonInteractionId));
}
else if (RepairInteractions.IsRepairInteractionId(CreatedButtonInteractionId) && CurrentRepairInteractions != null)
{
button.SetButtonInteraction(CurrentRepairInteractions.GetButtonInteraction(CreatedButtonInteractionId));
}
____text.text += " (x" + count + ")";
}
}
}
public class CleanUpInteractionsPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
else if (caption == EItemInfoButton.LoadAmmo.ToString())
{
return AccessTools.Method(typeof(SimpleContextMenu), nameof(SimpleContextMenu.Close));
}
[PatchPostfix]
public static void Postfix()
{
CurrentInsuranceInteractions = null;
CurrentRepairInteractions = null;
CreatedButtonInteractionId = null;
}
}
public class EnableInsureInnerItemsPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(R.ContextMenuHelper.Type, "IsInteractive");
}
[PatchPrefix]
public static bool Prefix(object __instance, EItemInfoButton button, ref IResult __result, Item ___item_0)
{
if (button != EItemInfoButton.Insure)
int count = MultiSelect.InteractionCount(EItemInfoButton.LoadAmmo, ItemUiContext.Instance);
if (count > 0)
{
return true;
}
InsuranceCompanyClass insurance = new R.ContextMenuHelper(__instance).InsuranceCompany;
IEnumerable<Item> items = MultiSelect.Active ? MultiSelect.ItemContexts.Select(ic => ic.Item) : [___item_0];
IEnumerable<InsuranceItem> InsuranceItemes = items.Select(InsuranceItem.FindOrCreate);
IEnumerable<InsuranceItem> insurableItems = InsuranceItemes.SelectMany(insurance.GetItemChildren)
.Flatten(insurance.GetItemChildren)
.Concat(InsuranceItemes)
.Where(i => insurance.ItemTypeAvailableForInsurance(i) && !insurance.InsuredItems.Contains(i));
if (insurableItems.Any())
{
__result = SuccessfulResult.New;
return false;
}
return true;
}
}
public class DisableLoadPresetOnBulletsPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(MagazineBuildClass), nameof(MagazineBuildClass.TryFindPresetSource));
}
[PatchPrefix]
public static bool Prefix(Item selectedItem, ref GStruct416<Item> __result)
{
if (Settings.LoadMagPresetOnBullets.Value)
{
return true;
}
if (selectedItem is BulletClass)
{
__result = new MagazineBuildClass.Class3183(selectedItem);
return false;
}
return true;
}
}
public class EmptySlotMenuPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.DeclaredMethod(typeof(ModSlotView), nameof(ModSlotView.Show));
}
[PatchPostfix]
public static void Postfix(ModSlotView __instance, Slot slot, ItemContextAbstractClass parentItemContext, ItemUiContext itemUiContext)
{
if (!Settings.EnableSlotSearch.Value || slot.ContainedItem != null)
{
return;
}
EmptySlotMenuTrigger menuTrigger = __instance.GetOrAddComponent<EmptySlotMenuTrigger>();
menuTrigger.Init(slot, parentItemContext, itemUiContext);
}
}
public class EmptySlotMenuRemovePatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.DeclaredMethod(typeof(ModSlotView), nameof(ModSlotView.SetupItemView));
}
[PatchPostfix]
public static void Postfix(ModSlotView __instance)
{
EmptySlotMenuTrigger menuTrigger = __instance.GetComponent<EmptySlotMenuTrigger>();
if (menuTrigger != null)
{
UnityEngine.Object.Destroy(menuTrigger);
____text.text += " (x" + count + ")";
}
}
}
public class PositionSubMenuPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
else if (caption == EItemInfoButton.UnloadAmmo.ToString())
{
return AccessTools.Method(R.InventoryInteractions.Type, "CreateSubInteractions");
}
// Existing logic tries to place it on the right, moving to the left if necessary. They didn't do it correctly, so it always goes on the left.
[PatchPostfix]
public static void Postfix(ISubInteractions subInteractionsWrapper)
{
if (subInteractionsWrapper is not InteractionButtonsContainer buttonsContainer)
int count = MultiSelect.InteractionCount(EItemInfoButton.UnloadAmmo, ItemUiContext.Instance);
if (count > 0)
{
return;
}
var wrappedContainer = buttonsContainer.R();
SimpleContextMenuButton button = wrappedContainer.ContextMenuButton;
SimpleContextMenu flyoutMenu = wrappedContainer.ContextMenu;
if (button == null || flyoutMenu == null)
{
return;
}
PositionContextMenuFlyout(button, flyoutMenu);
}
}
// Insurance submenu is async, need to postfix the actual set call
public class PositionInsuranceSubMenuPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(InteractionButtonsContainer), nameof(InteractionButtonsContainer.SetSubInteractions)).MakeGenericMethod([typeof(InsuranceInteractions.EInsurers)]);
}
// Existing logic tries to place it on the right, moving to the left if necessary. They didn't do it correctly, so it always goes on the left.
[PatchPostfix]
public static void Postfix(SimpleContextMenuButton ___simpleContextMenuButton_0, SimpleContextMenu ___simpleContextMenu_0)
{
PositionContextMenuFlyout(___simpleContextMenuButton_0, ___simpleContextMenu_0);
}
}
private static void PositionContextMenuFlyout(SimpleContextMenuButton button, SimpleContextMenu flyoutMenu)
{
RectTransform buttonTransform = button.RectTransform();
RectTransform flyoutTransform = flyoutMenu.RectTransform();
Vector2 leftPosition = flyoutTransform.position; // BSG's code will always put it on the left
leftPosition = new Vector2((float)Math.Round((double)leftPosition.x), (float)Math.Round((double)leftPosition.y));
Vector2 size = buttonTransform.rect.size;
Vector2 rightPosition = size - size * buttonTransform.pivot;
rightPosition = buttonTransform.TransformPoint(rightPosition);
// Round vector the way that CorrectPosition does
rightPosition = new Vector2((float)Math.Round((double)rightPosition.x), (float)Math.Round((double)rightPosition.y));
if (Settings.ContextMenuOnRight.Value)
{
// Try on the right
flyoutTransform.position = rightPosition;
flyoutMenu.CorrectPosition();
// This means CorrectPosition() moved it
if (!(flyoutTransform.position.x - rightPosition.x).IsZero())
{
flyoutTransform.position = leftPosition;
____text.text += " (x" + count + ")";
}
}
else
else if (caption == EItemInfoButton.ApplyMagPreset.ToString())
{
flyoutTransform.position = leftPosition;
flyoutMenu.CorrectPosition();
if (!(flyoutTransform.position.x - leftPosition.x).IsZero())
int count = MultiSelect.InteractionCount(EItemInfoButton.ApplyMagPreset, ItemUiContext.Instance);
if (count > 0)
{
flyoutTransform.position = rightPosition;
____text.text += " (x" + count + ")";
}
}
}
private static int GetPlayerRubles(ItemUiContext itemUiContext)
{
StashClass stash = itemUiContext.R().InventoryController.Inventory.Stash;
if (stash == null)
else if (caption == EItemInfoButton.Unpack.ToString())
{
return 0;
int count = MultiSelect.InteractionCount(EItemInfoButton.Unpack, ItemUiContext.Instance);
if (count > 0)
{
____text.text += " (x" + count + ")";
}
}
return R.Money.GetMoneySums(stash.Grid.ContainedItems.Keys)[ECurrencyType.RUB];
}
}
public class DeclareSubInteractionsInventoryPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(R.InventoryInteractions.Type, "get_SubInteractions");
}
[PatchPostfix]
public static void Postfix(ref IEnumerable<EItemInfoButton> __result)
{
__result = __result.Append(EItemInfoButton.Repair).Append(EItemInfoButton.Insure);
}
}
public class CreateSubInteractionsInventoryPatch : ModulePatch
{
private static bool LoadingInsuranceActions = false;
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(R.InventoryInteractions.Type, "CreateSubInteractions");
}
[PatchPrefix]
public static bool Prefix(EItemInfoButton parentInteraction, ISubInteractions subInteractionsWrapper, Item ___item_0, ItemUiContext ___itemUiContext_1)
{
// Clear this, since something else should be active (even a different mouseover of the insurance button)
LoadingInsuranceActions = false;
if (parentInteraction == EItemInfoButton.Insure)
{
int playerRubles = GetPlayerRubles(___itemUiContext_1);
CurrentInsuranceInteractions = MultiSelect.Active ?
new(MultiSelect.ItemContexts.Select(ic => ic.Item), ___itemUiContext_1, playerRubles) :
new(___item_0, ___itemUiContext_1, playerRubles);
// Because this is async, need to protect against a different subInteractions activating before loading is done
// This isn't thread-safe at all but now the race condition is a microsecond instead of hundreds of milliseconds.
LoadingInsuranceActions = true;
CurrentInsuranceInteractions.LoadAsync(() =>
{
if (LoadingInsuranceActions)
{
subInteractionsWrapper.SetSubInteractions(CurrentInsuranceInteractions);
LoadingInsuranceActions = false;
}
});
return false;
}
if (parentInteraction == EItemInfoButton.Repair)
{
int playerRubles = GetPlayerRubles(___itemUiContext_1);
CurrentRepairInteractions = new(___item_0, ___itemUiContext_1, playerRubles);
subInteractionsWrapper.SetSubInteractions(CurrentRepairInteractions);
return false;
}
return true;
}
}
public class DeclareSubInteractionsTradingPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(R.TradingInteractions.Type, "get_SubInteractions");
}
[PatchPostfix]
public static void Postfix(ref IEnumerable<EItemInfoButton> __result)
{
__result = __result.Append(EItemInfoButton.Repair).Append(EItemInfoButton.Insure);
}
}
public class CreateSubInteractionsTradingPatch : ModulePatch
{
private static bool LoadingInsuranceActions = false;
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(R.TradingInteractions.Type, "CreateSubInteractions");
}
[PatchPrefix]
public static bool Prefix(object __instance, EItemInfoButton parentInteraction, ISubInteractions subInteractionsWrapper, ItemUiContext ___itemUiContext_0)
{
// Clear this, since something else should be active (even a different mouseover of the insurance button)
LoadingInsuranceActions = false;
var wrappedInstance = new R.TradingInteractions(__instance);
if (parentInteraction == EItemInfoButton.Insure)
{
int playerRubles = GetPlayerRubles(___itemUiContext_0);
// CreateSubInteractions is only on the base class here, which doesn't have an Item. But __instance is actually a GClass3054
Item item = wrappedInstance.Item;
CurrentInsuranceInteractions = new(item, ___itemUiContext_0, playerRubles);
CurrentInsuranceInteractions = MultiSelect.Active ?
new(MultiSelect.ItemContexts.Select(ic => ic.Item), ___itemUiContext_0, playerRubles) :
new(item, ___itemUiContext_0, playerRubles);
// Because this is async, need to protect against a different subInteractions activating before loading is done
// This isn't thread-safe at all but now the race condition is a microsecond instead of hundreds of milliseconds.
LoadingInsuranceActions = true;
CurrentInsuranceInteractions.LoadAsync(() =>
{
if (LoadingInsuranceActions)
{
subInteractionsWrapper.SetSubInteractions(CurrentInsuranceInteractions);
LoadingInsuranceActions = false;
}
});
return false;
}
if (parentInteraction == EItemInfoButton.Repair)
{
int playerRubles = GetPlayerRubles(___itemUiContext_0);
// CreateSubInteractions is only on the base class here, which doesn't have an Item. But __instance is actually a GClass3054
Item item = wrappedInstance.Item;
CurrentRepairInteractions = new(item, ___itemUiContext_0, playerRubles);
subInteractionsWrapper.SetSubInteractions(CurrentRepairInteractions);
return false;
}
return true;
}
}
public class SniffInteractionButtonCreationPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(InteractionButtonsContainer), nameof(InteractionButtonsContainer.method_3));
}
[PatchPrefix]
public static void Prefix(DynamicInteractionClass interaction)
{
if (interaction.IsInsuranceInteraction() || interaction.IsRepairInteraction())
{
CreatedButtonInteractionId = interaction.Id;
}
}
[PatchPostfix]
public static void Postfix()
{
CreatedButtonInteractionId = null;
}
}
public class ChangeInteractionButtonCreationPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(InteractionButtonsContainer), nameof(InteractionButtonsContainer.method_5));
}
[PatchPrefix]
public static void Prefix(SimpleContextMenuButton button)
{
if (!String.IsNullOrEmpty(CreatedButtonInteractionId))
{
if (InsuranceInteractions.IsInsuranceInteractionId(CreatedButtonInteractionId) && CurrentInsuranceInteractions != null)
{
button.SetButtonInteraction(CurrentInsuranceInteractions.GetButtonInteraction(CreatedButtonInteractionId));
}
else if (RepairInteractions.IsRepairInteractionId(CreatedButtonInteractionId) && CurrentRepairInteractions != null)
{
button.SetButtonInteraction(CurrentRepairInteractions.GetButtonInteraction(CreatedButtonInteractionId));
}
}
}
}
public class CleanUpInteractionsPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(SimpleContextMenu), nameof(SimpleContextMenu.Close));
}
[PatchPostfix]
public static void Postfix()
{
CurrentInsuranceInteractions = null;
CurrentRepairInteractions = null;
CreatedButtonInteractionId = null;
}
}
public class EnableInsureInnerItemsPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(R.ContextMenuHelper.Type, "IsInteractive");
}
[PatchPrefix]
public static bool Prefix(object __instance, EItemInfoButton button, ref IResult __result, Item ___item_0)
{
if (button != EItemInfoButton.Insure)
{
return true;
}
InsuranceCompanyClass insurance = new R.ContextMenuHelper(__instance).InsuranceCompany;
IEnumerable<Item> items = MultiSelect.Active ? MultiSelect.ItemContexts.Select(ic => ic.Item) : [___item_0];
IEnumerable<InsuranceItem> InsuranceItemes = items.Select(InsuranceItem.FindOrCreate);
IEnumerable<InsuranceItem> insurableItems = InsuranceItemes.SelectMany(insurance.GetItemChildren)
.Flatten(insurance.GetItemChildren)
.Concat(InsuranceItemes)
.Where(i => insurance.ItemTypeAvailableForInsurance(i) && !insurance.InsuredItems.Contains(i));
if (insurableItems.Any())
{
__result = SuccessfulResult.New;
return false;
}
return true;
}
}
public class DisableLoadPresetOnBulletsPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(MagazineBuildClass), nameof(MagazineBuildClass.TryFindPresetSource));
}
[PatchPrefix]
public static bool Prefix(Item selectedItem, ref GStruct416<Item> __result)
{
if (Settings.LoadMagPresetOnBullets.Value)
{
return true;
}
if (selectedItem is BulletClass)
{
__result = new MagazineBuildClass.Class3183(selectedItem);
return false;
}
return true;
}
}
public class EmptySlotMenuPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.DeclaredMethod(typeof(ModSlotView), nameof(ModSlotView.Show));
}
[PatchPostfix]
public static void Postfix(ModSlotView __instance, Slot slot, ItemContextAbstractClass parentItemContext, ItemUiContext itemUiContext)
{
if (!Settings.EnableSlotSearch.Value || slot.ContainedItem != null)
{
return;
}
EmptySlotMenuTrigger menuTrigger = __instance.GetOrAddComponent<EmptySlotMenuTrigger>();
menuTrigger.Init(slot, parentItemContext, itemUiContext);
}
}
public class EmptySlotMenuRemovePatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.DeclaredMethod(typeof(ModSlotView), nameof(ModSlotView.SetupItemView));
}
[PatchPostfix]
public static void Postfix(ModSlotView __instance)
{
EmptySlotMenuTrigger menuTrigger = __instance.GetComponent<EmptySlotMenuTrigger>();
if (menuTrigger != null)
{
UnityEngine.Object.Destroy(menuTrigger);
}
}
}
public class PositionSubMenuPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(R.InventoryInteractions.Type, "CreateSubInteractions");
}
// Existing logic tries to place it on the right, moving to the left if necessary. They didn't do it correctly, so it always goes on the left.
[PatchPostfix]
public static void Postfix(ISubInteractions subInteractionsWrapper)
{
if (subInteractionsWrapper is not InteractionButtonsContainer buttonsContainer)
{
return;
}
var wrappedContainer = buttonsContainer.R();
SimpleContextMenuButton button = wrappedContainer.ContextMenuButton;
SimpleContextMenu flyoutMenu = wrappedContainer.ContextMenu;
if (button == null || flyoutMenu == null)
{
return;
}
PositionContextMenuFlyout(button, flyoutMenu);
}
}
// Insurance submenu is async, need to postfix the actual set call
public class PositionInsuranceSubMenuPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(InteractionButtonsContainer), nameof(InteractionButtonsContainer.SetSubInteractions)).MakeGenericMethod([typeof(InsuranceInteractions.EInsurers)]);
}
// Existing logic tries to place it on the right, moving to the left if necessary. They didn't do it correctly, so it always goes on the left.
[PatchPostfix]
public static void Postfix(SimpleContextMenuButton ___simpleContextMenuButton_0, SimpleContextMenu ___simpleContextMenu_0)
{
PositionContextMenuFlyout(___simpleContextMenuButton_0, ___simpleContextMenu_0);
}
}
private static void PositionContextMenuFlyout(SimpleContextMenuButton button, SimpleContextMenu flyoutMenu)
{
RectTransform buttonTransform = button.RectTransform();
RectTransform flyoutTransform = flyoutMenu.RectTransform();
Vector2 leftPosition = flyoutTransform.position; // BSG's code will always put it on the left
leftPosition = new Vector2((float)Math.Round((double)leftPosition.x), (float)Math.Round((double)leftPosition.y));
Vector2 size = buttonTransform.rect.size;
Vector2 rightPosition = size - size * buttonTransform.pivot;
rightPosition = buttonTransform.TransformPoint(rightPosition);
// Round vector the way that CorrectPosition does
rightPosition = new Vector2((float)Math.Round((double)rightPosition.x), (float)Math.Round((double)rightPosition.y));
if (Settings.ContextMenuOnRight.Value)
{
// Try on the right
flyoutTransform.position = rightPosition;
flyoutMenu.CorrectPosition();
// This means CorrectPosition() moved it
if (!(flyoutTransform.position.x - rightPosition.x).IsZero())
{
flyoutTransform.position = leftPosition;
}
}
else
{
flyoutTransform.position = leftPosition;
flyoutMenu.CorrectPosition();
if (!(flyoutTransform.position.x - leftPosition.x).IsZero())
{
flyoutTransform.position = rightPosition;
}
}
}
private static int GetPlayerRubles(ItemUiContext itemUiContext)
{
StashClass stash = itemUiContext.R().InventoryController.Inventory.Stash;
if (stash == null)
{
return 0;
}
return R.Money.GetMoneySums(stash.Grid.ContainedItems.Keys)[ECurrencyType.RUB];
}
}

View File

@@ -8,217 +8,216 @@ using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;
namespace UIFixes
namespace UIFixes;
public static class ContextMenuShortcutPatches
{
public static class ContextMenuShortcutPatches
private static TMP_InputField LastSelectedInput = null;
public static void Enable()
{
private static TMP_InputField LastSelectedInput = null;
new ItemUiContextPatch().Enable();
public static void Enable()
new HideoutItemViewRegisterContextPatch().Enable();
new HideoutItemViewUnegisterContextPatch().Enable();
new TradingPanelRegisterContextPatch().Enable();
new TradingPanelUnregisterContextPatch().Enable();
new SelectCurrentContextPatch().Enable();
new DeselectCurrentContextPatch().Enable();
}
public class ItemUiContextPatch : ModulePatch
{
private static ItemInfoInteractionsAbstractClass<EItemInfoButton> Interactions;
protected override MethodBase GetTargetMethod()
{
new ItemUiContextPatch().Enable();
new HideoutItemViewRegisterContextPatch().Enable();
new HideoutItemViewUnegisterContextPatch().Enable();
new TradingPanelRegisterContextPatch().Enable();
new TradingPanelUnregisterContextPatch().Enable();
new SelectCurrentContextPatch().Enable();
new DeselectCurrentContextPatch().Enable();
return AccessTools.Method(typeof(ItemUiContext), nameof(ItemUiContext.Update));
}
public class ItemUiContextPatch : ModulePatch
[PatchPostfix]
public static void Postfix(ItemUiContext __instance)
{
private static ItemInfoInteractionsAbstractClass<EItemInfoButton> Interactions;
protected override MethodBase GetTargetMethod()
// Need an item context to operate on
ItemContextAbstractClass itemContext = __instance.R().ItemContext;
if (itemContext == null)
{
return AccessTools.Method(typeof(ItemUiContext), nameof(ItemUiContext.Update));
return;
}
[PatchPostfix]
public static void Postfix(ItemUiContext __instance)
if (!Settings.ItemContextBlocksTextInputs.Value &&
EventSystem.current?.currentSelectedGameObject != null &&
EventSystem.current.currentSelectedGameObject.GetComponent<TMP_InputField>() != null)
{
// Need an item context to operate on
ItemContextAbstractClass itemContext = __instance.R().ItemContext;
if (itemContext == null)
{
return;
}
if (!Settings.ItemContextBlocksTextInputs.Value &&
EventSystem.current?.currentSelectedGameObject != null &&
EventSystem.current.currentSelectedGameObject.GetComponent<TMP_InputField>() != null)
{
return;
}
if (Settings.InspectKeyBind.Value.IsDown())
{
TryInteraction(__instance, itemContext, EItemInfoButton.Inspect);
}
if (Settings.OpenKeyBind.Value.IsDown())
{
TryInteraction(__instance, itemContext, EItemInfoButton.Open);
}
if (Settings.TopUpKeyBind.Value.IsDown())
{
TryInteraction(__instance, itemContext, EItemInfoButton.TopUp);
}
if (Settings.UseKeyBind.Value.IsDown())
{
TryInteraction(__instance, itemContext, EItemInfoButton.Use);
}
if (Settings.UseAllKeyBind.Value.IsDown())
{
TryInteraction(__instance, itemContext, EItemInfoButton.UseAll, EItemInfoButton.Use);
}
if (Settings.UnloadKeyBind.Value.IsDown())
{
TryInteraction(__instance, itemContext, EItemInfoButton.Unload, EItemInfoButton.UnloadAmmo);
}
if (Settings.UnpackKeyBind.Value.IsDown())
{
TryInteraction(__instance, itemContext, EItemInfoButton.Unpack);
}
if (Settings.FilterByKeyBind.Value.IsDown())
{
TryInteraction(__instance, itemContext, EItemInfoButton.FilterSearch);
}
if (Settings.LinkedSearchKeyBind.Value.IsDown())
{
TryInteraction(__instance, itemContext, EItemInfoButton.LinkedSearch);
}
Interactions = null;
return;
}
private static void TryInteraction(ItemUiContext itemUiContext, ItemContextAbstractClass itemContext, EItemInfoButton interaction, EItemInfoButton? fallbackInteraction = null)
if (Settings.InspectKeyBind.Value.IsDown())
{
Interactions ??= itemUiContext.GetItemContextInteractions(itemContext, null);
if (!Interactions.ExecuteInteraction(interaction) && fallbackInteraction.HasValue)
{
Interactions.ExecuteInteraction(fallbackInteraction.Value);
}
TryInteraction(__instance, itemContext, EItemInfoButton.Inspect);
}
if (Settings.OpenKeyBind.Value.IsDown())
{
TryInteraction(__instance, itemContext, EItemInfoButton.Open);
}
if (Settings.TopUpKeyBind.Value.IsDown())
{
TryInteraction(__instance, itemContext, EItemInfoButton.TopUp);
}
if (Settings.UseKeyBind.Value.IsDown())
{
TryInteraction(__instance, itemContext, EItemInfoButton.Use);
}
if (Settings.UseAllKeyBind.Value.IsDown())
{
TryInteraction(__instance, itemContext, EItemInfoButton.UseAll, EItemInfoButton.Use);
}
if (Settings.UnloadKeyBind.Value.IsDown())
{
TryInteraction(__instance, itemContext, EItemInfoButton.Unload, EItemInfoButton.UnloadAmmo);
}
if (Settings.UnpackKeyBind.Value.IsDown())
{
TryInteraction(__instance, itemContext, EItemInfoButton.Unpack);
}
if (Settings.FilterByKeyBind.Value.IsDown())
{
TryInteraction(__instance, itemContext, EItemInfoButton.FilterSearch);
}
if (Settings.LinkedSearchKeyBind.Value.IsDown())
{
TryInteraction(__instance, itemContext, EItemInfoButton.LinkedSearch);
}
Interactions = null;
}
// HideoutItemViews don't register themselves with ItemUiContext for some reason
public class HideoutItemViewRegisterContextPatch : ModulePatch
private static void TryInteraction(ItemUiContext itemUiContext, ItemContextAbstractClass itemContext, EItemInfoButton interaction, EItemInfoButton? fallbackInteraction = null)
{
protected override MethodBase GetTargetMethod()
Interactions ??= itemUiContext.GetItemContextInteractions(itemContext, null);
if (!Interactions.ExecuteInteraction(interaction) && fallbackInteraction.HasValue)
{
return AccessTools.Method(typeof(HideoutItemView), nameof(HideoutItemView.OnPointerEnter));
}
[PatchPostfix]
public static void Postfix(HideoutItemView __instance, ItemUiContext ___ItemUiContext)
{
___ItemUiContext.RegisterCurrentItemContext(__instance.ItemContext);
}
}
public class HideoutItemViewUnegisterContextPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(HideoutItemView), nameof(HideoutItemView.OnPointerExit));
}
[PatchPostfix]
public static void Postfix(HideoutItemView __instance, ItemUiContext ___ItemUiContext)
{
___ItemUiContext.UnregisterCurrentItemContext(__instance.ItemContext);
}
}
public class TradingPanelRegisterContextPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(TradingRequisitePanel), nameof(TradingRequisitePanel.method_1)); // OnHoverStart
}
[PatchPostfix]
public static void Postfix(ItemUiContext ___itemUiContext_0, ItemContextAbstractClass ___itemContextAbstractClass)
{
___itemUiContext_0.RegisterCurrentItemContext(___itemContextAbstractClass);
}
}
public class TradingPanelUnregisterContextPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(TradingRequisitePanel), nameof(TradingRequisitePanel.method_2)); // OnHoverEnd
}
[PatchPostfix]
public static void Postfix(ItemUiContext ___itemUiContext_0, ItemContextAbstractClass ___itemContextAbstractClass)
{
___itemUiContext_0.UnregisterCurrentItemContext(___itemContextAbstractClass);
}
}
// Keybinds don't work if a textbox has focus - setting the textbox to readonly seems the best way to fix this
// without changing selection and causing weird side effects
public class SelectCurrentContextPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(ItemUiContext), nameof(ItemUiContext.RegisterCurrentItemContext));
}
[PatchPostfix]
public static void Postfix()
{
if (!Settings.ItemContextBlocksTextInputs.Value)
{
return;
}
if (EventSystem.current?.currentSelectedGameObject != null)
{
LastSelectedInput = EventSystem.current.currentSelectedGameObject.GetComponent<TMP_InputField>();
if (LastSelectedInput != null)
{
LastSelectedInput.readOnly = true;
}
}
}
}
public class DeselectCurrentContextPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(ItemUiContext), nameof(ItemUiContext.UnregisterCurrentItemContext));
}
[PatchPostfix]
public static void Postfix()
{
if (!Settings.ItemContextBlocksTextInputs.Value)
{
return;
}
if (LastSelectedInput != null)
{
LastSelectedInput.readOnly = false;
}
LastSelectedInput = null;
Interactions.ExecuteInteraction(fallbackInteraction.Value);
}
}
}
// HideoutItemViews don't register themselves with ItemUiContext for some reason
public class HideoutItemViewRegisterContextPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(HideoutItemView), nameof(HideoutItemView.OnPointerEnter));
}
[PatchPostfix]
public static void Postfix(HideoutItemView __instance, ItemUiContext ___ItemUiContext)
{
___ItemUiContext.RegisterCurrentItemContext(__instance.ItemContext);
}
}
public class HideoutItemViewUnegisterContextPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(HideoutItemView), nameof(HideoutItemView.OnPointerExit));
}
[PatchPostfix]
public static void Postfix(HideoutItemView __instance, ItemUiContext ___ItemUiContext)
{
___ItemUiContext.UnregisterCurrentItemContext(__instance.ItemContext);
}
}
public class TradingPanelRegisterContextPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(TradingRequisitePanel), nameof(TradingRequisitePanel.method_1)); // OnHoverStart
}
[PatchPostfix]
public static void Postfix(ItemUiContext ___itemUiContext_0, ItemContextAbstractClass ___itemContextAbstractClass)
{
___itemUiContext_0.RegisterCurrentItemContext(___itemContextAbstractClass);
}
}
public class TradingPanelUnregisterContextPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(TradingRequisitePanel), nameof(TradingRequisitePanel.method_2)); // OnHoverEnd
}
[PatchPostfix]
public static void Postfix(ItemUiContext ___itemUiContext_0, ItemContextAbstractClass ___itemContextAbstractClass)
{
___itemUiContext_0.UnregisterCurrentItemContext(___itemContextAbstractClass);
}
}
// Keybinds don't work if a textbox has focus - setting the textbox to readonly seems the best way to fix this
// without changing selection and causing weird side effects
public class SelectCurrentContextPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(ItemUiContext), nameof(ItemUiContext.RegisterCurrentItemContext));
}
[PatchPostfix]
public static void Postfix()
{
if (!Settings.ItemContextBlocksTextInputs.Value)
{
return;
}
if (EventSystem.current?.currentSelectedGameObject != null)
{
LastSelectedInput = EventSystem.current.currentSelectedGameObject.GetComponent<TMP_InputField>();
if (LastSelectedInput != null)
{
LastSelectedInput.readOnly = true;
}
}
}
}
public class DeselectCurrentContextPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(ItemUiContext), nameof(ItemUiContext.UnregisterCurrentItemContext));
}
[PatchPostfix]
public static void Postfix()
{
if (!Settings.ItemContextBlocksTextInputs.Value)
{
return;
}
if (LastSelectedInput != null)
{
LastSelectedInput.readOnly = false;
}
LastSelectedInput = null;
}
}
}

View File

@@ -6,94 +6,93 @@ using TMPro;
using UnityEngine;
using UnityEngine.UI;
namespace UIFixes
namespace UIFixes;
public static class FixFleaPatches
{
public static class FixFleaPatches
public static void Enable()
{
public static void Enable()
{
// These two are anal AF
new DoNotToggleOnMouseOverPatch().Enable();
new ToggleOnOpenPatch().Enable();
// These two are anal AF
new DoNotToggleOnMouseOverPatch().Enable();
new ToggleOnOpenPatch().Enable();
new OfferItemFixMaskPatch().Enable();
new OfferViewTweaksPatch().Enable();
new OfferItemFixMaskPatch().Enable();
new OfferViewTweaksPatch().Enable();
}
public class DoNotToggleOnMouseOverPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(CategoryView), nameof(CategoryView.PointerEnterHandler));
}
public class DoNotToggleOnMouseOverPatch : ModulePatch
[PatchPostfix]
public static void Postfix(Image ____toggleImage, Sprite ____closeSprite, bool ___bool_3)
{
protected override MethodBase GetTargetMethod()
if (!___bool_3)
{
return AccessTools.Method(typeof(CategoryView), nameof(CategoryView.PointerEnterHandler));
}
[PatchPostfix]
public static void Postfix(Image ____toggleImage, Sprite ____closeSprite, bool ___bool_3)
{
if (!___bool_3)
{
____toggleImage.sprite = ____closeSprite;
}
}
}
public class ToggleOnOpenPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(CategoryView), nameof(CategoryView.OpenCategory));
}
[PatchPostfix]
public static void Postfix(Image ____toggleImage, Sprite ____openSprite, bool ___bool_3)
{
if (___bool_3)
{
____toggleImage.sprite = ____openSprite;
}
}
}
public class OfferItemFixMaskPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(OfferItemDescription), nameof(OfferItemDescription.Show));
}
[PatchPostfix]
public static void Postfix(TextMeshProUGUI ____offerItemName)
{
____offerItemName.maskable = true;
foreach (var item in ____offerItemName.GetComponentsInChildren<TMP_SubMeshUI>())
{
item.maskable = true;
}
}
}
public class OfferViewTweaksPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(OfferView), nameof(OfferView.Awake));
}
[PatchPostfix]
public static void Postfix(OfferView __instance, GameObject ____expirationTimePanel)
{
// Intercept clicks on the actions area
var blocker = __instance.transform.Find("Actions").gameObject.GetOrAddComponent<Button>();
blocker.transition = Selectable.Transition.None;
// But enable clicks specifically on the minimize button
var minimizeButton = __instance.transform.Find("Actions/MinimizeButton").gameObject.GetOrAddComponent<Button>();
minimizeButton.onClick.AddListener(() => __instance.OnPointerClick(null));
// Stop expiration clock from dancing around
var timeLeft = ____expirationTimePanel.transform.Find("TimeLeft").GetComponent<HorizontalLayoutGroup>();
timeLeft.childControlWidth = false;
____toggleImage.sprite = ____closeSprite;
}
}
}
public class ToggleOnOpenPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(CategoryView), nameof(CategoryView.OpenCategory));
}
[PatchPostfix]
public static void Postfix(Image ____toggleImage, Sprite ____openSprite, bool ___bool_3)
{
if (___bool_3)
{
____toggleImage.sprite = ____openSprite;
}
}
}
public class OfferItemFixMaskPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(OfferItemDescription), nameof(OfferItemDescription.Show));
}
[PatchPostfix]
public static void Postfix(TextMeshProUGUI ____offerItemName)
{
____offerItemName.maskable = true;
foreach (var item in ____offerItemName.GetComponentsInChildren<TMP_SubMeshUI>())
{
item.maskable = true;
}
}
}
public class OfferViewTweaksPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(OfferView), nameof(OfferView.Awake));
}
[PatchPostfix]
public static void Postfix(OfferView __instance, GameObject ____expirationTimePanel)
{
// Intercept clicks on the actions area
var blocker = __instance.transform.Find("Actions").gameObject.GetOrAddComponent<Button>();
blocker.transition = Selectable.Transition.None;
// But enable clicks specifically on the minimize button
var minimizeButton = __instance.transform.Find("Actions/MinimizeButton").gameObject.GetOrAddComponent<Button>();
minimizeButton.onClick.AddListener(() => __instance.OnPointerClick(null));
// Stop expiration clock from dancing around
var timeLeft = ____expirationTimePanel.transform.Find("TimeLeft").GetComponent<HorizontalLayoutGroup>();
timeLeft.childControlWidth = false;
}
}
}

View File

@@ -3,21 +3,20 @@ using HarmonyLib;
using SPT.Reflection.Patching;
using System.Reflection;
namespace UIFixes
{
public class FixMailRecieveAllPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(ChatMessageSendBlock), nameof(ChatMessageSendBlock.Show));
}
namespace UIFixes;
[PatchPrefix]
public static void Prefix(DialogueClass dialogue)
{
// Force this false will recalculate each time. This is less than ideal, but the way the code is structured makes it very difficult to do correctly.
dialogue.HasMessagesWithRewards = false;
}
public class FixMailRecieveAllPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(ChatMessageSendBlock), nameof(ChatMessageSendBlock.Show));
}
[PatchPrefix]
public static void Prefix(DialogueClass dialogue)
{
// Force this false will recalculate each time. This is less than ideal, but the way the code is structured makes it very difficult to do correctly.
dialogue.HasMessagesWithRewards = false;
}
}

View File

@@ -6,64 +6,63 @@ using System.Reflection;
using TMPro;
using UnityEngine;
namespace UIFixes
namespace UIFixes;
public static class FixTooltipPatches
{
public static class FixTooltipPatches
public static void Enable()
{
public static void Enable()
new QuestTooltipPatch().Enable();
new ArmorTooltipPatch().Enable();
}
public class QuestTooltipPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
new QuestTooltipPatch().Enable();
new ArmorTooltipPatch().Enable();
return AccessTools.Method(typeof(QuestItemViewPanel), nameof(QuestItemViewPanel.method_2));
}
public class QuestTooltipPatch : ModulePatch
[PatchPostfix]
public static void Postfix(QuestItemViewPanel __instance)
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(QuestItemViewPanel), nameof(QuestItemViewPanel.method_2));
}
GridItemView parent = __instance.GetComponentInParent<GridItemView>();
parent?.ShowTooltip();
}
}
[PatchPostfix]
public static void Postfix(QuestItemViewPanel __instance)
{
GridItemView parent = __instance.GetComponentInParent<GridItemView>();
parent?.ShowTooltip();
}
public class ArmorTooltipPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(GridItemView), nameof(GridItemView.NewGridItemView));
}
public class ArmorTooltipPatch : ModulePatch
// BSG loves to implement the same stuff in totally different ways, and this way is bad and also wrong
[PatchPostfix]
public static void Postfix(GridItemView __instance, TextMeshProUGUI ___ItemValue, PointerEventsProxy ____valuePointerEventsProxy, QuestItemViewPanel ____questsItemViewPanel)
{
protected override MethodBase GetTargetMethod()
// Add hover events to the correct place
HoverTrigger trigger = ___ItemValue.GetComponent<HoverTrigger>();
if (trigger == null)
{
return AccessTools.Method(typeof(GridItemView), nameof(GridItemView.NewGridItemView));
}
// BSG loves to implement the same stuff in totally different ways, and this way is bad and also wrong
[PatchPostfix]
public static void Postfix(GridItemView __instance, TextMeshProUGUI ___ItemValue, PointerEventsProxy ____valuePointerEventsProxy, QuestItemViewPanel ____questsItemViewPanel)
{
// Add hover events to the correct place
HoverTrigger trigger = ___ItemValue.GetComponent<HoverTrigger>();
if (trigger == null)
trigger = ___ItemValue.gameObject.AddComponent<HoverTrigger>();
trigger.OnHoverStart += eventData => __instance.method_31();
trigger.OnHoverEnd += eventData =>
{
trigger = ___ItemValue.gameObject.AddComponent<HoverTrigger>();
trigger.OnHoverStart += eventData => __instance.method_31();
trigger.OnHoverEnd += eventData =>
{
__instance.method_32();
__instance.ShowTooltip();
};
__instance.method_32();
__instance.ShowTooltip();
};
// Need a child component for some reason, copying how the quest item tooltip does it
Transform hover = ____questsItemViewPanel?.transform.Find("Hover");
if (hover != null)
{
UnityEngine.Object.Instantiate(hover, trigger.transform, false);
}
// Remove old hover handler that covered the whole info panel
UnityEngine.Object.Destroy(____valuePointerEventsProxy);
// Need a child component for some reason, copying how the quest item tooltip does it
Transform hover = ____questsItemViewPanel?.transform.Find("Hover");
if (hover != null)
{
UnityEngine.Object.Instantiate(hover, trigger.transform, false);
}
// Remove old hover handler that covered the whole info panel
UnityEngine.Object.Destroy(____valuePointerEventsProxy);
}
}
}

View File

@@ -4,79 +4,78 @@ using HarmonyLib;
using SPT.Reflection.Patching;
using System.Reflection;
namespace UIFixes
namespace UIFixes;
public class FixTraderControllerSimulateFalsePatch : ModulePatch
{
public class FixTraderControllerSimulateFalsePatch : ModulePatch
protected override MethodBase GetTargetMethod()
{
protected override MethodBase GetTargetMethod()
return AccessTools.Method(typeof(TraderControllerClass), nameof(TraderControllerClass.ExecutePossibleAction), [typeof(ItemContextAbstractClass), typeof(Item), typeof(bool), typeof(bool)]);
}
// Recreating this function to add the comment section, so calling this with simulate = false doesn't break everything
[PatchPrefix]
[HarmonyPriority(Priority.Last)]
public static bool Prefix(TraderControllerClass __instance, ItemContextAbstractClass itemContext, Item targetItem, bool partialTransferOnly, bool simulate, ref ItemOperation __result)
{
TargetItemOperation opStruct;
opStruct.targetItem = targetItem;
opStruct.traderControllerClass = __instance;
opStruct.simulate = simulate;
opStruct.item = itemContext.Item;
Error error = new NoPossibleActionsError(opStruct.item);
bool mergeAvailable = itemContext.MergeAvailable;
bool splitAvailable = itemContext.SplitAvailable;
partialTransferOnly &= splitAvailable;
if (mergeAvailable)
{
return AccessTools.Method(typeof(TraderControllerClass), nameof(TraderControllerClass.ExecutePossibleAction), [typeof(ItemContextAbstractClass), typeof(Item), typeof(bool), typeof(bool)]);
if (partialTransferOnly)
{
__result = __instance.method_24(ref error, ref opStruct);
return false;
}
var operation = __instance.method_22(ref error, ref opStruct);
if (operation.Succeeded)
{
__result = operation;
return false;
}
}
// Recreating this function to add the comment section, so calling this with simulate = false doesn't break everything
[PatchPrefix]
[HarmonyPriority(Priority.Last)]
public static bool Prefix(TraderControllerClass __instance, ItemContextAbstractClass itemContext, Item targetItem, bool partialTransferOnly, bool simulate, ref ItemOperation __result)
if (opStruct.targetItem is IApplicable applicable)
{
TargetItemOperation opStruct;
opStruct.targetItem = targetItem;
opStruct.traderControllerClass = __instance;
opStruct.simulate = simulate;
opStruct.item = itemContext.Item;
Error error = new NoPossibleActionsError(opStruct.item);
bool mergeAvailable = itemContext.MergeAvailable;
bool splitAvailable = itemContext.SplitAvailable;
partialTransferOnly &= splitAvailable;
if (mergeAvailable)
var operation = __instance.method_23(applicable, ref error, ref opStruct);
if (operation.Succeeded)
{
if (partialTransferOnly)
{
__result = __instance.method_24(ref error, ref opStruct);
return false;
}
var operation = __instance.method_22(ref error, ref opStruct);
if (operation.Succeeded)
if (itemContext.IsOperationAllowed(operation.Value))
{
__result = operation;
return false;
}
}
if (opStruct.targetItem is IApplicable applicable)
{
var operation = __instance.method_23(applicable, ref error, ref opStruct);
if (operation.Succeeded)
// Begin added section
else if (!simulate && operation.Value != null)
{
if (itemContext.IsOperationAllowed(operation.Value))
{
__result = operation;
return false;
}
// Begin added section
else if (!simulate && operation.Value != null)
{
// BSG dropped this operation on the floor, but it needs to be rolled back if it's not going to be returned
operation.Value.RollBack();
}
// End added section
// BSG dropped this operation on the floor, but it needs to be rolled back if it's not going to be returned
operation.Value.RollBack();
}
// End added section
}
if (mergeAvailable && splitAvailable)
{
var operation = __instance.method_24(ref error, ref opStruct);
if (operation.Succeeded)
{
__result = operation;
return false;
}
}
__result = error;
return false;
}
if (mergeAvailable && splitAvailable)
{
var operation = __instance.method_24(ref error, ref opStruct);
if (operation.Succeeded)
{
__result = operation;
return false;
}
}
__result = error;
return false;
}
}

View File

@@ -4,49 +4,48 @@ using SPT.Reflection.Patching;
using System.Collections.Generic;
using System.Reflection;
namespace UIFixes
namespace UIFixes;
public class FixUnloadLastBulletPatch : ModulePatch
{
public class FixUnloadLastBulletPatch : ModulePatch
protected override MethodBase GetTargetMethod()
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(TraderControllerClass), nameof(TraderControllerClass.HasForeignEvents));
}
return AccessTools.Method(typeof(TraderControllerClass), nameof(TraderControllerClass.HasForeignEvents));
}
// Reimplement because theirs is wrong
[PatchPrefix]
public static bool Prefix(TraderControllerClass __instance, Item item, TraderControllerClass anotherOwner, ref bool __result, List<Item> ___list_2)
// Reimplement because theirs is wrong
[PatchPrefix]
public static bool Prefix(TraderControllerClass __instance, Item item, TraderControllerClass anotherOwner, ref bool __result, List<Item> ___list_2)
{
if (__instance == anotherOwner)
{
if (__instance == anotherOwner)
{
__result = false;
return false;
}
___list_2.Clear();
item.GetAllItemsNonAlloc(___list_2, false, true);
foreach (Item item2 in ___list_2)
{
foreach (var eventArgs in __instance.List_0)
{
ItemAddress location = eventArgs.GetLocation();
if (!eventArgs.OwnerId.Equals(anotherOwner.ID) && !eventArgs.OwnerId.Equals(__instance.ID)) // checking against this is what I changed
{
if (item2 == eventArgs.Item)
{
__result = true;
return false;
}
if (location != null && location.Container.ParentItem == item2)
{
__result = true;
return false;
}
}
}
}
__result = false;
return false;
}
___list_2.Clear();
item.GetAllItemsNonAlloc(___list_2, false, true);
foreach (Item item2 in ___list_2)
{
foreach (var eventArgs in __instance.List_0)
{
ItemAddress location = eventArgs.GetLocation();
if (!eventArgs.OwnerId.Equals(anotherOwner.ID) && !eventArgs.OwnerId.Equals(__instance.ID)) // checking against this is what I changed
{
if (item2 == eventArgs.Item)
{
__result = true;
return false;
}
if (location != null && location.Container.ParentItem == item2)
{
__result = true;
return false;
}
}
}
}
__result = false;
return false;
}
}

View File

@@ -11,425 +11,424 @@ using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.UI;
namespace UIFixes
namespace UIFixes;
public static class FleaPrevSearchPatches
{
public static class FleaPrevSearchPatches
private class HistoryEntry
{
private class HistoryEntry
public FilterRule filterRule;
public float scrollPosition = 0f;
}
private static readonly Stack<HistoryEntry> History = new();
private static string DelayedHandbookId = string.Empty;
private static float PossibleScrollPosition = -1f;
public static void Enable()
{
new RagfairScreenShowPatch().Enable();
new OfferViewListCategoryPickedPatch().Enable();
new OfferViewListDoneLoadingPatch().Enable();
new ChangedViewListTypePatch().Enable();
Settings.EnableFleaHistory.Subscribe(enabled =>
{
public FilterRule filterRule;
public float scrollPosition = 0f;
if (!enabled && PreviousFilterButton.Instance != null)
{
UnityEngine.Object.Destroy(PreviousFilterButton.Instance.gameObject);
PreviousFilterButton.Instance = null;
History.Clear();
}
});
}
public class PreviousFilterButton : MonoBehaviour
{
private RagFairClass ragfair;
private RagfairScreen ragfairScreen;
private DefaultUIButton button;
private LayoutElement layoutElement;
private bool goingBack = false;
public static PreviousFilterButton Instance;
public void Awake()
{
Instance = this;
button = GetComponent<DefaultUIButton>();
layoutElement = GetComponent<LayoutElement>();
button.OnClick.RemoveAllListeners();
button.OnClick.AddListener(OnClick);
}
private static readonly Stack<HistoryEntry> History = new();
private static string DelayedHandbookId = string.Empty;
private static float PossibleScrollPosition = -1f;
public static void Enable()
public void Show(RagfairScreen ragfairScreen, RagFairClass ragfair)
{
new RagfairScreenShowPatch().Enable();
new OfferViewListCategoryPickedPatch().Enable();
new OfferViewListDoneLoadingPatch().Enable();
new ChangedViewListTypePatch().Enable();
this.ragfair = ragfair;
this.ragfairScreen = ragfairScreen;
Settings.EnableFleaHistory.Subscribe(enabled =>
button.SetRawText("< " + "back".Localized(), 20);
layoutElement.minWidth = -1;
layoutElement.preferredWidth = -1;
// Prime the first filter
if (!History.Any())
{
if (!enabled && PreviousFilterButton.Instance != null)
{
UnityEngine.Object.Destroy(PreviousFilterButton.Instance.gameObject);
PreviousFilterButton.Instance = null;
History.Clear();
}
});
History.Push(new HistoryEntry() { filterRule = ragfair.method_3(EViewListType.AllOffers) }); // Player's saved default rule
}
// Load what they're searching now, which may or may not be the same as the default
OnFilterRuleChanged();
ragfair.OnFilterRuleChanged += OnFilterRuleChanged;
if (History.Count < 2)
{
button.Interactable = false;
}
gameObject.SetActive(ragfair.FilterRule.ViewListType == EViewListType.AllOffers);
}
public class PreviousFilterButton : MonoBehaviour
public void Close()
{
private RagFairClass ragfair;
private RagfairScreen ragfairScreen;
private DefaultUIButton button;
private LayoutElement layoutElement;
ragfair.OnFilterRuleChanged -= OnFilterRuleChanged;
ragfair = null;
ragfairScreen = null;
}
private bool goingBack = false;
public static PreviousFilterButton Instance;
public void Awake()
public void OnOffersLoaded(OfferViewList offerViewList)
{
if (!String.IsNullOrEmpty(DelayedHandbookId))
{
Instance = this;
button = GetComponent<DefaultUIButton>();
layoutElement = GetComponent<LayoutElement>();
// Super important to clear DelayedHandbookId *before* calling method_10, or infinite loops can occur!
string newHandbookId = DelayedHandbookId;
DelayedHandbookId = string.Empty;
button.OnClick.RemoveAllListeners();
button.OnClick.AddListener(OnClick);
offerViewList.method_10(newHandbookId, false);
return;
}
public void Show(RagfairScreen ragfairScreen, RagFairClass ragfair)
// Restore scroll position now that offers are loaded
if (History.Any())
{
this.ragfair = ragfair;
this.ragfairScreen = ragfairScreen;
offerViewList.R().Scroller.SetScrollPosition(History.Peek().scrollPosition);
}
}
button.SetRawText("< " + "back".Localized(), 20);
layoutElement.minWidth = -1;
layoutElement.preferredWidth = -1;
// Prime the first filter
if (!History.Any())
{
History.Push(new HistoryEntry() { filterRule = ragfair.method_3(EViewListType.AllOffers) }); // Player's saved default rule
}
// Load what they're searching now, which may or may not be the same as the default
OnFilterRuleChanged();
ragfair.OnFilterRuleChanged += OnFilterRuleChanged;
if (History.Count < 2)
{
button.Interactable = false;
}
gameObject.SetActive(ragfair.FilterRule.ViewListType == EViewListType.AllOffers);
private void OnClick()
{
History.Pop(); // remove current
if (History.Count < 2)
{
button.Interactable = false;
}
public void Close()
HistoryEntry previousEntry = History.Peek();
// Manually update parts of the UI because BSG sucks
UpdateColumnHeaders(ragfairScreen.R().OfferViewList.R().FiltersPanel, previousEntry.filterRule.SortType, previousEntry.filterRule.SortDirection);
goingBack = true;
ApplyFullFilter(previousEntry.filterRule);
goingBack = false;
}
private void OnFilterRuleChanged(RagFairClass.ESetFilterSource source = 0, bool clear = false, bool updateCategories = false)
{
if (goingBack || !string.IsNullOrEmpty(DelayedHandbookId) || ragfair.FilterRule.ViewListType != EViewListType.AllOffers)
{
ragfair.OnFilterRuleChanged -= OnFilterRuleChanged;
ragfair = null;
ragfairScreen = null;
return;
}
public void OnOffersLoaded(OfferViewList offerViewList)
HistoryEntry current = History.Any() ? History.Peek() : null;
if (current != null && current.filterRule.IsSimilarTo(ragfair.FilterRule))
{
if (!String.IsNullOrEmpty(DelayedHandbookId))
{
// Super important to clear DelayedHandbookId *before* calling method_10, or infinite loops can occur!
string newHandbookId = DelayedHandbookId;
DelayedHandbookId = string.Empty;
offerViewList.method_10(newHandbookId, false);
return;
}
// Restore scroll position now that offers are loaded
if (History.Any())
{
offerViewList.R().Scroller.SetScrollPosition(History.Peek().scrollPosition);
}
// Minor filter change, just update the current one
current.filterRule = ragfair.FilterRule;
return;
}
private void OnClick()
// Save the current scroll position before pushing the new entry
if (current != null)
{
History.Pop(); // remove current
if (History.Count < 2)
if (PossibleScrollPosition >= 0f)
{
button.Interactable = false;
}
HistoryEntry previousEntry = History.Peek();
// Manually update parts of the UI because BSG sucks
UpdateColumnHeaders(ragfairScreen.R().OfferViewList.R().FiltersPanel, previousEntry.filterRule.SortType, previousEntry.filterRule.SortDirection);
goingBack = true;
ApplyFullFilter(previousEntry.filterRule);
goingBack = false;
}
private void OnFilterRuleChanged(RagFairClass.ESetFilterSource source = 0, bool clear = false, bool updateCategories = false)
{
if (goingBack || !string.IsNullOrEmpty(DelayedHandbookId) || ragfair.FilterRule.ViewListType != EViewListType.AllOffers)
{
return;
}
HistoryEntry current = History.Any() ? History.Peek() : null;
if (current != null && current.filterRule.IsSimilarTo(ragfair.FilterRule))
{
// Minor filter change, just update the current one
current.filterRule = ragfair.FilterRule;
return;
}
// Save the current scroll position before pushing the new entry
if (current != null)
{
if (PossibleScrollPosition >= 0f)
{
current.scrollPosition = PossibleScrollPosition;
}
else
{
LightScroller scroller = ragfairScreen.R().OfferViewList.R().Scroller;
current.scrollPosition = scroller.NormalizedScrollPosition;
}
}
History.Push(new HistoryEntry() { filterRule = ragfair.FilterRule });
if (History.Count >= 2)
{
button.Interactable = true;
}
// Basic sanity to keep this from growing out of control
if (History.Count > 50)
{
var tempStack = new Stack<HistoryEntry>();
for (int i = History.Count / 2; i >= 0; i--)
{
tempStack.Push(History.Pop());
}
History.Clear();
while (tempStack.Any())
{
History.Push(tempStack.Pop());
}
}
}
// Copied from RagFairClass.AddSearchesInRule, but actually all of the properties
private void ApplyFullFilter(FilterRule filterRule)
{
// Order impacts the order the filters show in the UI
var searches = new List<RagfairSearch>();
// This part was tricky to figure out. Adding OR removing any of these ID filters will clear the others, so you can only do one of them.
// When going to a state with no id filter, you MUST remove something (or all to be safe)
if (!filterRule.FilterSearchId.IsNullOrEmpty())
{
searches.Add(new(EFilterType.FilterSearch, filterRule.FilterSearchId, true));
}
else if (!filterRule.NeededSearchId.IsNullOrEmpty())
{
searches.Add(new(EFilterType.NeededSearch, filterRule.NeededSearchId, true));
}
else if (!filterRule.LinkedSearchId.IsNullOrEmpty())
{
searches.Add(new(EFilterType.LinkedSearch, filterRule.LinkedSearchId, true));
current.scrollPosition = PossibleScrollPosition;
}
else
{
searches.Add(new(EFilterType.FilterSearch, String.Empty, false));
searches.Add(new(EFilterType.NeededSearch, String.Empty, false));
searches.Add(new(EFilterType.LinkedSearch, String.Empty, false));
}
searches.Add(new(EFilterType.Currency, filterRule.CurrencyType, filterRule.CurrencyType != 0));
searches.Add(new(EFilterType.PriceFrom, filterRule.PriceFrom, filterRule.PriceFrom != 0));
searches.Add(new(EFilterType.PriceTo, filterRule.PriceTo, filterRule.PriceTo != 0));
searches.Add(new(EFilterType.QuantityFrom, filterRule.QuantityFrom, filterRule.QuantityFrom != 0));
searches.Add(new(EFilterType.QuantityTo, filterRule.QuantityTo, filterRule.QuantityTo != 0));
searches.Add(new(EFilterType.ConditionFrom, filterRule.ConditionFrom, filterRule.ConditionFrom != 0));
searches.Add(new(EFilterType.ConditionTo, filterRule.ConditionTo, filterRule.ConditionTo != 100));
searches.Add(new(EFilterType.OneHourExpiration, filterRule.OneHourExpiration ? 1 : 0, filterRule.OneHourExpiration));
searches.Add(new(EFilterType.RemoveBartering, filterRule.RemoveBartering ? 1 : 0, filterRule.RemoveBartering));
searches.Add(new(EFilterType.OfferOwnerType, filterRule.OfferOwnerType, filterRule.OfferOwnerType != 0));
searches.Add(new(EFilterType.OnlyFunctional, filterRule.OnlyFunctional ? 1 : 0, filterRule.OnlyFunctional));
ragfair.method_24(filterRule.ViewListType, [.. searches], false, out FilterRule newRule);
// These properties don't consistute a new search, so much as a different view of the same search
newRule.Page = filterRule.Page;
newRule.SortType = filterRule.SortType;
newRule.SortDirection = filterRule.SortDirection;
// Can't set handbookId yet - it limits the result set and that in turn limits what categories even display
DelayedHandbookId = filterRule.HandbookId;
ragfair.SetFilterRule(newRule, true, true);
}
private static void UpdateColumnHeaders(FiltersPanel filtersPanel, ESortType sortType, bool sortDirection)
{
var wrappedFiltersPanel = filtersPanel.R();
RagfairFilterButton button = sortType switch
{
ESortType.Barter => wrappedFiltersPanel.BarterButton,
ESortType.Rating => wrappedFiltersPanel.RatingButton,
ESortType.OfferItem => wrappedFiltersPanel.OfferItemButton,
ESortType.ExpirationDate => wrappedFiltersPanel.ExpirationButton,
_ => wrappedFiltersPanel.PriceButton,
};
wrappedFiltersPanel.SortDescending = sortDirection;
filtersPanel.method_4(button);
}
}
public class RagfairScreenShowPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(RagfairScreen), nameof(RagfairScreen.Show));
}
[PatchPrefix]
public static void Prefix(DefaultUIButton ____addOfferButton, ref PreviousFilterButton __state)
{
// Create previous button
if (!Settings.EnableFleaHistory.Value)
{
return;
}
__state = ____addOfferButton.transform.parent.Find("PreviousFilterButton")?.GetComponent<PreviousFilterButton>();
if (__state == null)
{
var clone = UnityEngine.Object.Instantiate(____addOfferButton, ____addOfferButton.transform.parent, false);
clone.name = "PreviousFilterButton";
clone.transform.SetAsFirstSibling();
__state = clone.GetOrAddComponent<PreviousFilterButton>();
LightScroller scroller = ragfairScreen.R().OfferViewList.R().Scroller;
current.scrollPosition = scroller.NormalizedScrollPosition;
}
}
[PatchPostfix]
public static void Postfix(RagfairScreen __instance, ISession session, DefaultUIButton ____addOfferButton, PreviousFilterButton __state)
{
// Delete the upper right display options, since they aren't even implemented
var tabs = __instance.transform.Find("TopRightPanel/Tabs");
tabs?.gameObject.SetActive(false);
History.Push(new HistoryEntry() { filterRule = ragfair.FilterRule });
if (!Settings.EnableFleaHistory.Value)
if (History.Count >= 2)
{
button.Interactable = true;
}
// Basic sanity to keep this from growing out of control
if (History.Count > 50)
{
var tempStack = new Stack<HistoryEntry>();
for (int i = History.Count / 2; i >= 0; i--)
{
return;
tempStack.Push(History.Pop());
}
__state.Show(__instance, session.RagFair);
__instance.R().UI.AddDisposable(__state.Close);
History.Clear();
// Resize the Add Offer button to use less extra space
var addOfferLayout = ____addOfferButton.GetComponent<LayoutElement>();
addOfferLayout.minWidth = -1;
addOfferLayout.preferredWidth = -1;
// Recenter the add offer text
var addOfferLabel = ____addOfferButton.transform.Find("SizeLabel");
addOfferLabel.localPosition = new Vector3(0f, 0f, 0f);
// Tighten up the spacing
var layoutGroup = PreviousFilterButton.Instance.transform.parent.GetComponent<HorizontalLayoutGroup>();
layoutGroup.spacing = 5f;
}
}
public class ChangedViewListTypePatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(RagfairScreen), nameof(RagfairScreen.method_9));
}
[PatchPostfix]
public static void Postfix(EViewListType type)
{
PreviousFilterButton.Instance?.gameObject.SetActive(type == EViewListType.AllOffers);
}
}
public class OfferViewListCategoryPickedPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(OfferViewList), nameof(OfferViewList.method_10));
}
// The first thing this method does is set scrollposition to 0, so grab it first
[PatchPrefix]
public static void Prefix(LightScroller ____scroller)
{
PossibleScrollPosition = ____scroller.NormalizedScrollPosition;
}
[PatchPostfix]
public static void Postfix()
{
PossibleScrollPosition = -1f;
}
}
public class OfferViewListDoneLoadingPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(OfferViewList), nameof(OfferViewList.method_12));
}
[PatchPostfix]
public static async void Postfix(OfferViewList __instance, Task __result, EViewListType ___eviewListType_0)
{
await __result;
if (___eviewListType_0 != EViewListType.AllOffers)
while (tempStack.Any())
{
return;
}
PreviousFilterButton.Instance.OnOffersLoaded(__instance);
if (Settings.AutoExpandCategories.Value)
{
// Try to auto-expand categories to use available space. Gotta do math to see what fits
const int PanelHeight = 780;
const int CategoryHeight = 34;
const int SubcategoryHeight = 25;
var activeCategories = __instance.GetComponentsInChildren<CategoryView>();
var activeSubcategories = __instance.GetComponentsInChildren<SubcategoryView>();
int currentHeight = activeCategories.Length * CategoryHeight + activeSubcategories.Length * SubcategoryHeight;
var categories = __instance.GetComponentsInChildren<CombinedView>()
.Where(cv => cv.transform.childCount > 0)
.Select(cv => cv.transform.GetChild(0).GetComponent<CategoryView>())
.Where(c => c != null && c.gameObject.activeInHierarchy);
while (categories.Any())
{
// This is all child categories that aren't already open; have matching *offers* (x.Count); and if they have children themselves they're a category, otherwise a subcategory
int additionalHeight = categories
.Where(c => !c.R().IsOpen && c.Node != null)
.SelectMany(c => c.Node.Children)
.Where(n => n.Count > 0)
.Sum(n => n.Children.Any() ? CategoryHeight : SubcategoryHeight);
if (currentHeight + additionalHeight > PanelHeight)
{
break;
}
currentHeight += additionalHeight;
categories = categories.SelectMany(c => c.OpenCategory()).Where(v => v.gameObject.activeInHierarchy).OfType<CategoryView>();
}
History.Push(tempStack.Pop());
}
}
}
// Commented out properties just affect the view, so consider the two filters to be a single history entry
public static bool IsSimilarTo(this FilterRule one, FilterRule two)
// Copied from RagFairClass.AddSearchesInRule, but actually all of the properties
private void ApplyFullFilter(FilterRule filterRule)
{
return one.ViewListType == two.ViewListType &&
// one.Page == two.Page &&
// one.SortType == two.SortType &&
// one.SortDirection == two.SortDirection &&
// one.HandbookId == two.HandbookId &&
one.CurrencyType == two.CurrencyType &&
one.PriceFrom == two.PriceFrom &&
one.PriceTo == two.PriceTo &&
one.QuantityFrom == two.QuantityFrom &&
one.QuantityTo == two.QuantityTo &&
one.ConditionFrom == two.ConditionFrom &&
one.ConditionTo == two.ConditionTo &&
one.OneHourExpiration == two.OneHourExpiration &&
one.RemoveBartering == two.RemoveBartering &&
one.OfferOwnerType == two.OfferOwnerType &&
one.OnlyFunctional == two.OnlyFunctional &&
one.FilterSearchId == two.FilterSearchId &&
one.LinkedSearchId == two.LinkedSearchId &&
one.NeededSearchId == two.NeededSearchId;
// Order impacts the order the filters show in the UI
var searches = new List<RagfairSearch>();
// This part was tricky to figure out. Adding OR removing any of these ID filters will clear the others, so you can only do one of them.
// When going to a state with no id filter, you MUST remove something (or all to be safe)
if (!filterRule.FilterSearchId.IsNullOrEmpty())
{
searches.Add(new(EFilterType.FilterSearch, filterRule.FilterSearchId, true));
}
else if (!filterRule.NeededSearchId.IsNullOrEmpty())
{
searches.Add(new(EFilterType.NeededSearch, filterRule.NeededSearchId, true));
}
else if (!filterRule.LinkedSearchId.IsNullOrEmpty())
{
searches.Add(new(EFilterType.LinkedSearch, filterRule.LinkedSearchId, true));
}
else
{
searches.Add(new(EFilterType.FilterSearch, String.Empty, false));
searches.Add(new(EFilterType.NeededSearch, String.Empty, false));
searches.Add(new(EFilterType.LinkedSearch, String.Empty, false));
}
searches.Add(new(EFilterType.Currency, filterRule.CurrencyType, filterRule.CurrencyType != 0));
searches.Add(new(EFilterType.PriceFrom, filterRule.PriceFrom, filterRule.PriceFrom != 0));
searches.Add(new(EFilterType.PriceTo, filterRule.PriceTo, filterRule.PriceTo != 0));
searches.Add(new(EFilterType.QuantityFrom, filterRule.QuantityFrom, filterRule.QuantityFrom != 0));
searches.Add(new(EFilterType.QuantityTo, filterRule.QuantityTo, filterRule.QuantityTo != 0));
searches.Add(new(EFilterType.ConditionFrom, filterRule.ConditionFrom, filterRule.ConditionFrom != 0));
searches.Add(new(EFilterType.ConditionTo, filterRule.ConditionTo, filterRule.ConditionTo != 100));
searches.Add(new(EFilterType.OneHourExpiration, filterRule.OneHourExpiration ? 1 : 0, filterRule.OneHourExpiration));
searches.Add(new(EFilterType.RemoveBartering, filterRule.RemoveBartering ? 1 : 0, filterRule.RemoveBartering));
searches.Add(new(EFilterType.OfferOwnerType, filterRule.OfferOwnerType, filterRule.OfferOwnerType != 0));
searches.Add(new(EFilterType.OnlyFunctional, filterRule.OnlyFunctional ? 1 : 0, filterRule.OnlyFunctional));
ragfair.method_24(filterRule.ViewListType, [.. searches], false, out FilterRule newRule);
// These properties don't consistute a new search, so much as a different view of the same search
newRule.Page = filterRule.Page;
newRule.SortType = filterRule.SortType;
newRule.SortDirection = filterRule.SortDirection;
// Can't set handbookId yet - it limits the result set and that in turn limits what categories even display
DelayedHandbookId = filterRule.HandbookId;
ragfair.SetFilterRule(newRule, true, true);
}
private static void UpdateColumnHeaders(FiltersPanel filtersPanel, ESortType sortType, bool sortDirection)
{
var wrappedFiltersPanel = filtersPanel.R();
RagfairFilterButton button = sortType switch
{
ESortType.Barter => wrappedFiltersPanel.BarterButton,
ESortType.Rating => wrappedFiltersPanel.RatingButton,
ESortType.OfferItem => wrappedFiltersPanel.OfferItemButton,
ESortType.ExpirationDate => wrappedFiltersPanel.ExpirationButton,
_ => wrappedFiltersPanel.PriceButton,
};
wrappedFiltersPanel.SortDescending = sortDirection;
filtersPanel.method_4(button);
}
}
public class RagfairScreenShowPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(RagfairScreen), nameof(RagfairScreen.Show));
}
[PatchPrefix]
public static void Prefix(DefaultUIButton ____addOfferButton, ref PreviousFilterButton __state)
{
// Create previous button
if (!Settings.EnableFleaHistory.Value)
{
return;
}
__state = ____addOfferButton.transform.parent.Find("PreviousFilterButton")?.GetComponent<PreviousFilterButton>();
if (__state == null)
{
var clone = UnityEngine.Object.Instantiate(____addOfferButton, ____addOfferButton.transform.parent, false);
clone.name = "PreviousFilterButton";
clone.transform.SetAsFirstSibling();
__state = clone.GetOrAddComponent<PreviousFilterButton>();
}
}
[PatchPostfix]
public static void Postfix(RagfairScreen __instance, ISession session, DefaultUIButton ____addOfferButton, PreviousFilterButton __state)
{
// Delete the upper right display options, since they aren't even implemented
var tabs = __instance.transform.Find("TopRightPanel/Tabs");
tabs?.gameObject.SetActive(false);
if (!Settings.EnableFleaHistory.Value)
{
return;
}
__state.Show(__instance, session.RagFair);
__instance.R().UI.AddDisposable(__state.Close);
// Resize the Add Offer button to use less extra space
var addOfferLayout = ____addOfferButton.GetComponent<LayoutElement>();
addOfferLayout.minWidth = -1;
addOfferLayout.preferredWidth = -1;
// Recenter the add offer text
var addOfferLabel = ____addOfferButton.transform.Find("SizeLabel");
addOfferLabel.localPosition = new Vector3(0f, 0f, 0f);
// Tighten up the spacing
var layoutGroup = PreviousFilterButton.Instance.transform.parent.GetComponent<HorizontalLayoutGroup>();
layoutGroup.spacing = 5f;
}
}
public class ChangedViewListTypePatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(RagfairScreen), nameof(RagfairScreen.method_9));
}
[PatchPostfix]
public static void Postfix(EViewListType type)
{
PreviousFilterButton.Instance?.gameObject.SetActive(type == EViewListType.AllOffers);
}
}
public class OfferViewListCategoryPickedPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(OfferViewList), nameof(OfferViewList.method_10));
}
// The first thing this method does is set scrollposition to 0, so grab it first
[PatchPrefix]
public static void Prefix(LightScroller ____scroller)
{
PossibleScrollPosition = ____scroller.NormalizedScrollPosition;
}
[PatchPostfix]
public static void Postfix()
{
PossibleScrollPosition = -1f;
}
}
public class OfferViewListDoneLoadingPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(OfferViewList), nameof(OfferViewList.method_12));
}
[PatchPostfix]
public static async void Postfix(OfferViewList __instance, Task __result, EViewListType ___eviewListType_0)
{
await __result;
if (___eviewListType_0 != EViewListType.AllOffers)
{
return;
}
PreviousFilterButton.Instance.OnOffersLoaded(__instance);
if (Settings.AutoExpandCategories.Value)
{
// Try to auto-expand categories to use available space. Gotta do math to see what fits
const int PanelHeight = 780;
const int CategoryHeight = 34;
const int SubcategoryHeight = 25;
var activeCategories = __instance.GetComponentsInChildren<CategoryView>();
var activeSubcategories = __instance.GetComponentsInChildren<SubcategoryView>();
int currentHeight = activeCategories.Length * CategoryHeight + activeSubcategories.Length * SubcategoryHeight;
var categories = __instance.GetComponentsInChildren<CombinedView>()
.Where(cv => cv.transform.childCount > 0)
.Select(cv => cv.transform.GetChild(0).GetComponent<CategoryView>())
.Where(c => c != null && c.gameObject.activeInHierarchy);
while (categories.Any())
{
// This is all child categories that aren't already open; have matching *offers* (x.Count); and if they have children themselves they're a category, otherwise a subcategory
int additionalHeight = categories
.Where(c => !c.R().IsOpen && c.Node != null)
.SelectMany(c => c.Node.Children)
.Where(n => n.Count > 0)
.Sum(n => n.Children.Any() ? CategoryHeight : SubcategoryHeight);
if (currentHeight + additionalHeight > PanelHeight)
{
break;
}
currentHeight += additionalHeight;
categories = categories.SelectMany(c => c.OpenCategory()).Where(v => v.gameObject.activeInHierarchy).OfType<CategoryView>();
}
}
}
}
// Commented out properties just affect the view, so consider the two filters to be a single history entry
public static bool IsSimilarTo(this FilterRule one, FilterRule two)
{
return one.ViewListType == two.ViewListType &&
// one.Page == two.Page &&
// one.SortType == two.SortType &&
// one.SortDirection == two.SortDirection &&
// one.HandbookId == two.HandbookId &&
one.CurrencyType == two.CurrencyType &&
one.PriceFrom == two.PriceFrom &&
one.PriceTo == two.PriceTo &&
one.QuantityFrom == two.QuantityFrom &&
one.QuantityTo == two.QuantityTo &&
one.ConditionFrom == two.ConditionFrom &&
one.ConditionTo == two.ConditionTo &&
one.OneHourExpiration == two.OneHourExpiration &&
one.RemoveBartering == two.RemoveBartering &&
one.OfferOwnerType == two.OfferOwnerType &&
one.OnlyFunctional == two.OnlyFunctional &&
one.FilterSearchId == two.FilterSearchId &&
one.LinkedSearchId == two.LinkedSearchId &&
one.NeededSearchId == two.NeededSearchId;
}
}

View File

@@ -4,51 +4,50 @@ using SPT.Reflection.Patching;
using System.Linq;
using System.Reflection;
namespace UIFixes
namespace UIFixes;
public static class FleaSlotSearchPatches
{
public static class FleaSlotSearchPatches
public static void Enable()
{
public static void Enable()
new HandbookWorkaroundPatch().Enable();
}
public class HandbookWorkaroundPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
new HandbookWorkaroundPatch().Enable();
return AccessTools.Method(typeof(RagFairClass), nameof(RagFairClass.method_24));
}
public class HandbookWorkaroundPatch : ModulePatch
[PatchPrefix]
public static void Prefix(RagfairSearch[] searches, ref string __state, HandbookClass ___handbookClass)
{
protected override MethodBase GetTargetMethod()
if (!Settings.EnableSlotSearch.Value)
{
return AccessTools.Method(typeof(RagFairClass), nameof(RagFairClass.method_24));
return;
}
[PatchPrefix]
public static void Prefix(RagfairSearch[] searches, ref string __state, HandbookClass ___handbookClass)
var search = searches.FirstOrDefault(s => s.Type == EFilterType.LinkedSearch && s.StringValue.Contains(":"));
if (search != null)
{
if (!Settings.EnableSlotSearch.Value)
{
return;
}
__state = search.StringValue.Split(':')[0];
___handbookClass[__state].Data.Id = search.StringValue;
searches[searches.IndexOf(search)] = new(EFilterType.LinkedSearch, __state, search.Add);
}
}
var search = searches.FirstOrDefault(s => s.Type == EFilterType.LinkedSearch && s.StringValue.Contains(":"));
if (search != null)
{
__state = search.StringValue.Split(':')[0];
___handbookClass[__state].Data.Id = search.StringValue;
searches[searches.IndexOf(search)] = new(EFilterType.LinkedSearch, __state, search.Add);
}
[PatchPostfix]
public static void Postfix(ref string __state, HandbookClass ___handbookClass)
{
if (!Settings.EnableSlotSearch.Value)
{
return;
}
[PatchPostfix]
public static void Postfix(ref string __state, HandbookClass ___handbookClass)
if (__state != null)
{
if (!Settings.EnableSlotSearch.Value)
{
return;
}
if (__state != null)
{
___handbookClass[__state].Data.Id = __state;
}
___handbookClass[__state].Data.Id = __state;
}
}
}

View File

@@ -6,69 +6,68 @@ using System.Reflection;
using TMPro;
using UnityEngine;
namespace UIFixes
namespace UIFixes;
public static class FocusFleaOfferNumberPatches
{
public static class FocusFleaOfferNumberPatches
public static void Enable()
{
public static void Enable()
new MoneyPatch().Enable();
new BarterPatch().Enable();
}
public class MoneyPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
new MoneyPatch().Enable();
new BarterPatch().Enable();
return AccessTools.DeclaredMethod(typeof(HandoverRagfairMoneyWindow), nameof(HandoverRagfairMoneyWindow.Show));
}
public class MoneyPatch : ModulePatch
[PatchPostfix]
public static void Postfix(HandoverRagfairMoneyWindow __instance, TMP_InputField ____inputField)
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.DeclaredMethod(typeof(HandoverRagfairMoneyWindow), nameof(HandoverRagfairMoneyWindow.Show));
}
AllButtonKeybind allKeybind = __instance.GetOrAddComponent<AllButtonKeybind>();
allKeybind.Init(__instance.method_9);
[PatchPostfix]
public static void Postfix(HandoverRagfairMoneyWindow __instance, TMP_InputField ____inputField)
{
AllButtonKeybind allKeybind = __instance.GetOrAddComponent<AllButtonKeybind>();
allKeybind.Init(__instance.method_9);
____inputField.contentType = TMP_InputField.ContentType.IntegerNumber;
____inputField.ActivateInputField();
____inputField.Select();
}
}
____inputField.contentType = TMP_InputField.ContentType.IntegerNumber;
____inputField.ActivateInputField();
____inputField.Select();
}
public class BarterPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.DeclaredMethod(typeof(HandoverExchangeableItemsWindow), nameof(HandoverExchangeableItemsWindow.Show));
}
public class BarterPatch : ModulePatch
[PatchPostfix]
public static void Postfix(HandoverExchangeableItemsWindow __instance, TMP_InputField ____inputField)
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.DeclaredMethod(typeof(HandoverExchangeableItemsWindow), nameof(HandoverExchangeableItemsWindow.Show));
}
AllButtonKeybind allKeybind = __instance.GetOrAddComponent<AllButtonKeybind>();
allKeybind.Init(__instance.method_16);
[PatchPostfix]
public static void Postfix(HandoverExchangeableItemsWindow __instance, TMP_InputField ____inputField)
{
AllButtonKeybind allKeybind = __instance.GetOrAddComponent<AllButtonKeybind>();
allKeybind.Init(__instance.method_16);
____inputField.contentType = TMP_InputField.ContentType.IntegerNumber;
____inputField.ActivateInputField();
____inputField.Select();
}
}
____inputField.contentType = TMP_InputField.ContentType.IntegerNumber;
____inputField.ActivateInputField();
____inputField.Select();
}
public class AllButtonKeybind : MonoBehaviour
{
private Action purchaseAllAction;
public void Init(Action purchaseAllAction)
{
this.purchaseAllAction = purchaseAllAction;
}
public class AllButtonKeybind : MonoBehaviour
public void Update()
{
private Action purchaseAllAction;
public void Init(Action purchaseAllAction)
if (Settings.PurchaseAllKeybind.Value.IsDown())
{
this.purchaseAllAction = purchaseAllAction;
}
public void Update()
{
if (Settings.PurchaseAllKeybind.Value.IsDown())
{
purchaseAllAction();
}
purchaseAllAction();
}
}
}

View File

@@ -4,23 +4,22 @@ using SPT.Reflection.Patching;
using System.Reflection;
using TMPro;
namespace UIFixes
{
public class FocusTradeQuantityPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(BarterSchemePanel), nameof(BarterSchemePanel.method_10));
}
namespace UIFixes;
// Gets called on the TransactionChanged event.
// The reason quantity isn't focused on the 2nd+ purchase is that BSG calls ActivateInputField() and Select() before the transaction is finished
// During the transaction, the whole canvas group is not interactable, and these methods don't work on non-interactable fields
[PatchPostfix]
public static void Postfix(TMP_InputField ____quantity)
{
____quantity.ActivateInputField();
____quantity.Select();
}
public class FocusTradeQuantityPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(BarterSchemePanel), nameof(BarterSchemePanel.method_10));
}
// Gets called on the TransactionChanged event.
// The reason quantity isn't focused on the 2nd+ purchase is that BSG calls ActivateInputField() and Select() before the transaction is finished
// During the transaction, the whole canvas group is not interactable, and these methods don't work on non-interactable fields
[PatchPostfix]
public static void Postfix(TMP_InputField ____quantity)
{
____quantity.ActivateInputField();
____quantity.Select();
}
}

View File

@@ -9,117 +9,116 @@ using TMPro;
using UnityEngine;
using UnityEngine.UI;
namespace UIFixes
namespace UIFixes;
public static class GPCoinPatches
{
public static class GPCoinPatches
public static void Enable()
{
public static void Enable()
new MoneyPanelPatch().Enable();
new MoneyPanelTMPTextPatch().Enable();
}
public class MoneyPanelPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
new MoneyPanelPatch().Enable();
new MoneyPanelTMPTextPatch().Enable();
return AccessTools.Method(typeof(DisplayMoneyPanel), nameof(DisplayMoneyPanel.Show));
}
public class MoneyPanelPatch : ModulePatch
[PatchPrefix]
public static bool Prefix(DisplayMoneyPanel __instance, IEnumerable<Item> inventoryItems, TextMeshProUGUI ____roubles, TextMeshProUGUI ____euros, TextMeshProUGUI ____dollars)
{
protected override MethodBase GetTargetMethod()
if (!Settings.ShowGPCurrency.Value)
{
return AccessTools.Method(typeof(DisplayMoneyPanel), nameof(DisplayMoneyPanel.Show));
return true;
}
[PatchPrefix]
public static bool Prefix(DisplayMoneyPanel __instance, IEnumerable<Item> inventoryItems, TextMeshProUGUI ____roubles, TextMeshProUGUI ____euros, TextMeshProUGUI ____dollars)
__instance.ShowGameObject();
Transform gpCoinsTransform = __instance.transform.Find("GPCoins");
if (gpCoinsTransform == null)
{
if (!Settings.ShowGPCurrency.Value)
Transform dollars = __instance.transform.Find("Dollars");
gpCoinsTransform = UnityEngine.Object.Instantiate(dollars, __instance.transform, false);
gpCoinsTransform.name = "GPCoins";
Image icon = gpCoinsTransform.Find("Image").GetComponent<Image>();
icon.sprite = EFTHardSettings.Instance.StaticIcons.GetSmallCurrencySign(ECurrencyType.GP);
LayoutElement imageLayout = icon.GetComponent<LayoutElement>();
imageLayout.preferredHeight = -1f;
imageLayout.preferredWidth = -1f;
Settings.ShowGPCurrency.Subscribe(enabled =>
{
return true;
}
__instance.ShowGameObject();
Transform gpCoinsTransform = __instance.transform.Find("GPCoins");
if (gpCoinsTransform == null)
{
Transform dollars = __instance.transform.Find("Dollars");
gpCoinsTransform = UnityEngine.Object.Instantiate(dollars, __instance.transform, false);
gpCoinsTransform.name = "GPCoins";
Image icon = gpCoinsTransform.Find("Image").GetComponent<Image>();
icon.sprite = EFTHardSettings.Instance.StaticIcons.GetSmallCurrencySign(ECurrencyType.GP);
LayoutElement imageLayout = icon.GetComponent<LayoutElement>();
imageLayout.preferredHeight = -1f;
imageLayout.preferredWidth = -1f;
Settings.ShowGPCurrency.Subscribe(enabled =>
if (!enabled && gpCoinsTransform != null)
{
if (!enabled && gpCoinsTransform != null)
{
UnityEngine.Object.Destroy(gpCoinsTransform.gameObject);
}
});
}
TextMeshProUGUI gpCoins = gpCoinsTransform.Find("Label").GetComponent<TextMeshProUGUI>();
var sums = R.Money.GetMoneySums(inventoryItems);
NumberFormatInfo numberFormatInfo = new() { NumberGroupSeparator = " " };
____roubles.text = sums[ECurrencyType.RUB].ToString("N0", numberFormatInfo);
____euros.text = sums[ECurrencyType.EUR].ToString("N0", numberFormatInfo);
____dollars.text = sums[ECurrencyType.USD].ToString("N0", numberFormatInfo);
gpCoins.text = sums[ECurrencyType.GP].ToString("N0", numberFormatInfo);
return false;
UnityEngine.Object.Destroy(gpCoinsTransform.gameObject);
}
});
}
TextMeshProUGUI gpCoins = gpCoinsTransform.Find("Label").GetComponent<TextMeshProUGUI>();
var sums = R.Money.GetMoneySums(inventoryItems);
NumberFormatInfo numberFormatInfo = new() { NumberGroupSeparator = " " };
____roubles.text = sums[ECurrencyType.RUB].ToString("N0", numberFormatInfo);
____euros.text = sums[ECurrencyType.EUR].ToString("N0", numberFormatInfo);
____dollars.text = sums[ECurrencyType.USD].ToString("N0", numberFormatInfo);
gpCoins.text = sums[ECurrencyType.GP].ToString("N0", numberFormatInfo);
return false;
}
}
public class MoneyPanelTMPTextPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(DisplayMoneyPanelTMPText), nameof(DisplayMoneyPanelTMPText.Show));
}
public class MoneyPanelTMPTextPatch : ModulePatch
[PatchPrefix]
public static bool Prefix(DisplayMoneyPanelTMPText __instance, IEnumerable<Item> inventoryItems, TMP_Text ____roubles, TMP_Text ____euros, TMP_Text ____dollars)
{
protected override MethodBase GetTargetMethod()
if (!Settings.ShowGPCurrency.Value)
{
return AccessTools.Method(typeof(DisplayMoneyPanelTMPText), nameof(DisplayMoneyPanelTMPText.Show));
return true;
}
[PatchPrefix]
public static bool Prefix(DisplayMoneyPanelTMPText __instance, IEnumerable<Item> inventoryItems, TMP_Text ____roubles, TMP_Text ____euros, TMP_Text ____dollars)
__instance.ShowGameObject();
Transform gpCoinsTransform = __instance.transform.Find("GP");
if (gpCoinsTransform == null)
{
if (!Settings.ShowGPCurrency.Value)
Transform dollars = __instance.transform.Find("USD");
gpCoinsTransform = UnityEngine.Object.Instantiate(dollars, __instance.transform, false);
gpCoinsTransform.name = "GP";
Settings.ShowGPCurrency.Subscribe(enabled =>
{
return true;
}
__instance.ShowGameObject();
Transform gpCoinsTransform = __instance.transform.Find("GP");
if (gpCoinsTransform == null)
{
Transform dollars = __instance.transform.Find("USD");
gpCoinsTransform = UnityEngine.Object.Instantiate(dollars, __instance.transform, false);
gpCoinsTransform.name = "GP";
Settings.ShowGPCurrency.Subscribe(enabled =>
if (!enabled && gpCoinsTransform != null)
{
if (!enabled && gpCoinsTransform != null)
{
UnityEngine.Object.Destroy(gpCoinsTransform.gameObject);
}
});
}
TextMeshProUGUI gpCoins = gpCoinsTransform.GetComponent<TextMeshProUGUI>();
var sums = R.Money.GetMoneySums(inventoryItems);
NumberFormatInfo numberFormatInfo = new() { NumberGroupSeparator = " " };
____roubles.text = CurrencyInfo.GetCurrencyChar(ECurrencyType.RUB) + " " + sums[ECurrencyType.RUB].ToString("N0", numberFormatInfo);
____euros.text = CurrencyInfo.GetCurrencyChar(ECurrencyType.EUR) + " " + sums[ECurrencyType.EUR].ToString("N0", numberFormatInfo);
____dollars.text = CurrencyInfo.GetCurrencyChar(ECurrencyType.USD) + " " + sums[ECurrencyType.USD].ToString("N0", numberFormatInfo);
gpCoins.text = "GP " + sums[ECurrencyType.GP].ToString("N0", numberFormatInfo);
return false;
UnityEngine.Object.Destroy(gpCoinsTransform.gameObject);
}
});
}
TextMeshProUGUI gpCoins = gpCoinsTransform.GetComponent<TextMeshProUGUI>();
var sums = R.Money.GetMoneySums(inventoryItems);
NumberFormatInfo numberFormatInfo = new() { NumberGroupSeparator = " " };
____roubles.text = CurrencyInfo.GetCurrencyChar(ECurrencyType.RUB) + " " + sums[ECurrencyType.RUB].ToString("N0", numberFormatInfo);
____euros.text = CurrencyInfo.GetCurrencyChar(ECurrencyType.EUR) + " " + sums[ECurrencyType.EUR].ToString("N0", numberFormatInfo);
____dollars.text = CurrencyInfo.GetCurrencyChar(ECurrencyType.USD) + " " + sums[ECurrencyType.USD].ToString("N0", numberFormatInfo);
gpCoins.text = "GP " + sums[ECurrencyType.GP].ToString("N0", numberFormatInfo);
return false;
}
}
}

View File

@@ -6,96 +6,95 @@ using System.Reflection;
using UnityEngine;
using UnityEngine.UI;
namespace UIFixes
namespace UIFixes;
public class GridWindowButtonsPatch : ModulePatch
{
public class GridWindowButtonsPatch : ModulePatch
protected override MethodBase GetTargetMethod()
{
protected override MethodBase GetTargetMethod()
return AccessTools.DeclaredMethod(typeof(GridWindow), nameof(GridWindow.Show));
}
[PatchPostfix]
public static void Postfix(GridWindow __instance)
{
var wrappedInstance = __instance.R();
if (Settings.AddContainerButtons.Value && wrappedInstance.LootItem.Int32_0 > 3) // Greater than 3 cells wide
{
return AccessTools.DeclaredMethod(typeof(GridWindow), nameof(GridWindow.Show));
Transform closeButton = __instance.transform.Find("Caption Panel/Close Button");
Image sortBackground = __instance.transform.Find("Caption Panel/Sort Button")?.GetComponent<Image>();
// Left button
Button leftButton = CreateButton(closeButton, sortBackground.sprite, EItemAttributeId.RecoilBack);
leftButton.onClick.AddListener(() => SnapLeft(__instance));
wrappedInstance.UI.AddDisposable(() => leftButton.onClick.RemoveAllListeners());
// Right button
Button rightButton = CreateButton(closeButton, sortBackground.sprite, EItemAttributeId.RecoilBack);
rightButton.transform.Find("X").Rotate(0f, 180f, 0f);
rightButton.onClick.AddListener(() => SnapRight(__instance));
wrappedInstance.UI.AddDisposable(() => rightButton.onClick.RemoveAllListeners());
// Put close back on the end
closeButton.SetAsLastSibling();
}
[PatchPostfix]
public static void Postfix(GridWindow __instance)
// Keybinds
LeftRightKeybind leftRightKeybind = __instance.GetOrAddComponent<LeftRightKeybind>();
leftRightKeybind.Init(__instance);
}
private static Button CreateButton(Transform template, Sprite backgroundSprite, EItemAttributeId attributeIcon)
{
Transform transform = UnityEngine.Object.Instantiate(template, template.parent, false);
Image background = transform.GetComponent<Image>();
background.sprite = backgroundSprite;
Image icon = transform.Find("X").GetComponent<Image>();
icon.sprite = EFTHardSettings.Instance.StaticIcons.GetAttributeIcon(attributeIcon);
icon.overrideSprite = null;
icon.SetNativeSize();
Button button = transform.GetComponent<Button>();
button.navigation = new Navigation() { mode = Navigation.Mode.None };
return button;
}
public class LeftRightKeybind : MonoBehaviour
{
private GridWindow window;
public void Init(GridWindow window)
{
var wrappedInstance = __instance.R();
if (Settings.AddContainerButtons.Value && wrappedInstance.LootItem.Int32_0 > 3) // Greater than 3 cells wide
this.window = window;
}
public void Update()
{
bool isTopWindow = window.transform.GetSiblingIndex() == window.transform.parent.childCount - 1;
if (Settings.SnapLeftKeybind.Value.IsDown() && isTopWindow)
{
Transform closeButton = __instance.transform.Find("Caption Panel/Close Button");
Image sortBackground = __instance.transform.Find("Caption Panel/Sort Button")?.GetComponent<Image>();
// Left button
Button leftButton = CreateButton(closeButton, sortBackground.sprite, EItemAttributeId.RecoilBack);
leftButton.onClick.AddListener(() => SnapLeft(__instance));
wrappedInstance.UI.AddDisposable(() => leftButton.onClick.RemoveAllListeners());
// Right button
Button rightButton = CreateButton(closeButton, sortBackground.sprite, EItemAttributeId.RecoilBack);
rightButton.transform.Find("X").Rotate(0f, 180f, 0f);
rightButton.onClick.AddListener(() => SnapRight(__instance));
wrappedInstance.UI.AddDisposable(() => rightButton.onClick.RemoveAllListeners());
// Put close back on the end
closeButton.SetAsLastSibling();
SnapLeft(window);
}
// Keybinds
LeftRightKeybind leftRightKeybind = __instance.GetOrAddComponent<LeftRightKeybind>();
leftRightKeybind.Init(__instance);
}
private static Button CreateButton(Transform template, Sprite backgroundSprite, EItemAttributeId attributeIcon)
{
Transform transform = UnityEngine.Object.Instantiate(template, template.parent, false);
Image background = transform.GetComponent<Image>();
background.sprite = backgroundSprite;
Image icon = transform.Find("X").GetComponent<Image>();
icon.sprite = EFTHardSettings.Instance.StaticIcons.GetAttributeIcon(attributeIcon);
icon.overrideSprite = null;
icon.SetNativeSize();
Button button = transform.GetComponent<Button>();
button.navigation = new Navigation() { mode = Navigation.Mode.None };
return button;
}
public class LeftRightKeybind : MonoBehaviour
{
private GridWindow window;
public void Init(GridWindow window)
if (Settings.SnapRightKeybind.Value.IsDown() && isTopWindow)
{
this.window = window;
SnapRight(window);
}
public void Update()
{
bool isTopWindow = window.transform.GetSiblingIndex() == window.transform.parent.childCount - 1;
if (Settings.SnapLeftKeybind.Value.IsDown() && isTopWindow)
{
SnapLeft(window);
}
if (Settings.SnapRightKeybind.Value.IsDown() && isTopWindow)
{
SnapRight(window);
}
}
}
private static void SnapLeft(GridWindow window)
{
RectTransform inspectRect = (RectTransform)window.transform;
inspectRect.anchoredPosition = new Vector2((float)Screen.width / 4f / inspectRect.lossyScale.x, inspectRect.anchoredPosition.y);
}
private static void SnapRight(GridWindow window)
{
RectTransform inspectRect = (RectTransform)window.transform;
inspectRect.anchoredPosition = new Vector2((float)Screen.width * 3f / 4f / inspectRect.lossyScale.x, inspectRect.anchoredPosition.y);
}
}
private static void SnapLeft(GridWindow window)
{
RectTransform inspectRect = (RectTransform)window.transform;
inspectRect.anchoredPosition = new Vector2((float)Screen.width / 4f / inspectRect.lossyScale.x, inspectRect.anchoredPosition.y);
}
private static void SnapRight(GridWindow window)
{
RectTransform inspectRect = (RectTransform)window.transform;
inspectRect.anchoredPosition = new Vector2((float)Screen.width * 3f / 4f / inspectRect.lossyScale.x, inspectRect.anchoredPosition.y);
}
}

View File

@@ -3,85 +3,85 @@ using HarmonyLib;
using SPT.Reflection.Patching;
using System.Reflection;
namespace UIFixes
namespace UIFixes;
public static class HideoutLevelPatches
{
public static class HideoutLevelPatches
private static string CurrentArea;
private static ELevelType CurrentLevel = ELevelType.NotSet;
public static void Enable()
{
private static string CurrentArea;
private static ELevelType CurrentLevel = ELevelType.NotSet;
new SelectAreaPatch().Enable();
new ChangeLevelPatch().Enable();
new PickInitialLevelPatch().Enable();
new ClearLevelPatch().Enable();
}
public static void Enable()
public class SelectAreaPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
new SelectAreaPatch().Enable();
new ChangeLevelPatch().Enable();
new PickInitialLevelPatch().Enable();
new ClearLevelPatch().Enable();
return AccessTools.Method(typeof(AreaScreenSubstrate), nameof(AreaScreenSubstrate.SelectArea));
}
public class SelectAreaPatch : ModulePatch
[PatchPrefix]
public static void Prefix(AreaData areaData)
{
protected override MethodBase GetTargetMethod()
if (areaData.Template.Id != CurrentArea)
{
return AccessTools.Method(typeof(AreaScreenSubstrate), nameof(AreaScreenSubstrate.SelectArea));
}
[PatchPrefix]
public static void Prefix(AreaData areaData)
{
if (areaData.Template.Id != CurrentArea)
{
CurrentArea = areaData.Template.Id;
CurrentLevel = ELevelType.NotSet;
}
}
}
public class ChangeLevelPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(AreaScreenSubstrate), nameof(AreaScreenSubstrate.method_6));
}
[PatchPrefix]
public static void Prefix(ELevelType state)
{
CurrentLevel = state;
}
}
public class PickInitialLevelPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(AreaScreenSubstrate), nameof(AreaScreenSubstrate.method_3));
}
[PatchPrefix]
public static bool Prefix(ref ELevelType __result)
{
if (CurrentLevel != ELevelType.NotSet) {
__result = CurrentLevel;
return false;
}
return true;
}
}
public class ClearLevelPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(HideoutScreenOverlay), nameof(HideoutScreenOverlay.ReturnToPreviousState));
}
[PatchPostfix]
public static void Postfix()
{
CurrentArea = null;
CurrentArea = areaData.Template.Id;
CurrentLevel = ELevelType.NotSet;
}
}
}
public class ChangeLevelPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(AreaScreenSubstrate), nameof(AreaScreenSubstrate.method_6));
}
[PatchPrefix]
public static void Prefix(ELevelType state)
{
CurrentLevel = state;
}
}
public class PickInitialLevelPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(AreaScreenSubstrate), nameof(AreaScreenSubstrate.method_3));
}
[PatchPrefix]
public static bool Prefix(ref ELevelType __result)
{
if (CurrentLevel != ELevelType.NotSet)
{
__result = CurrentLevel;
return false;
}
return true;
}
}
public class ClearLevelPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(HideoutScreenOverlay), nameof(HideoutScreenOverlay.ReturnToPreviousState));
}
[PatchPostfix]
public static void Postfix()
{
CurrentArea = null;
CurrentLevel = ELevelType.NotSet;
}
}
}

View File

@@ -11,228 +11,227 @@ using TMPro;
using UnityEngine.EventSystems;
using UnityEngine.UI;
namespace UIFixes
namespace UIFixes;
public static class HideoutSearchPatches
{
public static class HideoutSearchPatches
private static readonly Dictionary<string, string> LastSearches = [];
private static float LastAbsoluteDownScrollPosition = -1f;
private static void ClearLastScrollPosition() => LastAbsoluteDownScrollPosition = -1f;
public static void Enable()
{
private static readonly Dictionary<string, string> LastSearches = [];
new LazyLoadPatch().Enable();
new RestoreHideoutSearchPatch().Enable();
new SaveHideoutSearchPatch().Enable();
new CloseHideoutSearchPatch().Enable();
new FastHideoutSearchPatch().Enable();
new FixHideoutSearchAgainPatch().Enable();
new CancelScrollOnMouseWheelPatch().Enable();
new BlockHideoutEnterPatch().Enable();
}
private static float LastAbsoluteDownScrollPosition = -1f;
private static void ClearLastScrollPosition() => LastAbsoluteDownScrollPosition = -1f;
public static void Enable()
// Deactivate ProduceViews as they lazy load if they don't match the search
public class LazyLoadPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
new LazyLoadPatch().Enable();
new RestoreHideoutSearchPatch().Enable();
new SaveHideoutSearchPatch().Enable();
new CloseHideoutSearchPatch().Enable();
new FastHideoutSearchPatch().Enable();
new FixHideoutSearchAgainPatch().Enable();
new CancelScrollOnMouseWheelPatch().Enable();
new BlockHideoutEnterPatch().Enable();
return AccessTools.Method(R.ProductionPanelShowSubclass.Type, "method_2");
}
// Deactivate ProduceViews as they lazy load if they don't match the search
public class LazyLoadPatch : ModulePatch
[PatchPostfix]
public static void Postfix(object __instance, object scheme, ProduceView view)
{
protected override MethodBase GetTargetMethod()
var instance = new R.ProductionPanelShowSubclass(__instance);
var productScheme = new R.Scheme(scheme);
ValidationInputField searchField = instance.ProductionPanel.R().SeachInputField;
if (searchField.text.Length > 0 && productScheme.EndProduct.LocalizedName().IndexOf(searchField.text, StringComparison.InvariantCultureIgnoreCase) < 0)
{
return AccessTools.Method(R.ProductionPanelShowSubclass.Type, "method_2");
view.GameObject.SetActive(false);
}
[PatchPostfix]
public static void Postfix(object __instance, object scheme, ProduceView view)
// As the objects load in, try to restore the old scroll position
if (LastAbsoluteDownScrollPosition >= 0f)
{
var instance = new R.ProductionPanelShowSubclass(__instance);
var productScheme = new R.Scheme(scheme);
ValidationInputField searchField = instance.ProductionPanel.R().SeachInputField;
if (searchField.text.Length > 0 && productScheme.EndProduct.LocalizedName().IndexOf(searchField.text, StringComparison.InvariantCultureIgnoreCase) < 0)
{
view.GameObject.SetActive(false);
}
// As the objects load in, try to restore the old scroll position
if (LastAbsoluteDownScrollPosition >= 0f)
{
ScrollRect scrollRect = view.GetComponentInParent<ScrollRect>();
if (scrollRect != null)
{
LayoutRebuilder.ForceRebuildLayoutImmediate(scrollRect.RectTransform());
float currentAbsoluteDownScrollPosition = (1f - scrollRect.verticalNormalizedPosition) * (scrollRect.content.rect.height - scrollRect.viewport.rect.height);
if (LastAbsoluteDownScrollPosition > currentAbsoluteDownScrollPosition + 112f) // 112 is about the height of each item
{
scrollRect.verticalNormalizedPosition = 0f;
}
else
{
// Last one, try to set it exactly
scrollRect.verticalNormalizedPosition = 1f - (LastAbsoluteDownScrollPosition / (scrollRect.content.rect.height - scrollRect.viewport.rect.height));
ClearLastScrollPosition();
}
}
}
}
}
// Populate the search box, and force the window to render
public class RestoreHideoutSearchPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(ProductionPanel), nameof(ProductionPanel.ShowContents));
}
[PatchPrefix]
public static void Prefix(ProductionPanel __instance, ValidationInputField ____searchInputField)
{
if (LastSearches.TryGetValue(__instance.AreaData.ToString(), out string lastSearch))
{
____searchInputField.text = lastSearch;
}
ScrollPatches.KeyScrollListener listener = __instance.GetComponentInParent<ScrollPatches.KeyScrollListener>();
listener?.OnKeyScroll.AddListener(ClearLastScrollPosition);
}
[PatchPostfix]
public static void Postfix(ProductionPanel __instance, ValidationInputField ____searchInputField)
{
// Force it to render immediately, at full height, even if the search filtering would reduce the number of children
if (__instance.method_9().Count() > 2)
{
AreaScreenSubstrate areaScreenSubstrate = __instance.GetComponentInParent<AreaScreenSubstrate>();
LayoutElement layoutElement = areaScreenSubstrate.R().ContentLayout;
layoutElement.minHeight = 750f; // aka areaScreenSubstrate._maxHeight
areaScreenSubstrate.method_8();
}
____searchInputField.ActivateInputField();
____searchInputField.Select();
}
}
// method_9 gets the sorted list of products. If there's a search term, prioritize the matching items so they load first
public class FastHideoutSearchPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(ProductionPanel), nameof(ProductionPanel.method_9));
}
// Copied directly from method_9
[PatchPrefix]
public static bool Prefix(ProductionPanel __instance, ref IEnumerable<Scheme> __result, ValidationInputField ____searchInputField)
{
__result = __instance.R().ProductionBuilds.OfType<Scheme>().Where(scheme => !scheme.locked)
.OrderBy(scheme => scheme.endProduct.LocalizedName().Contains(____searchInputField.text) ? 0 : 1) // search-matching items first
.ThenBy(__instance.method_18)
.ThenBy(scheme => scheme.FavoriteIndex)
.ThenBy(scheme => scheme.Level);
return false;
}
}
// method_14 activates/deactivates the product game objects based on the search. Need to resort the list due to above patch
public class FixHideoutSearchAgainPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(ProductionPanel), nameof(ProductionPanel.method_14));
}
[PatchPrefix]
public static void Prefix(ProductionPanel __instance)
{
__instance.method_13(); // update sort order
}
}
// Save the search as the window closes
public class SaveHideoutSearchPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(ProductionPanel), nameof(ProductionPanel.Close));
}
[PatchPrefix]
public static void Prefix(ProductionPanel __instance, ValidationInputField ____searchInputField)
{
LastSearches[__instance.AreaData.ToString()] = ____searchInputField.text;
ScrollRect scrollRect = __instance.GetComponentInParent<ScrollRect>();
ScrollRect scrollRect = view.GetComponentInParent<ScrollRect>();
if (scrollRect != null)
{
if (Settings.RestoreAsyncScrollPositions.Value)
LayoutRebuilder.ForceRebuildLayoutImmediate(scrollRect.RectTransform());
float currentAbsoluteDownScrollPosition = (1f - scrollRect.verticalNormalizedPosition) * (scrollRect.content.rect.height - scrollRect.viewport.rect.height);
if (LastAbsoluteDownScrollPosition > currentAbsoluteDownScrollPosition + 112f) // 112 is about the height of each item
{
// Need to save the absolute DOWN position, because that's the direction the scrollbox will grow.
// Subtract the viewport height from content heigh because that's the actual RANGE of the scroll position
LastAbsoluteDownScrollPosition = (1f - scrollRect.verticalNormalizedPosition) * (scrollRect.content.rect.height - scrollRect.viewport.rect.height);
scrollRect.verticalNormalizedPosition = 0f;
}
else
{
// Last one, try to set it exactly
scrollRect.verticalNormalizedPosition = 1f - (LastAbsoluteDownScrollPosition / (scrollRect.content.rect.height - scrollRect.viewport.rect.height));
ClearLastScrollPosition();
}
scrollRect.GetComponent<ScrollPatches.KeyScrollListener>()?.OnKeyScroll.RemoveListener(ClearLastScrollPosition);
}
// Reset the default behavior
AreaScreenSubstrate areaScreenSubstrate = __instance.GetComponentInParent<AreaScreenSubstrate>();
LayoutElement layoutElement = areaScreenSubstrate.R().ContentLayout;
layoutElement.minHeight = -1f;
}
}
// Clear the search stuff when you exit out
public class CloseHideoutSearchPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(HideoutScreenOverlay), nameof(HideoutScreenOverlay.ReturnToPreviousState));
}
[PatchPostfix]
public static void Postfix()
{
LastSearches.Clear();
ClearLastScrollPosition();
}
}
public class CancelScrollOnMouseWheelPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(ScrollRectNoDrag), nameof(ScrollRectNoDrag.OnScroll));
}
[PatchPostfix]
public static void Postfix()
{
ClearLastScrollPosition();
}
}
public class BlockHideoutEnterPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(HideoutScreenOverlay), nameof(HideoutScreenOverlay.TranslateCommand));
}
[PatchPrefix]
public static bool Prefix(ECommand command, ref InputNode.ETranslateResult __result)
{
if (command == ECommand.Enter &&
EventSystem.current?.currentSelectedGameObject != null &&
EventSystem.current.currentSelectedGameObject.GetComponent<TMP_InputField>() != null)
{
__result = InputNode.ETranslateResult.Block;
return false;
}
return true;
}
}
}
// Populate the search box, and force the window to render
public class RestoreHideoutSearchPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(ProductionPanel), nameof(ProductionPanel.ShowContents));
}
[PatchPrefix]
public static void Prefix(ProductionPanel __instance, ValidationInputField ____searchInputField)
{
if (LastSearches.TryGetValue(__instance.AreaData.ToString(), out string lastSearch))
{
____searchInputField.text = lastSearch;
}
ScrollPatches.KeyScrollListener listener = __instance.GetComponentInParent<ScrollPatches.KeyScrollListener>();
listener?.OnKeyScroll.AddListener(ClearLastScrollPosition);
}
[PatchPostfix]
public static void Postfix(ProductionPanel __instance, ValidationInputField ____searchInputField)
{
// Force it to render immediately, at full height, even if the search filtering would reduce the number of children
if (__instance.method_9().Count() > 2)
{
AreaScreenSubstrate areaScreenSubstrate = __instance.GetComponentInParent<AreaScreenSubstrate>();
LayoutElement layoutElement = areaScreenSubstrate.R().ContentLayout;
layoutElement.minHeight = 750f; // aka areaScreenSubstrate._maxHeight
areaScreenSubstrate.method_8();
}
____searchInputField.ActivateInputField();
____searchInputField.Select();
}
}
// method_9 gets the sorted list of products. If there's a search term, prioritize the matching items so they load first
public class FastHideoutSearchPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(ProductionPanel), nameof(ProductionPanel.method_9));
}
// Copied directly from method_9
[PatchPrefix]
public static bool Prefix(ProductionPanel __instance, ref IEnumerable<Scheme> __result, ValidationInputField ____searchInputField)
{
__result = __instance.R().ProductionBuilds.OfType<Scheme>().Where(scheme => !scheme.locked)
.OrderBy(scheme => scheme.endProduct.LocalizedName().Contains(____searchInputField.text) ? 0 : 1) // search-matching items first
.ThenBy(__instance.method_18)
.ThenBy(scheme => scheme.FavoriteIndex)
.ThenBy(scheme => scheme.Level);
return false;
}
}
// method_14 activates/deactivates the product game objects based on the search. Need to resort the list due to above patch
public class FixHideoutSearchAgainPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(ProductionPanel), nameof(ProductionPanel.method_14));
}
[PatchPrefix]
public static void Prefix(ProductionPanel __instance)
{
__instance.method_13(); // update sort order
}
}
// Save the search as the window closes
public class SaveHideoutSearchPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(ProductionPanel), nameof(ProductionPanel.Close));
}
[PatchPrefix]
public static void Prefix(ProductionPanel __instance, ValidationInputField ____searchInputField)
{
LastSearches[__instance.AreaData.ToString()] = ____searchInputField.text;
ScrollRect scrollRect = __instance.GetComponentInParent<ScrollRect>();
if (scrollRect != null)
{
if (Settings.RestoreAsyncScrollPositions.Value)
{
// Need to save the absolute DOWN position, because that's the direction the scrollbox will grow.
// Subtract the viewport height from content heigh because that's the actual RANGE of the scroll position
LastAbsoluteDownScrollPosition = (1f - scrollRect.verticalNormalizedPosition) * (scrollRect.content.rect.height - scrollRect.viewport.rect.height);
}
scrollRect.GetComponent<ScrollPatches.KeyScrollListener>()?.OnKeyScroll.RemoveListener(ClearLastScrollPosition);
}
// Reset the default behavior
AreaScreenSubstrate areaScreenSubstrate = __instance.GetComponentInParent<AreaScreenSubstrate>();
LayoutElement layoutElement = areaScreenSubstrate.R().ContentLayout;
layoutElement.minHeight = -1f;
}
}
// Clear the search stuff when you exit out
public class CloseHideoutSearchPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(HideoutScreenOverlay), nameof(HideoutScreenOverlay.ReturnToPreviousState));
}
[PatchPostfix]
public static void Postfix()
{
LastSearches.Clear();
ClearLastScrollPosition();
}
}
public class CancelScrollOnMouseWheelPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(ScrollRectNoDrag), nameof(ScrollRectNoDrag.OnScroll));
}
[PatchPostfix]
public static void Postfix()
{
ClearLastScrollPosition();
}
}
public class BlockHideoutEnterPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(HideoutScreenOverlay), nameof(HideoutScreenOverlay.TranslateCommand));
}
[PatchPrefix]
public static bool Prefix(ECommand command, ref InputNode.ETranslateResult __result)
{
if (command == ECommand.Enter &&
EventSystem.current?.currentSelectedGameObject != null &&
EventSystem.current.currentSelectedGameObject.GetComponent<TMP_InputField>() != null)
{
__result = InputNode.ETranslateResult.Block;
return false;
}
return true;
}
}
}

View File

@@ -9,273 +9,271 @@ using System.Reflection;
using UnityEngine;
using UnityEngine.UI;
namespace UIFixes
namespace UIFixes;
internal static class InspectWindowResizePatches
{
internal static class InspectWindowResizePatches
private static float SavedPreferredWidth = -1f;
private static float SavedPreferredHeight = -1f;
// Seems like this is the always the default for ItemSpecificationPanels
private const float DefaultPreferredWidth = 670f;
private const float DefaultPreferredHeight = 500f;
private const float ButtonPadding = 3f;
private static Image ButtonBackground; // Nice gray background for the new buttons
public static void Enable()
{
private static float SavedPreferredWidth = -1f;
private static float SavedPreferredHeight = -1f;
new SaveInspectWindowSizePatch().Enable();
new AddInspectWindowButtonsPatch().Enable();
new GrowInspectWindowDescriptionPatch().Enable();
new LeftRightKeybindsPatch().Enable();
}
// Seems like this is the always the default for ItemSpecificationPanels
private const float DefaultPreferredWidth = 670f;
private const float DefaultPreferredHeight = 500f;
private const float ButtonPadding = 3f;
private static Image ButtonBackground; // Nice gray background for the new buttons
public static void Enable()
public class SaveInspectWindowSizePatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
new SaveInspectWindowSizePatch().Enable();
new AddInspectWindowButtonsPatch().Enable();
new GrowInspectWindowDescriptionPatch().Enable();
new LeftRightKeybindsPatch().Enable();
return AccessTools.Method(typeof(StretchArea), nameof(StretchArea.OnDrag));
}
public class SaveInspectWindowSizePatch : ModulePatch
[PatchPostfix]
public static void Postfix(LayoutElement ___layoutElement_0)
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(StretchArea), nameof(StretchArea.OnDrag));
}
[PatchPostfix]
public static void Postfix(LayoutElement ___layoutElement_0)
{
if (!Settings.RememberInspectSize.Value || ___layoutElement_0.GetComponent<ItemSpecificationPanel>() == null)
{
return;
}
SavedPreferredWidth = ___layoutElement_0.preferredWidth;
SavedPreferredHeight = ___layoutElement_0.preferredHeight;
Button resizeButton = ___layoutElement_0.transform.Find("Inner/Caption Panel/Restore")?.GetComponent<Button>();
if (resizeButton != null && !resizeButton.IsActive())
{
resizeButton.gameObject.SetActive(true);
}
}
}
public class AddInspectWindowButtonsPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(ItemSpecificationPanel), nameof(ItemSpecificationPanel.Show));
}
[PatchPrefix]
public static void Prefix(LayoutElement ___layoutElement_0)
{
if (Settings.RememberInspectSize.Value)
{
RestoreSavedSize(___layoutElement_0);
}
}
[PatchPostfix]
public static void Postfix(ItemSpecificationPanel __instance, LayoutElement ___layoutElement_0, ItemUiContext ___itemUiContext_0)
{
if (Settings.LockInspectPreviewSize.Value)
{
LayoutElement previewPanel = __instance.transform.Find("Inner/Contents/Preview Panel")?.GetComponent<LayoutElement>();
if (previewPanel != null)
{
previewPanel.flexibleHeight = -1;
}
}
if (ButtonBackground == null)
{
// Steal the background image fom gridwindow sort
ButtonBackground = ___itemUiContext_0.R().GridWindowTemplate.R().GridSortPanel.R().Button.image;
}
Button closeButton = __instance.transform.Find("Inner/Caption Panel/Close Button")?.GetComponent<Button>();
if (closeButton != null)
{
CreateRightButton(__instance, closeButton);
CreateLeftButton(__instance, closeButton);
CreateRestoreButton(__instance, ___layoutElement_0, closeButton);
}
}
private static void CreateRestoreButton(ItemSpecificationPanel inspectPanel, LayoutElement inspectLayout, Button template)
{
RectTransform templateRect = (RectTransform)template.transform;
Button restoreButton = UnityEngine.Object.Instantiate(template, template.transform.parent, false);
restoreButton.name = "Restore";
restoreButton.navigation = new Navigation() { mode = Navigation.Mode.None };
RectTransform restoreRect = (RectTransform)restoreButton.transform;
restoreRect.localPosition = new Vector3(templateRect.localPosition.x - 3 * (templateRect.rect.width + ButtonPadding), templateRect.localPosition.y, templateRect.localPosition.z);
Image background = restoreButton.GetComponent<Image>();
background.sprite = ButtonBackground.sprite;
Image restoreImage = restoreButton.transform.Find("X").GetComponent<Image>();
restoreImage.sprite = EFTHardSettings.Instance.StaticIcons.GetAttributeIcon(EItemAttributeId.EffectiveDist);
restoreImage.overrideSprite = null;
restoreImage.SetNativeSize();
restoreImage.transform.localScale = new Vector3(restoreImage.transform.localScale.x * 0.8f, restoreImage.transform.localScale.y * 0.8f, restoreImage.transform.localScale.z);
Image restoreImage2 = UnityEngine.Object.Instantiate(restoreImage, restoreImage.transform.parent, false);
restoreImage2.transform.Rotate(0f, 0f, 180f);
Vector3 startPosition = restoreImage2.transform.localPosition;
restoreImage.transform.localPosition = new Vector3(startPosition.x - 3f, startPosition.y - 3f, startPosition.z);
restoreImage2.transform.localPosition = new Vector3(startPosition.x + 2.5f, startPosition.y + 2f, startPosition.z);
if (SavedPreferredWidth < 0 && SavedPreferredHeight < 0)
{
restoreButton.gameObject.SetActive(false);
}
restoreButton.onClick.AddListener(() =>
{
SavedPreferredWidth = -1f;
SavedPreferredHeight = -1f;
RestoreSavedSize(inspectLayout);
restoreButton.gameObject.SetActive(false);
// I'm really not sure why this is necessary, but something in the layout gets borked trying to just restore the default size
// This recreates a lot of the children, but it works
inspectPanel.method_1();
StretchDescription(inspectLayout);
});
inspectPanel.AddDisposable(() => restoreButton.onClick.RemoveAllListeners());
}
private static void CreateLeftButton(ItemSpecificationPanel inspectPanel, Button template)
{
RectTransform templateRect = (RectTransform)template.transform;
Button leftButton = UnityEngine.Object.Instantiate(template, template.transform.parent, false);
leftButton.navigation = new Navigation() { mode = Navigation.Mode.None };
RectTransform leftRect = (RectTransform)leftButton.transform;
leftRect.localPosition = new Vector3(templateRect.localPosition.x - 2 * (templateRect.rect.width + ButtonPadding), templateRect.localPosition.y, templateRect.localPosition.z);
Image background = leftButton.GetComponent<Image>();
background.sprite = ButtonBackground.sprite;
Image leftImage = leftButton.transform.Find("X").GetComponent<Image>();
leftImage.sprite = EFTHardSettings.Instance.StaticIcons.GetAttributeIcon(EItemAttributeId.RecoilBack);
leftImage.overrideSprite = null;
leftImage.SetNativeSize();
leftButton.onClick.AddListener(() => SnapLeft(inspectPanel));
inspectPanel.AddDisposable(() => leftButton.onClick.RemoveAllListeners());
}
private static void CreateRightButton(ItemSpecificationPanel inspectPanel, Button template)
{
RectTransform templateRect = (RectTransform)template.transform;
Button rightButton = UnityEngine.Object.Instantiate(template, template.transform.parent, false);
rightButton.navigation = new Navigation() { mode = Navigation.Mode.None };
RectTransform rightRect = (RectTransform)rightButton.transform;
rightRect.localPosition = new Vector3(templateRect.localPosition.x - (templateRect.rect.width + ButtonPadding), templateRect.localPosition.y, templateRect.localPosition.z);
Image background = rightButton.GetComponent<Image>();
background.sprite = ButtonBackground.sprite;
Image rightImage = rightButton.transform.Find("X").GetComponent<Image>();
rightImage.sprite = EFTHardSettings.Instance.StaticIcons.GetAttributeIcon(EItemAttributeId.RecoilBack);
rightImage.transform.Rotate(0f, 180f, 0f);
rightImage.overrideSprite = null;
rightImage.SetNativeSize();
rightButton.onClick.AddListener(() => SnapRight(inspectPanel));
inspectPanel.AddDisposable(() => rightButton.onClick.RemoveAllListeners());
}
// Informed by StretchArea.OnDrag
private static void RestoreSavedSize(LayoutElement layout)
{
RectTransform layoutRect = (RectTransform)layout.transform;
layout.preferredWidth = SavedPreferredWidth > 0 ? SavedPreferredWidth : DefaultPreferredWidth;
layout.preferredHeight = SavedPreferredHeight > 0 ? SavedPreferredHeight : DefaultPreferredHeight;
LayoutRebuilder.ForceRebuildLayoutImmediate(layoutRect);
layoutRect.CorrectPositionResolution(default);
}
}
private static void SnapLeft(ItemSpecificationPanel panel)
{
RectTransform inspectRect = (RectTransform)panel.transform;
inspectRect.anchoredPosition = new Vector2((float)Screen.width / 4f / inspectRect.lossyScale.x, inspectRect.anchoredPosition.y);
}
private static void SnapRight(ItemSpecificationPanel panel)
{
RectTransform inspectRect = (RectTransform)panel.transform;
inspectRect.anchoredPosition = new Vector2((float)Screen.width * 3f / 4f / inspectRect.lossyScale.x, inspectRect.anchoredPosition.y);
}
private static void StretchDescription(LayoutElement inspectLayout)
{
if (!Settings.ExpandDescriptionHeight.Value)
if (!Settings.RememberInspectSize.Value || ___layoutElement_0.GetComponent<ItemSpecificationPanel>() == null)
{
return;
}
LayoutElement scrollArea = inspectLayout.transform.Find("Inner/Contents/DescriptionPanel/DescriptionPanel/Scroll Area").GetComponent<LayoutElement>();
if (inspectLayout != null && scrollArea != null && scrollArea.transform.childCount > 0)
{
RectTransform description = (RectTransform)scrollArea.transform.GetChild(0);
SavedPreferredWidth = ___layoutElement_0.preferredWidth;
SavedPreferredHeight = ___layoutElement_0.preferredHeight;
// Try to figure out how much extra I can work with
float maxGrowth = (Screen.height / inspectLayout.transform.lossyScale.y) - ((RectTransform)inspectLayout.transform).rect.height;
scrollArea.minHeight = Mathf.Max(scrollArea.minHeight, Mathf.Min(maxGrowth, description.rect.height));
Button resizeButton = ___layoutElement_0.transform.Find("Inner/Caption Panel/Restore")?.GetComponent<Button>();
if (resizeButton != null && !resizeButton.IsActive())
{
resizeButton.gameObject.SetActive(true);
}
}
}
public class AddInspectWindowButtonsPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(ItemSpecificationPanel), nameof(ItemSpecificationPanel.Show));
}
[PatchPrefix]
public static void Prefix(LayoutElement ___layoutElement_0)
{
if (Settings.RememberInspectSize.Value)
{
RestoreSavedSize(___layoutElement_0);
}
}
public class GrowInspectWindowDescriptionPatch : ModulePatch
[PatchPostfix]
public static void Postfix(ItemSpecificationPanel __instance, LayoutElement ___layoutElement_0, ItemUiContext ___itemUiContext_0)
{
protected override MethodBase GetTargetMethod()
if (Settings.LockInspectPreviewSize.Value)
{
return AccessTools.Method(typeof(ItemUiContext), nameof(ItemUiContext.Inspect));
LayoutElement previewPanel = __instance.transform.Find("Inner/Contents/Preview Panel")?.GetComponent<LayoutElement>();
if (previewPanel != null)
{
previewPanel.flexibleHeight = -1;
}
}
[PatchPostfix]
public static void Postfix(List<InputNode> ____children)
if (ButtonBackground == null)
{
var inspectWindow = ____children.Last();
if (inspectWindow != null)
{
StretchDescription(inspectWindow.GetComponent<LayoutElement>());
}
// Steal the background image fom gridwindow sort
ButtonBackground = ___itemUiContext_0.R().GridWindowTemplate.R().GridSortPanel.R().Button.image;
}
Button closeButton = __instance.transform.Find("Inner/Caption Panel/Close Button")?.GetComponent<Button>();
if (closeButton != null)
{
CreateRightButton(__instance, closeButton);
CreateLeftButton(__instance, closeButton);
CreateRestoreButton(__instance, ___layoutElement_0, closeButton);
}
}
public class LeftRightKeybindsPatch : ModulePatch
private static void CreateRestoreButton(ItemSpecificationPanel inspectPanel, LayoutElement inspectLayout, Button template)
{
protected override MethodBase GetTargetMethod()
RectTransform templateRect = (RectTransform)template.transform;
Button restoreButton = UnityEngine.Object.Instantiate(template, template.transform.parent, false);
restoreButton.name = "Restore";
restoreButton.navigation = new Navigation() { mode = Navigation.Mode.None };
RectTransform restoreRect = (RectTransform)restoreButton.transform;
restoreRect.localPosition = new Vector3(templateRect.localPosition.x - 3 * (templateRect.rect.width + ButtonPadding), templateRect.localPosition.y, templateRect.localPosition.z);
Image background = restoreButton.GetComponent<Image>();
background.sprite = ButtonBackground.sprite;
Image restoreImage = restoreButton.transform.Find("X").GetComponent<Image>();
restoreImage.sprite = EFTHardSettings.Instance.StaticIcons.GetAttributeIcon(EItemAttributeId.EffectiveDist);
restoreImage.overrideSprite = null;
restoreImage.SetNativeSize();
restoreImage.transform.localScale = new Vector3(restoreImage.transform.localScale.x * 0.8f, restoreImage.transform.localScale.y * 0.8f, restoreImage.transform.localScale.z);
Image restoreImage2 = UnityEngine.Object.Instantiate(restoreImage, restoreImage.transform.parent, false);
restoreImage2.transform.Rotate(0f, 0f, 180f);
Vector3 startPosition = restoreImage2.transform.localPosition;
restoreImage.transform.localPosition = new Vector3(startPosition.x - 3f, startPosition.y - 3f, startPosition.z);
restoreImage2.transform.localPosition = new Vector3(startPosition.x + 2.5f, startPosition.y + 2f, startPosition.z);
if (SavedPreferredWidth < 0 && SavedPreferredHeight < 0)
{
return AccessTools.Method(typeof(ItemSpecificationPanel), nameof(ItemSpecificationPanel.Update));
restoreButton.gameObject.SetActive(false);
}
[PatchPostfix]
public static void Postfix(ItemSpecificationPanel __instance)
restoreButton.onClick.AddListener(() =>
{
bool isTopWindow = __instance.transform.GetSiblingIndex() == __instance.transform.parent.childCount - 1;
if (Settings.SnapLeftKeybind.Value.IsDown() && isTopWindow)
{
SnapLeft(__instance);
}
SavedPreferredWidth = -1f;
SavedPreferredHeight = -1f;
RestoreSavedSize(inspectLayout);
restoreButton.gameObject.SetActive(false);
if (Settings.SnapRightKeybind.Value.IsDown() && isTopWindow)
{
SnapRight(__instance);
}
// I'm really not sure why this is necessary, but something in the layout gets borked trying to just restore the default size
// This recreates a lot of the children, but it works
inspectPanel.method_1();
StretchDescription(inspectLayout);
});
inspectPanel.AddDisposable(() => restoreButton.onClick.RemoveAllListeners());
}
private static void CreateLeftButton(ItemSpecificationPanel inspectPanel, Button template)
{
RectTransform templateRect = (RectTransform)template.transform;
Button leftButton = UnityEngine.Object.Instantiate(template, template.transform.parent, false);
leftButton.navigation = new Navigation() { mode = Navigation.Mode.None };
RectTransform leftRect = (RectTransform)leftButton.transform;
leftRect.localPosition = new Vector3(templateRect.localPosition.x - 2 * (templateRect.rect.width + ButtonPadding), templateRect.localPosition.y, templateRect.localPosition.z);
Image background = leftButton.GetComponent<Image>();
background.sprite = ButtonBackground.sprite;
Image leftImage = leftButton.transform.Find("X").GetComponent<Image>();
leftImage.sprite = EFTHardSettings.Instance.StaticIcons.GetAttributeIcon(EItemAttributeId.RecoilBack);
leftImage.overrideSprite = null;
leftImage.SetNativeSize();
leftButton.onClick.AddListener(() => SnapLeft(inspectPanel));
inspectPanel.AddDisposable(() => leftButton.onClick.RemoveAllListeners());
}
private static void CreateRightButton(ItemSpecificationPanel inspectPanel, Button template)
{
RectTransform templateRect = (RectTransform)template.transform;
Button rightButton = UnityEngine.Object.Instantiate(template, template.transform.parent, false);
rightButton.navigation = new Navigation() { mode = Navigation.Mode.None };
RectTransform rightRect = (RectTransform)rightButton.transform;
rightRect.localPosition = new Vector3(templateRect.localPosition.x - (templateRect.rect.width + ButtonPadding), templateRect.localPosition.y, templateRect.localPosition.z);
Image background = rightButton.GetComponent<Image>();
background.sprite = ButtonBackground.sprite;
Image rightImage = rightButton.transform.Find("X").GetComponent<Image>();
rightImage.sprite = EFTHardSettings.Instance.StaticIcons.GetAttributeIcon(EItemAttributeId.RecoilBack);
rightImage.transform.Rotate(0f, 180f, 0f);
rightImage.overrideSprite = null;
rightImage.SetNativeSize();
rightButton.onClick.AddListener(() => SnapRight(inspectPanel));
inspectPanel.AddDisposable(() => rightButton.onClick.RemoveAllListeners());
}
// Informed by StretchArea.OnDrag
private static void RestoreSavedSize(LayoutElement layout)
{
RectTransform layoutRect = (RectTransform)layout.transform;
layout.preferredWidth = SavedPreferredWidth > 0 ? SavedPreferredWidth : DefaultPreferredWidth;
layout.preferredHeight = SavedPreferredHeight > 0 ? SavedPreferredHeight : DefaultPreferredHeight;
LayoutRebuilder.ForceRebuildLayoutImmediate(layoutRect);
layoutRect.CorrectPositionResolution(default);
}
}
private static void SnapLeft(ItemSpecificationPanel panel)
{
RectTransform inspectRect = (RectTransform)panel.transform;
inspectRect.anchoredPosition = new Vector2((float)Screen.width / 4f / inspectRect.lossyScale.x, inspectRect.anchoredPosition.y);
}
private static void SnapRight(ItemSpecificationPanel panel)
{
RectTransform inspectRect = (RectTransform)panel.transform;
inspectRect.anchoredPosition = new Vector2((float)Screen.width * 3f / 4f / inspectRect.lossyScale.x, inspectRect.anchoredPosition.y);
}
private static void StretchDescription(LayoutElement inspectLayout)
{
if (!Settings.ExpandDescriptionHeight.Value)
{
return;
}
LayoutElement scrollArea = inspectLayout.transform.Find("Inner/Contents/DescriptionPanel/DescriptionPanel/Scroll Area").GetComponent<LayoutElement>();
if (inspectLayout != null && scrollArea != null && scrollArea.transform.childCount > 0)
{
RectTransform description = (RectTransform)scrollArea.transform.GetChild(0);
// Try to figure out how much extra I can work with
float maxGrowth = (Screen.height / inspectLayout.transform.lossyScale.y) - ((RectTransform)inspectLayout.transform).rect.height;
scrollArea.minHeight = Mathf.Max(scrollArea.minHeight, Mathf.Min(maxGrowth, description.rect.height));
}
}
public class GrowInspectWindowDescriptionPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(ItemUiContext), nameof(ItemUiContext.Inspect));
}
[PatchPostfix]
public static void Postfix(List<InputNode> ____children)
{
var inspectWindow = ____children.Last();
if (inspectWindow != null)
{
StretchDescription(inspectWindow.GetComponent<LayoutElement>());
}
}
}
public class LeftRightKeybindsPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(ItemSpecificationPanel), nameof(ItemSpecificationPanel.Update));
}
[PatchPostfix]
public static void Postfix(ItemSpecificationPanel __instance)
{
bool isTopWindow = __instance.transform.GetSiblingIndex() == __instance.transform.parent.childCount - 1;
if (Settings.SnapLeftKeybind.Value.IsDown() && isTopWindow)
{
SnapLeft(__instance);
}
if (Settings.SnapRightKeybind.Value.IsDown() && isTopWindow)
{
SnapRight(__instance);
}
}
}

View File

@@ -13,508 +13,507 @@ using TMPro;
using UnityEngine;
using UnityEngine.UI;
namespace UIFixes
namespace UIFixes;
public static class InspectWindowStatsPatches
{
public static class InspectWindowStatsPatches
public static void Enable()
{
public static void Enable()
new AddShowHideModStatsButtonPatch().Enable();
new CalculateModStatsPatch().Enable();
new CompareModStatsPatch().Enable();
new FormatCompactValuesPatch().Enable();
new FormatFullValuesPatch().Enable();
new FixDurabilityBarPatch().Enable();
}
public class CalculateModStatsPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
new AddShowHideModStatsButtonPatch().Enable();
new CalculateModStatsPatch().Enable();
new CompareModStatsPatch().Enable();
new FormatCompactValuesPatch().Enable();
new FormatFullValuesPatch().Enable();
new FixDurabilityBarPatch().Enable();
return AccessTools.Method(typeof(ItemSpecificationPanel), nameof(ItemSpecificationPanel.method_5));
}
public class CalculateModStatsPatch : ModulePatch
[PatchPostfix]
public static void Postfix(
ItemSpecificationPanel __instance,
Item ___item_0,
CompactCharacteristicPanel ____compactCharTemplate,
Transform ____compactPanel,
SimpleTooltip ___simpleTooltip_0)
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(ItemSpecificationPanel), nameof(ItemSpecificationPanel.method_5));
}
var instance = __instance.R();
[PatchPostfix]
public static void Postfix(
ItemSpecificationPanel __instance,
Item ___item_0,
CompactCharacteristicPanel ____compactCharTemplate,
Transform ____compactPanel,
SimpleTooltip ___simpleTooltip_0)
{
var instance = __instance.R();
if (!Settings.ShowModStats.Value || ___item_0 is not Mod)
{
return;
}
var deepAttributes = GetDeepAttributes(___item_0, out bool changed);
if (!changed)
{
return;
}
// Clean up existing one
if (instance.CompactCharacteristicPanels is IDisposable compactPanels)
{
compactPanels.Dispose();
}
var newCompactPanels = R.ItemSpecificationPanel.CreateCompactCharacteristicPanels(
deepAttributes,
____compactCharTemplate,
____compactPanel,
(attribute, viewer) => viewer.Show(attribute, ___simpleTooltip_0, __instance.Boolean_0, 100));
instance.CompactCharacteristicPanels = newCompactPanels;
if (newCompactPanels.Any())
{
newCompactPanels.Last().Value.OnTextWidthCalculated += __instance.method_3;
int siblingIndex = newCompactPanels.Last().Value.Transform.GetSiblingIndex();
foreach (var item in instance.CompactCharacteristicDropdowns)
{
item.Value.Transform.SetSiblingIndex(++siblingIndex);
}
}
__instance.method_10(0f);
__instance.method_6(null);
}
}
// The fundamental thing about mods is that unlike weapons, armor, etc, they do not change their own attributes when they "accept" inner mods.
// I guess weapons figure out their stats by deeply iterating all mods, rather than just their direct mods
// As a result, the compare method that works with weapons/armor doesn't work with mods. Normally, it "adds" the mod, clones the result, then reverts the "add". Hence
// the compareItem is the item with the mods. But again, as mods don't change their values, we see no change.
// I wish I could prefix method_6 and update the compare item with the deep attributes, but that only works when adding a mod
// When removing, current item and compare item end up the same since current item never considers the mod anyway
// So I have to forcably call the refresh values method
public class CompareModStatsPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(ItemSpecificationPanel), nameof(ItemSpecificationPanel.method_6));
}
[PatchPrefix]
public static void Prefix(Item compareItem)
{
if (compareItem == null)
{
return;
}
// Armor points is added in method_5, but not in other places so it's missed by compare
ArmorComponent[] armorComponents = compareItem.GetItemComponentsInChildren<ArmorComponent>(true).Where(c => c.ArmorClass > 0).ToArray<ArmorComponent>();
if (armorComponents.Any())
{
float maxDurability = armorComponents.Sum(c => c.Repairable.Durability);
var itemAttributeClass = new ItemAttributeClass(EItemAttributeId.ArmorPoints)
{
Name = EItemAttributeId.ArmorPoints.GetName(),
Base = () => maxDurability,
StringValue = () => Math.Round(maxDurability, 1).ToString(CultureInfo.InvariantCulture),
DisplayType = () => EItemAttributeDisplayType.Compact
};
compareItem.Attributes.Insert(0, itemAttributeClass);
}
}
[PatchPostfix]
public static void Postfix(ItemSpecificationPanel __instance, Item compareItem)
{
if (!Settings.ShowModStats.Value || compareItem is not Mod)
{
return;
}
List<ItemAttributeClass> deepAttributes = GetDeepAttributes(compareItem, out bool changed);
if (!changed)
{
return;
}
var compactPanels = __instance.R().CompactCharacteristicPanels;
R.ItemSpecificationPanel.Refresh(compactPanels, deepAttributes);
}
}
public class AddShowHideModStatsButtonPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(ItemSpecificationPanel), nameof(ItemSpecificationPanel.method_4));
}
private static string GetLabel()
{
return Settings.ShowModStats.Value ? "HIDE MOD STATS" : "SHOW MOD STATS";
}
[PatchPostfix]
public static void Postfix(ItemSpecificationPanel __instance, ItemInfoInteractionsAbstractClass<EItemInfoButton> contextInteractions, Item ___item_0, InteractionButtonsContainer ____interactionButtonsContainer)
{
if (___item_0 is not Mod)
{
return;
}
var buttonsContainer = ____interactionButtonsContainer.R();
ContextMenuButton toggleButton = null;
// Listen to the setting and the work there to handle multiple windows open at once
void onSettingChanged(object sender, EventArgs args)
{
var text = toggleButton.R().Text;
text.text = GetLabel();
__instance.method_5(); // rebuild stat panels
}
Settings.ShowModStats.SettingChanged += onSettingChanged;
static void onClick()
{
Settings.ShowModStats.Value = !Settings.ShowModStats.Value;
}
void createButton()
{
Sprite sprite = CacheResourcesPopAbstractClass.Pop<Sprite>("Characteristics/Icons/Modding");
toggleButton = (ContextMenuButton)UnityEngine.Object.Instantiate(buttonsContainer.ButtonTemplate, buttonsContainer.Container, false);
toggleButton.Show(GetLabel(), null, sprite, onClick, null);
____interactionButtonsContainer.method_5(toggleButton); // add to disposable list
}
// Subscribe to redraws to recreate when mods get dropped in
contextInteractions.OnRedrawRequired += createButton;
// And unsubscribe when the window goes away
buttonsContainer.UI.AddDisposable(() =>
{
contextInteractions.OnRedrawRequired -= createButton;
Settings.ShowModStats.SettingChanged -= onSettingChanged;
});
createButton();
}
}
public class FormatCompactValuesPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(CompactCharacteristicPanel), nameof(CompactCharacteristicPanel.SetValues));
}
[PatchPostfix]
public static void Postfix(CompactCharacteristicPanel __instance, TextMeshProUGUI ___ValueText)
{
try
{
FormatText(__instance, ___ValueText);
}
catch (Exception ex)
{
Logger.LogError(ex);
}
}
}
public class FormatFullValuesPatch : ModulePatch
{
private static MethodInfo RoundToIntMethod;
private static MethodInfo ToStringMethod;
protected override MethodBase GetTargetMethod()
{
RoundToIntMethod = AccessTools.Method(typeof(Mathf), nameof(Mathf.RoundToInt));
ToStringMethod = AccessTools.Method(typeof(float), nameof(float.ToString), [typeof(string)]);
return AccessTools.Method(typeof(CharacteristicPanel), nameof(CharacteristicPanel.SetValues));
}
[PatchPostfix]
public static void Postfix(CharacteristicPanel __instance, TextMeshProUGUI ___ValueText)
{
try
{
FormatText(__instance, ___ValueText, true);
}
catch (Exception ex)
{
Logger.LogError(ex);
}
}
// This transpiler looks for where it rounds a float to an int, and skips that. Instead it calls ToString("0.0#") on it
[PatchTranspiler]
public static IEnumerable<CodeInstruction> Transpile(IEnumerable<CodeInstruction> instructions)
{
int skip = 0;
CodeInstruction lastInstruction = null;
CodeInstruction currentInstruction = null;
foreach (var instruction in instructions)
{
if (lastInstruction == null)
{
lastInstruction = instruction;
continue;
}
currentInstruction = instruction;
if (skip > 0)
{
--skip;
}
else if (currentInstruction.Calls(RoundToIntMethod))
{
yield return new CodeInstruction(OpCodes.Ldloca_S, 17);
yield return new CodeInstruction(OpCodes.Ldstr, "0.0#");
yield return new CodeInstruction(OpCodes.Call, ToStringMethod);
skip = 4;
}
else
{
yield return lastInstruction;
}
lastInstruction = instruction;
}
if (currentInstruction != null)
{
yield return currentInstruction;
}
}
}
public class FixDurabilityBarPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.DeclaredMethod(typeof(DurabilityPanel), nameof(DurabilityPanel.SetValues));
}
// Bar width is currently set to durability/100, and that 100 is pretty much hardcoded by the client
// Just clamp the bar to keep it from overflowing
[PatchPostfix]
public static void Postfix(Image ___Current)
{
___Current.rectTransform.anchorMax = new Vector2(
Mathf.Min(___Current.rectTransform.anchorMax.x, 1f),
___Current.rectTransform.anchorMax.y);
}
}
// These fields are percents, but have been manually multipied by 100 already
private static readonly EItemAttributeId[] NonPercentPercents = [EItemAttributeId.ChangeMovementSpeed, EItemAttributeId.ChangeTurningSpeed, EItemAttributeId.Ergonomics];
private static void FormatText(CompactCharacteristicPanel panel, TextMeshProUGUI textMesh, bool fullBar = false)
{
// Comparisons are shown as <value>(<changed>)
// <value> is from each attribute type's StringValue() function, so is formatted *mostly* ok
// <changed> is just naively formatted with ToString("F2"), so I have to figure out what it is and fix that
// This method is a gnarly pile of regex and replacements, blame BSG
if (!Settings.StyleItemPanel.Value)
if (!Settings.ShowModStats.Value || ___item_0 is not Mod)
{
return;
}
// These come from CompactCharacteristicPanel._increasingColor and _decreasingColor, which are hardcoded. Hardcoding here too because
// CharacteristicPanel doesn't define and you get clear
const string IncreasingColorHex = "#5EC1FF";
const string DecreasingColorHex = "#C40000";
string text = textMesh.text;
var wrappedPanel = panel.R();
ItemAttributeClass attribute = wrappedPanel.ItemAttribute;
// Holy shit did they mess up MOA. Half of the calculation is done in the StringValue() method, so calculating delta from Base() loses all that
// Plus, they round the difference to the nearest integer (!?)
// Completely redo it
if ((EItemAttributeId)attribute.Id == EItemAttributeId.CenterOfImpact)
var deepAttributes = GetDeepAttributes(___item_0, out bool changed);
if (!changed)
{
var compareAttribute = wrappedPanel.CompareItemAttribute;
if (compareAttribute != null)
{
string currentStringValue = attribute.StringValue();
var moaMatch = Regex.Match(currentStringValue, @"^(\S+)");
if (float.TryParse(moaMatch.Groups[1].Value, out float moa))
{
string compareStringValue = compareAttribute.StringValue();
moaMatch = Regex.Match(compareStringValue, @"^(\S+)");
if (float.TryParse(moaMatch.Groups[1].Value, out float compareMoa))
{
float delta = compareMoa - moa;
string final = currentStringValue;
if (Math.Abs(delta) > 0)
{
string sign = delta > 0 ? "+" : "";
string color = (attribute.LessIsGood && delta < 0) || (!attribute.LessIsGood && delta > 0) ? IncreasingColorHex : DecreasingColorHex;
final += " <color=" + color + ">(" + sign + delta.ToString("0.0#") + ")</color>";
}
return;
}
textMesh.text = final;
return;
// Clean up existing one
if (instance.CompactCharacteristicPanels is IDisposable compactPanels)
{
compactPanels.Dispose();
}
var newCompactPanels = R.ItemSpecificationPanel.CreateCompactCharacteristicPanels(
deepAttributes,
____compactCharTemplate,
____compactPanel,
(attribute, viewer) => viewer.Show(attribute, ___simpleTooltip_0, __instance.Boolean_0, 100));
instance.CompactCharacteristicPanels = newCompactPanels;
if (newCompactPanels.Any())
{
newCompactPanels.Last().Value.OnTextWidthCalculated += __instance.method_3;
int siblingIndex = newCompactPanels.Last().Value.Transform.GetSiblingIndex();
foreach (var item in instance.CompactCharacteristicDropdowns)
{
item.Value.Transform.SetSiblingIndex(++siblingIndex);
}
}
__instance.method_10(0f);
__instance.method_6(null);
}
}
// The fundamental thing about mods is that unlike weapons, armor, etc, they do not change their own attributes when they "accept" inner mods.
// I guess weapons figure out their stats by deeply iterating all mods, rather than just their direct mods
// As a result, the compare method that works with weapons/armor doesn't work with mods. Normally, it "adds" the mod, clones the result, then reverts the "add". Hence
// the compareItem is the item with the mods. But again, as mods don't change their values, we see no change.
// I wish I could prefix method_6 and update the compare item with the deep attributes, but that only works when adding a mod
// When removing, current item and compare item end up the same since current item never considers the mod anyway
// So I have to forcably call the refresh values method
public class CompareModStatsPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(ItemSpecificationPanel), nameof(ItemSpecificationPanel.method_6));
}
[PatchPrefix]
public static void Prefix(Item compareItem)
{
if (compareItem == null)
{
return;
}
// Armor points is added in method_5, but not in other places so it's missed by compare
ArmorComponent[] armorComponents = compareItem.GetItemComponentsInChildren<ArmorComponent>(true).Where(c => c.ArmorClass > 0).ToArray<ArmorComponent>();
if (armorComponents.Any())
{
float maxDurability = armorComponents.Sum(c => c.Repairable.Durability);
var itemAttributeClass = new ItemAttributeClass(EItemAttributeId.ArmorPoints)
{
Name = EItemAttributeId.ArmorPoints.GetName(),
Base = () => maxDurability,
StringValue = () => Math.Round(maxDurability, 1).ToString(CultureInfo.InvariantCulture),
DisplayType = () => EItemAttributeDisplayType.Compact
};
compareItem.Attributes.Insert(0, itemAttributeClass);
}
}
[PatchPostfix]
public static void Postfix(ItemSpecificationPanel __instance, Item compareItem)
{
if (!Settings.ShowModStats.Value || compareItem is not Mod)
{
return;
}
List<ItemAttributeClass> deepAttributes = GetDeepAttributes(compareItem, out bool changed);
if (!changed)
{
return;
}
var compactPanels = __instance.R().CompactCharacteristicPanels;
R.ItemSpecificationPanel.Refresh(compactPanels, deepAttributes);
}
}
public class AddShowHideModStatsButtonPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(ItemSpecificationPanel), nameof(ItemSpecificationPanel.method_4));
}
private static string GetLabel()
{
return Settings.ShowModStats.Value ? "HIDE MOD STATS" : "SHOW MOD STATS";
}
[PatchPostfix]
public static void Postfix(ItemSpecificationPanel __instance, ItemInfoInteractionsAbstractClass<EItemInfoButton> contextInteractions, Item ___item_0, InteractionButtonsContainer ____interactionButtonsContainer)
{
if (___item_0 is not Mod)
{
return;
}
var buttonsContainer = ____interactionButtonsContainer.R();
ContextMenuButton toggleButton = null;
// Listen to the setting and the work there to handle multiple windows open at once
void onSettingChanged(object sender, EventArgs args)
{
var text = toggleButton.R().Text;
text.text = GetLabel();
__instance.method_5(); // rebuild stat panels
}
Settings.ShowModStats.SettingChanged += onSettingChanged;
static void onClick()
{
Settings.ShowModStats.Value = !Settings.ShowModStats.Value;
}
void createButton()
{
Sprite sprite = CacheResourcesPopAbstractClass.Pop<Sprite>("Characteristics/Icons/Modding");
toggleButton = (ContextMenuButton)UnityEngine.Object.Instantiate(buttonsContainer.ButtonTemplate, buttonsContainer.Container, false);
toggleButton.Show(GetLabel(), null, sprite, onClick, null);
____interactionButtonsContainer.method_5(toggleButton); // add to disposable list
}
// Subscribe to redraws to recreate when mods get dropped in
contextInteractions.OnRedrawRequired += createButton;
// And unsubscribe when the window goes away
buttonsContainer.UI.AddDisposable(() =>
{
contextInteractions.OnRedrawRequired -= createButton;
Settings.ShowModStats.SettingChanged -= onSettingChanged;
});
createButton();
}
}
public class FormatCompactValuesPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(CompactCharacteristicPanel), nameof(CompactCharacteristicPanel.SetValues));
}
[PatchPostfix]
public static void Postfix(CompactCharacteristicPanel __instance, TextMeshProUGUI ___ValueText)
{
try
{
FormatText(__instance, ___ValueText);
}
catch (Exception ex)
{
Logger.LogError(ex);
}
}
}
public class FormatFullValuesPatch : ModulePatch
{
private static MethodInfo RoundToIntMethod;
private static MethodInfo ToStringMethod;
protected override MethodBase GetTargetMethod()
{
RoundToIntMethod = AccessTools.Method(typeof(Mathf), nameof(Mathf.RoundToInt));
ToStringMethod = AccessTools.Method(typeof(float), nameof(float.ToString), [typeof(string)]);
return AccessTools.Method(typeof(CharacteristicPanel), nameof(CharacteristicPanel.SetValues));
}
[PatchPostfix]
public static void Postfix(CharacteristicPanel __instance, TextMeshProUGUI ___ValueText)
{
try
{
FormatText(__instance, ___ValueText, true);
}
catch (Exception ex)
{
Logger.LogError(ex);
}
}
// This transpiler looks for where it rounds a float to an int, and skips that. Instead it calls ToString("0.0#") on it
[PatchTranspiler]
public static IEnumerable<CodeInstruction> Transpile(IEnumerable<CodeInstruction> instructions)
{
int skip = 0;
CodeInstruction lastInstruction = null;
CodeInstruction currentInstruction = null;
foreach (var instruction in instructions)
{
if (lastInstruction == null)
{
lastInstruction = instruction;
continue;
}
currentInstruction = instruction;
if (skip > 0)
{
--skip;
}
else if (currentInstruction.Calls(RoundToIntMethod))
{
yield return new CodeInstruction(OpCodes.Ldloca_S, 17);
yield return new CodeInstruction(OpCodes.Ldstr, "0.0#");
yield return new CodeInstruction(OpCodes.Call, ToStringMethod);
skip = 4;
}
else
{
yield return lastInstruction;
}
lastInstruction = instruction;
}
if (currentInstruction != null)
{
yield return currentInstruction;
}
}
}
public class FixDurabilityBarPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.DeclaredMethod(typeof(DurabilityPanel), nameof(DurabilityPanel.SetValues));
}
// Bar width is currently set to durability/100, and that 100 is pretty much hardcoded by the client
// Just clamp the bar to keep it from overflowing
[PatchPostfix]
public static void Postfix(Image ___Current)
{
___Current.rectTransform.anchorMax = new Vector2(
Mathf.Min(___Current.rectTransform.anchorMax.x, 1f),
___Current.rectTransform.anchorMax.y);
}
}
// These fields are percents, but have been manually multipied by 100 already
private static readonly EItemAttributeId[] NonPercentPercents = [EItemAttributeId.ChangeMovementSpeed, EItemAttributeId.ChangeTurningSpeed, EItemAttributeId.Ergonomics];
private static void FormatText(CompactCharacteristicPanel panel, TextMeshProUGUI textMesh, bool fullBar = false)
{
// Comparisons are shown as <value>(<changed>)
// <value> is from each attribute type's StringValue() function, so is formatted *mostly* ok
// <changed> is just naively formatted with ToString("F2"), so I have to figure out what it is and fix that
// This method is a gnarly pile of regex and replacements, blame BSG
if (!Settings.StyleItemPanel.Value)
{
return;
}
// These come from CompactCharacteristicPanel._increasingColor and _decreasingColor, which are hardcoded. Hardcoding here too because
// CharacteristicPanel doesn't define and you get clear
const string IncreasingColorHex = "#5EC1FF";
const string DecreasingColorHex = "#C40000";
string text = textMesh.text;
var wrappedPanel = panel.R();
ItemAttributeClass attribute = wrappedPanel.ItemAttribute;
// Holy shit did they mess up MOA. Half of the calculation is done in the StringValue() method, so calculating delta from Base() loses all that
// Plus, they round the difference to the nearest integer (!?)
// Completely redo it
if ((EItemAttributeId)attribute.Id == EItemAttributeId.CenterOfImpact)
{
var compareAttribute = wrappedPanel.CompareItemAttribute;
if (compareAttribute != null)
{
string currentStringValue = attribute.StringValue();
var moaMatch = Regex.Match(currentStringValue, @"^(\S+)");
if (float.TryParse(moaMatch.Groups[1].Value, out float moa))
{
string compareStringValue = compareAttribute.StringValue();
moaMatch = Regex.Match(compareStringValue, @"^(\S+)");
if (float.TryParse(moaMatch.Groups[1].Value, out float compareMoa))
{
float delta = compareMoa - moa;
string final = currentStringValue;
if (Math.Abs(delta) > 0)
{
string sign = delta > 0 ? "+" : "";
string color = (attribute.LessIsGood && delta < 0) || (!attribute.LessIsGood && delta > 0) ? IncreasingColorHex : DecreasingColorHex;
final += " <color=" + color + ">(" + sign + delta.ToString("0.0#") + ")</color>";
}
textMesh.text = final;
return;
}
}
}
}
// Some percents are formatted with ToString("P1"), which puts a space before the %. These are percents from 0-1, so the <changed> value need to be converted
var match = Regex.Match(text, @" %\(([+-].*)\)");
// Some percents are formatted with ToString("P1"), which puts a space before the %. These are percents from 0-1, so the <changed> value need to be converted
var match = Regex.Match(text, @" %\(([+-].*)\)");
if (match.Success)
{
// If this fails to parse, I don't know what it is, leave it be
if (float.TryParse(match.Groups[1].Value, out float value))
{
string sign = value > 0 ? "+" : "";
string color = (attribute.LessIsGood && value < 0) || (!attribute.LessIsGood && value > 0) ? IncreasingColorHex : DecreasingColorHex;
// Except some that have a space weren't actually formatted with P1 and are 0-100 with a manually added " %"
if (NonPercentPercents.Contains((EItemAttributeId)attribute.Id))
{
text = Regex.Replace(text, @"%\([+-].*\)", "%<color=" + color + ">(" + sign + value + "%)</color>");
}
else
{
text = Regex.Replace(text, @"%\([+-].*\)", "%<color=" + color + ">(" + sign + value.ToString("P1") + ")</color>");
}
}
}
else
{
// Others are rendered as num + "%", so there's no space before the %. These are percents but are from 0-100, not 0-1.
match = Regex.Match(text, @"(\S)%\(([+-].*)\)");
if (match.Success)
{
// If this fails to parse, I don't know what it is, leave it be
if (float.TryParse(match.Groups[1].Value, out float value))
if (float.TryParse(match.Groups[2].Value, out float value))
{
string sign = value > 0 ? "+" : "";
string color = (attribute.LessIsGood && value < 0) || (!attribute.LessIsGood && value > 0) ? IncreasingColorHex : DecreasingColorHex;
// Except some that have a space weren't actually formatted with P1 and are 0-100 with a manually added " %"
if (NonPercentPercents.Contains((EItemAttributeId)attribute.Id))
{
text = Regex.Replace(text, @"%\([+-].*\)", "%<color=" + color + ">(" + sign + value + "%)</color>");
}
else
{
text = Regex.Replace(text, @"%\([+-].*\)", "%<color=" + color + ">(" + sign + value.ToString("P1") + ")</color>");
}
text = Regex.Replace(text, @"(\S)%\(([+-].*)\)", match.Groups[1].Value + "%<color=" + color + ">(" + sign + value + "%)</color>");
}
}
else
{
// Others are rendered as num + "%", so there's no space before the %. These are percents but are from 0-100, not 0-1.
match = Regex.Match(text, @"(\S)%\(([+-].*)\)");
// Finally the ones that aren't percents
match = Regex.Match(text, @"\(([+-].*)\)");
if (match.Success)
{
// If this fails to parse, I don't know what it is, leave it be
if (float.TryParse(match.Groups[2].Value, out float value))
if (float.TryParse(match.Groups[1].Value, out float value))
{
string sign = value > 0 ? "+" : "";
string color = (attribute.LessIsGood && value < 0) || (!attribute.LessIsGood && value > 0) ? IncreasingColorHex : DecreasingColorHex;
text = Regex.Replace(text, @"(\S)%\(([+-].*)\)", match.Groups[1].Value + "%<color=" + color + ">(" + sign + value + "%)</color>");
}
}
else
{
// Finally the ones that aren't percents
match = Regex.Match(text, @"\(([+-].*)\)");
if (match.Success)
{
// If this fails to parse, I don't know what it is, leave it be
if (float.TryParse(match.Groups[1].Value, out float value))
if (fullBar && Math.Abs(value) >= 1)
{
string sign = value > 0 ? "+" : "";
string color = (attribute.LessIsGood && value < 0) || (!attribute.LessIsGood && value > 0) ? IncreasingColorHex : DecreasingColorHex;
if (fullBar && Math.Abs(value) >= 1)
{
// Fullbar rounds to nearest int, but I transpiled it not to. Restore the rounding, but only if the value won't just round to 0
value = Mathf.RoundToInt(value);
}
text = Regex.Replace(text, @"\(([+-].*)\)", "<color=" + color + ">(" + sign + value + ")</color>");
// Fullbar rounds to nearest int, but I transpiled it not to. Restore the rounding, but only if the value won't just round to 0
value = Mathf.RoundToInt(value);
}
text = Regex.Replace(text, @"\(([+-].*)\)", "<color=" + color + ">(" + sign + value + ")</color>");
}
}
}
// Remove trailing 0s
text = RemoveTrailingZeros(text);
// Fix spacing
text = text.Replace(" %", "%");
text = text.Replace("(", " (");
textMesh.text = text;
}
private static List<ItemAttributeClass> GetDeepAttributes(Item item, out bool changed)
{
changed = false;
List<ItemAttributeClass> itemAttributes = item.Attributes.Where(a => a.DisplayType() == EItemAttributeDisplayType.Compact).ToList();
foreach (var subItem in item.GetAllItems()) // This get all items, recursively
{
if (subItem == item)
{
continue;
}
// Remove trailing 0s
text = RemoveTrailingZeros(text);
var subAttributes = subItem.Attributes.Where(a => a.DisplayType() == EItemAttributeDisplayType.Compact).ToList();
itemAttributes = CombineAttributes(itemAttributes, subAttributes).ToList();
changed = true;
// Fix spacing
text = text.Replace(" %", "%");
text = text.Replace("(", " (");
textMesh.text = text;
}
private static List<ItemAttributeClass> GetDeepAttributes(Item item, out bool changed)
{
changed = false;
List<ItemAttributeClass> itemAttributes = item.Attributes.Where(a => a.DisplayType() == EItemAttributeDisplayType.Compact).ToList();
foreach (var subItem in item.GetAllItems()) // This get all items, recursively
{
if (subItem == item)
{
continue;
}
return itemAttributes;
var subAttributes = subItem.Attributes.Where(a => a.DisplayType() == EItemAttributeDisplayType.Compact).ToList();
itemAttributes = CombineAttributes(itemAttributes, subAttributes).ToList();
changed = true;
}
private static IEnumerable<ItemAttributeClass> CombineAttributes(IList<ItemAttributeClass> first, IList<ItemAttributeClass> second)
return itemAttributes;
}
private static IEnumerable<ItemAttributeClass> CombineAttributes(IList<ItemAttributeClass> first, IList<ItemAttributeClass> second)
{
foreach (EItemAttributeId id in first.Select(a => a.Id).Union(second.Select(a => a.Id)).Select(v => (EItemAttributeId)v))
{
foreach (EItemAttributeId id in first.Select(a => a.Id).Union(second.Select(a => a.Id)).Select(v => (EItemAttributeId)v))
// Need to cast the id since it's of type Enum for some reason
var attribute = first.FirstOrDefault(a => (EItemAttributeId)a.Id == id);
var other = second.FirstOrDefault(a => (EItemAttributeId)a.Id == id);
if (attribute == null)
{
// Need to cast the id since it's of type Enum for some reason
var attribute = first.FirstOrDefault(a => (EItemAttributeId)a.Id == id);
var other = second.FirstOrDefault(a => (EItemAttributeId)a.Id == id);
if (attribute == null)
{
yield return other;
}
else if (other == null)
{
yield return attribute;
}
else
{
var combined = attribute.Clone();
switch (attribute.Id)
{
case EItemAttributeId.EffectiveDist:
case EItemAttributeId.SightingRange:
combined.Base = () => Math.Max(attribute.Base(), other.Base());
combined.StringValue = () => combined.Base().ToString();
break;
case EItemAttributeId.Accuracy:
case EItemAttributeId.Recoil:
combined.Base = () => attribute.Base() + other.Base();
combined.StringValue = () => combined.Base() + "%";
break;
case EItemAttributeId.Loudness:
case EItemAttributeId.Ergonomics:
case EItemAttributeId.Velocity:
combined.Base = () => attribute.Base() + other.Base();
combined.StringValue = () => combined.Base().ToString();
break;
case EItemAttributeId.DurabilityBurn:
case EItemAttributeId.HeatFactor:
case EItemAttributeId.CoolFactor:
combined.Base = () => attribute.Base() + other.Base();
combined.StringValue = () => combined.Base().ToString("P1");
break;
case EItemAttributeId.RaidModdable:
break;
default:
break;
}
yield return combined;
}
yield return other;
}
}
else if (other == null)
{
yield return attribute;
}
else
{
var combined = attribute.Clone();
switch (attribute.Id)
{
case EItemAttributeId.EffectiveDist:
case EItemAttributeId.SightingRange:
combined.Base = () => Math.Max(attribute.Base(), other.Base());
combined.StringValue = () => combined.Base().ToString();
break;
case EItemAttributeId.Accuracy:
case EItemAttributeId.Recoil:
combined.Base = () => attribute.Base() + other.Base();
combined.StringValue = () => combined.Base() + "%";
break;
case EItemAttributeId.Loudness:
case EItemAttributeId.Ergonomics:
case EItemAttributeId.Velocity:
combined.Base = () => attribute.Base() + other.Base();
combined.StringValue = () => combined.Base().ToString();
break;
case EItemAttributeId.DurabilityBurn:
case EItemAttributeId.HeatFactor:
case EItemAttributeId.CoolFactor:
combined.Base = () => attribute.Base() + other.Base();
combined.StringValue = () => combined.Base().ToString("P1");
break;
case EItemAttributeId.RaidModdable:
break;
default:
break;
}
public static string RemoveTrailingZeros(string input)
{
// This matches: a number (so it doesn't apply to periods in words), named "integer"
// Followed by either
// a) a dot, some digits, and then a non-zero digit (named "significantDigits"), which is followed by one or more trailing 0
// b) a dot and some trailing 0
// And all that is replaced to the original integer, and the significantDigits (if they exist)
// If neither matches this doesn't match and does nothing
return Regex.Replace(input, @"(?<integer>\d)((?<significantDecimals>\.[0-9]*[1-9])0*\b)?(\.0+\b)?", "${integer}${significantDecimals}");
yield return combined;
}
}
}
public static string RemoveTrailingZeros(string input)
{
// This matches: a number (so it doesn't apply to periods in words), named "integer"
// Followed by either
// a) a dot, some digits, and then a non-zero digit (named "significantDigits"), which is followed by one or more trailing 0
// b) a dot and some trailing 0
// And all that is replaced to the original integer, and the significantDigits (if they exist)
// If neither matches this doesn't match and does nothing
return Regex.Replace(input, @"(?<integer>\d)((?<significantDecimals>\.[0-9]*[1-9])0*\b)?(\.0+\b)?", "${integer}${significantDecimals}");
}
}

View File

@@ -5,50 +5,49 @@ using HarmonyLib;
using SPT.Reflection.Patching;
using System.Reflection;
namespace UIFixes
namespace UIFixes;
public static class KeepMessagesOpenPatches
{
public static class KeepMessagesOpenPatches
private static bool ReopenMessages = false;
public static void Enable()
{
private static bool ReopenMessages = false;
new SniffChatPanelClosePatch().Enable();
new ReopenMessagesPatch().Enable();
}
public static void Enable()
public class SniffChatPanelClosePatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
new SniffChatPanelClosePatch().Enable();
new ReopenMessagesPatch().Enable();
return AccessTools.Method(typeof(ChatScreen), nameof(ChatScreen.method_6));
}
public class SniffChatPanelClosePatch : ModulePatch
[PatchPostfix]
public static void Postfix()
{
protected override MethodBase GetTargetMethod()
if (Settings.KeepMessagesOpen.Value)
{
return AccessTools.Method(typeof(ChatScreen), nameof(ChatScreen.method_6));
}
[PatchPostfix]
public static void Postfix()
{
if (Settings.KeepMessagesOpen.Value)
{
ReopenMessages = true;
}
ReopenMessages = true;
}
}
}
public class ReopenMessagesPatch : ModulePatch
public class ReopenMessagesPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(MainMenuController), nameof(MainMenuController.method_0));
}
return AccessTools.Method(typeof(MainMenuController), nameof(MainMenuController.method_0));
}
[PatchPostfix]
public static void Postfix(MainMenuController __instance, EEftScreenType eftScreenType)
[PatchPostfix]
public static void Postfix(MainMenuController __instance, EEftScreenType eftScreenType)
{
if (Settings.KeepMessagesOpen.Value && eftScreenType != EEftScreenType.TransferItems && ReopenMessages)
{
if (Settings.KeepMessagesOpen.Value && eftScreenType != EEftScreenType.TransferItems && ReopenMessages)
{
ReopenMessages = false;
__instance.ShowScreen(EMenuType.Chat, true);
}
ReopenMessages = false;
__instance.ShowScreen(EMenuType.Chat, true);
}
}
}

View File

@@ -7,153 +7,152 @@ using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
namespace UIFixes
namespace UIFixes;
public static class KeepOfferWindowOpenPatches
{
public static class KeepOfferWindowOpenPatches
private static bool BlockClose = false;
private static TaskCompletionSource CompletionSource;
private static readonly List<Task> AddOfferTasks = [];
private static AddOfferWindow Window;
public static void Enable()
{
private static bool BlockClose = false;
new GetTaskCompletionSourcePatch().Enable();
new PlaceOfferClickPatch().Enable();
new ClosePatch().Enable();
new ManageTaskPatch().Enable();
}
private static TaskCompletionSource CompletionSource;
private static readonly List<Task> AddOfferTasks = [];
private static AddOfferWindow Window;
public static void Enable()
public class GetTaskCompletionSourcePatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
new GetTaskCompletionSourcePatch().Enable();
new PlaceOfferClickPatch().Enable();
new ClosePatch().Enable();
new ManageTaskPatch().Enable();
return AccessTools.DeclaredMethod(typeof(AddOfferWindow), nameof(AddOfferWindow.Show));
}
public class GetTaskCompletionSourcePatch : ModulePatch
[PatchPostfix]
public static void Postfix(AddOfferWindow __instance, ref Task __result)
{
protected override MethodBase GetTargetMethod()
if (!Settings.KeepAddOfferOpen.Value)
{
return AccessTools.DeclaredMethod(typeof(AddOfferWindow), nameof(AddOfferWindow.Show));
return;
}
[PatchPostfix]
public static void Postfix(AddOfferWindow __instance, ref Task __result)
Window = __instance;
AddOfferTasks.Clear();
// Use a different task to mark when everything is actually done
CompletionSource = new TaskCompletionSource();
__result = CompletionSource.Task;
}
}
public class PlaceOfferClickPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(AddOfferWindow), nameof(AddOfferWindow.method_1));
}
[PatchPrefix]
public static void Prefix(AddOfferWindow __instance, TaskCompletionSource ___taskCompletionSource_0, ref TaskCompletionSource __state)
{
if (!Settings.KeepAddOfferOpen.Value)
{
if (!Settings.KeepAddOfferOpen.Value)
{
return;
}
return;
}
Window = __instance;
AddOfferTasks.Clear();
__state = ___taskCompletionSource_0;
// Use a different task to mark when everything is actually done
CompletionSource = new TaskCompletionSource();
__result = CompletionSource.Task;
// Close the window if you're gonna hit max offers
var ragfair = __instance.R().Ragfair;
if (Settings.KeepAddOfferOpenIgnoreMaxOffers.Value || ragfair.MyOffersCount + 1 < ragfair.GetMaxOffersCount(ragfair.MyRating))
{
BlockClose = true;
}
}
public class PlaceOfferClickPatch : ModulePatch
[PatchPostfix]
public static void Postfix(RequirementView[] ____requirementViews, TaskCompletionSource ___taskCompletionSource_0, ref TaskCompletionSource __state)
{
protected override MethodBase GetTargetMethod()
BlockClose = false;
if (!Settings.KeepAddOfferOpen.Value)
{
return AccessTools.Method(typeof(AddOfferWindow), nameof(AddOfferWindow.method_1));
return;
}
[PatchPrefix]
public static void Prefix(AddOfferWindow __instance, TaskCompletionSource ___taskCompletionSource_0, ref TaskCompletionSource __state)
// If the taskCompletionSource member was changed, then it's adding an offer :S
if (__state != ___taskCompletionSource_0)
{
if (!Settings.KeepAddOfferOpen.Value)
AddOfferTasks.Add(__state.Task); // This is the task completion source passed into the add offer call
// clear old prices
foreach (var requirementView in ____requirementViews)
{
return;
requirementView.ResetRequirementInformation();
}
__state = ___taskCompletionSource_0;
// Close the window if you're gonna hit max offers
var ragfair = __instance.R().Ragfair;
if (Settings.KeepAddOfferOpenIgnoreMaxOffers.Value || ragfair.MyOffersCount + 1 < ragfair.GetMaxOffersCount(ragfair.MyRating))
{
BlockClose = true;
}
}
[PatchPostfix]
public static void Postfix(RequirementView[] ____requirementViews, TaskCompletionSource ___taskCompletionSource_0, ref TaskCompletionSource __state)
{
BlockClose = false;
if (!Settings.KeepAddOfferOpen.Value)
{
return;
}
// If the taskCompletionSource member was changed, then it's adding an offer :S
if (__state != ___taskCompletionSource_0)
{
AddOfferTasks.Add(__state.Task); // This is the task completion source passed into the add offer call
// clear old prices
foreach (var requirementView in ____requirementViews)
{
requirementView.ResetRequirementInformation();
}
}
}
}
public class ClosePatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(AddOfferWindow), nameof(Window.Close));
}
[PatchPrefix]
public static bool Prefix()
{
if (!Settings.KeepAddOfferOpen.Value)
{
return true;
}
if (!BlockClose && CompletionSource != null && AddOfferTasks.All(t => t.IsCompleted))
{
CompletionSource.Complete();
CompletionSource = null;
AddOfferTasks.Clear();
Window = null;
}
return !BlockClose;
}
}
// The window has a task completion source that completes when closing window or upon successful offer placement (which assumes window closes too)
// Replace implementation to ensure it only completes when window is closed, or placement is successful AND window has since closed
public class ManageTaskPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
Type type = typeof(AddOfferWindow).GetNestedTypes().Single(t => t.GetField("completionSource") != null); // AddOfferWindow.Class3068
return AccessTools.Method(type, "method_0");
}
[PatchPrefix]
public static bool Prefix(TaskCompletionSource ___completionSource)
{
if (!Settings.KeepAddOfferOpen.Value || Window == null)
{
return true;
}
___completionSource.Complete();
if (!Window.gameObject.activeInHierarchy && AddOfferTasks.All(t => t.IsCompleted))
{
CompletionSource.Complete();
CompletionSource = null;
AddOfferTasks.Clear();
Window = null;
}
return false;
}
}
}
public class ClosePatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(AddOfferWindow), nameof(Window.Close));
}
[PatchPrefix]
public static bool Prefix()
{
if (!Settings.KeepAddOfferOpen.Value)
{
return true;
}
if (!BlockClose && CompletionSource != null && AddOfferTasks.All(t => t.IsCompleted))
{
CompletionSource.Complete();
CompletionSource = null;
AddOfferTasks.Clear();
Window = null;
}
return !BlockClose;
}
}
// The window has a task completion source that completes when closing window or upon successful offer placement (which assumes window closes too)
// Replace implementation to ensure it only completes when window is closed, or placement is successful AND window has since closed
public class ManageTaskPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
Type type = typeof(AddOfferWindow).GetNestedTypes().Single(t => t.GetField("completionSource") != null); // AddOfferWindow.Class3068
return AccessTools.Method(type, "method_0");
}
[PatchPrefix]
public static bool Prefix(TaskCompletionSource ___completionSource)
{
if (!Settings.KeepAddOfferOpen.Value || Window == null)
{
return true;
}
___completionSource.Complete();
if (!Window.gameObject.activeInHierarchy && AddOfferTasks.All(t => t.IsCompleted))
{
CompletionSource.Complete();
CompletionSource = null;
AddOfferTasks.Clear();
Window = null;
}
return false;
}
}
}

View File

@@ -6,36 +6,35 @@ using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace UIFixes
namespace UIFixes;
public static class KeepWindowsOnScreenPatches
{
public static class KeepWindowsOnScreenPatches
public static void Enable()
{
public static void Enable()
new KeepWindowOnScreenPatch(nameof(ItemUiContext.Inspect)).Enable();
new KeepWindowOnScreenPatch(nameof(ItemUiContext.EditTag)).Enable();
new KeepWindowOnScreenPatch(nameof(ItemUiContext.OpenInsuranceWindow)).Enable();
new KeepWindowOnScreenPatch(nameof(ItemUiContext.OpenRepairWindow)).Enable();
new KeepWindowOnScreenPatch(nameof(ItemUiContext.method_2)).Enable(); // grids
}
private static void FixNewestWindow(List<InputNode> windows)
{
UIInputNode newWindow = windows.Last() as UIInputNode;
newWindow?.CorrectPosition();
}
public class KeepWindowOnScreenPatch(string methodName) : ModulePatch
{
private readonly string methodName = methodName;
protected override MethodBase GetTargetMethod()
{
new KeepWindowOnScreenPatch(nameof(ItemUiContext.Inspect)).Enable();
new KeepWindowOnScreenPatch(nameof(ItemUiContext.EditTag)).Enable();
new KeepWindowOnScreenPatch(nameof(ItemUiContext.OpenInsuranceWindow)).Enable();
new KeepWindowOnScreenPatch(nameof(ItemUiContext.OpenRepairWindow)).Enable();
new KeepWindowOnScreenPatch(nameof(ItemUiContext.method_2)).Enable(); // grids
return AccessTools.Method(typeof(ItemUiContext), methodName);
}
private static void FixNewestWindow(List<InputNode> windows)
{
UIInputNode newWindow = windows.Last() as UIInputNode;
newWindow?.CorrectPosition();
}
public class KeepWindowOnScreenPatch(string methodName) : ModulePatch
{
private readonly string methodName = methodName;
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(ItemUiContext), methodName);
}
[PatchPostfix]
public static void Postfix(List<InputNode> ____children) => FixNewestWindow(____children);
}
[PatchPostfix]
public static void Postfix(List<InputNode> ____children) => FixNewestWindow(____children);
}
}

View File

@@ -8,76 +8,75 @@ using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
namespace UIFixes
namespace UIFixes;
public class LoadAmmoInRaidPatches
{
public class LoadAmmoInRaidPatches
public static void Enable()
{
public static void Enable()
new EnableContextMenuPatch().Enable();
new SlowLoadingPatch().Enable();
}
public class EnableContextMenuPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
new EnableContextMenuPatch().Enable();
new SlowLoadingPatch().Enable();
return AccessTools.Method(R.ContextMenuHelper.Type, "IsActive");
}
public class EnableContextMenuPatch : ModulePatch
[PatchPrefix]
public static bool Prefix(EItemInfoButton button, ref bool __result, Item ___item_0)
{
protected override MethodBase GetTargetMethod()
if (button != EItemInfoButton.LoadAmmo || !Plugin.InRaid() || !Settings.EnableLoadAmmo.Value)
{
return AccessTools.Method(R.ContextMenuHelper.Type, "IsActive");
return true;
}
[PatchPrefix]
public static bool Prefix(EItemInfoButton button, ref bool __result, Item ___item_0)
{
if (button != EItemInfoButton.LoadAmmo || !Plugin.InRaid() || !Settings.EnableLoadAmmo.Value)
{
return true;
}
__result = MagazineBuildClass.TryFindPresetSource(___item_0).Succeeded;
return false;
}
}
__result = MagazineBuildClass.TryFindPresetSource(___item_0).Succeeded;
return false;
}
public class SlowLoadingPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(ItemUiContext), nameof(ItemUiContext.LoadAmmoByType));
}
public class SlowLoadingPatch : ModulePatch
// This code is a mix of ItemUiContext.LoadAmmoByType, but then switching over to GridView.AcceptItem
[PatchPrefix]
public static bool Prefix(ItemUiContext __instance, MagazineClass magazine, string ammoTemplateId, ref Task __result)
{
protected override MethodBase GetTargetMethod()
if (!Plugin.InRaid() || !Settings.EnableLoadAmmo.Value)
{
return AccessTools.Method(typeof(ItemUiContext), nameof(ItemUiContext.LoadAmmoByType));
return true;
}
// This code is a mix of ItemUiContext.LoadAmmoByType, but then switching over to GridView.AcceptItem
[PatchPrefix]
public static bool Prefix(ItemUiContext __instance, MagazineClass magazine, string ammoTemplateId, ref Task __result)
InventoryControllerClass inventoryController = __instance.R().InventoryController;
EquipmentClass equipment = inventoryController.Inventory.Equipment;
List<BulletClass> ammo = [];
equipment.GetAllAssembledItems(ammo);
// Just do the first stack
BulletClass bullets = ammo.Where(a => a.TemplateId == ammoTemplateId && a.Parent.Container is not Slot)
.OrderBy(a => a.SpawnedInSession)
.ThenBy(a => a.StackObjectsCount)
.FirstOrDefault();
if (bullets != null)
{
if (!Plugin.InRaid() || !Settings.EnableLoadAmmo.Value)
{
return true;
}
InventoryControllerClass inventoryController = __instance.R().InventoryController;
EquipmentClass equipment = inventoryController.Inventory.Equipment;
List<BulletClass> ammo = [];
equipment.GetAllAssembledItems(ammo);
// Just do the first stack
BulletClass bullets = ammo.Where(a => a.TemplateId == ammoTemplateId && a.Parent.Container is not Slot)
.OrderBy(a => a.SpawnedInSession)
.ThenBy(a => a.StackObjectsCount)
.FirstOrDefault();
if (bullets != null)
{
int count = GridView.smethod_0(magazine, bullets);
__result = inventoryController.LoadMagazine(bullets, magazine, count, false);
}
else
{
__result = Task.CompletedTask;
}
return false;
int count = GridView.smethod_0(magazine, bullets);
__result = inventoryController.LoadMagazine(bullets, magazine, count, false);
}
else
{
__result = Task.CompletedTask;
}
return false;
}
}
}

View File

@@ -5,46 +5,45 @@ using System;
using System.Collections.Generic;
using System.Reflection;
namespace UIFixes
namespace UIFixes;
public class LoadMagPresetsPatch : ModulePatch
{
public class LoadMagPresetsPatch : ModulePatch
protected override MethodBase GetTargetMethod()
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(MagazineBuildPresetClass), nameof(MagazineBuildPresetClass.smethod_0));
}
return AccessTools.Method(typeof(MagazineBuildPresetClass), nameof(MagazineBuildPresetClass.smethod_0));
}
// This method returns a list of places to search for ammo. For whatever reason, it only looks
// in equipment if stash + sorting table are not present.
// Can't just add equipment because that includes equipped slots and it likes to pull the chambered bullet out of equipped guns
[PatchPostfix]
public static void Postfix(Inventory inventory, List<LootItemClass> __result)
// This method returns a list of places to search for ammo. For whatever reason, it only looks
// in equipment if stash + sorting table are not present.
// Can't just add equipment because that includes equipped slots and it likes to pull the chambered bullet out of equipped guns
[PatchPostfix]
public static void Postfix(Inventory inventory, List<LootItemClass> __result)
{
if (!__result.Contains(inventory.Equipment))
{
if (!__result.Contains(inventory.Equipment))
var vest = inventory.Equipment.GetSlot(EquipmentSlot.TacticalVest);
if (vest.ContainedItem is LootItemClass vestLootItem)
{
var vest = inventory.Equipment.GetSlot(EquipmentSlot.TacticalVest);
if (vest.ContainedItem is LootItemClass vestLootItem)
{
__result.Add(vestLootItem);
}
__result.Add(vestLootItem);
}
var pockets = inventory.Equipment.GetSlot(EquipmentSlot.Pockets);
if (pockets.ContainedItem is LootItemClass pocketsLootItem)
{
__result.Add(pocketsLootItem);
}
var pockets = inventory.Equipment.GetSlot(EquipmentSlot.Pockets);
if (pockets.ContainedItem is LootItemClass pocketsLootItem)
{
__result.Add(pocketsLootItem);
}
var backpack = inventory.Equipment.GetSlot(EquipmentSlot.Backpack);
if (backpack.ContainedItem is LootItemClass backpackLootItem)
{
__result.Add(backpackLootItem);
}
var backpack = inventory.Equipment.GetSlot(EquipmentSlot.Backpack);
if (backpack.ContainedItem is LootItemClass backpackLootItem)
{
__result.Add(backpackLootItem);
}
var secureContainer = inventory.Equipment.GetSlot(EquipmentSlot.SecuredContainer);
if (secureContainer.ContainedItem is LootItemClass secureContainerLootItem)
{
__result.Add(secureContainerLootItem);
}
var secureContainer = inventory.Equipment.GetSlot(EquipmentSlot.SecuredContainer);
if (secureContainer.ContainedItem is LootItemClass secureContainerLootItem)
{
__result.Add(secureContainerLootItem);
}
}
}

View File

@@ -9,142 +9,141 @@ using System.Reflection;
using System.Threading.Tasks;
using UnityEngine;
namespace UIFixes
namespace UIFixes;
public static class LoadMultipleMagazinesPatches
{
public static class LoadMultipleMagazinesPatches
private static ItemFilter[] CombinedFilters;
public static void Enable()
{
private static ItemFilter[] CombinedFilters;
new FindCompatibleAmmoPatch().Enable();
new CheckItemFilterPatch().Enable();
new LoadAmmoPatch().Enable();
new FilterMagPresetsPatch().Enable();
new LoadPresetPatch().Enable();
}
public static void Enable()
public class FindCompatibleAmmoPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
new FindCompatibleAmmoPatch().Enable();
new CheckItemFilterPatch().Enable();
new LoadAmmoPatch().Enable();
new FilterMagPresetsPatch().Enable();
new LoadPresetPatch().Enable();
return AccessTools.Method(typeof(ItemUiContext), nameof(ItemUiContext.FindCompatibleAmmo));
}
public class FindCompatibleAmmoPatch : ModulePatch
[PatchPrefix]
public static void Prefix()
{
protected override MethodBase GetTargetMethod()
if (MultiSelect.Active)
{
return AccessTools.Method(typeof(ItemUiContext), nameof(ItemUiContext.FindCompatibleAmmo));
}
[PatchPrefix]
public static void Prefix()
{
if (MultiSelect.Active)
{
CombinedFilters = MultiSelect.SortedItemContexts()
.Select(itemContext => itemContext.Item)
.OfType<MagazineClass>()
.SelectMany(mag => mag.Cartridges.Filters)
.ToArray();
}
}
[PatchPostfix]
public static void Postfix()
{
CombinedFilters = null;
CombinedFilters = MultiSelect.SortedItemContexts()
.Select(itemContext => itemContext.Item)
.OfType<MagazineClass>()
.SelectMany(mag => mag.Cartridges.Filters)
.ToArray();
}
}
public class CheckItemFilterPatch : ModulePatch
[PatchPostfix]
public static void Postfix()
{
protected override MethodBase GetTargetMethod()
CombinedFilters = null;
}
}
public class CheckItemFilterPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(ItemFilterExtensions), nameof(ItemFilterExtensions.CheckItemFilter));
}
[PatchPrefix]
public static void Prefix(ref ItemFilter[] filters)
{
if (CombinedFilters == null)
{
return AccessTools.Method(typeof(ItemFilterExtensions), nameof(ItemFilterExtensions.CheckItemFilter));
return;
}
[PatchPrefix]
public static void Prefix(ref ItemFilter[] filters)
{
if (CombinedFilters == null)
{
return;
}
filters = CombinedFilters;
}
}
filters = CombinedFilters;
public class LoadAmmoPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
Type type = PatchConstants.EftTypes.Single(t => t.GetNestedType("EMagInteraction") != null);
return AccessTools.Method(type, "method_6");
}
[PatchPrefix]
public static bool Prefix(string ammoTemplateId, ref Task __result, ItemUiContext ___itemUiContext_0)
{
if (!MultiSelect.Active)
{
return true;
}
__result = MultiSelect.LoadAmmoAll(___itemUiContext_0, ammoTemplateId, false);
return false;
}
}
public class FilterMagPresetsPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
Type type = PatchConstants.EftTypes.Single(t => t.GetNestedType("EMagPresetInteraction") != null);
return AccessTools.Method(type, "method_7");
}
[PatchPrefix]
public static void Prefix()
{
if (MultiSelect.Active)
{
CombinedFilters = MultiSelect.SortedItemContexts()
.Select(itemContext => itemContext.Item)
.OfType<MagazineClass>()
.SelectMany(mag => mag.Cartridges.Filters)
.ToArray();
}
}
public class LoadAmmoPatch : ModulePatch
[PatchPostfix]
public static void Postfix()
{
protected override MethodBase GetTargetMethod()
{
Type type = PatchConstants.EftTypes.Single(t => t.GetNestedType("EMagInteraction") != null);
return AccessTools.Method(type, "method_6");
}
CombinedFilters = null;
}
}
[PatchPrefix]
public static bool Prefix(string ammoTemplateId, ref Task __result, ItemUiContext ___itemUiContext_0)
{
if (!MultiSelect.Active)
{
return true;
}
__result = MultiSelect.LoadAmmoAll(___itemUiContext_0, ammoTemplateId, false);
return false;
}
public class LoadPresetPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
Type type = PatchConstants.EftTypes.Single(t => t.GetNestedType("EMagPresetInteraction") != null);
return AccessTools.Method(type, "method_6");
}
public class FilterMagPresetsPatch : ModulePatch
[PatchPrefix]
public static bool Prefix(MagazineBuildPresetClass preset, ItemUiContext ___itemUiContext_1)
{
protected override MethodBase GetTargetMethod()
if (!MultiSelect.Active)
{
Type type = PatchConstants.EftTypes.Single(t => t.GetNestedType("EMagPresetInteraction") != null);
return AccessTools.Method(type, "method_7");
return true;
}
[PatchPrefix]
public static void Prefix()
if (Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl))
{
if (MultiSelect.Active)
{
CombinedFilters = MultiSelect.SortedItemContexts()
.Select(itemContext => itemContext.Item)
.OfType<MagazineClass>()
.SelectMany(mag => mag.Cartridges.Filters)
.ToArray();
}
return true;
}
[PatchPostfix]
public static void Postfix()
{
CombinedFilters = null;
}
}
var magazines = MultiSelect.SortedItemContexts().Select(itemContext => itemContext.Item).OfType<MagazineClass>();
___itemUiContext_1.ApplyMagPreset(preset, magazines.ToList()).HandleExceptions();
public class LoadPresetPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
Type type = PatchConstants.EftTypes.Single(t => t.GetNestedType("EMagPresetInteraction") != null);
return AccessTools.Method(type, "method_6");
}
[PatchPrefix]
public static bool Prefix(MagazineBuildPresetClass preset, ItemUiContext ___itemUiContext_1)
{
if (!MultiSelect.Active)
{
return true;
}
if (Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl))
{
return true;
}
var magazines = MultiSelect.SortedItemContexts().Select(itemContext => itemContext.Item).OfType<MagazineClass>();
___itemUiContext_1.ApplyMagPreset(preset, magazines.ToList()).HandleExceptions();
return false;
}
return false;
}
}
}

View File

@@ -5,21 +5,20 @@ using System.Linq;
using System.Reflection;
using UnityEngine;
namespace UIFixes
namespace UIFixes;
public class MoveTaskbarPatch : ModulePatch
{
public class MoveTaskbarPatch : ModulePatch
protected override MethodBase GetTargetMethod()
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(MenuTaskBar), nameof(MenuTaskBar.Awake));
}
return AccessTools.Method(typeof(MenuTaskBar), nameof(MenuTaskBar.Awake));
}
[PatchPostfix]
public static void Postfix(MenuTaskBar __instance)
{
var bottomPanel = __instance.GetComponentsInParent<RectTransform>().First(c => c.name == "BottomPanel");
[PatchPostfix]
public static void Postfix(MenuTaskBar __instance)
{
var bottomPanel = __instance.GetComponentsInParent<RectTransform>().First(c => c.name == "BottomPanel");
bottomPanel.localPosition = new Vector3(0f, -3f, 0f);
}
bottomPanel.localPosition = new Vector3(0f, -3f, 0f);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -4,56 +4,55 @@ using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
namespace UIFixes
namespace UIFixes;
public class NoRandomGrenadesPatch : ModulePatch
{
public class NoRandomGrenadesPatch : ModulePatch
private static NoRandomGrenadesPatch Patch;
public static void Init()
{
private static NoRandomGrenadesPatch Patch;
public static void Init()
Settings.DeterministicGrenades.Bind(enabled =>
{
Settings.DeterministicGrenades.Bind(enabled =>
if (enabled)
{
if (enabled)
{
Patch ??= new NoRandomGrenadesPatch();
Patch.Enable();
}
else
{
Patch?.Disable();
}
});
}
// Make ctor private so I don't forget to call Init() instead
private NoRandomGrenadesPatch() { }
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(Class1472), nameof(Class1472.vmethod_1));
}
[PatchTranspiler]
public static IEnumerable<CodeInstruction> Transpile(IEnumerable<CodeInstruction> instructions)
{
foreach (var instruction in instructions)
Patch ??= new NoRandomGrenadesPatch();
Patch.Enable();
}
else
{
if (instruction.opcode == OpCodes.Ble_S || instruction.opcode == OpCodes.Ble) // DnSpy is lying about which one this is!?
Patch?.Disable();
}
});
}
// Make ctor private so I don't forget to call Init() instead
private NoRandomGrenadesPatch() { }
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(Class1472), nameof(Class1472.vmethod_1));
}
[PatchTranspiler]
public static IEnumerable<CodeInstruction> Transpile(IEnumerable<CodeInstruction> instructions)
{
foreach (var instruction in instructions)
{
if (instruction.opcode == OpCodes.Ble_S || instruction.opcode == OpCodes.Ble) // DnSpy is lying about which one this is!?
{
// This is the line
// if (count > 0)
// which in IL does "if count is less than or equal to 1, jump over"
// So switch the IL to bge, so it jumps over if count is greater or equal to 1, thus skipping the randomizer
yield return new CodeInstruction(instruction)
{
// This is the line
// if (count > 0)
// which in IL does "if count is less than or equal to 1, jump over"
// So switch the IL to bge, so it jumps over if count is greater or equal to 1, thus skipping the randomizer
yield return new CodeInstruction(instruction)
{
opcode = instruction.opcode == OpCodes.Ble_S ? OpCodes.Bge_S : OpCodes.Bge,
};
}
else
{
yield return instruction;
}
opcode = instruction.opcode == OpCodes.Ble_S ? OpCodes.Bge_S : OpCodes.Bge,
};
}
else
{
yield return instruction;
}
}
}

View File

@@ -6,45 +6,44 @@ using System.Linq;
using System.Reflection;
using UnityEngine;
namespace UIFixes
namespace UIFixes;
public class OpenSortingTablePatch : ModulePatch
{
public class OpenSortingTablePatch : ModulePatch
private static readonly EItemUiContextType[] AllowedScreens = [EItemUiContextType.InventoryScreen, EItemUiContextType.ScavengerInventoryScreen];
protected override MethodBase GetTargetMethod()
{
private static readonly EItemUiContextType[] AllowedScreens = [EItemUiContextType.InventoryScreen, EItemUiContextType.ScavengerInventoryScreen];
return AccessTools.Method(typeof(ItemUiContext), nameof(ItemUiContext.QuickMoveToSortingTable));
}
protected override MethodBase GetTargetMethod()
[PatchPrefix]
public static void Prefix(ItemUiContext __instance)
{
if (!Settings.AutoOpenSortingTable.Value || !AllowedScreens.Contains(__instance.ContextType) || Plugin.InRaid())
{
return AccessTools.Method(typeof(ItemUiContext), nameof(ItemUiContext.QuickMoveToSortingTable));
return;
}
[PatchPrefix]
public static void Prefix(ItemUiContext __instance)
// Temporary work-around for LootValue bug - bail out if the ALT key is down
if (Input.GetKey(KeyCode.LeftAlt))
{
if (!Settings.AutoOpenSortingTable.Value || !AllowedScreens.Contains(__instance.ContextType) || Plugin.InRaid())
{
return;
}
return;
}
// Temporary work-around for LootValue bug - bail out if the ALT key is down
if (Input.GetKey(KeyCode.LeftAlt))
SortingTableClass sortingTable = __instance.R().InventoryController.Inventory.SortingTable;
if (sortingTable != null && !sortingTable.IsVisible)
{
if (__instance.ContextType == EItemUiContextType.InventoryScreen)
{
return;
Singleton<CommonUI>.Instance.InventoryScreen.method_6();
Singleton<CommonUI>.Instance.InventoryScreen.R().SimpleStashPanel.ChangeSortingTableTabState(true);
}
SortingTableClass sortingTable = __instance.R().InventoryController.Inventory.SortingTable;
if (sortingTable != null && !sortingTable.IsVisible)
else if (__instance.ContextType == EItemUiContextType.ScavengerInventoryScreen)
{
if (__instance.ContextType == EItemUiContextType.InventoryScreen)
{
Singleton<CommonUI>.Instance.InventoryScreen.method_6();
Singleton<CommonUI>.Instance.InventoryScreen.R().SimpleStashPanel.ChangeSortingTableTabState(true);
}
else if (__instance.ContextType == EItemUiContextType.ScavengerInventoryScreen)
{
Singleton<CommonUI>.Instance.ScavengerInventoryScreen.method_7();
Singleton<CommonUI>.Instance.ScavengerInventoryScreen.R().SimpleStashPanel.ChangeSortingTableTabState(true);
}
Singleton<CommonUI>.Instance.ScavengerInventoryScreen.method_7();
Singleton<CommonUI>.Instance.ScavengerInventoryScreen.R().SimpleStashPanel.ChangeSortingTableTabState(true);
}
}
}

View File

@@ -7,62 +7,61 @@ using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace UIFixes
namespace UIFixes;
public class PutToolsBackPatch : ModulePatch
{
public class PutToolsBackPatch : ModulePatch
protected override MethodBase GetTargetMethod()
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(R.ItemReceiver.Type, "method_9"); // GClass1855
}
return AccessTools.Method(R.ItemReceiver.Type, "method_9"); // GClass1855
}
// The patched method can't handle new items that aren't in stash root.
// Find items that are in subcontainers and handle them first - the patched method will ignore items that have a CurrentAddress
// This is a subset of the original method - doesn't handle slots, equipment containers, etc.
[PatchPrefix]
public static void Prefix(object __instance, ref JsonItem[] newItems, Profile ___profile_0, ItemFactory ___itemFactory)
// The patched method can't handle new items that aren't in stash root.
// Find items that are in subcontainers and handle them first - the patched method will ignore items that have a CurrentAddress
// This is a subset of the original method - doesn't handle slots, equipment containers, etc.
[PatchPrefix]
public static void Prefix(object __instance, ref JsonItem[] newItems, Profile ___profile_0, ItemFactory ___itemFactory)
{
Inventory inventory = ___profile_0.Inventory;
StashClass stash = inventory.Stash;
if (inventory != null && stash != null)
{
Inventory inventory = ___profile_0.Inventory;
StashClass stash = inventory.Stash;
if (inventory != null && stash != null)
// Handled items are either in these top level containers or are nested inside each other (mods, attachments, etc)
var handledContainerIds = newItems.Select(i => i._id).Concat([inventory.Stash.Id, inventory.Equipment.Id, inventory.QuestRaidItems.Id, inventory.QuestStashItems.Id, inventory.SortingTable.Id]);
var unhandledItems = newItems.Where(i => !String.IsNullOrEmpty(i.parentId) && !handledContainerIds.Contains(i.parentId));
if (!unhandledItems.Any())
{
// Handled items are either in these top level containers or are nested inside each other (mods, attachments, etc)
var handledContainerIds = newItems.Select(i => i._id).Concat([inventory.Stash.Id, inventory.Equipment.Id, inventory.QuestRaidItems.Id, inventory.QuestStashItems.Id, inventory.SortingTable.Id]);
var unhandledItems = newItems.Where(i => !String.IsNullOrEmpty(i.parentId) && !handledContainerIds.Contains(i.parentId));
return;
}
if (!unhandledItems.Any())
// Change the parameter to remove the items handled here
newItems = newItems.Except(unhandledItems).ToArray();
List<Item> stashItems = stash.GetNotMergedItems().ToList();
InventoryControllerClass inventoryController = new R.ItemReceiver(__instance).InventoryController;
var tree = ___itemFactory.FlatItemsToTree(unhandledItems.ToArray(), true, null);
foreach (Item item in tree.Items.Values.Where(i => i.CurrentAddress == null))
{
var newItem = unhandledItems.First(i => i._id == item.Id);
if (newItem.parentId != null || newItem.slotId != null)
{
return;
}
// Change the parameter to remove the items handled here
newItems = newItems.Except(unhandledItems).ToArray();
List<Item> stashItems = stash.GetNotMergedItems().ToList();
InventoryControllerClass inventoryController = new R.ItemReceiver(__instance).InventoryController;
var tree = ___itemFactory.FlatItemsToTree(unhandledItems.ToArray(), true, null);
foreach (Item item in tree.Items.Values.Where(i => i.CurrentAddress == null))
{
var newItem = unhandledItems.First(i => i._id == item.Id);
if (newItem.parentId != null || newItem.slotId != null)
// Assuming here that unhandled items are trying to go into containers in the stash - find that container
Item parent = stashItems.FirstOrDefault(i => i.Id == newItem.parentId);
if (parent is ContainerCollection containerCollection)
{
// Assuming here that unhandled items are trying to go into containers in the stash - find that container
Item parent = stashItems.FirstOrDefault(i => i.Id == newItem.parentId);
if (parent is ContainerCollection containerCollection)
if (containerCollection.GetContainer(newItem.slotId) is StashGridClass grid)
{
if (containerCollection.GetContainer(newItem.slotId) is StashGridClass grid)
{
LocationInGrid location = LocationJsonParser.CreateItemLocation<LocationInGrid>(newItem.location);
ItemAddress itemAddress = new GridItemAddress(grid, location);
LocationInGrid location = LocationJsonParser.CreateItemLocation<LocationInGrid>(newItem.location);
ItemAddress itemAddress = new GridItemAddress(grid, location);
var operation = InteractionsHandlerClass.Add(item, itemAddress, inventoryController, false);
if (operation.Succeeded)
{
operation.Value.RaiseEvents(inventoryController, CommandStatus.Begin);
operation.Value.RaiseEvents(inventoryController, CommandStatus.Succeed);
}
var operation = InteractionsHandlerClass.Add(item, itemAddress, inventoryController, false);
if (operation.Succeeded)
{
operation.Value.RaiseEvents(inventoryController, CommandStatus.Begin);
operation.Value.RaiseEvents(inventoryController, CommandStatus.Succeed);
}
}
}

View File

@@ -8,98 +8,97 @@ using HarmonyLib;
using SPT.Reflection.Patching;
using System.Reflection;
namespace UIFixes
namespace UIFixes;
public static class QuickAccessPanelPatches
{
public static class QuickAccessPanelPatches
public static void Enable()
{
public static void Enable()
new FixWeaponBindsDisplayPatch().Enable();
new FixVisibilityPatch().Enable();
new TranslateCommandHackPatch().Enable();
}
public class FixWeaponBindsDisplayPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
new FixWeaponBindsDisplayPatch().Enable();
new FixVisibilityPatch().Enable();
new TranslateCommandHackPatch().Enable();
return AccessTools.Method(R.ControlSettings.Type, "GetBoundItemNames");
}
public class FixWeaponBindsDisplayPatch : ModulePatch
[PatchPostfix]
public static void Postfix(object __instance, EBoundItem boundItem, ref string __result)
{
protected override MethodBase GetTargetMethod()
var instance = new R.ControlSettings(__instance);
switch (boundItem)
{
return AccessTools.Method(R.ControlSettings.Type, "GetBoundItemNames");
}
[PatchPostfix]
public static void Postfix(object __instance, EBoundItem boundItem, ref string __result)
{
var instance = new R.ControlSettings(__instance);
switch (boundItem)
{
case EBoundItem.Item1:
__result = instance.GetKeyName(EGameKey.SecondaryWeapon);
break;
case EBoundItem.Item2:
__result = instance.GetKeyName(EGameKey.PrimaryWeaponFirst);
break;
case EBoundItem.Item3:
__result = instance.GetKeyName(EGameKey.PrimaryWeaponSecond);
break;
}
}
}
public class FixVisibilityPatch : ModulePatch
{
public static bool Ignorable = false;
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(InventoryScreenQuickAccessPanel), nameof(InventoryScreenQuickAccessPanel.method_4));
}
// This method is a mess. The visibility setting has to be ignored in some cases, respected in others
// In most calls, visible=true must be followed regardless of setting preference, e.g. mag selection
// When coming from translatecommand, which is when you hit a quickbind key, visible=true can be ignored if the setting is never
// Ironically this is also the only time that autohide matters, since the other places will explicitly call hide
// visible=false can always be ignored if setting is always
[PatchPrefix]
public static bool Prefix(InventoryScreenQuickAccessPanel __instance, bool visible)
{
GameSetting<EVisibilityMode> quickSlotsVisibility = Singleton<SharedGameSettingsClass>.Instance.Game.Settings.QuickSlotsVisibility;
bool shouldShow = visible && !__instance.IsDisabled;
bool blocked = Ignorable && quickSlotsVisibility == EVisibilityMode.Never;
if (shouldShow && !blocked)
{
bool autohide = Ignorable && quickSlotsVisibility == EVisibilityMode.Autohide;
__instance.AnimatedShow(autohide);
}
else if (!shouldShow && quickSlotsVisibility != EVisibilityMode.Always)
{
__instance.AnimatedHide();
}
return false;
}
}
public class TranslateCommandHackPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(InventoryScreenQuickAccessPanel), nameof(InventoryScreenQuickAccessPanel.TranslateCommand));
}
[PatchPrefix]
public static void Prefix(ECommand command)
{
FixVisibilityPatch.Ignorable = QuickBindCommandMap.SlotBySelectCommandDictionary.ContainsKey(command);
}
[PatchPostfix]
public static void Postfix()
{
FixVisibilityPatch.Ignorable = false;
case EBoundItem.Item1:
__result = instance.GetKeyName(EGameKey.SecondaryWeapon);
break;
case EBoundItem.Item2:
__result = instance.GetKeyName(EGameKey.PrimaryWeaponFirst);
break;
case EBoundItem.Item3:
__result = instance.GetKeyName(EGameKey.PrimaryWeaponSecond);
break;
}
}
}
public class FixVisibilityPatch : ModulePatch
{
public static bool Ignorable = false;
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(InventoryScreenQuickAccessPanel), nameof(InventoryScreenQuickAccessPanel.method_4));
}
// This method is a mess. The visibility setting has to be ignored in some cases, respected in others
// In most calls, visible=true must be followed regardless of setting preference, e.g. mag selection
// When coming from translatecommand, which is when you hit a quickbind key, visible=true can be ignored if the setting is never
// Ironically this is also the only time that autohide matters, since the other places will explicitly call hide
// visible=false can always be ignored if setting is always
[PatchPrefix]
public static bool Prefix(InventoryScreenQuickAccessPanel __instance, bool visible)
{
GameSetting<EVisibilityMode> quickSlotsVisibility = Singleton<SharedGameSettingsClass>.Instance.Game.Settings.QuickSlotsVisibility;
bool shouldShow = visible && !__instance.IsDisabled;
bool blocked = Ignorable && quickSlotsVisibility == EVisibilityMode.Never;
if (shouldShow && !blocked)
{
bool autohide = Ignorable && quickSlotsVisibility == EVisibilityMode.Autohide;
__instance.AnimatedShow(autohide);
}
else if (!shouldShow && quickSlotsVisibility != EVisibilityMode.Always)
{
__instance.AnimatedHide();
}
return false;
}
}
public class TranslateCommandHackPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(InventoryScreenQuickAccessPanel), nameof(InventoryScreenQuickAccessPanel.TranslateCommand));
}
[PatchPrefix]
public static void Prefix(ECommand command)
{
FixVisibilityPatch.Ignorable = QuickBindCommandMap.SlotBySelectCommandDictionary.ContainsKey(command);
}
[PatchPostfix]
public static void Postfix()
{
FixVisibilityPatch.Ignorable = false;
}
}
}

View File

@@ -7,41 +7,40 @@ using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace UIFixes
{
public class RebindGrenadesPatch : ModulePatch
{
private static readonly EquipmentSlot[] Slots = [EquipmentSlot.Pockets, EquipmentSlot.TacticalVest, EquipmentSlot.Backpack, EquipmentSlot.SecuredContainer, EquipmentSlot.ArmBand];
namespace UIFixes;
protected override MethodBase GetTargetMethod()
public class RebindGrenadesPatch : ModulePatch
{
private static readonly EquipmentSlot[] Slots = [EquipmentSlot.Pockets, EquipmentSlot.TacticalVest, EquipmentSlot.Backpack, EquipmentSlot.SecuredContainer, EquipmentSlot.ArmBand];
protected override MethodBase GetTargetMethod()
{
Type type = typeof(Player).GetNestedTypes().Single(t => t.GetField("DiscardResult") != null);
return AccessTools.Method(type, "RaiseEvents");
}
// This is a grenade specific event emitter that has all the info needed to do this
[PatchPostfix]
public static void Postfix(CommandStatus status, DiscardResult ___DiscardResult)
{
if (status != CommandStatus.Succeed)
{
Type type = typeof(Player).GetNestedTypes().Single(t => t.GetField("DiscardResult") != null);
return AccessTools.Method(type, "RaiseEvents");
return;
}
// This is a grenade specific event emitter that has all the info needed to do this
[PatchPostfix]
public static void Postfix(CommandStatus status, DiscardResult ___DiscardResult)
var unbindResult = ___DiscardResult.UnbindResults.FirstOrDefault();
if (unbindResult != null)
{
if (status != CommandStatus.Succeed)
InventoryControllerClass controller = unbindResult.Controller;
EBoundItem index = unbindResult.Index;
List<GrenadeClass> matchingGrenades = [];
controller.GetAcceptableItemsNonAlloc<GrenadeClass>(Slots, matchingGrenades, g => g.TemplateId == unbindResult.Item.TemplateId);
var nextGrenade = matchingGrenades.FirstOrDefault(g => controller.IsAtBindablePlace(g));
if (nextGrenade != null)
{
return;
}
var unbindResult = ___DiscardResult.UnbindResults.FirstOrDefault();
if (unbindResult != null)
{
InventoryControllerClass controller = unbindResult.Controller;
EBoundItem index = unbindResult.Index;
List<GrenadeClass> matchingGrenades = [];
controller.GetAcceptableItemsNonAlloc<GrenadeClass>(Slots, matchingGrenades, g => g.TemplateId == unbindResult.Item.TemplateId);
var nextGrenade = matchingGrenades.FirstOrDefault(g => controller.IsAtBindablePlace(g));
if (nextGrenade != null)
{
controller.TryRunNetworkTransaction(BindOperation.Run(controller, nextGrenade, index, true), null);
}
controller.TryRunNetworkTransaction(BindOperation.Run(controller, nextGrenade, index, true), null);
}
}
}

View File

@@ -4,49 +4,48 @@ using SPT.Reflection.Patching;
using System.Reflection;
using UnityEngine;
namespace UIFixes
namespace UIFixes;
public static class RememberRepairerPatches
{
public static class RememberRepairerPatches
public static void Enable()
{
public static void Enable()
new RememberRepairerPatch().Enable();
new DefaultMaxRepairPatch().Enable();
}
public class RememberRepairerPatch : ModulePatch
{
private static readonly string PlayerPrefKey = "UIFixes.Repair.CurrentRepairerIndex";
protected override MethodBase GetTargetMethod()
{
new RememberRepairerPatch().Enable();
new DefaultMaxRepairPatch().Enable();
return AccessTools.Method(typeof(RepairerParametersPanel), nameof(RepairerParametersPanel.Show));
}
public class RememberRepairerPatch : ModulePatch
[PatchPostfix]
public static void Postfix(RepairerParametersPanel __instance, DropDownBox ____tradersDropDown)
{
private static readonly string PlayerPrefKey = "UIFixes.Repair.CurrentRepairerIndex";
__instance.R().UI.AddDisposable(____tradersDropDown.OnValueChanged.Subscribe(index => PlayerPrefs.SetInt(PlayerPrefKey, index)));
protected override MethodBase GetTargetMethod()
if (PlayerPrefs.HasKey(PlayerPrefKey))
{
return AccessTools.Method(typeof(RepairerParametersPanel), nameof(RepairerParametersPanel.Show));
}
[PatchPostfix]
public static void Postfix(RepairerParametersPanel __instance, DropDownBox ____tradersDropDown)
{
__instance.R().UI.AddDisposable(____tradersDropDown.OnValueChanged.Subscribe(index => PlayerPrefs.SetInt(PlayerPrefKey, index)));
if (PlayerPrefs.HasKey(PlayerPrefKey))
{
____tradersDropDown.UpdateValue(PlayerPrefs.GetInt(PlayerPrefKey));
}
}
}
public class DefaultMaxRepairPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(RepairerParametersPanel), nameof(RepairerParametersPanel.method_3));
}
[PatchPostfix]
public static void Postfix(ConditionCharacteristicsSlider ____conditionSlider)
{
____conditionSlider.method_1(); // like clicking >>, aka select max value
____tradersDropDown.UpdateValue(PlayerPrefs.GetInt(PlayerPrefKey));
}
}
}
public class DefaultMaxRepairPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(RepairerParametersPanel), nameof(RepairerParametersPanel.method_3));
}
[PatchPostfix]
public static void Postfix(ConditionCharacteristicsSlider ____conditionSlider)
{
____conditionSlider.method_1(); // like clicking >>, aka select max value
}
}
}

View File

@@ -4,33 +4,32 @@ using System;
using System.Linq;
using System.Reflection;
namespace UIFixes
namespace UIFixes;
public class RemoveDoorActionsPatch : ModulePatch
{
public class RemoveDoorActionsPatch : ModulePatch
private static readonly string[] UnimplementedActions = ["Bang & clear", "Flash & clear", "Move in"];
protected override MethodBase GetTargetMethod()
{
private static readonly string[] UnimplementedActions = ["Bang & clear", "Flash & clear", "Move in"];
protected override MethodBase GetTargetMethod()
Type type = typeof(GetActionsClass);
return AccessTools.GetDeclaredMethods(type).FirstOrDefault(x =>
{
Type type = typeof(GetActionsClass);
return AccessTools.GetDeclaredMethods(type).FirstOrDefault(x =>
{
var parameters = x.GetParameters();
return x.Name == nameof(GetActionsClass.GetAvailableActions) && parameters[0].Name == "owner";
});
}
var parameters = x.GetParameters();
return x.Name == nameof(GetActionsClass.GetAvailableActions) && parameters[0].Name == "owner";
});
}
[PatchPostfix]
public static void Postfix(ref ActionsReturnClass __result)
[PatchPostfix]
public static void Postfix(ref ActionsReturnClass __result)
{
if (Settings.RemoveDisabledActions.Value && __result != null)
{
if (Settings.RemoveDisabledActions.Value && __result != null)
for (int i = __result.Actions.Count - 1; i >= 0; i--)
{
for (int i = __result.Actions.Count - 1; i >= 0; i--)
if (UnimplementedActions.Contains(__result.Actions[i].Name))
{
if (UnimplementedActions.Contains(__result.Actions[i].Name))
{
__result.Actions.RemoveAt(i);
}
__result.Actions.RemoveAt(i);
}
}
}

View File

@@ -7,137 +7,136 @@ using System.Linq;
using System.Reflection;
using UnityEngine;
namespace UIFixes
namespace UIFixes;
public static class ReorderGridsPatches
{
public static class ReorderGridsPatches
public static void Enable()
{
public static void Enable()
new ReorderGridsPatch().Enable();
}
/* There are 3 cases to handle in TemplatedGridsView.Show
* 1. An item is shown for the first time
* - It renders on its own, and the UI is correct
* - Use the UI to sort Grids, and update GridViews to match
* 2. An item is shown for the 2nd+ time in a new context
* - The GridViews will be recreated, so in the prefix we need to reorder them to match the Grids order
* 3. An existing TemplatedGridsView is reshown
* - Everything is already in place, no action needed
*/
public class ReorderGridsPatch : ModulePatch
{
private static readonly Dictionary<string, int[]> GridMaps = [];
protected override MethodBase GetTargetMethod()
{
new ReorderGridsPatch().Enable();
return AccessTools.DeclaredMethod(typeof(TemplatedGridsView), nameof(TemplatedGridsView.Show));
}
/* There are 3 cases to handle in TemplatedGridsView.Show
* 1. An item is shown for the first time
* - It renders on its own, and the UI is correct
* - Use the UI to sort Grids, and update GridViews to match
* 2. An item is shown for the 2nd+ time in a new context
* - The GridViews will be recreated, so in the prefix we need to reorder them to match the Grids order
* 3. An existing TemplatedGridsView is reshown
* - Everything is already in place, no action needed
*/
public class ReorderGridsPatch : ModulePatch
[PatchPrefix]
public static void Prefix(TemplatedGridsView __instance, LootItemClass compoundItem, ref GridView[] ____presetGridViews)
{
private static readonly Dictionary<string, int[]> GridMaps = [];
protected override MethodBase GetTargetMethod()
if (!Settings.ReorderGrids.Value)
{
return AccessTools.DeclaredMethod(typeof(TemplatedGridsView), nameof(TemplatedGridsView.Show));
}
[PatchPrefix]
public static void Prefix(TemplatedGridsView __instance, LootItemClass compoundItem, ref GridView[] ____presetGridViews)
{
if (!Settings.ReorderGrids.Value)
// To properly support disabling this feature:
// 1. Items that sorted their Grids need to return them to original order
// 2. If this TemplatedGridsView was sorted, it needs to be unsorted to match
if (compoundItem.GetReordered() && GridMaps.TryGetValue(compoundItem.TemplateId, out int[] unwantedMap))
{
// To properly support disabling this feature:
// 1. Items that sorted their Grids need to return them to original order
// 2. If this TemplatedGridsView was sorted, it needs to be unsorted to match
if (compoundItem.GetReordered() && GridMaps.TryGetValue(compoundItem.TemplateId, out int[] unwantedMap))
StashGridClass[] orderedGrids = new StashGridClass[compoundItem.Grids.Length];
for (int i = 0; i < compoundItem.Grids.Length; i++)
{
StashGridClass[] orderedGrids = new StashGridClass[compoundItem.Grids.Length];
for (int i = 0; i < compoundItem.Grids.Length; i++)
{
orderedGrids[i] = compoundItem.Grids[unwantedMap[i]];
}
compoundItem.Grids = orderedGrids;
compoundItem.SetReordered(false);
if (__instance.GetReordered())
{
GridView[] orderedGridView = new GridView[____presetGridViews.Length];
for (int i = 0; i < ____presetGridViews.Length; i++)
{
orderedGridView[i] = ____presetGridViews[unwantedMap[i]];
}
____presetGridViews = orderedGridView;
__instance.SetReordered(false);
}
orderedGrids[i] = compoundItem.Grids[unwantedMap[i]];
}
return;
}
compoundItem.Grids = orderedGrids;
compoundItem.SetReordered(false);
if (compoundItem.GetReordered() && !__instance.GetReordered())
{
// This is a new context of a sorted Item, need to presort the GridViews
if (GridMaps.TryGetValue(compoundItem.TemplateId, out int[] map))
if (__instance.GetReordered())
{
GridView[] orderedGridView = new GridView[____presetGridViews.Length];
for (int i = 0; i < ____presetGridViews.Length; i++)
{
orderedGridView[map[i]] = ____presetGridViews[i];
orderedGridView[i] = ____presetGridViews[unwantedMap[i]];
}
____presetGridViews = orderedGridView;
__instance.SetReordered(true);
}
else
{
Logger.LogError($"Item {compoundItem.Id}, tpl: {compoundItem.TemplateId} has sorted Grids but no map to sort GridViews!");
__instance.SetReordered(false);
}
}
return;
}
[PatchPostfix]
public static void Postfix(TemplatedGridsView __instance, LootItemClass compoundItem, ref GridView[] ____presetGridViews)
if (compoundItem.GetReordered() && !__instance.GetReordered())
{
if (!Settings.ReorderGrids.Value || compoundItem.GetReordered())
// This is a new context of a sorted Item, need to presort the GridViews
if (GridMaps.TryGetValue(compoundItem.TemplateId, out int[] map))
{
return;
}
var pairs = compoundItem.Grids.Zip(____presetGridViews, (g, gv) => new KeyValuePair<StashGridClass, GridView>(g, gv));
RectTransform parentView = __instance.RectTransform();
Vector2 parentPosition = parentView.pivot.y == 1 ? parentView.position : new Vector2(parentView.position.x, parentView.position.y + parentView.sizeDelta.y);
Vector2 gridSize = new(64f * parentView.lossyScale.x, 64f * parentView.lossyScale.y);
var sorted = pairs.OrderBy(pair =>
{
var grid = pair.Key;
var gridView = pair.Value;
float xOffset = gridView.transform.position.x - parentPosition.x;
float yOffset = -(gridView.transform.position.y - parentPosition.y); // invert y since grid coords are upper-left origin
int x = (int)Math.Round(xOffset / gridSize.x, MidpointRounding.AwayFromZero);
int y = (int)Math.Round(yOffset / gridSize.y, MidpointRounding.AwayFromZero);
return y * 100 + x;
});
GridView[] orderedGridViews = sorted.Select(pair => pair.Value).ToArray();
// Populate the gridmap
if (!GridMaps.ContainsKey(compoundItem.TemplateId))
{
int[] map = new int[____presetGridViews.Length];
GridView[] orderedGridView = new GridView[____presetGridViews.Length];
for (int i = 0; i < ____presetGridViews.Length; i++)
{
map[i] = orderedGridViews.IndexOf(____presetGridViews[i]);
orderedGridView[map[i]] = ____presetGridViews[i];
}
GridMaps.Add(compoundItem.TemplateId, map);
____presetGridViews = orderedGridView;
__instance.SetReordered(true);
}
else
{
Logger.LogError($"Item {compoundItem.Id}, tpl: {compoundItem.TemplateId} has sorted Grids but no map to sort GridViews!");
}
}
}
[PatchPostfix]
public static void Postfix(TemplatedGridsView __instance, LootItemClass compoundItem, ref GridView[] ____presetGridViews)
{
if (!Settings.ReorderGrids.Value || compoundItem.GetReordered())
{
return;
}
var pairs = compoundItem.Grids.Zip(____presetGridViews, (g, gv) => new KeyValuePair<StashGridClass, GridView>(g, gv));
RectTransform parentView = __instance.RectTransform();
Vector2 parentPosition = parentView.pivot.y == 1 ? parentView.position : new Vector2(parentView.position.x, parentView.position.y + parentView.sizeDelta.y);
Vector2 gridSize = new(64f * parentView.lossyScale.x, 64f * parentView.lossyScale.y);
var sorted = pairs.OrderBy(pair =>
{
var grid = pair.Key;
var gridView = pair.Value;
float xOffset = gridView.transform.position.x - parentPosition.x;
float yOffset = -(gridView.transform.position.y - parentPosition.y); // invert y since grid coords are upper-left origin
int x = (int)Math.Round(xOffset / gridSize.x, MidpointRounding.AwayFromZero);
int y = (int)Math.Round(yOffset / gridSize.y, MidpointRounding.AwayFromZero);
return y * 100 + x;
});
GridView[] orderedGridViews = sorted.Select(pair => pair.Value).ToArray();
// Populate the gridmap
if (!GridMaps.ContainsKey(compoundItem.TemplateId))
{
int[] map = new int[____presetGridViews.Length];
for (int i = 0; i < ____presetGridViews.Length; i++)
{
map[i] = orderedGridViews.IndexOf(____presetGridViews[i]);
}
compoundItem.Grids = sorted.Select(pair => pair.Key).ToArray();
____presetGridViews = orderedGridViews;
compoundItem.SetReordered(true);
__instance.SetReordered(true);
GridMaps.Add(compoundItem.TemplateId, map);
}
compoundItem.Grids = sorted.Select(pair => pair.Key).ToArray();
____presetGridViews = orderedGridViews;
compoundItem.SetReordered(true);
__instance.SetReordered(true);
}
}
}

View File

@@ -13,353 +13,352 @@ using UnityEngine.Events;
using UnityEngine.EventSystems;
using UnityEngine.UI;
namespace UIFixes
namespace UIFixes;
public static class ScrollPatches
{
public static class ScrollPatches
public static void Enable()
{
public static void Enable()
{
new EnhanceStashScrollingPatch().Enable();
new EnchanceTraderStashScrollingPatch().Enable();
new EnhanceFleaScrollingPatch().Enable();
new EnhanceMailScrollingPatch().Enable();
new MouseScrollingSpeedPatch().Enable();
new EnhanceHideoutScrollingPatch().Enable();
new EnhanceTaskListScrollingPatch().Enable();
new OpenLastTaskPatch().Enable();
}
new EnhanceStashScrollingPatch().Enable();
new EnchanceTraderStashScrollingPatch().Enable();
new EnhanceFleaScrollingPatch().Enable();
new EnhanceMailScrollingPatch().Enable();
new MouseScrollingSpeedPatch().Enable();
new EnhanceHideoutScrollingPatch().Enable();
new EnhanceTaskListScrollingPatch().Enable();
new OpenLastTaskPatch().Enable();
}
private static bool HandleInput(ScrollRect scrollRect)
private static bool HandleInput(ScrollRect scrollRect)
{
if (scrollRect != null)
{
if (scrollRect != null)
if (Settings.UseHomeEnd.Value)
{
if (Settings.UseHomeEnd.Value)
if (Input.GetKeyDown(KeyCode.Home))
{
if (Input.GetKeyDown(KeyCode.Home))
{
scrollRect.verticalNormalizedPosition = 1f;
return true;
}
if (Input.GetKeyDown(KeyCode.End))
{
scrollRect.verticalNormalizedPosition = 0f;
return true;
}
scrollRect.verticalNormalizedPosition = 1f;
return true;
}
if (Settings.RebindPageUpDown.Value)
if (Input.GetKeyDown(KeyCode.End))
{
if (Input.GetKeyDown(KeyCode.PageUp))
{
// Duplicate this code to avoid running it every frame
Rect contentRect = scrollRect.content.rect;
Rect viewRect = scrollRect.RectTransform().rect;
float pageSize = viewRect.height / contentRect.height;
scrollRect.verticalNormalizedPosition = Math.Min(1f, scrollRect.verticalNormalizedPosition + pageSize);
return true;
}
if (Input.GetKeyDown(KeyCode.PageDown))
{
// Duplicate this code to avoid running it every frame
Rect contentRect = scrollRect.content.rect;
Rect viewRect = scrollRect.RectTransform().rect;
float pageSize = viewRect.height / contentRect.height;
scrollRect.verticalNormalizedPosition = Math.Max(0f, scrollRect.verticalNormalizedPosition - pageSize);
return true;
}
scrollRect.verticalNormalizedPosition = 0f;
return true;
}
}
return false;
}
// LightScrollers don't expose heights that I can see, so just fudge it with fake OnScroll events
private static bool HandleInput(LightScroller lightScroller)
{
if (lightScroller != null)
if (Settings.RebindPageUpDown.Value)
{
if (Settings.UseHomeEnd.Value)
if (Input.GetKeyDown(KeyCode.PageUp))
{
if (Input.GetKeyDown(KeyCode.Home))
{
lightScroller.SetScrollPosition(0f);
return true;
}
if (Input.GetKeyDown(KeyCode.End))
{
lightScroller.SetScrollPosition(1f);
return true;
}
// Duplicate this code to avoid running it every frame
Rect contentRect = scrollRect.content.rect;
Rect viewRect = scrollRect.RectTransform().rect;
float pageSize = viewRect.height / contentRect.height;
scrollRect.verticalNormalizedPosition = Math.Min(1f, scrollRect.verticalNormalizedPosition + pageSize);
return true;
}
if (Settings.RebindPageUpDown.Value)
if (Input.GetKeyDown(KeyCode.PageDown))
{
if (Input.GetKeyDown(KeyCode.PageUp))
{
var eventData = new PointerEventData(EventSystem.current)
{
scrollDelta = new Vector2(0f, 25f)
};
lightScroller.OnScroll(eventData);
return true;
}
if (Input.GetKeyDown(KeyCode.PageDown))
{
var eventData = new PointerEventData(EventSystem.current)
{
scrollDelta = new Vector2(0f, -25f)
};
lightScroller.OnScroll(eventData);
return true;
}
// Duplicate this code to avoid running it every frame
Rect contentRect = scrollRect.content.rect;
Rect viewRect = scrollRect.RectTransform().rect;
float pageSize = viewRect.height / contentRect.height;
scrollRect.verticalNormalizedPosition = Math.Max(0f, scrollRect.verticalNormalizedPosition - pageSize);
return true;
}
}
}
return false;
}
// LightScrollers don't expose heights that I can see, so just fudge it with fake OnScroll events
private static bool HandleInput(LightScroller lightScroller)
{
if (lightScroller != null)
{
if (Settings.UseHomeEnd.Value)
{
if (Input.GetKeyDown(KeyCode.Home))
{
lightScroller.SetScrollPosition(0f);
return true;
}
if (Input.GetKeyDown(KeyCode.End))
{
lightScroller.SetScrollPosition(1f);
return true;
}
}
return false;
}
private static IEnumerable<CodeInstruction> RemovePageUpDownHandling(IEnumerable<CodeInstruction> instructions)
{
foreach (var instruction in instructions)
if (Settings.RebindPageUpDown.Value)
{
if (instruction.LoadsConstant((int)KeyCode.PageUp))
if (Input.GetKeyDown(KeyCode.PageUp))
{
yield return new CodeInstruction(instruction)
var eventData = new PointerEventData(EventSystem.current)
{
operand = 0
scrollDelta = new Vector2(0f, 25f)
};
lightScroller.OnScroll(eventData);
return true;
}
else if (instruction.LoadsConstant((int)KeyCode.PageDown))
if (Input.GetKeyDown(KeyCode.PageDown))
{
yield return new CodeInstruction(instruction)
var eventData = new PointerEventData(EventSystem.current)
{
operand = 0
scrollDelta = new Vector2(0f, -25f)
};
lightScroller.OnScroll(eventData);
return true;
}
else
}
}
return false;
}
private static IEnumerable<CodeInstruction> RemovePageUpDownHandling(IEnumerable<CodeInstruction> instructions)
{
foreach (var instruction in instructions)
{
if (instruction.LoadsConstant((int)KeyCode.PageUp))
{
yield return new CodeInstruction(instruction)
{
yield return instruction;
}
operand = 0
};
}
}
public class KeyScrollListener : MonoBehaviour
{
private ScrollRect scrollRect;
public UnityEvent OnKeyScroll;
public void Awake()
else if (instruction.LoadsConstant((int)KeyCode.PageDown))
{
scrollRect = GetComponent<ScrollRect>();
OnKeyScroll = new();
}
public void Update()
{
if (HandleInput(scrollRect))
yield return new CodeInstruction(instruction)
{
OnKeyScroll.Invoke();
}
operand = 0
};
}
else
{
yield return instruction;
}
}
}
public class EnhanceStashScrollingPatch : ModulePatch
public class KeyScrollListener : MonoBehaviour
{
private ScrollRect scrollRect;
public UnityEvent OnKeyScroll;
public void Awake()
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(SimpleStashPanel), nameof(SimpleStashPanel.Update));
}
[PatchPrefix]
public static void Prefix(SimpleStashPanel __instance, ScrollRect ____stashScroll)
{
// For some reason, sometimes SimpleStashPanel doesn't have a reference to its own ScrollRect?
HandleInput(____stashScroll ?? __instance.GetComponentInChildren<ScrollRect>());
}
[PatchTranspiler]
public static IEnumerable<CodeInstruction> Transpile(IEnumerable<CodeInstruction> instructions)
{
if (Settings.RebindPageUpDown.Value)
{
return RemovePageUpDownHandling(instructions);
}
return instructions;
}
scrollRect = GetComponent<ScrollRect>();
OnKeyScroll = new();
}
public class EnchanceTraderStashScrollingPatch : ModulePatch
public void Update()
{
protected override MethodBase GetTargetMethod()
if (HandleInput(scrollRect))
{
return AccessTools.Method(typeof(TraderDealScreen), nameof(TraderDealScreen.Update));
}
[PatchPrefix]
public static void Prefix(TraderDealScreen.ETraderMode ___etraderMode_0, ScrollRect ____traderScroll, ScrollRect ____stashScroll)
{
HandleInput(___etraderMode_0 == TraderDealScreen.ETraderMode.Purchase ? ____traderScroll : ____stashScroll);
}
[PatchTranspiler]
public static IEnumerable<CodeInstruction> Transpile(IEnumerable<CodeInstruction> instructions)
{
if (Settings.RebindPageUpDown.Value)
{
return RemovePageUpDownHandling(instructions);
}
return instructions;
OnKeyScroll.Invoke();
}
}
}
public class EnhanceFleaScrollingPatch : ModulePatch
public class EnhanceStashScrollingPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(OfferViewList), nameof(OfferViewList.Update));
}
[PatchPrefix]
public static void Prefix(LightScroller ____scroller)
{
HandleInput(____scroller);
}
[PatchTranspiler]
private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
{
if (Settings.RebindPageUpDown.Value)
{
return RemovePageUpDownHandling(instructions);
}
return instructions;
}
return AccessTools.Method(typeof(SimpleStashPanel), nameof(SimpleStashPanel.Update));
}
public class EnhanceHideoutScrollingPatch : ModulePatch
[PatchPrefix]
public static void Prefix(SimpleStashPanel __instance, ScrollRect ____stashScroll)
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(AreaScreenSubstrate), nameof(AreaScreenSubstrate.Awake));
}
[PatchPostfix]
public static void Postfix(AreaScreenSubstrate __instance)
{
ScrollRect scrollRect = __instance.transform.Find("Content/CurrentLevel/CurrentContainer/Scrollview")?.GetComponent<ScrollRect>();
if (scrollRect == null)
{
return;
}
scrollRect.GetOrAddComponent<KeyScrollListener>();
}
// For some reason, sometimes SimpleStashPanel doesn't have a reference to its own ScrollRect?
HandleInput(____stashScroll ?? __instance.GetComponentInChildren<ScrollRect>());
}
public class EnhanceMailScrollingPatch : ModulePatch
[PatchTranspiler]
public static IEnumerable<CodeInstruction> Transpile(IEnumerable<CodeInstruction> instructions)
{
protected override MethodBase GetTargetMethod()
if (Settings.RebindPageUpDown.Value)
{
return AccessTools.Method(typeof(MessagesContainer), nameof(MessagesContainer.Update));
return RemovePageUpDownHandling(instructions);
}
[PatchPrefix]
public static void Prefix(LightScroller ____scroller)
{
HandleInput(____scroller);
}
return instructions;
}
}
[PatchTranspiler]
private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
{
if (Settings.RebindPageUpDown.Value)
{
return RemovePageUpDownHandling(instructions);
}
return instructions;
}
public class EnchanceTraderStashScrollingPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(TraderDealScreen), nameof(TraderDealScreen.Update));
}
public class MouseScrollingSpeedPatch : ModulePatch
[PatchPrefix]
public static void Prefix(TraderDealScreen.ETraderMode ___etraderMode_0, ScrollRect ____traderScroll, ScrollRect ____stashScroll)
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(ScrollRectNoDrag), nameof(ScrollRectNoDrag.OnScroll));
}
[PatchPrefix]
public static void Prefix(PointerEventData data)
{
int multi = Settings.UseRaidMouseScrollMulti.Value && Plugin.InRaid() ? Settings.MouseScrollMultiInRaid.Value : Settings.MouseScrollMulti.Value;
data.scrollDelta *= multi;
}
HandleInput(___etraderMode_0 == TraderDealScreen.ETraderMode.Purchase ? ____traderScroll : ____stashScroll);
}
public class EnhanceTaskListScrollingPatch : ModulePatch
[PatchTranspiler]
public static IEnumerable<CodeInstruction> Transpile(IEnumerable<CodeInstruction> instructions)
{
protected override MethodBase GetTargetMethod()
if (Settings.RebindPageUpDown.Value)
{
return AccessTools.Method(typeof(TasksScreen), nameof(TasksScreen.Awake));
return RemovePageUpDownHandling(instructions);
}
[PatchPostfix]
public static void Postfix(ScrollRect ____scrollRect)
{
var keyScroller = ____scrollRect.GetOrAddComponent<KeyScroller>();
keyScroller.Init(____scrollRect);
}
return instructions;
}
}
public class EnhanceFleaScrollingPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(OfferViewList), nameof(OfferViewList.Update));
}
public class KeyScroller : MonoBehaviour
[PatchPrefix]
public static void Prefix(LightScroller ____scroller)
{
ScrollRect scrollRect;
public void Init(ScrollRect scrollRect)
{
this.scrollRect = scrollRect;
}
public void Update()
{
HandleInput(scrollRect);
}
HandleInput(____scroller);
}
public class OpenLastTaskPatch : ModulePatch
[PatchTranspiler]
private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
{
private static string LastQuestId = null;
protected override MethodBase GetTargetMethod()
if (Settings.RebindPageUpDown.Value)
{
return AccessTools.Method(typeof(NotesTask), nameof(NotesTask.Show));
return RemovePageUpDownHandling(instructions);
}
[PatchPostfix]
public static void Postfix(NotesTask __instance, QuestClass quest)
return instructions;
}
}
public class EnhanceHideoutScrollingPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(AreaScreenSubstrate), nameof(AreaScreenSubstrate.Awake));
}
[PatchPostfix]
public static void Postfix(AreaScreenSubstrate __instance)
{
ScrollRect scrollRect = __instance.transform.Find("Content/CurrentLevel/CurrentContainer/Scrollview")?.GetComponent<ScrollRect>();
if (scrollRect == null)
{
void OnTaskSelected(bool open)
{
LastQuestId = open ? quest.Id : null;
}
return;
}
Toggle toggle = __instance.GetComponent<Toggle>();
toggle.onValueChanged.AddListener(OnTaskSelected);
__instance.R().UI.AddDisposable(() => toggle.onValueChanged.RemoveListener(OnTaskSelected));
scrollRect.GetOrAddComponent<KeyScrollListener>();
}
}
if (quest.Id == LastQuestId)
{
toggle.isOn = true;
}
public class EnhanceMailScrollingPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(MessagesContainer), nameof(MessagesContainer.Update));
}
[PatchPrefix]
public static void Prefix(LightScroller ____scroller)
{
HandleInput(____scroller);
}
[PatchTranspiler]
private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
{
if (Settings.RebindPageUpDown.Value)
{
return RemovePageUpDownHandling(instructions);
}
return instructions;
}
}
public class MouseScrollingSpeedPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(ScrollRectNoDrag), nameof(ScrollRectNoDrag.OnScroll));
}
[PatchPrefix]
public static void Prefix(PointerEventData data)
{
int multi = Settings.UseRaidMouseScrollMulti.Value && Plugin.InRaid() ? Settings.MouseScrollMultiInRaid.Value : Settings.MouseScrollMulti.Value;
data.scrollDelta *= multi;
}
}
public class EnhanceTaskListScrollingPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(TasksScreen), nameof(TasksScreen.Awake));
}
[PatchPostfix]
public static void Postfix(ScrollRect ____scrollRect)
{
var keyScroller = ____scrollRect.GetOrAddComponent<KeyScroller>();
keyScroller.Init(____scrollRect);
}
}
public class KeyScroller : MonoBehaviour
{
ScrollRect scrollRect;
public void Init(ScrollRect scrollRect)
{
this.scrollRect = scrollRect;
}
public void Update()
{
HandleInput(scrollRect);
}
}
public class OpenLastTaskPatch : ModulePatch
{
private static string LastQuestId = null;
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(NotesTask), nameof(NotesTask.Show));
}
[PatchPostfix]
public static void Postfix(NotesTask __instance, QuestClass quest)
{
void OnTaskSelected(bool open)
{
LastQuestId = open ? quest.Id : null;
}
Toggle toggle = __instance.GetComponent<Toggle>();
toggle.onValueChanged.AddListener(OnTaskSelected);
__instance.R().UI.AddDisposable(() => toggle.onValueChanged.RemoveListener(OnTaskSelected));
if (quest.Id == LastQuestId)
{
toggle.isOn = true;
}
}
}

View File

@@ -6,96 +6,95 @@ using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace UIFixes
namespace UIFixes;
public static class StackFirItemsPatches
{
public static class StackFirItemsPatches
public static void Enable()
{
public static void Enable()
new ContainerStackPatch().Enable();
new TopUpStackPatch().Enable();
}
public class ContainerStackPatch : ModulePatch
{
private static Type MergeableItemType;
protected override MethodBase GetTargetMethod()
{
new ContainerStackPatch().Enable();
new TopUpStackPatch().Enable();
MethodInfo method = AccessTools.Method(typeof(InteractionsHandlerClass), nameof(InteractionsHandlerClass.smethod_0));
MergeableItemType = method.GetParameters()[2].ParameterType.GetElementType(); // parameter is a ref type, get underlying type, GClass2751
return method;
}
public class ContainerStackPatch : ModulePatch
// Reimplementing this entire method to ignore SpawnedInSession for certain types
[PatchPrefix]
public static bool Prefix(IEnumerable<EFT.InventoryLogic.IContainer> containersToPut, Item itemToMerge, ref object mergeableItem, int overrideCount, ref bool __result)
{
private static Type MergeableItemType;
protected override MethodBase GetTargetMethod()
if (!MergeableItemType.IsInstanceOfType(itemToMerge))
{
MethodInfo method = AccessTools.Method(typeof(InteractionsHandlerClass), nameof(InteractionsHandlerClass.smethod_0));
MergeableItemType = method.GetParameters()[2].ParameterType.GetElementType(); // parameter is a ref type, get underlying type, GClass2751
return method;
mergeableItem = null;
__result = false;
}
// Reimplementing this entire method to ignore SpawnedInSession for certain types
[PatchPrefix]
public static bool Prefix(IEnumerable<EFT.InventoryLogic.IContainer> containersToPut, Item itemToMerge, ref object mergeableItem, int overrideCount, ref bool __result)
if (overrideCount <= 0)
{
if (!MergeableItemType.IsInstanceOfType(itemToMerge))
{
mergeableItem = null;
__result = false;
}
if (overrideCount <= 0)
{
overrideCount = itemToMerge.StackObjectsCount;
}
bool ignoreSpawnedInSession;
if (itemToMerge.Template is MoneyClass)
{
ignoreSpawnedInSession = Settings.MergeFIRMoney.Value;
}
else if (itemToMerge.Template is AmmoTemplate)
{
ignoreSpawnedInSession = Settings.MergeFIRAmmo.Value;
}
else
{
ignoreSpawnedInSession = Settings.MergeFIROther.Value;
}
mergeableItem = containersToPut.SelectMany(x => x.Items)
.Where(MergeableItemType.IsInstanceOfType)
.Where(x => x != itemToMerge)
.Where(x => x.TemplateId == itemToMerge.TemplateId)
.Where(x => ignoreSpawnedInSession || x.SpawnedInSession == itemToMerge.SpawnedInSession)
.Where(x => x.StackObjectsCount < x.StackMaxSize)
.FirstOrDefault(x => overrideCount <= x.StackMaxSize - x.StackObjectsCount);
__result = mergeableItem != null;
return false;
overrideCount = itemToMerge.StackObjectsCount;
}
bool ignoreSpawnedInSession;
if (itemToMerge.Template is MoneyClass)
{
ignoreSpawnedInSession = Settings.MergeFIRMoney.Value;
}
else if (itemToMerge.Template is AmmoTemplate)
{
ignoreSpawnedInSession = Settings.MergeFIRAmmo.Value;
}
else
{
ignoreSpawnedInSession = Settings.MergeFIROther.Value;
}
mergeableItem = containersToPut.SelectMany(x => x.Items)
.Where(MergeableItemType.IsInstanceOfType)
.Where(x => x != itemToMerge)
.Where(x => x.TemplateId == itemToMerge.TemplateId)
.Where(x => ignoreSpawnedInSession || x.SpawnedInSession == itemToMerge.SpawnedInSession)
.Where(x => x.StackObjectsCount < x.StackMaxSize)
.FirstOrDefault(x => overrideCount <= x.StackMaxSize - x.StackObjectsCount);
__result = mergeableItem != null;
return false;
}
}
public class TopUpStackPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(Item), nameof(Item.IsSameItem));
}
public class TopUpStackPatch : ModulePatch
[PatchPrefix]
public static bool Prefix(Item __instance, Item other, ref bool __result)
{
protected override MethodBase GetTargetMethod()
bool ignoreSpawnedInSession;
if (__instance.Template is MoneyClass)
{
return AccessTools.Method(typeof(Item), nameof(Item.IsSameItem));
ignoreSpawnedInSession = Settings.MergeFIRMoney.Value;
}
else if (__instance.Template is AmmoTemplate)
{
ignoreSpawnedInSession = Settings.MergeFIRAmmo.Value;
}
else
{
ignoreSpawnedInSession = Settings.MergeFIROther.Value;
}
[PatchPrefix]
public static bool Prefix(Item __instance, Item other, ref bool __result)
{
bool ignoreSpawnedInSession;
if (__instance.Template is MoneyClass)
{
ignoreSpawnedInSession = Settings.MergeFIRMoney.Value;
}
else if (__instance.Template is AmmoTemplate)
{
ignoreSpawnedInSession = Settings.MergeFIRAmmo.Value;
}
else
{
ignoreSpawnedInSession = Settings.MergeFIROther.Value;
}
__result = __instance.TemplateId == other.TemplateId && __instance.Id != other.Id && (ignoreSpawnedInSession || __instance.SpawnedInSession == other.SpawnedInSession);
return false;
}
__result = __instance.TemplateId == other.TemplateId && __instance.Id != other.Id && (ignoreSpawnedInSession || __instance.SpawnedInSession == other.SpawnedInSession);
return false;
}
}
}

View File

@@ -6,84 +6,83 @@ using System.Threading;
using System.Threading.Tasks;
using UnityEngine;
namespace UIFixes
namespace UIFixes;
public static class StackMoveGreedyPatches
{
public static class StackMoveGreedyPatches
private static bool InPatch = false;
public static void Enable()
{
private static bool InPatch = false;
new GridViewPatch().Enable();
new SlotViewPatch().Enable();
}
public static void Enable()
public class GridViewPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
new GridViewPatch().Enable();
new SlotViewPatch().Enable();
return AccessTools.Method(typeof(GridView), nameof(GridView.AcceptItem));
}
public class GridViewPatch : ModulePatch
[PatchPrefix]
[HarmonyPriority(Priority.LowerThanNormal)]
public static bool Prefix(GridView __instance, DragItemContext itemContext, ItemContextAbstractClass targetItemContext, ref Task __result)
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(GridView), nameof(GridView.AcceptItem));
}
[PatchPrefix]
[HarmonyPriority(Priority.LowerThanNormal)]
public static bool Prefix(GridView __instance, DragItemContext itemContext, ItemContextAbstractClass targetItemContext, ref Task __result)
{
return AcceptStackable(__instance, itemContext, targetItemContext, ref __result);
}
}
public class SlotViewPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(SlotView), nameof(SlotView.AcceptItem));
}
[PatchPrefix]
[HarmonyPriority(Priority.LowerThanNormal)]
public static bool Prefix(SlotView __instance, DragItemContext itemContext, ItemContextAbstractClass targetItemContext, ref Task __result)
{
return AcceptStackable(__instance, itemContext, targetItemContext, ref __result);
}
}
// Specific type of TaskSerializer because Unity can't understand generics
public class ItemContextTaskSerializer : TaskSerializer<DragItemContext> { }
private static bool AcceptStackable<T>(T __instance, DragItemContext itemContext, ItemContextAbstractClass targetItemContext, ref Task __result) where T : MonoBehaviour, IContainer
{
if (!Settings.GreedyStackMove.Value || InPatch || itemContext.Item.StackObjectsCount <= 1 || targetItemContext == null)
{
return true;
}
InPatch = true;
int stackCount = int.MaxValue;
var serializer = __instance.gameObject.AddComponent<ItemContextTaskSerializer>();
__result = serializer.Initialize(itemContext.RepeatUntilEmpty(), ic =>
{
if (ic.Item.StackObjectsCount >= stackCount)
{
// Nothing happened, bail out
return Task.FromCanceled(new CancellationToken(true));
}
stackCount = ic.Item.StackObjectsCount;
return __instance.AcceptItem(ic, targetItemContext);
});
// This won't block the first action from swapping, but will prevent follow up swaps
SwapPatches.BlockSwaps = true;
__result.ContinueWith(_ =>
{
InPatch = false;
SwapPatches.BlockSwaps = false;
});
return false;
return AcceptStackable(__instance, itemContext, targetItemContext, ref __result);
}
}
public class SlotViewPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(SlotView), nameof(SlotView.AcceptItem));
}
[PatchPrefix]
[HarmonyPriority(Priority.LowerThanNormal)]
public static bool Prefix(SlotView __instance, DragItemContext itemContext, ItemContextAbstractClass targetItemContext, ref Task __result)
{
return AcceptStackable(__instance, itemContext, targetItemContext, ref __result);
}
}
// Specific type of TaskSerializer because Unity can't understand generics
public class ItemContextTaskSerializer : TaskSerializer<DragItemContext> { }
private static bool AcceptStackable<T>(T __instance, DragItemContext itemContext, ItemContextAbstractClass targetItemContext, ref Task __result) where T : MonoBehaviour, IContainer
{
if (!Settings.GreedyStackMove.Value || InPatch || itemContext.Item.StackObjectsCount <= 1 || targetItemContext == null)
{
return true;
}
InPatch = true;
int stackCount = int.MaxValue;
var serializer = __instance.gameObject.AddComponent<ItemContextTaskSerializer>();
__result = serializer.Initialize(itemContext.RepeatUntilEmpty(), ic =>
{
if (ic.Item.StackObjectsCount >= stackCount)
{
// Nothing happened, bail out
return Task.FromCanceled(new CancellationToken(true));
}
stackCount = ic.Item.StackObjectsCount;
return __instance.AcceptItem(ic, targetItemContext);
});
// This won't block the first action from swapping, but will prevent follow up swaps
SwapPatches.BlockSwaps = true;
__result.ContinueWith(_ =>
{
InPatch = false;
SwapPatches.BlockSwaps = false;
});
return false;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -6,83 +6,82 @@ using System.Reflection;
using UnityEngine;
using UnityEngine.UI;
namespace UIFixes
namespace UIFixes;
public static class SyncScrollPositionPatches
{
public static class SyncScrollPositionPatches
private static float StashScrollPosition = 1f;
public static void Enable()
{
private static float StashScrollPosition = 1f;
new SyncStashScrollPatch().Enable();
new SyncTraderStashScrollPatch().Enable();
new SyncOfferStashScrollPatch().Enable();
}
public static void Enable()
private static void UpdateScrollPosition(Vector2 position)
{
StashScrollPosition = position.y;
}
private static void SynchronizeScrollRect(UIElement element, ScrollRect scrollRect = null)
{
if (!Settings.SynchronizeStashScrolling.Value || element == null || (scrollRect ??= element.GetComponentInChildren<ScrollRect>()) == null)
{
new SyncStashScrollPatch().Enable();
new SyncTraderStashScrollPatch().Enable();
new SyncOfferStashScrollPatch().Enable();
return;
}
private static void UpdateScrollPosition(Vector2 position)
scrollRect.verticalNormalizedPosition = StashScrollPosition;
scrollRect.onValueChanged.RemoveListener(UpdateScrollPosition);
scrollRect.onValueChanged.AddListener(UpdateScrollPosition);
//element.R().UI.AddDisposable(() => scrollRect.onValueChanged.RemoveListener(UpdateScrollPosition));
}
public class SyncStashScrollPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
StashScrollPosition = position.y;
return AccessTools.Method(typeof(SimpleStashPanel), nameof(SimpleStashPanel.Show));
}
private static void SynchronizeScrollRect(UIElement element, ScrollRect scrollRect = null)
[PatchPostfix]
public static void Postfix(SimpleStashPanel __instance)
{
if (!Settings.SynchronizeStashScrolling.Value || element == null || (scrollRect ??= element.GetComponentInChildren<ScrollRect>()) == null)
{
return;
}
SynchronizeScrollRect(__instance);
}
}
scrollRect.verticalNormalizedPosition = StashScrollPosition;
scrollRect.onValueChanged.RemoveListener(UpdateScrollPosition);
scrollRect.onValueChanged.AddListener(UpdateScrollPosition);
//element.R().UI.AddDisposable(() => scrollRect.onValueChanged.RemoveListener(UpdateScrollPosition));
public class SyncTraderStashScrollPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(TraderDealScreen), nameof(TraderDealScreen.method_3));
}
public class SyncStashScrollPatch : ModulePatch
// TraderDealScreen is a monstrosity that loads multiple times and isn't done loading when Show() is done
// method_3 shows the stash grid, if method_5() returned true
[PatchPostfix]
public static void Postfix(TraderDealScreen __instance, ScrollRect ____stashScroll)
{
protected override MethodBase GetTargetMethod()
if (__instance.method_5())
{
return AccessTools.Method(typeof(SimpleStashPanel), nameof(SimpleStashPanel.Show));
}
[PatchPostfix]
public static void Postfix(SimpleStashPanel __instance)
{
SynchronizeScrollRect(__instance);
}
}
public class SyncTraderStashScrollPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(TraderDealScreen), nameof(TraderDealScreen.method_3));
}
// TraderDealScreen is a monstrosity that loads multiple times and isn't done loading when Show() is done
// method_3 shows the stash grid, if method_5() returned true
[PatchPostfix]
public static void Postfix(TraderDealScreen __instance, ScrollRect ____stashScroll)
{
if (__instance.method_5())
{
SynchronizeScrollRect(__instance, ____stashScroll);
}
}
}
public class SyncOfferStashScrollPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(AddOfferWindow), nameof(AddOfferWindow.Show));
}
[PatchPostfix]
public static void Postfix(AddOfferWindow __instance)
{
SynchronizeScrollRect(__instance);
SynchronizeScrollRect(__instance, ____stashScroll);
}
}
}
public class SyncOfferStashScrollPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(AddOfferWindow), nameof(AddOfferWindow.Show));
}
[PatchPostfix]
public static void Postfix(AddOfferWindow __instance)
{
SynchronizeScrollRect(__instance);
}
}
}

View File

@@ -7,99 +7,98 @@ using System.Reflection;
using UnityEngine;
using UnityEngine.EventSystems;
namespace UIFixes
namespace UIFixes;
public static class TradingAutoSwitchPatches
{
public static class TradingAutoSwitchPatches
private static Tab BuyTab;
private static Tab SellTab;
public static void Enable()
{
private static Tab BuyTab;
private static Tab SellTab;
new GetTraderScreensGroupPatch().Enable();
new SwitchOnClickPatch().Enable();
}
public static void Enable()
public class GetTraderScreensGroupPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
new GetTraderScreensGroupPatch().Enable();
new SwitchOnClickPatch().Enable();
return AccessTools.Method(typeof(TraderScreensGroup), nameof(TraderScreensGroup.Show));
}
public class GetTraderScreensGroupPatch : ModulePatch
[PatchPostfix]
public static void Postfix(TraderScreensGroup __instance)
{
protected override MethodBase GetTargetMethod()
var wrappedInstance = __instance.R();
BuyTab = wrappedInstance.BuyTab;
SellTab = wrappedInstance.SellTab;
wrappedInstance.UI.AddDisposable(() =>
{
return AccessTools.Method(typeof(TraderScreensGroup), nameof(TraderScreensGroup.Show));
}
BuyTab = null;
SellTab = null;
});
}
}
[PatchPostfix]
public static void Postfix(TraderScreensGroup __instance)
{
var wrappedInstance = __instance.R();
BuyTab = wrappedInstance.BuyTab;
SellTab = wrappedInstance.SellTab;
wrappedInstance.UI.AddDisposable(() =>
{
BuyTab = null;
SellTab = null;
});
}
public class SwitchOnClickPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(TradingItemView), nameof(TradingItemView.OnClick));
}
public class SwitchOnClickPatch : ModulePatch
// Basically reimplementing this method for the two cases I want to handle
// Key difference being NOT to check the current trading mode, and to call switch at the end
// Have to call switch *after*, because it completely rebuilds the entire player-side grid
[PatchPrefix]
public static bool Prefix(
TradingItemView __instance,
PointerEventData.InputButton button,
bool doubleClick,
ETradingItemViewType ___etradingItemViewType_0, bool ___bool_8)
{
protected override MethodBase GetTargetMethod()
if (!Settings.AutoSwitchTrading.Value)
{
return AccessTools.Method(typeof(TradingItemView), nameof(TradingItemView.OnClick));
}
// Basically reimplementing this method for the two cases I want to handle
// Key difference being NOT to check the current trading mode, and to call switch at the end
// Have to call switch *after*, because it completely rebuilds the entire player-side grid
[PatchPrefix]
public static bool Prefix(
TradingItemView __instance,
PointerEventData.InputButton button,
bool doubleClick,
ETradingItemViewType ___etradingItemViewType_0, bool ___bool_8)
{
if (!Settings.AutoSwitchTrading.Value)
{
return true;
}
var tradingItemView = __instance.R();
if (button != PointerEventData.InputButton.Left || ___etradingItemViewType_0 == ETradingItemViewType.TradingTable)
{
return true;
}
bool ctrlPressed = Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl);
if (!ctrlPressed && doubleClick)
{
return true;
}
if (!___bool_8 && ctrlPressed && tradingItemView.TraderAssortmentController.QuickFindTradingAppropriatePlace(__instance.Item, null))
{
__instance.ItemContext.CloseDependentWindows();
__instance.HideTooltip();
Singleton<GUISounds>.Instance.PlayItemSound(__instance.Item.ItemSound, EInventorySoundType.pickup, false);
SellTab.OnPointerClick(null);
return false;
}
if (___bool_8)
{
tradingItemView.TraderAssortmentController.SelectItem(__instance.Item);
BuyTab.OnPointerClick(null);
return false;
}
return true;
}
var tradingItemView = __instance.R();
if (button != PointerEventData.InputButton.Left || ___etradingItemViewType_0 == ETradingItemViewType.TradingTable)
{
return true;
}
bool ctrlPressed = Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl);
if (!ctrlPressed && doubleClick)
{
return true;
}
if (!___bool_8 && ctrlPressed && tradingItemView.TraderAssortmentController.QuickFindTradingAppropriatePlace(__instance.Item, null))
{
__instance.ItemContext.CloseDependentWindows();
__instance.HideTooltip();
Singleton<GUISounds>.Instance.PlayItemSound(__instance.Item.ItemSound, EInventorySoundType.pickup, false);
SellTab.OnPointerClick(null);
return false;
}
if (___bool_8)
{
tradingItemView.TraderAssortmentController.SelectItem(__instance.Item);
BuyTab.OnPointerClick(null);
return false;
}
return true;
}
}
}

View File

@@ -4,26 +4,25 @@ using SPT.Reflection.Patching;
using System.Reflection;
using System.Threading.Tasks;
namespace UIFixes
namespace UIFixes;
public class TransferConfirmPatch : ModulePatch
{
public class TransferConfirmPatch : ModulePatch
protected override MethodBase GetTargetMethod()
{
protected override MethodBase GetTargetMethod()
return AccessTools.Method(typeof(TransferItemsScreen), nameof(TransferItemsScreen.method_4));
}
[PatchPrefix]
public static bool Prefix(ref Task<bool> __result)
{
if (Settings.ShowTransferConfirmations.Value == TransferConfirmationOption.Always)
{
return AccessTools.Method(typeof(TransferItemsScreen), nameof(TransferItemsScreen.method_4));
return true;
}
[PatchPrefix]
public static bool Prefix(ref Task<bool> __result)
{
if (Settings.ShowTransferConfirmations.Value == TransferConfirmationOption.Always)
{
return true;
}
__result = Task.FromResult(true);
return false;
}
__result = Task.FromResult(true);
return false;
}
}

View File

@@ -9,94 +9,93 @@ using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
namespace UIFixes
namespace UIFixes;
public static class UnloadAmmoPatches
{
public static class UnloadAmmoPatches
public static void Enable()
{
public static void Enable()
new TradingPlayerPatch().Enable();
new TransferPlayerPatch().Enable();
new UnloadScavTransferPatch().Enable();
new NoScavStashPatch().Enable();
}
public class TradingPlayerPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
new TradingPlayerPatch().Enable();
new TransferPlayerPatch().Enable();
new UnloadScavTransferPatch().Enable();
new NoScavStashPatch().Enable();
return AccessTools.DeclaredProperty(R.TradingInteractions.Type, "AvailableInteractions").GetMethod;
}
public class TradingPlayerPatch : ModulePatch
[PatchPostfix]
public static void Postfix(ref IEnumerable<EItemInfoButton> __result)
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.DeclaredProperty(R.TradingInteractions.Type, "AvailableInteractions").GetMethod;
}
var list = __result.ToList();
list.Insert(list.IndexOf(EItemInfoButton.Repair), EItemInfoButton.UnloadAmmo);
__result = list;
}
}
[PatchPostfix]
public static void Postfix(ref IEnumerable<EItemInfoButton> __result)
{
var list = __result.ToList();
list.Insert(list.IndexOf(EItemInfoButton.Repair), EItemInfoButton.UnloadAmmo);
__result = list;
}
public class TransferPlayerPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.DeclaredProperty(R.TransferInteractions.Type, "AvailableInteractions").GetMethod;
}
public class TransferPlayerPatch : ModulePatch
[PatchPostfix]
public static void Postfix(ref IEnumerable<EItemInfoButton> __result)
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.DeclaredProperty(R.TransferInteractions.Type, "AvailableInteractions").GetMethod;
}
var list = __result.ToList();
list.Insert(list.IndexOf(EItemInfoButton.Fold), EItemInfoButton.UnloadAmmo);
__result = list;
}
}
[PatchPostfix]
public static void Postfix(ref IEnumerable<EItemInfoButton> __result)
{
var list = __result.ToList();
list.Insert(list.IndexOf(EItemInfoButton.Fold), EItemInfoButton.UnloadAmmo);
__result = list;
}
// The scav inventory screen has two inventory controllers, the player's and the scav's. Unload always uses the player's, which causes issues
// because the bullets are never marked as "known" by the scav, so if you click back/next they show up as unsearched, with no way to search
// This patch forces unload to use the controller of whoever owns the magazine.
public class UnloadScavTransferPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.DeclaredMethod(typeof(InventoryControllerClass), nameof(InventoryControllerClass.UnloadMagazine));
}
// The scav inventory screen has two inventory controllers, the player's and the scav's. Unload always uses the player's, which causes issues
// because the bullets are never marked as "known" by the scav, so if you click back/next they show up as unsearched, with no way to search
// This patch forces unload to use the controller of whoever owns the magazine.
public class UnloadScavTransferPatch : ModulePatch
[PatchPrefix]
public static bool Prefix(InventoryControllerClass __instance, MagazineClass magazine, ref Task<IResult> __result)
{
protected override MethodBase GetTargetMethod()
if (ItemUiContext.Instance.ContextType != EItemUiContextType.ScavengerInventoryScreen)
{
return AccessTools.DeclaredMethod(typeof(InventoryControllerClass), nameof(InventoryControllerClass.UnloadMagazine));
return true;
}
[PatchPrefix]
public static bool Prefix(InventoryControllerClass __instance, MagazineClass magazine, ref Task<IResult> __result)
if (magazine.Owner == __instance || magazine.Owner is not InventoryControllerClass ownerInventoryController)
{
if (ItemUiContext.Instance.ContextType != EItemUiContextType.ScavengerInventoryScreen)
{
return true;
}
if (magazine.Owner == __instance || magazine.Owner is not InventoryControllerClass ownerInventoryController)
{
return true;
}
__result = ownerInventoryController.UnloadMagazine(magazine);
return false;
return true;
}
__result = ownerInventoryController.UnloadMagazine(magazine);
return false;
}
}
// Because of the above patch, unload uses the scav's inventory controller, which provides locations to unload ammo: equipment and stash. Why do scavs have a stash?
// If the equipment is full, the bullets would go to the scav stash, aka a black hole, and are never seen again.
// Remove the scav's stash
public class NoScavStashPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
Type type = typeof(ScavengerInventoryScreen).GetNestedTypes().Single(t => t.GetField("ScavController") != null); // ScavengerInventoryScreen.GClass3156
return AccessTools.GetDeclaredConstructors(type).Single();
}
// Because of the above patch, unload uses the scav's inventory controller, which provides locations to unload ammo: equipment and stash. Why do scavs have a stash?
// If the equipment is full, the bullets would go to the scav stash, aka a black hole, and are never seen again.
// Remove the scav's stash
public class NoScavStashPatch : ModulePatch
[PatchPrefix]
public static void Prefix(InventoryContainerClass scavController)
{
protected override MethodBase GetTargetMethod()
{
Type type = typeof(ScavengerInventoryScreen).GetNestedTypes().Single(t => t.GetField("ScavController") != null); // ScavengerInventoryScreen.GClass3156
return AccessTools.GetDeclaredConstructors(type).Single();
}
[PatchPrefix]
public static void Prefix(InventoryContainerClass scavController)
{
scavController.Inventory.Stash = null;
}
scavController.Inventory.Stash = null;
}
}
}

View File

@@ -6,59 +6,57 @@ using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
namespace UIFixes
namespace UIFixes;
// Two patches are required for the edit preset screen - one to grab the value of moveForward from CloseScreenInterruption(), and one to use it.
// This is because BSG didn't think to pass the argument in to method_35
public static class WeaponPresetConfirmPatches
{
// Two patches are required for the edit preset screen - one to grab the value of moveForward from CloseScreenInterruption(), and one to use it.
// This is because BSG didn't think to pass the argument in to method_35
public static class WeaponPresetConfirmPatches
public static bool MoveForward;
public static void Enable()
{
public static bool MoveForward;
new DetectWeaponPresetCloseTypePatch().Enable();
new ConfirmDiscardWeaponPresetChangesPatch().Enable();
}
public static void Enable()
// This patch just caches whether this navigation is a forward navigation, which determines if the preset is actually closing
public class DetectWeaponPresetCloseTypePatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
new DetectWeaponPresetCloseTypePatch().Enable();
new ConfirmDiscardWeaponPresetChangesPatch().Enable();
Type type = typeof(EditBuildScreen).GetNestedTypes().Single(x => x.GetMethod("CloseScreenInterruption") != null); // EditBuildScreen.GClass3151
return AccessTools.Method(type, "CloseScreenInterruption");
}
// This patch just caches whether this navigation is a forward navigation, which determines if the preset is actually closing
public class DetectWeaponPresetCloseTypePatch : ModulePatch
[PatchPrefix]
public static void Prefix(bool moveForward)
{
protected override MethodBase GetTargetMethod()
{
Type type = typeof(EditBuildScreen).GetNestedTypes().Single(x => x.GetMethod("CloseScreenInterruption") != null); // EditBuildScreen.GClass3151
return AccessTools.Method(type, "CloseScreenInterruption");
}
MoveForward = moveForward;
}
}
[PatchPrefix]
public static void Prefix(bool moveForward)
{
MoveForward = moveForward;
}
public class ConfirmDiscardWeaponPresetChangesPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(EditBuildScreen), nameof(EditBuildScreen.method_35));
}
public class ConfirmDiscardWeaponPresetChangesPatch : ModulePatch
[PatchPrefix]
public static bool Prefix(ref Task<bool> __result)
{
protected override MethodBase GetTargetMethod()
if (MoveForward && Settings.ShowPresetConfirmations.Value == WeaponPresetConfirmationOption.Always)
{
return AccessTools.Method(typeof(EditBuildScreen), nameof(EditBuildScreen.method_35));
return true;
}
[PatchPrefix]
public static bool Prefix(ref Task<bool> __result)
if (!MoveForward && Settings.ShowPresetConfirmations.Value != WeaponPresetConfirmationOption.Never)
{
if (MoveForward && Settings.ShowPresetConfirmations.Value == WeaponPresetConfirmationOption.Always)
{
return true;
}
if (!MoveForward && Settings.ShowPresetConfirmations.Value != WeaponPresetConfirmationOption.Never)
{
return true;
}
__result = Task.FromResult(true);
return false;
return true;
}
__result = Task.FromResult(true);
return false;
}
}
}

View File

@@ -5,58 +5,57 @@ using SPT.Reflection.Patching;
using System.Reflection;
using UnityEngine.EventSystems;
namespace UIFixes
namespace UIFixes;
public static class WeaponZoomPatches
{
public static class WeaponZoomPatches
public static void Enable()
{
public static void Enable()
new EditBuildScreenZoomPatch().Enable();
new WeaponModdingScreenZoomPatch().Enable();
}
public class EditBuildScreenZoomPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
new EditBuildScreenZoomPatch().Enable();
new WeaponModdingScreenZoomPatch().Enable();
return AccessTools.Method(typeof(EditBuildScreen), nameof(EditBuildScreen.Awake));
}
public class EditBuildScreenZoomPatch : ModulePatch
[PatchPrefix]
public static void Prefix(EditBuildScreen __instance, WeaponPreview ____weaponPreview)
{
protected override MethodBase GetTargetMethod()
var scrollTrigger = __instance.gameObject.AddComponent<ScrollTrigger>();
scrollTrigger.OnOnScroll += (PointerEventData eventData) =>
{
return AccessTools.Method(typeof(EditBuildScreen), nameof(EditBuildScreen.Awake));
}
[PatchPrefix]
public static void Prefix(EditBuildScreen __instance, WeaponPreview ____weaponPreview)
{
var scrollTrigger = __instance.gameObject.AddComponent<ScrollTrigger>();
scrollTrigger.OnOnScroll += (PointerEventData eventData) =>
if (____weaponPreview != null && __instance != null)
{
if (____weaponPreview != null && __instance != null)
{
____weaponPreview.Zoom(eventData.scrollDelta.y * 0.12f);
__instance.UpdatePositions();
}
};
}
____weaponPreview.Zoom(eventData.scrollDelta.y * 0.12f);
__instance.UpdatePositions();
}
};
}
}
public class WeaponModdingScreenZoomPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(WeaponModdingScreen), nameof(WeaponModdingScreen.Awake));
}
public class WeaponModdingScreenZoomPatch : ModulePatch
[PatchPrefix]
public static void Prefix(WeaponModdingScreen __instance, WeaponPreview ____weaponPreview)
{
protected override MethodBase GetTargetMethod()
var scrollTrigger = __instance.gameObject.AddComponent<ScrollTrigger>();
scrollTrigger.OnOnScroll += (PointerEventData eventData) =>
{
return AccessTools.Method(typeof(WeaponModdingScreen), nameof(WeaponModdingScreen.Awake));
}
[PatchPrefix]
public static void Prefix(WeaponModdingScreen __instance, WeaponPreview ____weaponPreview)
{
var scrollTrigger = __instance.gameObject.AddComponent<ScrollTrigger>();
scrollTrigger.OnOnScroll += (PointerEventData eventData) =>
if (____weaponPreview != null && __instance != null)
{
if (____weaponPreview != null && __instance != null)
{
____weaponPreview.Zoom(eventData.scrollDelta.y * 0.12f);
__instance.UpdatePositions();
}
};
}
____weaponPreview.Zoom(eventData.scrollDelta.y * 0.12f);
__instance.UpdatePositions();
}
};
}
}
}

125
Plugin.cs
View File

@@ -2,73 +2,72 @@
using Comfort.Common;
using EFT;
namespace UIFixes
namespace UIFixes;
[BepInPlugin(PluginInfo.PLUGIN_GUID, PluginInfo.PLUGIN_NAME, PluginInfo.PLUGIN_VERSION)]
public class Plugin : BaseUnityPlugin
{
[BepInPlugin(PluginInfo.PLUGIN_GUID, PluginInfo.PLUGIN_NAME, PluginInfo.PLUGIN_VERSION)]
public class Plugin : BaseUnityPlugin
public void Awake()
{
public void Awake()
{
Settings.Init(Config);
Settings.Init(Config);
R.Init();
R.Init();
ConfirmDialogKeysPatches.Enable();
new FixMailRecieveAllPatch().Enable();
FixTooltipPatches.Enable();
QuickAccessPanelPatches.Enable();
FocusFleaOfferNumberPatches.Enable();
HideoutSearchPatches.Enable();
HideoutLevelPatches.Enable();
InspectWindowResizePatches.Enable();
InspectWindowStatsPatches.Enable();
new RemoveDoorActionsPatch().Enable();
ScrollPatches.Enable();
StackFirItemsPatches.Enable();
SwapPatches.Enable();
SyncScrollPositionPatches.Enable();
new TransferConfirmPatch().Enable();
WeaponPresetConfirmPatches.Enable();
WeaponZoomPatches.Enable();
new MoveTaskbarPatch().Enable();
FixFleaPatches.Enable();
FleaPrevSearchPatches.Enable();
KeepOfferWindowOpenPatches.Enable();
AddOfferClickablePricesPatches.Enable();
new AssortUnlocksPatch().Enable();
new AutofillQuestItemsPatch().Enable();
ContextMenuPatches.Enable();
TradingAutoSwitchPatches.Enable();
AddOfferRememberAutoselectPatches.Enable();
KeepMessagesOpenPatches.Enable();
new FocusTradeQuantityPatch().Enable();
RememberRepairerPatches.Enable();
new GridWindowButtonsPatch().Enable();
new LoadMagPresetsPatch().Enable();
KeepWindowsOnScreenPatches.Enable();
ContextMenuShortcutPatches.Enable();
new OpenSortingTablePatch().Enable();
LoadAmmoInRaidPatches.Enable();
MultiSelectPatches.Enable();
new FixUnloadLastBulletPatch().Enable();
StackMoveGreedyPatches.Enable();
UnloadAmmoPatches.Enable();
new FixTraderControllerSimulateFalsePatch().Enable();
LoadMultipleMagazinesPatches.Enable();
new PutToolsBackPatch().Enable();
new RebindGrenadesPatch().Enable();
AimToggleHoldPatches.Enable();
ReorderGridsPatches.Enable();
NoRandomGrenadesPatch.Init();
GPCoinPatches.Enable();
FleaSlotSearchPatches.Enable();
MoveSortingTablePatches.Enable();
}
ConfirmDialogKeysPatches.Enable();
new FixMailRecieveAllPatch().Enable();
FixTooltipPatches.Enable();
QuickAccessPanelPatches.Enable();
FocusFleaOfferNumberPatches.Enable();
HideoutSearchPatches.Enable();
HideoutLevelPatches.Enable();
InspectWindowResizePatches.Enable();
InspectWindowStatsPatches.Enable();
new RemoveDoorActionsPatch().Enable();
ScrollPatches.Enable();
StackFirItemsPatches.Enable();
SwapPatches.Enable();
SyncScrollPositionPatches.Enable();
new TransferConfirmPatch().Enable();
WeaponPresetConfirmPatches.Enable();
WeaponZoomPatches.Enable();
new MoveTaskbarPatch().Enable();
FixFleaPatches.Enable();
FleaPrevSearchPatches.Enable();
KeepOfferWindowOpenPatches.Enable();
AddOfferClickablePricesPatches.Enable();
new AssortUnlocksPatch().Enable();
new AutofillQuestItemsPatch().Enable();
ContextMenuPatches.Enable();
TradingAutoSwitchPatches.Enable();
AddOfferRememberAutoselectPatches.Enable();
KeepMessagesOpenPatches.Enable();
new FocusTradeQuantityPatch().Enable();
RememberRepairerPatches.Enable();
new GridWindowButtonsPatch().Enable();
new LoadMagPresetsPatch().Enable();
KeepWindowsOnScreenPatches.Enable();
ContextMenuShortcutPatches.Enable();
new OpenSortingTablePatch().Enable();
LoadAmmoInRaidPatches.Enable();
MultiSelectPatches.Enable();
new FixUnloadLastBulletPatch().Enable();
StackMoveGreedyPatches.Enable();
UnloadAmmoPatches.Enable();
new FixTraderControllerSimulateFalsePatch().Enable();
LoadMultipleMagazinesPatches.Enable();
new PutToolsBackPatch().Enable();
new RebindGrenadesPatch().Enable();
AimToggleHoldPatches.Enable();
ReorderGridsPatches.Enable();
NoRandomGrenadesPatch.Init();
GPCoinPatches.Enable();
FleaSlotSearchPatches.Enable();
MoveSortingTablePatches.Enable();
}
public static bool InRaid()
{
bool? inRaid = Singleton<AbstractGame>.Instance?.InRaid;
return inRaid.HasValue && inRaid.Value;
}
public static bool InRaid()
{
bool? inRaid = Singleton<AbstractGame>.Instance?.InRaid;
return inRaid.HasValue && inRaid.Value;
}
}

1389
R.cs

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -3,77 +3,76 @@ using System.Collections.Generic;
using System.Threading.Tasks;
using UnityEngine;
namespace UIFixes
namespace UIFixes;
public class TaskSerializer<T> : MonoBehaviour
{
public class TaskSerializer<T> : MonoBehaviour
private Func<T, Task> func;
private Func<T, bool> canContinue;
private IEnumerator<T> enumerator;
private Task currentTask;
private TaskCompletionSource totalTask;
public Task Initialize(IEnumerable<T> items, Func<T, Task> func, Func<T, bool> canContinue = null)
{
private Func<T, Task> func;
private Func<T, bool> canContinue;
private IEnumerator<T> enumerator;
private Task currentTask;
private TaskCompletionSource totalTask;
this.enumerator = items.GetEnumerator();
this.func = func;
this.canContinue = canContinue;
public Task Initialize(IEnumerable<T> items, Func<T, Task> func, Func<T, bool> canContinue = null)
currentTask = Task.CompletedTask;
totalTask = new TaskCompletionSource();
LateUpdate();
return totalTask.Task;
}
public void Cancel()
{
if (!totalTask.Task.IsCompleted)
{
this.enumerator = items.GetEnumerator();
this.func = func;
this.canContinue = canContinue;
currentTask = Task.CompletedTask;
totalTask = new TaskCompletionSource();
LateUpdate();
return totalTask.Task;
}
public void Cancel()
{
if (!totalTask.Task.IsCompleted)
{
totalTask.TrySetCanceled();
Complete();
}
}
public void OnDisable()
{
Cancel();
}
public void LateUpdate()
{
if (currentTask.IsCanceled)
{
Complete();
return;
}
if (totalTask.Task.IsCompleted || !currentTask.IsCompleted)
{
return;
}
if (canContinue != null && enumerator.Current != null && !canContinue(enumerator.Current))
{
return;
}
if (enumerator.MoveNext())
{
currentTask = func(enumerator.Current);
}
else
{
Complete();
}
}
private void Complete()
{
totalTask.TryComplete();
func = null;
Destroy(this);
totalTask.TrySetCanceled();
Complete();
}
}
public void OnDisable()
{
Cancel();
}
public void LateUpdate()
{
if (currentTask.IsCanceled)
{
Complete();
return;
}
if (totalTask.Task.IsCompleted || !currentTask.IsCompleted)
{
return;
}
if (canContinue != null && enumerator.Current != null && !canContinue(enumerator.Current))
{
return;
}
if (enumerator.MoveNext())
{
currentTask = func(enumerator.Current);
}
else
{
Complete();
}
}
private void Complete()
{
totalTask.TryComplete();
func = null;
Destroy(this);
}
}

View File

@@ -1,86 +1,85 @@
using EFT.InputSystem;
namespace UIFixes
namespace UIFixes;
public enum ToggleHoldState
{
public enum ToggleHoldState
Idle = 13,
ClickOrHold = 14,
Holding = 15
}
public class ToggleHoldIdleState(KeyCombination keyCombination) : KeyCombination.KeyCombinationState(keyCombination)
{
public override ECommand GetCommand(float deltaTime)
{
Idle = 13,
ClickOrHold = 14,
Holding = 15
}
public class ToggleHoldIdleState(KeyCombination keyCombination) : KeyCombination.KeyCombinationState(keyCombination)
{
public override ECommand GetCommand(float deltaTime)
if (!CanProcess())
{
if (!CanProcess())
{
return ECommand.None;
}
HandleKeys(false);
KeyCombination.method_0((KeyCombination.EKeyState)ToggleHoldState.ClickOrHold);
return GetCommandInternal();
}
protected bool CanProcess()
{
return GetKeysStatus(out EKeyPress ekeyPress) && (ekeyPress == EKeyPress.Down);
}
}
public class ToggleHoldClickOrHoldState(KeyCombination keyCombination) : KeyCombination.KeyCombinationState(keyCombination)
{
public override void Enter()
{
timer = KeyCombination.DoubleClickTimeout;
}
public override ECommand GetCommand(float deltaTime)
{
if (GetKeysStatus(out EKeyPress ekeyPress))
{
if (ekeyPress == EKeyPress.Hold)
{
HandleKeys(false);
if (LongEnough(deltaTime))
{
KeyCombination.method_0((KeyCombination.EKeyState)ToggleHoldState.Holding);
}
return ECommand.None;
}
}
UnhandleKeys(null);
KeyCombination.method_0((KeyCombination.EKeyState)ToggleHoldState.Idle);
return ECommand.None;
}
private bool LongEnough(float deltaTime)
{
timer -= deltaTime;
return timer <= 0f;
}
private float timer;
HandleKeys(false);
KeyCombination.method_0((KeyCombination.EKeyState)ToggleHoldState.ClickOrHold);
return GetCommandInternal();
}
public class ToggleHoldHoldState(KeyCombination keyCombination, ECommand disableCommand) : KeyCombination.KeyCombinationState(keyCombination)
protected bool CanProcess()
{
private readonly ECommand disableCommand = disableCommand;
public override ECommand GetCommand(float deltaTime)
{
if (GetKeysStatus(out EKeyPress ekeyPress) && ekeyPress == EKeyPress.Hold)
{
HandleKeys(false);
return ECommand.None;
}
UnhandleKeys(null);
KeyCombination.method_0((KeyCombination.EKeyState)ToggleHoldState.Idle);
return disableCommand;
}
return GetKeysStatus(out EKeyPress ekeyPress) && (ekeyPress == EKeyPress.Down);
}
}
public class ToggleHoldClickOrHoldState(KeyCombination keyCombination) : KeyCombination.KeyCombinationState(keyCombination)
{
public override void Enter()
{
timer = KeyCombination.DoubleClickTimeout;
}
public override ECommand GetCommand(float deltaTime)
{
if (GetKeysStatus(out EKeyPress ekeyPress))
{
if (ekeyPress == EKeyPress.Hold)
{
HandleKeys(false);
if (LongEnough(deltaTime))
{
KeyCombination.method_0((KeyCombination.EKeyState)ToggleHoldState.Holding);
}
return ECommand.None;
}
}
UnhandleKeys(null);
KeyCombination.method_0((KeyCombination.EKeyState)ToggleHoldState.Idle);
return ECommand.None;
}
private bool LongEnough(float deltaTime)
{
timer -= deltaTime;
return timer <= 0f;
}
private float timer;
}
public class ToggleHoldHoldState(KeyCombination keyCombination, ECommand disableCommand) : KeyCombination.KeyCombinationState(keyCombination)
{
private readonly ECommand disableCommand = disableCommand;
public override ECommand GetCommand(float deltaTime)
{
if (GetKeysStatus(out EKeyPress ekeyPress) && ekeyPress == EKeyPress.Hold)
{
HandleKeys(false);
return ECommand.None;
}
UnhandleKeys(null);
KeyCombination.method_0((KeyCombination.EKeyState)ToggleHoldState.Idle);
return disableCommand;
}
}