Merge branch 'main' into CykaFix

This commit is contained in:
PhatPhuckDave
2024-07-23 10:02:16 +02:00
27 changed files with 1224 additions and 196 deletions

View File

@@ -1,6 +1,7 @@
using EFT.InventoryLogic;
using EFT.UI.DragAndDrop;
using System.Runtime.CompilerServices;
using UnityEngine;
namespace UIFixes;
@@ -10,7 +11,7 @@ public static class ExtraItemProperties
private class Properties
{
public bool Reordered;
public bool Reordered = false;
}
public static bool GetReordered(this Item item) => properties.GetOrCreateValue(item).Reordered;
@@ -23,7 +24,7 @@ public static class ExtraTemplatedGridsViewProperties
private class Properties
{
public bool Reordered;
public bool Reordered = false;
}
public static bool GetReordered(this TemplatedGridsView gridsView) => properties.GetOrCreateValue(gridsView).Reordered;
@@ -42,3 +43,47 @@ public static class ExtraTradingGridProperties
public static bool GetShowOutOfStock(this TradingGridView gridView) => properties.GetOrCreateValue(gridView).ShowOutOfStock;
public static void SetShowOutOfStock(this TradingGridView gridView, bool value) => properties.GetOrCreateValue(gridView).ShowOutOfStock = value;
}
public static class ExtraRagfairOfferItemViewProperties
{
private static readonly ConditionalWeakTable<RagfairOfferItemView, Properties> properties = new();
private class Properties
{
public Vector2? SizeOverride = null;
public bool ShowCaption = false;
public string Inscription = null;
public string Count = null;
public string Tooltip = null;
}
public static Vector2? GetSizeOverride(this RagfairOfferItemView itemView) => properties.GetOrCreateValue(itemView).SizeOverride;
public static void SetSizeOverride(this RagfairOfferItemView itemView, Vector2 value) => properties.GetOrCreateValue(itemView).SizeOverride = value;
public static bool GetShowCaption(this RagfairOfferItemView itemView) => properties.GetOrCreateValue(itemView).ShowCaption;
public static void SetShowCaption(this RagfairOfferItemView itemView, bool value) => properties.GetOrCreateValue(itemView).ShowCaption = value;
public static string GetInscription(this RagfairOfferItemView itemView) => properties.GetOrCreateValue(itemView).Inscription;
public static void SetInscription(this RagfairOfferItemView itemView, string value) => properties.GetOrCreateValue(itemView).Inscription = value;
public static string GetCount(this RagfairOfferItemView itemView) => properties.GetOrCreateValue(itemView).Count;
public static void SetCount(this RagfairOfferItemView itemView, string value) => properties.GetOrCreateValue(itemView).Count = value;
public static string GetTooltip(this RagfairOfferItemView itemView) => properties.GetOrCreateValue(itemView).Tooltip;
public static void SetTooltip(this RagfairOfferItemView itemView, string value) => properties.GetOrCreateValue(itemView).Tooltip = value;
}
public static class ExtraItemViewStatsProperties
{
private static readonly ConditionalWeakTable<ItemViewStats, Properties> properties = new();
private class Properties
{
public bool HideMods = false;
}
public static bool GetHideMods(this ItemViewStats itemViewStats) => properties.GetOrCreateValue(itemViewStats).HideMods;
public static void SetHideMods(this ItemViewStats itemViewStats, bool value) => properties.GetOrCreateValue(itemViewStats).HideMods = value;
}

View File

@@ -18,6 +18,7 @@ global using DiscardResult = GClass2799;
global using ItemSorter = GClass2772;
global using ItemWithLocation = GClass2521;
global using SearchableGrid = GClass2516;
global using CursorManager = GClass3034;
// State machine states
global using FirearmReadyState = EFT.Player.FirearmController.GClass1619;

View File

@@ -1,4 +1,5 @@
using Comfort.Common;
using BepInEx.Configuration;
using Comfort.Common;
using EFT.UI;
using EFT.UI.DragAndDrop;
using System;
@@ -24,6 +25,8 @@ public class DrawMultiSelect : MonoBehaviour
private bool drawing;
private bool secondary;
private static Vector2 Deadzone = new(5f, 5f);
public void Start()
{
selectTexture = new Texture2D(1, 1);
@@ -56,37 +59,34 @@ public class DrawMultiSelect : MonoBehaviour
return;
}
// checking ItemUiContext is a quick and easy way to know the mouse is over an item
if (Input.GetKeyDown(Settings.SelectionBoxKey.Value.MainKey) && ItemUiContext.Instance.R().ItemContext == null)
if (Settings.SelectionBoxKey.Value.IsDownIgnoreOthers())
{
PointerEventData eventData = new(EventSystem.current)
{
position = Input.mousePosition
};
bool shiftDown = Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift);
List<RaycastResult> results = [];
localRaycaster.Raycast(eventData, results);
preloaderRaycaster.Raycast(eventData, results);
foreach (GameObject gameObject in results.Select(r => r.gameObject))
{
var draggables = gameObject.GetComponents<MonoBehaviour>()
.Where(c => c is IDragHandler || c is IBeginDragHandler || c is TextMeshProUGUI) // tmp_inputfield is draggable, but textmesh isn't so explicitly include
.Where(c => c is not ScrollRectNoDrag) // this disables scrolling, it doesn't add it
.Where(c => c.name != "Inner"); // there's a random DragTrigger sitting in ItemInfoWindows
var clickables = gameObject.GetComponents<MonoBehaviour>()
.Where(c => c is IPointerClickHandler || c is IPointerDownHandler || c is IPointerUpHandler);
if (draggables.Any() || clickables.Any())
// Only need to check we aren't over draggables/clickables if the multiselect key is left mouse
if (Settings.SelectionBoxKey.Value.MainKey == KeyCode.Mouse0 && !shiftDown && !MouseIsOverClickable())
{
return;
}
}
selectOrigin = Input.mousePosition;
drawing = true;
secondary = Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift);
secondary = shiftDown;
if (!secondary)
{
MultiSelect.Clear();
}
}
if (drawing && !Settings.SelectionBoxKey.Value.IsPressedIgnoreOthers())
{
drawing = false;
if (secondary)
{
MultiSelect.CombineSecondary();
secondary = false;
}
}
if (drawing)
@@ -94,6 +94,10 @@ public class DrawMultiSelect : MonoBehaviour
selectEnd = Input.mousePosition;
Rect selectRect = new(selectOrigin, selectEnd - selectOrigin);
if (Mathf.Abs(selectRect.size.x) < Deadzone.x && Mathf.Abs(selectRect.size.y) < Deadzone.y)
{
return;
}
// If not secondary, then we can kick out any non-rendered items, plus they won't be covered by the foreach below
if (!secondary)
@@ -136,16 +140,6 @@ public class DrawMultiSelect : MonoBehaviour
MultiSelect.Deselect(gridItemView, secondary);
}
}
if (drawing && !Input.GetKey(Settings.SelectionBoxKey.Value.MainKey))
{
drawing = false;
if (secondary)
{
MultiSelect.CombineSecondary();
secondary = false;
}
}
}
public void OnGUI()
@@ -171,6 +165,42 @@ public class DrawMultiSelect : MonoBehaviour
}
}
private bool MouseIsOverClickable()
{
// checking ItemUiContext is a quick and easy way to know the mouse is over an item
if (ItemUiContext.Instance.R().ItemContext != null)
{
return false;
}
PointerEventData eventData = new(EventSystem.current)
{
position = Input.mousePosition
};
List<RaycastResult> results = [];
localRaycaster.Raycast(eventData, results);
preloaderRaycaster.Raycast(eventData, results);
foreach (GameObject gameObject in results.Select(r => r.gameObject))
{
var draggables = gameObject.GetComponents<MonoBehaviour>()
.Where(c => c is IDragHandler || c is IBeginDragHandler || c is TextMeshProUGUI) // tmp_inputfield is draggable, but textmesh isn't so explicitly include
.Where(c => c is not ScrollRectNoDrag) // this disables scrolling, it doesn't add it
.Where(c => c.name != "Inner"); // there's a random DragTrigger sitting in ItemInfoWindows
var clickables = gameObject.GetComponents<MonoBehaviour>()
.Where(c => c is IPointerClickHandler || c is IPointerDownHandler || c is IPointerUpHandler);
if (draggables.Any() || clickables.Any())
{
return false;
}
}
return true;
}
private bool IsOnTop(Rect itemRect, Transform itemTransform, GraphicRaycaster raycaster)
{
// Otherwise, ensure it's not overlapped by window UI

View File

@@ -125,6 +125,8 @@ public class MultiSelect
public static void OnKillItemView(GridItemView itemView)
{
CombineSecondary();
MultiSelectItemContext itemContext = SelectedItems.FirstOrDefault(x => x.Value == itemView).Key;
if (itemContext != null)
{
@@ -140,6 +142,8 @@ public class MultiSelect
return;
}
CombineSecondary();
MultiSelectItemContext itemContext = SelectedItems.FirstOrDefault(x => x.Key.Item == itemView.Item).Key;
if (itemContext != null)
{
@@ -158,6 +162,8 @@ public class MultiSelect
return;
}
CombineSecondary();
MultiSelectItemContext oldItemContext = SelectedItems.FirstOrDefault(x => x.Key.Item == eventArgs.Item).Key;
if (oldItemContext != null)
{
@@ -221,7 +227,7 @@ public class MultiSelect
public static bool Active
{
get { return SelectedItems.Count > 0; }
get { return SelectedItems.Count > 0 || SecondaryItems.Count > 0; }
}
// Sort the items to prioritize the items that share a grid with the dragged item, prepend the dragContext as the first one

View File

@@ -69,6 +69,6 @@ public class MultiSelectDebug : MonoBehaviour
LocationInGrid location = address is GridItemAddress gridAddress ? gridAddress.LocationInGrid : null;
string locationString = location != null ? $"({location.x}, {location.y})" : "(slot)";
return $"x{itemContext.Item.StackObjectsCount} {address.Container.ID} {locationString} {itemContext.Item.Name.Localized()}";
return $"x{itemContext.Item.StackObjectsCount} {(address != null ? address.Container.ID : "")} {locationString} {itemContext.Item.Name.Localized()}";
}
}

View File

@@ -0,0 +1,334 @@
using EFT;
using EFT.InventoryLogic;
using EFT.UI;
using EFT.UI.DragAndDrop;
using EFT.UI.Ragfair;
using HarmonyLib;
using SPT.Reflection.Patching;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
namespace UIFixes;
public static class BarterOfferPatches
{
public static void Enable()
{
new IconsPatch().Enable();
new ItemViewScalePatch().Enable();
new ItemUpdateInfoPatch().Enable();
new HideItemViewStatsPatch().Enable();
new OverrideGridItemViewTooltipPatch().Enable();
new NoPointerEnterPatch().Enable();
new NoPointerExitPatch().Enable();
new NoPointerClickPatch().Enable();
}
public class IconsPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.DeclaredMethod(typeof(OfferItemPriceBarter), nameof(OfferItemPriceBarter.Show));
}
[PatchPostfix]
public static void Postfix(
OfferItemPriceBarter __instance,
IExchangeRequirement requirement,
ItemTooltip tooltip,
Offer offer,
InventoryControllerClass inventoryController,
ItemUiContext itemUiContext,
InsuranceCompanyClass insuranceCompany,
int index,
bool expanded,
GameObject ____barterIcon,
TextMeshProUGUI ____requirementName,
GameObject ____separator)
{
if (!Settings.ShowBarterIcons.Value)
{
return;
}
if (requirement is not HandoverRequirement handoverRequirement)
{
return;
}
bool isDogtag = requirement.Item.GetItemComponent<DogtagComponent>() != null;
HorizontalOrVerticalLayoutGroup layoutGroup = __instance.transform.parent.GetComponent<HorizontalOrVerticalLayoutGroup>();
if (layoutGroup != null)
{
layoutGroup.spacing = 1f;
}
Vector2 smallSizeDelta = ____barterIcon.RectTransform().sizeDelta;
RagfairOfferItemView itemView = ItemViewFactory.CreateFromPool<RagfairOfferItemView>("ragfair_offer_layout");
itemView.transform.SetParent(__instance.transform, false);
if (!expanded)
{
itemView.SetSizeOverride(smallSizeDelta);
ItemViewStats itemViewStats = itemView.GetComponent<ItemViewStats>();
itemViewStats.SetHideMods(true);
}
else
{
if (isDogtag)
{
if (handoverRequirement.Side != EDogtagExchangeSide.Any)
{
itemView.SetShowCaption(true);
}
itemView.SetInscription("LVLKILLLIST".Localized() + " " + handoverRequirement.Level);
}
int ownedCount = GetOwnedCount(requirement, inventoryController);
itemView.SetCount(string.Format("<color=#{2}><b>{0}</b></color>/{1}", ownedCount.FormatSeparate(" "), requirement.IntCount.FormatSeparate(" "), "C5C3B2"));
}
if (isDogtag)
{
itemView.SetTooltip(string.Concat(
[
"Dogtag".Localized(),
" ≥ ",
handoverRequirement.Level,
" ",
"LVLKILLLIST".Localized(),
(handoverRequirement.Side != EDogtagExchangeSide.Any ? ", " + handoverRequirement.Side : "").ToUpper()
]));
}
Vector2 sizeDelta = expanded ? new Vector2(64f, 64f) : smallSizeDelta;
LayoutElement layoutElement = itemView.GetComponent<LayoutElement>();
layoutElement.preferredWidth = layoutElement.minWidth = sizeDelta.x;
layoutElement.preferredHeight = layoutElement.minHeight = sizeDelta.y;
itemView.Show(null, requirement.Item, ItemRotation.Horizontal, false, inventoryController, requirement.Item.Owner, itemUiContext, null);
ItemViewManager itemViewManager = __instance.GetOrAddComponent<ItemViewManager>();
itemViewManager.Init(itemView);
____barterIcon.SetActive(false);
____separator?.SetActive(false);
if (expanded)
{
____requirementName.transform.parent.gameObject.SetActive(false); // The name and the ? icon
}
else
{
____requirementName.gameObject.SetActive(false);
}
}
private static int GetOwnedCount(IExchangeRequirement requirement, InventoryControllerClass inventoryController)
{
List<Item> allItems = [];
inventoryController.Inventory.Stash.GetAllAssembledItemsNonAlloc(allItems);
inventoryController.Inventory.QuestStashItems.GetAllAssembledItemsNonAlloc(allItems);
inventoryController.Inventory.QuestRaidItems.GetAllAssembledItemsNonAlloc(allItems);
if (requirement is not HandoverRequirement handoverRequirement)
{
return 0;
}
if (requirement.Item.GetItemComponent<DogtagComponent>() != null)
{
return allItems.Where(item => RagFairClass.CanUseForBarterExchange(item, out string error))
.Select(item => item.GetItemComponent<DogtagComponent>())
.Where(dogtag => dogtag != null)
.Where(dogtag => dogtag.Level >= handoverRequirement.Level)
.Where(dogtag => handoverRequirement.Side == EDogtagExchangeSide.Any || dogtag.Side.ToString() == handoverRequirement.Side.ToString())
.Count();
}
return allItems.Where(item => RagFairClass.CanUseForBarterExchange(item, out string error))
.Where(item => item.TemplateId == requirement.Item.TemplateId)
.Where(item => !requirement.OnlyFunctional || item is not LootItemClass lootItem || !lootItem.MissingVitalParts.Any())
.Where(item => item is not GInterface325 encodable || requirement.Item is not GInterface325 || encodable.IsEncoded() == requirement.IsEncoded)
.Sum(item => item.StackObjectsCount);
}
}
public class ItemViewScalePatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.DeclaredMethod(typeof(RagfairOfferItemView), nameof(RagfairOfferItemView.UpdateScale));
}
[PatchPostfix]
public static void Postfix(RagfairOfferItemView __instance, Image ___MainImage)
{
Vector2? sizeOverride = __instance.GetSizeOverride();
if (sizeOverride.HasValue)
{
Vector2 sizeDelta = ___MainImage.rectTransform.sizeDelta;
float x = sizeDelta.x;
float y = sizeDelta.y;
// Calculate scale and multiply to preserve aspect ratio
float scale = Mathf.Min((float)sizeOverride.Value.x / x, (float)sizeOverride.Value.y / y);
___MainImage.rectTransform.sizeDelta = new Vector2(x * scale, y * scale);
}
}
}
public class ItemUpdateInfoPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.DeclaredMethod(typeof(RagfairOfferItemView), nameof(RagfairOfferItemView.UpdateInfo));
}
[PatchPostfix]
public static void Postfix(RagfairOfferItemView __instance, TextMeshProUGUI ___Caption, TextMeshProUGUI ___ItemInscription, TextMeshProUGUI ___ItemValue)
{
if (__instance.GetShowCaption())
{
___Caption.gameObject.SetActive(true);
}
string inscription = __instance.GetInscription();
if (!string.IsNullOrEmpty(inscription))
{
___ItemInscription.text = inscription;
___ItemInscription.gameObject.SetActive(true);
}
string value = __instance.GetCount();
if (!string.IsNullOrEmpty(value))
{
___ItemValue.text = value;
___ItemValue.fontSize = 16f;
___ItemValue.alignment = TextAlignmentOptions.Left;
RectTransform rectTransform = ___ItemValue.RectTransform();
rectTransform.pivot = new Vector2(0f, 0.5f);
rectTransform.anchorMin = rectTransform.anchorMax = new Vector2(1f, 0.5f);
rectTransform.anchoredPosition = new Vector2(5f, 0f);
___ItemValue.gameObject.SetActive(true);
}
}
}
public class OverrideGridItemViewTooltipPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.DeclaredMethod(typeof(GridItemView), nameof(GridItemView.ShowTooltip));
}
[PatchPrefix]
public static bool Prefix(GridItemView __instance, ItemUiContext ___ItemUiContext)
{
if (__instance is not RagfairOfferItemView ragfairOfferItemView)
{
return true;
}
string tooltip = ragfairOfferItemView.GetTooltip();
if (!string.IsNullOrEmpty(tooltip))
{
___ItemUiContext.Tooltip.Show(tooltip, null, 0.5f);
return false;
}
return true;
}
}
public class HideItemViewStatsPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(ItemViewStats), nameof(ItemViewStats.SetStaticInfo));
}
[PatchPrefix]
public static bool Prefix(ItemViewStats __instance, Image ____modIcon, Image ____modTypeIcon, Image ____specialIcon, Image ____armorClassIcon)
{
if (!__instance.GetHideMods())
{
return true;
}
____modIcon.gameObject.SetActive(false);
____modTypeIcon.gameObject.SetActive(false);
____specialIcon.gameObject.SetActive(false);
____armorClassIcon?.gameObject.SetActive(false);
return false;
}
}
public class NoPointerEnterPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(OfferItemPriceBarter), nameof(OfferItemPriceBarter.OnPointerEnter));
}
[PatchPrefix]
public static bool Prefix()
{
return !Settings.ShowBarterIcons.Value;
}
}
public class NoPointerExitPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(OfferItemPriceBarter), nameof(OfferItemPriceBarter.OnPointerExit));
}
[PatchPrefix]
public static bool Prefix()
{
return !Settings.ShowBarterIcons.Value;
}
}
public class NoPointerClickPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(OfferItemPriceBarter), nameof(OfferItemPriceBarter.OnPointerClick));
}
[PatchPrefix]
public static bool Prefix()
{
return !Settings.ShowBarterIcons.Value;
}
}
public class ItemViewManager : MonoBehaviour
{
RagfairOfferItemView itemView;
public void Init(RagfairOfferItemView itemView)
{
this.itemView = itemView;
}
public void OnDestroy()
{
itemView.IsStub = true;
itemView.Kill();
}
}
}

View File

@@ -192,15 +192,22 @@ public static class ContextMenuPatches
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(R.TradingInteractions.Type, "get_SubInteractions");
return AccessTools.PropertyGetter(
typeof(ItemInfoInteractionsAbstractClass<EItemInfoButton>),
nameof(ItemInfoInteractionsAbstractClass<EItemInfoButton>.SubInteractions));
}
[PatchPostfix]
public static void Postfix(ref IEnumerable<EItemInfoButton> __result)
public static void Postfix(
ItemInfoInteractionsAbstractClass<EItemInfoButton> __instance,
ref IEnumerable<EItemInfoButton> __result)
{
if (R.TradingInteractions.Type.IsInstanceOfType(__instance))
{
__result = __result.Append(EItemInfoButton.Repair).Append(EItemInfoButton.Insure);
}
}
}
public class CreateSubInteractionsTradingPatch : ModulePatch
{
@@ -208,12 +215,23 @@ public static class ContextMenuPatches
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(R.TradingInteractions.Type, "CreateSubInteractions");
return AccessTools.Method(
typeof(ItemInfoInteractionsAbstractClass<EItemInfoButton>),
nameof(ItemInfoInteractionsAbstractClass<EItemInfoButton>.CreateSubInteractions));
}
[PatchPrefix]
public static bool Prefix(object __instance, EItemInfoButton parentInteraction, ISubInteractions subInteractionsWrapper, ItemUiContext ___itemUiContext_0)
public static bool Prefix(
ItemInfoInteractionsAbstractClass<EItemInfoButton> __instance,
EItemInfoButton parentInteraction,
ISubInteractions subInteractionsWrapper,
ItemUiContext ___itemUiContext_0)
{
if (!R.TradingInteractions.Type.IsInstanceOfType(__instance))
{
return true;
}
// Clear this, since something else should be active (even a different mouseover of the insurance button)
LoadingInsuranceActions = false;

View File

@@ -1,11 +1,11 @@
using EFT.InventoryLogic;
using Comfort.Common;
using EFT.InventoryLogic;
using EFT.UI;
using EFT.UI.DragAndDrop;
using HarmonyLib;
using SPT.Reflection.Patching;
using System.Reflection;
using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;
namespace UIFixes;
@@ -19,7 +19,6 @@ public static class ContextMenuShortcutPatches
new ItemUiContextPatch().Enable();
new HideoutItemViewRegisterContextPatch().Enable();
new HideoutItemViewUnegisterContextPatch().Enable();
new TradingPanelRegisterContextPatch().Enable();
new TradingPanelUnregisterContextPatch().Enable();
@@ -76,12 +75,12 @@ public static class ContextMenuShortcutPatches
if (Settings.UseAllKeyBind.Value.IsDown())
{
TryInteraction(__instance, itemContext, EItemInfoButton.UseAll, EItemInfoButton.Use);
TryInteraction(__instance, itemContext, EItemInfoButton.UseAll, [EItemInfoButton.Use]);
}
if (Settings.UnloadKeyBind.Value.IsDown())
{
TryInteraction(__instance, itemContext, EItemInfoButton.Unload, EItemInfoButton.UnloadAmmo);
TryInteraction(__instance, itemContext, EItemInfoButton.Unload, [EItemInfoButton.UnloadAmmo]);
}
if (Settings.UnpackKeyBind.Value.IsDown())
@@ -99,15 +98,62 @@ public static class ContextMenuShortcutPatches
TryInteraction(__instance, itemContext, EItemInfoButton.LinkedSearch);
}
if (Settings.SortingTableKeyBind.Value.IsDown())
{
MoveToFromSortingTable(itemContext, __instance);
}
if (Settings.ExamineKeyBind.Value.IsDown())
{
TryInteraction(__instance, itemContext, EItemInfoButton.Examine,
[EItemInfoButton.Fold, EItemInfoButton.Unfold, EItemInfoButton.TurnOn, EItemInfoButton.TurnOff, EItemInfoButton.CheckMagazine]);
}
Interactions = null;
}
private static void TryInteraction(ItemUiContext itemUiContext, ItemContextAbstractClass itemContext, EItemInfoButton interaction, EItemInfoButton? fallbackInteraction = null)
private static void TryInteraction(ItemUiContext itemUiContext, ItemContextAbstractClass itemContext, EItemInfoButton interaction, EItemInfoButton[] fallbackInteractions = null)
{
Interactions ??= itemUiContext.GetItemContextInteractions(itemContext, null);
if (!Interactions.ExecuteInteraction(interaction) && fallbackInteraction.HasValue)
if (!Interactions.ExecuteInteraction(interaction) && fallbackInteractions != null)
{
Interactions.ExecuteInteraction(fallbackInteraction.Value);
foreach (var fallbackInteraction in fallbackInteractions)
{
if (Interactions.ExecuteInteraction(fallbackInteraction))
{
return;
}
}
}
}
private static void MoveToFromSortingTable(ItemContextAbstractClass itemContext, ItemUiContext itemUiContext)
{
Item item = itemContext.Item;
if (item.Owner is not InventoryControllerClass controller)
{
return;
}
SortingTableClass sortingTable = controller.Inventory.SortingTable;
bool isInSortingTable = sortingTable != null && item.Parent.Container.ParentItem == sortingTable;
var operation = isInSortingTable ? itemUiContext.QuickFindAppropriatePlace(itemContext, controller, false, true, true) : itemUiContext.QuickMoveToSortingTable(item, true);
if (operation.Succeeded && controller.CanExecute(operation.Value))
{
if (operation.Value is IDestroyResult destroyResult && destroyResult.ItemsDestroyRequired)
{
NotificationManagerClass.DisplayWarningNotification(new DestroyError(item, destroyResult.ItemsToDestroy).GetLocalizedDescription());
return;
}
controller.RunNetworkTransaction(operation.Value, null);
if (itemUiContext.Tooltip != null)
{
itemUiContext.Tooltip.Close();
}
Singleton<GUISounds>.Instance.PlayItemSound(item.ItemSound, EInventorySoundType.pickup, false);
}
}
}
@@ -127,20 +173,6 @@ public static class ContextMenuShortcutPatches
}
}
public class HideoutItemViewUnegisterContextPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(HideoutItemView), nameof(HideoutItemView.OnPointerExit));
}
[PatchPostfix]
public static void Postfix(HideoutItemView __instance, ItemUiContext ___ItemUiContext)
{
___ItemUiContext.UnregisterCurrentItemContext(__instance.ItemContext);
}
}
public class TradingPanelRegisterContextPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()

View File

@@ -1,6 +1,8 @@
using EFT.UI.Ragfair;
using EFT.UI;
using EFT.UI.Ragfair;
using HarmonyLib;
using SPT.Reflection.Patching;
using System.Linq;
using System.Reflection;
using TMPro;
using UnityEngine;
@@ -12,12 +14,15 @@ public static class FixFleaPatches
{
public static void Enable()
{
// These two are anal AF
// These are anal AF
new DoNotToggleOnMouseOverPatch().Enable();
new ToggleOnOpenPatch().Enable();
new DropdownHeightPatch().Enable();
new OfferItemFixMaskPatch().Enable();
new OfferViewTweaksPatch().Enable();
new SearchPatch().Enable();
}
public class DoNotToggleOnMouseOverPatch : ModulePatch
@@ -95,4 +100,57 @@ public static class FixFleaPatches
timeLeft.childControlWidth = false;
}
}
public class SearchPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(RagfairCategoriesPanel), nameof(RagfairCategoriesPanel.method_9));
}
[PatchPrefix]
public static bool Prefix(RagfairCategoriesPanel __instance, string arg)
{
if (!Settings.ClearFiltersOnSearch.Value)
{
return true;
}
if (arg.StartsWith("#") || __instance.Ragfair == null || __instance.EViewListType_0 != EViewListType.AllOffers)
{
return true;
}
if (__instance.FilteredNodes.Values.Sum(node => node.Count) > 0)
{
return true;
}
__instance.Ragfair.CancellableFilters.Clear();
FilterRule filterRule = __instance.Ragfair.method_3(EViewListType.AllOffers);
filterRule.HandbookId = string.Empty;
__instance.Ragfair.AddSearchesInRule(filterRule, true);
return false;
}
}
public class DropdownHeightPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.DeclaredMethod(typeof(DropDownBox), nameof(DropDownBox.Init));
}
[PatchPostfix]
public static void Postfix(ref float ____maxVisibleHeight)
{
if (____maxVisibleHeight == 120f)
{
____maxVisibleHeight = 240f;
}
}
}
}

View File

@@ -26,7 +26,7 @@ public static class MultiSelectPatches
// Used to prevent infinite recursion of CanAccept/AcceptItem
private static bool InPatch = false;
// If the CanAccept method should render highlights
// Keep track of preview images when dragging
private static readonly List<Image> Previews = [];
// Point that various QuickFindPlace overrides should start at
@@ -150,7 +150,7 @@ public static class MultiSelectPatches
}
[PatchPostfix]
public static void Postfix(ItemView __instance, PointerEventData eventData)
public static void Postfix(ItemView __instance, PointerEventData eventData, TraderControllerClass ___ItemController)
{
if (!MultiSelect.Enabled || __instance is RagfairNewOfferItemView || __instance is InsuranceItemView)
{
@@ -161,12 +161,36 @@ public static class MultiSelectPatches
bool shiftDown = Input.GetKey(KeyCode.LeftShift) && !Input.GetKey(KeyCode.RightShift);
bool altDown = Input.GetKey(KeyCode.LeftAlt) && !Input.GetKey(KeyCode.RightAlt);
if (Settings.EnableMultiClick.Value && __instance is GridItemView gridItemView && eventData.button == PointerEventData.InputButton.Left && shiftDown && !ctrlDown && !altDown)
// If sorting table is open and default shift-click behavior is enabled, don't multiselect
bool couldBeSortingTableMove = false;
if (Settings.DefaultSortingTableBind.Value &&
shiftDown &&
eventData.button == PointerEventData.InputButton.Left &&
___ItemController is InventoryControllerClass inventoryController)
{
SortingTableClass sortingTable = inventoryController.Inventory.SortingTable;
if (sortingTable != null && sortingTable.IsVisible)
{
couldBeSortingTableMove = true;
}
}
if (Settings.EnableMultiClick.Value &&
!couldBeSortingTableMove &&
__instance is GridItemView gridItemView &&
eventData.button == PointerEventData.InputButton.Left &&
shiftDown && !ctrlDown && !altDown)
{
MultiSelect.Toggle(gridItemView);
return;
}
// Mainly this tests for when selection box is rebound to another mouse button, to enable secondary selection
if (!couldBeSortingTableMove && shiftDown && Settings.SelectionBoxKey.Value.IsDownIgnoreOthers())
{
return;
}
if (__instance is not GridItemView gridItemView2 || !MultiSelect.IsSelected(gridItemView2))
{
MultiSelect.Clear();
@@ -211,9 +235,14 @@ public static class MultiSelectPatches
return false;
}
if (shiftDown)
if (shiftDown && !ctrlDown && !altDown)
{
// Nothing to do, mousedown handled it.
if (Settings.DefaultSortingTableBind.Value)
{
QuickMove(__instance, ___ItemUiContext, ___ItemController, true);
return false;
}
return true;
}
@@ -222,15 +251,17 @@ public static class MultiSelectPatches
return true;
}
private static void QuickMove(GridItemView gridItemView, ItemUiContext itemUiContext, TraderControllerClass itemController)
private static void QuickMove(GridItemView gridItemView, ItemUiContext itemUiContext, TraderControllerClass itemController, bool moveToSortingTable = false)
{
bool succeeded = true;
DisableMerge = true;
IgnoreItemParent = true;
Stack<ItemOperation> operations = new();
foreach (DragItemContext selectedItemContext in MultiSelect.SortedItemContexts())
foreach (var selectedItemContext in MultiSelect.SortedItemContexts())
{
ItemOperation operation = itemUiContext.QuickFindAppropriatePlace(selectedItemContext, itemController, false /*forceStash*/, false /*showWarnings*/, false /*simulate*/);
ItemOperation operation = moveToSortingTable ?
itemUiContext.QuickMoveToSortingTable(selectedItemContext.Item, false /*simulate*/) :
itemUiContext.QuickFindAppropriatePlace(selectedItemContext, itemController, false /*forceStash*/, false /*showWarnings*/, false /*simulate*/);
if (operation.Succeeded && itemController.CanExecute(operation.Value))
{
operations.Push(operation);
@@ -434,6 +465,13 @@ public static class MultiSelectPatches
return AccessTools.Method(typeof(ItemView), nameof(ItemView.OnBeginDrag));
}
[PatchPrefix]
public static bool Prefix()
{
// Disable drag if shift is down
return !Input.GetKey(KeyCode.LeftShift) && !Input.GetKey(KeyCode.RightShift);
}
[PatchPostfix]
public static void Postfix(ItemView __instance)
{

View File

@@ -1,59 +0,0 @@
using Comfort.Common;
using EFT.UI;
using HarmonyLib;
using SPT.Reflection.Patching;
using System.Linq;
using System.Reflection;
using UnityEngine;
namespace UIFixes;
public class OpenSortingTablePatch : ModulePatch
{
private static readonly EItemUiContextType[] AllowedScreens = [EItemUiContextType.InventoryScreen, EItemUiContextType.ScavengerInventoryScreen];
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(ItemUiContext), nameof(ItemUiContext.QuickMoveToSortingTable));
}
[PatchPrefix]
public static bool Prefix(ItemUiContext __instance, ref ItemOperation __result)
{
// BSG checks visibility, not in-raid. There's a bug where somehow that visibility can be true in raid
if (Plugin.InRaid())
{
__result = new GClass3370("SortingTable/VisibilityError");
return false;
}
if (!Settings.AutoOpenSortingTable.Value || !AllowedScreens.Contains(__instance.ContextType))
{
return true;
}
// Temporary work-around for LootValue bug - bail out if the ALT key is down
if (Input.GetKey(KeyCode.LeftAlt))
{
return true;
}
SortingTableClass sortingTable = __instance.R().InventoryController.Inventory.SortingTable;
if (sortingTable != null && !sortingTable.IsVisible)
{
if (__instance.ContextType == EItemUiContextType.InventoryScreen)
{
Singleton<CommonUI>.Instance.InventoryScreen.method_6();
Singleton<CommonUI>.Instance.InventoryScreen.R().SimpleStashPanel.ChangeSortingTableTabState(true);
}
else if (__instance.ContextType == EItemUiContextType.ScavengerInventoryScreen)
{
Singleton<CommonUI>.Instance.ScavengerInventoryScreen.method_7();
Singleton<CommonUI>.Instance.ScavengerInventoryScreen.R().SimpleStashPanel.ChangeSortingTableTabState(true);
}
}
return true;
}
}

View File

@@ -0,0 +1,98 @@
using Comfort.Common;
using EFT.UI;
using EFT.UI.DragAndDrop;
using HarmonyLib;
using SPT.Reflection.Patching;
using System.Linq;
using System.Reflection;
using UnityEngine;
using UnityEngine.EventSystems;
namespace UIFixes;
public static class OpenSortingTablePatches
{
public static void Enable()
{
new AutoOpenPatch().Enable();
new DefaultBindPatch().Enable();
}
public class AutoOpenPatch : ModulePatch
{
private static readonly EItemUiContextType[] AllowedScreens = [EItemUiContextType.InventoryScreen, EItemUiContextType.ScavengerInventoryScreen];
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(ItemUiContext), nameof(ItemUiContext.QuickMoveToSortingTable));
}
[PatchPrefix]
public static bool Prefix(ItemUiContext __instance, ref ItemOperation __result)
{
// BSG checks visibility, not in-raid. There's a bug where somehow that visibility can be true in raid
if (Plugin.InRaid())
{
__result = new GClass3370("SortingTable/VisibilityError");
return false;
}
// Allowed screens only, and auto-open is enabled or the custom bind is active
if (!AllowedScreens.Contains(__instance.ContextType) || (!Settings.AutoOpenSortingTable.Value && !Settings.SortingTableKeyBind.Value.IsDown()))
{
return true;
}
// Temporary work-around for LootValue bug - bail out if the ALT key is down
if (Input.GetKey(KeyCode.LeftAlt))
{
return true;
}
SortingTableClass sortingTable = __instance.R().InventoryController.Inventory.SortingTable;
if (sortingTable != null && !sortingTable.IsVisible)
{
if (__instance.ContextType == EItemUiContextType.InventoryScreen)
{
Singleton<CommonUI>.Instance.InventoryScreen.method_6();
Singleton<CommonUI>.Instance.InventoryScreen.R().SimpleStashPanel.ChangeSortingTableTabState(true);
}
else if (__instance.ContextType == EItemUiContextType.ScavengerInventoryScreen)
{
Singleton<CommonUI>.Instance.ScavengerInventoryScreen.method_7();
Singleton<CommonUI>.Instance.ScavengerInventoryScreen.R().SimpleStashPanel.ChangeSortingTableTabState(true);
}
}
return true;
}
}
public class DefaultBindPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(ItemView), nameof(ItemView.OnClick));
}
[PatchPrefix]
public static bool Prefix(PointerEventData.InputButton button, bool doubleClick)
{
if (Settings.DefaultSortingTableBind.Value)
{
return true;
}
bool ctrlDown = Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl);
bool altDown = Input.GetKey(KeyCode.LeftAlt) || Input.GetKey(KeyCode.RightAlt);
bool shiftDown = Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift);
if (button == PointerEventData.InputButton.Left && !doubleClick && !ctrlDown && !altDown && shiftDown)
{
return false;
}
return true;
}
}
}

View File

@@ -0,0 +1,156 @@
using EFT;
using EFT.UI;
using HarmonyLib;
using SPT.Reflection.Patching;
using System;
using System.Linq;
using System.Reflection;
namespace UIFixes;
public static class ReloadInPlacePatches
{
private static bool IsReloading = false;
private static MagazineClass FoundMagazine = null;
public static void Enable()
{
// These patch ItemUiContext.ReloadWeapon, which is called from the context menu Reload
new ReloadInPlacePatch().Enable();
new ReloadInPlaceFindMagPatch().Enable();
new ReloadInPlaceFindSpotPatch().Enable();
// This patches the firearmsController code when you hit R in raid with an external magazine class
new SwapIfNoSpacePatch().Enable();
}
public class ReloadInPlacePatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(ItemUiContext), nameof(ItemUiContext.ReloadWeapon));
}
[PatchPrefix]
public static void Prefix()
{
IsReloading = Settings.SwapMags.Value;
}
[PatchPostfix]
public static void Postfix()
{
IsReloading = false;
FoundMagazine = null;
}
}
public class ReloadInPlaceFindMagPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(ItemUiContext), nameof(ItemUiContext.method_5));
}
[PatchPostfix]
public static void Postfix(MagazineClass __result)
{
if (IsReloading)
{
FoundMagazine = __result;
}
}
}
public class ReloadInPlaceFindSpotPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
Type type = typeof(ItemUiContext).GetNestedTypes().Single(t => t.GetField("currentMagazine") != null);
return AccessTools.Method(type, "method_0");
}
[PatchPrefix]
public static void Prefix(StashGridClass grid, ref GStruct414<GClass2801> __state)
{
if (!Settings.SwapMags.Value)
{
return;
}
if (grid.Contains(FoundMagazine))
{
__state = InteractionsHandlerClass.Remove(FoundMagazine, grid.ParentItem.Owner as TraderControllerClass, false, false);
}
}
[PatchPostfix]
public static void Postfix(GStruct414<GClass2801> __state)
{
if (!Settings.SwapMags.Value || __state.Value == null)
{
return;
}
if (__state.Succeeded)
{
__state.Value.RollBack();
}
}
}
public class SwapIfNoSpacePatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(Player.FirearmController), nameof(Player.FirearmController.ReloadMag));
}
// By default this method will do a series of removes and adds, but not swap, to reload
// This tied to a different animation state machine sequence than Swap(), and is faster than Swap.
// So only use Swap if *needed*, otherwise its penalizing all reload speeds
[PatchPrefix]
public static bool Prefix(Player.FirearmController __instance, MagazineClass magazine, ItemAddressClass gridItemAddress)
{
// If gridItemAddress isn't null, it already found a place for the current mag, so let it run (unless always swap is enabled)
if (!Settings.SwapMags.Value || (gridItemAddress != null && !Settings.AlwaysSwapMags.Value))
{
return true;
}
// Weapon doesn't currently have a magazine, let the default run (will load one)
MagazineClass currentMagazine = __instance.Weapon.GetCurrentMagazine();
if (currentMagazine == null)
{
return true;
}
InventoryControllerClass controller = __instance.Weapon.Owner as InventoryControllerClass;
// 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);
if (operation.Failed)
{
return true;
}
gridItemAddress = controller.Inventory.Equipment.GetPrioritizedGridsForUnloadedObject(false)
.Select(grid => grid.FindLocationForItem(currentMagazine))
.Where(address => address != null)
.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
// Put the magazine back
operation.Value.RollBack();
if (gridItemAddress == null)
{
// Didn't work, nowhere to put magazine. Let it run (will drop mag on ground)
return true;
}
controller.TryRunNetworkTransaction(InteractionsHandlerClass.Swap(currentMagazine, gridItemAddress, magazine, new GClass2783(__instance.Weapon.GetMagazineSlot()), controller, true), null);
return false;
}
}
}

View File

@@ -23,7 +23,10 @@ public static class ScrollPatches
new EnchanceTraderStashScrollingPatch().Enable();
new EnhanceFleaScrollingPatch().Enable();
new EnhanceMailScrollingPatch().Enable();
new MouseScrollingSpeedPatch().Enable();
new LightScrollerSpeedPatch().Enable();
new EnhanceHideoutScrollingPatch().Enable();
new EnhanceTaskListScrollingPatch().Enable();
new OpenLastTaskPatch().Enable();
@@ -305,6 +308,21 @@ public static class ScrollPatches
}
}
public class LightScrollerSpeedPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(LightScroller), nameof(LightScroller.method_1));
}
[PatchPrefix]
public static void Prefix(ref float deltaPixels)
{
int multi = Settings.UseRaidMouseScrollMulti.Value && Plugin.InRaid() ? Settings.MouseScrollMultiInRaid.Value : Settings.MouseScrollMulti.Value;
deltaPixels *= multi;
}
}
public class EnhanceTaskListScrollingPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()

View File

@@ -119,7 +119,6 @@ public static class SortPatches
{
Error error = null;
var mergeableItems = lootItem.Grids.SelectMany(g => g.Items)
.OfType<Stackable>()
.Where(i => i.StackObjectsCount < i.StackMaxSize)
.ToArray();
@@ -131,7 +130,7 @@ public static class SortPatches
continue;
}
if (InteractionsHandlerClass.smethod_0(lootItem.Grids, item, out Stackable targetItem, 1))
if (Sorter.FindStackForMerge(lootItem.Grids, item, out Item targetItem, 1))
{
var operation = InteractionsHandlerClass.TransferOrMerge(item, targetItem, inventoryController, true);
if (operation.Succeeded)

View File

@@ -27,44 +27,10 @@ public static class StackFirItemsPatches
return method;
}
// Reimplementing this entire method to ignore SpawnedInSession for certain types
[PatchPrefix]
public static bool Prefix(IEnumerable<EFT.InventoryLogic.IContainer> containersToPut, Item itemToMerge, ref object mergeableItem, int overrideCount, ref bool __result)
public static bool Prefix(IEnumerable<EFT.InventoryLogic.IContainer> containersToPut, Item itemToMerge, ref Item mergeableItem, int overrideCount, ref bool __result)
{
if (!MergeableItemType.IsInstanceOfType(itemToMerge))
{
mergeableItem = null;
__result = false;
}
if (overrideCount <= 0)
{
overrideCount = itemToMerge.StackObjectsCount;
}
bool ignoreSpawnedInSession;
if (itemToMerge.Template is MoneyClass)
{
ignoreSpawnedInSession = Settings.MergeFIRMoney.Value;
}
else if (itemToMerge.Template is AmmoTemplate)
{
ignoreSpawnedInSession = Settings.MergeFIRAmmo.Value;
}
else
{
ignoreSpawnedInSession = Settings.MergeFIROther.Value;
}
mergeableItem = containersToPut.SelectMany(x => x.Items)
.Where(MergeableItemType.IsInstanceOfType)
.Where(x => x != itemToMerge)
.Where(x => x.TemplateId == itemToMerge.TemplateId)
.Where(x => ignoreSpawnedInSession || x.SpawnedInSession == itemToMerge.SpawnedInSession)
.Where(x => x.StackObjectsCount < x.StackMaxSize)
.FirstOrDefault(x => overrideCount <= x.StackMaxSize - x.StackObjectsCount);
__result = mergeableItem != null;
__result = Sorter.FindStackForMerge(containersToPut, itemToMerge, out mergeableItem, overrideCount);
return false;
}
}
@@ -79,19 +45,12 @@ public static class StackFirItemsPatches
[PatchPrefix]
public static bool Prefix(Item __instance, Item other, ref bool __result)
{
bool ignoreSpawnedInSession;
if (__instance.Template is MoneyClass)
bool ignoreSpawnedInSession = __instance.Template switch
{
ignoreSpawnedInSession = Settings.MergeFIRMoney.Value;
}
else if (__instance.Template is AmmoTemplate)
{
ignoreSpawnedInSession = Settings.MergeFIRAmmo.Value;
}
else
{
ignoreSpawnedInSession = Settings.MergeFIROther.Value;
}
MoneyClass _ => Settings.MergeFIRMoney.Value,
AmmoTemplate _ => Settings.MergeFIRMoney.Value,
_ => Settings.MergeFIROther.Value,
};
__result = __instance.TemplateId == other.TemplateId && __instance.Id != other.Id && (ignoreSpawnedInSession || __instance.SpawnedInSession == other.SpawnedInSession);
return false;

View File

@@ -3,6 +3,7 @@ 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;
@@ -13,12 +14,17 @@ 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
@@ -98,4 +104,97 @@ public static class UnloadAmmoPatches
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);
}
}
}

View File

@@ -0,0 +1,33 @@
using HarmonyLib;
using SPT.Reflection.Patching;
using System;
using System.Linq;
using System.Reflection;
using UnityEngine;
namespace UIFixes;
public class UnlockCursorPatch : ModulePatch
{
private static readonly FullScreenMode[] WindowedModes = [FullScreenMode.Windowed, FullScreenMode.MaximizedWindow, FullScreenMode.FullScreenWindow];
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(CursorManager), nameof(CursorManager.SetCursorLockMode));
}
[PatchPrefix]
public static bool Prefix(bool cursorVisible, FullScreenMode fullscreenMode, Action ___action_0)
{
Cursor.lockState = cursorVisible ?
Settings.UnlockCursor.Value && WindowedModes.Contains(fullscreenMode) ? CursorLockMode.None : CursorLockMode.Confined :
CursorLockMode.Locked;
if (___action_0 != null)
{
___action_0();
}
return false;
}
}

View File

@@ -47,7 +47,7 @@ public class Plugin : BaseUnityPlugin
new LoadMagPresetsPatch().Enable();
KeepWindowsOnScreenPatches.Enable();
ContextMenuShortcutPatches.Enable();
new OpenSortingTablePatch().Enable();
OpenSortingTablePatches.Enable();
LoadAmmoInRaidPatches.Enable();
MultiSelectPatches.Enable();
new FixUnloadLastBulletPatch().Enable();
@@ -65,6 +65,9 @@ public class Plugin : BaseUnityPlugin
MoveSortingTablePatches.Enable();
FilterOutOfStockPatches.Enable();
SortPatches.Enable();
ReloadInPlacePatches.Enable();
BarterOfferPatches.Enable();
new UnlockCursorPatch().Enable();
}
public static bool InRaid()

View File

@@ -28,12 +28,16 @@ Existing SPT features made better
- Rebind Home/End, PageUp/PageDown to work like you would expect
- Customizable mouse scrolling speed
- Moving stacks into containers always moves entire stack
- ✨ Items made stackable by other mods follow normal stacking behavior
- Allow found in raid money and ammo automatically stack with non-found-in-raid items
- Synchronize stash scroll position everywhere your stash is visible
- Insure and repair items directly from the context menu
- Load ammo via context menu _in raid_
- Load ammo preset will pull ammo from inventory, not just stash
- ✨ Multi-grid vest and backpack grids reordered to be left to right, top to bottom.
- ✨ Sorting will stack and combine stacks of items
- ✨ Shift-clicking sort will only sort loose items, leaving containers in place
#### Inspect windows
@@ -57,6 +61,8 @@ Existing SPT features made better
- Option to keep the Add Offer window open after placing your offer
- Set prices in the Add Offer window by clicking the min/avg/max market prices (multiplies for bulk orders)
- Autoselect Similar checkbox is remembered across sessions and application restarts
- ✨ Replace barter offers icons with actual item images, plus owned/required counts on expansion
- ✨ Clears filters for you when you type in search bar and there's no match
#### Weapon modding/presets
@@ -70,6 +76,7 @@ Existing SPT features made better
#### In raid
- ✨ Reloading will swap magazines in-place, instead of dropping them on the ground when there's no room.
- ✨ Grenade quickbinds will transfer to the next grenade of the same type after throwing.
- ✨ Option to change the behavior of the grenade key from selecting a random grenade to a deterministic one

View File

@@ -2,6 +2,7 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using UnityEngine;
namespace UIFixes;
@@ -47,6 +48,7 @@ internal class Settings
private const string FleaMarketSection = "6. Flea Market";
// General
public static ConfigEntry<bool> UnlockCursor { get; set; }
public static ConfigEntry<WeaponPresetConfirmationOption> ShowPresetConfirmations { get; set; }
public static ConfigEntry<TransferConfirmationOption> ShowTransferConfirmations { get; set; }
public static ConfigEntry<bool> KeepMessagesOpen { get; set; }
@@ -62,6 +64,7 @@ internal class Settings
public static ConfigEntry<int> MouseScrollMulti { get; set; }
public static ConfigEntry<KeyboardShortcut> InspectKeyBind { get; set; }
public static ConfigEntry<KeyboardShortcut> OpenKeyBind { get; set; }
public static ConfigEntry<KeyboardShortcut> ExamineKeyBind { get; set; }
public static ConfigEntry<KeyboardShortcut> TopUpKeyBind { get; set; }
public static ConfigEntry<KeyboardShortcut> UseKeyBind { get; set; }
public static ConfigEntry<KeyboardShortcut> UseAllKeyBind { get; set; }
@@ -69,6 +72,7 @@ internal class Settings
public static ConfigEntry<KeyboardShortcut> UnpackKeyBind { get; set; }
public static ConfigEntry<KeyboardShortcut> FilterByKeyBind { get; set; }
public static ConfigEntry<KeyboardShortcut> LinkedSearchKeyBind { get; set; }
public static ConfigEntry<KeyboardShortcut> SortingTableKeyBind { get; set; }
public static ConfigEntry<bool> UseRaidMouseScrollMulti { get; set; } // Advanced
public static ConfigEntry<int> MouseScrollMultiInRaid { get; set; } // Advanced
public static ConfigEntry<bool> ItemContextBlocksTextInputs { get; set; } // Advanced
@@ -81,6 +85,8 @@ internal class Settings
public static ConfigEntry<MultiSelectStrategy> MultiSelectStrat { get; set; }
public static ConfigEntry<bool> ShowMultiSelectDebug { get; set; } // Advanced
public static ConfigEntry<bool> SwapItems { get; set; }
public static ConfigEntry<bool> SwapMags { get; set; }
public static ConfigEntry<bool> AlwaysSwapMags { get; set; }
public static ConfigEntry<bool> SwapImpossibleContainers { get; set; }
public static ConfigEntry<bool> ReorderGrids { get; set; }
public static ConfigEntry<bool> SynchronizeStashScrolling { get; set; }
@@ -90,6 +96,7 @@ internal class Settings
public static ConfigEntry<bool> MergeFIRAmmo { get; set; }
public static ConfigEntry<bool> MergeFIROther { get; set; }
public static ConfigEntry<bool> AutoOpenSortingTable { get; set; }
public static ConfigEntry<bool> DefaultSortingTableBind { get; set; } // Advanced
public static ConfigEntry<bool> ContextMenuOnRight { get; set; }
public static ConfigEntry<bool> ShowGPCurrency { get; set; }
public static ConfigEntry<bool> ShowOutOfStockCheckbox { get; set; }
@@ -113,9 +120,11 @@ internal class Settings
// Flea Market
public static ConfigEntry<bool> EnableFleaHistory { get; set; }
public static ConfigEntry<bool> ShowBarterIcons { get; set; }
public static ConfigEntry<bool> EnableSlotSearch { get; set; }
public static ConfigEntry<bool> ShowRequiredQuest { get; set; }
public static ConfigEntry<bool> AutoExpandCategories { get; set; }
public static ConfigEntry<bool> ClearFiltersOnSearch { get; set; }
public static ConfigEntry<bool> KeepAddOfferOpen { get; set; }
public static ConfigEntry<KeyboardShortcut> PurchaseAllKeybind { get; set; }
public static ConfigEntry<bool> KeepAddOfferOpenIgnoreMaxOffers { get; set; } // Advanced
@@ -126,6 +135,15 @@ internal class Settings
var configEntries = new List<ConfigEntryBase>();
// General
configEntries.Add(UnlockCursor = config.Bind(
GeneralSection,
"Unlock Cursor",
true,
new ConfigDescription(
"Unlock cursor in Windowed, Maximized Windowed, and FullScreen Windowed modes. Note that you must alt-tab out of the game and back in for this to take affect.",
null,
new ConfigurationManagerAttributes { })));
configEntries.Add(ShowPresetConfirmations = config.Bind(
GeneralSection,
"Show Weapon Preset Confirmation Dialog",
@@ -262,6 +280,15 @@ internal class Settings
null,
new ConfigurationManagerAttributes { })));
configEntries.Add(ExamineKeyBind = config.Bind(
InputSection,
"Examine/Interact Shortcut",
new KeyboardShortcut(KeyCode.None),
new ConfigDescription(
"Keybind to examine an item, fold it, unfold it, turn it on, turn it off, or check a magazine",
null,
new ConfigurationManagerAttributes { })));
configEntries.Add(TopUpKeyBind = config.Bind(
InputSection,
"Top Up Ammo Shortcut",
@@ -325,6 +352,15 @@ internal class Settings
null,
new ConfigurationManagerAttributes { })));
configEntries.Add(SortingTableKeyBind = config.Bind(
InputSection,
"Transfer to/from Sorting Table",
new KeyboardShortcut(KeyCode.None),
new ConfigDescription(
"Keybind to transfer items to and from the sorting table. Will auto-open sorting table if necessary.",
null,
new ConfigurationManagerAttributes { })));
configEntries.Add(ItemContextBlocksTextInputs = config.Bind(
InputSection,
"Block Text Inputs on Item Mouseover",
@@ -398,6 +434,24 @@ internal class Settings
null,
new ConfigurationManagerAttributes { })));
configEntries.Add(SwapMags = config.Bind(
InventorySection,
"Reload Magazines In-Place",
true,
new ConfigDescription(
"When reloading a weapon with a magazine, swap locations with the new magazine if necessary (and possible)",
null,
new ConfigurationManagerAttributes { })));
configEntries.Add(AlwaysSwapMags = config.Bind(
InventorySection,
"Always Reload Magazines In-Place",
false,
new ConfigDescription(
"Always reload magazines in-place, even if there's space not to. Note that in-place reloads are slower.",
null,
new ConfigurationManagerAttributes { })));
configEntries.Add(SwapImpossibleContainers = config.Bind(
InventorySection,
"Swap with Incompatible Containers",
@@ -479,6 +533,15 @@ internal class Settings
null,
new ConfigurationManagerAttributes { })));
configEntries.Add(DefaultSortingTableBind = config.Bind(
InventorySection,
"Shift-Click to Sorting Table",
true,
new ConfigDescription(
"This setting lets you enable/disable the default Tarkov behavior of shift-clicking items to transfer them to the sorting table.",
null,
new ConfigurationManagerAttributes { IsAdvanced = true })));
configEntries.Add(ContextMenuOnRight = config.Bind(
InventorySection,
"Context Menu Flyout on Right",
@@ -635,6 +698,15 @@ internal class Settings
null,
new ConfigurationManagerAttributes { })));
configEntries.Add(ShowBarterIcons = config.Bind(
FleaMarketSection,
"Show Barter Icons",
true,
new ConfigDescription(
"Show item icons for barters instead of the generic barter icon",
null,
new ConfigurationManagerAttributes { })));
configEntries.Add(EnableSlotSearch = config.Bind(
FleaMarketSection,
"Enable Linked Slot Search",
@@ -653,6 +725,15 @@ internal class Settings
null,
new ConfigurationManagerAttributes { })));
configEntries.Add(ClearFiltersOnSearch = config.Bind(
FleaMarketSection,
"Clear Filters on Search",
true,
new ConfigDescription(
"Pressing Enter after typing in the flea search bar will clear non-default filters",
null,
new ConfigurationManagerAttributes { })));
configEntries.Add(ShowRequiredQuest = config.Bind(
FleaMarketSection,
"Show Required Quest for Locked Offers",
@@ -793,10 +874,25 @@ public static class SettingExtensions
configEntry.SettingChanged += (_, _) => onChange(configEntry.Value);
}
public static void Bind<T>(this ConfigEntry<T> configEntry, Action<T> onChange)
{
configEntry.Subscribe(onChange);
onChange(configEntry.Value);
}
// KeyboardShortcut methods return false if any other key is down
public static bool IsDownIgnoreOthers(this KeyboardShortcut shortcut)
{
return Input.GetKeyDown(shortcut.MainKey) && shortcut.Modifiers.All(Input.GetKey);
}
public static bool IsPressedIgnoreOthers(this KeyboardShortcut shortcut)
{
return Input.GetKey(shortcut.MainKey) && shortcut.Modifiers.All(Input.GetKey);
}
public static bool IsUpIgnoreOthers(this KeyboardShortcut shortcut)
{
return Input.GetKeyUp(shortcut.MainKey) && shortcut.Modifiers.All(Input.GetKey);
}
}

View File

@@ -106,4 +106,30 @@ public static class Sorter
return operation;
}
// Recreation of InteractionsHandlerClass.smethod_0, but without the out type being Stackable.
// minimumStackSpace of 0 means complete merge only, i.e. mininumStackSpace = itemToMerge.StackObjectCount
public static bool FindStackForMerge(IEnumerable<EFT.InventoryLogic.IContainer> containers, Item itemToMerge, out Item mergeableItem, int minimumStackSpace = 0)
{
if (minimumStackSpace <= 0)
{
minimumStackSpace = itemToMerge.StackObjectsCount;
}
bool ignoreSpawnedInSession = itemToMerge.Template switch
{
MoneyClass _ => Settings.MergeFIRMoney.Value,
AmmoTemplate _ => Settings.MergeFIRMoney.Value,
_ => Settings.MergeFIROther.Value,
};
mergeableItem = containers.SelectMany(x => x.Items)
.Where(x => x != itemToMerge)
.Where(x => x.TemplateId == itemToMerge.TemplateId)
.Where(x => ignoreSpawnedInSession || x.SpawnedInSession == itemToMerge.SpawnedInSession)
.Where(x => x.StackObjectsCount < x.StackMaxSize)
.FirstOrDefault(x => minimumStackSpace <= x.StackMaxSize - x.StackObjectsCount);
return mergeableItem != null;
}
}

View File

@@ -4,7 +4,7 @@
<TargetFramework>net471</TargetFramework>
<AssemblyName>Tyfon.UIFixes</AssemblyName>
<Description>SPT UI Fixes</Description>
<Version>2.2.2</Version>
<Version>2.3.1</Version>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<LangVersion>latest</LangVersion>
<Configurations>Debug;Release</Configurations>
@@ -104,8 +104,7 @@
xcopy /F /Y "$(TargetPath)" "$(ProjectDir)dist\BepInEx\plugins\"
xcopy /F /Y /S "$(ProjectDir)dist\BepInEx" "$(ProjectDir)$(PathToSPT)\BepInEx"
xcopy /F /Y /S "$(ProjectDir)dist\user" "$(ProjectDir)$(PathToSPT)\user"
7z a -t7z $(TargetName.Replace(".", "-"))-$(Version).7z $(ProjectDir)dist\BepInEx $(ProjectDir)dist\user
move /Y $(TargetName.Replace(".", "-"))-$(Version).7z dist\
7z a -tzip dist\$(TargetName.Replace(".", "-"))-$(Version).zip $(ProjectDir)dist\BepInEx $(ProjectDir)dist\user
)' />
</Target>
</Project>

View File

@@ -38,7 +38,7 @@ import ignore from "ignore";
import archiver from "archiver";
import winston from "winston";
const sptPath = "/SPT/3.9.2-debug";
const sptPath = "/SPT/3.9.3-debug";
// Get the command line arguments to determine whether to use verbose logging.
const args = process.argv.slice(2);

View File

@@ -1,6 +1,6 @@
{
"name": "uifixes",
"version": "2.2.2",
"version": "2.3.1",
"main": "src/mod.js",
"license": "MIT",
"author": "Tyfon",

View File

@@ -13,6 +13,8 @@ import type { ICloner } from "@spt/utils/cloners/ICloner";
import { RagfairLinkedSlotItemService } from "./RagfairLinkedSlotItemService";
import config from "../config/config.json";
import { RagfairOfferGenerator } from "@spt/generators/RagfairOfferGenerator";
import { IRagfairOffer } from "@spt/models/eft/ragfair/IRagfairOffer";
class UIFixes implements IPreSptLoadMod {
private databaseService: DatabaseService;
@@ -50,6 +52,36 @@ class UIFixes implements IPreSptLoadMod {
{ frequency: "Always" }
);
// Trader offers with dogtag barter - fixed in next SPT release *after* 3.9.3
container.afterResolution(
"RagfairOfferGenerator",
(_, ragfairOfferGenerator: RagfairOfferGenerator) => {
const original = ragfairOfferGenerator["createOffer"]; // By name because protected
ragfairOfferGenerator["createOffer"] = (userID, time, items, barterScheme, loyalLevel, isPackOffer) => {
const offer: IRagfairOffer = original.call(
ragfairOfferGenerator,
userID,
time,
items,
barterScheme,
loyalLevel,
isPackOffer
);
for (let i = 0; i < offer.requirements.length; i++) {
if (barterScheme[i]["level"] !== undefined) {
offer.requirements[i]["level"] = barterScheme[i]["level"];
offer.requirements[i]["side"] = barterScheme[i]["side"];
}
}
return offer;
};
},
{ frequency: "Always" }
);
// Better tool return - starting production
if (config.putToolsBack) {
container.afterResolution(
@@ -197,4 +229,4 @@ class UIFixes implements IPreSptLoadMod {
}
}
module.exports = { mod: new UIFixes() };
export const mod = new UIFixes();

View File

@@ -1,9 +1,9 @@
{
"compilerOptions": {
"allowJs": true,
"module": "CommonJS",
"module": "NodeNext",
"target": "ES2022",
"moduleResolution": "Node10",
"moduleResolution": "NodeNext",
"esModuleInterop": true,
"downlevelIteration": true,
"experimentalDecorators": true,