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 __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 __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; } } }