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 callback) { Item boundItem = __instance.InventoryControllerClass.Inventory.FastAccess.GetBoundItem(quickSlot); if (boundItem == null) { return true; } LightComponent lightComponent = boundItem.GetItemComponent(); if (lightComponent != null) { ToggleLight(__instance, boundItem, lightComponent); callback(null); return false; } NightVisionComponent nightVisionComponent = boundItem.GetItemComponent(); if (nightVisionComponent != null) { Item rootItem = boundItem.GetRootItemNotEquipment(); if (rootItem is Helmet helmet && __instance.Inventory.Equipment.GetSlot(EquipmentSlot.Headwear).ContainedItem == helmet) { __instance.InventoryControllerClass.TryRunNetworkTransaction( nightVisionComponent.Togglable.Set(!nightVisionComponent.Togglable.On, true, false)); } callback(null); return false; } return true; } private static void ToggleLight(Player player, Item boundItem, LightComponent lightComponent) { FirearmLightStateStruct lightState = new() { Id = lightComponent.Item.Id, 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.Instance.Control.Controller.method_3(); } } public class BindTacticalPatch : ModulePatch { protected override MethodBase GetTargetMethod() { return AccessTools.Method(typeof(GClass2818), nameof(GClass2818.Run)); } [PatchPostfix] public static void Postfix(InventoryControllerClass controller, Item item, EBoundItem index) { UpdateQuickbindType(item, index); // Will "save" control settings, running GClass1911.UpdateInput, which will set (or unset) toggle/hold behavior Singleton.Instance.Control.Controller.method_3(); } } public class UnbindTacticalPatch : ModulePatch { protected override MethodBase GetTargetMethod() { return AccessTools.Method(typeof(GClass2819), nameof(GClass2819.Run)); } [PatchPostfix] public static void Postfix(InventoryControllerClass controller, Item item, EBoundItem index) { Quickbind.SetType(index, Quickbind.ItemType.Other); // Will "save" control settings, running GClass1911.UpdateInput, which will set (or unset) toggle/hold behavior Singleton.Instance.Control.Controller.method_3(); } } private static bool IsEquippedTacticalDevice(InventoryControllerClass inventoryController, Item item) { LightComponent lightComponent = item.GetItemComponent(); NightVisionComponent nightVisionComponent = item.GetItemComponent(); 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(); if (lightComponent != null) { Item rootItem = item.GetRootItemNotEquipment(); if (rootItem is Weapon) { Quickbind.SetType(index, Quickbind.ItemType.Tactical); return; } if (rootItem is Helmet) { Quickbind.SetType(index, Quickbind.ItemType.Headlight); return; } } NightVisionComponent nvComponent = item.GetItemComponent(); if (nvComponent != null) { Quickbind.SetType(index, Quickbind.ItemType.NightVision); return; } Quickbind.SetType(index, Quickbind.ItemType.Other); } } public static class Quickbind { public enum ItemType { Other, Tactical, Headlight, NightVision } private static readonly Dictionary TacticalQuickbinds = new() { { EBoundItem.Item4, ItemType.Other }, { EBoundItem.Item5, ItemType.Other }, { EBoundItem.Item6, ItemType.Other }, { EBoundItem.Item7, ItemType.Other }, { EBoundItem.Item8, ItemType.Other }, { EBoundItem.Item9, ItemType.Other }, { EBoundItem.Item10, ItemType.Other }, }; public static ItemType GetType(EBoundItem index) => TacticalQuickbinds[index]; public static void SetType(EBoundItem index, ItemType type) => TacticalQuickbinds[index] = type; public static ItemType GetType(EGameKey gameKey) { int offset = gameKey - EGameKey.Slot4; return GetType(EBoundItem.Item4 + offset); } }