Merge branch 'main' into CykaFix
This commit is contained in:
@@ -1,97 +0,0 @@
|
|||||||
using EFT.UI.Ragfair;
|
|
||||||
using HarmonyLib;
|
|
||||||
using JetBrains.Annotations;
|
|
||||||
using SPT.Reflection.Patching;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Reflection;
|
|
||||||
using TMPro;
|
|
||||||
using UnityEngine;
|
|
||||||
using UnityEngine.EventSystems;
|
|
||||||
using UnityEngine.UI;
|
|
||||||
|
|
||||||
namespace UIFixes;
|
|
||||||
|
|
||||||
public static class AddOfferClickablePricesPatches
|
|
||||||
{
|
|
||||||
public static void Enable()
|
|
||||||
{
|
|
||||||
new AddButtonPatch().Enable();
|
|
||||||
}
|
|
||||||
|
|
||||||
public class AddButtonPatch : ModulePatch
|
|
||||||
{
|
|
||||||
protected override MethodBase GetTargetMethod()
|
|
||||||
{
|
|
||||||
return AccessTools.Method(typeof(AddOfferWindow), nameof(AddOfferWindow.Show));
|
|
||||||
}
|
|
||||||
|
|
||||||
[PatchPostfix]
|
|
||||||
public static void Postfix(AddOfferWindow __instance, ItemMarketPricesPanel ____pricesPanel, RequirementView[] ____requirementViews)
|
|
||||||
{
|
|
||||||
var panel = ____pricesPanel.R();
|
|
||||||
|
|
||||||
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 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void SetRequirement(AddOfferWindow window, RequirementView requirement, float price)
|
|
||||||
{
|
|
||||||
if (window.R().BulkOffer)
|
|
||||||
{
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
_text = GetComponent<TextMeshProUGUI>();
|
|
||||||
}
|
|
||||||
|
|
||||||
return _text;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void OnPointerEnter([NotNull] PointerEventData eventData)
|
|
||||||
{
|
|
||||||
base.OnPointerEnter(eventData);
|
|
||||||
|
|
||||||
originalColor = Text.color;
|
|
||||||
originalOverrideColorTags = Text.overrideColorTags;
|
|
||||||
|
|
||||||
Text.overrideColorTags = true;
|
|
||||||
Text.color = Color.white;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void OnPointerExit([NotNull] PointerEventData eventData)
|
|
||||||
{
|
|
||||||
base.OnPointerExit(eventData);
|
|
||||||
|
|
||||||
Text.overrideColorTags = originalOverrideColorTags;
|
|
||||||
Text.color = originalColor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,72 +0,0 @@
|
|||||||
using Comfort.Common;
|
|
||||||
using EFT.InputSystem;
|
|
||||||
using HarmonyLib;
|
|
||||||
using SPT.Reflection.Patching;
|
|
||||||
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();
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
[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)
|
|
||||||
{
|
|
||||||
new ToggleHoldIdleState(__instance),
|
|
||||||
new ToggleHoldClickOrHoldState(__instance),
|
|
||||||
new ToggleHoldHoldState(__instance, disableCommand)
|
|
||||||
};
|
|
||||||
|
|
||||||
StateMachineArray.SetValue(__instance, states.ToArray());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class UpdateInputPatch : ModulePatch
|
|
||||||
{
|
|
||||||
protected override MethodBase GetTargetMethod()
|
|
||||||
{
|
|
||||||
return AccessTools.Method(typeof(KeyCombination), nameof(KeyCombination.UpdateInput));
|
|
||||||
}
|
|
||||||
|
|
||||||
[PatchPostfix]
|
|
||||||
public static void Postfix(KeyCombination __instance)
|
|
||||||
{
|
|
||||||
if (!Settings.ToggleOrHoldAim.Value || __instance.GameKey != EGameKey.Aim)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
__instance.method_0((KeyCombination.EKeyState)ToggleHoldState.Idle);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,200 +0,0 @@
|
|||||||
using Comfort.Common;
|
|
||||||
using EFT.InventoryLogic;
|
|
||||||
using EFT.UI;
|
|
||||||
using HarmonyLib;
|
|
||||||
using SPT.Reflection.Patching;
|
|
||||||
using SPT.Reflection.Utils;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Reflection;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace UIFixes;
|
|
||||||
|
|
||||||
public static class UnloadAmmoPatches
|
|
||||||
{
|
|
||||||
private static UnloadAmmoBoxState UnloadState = null;
|
|
||||||
|
|
||||||
public static void Enable()
|
|
||||||
{
|
|
||||||
new TradingPlayerPatch().Enable();
|
|
||||||
new TransferPlayerPatch().Enable();
|
|
||||||
new UnloadScavTransferPatch().Enable();
|
|
||||||
new NoScavStashPatch().Enable();
|
|
||||||
|
|
||||||
new UnloadAmmoBoxPatch().Enable();
|
|
||||||
new QuickFindUnloadAmmoBoxPatch().Enable();
|
|
||||||
}
|
|
||||||
|
|
||||||
public class TradingPlayerPatch : ModulePatch
|
|
||||||
{
|
|
||||||
protected override MethodBase GetTargetMethod()
|
|
||||||
{
|
|
||||||
return AccessTools.DeclaredProperty(R.TradingInteractions.Type, "AvailableInteractions").GetMethod;
|
|
||||||
}
|
|
||||||
|
|
||||||
[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;
|
|
||||||
}
|
|
||||||
|
|
||||||
[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));
|
|
||||||
}
|
|
||||||
|
|
||||||
[PatchPrefix]
|
|
||||||
public static bool Prefix(InventoryControllerClass __instance, MagazineClass magazine, ref Task<IResult> __result)
|
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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();
|
|
||||||
}
|
|
||||||
|
|
||||||
[PatchPrefix]
|
|
||||||
public static void Prefix(InventoryContainerClass scavController)
|
|
||||||
{
|
|
||||||
scavController.Inventory.Stash = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class UnloadAmmoBoxPatch : ModulePatch
|
|
||||||
{
|
|
||||||
protected override MethodBase GetTargetMethod()
|
|
||||||
{
|
|
||||||
return AccessTools.Method(typeof(ItemUiContext), nameof(ItemUiContext.UnloadAmmo));
|
|
||||||
}
|
|
||||||
|
|
||||||
[PatchPrefix]
|
|
||||||
public static void Prefix(Item item)
|
|
||||||
{
|
|
||||||
if (item is AmmoBox)
|
|
||||||
{
|
|
||||||
UnloadState = new();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[PatchPostfix]
|
|
||||||
public static void Postfix()
|
|
||||||
{
|
|
||||||
UnloadState = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class QuickFindUnloadAmmoBoxPatch : ModulePatch
|
|
||||||
{
|
|
||||||
protected override MethodBase GetTargetMethod()
|
|
||||||
{
|
|
||||||
return AccessTools.Method(typeof(InteractionsHandlerClass), nameof(InteractionsHandlerClass.QuickFindAppropriatePlace));
|
|
||||||
}
|
|
||||||
|
|
||||||
[PatchPrefix]
|
|
||||||
public static void Prefix(Item item, TraderControllerClass controller, ref IEnumerable<LootItemClass> targets, ref InteractionsHandlerClass.EMoveItemOrder order)
|
|
||||||
{
|
|
||||||
if (UnloadState == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
AmmoBox box = item.Parent.Container.ParentItem as AmmoBox;
|
|
||||||
if (box == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ammo boxes with multiple stacks will loop through this code, so we only want to move the box once
|
|
||||||
if (UnloadState.initialized)
|
|
||||||
{
|
|
||||||
order = UnloadState.order;
|
|
||||||
targets = UnloadState.targets;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Have to do this for them, since the calls to get parent will be wrong once we move the box
|
|
||||||
if (!order.HasFlag(InteractionsHandlerClass.EMoveItemOrder.IgnoreItemParent))
|
|
||||||
{
|
|
||||||
LootItemClass parent = (item.GetNotMergedParent() as LootItemClass) ?? (item.GetRootMergedItem() as EquipmentClass);
|
|
||||||
if (parent != null)
|
|
||||||
{
|
|
||||||
UnloadState.targets = targets = order.HasFlag(InteractionsHandlerClass.EMoveItemOrder.PrioritizeParent) ?
|
|
||||||
parent.ToEnumerable().Concat(targets).Distinct() :
|
|
||||||
targets.Concat(parent.ToEnumerable()).Distinct();
|
|
||||||
}
|
|
||||||
|
|
||||||
UnloadState.order = order |= InteractionsHandlerClass.EMoveItemOrder.IgnoreItemParent;
|
|
||||||
}
|
|
||||||
|
|
||||||
var operation = InteractionsHandlerClass.Move(box, UnloadState.fakeStash.Grid.FindLocationForItem(box), controller, false);
|
|
||||||
operation.Value.RaiseEvents(controller, CommandStatus.Begin);
|
|
||||||
operation.Value.RaiseEvents(controller, CommandStatus.Succeed);
|
|
||||||
|
|
||||||
UnloadState.initialized = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class UnloadAmmoBoxState
|
|
||||||
{
|
|
||||||
public StashClass fakeStash;
|
|
||||||
public TraderControllerClass fakeController;
|
|
||||||
|
|
||||||
public bool initialized;
|
|
||||||
public InteractionsHandlerClass.EMoveItemOrder order;
|
|
||||||
public IEnumerable<LootItemClass> targets;
|
|
||||||
|
|
||||||
public UnloadAmmoBoxState()
|
|
||||||
{
|
|
||||||
fakeStash = (StashClass)Singleton<ItemFactory>.Instance.CreateItem("FakeStash", "566abbc34bdc2d92178b4576", null);
|
|
||||||
|
|
||||||
var profile = PatchConstants.BackEndSession.Profile;
|
|
||||||
fakeController = new(fakeStash, profile.ProfileId, profile.Nickname);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
57
README.md
57
README.md
@@ -15,10 +15,12 @@ New UI features enabled by this mod
|
|||||||
- Ctrl-click and Alt-click to quick move or equip them all. Compatible with Quick Move to Containers!
|
- Ctrl-click and Alt-click to quick move or equip them all. Compatible with Quick Move to Containers!
|
||||||
- Context menu to insure all, equip all, unequip all, unload ammo from all
|
- Context menu to insure all, equip all, unequip all, unload ammo from all
|
||||||
- Swap items in place - drag one item over another to swap their locations!
|
- Swap items in place - drag one item over another to swap their locations!
|
||||||
|
- ✨ Add offer to the flea market from an item's context menu
|
||||||
- Flea market history - press the new back button to go back to the previous search
|
- Flea market history - press the new back button to go back to the previous search
|
||||||
- ✨ Linked flea search from empty slots - find mods that fit that specific slot
|
- Linked flea search from empty slots - find mods that fit that specific slot
|
||||||
- Keybinds for most context menu actions
|
- Keybinds for most context menu actions
|
||||||
- ✨ Toggle/Hold Aiming - tap to toggle ADS, or hold to ADS and stop when you release
|
- Toggle/Hold input - tap a key for "Press" mechanics, hold the key for "Continuous" mechanics
|
||||||
|
- Can be set for aiming, sprinting, tactical devices, headlights, and goggles/faceshields
|
||||||
|
|
||||||
## Improved features
|
## Improved features
|
||||||
|
|
||||||
@@ -26,25 +28,28 @@ Existing SPT features made better
|
|||||||
|
|
||||||
#### Inventory
|
#### Inventory
|
||||||
|
|
||||||
|
- ✨ Modify equipped weapons
|
||||||
- Rebind Home/End, PageUp/PageDown to work like you would expect
|
- Rebind Home/End, PageUp/PageDown to work like you would expect
|
||||||
- Customizable mouse scrolling speed
|
- Customizable mouse scrolling speed
|
||||||
- Moving stacks into containers always moves entire stack
|
- Moving stacks into containers always moves entire stack
|
||||||
- ✨ Items made stackable by other mods follow normal stacking behavior
|
- Items made stackable by other mods follow normal stacking behavior
|
||||||
- Allow found in raid money and ammo automatically stack with non-found-in-raid items
|
- Allow found in raid money and ammo automatically stack with non-found-in-raid items
|
||||||
- Synchronize stash scroll position everywhere your stash is visible
|
- Synchronize stash scroll position everywhere your stash is visible
|
||||||
- Insure and repair items directly from the context menu
|
- Insure and repair items directly from the context menu
|
||||||
- Load ammo via context menu _in raid_
|
- Load ammo via context menu _in raid_
|
||||||
- Load ammo preset will pull ammo from inventory, not just stash
|
- Load ammo preset will pull ammo from inventory, not just stash
|
||||||
- ✨ Multi-grid vest and backpack grids reordered to be left to right, top to bottom.
|
- Multi-grid vest and backpack grids reordered to be left to right, top to bottom
|
||||||
- ✨ Sorting will stack and combine stacks of items
|
- Sorting will stack and combine stacks of items
|
||||||
- ✨ Shift-clicking sort will only sort loose items, leaving containers in place
|
- Shift-clicking sort will only sort loose items, leaving containers in place
|
||||||
|
- ✨ Open->All context flyout that will recursively open nested containers to get at that innermost bag
|
||||||
|
- ✨ Add/Remove from wishlist everywhere
|
||||||
|
|
||||||
#### Inspect windows
|
#### Inspect windows
|
||||||
|
|
||||||
- Show the total stats (including sub-mods) when inspecting mods (optional, toggleable _in_ the inspect pane with a new button)
|
- Show the total stats (including sub-mods) when inspecting mods (optional, toggleable _in_ the inspect pane with a new button)
|
||||||
- See stats change as you add/remove mods, with color-coded deltas
|
- See stats change as you add/remove mods, with color-coded deltas
|
||||||
- Remember last window size when you change it, with restore button to resize to default
|
- Remember last window size when you change it, with restore button to resize to default
|
||||||
- Move left and move right buttons + keybinds to quickly snap inspect windows to the left or right half of the screen, for easy comparisons.
|
- Move left and move right buttons + keybinds to quickly snap inspect windows to the left or right half of the screen, for easy comparisons
|
||||||
- Auto-expand descriptions when possible (great for showing extra text from mods like Item Info)
|
- Auto-expand descriptions when possible (great for showing extra text from mods like Item Info)
|
||||||
- Quickbinds will not be removed from items you keep when you die
|
- Quickbinds will not be removed from items you keep when you die
|
||||||
|
|
||||||
@@ -61,11 +66,12 @@ Existing SPT features made better
|
|||||||
- Option to keep the Add Offer window open after placing your offer
|
- Option to keep the Add Offer window open after placing your offer
|
||||||
- Set prices in the Add Offer window by clicking the min/avg/max market prices (multiplies for bulk orders)
|
- Set prices in the Add Offer window by clicking the min/avg/max market prices (multiplies for bulk orders)
|
||||||
- Autoselect Similar checkbox is remembered across sessions and application restarts
|
- Autoselect Similar checkbox is remembered across sessions and application restarts
|
||||||
- ✨ Replace barter offers icons with actual item images, plus owned/required counts on expansion
|
- Replace barter offers icons with actual item images, plus owned/required counts on expansion
|
||||||
- ✨ Clears filters for you when you type in search bar and there's no match
|
- Clears filters for you when you type in search bar and there's no match
|
||||||
|
|
||||||
#### Weapon modding/presets
|
#### Weapon modding/presets
|
||||||
|
|
||||||
|
- ✨ Weapons can grow left or up, not just right and down
|
||||||
- Enable zooming with mousewheel
|
- Enable zooming with mousewheel
|
||||||
- Skip needless unsaved changes warnings when not actually closing the screen
|
- Skip needless unsaved changes warnings when not actually closing the screen
|
||||||
|
|
||||||
@@ -76,9 +82,11 @@ Existing SPT features made better
|
|||||||
|
|
||||||
#### In raid
|
#### In raid
|
||||||
|
|
||||||
- ✨ Reloading will swap magazines in-place, instead of dropping them on the ground when there's no room.
|
- ✨ Quickbind tactical devices to control them individually
|
||||||
- ✨ Grenade quickbinds will transfer to the next grenade of the same type after throwing.
|
- ✨ Option to make unequipped weapons moddable in raid, optionally with multitool
|
||||||
- ✨ Option to change the behavior of the grenade key from selecting a random grenade to a deterministic one
|
- Reloading will swap magazines in-place, instead of dropping them on the ground when there's no room
|
||||||
|
- Grenade quickbinds will transfer to the next grenade of the same type after throwing
|
||||||
|
- Option to change the behavior of the grenade key from selecting a random grenade to a deterministic one
|
||||||
|
|
||||||
#### Mail
|
#### Mail
|
||||||
|
|
||||||
@@ -110,3 +118,28 @@ Fixing bugs that BSG won't or can't
|
|||||||
|
|
||||||
- Skips "You can return to this later" warnings when not transferring all items
|
- Skips "You can return to this later" warnings when not transferring all items
|
||||||
- "Receive All" button no longer shows up when there is nothing to receive
|
- "Receive All" button no longer shows up when there is nothing to receive
|
||||||
|
|
||||||
|
## Interop
|
||||||
|
|
||||||
|
UI Fixes offers interop with other mods that want to use the multi-select functionality, _without_ taking a hard dependency on `Tyfon.UIFixes.dll`.
|
||||||
|
|
||||||
|
To do this, simply download and add [MultiSelectInterop.cs](src/Multiselect/MultiSelectInterop.cs) to your client project. It will take care of testing if UI Fixes is present and, using reflection, interoping with the mod.
|
||||||
|
|
||||||
|
MultiSelectInterop exposes a small static surface to give you access to the multi-selection.
|
||||||
|
|
||||||
|
```cs
|
||||||
|
public static class MultiSelect
|
||||||
|
{
|
||||||
|
// Returns the number of items in the current selection
|
||||||
|
public static int Count { get; }
|
||||||
|
|
||||||
|
// Returns the items in the current selection
|
||||||
|
public static IEnumerable<Item> Items { get; }
|
||||||
|
|
||||||
|
// Executes an operation on each item in the selection, sequentially
|
||||||
|
// Passing an ItemUiContext is optional as it will use ItemUiContext.Instance if needed
|
||||||
|
// The second overload takes an async operation and returns a task representing the aggregate.
|
||||||
|
public static void Apply(Action<Item> action, ItemUiContext context = null);
|
||||||
|
public static Task Apply(Func<Item, Task> func, ItemUiContext context = null);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
@@ -4,7 +4,7 @@
|
|||||||
<TargetFramework>net471</TargetFramework>
|
<TargetFramework>net471</TargetFramework>
|
||||||
<AssemblyName>Tyfon.UIFixes</AssemblyName>
|
<AssemblyName>Tyfon.UIFixes</AssemblyName>
|
||||||
<Description>SPT UI Fixes</Description>
|
<Description>SPT UI Fixes</Description>
|
||||||
<Version>2.3.1</Version>
|
<Version>2.5.1</Version>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
<LangVersion>latest</LangVersion>
|
<LangVersion>latest</LangVersion>
|
||||||
<Configurations>Debug;Release</Configurations>
|
<Configurations>Debug;Release</Configurations>
|
||||||
@@ -79,7 +79,8 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup Condition="'$(TargetFramework.TrimEnd(`0123456789`))' == 'net'">
|
<ItemGroup Condition="'$(TargetFramework.TrimEnd(`0123456789`))' == 'net'">
|
||||||
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.2" PrivateAssets="all" />
|
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.2"
|
||||||
|
PrivateAssets="all" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "uifixes",
|
"name": "uifixes",
|
||||||
"version": "2.3.1",
|
"version": "2.5.1",
|
||||||
"main": "src/mod.js",
|
"main": "src/mod.js",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"author": "Tyfon",
|
"author": "Tyfon",
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import type { DependencyContainer } from "tsyringe";
|
import type { DependencyContainer } from "tsyringe";
|
||||||
|
|
||||||
|
import type { InraidController } from "@spt/controllers/InraidController";
|
||||||
import type { HideoutHelper } from "@spt/helpers/HideoutHelper";
|
import type { HideoutHelper } from "@spt/helpers/HideoutHelper";
|
||||||
import type { InRaidHelper } from "@spt/helpers/InRaidHelper";
|
|
||||||
import type { InventoryHelper } from "@spt/helpers/InventoryHelper";
|
import type { InventoryHelper } from "@spt/helpers/InventoryHelper";
|
||||||
import type { ItemHelper } from "@spt/helpers/ItemHelper";
|
import type { ItemHelper } from "@spt/helpers/ItemHelper";
|
||||||
import type { IHideoutSingleProductionStartRequestData } from "@spt/models/eft/hideout/IHideoutSingleProductionStartRequestData";
|
import type { IHideoutSingleProductionStartRequestData } from "@spt/models/eft/hideout/IHideoutSingleProductionStartRequestData";
|
||||||
@@ -13,8 +13,6 @@ import type { ICloner } from "@spt/utils/cloners/ICloner";
|
|||||||
import { RagfairLinkedSlotItemService } from "./RagfairLinkedSlotItemService";
|
import { RagfairLinkedSlotItemService } from "./RagfairLinkedSlotItemService";
|
||||||
|
|
||||||
import config from "../config/config.json";
|
import config from "../config/config.json";
|
||||||
import { RagfairOfferGenerator } from "@spt/generators/RagfairOfferGenerator";
|
|
||||||
import { IRagfairOffer } from "@spt/models/eft/ragfair/IRagfairOffer";
|
|
||||||
|
|
||||||
class UIFixes implements IPreSptLoadMod {
|
class UIFixes implements IPreSptLoadMod {
|
||||||
private databaseService: DatabaseService;
|
private databaseService: DatabaseService;
|
||||||
@@ -30,53 +28,29 @@ class UIFixes implements IPreSptLoadMod {
|
|||||||
|
|
||||||
// Keep quickbinds for items that aren't actually lost on death
|
// Keep quickbinds for items that aren't actually lost on death
|
||||||
container.afterResolution(
|
container.afterResolution(
|
||||||
"InRaidHelper",
|
"InraidController",
|
||||||
(_, inRaidHelper: InRaidHelper) => {
|
(_, inRaidController: InraidController) => {
|
||||||
const original = inRaidHelper.deleteInventory;
|
const original = inRaidController["performPostRaidActionsWhenDead"]; // protected, can only access by name
|
||||||
|
|
||||||
inRaidHelper.deleteInventory = (pmcData, sessionId) => {
|
inRaidController["performPostRaidActionsWhenDead"] = (postRaidSaveRequest, pmcData, sessionId) => {
|
||||||
// Copy the existing quickbinds
|
// Copy the existing quickbinds
|
||||||
const fastPanel = cloner.clone(pmcData.Inventory.fastPanel);
|
const fastPanel = cloner.clone(pmcData.Inventory.fastPanel);
|
||||||
|
|
||||||
// Nukes the inventory and the fastpanel
|
// Nukes the inventory and the fastpanel
|
||||||
original.call(inRaidHelper, pmcData, sessionId);
|
const result = original.call(inRaidController, postRaidSaveRequest, pmcData, sessionId);
|
||||||
|
|
||||||
// Restore the quickbinds for items that still exist
|
// Restore the quickbinds for items that still exist
|
||||||
|
try {
|
||||||
for (const index in fastPanel) {
|
for (const index in fastPanel) {
|
||||||
if (pmcData.Inventory.items.find(i => i._id == fastPanel[index])) {
|
if (pmcData.Inventory.items.find(i => i._id == fastPanel[index])) {
|
||||||
pmcData.Inventory.fastPanel[index] = fastPanel[index];
|
pmcData.Inventory.fastPanel[index] = fastPanel[index];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
} catch (error) {
|
||||||
},
|
this.logger.error(`UIFixes: Failed to restore quickbinds\n ${error}`);
|
||||||
{ frequency: "Always" }
|
|
||||||
);
|
|
||||||
|
|
||||||
// Trader offers with dogtag barter - fixed in next SPT release *after* 3.9.3
|
|
||||||
container.afterResolution(
|
|
||||||
"RagfairOfferGenerator",
|
|
||||||
(_, ragfairOfferGenerator: RagfairOfferGenerator) => {
|
|
||||||
const original = ragfairOfferGenerator["createOffer"]; // By name because protected
|
|
||||||
|
|
||||||
ragfairOfferGenerator["createOffer"] = (userID, time, items, barterScheme, loyalLevel, isPackOffer) => {
|
|
||||||
const offer: IRagfairOffer = original.call(
|
|
||||||
ragfairOfferGenerator,
|
|
||||||
userID,
|
|
||||||
time,
|
|
||||||
items,
|
|
||||||
barterScheme,
|
|
||||||
loyalLevel,
|
|
||||||
isPackOffer
|
|
||||||
);
|
|
||||||
|
|
||||||
for (let i = 0; i < offer.requirements.length; i++) {
|
|
||||||
if (barterScheme[i]["level"] !== undefined) {
|
|
||||||
offer.requirements[i]["level"] = barterScheme[i]["level"];
|
|
||||||
offer.requirements[i]["side"] = barterScheme[i]["side"];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return offer;
|
return result;
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
{ frequency: "Always" }
|
{ frequency: "Always" }
|
||||||
@@ -93,15 +67,30 @@ class UIFixes implements IPreSptLoadMod {
|
|||||||
const result = original.call(hideoutHelper, pmcData, body, sessionID);
|
const result = original.call(hideoutHelper, pmcData, body, sessionID);
|
||||||
|
|
||||||
// The items haven't been deleted yet, augment the list with their parentId
|
// The items haven't been deleted yet, augment the list with their parentId
|
||||||
|
try {
|
||||||
const bodyAsSingle = body as IHideoutSingleProductionStartRequestData;
|
const bodyAsSingle = body as IHideoutSingleProductionStartRequestData;
|
||||||
if (bodyAsSingle && bodyAsSingle.tools?.length > 0) {
|
if (bodyAsSingle && bodyAsSingle.tools?.length > 0) {
|
||||||
const requestTools = bodyAsSingle.tools;
|
const requestTools = bodyAsSingle.tools;
|
||||||
const tools = pmcData.Hideout.Production[body.recipeId].sptRequiredTools;
|
const tools = pmcData.Hideout.Production[body.recipeId].sptRequiredTools;
|
||||||
for (let i = 0; i < tools.length; i++) {
|
for (let i = 0; i < tools.length; i++) {
|
||||||
const originalTool = pmcData.Inventory.items.find(x => x._id === requestTools[i].id);
|
const originalTool = pmcData.Inventory.items.find(
|
||||||
|
x => x._id === requestTools[i].id
|
||||||
|
);
|
||||||
|
|
||||||
|
// If the tool is in the stash itself, skip it. Same check as InventoryHelper.isItemInStash
|
||||||
|
if (
|
||||||
|
originalTool.parentId === pmcData.Inventory.stash &&
|
||||||
|
originalTool.slotId === "hideout"
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
tools[i]["uifixes.returnTo"] = [originalTool.parentId, originalTool.slotId];
|
tools[i]["uifixes.returnTo"] = [originalTool.parentId, originalTool.slotId];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error(`UIFixes: Failed to save tool origin\n ${error}`);
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
@@ -121,11 +110,13 @@ class UIFixes implements IPreSptLoadMod {
|
|||||||
// If a tool marked with uifixes is there, try to return it to its original container
|
// If a tool marked with uifixes is there, try to return it to its original container
|
||||||
const tool = itemWithModsToAddClone[0];
|
const tool = itemWithModsToAddClone[0];
|
||||||
if (tool["uifixes.returnTo"]) {
|
if (tool["uifixes.returnTo"]) {
|
||||||
|
try {
|
||||||
const [containerId, slotId] = tool["uifixes.returnTo"];
|
const [containerId, slotId] = tool["uifixes.returnTo"];
|
||||||
|
|
||||||
const container = pmcData.Inventory.items.find(x => x._id === containerId);
|
const container = pmcData.Inventory.items.find(x => x._id === containerId);
|
||||||
if (container) {
|
if (container) {
|
||||||
const containerTemplate = itemHelper.getItem(container._tpl)[1];
|
const [foundTemplate, containerTemplate] = itemHelper.getItem(container._tpl);
|
||||||
|
if (foundTemplate && containerTemplate) {
|
||||||
const containerFS2D = inventoryHelper.getContainerMap(
|
const containerFS2D = inventoryHelper.getContainerMap(
|
||||||
containerTemplate._props.Grids[0]._props.cellsH,
|
containerTemplate._props.Grids[0]._props.cellsH,
|
||||||
containerTemplate._props.Grids[0]._props.cellsV,
|
containerTemplate._props.Grids[0]._props.cellsV,
|
||||||
@@ -168,6 +159,14 @@ class UIFixes implements IPreSptLoadMod {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error(`UIFixes: Encounted an error trying to put tool back.\n ${error}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.info(
|
||||||
|
"UIFixes: Unable to put tool back in its original container, returning it to stash."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return original.call(inventoryHelper, sessionId, request, pmcData, output);
|
return original.call(inventoryHelper, sessionId, request, pmcData, output);
|
||||||
};
|
};
|
||||||
@@ -214,7 +213,7 @@ class UIFixes implements IPreSptLoadMod {
|
|||||||
|
|
||||||
if (!quests[questId]) {
|
if (!quests[questId]) {
|
||||||
this.logger.error(
|
this.logger.error(
|
||||||
`Trader ${traderId} questassort references unknown quest ${JSON.stringify(questId)}!`
|
`UIFixes: Trader ${traderId} questassort references unknown quest ${JSON.stringify(questId)}!`
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@@ -32,6 +32,9 @@ public class EmptySlotMenuTrigger : MonoBehaviour, IPointerClickHandler, IPointe
|
|||||||
using EmptySlotContext context = new(slot, parentContext, itemUiContext);
|
using EmptySlotContext context = new(slot, parentContext, itemUiContext);
|
||||||
var interactions = itemUiContext.GetItemContextInteractions(context, null);
|
var interactions = itemUiContext.GetItemContextInteractions(context, null);
|
||||||
interactions.ExecuteInteraction(EItemInfoButton.LinkedSearch);
|
interactions.ExecuteInteraction(EItemInfoButton.LinkedSearch);
|
||||||
|
|
||||||
|
// Call this explicitly since screen transition prevents it from firing normally
|
||||||
|
OnPointerExit(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
83
src/ContextMenus/OpenInteractions.cs
Normal file
83
src/ContextMenus/OpenInteractions.cs
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
using Comfort.Common;
|
||||||
|
using EFT.UI;
|
||||||
|
using EFT.UI.DragAndDrop;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace UIFixes;
|
||||||
|
|
||||||
|
public class OpenInteractions(ItemContextAbstractClass itemContext, ItemUiContext itemUiContext) : ItemInfoInteractionsAbstractClass<OpenInteractions.Options>(itemUiContext)
|
||||||
|
{
|
||||||
|
private readonly ItemContextAbstractClass itemContext = itemContext;
|
||||||
|
|
||||||
|
public override void ExecuteInteractionInternal(Options interaction)
|
||||||
|
{
|
||||||
|
if (itemContext == null || itemContext.Item is not LootItemClass compoundItem)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var taskSerializer = itemUiContext_0.gameObject.AddComponent<NestedContainerTaskSerializer>();
|
||||||
|
taskSerializer.Initialize(GetNestedContainers(itemContext), containerContext =>
|
||||||
|
{
|
||||||
|
if (containerContext != null)
|
||||||
|
{
|
||||||
|
itemUiContext_0.OpenItem(containerContext.Item as LootItemClass, containerContext, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool IsActive(Options button)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IResult IsInteractive(Options button)
|
||||||
|
{
|
||||||
|
return SuccessfulResult.New;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool HasIcons
|
||||||
|
{
|
||||||
|
get { return false; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Options
|
||||||
|
{
|
||||||
|
All
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerable<ItemContextAbstractClass> GetNestedContainers(ItemContextAbstractClass first)
|
||||||
|
{
|
||||||
|
var windowRoot = Singleton<PreloaderUI>.Instance;
|
||||||
|
LootItemClass parent = first.Item as LootItemClass;
|
||||||
|
|
||||||
|
yield return first;
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
var innerContainers = parent.GetFirstLevelItems()
|
||||||
|
.Where(i => i != parent)
|
||||||
|
.Where(i => i is LootItemClass innerContainer && innerContainer.Grids.Any());
|
||||||
|
if (innerContainers.Count() != 1)
|
||||||
|
{
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var targetId = innerContainers.First().Id;
|
||||||
|
var targetItemView = windowRoot.GetComponentsInChildren<GridItemView>().FirstOrDefault(itemView => itemView.Item.Id == targetId);
|
||||||
|
if (targetItemView == null)
|
||||||
|
{
|
||||||
|
yield return null; // Keeps returning null until the window is open
|
||||||
|
}
|
||||||
|
|
||||||
|
parent = targetItemView.Item as LootItemClass;
|
||||||
|
yield return targetItemView.ItemContext;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class NestedContainerTaskSerializer : TaskSerializer<ItemContextAbstractClass> { }
|
22
src/Extensions.cs
Normal file
22
src/Extensions.cs
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using EFT.InventoryLogic;
|
||||||
|
|
||||||
|
namespace UIFixes;
|
||||||
|
|
||||||
|
public static class Extensions
|
||||||
|
{
|
||||||
|
public static Item GetRootItemNotEquipment(this Item item)
|
||||||
|
{
|
||||||
|
return item.GetAllParentItemsAndSelf(true).LastOrDefault(i => i is not EquipmentClass) ?? item;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Item GetRootItemNotEquipment(this ItemAddress itemAddress)
|
||||||
|
{
|
||||||
|
if (itemAddress.Container == null || itemAddress.Container.ParentItem == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return itemAddress.Container.ParentItem.GetRootItemNotEquipment();
|
||||||
|
}
|
||||||
|
}
|
@@ -1,5 +1,7 @@
|
|||||||
using EFT.InventoryLogic;
|
using EFT.InventoryLogic;
|
||||||
using EFT.UI.DragAndDrop;
|
using EFT.UI.DragAndDrop;
|
||||||
|
using EFT.UI.Ragfair;
|
||||||
|
using System;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
@@ -87,3 +89,29 @@ public static class ExtraItemViewStatsProperties
|
|||||||
public static void SetHideMods(this ItemViewStats itemViewStats, bool value) => properties.GetOrCreateValue(itemViewStats).HideMods = value;
|
public static void SetHideMods(this ItemViewStats itemViewStats, bool value) => properties.GetOrCreateValue(itemViewStats).HideMods = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class ExtraItemMarketPricesPanelProperties
|
||||||
|
{
|
||||||
|
private static readonly ConditionalWeakTable<ItemMarketPricesPanel, Properties> properties = new();
|
||||||
|
|
||||||
|
private class Properties
|
||||||
|
{
|
||||||
|
public Action OnMarketPricesCallback = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Action GetOnMarketPricesCallback(this ItemMarketPricesPanel panel) => properties.GetOrCreateValue(panel).OnMarketPricesCallback;
|
||||||
|
public static void SetOnMarketPricesCallback(this ItemMarketPricesPanel panel, Action handler) => properties.GetOrCreateValue(panel).OnMarketPricesCallback = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ExtraEventResultProperties
|
||||||
|
{
|
||||||
|
private static readonly ConditionalWeakTable<GClass2803, Properties> properties = new();
|
||||||
|
|
||||||
|
private class Properties
|
||||||
|
{
|
||||||
|
public MoveOperation MoveOperation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MoveOperation GetMoveOperation(this GClass2803 result) => properties.GetOrCreateValue(result).MoveOperation;
|
||||||
|
public static void SetMoveOperation(this GClass2803 result, MoveOperation operation) => properties.GetOrCreateValue(result).MoveOperation = operation;
|
||||||
|
}
|
||||||
|
|
@@ -1,4 +1,4 @@
|
|||||||
// These shouln't change (unless they do)
|
// These shouldn't change (unless they do)
|
||||||
global using GridItemAddress = ItemAddressClass;
|
global using GridItemAddress = ItemAddressClass;
|
||||||
global using DragItemContext = ItemContextClass;
|
global using DragItemContext = ItemContextClass;
|
||||||
global using InsuranceItem = ItemClass;
|
global using InsuranceItem = ItemClass;
|
||||||
@@ -19,6 +19,7 @@ global using ItemSorter = GClass2772;
|
|||||||
global using ItemWithLocation = GClass2521;
|
global using ItemWithLocation = GClass2521;
|
||||||
global using SearchableGrid = GClass2516;
|
global using SearchableGrid = GClass2516;
|
||||||
global using CursorManager = GClass3034;
|
global using CursorManager = GClass3034;
|
||||||
|
global using Helmet = GClass2651;
|
||||||
|
|
||||||
// State machine states
|
// State machine states
|
||||||
global using FirearmReadyState = EFT.Player.FirearmController.GClass1619;
|
global using FirearmReadyState = EFT.Player.FirearmController.GClass1619;
|
||||||
@@ -38,10 +39,17 @@ global using NoPossibleActionsError = GClass3317;
|
|||||||
global using CannotSortError = GClass3325;
|
global using CannotSortError = GClass3325;
|
||||||
global using FailedToSortError = GClass3326;
|
global using FailedToSortError = GClass3326;
|
||||||
global using MoveSameSpaceError = InteractionsHandlerClass.GClass3353;
|
global using MoveSameSpaceError = InteractionsHandlerClass.GClass3353;
|
||||||
|
global using NotModdableInRaidError = GClass3321;
|
||||||
|
global using MultitoolNeededError = GClass3322;
|
||||||
|
global using ModVitalPartInRaidError = GClass3323;
|
||||||
|
global using SlotNotEmptyError = EFT.InventoryLogic.Slot.GClass3339;
|
||||||
|
|
||||||
// Operations
|
// Operations
|
||||||
global using ItemOperation = GStruct413;
|
global using ItemOperation = GStruct413;
|
||||||
global using MoveOperation = GClass2802;
|
global using MoveOperation = GClass2802;
|
||||||
|
global using AddOperation = GClass2798;
|
||||||
|
global using ResizeOperation = GClass2803;
|
||||||
|
global using FoldOperation = GClass2815;
|
||||||
global using NoOpMove = GClass2795;
|
global using NoOpMove = GClass2795;
|
||||||
global using BindOperation = GClass2818;
|
global using BindOperation = GClass2818;
|
||||||
global using SortOperation = GClass2824;
|
global using SortOperation = GClass2824;
|
@@ -63,8 +63,8 @@ public class DrawMultiSelect : MonoBehaviour
|
|||||||
{
|
{
|
||||||
bool shiftDown = Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift);
|
bool shiftDown = Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift);
|
||||||
|
|
||||||
// Only need to check we aren't over draggables/clickables if the multiselect key is left mouse
|
// Special case: if selection key is mouse0 (left), don't start selection if over a clickable
|
||||||
if (Settings.SelectionBoxKey.Value.MainKey == KeyCode.Mouse0 && !shiftDown && !MouseIsOverClickable())
|
if (Settings.SelectionBoxKey.Value.MainKey == KeyCode.Mouse0 && !shiftDown && MouseIsOverClickable())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -74,10 +74,14 @@ public class DrawMultiSelect : MonoBehaviour
|
|||||||
secondary = shiftDown;
|
secondary = shiftDown;
|
||||||
|
|
||||||
if (!secondary)
|
if (!secondary)
|
||||||
|
{
|
||||||
|
// Special case: if selection key is any mouse key (center,right), don't clear selection on mouse down if over item
|
||||||
|
if (Settings.SelectionBoxKey.Value.MainKey != KeyCode.Mouse1 && Settings.SelectionBoxKey.Value.MainKey != KeyCode.Mouse2 || !MouseIsOverItem())
|
||||||
{
|
{
|
||||||
MultiSelect.Clear();
|
MultiSelect.Clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (drawing && !Settings.SelectionBoxKey.Value.IsPressedIgnoreOthers())
|
if (drawing && !Settings.SelectionBoxKey.Value.IsPressedIgnoreOthers())
|
||||||
{
|
{
|
||||||
@@ -165,12 +169,17 @@ public class DrawMultiSelect : MonoBehaviour
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool MouseIsOverClickable()
|
private bool MouseIsOverItem()
|
||||||
{
|
{
|
||||||
// checking ItemUiContext is a quick and easy way to know the mouse is over an item
|
// checking ItemUiContext is a quick and easy way to know the mouse is over an item
|
||||||
if (ItemUiContext.Instance.R().ItemContext != null)
|
return ItemUiContext.Instance.R().ItemContext != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool MouseIsOverClickable()
|
||||||
{
|
{
|
||||||
return false;
|
if (MouseIsOverItem())
|
||||||
|
{
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
PointerEventData eventData = new(EventSystem.current)
|
PointerEventData eventData = new(EventSystem.current)
|
||||||
@@ -179,26 +188,42 @@ public class DrawMultiSelect : MonoBehaviour
|
|||||||
};
|
};
|
||||||
|
|
||||||
List<RaycastResult> results = [];
|
List<RaycastResult> results = [];
|
||||||
|
preloaderRaycaster.Raycast(eventData, results); // preload objects are on top, so check that first
|
||||||
localRaycaster.Raycast(eventData, results);
|
localRaycaster.Raycast(eventData, results);
|
||||||
preloaderRaycaster.Raycast(eventData, results);
|
|
||||||
|
|
||||||
foreach (GameObject gameObject in results.Select(r => r.gameObject))
|
GameObject gameObject = results.FirstOrDefault().gameObject;
|
||||||
|
if (gameObject == null)
|
||||||
{
|
{
|
||||||
var draggables = gameObject.GetComponents<MonoBehaviour>()
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var draggables = gameObject.GetComponentsInParent<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 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 is not ScrollRectNoDrag) // this disables scrolling, it doesn't add it
|
||||||
.Where(c => c.name != "Inner"); // there's a random DragTrigger sitting in ItemInfoWindows
|
.Where(c => c.name != "Inner"); // there's a random DragTrigger sitting in ItemInfoWindows
|
||||||
|
|
||||||
var clickables = gameObject.GetComponents<MonoBehaviour>()
|
var clickables = gameObject.GetComponentsInParent<MonoBehaviour>()
|
||||||
.Where(c => c is IPointerClickHandler || c is IPointerDownHandler || c is IPointerUpHandler);
|
.Where(c => c is IPointerClickHandler || c is IPointerDownHandler || c is IPointerUpHandler)
|
||||||
|
.Where(c => c is not EmptySlotMenuTrigger); // ignore empty slots that are right-clickable due to UIFixes
|
||||||
|
|
||||||
|
// Windows are clickable to focus them, but that shouldn't block selection
|
||||||
|
var windows = clickables
|
||||||
|
.Where(c => c is UIInputNode) // Windows<>'s parent, cheap check
|
||||||
|
.Where(c =>
|
||||||
|
{
|
||||||
|
// Most window types implement IPointerClickHandler and inherit directly from Window<>
|
||||||
|
Type baseType = c.GetType().BaseType;
|
||||||
|
return baseType != null && baseType.IsGenericType && baseType.GetGenericTypeDefinition() == typeof(Window<>);
|
||||||
|
});
|
||||||
|
|
||||||
|
clickables = clickables.Except(windows);
|
||||||
|
|
||||||
if (draggables.Any() || clickables.Any())
|
if (draggables.Any() || clickables.Any())
|
||||||
{
|
{
|
||||||
return false;
|
return true;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsOnTop(Rect itemRect, Transform itemTransform, GraphicRaycaster raycaster)
|
private bool IsOnTop(Rect itemRect, Transform itemTransform, GraphicRaycaster raycaster)
|
@@ -419,6 +419,38 @@ public class MultiSelect
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void WishlistAll(ItemUiContext itemUiContext, BaseItemInfoInteractions interactions, bool add, bool allOrNothing)
|
||||||
|
{
|
||||||
|
EItemInfoButton interaction = add ? EItemInfoButton.AddToWishlist : EItemInfoButton.RemoveFromWishlist;
|
||||||
|
if (!allOrNothing || InteractionCount(interaction, itemUiContext) == Count)
|
||||||
|
{
|
||||||
|
var taskSerializer = itemUiContext.gameObject.AddComponent<MultiSelectItemContextTaskSerializer>();
|
||||||
|
taskSerializer.Initialize(ItemContexts.Where(ic => InteractionAvailable(ic, interaction, itemUiContext)),
|
||||||
|
itemContext =>
|
||||||
|
{
|
||||||
|
TaskCompletionSource taskSource = new();
|
||||||
|
void callback()
|
||||||
|
{
|
||||||
|
interactions.RequestRedrawForItem();
|
||||||
|
taskSource.Complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (add)
|
||||||
|
{
|
||||||
|
itemUiContext.AddToWishList(itemContext.Item, callback);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
itemUiContext.RemoveFromWishList(itemContext.Item, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
return taskSource.Task;
|
||||||
|
});
|
||||||
|
|
||||||
|
itemUiContext.Tooltip?.Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static void ShowSelection(GridItemView itemView)
|
private static void ShowSelection(GridItemView itemView)
|
||||||
{
|
{
|
||||||
GameObject selectedMark = itemView.transform.Find("SelectedMark")?.gameObject;
|
GameObject selectedMark = itemView.transform.Find("SelectedMark")?.gameObject;
|
31
src/Multiselect/MultiSelectController.cs
Normal file
31
src/Multiselect/MultiSelectController.cs
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
using EFT.InventoryLogic;
|
||||||
|
using EFT.UI;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace UIFixes;
|
||||||
|
|
||||||
|
// This class exists to create a layer between MultiSelectInterop and the MultiSelect implementation.
|
||||||
|
public static class MultiSelectController
|
||||||
|
{
|
||||||
|
public static int GetCount()
|
||||||
|
{
|
||||||
|
return MultiSelect.Count;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<Item> GetItems()
|
||||||
|
{
|
||||||
|
return MultiSelect.SortedItemContexts().Select(ic => ic.Item);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Task Apply(Func<Item, Task> func, ItemUiContext itemUiContext = null)
|
||||||
|
{
|
||||||
|
itemUiContext ??= ItemUiContext.Instance;
|
||||||
|
var taskSerializer = itemUiContext.gameObject.AddComponent<ItemTaskSerializer>();
|
||||||
|
return taskSerializer.Initialize(GetItems(), func);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ItemTaskSerializer : TaskSerializer<Item> { }
|
139
src/Multiselect/MultiSelectInterop.cs
Normal file
139
src/Multiselect/MultiSelectInterop.cs
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
using BepInEx;
|
||||||
|
using BepInEx.Bootstrap;
|
||||||
|
using EFT.InventoryLogic;
|
||||||
|
using EFT.UI;
|
||||||
|
using HarmonyLib;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
/*
|
||||||
|
UI Fixes Multi-Select InterOp
|
||||||
|
|
||||||
|
First, add the following attribute to your plugin class:
|
||||||
|
|
||||||
|
[BepInDependency("Tyfon.UIFixes", BepInDependency.DependencyFlags.SoftDependency)]
|
||||||
|
|
||||||
|
This will ensure UI Fixes is loaded already when your code is run. It will fail gracefully if UI Fixes is missing.
|
||||||
|
|
||||||
|
Second, add this file to your project. Use the below UIFixesInterop.MultiSelect static methods, no explicit initialization required.
|
||||||
|
|
||||||
|
Some things to keep in mind:
|
||||||
|
- While you can use MultiSelect.Items to get the items, this should only be used for reading purproses. If you need to
|
||||||
|
execute an operation on the items, I strongly suggest using the provided MultiSelect.Apply() method.
|
||||||
|
|
||||||
|
- Apply() will execute the provided operation on each item, sequentially (sorted by grid order), maximum of one operation per frame.
|
||||||
|
It does this because strange bugs manifest if you try to do more than one thing in a single frame.
|
||||||
|
|
||||||
|
- If the operation you are passing to Apply() does anything async, use the overload that takes a Func<Item, Task>. It will wait
|
||||||
|
until each operation is over before doing the next. This is especially important if an operation could be affected by the preceding one,
|
||||||
|
for example in a quick-move where the avaiable space changes. It's also required if you are doing anything in-raid that will trigger
|
||||||
|
an animation, as starting the next one before it is complete will likely cancel the first.
|
||||||
|
*/
|
||||||
|
namespace UIFixesInterop
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides access to UI Fixes' multiselect functionality.
|
||||||
|
/// </summary>
|
||||||
|
internal static class MultiSelect
|
||||||
|
{
|
||||||
|
private static readonly Version RequiredVersion = new Version(2, 5);
|
||||||
|
|
||||||
|
private static bool? UIFixesLoaded;
|
||||||
|
|
||||||
|
private static Type MultiSelectType;
|
||||||
|
private static MethodInfo GetCountMethod;
|
||||||
|
private static MethodInfo GetItemsMethod;
|
||||||
|
private static MethodInfo ApplyMethod;
|
||||||
|
|
||||||
|
/// <value><c>Count</c> represents the number of items in the current selection, 0 if UI Fixes is not present.</value>
|
||||||
|
public static int Count
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (!Loaded())
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (int)GetCountMethod.Invoke(null, new object[] { });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <value><c>Items</c> is an enumerable list of items in the current selection, empty if UI Fixes is not present.</value>
|
||||||
|
public static IEnumerable<Item> Items
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (!Loaded())
|
||||||
|
{
|
||||||
|
return new Item[] { };
|
||||||
|
}
|
||||||
|
|
||||||
|
return (IEnumerable<Item>)GetItemsMethod.Invoke(null, new object[] { });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This method takes an <c>Action</c> and calls it *sequentially* on each item in the current selection.
|
||||||
|
/// Will no-op if UI Fixes is not present.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="action">The action to call on each item.</param>
|
||||||
|
/// <param name="itemUiContext">Optional <c>ItemUiContext</c>; will use <c>ItemUiContext.Instance</c> if not provided.</param>
|
||||||
|
public static void Apply(Action<Item> action, ItemUiContext itemUiContext = null)
|
||||||
|
{
|
||||||
|
if (!Loaded())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Func<Item, Task> func = item =>
|
||||||
|
{
|
||||||
|
action(item);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
};
|
||||||
|
|
||||||
|
ApplyMethod.Invoke(null, new object[] { func, itemUiContext });
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This method takes an <c>Func</c> that returns a <c>Task</c> and calls it *sequentially* on each item in the current selection.
|
||||||
|
/// Will return a completed task immediately if UI Fixes is not present.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="func">The function to call on each item</param>
|
||||||
|
/// <param name="itemUiContext">Optional <c>ItemUiContext</c>; will use <c>ItemUiContext.Instance</c> if not provided.</param>
|
||||||
|
/// <returns>A <c>Task</c> that will complete when all the function calls are complete.</returns>
|
||||||
|
public static Task Apply(Func<Item, Task> func, ItemUiContext itemUiContext = null)
|
||||||
|
{
|
||||||
|
if (!Loaded())
|
||||||
|
{
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (Task)ApplyMethod.Invoke(null, new object[] { func, itemUiContext });
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool Loaded()
|
||||||
|
{
|
||||||
|
if (!UIFixesLoaded.HasValue)
|
||||||
|
{
|
||||||
|
bool present = Chainloader.PluginInfos.TryGetValue("Tyfon.UIFixes", out PluginInfo pluginInfo);
|
||||||
|
UIFixesLoaded = present && pluginInfo.Metadata.Version >= RequiredVersion;
|
||||||
|
|
||||||
|
if (UIFixesLoaded.Value)
|
||||||
|
{
|
||||||
|
MultiSelectType = Type.GetType("UIFixes.MultiSelectController, Tyfon.UIFixes");
|
||||||
|
if (MultiSelectType != null)
|
||||||
|
{
|
||||||
|
GetCountMethod = AccessTools.Method(MultiSelectType, "GetCount");
|
||||||
|
GetItemsMethod = AccessTools.Method(MultiSelectType, "GetItems");
|
||||||
|
ApplyMethod = AccessTools.Method(MultiSelectType, "Apply");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return UIFixesLoaded.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
211
src/Patches/AddOfferClickablePricesPatches.cs
Normal file
211
src/Patches/AddOfferClickablePricesPatches.cs
Normal file
@@ -0,0 +1,211 @@
|
|||||||
|
using EFT.InventoryLogic;
|
||||||
|
using EFT.UI.Ragfair;
|
||||||
|
using HarmonyLib;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using SPT.Reflection.Patching;
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using TMPro;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.EventSystems;
|
||||||
|
using UnityEngine.UI;
|
||||||
|
|
||||||
|
namespace UIFixes;
|
||||||
|
|
||||||
|
public static class AddOfferClickablePricesPatches
|
||||||
|
{
|
||||||
|
public static void Enable()
|
||||||
|
{
|
||||||
|
new AddButtonPatch().Enable();
|
||||||
|
new MarketPriceUpdatePatch().Enable();
|
||||||
|
new BulkTogglePatch().Enable();
|
||||||
|
new MultipleStacksPatch().Enable();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AddButtonPatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return AccessTools.Method(typeof(AddOfferWindow), nameof(AddOfferWindow.Show));
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPostfix]
|
||||||
|
public static void Postfix(AddOfferWindow __instance, ItemMarketPricesPanel ____pricesPanel, RequirementView[] ____requirementViews)
|
||||||
|
{
|
||||||
|
var panel = ____pricesPanel.R();
|
||||||
|
|
||||||
|
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 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);
|
||||||
|
|
||||||
|
____pricesPanel.SetOnMarketPricesCallback(() => PopulateOfferPrice(__instance, ____pricesPanel, rublesRequirement));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MarketPriceUpdatePatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return AccessTools.Method(typeof(ItemMarketPricesPanel), nameof(ItemMarketPricesPanel.method_1));
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPostfix]
|
||||||
|
public static void Postfix(ItemMarketPricesPanel __instance)
|
||||||
|
{
|
||||||
|
var action = __instance.GetOnMarketPricesCallback();
|
||||||
|
action?.Invoke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class BulkTogglePatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return AccessTools.Method(typeof(AddOfferWindow), nameof(AddOfferWindow.method_12));
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPostfix]
|
||||||
|
public static void Postfix(AddOfferWindow __instance, bool arg, ItemMarketPricesPanel ____pricesPanel, RequirementView[] ____requirementViews)
|
||||||
|
{
|
||||||
|
if (!Settings.UpdatePriceOnBulk.Value)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RequirementView rublesRequirement = ____requirementViews.First(rv => rv.name == "Requirement (RUB)");
|
||||||
|
double currentPrice = rublesRequirement.Requirement.PreciseCount;
|
||||||
|
if (currentPrice <= 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRequirement will multiply (or not), so just need the individual price
|
||||||
|
double individualPrice = arg ? currentPrice : Math.Ceiling(currentPrice / __instance.Int32_0);
|
||||||
|
SetRequirement(__instance, rublesRequirement, individualPrice);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called when item selection changes. Handles updating price if bulk is (or was) checked
|
||||||
|
public class MultipleStacksPatch : ModulePatch
|
||||||
|
{
|
||||||
|
private static bool WasBulk;
|
||||||
|
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return AccessTools.Method(typeof(AddOfferWindow), nameof(AddOfferWindow.method_9));
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPrefix]
|
||||||
|
public static void Prefix(AddOfferWindow __instance)
|
||||||
|
{
|
||||||
|
WasBulk = __instance.R().BulkOffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPostfix]
|
||||||
|
public static void Postfix(AddOfferWindow __instance, Item item, bool selected, ItemMarketPricesPanel ____pricesPanel, RequirementView[] ____requirementViews)
|
||||||
|
{
|
||||||
|
if (!Settings.UpdatePriceOnBulk.Value || __instance.Int32_0 < 1)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bulk can autochange when selecting/deselecting, so only bail if it wasn't and still isn't bulk
|
||||||
|
if (!WasBulk && !__instance.R().BulkOffer)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var rublesRequirement = ____requirementViews.First(rv => rv.name == "Requirement (RUB)");
|
||||||
|
double currentPrice = rublesRequirement.Requirement.PreciseCount;
|
||||||
|
|
||||||
|
// Need to figure out the price per item *before* this item was added/removed
|
||||||
|
int oldCount = __instance.Int32_0 + (selected ? -item.StackObjectsCount : item.StackObjectsCount);
|
||||||
|
if (oldCount <= 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SetRequirement(__instance, rublesRequirement, currentPrice / oldCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SetRequirement(AddOfferWindow window, RequirementView requirement, double price)
|
||||||
|
{
|
||||||
|
if (window.R().BulkOffer)
|
||||||
|
{
|
||||||
|
price *= window.Int32_0; // offer item count
|
||||||
|
}
|
||||||
|
|
||||||
|
requirement.method_0(price.ToString("F0"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void PopulateOfferPrice(AddOfferWindow window, ItemMarketPricesPanel pricesPanel, RequirementView rublesRequirement)
|
||||||
|
{
|
||||||
|
switch (Settings.AutoOfferPrice.Value)
|
||||||
|
{
|
||||||
|
case AutoFleaPrice.Minimum:
|
||||||
|
SetRequirement(window, rublesRequirement, pricesPanel.Minimum);
|
||||||
|
break;
|
||||||
|
case AutoFleaPrice.Average:
|
||||||
|
SetRequirement(window, rublesRequirement, pricesPanel.Average);
|
||||||
|
break;
|
||||||
|
case AutoFleaPrice.Maximum:
|
||||||
|
SetRequirement(window, rublesRequirement, pricesPanel.Maximum);
|
||||||
|
break;
|
||||||
|
case AutoFleaPrice.None:
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public class HighlightButton : Button
|
||||||
|
{
|
||||||
|
private Color originalColor;
|
||||||
|
bool originalOverrideColorTags;
|
||||||
|
|
||||||
|
private TextMeshProUGUI _text;
|
||||||
|
private TextMeshProUGUI Text
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_text == null)
|
||||||
|
{
|
||||||
|
_text = GetComponent<TextMeshProUGUI>();
|
||||||
|
}
|
||||||
|
|
||||||
|
return _text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnPointerEnter([NotNull] PointerEventData eventData)
|
||||||
|
{
|
||||||
|
base.OnPointerEnter(eventData);
|
||||||
|
|
||||||
|
originalColor = Text.color;
|
||||||
|
originalOverrideColorTags = Text.overrideColorTags;
|
||||||
|
|
||||||
|
Text.overrideColorTags = true;
|
||||||
|
Text.color = Color.white;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnPointerExit([NotNull] PointerEventData eventData)
|
||||||
|
{
|
||||||
|
base.OnPointerExit(eventData);
|
||||||
|
|
||||||
|
Text.overrideColorTags = originalOverrideColorTags;
|
||||||
|
Text.color = originalColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
219
src/Patches/AddOfferContextMenuPatches.cs
Normal file
219
src/Patches/AddOfferContextMenuPatches.cs
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
using Comfort.Common;
|
||||||
|
using EFT.InventoryLogic;
|
||||||
|
using EFT.UI;
|
||||||
|
using EFT.UI.Ragfair;
|
||||||
|
using HarmonyLib;
|
||||||
|
using SPT.Reflection.Patching;
|
||||||
|
using SPT.Reflection.Utils;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace UIFixes;
|
||||||
|
|
||||||
|
public static class AddOfferContextMenuPatches
|
||||||
|
{
|
||||||
|
private static Item AddOfferItem = null;
|
||||||
|
|
||||||
|
public static void Enable()
|
||||||
|
{
|
||||||
|
new AddOfferInventoryMenuPatch().Enable();
|
||||||
|
new AddOfferTradingMenuPatch().Enable();
|
||||||
|
new AddOfferIsActivePatch().Enable();
|
||||||
|
new AddOfferIsInteractivePatch().Enable();
|
||||||
|
new AddOfferNameIconPatch().Enable();
|
||||||
|
|
||||||
|
new AddOfferExecutePatch().Enable();
|
||||||
|
new ShowAddOfferWindowPatch().Enable();
|
||||||
|
new SelectItemPatch().Enable();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AddOfferInventoryMenuPatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return AccessTools.DeclaredProperty(R.InventoryInteractions.CompleteType, "AvailableInteractions").GetMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPostfix]
|
||||||
|
public static void Postfix(ref IEnumerable<EItemInfoButton> __result)
|
||||||
|
{
|
||||||
|
if (Settings.AddOfferContextMenu.Value)
|
||||||
|
{
|
||||||
|
var list = __result.ToList();
|
||||||
|
list.Insert(list.IndexOf(EItemInfoButton.Tag), EItemInfoButtonExt.AddOffer);
|
||||||
|
__result = list;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AddOfferTradingMenuPatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return AccessTools.DeclaredProperty(R.TradingInteractions.Type, "AvailableInteractions").GetMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPostfix]
|
||||||
|
public static void Postfix(ref IEnumerable<EItemInfoButton> __result)
|
||||||
|
{
|
||||||
|
if (Settings.AddOfferContextMenu.Value)
|
||||||
|
{
|
||||||
|
var list = __result.ToList();
|
||||||
|
list.Insert(list.IndexOf(EItemInfoButton.Tag), EItemInfoButtonExt.AddOffer);
|
||||||
|
__result = list;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AddOfferNameIconPatch : ModulePatch
|
||||||
|
{
|
||||||
|
private static Sprite FleaSprite = null;
|
||||||
|
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return AccessTools.Method(typeof(InteractionButtonsContainer), nameof(InteractionButtonsContainer.Show)).MakeGenericMethod(typeof(EItemInfoButton));
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPrefix]
|
||||||
|
public static void Prefix(ref IReadOnlyDictionary<EItemInfoButton, string> names, ref IReadOnlyDictionary<EItemInfoButton, Sprite> icons)
|
||||||
|
{
|
||||||
|
names ??= new Dictionary<EItemInfoButton, string>()
|
||||||
|
{
|
||||||
|
{ EItemInfoButtonExt.AddOffer, "ragfair/OFFER ADD" }
|
||||||
|
};
|
||||||
|
|
||||||
|
FleaSprite ??= Resources.FindObjectsOfTypeAll<Sprite>().Single(s => s.name == "icon_flea_market");
|
||||||
|
icons ??= new Dictionary<EItemInfoButton, Sprite>()
|
||||||
|
{
|
||||||
|
{ EItemInfoButtonExt.AddOffer, FleaSprite }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AddOfferIsActivePatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return AccessTools.Method(R.ContextMenuHelper.Type, "IsActive");
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPrefix]
|
||||||
|
public static bool Prefix(EItemInfoButton button, ref bool __result)
|
||||||
|
{
|
||||||
|
if (button != EItemInfoButtonExt.AddOffer)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Plugin.InRaid())
|
||||||
|
{
|
||||||
|
__result = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
__result = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AddOfferIsInteractivePatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return AccessTools.Method(R.ContextMenuHelper.Type, "IsInteractive");
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPostfix]
|
||||||
|
public static void Postfix(EItemInfoButton button, ref IResult __result, Item ___item_0)
|
||||||
|
{
|
||||||
|
if (button != EItemInfoButtonExt.AddOffer)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ISession session = PatchConstants.BackEndSession;
|
||||||
|
RagFairClass ragfair = session.RagFair;
|
||||||
|
if (ragfair.Status != RagFairClass.ERagFairStatus.Available)
|
||||||
|
{
|
||||||
|
__result = new FailedResult(ragfair.GetFormattedStatusDescription());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ragfair.MyOffersCount >= ragfair.GetMaxOffersCount(ragfair.MyRating))
|
||||||
|
{
|
||||||
|
__result = new FailedResult("ragfair/Reached maximum amount of offers");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RagfairOfferSellHelperClass ragfairHelper = new(session.Profile, session.Profile.Inventory.Stash.Grid);
|
||||||
|
if (!ragfairHelper.method_4(___item_0, out string error))
|
||||||
|
{
|
||||||
|
__result = new FailedResult(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AddOfferExecutePatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return AccessTools.Method(typeof(BaseItemInfoInteractions), nameof(BaseItemInfoInteractions.ExecuteInteractionInternal));
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPrefix]
|
||||||
|
public static bool Prefix(ItemInfoInteractionsAbstractClass<EItemInfoButton> __instance, EItemInfoButton interaction, Item ___item_0)
|
||||||
|
{
|
||||||
|
if (interaction != EItemInfoButtonExt.AddOffer)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
AddOfferItem = ___item_0;
|
||||||
|
|
||||||
|
__instance.ExecuteInteractionInternal(EItemInfoButton.FilterSearch);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ShowAddOfferWindowPatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return AccessTools.Method(typeof(RagfairScreen), nameof(RagfairScreen.Show));
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPostfix]
|
||||||
|
public static void Postfix(RagfairScreen __instance)
|
||||||
|
{
|
||||||
|
if (AddOfferItem == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
__instance.method_27(); // click the add offer button
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SelectItemPatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return AccessTools.Method(typeof(AddOfferWindow), nameof(AddOfferWindow.Show));
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPostfix]
|
||||||
|
public static void Postfix(RagfairOfferSellHelperClass ___ragfairOfferSellHelperClass)
|
||||||
|
{
|
||||||
|
if (AddOfferItem == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
___ragfairOfferSellHelperClass.SelectItem(AddOfferItem);
|
||||||
|
AddOfferItem = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
146
src/Patches/AimToggleHoldPatches.cs
Normal file
146
src/Patches/AimToggleHoldPatches.cs
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
using Comfort.Common;
|
||||||
|
using EFT.InputSystem;
|
||||||
|
using HarmonyLib;
|
||||||
|
using JsonType;
|
||||||
|
using SPT.Reflection.Patching;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace UIFixes;
|
||||||
|
|
||||||
|
public static class AimToggleHoldPatches
|
||||||
|
{
|
||||||
|
public static void Enable()
|
||||||
|
{
|
||||||
|
new AddTwoKeyStatesPatch().Enable();
|
||||||
|
new AddOneKeyStatesPatch().Enable();
|
||||||
|
new UpdateInputPatch().Enable();
|
||||||
|
|
||||||
|
Settings.ToggleOrHoldAim.SettingChanged += OnSettingChanged;
|
||||||
|
Settings.ToggleOrHoldSprint.SettingChanged += OnSettingChanged;
|
||||||
|
Settings.ToggleOrHoldTactical.SettingChanged += OnSettingChanged;
|
||||||
|
Settings.ToggleOrHoldHeadlight.SettingChanged += OnSettingChanged;
|
||||||
|
Settings.ToggleOrHoldGoggles.SettingChanged += OnSettingChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AddTwoKeyStatesPatch : ModulePatch
|
||||||
|
{
|
||||||
|
private static FieldInfo StateMachineArray;
|
||||||
|
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
StateMachineArray = AccessTools.Field(typeof(KeyCombination), "keyCombinationState_1");
|
||||||
|
return AccessTools.GetDeclaredConstructors(typeof(ToggleKeyCombination)).Single();
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPostfix]
|
||||||
|
public static void Postfix(ToggleKeyCombination __instance, EGameKey gameKey, ECommand disableCommand, KeyCombination.KeyCombinationState[] ___keyCombinationState_1)
|
||||||
|
{
|
||||||
|
bool useToggleHold = gameKey switch
|
||||||
|
{
|
||||||
|
EGameKey.Aim => Settings.ToggleOrHoldAim.Value,
|
||||||
|
EGameKey.Sprint => Settings.ToggleOrHoldSprint.Value,
|
||||||
|
_ => false
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!useToggleHold)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<KeyCombination.KeyCombinationState> states = new(___keyCombinationState_1)
|
||||||
|
{
|
||||||
|
new ToggleHoldIdleState(__instance),
|
||||||
|
new ToggleHoldClickOrHoldState(__instance),
|
||||||
|
new ToggleHoldHoldState(__instance, disableCommand)
|
||||||
|
};
|
||||||
|
|
||||||
|
StateMachineArray.SetValue(__instance, states.ToArray());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AddOneKeyStatesPatch : ModulePatch
|
||||||
|
{
|
||||||
|
private static FieldInfo StateMachineArray;
|
||||||
|
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
StateMachineArray = AccessTools.Field(typeof(KeyCombination), "keyCombinationState_1");
|
||||||
|
return AccessTools.GetDeclaredConstructors(typeof(KeyCombination)).Single();
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPostfix]
|
||||||
|
public static void Postfix(ToggleKeyCombination __instance, EGameKey gameKey, ECommand command, KeyCombination.KeyCombinationState[] ___keyCombinationState_1)
|
||||||
|
{
|
||||||
|
if (!UseToggleHold(gameKey))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<KeyCombination.KeyCombinationState> states = new(___keyCombinationState_1)
|
||||||
|
{
|
||||||
|
new ToggleHoldIdleState(__instance),
|
||||||
|
new ToggleHoldClickOrHoldState(__instance),
|
||||||
|
new ToggleHoldHoldState(__instance, command)
|
||||||
|
};
|
||||||
|
|
||||||
|
StateMachineArray.SetValue(__instance, states.ToArray());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class UpdateInputPatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return AccessTools.Method(typeof(KeyCombination), nameof(KeyCombination.UpdateInput));
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPostfix]
|
||||||
|
public static void Postfix(KeyCombination __instance)
|
||||||
|
{
|
||||||
|
if (UseToggleHold(__instance.GameKey))
|
||||||
|
{
|
||||||
|
__instance.method_0((KeyCombination.EKeyState)ToggleHoldState.Idle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool UseToggleHold(EGameKey gameKey)
|
||||||
|
{
|
||||||
|
return gameKey switch
|
||||||
|
{
|
||||||
|
EGameKey.Aim => Settings.ToggleOrHoldAim.Value,
|
||||||
|
EGameKey.Tactical => Settings.ToggleOrHoldTactical.Value,
|
||||||
|
EGameKey.ToggleGoggles => Settings.ToggleOrHoldGoggles.Value,
|
||||||
|
EGameKey.ToggleHeadLight => Settings.ToggleOrHoldHeadlight.Value,
|
||||||
|
EGameKey.Sprint => Settings.ToggleOrHoldSprint.Value,
|
||||||
|
EGameKey.Slot4 => UseToggleHoldQuickBind(EGameKey.Slot4),
|
||||||
|
EGameKey.Slot5 => UseToggleHoldQuickBind(EGameKey.Slot5),
|
||||||
|
EGameKey.Slot6 => UseToggleHoldQuickBind(EGameKey.Slot6),
|
||||||
|
EGameKey.Slot7 => UseToggleHoldQuickBind(EGameKey.Slot7),
|
||||||
|
EGameKey.Slot8 => UseToggleHoldQuickBind(EGameKey.Slot8),
|
||||||
|
EGameKey.Slot9 => UseToggleHoldQuickBind(EGameKey.Slot9),
|
||||||
|
EGameKey.Slot0 => UseToggleHoldQuickBind(EGameKey.Slot0),
|
||||||
|
_ => false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool UseToggleHoldQuickBind(EGameKey gameKey)
|
||||||
|
{
|
||||||
|
return Quickbind.GetType(gameKey) switch
|
||||||
|
{
|
||||||
|
Quickbind.ItemType.Tactical => Settings.ToggleOrHoldTactical.Value,
|
||||||
|
Quickbind.ItemType.Headlight => Settings.ToggleOrHoldHeadlight.Value,
|
||||||
|
Quickbind.ItemType.NightVision => Settings.ToggleOrHoldGoggles.Value,
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void OnSettingChanged(object sender, EventArgs args)
|
||||||
|
{
|
||||||
|
// Will "save" control settings, running GClass1911.UpdateInput, which will set (or unset) toggle/hold behavior
|
||||||
|
Singleton<SharedGameSettingsClass>.Instance.Control.Controller.method_3();
|
||||||
|
}
|
||||||
|
}
|
@@ -4,6 +4,7 @@ using EFT.UI;
|
|||||||
using EFT.UI.DragAndDrop;
|
using EFT.UI.DragAndDrop;
|
||||||
using HarmonyLib;
|
using HarmonyLib;
|
||||||
using SPT.Reflection.Patching;
|
using SPT.Reflection.Patching;
|
||||||
|
using SPT.Reflection.Utils;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@@ -13,6 +14,11 @@ using UnityEngine;
|
|||||||
|
|
||||||
namespace UIFixes;
|
namespace UIFixes;
|
||||||
|
|
||||||
|
public static class EItemInfoButtonExt
|
||||||
|
{
|
||||||
|
public const EItemInfoButton AddOffer = (EItemInfoButton)77;
|
||||||
|
}
|
||||||
|
|
||||||
public static class ContextMenuPatches
|
public static class ContextMenuPatches
|
||||||
{
|
{
|
||||||
private static InsuranceInteractions CurrentInsuranceInteractions = null;
|
private static InsuranceInteractions CurrentInsuranceInteractions = null;
|
||||||
@@ -42,6 +48,9 @@ public static class ContextMenuPatches
|
|||||||
new EmptyModSlotMenuRemovePatch().Enable();
|
new EmptyModSlotMenuRemovePatch().Enable();
|
||||||
new EmptySlotMenuPatch().Enable();
|
new EmptySlotMenuPatch().Enable();
|
||||||
new EmptySlotMenuRemovePatch().Enable();
|
new EmptySlotMenuRemovePatch().Enable();
|
||||||
|
|
||||||
|
new InventoryWishlistPatch().Enable();
|
||||||
|
new TradingWishlistPatch().Enable();
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ContextMenuNamesPatch : ModulePatch
|
public class ContextMenuNamesPatch : ModulePatch
|
||||||
@@ -59,67 +68,53 @@ public static class ContextMenuPatches
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int count = 0;
|
||||||
if (caption == EItemInfoButton.Insure.ToString())
|
if (caption == EItemInfoButton.Insure.ToString())
|
||||||
{
|
{
|
||||||
InsuranceCompanyClass insurance = ItemUiContext.Instance.Session.InsuranceCompany;
|
InsuranceCompanyClass insurance = ItemUiContext.Instance.Session.InsuranceCompany;
|
||||||
int count = MultiSelect.ItemContexts.Select(ic => InsuranceItem.FindOrCreate(ic.Item))
|
count = MultiSelect.ItemContexts.Select(ic => InsuranceItem.FindOrCreate(ic.Item))
|
||||||
.Where(i => insurance.ItemTypeAvailableForInsurance(i) && !insurance.InsuredItems.Contains(i))
|
.Where(i => insurance.ItemTypeAvailableForInsurance(i) && !insurance.InsuredItems.Contains(i))
|
||||||
.Count();
|
.Count();
|
||||||
|
|
||||||
|
}
|
||||||
|
else if (caption == EItemInfoButton.Equip.ToString())
|
||||||
|
{
|
||||||
|
count = MultiSelect.InteractionCount(EItemInfoButton.Equip, ItemUiContext.Instance);
|
||||||
|
}
|
||||||
|
else if (caption == EItemInfoButton.Unequip.ToString())
|
||||||
|
{
|
||||||
|
count = MultiSelect.InteractionCount(EItemInfoButton.Unequip, ItemUiContext.Instance);
|
||||||
|
}
|
||||||
|
else if (caption == EItemInfoButton.LoadAmmo.ToString())
|
||||||
|
{
|
||||||
|
count = MultiSelect.InteractionCount(EItemInfoButton.LoadAmmo, ItemUiContext.Instance);
|
||||||
|
}
|
||||||
|
else if (caption == EItemInfoButton.UnloadAmmo.ToString())
|
||||||
|
{
|
||||||
|
count = MultiSelect.InteractionCount(EItemInfoButton.UnloadAmmo, ItemUiContext.Instance);
|
||||||
|
}
|
||||||
|
else if (caption == EItemInfoButton.ApplyMagPreset.ToString())
|
||||||
|
{
|
||||||
|
count = MultiSelect.InteractionCount(EItemInfoButton.ApplyMagPreset, ItemUiContext.Instance);
|
||||||
|
}
|
||||||
|
else if (caption == EItemInfoButton.Unpack.ToString())
|
||||||
|
{
|
||||||
|
count = MultiSelect.InteractionCount(EItemInfoButton.Unpack, ItemUiContext.Instance);
|
||||||
|
}
|
||||||
|
else if (caption == EItemInfoButton.AddToWishlist.ToString())
|
||||||
|
{
|
||||||
|
count = MultiSelect.InteractionCount(EItemInfoButton.AddToWishlist, ItemUiContext.Instance);
|
||||||
|
}
|
||||||
|
else if (caption == EItemInfoButton.RemoveFromWishlist.ToString())
|
||||||
|
{
|
||||||
|
count = MultiSelect.InteractionCount(EItemInfoButton.RemoveFromWishlist, ItemUiContext.Instance);
|
||||||
|
}
|
||||||
|
|
||||||
if (count > 0)
|
if (count > 0)
|
||||||
{
|
{
|
||||||
____text.text += " (x" + count + ")";
|
____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 + ")";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class DeclareSubInteractionsInventoryPatch : ModulePatch
|
public class DeclareSubInteractionsInventoryPatch : ModulePatch
|
||||||
@@ -130,9 +125,20 @@ public static class ContextMenuPatches
|
|||||||
}
|
}
|
||||||
|
|
||||||
[PatchPostfix]
|
[PatchPostfix]
|
||||||
public static void Postfix(ref IEnumerable<EItemInfoButton> __result)
|
public static void Postfix(ref IEnumerable<EItemInfoButton> __result, Item ___item_0)
|
||||||
{
|
{
|
||||||
__result = __result.Append(EItemInfoButton.Repair).Append(EItemInfoButton.Insure);
|
__result = __result.Append(EItemInfoButton.Repair).Append(EItemInfoButton.Insure);
|
||||||
|
|
||||||
|
if (___item_0 is LootItemClass container && container.Grids.Any())
|
||||||
|
{
|
||||||
|
var innerContainers = container.GetFirstLevelItems()
|
||||||
|
.Where(i => i != container)
|
||||||
|
.Where(i => i is LootItemClass innerContainer && innerContainer.Grids.Any());
|
||||||
|
if (innerContainers.Count() == 1)
|
||||||
|
{
|
||||||
|
__result = __result.Append(EItemInfoButton.Open);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,7 +152,12 @@ public static class ContextMenuPatches
|
|||||||
}
|
}
|
||||||
|
|
||||||
[PatchPrefix]
|
[PatchPrefix]
|
||||||
public static bool Prefix(EItemInfoButton parentInteraction, ISubInteractions subInteractionsWrapper, Item ___item_0, ItemUiContext ___itemUiContext_1)
|
public static bool Prefix(
|
||||||
|
EItemInfoButton parentInteraction,
|
||||||
|
ISubInteractions subInteractionsWrapper,
|
||||||
|
Item ___item_0,
|
||||||
|
ItemContextAbstractClass ___itemContextAbstractClass,
|
||||||
|
ItemUiContext ___itemUiContext_1)
|
||||||
{
|
{
|
||||||
// Clear this, since something else should be active (even a different mouseover of the insurance button)
|
// Clear this, since something else should be active (even a different mouseover of the insurance button)
|
||||||
LoadingInsuranceActions = false;
|
LoadingInsuranceActions = false;
|
||||||
@@ -184,6 +195,12 @@ public static class ContextMenuPatches
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Settings.OpenAllContextMenu.Value && parentInteraction == EItemInfoButton.Open)
|
||||||
|
{
|
||||||
|
subInteractionsWrapper.SetSubInteractions(new OpenInteractions(___itemContextAbstractClass, ___itemUiContext_1));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -514,7 +531,9 @@ public static class ContextMenuPatches
|
|||||||
{
|
{
|
||||||
protected override MethodBase GetTargetMethod()
|
protected override MethodBase GetTargetMethod()
|
||||||
{
|
{
|
||||||
return AccessTools.Method(typeof(InteractionButtonsContainer), nameof(InteractionButtonsContainer.SetSubInteractions)).MakeGenericMethod([typeof(InsuranceInteractions.EInsurers)]);
|
return AccessTools.Method(
|
||||||
|
typeof(InteractionButtonsContainer),
|
||||||
|
nameof(InteractionButtonsContainer.SetSubInteractions)).MakeGenericMethod([typeof(InsuranceInteractions.EInsurers)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Existing logic tries to place it on the right, moving to the left if necessary. They didn't do it correctly, so it always goes on the left.
|
// 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.
|
||||||
@@ -525,6 +544,54 @@ public static class ContextMenuPatches
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class InventoryWishlistPatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
// R.InventoryActions.Type is only ever referenced by it's child class, which overrides AvailableInteractions
|
||||||
|
Type type = PatchConstants.EftTypes.First(t => t.BaseType == R.InventoryInteractions.Type);
|
||||||
|
return AccessTools.DeclaredProperty(type, "AvailableInteractions").GetMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPostfix]
|
||||||
|
public static void Postfix(ref IEnumerable<EItemInfoButton> __result)
|
||||||
|
{
|
||||||
|
if (!Settings.WishlistContextEverywhere.Value)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var list = __result.ToList();
|
||||||
|
int index = list.IndexOf(EItemInfoButton.Tag);
|
||||||
|
list.Insert(index, EItemInfoButton.RemoveFromWishlist);
|
||||||
|
list.Insert(index, EItemInfoButton.AddToWishlist);
|
||||||
|
__result = list;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TradingWishlistPatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return AccessTools.DeclaredProperty(R.TradingInteractions.Type, "AvailableInteractions").GetMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPostfix]
|
||||||
|
public static void Postfix(ref IEnumerable<EItemInfoButton> __result)
|
||||||
|
{
|
||||||
|
if (!Settings.WishlistContextEverywhere.Value)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var list = __result.ToList();
|
||||||
|
int index = list.IndexOf(EItemInfoButton.Tag);
|
||||||
|
list.Insert(index, EItemInfoButton.RemoveFromWishlist);
|
||||||
|
list.Insert(index, EItemInfoButton.AddToWishlist);
|
||||||
|
__result = list;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static void PositionContextMenuFlyout(SimpleContextMenuButton button, SimpleContextMenu flyoutMenu)
|
private static void PositionContextMenuFlyout(SimpleContextMenuButton button, SimpleContextMenu flyoutMenu)
|
||||||
{
|
{
|
||||||
RectTransform buttonTransform = button.RectTransform();
|
RectTransform buttonTransform = button.RectTransform();
|
@@ -46,9 +46,7 @@ public static class ContextMenuShortcutPatches
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Settings.ItemContextBlocksTextInputs.Value &&
|
if (!Settings.ItemContextBlocksTextInputs.Value && Plugin.TextboxActive())
|
||||||
EventSystem.current?.currentSelectedGameObject != null &&
|
|
||||||
EventSystem.current.currentSelectedGameObject.GetComponent<TMP_InputField>() != null)
|
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -78,6 +76,11 @@ public static class ContextMenuShortcutPatches
|
|||||||
TryInteraction(__instance, itemContext, EItemInfoButton.UseAll, [EItemInfoButton.Use]);
|
TryInteraction(__instance, itemContext, EItemInfoButton.UseAll, [EItemInfoButton.Use]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Settings.ReloadKeyBind.Value.IsDown())
|
||||||
|
{
|
||||||
|
TryInteraction(__instance, itemContext, EItemInfoButton.Reload);
|
||||||
|
}
|
||||||
|
|
||||||
if (Settings.UnloadKeyBind.Value.IsDown())
|
if (Settings.UnloadKeyBind.Value.IsDown())
|
||||||
{
|
{
|
||||||
TryInteraction(__instance, itemContext, EItemInfoButton.Unload, [EItemInfoButton.UnloadAmmo]);
|
TryInteraction(__instance, itemContext, EItemInfoButton.Unload, [EItemInfoButton.UnloadAmmo]);
|
||||||
@@ -98,6 +101,11 @@ public static class ContextMenuShortcutPatches
|
|||||||
TryInteraction(__instance, itemContext, EItemInfoButton.LinkedSearch);
|
TryInteraction(__instance, itemContext, EItemInfoButton.LinkedSearch);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Settings.RequiredSearchKeyBind.Value.IsDown())
|
||||||
|
{
|
||||||
|
TryInteraction(__instance, itemContext, EItemInfoButton.NeededSearch);
|
||||||
|
}
|
||||||
|
|
||||||
if (Settings.SortingTableKeyBind.Value.IsDown())
|
if (Settings.SortingTableKeyBind.Value.IsDown())
|
||||||
{
|
{
|
||||||
MoveToFromSortingTable(itemContext, __instance);
|
MoveToFromSortingTable(itemContext, __instance);
|
||||||
@@ -109,6 +117,11 @@ public static class ContextMenuShortcutPatches
|
|||||||
[EItemInfoButton.Fold, EItemInfoButton.Unfold, EItemInfoButton.TurnOn, EItemInfoButton.TurnOff, EItemInfoButton.CheckMagazine]);
|
[EItemInfoButton.Fold, EItemInfoButton.Unfold, EItemInfoButton.TurnOn, EItemInfoButton.TurnOff, EItemInfoButton.CheckMagazine]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Settings.AddOfferKeyBind.Value.IsDown())
|
||||||
|
{
|
||||||
|
TryInteraction(__instance, itemContext, EItemInfoButtonExt.AddOffer);
|
||||||
|
}
|
||||||
|
|
||||||
Interactions = null;
|
Interactions = null;
|
||||||
}
|
}
|
||||||
|
|
@@ -1,9 +1,11 @@
|
|||||||
using EFT.UI;
|
using EFT.HandBook;
|
||||||
|
using EFT.UI;
|
||||||
using EFT.UI.Ragfair;
|
using EFT.UI.Ragfair;
|
||||||
using HarmonyLib;
|
using HarmonyLib;
|
||||||
using SPT.Reflection.Patching;
|
using SPT.Reflection.Patching;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using TMPro;
|
using TMPro;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.UI;
|
using UnityEngine.UI;
|
||||||
@@ -12,6 +14,8 @@ namespace UIFixes;
|
|||||||
|
|
||||||
public static class FixFleaPatches
|
public static class FixFleaPatches
|
||||||
{
|
{
|
||||||
|
private static Task SearchFilterTask;
|
||||||
|
|
||||||
public static void Enable()
|
public static void Enable()
|
||||||
{
|
{
|
||||||
// These are anal AF
|
// These are anal AF
|
||||||
@@ -19,10 +23,15 @@ public static class FixFleaPatches
|
|||||||
new ToggleOnOpenPatch().Enable();
|
new ToggleOnOpenPatch().Enable();
|
||||||
new DropdownHeightPatch().Enable();
|
new DropdownHeightPatch().Enable();
|
||||||
|
|
||||||
|
new AddOfferWindowDoubleScrollPatch().Enable();
|
||||||
|
|
||||||
new OfferItemFixMaskPatch().Enable();
|
new OfferItemFixMaskPatch().Enable();
|
||||||
new OfferViewTweaksPatch().Enable();
|
new OfferViewTweaksPatch().Enable();
|
||||||
|
|
||||||
|
new SearchFilterPatch().Enable();
|
||||||
new SearchPatch().Enable();
|
new SearchPatch().Enable();
|
||||||
|
new SearchKeyPatch().Enable();
|
||||||
|
new SearchKeyHandbookPatch().Enable();
|
||||||
}
|
}
|
||||||
|
|
||||||
public class DoNotToggleOnMouseOverPatch : ModulePatch
|
public class DoNotToggleOnMouseOverPatch : ModulePatch
|
||||||
@@ -101,6 +110,42 @@ public static class FixFleaPatches
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class AddOfferWindowDoubleScrollPatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return AccessTools.Method(typeof(AddOfferWindow), nameof(AddOfferWindow.Awake));
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPostfix]
|
||||||
|
public static void Postfix(AddOfferWindow __instance, GameObject ____noOfferPanel, GameObject ____selectedItemPanel)
|
||||||
|
{
|
||||||
|
// Not sure how they messed it this up, but the widths on some of these are hardcoded
|
||||||
|
// badly, so things move around
|
||||||
|
Transform stashPart = __instance.transform.Find("Inner/Contents/StashPart");
|
||||||
|
var stashLayout = stashPart.gameObject.GetComponent<LayoutElement>();
|
||||||
|
stashLayout.preferredWidth = 644f;
|
||||||
|
|
||||||
|
var noItemLayout = ____noOfferPanel.GetComponent<LayoutElement>();
|
||||||
|
var requirementLayout = ____selectedItemPanel.GetComponent<LayoutElement>();
|
||||||
|
requirementLayout.preferredWidth = noItemLayout.preferredWidth = 450f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SearchFilterPatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return AccessTools.Method(typeof(BrowseCategoriesPanel), nameof(BrowseCategoriesPanel.Filter));
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPostfix]
|
||||||
|
public static void Postfix(Task __result)
|
||||||
|
{
|
||||||
|
SearchFilterTask = __result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public class SearchPatch : ModulePatch
|
public class SearchPatch : ModulePatch
|
||||||
{
|
{
|
||||||
protected override MethodBase GetTargetMethod()
|
protected override MethodBase GetTargetMethod()
|
||||||
@@ -121,19 +166,64 @@ public static class FixFleaPatches
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (SearchFilterTask != null && !SearchFilterTask.IsCompleted)
|
||||||
|
{
|
||||||
|
SearchFilterTask.ContinueWith(t => DoSearch(__instance), TaskScheduler.FromCurrentSynchronizationContext());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (__instance.FilteredNodes.Values.Sum(node => node.Count) > 0)
|
if (__instance.FilteredNodes.Values.Sum(node => node.Count) > 0)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
__instance.Ragfair.CancellableFilters.Clear();
|
DoSearch(__instance);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
FilterRule filterRule = __instance.Ragfair.method_3(EViewListType.AllOffers);
|
private static void DoSearch(RagfairCategoriesPanel panel)
|
||||||
|
{
|
||||||
|
if (panel.FilteredNodes.Values.Sum(node => node.Count) > 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
panel.Ragfair.CancellableFilters.Clear();
|
||||||
|
|
||||||
|
FilterRule filterRule = panel.Ragfair.method_3(EViewListType.AllOffers);
|
||||||
filterRule.HandbookId = string.Empty;
|
filterRule.HandbookId = string.Empty;
|
||||||
|
|
||||||
__instance.Ragfair.AddSearchesInRule(filterRule, true);
|
panel.Ragfair.AddSearchesInRule(filterRule, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
public class SearchKeyPatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return AccessTools.Method(typeof(BrowseCategoriesPanel), nameof(BrowseCategoriesPanel.Awake));
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPostfix]
|
||||||
|
public static void Postfix(TMP_InputField ___SearchInputField)
|
||||||
|
{
|
||||||
|
___SearchInputField.GetOrAddComponent<SearchKeyListener>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Have to target HandbookCategoriesPanel specifically because even though it inherits from BrowseCategoriesPanel,
|
||||||
|
// BSG couldn't be bothered to call base.Awake()
|
||||||
|
public class SearchKeyHandbookPatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return AccessTools.Method(typeof(HandbookCategoriesPanel), nameof(HandbookCategoriesPanel.Awake));
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPostfix]
|
||||||
|
public static void Postfix(TMP_InputField ___SearchInputField)
|
||||||
|
{
|
||||||
|
___SearchInputField.GetOrAddComponent<SearchKeyListener>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,15 +231,15 @@ public static class FixFleaPatches
|
|||||||
{
|
{
|
||||||
protected override MethodBase GetTargetMethod()
|
protected override MethodBase GetTargetMethod()
|
||||||
{
|
{
|
||||||
return AccessTools.DeclaredMethod(typeof(DropDownBox), nameof(DropDownBox.Init));
|
return AccessTools.DeclaredMethod(typeof(DropDownBox), nameof(DropDownBox.Show));
|
||||||
}
|
}
|
||||||
|
|
||||||
[PatchPostfix]
|
[PatchPostfix]
|
||||||
public static void Postfix(ref float ____maxVisibleHeight)
|
public static void Postfix(DropDownBox __instance, ref float ____maxVisibleHeight)
|
||||||
{
|
{
|
||||||
if (____maxVisibleHeight == 120f)
|
if (____maxVisibleHeight == 120f)
|
||||||
{
|
{
|
||||||
____maxVisibleHeight = 240f;
|
____maxVisibleHeight = 150f;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -16,8 +16,25 @@ public class FixTraderControllerSimulateFalsePatch : ModulePatch
|
|||||||
// Recreating this function to add the comment section, so calling this with simulate = false doesn't break everything
|
// Recreating this function to add the comment section, so calling this with simulate = false doesn't break everything
|
||||||
[PatchPrefix]
|
[PatchPrefix]
|
||||||
[HarmonyPriority(Priority.Last)]
|
[HarmonyPriority(Priority.Last)]
|
||||||
public static bool Prefix(TraderControllerClass __instance, ItemContextAbstractClass itemContext, Item targetItem, bool partialTransferOnly, bool simulate, ref ItemOperation __result)
|
public static bool Prefix(
|
||||||
|
TraderControllerClass __instance,
|
||||||
|
ItemContextAbstractClass itemContext,
|
||||||
|
Item targetItem,
|
||||||
|
bool partialTransferOnly,
|
||||||
|
bool simulate,
|
||||||
|
ref ItemOperation __result,
|
||||||
|
bool __runOriginal)
|
||||||
{
|
{
|
||||||
|
if (!__runOriginal)
|
||||||
|
{
|
||||||
|
// This is a little hairy, as *some* prefix didn't want to run. If MergeConsumables is present, assume it's that.
|
||||||
|
// If MC succeeded, bail out. If it failed, we might still want to swap
|
||||||
|
if (Plugin.MergeConsumablesPresent() && __result.Succeeded)
|
||||||
|
{
|
||||||
|
return __runOriginal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TargetItemOperation opStruct;
|
TargetItemOperation opStruct;
|
||||||
opStruct.targetItem = targetItem;
|
opStruct.targetItem = targetItem;
|
||||||
opStruct.traderControllerClass = __instance;
|
opStruct.traderControllerClass = __instance;
|
@@ -327,7 +327,10 @@ public static class FleaPrevSearchPatches
|
|||||||
[PatchPostfix]
|
[PatchPostfix]
|
||||||
public static void Postfix(EViewListType type)
|
public static void Postfix(EViewListType type)
|
||||||
{
|
{
|
||||||
PreviousFilterButton.Instance?.gameObject.SetActive(type == EViewListType.AllOffers);
|
if (PreviousFilterButton.Instance != null)
|
||||||
|
{
|
||||||
|
PreviousFilterButton.Instance.gameObject.SetActive(type == EViewListType.AllOffers);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -369,7 +372,10 @@ public static class FleaPrevSearchPatches
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (PreviousFilterButton.Instance != null)
|
||||||
|
{
|
||||||
PreviousFilterButton.Instance.OnOffersLoaded(__instance);
|
PreviousFilterButton.Instance.OnOffersLoaded(__instance);
|
||||||
|
}
|
||||||
|
|
||||||
if (Settings.AutoExpandCategories.Value)
|
if (Settings.AutoExpandCategories.Value)
|
||||||
{
|
{
|
@@ -73,6 +73,11 @@ public class GridWindowButtonsPatch : ModulePatch
|
|||||||
|
|
||||||
public void Update()
|
public void Update()
|
||||||
{
|
{
|
||||||
|
if (Plugin.TextboxActive())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
bool isTopWindow = window.transform.GetSiblingIndex() == window.transform.parent.childCount - 1;
|
bool isTopWindow = window.transform.GetSiblingIndex() == window.transform.parent.childCount - 1;
|
||||||
if (Settings.SnapLeftKeybind.Value.IsDown() && isTopWindow)
|
if (Settings.SnapLeftKeybind.Value.IsDown() && isTopWindow)
|
||||||
{
|
{
|
20
src/Patches/HideoutCameraPatches.cs
Normal file
20
src/Patches/HideoutCameraPatches.cs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
using EFT.Hideout;
|
||||||
|
using HarmonyLib;
|
||||||
|
using SPT.Reflection.Patching;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace UIFixes;
|
||||||
|
|
||||||
|
public class HideoutCameraPatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return AccessTools.Method(typeof(HideoutCameraController), nameof(HideoutCameraController.LateUpdate));
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPrefix]
|
||||||
|
public static bool Prefix(HideoutCameraController __instance)
|
||||||
|
{
|
||||||
|
return !__instance.AreaSelected;
|
||||||
|
}
|
||||||
|
}
|
@@ -7,8 +7,6 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using TMPro;
|
|
||||||
using UnityEngine.EventSystems;
|
|
||||||
using UnityEngine.UI;
|
using UnityEngine.UI;
|
||||||
|
|
||||||
namespace UIFixes;
|
namespace UIFixes;
|
||||||
@@ -108,6 +106,8 @@ public static class HideoutSearchPatches
|
|||||||
areaScreenSubstrate.method_8();
|
areaScreenSubstrate.method_8();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
____searchInputField.GetOrAddComponent<SearchKeyListener>();
|
||||||
|
|
||||||
____searchInputField.ActivateInputField();
|
____searchInputField.ActivateInputField();
|
||||||
____searchInputField.Select();
|
____searchInputField.Select();
|
||||||
}
|
}
|
||||||
@@ -223,9 +223,7 @@ public static class HideoutSearchPatches
|
|||||||
[PatchPrefix]
|
[PatchPrefix]
|
||||||
public static bool Prefix(ECommand command, ref InputNode.ETranslateResult __result)
|
public static bool Prefix(ECommand command, ref InputNode.ETranslateResult __result)
|
||||||
{
|
{
|
||||||
if (command == ECommand.Enter &&
|
if (command == ECommand.Enter && Plugin.TextboxActive())
|
||||||
EventSystem.current?.currentSelectedGameObject != null &&
|
|
||||||
EventSystem.current.currentSelectedGameObject.GetComponent<TMP_InputField>() != null)
|
|
||||||
{
|
{
|
||||||
__result = InputNode.ETranslateResult.Block;
|
__result = InputNode.ETranslateResult.Block;
|
||||||
return false;
|
return false;
|
44
src/Patches/LimitDragPatches.cs
Normal file
44
src/Patches/LimitDragPatches.cs
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
|
using EFT.UI;
|
||||||
|
using HarmonyLib;
|
||||||
|
using SPT.Reflection.Patching;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.EventSystems;
|
||||||
|
|
||||||
|
namespace UIFixes;
|
||||||
|
|
||||||
|
public static class LimitDragPatches
|
||||||
|
{
|
||||||
|
public static void Enable()
|
||||||
|
{
|
||||||
|
new OnDragEventPatch(typeof(DragTrigger), nameof(DragTrigger.OnDrag)).Enable();
|
||||||
|
new OnDragEventPatch(typeof(DragTrigger), nameof(DragTrigger.OnBeginDrag)).Enable();
|
||||||
|
new OnDragEventPatch(typeof(DragTrigger), nameof(DragTrigger.OnEndDrag)).Enable();
|
||||||
|
|
||||||
|
new OnDragEventPatch(typeof(UIDragComponent), "UnityEngine.EventSystems.IDragHandler.OnDrag").Enable();
|
||||||
|
new OnDragEventPatch(typeof(UIDragComponent), "UnityEngine.EventSystems.IBeginDragHandler.OnBeginDrag").Enable();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class OnDragEventPatch(Type type, string methodName) : ModulePatch
|
||||||
|
{
|
||||||
|
private readonly string methodName = methodName;
|
||||||
|
private readonly Type type = type;
|
||||||
|
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return AccessTools.Method(type, methodName);
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPrefix]
|
||||||
|
public static bool Prefix(PointerEventData eventData)
|
||||||
|
{
|
||||||
|
if (!Settings.LimitNonstandardDrags.Value)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return eventData.button == PointerEventData.InputButton.Left && !Input.GetKey(KeyCode.LeftShift) && !Input.GetKey(KeyCode.RightShift);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -38,6 +38,8 @@ public static class MultiSelectPatches
|
|||||||
private static bool DisableMerge = false;
|
private static bool DisableMerge = false;
|
||||||
private static bool IgnoreItemParent = false;
|
private static bool IgnoreItemParent = false;
|
||||||
|
|
||||||
|
private static bool DisableMagnify = false; // Causes issues during multi drag
|
||||||
|
|
||||||
private static readonly Color ValidMoveColor = new(0.06f, 0.38f, 0.06f, 0.57f);
|
private static readonly Color ValidMoveColor = new(0.06f, 0.38f, 0.06f, 0.57f);
|
||||||
|
|
||||||
public static void Enable()
|
public static void Enable()
|
||||||
@@ -58,6 +60,7 @@ public static class MultiSelectPatches
|
|||||||
new DisableSplitPatch().Enable();
|
new DisableSplitPatch().Enable();
|
||||||
new DisableSplitTargetPatch().Enable();
|
new DisableSplitTargetPatch().Enable();
|
||||||
new FixSearchedContextPatch().Enable();
|
new FixSearchedContextPatch().Enable();
|
||||||
|
new DisableMagnifyPatch().Enable();
|
||||||
|
|
||||||
// Actions
|
// Actions
|
||||||
new ItemViewClickPatch().Enable();
|
new ItemViewClickPatch().Enable();
|
||||||
@@ -170,7 +173,7 @@ public static class MultiSelectPatches
|
|||||||
___ItemController is InventoryControllerClass inventoryController)
|
___ItemController is InventoryControllerClass inventoryController)
|
||||||
{
|
{
|
||||||
SortingTableClass sortingTable = inventoryController.Inventory.SortingTable;
|
SortingTableClass sortingTable = inventoryController.Inventory.SortingTable;
|
||||||
if (sortingTable != null && sortingTable.IsVisible)
|
if (sortingTable != null && sortingTable.IsVisible && !Plugin.InRaid())
|
||||||
{
|
{
|
||||||
couldBeSortingTableMove = true;
|
couldBeSortingTableMove = true;
|
||||||
}
|
}
|
||||||
@@ -286,7 +289,7 @@ public static class MultiSelectPatches
|
|||||||
}
|
}
|
||||||
|
|
||||||
DisableMerge = false;
|
DisableMerge = false;
|
||||||
IgnoreItemParent = true;
|
IgnoreItemParent = false;
|
||||||
|
|
||||||
if (succeeded)
|
if (succeeded)
|
||||||
{
|
{
|
||||||
@@ -460,7 +463,7 @@ public static class MultiSelectPatches
|
|||||||
}
|
}
|
||||||
|
|
||||||
[PatchPrefix]
|
[PatchPrefix]
|
||||||
public static bool Prefix(EItemInfoButton interaction, ItemUiContext ___itemUiContext_1)
|
public static bool Prefix(BaseItemInfoInteractions __instance, EItemInfoButton interaction, ItemUiContext ___itemUiContext_1)
|
||||||
{
|
{
|
||||||
if (!MultiSelect.Active)
|
if (!MultiSelect.Active)
|
||||||
{
|
{
|
||||||
@@ -481,6 +484,12 @@ public static class MultiSelectPatches
|
|||||||
case EItemInfoButton.Unpack:
|
case EItemInfoButton.Unpack:
|
||||||
MultiSelect.UnpackAll(___itemUiContext_1, false);
|
MultiSelect.UnpackAll(___itemUiContext_1, false);
|
||||||
return false;
|
return false;
|
||||||
|
case EItemInfoButton.AddToWishlist:
|
||||||
|
MultiSelect.WishlistAll(___itemUiContext_1, __instance, true, false);
|
||||||
|
return false;
|
||||||
|
case EItemInfoButton.RemoveFromWishlist:
|
||||||
|
MultiSelect.WishlistAll(___itemUiContext_1, __instance, false, false);
|
||||||
|
return false;
|
||||||
default:
|
default:
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -674,6 +683,24 @@ public static class MultiSelectPatches
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MagnifyIfPossible gets called when a dynamic grid (sorting table) resizes. It causes GridViews to be killed and recreated asynchronously (!)
|
||||||
|
// This causes all sorts of issues with multiselect move, as there are race conditions and items get dropped and views duplicated
|
||||||
|
// I'm not 100% sure what it does, it appears to be trying to unload items that may now be out of sight, an optimization I'm willing
|
||||||
|
// to sacrifice for this actually work properly.
|
||||||
|
public class DisableMagnifyPatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return AccessTools.Method(typeof(GridView), nameof(GridView.MagnifyIfPossible), []);
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPrefix]
|
||||||
|
public static bool Prefix()
|
||||||
|
{
|
||||||
|
return !DisableMagnify;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public class GridViewCanAcceptPatch : ModulePatch
|
public class GridViewCanAcceptPatch : ModulePatch
|
||||||
{
|
{
|
||||||
protected override MethodBase GetTargetMethod()
|
protected override MethodBase GetTargetMethod()
|
||||||
@@ -731,6 +758,7 @@ public static class MultiSelectPatches
|
|||||||
|
|
||||||
Item targetItem = __instance.method_8(targetItemContext);
|
Item targetItem = __instance.method_8(targetItemContext);
|
||||||
DisableMerge = targetItem == null;
|
DisableMerge = targetItem == null;
|
||||||
|
DisableMagnify = true;
|
||||||
bool isGridPlacement = targetItem == null;
|
bool isGridPlacement = targetItem == null;
|
||||||
|
|
||||||
// If everything selected is the same type and is a stackable type, allow partial success
|
// If everything selected is the same type and is a stackable type, allow partial success
|
||||||
@@ -858,6 +886,8 @@ public static class MultiSelectPatches
|
|||||||
operations.Pop().Value?.RollBack();
|
operations.Pop().Value?.RollBack();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DisableMagnify = false;
|
||||||
|
|
||||||
// result and operation are set to the last one that completed - so success if they all passed, or the first failure
|
// result and operation are set to the last one that completed - so success if they all passed, or the first failure
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -1453,28 +1483,35 @@ public static class MultiSelectPatches
|
|||||||
int firstStart = FindOrigin != null ? invertDimensions ? FindOrigin.LocationInGrid.x : FindOrigin.LocationInGrid.y : 0;
|
int firstStart = FindOrigin != null ? invertDimensions ? FindOrigin.LocationInGrid.x : FindOrigin.LocationInGrid.y : 0;
|
||||||
int secondStart = FindOrigin != null ? invertDimensions ? FindOrigin.LocationInGrid.y : FindOrigin.LocationInGrid.x : 0;
|
int secondStart = FindOrigin != null ? invertDimensions ? FindOrigin.LocationInGrid.y : FindOrigin.LocationInGrid.x : 0;
|
||||||
|
|
||||||
// Walks the first dimension until it finds a row/column with enough space, then walks down that row
|
// Walks the first dimension until it finds a row/column with enough space,
|
||||||
// /column until it finds a column/row with enough space
|
// then walks down that row/column until it finds a column/row with enough space
|
||||||
// Starts at origin, wraps around
|
// Starts at origin, wraps around
|
||||||
for (int i = 0; i < firstDimensionSize; i++)
|
for (int i = 0; i < firstDimensionSize; i++)
|
||||||
{
|
{
|
||||||
int firstDim = (firstStart + i) % firstDimensionSize;
|
int firstDim = (firstStart + i) % firstDimensionSize; // loop around from start
|
||||||
//for (int j = i == firstStart ? secondStart : 0; j + itemSecondSize <= secondDimensionSize; j++)
|
|
||||||
for (int j = 0; j < secondDimensionSize; j++)
|
for (int j = 0; j < secondDimensionSize; j++)
|
||||||
{
|
{
|
||||||
|
// second dimension starts at FindOrigin, but after first dimension increases, starts back at 0
|
||||||
|
// e.g. there wasn't room on the first row, then on the second row we start with first column
|
||||||
int secondDim = firstDim == firstStart ? (secondStart + j) % secondDimensionSize : j;
|
int secondDim = firstDim == firstStart ? (secondStart + j) % secondDimensionSize : j;
|
||||||
if (secondDim + itemSecondSize > secondDimensionSize)
|
if (secondDim + itemSecondSize > secondDimensionSize)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
int secondDimOpenSpaces = (invertDimensions ? secondDimensionSpaces[secondDim * firstDimensionSize + firstDim] : secondDimensionSpaces[firstDim * secondDimensionSize + secondDim]);
|
// Open spaces is a look-ahead number of open spaces in that dimension
|
||||||
if (secondDimOpenSpaces >= itemSecondSize || secondDimOpenSpaces == -1) // no idea what -1 means
|
// -1 means "infinite", the grid can stretch in that direction (and there's no item further in that direction)
|
||||||
|
int secondDimOpenSpaces = invertDimensions ?
|
||||||
|
secondDimensionSpaces[secondDim * firstDimensionSize + firstDim] :
|
||||||
|
secondDimensionSpaces[firstDim * secondDimensionSize + secondDim];
|
||||||
|
if (secondDimOpenSpaces >= itemSecondSize || secondDimOpenSpaces == -1)
|
||||||
{
|
{
|
||||||
bool enoughSpace = true;
|
bool enoughSpace = true;
|
||||||
for (int k = secondDim; enoughSpace && k < secondDim + itemSecondSize; k++)
|
for (int k = secondDim; enoughSpace && k < secondDim + itemSecondSize; k++)
|
||||||
{
|
{
|
||||||
int firstDimOpenSpaces = (invertDimensions ? firstDimensionSpaces[k * firstDimensionSize + firstDim] : firstDimensionSpaces[firstDim * secondDimensionSize + k]);
|
int firstDimOpenSpaces = invertDimensions ?
|
||||||
|
firstDimensionSpaces[k * firstDimensionSize + firstDim] :
|
||||||
|
firstDimensionSpaces[firstDim * secondDimensionSize + k];
|
||||||
enoughSpace &= firstDimOpenSpaces >= itemMainSize || firstDimOpenSpaces == -1;
|
enoughSpace &= firstDimOpenSpaces >= itemMainSize || firstDimOpenSpaces == -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1535,6 +1572,11 @@ public static class MultiSelectPatches
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (gridAddress == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (gridAddress.Grid != gridView.Grid)
|
if (gridAddress.Grid != gridView.Grid)
|
||||||
{
|
{
|
||||||
GridView otherGridView = gridView.transform.parent.GetComponentsInChildren<GridView>().FirstOrDefault(gv => gv.Grid == gridAddress.Grid);
|
GridView otherGridView = gridView.transform.parent.GetComponentsInChildren<GridView>().FirstOrDefault(gv => gv.Grid == gridAddress.Grid);
|
24
src/Patches/OperationQueuePatch.cs
Normal file
24
src/Patches/OperationQueuePatch.cs
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
using HarmonyLib;
|
||||||
|
using SPT.Reflection.Patching;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace UIFixes;
|
||||||
|
|
||||||
|
public class OperationQueuePatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return AccessTools.Method(typeof(ProfileEndpointFactoryAbstractClass), nameof(ProfileEndpointFactoryAbstractClass.TrySendCommands));
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPrefix]
|
||||||
|
public static void Prefix(ref float ___float_0)
|
||||||
|
{
|
||||||
|
// The target method is hardcoded to 60 seconds. Rather than try to change that, just lie to it about when it last sent
|
||||||
|
if (Time.realtimeSinceStartup - ___float_0 > Settings.OperationQueueTime.Value)
|
||||||
|
{
|
||||||
|
___float_0 = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -3,10 +3,13 @@ using Comfort.Common;
|
|||||||
using EFT.InputSystem;
|
using EFT.InputSystem;
|
||||||
using EFT.InventoryLogic;
|
using EFT.InventoryLogic;
|
||||||
using EFT.UI;
|
using EFT.UI;
|
||||||
|
using EFT.UI.DragAndDrop;
|
||||||
using EFT.UI.Settings;
|
using EFT.UI.Settings;
|
||||||
using HarmonyLib;
|
using HarmonyLib;
|
||||||
using SPT.Reflection.Patching;
|
using SPT.Reflection.Patching;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.UI;
|
||||||
|
|
||||||
namespace UIFixes;
|
namespace UIFixes;
|
||||||
|
|
||||||
@@ -17,6 +20,7 @@ public static class QuickAccessPanelPatches
|
|||||||
new FixWeaponBindsDisplayPatch().Enable();
|
new FixWeaponBindsDisplayPatch().Enable();
|
||||||
new FixVisibilityPatch().Enable();
|
new FixVisibilityPatch().Enable();
|
||||||
new TranslateCommandHackPatch().Enable();
|
new TranslateCommandHackPatch().Enable();
|
||||||
|
new RotationPatch().Enable();
|
||||||
}
|
}
|
||||||
|
|
||||||
public class FixWeaponBindsDisplayPatch : ModulePatch
|
public class FixWeaponBindsDisplayPatch : ModulePatch
|
||||||
@@ -101,4 +105,34 @@ public static class QuickAccessPanelPatches
|
|||||||
FixVisibilityPatch.Ignorable = false;
|
FixVisibilityPatch.Ignorable = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class RotationPatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return AccessTools.Method(typeof(QuickSlotItemView), nameof(QuickSlotItemView.UpdateScale));
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPostfix]
|
||||||
|
public static void Postfix(QuickSlotItemView __instance, Image ___MainImage)
|
||||||
|
{
|
||||||
|
if (__instance.IconScale == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Already square items don't need to be rotated. Still need to be scaled though!
|
||||||
|
XYCellSizeStruct cellSize = __instance.Item.CalculateCellSize();
|
||||||
|
if (cellSize.X == cellSize.Y)
|
||||||
|
{
|
||||||
|
Transform transform = ___MainImage.transform;
|
||||||
|
transform.localRotation = Quaternion.identity;
|
||||||
|
|
||||||
|
Vector3 size = ___MainImage.rectTransform.rect.size;
|
||||||
|
float xScale = __instance.IconScale.Value.x / Mathf.Abs(size.x);
|
||||||
|
float yScale = __instance.IconScale.Value.y / Mathf.Abs(size.y);
|
||||||
|
transform.localScale = Vector3.one * Mathf.Min(xScale, yScale);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@@ -1,4 +1,5 @@
|
|||||||
using EFT;
|
using EFT;
|
||||||
|
using EFT.InventoryLogic;
|
||||||
using EFT.UI;
|
using EFT.UI;
|
||||||
using HarmonyLib;
|
using HarmonyLib;
|
||||||
using SPT.Reflection.Patching;
|
using SPT.Reflection.Patching;
|
||||||
@@ -12,6 +13,7 @@ public static class ReloadInPlacePatches
|
|||||||
{
|
{
|
||||||
private static bool IsReloading = false;
|
private static bool IsReloading = false;
|
||||||
private static MagazineClass FoundMagazine = null;
|
private static MagazineClass FoundMagazine = null;
|
||||||
|
private static ItemAddress FoundAddress = null;
|
||||||
|
|
||||||
public static void Enable()
|
public static void Enable()
|
||||||
{
|
{
|
||||||
@@ -19,6 +21,7 @@ public static class ReloadInPlacePatches
|
|||||||
new ReloadInPlacePatch().Enable();
|
new ReloadInPlacePatch().Enable();
|
||||||
new ReloadInPlaceFindMagPatch().Enable();
|
new ReloadInPlaceFindMagPatch().Enable();
|
||||||
new ReloadInPlaceFindSpotPatch().Enable();
|
new ReloadInPlaceFindSpotPatch().Enable();
|
||||||
|
new AlwaysSwapPatch().Enable();
|
||||||
|
|
||||||
// This patches the firearmsController code when you hit R in raid with an external magazine class
|
// This patches the firearmsController code when you hit R in raid with an external magazine class
|
||||||
new SwapIfNoSpacePatch().Enable();
|
new SwapIfNoSpacePatch().Enable();
|
||||||
@@ -42,6 +45,7 @@ public static class ReloadInPlacePatches
|
|||||||
{
|
{
|
||||||
IsReloading = false;
|
IsReloading = false;
|
||||||
FoundMagazine = null;
|
FoundMagazine = null;
|
||||||
|
FoundAddress = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,9 +59,10 @@ public static class ReloadInPlacePatches
|
|||||||
[PatchPostfix]
|
[PatchPostfix]
|
||||||
public static void Postfix(MagazineClass __result)
|
public static void Postfix(MagazineClass __result)
|
||||||
{
|
{
|
||||||
if (IsReloading)
|
if (__result != null && IsReloading)
|
||||||
{
|
{
|
||||||
FoundMagazine = __result;
|
FoundMagazine = __result;
|
||||||
|
FoundAddress = FoundMagazine.Parent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -66,7 +71,7 @@ public static class ReloadInPlacePatches
|
|||||||
{
|
{
|
||||||
protected override MethodBase GetTargetMethod()
|
protected override MethodBase GetTargetMethod()
|
||||||
{
|
{
|
||||||
Type type = typeof(ItemUiContext).GetNestedTypes().Single(t => t.GetField("currentMagazine") != null);
|
Type type = typeof(ItemUiContext).GetNestedTypes().Single(t => t.GetField("currentMagazine") != null); // ItemUiContext.Class2546
|
||||||
return AccessTools.Method(type, "method_0");
|
return AccessTools.Method(type, "method_0");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,10 +104,40 @@ public static class ReloadInPlacePatches
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class AlwaysSwapPatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
Type type = typeof(ItemUiContext).GetNestedTypes().Single(t => t.GetField("func_3") != null); // ItemUiContext.Class2536
|
||||||
|
return AccessTools.Method(type, "method_4");
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPostfix]
|
||||||
|
public static void Postfix(ItemAddressClass g, ref int __result)
|
||||||
|
{
|
||||||
|
if (!Settings.AlwaysSwapMags.Value)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!g.Equals(FoundAddress))
|
||||||
|
{
|
||||||
|
// Addresses that aren't the found address get massive value increase so found address is sorted first
|
||||||
|
__result += 1000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public class SwapIfNoSpacePatch : ModulePatch
|
public class SwapIfNoSpacePatch : ModulePatch
|
||||||
{
|
{
|
||||||
protected override MethodBase GetTargetMethod()
|
protected override MethodBase GetTargetMethod()
|
||||||
{
|
{
|
||||||
|
if (Plugin.FikaPresent())
|
||||||
|
{
|
||||||
|
Type type = Type.GetType("Fika.Core.Coop.ClientClasses.CoopClientFirearmController, Fika.Core");
|
||||||
|
return AccessTools.Method(type, "ReloadMag");
|
||||||
|
}
|
||||||
|
|
||||||
return AccessTools.Method(typeof(Player.FirearmController), nameof(Player.FirearmController.ReloadMag));
|
return AccessTools.Method(typeof(Player.FirearmController), nameof(Player.FirearmController.ReloadMag));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,6 +161,7 @@ public static class ReloadInPlacePatches
|
|||||||
}
|
}
|
||||||
|
|
||||||
InventoryControllerClass controller = __instance.Weapon.Owner as InventoryControllerClass;
|
InventoryControllerClass controller = __instance.Weapon.Owner as InventoryControllerClass;
|
||||||
|
ItemAddress magAddress = magazine.Parent;
|
||||||
|
|
||||||
// Null address means it couldn't find a spot. Try to remove magazine (temporarily) and try again
|
// Null address means it couldn't find a spot. Try to remove magazine (temporarily) and try again
|
||||||
var operation = InteractionsHandlerClass.Remove(magazine, controller, false, false);
|
var operation = InteractionsHandlerClass.Remove(magazine, controller, false, false);
|
||||||
@@ -137,6 +173,7 @@ public static class ReloadInPlacePatches
|
|||||||
gridItemAddress = controller.Inventory.Equipment.GetPrioritizedGridsForUnloadedObject(false)
|
gridItemAddress = controller.Inventory.Equipment.GetPrioritizedGridsForUnloadedObject(false)
|
||||||
.Select(grid => grid.FindLocationForItem(currentMagazine))
|
.Select(grid => grid.FindLocationForItem(currentMagazine))
|
||||||
.Where(address => address != null)
|
.Where(address => address != null)
|
||||||
|
.OrderByDescending(address => Settings.AlwaysSwapMags.Value && address.Equals(magAddress)) // Prioritize swapping if desired
|
||||||
.OrderBy(address => address.Grid.GridWidth.Value * address.Grid.GridHeight.Value)
|
.OrderBy(address => address.Grid.GridWidth.Value * address.Grid.GridHeight.Value)
|
||||||
.FirstOrDefault(); // BSG's version checks null again, but there's no nulls already. If there's no matches, the enumerable is empty
|
.FirstOrDefault(); // BSG's version checks null again, but there's no nulls already. If there's no matches, the enumerable is empty
|
||||||
|
|
@@ -64,6 +64,8 @@ public static class ReorderGridsPatches
|
|||||||
____presetGridViews = orderedGridView;
|
____presetGridViews = orderedGridView;
|
||||||
__instance.SetReordered(false);
|
__instance.SetReordered(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GridMaps.Remove(compoundItem.TemplateId);
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
@@ -99,26 +101,9 @@ public static class ReorderGridsPatches
|
|||||||
}
|
}
|
||||||
|
|
||||||
var pairs = compoundItem.Grids.Zip(____presetGridViews, (g, gv) => new KeyValuePair<StashGridClass, GridView>(g, gv));
|
var pairs = compoundItem.Grids.Zip(____presetGridViews, (g, gv) => new KeyValuePair<StashGridClass, GridView>(g, gv));
|
||||||
|
var sortedPairs = SortGrids(__instance, pairs);
|
||||||
|
|
||||||
RectTransform parentView = __instance.RectTransform();
|
GridView[] orderedGridViews = sortedPairs.Select(pair => pair.Value).ToArray();
|
||||||
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
|
// Populate the gridmap
|
||||||
if (!GridMaps.ContainsKey(compoundItem.TemplateId))
|
if (!GridMaps.ContainsKey(compoundItem.TemplateId))
|
||||||
@@ -132,11 +117,41 @@ public static class ReorderGridsPatches
|
|||||||
GridMaps.Add(compoundItem.TemplateId, map);
|
GridMaps.Add(compoundItem.TemplateId, map);
|
||||||
}
|
}
|
||||||
|
|
||||||
compoundItem.Grids = sorted.Select(pair => pair.Key).ToArray();
|
compoundItem.Grids = sortedPairs.Select(pair => pair.Key).ToArray();
|
||||||
____presetGridViews = orderedGridViews;
|
____presetGridViews = orderedGridViews;
|
||||||
|
|
||||||
compoundItem.SetReordered(true);
|
compoundItem.SetReordered(true);
|
||||||
__instance.SetReordered(true);
|
__instance.SetReordered(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static IOrderedEnumerable<KeyValuePair<StashGridClass, GridView>> SortGrids(
|
||||||
|
TemplatedGridsView __instance,
|
||||||
|
IEnumerable<KeyValuePair<StashGridClass, GridView>> pairs)
|
||||||
|
{
|
||||||
|
RectTransform parentView = __instance.RectTransform();
|
||||||
|
Vector2 parentPosition = parentView.pivot.y == 1 ? parentView.position : new Vector2(parentView.position.x, parentView.position.y + parentView.sizeDelta.y);
|
||||||
|
Vector2 gridSize = new(64f * parentView.lossyScale.x, 64f * parentView.lossyScale.y);
|
||||||
|
|
||||||
|
int calculateCoords(KeyValuePair<StashGridClass, GridView> pair)
|
||||||
|
{
|
||||||
|
var grid = pair.Key;
|
||||||
|
var gridView = pair.Value;
|
||||||
|
|
||||||
|
float xOffset = gridView.transform.position.x - parentPosition.x;
|
||||||
|
float yOffset = -(gridView.transform.position.y - parentPosition.y); // invert y since grid coords are upper-left origin
|
||||||
|
|
||||||
|
int x = (int)Math.Round(xOffset / gridSize.x, MidpointRounding.AwayFromZero);
|
||||||
|
int y = (int)Math.Round(yOffset / gridSize.y, MidpointRounding.AwayFromZero);
|
||||||
|
|
||||||
|
return y * 100 + x;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Settings.PrioritizeSmallerGrids.Value)
|
||||||
|
{
|
||||||
|
return pairs.OrderBy(pair => pair.Key.GridWidth.Value).ThenBy(pair => pair.Key.GridHeight.Value).ThenBy(calculateCoords);
|
||||||
|
}
|
||||||
|
|
||||||
|
return pairs.OrderBy(calculateCoords);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -8,6 +8,7 @@ using SPT.Reflection.Patching;
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using TMPro;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.Events;
|
using UnityEngine.Events;
|
||||||
using UnityEngine.EventSystems;
|
using UnityEngine.EventSystems;
|
||||||
@@ -34,6 +35,11 @@ public static class ScrollPatches
|
|||||||
|
|
||||||
private static bool HandleInput(ScrollRect scrollRect)
|
private static bool HandleInput(ScrollRect scrollRect)
|
||||||
{
|
{
|
||||||
|
if (Plugin.TextboxActive())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (scrollRect != null)
|
if (scrollRect != null)
|
||||||
{
|
{
|
||||||
if (Settings.UseHomeEnd.Value)
|
if (Settings.UseHomeEnd.Value)
|
73
src/Patches/SliderPatch.cs
Normal file
73
src/Patches/SliderPatch.cs
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
using EFT.UI;
|
||||||
|
using HarmonyLib;
|
||||||
|
using SPT.Reflection.Patching;
|
||||||
|
using System.Reflection;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.UI;
|
||||||
|
|
||||||
|
namespace UIFixes;
|
||||||
|
|
||||||
|
public static class SliderPatches
|
||||||
|
{
|
||||||
|
public static void Enable()
|
||||||
|
{
|
||||||
|
new IntSliderPatch().Enable();
|
||||||
|
new StepSliderPatch().Enable();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class IntSliderPatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return AccessTools.Method(typeof(IntSlider), nameof(IntSlider.Awake));
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPostfix]
|
||||||
|
public static void Postfix(Slider ____slider)
|
||||||
|
{
|
||||||
|
____slider.GetOrAddComponent<SliderMouseListener>().Init(____slider);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class StepSliderPatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return AccessTools.Method(typeof(StepSlider), nameof(StepSlider.Awake));
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPostfix]
|
||||||
|
public static void Postfix(Slider ____slider)
|
||||||
|
{
|
||||||
|
____slider.GetOrAddComponent<SliderMouseListener>().Init(____slider);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SliderMouseListener : MonoBehaviour
|
||||||
|
{
|
||||||
|
private Slider slider;
|
||||||
|
|
||||||
|
public void Init(Slider slider)
|
||||||
|
{
|
||||||
|
this.slider = slider;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Update()
|
||||||
|
{
|
||||||
|
if (slider == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Input.mouseScrollDelta.y > float.Epsilon)
|
||||||
|
{
|
||||||
|
slider.value = Mathf.Min(slider.value + 1, slider.maxValue);
|
||||||
|
|
||||||
|
}
|
||||||
|
else if (Input.mouseScrollDelta.y < -float.Epsilon)
|
||||||
|
{
|
||||||
|
slider.value = Mathf.Max(slider.value - 1, slider.minValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -43,6 +43,7 @@ public static class SwapPatches
|
|||||||
new DetectGridHighlightPrecheckPatch().Enable();
|
new DetectGridHighlightPrecheckPatch().Enable();
|
||||||
new DetectSlotHighlightPrecheckPatch().Enable();
|
new DetectSlotHighlightPrecheckPatch().Enable();
|
||||||
new SlotCanAcceptSwapPatch().Enable();
|
new SlotCanAcceptSwapPatch().Enable();
|
||||||
|
new WeaponApplyPatch().Enable();
|
||||||
new DetectFilterForSwapPatch().Enable();
|
new DetectFilterForSwapPatch().Enable();
|
||||||
new FixNoGridErrorPatch().Enable();
|
new FixNoGridErrorPatch().Enable();
|
||||||
new SwapOperationRaiseEventsPatch().Enable();
|
new SwapOperationRaiseEventsPatch().Enable();
|
||||||
@@ -145,7 +146,7 @@ public static class SwapPatches
|
|||||||
{
|
{
|
||||||
protected override MethodBase GetTargetMethod()
|
protected override MethodBase GetTargetMethod()
|
||||||
{
|
{
|
||||||
return AccessTools.Method(typeof(ItemView), nameof(ItemView.OnDrag));
|
return AccessTools.Method(typeof(ItemView), nameof(ItemView.OnBeginDrag));
|
||||||
}
|
}
|
||||||
|
|
||||||
[PatchPrefix]
|
[PatchPrefix]
|
||||||
@@ -408,6 +409,39 @@ public static class SwapPatches
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class WeaponApplyPatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return AccessTools.Method(typeof(Weapon), nameof(Weapon.Apply));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow dragging magazines onto weapons and do a mag swap
|
||||||
|
[PatchPostfix]
|
||||||
|
public static void Postfix(Weapon __instance, TraderControllerClass itemController, Item item, bool simulate, ref ItemOperation __result)
|
||||||
|
{
|
||||||
|
if (!Settings.SwapItems.Value || MultiSelect.Active)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the source container is a non-interactable GridView. Specifically for StashSearch, but may exist in other scenarios?
|
||||||
|
if (SourceContainer != null && SourceContainer is GridView && new R.GridView(SourceContainer).NonInteractable)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (__result.Succeeded || item is not MagazineClass || __result.Error is not SlotNotEmptyError)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Slot magazineSlot = __instance.GetMagazineSlot();
|
||||||
|
|
||||||
|
__result = InteractionsHandlerClass.Swap(item, magazineSlot.ContainedItem.Parent, magazineSlot.ContainedItem, item.Parent, itemController, simulate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// The patched method here is called when iterating over all slots to highlight ones that the dragged item can interact with
|
// The patched method here is called when iterating over all slots to highlight ones that the dragged item can interact with
|
||||||
// Since swap has no special highlight, I just skip the patch here (minor perf savings, plus makes debugging a million times easier)
|
// Since swap has no special highlight, I just skip the patch here (minor perf savings, plus makes debugging a million times easier)
|
||||||
public class DetectGridHighlightPrecheckPatch : ModulePatch
|
public class DetectGridHighlightPrecheckPatch : ModulePatch
|
297
src/Patches/TacticalBindsPatches.cs
Normal file
297
src/Patches/TacticalBindsPatches.cs
Normal file
@@ -0,0 +1,297 @@
|
|||||||
|
using Comfort.Common;
|
||||||
|
using EFT;
|
||||||
|
using EFT.InputSystem;
|
||||||
|
using EFT.InventoryLogic;
|
||||||
|
using HarmonyLib;
|
||||||
|
using SPT.Reflection.Patching;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace UIFixes;
|
||||||
|
|
||||||
|
public static class TacticalBindsPatches
|
||||||
|
{
|
||||||
|
public static void Enable()
|
||||||
|
{
|
||||||
|
new BindableTacticalPatch().Enable();
|
||||||
|
new ReachableTacticalPatch().Enable();
|
||||||
|
new UseTacticalPatch().Enable();
|
||||||
|
|
||||||
|
new BindTacticalPatch().Enable();
|
||||||
|
new UnbindTacticalPatch().Enable();
|
||||||
|
new InitQuickBindsPatch().Enable();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class BindableTacticalPatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return AccessTools.Method(typeof(InventoryControllerClass), nameof(InventoryControllerClass.IsAtBindablePlace));
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPostfix]
|
||||||
|
public static void Postfix(InventoryControllerClass __instance, Item item, ref bool __result)
|
||||||
|
{
|
||||||
|
if (__result)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
__result = IsEquippedTacticalDevice(__instance, item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ReachableTacticalPatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return AccessTools.Method(typeof(InventoryControllerClass), nameof(InventoryControllerClass.IsAtReachablePlace));
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPostfix]
|
||||||
|
public static void Postfix(InventoryControllerClass __instance, Item item, ref bool __result)
|
||||||
|
{
|
||||||
|
if (__result)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
__result = IsEquippedTacticalDevice(__instance, item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class UseTacticalPatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return AccessTools.Method(typeof(Player), nameof(Player.SetQuickSlotItem));
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPrefix]
|
||||||
|
public static bool Prefix(Player __instance, EBoundItem quickSlot, Callback<IHandsController> callback)
|
||||||
|
{
|
||||||
|
Item boundItem = __instance.InventoryControllerClass.Inventory.FastAccess.GetBoundItem(quickSlot);
|
||||||
|
if (boundItem == null)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
LightComponent lightComponent = boundItem.GetItemComponent<LightComponent>();
|
||||||
|
if (lightComponent != null)
|
||||||
|
{
|
||||||
|
ToggleLight(__instance, boundItem, lightComponent);
|
||||||
|
callback(null);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
NightVisionComponent nightVisionComponent = boundItem.GetItemComponent<NightVisionComponent>();
|
||||||
|
if (nightVisionComponent != null)
|
||||||
|
{
|
||||||
|
Item rootItem = boundItem.GetRootItemNotEquipment();
|
||||||
|
if (rootItem is Helmet helmet &&
|
||||||
|
__instance.Inventory.Equipment.GetSlot(EquipmentSlot.Headwear).ContainedItem == helmet)
|
||||||
|
{
|
||||||
|
__instance.InventoryControllerClass.TryRunNetworkTransaction(
|
||||||
|
nightVisionComponent.Togglable.Set(!nightVisionComponent.Togglable.On, true, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
callback(null);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ToggleLight(Player player, Item boundItem, LightComponent lightComponent)
|
||||||
|
{
|
||||||
|
FirearmLightStateStruct lightState = new()
|
||||||
|
{
|
||||||
|
Id = lightComponent.Item.Id,
|
||||||
|
IsActive = lightComponent.IsActive,
|
||||||
|
LightMode = lightComponent.SelectedMode
|
||||||
|
};
|
||||||
|
|
||||||
|
if (IsTacticalModeModifierPressed())
|
||||||
|
{
|
||||||
|
lightState.LightMode++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
lightState.IsActive = !lightState.IsActive;
|
||||||
|
}
|
||||||
|
|
||||||
|
Item rootItem = boundItem.GetRootItemNotEquipment();
|
||||||
|
if (rootItem is Weapon weapon &&
|
||||||
|
player.HandsController is Player.FirearmController firearmController &&
|
||||||
|
firearmController.Item == weapon)
|
||||||
|
{
|
||||||
|
firearmController.SetLightsState([lightState], false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rootItem is Helmet helmet &&
|
||||||
|
player.Inventory.Equipment.GetSlot(EquipmentSlot.Headwear).ContainedItem == helmet)
|
||||||
|
{
|
||||||
|
lightComponent.SetLightState(lightState);
|
||||||
|
player.SendHeadlightsPacket(false);
|
||||||
|
player.SwitchHeadLightsAnimation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class InitQuickBindsPatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return AccessTools.Method(typeof(MainMenuController), nameof(MainMenuController.method_5));
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPostfix]
|
||||||
|
public static async void Postfix(MainMenuController __instance, Task __result)
|
||||||
|
{
|
||||||
|
await __result;
|
||||||
|
|
||||||
|
for (EBoundItem index = EBoundItem.Item4; index <= EBoundItem.Item10; index++)
|
||||||
|
{
|
||||||
|
if (__instance.InventoryController.Inventory.FastAccess.BoundItems.ContainsKey(index))
|
||||||
|
{
|
||||||
|
UpdateQuickbindType(__instance.InventoryController.Inventory.FastAccess.BoundItems[index], index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Will "save" control settings, running GClass1911.UpdateInput, which will set (or unset) toggle/hold behavior
|
||||||
|
Singleton<SharedGameSettingsClass>.Instance.Control.Controller.method_3();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class BindTacticalPatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return AccessTools.Method(typeof(GClass2818), nameof(GClass2818.Run));
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPostfix]
|
||||||
|
public static void Postfix(InventoryControllerClass controller, Item item, EBoundItem index)
|
||||||
|
{
|
||||||
|
UpdateQuickbindType(item, index);
|
||||||
|
|
||||||
|
// Will "save" control settings, running GClass1911.UpdateInput, which will set (or unset) toggle/hold behavior
|
||||||
|
Singleton<SharedGameSettingsClass>.Instance.Control.Controller.method_3();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class UnbindTacticalPatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return AccessTools.Method(typeof(GClass2819), nameof(GClass2819.Run));
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPostfix]
|
||||||
|
public static void Postfix(InventoryControllerClass controller, Item item, EBoundItem index)
|
||||||
|
{
|
||||||
|
Quickbind.SetType(index, Quickbind.ItemType.Other);
|
||||||
|
|
||||||
|
// Will "save" control settings, running GClass1911.UpdateInput, which will set (or unset) toggle/hold behavior
|
||||||
|
Singleton<SharedGameSettingsClass>.Instance.Control.Controller.method_3();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsEquippedTacticalDevice(InventoryControllerClass inventoryController, Item item)
|
||||||
|
{
|
||||||
|
LightComponent lightComponent = item.GetItemComponent<LightComponent>();
|
||||||
|
NightVisionComponent nightVisionComponent = item.GetItemComponent<NightVisionComponent>();
|
||||||
|
if (lightComponent == null && nightVisionComponent == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Item rootItem = item.GetRootItemNotEquipment();
|
||||||
|
if (rootItem is Weapon || rootItem is Helmet)
|
||||||
|
{
|
||||||
|
return inventoryController.Inventory.Equipment.Contains(rootItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsTacticalModeModifierPressed()
|
||||||
|
{
|
||||||
|
return Settings.TacticalModeModifier.Value switch
|
||||||
|
{
|
||||||
|
TacticalBindModifier.Shift => Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift),
|
||||||
|
TacticalBindModifier.Control => Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl),
|
||||||
|
TacticalBindModifier.Alt => Input.GetKey(KeyCode.LeftAlt) || Input.GetKey(KeyCode.RightAlt),
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void UpdateQuickbindType(Item item, EBoundItem index)
|
||||||
|
{
|
||||||
|
if (item == null)
|
||||||
|
{
|
||||||
|
Quickbind.SetType(index, Quickbind.ItemType.Other);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LightComponent lightComponent = item.GetItemComponent<LightComponent>();
|
||||||
|
if (lightComponent != null)
|
||||||
|
{
|
||||||
|
Item rootItem = item.GetRootItemNotEquipment();
|
||||||
|
if (rootItem is Weapon)
|
||||||
|
{
|
||||||
|
Quickbind.SetType(index, Quickbind.ItemType.Tactical);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rootItem is Helmet)
|
||||||
|
{
|
||||||
|
Quickbind.SetType(index, Quickbind.ItemType.Headlight);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NightVisionComponent nvComponent = item.GetItemComponent<NightVisionComponent>();
|
||||||
|
if (nvComponent != null)
|
||||||
|
{
|
||||||
|
Quickbind.SetType(index, Quickbind.ItemType.NightVision);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Quickbind.SetType(index, Quickbind.ItemType.Other);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Quickbind
|
||||||
|
{
|
||||||
|
public enum ItemType
|
||||||
|
{
|
||||||
|
Other,
|
||||||
|
Tactical,
|
||||||
|
Headlight,
|
||||||
|
NightVision
|
||||||
|
}
|
||||||
|
|
||||||
|
private static readonly Dictionary<EBoundItem, ItemType> TacticalQuickbinds = new()
|
||||||
|
{
|
||||||
|
{ EBoundItem.Item4, ItemType.Other },
|
||||||
|
{ EBoundItem.Item5, ItemType.Other },
|
||||||
|
{ EBoundItem.Item6, ItemType.Other },
|
||||||
|
{ EBoundItem.Item7, ItemType.Other },
|
||||||
|
{ EBoundItem.Item8, ItemType.Other },
|
||||||
|
{ EBoundItem.Item9, ItemType.Other },
|
||||||
|
{ EBoundItem.Item10, ItemType.Other },
|
||||||
|
};
|
||||||
|
|
||||||
|
public static ItemType GetType(EBoundItem index) => TacticalQuickbinds[index];
|
||||||
|
public static void SetType(EBoundItem index, ItemType type) => TacticalQuickbinds[index] = type;
|
||||||
|
|
||||||
|
public static ItemType GetType(EGameKey gameKey)
|
||||||
|
{
|
||||||
|
int offset = gameKey - EGameKey.Slot4;
|
||||||
|
return GetType(EBoundItem.Item4 + offset);
|
||||||
|
}
|
||||||
|
}
|
75
src/Patches/TagPatches.cs
Normal file
75
src/Patches/TagPatches.cs
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
using EFT.UI;
|
||||||
|
using EFT.UI.DragAndDrop;
|
||||||
|
using HarmonyLib;
|
||||||
|
using SPT.Reflection.Patching;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using TMPro;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.UI;
|
||||||
|
|
||||||
|
namespace UIFixes;
|
||||||
|
|
||||||
|
public static class TagPatches
|
||||||
|
{
|
||||||
|
public static void Enable()
|
||||||
|
{
|
||||||
|
new OnEnterPatch().Enable();
|
||||||
|
new TagsOverCaptionsPatch().Enable();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class OnEnterPatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return AccessTools.DeclaredMethod(typeof(EditTagWindow), nameof(EditTagWindow.Show));
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPostfix]
|
||||||
|
public static void Postfix(EditTagWindow __instance, ValidationInputField ____tagInput)
|
||||||
|
{
|
||||||
|
____tagInput.onSubmit.AddListener(value => __instance.method_4());
|
||||||
|
____tagInput.ActivateInputField();
|
||||||
|
____tagInput.Select();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TagsOverCaptionsPatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return AccessTools.Method(typeof(GridItemView), nameof(GridItemView.method_21));
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPostfix]
|
||||||
|
public static async void Postfix(GridItemView __instance, TextMeshProUGUI ___TagName, TextMeshProUGUI ___Caption, Image ____tagColor, Task __result)
|
||||||
|
{
|
||||||
|
await __result;
|
||||||
|
|
||||||
|
// Rerun logic with preferred priority. Running again rather than prefix overwrite because this also fixes the existing race condition
|
||||||
|
___TagName.gameObject.SetActive(false);
|
||||||
|
___Caption.gameObject.SetActive(true);
|
||||||
|
await Task.Yield();
|
||||||
|
RectTransform tagTransform = ____tagColor.rectTransform;
|
||||||
|
float tagSpace = __instance.RectTransform.sizeDelta.x - ___Caption.renderedWidth - 2f;
|
||||||
|
if (tagSpace < 40f)
|
||||||
|
{
|
||||||
|
tagTransform.sizeDelta = new Vector2(__instance.RectTransform.sizeDelta.x, tagTransform.sizeDelta.y);
|
||||||
|
if (Settings.TagsOverCaptions.Value)
|
||||||
|
{
|
||||||
|
___TagName.gameObject.SetActive(true);
|
||||||
|
float tagSize = Mathf.Clamp(___TagName.preferredWidth + 12f, 40f, __instance.RectTransform.sizeDelta.x - 2f);
|
||||||
|
tagTransform.sizeDelta = new Vector2(tagSize, ____tagColor.rectTransform.sizeDelta.y);
|
||||||
|
|
||||||
|
___Caption.gameObject.SetActive(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
___TagName.gameObject.SetActive(true);
|
||||||
|
float tagSize = Mathf.Clamp(___TagName.preferredWidth + 12f, 40f, tagSpace);
|
||||||
|
tagTransform.sizeDelta = new Vector2(tagSize, ____tagColor.rectTransform.sizeDelta.y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -3,6 +3,7 @@ using EFT.UI;
|
|||||||
using EFT.UI.DragAndDrop;
|
using EFT.UI.DragAndDrop;
|
||||||
using HarmonyLib;
|
using HarmonyLib;
|
||||||
using SPT.Reflection.Patching;
|
using SPT.Reflection.Patching;
|
||||||
|
using System;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.EventSystems;
|
using UnityEngine.EventSystems;
|
||||||
@@ -58,29 +59,36 @@ public static class TradingAutoSwitchPatches
|
|||||||
TradingItemView __instance,
|
TradingItemView __instance,
|
||||||
PointerEventData.InputButton button,
|
PointerEventData.InputButton button,
|
||||||
bool doubleClick,
|
bool doubleClick,
|
||||||
ETradingItemViewType ___etradingItemViewType_0, bool ___bool_8)
|
ETradingItemViewType ___etradingItemViewType_0,
|
||||||
|
bool ___bool_8)
|
||||||
{
|
{
|
||||||
if (!Settings.AutoSwitchTrading.Value)
|
if (!Settings.AutoSwitchTrading.Value || SellTab == null || BuyTab == null)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var assortmentController = __instance.R().TraderAssortmentController;
|
||||||
|
if (assortmentController == null)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
var tradingItemView = __instance.R();
|
|
||||||
if (button != PointerEventData.InputButton.Left || ___etradingItemViewType_0 == ETradingItemViewType.TradingTable)
|
if (button != PointerEventData.InputButton.Left || ___etradingItemViewType_0 == ETradingItemViewType.TradingTable)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ctrlPressed = Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl);
|
bool ctrlPressed = Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl);
|
||||||
|
|
||||||
if (!ctrlPressed && doubleClick)
|
if (!ctrlPressed && doubleClick)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!___bool_8 && ctrlPressed && tradingItemView.TraderAssortmentController.QuickFindTradingAppropriatePlace(__instance.Item, null))
|
try
|
||||||
{
|
{
|
||||||
__instance.ItemContext.CloseDependentWindows();
|
if (!___bool_8 && ctrlPressed && assortmentController.QuickFindTradingAppropriatePlace(__instance.Item, null))
|
||||||
|
{
|
||||||
|
__instance.ItemContext?.CloseDependentWindows();
|
||||||
__instance.HideTooltip();
|
__instance.HideTooltip();
|
||||||
Singleton<GUISounds>.Instance.PlayItemSound(__instance.Item.ItemSound, EInventorySoundType.pickup, false);
|
Singleton<GUISounds>.Instance.PlayItemSound(__instance.Item.ItemSound, EInventorySoundType.pickup, false);
|
||||||
|
|
||||||
@@ -91,12 +99,17 @@ public static class TradingAutoSwitchPatches
|
|||||||
|
|
||||||
if (___bool_8)
|
if (___bool_8)
|
||||||
{
|
{
|
||||||
tradingItemView.TraderAssortmentController.SelectItem(__instance.Item);
|
assortmentController.SelectItem(__instance.Item);
|
||||||
|
|
||||||
BuyTab.OnPointerClick(null);
|
BuyTab.OnPointerClick(null);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger.LogError(e);
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
231
src/Patches/UnloadAmmoPatches.cs
Normal file
231
src/Patches/UnloadAmmoPatches.cs
Normal file
@@ -0,0 +1,231 @@
|
|||||||
|
using Comfort.Common;
|
||||||
|
using EFT;
|
||||||
|
using EFT.Communications;
|
||||||
|
using EFT.InventoryLogic;
|
||||||
|
using EFT.UI;
|
||||||
|
using HarmonyLib;
|
||||||
|
using SPT.Reflection.Patching;
|
||||||
|
using SPT.Reflection.Utils;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace UIFixes;
|
||||||
|
|
||||||
|
public static class UnloadAmmoPatches
|
||||||
|
{
|
||||||
|
public static void Enable()
|
||||||
|
{
|
||||||
|
new TradingPlayerPatch().Enable();
|
||||||
|
new TransferPlayerPatch().Enable();
|
||||||
|
new UnloadScavTransferPatch().Enable();
|
||||||
|
new NoScavStashPatch().Enable();
|
||||||
|
|
||||||
|
new UnloadAmmoBoxPatch().Enable();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TradingPlayerPatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return AccessTools.DeclaredProperty(R.TradingInteractions.Type, "AvailableInteractions").GetMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
[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;
|
||||||
|
}
|
||||||
|
|
||||||
|
[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));
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPrefix]
|
||||||
|
public static bool Prefix(InventoryControllerClass __instance, MagazineClass magazine, ref Task<IResult> __result)
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPrefix]
|
||||||
|
public static void Prefix(InventoryContainerClass scavController)
|
||||||
|
{
|
||||||
|
scavController.Inventory.Stash = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class UnloadAmmoBoxPatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return AccessTools.Method(typeof(ItemUiContext), nameof(ItemUiContext.UnloadAmmo));
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPrefix]
|
||||||
|
public static bool Prefix(Item item, ref Task __result, InventoryContainerClass ___inventoryControllerClass)
|
||||||
|
{
|
||||||
|
if (!Settings.UnloadAmmoBoxInPlace.Value || item is not AmmoBox ammoBox)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ammoBox.Cartridges.Last is not BulletClass lastBullet)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
__result = UnloadAmmoBox(ammoBox, ___inventoryControllerClass);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task UnloadAmmoBox(AmmoBox ammoBox, InventoryControllerClass inventoryController)
|
||||||
|
{
|
||||||
|
BulletClass lastBullet = ammoBox.Cartridges.Last as BulletClass;
|
||||||
|
IEnumerable<LootItemClass> containers = inventoryController.Inventory.Stash != null ?
|
||||||
|
[inventoryController.Inventory.Equipment, inventoryController.Inventory.Stash] :
|
||||||
|
[inventoryController.Inventory.Equipment];
|
||||||
|
|
||||||
|
// Explicitly add the current parent before its moved. IgnoreParentItem will be sent along later
|
||||||
|
containers = containers.Prepend(ammoBox.Parent.Container.ParentItem as LootItemClass);
|
||||||
|
|
||||||
|
// Move the box to a temporary stash so it can unload in place
|
||||||
|
TraderControllerClass tempController = GetTempController();
|
||||||
|
StashClass tempStash = tempController.RootItem as StashClass;
|
||||||
|
var moveOperation = InteractionsHandlerClass.Move(ammoBox, tempStash.Grid.FindLocationForItem(ammoBox), inventoryController, true);
|
||||||
|
if (moveOperation.Succeeded)
|
||||||
|
{
|
||||||
|
IResult networkResult = await inventoryController.TryRunNetworkTransaction(moveOperation);
|
||||||
|
if (networkResult.Failed)
|
||||||
|
{
|
||||||
|
moveOperation = new GClass3370(networkResult.Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Surprise! The operation is STILL not done. <insert enraged, profanity-laced, unhinged anti-BSG rant here>
|
||||||
|
await Task.Yield();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (moveOperation.Failed)
|
||||||
|
{
|
||||||
|
NotificationManagerClass.DisplayWarningNotification(moveOperation.Error.ToString(), ENotificationDurationType.Default);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool unloadedAny = false;
|
||||||
|
ItemOperation operation = default;
|
||||||
|
for (BulletClass bullet = lastBullet; bullet != null; bullet = ammoBox.Cartridges.Last as BulletClass)
|
||||||
|
{
|
||||||
|
operation = InteractionsHandlerClass.QuickFindAppropriatePlace(
|
||||||
|
bullet,
|
||||||
|
inventoryController,
|
||||||
|
containers,
|
||||||
|
InteractionsHandlerClass.EMoveItemOrder.UnloadAmmo | InteractionsHandlerClass.EMoveItemOrder.IgnoreItemParent,
|
||||||
|
true);
|
||||||
|
|
||||||
|
if (operation.Failed)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
unloadedAny = true;
|
||||||
|
|
||||||
|
IResult networkResult = await inventoryController.TryRunNetworkTransaction(operation);
|
||||||
|
if (networkResult.Failed)
|
||||||
|
{
|
||||||
|
operation = new GClass3370(networkResult.Error);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (operation.Value is GInterface343 raisable)
|
||||||
|
{
|
||||||
|
raisable.TargetItem.RaiseRefreshEvent(false, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Surprise! The operation STILL IS NOT DONE. <insert enraged, profanity-laced, unhinged anti-BSG rant here>
|
||||||
|
await Task.Yield();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (unloadedAny && Singleton<GUISounds>.Instantiated)
|
||||||
|
{
|
||||||
|
Singleton<GUISounds>.Instance.PlayItemSound(lastBullet.ItemSound, EInventorySoundType.drop, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (operation.Succeeded)
|
||||||
|
{
|
||||||
|
inventoryController.DestroyItem(ammoBox);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ammoBox.RaiseRefreshEvent(false, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (operation.Failed)
|
||||||
|
{
|
||||||
|
NotificationManagerClass.DisplayWarningNotification(operation.Error.ToString(), ENotificationDurationType.Default);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TraderControllerClass GetTempController()
|
||||||
|
{
|
||||||
|
if (Plugin.InRaid())
|
||||||
|
{
|
||||||
|
return Singleton<GameWorld>.Instance.R().TraderController;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var profile = PatchConstants.BackEndSession.Profile;
|
||||||
|
StashClass fakeStash = Singleton<ItemFactory>.Instance.CreateFakeStash();
|
||||||
|
return new TraderControllerClass(fakeStash, profile.ProfileId, profile.Nickname);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
535
src/Patches/WeaponModdingPatches.cs
Normal file
535
src/Patches/WeaponModdingPatches.cs
Normal file
@@ -0,0 +1,535 @@
|
|||||||
|
using Comfort.Common;
|
||||||
|
using EFT;
|
||||||
|
using EFT.InventoryLogic;
|
||||||
|
using EFT.UI.DragAndDrop;
|
||||||
|
using HarmonyLib;
|
||||||
|
using SPT.Reflection.Patching;
|
||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace UIFixes;
|
||||||
|
|
||||||
|
public static class WeaponModdingPatches
|
||||||
|
{
|
||||||
|
private const string MultitoolId = "544fb5454bdc2df8738b456a";
|
||||||
|
private static readonly string[] EquippedSlots = ["FirstPrimaryWeapon", "SecondPrimaryWeapon", "Holster"];
|
||||||
|
|
||||||
|
public static void Enable()
|
||||||
|
{
|
||||||
|
new ResizePatch().Enable();
|
||||||
|
new ResizeHelperPatch().Enable();
|
||||||
|
new ResizeOperationRollbackPatch().Enable();
|
||||||
|
new MoveBeforeNetworkTransactionPatch().Enable();
|
||||||
|
|
||||||
|
new ModEquippedPatch().Enable();
|
||||||
|
new InspectLockedPatch().Enable();
|
||||||
|
new ModCanBeMovedPatch().Enable();
|
||||||
|
new ModCanDetachPatch().Enable();
|
||||||
|
new ModCanApplyPatch().Enable();
|
||||||
|
new ModRaidModdablePatch().Enable();
|
||||||
|
new EmptyVitalPartsPatch().Enable();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ResizePatch : ModulePatch
|
||||||
|
{
|
||||||
|
public static MoveOperation NecessaryMoveOperation = null;
|
||||||
|
|
||||||
|
private static bool InPatch = false;
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return AccessTools.Method(typeof(StashGridClass), nameof(StashGridClass.Resize));
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPostfix]
|
||||||
|
public static void Postfix(StashGridClass __instance, Item item, XYCellSizeStruct oldSize, XYCellSizeStruct newSize, bool simulate, ref bool __result)
|
||||||
|
{
|
||||||
|
if (__result || InPatch)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.Owner is not InventoryControllerClass inventoryController)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LocationInGrid itemLocation = __instance.GetItemLocation(item);
|
||||||
|
|
||||||
|
// The sizes passed in are the template sizes, need to make match the item's rotation
|
||||||
|
XYCellSizeStruct actualOldSize = itemLocation.r.Rotate(oldSize);
|
||||||
|
XYCellSizeStruct actualNewSize = itemLocation.r.Rotate(newSize);
|
||||||
|
|
||||||
|
// Figure out which direction(s) its growing
|
||||||
|
int horizontalGrowth = actualNewSize.X - actualOldSize.X;
|
||||||
|
int verticalGrowth = actualNewSize.Y - actualOldSize.Y;
|
||||||
|
|
||||||
|
// Can't move up/left more than the position
|
||||||
|
horizontalGrowth = Math.Min(horizontalGrowth, itemLocation.x);
|
||||||
|
verticalGrowth = Math.Min(verticalGrowth, itemLocation.y);
|
||||||
|
|
||||||
|
// Try moving it
|
||||||
|
try
|
||||||
|
{
|
||||||
|
InPatch = true;
|
||||||
|
for (int x = 0; x <= horizontalGrowth; x++)
|
||||||
|
{
|
||||||
|
for (int y = 0; y <= verticalGrowth; y++)
|
||||||
|
{
|
||||||
|
if (x + y == 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
LocationInGrid newLocation = new(itemLocation.x - x, itemLocation.y - y, itemLocation.r);
|
||||||
|
ItemAddress newAddress = new GridItemAddress(__instance, newLocation);
|
||||||
|
|
||||||
|
var moveOperation = InteractionsHandlerClass.Move(item, newAddress, inventoryController, false);
|
||||||
|
if (moveOperation.Failed || moveOperation.Value == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool resizeResult = __instance.Resize(item, oldSize, newSize, simulate);
|
||||||
|
|
||||||
|
// If simulating, rollback. Note that for some reason, only the Fold case even uses simulate
|
||||||
|
// The other cases (adding a mod, etc) never simulate, and then rollback later. Likely because there is normally
|
||||||
|
// no server side-effect of a resize - the only effect is updating the grid's free/used map.
|
||||||
|
if (simulate || !resizeResult)
|
||||||
|
{
|
||||||
|
moveOperation.Value.RollBack();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resizeResult)
|
||||||
|
{
|
||||||
|
// Stash the move operation so it can be executed or rolled back later
|
||||||
|
NecessaryMoveOperation = moveOperation.Value;
|
||||||
|
|
||||||
|
__result = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
InPatch = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ResizeHelperPatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return AccessTools.Method(typeof(InteractionsHandlerClass), nameof(InteractionsHandlerClass.Resize_Helper));
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPostfix]
|
||||||
|
public static void Postfix(ref GStruct414<ResizeOperation> __result)
|
||||||
|
{
|
||||||
|
if (__result.Failed || __result.Value == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ResizePatch.NecessaryMoveOperation != null)
|
||||||
|
{
|
||||||
|
__result.Value.SetMoveOperation(ResizePatch.NecessaryMoveOperation);
|
||||||
|
ResizePatch.NecessaryMoveOperation = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ResizeOperationRollbackPatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return AccessTools.Method(typeof(ResizeOperation), nameof(ResizeOperation.RollBack));
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPostfix]
|
||||||
|
public static void Postfix(ResizeOperation __instance)
|
||||||
|
{
|
||||||
|
MoveOperation moveOperation = __instance.GetMoveOperation();
|
||||||
|
if (moveOperation != null)
|
||||||
|
{
|
||||||
|
moveOperation.RollBack();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MoveBeforeNetworkTransactionPatch : ModulePatch
|
||||||
|
{
|
||||||
|
private static bool InPatch = false;
|
||||||
|
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return AccessTools.Method(typeof(TraderControllerClass), nameof(TraderControllerClass.RunNetworkTransaction));
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPrefix]
|
||||||
|
public static void Prefix(TraderControllerClass __instance, IRaiseEvents operationResult)
|
||||||
|
{
|
||||||
|
if (InPatch)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
MoveOperation extraOperation = null;
|
||||||
|
if (operationResult is MoveOperation moveOperation)
|
||||||
|
{
|
||||||
|
extraOperation = moveOperation.R().AddOperation?.R().ResizeOperation?.GetMoveOperation();
|
||||||
|
}
|
||||||
|
else if (operationResult is FoldOperation foldOperation)
|
||||||
|
{
|
||||||
|
extraOperation = foldOperation.ResizeResult?.GetMoveOperation();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (extraOperation != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
InPatch = true;
|
||||||
|
__instance.RunNetworkTransaction(extraOperation);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
InPatch = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ModEquippedPatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return AccessTools.Method(R.ContextMenuHelper.Type, "IsInteractive");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enable/disable options in the context menu
|
||||||
|
[PatchPostfix]
|
||||||
|
public static void Postfix(EItemInfoButton button, ref IResult __result, Item ___item_0)
|
||||||
|
{
|
||||||
|
// These two are only visible out of raid, enable them
|
||||||
|
if (Settings.ModifyEquippedWeapons.Value && (button == EItemInfoButton.Modding || button == EItemInfoButton.EditBuild))
|
||||||
|
{
|
||||||
|
if (__result.Succeed || !Singleton<BonusController>.Instance.HasBonus(EBonusType.UnlockWeaponModification))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
__result = SuccessfulResult.New;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is surprisingly active in raid? Only enable out of raid.
|
||||||
|
if (button == EItemInfoButton.Disassemble)
|
||||||
|
{
|
||||||
|
if (!Plugin.InRaid() && Settings.ModifyEquippedWeapons.Value)
|
||||||
|
{
|
||||||
|
__result = SuccessfulResult.New;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// These are on mods; normally the context menu is disabled so these are individually not disabled
|
||||||
|
// Need to do the disabling as appropriate
|
||||||
|
if (___item_0 is Mod mod && (button == EItemInfoButton.Uninstall || button == EItemInfoButton.Discard))
|
||||||
|
{
|
||||||
|
if (!CanModify(mod, out string error))
|
||||||
|
{
|
||||||
|
__result = new FailedResult(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class InspectLockedPatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return AccessTools.Method(typeof(ModSlotView), nameof(ModSlotView.method_14));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enable context menu on normally unmoddable slots, maybe keep them gray
|
||||||
|
[PatchPostfix]
|
||||||
|
public static void Postfix(ModSlotView __instance, ref bool ___bool_1, CanvasGroup ____canvasGroup)
|
||||||
|
{
|
||||||
|
// Keep it grayed out and warning text if its not draggable, even if context menu is enabled
|
||||||
|
if (__instance.Slot.ContainedItem is Mod mod && CanModify(mod, out string error))
|
||||||
|
{
|
||||||
|
___bool_1 = false;
|
||||||
|
____canvasGroup.alpha = 1f;
|
||||||
|
}
|
||||||
|
|
||||||
|
____canvasGroup.blocksRaycasts = true;
|
||||||
|
____canvasGroup.interactable = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ModCanBeMovedPatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return AccessTools.Method(typeof(Mod), nameof(Mod.CanBeMoved));
|
||||||
|
}
|
||||||
|
|
||||||
|
// As far as I can tell this never gets called, but hey
|
||||||
|
[PatchPostfix]
|
||||||
|
public static void Postfix(Mod __instance, IContainer toContainer, ref GStruct416<bool> __result)
|
||||||
|
{
|
||||||
|
if (__result.Succeeded)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!CanModify(__instance, out string itemError))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (toContainer is not Slot toSlot || !CanModify(R.SlotItemAddress.Create(toSlot), out string slotError))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
__result = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ModCanDetachPatch : ModulePatch
|
||||||
|
{
|
||||||
|
private static Type TargetMethodReturnType;
|
||||||
|
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
MethodInfo method = AccessTools.Method(typeof(InteractionsHandlerClass), nameof(InteractionsHandlerClass.smethod_1));
|
||||||
|
TargetMethodReturnType = method.ReturnType;
|
||||||
|
return method;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This gets invoked when dragging items around between slots
|
||||||
|
[PatchPostfix]
|
||||||
|
public static void Postfix(Item item, ItemAddress to, TraderControllerClass itemController, ref GStruct416<GClass3372> __result)
|
||||||
|
{
|
||||||
|
if (item is not Mod mod)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Plugin.InRaid() && __result.Succeeded)
|
||||||
|
{
|
||||||
|
// In raid successes are all fine
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool canModify = CanModify(mod, out string error) && CanModify(to, out error);
|
||||||
|
if (canModify == __result.Succeeded)
|
||||||
|
{
|
||||||
|
// In agreement, just check the error is best to show
|
||||||
|
if (Settings.ModifyRaidWeapons.Value == ModRaidWeapon.WithTool &&
|
||||||
|
(__result.Error is NotModdableInRaidError || __result.Error is ModVitalPartInRaidError))
|
||||||
|
{
|
||||||
|
// Double check this is an unequipped weapon
|
||||||
|
Weapon weapon = item.GetRootItemNotEquipment() as Weapon ?? to.GetRootItemNotEquipment() as Weapon;
|
||||||
|
if (weapon != null && !EquippedSlots.Contains(weapon.Parent.Container.ID))
|
||||||
|
{
|
||||||
|
__result = new MultitoolNeededError(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (__result.Failed && canModify)
|
||||||
|
{
|
||||||
|
// Override result with success if DestinationCheck passes
|
||||||
|
var destinationCheck = InteractionsHandlerClass.DestinationCheck(item.Parent, to, itemController.OwnerType);
|
||||||
|
if (destinationCheck.Failed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
__result = default;
|
||||||
|
}
|
||||||
|
else if (__result.Succeeded && !canModify)
|
||||||
|
{
|
||||||
|
// Out of raid, likely dragging a mod that was previously non-interactive, need to actually block
|
||||||
|
__result = new VitalPartInHandsError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class VitalPartInHandsError : InventoryError
|
||||||
|
{
|
||||||
|
public override string GetLocalizedDescription()
|
||||||
|
{
|
||||||
|
return "Vital mod weapon in hands".Localized();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return "Vital mod weapon in hands";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ModCanApplyPatch : ModulePatch
|
||||||
|
{
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return AccessTools.Method(typeof(LootItemClass), nameof(LootItemClass.Apply));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gets called when dropping mods on top of weapons
|
||||||
|
[PatchPrefix]
|
||||||
|
public static void Prefix(LootItemClass __instance, Item item)
|
||||||
|
{
|
||||||
|
if (!Plugin.InRaid())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (__instance is not Weapon weapon || item is not Mod mod || EquippedSlots.Contains(weapon.Parent.Container.ID))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CanModify(mod, out string error))
|
||||||
|
{
|
||||||
|
ModRaidModdablePatch.Override = true;
|
||||||
|
EmptyVitalPartsPatch.Override = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPostfix]
|
||||||
|
public static void Postfix(LootItemClass __instance, ref ItemOperation __result)
|
||||||
|
{
|
||||||
|
ModRaidModdablePatch.Override = false;
|
||||||
|
EmptyVitalPartsPatch.Override = false;
|
||||||
|
|
||||||
|
// If setting is multitool, may need to change some errors
|
||||||
|
if (Settings.ModifyRaidWeapons.Value == ModRaidWeapon.WithTool)
|
||||||
|
{
|
||||||
|
if (__instance is not Weapon weapon || EquippedSlots.Contains(weapon.Parent.Container.ID))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (__result.Error is NotModdableInRaidError || __result.Error is ModVitalPartInRaidError)
|
||||||
|
{
|
||||||
|
__result = new MultitoolNeededError(__instance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ModRaidModdablePatch : ModulePatch
|
||||||
|
{
|
||||||
|
public static bool Override = false;
|
||||||
|
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return AccessTools.Property(typeof(Mod), nameof(Mod.RaidModdable)).GetMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPostfix]
|
||||||
|
public static void Postfix(ref bool __result)
|
||||||
|
{
|
||||||
|
__result = __result || Override;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class EmptyVitalPartsPatch : ModulePatch
|
||||||
|
{
|
||||||
|
public static bool Override = false;
|
||||||
|
|
||||||
|
protected override MethodBase GetTargetMethod()
|
||||||
|
{
|
||||||
|
return AccessTools.Property(typeof(LootItemClass), nameof(LootItemClass.VitalParts)).GetMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
[PatchPrefix]
|
||||||
|
public static bool Prefix(ref IEnumerable<Slot> __result)
|
||||||
|
{
|
||||||
|
if (Override)
|
||||||
|
{
|
||||||
|
__result = [];
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool CanModify(Mod item, out string error)
|
||||||
|
{
|
||||||
|
return CanModify(item, item?.Parent, out error);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool CanModify(ItemAddress itemAddress, out string error)
|
||||||
|
{
|
||||||
|
return CanModify(null, itemAddress, out error);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool CanModify(Mod item, ItemAddress itemAddress, out string error)
|
||||||
|
{
|
||||||
|
error = null;
|
||||||
|
|
||||||
|
// If it's raidmoddable and not in a vital slot, then it's all good
|
||||||
|
if ((item == null || item.RaidModdable) &&
|
||||||
|
(!R.SlotItemAddress.Type.IsAssignableFrom(itemAddress.GetType()) || !new R.SlotItemAddress(itemAddress).Slot.Required))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Item rootItem = itemAddress.GetRootItemNotEquipment();
|
||||||
|
if (rootItem is not Weapon weapon || weapon.CurrentAddress == null)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Can't modify weapon in hands
|
||||||
|
if (EquippedSlots.Contains(weapon.Parent.Container.ID))
|
||||||
|
{
|
||||||
|
if (Plugin.InRaid())
|
||||||
|
{
|
||||||
|
error = "Inventory Errors/Not moddable in raid";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (!Settings.ModifyEquippedWeapons.Value)
|
||||||
|
{
|
||||||
|
error = "Vital mod weapon in hands";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not in raid, not in hands: anything is possible
|
||||||
|
if (!Plugin.InRaid())
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Settings.ModifyRaidWeapons.Value == ModRaidWeapon.Never)
|
||||||
|
{
|
||||||
|
error = "Inventory Errors/Not moddable in raid";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Player player = Singleton<GameWorld>.Instance.MainPlayer;
|
||||||
|
bool hasMultitool = player.Equipment.GetAllItems().Any(i => i.TemplateId == MultitoolId);
|
||||||
|
|
||||||
|
if (Settings.ModifyRaidWeapons.Value == ModRaidWeapon.WithTool && !hasMultitool)
|
||||||
|
{
|
||||||
|
error = "Inventory Errors/Not moddable without multitool";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
@@ -1,10 +1,14 @@
|
|||||||
using BepInEx;
|
using BepInEx;
|
||||||
|
using BepInEx.Bootstrap;
|
||||||
using Comfort.Common;
|
using Comfort.Common;
|
||||||
using EFT;
|
using EFT;
|
||||||
|
using TMPro;
|
||||||
|
using UnityEngine.EventSystems;
|
||||||
|
|
||||||
namespace UIFixes;
|
namespace UIFixes;
|
||||||
|
|
||||||
[BepInPlugin(PluginInfo.PLUGIN_GUID, PluginInfo.PLUGIN_NAME, PluginInfo.PLUGIN_VERSION)]
|
[BepInPlugin(PluginInfo.PLUGIN_GUID, PluginInfo.PLUGIN_NAME, PluginInfo.PLUGIN_VERSION)]
|
||||||
|
[BepInDependency("com.fika.core", BepInDependency.DependencyFlags.SoftDependency)]
|
||||||
public class Plugin : BaseUnityPlugin
|
public class Plugin : BaseUnityPlugin
|
||||||
{
|
{
|
||||||
public void Awake()
|
public void Awake()
|
||||||
@@ -68,6 +72,14 @@ public class Plugin : BaseUnityPlugin
|
|||||||
ReloadInPlacePatches.Enable();
|
ReloadInPlacePatches.Enable();
|
||||||
BarterOfferPatches.Enable();
|
BarterOfferPatches.Enable();
|
||||||
new UnlockCursorPatch().Enable();
|
new UnlockCursorPatch().Enable();
|
||||||
|
LimitDragPatches.Enable();
|
||||||
|
new HideoutCameraPatch().Enable();
|
||||||
|
WeaponModdingPatches.Enable();
|
||||||
|
TagPatches.Enable();
|
||||||
|
TacticalBindsPatches.Enable();
|
||||||
|
AddOfferContextMenuPatches.Enable();
|
||||||
|
new OperationQueuePatch().Enable();
|
||||||
|
SliderPatches.Enable();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool InRaid()
|
public static bool InRaid()
|
||||||
@@ -75,4 +87,34 @@ public class Plugin : BaseUnityPlugin
|
|||||||
bool? inRaid = Singleton<AbstractGame>.Instance?.InRaid;
|
bool? inRaid = Singleton<AbstractGame>.Instance?.InRaid;
|
||||||
return inRaid.HasValue && inRaid.Value;
|
return inRaid.HasValue && inRaid.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static bool TextboxActive()
|
||||||
|
{
|
||||||
|
return EventSystem.current?.currentSelectedGameObject != null &&
|
||||||
|
EventSystem.current.currentSelectedGameObject.GetComponent<TMP_InputField>() != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool? IsFikaPresent;
|
||||||
|
|
||||||
|
public static bool FikaPresent()
|
||||||
|
{
|
||||||
|
if (!IsFikaPresent.HasValue)
|
||||||
|
{
|
||||||
|
IsFikaPresent = Chainloader.PluginInfos.ContainsKey("com.fika.core");
|
||||||
|
}
|
||||||
|
|
||||||
|
return IsFikaPresent.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool? IsMergeConsumablesPresent;
|
||||||
|
|
||||||
|
public static bool MergeConsumablesPresent()
|
||||||
|
{
|
||||||
|
if (!IsMergeConsumablesPresent.HasValue)
|
||||||
|
{
|
||||||
|
IsMergeConsumablesPresent = Chainloader.PluginInfos.ContainsKey("com.lacyway.mc");
|
||||||
|
}
|
||||||
|
|
||||||
|
return IsMergeConsumablesPresent.Value;
|
||||||
|
}
|
||||||
}
|
}
|
@@ -67,6 +67,9 @@ public static class R
|
|||||||
InventoryScreen.InitTypes();
|
InventoryScreen.InitTypes();
|
||||||
ScavengerInventoryScreen.InitTypes();
|
ScavengerInventoryScreen.InitTypes();
|
||||||
LocalizedText.InitTypes();
|
LocalizedText.InitTypes();
|
||||||
|
GameWorld.InitTypes();
|
||||||
|
MoveOperationResult.InitTypes();
|
||||||
|
AddOperationResult.InitTypes();
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract class Wrapper(object value)
|
public abstract class Wrapper(object value)
|
||||||
@@ -733,10 +736,12 @@ public static class R
|
|||||||
public class InventoryInteractions(object value) : Wrapper(value)
|
public class InventoryInteractions(object value) : Wrapper(value)
|
||||||
{
|
{
|
||||||
public static Type Type { get; private set; }
|
public static Type Type { get; private set; }
|
||||||
|
public static Type CompleteType { get; private set; }
|
||||||
|
|
||||||
public static void InitTypes()
|
public static void InitTypes()
|
||||||
{
|
{
|
||||||
Type = PatchConstants.EftTypes.Single(t => t.GetField("HIDEOUT_WEAPON_MODIFICATION_REQUIRED") != null); // GClass3045
|
Type = PatchConstants.EftTypes.Single(t => t.GetField("HIDEOUT_WEAPON_MODIFICATION_REQUIRED") != null); // GClass3045
|
||||||
|
CompleteType = PatchConstants.EftTypes.Single(t => t != Type && Type.IsAssignableFrom(t)); // GClass3046
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -867,6 +872,48 @@ public static class R
|
|||||||
set { StringCaseField.SetValue(Value, value); }
|
set { StringCaseField.SetValue(Value, value); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class GameWorld(object value) : Wrapper(value)
|
||||||
|
{
|
||||||
|
public static Type Type { get; private set; }
|
||||||
|
private static FieldInfo TraderControllerField;
|
||||||
|
|
||||||
|
public static void InitTypes()
|
||||||
|
{
|
||||||
|
Type = typeof(EFT.GameWorld);
|
||||||
|
TraderControllerField = AccessTools.Field(Type, "traderControllerClass");
|
||||||
|
}
|
||||||
|
|
||||||
|
public TraderControllerClass TraderController { get { return (TraderControllerClass)TraderControllerField.GetValue(Value); } }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MoveOperationResult(object value) : Wrapper(value)
|
||||||
|
{
|
||||||
|
public static Type Type { get; private set; }
|
||||||
|
private static FieldInfo AddOperationField;
|
||||||
|
|
||||||
|
public static void InitTypes()
|
||||||
|
{
|
||||||
|
Type = typeof(MoveOperation);
|
||||||
|
AddOperationField = AccessTools.Field(Type, "gclass2798_0");
|
||||||
|
}
|
||||||
|
|
||||||
|
public AddOperation AddOperation { get { return (AddOperation)AddOperationField.GetValue(Value); } }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AddOperationResult(object value) : Wrapper(value)
|
||||||
|
{
|
||||||
|
public static Type Type { get; private set; }
|
||||||
|
private static FieldInfo ResizeOperationField;
|
||||||
|
|
||||||
|
public static void InitTypes()
|
||||||
|
{
|
||||||
|
Type = typeof(AddOperation);
|
||||||
|
ResizeOperationField = AccessTools.Field(Type, "gclass2803_0");
|
||||||
|
}
|
||||||
|
|
||||||
|
public ResizeOperation ResizeOperation { get { return (ResizeOperation)ResizeOperationField.GetValue(Value); } }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class RExtentensions
|
public static class RExtentensions
|
||||||
@@ -898,4 +945,7 @@ public static class RExtentensions
|
|||||||
public static R.InventoryScreen R(this InventoryScreen value) => new(value);
|
public static R.InventoryScreen R(this InventoryScreen value) => new(value);
|
||||||
public static R.ScavengerInventoryScreen R(this ScavengerInventoryScreen value) => new(value);
|
public static R.ScavengerInventoryScreen R(this ScavengerInventoryScreen value) => new(value);
|
||||||
public static R.LocalizedText R(this LocalizedText value) => new(value);
|
public static R.LocalizedText R(this LocalizedText value) => new(value);
|
||||||
|
public static R.GameWorld R(this GameWorld value) => new(value);
|
||||||
|
public static R.MoveOperationResult R(this MoveOperation value) => new(value);
|
||||||
|
public static R.AddOperationResult R(this AddOperation value) => new(value);
|
||||||
}
|
}
|
20
src/SearchKeyListener.cs
Normal file
20
src/SearchKeyListener.cs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
using TMPro;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace UIFixes;
|
||||||
|
|
||||||
|
public class SearchKeyListener : MonoBehaviour
|
||||||
|
{
|
||||||
|
public void Update()
|
||||||
|
{
|
||||||
|
if (Settings.SearchKeyBind.Value.IsDown())
|
||||||
|
{
|
||||||
|
TMP_InputField searchField = GetComponent<TMP_InputField>();
|
||||||
|
if (searchField != null)
|
||||||
|
{
|
||||||
|
searchField.ActivateInputField();
|
||||||
|
searchField.Select();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,4 +1,5 @@
|
|||||||
using BepInEx.Configuration;
|
using BepInEx.Bootstrap;
|
||||||
|
using BepInEx.Configuration;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
@@ -37,6 +38,29 @@ internal enum SortingTableDisplay
|
|||||||
Both
|
Both
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal enum AutoFleaPrice
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
Minimum,
|
||||||
|
Average,
|
||||||
|
Maximum
|
||||||
|
}
|
||||||
|
|
||||||
|
internal enum TacticalBindModifier
|
||||||
|
{
|
||||||
|
Shift,
|
||||||
|
Control,
|
||||||
|
Alt
|
||||||
|
}
|
||||||
|
|
||||||
|
internal enum ModRaidWeapon
|
||||||
|
{
|
||||||
|
Never,
|
||||||
|
[Description("With Multitool")]
|
||||||
|
WithTool,
|
||||||
|
Always
|
||||||
|
}
|
||||||
|
|
||||||
internal class Settings
|
internal class Settings
|
||||||
{
|
{
|
||||||
// Categories
|
// Categories
|
||||||
@@ -56,25 +80,36 @@ internal class Settings
|
|||||||
public static ConfigEntry<bool> AutoSwitchTrading { get; set; }
|
public static ConfigEntry<bool> AutoSwitchTrading { get; set; }
|
||||||
public static ConfigEntry<bool> ClickOutOfDialogs { get; set; } // Advanced
|
public static ConfigEntry<bool> ClickOutOfDialogs { get; set; } // Advanced
|
||||||
public static ConfigEntry<bool> RestoreAsyncScrollPositions { get; set; } // Advanced
|
public static ConfigEntry<bool> RestoreAsyncScrollPositions { get; set; } // Advanced
|
||||||
|
public static ConfigEntry<int> OperationQueueTime { get; set; } // Advanced
|
||||||
|
|
||||||
// Input
|
// Input
|
||||||
public static ConfigEntry<bool> ToggleOrHoldAim { get; set; }
|
public static ConfigEntry<bool> ToggleOrHoldAim { get; set; }
|
||||||
|
public static ConfigEntry<bool> ToggleOrHoldSprint { get; set; }
|
||||||
|
public static ConfigEntry<bool> ToggleOrHoldTactical { get; set; }
|
||||||
|
public static ConfigEntry<bool> ToggleOrHoldHeadlight { get; set; }
|
||||||
|
public static ConfigEntry<bool> ToggleOrHoldGoggles { get; set; }
|
||||||
|
public static ConfigEntry<TacticalBindModifier> TacticalModeModifier { get; set; }
|
||||||
public static ConfigEntry<bool> UseHomeEnd { get; set; }
|
public static ConfigEntry<bool> UseHomeEnd { get; set; }
|
||||||
public static ConfigEntry<bool> RebindPageUpDown { get; set; }
|
public static ConfigEntry<bool> RebindPageUpDown { get; set; }
|
||||||
public static ConfigEntry<int> MouseScrollMulti { get; set; }
|
public static ConfigEntry<int> MouseScrollMulti { get; set; }
|
||||||
|
public static ConfigEntry<bool> UseRaidMouseScrollMulti { get; set; } // Advanced
|
||||||
|
public static ConfigEntry<int> MouseScrollMultiInRaid { get; set; } // Advanced
|
||||||
public static ConfigEntry<KeyboardShortcut> InspectKeyBind { get; set; }
|
public static ConfigEntry<KeyboardShortcut> InspectKeyBind { get; set; }
|
||||||
public static ConfigEntry<KeyboardShortcut> OpenKeyBind { get; set; }
|
public static ConfigEntry<KeyboardShortcut> OpenKeyBind { get; set; }
|
||||||
public static ConfigEntry<KeyboardShortcut> ExamineKeyBind { get; set; }
|
public static ConfigEntry<KeyboardShortcut> ExamineKeyBind { get; set; }
|
||||||
public static ConfigEntry<KeyboardShortcut> TopUpKeyBind { get; set; }
|
public static ConfigEntry<KeyboardShortcut> TopUpKeyBind { get; set; }
|
||||||
public static ConfigEntry<KeyboardShortcut> UseKeyBind { get; set; }
|
public static ConfigEntry<KeyboardShortcut> UseKeyBind { get; set; }
|
||||||
public static ConfigEntry<KeyboardShortcut> UseAllKeyBind { get; set; }
|
public static ConfigEntry<KeyboardShortcut> UseAllKeyBind { get; set; }
|
||||||
|
public static ConfigEntry<KeyboardShortcut> ReloadKeyBind { get; set; }
|
||||||
public static ConfigEntry<KeyboardShortcut> UnloadKeyBind { get; set; }
|
public static ConfigEntry<KeyboardShortcut> UnloadKeyBind { get; set; }
|
||||||
public static ConfigEntry<KeyboardShortcut> UnpackKeyBind { get; set; }
|
public static ConfigEntry<KeyboardShortcut> UnpackKeyBind { get; set; }
|
||||||
public static ConfigEntry<KeyboardShortcut> FilterByKeyBind { get; set; }
|
public static ConfigEntry<KeyboardShortcut> FilterByKeyBind { get; set; }
|
||||||
public static ConfigEntry<KeyboardShortcut> LinkedSearchKeyBind { get; set; }
|
public static ConfigEntry<KeyboardShortcut> LinkedSearchKeyBind { get; set; }
|
||||||
|
public static ConfigEntry<KeyboardShortcut> RequiredSearchKeyBind { get; set; }
|
||||||
|
public static ConfigEntry<KeyboardShortcut> AddOfferKeyBind { get; set; }
|
||||||
public static ConfigEntry<KeyboardShortcut> SortingTableKeyBind { get; set; }
|
public static ConfigEntry<KeyboardShortcut> SortingTableKeyBind { get; set; }
|
||||||
public static ConfigEntry<bool> UseRaidMouseScrollMulti { get; set; } // Advanced
|
public static ConfigEntry<KeyboardShortcut> SearchKeyBind { get; set; }
|
||||||
public static ConfigEntry<int> MouseScrollMultiInRaid { get; set; } // Advanced
|
public static ConfigEntry<bool> LimitNonstandardDrags { get; set; } // Advanced
|
||||||
public static ConfigEntry<bool> ItemContextBlocksTextInputs { get; set; } // Advanced
|
public static ConfigEntry<bool> ItemContextBlocksTextInputs { get; set; } // Advanced
|
||||||
|
|
||||||
// Inventory
|
// Inventory
|
||||||
@@ -87,8 +122,12 @@ internal class Settings
|
|||||||
public static ConfigEntry<bool> SwapItems { get; set; }
|
public static ConfigEntry<bool> SwapItems { get; set; }
|
||||||
public static ConfigEntry<bool> SwapMags { get; set; }
|
public static ConfigEntry<bool> SwapMags { get; set; }
|
||||||
public static ConfigEntry<bool> AlwaysSwapMags { get; set; }
|
public static ConfigEntry<bool> AlwaysSwapMags { get; set; }
|
||||||
|
public static ConfigEntry<bool> UnloadAmmoBoxInPlace { get; set; } // Advanced
|
||||||
public static ConfigEntry<bool> SwapImpossibleContainers { get; set; }
|
public static ConfigEntry<bool> SwapImpossibleContainers { get; set; }
|
||||||
|
public static ConfigEntry<bool> ModifyEquippedWeapons { get; set; }
|
||||||
|
public static ConfigEntry<ModRaidWeapon> ModifyRaidWeapons { get; set; }
|
||||||
public static ConfigEntry<bool> ReorderGrids { get; set; }
|
public static ConfigEntry<bool> ReorderGrids { get; set; }
|
||||||
|
public static ConfigEntry<bool> PrioritizeSmallerGrids { get; set; }
|
||||||
public static ConfigEntry<bool> SynchronizeStashScrolling { get; set; }
|
public static ConfigEntry<bool> SynchronizeStashScrolling { get; set; }
|
||||||
public static ConfigEntry<bool> GreedyStackMove { get; set; }
|
public static ConfigEntry<bool> GreedyStackMove { get; set; }
|
||||||
public static ConfigEntry<bool> StackBeforeSort { get; set; }
|
public static ConfigEntry<bool> StackBeforeSort { get; set; }
|
||||||
@@ -98,9 +137,13 @@ internal class Settings
|
|||||||
public static ConfigEntry<bool> AutoOpenSortingTable { get; set; }
|
public static ConfigEntry<bool> AutoOpenSortingTable { get; set; }
|
||||||
public static ConfigEntry<bool> DefaultSortingTableBind { get; set; } // Advanced
|
public static ConfigEntry<bool> DefaultSortingTableBind { get; set; } // Advanced
|
||||||
public static ConfigEntry<bool> ContextMenuOnRight { get; set; }
|
public static ConfigEntry<bool> ContextMenuOnRight { get; set; }
|
||||||
|
public static ConfigEntry<bool> AddOfferContextMenu { get; set; }
|
||||||
|
public static ConfigEntry<bool> WishlistContextEverywhere { get; set; }
|
||||||
|
public static ConfigEntry<bool> OpenAllContextMenu { get; set; }
|
||||||
public static ConfigEntry<bool> ShowGPCurrency { get; set; }
|
public static ConfigEntry<bool> ShowGPCurrency { get; set; }
|
||||||
public static ConfigEntry<bool> ShowOutOfStockCheckbox { get; set; }
|
public static ConfigEntry<bool> ShowOutOfStockCheckbox { get; set; }
|
||||||
public static ConfigEntry<SortingTableDisplay> SortingTableButton { get; set; }
|
public static ConfigEntry<SortingTableDisplay> SortingTableButton { get; set; }
|
||||||
|
public static ConfigEntry<bool> TagsOverCaptions { get; set; }
|
||||||
public static ConfigEntry<bool> LoadMagPresetOnBullets { get; set; } // Advanced
|
public static ConfigEntry<bool> LoadMagPresetOnBullets { get; set; } // Advanced
|
||||||
|
|
||||||
// Inspect Panels
|
// Inspect Panels
|
||||||
@@ -125,6 +168,8 @@ internal class Settings
|
|||||||
public static ConfigEntry<bool> ShowRequiredQuest { get; set; }
|
public static ConfigEntry<bool> ShowRequiredQuest { get; set; }
|
||||||
public static ConfigEntry<bool> AutoExpandCategories { get; set; }
|
public static ConfigEntry<bool> AutoExpandCategories { get; set; }
|
||||||
public static ConfigEntry<bool> ClearFiltersOnSearch { get; set; }
|
public static ConfigEntry<bool> ClearFiltersOnSearch { get; set; }
|
||||||
|
public static ConfigEntry<AutoFleaPrice> AutoOfferPrice { get; set; }
|
||||||
|
public static ConfigEntry<bool> UpdatePriceOnBulk { get; set; }
|
||||||
public static ConfigEntry<bool> KeepAddOfferOpen { get; set; }
|
public static ConfigEntry<bool> KeepAddOfferOpen { get; set; }
|
||||||
public static ConfigEntry<KeyboardShortcut> PurchaseAllKeybind { get; set; }
|
public static ConfigEntry<KeyboardShortcut> PurchaseAllKeybind { get; set; }
|
||||||
public static ConfigEntry<bool> KeepAddOfferOpenIgnoreMaxOffers { get; set; } // Advanced
|
public static ConfigEntry<bool> KeepAddOfferOpenIgnoreMaxOffers { get; set; } // Advanced
|
||||||
@@ -207,13 +252,67 @@ internal class Settings
|
|||||||
null,
|
null,
|
||||||
new ConfigurationManagerAttributes { IsAdvanced = true })));
|
new ConfigurationManagerAttributes { IsAdvanced = true })));
|
||||||
|
|
||||||
|
configEntries.Add(OperationQueueTime = config.Bind(
|
||||||
|
GeneralSection,
|
||||||
|
"Server Operation Queue Time",
|
||||||
|
15,
|
||||||
|
new ConfigDescription(
|
||||||
|
"The client waits this long to batch inventory operations before sending them to the server. Vanilla Tarkov is 60 (!)",
|
||||||
|
new AcceptableValueRange<int>(0, 60),
|
||||||
|
new ConfigurationManagerAttributes { IsAdvanced = true })));
|
||||||
|
|
||||||
// Input
|
// Input
|
||||||
configEntries.Add(ToggleOrHoldAim = config.Bind(
|
configEntries.Add(ToggleOrHoldAim = config.Bind(
|
||||||
InputSection,
|
InputSection,
|
||||||
"Use Toggle/Hold Aiming",
|
"Use Toggle/Hold Aiming",
|
||||||
false,
|
false,
|
||||||
new ConfigDescription(
|
new ConfigDescription(
|
||||||
"Tap the aim key to toggle aiming, or hold the aim key for continuous aiming",
|
"Tap the aim key to toggle aiming, or hold the key for continuous aiming",
|
||||||
|
null,
|
||||||
|
new ConfigurationManagerAttributes { })));
|
||||||
|
|
||||||
|
configEntries.Add(ToggleOrHoldSprint = config.Bind(
|
||||||
|
InputSection,
|
||||||
|
"Use Toggle/Hold Sprint",
|
||||||
|
false,
|
||||||
|
new ConfigDescription(
|
||||||
|
"Tap the sprint key to toggle sprinting, or hold the key for continuous sprinting",
|
||||||
|
null,
|
||||||
|
new ConfigurationManagerAttributes { })));
|
||||||
|
|
||||||
|
configEntries.Add(ToggleOrHoldTactical = config.Bind(
|
||||||
|
InputSection,
|
||||||
|
"Use Toggle/Hold Tactical Device",
|
||||||
|
false,
|
||||||
|
new ConfigDescription(
|
||||||
|
"Tap the tactical device key to toggle your tactical device, or hold the key for continuous",
|
||||||
|
null,
|
||||||
|
new ConfigurationManagerAttributes { })));
|
||||||
|
|
||||||
|
configEntries.Add(ToggleOrHoldHeadlight = config.Bind(
|
||||||
|
InputSection,
|
||||||
|
"Use Toggle/Hold Headlight",
|
||||||
|
false,
|
||||||
|
new ConfigDescription(
|
||||||
|
"Tap the headlight key to toggle your headlight, or hold the key for continuous",
|
||||||
|
null,
|
||||||
|
new ConfigurationManagerAttributes { })));
|
||||||
|
|
||||||
|
configEntries.Add(ToggleOrHoldGoggles = config.Bind(
|
||||||
|
InputSection,
|
||||||
|
"Use Toggle/Hold Goggles",
|
||||||
|
false,
|
||||||
|
new ConfigDescription(
|
||||||
|
"Tap the goggles key to toggle night vision/goggles/faceshield, or hold the key for continuous",
|
||||||
|
null,
|
||||||
|
new ConfigurationManagerAttributes { })));
|
||||||
|
|
||||||
|
configEntries.Add(TacticalModeModifier = config.Bind(
|
||||||
|
InputSection,
|
||||||
|
"Change Quickbound Tactical Mode",
|
||||||
|
TacticalBindModifier.Shift,
|
||||||
|
new ConfigDescription(
|
||||||
|
"Holding this modifer when activating a quickbound tactical device will switch its active mode",
|
||||||
null,
|
null,
|
||||||
new ConfigurationManagerAttributes { })));
|
new ConfigurationManagerAttributes { })));
|
||||||
|
|
||||||
@@ -316,6 +415,15 @@ internal class Settings
|
|||||||
null,
|
null,
|
||||||
new ConfigurationManagerAttributes { })));
|
new ConfigurationManagerAttributes { })));
|
||||||
|
|
||||||
|
configEntries.Add(ReloadKeyBind = config.Bind(
|
||||||
|
InputSection,
|
||||||
|
"Reload Weapon Shortcut",
|
||||||
|
new KeyboardShortcut(KeyCode.R),
|
||||||
|
new ConfigDescription(
|
||||||
|
"Keybind to reload a weapon. Note that this is solely in the menus, and doesn't affect the normal reload key.",
|
||||||
|
null,
|
||||||
|
new ConfigurationManagerAttributes { })));
|
||||||
|
|
||||||
configEntries.Add(UnloadKeyBind = config.Bind(
|
configEntries.Add(UnloadKeyBind = config.Bind(
|
||||||
InputSection,
|
InputSection,
|
||||||
"Unload Mag/Ammo Shortcut",
|
"Unload Mag/Ammo Shortcut",
|
||||||
@@ -352,6 +460,24 @@ internal class Settings
|
|||||||
null,
|
null,
|
||||||
new ConfigurationManagerAttributes { })));
|
new ConfigurationManagerAttributes { })));
|
||||||
|
|
||||||
|
configEntries.Add(RequiredSearchKeyBind = config.Bind(
|
||||||
|
InputSection,
|
||||||
|
"Required Search Shortcut",
|
||||||
|
new KeyboardShortcut(KeyCode.None),
|
||||||
|
new ConfigDescription(
|
||||||
|
"Keybind to search flea market for items to barter for this item",
|
||||||
|
null,
|
||||||
|
new ConfigurationManagerAttributes { })));
|
||||||
|
|
||||||
|
configEntries.Add(AddOfferKeyBind = config.Bind(
|
||||||
|
InputSection,
|
||||||
|
"Add Offer Shortcut",
|
||||||
|
new KeyboardShortcut(KeyCode.None),
|
||||||
|
new ConfigDescription(
|
||||||
|
"Keybind to list item on the flea market",
|
||||||
|
null,
|
||||||
|
new ConfigurationManagerAttributes { })));
|
||||||
|
|
||||||
configEntries.Add(SortingTableKeyBind = config.Bind(
|
configEntries.Add(SortingTableKeyBind = config.Bind(
|
||||||
InputSection,
|
InputSection,
|
||||||
"Transfer to/from Sorting Table",
|
"Transfer to/from Sorting Table",
|
||||||
@@ -361,6 +487,24 @@ internal class Settings
|
|||||||
null,
|
null,
|
||||||
new ConfigurationManagerAttributes { })));
|
new ConfigurationManagerAttributes { })));
|
||||||
|
|
||||||
|
configEntries.Add(SearchKeyBind = config.Bind(
|
||||||
|
InputSection,
|
||||||
|
"Highlight Search Box",
|
||||||
|
new KeyboardShortcut(KeyCode.F, KeyCode.LeftControl),
|
||||||
|
new ConfigDescription(
|
||||||
|
"Keybind to highlight the search box in hideout crafting, handbook, and flea market",
|
||||||
|
null,
|
||||||
|
new ConfigurationManagerAttributes { })));
|
||||||
|
|
||||||
|
configEntries.Add(LimitNonstandardDrags = config.Bind(
|
||||||
|
InputSection,
|
||||||
|
"Limit Nonstandard Drags",
|
||||||
|
true,
|
||||||
|
new ConfigDescription(
|
||||||
|
"Constrain dragging to the left mouse, when shift is not down",
|
||||||
|
null,
|
||||||
|
new ConfigurationManagerAttributes { IsAdvanced = true })));
|
||||||
|
|
||||||
configEntries.Add(ItemContextBlocksTextInputs = config.Bind(
|
configEntries.Add(ItemContextBlocksTextInputs = config.Bind(
|
||||||
InputSection,
|
InputSection,
|
||||||
"Block Text Inputs on Item Mouseover",
|
"Block Text Inputs on Item Mouseover",
|
||||||
@@ -452,6 +596,15 @@ internal class Settings
|
|||||||
null,
|
null,
|
||||||
new ConfigurationManagerAttributes { })));
|
new ConfigurationManagerAttributes { })));
|
||||||
|
|
||||||
|
configEntries.Add(UnloadAmmoBoxInPlace = config.Bind(
|
||||||
|
InventorySection,
|
||||||
|
"Unload Ammo Boxes In-Place",
|
||||||
|
!Chainloader.PluginInfos.ContainsKey("com.fika.core"), // default false if fika present, has issues with ground loot
|
||||||
|
new ConfigDescription(
|
||||||
|
"Whether to unload ammo boxes in-place, otherwise there needs to be free space somewhere",
|
||||||
|
null,
|
||||||
|
new ConfigurationManagerAttributes { IsAdvanced = true })));
|
||||||
|
|
||||||
configEntries.Add(SwapImpossibleContainers = config.Bind(
|
configEntries.Add(SwapImpossibleContainers = config.Bind(
|
||||||
InventorySection,
|
InventorySection,
|
||||||
"Swap with Incompatible Containers",
|
"Swap with Incompatible Containers",
|
||||||
@@ -461,6 +614,24 @@ internal class Settings
|
|||||||
null,
|
null,
|
||||||
new ConfigurationManagerAttributes { })));
|
new ConfigurationManagerAttributes { })));
|
||||||
|
|
||||||
|
configEntries.Add(ModifyEquippedWeapons = config.Bind(
|
||||||
|
InventorySection,
|
||||||
|
"Modify Equipped Weapons",
|
||||||
|
true,
|
||||||
|
new ConfigDescription(
|
||||||
|
"Enable the modification of equipped weapons, including vital parts, out of raid",
|
||||||
|
null,
|
||||||
|
new ConfigurationManagerAttributes { })));
|
||||||
|
|
||||||
|
configEntries.Add(ModifyRaidWeapons = config.Bind(
|
||||||
|
InventorySection,
|
||||||
|
"Modify Weapons In Raid",
|
||||||
|
ModRaidWeapon.Never,
|
||||||
|
new ConfigDescription(
|
||||||
|
"When to enable the modification of vital parts of unequipped weapons, in raid",
|
||||||
|
null,
|
||||||
|
new ConfigurationManagerAttributes { })));
|
||||||
|
|
||||||
configEntries.Add(ReorderGrids = config.Bind(
|
configEntries.Add(ReorderGrids = config.Bind(
|
||||||
InventorySection,
|
InventorySection,
|
||||||
"Standardize Grid Order",
|
"Standardize Grid Order",
|
||||||
@@ -470,6 +641,15 @@ internal class Settings
|
|||||||
null,
|
null,
|
||||||
new ConfigurationManagerAttributes { })));
|
new ConfigurationManagerAttributes { })));
|
||||||
|
|
||||||
|
configEntries.Add(PrioritizeSmallerGrids = config.Bind(
|
||||||
|
InventorySection,
|
||||||
|
"Prioritize Smaller Slots (requires restart)",
|
||||||
|
false,
|
||||||
|
new ConfigDescription(
|
||||||
|
"When adding items to containers with multiple slots, place the item in the smallest slot that can hold it, rather than just the first empty space. Requires Standardize Grid Order.",
|
||||||
|
null,
|
||||||
|
new ConfigurationManagerAttributes { })));
|
||||||
|
|
||||||
configEntries.Add(SynchronizeStashScrolling = config.Bind(
|
configEntries.Add(SynchronizeStashScrolling = config.Bind(
|
||||||
InventorySection,
|
InventorySection,
|
||||||
"Synchronize Stash Scroll Position",
|
"Synchronize Stash Scroll Position",
|
||||||
@@ -551,6 +731,33 @@ internal class Settings
|
|||||||
null,
|
null,
|
||||||
new ConfigurationManagerAttributes { })));
|
new ConfigurationManagerAttributes { })));
|
||||||
|
|
||||||
|
configEntries.Add(AddOfferContextMenu = config.Bind(
|
||||||
|
InventorySection,
|
||||||
|
"Add Offer Context Menu",
|
||||||
|
true,
|
||||||
|
new ConfigDescription(
|
||||||
|
"Add a context menu to list the item on the flea market",
|
||||||
|
null,
|
||||||
|
new ConfigurationManagerAttributes { })));
|
||||||
|
|
||||||
|
configEntries.Add(WishlistContextEverywhere = config.Bind(
|
||||||
|
InventorySection,
|
||||||
|
"Wishlist Context Menu Everywhere",
|
||||||
|
true,
|
||||||
|
new ConfigDescription(
|
||||||
|
"Add/Remove to wishlist available in the context menu on all screens",
|
||||||
|
null,
|
||||||
|
new ConfigurationManagerAttributes { })));
|
||||||
|
|
||||||
|
configEntries.Add(OpenAllContextMenu = config.Bind(
|
||||||
|
InventorySection,
|
||||||
|
"Open All Context Flyout",
|
||||||
|
true,
|
||||||
|
new ConfigDescription(
|
||||||
|
"Add a flyout to the Open context menu to recursively open a stack of containers",
|
||||||
|
null,
|
||||||
|
new ConfigurationManagerAttributes { })));
|
||||||
|
|
||||||
configEntries.Add(ShowGPCurrency = config.Bind(
|
configEntries.Add(ShowGPCurrency = config.Bind(
|
||||||
InventorySection,
|
InventorySection,
|
||||||
"Show GP Coins in Currency",
|
"Show GP Coins in Currency",
|
||||||
@@ -578,6 +785,15 @@ internal class Settings
|
|||||||
null,
|
null,
|
||||||
new ConfigurationManagerAttributes { })));
|
new ConfigurationManagerAttributes { })));
|
||||||
|
|
||||||
|
configEntries.Add(TagsOverCaptions = config.Bind(
|
||||||
|
InventorySection,
|
||||||
|
"Prioritize Tags Over Names",
|
||||||
|
true,
|
||||||
|
new ConfigDescription(
|
||||||
|
"When there isn't enough space to show both the tag and the name of an item, show the tag",
|
||||||
|
null,
|
||||||
|
new ConfigurationManagerAttributes { })));
|
||||||
|
|
||||||
configEntries.Add(LoadMagPresetOnBullets = config.Bind(
|
configEntries.Add(LoadMagPresetOnBullets = config.Bind(
|
||||||
InventorySection,
|
InventorySection,
|
||||||
"Mag Presets Context Menu on Bullets",
|
"Mag Presets Context Menu on Bullets",
|
||||||
@@ -734,6 +950,24 @@ internal class Settings
|
|||||||
null,
|
null,
|
||||||
new ConfigurationManagerAttributes { })));
|
new ConfigurationManagerAttributes { })));
|
||||||
|
|
||||||
|
configEntries.Add(AutoOfferPrice = config.Bind(
|
||||||
|
FleaMarketSection,
|
||||||
|
"Autopopulate Offer Price",
|
||||||
|
AutoFleaPrice.None,
|
||||||
|
new ConfigDescription(
|
||||||
|
"Autopopulte new offers with min/avg/max market price, or leave blank",
|
||||||
|
null,
|
||||||
|
new ConfigurationManagerAttributes { })));
|
||||||
|
|
||||||
|
configEntries.Add(UpdatePriceOnBulk = config.Bind(
|
||||||
|
FleaMarketSection,
|
||||||
|
"Update Offer Price on Bulk",
|
||||||
|
true,
|
||||||
|
new ConfigDescription(
|
||||||
|
"Automatically multiply or divide the price when you check/uncheck bulk, or or when you change the number of selected items while bulk is checked.",
|
||||||
|
null,
|
||||||
|
new ConfigurationManagerAttributes { })));
|
||||||
|
|
||||||
configEntries.Add(ShowRequiredQuest = config.Bind(
|
configEntries.Add(ShowRequiredQuest = config.Bind(
|
||||||
FleaMarketSection,
|
FleaMarketSection,
|
||||||
"Show Required Quest for Locked Offers",
|
"Show Required Quest for Locked Offers",
|
||||||
@@ -781,12 +1015,13 @@ internal class Settings
|
|||||||
|
|
||||||
RecalcOrder(configEntries);
|
RecalcOrder(configEntries);
|
||||||
|
|
||||||
|
|
||||||
MakeDependent(EnableMultiSelect, EnableMultiSelectInRaid);
|
MakeDependent(EnableMultiSelect, EnableMultiSelectInRaid);
|
||||||
MakeDependent(EnableMultiSelect, ShowMultiSelectDebug, false);
|
MakeDependent(EnableMultiSelect, ShowMultiSelectDebug, false);
|
||||||
MakeDependent(EnableMultiSelect, EnableMultiClick);
|
MakeDependent(EnableMultiSelect, EnableMultiClick);
|
||||||
|
|
||||||
MakeExclusive(EnableMultiClick, AutoOpenSortingTable, false);
|
MakeExclusive(EnableMultiClick, AutoOpenSortingTable, false);
|
||||||
|
|
||||||
|
MakeDependent(ReorderGrids, PrioritizeSmallerGrids, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void RecalcOrder(List<ConfigEntryBase> configEntries)
|
private static void RecalcOrder(List<ConfigEntryBase> configEntries)
|
Reference in New Issue
Block a user